diff --git a/AGENTS.md b/AGENTS.md index ff0870d..a200e7f 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -168,6 +168,7 @@ Avue 表格/表单配置独立存放于 `src/option/` 目录,与 `views` 和 ` - 表格线条颜色需统一使用 `#EFF1F7`,包括表格外边框、单元格分割线和固定列边线。 - 表格偶数行需统一使用 `#FAFAFA` 背景色,固定列单元格必须与对应行背景保持一致。 - 表格操作列同时展示“查看、编辑、删除”等三个按钮时,操作列宽度统一不小于 `220px`,禁止出现按钮裁切或显示不全。 +- 表格操作列按钮统一仅展示文字,禁止配置 `icon` / `:icon` 或在操作按钮、操作下拉项中嵌入图标;顶部工具栏按钮不受此条限制。 - `.avue-crud__header` 顶部间距统一为 `12px`。 - 分页组件整体靠右展示,必须展示接口返回的数据总条数,`X条/页` 的页容量选择器必须放在总条数右侧。 - `.basic-container__card` 的直接子级 `.el-card__body` 内边距统一去除,保持 `padding: 0` diff --git a/src/api/common.js b/src/api/common.js index 3c2e7f8..b89c0e5 100644 --- a/src/api/common.js +++ b/src/api/common.js @@ -1,17 +1,37 @@ import request from '@/axios'; +import { ElLoading, ElMessage } from 'element-plus'; /** * 文件流返回 * @param url 接口地址 * @param params 接口参数 */ -export const exportBlob = (url, params) => { +export const exportBlob = (url, params, options = {}) => { + const { feedback = false, loadingText = '导出中', successText = '导出完成' } = options; + const loading = feedback + ? ElLoading.service({ + lock: true, + text: loadingText, + background: 'rgba(255, 255, 255, 0.7)', + }) + : null; return request({ url: url, params: params, method: 'get', responseType: 'blob', - }); + }) + .then(res => { + if (feedback) { + ElMessage.success(successText); + } + return res; + }) + .finally(() => { + if (loading) { + loading.close(); + } + }); }; export const importBlob = (url, file) => { diff --git a/src/components/empty-pagination/main.vue b/src/components/empty-pagination/main.vue index a71cfe2..bc6d59d 100644 --- a/src/components/empty-pagination/main.vue +++ b/src/components/empty-pagination/main.vue @@ -1,5 +1,5 @@ - + 启用 @@ -52,7 +51,6 @@ 禁用 @@ -60,7 +58,6 @@ 日志 diff --git a/src/views/authority/apiscope.vue b/src/views/authority/apiscope.vue index ec1730e..843bfe3 100644 --- a/src/views/authority/apiscope.vue +++ b/src/views/authority/apiscope.vue @@ -65,7 +65,6 @@ 启用 @@ -63,7 +62,6 @@ 禁用 @@ -71,7 +69,6 @@ 日志 diff --git a/src/views/authority/role.vue b/src/views/authority/role.vue index 707eb7d..97a5e83 100644 --- a/src/views/authority/role.vue +++ b/src/views/authority/role.vue @@ -40,7 +40,6 @@ @@ -72,7 +71,6 @@ @@ -142,6 +140,47 @@ import NProgress from 'nprogress'; import 'nprogress/nprogress.css'; const DEFAULT_COUNTRY_CODE = '+86'; +const REQUIRED_EXPORT_HEADERS = ['区域层级', '区域编码', '区域名称']; + +const getExcelCellText = value => { + if (value === undefined || value === null) return ''; + if (typeof value === 'object') { + if (Array.isArray(value.richText)) { + return value.richText.map(item => item.text || '').join(''); + } + if (value.text) return value.text; + if (value.result) return value.result; + } + return String(value); +}; + +const addWorkbookRequiredHeaderMarks = workbook => { + workbook.eachSheet(worksheet => { + const maxHeaderRow = Math.min(worksheet.rowCount || 0, 10); + for (let rowIndex = 1; rowIndex <= maxHeaderRow; rowIndex += 1) { + const row = worksheet.getRow(rowIndex); + let hasRequiredHeader = false; + row.eachCell(cell => { + const text = getExcelCellText(cell.value).trim(); + const header = text.replace(/\*+$/g, '').trim(); + if (REQUIRED_EXPORT_HEADERS.includes(header)) { + cell.value = `${header}*`; + hasRequiredHeader = true; + } + }); + if (hasRequiredHeader) break; + } + }); +}; + +const addExcelRequiredHeaderMarks = async blob => { + const ExcelJS = await import('exceljs'); + const workbook = new ExcelJS.Workbook(); + await workbook.xlsx.load(await blob.arrayBuffer()); + addWorkbookRequiredHeaderMarks(workbook); + const buffer = await workbook.xlsx.writeBuffer(); + return new Blob([buffer], { type: 'application/vnd.ms-excel' }); +}; export default { data() { @@ -830,10 +869,15 @@ export default { type: 'warning', }).then(() => { NProgress.start(); - exportBlob('/blade-system/airport-master/export-airport-master', this.buildExportParams()) - .then(res => { + exportBlob( + '/blade-system/airport-master/export-airport-master', + this.buildExportParams(), + { feedback: true } + ) + .then(async res => { + const blob = await addExcelRequiredHeaderMarks(res.data); downloadXls( - res.data, + blob, `空港机场主数据${this.$dayjs().format('YYYY-MM-DD HH:mm:ss')}.xlsx` ); }) @@ -851,7 +895,9 @@ export default { }, handleTemplate() { exportBlob( - `/blade-system/airport-master/export-template?${this.website.tokenHeader}=${getToken()}` + `/blade-system/airport-master/export-template?${this.website.tokenHeader}=${getToken()}`, + undefined, + { feedback: true } ).then(res => { downloadXls(res.data, '空港机场主数据模板.xlsx'); }); diff --git a/src/views/base/cargo-type.vue b/src/views/base/cargo-type.vue index 453cb06..e8c52d4 100644 --- a/src/views/base/cargo-type.vue +++ b/src/views/base/cargo-type.vue @@ -553,7 +553,11 @@ export default { type: 'warning', }).then(() => { NProgress.start(); - exportBlob('/blade-system/cargo-type/export-cargo-type', this.buildExportParams()) + exportBlob( + '/blade-system/cargo-type/export-cargo-type', + this.buildExportParams(), + { feedback: true } + ) .then(res => { downloadXls(res.data, `货物类型${this.$dayjs().format('YYYY-MM-DD HH:mm:ss')}.xlsx`); }) diff --git a/src/views/base/common-address.vue b/src/views/base/common-address.vue index f49faea..59892be 100644 --- a/src/views/base/common-address.vue +++ b/src/views/base/common-address.vue @@ -57,7 +57,6 @@ @@ -66,7 +65,6 @@ @@ -75,7 +73,6 @@ @@ -914,7 +911,8 @@ export default { NProgress.start(); exportBlob( '/blade-transport/common-address/export-common-address', - this.buildExportParams() + this.buildExportParams(), + { feedback: true } ) .then(res => { downloadXls(res.data, `常用地址${this.$dayjs().format('YYYY-MM-DD HH:mm:ss')}.xlsx`); diff --git a/src/views/base/currency.vue b/src/views/base/currency.vue index e174ebf..408de3e 100644 --- a/src/views/base/currency.vue +++ b/src/views/base/currency.vue @@ -71,7 +71,6 @@ @@ -80,7 +79,6 @@ @@ -551,7 +549,7 @@ export default { type: 'warning', }).then(() => { NProgress.start(); - exportBlob('/blade-system/currency/export-currency', this.buildExportParams()) + exportBlob('/blade-system/currency/export-currency', this.buildExportParams(), { feedback: true }) .then(res => { downloadXls(res.data, `币种汇率${this.$dayjs().format('YYYY-MM-DD HH:mm:ss')}.xlsx`); }) @@ -569,7 +567,8 @@ export default { }, handleTemplate() { exportBlob( - `/blade-system/currency/export-template?${this.website.tokenHeader}=${getToken()}` + `/blade-system/currency/export-template?${this.website.tokenHeader}=${getToken()}`, + { feedback: true } ).then(res => { downloadXls(res.data, '币种汇率模板.xlsx'); }); diff --git a/src/views/base/customer-type.vue b/src/views/base/customer-type.vue index c9ea090..b441b39 100644 --- a/src/views/base/customer-type.vue +++ b/src/views/base/customer-type.vue @@ -39,7 +39,6 @@ @@ -48,7 +47,6 @@ diff --git a/src/views/base/fee-item.vue b/src/views/base/fee-item.vue index f395c91..e06b159 100644 --- a/src/views/base/fee-item.vue +++ b/src/views/base/fee-item.vue @@ -39,7 +39,6 @@ @@ -48,7 +47,6 @@ diff --git a/src/views/base/port-terminal.vue b/src/views/base/port-terminal.vue index f166b80..f2486ad 100644 --- a/src/views/base/port-terminal.vue +++ b/src/views/base/port-terminal.vue @@ -116,7 +116,6 @@ @@ -125,7 +124,6 @@ @@ -172,6 +170,9 @@ /> + + {{ row.detailAddress || '-' }} + { + if (value === undefined || value === null) return ''; + if (typeof value === 'object') { + if (Array.isArray(value.richText)) { + return value.richText.map(item => item.text || '').join(''); + } + if (value.text) return value.text; + if (value.result) return value.result; + } + return String(value); +}; + +const removeWorkbookColumnsByHeaders = workbook => { + workbook.eachSheet(worksheet => { + const maxHeaderRow = Math.min(worksheet.rowCount || 0, 10); + for (let rowIndex = 1; rowIndex <= maxHeaderRow; rowIndex += 1) { + const row = worksheet.getRow(rowIndex); + const indexes = []; + row.eachCell((cell, colNumber) => { + const text = getExcelCellText(cell.value).trim(); + if (EXCLUDED_EXCEL_HEADERS.includes(text)) { + indexes.push(colNumber); + } + }); + if (indexes.length) { + indexes + .sort((a, b) => b - a) + .forEach(colNumber => worksheet.spliceColumns(colNumber, 1)); + break; + } + } + }); +}; + +const addWorkbookRequiredHeaderMarks = workbook => { + workbook.eachSheet(worksheet => { + const maxHeaderRow = Math.min(worksheet.rowCount || 0, 10); + for (let rowIndex = 1; rowIndex <= maxHeaderRow; rowIndex += 1) { + const row = worksheet.getRow(rowIndex); + let hasRequiredHeader = false; + row.eachCell(cell => { + const text = getExcelCellText(cell.value).trim(); + const header = text.replace(/\*+$/g, '').trim(); + if (REQUIRED_EXPORT_HEADERS.includes(header)) { + cell.value = `${header}*`; + hasRequiredHeader = true; + } + }); + if (hasRequiredHeader) break; + } + }); +}; + +const removeExcelColumnsByHeaders = async (blob, options = {}) => { + const { markRequiredHeaders = false } = options; + const ExcelJS = await import('exceljs'); + const workbook = new ExcelJS.Workbook(); + await workbook.xlsx.load(await blob.arrayBuffer()); + removeWorkbookColumnsByHeaders(workbook); + if (markRequiredHeaders) { + addWorkbookRequiredHeaderMarks(workbook); + } + const buffer = await workbook.xlsx.writeBuffer(); + return new Blob([buffer], { type: 'application/vnd.ms-excel' }); +}; export default { data() { @@ -466,6 +535,7 @@ export default { label: '详细地址', prop: 'detailAddress', type: 'textarea', + slot: true, minRows: 3, span: 24, minWidth: 220, @@ -1184,9 +1254,16 @@ export default { }); }, handleImport() { - openImportDialog(this, '港口码头主数据', () => { - this.loadPortOptions(); - }); + openImportDialog( + this, + '港口码头主数据', + () => { + this.loadPortOptions(); + }, + { + failDetailDecorator: removeWorkbookColumnsByHeaders, + } + ); }, handleExport() { this.$confirm('是否导出港口码头主数据?', '提示', { @@ -1195,10 +1272,17 @@ export default { type: 'warning', }).then(() => { NProgress.start(); - exportBlob('/blade-system/port-terminal/export-port-terminal', this.buildExportParams()) - .then(res => { + exportBlob( + '/blade-system/port-terminal/export-port-terminal', + this.buildExportParams(), + { feedback: true } + ) + .then(async res => { + const blob = await removeExcelColumnsByHeaders(res.data, { + markRequiredHeaders: true, + }); downloadXls( - res.data, + blob, `港口码头主数据${this.$dayjs().format('YYYY-MM-DD HH:mm:ss')}.xlsx` ); }) @@ -1216,11 +1300,24 @@ export default { }, handleTemplate() { exportBlob( - `/blade-system/port-terminal/export-template?${this.website.tokenHeader}=${getToken()}` - ).then(res => { - downloadXls(res.data, '港口码头主数据模板.xlsx'); + `/blade-system/port-terminal/export-template?${this.website.tokenHeader}=${getToken()}`, + undefined, + { feedback: true } + ).then(async res => { + const blob = await removeExcelColumnsByHeaders(res.data); + downloadXls(blob, '港口码头主数据模板.xlsx'); }); }, }, }; + + diff --git a/src/views/base/railway-station.vue b/src/views/base/railway-station.vue index 9f0817d..d47e8e2 100644 --- a/src/views/base/railway-station.vue +++ b/src/views/base/railway-station.vue @@ -63,7 +63,6 @@ @@ -72,7 +71,6 @@ @@ -154,7 +152,15 @@ export default { }; const validateTelegraphCode = (rule, value, callback) => { if (!/^[A-Z]{3}$/.test(String(value || '').toUpperCase())) { - callback(new Error('电报码为3位大写字母')); + callback(new Error('电报码格式不正确或已存在')); + } else { + callback(); + } + }; + const validateStationName = (rule, value, callback) => { + const name = String(value || '').trim(); + if (!name || name.length > 50) { + callback(new Error('请输入车站名称')); } else { callback(); } @@ -162,7 +168,7 @@ export default { const validateLongitude = (rule, value, callback) => { const message = getCoordinateValidationMessage(value, 'longitude'); if (message) { - callback(new Error(message)); + callback(new Error('经度范围不正确')); } else { callback(); } @@ -170,7 +176,7 @@ export default { const validateLatitude = (rule, value, callback) => { const message = getCoordinateValidationMessage(value, 'latitude'); if (message) { - callback(new Error(message)); + callback(new Error('纬度范围不正确')); } else { callback(); } @@ -245,7 +251,7 @@ export default { minWidth: 100, maxlength: 3, rules: [ - { required: true, message: '请输入电报码', trigger: 'blur' }, + { required: true, message: '电报码格式不正确或已存在', trigger: 'blur' }, { validator: validateTelegraphCode, trigger: 'blur' }, ], change: ({ value }) => this.handleTelegraphChange(value), @@ -255,8 +261,9 @@ export default { label: '车站名称', prop: 'name', minWidth: 120, + maxlength: 50, search: true, - rules: [{ required: true, message: '请输入车站名称', trigger: 'blur' }], + rules: [{ validator: validateStationName, trigger: 'blur' }], }, // { // label: '所属铁路线路', @@ -357,10 +364,7 @@ export default { formslot: true, minWidth: 130, overHidden: false, - rules: [ - { required: true, message: '请输入经度', trigger: 'blur' }, - { validator: validateLongitude, trigger: 'blur' }, - ], + rules: [{ validator: validateLongitude, trigger: 'blur' }], }, { label: '纬度', @@ -368,10 +372,7 @@ export default { formslot: true, minWidth: 130, overHidden: false, - rules: [ - { required: true, message: '请输入纬度', trigger: 'blur' }, - { validator: validateLatitude, trigger: 'blur' }, - ], + rules: [{ validator: validateLatitude, trigger: 'blur' }], }, { label: '数据来源', @@ -653,14 +654,14 @@ export default { return row; }, validateCoordinateFields(row) { - const longitudeMessage = getCoordinateValidationMessage(row.longitude, 'longitude', true); + const longitudeMessage = getCoordinateValidationMessage(row.longitude, 'longitude'); if (longitudeMessage) { - this.$message.warning(longitudeMessage); + this.$message.warning('经度范围不正确'); return false; } - const latitudeMessage = getCoordinateValidationMessage(row.latitude, 'latitude', true); + const latitudeMessage = getCoordinateValidationMessage(row.latitude, 'latitude'); if (latitudeMessage) { - this.$message.warning(latitudeMessage); + this.$message.warning('纬度范围不正确'); return false; } return true; @@ -681,13 +682,13 @@ export default { return Promise.reject(new Error('该TMIS编码已存在')); } if (telegraphExists) { - return Promise.reject(new Error('该电报码已存在')); + return Promise.reject(new Error('电报码格式不正确或已存在')); } return Promise.resolve(); }); }, handleSubmitError(error, loading) { - const uniqueMessages = ['该TMIS编码已存在', '该电报码已存在']; + const uniqueMessages = ['该TMIS编码已存在', '电报码格式不正确或已存在']; if (uniqueMessages.includes(error.message)) { this.$message.warning(error.message); } @@ -846,7 +847,11 @@ export default { type: 'warning', }).then(() => { NProgress.start(); - exportBlob('/blade-system/railway-station/export-railway-station', this.buildExportParams()) + exportBlob( + '/blade-system/railway-station/export-railway-station', + this.buildExportParams(), + { feedback: true } + ) .then(res => { downloadXls( res.data, @@ -867,7 +872,9 @@ export default { }, handleTemplate() { exportBlob( - `/blade-system/railway-station/export-template?${this.website.tokenHeader}=${getToken()}` + `/blade-system/railway-station/export-template?${this.website.tokenHeader}=${getToken()}`, + undefined, + { feedback: true } ).then(res => { downloadXls(res.data, '铁路车站主数据模板.xlsx'); }); diff --git a/src/views/base/region.vue b/src/views/base/region.vue index 9760e86..31f8673 100644 --- a/src/views/base/region.vue +++ b/src/views/base/region.vue @@ -625,7 +625,8 @@ export default { }).then(() => { NProgress.start(); exportBlob( - `/blade-system/region/export-region?${this.website.tokenHeader}=${getToken()}` + `/blade-system/region/export-region?${this.website.tokenHeader}=${getToken()}`, + { feedback: true } ).then(res => { downloadXls(res.data, `行政区划数据${this.$dayjs().format('YYYY-MM-DD HH:mm:ss')}.xlsx`); NProgress.done(); @@ -634,7 +635,8 @@ export default { }, handleTemplate() { exportBlob( - `/blade-system/region/export-template?${this.website.tokenHeader}=${getToken()}` + `/blade-system/region/export-template?${this.website.tokenHeader}=${getToken()}`, + { feedback: true } ).then(res => { downloadXls(res.data, '行政区划模板.xlsx'); }); diff --git a/src/views/business/common-cargo.vue b/src/views/business/common-cargo.vue index 43390b3..514419e 100644 --- a/src/views/business/common-cargo.vue +++ b/src/views/business/common-cargo.vue @@ -148,7 +148,6 @@ @@ -157,7 +156,6 @@ @@ -166,7 +164,6 @@ @@ -585,7 +582,7 @@ export default { type: 'warning', }).then(() => { NProgress.start(); - exportBlob(config.exportUrl, this.buildExportParams()) + exportBlob(config.exportUrl, this.buildExportParams(), { feedback: true }) .then(res => { downloadXls( res.data, @@ -599,9 +596,13 @@ export default { }, handleTemplate() { NProgress.start(); - exportBlob(config.templateUrl, { - [this.website.tokenHeader]: getToken(), - }) + exportBlob( + config.templateUrl, + { + [this.website.tokenHeader]: getToken(), + }, + { feedback: true } + ) .then(res => { downloadXls(res.data, `${config.title}模板.xlsx`); }) diff --git a/src/views/business/common-route.vue b/src/views/business/common-route.vue index a020278..47bbf66 100644 --- a/src/views/business/common-route.vue +++ b/src/views/business/common-route.vue @@ -105,7 +105,6 @@ @@ -114,7 +113,6 @@ @@ -123,7 +121,6 @@ @@ -612,7 +609,7 @@ export default { type: 'warning', }).then(() => { NProgress.start(); - exportBlob(config.exportUrl, this.buildExportParams()) + exportBlob(config.exportUrl, this.buildExportParams(), { feedback: true }) .then(res => { downloadXls( res.data, diff --git a/src/views/business/components/business-crud-page.vue b/src/views/business/components/business-crud-page.vue index c401be5..625b886 100644 --- a/src/views/business/components/business-crud-page.vue +++ b/src/views/business/components/business-crud-page.vue @@ -109,7 +109,6 @@ @@ -118,28 +117,27 @@ 编辑 - + 复制 - + 启用 - + 停用 - + 重新派单 - + 取消 - + 完成 {{ operation.label }} - + 删除 @@ -562,7 +559,7 @@ export default { type: 'warning', }).then(() => { NProgress.start(); - exportBlob(this.config.exportUrl, this.buildExportParams()) + exportBlob(this.config.exportUrl, this.buildExportParams(), { feedback: true }) .then(res => { downloadXls(res.data, `${this.config.exportName || this.config.title}${this.$dayjs().format('YYYY-MM-DD HH:mm:ss')}.xlsx`); }) @@ -573,9 +570,13 @@ export default { }, handleTemplate() { NProgress.start(); - exportBlob(this.config.templateUrl, { - [this.website.tokenHeader]: getToken(), - }) + exportBlob( + this.config.templateUrl, + { + [this.website.tokenHeader]: getToken(), + }, + { feedback: true } + ) .then(res => { downloadXls(res.data, `${this.config.title}模板.xlsx`); }) diff --git a/src/views/business/process-config.vue b/src/views/business/process-config.vue index 7f7b5c2..7f21472 100644 --- a/src/views/business/process-config.vue +++ b/src/views/business/process-config.vue @@ -336,7 +336,6 @@ @@ -345,7 +344,6 @@ @@ -354,7 +352,6 @@ @@ -363,7 +360,6 @@ @@ -372,7 +368,6 @@ @@ -381,7 +376,6 @@ @@ -952,7 +946,7 @@ export default { type: 'warning', }).then(() => { NProgress.start(); - exportBlob(config.exportUrl, this.buildExportParams()) + exportBlob(config.exportUrl, this.buildExportParams(), { feedback: true }) .then(res => { downloadXls( res.data, diff --git a/src/views/business/project-apply.vue b/src/views/business/project-apply.vue index 55d2064..fe264d8 100644 --- a/src/views/business/project-apply.vue +++ b/src/views/business/project-apply.vue @@ -1,20 +1,1563 @@ - + + + + + 新增 + + + 批量导出 + + + 批量删除 + + + + + + + {{ displayStatus(row, 'approvalStatus') }} + + + + + + 查看 + + + 编辑 + + + {{ operation.label }} + + + 删除 + + + + + + + + + 项目基本信息 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 项目主要内容 + + + visible && loadCustomerOptions('客户')" + @change="handleCustomerChange" + > + + + + + visible && loadCustomerOptions('承运商')" + @change="handleCarrierChange" + > + + + + + handleNumberInput('fundLimit', value)" + /> + + + handleNumberInput('receivableLimit', value)" + /> + + + handleIntegerInput('receivableDays', value)" + > + 天 + + + + handleIntegerInput('paymentDays', value)" + > + 天 + + + + + + + + + handleIntegerInput('cargoQuantity', value)" + /> + + + + + + + + + + + + + + + + + + + handleNumberInput('projectScale', value)" + /> + + + + + + + + handleNumberInput('estimatedProfit', value)" + /> + + + handleNumberInput('fundDemand', value)" + /> + + + + + + + + + + + + + + + + + + + 客户信息 + 选择客户/承运商信息后,自动关联出客商信息 + + + + + + + + + + + + + 承运商信息 + + + + + + + + + + + 项目情况说明 + + + + + + + + + + + + + + + + + + 项目材料 + + 项目立项报告、考察报告、立项成本测算表、企业资质板附件未上传 + + + 批量下载 + + + + + + + + + + + + + + + + + + + + + + {{ formatFileSize(row.size) }} + + + + + + 下载 + 删除 + + + + + + + 变更记录 + 查看页面显示变更记录,变更提交后生成,可查看详情页面 + + + + + + + + + + + + + {{ row.action || '查看' }} + + + + + + + + + + + + + + + + 查询 + 重置 + + + + + + + + + 选择 + + + + + + + + + + diff --git a/src/views/flow/manager.vue b/src/views/flow/manager.vue index 8fe4e19..46e4a1d 100644 --- a/src/views/flow/manager.vue +++ b/src/views/flow/manager.vue @@ -26,7 +26,6 @@ 变更状态 @@ -34,7 +33,6 @@ 流程图 @@ -42,7 +40,6 @@ 删除 diff --git a/src/views/flow/model.vue b/src/views/flow/model.vue index bef2f28..2b56e0d 100644 --- a/src/views/flow/model.vue +++ b/src/views/flow/model.vue @@ -40,7 +40,6 @@ type="primary" text size="default" - icon="el-icon-setting" v-if="permission.flow_model_update" @click.stop="handleUpdate(scope.row, scope.index)" >配置 @@ -49,7 +48,6 @@ type="primary" text size="default" - icon="el-icon-upload" v-if="permission.flow_model_deploy" @click.stop="handleDeploy(scope.row, scope.index)" >部署 @@ -58,7 +56,6 @@ type="primary" text size="default" - icon="el-icon-delete" v-if="permission.flow_model_delete" @click.stop="handleSlotDelete(scope.row, scope.index)" >删除 diff --git a/src/views/job/jobinfo.vue b/src/views/job/jobinfo.vue index 01f007b..d237025 100644 --- a/src/views/job/jobinfo.vue +++ b/src/views/job/jobinfo.vue @@ -35,7 +35,7 @@ - 运 行 diff --git a/src/views/job/jobserver.vue b/src/views/job/jobserver.vue index 22d93ac..f6fbbe0 100644 --- a/src/views/job/jobserver.vue +++ b/src/views/job/jobserver.vue @@ -35,7 +35,7 @@ - 服务跳转 diff --git a/src/views/report/reportlist.vue b/src/views/report/reportlist.vue index 314cc0c..2fcc7fc 100644 --- a/src/views/report/reportlist.vue +++ b/src/views/report/reportlist.vue @@ -26,7 +26,6 @@ 设计 @@ -34,7 +33,6 @@ 预览 diff --git a/src/views/resource/attach.vue b/src/views/resource/attach.vue index 31e1085..0c4870b 100644 --- a/src/views/resource/attach.vue +++ b/src/views/resource/attach.vue @@ -40,7 +40,6 @@ 下载 diff --git a/src/views/resource/oss.vue b/src/views/resource/oss.vue index 14f8cc1..6365752 100644 --- a/src/views/resource/oss.vue +++ b/src/views/resource/oss.vue @@ -34,7 +34,6 @@ 调试 @@ -42,7 +41,6 @@ 启用 diff --git a/src/views/resource/sms.vue b/src/views/resource/sms.vue index a97e924..043793f 100644 --- a/src/views/resource/sms.vue +++ b/src/views/resource/sms.vue @@ -34,7 +34,6 @@ 调试 @@ -42,7 +41,6 @@ 启用 diff --git a/src/views/system/authlock.vue b/src/views/system/authlock.vue index 4bca7d8..4c7db05 100644 --- a/src/views/system/authlock.vue +++ b/src/views/system/authlock.vue @@ -49,7 +49,6 @@ 查看 @@ -57,7 +56,6 @@ v-if="(row.lockStatus === 'LOCKED' || row.lockStatus === 'PRE_LOCKED') && !row.expired" text type="primary" - icon="el-icon-key" @click.stop="handleAuthLockUnlock(row)" >解锁 diff --git a/src/views/system/authlog.vue b/src/views/system/authlog.vue index 5a44537..22ab38f 100644 --- a/src/views/system/authlog.vue +++ b/src/views/system/authlog.vue @@ -21,7 +21,6 @@ 查看 diff --git a/src/views/system/dept.vue b/src/views/system/dept.vue index 5f3b684..c78e94e 100644 --- a/src/views/system/dept.vue +++ b/src/views/system/dept.vue @@ -35,7 +35,6 @@ 新增子项 diff --git a/src/views/system/dict.vue b/src/views/system/dict.vue index 6914445..9dc8956 100644 --- a/src/views/system/dict.vue +++ b/src/views/system/dict.vue @@ -35,7 +35,6 @@ 字典配置 @@ -83,7 +82,6 @@ 新增子项 diff --git a/src/views/system/dictbiz.vue b/src/views/system/dictbiz.vue index 3b31529..e77df2b 100644 --- a/src/views/system/dictbiz.vue +++ b/src/views/system/dictbiz.vue @@ -35,7 +35,6 @@ 字典配置 @@ -83,7 +82,6 @@ 新增子项 diff --git a/src/views/system/menu.vue b/src/views/system/menu.vue index 31d6f62..dd54e05 100644 --- a/src/views/system/menu.vue +++ b/src/views/system/menu.vue @@ -35,7 +35,6 @@ 新增子项 diff --git a/src/views/system/param.vue b/src/views/system/param.vue index cb0a975..a5355e7 100644 --- a/src/views/system/param.vue +++ b/src/views/system/param.vue @@ -13,7 +13,7 @@ 搜 索 - 清 空 + 重 置 @@ -87,7 +87,6 @@ v-if="this.permissionList.viewBtn" type="primary" text - icon="el-icon-view" @click="handleView(row)" >查看 @@ -95,7 +94,6 @@ v-if="this.permissionList.editBtn" type="primary" text - icon="el-icon-edit" @click="handleEdit(row)" >编辑 @@ -103,7 +101,6 @@ v-if="this.permissionList.delBtn" type="primary" text - icon="el-icon-delete" @click="rowDel(row)" >删除 diff --git a/src/views/system/tenant.vue b/src/views/system/tenant.vue index ccc54b9..ea7e2ab 100644 --- a/src/views/system/tenant.vue +++ b/src/views/system/tenant.vue @@ -119,7 +119,6 @@ v-if="userInfo.authority.includes('administrator') && !recycleMode" type="primary" text - icon="el-icon-coin" @click.stop="handleRowDatasource(scope.row)" >数据源 @@ -127,7 +126,6 @@ v-if="userInfo.authority.includes('administrator') && !recycleMode" type="primary" text - icon="el-icon-notebook" @click.stop="handleRowPackage(scope.row)" >产品包 diff --git a/src/views/system/topmenu.vue b/src/views/system/topmenu.vue index a67eeb7..867f4ac 100644 --- a/src/views/system/topmenu.vue +++ b/src/views/system/topmenu.vue @@ -42,7 +42,6 @@ v-if="permission.topmenu_setting" type="primary" text - icon="el-icon-setting" @click.stop="handleRowMenuSetting(scope.row)" >配置 diff --git a/src/views/system/user.vue b/src/views/system/user.vue index 905ae90..4760c1d 100644 --- a/src/views/system/user.vue +++ b/src/views/system/user.vue @@ -128,7 +128,6 @@ 查看 @@ -136,48 +135,39 @@ 编辑 handleDropdownCommand(command, row, index)"> - 更 多 + 更 多 - {{ row.isLeader === 1 ? '取消主管' : '设置主管' }} - 角色配置 - 平台配置 - 认证日志 - 锁定账号 - 密码重置 - 修改密码 - 删除用户 diff --git a/src/views/tool/code.vue b/src/views/tool/code.vue index fcefeed..6cfa1f6 100644 --- a/src/views/tool/code.vue +++ b/src/views/tool/code.vue @@ -85,7 +85,6 @@ 启 用 diff --git a/src/views/tool/formsetting.vue b/src/views/tool/formsetting.vue index 52db0b0..303fe06 100644 --- a/src/views/tool/formsetting.vue +++ b/src/views/tool/formsetting.vue @@ -27,7 +27,7 @@ - 设 计 diff --git a/src/views/tool/model.vue b/src/views/tool/model.vue index 35b5974..f25228b 100644 --- a/src/views/tool/model.vue +++ b/src/views/tool/model.vue @@ -29,7 +29,6 @@ @@ -104,7 +103,6 @@ @@ -113,7 +111,6 @@ @@ -917,7 +914,7 @@ export default { type: 'warning', }).then(() => { NProgress.start(); - exportBlob('/blade-transport/driver/export-driver', this.buildExportParams()) + exportBlob('/blade-transport/driver/export-driver', this.buildExportParams(), { feedback: true }) .then(res => { downloadXls(res.data, `司机管理${this.$dayjs().format('YYYY-MM-DD HH:mm:ss')}.xlsx`); }) diff --git a/src/views/transportCapacity/ship.vue b/src/views/transportCapacity/ship.vue index dce55fa..8a0a8c8 100644 --- a/src/views/transportCapacity/ship.vue +++ b/src/views/transportCapacity/ship.vue @@ -90,7 +90,6 @@ @@ -99,7 +98,6 @@ @@ -108,7 +106,6 @@ @@ -945,7 +942,8 @@ export default { NProgress.start(); exportBlob( '/blade-transport/transport-ship/export-transport-ship', - this.buildExportParams() + this.buildExportParams(), + { feedback: true } ) .then(res => { downloadXls(res.data, `船舶管理${this.$dayjs().format('YYYY-MM-DD HH:mm:ss')}.xlsx`); diff --git a/src/views/transportCapacity/vehicle.vue b/src/views/transportCapacity/vehicle.vue index 2df4fc8..d72b645 100644 --- a/src/views/transportCapacity/vehicle.vue +++ b/src/views/transportCapacity/vehicle.vue @@ -82,7 +82,6 @@ @@ -91,7 +90,6 @@ @@ -100,7 +98,6 @@ @@ -894,7 +891,8 @@ export default { NProgress.start(); exportBlob( '/blade-transport/transport-vehicle/export-transport-vehicle', - this.buildExportParams() + this.buildExportParams(), + { feedback: true } ) .then(res => { downloadXls(res.data, `车辆管理${this.$dayjs().format('YYYY-MM-DD HH:mm:ss')}.xlsx`); diff --git a/src/views/util/crud-form.vue b/src/views/util/crud-form.vue index b875e93..87550c4 100644 --- a/src/views/util/crud-form.vue +++ b/src/views/util/crud-form.vue @@ -6,7 +6,7 @@ add - edit diff --git a/src/views/util/demo/dict-classic.vue b/src/views/util/demo/dict-classic.vue index 6cff805..29af2da 100644 --- a/src/views/util/demo/dict-classic.vue +++ b/src/views/util/demo/dict-classic.vue @@ -37,7 +37,6 @@ 新增子项 diff --git a/src/views/util/demo/dict.vue b/src/views/util/demo/dict.vue index 4b7df10..0332252 100644 --- a/src/views/util/demo/dict.vue +++ b/src/views/util/demo/dict.vue @@ -91,7 +91,6 @@ 新增子项 diff --git a/src/views/util/demo/dictbiz.vue b/src/views/util/demo/dictbiz.vue index 30316c0..2e1c6e1 100644 --- a/src/views/util/demo/dictbiz.vue +++ b/src/views/util/demo/dictbiz.vue @@ -91,7 +91,6 @@ 新增子项 diff --git a/src/views/vehicle/accident-record.vue b/src/views/vehicle/accident-record.vue index c6122c6..b53628a 100644 --- a/src/views/vehicle/accident-record.vue +++ b/src/views/vehicle/accident-record.vue @@ -439,7 +439,8 @@ export default { NProgress.start(); exportBlob( '/blade-transport/accident-record/export-accident-record', - this.buildExportParams() + this.buildExportParams(), + { feedback: true } ) .then(res => { downloadXls(res.data, `事故记录${this.$dayjs().format('YYYY-MM-DD HH:mm:ss')}.xlsx`); @@ -461,7 +462,8 @@ export default { }, handleTemplate() { exportBlob( - `/blade-transport/accident-record/export-template?${this.website.tokenHeader}=${getToken()}` + `/blade-transport/accident-record/export-template?${this.website.tokenHeader}=${getToken()}`, + { feedback: true } ).then(res => { downloadXls(res.data, '事故记录模板.xlsx'); }); diff --git a/src/views/vehicle/annual-inspection-record.vue b/src/views/vehicle/annual-inspection-record.vue index b14e039..7a769d7 100644 --- a/src/views/vehicle/annual-inspection-record.vue +++ b/src/views/vehicle/annual-inspection-record.vue @@ -422,7 +422,8 @@ export default { NProgress.start(); exportBlob( '/blade-transport/annual-inspection-record/export-annual-inspection-record', - this.buildExportParams() + this.buildExportParams(), + { feedback: true } ) .then(res => { downloadXls(res.data, `年检记录${this.$dayjs().format('YYYY-MM-DD HH:mm:ss')}.xlsx`); @@ -443,7 +444,8 @@ export default { exportBlob( `/blade-transport/annual-inspection-record/export-template?${ this.website.tokenHeader - }=${getToken()}` + }=${getToken()}`, + { feedback: true } ).then(res => { downloadXls(res.data, '年检记录模板.xlsx'); }); diff --git a/src/views/vehicle/credit-score-quantification.vue b/src/views/vehicle/credit-score-quantification.vue index 74132d2..8f75f81 100644 --- a/src/views/vehicle/credit-score-quantification.vue +++ b/src/views/vehicle/credit-score-quantification.vue @@ -72,7 +72,6 @@ @@ -81,8 +80,7 @@ 发布 @@ -90,7 +88,6 @@ @@ -99,7 +96,6 @@ @@ -147,7 +143,7 @@ 评分表 - + {{ row.items.length }} @@ -166,7 +162,7 @@ - + 评分类目:{{ currentCategory.categoryName }} @@ -211,7 +208,7 @@ - + @@ -220,9 +217,15 @@ 编辑 - 删除 + 删除 + @@ -232,39 +235,57 @@ - - - - - - - - - - - - - - + + + + + + + + + + + + + - + - - - 分 + + + + 分 + 删除 - +添加选项 + + + +添加选项 + + @@ -274,12 +295,23 @@ - + - + index !== currentIndex && String(item.itemName || '').trim() === name + ); + }, saveItem() { if (!this.itemForm.itemName || !this.itemForm.itemName.trim()) { this.$message.warning('请输入评分项目'); return; } + if (this.hasDuplicateItemName(this.itemForm.itemName, this.currentItemIndex)) { + this.$message.warning('评分项目名称不能重复'); + return; + } + if ( + this.itemForm.score === undefined || + this.itemForm.score === null || + this.itemForm.score === '' + ) { + this.$message.warning('请输入分值'); + return; + } if (!this.itemForm.options.length) { this.$message.warning('至少添加1条档位选项'); return; } + const invalidOption = this.itemForm.options.some( + option => + !String(option.optionName || '').trim() || + option.score === undefined || + option.score === null || + option.score === '' + ); + if (invalidOption) { + this.$message.warning('请完整填写档位选项'); + return; + } const item = JSON.parse(JSON.stringify(this.itemForm)); + item.itemName = String(item.itemName || '').trim(); + item.options = item.options.map(option => ({ + ...option, + optionName: String(option.optionName || '').trim(), + })); if (this.currentItemIndex >= 0) { this.currentCategory.items.splice(this.currentItemIndex, 1, item); } else { @@ -629,6 +699,10 @@ export default { this.itemBox = false; }, removeItem(index) { + if (this.currentCategory.items.length <= 1) { + this.$message.warning('评分类目详情至少保留1条评分项目'); + return; + } this.currentCategory.items.splice(index, 1); }, addOption() { @@ -648,6 +722,13 @@ export default { (item, index) => index !== currentIndex && String(item.creditLevel || '').trim() === level ); }, + handleStandardCreditLevelChange(value) { + if (!value) return; + if (this.hasDuplicateCreditLevel(value, this.currentStandardIndex)) { + this.$message.warning('信用等级不能重复'); + this.standardForm.creditLevel = ''; + } + }, isValidCreditLevel(creditLevel) { return this.creditLevelOptions.some(item => item.value === String(creditLevel || '').trim()); }, @@ -681,8 +762,28 @@ export default { this.$message.warning('得分率下限必须小于上限'); return; } + if ( + Number(this.standardForm.creditLimitLower) >= Number(this.standardForm.creditLimitUpper) + ) { + this.$message.warning('授信额度下限必须小于上限'); + return; + } const standard = JSON.parse(JSON.stringify(this.standardForm)); standard.creditLevel = String(standard.creditLevel || '').trim(); + const nextStandards = JSON.parse(JSON.stringify(this.configForm.standards)); + if (this.currentStandardIndex >= 0) { + nextStandards.splice(this.currentStandardIndex, 1, standard); + } else { + nextStandards.push(standard); + } + if (this.hasRangeOverlap(nextStandards, 'scoreRateLower', 'scoreRateUpper')) { + this.$message.warning('评估标准中的得分率区间不能重叠'); + return; + } + if (this.hasRangeOverlap(nextStandards, 'creditLimitLower', 'creditLimitUpper')) { + this.$message.warning('评估标准中的授信额度区间不能重叠'); + return; + } if (this.currentStandardIndex >= 0) { this.configForm.standards.splice(this.currentStandardIndex, 1, standard); } else { @@ -693,6 +794,21 @@ export default { removeStandard(index) { this.configForm.standards.splice(index, 1); }, + isBlankValue(value) { + return value === undefined || value === null || value === ''; + }, + hasRangeOverlap(list, lowerProp, upperProp) { + const ranges = list + .map(item => ({ + lower: Number(item[lowerProp]), + upper: Number(item[upperProp]), + })) + .sort((a, b) => a.lower - b.lower); + return ranges.some((range, index) => { + const next = ranges[index + 1]; + return next && range.upper > next.lower; + }); + }, validateConfig() { if (!this.configForm.name || !this.configForm.name.trim()) { this.$message.warning('请输入评定表名称'); @@ -704,6 +820,13 @@ export default { return false; } for (const category of this.configForm.categories) { + const itemNames = (category.items || []) + .map(item => String(item.itemName || '').trim()) + .filter(Boolean); + if (new Set(itemNames).size !== itemNames.length) { + this.$message.warning(`${category.categoryName}的评分项目名称不能重复`); + return false; + } for (const item of category.items) { if (!item.options || item.options.length === 0) { this.$message.warning(`${item.itemName}至少存在1条档位选项`); @@ -724,9 +847,90 @@ export default { } return true; }, + validatePublishConfig(config) { + const detail = this.normalizeDetail(config || {}); + const basic = detail.categories.find(item => item.categoryCode === 'basic'); + if (!basic || !basic.items || basic.items.length === 0) { + this.$message.warning('发布前至少配置1条基础得分项目'); + return false; + } + for (const category of detail.categories) { + for (const item of category.items || []) { + if (!String(item.itemName || '').trim()) { + this.$message.warning('发布前请完整填写评分项目名称'); + return false; + } + if (!item.options || item.options.length === 0) { + this.$message.warning(`${item.itemName || '评分项目'}至少配置1个档位选项`); + return false; + } + const invalidOption = item.options.some( + option => !String(option.optionName || '').trim() || this.isBlankValue(option.score) + ); + if (invalidOption) { + this.$message.warning(`${item.itemName || '评分项目'}的档位选项未填写完整`); + return false; + } + } + } + + const standards = detail.standards || []; + if (standards.length === 0) { + this.$message.warning('发布前至少配置1条信用等级评估标准'); + return false; + } + const requiredFields = [ + 'scoreRateLower', + 'scoreRateUpper', + 'scoreRateIncrease', + 'creditLimitLower', + 'creditLimitUpper', + 'creditLimitIncrease', + ]; + for (const standard of standards) { + if (!standard.creditLevel) { + this.$message.warning('评估标准中的信用等级不能为空'); + return false; + } + if (!this.isValidCreditLevel(standard.creditLevel)) { + this.$message.warning('评估标准中的信用等级只能选择A/B/C/D/E/F'); + return false; + } + if (requiredFields.some(field => this.isBlankValue(standard[field]))) { + this.$message.warning(`${standard.creditLevel}信用等级评估标准必填项未填写完整`); + return false; + } + if (Number(standard.scoreRateLower) >= Number(standard.scoreRateUpper)) { + this.$message.warning(`${standard.creditLevel}信用等级得分率下限必须小于上限`); + return false; + } + if (Number(standard.creditLimitLower) >= Number(standard.creditLimitUpper)) { + this.$message.warning(`${standard.creditLevel}信用等级授信额度下限必须小于上限`); + return false; + } + } + + const creditLevels = standards.map(item => String(item.creditLevel || '').trim()); + if (new Set(creditLevels).size !== creditLevels.length) { + this.$message.warning('评估标准中的信用等级不能重复'); + return false; + } + if (this.hasRangeOverlap(standards, 'scoreRateLower', 'scoreRateUpper')) { + this.$message.warning('评估标准中的得分率区间不能重叠'); + return false; + } + if (this.hasRangeOverlap(standards, 'creditLimitLower', 'creditLimitUpper')) { + this.$message.warning('评估标准中的授信额度区间不能重叠'); + return false; + } + return true; + }, saveConfig() { if (!this.validateConfig()) return; - submit(this.configForm).then(() => { + submit({ + ...this.configForm, + status: 3, + }).then(() => { this.configBox = false; this.onLoad(this.page); this.$message({ type: 'success', message: '操作成功!' }); @@ -767,8 +971,16 @@ export default { cancelButtonText: '取消', type: 'warning', }) - .then(() => publish(row.id)) - .then(() => { + .then(() => getDetail(row.id)) + .then(res => { + const detail = this.normalizeDetail(res.data.data || {}); + if (!this.validatePublishConfig(detail)) { + return false; + } + return publish(row.id).then(() => true); + }) + .then(published => { + if (!published) return; this.onLoad(this.page); this.$message({ type: 'success', message: '发布成功!' }); }); @@ -776,7 +988,7 @@ export default { handleStatus(row) { const nextStatus = row.status === 1 ? 2 : 1; const actionName = nextStatus === 1 ? '启用' : '停用'; - this.$confirm(`是否确认${actionName}该量表?`, { + this.$confirm(`是否确认${actionName}该评分量化表?`, { confirmButtonText: '确定', cancelButtonText: '取消', type: 'warning', @@ -830,7 +1042,8 @@ export default { NProgress.start(); exportBlob( '/blade-transport/credit-score-quantification/export-credit-score-quantification', - this.buildExportParams() + this.buildExportParams(), + { feedback: true } ) .then(res => { downloadXls(res.data, `评分量化表${this.$dayjs().format('YYYY-MM-DD HH:mm:ss')}.xlsx`); @@ -854,7 +1067,8 @@ export default { exportBlob( `/blade-transport/credit-score-quantification/export-template?${ this.website.tokenHeader - }=${getToken()}` + }=${getToken()}`, + { feedback: true } ).then(res => { downloadXls(res.data, '评分量化表模板.xlsx'); }); @@ -907,18 +1121,105 @@ export default { color: #606266; } +.score-item-form { + width: 100%; + max-width: 1400px; + margin: 0 auto; + padding: 4px 0 6px; + + :deep(.el-form-item) { + margin-bottom: 18px; + } + + :deep(.el-form-item__label) { + padding-right: 10px; + color: #606266; + font-size: 14px; + line-height: 32px; + } + + :deep(.el-input), + :deep(.el-input-number) { + width: 100%; + } + + :deep(.el-input__wrapper), + :deep(.el-input-number .el-input__wrapper) { + min-height: 32px; + border-radius: 0; + box-shadow: 0 0 0 1px #dcdfe6 inset; + } + + :deep(.el-input__inner) { + height: 32px; + color: #303133; + font-size: 14px; + font-weight: 400; + text-align: center; + } + + &__top { + display: grid; + grid-template-columns: 360px 360px; + column-gap: 112px; + + :deep(.el-form-item) { + margin-bottom: 18px; + } + } +} + .option-editor { width: 100%; } .option-row { display: grid; - grid-template-columns: minmax(260px, 1fr) 190px 32px 70px; - gap: 12px; + grid-template-columns: minmax(420px, 1fr) 315px 74px; + gap: 54px; align-items: center; margin-bottom: 14px; } +.option-score { + position: relative; + + .score-unit { + position: absolute; + top: 50%; + right: 12px; + z-index: 1; + margin: 0; + color: #303133; + font-size: 14px; + line-height: 1; + transform: translateY(-50%); + } + + :deep(.el-input__inner) { + padding-right: 42px; + } +} + +.option-delete { + justify-self: start; + min-height: 32px; + padding: 0; + font-size: 14px; +} + +.option-add { + display: flex; + justify-content: center; + margin-top: 6px; + + :deep(.el-button) { + min-height: 32px; + font-size: 16px; + font-weight: 600; + } +} + .range-input { display: flex; align-items: center; @@ -934,4 +1235,42 @@ export default { max-height: 70vh; overflow: auto; } + +:deep(.score-item-dialog) { + .el-dialog__header { + padding: 20px 24px 10px; + } + + .el-dialog__title { + color: #303133; + font-size: 20px; + font-weight: 700; + line-height: 1; + } + + .el-dialog__body { + padding: 12px 24px 14px; + } + + .el-dialog__footer { + padding: 12px 24px 20px; + } + + .dialog-footer, + .el-dialog__footer { + text-align: right; + } + + .el-dialog__footer .el-button { + min-width: 88px; + min-height: 32px; + border-radius: 4px; + font-size: 14px; + font-weight: 400; + } + + .el-dialog__footer .el-button + .el-button { + margin-left: 12px; + } +} diff --git a/src/views/vehicle/customer-archive.vue b/src/views/vehicle/customer-archive.vue index b074f27..f71abc8 100644 --- a/src/views/vehicle/customer-archive.vue +++ b/src/views/vehicle/customer-archive.vue @@ -66,7 +66,6 @@ @@ -75,7 +74,6 @@ @@ -84,7 +82,6 @@ @@ -105,7 +101,6 @@ @@ -114,7 +109,6 @@ @@ -123,7 +117,6 @@ @@ -152,36 +145,22 @@ :model="archiveForm" :rules="formRules" label-position="right" + label-width="auto" :disabled="readonly" class="archive-form" > 基本信息 - - + - - - 临时客商 - 正式客商 - - - - - - + + - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + - + - - + + handleDecimalInput('registeredCapital', value)" + /> - + + + + + + + + + + + + + @@ -261,83 +240,101 @@ + + + + + + 无固定期限 + + + + - - - - - - - - - - - - - - handleDecimalInput('invoiceTaxRate', value, 13)" /> - - - - - - - - - - + + + + + - + - + handleDecimalInput('maxCreditLimit', value)" + /> - - - - - - - - - - handleDecimalInput('applyCreditLimit', value)" /> - + + + + + + + + + - - 资质附件 - - OCR上传识别 - 添加附件 - 批量下载 - - - - - - - - - - - - - - - - - - - - handleQualificationUploadSuccess(file, row)" - /> - - - - - - - - - - {{ row.size || '-' }} - - - - - {{ row.uploadUserName || '-' }} - - - - - {{ row.uploadTime || '-' }} - - - - - 删除 - - - - @@ -437,90 +355,271 @@ 添加评分记录 - - - + + - + {{ row.__index }} - - - - + - + {{ row.scoreDate }} - + - + {{ formatScoreValue(row.selfScore) }} - - - - - 评分明细 - 完成评分 - - 删除 + + + {{ formatScoreValue(row.reviewScore) }} + + + + + {{ row.creditLevel }} + + + + + 最大资金使用额度(万元) + + + {{ formatScoreValue(row.maxCreditLimit) }} + + + + + 拟申请总资金使用额度(万元) + + + {{ formatScoreValue(row.applyCreditLimit) }} + + + + + {{ row.selfStatus }} + + + + + {{ row.reviewStatus }} + + + + + + 详情 + - + + + 新增联系人 + + + + + + + + + + {{ formatContactAddress(row) || '-' }} + + + + + + {{ row.branchName || archiveForm.deptName || '-' }} + + + + + + 修改 + + 删除 + + + + handleDetailSizeChange('contact', size)" + @current-change="page => handleDetailCurrentChange('contact', page)" /> - + + + 新增收款信息 + + + + + + + + + + + + + 修改 + + 删除 + + + + handleDetailSizeChange('receipt', size)" + @current-change="page => handleDetailCurrentChange('receipt', page)" /> - + + + 新增发票信息 + + + + + + + + + + + + {{ formatInvoiceRegisteredAddress(row) || '-' }} + + + + + + + 修改 + + 删除 + + + + handleDetailSizeChange('invoice', size)" + @current-change="page => handleDetailCurrentChange('invoice', page)" /> - - - - - - - - + + + 客商材料 + + 批量下载 + 普通上传 + + OCR上传识别 + + + + + + + + + + + + + + + + + + + handleQualificationUploadSuccess(file, row)" + /> + + + + + + + + + + {{ row.size || '-' }} + + + + + {{ row.uploadUserName || '-' }} + + + + + {{ row.uploadTime || '-' }} + + + + + + 删除 + + + + + + + + 变更记录 + + + + + + + + {{ readonly ? '关闭' : '取消' }} 保存 @@ -530,54 +629,472 @@ + + + + + + + + + + + + + + + + + + + + + + 保存 + 返回 + + + + + + + + 搜索 + + + + + {{ contactMapStatus }} + + 行政区划:{{ contactMapSelected.regionName }} + + + + 取消 + + 确定 + + + + + + + + + + + + + + + + + + + + + + + + + + 保存 + 返回 + + + + + + 发票信息 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 邮寄信息 + + + + + + + + + + + + + + + + + + + + + + + + 保存 + 返回 + + + + - - - - - - - - - + + + + - - - - + + + handleScoreDecimalInput('applyCreditLimit', value)" + /> + + + + + + + + 至 + + + + + + + + + + + + + handleScoreDecimalInput('tempApplyCreditLimit', value)" + /> + + + + + + + + + + + + + + + + + + + - + + + + + + 请选择: + + + + + + + + + + {{ formatScoreValue(detail.selfScore) }}分 + + + + + handleScoreDetailScoreInput(detail, 'reviewScore', value)" + > + 分 + + + + - + + - + {{ row.categoryName }} + 自评:{{ row.selfScore }}分 + 复评:{{ row.reviewScore }}分 + + + + + + + - + + + + 证明材料 + + + + + + + + + {{ formatAttachmentSize(row.size) || '-' }} + + + + + {{ row.uploadUserName || '-' }} + + + + + {{ row.uploadTime || '-' }} + + + + + + 删除 + + + + + + - 关闭 - 保存明细 + 保存 + 复评确认 + 关闭 @@ -595,12 +1112,18 @@ import { remove, getScoreTemplate, } from '@/api/vehicle/customer-archive'; -import { getList as getCustomerTypeList } from '@/api/base/customer-type'; +import { + getList as getCreditScoreQuantificationList, + getDetail as getCreditScoreQuantificationDetail, +} from '@/api/vehicle/credit-score-quantification'; +import { getDictionary } from '@/api/system/dictbiz'; import { getDeptTree } from '@/api/system/dept'; +import { getLazyTree } from '@/api/base/region'; import { exportBlob } from '@/api/common'; import { downloadXls } from '@/utils/util'; import { getToken } from '@/utils/auth'; import { normalizeSearchRangeParams } from '@/utils/search-range'; +import { ArrowRight } from '@element-plus/icons-vue'; import { mapGetters } from 'vuex'; import NProgress from 'nprogress'; import 'nprogress/nprogress.css'; @@ -609,57 +1132,13 @@ const createTimeRangeMap = { createTimeRange: ['createTimeStart', 'createTimeEnd'], }; -const EditableTable = { - props: { - data: { - type: Array, - default: () => [], - }, - columns: { - type: Array, - default: () => [], - }, - readonly: { - type: Boolean, - default: false, - }, - }, - emits: ['add'], - template: ` - - - - 添加 - - - - - - - - - - - - - - - 删除 - - - - - `, -}; +const AMAP_KEY = '653b7cf105ad7fb8ec9b2f5198ade315'; +const AMAP_SECURITY_CODE = '5aab65c632e48ebae0d0e28f5b28e21a'; +let amapLoader; export default { components: { - EditableTable, + ArrowRight, }, data() { const nonNegativeLabelMap = { @@ -677,6 +1156,15 @@ export default { callback(); } }; + const validateInvoiceTaxRate = (rule, value, callback) => { + if (value === undefined || value === null || value === '') { + callback(); + } else if (Number(value) < 0 || Number(value) > 13) { + callback(new Error('开票税点必须在0到13之间')); + } else { + callback(); + } + }; const validateBusinessEndDate = (rule, value, callback) => { if (this.archiveForm.businessTermType === '固定期限' && !value) { callback(new Error('请选择营业期限截止日')); @@ -684,6 +1172,19 @@ export default { callback(); } }; + const validateInvoiceRegisteredAddress = (rule, value, callback) => { + const regionName = (this.invoiceForm.registeredRegionName || '').trim(); + const detailAddress = (this.invoiceForm.registeredDetailAddress || '').trim(); + if (!regionName) { + callback(new Error('请选择注册地址')); + } else if (!detailAddress) { + callback(new Error('请输入详细地址')); + } else if (detailAddress.length > 255) { + callback(new Error('详细地址最多255个字符')); + } else { + callback(); + } + }; return { form: {}, query: {}, @@ -698,17 +1199,65 @@ export default { selectionList: [], archiveBox: false, scoreBox: false, + contactBox: false, + receiptBox: false, + invoiceBox: false, readonly: false, activeTab: 'scores', archiveForm: this.emptyArchive(), currentScore: { details: [] }, + scoreRecordIndex: -1, + scoreAttachmentFiles: [], + expandedScoreCategoryKeys: [], + scoreQuantificationLoading: false, + scoreQuantificationOptions: [], + scorePage: { + currentPage: 1, + pageSize: 3, + pageSizes: [3, 10, 20, 50], + }, + contactPage: { + currentPage: 1, + pageSize: 10, + pageSizes: [10, 20, 50, 100], + }, + receiptPage: { + currentPage: 1, + pageSize: 10, + pageSizes: [10, 20, 50, 100], + }, + invoicePage: { + currentPage: 1, + pageSize: 10, + pageSizes: [10, 20, 50, 100], + }, + contactIndex: -1, + contactForm: this.emptyContact(), + contactMapBox: false, + contactMapLoading: false, + contactMapKeyword: '', + contactMapStatus: '可搜索地址或点击地图选点', + contactMapSelected: {}, + contactMapTarget: 'contact', + contactAmap: null, + contactAmapMarker: null, + contactAmapGeocoder: null, + receiptIndex: -1, + receiptForm: this.emptyReceiptAccount(), + invoiceIndex: -1, + invoiceForm: this.emptyInvoice(), qualificationFiles: [], deptTree: [], deptOptions: [], - customerTypeOptions: [ - { label: '客户', value: '客户' }, - { label: '承运商', value: '承运商' }, - ], + regionOptions: [], + customerNatureOptions: [], + customerTypeOptions: [], + businessScopeOptions: [], + bankListOptions: [], + creditLevelOptions: ['A', 'B', 'C', 'D', 'E', 'F'].map(item => ({ + label: `${item}级`, + value: `${item}级`, + })), accessTypeMap: { temporary: '临时客商', formal: '正式客商', @@ -725,38 +1274,8 @@ export default { approved: { label: '审核通过', type: 'success' }, rejected: { label: '审核不通过', type: 'danger' }, }, - contactColumns: [ - { label: '联系人姓名', prop: 'contactName', minWidth: 150, maxlength: 30 }, - { label: '联系电话', prop: 'contactPhone', minWidth: 150, maxlength: 20 }, - { label: '邮箱', prop: 'email', minWidth: 200, maxlength: 100 }, - { label: '职务', prop: 'positionName', minWidth: 140, maxlength: 50 }, - { label: '是否默认', prop: 'isDefault', type: 'select', minWidth: 110 }, - { label: '备注', prop: 'remark', minWidth: 220, maxlength: 200 }, - ], - receiptColumns: [ - { label: '收款方名称', prop: 'accountName', minWidth: 180, maxlength: 100 }, - { label: '开户行名称', prop: 'bankName', minWidth: 180, maxlength: 100 }, - { label: '银行账号', prop: 'bankAccount', minWidth: 180, maxlength: 50 }, - { label: '注册电话', prop: 'registeredPhone', minWidth: 150, maxlength: 20 }, - { label: '注册地址', prop: 'registeredAddress', minWidth: 220, maxlength: 200 }, - { label: '纳税人识别号', prop: 'taxNo', minWidth: 180, maxlength: 30 }, - { label: '是否默认', prop: 'isDefault', type: 'select', minWidth: 110 }, - ], - invoiceColumns: [ - { label: '受票方名称', prop: 'invoiceTitle', minWidth: 180, maxlength: 100 }, - { label: '纳税人识别号', prop: 'taxNo', minWidth: 180, maxlength: 30 }, - { label: '开户行名称', prop: 'bankName', minWidth: 180, maxlength: 100 }, - { label: '注册电话', prop: 'registeredPhone', minWidth: 150, maxlength: 20 }, - { label: '银行账号', prop: 'bankAccount', minWidth: 180, maxlength: 50 }, - { label: '注册地址', prop: 'registeredAddress', minWidth: 220, maxlength: 200 }, - { label: '邮箱', prop: 'email', minWidth: 200, maxlength: 100 }, - { label: '收件人姓名', prop: 'receiverName', minWidth: 150, maxlength: 30 }, - { label: '收件人电话', prop: 'receiverPhone', minWidth: 150, maxlength: 20 }, - { label: '收件人地址', prop: 'receiverAddress', minWidth: 220, maxlength: 200 }, - { label: '是否默认', prop: 'isDefault', type: 'select', minWidth: 110 }, - ], formRules: { - fullName: [{ required: true, message: '请输入客商全称', trigger: 'blur' }], + fullName: [{ required: true, message: '请输入客商名称', trigger: 'blur' }], customerNature: [{ required: true, message: '请选择客商性质', trigger: 'change' }], unifiedCreditCode: [ { required: true, message: '请输入统一社会信用代码', trigger: 'blur' }, @@ -766,24 +1285,53 @@ export default { trigger: 'blur', }, ], - customerType: [ - { type: 'array', required: true, message: '请选择客商类型', trigger: 'change' }, - ], - legalPerson: [{ required: true, message: '请输入法人/负责人', trigger: 'blur' }], + customerType: [{ required: true, message: '请选择客商类型', trigger: 'change' }], + legalPerson: [{ required: true, message: '请输入企业法人', trigger: 'blur' }], contactPhone: [ { required: true, message: '请输入联系电话', trigger: 'blur' }, { pattern: /^1\d{10}$/, message: '联系电话必须为11位手机号', trigger: 'blur' }, ], deptName: [{ required: true, message: '请选择所属组织', trigger: 'change' }], - invoiceTaxRate: [{ validator: validateNonNegative, trigger: 'blur' }], + invoiceTaxRate: [{ validator: validateInvoiceTaxRate, trigger: 'blur' }], registeredCapital: [{ validator: validateNonNegative, trigger: 'blur' }], maxCreditLimit: [{ validator: validateNonNegative, trigger: 'blur' }], applyCreditLimit: [{ validator: validateNonNegative, trigger: 'blur' }], businessEndDate: [{ validator: validateBusinessEndDate, trigger: 'change' }], - registeredAddress: [{ max: 200, message: '地址最多200个字符', trigger: 'blur' }], - businessScope: [{ max: 500, message: '经营范围最多500个字符', trigger: 'blur' }], + registeredDetailAddress: [{ max: 255, message: '详细地址最多255个字符', trigger: 'blur' }], + businessScope: [{ type: 'array', message: '请选择经营范围', trigger: 'change' }], remark: [{ max: 500, message: '备注最多500个字符', trigger: 'blur' }], }, + contactRules: { + contactName: [{ required: true, message: '请输入联系人姓名', trigger: 'blur' }], + contactPhone: [ + { required: true, message: '请输入手机号', trigger: 'blur' }, + { pattern: /^1\d{10}$/, message: '手机号必须为11位手机号', trigger: 'blur' }, + ], + email: [{ type: 'email', message: '请输入正确的邮箱', trigger: 'blur' }], + detailAddress: [{ max: 255, message: '详细地址最多255个字符', trigger: 'blur' }], + remark: [{ max: 200, message: '备注最多200个字符', trigger: 'blur' }], + }, + receiptRules: { + accountName: [{ required: true, message: '请输入收款单位名称', trigger: 'blur' }], + accountHolderName: [{ required: true, message: '请输入开户人姓名', trigger: 'blur' }], + bankName: [{ required: true, message: '请输入开户行', trigger: 'blur' }], + bankAccount: [{ required: true, message: '请输入收款账号', trigger: 'blur' }], + remark: [{ max: 200, message: '备注最多200个字符', trigger: 'blur' }], + }, + invoiceRules: { + taxNo: [{ required: true, message: '请输入纳税人识别号', trigger: 'blur' }], + invoiceTitle: [{ required: true, message: '请输入收票方名称', trigger: 'blur' }], + registeredPhone: [{ required: true, message: '请输入注册电话', trigger: 'blur' }], + bankName: [{ required: true, message: '请输入开户行名称', trigger: 'blur' }], + registeredRegionName: [ + { required: true, validator: validateInvoiceRegisteredAddress, trigger: 'blur' }, + ], + bankAccount: [{ required: true, message: '请输入银行账号', trigger: 'blur' }], + receiverDetailAddress: [ + { max: 255, message: '收件人详细地址最多255个字符', trigger: 'blur' }, + ], + email: [{ type: 'email', message: '请输入正确的邮箱', trigger: 'blur' }], + }, option: { height: 'auto', calcHeight: 32, @@ -798,6 +1346,7 @@ export default { border: true, index: true, indexLabel: '序号', + indexWidth: 90, addBtn: false, viewBtn: false, delBtn: false, @@ -806,29 +1355,73 @@ export default { dialogClickModal: false, menuWidth: 160, column: [ - { label: '客商编号', prop: 'customerCode', slot: true, search: true, minWidth: 140 }, - { label: '客商简称', prop: 'shortName', search: true, minWidth: 150 }, - { label: '客商名称', prop: 'fullName', search: true, minWidth: 220, overHidden: true }, - { label: '客商类型', prop: 'customerType', search: true, minWidth: 140 }, { - label: '客商性质', + label: '客商编号', + prop: 'customerCode', + slot: true, + search: true, + searchOrder: 10, + minWidth: 140, + }, + { + label: '客商简称', + prop: 'shortName', + search: true, + searchOrder: 2, + minWidth: 150, + }, + { + label: '客商名称', + prop: 'fullName', + search: true, + searchOrder: 9, + minWidth: 220, + overHidden: true, + }, + { + label: '客户类型', + prop: 'customerType', + search: true, + searchType: 'select', + searchOrder: 7, + minWidth: 140, + dicData: this.customerTypeOptions, + }, + { + label: '客户性质', prop: 'customerNature', type: 'select', search: true, + searchOrder: 5, minWidth: 120, dicData: [ { label: '有限公司', value: '有限公司' }, { label: '个体工商户', value: '个体工商户' }, ], }, - { label: '统一信用代码', prop: 'unifiedCreditCode', search: true, minWidth: 190 }, - { label: '所属组织', prop: 'deptName', search: true, minWidth: 180 }, { - label: '准入类型', + label: '统一信用代码', + prop: 'unifiedCreditCode', + search: true, + searchOrder: 3, + minWidth: 190, + }, + { + label: '所属组织', + prop: 'deptName', + search: true, + searchType: 'select', + searchOrder: 1, + minWidth: 180, + dicData: [], + }, + { + label: '准入标准', prop: 'accessType', slot: true, type: 'select', search: true, + searchOrder: 8, minWidth: 120, dicData: [ { label: '临时客商', value: 'temporary' }, @@ -841,6 +1434,7 @@ export default { slot: true, type: 'select', search: true, + searchOrder: 4, minWidth: 130, dicData: [ { label: '草稿', value: 'draft' }, @@ -865,6 +1459,7 @@ export default { slot: true, type: 'select', search: true, + searchOrder: 6, dataType: 'number', minWidth: 100, dicData: [ @@ -873,24 +1468,15 @@ export default { ], clearable: true, }, - { - label: '创建时间', - prop: 'createTimeRange', - type: 'date', - format: 'YYYY-MM-DD', - valueFormat: 'YYYY-MM-DD', - searchRange: true, - search: true, - hide: true, - display: false, - }, ], }, }; }, created() { this.initDeptTree(); - this.initCustomerTypes(); + this.initRegionOptions(); + this.initBusinessDictionaries(); + this.initScoreQuantificationOptions(); }, computed: { ...mapGetters(['permission', 'userInfo']), @@ -903,6 +1489,14 @@ export default { addBtn: false, }; }, + regionCascaderProps() { + return { + value: 'id', + label: 'title', + children: 'children', + emitPath: true, + }; + }, ids() { let ids = []; this.selectionList.forEach(ele => { @@ -914,22 +1508,97 @@ export default { if (this.readonly) return '查看客商档案'; return this.archiveForm.id ? '编辑客商档案' : '新增客商档案'; }, + contactDialogTitle() { + return this.contactIndex > -1 ? '编辑联系人' : '新增联系人'; + }, + receiptDialogTitle() { + return this.receiptIndex > -1 ? '编辑收款信息' : '新增收款信息'; + }, + invoiceDialogTitle() { + return this.invoiceIndex > -1 ? '编辑发票信息' : '新增发票信息'; + }, missingQualificationText() { if (this.archiveForm.accessType !== 'formal') return ''; const types = this.qualificationFiles .filter(item => item.url) .map(item => item.type || item.name) .filter(Boolean); - const missing = ['公司营业执照', '道路运输证'].filter( - item => !types.some(type => String(type).includes(item)) - ); - return missing.length ? `缺失核心资质材料:${missing.join('、')}` : ''; + const requiredMaterials = [ + { label: '法人身份证', aliases: ['法人身份证'] }, + { label: '道路运输许可证', aliases: ['道路运输许可证', '道路运输证'] }, + ]; + const missing = requiredMaterials + .filter( + item => !types.some(type => item.aliases.some(alias => String(type).includes(alias))) + ) + .map(item => item.label); + return missing.length ? `缺失核心客商材料:${missing.join('、')}` : ''; }, uploadHeaders() { return { [this.website.tokenHeader]: getToken(), }; }, + scoreList() { + return this.archiveForm.scores || []; + }, + scorePageCount() { + return Math.max(Math.ceil(this.scoreList.length / this.scorePage.pageSize), 1); + }, + scorePaginationPage() { + return { + ...this.scorePage, + total: this.scoreList.length, + }; + }, + scoreTableData() { + const start = (this.scorePage.currentPage - 1) * this.scorePage.pageSize; + return this.scoreList + .slice(start, start + this.scorePage.pageSize) + .map((item, index) => ({ + ...item, + __raw: item, + __rawIndex: start + index, + __index: start + index + 1, + maxCreditLimit: item.maxCreditLimit || '', + })); + }, + contactPaginationPage() { + return this.buildDetailPaginationPage('contact'); + }, + contactTableData() { + return this.buildDetailTableData('contact'); + }, + receiptPaginationPage() { + return this.buildDetailPaginationPage('receipt'); + }, + receiptTableData() { + return this.buildDetailTableData('receipt'); + }, + invoicePaginationPage() { + return this.buildDetailPaginationPage('invoice'); + }, + invoiceTableData() { + return this.buildDetailTableData('invoice'); + }, + scoreCategoryRows() { + const categoryMap = [ + { categoryCode: 'basic', categoryName: '基础得分项' }, + { categoryCode: 'plus', categoryName: '加分项目' }, + { categoryCode: 'minus', categoryName: '减分项目' }, + ]; + return categoryMap.map((category, index) => { + const details = this.getScoreCategoryDetails(category.categoryCode); + const selfScore = details.reduce((sum, item) => sum + Number(item.selfScore || 0), 0); + const reviewScore = details.reduce((sum, item) => sum + Number(item.reviewScore || 0), 0); + return { + ...category, + index: index + 1, + selfScore: Number(selfScore.toFixed(2)), + reviewScore: Number(reviewScore.toFixed(2)), + }; + }); + }, }, methods: { hasPermission(code) { @@ -940,7 +1609,16 @@ export default { accessType: 'temporary', approvalStatus: 'draft', status: 1, - customerType: [], + deptId: [], + deptIds: '', + deptName: '', + customerType: '', + businessScope: [], + businessTermType: '固定期限', + registeredRegionPath: [], + registeredRegionName: '', + registeredDetailAddress: '', + registeredAddress: '', contacts: [], receiptAccounts: [], invoices: [], @@ -948,11 +1626,72 @@ export default { changeRecords: [], }; }, + getDetailList(type) { + const listMap = { + contact: this.archiveForm.contacts, + receipt: this.archiveForm.receiptAccounts, + invoice: this.archiveForm.invoices, + }; + return listMap[type] || []; + }, + getDetailPage(type) { + const pageMap = { + contact: this.contactPage, + receipt: this.receiptPage, + invoice: this.invoicePage, + }; + return pageMap[type]; + }, + getDetailPageCount(type) { + const page = this.getDetailPage(type); + return Math.max(Math.ceil(this.getDetailList(type).length / page.pageSize), 1); + }, + buildDetailPaginationPage(type) { + return { + ...this.getDetailPage(type), + total: this.getDetailList(type).length, + }; + }, + buildDetailTableData(type) { + const page = this.getDetailPage(type); + const start = (page.currentPage - 1) * page.pageSize; + return this.getDetailList(type) + .slice(start, start + page.pageSize) + .map((item, index) => ({ + ...item, + __raw: item, + __rawIndex: start + index, + __index: start + index + 1, + })); + }, + handleDetailCurrentChange(type, currentPage) { + const page = this.getDetailPage(type); + const nextPage = Number(currentPage) || 1; + page.currentPage = Math.min(Math.max(nextPage, 1), this.getDetailPageCount(type)); + }, + handleDetailSizeChange(type, pageSize) { + const page = this.getDetailPage(type); + page.pageSize = Number(pageSize) || 10; + this.handleDetailCurrentChange(type, 1); + }, + resetDetailPagination() { + this.handleScoreCurrentChange(1); + this.handleDetailCurrentChange('contact', 1); + this.handleDetailCurrentChange('receipt', 1); + this.handleDetailCurrentChange('invoice', 1); + }, emptyContact() { return { contactName: '', contactPhone: '', email: '', + regionName: '', + regionCode: '', + detailAddress: '', + longitude: '', + latitude: '', + contactAddress: '', + branchName: '', positionName: '', isDefault: 0, remark: '', @@ -961,12 +1700,14 @@ export default { emptyReceiptAccount() { return { accountName: '', + accountHolderName: '', bankName: '', bankAccount: '', registeredPhone: '', registeredAddress: '', taxNo: '', isDefault: 0, + remark: '', }; }, emptyInvoice() { @@ -976,43 +1717,184 @@ export default { bankName: '', registeredPhone: '', bankAccount: '', + registeredRegionName: '', + registeredDetailAddress: '', registeredAddress: '', email: '', receiverName: '', receiverPhone: '', + receiverRegionName: '', + receiverDetailAddress: '', receiverAddress: '', isDefault: 0, }; }, initDeptTree() { getDeptTree(this.userInfo.tenantId).then(res => { - this.deptTree = res.data.data || []; + this.deptTree = this.normalizeDeptTree(res.data.data || []); this.deptOptions = this.flattenDept(this.deptTree); + const deptColumn = this.findColumn(this.option.column, 'deptName'); + deptColumn.dicData = this.deptOptions.map(item => ({ + label: item.label, + value: item.rawLabel, + })); }); }, - initCustomerTypes() { - getCustomerTypeList(1, 200, { status: 1 }).then(res => { - const records = res.data.data.records || []; - if (records.length) { - this.customerTypeOptions = records.map(item => ({ - label: item.name, - value: item.name, - })); - ['客户', '承运商'].forEach(type => { - if (!this.customerTypeOptions.some(item => item.value === type)) { - this.customerTypeOptions.push({ label: type, value: type }); - } + initRegionOptions() { + getLazyTree().then(res => { + const regions = this.buildRegionTree(res.data.data || []); + this.regionOptions = this.extractChinaRegionOptions(regions); + }); + }, + buildRegionTree(regions = []) { + const flatRegions = []; + const collectRegions = list => { + (list || []).forEach(item => { + flatRegions.push({ + ...item, + children: undefined, }); + if (item.children?.length) collectRegions(item.children); + }); + }; + collectRegions(regions); + + const nodeMap = new Map(); + flatRegions.forEach(item => { + const id = item.id === undefined || item.id === null ? item.value : item.id; + if (id === undefined || id === null) return; + nodeMap.set(String(id), { + ...item, + id: String(id), + parentId: + item.parentId === undefined || item.parentId === null ? '' : String(item.parentId), + children: [], + }); + }); + + const rootNodes = []; + nodeMap.forEach(node => { + const parent = nodeMap.get(node.parentId); + if (parent && parent !== node) { + parent.children.push(node); + } else { + rootNodes.push(node); } }); + return rootNodes.map(item => this.normalizeRegionTreeNode(item)); + }, + normalizeRegionTreeNode(region) { + const children = (region.children || []).map(item => this.normalizeRegionTreeNode(item)); + return { + ...region, + children: children.length ? children : undefined, + leaf: !children.length, + }; + }, + isChinaRegion(region = {}) { + const title = String(region.title || region.name || '').trim(); + return title === '中国' || title === '中华人民共和国'; + }, + extractChinaRegionOptions(regions) { + const china = (regions || []).find(item => this.isChinaRegion(item)); + if (china?.children?.length) return china.children; + if (regions.length === 1 && regions[0].children?.length) return regions[0].children; + return regions; + }, + findRegionOption(options, value) { + for (const item of options || []) { + if (String(item.id) === String(value)) return item; + const child = this.findRegionOption(item.children, value); + if (child) return child; + } + return null; + }, + getRegisteredRegionLabels(value) { + const nodes = this.$refs.registeredRegionCascader?.getCheckedNodes?.() || []; + if (nodes.length && nodes[0].pathLabels?.length) { + return nodes[0].pathLabels; + } + return (value || []) + .map(code => this.findRegionOption(this.regionOptions, code)?.title) + .filter(Boolean); + }, + handleRegisteredRegionChange(value) { + if (!value || !value.length) { + this.archiveForm.registeredRegionName = ''; + return; + } + this.$nextTick(() => { + this.archiveForm.registeredRegionName = this.getRegisteredRegionLabels(value).join(''); + }); + }, + initBusinessDictionaries() { + this.loadDictOptions( + 'nature_of_client', + 'customerNatureOptions', + ['有限公司', '个体工商户'], + options => { + const customerNatureColumn = this.findColumn(this.option.column, 'customerNature'); + customerNatureColumn.dicData = options; + } + ); + this.loadDictOptions('customer_type', 'customerTypeOptions', ['客户', '承运商'], options => { + const customerTypeColumn = this.findColumn(this.option.column, 'customerType'); + customerTypeColumn.dicData = options; + }); + this.loadDictOptions('scope_of_business', 'businessScopeOptions'); + this.loadDictOptions('bank_list', 'bankListOptions'); + }, + initScoreQuantificationOptions() { + this.scoreQuantificationLoading = true; + getCreditScoreQuantificationList(1, 9999, { status: 1 }) + .then(res => { + const data = res.data.data || {}; + const records = Array.isArray(data) ? data : data.records || []; + this.scoreQuantificationOptions = records + .filter(item => Number(item.status) === 1) + .map(item => ({ + id: item.id, + name: item.name || item.configName || item.title || item.id, + })); + }) + .finally(() => { + this.scoreQuantificationLoading = false; + }); + }, + loadDictOptions(code, target, fallback = [], callback) { + getDictionary({ code }).then(res => { + const options = (res.data.data || []).map(item => ({ + label: item.dictValue, + value: item.dictValue, + })); + fallback.forEach(value => { + if (!options.some(item => item.value === value)) { + options.push({ label: value, value }); + } + }); + this[target] = options; + if (callback) callback(options); + }); + }, + normalizeDeptTree(tree = []) { + return (tree || []).map(item => { + const label = item.label || item.title || item.deptName || item.name || ''; + const value = String(item.value || item.id || ''); + return { + ...item, + label, + value, + children: this.normalizeDeptTree(item.children || []), + }; + }); }, flattenDept(tree, level = 0) { const result = []; tree.forEach(item => { result.push({ - label: `${' '.repeat(level)}${item.title || item.deptName || item.name}`, - value: item.id, - rawLabel: item.title || item.deptName || item.name, + label: `${' '.repeat(level)}${item.label || item.title || item.deptName || item.name}`, + value: item.value || item.id, + rawLabel: item.label || item.title || item.deptName || item.name, }); if (item.children && item.children.length) { result.push(...this.flattenDept(item.children, level + 1)); @@ -1021,10 +1903,516 @@ export default { return result; }, onDeptChange(value) { - const dept = this.deptOptions.find(item => item.value === value); - this.archiveForm.deptName = dept ? dept.rawLabel : ''; + const deptIds = Array.isArray(value) ? value : value ? [value] : []; + const deptNames = deptIds + .map(id => this.deptOptions.find(item => String(item.value) === String(id))?.rawLabel) + .filter(Boolean); + this.archiveForm.deptIds = deptIds.join(','); + this.archiveForm.deptName = deptNames.join(','); this.$refs.archiveForm?.validateField('deptName'); }, + handleNoFixedTermChange(checked) { + this.archiveForm.businessTermType = checked ? '长期' : '固定期限'; + if (checked) { + this.archiveForm.businessEndDate = ''; + } + this.$refs.archiveForm?.validateField('businessEndDate'); + }, + handleBusinessEndDateChange(value) { + if (value) { + this.archiveForm.businessTermType = '固定期限'; + } + }, + handleDecimalInput(prop, value, max) { + const normalized = String(value || '') + .replace(/[^\d.]/g, '') + .replace(/^\./, '') + .replace(/(\..*)\./g, '$1') + .replace(/^(\d+)(\.\d{0,2})?.*$/, '$1$2'); + if (max !== undefined && normalized !== '' && Number(normalized) > max) { + this.archiveForm[prop] = String(max); + return; + } + this.archiveForm[prop] = normalized; + }, + splitValue(value) { + return value + ? String(value) + .split(',') + .map(item => item.trim()) + .filter(Boolean) + : []; + }, + normalizeDeptIds(detail) { + const value = detail.deptIds || detail.deptId; + return value + ? String(value) + .split(',') + .map(item => item.trim()) + .filter(item => item !== '') + : []; + }, + formatArchiveAddress(archive = {}) { + return [archive.registeredRegionName, archive.registeredDetailAddress] + .map(item => String(item || '').trim()) + .filter(Boolean) + .join(''); + }, + formatContactAddress(row = {}) { + return ( + row.contactAddress || + [row.regionName, row.detailAddress] + .map(item => String(item || '').trim()) + .filter(Boolean) + .join('') + ); + }, + formatInvoiceRegisteredAddress(row = {}) { + return ( + row.registeredAddress || + [row.registeredRegionName, row.registeredDetailAddress] + .map(item => String(item || '').trim()) + .filter(Boolean) + .join('') + ); + }, + formatInvoiceReceiverAddress(row = {}) { + return ( + row.receiverAddress || + [row.receiverRegionName, row.receiverDetailAddress] + .map(item => String(item || '').trim()) + .filter(Boolean) + .join('') + ); + }, + normalizeContact(contact = {}) { + const contactAddress = + contact.contactAddress || contact.address || this.formatContactAddress(contact); + return { + ...this.emptyContact(), + ...contact, + contactAddress, + branchName: contact.branchName || contact.deptName || this.archiveForm.deptName || '', + }; + }, + openContact(row, index = -1) { + this.contactIndex = index; + this.contactForm = row + ? this.normalizeContact(row) + : { + ...this.emptyContact(), + branchName: this.archiveForm.deptName || '', + }; + this.contactBox = true; + }, + resetContact() { + this.contactIndex = -1; + this.contactForm = this.emptyContact(); + this.$refs.contactForm?.clearValidate(); + }, + saveContact() { + this.$refs.contactForm.validate(valid => { + if (!valid) return; + const contact = this.normalizeContact({ + ...this.contactForm, + contactAddress: this.formatContactAddress(this.contactForm), + }); + if (this.contactIndex > -1) { + this.archiveForm.contacts.splice(this.contactIndex, 1, contact); + } else { + this.archiveForm.contacts.push(contact); + this.handleDetailCurrentChange('contact', this.getDetailPageCount('contact')); + } + this.contactBox = false; + }); + }, + deleteContact(index) { + this.archiveForm.contacts.splice(index, 1); + this.handleDetailCurrentChange('contact', this.contactPage.currentPage); + }, + handleContactMapPick() { + this.contactMapTarget = 'contact'; + this.contactMapKeyword = this.contactForm.detailAddress || this.contactForm.regionName || ''; + this.contactMapSelected = this.buildContactMapSelection({ + lng: this.contactForm.longitude, + lat: this.contactForm.latitude, + address: this.contactForm.detailAddress, + regionName: this.contactForm.regionName, + regionCode: this.contactForm.regionCode, + }); + this.contactMapStatus = this.contactMapSelected.longitude + ? '已加载当前选点,可重新选点' + : '可搜索地址或点击地图选点'; + this.contactMapBox = true; + }, + handleInvoiceRegisteredMapPick() { + this.contactMapTarget = 'invoiceRegistered'; + this.contactMapKeyword = + this.invoiceForm.registeredDetailAddress || this.invoiceForm.registeredRegionName || ''; + this.contactMapSelected = this.buildContactMapSelection({ + address: this.invoiceForm.registeredDetailAddress, + regionName: this.invoiceForm.registeredRegionName, + }); + this.contactMapStatus = '可搜索地址或点击地图选点'; + this.contactMapBox = true; + }, + loadAmap() { + if (window.AMap && window.AMap.Map) { + return Promise.resolve(); + } + if (!amapLoader) { + amapLoader = new Promise((resolve, reject) => { + window._AMapSecurityConfig = { + securityJsCode: AMAP_SECURITY_CODE, + }; + const script = document.createElement('script'); + script.src = `https://webapi.amap.com/maps?v=2.0&key=${AMAP_KEY}`; + script.async = true; + script.onload = resolve; + script.onerror = () => reject(new Error('高德地图组件加载失败')); + document.body.appendChild(script); + }); + } + return amapLoader; + }, + initContactAmap() { + this.loadAmap() + .then(() => { + this.$nextTick(() => { + if (!this.contactAmap) { + const center = this.toLngLat( + this.contactMapSelected.longitude || 116.40769, + this.contactMapSelected.latitude || 39.89945 + ); + this.contactAmap = new window.AMap.Map(this.$refs.contactAmap, { + center, + zoom: this.contactMapSelected.longitude ? 14 : 11, + }); + window.AMap.plugin(['AMap.ToolBar', 'AMap.Geocoder'], () => { + this.contactAmap.addControl(new window.AMap.ToolBar()); + if (!this.contactAmapGeocoder) { + this.contactAmapGeocoder = new window.AMap.Geocoder(); + } + }); + this.contactAmap.on('click', event => this.pickContactMapPoint(event.lnglat)); + } else if (typeof this.contactAmap.resize === 'function') { + this.contactAmap.resize(); + } + if (this.contactMapSelected.longitude) { + this.renderContactMapMarker( + this.toLngLat( + this.contactMapSelected.longitude, + this.contactMapSelected.latitude + ) + ); + } else { + this.clearContactMapMarker(); + } + }); + }) + .catch(() => { + this.$message.error('高德地图组件加载失败,请稍后重试或重新打开弹窗'); + this.contactMapBox = false; + }); + }, + searchContactMapKeyword() { + const keyword = String(this.contactMapKeyword || '').trim(); + if (!keyword) { + this.$message.warning('请输入地址关键词'); + return; + } + this.loadAmap() + .then(() => { + this.contactMapLoading = true; + this.ensureContactAmapGeocoder() + .then(() => this.runAmapGeocode('location', keyword)) + .then(result => { + const point = this.resolveMapPoint(result); + if (!point) { + this.contactMapStatus = '未找到匹配地址'; + this.$message.warning('地图搜索无匹配地址'); + return; + } + this.pickContactMapPoint(point, keyword); + }) + .catch(() => { + this.contactMapStatus = '地图搜索失败'; + this.$message.error('地图搜索失败,请稍后重试'); + }) + .finally(() => { + this.contactMapLoading = false; + }); + }) + .catch(() => { + this.$message.error('高德地图组件加载失败,请稍后重试或重新打开弹窗'); + }); + }, + pickContactMapPoint(lnglat, keyword) { + const longitude = this.getPointLng(lnglat); + const latitude = this.getPointLat(lnglat); + if (longitude === undefined || latitude === undefined) { + this.$message.warning('选点坐标无效'); + return; + } + const point = this.toLngLat(longitude, latitude); + this.renderContactMapMarker(point); + this.contactMapSelected = this.buildContactMapSelection({ + lng: longitude, + lat: latitude, + address: keyword, + }); + this.contactMapStatus = '正在反查地址...'; + this.ensureContactAmapGeocoder() + .then(() => this.runAmapGeocode('address', point)) + .then(result => { + const address = this.resolveMapAddress(result); + this.contactMapSelected = { + ...this.contactMapSelected, + detailAddress: address.detailAddress || this.contactMapSelected.detailAddress, + regionName: address.regionName || this.contactMapSelected.regionName, + regionCode: address.regionCode || this.contactMapSelected.regionCode, + }; + this.contactMapKeyword = this.contactMapSelected.detailAddress || this.contactMapKeyword; + this.contactMapStatus = this.contactMapSelected.detailAddress || '已选点,可确认回填'; + }) + .catch(() => { + this.contactMapStatus = '反查地址失败'; + this.$message.error('反查地址失败,请重新选点'); + }); + }, + renderContactMapMarker(point) { + if (!this.contactAmap || !window.AMap) return; + this.clearContactMapMarker(); + this.contactAmapMarker = new window.AMap.Marker({ + position: point, + }); + this.contactAmapMarker.setMap(this.contactAmap); + this.contactAmap.setCenter(point); + }, + clearContactMapMarker() { + if (this.contactAmapMarker) { + this.contactAmapMarker.setMap(null); + this.contactAmapMarker = null; + } + }, + confirmContactMapPick() { + if (!this.contactMapSelected.longitude) { + this.$message.warning('请先搜索或点击地图完成选点'); + return; + } + if (this.contactMapTarget === 'invoiceRegistered') { + this.invoiceForm.registeredRegionName = + this.contactMapSelected.regionName || this.invoiceForm.registeredRegionName; + this.invoiceForm.registeredDetailAddress = this.formatMapDetailAddress( + this.contactMapSelected.detailAddress || this.invoiceForm.registeredDetailAddress, + this.invoiceForm.registeredRegionName + ); + this.$refs.invoiceForm?.validateField('registeredRegionName'); + this.contactMapBox = false; + return; + } + this.contactForm.detailAddress = + this.contactMapSelected.detailAddress || this.contactForm.detailAddress; + this.contactForm.regionName = + this.contactMapSelected.regionName || this.contactForm.regionName; + this.contactForm.regionCode = + this.contactMapSelected.regionCode || this.contactForm.regionCode; + this.contactForm.longitude = this.contactMapSelected.longitude; + this.contactForm.latitude = this.contactMapSelected.latitude; + this.$refs.contactForm?.validateField('detailAddress'); + this.contactMapBox = false; + }, + formatMapDetailAddress(address, regionName) { + const rawAddress = String(address || '').trim(); + const rawRegionName = String(regionName || '').trim(); + if (!rawAddress || !rawRegionName) return rawAddress; + return rawAddress.startsWith(rawRegionName) + ? rawAddress.slice(rawRegionName.length).trim() + : rawAddress; + }, + buildContactMapSelection({ lng, lat, address, regionName, regionCode }) { + const longitude = this.formatCoordinate(lng); + const latitude = this.formatCoordinate(lat); + return { + longitude, + latitude, + detailAddress: address || '', + regionName: regionName || '', + regionCode: regionCode || '', + }; + }, + formatCoordinate(value) { + if (value === undefined || value === null || value === '') { + return ''; + } + const numberValue = Number(value); + return Number.isFinite(numberValue) ? numberValue.toFixed(6) : ''; + }, + toLngLat(lng, lat) { + return new window.AMap.LngLat(Number(lng), Number(lat)); + }, + getPointLng(point) { + if (!point) return undefined; + if (typeof point.getLng === 'function') return point.getLng(); + return point.lng ?? point.lon; + }, + getPointLat(point) { + if (!point) return undefined; + if (typeof point.getLat === 'function') return point.getLat(); + return point.lat; + }, + ensureContactAmapGeocoder() { + if (this.contactAmapGeocoder) { + return Promise.resolve(this.contactAmapGeocoder); + } + return new Promise((resolve, reject) => { + if (!window.AMap || !window.AMap.plugin) { + reject(new Error('高德地图组件未就绪')); + return; + } + window.AMap.plugin(['AMap.Geocoder'], () => { + try { + this.contactAmapGeocoder = new window.AMap.Geocoder(); + resolve(this.contactAmapGeocoder); + } catch (error) { + reject(error); + } + }); + }); + }, + runAmapGeocode(action, input) { + return new Promise((resolve, reject) => { + const timer = window.setTimeout(() => { + reject(new Error('高德地图请求超时')); + }, 10000); + const done = (status, result) => { + window.clearTimeout(timer); + if (status === 'complete' && result) { + resolve(result); + return; + } + reject(new Error('高德地图请求失败')); + }; + try { + if (action === 'location') { + this.contactAmapGeocoder.getLocation(input, done); + } else { + this.contactAmapGeocoder.getAddress(input, done); + } + } catch (error) { + window.clearTimeout(timer); + reject(error); + } + }); + }, + resolveMapPoint(result) { + if (!result) return null; + const geocode = Array.isArray(result.geocodes) ? result.geocodes[0] : null; + if (geocode && geocode.location) return geocode.location; + if (result.location) return result.location; + if (result.lnglat) return result.lnglat; + if (result.getLng || result.lng || result.lon) return result; + return null; + }, + resolveMapAddress(result = {}) { + const regeocode = result.regeocode || {}; + const component = regeocode.addressComponent || {}; + const city = Array.isArray(component.city) ? '' : component.city; + const regionName = [component.province, city, component.district].filter(Boolean).join(''); + return { + detailAddress: regeocode.formattedAddress || '', + regionName, + regionCode: component.adcode || '', + }; + }, + normalizeReceipt(receipt = {}) { + return { + ...this.emptyReceiptAccount(), + ...receipt, + }; + }, + openReceipt(row, index = -1) { + this.receiptIndex = index; + this.receiptForm = row ? this.normalizeReceipt(row) : this.emptyReceiptAccount(); + this.receiptBox = true; + }, + resetReceipt() { + this.receiptIndex = -1; + this.receiptForm = this.emptyReceiptAccount(); + this.$refs.receiptForm?.clearValidate(); + }, + saveReceipt() { + this.$refs.receiptForm.validate(valid => { + if (!valid) return; + const receipt = this.normalizeReceipt(this.receiptForm); + if (this.receiptIndex > -1) { + this.archiveForm.receiptAccounts.splice(this.receiptIndex, 1, receipt); + } else { + this.archiveForm.receiptAccounts.push(receipt); + this.handleDetailCurrentChange('receipt', this.getDetailPageCount('receipt')); + } + this.receiptBox = false; + }); + }, + deleteReceipt(index) { + this.archiveForm.receiptAccounts.splice(index, 1); + this.handleDetailCurrentChange('receipt', this.receiptPage.currentPage); + }, + normalizeInvoice(invoice = {}) { + const registeredDetailAddress = + invoice.registeredDetailAddress || + (invoice.registeredRegionName ? '' : invoice.registeredAddress) || + ''; + const receiverDetailAddress = + invoice.receiverDetailAddress || + (invoice.receiverRegionName ? '' : invoice.receiverAddress) || + ''; + return { + ...this.emptyInvoice(), + ...invoice, + registeredDetailAddress, + registeredAddress: this.formatInvoiceRegisteredAddress({ + ...invoice, + registeredDetailAddress, + }), + receiverDetailAddress, + receiverAddress: this.formatInvoiceReceiverAddress({ + ...invoice, + receiverDetailAddress, + }), + }; + }, + openInvoice(row, index = -1) { + this.invoiceIndex = index; + this.invoiceForm = row ? this.normalizeInvoice(row) : this.emptyInvoice(); + this.invoiceBox = true; + }, + resetInvoice() { + this.invoiceIndex = -1; + this.invoiceForm = this.emptyInvoice(); + this.$refs.invoiceForm?.clearValidate(); + }, + saveInvoice() { + this.$refs.invoiceForm.validate(valid => { + if (!valid) return; + const invoice = this.normalizeInvoice({ + ...this.invoiceForm, + registeredAddress: this.formatInvoiceRegisteredAddress(this.invoiceForm), + receiverAddress: this.formatInvoiceReceiverAddress(this.invoiceForm), + }); + if (this.invoiceIndex > -1) { + this.archiveForm.invoices.splice(this.invoiceIndex, 1, invoice); + } else { + this.archiveForm.invoices.push(invoice); + this.handleDetailCurrentChange('invoice', this.getDetailPageCount('invoice')); + } + this.invoiceBox = false; + }); + }, + deleteInvoice(index) { + this.archiveForm.invoices.splice(index, 1); + this.handleDetailCurrentChange('invoice', this.invoicePage.currentPage); + }, parseAttachments(value) { if (!value) return []; if (Array.isArray(value)) return value; @@ -1083,7 +2471,7 @@ export default { return `${(byteSize / 1024).toFixed(2)} KB`; }, handleQualificationUploadSuccess(file, row) { - row.name = file.name || row.name || '资质附件'; + row.name = file.name || row.name || '客商材料'; row.size = this.formatAttachmentSize(file.size || row.size); row.uploadUserName = this.userInfo.realName || this.userInfo.userName || ''; row.uploadTime = this.$dayjs().format('YYYY-MM-DD HH:mm:ss'); @@ -1099,36 +2487,93 @@ export default { files.forEach(item => window.open(item.url, '_blank')); }, normalizeDetail(detail) { - const customerType = detail.customerType - ? String(detail.customerType) - .split(',') - .map(item => item.trim()) - .filter(Boolean) - : []; - return { + const businessScope = this.splitValue(detail.businessScope); + const registeredDetailAddress = + detail.registeredDetailAddress || + (detail.registeredRegionName ? '' : detail.registeredAddress) || + ''; + const archive = { ...this.emptyArchive(), ...detail, - customerType, - contacts: detail.contacts || [], - receiptAccounts: detail.receiptAccounts || [], - invoices: detail.invoices || [], - scores: (detail.scores || []).map(this.normalizeScore), + deptId: this.normalizeDeptIds(detail), + customerType: detail.customerType || '', + businessScope, + businessTermType: detail.businessTermType || '固定期限', + registeredRegionPath: Array.isArray(detail.registeredRegionPath) + ? detail.registeredRegionPath + : [], + registeredRegionName: detail.registeredRegionName || '', + registeredDetailAddress, + contacts: (detail.contacts || []).map(item => + this.normalizeContact({ + ...item, + branchName: item.branchName || item.deptName || detail.deptName || '', + }) + ), + receiptAccounts: (detail.receiptAccounts || []).map(item => this.normalizeReceipt(item)), + invoices: (detail.invoices || []).map(item => this.normalizeInvoice(item)), + scores: (detail.scores || []).map(score => this.calculateScore(this.normalizeScore(score))), changeRecords: detail.changeRecords || [], }; + return this.applyArchiveCreditFromScores(archive); }, normalizeScore(score) { return { + quantificationId: '', scoreDate: this.$dayjs().format('YYYY-MM-DD'), + applyCreditLimit: this.archiveForm.applyCreditLimit || '', + maxCreditLimit: '', + creditLevel: '', + standards: [], + tempApplyCreditLimit: '', + tempCreditStartDate: '', + tempCreditEndDate: '', + remark: '', selfStatus: '未完成', reviewStatus: '未完成', + proofAttachments: '', + attachments: [], details: [], ...score, + attachments: score.attachments || this.parseAttachments(score.proofAttachments), details: score.details || [], }; }, + getScoreTimeValue(value) { + if (!value) return 0; + const time = this.$dayjs(value).valueOf(); + return Number.isFinite(time) ? time : 0; + }, + getLatestCreditScore(scores = []) { + return (scores || []) + .map((score, index) => ({ score, index })) + .filter(item => String(item.score.creditLevel || '').trim()) + .sort((a, b) => { + const scoreDateDiff = + this.getScoreTimeValue(b.score.scoreDate) - this.getScoreTimeValue(a.score.scoreDate); + if (scoreDateDiff) return scoreDateDiff; + const updateTimeDiff = + this.getScoreTimeValue(b.score.updateTime) - this.getScoreTimeValue(a.score.updateTime); + if (updateTimeDiff) return updateTimeDiff; + const createTimeDiff = + this.getScoreTimeValue(b.score.createTime) - this.getScoreTimeValue(a.score.createTime); + if (createTimeDiff) return createTimeDiff; + return b.index - a.index; + })[0]?.score; + }, + applyArchiveCreditFromScores(archive) { + const latestScore = this.getLatestCreditScore(archive.scores || []); + archive.customerLevel = latestScore?.creditLevel || archive.customerLevel || ''; + archive.maxCreditLimit = latestScore?.maxCreditLimit || archive.maxCreditLimit || ''; + return archive; + }, + syncArchiveCreditFromScores() { + this.applyArchiveCreditFromScores(this.archiveForm); + }, openArchive(row, readonly = false) { this.readonly = readonly; this.activeTab = 'scores'; + this.resetDetailPagination(); if (row && row.id) { getDetail(row.id).then(res => { this.archiveForm = this.normalizeDetail(res.data.data); @@ -1147,14 +2592,24 @@ export default { this.readonly = false; this.archiveForm = this.emptyArchive(); this.qualificationFiles = []; - this.currentScore = { details: [] }; this.scoreBox = false; + this.resetScoreDialog(); + this.resetDetailPagination(); + this.contactBox = false; + this.contactIndex = -1; + this.contactForm = this.emptyContact(); + this.receiptBox = false; + this.receiptIndex = -1; + this.receiptForm = this.emptyReceiptAccount(); + this.invoiceBox = false; + this.invoiceIndex = -1; + this.invoiceForm = this.emptyInvoice(); this.$refs.archiveForm?.clearValidate(); }, validateFormalForApproval(archive) { if (archive.accessType !== 'formal') return true; if (!archive.qualificationAttachments) { - this.$message.warning('正式客商提交审核前必须维护资质附件'); + this.$message.warning('正式客商提交审核前必须维护客商材料'); return false; } const hasCompletedScore = (archive.scores || []).some( @@ -1171,15 +2626,39 @@ export default { archive.customerType = Array.isArray(archive.customerType) ? archive.customerType.join(',') : archive.customerType; + archive.businessScope = Array.isArray(archive.businessScope) + ? archive.businessScope.join(',') + : archive.businessScope; + const deptIds = Array.isArray(archive.deptId) + ? archive.deptId + : archive.deptId + ? [archive.deptId] + : []; + const deptNames = deptIds + .map(id => this.deptOptions.find(item => String(item.value) === String(id))?.rawLabel) + .filter(Boolean); + archive.deptIds = deptIds.join(','); + archive.deptId = deptIds.length ? deptIds[0] : ''; + archive.deptName = deptNames.length ? deptNames.join(',') : archive.deptName; + archive.registeredAddress = this.formatArchiveAddress(archive); + delete archive.registeredRegionPath; archive.qualificationAttachments = this.stringifyAttachments(this.qualificationFiles); - archive.contacts = (archive.contacts || []).filter( - item => item.contactName || item.contactPhone - ); - archive.receiptAccounts = (archive.receiptAccounts || []).filter( - item => item.accountName || item.bankAccount - ); - archive.invoices = (archive.invoices || []).filter(item => item.invoiceTitle || item.taxNo); + archive.contacts = (archive.contacts || []) + .map(item => + this.normalizeContact({ + ...item, + contactAddress: this.formatContactAddress(item), + }) + ) + .filter(item => item.contactName || item.contactPhone); + archive.receiptAccounts = (archive.receiptAccounts || []) + .map(item => this.normalizeReceipt(item)) + .filter(item => item.accountName || item.accountHolderName || item.bankAccount); + archive.invoices = (archive.invoices || []) + .map(item => this.normalizeInvoice(item)) + .filter(item => item.invoiceTitle || item.taxNo || item.bankAccount); archive.scores = (archive.scores || []).map(score => this.calculateScore(score)); + this.applyArchiveCreditFromScores(archive); return archive; }, saveArchive() { @@ -1221,26 +2700,161 @@ export default { }); }, addScore() { - getScoreTemplate().then(res => { - const score = this.normalizeScore(res.data.data); - if (!score.details || !score.details.length) { + this.scoreRecordIndex = -1; + this.currentScore = this.normalizeScore({}); + this.scoreAttachmentFiles = []; + this.expandedScoreCategoryKeys = []; + this.scoreBox = true; + if (!this.scoreQuantificationOptions.length) { + this.initScoreQuantificationOptions(); + } + }, + openScore(row, index = -1) { + this.scoreRecordIndex = index; + this.currentScore = this.normalizeScore(JSON.parse(JSON.stringify(row || {}))); + this.scoreAttachmentFiles = JSON.parse(JSON.stringify(this.currentScore.attachments || [])); + this.expandedScoreCategoryKeys = []; + this.scoreBox = true; + if (!this.scoreQuantificationOptions.length) { + this.initScoreQuantificationOptions(); + } + }, + resetScoreDialog() { + this.scoreRecordIndex = -1; + this.currentScore = { details: [] }; + this.scoreAttachmentFiles = []; + this.expandedScoreCategoryKeys = []; + }, + handleScoreQuantificationChange(quantificationId) { + if (!quantificationId) { + this.currentScore.details = []; + this.expandedScoreCategoryKeys = []; + return; + } + Promise.all([ + getScoreTemplate(quantificationId), + getCreditScoreQuantificationDetail(quantificationId), + ]).then(([templateRes, quantificationRes]) => { + const templateData = templateRes.data.data || {}; + const quantificationData = quantificationRes.data.data || {}; + const template = this.normalizeScore({ + ...templateData, + quantificationId, + scoreDate: this.currentScore.scoreDate, + applyCreditLimit: this.currentScore.applyCreditLimit, + maxCreditLimit: '', + creditLevel: '', + tempApplyCreditLimit: this.currentScore.tempApplyCreditLimit, + tempCreditStartDate: this.currentScore.tempCreditStartDate, + tempCreditEndDate: this.currentScore.tempCreditEndDate, + remark: this.currentScore.remark, + attachments: this.scoreAttachmentFiles, + standards: quantificationData.standards || templateData.standards || [], + details: this.normalizeScoreTemplateDetails(templateData), + }); + if (!template.details || !template.details.length) { this.$message.warning('当前评分量化表未配置评分项目'); - return; } - this.archiveForm.scores.push(score); - this.activeTab = 'scores'; + this.currentScore = template; + this.expandedScoreCategoryKeys = []; + this.calculateScore(this.currentScore); }); }, - openScore(row) { - this.currentScore = row; - this.scoreBox = true; + normalizeScoreTemplateDetails(template = {}) { + const categoryDetails = (template.categories || []).flatMap(category => + (category.items || []).map(item => ({ + ...item, + categoryCode: category.categoryCode, + categoryName: category.categoryName, + optionsJson: + item.optionsJson || + JSON.stringify( + (item.options || []).map(option => ({ + label: option.label || option.optionName || option.value, + value: option.value || option.optionName || option.label, + score: option.score, + })) + ), + })) + ); + if (!template.details || !template.details.length) return categoryDetails; + return template.details.map(detail => { + const category = (template.categories || []).find(item => { + if (detail.categoryCode) return item.categoryCode === detail.categoryCode; + if (detail.categoryName) return item.categoryName === detail.categoryName; + return (item.items || []).some(scoreItem => { + if (detail.itemId && scoreItem.id) return String(scoreItem.id) === String(detail.itemId); + return scoreItem.itemName && scoreItem.itemName === detail.itemName; + }); + }); + return { + ...detail, + categoryCode: detail.categoryCode || category?.categoryCode || '', + categoryName: detail.categoryName || category?.categoryName || '', + }; + }); + }, + getScoreDetailCategoryCode(detail = {}) { + const rawCode = String( + detail.categoryCode || + detail.scoreCategory || + detail.categoryType || + detail.itemCategory || + detail.type || + '' + ).toLowerCase(); + if (['basic', 'plus', 'minus'].includes(rawCode)) return rawCode; + if (rawCode === '1') return 'basic'; + if (rawCode === '2') return 'plus'; + if (rawCode === '3') return 'minus'; + const categoryText = [ + detail.categoryName, + detail.scoreCategoryName, + detail.category, + detail.categoryTypeName, + detail.typeName, + ] + .filter(Boolean) + .join(' '); + if (categoryText.includes('减分')) return 'minus'; + if (categoryText.includes('加分')) return 'plus'; + if (categoryText.includes('基础')) return 'basic'; + return ''; + }, + getScoreCategoryDetailsByList(details, categoryCode) { + return (details || []).filter(item => this.getScoreDetailCategoryCode(item) === categoryCode); + }, + getScoreCategoryDetails(categoryCode) { + return this.getScoreCategoryDetailsByList(this.currentScore.details || [], categoryCode); + }, + isScoreCategoryExpanded(categoryCode) { + return this.expandedScoreCategoryKeys.includes(categoryCode); + }, + toggleScoreCategory(row) { + const categoryCode = row.categoryCode; + this.expandedScoreCategoryKeys = this.isScoreCategoryExpanded(categoryCode) + ? [] + : [categoryCode]; + }, + handleScoreCategoryExpandChange(row, expandedRows) { + const expandedCategoryCodes = (expandedRows || []).map(item => item.categoryCode); + this.expandedScoreCategoryKeys = expandedCategoryCodes.includes(row.categoryCode) + ? [row.categoryCode] + : []; }, parseScoreOptions(value) { if (!value) return []; - if (Array.isArray(value)) return value; + const normalize = list => + list.map(item => ({ + ...item, + label: item.label || item.optionName || item.value, + value: item.value || item.optionName || item.label, + score: item.score, + })); + if (Array.isArray(value)) return normalize(value); try { const options = JSON.parse(value); - return Array.isArray(options) ? options : []; + return Array.isArray(options) ? normalize(options) : []; } catch (error) { return []; } @@ -1251,24 +2865,156 @@ export default { ); const score = Number(option?.score || 0); detail.selfScore = score; - detail.reviewScore = score; + this.calculateScore(this.currentScore); + }, + handleScoreDetailScoreInput(detail, prop, value) { + const normalized = String(value || '') + .replace(/[^\d.]/g, '') + .replace(/^\./, '') + .replace(/(\..*)\./g, '$1') + .replace(/^(\d+)(\.\d{0,2})?.*$/, '$1$2'); + if (prop === 'reviewScore' && normalized !== '') { + const maxScore = this.getScoreItemFullScore(detail); + if (Number(normalized) > maxScore) { + detail[prop] = String(maxScore); + this.$message.warning( + `${detail.itemName || '评分项目'}复评得分不能高于配置的最高得分${this.formatScoreValue( + maxScore + )}分` + ); + this.calculateScore(this.currentScore); + return; + } + } + detail[prop] = normalized; + this.calculateScore(this.currentScore); + }, + handleScoreDecimalInput(prop, value) { + const normalized = String(value || '') + .replace(/[^\d.]/g, '') + .replace(/^\./, '') + .replace(/(\..*)\./g, '$1') + .replace(/^(\d+)(\.\d{0,2})?.*$/, '$1$2'); + this.currentScore[prop] = normalized; + }, + handleScoreAttachmentUploadSuccess(file) { + const index = this.scoreAttachmentFiles.findIndex( + item => item.uid === file.uid || item.url === file.url + ); + const attachment = { + ...file, + size: file.size || '', + uploadUserName: this.userInfo.realName || this.userInfo.userName || '', + uploadTime: this.$dayjs().format('YYYY-MM-DD HH:mm:ss'), + }; + if (index > -1) { + this.scoreAttachmentFiles.splice(index, 1, { + ...this.scoreAttachmentFiles[index], + ...attachment, + }); + return; + } + this.scoreAttachmentFiles.push(attachment); + }, + isMinusScoreDetail(detail = {}) { + return this.getScoreDetailCategoryCode(detail) === 'minus'; + }, + isBasicScoreDetail(detail = {}) { + return this.getScoreDetailCategoryCode(detail) === 'basic'; + }, + getSignedScore(detail, prop) { + const score = Number(detail[prop] || 0); + return this.isMinusScoreDetail(detail) ? -score : score; + }, + getScoreItemFullScore(detail = {}) { + const itemScore = Number(detail.score); + if (Number.isFinite(itemScore) && itemScore > 0) return itemScore; + const optionScores = this.parseScoreOptions(detail.optionsJson) + .map(item => Number(item.score)) + .filter(score => Number.isFinite(score)); + return optionScores.length ? Math.max(...optionScores) : 0; + }, + getScoreFullMark(details = []) { + return details.reduce((sum, item) => { + if (!this.isBasicScoreDetail(item)) return sum; + return sum + this.getScoreItemFullScore(item); + }, 0); + }, + matchCreditStandard(score, scoreRate) { + const standards = (score.standards || []) + .filter(item => item && item.creditLevel) + .sort((a, b) => Number(b.scoreRateLower || 0) - Number(a.scoreRateLower || 0)); + return standards.find(standard => { + const lower = Number(standard.scoreRateLower); + const upper = Number(standard.scoreRateUpper); + if (!Number.isFinite(lower) || !Number.isFinite(upper)) return false; + return scoreRate >= lower && scoreRate <= upper; + }); + }, + calculateCreditLimitByStandard(standard, scoreRate) { + if (!standard) return ''; + const lowerLimit = Number(standard.creditLimitLower); + const upperLimit = Number(standard.creditLimitUpper); + if (!Number.isFinite(lowerLimit) || !Number.isFinite(upperLimit)) return ''; + const scoreRateIncrease = Number(standard.scoreRateIncrease || 0); + const creditLimitIncrease = Number(standard.creditLimitIncrease || 0); + if (scoreRateIncrease <= 0 || creditLimitIncrease <= 0) { + return Number(Math.min(lowerLimit, upperLimit).toFixed(2)); + } + const steps = Math.max( + 0, + Math.floor((scoreRate - Number(standard.scoreRateLower || 0)) / scoreRateIncrease) + ); + const limit = lowerLimit + steps * creditLimitIncrease; + return Number(Math.min(limit, upperLimit).toFixed(2)); }, calculateScore(score) { const details = score.details || []; - const selfScore = details.reduce((sum, item) => sum + Number(item.selfScore || 0), 0); - const reviewScore = details.reduce((sum, item) => sum + Number(item.reviewScore || 0), 0); + const basicDetails = this.getScoreCategoryDetailsByList(details, 'basic'); + const plusDetails = this.getScoreCategoryDetailsByList(details, 'plus'); + const minusDetails = this.getScoreCategoryDetailsByList(details, 'minus'); + const sumScore = (list, prop) => + list.reduce((sum, item) => sum + Number(item[prop] || 0), 0); + const selfScore = + sumScore(basicDetails, 'selfScore') + + sumScore(plusDetails, 'selfScore') - + sumScore(minusDetails, 'selfScore'); + const reviewScore = + sumScore(basicDetails, 'reviewScore') + + sumScore(plusDetails, 'reviewScore') - + sumScore(minusDetails, 'reviewScore'); + const hasReviewScore = details.some( + item => + item.reviewScore !== undefined && item.reviewScore !== null && item.reviewScore !== '' + ); + const finalScore = hasReviewScore ? reviewScore : selfScore; + const fullMark = this.getScoreFullMark(details); + const scoreRate = + fullMark > 0 ? Math.max(0, Math.min(100, (finalScore / fullMark) * 100)) : 0; + const standard = this.matchCreditStandard(score, scoreRate); + const hasStandards = (score.standards || []).some(item => item && item.creditLevel); score.selfScore = Number(selfScore.toFixed(2)); score.reviewScore = Number(reviewScore.toFixed(2)); - score.finalScore = Number((reviewScore || selfScore).toFixed(2)); + score.finalScore = Number(finalScore.toFixed(2)); + score.scoreRate = Number(scoreRate.toFixed(2)); + if (hasStandards) { + score.creditLevel = standard?.creditLevel || ''; + score.maxCreditLimit = standard + ? this.calculateCreditLimitByStandard(standard, scoreRate) + : ''; + } return score; }, + hasIncompleteBasicScoreDetail(details = []) { + return this.getScoreCategoryDetailsByList(details, 'basic').some(item => !item.selectedOption); + }, finishScore(score) { if (!score.details || !score.details.length) { this.$message.warning('请先读取评分量化表'); return; } - if (score.details.some(item => !item.selectedOption)) { - this.$message.warning('请完整选择评分明细选项'); + if (this.hasIncompleteBasicScoreDetail(score.details)) { + this.$message.warning('请完整选择基础得分项评分明细选项'); return; } this.calculateScore(score); @@ -1277,8 +3023,91 @@ export default { this.$message.success('评分已完成'); }, saveScoreDetail() { + if (!this.validateScoreForm(false)) return; + this.currentScore.attachments = JSON.parse(JSON.stringify(this.scoreAttachmentFiles || [])); + this.currentScore.proofAttachments = this.stringifyAttachments(this.scoreAttachmentFiles); this.calculateScore(this.currentScore); + const score = JSON.parse(JSON.stringify(this.currentScore)); + if (this.scoreRecordIndex > -1) { + this.archiveForm.scores.splice(this.scoreRecordIndex, 1, score); + } else { + this.archiveForm.scores.push(score); + this.handleScoreCurrentChange(this.scorePageCount); + } + this.syncArchiveCreditFromScores(); + this.activeTab = 'scores'; this.scoreBox = false; + this.saveScoreArchive(); + }, + saveScoreArchive() { + if (!this.archiveForm.id) { + this.$message.success('评分记录已添加,请保存客商档案后生效'); + return; + } + submit(this.normalizeArchive()).then(() => { + this.$message.success('评分记录已保存'); + }); + }, + confirmReviewScore() { + if (!this.validateScoreForm(true)) return; + this.currentScore.selfStatus = '已完成'; + this.currentScore.reviewStatus = '已完成'; + this.saveScoreDetail(); + this.$message.success('复评确认成功'); + }, + validateScoreForm(requireComplete) { + if (!this.currentScore.scoreDate) { + this.$message.warning('请选择评定日期'); + return false; + } + if (!this.currentScore.applyCreditLimit) { + this.$message.warning('请输入拟申请总资金使用额度'); + return false; + } + if (Number(this.currentScore.applyCreditLimit) < 0) { + this.$message.warning('拟申请总资金使用额度不能小于0'); + return false; + } + if (!this.currentScore.quantificationId) { + this.$message.warning('请选择评分量化表'); + return false; + } + if (!this.currentScore.details || !this.currentScore.details.length) { + this.$message.warning('当前评分量化表未配置评分项目'); + return false; + } + const overMaxReviewScore = this.currentScore.details.find(item => { + if (item.reviewScore === undefined || item.reviewScore === null || item.reviewScore === '') { + return false; + } + return Number(item.reviewScore) > this.getScoreItemFullScore(item); + }); + if (overMaxReviewScore) { + this.$message.warning( + `${ + overMaxReviewScore.itemName || '评分项目' + }复评得分不能高于配置的最高得分${this.formatScoreValue( + this.getScoreItemFullScore(overMaxReviewScore) + )}分` + ); + return false; + } + if (requireComplete && this.hasIncompleteBasicScoreDetail(this.currentScore.details)) { + this.$message.warning('请完整选择基础得分项评分明细选项'); + return false; + } + return true; + }, + handleScoreCurrentChange(currentPage) { + const page = Number(currentPage) || 1; + this.scorePage.currentPage = Math.min(Math.max(page, 1), this.scorePageCount); + }, + handleScoreSizeChange(pageSize) { + this.scorePage.pageSize = Number(pageSize) || 3; + this.handleScoreCurrentChange(1); + }, + formatScoreValue(value) { + return value === undefined || value === null || value === '' ? '' : value; }, rowDel(row) { this.$confirm('仅草稿状态客商可删除,确定删除该数据?', { @@ -1416,7 +3245,8 @@ export default { NProgress.start(); exportBlob( '/blade-transport/customer-archive/export-customer-archive', - this.buildExportParams() + this.buildExportParams(), + { feedback: true } ) .then(res => { downloadXls(res.data, `客商档案${this.$dayjs().format('YYYY-MM-DD HH:mm:ss')}.xlsx`); @@ -1451,11 +3281,44 @@ export default { .archive-form { :deep(.el-input-number), :deep(.el-select), + :deep(.el-cascader), :deep(.el-date-editor) { width: 100%; } } +.business-term-field, +.archive-form__address { + display: flex; + align-items: center; + gap: 10px; + width: 100%; +} + +.business-term-field { + :deep(.el-date-editor) { + flex: 1; + min-width: 0; + } + + :deep(.el-checkbox) { + flex: 0 0 auto; + margin-right: 0; + } +} + +.archive-form__address { + .el-input, + :deep(.el-cascader) { + flex: 1; + min-width: 0; + } + + :deep(.el-cascader) { + flex: 0 0 36%; + } +} + .section-title { margin: 16px 0 12px; font-size: 18px; @@ -1489,12 +3352,303 @@ export default { margin-top: 18px; } +.score-list-table { + width: 100%; +} + +.score-detail-form { + padding: 8px 36px 0; + + :deep(.el-form-item) { + margin-bottom: 18px; + } + + :deep(.el-form-item__label) { + color: #70757a; + font-size: 15px; + font-weight: 600; + } + + :deep(.el-input), + :deep(.el-select), + :deep(.el-date-editor) { + width: 230px; + } + + :deep(.el-col:nth-child(2) .el-form-item) { + justify-content: flex-end; + } +} + +.score-detail-form__range { + display: flex; + align-items: center; + gap: 14px; + + :deep(.el-date-editor) { + width: 230px; + } +} + +.score-category-table { + width: calc(100% - 72px); + margin: 8px 36px 0; + + :deep(.el-table__body td) { + height: 56px; + color: #303133; + } + + :deep(.el-table__body tr) { + cursor: pointer; + } +} + +.score-category-table__name { + display: inline-block; + min-width: 96px; + margin-right: 10px; + font-weight: 700; +} + +.score-category-table__arrow { + font-size: 18px; + font-weight: 700; + color: #303133; + transition: transform 0.2s ease; + + &.is-expanded { + transform: rotate(90deg); + } +} + +.score-category-table { + :deep(.score-category-table__expand-column) { + width: 0 !important; + padding: 0 !important; + border-right: 0; + + .cell { + display: none; + } + } + + :deep(.el-table__expanded-cell) { + padding: 0; + background-color: #fff; + } +} + +.score-standard-cell { + display: flex; + flex-direction: column; + justify-content: center; + min-height: 104px; + padding: 8px 24px; +} + +.score-standard-cell__label { + margin-bottom: 12px; + color: #303133; +} + +.score-detail-table--inline { + margin-top: 0; + + :deep(.el-table__header th) { + height: 46px; + background-color: #f5f7fa; + color: #303133; + font-weight: 700; + } + + :deep(.el-table__body td) { + min-height: 112px; + color: #303133; + } + + :deep(.el-select), + :deep(.el-input), + :deep(.el-select) { + width: 100%; + } +} + +.customer-material-section { + margin-top: 18px; +} + +.change-record-section { + margin-top: 18px; +} + +.score-list-table, +.contact-table, +.receipt-table, +.invoice-table { + :deep(.el-table__header th) { + background-color: #f5f7fa; + } +} + +.contact-form, +.receipt-form, +.invoice-form { + max-width: 620px; + + :deep(.el-form-item) { + margin-bottom: 16px; + } + + :deep(.el-input) { + width: 100%; + } +} + +.invoice-form { + max-width: none; + + &__section-title { + display: flex; + align-items: center; + justify-content: center; + gap: 16px; + margin: 8px 0 20px; + font-size: 16px; + font-weight: 700; + color: #303133; + + &::before, + &::after { + flex: 1; + height: 1px; + content: ''; + background-color: #dcdfe6; + } + } +} + +.contact-form__address { + display: flex; + gap: 10px; + width: 100%; + + .el-input { + flex: 1; + min-width: 0; + } + + .el-input:first-child { + flex: 0 0 38%; + } +} + +.contact-form__map-toolbar { + display: flex; + gap: 10px; + margin-bottom: 12px; + + .el-input { + flex: 1; + } +} + +.contact-form__map { + width: 100%; + height: 460px; + border: 1px solid #dcdfe6; +} + +.contact-form__map-info { + display: flex; + justify-content: space-between; + gap: 16px; + min-height: 22px; + margin-top: 10px; + color: #606266; + font-size: 13px; +} + +.invoice-form__address { + display: flex; + gap: 10px; + width: 100%; + + .el-input { + flex: 1; + min-width: 0; + } + + .el-input:first-child { + flex: 0 0 38%; + } +} + .score-detail-table { width: 100%; + margin-top: 12px; +} + +:deep(.score-detail-dialog .el-dialog__body) { + max-height: 74vh; + overflow: auto; + padding: 18px 20px 22px; +} + +:deep(.score-detail-dialog .el-dialog__footer) { + text-align: center; + + .el-button { + min-width: 160px; + height: 42px; + margin: 0 18px; + } +} + +.score-material-section { + margin: 26px 36px 0; +} + +.score-material-section__head { + display: flex; + align-items: center; + gap: 18px; + margin-bottom: 16px; + + .dialog-section-title { + margin: 0; + } + + :deep(.vehicle-attachment-upload) { + width: auto; + } + + :deep(.el-upload-list) { + display: none; + } +} + +.score-material-table { + width: 100%; } :deep(.archive-dialog .el-dialog__body) { max-height: 72vh; overflow: auto; } + +:deep(.contact-dialog .el-dialog__body) { + max-height: 70vh; + overflow: auto; +} + +:deep(.receipt-dialog .el-dialog__body) { + max-height: 70vh; + overflow: auto; +} + +:deep(.invoice-dialog .el-dialog__body) { + max-height: 72vh; + overflow: auto; +} diff --git a/src/views/vehicle/etc-record.vue b/src/views/vehicle/etc-record.vue index 95a9644..ce91eee 100644 --- a/src/views/vehicle/etc-record.vue +++ b/src/views/vehicle/etc-record.vue @@ -379,7 +379,11 @@ export default { type: 'warning', }).then(() => { NProgress.start(); - exportBlob('/blade-transport/etc-record/export-etc-record', this.buildExportParams()) + exportBlob( + '/blade-transport/etc-record/export-etc-record', + this.buildExportParams(), + { feedback: true } + ) .then(res => { downloadXls(res.data, `ETC记录${this.$dayjs().format('YYYY-MM-DD HH:mm:ss')}.xlsx`); }) @@ -397,7 +401,8 @@ export default { }, handleTemplate() { exportBlob( - `/blade-transport/etc-record/export-template?${this.website.tokenHeader}=${getToken()}` + `/blade-transport/etc-record/export-template?${this.website.tokenHeader}=${getToken()}`, + { feedback: true } ).then(res => { downloadXls(res.data, 'ETC记录模板.xlsx'); }); diff --git a/src/views/vehicle/insurance-record.vue b/src/views/vehicle/insurance-record.vue index 16640c5..3999d67 100644 --- a/src/views/vehicle/insurance-record.vue +++ b/src/views/vehicle/insurance-record.vue @@ -441,7 +441,8 @@ export default { NProgress.start(); exportBlob( '/blade-transport/insurance-record/export-insurance-record', - this.buildExportParams() + this.buildExportParams(), + { feedback: true } ) .then(res => { downloadXls(res.data, `保险记录${this.$dayjs().format('YYYY-MM-DD HH:mm:ss')}.xlsx`); @@ -462,7 +463,8 @@ export default { exportBlob( `/blade-transport/insurance-record/export-template?${ this.website.tokenHeader - }=${getToken()}` + }=${getToken()}`, + { feedback: true } ).then(res => { downloadXls(res.data, '保险记录模板.xlsx'); }); diff --git a/src/views/vehicle/maintenance-plan.vue b/src/views/vehicle/maintenance-plan.vue index 31f7302..e862bb1 100644 --- a/src/views/vehicle/maintenance-plan.vue +++ b/src/views/vehicle/maintenance-plan.vue @@ -692,7 +692,8 @@ export default { NProgress.start(); exportBlob( '/blade-transport/maintenance-plan/export-maintenance-plan', - this.buildExportParams() + this.buildExportParams(), + { feedback: true } ) .then(res => { downloadXls(res.data, `保养记录${this.$dayjs().format('YYYY-MM-DD HH:mm:ss')}.xlsx`); @@ -716,7 +717,8 @@ export default { exportBlob( `/blade-transport/maintenance-plan/export-template?${ this.website.tokenHeader - }=${getToken()}` + }=${getToken()}`, + { feedback: true } ).then(res => { downloadXls(res.data, '保养记录模板.xlsx'); }); diff --git a/src/views/vehicle/maintenance-record.vue b/src/views/vehicle/maintenance-record.vue index b0c9f39..f2f3381 100644 --- a/src/views/vehicle/maintenance-record.vue +++ b/src/views/vehicle/maintenance-record.vue @@ -677,7 +677,8 @@ export default { NProgress.start(); exportBlob( '/blade-transport/maintenance-record/export-maintenance-record', - this.buildExportParams() + this.buildExportParams(), + { feedback: true } ) .then(res => { downloadXls(res.data, `维修记录${this.$dayjs().format('YYYY-MM-DD HH:mm:ss')}.xlsx`); @@ -701,7 +702,8 @@ export default { exportBlob( `/blade-transport/maintenance-record/export-template?${ this.website.tokenHeader - }=${getToken()}` + }=${getToken()}`, + { feedback: true } ).then(res => { downloadXls(res.data, '维修记录模板.xlsx'); }); diff --git a/src/views/vehicle/mileage-record.vue b/src/views/vehicle/mileage-record.vue index da2f6be..7c5b875 100644 --- a/src/views/vehicle/mileage-record.vue +++ b/src/views/vehicle/mileage-record.vue @@ -522,7 +522,8 @@ export default { NProgress.start(); exportBlob( '/blade-transport/mileage-record/export-mileage-record', - this.buildExportParams() + this.buildExportParams(), + { feedback: true } ) .then(res => { downloadXls(res.data, `里程记录${this.$dayjs().format('YYYY-MM-DD HH:mm:ss')}.xlsx`); @@ -544,7 +545,8 @@ export default { }, handleTemplate() { exportBlob( - `/blade-transport/mileage-record/export-template?${this.website.tokenHeader}=${getToken()}` + `/blade-transport/mileage-record/export-template?${this.website.tokenHeader}=${getToken()}`, + { feedback: true } ).then(res => { downloadXls(res.data, '里程记录模板.xlsx'); }); diff --git a/src/views/vehicle/oil-electric-record.vue b/src/views/vehicle/oil-electric-record.vue index be7d516..aa9f49e 100644 --- a/src/views/vehicle/oil-electric-record.vue +++ b/src/views/vehicle/oil-electric-record.vue @@ -434,7 +434,8 @@ export default { NProgress.start(); exportBlob( '/blade-transport/oil-electric-record/export-oil-electric-record', - this.buildExportParams() + this.buildExportParams(), + { feedback: true } ) .then(res => { downloadXls(res.data, `油电记录${this.$dayjs().format('YYYY-MM-DD HH:mm:ss')}.xlsx`); @@ -455,7 +456,8 @@ export default { exportBlob( `/blade-transport/oil-electric-record/export-template?${ this.website.tokenHeader - }=${getToken()}` + }=${getToken()}`, + { feedback: true } ).then(res => { downloadXls(res.data, '油电记录模板.xlsx'); }); diff --git a/src/views/vehicle/other-expense-record.vue b/src/views/vehicle/other-expense-record.vue index 897f6c3..848d71a 100644 --- a/src/views/vehicle/other-expense-record.vue +++ b/src/views/vehicle/other-expense-record.vue @@ -425,7 +425,8 @@ export default { NProgress.start(); exportBlob( '/blade-transport/other-expense-record/export-other-expense-record', - this.buildExportParams() + this.buildExportParams(), + { feedback: true } ) .then(res => { downloadXls( @@ -449,7 +450,8 @@ export default { exportBlob( `/blade-transport/other-expense-record/export-template?${ this.website.tokenHeader - }=${getToken()}` + }=${getToken()}`, + { feedback: true } ).then(res => { downloadXls(res.data, '其他费用记录模板.xlsx'); }); diff --git a/src/views/vehicle/tire-replacement-record.vue b/src/views/vehicle/tire-replacement-record.vue index cd62309..6f2d6c4 100644 --- a/src/views/vehicle/tire-replacement-record.vue +++ b/src/views/vehicle/tire-replacement-record.vue @@ -379,7 +379,8 @@ export default { NProgress.start(); exportBlob( '/blade-transport/tire-replacement-record/export-tire-replacement-record', - this.buildExportParams() + this.buildExportParams(), + { feedback: true } ) .then(res => { downloadXls(res.data, `换胎记录${this.$dayjs().format('YYYY-MM-DD HH:mm:ss')}.xlsx`); @@ -403,7 +404,8 @@ export default { exportBlob( `/blade-transport/tire-replacement-record/export-template?${ this.website.tokenHeader - }=${getToken()}` + }=${getToken()}`, + { feedback: true } ).then(res => { downloadXls(res.data, '换胎记录模板.xlsx'); }); diff --git a/src/views/vehicle/transport-change-record.vue b/src/views/vehicle/transport-change-record.vue index 6cd2908..58796f2 100644 --- a/src/views/vehicle/transport-change-record.vue +++ b/src/views/vehicle/transport-change-record.vue @@ -397,7 +397,8 @@ export default { NProgress.start(); exportBlob( '/blade-transport/transport-change-record/export-transport-change-record', - this.buildExportParams() + this.buildExportParams(), + { feedback: true } ) .then(res => { downloadXls(res.data, `变更记录${this.$dayjs().format('YYYY-MM-DD HH:mm:ss')}.xlsx`); @@ -421,7 +422,8 @@ export default { exportBlob( `/blade-transport/transport-change-record/export-template?${ this.website.tokenHeader - }=${getToken()}` + }=${getToken()}`, + { feedback: true } ).then(res => { downloadXls(res.data, '变更记录模板.xlsx'); }); diff --git a/src/views/vehicle/violation-record.vue b/src/views/vehicle/violation-record.vue index a53fe4c..74c483f 100644 --- a/src/views/vehicle/violation-record.vue +++ b/src/views/vehicle/violation-record.vue @@ -505,7 +505,8 @@ export default { NProgress.start(); exportBlob( '/blade-transport/violation-record/export-violation-record', - this.buildExportParams() + this.buildExportParams(), + { feedback: true } ) .then(res => { downloadXls(res.data, `违章记录${this.$dayjs().format('YYYY-MM-DD HH:mm:ss')}.xlsx`); @@ -529,7 +530,8 @@ export default { exportBlob( `/blade-transport/violation-record/export-template?${ this.website.tokenHeader - }=${getToken()}` + }=${getToken()}`, + { feedback: true } ).then(res => { downloadXls(res.data, '违章记录模板.xlsx'); }); diff --git a/src/views/work/claim.vue b/src/views/work/claim.vue index 22f37d3..03f7d1f 100644 --- a/src/views/work/claim.vue +++ b/src/views/work/claim.vue @@ -18,7 +18,6 @@ 签收 @@ -26,7 +25,6 @@ 详情 @@ -34,7 +32,6 @@ 流程图 diff --git a/src/views/work/done.vue b/src/views/work/done.vue index 2f81301..c3aa9ff 100644 --- a/src/views/work/done.vue +++ b/src/views/work/done.vue @@ -19,7 +19,6 @@ 详情 @@ -27,7 +26,6 @@ 流程图 diff --git a/src/views/work/send.vue b/src/views/work/send.vue index 6936697..06457f9 100644 --- a/src/views/work/send.vue +++ b/src/views/work/send.vue @@ -19,7 +19,6 @@ 详情 @@ -27,7 +26,6 @@ 流程图 diff --git a/src/views/work/start.vue b/src/views/work/start.vue index b2c0e3f..98d9cc7 100644 --- a/src/views/work/start.vue +++ b/src/views/work/start.vue @@ -25,7 +25,6 @@ 发起 @@ -33,7 +32,6 @@ 流程图 diff --git a/src/views/work/todo.vue b/src/views/work/todo.vue index f6836d0..20d1ca2 100644 --- a/src/views/work/todo.vue +++ b/src/views/work/todo.vue @@ -18,7 +18,6 @@ 处理 @@ -26,7 +25,6 @@ 详情 @@ -34,7 +32,6 @@ 流程图 diff --git a/vite.config.mjs b/vite.config.mjs index fbebcf8..00cdeec 100644 --- a/vite.config.mjs +++ b/vite.config.mjs @@ -48,14 +48,25 @@ export default ({ define: { __VUE_I18N_FULL_INSTALL__: true, __VUE_I18N_LEGACY_API__: true, - __INTLIFY_PROD_DEVTOOLS__: false + __INTLIFY_PROD_DEVTOOLS__: false, }, + // server: { + // port: 2888, + // proxy: { + // '/api': { + // target: 'http://172.16.203.228:8000', + // changeOrigin: true, + // }, + // }, + // }, server: { port: 2888, proxy: { '/api': { - target: 'http://172.16.203.228:8000', + target: 'http://localhost', + //target: 'https://saber3.bladex.cn/api', changeOrigin: true, + rewrite: path => path.replace(/^\/api/, ''), }, }, },