项目、合同fix

This commit is contained in:
2026-08-06 17:40:03 +08:00
parent 5e92124710
commit e6d4edf362
15 changed files with 887 additions and 335 deletions

View File

@@ -45,6 +45,13 @@ export const reject = id =>
params: { id }, params: { id },
}); });
export const updateAttachments = row =>
request({
url: `${baseUrl}/update-attachments`,
method: 'post',
data: row,
});
export const withdraw = id => export const withdraw = id =>
request({ request({
url: `${baseUrl}/withdraw`, url: `${baseUrl}/withdraw`,

View File

@@ -87,8 +87,10 @@ export const approvalStatusOptions = [
]; ];
export const effectiveTypeOptions = [ export const effectiveTypeOptions = [
{ label: '临时', value: 'temporary' }, { label: '未生效', value: 'not_effective' },
{ label: '正式', value: 'formal' }, { label: '临时生效', value: 'temporary_effective' },
{ label: '正式生效', value: 'formal_effective' },
{ label: '已过期', value: 'expired' },
]; ];
export const businessTypeOptions = [ export const businessTypeOptions = [

View File

@@ -6,11 +6,18 @@ import {
createCrudOption, createCrudOption,
effectiveTypeOptions, effectiveTypeOptions,
selectRule, selectRule,
settlementModeOptions,
signTypeOptions, signTypeOptions,
textRule, textRule,
} from './common'; } from './common';
const contractSettlementModeOptions = [
{ label: '现结', value: '现结' },
{ label: '日结', value: '日结' },
{ label: '周结', value: '周结' },
{ label: '月结', value: '月结' },
{ label: '按固定天数周期', value: '按固定天数周期' },
];
const listDicFormatter = res => { const listDicFormatter = res => {
const data = res?.data || res; const data = res?.data || res;
if (Array.isArray(data)) return data; if (Array.isArray(data)) return data;
@@ -25,7 +32,13 @@ export const config = {
permission: 'contract_manage', permission: 'contract_manage',
exportUrl: '/blade-transport/contract-manage/export-contract-manage', exportUrl: '/blade-transport/contract-manage/export-contract-manage',
exportName: '合同管理', exportName: '合同管理',
enableAllDept: false, defaultForm: {
contractCategory: '客户合同',
},
projectPlaceholder: '请选择',
organizationPlaceholder: '请选择',
enableAllDept: true,
allDeptReadonly: false,
batchDelete: false, batchDelete: false,
enableContractForm: true, enableContractForm: true,
enableProjectSelect: true, enableProjectSelect: true,
@@ -39,6 +52,7 @@ export const config = {
enableChangeRecord: true, enableChangeRecord: true,
enableOrganizationSelect: true, enableOrganizationSelect: true,
enableContractCreateActions: true, enableContractCreateActions: true,
enableContractExpiryTags: true,
actions: ['copy'], actions: ['copy'],
statusProp: 'approvalStatus', statusProp: 'approvalStatus',
statusTextProp: 'approvalStatusName', statusTextProp: 'approvalStatusName',
@@ -63,9 +77,25 @@ export const config = {
label: '撤回', label: '撤回',
statusProp: 'approvalStatus', statusProp: 'approvalStatus',
status: ['reviewing'], status: ['reviewing'],
stage: ['temporary'], stage: ['temporary', 'formal'],
permission: 'contract_manage_withdraw', permission: 'contract_manage_withdraw',
}, },
{
action: 'approve',
label: '通过',
statusProp: 'approvalStatus',
status: ['reviewing'],
stage: ['temporary', 'formal'],
permission: 'contract_manage_approve',
},
{
action: 'reject',
label: '驳回',
statusProp: 'approvalStatus',
status: ['reviewing'],
stage: ['temporary', 'formal'],
permission: 'contract_manage_reject',
},
{ {
action: 'submitFormal', action: 'submitFormal',
label: '重新提交转正', label: '重新提交转正',
@@ -143,16 +173,18 @@ export const option = createCrudOption([
prop: 'contractNo', prop: 'contractNo',
search: true, search: true,
searchOrder: 6, searchOrder: 6,
searchPlaceholder: '请输入',
order: 185, order: 185,
disabled: true, disabled: true,
placeholder: '不填写,则系统自动生成', placeholder: '不填写,则系统自动生成',
minWidth: 160, minWidth: 200,
}, },
{ {
label: '合同名称', label: '合同名称',
prop: 'contractName', prop: 'contractName',
search: true, search: true,
searchOrder: 7, searchOrder: 7,
searchPlaceholder: '请输入',
order: 180, order: 180,
minWidth: 180, minWidth: 180,
rules: textRule('合同名称', 100, true), rules: textRule('合同名称', 100, true),
@@ -163,6 +195,7 @@ export const option = createCrudOption([
search: true, search: true,
searchOrder: 8, searchOrder: 8,
searchType: 'select', searchType: 'select',
searchPlaceholder: '请选择',
formslot: true, formslot: true,
order: 140, order: 140,
dicUrl: '/blade-transport/project-apply/list?current=1&size=9999', dicUrl: '/blade-transport/project-apply/list?current=1&size=9999',
@@ -187,6 +220,7 @@ export const option = createCrudOption([
search: true, search: true,
searchOrder: 3, searchOrder: 3,
searchType: 'cascader', searchType: 'cascader',
searchPlaceholder: '全部',
formslot: true, formslot: true,
order: 120, order: 120,
dicData: [], dicData: [],
@@ -208,6 +242,7 @@ export const option = createCrudOption([
type: 'select', type: 'select',
search: true, search: true,
searchOrder: 9, searchOrder: 9,
searchPlaceholder: '请选择',
order: 170, order: 170,
dicData: contractCategoryOptions, dicData: contractCategoryOptions,
minWidth: 130, minWidth: 130,
@@ -217,10 +252,8 @@ export const option = createCrudOption([
label: '签约类型', label: '签约类型',
prop: 'signType', prop: 'signType',
type: 'select', type: 'select',
search: true, search: false,
searchOrder: 1,
span: 6, span: 6,
row: true,
order: 190, order: 190,
dicData: signTypeOptions, dicData: signTypeOptions,
minWidth: 120, minWidth: 120,
@@ -232,6 +265,7 @@ export const option = createCrudOption([
type: 'select', type: 'select',
search: true, search: true,
searchOrder: 5, searchOrder: 5,
searchPlaceholder: '请选择',
dicData: effectiveTypeOptions, dicData: effectiveTypeOptions,
hide: true, hide: true,
display: false, display: false,
@@ -241,7 +275,8 @@ export const option = createCrudOption([
prop: 'partyA', prop: 'partyA',
type: 'select', type: 'select',
order: 160, order: 160,
dicUrl: '/blade-transport/customer-archive/list?current=1&size=9999&approvalStatus=approved&status=1', dicUrl:
'/blade-transport/customer-archive/list?current=1&size=9999&customerType=客户&approvalStatus=approved&status=1',
dicFormatter: listDicFormatter, dicFormatter: listDicFormatter,
props: { props: {
label: 'fullName', label: 'fullName',
@@ -256,7 +291,8 @@ export const option = createCrudOption([
prop: 'partyB', prop: 'partyB',
type: 'select', type: 'select',
order: 150, order: 150,
dicUrl: '/blade-transport/customer-archive/list?current=1&size=9999&approvalStatus=approved&status=1', dicUrl:
'/blade-transport/customer-archive/list?current=1&size=9999&customerType=承运商&approvalStatus=approved&status=1',
dicFormatter: listDicFormatter, dicFormatter: listDicFormatter,
props: { props: {
label: 'fullName', label: 'fullName',
@@ -334,14 +370,14 @@ export const option = createCrudOption([
rules: [{ required: true, message: '请选择签订日期', trigger: 'change' }], rules: [{ required: true, message: '请选择签订日期', trigger: 'change' }],
}, },
{ {
label: '结算方式', label: '结算类型',
prop: 'settlementMode', prop: 'settlementMode',
type: 'select', type: 'select',
dicData: settlementModeOptions, dicData: contractSettlementModeOptions,
hide: true, hide: true,
order: 90, order: 90,
minWidth: 120, minWidth: 120,
rules: selectRule('结算方式'), rules: selectRule('结算类型'),
}, },
{ {
label: '合同格式', label: '合同格式',
@@ -392,7 +428,7 @@ export const option = createCrudOption([
type: 'datetime', type: 'datetime',
format: 'YYYY-MM-DD HH:mm:ss', format: 'YYYY-MM-DD HH:mm:ss',
valueFormat: 'YYYY-MM-DD HH:mm:ss', valueFormat: 'YYYY-MM-DD HH:mm:ss',
minWidth: 160, minWidth: 190,
addDisplay: false, addDisplay: false,
editDisplay: false, editDisplay: false,
}, },
@@ -402,6 +438,7 @@ export const option = createCrudOption([
type: 'select', type: 'select',
search: true, search: true,
searchOrder: 2, searchOrder: 2,
searchPlaceholder: '请选择',
slot: true, slot: true,
dicData: contractStageOptions, dicData: contractStageOptions,
minWidth: 120, minWidth: 120,
@@ -414,6 +451,7 @@ export const option = createCrudOption([
type: 'select', type: 'select',
search: true, search: true,
searchOrder: 4, searchOrder: 4,
searchPlaceholder: '请选择',
slot: true, slot: true,
dicData: contractApprovalStatusOptions, dicData: contractApprovalStatusOptions,
minWidth: 130, minWidth: 130,
@@ -572,3 +610,6 @@ export const option = createCrudOption([
option.span = 8; option.span = 8;
option.dialogWidth = 1480; option.dialogWidth = 1480;
option.dialogTop = '10px';
option.dialogCloseBtn = true;
option.menuWidth = 320;

View File

@@ -29,6 +29,14 @@ const userDicFormatter = res => {
})); }));
}; };
const formatUserRealName = (row, role) =>
row[`${role}RealName`] ||
row[`${role}UserRealName`] ||
row[`${role}User`]?.realName ||
row[role]?.realName ||
row[`${role}UserName`] ||
'';
export const config = { export const config = {
title: '项目管理', title: '项目管理',
permission: 'project_apply', permission: 'project_apply',
@@ -94,8 +102,9 @@ export const option = createCrudOption([
label: '立项申请单号', label: '立项申请单号',
prop: 'applyNo', prop: 'applyNo',
search: true, search: true,
searchPlaceholder: '请输入',
searchOrder: 7, searchOrder: 7,
minWidth: 160, minWidth: 220,
addDisplay: false, addDisplay: false,
editDisabled: true, editDisabled: true,
}, },
@@ -110,6 +119,7 @@ export const option = createCrudOption([
label: '项目名称', label: '项目名称',
prop: 'projectName', prop: 'projectName',
search: true, search: true,
searchPlaceholder: '请输入',
searchOrder: 6, searchOrder: 6,
minWidth: 180, minWidth: 180,
maxlength: 50, maxlength: 50,
@@ -128,6 +138,7 @@ export const option = createCrudOption([
prop: 'projectType', prop: 'projectType',
type: 'select', type: 'select',
search: true, search: true,
searchPlaceholder: '请选择',
searchOrder: 3, searchOrder: 3,
dicData: projectTypeOptions, dicData: projectTypeOptions,
minWidth: 120, minWidth: 120,
@@ -156,7 +167,7 @@ export const option = createCrudOption([
label: '承办部门', label: '承办部门',
prop: 'undertakeDeptName', prop: 'undertakeDeptName',
search: true, search: true,
searchLabel: '承办单位', searchPlaceholder: '请输入',
searchOrder: 5, searchOrder: 5,
minWidth: 150, minWidth: 150,
rules: textRule('承办部门', 50, true), rules: textRule('承办部门', 50, true),
@@ -170,6 +181,7 @@ export const option = createCrudOption([
label: '项目负责人', label: '项目负责人',
prop: 'principalUserName', prop: 'principalUserName',
minWidth: 130, minWidth: 130,
formatter: row => formatUserRealName(row, 'principal'),
rules: textRule('项目负责人', 50, true), rules: textRule('项目负责人', 50, true),
}, },
{ {
@@ -177,6 +189,7 @@ export const option = createCrudOption([
prop: 'principalUserId', prop: 'principalUserId',
type: 'select', type: 'select',
search: true, search: true,
searchPlaceholder: '请选择',
searchOrder: 4, searchOrder: 4,
filterable: true, filterable: true,
dicUrl: '/blade-system/user/user-list', dicUrl: '/blade-system/user/user-list',
@@ -193,6 +206,7 @@ export const option = createCrudOption([
prop: 'handlerUserName', prop: 'handlerUserName',
hide: true, hide: true,
minWidth: 130, minWidth: 130,
formatter: row => formatUserRealName(row, 'handler'),
rules: textRule('项目经办人', 50, true), rules: textRule('项目经办人', 50, true),
}, },
{ {
@@ -336,6 +350,7 @@ export const option = createCrudOption([
prop: 'approvalStatus', prop: 'approvalStatus',
type: 'select', type: 'select',
search: true, search: true,
searchPlaceholder: '请选择',
searchOrder: 2, searchOrder: 2,
slot: true, slot: true,
dicData: projectApprovalStatusOptions, dicData: projectApprovalStatusOptions,
@@ -362,6 +377,7 @@ export const option = createCrudOption([
prop: 'effectiveType', prop: 'effectiveType',
type: 'select', type: 'select',
search: true, search: true,
searchPlaceholder: '请选择',
searchOrder: 1, searchOrder: 1,
dicData: effectiveTypeOptions, dicData: effectiveTypeOptions,
value: 'temporary', value: 'temporary',

View File

@@ -28,6 +28,9 @@ export const config = {
exportName: '临时额度申请', exportName: '临时额度申请',
enableAllDept: false, enableAllDept: false,
enableProjectSelect: true, enableProjectSelect: true,
projectQueryParams: {
temporaryCreditLimitSelectable: true,
},
enableAttachmentTable: true, enableAttachmentTable: true,
enableDraftSave: true, enableDraftSave: true,
saveAsDraft: true, saveAsDraft: true,
@@ -108,6 +111,7 @@ export const option = {
value: 'projectName', value: 'projectName',
}, },
filterable: true, filterable: true,
span: 12,
minWidth: 180, minWidth: 180,
rules: textRule('项目名称', 100, true), rules: textRule('项目名称', 100, true),
}, },
@@ -122,14 +126,16 @@ export const option = {
prop: 'projectCode', prop: 'projectCode',
hide: true, hide: true,
disabled: true, disabled: true,
span: 12,
minWidth: 150, minWidth: 150,
rules: textRule('项目编号', 50, true), rules: textRule('项目编号', 50),
}, },
{ {
label: '承办部门', label: '承办部门',
prop: 'undertakeDeptName', prop: 'undertakeDeptName',
hide: true, hide: true,
disabled: true, disabled: true,
span: 12,
minWidth: 150, minWidth: 150,
rules: textRule('承办部门', 50, true), rules: textRule('承办部门', 50, true),
}, },
@@ -138,6 +144,7 @@ export const option = {
prop: 'projectFundLimit', prop: 'projectFundLimit',
hide: true, hide: true,
disabled: true, disabled: true,
span: 12,
minWidth: 200, minWidth: 200,
rules: [nonNegativeRule('项目资金使用额度')], rules: [nonNegativeRule('项目资金使用额度')],
}, },
@@ -146,6 +153,7 @@ export const option = {
prop: 'usedFundLimit', prop: 'usedFundLimit',
hide: true, hide: true,
disabled: true, disabled: true,
span: 12,
minWidth: 220, minWidth: 220,
value: 0, value: 0,
rules: [nonNegativeRule('已使用项目资金额度')], rules: [nonNegativeRule('已使用项目资金额度')],
@@ -155,6 +163,7 @@ export const option = {
prop: 'remainingFundLimit', prop: 'remainingFundLimit',
hide: true, hide: true,
disabled: true, disabled: true,
span: 12,
minWidth: 230, minWidth: 230,
value: 0, value: 0,
rules: [nonNegativeRule('剩余项目资金使用额度')], rules: [nonNegativeRule('剩余项目资金使用额度')],
@@ -163,6 +172,7 @@ export const option = {
label: '申请临时额度(万元)', label: '申请临时额度(万元)',
prop: 'applyLimit', prop: 'applyLimit',
formslot: true, formslot: true,
span: 12,
minWidth: 180, minWidth: 180,
rules: [ rules: [
{ required: true, message: '请输入申请临时额度', trigger: 'blur' }, { required: true, message: '请输入申请临时额度', trigger: 'blur' },
@@ -175,6 +185,7 @@ export const option = {
type: 'date', type: 'date',
format: 'YYYY-MM-DD', format: 'YYYY-MM-DD',
valueFormat: 'YYYY-MM-DD', valueFormat: 'YYYY-MM-DD',
span: 12,
minWidth: 140, minWidth: 140,
rules: [{ required: true, message: '请选择申请有效期至', trigger: 'change' }], rules: [{ required: true, message: '请选择申请有效期至', trigger: 'change' }],
}, },
@@ -260,6 +271,6 @@ export const option = {
}, },
...auditColumns.map(column => ({ ...column, hide: true })), ...auditColumns.map(column => ({ ...column, hide: true })),
]), ]),
dialogWidth: 1480, dialogWidth: 1010,
index: false, index: false,
}; };

View File

@@ -14,23 +14,30 @@ export const option = {
editBtn: false, editBtn: false,
delBtn: false, delBtn: false,
viewBtn: false, viewBtn: false,
menuWidth: 160, menuWidth: 220,
dialogClickModal: false, dialogClickModal: false,
column: [ column: [
{ label: '船舶名', prop: 'shipName', search: true, searchOrder: 5, slot: true, width: 130 }, {
label: '船舶名',
prop: 'shipName',
search: true,
searchOrder: 5,
slot: true,
minWidth: 130,
},
{ {
label: '船舶识别号', label: '船舶识别号',
prop: 'shipIdentifierNo', prop: 'shipIdentifierNo',
search: true, search: true,
searchOrder: 4, searchOrder: 4,
width: 140, minWidth: 140,
}, },
{ {
label: '船检登记号', label: '船检登记号',
prop: 'shipInspectionNo', prop: 'shipInspectionNo',
search: true, search: true,
searchOrder: 3, searchOrder: 3,
width: 130, minWidth: 130,
}, },
{ {
label: '船舶类型', label: '船舶类型',
@@ -44,7 +51,7 @@ export const option = {
{ label: '杂货船', value: '杂货船' }, { label: '杂货船', value: '杂货船' },
{ label: '油船', value: '油船' }, { label: '油船', value: '油船' },
], ],
width: 110, minWidth: 110,
align: 'center', align: 'center',
}, },
{ {
@@ -55,7 +62,7 @@ export const option = {
type: 'select', type: 'select',
filterable: true, filterable: true,
dicData: [], dicData: [],
width: 160, minWidth: 160,
}, },
{ {
label: '状态', label: '状态',
@@ -66,30 +73,36 @@ export const option = {
{ label: '启用', value: 1 }, { label: '启用', value: 1 },
{ label: '停用', value: 2 }, { label: '停用', value: 2 },
], ],
width: 90, minWidth: 90,
align: 'center', align: 'center',
}, },
{ {
label: '国籍证有效期至', label: '国籍证有效期至',
prop: 'nationalityCertEndDate', prop: 'nationalityCertEndDate',
slot: true, slot: true,
width: 150, minWidth: 150,
align: 'center', align: 'center',
}, },
{ {
label: '最低安全配员证书有效期至', label: '最低安全配员证书有效期至',
prop: 'safeManningCertEndDate', prop: 'safeManningCertEndDate',
slot: true, slot: true,
width: 210, minWidth: 210,
align: 'center', align: 'center',
}, },
{ {
label: '营业运输证有效期至', label: '营业运输证有效期至',
prop: 'businessTransportCertEndDate', prop: 'businessTransportCertEndDate',
slot: true, slot: true,
width: 170, minWidth: 170,
align: 'center',
},
{
label: '承租有效期至',
prop: 'leaseEndDate',
slot: true,
minWidth: 140,
align: 'center', align: 'center',
}, },
{ label: '承租有效期至', prop: 'leaseEndDate', slot: true, width: 140, align: 'center' },
], ],
}; };

View File

@@ -14,7 +14,7 @@ export const option = {
editBtn: false, editBtn: false,
delBtn: false, delBtn: false,
viewBtn: false, viewBtn: false,
menuWidth: 160, menuWidth: 220,
dialogClickModal: false, dialogClickModal: false,
column: [ column: [
{ {
@@ -23,7 +23,7 @@ export const option = {
search: true, search: true,
searchOrder: 5, searchOrder: 5,
slot: true, slot: true,
width: 120, minWidth: 120,
}, },
{ {
label: '所属组织', label: '所属组织',
@@ -33,7 +33,7 @@ export const option = {
type: 'select', type: 'select',
filterable: true, filterable: true,
dicData: [], dicData: [],
width: 150, minWidth: 150,
}, },
{ {
label: '业务关系', label: '业务关系',
@@ -47,7 +47,7 @@ export const option = {
{ label: '管理挂靠', value: '管理挂靠' }, { label: '管理挂靠', value: '管理挂靠' },
{ label: '业务合作', value: '业务合作' }, { label: '业务合作', value: '业务合作' },
], ],
width: 120, minWidth: 120,
align: 'center', align: 'center',
}, },
{ {
@@ -65,13 +65,13 @@ export const option = {
{ label: '拖挂车', value: '拖挂' }, { label: '拖挂车', value: '拖挂' },
{ label: '半挂车', value: '半挂' }, { label: '半挂车', value: '半挂' },
], ],
width: 110, minWidth: 110,
align: 'center', align: 'center',
}, },
{ {
label: '核定载质量(KG)', label: '核定载质量(KG)',
prop: 'approvedLoadKg', prop: 'approvedLoadKg',
width: 140, minWidth: 140,
align: 'right', align: 'right',
}, },
{ {
@@ -85,28 +85,28 @@ export const option = {
{ label: '启用', value: 1 }, { label: '启用', value: 1 },
{ label: '停用', value: 2 }, { label: '停用', value: 2 },
], ],
width: 90, minWidth: 90,
align: 'center', align: 'center',
}, },
{ {
label: '行驶证有效期', label: '行驶证有效期',
prop: 'drivingLicenseEndDate', prop: 'drivingLicenseEndDate',
slot: true, slot: true,
width: 140, minWidth: 140,
align: 'center', align: 'center',
}, },
{ {
label: '道路运输证有效期', label: '道路运输证有效期',
prop: 'roadTransportCertEndDate', prop: 'roadTransportCertEndDate',
slot: true, slot: true,
width: 160, minWidth: 160,
align: 'center', align: 'center',
}, },
{ {
label: '年审有效期', label: '年审有效期',
prop: 'annualReviewEndDate', prop: 'annualReviewEndDate',
slot: true, slot: true,
width: 130, minWidth: 130,
align: 'center', align: 'center',
}, },
], ],

View File

@@ -366,15 +366,6 @@ export default {
addDisplay: false, addDisplay: false,
editDisplay: false, editDisplay: false,
}, },
{
label: '行政区划编号',
prop: 'regionCode',
minWidth: 160,
overHidden: false,
addDisplay: false,
editDisplay: false,
rules: [{ max: 32, message: '行政区划编码不能超过32字', trigger: 'blur' }],
},
{ {
label: '详细地址', label: '详细地址',
prop: 'detailAddress', prop: 'detailAddress',
@@ -409,6 +400,15 @@ export default {
{ validator: validateLatitude, trigger: 'blur' }, { validator: validateLatitude, trigger: 'blur' },
], ],
}, },
{
label: '行政区划编号',
prop: 'regionCode',
minWidth: 160,
overHidden: false,
addDisplay: false,
editDisplay: false,
rules: [{ max: 32, message: '行政区划编码不能超过32字', trigger: 'blur' }],
},
{ {
label: '数据来源', label: '数据来源',
prop: 'dataSource', prop: 'dataSource',
@@ -425,23 +425,6 @@ export default {
{ label: '手动录入', value: '手动录入' }, { label: '手动录入', value: '手动录入' },
], ],
}, },
{
label: '启停状态',
prop: 'status',
type: 'select',
search: true,
slot: true,
dataType: 'number',
minWidth: 110,
overHidden: false,
addDisplay: false,
editDisplay: false,
dicData: [
{ label: '全部', value: '' },
{ label: '启用', value: 1 },
{ label: '停用', value: 2 },
],
},
{ {
label: '备注', label: '备注',
prop: 'remark', prop: 'remark',
@@ -483,6 +466,23 @@ export default {
display: false, display: false,
minWidth: 160, minWidth: 160,
}, },
{
label: '启停状态',
prop: 'status',
type: 'select',
search: true,
slot: true,
dataType: 'number',
minWidth: 110,
overHidden: false,
addDisplay: false,
editDisplay: false,
dicData: [
{ label: '全部', value: '' },
{ label: '启用', value: 1 },
{ label: '停用', value: 2 },
],
},
], ],
}, },
excelOption: { excelOption: {
@@ -869,17 +869,12 @@ export default {
type: 'warning', type: 'warning',
}).then(() => { }).then(() => {
NProgress.start(); NProgress.start();
exportBlob( exportBlob('/blade-system/airport-master/export-airport-master', this.buildExportParams(), {
'/blade-system/airport-master/export-airport-master', feedback: true,
this.buildExportParams(), })
{ feedback: true }
)
.then(async res => { .then(async res => {
const blob = await addExcelRequiredHeaderMarks(res.data); const blob = await addExcelRequiredHeaderMarks(res.data);
downloadXls( downloadXls(blob, `空港机场主数据${this.$dayjs().format('YYYY-MM-DD HH:mm:ss')}.xlsx`);
blob,
`空港机场主数据${this.$dayjs().format('YYYY-MM-DD HH:mm:ss')}.xlsx`
);
}) })
.finally(() => { .finally(() => {
NProgress.done(); NProgress.done();

View File

@@ -229,7 +229,15 @@ import 'nprogress/nprogress.css';
const DEFAULT_COUNTRY_CODE = '+86'; const DEFAULT_COUNTRY_CODE = '+86';
const EXCLUDED_EXCEL_HEADERS = ['数据来源', '启停状态']; const EXCLUDED_EXCEL_HEADERS = ['数据来源', '启停状态'];
const REQUIRED_EXPORT_HEADERS = ['港口码头名称', '港口/码头名称', '类型', '国家', '城市', '经度', '纬度']; const REQUIRED_EXPORT_HEADERS = [
'港口码头名称',
'港口/码头名称',
'类型',
'国家',
'城市',
'经度',
'纬度',
];
const getExcelCellText = value => { const getExcelCellText = value => {
if (value === undefined || value === null) return ''; if (value === undefined || value === null) return '';
@@ -256,9 +264,7 @@ const removeWorkbookColumnsByHeaders = workbook => {
} }
}); });
if (indexes.length) { if (indexes.length) {
indexes indexes.sort((a, b) => b - a).forEach(colNumber => worksheet.spliceColumns(colNumber, 1));
.sort((a, b) => b - a)
.forEach(colNumber => worksheet.spliceColumns(colNumber, 1));
break; break;
} }
} }
@@ -383,13 +389,42 @@ export default {
dialogClickModal: false, dialogClickModal: false,
menuWidth: 160, menuWidth: 160,
column: [ column: [
{
label: '编码',
prop: 'code',
search: true,
formslot: true,
width: 140,
span: 12,
order: 70,
placeholder: '请输入编码',
rules: [
{ required: true, message: '请输入编码', trigger: 'blur' },
{ validator: validateCode, trigger: 'blur' },
],
change: ({ value }) => this.handleCodeChange(value),
blur: ({ value }) => this.handleCodeChange(value),
},
{
label: '港口/码头名称',
prop: 'name',
minWidth: 140,
search: true,
searchOrder: 7,
span: 12,
order: 80,
placeholder: '请输入',
rules: [{ required: true, message: '请输入港口/码头名称', trigger: 'blur' }],
},
{ {
label: '类型', label: '类型',
prop: 'category', prop: 'category',
type: 'select', type: 'select',
search: true, search: true,
searchOrder: 6,
formslot: true, formslot: true,
span: 24, span: 24,
order: 100,
dicData: [ dicData: [
{ label: '全部', value: '' }, { label: '全部', value: '' },
{ label: '港口', value: '港口' }, { label: '港口', value: '港口' },
@@ -409,6 +444,7 @@ export default {
dicData: [], dicData: [],
hide: true, hide: true,
span: 12, span: 12,
order: 90,
placeholder: '请选择', placeholder: '请选择',
rules: [{ required: false, message: '请选择上级港口', trigger: 'change' }], rules: [{ required: false, message: '请选择上级港口', trigger: 'change' }],
change: ({ value }) => this.handleParentChange(value), change: ({ value }) => this.handleParentChange(value),
@@ -416,7 +452,7 @@ export default {
{ {
label: '上级港口', label: '上级港口',
prop: 'parentName', prop: 'parentName',
minWidth: 120, minWidth: 140,
slot: true, slot: true,
addDisplay: false, addDisplay: false,
editDisplay: false, editDisplay: false,
@@ -432,39 +468,17 @@ export default {
placeholder: '自动带出', placeholder: '自动带出',
rules: [{ required: false, message: '请选择上级港口', trigger: 'change' }], rules: [{ required: false, message: '请选择上级港口', trigger: 'change' }],
}, },
{
label: '港口/码头名称',
prop: 'name',
minWidth: 140,
search: true,
span: 12,
placeholder: '请输入',
rules: [{ required: true, message: '请输入港口/码头名称', trigger: 'blur' }],
},
{
label: '编码',
prop: 'code',
search: true,
formslot: true,
width: 140,
span: 12,
placeholder: '请输入编码',
rules: [
{ required: true, message: '请输入编码', trigger: 'blur' },
{ validator: validateCode, trigger: 'blur' },
],
change: ({ value }) => this.handleCodeChange(value),
blur: ({ value }) => this.handleCodeChange(value),
},
{ {
label: '国家', label: '国家',
prop: 'country', prop: 'country',
type: 'select', type: 'select',
search: true, search: true,
searchOrder: 5,
formslot: true, formslot: true,
filterable: true, filterable: true,
dicData: [], dicData: [],
span: 12, span: 12,
order: 60,
minWidth: 140, minWidth: 140,
overHidden: false, overHidden: false,
placeholder: '自动带出', placeholder: '自动带出',
@@ -478,6 +492,7 @@ export default {
formslot: true, formslot: true,
hide: true, hide: true,
span: 12, span: 12,
order: 50,
placeholder: '自动带出', placeholder: '自动带出',
rules: [{ required: true, message: '请选择所属城市', trigger: 'change' }], rules: [{ required: true, message: '请选择所属城市', trigger: 'change' }],
}, },
@@ -486,6 +501,7 @@ export default {
prop: 'city', prop: 'city',
type: 'select', type: 'select',
search: true, search: true,
searchOrder: 4,
filterable: true, filterable: true,
dicData: [], dicData: [],
addDisplay: false, addDisplay: false,
@@ -513,14 +529,44 @@ export default {
prop: 'districtName', prop: 'districtName',
type: 'select', type: 'select',
search: true, search: true,
searchOrder: 3,
filterable: true, filterable: true,
dicData: [], dicData: [],
minWidth: 100, minWidth: 100,
hide: true,
addDisplay: false, addDisplay: false,
editDisplay: false, editDisplay: false,
}, },
{ {
label: '行政区划编号', label: '经度',
prop: 'longitude',
formslot: true,
minWidth: 130,
span: 12,
order: 20,
placeholder: '请输入',
overHidden: false,
rules: [
{ required: true, message: '请输入经度', trigger: 'blur' },
{ validator: validateLongitude, trigger: 'blur' },
],
},
{
label: '纬度',
prop: 'latitude',
formslot: true,
minWidth: 130,
span: 12,
order: 10,
placeholder: '请输入',
overHidden: false,
rules: [
{ required: true, message: '请输入纬度', trigger: 'blur' },
{ validator: validateLatitude, trigger: 'blur' },
],
},
{
label: '行政区划编码',
prop: 'regionCode', prop: 'regionCode',
minWidth: 140, minWidth: 140,
span: 12, span: 12,
@@ -538,42 +584,18 @@ export default {
span: 24, span: 24,
minWidth: 220, minWidth: 220,
overHidden: false, overHidden: false,
order: 30,
placeholder: '请输入', placeholder: '请输入',
maxlength: 255, maxlength: 255,
showWordLimit: true, showWordLimit: true,
rules: [{ max: 255, message: '详细地址不能超过255字', trigger: 'blur' }], rules: [{ max: 255, message: '详细地址不能超过255字', trigger: 'blur' }],
}, },
{
label: '经度',
prop: 'longitude',
formslot: true,
minWidth: 130,
span: 12,
placeholder: '请输入',
overHidden: false,
rules: [
{ required: true, message: '请输入经度', trigger: 'blur' },
{ validator: validateLongitude, trigger: 'blur' },
],
},
{
label: '纬度',
prop: 'latitude',
formslot: true,
minWidth: 130,
span: 12,
placeholder: '请输入',
overHidden: false,
rules: [
{ required: true, message: '请输入纬度', trigger: 'blur' },
{ validator: validateLatitude, trigger: 'blur' },
],
},
{ {
label: '数据来源', label: '数据来源',
prop: 'dataSource', prop: 'dataSource',
type: 'select', type: 'select',
search: true, search: true,
searchOrder: 2,
minWidth: 120, minWidth: 120,
overHidden: false, overHidden: false,
addDisplay: false, addDisplay: false,
@@ -585,23 +607,6 @@ export default {
{ label: '手工导入', value: '手工导入' }, { label: '手工导入', value: '手工导入' },
], ],
}, },
{
label: '启停状态',
prop: 'status',
type: 'select',
search: true,
slot: true,
dataType: 'number',
minWidth: 110,
overHidden: false,
addDisplay: false,
editDisplay: false,
dicData: [
{ label: '全部', value: '' },
{ label: '启用', value: 1 },
{ label: '停用', value: 2 },
],
},
{ {
label: '备注', label: '备注',
prop: 'remark', prop: 'remark',
@@ -639,9 +644,26 @@ export default {
valueFormat: 'YYYY-MM-DD HH:mm:ss', valueFormat: 'YYYY-MM-DD HH:mm:ss',
addDisplay: false, addDisplay: false,
editDisplay: false, editDisplay: false,
display: false,
minWidth: 160, minWidth: 160,
}, },
{
label: '状态',
prop: 'status',
type: 'select',
search: true,
searchOrder: 1,
slot: true,
dataType: 'number',
minWidth: 110,
overHidden: false,
addDisplay: false,
editDisplay: false,
dicData: [
{ label: '全部', value: '' },
{ label: '启用', value: 1 },
{ label: '停用', value: 2 },
],
},
], ],
}, },
excelOption: { excelOption: {
@@ -1270,19 +1292,14 @@ export default {
type: 'warning', type: 'warning',
}).then(() => { }).then(() => {
NProgress.start(); NProgress.start();
exportBlob( exportBlob('/blade-system/port-terminal/export-port-terminal', this.buildExportParams(), {
'/blade-system/port-terminal/export-port-terminal', feedback: true,
this.buildExportParams(), })
{ feedback: true }
)
.then(async res => { .then(async res => {
const blob = await removeExcelColumnsByHeaders(res.data, { const blob = await removeExcelColumnsByHeaders(res.data, {
markRequiredHeaders: true, markRequiredHeaders: true,
}); });
downloadXls( downloadXls(blob, `港口码头主数据${this.$dayjs().format('YYYY-MM-DD HH:mm:ss')}.xlsx`);
blob,
`港口码头主数据${this.$dayjs().format('YYYY-MM-DD HH:mm:ss')}.xlsx`
);
}) })
.finally(() => { .finally(() => {
NProgress.done(); NProgress.done();

View File

@@ -335,15 +335,6 @@ export default {
addDisplay: false, addDisplay: false,
editDisplay: false, editDisplay: false,
}, },
{
label: '行政区划编号',
prop: 'regionCode',
minWidth: 160,
overHidden: false,
addDisplay: false,
editDisplay: false,
rules: [{ max: 32, message: '行政区划编码不能超过32字', trigger: 'blur' }],
},
{ {
label: '详细地址', label: '详细地址',
prop: 'detailAddress', prop: 'detailAddress',
@@ -373,20 +364,13 @@ export default {
rules: [{ validator: validateLatitude, trigger: 'blur' }], rules: [{ validator: validateLatitude, trigger: 'blur' }],
}, },
{ {
label: '数据来源', label: '行政区划编号',
prop: 'dataSource', prop: 'regionCode',
type: 'select', minWidth: 160,
search: true,
minWidth: 120,
overHidden: false, overHidden: false,
addDisplay: false, addDisplay: false,
editDisplay: false, editDisplay: false,
dicData: [ rules: [{ max: 32, message: '行政区划编码不能超过32字', trigger: 'blur' }],
{ label: '全部', value: '' },
{ label: '初始化导入', value: '初始化导入' },
{ label: '批量', value: '批量' },
{ label: '手动', value: '手动' },
],
}, },
{ {
label: '启停状态', label: '启停状态',
@@ -446,6 +430,22 @@ export default {
display: false, display: false,
minWidth: 160, minWidth: 160,
}, },
{
label: '数据来源',
prop: 'dataSource',
type: 'select',
search: true,
minWidth: 120,
overHidden: false,
addDisplay: false,
editDisplay: false,
dicData: [
{ label: '全部', value: '' },
{ label: '初始化导入', value: '初始化导入' },
{ label: '批量', value: '批量' },
{ label: '手动', value: '手动' },
],
},
], ],
}, },
excelOption: { excelOption: {

View File

@@ -11,6 +11,37 @@
</el-col> </el-col>
<el-col class="region-page__form-panel" :span="18"> <el-col class="region-page__form-panel" :span="18">
<basic-container> <basic-container>
<el-form class="region-page__search" :model="searchForm" inline>
<el-form-item label="区域编码">
<el-input v-model="searchForm.code" clearable placeholder="请输入区域编码" />
</el-form-item>
<el-form-item label="区域名称">
<el-input v-model="searchForm.name" clearable placeholder="请输入区域名称" />
</el-form-item>
<el-form-item label="区域层级">
<el-select v-model="searchForm.regionLevel" clearable placeholder="请选择区域层级">
<el-option label="省份" :value="1" />
<el-option label="省市" :value="2" />
<el-option label="区县" :value="3" />
</el-select>
</el-form-item>
<el-form-item label="数据来源">
<el-select v-model="searchForm.dataSource" clearable placeholder="请选择数据来源">
<el-option label="手动录入" value="手动录入" />
<el-option label="初始化导入" value="初始化导入" />
</el-select>
</el-form-item>
<el-form-item label="启停状态">
<el-select v-model="searchForm.status" clearable placeholder="请选择启停状态">
<el-option label="启用" :value="1" />
<el-option label="停用" :value="2" />
</el-select>
</el-form-item>
<el-form-item class="region-page__search-actions">
<el-button type="primary" @click="handleSearch">搜索</el-button>
<el-button @click="handleSearchReset">清空</el-button>
</el-form-item>
</el-form>
<el-button-group> <el-button-group>
<el-button <el-button
v-if="permission.region_add" v-if="permission.region_add"
@@ -136,14 +167,21 @@ export default {
treeCode: '', treeCode: '',
treeParentCode: '', treeParentCode: '',
treeData: [], treeData: [],
searchForm: {
code: '',
name: '',
regionLevel: undefined,
dataSource: '',
status: undefined,
},
parentOptions: [], parentOptions: [],
syncLoading: false, syncLoading: false,
treeOption: { treeOption: {
nodeKey: 'id', nodeKey: 'id',
lazy: true, lazy: true,
treeLoad: function (node, resolve) { treeLoad: (node, resolve) => {
const parentCode = node.level === 0 ? ROOT_PARENT_CODE : node.data.id; const parentCode = node.level === 0 ? ROOT_PARENT_CODE : node.data.id;
getLazyTree(parentCode).then(res => { getLazyTree(parentCode, this.searchForm).then(res => {
resolve( resolve(
res.data.data.map(item => { res.data.data.map(item => {
return { return {
@@ -402,7 +440,7 @@ export default {
methods: { methods: {
initTree() { initTree() {
this.treeData = []; this.treeData = [];
getLazyTree(this.topCode).then(res => { getLazyTree(this.topCode, this.searchForm).then(res => {
this.treeData = res.data.data.map(item => { this.treeData = res.data.data.map(item => {
return { return {
...item, ...item,
@@ -411,6 +449,20 @@ export default {
}); });
}); });
}, },
handleSearch() {
this.initTree();
this.regionForm = {};
},
handleSearchReset() {
this.searchForm = {
code: '',
name: '',
regionLevel: undefined,
dataSource: '',
status: undefined,
};
this.handleSearch();
},
nodeClick(data) { nodeClick(data) {
this.setRegionColumnDisplay('code', true); this.setRegionColumnDisplay('code', true);
this.treeCode = data.id; this.treeCode = data.id;
@@ -624,20 +676,18 @@ export default {
type: 'warning', type: 'warning',
}).then(() => { }).then(() => {
NProgress.start(); NProgress.start();
exportBlob( exportBlob(`/blade-system/region/export-region?${this.website.tokenHeader}=${getToken()}`, {
`/blade-system/region/export-region?${this.website.tokenHeader}=${getToken()}`, feedback: true,
{ feedback: true } }).then(res => {
).then(res => {
downloadXls(res.data, `行政区划数据${this.$dayjs().format('YYYY-MM-DD HH:mm:ss')}.xlsx`); downloadXls(res.data, `行政区划数据${this.$dayjs().format('YYYY-MM-DD HH:mm:ss')}.xlsx`);
NProgress.done(); NProgress.done();
}); });
}); });
}, },
handleTemplate() { handleTemplate() {
exportBlob( exportBlob(`/blade-system/region/export-template?${this.website.tokenHeader}=${getToken()}`, {
`/blade-system/region/export-template?${this.website.tokenHeader}=${getToken()}`, feedback: true,
{ feedback: true } }).then(res => {
).then(res => {
downloadXls(res.data, '行政区划模板.xlsx'); downloadXls(res.data, '行政区划模板.xlsx');
}); });
}, },

View File

@@ -18,6 +18,7 @@
@current-change="currentChange" @current-change="currentChange"
@size-change="sizeChange" @size-change="sizeChange"
@refresh-change="refreshChange" @refresh-change="refreshChange"
@search-icon-change="handleSearchIconChange"
@on-load="onLoad" @on-load="onLoad"
> >
<template #menu-left> <template #menu-left>
@@ -113,11 +114,35 @@
<template #contractCategory-label>合同类型</template> <template #contractCategory-label>合同类型</template>
<template v-if="config.enableContractExpiryTags" #search-menu>
<div v-show="searchExpanded" class="business-crud-page__contract-expiry-search">
<span class="business-crud-page__contract-expiry-label">签约类型:</span>
<el-select v-model="contractSignType" clearable placeholder="请选择">
<el-option
v-for="item in contractSignTypeOptions"
:key="item.value"
:label="item.label"
:value="item.value"
/>
</el-select>
<div class="business-crud-page__contract-expiry-tags">
<el-check-tag
v-for="item in contractExpiryTagOptions"
:key="item.value"
:checked="contractExpiryScope === item.value"
@change="handleContractExpiryScopeChange(item.value)"
>
{{ item.label }}{{ contractExpiryStats[item.statKey] || 0 }}
</el-check-tag>
</div>
</div>
</template>
<template #projectName-form> <template #projectName-form>
<el-select <el-select
v-model="selectedProjectId" v-model="selectedProjectId"
class="business-crud-page__field" class="business-crud-page__field"
placeholder="请选择项目名称" :placeholder="config.projectPlaceholder || '请选择项目名称'"
filterable filterable
clearable clearable
:loading="projectLoading" :loading="projectLoading"
@@ -1220,7 +1245,7 @@
<el-cascader <el-cascader
v-model="selectedOrganizationId" v-model="selectedOrganizationId"
class="business-crud-page__field" class="business-crud-page__field"
placeholder="请选择所属组织" :placeholder="config.organizationPlaceholder || '请选择所属组织'"
:options="organizationOptions" :options="organizationOptions"
:props="organizationCascaderProps" :props="organizationCascaderProps"
clearable clearable
@@ -1401,12 +1426,17 @@
</el-radio-group> </el-radio-group>
</div> </div>
<el-form <el-form
v-if="settlementRuleEnabled"
:model="settlementRuleForm" :model="settlementRuleForm"
label-position="right" label-position="right"
label-width="auto" label-width="auto"
class="business-crud-page__module-form business-crud-page__module-form--three" class="business-crud-page__module-form business-crud-page__module-form--three"
> >
<el-form-item label="账单起始日期" required> <el-form-item
label="账单起始日期"
required
:error="settlementRuleErrors.billStartDate"
>
<el-date-picker <el-date-picker
v-model="settlementRuleForm.billStartDate" v-model="settlementRuleForm.billStartDate"
type="date" type="date"
@@ -1414,9 +1444,10 @@
format="YYYY-MM-DD" format="YYYY-MM-DD"
value-format="YYYY-MM-DD" value-format="YYYY-MM-DD"
:disabled="dialogReadonly" :disabled="dialogReadonly"
@change="clearSettlementRuleError('billStartDate')"
/> />
</el-form-item> </el-form-item>
<el-form-item label="结算类型" required> <el-form-item label="结算类型" required :error="settlementRuleErrors.settlementType">
<el-select <el-select
v-model="settlementRuleForm.settlementType" v-model="settlementRuleForm.settlementType"
placeholder="请选择结算类型" placeholder="请选择结算类型"
@@ -1431,7 +1462,12 @@
/> />
</el-select> </el-select>
</el-form-item> </el-form-item>
<el-form-item v-if="showSettlementBillCycleType" label="账单周期类型" required> <el-form-item
v-if="showSettlementBillCycleType"
label="账单周期类型"
required
:error="settlementRuleErrors.billCycleType"
>
<el-select <el-select
v-model="settlementRuleForm.billCycleType" v-model="settlementRuleForm.billCycleType"
placeholder="请选择账单周期类型" placeholder="请选择账单周期类型"
@@ -1446,11 +1482,17 @@
/> />
</el-select> </el-select>
</el-form-item> </el-form-item>
<el-form-item v-if="showSettlementBillCutoffDay" label="账单截单日"> <el-form-item
v-if="showSettlementBillCutoffDay"
label="账单截单日"
required
:error="settlementRuleErrors.billCutoffDay"
>
<el-select <el-select
v-model="settlementRuleForm.billCutoffDay" v-model="settlementRuleForm.billCutoffDay"
placeholder="请选择账单截单日" placeholder="请选择账单截单日"
:disabled="dialogReadonly" :disabled="dialogReadonly"
@change="clearSettlementRuleError('billCutoffDay')"
> >
<el-option <el-option
v-for="item in billCutoffDayOptions" v-for="item in billCutoffDayOptions"
@@ -1460,11 +1502,17 @@
/> />
</el-select> </el-select>
</el-form-item> </el-form-item>
<el-form-item v-if="showSettlementCycleDays" label="周期天数" required> <el-form-item
v-if="showSettlementCycleDays"
label="周期天数"
required
:error="settlementRuleErrors.cycleDays"
>
<el-select <el-select
v-model="settlementRuleForm.cycleDays" v-model="settlementRuleForm.cycleDays"
placeholder="请选择周期天数" placeholder="请选择周期天数"
:disabled="dialogReadonly" :disabled="dialogReadonly"
@change="clearSettlementRuleError('cycleDays')"
> >
<el-option <el-option
v-for="item in cycleDayOptions" v-for="item in cycleDayOptions"
@@ -2798,7 +2846,7 @@
:title="billingPlanMode === 'edit' ? '编辑计费方案' : '添加计费方案'" :title="billingPlanMode === 'edit' ? '编辑计费方案' : '添加计费方案'"
append-to-body append-to-body
v-model="billingPlanBox" v-model="billingPlanBox"
width="96%" width="90%"
class="business-crud-page__billing-dialog" class="business-crud-page__billing-dialog"
> >
<el-form <el-form
@@ -2825,6 +2873,7 @@
<el-form-item label="备注"> <el-form-item label="备注">
<el-input <el-input
v-model="billingPlanForm.remark" v-model="billingPlanForm.remark"
class="business-crud-page__billing-plan-remark"
maxlength="100" maxlength="100"
show-word-limit show-word-limit
:disabled="dialogReadonly" :disabled="dialogReadonly"
@@ -3956,6 +4005,7 @@ export default {
cargoImportLoading: false, cargoImportLoading: false,
billingPlanRows: [], billingPlanRows: [],
settlementRuleForm: defaultSettlementRule(), settlementRuleForm: defaultSettlementRule(),
settlementRuleErrors: {},
reconciliationForm: defaultReconciliation(), reconciliationForm: defaultReconciliation(),
changeRecordRows: [], changeRecordRows: [],
billingPlanBox: false, billingPlanBox: false,
@@ -4033,9 +4083,23 @@ export default {
total: 0, total: 0,
}, },
selectionList: [], selectionList: [],
contractExpiryScope: '',
contractSignType: '',
searchExpanded: false,
attachmentUploadMode: false,
contractExpiryStats: {
all: 0,
within30: 0,
within90: 0,
over90: 0,
expired: 0,
},
}; };
}, },
created() { created() {
if (this.config.enableAllDept && this.isAdmin) {
this.allDept = 1;
}
if (this.config.enableProjectSelect) { if (this.config.enableProjectSelect) {
this.loadProjectOptions(); this.loadProjectOptions();
} }
@@ -4074,6 +4138,18 @@ export default {
actionButtonLikeSearch() { actionButtonLikeSearch() {
return this.config.actionButtonLikeSearch === true; return this.config.actionButtonLikeSearch === true;
}, },
contractSignTypeOptions() {
return this.option.column?.find(column => column.prop === 'signType')?.dicData || [];
},
contractExpiryTagOptions() {
return [
{ label: '全部', value: '', statKey: 'all' },
{ label: '30天内到期', value: 'within30', statKey: 'within30' },
{ label: '90天内到期', value: 'within90', statKey: 'within90' },
{ label: '90天以上', value: 'over90', statKey: 'over90' },
{ label: '已到期', value: 'expired', statKey: 'expired' },
];
},
contractSelectEnabled() { contractSelectEnabled() {
return this.config.enableContractSelect || this.config.enableTransportPlanForm; return this.config.enableContractSelect || this.config.enableTransportPlanForm;
}, },
@@ -4240,7 +4316,9 @@ export default {
return this.isAdmin; return this.isAdmin;
}, },
readonlyScope() { readonlyScope() {
return this.config.enableAllDept && this.allDept === 1; return (
this.config.enableAllDept && this.config.allDeptReadonly !== false && this.allDept === 1
);
}, },
canCreate() { canCreate() {
return this.hasPermission(`${this.config.permission}_add`) && !this.readonlyScope; return this.hasPermission(`${this.config.permission}_add`) && !this.readonlyScope;
@@ -4299,6 +4377,9 @@ export default {
emitPath: true, emitPath: true,
}; };
}, },
settlementRuleEnabled() {
return Number(this.settlementRuleForm.autoGenerate) === 1;
},
showSettlementBillCycleType() { showSettlementBillCycleType() {
return this.settlementRuleForm.settlementType === '月结'; return this.settlementRuleForm.settlementType === '月结';
}, },
@@ -4535,6 +4616,7 @@ export default {
return; return;
} }
if (action.action === 'edit' || action.action === 'attachmentUpload') { if (action.action === 'edit' || action.action === 'attachmentUpload') {
this.attachmentUploadMode = action.action === 'attachmentUpload';
this.$refs.crud.rowEdit(row, index); this.$refs.crud.rowEdit(row, index);
return; return;
} }
@@ -4712,6 +4794,7 @@ export default {
return rule.settlementType === '固定天数周期结算'; return rule.settlementType === '固定天数周期结算';
}, },
handleSettlementTypeChange(value) { handleSettlementTypeChange(value) {
this.clearSettlementRuleError('settlementType');
if (value === '月结') { if (value === '月结') {
if (!this.settlementRuleForm.billCycleType) { if (!this.settlementRuleForm.billCycleType) {
this.settlementRuleForm.billCycleType = '固定截单日'; this.settlementRuleForm.billCycleType = '固定截单日';
@@ -4729,6 +4812,7 @@ export default {
} }
}, },
handleSettlementBillCycleTypeChange(value) { handleSettlementBillCycleTypeChange(value) {
this.clearSettlementRuleError('billCycleType');
if (value === '固定截单日') { if (value === '固定截单日') {
this.settlementRuleForm.billCutoffDay = this.settlementRuleForm.billCutoffDay || 25; this.settlementRuleForm.billCutoffDay = this.settlementRuleForm.billCutoffDay || 25;
return; return;
@@ -4751,23 +4835,40 @@ export default {
} }
return nextRule; return nextRule;
}, },
clearSettlementRuleError(field) {
if (!this.settlementRuleErrors[field]) return;
this.settlementRuleErrors = {
...this.settlementRuleErrors,
[field]: '',
};
},
validateSettlementRule() { validateSettlementRule() {
const rule = this.normalizeSettlementRule(this.settlementRuleForm); const rule = this.normalizeSettlementRule(this.settlementRuleForm);
this.settlementRuleErrors = {};
if (this.isBillingFieldEmpty(rule.billStartDate)) { if (this.isBillingFieldEmpty(rule.billStartDate)) {
this.settlementRuleErrors = { billStartDate: '请选择账单起始日期' };
this.$message.warning('请选择账单起始日期'); this.$message.warning('请选择账单起始日期');
return false; return false;
} }
if (this.isBillingFieldEmpty(rule.settlementType)) { if (this.isBillingFieldEmpty(rule.settlementType)) {
this.settlementRuleErrors = { settlementType: '请选择结算类型' };
this.$message.warning('请选择结算类型'); this.$message.warning('请选择结算类型');
return false; return false;
} }
if (this.isMonthlySettlement(rule) && this.isBillingFieldEmpty(rule.billCycleType)) { if (this.isMonthlySettlement(rule) && this.isBillingFieldEmpty(rule.billCycleType)) {
this.settlementRuleErrors = { billCycleType: '请选择账单周期类型' };
this.$message.warning('请选择账单周期类型'); this.$message.warning('请选择账单周期类型');
return false; return false;
} }
if (this.isFixedBillCutoffSettlement(rule)) { if (this.isFixedBillCutoffSettlement(rule)) {
if (this.isBillingFieldEmpty(rule.billCutoffDay)) {
this.settlementRuleErrors = { billCutoffDay: '请选择账单截单日' };
this.$message.warning('请选择账单截单日');
return false;
}
const billCutoffDay = Number(rule.billCutoffDay); const billCutoffDay = Number(rule.billCutoffDay);
if (!Number.isInteger(billCutoffDay) || billCutoffDay < 1 || billCutoffDay > 31) { if (!Number.isInteger(billCutoffDay) || billCutoffDay < 1 || billCutoffDay > 31) {
this.settlementRuleErrors = { billCutoffDay: '账单截单日必须为1~31的整数' };
this.$message.warning('账单截单日必须为1~31的整数'); this.$message.warning('账单截单日必须为1~31的整数');
return false; return false;
} }
@@ -4775,13 +4876,14 @@ export default {
if (this.isFixedCycleSettlement(rule)) { if (this.isFixedCycleSettlement(rule)) {
const cycleDays = Number(rule.cycleDays); const cycleDays = Number(rule.cycleDays);
if (!Number.isInteger(cycleDays) || cycleDays < 1 || cycleDays > 365) { if (!Number.isInteger(cycleDays) || cycleDays < 1 || cycleDays > 365) {
this.settlementRuleErrors = { cycleDays: '周期天数必须为1~365的整数' };
this.$message.warning('周期天数必须为1~365的整数'); this.$message.warning('周期天数必须为1~365的整数');
return false; return false;
} }
} }
return true; return true;
}, },
normalizeRow(row) { normalizeRow(row, saveAsDraft = false) {
const submitRow = { ...row }; const submitRow = { ...row };
if (this.config.enableTransportPlanForm) { if (this.config.enableTransportPlanForm) {
if (!this.validateTransportPlanForm(submitRow)) return false; if (!this.validateTransportPlanForm(submitRow)) return false;
@@ -4801,7 +4903,7 @@ export default {
submitRow.billingPlanJson = JSON.stringify(this.billingPlanRows); submitRow.billingPlanJson = JSON.stringify(this.billingPlanRows);
} }
if (this.config.enableSettlementRule) { if (this.config.enableSettlementRule) {
if (!this.validateSettlementRule()) return false; if (this.settlementRuleEnabled && !this.validateSettlementRule()) return false;
submitRow.settlementRuleJson = JSON.stringify(this.normalizeSettlementRule()); submitRow.settlementRuleJson = JSON.stringify(this.normalizeSettlementRule());
} }
if (this.config.enableReconciliation) { if (this.config.enableReconciliation) {
@@ -4810,7 +4912,7 @@ export default {
if (this.config.enableChangeRecord) { if (this.config.enableChangeRecord) {
submitRow.changeRecordJson = JSON.stringify(this.changeRecordRows); submitRow.changeRecordJson = JSON.stringify(this.changeRecordRows);
} }
if (this.config.saveAsDraft) { if (this.config.saveAsDraft && saveAsDraft) {
submitRow.approvalStatus = 'draft'; submitRow.approvalStatus = 'draft';
} }
delete submitRow.basicInfoTitle; delete submitRow.basicInfoTitle;
@@ -4894,22 +4996,25 @@ export default {
this.stopSubmitLoading(loading); this.stopSubmitLoading(loading);
return; return;
} }
this.getSaveRequest()(submitRow).then( const request =
this.attachmentUploadMode && typeof this.api.updateAttachments === 'function'
? this.api.updateAttachments
: this.getSaveRequest();
request(submitRow).then(
() => { () => {
this.attachmentUploadMode = false;
this.onLoad(this.page); this.onLoad(this.page);
this.$message({ type: 'success', message: '操作成功!' }); this.$message({ type: 'success', message: '操作成功!' });
done(); done();
}, },
error => { error => {
this.attachmentUploadMode = false;
window.console.log(error); window.console.log(error);
this.stopSubmitLoading(loading); this.stopSubmitLoading(loading);
} }
); );
}, },
getSaveRequest() { getSaveRequest() {
if (this.config.saveAsDraft && typeof this.api.saveDraft === 'function') {
return this.api.saveDraft;
}
return this.api.submit; return this.api.submit;
}, },
buildContractCreateSubmitRow(action) { buildContractCreateSubmitRow(action) {
@@ -5031,7 +5136,7 @@ export default {
handleDraftSave() { handleDraftSave() {
const request = const request =
typeof this.api.saveDraft === 'function' ? this.api.saveDraft : this.api.submit; typeof this.api.saveDraft === 'function' ? this.api.saveDraft : this.api.submit;
const submitRow = this.normalizeRow(this.form); const submitRow = this.normalizeRow(this.form, true);
if (!submitRow) return; if (!submitRow) return;
this.draftSaveLoading = true; this.draftSaveLoading = true;
request(submitRow) request(submitRow)
@@ -5094,6 +5199,7 @@ export default {
}, },
beforeOpen(done, type) { beforeOpen(done, type) {
this.crudDialogType = type; this.crudDialogType = type;
this.settlementRuleErrors = {};
this.syncCustomCreateDialogButtons(type); this.syncCustomCreateDialogButtons(type);
this.dialogReadonly = type === 'view'; this.dialogReadonly = type === 'view';
this.syncContractNoDisabled(type); this.syncContractNoDisabled(type);
@@ -5175,8 +5281,7 @@ export default {
this.syncCurrentOrganizationOption(); this.syncCurrentOrganizationOption();
} }
if (this.config.enableProjectSelect) { if (this.config.enableProjectSelect) {
this.form.usedFundLimit = 0; this.form.remainingFundLimit = this.calculateRemainingFundLimit();
this.form.remainingFundLimit = 0;
this.syncCurrentProjectOption(); this.syncCurrentProjectOption();
} }
if (this.contractSelectEnabled) { if (this.contractSelectEnabled) {
@@ -5223,7 +5328,7 @@ export default {
loadProjectOptions() { loadProjectOptions() {
if (!this.config.enableProjectSelect || this.projectLoading) return; if (!this.config.enableProjectSelect || this.projectLoading) return;
this.projectLoading = true; this.projectLoading = true;
getProjectList(1, 9999, {}) getProjectList(1, 9999, this.config.projectQueryParams || {})
.then(res => { .then(res => {
this.projectOptions = extractRecords(res); this.projectOptions = extractRecords(res);
this.syncCurrentProjectOption(); this.syncCurrentProjectOption();
@@ -5247,6 +5352,12 @@ export default {
}); });
} }
}, },
calculateRemainingFundLimit() {
const projectFundLimit = Number(this.form.projectFundLimit);
const usedFundLimit = Number(this.form.usedFundLimit);
if (!Number.isFinite(projectFundLimit) || !Number.isFinite(usedFundLimit)) return '';
return Math.round((projectFundLimit - usedFundLimit + Number.EPSILON) * 100) / 100;
},
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) {
@@ -5274,7 +5385,7 @@ export default {
this.form.undertakeDeptName = project.undertakeDeptName || ''; this.form.undertakeDeptName = project.undertakeDeptName || '';
this.form.projectFundLimit = project.fundLimit || project.projectFundLimit || 0; this.form.projectFundLimit = project.fundLimit || project.projectFundLimit || 0;
this.form.usedFundLimit = 0; this.form.usedFundLimit = 0;
this.form.remainingFundLimit = 0; this.form.remainingFundLimit = this.calculateRemainingFundLimit();
if (this.contractSelectEnabled || this.config.enableTransportPlanForm) { if (this.contractSelectEnabled || this.config.enableTransportPlanForm) {
this.form.customerName = this.form.customerName =
project.customerName || project.customerName ||
@@ -7616,11 +7727,16 @@ export default {
}, },
searchReset() { searchReset() {
this.query = {}; this.query = {};
this.contractExpiryScope = '';
this.contractSignType = '';
this.page.currentPage = 1; this.page.currentPage = 1;
this.onLoad(this.page); this.onLoad(this.page);
}, },
searchChange(params, done) { searchChange(params, done) {
this.query = params; this.query = {
...params,
...(this.contractSignType ? { signType: this.contractSignType } : {}),
};
this.page.currentPage = 1; this.page.currentPage = 1;
this.onLoad(this.page, params); this.onLoad(this.page, params);
done(); done();
@@ -7643,6 +7759,25 @@ export default {
refreshChange() { refreshChange() {
this.onLoad(this.page, this.query); this.onLoad(this.page, this.query);
}, },
handleSearchIconChange(expanded) {
this.searchExpanded = expanded;
},
handleContractExpiryScopeChange(expireScope) {
this.contractExpiryScope = expireScope;
this.page.currentPage = 1;
this.onLoad(this.page, this.query);
},
refreshContractExpiryStats(query) {
if (!this.config.enableContractExpiryTags || typeof this.api.expireStats !== 'function') return;
const statsQuery = { ...query };
delete statsQuery.expireScope;
this.api.expireStats(statsQuery).then(res => {
this.contractExpiryStats = {
...this.contractExpiryStats,
...(res.data.data || {}),
};
});
},
handleScopeChange() { handleScopeChange() {
if (!this.canViewAllDept) { if (!this.canViewAllDept) {
this.allDept = 0; this.allDept = 0;
@@ -7699,7 +7834,13 @@ export default {
}, },
onLoad(page, params = {}) { onLoad(page, params = {}) {
this.loading = true; this.loading = true;
const query = this.normalizeQueryParams({ ...params, ...this.query, allDept: this.allDept }); const query = this.normalizeQueryParams({
...params,
...this.query,
allDept: this.allDept,
...(this.contractSignType ? { signType: this.contractSignType } : {}),
...(this.contractExpiryScope ? { expireScope: this.contractExpiryScope } : {}),
});
this.api this.api
.getList(page.currentPage, page.pageSize, query) .getList(page.currentPage, page.pageSize, query)
.then(res => { .then(res => {
@@ -7707,6 +7848,7 @@ export default {
this.page.total = result.total; this.page.total = result.total;
this.data = result.records; this.data = result.records;
this.selectionClear(); this.selectionClear();
this.refreshContractExpiryStats(query);
}) })
.finally(() => { .finally(() => {
this.loading = false; this.loading = false;
@@ -8610,6 +8752,38 @@ export default {
width: 100%; width: 100%;
} }
&__contract-expiry-search {
display: flex;
order: -1;
align-items: center;
gap: 8px;
margin-right: auto;
min-width: 0;
:deep(.el-select) {
width: 160px;
}
}
&__contract-expiry-label {
flex: 0 0 auto;
min-width: 140px;
color: #606266;
text-align: right;
white-space: nowrap;
}
&__contract-expiry-tags {
display: flex;
flex-wrap: wrap;
gap: 8px;
:deep(.el-check-tag) {
margin: 0;
white-space: nowrap;
}
}
.dialog-section-title { .dialog-section-title {
display: flex; display: flex;
align-items: center; align-items: center;
@@ -9404,6 +9578,10 @@ export default {
margin-bottom: 18px; margin-bottom: 18px;
} }
&__billing-plan-remark {
width: 80%;
}
&__billing-rule-head { &__billing-rule-head {
display: flex; display: flex;
align-items: center; align-items: center;

View File

@@ -174,21 +174,16 @@
</el-select> </el-select>
</el-form-item> </el-form-item>
<el-form-item label="承办部门" prop="undertakeDeptId"> <el-form-item label="承办部门" prop="undertakeDeptId">
<el-select <el-cascader
v-model="form.undertakeDeptId" v-model="form.undertakeDeptId"
placeholder="请选择承办部门" placeholder="请选择承办部门"
filterable
clearable clearable
filterable
:options="deptTreeOptions"
:props="deptCascaderProps"
:disabled="isBasicInfoReadonly" :disabled="isBasicInfoReadonly"
@change="handleUndertakeDeptChange" @change="handleUndertakeDeptChange"
> />
<el-option
v-for="item in deptOptions"
:key="item.value"
:label="item.label"
:value="item.value"
/>
</el-select>
</el-form-item> </el-form-item>
<el-form-item label="项目由来" prop="projectSource"> <el-form-item label="项目由来" prop="projectSource">
<el-select <el-select
@@ -215,9 +210,11 @@
<el-form-item label="客户名称" prop="customerNames"> <el-form-item label="客户名称" prop="customerNames">
<el-select <el-select
v-model="selectedCustomerId" v-model="selectedCustomerId"
placeholder="搜索选择" placeholder="支持多选,选择后下方列表展示"
multiple
filterable filterable
clearable clearable
collapse-tags
:loading="customerLoading" :loading="customerLoading"
@visible-change="visible => visible && loadCustomerOptions('客户')" @visible-change="visible => visible && loadCustomerOptions('客户')"
@change="handleCustomerChange" @change="handleCustomerChange"
@@ -255,6 +252,7 @@
v-model="form.fundLimit" v-model="form.fundLimit"
placeholder="实际业务回款周期内付款额度" placeholder="实际业务回款周期内付款额度"
@input="value => handleNumberInput('fundLimit', value)" @input="value => handleNumberInput('fundLimit', value)"
@blur="formatAmountField('fundLimit')"
/> />
</el-form-item> </el-form-item>
<el-form-item label="项目应收账款额度万元" prop="receivableLimit"> <el-form-item label="项目应收账款额度万元" prop="receivableLimit">
@@ -262,6 +260,7 @@
v-model="form.receivableLimit" v-model="form.receivableLimit"
placeholder="回款期内最大应收账款额度" placeholder="回款期内最大应收账款额度"
@input="value => handleNumberInput('receivableLimit', value)" @input="value => handleNumberInput('receivableLimit', value)"
@blur="formatAmountField('receivableLimit')"
/> />
</el-form-item> </el-form-item>
<el-form-item label="应收账款回款期限" prop="receivableDays"> <el-form-item label="应收账款回款期限" prop="receivableDays">
@@ -464,65 +463,75 @@
</el-form-item> </el-form-item>
</div> </div>
<div class="project-apply-form__material-head"> <div class="project-apply-form__material-card">
<div class="dialog-section-title project-apply-form__material-title">项目材料</div> <div class="project-apply-form__material-head">
<el-button type="primary" :disabled="!attachmentRows.length" @click="handleBatchDownload" <div class="dialog-section-title project-apply-form__material-title">项目材料</div>
>批量下载</el-button <el-button
> type="primary"
</div> :disabled="!attachmentRows.length"
<el-table :data="attachmentRows" border class="project-apply-form__table"> @click="handleBatchDownload"
<el-table-column type="index" label="序号" width="80" align="center" /> >批量下载</el-button
<el-table-column label="附件类型" min-width="160" align="center"> >
<template #default="{ row }"> </div>
<el-select v-model="row.fileType" placeholder="请选择" :disabled="dialogReadonly"> <el-alert
<el-option v-if="dialogReadonly && missingRequiredAttachmentTypes.length"
v-for="item in projectAttachmentTypeOptions" class="project-apply-form__material-warning"
:key="item.value" type="warning"
:label="item.label" :closable="false"
:value="item.value" show-icon
:title="`缺少必需材料:${missingRequiredAttachmentTypes.join('、')}`"
/>
<el-table :data="attachmentRows" border class="project-apply-form__material-table">
<el-table-column type="index" label="序号" width="80" align="center" />
<el-table-column label="附件类型" min-width="160" align="center">
<template #default="{ row }">
<el-select v-model="row.fileType" placeholder="请选择" :disabled="dialogReadonly">
<el-option
v-for="item in projectAttachmentTypeOptions"
:key="item.value"
:label="item.label"
:value="item.value"
/>
</el-select>
</template>
</el-table-column>
<el-table-column label="文件名" min-width="220" align="center" show-overflow-tooltip>
<template #default="{ row }">{{ row.originalName || row.name }}</template>
</el-table-column>
<el-table-column label="附件描述" min-width="240" align="center">
<template #default="{ row }">
<el-input
v-model="row.description"
placeholder="请输入附件描述"
:disabled="dialogReadonly"
/> />
</el-select> </template>
</template> </el-table-column>
</el-table-column> <el-table-column label="文件大小" min-width="120" align="center">
<el-table-column label="文件名" min-width="220" align="center" show-overflow-tooltip> <template #default="{ row }">{{ formatFileSize(row.size) }}</template>
<template #default="{ row }">{{ row.originalName || row.name }}</template> </el-table-column>
</el-table-column> <el-table-column prop="uploadUserName" label="上传人" min-width="140" align="center" />
<el-table-column label="附件描述" min-width="240" align="center"> <el-table-column prop="uploadTime" label="上传时间" min-width="170" align="center" />
<template #default="{ row }"> <el-table-column label="操作" width="110" align="center">
<el-input <template #default="{ row, $index }">
v-model="row.description" <el-link type="primary" @click="downloadAttachment(row)">下载</el-link>
placeholder="请输入附件描述" <el-link v-if="!dialogReadonly" type="primary" @click="removeAttachment($index)"
:disabled="dialogReadonly" >删除</el-link
/> >
</template> </template>
</el-table-column> </el-table-column>
<el-table-column label="文件大小" min-width="120" align="center"> </el-table>
<template #default="{ row }">{{ formatFileSize(row.size) }}</template> <vehicle-attachment-upload
</el-table-column> v-model="attachmentRows"
<el-table-column prop="uploadUserName" label="上传人" min-width="140" align="center" /> class="project-apply-form__upload"
<el-table-column prop="uploadTime" label="上传时间" min-width="170" align="center" /> :readonly="dialogReadonly"
<el-table-column label="操作" width="110" align="center"> :file-types="attachmentFileTypes"
<template #default="{ row, $index }"> :max-size="50"
<el-link type="primary" @click="downloadAttachment(row)">下载</el-link> button-text="上传附件"
<el-link tip="支持pdfbmpjpegpngjpgdocdocxpptpptxxlsxxlsemlmsgzip的文件格式单个文件大小限制50M"
v-if="!dialogReadonly" @change="handleAttachmentChange"
type="primary" />
@click="removeAttachment($index)" </div>
>删除</el-link
>
</template>
</el-table-column>
</el-table>
<vehicle-attachment-upload
v-model="attachmentRows"
class="project-apply-form__upload"
:readonly="dialogReadonly"
:file-types="attachmentFileTypes"
:max-size="500"
button-text="上传附件"
tip="支持pdfbmpjpegpngjpgdocdocxpptpptxxlsxxlsemlmsgzip的文件格式单个文件大小限制500M"
@change="handleAttachmentChange"
/>
<div v-if="!isChangeDialog" class="dialog-section-title project-apply-form__table-title"> <div v-if="!isChangeDialog" class="dialog-section-title project-apply-form__table-title">
变更记录 变更记录
@@ -593,16 +602,24 @@
:class="{ 'project-apply-dialog__footer--change': isChangeDialog }" :class="{ 'project-apply-dialog__footer--change': isChangeDialog }"
> >
<template v-if="isChangeDialog"> <template v-if="isChangeDialog">
<el-button type="primary" :loading="submitLoading" @click="submitChangeProject">
提交
</el-button>
<el-button type="primary" :loading="submitLoading" @click="saveChangeProject"> <el-button type="primary" :loading="submitLoading" @click="saveChangeProject">
保存 保存
</el-button> </el-button>
<el-button @click="projectBox = false">关闭</el-button> <el-button type="primary" :loading="submitLoading" @click="submitChangeProject">
提交
</el-button>
<el-button @click="handleCancelProject">取消</el-button>
</template> </template>
<template v-else> <template v-else>
<el-button @click="projectBox = false">取消</el-button> <el-button @click="handleCancelProject">取消</el-button>
<el-button
v-if="!dialogReadonly"
type="primary"
:loading="submitLoading"
@click="saveProject"
>
保存
</el-button>
<el-button <el-button
v-if="!dialogReadonly" v-if="!dialogReadonly"
type="primary" type="primary"
@@ -743,6 +760,7 @@ const changeTypeOptions = [
]; ];
const normalProjectAttachmentTypeOptions = [ const normalProjectAttachmentTypeOptions = [
'项目立项报告',
'利润测算表', '利润测算表',
'单一来源供应商申请表/三方比价表', '单一来源供应商申请表/三方比价表',
'合同模板', '合同模板',
@@ -752,6 +770,7 @@ const normalProjectAttachmentTypeOptions = [
].map(item => ({ label: item, value: item })); ].map(item => ({ label: item, value: item }));
const majorProjectAttachmentTypeOptions = [ const majorProjectAttachmentTypeOptions = [
'项目立项报告',
'会议纪要', '会议纪要',
'评审表决票', '评审表决票',
'利润测算表', '利润测算表',
@@ -873,6 +892,8 @@ export default {
fundDemand: [{ label: '资金需求', validator: validateAmount, trigger: 'blur' }], fundDemand: [{ label: '资金需求', validator: validateAmount, trigger: 'blur' }],
handlerUserId: [ handlerUserId: [
{ {
required: true,
message: '请选择项目经办人',
label: '项目经办人', label: '项目经办人',
nameProp: 'handlerUserName', nameProp: 'handlerUserName',
validator: validateUser, validator: validateUser,
@@ -881,6 +902,8 @@ export default {
], ],
principalUserId: [ principalUserId: [
{ {
required: true,
message: '请选择项目负责人',
label: '项目负责人', label: '项目负责人',
nameProp: 'principalUserName', nameProp: 'principalUserName',
validator: validateUser, validator: validateUser,
@@ -897,8 +920,16 @@ export default {
], ],
}, },
deptOptions: [], deptOptions: [],
deptTreeOptions: [],
deptCascaderProps: {
label: 'label',
value: 'value',
children: 'children',
emitPath: false,
checkStrictly: true,
},
cargoTypeOptions: [], cargoTypeOptions: [],
selectedCustomerId: '', selectedCustomerId: [],
selectedCarrierIds: [], selectedCarrierIds: [],
customerOptions: [], customerOptions: [],
carrierOptions: [], carrierOptions: [],
@@ -991,6 +1022,12 @@ export default {
? majorProjectAttachmentTypeOptions ? majorProjectAttachmentTypeOptions
: normalProjectAttachmentTypeOptions; : normalProjectAttachmentTypeOptions;
}, },
missingRequiredAttachmentTypes() {
const uploadedTypes = new Set(this.attachmentRows.map(item => item.fileType).filter(Boolean));
return this.projectAttachmentTypeOptions
.map(item => item.value)
.filter(fileType => !uploadedTypes.has(fileType));
},
}, },
created() { created() {
this.loadDeptOptions(); this.loadDeptOptions();
@@ -1231,7 +1268,7 @@ export default {
if (type === 'add' || type === 'majorSupplement') { if (type === 'add' || type === 'majorSupplement') {
this.applyProjectDetail({ this.applyProjectDetail({
...emptyForm(), ...emptyForm(),
projectType: type === 'majorSupplement' ? '重大项目' : '', projectType: type === 'majorSupplement' ? '重大项目' : '普通项目',
}); });
this.fillDefaultUsers(); this.fillDefaultUsers();
return; return;
@@ -1251,21 +1288,35 @@ export default {
this.carrierRows = []; this.carrierRows = [];
this.attachmentRows = []; this.attachmentRows = [];
this.changeRows = []; this.changeRows = [];
this.selectedCustomerId = ''; this.selectedCustomerId = [];
this.selectedCarrierIds = []; this.selectedCarrierIds = [];
}, },
applyProjectDetail(row) { applyProjectDetail(row) {
const displayRow = {
...row,
fundLimit: this.formatAmount(row.fundLimit),
receivableLimit: this.formatAmount(row.receivableLimit),
...(this.dialogReadonly
? {
projectScale: this.normalizeReadonlyAmount(row.projectScale),
estimatedProfit: this.normalizeReadonlyAmount(row.estimatedProfit),
fundDemand: this.normalizeReadonlyAmount(row.fundDemand),
}
: {}),
};
const form = { const form = {
...emptyForm(), ...emptyForm(),
...row, ...displayRow,
handlerUserName: this.resolveUserRealName(displayRow, 'handler'),
principalUserName: this.resolveUserRealName(displayRow, 'principal'),
businessDateRange: businessDateRange:
row.businessStartDate && row.businessEndDate displayRow.businessStartDate && displayRow.businessEndDate
? [row.businessStartDate, row.businessEndDate] ? [displayRow.businessStartDate, displayRow.businessEndDate]
: [], : [],
}; };
const situation = this.parseSituation(row.situationRemark); const situation = this.parseSituation(displayRow.situationRemark);
const customerRows = this.parseJsonArray(row.customerJson); const customerRows = this.parseJsonArray(displayRow.customerJson);
const carrierRows = this.parseJsonArray(row.carrierJson); const carrierRows = this.parseJsonArray(displayRow.carrierJson);
this.form = { this.form = {
...form, ...form,
...situation, ...situation,
@@ -1274,9 +1325,11 @@ export default {
}; };
this.customerRows = customerRows; this.customerRows = customerRows;
this.carrierRows = carrierRows; this.carrierRows = carrierRows;
this.attachmentRows = this.parseJsonArray(row.attachmentsJson); this.attachmentRows = this.parseJsonArray(displayRow.attachmentsJson);
this.changeRows = this.buildChangeRows(row); this.changeRows = this.buildChangeRows(displayRow);
this.selectedCustomerId = this.getCustomerRowId(this.customerRows[0]); this.selectedCustomerId = this.customerRows
.map(item => this.getCustomerRowId(item))
.filter(Boolean);
this.selectedCarrierIds = this.carrierRows this.selectedCarrierIds = this.carrierRows
.map(item => this.getCustomerRowId(item)) .map(item => this.getCustomerRowId(item))
.filter(Boolean); .filter(Boolean);
@@ -1285,7 +1338,13 @@ export default {
fillDefaultUsers() { fillDefaultUsers() {
const userId = this.userInfo?.userId || this.userInfo?.id || ''; const userId = this.userInfo?.userId || this.userInfo?.id || '';
const userName = const userName =
this.userInfo?.realName || this.userInfo?.userName || this.userInfo?.account || ''; this.userInfo?.realName ||
this.userInfo?.real_name ||
this.userInfo?.name ||
this.userInfo?.userName ||
this.userInfo?.user_name ||
this.userInfo?.account ||
'';
if (userId && userName) { if (userId && userName) {
this.form.handlerUserId = userId; this.form.handlerUserId = userId;
this.form.handlerUserName = userName; this.form.handlerUserName = userName;
@@ -1293,6 +1352,16 @@ export default {
this.form.principalUserName = userName; this.form.principalUserName = userName;
} }
}, },
resolveUserRealName(row = {}, role) {
return (
row[`${role}RealName`] ||
row[`${role}UserRealName`] ||
row[`${role}User`]?.realName ||
row[role]?.realName ||
row[`${role}UserName`] ||
''
);
},
resolveChangeType(row = {}) { resolveChangeType(row = {}) {
if (row.changeType) return row.changeType; if (row.changeType) return row.changeType;
const node = row.currentNode || ''; const node = row.currentNode || '';
@@ -1328,6 +1397,36 @@ export default {
}); });
}); });
}, },
saveProject() {
this.submitLoading = true;
this.api
.saveDraft(this.normalizeSubmitForm())
.then(res => {
if (res.data?.success === false || res.data?.data === false) {
this.$message.error(res.data?.msg || '保存失败,请联系管理员');
return;
}
this.$message.success('保存成功!');
this.projectBox = false;
this.onLoad(this.page, this.query);
})
.finally(() => {
this.submitLoading = false;
});
},
handleCancelProject() {
if (!['add', 'majorSupplement', 'change'].includes(this.dialogType)) {
this.projectBox = false;
return;
}
this.$confirm('确认后直接关闭弹窗,列表无数据变更,是否继续?', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning',
}).then(() => {
this.projectBox = false;
});
},
saveChangeProject() { saveChangeProject() {
this.submitChangeForm(false); this.submitChangeForm(false);
}, },
@@ -1364,6 +1463,8 @@ export default {
const [businessStartDate, businessEndDate] = this.form.businessDateRange || []; const [businessStartDate, businessEndDate] = this.form.businessDateRange || [];
const submitRow = { const submitRow = {
...this.form, ...this.form,
fundLimit: this.formatAmount(this.form.fundLimit),
receivableLimit: this.formatAmount(this.form.receivableLimit),
businessStartDate, businessStartDate,
businessEndDate, businessEndDate,
customerJson: JSON.stringify(this.customerRows), customerJson: JSON.stringify(this.customerRows),
@@ -1477,6 +1578,11 @@ export default {
}) })
.filter(Boolean); .filter(Boolean);
}, },
normalizeReadonlyAmount(value) {
return value === null || value === undefined || value === '' || Number(value) === -1
? ''
: value;
},
mergeCustomerOptions(options = [], selectedOptions = []) { mergeCustomerOptions(options = [], selectedOptions = []) {
const result = [...selectedOptions]; const result = [...selectedOptions];
options.forEach(option => { options.forEach(option => {
@@ -1505,7 +1611,7 @@ export default {
changeType: changeType:
row.changeType || row.changeType ||
(String(row.currentNode || '').includes('备案') ? '项目备案调整' : '项目变更'), (String(row.currentNode || '').includes('备案') ? '项目备案调整' : '项目变更'),
handler: row.handlerUserName || '', handler: this.resolveUserRealName(row, 'handler'),
content: row.changeContent || '', content: row.changeContent || '',
reason: row.changeReason || '', reason: row.changeReason || '',
status: row.approvalStatusName || row.approvalStatus || '', status: row.approvalStatusName || row.approvalStatus || '',
@@ -1529,9 +1635,18 @@ export default {
}, },
loadDeptOptions() { loadDeptOptions() {
getDeptTree().then(res => { getDeptTree().then(res => {
this.deptOptions = this.flattenDept(res.data.data || []); const deptTree = res.data.data || [];
this.deptOptions = this.flattenDept(deptTree);
this.deptTreeOptions = this.toDeptCascaderOptions(deptTree);
}); });
}, },
toDeptCascaderOptions(list = []) {
return list.map(item => ({
label: item.title || item.deptName || item.name,
value: item.id,
children: item.children?.length ? this.toDeptCascaderOptions(item.children) : undefined,
}));
},
flattenDept(list = [], level = 0) { flattenDept(list = [], level = 0) {
return list.reduce((result, item) => { return list.reduce((result, item) => {
const label = item.title || item.deptName || item.name; const label = item.title || item.deptName || item.name;
@@ -1594,16 +1709,16 @@ export default {
raw: item, raw: item,
}; };
}, },
handleCustomerChange(id) { handleCustomerChange(ids) {
if (!id) { if (!ids || !ids.length) {
this.customerRows = []; this.customerRows = [];
this.form.customerNames = ''; this.form.customerNames = '';
this.form.customerJson = ''; this.form.customerJson = '';
return; return;
} }
this.loadCustomerDetailRow(id, 'customer').then(row => { Promise.all(ids.map(id => this.loadCustomerDetailRow(id, 'customer'))).then(rows => {
this.customerRows = [row]; this.customerRows = rows;
this.form.customerNames = row.customer; this.form.customerNames = rows.map(item => item.customer).join('、');
this.form.customerJson = JSON.stringify(this.customerRows); this.form.customerJson = JSON.stringify(this.customerRows);
this.syncSelectedCustomerOptions(); this.syncSelectedCustomerOptions();
this.$refs.projectForm?.validateField('customerNames'); this.$refs.projectForm?.validateField('customerNames');
@@ -1656,6 +1771,14 @@ export default {
this.form[prop] = this.form[prop] =
parts.length > 1 ? `${parts[0]}.${parts.slice(1).join('').slice(0, 2)}` : parts[0]; parts.length > 1 ? `${parts[0]}.${parts.slice(1).join('').slice(0, 2)}` : parts[0];
}, },
formatAmount(value) {
if (value === null || value === undefined || value === '' || Number(value) === -1) return '';
const amount = Number(value);
return Number.isFinite(amount) ? amount.toFixed(2) : value;
},
formatAmountField(prop) {
this.form[prop] = this.formatAmount(this.form[prop]);
},
handleIntegerInput(prop, value) { handleIntegerInput(prop, value) {
this.form[prop] = String(value || '').replace(/[^\d]/g, ''); this.form[prop] = String(value || '').replace(/[^\d]/g, '');
}, },
@@ -1836,13 +1959,36 @@ export default {
display: flex; display: flex;
align-items: center; align-items: center;
justify-content: space-between; justify-content: space-between;
margin-bottom: 12px;
}
&__material-card {
margin-bottom: 16px; margin-bottom: 16px;
background: #fff; background: #fff;
border-radius: 6px; border-radius: 6px;
padding: 12px 16px; padding: 14px 16px;
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.04); box-shadow: 0 1px 2px rgba(0, 0, 0, 0.04);
} }
&__material-table {
width: 100%;
margin-bottom: 12px;
:deep(.el-table__header th) {
background: #f5f7fa;
color: #303133;
font-weight: 600;
}
:deep(.el-table__cell) {
border-color: #eff1f7;
}
}
&__material-warning {
margin-bottom: 12px;
}
&__material-title { &__material-title {
flex: 1; flex: 1;
} }

View File

@@ -283,43 +283,62 @@
</el-col> </el-col>
<el-col :span="8"> <el-col :span="8">
<el-form-item label="得分率(%)" required> <el-form-item label="得分率(%)" required>
<div class="range-input"> <div class="range-input standard-range-input">
<el-input-number <el-input
v-model="standardForm.scoreRateLower" v-model="standardForm.scoreRateLower"
:min="0" inputmode="numeric"
:max="100" @input="value => handleStandardNumberInput('scoreRateLower', value, 0, 100)"
:precision="0"
/> />
<span></span> <span></span>
<el-input-number <el-input
v-model="standardForm.scoreRateUpper" v-model="standardForm.scoreRateUpper"
:min="0" inputmode="numeric"
:max="100" @input="value => handleStandardNumberInput('scoreRateUpper', value, 0, 100)"
:precision="0"
/> />
</div> </div>
</el-form-item> </el-form-item>
</el-col> </el-col>
<el-col :span="8">
<el-form-item label="得分率每增加" required>
<el-input-number v-model="standardForm.scoreRateIncrease" :min="0" :precision="0" />
</el-form-item>
</el-col>
</el-row> </el-row>
<el-row :gutter="20"> <el-row :gutter="20">
<el-col :span="8"> <el-col :span="8">
<el-form-item label="最大资金使用额度(万元)" required> <el-form-item label="得分率每增加" required>
<div class="range-input"> <el-input
<el-input-number v-model="standardForm.creditLimitLower" :min="0" :precision="2" /> v-model="standardForm.scoreRateIncrease"
<span></span> inputmode="numeric"
<el-input-number v-model="standardForm.creditLimitUpper" :min="0" :precision="2" /> @input="value => handleStandardNumberInput('scoreRateIncrease', value)"
</div> >
<template #suffix>%</template>
</el-input>
</el-form-item> </el-form-item>
</el-col> </el-col>
<el-col :span="8"> <el-col :span="6">
<el-form-item label="额度增加" required> <el-form-item label="额度增加" required>
<el-input-number v-model="standardForm.creditLimitIncrease" :min="0" :precision="2" /> <el-input
<span class="score-unit">万元</span> v-model="standardForm.creditLimitIncrease"
inputmode="decimal"
@input="value => handleStandardNumberInput('creditLimitIncrease', value, 2)"
>
<template #suffix>万元</template>
</el-input>
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="20">
<el-col :span="12">
<el-form-item label="最大资金使用额度(万元)" required>
<div class="range-input standard-range-input">
<el-input
v-model="standardForm.creditLimitLower"
inputmode="decimal"
@input="value => handleStandardNumberInput('creditLimitLower', value, 2)"
/>
<span></span>
<el-input
v-model="standardForm.creditLimitUpper"
inputmode="decimal"
@input="value => handleStandardNumberInput('creditLimitUpper', value, 2)"
/>
</div>
</el-form-item> </el-form-item>
</el-col> </el-col>
</el-row> </el-row>
@@ -657,6 +676,18 @@ export default {
this.standardForm.creditLevel = ''; this.standardForm.creditLevel = '';
} }
}, },
handleStandardNumberInput(field, value, precision = 0, max) {
let normalized = String(value || '').replace(precision > 0 ? /[^\d.]/g : /\D/g, '');
if (precision > 0) {
const [integer = '', ...decimalParts] = normalized.split('.');
const decimal = decimalParts.join('').slice(0, precision);
normalized = decimalParts.length ? `${integer}.${decimal}` : integer;
}
if (max !== undefined && normalized !== '' && Number(normalized) > max) {
normalized = String(max);
}
this.standardForm[field] = normalized;
},
isValidCreditLevel(creditLevel) { isValidCreditLevel(creditLevel) {
return this.creditLevelOptions.some(item => item.value === String(creditLevel || '').trim()); return this.creditLevelOptions.some(item => item.value === String(creditLevel || '').trim());
}, },
@@ -698,6 +729,16 @@ 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();
[
'scoreRateLower',
'scoreRateUpper',
'scoreRateIncrease',
'creditLimitLower',
'creditLimitUpper',
'creditLimitIncrease',
].forEach(field => {
standard[field] = Number(standard[field]);
});
const nextStandards = JSON.parse(JSON.stringify(this.configForm.standards)); const nextStandards = JSON.parse(JSON.stringify(this.configForm.standards));
if (this.currentStandardIndex >= 0) { if (this.currentStandardIndex >= 0) {
nextStandards.splice(this.currentStandardIndex, 1, standard); nextStandards.splice(this.currentStandardIndex, 1, standard);
@@ -1128,6 +1169,17 @@ export default {
gap: 10px; gap: 10px;
} }
.standard-range-input {
width: 100%;
min-width: 0;
:deep(.el-input) {
flex: 1;
width: 0;
min-width: 0;
}
}
.score-unit { .score-unit {
margin-left: 8px; margin-left: 8px;
color: #606266; color: #606266;

View File

@@ -206,7 +206,8 @@
<el-form-item label="客商类型" prop="customerType"> <el-form-item label="客商类型" prop="customerType">
<el-select <el-select
v-model="archiveForm.customerType" v-model="archiveForm.customerType"
placeholder="请输入" multiple
placeholder="请选择"
filterable filterable
clearable clearable
> >
@@ -237,7 +238,11 @@
</el-form-item> </el-form-item>
</el-col> </el-col>
<el-col :span="6"> <el-col :span="6">
<el-form-item label="营业期限" prop="businessEndDate" required> <el-form-item
label="营业期限"
prop="businessEndDate"
:required="archiveForm.businessTermType === '固定期限'"
>
<div class="business-term-field"> <div class="business-term-field">
<el-date-picker <el-date-picker
v-model="archiveForm.businessEndDate" v-model="archiveForm.businessEndDate"
@@ -1691,7 +1696,7 @@ export default {
deptId: [], deptId: [],
deptIds: '', deptIds: '',
deptName: '', deptName: '',
customerType: '', customerType: [],
businessScope: [], businessScope: [],
businessTermType: '固定期限', businessTermType: '固定期限',
registeredRegionPath: [], registeredRegionPath: [],
@@ -2020,7 +2025,12 @@ export default {
if (checked) { if (checked) {
this.archiveForm.businessEndDate = ''; this.archiveForm.businessEndDate = '';
} }
this.$refs.archiveForm?.validateField('businessEndDate'); this.$nextTick(() => {
this.$refs.archiveForm?.clearValidate('businessEndDate');
if (!checked) {
this.$refs.archiveForm?.validateField('businessEndDate');
}
});
}, },
handleBusinessEndDateChange(value) { handleBusinessEndDateChange(value) {
if (value) { if (value) {
@@ -2635,7 +2645,9 @@ export default {
...this.emptyArchive(), ...this.emptyArchive(),
...detail, ...detail,
deptId: this.normalizeDeptIds(detail), deptId: this.normalizeDeptIds(detail),
customerType: detail.customerType || '', customerType: this.splitValue(detail.customerType),
invoiceTaxRate: this.normalizeOptionalAmount(detail.invoiceTaxRate),
registeredCapital: this.normalizeOptionalAmount(detail.registeredCapital),
businessScope, businessScope,
businessTermType: detail.businessTermType || '固定期限', businessTermType: detail.businessTermType || '固定期限',
registeredRegionPath: Array.isArray(detail.registeredRegionPath) registeredRegionPath: Array.isArray(detail.registeredRegionPath)
@@ -2674,10 +2686,15 @@ export default {
attachments: [], attachments: [],
details: [], details: [],
...score, ...score,
tempApplyCreditLimit: this.normalizeOptionalAmount(score.tempApplyCreditLimit),
attachments: score.attachments || this.parseAttachments(score.proofAttachments), attachments: score.attachments || this.parseAttachments(score.proofAttachments),
details: score.details || [], details: score.details || [],
}; };
}, },
normalizeOptionalAmount(value) {
const amount = Number(value);
return Number.isFinite(amount) && amount < 0 ? '' : value ?? '';
},
getScoreTimeValue(value) { getScoreTimeValue(value) {
if (!value) return 0; if (!value) return 0;
const time = this.$dayjs(value).valueOf(); const time = this.$dayjs(value).valueOf();
@@ -2763,6 +2780,13 @@ export default {
}, },
normalizeArchive() { normalizeArchive() {
const archive = JSON.parse(JSON.stringify(this.archiveForm)); const archive = JSON.parse(JSON.stringify(this.archiveForm));
if (archive.businessTermType === '长期') {
archive.businessEndDate = null;
}
const invoiceTaxRate = this.normalizeOptionalAmount(archive.invoiceTaxRate);
const registeredCapital = this.normalizeOptionalAmount(archive.registeredCapital);
archive.invoiceTaxRate = invoiceTaxRate === '' ? null : invoiceTaxRate;
archive.registeredCapital = registeredCapital === '' ? null : registeredCapital;
archive.customerType = Array.isArray(archive.customerType) archive.customerType = Array.isArray(archive.customerType)
? archive.customerType.join(',') ? archive.customerType.join(',')
: archive.customerType; : archive.customerType;