🎨 异常跟进修排版并支持上传附件

跟进弹窗按编辑弹窗的排法改造:section-card 分组 + el-row/el-col 栅格,
依次为异常信息、现场照片、跟进、历史跟进。

新增跟进附件上传(v-model 走 vehicle-attachment-upload),
历史跟进表补附件列。跟进说明与附件至少填其一,二者都为空时前端拦下,
与后端校验一致。
This commit is contained in:
2026-10-05 01:09:39 +08:00
parent 2994539de4
commit 3d27e981e1
+120 -76
View File
@@ -283,68 +283,103 @@
</el-form>
<template v-else>
<div class="dialog-section-title">异常信息</div>
<dl class="exception-disposal-page__detail-grid">
<template v-for="field in detailFields" :key="field.prop">
<div :class="{ 'is-wide': field.span === 2 }">
<dt>{{ field.label }}</dt>
<dd>
<el-link v-if="field.link && detail[field.prop]" type="primary">
{{ detail[field.prop] }}
</el-link>
<span v-else>{{ detail[field.prop] || '-' }}</span>
</dd>
</div>
</template>
</dl>
<section-card title="异常信息">
<dl class="exception-disposal-page__detail-grid">
<template v-for="field in detailFields" :key="field.prop">
<div :class="{ 'is-wide': field.span === 2 }">
<dt>{{ field.label }}</dt>
<dd>
<el-link v-if="field.link && detail[field.prop]" type="primary">
{{ detail[field.prop] }}
</el-link>
<span v-else>{{ detail[field.prop] || '-' }}</span>
</dd>
</div>
</template>
</dl>
</section-card>
<div class="dialog-section-title">现场照片</div>
<div class="exception-disposal-page__photos">
<el-image
v-for="(url, index) in photoUrls"
:key="url"
:src="url"
:preview-src-list="photoUrls"
:initial-index="index"
fit="cover"
/>
<el-empty v-if="!photoUrls.length" description="暂无现场照片" :image-size="56" />
</div>
<el-form
v-if="dialog.mode === 'follow'"
ref="followFormRef"
:model="followForm"
:rules="followRules"
label-position="right"
label-width="auto"
class="exception-disposal-page__follow-form"
>
<el-form-item label="跟进说明" prop="followContent">
<el-input
v-model="followForm.followContent"
type="textarea"
maxlength="500"
show-word-limit
:rows="2"
placeholder="请输入"
<section-card title="现场照片">
<div class="exception-disposal-page__photos">
<el-image
v-for="(url, index) in photoUrls"
:key="url"
:src="url"
:preview-src-list="photoUrls"
:initial-index="index"
fit="cover"
/>
</el-form-item>
</el-form>
<el-empty v-if="!photoUrls.length" description="暂无现场照片" :image-size="56" />
</div>
</section-card>
<div class="dialog-section-title">历史跟进</div>
<el-table :data="detail.followRecords || []" border>
<el-table-column type="index" label="序号" width="70" align="center" />
<el-table-column
v-for="column in followColumns"
:key="column.prop"
:prop="column.prop"
:label="column.label"
:min-width="column.minWidth"
align="center"
show-overflow-tooltip
/>
</el-table>
<section-card v-if="dialog.mode === 'follow'" title="跟进">
<el-form
ref="followFormRef"
:model="followForm"
:rules="followRules"
label-position="right"
label-width="96px"
>
<el-row :gutter="18">
<el-col :span="24">
<el-form-item label="跟进说明" prop="followContent">
<el-input
v-model="followForm.followContent"
type="textarea"
maxlength="500"
show-word-limit
:rows="3"
placeholder="请输入"
/>
</el-form-item>
</el-col>
<el-col :span="24">
<el-form-item label="跟进附件">
<vehicle-attachment-upload
v-model="followForm.attachments"
:limit="20"
:max-size="500"
button-text="上传附件"
:show-tip="false"
/>
</el-form-item>
</el-col>
</el-row>
</el-form>
</section-card>
<section-card title="历史跟进">
<el-table :data="detail.followRecords || []" border>
<el-table-column type="index" label="序号" width="70" align="center" />
<el-table-column
v-for="column in followColumns"
:key="column.prop"
:prop="column.prop"
:label="column.label"
:min-width="column.minWidth"
align="center"
show-overflow-tooltip
/>
<el-table-column label="附件" min-width="120" align="center">
<template #default="{ row }">
<template v-if="parseAttachments(row.attachmentsJson).length">
<el-link
v-for="(file, index) in parseAttachments(row.attachmentsJson)"
:key="index"
type="primary"
class="exception-disposal-page__attachment-link"
:href="file.url"
target="_blank"
>
{{ file.name || '附件' + (index + 1) }}
</el-link>
</template>
<span v-else>-</span>
</template>
</el-table-column>
</el-table>
</section-card>
</template>
</div>
<template #footer>
@@ -417,12 +452,9 @@ export default {
page: { current: 1, size: 10, total: 0 },
dialog: { visible: false, mode: 'follow', loading: false, submitting: false },
detail: {},
followForm: { followContent: '' },
followForm: { followContent: '', attachments: [] },
followRules: {
followContent: [
{ required: true, message: '请输入跟进说明', trigger: 'blur' },
{ max: 500, message: '跟进说明不能超过500个字符', trigger: 'blur' },
],
followContent: [{ max: 500, message: '跟进说明不能超过500个字符', trigger: 'blur' }],
},
editForm: {
exceptionType: '',
@@ -503,7 +535,7 @@ export default {
},
async openDisposal(row, mode) {
this.dialog = { visible: true, mode, loading: true, submitting: false };
this.followForm = { followContent: '' };
this.followForm = { followContent: '', attachments: [] };
try {
const res = await api.getDetail(row.id);
this.detail = this.decorateRow(res.data?.data || res.data || res || {});
@@ -582,9 +614,18 @@ export default {
},
async submitFollow() {
await this.$refs.followFormRef.validate();
const followContent = (this.followForm.followContent || '').trim();
const attachmentsJson = this.followForm.attachments.length
? JSON.stringify(this.followForm.attachments)
: '';
// 后端要求二者至少有其一
if (!followContent && !attachmentsJson) {
this.$message.warning('请填写跟进说明或上传附件');
return;
}
this.dialog.submitting = true;
try {
await api.follow({ id: this.detail.id, followContent: this.followForm.followContent });
await api.follow({ id: this.detail.id, followContent, attachmentsJson });
this.$message.success('跟进成功');
this.dialog.visible = false;
this.loadTable();
@@ -592,6 +633,16 @@ export default {
this.dialog.submitting = false;
}
},
parseAttachments(value) {
if (!value) return [];
if (Array.isArray(value)) return value;
try {
const parsed = JSON.parse(value);
return Array.isArray(parsed) ? parsed.filter(item => item && item.url) : [];
} catch (error) {
return [];
}
},
async completeRow(row) {
await this.$confirm('确认完成该异常处置?', '提示', { type: 'warning' });
await api.complete({ id: row.id });
@@ -751,12 +802,9 @@ export default {
padding: 4px 0 0;
}
.dialog-section-title {
margin: 12px 0;
padding-left: 12px;
border-left: 4px solid #409eff;
font-size: 16px;
font-weight: 600;
.exception-disposal-page__attachment-link {
display: block;
margin-bottom: 2px;
}
.exception-disposal-page__detail-grid {
@@ -799,10 +847,6 @@ export default {
}
}
.exception-disposal-page__follow-form {
margin-top: 16px;
}
.exception-disposal-page__dialog :deep(.section-card + .section-card) {
margin-top: 16px;
}