2 Commits

Author SHA1 Message Date
b2894lxlx 6212d68a77 付款申请表单优化与结算单过滤,并完善相关体验
进度预付仅可选预结算、尾款付款可选正式/预结算;同步收款账户、申请金额校验、关闭标签与费用明细残留等交互,并补充运单复制需求说明。

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

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-09-22 20:13:44 +08:00
19 changed files with 1024 additions and 557 deletions
+1 -1
View File
@@ -24,7 +24,7 @@ export const syncKingdeeBatch = ids =>
export const paymentTypeOptions = [
{ label: '项目预付', value: 'project_advance' },
{ label: '进度预付', value: 'progress_advance' },
{ label: '结算付款', value: 'settlement_payment' },
{ label: '尾款付款', value: 'settlement_payment' },
];
export const approvalStatusOptions = [
{ label: '草稿', value: 'draft' },
@@ -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>
<div v-loading="loading" class="master-detail">
<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">
<div>
<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)}` : '-'; },
},
async mounted() {
if (!this.id) return;
this.loading = true;
try {
const res = await api.getDetail(this.id);
@@ -282,8 +293,36 @@ export default {
<style scoped lang="scss">
.master-detail { padding-bottom: 20px; color: #303133; }
.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; } }
.transport-flow-tag { margin-left: 8px; }
.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;
}
: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; } }
.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; } }
@@ -295,6 +295,7 @@ export default {
},
methods: {
async load() {
if (!this.id) return;
this.loading = true;
try {
const res = await api.getDetail(this.id);
@@ -113,6 +113,12 @@
label="导入方式"
width="100"
/>
<el-table-column
v-if="columnVisible.statusName"
prop="statusName"
label="状态"
width="100"
/>
<el-table-column
v-if="columnVisible.createUserName"
prop="createUserName"
@@ -121,12 +127,6 @@
>
<template #default="{ row }">{{ row.createUserName || '-' }}</template>
</el-table-column>
<el-table-column
v-if="columnVisible.statusName"
prop="statusName"
label="状态"
width="100"
/>
<el-table-column
v-if="columnVisible.createTime"
prop="createTime"
@@ -621,8 +621,8 @@ const columnOptions = [
{ prop: 'carrierType', label: '承运类型' },
{ prop: 'waybillCount', label: '运单数' },
{ prop: 'importTypeName', label: '导入方式' },
{ prop: 'createUserName', label: '创建人' },
{ prop: 'statusName', label: '状态' },
{ prop: 'createUserName', label: '创建人' },
{ prop: 'createTime', label: '创建时间' },
{ prop: 'updateTime', label: '更新时间' },
];
@@ -1774,12 +1774,17 @@
<div class="waybill-manage-page__waybill-heading">
<strong>运单详情</strong>
<span>{{ detailRow.waybillNo || '-' }}</span>
<span class="detail-status-text status-text-success">{{
displayStatus(detailRow, 'businessStatus')
}}</span>
<span class="detail-status-text detail-transport-type">
{{ getTransportTypeLabel(waybillTransportMode(detailRow)) || '-' }}
</span>
<el-tag :type="statusTagType(detailRow.businessStatus)">
{{ displayStatus(detailRow, 'businessStatus') }}
</el-tag>
<el-tag type="primary">
{{
detailRow.transportTypeName ||
getTransportTypeLabel(detailRow.transportType) ||
waybillTransportMode(detailRow) ||
'-'
}}
</el-tag>
<el-link
v-if="detailRow.id"
class="waybill-manage-page__waybill-route-change-link"
@@ -2184,7 +2189,11 @@
/>
</div>
</el-tab-pane>
<el-tab-pane label="司机上传" name="driverUpload">
<el-tab-pane
v-if="waybillDriverUploads.length"
label="司机上传"
name="driverUpload"
>
<div
v-loading="waybillPunchRecordsLoading"
class="waybill-manage-page__driver-upload-grid"
@@ -2201,11 +2210,6 @@
{{ photo.label || '凭证' }}
</div>
</div>
<el-empty
v-if="!waybillPunchRecordsLoading && !waybillDriverUploads.length"
description="暂无司机上传数据"
:image-size="50"
/>
</div>
</el-tab-pane>
</el-tabs>
@@ -5265,7 +5269,9 @@ export default {
: defaultText;
},
statusTagType(status) {
if ([1, 'pending', 'processing', 'approved', 'change_approved'].includes(status)) {
if (
[1, 'pending', 'processing', 'running', 'approved', 'change_approved'].includes(status)
) {
return 'success';
}
if ([2, 'cancelled', 'withdrawn', 'draft'].includes(status)) {
@@ -9763,6 +9769,15 @@ export default {
strong {
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 {
+12 -57
View File
@@ -966,44 +966,10 @@
@close="attachmentImagePreviewVisible = false"
/>
<el-dialog
<change-record-detail-dialog
v-model="detailChangeRecordVisible"
title="变更记录详情"
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>
:rows="detailChangeRecordDetailRows"
/>
<billing-plan-editor
v-model="detailBillingPlanBox"
@@ -1042,6 +1008,7 @@ import { getToken } from '@/utils/auth';
import { downloadFileByUrl, downloadXls } from '@/utils/util';
import { submitMkApprovalFlow } from '@/utils/mk-approval';
import BillingPlanEditor from './components/billing-plan-editor.vue';
import ChangeRecordDetailDialog from '@/components/change-record-detail-dialog/main.vue';
import ContractAttachmentSection, {
attachmentName as sharedAttachmentName,
attachmentUrl as sharedAttachmentUrl,
@@ -1194,7 +1161,14 @@ const attachmentViewerPlugins = [
export default {
name: 'ContractManage',
components: { ContractAttachmentSection, BillingPlanEditor, ElImageViewer, OpenFileViewer, PdfPreview },
components: {
ContractAttachmentSection,
BillingPlanEditor,
ChangeRecordDetailDialog,
ElImageViewer,
OpenFileViewer,
PdfPreview,
},
data() {
return {
api,
@@ -2969,25 +2943,6 @@ export default {
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) {
left: 60px;
}
+16 -12
View File
@@ -395,14 +395,8 @@
<div class="loading-detail__summary">
<div class="loading-detail__heading">配载单详情</div>
<div>{{ dialogForm.loadingNo || '-' }}</div>
<div>
<el-tag :type="detailStatusType" class="status-text">{{
statusText(dialogForm)
}}</el-tag>
</div>
<div>
<el-tag type="warning">{{ transportTypeLabel(dialogForm.transportType) }}</el-tag>
</div>
<el-tag :type="detailStatusType">{{ statusText(dialogForm) }}</el-tag>
<el-tag type="primary">{{ transportTypeLabel(dialogForm.transportType) }}</el-tag>
</div>
<div class="loading-detail__task-row">
<div class="loading-detail__field">
@@ -1683,11 +1677,12 @@ export default {
const typeMap = {
pending: 'success',
running: 'success',
completed: 'info',
processing: 'success',
completed: 'primary',
cancelled: 'info',
draft: 'warning',
draft: 'info',
};
return typeMap[this.dialogForm.businessStatus] || 'info';
return typeMap[this.dialogForm.businessStatus] || 'warning';
},
taskSummary() {
return (
@@ -3604,8 +3599,17 @@ export default {
display: flex;
align-items: center;
flex-wrap: wrap;
gap: 24px;
gap: 12px;
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 {
font-size: 20px;
+11 -1
View File
@@ -132,7 +132,7 @@
/>
</template>
<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"
><span>{{ confirm.message }}</span
><template #footer
@@ -153,6 +153,10 @@ export default {
components: { MasterOrderEditor, MasterOrderDispatch, MasterOrderDetail },
data() {
return {
// 实例创建时锁定所属路由。keep-alive 缓存后 $route 会变成其它页面,
// 不能再用全局 query.mode / query.id 决定本页形态,否则打开「新增项目管理」
//mode=add 且无 id)会落入详情分支并请求缺少 id 的详情接口。
routePathLocked: this.$route.path,
searchExpanded: false,
loading: false,
records: [],
@@ -180,10 +184,15 @@ export default {
};
},
computed: {
isOwnedRoute() {
return this.$route.path === this.routePathLocked;
},
mode() {
if (!this.isOwnedRoute) return 'list';
return this.$route.query.mode || 'list';
},
routeId() {
if (!this.isOwnedRoute) return '';
return this.$route.query.id;
},
masterEditorTitle() {
@@ -194,6 +203,7 @@ export default {
'$route.query': {
immediate: true,
handler() {
if (!this.isOwnedRoute) return;
this.syncTagTitle();
if (this.mode === 'list') this.load();
},
+8 -58
View File
@@ -196,7 +196,7 @@
<el-form-item label="项目编号" prop="projectCode">
<el-input
v-model="form.projectCode"
placeholder="请输入"
:placeholder="dialogType === 'add' ? '若不填写,系统自动生成' : '请输入'"
:disabled="dialogType === 'edit'"
/>
</el-form-item>
@@ -703,6 +703,7 @@
label="变更内容"
min-width="180"
align="center"
:show-overflow-tooltip="false"
>
<template #default="{ row }">
<el-tooltip placement="top" :show-after="200">
@@ -730,43 +731,10 @@
</el-table-column>
</el-table>
<el-dialog
<change-record-detail-dialog
v-model="changeRecordDetailVisible"
title="变更记录详情"
append-to-body
destroy-on-close
width="1100px"
top="10px"
class="project-change-record-detail-dialog"
>
<div v-if="changeRecordDetail" class="project-change-record-detail-meta">
<span>变更日期:{{ changeRecordDetail.changeDate || '-' }}</span>
<span>变更账号:{{ changeRecordDetail.handler || '-' }}</span>
</div>
<el-table :data="changeRecordDetailRows" border :show-overflow-tooltip="false">
<el-table-column prop="field" label="变更字段" min-width="180" />
<el-table-column
prop="before"
label="变更前"
min-width="360"
class-name="project-change-record-detail-value"
/>
<el-table-column
prop="after"
label="变更后"
min-width="500"
class-name="project-change-record-detail-value"
/>
</el-table>
<el-empty
v-if="!changeRecordDetailRows.length"
description="暂无变更内容"
:image-size="60"
/>
<template #footer>
<el-button type="primary" @click="changeRecordDetailVisible = false">关闭</el-button>
</template>
</el-dialog>
:rows="changeRecordDetailRows"
/>
<template v-if="isChangeDialog">
<div class="dialog-section-title">变更原因</div>
@@ -947,6 +915,7 @@ import { submitMkApprovalFlow } from '@/utils/mk-approval';
import { ElImageViewer } from 'element-plus';
import { OpenFileViewer } from '@open-file-viewer/vue';
import PdfPreview from '@/components/pdf-preview/main.vue';
import ChangeRecordDetailDialog from '@/components/change-record-detail-dialog/main.vue';
import {
fallbackPlugin,
imagePlugin,
@@ -1066,6 +1035,7 @@ export default {
name: 'ProjectApply',
components: {
CustomerArchive: defineAsyncComponent(() => import('@/views/vehicle/customer-archive.vue')),
ChangeRecordDetailDialog,
ElImageViewer,
OpenFileViewer,
PdfPreview,
@@ -1520,6 +1490,7 @@ export default {
this.fillDefaultUsers();
return;
}
if (!id) return;
this.api.getDetail(id).then(res => {
const detail = res.data.data || {};
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) {
max-height: 76vh;
overflow-y: auto;
+32 -10
View File
@@ -610,7 +610,12 @@ export default {
return value;
},
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.attachmentRows = this.parseJsonArray(detail.attachmentsJson);
this.selectedAttachments = [];
@@ -620,6 +625,9 @@ export default {
const { projectQuotaInfoTitle, temporaryCreditInfoTitle, ...payload } = row || {};
return {
...payload,
projectFundLimit: this.toOptionalAmount(payload.projectFundLimit),
usedFundLimit: this.toOptionalAmount(payload.usedFundLimit),
remainingFundLimit: this.toOptionalAmount(payload.remainingFundLimit),
applyLimit: payload.applyLimit === '' ? '' : Number(payload.applyLimit),
attachmentsJson: JSON.stringify(this.attachmentRows),
};
@@ -765,8 +773,8 @@ export default {
projectName: project.projectName || '',
projectCode: project.projectCode || '',
undertakeDeptName: project.undertakeDeptName || '',
projectFundLimit: project.fundLimit || project.projectFundLimit || 0,
usedFundLimit: project.usedFundLimit || 0,
projectFundLimit: this.blankSentinelAmount(project.fundLimit ?? project.projectFundLimit),
usedFundLimit: this.blankSentinelAmount(project.usedFundLimit),
});
this.form.remainingFundLimit = this.calculateRemainingFundLimit();
getProjectDetail(project.id).then(res => {
@@ -774,18 +782,32 @@ export default {
Object.assign(this.form, {
projectCode: detail.projectCode || this.form.projectCode,
undertakeDeptName: detail.undertakeDeptName || this.form.undertakeDeptName,
projectFundLimit:
detail.fundLimit ?? detail.projectFundLimit ?? this.form.projectFundLimit,
usedFundLimit: detail.usedFundLimit ?? this.form.usedFundLimit,
projectFundLimit: this.blankSentinelAmount(
detail.fundLimit ?? detail.projectFundLimit ?? this.form.projectFundLimit
),
usedFundLimit: this.blankSentinelAmount(
detail.usedFundLimit ?? this.form.usedFundLimit
),
});
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() {
const total = Number(this.form.projectFundLimit);
const used = Number(this.form.usedFundLimit);
if (!Number.isFinite(total) || !Number.isFinite(used)) return '';
return Math.round((total - used + Number.EPSILON) * 100) / 100;
const total = this.blankSentinelAmount(this.form.projectFundLimit);
const used = this.blankSentinelAmount(this.form.usedFundLimit);
if (total === '' || used === '') return '';
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) {
const text = String(value || '').replace(/[^\d.]/g, '');
File diff suppressed because it is too large Load Diff
+1 -1
View File
@@ -326,7 +326,7 @@ export default {
openProjectAdvance() {
this.$router.push({
path: '/payment/payment-application/form',
query: { mode: 'add', paymentType: 'project_advance' },
query: { mode: 'add', paymentType: 'progress_advance' },
});
},
openEdit(row) {
@@ -66,6 +66,9 @@
<span v-else-if="field.prop === 'settlementType'" class="form-readonly">
{{ settlementTypeName }}
</span>
<span v-else-if="field.prop === 'preSettlementNo'" class="form-readonly">
{{ form.preSettlementNo || (!form.id ? '系统自动生成' : '-') }}
</span>
<span v-else-if="field.prop === 'createTime'" class="form-readonly">
{{ formatCreateDate(form.createTime) }}
</span>
@@ -850,35 +853,10 @@
</template>
</el-dialog>
<el-dialog
<change-record-detail-dialog
v-model="adjustChangeRecordVisible"
title="变更记录详情"
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>
:rows="adjustChangeRecordDetailRows"
/>
<el-dialog
v-model="billingRuleDialog.visible"
@@ -950,6 +928,7 @@
import { h } from 'vue';
import { mapGetters } from 'vuex';
import { InfoFilled } from '@element-plus/icons-vue';
import ChangeRecordDetailDialog from '@/components/change-record-detail-dialog/main.vue';
import { getMkPublicDetail } from '@/api/mk-process';
import {
adjustDetail,
@@ -989,7 +968,7 @@ import * as XLSX from 'xlsx';
export default {
name: 'PreSettlementEditor',
components: { InfoFilled },
components: { InfoFilled, ChangeRecordDetailDialog },
props: {
modelValue: {
type: Boolean,
@@ -2990,23 +2969,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) {
padding: 12px 16px;
}
+1
View File
@@ -497,6 +497,7 @@ export default {
path: '/payment/payment-application/form',
query: {
mode: 'add',
paymentType: 'progress_advance',
transferToken,
preSettlementIds: sourcePreSettlements
.map(row => row.preSettlementId)
@@ -183,6 +183,7 @@
title="变更记录详情"
width="88%"
append-to-body
align-center
>
<el-table v-loading="changeRecordsDialog.loading" :data="changeRows" border>
<el-table-column type="index" label="序号" width="64" align="center" />
@@ -1197,10 +1198,24 @@ export default {
await this.loadTable();
await this.openRouteDetail();
},
// keep-alive teleport/append-to-body
deactivated() {
this.closeInnerDialogs();
},
beforeUnmount() {
this.clearAdjustCalculations();
this.closeInnerDialogs();
},
methods: {
closeInnerDialogs() {
this.closeDetailPanel();
this.closeAdjustPanel();
this.changeRecordsDialog.visible = false;
this.updateFeeDialog.visible = false;
this.transferDialog.visible = false;
this.generateDialog.visible = false;
this.previewDialog.visible = false;
this.generateContractDialog.visible = false;
},
contractCategoryName(value) {
return (
this.contractCategoryOptions.find(item => String(item.value) === String(value))?.label ||
+61 -21
View File
@@ -129,7 +129,7 @@
>
<el-form
label-position="right"
label-width="calc(7em + 24px)"
label-width="calc(4em + 12px)"
class="certification-audit archive-form"
>
<section-card title="基础信息">
@@ -145,19 +145,25 @@
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="外廓尺寸(毫米)">
<el-form-item label="外廓尺寸">
<div class="dimension-group">
<div class="dimension-group__item">
<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 class="dimension-group__item">
<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 class="dimension-group__item">
<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>
</el-form-item>
@@ -165,13 +171,17 @@
</el-row>
<el-row :gutter="18">
<el-col :span="6">
<el-form-item label="核定载质量KG">
<span class="certification-audit__value">{{ auditValue('approvedLoadKg') }}</span>
<el-form-item label="核定载质量">
<span class="certification-audit__value">
{{ auditValueWithUnit('approvedLoadKg', 'KG') }}
</span>
</el-form-item>
</el-col>
<el-col :span="6">
<el-form-item label="准牵引总质量KG">
<span class="certification-audit__value">{{ auditValue('tractionMassKg') }}</span>
<el-form-item label="准牵引总质量">
<span class="certification-audit__value">
{{ auditValueWithUnit('tractionMassKg', 'KG') }}
</span>
</el-form-item>
</el-col>
<el-col :span="6">
@@ -357,7 +367,7 @@
v-if="showRejectReason"
class="certification-audit__reason"
label-position="right"
label-width="calc(7em + 24px)"
label-width="calc(4em + 24px)"
>
<el-form-item label="驳回原因" required>
<el-input
@@ -427,7 +437,7 @@
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="外廓尺寸(毫米)">
<el-form-item label="外廓尺寸">
<div class="dimension-group">
<div class="dimension-group__item">
<span class="dimension-group__label"></span>
@@ -436,7 +446,9 @@
inputmode="numeric"
placeholder="请输入"
@input="vehicleForm.outerLength = digitsOnly($event)"
/>
>
<template #suffix>mm</template>
</el-input>
</div>
<div class="dimension-group__item">
<span class="dimension-group__label"></span>
@@ -445,7 +457,9 @@
inputmode="numeric"
placeholder="请输入"
@input="vehicleForm.outerWidth = digitsOnly($event)"
/>
>
<template #suffix>mm</template>
</el-input>
</div>
<div class="dimension-group__item">
<span class="dimension-group__label"></span>
@@ -454,7 +468,9 @@
inputmode="numeric"
placeholder="请输入"
@input="vehicleForm.outerHeight = digitsOnly($event)"
/>
>
<template #suffix>mm</template>
</el-input>
</div>
</div>
</el-form-item>
@@ -462,23 +478,27 @@
</el-row>
<el-row :gutter="18">
<el-col :span="6">
<el-form-item label="核定载质量KG" prop="approvedLoadKg">
<el-form-item label="核定载质量" prop="approvedLoadKg">
<el-input
v-model="vehicleForm.approvedLoadKg"
inputmode="numeric"
placeholder="请输入"
@input="vehicleForm.approvedLoadKg = digitsOnly($event)"
/>
>
<template #suffix>KG</template>
</el-input>
</el-form-item>
</el-col>
<el-col :span="6">
<el-form-item label="准牵引总质量KG" prop="tractionMassKg">
<el-form-item label="准牵引总质量" prop="tractionMassKg">
<el-input
v-model="vehicleForm.tractionMassKg"
inputmode="numeric"
placeholder="请输入"
@input="vehicleForm.tractionMassKg = digitsOnly($event)"
/>
>
<template #suffix>KG</template>
</el-input>
</el-form-item>
</el-col>
<el-col :span="6">
@@ -896,6 +916,7 @@ export default {
energyTypeOptions: ['柴油', '汽油', '新能源', '天然气'],
baseRules: {
organizationName: [{ required: true, message: '请选择所属组织', trigger: 'change' }],
useDepartment: [{ required: true, message: '请选择使用部门', trigger: 'change' }],
plateNo: [
{ required: true, message: '请输入车牌号', trigger: 'change' },
{ validator: this.validatePlateNo, trigger: 'change' },
@@ -986,6 +1007,9 @@ export default {
if (this.vehicleBox && !this.vehicleForm.id && !this.vehicleForm.organizationName) {
this.vehicleForm.organizationName = this.getCurrentOrganizationName();
}
if (this.vehicleBox && !this.vehicleForm.useDepartment) {
this.vehicleForm.useDepartment = this.getCurrentOrganizationName();
}
});
},
flattenDeptOptions(tree = [], level = 0) {
@@ -1061,6 +1085,7 @@ export default {
if (!row?.id) {
this.vehicleForm = emptyForm();
this.vehicleForm.organizationName = this.getCurrentOrganizationName();
this.vehicleForm.useDepartment = this.getCurrentOrganizationName();
this.syncPlateNo(false);
this.vehicleBox = true;
return;
@@ -1070,6 +1095,9 @@ export default {
...emptyForm(),
...(res.data.data || {}),
};
if (!this.vehicleForm.useDepartment) {
this.vehicleForm.useDepartment = this.getCurrentOrganizationName();
}
this.splitPlateNo();
this.syncPlateNo(false);
this.vehicleBox = true;
@@ -1102,6 +1130,10 @@ export default {
const value = this.certificationAuditForm[prop];
return value === undefined || value === null || value === '' ? '-' : value;
},
auditValueWithUnit(prop, unit) {
const value = this.auditValue(prop);
return value === '-' ? value : `${value}${unit}`;
},
handleCertificationApprove() {
auditCertification(this.certificationAuditForm.id, 1).then(() => {
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 {
:deep(.el-form-item__label) {
flex: 0 0 calc(7em + 24px) !important;
width: calc(7em + 24px) !important;
flex: 0 0 calc(4em + 24px) !important;
width: calc(4em + 24px) !important;
max-width: calc(4em + 24px);
}
}
+207 -108
View File
@@ -144,9 +144,24 @@
>
撤回
</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
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)"
>
审核通过
@@ -517,7 +532,7 @@
<span>{{ formatScoreValue(row.selfScore) }}</span>
</template>
</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 }">
<span>{{ formatScoreValue(row.reviewScore) }}</span>
</template>
@@ -553,11 +568,18 @@
<span>{{ row.reviewStatus }}</span>
</template>
</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 }">
<el-link type="primary" @click="openScore(row.__raw, row.__rawIndex)">
详情
</el-link>
<el-link
v-if="showScoreDelete"
type="danger"
@click="deleteScore(row.__rawIndex)"
>
删除
</el-link>
</template>
</el-table-column>
</el-table>
@@ -876,43 +898,10 @@
/>
</section-card>
<el-dialog
<change-record-detail-dialog
v-model="changeRecordDetailVisible"
title="变更记录详情"
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>
:rows="changeRecordDetailRows"
/>
<div class="archive-form__footer" v-if="!isPublicViewPage">
<!-- 次要独立页返回 / 只读关闭 / 弹窗取消 -->
@@ -1323,7 +1312,25 @@
class="score-detail-table score-detail-table--inline"
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">
<template #default="{ row: detail }">
<div class="score-standard-cell">
@@ -1333,6 +1340,7 @@
inputmode="decimal"
:disabled="readonly"
@input="value => handleScoreValueInput(detail, value)"
@blur="handleScoreValueBlur(detail)"
>
<template #append>{{ getScoreRule(detail)?.changeUnit || '' }}</template>
</el-input>
@@ -1362,13 +1370,15 @@
<span>{{ formatScoreValue(getSignedScore(detail, 'selfScore')) }}</span>
</template>
</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 }">
<el-input
v-model="detail.reviewScore"
maxlength="10"
inputmode="decimal"
:disabled="readonly || !hasPermission('customer_type_re_cert')"
:disabled="
(!reviewMode && readonly) || !hasPermission('customer_type_re_cert')
"
@input="value => handleScoreDetailScoreInput(detail, 'reviewScore', value)"
>
<template
@@ -1389,7 +1399,7 @@
<template #default="{ row }">
<span class="score-category-table__name">{{ row.categoryName }}</span>
<span>自评{{ getSignedScore(row, 'selfScore') }}</span>
<span>复评{{ getSignedScore(row, 'reviewScore') }}</span>
<span v-if="!isNewScore">复评{{ getSignedScore(row, 'reviewScore') }}</span>
</template>
</el-table-column>
<el-table-column width="60" align="center">
@@ -1463,12 +1473,15 @@
<div class="score-detail-dialog__total">
<span>总分合计</span>
<span>自评{{ formatScoreValue(currentScore.selfScore) }}</span>
<span>复评{{ formatScoreValue(currentScore.reviewScore) }}</span>
<span v-if="!isNewScore">复评{{ formatScoreValue(currentScore.reviewScore) }}</span>
</div>
<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
v-if="!readonly && hasPermission('customer_type_re_cert')"
v-if="!isNewScore && (!readonly || reviewMode) && hasPermission('customer_type_re_cert')"
type="primary"
@click="confirmReviewScore"
>
@@ -1523,6 +1536,7 @@ import NProgress from 'nprogress';
import 'nprogress/nprogress.css';
import SectionCard from '@/components/section-card/main.vue';
import PdfPreview from '@/components/pdf-preview/main.vue';
import ChangeRecordDetailDialog from '@/components/change-record-detail-dialog/main.vue';
const createTimeRangeMap = {
createTimeRange: ['createTimeStart', 'createTimeEnd'],
@@ -1538,6 +1552,7 @@ const qualificationViewerPlugins = [
export default {
components: {
SectionCard,
ChangeRecordDetailDialog,
ArrowRight,
ElImageViewer,
OpenFileViewer,
@@ -1631,6 +1646,8 @@ export default {
receiptBox: false,
invoiceBox: false,
readonly: false,
reviewMode: false,
listActivated: false,
originalAccessType: '',
activeTab: 'scores',
archiveForm: this.emptyArchive(),
@@ -1825,7 +1842,7 @@ export default {
editBtn: false,
selection: true,
dialogClickModal: false,
menuWidth: 280,
menuWidth: 320,
column: [
{
label: '客商编号',
@@ -2019,10 +2036,20 @@ export default {
this.initBusinessDictionaries();
this.initScoreQuantificationOptions();
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);
}
},
activated() {
if (this.isArchivePage || this.isPublicViewPage) return;
if (!this.listActivated) {
this.listActivated = true;
return;
}
this.onLoad(this.page, this.query);
},
computed: {
...mapGetters(['permission', 'userInfo']),
isAdmin() {
@@ -2108,6 +2135,7 @@ export default {
return ids.join(',');
},
dialogTitle() {
if (this.reviewMode) return '客商复评';
if (this.readonly) return '查看客商档案';
return this.archiveForm.id ? '编辑客商档案' : '新增客商档案';
},
@@ -2151,6 +2179,14 @@ export default {
scoreList() {
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() {
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' },
});
},
openReview(row) {
this.$router.push({
path: '/vehicle/customer-archive/form',
query: { id: row.id, name: '客商复评', review: '1' },
});
},
closeArchive() {
if (this.isPublicViewPage) return;
this.archiveBox = false;
@@ -3993,6 +4035,7 @@ export default {
if (this.isPublicViewPage) this.ensurePublicViewOptions(this.archiveForm);
this.archiveBox = true;
this.loadChangeRecords();
if (this.reviewMode) this.openReviewScore();
})
.catch(() => {
this.archiveBox = true;
@@ -4034,14 +4077,15 @@ export default {
this.invoiceForm = this.emptyInvoice();
this.$refs.archiveForm?.clearValidate();
},
//
validateFormalForApproval(archive) {
if (archive.accessType !== 'formal') return true;
const hasCompletedScore = (archive.scores || []).some(
item => item.selfStatus === '已完成' || item.reviewStatus === '已完成' || item.finalScore
);
if (!hasCompletedScore) {
this.$message.warning('正式客商提交审核前必须完成信用评分');
//
validateScoreForApproval(archive) {
const scores = archive.scores || [];
if (scores.length > 1) {
this.$message.warning('评分记录只能有一条');
return false;
}
if (scores.length !== 1 || scores[0].selfStatus !== '已完成') {
this.$message.warning('提交审批前必须完成自评');
return false;
}
return true;
@@ -4151,7 +4195,7 @@ export default {
const archive = this.normalizeArchive();
//
archive.recordChange = true;
if (!this.validateFormalForApproval(archive)) return;
if (!this.validateScoreForApproval(archive)) return;
submit(archive).then(res => {
const data = res.data.data;
const id = (data && typeof data === 'object' ? data.id : data) || this.archiveForm.id;
@@ -4189,9 +4233,21 @@ export default {
approvalStatus,
}).then(() => submitApproval(id));
},
openReviewScore() {
const scores = this.archiveForm.scores || [];
if (!scores.length) {
this.$message.warning('暂无评分记录,无法复评');
return;
}
this.$nextTick(() => this.openScore(scores[0], 0));
},
addScore() {
if ((this.archiveForm.scores || []).length) {
this.$message.warning('请勿重复添加');
return;
}
this.scoreRecordIndex = -1;
this.currentScore = this.normalizeScore({});
this.currentScore = this.normalizeScore({ applyCreditLimit: '' });
this.scoreAttachmentFiles = [];
this.expandedScoreCategoryKeys = [];
this.scoreBox = true;
@@ -4273,7 +4329,7 @@ export default {
JSON.stringify(
(item.options || []).map(option => ({
label: option.label || option.optionName || option.value,
value: option.value || option.optionName || option.label,
value: option.value ?? option.optionName ?? option.label,
score: option.score,
changeType: option.changeType,
changeValue: option.changeValue,
@@ -4363,20 +4419,21 @@ export default {
const isScoreOption = item.changeType || item.changeValue || item.changeUnit;
const changeLabel = item.changeType === 'decrease' ? '每减少' : '每增加';
const scoreLabel = item.scoreType === 'subtract' ? '减' : '加';
const rawScore = item.score;
const generatedLabel = isScoreOption
? `${changeLabel}${item.changeValue || ''}${item.changeUnit || ''}${scoreLabel}${
item.score || ''
this.isBlankScore(rawScore) ? '' : rawScore
}`
: '';
const label = item.label || item.optionName || item.value || generatedLabel;
return {
...item,
label,
value: item.value || item.optionName || item.label || generatedLabel,
value: item.value ?? item.optionName ?? item.label ?? generatedLabel,
score:
item.scoreType === 'subtract' && item.score !== undefined
? -Math.abs(Number(item.score))
: item.score,
item.scoreType === 'subtract' && !this.isBlankScore(rawScore)
? -Math.abs(Number(rawScore))
: rawScore,
};
});
if (Array.isArray(value)) return normalize(value);
@@ -4440,23 +4497,35 @@ export default {
const valid = increase ? input >= base : input <= base;
if (!valid) {
detail.selfScore = '';
this.$message.warning(`输入值${increase ? '不能小于' : '不能大于'}基准数值${base}`);
this.calculateScore(this.currentScore);
return;
}
detail.selfScore = this.getScoreTypeCalculatedScore(detail);
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) {
const option = this.parseScoreOptions(detail.optionsJson).find(
item => item.value === optionValue
);
const score = Number(option?.score || 0);
detail.selfScore = score;
const rawScore = option?.score;
detail.selfScore = this.isBlankScore(rawScore) ? 0 : Number(rawScore);
this.calculateScore(this.currentScore);
},
handleScoreDetailScoreInput(detail, prop, value) {
const normalized = String(value || '')
const normalized = String(value ?? '')
.replace(/[^\d.]/g, '')
.replace(/^\./, '')
.replace(/(\..*)\./g, '$1')
@@ -4588,8 +4657,7 @@ export default {
sumScore(plusDetails, 'reviewScore') -
sumScore(minusDetails, 'reviewScore');
const hasReviewScore = details.some(
item =>
item.reviewScore !== undefined && item.reviewScore !== null && item.reviewScore !== ''
item => !this.isBlankScore(item.reviewScore)
);
const finalScore = hasReviewScore ? reviewScore : selfScore;
const fullMark = this.getScoreFullMark(details);
@@ -4609,13 +4677,17 @@ export default {
}
return score;
},
isBlankScore(value) {
return value === undefined || value === null || String(value).trim() === '';
},
hasIncompleteBasicScoreDetail(details = []) {
return this.getScoreCategoryDetailsByList(details, 'basic').some(item => {
if (this.isScoreTypeDetail(item)) {
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) {
@@ -4632,10 +4704,13 @@ export default {
score.reviewStatus = '已完成';
this.$message.success('评分已完成');
},
saveScoreDetail() {
saveScoreDetail(successMessage) {
if (!this.validateScoreForm(false)) return;
this.currentScore.attachments = JSON.parse(JSON.stringify(this.scoreAttachmentFiles || []));
this.currentScore.proofAttachments = this.stringifyAttachments(this.scoreAttachmentFiles);
this.currentScore.selfStatus = this.hasIncompleteBasicScoreDetail(this.currentScore.details)
? '未完成'
: '已完成';
this.calculateScore(this.currentScore);
const score = JSON.parse(JSON.stringify(this.currentScore));
if (this.scoreRecordIndex > -1) {
@@ -4647,23 +4722,62 @@ export default {
this.syncArchiveCreditFromScores();
this.activeTab = 'scores';
this.scoreBox = false;
this.saveScoreArchive();
},
saveScoreArchive() {
if (!this.archiveForm.id) {
this.$message.success('评分记录已添加,请保存客商档案后生效');
return;
const pending = this.saveScoreArchive(successMessage);
if (this.reviewMode && successMessage && pending && pending.then) {
pending.then(() => this.closeArchive());
}
submit(this.normalizeArchive()).then(() => {
this.$message.success('评分记录已保存');
return pending;
},
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() {
if (!this.validateScoreForm(true)) return;
this.currentScore.selfStatus = '已完成';
this.currentScore.reviewStatus = '已完成';
this.saveScoreDetail();
this.$message.success('复评确认成功');
this.saveScoreDetail('复评确认成功');
},
validateScoreForm(requireComplete) {
if (!this.currentScore.scoreDate) {
@@ -4701,7 +4815,7 @@ export default {
if (
item.reviewScore === undefined ||
item.reviewScore === null ||
item.reviewScore === ''
String(item.reviewScore).trim() === ''
) {
return false;
}
@@ -4718,7 +4832,7 @@ export default {
return false;
}
if (requireComplete && this.hasIncompleteBasicScoreDetail(this.currentScore.details)) {
this.$message.warning('请完整填写基础得分项评分明细');
this.$message.warning('请完整填写所有基础项目后再提交自评');
return false;
}
return true;
@@ -4773,7 +4887,7 @@ export default {
getDetail(row.id).then(res => {
const archive = this.normalizeDetail(res.data.data);
archive.qualificationAttachments = archive.qualificationAttachments || '';
if (!this.validateFormalForApproval(archive)) return;
if (!this.validateScoreForApproval(archive)) return;
this.$confirm('是否提交客商准入审批?', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
@@ -5260,10 +5374,16 @@ export default {
}
:deep(.el-table__body td) {
//min-height: 112px;
color: #303133;
}
:deep(.score-item-name) {
display: block;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
:deep(.el-select),
:deep(.el-input),
:deep(.el-select) {
@@ -5446,27 +5566,6 @@ export default {
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 {
:deep(.archive-page-form .archive-form__footer) {
left: 0;
+7 -4
View File
@@ -570,19 +570,22 @@
- 功能点:
- 任意状态的运单均支持复制。
- 复制后生成新的草稿运单,运单号重新生成。
- 复制时保留运单基础信息、货物信息、承运信息和过程配置相关信息,状态与过程记录重新初始化。
- 复制成功后跳转编辑页面
- 复制后运单号重新生成,业务状态与原运单保持一致
- 复制时保留运单基础信息、货物信息、承运信息和过程配置相关信息;过程打卡记录重新初始化。
- 若原运单为已完成,复制保存后按合同系统计费规则自动生成对应应收、应付明细
- 复制成功后跳转编辑页面(独立表单模式)或刷新列表(弹窗模式)。
- 异常与边界:
- 原运单不存在时提示数据不存在。
- 复制后名称、编号或关联字段触发唯一性校验时应重新处理。
- 复制失败时不影响原运单数据。
- 合同未开启系统计费、无匹配计费方案或自有运输等场景下,按既有规则跳过对应应收/应付生成。
- 反例:
- 不允许复制后沿用原运单号。
- 不允许复制后直接生成进行中或已完成状态。
- 不允许复制后状态与原运单不一致
- 不允许复制后保留原过程节点完成记录。
- 不允许已完成运单复制成功后遗漏按规则应生成的应收应付明细。
---