<template>
|
<el-dialog v-model="visible" :title="type === 'receivable' ? '生成应收账单' : '生成应付账单'" width="1000px" destroy-on-close
|
@closed="handleClosed">
|
<div class="dialog-content">
|
<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 :span="12">
|
<el-form-item label="账单名称" prop="billName"
|
:rules="[{ required: true, message: '请输入账单名称', trigger: 'blur' }]">
|
<el-input v-model="formData.billName" placeholder="请输入账单名称" />
|
</el-form-item>
|
</el-col>
|
|
<el-col :span="12">
|
<el-form-item label="账单类型" prop="billType"
|
:rules="[{ required: true, message: '请选择账单类型', trigger: 'change' }]">
|
<el-radio-group v-model="formData.billType">
|
<el-radio :label="0">人民币账单</el-radio>
|
<el-radio :label="1">港币账单</el-radio>
|
</el-radio-group>
|
</el-form-item>
|
</el-col>
|
</el-row>
|
</el-form>
|
|
<el-descriptions :column="3" border class="mb-6">
|
<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="type === 'receivable' ? '应收港币' : '应付港币'">
|
<span class="text-bold">{{ statistics.amountReceivable }}</span>
|
</el-descriptions-item>
|
<el-descriptions-item :label="(type === 'receivable' ? '应收总金额' : '应付总金额') + ' (以人民币计)'">
|
<span class="text-primary">{{ statistics.totalAmountRmb }}</span>
|
</el-descriptions-item>
|
|
<el-descriptions-item :label="(type === 'receivable' ? '应收总金额' : '应付总金额') + ' (以港币计)'">
|
<span class="text-success">{{ statistics.totalAmountHkd }}</span>
|
</el-descriptions-item>
|
|
|
</el-descriptions>
|
|
<div class="table-header">
|
<h3 class="section-title">账单明细</h3>
|
</div>
|
<el-table :data="detailList" border stripe height="300px" style="width: 100%">
|
<el-table-column prop="systemNo" align="center" label="系统编号" width="140" />
|
<el-table-column prop="sourceSystem" align="center" label="来源系统" width="120">
|
<template #default="scope">
|
{{ dictFormat(sys_system, scope.row.sourceSystem) }}
|
</template>
|
</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>
|
<el-table-column prop="documentType" align="center" label="单据类型" width="120">
|
<template #default="scope">
|
{{ dictFormat(sys_receipts, scope.row.documentType) }}
|
</template>
|
</el-table-column>
|
<el-table-column prop="documentNo" 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="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) }}
|
</template>
|
</el-table-column>
|
</el-table>
|
</div>
|
|
<template #footer>
|
<el-button @click="cancel">取消</el-button>
|
<el-button type="primary" @click="handleConfirm">确认生成</el-button>
|
</template>
|
</el-dialog>
|
</template>
|
|
<script setup lang="ts">
|
import { ref, reactive } from 'vue';
|
import { ElMessage } from 'element-plus';
|
import CustomerSelectDialog from '../CustomerSelectDialog/index';
|
import useCurrentInstance from "@/utils/useCurrentInstance";
|
|
const { proxy } = useCurrentInstance()
|
const { sys_system, sys_business, sys_receipts, sys_supplier, sys_whether_type, sys_currency } = proxy.useDict(
|
'sys_system',
|
'sys_business',
|
'sys_receipts',
|
'sys_supplier',
|
'sys_whether_type', 'sys_currency'
|
)
|
const dictFormat = (dict: any, value: any) => {
|
return proxy.selectDictLabel(dict, value);
|
}
|
// 增加类型区分
|
const type = ref<'receivable' | 'payable'>('receivable');
|
// 定义接口结构
|
interface StatisticsData {
|
documentCount: number;
|
rate: number;
|
rateInverse?: number; // 转换后的反向汇率
|
totalReceivableAmount: number;
|
totalAmountRmb: number;
|
totalAmountHkd: number;
|
}
|
const formRef = ref();
|
const visible = ref(false);
|
const loading = ref(false);
|
const detailList = ref<any[]>([]);
|
const statistics = ref<Partial<StatisticsData>>({});
|
|
const formData = reactive({
|
billType: 0,
|
billName: ''
|
});
|
|
const emit = defineEmits(['confirm']);
|
|
// 暴露给父组件的打开方法
|
// 修改 open 方法,增加 mode 参数
|
const open = (data: any, selectionList: any[], mode: 'receivable' | 'payable' = 'receivable') => {
|
type.value = mode;
|
visible.value = true;
|
|
if (data) {
|
// 1. 处理明细数据拆分(你原有的逻辑)
|
let processedList = [];
|
if (selectionList && selectionList.length > 0) {
|
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 => {
|
const match = part.match(/([\d.]+)([\u4e00-\u9fa5]+)/);
|
let amount = mode === 'receivable' ? item.receivableAmount : item.payableAmount;
|
let currencyValue = item.currency;
|
if (match) {
|
amount = parseFloat(match[1]);
|
const currencyName = match[2];
|
if (currencyName.includes('人民币')) currencyValue = 'RMB';
|
else if (currencyName.includes('港币')) currencyValue = 'HKD';
|
}
|
const newItem = { ...item };
|
if (mode === 'receivable') newItem.receivableAmount = amount;
|
else newItem.payableAmount = amount;
|
newItem.currency = currencyValue;
|
return newItem;
|
});
|
});
|
}
|
|
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 () => {
|
if (!formRef.value) return;
|
|
// 手动触发表单校验
|
await formRef.value.validate((valid: boolean) => {
|
if (valid) {
|
loading.value = true;
|
emit('confirm', statistics.value, formData);
|
|
// 注意:通常在这里不直接关闭,而是等父组件请求成功后由父组件调用 cancel()
|
} else {
|
console.log('校验失败');
|
}
|
});
|
};
|
|
const handleClosed = () => {
|
// 重置数据
|
detailList.value = [];
|
statistics.value = {};
|
formData.billType = 0;
|
formData.billName = ''; // 记得重置名称
|
if (formRef.value) formRef.value.resetFields(); // 清除校验残余
|
};
|
const cancel = () => {
|
|
visible.value = false;
|
handleClosed()
|
};
|
// 暴露方法
|
defineExpose({ open, cancel });
|
</script>
|
|
<style scoped>
|
.section-title {
|
font-size: 16px;
|
font-weight: bold;
|
margin-bottom: 20px;
|
color: #303133;
|
}
|
|
.mb-6 {
|
margin-bottom: 24px;
|
}
|
|
.table-header {
|
margin-top: 20px;
|
border-top: 1px solid #ebeef5;
|
padding-top: 20px;
|
}
|
|
.text-bold {
|
font-weight: bold;
|
}
|
|
.text-primary {
|
color: #409eff;
|
font-weight: bold;
|
}
|
|
.text-success {
|
color: #67c23a;
|
font-weight: bold;
|
}
|
|
:deep(.el-descriptions__label) {
|
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>
|