✨ 结算单用印类型、合同甲乙丙列表与项目调整材料

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-11-09 22:53:55 +08:00
co-authored by Cursor
parent 748214a4b6
commit 1663b2d910
19 changed files with 695 additions and 35 deletions
+329 -6
View File
@@ -676,7 +676,7 @@
<div class="project-apply-form__material-head">
<div class="dialog-section-title project-apply-form__material-title">项目材料</div>
<span
v-if="missingRequiredAttachmentTypes.length"
v-if="!isChangeDialog && missingRequiredAttachmentTypes.length"
class="project-apply-form__material-warn"
>
未上传:{{ missingRequiredAttachmentTypes.join('、') }}
@@ -698,7 +698,11 @@
<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-select
v-model="row.fileType"
placeholder="请选择"
:disabled="dialogReadonly || isChangeDialog"
>
<el-option
v-for="item in projectAttachmentTypeOptions"
:key="item.value"
@@ -731,7 +735,7 @@
<el-input
v-model="row.description"
placeholder="请输入"
:disabled="dialogReadonly"
:disabled="dialogReadonly || isChangeDialog"
/>
</template>
</el-table-column>
@@ -741,14 +745,26 @@
<el-table-column prop="uploadUserName" label="上传人" min-width="140" align="center" />
<el-table-column prop="uploadTime" label="上传时间" min-width="170" align="center" sortable/>
<el-table-column label="操作" width="110" align="center">
<template #default="{ $index }">
<el-link v-if="!dialogReadonly" type="danger" @click="removeAttachment($index)"
>删除</el-link
<template #default="{ row, $index }">
<el-link
v-if="isChangeDialog && !dialogReadonly"
type="primary"
@click="openAdjustAttachmentDialog(row, $index)"
>
调整
</el-link>
<el-link
v-else-if="!dialogReadonly && !isChangeDialog"
type="danger"
@click="removeAttachment($index)"
>
删除
</el-link>
</template>
</el-table-column>
</el-table>
<vehicle-attachment-upload
v-if="!isChangeDialog"
v-model="attachmentRows"
class="project-apply-form__upload"
:readonly="dialogReadonly"
@@ -761,6 +777,57 @@
/>
</div>
<div v-if="isChangeDialog" class="project-apply-form__material-card">
<div class="project-apply-form__material-head">
<div class="dialog-section-title project-apply-form__material-title">调整材料</div>
</div>
<el-table :data="adjustAttachmentRows" border class="project-apply-form__material-table">
<el-table-column type="index" label="序号" width="80" align="center" />
<el-table-column label="原附件" min-width="180" align="center" show-overflow-tooltip>
<template #default="{ row }">
{{ attachmentName(row.originAttachment) || '-' }}
</template>
</el-table-column>
<el-table-column label="附件类型" min-width="160" align="center">
<template #default="{ row }">{{ row.fileType || '-' }}</template>
</el-table-column>
<el-table-column
label="文件名"
min-width="220"
align="center"
show-overflow-tooltip
class-name="is-ellipsis-link"
>
<template #default="{ row }">
<span
class="attachment-file-name"
role="button"
tabindex="0"
@click="previewAttachment(row)"
@keydown.enter="previewAttachment(row)"
@keydown.space.prevent="previewAttachment(row)"
>{{ attachmentName(row) }}</span>
</template>
</el-table-column>
<el-table-column label="附件描述" min-width="200" align="center" show-overflow-tooltip>
<template #default="{ row }">{{ row.description || '-' }}</template>
</el-table-column>
<el-table-column label="调整原因" min-width="180" align="center" show-overflow-tooltip>
<template #default="{ row }">{{ row.adjustReason || '-' }}</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 v-if="!dialogReadonly" label="操作" width="110" align="center">
<template #default="{ $index }">
<el-link type="danger" @click="removeAdjustAttachment($index)">删除</el-link>
</template>
</el-table-column>
</el-table>
</div>
<template v-if="showProjectContractSection">
<div class="dialog-section-title project-apply-form__table-title">项目合同</div>
<el-table
@@ -1011,6 +1078,86 @@
@close="attachmentImagePreviewVisible = false"
/>
<el-dialog
v-model="adjustAttachmentBox"
title="调整材料"
append-to-body
destroy-on-close
width="720px"
class="project-apply-adjust-dialog"
@closed="resetAdjustAttachmentForm"
>
<el-form
ref="adjustAttachmentFormRef"
:model="adjustAttachmentForm"
:rules="adjustAttachmentRules"
label-position="right"
label-width="auto"
>
<el-form-item label="原附件">
<span>{{ attachmentName(adjustAttachmentForm.originAttachment) || '-' }}</span>
</el-form-item>
<el-form-item label="附件类型" prop="fileType">
<el-select v-model="adjustAttachmentForm.fileType" placeholder="请选择" clearable>
<el-option
v-for="item in projectAttachmentTypeOptions"
:key="item.value"
:label="item.label"
:value="item.value"
/>
</el-select>
</el-form-item>
<el-form-item label="附件文件" prop="url">
<div class="project-apply-adjust-dialog__upload">
<span
v-if="attachmentName(adjustAttachmentForm)"
class="attachment-file-name"
role="button"
tabindex="0"
@click="previewAttachment(adjustAttachmentForm)"
@keydown.enter="previewAttachment(adjustAttachmentForm)"
@keydown.space.prevent="previewAttachment(adjustAttachmentForm)"
>
{{ attachmentName(adjustAttachmentForm) }}
</span>
<span v-else class="project-apply-adjust-dialog__upload-empty">请上传新材料</span>
<vehicle-attachment-upload
v-model="adjustAttachmentUploadRows"
:readonly="false"
:multiple="false"
:limit="1"
:file-types="attachmentFileTypes"
:max-size="50"
:show-file-list="false"
button-text="上传附件"
tip="支持pdf、bmp、jpeg、png、jpg、doc、docx、ppt、pptx、xlsx、xls、eml、msg、zip,单个文件不超过50M"
@change="handleAdjustAttachmentUploadChange"
/>
</div>
</el-form-item>
<el-form-item label="附件描述" prop="description">
<el-input v-model="adjustAttachmentForm.description" placeholder="请输入" maxlength="200" />
</el-form-item>
<el-form-item label="文件大小">
<span>{{ formatFileSize(adjustAttachmentForm.size) || '-' }}</span>
</el-form-item>
<el-form-item label="调整原因" prop="adjustReason" class="project-apply-form__change-textarea">
<el-input
v-model="adjustAttachmentForm.adjustReason"
type="textarea"
:rows="3"
maxlength="2000"
show-word-limit
placeholder="请输入调整原因"
/>
</el-form-item>
</el-form>
<template #footer>
<el-button @click="adjustAttachmentBox = false">取消</el-button>
<el-button type="primary" @click="confirmAdjustAttachment">确定</el-button>
</template>
</el-dialog>
<el-dialog
v-model="userBox"
title="选择用户"
@@ -1166,6 +1313,7 @@ const emptyForm = () => ({
customerJson: '',
carrierJson: '',
attachmentsJson: '',
adjustAttachmentsJson: '',
situationRemark: '',
projectIntro: '',
profitRemark: '',
@@ -1175,6 +1323,24 @@ const emptyForm = () => ({
changeReason: '',
});
const emptyAdjustAttachmentForm = () => ({
uid: '',
fileType: '',
originalName: '',
name: '',
fileName: '',
url: '',
link: '',
fileUrl: '',
description: '',
size: '',
uploadUserName: '',
uploadTime: '',
adjustReason: '',
originAttachment: null,
originKey: '',
});
const optionalSentinelFields = [
'projectScale',
'estimatedProfit',
@@ -1449,6 +1615,29 @@ export default {
customerRows: [],
carrierRows: [],
attachmentRows: [],
adjustAttachmentRows: [],
adjustAttachmentBox: false,
adjustAttachmentForm: emptyAdjustAttachmentForm(),
adjustAttachmentUploadRows: [],
adjustAttachmentRules: {
fileType: [{ required: true, message: '请选择附件类型', trigger: 'change' }],
url: [
{
validator: (rule, value, callback) => {
if (!this.attachmentUrl(this.adjustAttachmentForm)) {
callback(new Error('请上传新材料'));
return;
}
callback();
},
trigger: 'change',
},
],
adjustReason: [
{ required: true, message: '请输入调整原因', trigger: 'blur' },
{ max: 2000, message: '调整原因不能超过2000个字符', trigger: 'blur' },
],
},
selectedAttachmentRows: [],
attachmentImagePreviewVisible: false,
attachmentImagePreviewUrls: [],
@@ -2207,6 +2396,9 @@ export default {
this.customerRows = [];
this.carrierRows = [];
this.attachmentRows = [];
this.adjustAttachmentRows = [];
this.adjustAttachmentBox = false;
this.resetAdjustAttachmentForm();
this.selectedAttachmentRows = [];
this.changeRows = [];
this.projectContractRows = [];
@@ -2248,6 +2440,7 @@ export default {
this.carrierRows = carrierRows;
this.attachmentRows = this.parseJsonArray(displayRow.attachmentsJson);
this.normalizeAttachmentFileTypes();
this.adjustAttachmentRows = this.parseJsonArray(displayRow.adjustAttachmentsJson);
this.selectedAttachmentRows = [];
this.changeRows = this.buildChangeRows(displayRow);
this.selectedCustomerId = this.customerRows
@@ -2556,6 +2749,9 @@ export default {
customerJson: JSON.stringify(this.customerRows),
carrierJson: JSON.stringify(this.carrierRows),
attachmentsJson: JSON.stringify(this.attachmentRows),
adjustAttachmentsJson: this.isChangeDialog
? JSON.stringify(this.adjustAttachmentRows || [])
: undefined,
situationRemark: JSON.stringify({
projectIntro: this.form.projectIntro || '',
profitRemark: this.form.profitRemark || '',
@@ -2571,6 +2767,9 @@ export default {
if (this.isNewProjectDialog && !options.includeChangeType) {
delete submitRow.changeReason;
}
if (!this.isChangeDialog) {
delete submitRow.adjustAttachmentsJson;
}
['projectIntro', 'profitRemark', 'riskPoint', 'emergencyPlan'].forEach(
key => delete submitRow[key]
);
@@ -3191,6 +3390,112 @@ export default {
removeAttachment(index) {
this.attachmentRows.splice(index, 1);
},
attachmentIdentity(row = {}) {
const uid = String(row.uid || '').trim();
if (uid) return `uid:${uid}`;
const url = String(this.attachmentUrl(row) || '').trim();
if (url) return `url:${url}`;
const name = String(this.attachmentName(row) || '').trim();
const fileType = String(row.fileType || '').trim();
if (name || fileType) return `name:${fileType}|${name}`;
return '';
},
openAdjustAttachmentDialog(row) {
const originKey = this.attachmentIdentity(row);
const existing = this.adjustAttachmentRows.find(item => item.originKey === originKey);
if (existing) {
this.adjustAttachmentForm = {
...emptyAdjustAttachmentForm(),
...existing,
originAttachment: existing.originAttachment || { ...row },
originKey,
};
this.adjustAttachmentUploadRows = this.attachmentUrl(existing)
? [{ ...existing }]
: [];
} else {
this.adjustAttachmentForm = {
...emptyAdjustAttachmentForm(),
fileType: row.fileType || '',
description: row.description || '',
originAttachment: { ...row },
originKey,
};
this.adjustAttachmentUploadRows = [];
}
this.adjustAttachmentBox = true;
this.$nextTick(() => this.$refs.adjustAttachmentFormRef?.clearValidate?.());
},
resetAdjustAttachmentForm() {
this.adjustAttachmentForm = emptyAdjustAttachmentForm();
this.adjustAttachmentUploadRows = [];
this.$refs.adjustAttachmentFormRef?.clearValidate?.();
},
handleAdjustAttachmentUploadChange(list) {
const latest = Array.isArray(list) && list.length ? list[list.length - 1] : null;
if (!latest) {
this.adjustAttachmentForm = {
...this.adjustAttachmentForm,
uid: '',
originalName: '',
name: '',
fileName: '',
url: '',
link: '',
fileUrl: '',
size: '',
uploadUserName: '',
uploadTime: '',
};
this.adjustAttachmentUploadRows = [];
return;
}
const userName = this.userInfo?.realName || this.userInfo?.userName || '';
const uploadTime = this.$dayjs().format('YYYY-MM-DD HH:mm:ss');
this.adjustAttachmentForm = {
...this.adjustAttachmentForm,
...latest,
fileType:
this.adjustAttachmentForm.fileType || this.resolveAttachmentFileType(latest),
description: this.adjustAttachmentForm.description || latest.description || '',
uploadUserName: latest.uploadUserName || userName,
uploadTime: latest.uploadTime || uploadTime,
};
this.adjustAttachmentUploadRows = [{ ...latest }];
this.$refs.adjustAttachmentFormRef?.validateField?.('url');
},
confirmAdjustAttachment() {
this.$refs.adjustAttachmentFormRef?.validate(valid => {
if (!valid) return;
if (!this.attachmentUrl(this.adjustAttachmentForm)) {
this.$message.warning('请上传新材料');
return;
}
const nextRow = {
...this.adjustAttachmentForm,
fileType: this.adjustAttachmentForm.fileType,
description: this.adjustAttachmentForm.description || '',
adjustReason: String(this.adjustAttachmentForm.adjustReason || '').trim(),
originAttachment: this.adjustAttachmentForm.originAttachment
? { ...this.adjustAttachmentForm.originAttachment }
: null,
originKey: this.adjustAttachmentForm.originKey,
};
const index = this.adjustAttachmentRows.findIndex(
item => item.originKey === nextRow.originKey
);
if (index >= 0) {
this.adjustAttachmentRows.splice(index, 1, nextRow);
} else {
this.adjustAttachmentRows.push(nextRow);
}
this.adjustAttachmentBox = false;
this.$message.success('已加入调整材料');
});
},
removeAdjustAttachment(index) {
this.adjustAttachmentRows.splice(index, 1);
},
formatFileSize(size) {
const number = Number(size);
if (!number) return '';
@@ -3708,6 +4013,24 @@ export default {
}
}
.project-apply-adjust-dialog {
&__upload {
display: flex;
flex-direction: column;
gap: 8px;
width: 100%;
}
&__upload-empty {
color: #909399;
}
:deep(.el-select),
:deep(.el-input) {
width: 100%;
}
}
@media (max-width: 1200px) {
.project-apply-form {
&__grid {