项目、合同fix
This commit is contained in:
@@ -174,21 +174,16 @@
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="承办部门" prop="undertakeDeptId">
|
||||
<el-select
|
||||
<el-cascader
|
||||
v-model="form.undertakeDeptId"
|
||||
placeholder="请选择承办部门"
|
||||
filterable
|
||||
clearable
|
||||
filterable
|
||||
:options="deptTreeOptions"
|
||||
:props="deptCascaderProps"
|
||||
:disabled="isBasicInfoReadonly"
|
||||
@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 label="项目由来" prop="projectSource">
|
||||
<el-select
|
||||
@@ -215,9 +210,11 @@
|
||||
<el-form-item label="客户名称" prop="customerNames">
|
||||
<el-select
|
||||
v-model="selectedCustomerId"
|
||||
placeholder="搜索选择"
|
||||
placeholder="支持多选,选择后下方列表展示"
|
||||
multiple
|
||||
filterable
|
||||
clearable
|
||||
collapse-tags
|
||||
:loading="customerLoading"
|
||||
@visible-change="visible => visible && loadCustomerOptions('客户')"
|
||||
@change="handleCustomerChange"
|
||||
@@ -255,6 +252,7 @@
|
||||
v-model="form.fundLimit"
|
||||
placeholder="实际业务回款周期内付款额度"
|
||||
@input="value => handleNumberInput('fundLimit', value)"
|
||||
@blur="formatAmountField('fundLimit')"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="项目应收账款额度(万元)" prop="receivableLimit">
|
||||
@@ -262,6 +260,7 @@
|
||||
v-model="form.receivableLimit"
|
||||
placeholder="回款期内最大应收账款额度"
|
||||
@input="value => handleNumberInput('receivableLimit', value)"
|
||||
@blur="formatAmountField('receivableLimit')"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="应收账款回款期限" prop="receivableDays">
|
||||
@@ -464,65 +463,75 @@
|
||||
</el-form-item>
|
||||
</div>
|
||||
|
||||
<div class="project-apply-form__material-head">
|
||||
<div class="dialog-section-title project-apply-form__material-title">项目材料</div>
|
||||
<el-button type="primary" :disabled="!attachmentRows.length" @click="handleBatchDownload"
|
||||
>批量下载</el-button
|
||||
>
|
||||
</div>
|
||||
<el-table :data="attachmentRows" border class="project-apply-form__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"
|
||||
<div class="project-apply-form__material-card">
|
||||
<div class="project-apply-form__material-head">
|
||||
<div class="dialog-section-title project-apply-form__material-title">项目材料</div>
|
||||
<el-button
|
||||
type="primary"
|
||||
:disabled="!attachmentRows.length"
|
||||
@click="handleBatchDownload"
|
||||
>批量下载</el-button
|
||||
>
|
||||
</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 :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>
|
||||
</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"
|
||||
/>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="文件大小" min-width="120" align="center">
|
||||
<template #default="{ row }">{{ formatFileSize(row.size) }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="uploadUserName" label="上传人" min-width="140" align="center" />
|
||||
<el-table-column prop="uploadTime" label="上传时间" min-width="170" align="center" />
|
||||
<el-table-column label="操作" width="110" align="center">
|
||||
<template #default="{ row, $index }">
|
||||
<el-link type="primary" @click="downloadAttachment(row)">下载</el-link>
|
||||
<el-link
|
||||
v-if="!dialogReadonly"
|
||||
type="primary"
|
||||
@click="removeAttachment($index)"
|
||||
>删除</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="支持pdf、bmp、jpeg、png、jpg、doc、docx、ppt、pptx、xlsx、xls、eml、msg、zip的文件格式,单个文件大小限制500M"
|
||||
@change="handleAttachmentChange"
|
||||
/>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="文件大小" min-width="120" align="center">
|
||||
<template #default="{ row }">{{ formatFileSize(row.size) }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="uploadUserName" label="上传人" min-width="140" align="center" />
|
||||
<el-table-column prop="uploadTime" label="上传时间" min-width="170" align="center" />
|
||||
<el-table-column label="操作" width="110" align="center">
|
||||
<template #default="{ row, $index }">
|
||||
<el-link type="primary" @click="downloadAttachment(row)">下载</el-link>
|
||||
<el-link v-if="!dialogReadonly" type="primary" @click="removeAttachment($index)"
|
||||
>删除</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="50"
|
||||
button-text="上传附件"
|
||||
tip="支持pdf、bmp、jpeg、png、jpg、doc、docx、ppt、pptx、xlsx、xls、eml、msg、zip的文件格式,单个文件大小限制50M"
|
||||
@change="handleAttachmentChange"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div v-if="!isChangeDialog" class="dialog-section-title project-apply-form__table-title">
|
||||
变更记录
|
||||
@@ -593,16 +602,24 @@
|
||||
:class="{ 'project-apply-dialog__footer--change': 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>
|
||||
<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 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
|
||||
v-if="!dialogReadonly"
|
||||
type="primary"
|
||||
@@ -743,6 +760,7 @@ const changeTypeOptions = [
|
||||
];
|
||||
|
||||
const normalProjectAttachmentTypeOptions = [
|
||||
'项目立项报告',
|
||||
'利润测算表',
|
||||
'单一来源供应商申请表/三方比价表',
|
||||
'合同模板',
|
||||
@@ -752,6 +770,7 @@ const normalProjectAttachmentTypeOptions = [
|
||||
].map(item => ({ label: item, value: item }));
|
||||
|
||||
const majorProjectAttachmentTypeOptions = [
|
||||
'项目立项报告',
|
||||
'会议纪要',
|
||||
'评审表决票',
|
||||
'利润测算表',
|
||||
@@ -873,6 +892,8 @@ export default {
|
||||
fundDemand: [{ label: '资金需求', validator: validateAmount, trigger: 'blur' }],
|
||||
handlerUserId: [
|
||||
{
|
||||
required: true,
|
||||
message: '请选择项目经办人',
|
||||
label: '项目经办人',
|
||||
nameProp: 'handlerUserName',
|
||||
validator: validateUser,
|
||||
@@ -881,6 +902,8 @@ export default {
|
||||
],
|
||||
principalUserId: [
|
||||
{
|
||||
required: true,
|
||||
message: '请选择项目负责人',
|
||||
label: '项目负责人',
|
||||
nameProp: 'principalUserName',
|
||||
validator: validateUser,
|
||||
@@ -897,8 +920,16 @@ export default {
|
||||
],
|
||||
},
|
||||
deptOptions: [],
|
||||
deptTreeOptions: [],
|
||||
deptCascaderProps: {
|
||||
label: 'label',
|
||||
value: 'value',
|
||||
children: 'children',
|
||||
emitPath: false,
|
||||
checkStrictly: true,
|
||||
},
|
||||
cargoTypeOptions: [],
|
||||
selectedCustomerId: '',
|
||||
selectedCustomerId: [],
|
||||
selectedCarrierIds: [],
|
||||
customerOptions: [],
|
||||
carrierOptions: [],
|
||||
@@ -991,6 +1022,12 @@ export default {
|
||||
? majorProjectAttachmentTypeOptions
|
||||
: 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() {
|
||||
this.loadDeptOptions();
|
||||
@@ -1231,7 +1268,7 @@ export default {
|
||||
if (type === 'add' || type === 'majorSupplement') {
|
||||
this.applyProjectDetail({
|
||||
...emptyForm(),
|
||||
projectType: type === 'majorSupplement' ? '重大项目' : '',
|
||||
projectType: type === 'majorSupplement' ? '重大项目' : '普通项目',
|
||||
});
|
||||
this.fillDefaultUsers();
|
||||
return;
|
||||
@@ -1251,21 +1288,35 @@ export default {
|
||||
this.carrierRows = [];
|
||||
this.attachmentRows = [];
|
||||
this.changeRows = [];
|
||||
this.selectedCustomerId = '';
|
||||
this.selectedCustomerId = [];
|
||||
this.selectedCarrierIds = [];
|
||||
},
|
||||
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 = {
|
||||
...emptyForm(),
|
||||
...row,
|
||||
...displayRow,
|
||||
handlerUserName: this.resolveUserRealName(displayRow, 'handler'),
|
||||
principalUserName: this.resolveUserRealName(displayRow, 'principal'),
|
||||
businessDateRange:
|
||||
row.businessStartDate && row.businessEndDate
|
||||
? [row.businessStartDate, row.businessEndDate]
|
||||
displayRow.businessStartDate && displayRow.businessEndDate
|
||||
? [displayRow.businessStartDate, displayRow.businessEndDate]
|
||||
: [],
|
||||
};
|
||||
const situation = this.parseSituation(row.situationRemark);
|
||||
const customerRows = this.parseJsonArray(row.customerJson);
|
||||
const carrierRows = this.parseJsonArray(row.carrierJson);
|
||||
const situation = this.parseSituation(displayRow.situationRemark);
|
||||
const customerRows = this.parseJsonArray(displayRow.customerJson);
|
||||
const carrierRows = this.parseJsonArray(displayRow.carrierJson);
|
||||
this.form = {
|
||||
...form,
|
||||
...situation,
|
||||
@@ -1274,9 +1325,11 @@ export default {
|
||||
};
|
||||
this.customerRows = customerRows;
|
||||
this.carrierRows = carrierRows;
|
||||
this.attachmentRows = this.parseJsonArray(row.attachmentsJson);
|
||||
this.changeRows = this.buildChangeRows(row);
|
||||
this.selectedCustomerId = this.getCustomerRowId(this.customerRows[0]);
|
||||
this.attachmentRows = this.parseJsonArray(displayRow.attachmentsJson);
|
||||
this.changeRows = this.buildChangeRows(displayRow);
|
||||
this.selectedCustomerId = this.customerRows
|
||||
.map(item => this.getCustomerRowId(item))
|
||||
.filter(Boolean);
|
||||
this.selectedCarrierIds = this.carrierRows
|
||||
.map(item => this.getCustomerRowId(item))
|
||||
.filter(Boolean);
|
||||
@@ -1285,7 +1338,13 @@ export default {
|
||||
fillDefaultUsers() {
|
||||
const userId = this.userInfo?.userId || this.userInfo?.id || '';
|
||||
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) {
|
||||
this.form.handlerUserId = userId;
|
||||
this.form.handlerUserName = userName;
|
||||
@@ -1293,6 +1352,16 @@ export default {
|
||||
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 = {}) {
|
||||
if (row.changeType) return row.changeType;
|
||||
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() {
|
||||
this.submitChangeForm(false);
|
||||
},
|
||||
@@ -1364,6 +1463,8 @@ export default {
|
||||
const [businessStartDate, businessEndDate] = this.form.businessDateRange || [];
|
||||
const submitRow = {
|
||||
...this.form,
|
||||
fundLimit: this.formatAmount(this.form.fundLimit),
|
||||
receivableLimit: this.formatAmount(this.form.receivableLimit),
|
||||
businessStartDate,
|
||||
businessEndDate,
|
||||
customerJson: JSON.stringify(this.customerRows),
|
||||
@@ -1477,6 +1578,11 @@ export default {
|
||||
})
|
||||
.filter(Boolean);
|
||||
},
|
||||
normalizeReadonlyAmount(value) {
|
||||
return value === null || value === undefined || value === '' || Number(value) === -1
|
||||
? ''
|
||||
: value;
|
||||
},
|
||||
mergeCustomerOptions(options = [], selectedOptions = []) {
|
||||
const result = [...selectedOptions];
|
||||
options.forEach(option => {
|
||||
@@ -1505,7 +1611,7 @@ export default {
|
||||
changeType:
|
||||
row.changeType ||
|
||||
(String(row.currentNode || '').includes('备案') ? '项目备案调整' : '项目变更'),
|
||||
handler: row.handlerUserName || '',
|
||||
handler: this.resolveUserRealName(row, 'handler'),
|
||||
content: row.changeContent || '',
|
||||
reason: row.changeReason || '',
|
||||
status: row.approvalStatusName || row.approvalStatus || '',
|
||||
@@ -1529,9 +1635,18 @@ export default {
|
||||
},
|
||||
loadDeptOptions() {
|
||||
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) {
|
||||
return list.reduce((result, item) => {
|
||||
const label = item.title || item.deptName || item.name;
|
||||
@@ -1594,16 +1709,16 @@ export default {
|
||||
raw: item,
|
||||
};
|
||||
},
|
||||
handleCustomerChange(id) {
|
||||
if (!id) {
|
||||
handleCustomerChange(ids) {
|
||||
if (!ids || !ids.length) {
|
||||
this.customerRows = [];
|
||||
this.form.customerNames = '';
|
||||
this.form.customerJson = '';
|
||||
return;
|
||||
}
|
||||
this.loadCustomerDetailRow(id, 'customer').then(row => {
|
||||
this.customerRows = [row];
|
||||
this.form.customerNames = row.customer;
|
||||
Promise.all(ids.map(id => this.loadCustomerDetailRow(id, 'customer'))).then(rows => {
|
||||
this.customerRows = rows;
|
||||
this.form.customerNames = rows.map(item => item.customer).join('、');
|
||||
this.form.customerJson = JSON.stringify(this.customerRows);
|
||||
this.syncSelectedCustomerOptions();
|
||||
this.$refs.projectForm?.validateField('customerNames');
|
||||
@@ -1656,6 +1771,14 @@ export default {
|
||||
this.form[prop] =
|
||||
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) {
|
||||
this.form[prop] = String(value || '').replace(/[^\d]/g, '');
|
||||
},
|
||||
@@ -1836,13 +1959,36 @@ export default {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
&__material-card {
|
||||
margin-bottom: 16px;
|
||||
background: #fff;
|
||||
border-radius: 6px;
|
||||
padding: 12px 16px;
|
||||
padding: 14px 16px;
|
||||
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 {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user