Compare commits
8 Commits
325e708ef1
...
dc5a2235eb
| Author | SHA1 | Date | |
|---|---|---|---|
| dc5a2235eb | |||
| 3da2d428f3 | |||
| cb98355715 | |||
| 335e45c3f1 | |||
| 665503f43e | |||
| 4340c9fc59 | |||
| d6b6be0e9b | |||
| 55583551b5 |
@@ -13,7 +13,8 @@ export const getCandidates = (current, size, params) =>
|
|||||||
});
|
});
|
||||||
export const getContractOptions = (keyword, projectId) =>
|
export const getContractOptions = (keyword, projectId) =>
|
||||||
request({ url: `${baseUrl}/contract-options`, method: 'get', params: { keyword, projectId } });
|
request({ url: `${baseUrl}/contract-options`, method: 'get', params: { keyword, projectId } });
|
||||||
export const getNextNo = () => request({ url: `${baseUrl}/next-no`, method: 'get' });
|
export const getNextNo = settlementType =>
|
||||||
|
request({ url: `${baseUrl}/next-no`, method: 'get', params: { settlementType } });
|
||||||
export const getFeeOptions = () => request({ url: `${baseUrl}/fee-options`, method: 'get' });
|
export const getFeeOptions = () => request({ url: `${baseUrl}/fee-options`, method: 'get' });
|
||||||
export const getCandidateDetails = (current, size, params) =>
|
export const getCandidateDetails = (current, size, params) =>
|
||||||
request({
|
request({
|
||||||
|
|||||||
@@ -148,6 +148,16 @@ export const config = {
|
|||||||
stage: ['formal'],
|
stage: ['formal'],
|
||||||
permission: 'contract_manage_reject',
|
permission: 'contract_manage_reject',
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
action: 'submitFormal',
|
||||||
|
label: '转正式合同',
|
||||||
|
statusProp: 'approvalStatus',
|
||||||
|
status: ['approved'],
|
||||||
|
stage: ['temporary'],
|
||||||
|
permission: 'contract_manage_formal',
|
||||||
|
confirmMessage: '确定将该临时合同提交正式合同审批吗?',
|
||||||
|
successMessage: '已提交正式合同审批',
|
||||||
|
},
|
||||||
{
|
{
|
||||||
action: 'submitFormal',
|
action: 'submitFormal',
|
||||||
label: '重新提交转正',
|
label: '重新提交转正',
|
||||||
|
|||||||
@@ -2,7 +2,6 @@ import {
|
|||||||
auditColumns,
|
auditColumns,
|
||||||
businessTypeOptions,
|
businessTypeOptions,
|
||||||
createCrudOption,
|
createCrudOption,
|
||||||
effectiveTypeOptions,
|
|
||||||
nonNegativeRule,
|
nonNegativeRule,
|
||||||
projectApprovalStatusOptions,
|
projectApprovalStatusOptions,
|
||||||
projectSourceOptions,
|
projectSourceOptions,
|
||||||
@@ -12,6 +11,11 @@ import {
|
|||||||
textRule,
|
textRule,
|
||||||
} from './common';
|
} from './common';
|
||||||
|
|
||||||
|
const projectEffectiveTypeOptions = [
|
||||||
|
{ label: '临时', value: 'temporary' },
|
||||||
|
{ label: '正式', value: 'formal' },
|
||||||
|
];
|
||||||
|
|
||||||
const amountRules = label => [
|
const amountRules = label => [
|
||||||
{ required: true, message: `请输入${label}`, trigger: 'blur' },
|
{ required: true, message: `请输入${label}`, trigger: 'blur' },
|
||||||
nonNegativeRule(label),
|
nonNegativeRule(label),
|
||||||
@@ -379,7 +383,7 @@ export const option = createCrudOption([
|
|||||||
search: true,
|
search: true,
|
||||||
searchPlaceholder: '请选择',
|
searchPlaceholder: '请选择',
|
||||||
searchOrder: 1,
|
searchOrder: 1,
|
||||||
dicData: effectiveTypeOptions,
|
dicData: projectEffectiveTypeOptions,
|
||||||
value: 'temporary',
|
value: 'temporary',
|
||||||
minWidth: 110,
|
minWidth: 110,
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
export const formalSettlementTableColumns = [
|
export const formalSettlementTableColumns = [
|
||||||
{ label: '结算单号', prop: 'formalSettlementNo', minWidth: 160, link: true, fixed: 'left' },
|
{ label: '结算单号', prop: 'formalSettlementNo', minWidth: 160, link: true, fixed: 'left' },
|
||||||
{ label: '预结算单号', prop: 'preSettlementNos', minWidth: 190 },
|
|
||||||
{ label: '来源', prop: 'sourceType', minWidth: 110 },
|
{ label: '来源', prop: 'sourceType', minWidth: 110 },
|
||||||
{ label: '付款方', prop: 'payerName', minWidth: 150 },
|
{ label: '付款方', prop: 'payerName', minWidth: 150 },
|
||||||
{ label: '收款方', prop: 'payeeName', minWidth: 150 },
|
{ label: '收款方', prop: 'payeeName', minWidth: 150 },
|
||||||
@@ -12,13 +11,16 @@ export const formalSettlementTableColumns = [
|
|||||||
{ label: '本位币结算金额', prop: 'localSettlementAmount', minWidth: 155, money: true },
|
{ label: '本位币结算金额', prop: 'localSettlementAmount', minWidth: 155, money: true },
|
||||||
{ label: '结算汇率', prop: 'exchangeRate', minWidth: 110 },
|
{ label: '结算汇率', prop: 'exchangeRate', minWidth: 110 },
|
||||||
{ label: '申请付款金额(含预付)', prop: 'appliedPaymentAmount', minWidth: 185, money: true },
|
{ label: '申请付款金额(含预付)', prop: 'appliedPaymentAmount', minWidth: 185, money: true },
|
||||||
{ label: '已收/已付合计', prop: 'paidAmount', minWidth: 145, money: true },
|
{ label: '已收/已付合计(含预付)', prop: 'paidAmount', minWidth: 200, money: true },
|
||||||
|
{ label: '剩余可付金额', prop: 'remainingPayableAmount', minWidth: 145, money: true },
|
||||||
|
{ label: '发票金额', prop: 'invoiceAmount', minWidth: 130, money: true },
|
||||||
{ label: '发票状态', prop: 'invoiceStatusName', minWidth: 120 },
|
{ label: '发票状态', prop: 'invoiceStatusName', minWidth: 120 },
|
||||||
{ label: '收付款状态', prop: 'paymentStatusName', minWidth: 120 },
|
{ label: '收付款状态', prop: 'paymentStatusName', minWidth: 120 },
|
||||||
{ label: '审核状态', prop: 'approvalStatusName', minWidth: 110, status: true },
|
{ label: '审核状态', prop: 'approvalStatusName', minWidth: 110, status: true },
|
||||||
{ label: '当前节点', prop: 'currentNode', minWidth: 130 },
|
{ label: '当前节点', prop: 'currentNode', minWidth: 130 },
|
||||||
{ label: '当前处理人', prop: 'currentProcessor', minWidth: 130 },
|
{ label: '当前处理人', prop: 'currentProcessor', minWidth: 130 },
|
||||||
{ label: '金蝶单据号', prop: 'kingdeeBillNo', minWidth: 150 },
|
{ label: '金蝶单据号', prop: 'kingdeeBillNo', minWidth: 150 },
|
||||||
|
{ label: '金蝶单据状态', prop: 'kingdeeSyncStatus', minWidth: 135 },
|
||||||
{ label: '创建人', prop: 'createUserName', minWidth: 110 },
|
{ label: '创建人', prop: 'createUserName', minWidth: 110 },
|
||||||
{ label: '创建时间', prop: 'createTime', minWidth: 170 },
|
{ label: '创建时间', prop: 'createTime', minWidth: 170 },
|
||||||
];
|
];
|
||||||
|
|||||||
@@ -57,14 +57,13 @@ export const feeDetailBaseColumns = [
|
|||||||
{ label: '货物类型', prop: 'cargoType', minWidth: 130 },
|
{ label: '货物类型', prop: 'cargoType', minWidth: 130 },
|
||||||
{ label: '规格', prop: 'specification', minWidth: 120 },
|
{ label: '规格', prop: 'specification', minWidth: 120 },
|
||||||
{ label: '型号', prop: 'model', minWidth: 120 },
|
{ label: '型号', prop: 'model', minWidth: 120 },
|
||||||
{ label: '运费计费要素', prop: 'billingFactor', minWidth: 130 },
|
{ label: '计费要素', prop: 'billingFactor', minWidth: 130 },
|
||||||
{ label: '运费计费类型', prop: 'billingType', minWidth: 130 },
|
{ label: '计费类型', prop: 'billingType', minWidth: 130 },
|
||||||
{ label: '运输量', prop: 'transportQuantityText', minWidth: 120 },
|
{ label: '运输量', prop: 'transportQuantityText', minWidth: 120 },
|
||||||
{ label: '运费计算单位', prop: 'priceUnit', minWidth: 130 },
|
{ label: '运费计算单位', prop: 'priceUnit', minWidth: 130 },
|
||||||
{ label: '运输单价', prop: 'unitPrice', minWidth: 120 },
|
{ label: '运输单价', prop: 'unitPrice', minWidth: 120 },
|
||||||
{ label: '里程(KM)', prop: 'mileage', minWidth: 120 },
|
{ label: '里程(KM)', prop: 'mileage', minWidth: 120 },
|
||||||
{ label: '运输费', prop: 'freightAmount', minWidth: 180 }
|
{ label: '运输费', prop: 'freightAmount', minWidth: 180 },
|
||||||
|
|
||||||
];
|
];
|
||||||
|
|
||||||
export const feeDetailTailColumns = [
|
export const feeDetailTailColumns = [
|
||||||
|
|||||||
@@ -24,5 +24,6 @@ export const createSettlementAdjustmentForm = () => ({
|
|||||||
adjustmentAmount: 0,
|
adjustmentAmount: 0,
|
||||||
originalSettlementAmount: 0,
|
originalSettlementAmount: 0,
|
||||||
adjustedSettlementAmount: 0,
|
adjustedSettlementAmount: 0,
|
||||||
|
attachmentsJson: '[]',
|
||||||
remark: '',
|
remark: '',
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -190,7 +190,9 @@ export default {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
const validateIcaoCode = (rule, value, callback) => {
|
const validateIcaoCode = (rule, value, callback) => {
|
||||||
if (!/^[A-Z]{4}$/.test(String(value || '').toUpperCase())) {
|
if (!value) {
|
||||||
|
callback();
|
||||||
|
} else if (!/^[A-Z]{4}$/.test(String(value).toUpperCase())) {
|
||||||
callback(new Error('ICAO代码为4位大写字母'));
|
callback(new Error('ICAO代码为4位大写字母'));
|
||||||
} else {
|
} else {
|
||||||
callback();
|
callback();
|
||||||
@@ -281,10 +283,7 @@ export default {
|
|||||||
prop: 'icaoCode',
|
prop: 'icaoCode',
|
||||||
minWidth: 100,
|
minWidth: 100,
|
||||||
maxlength: 4,
|
maxlength: 4,
|
||||||
rules: [
|
rules: [{ validator: validateIcaoCode, trigger: 'blur' }],
|
||||||
{ required: true, message: '请输入ICAO代码', trigger: 'blur' },
|
|
||||||
{ validator: validateIcaoCode, trigger: 'blur' },
|
|
||||||
],
|
|
||||||
change: ({ value }) => this.handleIcaoChange(value),
|
change: ({ value }) => this.handleIcaoChange(value),
|
||||||
blur: ({ value }) => this.handleIcaoChange(value),
|
blur: ({ value }) => this.handleIcaoChange(value),
|
||||||
},
|
},
|
||||||
@@ -376,7 +375,10 @@ export default {
|
|||||||
overHidden: false,
|
overHidden: false,
|
||||||
maxlength: 255,
|
maxlength: 255,
|
||||||
showWordLimit: true,
|
showWordLimit: true,
|
||||||
rules: [{ max: 255, message: '详细地址不能超过255字', trigger: 'blur' }],
|
rules: [
|
||||||
|
{ required: true, message: '请输入详细地址', trigger: 'blur' },
|
||||||
|
{ max: 255, message: '详细地址不能超过255字', trigger: 'blur' },
|
||||||
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: '经度',
|
label: '经度',
|
||||||
@@ -674,6 +676,7 @@ export default {
|
|||||||
row.districtCode = String(row.districtCode || '').trim();
|
row.districtCode = String(row.districtCode || '').trim();
|
||||||
row.districtName = String(row.districtName || '').trim();
|
row.districtName = String(row.districtName || '').trim();
|
||||||
row.detailAddress = String(row.detailAddress || '').trim();
|
row.detailAddress = String(row.detailAddress || '').trim();
|
||||||
|
row.icaoCode = String(row.icaoCode || '').trim() || undefined;
|
||||||
row.longitude = normalizeCoordinateInput(row.longitude);
|
row.longitude = normalizeCoordinateInput(row.longitude);
|
||||||
row.latitude = normalizeCoordinateInput(row.latitude);
|
row.latitude = normalizeCoordinateInput(row.latitude);
|
||||||
if (!row.dataSource) row.dataSource = '手动录入';
|
if (!row.dataSource) row.dataSource = '手动录入';
|
||||||
@@ -696,6 +699,9 @@ export default {
|
|||||||
validateUnique(row) {
|
validateUnique(row) {
|
||||||
const rowId = String(row.id || '');
|
const rowId = String(row.id || '');
|
||||||
const hasDuplicate = (params, prop) => {
|
const hasDuplicate = (params, prop) => {
|
||||||
|
if (!row[prop]) {
|
||||||
|
return Promise.resolve(false);
|
||||||
|
}
|
||||||
return getList(1, 10, params).then(res => {
|
return getList(1, 10, params).then(res => {
|
||||||
const records = res.data.data.records || [];
|
const records = res.data.data.records || [];
|
||||||
return records.some(item => item[prop] === row[prop] && String(item.id || '') !== rowId);
|
return records.some(item => item[prop] === row[prop] && String(item.id || '') !== rowId);
|
||||||
|
|||||||
@@ -213,9 +213,9 @@ export const decorateCargoTypeFailDetail = async workbook => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
reasonCell.value = decorated.join(';');
|
reasonCell.value = decorated.join(';');
|
||||||
// 仅操作 G 列:黑色字体、无背景填充,其余列保持后端原样
|
// 仅失败原因列使用红色字体,其余列保持普通字体
|
||||||
reasonCell.fill = { type: 'pattern', pattern: 'none' };
|
reasonCell.fill = { type: 'pattern', pattern: 'none' };
|
||||||
reasonCell.font = { color: { argb: 'FF000000' } };
|
reasonCell.font = { color: { argb: 'FFFF0000' } };
|
||||||
reasonCell.alignment = { wrapText: true, vertical: 'top' };
|
reasonCell.alignment = { wrapText: true, vertical: 'top' };
|
||||||
});
|
});
|
||||||
// 加宽各列,避免内容拥挤(仅改列宽,不改动单元格样式)
|
// 加宽各列,避免内容拥挤(仅改列宽,不改动单元格样式)
|
||||||
|
|||||||
+14
-60
@@ -21,12 +21,6 @@
|
|||||||
@on-load="onLoad"
|
@on-load="onLoad"
|
||||||
>
|
>
|
||||||
<template #menu-left>
|
<template #menu-left>
|
||||||
<el-button type="primary" plain v-if="permission.fee_item_import" @click="handleImport">
|
|
||||||
<el-icon class="el-icon--left"><Upload /></el-icon>批量导入
|
|
||||||
</el-button>
|
|
||||||
<el-button type="primary" plain v-if="permission.fee_item_template" @click="handleTemplate">
|
|
||||||
<el-icon class="el-icon--left"><Download /></el-icon>下载模板
|
|
||||||
</el-button>
|
|
||||||
<el-button type="primary" plain v-if="permission.fee_item_export" @click="handleExport">
|
<el-button type="primary" plain v-if="permission.fee_item_export" @click="handleExport">
|
||||||
<el-icon class="el-icon--left"><Download /></el-icon>批量导出
|
<el-icon class="el-icon--left"><Download /></el-icon>批量导出
|
||||||
</el-button>
|
</el-button>
|
||||||
@@ -80,16 +74,6 @@
|
|||||||
</el-link>
|
</el-link>
|
||||||
</template>
|
</template>
|
||||||
</avue-crud>
|
</avue-crud>
|
||||||
|
|
||||||
<el-dialog title="费用项导入" append-to-body v-model="excelBox" width="555px">
|
|
||||||
<avue-form :option="excelOption" v-model="excelForm">
|
|
||||||
<template #excelTemplate>
|
|
||||||
<el-button type="primary" @click="handleTemplate">
|
|
||||||
点击下载<i class="el-icon-download el-icon--right"></i>
|
|
||||||
</el-button>
|
|
||||||
</template>
|
|
||||||
</avue-form>
|
|
||||||
</el-dialog>
|
|
||||||
</basic-container>
|
</basic-container>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@@ -99,24 +83,20 @@ import { exportBlob } from '@/api/common';
|
|||||||
import { getDictionary } from '@/api/system/dictbiz';
|
import { getDictionary } from '@/api/system/dictbiz';
|
||||||
import { getDeptTree } from '@/api/system/dept';
|
import { getDeptTree } from '@/api/system/dept';
|
||||||
import { downloadXls } from '@/utils/util';
|
import { downloadXls } from '@/utils/util';
|
||||||
import { openImportDialog } from '@/utils/import-excel';
|
|
||||||
import { getToken } from '@/utils/auth';
|
import { getToken } from '@/utils/auth';
|
||||||
import { getUploadHeaders } from '@/utils/upload';
|
import { Download } from '@element-plus/icons-vue';
|
||||||
import { Upload, Download } from '@element-plus/icons-vue';
|
|
||||||
import { mapGetters } from 'vuex';
|
import { mapGetters } from 'vuex';
|
||||||
import NProgress from 'nprogress';
|
import NProgress from 'nprogress';
|
||||||
import 'nprogress/nprogress.css';
|
import 'nprogress/nprogress.css';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
components: { Upload, Download },
|
components: { Download },
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
form: {},
|
form: {},
|
||||||
query: {},
|
query: {},
|
||||||
loading: true,
|
loading: true,
|
||||||
data: [],
|
data: [],
|
||||||
excelBox: false,
|
|
||||||
excelForm: {},
|
|
||||||
dialogType: 'add',
|
dialogType: 'add',
|
||||||
feeCategoryOptions: [],
|
feeCategoryOptions: [],
|
||||||
page: {
|
page: {
|
||||||
@@ -184,6 +164,17 @@ export default {
|
|||||||
maxlength: 50,
|
maxlength: 50,
|
||||||
rules: [{ required: true, message: '请输入费用项', trigger: 'blur' }],
|
rules: [{ required: true, message: '请输入费用项', trigger: 'blur' }],
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
label: '备注',
|
||||||
|
prop: 'remark',
|
||||||
|
type: 'textarea',
|
||||||
|
minRows: 2,
|
||||||
|
span: 24,
|
||||||
|
hide: true,
|
||||||
|
maxlength: 200,
|
||||||
|
showWordLimit: true,
|
||||||
|
rules: [{ max: 200, message: '备注不能超过200个字', trigger: 'blur' }],
|
||||||
|
},
|
||||||
{
|
{
|
||||||
label: '组织',
|
label: '组织',
|
||||||
prop: 'createDeptName',
|
prop: 'createDeptName',
|
||||||
@@ -259,32 +250,6 @@ export default {
|
|||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
excelOption: {
|
|
||||||
submitBtn: false,
|
|
||||||
emptyBtn: false,
|
|
||||||
column: [
|
|
||||||
{
|
|
||||||
label: '模板上传',
|
|
||||||
prop: 'excelFile',
|
|
||||||
type: 'upload',
|
|
||||||
drag: true,
|
|
||||||
loadText: '模板上传中,请稍等',
|
|
||||||
span: 24,
|
|
||||||
propsHttp: {
|
|
||||||
res: 'data',
|
|
||||||
},
|
|
||||||
headers: getUploadHeaders(),
|
|
||||||
tip: '请上传 .xls,.xlsx 标准格式文件',
|
|
||||||
action: '/blade-system/fee-item/import-fee-item',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: '模板下载',
|
|
||||||
prop: 'excelTemplate',
|
|
||||||
formslot: true,
|
|
||||||
span: 24,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
created() {
|
created() {
|
||||||
@@ -364,6 +329,7 @@ export default {
|
|||||||
values.feeCategory = String(values.feeCategory || '').trim();
|
values.feeCategory = String(values.feeCategory || '').trim();
|
||||||
values.name = String(values.name || '').trim();
|
values.name = String(values.name || '').trim();
|
||||||
values.englishName = prefix ? `${prefix}${code}` : code;
|
values.englishName = prefix ? `${prefix}${code}` : code;
|
||||||
|
values.remark = String(values.remark || '').trim() || undefined;
|
||||||
if (!values.status) values.status = 1;
|
if (!values.status) values.status = 1;
|
||||||
return values;
|
return values;
|
||||||
},
|
},
|
||||||
@@ -464,9 +430,6 @@ export default {
|
|||||||
refreshChange() {
|
refreshChange() {
|
||||||
this.onLoad(this.page, this.query);
|
this.onLoad(this.page, this.query);
|
||||||
},
|
},
|
||||||
handleImport() {
|
|
||||||
openImportDialog(this, '费用项');
|
|
||||||
},
|
|
||||||
buildExportParams() {
|
buildExportParams() {
|
||||||
return {
|
return {
|
||||||
...this.query,
|
...this.query,
|
||||||
@@ -492,15 +455,6 @@ export default {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
handleTemplate() {
|
|
||||||
exportBlob(
|
|
||||||
`/blade-system/fee-item/export-template?${this.website.tokenHeader}=${getToken()}`,
|
|
||||||
undefined,
|
|
||||||
{ feedback: true }
|
|
||||||
).then(res => {
|
|
||||||
downloadXls(res.data, '费用项模板.xlsx');
|
|
||||||
});
|
|
||||||
},
|
|
||||||
onLoad(page, params = {}) {
|
onLoad(page, params = {}) {
|
||||||
this.loading = true;
|
this.loading = true;
|
||||||
getList(page.currentPage, page.pageSize, Object.assign(params, this.query)).then(res => {
|
getList(page.currentPage, page.pageSize, Object.assign(params, this.query)).then(res => {
|
||||||
|
|||||||
@@ -373,7 +373,7 @@ export default {
|
|||||||
searchOrder: 6,
|
searchOrder: 6,
|
||||||
formslot: true,
|
formslot: true,
|
||||||
span: 24,
|
span: 24,
|
||||||
order: 100,
|
order: 102,
|
||||||
dicData: [
|
dicData: [
|
||||||
{ label: '全部', value: '' },
|
{ label: '全部', value: '' },
|
||||||
{ label: '港口', value: '港口' },
|
{ label: '港口', value: '港口' },
|
||||||
@@ -393,7 +393,7 @@ export default {
|
|||||||
dicData: [],
|
dicData: [],
|
||||||
hide: true,
|
hide: true,
|
||||||
span: 12,
|
span: 12,
|
||||||
order: 100,
|
order: 101,
|
||||||
placeholder: '请选择',
|
placeholder: '请选择',
|
||||||
rules: [{ required: false, message: '请选择上级港口', trigger: 'change' }],
|
rules: [{ required: false, message: '请选择上级港口', trigger: 'change' }],
|
||||||
change: ({ value }) => this.handleParentChange(value),
|
change: ({ value }) => this.handleParentChange(value),
|
||||||
@@ -412,10 +412,11 @@ export default {
|
|||||||
minWidth: 120,
|
minWidth: 120,
|
||||||
slot: true,
|
slot: true,
|
||||||
span: 12,
|
span: 12,
|
||||||
|
order: 101,
|
||||||
|
disabled: true,
|
||||||
addDisplay: false,
|
addDisplay: false,
|
||||||
editDisplay: false,
|
editDisplay: false,
|
||||||
placeholder: '自动带出',
|
placeholder: '自动带出',
|
||||||
rules: [{ required: false, message: '请选择上级港口', trigger: 'change' }],
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: '国家',
|
label: '国家',
|
||||||
@@ -537,7 +538,10 @@ export default {
|
|||||||
placeholder: '请输入',
|
placeholder: '请输入',
|
||||||
maxlength: 255,
|
maxlength: 255,
|
||||||
showWordLimit: true,
|
showWordLimit: true,
|
||||||
rules: [{ max: 255, message: '详细地址不能超过255字', trigger: 'blur' }],
|
rules: [
|
||||||
|
{ required: true, message: '请输入详细地址', trigger: 'blur' },
|
||||||
|
{ max: 255, message: '详细地址不能超过255字', trigger: 'blur' },
|
||||||
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: '数据来源',
|
label: '数据来源',
|
||||||
@@ -551,9 +555,9 @@ export default {
|
|||||||
editDisplay: false,
|
editDisplay: false,
|
||||||
dicData: [
|
dicData: [
|
||||||
{ label: '全部', value: '' },
|
{ label: '全部', value: '' },
|
||||||
{ label: '初始化导入', value: '初始化导入' },
|
{ label: '初始化录入', value: '初始化录入' },
|
||||||
{ label: '批量导入', value: '批量导入' },
|
{ label: '批量导入', value: '批量导入' },
|
||||||
{ label: '手工导入', value: '手工导入' },
|
{ label: '手动录入', value: '手动录入' },
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -761,6 +765,7 @@ export default {
|
|||||||
parentColumn.rules[0].required = required;
|
parentColumn.rules[0].required = required;
|
||||||
}
|
}
|
||||||
this.setColumnDisplay('parentId', required);
|
this.setColumnDisplay('parentId', required);
|
||||||
|
this.setColumnDisplay('parentCode', required);
|
||||||
if (value === '港口') {
|
if (value === '港口') {
|
||||||
this.form.parentId = undefined;
|
this.form.parentId = undefined;
|
||||||
this.form.parentCode = '';
|
this.form.parentCode = '';
|
||||||
@@ -992,7 +997,7 @@ export default {
|
|||||||
submitRow.parentCode = undefined;
|
submitRow.parentCode = undefined;
|
||||||
submitRow.parentName = undefined;
|
submitRow.parentName = undefined;
|
||||||
}
|
}
|
||||||
if (!submitRow.dataSource) submitRow.dataSource = '手工导入';
|
if (!submitRow.dataSource) submitRow.dataSource = '手动录入';
|
||||||
if (!submitRow.status) submitRow.status = 1;
|
if (!submitRow.status) submitRow.status = 1;
|
||||||
delete submitRow.terminalCode;
|
delete submitRow.terminalCode;
|
||||||
delete submitRow.cityCode;
|
delete submitRow.cityCode;
|
||||||
@@ -1021,6 +1026,10 @@ export default {
|
|||||||
this.$message.warning('请选择区县');
|
this.$message.warning('请选择区县');
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
if (isBlank(row.detailAddress)) {
|
||||||
|
this.$message.warning('请输入详细地址');
|
||||||
|
return false;
|
||||||
|
}
|
||||||
if (isBlank(row.longitude)) {
|
if (isBlank(row.longitude)) {
|
||||||
this.$message.warning('请输入经度');
|
this.$message.warning('请输入经度');
|
||||||
return false;
|
return false;
|
||||||
@@ -1133,7 +1142,7 @@ export default {
|
|||||||
beforeOpen(done, type) {
|
beforeOpen(done, type) {
|
||||||
if (type === 'add') {
|
if (type === 'add') {
|
||||||
this.form.category = this.form.category || '港口';
|
this.form.category = this.form.category || '港口';
|
||||||
this.form.dataSource = this.form.dataSource || '手工导入';
|
this.form.dataSource = this.form.dataSource || '手动录入';
|
||||||
this.form.status = this.form.status || 1;
|
this.form.status = this.form.status || 1;
|
||||||
}
|
}
|
||||||
this.handleCategoryChange(this.form.category || '港口');
|
this.handleCategoryChange(this.form.category || '港口');
|
||||||
|
|||||||
@@ -347,7 +347,10 @@ export default {
|
|||||||
overHidden: false,
|
overHidden: false,
|
||||||
maxlength: 255,
|
maxlength: 255,
|
||||||
showWordLimit: true,
|
showWordLimit: true,
|
||||||
rules: [{ max: 255, message: '详细地址不能超过255字', trigger: 'blur' }],
|
rules: [
|
||||||
|
{ required: true, message: '请输入详细地址', trigger: 'blur' },
|
||||||
|
{ max: 255, message: '详细地址不能超过255字', trigger: 'blur' },
|
||||||
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: '经度',
|
label: '经度',
|
||||||
|
|||||||
@@ -166,7 +166,7 @@
|
|||||||
class="common-route-page__address-dialog"
|
class="common-route-page__address-dialog"
|
||||||
@opened="loadAddressList"
|
@opened="loadAddressList"
|
||||||
>
|
>
|
||||||
<section-card title="查询条件">
|
<section-card>
|
||||||
<div class="common-route-page__address-search">
|
<div class="common-route-page__address-search">
|
||||||
<el-form :model="addressQuery" label-position="right" label-width="86px">
|
<el-form :model="addressQuery" label-position="right" label-width="86px">
|
||||||
<el-row :gutter="16">
|
<el-row :gutter="16">
|
||||||
@@ -222,9 +222,6 @@
|
|||||||
</el-row>
|
</el-row>
|
||||||
</el-form>
|
</el-form>
|
||||||
</div>
|
</div>
|
||||||
</section-card>
|
|
||||||
|
|
||||||
<section-card title="常用地址">
|
|
||||||
<el-table
|
<el-table
|
||||||
border
|
border
|
||||||
:data="addressData"
|
:data="addressData"
|
||||||
|
|||||||
@@ -10,6 +10,9 @@
|
|||||||
<el-descriptions-item label="方案名称">{{
|
<el-descriptions-item label="方案名称">{{
|
||||||
displayValue(draft.planName)
|
displayValue(draft.planName)
|
||||||
}}</el-descriptions-item>
|
}}</el-descriptions-item>
|
||||||
|
<el-descriptions-item label="运输方式">{{
|
||||||
|
transportModeLabel(draft.transportMode)
|
||||||
|
}}</el-descriptions-item>
|
||||||
<el-descriptions-item label="默认方案">{{
|
<el-descriptions-item label="默认方案">{{
|
||||||
isDefaultPlan(draft) ? '是' : '否'
|
isDefaultPlan(draft) ? '是' : '否'
|
||||||
}}</el-descriptions-item>
|
}}</el-descriptions-item>
|
||||||
@@ -31,10 +34,27 @@
|
|||||||
maxlength="100"
|
maxlength="100"
|
||||||
:disabled="readonly" /></el-form-item
|
:disabled="readonly" /></el-form-item
|
||||||
></el-col>
|
></el-col>
|
||||||
|
<el-col :span="8"
|
||||||
|
><el-form-item label="运输方式" prop="transportMode"
|
||||||
|
><el-select
|
||||||
|
v-model="draft.transportMode"
|
||||||
|
clearable
|
||||||
|
filterable
|
||||||
|
:loading="transportModeLoading"
|
||||||
|
placeholder="请选择运输方式"
|
||||||
|
@visible-change="visible => visible && ensureTransportModes()"
|
||||||
|
><el-option
|
||||||
|
v-for="item in transportModeOptions"
|
||||||
|
:key="item.value"
|
||||||
|
:label="item.label"
|
||||||
|
:value="item.value" /></el-select></el-form-item
|
||||||
|
></el-col>
|
||||||
<el-col :span="8"
|
<el-col :span="8"
|
||||||
><el-form-item label="默认方案"
|
><el-form-item label="默认方案"
|
||||||
><el-checkbox v-model="draft.defaultPlan" :disabled="readonly"
|
><el-checkbox v-model="draft.defaultPlan" :disabled="readonly">默认方案</el-checkbox
|
||||||
>默认方案</el-checkbox
|
><el-icon class="billing-plan-editor__default-tip" @click="showDefaultPlanTip"
|
||||||
|
><InfoFilled
|
||||||
|
/></el-icon>
|
||||||
></el-form-item
|
></el-form-item
|
||||||
></el-col
|
></el-col
|
||||||
>
|
>
|
||||||
@@ -340,6 +360,7 @@
|
|||||||
import { getList as getCargoTypeList } from '@/api/base/cargo-type';
|
import { getList as getCargoTypeList } from '@/api/base/cargo-type';
|
||||||
import { getList as getFeeItemList } from '@/api/base/fee-item';
|
import { getList as getFeeItemList } from '@/api/base/fee-item';
|
||||||
import { getLazyTree as getRegionLazyTree } from '@/api/base/region';
|
import { getLazyTree as getRegionLazyTree } from '@/api/base/region';
|
||||||
|
import { InfoFilled } from '@element-plus/icons-vue';
|
||||||
import { getDictionary } from '@/api/system/dictbiz';
|
import { getDictionary } from '@/api/system/dictbiz';
|
||||||
|
|
||||||
const clone = value => JSON.parse(JSON.stringify(value));
|
const clone = value => JSON.parse(JSON.stringify(value));
|
||||||
@@ -370,6 +391,7 @@ const defaultRule = () => ({
|
|||||||
});
|
});
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
|
components: { InfoFilled },
|
||||||
props: {
|
props: {
|
||||||
modelValue: Boolean,
|
modelValue: Boolean,
|
||||||
value: { type: Object, default: () => ({}) },
|
value: { type: Object, default: () => ({}) },
|
||||||
@@ -417,7 +439,10 @@ export default {
|
|||||||
cargoFlatOptions: [],
|
cargoFlatOptions: [],
|
||||||
cargoLoading: false,
|
cargoLoading: false,
|
||||||
cargoRequest: null,
|
cargoRequest: null,
|
||||||
formRules: { planName: [{ required: true, message: '请输入方案名称', trigger: 'blur' }] },
|
formRules: {
|
||||||
|
planName: [{ required: true, message: '请输入方案名称', trigger: 'blur' }],
|
||||||
|
transportMode: [{ required: true, message: '请选择运输方式', trigger: 'change' }],
|
||||||
|
},
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
@@ -446,6 +471,7 @@ export default {
|
|||||||
},
|
},
|
||||||
mounted() {
|
mounted() {
|
||||||
this.loadDictionaries();
|
this.loadDictionaries();
|
||||||
|
this.ensureTransportModes();
|
||||||
},
|
},
|
||||||
watch: {
|
watch: {
|
||||||
value: {
|
value: {
|
||||||
@@ -474,7 +500,7 @@ export default {
|
|||||||
const option = this.transportModeOptions.find(
|
const option = this.transportModeOptions.find(
|
||||||
item => String(item.value) === String(value) || String(item.label) === String(value)
|
item => String(item.value) === String(value) || String(item.label) === String(value)
|
||||||
);
|
);
|
||||||
return this.displayValue(option?.label || value);
|
return this.displayValue(option?.label || this.draft.transportModeLabel || value);
|
||||||
},
|
},
|
||||||
isDefaultPlan(plan) {
|
isDefaultPlan(plan) {
|
||||||
return (
|
return (
|
||||||
@@ -484,7 +510,14 @@ export default {
|
|||||||
);
|
);
|
||||||
},
|
},
|
||||||
normalizePlan(value = {}) {
|
normalizePlan(value = {}) {
|
||||||
const plan = { planName: '', defaultPlan: false, remark: '', ...clone(value || {}) };
|
const plan = {
|
||||||
|
planName: '',
|
||||||
|
transportMode: '',
|
||||||
|
transportModeLabel: '',
|
||||||
|
defaultPlan: false,
|
||||||
|
remark: '',
|
||||||
|
...clone(value || {}),
|
||||||
|
};
|
||||||
plan.rules = (value?.rules?.length ? value.rules : [defaultRule()]).map(rule =>
|
plan.rules = (value?.rules?.length ? value.rules : [defaultRule()]).map(rule =>
|
||||||
this.normalizeRule(rule)
|
this.normalizeRule(rule)
|
||||||
);
|
);
|
||||||
@@ -818,6 +851,10 @@ export default {
|
|||||||
this.$refs.formRef.validate(valid => {
|
this.$refs.formRef.validate(valid => {
|
||||||
if (!valid || !this.validateRules()) return;
|
if (!valid || !this.validateRules()) return;
|
||||||
const plan = clone(this.draft);
|
const plan = clone(this.draft);
|
||||||
|
const transportMode = this.transportModeOptions.find(
|
||||||
|
item => String(item.value) === String(plan.transportMode)
|
||||||
|
);
|
||||||
|
plan.transportModeLabel = transportMode?.label || plan.transportMode;
|
||||||
plan.rules = plan.rules.map(rule => {
|
plan.rules = plan.rules.map(rule => {
|
||||||
const ranges = this.canEditLimit(rule) ? this.getRanges(rule) : [];
|
const ranges = this.canEditLimit(rule) ? this.getRanges(rule) : [];
|
||||||
return {
|
return {
|
||||||
@@ -837,6 +874,9 @@ export default {
|
|||||||
this.visible = false;
|
this.visible = false;
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
showDefaultPlanTip() {
|
||||||
|
this.$message.info('同一运输方式仅支持配置一个默认计费方案');
|
||||||
|
},
|
||||||
openMatch(row, index) {
|
openMatch(row, index) {
|
||||||
this.matchIndex = index;
|
this.matchIndex = index;
|
||||||
this.matchForm = { ...defaultRule().matchCondition, ...(row.matchCondition || {}) };
|
this.matchForm = { ...defaultRule().matchCondition, ...(row.matchCondition || {}) };
|
||||||
@@ -1097,6 +1137,12 @@ export default {
|
|||||||
margin: 12px 0;
|
margin: 12px 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.billing-plan-editor__default-tip {
|
||||||
|
margin-left: 6px;
|
||||||
|
color: #909399;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
.limit-list {
|
.limit-list {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
|
|||||||
@@ -4552,7 +4552,7 @@
|
|||||||
<el-cascader
|
<el-cascader
|
||||||
v-model="billingMatchForm.cargoTypePath"
|
v-model="billingMatchForm.cargoTypePath"
|
||||||
:options="billingCargoTypeOptions"
|
:options="billingCargoTypeOptions"
|
||||||
:props="billingCargoTypeCascaderProps"
|
:props="billingMatchCargoTypeCascaderProps"
|
||||||
:placeholder="billingMatchForm.cargoType || '请选择货物类型'"
|
:placeholder="billingMatchForm.cargoType || '请选择货物类型'"
|
||||||
:loading="billingCargoTypeLoading"
|
:loading="billingCargoTypeLoading"
|
||||||
:disabled="dialogReadonly"
|
:disabled="dialogReadonly"
|
||||||
@@ -4564,6 +4564,32 @@
|
|||||||
/>
|
/>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
</el-col>
|
</el-col>
|
||||||
|
<el-col :span="12">
|
||||||
|
<el-form-item label="货物名称">
|
||||||
|
<el-select
|
||||||
|
v-model="billingMatchForm.cargoNames"
|
||||||
|
multiple
|
||||||
|
collapse-tags
|
||||||
|
collapse-tags-tooltip
|
||||||
|
clearable
|
||||||
|
filterable
|
||||||
|
:loading="billingMatchCargoNameLoading"
|
||||||
|
:disabled="dialogReadonly || !billingMatchForm.cargoTypePath?.length"
|
||||||
|
:placeholder="
|
||||||
|
billingMatchForm.cargoTypePath?.length
|
||||||
|
? '请选择货物名称(可多选)'
|
||||||
|
: '请先选择货物类型'
|
||||||
|
"
|
||||||
|
>
|
||||||
|
<el-option
|
||||||
|
v-for="item in billingMatchCargoNameOptions"
|
||||||
|
:key="item.value"
|
||||||
|
:label="item.label"
|
||||||
|
:value="item.value"
|
||||||
|
/>
|
||||||
|
</el-select>
|
||||||
|
</el-form-item>
|
||||||
|
</el-col>
|
||||||
</el-row>
|
</el-row>
|
||||||
</el-form>
|
</el-form>
|
||||||
<template #footer>
|
<template #footer>
|
||||||
@@ -5304,6 +5330,7 @@ const defaultBillingRule = () => ({
|
|||||||
cargoType: '',
|
cargoType: '',
|
||||||
cargoTypeCode: '',
|
cargoTypeCode: '',
|
||||||
cargoTypePath: [],
|
cargoTypePath: [],
|
||||||
|
cargoNames: [],
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -5685,6 +5712,9 @@ export default {
|
|||||||
billingMatchRegionOptions: [],
|
billingMatchRegionOptions: [],
|
||||||
billingMatchRegionLoading: false,
|
billingMatchRegionLoading: false,
|
||||||
billingMatchRegionRequest: null,
|
billingMatchRegionRequest: null,
|
||||||
|
billingMatchCargoNameOptions: [],
|
||||||
|
billingMatchCargoNameLoading: false,
|
||||||
|
billingMatchCargoNameRequestId: 0,
|
||||||
billingCargoTypeOptions: [],
|
billingCargoTypeOptions: [],
|
||||||
billingCargoTypeFlatOptions: [],
|
billingCargoTypeFlatOptions: [],
|
||||||
billingCargoTypeLoading: false,
|
billingCargoTypeLoading: false,
|
||||||
@@ -6240,6 +6270,16 @@ export default {
|
|||||||
emitPath: true,
|
emitPath: true,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
billingMatchCargoTypeCascaderProps() {
|
||||||
|
return {
|
||||||
|
label: 'cargoName',
|
||||||
|
value: 'id',
|
||||||
|
children: 'children',
|
||||||
|
leaf: 'leaf',
|
||||||
|
checkStrictly: true,
|
||||||
|
emitPath: true,
|
||||||
|
};
|
||||||
|
},
|
||||||
billingCargoTypeCascaderProps() {
|
billingCargoTypeCascaderProps() {
|
||||||
return {
|
return {
|
||||||
label: 'cargoName',
|
label: 'cargoName',
|
||||||
@@ -10156,10 +10196,14 @@ export default {
|
|||||||
downloadFileByUrl(url, this.attachmentName(row));
|
downloadFileByUrl(url, this.attachmentName(row));
|
||||||
},
|
},
|
||||||
handleBatchDownload() {
|
handleBatchDownload() {
|
||||||
const rows = this.selectedAttachmentRows.length
|
const rows = this.isShippingTemplatePage
|
||||||
? this.selectedAttachmentRows
|
? this.attachmentRows
|
||||||
: this.attachmentRows;
|
: this.selectedAttachmentRows.length
|
||||||
rows.forEach(row => this.downloadAttachment(row));
|
? this.selectedAttachmentRows
|
||||||
|
: this.attachmentRows;
|
||||||
|
rows.forEach((row, index) => {
|
||||||
|
window.setTimeout(() => this.downloadAttachment(row), index * 200);
|
||||||
|
});
|
||||||
},
|
},
|
||||||
handleDispatchItemAttachmentChange(list) {
|
handleDispatchItemAttachmentChange(list) {
|
||||||
const userName = this.userInfo?.realName || this.userInfo?.userName || '';
|
const userName = this.userInfo?.realName || this.userInfo?.userName || '';
|
||||||
@@ -10472,6 +10516,33 @@ export default {
|
|||||||
null
|
null
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
findBillingMatchCargoTypeByPath(path = []) {
|
||||||
|
const values = this.normalizeBillingMatchCargoTypePath(path);
|
||||||
|
if (!values.length) return null;
|
||||||
|
return (
|
||||||
|
this.billingCargoTypeFlatOptions.find(
|
||||||
|
item =>
|
||||||
|
item.path?.length === values.length &&
|
||||||
|
item.path.every((value, index) => String(value) === values[index])
|
||||||
|
) || null
|
||||||
|
);
|
||||||
|
},
|
||||||
|
findBillingMatchCargoTypeByCodeOrName(condition = {}) {
|
||||||
|
const code = String(condition.cargoTypeCode || '').trim();
|
||||||
|
const name = String(condition.cargoType || '').trim();
|
||||||
|
return (
|
||||||
|
this.billingCargoTypeFlatOptions.find(item => {
|
||||||
|
const cargoCode = String(item.cargoCode || item.code || item.id || '');
|
||||||
|
return code && cargoCode === code;
|
||||||
|
}) ||
|
||||||
|
this.billingCargoTypeFlatOptions.find(item => {
|
||||||
|
const cargoName = this.formatBillingCargoTypeLabel(item);
|
||||||
|
const pathName = this.getBillingCargoTypePathLabels(item.path).join('/');
|
||||||
|
return name && (cargoName === name || pathName === name);
|
||||||
|
}) ||
|
||||||
|
null
|
||||||
|
);
|
||||||
|
},
|
||||||
resolveCommonCargoTypePath(row = {}) {
|
resolveCommonCargoTypePath(row = {}) {
|
||||||
const path = this.normalizeBillingCargoTypePath(row.cargoTypePath);
|
const path = this.normalizeBillingCargoTypePath(row.cargoTypePath);
|
||||||
if (path.length) return path;
|
if (path.length) return path;
|
||||||
@@ -10486,17 +10557,17 @@ export default {
|
|||||||
});
|
});
|
||||||
return cargoType?.path || [];
|
return cargoType?.path || [];
|
||||||
},
|
},
|
||||||
handleBillingMatchCargoTypeChange(value) {
|
handleBillingMatchCargoTypeChange(value, resetCargoNames = true) {
|
||||||
const path =
|
const path = this.normalizeBillingMatchCargoTypePath(value);
|
||||||
Array.isArray(value) && value.length >= 2
|
const cargoType = this.findBillingMatchCargoTypeByPath(path);
|
||||||
? value.slice(0, 2).map(item => String(item))
|
|
||||||
: [];
|
|
||||||
const cargoType = this.findBillingCargoTypeByPath(path);
|
|
||||||
const labels = this.getBillingCargoTypePathLabels(path);
|
const labels = this.getBillingCargoTypePathLabels(path);
|
||||||
this.billingMatchForm.cargoTypePath = path;
|
this.billingMatchForm.cargoTypePath = path;
|
||||||
|
if (resetCargoNames) this.billingMatchForm.cargoNames = [];
|
||||||
if (!path.length || !cargoType) {
|
if (!path.length || !cargoType) {
|
||||||
this.billingMatchForm.cargoType = '';
|
this.billingMatchForm.cargoType = '';
|
||||||
this.billingMatchForm.cargoTypeCode = '';
|
this.billingMatchForm.cargoTypeCode = '';
|
||||||
|
this.billingMatchCargoNameOptions = [];
|
||||||
|
this.billingMatchCargoNameRequestId += 1;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
this.billingMatchForm.cargoType =
|
this.billingMatchForm.cargoType =
|
||||||
@@ -10505,16 +10576,81 @@ export default {
|
|||||||
: labels[0] || this.formatBillingCargoTypeLabel(cargoType || {});
|
: labels[0] || this.formatBillingCargoTypeLabel(cargoType || {});
|
||||||
this.billingMatchForm.cargoTypeCode =
|
this.billingMatchForm.cargoTypeCode =
|
||||||
cargoType?.cargoCode || cargoType?.code || cargoType?.id || '';
|
cargoType?.cargoCode || cargoType?.code || cargoType?.id || '';
|
||||||
|
this.loadBillingMatchCargoNameOptions(path, !resetCargoNames);
|
||||||
|
},
|
||||||
|
loadBillingMatchCargoNameOptions(path = [], preserveSelected = false) {
|
||||||
|
const normalizedPath = this.normalizeBillingMatchCargoTypePath(path);
|
||||||
|
if (!normalizedPath.length) {
|
||||||
|
this.billingMatchCargoNameOptions = [];
|
||||||
|
this.billingMatchCargoNameLoading = false;
|
||||||
|
return Promise.resolve([]);
|
||||||
|
}
|
||||||
|
const requestId = ++this.billingMatchCargoNameRequestId;
|
||||||
|
const selected = preserveSelected
|
||||||
|
? this.normalizeBillingMatchCargoNames(this.billingMatchForm)
|
||||||
|
: [];
|
||||||
|
const firstCargoType = this.billingCargoTypeFlatOptions.find(
|
||||||
|
item => String(item.id) === normalizedPath[0] && item.path?.length === 1
|
||||||
|
);
|
||||||
|
const secondCargoType = this.findBillingCargoTypeByPath(normalizedPath);
|
||||||
|
const firstCargoTypeCode = firstCargoType?.cargoCode || firstCargoType?.code || '';
|
||||||
|
const secondCargoTypeCode = secondCargoType?.cargoCode || secondCargoType?.code || '';
|
||||||
|
this.billingMatchCargoNameLoading = true;
|
||||||
|
return getCommonCargoList(1, 9999, {
|
||||||
|
...(firstCargoTypeCode
|
||||||
|
? { firstCargoTypeCode }
|
||||||
|
: { firstCargoTypeId: normalizedPath[0] }),
|
||||||
|
...(normalizedPath[1]
|
||||||
|
? secondCargoTypeCode
|
||||||
|
? { secondCargoTypeCode }
|
||||||
|
: { secondCargoTypeId: normalizedPath[1] }
|
||||||
|
: {}),
|
||||||
|
allDept: 0,
|
||||||
|
})
|
||||||
|
.then(res => {
|
||||||
|
if (requestId !== this.billingMatchCargoNameRequestId) return [];
|
||||||
|
const names = extractRecords(res)
|
||||||
|
.map(item => String(item.cargoName || '').trim())
|
||||||
|
.filter(Boolean);
|
||||||
|
const options = [...new Set([...selected, ...names])];
|
||||||
|
this.billingMatchCargoNameOptions = options.map(value => ({ label: value, value }));
|
||||||
|
return this.billingMatchCargoNameOptions;
|
||||||
|
})
|
||||||
|
.catch(error => {
|
||||||
|
if (requestId === this.billingMatchCargoNameRequestId) {
|
||||||
|
this.billingMatchCargoNameOptions = selected.map(value => ({ label: value, value }));
|
||||||
|
}
|
||||||
|
window.console.log(error);
|
||||||
|
return [];
|
||||||
|
})
|
||||||
|
.finally(() => {
|
||||||
|
if (requestId === this.billingMatchCargoNameRequestId) {
|
||||||
|
this.billingMatchCargoNameLoading = false;
|
||||||
|
}
|
||||||
|
});
|
||||||
},
|
},
|
||||||
normalizeBillingCargoTypePath(path) {
|
normalizeBillingCargoTypePath(path) {
|
||||||
return Array.isArray(path) && path.length >= 2
|
return Array.isArray(path) && path.length >= 2
|
||||||
? path.slice(0, 2).map(value => String(value))
|
? path.slice(0, 2).map(value => String(value))
|
||||||
: [];
|
: [];
|
||||||
},
|
},
|
||||||
|
normalizeBillingMatchCargoTypePath(path) {
|
||||||
|
return Array.isArray(path) && path.length
|
||||||
|
? path.slice(0, 2).map(value => String(value))
|
||||||
|
: [];
|
||||||
|
},
|
||||||
|
normalizeBillingMatchCargoNames(condition = {}) {
|
||||||
|
const values = Array.isArray(condition.cargoNames)
|
||||||
|
? condition.cargoNames
|
||||||
|
: condition.cargoName
|
||||||
|
? [condition.cargoName]
|
||||||
|
: [];
|
||||||
|
return [...new Set(values.map(value => String(value || '').trim()).filter(Boolean))];
|
||||||
|
},
|
||||||
resolveBillingMatchCargoTypePath(condition = {}) {
|
resolveBillingMatchCargoTypePath(condition = {}) {
|
||||||
const path = this.normalizeBillingCargoTypePath(condition.cargoTypePath);
|
const path = this.normalizeBillingMatchCargoTypePath(condition.cargoTypePath);
|
||||||
if (path.length) return path;
|
if (path.length) return path;
|
||||||
const cargoType = this.findBillingCargoTypeByCodeOrName(condition);
|
const cargoType = this.findBillingMatchCargoTypeByCodeOrName(condition);
|
||||||
return cargoType?.path || [];
|
return cargoType?.path || [];
|
||||||
},
|
},
|
||||||
normalizeBillingMatchRegionPath(path) {
|
normalizeBillingMatchRegionPath(path) {
|
||||||
@@ -10532,7 +10668,10 @@ export default {
|
|||||||
nextCondition.destinationPath = this.normalizeBillingMatchRegionPath(
|
nextCondition.destinationPath = this.normalizeBillingMatchRegionPath(
|
||||||
condition?.destinationPath
|
condition?.destinationPath
|
||||||
);
|
);
|
||||||
nextCondition.cargoTypePath = this.normalizeBillingCargoTypePath(condition?.cargoTypePath);
|
nextCondition.cargoTypePath = this.normalizeBillingMatchCargoTypePath(
|
||||||
|
condition?.cargoTypePath
|
||||||
|
);
|
||||||
|
nextCondition.cargoNames = this.normalizeBillingMatchCargoNames(condition);
|
||||||
return nextCondition;
|
return nextCondition;
|
||||||
},
|
},
|
||||||
ensureBillingFeeCategoryOptions() {
|
ensureBillingFeeCategoryOptions() {
|
||||||
@@ -10888,12 +11027,13 @@ export default {
|
|||||||
openBillingMatch(row, index) {
|
openBillingMatch(row, index) {
|
||||||
this.billingMatchRuleIndex = index;
|
this.billingMatchRuleIndex = index;
|
||||||
this.billingMatchForm = this.normalizeBillingMatchCondition(row.matchCondition);
|
this.billingMatchForm = this.normalizeBillingMatchCondition(row.matchCondition);
|
||||||
|
this.billingMatchCargoNameOptions = [];
|
||||||
this.billingMatchBox = true;
|
this.billingMatchBox = true;
|
||||||
this.ensureBillingMatchRegionOptions();
|
this.ensureBillingMatchRegionOptions();
|
||||||
this.ensureBillingCargoTypeOptions().then(() => {
|
this.ensureBillingCargoTypeOptions().then(() => {
|
||||||
const path = this.resolveBillingMatchCargoTypePath(this.billingMatchForm);
|
const path = this.resolveBillingMatchCargoTypePath(this.billingMatchForm);
|
||||||
if (path.length) {
|
if (path.length) {
|
||||||
this.handleBillingMatchCargoTypeChange(path);
|
this.handleBillingMatchCargoTypeChange(path, false);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
@@ -10902,7 +11042,7 @@ export default {
|
|||||||
if (row) {
|
if (row) {
|
||||||
this.handleBillingMatchRegionChange('origin', this.billingMatchForm.originPath);
|
this.handleBillingMatchRegionChange('origin', this.billingMatchForm.originPath);
|
||||||
this.handleBillingMatchRegionChange('destination', this.billingMatchForm.destinationPath);
|
this.handleBillingMatchRegionChange('destination', this.billingMatchForm.destinationPath);
|
||||||
this.handleBillingMatchCargoTypeChange(this.billingMatchForm.cargoTypePath);
|
this.handleBillingMatchCargoTypeChange(this.billingMatchForm.cargoTypePath, false);
|
||||||
row.matchCondition = this.cloneData(this.billingMatchForm);
|
row.matchCondition = this.cloneData(this.billingMatchForm);
|
||||||
}
|
}
|
||||||
this.billingMatchBox = false;
|
this.billingMatchBox = false;
|
||||||
|
|||||||
@@ -80,7 +80,7 @@
|
|||||||
</el-col>
|
</el-col>
|
||||||
<el-col :span="4"><el-form-item label="联系人"><el-input v-model="route.departureContact" placeholder="请输入" /></el-form-item></el-col>
|
<el-col :span="4"><el-form-item label="联系人"><el-input v-model="route.departureContact" placeholder="请输入" /></el-form-item></el-col>
|
||||||
<el-col :span="4"><el-form-item label="联系方式"><el-input v-model="route.departurePhone" placeholder="请输入" /></el-form-item></el-col>
|
<el-col :span="4"><el-form-item label="联系方式"><el-input v-model="route.departurePhone" placeholder="请输入" /></el-form-item></el-col>
|
||||||
<el-col :span="4"><el-form-item :label="dateStartLabel(route)" required><el-date-picker v-model="route.estimatedStartTime" type="date" value-format="YYYY-MM-DD" placeholder="请选择" /></el-form-item></el-col>
|
<el-col :span="4"><el-form-item :label="dateStartLabel(route)"><el-date-picker v-model="route.estimatedStartTime" type="date" value-format="YYYY-MM-DD" placeholder="请选择" /></el-form-item></el-col>
|
||||||
</el-row>
|
</el-row>
|
||||||
<el-row :gutter="16">
|
<el-row :gutter="16">
|
||||||
<el-col :span="12">
|
<el-col :span="12">
|
||||||
@@ -88,7 +88,7 @@
|
|||||||
</el-col>
|
</el-col>
|
||||||
<el-col :span="4"><el-form-item label="联系人"><el-input v-model="route.arrivalContact" placeholder="请输入" /></el-form-item></el-col>
|
<el-col :span="4"><el-form-item label="联系人"><el-input v-model="route.arrivalContact" placeholder="请输入" /></el-form-item></el-col>
|
||||||
<el-col :span="4"><el-form-item label="联系方式"><el-input v-model="route.arrivalPhone" placeholder="请输入" /></el-form-item></el-col>
|
<el-col :span="4"><el-form-item label="联系方式"><el-input v-model="route.arrivalPhone" placeholder="请输入" /></el-form-item></el-col>
|
||||||
<el-col :span="4"><el-form-item :label="dateEndLabel(route)" required><el-date-picker v-model="route.estimatedEndTime" type="date" value-format="YYYY-MM-DD" placeholder="请选择" /></el-form-item></el-col>
|
<el-col :span="4"><el-form-item :label="dateEndLabel(route)"><el-date-picker v-model="route.estimatedEndTime" type="date" value-format="YYYY-MM-DD" placeholder="请选择" /></el-form-item></el-col>
|
||||||
</el-row>
|
</el-row>
|
||||||
</el-form>
|
</el-form>
|
||||||
|
|
||||||
@@ -601,8 +601,6 @@ export default {
|
|||||||
if (goods.some(item => Number(item.dispatchQuantity) > this.availableDispatchQuantity(route, item))) return this.$message.warning('本次数量不能超过剩余数量');
|
if (goods.some(item => Number(item.dispatchQuantity) > this.availableDispatchQuantity(route, item))) return this.$message.warning('本次数量不能超过剩余数量');
|
||||||
this.syncFreightItems(route);
|
this.syncFreightItems(route);
|
||||||
if (!this.validateFreightItems(route)) return;
|
if (!this.validateFreightItems(route)) return;
|
||||||
if (!route.estimatedStartTime) return this.$message.warning(`请选择${this.dateStartLabel(route)}`);
|
|
||||||
if (!route.estimatedEndTime) return this.$message.warning(`请选择${this.dateEndLabel(route)}`);
|
|
||||||
if (!this.validateRoutePhones(route)) return;
|
if (!this.validateRoutePhones(route)) return;
|
||||||
if (route.documentType === '运单') {
|
if (route.documentType === '运单') {
|
||||||
if (this.isRoad(route)) {
|
if (this.isRoad(route)) {
|
||||||
|
|||||||
@@ -103,6 +103,11 @@ const viewerPlugins = [
|
|||||||
textPlugin(),
|
textPlugin(),
|
||||||
fallbackPlugin(),
|
fallbackPlugin(),
|
||||||
];
|
];
|
||||||
|
const normalizeOptionalInteger = value => {
|
||||||
|
if (value === undefined || value === null || value === '' || Number(value) < 0) return null;
|
||||||
|
const number = Number(value);
|
||||||
|
return Number.isFinite(number) ? Math.trunc(number) : null;
|
||||||
|
};
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
components: { BillingPlanEditor, ElImageViewer, OpenFileViewer },
|
components: { BillingPlanEditor, ElImageViewer, OpenFileViewer },
|
||||||
@@ -111,7 +116,7 @@ export default {
|
|||||||
mounted() { this.load(); },
|
mounted() { this.load(); },
|
||||||
watch: { settlementConfigTab(tab, oldTab) { if (tab === oldTab) return; if (oldTab === 'pre') this.preSettlementConfig = { ...this.settlementRule }; else this.formalSettlementConfig = { ...this.settlementRule }; this.settlementRule = { ...(tab === 'pre' ? this.preSettlementConfig : this.formalSettlementConfig) }; } },
|
watch: { settlementConfigTab(tab, oldTab) { if (tab === oldTab) return; if (oldTab === 'pre') this.preSettlementConfig = { ...this.settlementRule }; else this.formalSettlementConfig = { ...this.settlementRule }; this.settlementRule = { ...(tab === 'pre' ? this.preSettlementConfig : this.formalSettlementConfig) }; } },
|
||||||
methods: {
|
methods: {
|
||||||
async load() { const id = this.$route.query.id; if (!id) return; const res = await api.getDetail(id); const data = res.data?.data || res.data || {}; this.form = { ...data, changeType: '合同信息变更' }; this.period = data.startDate && data.endDate ? [data.startDate, data.endDate] : []; this.plans = this.parse(data.billingPlanJson); this.attachments = this.parse(data.attachmentsJson); this.contractFileRows = this.parse(data.contractFileJson); const rules = this.parseObject(data.settlementRuleJson); const pre = this.parseObject(data.preSettlementConfigJson); const formal = this.parseObject(data.formalSettlementConfigJson); const legacy = Object.keys(rules).some(key => !['preSettlementConfig', 'formalSettlementConfig'].includes(key)) ? rules : {}; this.preSettlementConfig = rules.preSettlementConfig || (Object.keys(pre).length ? pre : legacy); this.formalSettlementConfig = rules.formalSettlementConfig || (Object.keys(formal).length ? formal : legacy); this.settlementRule = { ...this.preSettlementConfig }; this.feeGenerationMode = data.feeGenerationMode || (Number(data.billingEnabled) === 0 ? 'manual' : 'system'); this.paymentRatioRows = this.parse(data.paymentRatioJson); },
|
async load() { const id = this.$route.query.id; if (!id) return; const res = await api.getDetail(id); const data = res.data?.data || res.data || {}; this.form = { ...data, copyCount: normalizeOptionalInteger(data.copyCount), paymentDays: normalizeOptionalInteger(data.paymentDays), changeType: '合同信息变更' }; this.period = data.startDate && data.endDate ? [data.startDate, data.endDate] : []; this.plans = this.parse(data.billingPlanJson); this.attachments = this.parse(data.attachmentsJson); this.contractFileRows = this.parse(data.contractFileJson); const rules = this.parseObject(data.settlementRuleJson); const pre = this.parseObject(data.preSettlementConfigJson); const formal = this.parseObject(data.formalSettlementConfigJson); const legacy = Object.keys(rules).some(key => !['preSettlementConfig', 'formalSettlementConfig'].includes(key)) ? rules : {}; this.preSettlementConfig = rules.preSettlementConfig || (Object.keys(pre).length ? pre : legacy); this.formalSettlementConfig = rules.formalSettlementConfig || (Object.keys(formal).length ? formal : legacy); this.settlementRule = { ...this.preSettlementConfig }; this.feeGenerationMode = data.feeGenerationMode || (Number(data.billingEnabled) === 0 ? 'manual' : 'system'); this.paymentRatioRows = this.parse(data.paymentRatioJson); },
|
||||||
parse(value) { try { const result = JSON.parse(value || '[]'); return Array.isArray(result) ? result : []; } catch { return []; } },
|
parse(value) { try { const result = JSON.parse(value || '[]'); return Array.isArray(result) ? result : []; } catch { return []; } },
|
||||||
parseObject(value) { try { return { autoGenerate: 1, settlementType: '月结', billCycleType: '固定截单日', billCutoffDay: 25, cycleDays: 15, ...(JSON.parse(value || '{}') || {}) }; } catch { return { autoGenerate: 1, settlementType: '月结', billCycleType: '固定截单日', billCutoffDay: 25, cycleDays: 15 }; } },
|
parseObject(value) { try { return { autoGenerate: 1, settlementType: '月结', billCycleType: '固定截单日', billCutoffDay: 25, cycleDays: 15, ...(JSON.parse(value || '{}') || {}) }; } catch { return { autoGenerate: 1, settlementType: '月结', billCycleType: '固定截单日', billCutoffDay: 25, cycleDays: 15 }; } },
|
||||||
addPlan() { this.planEditorIndex = -1; this.planEditor = { planName: `计费方案${this.plans.length + 1}`, defaultPlan: !this.plans.length, remark: '', rules: [{}] }; this.planDialogVisible = true; },
|
addPlan() { this.planEditorIndex = -1; this.planEditor = { planName: `计费方案${this.plans.length + 1}`, defaultPlan: !this.plans.length, remark: '', rules: [{}] }; this.planDialogVisible = true; },
|
||||||
@@ -134,7 +139,7 @@ export default {
|
|||||||
formatFileSize(value) { const size = Number(value || 0); return size > 1024 * 1024 ? `${(size / 1024 / 1024).toFixed(2)}MB` : `${Math.max(1, Math.ceil(size / 1024))}KB`; },
|
formatFileSize(value) { const size = Number(value || 0); return size > 1024 * 1024 ? `${(size / 1024 / 1024).toFixed(2)}MB` : `${Math.max(1, Math.ceil(size / 1024))}KB`; },
|
||||||
handleChangeMaterial(event) { if (event.raw) this.changeMaterials.push(event.raw); },
|
handleChangeMaterial(event) { if (event.raw) this.changeMaterials.push(event.raw); },
|
||||||
addPaymentRatioRow() { this.paymentRatioRows.push({ paymentTerm: `第${this.paymentRatioRows.length + 1}笔`, ratioLimit: '', remark: '' }); },
|
addPaymentRatioRow() { this.paymentRatioRows.push({ paymentTerm: `第${this.paymentRatioRows.length + 1}笔`, ratioLimit: '', remark: '' }); },
|
||||||
async submit() { await this.$refs.formRef.validate(); const total = this.paymentRatioRows.reduce((sum, row) => sum + Number(row.ratioLimit || 0), 0); if (this.paymentRatioRows.length && Math.abs(total - 100) > 0.0001) { this.$message.warning('付款比例上限合计必须等于100%'); return; } const settlementRule = { preSettlementConfig: this.preSettlementConfig, formalSettlementConfig: this.formalSettlementConfig }; await api.submitChange({ ...this.form, startDate: this.period[0], endDate: this.period[1], feeGenerationMode: this.feeGenerationMode, billingEnabled: this.feeGenerationMode === 'system' ? 1 : 0, billingPlanJson: JSON.stringify(this.plans), settlementRuleJson: JSON.stringify(settlementRule), preSettlementConfigJson: JSON.stringify(this.preSettlementConfig), formalSettlementConfigJson: JSON.stringify(this.formalSettlementConfig), paymentRatioJson: JSON.stringify(this.paymentRatioRows), contractFileJson: JSON.stringify(this.contractFileRows), attachmentsJson: JSON.stringify(this.attachments), changeContent: this.form.changeReason, changeReason: this.form.changeReason }); this.$message.success('变更已提交'); this.$router.back(); },
|
async submit() { await this.$refs.formRef.validate(); const total = this.paymentRatioRows.reduce((sum, row) => sum + Number(row.ratioLimit || 0), 0); if (this.paymentRatioRows.length && Math.abs(total - 100) > 0.0001) { this.$message.warning('付款比例上限合计必须等于100%'); return; } const settlementRule = { preSettlementConfig: this.preSettlementConfig, formalSettlementConfig: this.formalSettlementConfig }; await api.submitChange({ ...this.form, copyCount: normalizeOptionalInteger(this.form.copyCount), paymentDays: normalizeOptionalInteger(this.form.paymentDays), startDate: this.period[0], endDate: this.period[1], feeGenerationMode: this.feeGenerationMode, billingEnabled: this.feeGenerationMode === 'system' ? 1 : 0, billingPlanJson: JSON.stringify(this.plans), settlementRuleJson: JSON.stringify(settlementRule), preSettlementConfigJson: JSON.stringify(this.preSettlementConfig), formalSettlementConfigJson: JSON.stringify(this.formalSettlementConfig), paymentRatioJson: JSON.stringify(this.paymentRatioRows), contractFileJson: JSON.stringify(this.contractFileRows), attachmentsJson: JSON.stringify(this.attachments), changeContent: this.form.changeReason, changeReason: this.form.changeReason }); this.$message.success('变更已提交'); this.$router.back(); },
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -178,7 +178,7 @@
|
|||||||
filterable
|
filterable
|
||||||
clearable
|
clearable
|
||||||
:loading="contractPartyLoading"
|
:loading="contractPartyLoading"
|
||||||
:disabled="!form.projectId"
|
@visible-change="visible => visible && loadContractPartyOptions()"
|
||||||
>
|
>
|
||||||
<el-option
|
<el-option
|
||||||
v-for="item in contractPartyAOptions"
|
v-for="item in contractPartyAOptions"
|
||||||
@@ -195,7 +195,7 @@
|
|||||||
filterable
|
filterable
|
||||||
clearable
|
clearable
|
||||||
:loading="contractPartyLoading"
|
:loading="contractPartyLoading"
|
||||||
:disabled="!form.projectId"
|
@visible-change="visible => visible && loadContractPartyOptions()"
|
||||||
>
|
>
|
||||||
<el-option
|
<el-option
|
||||||
v-for="item in contractPartyBOptions"
|
v-for="item in contractPartyBOptions"
|
||||||
@@ -324,16 +324,11 @@
|
|||||||
<el-table :data="billingPlanRows" border>
|
<el-table :data="billingPlanRows" border>
|
||||||
<el-table-column type="index" label="序号" width="80" align="center" />
|
<el-table-column type="index" label="序号" width="80" align="center" />
|
||||||
<el-table-column prop="planName" label="方案名称" min-width="220" align="center" />
|
<el-table-column prop="planName" label="方案名称" min-width="220" align="center" />
|
||||||
<el-table-column label="默认方案" width="140" align="center">
|
<el-table-column label="运输方式" min-width="180" align="center">
|
||||||
<template #default="{ row }">{{ isDefaultBillingPlan(row) ? '是' : '' }}</template>
|
<template #default="{ row }">{{
|
||||||
|
row.transportModeLabel || row.transportMode || '-'
|
||||||
|
}}</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
<el-table-column
|
|
||||||
prop="remark"
|
|
||||||
label="备注"
|
|
||||||
min-width="260"
|
|
||||||
align="center"
|
|
||||||
show-overflow-tooltip
|
|
||||||
/>
|
|
||||||
<el-table-column label="操作" width="180" align="center" fixed="right">
|
<el-table-column label="操作" width="180" align="center" fixed="right">
|
||||||
<template #default="{ row, $index }">
|
<template #default="{ row, $index }">
|
||||||
<el-link type="primary" @click="openBillingPlan(row, $index)">编辑</el-link>
|
<el-link type="primary" @click="openBillingPlan(row, $index)">编辑</el-link>
|
||||||
@@ -431,14 +426,13 @@
|
|||||||
<el-table-column prop="paymentTerm" label="付款笔数" min-width="180" align="center" />
|
<el-table-column prop="paymentTerm" label="付款笔数" min-width="180" align="center" />
|
||||||
<el-table-column label="付款比例上限" min-width="220" align="center">
|
<el-table-column label="付款比例上限" min-width="220" align="center">
|
||||||
<template #default="{ row }">
|
<template #default="{ row }">
|
||||||
<el-input-number
|
<el-input
|
||||||
v-model="row.ratioLimit"
|
v-model="row.ratioLimit"
|
||||||
:min="0"
|
placeholder="请输入"
|
||||||
:max="100"
|
@input="value => ratioInput(row, value)"
|
||||||
:precision="2"
|
>
|
||||||
controls-position="right"
|
<template #append>%</template>
|
||||||
/>
|
</el-input>
|
||||||
<span class="contract-manage-form__ratio-unit">%</span>
|
|
||||||
</template>
|
</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
<el-table-column label="备注" min-width="220">
|
<el-table-column label="备注" min-width="220">
|
||||||
@@ -532,6 +526,7 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<billing-plan-editor
|
<billing-plan-editor
|
||||||
|
ref="billingPlanEditor"
|
||||||
v-model="billingPlanBox"
|
v-model="billingPlanBox"
|
||||||
:value="billingPlanForm"
|
:value="billingPlanForm"
|
||||||
:index="billingPlanIndex"
|
:index="billingPlanIndex"
|
||||||
@@ -648,16 +643,11 @@
|
|||||||
<el-table :data="detailBillingPlanRows" border>
|
<el-table :data="detailBillingPlanRows" border>
|
||||||
<el-table-column type="index" label="序号" width="80" align="center" />
|
<el-table-column type="index" label="序号" width="80" align="center" />
|
||||||
<el-table-column prop="planName" label="方案名称" min-width="220" align="center" />
|
<el-table-column prop="planName" label="方案名称" min-width="220" align="center" />
|
||||||
<el-table-column label="默认方案" width="140" align="center">
|
<el-table-column label="运输方式" min-width="180" align="center">
|
||||||
<template #default="{ row }">{{ isDefaultBillingPlan(row) ? '是' : '' }}</template>
|
<template #default="{ row }">{{
|
||||||
|
row.transportModeLabel || row.transportMode || '-'
|
||||||
|
}}</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
<el-table-column
|
|
||||||
prop="remark"
|
|
||||||
label="备注"
|
|
||||||
min-width="260"
|
|
||||||
align="center"
|
|
||||||
show-overflow-tooltip
|
|
||||||
/>
|
|
||||||
<el-table-column label="操作" width="120" align="center" fixed="right">
|
<el-table-column label="操作" width="120" align="center" fixed="right">
|
||||||
<template #default="{ row, $index }">
|
<template #default="{ row, $index }">
|
||||||
<el-link type="primary" @click="openDetailBillingPlan(row, $index)"
|
<el-link type="primary" @click="openDetailBillingPlan(row, $index)"
|
||||||
@@ -794,10 +784,8 @@ import NProgress from 'nprogress';
|
|||||||
import 'nprogress/nprogress.css';
|
import 'nprogress/nprogress.css';
|
||||||
import { exportBlob } from '@/api/common';
|
import { exportBlob } from '@/api/common';
|
||||||
import * as api from '@/api/business/contract-manage';
|
import * as api from '@/api/business/contract-manage';
|
||||||
import {
|
import { getList as getProjectList } from '@/api/business/project-apply';
|
||||||
getDetail as getProjectDetail,
|
import { getList as getCustomerArchiveList } from '@/api/vehicle/customer-archive';
|
||||||
getList as getProjectList,
|
|
||||||
} from '@/api/business/project-apply';
|
|
||||||
import { getDeptTree } from '@/api/system/dept';
|
import { getDeptTree } from '@/api/system/dept';
|
||||||
import { config, option } from '@/option/business/contract-manage';
|
import { config, option } from '@/option/business/contract-manage';
|
||||||
import { getToken } from '@/utils/auth';
|
import { getToken } from '@/utils/auth';
|
||||||
@@ -816,6 +804,13 @@ import '@open-file-viewer/core/style.css';
|
|||||||
import pdfWorkerSrc from 'pdfjs-dist/build/pdf.worker.mjs?url';
|
import pdfWorkerSrc from 'pdfjs-dist/build/pdf.worker.mjs?url';
|
||||||
|
|
||||||
const clone = value => JSON.parse(JSON.stringify(value));
|
const clone = value => JSON.parse(JSON.stringify(value));
|
||||||
|
const normalizeOptionalInteger = (value, emptyValue = null) => {
|
||||||
|
if (value === undefined || value === null || value === '' || Number(value) < 0) {
|
||||||
|
return emptyValue;
|
||||||
|
}
|
||||||
|
const number = Number(value);
|
||||||
|
return Number.isFinite(number) ? Math.trunc(number) : emptyValue;
|
||||||
|
};
|
||||||
const defaultSettlementRule = () => ({
|
const defaultSettlementRule = () => ({
|
||||||
autoGenerate: 1,
|
autoGenerate: 1,
|
||||||
billStartDate: '',
|
billStartDate: '',
|
||||||
@@ -1459,7 +1454,10 @@ export default {
|
|||||||
},
|
},
|
||||||
initFormPage() {
|
initFormPage() {
|
||||||
this.resetFormState();
|
this.resetFormState();
|
||||||
if (this.formMode === 'add') return;
|
if (this.formMode === 'add') {
|
||||||
|
this.loadContractPartyOptions();
|
||||||
|
return;
|
||||||
|
}
|
||||||
const id = this.$route.query.id;
|
const id = this.$route.query.id;
|
||||||
if (!id) return;
|
if (!id) return;
|
||||||
this.api.getDetail(id).then(res => this.applyDetail(res.data?.data || {}));
|
this.api.getDetail(id).then(res => this.applyDetail(res.data?.data || {}));
|
||||||
@@ -1489,6 +1487,8 @@ export default {
|
|||||||
this.form = {
|
this.form = {
|
||||||
...defaultForm(),
|
...defaultForm(),
|
||||||
...detail,
|
...detail,
|
||||||
|
copyCount: normalizeOptionalInteger(detail.copyCount, ''),
|
||||||
|
paymentDays: normalizeOptionalInteger(detail.paymentDays, ''),
|
||||||
feeGenerationMode:
|
feeGenerationMode:
|
||||||
detail.feeGenerationMode || (Number(detail.billingEnabled) === 0 ? 'manual' : 'system'),
|
detail.feeGenerationMode || (Number(detail.billingEnabled) === 0 ? 'manual' : 'system'),
|
||||||
};
|
};
|
||||||
@@ -1517,7 +1517,7 @@ export default {
|
|||||||
this.settlementRuleForm = { ...this.preSettlementRuleForm };
|
this.settlementRuleForm = { ...this.preSettlementRuleForm };
|
||||||
this.syncCurrentProjectOption();
|
this.syncCurrentProjectOption();
|
||||||
this.syncCurrentOrganizationOption();
|
this.syncCurrentOrganizationOption();
|
||||||
this.loadContractPartyOptions(detail.projectId, detail, true);
|
this.loadContractPartyOptions();
|
||||||
},
|
},
|
||||||
closeForm() {
|
closeForm() {
|
||||||
this.$router.$avueRouter?.closeTag?.();
|
this.$router.$avueRouter?.closeTag?.();
|
||||||
@@ -1563,6 +1563,8 @@ export default {
|
|||||||
this.syncSettlementConfig();
|
this.syncSettlementConfig();
|
||||||
return {
|
return {
|
||||||
...this.form,
|
...this.form,
|
||||||
|
copyCount: normalizeOptionalInteger(this.form.copyCount),
|
||||||
|
paymentDays: normalizeOptionalInteger(this.form.paymentDays),
|
||||||
billingEnabled: this.form.feeGenerationMode === 'manual' ? 0 : 1,
|
billingEnabled: this.form.feeGenerationMode === 'manual' ? 0 : 1,
|
||||||
contractFileJson: JSON.stringify(this.contractFileRows),
|
contractFileJson: JSON.stringify(this.contractFileRows),
|
||||||
attachmentsJson: JSON.stringify(this.attachmentRows),
|
attachmentsJson: JSON.stringify(this.attachmentRows),
|
||||||
@@ -1646,76 +1648,29 @@ export default {
|
|||||||
handleProjectChange(projectId) {
|
handleProjectChange(projectId) {
|
||||||
const project = this.projectOptions.find(item => String(item.id) === String(projectId));
|
const project = this.projectOptions.find(item => String(item.id) === String(projectId));
|
||||||
if (!project) {
|
if (!project) {
|
||||||
Object.assign(this.form, { projectId: '', projectName: '', partyA: '', partyB: '' });
|
Object.assign(this.form, { projectId: '', projectName: '' });
|
||||||
this.contractPartyAOptions = [];
|
|
||||||
this.contractPartyBOptions = [];
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
Object.assign(this.form, {
|
Object.assign(this.form, {
|
||||||
projectId: project.id,
|
projectId: project.id,
|
||||||
projectName: project.projectName || '',
|
projectName: project.projectName || '',
|
||||||
partyA: '',
|
|
||||||
partyB: '',
|
|
||||||
});
|
});
|
||||||
this.loadContractPartyOptions(project.id, project, false);
|
|
||||||
},
|
},
|
||||||
partyNames(value, type) {
|
loadContractPartyOptions() {
|
||||||
const names = [];
|
if (this.contractPartyLoading) return;
|
||||||
const append = item => {
|
|
||||||
if (item === undefined || item === null) return;
|
|
||||||
if (Array.isArray(item)) return item.forEach(append);
|
|
||||||
if (typeof item === 'object') {
|
|
||||||
append(
|
|
||||||
item[type === 'carrier' ? 'carrier' : 'customer'] ||
|
|
||||||
item.fullName ||
|
|
||||||
item.shortName ||
|
|
||||||
item.customerName ||
|
|
||||||
item.carrierName ||
|
|
||||||
item.name
|
|
||||||
);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const text = String(item).trim();
|
|
||||||
if (!text) return;
|
|
||||||
try {
|
|
||||||
const parsed = JSON.parse(text);
|
|
||||||
if (parsed !== item) return append(parsed);
|
|
||||||
} catch (error) {
|
|
||||||
// 普通名称按项目维护时使用的分隔符拆分。
|
|
||||||
}
|
|
||||||
text
|
|
||||||
.split(/[、,,;;\n]/)
|
|
||||||
.map(name => name.trim())
|
|
||||||
.filter(Boolean)
|
|
||||||
.forEach(name => {
|
|
||||||
if (!names.includes(name)) names.push(name);
|
|
||||||
});
|
|
||||||
};
|
|
||||||
append(value);
|
|
||||||
return names;
|
|
||||||
},
|
|
||||||
syncContractPartyOptions(project, preserve) {
|
|
||||||
const customers = this.partyNames(
|
|
||||||
[project.customerJson, project.customerNames, project.customerName, project.customers],
|
|
||||||
'customer'
|
|
||||||
);
|
|
||||||
const carriers = this.partyNames(
|
|
||||||
[project.carrierJson, project.carrierNames, project.carrierName, project.carriers],
|
|
||||||
'carrier'
|
|
||||||
);
|
|
||||||
this.contractPartyAOptions = customers.map(name => ({ label: name, value: name }));
|
|
||||||
this.contractPartyBOptions = carriers.map(name => ({ label: name, value: name }));
|
|
||||||
if (!preserve) Object.assign(this.form, { partyA: '', partyB: '' });
|
|
||||||
},
|
|
||||||
loadContractPartyOptions(projectId, project = {}, preserve = true) {
|
|
||||||
if (!projectId) return;
|
|
||||||
const requestId = ++this.contractPartyRequestId;
|
const requestId = ++this.contractPartyRequestId;
|
||||||
this.syncContractPartyOptions(project, preserve);
|
|
||||||
this.contractPartyLoading = true;
|
this.contractPartyLoading = true;
|
||||||
getProjectDetail(projectId)
|
getCustomerArchiveList(1, 9999, { status: 1, approvalStatus: 'approved' })
|
||||||
.then(res => {
|
.then(res => {
|
||||||
if (requestId !== this.contractPartyRequestId) return;
|
if (requestId !== this.contractPartyRequestId) return;
|
||||||
this.syncContractPartyOptions({ ...project, ...(res.data?.data || {}) }, preserve);
|
const names = extractRecords(res)
|
||||||
|
.map(item => item.fullName || item.shortName || item.customerName || item.customerCode)
|
||||||
|
.map(name => String(name || '').trim())
|
||||||
|
.filter(Boolean);
|
||||||
|
[this.form.partyA, this.form.partyB].filter(Boolean).forEach(name => names.unshift(name));
|
||||||
|
const options = [...new Set(names)].map(name => ({ label: name, value: name }));
|
||||||
|
this.contractPartyAOptions = options;
|
||||||
|
this.contractPartyBOptions = options;
|
||||||
})
|
})
|
||||||
.finally(() => {
|
.finally(() => {
|
||||||
if (requestId === this.contractPartyRequestId) this.contractPartyLoading = false;
|
if (requestId === this.contractPartyRequestId) this.contractPartyLoading = false;
|
||||||
@@ -1818,14 +1773,19 @@ export default {
|
|||||||
openBillingPlan(row = {}, index = -1) {
|
openBillingPlan(row = {}, index = -1) {
|
||||||
this.billingPlanIndex = index;
|
this.billingPlanIndex = index;
|
||||||
this.billingPlanForm =
|
this.billingPlanForm =
|
||||||
index < 0 ? { planName: '', defaultPlan: false, remark: '', rules: [] } : clone(row);
|
index < 0
|
||||||
|
? { planName: '', transportMode: '', defaultPlan: false, remark: '', rules: [] }
|
||||||
|
: clone(row);
|
||||||
this.billingPlanBox = true;
|
this.billingPlanBox = true;
|
||||||
|
this.$nextTick(() => this.$refs.billingPlanEditor?.ensureTransportModes?.());
|
||||||
},
|
},
|
||||||
saveBillingPlan(plan, index) {
|
saveBillingPlan(plan, index) {
|
||||||
const nextPlan = clone(plan);
|
const nextPlan = clone(plan);
|
||||||
if (this.isDefaultBillingPlan(nextPlan)) {
|
if (this.isDefaultBillingPlan(nextPlan)) {
|
||||||
this.billingPlanRows.forEach((item, itemIndex) => {
|
this.billingPlanRows.forEach((item, itemIndex) => {
|
||||||
if (itemIndex !== index) item.defaultPlan = false;
|
if (itemIndex !== index && item.transportMode === nextPlan.transportMode) {
|
||||||
|
item.defaultPlan = false;
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
if (index < 0) this.billingPlanRows.push(nextPlan);
|
if (index < 0) this.billingPlanRows.push(nextPlan);
|
||||||
@@ -1846,9 +1806,18 @@ export default {
|
|||||||
},
|
},
|
||||||
validateBillingPlanDefault() {
|
validateBillingPlanDefault() {
|
||||||
if (!this.billingPlanRows.length) return true;
|
if (!this.billingPlanRows.length) return true;
|
||||||
const count = this.billingPlanRows.filter(this.isDefaultBillingPlan).length;
|
const defaultCountByTransportMode = this.billingPlanRows.reduce((result, plan) => {
|
||||||
if (count !== 1) {
|
const transportMode = plan.transportMode || '';
|
||||||
this.$message.warning(count ? '请仅保留一个默认计费方案' : '请勾选一个默认计费方案');
|
result[transportMode] =
|
||||||
|
(result[transportMode] || 0) + Number(this.isDefaultBillingPlan(plan));
|
||||||
|
return result;
|
||||||
|
}, {});
|
||||||
|
const invalidTransportMode = Object.entries(defaultCountByTransportMode).find(
|
||||||
|
([, count]) => count > 1
|
||||||
|
);
|
||||||
|
if (invalidTransportMode) {
|
||||||
|
const [transportMode, count] = invalidTransportMode;
|
||||||
|
this.$message.warning(`运输方式“${transportMode}”仅支持配置一个默认计费方案`);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
@@ -1932,6 +1901,16 @@ export default {
|
|||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
},
|
},
|
||||||
|
ratioInput(row, value) {
|
||||||
|
let nextValue = String(value ?? '').replace(/[^\d.]/g, '');
|
||||||
|
const parts = nextValue.split('.');
|
||||||
|
if (parts.length > 2) nextValue = `${parts.shift()}.${parts.join('')}`;
|
||||||
|
const [integerPart, decimalPart] = nextValue.split('.');
|
||||||
|
nextValue =
|
||||||
|
decimalPart === undefined ? integerPart : `${integerPart}.${decimalPart.slice(0, 2)}`;
|
||||||
|
if (Number(nextValue) > 100) nextValue = '100';
|
||||||
|
row.ratioLimit = nextValue;
|
||||||
|
},
|
||||||
openDetail(row) {
|
openDetail(row) {
|
||||||
this.detailBox = true;
|
this.detailBox = true;
|
||||||
this.detailLoading = true;
|
this.detailLoading = true;
|
||||||
@@ -1946,7 +1925,11 @@ export default {
|
|||||||
});
|
});
|
||||||
},
|
},
|
||||||
applyDetailState(detail = {}) {
|
applyDetailState(detail = {}) {
|
||||||
this.detailRow = { ...detail };
|
this.detailRow = {
|
||||||
|
...detail,
|
||||||
|
copyCount: normalizeOptionalInteger(detail.copyCount),
|
||||||
|
paymentDays: normalizeOptionalInteger(detail.paymentDays),
|
||||||
|
};
|
||||||
this.detailContractFileRows = parseArray(detail.contractFileJson);
|
this.detailContractFileRows = parseArray(detail.contractFileJson);
|
||||||
this.detailAttachmentRows = parseArray(detail.attachmentsJson);
|
this.detailAttachmentRows = parseArray(detail.attachmentsJson);
|
||||||
this.detailBillingPlanRows = parseArray(detail.billingPlanJson);
|
this.detailBillingPlanRows = parseArray(detail.billingPlanJson);
|
||||||
@@ -1978,7 +1961,9 @@ export default {
|
|||||||
},
|
},
|
||||||
detailUnitValue(prop, unit) {
|
detailUnitValue(prop, unit) {
|
||||||
const value = this.detailRow[prop];
|
const value = this.detailRow[prop];
|
||||||
return value === undefined || value === null || value === '' ? '-' : `${value}${unit}`;
|
return value === undefined || value === null || value === '' || Number(value) < 0
|
||||||
|
? ''
|
||||||
|
: `${value}${unit}`;
|
||||||
},
|
},
|
||||||
detailObjectUnitValue(row, prop, unit) {
|
detailObjectUnitValue(row, prop, unit) {
|
||||||
const value = row?.[prop];
|
const value = row?.[prop];
|
||||||
@@ -2017,7 +2002,7 @@ export default {
|
|||||||
const run = value => {
|
const run = value => {
|
||||||
const args = operation.prompt ? [row.id, value] : [row.id];
|
const args = operation.prompt ? [row.id, value] : [row.id];
|
||||||
this.api[operation.action](...args).then(() => {
|
this.api[operation.action](...args).then(() => {
|
||||||
this.$message.success(`${operation.label}成功`);
|
this.$message.success(operation.successMessage || `${operation.label}成功`);
|
||||||
this.onLoad(this.page, this.query);
|
this.onLoad(this.page, this.query);
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
@@ -2029,9 +2014,9 @@ export default {
|
|||||||
inputErrorMessage: '请输入内容',
|
inputErrorMessage: '请输入内容',
|
||||||
}).then(({ value }) => run(value));
|
}).then(({ value }) => run(value));
|
||||||
} else {
|
} else {
|
||||||
this.$confirm(`确定${operation.label}该合同?`, '提示', { type: 'warning' }).then(() =>
|
this.$confirm(operation.confirmMessage || `确定${operation.label}该合同?`, '提示', {
|
||||||
run()
|
type: 'warning',
|
||||||
);
|
}).then(() => run());
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
removeRow(row) {
|
removeRow(row) {
|
||||||
|
|||||||
@@ -391,7 +391,11 @@
|
|||||||
<el-table :data="customerRows" border class="project-apply-form__table">
|
<el-table :data="customerRows" border class="project-apply-form__table">
|
||||||
<el-table-column type="index" label="序号" width="80" align="center" />
|
<el-table-column type="index" label="序号" width="80" align="center" />
|
||||||
<el-table-column prop="customer" label="客户" min-width="160" align="center" />
|
<el-table-column prop="customer" label="客户" min-width="160" align="center" />
|
||||||
<el-table-column prop="credit" label="客户信用额度" min-width="150" align="center" />
|
<el-table-column prop="credit" label="客户信用额度" min-width="150" align="center">
|
||||||
|
<template #default="{ row }">
|
||||||
|
{{ normalizeReadonlyAmount(row.credit) }}
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
<el-table-column prop="companyNature" label="企业性质" min-width="140" align="center" />
|
<el-table-column prop="companyNature" label="企业性质" min-width="140" align="center" />
|
||||||
<el-table-column prop="legalPerson" label="法定代表人" min-width="140" align="center" />
|
<el-table-column prop="legalPerson" label="法定代表人" min-width="140" align="center" />
|
||||||
<el-table-column
|
<el-table-column
|
||||||
@@ -505,7 +509,11 @@
|
|||||||
</template>
|
</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
<el-table-column label="文件名" min-width="220" align="center" show-overflow-tooltip>
|
<el-table-column label="文件名" min-width="220" align="center" show-overflow-tooltip>
|
||||||
<template #default="{ row }">{{ row.originalName || row.name }}</template>
|
<template #default="{ row }">
|
||||||
|
<el-link type="primary" @click="previewAttachment(row)">
|
||||||
|
{{ attachmentName(row) }}
|
||||||
|
</el-link>
|
||||||
|
</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
<el-table-column label="附件描述" min-width="240" align="center">
|
<el-table-column label="附件描述" min-width="240" align="center">
|
||||||
<template #default="{ row }">
|
<template #default="{ row }">
|
||||||
@@ -536,6 +544,7 @@
|
|||||||
:readonly="dialogReadonly"
|
:readonly="dialogReadonly"
|
||||||
:file-types="attachmentFileTypes"
|
:file-types="attachmentFileTypes"
|
||||||
:max-size="500"
|
:max-size="500"
|
||||||
|
:show-file-list="false"
|
||||||
button-text="上传附件"
|
button-text="上传附件"
|
||||||
tip="支持pdf、bmp、jpeg、png、jpg、doc、docx、ppt、pptx、xlsx、xls、eml、msg、zip的文件格式,单个文件不超过500M"
|
tip="支持pdf、bmp、jpeg、png、jpg、doc、docx、ppt、pptx、xlsx、xls、eml、msg、zip的文件格式,单个文件不超过500M"
|
||||||
@change="handleAttachmentChange"
|
@change="handleAttachmentChange"
|
||||||
@@ -642,6 +651,38 @@
|
|||||||
</div>
|
</div>
|
||||||
</component>
|
</component>
|
||||||
|
|
||||||
|
<el-dialog
|
||||||
|
v-model="attachmentDocumentPreviewVisible"
|
||||||
|
:title="attachmentPreviewFile.name || '附件预览'"
|
||||||
|
append-to-body
|
||||||
|
destroy-on-close
|
||||||
|
width="90%"
|
||||||
|
top="4vh"
|
||||||
|
class="project-apply-form__attachment-viewer-dialog"
|
||||||
|
>
|
||||||
|
<open-file-viewer
|
||||||
|
v-if="attachmentDocumentPreviewVisible && attachmentPreviewFile.url"
|
||||||
|
:file="attachmentPreviewFile.url"
|
||||||
|
:file-name="attachmentPreviewFile.name"
|
||||||
|
:mime-type="attachmentPreviewFile.mimeType"
|
||||||
|
width="100%"
|
||||||
|
height="72vh"
|
||||||
|
fit="contain"
|
||||||
|
theme="auto"
|
||||||
|
locale="zh-CN"
|
||||||
|
:toolbar="attachmentViewerToolbar"
|
||||||
|
:plugins="attachmentViewerPlugins"
|
||||||
|
@unsupported="handleAttachmentPreviewUnsupported"
|
||||||
|
@error="handleAttachmentPreviewError"
|
||||||
|
/>
|
||||||
|
</el-dialog>
|
||||||
|
<el-image-viewer
|
||||||
|
v-if="attachmentImagePreviewVisible"
|
||||||
|
:url-list="attachmentImagePreviewUrls"
|
||||||
|
:initial-index="attachmentImagePreviewIndex"
|
||||||
|
@close="attachmentImagePreviewVisible = false"
|
||||||
|
/>
|
||||||
|
|
||||||
<el-dialog
|
<el-dialog
|
||||||
v-model="userBox"
|
v-model="userBox"
|
||||||
title="选择用户"
|
title="选择用户"
|
||||||
@@ -705,6 +746,17 @@ import { getDictionary as getBizDictionary } from '@/api/system/dictbiz';
|
|||||||
import { getList as getUserList } from '@/api/system/user';
|
import { getList as getUserList } from '@/api/system/user';
|
||||||
import { getToken } from '@/utils/auth';
|
import { getToken } from '@/utils/auth';
|
||||||
import { downloadFileByUrl, downloadXls } from '@/utils/util';
|
import { downloadFileByUrl, downloadXls } from '@/utils/util';
|
||||||
|
import { ElImageViewer } from 'element-plus';
|
||||||
|
import { OpenFileViewer } from '@open-file-viewer/vue';
|
||||||
|
import {
|
||||||
|
fallbackPlugin,
|
||||||
|
imagePlugin,
|
||||||
|
officePlugin,
|
||||||
|
pdfPlugin,
|
||||||
|
textPlugin,
|
||||||
|
} from '@open-file-viewer/core';
|
||||||
|
import '@open-file-viewer/core/style.css';
|
||||||
|
import pdfWorkerSrc from 'pdfjs-dist/build/pdf.worker.mjs?url';
|
||||||
import { mapGetters } from 'vuex';
|
import { mapGetters } from 'vuex';
|
||||||
import NProgress from 'nprogress';
|
import NProgress from 'nprogress';
|
||||||
import 'nprogress/nprogress.css';
|
import 'nprogress/nprogress.css';
|
||||||
@@ -767,8 +819,15 @@ const changeTypeOptions = [
|
|||||||
{ label: '项目备案调整', value: '项目备案调整' },
|
{ label: '项目备案调整', value: '项目备案调整' },
|
||||||
];
|
];
|
||||||
|
|
||||||
|
const attachmentViewerPlugins = [
|
||||||
|
imagePlugin(),
|
||||||
|
pdfPlugin({ workerSrc: pdfWorkerSrc, useFetchData: true }),
|
||||||
|
officePlugin({ pdf: { workerSrc: pdfWorkerSrc, useFetchData: true } }),
|
||||||
|
textPlugin(),
|
||||||
|
fallbackPlugin(),
|
||||||
|
];
|
||||||
|
|
||||||
const normalProjectAttachmentTypeOptions = [
|
const normalProjectAttachmentTypeOptions = [
|
||||||
'项目立项报告',
|
|
||||||
'利润测算表',
|
'利润测算表',
|
||||||
'单一来源供应商申请表/三方比价表',
|
'单一来源供应商申请表/三方比价表',
|
||||||
'合同模板',
|
'合同模板',
|
||||||
@@ -778,7 +837,6 @@ const normalProjectAttachmentTypeOptions = [
|
|||||||
].map(item => ({ label: item, value: item }));
|
].map(item => ({ label: item, value: item }));
|
||||||
|
|
||||||
const majorProjectAttachmentTypeOptions = [
|
const majorProjectAttachmentTypeOptions = [
|
||||||
'项目立项报告',
|
|
||||||
'会议纪要',
|
'会议纪要',
|
||||||
'评审表决票',
|
'评审表决票',
|
||||||
'利润测算表',
|
'利润测算表',
|
||||||
@@ -793,6 +851,10 @@ const majorProjectAttachmentTypeOptions = [
|
|||||||
].map(item => ({ label: item, value: item }));
|
].map(item => ({ label: item, value: item }));
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
|
components: {
|
||||||
|
ElImageViewer,
|
||||||
|
OpenFileViewer,
|
||||||
|
},
|
||||||
data() {
|
data() {
|
||||||
const validateAmount = (rule, value, callback) => {
|
const validateAmount = (rule, value, callback) => {
|
||||||
if (value === '' || value === undefined || value === null) {
|
if (value === '' || value === undefined || value === null) {
|
||||||
@@ -945,6 +1007,19 @@ export default {
|
|||||||
carrierRows: [],
|
carrierRows: [],
|
||||||
attachmentRows: [],
|
attachmentRows: [],
|
||||||
selectedAttachmentRows: [],
|
selectedAttachmentRows: [],
|
||||||
|
attachmentImagePreviewVisible: false,
|
||||||
|
attachmentImagePreviewUrls: [],
|
||||||
|
attachmentImagePreviewIndex: 0,
|
||||||
|
attachmentDocumentPreviewVisible: false,
|
||||||
|
attachmentPreviewFile: {},
|
||||||
|
attachmentViewerPlugins,
|
||||||
|
attachmentViewerToolbar: {
|
||||||
|
download: true,
|
||||||
|
fullscreen: true,
|
||||||
|
print: true,
|
||||||
|
rotate: true,
|
||||||
|
zoom: true,
|
||||||
|
},
|
||||||
attachmentFileTypes: [
|
attachmentFileTypes: [
|
||||||
'pdf',
|
'pdf',
|
||||||
'bmp',
|
'bmp',
|
||||||
@@ -1532,12 +1607,41 @@ export default {
|
|||||||
return submitRow;
|
return submitRow;
|
||||||
},
|
},
|
||||||
normalizeAttachmentFileTypes() {
|
normalizeAttachmentFileTypes() {
|
||||||
const values = this.projectAttachmentTypeOptions.map(item => item.value);
|
|
||||||
this.attachmentRows = this.attachmentRows.map(row => ({
|
this.attachmentRows = this.attachmentRows.map(row => ({
|
||||||
...row,
|
...row,
|
||||||
fileType: values.includes(row.fileType) ? row.fileType : '',
|
fileType: this.resolveAttachmentFileType(row),
|
||||||
}));
|
}));
|
||||||
},
|
},
|
||||||
|
normalizeAttachmentTypeText(value) {
|
||||||
|
let text = String(value || '').split(/[?#]/)[0];
|
||||||
|
try {
|
||||||
|
text = decodeURIComponent(text);
|
||||||
|
} catch (error) {
|
||||||
|
// 文件名可能包含不完整的转义字符,直接使用原始名称继续匹配。
|
||||||
|
}
|
||||||
|
return text
|
||||||
|
.replace(/\.[^.\\/]+$/, '')
|
||||||
|
.toLowerCase()
|
||||||
|
.replace(/[\s_\-—–·•()()\[\]【】{}《》<>“”"'、,,。.]/g, '');
|
||||||
|
},
|
||||||
|
attachmentTypeKeywords(fileType) {
|
||||||
|
if (fileType === '单一来源供应商申请表/三方比价表') {
|
||||||
|
return ['单一来源供应商申请表', '三方比价表'];
|
||||||
|
}
|
||||||
|
return [fileType];
|
||||||
|
},
|
||||||
|
resolveAttachmentFileType(row = {}) {
|
||||||
|
const values = this.projectAttachmentTypeOptions.map(item => item.value);
|
||||||
|
if (values.includes(row.fileType)) return row.fileType;
|
||||||
|
const fileName = this.normalizeAttachmentTypeText(this.attachmentName(row));
|
||||||
|
if (!fileName) return '';
|
||||||
|
const option = this.projectAttachmentTypeOptions.find(item =>
|
||||||
|
this.attachmentTypeKeywords(item.value).some(keyword =>
|
||||||
|
fileName.includes(this.normalizeAttachmentTypeText(keyword))
|
||||||
|
)
|
||||||
|
);
|
||||||
|
return option?.value || '';
|
||||||
|
},
|
||||||
validateAttachmentFileTypes() {
|
validateAttachmentFileTypes() {
|
||||||
const values = this.projectAttachmentTypeOptions.map(item => item.value);
|
const values = this.projectAttachmentTypeOptions.map(item => item.value);
|
||||||
const invalidIndex = this.attachmentRows.findIndex(row => !values.includes(row.fileType));
|
const invalidIndex = this.attachmentRows.findIndex(row => !values.includes(row.fileType));
|
||||||
@@ -1676,10 +1780,25 @@ export default {
|
|||||||
loadDeptOptions() {
|
loadDeptOptions() {
|
||||||
getDeptTree().then(res => {
|
getDeptTree().then(res => {
|
||||||
const deptTree = res.data.data || [];
|
const deptTree = res.data.data || [];
|
||||||
|
const undertakeDeptTree =
|
||||||
|
this.dialogType === 'add' ? this.excludeExternalOrganization(deptTree) : deptTree;
|
||||||
this.deptOptions = this.flattenDept(deptTree);
|
this.deptOptions = this.flattenDept(deptTree);
|
||||||
this.deptTreeOptions = this.toDeptCascaderOptions(deptTree);
|
this.deptTreeOptions = this.toDeptCascaderOptions(undertakeDeptTree);
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
excludeExternalOrganization(list = []) {
|
||||||
|
return list.reduce((result, item) => {
|
||||||
|
const deptName = item.title || item.deptName || item.name;
|
||||||
|
if (String(deptName || '').trim() === '外部组织') return result;
|
||||||
|
result.push({
|
||||||
|
...item,
|
||||||
|
children: item.children?.length
|
||||||
|
? this.excludeExternalOrganization(item.children)
|
||||||
|
: item.children,
|
||||||
|
});
|
||||||
|
return result;
|
||||||
|
}, []);
|
||||||
|
},
|
||||||
toDeptCascaderOptions(list = []) {
|
toDeptCascaderOptions(list = []) {
|
||||||
return list.map(item => ({
|
return list.map(item => ({
|
||||||
label: item.title || item.deptName || item.name,
|
label: item.title || item.deptName || item.name,
|
||||||
@@ -1825,10 +1944,9 @@ export default {
|
|||||||
handleAttachmentChange(list) {
|
handleAttachmentChange(list) {
|
||||||
const userName = this.userInfo?.realName || this.userInfo?.userName || '';
|
const userName = this.userInfo?.realName || this.userInfo?.userName || '';
|
||||||
const uploadTime = this.$dayjs().format('YYYY-MM-DD HH:mm:ss');
|
const uploadTime = this.$dayjs().format('YYYY-MM-DD HH:mm:ss');
|
||||||
const values = this.projectAttachmentTypeOptions.map(item => item.value);
|
|
||||||
this.attachmentRows = (list || []).map(item => ({
|
this.attachmentRows = (list || []).map(item => ({
|
||||||
...item,
|
...item,
|
||||||
fileType: values.includes(item.fileType) ? item.fileType : '',
|
fileType: this.resolveAttachmentFileType(item),
|
||||||
description: item.description || '',
|
description: item.description || '',
|
||||||
uploadUserName: item.uploadUserName || userName,
|
uploadUserName: item.uploadUserName || userName,
|
||||||
uploadTime: item.uploadTime || uploadTime,
|
uploadTime: item.uploadTime || uploadTime,
|
||||||
@@ -1848,19 +1966,74 @@ export default {
|
|||||||
if (number < 1024 * 1024) return `${(number / 1024).toFixed(1)}KB`;
|
if (number < 1024 * 1024) return `${(number / 1024).toFixed(1)}KB`;
|
||||||
return `${(number / 1024 / 1024).toFixed(1)}MB`;
|
return `${(number / 1024 / 1024).toFixed(1)}MB`;
|
||||||
},
|
},
|
||||||
|
attachmentUrl(row = {}) {
|
||||||
|
return row.url || row.link || row.fileUrl || row.src || row.domain || '';
|
||||||
|
},
|
||||||
|
attachmentName(row = {}) {
|
||||||
|
return row.originalName || row.name || row.fileName || '附件';
|
||||||
|
},
|
||||||
|
attachmentExtension(row = {}) {
|
||||||
|
const source = String(this.attachmentName(row) || this.attachmentUrl(row)).split('?')[0];
|
||||||
|
const index = source.lastIndexOf('.');
|
||||||
|
return index > -1 ? source.slice(index + 1).toLowerCase() : '';
|
||||||
|
},
|
||||||
|
isAttachmentImage(row) {
|
||||||
|
return ['jpg', 'jpeg', 'png', 'gif', 'bmp', 'webp'].includes(
|
||||||
|
this.attachmentExtension(row)
|
||||||
|
);
|
||||||
|
},
|
||||||
|
previewAttachment(row) {
|
||||||
|
const url = this.attachmentUrl(row);
|
||||||
|
if (!url) {
|
||||||
|
this.$message.warning('附件地址为空,无法预览');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (this.isAttachmentImage(row)) {
|
||||||
|
this.attachmentImagePreviewUrls = this.attachmentRows
|
||||||
|
.filter(item => this.isAttachmentImage(item) && this.attachmentUrl(item))
|
||||||
|
.map(item => this.attachmentUrl(item));
|
||||||
|
this.attachmentImagePreviewIndex = Math.max(
|
||||||
|
this.attachmentImagePreviewUrls.indexOf(url),
|
||||||
|
0
|
||||||
|
);
|
||||||
|
this.attachmentImagePreviewVisible = true;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.attachmentPreviewFile = {
|
||||||
|
name: this.attachmentName(row),
|
||||||
|
url,
|
||||||
|
mimeType: row.mimeType || row.contentType || '',
|
||||||
|
};
|
||||||
|
this.attachmentDocumentPreviewVisible = true;
|
||||||
|
},
|
||||||
|
handleAttachmentPreviewUnsupported() {
|
||||||
|
this.$message.warning('当前文件暂不支持在线预览');
|
||||||
|
},
|
||||||
|
handleAttachmentPreviewError() {
|
||||||
|
this.$message.error('附件预览失败');
|
||||||
|
},
|
||||||
downloadAttachment(row) {
|
downloadAttachment(row) {
|
||||||
const url = row.url || row.link;
|
const url = this.attachmentUrl(row);
|
||||||
if (!url) {
|
if (!url) {
|
||||||
this.$message.warning('附件地址为空');
|
this.$message.warning('附件地址为空');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
downloadFileByUrl(url, row.originalName || row.name || '附件');
|
downloadFileByUrl(url, this.attachmentName(row));
|
||||||
},
|
},
|
||||||
handleBatchDownload() {
|
handleBatchDownload() {
|
||||||
const rows = this.selectedAttachmentRows.length
|
const rows = this.selectedAttachmentRows.length
|
||||||
? this.selectedAttachmentRows
|
? this.selectedAttachmentRows
|
||||||
: this.attachmentRows;
|
: this.attachmentRows;
|
||||||
rows.forEach(row => this.downloadAttachment(row));
|
const downloadableRows = rows.filter(row => this.attachmentUrl(row));
|
||||||
|
if (!downloadableRows.length) {
|
||||||
|
this.$message.warning(
|
||||||
|
this.selectedAttachmentRows.length ? '所选附件暂无可下载地址' : '暂无可下载附件'
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
downloadableRows.forEach((row, index) => {
|
||||||
|
window.setTimeout(() => this.downloadAttachment(row), index * 300);
|
||||||
|
});
|
||||||
},
|
},
|
||||||
handleChangeRecordAction() {
|
handleChangeRecordAction() {
|
||||||
this.$message.info('变更记录详情由后端明细能力生成后展示');
|
this.$message.info('变更记录详情由后端明细能力生成后展示');
|
||||||
|
|||||||
@@ -93,7 +93,7 @@
|
|||||||
<el-table-column type="index" label="序号" width="70" align="center" />
|
<el-table-column type="index" label="序号" width="70" align="center" />
|
||||||
<el-table-column label="文件名" min-width="240" show-overflow-tooltip>
|
<el-table-column label="文件名" min-width="240" show-overflow-tooltip>
|
||||||
<template #default="{ row }">
|
<template #default="{ row }">
|
||||||
<el-link type="primary" @click="downloadAttachment(row)">
|
<el-link type="primary" @click="previewAttachment(row)">
|
||||||
{{ attachmentName(row) }}
|
{{ attachmentName(row) }}
|
||||||
</el-link>
|
</el-link>
|
||||||
</template>
|
</template>
|
||||||
@@ -169,6 +169,38 @@
|
|||||||
@load="onLoad(page, query)"
|
@load="onLoad(page, query)"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
<el-dialog
|
||||||
|
v-model="attachmentDocumentPreviewVisible"
|
||||||
|
:title="attachmentPreviewFile.name || '附件预览'"
|
||||||
|
append-to-body
|
||||||
|
destroy-on-close
|
||||||
|
width="90%"
|
||||||
|
top="4vh"
|
||||||
|
class="temporary-credit-limit-page__attachment-viewer-dialog"
|
||||||
|
>
|
||||||
|
<open-file-viewer
|
||||||
|
v-if="attachmentDocumentPreviewVisible && attachmentPreviewFile.url"
|
||||||
|
:file="attachmentPreviewFile.url"
|
||||||
|
:file-name="attachmentPreviewFile.name"
|
||||||
|
:mime-type="attachmentPreviewFile.mimeType"
|
||||||
|
width="100%"
|
||||||
|
height="72vh"
|
||||||
|
fit="contain"
|
||||||
|
theme="auto"
|
||||||
|
locale="zh-CN"
|
||||||
|
:toolbar="attachmentViewerToolbar"
|
||||||
|
:plugins="attachmentViewerPlugins"
|
||||||
|
@unsupported="handleAttachmentPreviewUnsupported"
|
||||||
|
@error="handleAttachmentPreviewError"
|
||||||
|
/>
|
||||||
|
</el-dialog>
|
||||||
|
<el-image-viewer
|
||||||
|
v-if="attachmentImagePreviewVisible"
|
||||||
|
:url-list="attachmentImagePreviewUrls"
|
||||||
|
:initial-index="attachmentImagePreviewIndex"
|
||||||
|
@close="attachmentImagePreviewVisible = false"
|
||||||
|
/>
|
||||||
|
|
||||||
<flow-design-step
|
<flow-design-step
|
||||||
v-if="website.design.designMode"
|
v-if="website.design.designMode"
|
||||||
v-model:is-display="flowBox"
|
v-model:is-display="flowBox"
|
||||||
@@ -191,6 +223,17 @@ import {
|
|||||||
import { config, option } from '@/option/business/temporary-credit-limit';
|
import { config, option } from '@/option/business/temporary-credit-limit';
|
||||||
import { getToken } from '@/utils/auth';
|
import { getToken } from '@/utils/auth';
|
||||||
import { downloadFileByUrl, downloadXls } from '@/utils/util';
|
import { downloadFileByUrl, downloadXls } from '@/utils/util';
|
||||||
|
import { ElImageViewer } from 'element-plus';
|
||||||
|
import { OpenFileViewer } from '@open-file-viewer/vue';
|
||||||
|
import {
|
||||||
|
fallbackPlugin,
|
||||||
|
imagePlugin,
|
||||||
|
officePlugin,
|
||||||
|
pdfPlugin,
|
||||||
|
textPlugin,
|
||||||
|
} from '@open-file-viewer/core';
|
||||||
|
import '@open-file-viewer/core/style.css';
|
||||||
|
import pdfWorkerSrc from 'pdfjs-dist/build/pdf.worker.mjs?url';
|
||||||
import { mapGetters } from 'vuex';
|
import { mapGetters } from 'vuex';
|
||||||
import NProgress from 'nprogress';
|
import NProgress from 'nprogress';
|
||||||
import 'nprogress/nprogress.css';
|
import 'nprogress/nprogress.css';
|
||||||
@@ -212,6 +255,14 @@ const attachmentFileTypes = [
|
|||||||
'zip',
|
'zip',
|
||||||
];
|
];
|
||||||
|
|
||||||
|
const attachmentViewerPlugins = [
|
||||||
|
imagePlugin(),
|
||||||
|
pdfPlugin({ workerSrc: pdfWorkerSrc, useFetchData: true }),
|
||||||
|
officePlugin({ pdf: { workerSrc: pdfWorkerSrc, useFetchData: true } }),
|
||||||
|
textPlugin(),
|
||||||
|
fallbackPlugin(),
|
||||||
|
];
|
||||||
|
|
||||||
const extractRecords = res => {
|
const extractRecords = res => {
|
||||||
const data = res?.data?.data || res?.data || {};
|
const data = res?.data?.data || res?.data || {};
|
||||||
return Array.isArray(data) ? data : data.records || [];
|
return Array.isArray(data) ? data : data.records || [];
|
||||||
@@ -219,6 +270,10 @@ const extractRecords = res => {
|
|||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'TemporaryCreditLimit',
|
name: 'TemporaryCreditLimit',
|
||||||
|
components: {
|
||||||
|
ElImageViewer,
|
||||||
|
OpenFileViewer,
|
||||||
|
},
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
api,
|
api,
|
||||||
@@ -242,6 +297,19 @@ export default {
|
|||||||
projectLoading: false,
|
projectLoading: false,
|
||||||
attachmentRows: [],
|
attachmentRows: [],
|
||||||
selectedAttachments: [],
|
selectedAttachments: [],
|
||||||
|
attachmentImagePreviewVisible: false,
|
||||||
|
attachmentImagePreviewUrls: [],
|
||||||
|
attachmentImagePreviewIndex: 0,
|
||||||
|
attachmentDocumentPreviewVisible: false,
|
||||||
|
attachmentPreviewFile: {},
|
||||||
|
attachmentViewerPlugins,
|
||||||
|
attachmentViewerToolbar: {
|
||||||
|
download: true,
|
||||||
|
fullscreen: true,
|
||||||
|
print: true,
|
||||||
|
rotate: true,
|
||||||
|
zoom: true,
|
||||||
|
},
|
||||||
attachmentFileTypes,
|
attachmentFileTypes,
|
||||||
flowBox: false,
|
flowBox: false,
|
||||||
flowUrl: '',
|
flowUrl: '',
|
||||||
@@ -587,6 +655,46 @@ export default {
|
|||||||
attachmentUrl(row = {}) {
|
attachmentUrl(row = {}) {
|
||||||
return row.url || row.link || row.fileUrl || row.downloadUrl || row.domain || '';
|
return row.url || row.link || row.fileUrl || row.downloadUrl || row.domain || '';
|
||||||
},
|
},
|
||||||
|
attachmentExtension(row = {}) {
|
||||||
|
const source = String(this.attachmentName(row) || this.attachmentUrl(row)).split('?')[0];
|
||||||
|
const index = source.lastIndexOf('.');
|
||||||
|
return index > -1 ? source.slice(index + 1).toLowerCase() : '';
|
||||||
|
},
|
||||||
|
isAttachmentImage(row) {
|
||||||
|
return ['jpg', 'jpeg', 'png', 'gif', 'bmp', 'webp'].includes(
|
||||||
|
this.attachmentExtension(row)
|
||||||
|
);
|
||||||
|
},
|
||||||
|
previewAttachment(row) {
|
||||||
|
const url = this.attachmentUrl(row);
|
||||||
|
if (!url) {
|
||||||
|
this.$message.warning('附件地址为空,无法预览');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (this.isAttachmentImage(row)) {
|
||||||
|
this.attachmentImagePreviewUrls = this.attachmentRows
|
||||||
|
.filter(item => this.isAttachmentImage(item) && this.attachmentUrl(item))
|
||||||
|
.map(item => this.attachmentUrl(item));
|
||||||
|
this.attachmentImagePreviewIndex = Math.max(
|
||||||
|
this.attachmentImagePreviewUrls.indexOf(url),
|
||||||
|
0
|
||||||
|
);
|
||||||
|
this.attachmentImagePreviewVisible = true;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.attachmentPreviewFile = {
|
||||||
|
name: this.attachmentName(row),
|
||||||
|
url,
|
||||||
|
mimeType: row.mimeType || row.contentType || '',
|
||||||
|
};
|
||||||
|
this.attachmentDocumentPreviewVisible = true;
|
||||||
|
},
|
||||||
|
handleAttachmentPreviewUnsupported() {
|
||||||
|
this.$message.warning('当前文件暂不支持在线预览');
|
||||||
|
},
|
||||||
|
handleAttachmentPreviewError() {
|
||||||
|
this.$message.error('附件预览失败');
|
||||||
|
},
|
||||||
downloadAttachment(row) {
|
downloadAttachment(row) {
|
||||||
const url = this.attachmentUrl(row);
|
const url = this.attachmentUrl(row);
|
||||||
if (!url) {
|
if (!url) {
|
||||||
@@ -597,7 +705,16 @@ export default {
|
|||||||
},
|
},
|
||||||
batchDownload() {
|
batchDownload() {
|
||||||
const rows = this.selectedAttachments.length ? this.selectedAttachments : this.attachmentRows;
|
const rows = this.selectedAttachments.length ? this.selectedAttachments : this.attachmentRows;
|
||||||
rows.forEach(this.downloadAttachment);
|
const downloadableRows = rows.filter(row => this.attachmentUrl(row));
|
||||||
|
if (!downloadableRows.length) {
|
||||||
|
this.$message.warning(
|
||||||
|
this.selectedAttachments.length ? '所选附件暂无可下载地址' : '暂无可下载附件'
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
downloadableRows.forEach((row, index) => {
|
||||||
|
window.setTimeout(() => this.downloadAttachment(row), index * 300);
|
||||||
|
});
|
||||||
},
|
},
|
||||||
formatFileSize(size) {
|
formatFileSize(size) {
|
||||||
const value = Number(size);
|
const value = Number(size);
|
||||||
|
|||||||
@@ -12,7 +12,7 @@
|
|||||||
class="bill-ledger-form-page__form"
|
class="bill-ledger-form-page__form"
|
||||||
>
|
>
|
||||||
<section-card title="基本信息">
|
<section-card title="基本信息">
|
||||||
<el-row :gutter="24">
|
<el-row :gutter="24" class="bill-ledger-form-page__basic-grid">
|
||||||
<el-col :span="6">
|
<el-col :span="6">
|
||||||
<el-form-item label="票据号码" prop="billNo">
|
<el-form-item label="票据号码" prop="billNo">
|
||||||
<el-input
|
<el-input
|
||||||
@@ -23,6 +23,18 @@
|
|||||||
/>
|
/>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
</el-col>
|
</el-col>
|
||||||
|
<el-col :span="6">
|
||||||
|
<el-form-item label="汇票类型" prop="billType">
|
||||||
|
<el-select v-model="form.billType" placeholder="请选择">
|
||||||
|
<el-option
|
||||||
|
v-for="item in billTypeOptions"
|
||||||
|
:key="item.value"
|
||||||
|
:label="item.label"
|
||||||
|
:value="item.value"
|
||||||
|
/>
|
||||||
|
</el-select>
|
||||||
|
</el-form-item>
|
||||||
|
</el-col>
|
||||||
<el-col :span="6">
|
<el-col :span="6">
|
||||||
<el-form-item label="出票单位" prop="issuerId">
|
<el-form-item label="出票单位" prop="issuerId">
|
||||||
<el-select
|
<el-select
|
||||||
@@ -43,6 +55,24 @@
|
|||||||
</el-select>
|
</el-select>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
</el-col>
|
</el-col>
|
||||||
|
<el-col :span="6">
|
||||||
|
<el-form-item label="收票单位" prop="receiverName">
|
||||||
|
<el-select
|
||||||
|
v-model="form.receiverName"
|
||||||
|
filterable
|
||||||
|
allow-create
|
||||||
|
default-first-option
|
||||||
|
placeholder="请选择或输入"
|
||||||
|
>
|
||||||
|
<el-option
|
||||||
|
v-for="item in receiverOptions"
|
||||||
|
:key="item"
|
||||||
|
:label="item"
|
||||||
|
:value="item"
|
||||||
|
/>
|
||||||
|
</el-select>
|
||||||
|
</el-form-item>
|
||||||
|
</el-col>
|
||||||
<el-col :span="6">
|
<el-col :span="6">
|
||||||
<el-form-item label="票面金额" prop="faceAmount">
|
<el-form-item label="票面金额" prop="faceAmount">
|
||||||
<el-input-number
|
<el-input-number
|
||||||
@@ -54,6 +84,11 @@
|
|||||||
/>
|
/>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
</el-col>
|
</el-col>
|
||||||
|
<el-col :span="6">
|
||||||
|
<el-form-item label="可用余额">
|
||||||
|
<el-input :model-value="formatMoney(projectedAvailableBalance)" disabled />
|
||||||
|
</el-form-item>
|
||||||
|
</el-col>
|
||||||
<el-col :span="6">
|
<el-col :span="6">
|
||||||
<el-form-item label="出票日期" prop="issueDate">
|
<el-form-item label="出票日期" prop="issueDate">
|
||||||
<el-date-picker
|
<el-date-picker
|
||||||
@@ -64,6 +99,16 @@
|
|||||||
/>
|
/>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
</el-col>
|
</el-col>
|
||||||
|
<el-col :span="6">
|
||||||
|
<el-form-item label="到期日期" prop="maturityDate">
|
||||||
|
<el-date-picker
|
||||||
|
v-model="form.maturityDate"
|
||||||
|
type="date"
|
||||||
|
value-format="YYYY-MM-DD"
|
||||||
|
placeholder="请选择"
|
||||||
|
/>
|
||||||
|
</el-form-item>
|
||||||
|
</el-col>
|
||||||
<el-col :span="6">
|
<el-col :span="6">
|
||||||
<el-form-item label="可用部门" prop="availableDeptIds">
|
<el-form-item label="可用部门" prop="availableDeptIds">
|
||||||
<el-tree-select
|
<el-tree-select
|
||||||
@@ -80,6 +125,19 @@
|
|||||||
/>
|
/>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
</el-col>
|
</el-col>
|
||||||
|
<el-col :span="6">
|
||||||
|
<el-form-item label="出票行" prop="issuingBank">
|
||||||
|
<el-select
|
||||||
|
v-model="form.issuingBank"
|
||||||
|
filterable
|
||||||
|
allow-create
|
||||||
|
default-first-option
|
||||||
|
placeholder="请选择或输入"
|
||||||
|
>
|
||||||
|
<el-option v-for="item in bankOptions" :key="item" :label="item" :value="item" />
|
||||||
|
</el-select>
|
||||||
|
</el-form-item>
|
||||||
|
</el-col>
|
||||||
<el-col :span="6">
|
<el-col :span="6">
|
||||||
<el-form-item label="费用承担方" prop="feeBearerId">
|
<el-form-item label="费用承担方" prop="feeBearerId">
|
||||||
<el-select
|
<el-select
|
||||||
@@ -101,9 +159,9 @@
|
|||||||
</el-form-item>
|
</el-form-item>
|
||||||
</el-col>
|
</el-col>
|
||||||
<el-col :span="6">
|
<el-col :span="6">
|
||||||
<el-form-item label="双方确认贴现率" prop="confirmedDiscountRate">
|
<el-form-item label="银行贴现参考率" prop="bankDiscountReferenceRate">
|
||||||
<el-input-number
|
<el-input-number
|
||||||
v-model="form.confirmedDiscountRate"
|
v-model="form.bankDiscountReferenceRate"
|
||||||
:min="0"
|
:min="0"
|
||||||
:max="100"
|
:max="100"
|
||||||
:precision="4"
|
:precision="4"
|
||||||
@@ -115,67 +173,9 @@
|
|||||||
</el-form-item>
|
</el-form-item>
|
||||||
</el-col>
|
</el-col>
|
||||||
<el-col :span="6">
|
<el-col :span="6">
|
||||||
<el-form-item label="汇票类型" prop="billType">
|
<el-form-item label="双方确认贴现率" prop="confirmedDiscountRate">
|
||||||
<el-select v-model="form.billType" placeholder="请选择">
|
|
||||||
<el-option
|
|
||||||
v-for="item in billTypeOptions"
|
|
||||||
:key="item.value"
|
|
||||||
:label="item.label"
|
|
||||||
:value="item.value"
|
|
||||||
/>
|
|
||||||
</el-select>
|
|
||||||
</el-form-item>
|
|
||||||
</el-col>
|
|
||||||
<el-col :span="6">
|
|
||||||
<el-form-item label="收票单位" prop="receiverName">
|
|
||||||
<el-select
|
|
||||||
v-model="form.receiverName"
|
|
||||||
filterable
|
|
||||||
allow-create
|
|
||||||
default-first-option
|
|
||||||
placeholder="请选择或输入"
|
|
||||||
>
|
|
||||||
<el-option
|
|
||||||
v-for="item in receiverOptions"
|
|
||||||
:key="item"
|
|
||||||
:label="item"
|
|
||||||
:value="item"
|
|
||||||
/>
|
|
||||||
</el-select>
|
|
||||||
</el-form-item>
|
|
||||||
</el-col>
|
|
||||||
<el-col :span="6">
|
|
||||||
<el-form-item label="可用余额">
|
|
||||||
<el-input :model-value="formatMoney(projectedAvailableBalance)" disabled />
|
|
||||||
</el-form-item>
|
|
||||||
</el-col>
|
|
||||||
<el-col :span="6">
|
|
||||||
<el-form-item label="到期日期" prop="maturityDate">
|
|
||||||
<el-date-picker
|
|
||||||
v-model="form.maturityDate"
|
|
||||||
type="date"
|
|
||||||
value-format="YYYY-MM-DD"
|
|
||||||
placeholder="请选择"
|
|
||||||
/>
|
|
||||||
</el-form-item>
|
|
||||||
</el-col>
|
|
||||||
<el-col :span="6">
|
|
||||||
<el-form-item label="出票行" prop="issuingBank">
|
|
||||||
<el-select
|
|
||||||
v-model="form.issuingBank"
|
|
||||||
filterable
|
|
||||||
allow-create
|
|
||||||
default-first-option
|
|
||||||
placeholder="请选择或输入"
|
|
||||||
>
|
|
||||||
<el-option v-for="item in bankOptions" :key="item" :label="item" :value="item" />
|
|
||||||
</el-select>
|
|
||||||
</el-form-item>
|
|
||||||
</el-col>
|
|
||||||
<el-col :span="6">
|
|
||||||
<el-form-item label="银行贴现参考率" prop="bankDiscountReferenceRate">
|
|
||||||
<el-input-number
|
<el-input-number
|
||||||
v-model="form.bankDiscountReferenceRate"
|
v-model="form.confirmedDiscountRate"
|
||||||
:min="0"
|
:min="0"
|
||||||
:max="100"
|
:max="100"
|
||||||
:precision="4"
|
:precision="4"
|
||||||
@@ -560,11 +560,19 @@ export default {
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped lang="scss">
|
<style scoped lang="scss">
|
||||||
|
|
||||||
|
|
||||||
.bill-ledger-form-page__form :deep(.el-form-item) {
|
.bill-ledger-form-page__form :deep(.el-form-item) {
|
||||||
margin-bottom: 16px;
|
margin-bottom: 16px;
|
||||||
}
|
}
|
||||||
|
.bill-ledger-form-page__basic-grid {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(3, minmax(0, 1fr));
|
||||||
|
grid-auto-flow: row;
|
||||||
|
}
|
||||||
|
.bill-ledger-form-page__basic-grid > .el-col {
|
||||||
|
width: 100%;
|
||||||
|
max-width: 100%;
|
||||||
|
flex: none;
|
||||||
|
}
|
||||||
.bill-ledger-form-page__form :deep(.el-input),
|
.bill-ledger-form-page__form :deep(.el-input),
|
||||||
.bill-ledger-form-page__form :deep(.el-select),
|
.bill-ledger-form-page__form :deep(.el-select),
|
||||||
.bill-ledger-form-page__form :deep(.el-input-number),
|
.bill-ledger-form-page__form :deep(.el-input-number),
|
||||||
@@ -591,9 +599,10 @@ export default {
|
|||||||
background: #fafafa;
|
background: #fafafa;
|
||||||
}
|
}
|
||||||
@media (max-width: 1024px) {
|
@media (max-width: 1024px) {
|
||||||
.bill-ledger-form-page :deep(.el-col-6) {
|
.bill-ledger-form-page__basic-grid {
|
||||||
max-width: 100%;
|
grid-template-columns: minmax(0, 1fr);
|
||||||
flex: 0 0 100%;
|
grid-template-rows: none;
|
||||||
|
grid-auto-flow: row;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -16,7 +16,7 @@
|
|||||||
<el-row :gutter="24">
|
<el-row :gutter="24">
|
||||||
<el-col :span="6">
|
<el-col :span="6">
|
||||||
<el-form-item label="结算单" prop="settlementIds">
|
<el-form-item label="结算单" prop="settlementIds">
|
||||||
<el-input :model-value="settlementLabel" readonly placeholder="请选择正式结算单">
|
<el-input :model-value="settlementLabel" readonly placeholder="请选择应付正式结算单">
|
||||||
<template v-if="!readonly" #append>
|
<template v-if="!readonly" #append>
|
||||||
<el-button @click="openSettlementDialog">选择</el-button>
|
<el-button @click="openSettlementDialog">选择</el-button>
|
||||||
</template>
|
</template>
|
||||||
@@ -429,7 +429,12 @@
|
|||||||
</div>
|
</div>
|
||||||
</el-form>
|
</el-form>
|
||||||
|
|
||||||
<el-dialog v-model="settlementDialog.visible" title="选择正式结算单" width="86%" append-to-body>
|
<el-dialog
|
||||||
|
v-model="settlementDialog.visible"
|
||||||
|
title="选择应付正式结算单"
|
||||||
|
width="86%"
|
||||||
|
append-to-body
|
||||||
|
>
|
||||||
<div class="invoice-form-page__dialog-search">
|
<div class="invoice-form-page__dialog-search">
|
||||||
<el-input
|
<el-input
|
||||||
v-model="settlementDialog.keyword"
|
v-model="settlementDialog.keyword"
|
||||||
@@ -723,8 +728,10 @@ export default {
|
|||||||
async loadSettlementCandidates() {
|
async loadSettlementCandidates() {
|
||||||
this.settlementDialog.loading = true;
|
this.settlementDialog.loading = true;
|
||||||
try {
|
try {
|
||||||
this.settlementDialog.rows =
|
const data = this.unwrapData(
|
||||||
this.unwrapData(await api.getSettlementCandidates(this.settlementDialog.keyword)) || [];
|
await api.getSettlementCandidates(this.settlementDialog.keyword)
|
||||||
|
);
|
||||||
|
this.settlementDialog.rows = Array.isArray(data) ? data : data?.records || [];
|
||||||
} finally {
|
} finally {
|
||||||
this.settlementDialog.loading = false;
|
this.settlementDialog.loading = false;
|
||||||
}
|
}
|
||||||
@@ -732,7 +739,7 @@ export default {
|
|||||||
async confirmSettlements() {
|
async confirmSettlements() {
|
||||||
const rows = this.settlementDialog.selection;
|
const rows = this.settlementDialog.selection;
|
||||||
if (!rows.length) {
|
if (!rows.length) {
|
||||||
this.$message.warning('请至少选择一张正式结算单');
|
this.$message.warning('请至少选择一张应付正式结算单');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const first = rows[0];
|
const first = rows[0];
|
||||||
|
|||||||
@@ -123,12 +123,25 @@
|
|||||||
><el-input v-model="form.payeeName" disabled /></el-form-item
|
><el-input v-model="form.payeeName" disabled /></el-form-item
|
||||||
></el-col>
|
></el-col>
|
||||||
<el-col :span="6"
|
<el-col :span="6"
|
||||||
><el-form-item label="收款账号"
|
><el-form-item label="收款账号" prop="receiptAccountId"
|
||||||
><el-input v-model="form.bankAccount" :disabled="readonly" /></el-form-item
|
><el-select
|
||||||
|
v-model="form.receiptAccountId"
|
||||||
|
:disabled="readonly || !form.payeeName"
|
||||||
|
:loading="receiptAccountLoading"
|
||||||
|
filterable
|
||||||
|
clearable
|
||||||
|
placeholder="请选择收款方的收款账号"
|
||||||
|
no-data-text="该收款方未维护可用收款信息"
|
||||||
|
@change="handleReceiptAccountChange"
|
||||||
|
><el-option
|
||||||
|
v-for="item in receiptAccountOptions"
|
||||||
|
:key="item.id"
|
||||||
|
:label="receiptAccountLabel(item)"
|
||||||
|
:value="item.id" /></el-select></el-form-item
|
||||||
></el-col>
|
></el-col>
|
||||||
<el-col :span="6"
|
<el-col :span="6"
|
||||||
><el-form-item label="开户银行"
|
><el-form-item label="开户银行"
|
||||||
><el-input v-model="form.bankName" :disabled="readonly" /></el-form-item
|
><el-input v-model="form.bankName" disabled /></el-form-item
|
||||||
></el-col>
|
></el-col>
|
||||||
<el-col :span="6"
|
<el-col :span="6"
|
||||||
><el-form-item label="申请人"
|
><el-form-item label="申请人"
|
||||||
@@ -244,7 +257,24 @@
|
|||||||
min-width="160"
|
min-width="160"
|
||||||
/></el-table>
|
/></el-table>
|
||||||
</section-card>
|
</section-card>
|
||||||
<section-card title="附件">
|
<section-card>
|
||||||
|
<template #title>
|
||||||
|
<span>附件</span>
|
||||||
|
<span v-if="missingAttachmentMaterials.length" class="payment-form-page__missing-text">
|
||||||
|
未上传:{{ missingAttachmentMaterials.map(item => item.label).join('、') }}
|
||||||
|
</span>
|
||||||
|
</template>
|
||||||
|
<div v-loading="attachmentRuleLoading" class="payment-form-page__attachment-checklist">
|
||||||
|
<span class="payment-form-page__attachment-checklist-label">材料清单</span>
|
||||||
|
<el-tag
|
||||||
|
v-for="item in requiredAttachmentMaterials"
|
||||||
|
:key="item.key"
|
||||||
|
:type="hasAttachmentMaterial(item) ? 'success' : 'danger'"
|
||||||
|
effect="plain"
|
||||||
|
>
|
||||||
|
{{ item.label }}({{ hasAttachmentMaterial(item) ? '已上传' : '未上传' }})
|
||||||
|
</el-tag>
|
||||||
|
</div>
|
||||||
<el-table :data="form.attachments" border
|
<el-table :data="form.attachments" border
|
||||||
><el-table-column type="index" label="序号" width="70" /><el-table-column
|
><el-table-column type="index" label="序号" width="70" /><el-table-column
|
||||||
label="附件类型"
|
label="附件类型"
|
||||||
@@ -252,8 +282,10 @@
|
|||||||
><template #default="{ row }"
|
><template #default="{ row }"
|
||||||
><el-select v-model="row.attachmentType" :disabled="readonly"
|
><el-select v-model="row.attachmentType" :disabled="readonly"
|
||||||
><el-option label="磅单" value="weighing_slip" /><el-option
|
><el-option label="磅单" value="weighing_slip" /><el-option
|
||||||
label="结算单"
|
label="委托单"
|
||||||
value="settlement" /><el-option label="合同签章文件" value="contract" /><el-option
|
value="entrust_order" /><el-option label="结算单" value="settlement" /><el-option
|
||||||
|
label="合同签章文件"
|
||||||
|
value="contract" /><el-option
|
||||||
label="特批附件"
|
label="特批附件"
|
||||||
value="special_approval" /><el-option
|
value="special_approval" /><el-option
|
||||||
label="其他"
|
label="其他"
|
||||||
@@ -297,7 +329,7 @@
|
|||||||
</section-card>
|
</section-card>
|
||||||
<div class="payment-form-page__actions">
|
<div class="payment-form-page__actions">
|
||||||
<el-button @click="goBack">返回</el-button
|
<el-button @click="goBack">返回</el-button
|
||||||
><el-button v-if="!readonly && canSave" @click="saveDraft">保存</el-button
|
><el-button v-if="!readonly && canSave" @click="saveDraft(false)">保存</el-button
|
||||||
><el-button
|
><el-button
|
||||||
v-if="!readonly && canSave && hasPermission('payment_application_submit')"
|
v-if="!readonly && canSave && hasPermission('payment_application_submit')"
|
||||||
type="primary"
|
type="primary"
|
||||||
@@ -347,8 +379,38 @@
|
|||||||
import { mapGetters } from 'vuex';
|
import { mapGetters } from 'vuex';
|
||||||
import * as api from '@/api/payment/paymentApplication';
|
import * as api from '@/api/payment/paymentApplication';
|
||||||
import * as billLedgerApi from '@/api/payment/billLedger';
|
import * as billLedgerApi from '@/api/payment/billLedger';
|
||||||
|
import { getDetail as getContractDetail } from '@/api/business/contract-manage';
|
||||||
import * as formalApi from '@/api/settlement/formalSettlement';
|
import * as formalApi from '@/api/settlement/formalSettlement';
|
||||||
import * as preApi from '@/api/settlement/preSettlement';
|
import * as preApi from '@/api/settlement/preSettlement';
|
||||||
|
import {
|
||||||
|
getDetail as getCustomerArchiveDetail,
|
||||||
|
getList as getCustomerArchiveList,
|
||||||
|
} from '@/api/vehicle/customer-archive';
|
||||||
|
import { readSettlementTransfer, removeSettlementTransfer } from '@/utils/settlement-transfer';
|
||||||
|
|
||||||
|
const ATTACHMENT_MATERIALS = {
|
||||||
|
weighingSlip: { key: 'weighingSlip', label: '磅单', keywords: ['磅单'] },
|
||||||
|
entrustOrder: { key: 'entrustOrder', label: '委托单', keywords: ['委托单'] },
|
||||||
|
settlement: { key: 'settlement', label: '结算单', keywords: ['结算单'] },
|
||||||
|
contract: {
|
||||||
|
key: 'contract',
|
||||||
|
label: '合同签章文件',
|
||||||
|
keywords: ['合同签章文件', '合同签章', '签章'],
|
||||||
|
},
|
||||||
|
specialApproval: {
|
||||||
|
key: 'specialApproval',
|
||||||
|
label: '特批附件',
|
||||||
|
keywords: ['特批附件', '特批'],
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const MATERIAL_TYPE_MAP = {
|
||||||
|
weighingSlip: 'weighing_slip',
|
||||||
|
entrustOrder: 'entrust_order',
|
||||||
|
settlement: 'settlement',
|
||||||
|
contract: 'contract',
|
||||||
|
specialApproval: 'special_approval',
|
||||||
|
};
|
||||||
|
|
||||||
const emptyForm = () => ({
|
const emptyForm = () => ({
|
||||||
id: null,
|
id: null,
|
||||||
@@ -356,6 +418,7 @@ const emptyForm = () => ({
|
|||||||
paymentType: 'project_advance',
|
paymentType: 'project_advance',
|
||||||
settlementId: null,
|
settlementId: null,
|
||||||
preSettlementId: null,
|
preSettlementId: null,
|
||||||
|
preSettlementIds: [],
|
||||||
projectId: null,
|
projectId: null,
|
||||||
projectName: '',
|
projectName: '',
|
||||||
deptId: null,
|
deptId: null,
|
||||||
@@ -389,6 +452,8 @@ export default {
|
|||||||
return {
|
return {
|
||||||
form: emptyForm(),
|
form: emptyForm(),
|
||||||
amountSyncing: false,
|
amountSyncing: false,
|
||||||
|
transferPayload: null,
|
||||||
|
referenceRows: [],
|
||||||
paymentRecords: [],
|
paymentRecords: [],
|
||||||
referenceVisible: false,
|
referenceVisible: false,
|
||||||
referenceTab: 'formal',
|
referenceTab: 'formal',
|
||||||
@@ -397,6 +462,11 @@ export default {
|
|||||||
contractRows: [],
|
contractRows: [],
|
||||||
billOptions: [],
|
billOptions: [],
|
||||||
billLoading: false,
|
billLoading: false,
|
||||||
|
receiptAccountOptions: [],
|
||||||
|
receiptAccountLoading: false,
|
||||||
|
firstContractPayment: false,
|
||||||
|
payeeCustomerLevel: '',
|
||||||
|
attachmentRuleLoading: false,
|
||||||
paymentTypeOptions: api.paymentTypeOptions,
|
paymentTypeOptions: api.paymentTypeOptions,
|
||||||
paymentMethodOptions: api.paymentMethodOptions,
|
paymentMethodOptions: api.paymentMethodOptions,
|
||||||
attachmentFileTypes: [
|
attachmentFileTypes: [
|
||||||
@@ -420,6 +490,7 @@ export default {
|
|||||||
paymentType: [{ required: true, message: '请选择付款类型' }],
|
paymentType: [{ required: true, message: '请选择付款类型' }],
|
||||||
referenceId: [{ validator: this.validateReference, trigger: 'change' }],
|
referenceId: [{ validator: this.validateReference, trigger: 'change' }],
|
||||||
paymentMethod: [{ required: true, message: '请选择付款方式' }],
|
paymentMethod: [{ required: true, message: '请选择付款方式' }],
|
||||||
|
receiptAccountId: [{ required: true, message: '请选择收款账号', trigger: 'change' }],
|
||||||
billLedgerId: [{ validator: this.validateBillLedger, trigger: 'change' }],
|
billLedgerId: [{ validator: this.validateBillLedger, trigger: 'change' }],
|
||||||
appliedAmount: [{ validator: this.validateAppliedAmount, trigger: 'change' }],
|
appliedAmount: [{ validator: this.validateAppliedAmount, trigger: 'change' }],
|
||||||
},
|
},
|
||||||
@@ -441,9 +512,26 @@ export default {
|
|||||||
return ['bank_draft', 'commercial_draft'].includes(this.form.paymentMethod);
|
return ['bank_draft', 'commercial_draft'].includes(this.form.paymentMethod);
|
||||||
},
|
},
|
||||||
referenceLabel() {
|
referenceLabel() {
|
||||||
|
if (this.referenceRows.length) {
|
||||||
|
return this.referenceRows
|
||||||
|
.map(item => item.settlementNo)
|
||||||
|
.filter(Boolean)
|
||||||
|
.join('、');
|
||||||
|
}
|
||||||
return this.form.settlementNo || this.form.preSettlementNo || '';
|
return this.form.settlementNo || this.form.preSettlementNo || '';
|
||||||
},
|
},
|
||||||
settlementRows() {
|
settlementRows() {
|
||||||
|
if (this.referenceRows.length) {
|
||||||
|
return this.referenceRows.map(item => ({
|
||||||
|
...item,
|
||||||
|
paymentRatio: this.form.paymentRatio,
|
||||||
|
appliedAmount: Number(
|
||||||
|
((Number(item.payableAmount || 0) * Number(this.form.paymentRatio || 0)) / 100).toFixed(
|
||||||
|
2
|
||||||
|
)
|
||||||
|
),
|
||||||
|
}));
|
||||||
|
}
|
||||||
if (!this.referenceLabel) return [];
|
if (!this.referenceLabel) return [];
|
||||||
return [
|
return [
|
||||||
{
|
{
|
||||||
@@ -455,8 +543,32 @@ export default {
|
|||||||
},
|
},
|
||||||
];
|
];
|
||||||
},
|
},
|
||||||
|
highRiskPayee() {
|
||||||
|
const level = String(this.payeeCustomerLevel || '')
|
||||||
|
.trim()
|
||||||
|
.toUpperCase();
|
||||||
|
return ['D', 'D级', 'E', 'E级', 'F', 'F级'].includes(level) || level.includes('高风险');
|
||||||
|
},
|
||||||
|
requiredAttachmentMaterials() {
|
||||||
|
const materials = [ATTACHMENT_MATERIALS.weighingSlip, ATTACHMENT_MATERIALS.settlement];
|
||||||
|
if (this.form.paymentType === 'project_advance') {
|
||||||
|
materials.splice(1, 0, ATTACHMENT_MATERIALS.entrustOrder);
|
||||||
|
}
|
||||||
|
if (this.firstContractPayment) materials.push(ATTACHMENT_MATERIALS.contract);
|
||||||
|
if (this.highRiskPayee) materials.push(ATTACHMENT_MATERIALS.specialApproval);
|
||||||
|
return materials;
|
||||||
|
},
|
||||||
|
missingAttachmentMaterials() {
|
||||||
|
return this.requiredAttachmentMaterials.filter(item => !this.hasAttachmentMaterial(item));
|
||||||
|
},
|
||||||
},
|
},
|
||||||
watch: {
|
watch: {
|
||||||
|
'$route.query.transferToken': async function (token, oldToken) {
|
||||||
|
if (!this.recordId && token && token !== oldToken) {
|
||||||
|
this.transferPayload = readSettlementTransfer(token);
|
||||||
|
await this.initialize();
|
||||||
|
}
|
||||||
|
},
|
||||||
'form.paymentRatio'(value) {
|
'form.paymentRatio'(value) {
|
||||||
if (this.amountSyncing || !Number(this.form.payableAmount)) return;
|
if (this.amountSyncing || !Number(this.form.payableAmount)) return;
|
||||||
this.amountSyncing = true;
|
this.amountSyncing = true;
|
||||||
@@ -479,6 +591,7 @@ export default {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
created() {
|
created() {
|
||||||
|
this.transferPayload = readSettlementTransfer(this.$route.query.transferToken);
|
||||||
this.initialize();
|
this.initialize();
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
@@ -486,7 +599,11 @@ export default {
|
|||||||
return this.permission?.[code] !== false;
|
return this.permission?.[code] !== false;
|
||||||
},
|
},
|
||||||
validateReference(rule, value, callback) {
|
validateReference(rule, value, callback) {
|
||||||
if (this.form.paymentType === 'progress_advance' && !this.form.preSettlementId) {
|
if (
|
||||||
|
this.form.paymentType === 'progress_advance' &&
|
||||||
|
!this.form.preSettlementId &&
|
||||||
|
!this.form.preSettlementIds?.length
|
||||||
|
) {
|
||||||
callback(new Error('请选择预结算单'));
|
callback(new Error('请选择预结算单'));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -556,17 +673,133 @@ export default {
|
|||||||
this.form = {
|
this.form = {
|
||||||
...emptyForm(),
|
...emptyForm(),
|
||||||
...data,
|
...data,
|
||||||
|
preSettlementIds: data.preSettlementIds?.length
|
||||||
|
? data.preSettlementIds
|
||||||
|
: data.preSettlementId
|
||||||
|
? [data.preSettlementId]
|
||||||
|
: [],
|
||||||
attachments: this.parse(data.attachmentsJson),
|
attachments: this.parse(data.attachmentsJson),
|
||||||
invoices: data.invoices || [],
|
invoices: data.invoices || [],
|
||||||
};
|
};
|
||||||
|
const referenceRow = this.createReferenceRow(data);
|
||||||
|
this.referenceRows = referenceRow.settlementNo ? [referenceRow] : [];
|
||||||
this.paymentRecords = data.paymentRecords || [];
|
this.paymentRecords = data.paymentRecords || [];
|
||||||
|
await this.loadReceiptAccountOptions(false);
|
||||||
|
await this.loadAttachmentRuleData(await this.loadCurrentSettlementDetails());
|
||||||
if (this.billPayment) await this.loadBillOptions('', data.billLedgerId);
|
if (this.billPayment) await this.loadBillOptions('', data.billLedgerId);
|
||||||
} else {
|
} else {
|
||||||
|
this.form = emptyForm();
|
||||||
|
this.referenceRows = [];
|
||||||
this.form.applyDate = this.$dayjs().format('YYYY-MM-DD');
|
this.form.applyDate = this.$dayjs().format('YYYY-MM-DD');
|
||||||
this.form.applicantName =
|
this.form.applicantName =
|
||||||
this.$store.getters.userInfo?.realName || this.$store.getters.userInfo?.userName || '';
|
this.$store.getters.userInfo?.realName || this.$store.getters.userInfo?.userName || '';
|
||||||
|
await this.loadTransferredPreSettlements();
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
getTransferredPreSettlementIds() {
|
||||||
|
const payloadIds = (this.transferPayload?.sourcePreSettlements || []).map(
|
||||||
|
item => item.preSettlementId || item.id
|
||||||
|
);
|
||||||
|
const queryIds = String(this.$route.query.preSettlementIds || '')
|
||||||
|
.split(',')
|
||||||
|
.map(item => item.trim())
|
||||||
|
.filter(Boolean);
|
||||||
|
return [...new Set([...payloadIds, ...queryIds].map(String))];
|
||||||
|
},
|
||||||
|
async loadTransferredPreSettlements() {
|
||||||
|
const fallbackRows = this.transferPayload?.sourcePreSettlements || [];
|
||||||
|
const ids = this.getTransferredPreSettlementIds();
|
||||||
|
if (!ids.length) {
|
||||||
|
this.applyTransferredPreSettlements(fallbackRows);
|
||||||
|
await Promise.all([
|
||||||
|
this.loadReceiptAccountOptions(),
|
||||||
|
this.loadAttachmentRuleData(fallbackRows),
|
||||||
|
]);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
const responses = await Promise.all(ids.map(id => preApi.getDetail(id)));
|
||||||
|
const rows = responses.map(response => this.unwrapData(response)).filter(item => item?.id);
|
||||||
|
const sourceRows = rows.length ? rows : fallbackRows;
|
||||||
|
this.applyTransferredPreSettlements(sourceRows);
|
||||||
|
await Promise.all([
|
||||||
|
this.loadReceiptAccountOptions(),
|
||||||
|
this.loadAttachmentRuleData(sourceRows),
|
||||||
|
]);
|
||||||
|
} catch (error) {
|
||||||
|
if (fallbackRows.length) {
|
||||||
|
this.applyTransferredPreSettlements(fallbackRows);
|
||||||
|
await Promise.all([
|
||||||
|
this.loadReceiptAccountOptions(),
|
||||||
|
this.loadAttachmentRuleData(fallbackRows),
|
||||||
|
]);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.$message.error('预结算信息加载失败,请返回后重新发起预付申请');
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
createReferenceRow(row) {
|
||||||
|
const settlementAmount = Number(row.settlementAmount || 0);
|
||||||
|
const appliedAmount = Number(row.advanceAppliedAmount || row.appliedPaymentAmount || 0);
|
||||||
|
return {
|
||||||
|
id: row.preSettlementId || row.settlementId || row.id,
|
||||||
|
settlementNo: row.preSettlementNo || row.formalSettlementNo || row.settlementNo || '',
|
||||||
|
settlementAmount,
|
||||||
|
payableAmount:
|
||||||
|
row.payableAmount === undefined || row.payableAmount === null
|
||||||
|
? Math.max(0, settlementAmount - appliedAmount)
|
||||||
|
: Number(row.payableAmount || 0),
|
||||||
|
};
|
||||||
|
},
|
||||||
|
applyTransferredPreSettlements(rows = []) {
|
||||||
|
if (!rows.length) return;
|
||||||
|
const contractIds = new Set(rows.map(item => String(item.contractId || '')));
|
||||||
|
if (contractIds.size > 1 || contractIds.has('')) {
|
||||||
|
this.$message.warning('所选预结算单合同信息不一致,请返回后重新选择');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const first = rows[0];
|
||||||
|
this.referenceRows = rows.map(item => this.createReferenceRow(item));
|
||||||
|
const settlementAmount = this.referenceRows.reduce(
|
||||||
|
(total, item) => total + Number(item.settlementAmount || 0),
|
||||||
|
0
|
||||||
|
);
|
||||||
|
const payableAmount = this.referenceRows.reduce(
|
||||||
|
(total, item) => total + Number(item.payableAmount || 0),
|
||||||
|
0
|
||||||
|
);
|
||||||
|
const paymentRatio = Number(this.form.paymentRatio || 0);
|
||||||
|
this.amountSyncing = true;
|
||||||
|
Object.assign(this.form, {
|
||||||
|
paymentType: 'progress_advance',
|
||||||
|
settlementId: null,
|
||||||
|
settlementNo: '',
|
||||||
|
preSettlementId: first.preSettlementId || first.id,
|
||||||
|
preSettlementIds: rows.map(item => item.preSettlementId || item.id),
|
||||||
|
preSettlementNo: this.referenceRows
|
||||||
|
.map(item => item.settlementNo)
|
||||||
|
.filter(Boolean)
|
||||||
|
.join('、'),
|
||||||
|
projectId: first.projectId,
|
||||||
|
projectName: first.projectName,
|
||||||
|
deptId: first.deptId,
|
||||||
|
deptName: first.deptName,
|
||||||
|
contractId: first.contractId,
|
||||||
|
contractNo: first.contractNo,
|
||||||
|
contractName: first.contractName,
|
||||||
|
payerName: first.payerName,
|
||||||
|
payeeName: first.payeeName,
|
||||||
|
settlementAmount: Number(settlementAmount.toFixed(2)),
|
||||||
|
payableAmount: Number(payableAmount.toFixed(2)),
|
||||||
|
billType: '预结算单',
|
||||||
|
appliedAmount: Number(((payableAmount * paymentRatio) / 100).toFixed(2)),
|
||||||
|
});
|
||||||
|
this.$nextTick(() => {
|
||||||
|
this.amountSyncing = false;
|
||||||
|
this.$refs.formRef?.clearValidate();
|
||||||
|
});
|
||||||
|
},
|
||||||
parse(value) {
|
parse(value) {
|
||||||
if (!value) return [];
|
if (!value) return [];
|
||||||
if (Array.isArray(value)) return value;
|
if (Array.isArray(value)) return value;
|
||||||
@@ -576,11 +809,218 @@ export default {
|
|||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
attachmentFileName(file) {
|
||||||
|
return String(file?.originalName || file?.name || file?.fileName || '').trim();
|
||||||
|
},
|
||||||
|
attachmentFileUrl(file) {
|
||||||
|
return file?.url || file?.link || file?.src || file?.domain || '';
|
||||||
|
},
|
||||||
|
attachmentMatchesMaterial(file, material) {
|
||||||
|
const fileName = this.attachmentFileName(file).toLocaleLowerCase();
|
||||||
|
return (
|
||||||
|
Boolean(this.attachmentFileUrl(file)) &&
|
||||||
|
material.keywords.some(keyword => fileName.includes(keyword.toLocaleLowerCase()))
|
||||||
|
);
|
||||||
|
},
|
||||||
|
hasAttachmentMaterial(material) {
|
||||||
|
return (this.form.attachments || []).some(file =>
|
||||||
|
this.attachmentMatchesMaterial(file, material)
|
||||||
|
);
|
||||||
|
},
|
||||||
|
resolveAttachmentMaterial(file, materials = Object.values(ATTACHMENT_MATERIALS)) {
|
||||||
|
return [...materials]
|
||||||
|
.sort(
|
||||||
|
(a, b) =>
|
||||||
|
Math.max(...b.keywords.map(String.length)) - Math.max(...a.keywords.map(String.length))
|
||||||
|
)
|
||||||
|
.find(material => this.attachmentMatchesMaterial(file, material));
|
||||||
|
},
|
||||||
|
mergeAutomaticAttachments(files, materials, sourceName) {
|
||||||
|
const existingUrls = new Set(
|
||||||
|
(this.form.attachments || []).map(file => this.attachmentFileUrl(file)).filter(Boolean)
|
||||||
|
);
|
||||||
|
const imported = (files || [])
|
||||||
|
.map(file => ({ file, material: this.resolveAttachmentMaterial(file, materials) }))
|
||||||
|
.filter(item => item.material && !existingUrls.has(this.attachmentFileUrl(item.file)))
|
||||||
|
.map(({ file, material }) => {
|
||||||
|
existingUrls.add(this.attachmentFileUrl(file));
|
||||||
|
return {
|
||||||
|
...file,
|
||||||
|
originalName: this.attachmentFileName(file),
|
||||||
|
name: this.attachmentFileName(file),
|
||||||
|
url: this.attachmentFileUrl(file),
|
||||||
|
link: file.link || this.attachmentFileUrl(file),
|
||||||
|
attachmentType: MATERIAL_TYPE_MAP[material.key],
|
||||||
|
description: file.description || `自动取自${sourceName}`,
|
||||||
|
sourceImported: true,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
if (imported.length) this.form.attachments = [...this.form.attachments, ...imported];
|
||||||
|
},
|
||||||
|
async loadCurrentSettlementDetails() {
|
||||||
|
const requests = [];
|
||||||
|
if (this.form.preSettlementId) requests.push(preApi.getDetail(this.form.preSettlementId));
|
||||||
|
if (this.form.settlementId) requests.push(formalApi.getDetail(this.form.settlementId));
|
||||||
|
if (!requests.length) return [];
|
||||||
|
const responses = await Promise.all(requests);
|
||||||
|
return responses.map(response => this.unwrapData(response)).filter(item => item?.id);
|
||||||
|
},
|
||||||
|
async loadAttachmentRuleData(settlementRows = []) {
|
||||||
|
this.attachmentRuleLoading = true;
|
||||||
|
this.firstContractPayment = false;
|
||||||
|
try {
|
||||||
|
const settlementMaterials = [
|
||||||
|
ATTACHMENT_MATERIALS.weighingSlip,
|
||||||
|
ATTACHMENT_MATERIALS.entrustOrder,
|
||||||
|
ATTACHMENT_MATERIALS.settlement,
|
||||||
|
];
|
||||||
|
const settlementFiles = (settlementRows || []).flatMap(row =>
|
||||||
|
this.parse(row.attachmentsJson)
|
||||||
|
);
|
||||||
|
this.mergeAutomaticAttachments(settlementFiles, settlementMaterials, '结算单');
|
||||||
|
if (!this.form.contractId) {
|
||||||
|
this.firstContractPayment = false;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const [contractResponse, paymentResponse] = await Promise.all([
|
||||||
|
getContractDetail(this.form.contractId),
|
||||||
|
api.getList(1, 9999, { contractId: this.form.contractId }),
|
||||||
|
]);
|
||||||
|
const contract = this.unwrapData(contractResponse) || {};
|
||||||
|
const payments = this.unwrapData(paymentResponse)?.records || [];
|
||||||
|
this.firstContractPayment = !payments.some(
|
||||||
|
item =>
|
||||||
|
String(item.contractId || '') === String(this.form.contractId) &&
|
||||||
|
String(item.id || '') !== String(this.form.id || '') &&
|
||||||
|
item.approvalStatus !== 'voided'
|
||||||
|
);
|
||||||
|
if (this.firstContractPayment) {
|
||||||
|
this.mergeAutomaticAttachments(
|
||||||
|
this.parse(contract.contractFileJson),
|
||||||
|
[ATTACHMENT_MATERIALS.contract],
|
||||||
|
'合同'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
this.$message.warning('附件清单来源材料加载失败,请核对后手工补充');
|
||||||
|
} finally {
|
||||||
|
this.attachmentRuleLoading = false;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
validateRequiredAttachments() {
|
||||||
|
if (!this.missingAttachmentMaterials.length) return true;
|
||||||
|
this.$message.warning(
|
||||||
|
`请先上传付款申请所需材料:${this.missingAttachmentMaterials
|
||||||
|
.map(item => item.label)
|
||||||
|
.join('、')}`
|
||||||
|
);
|
||||||
|
return false;
|
||||||
|
},
|
||||||
handleTypeChange() {
|
handleTypeChange() {
|
||||||
if (this.form.paymentType === 'project_advance') {
|
if (this.form.paymentType === 'project_advance') {
|
||||||
this.form.settlementId = null;
|
this.form.settlementId = null;
|
||||||
this.form.preSettlementId = null;
|
this.form.preSettlementId = null;
|
||||||
|
this.form.preSettlementIds = [];
|
||||||
|
this.form.settlementNo = '';
|
||||||
|
this.form.preSettlementNo = '';
|
||||||
|
this.referenceRows = [];
|
||||||
}
|
}
|
||||||
|
this.loadAttachmentRuleData([]);
|
||||||
|
},
|
||||||
|
clearReceiptAccount() {
|
||||||
|
Object.assign(this.form, {
|
||||||
|
receiptAccountId: null,
|
||||||
|
receiptAccountName: '',
|
||||||
|
bankName: '',
|
||||||
|
bankAccount: '',
|
||||||
|
});
|
||||||
|
},
|
||||||
|
customerRecords(response) {
|
||||||
|
return this.unwrapData(response)?.records || [];
|
||||||
|
},
|
||||||
|
async loadReceiptAccountOptions(autoSelectDefault = true) {
|
||||||
|
const payeeName = String(this.form.payeeName || '').trim();
|
||||||
|
const preserveCurrentValue = autoSelectDefault === false;
|
||||||
|
this.receiptAccountOptions = [];
|
||||||
|
this.payeeCustomerLevel = '';
|
||||||
|
if (!payeeName) {
|
||||||
|
if (!preserveCurrentValue) this.clearReceiptAccount();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.receiptAccountLoading = true;
|
||||||
|
try {
|
||||||
|
const [fullNameResponse, shortNameResponse] = await Promise.all([
|
||||||
|
getCustomerArchiveList(1, 20, {
|
||||||
|
fullName: payeeName,
|
||||||
|
approvalStatus: 'approved',
|
||||||
|
status: 1,
|
||||||
|
}),
|
||||||
|
getCustomerArchiveList(1, 20, {
|
||||||
|
shortName: payeeName,
|
||||||
|
approvalStatus: 'approved',
|
||||||
|
status: 1,
|
||||||
|
}),
|
||||||
|
]);
|
||||||
|
const customers = [
|
||||||
|
...this.customerRecords(fullNameResponse),
|
||||||
|
...this.customerRecords(shortNameResponse),
|
||||||
|
];
|
||||||
|
const customer = customers.find(
|
||||||
|
item => item.fullName === payeeName || item.shortName === payeeName
|
||||||
|
);
|
||||||
|
if (!customer?.id) {
|
||||||
|
if (!preserveCurrentValue) this.clearReceiptAccount();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const detail = this.unwrapData(await getCustomerArchiveDetail(customer.id));
|
||||||
|
this.payeeCustomerLevel = detail.customerLevel || '';
|
||||||
|
this.receiptAccountOptions = (detail.receiptAccounts || []).map(item => ({
|
||||||
|
...item,
|
||||||
|
id: String(item.id),
|
||||||
|
}));
|
||||||
|
const selected = this.receiptAccountOptions.find(
|
||||||
|
item => String(item.id) === String(this.form.receiptAccountId || '')
|
||||||
|
);
|
||||||
|
if (selected) {
|
||||||
|
this.applyReceiptAccount(selected);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (preserveCurrentValue) return;
|
||||||
|
this.clearReceiptAccount();
|
||||||
|
if (autoSelectDefault && !this.readonly) {
|
||||||
|
const defaultAccount =
|
||||||
|
this.receiptAccountOptions.find(item => Number(item.isDefault) === 1) ||
|
||||||
|
(this.receiptAccountOptions.length === 1 ? this.receiptAccountOptions[0] : null);
|
||||||
|
if (defaultAccount) this.applyReceiptAccount(defaultAccount);
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
if (!preserveCurrentValue) this.clearReceiptAccount();
|
||||||
|
this.$message.warning('收款方的客商收款信息加载失败,请稍后重试');
|
||||||
|
} finally {
|
||||||
|
this.receiptAccountLoading = false;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
receiptAccountLabel(item) {
|
||||||
|
return [item.accountName || item.accountHolderName, item.bankName, item.bankAccount]
|
||||||
|
.filter(Boolean)
|
||||||
|
.join('|');
|
||||||
|
},
|
||||||
|
applyReceiptAccount(account) {
|
||||||
|
Object.assign(this.form, {
|
||||||
|
receiptAccountId: account.id,
|
||||||
|
receiptAccountName: account.accountName || account.accountHolderName || '',
|
||||||
|
bankName: account.bankName || '',
|
||||||
|
bankAccount: account.bankAccount || '',
|
||||||
|
});
|
||||||
|
this.$nextTick(() => this.$refs.formRef?.validateField('receiptAccountId').catch(() => {}));
|
||||||
|
},
|
||||||
|
handleReceiptAccountChange(id) {
|
||||||
|
const account = this.receiptAccountOptions.find(item => String(item.id) === String(id || ''));
|
||||||
|
if (account) {
|
||||||
|
this.applyReceiptAccount(account);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.clearReceiptAccount();
|
||||||
},
|
},
|
||||||
async handlePaymentMethodChange() {
|
async handlePaymentMethodChange() {
|
||||||
if (!this.billPayment) {
|
if (!this.billPayment) {
|
||||||
@@ -624,7 +1064,10 @@ export default {
|
|||||||
await this.loadReferences();
|
await this.loadReferences();
|
||||||
this.referenceVisible = true;
|
this.referenceVisible = true;
|
||||||
},
|
},
|
||||||
selectContract(row) {
|
async selectContract(row) {
|
||||||
|
this.referenceRows = [];
|
||||||
|
this.clearReceiptAccount();
|
||||||
|
const settlementType = row.settlementType || 'payable';
|
||||||
Object.assign(this.form, {
|
Object.assign(this.form, {
|
||||||
projectId: row.projectId,
|
projectId: row.projectId,
|
||||||
projectName: row.projectName,
|
projectName: row.projectName,
|
||||||
@@ -633,21 +1076,31 @@ export default {
|
|||||||
contractId: row.id,
|
contractId: row.id,
|
||||||
contractNo: row.contractNo,
|
contractNo: row.contractNo,
|
||||||
contractName: row.contractName,
|
contractName: row.contractName,
|
||||||
payerName: row.payerName,
|
payerName:
|
||||||
payeeName: row.payeeName,
|
row.payerName || (settlementType === 'receivable' ? row.partyB : row.partyA) || '',
|
||||||
|
payeeName:
|
||||||
|
row.payeeName || (settlementType === 'receivable' ? row.partyA : row.partyB) || '',
|
||||||
billType: '项目预付',
|
billType: '项目预付',
|
||||||
});
|
});
|
||||||
this.referenceVisible = false;
|
this.referenceVisible = false;
|
||||||
|
await Promise.all([this.loadReceiptAccountOptions(), this.loadAttachmentRuleData([])]);
|
||||||
if (this.billPayment) this.loadBillOptions();
|
if (this.billPayment) this.loadBillOptions();
|
||||||
},
|
},
|
||||||
selectFormal(row) {
|
async selectFormal(row) {
|
||||||
|
this.referenceRows = [this.createReferenceRow(row)];
|
||||||
|
this.clearReceiptAccount();
|
||||||
Object.assign(this.form, {
|
Object.assign(this.form, {
|
||||||
settlementId: row.id,
|
settlementId: row.id,
|
||||||
preSettlementId: null,
|
preSettlementId: null,
|
||||||
|
preSettlementIds: [],
|
||||||
settlementNo: row.formalSettlementNo,
|
settlementNo: row.formalSettlementNo,
|
||||||
|
preSettlementNo: '',
|
||||||
|
projectId: row.projectId,
|
||||||
projectName: row.projectName,
|
projectName: row.projectName,
|
||||||
deptId: row.deptId,
|
deptId: row.deptId,
|
||||||
deptName: row.deptName,
|
deptName: row.deptName,
|
||||||
|
contractId: row.contractId,
|
||||||
|
contractNo: row.contractNo,
|
||||||
contractName: row.contractName,
|
contractName: row.contractName,
|
||||||
settlementAmount: row.settlementAmount,
|
settlementAmount: row.settlementAmount,
|
||||||
payableAmount: Math.max(
|
payableAmount: Math.max(
|
||||||
@@ -655,19 +1108,32 @@ export default {
|
|||||||
Number(row.settlementAmount || 0) - Number(row.appliedPaymentAmount || 0)
|
Number(row.settlementAmount || 0) - Number(row.appliedPaymentAmount || 0)
|
||||||
),
|
),
|
||||||
billType: '正式结算单',
|
billType: '正式结算单',
|
||||||
|
payerName: row.payerName,
|
||||||
payeeName: row.payeeName,
|
payeeName: row.payeeName,
|
||||||
});
|
});
|
||||||
this.referenceVisible = false;
|
this.referenceVisible = false;
|
||||||
|
const detail = this.unwrapData(await formalApi.getDetail(row.id));
|
||||||
|
await Promise.all([
|
||||||
|
this.loadReceiptAccountOptions(),
|
||||||
|
this.loadAttachmentRuleData(detail?.id ? [detail] : []),
|
||||||
|
]);
|
||||||
if (this.billPayment) this.loadBillOptions();
|
if (this.billPayment) this.loadBillOptions();
|
||||||
},
|
},
|
||||||
selectPre(row) {
|
async selectPre(row) {
|
||||||
|
this.referenceRows = [this.createReferenceRow(row)];
|
||||||
|
this.clearReceiptAccount();
|
||||||
Object.assign(this.form, {
|
Object.assign(this.form, {
|
||||||
preSettlementId: row.id,
|
preSettlementId: row.id,
|
||||||
|
preSettlementIds: [row.id],
|
||||||
settlementId: null,
|
settlementId: null,
|
||||||
preSettlementNo: row.preSettlementNo,
|
preSettlementNo: row.preSettlementNo,
|
||||||
|
settlementNo: '',
|
||||||
|
projectId: row.projectId,
|
||||||
projectName: row.projectName,
|
projectName: row.projectName,
|
||||||
deptId: row.deptId,
|
deptId: row.deptId,
|
||||||
deptName: row.deptName,
|
deptName: row.deptName,
|
||||||
|
contractId: row.contractId,
|
||||||
|
contractNo: row.contractNo,
|
||||||
contractName: row.contractName,
|
contractName: row.contractName,
|
||||||
settlementAmount: row.settlementAmount,
|
settlementAmount: row.settlementAmount,
|
||||||
payableAmount: Math.max(
|
payableAmount: Math.max(
|
||||||
@@ -675,9 +1141,15 @@ export default {
|
|||||||
Number(row.settlementAmount || 0) - Number(row.advanceAppliedAmount || 0)
|
Number(row.settlementAmount || 0) - Number(row.advanceAppliedAmount || 0)
|
||||||
),
|
),
|
||||||
billType: '预结算单',
|
billType: '预结算单',
|
||||||
|
payerName: row.payerName,
|
||||||
payeeName: row.payeeName,
|
payeeName: row.payeeName,
|
||||||
});
|
});
|
||||||
this.referenceVisible = false;
|
this.referenceVisible = false;
|
||||||
|
const detail = this.unwrapData(await preApi.getDetail(row.id));
|
||||||
|
await Promise.all([
|
||||||
|
this.loadReceiptAccountOptions(),
|
||||||
|
this.loadAttachmentRuleData(detail?.id ? [detail] : []),
|
||||||
|
]);
|
||||||
if (this.billPayment) this.loadBillOptions();
|
if (this.billPayment) this.loadBillOptions();
|
||||||
},
|
},
|
||||||
addInvoice() {
|
addInvoice() {
|
||||||
@@ -698,7 +1170,10 @@ export default {
|
|||||||
const time = this.$dayjs().format('YYYY-MM-DD HH:mm:ss');
|
const time = this.$dayjs().format('YYYY-MM-DD HH:mm:ss');
|
||||||
this.form.attachments = (files || []).map(file => ({
|
this.form.attachments = (files || []).map(file => ({
|
||||||
...file,
|
...file,
|
||||||
attachmentType: file.attachmentType || 'other',
|
attachmentType:
|
||||||
|
file.attachmentType ||
|
||||||
|
MATERIAL_TYPE_MAP[this.resolveAttachmentMaterial(file)?.key] ||
|
||||||
|
'other',
|
||||||
description: file.description || '',
|
description: file.description || '',
|
||||||
uploadUserName: file.uploadUserName || userName,
|
uploadUserName: file.uploadUserName || userName,
|
||||||
uploadTime: file.uploadTime || time,
|
uploadTime: file.uploadTime || time,
|
||||||
@@ -710,6 +1185,7 @@ export default {
|
|||||||
paymentType,
|
paymentType,
|
||||||
settlementId,
|
settlementId,
|
||||||
preSettlementId,
|
preSettlementId,
|
||||||
|
preSettlementIds,
|
||||||
projectId,
|
projectId,
|
||||||
projectName,
|
projectName,
|
||||||
deptId,
|
deptId,
|
||||||
@@ -740,6 +1216,7 @@ export default {
|
|||||||
paymentType,
|
paymentType,
|
||||||
settlementId,
|
settlementId,
|
||||||
preSettlementId,
|
preSettlementId,
|
||||||
|
preSettlementIds,
|
||||||
projectId,
|
projectId,
|
||||||
projectName,
|
projectName,
|
||||||
deptId,
|
deptId,
|
||||||
@@ -766,12 +1243,13 @@ export default {
|
|||||||
invoices,
|
invoices,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
async saveDraft() {
|
async saveDraft(validateMaterials = false) {
|
||||||
await this.$refs.formRef.validate();
|
await this.$refs.formRef.validate();
|
||||||
if (this.form.paymentType === 'project_advance' && !this.form.projectId) {
|
if (this.form.paymentType === 'project_advance' && !this.form.projectId) {
|
||||||
this.$message.warning('请选择所属项目');
|
this.$message.warning('请选择所属项目');
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
if (validateMaterials && !this.validateRequiredAttachments()) return false;
|
||||||
try {
|
try {
|
||||||
this.validateInvoices();
|
this.validateInvoices();
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
@@ -784,13 +1262,14 @@ export default {
|
|||||||
return true;
|
return true;
|
||||||
},
|
},
|
||||||
async submitForm() {
|
async submitForm() {
|
||||||
const saved = await this.saveDraft();
|
const saved = await this.saveDraft(true);
|
||||||
if (!saved) return;
|
if (!saved) return;
|
||||||
await api.submit({ id: this.form.id });
|
await api.submit({ id: this.form.id });
|
||||||
this.$message.success('提交成功');
|
this.$message.success('提交成功');
|
||||||
this.goBack();
|
this.goBack();
|
||||||
},
|
},
|
||||||
goBack() {
|
goBack() {
|
||||||
|
removeSettlementTransfer(this.$route.query.transferToken);
|
||||||
this.$router.push('/payment/payment-application');
|
this.$router.push('/payment/payment-application');
|
||||||
},
|
},
|
||||||
formatMoney(value) {
|
formatMoney(value) {
|
||||||
@@ -818,6 +1297,23 @@ export default {
|
|||||||
justify-content: flex-start;
|
justify-content: flex-start;
|
||||||
margin-top: 12px;
|
margin-top: 12px;
|
||||||
}
|
}
|
||||||
|
.payment-form-page__missing-text {
|
||||||
|
margin-left: 12px;
|
||||||
|
color: var(--el-color-danger);
|
||||||
|
font-size: 13px;
|
||||||
|
font-weight: 400;
|
||||||
|
}
|
||||||
|
.payment-form-page__attachment-checklist {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
margin-bottom: 12px;
|
||||||
|
}
|
||||||
|
.payment-form-page__attachment-checklist-label {
|
||||||
|
color: var(--el-text-color-regular);
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
.payment-form-page__attachment-upload .vehicle-attachment-upload {
|
.payment-form-page__attachment-upload .vehicle-attachment-upload {
|
||||||
width: auto;
|
width: auto;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -136,6 +136,9 @@
|
|||||||
<el-input-number
|
<el-input-number
|
||||||
v-else-if="editable && column.prop === 'adjustAmount'"
|
v-else-if="editable && column.prop === 'adjustAmount'"
|
||||||
v-model="row.adjustAmount"
|
v-model="row.adjustAmount"
|
||||||
|
:class="{
|
||||||
|
'formal-editor__negative-amount': Number(row.adjustAmount || 0) < 0,
|
||||||
|
}"
|
||||||
:precision="2"
|
:precision="2"
|
||||||
:step="0.01"
|
:step="0.01"
|
||||||
:controls="false"
|
:controls="false"
|
||||||
@@ -147,7 +150,13 @@
|
|||||||
maxlength="50"
|
maxlength="50"
|
||||||
show-word-limit
|
show-word-limit
|
||||||
/>
|
/>
|
||||||
<span v-else-if="isSummaryMoney(column.prop)">
|
<span
|
||||||
|
v-else-if="isSummaryMoney(column.prop)"
|
||||||
|
:class="{
|
||||||
|
'formal-editor__negative-amount':
|
||||||
|
column.prop === 'adjustAmount' && Number(row[column.prop] || 0) < 0,
|
||||||
|
}"
|
||||||
|
>
|
||||||
{{ formatMoney(row[column.prop]) }}
|
{{ formatMoney(row[column.prop]) }}
|
||||||
</span>
|
</span>
|
||||||
<span v-else-if="column.prop === 'feeType'">{{ feeCategoryName(row.feeType) }}</span>
|
<span v-else-if="column.prop === 'feeType'">{{ feeCategoryName(row.feeType) }}</span>
|
||||||
@@ -214,6 +223,9 @@
|
|||||||
>
|
>
|
||||||
<template #default="{ row }">
|
<template #default="{ row }">
|
||||||
<span v-if="column.money">{{ formatMoney(row[column.prop]) }}</span>
|
<span v-if="column.money">{{ formatMoney(row[column.prop]) }}</span>
|
||||||
|
<span v-else-if="column.prop === 'transportType'">
|
||||||
|
{{ transportTypeName(row[column.prop]) }}
|
||||||
|
</span>
|
||||||
<span v-else>{{ displayValue(row[column.prop]) }}</span>
|
<span v-else>{{ displayValue(row[column.prop]) }}</span>
|
||||||
</template>
|
</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
@@ -232,15 +244,79 @@
|
|||||||
</el-table>
|
</el-table>
|
||||||
</section-card>
|
</section-card>
|
||||||
|
|
||||||
<section-card title="附件">
|
<section-card>
|
||||||
<vehicle-attachment-upload
|
<template #title>
|
||||||
v-model="attachments"
|
<span>附件</span>
|
||||||
:readonly="!editable"
|
<span v-if="missingAttachmentTypeText" class="formal-editor__attachment-missing">
|
||||||
:multiple="true"
|
{{ missingAttachmentTypeText }}
|
||||||
:limit="20"
|
</span>
|
||||||
:max-size="500"
|
</template>
|
||||||
:file-types="attachmentFileTypes"
|
<el-table :data="attachments" border>
|
||||||
/>
|
<el-table-column type="index" label="序号" width="64" align="center" />
|
||||||
|
<el-table-column label="类型" min-width="180" align="center">
|
||||||
|
<template #default="{ row }">
|
||||||
|
<el-select
|
||||||
|
v-if="editable"
|
||||||
|
v-model="row.type"
|
||||||
|
filterable
|
||||||
|
placeholder="请选择类型"
|
||||||
|
@change="sortAttachments"
|
||||||
|
>
|
||||||
|
<el-option
|
||||||
|
v-for="item in attachmentTypeOptions"
|
||||||
|
:key="item.value"
|
||||||
|
:label="item.label"
|
||||||
|
:value="item.value"
|
||||||
|
/>
|
||||||
|
</el-select>
|
||||||
|
<span v-else>{{ attachmentTypeName(row.type) }}</span>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column label="文件名" min-width="220" align="left" show-overflow-tooltip>
|
||||||
|
<template #default="{ row }">
|
||||||
|
<el-link
|
||||||
|
:disabled="!getAttachmentUrl(row)"
|
||||||
|
type="primary"
|
||||||
|
@click="previewAttachment(row)"
|
||||||
|
>
|
||||||
|
{{ row.originalName || row.name || '-' }}
|
||||||
|
</el-link>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column prop="uploadUserName" label="上传人" min-width="120" align="center">
|
||||||
|
<template #default="{ row }">
|
||||||
|
{{ displayValue(row.uploadUserName) }}
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column prop="uploadTime" label="上传时间" min-width="170" align="center">
|
||||||
|
<template #default="{ row }">
|
||||||
|
{{ displayValue(row.uploadTime) }}
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column prop="size" label="文件大小" min-width="120" align="center">
|
||||||
|
<template #default="{ row }">
|
||||||
|
{{ displayValue(row.size) }}
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column v-if="editable" label="操作" width="90" align="center">
|
||||||
|
<template #default="{ $index }">
|
||||||
|
<el-link type="danger" @click="removeAttachment($index)">删除</el-link>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
</el-table>
|
||||||
|
<div v-if="editable" class="formal-editor__attachment-upload">
|
||||||
|
<vehicle-attachment-upload
|
||||||
|
v-model="attachmentUploadFiles"
|
||||||
|
:multiple="true"
|
||||||
|
:limit="20"
|
||||||
|
:max-size="500"
|
||||||
|
:file-types="attachmentFileTypes"
|
||||||
|
button-text="上传附件"
|
||||||
|
:show-file-list="false"
|
||||||
|
:show-uploading="true"
|
||||||
|
@success="handleAttachmentUploadSuccess"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
</section-card>
|
</section-card>
|
||||||
|
|
||||||
<section-card v-if="readonly && payments.length" title="付款信息">
|
<section-card v-if="readonly && payments.length" title="付款信息">
|
||||||
@@ -313,10 +389,13 @@
|
|||||||
v-bind="column"
|
v-bind="column"
|
||||||
align="center"
|
align="center"
|
||||||
>
|
>
|
||||||
<template #default="{ row }"
|
<template #default="{ row }">
|
||||||
><span v-if="column.money">{{ formatMoney(row[column.prop]) }}</span
|
<span v-if="column.money">{{ formatMoney(row[column.prop]) }}</span>
|
||||||
><span v-else>{{ displayValue(row[column.prop]) }}</span></template
|
<span v-else-if="column.prop === 'transportType'">
|
||||||
>
|
{{ transportTypeName(row[column.prop]) }}
|
||||||
|
</span>
|
||||||
|
<span v-else>{{ displayValue(row[column.prop]) }}</span>
|
||||||
|
</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
</el-table>
|
</el-table>
|
||||||
<div class="formal-editor__pagination">
|
<div class="formal-editor__pagination">
|
||||||
@@ -493,6 +572,8 @@ import {
|
|||||||
sourceColumns,
|
sourceColumns,
|
||||||
summaryColumns,
|
summaryColumns,
|
||||||
} from '@/option/settlement/formalSettlementTable';
|
} from '@/option/settlement/formalSettlementTable';
|
||||||
|
import { getDictionary } from '@/api/system/dictbiz';
|
||||||
|
import { mapGetters } from 'vuex';
|
||||||
import * as XLSX from 'xlsx';
|
import * as XLSX from 'xlsx';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
@@ -518,6 +599,8 @@ export default {
|
|||||||
summaryFees: [],
|
summaryFees: [],
|
||||||
payments: [],
|
payments: [],
|
||||||
attachments: [],
|
attachments: [],
|
||||||
|
attachmentUploadFiles: [],
|
||||||
|
attachmentTypeOptions: [],
|
||||||
attachmentFileTypes: [
|
attachmentFileTypes: [
|
||||||
'pdf',
|
'pdf',
|
||||||
'bmp',
|
'bmp',
|
||||||
@@ -539,6 +622,7 @@ export default {
|
|||||||
allContracts: [],
|
allContracts: [],
|
||||||
projects: [],
|
projects: [],
|
||||||
feeOptions: [],
|
feeOptions: [],
|
||||||
|
transportTypeOptions: [],
|
||||||
fields: formalSettlementFormFields,
|
fields: formalSettlementFormFields,
|
||||||
sourceTableColumns: sourceColumns,
|
sourceTableColumns: sourceColumns,
|
||||||
summaryTableColumns: summaryColumns,
|
summaryTableColumns: summaryColumns,
|
||||||
@@ -592,6 +676,7 @@ export default {
|
|||||||
};
|
};
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
|
...mapGetters(['userInfo']),
|
||||||
visible: {
|
visible: {
|
||||||
get() {
|
get() {
|
||||||
return this.modelValue;
|
return this.modelValue;
|
||||||
@@ -635,6 +720,10 @@ export default {
|
|||||||
})
|
})
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
missingAttachmentTypeText() {
|
||||||
|
const missing = this.getMissingAttachmentTypes();
|
||||||
|
return missing.length ? `未上传:${missing.join('、')}` : '';
|
||||||
|
},
|
||||||
},
|
},
|
||||||
watch: {
|
watch: {
|
||||||
modelValue: {
|
modelValue: {
|
||||||
@@ -663,14 +752,19 @@ export default {
|
|||||||
this.summaryFees = [];
|
this.summaryFees = [];
|
||||||
this.payments = [];
|
this.payments = [];
|
||||||
this.attachments = [];
|
this.attachments = [];
|
||||||
|
this.attachmentUploadFiles = [];
|
||||||
this.detailCollapsed = false;
|
this.detailCollapsed = false;
|
||||||
this.resetDetailQuery(false);
|
this.resetDetailQuery(false);
|
||||||
await Promise.all([this.loadAllContracts(), this.loadFeeOptions()]);
|
await Promise.all([
|
||||||
|
this.loadAllContracts(),
|
||||||
|
this.loadFeeOptions(),
|
||||||
|
this.loadAttachmentTypeOptions(),
|
||||||
|
this.loadTransportTypeOptions(),
|
||||||
|
]);
|
||||||
if (!this.recordId) {
|
if (!this.recordId) {
|
||||||
this.form.exchangeRateDate = this.$dayjs().format('YYYY-MM-DD');
|
this.form.exchangeRateDate = this.$dayjs().format('YYYY-MM-DD');
|
||||||
const response = await getNextNo();
|
|
||||||
this.form.formalSettlementNo = this.unwrapData(response) || '';
|
|
||||||
if (this.initialData) await this.applyInitialData();
|
if (this.initialData) await this.applyInitialData();
|
||||||
|
await this.refreshFormalSettlementNo();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
this.loading = true;
|
this.loading = true;
|
||||||
@@ -690,6 +784,7 @@ export default {
|
|||||||
this.summaryFees = data.summaryFees || [];
|
this.summaryFees = data.summaryFees || [];
|
||||||
this.payments = data.payments || [];
|
this.payments = data.payments || [];
|
||||||
this.attachments = this.parseAttachments(data.attachmentsJson);
|
this.attachments = this.parseAttachments(data.attachmentsJson);
|
||||||
|
this.sortAttachments();
|
||||||
this.contracts = this.allContracts.filter(
|
this.contracts = this.allContracts.filter(
|
||||||
item => String(item.projectId) === String(this.form.projectId)
|
item => String(item.projectId) === String(this.form.projectId)
|
||||||
);
|
);
|
||||||
@@ -697,6 +792,14 @@ export default {
|
|||||||
this.loading = false;
|
this.loading = false;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
async refreshFormalSettlementNo() {
|
||||||
|
if (this.form.id || !this.form.settlementType) {
|
||||||
|
if (!this.form.id) this.form.formalSettlementNo = '';
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const response = await getNextNo(this.form.settlementType);
|
||||||
|
this.form.formalSettlementNo = this.unwrapData(response) || '';
|
||||||
|
},
|
||||||
async applyInitialData() {
|
async applyInitialData() {
|
||||||
const sourcePreSettlements = Array.isArray(this.initialData?.sourcePreSettlements)
|
const sourcePreSettlements = Array.isArray(this.initialData?.sourcePreSettlements)
|
||||||
? this.initialData.sourcePreSettlements
|
? this.initialData.sourcePreSettlements
|
||||||
@@ -739,7 +842,7 @@ export default {
|
|||||||
settlementType,
|
settlementType,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
this.handleContractChange(contractId);
|
this.handleContractChange(contractId, false);
|
||||||
Object.assign(this.form, {
|
Object.assign(this.form, {
|
||||||
contractId,
|
contractId,
|
||||||
contractNo: first.contractNo || this.form.contractNo,
|
contractNo: first.contractNo || this.form.contractNo,
|
||||||
@@ -781,7 +884,7 @@ export default {
|
|||||||
settlementType,
|
settlementType,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
this.handleContractChange(contractId);
|
this.handleContractChange(contractId, false);
|
||||||
this.sources = rows.map(row => ({
|
this.sources = rows.map(row => ({
|
||||||
...row,
|
...row,
|
||||||
preSettlementId: row.preSettlementId || row.id,
|
preSettlementId: row.preSettlementId || row.id,
|
||||||
@@ -869,7 +972,7 @@ export default {
|
|||||||
this.form.sourceDetailIds = [];
|
this.form.sourceDetailIds = [];
|
||||||
this.loadContracts();
|
this.loadContracts();
|
||||||
},
|
},
|
||||||
handleContractChange(id) {
|
handleContractChange(id, refreshSettlementNo = true) {
|
||||||
const contract = this.contracts.find(item => String(item.id) === String(id));
|
const contract = this.contracts.find(item => String(item.id) === String(id));
|
||||||
if (!contract) return;
|
if (!contract) return;
|
||||||
const formalSettlementId = this.form.id;
|
const formalSettlementId = this.form.id;
|
||||||
@@ -889,6 +992,7 @@ export default {
|
|||||||
this.summaryFees = [];
|
this.summaryFees = [];
|
||||||
this.form.sourcePreSettlementIds = [];
|
this.form.sourcePreSettlementIds = [];
|
||||||
this.form.sourceDetailIds = [];
|
this.form.sourceDetailIds = [];
|
||||||
|
if (refreshSettlementNo) this.refreshFormalSettlementNo();
|
||||||
},
|
},
|
||||||
openCandidateDialog() {
|
openCandidateDialog() {
|
||||||
this.candidate.visible = true;
|
this.candidate.visible = true;
|
||||||
@@ -933,6 +1037,7 @@ export default {
|
|||||||
contractId: this.sources[0].contractId,
|
contractId: this.sources[0].contractId,
|
||||||
formalSettlementNo: this.form.formalSettlementNo,
|
formalSettlementNo: this.form.formalSettlementNo,
|
||||||
});
|
});
|
||||||
|
this.refreshFormalSettlementNo();
|
||||||
}
|
}
|
||||||
this.form.settlementAmount = this.sources.reduce(
|
this.form.settlementAmount = this.sources.reduce(
|
||||||
(sum, item) => sum + Number(item.settlementAmount || 0),
|
(sum, item) => sum + Number(item.settlementAmount || 0),
|
||||||
@@ -1187,6 +1292,7 @@ export default {
|
|||||||
if (invalidManualFee) {
|
if (invalidManualFee) {
|
||||||
return this.$message.warning('请完善手工费用的费用类型和费用项');
|
return this.$message.warning('请完善手工费用的费用类型和费用项');
|
||||||
}
|
}
|
||||||
|
if (!this.validateAttachments()) return;
|
||||||
this.saving = true;
|
this.saving = true;
|
||||||
try {
|
try {
|
||||||
await save({
|
await save({
|
||||||
@@ -1213,7 +1319,7 @@ export default {
|
|||||||
remark: row.remark,
|
remark: row.remark,
|
||||||
manualFlag: row.manualFlag || 0,
|
manualFlag: row.manualFlag || 0,
|
||||||
})),
|
})),
|
||||||
attachmentsJson: JSON.stringify(this.attachments || []),
|
attachmentsJson: this.stringifyAttachments(this.attachments),
|
||||||
remark: this.form.remark,
|
remark: this.form.remark,
|
||||||
});
|
});
|
||||||
this.$message.success('保存成功');
|
this.$message.success('保存成功');
|
||||||
@@ -1233,6 +1339,142 @@ export default {
|
|||||||
formatMoney(value) {
|
formatMoney(value) {
|
||||||
return `${Number(value || 0).toFixed(2)} RMB`;
|
return `${Number(value || 0).toFixed(2)} RMB`;
|
||||||
},
|
},
|
||||||
|
async loadTransportTypeOptions() {
|
||||||
|
const { data } = await getDictionary({ code: 'transport_type' });
|
||||||
|
this.transportTypeOptions = data?.data || [];
|
||||||
|
},
|
||||||
|
transportTypeName(value) {
|
||||||
|
if (value === undefined || value === null || value === '') return '';
|
||||||
|
const normalizedValue = String(value).trim().toLocaleLowerCase();
|
||||||
|
const option = this.transportTypeOptions.find(
|
||||||
|
item =>
|
||||||
|
String(item.dictKey ?? '')
|
||||||
|
.trim()
|
||||||
|
.toLocaleLowerCase() === normalizedValue ||
|
||||||
|
String(item.dictValue ?? '')
|
||||||
|
.trim()
|
||||||
|
.toLocaleLowerCase() === normalizedValue
|
||||||
|
);
|
||||||
|
return option?.dictValue || value;
|
||||||
|
},
|
||||||
|
async loadAttachmentTypeOptions() {
|
||||||
|
const response = await getDictionary({ code: 'settle_attachment_types' });
|
||||||
|
const data = response?.data?.data || [];
|
||||||
|
this.attachmentTypeOptions = data.map(item => ({
|
||||||
|
label: item.dictValue,
|
||||||
|
value: item.dictValue,
|
||||||
|
}));
|
||||||
|
this.sortAttachments();
|
||||||
|
},
|
||||||
|
resolveAttachmentType(fileName) {
|
||||||
|
const normalizedName = String(fileName || '').toLocaleLowerCase();
|
||||||
|
const matchedType = [...this.attachmentTypeOptions]
|
||||||
|
.filter(item => item?.label || item?.value)
|
||||||
|
.sort(
|
||||||
|
(a, b) =>
|
||||||
|
String(b.label || b.value || '').length - String(a.label || a.value || '').length
|
||||||
|
)
|
||||||
|
.find(item => {
|
||||||
|
const typeText = String(item.label || item.value || '').toLocaleLowerCase();
|
||||||
|
return typeText && normalizedName.includes(typeText);
|
||||||
|
});
|
||||||
|
if (matchedType?.value) return matchedType.value;
|
||||||
|
const otherType = this.attachmentTypeOptions.find(item =>
|
||||||
|
String(item.label || item.value || '').includes('其他')
|
||||||
|
);
|
||||||
|
if (otherType?.value) return otherType.value;
|
||||||
|
this.attachmentTypeOptions.push({ label: '其他附件', value: '其他附件' });
|
||||||
|
return '其他附件';
|
||||||
|
},
|
||||||
|
getAttachmentTypeOrder(type) {
|
||||||
|
const normalizedType = String(type || '').trim();
|
||||||
|
const index = this.attachmentTypeOptions.findIndex(
|
||||||
|
item =>
|
||||||
|
String(item.value || '').trim() === normalizedType ||
|
||||||
|
String(item.label || '').trim() === normalizedType
|
||||||
|
);
|
||||||
|
return index === -1 ? Number.MAX_SAFE_INTEGER : index;
|
||||||
|
},
|
||||||
|
sortAttachments() {
|
||||||
|
this.attachments = (this.attachments || [])
|
||||||
|
.map((file, index) => ({ file, index }))
|
||||||
|
.sort((a, b) => {
|
||||||
|
const orderDifference =
|
||||||
|
this.getAttachmentTypeOrder(a.file.type) - this.getAttachmentTypeOrder(b.file.type);
|
||||||
|
return orderDifference || a.index - b.index;
|
||||||
|
})
|
||||||
|
.map(item => item.file);
|
||||||
|
},
|
||||||
|
getMissingAttachmentTypes(files = this.attachments) {
|
||||||
|
const uploadedTypes = new Set(
|
||||||
|
(files || [])
|
||||||
|
.filter(item => this.getAttachmentUrl(item))
|
||||||
|
.map(item => String(item.type || '').trim())
|
||||||
|
.filter(Boolean)
|
||||||
|
);
|
||||||
|
return this.attachmentTypeOptions
|
||||||
|
.filter(item => String(item.label || item.value || '').trim() !== '其他附件')
|
||||||
|
.filter(item => !uploadedTypes.has(String(item.value || item.label || '').trim()))
|
||||||
|
.map(item => item.label || item.value);
|
||||||
|
},
|
||||||
|
validateAttachments(files = this.attachments) {
|
||||||
|
const missing = this.getMissingAttachmentTypes(files);
|
||||||
|
if (!missing.length) return true;
|
||||||
|
this.$message.warning(`请先上传附件:${missing.join('、')}`);
|
||||||
|
return false;
|
||||||
|
},
|
||||||
|
handleAttachmentUploadSuccess(file) {
|
||||||
|
const originalName = file.originalName || file.name || '附件';
|
||||||
|
this.attachments.push({
|
||||||
|
type: this.resolveAttachmentType(originalName),
|
||||||
|
originalName,
|
||||||
|
name: originalName,
|
||||||
|
uploadUserName: this.userInfo.realName || this.userInfo.userName || '',
|
||||||
|
uploadTime: this.$dayjs().format('YYYY-MM-DD HH:mm:ss'),
|
||||||
|
size: this.formatAttachmentSize(file.size),
|
||||||
|
url: file.url || file.link || '',
|
||||||
|
});
|
||||||
|
this.sortAttachments();
|
||||||
|
},
|
||||||
|
removeAttachment(index) {
|
||||||
|
this.attachments.splice(index, 1);
|
||||||
|
},
|
||||||
|
getAttachmentUrl(file = {}) {
|
||||||
|
return file.url || file.link || file.src || file.domain || '';
|
||||||
|
},
|
||||||
|
previewAttachment(file) {
|
||||||
|
const url = this.getAttachmentUrl(file);
|
||||||
|
if (!url) return this.$message.warning('附件地址为空,无法预览');
|
||||||
|
window.open(url, '_blank');
|
||||||
|
},
|
||||||
|
attachmentTypeName(type) {
|
||||||
|
const option = this.attachmentTypeOptions.find(
|
||||||
|
item => String(item.value) === String(type) || String(item.label) === String(type)
|
||||||
|
);
|
||||||
|
return this.displayValue(option?.label || type);
|
||||||
|
},
|
||||||
|
formatAttachmentSize(size) {
|
||||||
|
if (!size) return '';
|
||||||
|
const byteSize = Number(size);
|
||||||
|
if (!Number.isFinite(byteSize)) return String(size);
|
||||||
|
const mb = byteSize / 1024 / 1024;
|
||||||
|
if (mb >= 1) return `${mb.toFixed(2)} MB`;
|
||||||
|
return `${(byteSize / 1024).toFixed(2)} KB`;
|
||||||
|
},
|
||||||
|
stringifyAttachments(list) {
|
||||||
|
const files = (list || [])
|
||||||
|
.filter(item => this.getAttachmentUrl(item))
|
||||||
|
.map(item => ({
|
||||||
|
type: String(item.type || '').trim(),
|
||||||
|
originalName: String(item.originalName || item.name || '').trim(),
|
||||||
|
name: String(item.originalName || item.name || '').trim(),
|
||||||
|
uploadUserName: String(item.uploadUserName || '').trim(),
|
||||||
|
uploadTime: String(item.uploadTime || '').trim(),
|
||||||
|
size: String(item.size || '').trim(),
|
||||||
|
url: String(this.getAttachmentUrl(item)).trim(),
|
||||||
|
}));
|
||||||
|
return files.length ? JSON.stringify(files) : '';
|
||||||
|
},
|
||||||
parseFeeItems(value) {
|
parseFeeItems(value) {
|
||||||
if (!value) return {};
|
if (!value) return {};
|
||||||
if (typeof value === 'object') return value;
|
if (typeof value === 'object') return value;
|
||||||
@@ -1244,12 +1486,38 @@ export default {
|
|||||||
},
|
},
|
||||||
parseAttachments(value) {
|
parseAttachments(value) {
|
||||||
if (!value) return [];
|
if (!value) return [];
|
||||||
if (Array.isArray(value)) return value;
|
let attachments = value;
|
||||||
|
if (!Array.isArray(value)) {
|
||||||
|
try {
|
||||||
|
attachments = JSON.parse(value);
|
||||||
|
} catch {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!Array.isArray(attachments)) return [];
|
||||||
|
return attachments.map(item => {
|
||||||
|
const url = this.getAttachmentUrl(item);
|
||||||
|
const originalName =
|
||||||
|
item.originalName || item.name || this.getAttachmentFileName(url) || '附件';
|
||||||
|
return {
|
||||||
|
...item,
|
||||||
|
type: item.type || this.resolveAttachmentType(originalName),
|
||||||
|
originalName,
|
||||||
|
name: originalName,
|
||||||
|
uploadUserName: item.uploadUserName || item.createUserName || item.uploadUser || '',
|
||||||
|
uploadTime: item.uploadTime || item.createTime || '',
|
||||||
|
size: this.formatAttachmentSize(item.size || item.attachSize),
|
||||||
|
url,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
},
|
||||||
|
getAttachmentFileName(url) {
|
||||||
|
if (!url) return '';
|
||||||
|
const path = String(url).split('?')[0];
|
||||||
try {
|
try {
|
||||||
const parsed = JSON.parse(value);
|
return decodeURIComponent(path.substring(path.lastIndexOf('/') + 1));
|
||||||
return Array.isArray(parsed) ? parsed : [];
|
|
||||||
} catch {
|
} catch {
|
||||||
return [];
|
return path.substring(path.lastIndexOf('/') + 1);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@@ -1305,6 +1573,21 @@ export default {
|
|||||||
.formal-editor__adjust-reason {
|
.formal-editor__adjust-reason {
|
||||||
margin-top: 16px;
|
margin-top: 16px;
|
||||||
}
|
}
|
||||||
|
.formal-editor__attachment-missing {
|
||||||
|
margin-left: 12px;
|
||||||
|
color: var(--el-color-danger);
|
||||||
|
font-size: 13px;
|
||||||
|
font-weight: 400;
|
||||||
|
}
|
||||||
|
.formal-editor__attachment-upload {
|
||||||
|
margin-top: 12px;
|
||||||
|
}
|
||||||
|
.formal-editor__negative-amount {
|
||||||
|
color: var(--el-color-danger);
|
||||||
|
}
|
||||||
|
.formal-editor :deep(.formal-editor__negative-amount .el-input__inner) {
|
||||||
|
color: var(--el-color-danger);
|
||||||
|
}
|
||||||
/* 选择预结算单 / 选择结算明细 弹窗搜索区白底 */
|
/* 选择预结算单 / 选择结算明细 弹窗搜索区白底 */
|
||||||
.formal-editor__candidate-search,
|
.formal-editor__candidate-search,
|
||||||
.formal-editor__detail-search {
|
.formal-editor__detail-search {
|
||||||
|
|||||||
@@ -49,9 +49,9 @@
|
|||||||
v-loading="loading"
|
v-loading="loading"
|
||||||
:data="rows"
|
:data="rows"
|
||||||
border
|
border
|
||||||
@selection-change="handleSelectionChange"
|
highlight-current-row
|
||||||
|
@current-change="handleCurrentChange"
|
||||||
>
|
>
|
||||||
<el-table-column type="selection" width="52" fixed="left" align="center" />
|
|
||||||
<el-table-column type="index" label="序号" width="64" fixed="left" align="center" />
|
<el-table-column type="index" label="序号" width="64" fixed="left" align="center" />
|
||||||
<el-table-column
|
<el-table-column
|
||||||
v-for="column in columns"
|
v-for="column in columns"
|
||||||
@@ -61,12 +61,18 @@
|
|||||||
show-overflow-tooltip
|
show-overflow-tooltip
|
||||||
>
|
>
|
||||||
<template #default="{ row }">
|
<template #default="{ row }">
|
||||||
<el-link v-if="column.link && row[column.prop]" type="primary" @click="emitAction('view', row)">{{
|
<el-link
|
||||||
row[column.prop]
|
v-if="column.link && row[column.prop]"
|
||||||
}}</el-link>
|
type="primary"
|
||||||
<el-tag v-else-if="column.status" :type="statusType(row.approvalStatus)" class="status-text">{{
|
@click="emitAction('view', row)"
|
||||||
displayValue(row[column.prop])
|
>{{ row[column.prop] }}</el-link
|
||||||
}}</el-tag>
|
>
|
||||||
|
<el-tag
|
||||||
|
v-else-if="column.status"
|
||||||
|
:type="statusType(row.approvalStatus)"
|
||||||
|
class="status-text"
|
||||||
|
>{{ displayValue(row[column.prop]) }}</el-tag
|
||||||
|
>
|
||||||
<span v-else-if="column.money">{{ formatMoney(row[column.prop], row.currency) }}</span>
|
<span v-else-if="column.money">{{ formatMoney(row[column.prop], row.currency) }}</span>
|
||||||
<span v-else-if="column.prop === 'invoiceStatusName'">{{
|
<span v-else-if="column.prop === 'invoiceStatusName'">{{
|
||||||
invoiceName(row.invoiceStatus)
|
invoiceName(row.invoiceStatus)
|
||||||
@@ -74,10 +80,13 @@
|
|||||||
<span v-else-if="column.prop === 'paymentStatusName'">{{
|
<span v-else-if="column.prop === 'paymentStatusName'">{{
|
||||||
paymentName(row.paymentStatus)
|
paymentName(row.paymentStatus)
|
||||||
}}</span>
|
}}</span>
|
||||||
|
<span v-else-if="column.prop === 'kingdeeSyncStatus'">{{
|
||||||
|
kingdeeSyncName(row.kingdeeSyncStatus)
|
||||||
|
}}</span>
|
||||||
<span v-else>{{ displayValue(row[column.prop]) }}</span>
|
<span v-else>{{ displayValue(row[column.prop]) }}</span>
|
||||||
</template>
|
</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
<el-table-column label="操作" width="120" fixed="right" align="center">
|
<el-table-column label="操作" width="320" fixed="right" align="center">
|
||||||
<template #default="{ row }"
|
<template #default="{ row }"
|
||||||
><div class="fs-table-panel__links">
|
><div class="fs-table-panel__links">
|
||||||
<el-link
|
<el-link
|
||||||
@@ -109,6 +118,7 @@
|
|||||||
import { Refresh } from '@element-plus/icons-vue';
|
import { Refresh } from '@element-plus/icons-vue';
|
||||||
import {
|
import {
|
||||||
invoiceStatusOptions,
|
invoiceStatusOptions,
|
||||||
|
kingdeeSyncStatusOptions,
|
||||||
paymentStatusOptions,
|
paymentStatusOptions,
|
||||||
} from '@/option/settlement/formalSettlementSearch';
|
} from '@/option/settlement/formalSettlementSearch';
|
||||||
|
|
||||||
@@ -129,9 +139,9 @@ export default {
|
|||||||
};
|
};
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
handleSelectionChange(value) {
|
handleCurrentChange(row) {
|
||||||
this.selection = value;
|
this.selection = row ? [row] : [];
|
||||||
this.$emit('update:selection', value);
|
this.$emit('update:selection', this.selection);
|
||||||
},
|
},
|
||||||
emitAction(type, row) {
|
emitAction(type, row) {
|
||||||
this.$emit('action', { type, row });
|
this.$emit('action', { type, row });
|
||||||
@@ -180,6 +190,9 @@ export default {
|
|||||||
paymentName(value) {
|
paymentName(value) {
|
||||||
return paymentStatusOptions.find(item => item.value === value)?.label || value || '-';
|
return paymentStatusOptions.find(item => item.value === value)?.label || value || '-';
|
||||||
},
|
},
|
||||||
|
kingdeeSyncName(value) {
|
||||||
|
return kingdeeSyncStatusOptions.find(item => item.value === value)?.label || value || '-';
|
||||||
|
},
|
||||||
displayValue(value) {
|
displayValue(value) {
|
||||||
return value === null || value === undefined || value === '' ? '-' : value;
|
return value === null || value === undefined || value === '' ? '-' : value;
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -419,7 +419,7 @@
|
|||||||
|
|
||||||
<el-dialog
|
<el-dialog
|
||||||
v-model="candidateDialog.visible"
|
v-model="candidateDialog.visible"
|
||||||
title="添加应收应付明细进预结算单"
|
title="选择结算明细"
|
||||||
width="92%"
|
width="92%"
|
||||||
append-to-body
|
append-to-body
|
||||||
destroy-on-close
|
destroy-on-close
|
||||||
@@ -466,6 +466,9 @@
|
|||||||
>
|
>
|
||||||
<template #default="{ row }">
|
<template #default="{ row }">
|
||||||
<span v-if="column.money">{{ formatMoney(row[column.prop], row.currency) }}</span>
|
<span v-if="column.money">{{ formatMoney(row[column.prop], row.currency) }}</span>
|
||||||
|
<span v-else-if="column.prop === 'transportType'">
|
||||||
|
{{ transportTypeName(row[column.prop]) }}
|
||||||
|
</span>
|
||||||
<span v-else>{{ displayValue(row[column.prop]) }}</span>
|
<span v-else>{{ displayValue(row[column.prop]) }}</span>
|
||||||
</template>
|
</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
@@ -1053,6 +1056,7 @@ export default {
|
|||||||
id: row.id || undefined,
|
id: row.id || undefined,
|
||||||
feeType: row.feeType || '',
|
feeType: row.feeType || '',
|
||||||
feeItem: row.feeItem,
|
feeItem: row.feeItem,
|
||||||
|
originalAmount: Number(row.originalAmount || 0),
|
||||||
adjustAmount: Number(row.adjustAmount || 0),
|
adjustAmount: Number(row.adjustAmount || 0),
|
||||||
remark: row.remark,
|
remark: row.remark,
|
||||||
manualFlag: row.manualFlag || 0,
|
manualFlag: row.manualFlag || 0,
|
||||||
@@ -1086,8 +1090,15 @@ export default {
|
|||||||
},
|
},
|
||||||
transportTypeName(value) {
|
transportTypeName(value) {
|
||||||
if (value === undefined || value === null || value === '') return '';
|
if (value === undefined || value === null || value === '') return '';
|
||||||
|
const normalizedValue = String(value).trim().toLocaleLowerCase();
|
||||||
const option = this.transportTypeOptions.find(
|
const option = this.transportTypeOptions.find(
|
||||||
item => String(item.dictKey) === String(value) || String(item.dictValue) === String(value)
|
item =>
|
||||||
|
String(item.dictKey ?? '')
|
||||||
|
.trim()
|
||||||
|
.toLocaleLowerCase() === normalizedValue ||
|
||||||
|
String(item.dictValue ?? '')
|
||||||
|
.trim()
|
||||||
|
.toLocaleLowerCase() === normalizedValue
|
||||||
);
|
);
|
||||||
return option?.dictValue || value;
|
return option?.dictValue || value;
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -102,14 +102,13 @@
|
|||||||
}}</span></template
|
}}</span></template
|
||||||
></el-table-column
|
></el-table-column
|
||||||
>
|
>
|
||||||
<el-table-column label="调整金额(不含税)" min-width="170" align="center"
|
<el-table-column
|
||||||
|
v-if="!editable"
|
||||||
|
label="调整金额(不含税)"
|
||||||
|
min-width="170"
|
||||||
|
align="center"
|
||||||
><template #default="{ row }"
|
><template #default="{ row }"
|
||||||
><el-input-number
|
><span>{{ formatMoney(row.adjustmentAmountNoTax) }}</span></template
|
||||||
v-if="editable"
|
|
||||||
v-model="row.adjustmentAmountNoTax"
|
|
||||||
:precision="2"
|
|
||||||
:controls="false"
|
|
||||||
/><span v-else>{{ formatMoney(row.adjustmentAmountNoTax) }}</span></template
|
|
||||||
></el-table-column
|
></el-table-column
|
||||||
>
|
>
|
||||||
<el-table-column label="备注" min-width="180" align="center"
|
<el-table-column label="备注" min-width="180" align="center"
|
||||||
@@ -140,6 +139,70 @@
|
|||||||
>;调整后结算金额:<strong>{{ formatMoney(form.adjustedSettlementAmount) }}</strong>
|
>;调整后结算金额:<strong>{{ formatMoney(form.adjustedSettlementAmount) }}</strong>
|
||||||
</div>
|
</div>
|
||||||
</section-card>
|
</section-card>
|
||||||
|
|
||||||
|
<section-card title="附件材料">
|
||||||
|
<div class="settlement-adjustment-editor__attachment-actions">
|
||||||
|
<el-button
|
||||||
|
type="primary"
|
||||||
|
:disabled="!attachments.length"
|
||||||
|
@click="batchDownloadAttachments"
|
||||||
|
>
|
||||||
|
批量下载
|
||||||
|
</el-button>
|
||||||
|
</div>
|
||||||
|
<el-table :data="attachments" border @selection-change="selectedAttachments = $event">
|
||||||
|
<el-table-column type="selection" width="55" align="center" />
|
||||||
|
<el-table-column type="index" label="序号" width="64" align="center" />
|
||||||
|
<el-table-column label="文件名" min-width="220" show-overflow-tooltip>
|
||||||
|
<template #default="{ row }">
|
||||||
|
<el-link type="primary" @click="previewAttachment(row)">
|
||||||
|
{{ attachmentName(row) }}
|
||||||
|
</el-link>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column label="附件描述" min-width="240">
|
||||||
|
<template #default="{ row }">
|
||||||
|
<el-input
|
||||||
|
v-if="editable"
|
||||||
|
v-model="row.description"
|
||||||
|
maxlength="200"
|
||||||
|
placeholder="请输入附件描述"
|
||||||
|
/>
|
||||||
|
<span v-else>{{ displayValue(row.description) }}</span>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column label="文件大小" width="120" align="center">
|
||||||
|
<template #default="{ row }">{{ formatFileSize(row) }}</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column prop="uploadUserName" label="上传人" width="140" align="center" />
|
||||||
|
<el-table-column prop="uploadTime" label="上传时间" width="170" align="center" />
|
||||||
|
<el-table-column label="操作" :width="editable ? 130 : 70" fixed="right" align="center">
|
||||||
|
<template #default="{ row, $index }">
|
||||||
|
<div class="settlement-adjustment-editor__links">
|
||||||
|
<el-link type="primary" @click="downloadAttachment(row)">下载</el-link>
|
||||||
|
<el-link v-if="editable" type="danger" @click="removeAttachment($index)">
|
||||||
|
删除
|
||||||
|
</el-link>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
<template #empty><el-empty description="暂无附件材料" /></template>
|
||||||
|
</el-table>
|
||||||
|
<div v-show="editable" class="settlement-adjustment-editor__attachment-upload">
|
||||||
|
<vehicle-attachment-upload
|
||||||
|
ref="attachmentUploadRef"
|
||||||
|
v-model="attachments"
|
||||||
|
:readonly="!editable"
|
||||||
|
:multiple="true"
|
||||||
|
:limit="20"
|
||||||
|
:max-size="500"
|
||||||
|
:file-types="attachmentFileTypes"
|
||||||
|
:show-file-list="false"
|
||||||
|
button-text="上传附件"
|
||||||
|
@change="handleAttachmentChange"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</section-card>
|
||||||
</div>
|
</div>
|
||||||
<template #footer
|
<template #footer
|
||||||
><el-button @click="visible = false">取消</el-button
|
><el-button @click="visible = false">取消</el-button
|
||||||
@@ -199,6 +262,7 @@ import {
|
|||||||
createSettlementAdjustmentForm,
|
createSettlementAdjustmentForm,
|
||||||
settlementAdjustmentFormFields,
|
settlementAdjustmentFormFields,
|
||||||
} from '@/option/settlement/settlementAdjustmentForm';
|
} from '@/option/settlement/settlementAdjustmentForm';
|
||||||
|
import { downloadFileByUrl } from '@/utils/util';
|
||||||
export default {
|
export default {
|
||||||
name: 'SettlementAdjustmentEditor',
|
name: 'SettlementAdjustmentEditor',
|
||||||
props: { modelValue: Boolean, recordId: [String, Number], readonly: Boolean },
|
props: { modelValue: Boolean, recordId: [String, Number], readonly: Boolean },
|
||||||
@@ -209,6 +273,24 @@ export default {
|
|||||||
form: createSettlementAdjustmentForm(),
|
form: createSettlementAdjustmentForm(),
|
||||||
fields: settlementAdjustmentFormFields,
|
fields: settlementAdjustmentFormFields,
|
||||||
details: [],
|
details: [],
|
||||||
|
attachments: [],
|
||||||
|
selectedAttachments: [],
|
||||||
|
attachmentFileTypes: [
|
||||||
|
'pdf',
|
||||||
|
'bmp',
|
||||||
|
'jpeg',
|
||||||
|
'png',
|
||||||
|
'jpg',
|
||||||
|
'doc',
|
||||||
|
'docx',
|
||||||
|
'ppt',
|
||||||
|
'pptx',
|
||||||
|
'xlsx',
|
||||||
|
'xls',
|
||||||
|
'eml',
|
||||||
|
'msg',
|
||||||
|
'zip',
|
||||||
|
],
|
||||||
candidates: [],
|
candidates: [],
|
||||||
feeRows: [],
|
feeRows: [],
|
||||||
rules: {
|
rules: {
|
||||||
@@ -248,6 +330,8 @@ export default {
|
|||||||
async initialize() {
|
async initialize() {
|
||||||
this.form = createSettlementAdjustmentForm();
|
this.form = createSettlementAdjustmentForm();
|
||||||
this.details = [];
|
this.details = [];
|
||||||
|
this.attachments = [];
|
||||||
|
this.selectedAttachments = [];
|
||||||
this.candidates = [];
|
this.candidates = [];
|
||||||
this.feeRows = [];
|
this.feeRows = [];
|
||||||
if (!this.recordId) {
|
if (!this.recordId) {
|
||||||
@@ -264,6 +348,7 @@ export default {
|
|||||||
adjustmentAmountNoTax:
|
adjustmentAmountNoTax:
|
||||||
item.adjustmentAmountNoTax == null ? null : Number(item.adjustmentAmountNoTax),
|
item.adjustmentAmountNoTax == null ? null : Number(item.adjustmentAmountNoTax),
|
||||||
}));
|
}));
|
||||||
|
this.attachments = this.parseAttachments(data.attachmentsJson);
|
||||||
await this.loadFormalSettlements(this.form.formalSettlementNo || '');
|
await this.loadFormalSettlements(this.form.formalSettlementNo || '');
|
||||||
} finally {
|
} finally {
|
||||||
this.loading = false;
|
this.loading = false;
|
||||||
@@ -274,16 +359,48 @@ export default {
|
|||||||
this.candidates = data?.data || data || [];
|
this.candidates = data?.data || data || [];
|
||||||
},
|
},
|
||||||
async handleFormalChange(id) {
|
async handleFormalChange(id) {
|
||||||
|
if (!id) {
|
||||||
|
Object.assign(this.form, {
|
||||||
|
formalSettlementId: null,
|
||||||
|
formalSettlementNo: '',
|
||||||
|
settlementType: '',
|
||||||
|
projectName: '',
|
||||||
|
deptName: '',
|
||||||
|
customerName: '',
|
||||||
|
contractNo: '',
|
||||||
|
contractName: '',
|
||||||
|
originalSettlementAmount: 0,
|
||||||
|
});
|
||||||
|
this.details = [];
|
||||||
|
this.recalculate();
|
||||||
|
return;
|
||||||
|
}
|
||||||
const item = this.candidates.find(row => String(row.id) === String(id));
|
const item = this.candidates.find(row => String(row.id) === String(id));
|
||||||
if (!item) return;
|
if (!item) return;
|
||||||
Object.assign(this.form, item, {
|
const { id: formalSettlementId, ...formalSettlement } = item;
|
||||||
formalSettlementId: id,
|
Object.assign(this.form, formalSettlement, {
|
||||||
|
formalSettlementId,
|
||||||
formalSettlementNo: item.formalSettlementNo,
|
formalSettlementNo: item.formalSettlementNo,
|
||||||
originalSettlementAmount: Number(item.settlementAmount || 0),
|
originalSettlementAmount: Number(item.settlementAmount || 0),
|
||||||
settlementTypeName: item.settlementTypeName,
|
settlementTypeName: item.settlementTypeName,
|
||||||
});
|
});
|
||||||
this.details = [];
|
this.details = [];
|
||||||
this.recalculate();
|
this.recalculate();
|
||||||
|
this.loading = true;
|
||||||
|
try {
|
||||||
|
const { data } = await api.getFormalDetails(formalSettlementId);
|
||||||
|
if (String(this.form.formalSettlementId) !== String(formalSettlementId)) return;
|
||||||
|
this.details = (data?.data || data || []).map(fee => ({
|
||||||
|
...fee,
|
||||||
|
originalAmountTax: Number(fee.originalAmountTax || 0),
|
||||||
|
adjustmentAmountTax: 0,
|
||||||
|
adjustmentAmountNoTax: null,
|
||||||
|
remark: '',
|
||||||
|
}));
|
||||||
|
this.recalculate();
|
||||||
|
} finally {
|
||||||
|
this.loading = false;
|
||||||
|
}
|
||||||
},
|
},
|
||||||
async openFeeDialog() {
|
async openFeeDialog() {
|
||||||
const { data } = await api.getFormalDetails(this.form.formalSettlementId);
|
const { data } = await api.getFormalDetails(this.form.formalSettlementId);
|
||||||
@@ -321,8 +438,9 @@ export default {
|
|||||||
this.saving = true;
|
this.saving = true;
|
||||||
try {
|
try {
|
||||||
await api.save({
|
await api.save({
|
||||||
id: this.form.id,
|
...(this.recordId ? { id: this.form.id } : {}),
|
||||||
formalSettlementId: this.form.formalSettlementId,
|
formalSettlementId: this.form.formalSettlementId,
|
||||||
|
attachmentsJson: JSON.stringify(this.attachments || []),
|
||||||
remark: this.form.remark,
|
remark: this.form.remark,
|
||||||
details: this.details.map(item => ({
|
details: this.details.map(item => ({
|
||||||
formalSettlementDetailId: item.formalSettlementDetailId,
|
formalSettlementDetailId: item.formalSettlementDetailId,
|
||||||
@@ -347,15 +465,97 @@ export default {
|
|||||||
formatMoney(value) {
|
formatMoney(value) {
|
||||||
return Number(value || 0).toFixed(2);
|
return Number(value || 0).toFixed(2);
|
||||||
},
|
},
|
||||||
|
handleAttachmentChange(files) {
|
||||||
|
const userInfo = this.$store.getters.userInfo || {};
|
||||||
|
const uploadUserName = userInfo.realName || userInfo.userName || '';
|
||||||
|
const uploadTime = this.$dayjs().format('YYYY-MM-DD HH:mm:ss');
|
||||||
|
this.attachments = (files || []).map(file => ({
|
||||||
|
...file,
|
||||||
|
description: file.description || '',
|
||||||
|
uploadUserName: file.uploadUserName || uploadUserName,
|
||||||
|
uploadTime: file.uploadTime || uploadTime,
|
||||||
|
}));
|
||||||
|
this.selectedAttachments = [];
|
||||||
|
},
|
||||||
|
parseAttachments(value) {
|
||||||
|
if (!value) return [];
|
||||||
|
if (Array.isArray(value)) return value;
|
||||||
|
try {
|
||||||
|
const parsed = JSON.parse(value);
|
||||||
|
return Array.isArray(parsed) ? parsed : [];
|
||||||
|
} catch (error) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
},
|
||||||
|
attachmentName(file = {}) {
|
||||||
|
return file.originalName || file.name || file.fileName || '附件';
|
||||||
|
},
|
||||||
|
attachmentUrl(file = {}) {
|
||||||
|
return file.url || file.link || file.fileUrl || file.domain || '';
|
||||||
|
},
|
||||||
|
formatFileSize(file = {}) {
|
||||||
|
const bytes = Number(file.size || file.fileSize || file.attachSize || 0);
|
||||||
|
if (!bytes) return '-';
|
||||||
|
if (bytes < 1024) return `${bytes}B`;
|
||||||
|
if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)}KB`;
|
||||||
|
return `${(bytes / 1024 / 1024).toFixed(1)}MB`;
|
||||||
|
},
|
||||||
|
previewAttachment(file) {
|
||||||
|
this.$refs.attachmentUploadRef?.handlePreview(file);
|
||||||
|
},
|
||||||
|
downloadAttachment(file) {
|
||||||
|
const url = this.attachmentUrl(file);
|
||||||
|
if (!url) {
|
||||||
|
this.$message.warning('附件地址为空');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
downloadFileByUrl(url, this.attachmentName(file));
|
||||||
|
},
|
||||||
|
batchDownloadAttachments() {
|
||||||
|
const files = this.selectedAttachments.length ? this.selectedAttachments : this.attachments;
|
||||||
|
const downloadableFiles = files.filter(file => this.attachmentUrl(file));
|
||||||
|
if (!downloadableFiles.length) {
|
||||||
|
this.$message.warning(
|
||||||
|
this.selectedAttachments.length ? '所选附件暂无可下载地址' : '暂无可下载附件'
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
downloadableFiles.forEach((file, index) => {
|
||||||
|
window.setTimeout(() => this.downloadAttachment(file), index * 200);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
removeAttachment(index) {
|
||||||
|
const [removed] = this.attachments.splice(index, 1);
|
||||||
|
this.selectedAttachments = this.selectedAttachments.filter(file => file !== removed);
|
||||||
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped lang="scss">
|
<style scoped lang="scss">
|
||||||
|
.settlement-adjustment-editor {
|
||||||
|
max-height: 72vh;
|
||||||
|
overflow-y: auto;
|
||||||
|
padding-right: 4px;
|
||||||
|
}
|
||||||
.settlement-adjustment-editor__summary {
|
.settlement-adjustment-editor__summary {
|
||||||
padding: 12px 0;
|
padding: 12px 0;
|
||||||
text-align: right;
|
text-align: right;
|
||||||
}
|
}
|
||||||
|
.settlement-adjustment-editor__attachment-actions {
|
||||||
|
display: flex;
|
||||||
|
justify-content: flex-end;
|
||||||
|
margin-bottom: 12px;
|
||||||
|
}
|
||||||
|
.settlement-adjustment-editor__attachment-upload {
|
||||||
|
padding-top: 12px;
|
||||||
|
}
|
||||||
|
.settlement-adjustment-editor__links {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
justify-content: center;
|
||||||
|
gap: 8px;
|
||||||
|
}
|
||||||
.settlement-adjustment-editor :deep(.el-form-item) {
|
.settlement-adjustment-editor :deep(.el-form-item) {
|
||||||
margin-bottom: 16px;
|
margin-bottom: 16px;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -200,8 +200,14 @@ export default {
|
|||||||
);
|
);
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
watch: {
|
||||||
|
'$route.query.detailNo'(detailNo) {
|
||||||
|
if (detailNo) this.openRouteDetail(detailNo);
|
||||||
|
},
|
||||||
|
},
|
||||||
mounted() {
|
mounted() {
|
||||||
this.loadTable();
|
this.loadTable();
|
||||||
|
this.openRouteDetail();
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
hasPermission(code) {
|
hasPermission(code) {
|
||||||
@@ -267,6 +273,19 @@ export default {
|
|||||||
openView(row) {
|
openView(row) {
|
||||||
this.editor = { visible: true, id: row.id, readonly: true };
|
this.editor = { visible: true, id: row.id, readonly: true };
|
||||||
},
|
},
|
||||||
|
async openRouteDetail(detailNo = this.$route.query.detailNo) {
|
||||||
|
const formalSettlementNo = String(detailNo || '').trim();
|
||||||
|
if (!formalSettlementNo) return;
|
||||||
|
const response = await api.getList(1, 10, { formalSettlementNo });
|
||||||
|
const data = this.unwrapData(response);
|
||||||
|
const row = (data.records || []).find(item => item.formalSettlementNo === formalSettlementNo);
|
||||||
|
if (!row) {
|
||||||
|
this.$message.warning('未找到对应的正式结算单');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.activeSettlementType = row.settlementType || this.activeSettlementType;
|
||||||
|
this.openView(row);
|
||||||
|
},
|
||||||
async handleDelete(row) {
|
async handleDelete(row) {
|
||||||
await this.$confirm('确认删除该正式结算草稿?', '提示', { type: 'warning' });
|
await this.$confirm('确认删除该正式结算草稿?', '提示', { type: 'warning' });
|
||||||
await api.remove(row.id);
|
await api.remove(row.id);
|
||||||
|
|||||||
@@ -53,8 +53,8 @@
|
|||||||
v-if="hasPermission('pre_settlement_advance')"
|
v-if="hasPermission('pre_settlement_advance')"
|
||||||
type="primary"
|
type="primary"
|
||||||
plain
|
plain
|
||||||
disabled
|
:disabled="!selection.length"
|
||||||
@click="openAdvanceDialog"
|
@click="handleAdvanceApplication"
|
||||||
>
|
>
|
||||||
预付申请
|
预付申请
|
||||||
</el-button>
|
</el-button>
|
||||||
@@ -118,7 +118,11 @@
|
|||||||
>
|
>
|
||||||
{{ row[column.prop] }}
|
{{ row[column.prop] }}
|
||||||
</el-link>
|
</el-link>
|
||||||
<el-tag v-else-if="column.status" :type="statusTagType(row.approvalStatus)" class="status-text">
|
<el-tag
|
||||||
|
v-else-if="column.status"
|
||||||
|
:type="statusTagType(row.approvalStatus)"
|
||||||
|
class="status-text"
|
||||||
|
>
|
||||||
{{ row[column.prop] || '-' }}
|
{{ row[column.prop] || '-' }}
|
||||||
</el-tag>
|
</el-tag>
|
||||||
<span v-else-if="column.money">
|
<span v-else-if="column.money">
|
||||||
@@ -211,52 +215,6 @@
|
|||||||
@success="loadTable"
|
@success="loadTable"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<el-dialog v-model="advanceDialog.visible" title="预付申请" width="520px" append-to-body>
|
|
||||||
<el-form
|
|
||||||
ref="advanceFormRef"
|
|
||||||
:model="advanceForm"
|
|
||||||
:rules="advanceRules"
|
|
||||||
label-position="right"
|
|
||||||
label-width="auto"
|
|
||||||
>
|
|
||||||
<el-form-item label="预结算单号">
|
|
||||||
<span>{{ advanceDialog.row.preSettlementNo || '-' }}</span>
|
|
||||||
</el-form-item>
|
|
||||||
<el-form-item label="结算金额">
|
|
||||||
<span>
|
|
||||||
{{ formatMoney(advanceDialog.row.settlementAmount, advanceDialog.row.currency) }}
|
|
||||||
</span>
|
|
||||||
</el-form-item>
|
|
||||||
<el-form-item label="已申请预付金额">
|
|
||||||
<span>
|
|
||||||
{{ formatMoney(advanceDialog.row.advanceAppliedAmount, advanceDialog.row.currency) }}
|
|
||||||
</span>
|
|
||||||
</el-form-item>
|
|
||||||
<el-form-item label="申请预付金额" prop="appliedAmount">
|
|
||||||
<el-input-number
|
|
||||||
v-model="advanceForm.appliedAmount"
|
|
||||||
:min="0"
|
|
||||||
:max="advanceAvailableAmount"
|
|
||||||
:precision="2"
|
|
||||||
:controls="false"
|
|
||||||
/>
|
|
||||||
</el-form-item>
|
|
||||||
<el-form-item label="金蝶预付单号">
|
|
||||||
<el-input
|
|
||||||
v-model="advanceForm.kingdeeAdvanceNo"
|
|
||||||
maxlength="100"
|
|
||||||
placeholder="外部系统回写时可填写"
|
|
||||||
/>
|
|
||||||
</el-form-item>
|
|
||||||
</el-form>
|
|
||||||
<template #footer>
|
|
||||||
<el-button @click="advanceDialog.visible = false">取消</el-button>
|
|
||||||
<el-button type="primary" :loading="advanceDialog.submitting" @click="submitAdvance">
|
|
||||||
提交
|
|
||||||
</el-button>
|
|
||||||
</template>
|
|
||||||
</el-dialog>
|
|
||||||
|
|
||||||
<el-dialog
|
<el-dialog
|
||||||
v-model="printDialog.visible"
|
v-model="printDialog.visible"
|
||||||
title="打印"
|
title="打印"
|
||||||
@@ -312,7 +270,6 @@
|
|||||||
import { ArrowDown, ArrowUp, Refresh } from '@element-plus/icons-vue';
|
import { ArrowDown, ArrowUp, Refresh } from '@element-plus/icons-vue';
|
||||||
import { mapGetters } from 'vuex';
|
import { mapGetters } from 'vuex';
|
||||||
import {
|
import {
|
||||||
applyAdvance,
|
|
||||||
approve,
|
approve,
|
||||||
exportList,
|
exportList,
|
||||||
getDetail,
|
getDetail,
|
||||||
@@ -366,34 +323,6 @@ export default {
|
|||||||
id: '',
|
id: '',
|
||||||
readonly: false,
|
readonly: false,
|
||||||
},
|
},
|
||||||
advanceDialog: {
|
|
||||||
visible: false,
|
|
||||||
submitting: false,
|
|
||||||
row: {},
|
|
||||||
},
|
|
||||||
advanceForm: {
|
|
||||||
appliedAmount: null,
|
|
||||||
kingdeeAdvanceNo: '',
|
|
||||||
},
|
|
||||||
advanceRules: {
|
|
||||||
appliedAmount: [
|
|
||||||
{ required: true, message: '请输入申请预付金额', trigger: 'blur' },
|
|
||||||
{
|
|
||||||
validator: (_rule, value, callback) => {
|
|
||||||
if (!value || Number(value) <= 0) {
|
|
||||||
callback(new Error('申请预付金额必须大于0'));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (Number(value) > this.advanceAvailableAmount) {
|
|
||||||
callback(new Error('申请预付金额不能超过剩余可申请金额'));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
callback();
|
|
||||||
},
|
|
||||||
trigger: ['blur', 'change'],
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
printDialog: {
|
printDialog: {
|
||||||
visible: false,
|
visible: false,
|
||||||
loading: false,
|
loading: false,
|
||||||
@@ -418,16 +347,15 @@ export default {
|
|||||||
visibleSearchFields() {
|
visibleSearchFields() {
|
||||||
return this.searchExpanded ? this.searchFields : this.searchFields.slice(0, 4);
|
return this.searchExpanded ? this.searchFields : this.searchFields.slice(0, 4);
|
||||||
},
|
},
|
||||||
advanceAvailableAmount() {
|
},
|
||||||
return Math.max(
|
watch: {
|
||||||
Number(this.advanceDialog.row.settlementAmount || 0) -
|
'$route.query.detailNo'(detailNo) {
|
||||||
Number(this.advanceDialog.row.advanceAppliedAmount || 0),
|
if (detailNo) this.openRouteDetail(detailNo);
|
||||||
0
|
|
||||||
);
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
created() {
|
created() {
|
||||||
this.loadTable();
|
this.loadTable();
|
||||||
|
this.openRouteDetail();
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
hasPermission(code) {
|
hasPermission(code) {
|
||||||
@@ -487,6 +415,18 @@ export default {
|
|||||||
openView(row) {
|
openView(row) {
|
||||||
this.editor = { visible: true, id: row.id, readonly: true };
|
this.editor = { visible: true, id: row.id, readonly: true };
|
||||||
},
|
},
|
||||||
|
async openRouteDetail(detailNo = this.$route.query.detailNo) {
|
||||||
|
const preSettlementNo = String(detailNo || '').trim();
|
||||||
|
if (!preSettlementNo) return;
|
||||||
|
const { data } = await getList(1, 10, { preSettlementNo });
|
||||||
|
const records = data?.data?.records || [];
|
||||||
|
const row = records.find(item => item.preSettlementNo === preSettlementNo);
|
||||||
|
if (!row) {
|
||||||
|
this.$message.warning('未找到对应的预结算单');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.openView(row);
|
||||||
|
},
|
||||||
isEditable(row) {
|
isEditable(row) {
|
||||||
return ['draft', 'returned'].includes(row.approvalStatus);
|
return ['draft', 'returned'].includes(row.approvalStatus);
|
||||||
},
|
},
|
||||||
@@ -503,37 +443,49 @@ export default {
|
|||||||
}
|
}
|
||||||
return this.selection[0];
|
return this.selection[0];
|
||||||
},
|
},
|
||||||
openAdvanceDialog() {
|
handleAdvanceApplication() {
|
||||||
const row = this.selectedOne('预付申请');
|
if (!this.selection.length) {
|
||||||
if (!row) return;
|
this.$message.warning('请选择需要发起预付申请的预结算单');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (this.selection.some(row => !row.contractId)) {
|
||||||
|
this.$message.warning('所选预结算单缺少合同ID,无法发起预付申请');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const contractIds = new Set(this.selection.map(row => String(row.contractId)));
|
||||||
|
if (contractIds.size > 1) {
|
||||||
|
this.$message.warning('预付申请只能选择同一合同下的预结算单');
|
||||||
|
return;
|
||||||
|
}
|
||||||
if (
|
if (
|
||||||
row.approvalStatus !== 'approved' ||
|
this.selection.some(
|
||||||
row.settlementType !== 'payable' ||
|
row =>
|
||||||
row.formalSettlementNo
|
row.approvalStatus !== 'approved' ||
|
||||||
|
row.settlementType !== 'payable' ||
|
||||||
|
row.formalSettlementNo
|
||||||
|
)
|
||||||
) {
|
) {
|
||||||
this.$message.warning('仅审批通过、未转正式结算的应付预结算单可发起预付');
|
this.$message.warning('仅审批通过、未转正式结算的应付预结算单可发起预付');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
this.advanceDialog.row = row;
|
const sourcePreSettlements = this.selection.map(row => ({
|
||||||
this.advanceDialog.visible = true;
|
...row,
|
||||||
this.advanceForm = { appliedAmount: null, kingdeeAdvanceNo: '' };
|
preSettlementId: row.preSettlementId || row.id,
|
||||||
this.$nextTick(() => this.$refs.advanceFormRef?.clearValidate());
|
}));
|
||||||
},
|
const transferToken = createSettlementTransfer({
|
||||||
async submitAdvance() {
|
sourcePreSettlements,
|
||||||
await this.$refs.advanceFormRef?.validate();
|
});
|
||||||
this.advanceDialog.submitting = true;
|
this.$router.push({
|
||||||
try {
|
path: '/payment/payment-application/form',
|
||||||
await applyAdvance({
|
query: {
|
||||||
preSettlementId: this.advanceDialog.row.id,
|
mode: 'add',
|
||||||
appliedAmount: this.advanceForm.appliedAmount,
|
transferToken,
|
||||||
kingdeeAdvanceNo: this.advanceForm.kingdeeAdvanceNo,
|
preSettlementIds: sourcePreSettlements
|
||||||
});
|
.map(row => row.preSettlementId)
|
||||||
this.$message.success('预付申请提交成功');
|
.filter(Boolean)
|
||||||
this.advanceDialog.visible = false;
|
.join(','),
|
||||||
this.loadTable();
|
},
|
||||||
} finally {
|
});
|
||||||
this.advanceDialog.submitting = false;
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
handleFormalSettlement() {
|
handleFormalSettlement() {
|
||||||
if (!this.selection.length) {
|
if (!this.selection.length) {
|
||||||
|
|||||||
@@ -190,8 +190,16 @@
|
|||||||
<div
|
<div
|
||||||
class="settlement-detail-page__detail-panel-body settlement-detail-page__adjust-panel-body"
|
class="settlement-detail-page__detail-panel-body settlement-detail-page__adjust-panel-body"
|
||||||
>
|
>
|
||||||
|
<div v-if="isReceivable" class="settlement-detail-page__adjust-toolbar">
|
||||||
|
<el-button type="primary" :disabled="adjustDialog.loading" @click="addAdjustFee">
|
||||||
|
新增费用
|
||||||
|
</el-button>
|
||||||
|
</div>
|
||||||
<el-table v-loading="adjustDialog.loading" :data="adjustRows" border>
|
<el-table v-loading="adjustDialog.loading" :data="adjustRows" border>
|
||||||
<el-table-column type="index" label="序号" width="64" align="center" />
|
<el-table-column type="index" label="序号" width="64" align="center" />
|
||||||
|
<el-table-column label="来源" width="110" align="center">
|
||||||
|
<template #default="{ row }">{{ feeSourceLabel(row.dataSource) }}</template>
|
||||||
|
</el-table-column>
|
||||||
<el-table-column
|
<el-table-column
|
||||||
v-for="column in adjustFeeColumns"
|
v-for="column in adjustFeeColumns"
|
||||||
:key="column.prop || column.feeItemName"
|
:key="column.prop || column.feeItemName"
|
||||||
@@ -201,29 +209,92 @@
|
|||||||
show-overflow-tooltip
|
show-overflow-tooltip
|
||||||
>
|
>
|
||||||
<template #default="{ row }">
|
<template #default="{ row }">
|
||||||
|
<span
|
||||||
|
v-if="row.manualFee && ['billingFactor', 'billingType'].includes(column.prop)"
|
||||||
|
>
|
||||||
|
-
|
||||||
|
</span>
|
||||||
<el-input
|
<el-input
|
||||||
v-if="column.prop === 'transportQuantityText' && !row.manualFee"
|
v-else-if="column.prop === 'cargoName' && row.manualFee"
|
||||||
|
v-model="row.cargoName"
|
||||||
|
clearable
|
||||||
|
maxlength="100"
|
||||||
|
placeholder="请输入"
|
||||||
|
/>
|
||||||
|
<span v-else-if="column.prop === 'cargoName'">
|
||||||
|
{{ formatDetailCell(row, column.prop) }}
|
||||||
|
</span>
|
||||||
|
<el-cascader
|
||||||
|
v-else-if="column.prop === 'cargoType' && row.manualFee"
|
||||||
|
v-model="row.cargoTypePath"
|
||||||
|
class="settlement-detail-page__adjust-control"
|
||||||
|
:options="transportCargoTypeOptions"
|
||||||
|
:props="cargoTypeCascaderProps"
|
||||||
|
:loading="cargoTypeLoading"
|
||||||
|
clearable
|
||||||
|
filterable
|
||||||
|
:filter-method="filterCargoType"
|
||||||
|
placeholder="请选择到二级"
|
||||||
|
@visible-change="visible => visible && loadCargoTypeOptions()"
|
||||||
|
@change="value => handleAdjustCargoTypeChange(row, value)"
|
||||||
|
/>
|
||||||
|
<span v-else-if="column.prop === 'cargoType'">
|
||||||
|
{{ formatDetailCell(row, column.prop) }}
|
||||||
|
</span>
|
||||||
|
<el-select
|
||||||
|
v-else-if="column.prop === 'priceUnit'"
|
||||||
|
v-model="row.priceUnit"
|
||||||
|
class="settlement-detail-page__adjust-control"
|
||||||
|
:loading="priceUnitLoading"
|
||||||
|
clearable
|
||||||
|
filterable
|
||||||
|
placeholder="请选择"
|
||||||
|
@visible-change="visible => visible && loadPriceUnitOptions()"
|
||||||
|
>
|
||||||
|
<el-option
|
||||||
|
v-for="item in priceUnitOptions"
|
||||||
|
:key="item.id || item.dictKey || item.dictValue"
|
||||||
|
:label="item.dictValue"
|
||||||
|
:value="item.dictValue"
|
||||||
|
/>
|
||||||
|
</el-select>
|
||||||
|
<el-input
|
||||||
|
v-else-if="adjustTextProps.includes(column.prop)"
|
||||||
|
v-model="row[column.prop]"
|
||||||
|
clearable
|
||||||
|
:maxlength="adjustTextMaxlength(column.prop)"
|
||||||
|
placeholder="请输入"
|
||||||
|
/>
|
||||||
|
<el-input
|
||||||
|
v-else-if="column.prop === 'transportQuantityText'"
|
||||||
class="settlement-detail-page__adjust-input"
|
class="settlement-detail-page__adjust-input"
|
||||||
:model-value="row.transportQuantity"
|
:model-value="row.transportQuantity"
|
||||||
inputmode="decimal"
|
inputmode="decimal"
|
||||||
@input="value => handleAdjustDecimalInput(row, 'transportQuantity', value)"
|
@input="value => handleAdjustDecimalInput(row, 'transportQuantity', value)"
|
||||||
/>
|
/>
|
||||||
<el-input
|
<el-input
|
||||||
v-else-if="column.prop === 'mileage' && !row.manualFee"
|
v-else-if="column.prop === 'unitPrice'"
|
||||||
|
class="settlement-detail-page__adjust-input"
|
||||||
|
:model-value="row.unitPrice"
|
||||||
|
inputmode="decimal"
|
||||||
|
@input="value => handleAdjustDecimalInput(row, 'unitPrice', value)"
|
||||||
|
/>
|
||||||
|
<el-input
|
||||||
|
v-else-if="column.prop === 'mileage'"
|
||||||
class="settlement-detail-page__adjust-input"
|
class="settlement-detail-page__adjust-input"
|
||||||
:model-value="row.mileage"
|
:model-value="row.mileage"
|
||||||
inputmode="decimal"
|
inputmode="decimal"
|
||||||
@input="value => handleAdjustDecimalInput(row, 'mileage', value)"
|
@input="value => handleAdjustDecimalInput(row, 'mileage', value)"
|
||||||
/>
|
/>
|
||||||
<el-input
|
<el-input
|
||||||
v-else-if="column.prop === 'freightAmount' && !row.manualFee"
|
v-else-if="column.prop === 'freightAmount'"
|
||||||
class="settlement-detail-page__adjust-input"
|
class="settlement-detail-page__adjust-input"
|
||||||
:model-value="row.freightAmount"
|
:model-value="row.freightAmount"
|
||||||
inputmode="decimal"
|
inputmode="decimal"
|
||||||
@input="value => handleAdjustDecimalInput(row, 'freightAmount', value, 'freight')"
|
@input="value => handleAdjustDecimalInput(row, 'freightAmount', value, 'freight')"
|
||||||
/>
|
/>
|
||||||
<el-input
|
<el-input
|
||||||
v-else-if="column.dynamic && !row.manualFee"
|
v-else-if="column.dynamic"
|
||||||
class="settlement-detail-page__adjust-input"
|
class="settlement-detail-page__adjust-input"
|
||||||
:model-value="row.feeItems[column.feeItemName]"
|
:model-value="row.feeItems[column.feeItemName]"
|
||||||
inputmode="decimal"
|
inputmode="decimal"
|
||||||
@@ -551,6 +622,7 @@ import {
|
|||||||
import * as api from '@/api/settlement/receivable-payable-detail';
|
import * as api from '@/api/settlement/receivable-payable-detail';
|
||||||
import { getList as getContractList } from '@/api/business/contract-manage';
|
import { getList as getContractList } from '@/api/business/contract-manage';
|
||||||
import { getContractOptions as getSettlementContractOptions } from '@/api/settlement/preSettlement';
|
import { getContractOptions as getSettlementContractOptions } from '@/api/settlement/preSettlement';
|
||||||
|
import { getList as getCargoTypeList } from '@/api/base/cargo-type';
|
||||||
import { exportBlob } from '@/api/common';
|
import { exportBlob } from '@/api/common';
|
||||||
import { getDictionary } from '@/api/system/dictbiz';
|
import { getDictionary } from '@/api/system/dictbiz';
|
||||||
import { downloadXls } from '@/utils/util';
|
import { downloadXls } from '@/utils/util';
|
||||||
@@ -594,7 +666,20 @@ export default {
|
|||||||
adjustDialog: { visible: false, loading: false, submitting: false, row: null },
|
adjustDialog: { visible: false, loading: false, submitting: false, row: null },
|
||||||
adjustRows: [],
|
adjustRows: [],
|
||||||
adjustDynamicFeeColumns: [],
|
adjustDynamicFeeColumns: [],
|
||||||
adjustCalculateTimers: {},
|
adjustTextProps: ['specification', 'model', 'billingFactor', 'billingType'],
|
||||||
|
cargoTypeOptions: [],
|
||||||
|
cargoTypeFlatOptions: [],
|
||||||
|
cargoTypeLoading: false,
|
||||||
|
cargoTypeRequest: null,
|
||||||
|
cargoTypeCascaderProps: {
|
||||||
|
label: 'cargoName',
|
||||||
|
value: 'id',
|
||||||
|
children: 'children',
|
||||||
|
emitPath: true,
|
||||||
|
},
|
||||||
|
priceUnitOptions: [],
|
||||||
|
priceUnitLoading: false,
|
||||||
|
priceUnitRequest: null,
|
||||||
changeRows: [],
|
changeRows: [],
|
||||||
changePage: { current: 1, size: 10, total: 0 },
|
changePage: { current: 1, size: 10, total: 0 },
|
||||||
changeDialog: { loading: false },
|
changeDialog: { loading: false },
|
||||||
@@ -630,6 +715,9 @@ export default {
|
|||||||
if (this.settlementType === 'payable') return '应付';
|
if (this.settlementType === 'payable') return '应付';
|
||||||
return '应收应付';
|
return '应收应付';
|
||||||
},
|
},
|
||||||
|
isReceivable() {
|
||||||
|
return this.settlementType === 'receivable';
|
||||||
|
},
|
||||||
visibleSearchFields() {
|
visibleSearchFields() {
|
||||||
return this.searchExpanded ? this.searchFields : this.searchFields.slice(0, 4);
|
return this.searchExpanded ? this.searchFields : this.searchFields.slice(0, 4);
|
||||||
},
|
},
|
||||||
@@ -662,9 +750,14 @@ export default {
|
|||||||
{ label: '变更原因', prop: 'changeReason', minWidth: 220 },
|
{ label: '变更原因', prop: 'changeReason', minWidth: 220 },
|
||||||
];
|
];
|
||||||
},
|
},
|
||||||
|
transportCargoTypeOptions() {
|
||||||
|
return this.cargoTypeOptions.filter(item => item.children?.length);
|
||||||
|
},
|
||||||
},
|
},
|
||||||
mounted() {
|
mounted() {
|
||||||
this.loadTransportTypeOptions();
|
this.loadTransportTypeOptions();
|
||||||
|
this.loadCargoTypeOptions();
|
||||||
|
this.loadPriceUnitOptions();
|
||||||
this.loadTable();
|
this.loadTable();
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
@@ -682,6 +775,152 @@ export default {
|
|||||||
this.transportTypeOptions.find(item => String(item.value) === String(value))?.label || value
|
this.transportTypeOptions.find(item => String(item.value) === String(value))?.label || value
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
loadCargoTypeOptions() {
|
||||||
|
if (this.cargoTypeOptions.length) return Promise.resolve(this.cargoTypeOptions);
|
||||||
|
if (this.cargoTypeRequest) return this.cargoTypeRequest;
|
||||||
|
this.cargoTypeLoading = true;
|
||||||
|
this.cargoTypeRequest = getCargoTypeList(1, 9999)
|
||||||
|
.then(res => {
|
||||||
|
this.cargoTypeOptions = this.buildCargoTypeTree(this.extractRecords(res));
|
||||||
|
this.cargoTypeFlatOptions = this.flattenCargoTypeOptions(this.cargoTypeOptions);
|
||||||
|
return this.cargoTypeOptions;
|
||||||
|
})
|
||||||
|
.finally(() => {
|
||||||
|
this.cargoTypeLoading = false;
|
||||||
|
this.cargoTypeRequest = null;
|
||||||
|
});
|
||||||
|
return this.cargoTypeRequest;
|
||||||
|
},
|
||||||
|
loadPriceUnitOptions() {
|
||||||
|
if (this.priceUnitOptions.length) return Promise.resolve(this.priceUnitOptions);
|
||||||
|
if (this.priceUnitRequest) return this.priceUnitRequest;
|
||||||
|
this.priceUnitLoading = true;
|
||||||
|
this.priceUnitRequest = getDictionary({ code: 'unit_fee' })
|
||||||
|
.then(res => {
|
||||||
|
this.priceUnitOptions = this.extractRecords(res);
|
||||||
|
return this.priceUnitOptions;
|
||||||
|
})
|
||||||
|
.finally(() => {
|
||||||
|
this.priceUnitLoading = false;
|
||||||
|
this.priceUnitRequest = null;
|
||||||
|
});
|
||||||
|
return this.priceUnitRequest;
|
||||||
|
},
|
||||||
|
buildCargoTypeTree(cargoTypes = []) {
|
||||||
|
const flatCargoTypes = [];
|
||||||
|
const collectCargoTypes = list => {
|
||||||
|
(list || []).forEach(item => {
|
||||||
|
flatCargoTypes.push({ ...item, children: undefined });
|
||||||
|
if (item.children?.length) collectCargoTypes(item.children);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
collectCargoTypes(cargoTypes);
|
||||||
|
|
||||||
|
const nodeMap = new Map();
|
||||||
|
const codeMap = new Map();
|
||||||
|
flatCargoTypes.forEach(item => {
|
||||||
|
const id = item.id ?? item.cargoCode ?? item.code;
|
||||||
|
if (id === undefined || id === null) return;
|
||||||
|
const node = {
|
||||||
|
...item,
|
||||||
|
id: String(id),
|
||||||
|
cargoName: this.formatCargoTypeLabel(item),
|
||||||
|
cargoCode: item.cargoCode || item.code || '',
|
||||||
|
parentId:
|
||||||
|
item.parentId === undefined || item.parentId === null ? '' : String(item.parentId),
|
||||||
|
parentCargoCode: item.parentCargoCode || item.parentCode || '',
|
||||||
|
children: [],
|
||||||
|
};
|
||||||
|
nodeMap.set(node.id, node);
|
||||||
|
if (node.cargoCode) codeMap.set(String(node.cargoCode), node);
|
||||||
|
});
|
||||||
|
|
||||||
|
const rootNodes = [];
|
||||||
|
nodeMap.forEach(node => {
|
||||||
|
const parent =
|
||||||
|
nodeMap.get(node.parentId) ||
|
||||||
|
codeMap.get(String(node.parentCargoCode || '')) ||
|
||||||
|
codeMap.get(String(node.parentId || ''));
|
||||||
|
if (parent && parent !== node && Number(node.typeLevel) !== 1) {
|
||||||
|
parent.children.push(node);
|
||||||
|
} else {
|
||||||
|
rootNodes.push(node);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return rootNodes.map(item => this.normalizeCargoTypeNode(item, 1)).filter(Boolean);
|
||||||
|
},
|
||||||
|
normalizeCargoTypeNode(cargoType, level = 1, parentPath = []) {
|
||||||
|
if (level > 2) return null;
|
||||||
|
const id = String(cargoType.id);
|
||||||
|
const path = [...parentPath, id];
|
||||||
|
const children =
|
||||||
|
level === 1
|
||||||
|
? (cargoType.children || [])
|
||||||
|
.map(item => this.normalizeCargoTypeNode(item, level + 1, path))
|
||||||
|
.filter(Boolean)
|
||||||
|
: [];
|
||||||
|
return {
|
||||||
|
...cargoType,
|
||||||
|
id,
|
||||||
|
cargoName: this.formatCargoTypeLabel(cargoType),
|
||||||
|
path,
|
||||||
|
leaf: level === 2,
|
||||||
|
children: children.length ? children : undefined,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
flattenCargoTypeOptions(options = []) {
|
||||||
|
const result = [];
|
||||||
|
const collectOptions = list => {
|
||||||
|
(list || []).forEach(item => {
|
||||||
|
result.push(item);
|
||||||
|
if (item.children?.length) collectOptions(item.children);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
collectOptions(options);
|
||||||
|
return result;
|
||||||
|
},
|
||||||
|
formatCargoTypeLabel(item = {}) {
|
||||||
|
return item.cargoName || item.name || item.typeName || item.label || '';
|
||||||
|
},
|
||||||
|
filterCargoType(node, keyword) {
|
||||||
|
const text = String(keyword || '').trim();
|
||||||
|
if (!text) return true;
|
||||||
|
const labels = node.pathLabels?.length ? node.pathLabels : [node.label];
|
||||||
|
return (
|
||||||
|
labels.some(label => String(label || '').includes(text)) ||
|
||||||
|
String(node.text || '').includes(text)
|
||||||
|
);
|
||||||
|
},
|
||||||
|
getCargoTypePathLabels(path = []) {
|
||||||
|
let options = this.cargoTypeOptions;
|
||||||
|
const labels = [];
|
||||||
|
(path || []).forEach(value => {
|
||||||
|
const option = (options || []).find(item => String(item.id) === String(value));
|
||||||
|
if (!option) return;
|
||||||
|
labels.push(option.cargoName || '');
|
||||||
|
options = option.children || [];
|
||||||
|
});
|
||||||
|
return labels.filter(Boolean);
|
||||||
|
},
|
||||||
|
resolveCargoTypePath(cargoTypeName) {
|
||||||
|
const name = String(cargoTypeName || '').trim();
|
||||||
|
if (!name) return [];
|
||||||
|
const cargoType = this.cargoTypeFlatOptions.find(item => {
|
||||||
|
const label = this.formatCargoTypeLabel(item);
|
||||||
|
return item.path?.length >= 2 && (label === name || name.endsWith(label));
|
||||||
|
});
|
||||||
|
return cargoType?.path || [];
|
||||||
|
},
|
||||||
|
handleAdjustCargoTypeChange(row, value) {
|
||||||
|
const path =
|
||||||
|
Array.isArray(value) && value.length >= 2
|
||||||
|
? value.slice(0, 2).map(item => String(item))
|
||||||
|
: [];
|
||||||
|
const labels = this.getCargoTypePathLabels(path);
|
||||||
|
row.cargoTypePath = path;
|
||||||
|
row.cargoType = labels.length ? labels[labels.length - 1] : '';
|
||||||
|
row.cargoName = '';
|
||||||
|
},
|
||||||
formatColumnValue(row, column) {
|
formatColumnValue(row, column) {
|
||||||
if (column.prop === 'transportType') return this.transportTypeLabel(row[column.prop]);
|
if (column.prop === 'transportType') return this.transportTypeLabel(row[column.prop]);
|
||||||
if (column.prop === 'transportQuantity') return this.fixedTwoDecimals(row[column.prop]);
|
if (column.prop === 'transportQuantity') return this.fixedTwoDecimals(row[column.prop]);
|
||||||
@@ -719,7 +958,6 @@ export default {
|
|||||||
this.contractLoading = true;
|
this.contractLoading = true;
|
||||||
try {
|
try {
|
||||||
const res = await getContractList(1, 999, {
|
const res = await getContractList(1, 999, {
|
||||||
approvalStatuses: 'approved,change_approved',
|
|
||||||
contractCategory,
|
contractCategory,
|
||||||
});
|
});
|
||||||
const data = this.unwrapPage(res);
|
const data = this.unwrapPage(res);
|
||||||
@@ -751,8 +989,6 @@ export default {
|
|||||||
this.detailDialog.row = null;
|
this.detailDialog.row = null;
|
||||||
},
|
},
|
||||||
closeAdjustPanel() {
|
closeAdjustPanel() {
|
||||||
Object.values(this.adjustCalculateTimers).forEach(timer => clearTimeout(timer));
|
|
||||||
this.adjustCalculateTimers = {};
|
|
||||||
this.adjustDialog.visible = false;
|
this.adjustDialog.visible = false;
|
||||||
this.adjustDialog.row = null;
|
this.adjustDialog.row = null;
|
||||||
},
|
},
|
||||||
@@ -761,8 +997,23 @@ export default {
|
|||||||
this.openWaybillDetail(row);
|
this.openWaybillDetail(row);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
if (this.settlementType === 'payable' && column.prop === 'preSettlementNo') {
|
||||||
|
this.openSettlementDetail('/settlement/pre-settlement', row.preSettlementNo);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (this.settlementType === 'payable' && column.prop === 'formalSettlementNo') {
|
||||||
|
this.openSettlementDetail('/settlement/formal-settlement', row.formalSettlementNo);
|
||||||
|
return;
|
||||||
|
}
|
||||||
this.openDetailDialog(row);
|
this.openDetailDialog(row);
|
||||||
},
|
},
|
||||||
|
openSettlementDetail(path, detailNo) {
|
||||||
|
if (!detailNo) {
|
||||||
|
this.$message.info('当前记录未关联结算单详情');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.$router.push({ path, query: { detailNo } });
|
||||||
|
},
|
||||||
openWaybillDetail(row) {
|
openWaybillDetail(row) {
|
||||||
if (!row.waybillId) {
|
if (!row.waybillId) {
|
||||||
this.$message.info('当前记录未关联运单详情');
|
this.$message.info('当前记录未关联运单详情');
|
||||||
@@ -788,7 +1039,11 @@ export default {
|
|||||||
this.closeDetailPanel();
|
this.closeDetailPanel();
|
||||||
this.adjustDialog = { ...this.adjustDialog, visible: true, row, loading: true };
|
this.adjustDialog = { ...this.adjustDialog, visible: true, row, loading: true };
|
||||||
try {
|
try {
|
||||||
const res = await api.getFeeDetail(row.id);
|
const [res] = await Promise.all([
|
||||||
|
api.getFeeDetail(row.id),
|
||||||
|
this.loadCargoTypeOptions(),
|
||||||
|
this.loadPriceUnitOptions(),
|
||||||
|
]);
|
||||||
const data = res.data?.data || res.data || res || {};
|
const data = res.data?.data || res.data || res || {};
|
||||||
this.adjustDynamicFeeColumns = (data.feeItemNames || []).map(name => ({
|
this.adjustDynamicFeeColumns = (data.feeItemNames || []).map(name => ({
|
||||||
label: name,
|
label: name,
|
||||||
@@ -812,23 +1067,53 @@ export default {
|
|||||||
freightAmount: Number(item.freightAmount || 0),
|
freightAmount: Number(item.freightAmount || 0),
|
||||||
originalAmount: Number(item.originalAmount || 0),
|
originalAmount: Number(item.originalAmount || 0),
|
||||||
feeItems,
|
feeItems,
|
||||||
manualFee: item.billingFactor === '手工调整',
|
dataSource:
|
||||||
feeItemName: item.billingFactor === '手工调整' ? Object.keys(feeItems)[0] || '' : '',
|
item.dataSource || (item.billingFactor === '手工调整' ? '手工录入' : '自动生成'),
|
||||||
feeType: item.billingType === '手工扣费' ? 'deduct' : 'charge',
|
manualFee: item.dataSource === '手工录入' || item.billingFactor === '手工调整',
|
||||||
amount: item.billingFactor === '手工调整' ? Math.abs(Number(item.afterAmount || 0)) : 0,
|
cargoTypePath: this.resolveCargoTypePath(item.cargoType),
|
||||||
};
|
};
|
||||||
if (adjusted.manualFee) this.recalculateManualAdjustRow(adjusted);
|
this.recalculateAdjustRow(adjusted);
|
||||||
else this.recalculateAdjustRow(adjusted);
|
|
||||||
return adjusted;
|
return adjusted;
|
||||||
});
|
});
|
||||||
} finally {
|
} finally {
|
||||||
this.adjustDialog.loading = false;
|
this.adjustDialog.loading = false;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
recalculateManualAdjustRow(row) {
|
addAdjustFee() {
|
||||||
const amount = Number(row.amount || 0);
|
if (!this.isReceivable) return;
|
||||||
row.afterAmount = Number((row.feeType === 'deduct' ? -amount : amount).toFixed(2));
|
const feeItems = this.adjustDynamicFeeColumns.reduce((items, column) => {
|
||||||
row.adjustAmount = Number((row.afterAmount - Number(row.originalAmount || 0)).toFixed(2));
|
items[column.feeItemName] = 0;
|
||||||
|
return items;
|
||||||
|
}, {});
|
||||||
|
this.adjustRows.push({
|
||||||
|
dataSource: '手工录入',
|
||||||
|
cargoName: '',
|
||||||
|
cargoType: '',
|
||||||
|
cargoTypePath: [],
|
||||||
|
specification: '',
|
||||||
|
model: '',
|
||||||
|
billingFactor: '-',
|
||||||
|
billingType: '-',
|
||||||
|
transportQuantity: '',
|
||||||
|
priceUnit: '',
|
||||||
|
unitPrice: '',
|
||||||
|
mileage: null,
|
||||||
|
freightAmount: '',
|
||||||
|
originalAmount: 0,
|
||||||
|
originalAmountText: '-',
|
||||||
|
adjustAmount: 0,
|
||||||
|
afterAmount: 0,
|
||||||
|
feeItems,
|
||||||
|
manualFee: true,
|
||||||
|
remark: '',
|
||||||
|
changeReason: '',
|
||||||
|
});
|
||||||
|
},
|
||||||
|
feeSourceLabel(value) {
|
||||||
|
return value === '手工录入' ? '手工录入' : '自动生成';
|
||||||
|
},
|
||||||
|
adjustTextMaxlength(prop) {
|
||||||
|
return ['billingFactor', 'billingType'].includes(prop) ? 100 : 255;
|
||||||
},
|
},
|
||||||
fixedTwoDecimals(value) {
|
fixedTwoDecimals(value) {
|
||||||
return Number(value || 0).toFixed(2);
|
return Number(value || 0).toFixed(2);
|
||||||
@@ -846,48 +1131,12 @@ export default {
|
|||||||
},
|
},
|
||||||
handleAdjustDecimalInput(row, prop, value, changedField) {
|
handleAdjustDecimalInput(row, prop, value, changedField) {
|
||||||
row[prop] = this.normalizeAdjustDecimal(value);
|
row[prop] = this.normalizeAdjustDecimal(value);
|
||||||
if (['transportQuantity', 'mileage'].includes(prop)) {
|
|
||||||
this.scheduleAdjustedFeeCalculation(row);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
this.recalculateAdjustRow(row, changedField);
|
this.recalculateAdjustRow(row, changedField);
|
||||||
},
|
},
|
||||||
handleAdjustFeeItemInput(row, feeItemName, value) {
|
handleAdjustFeeItemInput(row, feeItemName, value) {
|
||||||
row.feeItems[feeItemName] = this.normalizeAdjustDecimal(value);
|
row.feeItems[feeItemName] = this.normalizeAdjustDecimal(value);
|
||||||
this.recalculateAdjustRow(row, feeItemName);
|
this.recalculateAdjustRow(row, feeItemName);
|
||||||
},
|
},
|
||||||
scheduleAdjustedFeeCalculation(row) {
|
|
||||||
const key = String(row.id);
|
|
||||||
clearTimeout(this.adjustCalculateTimers[key]);
|
|
||||||
row.calculateSequence = Number(row.calculateSequence || 0) + 1;
|
|
||||||
const sequence = row.calculateSequence;
|
|
||||||
this.adjustCalculateTimers[key] = setTimeout(
|
|
||||||
() => this.calculateAdjustedFee(row, sequence),
|
|
||||||
350
|
|
||||||
);
|
|
||||||
},
|
|
||||||
async calculateAdjustedFee(row, sequence) {
|
|
||||||
if (!this.adjustDialog.row || !row.id) return;
|
|
||||||
row.calculating = true;
|
|
||||||
try {
|
|
||||||
const res = await api.calculateAdjustedFee({
|
|
||||||
detailId: this.adjustDialog.row.id,
|
|
||||||
feeId: row.id,
|
|
||||||
transportQuantity: row.transportQuantity,
|
|
||||||
mileage: row.mileage,
|
|
||||||
freightAmount: row.freightAmount,
|
|
||||||
feeItems: row.feeItems,
|
|
||||||
});
|
|
||||||
if (sequence !== row.calculateSequence || !this.adjustDialog.visible) return;
|
|
||||||
const data = res.data?.data || res.data || res || {};
|
|
||||||
row.freightAmount = Number(data.freightAmount || 0);
|
|
||||||
row.feeItems = { ...row.feeItems, ...(data.feeItems || {}) };
|
|
||||||
row.adjustAmount = Number(data.adjustAmount || 0);
|
|
||||||
row.afterAmount = Number(data.afterAmount || 0);
|
|
||||||
} finally {
|
|
||||||
if (sequence === row.calculateSequence) row.calculating = false;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
recalculateAdjustRow(row, changedField) {
|
recalculateAdjustRow(row, changedField) {
|
||||||
if (changedField && changedField !== 'freight' && this.isFreightFeeItem(changedField)) {
|
if (changedField && changedField !== 'freight' && this.isFreightFeeItem(changedField)) {
|
||||||
row.freightAmount = Number(row.feeItems[changedField] || 0);
|
row.freightAmount = Number(row.feeItems[changedField] || 0);
|
||||||
@@ -914,28 +1163,25 @@ export default {
|
|||||||
this.$message.warning('没有可调整的费用明细');
|
this.$message.warning('没有可调整的费用明细');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const invalidManualRow = this.adjustRows.find(
|
|
||||||
row =>
|
|
||||||
row.manualFee && (!String(row.feeItemName || '').trim() || Number(row.amount || 0) <= 0)
|
|
||||||
);
|
|
||||||
if (invalidManualRow) {
|
|
||||||
this.$message.warning('请完整填写手工费用项目和金额');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
this.adjustDialog.submitting = true;
|
this.adjustDialog.submitting = true;
|
||||||
try {
|
try {
|
||||||
await api.adjustFee({
|
await api.adjustFee({
|
||||||
detailId: this.adjustDialog.row.id,
|
detailId: this.adjustDialog.row.id,
|
||||||
rows: this.adjustRows.map(row => ({
|
rows: this.adjustRows.map(row => ({
|
||||||
id: row.id,
|
id: row.id,
|
||||||
|
cargoName: row.cargoName,
|
||||||
|
cargoType: row.cargoType,
|
||||||
|
specification: row.specification,
|
||||||
|
model: row.model,
|
||||||
|
billingFactor: row.billingFactor,
|
||||||
|
billingType: row.billingType,
|
||||||
transportQuantity: row.transportQuantity,
|
transportQuantity: row.transportQuantity,
|
||||||
|
priceUnit: row.priceUnit,
|
||||||
|
unitPrice: row.unitPrice,
|
||||||
mileage: row.mileage,
|
mileage: row.mileage,
|
||||||
freightAmount: row.freightAmount,
|
freightAmount: row.freightAmount,
|
||||||
feeItems: row.feeItems,
|
feeItems: row.feeItems,
|
||||||
manualFee: row.manualFee === true,
|
manualFee: row.manualFee === true,
|
||||||
feeItemName: row.feeItemName,
|
|
||||||
feeType: row.feeType,
|
|
||||||
amount: row.amount,
|
|
||||||
remark: row.remark,
|
remark: row.remark,
|
||||||
changeReason: row.changeReason,
|
changeReason: row.changeReason,
|
||||||
})),
|
})),
|
||||||
@@ -1393,16 +1639,15 @@ export default {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
handleExport() {
|
handleExport() {
|
||||||
|
const params = this.buildRequestParams(
|
||||||
|
this.normalizeQuery(this.query, 'generateDateRange', 'generateStartDate', 'generateEndDate')
|
||||||
|
);
|
||||||
|
if (this.selection.length) {
|
||||||
|
params.ids = this.selection.map(item => item.id).join(',');
|
||||||
|
}
|
||||||
exportBlob(
|
exportBlob(
|
||||||
'/blade-transport/receivable-payable-detail/export-receivable-payable-detail',
|
'/blade-transport/receivable-payable-detail/export-receivable-payable-detail',
|
||||||
this.buildRequestParams(
|
params,
|
||||||
this.normalizeQuery(
|
|
||||||
this.query,
|
|
||||||
'generateDateRange',
|
|
||||||
'generateStartDate',
|
|
||||||
'generateEndDate'
|
|
||||||
)
|
|
||||||
),
|
|
||||||
{ feedback: true }
|
{ feedback: true }
|
||||||
).then(res => {
|
).then(res => {
|
||||||
downloadXls(
|
downloadXls(
|
||||||
@@ -1509,6 +1754,14 @@ export default {
|
|||||||
const data = res.data?.data || res.data || res || {};
|
const data = res.data?.data || res.data || res || {};
|
||||||
return data.records ? data : { records: data.records || [], total: data.total || 0 };
|
return data.records ? data : { records: data.records || [], total: data.total || 0 };
|
||||||
},
|
},
|
||||||
|
extractRecords(res) {
|
||||||
|
const data = res?.data?.data || res?.data || res;
|
||||||
|
if (Array.isArray(data)) return data;
|
||||||
|
if (Array.isArray(data?.records)) return data.records;
|
||||||
|
if (Array.isArray(data?.data)) return data.data;
|
||||||
|
if (Array.isArray(data?.data?.records)) return data.data.records;
|
||||||
|
return [];
|
||||||
|
},
|
||||||
noop() {},
|
noop() {},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
@@ -1626,6 +1879,18 @@ export default {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.settlement-detail-page__adjust-toolbar {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: flex-end;
|
||||||
|
margin-bottom: 12px;
|
||||||
|
background: transparent;
|
||||||
|
}
|
||||||
|
|
||||||
|
.settlement-detail-page__adjust-control {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
.settlement-detail-page__generate-search {
|
.settlement-detail-page__generate-search {
|
||||||
margin-bottom: 16px;
|
margin-bottom: 16px;
|
||||||
padding: 12px 12px 4px;
|
padding: 12px 12px 4px;
|
||||||
|
|||||||
@@ -120,9 +120,11 @@
|
|||||||
<el-table-column label="评分项目数" width="140" align="center">
|
<el-table-column label="评分项目数" width="140" align="center">
|
||||||
<template #default="{ row }">{{ row.items.length }}</template>
|
<template #default="{ row }">{{ row.items.length }}</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
<el-table-column v-if="!readonly" label="操作" width="160" align="center">
|
<el-table-column label="操作" width="160" align="center">
|
||||||
<template #default="{ row }">
|
<template #default="{ row }">
|
||||||
<el-link type="primary" @click="openCategory(row)">编辑</el-link>
|
<el-link type="primary" @click="openCategory(row)">
|
||||||
|
{{ readonly ? '查看' : '编辑' }}
|
||||||
|
</el-link>
|
||||||
</template>
|
</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
</el-table>
|
</el-table>
|
||||||
@@ -192,7 +194,9 @@
|
|||||||
</el-table-column>
|
</el-table-column>
|
||||||
<el-table-column label="操作" width="160" align="center">
|
<el-table-column label="操作" width="160" align="center">
|
||||||
<template #default="{ row, $index }">
|
<template #default="{ row, $index }">
|
||||||
<el-link type="primary" @click="openItem(row, $index)">编辑</el-link>
|
<el-link type="primary" @click="openItem(row, $index)">
|
||||||
|
{{ readonly ? '查看' : '编辑' }}
|
||||||
|
</el-link>
|
||||||
<el-link
|
<el-link
|
||||||
type="danger"
|
type="danger"
|
||||||
v-if="!readonly"
|
v-if="!readonly"
|
||||||
@@ -207,7 +211,9 @@
|
|||||||
</section-card>
|
</section-card>
|
||||||
</div>
|
</div>
|
||||||
<template #footer>
|
<template #footer>
|
||||||
<el-button type="primary" @click="categoryBox = false">保存</el-button>
|
<el-button :type="readonly ? 'default' : 'primary'" @click="categoryBox = false">
|
||||||
|
{{ readonly ? '关闭' : '保存' }}
|
||||||
|
</el-button>
|
||||||
</template>
|
</template>
|
||||||
</el-dialog>
|
</el-dialog>
|
||||||
|
|
||||||
@@ -335,7 +341,7 @@
|
|||||||
</el-form>
|
</el-form>
|
||||||
</section-card>
|
</section-card>
|
||||||
<template #footer>
|
<template #footer>
|
||||||
<el-button @click="itemBox = false">取消</el-button>
|
<el-button @click="itemBox = false">{{ readonly ? '关闭' : '取消' }}</el-button>
|
||||||
<el-button type="primary" v-if="!readonly" @click="saveItem">保存</el-button>
|
<el-button type="primary" v-if="!readonly" @click="saveItem">保存</el-button>
|
||||||
</template>
|
</template>
|
||||||
</el-dialog>
|
</el-dialog>
|
||||||
@@ -467,6 +473,15 @@ const createTimeRangeMap = {
|
|||||||
createTimeRange: ['createTimeStart', 'createTimeEnd'],
|
createTimeRange: ['createTimeStart', 'createTimeEnd'],
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const standardNumberFields = [
|
||||||
|
'scoreRateLower',
|
||||||
|
'scoreRateUpper',
|
||||||
|
'scoreRateIncrease',
|
||||||
|
'creditLimitLower',
|
||||||
|
'creditLimitUpper',
|
||||||
|
'creditLimitIncrease',
|
||||||
|
];
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
@@ -657,14 +672,7 @@ export default {
|
|||||||
(detail.categories || []).forEach(category => {
|
(detail.categories || []).forEach(category => {
|
||||||
map[category.categoryCode] = {
|
map[category.categoryCode] = {
|
||||||
...category,
|
...category,
|
||||||
items: (category.items || []).map(item => ({
|
items: (category.items || []).map(item => this.normalizeItem(item)),
|
||||||
...item,
|
|
||||||
optionType: this.normalizeOptionType(item.optionType),
|
|
||||||
options:
|
|
||||||
item.options && item.options.length
|
|
||||||
? item.options.map(option => this.normalizeOption(option))
|
|
||||||
: [this.emptyOption()],
|
|
||||||
})),
|
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
return {
|
return {
|
||||||
@@ -673,7 +681,7 @@ export default {
|
|||||||
...(map[category.categoryCode] || category),
|
...(map[category.categoryCode] || category),
|
||||||
categoryName: category.categoryName,
|
categoryName: category.categoryName,
|
||||||
})),
|
})),
|
||||||
standards: detail.standards || [],
|
standards: (detail.standards || []).map(standard => this.normalizeStandard(standard)),
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
openConfig(row, readonly = false) {
|
openConfig(row, readonly = false) {
|
||||||
@@ -710,13 +718,16 @@ export default {
|
|||||||
return value === 'score' || value === 2 || String(value) === 'score' ? 'score' : 'option';
|
return value === 'score' || value === 2 || String(value) === 'score' ? 'score' : 'option';
|
||||||
},
|
},
|
||||||
normalizeItem(item = {}) {
|
normalizeItem(item = {}) {
|
||||||
|
const normalized = { ...this.emptyItem(), ...item };
|
||||||
|
const score = Number(this.normalizePlainDecimal(normalized.score));
|
||||||
return {
|
return {
|
||||||
...this.emptyItem(),
|
...normalized,
|
||||||
...item,
|
score: Number.isFinite(score) ? score : normalized.score,
|
||||||
optionType: this.normalizeOptionType(item.optionType),
|
baseValue: this.normalizePlainDecimal(normalized.baseValue),
|
||||||
|
optionType: this.normalizeOptionType(normalized.optionType),
|
||||||
options:
|
options:
|
||||||
item.options && item.options.length
|
normalized.options && normalized.options.length
|
||||||
? item.options.map(option => this.normalizeOption(option))
|
? normalized.options.map(option => this.normalizeOption(option))
|
||||||
: [this.emptyOption()],
|
: [this.emptyOption()],
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
@@ -724,6 +735,8 @@ export default {
|
|||||||
const normalized = { ...this.emptyOption(), ...option };
|
const normalized = { ...this.emptyOption(), ...option };
|
||||||
return {
|
return {
|
||||||
...normalized,
|
...normalized,
|
||||||
|
score: this.normalizePlainDecimal(normalized.score),
|
||||||
|
changeValue: this.normalizePlainDecimal(normalized.changeValue),
|
||||||
changeType:
|
changeType:
|
||||||
normalized.changeType === 'decrease' ||
|
normalized.changeType === 'decrease' ||
|
||||||
normalized.changeType === '每减少' ||
|
normalized.changeType === '每减少' ||
|
||||||
@@ -738,6 +751,35 @@ export default {
|
|||||||
: 'add',
|
: 'add',
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
normalizePlainDecimal(value) {
|
||||||
|
if (value === undefined || value === null || value === '') return '';
|
||||||
|
const text = String(value).trim();
|
||||||
|
const matched = text.match(/^([+-]?)(\d+)(?:\.(\d*))?[eE]([+-]?\d+)$/);
|
||||||
|
if (!matched) return text;
|
||||||
|
const [, sign, integer, decimal = '', exponentText] = matched;
|
||||||
|
const exponent = Number(exponentText);
|
||||||
|
const digits = `${integer}${decimal}`;
|
||||||
|
const decimalIndex = integer.length + exponent;
|
||||||
|
const resultLength =
|
||||||
|
decimalIndex <= 0 ? 2 - decimalIndex + digits.length : Math.max(decimalIndex, digits.length);
|
||||||
|
if (!Number.isSafeInteger(exponent) || resultLength > 1000) return text;
|
||||||
|
let result;
|
||||||
|
if (decimalIndex <= 0) {
|
||||||
|
result = `0.${'0'.repeat(-decimalIndex)}${digits}`;
|
||||||
|
} else if (decimalIndex >= digits.length) {
|
||||||
|
result = `${digits}${'0'.repeat(decimalIndex - digits.length)}`;
|
||||||
|
} else {
|
||||||
|
result = `${digits.slice(0, decimalIndex)}.${digits.slice(decimalIndex)}`;
|
||||||
|
}
|
||||||
|
return `${sign === '-' ? '-' : ''}${result}`;
|
||||||
|
},
|
||||||
|
normalizeStandard(standard = {}) {
|
||||||
|
const normalized = { ...this.emptyStandard(), ...standard };
|
||||||
|
standardNumberFields.forEach(field => {
|
||||||
|
normalized[field] = this.normalizePlainDecimal(normalized[field]);
|
||||||
|
});
|
||||||
|
return normalized;
|
||||||
|
},
|
||||||
handleOptionTypeChange(value) {
|
handleOptionTypeChange(value) {
|
||||||
this.itemForm.optionType = this.normalizeOptionType(value);
|
this.itemForm.optionType = this.normalizeOptionType(value);
|
||||||
this.itemForm.options = (this.itemForm.options || []).map(option =>
|
this.itemForm.options = (this.itemForm.options || []).map(option =>
|
||||||
@@ -770,6 +812,10 @@ export default {
|
|||||||
(payload.categories || []).forEach(category => {
|
(payload.categories || []).forEach(category => {
|
||||||
(category.items || []).forEach(item => {
|
(category.items || []).forEach(item => {
|
||||||
item.score = this.toScoreValue(item.score);
|
item.score = this.toScoreValue(item.score);
|
||||||
|
item.baseValue =
|
||||||
|
this.normalizeOptionType(item.optionType) === 'score'
|
||||||
|
? this.normalizePlainDecimal(item.baseValue)
|
||||||
|
: null;
|
||||||
(item.options || []).forEach(option => {
|
(item.options || []).forEach(option => {
|
||||||
option.score = this.toScoreValue(option.score);
|
option.score = this.toScoreValue(option.score);
|
||||||
if (this.normalizeOptionType(item.optionType) === 'score') {
|
if (this.normalizeOptionType(item.optionType) === 'score') {
|
||||||
@@ -778,6 +824,11 @@ export default {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
(payload.standards || []).forEach(standard => {
|
||||||
|
standardNumberFields.forEach(field => {
|
||||||
|
standard[field] = this.normalizePlainDecimal(standard[field]);
|
||||||
|
});
|
||||||
|
});
|
||||||
return payload;
|
return payload;
|
||||||
},
|
},
|
||||||
hasDuplicateItemName(itemName, currentIndex = -1) {
|
hasDuplicateItemName(itemName, currentIndex = -1) {
|
||||||
@@ -844,7 +895,8 @@ export default {
|
|||||||
const item = JSON.parse(JSON.stringify(this.itemForm));
|
const item = JSON.parse(JSON.stringify(this.itemForm));
|
||||||
item.itemName = String(item.itemName || '').trim();
|
item.itemName = String(item.itemName || '').trim();
|
||||||
item.optionType = this.normalizeOptionType(item.optionType);
|
item.optionType = this.normalizeOptionType(item.optionType);
|
||||||
item.baseValue = item.optionType === 'score' ? String(item.baseValue).trim() : null;
|
item.baseValue =
|
||||||
|
item.optionType === 'score' ? this.normalizePlainDecimal(item.baseValue) : null;
|
||||||
item.options = item.options.map(option => ({
|
item.options = item.options.map(option => ({
|
||||||
...option,
|
...option,
|
||||||
optionName: String(option.optionName || '').trim(),
|
optionName: String(option.optionName || '').trim(),
|
||||||
@@ -874,7 +926,9 @@ export default {
|
|||||||
},
|
},
|
||||||
openStandard(row, index = -1) {
|
openStandard(row, index = -1) {
|
||||||
this.currentStandardIndex = index;
|
this.currentStandardIndex = index;
|
||||||
this.standardForm = row ? JSON.parse(JSON.stringify(row)) : this.emptyStandard();
|
this.standardForm = row
|
||||||
|
? this.normalizeStandard(JSON.parse(JSON.stringify(row)))
|
||||||
|
: this.emptyStandard();
|
||||||
this.standardBox = true;
|
this.standardBox = true;
|
||||||
},
|
},
|
||||||
hasDuplicateCreditLevel(creditLevel, currentIndex = -1) {
|
hasDuplicateCreditLevel(creditLevel, currentIndex = -1) {
|
||||||
@@ -943,14 +997,7 @@ export default {
|
|||||||
}
|
}
|
||||||
const standard = JSON.parse(JSON.stringify(this.standardForm));
|
const standard = JSON.parse(JSON.stringify(this.standardForm));
|
||||||
standard.creditLevel = String(standard.creditLevel || '').trim();
|
standard.creditLevel = String(standard.creditLevel || '').trim();
|
||||||
[
|
standardNumberFields.forEach(field => {
|
||||||
'scoreRateLower',
|
|
||||||
'scoreRateUpper',
|
|
||||||
'scoreRateIncrease',
|
|
||||||
'creditLimitLower',
|
|
||||||
'creditLimitUpper',
|
|
||||||
'creditLimitIncrease',
|
|
||||||
].forEach(field => {
|
|
||||||
standard[field] = Number(standard[field]);
|
standard[field] = Number(standard[field]);
|
||||||
});
|
});
|
||||||
const nextStandards = JSON.parse(JSON.stringify(this.configForm.standards));
|
const nextStandards = JSON.parse(JSON.stringify(this.configForm.standards));
|
||||||
@@ -1130,14 +1177,6 @@ export default {
|
|||||||
this.$message.warning('发布前至少配置1条信用等级评估标准');
|
this.$message.warning('发布前至少配置1条信用等级评估标准');
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
const requiredFields = [
|
|
||||||
'scoreRateLower',
|
|
||||||
'scoreRateUpper',
|
|
||||||
'scoreRateIncrease',
|
|
||||||
'creditLimitLower',
|
|
||||||
'creditLimitUpper',
|
|
||||||
'creditLimitIncrease',
|
|
||||||
];
|
|
||||||
for (const standard of standards) {
|
for (const standard of standards) {
|
||||||
if (!standard.creditLevel) {
|
if (!standard.creditLevel) {
|
||||||
this.$message.warning('评估标准中的信用等级不能为空');
|
this.$message.warning('评估标准中的信用等级不能为空');
|
||||||
@@ -1147,7 +1186,7 @@ export default {
|
|||||||
this.$message.warning('评估标准中的信用等级只能选择A/B/C/D/E/F');
|
this.$message.warning('评估标准中的信用等级只能选择A/B/C/D/E/F');
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
if (requiredFields.some(field => this.isBlankValue(standard[field]))) {
|
if (standardNumberFields.some(field => this.isBlankValue(standard[field]))) {
|
||||||
this.$message.warning(`${standard.creditLevel}信用等级评估标准必填项未填写完整`);
|
this.$message.warning(`${standard.creditLevel}信用等级评估标准必填项未填写完整`);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -170,7 +170,7 @@
|
|||||||
:disabled="readonly"
|
:disabled="readonly"
|
||||||
class="archive-form dialog-form-label-fixed"
|
class="archive-form dialog-form-label-fixed"
|
||||||
>
|
>
|
||||||
<section-card title="工商信息">
|
<section-card title="基础信息">
|
||||||
<el-row :gutter="18">
|
<el-row :gutter="18">
|
||||||
<el-col :span="6">
|
<el-col :span="6">
|
||||||
<el-form-item label="客商编号" prop="customerCode">
|
<el-form-item label="客商编号" prop="customerCode">
|
||||||
@@ -726,7 +726,7 @@
|
|||||||
@close="qualificationImagePreviewVisible = false"
|
@close="qualificationImagePreviewVisible = false"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<section-card title="变更记录">
|
<section-card v-if="archiveForm.id" title="变更记录">
|
||||||
<el-table :data="changeRecordPage.records" border :show-overflow-tooltip="false">
|
<el-table :data="changeRecordPage.records" border :show-overflow-tooltip="false">
|
||||||
<el-table-column
|
<el-table-column
|
||||||
label="序号"
|
label="序号"
|
||||||
@@ -3517,6 +3517,8 @@ export default {
|
|||||||
customerType: this.splitValue(detail.customerType),
|
customerType: this.splitValue(detail.customerType),
|
||||||
invoiceTaxRate: this.normalizeOptionalAmount(detail.invoiceTaxRate),
|
invoiceTaxRate: this.normalizeOptionalAmount(detail.invoiceTaxRate),
|
||||||
registeredCapital: this.normalizeOptionalAmount(detail.registeredCapital),
|
registeredCapital: this.normalizeOptionalAmount(detail.registeredCapital),
|
||||||
|
maxCreditLimit: this.normalizeOptionalAmount(detail.maxCreditLimit),
|
||||||
|
applyCreditLimit: this.normalizeOptionalAmount(detail.applyCreditLimit),
|
||||||
businessScope,
|
businessScope,
|
||||||
businessTermType: detail.businessTermType || '固定期限',
|
businessTermType: detail.businessTermType || '固定期限',
|
||||||
registeredRegionPath: Array.isArray(detail.registeredRegionPath)
|
registeredRegionPath: Array.isArray(detail.registeredRegionPath)
|
||||||
@@ -3556,6 +3558,12 @@ export default {
|
|||||||
details: [],
|
details: [],
|
||||||
...score,
|
...score,
|
||||||
quantificationId: score.quantificationId ? String(score.quantificationId) : '',
|
quantificationId: score.quantificationId ? String(score.quantificationId) : '',
|
||||||
|
applyCreditLimit: this.normalizeOptionalAmount(
|
||||||
|
score.applyCreditLimit === undefined
|
||||||
|
? this.archiveForm.applyCreditLimit
|
||||||
|
: score.applyCreditLimit
|
||||||
|
),
|
||||||
|
maxCreditLimit: this.normalizeOptionalAmount(score.maxCreditLimit),
|
||||||
tempApplyCreditLimit: this.normalizeOptionalAmount(score.tempApplyCreditLimit),
|
tempApplyCreditLimit: this.normalizeOptionalAmount(score.tempApplyCreditLimit),
|
||||||
attachments: score.attachments || this.parseAttachments(score.proofAttachments),
|
attachments: score.attachments || this.parseAttachments(score.proofAttachments),
|
||||||
details: (score.details || []).map(detail => ({
|
details: (score.details || []).map(detail => ({
|
||||||
@@ -3740,8 +3748,22 @@ export default {
|
|||||||
archive.invoices = (archive.invoices || [])
|
archive.invoices = (archive.invoices || [])
|
||||||
.map(item => this.normalizeInvoice(item))
|
.map(item => this.normalizeInvoice(item))
|
||||||
.filter(item => item.invoiceTitle || item.taxNo || item.bankAccount);
|
.filter(item => item.invoiceTitle || item.taxNo || item.bankAccount);
|
||||||
archive.scores = (archive.scores || []).map(score => this.calculateScore(score));
|
archive.scores = (archive.scores || []).map(score => {
|
||||||
|
const calculatedScore = this.calculateScore(score);
|
||||||
|
const scoreMaxCreditLimit = this.normalizeOptionalAmount(calculatedScore.maxCreditLimit);
|
||||||
|
const scoreApplyCreditLimit = this.normalizeOptionalAmount(
|
||||||
|
calculatedScore.applyCreditLimit
|
||||||
|
);
|
||||||
|
calculatedScore.maxCreditLimit = scoreMaxCreditLimit === '' ? null : scoreMaxCreditLimit;
|
||||||
|
calculatedScore.applyCreditLimit =
|
||||||
|
scoreApplyCreditLimit === '' ? null : scoreApplyCreditLimit;
|
||||||
|
return calculatedScore;
|
||||||
|
});
|
||||||
this.applyArchiveCreditFromScores(archive);
|
this.applyArchiveCreditFromScores(archive);
|
||||||
|
const maxCreditLimit = this.normalizeOptionalAmount(archive.maxCreditLimit);
|
||||||
|
const applyCreditLimit = this.normalizeOptionalAmount(archive.applyCreditLimit);
|
||||||
|
archive.maxCreditLimit = maxCreditLimit === '' ? null : maxCreditLimit;
|
||||||
|
archive.applyCreditLimit = applyCreditLimit === '' ? null : applyCreditLimit;
|
||||||
return archive;
|
return archive;
|
||||||
},
|
},
|
||||||
saveArchive() {
|
saveArchive() {
|
||||||
|
|||||||
+13
-13
@@ -50,26 +50,26 @@ export default ({
|
|||||||
__VUE_I18N_LEGACY_API__: true,
|
__VUE_I18N_LEGACY_API__: true,
|
||||||
__INTLIFY_PROD_DEVTOOLS__: false,
|
__INTLIFY_PROD_DEVTOOLS__: false,
|
||||||
},
|
},
|
||||||
// server: {
|
|
||||||
// port: 2888,
|
|
||||||
// proxy: {
|
|
||||||
// '/api': {
|
|
||||||
// target: 'http://localhost',
|
|
||||||
// //target: 'https://saber3.bladex.cn/api',
|
|
||||||
// changeOrigin: true,
|
|
||||||
// rewrite: path => path.replace(/^\/api/, ''),
|
|
||||||
// },
|
|
||||||
// },
|
|
||||||
// },
|
|
||||||
server: {
|
server: {
|
||||||
port: 2889,
|
port: 2888,
|
||||||
proxy: {
|
proxy: {
|
||||||
'/api': {
|
'/api': {
|
||||||
target: 'http://172.16.203.228:8000',
|
target: 'http://localhost',
|
||||||
|
//target: 'https://saber3.bladex.cn/api',
|
||||||
changeOrigin: true,
|
changeOrigin: true,
|
||||||
|
rewrite: path => path.replace(/^\/api/, ''),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
// server: {
|
||||||
|
// port: 2889,
|
||||||
|
// proxy: {
|
||||||
|
// '/api': {
|
||||||
|
// target: 'http://172.16.203.228:8000',
|
||||||
|
// changeOrigin: true,
|
||||||
|
// },
|
||||||
|
// },
|
||||||
|
// },
|
||||||
resolve: {
|
resolve: {
|
||||||
alias: {
|
alias: {
|
||||||
'~': resolve(__dirname, './'),
|
'~': resolve(__dirname, './'),
|
||||||
|
|||||||
Reference in New Issue
Block a user