<template>
|
|
<el-dialog v-model="mainVisible" :title="`${typeText}费用明细`" width="1000px" destroy-on-close>
|
<el-form :model="mainForm" label-width="120px" :rules="mainRules" ref="mainFormRef">
|
<el-row :gutter="20">
|
<el-col :span="8">
|
<el-form-item label="来源系统" prop="sourceSystem">
|
<el-select v-model="mainForm.sourceSystem" placeholder="请选择来源系统" style="width: 100%;" clearable>
|
<el-option v-for="dict in sys_system" :key="dict.value" :label="dict.label"
|
:value="dict.value"></el-option>
|
</el-select>
|
</el-form-item>
|
</el-col>
|
<el-col :span="8">
|
<el-form-item label="业务板块" prop="businessSector">
|
<el-select v-model="mainForm.businessSector" placeholder="请选择业务板块" style="width: 100%;" clearable>
|
<el-option v-for="dict in sys_business" :key="dict.value" :label="dict.label"
|
:value="dict.value"></el-option>
|
</el-select>
|
</el-form-item>
|
</el-col>
|
<el-col :span="8">
|
<el-form-item label="单据类型" prop="documentType">
|
<el-select v-model="mainForm.documentType" placeholder="请选择单据类型" style="width: 100%;" clearable>
|
<el-option v-for="dict in sys_receipts" :key="dict.value" :label="dict.label"
|
:value="dict.value"></el-option>
|
</el-select>
|
</el-form-item>
|
</el-col>
|
</el-row>
|
|
<el-row :gutter="20">
|
<el-col :span="8">
|
<el-form-item label="单据编号" prop="documentNo">
|
<el-input v-model="mainForm.documentNo" />
|
</el-form-item>
|
</el-col>
|
|
<el-col :span="8" v-if="typeText == '应收'" >
|
<el-form-item label="客户名称" prop="customerName">
|
<!-- <el-select v-model="mainForm.customerName" placeholder="请选择供应商名称" style="width: 100%;" clearable>
|
<el-option v-for="dict in sys_supplier" :key="dict.value" :label="dict.label"
|
:value="dict.value"></el-option>
|
</el-select> -->
|
<el-input v-model="mainForm.customerName" placeholder="请选择 客户名称" readonly @click="handleCustonerClick"
|
class="clickable-input">
|
<template #suffix>
|
<el-icon @click="handleCustonerClick" class="search-icon">
|
<Search />
|
</el-icon>
|
</template>
|
</el-input>
|
</el-form-item>
|
</el-col>
|
|
<el-col :span="8" v-if="typeText == '应付'" >
|
<el-form-item label="供应商名称" prop="customerName">
|
<!-- <el-select v-model="mainForm.customerName" placeholder="请选择供应商名称" style="width: 100%;" clearable>
|
<el-option v-for="dict in sys_supplier" :key="dict.value" :label="dict.label"
|
:value="dict.value"></el-option>
|
</el-select> -->
|
<el-input v-model="mainForm.customerName" placeholder="请选择 供应商名称" readonly @click="handleCustonerClick"
|
class="clickable-input">
|
<template #suffix>
|
<el-icon @click="handleCustonerClick" class="search-icon">
|
<Search />
|
</el-icon>
|
</template>
|
</el-input>
|
</el-form-item>
|
</el-col>
|
<el-col :span="8">
|
<el-form-item label="项目名称" prop="projectName">
|
<el-input v-model="mainForm.projectName" />
|
</el-form-item>
|
</el-col>
|
</el-row>
|
|
<el-row :gutter="20">
|
<el-col :span="8">
|
<el-form-item label="业务发生时间" prop="businessTime">
|
<el-date-picker v-model="mainForm.businessTime" value-format="YYYY-MM-DD" type="date" class="w-full" />
|
</el-form-item>
|
</el-col>
|
<el-col :span="8" v-if="typeText == '应收'">
|
<el-form-item label="应收确认时间" prop="receivableConfirmTime">
|
<el-date-picker v-model="mainForm.receivableConfirmTime" value-format="YYYY-MM-DD" type="date"
|
class="w-full" />
|
</el-form-item>
|
</el-col>
|
<el-col :span="8" v-if="typeText == '应付'">
|
<el-form-item label="应付确认时间" prop="payableConfirmTime">
|
<el-date-picker v-model="mainForm.payableConfirmTime" value-format="YYYY-MM-DD" type="date"
|
class="w-full" />
|
</el-form-item>
|
</el-col>
|
<el-col :span="8" v-if="typeText == '应收'">
|
<el-form-item label="应收金额" prop="receivableAmount">
|
<el-input-number v-model="mainForm.receivableAmount" disabled :min="0" class="w-full" />
|
</el-form-item>
|
</el-col>
|
<el-col :span="8" v-if="typeText == '应付'">
|
<el-form-item label="应付金额" prop="payableAmount">
|
<el-input-number v-model="mainForm.payableAmount" :min="0" class="w-full" />
|
</el-form-item>
|
</el-col>
|
<el-col :span="8">
|
<el-form-item label="是否内部结算" prop="isInternalSettlement">
|
<el-select v-model="mainForm.isInternalSettlement" placeholder="请选择是否内部结算" style="width: 100%;" clearable>
|
<el-option v-for="dict in sys_whether_type" :key="dict.value" :label="dict.label"
|
:value="dict.value"></el-option>
|
</el-select>
|
</el-form-item>
|
</el-col>
|
<el-col :span="8" v-if="mainForm.isInternalSettlement == 1">
|
<el-form-item label="内部结算单位" prop="internalSettlementUnit">
|
<el-input v-model="mainForm.internalSettlementUnit" placeholder="请选择 内部结算单位" readonly
|
@click="handleInputClick" class="clickable-input">
|
<template #suffix>
|
<el-icon @click="handleInputClick" class="search-icon">
|
<Search />
|
</el-icon>
|
</template>
|
</el-input>
|
</el-form-item>
|
</el-col>
|
|
</el-row>
|
</el-form>
|
<div class="divider-title">费用明细</div>
|
<div class="mb-4">
|
<el-button type="primary" @click="handleOpenItemEditor">新增</el-button>
|
</div>
|
|
<!-- 应收费用明细列表 receivableFeeDetailList-->
|
<el-table :data="receivableFeeDetailList" border stripe style="width: 100%">
|
<el-table-column prop="feeType" label="费用类型" >
|
<template #default="scope">
|
{{ dictFormat(fee_type, scope.row.feeType) }}
|
</template>
|
</el-table-column>
|
<el-table-column prop="feeName" label="费用名称" />
|
<el-table-column prop="billingUnit" label="计费单位">
|
<template #default="scope">
|
{{ dictFormat(sys_unit, scope.row.billingUnit)}}
|
</template>
|
</el-table-column>
|
<el-table-column prop="unitPrice" label="计费单价" />
|
<el-table-column prop="billingAmount" label="计费金额" />
|
<el-table-column prop="actualAmount" label="实收金额" />
|
<el-table-column prop="currency" label="币制">
|
<template #default="scope">
|
{{ dictFormat(sys_currency, scope.row.currency)}}
|
</template>
|
</el-table-column>
|
<el-table-column prop="createTime" label="费用登记时间" width="180" />
|
<el-table-column label="操作" width="120">
|
<!-- <template #default="scope">
|
<el-button link type="primary">编辑</el-button>
|
<el-button link type="danger" @click="receivableFeeDetailList.splice(scope.$index, 1)">删除</el-button>
|
|
</template> -->
|
<template #default="scope">
|
<el-button type="text" icon="Edit" @click="handleUpdates(scope.row, scope.$index)">编辑
|
</el-button>
|
<el-button type="text" @click="handleDelte(scope.row, scope.$index)">删除
|
</el-button>
|
</template>
|
</el-table-column>
|
</el-table>
|
|
<template #footer>
|
<el-button @click="canceleClick">取消</el-button>
|
<el-button type="primary" @click="handleMainSubmit">确定</el-button>
|
</template>
|
</el-dialog>
|
|
<el-dialog v-model="itemVisible" :title="`${typeText}费用明细`" width="900px" append-to-body>
|
<el-form :model="itemForm" label-width="100px" :rules="itemRules" ref="itemFormRef">
|
<el-row :gutter="20">
|
<el-col :span="8">
|
<el-form-item label="费用类型" prop="feeType">
|
<el-select v-model="itemForm.feeType" placeholder="请选择费用类型" style="width: 100%;" clearable>
|
<el-option v-for="dict in fee_type" :key="dict.value" :label="dict.label" :value="dict.value"></el-option>
|
</el-select>
|
</el-form-item>
|
</el-col>
|
<el-col :span="8">
|
<el-form-item label="费用名称" prop="feeName"><el-input v-model="itemForm.feeName" /></el-form-item>
|
</el-col>
|
|
|
<el-col :span="8">
|
<el-form-item label="计费单位" prop="billingUnit">
|
<el-select v-model="itemForm.billingUnit" placeholder="请选择计费单位" style="width: 100%;" clearable>
|
<el-option v-for="dict in sys_unit" :key="dict.value" :label="dict.label" :value="dict.value"></el-option>
|
</el-select>
|
</el-form-item>
|
</el-col>
|
</el-row>
|
<el-row :gutter="20">
|
<el-col :span="8">
|
<el-form-item label="计费单价" prop="unitPrice">
|
<el-input-number v-model="itemForm.unitPrice" :precision="4" @change="calcItemAmount" class="w-full" />
|
</el-form-item>
|
</el-col>
|
<el-col :span="8">
|
<el-form-item label="计费数量" prop="billingQuantity">
|
<el-input-number v-model="itemForm.billingQuantity" :precision="4" @change="calcItemAmount"
|
class="w-full" />
|
</el-form-item>
|
</el-col>
|
<el-col :span="8">
|
<el-form-item label="计费金额" prop="billingAmount"><el-input v-model="itemForm.billingAmount"
|
disabled /></el-form-item>
|
</el-col>
|
</el-row>
|
<el-row :gutter="20">
|
<el-col :span="8">
|
<el-form-item :label="`实${typeText.slice(1)}金额`" prop="actualAmount">
|
<el-input-number v-model="itemForm.actualAmount" :precision="2" class="w-full" />
|
</el-form-item>
|
</el-col>
|
<el-col :span="8">
|
<el-form-item label="币制" prop="currency">
|
<el-select v-model="itemForm.currency" placeholder="请选择币制" clearable>
|
<el-option v-for="dict in sys_currency" :key="dict.value" :label="dict.label"
|
:value="dict.value"></el-option>
|
</el-select>
|
|
</el-form-item>
|
</el-col>
|
</el-row>
|
</el-form>
|
<template #footer>
|
<el-button @click="itemVisible = false">取消</el-button>
|
<el-button type="primary" @click="handleItemConfirm">确定</el-button>
|
</template>
|
</el-dialog>
|
|
<EntitySelector ref="selectorRef" @selected="handleEntitySelected" />
|
|
<CustomerSelectDialog :visible="isCustomerSelectVisibleIshow" :default-selected-id="mainForm.customerId"
|
@confirm="handleCustomerSelect" @close="isCustomerSelectVisibleIshow = false" />
|
</template>
|
|
<script setup lang="ts">
|
import { ref, reactive, computed, watch } from 'vue';
|
import { ElMessage, type FormInstance } from 'element-plus';
|
import useCurrentInstance from '@/utils/useCurrentInstance'
|
import EntitySelector from '../EntitySelector/index.vue';
|
import CustomerSelectDialog from '../CustomerSelectDialog/index';
|
|
const { proxy } = useCurrentInstance()
|
const { sys_system, sys_business, sys_receipts, sys_supplier, sys_whether_type, fee_type, sys_currency, sys_unit } = proxy.useDict(
|
'sys_system',
|
'sys_business',
|
'sys_receipts',
|
'sys_supplier',
|
'sys_whether_type',
|
'fee_type', 'sys_currency', 'sys_unit'
|
)
|
const props = defineProps({
|
type: { type: String, default: 'receivable' }
|
});
|
const emit = defineEmits(['submit']);
|
const dictFormat = (dict: any, value: any) => {
|
return proxy.selectDictLabel(dict, value);
|
}
|
// 1. 弹窗控制与基础数据
|
const mainVisible = ref(false);
|
const itemVisible = ref(false);
|
const mainFormRef = ref<FormInstance>();
|
const itemFormRef = ref<FormInstance>();
|
const receivableFeeDetailList = ref<any[]>([]);
|
// 修改计算属性,兼容 dealWith 这种传值
|
const typeText = computed(() =>
|
(props.type === 'receivable' || props.type === 'income') ? '应收' : '应付'
|
);
|
// 2. 表单数据定义
|
const mainForm = reactive({
|
sourceSystem: '', businessSegment: '', docType: '',
|
docNo: '', customerName: '', projectName: '',
|
businessDate: '', confirmDate: '', totalAmount: 0, internalSettlementUnit: '', isInternalSettlement: 1
|
});
|
|
const itemForm = reactive({
|
feeType: '', feeName: '', billingUnit: '',
|
unitPrice: 0, billingQuantity: 0, billingAmount: '0.0000',
|
actualAmount: 1, currency: ''
|
});
|
|
// 3. 校验规则
|
const mainRules = reactive({
|
sourceSystem: [{ required: true, message: '请选择来源系统', trigger: 'change' }],
|
businessSegment: [{ required: true, message: '请选择业务板块', trigger: 'change' }],
|
documentType: [{ required: true, message: '请选择单据类型', trigger: 'change' }],
|
documentNo: [{ required: true, message: '请输入单据编号', trigger: 'blur' }],
|
receivableConfirmTime: [{ required: true, message: `请选择应收确认时间`, trigger: 'change' }],
|
receivableAmount: [{ required: true, message: `请输入应收金额`, trigger: 'blur' }],
|
businessTime: [{ required: true, message: '请选择业务发生时间', trigger: 'change' }],
|
internalSettlementUnit: [{ required: true, message: '请选择内部结算单位', trigger: 'change' }],
|
});
|
|
const itemRules = reactive({
|
feeType: [{ required: true, message: '请选择费用类型', trigger: 'change' }],
|
feeName: [{ required: true, message: '请输入费用名称', trigger: 'blur' }],
|
actualAmount: [{ required: true, message: '必填项', trigger: 'blur' }],
|
currency: [{ required: true, message: '请选择币制', trigger: 'change' }]
|
|
});
|
|
// 4. 计算逻辑
|
const calcItemAmount = () => {
|
const res = (itemForm.unitPrice || 0) * (itemForm.billingQuantity || 0);
|
itemForm.billingAmount = res.toFixed(4); // 四舍五入保留4位小数
|
};
|
const addIshow = ref(false);
|
// 5. 弹窗操作方法
|
const open = (data?: any) => {
|
mainVisible.value = true;
|
|
// 1. 初始化/重置数据
|
resetForm();
|
|
// 2. 如果有数据传入,则进行回显
|
if (data) {
|
// 假设 data 的结构就是 mainForm 需要的结构
|
Object.assign(mainForm, data);
|
// 确保后端返回的明细字段名与此一致
|
console.log();
|
|
if (typeText.value === '应付') {
|
receivableFeeDetailList.value = [...data.payableFeeDetailList];
|
} else if (typeText.value === '应收') {
|
receivableFeeDetailList.value = [...data.receivableFeeDetailList];
|
}
|
addIshow.value = false;
|
|
} else {
|
addIshow.value = true;
|
}
|
console.log(addIshow.value);
|
|
};
|
|
// 重置表单的辅助函数
|
const resetForm = () => {
|
if (mainFormRef.value) mainFormRef.value.resetFields();
|
receivableFeeDetailList.value = [];
|
Object.assign(mainForm, {
|
sourceSystem: '', businessSector: '', documentType: '',
|
documentNo: '', customerName: '', projectName: '',
|
businessTime: '', receivableConfirmTime: '', receivableAmount: 0, internalSettlementUnit: '', isInternalSettlement: '1'
|
});
|
};
|
|
const handleOpenItemEditor = () => {
|
// 重置子表单并打开
|
editingIndex.value = null; // 明确当前是新增模式
|
Object.assign(itemForm, {
|
feeType: '', feeName: '', billingUnit: '', unitPrice: 0, billingQuantity: 0, billingAmount: '0.0000', actualAmount: 1, currency: '',
|
internalSettlementUnit: '', isInternalSettlement: '1'
|
});
|
itemVisible.value = true;
|
};
|
|
const handleItemConfirm = async () => {
|
if (!itemFormRef.value) return;
|
await itemFormRef.value.validate((valid) => {
|
if (valid) {
|
// 点击确定,将表单数据 push 到主表格
|
// 准备好要存入的数据副本
|
const finalRowData = { ...itemForm };
|
|
if (editingIndex.value !== null) {
|
// 编辑模式:使用新的副本替换旧数据
|
receivableFeeDetailList.value.splice(editingIndex.value, 1, finalRowData);
|
} else {
|
// 新增模式:添加新数据
|
receivableFeeDetailList.value.push({
|
...finalRowData,
|
});
|
}
|
|
itemVisible.value = false;
|
editingIndex.value = null; // 记得重置索引
|
|
|
}
|
});
|
};
|
const canceleClick = () => {
|
mainVisible.value = false;
|
editingIndex.value = null; // 重置索引
|
}
|
|
const handleMainSubmit = async () => {
|
if (!mainFormRef.value) return;
|
try {
|
await mainFormRef.value.validate();
|
// mainForm.receivableFeeDetailList = receivableFeeDetailList.value;
|
emit('submit', mainForm, receivableFeeDetailList.value);
|
// mainVisible.value = false;
|
} catch (err) {
|
|
ElMessage.warning('表单验证失败');
|
}
|
};
|
const handleDelte = (row: any, index: any) => {
|
// const index = invoiceInfoList.value.findIndex((item: any) => item === row);
|
if (index > -1) {
|
receivableFeeDetailList.value.splice(index, 1);
|
}
|
}
|
const editingIndex = ref(null);
|
const handleUpdates = (row: any, index: any) => {
|
editingIndex.value = index;
|
// 使用解构赋值将行数据拷贝到 itemForm,确保 itemForm 是独立的
|
Object.assign(itemForm, { ...row });
|
itemVisible.value = true;
|
}
|
|
const selectorRef = ref(null)
|
const result = ref(null)
|
|
const handleInputClick = () => {
|
selectorRef.value.open(mainForm.internalSettlementUnit)
|
}
|
|
// 公共处理方法:获取选中的数据
|
const handleEntitySelected = (data) => {
|
mainForm.internalSettlementUnit = data.customerFullName
|
// 在这里可以将数据赋值给表单或其他逻辑
|
}
|
const isCustomerSelectVisibleIshow = ref(false);
|
|
const handleCustonerClick = () => {
|
isCustomerSelectVisibleIshow.value = true;
|
|
}
|
const handleCustomerSelect = (selectedCustomer) => {
|
mainForm.customerName = selectedCustomer.customerFullName;
|
mainForm.customerId = selectedCustomer.id; // 现在字段已声明,赋值有效
|
isCustomerSelectVisibleIshow.value = false;
|
};
|
// 监听费用明细列表的变化,自动计算总额并保留2位小数
|
watch(
|
() => receivableFeeDetailList.value,
|
(newList) => {
|
const total = newList.reduce((sum, item) => {
|
// 确保取到的是数值,如果为空则默认为 0
|
const amount = parseFloat(item.actualAmount) || 0;
|
return sum + amount;
|
}, 0);
|
|
// 计算结果保留2位小数
|
// parseFloat(...toFixed(2)) 是为了确保最后存入 mainForm 的是数字类型而非字符串
|
const formattedTotal = parseFloat(total.toFixed(2));
|
|
// 根据类型赋值
|
if (typeText.value === '应收') {
|
mainForm.receivableAmount = formattedTotal;
|
} else {
|
mainForm.payableAmount = formattedTotal;
|
}
|
},
|
{ deep: true, immediate: true } // immediate: true 确保初始加载数据时也能计算一次
|
);
|
defineExpose({ open, canceleClick });
|
</script>
|
|
<style scoped>
|
.w-full {
|
width: 100%;
|
}
|
|
.mb-4 {
|
margin-bottom: 1rem;
|
}
|
|
.divider-title {
|
font-weight: bold;
|
margin: 20px 0 15px;
|
border-left: 4px solid #409eff;
|
padding-left: 10px;
|
}
|
</style>
|