调整项目

This commit is contained in:
2026-09-13 01:01:24 +08:00
parent 764c0f4581
commit dbeb174ae2
6 changed files with 493 additions and 110 deletions
+6
View File
@@ -6,6 +6,12 @@ const api = createCrudApi(baseUrl);
export const getList = api.getList; export const getList = api.getList;
export const getDetail = api.getDetail; export const getDetail = api.getDetail;
export const getChangeRecordDetail = (id, recordIndex) =>
request({
url: `${baseUrl}/change-record/detail`,
method: 'get',
params: { id, recordIndex },
});
export const submit = api.submit; export const submit = api.submit;
export const remove = api.remove; export const remove = api.remove;
+9 -9
View File
@@ -192,6 +192,15 @@ export const option = createCrudOption([
formatter: row => formatUserRealName(row, 'principal'), formatter: row => formatUserRealName(row, 'principal'),
rules: textRule('项目负责人', 50, true), rules: textRule('项目负责人', 50, true),
}, },
{
label: '资金使用风险',
prop: 'fundUseRisk',
slot: true,
minWidth: 150,
addDisplay: false,
editDisplay: false,
hide: false,
},
{ {
label: '项目负责人ID', label: '项目负责人ID',
prop: 'principalUserId', prop: 'principalUserId',
@@ -433,14 +442,5 @@ export const option = createCrudOption([
span: 24, span: 24,
hide: true, hide: true,
}, },
{
label: '资金使用风险',
prop: 'fundUseRisk',
slot: true,
minWidth: 130,
addDisplay: false,
editDisplay: false,
hide: false,
},
...auditColumns.map(column => ({ ...column, hide: true })), ...auditColumns.map(column => ({ ...column, hide: true })),
]); ]);
+9 -2
View File
@@ -786,14 +786,21 @@ export default {
this.nodeRows.map(item => ({ this.nodeRows.map(item => ({
key: item.key, key: item.key,
name: item.name, name: item.name,
type: item.type,
enabled: item.enabled, enabled: item.enabled,
confirmMode: item.confirmMode, confirmMode: item.confirmMode,
confirmDriver: item.confirmDriver, confirmDriver: item.confirmDriver,
confirmInternal: item.confirmInternal, confirmInternal: item.confirmInternal,
punch: item.punch, punch: item.punch,
location: item.location, location: item.location,
uploadCargo: item.uploadCargo, // 在途不支持货量;其它节点按配置
cargoTypes: item.uploadCargo ? item.cargoTypes : [], uploadCargo: item.type === 'transit' || item.key === 'transit' ? false : item.uploadCargo,
cargoTypes:
item.type === 'transit' || item.key === 'transit'
? []
: item.uploadCargo
? item.cargoTypes
: [],
frequencyDays: item.frequencyDays, frequencyDays: item.frequencyDays,
timeStart: item.timeStart, timeStart: item.timeStart,
timeEnd: item.timeEnd, timeEnd: item.timeEnd,
+359 -88
View File
@@ -66,9 +66,10 @@
<template #content> <template #content>
<div>中风险项目资金额度使用率 80%</div> <div>中风险项目资金额度使用率 80%</div>
<div>高风险项目资金额度使用率 90%</div> <div>高风险项目资金额度使用率 90%</div>
<div>使用率 = 已使用资金 / 项目资金使用额度 × 100%</div>
</template> </template>
<i class="el-icon-question project-apply-page__risk-help" /> <el-icon class="project-apply-page__risk-help">
<el-icon-question-filled />
</el-icon>
</el-tooltip> </el-tooltip>
</span> </span>
<el-check-tag <el-check-tag
@@ -83,15 +84,31 @@
</template> </template>
<template #fundUseRisk="{ row }"> <template #fundUseRisk="{ row }">
<el-tag v-if="row.fundUseRisk === 'high'" type="danger"> <span v-if="row.fundUseRisk === 'high'" class="project-apply-page__risk-high"></span>
{{ row.fundUseRiskName || '高风险' }} <span
</el-tag> v-else-if="row.fundUseRisk === 'medium'"
<el-tag v-else-if="row.fundUseRisk === 'medium'" type="warning"> class="project-apply-page__risk-medium"
{{ row.fundUseRiskName || '中风险' }} >
</el-tag>
</span>
<span v-else class="project-apply-page__risk-none">-</span> <span v-else class="project-apply-page__risk-none">-</span>
</template> </template>
<template #fundUseRisk-header>
<span class="project-apply-page__risk-header">
资金使用风险
<el-tooltip placement="top" effect="dark">
<template #content>
<div>中风险项目资金额度使用率 80%</div>
<div>高风险项目资金额度使用率 90%</div>
</template>
<el-icon class="project-apply-page__risk-help">
<el-icon-question-filled />
</el-icon>
</el-tooltip>
</span>
</template>
<template #menu="{ row, index }"> <template #menu="{ row, index }">
<el-link <el-link
type="primary" type="primary"
@@ -145,7 +162,7 @@
<div class="dialog-section-title">项目基本信息</div> <div class="dialog-section-title">项目基本信息</div>
<div class="project-apply-form__grid"> <div class="project-apply-form__grid">
<el-form-item v-if="isChangeDialog" label="变更类型" prop="changeType"> <el-form-item v-if="isChangeDialog" label="变更类型" prop="changeType">
<el-select v-model="form.changeType" placeholder="请选择变更类型"> <el-select v-model="form.changeType" placeholder="请选择">
<el-option <el-option
v-for="item in changeTypeOptions" v-for="item in changeTypeOptions"
:key="item.value" :key="item.value"
@@ -155,15 +172,15 @@
</el-select> </el-select>
</el-form-item> </el-form-item>
<el-form-item v-if="isChangeDialog" label="变更编号" prop="changeNo"> <el-form-item v-if="isChangeDialog" label="变更编号" prop="changeNo">
<el-input v-model="form.changeNo" disabled placeholder="系统自动生成" /> <el-input v-model="form.changeNo" disabled placeholder="请输入" />
</el-form-item> </el-form-item>
<el-form-item v-if="isChangeDialog" label="变更日期" prop="changeDate"> <el-form-item v-if="isChangeDialog" label="变更日期" prop="changeDate">
<el-input v-model="form.changeDate" disabled placeholder="系统自动生成" /> <el-input v-model="form.changeDate" disabled placeholder="请输入" />
</el-form-item> </el-form-item>
<el-form-item label="项目类型" prop="projectType"> <el-form-item label="项目类型" prop="projectType">
<el-select <el-select
v-model="form.projectType" v-model="form.projectType"
placeholder="请选择项目类型" placeholder="请选择"
clearable clearable
:disabled="isBasicInfoReadonly || isMajorProjectSupplementDialog" :disabled="isBasicInfoReadonly || isMajorProjectSupplementDialog"
@change="handleProjectTypeChange" @change="handleProjectTypeChange"
@@ -179,14 +196,14 @@
<el-form-item label="项目编号" prop="projectCode"> <el-form-item label="项目编号" prop="projectCode">
<el-input <el-input
v-model="form.projectCode" v-model="form.projectCode"
placeholder="请输入项目编号,不填自动生成" placeholder="请输入"
:disabled="dialogType === 'edit'" :disabled="dialogType === 'edit'"
/> />
</el-form-item> </el-form-item>
<el-form-item label="项目名称" prop="projectName"> <el-form-item label="项目名称" prop="projectName">
<el-input <el-input
v-model="form.projectName" v-model="form.projectName"
placeholder="请填写项目名称" placeholder="请输入"
maxlength="50" maxlength="50"
:disabled="isBasicInfoReadonly" :disabled="isBasicInfoReadonly"
/> />
@@ -194,7 +211,7 @@
<el-form-item label="项目简称" prop="projectShortName"> <el-form-item label="项目简称" prop="projectShortName">
<el-input <el-input
v-model="form.projectShortName" v-model="form.projectShortName"
placeholder="请填写项目简称" placeholder="请输入"
maxlength="20" maxlength="20"
:disabled="isBasicInfoReadonly" :disabled="isBasicInfoReadonly"
/> />
@@ -204,7 +221,7 @@
v-model="businessDeptCascaderValue" v-model="businessDeptCascaderValue"
:options="businessDeptTreeOptions" :options="businessDeptTreeOptions"
:props="deptCascaderProps" :props="deptCascaderProps"
placeholder="请选择业务部门" placeholder="请选择"
filterable filterable
clearable clearable
:disabled="isBasicInfoReadonly" :disabled="isBasicInfoReadonly"
@@ -215,7 +232,7 @@
v-model="undertakeDeptCascaderValue" v-model="undertakeDeptCascaderValue"
:options="deptTreeOptions" :options="deptTreeOptions"
:props="deptCascaderProps" :props="deptCascaderProps"
placeholder="请选择承办部门" placeholder="请选择"
clearable clearable
filterable filterable
:disabled="isBasicInfoReadonly" :disabled="isBasicInfoReadonly"
@@ -224,7 +241,7 @@
<el-form-item label="项目由来" prop="projectSource"> <el-form-item label="项目由来" prop="projectSource">
<el-select <el-select
v-model="form.projectSource" v-model="form.projectSource"
placeholder="请选择项目由来" placeholder="请选择"
clearable clearable
:disabled="isBasicInfoReadonly" :disabled="isBasicInfoReadonly"
> >
@@ -237,7 +254,14 @@
</el-select> </el-select>
</el-form-item> </el-form-item>
<el-form-item label="项目由来说明" prop="sourceRemark" class="project-apply-form__item--full"> <el-form-item label="项目由来说明" prop="sourceRemark" class="project-apply-form__item--full">
<el-input v-model="form.sourceRemark" type="textarea" rows="2" maxlength="300" :disabled="isBasicInfoReadonly" /> <el-input
v-model="form.sourceRemark"
type="textarea"
rows="2"
placeholder="请输入"
maxlength="300"
:disabled="isBasicInfoReadonly"
/>
</el-form-item> </el-form-item>
</div> </div>
@@ -320,6 +344,7 @@
<el-form-item label="应收账款回款期限" prop="receivableDays"> <el-form-item label="应收账款回款期限" prop="receivableDays">
<el-input <el-input
v-model="form.receivableDays" v-model="form.receivableDays"
placeholder="请输入"
@input="value => handleIntegerInput('receivableDays', value)" @input="value => handleIntegerInput('receivableDays', value)"
> >
<template #suffix>天</template> <template #suffix>天</template>
@@ -328,6 +353,7 @@
<el-form-item label="回款账期" prop="paymentDays"> <el-form-item label="回款账期" prop="paymentDays">
<el-input <el-input
v-model="form.paymentDays" v-model="form.paymentDays"
placeholder="请输入"
@input="value => handleIntegerInput('paymentDays', value)" @input="value => handleIntegerInput('paymentDays', value)"
> >
<template #suffix>天</template> <template #suffix>天</template>
@@ -350,22 +376,29 @@
@input="value => handleIntegerInput('cargoQuantity', value)" @input="value => handleIntegerInput('cargoQuantity', value)"
/> />
</el-form-item> </el-form-item>
<el-form-item label="业务周期" prop="businessDateRange"> <el-form-item label="业务周期" prop="businessStartDate">
<el-date-picker <el-date-picker
v-model="form.businessDateRange" v-model="form.businessStartDate"
type="daterange" type="date"
start-placeholder="开始日期" placeholder="请选择"
end-placeholder="结束日期" format="YYYY-MM-DD"
value-format="YYYY-MM-DD"
/>
</el-form-item>
<el-form-item label="业务周期止" prop="businessEndDate">
<el-date-picker
v-model="form.businessEndDate"
type="date"
placeholder="请选择"
format="YYYY-MM-DD" format="YYYY-MM-DD"
value-format="YYYY-MM-DD" value-format="YYYY-MM-DD"
unlink-panels
/> />
</el-form-item> </el-form-item>
<el-form-item label="运输线路" prop="transportRoute"> <el-form-item label="运输线路" prop="transportRoute">
<el-input v-model="form.transportRoute" maxlength="100" /> <el-input v-model="form.transportRoute" placeholder="请输入" maxlength="100" />
</el-form-item> </el-form-item>
<el-form-item label="运输类型" prop="transportType"> <el-form-item label="运输类型" prop="transportType">
<el-select v-model="form.transportType" placeholder="请选择运输类型" clearable> <el-select v-model="form.transportType" placeholder="请选择" clearable>
<el-option <el-option
v-for="item in transportTypeOptions" v-for="item in transportTypeOptions"
:key="item.value" :key="item.value"
@@ -375,7 +408,7 @@
</el-select> </el-select>
</el-form-item> </el-form-item>
<el-form-item label="业务类型" prop="businessType"> <el-form-item label="业务类型" prop="businessType">
<el-select v-model="form.businessType" placeholder="请选择业务类型" clearable> <el-select v-model="form.businessType" placeholder="请选择" clearable>
<el-option <el-option
v-for="item in businessTypeOptions" v-for="item in businessTypeOptions"
:key="item.value" :key="item.value"
@@ -385,7 +418,7 @@
</el-select> </el-select>
</el-form-item> </el-form-item>
<el-form-item label="业务模式" prop="businessMode"> <el-form-item label="业务模式" prop="businessMode">
<el-select v-model="form.businessMode" placeholder="请选择业务模式"> <el-select v-model="form.businessMode" placeholder="请选择">
<el-option <el-option
v-for="item in businessModeOptions" v-for="item in businessModeOptions"
:key="item.value" :key="item.value"
@@ -397,13 +430,14 @@
<el-form-item label="项目规模" prop="projectScale"> <el-form-item label="项目规模" prop="projectScale">
<el-input <el-input
v-model="form.projectScale" v-model="form.projectScale"
placeholder="请输入"
@input="value => handleNumberInput('projectScale', value)" @input="value => handleNumberInput('projectScale', value)"
> >
<template #suffix>万元</template> <template #suffix>万元</template>
</el-input> </el-input>
</el-form-item> </el-form-item>
<el-form-item label="结算方式" prop="settlementMode"> <el-form-item label="结算方式" prop="settlementMode">
<el-select v-model="form.settlementMode" placeholder="请选择结算方式" clearable> <el-select v-model="form.settlementMode" placeholder="请选择" clearable>
<el-option <el-option
v-for="item in settlementModeOptions" v-for="item in settlementModeOptions"
:key="item.value" :key="item.value"
@@ -415,6 +449,7 @@
<el-form-item label="预估利润" prop="estimatedProfit"> <el-form-item label="预估利润" prop="estimatedProfit">
<el-input <el-input
v-model="form.estimatedProfit" v-model="form.estimatedProfit"
placeholder="请输入"
@input="value => handleNumberInput('estimatedProfit', value)" @input="value => handleNumberInput('estimatedProfit', value)"
> >
<template #suffix>万元</template> <template #suffix>万元</template>
@@ -423,6 +458,7 @@
<el-form-item label="利润率" prop="profitRate"> <el-form-item label="利润率" prop="profitRate">
<el-input <el-input
v-model="form.profitRate" v-model="form.profitRate"
placeholder="请输入"
@input="value => handleNumberInput('profitRate', value)" @input="value => handleNumberInput('profitRate', value)"
> >
<template #suffix>%</template> <template #suffix>%</template>
@@ -431,6 +467,7 @@
<el-form-item label="履约保证金" prop="fundDemand"> <el-form-item label="履约保证金" prop="fundDemand">
<el-input <el-input
v-model="form.fundDemand" v-model="form.fundDemand"
placeholder="请输入"
@input="value => handleNumberInput('fundDemand', value)" @input="value => handleNumberInput('fundDemand', value)"
> >
<template #suffix>万元</template> <template #suffix>万元</template>
@@ -440,7 +477,7 @@
<el-input <el-input
v-model="form.handlerUserName" v-model="form.handlerUserName"
readonly readonly
placeholder="弹窗搜索选择" placeholder="请输入"
@click="openUserPicker('handler')" @click="openUserPicker('handler')"
> >
<template #suffix> <template #suffix>
@@ -452,7 +489,7 @@
<el-input <el-input
v-model="form.principalUserName" v-model="form.principalUserName"
readonly readonly
placeholder="弹窗搜索选择" placeholder="请输入"
@click="openUserPicker('principal')" @click="openUserPicker('principal')"
> >
<template #suffix> <template #suffix>
@@ -524,7 +561,7 @@
v-model="form.projectIntro" v-model="form.projectIntro"
type="textarea" type="textarea"
:rows="2" :rows="2"
placeholder="填写备注信息" placeholder="输入"
maxlength="200" maxlength="200"
show-word-limit show-word-limit
/> />
@@ -534,7 +571,7 @@
v-model="form.profitRemark" v-model="form.profitRemark"
type="textarea" type="textarea"
:rows="2" :rows="2"
placeholder="填写备注信息" placeholder="输入"
maxlength="200" maxlength="200"
show-word-limit show-word-limit
/> />
@@ -544,7 +581,7 @@
v-model="form.riskPoint" v-model="form.riskPoint"
type="textarea" type="textarea"
:rows="2" :rows="2"
placeholder="填写备注信息" placeholder="输入"
maxlength="200" maxlength="200"
show-word-limit show-word-limit
/> />
@@ -554,7 +591,7 @@
v-model="form.emergencyPlan" v-model="form.emergencyPlan"
type="textarea" type="textarea"
:rows="2" :rows="2"
placeholder="填写备注信息" placeholder="输入"
maxlength="200" maxlength="200"
show-word-limit show-word-limit
/> />
@@ -565,26 +602,18 @@
<div class="project-apply-form__material-head"> <div class="project-apply-form__material-head">
<div class="dialog-section-title project-apply-form__material-title">项目材料</div> <div class="dialog-section-title project-apply-form__material-title">项目材料</div>
<span <span
v-if="!dialogReadonly && missingRequiredAttachmentTypes.length" v-if="missingRequiredAttachmentTypes.length"
class="project-apply-form__material-warn" class="project-apply-form__material-warn"
> >
未上传:{{ missingRequiredAttachmentTypes.join('、') }} 未上传:{{ missingRequiredAttachmentTypes.join('、') }}
</span> </span>
<el-button <el-button
type="primary" type="primary"
:disabled="!attachmentRows.length" class="project-apply-form__material-download"
@click="handleBatchDownload" @click="handleBatchDownload"
>批量下载</el-button >批量下载</el-button
> >
</div> </div>
<el-alert
v-if="dialogReadonly && missingRequiredAttachmentTypes.length"
class="project-apply-form__material-warning"
type="warning"
:closable="false"
show-icon
:title="`缺少必需材料:${missingRequiredAttachmentTypes.join('、')}`"
/>
<el-table <el-table
:data="attachmentRows" :data="attachmentRows"
border border
@@ -616,7 +645,7 @@
<template #default="{ row }"> <template #default="{ row }">
<el-input <el-input
v-model="row.description" v-model="row.description"
placeholder="请输入附件描述" placeholder="请输入"
:disabled="dialogReadonly" :disabled="dialogReadonly"
/> />
</template> </template>
@@ -668,8 +697,16 @@
label="变更内容" label="变更内容"
min-width="180" min-width="180"
align="center" align="center"
show-overflow-tooltip >
/> <template #default="{ row }">
<el-tooltip placement="top" :show-after="200">
<template #content>
<div class="project-apply-form__change-record-content-tooltip">{{ row.content || '空' }}</div>
</template>
<span class="project-apply-form__change-record-content-cell">{{ row.content || '空' }}</span>
</el-tooltip>
</template>
</el-table-column>
<el-table-column <el-table-column
prop="reason" prop="reason"
label="变更原因" label="变更原因"
@@ -687,6 +724,44 @@
</el-table-column> </el-table-column>
</el-table> </el-table>
<el-dialog
v-model="changeRecordDetailVisible"
title="变更记录详情"
append-to-body
destroy-on-close
width="1100px"
top="10px"
class="project-change-record-detail-dialog"
>
<div v-if="changeRecordDetail" class="project-change-record-detail-meta">
<span>变更日期:{{ changeRecordDetail.changeDate || '-' }}</span>
<span>变更账号:{{ changeRecordDetail.handler || '-' }}</span>
</div>
<el-table :data="changeRecordDetailRows" border :show-overflow-tooltip="false">
<el-table-column prop="field" label="变更字段" min-width="180" />
<el-table-column
prop="before"
label="变更前"
min-width="360"
class-name="project-change-record-detail-value"
/>
<el-table-column
prop="after"
label="变更后"
min-width="500"
class-name="project-change-record-detail-value"
/>
</el-table>
<el-empty
v-if="!changeRecordDetailRows.length"
description="暂无变更内容"
:image-size="60"
/>
<template #footer>
<el-button type="primary" @click="changeRecordDetailVisible = false">关闭</el-button>
</template>
</el-dialog>
<template v-if="isChangeDialog"> <template v-if="isChangeDialog">
<div class="dialog-section-title">变更原因</div> <div class="dialog-section-title">变更原因</div>
<el-form-item prop="changeReason" class="project-apply-form__change-textarea"> <el-form-item prop="changeReason" class="project-apply-form__change-textarea">
@@ -694,7 +769,7 @@
v-model="form.changeReason" v-model="form.changeReason"
type="textarea" type="textarea"
:rows="2" :rows="2"
placeholder="2000字以内" placeholder="请输入"
maxlength="2000" maxlength="2000"
show-word-limit show-word-limit
/> />
@@ -888,7 +963,6 @@ const emptyForm = () => ({
paymentDays: '', paymentDays: '',
cargoType: '', cargoType: '',
cargoQuantity: '', cargoQuantity: '',
businessDateRange: [],
businessStartDate: '', businessStartDate: '',
businessEndDate: '', businessEndDate: '',
transportRoute: '', transportRoute: '',
@@ -938,6 +1012,8 @@ const attachmentViewerPlugins = [
fallbackPlugin(), fallbackPlugin(),
]; ];
const otherAttachmentType = { label: '其他附件', value: '其他附件' };
const normalProjectAttachmentTypeOptions = [ const normalProjectAttachmentTypeOptions = [
'利润测算表', '利润测算表',
'单一来源供应商申请表/三方比价表', '单一来源供应商申请表/三方比价表',
@@ -945,7 +1021,7 @@ const normalProjectAttachmentTypeOptions = [
'项目操作说明', '项目操作说明',
'项目分析报告', '项目分析报告',
'项目考察报告', '项目考察报告',
].map(item => ({ label: item, value: item })); ].map(item => ({ label: item, value: item })).concat(otherAttachmentType);
const majorProjectAttachmentTypeOptions = [ const majorProjectAttachmentTypeOptions = [
'会议纪要', '会议纪要',
@@ -959,7 +1035,7 @@ const majorProjectAttachmentTypeOptions = [
'上游尽职调查报告', '上游尽职调查报告',
'下游尽职调查报告', '下游尽职调查报告',
'业务承诺函', '业务承诺函',
].map(item => ({ label: item, value: item })); ].map(item => ({ label: item, value: item })).concat(otherAttachmentType);
export default { export default {
components: { components: {
@@ -1148,6 +1224,9 @@ export default {
'zip', 'zip',
], ],
changeRows: [], changeRows: [],
changeRecordDetailVisible: false,
changeRecordDetail: null,
changeRecordDetailRows: [],
userBox: false, userBox: false,
userPickType: '', userPickType: '',
userLoading: false, userLoading: false,
@@ -1279,6 +1358,7 @@ export default {
const uploadedTypes = new Set(this.attachmentRows.map(item => item.fileType).filter(Boolean)); const uploadedTypes = new Set(this.attachmentRows.map(item => item.fileType).filter(Boolean));
return this.projectAttachmentTypeOptions return this.projectAttachmentTypeOptions
.map(item => item.value) .map(item => item.value)
.filter(fileType => fileType !== otherAttachmentType.value)
.filter(fileType => !uploadedTypes.has(fileType)); .filter(fileType => !uploadedTypes.has(fileType));
}, },
}, },
@@ -1399,7 +1479,7 @@ export default {
this.page.total = result.total || 0; this.page.total = result.total || 0;
this.data = result.records || []; this.data = result.records || [];
this.selectionClear(); this.selectionClear();
// this.refreshFundRiskStats({ ...params, ...this.query, allDept: this.allDept }); this.refreshFundRiskStats({ ...params, ...this.query, allDept: this.allDept });
}) })
.finally(() => { .finally(() => {
this.loading = false; this.loading = false;
@@ -1591,10 +1671,6 @@ export default {
...displayRow, ...displayRow,
handlerUserName: this.resolveUserRealName(displayRow, 'handler'), handlerUserName: this.resolveUserRealName(displayRow, 'handler'),
principalUserName: this.resolveUserRealName(displayRow, 'principal'), principalUserName: this.resolveUserRealName(displayRow, 'principal'),
businessDateRange:
displayRow.businessStartDate && displayRow.businessEndDate
? [displayRow.businessStartDate, displayRow.businessEndDate]
: [],
}; };
const situation = this.parseSituation(displayRow.situationRemark); const situation = this.parseSituation(displayRow.situationRemark);
const customerRows = this.parseJsonArray(displayRow.customerJson); const customerRows = this.parseJsonArray(displayRow.customerJson);
@@ -1608,6 +1684,7 @@ export default {
this.customerRows = customerRows; this.customerRows = customerRows;
this.carrierRows = carrierRows; this.carrierRows = carrierRows;
this.attachmentRows = this.parseJsonArray(displayRow.attachmentsJson); this.attachmentRows = this.parseJsonArray(displayRow.attachmentsJson);
this.normalizeAttachmentFileTypes();
this.selectedAttachmentRows = []; this.selectedAttachmentRows = [];
this.changeRows = this.buildChangeRows(displayRow); this.changeRows = this.buildChangeRows(displayRow);
this.selectedCustomerId = this.customerRows this.selectedCustomerId = this.customerRows
@@ -1747,7 +1824,6 @@ export default {
}); });
}, },
normalizeSubmitForm(options = {}) { normalizeSubmitForm(options = {}) {
const [businessStartDate, businessEndDate] = this.form.businessDateRange || [];
const submitRow = { const submitRow = {
...this.form, ...this.form,
fundLimit: this.formatAmount(this.form.fundLimit), fundLimit: this.formatAmount(this.form.fundLimit),
@@ -1755,8 +1831,6 @@ export default {
...Object.fromEntries( ...Object.fromEntries(
optionalSentinelFields.map(key => [key, this.normalizeOptionalSentinel(this.form[key], null)]) optionalSentinelFields.map(key => [key, this.normalizeOptionalSentinel(this.form[key], null)])
), ),
businessStartDate,
businessEndDate,
customerJson: JSON.stringify(this.customerRows), customerJson: JSON.stringify(this.customerRows),
carrierJson: JSON.stringify(this.carrierRows), carrierJson: JSON.stringify(this.carrierRows),
attachmentsJson: JSON.stringify(this.attachmentRows), attachmentsJson: JSON.stringify(this.attachmentRows),
@@ -1767,7 +1841,6 @@ export default {
emergencyPlan: this.form.emergencyPlan || '', emergencyPlan: this.form.emergencyPlan || '',
}), }),
}; };
delete submitRow.businessDateRange;
if (!options.includeChangeType) { if (!options.includeChangeType) {
delete submitRow.changeType; delete submitRow.changeType;
} }
@@ -1779,6 +1852,7 @@ export default {
['projectIntro', 'profitRemark', 'riskPoint', 'emergencyPlan'].forEach( ['projectIntro', 'profitRemark', 'riskPoint', 'emergencyPlan'].forEach(
key => delete submitRow[key] key => delete submitRow[key]
); );
delete submitRow.changeRecordJson;
Object.keys(submitRow).forEach(key => { Object.keys(submitRow).forEach(key => {
if (typeof submitRow[key] === 'string') { if (typeof submitRow[key] === 'string') {
submitRow[key] = submitRow[key].trim(); submitRow[key] = submitRow[key].trim();
@@ -1814,13 +1888,13 @@ export default {
const values = this.projectAttachmentTypeOptions.map(item => item.value); const values = this.projectAttachmentTypeOptions.map(item => item.value);
if (values.includes(row.fileType)) return row.fileType; if (values.includes(row.fileType)) return row.fileType;
const fileName = this.normalizeAttachmentTypeText(this.attachmentName(row)); const fileName = this.normalizeAttachmentTypeText(this.attachmentName(row));
if (!fileName) return ''; if (!fileName) return otherAttachmentType.value;
const option = this.projectAttachmentTypeOptions.find(item => const option = this.projectAttachmentTypeOptions.find(item =>
this.attachmentTypeKeywords(item.value).some(keyword => this.attachmentTypeKeywords(item.value).some(keyword =>
fileName.includes(this.normalizeAttachmentTypeText(keyword)) fileName.includes(this.normalizeAttachmentTypeText(keyword))
) )
); );
return option?.value || ''; return option?.value || otherAttachmentType.value;
}, },
validateAttachmentFileTypes() { validateAttachmentFileTypes() {
const values = this.projectAttachmentTypeOptions.map(item => item.value); const values = this.projectAttachmentTypeOptions.map(item => item.value);
@@ -1868,7 +1942,8 @@ export default {
if (Array.isArray(value)) return value; if (Array.isArray(value)) return value;
try { try {
const list = JSON.parse(value); const list = JSON.parse(value);
return Array.isArray(list) ? list : []; if (Array.isArray(list)) return list;
return list && typeof list === 'object' ? [list] : [];
} catch (error) { } catch (error) {
return []; return [];
} }
@@ -1942,20 +2017,147 @@ export default {
); );
}, },
buildChangeRows(row) { buildChangeRows(row) {
if (!row || (!row.changeContent && !row.changeReason)) return []; const records = this.parseJsonArray(row?.changeRecordJson);
return [ return records.map((record, recordIndex) => {
{ const changeRow = {
changeDate: row.updateTime || row.createTime || '', ...record,
changeType: projectId: row?.id,
row.changeType || recordIndex,
(String(row.currentNode || '').includes('备案') ? '项目备案调整' : '项目变更'), changeDate: record.changeDate || record.changeTime || record.createTime || '',
handler: this.resolveUserRealName(row, 'handler'), changeType: record.changeType || '项目变更',
content: row.changeContent || '', handler: record.handlerUserName || record.changeUserName || record.handler || '',
reason: row.changeReason || '', reason: record.changeReason || '',
status: row.approvalStatusName || row.approvalStatus || '', status: record.statusName || record.status || '',
action: this.dialogReadonly ? '流程' : '编辑', action: '详情',
}, };
return { ...changeRow, content: this.formatChangeContent(changeRow) };
});
},
parseChangeData(value) {
if (!value || value === '-1') return {};
if (typeof value === 'object') return value;
const text = String(value).trim();
if (!text || text === '-1') return {};
try {
const parsed = JSON.parse(text);
return parsed && typeof parsed === 'object' ? parsed : { 变更内容: parsed };
} catch (error) {
return { 变更内容: text };
}
},
getProjectChangeFieldLabel(field) {
const labels = {
projectType: '项目类型',
projectName: '项目名称',
projectShortName: '项目简称',
businessDeptName: '业务部门',
undertakeDeptName: '承办部门',
projectSource: '项目由来',
sourceRemark: '项目由来说明',
fundLimit: '项目资金使用额度',
receivableLimit: '项目应收账款额度',
receivableDays: '应收账款回款期限',
paymentDays: '回款账期',
cargoType: '货物类型',
cargoQuantity: '预估货物数量',
businessStartDate: '业务周期起',
businessEndDate: '业务周期止',
transportRoute: '运输线路',
transportType: '运输类型',
businessType: '业务类型',
businessMode: '业务模式',
projectScale: '项目规模',
settlementMode: '结算方式',
estimatedProfit: '预计利润',
profitRate: '利润率',
fundDemand: '履约保证金',
handlerUserName: '项目经办人',
principalUserName: '项目负责人',
customerNames: '客户名称',
carrierNames: '下游承运商',
situationRemark: '项目情况说明',
attachmentsJson: '项目材料',
};
return labels[field] || field;
},
normalizeProjectChangeValue(value) {
if (typeof value !== 'string') return value;
const text = value.trim();
if (!text || (!text.startsWith('[') && !text.startsWith('{') && text !== 'null')) {
return value;
}
try {
return JSON.parse(text);
} catch (error) {
return value;
}
},
isEmptyProjectChangeValue(value) {
const normalized = this.normalizeProjectChangeValue(value);
return (
normalized === undefined ||
normalized === null ||
normalized === '' ||
normalized === '-1' ||
(Array.isArray(normalized) && normalized.length === 0) ||
(typeof normalized === 'object' && !Array.isArray(normalized) && Object.keys(normalized).length === 0)
);
},
isSameProjectChangeValue(before, after) {
if (this.isEmptyProjectChangeValue(before) && this.isEmptyProjectChangeValue(after)) {
return true;
}
return JSON.stringify(this.normalizeProjectChangeValue(before)) === JSON.stringify(this.normalizeProjectChangeValue(after));
},
formatProjectChangeValue(field, value) {
const normalized = this.normalizeProjectChangeValue(value);
if (this.isEmptyProjectChangeValue(normalized)) return '';
if (field === 'attachmentsJson') {
const attachments = Array.isArray(normalized)
? normalized
: normalized && typeof normalized === 'object'
? [normalized]
: [];
const names = attachments
.map(item => (typeof item === 'string' ? item : item?.originalName || item?.name || item?.fileName || item?.url || ''))
.map(item => String(item).split('?')[0].split('/').pop())
.filter(Boolean);
if (names.length) return names.join('');
}
if (Array.isArray(normalized)) {
return normalized.map(item => this.formatProjectChangeValue(field, item)).join('');
}
if (typeof normalized === 'object') return JSON.stringify(normalized);
return String(normalized);
},
buildChangeRecordDetailRows(row = {}) {
const beforeData = this.parseChangeData(row.beforeData);
const afterData = this.parseChangeData(row.afterData);
const fields = [
...new Set([
...(Array.isArray(row.changedFields) ? row.changedFields : []),
...Object.keys(beforeData),
...Object.keys(afterData),
]),
]; ];
const rows = fields
.filter(field => field !== '变更内容' && !this.isSameProjectChangeValue(beforeData[field], afterData[field]))
.map(field => ({
field: this.getProjectChangeFieldLabel(field),
before: this.formatProjectChangeValue(field, beforeData[field]),
after: this.formatProjectChangeValue(field, afterData[field]),
}));
if (!rows.length && String(row.changeContent || '').trim()) {
rows.push({ field: '变更内容', before: '', after: String(row.changeContent).trim() });
}
return rows;
},
formatChangeContent(row = {}) {
const rows = this.buildChangeRecordDetailRows(row);
return rows
.filter(item => item.field !== '变更内容')
.map(item => `${item.field}:变更前:${item.before} → 变更后:${item.after}`)
.join('') || String(row.changeContent || '').trim();
}, },
generateChangeNo(detail) { generateChangeNo(detail) {
const base = detail && detail.projectCode ? detail.projectCode : 'XM'; const base = detail && detail.projectCode ? detail.projectCode : 'XM';
@@ -2277,8 +2479,20 @@ export default {
window.setTimeout(() => this.downloadAttachment(row), index * 300); window.setTimeout(() => this.downloadAttachment(row), index * 300);
}); });
}, },
handleChangeRecordAction() { async handleChangeRecordAction(row) {
this.$message.info('变更记录详情由后端明细能力生成后展示'); if (!row?.projectId && !row?.id) {
this.$message.warning('项目ID为空无法查看变更详情');
return;
}
try {
const response = await api.getChangeRecordDetail(row.projectId || row.id, row.recordIndex);
const detail = response?.data?.data || {};
this.changeRecordDetail = { ...row, ...detail };
this.changeRecordDetailRows = this.buildChangeRecordDetailRows(this.changeRecordDetail);
this.changeRecordDetailVisible = true;
} catch (error) {
this.$message.error('变更记录详情加载失败');
}
}, },
openUserPicker(type) { openUserPicker(type) {
if (this.dialogReadonly) return; if (this.dialogReadonly) return;
@@ -2432,6 +2646,21 @@ export default {
} }
} }
&__change-record-content-cell {
display: block;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
&__change-record-content-tooltip {
max-width: 900px;
max-height: 320px;
overflow: auto;
white-space: pre-wrap;
word-break: break-word;
}
&__textarea-list { &__textarea-list {
margin-bottom: 12px; margin-bottom: 12px;
background: #fff; background: #fff;
@@ -2439,19 +2668,26 @@ export default {
padding: 14px 16px 4px; padding: 14px 16px 4px;
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.04); box-shadow: 0 1px 2px rgba(0, 0, 0, 0.04);
// 与项目基本信息网格使用相同的 6 字符 label 宽度,长文本自动换行
:deep(.el-form-item__label) {
width: 108px !important;
min-width: 0 !important;
white-space: normal;
}
:deep(.el-form-item) { :deep(.el-form-item) {
align-items: flex-start; align-items: flex-start;
} }
:deep(.el-form-item__content) { :deep(.el-form-item__content) {
width: calc(100% - 180px); width: calc(100% - 108px);
} }
} }
&__material-head { &__material-head {
display: flex; display: flex;
align-items: center; align-items: center;
justify-content: space-between; justify-content: flex-start;
margin-bottom: 12px; margin-bottom: 12px;
} }
@@ -2476,21 +2712,21 @@ export default {
} }
} }
&__material-warning {
margin-bottom: 12px;
}
&__material-title { &__material-title {
flex: 1; flex: 0 0 auto;
} }
&__material-warn { &__material-warn {
margin-left: 24px; margin-left: 8px;
color: #f56c6c; color: #f56c6c;
font-size: 14px; font-size: 14px;
font-weight: 500; font-weight: 500;
} }
&__material-download {
margin-left: auto;
}
&__upload { &__upload {
margin: 0 0 16px; margin: 0 0 16px;
background: #fff; background: #fff;
@@ -2530,6 +2766,27 @@ export default {
} }
} }
:deep(.project-change-record-detail-dialog .el-dialog__body) {
max-height: 65vh;
overflow: auto;
}
:deep(.project-change-record-detail-dialog .project-change-record-detail-value .cell) {
white-space: pre-wrap;
word-break: break-word;
overflow: visible;
text-overflow: clip;
line-height: 1.6;
}
.project-change-record-detail-meta {
display: flex;
gap: 32px;
margin-bottom: 16px;
color: #606266;
font-size: 14px;
}
:global(.project-apply-dialog .el-dialog__body) { :global(.project-apply-dialog .el-dialog__body) {
max-height: 76vh; max-height: 76vh;
overflow-y: auto; overflow-y: auto;
@@ -2565,6 +2822,12 @@ export default {
margin-right: 4px; margin-right: 4px;
} }
&__risk-header {
display: inline-flex;
align-items: center;
gap: 4px;
}
&__risk-help { &__risk-help {
margin-left: 4px; margin-left: 4px;
color: #a8abb2; color: #a8abb2;
@@ -2575,6 +2838,14 @@ export default {
&__risk-none { &__risk-none {
color: #c0c4cc; color: #c0c4cc;
} }
&__risk-high {
color: #f56c6c;
}
&__risk-medium {
color: #e6a23c;
}
} }
.project-apply-dialog__footer { .project-apply-dialog__footer {
+48 -5
View File
@@ -120,6 +120,7 @@
:model="driverForm" :model="driverForm"
:rules="formRules" :rules="formRules"
label-position="right" label-position="right"
label-width="calc(4em + 24px)"
:disabled="readonly" :disabled="readonly"
class="driver-form archive-form" class="driver-form archive-form"
> >
@@ -158,7 +159,12 @@
</el-col> </el-col>
<el-col :span="6"> <el-col :span="6">
<el-form-item label="性别" prop="gender"> <el-form-item label="性别" prop="gender">
<el-select v-model="driverForm.gender" clearable placeholder="请选择"> <el-select
v-model="driverForm.gender"
clearable
placeholder="请选择"
:validate-event="submitAttempted"
>
<el-option label="男" value="男" /> <el-option label="男" value="男" />
<el-option label="女" value="女" /> <el-option label="女" value="女" />
</el-select> </el-select>
@@ -302,6 +308,7 @@
allow-create allow-create
default-first-option default-first-option
placeholder="请选择" placeholder="请选择"
:validate-event="submitAttempted"
> >
<el-option <el-option
v-for="item in drivingTypeOptions" v-for="item in drivingTypeOptions"
@@ -321,7 +328,7 @@
/> />
</el-form-item> </el-form-item>
</el-col> </el-col>
<el-col :span="8"> <el-col :span="6">
<el-form-item label="有效期" prop="drivingLicenseEndDate"> <el-form-item label="有效期" prop="drivingLicenseEndDate">
<div class="date-range"> <div class="date-range">
<el-date-picker <el-date-picker
@@ -343,7 +350,7 @@
</div> </div>
</el-form-item> </el-form-item>
</el-col> </el-col>
<el-col :span="4"> <el-col :span="6">
<el-form-item label="长期有效" prop="drivingLicenseLongTerm"> <el-form-item label="长期有效" prop="drivingLicenseLongTerm">
<el-checkbox <el-checkbox
v-model="driverForm.drivingLicenseLongTerm" v-model="driverForm.drivingLicenseLongTerm"
@@ -395,6 +402,7 @@
clearable clearable
filterable filterable
placeholder="请选择" placeholder="请选择"
:validate-event="submitAttempted"
> >
<el-option <el-option
v-for="item in qualificationTypeOptions" v-for="item in qualificationTypeOptions"
@@ -414,7 +422,7 @@
/> />
</el-form-item> </el-form-item>
</el-col> </el-col>
<el-col :span="8"> <el-col :span="6">
<el-form-item label="有效期" prop="qualificationEndDate"> <el-form-item label="有效期" prop="qualificationEndDate">
<div class="date-range"> <div class="date-range">
<el-date-picker <el-date-picker
@@ -436,7 +444,7 @@
</div> </div>
</el-form-item> </el-form-item>
</el-col> </el-col>
<el-col :span="4"> <el-col :span="6">
<el-form-item label="长期有效" prop="qualificationLongTerm"> <el-form-item label="长期有效" prop="qualificationLongTerm">
<el-checkbox <el-checkbox
v-model="driverForm.qualificationLongTerm" v-model="driverForm.qualificationLongTerm"
@@ -746,6 +754,7 @@ export default {
total: 0, total: 0,
}, },
readonly: false, readonly: false,
submitAttempted: false,
driverForm: emptyForm(), driverForm: emptyForm(),
drivingLicenseUploads: {}, drivingLicenseUploads: {},
uploadHeaders: getUploadHeaders(), uploadHeaders: getUploadHeaders(),
@@ -1114,6 +1123,7 @@ export default {
}, },
openDriver(row, readonly = false) { openDriver(row, readonly = false) {
this.readonly = readonly; this.readonly = readonly;
this.submitAttempted = false;
if (!row?.id) { if (!row?.id) {
this.driverForm = emptyForm(); this.driverForm = emptyForm();
this.driverForm.organizationName = this.getCurrentOrganizationName(); this.driverForm.organizationName = this.getCurrentOrganizationName();
@@ -1138,6 +1148,7 @@ export default {
this.driverForm = emptyForm(); this.driverForm = emptyForm();
this.drivingLicenseUploads = {}; this.drivingLicenseUploads = {};
this.readonly = false; this.readonly = false;
this.submitAttempted = false;
this.submitLoading = false; this.submitLoading = false;
this.$refs.driverForm?.clearValidate(); this.$refs.driverForm?.clearValidate();
}, },
@@ -1600,6 +1611,7 @@ export default {
this.$refs.driverForm?.validateField(prop); this.$refs.driverForm?.validateField(prop);
}, },
handleSubmit() { handleSubmit() {
this.submitAttempted = true;
this.$refs.driverForm.validate(valid => { this.$refs.driverForm.validate(valid => {
if (!valid) { if (!valid) {
return; return;
@@ -1797,6 +1809,31 @@ export default {
} }
.driver-form { .driver-form {
:deep(.el-form-item__label) {
flex: 0 0 calc(4em + 24px) !important;
width: calc(4em + 24px) !important;
height: auto;
white-space: normal !important;
word-break: break-all;
overflow-wrap: anywhere;
line-height: 18px;
}
:deep(.el-form-item) {
align-items: center;
}
:deep(.el-form-item.is-required .el-form-item__label) {
align-items: flex-start;
}
:deep(.el-form-item.is-required .el-form-item__label::before) {
display: inline-block;
height: 18px;
line-height: 18px;
margin-right: 4px;
}
:deep(.el-date-editor.el-input), :deep(.el-date-editor.el-input),
:deep(.el-date-editor.el-input__wrapper), :deep(.el-date-editor.el-input__wrapper),
:deep(.el-cascader), :deep(.el-cascader),
@@ -1809,6 +1846,12 @@ export default {
display: grid; display: grid;
grid-template-columns: minmax(0, 1fr) minmax(0, 1fr); grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
gap: 8px; gap: 8px;
width: 100%;
}
.date-range :deep(.el-date-editor) {
width: 100%;
min-width: 0;
} }
} }
+62 -6
View File
@@ -127,7 +127,11 @@
top="4vh" top="4vh"
class="certification-audit-dialog" class="certification-audit-dialog"
> >
<el-form label-position="right" label-width="auto" class="certification-audit archive-form"> <el-form
label-position="right"
label-width="calc(7em + 24px)"
class="certification-audit archive-form"
>
<section-card title="基础信息"> <section-card title="基础信息">
<el-row :gutter="18"> <el-row :gutter="18">
<el-col :span="6"> <el-col :span="6">
@@ -353,7 +357,7 @@
v-if="showRejectReason" v-if="showRejectReason"
class="certification-audit__reason" class="certification-audit__reason"
label-position="right" label-position="right"
label-width="auto" label-width="calc(7em + 24px)"
> >
<el-form-item label="驳回原因" required> <el-form-item label="驳回原因" required>
<el-input <el-input
@@ -385,6 +389,8 @@
:model="vehicleForm" :model="vehicleForm"
:rules="formRules" :rules="formRules"
label-position="right" label-position="right"
label-width="calc(4em + 24px)"
:validate-on-rule-change="false"
:disabled="readonly" :disabled="readonly"
class="vehicle-form archive-form" class="vehicle-form archive-form"
> >
@@ -1632,6 +1638,50 @@ export default {
} }
// 车辆认证审核弹窗:与编辑弹窗一致,灰底 + section-card 白卡分组 // 车辆认证审核弹窗:与编辑弹窗一致,灰底 + section-card 白卡分组
.certification-audit,
.certification-audit__reason,
.vehicle-form {
:deep(.el-form-item__label) {
height: auto;
padding-right: 12px;
white-space: normal !important;
word-break: break-all;
overflow-wrap: anywhere;
line-height: 18px;
text-align: right;
}
:deep(.el-form-item) {
align-items: center;
}
:deep(.el-form-item.is-required .el-form-item__label) {
align-items: flex-start;
}
:deep(.el-form-item.is-required .el-form-item__label::before) {
display: inline-block;
height: 18px;
line-height: 18px;
margin-right: 4px;
}
}
.certification-audit,
.certification-audit__reason {
:deep(.el-form-item__label) {
flex: 0 0 calc(7em + 24px) !important;
width: calc(7em + 24px) !important;
}
}
.vehicle-form {
:deep(.el-form-item__label) {
flex: 0 0 calc(4em + 24px) !important;
width: calc(4em + 24px) !important;
}
}
.certification-audit { .certification-audit {
&__value { &__value {
display: block; display: block;
@@ -1679,6 +1729,7 @@ export default {
grid-template-columns: repeat(3, minmax(0, 1fr)); grid-template-columns: repeat(3, minmax(0, 1fr));
gap: 8px; gap: 8px;
align-items: center; align-items: center;
width: 100%;
} }
.dimension-group__item { .dimension-group__item {
@@ -1688,6 +1739,12 @@ export default {
min-width: 0; min-width: 0;
} }
.dimension-group__item :deep(.el-input) {
flex: 1 1 auto;
min-width: 0;
width: 100%;
}
.dimension-group__label { .dimension-group__label {
flex-shrink: 0; flex-shrink: 0;
color: #303133; color: #303133;
@@ -1697,11 +1754,13 @@ export default {
display: flex; display: flex;
align-items: center; align-items: center;
gap: 8px; gap: 8px;
width: 100%;
} }
.date-with-flag :deep(.el-date-editor) { .date-with-flag :deep(.el-date-editor) {
flex: 1; flex: 1 1 auto;
min-width: 0; min-width: 0;
width: 100%;
} }
.date-with-flag :deep(.el-checkbox) { .date-with-flag :deep(.el-checkbox) {
@@ -1719,9 +1778,6 @@ export default {
gap: 8px; gap: 8px;
} }
:deep(.el-form-item__label) {
white-space: nowrap;
}
} }
// 大尺寸证件照:按身份证 1.586:1 比例缩放(约真实尺寸 75%,240×151),左对齐显示 // 大尺寸证件照:按身份证 1.586:1 比例缩放(约真实尺寸 75%,240×151),左对齐显示