项目、合同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

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