You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

852 lines
30 KiB

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

<template>
<div>
<!-- 付款登记-->
<xy-dialog title="付款登记" :is-show.sync="isShowPaymentRegistration" type="form" class="payment-registration"
:form="paymentRegistrationForm" :rules="paymentRegistrationRules" @submit="submit" ref="paymentRegistration">
<template v-slot:extraFormTop>
<div class="payment-registration-row">
<div class="payment-registration-row-title">受款单位</div>
<div class="payment-registration-row-content">{{contract.supply}}</div>
</div>
<div class="payment-registration-row">
<div class="payment-registration-row-title">合同名称</div>
<div class="payment-registration-row-content">{{contract.name}}</div>
</div>
<div class="payment-registration-row">
<div class="payment-registration-row-title">合同金额</div>
<div class="payment-registration-row-content">{{priceFormat(contract.money)}} </div>
</div>
<div style="display: flex">
<div class="payment-registration-row">
<div class="payment-registration-row-title">已申请金额</div>
<div class="payment-registration-row-content">{{totalApplyMoney()}} </div>
</div>
<div class="payment-registration-row">
<div class="payment-registration-row-title">已申请笔数</div>
<div class="payment-registration-row-content">{{payment.length}}</div>
</div>
</div>
<div style="display: flex">
<div class="payment-registration-row">
<div class="payment-registration-row-title">已付金额</div>
<div class="payment-registration-row-content">{{totalMoney()}} </div>
</div>
<div class="payment-registration-row">
<div class="payment-registration-row-title">支付占比</div>
<div class="payment-registration-row-content">{{percentPay()}}%</div>
</div>
<div class="payment-registration-row">
<div class="payment-registration-row-title">已付笔数</div>
<div class="payment-registration-row-content">{{actNumsTotal()}}</div>
<div class="payment-registration-row-content" style="color: #ff0000;padding-left: 16px;cursor: pointer;">
<Poptip :transfer="true">
<div>点击查看列表</div>
<template v-slot:content>
<template v-if="payment&&payment.length>0">
<xy-table :height="200" :list="payment" :table-item="payTable">
<template v-slot:btns>
<p></p>
</template>
</xy-table>
</template>
<template v-else>
<div style="text-align: center">暂无已付笔数</div>
</template>
</template>
</Poptip>
</div>
</div>
</div>
</template>
<template v-slot:act_date>
<div v-if="contract_category && contract_category.show_apply_money == 1 && (!hasPostPaymentForm || (hasPostPaymentForm && currentStep === 2))" class="xy-table-item">
<div class="xy-table-item-label">
<span v-if="contract_category && contract_category.required_act_date == 1" style="color: red;font-weight: 600;padding-right: 4px;">*</span>
实付日期
</div>
<div class="xy-table-item-content">
<el-date-picker type="date" v-model="paymentRegistrationForm.act_date" value-format="yyyy-MM-dd" style="width: 150px;">
</el-date-picker>
</div>
</div>
</template>
<template v-slot:applyMoney>
<div v-if="contract_category && contract_category.show_apply_money == 1 && (!hasPostPaymentForm || (hasPostPaymentForm && currentStep === 2))" class="xy-table-item">
<div class="xy-table-item-label">
<span v-if="contract_category && contract_category.required_apply_money == 1" style="color: red;font-weight: 600;padding-right: 4px;">*</span>申请付款金额
</div>
<div class="xy-table-item-content xy-table-item-price">
<el-input clearable placeholder="请填写付款金额" v-model="paymentRegistrationForm.applyMoney"
style="width: 150px;" @input="checkIsEnd"/>
</div>
</div>
</template>
<template v-slot:audit_money>
<div v-if="contract_category && contract_category.show_audit_money == 1 && (!hasPostPaymentForm || (hasPostPaymentForm && currentStep === 2))" class="xy-table-item">
<div class="xy-table-item-label">
<span v-if="contract_category && contract_category.required_audit_money == 1" style="color: red;font-weight: 600;padding-right: 4px;">*</span>审计金额
</div>
<div class="xy-table-item-content xy-table-item-price">
<el-input clearable placeholder="请填写审计金额" v-model="paymentRegistrationForm.audit_money" style="width: 150px;" />
</div>
</div>
</template>
<template v-slot:deductionMoney>
<div v-if="contract_category && contract_category.show_discount_money == 1 && (!hasPostPaymentForm || (hasPostPaymentForm && currentStep === 2))" class="xy-table-item">
<div class="xy-table-item-label">
<span v-if="contract_category && contract_category.required_discount_money == 1" style="color: red;font-weight: 600;padding-right: 4px;">*</span>本期扣款金额
</div>
<div class="xy-table-item-content xy-table-item-price">
<el-input clearable placeholder="请填写扣款金额" v-model="paymentRegistrationForm.deductionMoney"
style="width: 150px;" />
</div>
</div>
</template>
<template v-slot:remark>
<div v-if="contract_category && contract_category.show_remark == 1 && (!hasPostPaymentForm || (hasPostPaymentForm && currentStep === 2))" class="xy-table-item">
<div class="xy-table-item-label">
<span v-if="contract_category && contract_category.required_remark == 1" style="color: red;font-weight: 600;padding-right: 4px;">*</span>备注
</div>
<div class="xy-table-item-content">
<el-input type="textarea" clearable placeholder="进度款日期2022.6.8-2022.7.7"
v-model="paymentRegistrationForm.remark" style="width: 300px;" />
</div>
</div>
</template>
<template v-slot:type>
<div v-if="contract_category && contract_category.show_type == 1 && (!hasPostPaymentForm || (hasPostPaymentForm && currentStep === 2))" class="xy-table-item">
<div class="xy-table-item-label">
<span v-if="contract_category && contract_category.required_type == 1" style="color: red;font-weight: 600;padding-right: 4px;">*</span>款项类型
</div>
<div class="xy-table-item-content">
<el-select placeholder="选择款项类型或直接录入其他类型" v-model="paymentRegistrationForm.type" style="width: 150px;"
filterable allow-create clearable>
<el-option v-for="item in paymentType" :key="item" :label="item" :value="item">
</el-option>
</el-select>
</div>
</div>
</template>
<template v-slot:isLast>
<div v-if="contract_category && contract_category.show_is_end == 1 && (!hasPostPaymentForm || (hasPostPaymentForm && currentStep === 2))" class="xy-table-item">
<div class="xy-table-item-label" style="width: 200px">
<span v-if="contract_category && contract_category.required_is_end == 1" style="color: red;font-weight: 600;padding-right: 4px;">*</span>是否为最后一笔
</div>
<div class="xy-table-item-content">
<el-switch v-model="paymentRegistrationForm.isLast" />
</div>
</div>
</template>
<template v-slot:end_time>
<div v-if="contract_category && contract_category.show_end_time == 1 && (!hasPostPaymentForm || (hasPostPaymentForm && currentStep === 2))" class="xy-table-item">
<div class="xy-table-item-label">项目完成时间</div>
<div class="xy-table-item-content">
<el-date-picker type="date" v-model="paymentRegistrationForm.end_time" value-format="yyyy-MM-dd HH:mm:ss" style="width: 150px;">
</el-date-picker>
</div>
</div>
</template>
<template v-slot:isCheck>
<div v-if="contract_category && contract_category.show_check == 1 && (!hasPostPaymentForm || (hasPostPaymentForm && currentStep === 2))" class="xy-table-item">
<div class="xy-table-item-label" style="width: 200px">
<span style="color: red;font-weight: 600;padding-right: 4px;"></span>是否验收
</div>
<div class="xy-table-item-content">
<el-switch v-model="paymentRegistrationForm.isCheck" />
</div>
</div>
</template>
<template v-slot:extraFormBottom>
<div v-if="hasPostPaymentForm && currentStep === 1" class="payment-table-section">
<div class="section-title">
事后支付表格
<el-button type="text" @click="openZoomedTable" style="margin-left: 10px;">
<i class="el-icon-zoom-in"></i> 放大查看
</el-button>
</div>
<div v-if="forms && forms.length > 0" v-html="forms" ref="zoomedForms"></div>
<div v-else-if="contractTemplate" v-html="contractTemplate" ref="zoomedTemplate"></div>
<div v-else class="no-data">暂无支付表格数据</div>
</div>
</template>
<template v-slot:footerContent>
<div class="dialog-footer">
<el-button v-if="hasPostPaymentForm && currentStep === 2" @click="currentStep = 1">上一步</el-button>
<el-button v-if="hasPostPaymentForm && currentStep === 1" @click="nextStep">下一步</el-button>
<el-button v-if="(hasPostPaymentForm && currentStep === 2) || (!hasPostPaymentForm)" type="primary" @click="submit">确定</el-button>
<el-button @click="resetForm">重置</el-button>
</div>
</template>
</xy-dialog>
<!-- 放大后的表格弹窗 -->
<el-dialog
title="事后支付表格"
:visible.sync="showZoomedTable"
width="80%"
:append-to-body="true"
custom-class="zoomed-table-dialog"
@close="handleZoomedDialogClose">
<div v-if="forms && forms.length > 0" v-html="forms" ref="previewPaymentForm"></div>
<div v-else-if="contractTemplate" v-html="contractTemplate" ref="previewPaymentForm"></div>
<div v-else class="no-data">暂无支付表格数据</div>
</el-dialog>
</div>
</template>
<script>
import {
getparameter
} from "@/api/system/dictionary"
import {
getFundLog,
addFundLog
} from "@/api/paymentRegistration/fundLog"
import {
getBudget
} from "@/api/budget/budget";
import {
detailContract,
editorContract
} from "@/api/contract/contract";
import {
Message
} from "element-ui";
import {
parseTime
} from "@/utils";
import { getContractTemplateContext } from '@/api/businessConfig/businessConfig';
// 添加同步表单DOM到HTML的函数
function syncFormDomToHtml(dom, contractTemplateFields) {
if (!dom) return '';
const inputs = dom.querySelectorAll('input, select, textarea');
inputs.forEach(input => {
const fieldName = input.getAttribute('data-field');
if (fieldName && contractTemplateFields) {
const field = contractTemplateFields.find(f => f.field === fieldName);
if (field) {
if (input.type === 'checkbox' || input.type === 'radio') {
const checkedInput = dom.querySelector(`[data-field="${fieldName}"]:checked`);
field.value = checkedInput ? checkedInput.value : '';
if (checkedInput) {
checkedInput.setAttribute('checked', 'checked');
}
} else {
field.value = input.value;
input.setAttribute('value', input.value);
}
}
}
});
return dom.innerHTML;
}
export default {
data() {
return {
currentStep: 1, // 当前步骤
searchContent: "",
planTotal: 0,
pageIndex: 1,
//付款登记
plans: [],
planTypes: [],
contract: {},
payment: [], //合同关联的付款登记
contractTemplate: null, // 合同模板HTML
forms: null, // 实际表单HTML
payTable: [{
label: '申请金额',
prop: 'apply_money',
sortable: false,
width: 160,
align: 'right'
},
{
label: '已付金额',
prop: 'act_money',
sortable: false,
width: 160,
align: 'right'
},
{
label: '时间',
prop: 'created_at',
sortable: false,
width: 120,
formatter: (t1, t2, value) => {
return parseTime(new Date(value), '{y}-{m}-{d}')
}
}
],
paymentType: ["预付款","进度款", "结算款", "质保金"],
isShowPaymentRegistration: false,
paymentRegistrationForm: {
applyMoney: "",
deductionMoney: "",
audit_money: "",
act_date: '',
type: "",
isLast: false,
end_time:'',
isCheck:false,
plan: [],
remark: ""
},
form: {
audit_money: 0
},
// paymentRegistrationRules: {
// applyMoney: [{
// required: true,
// message: "必填"
// },
// {
// pattern: /(^-?(?:\d+|\d{1,3}(?:,\d{3})+)(?:\.\d{1,2})?$)/,
// message: '必须为数字'
// }
// ],
// deductionMoney: [{
// required: true,
// message: "必填"
// },
// {
// pattern: /(^-?(?:\d+|\d{1,3}(?:,\d{3})+)(?:\.\d{1,2})?$)/,
// message: '必须为数字'
// }
// ],
// type: [{
// required: true,
// message: "必选"
// }]
// },
planTable: [{
sortable: false,
width: 36,
type: 'selection'
},
{
label: "分类",
prop: 'type',
formatter: (cell, data, value) => {
let res = this.planTypes.filter(item => {
return item.id === value
})
return res[0]?.value || '未知'
}
},
{
label: "名称",
prop: 'name',
align: 'left'
},
{
label: "计划金额",
prop: 'money',
align: 'right'
}
],
showZoomedTable: false, // 控制放大表格弹窗的显示
hasPostPaymentForm: false, // 是否有事后支付表格
contract_category: {},
templateContextData: null, // 合同模板关联数据
}
},
methods: {
getDefaultPaymentRegistrationForm() {
return {
applyMoney: "",
deductionMoney: "",
audit_money: "",
act_date: '',
type: "",
isLast: false,
end_time:'',
isCheck:false,
plan: [],
remark: ""
}
},
checkIsEnd (e) {
// this.paymentRegistrationForm.isLast = (Number(this.totalMoney()) + Number(e)) >= (this.contract.money * 0.95);
},
async getPlanTypes() {
const res = await getparameter({
number: 'money_way'
})
this.planTypes = res.detail
},
//翻页
pageChange(e) {
this.pageIndex = e
this.getBudgets()
},
//合计申请金额
totalApplyMoney() {
let total = 0.00
this.payment.map(item => {
total += Number(item.apply_money)
})
return total.toFixed(2)
},
//合计金额
totalMoney() {
let total = 0.00
this.payment.map(item => {
total += Number(item.act_money)
})
return total.toFixed(2)
},
//已付笔数
actNumsTotal() {
let total = 0
this.payment.map(item => {
if (Number(item.act_money)) {
total++
}
})
return total
},
//支付占比
percentPay() {
let total = this.totalMoney()
return ((total / this.contract.money) * 100).toFixed(2) || 0
},
//获取合同信息
async getContract(info) {
this.contract = await detailContract({
id: info.id
});
this.paymentRegistrationForm.plan = this.contract.plans.map(item => {
return {
plan_id: item.id,
use_money: item.useMoney,
new_money: item.money,
}
});
this.form.audit_money = this.contract.audit_money;
this.contract_category = this.contract.contract_category || {};
// 设置默认值
if (this.contract_category && this.contract_category.default_apply_money !== undefined) {
this.paymentRegistrationForm.applyMoney = this.contract_category.default_apply_money;
}
if (this.contract_category && this.contract_category.default_discount_money !== undefined) {
this.paymentRegistrationForm.deductionMoney = this.contract_category.default_discount_money;
}
if (this.contract_category && this.contract_category.default_audit_money !== undefined) {
this.paymentRegistrationForm.audit_money = this.contract_category.default_audit_money;
}
if (this.contract_category && this.contract_category.default_act_date !== undefined) {
this.paymentRegistrationForm.act_date = this.contract_category.default_act_date;
}
if (this.contract_category && this.contract_category.default_type !== undefined) {
this.paymentRegistrationForm.type = this.contract_category.default_type;
}
if (this.contract_category && this.contract_category.default_is_end !== undefined) {
this.paymentRegistrationForm.isLast = this.contract_category.default_is_end == 1 || this.contract_category.default_is_end === '1';
}
if (this.contract_category && this.contract_category.default_remark !== undefined) {
this.paymentRegistrationForm.remark = this.contract_category.default_remark;
}
// 动态生成rules
this.paymentRegistrationRules = {
applyMoney: [
{
required: this.contract_category.required_apply_money == 1,
message: '必填'
},
{
pattern: /(^-?(?:\d+|\d{1,3}(?:,\d{3})+)(?:\.\d{1,2})?$)/,
message: '必须为数字'
}
],
deductionMoney: [
{
required: this.contract_category.required_discount_money == 1,
message: '必填'
},
{
pattern: /(^-?(?:\d+|\d{1,3}(?:,\d{3})+)(?:\.\d{1,2})?$)/,
message: '必须为数字'
}
],
audit_money: [
{
required: this.contract_category.required_audit_money == 1,
message: '必填'
}
],
type: [
{
required: this.contract_category.required_type == 1,
message: '必选'
}
],
isLast: [
{
required: this.contract_category.required_is_end == 1,
message: '必选'
}
],
remark: [
{
required: this.contract_category.required_remark == 1,
message: '必填'
}
],
act_date: [
{
required: this.contract_category.required_act_date == 1,
message: '必填'
}
]
};
const res = await getFundLog({
contract_id: this.contract.id,
page: 1,
page_size: 999
})
this.payment = res.data;
// 判断是否有事后支付表格模板
if (this.contract.contract_template && this.contract.contract_template.template) {
this.hasPostPaymentForm = true;
this.currentStep = 1;
this.contractTemplate = this.contract.contract_template.template;
this.forms = this.contract.forms;
if (!this.contract.contract_template.contract_template_fields) {
this.contract.contract_template.contract_template_fields = this.contract.other_data || [];
}
} else {
this.hasPostPaymentForm = false;
this.currentStep = 1;
this.contractTemplate = null;
this.forms = null;
}
// 拉取合同模板关联数据
if (this.contract && this.contract.id) {
getContractTemplateContext({
id: this.contract.id,
model: 'Contract'
}).then(res => {
this.templateContextData = res;
});
}
},
submit() {
// 先进行表单校验
this.$refs['paymentRegistration'].$refs['elForm'].validate().then(res => {
if (res) {
// 保存事后支付表格的数据
if (this.contract.contract_template) {
const dom = this.$refs.zoomedForms || this.$refs.zoomedTemplate;
if (dom) {
// 获取所有输入控件
const inputs = dom.querySelectorAll('input, select, textarea');
// 遍历所有输入控件,更新值到 HTML
inputs.forEach(input => {
const fieldName = input.getAttribute('data-field');
if (fieldName) {
const field = this.contract.contract_template.contract_template_fields.find(f => f.field === fieldName);
if (field) {
if (input.type === 'checkbox' || input.type === 'radio') {
// 对于复选框和单选框,需要找到选中的值
const checkedInput = dom.querySelector(`[data-field="${fieldName}"]:checked`);
field.value = checkedInput ? checkedInput.value : '';
// 更新 HTML 中的 checked 状态
if (checkedInput) {
checkedInput.setAttribute('checked', 'checked');
}
} else {
field.value = input.value;
// 更新 HTML 中的 value
input.setAttribute('value', input.value);
}
}
}
});
// 获取更新后的 HTML
this.forms = dom.innerHTML;
}
}
let data = {
contract_id: this.contract.id,
apply_money: this.paymentRegistrationForm.applyMoney,
discount_money: this.paymentRegistrationForm.deductionMoney,
type: this.paymentRegistrationForm.type,
is_end: this.paymentRegistrationForm.isLast ? 1 : 0,
remark: this.paymentRegistrationForm.remark,
audit_money: this.paymentRegistrationForm.audit_money,
end_time:this.paymentRegistrationForm.end_time,
is_check:this.paymentRegistrationForm.isCheck ? 1 : 0,
// 提交更新后的HTML和字段数据
forms: this.forms,
other_data: this.contract.contract_template?.contract_template_fields || []
}
addFundLog(data).then(res => {
this.isShowPaymentRegistration = false
//付款申请后同时更新一下合同中审计金额
editorContract({
id: this.contract.id,
audit_money: this.form.audit_money,
// 同时更新合同的事后支付表格数据
forms: this.forms,
other_data: this.contract.contract_template?.contract_template_fields || []
}).then(r => {
Message({
type: 'success',
message: "操作成功"
})
});
this.$refs['paymentRegistration'].reset()
})
} else {
this.$Message.warning({
content: '请填写完整信息',
duration: 1
});
}
}).catch(err => {
this.$Message.warning({
content: '请填写完整信息',
duration: 1
});
});
},
//计划
//获取预算计划
async getBudgets() {
let res = await getBudget({
name: this.searchContent,
page_size: 10,
page: this.pageIndex
})
this.plans = res.list.data
this.planTotal = res.list.total
// this.toggleSelection(this.paymentRegistrationForm.plan.map(item => {
// return item.plan_id
// }))
},
planPageChange(e) {
this.plansPageIndex = e
this.getBudgets()
},
selectPlan(sel, row) {
if (sel) {
this.paymentRegistrationForm.plan = sel.map(item => {
return {
plan_id: item.id,
use_money: item.useMoney,
new_money: item.money
}
})
} else {
this.paymentRegistrationForm.plan = []
}
},
toggleSelection(e) {
if (!e) {
return
}
let plans = this.paymentRegistrationForm.plan.map(item => {
return item.plan_id
})
if (plans) {
this.plans.filter(plan => {
return plans.includes(plan.id)
}).map(row => {
this.$nextTick(() => {
this.$refs['planTable'].toggleRowSelection(row);
})
})
} else {
this.$refs['planTable'].clearSelection();
}
},
nextStep() {
// 如果有事后支付表格,下一步不做表单校验,直接切换 currentStep=2
if (this.hasPostPaymentForm) {
// 保存事后支付表格HTML数据参考 openZoomedTable 逻辑
const dom = this.$refs.zoomedForms || this.$refs.zoomedTemplate;
if (dom && this.contract.contract_template) {
this.forms = syncFormDomToHtml(dom, this.contract.contract_template.contract_template_fields);
this.contract.forms = this.forms;
}
this.currentStep = 2;
} else {
// 没有事后支付表格时,下一步其实就是提交,校验在 submit 里
if (this.$refs['paymentRegistration'] && this.$refs['paymentRegistration'].$refs['elForm']) {
this.$refs['paymentRegistration'].$refs['elForm'].validate().then(res => {
if (res) {
this.submit();
}
}).catch(err => {
this.$Message.warning({
content: '请填写完整信息',
duration: 1
});
});
} else {
this.submit();
}
}
},
resetForm() {
this.currentStep = 1;
this.$refs['paymentRegistration'].reset();
},
// 打开放大预览前,先同步当前表单数据
openZoomedTable() {
const dom = this.$refs.zoomedForms || this.$refs.zoomedTemplate;
if (dom && this.contract.contract_template) {
console.log(this.contract.contract_template.contract_template_fields)
this.forms = syncFormDomToHtml(dom, this.contract.contract_template.contract_template_fields);
this.contract.forms = this.forms;
}
this.showZoomedTable = true;
},
handleZoomedDialogClose() {
const dom = this.$refs.previewPaymentForm;
if (dom && this.contract.contract_template) {
this.forms = syncFormDomToHtml(dom, this.contract.contract_template.contract_template_fields);
this.contract.forms = this.forms;
}
this.showZoomedTable = false;
},
},
computed: {
priceFormat() {
return function(price) {
return Number(price).toFixed(2).replace(/(\d)(?=(\d{3})+\.)/g, '$1,')
}
}
},
watch: {
isShowPaymentRegistration(newVal) {
if (newVal) {
this.getBudgets();
this.currentStep = 1; // 重置到第一步
this.paymentRegistrationForm = this.getDefaultPaymentRegistrationForm(); // 每次弹窗打开时重置表单
// 重置校验状态,避免立刻弹出必填项提示
this.$nextTick(() => {
if (this.$refs.paymentRegistration && this.$refs.paymentRegistration.$refs.elForm) {
this.$refs.paymentRegistration.$refs.elForm.resetFields();
}
});
}
}
},
mounted() {
this.getPlanTypes()
}
}
</script>
<style scoped lang="scss">
.payment-registration {
&-row {
display: flex;
padding: 6px 0;
&-title {
padding: 0 10px;
}
&-content {}
}
}
.payment-table-section {
margin-top: 20px;
padding: 0 20px;
.section-title {
font-size: 14px;
font-weight: bold;
margin-bottom: 10px;
color: #333;
display: flex;
align-items: center;
}
.no-data {
text-align: center;
color: #909399;
padding: 20px 0;
}
::v-deep table {
width: 100%;
border-collapse: collapse;
margin-bottom: 20px;
th, td {
border: 1px solid #EBEEF5;
padding: 8px;
text-align: center;
}
th {
background-color: #F5F7FA;
color: #606266;
font-weight: 500;
}
}
}
.xy-table-item-label {
width: 140px;
}
.xy-table-item-price {
position: relative;
&::after {
z-index: 1;
position: absolute;
right: 0;
top: 0;
content: ''
}
::v-deep .el-input__clear {
position: relative;
right: 30px;
z-index: 2;
}
}
::v-deep .zoomed-table-dialog {
.el-dialog__body {
padding: 20px;
max-height: 70vh;
overflow-y: auto;
}
}
</style>