431 lines
16 KiB
Vue
431 lines
16 KiB
Vue
<template>
|
||
<div class="app-container">
|
||
<!-- 汇率试算 -->
|
||
<el-card shadow="never" class="calc-card">
|
||
<template #header>
|
||
<div class="calc-header">
|
||
<span class="calc-title">汇率换算 / 本位币(CNY)折算</span>
|
||
<el-tag size="small" type="info">取数:正向配置 → 反向取倒数 → 经CNY交叉换算</el-tag>
|
||
</div>
|
||
</template>
|
||
<el-form :inline="true" @submit.prevent>
|
||
<el-form-item label="金额">
|
||
<el-input-number v-model="calc.amount" :precision="2" :step="100" :min="0" controls-position="right" style="width: 170px;" />
|
||
</el-form-item>
|
||
<el-form-item label="源币种">
|
||
<el-select v-model="calc.fromCurrency" filterable allow-create style="width: 110px;" placeholder="如 USD">
|
||
<el-option v-for="c in currencyOptions" :key="c" :label="c" :value="c" />
|
||
</el-select>
|
||
</el-form-item>
|
||
<el-form-item label="目标币种">
|
||
<el-select v-model="calc.toCurrency" filterable allow-create style="width: 110px;" placeholder="如 CNY">
|
||
<el-option v-for="c in currencyOptions" :key="c" :label="c" :value="c" />
|
||
</el-select>
|
||
</el-form-item>
|
||
<el-form-item label="汇率类型">
|
||
<el-select v-model="calc.rateType" style="width: 110px;">
|
||
<el-option v-for="d in rateTypeOptions" :key="d.value" :label="d.label" :value="d.value" />
|
||
</el-select>
|
||
</el-form-item>
|
||
<el-form-item label="业务日期">
|
||
<el-date-picker v-model="calc.bizDate" type="date" value-format="YYYY-MM-DD" placeholder="默认今天" style="width: 150px;" />
|
||
</el-form-item>
|
||
<el-form-item>
|
||
<el-button type="primary" icon="Money" @click="handleConvert">换 算</el-button>
|
||
<el-button type="success" plain @click="handleToBase">折本位币(CNY)</el-button>
|
||
</el-form-item>
|
||
</el-form>
|
||
<el-alert v-if="calcResult" :closable="false" type="success" class="calc-result">
|
||
<template #title>
|
||
<span>{{ calcResult.amount }} {{ calcResult.fromCurrency }}</span>
|
||
<span class="calc-eq">=</span>
|
||
<span class="calc-amount">{{ calcResult.result }} {{ calcResult.toCurrency }}</span>
|
||
<el-divider direction="vertical" />
|
||
<span style="font-size: 12px; color: #909399;">
|
||
汇率 1 {{ calcResult.fromCurrency }} = {{ calcResult.rate }} {{ calcResult.toCurrency }}
|
||
({{ ratePathText(calcResult.ratePath) }},{{ rateTypeText(calcResult.rateType) }},{{ calcResult.bizDate }})
|
||
</span>
|
||
</template>
|
||
</el-alert>
|
||
</el-card>
|
||
|
||
<!-- 查询 -->
|
||
<el-form :model="queryParams" ref="queryRef" :inline="true" v-show="showSearch">
|
||
<el-form-item label="源币种" prop="fromCurrency">
|
||
<el-select v-model="queryParams.fromCurrency" filterable allow-create clearable style="width: 120px;" placeholder="全部">
|
||
<el-option v-for="c in currencyOptions" :key="c" :label="c" :value="c" />
|
||
</el-select>
|
||
</el-form-item>
|
||
<el-form-item label="目标币种" prop="toCurrency">
|
||
<el-select v-model="queryParams.toCurrency" filterable allow-create clearable style="width: 120px;" placeholder="全部">
|
||
<el-option v-for="c in currencyOptions" :key="c" :label="c" :value="c" />
|
||
</el-select>
|
||
</el-form-item>
|
||
<el-form-item label="汇率类型" prop="rateType">
|
||
<el-select v-model="queryParams.rateType" clearable style="width: 120px;" placeholder="全部">
|
||
<el-option v-for="d in rateTypeOptions" :key="d.value" :label="d.label" :value="d.value" />
|
||
</el-select>
|
||
</el-form-item>
|
||
<el-form-item label="来源" prop="source">
|
||
<el-input v-model="queryParams.source" placeholder="汇率来源" clearable style="width: 140px;" @keyup.enter="handleQuery" />
|
||
</el-form-item>
|
||
<el-form-item label="生效日期">
|
||
<el-date-picker v-model="dateRange" value-format="YYYY-MM-DD" type="daterange" range-separator="-" start-placeholder="开始" end-placeholder="结束" />
|
||
</el-form-item>
|
||
<el-form-item>
|
||
<el-button type="primary" icon="Search" @click="handleQuery">搜索</el-button>
|
||
<el-button icon="Refresh" @click="resetQuery">重置</el-button>
|
||
</el-form-item>
|
||
</el-form>
|
||
|
||
<el-row :gutter="10" class="mb8">
|
||
<el-col :span="1.5">
|
||
<el-button type="primary" plain icon="Plus" @click="handleAdd" v-hasPermi="['data:exchangerate:add']">新增汇率</el-button>
|
||
</el-col>
|
||
<el-col :span="1.5">
|
||
<el-button type="warning" plain icon="Download" @click="handleExport" v-hasPermi="['data:exchangerate:export']">导出</el-button>
|
||
</el-col>
|
||
<right-toolbar v-model:showSearch="showSearch" @queryTable="getList"></right-toolbar>
|
||
</el-row>
|
||
|
||
<el-table v-loading="loading" :data="rateList" border>
|
||
<el-table-column label="ID" align="center" prop="rateId" width="70" />
|
||
<el-table-column label="币种方向" align="center" width="150">
|
||
<template #default="scope">
|
||
<el-tag size="small">{{ scope.row.fromCurrency }}</el-tag>
|
||
<el-icon class="rate-arrow"><Right /></el-icon>
|
||
<el-tag size="small" type="success">{{ scope.row.toCurrency }}</el-tag>
|
||
</template>
|
||
</el-table-column>
|
||
<el-table-column label="类型" align="center" prop="rateType" width="100">
|
||
<template #default="scope">
|
||
<el-tag :type="rateTypeTag(scope.row.rateType)">{{ rateTypeText(scope.row.rateType) }}</el-tag>
|
||
</template>
|
||
</el-table-column>
|
||
<el-table-column label="汇率值(1源=?目标)" align="center" prop="exchangeRate" width="170">
|
||
<template #default="scope">
|
||
<span class="rate-value">{{ formatRate(scope.row.exchangeRate) }}</span>
|
||
</template>
|
||
</el-table-column>
|
||
<el-table-column label="生效日期" align="center" prop="effectiveDate" width="110" />
|
||
<el-table-column label="失效日期" align="center" prop="expireDate" width="110">
|
||
<template #default="scope">{{ scope.row.expireDate || '长期' }}</template>
|
||
</el-table-column>
|
||
<el-table-column label="来源" align="center" prop="source" width="110" />
|
||
<el-table-column label="状态" align="center" width="90">
|
||
<template #default="scope">
|
||
<el-tag :type="isEffective(scope.row) ? 'success' : 'info'" size="small">
|
||
{{ isEffective(scope.row) ? '生效中' : '未生效' }}
|
||
</el-tag>
|
||
</template>
|
||
</el-table-column>
|
||
<el-table-column label="备注" align="center" prop="remark" show-overflow-tooltip min-width="140" />
|
||
<el-table-column label="操作" align="center" width="140" fixed="right">
|
||
<template #default="scope">
|
||
<el-button link type="primary" size="small" icon="Edit" @click="handleUpdate(scope.row)" v-hasPermi="['data:exchangerate:edit']">修改</el-button>
|
||
<el-button link type="danger" size="small" icon="Delete" @click="handleDelete(scope.row)" v-hasPermi="['data:exchangerate:remove']">删除</el-button>
|
||
</template>
|
||
</el-table-column>
|
||
</el-table>
|
||
|
||
<pagination v-show="total > 0" :total="total" v-model:page="queryParams.pageNum" v-model:limit="queryParams.pageSize" @pagination="getList" />
|
||
|
||
<!-- 新增/修改弹窗 -->
|
||
<el-dialog :title="title" v-model="open" width="600px" append-to-body>
|
||
<el-form ref="rateRef" :model="form" :rules="rules" label-width="110px">
|
||
<el-row>
|
||
<el-col :span="12">
|
||
<el-form-item label="源币种" prop="fromCurrency">
|
||
<el-select v-model="form.fromCurrency" filterable allow-create style="width: 100%;" placeholder="ISO三位码 如 USD">
|
||
<el-option v-for="c in currencyOptions" :key="c" :label="c" :value="c" />
|
||
</el-select>
|
||
</el-form-item>
|
||
</el-col>
|
||
<el-col :span="12">
|
||
<el-form-item label="目标币种" prop="toCurrency">
|
||
<el-select v-model="form.toCurrency" filterable allow-create style="width: 100%;" placeholder="ISO三位码 如 CNY">
|
||
<el-option v-for="c in currencyOptions" :key="c" :label="c" :value="c" />
|
||
</el-select>
|
||
</el-form-item>
|
||
</el-col>
|
||
<el-col :span="12">
|
||
<el-form-item label="汇率类型" prop="rateType">
|
||
<el-select v-model="form.rateType" style="width: 100%;">
|
||
<el-option v-for="d in rateTypeOptions" :key="d.value" :label="d.label" :value="d.value" />
|
||
</el-select>
|
||
</el-form-item>
|
||
</el-col>
|
||
<el-col :span="12">
|
||
<el-form-item label="汇率值" prop="exchangeRate">
|
||
<el-input-number v-model="form.exchangeRate" :precision="8" :step="0.0001" :min="0.00000001" controls-position="right" style="width: 100%;" placeholder="1源币种=?目标币种" />
|
||
</el-form-item>
|
||
</el-col>
|
||
<el-col :span="12">
|
||
<el-form-item label="生效日期" prop="effectiveDate">
|
||
<el-date-picker v-model="form.effectiveDate" type="date" value-format="YYYY-MM-DD" style="width: 100%;" />
|
||
</el-form-item>
|
||
</el-col>
|
||
<el-col :span="12">
|
||
<el-form-item label="失效日期" prop="expireDate">
|
||
<el-date-picker v-model="form.expireDate" type="date" value-format="YYYY-MM-DD" placeholder="空=长期有效" style="width: 100%;" />
|
||
</el-form-item>
|
||
</el-col>
|
||
<el-col :span="12">
|
||
<el-form-item label="汇率来源" prop="source">
|
||
<el-input v-model="form.source" placeholder="中国银行/央行/手工录入" />
|
||
</el-form-item>
|
||
</el-col>
|
||
<el-col :span="24">
|
||
<el-form-item label="备注" prop="remark">
|
||
<el-input v-model="form.remark" type="textarea" :rows="2" placeholder="请输入备注" />
|
||
</el-form-item>
|
||
</el-col>
|
||
</el-row>
|
||
</el-form>
|
||
<template #footer>
|
||
<el-button type="primary" @click="submitForm">确 定</el-button>
|
||
<el-button @click="open = false">取 消</el-button>
|
||
</template>
|
||
</el-dialog>
|
||
</div>
|
||
</template>
|
||
|
||
<script setup name="ExchangeRate">
|
||
import { listRegionOptions } from '@/api/data/region'
|
||
import {
|
||
listExchangeRate,
|
||
getExchangeRate,
|
||
addExchangeRate,
|
||
updateExchangeRate,
|
||
delExchangeRate,
|
||
convertExchangeRate,
|
||
convertToBase
|
||
} from '@/api/data/exchangeRate'
|
||
|
||
const { proxy } = getCurrentInstance()
|
||
|
||
const rateList = ref([])
|
||
const loading = ref(true)
|
||
const showSearch = ref(true)
|
||
const total = ref(0)
|
||
const dateRange = ref([])
|
||
const title = ref('')
|
||
const open = ref(false)
|
||
const calcResult = ref(null)
|
||
|
||
const rateTypeOptions = [
|
||
{ value: '0', label: '即期汇率' },
|
||
{ value: '1', label: '月初汇率' },
|
||
{ value: '2', label: '月末汇率' },
|
||
{ value: '3', label: '固定汇率' }
|
||
]
|
||
|
||
/** 常用币种兜底 + 从国家资料(币种)去重加载 */
|
||
const currencyOptions = ref(['CNY', 'USD', 'EUR', 'GBP', 'HKD', 'JPY'])
|
||
function loadCurrencyOptions() {
|
||
listRegionOptions().then(res => {
|
||
const codes = (res.data || [])
|
||
.map(r => r.currencyCode)
|
||
.filter(c => !!c)
|
||
currencyOptions.value = [...new Set([...currencyOptions.value, ...codes])]
|
||
}).catch(() => {})
|
||
}
|
||
|
||
const queryParams = reactive({
|
||
pageNum: 1,
|
||
pageSize: 10,
|
||
fromCurrency: '',
|
||
toCurrency: '',
|
||
rateType: '',
|
||
source: ''
|
||
})
|
||
|
||
const calc = reactive({
|
||
amount: 100,
|
||
fromCurrency: 'USD',
|
||
toCurrency: 'CNY',
|
||
rateType: '0',
|
||
bizDate: ''
|
||
})
|
||
|
||
const form = ref({})
|
||
const rules = {
|
||
fromCurrency: [{ required: true, message: '源币种不能为空', trigger: 'change' }],
|
||
toCurrency: [{ required: true, message: '目标币种不能为空', trigger: 'change' }],
|
||
rateType: [{ required: true, message: '请选择汇率类型', trigger: 'change' }],
|
||
exchangeRate: [{ required: true, message: '汇率值不能为空', trigger: 'blur' }],
|
||
effectiveDate: [{ required: true, message: '生效日期不能为空', trigger: 'change' }]
|
||
}
|
||
|
||
function rateTypeText(v) {
|
||
const o = rateTypeOptions.find(d => d.value === String(v))
|
||
return o ? o.label : v
|
||
}
|
||
function rateTypeTag(v) {
|
||
return { '0': 'danger', '1': 'warning', '2': '', '3': 'success' }[v] || 'info'
|
||
}
|
||
function formatRate(v) {
|
||
const num = Number(v)
|
||
return isNaN(num) ? '-' : num.toFixed(8).replace(/0+$/, '').replace(/\.$/, '')
|
||
}
|
||
function ratePathText(v) {
|
||
return { DIRECT: '正向配置', REVERSE: '反向取倒数', CROSS: '经CNY交叉换算', SAME: '同币种' }[v] || v
|
||
}
|
||
function isEffective(row) {
|
||
const today = proxy.parseTime(new Date(), '{y}-{m}-{d}')
|
||
return row.effectiveDate <= today && (!row.expireDate || row.expireDate >= today)
|
||
}
|
||
|
||
function getList() {
|
||
loading.value = true
|
||
listExchangeRate(proxy.addDateRange({ ...queryParams }, dateRange.value)).then(res => {
|
||
rateList.value = res.rows
|
||
total.value = res.total
|
||
}).finally(() => {
|
||
loading.value = false
|
||
})
|
||
}
|
||
|
||
function handleQuery() {
|
||
queryParams.pageNum = 1
|
||
getList()
|
||
}
|
||
|
||
function resetQuery() {
|
||
dateRange.value = []
|
||
proxy.resetForm('queryRef')
|
||
handleQuery()
|
||
}
|
||
|
||
function reset() {
|
||
form.value = {
|
||
rateId: null,
|
||
fromCurrency: 'USD',
|
||
toCurrency: 'CNY',
|
||
rateType: '0',
|
||
exchangeRate: null,
|
||
effectiveDate: proxy.parseTime(new Date(), '{y}-{m}-{d}'),
|
||
expireDate: '',
|
||
source: '手工录入',
|
||
remark: ''
|
||
}
|
||
proxy.resetForm('rateRef')
|
||
}
|
||
|
||
function handleAdd() {
|
||
reset()
|
||
open.value = true
|
||
title.value = '新增汇率'
|
||
}
|
||
|
||
function handleUpdate(row) {
|
||
reset()
|
||
getExchangeRate(row.rateId).then(res => {
|
||
form.value = {
|
||
...res.data,
|
||
expireDate: res.data.expireDate || ''
|
||
}
|
||
open.value = true
|
||
title.value = '修改汇率'
|
||
})
|
||
}
|
||
|
||
function submitForm() {
|
||
proxy.$refs.rateRef.validate(valid => {
|
||
if (!valid) return
|
||
const data = { ...form.value }
|
||
data.fromCurrency = (data.fromCurrency || '').toUpperCase()
|
||
data.toCurrency = (data.toCurrency || '').toUpperCase()
|
||
if (!data.expireDate) data.expireDate = null
|
||
const action = data.rateId ? updateExchangeRate(data) : addExchangeRate(data)
|
||
action.then(() => {
|
||
proxy.$modal.msgSuccess(data.rateId ? '修改成功' : '新增成功')
|
||
open.value = false
|
||
getList()
|
||
})
|
||
})
|
||
}
|
||
|
||
function handleDelete(row) {
|
||
proxy.$modal.confirm('是否确认删除汇率【' + row.fromCurrency + ' → ' + row.toCurrency + ' / ' + rateTypeText(row.rateType) + '】?').then(() => {
|
||
return delExchangeRate(row.rateId)
|
||
}).then(() => {
|
||
getList()
|
||
proxy.$modal.msgSuccess('删除成功')
|
||
}).catch(() => {})
|
||
}
|
||
|
||
function handleExport() {
|
||
proxy.download('/data/exchangerate/export', { ...queryParams }, `exchange_rate_${new Date().getTime()}.xlsx`)
|
||
}
|
||
|
||
function handleConvert() {
|
||
if (!calc.amount && calc.amount !== 0) {
|
||
proxy.$modal.msgWarning('请输入金额')
|
||
return
|
||
}
|
||
convertExchangeRate({
|
||
amount: calc.amount,
|
||
fromCurrency: calc.fromCurrency,
|
||
toCurrency: calc.toCurrency,
|
||
rateType: calc.rateType,
|
||
bizDate: calc.bizDate || undefined
|
||
}).then(res => {
|
||
calcResult.value = res.data
|
||
})
|
||
}
|
||
|
||
function handleToBase() {
|
||
if (!calc.amount && calc.amount !== 0) {
|
||
proxy.$modal.msgWarning('请输入金额')
|
||
return
|
||
}
|
||
convertToBase({
|
||
amount: calc.amount,
|
||
currency: calc.fromCurrency,
|
||
rateType: calc.rateType,
|
||
bizDate: calc.bizDate || undefined
|
||
}).then(res => {
|
||
calcResult.value = res.data
|
||
})
|
||
}
|
||
|
||
loadCurrencyOptions()
|
||
getList()
|
||
</script>
|
||
|
||
<style scoped>
|
||
.calc-card {
|
||
margin-bottom: 12px;
|
||
}
|
||
.calc-header {
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: space-between;
|
||
}
|
||
.calc-title {
|
||
font-weight: 600;
|
||
}
|
||
.calc-result {
|
||
margin-top: 4px;
|
||
}
|
||
.calc-eq {
|
||
margin: 0 8px;
|
||
color: #909399;
|
||
}
|
||
.calc-amount {
|
||
font-size: 18px;
|
||
font-weight: 700;
|
||
color: var(--el-color-success);
|
||
}
|
||
.rate-arrow {
|
||
margin: 0 4px;
|
||
vertical-align: middle;
|
||
color: #909399;
|
||
}
|
||
.rate-value {
|
||
font-weight: 600;
|
||
color: var(--el-color-primary);
|
||
}
|
||
</style>
|