| | |
| | | <template> |
| | | <el-dialog v-model="visible" title="生成应收账单" width="1000px" destroy-on-close @closed="handleClosed"> |
| | | <el-dialog v-model="visible" :title="type === 'receivable' ? '生成应收账单' : '生成应付账单'" width="1000px" destroy-on-close |
| | | @closed="handleClosed"> |
| | | <div class="dialog-content"> |
| | | <h3 class="section-title">是否根据以下数据生成应收账单</h3> |
| | | <h3 class="section-title">是否根据以下数据生成{{ type === 'receivable' ? '应收' : '应付' }}账单</h3> |
| | | |
| | | <el-form :model="formData" ref="formRef" label-position="left" label-width="80px"> |
| | | <el-row :gutter="20"> |
| | |
| | | </el-col> |
| | | </el-row> |
| | | </el-form> |
| | | |
| | | <el-descriptions :column="3" border class="mb-6"> |
| | | <el-descriptions-item label="单据数量"> |
| | | {{ statistics.documentCount }} |
| | | <el-descriptions-item label="单据数量">{{ statistics.documentCount }}</el-descriptions-item> |
| | | |
| | | <el-descriptions-item label="汇率 (港币兑人民币)">{{ statistics.rate }}</el-descriptions-item> |
| | | <el-descriptions-item label="汇率 (人民币兑港币)">{{ statistics.rateRmb }}</el-descriptions-item> |
| | | |
| | | |
| | | <el-descriptions-item :label="type === 'receivable' ? '应收人民币' : '应付人民币'"> |
| | | <span class="text-bold">{{ statistics.receivable }}</span> |
| | | </el-descriptions-item> |
| | | <el-descriptions-item label="汇率 (港币兑人民币)"> |
| | | {{ statistics.rate }} |
| | | <el-descriptions-item :label="type === 'receivable' ? '应收港币' : '应付港币'"> |
| | | <span class="text-bold">{{ statistics.amountReceivable }}</span> |
| | | </el-descriptions-item> |
| | | <!-- <el-descriptions-item label="汇率 (人民币兑港币)"> |
| | | {{ statistics.rateInverse || '-' }} |
| | | </el-descriptions-item> --> |
| | | <el-descriptions-item label="应收金额"> |
| | | <span class="text-bold">{{ statistics.totalReceivableAmount }}</span> |
| | | </el-descriptions-item> |
| | | <el-descriptions-item label="应收金额 (以人民币计)"> |
| | | <el-descriptions-item :label="(type === 'receivable' ? '应收总金额' : '应付总金额') + ' (以人民币计)'"> |
| | | <span class="text-primary">{{ statistics.totalAmountRmb }}</span> |
| | | </el-descriptions-item> |
| | | <el-descriptions-item label="应收金额 (以港币计)"> |
| | | |
| | | <el-descriptions-item :label="(type === 'receivable' ? '应收总金额' : '应付总金额') + ' (以港币计)'"> |
| | | <span class="text-success">{{ statistics.totalAmountHkd }}</span> |
| | | </el-descriptions-item> |
| | | |
| | | |
| | | </el-descriptions> |
| | | |
| | | <div class="table-header"> |
| | |
| | | <template #default="scope"> |
| | | {{ dictFormat(sys_system, scope.row.sourceSystem) }} |
| | | </template> |
| | | </el-table-column><!-- sys_system --> |
| | | </el-table-column> |
| | | <el-table-column prop="businessSector" align="center" label="业务板块" width="120"> |
| | | <template #default="scope"> |
| | | {{ dictFormat(sys_business, scope.row.businessSector) }} |
| | | </template> |
| | | </el-table-column> |
| | | <!-- sys_business --> |
| | | <el-table-column prop="documentType" align="center" label="单据类型" width="120"> |
| | | <template #default="scope"> |
| | | {{ dictFormat(sys_receipts, scope.row.documentType) }} |
| | | </template> |
| | | </el-table-column><!-- sys_receipts --> |
| | | </el-table-column> |
| | | <el-table-column prop="documentNo" align="center" label="单据编号" width="150" /> |
| | | <el-table-column prop="customerName" align="center" label="客户名称" width="150" /> |
| | | <el-table-column :prop="type === 'receivable' ? 'customerName' : 'supplierName'" align="center" |
| | | :label="type === 'receivable' ? '客户名称' : '供应商名称'" width="150" /> |
| | | <el-table-column prop="projectName" align="center" label="项目名称" width="150" /> |
| | | <el-table-column prop="receivableAmount" align="center" label="应收金额" /> |
| | | <el-table-column :prop="type === 'receivable' ? 'receivableAmount' : 'payableAmount'" align="center" |
| | | :label="type === 'receivable' ? '应收金额' : '应付金额'" /> |
| | | <el-table-column prop="currency" align="center" label="币制" width="100"> |
| | | <template #default="scope"> |
| | | {{ dictFormat(sys_currency, scope.row.currency) }} |
| | |
| | | const dictFormat = (dict: any, value: any) => { |
| | | return proxy.selectDictLabel(dict, value); |
| | | } |
| | | // 增加类型区分 |
| | | const type = ref<'receivable' | 'payable'>('receivable'); |
| | | // 定义接口结构 |
| | | interface StatisticsData { |
| | | documentCount: number; |
| | |
| | | const emit = defineEmits(['confirm']); |
| | | |
| | | // 暴露给父组件的打开方法 |
| | | const open = (data: any, selectionList: any[]) => { |
| | | // 修改 open 方法,增加 mode 参数 |
| | | const open = (data: any, selectionList: any[], mode: 'receivable' | 'payable' = 'receivable') => { |
| | | type.value = mode; |
| | | visible.value = true; |
| | | |
| | | if (data) { |
| | | Object.assign(statistics.value, data); |
| | | |
| | | // 1. 处理明细数据拆分(你原有的逻辑) |
| | | let processedList = []; |
| | | if (selectionList && selectionList.length > 0) { |
| | | detailList.value = selectionList.flatMap(item => { |
| | | if (!item.receivableAmountStr) return [item]; |
| | | |
| | | // 1. 拆分多个币种字符串 |
| | | const amountParts = item.receivableAmountStr.trim().split(/\s+/); |
| | | |
| | | processedList = selectionList.flatMap(item => { |
| | | const amountStr = mode === 'receivable' ? item.receivableAmountStr : item.payableAmountStr; |
| | | if (!amountStr) return [item]; |
| | | const amountParts = amountStr.trim().split(/\s+/); |
| | | return amountParts.map(part => { |
| | | // 2. 正则解析提取数值和币种名称 |
| | | // ([\d.]+) 匹配数字和小数点 |
| | | // ([\u4e00-\u9fa5]+) 匹配中文字符(币种) |
| | | const match = part.match(/([\d.]+)([\u4e00-\u9fa5]+)/); |
| | | |
| | | let amount = item.receivableAmount; // 默认值 |
| | | let currencyValue = item.currency; // 默认值 |
| | | |
| | | let amount = mode === 'receivable' ? item.receivableAmount : item.payableAmount; |
| | | let currencyValue = item.currency; |
| | | if (match) { |
| | | amount = parseFloat(match[1]); // 提取的数字 |
| | | const currencyName = match[2]; // 提取的币种文字,如 "港币" |
| | | |
| | | // 3. 根据提取的文字匹配字典中的 Value |
| | | // 假设字典 sys_currency.value: 0 是人民币, 1 是港币 (请根据您实际字典值调整) |
| | | if (currencyName.includes('人民币')) { |
| | | currencyValue = 'RMB'; // 对应字典的人民币value |
| | | } else if (currencyName.includes('港币')) { |
| | | currencyValue = 'HKD'; // 对应字典的港币value |
| | | } |
| | | amount = parseFloat(match[1]); |
| | | const currencyName = match[2]; |
| | | if (currencyName.includes('人民币')) currencyValue = 'RMB'; |
| | | else if (currencyName.includes('港币')) currencyValue = 'HKD'; |
| | | } |
| | | |
| | | // 4. 返回新对象,覆盖金额和币制 |
| | | return { |
| | | ...item, |
| | | receivableAmount: amount, // 赋值提取的数字 |
| | | currency: currencyValue, // 赋值匹配到的字典ID |
| | | receivableAmountStr: part // 保持拆分后的文本 |
| | | }; |
| | | const newItem = { ...item }; |
| | | if (mode === 'receivable') newItem.receivableAmount = amount; |
| | | else newItem.payableAmount = amount; |
| | | newItem.currency = currencyValue; |
| | | return newItem; |
| | | }); |
| | | }); |
| | | } else { |
| | | detailList.value = []; |
| | | } |
| | | |
| | | detailList.value = processedList; |
| | | |
| | | // 2. 执行计算逻辑 |
| | | const calcRes = calculateStatistics(processedList, mode); |
| | | |
| | | const rateHkdToRmb = data.rate || 0; // 港币兑人民币 (例如 0.91) |
| | | |
| | | // 计算 人民币兑港币 (1 / 0.91),保留4位小数 |
| | | const rateRmbToHkd = rateHkdToRmb !== 0 |
| | | ? Number((1 / rateHkdToRmb).toFixed(4)) |
| | | : 0; |
| | | |
| | | // 3. 组装最终的 statistics 对象 |
| | | const currentRate = data.rate || 0; |
| | | statistics.value = { |
| | | ...data, |
| | | rateRmb: rateRmbToHkd, |
| | | ...calcRes, // 将计算出的 receivable 和 amountReceivable 合并进去 |
| | | // 自动计算总金额(以人民币为准 = 人民币部分 + 港币部分 * 汇率) |
| | | totalAmountRmb: Number((calcRes.receivable + calcRes.amountReceivable * currentRate).toFixed(2)), |
| | | // 自动计算总金额(以港币为准 = 港币部分 + 人民币部分 / 汇率) |
| | | totalAmountHkd: currentRate !== 0 |
| | | ? Number((calcRes.amountReceivable + calcRes.receivable / currentRate).toFixed(2)) |
| | | : 0 |
| | | }; |
| | | } |
| | | }; |
| | | |
| | | /** |
| | | * 计算账单统计数据 |
| | | * @param list 拆分后的明细列表 |
| | | * @param mode 当前模式:应收或应付 |
| | | */ |
| | | const calculateStatistics = (list: any[], mode: 'receivable' | 'payable') => { |
| | | let rmbTotal = 0; |
| | | let hkdTotal = 0; |
| | | |
| | | list.forEach(item => { |
| | | // 根据模式确定取值字段 |
| | | const amount = mode === 'receivable' ? item.receivableAmount : item.payableAmount; |
| | | const val = Number(amount) || 0; |
| | | |
| | | if (item.currency === 'RMB') { |
| | | rmbTotal += val; |
| | | } else if (item.currency === 'HKD') { |
| | | hkdTotal += val; |
| | | } |
| | | }); |
| | | |
| | | // 返回计算结果,保留两位小数(避免浮点误差) |
| | | return { |
| | | receivable: Number(rmbTotal.toFixed(2)), // 对应你模板中的 statistics.receivable |
| | | amountReceivable: Number(hkdTotal.toFixed(2)) // 对应你模板中的 statistics.amountReceivable |
| | | }; |
| | | }; |
| | | // 3. 确认生成按钮逻辑 |
| | | const handleConfirm = async () => { |
| | |
| | | width: 180px; |
| | | background-color: #f5f7fa; |
| | | } |
| | | ::v-deep .el-descriptions__body .el-descriptions__table.is-bordered .el-descriptions__cell { |
| | | border: var(--el-descriptions-table-border); |
| | | padding: 8px 11px; |
| | | width: 200px; |
| | | } |
| | | </style> |