完善变更记录、客商评分与车辆表单

变更记录详情统一为居中分页弹窗,并去掉经办人等信息行。
客商评分支持删除、提交前自评校验、复评后才能审批,以及 0 分提交。
车辆外廓尺寸和载质量单位改到输入框内,认证审核标签按四字换行,使用部门必填并默认当前部门。
临时额度的 -1 占位金额显示为空。新增项目编号可留空由系统生成。
修复缓存总单页在打开新增项目时误请求缺少 id 的详情接口。
详情页状态与运输方式标签样式统一。

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-09-22 20:13:44 +08:00
parent 3a8442e522
commit ce1ce02551
13 changed files with 532 additions and 329 deletions
@@ -0,0 +1,108 @@
<template>
<el-dialog
:model-value="modelValue"
title="变更记录详情"
append-to-body
destroy-on-close
align-center
width="1100px"
class="change-record-detail-dialog"
@update:model-value="$emit('update:modelValue', $event)"
@open="resetPage"
>
<el-table
:data="pageRows"
border
max-height="60vh"
:show-overflow-tooltip="false"
>
<el-table-column prop="field" label="变更字段" min-width="180" />
<el-table-column
prop="before"
label="变更前"
min-width="360"
class-name="change-record-detail-value"
/>
<el-table-column
prop="after"
label="变更后"
min-width="500"
class-name="change-record-detail-value"
/>
</el-table>
<el-empty v-if="!total" description="暂无变更内容" :image-size="60" />
<div class="change-record-detail-dialog__pager">
<el-pagination
v-model:current-page="currentPage"
v-model:page-size="pageSize"
background
:total="total"
:page-sizes="[10, 20, 50, 100]"
layout="total, sizes, prev, pager, next, jumper"
/>
</div>
<template #footer>
<el-button type="primary" @click="$emit('update:modelValue', false)">关闭</el-button>
</template>
</el-dialog>
</template>
<script>
export default {
name: 'ChangeRecordDetailDialog',
props: {
modelValue: {
type: Boolean,
default: false,
},
rows: {
type: Array,
default: () => [],
},
},
emits: ['update:modelValue'],
data() {
return {
currentPage: 1,
pageSize: 10,
};
},
computed: {
total() {
return this.rows.length;
},
pageRows() {
const start = (this.currentPage - 1) * this.pageSize;
return this.rows.slice(start, start + this.pageSize);
},
},
watch: {
rows() {
const maxPage = Math.max(1, Math.ceil(this.total / this.pageSize) || 1);
if (this.currentPage > maxPage) this.currentPage = 1;
},
},
methods: {
resetPage() {
this.currentPage = 1;
this.pageSize = 10;
},
},
};
</script>
<style lang="scss" scoped>
.change-record-detail-dialog__pager {
display: flex;
justify-content: flex-end;
margin-top: 12px;
}
:deep(.change-record-detail-value .cell) {
white-space: pre-wrap;
word-break: break-word;
overflow: visible;
text-overflow: clip;
line-height: 1.6;
}
</style>
@@ -1,7 +1,17 @@
<template> <template>
<div v-loading="loading" class="master-detail"> <div v-loading="loading" class="master-detail">
<section v-if="master" class="detail-overview"> <section v-if="master" class="detail-overview">
<div class="detail-heading"><div><h2>多联总单详情 <span>|</span> {{ master.masterNo }}</h2><el-tag :type="statusType(master.businessStatus)" class="status-text">{{ statusName(master.businessStatus) }}</el-tag><el-tag v-if="transportFlowLabel" type="info" class="transport-flow-tag">{{ transportFlowLabel }}</el-tag></div></div> <div class="detail-heading">
<div>
<h2>多联总单详情 <span>|</span> {{ master.masterNo }}</h2>
<el-tag :type="statusType(master.businessStatus)">{{
statusName(master.businessStatus)
}}</el-tag>
<el-tag v-if="transportFlowLabel" type="primary" class="transport-flow-tag">{{
transportFlowLabel
}}</el-tag>
</div>
</div>
<dl class="detail-meta"> <dl class="detail-meta">
<div> <div>
<dt>客户</dt> <dt>客户</dt>
@@ -132,6 +142,7 @@ export default {
dateRange() { return this.master?.planStartTime && this.master?.planEndTime ? `${String(this.master.planStartTime).slice(0, 10)} ~ ${String(this.master.planEndTime).slice(0, 10)}` : '-'; }, dateRange() { return this.master?.planStartTime && this.master?.planEndTime ? `${String(this.master.planStartTime).slice(0, 10)} ~ ${String(this.master.planEndTime).slice(0, 10)}` : '-'; },
}, },
async mounted() { async mounted() {
if (!this.id) return;
this.loading = true; this.loading = true;
try { try {
const res = await api.getDetail(this.id); const res = await api.getDetail(this.id);
@@ -282,8 +293,36 @@ export default {
<style scoped lang="scss"> <style scoped lang="scss">
.master-detail { padding-bottom: 20px; color: #303133; } .master-detail { padding-bottom: 20px; color: #303133; }
.detail-overview, .execution-detail-card { margin-bottom: 8px; border: 1px solid #eff1f7; background: #fff; } .detail-overview, .execution-detail-card { margin-bottom: 8px; border: 1px solid #eff1f7; background: #fff; }
.detail-heading { display: flex; align-items: center; justify-content: space-between; padding: 18px 24px; h2 { display: inline-block; margin: 0 16px 0 0; font-size: 20px; } h2 span { margin: 0 8px; color: #909399; font-weight: 400; } } .detail-heading {
.transport-flow-tag { margin-left: 8px; } display: flex;
align-items: center;
justify-content: space-between;
padding: 18px 24px;
h2 {
display: inline-block;
margin: 0 16px 0 0;
font-size: 20px;
}
h2 span {
margin: 0 8px;
color: #909399;
font-weight: 400;
}
:deep(.el-tag) {
height: 28px;
padding: 0 12px;
border: 0;
border-radius: 8px;
font-size: 13px;
line-height: 28px;
}
}
.transport-flow-tag {
margin-left: 8px;
}
.detail-meta { display: grid; grid-template-columns: repeat(4, minmax(0, 1fr)); gap: 18px 24px; margin: 0; padding: 0 24px 20px; dt { margin-bottom: 6px; color: #909399; font-size: 13px; } dd { display: flex; flex-wrap: wrap; gap: 8px 16px; margin: 0; color: #303133; font-size: 14px; word-break: break-all; } :deep(.el-link) { font-size: 14px; } } .detail-meta { display: grid; grid-template-columns: repeat(4, minmax(0, 1fr)); gap: 18px 24px; margin: 0; padding: 0 24px 20px; dt { margin-bottom: 6px; color: #909399; font-size: 13px; } dd { display: flex; flex-wrap: wrap; gap: 8px 16px; margin: 0; color: #303133; font-size: 14px; word-break: break-all; } :deep(.el-link) { font-size: 14px; } }
.route-map { display: flex; align-items: flex-start; padding: 16px 24px 20px; border-top: 1px solid #eff1f7; overflow-x: auto; } .route-map { display: flex; align-items: flex-start; padding: 16px 24px 20px; border-top: 1px solid #eff1f7; overflow-x: auto; }
.route-map__node { display: grid; flex: 0 0 150px; justify-items: center; gap: 6px; text-align: center; strong { font-size: 16px; white-space: nowrap; } small { color: #606266; white-space: nowrap; } } .route-map__node { display: grid; flex: 0 0 150px; justify-items: center; gap: 6px; text-align: center; strong { font-size: 16px; white-space: nowrap; } small { color: #606266; white-space: nowrap; } }
@@ -295,6 +295,7 @@ export default {
}, },
methods: { methods: {
async load() { async load() {
if (!this.id) return;
this.loading = true; this.loading = true;
try { try {
const res = await api.getDetail(this.id); const res = await api.getDetail(this.id);
@@ -1774,12 +1774,17 @@
<div class="waybill-manage-page__waybill-heading"> <div class="waybill-manage-page__waybill-heading">
<strong>运单详情</strong> <strong>运单详情</strong>
<span>{{ detailRow.waybillNo || '-' }}</span> <span>{{ detailRow.waybillNo || '-' }}</span>
<span class="detail-status-text status-text-success">{{ <el-tag :type="statusTagType(detailRow.businessStatus)">
displayStatus(detailRow, 'businessStatus') {{ displayStatus(detailRow, 'businessStatus') }}
}}</span> </el-tag>
<span class="detail-status-text detail-transport-type"> <el-tag type="primary">
{{ getTransportTypeLabel(waybillTransportMode(detailRow)) || '-' }} {{
</span> detailRow.transportTypeName ||
getTransportTypeLabel(detailRow.transportType) ||
waybillTransportMode(detailRow) ||
'-'
}}
</el-tag>
<el-link <el-link
v-if="detailRow.id" v-if="detailRow.id"
class="waybill-manage-page__waybill-route-change-link" class="waybill-manage-page__waybill-route-change-link"
@@ -2184,7 +2189,11 @@
/> />
</div> </div>
</el-tab-pane> </el-tab-pane>
<el-tab-pane label="司机上传" name="driverUpload"> <el-tab-pane
v-if="waybillDriverUploads.length"
label="司机上传"
name="driverUpload"
>
<div <div
v-loading="waybillPunchRecordsLoading" v-loading="waybillPunchRecordsLoading"
class="waybill-manage-page__driver-upload-grid" class="waybill-manage-page__driver-upload-grid"
@@ -2201,11 +2210,6 @@
{{ photo.label || '凭证' }} {{ photo.label || '凭证' }}
</div> </div>
</div> </div>
<el-empty
v-if="!waybillPunchRecordsLoading && !waybillDriverUploads.length"
description="暂无司机上传数据"
:image-size="50"
/>
</div> </div>
</el-tab-pane> </el-tab-pane>
</el-tabs> </el-tabs>
@@ -5265,7 +5269,9 @@ export default {
: defaultText; : defaultText;
}, },
statusTagType(status) { statusTagType(status) {
if ([1, 'pending', 'processing', 'approved', 'change_approved'].includes(status)) { if (
[1, 'pending', 'processing', 'running', 'approved', 'change_approved'].includes(status)
) {
return 'success'; return 'success';
} }
if ([2, 'cancelled', 'withdrawn', 'draft'].includes(status)) { if ([2, 'cancelled', 'withdrawn', 'draft'].includes(status)) {
@@ -9763,6 +9769,15 @@ export default {
strong { strong {
font-size: 20px; font-size: 20px;
} }
:deep(.el-tag) {
height: 28px;
padding: 0 12px;
border: 0;
border-radius: 8px;
font-size: 13px;
line-height: 28px;
}
} }
&__waybill-route-change-link { &__waybill-route-change-link {
+12 -57
View File
@@ -966,44 +966,10 @@
@close="attachmentImagePreviewVisible = false" @close="attachmentImagePreviewVisible = false"
/> />
<el-dialog <change-record-detail-dialog
v-model="detailChangeRecordVisible" v-model="detailChangeRecordVisible"
title="变更记录详情" :rows="detailChangeRecordDetailRows"
append-to-body />
destroy-on-close
width="1100px"
top="10px"
class="contract-change-record-detail-dialog"
>
<div v-if="detailChangeRecord" class="contract-change-record-detail-meta">
<span>经办人:{{ detailChangeRecord.handlerUserName || '-' }}</span>
<span>变更类型:{{ detailChangeRecord.changeType || '-' }}</span>
<span>状态:{{ detailChangeRecord.statusName || detailChangeRecord.status || '-' }}</span>
</div>
<el-table :data="detailChangeRecordDetailRows" 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="contract-change-record-detail-value"
/>
<el-table-column
prop="after"
label="变更后"
min-width="500"
class-name="contract-change-record-detail-value"
/>
</el-table>
<el-empty
v-if="!detailChangeRecordDetailRows.length"
description="暂无变更内容"
:image-size="60"
/>
<template #footer>
<el-button type="primary" @click="detailChangeRecordVisible = false">关闭</el-button>
</template>
</el-dialog>
<billing-plan-editor <billing-plan-editor
v-model="detailBillingPlanBox" v-model="detailBillingPlanBox"
@@ -1042,6 +1008,7 @@ import { getToken } from '@/utils/auth';
import { downloadFileByUrl, downloadXls } from '@/utils/util'; import { downloadFileByUrl, downloadXls } from '@/utils/util';
import { submitMkApprovalFlow } from '@/utils/mk-approval'; import { submitMkApprovalFlow } from '@/utils/mk-approval';
import BillingPlanEditor from './components/billing-plan-editor.vue'; import BillingPlanEditor from './components/billing-plan-editor.vue';
import ChangeRecordDetailDialog from '@/components/change-record-detail-dialog/main.vue';
import ContractAttachmentSection, { import ContractAttachmentSection, {
attachmentName as sharedAttachmentName, attachmentName as sharedAttachmentName,
attachmentUrl as sharedAttachmentUrl, attachmentUrl as sharedAttachmentUrl,
@@ -1194,7 +1161,14 @@ const attachmentViewerPlugins = [
export default { export default {
name: 'ContractManage', name: 'ContractManage',
components: { ContractAttachmentSection, BillingPlanEditor, ElImageViewer, OpenFileViewer, PdfPreview }, components: {
ContractAttachmentSection,
BillingPlanEditor,
ChangeRecordDetailDialog,
ElImageViewer,
OpenFileViewer,
PdfPreview,
},
data() { data() {
return { return {
api, api,
@@ -2969,25 +2943,6 @@ export default {
word-break: break-word; word-break: break-word;
} }
.contract-change-record-detail-meta {
display: flex;
flex-wrap: wrap;
gap: 8px 32px;
margin-bottom: 16px;
color: #606266;
}
:deep(.contract-change-record-detail-dialog .el-dialog__body) {
max-height: 65vh;
overflow: auto;
padding-top: 12px;
}
:deep(.contract-change-record-detail-dialog .contract-change-record-detail-value .cell) {
white-space: pre-wrap;
word-break: break-all;
}
:global(.avue--collapse .contract-manage-page__footer) { :global(.avue--collapse .contract-manage-page__footer) {
left: 60px; left: 60px;
} }
+16 -12
View File
@@ -395,14 +395,8 @@
<div class="loading-detail__summary"> <div class="loading-detail__summary">
<div class="loading-detail__heading">配载单详情</div> <div class="loading-detail__heading">配载单详情</div>
<div>{{ dialogForm.loadingNo || '-' }}</div> <div>{{ dialogForm.loadingNo || '-' }}</div>
<div> <el-tag :type="detailStatusType">{{ statusText(dialogForm) }}</el-tag>
<el-tag :type="detailStatusType" class="status-text">{{ <el-tag type="primary">{{ transportTypeLabel(dialogForm.transportType) }}</el-tag>
statusText(dialogForm)
}}</el-tag>
</div>
<div>
<el-tag type="warning">{{ transportTypeLabel(dialogForm.transportType) }}</el-tag>
</div>
</div> </div>
<div class="loading-detail__task-row"> <div class="loading-detail__task-row">
<div class="loading-detail__field"> <div class="loading-detail__field">
@@ -1683,11 +1677,12 @@ export default {
const typeMap = { const typeMap = {
pending: 'success', pending: 'success',
running: 'success', running: 'success',
completed: 'info', processing: 'success',
completed: 'primary',
cancelled: 'info', cancelled: 'info',
draft: 'warning', draft: 'info',
}; };
return typeMap[this.dialogForm.businessStatus] || 'info'; return typeMap[this.dialogForm.businessStatus] || 'warning';
}, },
taskSummary() { taskSummary() {
return ( return (
@@ -3604,8 +3599,17 @@ export default {
display: flex; display: flex;
align-items: center; align-items: center;
flex-wrap: wrap; flex-wrap: wrap;
gap: 24px; gap: 12px;
padding: 4px 0 16px; padding: 4px 0 16px;
:deep(.el-tag) {
height: 28px;
padding: 0 12px;
border: 0;
border-radius: 8px;
font-size: 13px;
line-height: 28px;
}
} }
.loading-detail__heading { .loading-detail__heading {
font-size: 20px; font-size: 20px;
+11 -1
View File
@@ -132,7 +132,7 @@
/> />
</template> </template>
<master-order-dispatch v-else-if="mode === 'dispatch'" :id="routeId" @back="goList" /> <master-order-dispatch v-else-if="mode === 'dispatch'" :id="routeId" @back="goList" />
<master-order-detail v-else :id="routeId" /> <master-order-detail v-else-if="mode === 'detail'" :id="routeId" />
<el-dialog v-model="confirm.visible" title="提示" width="400px" <el-dialog v-model="confirm.visible" title="提示" width="400px"
><span>{{ confirm.message }}</span ><span>{{ confirm.message }}</span
><template #footer ><template #footer
@@ -153,6 +153,10 @@ export default {
components: { MasterOrderEditor, MasterOrderDispatch, MasterOrderDetail }, components: { MasterOrderEditor, MasterOrderDispatch, MasterOrderDetail },
data() { data() {
return { return {
// 实例创建时锁定所属路由。keep-alive 缓存后 $route 会变成其它页面,
// 不能再用全局 query.mode / query.id 决定本页形态,否则打开「新增项目管理」
//mode=add 且无 id)会落入详情分支并请求缺少 id 的详情接口。
routePathLocked: this.$route.path,
searchExpanded: false, searchExpanded: false,
loading: false, loading: false,
records: [], records: [],
@@ -180,10 +184,15 @@ export default {
}; };
}, },
computed: { computed: {
isOwnedRoute() {
return this.$route.path === this.routePathLocked;
},
mode() { mode() {
if (!this.isOwnedRoute) return 'list';
return this.$route.query.mode || 'list'; return this.$route.query.mode || 'list';
}, },
routeId() { routeId() {
if (!this.isOwnedRoute) return '';
return this.$route.query.id; return this.$route.query.id;
}, },
masterEditorTitle() { masterEditorTitle() {
@@ -194,6 +203,7 @@ export default {
'$route.query': { '$route.query': {
immediate: true, immediate: true,
handler() { handler() {
if (!this.isOwnedRoute) return;
this.syncTagTitle(); this.syncTagTitle();
if (this.mode === 'list') this.load(); if (this.mode === 'list') this.load();
}, },
+8 -58
View File
@@ -196,7 +196,7 @@
<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="dialogType === 'add' ? '若不填写,系统自动生成' : '请输入'"
:disabled="dialogType === 'edit'" :disabled="dialogType === 'edit'"
/> />
</el-form-item> </el-form-item>
@@ -703,6 +703,7 @@
label="变更内容" label="变更内容"
min-width="180" min-width="180"
align="center" align="center"
:show-overflow-tooltip="false"
> >
<template #default="{ row }"> <template #default="{ row }">
<el-tooltip placement="top" :show-after="200"> <el-tooltip placement="top" :show-after="200">
@@ -730,43 +731,10 @@
</el-table-column> </el-table-column>
</el-table> </el-table>
<el-dialog <change-record-detail-dialog
v-model="changeRecordDetailVisible" v-model="changeRecordDetailVisible"
title="变更记录详情" :rows="changeRecordDetailRows"
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>
@@ -947,6 +915,7 @@ import { submitMkApprovalFlow } from '@/utils/mk-approval';
import { ElImageViewer } from 'element-plus'; import { ElImageViewer } from 'element-plus';
import { OpenFileViewer } from '@open-file-viewer/vue'; import { OpenFileViewer } from '@open-file-viewer/vue';
import PdfPreview from '@/components/pdf-preview/main.vue'; import PdfPreview from '@/components/pdf-preview/main.vue';
import ChangeRecordDetailDialog from '@/components/change-record-detail-dialog/main.vue';
import { import {
fallbackPlugin, fallbackPlugin,
imagePlugin, imagePlugin,
@@ -1066,6 +1035,7 @@ export default {
name: 'ProjectApply', name: 'ProjectApply',
components: { components: {
CustomerArchive: defineAsyncComponent(() => import('@/views/vehicle/customer-archive.vue')), CustomerArchive: defineAsyncComponent(() => import('@/views/vehicle/customer-archive.vue')),
ChangeRecordDetailDialog,
ElImageViewer, ElImageViewer,
OpenFileViewer, OpenFileViewer,
PdfPreview, PdfPreview,
@@ -1520,6 +1490,7 @@ export default {
this.fillDefaultUsers(); this.fillDefaultUsers();
return; return;
} }
if (!id) return;
this.api.getDetail(id).then(res => { this.api.getDetail(id).then(res => {
const detail = res.data.data || {}; const detail = res.data.data || {};
const isChange = type === 'change'; const isChange = type === 'change';
@@ -2999,27 +2970,6 @@ 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;
+32 -10
View File
@@ -610,7 +610,12 @@ export default {
return value; return value;
}, },
applyDetail(detail) { applyDetail(detail) {
this.form = { ...detail }; this.form = {
...detail,
projectFundLimit: this.blankSentinelAmount(detail.projectFundLimit),
usedFundLimit: this.blankSentinelAmount(detail.usedFundLimit),
remainingFundLimit: this.blankSentinelAmount(detail.remainingFundLimit),
};
this.selectedProjectId = detail.projectId || ''; this.selectedProjectId = detail.projectId || '';
this.attachmentRows = this.parseJsonArray(detail.attachmentsJson); this.attachmentRows = this.parseJsonArray(detail.attachmentsJson);
this.selectedAttachments = []; this.selectedAttachments = [];
@@ -620,6 +625,9 @@ export default {
const { projectQuotaInfoTitle, temporaryCreditInfoTitle, ...payload } = row || {}; const { projectQuotaInfoTitle, temporaryCreditInfoTitle, ...payload } = row || {};
return { return {
...payload, ...payload,
projectFundLimit: this.toOptionalAmount(payload.projectFundLimit),
usedFundLimit: this.toOptionalAmount(payload.usedFundLimit),
remainingFundLimit: this.toOptionalAmount(payload.remainingFundLimit),
applyLimit: payload.applyLimit === '' ? '' : Number(payload.applyLimit), applyLimit: payload.applyLimit === '' ? '' : Number(payload.applyLimit),
attachmentsJson: JSON.stringify(this.attachmentRows), attachmentsJson: JSON.stringify(this.attachmentRows),
}; };
@@ -765,8 +773,8 @@ export default {
projectName: project.projectName || '', projectName: project.projectName || '',
projectCode: project.projectCode || '', projectCode: project.projectCode || '',
undertakeDeptName: project.undertakeDeptName || '', undertakeDeptName: project.undertakeDeptName || '',
projectFundLimit: project.fundLimit || project.projectFundLimit || 0, projectFundLimit: this.blankSentinelAmount(project.fundLimit ?? project.projectFundLimit),
usedFundLimit: project.usedFundLimit || 0, usedFundLimit: this.blankSentinelAmount(project.usedFundLimit),
}); });
this.form.remainingFundLimit = this.calculateRemainingFundLimit(); this.form.remainingFundLimit = this.calculateRemainingFundLimit();
getProjectDetail(project.id).then(res => { getProjectDetail(project.id).then(res => {
@@ -774,18 +782,32 @@ export default {
Object.assign(this.form, { Object.assign(this.form, {
projectCode: detail.projectCode || this.form.projectCode, projectCode: detail.projectCode || this.form.projectCode,
undertakeDeptName: detail.undertakeDeptName || this.form.undertakeDeptName, undertakeDeptName: detail.undertakeDeptName || this.form.undertakeDeptName,
projectFundLimit: projectFundLimit: this.blankSentinelAmount(
detail.fundLimit ?? detail.projectFundLimit ?? this.form.projectFundLimit, detail.fundLimit ?? detail.projectFundLimit ?? this.form.projectFundLimit
usedFundLimit: detail.usedFundLimit ?? this.form.usedFundLimit, ),
usedFundLimit: this.blankSentinelAmount(
detail.usedFundLimit ?? this.form.usedFundLimit
),
}); });
this.form.remainingFundLimit = this.calculateRemainingFundLimit(); this.form.remainingFundLimit = this.calculateRemainingFundLimit();
}); });
}, },
blankSentinelAmount(value) {
if (value === undefined || value === null || value === '') return '';
return Number(value) === -1 ? '' : value;
},
toOptionalAmount(value) {
const amount = this.blankSentinelAmount(value);
return amount === '' ? null : amount;
},
calculateRemainingFundLimit() { calculateRemainingFundLimit() {
const total = Number(this.form.projectFundLimit); const total = this.blankSentinelAmount(this.form.projectFundLimit);
const used = Number(this.form.usedFundLimit); const used = this.blankSentinelAmount(this.form.usedFundLimit);
if (!Number.isFinite(total) || !Number.isFinite(used)) return ''; if (total === '' || used === '') return '';
return Math.round((total - used + Number.EPSILON) * 100) / 100; const totalAmount = Number(total);
const usedAmount = Number(used);
if (!Number.isFinite(totalAmount) || !Number.isFinite(usedAmount)) return '';
return Math.round((totalAmount - usedAmount + Number.EPSILON) * 100) / 100;
}, },
handleAmountInput(value) { handleAmountInput(value) {
const text = String(value || '').replace(/[^\d.]/g, ''); const text = String(value || '').replace(/[^\d.]/g, '');
@@ -850,35 +850,10 @@
</template> </template>
</el-dialog> </el-dialog>
<el-dialog <change-record-detail-dialog
v-model="adjustChangeRecordVisible" v-model="adjustChangeRecordVisible"
title="变更记录详情" :rows="adjustChangeRecordDetailRows"
append-to-body />
destroy-on-close
width="1100px"
top="10px"
class="pre-settlement-change-record-detail-dialog"
>
<div v-if="adjustChangeRecord" class="pre-settlement-change-record-detail-meta">
<span>变更日期:{{ adjustChangeRecord.changeTime || '-' }}</span>
<span>经办人:{{ adjustChangeRecord.operatorName || '-' }}</span>
<span>变更类型:{{ adjustChangeRecord.changeType || '-' }}</span>
<span>状态:已生效</span>
</div>
<el-table :data="adjustChangeRecordDetailRows" border :show-overflow-tooltip="false">
<el-table-column prop="field" label="变更字段" min-width="220" />
<el-table-column prop="before" label="变更前" min-width="330" />
<el-table-column prop="after" label="变更后" min-width="420" />
</el-table>
<el-empty
v-if="!adjustChangeRecordDetailRows.length"
description="暂无变更内容"
:image-size="60"
/>
<template #footer>
<el-button type="primary" @click="adjustChangeRecordVisible = false">关闭</el-button>
</template>
</el-dialog>
<el-dialog <el-dialog
v-model="billingRuleDialog.visible" v-model="billingRuleDialog.visible"
@@ -950,6 +925,7 @@
import { h } from 'vue'; import { h } from 'vue';
import { mapGetters } from 'vuex'; import { mapGetters } from 'vuex';
import { InfoFilled } from '@element-plus/icons-vue'; import { InfoFilled } from '@element-plus/icons-vue';
import ChangeRecordDetailDialog from '@/components/change-record-detail-dialog/main.vue';
import { getMkPublicDetail } from '@/api/mk-process'; import { getMkPublicDetail } from '@/api/mk-process';
import { import {
adjustDetail, adjustDetail,
@@ -989,7 +965,7 @@ import * as XLSX from 'xlsx';
export default { export default {
name: 'PreSettlementEditor', name: 'PreSettlementEditor',
components: { InfoFilled }, components: { InfoFilled, ChangeRecordDetailDialog },
props: { props: {
modelValue: { modelValue: {
type: Boolean, type: Boolean,
@@ -2990,23 +2966,6 @@ export default {
} }
} }
.pre-settlement-change-record-detail-meta {
display: flex;
flex-wrap: wrap;
gap: 8px 32px;
margin-bottom: 16px;
color: #606266;
}
:deep(.pre-settlement-change-record-detail-dialog .el-dialog__body) {
padding-top: 12px;
}
:deep(.pre-settlement-change-record-detail-dialog .el-table .cell) {
white-space: pre-wrap;
word-break: break-all;
}
:deep(.pre-settlement-editor .el-dialog__body) { :deep(.pre-settlement-editor .el-dialog__body) {
padding: 12px 16px; padding: 12px 16px;
} }
@@ -183,6 +183,7 @@
title="变更记录详情" title="变更记录详情"
width="88%" width="88%"
append-to-body append-to-body
align-center
> >
<el-table v-loading="changeRecordsDialog.loading" :data="changeRows" border> <el-table v-loading="changeRecordsDialog.loading" :data="changeRows" border>
<el-table-column type="index" label="序号" width="64" align="center" /> <el-table-column type="index" label="序号" width="64" align="center" />
+61 -21
View File
@@ -129,7 +129,7 @@
> >
<el-form <el-form
label-position="right" label-position="right"
label-width="calc(7em + 24px)" label-width="calc(4em + 12px)"
class="certification-audit archive-form" class="certification-audit archive-form"
> >
<section-card title="基础信息"> <section-card title="基础信息">
@@ -145,19 +145,25 @@
</el-form-item> </el-form-item>
</el-col> </el-col>
<el-col :span="12"> <el-col :span="12">
<el-form-item label="外廓尺寸(毫米)"> <el-form-item label="外廓尺寸">
<div class="dimension-group"> <div class="dimension-group">
<div class="dimension-group__item"> <div class="dimension-group__item">
<span class="dimension-group__label"></span> <span class="dimension-group__label"></span>
<span class="certification-audit__value">{{ auditValue('outerLength') }}</span> <span class="certification-audit__value">
{{ auditValueWithUnit('outerLength', 'mm') }}
</span>
</div> </div>
<div class="dimension-group__item"> <div class="dimension-group__item">
<span class="dimension-group__label"></span> <span class="dimension-group__label"></span>
<span class="certification-audit__value">{{ auditValue('outerWidth') }}</span> <span class="certification-audit__value">
{{ auditValueWithUnit('outerWidth', 'mm') }}
</span>
</div> </div>
<div class="dimension-group__item"> <div class="dimension-group__item">
<span class="dimension-group__label"></span> <span class="dimension-group__label"></span>
<span class="certification-audit__value">{{ auditValue('outerHeight') }}</span> <span class="certification-audit__value">
{{ auditValueWithUnit('outerHeight', 'mm') }}
</span>
</div> </div>
</div> </div>
</el-form-item> </el-form-item>
@@ -165,13 +171,17 @@
</el-row> </el-row>
<el-row :gutter="18"> <el-row :gutter="18">
<el-col :span="6"> <el-col :span="6">
<el-form-item label="核定载质量KG"> <el-form-item label="核定载质量">
<span class="certification-audit__value">{{ auditValue('approvedLoadKg') }}</span> <span class="certification-audit__value">
{{ auditValueWithUnit('approvedLoadKg', 'KG') }}
</span>
</el-form-item> </el-form-item>
</el-col> </el-col>
<el-col :span="6"> <el-col :span="6">
<el-form-item label="准牵引总质量KG"> <el-form-item label="准牵引总质量">
<span class="certification-audit__value">{{ auditValue('tractionMassKg') }}</span> <span class="certification-audit__value">
{{ auditValueWithUnit('tractionMassKg', 'KG') }}
</span>
</el-form-item> </el-form-item>
</el-col> </el-col>
<el-col :span="6"> <el-col :span="6">
@@ -357,7 +367,7 @@
v-if="showRejectReason" v-if="showRejectReason"
class="certification-audit__reason" class="certification-audit__reason"
label-position="right" label-position="right"
label-width="calc(7em + 24px)" label-width="calc(4em + 24px)"
> >
<el-form-item label="驳回原因" required> <el-form-item label="驳回原因" required>
<el-input <el-input
@@ -427,7 +437,7 @@
</el-form-item> </el-form-item>
</el-col> </el-col>
<el-col :span="12"> <el-col :span="12">
<el-form-item label="外廓尺寸(毫米)"> <el-form-item label="外廓尺寸">
<div class="dimension-group"> <div class="dimension-group">
<div class="dimension-group__item"> <div class="dimension-group__item">
<span class="dimension-group__label"></span> <span class="dimension-group__label"></span>
@@ -436,7 +446,9 @@
inputmode="numeric" inputmode="numeric"
placeholder="请输入" placeholder="请输入"
@input="vehicleForm.outerLength = digitsOnly($event)" @input="vehicleForm.outerLength = digitsOnly($event)"
/> >
<template #suffix>mm</template>
</el-input>
</div> </div>
<div class="dimension-group__item"> <div class="dimension-group__item">
<span class="dimension-group__label"></span> <span class="dimension-group__label"></span>
@@ -445,7 +457,9 @@
inputmode="numeric" inputmode="numeric"
placeholder="请输入" placeholder="请输入"
@input="vehicleForm.outerWidth = digitsOnly($event)" @input="vehicleForm.outerWidth = digitsOnly($event)"
/> >
<template #suffix>mm</template>
</el-input>
</div> </div>
<div class="dimension-group__item"> <div class="dimension-group__item">
<span class="dimension-group__label"></span> <span class="dimension-group__label"></span>
@@ -454,7 +468,9 @@
inputmode="numeric" inputmode="numeric"
placeholder="请输入" placeholder="请输入"
@input="vehicleForm.outerHeight = digitsOnly($event)" @input="vehicleForm.outerHeight = digitsOnly($event)"
/> >
<template #suffix>mm</template>
</el-input>
</div> </div>
</div> </div>
</el-form-item> </el-form-item>
@@ -462,23 +478,27 @@
</el-row> </el-row>
<el-row :gutter="18"> <el-row :gutter="18">
<el-col :span="6"> <el-col :span="6">
<el-form-item label="核定载质量KG" prop="approvedLoadKg"> <el-form-item label="核定载质量" prop="approvedLoadKg">
<el-input <el-input
v-model="vehicleForm.approvedLoadKg" v-model="vehicleForm.approvedLoadKg"
inputmode="numeric" inputmode="numeric"
placeholder="请输入" placeholder="请输入"
@input="vehicleForm.approvedLoadKg = digitsOnly($event)" @input="vehicleForm.approvedLoadKg = digitsOnly($event)"
/> >
<template #suffix>KG</template>
</el-input>
</el-form-item> </el-form-item>
</el-col> </el-col>
<el-col :span="6"> <el-col :span="6">
<el-form-item label="准牵引总质量KG" prop="tractionMassKg"> <el-form-item label="准牵引总质量" prop="tractionMassKg">
<el-input <el-input
v-model="vehicleForm.tractionMassKg" v-model="vehicleForm.tractionMassKg"
inputmode="numeric" inputmode="numeric"
placeholder="请输入" placeholder="请输入"
@input="vehicleForm.tractionMassKg = digitsOnly($event)" @input="vehicleForm.tractionMassKg = digitsOnly($event)"
/> >
<template #suffix>KG</template>
</el-input>
</el-form-item> </el-form-item>
</el-col> </el-col>
<el-col :span="6"> <el-col :span="6">
@@ -896,6 +916,7 @@ export default {
energyTypeOptions: ['柴油', '汽油', '新能源', '天然气'], energyTypeOptions: ['柴油', '汽油', '新能源', '天然气'],
baseRules: { baseRules: {
organizationName: [{ required: true, message: '请选择所属组织', trigger: 'change' }], organizationName: [{ required: true, message: '请选择所属组织', trigger: 'change' }],
useDepartment: [{ required: true, message: '请选择使用部门', trigger: 'change' }],
plateNo: [ plateNo: [
{ required: true, message: '请输入车牌号', trigger: 'change' }, { required: true, message: '请输入车牌号', trigger: 'change' },
{ validator: this.validatePlateNo, trigger: 'change' }, { validator: this.validatePlateNo, trigger: 'change' },
@@ -986,6 +1007,9 @@ export default {
if (this.vehicleBox && !this.vehicleForm.id && !this.vehicleForm.organizationName) { if (this.vehicleBox && !this.vehicleForm.id && !this.vehicleForm.organizationName) {
this.vehicleForm.organizationName = this.getCurrentOrganizationName(); this.vehicleForm.organizationName = this.getCurrentOrganizationName();
} }
if (this.vehicleBox && !this.vehicleForm.useDepartment) {
this.vehicleForm.useDepartment = this.getCurrentOrganizationName();
}
}); });
}, },
flattenDeptOptions(tree = [], level = 0) { flattenDeptOptions(tree = [], level = 0) {
@@ -1061,6 +1085,7 @@ export default {
if (!row?.id) { if (!row?.id) {
this.vehicleForm = emptyForm(); this.vehicleForm = emptyForm();
this.vehicleForm.organizationName = this.getCurrentOrganizationName(); this.vehicleForm.organizationName = this.getCurrentOrganizationName();
this.vehicleForm.useDepartment = this.getCurrentOrganizationName();
this.syncPlateNo(false); this.syncPlateNo(false);
this.vehicleBox = true; this.vehicleBox = true;
return; return;
@@ -1070,6 +1095,9 @@ export default {
...emptyForm(), ...emptyForm(),
...(res.data.data || {}), ...(res.data.data || {}),
}; };
if (!this.vehicleForm.useDepartment) {
this.vehicleForm.useDepartment = this.getCurrentOrganizationName();
}
this.splitPlateNo(); this.splitPlateNo();
this.syncPlateNo(false); this.syncPlateNo(false);
this.vehicleBox = true; this.vehicleBox = true;
@@ -1102,6 +1130,10 @@ export default {
const value = this.certificationAuditForm[prop]; const value = this.certificationAuditForm[prop];
return value === undefined || value === null || value === '' ? '-' : value; return value === undefined || value === null || value === '' ? '-' : value;
}, },
auditValueWithUnit(prop, unit) {
const value = this.auditValue(prop);
return value === '-' ? value : `${value}${unit}`;
},
handleCertificationApprove() { handleCertificationApprove() {
auditCertification(this.certificationAuditForm.id, 1).then(() => { auditCertification(this.certificationAuditForm.id, 1).then(() => {
this.$message.success('认证已通过'); this.$message.success('认证已通过');
@@ -1693,11 +1725,19 @@ export default {
} }
} }
.certification-audit, .certification-audit {
:deep(.el-form-item__label) {
flex: 0 0 calc(4em + 12px) !important;
width: calc(4em + 12px) !important;
max-width: calc(4em + 12px);
}
}
.certification-audit__reason { .certification-audit__reason {
:deep(.el-form-item__label) { :deep(.el-form-item__label) {
flex: 0 0 calc(7em + 24px) !important; flex: 0 0 calc(4em + 24px) !important;
width: calc(7em + 24px) !important; width: calc(4em + 24px) !important;
max-width: calc(4em + 24px);
} }
} }
+207 -108
View File
@@ -144,9 +144,24 @@
> >
撤回 撤回
</el-link> </el-link>
<el-link
type="primary"
v-if="
hasPermission('customer_type_re_cert') &&
row.approvalStatus === 'reviewing' &&
row.reviewStatus !== '已完成'
"
@click="openReview(row)"
>
复评
</el-link>
<el-link <el-link
type="success" type="success"
v-if="hasPermission('customer_archive_approve') && row.approvalStatus === 'reviewing'" v-if="
hasPermission('customer_archive_approve') &&
row.approvalStatus === 'reviewing' &&
row.reviewStatus === '已完成'
"
@click="handleApprove(row)" @click="handleApprove(row)"
> >
审核通过 审核通过
@@ -517,7 +532,7 @@
<span>{{ formatScoreValue(row.selfScore) }}</span> <span>{{ formatScoreValue(row.selfScore) }}</span>
</template> </template>
</el-table-column> </el-table-column>
<el-table-column label="复评得分" min-width="145" align="center"> <el-table-column v-if="archiveForm.id" label="复评得分" min-width="145" align="center">
<template #default="{ row }"> <template #default="{ row }">
<span>{{ formatScoreValue(row.reviewScore) }}</span> <span>{{ formatScoreValue(row.reviewScore) }}</span>
</template> </template>
@@ -553,11 +568,18 @@
<span>{{ row.reviewStatus }}</span> <span>{{ row.reviewStatus }}</span>
</template> </template>
</el-table-column> </el-table-column>
<el-table-column label="操作" width="135" fixed="right" align="center"> <el-table-column label="操作" width="160" fixed="right" align="center">
<template #default="{ row }"> <template #default="{ row }">
<el-link type="primary" @click="openScore(row.__raw, row.__rawIndex)"> <el-link type="primary" @click="openScore(row.__raw, row.__rawIndex)">
详情 详情
</el-link> </el-link>
<el-link
v-if="showScoreDelete"
type="danger"
@click="deleteScore(row.__rawIndex)"
>
删除
</el-link>
</template> </template>
</el-table-column> </el-table-column>
</el-table> </el-table>
@@ -876,43 +898,10 @@
/> />
</section-card> </section-card>
<el-dialog <change-record-detail-dialog
v-model="changeRecordDetailVisible" v-model="changeRecordDetailVisible"
title="变更记录详情" :rows="changeRecordDetailRows"
append-to-body />
destroy-on-close
width="1100px"
top="10px"
class="change-record-detail-dialog"
>
<div v-if="changeRecordDetail" class="change-record-detail-meta">
<span>变更日期:{{ changeRecordDetail.changeTime || '-' }}</span>
<span>变更账号:{{ changeRecordDetail.changeUserName || '-' }}</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="change-record-detail-value"
/>
<el-table-column
prop="after"
label="变更后"
min-width="500"
class-name="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>
<div class="archive-form__footer" v-if="!isPublicViewPage"> <div class="archive-form__footer" v-if="!isPublicViewPage">
<!-- 次要:独立页返回 / 只读关闭 / 弹窗取消 --> <!-- 次要:独立页返回 / 只读关闭 / 弹窗取消 -->
@@ -1323,7 +1312,25 @@
class="score-detail-table score-detail-table--inline" class="score-detail-table score-detail-table--inline"
max-height="520" max-height="520"
> >
<el-table-column label="评分项目" prop="itemName" width="150" align="center" /> <el-table-column
label="评分项目"
width="150"
align="center"
:show-overflow-tooltip="false"
>
<template #default="{ row: detail }">
<el-tooltip
:content="detail.itemName"
placement="top"
effect="dark"
append-to="body"
:show-after="200"
:disabled="!detail.itemName"
>
<span class="score-item-name">{{ detail.itemName }}</span>
</el-tooltip>
</template>
</el-table-column>
<el-table-column label="评分标准" min-width="430"> <el-table-column label="评分标准" min-width="430">
<template #default="{ row: detail }"> <template #default="{ row: detail }">
<div class="score-standard-cell"> <div class="score-standard-cell">
@@ -1333,6 +1340,7 @@
inputmode="decimal" inputmode="decimal"
:disabled="readonly" :disabled="readonly"
@input="value => handleScoreValueInput(detail, value)" @input="value => handleScoreValueInput(detail, value)"
@blur="handleScoreValueBlur(detail)"
> >
<template #append>{{ getScoreRule(detail)?.changeUnit || '' }}</template> <template #append>{{ getScoreRule(detail)?.changeUnit || '' }}</template>
</el-input> </el-input>
@@ -1362,13 +1370,15 @@
<span>{{ formatScoreValue(getSignedScore(detail, 'selfScore')) }}分</span> <span>{{ formatScoreValue(getSignedScore(detail, 'selfScore')) }}分</span>
</template> </template>
</el-table-column> </el-table-column>
<el-table-column label="复评得分" width="150" align="center"> <el-table-column v-if="!isNewScore" label="复评得分" width="150" align="center">
<template #default="{ row: detail }"> <template #default="{ row: detail }">
<el-input <el-input
v-model="detail.reviewScore" v-model="detail.reviewScore"
maxlength="10" maxlength="10"
inputmode="decimal" inputmode="decimal"
:disabled="readonly || !hasPermission('customer_type_re_cert')" :disabled="
(!reviewMode && readonly) || !hasPermission('customer_type_re_cert')
"
@input="value => handleScoreDetailScoreInput(detail, 'reviewScore', value)" @input="value => handleScoreDetailScoreInput(detail, 'reviewScore', value)"
> >
<template <template
@@ -1389,7 +1399,7 @@
<template #default="{ row }"> <template #default="{ row }">
<span class="score-category-table__name">{{ row.categoryName }}</span> <span class="score-category-table__name">{{ row.categoryName }}</span>
<span>自评:{{ getSignedScore(row, 'selfScore') }}分</span> <span>自评:{{ getSignedScore(row, 'selfScore') }}分</span>
<span>复评:{{ getSignedScore(row, 'reviewScore') }}分</span> <span v-if="!isNewScore">复评:{{ getSignedScore(row, 'reviewScore') }}分</span>
</template> </template>
</el-table-column> </el-table-column>
<el-table-column width="60" align="center"> <el-table-column width="60" align="center">
@@ -1463,12 +1473,15 @@
<div class="score-detail-dialog__total"> <div class="score-detail-dialog__total">
<span>总分合计</span> <span>总分合计</span>
<span>自评:{{ formatScoreValue(currentScore.selfScore) }}分</span> <span>自评:{{ formatScoreValue(currentScore.selfScore) }}分</span>
<span>复评:{{ formatScoreValue(currentScore.reviewScore) }}分</span> <span v-if="!isNewScore">复评:{{ formatScoreValue(currentScore.reviewScore) }}分</span>
</div> </div>
<el-button @click="scoreBox = false">取消</el-button> <el-button @click="scoreBox = false">取消</el-button>
<el-button type="primary" plain v-if="!readonly" @click="saveScoreDetail">保存</el-button> <el-button v-if="isNewScore" type="primary" @click="submitSelfScore">提交自评</el-button>
<el-button type="primary" plain v-if="!readonly && !isNewScore" @click="saveScoreDetail">
保存
</el-button>
<el-button <el-button
v-if="!readonly && hasPermission('customer_type_re_cert')" v-if="!isNewScore && (!readonly || reviewMode) && hasPermission('customer_type_re_cert')"
type="primary" type="primary"
@click="confirmReviewScore" @click="confirmReviewScore"
> >
@@ -1523,6 +1536,7 @@ import NProgress from 'nprogress';
import 'nprogress/nprogress.css'; import 'nprogress/nprogress.css';
import SectionCard from '@/components/section-card/main.vue'; import SectionCard from '@/components/section-card/main.vue';
import PdfPreview from '@/components/pdf-preview/main.vue'; import PdfPreview from '@/components/pdf-preview/main.vue';
import ChangeRecordDetailDialog from '@/components/change-record-detail-dialog/main.vue';
const createTimeRangeMap = { const createTimeRangeMap = {
createTimeRange: ['createTimeStart', 'createTimeEnd'], createTimeRange: ['createTimeStart', 'createTimeEnd'],
@@ -1538,6 +1552,7 @@ const qualificationViewerPlugins = [
export default { export default {
components: { components: {
SectionCard, SectionCard,
ChangeRecordDetailDialog,
ArrowRight, ArrowRight,
ElImageViewer, ElImageViewer,
OpenFileViewer, OpenFileViewer,
@@ -1631,6 +1646,8 @@ export default {
receiptBox: false, receiptBox: false,
invoiceBox: false, invoiceBox: false,
readonly: false, readonly: false,
reviewMode: false,
listActivated: false,
originalAccessType: '', originalAccessType: '',
activeTab: 'scores', activeTab: 'scores',
archiveForm: this.emptyArchive(), archiveForm: this.emptyArchive(),
@@ -1825,7 +1842,7 @@ export default {
editBtn: false, editBtn: false,
selection: true, selection: true,
dialogClickModal: false, dialogClickModal: false,
menuWidth: 280, menuWidth: 320,
column: [ column: [
{ {
label: '客商编号', label: '客商编号',
@@ -2019,10 +2036,20 @@ export default {
this.initBusinessDictionaries(); this.initBusinessDictionaries();
this.initScoreQuantificationOptions(); this.initScoreQuantificationOptions();
if (this.isArchivePage) { if (this.isArchivePage) {
const readonly = this.$route.query.view === '1' || this.$route.query.view === 'true'; this.reviewMode = this.$route.query.review === '1';
const readonly =
this.$route.query.view === '1' || this.$route.query.view === 'true' || this.reviewMode;
this.openArchive(this.$route.query.id ? { id: this.$route.query.id } : null, readonly); this.openArchive(this.$route.query.id ? { id: this.$route.query.id } : null, readonly);
} }
}, },
activated() {
if (this.isArchivePage || this.isPublicViewPage) return;
if (!this.listActivated) {
this.listActivated = true;
return;
}
this.onLoad(this.page, this.query);
},
computed: { computed: {
...mapGetters(['permission', 'userInfo']), ...mapGetters(['permission', 'userInfo']),
isAdmin() { isAdmin() {
@@ -2108,6 +2135,7 @@ export default {
return ids.join(','); return ids.join(',');
}, },
dialogTitle() { dialogTitle() {
if (this.reviewMode) return '客商复评';
if (this.readonly) return '查看客商档案'; if (this.readonly) return '查看客商档案';
return this.archiveForm.id ? '编辑客商档案' : '新增客商档案'; return this.archiveForm.id ? '编辑客商档案' : '新增客商档案';
}, },
@@ -2151,6 +2179,14 @@ export default {
scoreList() { scoreList() {
return this.archiveForm.scores || []; return this.archiveForm.scores || [];
}, },
showScoreDelete() {
if (this.readonly) return false;
const status = this.archiveForm.approvalStatus || 'draft';
return status === 'draft' || status === 'rejected';
},
isNewScore() {
return this.scoreRecordIndex < 0 && !this.reviewMode;
},
scorePageCount() { scorePageCount() {
return Math.max(Math.ceil(this.scoreList.length / this.scorePage.pageSize), 1); return Math.max(Math.ceil(this.scoreList.length / this.scorePage.pageSize), 1);
}, },
@@ -3962,6 +3998,12 @@ export default {
query: { id: row.id, name: '查看客商档案', view: '1' }, query: { id: row.id, name: '查看客商档案', view: '1' },
}); });
}, },
openReview(row) {
this.$router.push({
path: '/vehicle/customer-archive/form',
query: { id: row.id, name: '客商复评', review: '1' },
});
},
closeArchive() { closeArchive() {
if (this.isPublicViewPage) return; if (this.isPublicViewPage) return;
this.archiveBox = false; this.archiveBox = false;
@@ -3993,6 +4035,7 @@ export default {
if (this.isPublicViewPage) this.ensurePublicViewOptions(this.archiveForm); if (this.isPublicViewPage) this.ensurePublicViewOptions(this.archiveForm);
this.archiveBox = true; this.archiveBox = true;
this.loadChangeRecords(); this.loadChangeRecords();
if (this.reviewMode) this.openReviewScore();
}) })
.catch(() => { .catch(() => {
this.archiveBox = true; this.archiveBox = true;
@@ -4034,14 +4077,15 @@ export default {
this.invoiceForm = this.emptyInvoice(); this.invoiceForm = this.emptyInvoice();
this.$refs.archiveForm?.clearValidate(); this.$refs.archiveForm?.clearValidate();
}, },
// 客商材料不做提交校验,仅在页面上提示未上传项 // 客商材料不做提交校验,仅在页面上提示未上传项。提交审批前必须完成自评,且评分记录只能有一条。
validateFormalForApproval(archive) { validateScoreForApproval(archive) {
if (archive.accessType !== 'formal') return true; const scores = archive.scores || [];
const hasCompletedScore = (archive.scores || []).some( if (scores.length > 1) {
item => item.selfStatus === '已完成' || item.reviewStatus === '已完成' || item.finalScore this.$message.warning('评分记录只能有一条');
); return false;
if (!hasCompletedScore) { }
this.$message.warning('正式客商提交审核前必须完成信用评分'); if (scores.length !== 1 || scores[0].selfStatus !== '已完成') {
this.$message.warning('提交审批前必须完成自评');
return false; return false;
} }
return true; return true;
@@ -4151,7 +4195,7 @@ export default {
const archive = this.normalizeArchive(); const archive = this.normalizeArchive();
// 仅提交时落变更记录;保存不记录 // 仅提交时落变更记录;保存不记录
archive.recordChange = true; archive.recordChange = true;
if (!this.validateFormalForApproval(archive)) return; if (!this.validateScoreForApproval(archive)) return;
submit(archive).then(res => { submit(archive).then(res => {
const data = res.data.data; const data = res.data.data;
const id = (data && typeof data === 'object' ? data.id : data) || this.archiveForm.id; const id = (data && typeof data === 'object' ? data.id : data) || this.archiveForm.id;
@@ -4189,9 +4233,21 @@ export default {
approvalStatus, approvalStatus,
}).then(() => submitApproval(id)); }).then(() => submitApproval(id));
}, },
openReviewScore() {
const scores = this.archiveForm.scores || [];
if (!scores.length) {
this.$message.warning('暂无评分记录,无法复评');
return;
}
this.$nextTick(() => this.openScore(scores[0], 0));
},
addScore() { addScore() {
if ((this.archiveForm.scores || []).length) {
this.$message.warning('请勿重复添加');
return;
}
this.scoreRecordIndex = -1; this.scoreRecordIndex = -1;
this.currentScore = this.normalizeScore({}); this.currentScore = this.normalizeScore({ applyCreditLimit: '' });
this.scoreAttachmentFiles = []; this.scoreAttachmentFiles = [];
this.expandedScoreCategoryKeys = []; this.expandedScoreCategoryKeys = [];
this.scoreBox = true; this.scoreBox = true;
@@ -4273,7 +4329,7 @@ export default {
JSON.stringify( JSON.stringify(
(item.options || []).map(option => ({ (item.options || []).map(option => ({
label: option.label || option.optionName || option.value, label: option.label || option.optionName || option.value,
value: option.value || option.optionName || option.label, value: option.value ?? option.optionName ?? option.label,
score: option.score, score: option.score,
changeType: option.changeType, changeType: option.changeType,
changeValue: option.changeValue, changeValue: option.changeValue,
@@ -4363,20 +4419,21 @@ export default {
const isScoreOption = item.changeType || item.changeValue || item.changeUnit; const isScoreOption = item.changeType || item.changeValue || item.changeUnit;
const changeLabel = item.changeType === 'decrease' ? '每减少' : '每增加'; const changeLabel = item.changeType === 'decrease' ? '每减少' : '每增加';
const scoreLabel = item.scoreType === 'subtract' ? '减' : '加'; const scoreLabel = item.scoreType === 'subtract' ? '减' : '加';
const rawScore = item.score;
const generatedLabel = isScoreOption const generatedLabel = isScoreOption
? `${changeLabel}${item.changeValue || ''}${item.changeUnit || ''}${scoreLabel}${ ? `${changeLabel}${item.changeValue || ''}${item.changeUnit || ''}${scoreLabel}${
item.score || '' this.isBlankScore(rawScore) ? '' : rawScore
}分` }分`
: ''; : '';
const label = item.label || item.optionName || item.value || generatedLabel; const label = item.label || item.optionName || item.value || generatedLabel;
return { return {
...item, ...item,
label, label,
value: item.value || item.optionName || item.label || generatedLabel, value: item.value ?? item.optionName ?? item.label ?? generatedLabel,
score: score:
item.scoreType === 'subtract' && item.score !== undefined item.scoreType === 'subtract' && !this.isBlankScore(rawScore)
? -Math.abs(Number(item.score)) ? -Math.abs(Number(rawScore))
: item.score, : rawScore,
}; };
}); });
if (Array.isArray(value)) return normalize(value); if (Array.isArray(value)) return normalize(value);
@@ -4440,23 +4497,35 @@ export default {
const valid = increase ? input >= base : input <= base; const valid = increase ? input >= base : input <= base;
if (!valid) { if (!valid) {
detail.selfScore = ''; detail.selfScore = '';
this.$message.warning(`输入值${increase ? '不能小于' : '不能大于'}基准数值${base}`);
this.calculateScore(this.currentScore); this.calculateScore(this.currentScore);
return; return;
} }
detail.selfScore = this.getScoreTypeCalculatedScore(detail); detail.selfScore = this.getScoreTypeCalculatedScore(detail);
this.calculateScore(this.currentScore); this.calculateScore(this.currentScore);
}, },
handleScoreValueBlur(detail) {
const normalized = String(detail.scoreInput ?? '').trim();
if (!normalized || normalized === '-' || normalized === '.') return;
const rule = this.getScoreRule(detail);
const base = Number(detail.baseValue);
const input = Number(normalized);
if (!rule || !Number.isFinite(base) || !Number.isFinite(input)) return;
const increase = rule.changeType !== 'decrease';
const valid = increase ? input >= base : input <= base;
if (!valid) {
this.$message.warning(`输入值${increase ? '不能小于' : '不能大于'}基准数值${base}`);
}
},
scoreOptionChange(detail, optionValue) { scoreOptionChange(detail, optionValue) {
const option = this.parseScoreOptions(detail.optionsJson).find( const option = this.parseScoreOptions(detail.optionsJson).find(
item => item.value === optionValue item => item.value === optionValue
); );
const score = Number(option?.score || 0); const rawScore = option?.score;
detail.selfScore = score; detail.selfScore = this.isBlankScore(rawScore) ? 0 : Number(rawScore);
this.calculateScore(this.currentScore); this.calculateScore(this.currentScore);
}, },
handleScoreDetailScoreInput(detail, prop, value) { handleScoreDetailScoreInput(detail, prop, value) {
const normalized = String(value || '') const normalized = String(value ?? '')
.replace(/[^\d.]/g, '') .replace(/[^\d.]/g, '')
.replace(/^\./, '') .replace(/^\./, '')
.replace(/(\..*)\./g, '$1') .replace(/(\..*)\./g, '$1')
@@ -4588,8 +4657,7 @@ export default {
sumScore(plusDetails, 'reviewScore') - sumScore(plusDetails, 'reviewScore') -
sumScore(minusDetails, 'reviewScore'); sumScore(minusDetails, 'reviewScore');
const hasReviewScore = details.some( const hasReviewScore = details.some(
item => item => !this.isBlankScore(item.reviewScore)
item.reviewScore !== undefined && item.reviewScore !== null && item.reviewScore !== ''
); );
const finalScore = hasReviewScore ? reviewScore : selfScore; const finalScore = hasReviewScore ? reviewScore : selfScore;
const fullMark = this.getScoreFullMark(details); const fullMark = this.getScoreFullMark(details);
@@ -4609,13 +4677,17 @@ export default {
} }
return score; return score;
}, },
isBlankScore(value) {
return value === undefined || value === null || String(value).trim() === '';
},
hasIncompleteBasicScoreDetail(details = []) { hasIncompleteBasicScoreDetail(details = []) {
return this.getScoreCategoryDetailsByList(details, 'basic').some(item => { return this.getScoreCategoryDetailsByList(details, 'basic').some(item => {
if (this.isScoreTypeDetail(item)) { if (this.isScoreTypeDetail(item)) {
const input = String(item.scoreInput ?? '').trim(); const input = String(item.scoreInput ?? '').trim();
return !input || !Number.isFinite(Number(input)) || item.selfScore === ''; const inputBlank = input === '' || input === '-' || input === '.';
return inputBlank || !Number.isFinite(Number(input)) || this.isBlankScore(item.selfScore);
} }
return !item.selectedOption; return this.isBlankScore(item.selectedOption);
}); });
}, },
finishScore(score) { finishScore(score) {
@@ -4632,10 +4704,13 @@ export default {
score.reviewStatus = '已完成'; score.reviewStatus = '已完成';
this.$message.success('评分已完成'); this.$message.success('评分已完成');
}, },
saveScoreDetail() { saveScoreDetail(successMessage) {
if (!this.validateScoreForm(false)) return; if (!this.validateScoreForm(false)) return;
this.currentScore.attachments = JSON.parse(JSON.stringify(this.scoreAttachmentFiles || [])); this.currentScore.attachments = JSON.parse(JSON.stringify(this.scoreAttachmentFiles || []));
this.currentScore.proofAttachments = this.stringifyAttachments(this.scoreAttachmentFiles); this.currentScore.proofAttachments = this.stringifyAttachments(this.scoreAttachmentFiles);
this.currentScore.selfStatus = this.hasIncompleteBasicScoreDetail(this.currentScore.details)
? '未完成'
: '已完成';
this.calculateScore(this.currentScore); this.calculateScore(this.currentScore);
const score = JSON.parse(JSON.stringify(this.currentScore)); const score = JSON.parse(JSON.stringify(this.currentScore));
if (this.scoreRecordIndex > -1) { if (this.scoreRecordIndex > -1) {
@@ -4647,23 +4722,62 @@ export default {
this.syncArchiveCreditFromScores(); this.syncArchiveCreditFromScores();
this.activeTab = 'scores'; this.activeTab = 'scores';
this.scoreBox = false; this.scoreBox = false;
this.saveScoreArchive(); const pending = this.saveScoreArchive(successMessage);
}, if (this.reviewMode && successMessage && pending && pending.then) {
saveScoreArchive() { pending.then(() => this.closeArchive());
if (!this.archiveForm.id) {
this.$message.success('评分记录已添加,请保存客商档案后生效');
return;
} }
submit(this.normalizeArchive()).then(() => { return pending;
this.$message.success('评分记录已保存'); },
deleteScore(index) {
this.$confirm('确定删除该评分记录?', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning',
})
.then(() => {
if (!Array.isArray(this.archiveForm.scores)) this.archiveForm.scores = [];
this.archiveForm.scores.splice(index, 1);
if (this.scoreRecordIndex === index) {
this.scoreBox = false;
} else if (this.scoreRecordIndex > index) {
this.scoreRecordIndex -= 1;
}
const latestScore = this.getLatestCreditScore(this.archiveForm.scores);
if (latestScore) {
this.syncArchiveCreditFromScores();
} else {
this.archiveForm.customerLevel = '';
this.archiveForm.maxCreditLimit = '';
this.archiveForm.applyCreditLimit = '';
}
this.handleScoreCurrentChange(this.scorePage.currentPage);
this.saveScoreArchive('评分记录已删除', '评分记录已删除,请保存客商档案后生效');
})
.catch(() => {});
},
saveScoreArchive(
successMessage = '评分记录已保存',
draftMessage = '评分记录已添加,请保存客商档案后生效'
) {
if (!this.archiveForm.id) {
this.$message.success(draftMessage);
return Promise.resolve();
}
return submit(this.normalizeArchive()).then(() => {
this.$message.success(successMessage);
}); });
}, },
submitSelfScore() {
if (!this.validateScoreForm(true)) return;
this.currentScore.selfStatus = '已完成';
this.currentScore.reviewStatus = '未完成';
this.saveScoreDetail('自评已提交');
},
confirmReviewScore() { confirmReviewScore() {
if (!this.validateScoreForm(true)) return; if (!this.validateScoreForm(true)) return;
this.currentScore.selfStatus = '已完成'; this.currentScore.selfStatus = '已完成';
this.currentScore.reviewStatus = '已完成'; this.currentScore.reviewStatus = '已完成';
this.saveScoreDetail(); this.saveScoreDetail('复评确认成功');
this.$message.success('复评确认成功');
}, },
validateScoreForm(requireComplete) { validateScoreForm(requireComplete) {
if (!this.currentScore.scoreDate) { if (!this.currentScore.scoreDate) {
@@ -4701,7 +4815,7 @@ export default {
if ( if (
item.reviewScore === undefined || item.reviewScore === undefined ||
item.reviewScore === null || item.reviewScore === null ||
item.reviewScore === '' String(item.reviewScore).trim() === ''
) { ) {
return false; return false;
} }
@@ -4718,7 +4832,7 @@ export default {
return false; return false;
} }
if (requireComplete && this.hasIncompleteBasicScoreDetail(this.currentScore.details)) { if (requireComplete && this.hasIncompleteBasicScoreDetail(this.currentScore.details)) {
this.$message.warning('请完整填写基础得分项评分明细'); this.$message.warning('请完整填写所有基础项目后再提交自评');
return false; return false;
} }
return true; return true;
@@ -4773,7 +4887,7 @@ export default {
getDetail(row.id).then(res => { getDetail(row.id).then(res => {
const archive = this.normalizeDetail(res.data.data); const archive = this.normalizeDetail(res.data.data);
archive.qualificationAttachments = archive.qualificationAttachments || ''; archive.qualificationAttachments = archive.qualificationAttachments || '';
if (!this.validateFormalForApproval(archive)) return; if (!this.validateScoreForApproval(archive)) return;
this.$confirm('是否提交客商准入审批?', '提示', { this.$confirm('是否提交客商准入审批?', '提示', {
confirmButtonText: '确定', confirmButtonText: '确定',
cancelButtonText: '取消', cancelButtonText: '取消',
@@ -5260,10 +5374,16 @@ export default {
} }
:deep(.el-table__body td) { :deep(.el-table__body td) {
//min-height: 112px;
color: #303133; color: #303133;
} }
:deep(.score-item-name) {
display: block;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
:deep(.el-select), :deep(.el-select),
:deep(.el-input), :deep(.el-input),
:deep(.el-select) { :deep(.el-select) {
@@ -5446,27 +5566,6 @@ export default {
word-break: break-word; word-break: break-word;
} }
:deep(.change-record-detail-dialog .el-dialog__body) {
max-height: 65vh;
overflow: auto;
}
:deep(.change-record-detail-dialog .change-record-detail-value .cell) {
white-space: pre-wrap;
word-break: break-word;
overflow: visible;
text-overflow: clip;
line-height: 1.6;
}
.change-record-detail-meta {
display: flex;
gap: 32px;
margin-bottom: 16px;
color: #606266;
font-size: 14px;
}
.customer-archive-page.is-public-view { .customer-archive-page.is-public-view {
:deep(.archive-page-form .archive-form__footer) { :deep(.archive-page-form .archive-form__footer) {
left: 0; left: 0;