调整 合同、运力、客商、项目
This commit is contained in:
@@ -78,3 +78,10 @@ export const getDeptLazyTree = parentId => {
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
export const getPlatformCompanySelect = () => {
|
||||
return request({
|
||||
url: '/blade-system/dept/platform-company-select',
|
||||
method: 'get',
|
||||
});
|
||||
};
|
||||
|
||||
@@ -169,16 +169,16 @@ export const option = createCrudOption([
|
||||
rules: textRule('业务部门', 50, true),
|
||||
},
|
||||
{
|
||||
label: '承办部门',
|
||||
label: '平台公司',
|
||||
prop: 'undertakeDeptName',
|
||||
search: true,
|
||||
searchPlaceholder: '请输入',
|
||||
searchOrder: 8,
|
||||
minWidth: 150,
|
||||
rules: textRule('承办部门', 50, true),
|
||||
rules: textRule('平台公司', 50, true),
|
||||
},
|
||||
{
|
||||
label: '承办部门ID',
|
||||
label: '平台公司ID',
|
||||
prop: 'undertakeDeptId',
|
||||
hide: true,
|
||||
},
|
||||
|
||||
@@ -2,7 +2,6 @@ import {
|
||||
approvalStatusOptions,
|
||||
auditColumns,
|
||||
createCrudOption,
|
||||
futureDateRule,
|
||||
nonNegativeRule,
|
||||
textRule,
|
||||
withSearchPlaceholders,
|
||||
@@ -58,30 +57,25 @@ export const config = {
|
||||
detailAlignCenter: true,
|
||||
detailSections: [
|
||||
{
|
||||
title: '申请信息',
|
||||
title: '项目额度信息',
|
||||
fields: [
|
||||
['applicationNo', '申请单号', 1],
|
||||
['projectName', '项目', 1],
|
||||
['projectName', '项目名称', 1],
|
||||
['projectCode', '项目编号', 1],
|
||||
['undertakeDeptName', '承办部门', 1],
|
||||
['applyDeptName', '申请部门', 1],
|
||||
['applicantName', '申请人', 1],
|
||||
['approvalStatus', '审批状态', 1],
|
||||
['currentNode', '当前节点', 1],
|
||||
['currentProcessor', '当前处理人', 1],
|
||||
['validUntil', '申请有效期至', 1],
|
||||
['remark', '备注', 1],
|
||||
],
|
||||
},
|
||||
{
|
||||
title: '额度信息',
|
||||
fields: [
|
||||
['projectFundLimit', '项目资金使用额度(万元)', 1],
|
||||
['usedFundLimit', '已使用项目资金使用额度(万元)', 1],
|
||||
['remainingFundLimit', '剩余项目资金使用额度(万元)', 1],
|
||||
['applyLimit', '申请临时额度(万元)', 1],
|
||||
],
|
||||
},
|
||||
{
|
||||
title: '临时额度信息',
|
||||
fields: [
|
||||
['applyLimit', '申请临时额度(万元)', 1],
|
||||
['validUntil', '申请有效期至', 1],
|
||||
['remark', '备注', 4],
|
||||
],
|
||||
showAttachment: true,
|
||||
},
|
||||
],
|
||||
operations: [
|
||||
{
|
||||
@@ -140,6 +134,14 @@ export const option = {
|
||||
editDisplay: false,
|
||||
editDisabled: true,
|
||||
},
|
||||
{
|
||||
label: '',
|
||||
prop: 'projectQuotaInfoTitle',
|
||||
formslot: true,
|
||||
span: 24,
|
||||
hide: true,
|
||||
labelWidth: 0,
|
||||
},
|
||||
{
|
||||
label: '项目名称',
|
||||
prop: 'projectName',
|
||||
@@ -216,6 +218,14 @@ export const option = {
|
||||
value: 0,
|
||||
rules: [nonNegativeRule('剩余项目资金使用额度')],
|
||||
},
|
||||
{
|
||||
label: '',
|
||||
prop: 'temporaryCreditInfoTitle',
|
||||
formslot: true,
|
||||
span: 24,
|
||||
hide: true,
|
||||
labelWidth: 0,
|
||||
},
|
||||
{
|
||||
label: '申请临时额度(万元)',
|
||||
prop: 'applyLimit',
|
||||
@@ -238,9 +248,27 @@ export const option = {
|
||||
minWidth: 140,
|
||||
rules: [
|
||||
{ required: true, message: '请选择申请有效期至', trigger: 'change' },
|
||||
futureDateRule('申请有效期至'),
|
||||
],
|
||||
},
|
||||
{
|
||||
label: '备注',
|
||||
prop: 'remark',
|
||||
type: 'textarea',
|
||||
span: 24,
|
||||
minRows: 2,
|
||||
hide: true,
|
||||
placeholder: '请输入',
|
||||
maxlength: 200,
|
||||
showWordLimit: true,
|
||||
rules: textRule('备注', 200),
|
||||
},
|
||||
{
|
||||
label: '附件',
|
||||
prop: 'attachmentsJson',
|
||||
formslot: true,
|
||||
span: 24,
|
||||
hide: true,
|
||||
},
|
||||
{
|
||||
label: '申请部门',
|
||||
prop: 'applyDeptName',
|
||||
@@ -304,25 +332,6 @@ export const option = {
|
||||
addDisplay: false,
|
||||
editDisplay: false,
|
||||
},
|
||||
{
|
||||
label: '临时额度信息',
|
||||
prop: 'remark',
|
||||
type: 'textarea',
|
||||
span: 24,
|
||||
minRows: 2,
|
||||
hide: true,
|
||||
placeholder: '请输入',
|
||||
maxlength: 200,
|
||||
showWordLimit: true,
|
||||
rules: textRule('临时额度信息', 200),
|
||||
},
|
||||
{
|
||||
label: '附件',
|
||||
prop: 'attachmentsJson',
|
||||
formslot: true,
|
||||
span: 24,
|
||||
hide: true,
|
||||
},
|
||||
...auditColumns.map(column => ({ ...column, hide: true })),
|
||||
])),
|
||||
dialogWidth: '96%',
|
||||
|
||||
@@ -36,6 +36,7 @@ export const option = {
|
||||
menuFixed: 'right',
|
||||
menuWidth: 320,
|
||||
column: [
|
||||
// ========== 表格列(顺序固定)==========
|
||||
{
|
||||
label: '申请单号',
|
||||
prop: 'applicationNo',
|
||||
@@ -50,14 +51,14 @@ export const option = {
|
||||
placeholder: '保存后自动生成',
|
||||
},
|
||||
{
|
||||
label: '调度车辆',
|
||||
label: '车牌号',
|
||||
prop: 'plateNo',
|
||||
search: true,
|
||||
searchOrder: 4,
|
||||
searchPlaceholder: '请输入',
|
||||
minWidth: 120,
|
||||
formslot: true,
|
||||
rules: [{ required: true, message: '请输入车牌号', trigger: 'blur' }],
|
||||
rules: [{ required: true, message: '请选择车牌号', trigger: 'blur' }],
|
||||
},
|
||||
{
|
||||
label: '所属组织',
|
||||
@@ -67,7 +68,7 @@ export const option = {
|
||||
searchPlaceholder: '请输入',
|
||||
minWidth: 140,
|
||||
disabled: true,
|
||||
placeholder: '选择调度车辆后自动填充',
|
||||
placeholder: '选择车牌号后自动填充',
|
||||
rules: [{ required: true, message: '请输入所属组织', trigger: 'blur' }],
|
||||
},
|
||||
{
|
||||
@@ -79,7 +80,15 @@ export const option = {
|
||||
minWidth: 140,
|
||||
formslot: true,
|
||||
span: 12,
|
||||
rules: [{ required: true, message: '请输入使用部门', trigger: 'blur' }],
|
||||
rules: [{ required: true, message: '请选择使用部门', trigger: 'blur' }],
|
||||
},
|
||||
{
|
||||
label: '车辆类型',
|
||||
prop: 'vehicleType',
|
||||
minWidth: 110,
|
||||
addDisplay: false,
|
||||
editDisplay: false,
|
||||
viewDisplay: false,
|
||||
},
|
||||
{
|
||||
label: '审批状态',
|
||||
@@ -107,31 +116,6 @@ export const option = {
|
||||
addDisplay: false,
|
||||
editDisplay: false,
|
||||
},
|
||||
{
|
||||
label: '申请人',
|
||||
prop: 'createUserName',
|
||||
hide: true,
|
||||
display: true,
|
||||
addDisplay: true,
|
||||
editDisplay: true,
|
||||
viewDisplay: true,
|
||||
disabled: true,
|
||||
span: 12,
|
||||
},
|
||||
{
|
||||
label: '申请时间',
|
||||
prop: 'createTime',
|
||||
type: 'datetime',
|
||||
format: 'YYYY-MM-DD HH:mm:ss',
|
||||
valueFormat: 'YYYY-MM-DD HH:mm:ss',
|
||||
hide: true,
|
||||
display: true,
|
||||
addDisplay: true,
|
||||
editDisplay: true,
|
||||
viewDisplay: true,
|
||||
disabled: true,
|
||||
span: 12,
|
||||
},
|
||||
{
|
||||
label: '创建人',
|
||||
prop: 'createUserName',
|
||||
@@ -149,10 +133,37 @@ export const option = {
|
||||
addDisplay: false,
|
||||
editDisplay: false,
|
||||
},
|
||||
// ========== 新增/编辑表单专用(不进表格)==========
|
||||
{
|
||||
label: '申请人',
|
||||
prop: 'applicantName',
|
||||
hide: true,
|
||||
display: true,
|
||||
addDisplay: true,
|
||||
editDisplay: true,
|
||||
viewDisplay: true,
|
||||
disabled: true,
|
||||
span: 12,
|
||||
},
|
||||
{
|
||||
label: '申请时间',
|
||||
prop: 'applyTime',
|
||||
type: 'datetime',
|
||||
format: 'YYYY-MM-DD HH:mm:ss',
|
||||
valueFormat: 'YYYY-MM-DD HH:mm:ss',
|
||||
hide: true,
|
||||
display: true,
|
||||
addDisplay: true,
|
||||
editDisplay: true,
|
||||
viewDisplay: true,
|
||||
disabled: true,
|
||||
span: 12,
|
||||
},
|
||||
{
|
||||
label: '备注',
|
||||
prop: 'remark',
|
||||
type: 'textarea',
|
||||
hide: true,
|
||||
span: 24,
|
||||
maxlength: 200,
|
||||
showWordLimit: true,
|
||||
@@ -164,7 +175,6 @@ export const option = {
|
||||
hide: true,
|
||||
span: 24,
|
||||
minWidth: 100,
|
||||
slot: true,
|
||||
formslot: true,
|
||||
},
|
||||
],
|
||||
|
||||
@@ -2,11 +2,35 @@ import router from './router/';
|
||||
import store from './store';
|
||||
import { tabKeyOf } from '@/router/tab';
|
||||
import { getToken } from '@/utils/auth';
|
||||
import {
|
||||
isChunkLoadError,
|
||||
reloadForChunkError,
|
||||
setPendingRoutePath,
|
||||
} from '@/utils/chunk-reload';
|
||||
import { ElMessage } from 'element-plus';
|
||||
import NProgress from 'nprogress'; // progress bar
|
||||
import 'nprogress/nprogress.css'; // progress bar style
|
||||
NProgress.configure({ showSpinner: false });
|
||||
const lockPage = '/lock'; //锁屏页
|
||||
|
||||
// 懒加载 chunk / CSS 失败时整页刷新,避免菜单点击后卡死
|
||||
router.onError(error => {
|
||||
if (!isChunkLoadError(error)) return;
|
||||
if (reloadForChunkError()) {
|
||||
ElMessage.warning('页面资源加载失败,正在重新加载…');
|
||||
}
|
||||
});
|
||||
|
||||
window.addEventListener('unhandledrejection', event => {
|
||||
if (!isChunkLoadError(event.reason)) return;
|
||||
if (reloadForChunkError()) {
|
||||
event.preventDefault();
|
||||
ElMessage.warning('页面资源加载失败,正在重新加载…');
|
||||
}
|
||||
});
|
||||
|
||||
router.beforeEach((to, from, next) => {
|
||||
setPendingRoutePath(to.fullPath);
|
||||
const meta = to.meta || {};
|
||||
const isMenu = meta.menu === undefined ? to.query.menu : meta.menu;
|
||||
store.commit('SET_IS_MENU', isMenu === undefined);
|
||||
|
||||
@@ -155,6 +155,18 @@ export default [
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
path: '/business/contract-manage/detail',
|
||||
component: Layout,
|
||||
children: [
|
||||
{
|
||||
path: '',
|
||||
name: '合同详情',
|
||||
meta: { keepAlive: false, activeMenu: '/business/contract-manage' },
|
||||
component: () => import('@/views/business/contract-manage.vue'),
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
path: '/business/project-apply/form',
|
||||
component: Layout,
|
||||
|
||||
@@ -0,0 +1,38 @@
|
||||
/**
|
||||
* 懒加载 chunk / CSS preload 失败后的整页恢复。
|
||||
* 常见于:部署后旧 hash 失效、静态服务空闲断连、代理 CONNECTION_RESET。
|
||||
*/
|
||||
|
||||
const RELOAD_FLAG = 'app:chunk-reload-ts';
|
||||
const RELOAD_COOLDOWN_MS = 10000;
|
||||
|
||||
const CHUNK_ERROR_RE =
|
||||
/Failed to fetch dynamically imported module|Importing a module script failed|Unable to preload CSS|error loading dynamically imported module|Loading CSS chunk|Loading chunk .+ failed|ChunkLoadError/i;
|
||||
|
||||
/** 最近一次路由跳转目标,供 onError 时整页落到正确地址 */
|
||||
let pendingFullPath = '';
|
||||
|
||||
export function setPendingRoutePath(fullPath = '') {
|
||||
pendingFullPath = fullPath || '';
|
||||
}
|
||||
|
||||
export function isChunkLoadError(error) {
|
||||
if (!error) return false;
|
||||
const message = error.message || String(error);
|
||||
return CHUNK_ERROR_RE.test(message);
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns {boolean} 是否已触发刷新(冷却期内返回 false,避免死循环)
|
||||
*/
|
||||
export function reloadForChunkError(targetPath) {
|
||||
const now = Date.now();
|
||||
const last = Number(sessionStorage.getItem(RELOAD_FLAG) || 0);
|
||||
if (now - last < RELOAD_COOLDOWN_MS) {
|
||||
return false;
|
||||
}
|
||||
sessionStorage.setItem(RELOAD_FLAG, String(now));
|
||||
const path = targetPath || pendingFullPath || window.location.pathname + window.location.search + window.location.hash;
|
||||
window.location.assign(path);
|
||||
return true;
|
||||
}
|
||||
@@ -103,8 +103,8 @@
|
||||
<div class="contract-attachment-upload-dialog__body">
|
||||
<div class="contract-attachment-upload-dialog__field">
|
||||
<span class="contract-attachment-upload-dialog__label">附件位置</span>
|
||||
<el-select model-value="合同文件" disabled style="width: 100%">
|
||||
<el-option label="合同文件" value="合同文件" />
|
||||
<el-select :model-value="attachmentLocation" disabled style="width: 100%">
|
||||
<el-option :label="attachmentLocation" :value="attachmentLocation" />
|
||||
</el-select>
|
||||
</div>
|
||||
<div class="contract-attachment-upload-dialog__field">
|
||||
@@ -152,13 +152,16 @@ import { UploadFilled } from '@element-plus/icons-vue';
|
||||
import { baseUrl } from '@/config/env';
|
||||
import { getUploadHeaders } from '@/utils/upload';
|
||||
import { downloadFileByUrl } from '@/utils/util';
|
||||
import { getDictionary as getBizDictionary } from '@/api/system/dictbiz';
|
||||
|
||||
const CONTRACT_ATTACHMENT_TYPE_OTHER = '其它文件';
|
||||
export const contractAttachmentTypeOptions = [
|
||||
export const defaultContractAttachmentTypeOptions = [
|
||||
{ label: '合同文件', value: '合同文件' },
|
||||
{ label: '双章归档文件', value: '双章归档文件' },
|
||||
{ label: '其它文件', value: CONTRACT_ATTACHMENT_TYPE_OTHER },
|
||||
];
|
||||
/** @deprecated 兼容旧引用,实际选项由业务字典 contract_attachment_type 动态加载 */
|
||||
export const contractAttachmentTypeOptions = defaultContractAttachmentTypeOptions;
|
||||
export const attachmentFileTypes = [
|
||||
'pdf',
|
||||
'bmp',
|
||||
@@ -204,13 +207,29 @@ const attachmentTypeKeywords = fileType => {
|
||||
return [fileType];
|
||||
};
|
||||
|
||||
export const resolveContractAttachmentType = (row = {}) => {
|
||||
const values = contractAttachmentTypeOptions.map(item => item.value);
|
||||
const resolveOtherAttachmentType = (options = []) => {
|
||||
const list = options.length ? options : defaultContractAttachmentTypeOptions;
|
||||
const matched = list.find(
|
||||
item =>
|
||||
String(item.value) === CONTRACT_ATTACHMENT_TYPE_OTHER ||
|
||||
String(item.label).includes('其它') ||
|
||||
String(item.label).includes('其他')
|
||||
);
|
||||
return matched?.value || list[list.length - 1]?.value || CONTRACT_ATTACHMENT_TYPE_OTHER;
|
||||
};
|
||||
|
||||
export const resolveContractAttachmentType = (
|
||||
row = {},
|
||||
options = defaultContractAttachmentTypeOptions
|
||||
) => {
|
||||
const list = options.length ? options : defaultContractAttachmentTypeOptions;
|
||||
const values = list.map(item => item.value);
|
||||
if (values.includes(row.fileType)) return row.fileType;
|
||||
const fileName = normalizeAttachmentTypeText(attachmentName(row));
|
||||
if (!fileName) return CONTRACT_ATTACHMENT_TYPE_OTHER;
|
||||
const matched = contractAttachmentTypeOptions
|
||||
.filter(item => item.value !== CONTRACT_ATTACHMENT_TYPE_OTHER)
|
||||
const otherType = resolveOtherAttachmentType(list);
|
||||
if (!fileName) return otherType;
|
||||
const matched = list
|
||||
.filter(item => item.value !== otherType)
|
||||
.flatMap(item =>
|
||||
attachmentTypeKeywords(item.value).map(keyword => ({
|
||||
value: item.value,
|
||||
@@ -220,16 +239,27 @@ export const resolveContractAttachmentType = (row = {}) => {
|
||||
.filter(item => item.keyword)
|
||||
.sort((a, b) => b.keyword.length - a.keyword.length)
|
||||
.find(item => fileName.includes(item.keyword));
|
||||
return matched?.value || CONTRACT_ATTACHMENT_TYPE_OTHER;
|
||||
return matched?.value || otherType;
|
||||
};
|
||||
|
||||
export const normalizeContractFileRows = rows =>
|
||||
export const normalizeContractFileRows = (rows, options = defaultContractAttachmentTypeOptions) =>
|
||||
(rows || []).map(row => ({
|
||||
...row,
|
||||
fileType: resolveContractAttachmentType(row),
|
||||
fileType: resolveContractAttachmentType(row, options),
|
||||
description: row.description || '',
|
||||
}));
|
||||
|
||||
const mapDictOptions = response => {
|
||||
const data = response?.data?.data || response?.data || [];
|
||||
const records = Array.isArray(data) ? data : data.records || [];
|
||||
return records
|
||||
.map(item => ({
|
||||
label: item.dictValue,
|
||||
value: item.dictKey,
|
||||
}))
|
||||
.filter(item => item.label && item.value);
|
||||
};
|
||||
|
||||
export default {
|
||||
name: 'ContractAttachmentSection',
|
||||
components: { UploadFilled },
|
||||
@@ -242,6 +272,7 @@ export default {
|
||||
lockApproved: Boolean,
|
||||
markApprovedOnUpload: Boolean,
|
||||
useUploadDialog: Boolean,
|
||||
attachmentLocation: { type: String, default: '合同文件' },
|
||||
preview: { type: Function, default: null },
|
||||
},
|
||||
emits: ['update:rows'],
|
||||
@@ -249,9 +280,9 @@ export default {
|
||||
return {
|
||||
selected: [],
|
||||
attachmentFileTypes,
|
||||
contractAttachmentTypeOptions,
|
||||
contractAttachmentTypeOptions: [...defaultContractAttachmentTypeOptions],
|
||||
uploadDialogVisible: false,
|
||||
uploadDialogType: CONTRACT_ATTACHMENT_TYPE_OTHER,
|
||||
uploadDialogType: resolveOtherAttachmentType(defaultContractAttachmentTypeOptions),
|
||||
uploadDialogFiles: [],
|
||||
uploadDialogFileList: [],
|
||||
};
|
||||
@@ -266,9 +297,32 @@ export default {
|
||||
acceptText() {
|
||||
return this.attachmentFileTypes.map(item => `.${item}`).join(',');
|
||||
},
|
||||
defaultAttachmentType() {
|
||||
return resolveOtherAttachmentType(this.contractAttachmentTypeOptions);
|
||||
},
|
||||
},
|
||||
created() {
|
||||
this.loadAttachmentTypeOptions();
|
||||
},
|
||||
methods: {
|
||||
attachmentName,
|
||||
loadAttachmentTypeOptions() {
|
||||
return getBizDictionary({ code: 'contract_attachment_type' })
|
||||
.then(res => {
|
||||
const options = mapDictOptions(res);
|
||||
if (options.length) {
|
||||
this.contractAttachmentTypeOptions = options;
|
||||
if (
|
||||
!options.some(item => String(item.value) === String(this.uploadDialogType))
|
||||
) {
|
||||
this.uploadDialogType = this.defaultAttachmentType;
|
||||
}
|
||||
}
|
||||
})
|
||||
.catch(() => {
|
||||
// 字典加载失败时保留默认选项
|
||||
});
|
||||
},
|
||||
isRowLocked(row) {
|
||||
return this.lockApproved && isAttachmentApproved(row);
|
||||
},
|
||||
@@ -286,7 +340,7 @@ export default {
|
||||
this.update([...this.rows]);
|
||||
},
|
||||
openUploadDialog() {
|
||||
this.uploadDialogType = CONTRACT_ATTACHMENT_TYPE_OTHER;
|
||||
this.uploadDialogType = this.defaultAttachmentType;
|
||||
this.uploadDialogFiles = [];
|
||||
this.uploadDialogFileList = [];
|
||||
this.uploadDialogVisible = true;
|
||||
@@ -294,7 +348,7 @@ export default {
|
||||
resetUploadDialog() {
|
||||
this.uploadDialogFiles = [];
|
||||
this.uploadDialogFileList = [];
|
||||
this.uploadDialogType = CONTRACT_ATTACHMENT_TYPE_OTHER;
|
||||
this.uploadDialogType = this.defaultAttachmentType;
|
||||
},
|
||||
beforeUpload(file) {
|
||||
const extension = String(file.name || '')
|
||||
@@ -337,10 +391,13 @@ export default {
|
||||
...this.uploadDialogFiles.filter(item => String(item.uid) !== String(file.uid)),
|
||||
normalized,
|
||||
];
|
||||
this.uploadDialogType = resolveContractAttachmentType({
|
||||
this.uploadDialogType = resolveContractAttachmentType(
|
||||
{
|
||||
...normalized,
|
||||
fileType: '',
|
||||
});
|
||||
},
|
||||
this.contractAttachmentTypeOptions
|
||||
);
|
||||
this.$message.success('上传成功');
|
||||
},
|
||||
handleDialogError() {
|
||||
@@ -359,7 +416,7 @@ export default {
|
||||
this.$message.warning('请先上传附件');
|
||||
return;
|
||||
}
|
||||
if (this.attachmentType && !this.uploadDialogType) {
|
||||
if (!this.uploadDialogType) {
|
||||
this.$message.warning('请选择附件类型');
|
||||
return;
|
||||
}
|
||||
@@ -367,7 +424,7 @@ export default {
|
||||
const uploadTime = this.$dayjs().format('YYYY-MM-DD HH:mm:ss');
|
||||
const appended = this.uploadDialogFiles.map(row => ({
|
||||
...row,
|
||||
fileType: this.attachmentType ? this.uploadDialogType : row.fileType,
|
||||
fileType: this.uploadDialogType,
|
||||
description: row.description || '',
|
||||
uploadUserName: row.uploadUserName || uploadUserName,
|
||||
uploadTime: row.uploadTime || uploadTime,
|
||||
@@ -398,10 +455,13 @@ export default {
|
||||
approved: existing?.approved || row.approved || false,
|
||||
};
|
||||
if (this.attachmentType) {
|
||||
next.fileType = resolveContractAttachmentType({
|
||||
next.fileType = resolveContractAttachmentType(
|
||||
{
|
||||
...next,
|
||||
fileType: existing?.fileType || row.fileType,
|
||||
});
|
||||
},
|
||||
this.contractAttachmentTypeOptions
|
||||
);
|
||||
}
|
||||
if (this.markApprovedOnUpload && !existing) next.approved = true;
|
||||
return next;
|
||||
@@ -441,6 +501,49 @@ export default {
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.contract-manage-form__section {
|
||||
margin: 12px 0 0;
|
||||
padding: 14px 16px 16px;
|
||||
overflow: hidden;
|
||||
background: #fff;
|
||||
border-radius: 6px;
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.04);
|
||||
|
||||
.dialog-section-title {
|
||||
margin-bottom: 20px;
|
||||
color: #303133;
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
|
||||
&::before {
|
||||
display: inline-block;
|
||||
width: 4px;
|
||||
height: 16px;
|
||||
margin-right: 8px;
|
||||
vertical-align: -2px;
|
||||
background: #409eff;
|
||||
content: '';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.contract-manage-form__attachment-head {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
margin-bottom: 16px;
|
||||
|
||||
.dialog-section-title {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.contract-manage-form__attachment-upload {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-top: 12px;
|
||||
}
|
||||
|
||||
.contract-manage-form__attachment-tip {
|
||||
margin-left: 30px;
|
||||
color: #909399;
|
||||
|
||||
@@ -1,48 +1,199 @@
|
||||
<template>
|
||||
<basic-container class="contract-change-page">
|
||||
<div class="archive-page-form__title">{{ pageTitle }}</div>
|
||||
<el-form ref="formRef" :model="form" :rules="rules" label-position="right" label-width="auto" class="contract-change-form">
|
||||
<section class="change-section contract-basic-section">
|
||||
<div class="dialog-section-title">基本信息</div>
|
||||
<div class="contract-basic-section__grid">
|
||||
<el-form-item label="变更类型" class="contract-basic-section__change-type"><el-radio-group v-model="form.changeType"><el-radio label="合同信息变更" /><el-radio label="终止合同" /></el-radio-group></el-form-item>
|
||||
<el-form-item label="合同编号"><el-input v-model="form.contractNo" disabled /></el-form-item>
|
||||
<el-form-item label="合同名称" prop="contractName"><el-input v-model="form.contractName" /></el-form-item>
|
||||
<el-form-item label="合同类型"><el-select v-model="form.contractCategory" disabled><el-option label="客户合同" value="客户合同" /><el-option label="承运商合同" value="承运商合同" /></el-select></el-form-item>
|
||||
<el-form-item label="甲方"><el-input v-model="form.partyA" disabled /></el-form-item>
|
||||
<el-form-item label="乙方"><el-input v-model="form.partyB" disabled /></el-form-item>
|
||||
<el-form-item label="所属项目"><el-input v-model="form.projectName" disabled /></el-form-item>
|
||||
<el-form-item label="变更类型" class="contract-basic-section__change-type">
|
||||
<el-radio-group v-model="form.changeType">
|
||||
<el-radio label="合同信息变更" />
|
||||
<el-radio label="终止合同" />
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
<el-form-item label="签约类型" prop="signType">
|
||||
<el-select v-model="form.signType" placeholder="请选择签约类型" clearable>
|
||||
<el-option
|
||||
v-for="item in signTypeOptions"
|
||||
:key="item.value"
|
||||
:label="item.label"
|
||||
:value="item.value"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="合同编号">
|
||||
<el-input v-model="form.contractNo" disabled />
|
||||
</el-form-item>
|
||||
<el-form-item label="合同名称" prop="contractName">
|
||||
<el-input
|
||||
v-model="form.contractName"
|
||||
maxlength="100"
|
||||
show-word-limit
|
||||
placeholder="请输入合同名称"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="合同类型">
|
||||
<el-select v-model="form.contractCategory" disabled>
|
||||
<el-option
|
||||
v-for="item in contractCategoryOptions"
|
||||
:key="item.value"
|
||||
:label="item.label"
|
||||
:value="item.value"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="项目">
|
||||
<el-input v-model="form.projectName" disabled />
|
||||
</el-form-item>
|
||||
<el-form-item label="所属组织">
|
||||
<el-input v-model="form.organizationName" disabled />
|
||||
</el-form-item>
|
||||
<el-form-item label="甲方">
|
||||
<el-input v-model="form.partyA" disabled />
|
||||
</el-form-item>
|
||||
<el-form-item label="乙方">
|
||||
<el-input v-model="form.partyB" disabled />
|
||||
</el-form-item>
|
||||
<el-form-item label="签订日期">
|
||||
<el-date-picker
|
||||
v-model="form.signDate"
|
||||
type="date"
|
||||
format="YYYY-MM-DD"
|
||||
value-format="YYYY-MM-DD"
|
||||
disabled
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="合同期限">
|
||||
<div class="contract-basic-section__date-range">
|
||||
<el-date-picker v-model="period[0]" type="date" format="YYYY-MM-DD" value-format="YYYY-MM-DD" placeholder="YYYY-MM-DD" />
|
||||
<el-date-picker
|
||||
v-model="period[0]"
|
||||
type="date"
|
||||
format="YYYY-MM-DD"
|
||||
value-format="YYYY-MM-DD"
|
||||
placeholder="YYYY-MM-DD"
|
||||
/>
|
||||
<span>至</span>
|
||||
<el-date-picker v-model="period[1]" type="date" format="YYYY-MM-DD" value-format="YYYY-MM-DD" placeholder="YYYY-MM-DD" />
|
||||
<el-date-picker
|
||||
v-model="period[1]"
|
||||
type="date"
|
||||
format="YYYY-MM-DD"
|
||||
value-format="YYYY-MM-DD"
|
||||
placeholder="YYYY-MM-DD"
|
||||
/>
|
||||
</div>
|
||||
</el-form-item>
|
||||
<el-form-item label="所属组织"><el-input v-model="form.organizationName" disabled /></el-form-item>
|
||||
<el-form-item label="签订日期"><el-date-picker v-model="form.signDate" type="date" value-format="YYYY-MM-DD" disabled /></el-form-item>
|
||||
<el-form-item label="合同格式"><el-select v-model="form.contractFormat"><el-option label="电子合同" value="电子合同" /><el-option label="纸质合同" value="纸质合同" /></el-select></el-form-item>
|
||||
<el-form-item label="结算方式"><el-input v-model="form.settlementMode" /></el-form-item>
|
||||
<el-form-item label="是否需要加盖法人章"><el-select v-model="form.legalSealFlag"><el-option label="是" :value="1" /><el-option label="否" :value="0" /></el-select></el-form-item>
|
||||
<el-form-item label="一式(份)"><el-input v-model="form.copyCount" inputmode="numeric" maxlength="9" placeholder="请输入" @input="value => positiveIntegerInput('copyCount', value)" /></el-form-item>
|
||||
<el-form-item label="回款账期"><el-input v-model="form.paymentDays" inputmode="numeric" maxlength="9" placeholder="请输入" @input="value => positiveIntegerInput('paymentDays', value)" /></el-form-item>
|
||||
<el-form-item label="合同金额"><el-input-number v-model="form.contractAmount" :min="0" :precision="2" :controls="false" placeholder="请输入" /></el-form-item>
|
||||
<el-form-item label="是否范本"><el-select v-model="form.templateFlag" placeholder="请选择"><el-option label="否" :value="0" /><el-option label="是" :value="1" /></el-select></el-form-item>
|
||||
<el-form-item label="原件合同编号"><el-input v-model="form.originalContractNo" maxlength="100" placeholder="请输入" /></el-form-item>
|
||||
<el-form-item label="是否电子章"><el-select v-model="form.electronicSealFlag" placeholder="请选择"><el-option label="否" :value="0" /><el-option label="是" :value="1" /></el-select></el-form-item>
|
||||
<el-form-item label="回款账期">
|
||||
<el-input
|
||||
v-model="form.paymentDays"
|
||||
placeholder="请输入"
|
||||
@input="value => positiveIntegerInput('paymentDays', value)"
|
||||
>
|
||||
<template #suffix>天</template>
|
||||
</el-input>
|
||||
</el-form-item>
|
||||
<el-form-item label="合同金额">
|
||||
<el-input-number
|
||||
v-model="form.contractAmount"
|
||||
:min="0"
|
||||
:precision="2"
|
||||
:controls="false"
|
||||
placeholder="请输入"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="是否范本">
|
||||
<el-select v-model="form.templateFlag" placeholder="请选择">
|
||||
<el-option label="否" :value="0" />
|
||||
<el-option label="是" :value="1" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="原件合同编号">
|
||||
<el-input v-model="form.originalContractNo" maxlength="100" placeholder="请输入" />
|
||||
</el-form-item>
|
||||
<el-form-item label="是否电子章">
|
||||
<el-select v-model="form.electronicSealFlag" placeholder="请选择">
|
||||
<el-option label="否" :value="0" />
|
||||
<el-option label="是" :value="1" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="开票周期">
|
||||
<el-input
|
||||
v-model="form.invoiceCycle"
|
||||
placeholder="请输入"
|
||||
@input="value => positiveIntegerInput('invoiceCycle', value)"
|
||||
>
|
||||
<template #suffix>天</template>
|
||||
</el-input>
|
||||
</el-form-item>
|
||||
<el-form-item label="结算币种" prop="settlementCurrency">
|
||||
<el-select v-model="form.settlementCurrency" placeholder="请选择结算币种" clearable>
|
||||
<el-option
|
||||
v-for="item in settlementCurrencyOptions"
|
||||
:key="item.value"
|
||||
:label="item.label"
|
||||
:value="item.value"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="合同格式">
|
||||
<el-select v-model="form.contractFormat" placeholder="请选择合同格式" clearable>
|
||||
<el-option
|
||||
v-for="item in contractFormatOptions"
|
||||
:key="item.value"
|
||||
:label="item.label"
|
||||
:value="item.value"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="一式">
|
||||
<el-input
|
||||
v-model="form.copyCount"
|
||||
placeholder="请输入"
|
||||
@input="value => positiveIntegerInput('copyCount', value)"
|
||||
>
|
||||
<template #suffix>份</template>
|
||||
</el-input>
|
||||
</el-form-item>
|
||||
<el-form-item label="是否需要加盖法人章">
|
||||
<el-select v-model="form.legalSealFlag" placeholder="请选择">
|
||||
<el-option label="否" :value="0" />
|
||||
<el-option label="是" :value="1" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="结算方式">
|
||||
<el-select v-model="form.settlementMode" placeholder="请选择结算方式" clearable>
|
||||
<el-option
|
||||
v-for="item in settlementModeOptions"
|
||||
:key="item.value"
|
||||
:label="item.label"
|
||||
:value="item.value"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="经办人">
|
||||
<el-input v-model="form.handlerUserName" disabled />
|
||||
</el-form-item>
|
||||
</div>
|
||||
<el-form-item label="备注" class="contract-basic-section__remark"><el-input v-model="form.remark" type="textarea" :rows="2" maxlength="2000" show-word-limit placeholder="请输入备注" /></el-form-item>
|
||||
<el-form-item label="备注" class="contract-basic-section__remark">
|
||||
<el-input
|
||||
v-model="form.remark"
|
||||
type="textarea"
|
||||
:rows="2"
|
||||
maxlength="200"
|
||||
show-word-limit
|
||||
placeholder="请输入备注"
|
||||
/>
|
||||
</el-form-item>
|
||||
</section>
|
||||
|
||||
<section class="change-section">
|
||||
<div class="section-head">
|
||||
<div class="dialog-section-title">合同文件</div>
|
||||
<el-button type="primary" :disabled="!contractFileRows.length" @click="handleContractFileBatchDownload">批量下载</el-button>
|
||||
</div>
|
||||
<el-table :data="contractFileRows" border class="change-table" @selection-change="selectedContractFiles = $event"><el-table-column type="selection" width="55" /><el-table-column type="index" label="序号" width="70" /><el-table-column label="文件名" min-width="240"><template #default="{ row }"><el-link type="primary" @click="previewAttachment(row, contractFileRows)">{{ row.originalName || row.name }}</el-link></template></el-table-column><el-table-column label="文件大小" width="120"><template #default="{ row }">{{ formatFileSize(row.size) }}</template></el-table-column><el-table-column prop="uploadUserName" label="上传人" width="140" /><el-table-column prop="uploadTime" label="上传时间" width="170" /><el-table-column label="操作" width="100"><template #default="{ $index }"><el-link type="danger" @click="removeContractFile($index)">删除</el-link></template></el-table-column></el-table>
|
||||
<div class="attachment-upload">
|
||||
<vehicle-attachment-upload v-model="contractFileRows" :readonly="false" :file-types="attachmentFileTypes" :max-size="50" :show-file-list="false" button-text="上传附件" @change="handleContractFileChange" />
|
||||
</div>
|
||||
</section>
|
||||
<contract-attachment-section
|
||||
title="合同文件"
|
||||
description
|
||||
attachment-type
|
||||
use-upload-dialog
|
||||
:rows="contractFileRows"
|
||||
:preview="previewAttachment"
|
||||
@update:rows="contractFileRows = $event"
|
||||
/>
|
||||
|
||||
<section class="change-section">
|
||||
<div class="section-head"><div class="dialog-section-title">计费信息</div><el-button type="primary" plain @click="addPlan">添加</el-button></div>
|
||||
@@ -101,26 +252,41 @@
|
||||
|
||||
<billing-plan-editor v-model="planDialogVisible" :value="planEditor" :index="planEditorIndex" @save="savePlan" />
|
||||
|
||||
<section class="change-section attachment-section">
|
||||
<div class="section-head"><div class="dialog-section-title">其它附件</div><el-button type="primary" plain>批量下载</el-button></div>
|
||||
<el-table :data="attachments" border class="change-table"><el-table-column type="index" label="序号" width="70" /><el-table-column label="文件名" min-width="240"><template #default="{ row }"><el-link type="primary" @click="previewAttachment(row, attachments)">{{ row.originalName || row.name }}</el-link></template></el-table-column><el-table-column prop="description" label="附件描述" min-width="240" /><el-table-column prop="size" label="文件大小" width="120" /><el-table-column prop="uploadUserName" label="上传人" width="140" /><el-table-column prop="uploadTime" label="上传时间" width="180" /><el-table-column label="操作" width="100"><template #default="{ $index }"><el-link type="danger" @click="attachments.splice($index, 1)">删除</el-link></template></el-table-column></el-table>
|
||||
<el-upload action="#" :auto-upload="false" multiple :show-file-list="false" @change="handleAttachment"><el-button type="primary" plain icon="el-icon-upload">上传附件</el-button></el-upload>
|
||||
</section>
|
||||
<contract-attachment-section
|
||||
title="其它附件"
|
||||
description
|
||||
attachment-type
|
||||
use-upload-dialog
|
||||
attachment-location="其它附件"
|
||||
:rows="attachments"
|
||||
:preview="previewAttachment"
|
||||
@update:rows="attachments = $event"
|
||||
/>
|
||||
|
||||
<section class="change-section change-reason-section">
|
||||
<div class="dialog-section-title">变更内容</div>
|
||||
<el-form-item prop="changeContent"><el-input v-model="form.changeContent" type="textarea" :rows="3" maxlength="2000" show-word-limit placeholder="请输入变更内容" /></el-form-item>
|
||||
<div class="dialog-section-title is-required">变更原因</div>
|
||||
<el-form-item prop="changeReason"><el-input v-model="form.changeReason" type="textarea" :rows="3" maxlength="500" show-word-limit placeholder="请输入变更原因" /></el-form-item>
|
||||
<div class="dialog-section-title">变更材料</div>
|
||||
<el-table :data="changeMaterials" border class="change-table">
|
||||
<el-table-column type="index" label="序号" width="70" />
|
||||
<el-table-column label="文件名" min-width="240"><template #default="{ row }">{{ row.originalName || row.name }}</template></el-table-column>
|
||||
<el-table-column label="文件大小" width="120"><template #default="{ row }">{{ formatFileSize(row.size) }}</template></el-table-column>
|
||||
<el-table-column label="操作" width="100"><template #default="{ $index }"><el-link type="danger" @click="changeMaterials.splice($index, 1)">删除</el-link></template></el-table-column>
|
||||
</el-table>
|
||||
<div class="attachment-upload"><vehicle-attachment-upload v-model="changeMaterials" :readonly="false" :file-types="attachmentFileTypes" :max-size="50" :show-file-list="false" button-text="上传变更材料" /></div>
|
||||
<div class="dialog-section-title">变更原因</div>
|
||||
<el-form-item prop="changeReason" class="change-reason-section__input">
|
||||
<el-input
|
||||
v-model="form.changeReason"
|
||||
type="textarea"
|
||||
:rows="3"
|
||||
maxlength="500"
|
||||
show-word-limit
|
||||
placeholder="请输入变更原因"
|
||||
/>
|
||||
</el-form-item>
|
||||
</section>
|
||||
|
||||
<contract-attachment-section
|
||||
title="变更材料"
|
||||
description
|
||||
attachment-type
|
||||
use-upload-dialog
|
||||
attachment-location="变更材料"
|
||||
:rows="changeMaterials"
|
||||
:preview="previewAttachment"
|
||||
@update:rows="changeMaterials = $event"
|
||||
/>
|
||||
<div class="page-footer">
|
||||
<el-button @click="$router.back()">取消</el-button>
|
||||
<el-button type="primary" @click="submit">提交</el-button>
|
||||
@@ -137,6 +303,15 @@
|
||||
<script>
|
||||
import * as api from '@/api/business/contract-manage';
|
||||
import BillingPlanEditor from './components/billing-plan-editor.vue';
|
||||
import ContractAttachmentSection, {
|
||||
normalizeContractFileRows,
|
||||
} from './components/contract-attachment-section.vue';
|
||||
import { getDictionary as getSystemDictionary } from '@/api/system/dict';
|
||||
import { getDictionary as getBizDictionary } from '@/api/system/dictbiz';
|
||||
import {
|
||||
contractCategoryOptions as defaultContractCategoryOptions,
|
||||
signTypeOptions as defaultSignTypeOptions,
|
||||
} from '@/option/business/common';
|
||||
import { ElImageViewer } from 'element-plus';
|
||||
import { OpenFileViewer } from '@open-file-viewer/vue';
|
||||
import { fallbackPlugin, imagePlugin, officePlugin, pdfPlugin, textPlugin } from '@open-file-viewer/core';
|
||||
@@ -150,6 +325,17 @@ const viewerPlugins = [
|
||||
textPlugin(),
|
||||
fallbackPlugin(),
|
||||
];
|
||||
const defaultSettlementModeOptions = [
|
||||
{ label: '现结', value: '现结' },
|
||||
{ label: '日结', value: '日结' },
|
||||
{ label: '周结', value: '周结' },
|
||||
{ label: '月结', value: '月结' },
|
||||
{ label: '按固定天数周期', value: '按固定天数周期' },
|
||||
];
|
||||
const defaultContractFormatOptions = [
|
||||
{ label: '电子合同', value: '电子合同' },
|
||||
{ label: '纸质合同', value: '纸质合同' },
|
||||
];
|
||||
const normalizeOptionalPositiveInteger = value => {
|
||||
if (value === undefined || value === null || value === '') return null;
|
||||
const number = Number(value);
|
||||
@@ -169,58 +355,264 @@ const normalizeCustomPeriods = (periods = []) =>
|
||||
}));
|
||||
|
||||
export default {
|
||||
components: { BillingPlanEditor, ElImageViewer, OpenFileViewer },
|
||||
data() { return { form: {}, period: [], plans: [], attachments: [], contractFiles: [], contractFileRows: [], selectedContractFiles: [], paymentRatioRows: [], settlementConfigTab: 'pre', changeMaterials: [], imagePreviewVisible: false, imagePreviewUrls: [], imagePreviewIndex: 0, documentPreviewVisible: false, previewFile: {}, viewerPlugins, viewerToolbar: { zoom: true, rotate: true, download: true, fullscreen: true }, feeGenerationMode: 'system', settlementRule: { autoGenerate: '', billStartDate: '', settlementType: '', billCycleType: '', billCutoffDay: '', cycleDays: '', customPeriods: [] }, preSettlementConfig: {}, formalSettlementConfig: {}, settlementTypeOptions: ['月结','日结','周结','半月结','固定天数周期结算'], billCycleTypeOptions: ['固定截单日','自然月','自定义多周期'], billCutoffDayOptions: Array.from({ length: 31 }, (_, index) => ({ label: `${index + 1}日`, value: index + 1 })), cycleDayOptions: [7,15,30,60].map(value => ({ label: `${value}天`, value })), planDialogVisible: false, planEditor: { planName: '', defaultPlan: false, remark: '', rules: [] }, planEditorIndex: -1, billingElements: ['按重量','按体积','按车辆','按里程','按吨·公里','固定金额(整单一口价)','按数量'], attachmentFileTypes: ['pdf','bmp','jpeg','png','jpg','doc','docx','ppt','pptx','xlsx','xls','eml','msg','zip'], rules: { contractName: [{ required: true, message: '请输入合同名称', trigger: 'blur' }], changeReason: [{ required: true, message: '请输入变更原因', trigger: 'blur' }] } }; },
|
||||
computed: { showSettlementBillCycleType() { return this.settlementRule.settlementType === '月结'; }, showSettlementBillCutoffDay() { return this.showSettlementBillCycleType && this.settlementRule.billCycleType === '固定截单日'; }, showSettlementCustomPeriods() { return this.showSettlementBillCycleType && this.settlementRule.billCycleType === '自定义多周期'; }, showSettlementCycleDays() { return this.settlementRule.settlementType === '固定天数周期结算'; } },
|
||||
mounted() { this.load(); },
|
||||
watch: { settlementConfigTab(tab, oldTab) { if (tab === oldTab) return; if (oldTab === 'pre') this.preSettlementConfig = { ...this.settlementRule }; else this.formalSettlementConfig = { ...this.settlementRule }; this.settlementRule = { ...(tab === 'pre' ? this.preSettlementConfig : this.formalSettlementConfig) }; } },
|
||||
components: { BillingPlanEditor, ContractAttachmentSection, ElImageViewer, OpenFileViewer },
|
||||
data() {
|
||||
return {
|
||||
form: {},
|
||||
period: [],
|
||||
plans: [],
|
||||
attachments: [],
|
||||
contractFiles: [],
|
||||
contractFileRows: [],
|
||||
paymentRatioRows: [],
|
||||
settlementConfigTab: 'pre',
|
||||
changeMaterials: [],
|
||||
imagePreviewVisible: false,
|
||||
imagePreviewUrls: [],
|
||||
imagePreviewIndex: 0,
|
||||
documentPreviewVisible: false,
|
||||
previewFile: {},
|
||||
viewerPlugins,
|
||||
viewerToolbar: { zoom: true, rotate: true, download: true, fullscreen: true },
|
||||
feeGenerationMode: 'system',
|
||||
settlementRule: {
|
||||
autoGenerate: '',
|
||||
billStartDate: '',
|
||||
settlementType: '',
|
||||
billCycleType: '',
|
||||
billCutoffDay: '',
|
||||
cycleDays: '',
|
||||
customPeriods: [],
|
||||
},
|
||||
preSettlementConfig: {},
|
||||
formalSettlementConfig: {},
|
||||
settlementTypeOptions: ['月结', '日结', '周结', '半月结', '固定天数周期结算'],
|
||||
billCycleTypeOptions: ['固定截单日', '自然月', '自定义多周期'],
|
||||
billCutoffDayOptions: Array.from({ length: 31 }, (_, index) => ({
|
||||
label: `${index + 1}日`,
|
||||
value: index + 1,
|
||||
})),
|
||||
cycleDayOptions: [7, 15, 30, 60].map(value => ({ label: `${value}天`, value })),
|
||||
signTypeOptions: defaultSignTypeOptions,
|
||||
contractCategoryDictOptions: [],
|
||||
settlementCurrencyOptions: [],
|
||||
settlementModeDictOptions: [],
|
||||
contractFormatDictOptions: [],
|
||||
planDialogVisible: false,
|
||||
planEditor: { planName: '', defaultPlan: false, remark: '', rules: [] },
|
||||
planEditorIndex: -1,
|
||||
billingElements: [
|
||||
'按重量',
|
||||
'按体积',
|
||||
'按车辆',
|
||||
'按里程',
|
||||
'按吨·公里',
|
||||
'固定金额(整单一口价)',
|
||||
'按数量',
|
||||
],
|
||||
rules: {
|
||||
contractName: [{ required: true, message: '请输入合同名称', trigger: 'blur' }],
|
||||
changeReason: [{ max: 500, message: '变更原因不能超过500个字符', trigger: 'blur' }],
|
||||
},
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
pageTitle() {
|
||||
return this.$route.query.name || '合同变更';
|
||||
},
|
||||
contractCategoryOptions() {
|
||||
return this.contractCategoryDictOptions.length
|
||||
? this.contractCategoryDictOptions
|
||||
: defaultContractCategoryOptions;
|
||||
},
|
||||
settlementModeOptions() {
|
||||
return this.settlementModeDictOptions.length
|
||||
? this.settlementModeDictOptions
|
||||
: defaultSettlementModeOptions;
|
||||
},
|
||||
contractFormatOptions() {
|
||||
return this.contractFormatDictOptions.length
|
||||
? this.contractFormatDictOptions
|
||||
: defaultContractFormatOptions;
|
||||
},
|
||||
showSettlementBillCycleType() {
|
||||
return this.settlementRule.settlementType === '月结';
|
||||
},
|
||||
showSettlementBillCutoffDay() {
|
||||
return this.showSettlementBillCycleType && this.settlementRule.billCycleType === '固定截单日';
|
||||
},
|
||||
showSettlementCustomPeriods() {
|
||||
return this.showSettlementBillCycleType && this.settlementRule.billCycleType === '自定义多周期';
|
||||
},
|
||||
showSettlementCycleDays() {
|
||||
return this.settlementRule.settlementType === '固定天数周期结算';
|
||||
},
|
||||
},
|
||||
mounted() {
|
||||
this.loadDictionaries();
|
||||
this.load();
|
||||
},
|
||||
watch: {
|
||||
settlementConfigTab(tab, oldTab) {
|
||||
if (tab === oldTab) return;
|
||||
if (oldTab === 'pre') this.preSettlementConfig = { ...this.settlementRule };
|
||||
else this.formalSettlementConfig = { ...this.settlementRule };
|
||||
this.settlementRule = {
|
||||
...(tab === 'pre' ? this.preSettlementConfig : this.formalSettlementConfig),
|
||||
};
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
async load() { const id = this.$route.query.id; if (!id) return; const res = await api.getDetail(id); const data = res.data?.data || res.data || {}; this.form = { ...data, changeContent: '', changeReason: '', changeAttachmentsJson: '', copyCount: normalizeOptionalPositiveInteger(data.copyCount), paymentDays: normalizeOptionalPositiveInteger(data.paymentDays), contractAmount: normalizeOptionalAmount(data.contractAmount), templateFlag: data.templateFlag ?? 0, electronicSealFlag: data.electronicSealFlag ?? 0, changeType: '合同信息变更' }; this.changeMaterials = []; this.period = data.startDate && data.endDate ? [data.startDate, data.endDate] : []; this.plans = this.parse(data.billingPlanJson); this.attachments = this.mergeChangeAttachments(this.parse(data.attachmentsJson), this.parse(data.changeAttachmentsJson)); this.contractFileRows = this.parse(data.contractFileJson); const rules = this.parseObject(data.settlementRuleJson); const pre = this.parseObject(data.preSettlementConfigJson); const formal = this.parseObject(data.formalSettlementConfigJson); const legacy = Object.keys(rules).some(key => !['preSettlementConfig', 'formalSettlementConfig'].includes(key)) ? rules : {}; const normalizeRule = rule => { const next = { autoGenerate: '', billStartDate: '', settlementType: '', billCycleType: '', billCutoffDay: '', cycleDays: '', customPeriods: [], ...(rule || {}) }; if (next.settlementType === '月结' && next.billCycleType === '自定义多周期') next.customPeriods = normalizeCustomPeriods(Array.isArray(next.customPeriods) ? next.customPeriods : []); else next.customPeriods = []; return next; }; this.preSettlementConfig = normalizeRule(rules.preSettlementConfig || (Object.keys(pre).length ? pre : legacy)); this.formalSettlementConfig = normalizeRule(rules.formalSettlementConfig || (Object.keys(formal).length ? formal : legacy)); this.settlementRule = { ...this.preSettlementConfig }; this.feeGenerationMode = data.feeGenerationMode || (Number(data.billingEnabled) === 0 ? 'manual' : 'system'); this.paymentRatioRows = this.parse(data.paymentRatioJson); },
|
||||
parse(value) { try { const result = JSON.parse(value || '[]'); return Array.isArray(result) ? result : []; } catch { return []; } },
|
||||
loadDictionaries() {
|
||||
Promise.all([
|
||||
getSystemDictionary({ code: 'currency_type' }),
|
||||
getBizDictionary({ code: 'settle_method' }),
|
||||
getBizDictionary({ code: 'contractFormat' }),
|
||||
getBizDictionary({ code: 'contractCategory' }),
|
||||
]).then(([currencyRes, methodRes, formatRes, categoryRes]) => {
|
||||
const records = response => {
|
||||
const data = response?.data?.data || response?.data || [];
|
||||
return Array.isArray(data) ? data : data.records || [];
|
||||
};
|
||||
this.settlementCurrencyOptions = records(currencyRes).map(item => ({
|
||||
label: item.dictValue,
|
||||
value: item.dictKey,
|
||||
}));
|
||||
this.settlementModeDictOptions = records(methodRes).map(item => ({
|
||||
label: item.dictValue,
|
||||
value: item.dictKey,
|
||||
}));
|
||||
this.contractFormatDictOptions = records(formatRes).map(item => ({
|
||||
label: item.dictValue,
|
||||
value: item.dictKey,
|
||||
}));
|
||||
this.contractCategoryDictOptions = records(categoryRes).map(item => ({
|
||||
label: item.dictValue,
|
||||
value: item.dictKey,
|
||||
}));
|
||||
});
|
||||
},
|
||||
async load() {
|
||||
const id = this.$route.query.id;
|
||||
if (!id) return;
|
||||
const res = await api.getDetail(id);
|
||||
const data = res.data?.data || res.data || {};
|
||||
this.form = {
|
||||
...data,
|
||||
changeContent: '',
|
||||
changeReason: '',
|
||||
changeAttachmentsJson: '',
|
||||
copyCount: normalizeOptionalPositiveInteger(data.copyCount),
|
||||
invoiceCycle: normalizeOptionalPositiveInteger(data.invoiceCycle),
|
||||
paymentDays: normalizeOptionalPositiveInteger(data.paymentDays),
|
||||
contractAmount: normalizeOptionalAmount(data.contractAmount),
|
||||
templateFlag: data.templateFlag ?? 0,
|
||||
electronicSealFlag: data.electronicSealFlag ?? 0,
|
||||
archiveStatus: data.archiveStatus || '未归档',
|
||||
changeType: '合同信息变更',
|
||||
};
|
||||
this.changeMaterials = [];
|
||||
this.period = data.startDate && data.endDate ? [data.startDate, data.endDate] : [];
|
||||
this.plans = this.parse(data.billingPlanJson);
|
||||
this.attachments = this.mergeChangeAttachments(
|
||||
this.parse(data.attachmentsJson),
|
||||
this.parse(data.changeAttachmentsJson)
|
||||
);
|
||||
this.contractFileRows = normalizeContractFileRows(this.parse(data.contractFileJson));
|
||||
const rules = this.parseObject(data.settlementRuleJson);
|
||||
const pre = this.parseObject(data.preSettlementConfigJson);
|
||||
const formal = this.parseObject(data.formalSettlementConfigJson);
|
||||
const legacy = Object.keys(rules).some(
|
||||
key => !['preSettlementConfig', 'formalSettlementConfig'].includes(key)
|
||||
)
|
||||
? rules
|
||||
: {};
|
||||
const normalizeRule = rule => {
|
||||
const next = {
|
||||
autoGenerate: '',
|
||||
billStartDate: '',
|
||||
settlementType: '',
|
||||
billCycleType: '',
|
||||
billCutoffDay: '',
|
||||
cycleDays: '',
|
||||
customPeriods: [],
|
||||
...(rule || {}),
|
||||
};
|
||||
if (next.settlementType === '月结' && next.billCycleType === '自定义多周期') {
|
||||
next.customPeriods = normalizeCustomPeriods(
|
||||
Array.isArray(next.customPeriods) ? next.customPeriods : []
|
||||
);
|
||||
} else {
|
||||
next.customPeriods = [];
|
||||
}
|
||||
return next;
|
||||
};
|
||||
this.preSettlementConfig = normalizeRule(
|
||||
rules.preSettlementConfig || (Object.keys(pre).length ? pre : legacy)
|
||||
);
|
||||
this.formalSettlementConfig = normalizeRule(
|
||||
rules.formalSettlementConfig || (Object.keys(formal).length ? formal : legacy)
|
||||
);
|
||||
this.settlementRule = { ...this.preSettlementConfig };
|
||||
this.feeGenerationMode =
|
||||
data.feeGenerationMode || (Number(data.billingEnabled) === 0 ? 'manual' : 'system');
|
||||
this.paymentRatioRows = this.parse(data.paymentRatioJson);
|
||||
},
|
||||
parse(value) {
|
||||
try {
|
||||
const result = JSON.parse(value || '[]');
|
||||
return Array.isArray(result) ? result : [];
|
||||
} catch {
|
||||
return [];
|
||||
}
|
||||
},
|
||||
// 审核通过后上一次的变更材料归集到「其它附件」,按文件地址/文件名去重,避免重复展示
|
||||
mergeChangeAttachments(attachments = [], changeAttachments = []) {
|
||||
const key = item => this.attachmentUrl(item) || this.attachmentName(item);
|
||||
const exists = new Set(attachments.map(key));
|
||||
const merged = changeAttachments
|
||||
.filter(item => !exists.has(key(item)))
|
||||
.map(item => ({ ...item, size: /^\d+$/.test(String(item.size ?? '')) ? this.formatFileSize(item.size) : item.size }));
|
||||
.map(item => ({
|
||||
...item,
|
||||
size: /^\d+$/.test(String(item.size ?? '')) ? this.formatFileSize(item.size) : item.size,
|
||||
}));
|
||||
return [...attachments, ...merged];
|
||||
},
|
||||
parseObject(value) { try { return { autoGenerate: '', billStartDate: '', settlementType: '', billCycleType: '', billCutoffDay: '', cycleDays: '', customPeriods: [], ...(JSON.parse(value || '{}') || {}) }; } catch { return { autoGenerate: '', billStartDate: '', settlementType: '', billCycleType: '', billCutoffDay: '', cycleDays: '', customPeriods: [] }; } },
|
||||
positiveIntegerInput(prop, value) { this.form[prop] = String(value ?? '').replace(/\D/g, '').replace(/^0+/, ''); },
|
||||
parseObject(value) {
|
||||
try {
|
||||
return {
|
||||
autoGenerate: '',
|
||||
billStartDate: '',
|
||||
settlementType: '',
|
||||
billCycleType: '',
|
||||
billCutoffDay: '',
|
||||
cycleDays: '',
|
||||
customPeriods: [],
|
||||
...(JSON.parse(value || '{}') || {}),
|
||||
};
|
||||
} catch {
|
||||
return {
|
||||
autoGenerate: '',
|
||||
billStartDate: '',
|
||||
settlementType: '',
|
||||
billCycleType: '',
|
||||
billCutoffDay: '',
|
||||
cycleDays: '',
|
||||
customPeriods: [],
|
||||
};
|
||||
}
|
||||
},
|
||||
positiveIntegerInput(prop, value) {
|
||||
this.form[prop] = String(value ?? '')
|
||||
.replace(/\D/g, '')
|
||||
.replace(/^0+/, '');
|
||||
},
|
||||
addPlan() { this.planEditorIndex = -1; this.planEditor = { planName: `计费方案${this.plans.length + 1}`, defaultPlan: !this.plans.length, remark: '', rules: [{}] }; this.planDialogVisible = true; },
|
||||
editPlan(row, index) { this.planEditorIndex = index; this.planEditor = JSON.parse(JSON.stringify(row)); this.planDialogVisible = true; },
|
||||
toggleDefaultPlan(value) { if (value) this.plans.forEach(item => { item.defaultPlan = false; }); },
|
||||
savePlan(value, index) { if (index < 0) this.plans.push(value); else this.plans.splice(index, 1, value); if (value.defaultPlan) this.plans.forEach((item, current) => { if (current !== (index < 0 ? this.plans.length - 1 : index)) item.defaultPlan = false; }); },
|
||||
handleSettlementTypeChange(value) { if (value !== '月结') { this.settlementRule.billCycleType = ''; this.settlementRule.billCutoffDay = ''; this.settlementRule.customPeriods = []; } else if (this.settlementRule.billCycleType === '自定义多周期') this.settlementRule.customPeriods = normalizeCustomPeriods(this.settlementRule.customPeriods || []); else this.settlementRule.customPeriods = []; if (value !== '固定天数周期结算') this.settlementRule.cycleDays = ''; },
|
||||
handleCycleTypeChange(value) { if (value !== '固定截单日') this.settlementRule.billCutoffDay = ''; if (value === '自定义多周期') this.settlementRule.customPeriods = normalizeCustomPeriods(this.settlementRule.customPeriods || []); else this.settlementRule.customPeriods = []; },
|
||||
currentUploadUserName() {
|
||||
const userInfo = this.$store.getters.userInfo || {};
|
||||
return userInfo.realName || userInfo.userName || '';
|
||||
},
|
||||
handleAttachment(event) {
|
||||
const raw = event.raw;
|
||||
if (!raw) return;
|
||||
if (raw.size > 50 * 1024 * 1024) {
|
||||
this.$message.warning('单个文件大小不能超过50M');
|
||||
return;
|
||||
}
|
||||
this.attachments.push({
|
||||
name: raw.name,
|
||||
size: `${Math.ceil(raw.size / 1024)}KB`,
|
||||
uploadUserName: this.currentUploadUserName(),
|
||||
uploadTime: this.$dayjs().format('YYYY-MM-DD HH:mm:ss'),
|
||||
});
|
||||
},
|
||||
handleContractFileChange(list) {
|
||||
const uploadUserName = this.currentUploadUserName();
|
||||
const uploadTime = this.$dayjs().format('YYYY-MM-DD HH:mm:ss');
|
||||
this.contractFileRows = (list || []).map(item => ({
|
||||
...item,
|
||||
uploadUserName: item.uploadUserName || uploadUserName,
|
||||
uploadTime: item.uploadTime || uploadTime,
|
||||
}));
|
||||
},
|
||||
attachmentUrl(row = {}) { return row.url || row.link || row.fileUrl || row.downloadUrl || row.domain || ''; },
|
||||
attachmentName(row = {}) { return row.originalName || row.name || row.fileName || '附件'; },
|
||||
attachmentExtension(row = {}) { const source = String(this.attachmentName(row) || this.attachmentUrl(row)).split('?')[0]; const index = source.lastIndexOf('.'); return index > -1 ? source.slice(index + 1).toLowerCase() : ''; },
|
||||
@@ -228,8 +620,6 @@ export default {
|
||||
previewAttachment(row, rows = this.attachments) { const url = this.attachmentUrl(row); if (!url) { this.$message.warning('附件地址为空,无法预览'); return; } if (this.isAttachmentImage(row)) { this.imagePreviewUrls = (rows || []).filter(item => this.isAttachmentImage(item) && this.attachmentUrl(item)).map(item => this.attachmentUrl(item)); this.imagePreviewIndex = Math.max(this.imagePreviewUrls.indexOf(url), 0); this.imagePreviewVisible = true; return; } this.previewFile = { name: this.attachmentName(row), url, mimeType: row.mimeType || row.contentType || '' }; this.documentPreviewVisible = true; },
|
||||
handlePreviewUnsupported() { this.$message.warning('当前文件暂不支持在线预览'); },
|
||||
handlePreviewError() { this.$message.error('附件预览失败'); },
|
||||
removeContractFile(index) { this.contractFileRows.splice(index, 1); },
|
||||
handleContractFileBatchDownload() { (this.selectedContractFiles.length ? this.selectedContractFiles : this.contractFileRows).forEach(row => { if (row.url) window.open(row.url, '_blank'); }); },
|
||||
formatFileSize(value) { const size = Number(value || 0); return size > 1024 * 1024 ? `${(size / 1024 / 1024).toFixed(2)}MB` : `${Math.max(1, Math.ceil(size / 1024))}KB`; },
|
||||
addPaymentRatioRow() { this.paymentRatioRows.push({ paymentTerm: `第${this.paymentRatioRows.length + 1}笔`, ratioLimit: '', remark: '' }); },
|
||||
customPeriodEndDayOptions(row) { const startDay = Number(row?.startDay); if (!Number.isFinite(startDay) || startDay < 1) return this.billCutoffDayOptions; return Array.from({ length: 31 - startDay + 1 }, (_, index) => ({ label: `${startDay + index}日`, value: startDay + index })); },
|
||||
@@ -239,7 +629,7 @@ export default {
|
||||
removeCustomPeriodRow(index) { if (index <= 0) return; const periods = [...(this.settlementRule.customPeriods || [])]; periods.splice(index, 1); this.settlementRule.customPeriods = normalizeCustomPeriods(periods); },
|
||||
validateCustomPeriods(periods = [], label) { const rows = normalizeCustomPeriods(periods); if (!rows.length) { this.$message.warning(`${label}:请至少配置一段自定义周期`); return false; } for (let index = 0; index < rows.length; index += 1) { const row = rows[index]; const startDay = Number(row.startDay); const endDay = Number(row.endDay); if (!Number.isFinite(startDay) || startDay < 1 || startDay > 31) { this.$message.warning(`${label}:请选择第${index + 1}行运单区间开始日`); return false; } if (!Number.isFinite(endDay) || endDay < 1 || endDay > 31) { this.$message.warning(`${label}:请选择第${index + 1}行运单区间结束日`); return false; } if (endDay < startDay) { this.$message.warning(`${label}:第${index + 1}行结束日不能早于开始日`); return false; } if (index > 0 && startDay !== Number(rows[index - 1].endDay) + 1) { this.$message.warning(`${label}:自定义多周期区间必须连续,不允许重叠或存在日期缺口`); return false; } } return true; },
|
||||
validateSettlementRule(rule, label) { if (Number(rule.autoGenerate) !== 1) return true; if (!rule.billStartDate || !rule.settlementType) { this.$message.warning(`${label}:请完整填写账单起始日期和结算类型`); return false; } if (rule.settlementType === '月结' && !rule.billCycleType) { this.$message.warning(`${label}:请选择结算周期`); return false; } if (rule.settlementType === '月结' && rule.billCycleType === '固定截单日' && !rule.billCutoffDay) { this.$message.warning(`${label}:请选择账单截单日`); return false; } if (rule.settlementType === '月结' && rule.billCycleType === '自定义多周期') return this.validateCustomPeriods(rule.customPeriods, label); if (rule.settlementType === '固定天数周期结算' && !rule.cycleDays) { this.$message.warning(`${label}:请选择周期天数`); return false; } return true; },
|
||||
async submit() { await this.$refs.formRef.validate(); const total = this.paymentRatioRows.reduce((sum, row) => sum + Number(row.ratioLimit || 0), 0); if (this.paymentRatioRows.length && Math.abs(total - 100) > 0.0001) { this.$message.warning('付款比例上限合计必须等于100%'); return; } if (this.settlementConfigTab === 'pre') this.preSettlementConfig = { ...this.settlementRule }; else this.formalSettlementConfig = { ...this.settlementRule }; if (!this.validateSettlementRule(this.preSettlementConfig, '预结算配置') || !this.validateSettlementRule(this.formalSettlementConfig, '正式结算配置')) return; const settlementRule = { preSettlementConfig: this.preSettlementConfig, formalSettlementConfig: this.formalSettlementConfig }; await api.submitChange({ ...this.form, settlementCurrency: String(this.form.settlementCurrency || '').trim() || 'RMB', copyCount: normalizeOptionalPositiveInteger(this.form.copyCount), paymentDays: normalizeOptionalPositiveInteger(this.form.paymentDays), contractAmount: normalizeOptionalAmount(this.form.contractAmount), startDate: this.period[0], endDate: this.period[1], feeGenerationMode: this.feeGenerationMode, billingEnabled: this.feeGenerationMode === 'system' ? 1 : 0, billingPlanJson: JSON.stringify(this.plans), settlementRuleJson: JSON.stringify(settlementRule), preSettlementConfigJson: JSON.stringify(this.preSettlementConfig), formalSettlementConfigJson: JSON.stringify(this.formalSettlementConfig), paymentRatioJson: JSON.stringify(this.paymentRatioRows), contractFileJson: JSON.stringify(this.contractFileRows), attachmentsJson: JSON.stringify(this.attachments), changeContent: this.form.changeContent, changeReason: this.form.changeReason, changeAttachmentsJson: JSON.stringify(this.changeMaterials) }); this.$message.success('变更已提交'); this.$router.back(); },
|
||||
async submit() { await this.$refs.formRef.validate(); const total = this.paymentRatioRows.reduce((sum, row) => sum + Number(row.ratioLimit || 0), 0); if (this.paymentRatioRows.length && Math.abs(total - 100) > 0.0001) { this.$message.warning('付款比例上限合计必须等于100%'); return; } if (this.settlementConfigTab === 'pre') this.preSettlementConfig = { ...this.settlementRule }; else this.formalSettlementConfig = { ...this.settlementRule }; if (!this.validateSettlementRule(this.preSettlementConfig, '预结算配置') || !this.validateSettlementRule(this.formalSettlementConfig, '正式结算配置')) return; const settlementRule = { preSettlementConfig: this.preSettlementConfig, formalSettlementConfig: this.formalSettlementConfig }; await api.submitChange({ ...this.form, settlementCurrency: String(this.form.settlementCurrency || '').trim() || 'RMB', copyCount: normalizeOptionalPositiveInteger(this.form.copyCount), invoiceCycle: normalizeOptionalPositiveInteger(this.form.invoiceCycle), paymentDays: normalizeOptionalPositiveInteger(this.form.paymentDays), contractAmount: normalizeOptionalAmount(this.form.contractAmount), startDate: this.period[0], endDate: this.period[1], feeGenerationMode: this.feeGenerationMode, billingEnabled: this.feeGenerationMode === 'system' ? 1 : 0, billingPlanJson: JSON.stringify(this.plans), settlementRuleJson: JSON.stringify(settlementRule), preSettlementConfigJson: JSON.stringify(this.preSettlementConfig), formalSettlementConfigJson: JSON.stringify(this.formalSettlementConfig), paymentRatioJson: JSON.stringify(this.paymentRatioRows), contractFileJson: JSON.stringify(this.contractFileRows), attachmentsJson: JSON.stringify(this.attachments), changeContent: this.form.changeContent, changeReason: this.form.changeReason, changeAttachmentsJson: JSON.stringify(this.changeMaterials) }); this.$message.success('变更已提交'); this.$router.back(); },
|
||||
},
|
||||
};
|
||||
</script>
|
||||
@@ -265,7 +655,6 @@ export default {
|
||||
.contract-basic-section :deep(.el-date-editor) { width: 360px; max-width: 100%; }
|
||||
.dialog-section-title { margin-bottom: 18px; font-size: 16px; font-weight: 600; }
|
||||
.dialog-section-title::before { display: inline-block; width: 4px; height: 16px; margin-right: 8px; vertical-align: -2px; background: #409eff; content: ''; }
|
||||
.dialog-section-title.is-required::after { margin-left: 4px; color: #f56c6c; content: '*'; }
|
||||
.section-head { display: flex; align-items: center; justify-content: space-between; }
|
||||
.change-table { margin: 16px 0; }
|
||||
.ratio-tip { margin-bottom: 8px; color: #f56c6c; }
|
||||
@@ -279,6 +668,18 @@ export default {
|
||||
.settlement-switch-tip { margin: 0 32px 0 8px; color: #a8abb2; cursor: help; font-size: 14px; }
|
||||
.settlement-form { display: grid; grid-template-columns: repeat(3, minmax(240px, 1fr)); gap: 8px 28px; }
|
||||
.change-reason-section { margin: 20px 0; }
|
||||
.change-reason-section__input {
|
||||
width: 100%;
|
||||
:deep(.el-textarea__inner) {
|
||||
border-color: #f56c6c;
|
||||
box-shadow: 0 0 0 1px #f56c6c inset;
|
||||
}
|
||||
:deep(.el-textarea__inner:hover),
|
||||
:deep(.el-textarea__inner:focus) {
|
||||
border-color: #f56c6c;
|
||||
box-shadow: 0 0 0 1px #f56c6c inset;
|
||||
}
|
||||
}
|
||||
.page-footer {
|
||||
position: fixed;
|
||||
right: 0;
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
<template>
|
||||
<basic-container :class="['contract-manage-page', { 'contract-manage-page--form': isFormPage }]">
|
||||
<template v-if="!isFormPage">
|
||||
<basic-container
|
||||
:class="['contract-manage-page', { 'contract-manage-page--form': isFormPage || isDetailPage }]"
|
||||
>
|
||||
<template v-if="isListPage">
|
||||
<avue-crud
|
||||
ref="crud"
|
||||
v-model:page="page"
|
||||
@@ -102,6 +104,251 @@
|
||||
/>
|
||||
</template>
|
||||
|
||||
<template v-else-if="isDetailPage">
|
||||
<div class="archive-page-form__title">{{ detailPageTitle }}</div>
|
||||
<div v-loading="detailLoading" class="contract-manage-form contract-manage-detail">
|
||||
<section class="contract-manage-form__section contract-manage-form__section--panel">
|
||||
<div class="dialog-section-title">基本信息</div>
|
||||
<el-descriptions :column="3" class="contract-manage-detail__descriptions">
|
||||
<el-descriptions-item label="签约类型">{{
|
||||
detailValue('signType')
|
||||
}}</el-descriptions-item>
|
||||
<el-descriptions-item label="合同编号">{{
|
||||
detailValue('contractNo')
|
||||
}}</el-descriptions-item>
|
||||
<el-descriptions-item label="合同名称">{{
|
||||
detailValue('contractName')
|
||||
}}</el-descriptions-item>
|
||||
<el-descriptions-item label="合同类型">{{
|
||||
detailValue('contractCategory')
|
||||
}}</el-descriptions-item>
|
||||
<el-descriptions-item label="项目">{{
|
||||
detailValue('projectName')
|
||||
}}</el-descriptions-item>
|
||||
<el-descriptions-item label="甲方">{{ detailValue('partyA') }}</el-descriptions-item>
|
||||
<el-descriptions-item label="乙方">{{ detailValue('partyB') }}</el-descriptions-item>
|
||||
<el-descriptions-item label="合同期限">{{ detailContractPeriod }}</el-descriptions-item>
|
||||
<el-descriptions-item label="所属组织">{{
|
||||
detailValue('organizationName')
|
||||
}}</el-descriptions-item>
|
||||
<el-descriptions-item label="经办人">{{
|
||||
detailValue('handlerUserName')
|
||||
}}</el-descriptions-item>
|
||||
<el-descriptions-item label="归档状态">{{
|
||||
detailRow.archiveStatus || '未归档'
|
||||
}}</el-descriptions-item>
|
||||
<el-descriptions-item label="签订日期">{{
|
||||
detailValue('signDate')
|
||||
}}</el-descriptions-item>
|
||||
<el-descriptions-item label="合同格式">{{
|
||||
detailValue('contractFormat')
|
||||
}}</el-descriptions-item>
|
||||
<el-descriptions-item label="是否需要加盖法人章">{{
|
||||
detailLegalSealText
|
||||
}}</el-descriptions-item>
|
||||
<el-descriptions-item label="一式">{{
|
||||
detailUnitValue('copyCount', '份')
|
||||
}}</el-descriptions-item>
|
||||
<el-descriptions-item label="结算币种">{{
|
||||
detailDictionaryValue('settlementCurrency', settlementCurrencyOptions)
|
||||
}}</el-descriptions-item>
|
||||
<el-descriptions-item label="结算方式">{{
|
||||
detailDictionaryValue('settlementMode', settlementModeOptions)
|
||||
}}</el-descriptions-item>
|
||||
<el-descriptions-item label="开票周期">{{
|
||||
detailUnitValue('invoiceCycle', '天')
|
||||
}}</el-descriptions-item>
|
||||
<el-descriptions-item label="回款账期">{{
|
||||
detailUnitValue('paymentDays', '天')
|
||||
}}</el-descriptions-item>
|
||||
<el-descriptions-item label="合同金额">{{
|
||||
detailValue('contractAmount')
|
||||
}}</el-descriptions-item>
|
||||
<el-descriptions-item label="是否范本">{{ detailTemplateText }}</el-descriptions-item>
|
||||
<el-descriptions-item label="原件合同编号">{{
|
||||
detailValue('originalContractNo')
|
||||
}}</el-descriptions-item>
|
||||
<el-descriptions-item label="是否电子章">
|
||||
{{ detailElectronicSealText }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="备注" :span="3">{{
|
||||
detailValue('remark')
|
||||
}}</el-descriptions-item>
|
||||
</el-descriptions>
|
||||
</section>
|
||||
|
||||
<contract-attachment-section
|
||||
title="合同文件"
|
||||
description
|
||||
attachment-type
|
||||
readonly
|
||||
:rows="detailContractFileRows"
|
||||
:preview="previewAttachment"
|
||||
/>
|
||||
|
||||
<section class="contract-manage-form__section contract-manage-form__section--panel">
|
||||
<div class="dialog-section-title">计费信息</div>
|
||||
<el-descriptions :column="3" class="contract-manage-detail__descriptions">
|
||||
<el-descriptions-item label="费用生成模式">{{
|
||||
detailFeeGenerationMode
|
||||
}}</el-descriptions-item>
|
||||
</el-descriptions>
|
||||
<el-table :data="detailBillingPlanRows" border>
|
||||
<el-table-column type="index" label="序号" width="80" align="center" />
|
||||
<el-table-column prop="planName" label="方案名称" min-width="220" align="center" />
|
||||
<el-table-column label="运输方式" min-width="180" align="center">
|
||||
<template #default="{ row }">{{
|
||||
row.transportModeLabel || row.transportMode || '-'
|
||||
}}</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="操作" width="120" align="center" fixed="right">
|
||||
<template #default="{ row, $index }">
|
||||
<el-link type="primary" @click="openDetailBillingPlan(row, $index)"
|
||||
>查看详情</el-link
|
||||
>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
</section>
|
||||
|
||||
<section class="contract-manage-form__section contract-manage-form__section--panel">
|
||||
<div class="dialog-section-title">结算单规则</div>
|
||||
<el-tabs v-model="detailSettlementConfigTab">
|
||||
<el-tab-pane label="预结算配置" name="pre" />
|
||||
<el-tab-pane label="正式结算配置" name="formal" />
|
||||
</el-tabs>
|
||||
<el-descriptions :column="3" class="contract-manage-detail__descriptions">
|
||||
<el-descriptions-item label="自动生成结算单">{{
|
||||
Number(detailSettlementRule.autoGenerate) === 1 ? '开启' : '关闭'
|
||||
}}</el-descriptions-item>
|
||||
<template v-if="Number(detailSettlementRule.autoGenerate) === 1">
|
||||
<el-descriptions-item label="账单起始日期">{{
|
||||
displayValue(detailSettlementRule.billStartDate)
|
||||
}}</el-descriptions-item>
|
||||
<el-descriptions-item label="结算类型">{{
|
||||
displayValue(detailSettlementRule.settlementType)
|
||||
}}</el-descriptions-item>
|
||||
<el-descriptions-item
|
||||
v-if="detailSettlementRule.settlementType === '月结'"
|
||||
label="结算周期"
|
||||
>{{ displayValue(detailSettlementRule.billCycleType) }}</el-descriptions-item
|
||||
>
|
||||
<el-descriptions-item
|
||||
v-if="
|
||||
detailSettlementRule.settlementType === '月结' &&
|
||||
detailSettlementRule.billCycleType === '固定截单日'
|
||||
"
|
||||
label="账单截单日"
|
||||
>{{
|
||||
detailObjectUnitValue(detailSettlementRule, 'billCutoffDay', '日')
|
||||
}}</el-descriptions-item
|
||||
>
|
||||
<el-descriptions-item
|
||||
v-if="detailSettlementRule.settlementType === '固定天数周期结算'"
|
||||
label="周期天数"
|
||||
>{{
|
||||
detailObjectUnitValue(detailSettlementRule, 'cycleDays', '天')
|
||||
}}</el-descriptions-item
|
||||
>
|
||||
</template>
|
||||
</el-descriptions>
|
||||
<el-table
|
||||
v-if="
|
||||
Number(detailSettlementRule.autoGenerate) === 1 &&
|
||||
detailSettlementRule.settlementType === '月结' &&
|
||||
detailSettlementRule.billCycleType === '自定义多周期'
|
||||
"
|
||||
:data="detailSettlementRule.customPeriods || []"
|
||||
border
|
||||
class="contract-manage-form__custom-periods"
|
||||
>
|
||||
<el-table-column type="index" label="序号" width="80" align="center" />
|
||||
<el-table-column label="运单区间-开始日" min-width="180" align="center">
|
||||
<template #default="{ row }">{{ row.startDay || '-' }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="运单区间-结束日" min-width="180" align="center">
|
||||
<template #default="{ row }">{{ row.endDay || '-' }}</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
</section>
|
||||
|
||||
<section class="contract-manage-form__section contract-manage-form__section--panel">
|
||||
<div class="dialog-section-title">
|
||||
付款比例设置
|
||||
<el-tooltip content="非必填;配置付款比例时,合计必须等于100%。" placement="top">
|
||||
<el-icon style="margin-left: 4px; color: #909399; cursor: pointer"
|
||||
><el-icon-question-filled
|
||||
/></el-icon>
|
||||
</el-tooltip>
|
||||
</div>
|
||||
<el-table :data="detailPaymentRatioRows" border>
|
||||
<el-table-column type="index" label="序号" width="80" align="center" />
|
||||
<el-table-column prop="paymentTerm" label="付款笔数" min-width="180" align="center" />
|
||||
<el-table-column label="付款比例上限(%)" min-width="220" align="center">
|
||||
<template #default="{ row }">{{
|
||||
detailObjectUnitValue(row, 'ratioLimit', '%')
|
||||
}}</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="remark" label="备注" min-width="220" />
|
||||
</el-table>
|
||||
</section>
|
||||
|
||||
<contract-attachment-section
|
||||
title="其它附件"
|
||||
description
|
||||
readonly
|
||||
:rows="detailAttachmentRows"
|
||||
:preview="previewAttachment"
|
||||
/>
|
||||
|
||||
<section class="contract-manage-form__section contract-manage-form__section--panel">
|
||||
<div class="dialog-section-title">变更记录</div>
|
||||
<el-table :data="detailChangeRecordRows" border>
|
||||
<el-table-column type="index" label="序号" width="70" align="center" />
|
||||
<el-table-column
|
||||
prop="changeDate"
|
||||
label="变更日期"
|
||||
min-width="150"
|
||||
align="center"
|
||||
sortable
|
||||
/>
|
||||
<el-table-column prop="handlerUserName" label="经办人" min-width="140" align="center" />
|
||||
<el-table-column prop="changeType" label="变更类型" min-width="160" align="center" />
|
||||
<el-table-column label="变更内容" min-width="420">
|
||||
<template #default="{ row }">
|
||||
<el-tooltip placement="top" :show-after="200">
|
||||
<template #content>
|
||||
<div class="contract-change-record-content-tooltip">
|
||||
{{ formatContractChangeContent(row) }}
|
||||
</div>
|
||||
</template>
|
||||
<span class="contract-change-record-content-cell">{{
|
||||
formatContractChangeContent(row)
|
||||
}}</span>
|
||||
</el-tooltip>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
prop="changeReason"
|
||||
label="变更原因"
|
||||
min-width="240"
|
||||
align="center"
|
||||
show-overflow-tooltip
|
||||
/>
|
||||
<el-table-column prop="statusName" label="状态" min-width="140" align="center" />
|
||||
<el-table-column label="操作" width="120" align="center" fixed="right">
|
||||
<template #default="{ row }"
|
||||
><el-link type="primary" @click="openDetailChangeRecord(row)">详情</el-link></template
|
||||
>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
</section>
|
||||
</div>
|
||||
<div class="contract-manage-page__footer">
|
||||
<el-button @click="closeDetail">关闭</el-button>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<template v-else>
|
||||
<div class="archive-page-form__title">{{ formPageTitle }}</div>
|
||||
|
||||
@@ -328,9 +575,6 @@
|
||||
<el-form-item label="经办人">
|
||||
<el-input v-model="form.handlerUserName" disabled />
|
||||
</el-form-item>
|
||||
<el-form-item label="归档状态">
|
||||
<el-input :model-value="form.archiveStatus || '未归档'" disabled />
|
||||
</el-form-item>
|
||||
</div>
|
||||
<el-form-item label="备注" prop="remark" class="contract-manage-form__remark">
|
||||
<el-input
|
||||
@@ -566,6 +810,9 @@
|
||||
<contract-attachment-section
|
||||
title="其它附件"
|
||||
description
|
||||
attachment-type
|
||||
use-upload-dialog
|
||||
attachment-location="其它附件"
|
||||
:readonly="isAttachmentUploadMode"
|
||||
:rows="attachmentRows"
|
||||
:preview="previewAttachment"
|
||||
@@ -715,256 +962,6 @@
|
||||
@close="attachmentImagePreviewVisible = false"
|
||||
/>
|
||||
|
||||
<el-dialog
|
||||
v-model="detailBox"
|
||||
title="合同详情"
|
||||
append-to-body
|
||||
destroy-on-close
|
||||
class="contract-manage-detail-dialog"
|
||||
width="92%"
|
||||
top="4vh"
|
||||
>
|
||||
<div v-loading="detailLoading" class="contract-manage-form contract-manage-detail">
|
||||
<section class="contract-manage-form__section contract-manage-form__section--panel">
|
||||
<div class="dialog-section-title">基本信息</div>
|
||||
<el-descriptions :column="3" class="contract-manage-detail__descriptions">
|
||||
<el-descriptions-item label="签约类型">{{
|
||||
detailValue('signType')
|
||||
}}</el-descriptions-item>
|
||||
<el-descriptions-item label="合同编号">{{
|
||||
detailValue('contractNo')
|
||||
}}</el-descriptions-item>
|
||||
<el-descriptions-item label="合同名称">{{
|
||||
detailValue('contractName')
|
||||
}}</el-descriptions-item>
|
||||
<el-descriptions-item label="合同类型">{{
|
||||
detailValue('contractCategory')
|
||||
}}</el-descriptions-item>
|
||||
<el-descriptions-item label="项目">{{
|
||||
detailValue('projectName')
|
||||
}}</el-descriptions-item>
|
||||
<el-descriptions-item label="甲方">{{ detailValue('partyA') }}</el-descriptions-item>
|
||||
<el-descriptions-item label="乙方">{{ detailValue('partyB') }}</el-descriptions-item>
|
||||
<el-descriptions-item label="合同期限">{{ detailContractPeriod }}</el-descriptions-item>
|
||||
<el-descriptions-item label="所属组织">{{
|
||||
detailValue('organizationName')
|
||||
}}</el-descriptions-item>
|
||||
<el-descriptions-item label="经办人">{{
|
||||
detailValue('handlerUserName')
|
||||
}}</el-descriptions-item>
|
||||
<el-descriptions-item label="归档状态">{{
|
||||
detailRow.archiveStatus || '未归档'
|
||||
}}</el-descriptions-item>
|
||||
<el-descriptions-item label="签订日期">{{
|
||||
detailValue('signDate')
|
||||
}}</el-descriptions-item>
|
||||
<el-descriptions-item label="合同格式">{{
|
||||
detailValue('contractFormat')
|
||||
}}</el-descriptions-item>
|
||||
<el-descriptions-item label="是否需要加盖法人章">{{
|
||||
detailLegalSealText
|
||||
}}</el-descriptions-item>
|
||||
<el-descriptions-item label="一式">{{
|
||||
detailUnitValue('copyCount', '份')
|
||||
}}</el-descriptions-item>
|
||||
<el-descriptions-item label="结算币种">{{
|
||||
detailDictionaryValue('settlementCurrency', settlementCurrencyOptions)
|
||||
}}</el-descriptions-item>
|
||||
<el-descriptions-item label="结算方式">{{
|
||||
detailDictionaryValue('settlementMode', settlementModeOptions)
|
||||
}}</el-descriptions-item>
|
||||
<el-descriptions-item label="开票周期">{{
|
||||
detailUnitValue('invoiceCycle', '天')
|
||||
}}</el-descriptions-item>
|
||||
<el-descriptions-item label="回款账期">{{
|
||||
detailUnitValue('paymentDays', '天')
|
||||
}}</el-descriptions-item>
|
||||
<el-descriptions-item label="合同金额">{{
|
||||
detailValue('contractAmount')
|
||||
}}</el-descriptions-item>
|
||||
<el-descriptions-item label="是否范本">{{ detailTemplateText }}</el-descriptions-item>
|
||||
<el-descriptions-item label="原件合同编号">{{
|
||||
detailValue('originalContractNo')
|
||||
}}</el-descriptions-item>
|
||||
<el-descriptions-item label="是否电子章">
|
||||
{{ detailElectronicSealText }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="备注" :span="3">{{
|
||||
detailValue('remark')
|
||||
}}</el-descriptions-item>
|
||||
</el-descriptions>
|
||||
</section>
|
||||
|
||||
<contract-attachment-section
|
||||
title="合同文件"
|
||||
description
|
||||
attachment-type
|
||||
readonly
|
||||
:rows="detailContractFileRows"
|
||||
:preview="previewAttachment"
|
||||
/>
|
||||
|
||||
<section class="contract-manage-form__section contract-manage-form__section--panel">
|
||||
<div class="dialog-section-title">计费信息</div>
|
||||
<el-descriptions :column="3" class="contract-manage-detail__descriptions">
|
||||
<el-descriptions-item label="费用生成模式">{{
|
||||
detailFeeGenerationMode
|
||||
}}</el-descriptions-item>
|
||||
</el-descriptions>
|
||||
<el-table :data="detailBillingPlanRows" border>
|
||||
<el-table-column type="index" label="序号" width="80" align="center" />
|
||||
<el-table-column prop="planName" label="方案名称" min-width="220" align="center" />
|
||||
<el-table-column label="运输方式" min-width="180" align="center">
|
||||
<template #default="{ row }">{{
|
||||
row.transportModeLabel || row.transportMode || '-'
|
||||
}}</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="操作" width="120" align="center" fixed="right">
|
||||
<template #default="{ row, $index }">
|
||||
<el-link type="primary" @click="openDetailBillingPlan(row, $index)"
|
||||
>查看详情</el-link
|
||||
>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
</section>
|
||||
|
||||
<section class="contract-manage-form__section contract-manage-form__section--panel">
|
||||
<div class="dialog-section-title">结算单规则</div>
|
||||
<el-tabs v-model="detailSettlementConfigTab">
|
||||
<el-tab-pane label="预结算配置" name="pre" />
|
||||
<el-tab-pane label="正式结算配置" name="formal" />
|
||||
</el-tabs>
|
||||
<el-descriptions :column="3" class="contract-manage-detail__descriptions">
|
||||
<el-descriptions-item label="自动生成结算单">{{
|
||||
Number(detailSettlementRule.autoGenerate) === 1 ? '开启' : '关闭'
|
||||
}}</el-descriptions-item>
|
||||
<template v-if="Number(detailSettlementRule.autoGenerate) === 1">
|
||||
<el-descriptions-item label="账单起始日期">{{
|
||||
displayValue(detailSettlementRule.billStartDate)
|
||||
}}</el-descriptions-item>
|
||||
<el-descriptions-item label="结算类型">{{
|
||||
displayValue(detailSettlementRule.settlementType)
|
||||
}}</el-descriptions-item>
|
||||
<el-descriptions-item
|
||||
v-if="detailSettlementRule.settlementType === '月结'"
|
||||
label="结算周期"
|
||||
>{{ displayValue(detailSettlementRule.billCycleType) }}</el-descriptions-item
|
||||
>
|
||||
<el-descriptions-item
|
||||
v-if="
|
||||
detailSettlementRule.settlementType === '月结' &&
|
||||
detailSettlementRule.billCycleType === '固定截单日'
|
||||
"
|
||||
label="账单截单日"
|
||||
>{{
|
||||
detailObjectUnitValue(detailSettlementRule, 'billCutoffDay', '日')
|
||||
}}</el-descriptions-item
|
||||
>
|
||||
<el-descriptions-item
|
||||
v-if="detailSettlementRule.settlementType === '固定天数周期结算'"
|
||||
label="周期天数"
|
||||
>{{
|
||||
detailObjectUnitValue(detailSettlementRule, 'cycleDays', '天')
|
||||
}}</el-descriptions-item
|
||||
>
|
||||
</template>
|
||||
</el-descriptions>
|
||||
<el-table
|
||||
v-if="
|
||||
Number(detailSettlementRule.autoGenerate) === 1 &&
|
||||
detailSettlementRule.settlementType === '月结' &&
|
||||
detailSettlementRule.billCycleType === '自定义多周期'
|
||||
"
|
||||
:data="detailSettlementRule.customPeriods || []"
|
||||
border
|
||||
class="contract-manage-form__custom-periods"
|
||||
>
|
||||
<el-table-column type="index" label="序号" width="80" align="center" />
|
||||
<el-table-column label="运单区间-开始日" min-width="180" align="center">
|
||||
<template #default="{ row }">{{ row.startDay || '-' }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="运单区间-结束日" min-width="180" align="center">
|
||||
<template #default="{ row }">{{ row.endDay || '-' }}</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
</section>
|
||||
|
||||
<section class="contract-manage-form__section contract-manage-form__section--panel">
|
||||
<div class="dialog-section-title">
|
||||
付款比例设置
|
||||
<el-tooltip content="非必填;配置付款比例时,合计必须等于100%。" placement="top">
|
||||
<el-icon style="margin-left: 4px; color: #909399; cursor: pointer"><el-icon-question-filled /></el-icon>
|
||||
</el-tooltip>
|
||||
</div>
|
||||
<el-table :data="detailPaymentRatioRows" border>
|
||||
<el-table-column type="index" label="序号" width="80" align="center" />
|
||||
<el-table-column prop="paymentTerm" label="付款笔数" min-width="180" align="center" />
|
||||
<el-table-column label="付款比例上限(%)" min-width="220" align="center">
|
||||
<template #default="{ row }">{{
|
||||
detailObjectUnitValue(row, 'ratioLimit', '%')
|
||||
}}</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="remark" label="备注" min-width="220" />
|
||||
</el-table>
|
||||
</section>
|
||||
|
||||
<contract-attachment-section
|
||||
title="其它附件"
|
||||
description
|
||||
readonly
|
||||
:rows="detailAttachmentRows"
|
||||
:preview="previewAttachment"
|
||||
/>
|
||||
|
||||
<section class="contract-manage-form__section contract-manage-form__section--panel">
|
||||
<div class="dialog-section-title">变更记录</div>
|
||||
<el-table :data="detailChangeRecordRows" border>
|
||||
<el-table-column type="index" label="序号" width="70" align="center" />
|
||||
<el-table-column
|
||||
prop="changeDate"
|
||||
label="变更日期"
|
||||
min-width="150"
|
||||
align="center"
|
||||
sortable
|
||||
/>
|
||||
<el-table-column prop="handlerUserName" label="经办人" min-width="140" align="center" />
|
||||
<el-table-column prop="changeType" label="变更类型" min-width="160" align="center" />
|
||||
<el-table-column label="变更内容" min-width="420">
|
||||
<template #default="{ row }">
|
||||
<el-tooltip placement="top" :show-after="200">
|
||||
<template #content>
|
||||
<div class="contract-change-record-content-tooltip">
|
||||
{{ formatContractChangeContent(row) }}
|
||||
</div>
|
||||
</template>
|
||||
<span class="contract-change-record-content-cell">{{
|
||||
formatContractChangeContent(row)
|
||||
}}</span>
|
||||
</el-tooltip>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
prop="changeReason"
|
||||
label="变更原因"
|
||||
min-width="240"
|
||||
align="center"
|
||||
show-overflow-tooltip
|
||||
/>
|
||||
<el-table-column prop="statusName" label="状态" min-width="140" align="center" />
|
||||
<el-table-column label="操作" width="120" align="center" fixed="right">
|
||||
<template #default="{ row }"
|
||||
><el-link type="primary" @click="openDetailChangeRecord(row)">详情</el-link></template
|
||||
>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
</section>
|
||||
</div>
|
||||
<template #footer
|
||||
><el-button type="primary" @click="detailBox = false">关闭</el-button></template
|
||||
>
|
||||
</el-dialog>
|
||||
|
||||
<el-dialog
|
||||
v-model="detailChangeRecordVisible"
|
||||
title="变更记录详情"
|
||||
@@ -975,7 +972,6 @@
|
||||
class="contract-change-record-detail-dialog"
|
||||
>
|
||||
<div v-if="detailChangeRecord" class="contract-change-record-detail-meta">
|
||||
<span>变更日期:{{ detailChangeRecord.changeDate || '-' }}</span>
|
||||
<span>经办人:{{ detailChangeRecord.handlerUserName || '-' }}</span>
|
||||
<span>变更类型:{{ detailChangeRecord.changeType || '-' }}</span>
|
||||
<span>状态:{{ detailChangeRecord.statusName || detailChangeRecord.status || '-' }}</span>
|
||||
@@ -1277,7 +1273,6 @@ export default {
|
||||
changeRecordRows: [],
|
||||
settlementTypeOptions: ['月结', '日结', '周结', '半月结', '固定天数周期结算'],
|
||||
billCycleTypeOptions: ['固定截单日', '自然月', '自定义多周期'],
|
||||
detailBox: false,
|
||||
detailLoading: false,
|
||||
detailRow: {},
|
||||
detailContractFileRows: [],
|
||||
@@ -1313,9 +1308,15 @@ export default {
|
||||
canCreate() {
|
||||
return this.hasPermission(`${this.config.permission}_add`);
|
||||
},
|
||||
isListPage() {
|
||||
return !this.isFormPage && !this.isDetailPage;
|
||||
},
|
||||
isFormPage() {
|
||||
return this.$route.path === '/business/contract-manage/form';
|
||||
},
|
||||
isDetailPage() {
|
||||
return this.$route.path === '/business/contract-manage/detail';
|
||||
},
|
||||
formMode() {
|
||||
return this.$route.query.mode === 'edit' ? 'edit' : 'add';
|
||||
},
|
||||
@@ -1344,6 +1345,9 @@ export default {
|
||||
}
|
||||
return this.$route.query.name || `${this.formMode === 'edit' ? '编辑' : '新增'}合同管理`;
|
||||
},
|
||||
detailPageTitle() {
|
||||
return this.$route.query.name || '合同详情';
|
||||
},
|
||||
ids() {
|
||||
return this.selectionList.map(item => item.id).join(',');
|
||||
},
|
||||
@@ -1456,6 +1460,7 @@ export default {
|
||||
watch: {
|
||||
'$route.fullPath'() {
|
||||
if (this.isFormPage) this.initFormPage();
|
||||
if (this.isDetailPage) this.initDetailPage();
|
||||
},
|
||||
settlementConfigTab(tab, oldTab) {
|
||||
if (tab === oldTab) return;
|
||||
@@ -1471,6 +1476,7 @@ export default {
|
||||
this.loadSettlementDictionaries();
|
||||
this.loadOrganizationOptions();
|
||||
if (this.isFormPage) this.initFormPage();
|
||||
if (this.isDetailPage) this.initDetailPage();
|
||||
},
|
||||
methods: {
|
||||
buildTableOption() {
|
||||
@@ -2369,18 +2375,29 @@ export default {
|
||||
row.ratioLimit = nextValue;
|
||||
},
|
||||
openDetail(row) {
|
||||
this.detailBox = true;
|
||||
this.$router.push({
|
||||
path: '/business/contract-manage/detail',
|
||||
query: { id: row.id, name: '合同详情' },
|
||||
});
|
||||
},
|
||||
initDetailPage() {
|
||||
const id = this.$route.query.id;
|
||||
if (!id) return;
|
||||
this.detailLoading = true;
|
||||
this.applyDetailState(row);
|
||||
this.applyDetailState({});
|
||||
this.api
|
||||
.getDetail(row.id)
|
||||
.getDetail(id)
|
||||
.then(res => {
|
||||
this.applyDetailState(res.data?.data || row);
|
||||
this.applyDetailState(res.data?.data || {});
|
||||
})
|
||||
.finally(() => {
|
||||
this.detailLoading = false;
|
||||
});
|
||||
},
|
||||
closeDetail() {
|
||||
this.$router.$avueRouter?.closeTag?.();
|
||||
this.$router.push('/business/contract-manage');
|
||||
},
|
||||
applyDetailState(detail = {}) {
|
||||
this.detailRow = {
|
||||
...detail,
|
||||
@@ -2657,7 +2674,10 @@ export default {
|
||||
},
|
||||
handleOperation(operation, row) {
|
||||
if (operation.action === 'startChange') {
|
||||
this.$router.push({ path: '/business/contract-manage/change', query: { id: row.id } });
|
||||
this.$router.push({
|
||||
path: '/business/contract-manage/change',
|
||||
query: { id: row.id, name: '合同变更' },
|
||||
});
|
||||
return;
|
||||
}
|
||||
if (operation.action === 'flow') {
|
||||
@@ -3105,9 +3125,6 @@ export default {
|
||||
}
|
||||
|
||||
.contract-manage-detail {
|
||||
max-height: 74vh;
|
||||
overflow-y: auto;
|
||||
|
||||
&__descriptions {
|
||||
:deep(.el-descriptions__label) {
|
||||
display: inline-block;
|
||||
|
||||
@@ -227,16 +227,22 @@
|
||||
:disabled="isBasicInfoReadonly"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="承办部门" prop="undertakeDeptId">
|
||||
<el-cascader
|
||||
v-model="undertakeDeptCascaderValue"
|
||||
:options="deptTreeOptions"
|
||||
:props="deptCascaderProps"
|
||||
<el-form-item label="平台公司" prop="undertakeDeptId">
|
||||
<el-select
|
||||
v-model="form.undertakeDeptId"
|
||||
placeholder="请选择"
|
||||
clearable
|
||||
filterable
|
||||
:disabled="isBasicInfoReadonly"
|
||||
@change="handleUndertakeDeptChange"
|
||||
>
|
||||
<el-option
|
||||
v-for="item in platformCompanyOptions"
|
||||
:key="item.value"
|
||||
:label="item.label"
|
||||
:value="item.value"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="项目由来" prop="projectSource">
|
||||
<el-select
|
||||
@@ -913,7 +919,7 @@ import {
|
||||
getList as getCustomerArchiveList,
|
||||
getDetail as getCustomerArchiveDetail,
|
||||
} from '@/api/vehicle/customer-archive';
|
||||
import { getDeptTree } from '@/api/system/dept';
|
||||
import { getDeptTree, getPlatformCompanySelect } from '@/api/system/dept';
|
||||
import { getDictionary as getSystemDictionary } from '@/api/system/dict';
|
||||
import { getDictionary as getBizDictionary } from '@/api/system/dictbiz';
|
||||
import { getList as getUserList } from '@/api/system/user';
|
||||
@@ -1126,7 +1132,7 @@ export default {
|
||||
{ max: 20, message: '项目简称不能超过20个字符', trigger: 'blur' },
|
||||
],
|
||||
businessDeptId: [{ required: true, message: '请选择业务部门', trigger: 'change' }],
|
||||
undertakeDeptId: [{ required: true, message: '请选择承办部门', trigger: 'change' }],
|
||||
undertakeDeptId: [{ required: true, message: '请选择平台公司', trigger: 'change' }],
|
||||
businessMode: [{ required: true, message: '请选择业务模式', trigger: 'change' }],
|
||||
customerNames: [{ required: true, message: '请选择客户名称', trigger: 'change' }],
|
||||
carrierNames: [{ required: true, message: '请选择下游承运商', trigger: 'change' }],
|
||||
@@ -1182,7 +1188,7 @@ export default {
|
||||
},
|
||||
deptOptions: [],
|
||||
businessDeptTreeOptions: [],
|
||||
deptTreeOptions: [],
|
||||
platformCompanyOptions: [],
|
||||
cargoTypeOptions: [],
|
||||
selectedCustomerId: [],
|
||||
selectedCarrierIds: [],
|
||||
@@ -1269,20 +1275,6 @@ export default {
|
||||
this.handleBusinessDeptChange(this.form.businessDeptId);
|
||||
},
|
||||
},
|
||||
// 承办部门:同上
|
||||
undertakeDeptCascaderValue: {
|
||||
get() {
|
||||
const id = this.form.undertakeDeptId;
|
||||
return id ? this.findDeptPath(id, this.deptTreeOptions) || [id] : [];
|
||||
},
|
||||
set(path) {
|
||||
const value = Array.isArray(path)
|
||||
? path.filter(item => item !== undefined && item !== null && item !== '')
|
||||
: [];
|
||||
this.form.undertakeDeptId = value.length ? value.at(-1) : '';
|
||||
this.handleUndertakeDeptChange(this.form.undertakeDeptId);
|
||||
},
|
||||
},
|
||||
permissionList() {
|
||||
return {
|
||||
addBtn: this.canCreate,
|
||||
@@ -1694,6 +1686,7 @@ export default {
|
||||
.map(item => this.getCustomerRowId(item))
|
||||
.filter(Boolean);
|
||||
this.syncSelectedCustomerOptions();
|
||||
this.ensureCurrentPlatformOption();
|
||||
},
|
||||
fillDefaultUsers() {
|
||||
const userId = this.userInfo?.userId || this.userInfo?.id || '';
|
||||
@@ -2028,7 +2021,7 @@ export default {
|
||||
handler: record.handlerUserName || record.changeUserName || record.handler || '',
|
||||
reason: record.changeReason || '',
|
||||
status: record.statusName || record.status || '',
|
||||
action: '详情',
|
||||
action: '查看',
|
||||
};
|
||||
return { ...changeRow, content: this.formatChangeContent(changeRow) };
|
||||
});
|
||||
@@ -2051,7 +2044,7 @@ export default {
|
||||
projectName: '项目名称',
|
||||
projectShortName: '项目简称',
|
||||
businessDeptName: '业务部门',
|
||||
undertakeDeptName: '承办部门',
|
||||
undertakeDeptName: '平台公司',
|
||||
projectSource: '项目由来',
|
||||
sourceRemark: '项目由来说明',
|
||||
fundLimit: '项目资金使用额度',
|
||||
@@ -2112,6 +2105,18 @@ export default {
|
||||
formatProjectChangeValue(field, value) {
|
||||
const normalized = this.normalizeProjectChangeValue(value);
|
||||
if (this.isEmptyProjectChangeValue(normalized)) return '空';
|
||||
if (field === 'transportType') {
|
||||
const option = (this.transportTypeOptions || []).find(
|
||||
item => String(item.value) === String(normalized)
|
||||
);
|
||||
return option?.label || String(normalized);
|
||||
}
|
||||
if (field === 'settlementMode') {
|
||||
const option = (this.settlementModeOptions || []).find(
|
||||
item => String(item.value) === String(normalized)
|
||||
);
|
||||
return option?.label || String(normalized);
|
||||
}
|
||||
if (field === 'attachmentsJson') {
|
||||
const attachments = Array.isArray(normalized)
|
||||
? normalized
|
||||
@@ -2130,6 +2135,23 @@ export default {
|
||||
if (typeof normalized === 'object') return JSON.stringify(normalized);
|
||||
return String(normalized);
|
||||
},
|
||||
buildSituationChangeRows(beforeValue, afterValue) {
|
||||
const before = this.parseSituation(beforeValue);
|
||||
const after = this.parseSituation(afterValue);
|
||||
const labels = {
|
||||
projectIntro: '项目简介',
|
||||
profitRemark: '盈利模式及利润测算情况说明',
|
||||
riskPoint: '项目风险点',
|
||||
emergencyPlan: '风险防范措施及应急预案',
|
||||
};
|
||||
return Object.keys(labels)
|
||||
.filter(key => !this.isSameProjectChangeValue(before[key], after[key]))
|
||||
.map(key => ({
|
||||
field: labels[key],
|
||||
before: this.formatProjectChangeValue(key, before[key]),
|
||||
after: this.formatProjectChangeValue(key, after[key]),
|
||||
}));
|
||||
},
|
||||
buildChangeRecordDetailRows(row = {}) {
|
||||
const beforeData = this.parseChangeData(row.beforeData);
|
||||
const afterData = this.parseChangeData(row.afterData);
|
||||
@@ -2140,13 +2162,20 @@ export default {
|
||||
...Object.keys(afterData),
|
||||
]),
|
||||
];
|
||||
const rows = fields
|
||||
const rows = [];
|
||||
fields
|
||||
.filter(field => field !== '变更内容' && !this.isSameProjectChangeValue(beforeData[field], afterData[field]))
|
||||
.map(field => ({
|
||||
.forEach(field => {
|
||||
if (field === 'situationRemark') {
|
||||
rows.push(...this.buildSituationChangeRows(beforeData[field], afterData[field]));
|
||||
return;
|
||||
}
|
||||
rows.push({
|
||||
field: this.getProjectChangeFieldLabel(field),
|
||||
before: this.formatProjectChangeValue(field, beforeData[field]),
|
||||
after: this.formatProjectChangeValue(field, afterData[field]),
|
||||
}));
|
||||
});
|
||||
});
|
||||
if (!rows.length && String(row.changeContent || '').trim()) {
|
||||
rows.push({ field: '变更内容', before: '空', after: String(row.changeContent).trim() });
|
||||
}
|
||||
@@ -2172,8 +2201,8 @@ export default {
|
||||
},
|
||||
handleUndertakeDeptChange(value) {
|
||||
const id = Array.isArray(value) ? value.at(-1) : value;
|
||||
const dept = this.deptOptions.find(item => String(item.value) === String(id));
|
||||
this.form.undertakeDeptName = dept ? dept.rawLabel : '';
|
||||
const dept = this.platformCompanyOptions.find(item => String(item.value) === String(id));
|
||||
this.form.undertakeDeptName = dept ? dept.label : '';
|
||||
this.$refs.projectForm?.validateField('undertakeDeptId');
|
||||
},
|
||||
// 由部门 id 反查「根 → 该节点」的 id 路径,供级联回显(找不到返回 null)
|
||||
@@ -2198,12 +2227,31 @@ export default {
|
||||
const businessDeptTree = ['add', 'edit', 'majorSupplement'].includes(this.dialogType)
|
||||
? this.excludeExternalOrganization(deptTree)
|
||||
: deptTree;
|
||||
const undertakeDeptTree =
|
||||
this.dialogType === 'add' ? this.excludeExternalOrganization(deptTree) : deptTree;
|
||||
this.deptOptions = this.flattenDept(businessDeptTree);
|
||||
this.businessDeptTreeOptions = this.toDeptCascaderOptions(businessDeptTree);
|
||||
this.deptTreeOptions = this.toDeptCascaderOptions(undertakeDeptTree);
|
||||
});
|
||||
this.loadPlatformCompanyOptions();
|
||||
},
|
||||
loadPlatformCompanyOptions() {
|
||||
getPlatformCompanySelect().then(res => {
|
||||
const list = res.data.data || [];
|
||||
this.platformCompanyOptions = list.map(item => ({
|
||||
label: item.deptName || item.fullName || item.title || item.name,
|
||||
value: item.id,
|
||||
}));
|
||||
this.ensureCurrentPlatformOption();
|
||||
});
|
||||
},
|
||||
ensureCurrentPlatformOption() {
|
||||
const id = this.form.undertakeDeptId;
|
||||
if (!id) return;
|
||||
const exists = this.platformCompanyOptions.some(item => String(item.value) === String(id));
|
||||
if (!exists) {
|
||||
this.platformCompanyOptions.unshift({
|
||||
label: this.form.undertakeDeptName || String(id),
|
||||
value: id,
|
||||
});
|
||||
}
|
||||
},
|
||||
excludeExternalOrganization(list = []) {
|
||||
return list.reduce((result, item) => {
|
||||
@@ -2248,7 +2296,7 @@ export default {
|
||||
});
|
||||
},
|
||||
loadTransportTypeOptions() {
|
||||
getBizDictionary({ code: 'transport_type' }).then(res => {
|
||||
return getBizDictionary({ code: 'transport_type' }).then(res => {
|
||||
this.transportTypeOptions = (res.data.data || []).map(item => ({
|
||||
label: item.dictValue,
|
||||
value: item.dictKey,
|
||||
@@ -2256,7 +2304,7 @@ export default {
|
||||
});
|
||||
},
|
||||
loadSettlementModeOptions() {
|
||||
getBizDictionary({ code: 'settle_method' }).then(res => {
|
||||
return getBizDictionary({ code: 'settle_method' }).then(res => {
|
||||
const data = res.data?.data || [];
|
||||
this.settlementModeOptions = data.map(item => ({
|
||||
label: item.dictValue,
|
||||
@@ -2485,6 +2533,12 @@ export default {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
if (!this.transportTypeOptions.length) {
|
||||
await this.loadTransportTypeOptions();
|
||||
}
|
||||
if (!this.settlementModeOptions.length) {
|
||||
await this.loadSettlementModeOptions();
|
||||
}
|
||||
const response = await api.getChangeRecordDetail(row.projectId || row.id, row.recordIndex);
|
||||
const detail = response?.data?.data || {};
|
||||
this.changeRecordDetail = { ...row, ...detail };
|
||||
|
||||
@@ -50,7 +50,13 @@
|
||||
</el-tag>
|
||||
</template>
|
||||
|
||||
<template #projectName-label>项目额度信息</template>
|
||||
<template #projectQuotaInfoTitle-form>
|
||||
<div class="dialog-section-title">项目额度信息</div>
|
||||
</template>
|
||||
|
||||
<template #temporaryCreditInfoTitle-form>
|
||||
<div class="dialog-section-title">临时额度信息</div>
|
||||
</template>
|
||||
|
||||
<template #projectName-form>
|
||||
<el-select
|
||||
@@ -130,22 +136,19 @@
|
||||
</template>
|
||||
|
||||
<template #menu-form-before>
|
||||
<el-button
|
||||
v-if="!dialogReadonly"
|
||||
type="primary"
|
||||
plain
|
||||
:loading="draftLoading"
|
||||
@click="saveDraft"
|
||||
>
|
||||
<template v-if="!dialogReadonly">
|
||||
<el-button @click="closeFormDialog">取消</el-button>
|
||||
<el-button type="primary" plain :loading="draftLoading" @click="saveDraft">
|
||||
保存
|
||||
</el-button>
|
||||
</template>
|
||||
</template>
|
||||
|
||||
<template #menu="{ row }">
|
||||
<el-link
|
||||
v-if="hasPermission(`${config.permission}_view`)"
|
||||
type="primary"
|
||||
@click="$refs.crud.rowView(row)"
|
||||
@click="openDetail(row)"
|
||||
>
|
||||
查看
|
||||
</el-link>
|
||||
@@ -164,6 +167,74 @@
|
||||
</template>
|
||||
</avue-crud>
|
||||
|
||||
<el-dialog
|
||||
v-model="detailVisible"
|
||||
title="查看临时额度申请"
|
||||
append-to-body
|
||||
destroy-on-close
|
||||
width="96%"
|
||||
class="temporary-credit-limit-dialog temporary-credit-limit-detail-dialog"
|
||||
>
|
||||
<div v-loading="detailLoading" class="business-crud-page__detail-content">
|
||||
<section
|
||||
v-for="section in detailSections"
|
||||
:key="section.title"
|
||||
class="temporary-credit-limit-detail-dialog__section"
|
||||
>
|
||||
<div class="dialog-section-title">{{ section.title }}</div>
|
||||
<el-descriptions :column="4" class="temporary-credit-limit-detail-dialog__descriptions">
|
||||
<el-descriptions-item
|
||||
v-for="field in section.fields"
|
||||
:key="field[0]"
|
||||
:label="field[1]"
|
||||
:span="field[2] || 1"
|
||||
>
|
||||
{{ formatDetailValue(field[0]) }}
|
||||
</el-descriptions-item>
|
||||
</el-descriptions>
|
||||
<div
|
||||
v-if="section.showAttachment"
|
||||
class="temporary-credit-limit-detail-dialog__attachment"
|
||||
>
|
||||
<div class="temporary-credit-limit-page__attachment-head">
|
||||
<span class="temporary-credit-limit-detail-dialog__attachment-label">附件</span>
|
||||
<el-button type="primary" :disabled="!attachmentRows.length" @click="batchDownload">
|
||||
批量下载
|
||||
</el-button>
|
||||
</div>
|
||||
<el-table :data="attachmentRows" empty-text="暂无附件">
|
||||
<el-table-column type="index" label="序号" width="70" align="center" />
|
||||
<el-table-column label="文件名" min-width="240" show-overflow-tooltip>
|
||||
<template #default="{ row }">
|
||||
<el-link type="primary" @click="previewAttachment(row)">
|
||||
{{ attachmentName(row) }}
|
||||
</el-link>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="文件大小" width="120" align="center">
|
||||
<template #default="{ row }">{{ formatFileSize(row.size) }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="uploadUserName" label="上传人" width="140" align="center" />
|
||||
<el-table-column
|
||||
prop="uploadTime"
|
||||
label="上传时间"
|
||||
:width="config.detailAttachmentUploadTimeWidth || 180"
|
||||
align="center"
|
||||
/>
|
||||
<el-table-column label="操作" width="100" align="center" fixed="right">
|
||||
<template #default="{ row }">
|
||||
<el-link type="primary" @click="downloadAttachment(row)">下载</el-link>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
<template #footer>
|
||||
<el-button @click="detailVisible = false">关闭</el-button>
|
||||
</template>
|
||||
</el-dialog>
|
||||
|
||||
<empty-pagination
|
||||
:page="page"
|
||||
@size-change="sizeChange"
|
||||
@@ -313,6 +384,9 @@ export default {
|
||||
zoom: true,
|
||||
},
|
||||
attachmentFileTypes,
|
||||
detailVisible: false,
|
||||
detailLoading: false,
|
||||
detailRow: {},
|
||||
flowBox: false,
|
||||
flowUrl: '',
|
||||
processInstanceId: '',
|
||||
@@ -323,6 +397,9 @@ export default {
|
||||
permissionList() {
|
||||
return { addBtn: this.canCreate };
|
||||
},
|
||||
detailSections() {
|
||||
return this.config.detailSections || [];
|
||||
},
|
||||
isAdmin() {
|
||||
const authority = this.userInfo?.authority;
|
||||
return Array.isArray(authority)
|
||||
@@ -341,14 +418,54 @@ export default {
|
||||
},
|
||||
methods: {
|
||||
buildTableOption() {
|
||||
const columns = (option.column || []).map(column => ({ ...column }));
|
||||
const sectionTitleProps = ['projectQuotaInfoTitle', 'temporaryCreditInfoTitle'];
|
||||
const formGroups = [];
|
||||
let current = [];
|
||||
columns.forEach(col => {
|
||||
if (sectionTitleProps.includes(col.prop)) {
|
||||
if (current.length) formGroups.push(current);
|
||||
current = [col];
|
||||
return;
|
||||
}
|
||||
if (!current.length) return;
|
||||
// 列表/搜索专用字段不进入表单分组白卡
|
||||
if (col.addDisplay === false && col.editDisplay === false) return;
|
||||
current.push(col);
|
||||
});
|
||||
if (current.length) formGroups.push(current);
|
||||
|
||||
const formSectionProps = new Set();
|
||||
formGroups.forEach(group => {
|
||||
group.forEach(col => {
|
||||
if (col.prop) formSectionProps.add(col.prop);
|
||||
});
|
||||
});
|
||||
|
||||
// 表单字段全部进 group 拆白卡;Avue 会额外生成空的主列 group,由样式隐藏去掉顶部白横条
|
||||
return {
|
||||
...option,
|
||||
addBtn: false,
|
||||
viewBtn: false,
|
||||
editBtn: false,
|
||||
delBtn: false,
|
||||
// 隐藏 Avue 默认取消按钮,改由 menu-form-before 按「取消、保存、提交」顺序自定义
|
||||
cancelBtn: false,
|
||||
menuWidth: 320,
|
||||
column: (option.column || []).map(column => ({ ...column })),
|
||||
column: columns.map(col => {
|
||||
if (!formSectionProps.has(col.prop)) return col;
|
||||
return {
|
||||
...col,
|
||||
addDisplay: false,
|
||||
editDisplay: false,
|
||||
viewDisplay: false,
|
||||
};
|
||||
}),
|
||||
group: formGroups.map(group => ({
|
||||
label: '',
|
||||
arrow: false,
|
||||
column: group,
|
||||
})),
|
||||
};
|
||||
},
|
||||
hasPermission(code) {
|
||||
@@ -451,6 +568,32 @@ export default {
|
||||
.then(res => this.applyDetail(res.data?.data || {}))
|
||||
.finally(done);
|
||||
},
|
||||
openDetail(row) {
|
||||
this.detailVisible = true;
|
||||
this.detailLoading = true;
|
||||
this.detailRow = { ...row };
|
||||
this.attachmentRows = [];
|
||||
this.selectedAttachments = [];
|
||||
this.api
|
||||
.getDetail(row.id)
|
||||
.then(res => {
|
||||
const detail = res.data?.data || {};
|
||||
this.detailRow = { ...detail };
|
||||
this.attachmentRows = this.parseJsonArray(detail.attachmentsJson);
|
||||
})
|
||||
.finally(() => {
|
||||
this.detailLoading = false;
|
||||
});
|
||||
},
|
||||
formatDetailValue(prop) {
|
||||
const row = this.detailRow || {};
|
||||
if (prop === 'approvalStatus') {
|
||||
return this.displayStatus(row, 'approvalStatus');
|
||||
}
|
||||
const value = row[prop];
|
||||
if (value === undefined || value === null || value === '') return '-';
|
||||
return value;
|
||||
},
|
||||
applyDetail(detail) {
|
||||
this.form = { ...detail };
|
||||
this.selectedProjectId = detail.projectId || '';
|
||||
@@ -459,9 +602,10 @@ export default {
|
||||
this.syncCurrentProjectOption();
|
||||
},
|
||||
normalizeForm(row = this.form) {
|
||||
const { projectQuotaInfoTitle, temporaryCreditInfoTitle, ...payload } = row || {};
|
||||
return {
|
||||
...row,
|
||||
applyLimit: row.applyLimit === '' ? '' : Number(row.applyLimit),
|
||||
...payload,
|
||||
applyLimit: payload.applyLimit === '' ? '' : Number(payload.applyLimit),
|
||||
attachmentsJson: JSON.stringify(this.attachmentRows),
|
||||
};
|
||||
},
|
||||
@@ -492,13 +636,20 @@ export default {
|
||||
.saveDraft(this.normalizeForm())
|
||||
.then(() => {
|
||||
this.$message.success('保存成功');
|
||||
this.$refs.crud?.closeDialog();
|
||||
this.closeFormDialog();
|
||||
this.onLoad(this.page, this.query);
|
||||
})
|
||||
.finally(() => {
|
||||
this.draftLoading = false;
|
||||
});
|
||||
},
|
||||
closeFormDialog() {
|
||||
if (typeof this.$refs.crud?.closeDialog === 'function') {
|
||||
this.$refs.crud.closeDialog();
|
||||
return;
|
||||
}
|
||||
this.$refs.crud?.$refs?.dialogForm?.hide?.();
|
||||
},
|
||||
rowDel(row) {
|
||||
this.removeRows([row]);
|
||||
},
|
||||
@@ -824,7 +975,6 @@ export default {
|
||||
.el-form-item__label {
|
||||
height: auto !important;
|
||||
line-height: 18px;
|
||||
//padding-top: 7px; // 视觉补偿:让单行 label 仍接近 input 中线
|
||||
}
|
||||
|
||||
// 强制必填星号垂直居中于行盒,与第一行汉字字符中心同基线(关键)
|
||||
@@ -832,19 +982,257 @@ export default {
|
||||
display: inline-block;
|
||||
line-height: 18px;
|
||||
height: 18px;
|
||||
vertical-align: middle; // 关键:相对 baseline 上移,与汉字字符中心对齐
|
||||
vertical-align: middle;
|
||||
margin-right: 4px;
|
||||
}
|
||||
|
||||
// 「项目额度信息 / 临时额度信息」小标题:对齐项目管理「项目基本信息」样式
|
||||
.avue-form__group:has(.dialog-section-title) {
|
||||
.dialog-section-title {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
width: 100%;
|
||||
padding: 14px 16px 4px;
|
||||
margin-bottom: 0;
|
||||
color: #303133;
|
||||
font-size: 15px;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.avue-form__group--flex:has(.dialog-section-title),
|
||||
.el-form-item:has(.dialog-section-title) {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.el-form-item:has(.dialog-section-title) .el-form-item__content {
|
||||
margin-left: 0 !important;
|
||||
}
|
||||
}
|
||||
|
||||
// 分组白卡:Avue 实际结构为 .avue-form > .el-form > .el-row > .avue-group
|
||||
.avue-form {
|
||||
background: transparent !important;
|
||||
box-shadow: none !important;
|
||||
padding: 0 !important;
|
||||
margin: 0 !important;
|
||||
border-radius: 0 !important;
|
||||
min-height: 0 !important;
|
||||
}
|
||||
|
||||
.avue-form > .el-form > .el-row {
|
||||
margin-left: 0 !important;
|
||||
margin-right: 0 !important;
|
||||
}
|
||||
|
||||
// 无分区标题的空分组(Avue 主列残留)隐藏,去掉顶部白横条
|
||||
.avue-form > .el-form > .el-row > .avue-group:not(:has(.dialog-section-title)) {
|
||||
display: none !important;
|
||||
height: 0 !important;
|
||||
margin: 0 !important;
|
||||
padding: 0 !important;
|
||||
overflow: hidden !important;
|
||||
border: none !important;
|
||||
}
|
||||
|
||||
// 有分区标题的分组渲染为独立白卡
|
||||
.avue-form > .el-form > .el-row > .avue-group:has(.dialog-section-title) {
|
||||
background: #fff;
|
||||
border-radius: 6px;
|
||||
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.06);
|
||||
margin-bottom: 12px;
|
||||
padding: 0 0 4px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.avue-form > .el-form > .el-row > .avue-group:has(.dialog-section-title):last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.avue-group .el-collapse {
|
||||
border: none !important;
|
||||
}
|
||||
|
||||
.avue-group .el-collapse-item__header {
|
||||
display: none !important;
|
||||
height: 0 !important;
|
||||
min-height: 0 !important;
|
||||
line-height: 0 !important;
|
||||
padding: 0 !important;
|
||||
border: none !important;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.avue-group .avue-group__header {
|
||||
display: none !important;
|
||||
height: 0 !important;
|
||||
min-height: 0 !important;
|
||||
padding: 0 !important;
|
||||
margin: 0 !important;
|
||||
border: none !important;
|
||||
}
|
||||
|
||||
.avue-group .el-collapse-item__wrap {
|
||||
border: none !important;
|
||||
}
|
||||
|
||||
.avue-group .el-collapse-item__content {
|
||||
padding: 0 !important;
|
||||
}
|
||||
|
||||
// 卡片视觉由外层 avue-group 承担,内层去白底,避免叠卡/顶条
|
||||
.avue-group .avue-form__group {
|
||||
display: flex !important;
|
||||
background: transparent !important;
|
||||
box-shadow: none !important;
|
||||
margin: 0 !important;
|
||||
padding: 0 !important;
|
||||
border-radius: 0 !important;
|
||||
}
|
||||
}
|
||||
|
||||
.temporary-credit-limit-dialog .business-crud-page__detail-content .el-descriptions__label {
|
||||
width: 180px !important;
|
||||
}
|
||||
|
||||
// 新增/编辑/查看:高度随内容自适应,超出时再滚动;去掉强制全屏导致的底部大片空白
|
||||
.temporary-credit-limit-dialog.el-dialog {
|
||||
margin-top: 20px !important;
|
||||
margin-bottom: 20px !important;
|
||||
height: auto !important;
|
||||
max-height: calc(100vh - 40px) !important;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.temporary-credit-limit-dialog .el-dialog__header {
|
||||
flex: none;
|
||||
}
|
||||
|
||||
.temporary-credit-limit-dialog .el-dialog__body {
|
||||
flex: 0 1 auto;
|
||||
min-height: 0;
|
||||
max-height: calc(100vh - 140px) !important;
|
||||
display: block;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
// 覆盖全局 .el-dialog .avue-form 白底内边距,避免顶部出现白横条
|
||||
.temporary-credit-limit-dialog:not(.temporary-credit-limit-detail-dialog) .avue-form {
|
||||
background: transparent !important;
|
||||
box-shadow: none !important;
|
||||
padding: 0 !important;
|
||||
margin: 0 !important;
|
||||
border-radius: 0 !important;
|
||||
}
|
||||
|
||||
.temporary-credit-limit-dialog:not(.temporary-credit-limit-detail-dialog) .avue-dialog__footer,
|
||||
.temporary-credit-limit-detail-dialog .el-dialog__footer {
|
||||
position: sticky;
|
||||
bottom: 0;
|
||||
z-index: 5;
|
||||
flex: none;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: flex-end;
|
||||
gap: 12px;
|
||||
margin-top: 0 !important;
|
||||
padding: 12px 20px !important;
|
||||
background: #fff;
|
||||
border-top: 1px solid var(--el-border-color-lighter, #ebeef5) !important;
|
||||
border-radius: 0;
|
||||
box-shadow: none;
|
||||
|
||||
.el-button + .el-button {
|
||||
margin-left: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.temporary-credit-limit-detail-dialog {
|
||||
.business-crud-page__detail-content {
|
||||
flex: 1 1 auto;
|
||||
min-height: 0;
|
||||
overflow: auto;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
&__section {
|
||||
margin-bottom: 12px;
|
||||
padding: 0 0 4px;
|
||||
background: #fff;
|
||||
border-radius: 6px;
|
||||
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.06);
|
||||
overflow: hidden;
|
||||
|
||||
&:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
}
|
||||
|
||||
&__descriptions {
|
||||
padding: 0 16px 8px;
|
||||
|
||||
// 取消 Descriptions 表格边框样式,改为纯文本描述列表
|
||||
.el-descriptions__body {
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
.el-descriptions__table {
|
||||
border-collapse: separate;
|
||||
border-spacing: 0;
|
||||
}
|
||||
|
||||
.el-descriptions__cell {
|
||||
border: none !important;
|
||||
background: transparent !important;
|
||||
padding: 8px 12px 8px 0;
|
||||
vertical-align: top;
|
||||
}
|
||||
|
||||
.el-descriptions__label {
|
||||
color: #606266;
|
||||
font-weight: 400;
|
||||
white-space: normal;
|
||||
line-height: 18px;
|
||||
}
|
||||
|
||||
.el-descriptions__content {
|
||||
color: #303133;
|
||||
line-height: 18px;
|
||||
}
|
||||
}
|
||||
|
||||
.dialog-section-title {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
width: 100%;
|
||||
padding: 14px 16px 4px;
|
||||
margin-bottom: 0;
|
||||
color: #303133;
|
||||
font-size: 15px;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
&__attachment {
|
||||
padding: 8px 16px 16px;
|
||||
}
|
||||
|
||||
&__attachment-label {
|
||||
color: #606266;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.temporary-credit-limit-page__attachment-head {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
}
|
||||
|
||||
.el-form-item--default .el-form-item__label {
|
||||
height: auto !important;
|
||||
}
|
||||
.el-dialog .avue-form{
|
||||
padding: 16px 16px 4px !important;
|
||||
}
|
||||
|
||||
</style>
|
||||
|
||||
+121
-28
@@ -42,12 +42,6 @@
|
||||
<template #deptCategory="{ row }">
|
||||
<el-tag>{{ getDeptCategoryLabel(row.deptCategory) }}</el-tag>
|
||||
</template>
|
||||
<template #leaderId="{ row }">
|
||||
{{ getLeaderValue(row.leaderId, 'realName') }}
|
||||
</template>
|
||||
<template #leaderPhone="{ row }">
|
||||
{{ getLeaderValue(row.leaderId, 'phone') }}
|
||||
</template>
|
||||
<template #status="{ row }">
|
||||
<el-tag :type="Number(row.status) === 1 ? 'success' : 'info'" class="status-text">
|
||||
{{ Number(row.status) === 1 ? '启用' : '停用' }}
|
||||
@@ -174,6 +168,7 @@ export default {
|
||||
searchIndex: 4,
|
||||
searchMenuPosition: 'right',
|
||||
tree: true,
|
||||
rowKey: 'id',
|
||||
border: true,
|
||||
index: true,
|
||||
selection: true,
|
||||
@@ -183,6 +178,8 @@ export default {
|
||||
dialogClickModal: false,
|
||||
labelPosition: 'right',
|
||||
labelWidth: 'auto',
|
||||
// 禁用 Avue 列状态持久化,避免增列后表头/单元格错位
|
||||
columnState: false,
|
||||
column: [
|
||||
{
|
||||
label: '组织名称',
|
||||
@@ -206,22 +203,32 @@ export default {
|
||||
maxlength: 30,
|
||||
showWordLimit: true,
|
||||
rules: [
|
||||
{
|
||||
required: true,
|
||||
message: '请输入组织编码',
|
||||
trigger: 'blur',
|
||||
},
|
||||
{
|
||||
validator: (rule, value, callback) => {
|
||||
const deptCode = String(value || '').trim();
|
||||
// 组织编码非必填,为空直接通过
|
||||
if (!deptCode) {
|
||||
callback();
|
||||
return;
|
||||
}
|
||||
if (deptCode.length > 30) {
|
||||
callback(new Error('组织编码不能超过30个字符'));
|
||||
return;
|
||||
}
|
||||
if (!this.parentDeptCode) {
|
||||
const parentId = this.normalizeParentId(this.form?.parentId);
|
||||
if (!parentId) {
|
||||
callback(new Error('请先选择上级组织'));
|
||||
return;
|
||||
}
|
||||
if (!this.parentDept || String(this.parentDept.id) !== String(parentId)) {
|
||||
this.loadParentDeptCode(parentId);
|
||||
callback(new Error('正在加载上级组织编码,请稍后重试'));
|
||||
return;
|
||||
}
|
||||
if (!this.parentDeptCode) {
|
||||
callback(new Error('上级组织未配置组织编码,请先完善上级组织编码'));
|
||||
return;
|
||||
}
|
||||
const pattern = new RegExp(`^${escapeRegExp(this.parentDeptCode)}-\\d+$`);
|
||||
if (!pattern.test(deptCode)) {
|
||||
callback(new Error(`组织编码格式应为:${this.parentDeptCode}-分段数字`));
|
||||
@@ -239,6 +246,18 @@ export default {
|
||||
minWidth: 140,
|
||||
order: 60,
|
||||
},
|
||||
{
|
||||
label: '助记码',
|
||||
prop: 'mnemonicCode',
|
||||
minWidth: 120,
|
||||
order: 50,
|
||||
},
|
||||
{
|
||||
label: '拼音助记码',
|
||||
prop: 'pinyinMnemonic',
|
||||
minWidth: 140,
|
||||
order: 45,
|
||||
},
|
||||
{
|
||||
label: '组织类型',
|
||||
type: 'select',
|
||||
@@ -268,29 +287,40 @@ export default {
|
||||
label: '负责人',
|
||||
prop: 'leaderId',
|
||||
minWidth: 120,
|
||||
slot: true,
|
||||
addDisplay: false,
|
||||
editDisplay: false,
|
||||
viewDisplay: false,
|
||||
formatter: row => this.getLeaderValue(row.leaderId, 'realName'),
|
||||
},
|
||||
{
|
||||
label: '联系电话',
|
||||
prop: 'leaderPhone',
|
||||
minWidth: 140,
|
||||
slot: true,
|
||||
addDisplay: false,
|
||||
editDisplay: false,
|
||||
viewDisplay: false,
|
||||
formatter: row => this.getLeaderValue(row.leaderId, 'phone'),
|
||||
},
|
||||
{
|
||||
label: '助记码',
|
||||
prop: 'mnemonicCode',
|
||||
minWidth: 120,
|
||||
order: 40,
|
||||
},
|
||||
label: '是否平台公司',
|
||||
prop: 'isPlatformCompany',
|
||||
type: 'select',
|
||||
dicData: [
|
||||
{ label: '否', value: 0 },
|
||||
{ label: '是', value: 1 },
|
||||
],
|
||||
dataType: 'number',
|
||||
value: 0,
|
||||
width: 120,
|
||||
order: 35,
|
||||
formatter: row => (Number(row.isPlatformCompany) === 1 ? '是' : '否'),
|
||||
rules: [
|
||||
{
|
||||
label: '拼音助记码',
|
||||
prop: 'pinyinMnemonic',
|
||||
minWidth: 140,
|
||||
order: 50,
|
||||
required: true,
|
||||
message: '请选择是否平台公司',
|
||||
trigger: 'change',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
label: '排序',
|
||||
@@ -314,6 +344,7 @@ export default {
|
||||
slot: true,
|
||||
addDisplay: false,
|
||||
editDisplay: false,
|
||||
viewDisplay: false,
|
||||
},
|
||||
{
|
||||
label: '上级组织',
|
||||
@@ -372,6 +403,20 @@ export default {
|
||||
};
|
||||
},
|
||||
created() {
|
||||
// 清除可能错乱的表格列顺序/列状态缓存,避免新增列后表头/单元格错位
|
||||
try {
|
||||
const path = this.$route?.path || '/system/dept';
|
||||
Object.keys(localStorage).forEach(key => {
|
||||
if (
|
||||
(key.startsWith('tms:table-column-order:') && key.includes(path)) ||
|
||||
(key.startsWith('AVUE_COLUMN_STATE:') && key.includes('dept'))
|
||||
) {
|
||||
localStorage.removeItem(key);
|
||||
}
|
||||
});
|
||||
} catch (error) {
|
||||
// ignore storage errors
|
||||
}
|
||||
this.loadLeaderOptions();
|
||||
},
|
||||
computed: {
|
||||
@@ -400,16 +445,47 @@ export default {
|
||||
});
|
||||
},
|
||||
handleParentChange(parentId) {
|
||||
this.loadParentDeptCode(parentId);
|
||||
// Avue tree 选择时可能先触发空值 change,避免把已加载的上级编码清掉
|
||||
const normalizedId = this.normalizeParentId(parentId);
|
||||
if (!normalizedId && this.form?.parentId) {
|
||||
return;
|
||||
}
|
||||
this.loadParentDeptCode(normalizedId || this.form?.parentId);
|
||||
},
|
||||
normalizeParentId(parentId) {
|
||||
if (Array.isArray(parentId)) {
|
||||
parentId = parentId[0];
|
||||
}
|
||||
if (parentId && typeof parentId === 'object') {
|
||||
parentId = parentId.value ?? parentId.id ?? parentId.key;
|
||||
}
|
||||
if (!parentId || String(parentId) === '0') {
|
||||
return '';
|
||||
}
|
||||
return parentId;
|
||||
},
|
||||
loadParentDeptCode(parentId) {
|
||||
const normalizedId = this.normalizeParentId(parentId);
|
||||
if (!normalizedId) {
|
||||
this.parentDeptCode = '';
|
||||
this.parentDept = null;
|
||||
if (!parentId || String(parentId) === '0') return;
|
||||
getDept(parentId).then(res => {
|
||||
return;
|
||||
}
|
||||
getDept(normalizedId).then(res => {
|
||||
// 防止异步乱序:仅采纳当前表单上级对应的结果
|
||||
if (
|
||||
this.form?.parentId &&
|
||||
String(this.form.parentId) !== '0' &&
|
||||
String(this.form.parentId) !== String(normalizedId)
|
||||
) {
|
||||
return;
|
||||
}
|
||||
this.parentDept = res.data.data || null;
|
||||
this.parentDeptCode = this.parentDept?.deptCode || '';
|
||||
this.updateDeptCategoryOptions();
|
||||
this.$nextTick(() => {
|
||||
this.$refs.crud?.clearValidate?.('deptCode');
|
||||
});
|
||||
});
|
||||
},
|
||||
updateDeptCategoryOptions() {
|
||||
@@ -496,6 +572,10 @@ export default {
|
||||
loadLeaderOptions(tenantId) {
|
||||
getLeaderList(tenantId).then(res => {
|
||||
this.leaderOptions = res.data.data || [];
|
||||
// formatter 依赖负责人字典,异步返回后触发表格重绘
|
||||
if (Array.isArray(this.data) && this.data.length) {
|
||||
this.data = [...this.data];
|
||||
}
|
||||
});
|
||||
},
|
||||
getDeptCategoryLabel(deptCategory) {
|
||||
@@ -510,7 +590,16 @@ export default {
|
||||
return categoryMap[Number(deptCategory)] || '-';
|
||||
},
|
||||
getLeaderValue(leaderId, field) {
|
||||
const values = func.split(leaderId);
|
||||
// func.split 空值时返回 '',需兼容为数组,避免表格 formatter 渲染崩溃导致列错位
|
||||
const splitResult = func.split(leaderId);
|
||||
const values = Array.isArray(splitResult)
|
||||
? splitResult
|
||||
: String(splitResult || '')
|
||||
.split(',')
|
||||
.filter(Boolean);
|
||||
if (!values.length) {
|
||||
return '-';
|
||||
}
|
||||
const result = values
|
||||
.map(id => this.leaderOptions.find(item => String(item.id) === String(id))?.[field])
|
||||
.filter(Boolean);
|
||||
@@ -660,6 +749,9 @@ export default {
|
||||
beforeOpen(done, type) {
|
||||
if (type === 'add') {
|
||||
this.form.tenantId = this.form.tenantId || this.userInfo.tenantId || website.tenantId;
|
||||
if (this.form.isPlatformCompany === undefined || this.form.isPlatformCompany === null) {
|
||||
this.form.isPlatformCompany = 0;
|
||||
}
|
||||
this.initData(this.form.tenantId);
|
||||
this.loadParentDeptCode(this.form.parentId);
|
||||
}
|
||||
@@ -676,7 +768,8 @@ export default {
|
||||
}
|
||||
this.loadParentDeptCode(this.form.parentId);
|
||||
if (this.form.hasOwnProperty('leaderId')) {
|
||||
this.form.leaderId = func.split(this.form.leaderId);
|
||||
const splitResult = func.split(this.form.leaderId);
|
||||
this.form.leaderId = Array.isArray(splitResult) ? splitResult : [];
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -47,7 +47,7 @@
|
||||
clearable
|
||||
:fetch-suggestions="fetchVehicleSuggestions"
|
||||
:disabled="dialogType === 'view'"
|
||||
placeholder="请选择车辆"
|
||||
placeholder="请选择车牌号"
|
||||
@input="handleVehicleInput"
|
||||
@select="handleVehicleSelect"
|
||||
@blur="handleVehicleBlur"
|
||||
@@ -66,12 +66,6 @@
|
||||
@change="handleDepartmentChange"
|
||||
/>
|
||||
</template>
|
||||
<template #attachments="{ row }">
|
||||
<span>{{ formatAttachments(row.attachments) }}</span>
|
||||
</template>
|
||||
<template #attachmentsForm>
|
||||
<vehicle-attachment-table v-model="form.attachments" :readonly="dialogType === 'view'" />
|
||||
</template>
|
||||
<template #attachments-form>
|
||||
<vehicle-attachment-table v-model="form.attachments" :readonly="dialogType === 'view'" />
|
||||
</template>
|
||||
@@ -225,7 +219,7 @@ export default {
|
||||
},
|
||||
rowSave(row, done, loading) {
|
||||
if (!this.selectedVehicleValue || row.plateNo !== this.selectedVehicleValue) {
|
||||
this.$message.warning('请选择调度车辆');
|
||||
this.$message.warning('请选择车牌号');
|
||||
loading();
|
||||
return;
|
||||
}
|
||||
@@ -247,6 +241,8 @@ export default {
|
||||
.then(res => {
|
||||
this.form = res.data.data || {};
|
||||
this.form.attachments = this.parseAttachments(this.form.attachments);
|
||||
this.form.applicantName = this.form.createUserName || '';
|
||||
this.form.applyTime = this.form.createTime || '';
|
||||
this.selectedVehicleValue = this.form.plateNo || '';
|
||||
this.deptCascaderValue = this.findDepartmentPath(this.form.useDepartment);
|
||||
done();
|
||||
@@ -258,9 +254,10 @@ export default {
|
||||
this.selectedVehicleValue = '';
|
||||
this.form.attachments = [];
|
||||
this.form.approvalStatus = 'draft';
|
||||
this.form.createUserName =
|
||||
this.form.vehicleType = '';
|
||||
this.form.applicantName =
|
||||
this.userInfo?.realName || this.userInfo?.userName || this.userInfo?.account || '';
|
||||
this.form.createTime = this.$dayjs().format('YYYY-MM-DD HH:mm:ss');
|
||||
this.form.applyTime = this.$dayjs().format('YYYY-MM-DD HH:mm:ss');
|
||||
done();
|
||||
}
|
||||
},
|
||||
@@ -315,6 +312,7 @@ export default {
|
||||
records.map(item => ({
|
||||
value: item.plateNo,
|
||||
organizationName: item.organizationName,
|
||||
vehicleType: item.vehicleType,
|
||||
}))
|
||||
);
|
||||
})
|
||||
@@ -323,19 +321,22 @@ export default {
|
||||
handleVehicleSelect(vehicle) {
|
||||
this.form.plateNo = vehicle.value;
|
||||
this.form.organizationName = vehicle.organizationName || '';
|
||||
this.form.vehicleType = vehicle.vehicleType || '';
|
||||
this.selectedVehicleValue = vehicle.value;
|
||||
},
|
||||
handleVehicleInput(value) {
|
||||
if (value !== this.selectedVehicleValue) {
|
||||
this.selectedVehicleValue = '';
|
||||
this.form.organizationName = '';
|
||||
this.form.vehicleType = '';
|
||||
}
|
||||
},
|
||||
handleVehicleBlur() {
|
||||
if (this.form.plateNo && this.form.plateNo !== this.selectedVehicleValue) {
|
||||
this.form.plateNo = '';
|
||||
this.form.organizationName = '';
|
||||
this.$message.warning('请从下拉列表中选择调度车辆');
|
||||
this.form.vehicleType = '';
|
||||
this.$message.warning('请从下拉列表中选择车牌号');
|
||||
}
|
||||
},
|
||||
handleSubmitApproval(row) {
|
||||
|
||||
@@ -79,11 +79,12 @@
|
||||
资金使用额度风险
|
||||
<el-tooltip placement="top" effect="dark">
|
||||
<template #content>
|
||||
<div>中风险:80% ≤ 客户资金额度使用率 < 90%</div>
|
||||
<div>中风险:客户资金额度使用率 ≥ 80%</div>
|
||||
<div>高风险:客户资金额度使用率 ≥ 90%</div>
|
||||
<div>使用率 = (累计付款金额 - 累计收票金额) / 最大资金使用额度 × 100%</div>
|
||||
</template>
|
||||
<i class="el-icon-question fund-use-risk-help" />
|
||||
<el-icon class="fund-use-risk-help">
|
||||
<el-icon-question-filled />
|
||||
</el-icon>
|
||||
</el-tooltip>
|
||||
</span>
|
||||
</template>
|
||||
@@ -927,6 +928,7 @@
|
||||
width="680px"
|
||||
class="contact-dialog"
|
||||
@closed="resetContact"
|
||||
@opened="syncContactRegionPath"
|
||||
>
|
||||
<section-card>
|
||||
<el-form
|
||||
@@ -934,7 +936,7 @@
|
||||
:model="contactForm"
|
||||
:rules="contactRules"
|
||||
label-position="right"
|
||||
label-width="110px"
|
||||
label-width="auto"
|
||||
class="contact-form"
|
||||
>
|
||||
<el-form-item label="联系人姓名" prop="contactName" class="contact-form__compact-field">
|
||||
@@ -946,30 +948,41 @@
|
||||
<el-form-item label="邮箱" prop="email" class="contact-form__compact-field">
|
||||
<el-input v-model="contactForm.email" maxlength="100" />
|
||||
</el-form-item>
|
||||
<el-form-item label="地址" prop="detailAddress">
|
||||
<div class="contact-form__address">
|
||||
<el-input
|
||||
v-model="contactForm.regionName"
|
||||
readonly
|
||||
maxlength="100"
|
||||
placeholder="行政区划自动带出"
|
||||
<el-form-item label="所属分公司/事业部" prop="branchName" class="contact-form__compact-field">
|
||||
<el-cascader
|
||||
v-model="contactBranchCascaderValue"
|
||||
:options="deptTree"
|
||||
:props="deptCascaderProps"
|
||||
clearable
|
||||
filterable
|
||||
placeholder="请选择所属分公司/事业部"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="省市区" prop="regionName" class="contact-form__compact-field">
|
||||
<el-cascader
|
||||
ref="contactRegionCascader"
|
||||
v-model="contactForm.regionPath"
|
||||
:options="regionOptions"
|
||||
:props="regionCascaderProps"
|
||||
:placeholder="contactForm.regionName || '请选择省市区'"
|
||||
clearable
|
||||
filterable
|
||||
@change="handleContactRegionChange"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="详细地址" prop="detailAddress" class="contact-form__compact-field">
|
||||
<el-input
|
||||
v-model="contactForm.detailAddress"
|
||||
readonly
|
||||
maxlength="255"
|
||||
placeholder="点击后弹出地图组件"
|
||||
suffix-icon="el-icon-location"
|
||||
@click="handleContactMapPick"
|
||||
placeholder="请输入详细地址"
|
||||
/>
|
||||
</div>
|
||||
</el-form-item>
|
||||
<el-form-item label="备注" prop="remark" class="contact-form__compact-field">
|
||||
<el-input
|
||||
v-model="contactForm.remark"
|
||||
type="textarea"
|
||||
:rows="2"
|
||||
maxlength="500"
|
||||
maxlength="200"
|
||||
show-word-limit
|
||||
/>
|
||||
</el-form-item>
|
||||
@@ -983,52 +996,6 @@
|
||||
</template>
|
||||
</el-dialog>
|
||||
|
||||
<el-dialog
|
||||
title="地图选择行政区划"
|
||||
append-to-body
|
||||
v-model="contactMapBox"
|
||||
width="920px"
|
||||
class="contact-form__map-dialog"
|
||||
@opened="initContactAmap"
|
||||
>
|
||||
<div class="contact-form__map-toolbar">
|
||||
<el-input
|
||||
v-model="contactMapKeyword"
|
||||
clearable
|
||||
placeholder="输入地址关键词"
|
||||
@keyup.enter="searchContactMapKeyword"
|
||||
/>
|
||||
<el-button
|
||||
type="primary"
|
||||
icon="el-icon-search"
|
||||
:loading="contactMapLoading"
|
||||
@click="searchContactMapKeyword"
|
||||
>
|
||||
搜索
|
||||
</el-button>
|
||||
</div>
|
||||
<div class="map-picker-content">
|
||||
<map-search-results :results="contactMapSearchResults" @select="selectContactMapSearchResult" />
|
||||
<div ref="contactAmap" class="contact-form__map"></div>
|
||||
</div>
|
||||
<div class="contact-form__map-info">
|
||||
<span>{{ contactMapStatus }}</span>
|
||||
<span v-if="contactMapSelected.regionName">
|
||||
行政区划:{{ contactMapSelected.regionName }}
|
||||
</span>
|
||||
</div>
|
||||
<template #footer>
|
||||
<el-button @click="contactMapBox = false">取消</el-button>
|
||||
<el-button
|
||||
type="primary"
|
||||
:disabled="!contactMapSelected.longitude"
|
||||
@click="confirmContactMapPick"
|
||||
>
|
||||
确定
|
||||
</el-button>
|
||||
</template>
|
||||
</el-dialog>
|
||||
|
||||
<el-dialog
|
||||
:title="receiptDialogTitle"
|
||||
append-to-body
|
||||
@@ -1554,9 +1521,6 @@ const createTimeRangeMap = {
|
||||
createTimeRange: ['createTimeStart', 'createTimeEnd'],
|
||||
};
|
||||
|
||||
const AMAP_KEY = '653b7cf105ad7fb8ec9b2f5198ade315';
|
||||
const AMAP_SECURITY_CODE = '5aab65c632e48ebae0d0e28f5b28e21a';
|
||||
let amapLoader;
|
||||
const qualificationViewerPlugins = [
|
||||
imagePlugin(),
|
||||
officePlugin({ pdf: { workerSrc: pdfWorkerSrc, useFetchData: true } }),
|
||||
@@ -1695,16 +1659,6 @@ export default {
|
||||
changeRecordDetailRows: [],
|
||||
contactIndex: -1,
|
||||
contactForm: this.emptyContact(),
|
||||
contactMapBox: false,
|
||||
contactMapLoading: false,
|
||||
contactMapKeyword: '',
|
||||
contactMapStatus: '可搜索地址或点击地图选点',
|
||||
contactMapSelected: {},
|
||||
contactMapSearchResults: [],
|
||||
contactMapTarget: 'contact',
|
||||
contactAmap: null,
|
||||
contactAmapMarker: null,
|
||||
contactAmapGeocoder: null,
|
||||
receiptIndex: -1,
|
||||
receiptForm: this.emptyReceiptAccount(),
|
||||
invoiceIndex: -1,
|
||||
@@ -1814,6 +1768,7 @@ export default {
|
||||
{ pattern: /^1\d{10}$/, message: '手机号必须为11位手机号', trigger: 'blur' },
|
||||
],
|
||||
email: [{ type: 'email', message: '请输入正确的邮箱', trigger: 'blur' }],
|
||||
branchName: [{ required: true, message: '请选择所属分公司/事业部', trigger: 'change' }],
|
||||
detailAddress: [{ max: 255, message: '详细地址最多255个字符', trigger: 'blur' }],
|
||||
remark: [{ max: 200, message: '备注最多200个字符', trigger: 'blur' }],
|
||||
},
|
||||
@@ -1980,7 +1935,9 @@ export default {
|
||||
label: '资金使用额度风险',
|
||||
prop: 'fundUseRisk',
|
||||
slot: true,
|
||||
minWidth: 150,
|
||||
headerslot: true,
|
||||
width: 200,
|
||||
overHidden: false,
|
||||
},
|
||||
{
|
||||
label: '审批状态',
|
||||
@@ -2097,6 +2054,20 @@ export default {
|
||||
this.onDeptChange(path);
|
||||
},
|
||||
},
|
||||
// 联系人所属分公司/事业部:表单存名称,级联控件用 id 路径
|
||||
contactBranchCascaderValue: {
|
||||
get() {
|
||||
return this.findDeptPathByName(this.contactForm.branchName);
|
||||
},
|
||||
set(path) {
|
||||
const ids = Array.isArray(path)
|
||||
? path.filter(item => item !== undefined && item !== null && item !== '')
|
||||
: [];
|
||||
const deptId = ids.length ? String(ids[ids.length - 1]) : '';
|
||||
this.contactForm.branchName = deptId ? this.findDeptLabel(deptId) : '';
|
||||
this.$refs.contactForm?.validateField('branchName');
|
||||
},
|
||||
},
|
||||
ids() {
|
||||
let ids = [];
|
||||
this.selectionList.forEach(ele => {
|
||||
@@ -2650,11 +2621,9 @@ export default {
|
||||
contactName: '',
|
||||
contactPhone: '',
|
||||
email: '',
|
||||
regionPath: [],
|
||||
regionName: '',
|
||||
regionCode: '',
|
||||
detailAddress: '',
|
||||
longitude: '',
|
||||
latitude: '',
|
||||
contactAddress: '',
|
||||
branchName: '',
|
||||
positionName: '',
|
||||
@@ -2948,6 +2917,67 @@ export default {
|
||||
}
|
||||
return '';
|
||||
},
|
||||
findDeptPathByName(name, tree = this.deptTree, parents = []) {
|
||||
const target = String(name || '').trim();
|
||||
if (!target) return [];
|
||||
for (const node of tree || []) {
|
||||
const path = [...parents, String(node.value)];
|
||||
if (String(node.label || '').trim() === target) return path;
|
||||
const matched = this.findDeptPathByName(name, node.children || [], path);
|
||||
if (matched.length) return matched;
|
||||
}
|
||||
return [];
|
||||
},
|
||||
getCurrentOrganizationName() {
|
||||
const currentDeptId = String(this.userInfo.deptId || this.userInfo.dept_id || '')
|
||||
.split(',')
|
||||
.map(item => item.trim())
|
||||
.filter(Boolean)[0];
|
||||
if (currentDeptId) {
|
||||
const label = this.findDeptLabel(currentDeptId);
|
||||
if (label) return label;
|
||||
}
|
||||
return this.userInfo.deptName || this.userInfo.dept_name || '';
|
||||
},
|
||||
findRegionPathByName(options = [], regionName = '', parents = []) {
|
||||
const target = String(regionName || '').replace(/\s+/g, '');
|
||||
if (!target) return [];
|
||||
for (const item of options || []) {
|
||||
const nextParents = [...parents, item];
|
||||
const nextName = nextParents.map(region => region.title || region.name || '').join('');
|
||||
if (nextName === target) {
|
||||
return nextParents.map(region => region.id);
|
||||
}
|
||||
const childPath = this.findRegionPathByName(item.children, target, nextParents);
|
||||
if (childPath.length) return childPath;
|
||||
}
|
||||
return [];
|
||||
},
|
||||
getContactRegionLabels(value) {
|
||||
const nodes = this.$refs.contactRegionCascader?.getCheckedNodes?.() || [];
|
||||
if (nodes.length && nodes[0].pathLabels?.length) {
|
||||
return nodes[0].pathLabels;
|
||||
}
|
||||
return (value || [])
|
||||
.map(code => this.findRegionOption(this.regionOptions, code)?.title)
|
||||
.filter(Boolean);
|
||||
},
|
||||
handleContactRegionChange(value) {
|
||||
if (!value || !value.length) {
|
||||
this.contactForm.regionName = '';
|
||||
return;
|
||||
}
|
||||
this.$nextTick(() => {
|
||||
this.contactForm.regionName = this.getContactRegionLabels(value).join('');
|
||||
});
|
||||
},
|
||||
syncContactRegionPath() {
|
||||
if (!this.contactForm.regionName || this.contactForm.regionPath?.length) return;
|
||||
const path = this.findRegionPathByName(this.regionOptions, this.contactForm.regionName);
|
||||
if (path.length) {
|
||||
this.contactForm.regionPath = path;
|
||||
}
|
||||
},
|
||||
renderDeptSearch(scope) {
|
||||
// 搜索区所属组织:单选级联(与新增/编辑表单一致)
|
||||
// 级联返回的是根到选中节点的 ID 路径数组,提交时取末级 ID 转部门名称
|
||||
@@ -3071,11 +3101,20 @@ export default {
|
||||
normalizeContact(contact = {}) {
|
||||
const contactAddress =
|
||||
contact.contactAddress || contact.address || this.formatContactAddress(contact);
|
||||
const regionPath = Array.isArray(contact.regionPath)
|
||||
? contact.regionPath
|
||||
: this.findRegionPathByName(this.regionOptions, contact.regionName);
|
||||
return {
|
||||
...this.emptyContact(),
|
||||
...contact,
|
||||
regionPath,
|
||||
contactAddress,
|
||||
branchName: contact.branchName || contact.deptName || this.archiveForm.deptName || '',
|
||||
branchName:
|
||||
contact.branchName ||
|
||||
contact.deptName ||
|
||||
this.getCurrentOrganizationName() ||
|
||||
this.archiveForm.deptName ||
|
||||
'',
|
||||
};
|
||||
},
|
||||
openContact(row, index = -1) {
|
||||
@@ -3084,9 +3123,10 @@ export default {
|
||||
? this.normalizeContact(row)
|
||||
: {
|
||||
...this.emptyContact(),
|
||||
branchName: this.archiveForm.deptName || '',
|
||||
branchName: this.getCurrentOrganizationName() || this.archiveForm.deptName || '',
|
||||
};
|
||||
this.contactBox = true;
|
||||
this.$nextTick(() => this.syncContactRegionPath());
|
||||
},
|
||||
resetContact() {
|
||||
this.contactIndex = -1;
|
||||
@@ -3100,6 +3140,7 @@ export default {
|
||||
...this.contactForm,
|
||||
contactAddress: this.formatContactAddress(this.contactForm),
|
||||
});
|
||||
delete contact.regionPath;
|
||||
if (this.contactIndex > -1) {
|
||||
this.archiveForm.contacts.splice(this.contactIndex, 1, contact);
|
||||
} else {
|
||||
@@ -3121,279 +3162,6 @@ export default {
|
||||
})
|
||||
.catch(() => {});
|
||||
},
|
||||
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;
|
||||
},
|
||||
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 => {
|
||||
this.contactMapSearchResults = (result.geocodes || []).map((item, index) => ({
|
||||
id: item.id || index,
|
||||
name: item.formattedAddress || keyword,
|
||||
address: item.formattedAddress || keyword,
|
||||
location: item.location,
|
||||
}));
|
||||
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('高德地图组件加载失败,请稍后重试或重新打开弹窗');
|
||||
});
|
||||
},
|
||||
selectContactMapSearchResult(item) {
|
||||
if (item && item.location) {
|
||||
this.pickContactMapPoint(item.location, item.address || item.name || '');
|
||||
}
|
||||
},
|
||||
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;
|
||||
}
|
||||
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;
|
||||
},
|
||||
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(),
|
||||
@@ -4020,7 +3788,12 @@ export default {
|
||||
contacts: (detail.contacts || []).map(item =>
|
||||
this.normalizeContact({
|
||||
...item,
|
||||
branchName: item.branchName || item.deptName || detail.deptName || '',
|
||||
branchName:
|
||||
item.branchName ||
|
||||
item.deptName ||
|
||||
this.getCurrentOrganizationName() ||
|
||||
detail.deptName ||
|
||||
'',
|
||||
})
|
||||
),
|
||||
receiptAccounts: (detail.receiptAccounts || []).map(item => this.normalizeReceipt(item)),
|
||||
@@ -4226,12 +3999,14 @@ export default {
|
||||
delete archive.registeredRegionPath;
|
||||
archive.qualificationAttachments = this.stringifyAttachments(this.qualificationFiles);
|
||||
archive.contacts = (archive.contacts || [])
|
||||
.map(item =>
|
||||
this.normalizeContact({
|
||||
.map(item => {
|
||||
const contact = this.normalizeContact({
|
||||
...item,
|
||||
contactAddress: this.formatContactAddress(item),
|
||||
});
|
||||
delete contact.regionPath;
|
||||
return contact;
|
||||
})
|
||||
)
|
||||
.filter(item => item.contactName || item.contactPhone);
|
||||
archive.receiptAccounts = (archive.receiptAccounts || [])
|
||||
.map(item => this.normalizeReceipt(item))
|
||||
@@ -5048,6 +4823,12 @@ export default {
|
||||
:deep(.el-table td .cell) {
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
// 表头含问号图标,避免被压缩成省略号
|
||||
:deep(.el-table th .cell:has(.fund-use-risk-header)) {
|
||||
overflow: visible;
|
||||
text-overflow: clip;
|
||||
}
|
||||
}
|
||||
|
||||
.archive-tag--deep-blue {
|
||||
@@ -5073,13 +4854,15 @@ export default {
|
||||
.fund-use-risk-header {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 4px;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.fund-use-risk-help {
|
||||
margin-left: 4px;
|
||||
color: #a8abb2;
|
||||
cursor: help;
|
||||
font-size: 14px;
|
||||
flex: none;
|
||||
}
|
||||
|
||||
.archive-form {
|
||||
@@ -5417,23 +5200,9 @@ export default {
|
||||
}
|
||||
}
|
||||
|
||||
.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__compact-field {
|
||||
:deep(.el-input) {
|
||||
:deep(.el-input),
|
||||
:deep(.el-cascader) {
|
||||
width: 60%;
|
||||
}
|
||||
}
|
||||
@@ -5463,32 +5232,6 @@ export default {
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.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;
|
||||
}
|
||||
|
||||
.score-detail-table {
|
||||
width: 100%;
|
||||
margin-top: 12px;
|
||||
|
||||
Reference in New Issue
Block a user