Compare commits
2 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| fc7df95c1a | |||
| 088c9cace0 |
+2
-3
@@ -3,9 +3,8 @@
|
|||||||
VITE_APP_ENV = 'development'
|
VITE_APP_ENV = 'development'
|
||||||
|
|
||||||
#接口地址
|
#接口地址
|
||||||
# 开发环境建议填 [/api]:由 vite.config.mjs 的 proxy 同源转发到本地网关 http://localhost(80)
|
# 开发环境建议填 [/api]:由下方 vite.config.mjs 的 proxy 同源转发到后端(172.16.203.228:8000),避免跨域(CORS)被浏览器拦截
|
||||||
# 本地需先启动:Nacos + blade-gateway(80) + blade-auth + blade-system 等;8080 端口一般是 Nacos 控制台不是网关
|
# 如需直连线上绝对地址(如 http://172.16.203.228:8000/api),必须让后端 CORS 把 Allow-Origin 改为具体前端域名,不能用 `*`(配合 credentials 会被浏览器拒绝)
|
||||||
# 如需临时联调远程,把 vite proxy target 改为 http://172.16.203.228:8000 并去掉 rewrite
|
|
||||||
VITE_APP_API=/api
|
VITE_APP_API=/api
|
||||||
|
|
||||||
#调试参数
|
#调试参数
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
.DS_Store
|
.DS_Store
|
||||||
node_modules
|
node_modules
|
||||||
pnpm-lock.yaml
|
|
||||||
/dist
|
/dist
|
||||||
|
|
||||||
/tests/e2e/videos/
|
/tests/e2e/videos/
|
||||||
|
|||||||
+2
-5
@@ -6,8 +6,7 @@
|
|||||||
"prod": "vite --mode production",
|
"prod": "vite --mode production",
|
||||||
"build": "vite build",
|
"build": "vite build",
|
||||||
"build:prod": "vite build --mode production",
|
"build:prod": "vite build --mode production",
|
||||||
"serve": "vite preview --host",
|
"serve": "vite preview --host"
|
||||||
"typecheck": "vue-tsc --noEmit"
|
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@element-plus/icons-vue": "^2.3.1",
|
"@element-plus/icons-vue": "^2.3.1",
|
||||||
@@ -47,11 +46,9 @@
|
|||||||
"prettier": "^2.8.7",
|
"prettier": "^2.8.7",
|
||||||
"sass": "^1.85.1",
|
"sass": "^1.85.1",
|
||||||
"terser": "^5.31.1",
|
"terser": "^5.31.1",
|
||||||
"typescript": "^7.0.2",
|
|
||||||
"unplugin-auto-import": "^0.11.2",
|
"unplugin-auto-import": "^0.11.2",
|
||||||
"vite": "^5.4.19",
|
"vite": "^5.4.19",
|
||||||
"vite-plugin-compression": "^0.5.1",
|
"vite-plugin-compression": "^0.5.1",
|
||||||
"vite-plugin-vue-setup-extend": "^0.4.0",
|
"vite-plugin-vue-setup-extend": "^0.4.0"
|
||||||
"vue-tsc": "^3.3.11"
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Generated
+4259
File diff suppressed because it is too large
Load Diff
@@ -58,14 +58,3 @@ export const changeStatus = (id, status) => {
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
export const getBfiExchangeRate = (currencyCode, effectdate) => {
|
|
||||||
return request({
|
|
||||||
url: '/blade-transport/bfi/exchange-rate',
|
|
||||||
method: 'get',
|
|
||||||
params: {
|
|
||||||
currencyCode,
|
|
||||||
effectdate,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ export const approve = data => request({ url: `${baseUrl}/approve`, method: 'pos
|
|||||||
export const returnBill = data => request({ url: `${baseUrl}/return`, method: 'post', data });
|
export const returnBill = data => request({ url: `${baseUrl}/return`, method: 'post', data });
|
||||||
export const voidBill = data => request({ url: `${baseUrl}/void`, method: 'post', data });
|
export const voidBill = data => request({ url: `${baseUrl}/void`, method: 'post', data });
|
||||||
export const syncKingdee = id =>
|
export const syncKingdee = id =>
|
||||||
request({ url: `${baseUrl}/sync-kingdee`, method: 'post', timeout: 30000, params: { id } });
|
request({ url: `${baseUrl}/sync-kingdee`, method: 'post', params: { id } });
|
||||||
|
|
||||||
export const invoiceTypeOptions = [
|
export const invoiceTypeOptions = [
|
||||||
{ label: '电子专票', value: 'electronic_special' },
|
{ label: '电子专票', value: 'electronic_special' },
|
||||||
|
|||||||
@@ -5,17 +5,8 @@ const baseUrl = '/blade-transport/invoice-receipt';
|
|||||||
export const getList = (current, size, params) =>
|
export const getList = (current, size, params) =>
|
||||||
request({ url: `${baseUrl}/list`, method: 'get', params: { current, size, ...params } });
|
request({ url: `${baseUrl}/list`, method: 'get', params: { current, size, ...params } });
|
||||||
export const getDetail = id => request({ url: `${baseUrl}/detail`, method: 'get', params: { id } });
|
export const getDetail = id => request({ url: `${baseUrl}/detail`, method: 'get', params: { id } });
|
||||||
export const queryInvoicePool = params =>
|
export const getInvoicePool = keyword =>
|
||||||
request({ url: `${baseUrl}/invoice-pool`, method: 'get', params });
|
request({ url: `${baseUrl}/invoice-pool`, method: 'get', params: { keyword } });
|
||||||
export const downloadInvoicePoolAttachment = invoiceNo =>
|
|
||||||
// 金蝶票池发票文件下载(后端代理 A801-2 流式回源),耗时较普通接口长,放宽到 120s
|
|
||||||
request({
|
|
||||||
url: `${baseUrl}/invoice-pool/attachment/download`,
|
|
||||||
method: 'get',
|
|
||||||
params: { invoiceNo },
|
|
||||||
responseType: 'blob',
|
|
||||||
timeout: 120000,
|
|
||||||
});
|
|
||||||
export const getSettlementCandidates = (keyword, receiptId) =>
|
export const getSettlementCandidates = (keyword, receiptId) =>
|
||||||
request({
|
request({
|
||||||
url: `${baseUrl}/settlement-candidates`,
|
url: `${baseUrl}/settlement-candidates`,
|
||||||
@@ -34,8 +25,8 @@ export const submit = data => request({ url: `${baseUrl}/submit`, method: 'post'
|
|||||||
export const approve = data => request({ url: `${baseUrl}/approve`, method: 'post', data });
|
export const approve = data => request({ url: `${baseUrl}/approve`, method: 'post', data });
|
||||||
export const returnBill = data => request({ url: `${baseUrl}/return`, method: 'post', data });
|
export const returnBill = data => request({ url: `${baseUrl}/return`, method: 'post', data });
|
||||||
export const voidBill = data => request({ url: `${baseUrl}/void`, method: 'post', data });
|
export const voidBill = data => request({ url: `${baseUrl}/void`, method: 'post', data });
|
||||||
export const pushKingdee = ids =>
|
export const syncKingdee = id =>
|
||||||
request({ url: `${baseUrl}/push-kingdee`, method: 'post', timeout: 30000, data: ids });
|
request({ url: `${baseUrl}/sync-kingdee`, method: 'post', params: { id } });
|
||||||
|
|
||||||
export const approvalStatusOptions = [
|
export const approvalStatusOptions = [
|
||||||
{ label: '草稿', value: 'draft' },
|
{ label: '草稿', value: 'draft' },
|
||||||
@@ -45,9 +36,8 @@ export const approvalStatusOptions = [
|
|||||||
{ label: '已作废', value: 'voided' },
|
{ label: '已作废', value: 'voided' },
|
||||||
];
|
];
|
||||||
|
|
||||||
export const kingdeePushStatusOptions = [
|
export const kingdeeStatusOptions = [
|
||||||
{ label: '未推送', value: 'unsynced' },
|
{ label: '未同步', value: 'unsynced' },
|
||||||
{ label: '已保存收票单', value: 'pushed' },
|
{ label: '已同步', value: 'synced' },
|
||||||
{ label: '已指定应付', value: 'assigned' },
|
{ label: '同步失败', value: 'failed' },
|
||||||
{ label: '推送失败', value: 'failed' },
|
|
||||||
];
|
];
|
||||||
|
|||||||
@@ -16,20 +16,15 @@ export const submit = data => request({ url: `${baseUrl}/submit`, method: 'post'
|
|||||||
export const approve = data => request({ url: `${baseUrl}/approve`, method: 'post', data });
|
export const approve = data => request({ url: `${baseUrl}/approve`, method: 'post', data });
|
||||||
export const returnBill = data => request({ url: `${baseUrl}/return`, method: 'post', data });
|
export const returnBill = data => request({ url: `${baseUrl}/return`, method: 'post', data });
|
||||||
export const voidBill = data => request({ url: `${baseUrl}/void`, method: 'post', data });
|
export const voidBill = data => request({ url: `${baseUrl}/void`, method: 'post', data });
|
||||||
// 同步金蝶涉及多次金蝶远程接口调用(A103-2 预检 + A103-1/A103-3 推送),单笔可达 20s+,覆盖全局 10s 超时
|
|
||||||
export const syncKingdee = id =>
|
export const syncKingdee = id =>
|
||||||
request({ url: `${baseUrl}/sync-kingdee`, method: 'post', timeout: 30000, params: { id } });
|
request({ url: `${baseUrl}/sync-kingdee`, method: 'post', params: { id } });
|
||||||
// 批量同步逐单单据级事务串行推送,耗时随单量线性增长
|
|
||||||
export const syncKingdeeBatch = ids =>
|
export const syncKingdeeBatch = ids =>
|
||||||
request({ url: `${baseUrl}/sync-kingdee-batch`, method: 'post', timeout: 30000, data: ids });
|
request({ url: `${baseUrl}/sync-kingdee-batch`, method: 'post', data: ids });
|
||||||
// 回写任务按 paymentNo 分批远程查询 A103-2/A103-4,同样可能超过全局超时
|
|
||||||
export const syncKingdeeResult = () =>
|
|
||||||
request({ url: `${baseUrl}/sync-kingdee-result`, method: 'post', timeout: 30000 });
|
|
||||||
|
|
||||||
export const paymentTypeOptions = [
|
export const paymentTypeOptions = [
|
||||||
{ label: '项目预付', value: 'project_advance' },
|
{ label: '项目预付', value: 'project_advance' },
|
||||||
{ label: '进度预付', value: 'progress_advance' },
|
{ label: '进度预付', value: 'progress_advance' },
|
||||||
{ label: '尾款付款', value: 'settlement_payment' },
|
{ label: '结算付款', value: 'settlement_payment' },
|
||||||
];
|
];
|
||||||
export const approvalStatusOptions = [
|
export const approvalStatusOptions = [
|
||||||
{ label: '草稿', value: 'draft' },
|
{ label: '草稿', value: 'draft' },
|
||||||
@@ -41,8 +36,5 @@ export const approvalStatusOptions = [
|
|||||||
export const kingdeeStatusOptions = [
|
export const kingdeeStatusOptions = [
|
||||||
{ label: '未生成', value: 'unsynced' },
|
{ label: '未生成', value: 'unsynced' },
|
||||||
{ label: '已生成', value: 'synced' },
|
{ label: '已生成', value: 'synced' },
|
||||||
{ label: '付款中', value: 'paying' },
|
|
||||||
{ label: '已付款', value: 'paid' },
|
|
||||||
{ label: '已关闭', value: 'closed' },
|
|
||||||
{ label: '生成失败', value: 'failed' },
|
{ label: '生成失败', value: 'failed' },
|
||||||
];
|
];
|
||||||
|
|||||||
@@ -8,8 +8,6 @@ export const getDetail = id => request({ url: `${baseUrl}/detail`, method: 'get'
|
|||||||
export const updateAttachments = data =>
|
export const updateAttachments = data =>
|
||||||
request({ url: `${baseUrl}/attachments`, method: 'post', data });
|
request({ url: `${baseUrl}/attachments`, method: 'post', data });
|
||||||
export const voidClaim = id => request({ url: `${baseUrl}/void`, method: 'post', params: { id } });
|
export const voidClaim = id => request({ url: `${baseUrl}/void`, method: 'post', params: { id } });
|
||||||
export const queryKingdeeResult = () =>
|
|
||||||
request({ url: `${baseUrl}/query-kingdee-result`, method: 'post' });
|
|
||||||
|
|
||||||
export const claimStatusOptions = [
|
export const claimStatusOptions = [
|
||||||
{ label: '已认领', value: 'claimed' },
|
{ label: '已认领', value: 'claimed' },
|
||||||
@@ -18,8 +16,6 @@ export const claimStatusOptions = [
|
|||||||
|
|
||||||
export const kingdeeBillStatusOptions = [
|
export const kingdeeBillStatusOptions = [
|
||||||
{ label: '未生成', value: 'none' },
|
{ label: '未生成', value: 'none' },
|
||||||
{ label: '已推送', value: 'pushed' },
|
|
||||||
{ label: '审核通过', value: 'approved' },
|
{ label: '审核通过', value: 'approved' },
|
||||||
{ label: '已收款', value: 'received' },
|
|
||||||
{ label: '处理失败', value: 'failed' },
|
{ label: '处理失败', value: 'failed' },
|
||||||
];
|
];
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ export const getSettlementCandidates = (keyword, flowId) =>
|
|||||||
params: { keyword, flowId },
|
params: { keyword, flowId },
|
||||||
});
|
});
|
||||||
export const claim = data => request({ url: `${baseUrl}/claim`, method: 'post', data });
|
export const claim = data => request({ url: `${baseUrl}/claim`, method: 'post', data });
|
||||||
export const sync = () => request({ url: `${baseUrl}/sync`, method: 'post' });
|
export const sync = data => request({ url: `${baseUrl}/sync`, method: 'post', data });
|
||||||
|
|
||||||
export const claimStatusOptions = [
|
export const claimStatusOptions = [
|
||||||
{ label: '未认领', value: 'unclaimed' },
|
{ label: '未认领', value: 'unclaimed' },
|
||||||
|
|||||||
@@ -30,9 +30,8 @@ export const submit = data => request({ url: `${baseUrl}/submit`, method: 'post'
|
|||||||
export const approve = data => request({ url: `${baseUrl}/approve`, method: 'post', data });
|
export const approve = data => request({ url: `${baseUrl}/approve`, method: 'post', data });
|
||||||
export const returnBill = data => request({ url: `${baseUrl}/return`, method: 'post', data });
|
export const returnBill = data => request({ url: `${baseUrl}/return`, method: 'post', data });
|
||||||
export const voidBill = data => request({ url: `${baseUrl}/void`, method: 'post', data });
|
export const voidBill = data => request({ url: `${baseUrl}/void`, method: 'post', data });
|
||||||
// 同步金蝶涉及多次金蝶远程接口调用(A102-4 预检 + A102-2 保存),可达 20s+,覆盖全局 10s 超时
|
|
||||||
export const syncKingdee = id =>
|
export const syncKingdee = id =>
|
||||||
request({ url: `${baseUrl}/sync-kingdee`, method: 'post', timeout: 30000, params: { id } });
|
request({ url: `${baseUrl}/sync-kingdee`, method: 'post', params: { id } });
|
||||||
export const getDetailFees = detailId =>
|
export const getDetailFees = detailId =>
|
||||||
request({ url: `${baseUrl}/detail-fees`, method: 'get', params: { detailId } });
|
request({ url: `${baseUrl}/detail-fees`, method: 'get', params: { detailId } });
|
||||||
export const getReceiptClaims = formalSettlementId =>
|
export const getReceiptClaims = formalSettlementId =>
|
||||||
@@ -49,5 +48,3 @@ export const applyPayments = data =>
|
|||||||
request({ url: `${baseUrl}/apply-payments`, method: 'post', data });
|
request({ url: `${baseUrl}/apply-payments`, method: 'post', data });
|
||||||
export const claimInvoices = data =>
|
export const claimInvoices = data =>
|
||||||
request({ url: `${baseUrl}/claim-invoices`, method: 'post', data });
|
request({ url: `${baseUrl}/claim-invoices`, method: 'post', data });
|
||||||
export const queryInvoicePool = params =>
|
|
||||||
request({ url: `${baseUrl}/invoice-pool`, method: 'get', params });
|
|
||||||
|
|||||||
@@ -48,15 +48,6 @@ export const syncIamOrganizations = () => {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
export const clearNonTopDept = signal => {
|
|
||||||
return request({
|
|
||||||
url: '/blade-system/dept/clear-non-top',
|
|
||||||
method: 'post',
|
|
||||||
timeout: 60000,
|
|
||||||
signal,
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
export const syncOaCompany = (current = 1, size = 20, signal) => {
|
export const syncOaCompany = (current = 1, size = 20, signal) => {
|
||||||
return request({
|
return request({
|
||||||
url: '/blade-system/dept/sync-oa-company',
|
url: '/blade-system/dept/sync-oa-company',
|
||||||
|
|||||||
@@ -152,28 +152,6 @@ export const getScoreTemplate = quantificationId => {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
export const queryKingdeeCustomer = creditCode => {
|
|
||||||
return request({
|
|
||||||
url: '/blade-transport/bfi/customer/query-by-credit-code',
|
|
||||||
method: 'get',
|
|
||||||
timeout: 30000,
|
|
||||||
params: {
|
|
||||||
creditCode,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
export const syncKingdee = id => {
|
|
||||||
return request({
|
|
||||||
url: '/blade-transport/customer-archive/sync-kingdee',
|
|
||||||
method: 'post',
|
|
||||||
timeout: 30000,
|
|
||||||
params: {
|
|
||||||
id,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
export const recognizeBusinessLicenseOcr = imageUrl => {
|
export const recognizeBusinessLicenseOcr = imageUrl => {
|
||||||
return request({
|
return request({
|
||||||
url: '/blade-transport/baidu-ocr/recognize-url',
|
url: '/blade-transport/baidu-ocr/recognize-url',
|
||||||
|
|||||||
+56
-20
@@ -70,11 +70,44 @@ const redirectToLogin = () => {
|
|||||||
store.dispatch('FedLogOut').then(() => router.push({ path: '/login' }));
|
store.dispatch('FedLogOut').then(() => router.push({ path: '/login' }));
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 统一弹出错误提示并返回带标记的 Error。
|
||||||
|
* `_notified` 标记用于让业务层(批量导入等)识别「拦截器已经提示过」,
|
||||||
|
* 避免同一条错误被弹两次。
|
||||||
|
*/
|
||||||
|
const notifiedError = message => {
|
||||||
|
ElMessage({
|
||||||
|
message: message,
|
||||||
|
type: 'error',
|
||||||
|
});
|
||||||
|
const error = new Error(message);
|
||||||
|
error._notified = true;
|
||||||
|
return error;
|
||||||
|
};
|
||||||
|
|
||||||
axios.defaults.timeout = 10000;
|
axios.defaults.timeout = 10000;
|
||||||
//返回其他状态码
|
//返回其他状态码
|
||||||
axios.defaults.validateStatus = function (status) {
|
axios.defaults.validateStatus = function (status) {
|
||||||
return status >= 200 && status <= 500; // 默认的
|
return status >= 200 && status <= 500; // 默认的
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 判断响应是否为 JSON。
|
||||||
|
* 用于 blob 请求(导入/导出)的兜底:后端在业务校验失败时返回的是 JSON
|
||||||
|
* (Content-Type: application/json),而不是文件流,需要解析后才能取到 msg。
|
||||||
|
*/
|
||||||
|
const isJsonContentType = res => {
|
||||||
|
const headers = res.headers || {};
|
||||||
|
const contentType = String(headers['content-type'] || res.data?.type || '').toLowerCase();
|
||||||
|
return contentType.includes('application/json') || contentType.includes('text/json');
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 判断响应体是否已是解析好的对象(无需再解析)。
|
||||||
|
* 注意 Blob 也是 object,必须排除,否则会把待解析的文件流误判为已完成。
|
||||||
|
*/
|
||||||
|
const isPlainObject = value =>
|
||||||
|
value !== null && typeof value === 'object' && !(value instanceof Blob);
|
||||||
//跨域请求,允许保存cookie
|
//跨域请求,允许保存cookie
|
||||||
axios.defaults.withCredentials = true;
|
axios.defaults.withCredentials = true;
|
||||||
// NProgress Configuration
|
// NProgress Configuration
|
||||||
@@ -140,7 +173,7 @@ axios.interceptors.request.use(
|
|||||||
|
|
||||||
//http response拦截
|
//http response拦截
|
||||||
axios.interceptors.response.use(
|
axios.interceptors.response.use(
|
||||||
res => {
|
async res => {
|
||||||
NProgress.done();
|
NProgress.done();
|
||||||
//获取配置信息
|
//获取配置信息
|
||||||
const config = res.config;
|
const config = res.config;
|
||||||
@@ -149,6 +182,22 @@ axios.interceptors.response.use(
|
|||||||
if (cryptoData) {
|
if (cryptoData) {
|
||||||
res.data = JSON.parse(crypto.decryptAES(res.data, crypto.aesKey));
|
res.data = JSON.parse(crypto.decryptAES(res.data, crypto.aesKey));
|
||||||
}
|
}
|
||||||
|
// blob 请求(文件下载、导入)的响应体是 Blob,直接读 res.data.code / res.data.msg
|
||||||
|
// 拿不到任何字段:状态码会退回 res.status、提示文案会落到「系统错误」兜底,
|
||||||
|
// 用户就会以为系统崩了——而后端其实返回了明确的业务提示(如「未读取到数据行…」)。
|
||||||
|
// 这里按 Content-Type 判断:JSON 才解析回对象;真·文件流(Excel 等)保持原样不动。
|
||||||
|
// 兼容 Blob(浏览器 responseType='blob')与 string(非浏览器环境)两种形态。
|
||||||
|
if (isJsonContentType(res) && !isPlainObject(res.data)) {
|
||||||
|
try {
|
||||||
|
const text = res.data instanceof Blob ? await res.data.text() : res.data;
|
||||||
|
const parsed = typeof text === 'string' && text ? JSON.parse(text) : null;
|
||||||
|
if (parsed && typeof parsed === 'object') {
|
||||||
|
res.data = parsed;
|
||||||
|
}
|
||||||
|
} catch (ignored) {
|
||||||
|
// 声明是 JSON 却解析失败:保持原样,交给下游按文件处理
|
||||||
|
}
|
||||||
|
}
|
||||||
//获取状态信息
|
//获取状态信息
|
||||||
const status = res.data.error_code || res.data.code || res.status;
|
const status = res.data.error_code || res.data.code || res.status;
|
||||||
const statusWhiteList = website.statusWhiteList || [];
|
const statusWhiteList = website.statusWhiteList || [];
|
||||||
@@ -204,34 +253,21 @@ axios.interceptors.response.use(
|
|||||||
|
|
||||||
// OAuth2 业务错误码提示
|
// OAuth2 业务错误码提示
|
||||||
if (status > 2000 && !validatenull(res.data.error_description)) {
|
if (status > 2000 && !validatenull(res.data.error_description)) {
|
||||||
ElMessage({
|
return Promise.reject(notifiedError(message));
|
||||||
message: message,
|
|
||||||
type: 'error',
|
|
||||||
});
|
|
||||||
return Promise.reject(new Error(message));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 其余非 200 响应统一提示
|
// 其余非 200 响应统一提示
|
||||||
if (status !== 200) {
|
if (status !== 200) {
|
||||||
ElMessage({
|
return Promise.reject(notifiedError(message));
|
||||||
message: message,
|
|
||||||
type: 'error',
|
|
||||||
});
|
|
||||||
return Promise.reject(new Error(message));
|
|
||||||
}
|
}
|
||||||
return res;
|
return res;
|
||||||
},
|
},
|
||||||
error => {
|
error => {
|
||||||
NProgress.done();
|
NProgress.done();
|
||||||
// 网络级失败(HTTP 状态 >500、网关错误、超时、断网等 validateStatus 放行之外的响应)
|
// 保留原始 axios 错误对象:下游(如导入失败明细解析)需要 error.response
|
||||||
// 原为静默 reject,接口报错页面无任何感知;统一补错误提示,取消请求与刷新令牌请求除外
|
// 读取响应体。若包装成 new Error(error),response 会丢失、message 退化成
|
||||||
if (!axios.isCancel(error) && !isRefreshTokenRequest(error?.config)) {
|
// "[object Object]",用户只能看到无意义的通用提示。
|
||||||
ElMessage({
|
return Promise.reject(error);
|
||||||
message: error?.response?.data?.msg || '请求失败,请稍后重试',
|
|
||||||
type: 'error',
|
|
||||||
});
|
|
||||||
}
|
|
||||||
return Promise.reject(new Error(error));
|
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|||||||
@@ -10,8 +10,12 @@
|
|||||||
<el-button type="primary" :loading="searching" @click="searchKeyword">搜索</el-button>
|
<el-button type="primary" :loading="searching" @click="searchKeyword">搜索</el-button>
|
||||||
</div>
|
</div>
|
||||||
<div class="address-map-picker__content">
|
<div class="address-map-picker__content">
|
||||||
|
<map-search-results
|
||||||
|
v-if="searchResults.length"
|
||||||
|
:results="searchResults"
|
||||||
|
@select="selectSearchResult"
|
||||||
|
/>
|
||||||
<div ref="map" class="address-map-picker__map"></div>
|
<div ref="map" class="address-map-picker__map"></div>
|
||||||
<map-search-results :results="searchResults" @select="selectSearchResult" />
|
|
||||||
</div>
|
</div>
|
||||||
<div class="address-map-picker__info">{{ status }}</div>
|
<div class="address-map-picker__info">{{ status }}</div>
|
||||||
<template #footer>
|
<template #footer>
|
||||||
@@ -24,8 +28,6 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import { normalizeMapSearchResults } from '@/utils/map-search';
|
|
||||||
|
|
||||||
const AMAP_KEY = '653b7cf105ad7fb8ec9b2f5198ade315';
|
const AMAP_KEY = '653b7cf105ad7fb8ec9b2f5198ade315';
|
||||||
const AMAP_SECURITY_CODE = '5aab65c632e48ebae0d0e28f5b28e21a';
|
const AMAP_SECURITY_CODE = '5aab65c632e48ebae0d0e28f5b28e21a';
|
||||||
let amapLoader;
|
let amapLoader;
|
||||||
@@ -40,14 +42,6 @@ export default {
|
|||||||
type: String,
|
type: String,
|
||||||
default: '',
|
default: '',
|
||||||
},
|
},
|
||||||
longitude: {
|
|
||||||
type: [String, Number],
|
|
||||||
default: '',
|
|
||||||
},
|
|
||||||
latitude: {
|
|
||||||
type: [String, Number],
|
|
||||||
default: '',
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
emits: ['update:modelValue', 'confirm'],
|
emits: ['update:modelValue', 'confirm'],
|
||||||
data() {
|
data() {
|
||||||
@@ -72,11 +66,9 @@ export default {
|
|||||||
this.visible = value;
|
this.visible = value;
|
||||||
if (value) {
|
if (value) {
|
||||||
this.keyword = this.address || '';
|
this.keyword = this.address || '';
|
||||||
|
this.selected = {};
|
||||||
|
this.status = '可搜索地址或点击地图选点';
|
||||||
this.searchResults = [];
|
this.searchResults = [];
|
||||||
this.selected = this.buildInitialSelection();
|
|
||||||
this.status = this.selected.longitude
|
|
||||||
? this.selected.address || '已选点,可确认回填'
|
|
||||||
: '可搜索地址或点击地图选点';
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@@ -90,18 +82,6 @@ export default {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
buildInitialSelection() {
|
|
||||||
const longitude = Number(this.longitude);
|
|
||||||
const latitude = Number(this.latitude);
|
|
||||||
if (!Number.isFinite(longitude) || !Number.isFinite(latitude)) {
|
|
||||||
return {};
|
|
||||||
}
|
|
||||||
return {
|
|
||||||
longitude,
|
|
||||||
latitude,
|
|
||||||
address: this.address || '',
|
|
||||||
};
|
|
||||||
},
|
|
||||||
loadAmap() {
|
loadAmap() {
|
||||||
if (window.AMap && window.AMap.Map) {
|
if (window.AMap && window.AMap.Map) {
|
||||||
return Promise.resolve();
|
return Promise.resolve();
|
||||||
@@ -130,27 +110,15 @@ export default {
|
|||||||
return new Promise(resolve => {
|
return new Promise(resolve => {
|
||||||
this.$nextTick(() => {
|
this.$nextTick(() => {
|
||||||
if (!this.$refs.map) return resolve(null);
|
if (!this.$refs.map) return resolve(null);
|
||||||
const initial = this.buildInitialSelection();
|
|
||||||
const center =
|
|
||||||
Number.isFinite(initial.longitude) && Number.isFinite(initial.latitude)
|
|
||||||
? [initial.longitude, initial.latitude]
|
|
||||||
: [116.40769, 39.89945];
|
|
||||||
if (!this.amap) {
|
if (!this.amap) {
|
||||||
this.amap = new window.AMap.Map(this.$refs.map, {
|
this.amap = new window.AMap.Map(this.$refs.map, {
|
||||||
center,
|
center: [116.40769, 39.89945],
|
||||||
zoom: initial.longitude ? 14 : 11,
|
zoom: 11,
|
||||||
});
|
});
|
||||||
this.amap.on('click', event => this.pickPoint(event.lnglat));
|
this.amap.on('click', event => this.pickPoint(event.lnglat));
|
||||||
} else {
|
} else {
|
||||||
this.amap.resize();
|
this.amap.resize();
|
||||||
this.clearMarker();
|
this.clearMarker();
|
||||||
this.amap.setZoomAndCenter(initial.longitude ? 14 : 11, center);
|
|
||||||
}
|
|
||||||
if (initial.longitude) {
|
|
||||||
const point = new window.AMap.LngLat(initial.longitude, initial.latitude);
|
|
||||||
this.renderMarker(point);
|
|
||||||
this.selected = { ...initial };
|
|
||||||
this.status = initial.address || '已选点,可确认回填';
|
|
||||||
}
|
}
|
||||||
resolve(this.amap);
|
resolve(this.amap);
|
||||||
});
|
});
|
||||||
@@ -212,14 +180,19 @@ export default {
|
|||||||
.then(result => {
|
.then(result => {
|
||||||
if (!result) return;
|
if (!result) return;
|
||||||
const geocodes = result.geocodes || (result.location ? [result] : []);
|
const geocodes = result.geocodes || (result.location ? [result] : []);
|
||||||
this.searchResults = normalizeMapSearchResults(geocodes, keyword);
|
this.searchResults = geocodes.map((item, index) => ({
|
||||||
const point = this.searchResults[0]?.location || result.location;
|
id: item.id || index,
|
||||||
|
name: item.formattedAddress || item.address || keyword,
|
||||||
|
address: item.formattedAddress || item.address || keyword,
|
||||||
|
location: item.location,
|
||||||
|
}));
|
||||||
|
const point = geocodes[0]?.location || result.location;
|
||||||
if (!point) {
|
if (!point) {
|
||||||
throw new Error('未找到匹配地址');
|
throw new Error('未找到匹配地址');
|
||||||
}
|
}
|
||||||
return this.$nextTick().then(() => {
|
return this.$nextTick().then(() => {
|
||||||
this.amap?.resize();
|
this.amap?.resize();
|
||||||
this.pickPoint(point, this.searchResults[0]?.address || keyword);
|
this.pickPoint(point, geocodes[0]?.formattedAddress || keyword);
|
||||||
});
|
});
|
||||||
})
|
})
|
||||||
.catch(() => {
|
.catch(() => {
|
||||||
@@ -281,19 +254,11 @@ export default {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
confirm() {
|
confirm() {
|
||||||
if (!this.selected.longitude || !this.selected.latitude) {
|
|
||||||
this.$message.warning('请先搜索或点击地图完成选点');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (!this.selected.address) {
|
if (!this.selected.address) {
|
||||||
this.$message.warning('请等待地址解析完成后再确认');
|
this.$message.warning('请等待地址解析完成后再确认');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
this.$emit('confirm', {
|
this.$emit('confirm', this.selected.address);
|
||||||
address: this.selected.address,
|
|
||||||
longitude: this.selected.longitude,
|
|
||||||
latitude: this.selected.latitude,
|
|
||||||
});
|
|
||||||
this.visible = false;
|
this.visible = false;
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@@ -313,6 +278,7 @@ export default {
|
|||||||
}
|
}
|
||||||
|
|
||||||
&__map {
|
&__map {
|
||||||
|
flex: 1 1 auto;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
min-width: 0;
|
min-width: 0;
|
||||||
height: 420px;
|
height: 420px;
|
||||||
@@ -320,19 +286,9 @@ export default {
|
|||||||
}
|
}
|
||||||
|
|
||||||
&__content {
|
&__content {
|
||||||
position: relative;
|
display: flex;
|
||||||
display: block;
|
gap: 12px;
|
||||||
min-width: 0;
|
min-width: 0;
|
||||||
overflow: visible;
|
|
||||||
|
|
||||||
.address-map-picker__map {
|
|
||||||
position: relative;
|
|
||||||
z-index: 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
:deep(.map-search-results) {
|
|
||||||
z-index: 2000;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
&__info {
|
&__info {
|
||||||
|
|||||||
@@ -1,108 +0,0 @@
|
|||||||
<template>
|
|
||||||
<el-dialog
|
|
||||||
:model-value="modelValue"
|
|
||||||
title="变更记录详情"
|
|
||||||
append-to-body
|
|
||||||
destroy-on-close
|
|
||||||
align-center
|
|
||||||
width="1100px"
|
|
||||||
class="change-record-detail-dialog"
|
|
||||||
@update:model-value="$emit('update:modelValue', $event)"
|
|
||||||
@open="resetPage"
|
|
||||||
>
|
|
||||||
<el-table
|
|
||||||
:data="pageRows"
|
|
||||||
border
|
|
||||||
max-height="60vh"
|
|
||||||
:show-overflow-tooltip="false"
|
|
||||||
>
|
|
||||||
<el-table-column prop="field" label="变更字段" min-width="180" />
|
|
||||||
<el-table-column
|
|
||||||
prop="before"
|
|
||||||
label="变更前"
|
|
||||||
min-width="360"
|
|
||||||
class-name="change-record-detail-value"
|
|
||||||
/>
|
|
||||||
<el-table-column
|
|
||||||
prop="after"
|
|
||||||
label="变更后"
|
|
||||||
min-width="500"
|
|
||||||
class-name="change-record-detail-value"
|
|
||||||
/>
|
|
||||||
</el-table>
|
|
||||||
<el-empty v-if="!total" description="暂无变更内容" :image-size="60" />
|
|
||||||
<div class="change-record-detail-dialog__pager">
|
|
||||||
<el-pagination
|
|
||||||
v-model:current-page="currentPage"
|
|
||||||
v-model:page-size="pageSize"
|
|
||||||
background
|
|
||||||
:total="total"
|
|
||||||
:page-sizes="[10, 20, 50, 100]"
|
|
||||||
layout="total, sizes, prev, pager, next, jumper"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<template #footer>
|
|
||||||
<el-button type="primary" @click="$emit('update:modelValue', false)">关闭</el-button>
|
|
||||||
</template>
|
|
||||||
</el-dialog>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
export default {
|
|
||||||
name: 'ChangeRecordDetailDialog',
|
|
||||||
props: {
|
|
||||||
modelValue: {
|
|
||||||
type: Boolean,
|
|
||||||
default: false,
|
|
||||||
},
|
|
||||||
rows: {
|
|
||||||
type: Array,
|
|
||||||
default: () => [],
|
|
||||||
},
|
|
||||||
},
|
|
||||||
emits: ['update:modelValue'],
|
|
||||||
data() {
|
|
||||||
return {
|
|
||||||
currentPage: 1,
|
|
||||||
pageSize: 10,
|
|
||||||
};
|
|
||||||
},
|
|
||||||
computed: {
|
|
||||||
total() {
|
|
||||||
return this.rows.length;
|
|
||||||
},
|
|
||||||
pageRows() {
|
|
||||||
const start = (this.currentPage - 1) * this.pageSize;
|
|
||||||
return this.rows.slice(start, start + this.pageSize);
|
|
||||||
},
|
|
||||||
},
|
|
||||||
watch: {
|
|
||||||
rows() {
|
|
||||||
const maxPage = Math.max(1, Math.ceil(this.total / this.pageSize) || 1);
|
|
||||||
if (this.currentPage > maxPage) this.currentPage = 1;
|
|
||||||
},
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
resetPage() {
|
|
||||||
this.currentPage = 1;
|
|
||||||
this.pageSize = 10;
|
|
||||||
},
|
|
||||||
},
|
|
||||||
};
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
|
||||||
.change-record-detail-dialog__pager {
|
|
||||||
display: flex;
|
|
||||||
justify-content: flex-end;
|
|
||||||
margin-top: 12px;
|
|
||||||
}
|
|
||||||
|
|
||||||
:deep(.change-record-detail-value .cell) {
|
|
||||||
white-space: pre-wrap;
|
|
||||||
word-break: break-word;
|
|
||||||
overflow: visible;
|
|
||||||
text-overflow: clip;
|
|
||||||
line-height: 1.6;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
@@ -1,380 +0,0 @@
|
|||||||
<template>
|
|
||||||
<el-dialog v-model="visible" :title="title" width="92%" append-to-body destroy-on-close @open="handleOpen">
|
|
||||||
<el-form :model="query" inline label-width="130px" @submit.prevent>
|
|
||||||
<el-form-item label="发票号码">
|
|
||||||
<el-input v-model="query.invoiceNo" clearable @keyup.enter="handleQuery" />
|
|
||||||
</el-form-item>
|
|
||||||
<el-form-item label="开票日期">
|
|
||||||
<el-date-picker
|
|
||||||
v-model="query.invoiceDateRange"
|
|
||||||
type="daterange"
|
|
||||||
value-format="YYYY-MM-DD"
|
|
||||||
format="YYYY-MM-DD"
|
|
||||||
start-placeholder="开始日期"
|
|
||||||
end-placeholder="结束日期"
|
|
||||||
/>
|
|
||||||
</el-form-item>
|
|
||||||
<el-form-item label="销方(开票方税号)">
|
|
||||||
<el-input
|
|
||||||
v-model="query.salerTaxNo"
|
|
||||||
clearable
|
|
||||||
style="width: 180px"
|
|
||||||
@keyup.enter="handleQuery"
|
|
||||||
/>
|
|
||||||
</el-form-item>
|
|
||||||
<el-form-item label="购方(收票方税号)">
|
|
||||||
<el-input
|
|
||||||
v-model="query.buyerTaxNo"
|
|
||||||
clearable
|
|
||||||
style="width: 180px"
|
|
||||||
@keyup.enter="handleQuery"
|
|
||||||
/>
|
|
||||||
</el-form-item>
|
|
||||||
<el-form-item label="发票类型">
|
|
||||||
<el-select
|
|
||||||
v-model="query.invoiceType"
|
|
||||||
clearable
|
|
||||||
filterable
|
|
||||||
placeholder="不限"
|
|
||||||
style="width: 230px"
|
|
||||||
>
|
|
||||||
<el-option
|
|
||||||
v-for="option in invoiceTypeOptions"
|
|
||||||
:key="option.value"
|
|
||||||
:label="option.label"
|
|
||||||
:value="option.value"
|
|
||||||
/>
|
|
||||||
</el-select>
|
|
||||||
</el-form-item>
|
|
||||||
<el-form-item>
|
|
||||||
<el-button :disabled="loading" @click="resetQuery">重置</el-button>
|
|
||||||
<el-button type="primary" :disabled="loading" @click="handleQuery">查询</el-button>
|
|
||||||
</el-form-item>
|
|
||||||
</el-form>
|
|
||||||
<el-table
|
|
||||||
ref="poolTable"
|
|
||||||
v-loading="loading"
|
|
||||||
:data="rows"
|
|
||||||
:row-key="rowKey"
|
|
||||||
border
|
|
||||||
height="520"
|
|
||||||
:highlight-current-row="!multiple"
|
|
||||||
@selection-change="selection => (selected = selection)"
|
|
||||||
@row-dblclick="handleRowDblclick"
|
|
||||||
>
|
|
||||||
<el-table-column v-if="multiple" type="selection" width="52" fixed="left" align="center" reserve-selection />
|
|
||||||
<el-table-column v-else width="52" align="center" fixed="left">
|
|
||||||
<template #default="{ row }">
|
|
||||||
<el-radio
|
|
||||||
:model-value="current && current.invoiceNo"
|
|
||||||
:value="row.invoiceNo"
|
|
||||||
@change="current = row"
|
|
||||||
>
|
|
||||||
<span></span>
|
|
||||||
</el-radio>
|
|
||||||
</template>
|
|
||||||
</el-table-column>
|
|
||||||
<el-table-column prop="invoiceNo" label="发票号码" min-width="180" />
|
|
||||||
<el-table-column prop="invoiceDate" label="开票日期" min-width="120" />
|
|
||||||
<el-table-column prop="invoiceType" label="发票类型" min-width="140" />
|
|
||||||
<el-table-column prop="issuerName" label="开票单位" min-width="180" show-overflow-tooltip />
|
|
||||||
<el-table-column prop="receiverName" label="受票单位" min-width="180" show-overflow-tooltip />
|
|
||||||
<el-table-column label="金额(含税)" min-width="130" align="right">
|
|
||||||
<template #default="{ row }">{{ formatMoney(row.invoiceAmount) }}</template>
|
|
||||||
</el-table-column>
|
|
||||||
<el-table-column label="税率" min-width="90" align="center">
|
|
||||||
<template #default="{ row }">{{ formatRate(row.taxRate) }}</template>
|
|
||||||
</el-table-column>
|
|
||||||
<el-table-column label="税额" min-width="120" align="right">
|
|
||||||
<template #default="{ row }">{{ formatMoney(row.taxAmount) }}</template>
|
|
||||||
</el-table-column>
|
|
||||||
<el-table-column label="剩余可用金额" min-width="130" align="right">
|
|
||||||
<template #default="{ row }">{{ formatMoney(row.availableTaxAmount) }}</template>
|
|
||||||
</el-table-column>
|
|
||||||
<el-table-column prop="kingdeeBillNo" label="金蝶单据号" min-width="210" show-overflow-tooltip />
|
|
||||||
<el-table-column label="认领状态" min-width="100" align="center">
|
|
||||||
<template #default="{ row }">
|
|
||||||
<el-tag v-if="row.claimed" type="danger" size="small">已认领</el-tag>
|
|
||||||
<el-tag v-else type="info" size="small">未认领</el-tag>
|
|
||||||
</template>
|
|
||||||
</el-table-column>
|
|
||||||
<el-table-column label="登记状态" min-width="100" align="center">
|
|
||||||
<template #default="{ row }">
|
|
||||||
<el-tag v-if="row.registered" type="warning" size="small">已登记</el-tag>
|
|
||||||
<el-tag v-else type="info" size="small">未登记</el-tag>
|
|
||||||
</template>
|
|
||||||
</el-table-column>
|
|
||||||
<el-table-column label="附件" width="120" fixed="right" align="center">
|
|
||||||
<template #default="{ row }">
|
|
||||||
<el-link
|
|
||||||
type="primary"
|
|
||||||
:underline="false"
|
|
||||||
:disabled="attachmentBusy === row.invoiceNo"
|
|
||||||
@click.stop="viewAttachment(row)"
|
|
||||||
>查看</el-link
|
|
||||||
>
|
|
||||||
<el-link
|
|
||||||
type="primary"
|
|
||||||
:underline="false"
|
|
||||||
:disabled="attachmentBusy === row.invoiceNo"
|
|
||||||
@click.stop="downloadAttachment(row)"
|
|
||||||
>下载</el-link
|
|
||||||
>
|
|
||||||
</template>
|
|
||||||
</el-table-column>
|
|
||||||
<el-table-column v-if="!multiple" label="操作" width="90" fixed="right" align="center">
|
|
||||||
<template #default="{ row }">
|
|
||||||
<el-link type="primary" @click.stop="confirmRows([row])">选择</el-link>
|
|
||||||
</template>
|
|
||||||
</el-table-column>
|
|
||||||
</el-table>
|
|
||||||
<div class="kingdee-pool-dialog__pager">
|
|
||||||
<span class="kingdee-pool-dialog__pager-info">第 {{ page.current }} 页</span>
|
|
||||||
<el-button :disabled="page.current <= 1 || loading" @click="prevPage">上一页</el-button>
|
|
||||||
<el-button :disabled="!page.hasMore || loading" @click="nextPage">下一页</el-button>
|
|
||||||
<el-select v-model="page.size" style="width: 110px" @change="handleSizeChange">
|
|
||||||
<el-option v-for="n in [10, 20, 50, 100]" :key="n" :label="`${n}条/页`" :value="n" />
|
|
||||||
</el-select>
|
|
||||||
</div>
|
|
||||||
<template #footer>
|
|
||||||
<el-button @click="visible = false">取消</el-button>
|
|
||||||
<el-button type="primary" @click="handleConfirm">{{ confirmText }}</el-button>
|
|
||||||
</template>
|
|
||||||
</el-dialog>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
import {
|
|
||||||
downloadKingdeeInvoiceFile,
|
|
||||||
isKingdeePathAttachment,
|
|
||||||
viewKingdeeInvoiceFile,
|
|
||||||
} from '@/utils/kingdee-attachment';
|
|
||||||
|
|
||||||
export default {
|
|
||||||
name: 'KingdeeInvoicePoolDialog',
|
|
||||||
props: {
|
|
||||||
modelValue: { type: Boolean, required: true },
|
|
||||||
title: { type: String, default: '查询金蝶票据池' },
|
|
||||||
confirmText: { type: String, default: '确定' },
|
|
||||||
multiple: { type: Boolean, default: false },
|
|
||||||
initialInvoiceNo: { type: String, default: '' },
|
|
||||||
queryApi: { type: Function, required: true },
|
|
||||||
},
|
|
||||||
emits: ['update:modelValue', 'confirm'],
|
|
||||||
data() {
|
|
||||||
return {
|
|
||||||
loading: false,
|
|
||||||
// 与后端 BfiConstant.INVOICE_TYPE_NAMES 对齐:value 为发票类型编码,label 为中文名
|
|
||||||
// 选票场景只开放数电票(普票/专票)
|
|
||||||
invoiceTypeOptions: [
|
|
||||||
{ value: '26', label: '数电发票(普通发票)' },
|
|
||||||
{ value: '27', label: '数电发票(增值税专用发票)' },
|
|
||||||
],
|
|
||||||
rows: [],
|
|
||||||
selected: [],
|
|
||||||
current: null,
|
|
||||||
page: { current: 1, size: 10, hasMore: false },
|
|
||||||
// 续扫游标(服务端过滤补页后翻页须按游标对齐,防漏行/重行)
|
|
||||||
nextCursor: null,
|
|
||||||
// 下一页请求待回传的游标:nextPage 时暂存,load 成功或失败后清空
|
|
||||||
pendingCursor: null,
|
|
||||||
// 已浏览页缓存:上一页直接回显不再请求(0 远程调用)
|
|
||||||
pageCache: {},
|
|
||||||
// 正在查看/下载附件的发票号(行级 loading 守卫)
|
|
||||||
attachmentBusy: null,
|
|
||||||
query: this.emptyQuery(),
|
|
||||||
};
|
|
||||||
},
|
|
||||||
computed: {
|
|
||||||
visible: {
|
|
||||||
get() {
|
|
||||||
return this.modelValue;
|
|
||||||
},
|
|
||||||
set(value) {
|
|
||||||
this.$emit('update:modelValue', value);
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
emptyQuery() {
|
|
||||||
return {
|
|
||||||
invoiceNo: '',
|
|
||||||
// 默认查近一个月,随请求传后端并在页面回显
|
|
||||||
invoiceDateRange: [this.$dayjs().subtract(1, 'month').format('YYYY-MM-DD'), this.$dayjs().format('YYYY-MM-DD')],
|
|
||||||
salerTaxNo: '',
|
|
||||||
buyerTaxNo: '',
|
|
||||||
invoiceType: '',
|
|
||||||
};
|
|
||||||
},
|
|
||||||
// 远程实时查询的票未入本地镜像,id 恒空,用发票号码兜底作为行主键
|
|
||||||
rowKey(row) {
|
|
||||||
return row.invoiceNo || row.id;
|
|
||||||
},
|
|
||||||
// 票池行附件字段为复数 attachmentsJson,行内首项即发票文件({source,path,name})
|
|
||||||
rowAttachment(row = {}) {
|
|
||||||
const value = row.attachmentsJson;
|
|
||||||
if (!value) return null;
|
|
||||||
let parsed = value;
|
|
||||||
if (typeof value === 'string') {
|
|
||||||
try {
|
|
||||||
parsed = JSON.parse(value);
|
|
||||||
} catch {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return Array.isArray(parsed) ? parsed[0] || null : parsed || null;
|
|
||||||
},
|
|
||||||
attachmentName(row) {
|
|
||||||
const attachment = this.rowAttachment(row);
|
|
||||||
return attachment?.name || `${row.invoiceNo || '发票'}.pdf`;
|
|
||||||
},
|
|
||||||
async viewAttachment(row) {
|
|
||||||
const attachment = this.rowAttachment(row);
|
|
||||||
if (!isKingdeePathAttachment(attachment)) {
|
|
||||||
this.$message.warning('当前发票暂无可查看附件');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
this.attachmentBusy = row.invoiceNo;
|
|
||||||
try {
|
|
||||||
await viewKingdeeInvoiceFile(row.invoiceNo, this.attachmentName(row));
|
|
||||||
} finally {
|
|
||||||
this.attachmentBusy = null;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
async downloadAttachment(row) {
|
|
||||||
const attachment = this.rowAttachment(row);
|
|
||||||
if (!isKingdeePathAttachment(attachment)) {
|
|
||||||
this.$message.warning('当前发票暂无可下载附件');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
this.attachmentBusy = row.invoiceNo;
|
|
||||||
try {
|
|
||||||
await downloadKingdeeInvoiceFile(row.invoiceNo, this.attachmentName(row));
|
|
||||||
} finally {
|
|
||||||
this.attachmentBusy = null;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
handleOpen() {
|
|
||||||
this.query = this.emptyQuery();
|
|
||||||
this.query.invoiceNo = this.initialInvoiceNo || '';
|
|
||||||
this.current = null;
|
|
||||||
this.selected = [];
|
|
||||||
this.$refs.poolTable?.clearSelection();
|
|
||||||
this.page.current = 1;
|
|
||||||
this.resetScanState();
|
|
||||||
this.load();
|
|
||||||
},
|
|
||||||
handleQuery() {
|
|
||||||
this.page.current = 1;
|
|
||||||
this.resetScanState();
|
|
||||||
this.load();
|
|
||||||
},
|
|
||||||
handleSizeChange() {
|
|
||||||
this.page.current = 1;
|
|
||||||
this.resetScanState();
|
|
||||||
this.load();
|
|
||||||
},
|
|
||||||
prevPage() {
|
|
||||||
// 已浏览页直接回显缓存,不再请求
|
|
||||||
const cached = this.pageCache[this.page.current - 1];
|
|
||||||
if (cached) {
|
|
||||||
this.page.current -= 1;
|
|
||||||
this.rows = cached.rows;
|
|
||||||
this.page.hasMore = cached.hasMore;
|
|
||||||
this.nextCursor = cached.nextCursor;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
this.page.current -= 1;
|
|
||||||
this.load();
|
|
||||||
},
|
|
||||||
nextPage() {
|
|
||||||
this.pendingCursor = this.nextCursor;
|
|
||||||
this.page.current += 1;
|
|
||||||
this.load();
|
|
||||||
},
|
|
||||||
resetQuery() {
|
|
||||||
this.query = this.emptyQuery();
|
|
||||||
this.handleQuery();
|
|
||||||
},
|
|
||||||
// 新查询/换页容量/重新打开时清空游标与已浏览页缓存,重新锚定扫描
|
|
||||||
resetScanState() {
|
|
||||||
this.nextCursor = null;
|
|
||||||
this.pendingCursor = null;
|
|
||||||
this.pageCache = {};
|
|
||||||
},
|
|
||||||
unwrap(response) {
|
|
||||||
const body = response?.data || response || {};
|
|
||||||
return body?.data || body;
|
|
||||||
},
|
|
||||||
async load() {
|
|
||||||
this.loading = true;
|
|
||||||
// 请求前快照页码:prev/next 会先改写 current,远程失败时回滚避免停留在错页
|
|
||||||
const lastRequested = this.page.current;
|
|
||||||
const cursor = this.pendingCursor;
|
|
||||||
this.pendingCursor = null;
|
|
||||||
try {
|
|
||||||
const range = this.query.invoiceDateRange || [];
|
|
||||||
const data = this.unwrap(
|
|
||||||
await this.queryApi({
|
|
||||||
current: this.page.current,
|
|
||||||
size: this.page.size,
|
|
||||||
cursor: cursor || undefined,
|
|
||||||
invoiceNo: this.query.invoiceNo || undefined,
|
|
||||||
invoiceDateStart: range[0] || undefined,
|
|
||||||
invoiceDateEnd: range[1] || undefined,
|
|
||||||
salerTaxNo: this.query.salerTaxNo || undefined,
|
|
||||||
buyerTaxNo: this.query.buyerTaxNo || undefined,
|
|
||||||
invoiceType: this.query.invoiceType || undefined,
|
|
||||||
})
|
|
||||||
);
|
|
||||||
this.rows = data.records || [];
|
|
||||||
this.page.hasMore = !!data.hasMore;
|
|
||||||
this.nextCursor = data.nextCursor || null;
|
|
||||||
this.pageCache[this.page.current] = { rows: this.rows, hasMore: this.page.hasMore, nextCursor: this.nextCursor };
|
|
||||||
} catch (error) {
|
|
||||||
if (this.page.current !== lastRequested) {
|
|
||||||
this.page.current = lastRequested;
|
|
||||||
}
|
|
||||||
throw error;
|
|
||||||
} finally {
|
|
||||||
this.loading = false;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
handleRowDblclick(row) {
|
|
||||||
if (!this.multiple) {
|
|
||||||
this.confirmRows([row]);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
handleConfirm() {
|
|
||||||
const picked = this.multiple ? this.selected : this.current ? [this.current] : [];
|
|
||||||
this.confirmRows(picked);
|
|
||||||
},
|
|
||||||
confirmRows(rows) {
|
|
||||||
if (!rows.length) {
|
|
||||||
this.$message.warning(this.multiple ? '请至少选择一条发票' : '请选择一张发票');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
this.$emit('confirm', rows);
|
|
||||||
this.visible = false;
|
|
||||||
},
|
|
||||||
formatMoney(value) {
|
|
||||||
const number = Number(value);
|
|
||||||
return Number.isFinite(number) ? number.toFixed(2) : '-';
|
|
||||||
},
|
|
||||||
formatRate(value) {
|
|
||||||
const number = Number(value);
|
|
||||||
return Number.isFinite(number) ? `${number}%` : '-';
|
|
||||||
},
|
|
||||||
},
|
|
||||||
};
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
|
||||||
.kingdee-pool-dialog__pager {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: 12px;
|
|
||||||
justify-content: flex-end;
|
|
||||||
margin-top: 12px;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
@@ -1,55 +1,20 @@
|
|||||||
<template>
|
<template>
|
||||||
<div v-if="results.length" class="map-search-results">
|
<div v-if="results.length" class="map-search-results">
|
||||||
<div class="map-search-results__list">
|
<div class="map-search-results__title">搜索结果</div>
|
||||||
<div
|
<el-empty v-if="!results.length" description="暂无搜索结果" :image-size="60" />
|
||||||
v-for="(item, index) in pageResults"
|
<div
|
||||||
:key="item.id || `${currentPage}-${index}`"
|
v-for="(item, index) in results"
|
||||||
class="map-search-results__item"
|
:key="item.id || index"
|
||||||
:class="{ 'is-active': isActive(item, index) }"
|
class="map-search-results__item"
|
||||||
@click="handleSelect(item, index)"
|
@click="$emit('select', item)"
|
||||||
>
|
>
|
||||||
<img
|
<div class="map-search-results__name">{{ item.name || item.address || '未命名地址' }}</div>
|
||||||
v-if="item.photo || item.image"
|
<div class="map-search-results__address">{{ item.address || item.name || '-' }}</div>
|
||||||
class="map-search-results__thumb"
|
|
||||||
:src="item.photo || item.image"
|
|
||||||
alt=""
|
|
||||||
/>
|
|
||||||
<div class="map-search-results__body">
|
|
||||||
<div class="map-search-results__name">
|
|
||||||
{{ item.name || item.address || '未命名地址' }}
|
|
||||||
</div>
|
|
||||||
<div class="map-search-results__address">
|
|
||||||
地址:{{ item.address || item.name || '-' }}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div v-if="totalPages > 1" class="map-search-results__pager">
|
|
||||||
<button
|
|
||||||
v-for="page in visiblePages"
|
|
||||||
:key="page"
|
|
||||||
type="button"
|
|
||||||
class="map-search-results__page"
|
|
||||||
:class="{ 'is-active': page === currentPage }"
|
|
||||||
@click="currentPage = page"
|
|
||||||
>
|
|
||||||
{{ page }}
|
|
||||||
</button>
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
class="map-search-results__page map-search-results__page--next"
|
|
||||||
:disabled="currentPage >= totalPages"
|
|
||||||
@click="goNext"
|
|
||||||
>
|
|
||||||
下一页
|
|
||||||
</button>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
const PAGE_SIZE = 5;
|
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
props: {
|
props: {
|
||||||
results: {
|
results: {
|
||||||
@@ -58,167 +23,43 @@ export default {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
emits: ['select'],
|
emits: ['select'],
|
||||||
data() {
|
|
||||||
return {
|
|
||||||
currentPage: 1,
|
|
||||||
activeKey: '',
|
|
||||||
};
|
|
||||||
},
|
|
||||||
computed: {
|
|
||||||
totalPages() {
|
|
||||||
return Math.max(1, Math.ceil((this.results || []).length / PAGE_SIZE));
|
|
||||||
},
|
|
||||||
pageResults() {
|
|
||||||
const start = (this.currentPage - 1) * PAGE_SIZE;
|
|
||||||
return (this.results || []).slice(start, start + PAGE_SIZE);
|
|
||||||
},
|
|
||||||
visiblePages() {
|
|
||||||
const maxButtons = 3;
|
|
||||||
let start = Math.max(1, this.currentPage - 1);
|
|
||||||
let end = Math.min(this.totalPages, start + maxButtons - 1);
|
|
||||||
start = Math.max(1, end - maxButtons + 1);
|
|
||||||
const pages = [];
|
|
||||||
for (let page = start; page <= end; page += 1) {
|
|
||||||
pages.push(page);
|
|
||||||
}
|
|
||||||
return pages;
|
|
||||||
},
|
|
||||||
},
|
|
||||||
watch: {
|
|
||||||
results() {
|
|
||||||
this.currentPage = 1;
|
|
||||||
this.activeKey = '';
|
|
||||||
},
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
resultKey(item, index) {
|
|
||||||
return item?.id ?? `${this.currentPage}-${index}`;
|
|
||||||
},
|
|
||||||
isActive(item, index) {
|
|
||||||
return this.activeKey === this.resultKey(item, index);
|
|
||||||
},
|
|
||||||
handleSelect(item, index) {
|
|
||||||
this.activeKey = this.resultKey(item, index);
|
|
||||||
this.$emit('select', item);
|
|
||||||
},
|
|
||||||
goNext() {
|
|
||||||
if (this.currentPage < this.totalPages) {
|
|
||||||
this.currentPage += 1;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
},
|
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
.map-search-results {
|
.map-search-results {
|
||||||
position: absolute;
|
width: 260px;
|
||||||
top: 0;
|
height: 420px;
|
||||||
left: 0;
|
overflow-y: auto;
|
||||||
z-index: 2000;
|
border: 1px solid #eff1f7;
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
width: 360px;
|
|
||||||
max-height: 360px;
|
|
||||||
overflow: hidden;
|
|
||||||
background: #fff;
|
background: #fff;
|
||||||
border: 1px solid #dcdfe6;
|
flex: 0 0 260px;
|
||||||
box-shadow: 0 2px 12px rgba(0, 0, 0, 0.12);
|
|
||||||
pointer-events: auto;
|
|
||||||
|
|
||||||
&__list {
|
&__title {
|
||||||
flex: 1 1 auto;
|
padding: 10px 12px;
|
||||||
min-height: 0;
|
border-bottom: 1px solid #eff1f7;
|
||||||
overflow-y: auto;
|
font-weight: 600;
|
||||||
}
|
}
|
||||||
|
|
||||||
&__item {
|
&__item {
|
||||||
display: flex;
|
|
||||||
gap: 10px;
|
|
||||||
align-items: flex-start;
|
|
||||||
padding: 10px 12px;
|
padding: 10px 12px;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
border-bottom: 1px solid #ebeef5;
|
border-bottom: 1px solid #f5f5f5;
|
||||||
}
|
}
|
||||||
|
|
||||||
&__item:hover,
|
&__item:hover {
|
||||||
&__item.is-active {
|
background: #f5f9ff;
|
||||||
background: #f5f5f5;
|
|
||||||
}
|
|
||||||
|
|
||||||
&__thumb {
|
|
||||||
flex: 0 0 48px;
|
|
||||||
width: 48px;
|
|
||||||
height: 48px;
|
|
||||||
object-fit: cover;
|
|
||||||
border: 1px solid #ebeef5;
|
|
||||||
background: #fafafa;
|
|
||||||
}
|
|
||||||
|
|
||||||
&__body {
|
|
||||||
flex: 1 1 auto;
|
|
||||||
min-width: 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
&__name {
|
&__name {
|
||||||
overflow: hidden;
|
|
||||||
color: #303133;
|
color: #303133;
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
font-weight: 600;
|
|
||||||
line-height: 1.4;
|
|
||||||
white-space: nowrap;
|
|
||||||
text-overflow: ellipsis;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
&__address {
|
&__address {
|
||||||
margin-top: 4px;
|
margin-top: 4px;
|
||||||
color: #909399;
|
color: #909399;
|
||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
line-height: 1.5;
|
|
||||||
word-break: break-all;
|
|
||||||
}
|
|
||||||
|
|
||||||
&__pager {
|
|
||||||
display: flex;
|
|
||||||
flex: 0 0 auto;
|
|
||||||
flex-wrap: wrap;
|
|
||||||
gap: 6px;
|
|
||||||
align-items: center;
|
|
||||||
padding: 8px 10px;
|
|
||||||
border-top: 1px solid #ebeef5;
|
|
||||||
background: #fff;
|
|
||||||
}
|
|
||||||
|
|
||||||
&__page {
|
|
||||||
min-width: 28px;
|
|
||||||
height: 28px;
|
|
||||||
padding: 0 8px;
|
|
||||||
color: #606266;
|
|
||||||
font-size: 13px;
|
|
||||||
line-height: 26px;
|
|
||||||
text-align: center;
|
|
||||||
background: #fff;
|
|
||||||
border: 1px solid #dcdfe6;
|
|
||||||
cursor: pointer;
|
|
||||||
|
|
||||||
&:hover:not(:disabled) {
|
|
||||||
color: #409eff;
|
|
||||||
border-color: #409eff;
|
|
||||||
}
|
|
||||||
|
|
||||||
&.is-active {
|
|
||||||
color: #409eff;
|
|
||||||
border-color: #409eff;
|
|
||||||
}
|
|
||||||
|
|
||||||
&:disabled {
|
|
||||||
color: #c0c4cc;
|
|
||||||
cursor: not-allowed;
|
|
||||||
}
|
|
||||||
|
|
||||||
&--next {
|
|
||||||
min-width: 56px;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -212,14 +212,7 @@ export default {
|
|||||||
.filter(Boolean);
|
.filter(Boolean);
|
||||||
},
|
},
|
||||||
acceptText() {
|
acceptText() {
|
||||||
return this.acceptedList
|
return this.acceptedList.join(',');
|
||||||
.map(item => {
|
|
||||||
const value = String(item).trim();
|
|
||||||
if (!value || value.includes('/') || value.startsWith('.')) return value;
|
|
||||||
return `.${value}`;
|
|
||||||
})
|
|
||||||
.filter(Boolean)
|
|
||||||
.join(',');
|
|
||||||
},
|
},
|
||||||
tipText() {
|
tipText() {
|
||||||
return this.tip || `${UNIFIED_ATTACHMENT_TIP_PREFIX}${this.maxSize}M`;
|
return this.tip || `${UNIFIED_ATTACHMENT_TIP_PREFIX}${this.maxSize}M`;
|
||||||
@@ -311,22 +304,12 @@ export default {
|
|||||||
});
|
});
|
||||||
},
|
},
|
||||||
handleSuccess(response, file, files) {
|
handleSuccess(response, file, files) {
|
||||||
const code = response?.code;
|
if (!response || response.success === false || (response.code && response.code !== 200)) {
|
||||||
const codeInvalid = code !== undefined && code !== null && code !== '' && Number(code) !== 200;
|
|
||||||
if (!response || response.success === false || codeInvalid) {
|
|
||||||
this.$message.error((response && response.msg) || '上传失败');
|
this.$message.error((response && response.msg) || '上传失败');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (file && !file.response) {
|
this.emitUploadFiles(this.multiple ? files : [file]);
|
||||||
file.response = response;
|
this.$emit('success', this.normalizeUploadFile(file));
|
||||||
}
|
|
||||||
const normalized = this.normalizeUploadFile(file, response);
|
|
||||||
if (!normalized.url) {
|
|
||||||
this.$message.error('上传成功但未返回文件地址');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
this.$emit('success', normalized);
|
|
||||||
this.emitUploadFiles(this.multiple ? files : [file], normalized);
|
|
||||||
this.$message.success('上传成功');
|
this.$message.success('上传成功');
|
||||||
},
|
},
|
||||||
handleChange(file, files) {
|
handleChange(file, files) {
|
||||||
@@ -340,89 +323,35 @@ export default {
|
|||||||
this.emitUploadFiles(files);
|
this.emitUploadFiles(files);
|
||||||
return true;
|
return true;
|
||||||
},
|
},
|
||||||
emitUploadFiles(files, preferredFile) {
|
emitUploadFiles(files) {
|
||||||
const list = (files || [])
|
const list = files
|
||||||
.filter(file => {
|
.filter(file => file.status === 'success' || this.getFileUrl(file))
|
||||||
if (file?.status === 'fail') return false;
|
.map(this.normalizeUploadFile)
|
||||||
return (
|
|
||||||
file?.status === 'success' ||
|
|
||||||
this.getFileUrl(file) ||
|
|
||||||
file?.response?.data ||
|
|
||||||
file?.response?.link ||
|
|
||||||
file?.response
|
|
||||||
);
|
|
||||||
})
|
|
||||||
.map(file => this.normalizeUploadFile(file))
|
|
||||||
.filter(item => item.url);
|
.filter(item => item.url);
|
||||||
|
this.$emit('update:modelValue', list);
|
||||||
if (preferredFile?.url) {
|
this.$emit('change', list);
|
||||||
const exists = list.some(
|
|
||||||
item =>
|
|
||||||
(preferredFile.uid && item.uid && String(item.uid) === String(preferredFile.uid)) ||
|
|
||||||
item.url === preferredFile.url
|
|
||||||
);
|
|
||||||
if (!exists) list.push(preferredFile);
|
|
||||||
}
|
|
||||||
|
|
||||||
const current = this.parseValue(this.modelValue).map(item => ({
|
|
||||||
...item,
|
|
||||||
url: this.getFileUrl(item),
|
|
||||||
}));
|
|
||||||
const merged = [...current];
|
|
||||||
list.forEach(file => {
|
|
||||||
const index = merged.findIndex(
|
|
||||||
item =>
|
|
||||||
(file.uid && item.uid && String(item.uid) === String(file.uid)) ||
|
|
||||||
(file.url && this.getFileUrl(item) === file.url)
|
|
||||||
);
|
|
||||||
if (index >= 0) merged.splice(index, 1, { ...merged[index], ...file });
|
|
||||||
else merged.push(file);
|
|
||||||
});
|
|
||||||
|
|
||||||
const result = merged.filter(item => this.getFileUrl(item));
|
|
||||||
this.$emit('update:modelValue', result);
|
|
||||||
this.$emit('change', result);
|
|
||||||
},
|
},
|
||||||
normalizeUploadFile(file, responseOverride) {
|
normalizeUploadFile(file) {
|
||||||
const response = responseOverride || file?.response;
|
const data = (file.response && file.response.data) || file.data || file;
|
||||||
let data = {};
|
const url = data.link || data.url || data.domain || file.url || '';
|
||||||
if (response && typeof response === 'object') {
|
|
||||||
if (response.data && typeof response.data === 'object') {
|
|
||||||
data = response.data;
|
|
||||||
} else if (response.link || response.url || response.domain) {
|
|
||||||
data = response;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (!data.link && !data.url && !data.domain) {
|
|
||||||
data = file?.data && typeof file.data === 'object' ? file.data : file || {};
|
|
||||||
}
|
|
||||||
const url =
|
|
||||||
data.link ||
|
|
||||||
data.url ||
|
|
||||||
data.domain ||
|
|
||||||
data.fileLink ||
|
|
||||||
data.fileUrl ||
|
|
||||||
file?.url ||
|
|
||||||
file?.link ||
|
|
||||||
'';
|
|
||||||
const originalName =
|
const originalName =
|
||||||
data.originalName ||
|
data.originalName ||
|
||||||
file?.originalName ||
|
file.originalName ||
|
||||||
file?.name ||
|
file.name ||
|
||||||
data.name ||
|
data.name ||
|
||||||
this.getFileName(url) ||
|
this.getFileName(url) ||
|
||||||
'附件';
|
'附件';
|
||||||
const extension = this.getExtension({ name: originalName, url });
|
const extension = this.getExtension({ name: originalName, url });
|
||||||
return {
|
return {
|
||||||
...data,
|
...data,
|
||||||
uid: file?.uid || data.uid,
|
uid: file.uid || data.uid,
|
||||||
originalName,
|
originalName,
|
||||||
name: originalName,
|
name: originalName,
|
||||||
url,
|
url,
|
||||||
link: data.link || url,
|
link: data.link || url,
|
||||||
size: data.size || data.attachSize || file?.size || '',
|
size: data.size || data.attachSize || file.size || '',
|
||||||
extension,
|
extension,
|
||||||
mimeType: data.mimeType || data.contentType || file?.raw?.type || MIME_MAP[extension] || '',
|
mimeType: data.mimeType || data.contentType || file.raw?.type || MIME_MAP[extension] || '',
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
handlePreview(file) {
|
handlePreview(file) {
|
||||||
|
|||||||
@@ -42,7 +42,6 @@ export const createOption = () => ({
|
|||||||
label: '计量单位编码',
|
label: '计量单位编码',
|
||||||
prop: 'unitCode',
|
prop: 'unitCode',
|
||||||
minWidth: 150,
|
minWidth: 150,
|
||||||
span: 24,
|
|
||||||
search: true,
|
search: true,
|
||||||
searchOrder: 4,
|
searchOrder: 4,
|
||||||
searchSpan: 6,
|
searchSpan: 6,
|
||||||
|
|||||||
@@ -334,7 +334,7 @@ export const option = {
|
|||||||
},
|
},
|
||||||
...auditColumns.map(column => ({ ...column, hide: true })),
|
...auditColumns.map(column => ({ ...column, hide: true })),
|
||||||
])),
|
])),
|
||||||
dialogWidth: 1100,
|
dialogWidth: '96%',
|
||||||
menuFixed: 'right',
|
menuFixed: 'right',
|
||||||
menuWidth: 320,
|
menuWidth: 320,
|
||||||
index: false,
|
index: false,
|
||||||
|
|||||||
@@ -543,8 +543,7 @@ export const option = {
|
|||||||
prop: 'projectName',
|
prop: 'projectName',
|
||||||
formslot: true,
|
formslot: true,
|
||||||
search: true,
|
search: true,
|
||||||
searchLabel: '项目名称',
|
searchLabel: '项目',
|
||||||
searchPlaceholder: '请选择或输入',
|
|
||||||
searchOrder: 22,
|
searchOrder: 22,
|
||||||
span: 6,
|
span: 6,
|
||||||
order: 890,
|
order: 890,
|
||||||
@@ -556,7 +555,6 @@ export const option = {
|
|||||||
prop: 'customerName',
|
prop: 'customerName',
|
||||||
search: true,
|
search: true,
|
||||||
searchLabel: '客户名称',
|
searchLabel: '客户名称',
|
||||||
searchPlaceholder: '请选择或输入',
|
|
||||||
searchOrder: 21,
|
searchOrder: 21,
|
||||||
minWidth: 150,
|
minWidth: 150,
|
||||||
addDisplay: false,
|
addDisplay: false,
|
||||||
@@ -578,7 +576,6 @@ export const option = {
|
|||||||
prop: 'driverName',
|
prop: 'driverName',
|
||||||
search: true,
|
search: true,
|
||||||
searchLabel: '司机名称',
|
searchLabel: '司机名称',
|
||||||
searchPlaceholder: '请选择或输入',
|
|
||||||
searchOrder: 14,
|
searchOrder: 14,
|
||||||
minWidth: 120,
|
minWidth: 120,
|
||||||
display: false,
|
display: false,
|
||||||
@@ -629,7 +626,6 @@ export const option = {
|
|||||||
prop: 'carrierName',
|
prop: 'carrierName',
|
||||||
search: true,
|
search: true,
|
||||||
searchLabel: '承运商名称',
|
searchLabel: '承运商名称',
|
||||||
searchPlaceholder: '请选择或输入',
|
|
||||||
searchOrder: 15,
|
searchOrder: 15,
|
||||||
minWidth: 150,
|
minWidth: 150,
|
||||||
display: false,
|
display: false,
|
||||||
@@ -733,7 +729,6 @@ export const option = {
|
|||||||
prop: 'planName',
|
prop: 'planName',
|
||||||
formslot: true,
|
formslot: true,
|
||||||
search: true,
|
search: true,
|
||||||
searchPlaceholder: '请选择或输入',
|
|
||||||
searchOrder: 7,
|
searchOrder: 7,
|
||||||
span: 6,
|
span: 6,
|
||||||
order: 870,
|
order: 870,
|
||||||
@@ -743,7 +738,6 @@ export const option = {
|
|||||||
label: '货物类型',
|
label: '货物类型',
|
||||||
prop: 'cargoType',
|
prop: 'cargoType',
|
||||||
search: true,
|
search: true,
|
||||||
searchPlaceholder: '请选择或输入',
|
|
||||||
searchOrder: 18,
|
searchOrder: 18,
|
||||||
formatter: row => formatGoodsField(row, ['cargoType', 'goodsType', 'typeName']),
|
formatter: row => formatGoodsField(row, ['cargoType', 'goodsType', 'typeName']),
|
||||||
minWidth: 130,
|
minWidth: 130,
|
||||||
|
|||||||
@@ -13,7 +13,6 @@ export const invoiceApplicationTableColumns = [
|
|||||||
{ prop: 'currentProcessor', label: '当前处理人', minWidth: 130 },
|
{ prop: 'currentProcessor', label: '当前处理人', minWidth: 130 },
|
||||||
{ prop: 'kingdeeBillNo', label: '金蝶单据号', minWidth: 150 },
|
{ prop: 'kingdeeBillNo', label: '金蝶单据号', minWidth: 150 },
|
||||||
{ prop: 'kingdeeStatusName', label: '金蝶单据状态', minWidth: 140 },
|
{ prop: 'kingdeeStatusName', label: '金蝶单据状态', minWidth: 140 },
|
||||||
{ prop: 'attachmentSyncStatusName', label: '附件同步', minWidth: 100 },
|
|
||||||
];
|
];
|
||||||
|
|
||||||
export const invoiceApplicationDetailColumns = [
|
export const invoiceApplicationDetailColumns = [
|
||||||
|
|||||||
@@ -11,8 +11,6 @@ export const invoiceReceiptTableColumns = [
|
|||||||
{ prop: 'currentProcessor', label: '当前处理人', minWidth: 130 },
|
{ prop: 'currentProcessor', label: '当前处理人', minWidth: 130 },
|
||||||
{ prop: 'createUserName', label: '创建人', minWidth: 110 },
|
{ prop: 'createUserName', label: '创建人', minWidth: 110 },
|
||||||
{ prop: 'createTime', label: '创建时间', minWidth: 170 },
|
{ prop: 'createTime', label: '创建时间', minWidth: 170 },
|
||||||
{ prop: 'kingdeeReceivedInvoiceNo', label: '金蝶单据号', minWidth: 150 },
|
{ prop: 'kingdeeBillNo', label: '金蝶单据号', minWidth: 150 },
|
||||||
{ prop: 'kingdeePushStatusName', label: '金蝶状态', minWidth: 130 },
|
{ prop: 'kingdeeStatusName', label: '金蝶单据状态', minWidth: 140 },
|
||||||
{ prop: 'kingdeePushMsg', label: '失败原因', minWidth: 160 },
|
|
||||||
{ prop: 'attachmentSyncStatusName', label: '附件同步', minWidth: 100 },
|
|
||||||
];
|
];
|
||||||
|
|||||||
@@ -17,5 +17,4 @@ export const paymentApplicationTableColumns = [
|
|||||||
{ prop: 'currentProcessor', label: '当前处理人', minWidth: 120 },
|
{ prop: 'currentProcessor', label: '当前处理人', minWidth: 120 },
|
||||||
{ prop: 'kingdeeBillNo', label: '金蝶单据号', minWidth: 130 },
|
{ prop: 'kingdeeBillNo', label: '金蝶单据号', minWidth: 130 },
|
||||||
{ prop: 'kingdeeStatusName', label: '金蝶单据状态', minWidth: 130 },
|
{ prop: 'kingdeeStatusName', label: '金蝶单据状态', minWidth: 130 },
|
||||||
{ prop: 'attachmentSyncStatusName', label: '附件同步', minWidth: 100 },
|
|
||||||
];
|
];
|
||||||
|
|||||||
@@ -8,6 +8,5 @@ export const receiptFlowTableColumns = [
|
|||||||
{ prop: 'summary', label: '摘要', minWidth: 160 },
|
{ prop: 'summary', label: '摘要', minWidth: 160 },
|
||||||
{ prop: 'transactionTime', label: '交易时间', minWidth: 170 },
|
{ prop: 'transactionTime', label: '交易时间', minWidth: 170 },
|
||||||
{ prop: 'claimStatusName', label: '认领状态', minWidth: 110, status: true },
|
{ prop: 'claimStatusName', label: '认领状态', minWidth: 110, status: true },
|
||||||
{ prop: 'kingdeeClaimStatusName', label: '公告状态', minWidth: 110 },
|
|
||||||
{ prop: 'claimedAmount', label: '已认领金额', minWidth: 130, money: true },
|
{ prop: 'claimedAmount', label: '已认领金额', minWidth: 130, money: true },
|
||||||
];
|
];
|
||||||
|
|||||||
@@ -21,7 +21,6 @@ export const formalSettlementTableColumns = [
|
|||||||
{ label: '当前处理人', prop: 'currentProcessor', minWidth: 130 },
|
{ label: '当前处理人', prop: 'currentProcessor', minWidth: 130 },
|
||||||
{ label: '金蝶单据号', prop: 'kingdeeBillNo', minWidth: 150 },
|
{ label: '金蝶单据号', prop: 'kingdeeBillNo', minWidth: 150 },
|
||||||
{ label: '金蝶单据状态', prop: 'kingdeeSyncStatus', minWidth: 135 },
|
{ label: '金蝶单据状态', prop: 'kingdeeSyncStatus', minWidth: 135 },
|
||||||
{ label: '附件同步', prop: 'attachmentSyncStatusName', minWidth: 100 },
|
|
||||||
{ label: '创建人', prop: 'createUserName', minWidth: 110 },
|
{ label: '创建人', prop: 'createUserName', minWidth: 110 },
|
||||||
{ label: '创建时间', prop: 'createTime', minWidth: 170 },
|
{ label: '创建时间', prop: 'createTime', minWidth: 170 },
|
||||||
];
|
];
|
||||||
|
|||||||
@@ -88,8 +88,6 @@ export const updateFeeFormFields = [
|
|||||||
];
|
];
|
||||||
|
|
||||||
export const transferSearchFields = [
|
export const transferSearchFields = [
|
||||||
{ label: '单据号', prop: 'documentNo', type: 'input' },
|
|
||||||
{ label: '合同编码', prop: 'contractNo', type: 'input' },
|
|
||||||
{ label: '合同名称', prop: 'contractName', type: 'input' },
|
{ label: '合同名称', prop: 'contractName', type: 'input' },
|
||||||
{ label: '批次号', prop: 'batchNo', type: 'input' },
|
{ label: '批次号', prop: 'batchNo', type: 'input' },
|
||||||
{ label: '生成日期', prop: 'generateDateRange', type: 'daterange' },
|
{ label: '生成日期', prop: 'generateDateRange', type: 'daterange' },
|
||||||
|
|||||||
@@ -5,7 +5,6 @@ export const GRANT_TYPE_DIC = [
|
|||||||
{ label: '社交登录', value: 'social' },
|
{ label: '社交登录', value: 'social' },
|
||||||
{ label: '客户端凭证', value: 'client_credentials' },
|
{ label: '客户端凭证', value: 'client_credentials' },
|
||||||
{ label: '刷新令牌', value: 'refresh_token' },
|
{ label: '刷新令牌', value: 'refresh_token' },
|
||||||
{ label: '退出登录', value: 'logout' },
|
|
||||||
];
|
];
|
||||||
|
|
||||||
export const authLogOption = {
|
export const authLogOption = {
|
||||||
@@ -39,7 +38,7 @@ export const authLogOption = {
|
|||||||
prop: 'realName',
|
prop: 'realName',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: '操作类型',
|
label: '授权类型',
|
||||||
prop: 'grantType',
|
prop: 'grantType',
|
||||||
type: 'select',
|
type: 'select',
|
||||||
search: true,
|
search: true,
|
||||||
@@ -81,7 +80,7 @@ export const authLogOption = {
|
|||||||
width: 120,
|
width: 120,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: '操作时间',
|
label: '登录时间',
|
||||||
prop: 'loginTime',
|
prop: 'loginTime',
|
||||||
sortable: true,
|
sortable: true,
|
||||||
span: 24,
|
span: 24,
|
||||||
|
|||||||
@@ -1,9 +1,12 @@
|
|||||||
import { getDeptLazyTree } from '@/api/system/dept';
|
import { getDeptLazyTree } from '@/api/system/dept';
|
||||||
import { validateLoginPassword } from '@/utils/validate';
|
|
||||||
|
|
||||||
export const userOption = safe => {
|
export const userOption = safe => {
|
||||||
const validatePass = (rule, value, callback) => {
|
const validatePass = (rule, value, callback) => {
|
||||||
validateLoginPassword(rule, value, callback);
|
if (value === '') {
|
||||||
|
callback(new Error('请输入密码'));
|
||||||
|
} else {
|
||||||
|
callback();
|
||||||
|
}
|
||||||
};
|
};
|
||||||
const validatePass2 = (rule, value, callback) => {
|
const validatePass2 = (rule, value, callback) => {
|
||||||
if (value === '') {
|
if (value === '') {
|
||||||
|
|||||||
@@ -13,8 +13,8 @@ export default {
|
|||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
loginForm: {
|
loginForm: {
|
||||||
username: '',
|
username: 'admin',
|
||||||
password: '',
|
password: '123456',
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -24,7 +24,6 @@
|
|||||||
-->
|
-->
|
||||||
<userLogin v-if="activeName === 'user'"></userLogin>
|
<userLogin v-if="activeName === 'user'"></userLogin>
|
||||||
<registerLogin v-else-if="activeName === 'register'"></registerLogin>
|
<registerLogin v-else-if="activeName === 'register'"></registerLogin>
|
||||||
<!-- 暂时默认账号密码登录,IAM 选择入口先隐藏
|
|
||||||
<div v-else class="iam-login">
|
<div v-else class="iam-login">
|
||||||
<el-button type="primary" class="login-submit" @click.prevent="handleIamLogin">
|
<el-button type="primary" class="login-submit" @click.prevent="handleIamLogin">
|
||||||
IAM统一身份认证
|
IAM统一身份认证
|
||||||
@@ -33,7 +32,6 @@
|
|||||||
账号密码登录
|
账号密码登录
|
||||||
</el-button>
|
</el-button>
|
||||||
</div>
|
</div>
|
||||||
-->
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -63,7 +61,7 @@ export default {
|
|||||||
return {
|
return {
|
||||||
website: website,
|
website: website,
|
||||||
time: '',
|
time: '',
|
||||||
activeName: 'user',
|
activeName: 'iam',
|
||||||
socialForm: {
|
socialForm: {
|
||||||
tenantId: '000000',
|
tenantId: '000000',
|
||||||
source: '',
|
source: '',
|
||||||
|
|||||||
@@ -93,9 +93,9 @@ export default {
|
|||||||
//角色ID
|
//角色ID
|
||||||
roleId: '',
|
roleId: '',
|
||||||
//用户名
|
//用户名
|
||||||
username: '',
|
username: 'admin',
|
||||||
//密码
|
//密码
|
||||||
password: '',
|
password: 'admin',
|
||||||
//账号类型
|
//账号类型
|
||||||
type: 'account',
|
type: 'account',
|
||||||
//验证码的值
|
//验证码的值
|
||||||
|
|||||||
@@ -87,7 +87,7 @@ export default [
|
|||||||
{
|
{
|
||||||
path: '/business/project-apply/public-view',
|
path: '/business/project-apply/public-view',
|
||||||
name: '查看项目信息',
|
name: '查看项目信息',
|
||||||
component: () => import('@/views/business/project-apply-public-view.vue'),
|
component: () => import('@/views/mk/public-biz-view.vue'),
|
||||||
meta: {
|
meta: {
|
||||||
keepAlive: false,
|
keepAlive: false,
|
||||||
isTab: false,
|
isTab: false,
|
||||||
@@ -98,7 +98,7 @@ export default [
|
|||||||
{
|
{
|
||||||
path: '/business/contract-manage/public-view',
|
path: '/business/contract-manage/public-view',
|
||||||
name: '查看合同信息',
|
name: '查看合同信息',
|
||||||
component: () => import('@/views/business/contract-manage-public-view.vue'),
|
component: () => import('@/views/mk/public-biz-view.vue'),
|
||||||
meta: {
|
meta: {
|
||||||
keepAlive: false,
|
keepAlive: false,
|
||||||
isTab: false,
|
isTab: false,
|
||||||
@@ -109,7 +109,7 @@ export default [
|
|||||||
{
|
{
|
||||||
path: '/business/waybill-manage/public-view',
|
path: '/business/waybill-manage/public-view',
|
||||||
name: '查看运单信息',
|
name: '查看运单信息',
|
||||||
component: () => import('@/views/business/waybill-manage-public-view.vue'),
|
component: () => import('@/views/mk/public-biz-view.vue'),
|
||||||
meta: {
|
meta: {
|
||||||
keepAlive: false,
|
keepAlive: false,
|
||||||
isTab: false,
|
isTab: false,
|
||||||
@@ -120,7 +120,7 @@ export default [
|
|||||||
{
|
{
|
||||||
path: '/settlement/pre-settlement/public-view',
|
path: '/settlement/pre-settlement/public-view',
|
||||||
name: '查看预结算信息',
|
name: '查看预结算信息',
|
||||||
component: () => import('@/views/settlement/pre-settlement-public-view.vue'),
|
component: () => import('@/views/mk/public-biz-view.vue'),
|
||||||
meta: {
|
meta: {
|
||||||
keepAlive: false,
|
keepAlive: false,
|
||||||
isTab: false,
|
isTab: false,
|
||||||
@@ -131,7 +131,7 @@ export default [
|
|||||||
{
|
{
|
||||||
path: '/settlement/formal-settlement/public-view',
|
path: '/settlement/formal-settlement/public-view',
|
||||||
name: '查看正式结算信息',
|
name: '查看正式结算信息',
|
||||||
component: () => import('@/views/settlement/formal-settlement-public-view.vue'),
|
component: () => import('@/views/mk/public-biz-view.vue'),
|
||||||
meta: {
|
meta: {
|
||||||
keepAlive: false,
|
keepAlive: false,
|
||||||
isTab: false,
|
isTab: false,
|
||||||
@@ -142,7 +142,7 @@ export default [
|
|||||||
{
|
{
|
||||||
path: '/payment/payment-application/public-view',
|
path: '/payment/payment-application/public-view',
|
||||||
name: '查看付款申请信息',
|
name: '查看付款申请信息',
|
||||||
component: () => import('@/views/payment/payment-application-public-view.vue'),
|
component: () => import('@/views/mk/public-biz-view.vue'),
|
||||||
meta: {
|
meta: {
|
||||||
keepAlive: false,
|
keepAlive: false,
|
||||||
isTab: false,
|
isTab: false,
|
||||||
|
|||||||
+3
-10
@@ -150,15 +150,12 @@ a {
|
|||||||
bottom: 0;
|
bottom: 0;
|
||||||
}
|
}
|
||||||
.map-picker-content {
|
.map-picker-content {
|
||||||
position: relative;
|
display: flex;
|
||||||
display: block;
|
gap: 12px;
|
||||||
min-width: 0;
|
|
||||||
overflow: visible;
|
|
||||||
|
|
||||||
> [class$='__map'],
|
> [class$='__map'],
|
||||||
> .address-map {
|
> .address-map {
|
||||||
position: relative;
|
flex: 1 1 auto;
|
||||||
z-index: 1;
|
|
||||||
width: 100%;
|
width: 100%;
|
||||||
min-width: 0;
|
min-width: 0;
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
@@ -168,8 +165,4 @@ a {
|
|||||||
height: 100% !important;
|
height: 100% !important;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
> .map-search-results {
|
|
||||||
z-index: 2000;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,140 +0,0 @@
|
|||||||
import { getDetail, getList as getCustomerList } from '@/api/vehicle/customer-archive';
|
|
||||||
import { getList as getDriverList } from '@/api/transportCapacity/driver';
|
|
||||||
import { getList as getVehicleList } from '@/api/transportCapacity/transport-vehicle';
|
|
||||||
|
|
||||||
const orgNameCache = new Map();
|
|
||||||
|
|
||||||
const extractRecords = res => {
|
|
||||||
const data = res?.data?.data ?? res?.data ?? res;
|
|
||||||
if (Array.isArray(data)) return data;
|
|
||||||
if (Array.isArray(data?.records)) return data.records;
|
|
||||||
if (Array.isArray(data?.data)) return data.data;
|
|
||||||
return [];
|
|
||||||
};
|
|
||||||
|
|
||||||
const normalizeOrgName = value => String(value || '').trim();
|
|
||||||
|
|
||||||
const uniqueOrgNames = (values = []) => {
|
|
||||||
const seen = new Set();
|
|
||||||
const result = [];
|
|
||||||
values.forEach(value => {
|
|
||||||
const name = normalizeOrgName(value);
|
|
||||||
if (!name || seen.has(name)) return;
|
|
||||||
seen.add(name);
|
|
||||||
result.push(name);
|
|
||||||
});
|
|
||||||
return result;
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 解析承运商客商档案上联系人的所属组织(branchName),无联系人时回退客商所属组织。
|
|
||||||
*/
|
|
||||||
export const resolveCarrierOrganizationNames = async ({ carrierId, carrierName } = {}) => {
|
|
||||||
const id = String(carrierId || '').trim();
|
|
||||||
const name = normalizeOrgName(carrierName);
|
|
||||||
const cacheKey = id || name;
|
|
||||||
if (!cacheKey) return [];
|
|
||||||
if (orgNameCache.has(cacheKey)) return orgNameCache.get(cacheKey);
|
|
||||||
|
|
||||||
let detail = null;
|
|
||||||
if (id) {
|
|
||||||
try {
|
|
||||||
const res = await getDetail(id);
|
|
||||||
detail = res?.data?.data || res?.data || null;
|
|
||||||
} catch (error) {
|
|
||||||
detail = null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (!detail && name) {
|
|
||||||
try {
|
|
||||||
const res = await getCustomerList(1, 20, { fullName: name });
|
|
||||||
const records = extractRecords(res);
|
|
||||||
const matched =
|
|
||||||
records.find(item => normalizeOrgName(item.fullName || item.customerName) === name) ||
|
|
||||||
records[0];
|
|
||||||
if (matched?.id) {
|
|
||||||
const detailRes = await getDetail(matched.id);
|
|
||||||
detail = detailRes?.data?.data || detailRes?.data || matched;
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
detail = null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const contacts = Array.isArray(detail?.contacts) ? detail.contacts : [];
|
|
||||||
const orgNames = uniqueOrgNames([
|
|
||||||
...contacts.map(item => item.branchName),
|
|
||||||
detail?.deptName,
|
|
||||||
detail?.organizationName,
|
|
||||||
]);
|
|
||||||
orgNameCache.set(cacheKey, orgNames);
|
|
||||||
if (id && name) orgNameCache.set(name, orgNames);
|
|
||||||
return orgNames;
|
|
||||||
};
|
|
||||||
|
|
||||||
export const clearCarrierOrganizationCache = (carrier = {}) => {
|
|
||||||
const id = String(carrier.carrierId || '').trim();
|
|
||||||
const name = normalizeOrgName(carrier.carrierName);
|
|
||||||
if (id) orgNameCache.delete(id);
|
|
||||||
if (name) orgNameCache.delete(name);
|
|
||||||
};
|
|
||||||
|
|
||||||
export const matchOrganizationName = (value, orgNames = []) => {
|
|
||||||
const text = normalizeOrgName(value);
|
|
||||||
if (!text || !orgNames.length) return false;
|
|
||||||
return orgNames.some(org => text === org || text.includes(org) || org.includes(text));
|
|
||||||
};
|
|
||||||
|
|
||||||
const filterByOrganizations = (records = [], orgNames = []) => {
|
|
||||||
if (!orgNames.length) return [];
|
|
||||||
return records.filter(item => matchOrganizationName(item.organizationName, orgNames));
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 按承运商联系人所属组织筛选司机。
|
|
||||||
* 未选择承运商时返回空列表(需先选承运商)。
|
|
||||||
*/
|
|
||||||
export const fetchDriversByCarrierOrganizations = async (
|
|
||||||
query = {},
|
|
||||||
{ carrierId, carrierName } = {}
|
|
||||||
) => {
|
|
||||||
if (!String(carrierId || '').trim() && !normalizeOrgName(carrierName)) {
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
const orgNames = await resolveCarrierOrganizationNames({ carrierId, carrierName });
|
|
||||||
if (!orgNames.length) return [];
|
|
||||||
|
|
||||||
const size = Math.max(Number(query.size) || 50, 50);
|
|
||||||
const params = { ...query };
|
|
||||||
delete params.size;
|
|
||||||
// 单组织时用后端模糊条件缩小范围;多组织再前端精确过滤
|
|
||||||
if (orgNames.length === 1) {
|
|
||||||
params.organizationName = orgNames[0];
|
|
||||||
}
|
|
||||||
const res = await getDriverList(1, Math.min(size * 5, 200), params);
|
|
||||||
return filterByOrganizations(extractRecords(res), orgNames).slice(0, size);
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 按承运商联系人所属组织筛选车辆。
|
|
||||||
* 未选择承运商时返回空列表。
|
|
||||||
*/
|
|
||||||
export const fetchVehiclesByCarrierOrganizations = async (
|
|
||||||
query = {},
|
|
||||||
{ carrierId, carrierName } = {}
|
|
||||||
) => {
|
|
||||||
if (!String(carrierId || '').trim() && !normalizeOrgName(carrierName)) {
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
const orgNames = await resolveCarrierOrganizationNames({ carrierId, carrierName });
|
|
||||||
if (!orgNames.length) return [];
|
|
||||||
|
|
||||||
const size = Math.max(Number(query.size) || 50, 50);
|
|
||||||
const params = { ...query };
|
|
||||||
delete params.size;
|
|
||||||
if (orgNames.length === 1) {
|
|
||||||
params.organizationName = orgNames[0];
|
|
||||||
}
|
|
||||||
const res = await getVehicleList(1, Math.min(size * 5, 200), params);
|
|
||||||
return filterByOrganizations(extractRecords(res), orgNames).slice(0, size);
|
|
||||||
};
|
|
||||||
@@ -28,6 +28,34 @@ const parseBlobJson = async blob => {
|
|||||||
return text ? JSON.parse(text) : {};
|
return text ? JSON.parse(text) : {};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 解析导入失败时的错误提示文案。
|
||||||
|
*
|
||||||
|
* 背景:导入接口统一用 `responseType: 'blob'` 请求,因此**非 2xx 响应体也是 Blob**。
|
||||||
|
* axios 的错误分支不做解析,直接 reject,`error.message` 只有
|
||||||
|
* 「Request failed with status code 400」这类通用文案(或全局兜底的「系统错误」),
|
||||||
|
* 用户会误以为系统崩了——而后端其实已返回了明确的中文业务提示(如「导入数据不能为空」)。
|
||||||
|
*
|
||||||
|
* 这里补上这一步:从错误响应的 Blob 里取出后端 `msg`,取不到再退回通用文案。
|
||||||
|
*/
|
||||||
|
const resolveErrorMessage = async error => {
|
||||||
|
const blob = error?.response?.data ?? error?.data;
|
||||||
|
if (blob instanceof Blob) {
|
||||||
|
try {
|
||||||
|
const result = await parseBlobJson(blob);
|
||||||
|
if (result?.msg) return result.msg;
|
||||||
|
} catch (ignored) {
|
||||||
|
// 响应体不是 JSON(如网关返回的 HTML 错误页),退回通用文案
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const fallback = error?.response?.data?.msg || error?.message;
|
||||||
|
// axios 的英文通用文案对用户没有意义,统一换成中文提示
|
||||||
|
if (!fallback || /Request failed with status code/i.test(fallback)) {
|
||||||
|
return '导入失败,请检查导入文件后重试';
|
||||||
|
}
|
||||||
|
return fallback;
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 加工后端返回的导入失败明细 Excel。
|
* 加工后端返回的导入失败明细 Excel。
|
||||||
* 若传入 failDetailDecorator(函数),则动态加载 exceljs 读取 blob 流,
|
* 若传入 failDetailDecorator(函数),则动态加载 exceljs 读取 blob 流,
|
||||||
@@ -125,7 +153,11 @@ export const handleImportExcel = async (vm, option, column, businessName, onSucc
|
|||||||
option.onSuccess(res);
|
option.onSuccess(res);
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
vm.$message.error(error.message || '导入失败');
|
// 拦截器已提示过的错误(如后端 400 返回的业务提示)不再重复弹一次,
|
||||||
|
// 只保留一次清晰的中文提示。
|
||||||
|
if (!error?._notified) {
|
||||||
|
vm.$message.error(await resolveErrorMessage(error));
|
||||||
|
}
|
||||||
if (typeof option.onError === 'function') {
|
if (typeof option.onError === 'function') {
|
||||||
option.onError(error);
|
option.onError(error);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,90 +0,0 @@
|
|||||||
/**
|
|
||||||
* 金蝶侧发票附件查看/下载公共工具
|
|
||||||
*
|
|
||||||
* 票池发票附件为 {source:'piaozone', path, name}:path 是金蝶侧文件 id(非 http 直链),
|
|
||||||
* 须由后端代理 A801-2 流式回源;OSS 直链附件(url/link/fileUrl)仍由前端直连下载/预览。
|
|
||||||
* 三处入口共用:票池弹窗、正式结算认领发票行、付款申请发票附件行。
|
|
||||||
*/
|
|
||||||
import { ElMessage } from 'element-plus';
|
|
||||||
import { downloadFile } from '@/utils/util';
|
|
||||||
import { downloadInvoicePoolAttachment } from '@/api/payment/invoiceReceipt';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 金蝶侧附件判定:source=piaozone,或有 path 且非 http(s) 直链
|
|
||||||
* @param {Object|Null} attachment 附件对象(attachmentsJson/attachmentJson 行内首项)
|
|
||||||
* @returns {Boolean}
|
|
||||||
*/
|
|
||||||
export const isKingdeePathAttachment = attachment => {
|
|
||||||
if (!attachment) return false;
|
|
||||||
if (attachment.source === 'piaozone') return true;
|
|
||||||
return Boolean(attachment.path) && !/^https?:\/\//i.test(attachment.path);
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 请求金蝶文件流:成功返回 Blob;业务失败为 HTTP 200 + R.fail JSON(响应通道未写流时后端即失败),
|
|
||||||
* 按 Content-Type 识别并抛出带 msg 的错误;网络级失败拦截器已提示,标记 silent 避免重复提示
|
|
||||||
* @param {String} invoiceNo 发票号码
|
|
||||||
* @returns {Promise<Blob>}
|
|
||||||
*/
|
|
||||||
const requestKingdeeFile = async invoiceNo => {
|
|
||||||
let res;
|
|
||||||
try {
|
|
||||||
res = await downloadInvoicePoolAttachment(invoiceNo);
|
|
||||||
} catch (error) {
|
|
||||||
throw Object.assign(new Error(error?.message || '附件下载失败'), { silent: true });
|
|
||||||
}
|
|
||||||
const blob = res?.data;
|
|
||||||
if (!(blob instanceof Blob)) {
|
|
||||||
throw new Error('响应不是文件流');
|
|
||||||
}
|
|
||||||
if (blob.type && blob.type.includes('application/json')) {
|
|
||||||
let message = '附件下载失败';
|
|
||||||
try {
|
|
||||||
const parsed = JSON.parse(await blob.text());
|
|
||||||
message = parsed.msg || parsed.message || message;
|
|
||||||
} catch {
|
|
||||||
// 错误体非 JSON 时按默认提示
|
|
||||||
}
|
|
||||||
throw new Error(message);
|
|
||||||
}
|
|
||||||
return blob;
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 下载金蝶侧发票附件(经 A801-2 后端代理),失败统一 ElMessage 提示
|
|
||||||
* @param {String} invoiceNo 发票号码
|
|
||||||
* @param {String} fileName 下载文件名(缺省按 发票号.pdf)
|
|
||||||
*/
|
|
||||||
export const downloadKingdeeInvoiceFile = async (invoiceNo, fileName) => {
|
|
||||||
if (!invoiceNo) {
|
|
||||||
ElMessage.warning('当前发票缺少发票号码,无法下载金蝶侧附件');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
const blob = await requestKingdeeFile(invoiceNo);
|
|
||||||
downloadFile(blob, fileName || `${invoiceNo}.pdf`);
|
|
||||||
} catch (error) {
|
|
||||||
if (!error?.silent) ElMessage.error(error?.message || '金蝶附件下载失败');
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 新窗口预览金蝶侧发票附件:拉取 Blob 转 objectURL(PDF 可预览;OFD 等浏览器不可预览的格式由浏览器触发下载)
|
|
||||||
* @param {String} invoiceNo 发票号码
|
|
||||||
* @param {String} fileName 预览文件名(缺省按 发票号.pdf)
|
|
||||||
*/
|
|
||||||
export const viewKingdeeInvoiceFile = async (invoiceNo, fileName) => {
|
|
||||||
if (!invoiceNo) {
|
|
||||||
ElMessage.warning('当前发票缺少发票号码,无法预览金蝶侧附件');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
const blob = await requestKingdeeFile(invoiceNo);
|
|
||||||
const url = URL.createObjectURL(blob);
|
|
||||||
window.open(url, '_blank');
|
|
||||||
// 延迟释放 objectURL:立即回收可能导致新窗口尚未加载完成即失效
|
|
||||||
setTimeout(() => URL.revokeObjectURL(url), 60000);
|
|
||||||
} catch (error) {
|
|
||||||
if (!error?.silent) ElMessage.error(error?.message || '金蝶附件预览失败');
|
|
||||||
}
|
|
||||||
};
|
|
||||||
@@ -1,39 +0,0 @@
|
|||||||
/**
|
|
||||||
* 将高德 Geocoder 结果规范化为地图选址浮层列表数据。
|
|
||||||
* @param {Array|Object} geocodesOrResult geocodes 数组,或含 geocodes/location 的结果对象
|
|
||||||
* @param {string} keyword 搜索关键词兜底文案
|
|
||||||
* @returns {Array<{id: string|number, name: string, address: string, location: any, photo: string}>}
|
|
||||||
*/
|
|
||||||
export function normalizeMapSearchResults(geocodesOrResult, keyword = '') {
|
|
||||||
const fallback = String(keyword || '').trim();
|
|
||||||
let list = [];
|
|
||||||
if (Array.isArray(geocodesOrResult)) {
|
|
||||||
list = geocodesOrResult;
|
|
||||||
} else if (geocodesOrResult && typeof geocodesOrResult === 'object') {
|
|
||||||
if (Array.isArray(geocodesOrResult.geocodes)) {
|
|
||||||
list = geocodesOrResult.geocodes;
|
|
||||||
} else if (geocodesOrResult.location) {
|
|
||||||
list = [geocodesOrResult];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return list.map((item, index) => {
|
|
||||||
const buildingName = pickNamedField(item?.building);
|
|
||||||
const neighborhoodName = pickNamedField(item?.neighborhood);
|
|
||||||
const address = item?.formattedAddress || item?.address || fallback || '-';
|
|
||||||
return {
|
|
||||||
id: item?.id || index,
|
|
||||||
name: buildingName || neighborhoodName || address,
|
|
||||||
address,
|
|
||||||
location: item?.location,
|
|
||||||
photo: item?.photo || item?.image || '',
|
|
||||||
};
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function pickNamedField(value) {
|
|
||||||
if (!value) return '';
|
|
||||||
if (typeof value === 'string') return value.trim();
|
|
||||||
if (typeof value === 'object' && value.name) return String(value.name).trim();
|
|
||||||
return '';
|
|
||||||
}
|
|
||||||
@@ -40,22 +40,10 @@ export const MK_BIZ = {
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
const MK_SWITCH_NAME = '开启MK';
|
export async function resolveMkTemplateCode(dictName) {
|
||||||
|
|
||||||
async function loadMkTemplateList() {
|
|
||||||
const res = await getDictionary({ code: 'mk_template' });
|
const res = await getDictionary({ code: 'mk_template' });
|
||||||
return res?.data?.data || [];
|
const list = res?.data?.data || [];
|
||||||
}
|
const matched = list.find(item => String(item.dictValue || '').trim() === dictName);
|
||||||
|
|
||||||
/** 业务字典 mk_template:名称「开启MK」且键值为 1 时才请求 MK */
|
|
||||||
export function isMkEnabled(list = []) {
|
|
||||||
const matched = list.find(item => String(item.dictValue || '').trim() === MK_SWITCH_NAME);
|
|
||||||
return String(matched?.dictKey ?? '').trim() === '1';
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function resolveMkTemplateCode(dictName, list) {
|
|
||||||
const dictList = list || (await loadMkTemplateList());
|
|
||||||
const matched = dictList.find(item => String(item.dictValue || '').trim() === dictName);
|
|
||||||
const templateCode = matched?.dictKey;
|
const templateCode = matched?.dictKey;
|
||||||
if (!templateCode) {
|
if (!templateCode) {
|
||||||
ElMessage.warning(`未配置业务字典 mk_template「${dictName}」,无法提交审核流`);
|
ElMessage.warning(`未配置业务字典 mk_template「${dictName}」,无法提交审核流`);
|
||||||
@@ -74,14 +62,10 @@ export async function submitMkApprovalFlow({
|
|||||||
if (!conf) {
|
if (!conf) {
|
||||||
return Promise.reject(new Error(`不支持的MK业务类型:${bizType}`));
|
return Promise.reject(new Error(`不支持的MK业务类型:${bizType}`));
|
||||||
}
|
}
|
||||||
const list = await loadMkTemplateList();
|
|
||||||
if (!isMkEnabled(list)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if ((conf.rejected || []).includes(approvalStatus)) {
|
if ((conf.rejected || []).includes(approvalStatus)) {
|
||||||
await processDelete({ formInstanceId: String(formInstanceId) });
|
await processDelete({ formInstanceId: String(formInstanceId) });
|
||||||
}
|
}
|
||||||
const templateCode = await resolveMkTemplateCode(conf.dictName, list);
|
const templateCode = await resolveMkTemplateCode(conf.dictName);
|
||||||
const subject = subjectName
|
const subject = subjectName
|
||||||
? `${conf.subjectPrefix}:${subjectName}`
|
? `${conf.subjectPrefix}:${subjectName}`
|
||||||
: `${conf.subjectPrefix}:${formInstanceId}`;
|
: `${conf.subjectPrefix}:${formInstanceId}`;
|
||||||
|
|||||||
@@ -283,31 +283,3 @@ export function validatejson(val) {
|
|||||||
// 非对象、非数组、非字符串,或者字符串不是 JSON
|
// 非对象、非数组、非字符串,或者字符串不是 JSON
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 登录密码规则提示 */
|
|
||||||
export const LOGIN_PASSWORD_RULE_MESSAGE =
|
|
||||||
'密码须大于8位,且同时包含字母、数字和特殊字符(.!@#$%^&*)';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 校验登录密码强度:大于8位,同时包含字母、数字、特殊字符(.!@#$%^&*)
|
|
||||||
* @param {string} password
|
|
||||||
* @returns {boolean}
|
|
||||||
*/
|
|
||||||
export function isValidLoginPassword(password) {
|
|
||||||
return /^(?=.*[A-Za-z])(?=.*\d)(?=.*[.!@#$%^&*]).{9,}$/.test(String(password || ''));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Element Plus / Avue 表单校验器:登录密码强度
|
|
||||||
*/
|
|
||||||
export function validateLoginPassword(rule, value, callback) {
|
|
||||||
if (value === undefined || value === null || String(value).trim() === '') {
|
|
||||||
callback(new Error('请输入登录密码'));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (!isValidLoginPassword(value)) {
|
|
||||||
callback(new Error(LOGIN_PASSWORD_RULE_MESSAGE));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
callback();
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -94,17 +94,6 @@
|
|||||||
/>
|
/>
|
||||||
</el-select>
|
</el-select>
|
||||||
</template>
|
</template>
|
||||||
<template #detailAddress-form>
|
|
||||||
<el-input
|
|
||||||
v-model="form.detailAddress"
|
|
||||||
class="detail-address-input"
|
|
||||||
readonly
|
|
||||||
maxlength="255"
|
|
||||||
show-word-limit
|
|
||||||
placeholder="点击选择地图地址"
|
|
||||||
@click="openAddressMapPicker"
|
|
||||||
/>
|
|
||||||
</template>
|
|
||||||
<template #longitude-form>
|
<template #longitude-form>
|
||||||
<el-input
|
<el-input
|
||||||
v-model="form.longitude"
|
v-model="form.longitude"
|
||||||
@@ -132,13 +121,6 @@
|
|||||||
</template>
|
</template>
|
||||||
</avue-form>
|
</avue-form>
|
||||||
</el-dialog>
|
</el-dialog>
|
||||||
<address-map-picker
|
|
||||||
v-model="addressMapPickerVisible"
|
|
||||||
:address="form.detailAddress"
|
|
||||||
:longitude="form.longitude"
|
|
||||||
:latitude="form.latitude"
|
|
||||||
@confirm="handleAddressMapConfirm"
|
|
||||||
/>
|
|
||||||
</basic-container>
|
</basic-container>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@@ -152,7 +134,6 @@ import { openImportDialog } from '@/utils/import-excel';
|
|||||||
import { formatUpdateUserName } from '@/utils/audit';
|
import { formatUpdateUserName } from '@/utils/audit';
|
||||||
import { getToken } from '@/utils/auth';
|
import { getToken } from '@/utils/auth';
|
||||||
import { getCoordinateValidationMessage, normalizeCoordinateInput } from '@/utils/coordinate';
|
import { getCoordinateValidationMessage, normalizeCoordinateInput } from '@/utils/coordinate';
|
||||||
import AddressMapPicker from '@/components/address-map-picker/main.vue';
|
|
||||||
import NProgress from 'nprogress';
|
import NProgress from 'nprogress';
|
||||||
import 'nprogress/nprogress.css';
|
import 'nprogress/nprogress.css';
|
||||||
|
|
||||||
@@ -200,9 +181,6 @@ const addExcelRequiredHeaderMarks = async blob => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
components: {
|
|
||||||
AddressMapPicker,
|
|
||||||
},
|
|
||||||
data() {
|
data() {
|
||||||
const validateIataCode = (rule, value, callback) => {
|
const validateIataCode = (rule, value, callback) => {
|
||||||
if (!/^[A-Z]{3}$/.test(String(value || '').toUpperCase())) {
|
if (!/^[A-Z]{3}$/.test(String(value || '').toUpperCase())) {
|
||||||
@@ -242,7 +220,6 @@ export default {
|
|||||||
loading: true,
|
loading: true,
|
||||||
data: [],
|
data: [],
|
||||||
excelBox: false,
|
excelBox: false,
|
||||||
addressMapPickerVisible: false,
|
|
||||||
excelForm: {},
|
excelForm: {},
|
||||||
provinceOptions: [],
|
provinceOptions: [],
|
||||||
cityOptions: [],
|
cityOptions: [],
|
||||||
@@ -391,14 +368,15 @@ export default {
|
|||||||
{
|
{
|
||||||
label: '详细地址',
|
label: '详细地址',
|
||||||
prop: 'detailAddress',
|
prop: 'detailAddress',
|
||||||
formslot: true,
|
type: 'textarea',
|
||||||
|
minRows: 2,
|
||||||
span: 24,
|
span: 24,
|
||||||
minWidth: 220,
|
minWidth: 220,
|
||||||
overHidden: false,
|
overHidden: false,
|
||||||
maxlength: 255,
|
maxlength: 255,
|
||||||
showWordLimit: true,
|
showWordLimit: true,
|
||||||
rules: [
|
rules: [
|
||||||
{ required: true, message: '请选择详细地址', trigger: 'change' },
|
{ required: true, message: '请输入详细地址', trigger: 'blur' },
|
||||||
{ max: 255, message: '详细地址不能超过255字', trigger: 'blur' },
|
{ max: 255, message: '详细地址不能超过255字', trigger: 'blur' },
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
@@ -691,29 +669,6 @@ export default {
|
|||||||
handleCoordinateInput(prop, value) {
|
handleCoordinateInput(prop, value) {
|
||||||
this.form[prop] = normalizeCoordinateInput(value);
|
this.form[prop] = normalizeCoordinateInput(value);
|
||||||
},
|
},
|
||||||
openAddressMapPicker() {
|
|
||||||
this.addressMapPickerVisible = true;
|
|
||||||
},
|
|
||||||
handleAddressMapConfirm(payload) {
|
|
||||||
const selection = typeof payload === 'string' ? { address: payload } : payload || {};
|
|
||||||
if (selection.address) {
|
|
||||||
this.form.detailAddress = selection.address;
|
|
||||||
}
|
|
||||||
if (
|
|
||||||
selection.longitude !== undefined &&
|
|
||||||
selection.longitude !== null &&
|
|
||||||
selection.longitude !== ''
|
|
||||||
) {
|
|
||||||
this.form.longitude = Number(selection.longitude).toFixed(6);
|
|
||||||
}
|
|
||||||
if (
|
|
||||||
selection.latitude !== undefined &&
|
|
||||||
selection.latitude !== null &&
|
|
||||||
selection.latitude !== ''
|
|
||||||
) {
|
|
||||||
this.form.latitude = Number(selection.latitude).toFixed(6);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
normalizeRow(row) {
|
normalizeRow(row) {
|
||||||
row.code = row.iataCode ? `JC-${row.iataCode}` : row.code;
|
row.code = row.iataCode ? `JC-${row.iataCode}` : row.code;
|
||||||
row.regionCode = String(row.regionCode || '').trim();
|
row.regionCode = String(row.regionCode || '').trim();
|
||||||
@@ -962,13 +917,4 @@ export default {
|
|||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.detail-address-input {
|
|
||||||
cursor: pointer;
|
|
||||||
|
|
||||||
:deep(.el-input__wrapper),
|
|
||||||
:deep(.el-input__inner) {
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -233,8 +233,8 @@
|
|||||||
</el-button>
|
</el-button>
|
||||||
</div>
|
</div>
|
||||||
<div class="map-picker-content">
|
<div class="map-picker-content">
|
||||||
<div ref="amap" class="common-address-page__map"></div>
|
|
||||||
<map-search-results :results="mapSearchResults" @select="selectMapSearchResult" />
|
<map-search-results :results="mapSearchResults" @select="selectMapSearchResult" />
|
||||||
|
<div ref="amap" class="common-address-page__map"></div>
|
||||||
</div>
|
</div>
|
||||||
<div class="common-address-page__map-info">
|
<div class="common-address-page__map-info">
|
||||||
<span>{{ mapStatus }}</span>
|
<span>{{ mapStatus }}</span>
|
||||||
@@ -270,7 +270,6 @@ import { createOption } from '@/option/base/common-address';
|
|||||||
import { mapGetters } from 'vuex';
|
import { mapGetters } from 'vuex';
|
||||||
import { downloadXls } from '@/utils/util';
|
import { downloadXls } from '@/utils/util';
|
||||||
import { getToken } from '@/utils/auth';
|
import { getToken } from '@/utils/auth';
|
||||||
import { normalizeMapSearchResults } from '@/utils/map-search';
|
|
||||||
import { isMobile } from '@/utils/validate';
|
import { isMobile } from '@/utils/validate';
|
||||||
import NProgress from 'nprogress';
|
import NProgress from 'nprogress';
|
||||||
import 'nprogress/nprogress.css';
|
import 'nprogress/nprogress.css';
|
||||||
@@ -1118,7 +1117,12 @@ export default {
|
|||||||
this.ensureAmapGeocoder()
|
this.ensureAmapGeocoder()
|
||||||
.then(() => this.runAmapGeocode('location', keyword))
|
.then(() => this.runAmapGeocode('location', keyword))
|
||||||
.then(result => {
|
.then(result => {
|
||||||
this.mapSearchResults = normalizeMapSearchResults(result, keyword);
|
this.mapSearchResults = (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);
|
const point = this.resolveMapPoint(result);
|
||||||
if (!point) {
|
if (!point) {
|
||||||
this.mapStatus = '未找到匹配地址';
|
this.mapStatus = '未找到匹配地址';
|
||||||
|
|||||||
@@ -194,11 +194,10 @@
|
|||||||
<el-input
|
<el-input
|
||||||
v-model="form.detailAddress"
|
v-model="form.detailAddress"
|
||||||
class="detail-address-input"
|
class="detail-address-input"
|
||||||
readonly
|
clearable
|
||||||
maxlength="255"
|
maxlength="255"
|
||||||
show-word-limit
|
show-word-limit
|
||||||
placeholder="点击选择地图地址"
|
placeholder="请输入"
|
||||||
@click="openAddressMapPicker"
|
|
||||||
/>
|
/>
|
||||||
</template>
|
</template>
|
||||||
<template #remark-form>
|
<template #remark-form>
|
||||||
@@ -245,13 +244,6 @@
|
|||||||
</template>
|
</template>
|
||||||
</avue-form>
|
</avue-form>
|
||||||
</el-dialog>
|
</el-dialog>
|
||||||
<address-map-picker
|
|
||||||
v-model="addressMapPickerVisible"
|
|
||||||
:address="form.detailAddress"
|
|
||||||
:longitude="form.longitude"
|
|
||||||
:latitude="form.latitude"
|
|
||||||
@confirm="handleAddressMapConfirm"
|
|
||||||
/>
|
|
||||||
</basic-container>
|
</basic-container>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@@ -272,16 +264,12 @@ import { openImportDialog } from '@/utils/import-excel';
|
|||||||
import { formatUpdateUserName } from '@/utils/audit';
|
import { formatUpdateUserName } from '@/utils/audit';
|
||||||
import { getToken } from '@/utils/auth';
|
import { getToken } from '@/utils/auth';
|
||||||
import { getCoordinateValidationMessage, normalizeCoordinateInput } from '@/utils/coordinate';
|
import { getCoordinateValidationMessage, normalizeCoordinateInput } from '@/utils/coordinate';
|
||||||
import AddressMapPicker from '@/components/address-map-picker/main.vue';
|
|
||||||
import NProgress from 'nprogress';
|
import NProgress from 'nprogress';
|
||||||
import 'nprogress/nprogress.css';
|
import 'nprogress/nprogress.css';
|
||||||
|
|
||||||
const DEFAULT_COUNTRY_CODE = '+86';
|
const DEFAULT_COUNTRY_CODE = '+86';
|
||||||
const newLocal = '请选择类型';
|
const newLocal = '请选择类型';
|
||||||
export default {
|
export default {
|
||||||
components: {
|
|
||||||
AddressMapPicker,
|
|
||||||
},
|
|
||||||
data() {
|
data() {
|
||||||
const validateCode = (rule, value, callback) => {
|
const validateCode = (rule, value, callback) => {
|
||||||
const code = String(value || '').toUpperCase();
|
const code = String(value || '').toUpperCase();
|
||||||
@@ -327,7 +315,6 @@ export default {
|
|||||||
loading: true,
|
loading: true,
|
||||||
data: [],
|
data: [],
|
||||||
excelBox: false,
|
excelBox: false,
|
||||||
addressMapPickerVisible: false,
|
|
||||||
excelForm: {},
|
excelForm: {},
|
||||||
portOptions: [],
|
portOptions: [],
|
||||||
countryOptions: [],
|
countryOptions: [],
|
||||||
@@ -585,11 +572,11 @@ export default {
|
|||||||
minWidth: 220,
|
minWidth: 220,
|
||||||
overHidden: false,
|
overHidden: false,
|
||||||
order: 85,
|
order: 85,
|
||||||
placeholder: '点击选择地图地址',
|
placeholder: '请输入',
|
||||||
maxlength: 255,
|
maxlength: 255,
|
||||||
showWordLimit: true,
|
showWordLimit: true,
|
||||||
rules: [
|
rules: [
|
||||||
{ required: true, message: '请选择详细地址', trigger: 'change' },
|
{ required: true, message: '请输入详细地址', trigger: 'blur' },
|
||||||
{ max: 255, message: '详细地址不能超过255字', trigger: 'blur' },
|
{ max: 255, message: '详细地址不能超过255字', trigger: 'blur' },
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
@@ -1053,21 +1040,6 @@ export default {
|
|||||||
handleCoordinateInput(prop, value) {
|
handleCoordinateInput(prop, value) {
|
||||||
this.form[prop] = normalizeCoordinateInput(value);
|
this.form[prop] = normalizeCoordinateInput(value);
|
||||||
},
|
},
|
||||||
openAddressMapPicker() {
|
|
||||||
this.addressMapPickerVisible = true;
|
|
||||||
},
|
|
||||||
handleAddressMapConfirm(payload) {
|
|
||||||
const selection = typeof payload === 'string' ? { address: payload } : payload || {};
|
|
||||||
if (selection.address) {
|
|
||||||
this.form.detailAddress = selection.address;
|
|
||||||
}
|
|
||||||
if (selection.longitude !== undefined && selection.longitude !== null && selection.longitude !== '') {
|
|
||||||
this.form.longitude = Number(selection.longitude).toFixed(6);
|
|
||||||
}
|
|
||||||
if (selection.latitude !== undefined && selection.latitude !== null && selection.latitude !== '') {
|
|
||||||
this.form.latitude = Number(selection.latitude).toFixed(6);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
resolveCountryCode(country) {
|
resolveCountryCode(country) {
|
||||||
if (!country) {
|
if (!country) {
|
||||||
return Promise.resolve('');
|
return Promise.resolve('');
|
||||||
@@ -1135,7 +1107,7 @@ export default {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
if (isBlank(row.detailAddress)) {
|
if (isBlank(row.detailAddress)) {
|
||||||
this.$message.warning('请选择详细地址');
|
this.$message.warning('请输入详细地址');
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
if (isBlank(row.longitude)) {
|
if (isBlank(row.longitude)) {
|
||||||
@@ -1427,15 +1399,6 @@ export default {
|
|||||||
line-height: 20px;
|
line-height: 20px;
|
||||||
padding: 4px 0;
|
padding: 4px 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.detail-address-input {
|
|
||||||
cursor: pointer;
|
|
||||||
|
|
||||||
:deep(.el-input__wrapper),
|
|
||||||
:deep(.el-input__inner) {
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
<style lang="scss">
|
<style lang="scss">
|
||||||
|
|||||||
@@ -94,17 +94,6 @@
|
|||||||
/>
|
/>
|
||||||
</el-select>
|
</el-select>
|
||||||
</template>
|
</template>
|
||||||
<template #detailAddress-form>
|
|
||||||
<el-input
|
|
||||||
v-model="form.detailAddress"
|
|
||||||
class="detail-address-input"
|
|
||||||
readonly
|
|
||||||
maxlength="255"
|
|
||||||
show-word-limit
|
|
||||||
placeholder="点击选择地图地址"
|
|
||||||
@click="openAddressMapPicker"
|
|
||||||
/>
|
|
||||||
</template>
|
|
||||||
<template #longitude-form>
|
<template #longitude-form>
|
||||||
<el-input
|
<el-input
|
||||||
v-model="form.longitude"
|
v-model="form.longitude"
|
||||||
@@ -132,13 +121,6 @@
|
|||||||
</template>
|
</template>
|
||||||
</avue-form>
|
</avue-form>
|
||||||
</el-dialog>
|
</el-dialog>
|
||||||
<address-map-picker
|
|
||||||
v-model="addressMapPickerVisible"
|
|
||||||
:address="form.detailAddress"
|
|
||||||
:longitude="form.longitude"
|
|
||||||
:latitude="form.latitude"
|
|
||||||
@confirm="handleAddressMapConfirm"
|
|
||||||
/>
|
|
||||||
</basic-container>
|
</basic-container>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@@ -152,16 +134,12 @@ import { openImportDialog } from '@/utils/import-excel';
|
|||||||
import { formatUpdateUserName } from '@/utils/audit';
|
import { formatUpdateUserName } from '@/utils/audit';
|
||||||
import { getToken } from '@/utils/auth';
|
import { getToken } from '@/utils/auth';
|
||||||
import { getCoordinateValidationMessage, normalizeCoordinateInput } from '@/utils/coordinate';
|
import { getCoordinateValidationMessage, normalizeCoordinateInput } from '@/utils/coordinate';
|
||||||
import AddressMapPicker from '@/components/address-map-picker/main.vue';
|
|
||||||
import NProgress from 'nprogress';
|
import NProgress from 'nprogress';
|
||||||
import 'nprogress/nprogress.css';
|
import 'nprogress/nprogress.css';
|
||||||
|
|
||||||
const DEFAULT_COUNTRY_CODE = '+86';
|
const DEFAULT_COUNTRY_CODE = '+86';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
components: {
|
|
||||||
AddressMapPicker,
|
|
||||||
},
|
|
||||||
data() {
|
data() {
|
||||||
const validateTmisCode = (rule, value, callback) => {
|
const validateTmisCode = (rule, value, callback) => {
|
||||||
if (!/^\d{5}$/.test(value || '')) {
|
if (!/^\d{5}$/.test(value || '')) {
|
||||||
@@ -207,7 +185,6 @@ export default {
|
|||||||
loading: true,
|
loading: true,
|
||||||
data: [],
|
data: [],
|
||||||
excelBox: false,
|
excelBox: false,
|
||||||
addressMapPickerVisible: false,
|
|
||||||
excelForm: {},
|
excelForm: {},
|
||||||
provinceOptions: [],
|
provinceOptions: [],
|
||||||
cityOptions: [],
|
cityOptions: [],
|
||||||
@@ -363,14 +340,15 @@ export default {
|
|||||||
{
|
{
|
||||||
label: '详细地址',
|
label: '详细地址',
|
||||||
prop: 'detailAddress',
|
prop: 'detailAddress',
|
||||||
formslot: true,
|
type: 'textarea',
|
||||||
|
minRows: 2,
|
||||||
span: 24,
|
span: 24,
|
||||||
minWidth: 220,
|
minWidth: 220,
|
||||||
overHidden: false,
|
overHidden: false,
|
||||||
maxlength: 255,
|
maxlength: 255,
|
||||||
showWordLimit: true,
|
showWordLimit: true,
|
||||||
rules: [
|
rules: [
|
||||||
{ required: true, message: '请选择详细地址', trigger: 'change' },
|
{ required: true, message: '请输入详细地址', trigger: 'blur' },
|
||||||
{ max: 255, message: '详细地址不能超过255字', trigger: 'blur' },
|
{ max: 255, message: '详细地址不能超过255字', trigger: 'blur' },
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
@@ -674,29 +652,6 @@ export default {
|
|||||||
handleCoordinateInput(prop, value) {
|
handleCoordinateInput(prop, value) {
|
||||||
this.form[prop] = normalizeCoordinateInput(value);
|
this.form[prop] = normalizeCoordinateInput(value);
|
||||||
},
|
},
|
||||||
openAddressMapPicker() {
|
|
||||||
this.addressMapPickerVisible = true;
|
|
||||||
},
|
|
||||||
handleAddressMapConfirm(payload) {
|
|
||||||
const selection = typeof payload === 'string' ? { address: payload } : payload || {};
|
|
||||||
if (selection.address) {
|
|
||||||
this.form.detailAddress = selection.address;
|
|
||||||
}
|
|
||||||
if (
|
|
||||||
selection.longitude !== undefined &&
|
|
||||||
selection.longitude !== null &&
|
|
||||||
selection.longitude !== ''
|
|
||||||
) {
|
|
||||||
this.form.longitude = Number(selection.longitude).toFixed(6);
|
|
||||||
}
|
|
||||||
if (
|
|
||||||
selection.latitude !== undefined &&
|
|
||||||
selection.latitude !== null &&
|
|
||||||
selection.latitude !== ''
|
|
||||||
) {
|
|
||||||
this.form.latitude = Number(selection.latitude).toFixed(6);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
normalizeRow(row) {
|
normalizeRow(row) {
|
||||||
row.code = row.tmisCode ? `TL${row.tmisCode}` : row.code;
|
row.code = row.tmisCode ? `TL${row.tmisCode}` : row.code;
|
||||||
row.regionCode = String(row.regionCode || '').trim();
|
row.regionCode = String(row.regionCode || '').trim();
|
||||||
@@ -955,16 +910,9 @@ export default {
|
|||||||
:deep(.el-table td .cell) {
|
:deep(.el-table td .cell) {
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.detail-address-input {
|
|
||||||
cursor: pointer;
|
|
||||||
|
|
||||||
:deep(.el-input__wrapper),
|
|
||||||
:deep(.el-input__inner) {
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
<style lang="scss">
|
<style lang="scss">
|
||||||
|
|||||||
@@ -289,8 +289,8 @@
|
|||||||
</el-button>
|
</el-button>
|
||||||
</div>
|
</div>
|
||||||
<div class="map-picker-content">
|
<div class="map-picker-content">
|
||||||
<div ref="routeAmap" class="common-route-page__map"></div>
|
|
||||||
<map-search-results :results="routeMapSearchResults" @select="selectRouteMapSearchResult" />
|
<map-search-results :results="routeMapSearchResults" @select="selectRouteMapSearchResult" />
|
||||||
|
<div ref="routeAmap" class="common-route-page__map"></div>
|
||||||
</div>
|
</div>
|
||||||
<div class="common-route-page__map-info">
|
<div class="common-route-page__map-info">
|
||||||
<span>{{ routeMapStatus }}</span>
|
<span>{{ routeMapStatus }}</span>
|
||||||
@@ -323,7 +323,6 @@ import { addressTypeOptions } from '@/option/base/common-address';
|
|||||||
import { config, excelOption, option } from '@/option/business/common-route';
|
import { config, excelOption, option } from '@/option/business/common-route';
|
||||||
import { getToken } from '@/utils/auth';
|
import { getToken } from '@/utils/auth';
|
||||||
import { openImportDialog } from '@/utils/import-excel';
|
import { openImportDialog } from '@/utils/import-excel';
|
||||||
import { normalizeMapSearchResults } from '@/utils/map-search';
|
|
||||||
import { downloadXls } from '@/utils/util';
|
import { downloadXls } from '@/utils/util';
|
||||||
import { isMobile } from '@/utils/validate';
|
import { isMobile } from '@/utils/validate';
|
||||||
import { List, Search } from '@element-plus/icons-vue';
|
import { List, Search } from '@element-plus/icons-vue';
|
||||||
@@ -617,7 +616,12 @@ export default {
|
|||||||
})
|
})
|
||||||
.then(() => this.runAmapGeocode('location', keyword))
|
.then(() => this.runAmapGeocode('location', keyword))
|
||||||
.then(result => {
|
.then(result => {
|
||||||
this.routeMapSearchResults = normalizeMapSearchResults(result, keyword);
|
this.routeMapSearchResults = (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);
|
const point = this.resolveMapPoint(result);
|
||||||
if (!point) {
|
if (!point) {
|
||||||
this.routeMapStatus = '未找到匹配地址';
|
this.routeMapStatus = '未找到匹配地址';
|
||||||
|
|||||||
@@ -162,9 +162,9 @@
|
|||||||
v-model="row.billingUnit"
|
v-model="row.billingUnit"
|
||||||
clearable
|
clearable
|
||||||
filterable
|
filterable
|
||||||
:disabled="!canSelectBillingUnit(row)"
|
:disabled="!row.billingElement"
|
||||||
:loading="unitLoading"
|
:loading="unitLoading"
|
||||||
:placeholder="billingUnitPlaceholder(row)"
|
:placeholder="row.billingElement ? '请选择' : '请先选择计费要素'"
|
||||||
><el-option
|
><el-option
|
||||||
v-for="item in unitOptionsFor(row)"
|
v-for="item in unitOptionsFor(row)"
|
||||||
:key="item.id || item.value"
|
:key="item.id || item.value"
|
||||||
@@ -381,18 +381,11 @@ import { getDictionary } from '@/api/system/dictbiz';
|
|||||||
import SectionCard from '@/components/section-card/main.vue';
|
import SectionCard from '@/components/section-card/main.vue';
|
||||||
|
|
||||||
const clone = value => JSON.parse(JSON.stringify(value));
|
const clone = value => JSON.parse(JSON.stringify(value));
|
||||||
/** 计费要素与计量单位维度的对应关系(这些要素可选计费单位) */
|
/** 计费要素与计量单位维度的对应关系 */
|
||||||
const BILLING_ELEMENT_DIMENSION_MAP = {
|
const BILLING_ELEMENT_DIMENSION_MAP = {
|
||||||
按重量: '重量',
|
按重量: '重量',
|
||||||
按体积: '体积',
|
按体积: '体积',
|
||||||
按数量: '数量',
|
按车辆: '数量',
|
||||||
};
|
|
||||||
const BILLING_UNIT_SELECTABLE_ELEMENTS = Object.keys(BILLING_ELEMENT_DIMENSION_MAP);
|
|
||||||
/** 固定计费单位(不可编辑) */
|
|
||||||
const FIXED_BILLING_UNIT_MAP = {
|
|
||||||
按里程: '公里',
|
|
||||||
'按吨·公里': '吨公里',
|
|
||||||
'固定金额(整单一口价)': '单',
|
|
||||||
};
|
};
|
||||||
const defaultRule = () => ({
|
const defaultRule = () => ({
|
||||||
feeType: '',
|
feeType: '',
|
||||||
@@ -443,6 +436,7 @@ export default {
|
|||||||
billingElements: [
|
billingElements: [
|
||||||
'按重量',
|
'按重量',
|
||||||
'按体积',
|
'按体积',
|
||||||
|
'按车辆',
|
||||||
'按里程',
|
'按里程',
|
||||||
'按吨·公里',
|
'按吨·公里',
|
||||||
'固定金额(整单一口价)',
|
'固定金额(整单一口价)',
|
||||||
@@ -451,6 +445,7 @@ export default {
|
|||||||
typeMap: {
|
typeMap: {
|
||||||
按重量: ['固定单价', '区间单价', '阶梯单价', '区间阶梯一口价'],
|
按重量: ['固定单价', '区间单价', '阶梯单价', '区间阶梯一口价'],
|
||||||
按体积: ['固定单价', '区间单价', '阶梯单价', '区间阶梯一口价'],
|
按体积: ['固定单价', '区间单价', '阶梯单价', '区间阶梯一口价'],
|
||||||
|
按车辆: ['固定单价'],
|
||||||
按里程: ['固定单价', '区间单价', '阶梯单价', '区间阶梯一口价'],
|
按里程: ['固定单价', '区间单价', '阶梯单价', '区间阶梯一口价'],
|
||||||
'按吨·公里': ['固定单价', '区间单价', '阶梯单价', '区间阶梯一口价'],
|
'按吨·公里': ['固定单价', '区间单价', '阶梯单价', '区间阶梯一口价'],
|
||||||
'固定金额(整单一口价)': ['固定一口价'],
|
'固定金额(整单一口价)': ['固定一口价'],
|
||||||
@@ -565,7 +560,6 @@ export default {
|
|||||||
next.limitRanges = next.limitRanges.map(item => ({ ...item, minimumBillingWeight: '' }));
|
next.limitRanges = next.limitRanges.map(item => ({ ...item, minimumBillingWeight: '' }));
|
||||||
this.syncLegacyLimit(next);
|
this.syncLegacyLimit(next);
|
||||||
next.matchCondition = { ...defaultRule().matchCondition, ...(rule.matchCondition || {}) };
|
next.matchCondition = { ...defaultRule().matchCondition, ...(rule.matchCondition || {}) };
|
||||||
this.syncBillingUnit(next);
|
|
||||||
return next;
|
return next;
|
||||||
},
|
},
|
||||||
normalizeRanges(row) {
|
normalizeRanges(row) {
|
||||||
@@ -657,26 +651,7 @@ export default {
|
|||||||
measurementDimension(billingElement) {
|
measurementDimension(billingElement) {
|
||||||
return BILLING_ELEMENT_DIMENSION_MAP[billingElement] || '';
|
return BILLING_ELEMENT_DIMENSION_MAP[billingElement] || '';
|
||||||
},
|
},
|
||||||
fixedBillingUnit(billingElement) {
|
|
||||||
return FIXED_BILLING_UNIT_MAP[String(billingElement || '').trim()] || '';
|
|
||||||
},
|
|
||||||
canSelectBillingUnit(row) {
|
|
||||||
return BILLING_UNIT_SELECTABLE_ELEMENTS.includes(String(row?.billingElement || '').trim());
|
|
||||||
},
|
|
||||||
billingUnitPlaceholder(row) {
|
|
||||||
if (!row?.billingElement) return '请先选择计费要素';
|
|
||||||
if (this.fixedBillingUnit(row.billingElement)) return this.fixedBillingUnit(row.billingElement);
|
|
||||||
if (!this.canSelectBillingUnit(row)) return '当前计费要素无需选择';
|
|
||||||
return '请选择';
|
|
||||||
},
|
|
||||||
unitOptionsFor(row) {
|
unitOptionsFor(row) {
|
||||||
const fixedUnit = this.fixedBillingUnit(row?.billingElement);
|
|
||||||
if (fixedUnit) {
|
|
||||||
return [{ id: fixedUnit, label: fixedUnit, value: fixedUnit }];
|
|
||||||
}
|
|
||||||
if (!this.canSelectBillingUnit(row)) {
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
const dimension = this.measurementDimension(row?.billingElement);
|
const dimension = this.measurementDimension(row?.billingElement);
|
||||||
if (dimension) {
|
if (dimension) {
|
||||||
return this.measurementUnits
|
return this.measurementUnits
|
||||||
@@ -688,18 +663,15 @@ export default {
|
|||||||
}))
|
}))
|
||||||
.filter(item => item.value);
|
.filter(item => item.value);
|
||||||
}
|
}
|
||||||
return [];
|
return (this.unitOptions || [])
|
||||||
|
.map(item => ({
|
||||||
|
id: item.id || item.dictKey || item.dictValue,
|
||||||
|
label: item.dictValue,
|
||||||
|
value: item.dictValue,
|
||||||
|
}))
|
||||||
|
.filter(item => item.value);
|
||||||
},
|
},
|
||||||
syncBillingUnit(row) {
|
syncBillingUnit(row) {
|
||||||
const fixedUnit = this.fixedBillingUnit(row?.billingElement);
|
|
||||||
if (fixedUnit) {
|
|
||||||
row.billingUnit = fixedUnit;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (!this.canSelectBillingUnit(row)) {
|
|
||||||
row.billingUnit = '';
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const options = this.unitOptionsFor(row);
|
const options = this.unitOptionsFor(row);
|
||||||
if (!options.some(item => String(item.value) === String(row.billingUnit || ''))) {
|
if (!options.some(item => String(item.value) === String(row.billingUnit || ''))) {
|
||||||
row.billingUnit = '';
|
row.billingUnit = '';
|
||||||
@@ -920,6 +892,7 @@ export default {
|
|||||||
['taxRate', '税率'],
|
['taxRate', '税率'],
|
||||||
['billingElement', '计费要素'],
|
['billingElement', '计费要素'],
|
||||||
['billingType', '计费类型'],
|
['billingType', '计费类型'],
|
||||||
|
['billingUnit', '计费单位'],
|
||||||
];
|
];
|
||||||
for (const [i, row] of this.draft.rules.entries()) {
|
for (const [i, row] of this.draft.rules.entries()) {
|
||||||
const empty = required.find(
|
const empty = required.find(
|
||||||
@@ -929,15 +902,6 @@ export default {
|
|||||||
this.$message.warning(`第${i + 1}行${empty[1]}不能为空`);
|
this.$message.warning(`第${i + 1}行${empty[1]}不能为空`);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
if (
|
|
||||||
this.canSelectBillingUnit(row) &&
|
|
||||||
(row.billingUnit === undefined ||
|
|
||||||
row.billingUnit === null ||
|
|
||||||
String(row.billingUnit).trim() === '')
|
|
||||||
) {
|
|
||||||
this.$message.warning(`第${i + 1}行计费单位不能为空`);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
const feeItem = String(row.feeItem).trim();
|
const feeItem = String(row.feeItem).trim();
|
||||||
if (feeItemSet.has(feeItem)) {
|
if (feeItemSet.has(feeItem)) {
|
||||||
this.$message.warning(`费用项“${feeItem}”不能重复`);
|
this.$message.warning(`费用项“${feeItem}”不能重复`);
|
||||||
|
|||||||
@@ -1899,11 +1899,11 @@
|
|||||||
label-width="auto"
|
label-width="auto"
|
||||||
class="business-crud-page__module-form business-crud-page__module-form--three"
|
class="business-crud-page__module-form business-crud-page__module-form--three"
|
||||||
>
|
>
|
||||||
<el-form-item label="账单起始日" required :error="settlementRuleErrors.billStartDate">
|
<el-form-item label="账单起始日期" required :error="settlementRuleErrors.billStartDate">
|
||||||
<el-date-picker
|
<el-date-picker
|
||||||
v-model="settlementRuleForm.billStartDate"
|
v-model="settlementRuleForm.billStartDate"
|
||||||
type="date"
|
type="date"
|
||||||
placeholder="请选择账单起始日"
|
placeholder="请选择账单起始日期"
|
||||||
format="YYYY-MM-DD"
|
format="YYYY-MM-DD"
|
||||||
value-format="YYYY-MM-DD"
|
value-format="YYYY-MM-DD"
|
||||||
:disabled="dialogReadonly"
|
:disabled="dialogReadonly"
|
||||||
@@ -2478,7 +2478,7 @@
|
|||||||
{{ formatTransportPlanDetailTransportType(waybillTransportMode(detailRow)) }}
|
{{ formatTransportPlanDetailTransportType(waybillTransportMode(detailRow)) }}
|
||||||
</span>
|
</span>
|
||||||
<el-link
|
<el-link
|
||||||
v-if="detailRow.id && canChangeWaybillRoute(detailRow)"
|
v-if="detailRow.id"
|
||||||
class="business-crud-page__waybill-route-change-link"
|
class="business-crud-page__waybill-route-change-link"
|
||||||
type="primary"
|
type="primary"
|
||||||
@click="openWaybillRouteChangeDialog"
|
@click="openWaybillRouteChangeDialog"
|
||||||
@@ -2947,6 +2947,35 @@
|
|||||||
</el-table-column>
|
</el-table-column>
|
||||||
</el-table>
|
</el-table>
|
||||||
</section-card>
|
</section-card>
|
||||||
|
<section-card title="路线编辑">
|
||||||
|
<template #extra>
|
||||||
|
<span class="business-crud-page__route-change-hint">拖拽调整顺序</span>
|
||||||
|
</template>
|
||||||
|
<div
|
||||||
|
v-if="waybillRouteChangeNodes.length"
|
||||||
|
class="business-crud-page__route-change-list"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
v-for="(node, index) in waybillRouteChangeNodes"
|
||||||
|
:key="node.key || index"
|
||||||
|
class="business-crud-page__route-change-item"
|
||||||
|
draggable="true"
|
||||||
|
@dragstart="handleWaybillRouteChangeDragStart(index)"
|
||||||
|
@dragover.prevent
|
||||||
|
@drop="handleWaybillRouteChangeDrop(index)"
|
||||||
|
>
|
||||||
|
<span class="business-crud-page__route-change-type">
|
||||||
|
{{ waybillRouteChangeTypeText(index) }}
|
||||||
|
</span>
|
||||||
|
<span>{{ node.address }}</span>
|
||||||
|
<span class="business-crud-page__route-change-tags">
|
||||||
|
<el-tag v-for="tag in node.tags || []" :key="tag" size="small">{{ tag }}</el-tag>
|
||||||
|
</span>
|
||||||
|
<el-icon><Rank /></el-icon>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<el-empty v-else description="暂无路线" :image-size="60" />
|
||||||
|
</section-card>
|
||||||
<el-form label-width="auto">
|
<el-form label-width="auto">
|
||||||
<el-form-item label="变更备注" style="margin-top: 8px">
|
<el-form-item label="变更备注" style="margin-top: 8px">
|
||||||
<el-input
|
<el-input
|
||||||
@@ -4007,7 +4036,7 @@
|
|||||||
</el-input>
|
</el-input>
|
||||||
<div class="business-crud-page__dispatch-quantity-hint">
|
<div class="business-crud-page__dispatch-quantity-hint">
|
||||||
剩余数量:{{
|
剩余数量:{{
|
||||||
formatDispatchQuantity(dispatchItemHintRemainingQuantity(dispatchItemForm))
|
formatDispatchQuantity(dispatchItemRemainingQuantity(dispatchItemForm))
|
||||||
}}
|
}}
|
||||||
{{ dispatchItemForm.quantityUnit || '吨' }}
|
{{ dispatchItemForm.quantityUnit || '吨' }}
|
||||||
</div>
|
</div>
|
||||||
@@ -4064,11 +4093,7 @@
|
|||||||
</el-input>
|
</el-input>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
<el-form-item label="其他费用合计">
|
<el-form-item label="其他费用合计">
|
||||||
<el-input
|
<el-input v-model="dispatchItemForm.otherFeeTotal" placeholder="请输入">
|
||||||
v-model="dispatchItemForm.otherFeeTotal"
|
|
||||||
placeholder="请输入"
|
|
||||||
@input="value => handleDispatchNumberInput('otherFeeTotal', value)"
|
|
||||||
>
|
|
||||||
<template #suffix>{{ dispatchItemFreightCurrencyLabel }}</template>
|
<template #suffix>{{ dispatchItemFreightCurrencyLabel }}</template>
|
||||||
</el-input>
|
</el-input>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
@@ -5000,8 +5025,8 @@
|
|||||||
</el-button>
|
</el-button>
|
||||||
</div>
|
</div>
|
||||||
<div class="map-picker-content">
|
<div class="map-picker-content">
|
||||||
<div ref="transportAmap" class="business-crud-page__map"></div>
|
|
||||||
<map-search-results :results="transportMapSearchResults" @select="selectTransportMapSearchResult" />
|
<map-search-results :results="transportMapSearchResults" @select="selectTransportMapSearchResult" />
|
||||||
|
<div ref="transportAmap" class="business-crud-page__map"></div>
|
||||||
</div>
|
</div>
|
||||||
<div class="business-crud-page__map-info">
|
<div class="business-crud-page__map-info">
|
||||||
<span>{{ transportMapStatus }}</span>
|
<span>{{ transportMapStatus }}</span>
|
||||||
@@ -5275,7 +5300,7 @@ import {
|
|||||||
getVoucherImages as getProcessConfigVoucherImages,
|
getVoucherImages as getProcessConfigVoucherImages,
|
||||||
} from '@/api/business/process-config';
|
} from '@/api/business/process-config';
|
||||||
import { getList as getCarrierCustomerList } from '@/api/vehicle/customer-archive';
|
import { getList as getCarrierCustomerList } from '@/api/vehicle/customer-archive';
|
||||||
import { fetchDriversByCarrierOrganizations } from '@/utils/carrier-org-resource';
|
import { getList as getDriverList } from '@/api/transportCapacity/driver';
|
||||||
import { getDictionary } from '@/api/system/dictbiz';
|
import { getDictionary } from '@/api/system/dictbiz';
|
||||||
import { getDictionary as getSystemDictionary } from '@/api/system/dict';
|
import { getDictionary as getSystemDictionary } from '@/api/system/dict';
|
||||||
import { addressTypeOptions } from '@/option/base/common-address';
|
import { addressTypeOptions } from '@/option/base/common-address';
|
||||||
@@ -5283,11 +5308,10 @@ import { packageOptions } from '@/option/business/common';
|
|||||||
import { formatUpdateUserName } from '@/utils/audit';
|
import { formatUpdateUserName } from '@/utils/audit';
|
||||||
import { getToken } from '@/utils/auth';
|
import { getToken } from '@/utils/auth';
|
||||||
import { openImportDialog } from '@/utils/import-excel';
|
import { openImportDialog } from '@/utils/import-excel';
|
||||||
import { normalizeMapSearchResults } from '@/utils/map-search';
|
|
||||||
import { applyTableMenuWidth } from '@/utils/table-menu';
|
import { applyTableMenuWidth } from '@/utils/table-menu';
|
||||||
import { downloadFileByUrl, downloadXls } from '@/utils/util';
|
import { downloadFileByUrl, downloadXls } from '@/utils/util';
|
||||||
import { isMobile } from '@/utils/validate';
|
import { isMobile } from '@/utils/validate';
|
||||||
import { Location, OfficeBuilding, Search } from '@element-plus/icons-vue';
|
import { Location, OfficeBuilding, Rank, Search } from '@element-plus/icons-vue';
|
||||||
import { ElImageViewer } from 'element-plus';
|
import { ElImageViewer } from 'element-plus';
|
||||||
import { OpenFileViewer } from '@open-file-viewer/vue';
|
import { OpenFileViewer } from '@open-file-viewer/vue';
|
||||||
import {
|
import {
|
||||||
@@ -5550,6 +5574,7 @@ export default {
|
|||||||
components: {
|
components: {
|
||||||
BillingPlanEditor,
|
BillingPlanEditor,
|
||||||
PageAvueForm,
|
PageAvueForm,
|
||||||
|
Rank,
|
||||||
ElImageViewer,
|
ElImageViewer,
|
||||||
OpenFileViewer,
|
OpenFileViewer,
|
||||||
},
|
},
|
||||||
@@ -5612,6 +5637,7 @@ export default {
|
|||||||
waybillRouteChangeNodes: [],
|
waybillRouteChangeNodes: [],
|
||||||
waybillRouteChangeRemark: '',
|
waybillRouteChangeRemark: '',
|
||||||
waybillRouteChangeRecords: [],
|
waybillRouteChangeRecords: [],
|
||||||
|
waybillRouteChangeDragIndex: -1,
|
||||||
waybillRouteChangeSaving: false,
|
waybillRouteChangeSaving: false,
|
||||||
mileageDialog: {
|
mileageDialog: {
|
||||||
visible: false,
|
visible: false,
|
||||||
@@ -6345,21 +6371,19 @@ export default {
|
|||||||
if (this.dispatchItemForm.taskEntryMode !== 'full') {
|
if (this.dispatchItemForm.taskEntryMode !== 'full') {
|
||||||
const quantity = Number(this.dispatchItemForm.quantity || 0);
|
const quantity = Number(this.dispatchItemForm.quantity || 0);
|
||||||
const unitPrice = Number(this.dispatchItemForm.unitPrice || 0);
|
const unitPrice = Number(this.dispatchItemForm.unitPrice || 0);
|
||||||
const otherFeeRaw = this.normalizeDispatchFeeValue(this.dispatchItemForm.otherFeeTotal);
|
const otherFeeTotal = Number(this.dispatchItemForm.otherFeeTotal || 0);
|
||||||
const otherFeeTotal = Number(otherFeeRaw || 0);
|
|
||||||
const hasFreight = this.dispatchItemForm.unitPrice !== '' && quantity > 0;
|
const hasFreight = this.dispatchItemForm.unitPrice !== '' && quantity > 0;
|
||||||
const hasOtherFee = otherFeeRaw !== '';
|
const hasOtherFee = this.dispatchItemForm.otherFeeTotal !== '';
|
||||||
if (!hasFreight && !hasOtherFee) return '';
|
if (!hasFreight && !hasOtherFee) return '';
|
||||||
const total = (hasFreight ? quantity * unitPrice : 0) + (hasOtherFee ? otherFeeTotal : 0);
|
const total = (hasFreight ? quantity * unitPrice : 0) + (hasOtherFee ? otherFeeTotal : 0);
|
||||||
return Number.isInteger(total) ? String(total) : String(Number(total.toFixed(2)));
|
return Number.isInteger(total) ? String(total) : String(Number(total.toFixed(2)));
|
||||||
}
|
}
|
||||||
const otherFeeRaw = this.normalizeDispatchFeeValue(this.dispatchItemForm.otherFeeTotal);
|
const otherFeeTotal = Number(this.dispatchItemForm.otherFeeTotal || 0);
|
||||||
const otherFeeTotal = Number(otherFeeRaw || 0);
|
|
||||||
const freightTotal = this.dispatchItemCargoRows.reduce(
|
const freightTotal = this.dispatchItemCargoRows.reduce(
|
||||||
(total, cargo) => total + Number(this.dispatchCargoFreightAmount(cargo) || 0),
|
(total, cargo) => total + Number(this.dispatchCargoFreightAmount(cargo) || 0),
|
||||||
0
|
0
|
||||||
);
|
);
|
||||||
const hasOtherFee = otherFeeRaw !== '';
|
const hasOtherFee = this.dispatchItemForm.otherFeeTotal !== '';
|
||||||
if (!freightTotal && !hasOtherFee) return '';
|
if (!freightTotal && !hasOtherFee) return '';
|
||||||
const total = freightTotal + (hasOtherFee ? otherFeeTotal : 0);
|
const total = freightTotal + (hasOtherFee ? otherFeeTotal : 0);
|
||||||
return Number.isInteger(total) ? String(total) : String(Number(total.toFixed(2)));
|
return Number.isInteger(total) ? String(total) : String(Number(total.toFixed(2)));
|
||||||
@@ -7414,7 +7438,7 @@ export default {
|
|||||||
: [];
|
: [];
|
||||||
},
|
},
|
||||||
openWaybillRouteChangeDialog() {
|
openWaybillRouteChangeDialog() {
|
||||||
if (!this.detailRow.id || !this.canChangeWaybillRoute(this.detailRow)) return;
|
if (!this.detailRow.id) return;
|
||||||
this.restoreWaybillRouteChangeRecords();
|
this.restoreWaybillRouteChangeRecords();
|
||||||
this.waybillRouteChangeTab = 'change';
|
this.waybillRouteChangeTab = 'change';
|
||||||
this.waybillRouteChangeRemark = '';
|
this.waybillRouteChangeRemark = '';
|
||||||
@@ -7431,11 +7455,22 @@ export default {
|
|||||||
this.waybillRouteChangeNodes = this.buildWaybillRouteNodes(this.waybillRouteChangeRows);
|
this.waybillRouteChangeNodes = this.buildWaybillRouteNodes(this.waybillRouteChangeRows);
|
||||||
this.waybillRouteChangeBox = true;
|
this.waybillRouteChangeBox = true;
|
||||||
},
|
},
|
||||||
canChangeWaybillRoute(row = {}) {
|
waybillRouteChangeTypeText(index) {
|
||||||
const status = String(this.statusValue(row) || row.businessStatus || row.status || '');
|
if (index === 0) return '起';
|
||||||
if (status === 'completed') return false;
|
if (index === this.waybillRouteChangeNodes.length - 1) return '终';
|
||||||
const statusName = String(row.businessStatusName || row.statusName || '');
|
return '经';
|
||||||
return !statusName.includes('已完成');
|
},
|
||||||
|
handleWaybillRouteChangeDragStart(index) {
|
||||||
|
this.waybillRouteChangeDragIndex = index;
|
||||||
|
},
|
||||||
|
handleWaybillRouteChangeDrop(index) {
|
||||||
|
if (this.waybillRouteChangeDragIndex < 0 || this.waybillRouteChangeDragIndex === index)
|
||||||
|
return;
|
||||||
|
const nodes = [...this.waybillRouteChangeNodes];
|
||||||
|
const [node] = nodes.splice(this.waybillRouteChangeDragIndex, 1);
|
||||||
|
nodes.splice(index, 0, node);
|
||||||
|
this.waybillRouteChangeNodes = nodes;
|
||||||
|
this.waybillRouteChangeDragIndex = -1;
|
||||||
},
|
},
|
||||||
updateWaybillRouteChangeNodes() {
|
updateWaybillRouteChangeNodes() {
|
||||||
this.waybillRouteChangeNodes = this.buildWaybillRouteNodes(this.waybillRouteChangeRows);
|
this.waybillRouteChangeNodes = this.buildWaybillRouteNodes(this.waybillRouteChangeRows);
|
||||||
@@ -7484,8 +7519,9 @@ export default {
|
|||||||
row.departureAddress !== row.originalDepartureAddress ||
|
row.departureAddress !== row.originalDepartureAddress ||
|
||||||
row.arrivalAddress !== row.originalArrivalAddress;
|
row.arrivalAddress !== row.originalArrivalAddress;
|
||||||
const routeJson = JSON.stringify(this.waybillRouteChangeNodes);
|
const routeJson = JSON.stringify(this.waybillRouteChangeNodes);
|
||||||
if (!changed && !this.waybillRouteChangeRemark) {
|
const originalRouteJson = this.detailRow.routeJson || '';
|
||||||
this.$message.warning('请修改地址或填写变更备注');
|
if (!changed && routeJson === originalRouteJson && !this.waybillRouteChangeRemark) {
|
||||||
|
this.$message.warning('请修改地址、调整路线或填写变更备注');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (typeof this.api.changeRoute !== 'function') {
|
if (typeof this.api.changeRoute !== 'function') {
|
||||||
@@ -7493,9 +7529,11 @@ export default {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const content = `${row.waybillNo || '运单'}:发货地 ${row.originalDepartureAddress || '-'} → ${
|
const content = changed
|
||||||
row.departureAddress || '-'
|
? `${row.waybillNo || '运单'}:发货地 ${row.originalDepartureAddress || '-'} → ${
|
||||||
};到货地 ${row.originalArrivalAddress || '-'} → ${row.arrivalAddress || '-'}`;
|
row.departureAddress || '-'
|
||||||
|
};到货地 ${row.originalArrivalAddress || '-'} → ${row.arrivalAddress || '-'}`
|
||||||
|
: '调整运输路线顺序';
|
||||||
const records = [
|
const records = [
|
||||||
{
|
{
|
||||||
changeTime: this.$dayjs().format('YYYY-MM-DD HH:mm:ss'),
|
changeTime: this.$dayjs().format('YYYY-MM-DD HH:mm:ss'),
|
||||||
@@ -7746,8 +7784,8 @@ export default {
|
|||||||
const rule = this.normalizeSettlementRule(source);
|
const rule = this.normalizeSettlementRule(source);
|
||||||
this.settlementRuleErrors = {};
|
this.settlementRuleErrors = {};
|
||||||
if (this.isBillingFieldEmpty(rule.billStartDate)) {
|
if (this.isBillingFieldEmpty(rule.billStartDate)) {
|
||||||
this.settlementRuleErrors = { billStartDate: '请选择账单起始日' };
|
this.settlementRuleErrors = { billStartDate: '请选择账单起始日期' };
|
||||||
this.$message.warning(`${label}${label ? ':' : ''}请选择账单起始日`);
|
this.$message.warning(`${label}${label ? ':' : ''}请选择账单起始日期`);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
if (this.isBillingFieldEmpty(rule.settlementType)) {
|
if (this.isBillingFieldEmpty(rule.settlementType)) {
|
||||||
@@ -8926,7 +8964,6 @@ export default {
|
|||||||
this.form.carrierContractId = '';
|
this.form.carrierContractId = '';
|
||||||
}
|
}
|
||||||
this.form.carrierType = nextValue;
|
this.form.carrierType = nextValue;
|
||||||
this.clearTaskDriverVehicleFields();
|
|
||||||
if (this.isWaybillDetailLayout) {
|
if (this.isWaybillDetailLayout) {
|
||||||
this.taskCarrierRequestId += 1;
|
this.taskCarrierRequestId += 1;
|
||||||
this.taskCarrierOptions = [];
|
this.taskCarrierOptions = [];
|
||||||
@@ -9044,58 +9081,25 @@ export default {
|
|||||||
},
|
},
|
||||||
handleTaskCarrierChange(value) {
|
handleTaskCarrierChange(value) {
|
||||||
const carrier = this.taskCarrierOptions.find(item =>
|
const carrier = this.taskCarrierOptions.find(item =>
|
||||||
[
|
[item.value, item.customerName, item.carrierName, item.fullName, item.name].some(name =>
|
||||||
item.value,
|
String(name || '') === String(value || '')
|
||||||
item.customerName,
|
)
|
||||||
item.carrierName,
|
|
||||||
item.fullName,
|
|
||||||
item.name,
|
|
||||||
item.carrierContractId,
|
|
||||||
].some(name => String(name || '') === String(value || ''))
|
|
||||||
);
|
);
|
||||||
if (this.isWaybillDetailLayout && this.form.carrierType === '承运商') {
|
if (this.isWaybillDetailLayout && this.form.carrierType === '承运商') {
|
||||||
this.form.carrierContractId = carrier?.carrierContractId || '';
|
this.form.carrierContractId = carrier?.carrierContractId || '';
|
||||||
this.form.carrierId = carrier?.carrierId || '';
|
this.form.carrierId = carrier?.carrierId || '';
|
||||||
this.form.carrierName = carrier?.carrierName || value || '';
|
this.form.carrierName = carrier?.carrierName || value || '';
|
||||||
} else {
|
return;
|
||||||
this.form.carrierId = carrier?.id || carrier?.carrierId || '';
|
|
||||||
this.form.carrierName = value || '';
|
|
||||||
}
|
}
|
||||||
this.clearTaskDriverVehicleFields();
|
this.form.carrierId = carrier?.id || '';
|
||||||
},
|
this.form.carrierName = value || '';
|
||||||
clearTaskDriverVehicleFields() {
|
|
||||||
this.form.driverId = '';
|
|
||||||
this.form.driverName = '';
|
|
||||||
this.form.driverPhone = '';
|
|
||||||
this.form.vehicleNo = '';
|
|
||||||
this.form.trailerVehicleNo = '';
|
|
||||||
this.form.escortName = '';
|
|
||||||
this.form.escortPhone = '';
|
|
||||||
this.taskDriverOptions = [];
|
|
||||||
},
|
|
||||||
getActiveCarrierFilter() {
|
|
||||||
if (this.dispatchItemBox) {
|
|
||||||
return {
|
|
||||||
carrierId: this.dispatchItemForm.carrierId || '',
|
|
||||||
carrierName: this.dispatchItemForm.carrierName || '',
|
|
||||||
};
|
|
||||||
}
|
|
||||||
return {
|
|
||||||
carrierId: this.form.carrierId || '',
|
|
||||||
carrierName: this.form.carrierName || '',
|
|
||||||
};
|
|
||||||
},
|
},
|
||||||
loadTaskDriverOptions() {
|
loadTaskDriverOptions() {
|
||||||
if (!this.taskInfoFormEnabled || this.taskDriverLoading) return Promise.resolve([]);
|
if (!this.taskInfoFormEnabled || this.taskDriverLoading) return Promise.resolve([]);
|
||||||
const carrier = this.getActiveCarrierFilter();
|
|
||||||
if (!carrier.carrierId && !carrier.carrierName) {
|
|
||||||
this.taskDriverOptions = [];
|
|
||||||
return Promise.resolve([]);
|
|
||||||
}
|
|
||||||
this.taskDriverLoading = true;
|
this.taskDriverLoading = true;
|
||||||
return fetchDriversByCarrierOrganizations({ size: 9999, posts: '司机' }, carrier)
|
return getDriverList(1, 9999, {})
|
||||||
.then(records => {
|
.then(res => {
|
||||||
this.taskDriverOptions = records;
|
this.taskDriverOptions = extractRecords(res);
|
||||||
return this.taskDriverOptions;
|
return this.taskDriverOptions;
|
||||||
})
|
})
|
||||||
.finally(() => {
|
.finally(() => {
|
||||||
@@ -9104,17 +9108,10 @@ export default {
|
|||||||
},
|
},
|
||||||
fetchTaskDriverSuggestions(queryString, callback) {
|
fetchTaskDriverSuggestions(queryString, callback) {
|
||||||
const keyword = String(queryString || '').trim();
|
const keyword = String(queryString || '').trim();
|
||||||
const carrier = this.getActiveCarrierFilter();
|
|
||||||
if (!carrier.carrierId && !carrier.carrierName) {
|
|
||||||
callback([]);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
this.taskDriverLoading = true;
|
this.taskDriverLoading = true;
|
||||||
fetchDriversByCarrierOrganizations(
|
getDriverList(1, 20, keyword ? { driverName: keyword } : {})
|
||||||
{ size: 20, ...(keyword ? { driverName: keyword } : {}), posts: '司机' },
|
.then(res => {
|
||||||
carrier
|
const records = extractRecords(res);
|
||||||
)
|
|
||||||
.then(records => {
|
|
||||||
this.taskDriverOptions = records;
|
this.taskDriverOptions = records;
|
||||||
callback(
|
callback(
|
||||||
records.map(item => ({
|
records.map(item => ({
|
||||||
@@ -10287,7 +10284,12 @@ export default {
|
|||||||
this.ensureTransportAmapGeocoder()
|
this.ensureTransportAmapGeocoder()
|
||||||
.then(() => this.runAmapGeocode('location', keyword))
|
.then(() => this.runAmapGeocode('location', keyword))
|
||||||
.then(result => {
|
.then(result => {
|
||||||
this.transportMapSearchResults = normalizeMapSearchResults(result, keyword);
|
this.transportMapSearchResults = (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);
|
const point = this.resolveMapPoint(result);
|
||||||
if (!point) {
|
if (!point) {
|
||||||
this.transportMapStatus = '未找到匹配地址';
|
this.transportMapStatus = '未找到匹配地址';
|
||||||
@@ -12803,9 +12805,6 @@ export default {
|
|||||||
...baseRow,
|
...baseRow,
|
||||||
...(index >= 0 ? row : {}),
|
...(index >= 0 ? row : {}),
|
||||||
};
|
};
|
||||||
this.dispatchItemForm.otherFeeTotal = this.normalizeDispatchFeeValue(
|
|
||||||
this.dispatchItemForm.otherFeeTotal
|
|
||||||
);
|
|
||||||
this.dispatchItemForm.carrierType = this.dispatchItemForm.carrierType || '承运商';
|
this.dispatchItemForm.carrierType = this.dispatchItemForm.carrierType || '承运商';
|
||||||
const goodsRows = this.parseJsonArray(this.dispatchItemForm.goodsJson);
|
const goodsRows = this.parseJsonArray(this.dispatchItemForm.goodsJson);
|
||||||
this.dispatchItemCargoRows = (goodsRows.length ? goodsRows : [this.dispatchItemForm]).map(
|
this.dispatchItemCargoRows = (goodsRows.length ? goodsRows : [this.dispatchItemForm]).map(
|
||||||
@@ -12862,7 +12861,6 @@ export default {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
this.dispatchItemForm.carrierType = nextValue;
|
this.dispatchItemForm.carrierType = nextValue;
|
||||||
this.clearDispatchDriverVehicleFields();
|
|
||||||
this.loadDispatchCarrierOptions(this.dispatchRow);
|
this.loadDispatchCarrierOptions(this.dispatchRow);
|
||||||
},
|
},
|
||||||
isDispatchCarrierRequired(carrierType) {
|
isDispatchCarrierRequired(carrierType) {
|
||||||
@@ -12992,39 +12990,20 @@ export default {
|
|||||||
});
|
});
|
||||||
if (this.dispatchIsCarrierMode) {
|
if (this.dispatchIsCarrierMode) {
|
||||||
this.dispatchItemForm.carrierContractId = carrier?.carrierContractId || '';
|
this.dispatchItemForm.carrierContractId = carrier?.carrierContractId || '';
|
||||||
this.dispatchItemForm.carrierId = carrier?.carrierId || '';
|
this.dispatchItemForm.carrierId = '';
|
||||||
this.dispatchItemForm.carrierName = carrier?.carrierName || '';
|
this.dispatchItemForm.carrierName = carrier?.carrierName || '';
|
||||||
} else {
|
return;
|
||||||
this.dispatchItemForm.carrierContractId = '';
|
|
||||||
this.dispatchItemForm.carrierId = carrier?.id || carrier?.carrierId || '';
|
|
||||||
this.dispatchItemForm.carrierName = carrier?.carrierName || value || '';
|
|
||||||
}
|
}
|
||||||
this.clearDispatchDriverVehicleFields();
|
this.dispatchItemForm.carrierContractId = '';
|
||||||
},
|
this.dispatchItemForm.carrierId = carrier?.id || '';
|
||||||
clearDispatchDriverVehicleFields() {
|
this.dispatchItemForm.carrierName = carrier?.carrierName || value || '';
|
||||||
this.dispatchItemForm.driverId = '';
|
|
||||||
this.dispatchItemForm.driverName = '';
|
|
||||||
this.dispatchItemForm.driverPhone = '';
|
|
||||||
this.dispatchItemForm.vehicleNo = '';
|
|
||||||
this.dispatchItemForm.trailerVehicleNo = '';
|
|
||||||
this.dispatchItemForm.escortName = '';
|
|
||||||
this.dispatchItemForm.escortPhone = '';
|
|
||||||
this.taskDriverOptions = [];
|
|
||||||
},
|
},
|
||||||
loadDispatchDriverOptions() {
|
loadDispatchDriverOptions() {
|
||||||
if (this.taskDriverLoading) return Promise.resolve([]);
|
if (this.taskDriverLoading) return Promise.resolve([]);
|
||||||
const carrier = {
|
|
||||||
carrierId: this.dispatchItemForm.carrierId || '',
|
|
||||||
carrierName: this.dispatchItemForm.carrierName || '',
|
|
||||||
};
|
|
||||||
if (!carrier.carrierId && !carrier.carrierName) {
|
|
||||||
this.taskDriverOptions = [];
|
|
||||||
return Promise.resolve([]);
|
|
||||||
}
|
|
||||||
this.taskDriverLoading = true;
|
this.taskDriverLoading = true;
|
||||||
return fetchDriversByCarrierOrganizations({ size: 9999, posts: '司机' }, carrier)
|
return getDriverList(1, 9999, {})
|
||||||
.then(records => {
|
.then(res => {
|
||||||
this.taskDriverOptions = records;
|
this.taskDriverOptions = extractRecords(res);
|
||||||
return this.taskDriverOptions;
|
return this.taskDriverOptions;
|
||||||
})
|
})
|
||||||
.finally(() => {
|
.finally(() => {
|
||||||
@@ -13101,11 +13080,6 @@ export default {
|
|||||||
this.dispatchItemForm[prop] =
|
this.dispatchItemForm[prop] =
|
||||||
parts.length > 1 ? `${parts[0]}.${parts.slice(1).join('').slice(0, 2)}` : parts[0];
|
parts.length > 1 ? `${parts[0]}.${parts.slice(1).join('').slice(0, 2)}` : parts[0];
|
||||||
},
|
},
|
||||||
normalizeDispatchFeeValue(value) {
|
|
||||||
if (value === undefined || value === null || value === '') return '';
|
|
||||||
if (Number(value) === -1) return '';
|
|
||||||
return value;
|
|
||||||
},
|
|
||||||
addDispatchCargoRow(index = -1) {
|
addDispatchCargoRow(index = -1) {
|
||||||
const nextRow = this.normalizeTransportCargoRow({ priceUnit: '元/吨' });
|
const nextRow = this.normalizeTransportCargoRow({ priceUnit: '元/吨' });
|
||||||
if (index > -1) this.dispatchItemCargoRows.splice(index + 1, 0, nextRow);
|
if (index > -1) this.dispatchItemCargoRows.splice(index + 1, 0, nextRow);
|
||||||
@@ -13189,12 +13163,6 @@ export default {
|
|||||||
}, 0)
|
}, 0)
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
/** 精简录入提示:可调度余量扣减当前本次数量后的剩余 */
|
|
||||||
dispatchItemHintRemainingQuantity(row = {}) {
|
|
||||||
const available = this.dispatchItemRemainingQuantity(row);
|
|
||||||
const current = this.parseDispatchQuantity(row.quantity);
|
|
||||||
return Math.max(available - current, 0);
|
|
||||||
},
|
|
||||||
pruneDispatchPendingRows(rows = []) {
|
pruneDispatchPendingRows(rows = []) {
|
||||||
const planQuantities = new Map();
|
const planQuantities = new Map();
|
||||||
this.dispatchPlanGoodsRows.forEach(goods => {
|
this.dispatchPlanGoodsRows.forEach(goods => {
|
||||||
@@ -13441,29 +13409,14 @@ export default {
|
|||||||
}),
|
}),
|
||||||
];
|
];
|
||||||
const firstGoods = goodsRows[0] || {};
|
const firstGoods = goodsRows[0] || {};
|
||||||
const quantitySum = goodsRows.reduce(
|
|
||||||
(sum, goods) => sum + this.parseDispatchQuantity(goods.quantity),
|
|
||||||
0
|
|
||||||
);
|
|
||||||
const quantityText = quantitySum
|
|
||||||
? this.formatDispatchQuantity(quantitySum)
|
|
||||||
: firstGoods.quantity || this.dispatchItemForm.quantity || '';
|
|
||||||
const otherFeeTotal = this.normalizeDispatchFeeValue(this.dispatchItemForm.otherFeeTotal);
|
|
||||||
const nextRow = {
|
const nextRow = {
|
||||||
...this.dispatchItemForm,
|
...this.dispatchItemForm,
|
||||||
goodsJson: JSON.stringify(goodsRows),
|
goodsJson: JSON.stringify(goodsRows),
|
||||||
cargoType: firstGoods.cargoType || this.dispatchItemForm.cargoType || '',
|
cargoType: firstGoods.cargoType || this.dispatchItemForm.cargoType || '',
|
||||||
cargoTypeCode: firstGoods.cargoTypeCode || this.dispatchItemForm.cargoTypeCode || '',
|
cargoTypeCode: firstGoods.cargoTypeCode || this.dispatchItemForm.cargoTypeCode || '',
|
||||||
cargoTypePath: firstGoods.cargoTypePath || this.dispatchItemForm.cargoTypePath || [],
|
cargoTypePath: firstGoods.cargoTypePath || this.dispatchItemForm.cargoTypePath || [],
|
||||||
cargoName:
|
cargoName: firstGoods.cargoName || this.dispatchItemForm.cargoName || '',
|
||||||
goodsRows
|
quantity: firstGoods.quantity || this.dispatchItemForm.quantity || '',
|
||||||
.map(goods => goods.cargoName || goods.goodsName || '')
|
|
||||||
.filter(Boolean)
|
|
||||||
.join('.') ||
|
|
||||||
firstGoods.cargoName ||
|
|
||||||
this.dispatchItemForm.cargoName ||
|
|
||||||
'',
|
|
||||||
quantity: quantityText,
|
|
||||||
quantityUnit: firstGoods.quantityUnit || this.dispatchItemForm.quantityUnit || '',
|
quantityUnit: firstGoods.quantityUnit || this.dispatchItemForm.quantityUnit || '',
|
||||||
unitPrice: firstGoods.unitPrice || '',
|
unitPrice: firstGoods.unitPrice || '',
|
||||||
priceUnit: firstGoods.priceUnit || '',
|
priceUnit: firstGoods.priceUnit || '',
|
||||||
@@ -13471,7 +13424,7 @@ export default {
|
|||||||
freightJson: JSON.stringify({
|
freightJson: JSON.stringify({
|
||||||
currency: this.dispatchItemForm.freightCurrency || 'CNY',
|
currency: this.dispatchItemForm.freightCurrency || 'CNY',
|
||||||
totalFreightAmount: this.dispatchItemFreightSubtotal,
|
totalFreightAmount: this.dispatchItemFreightSubtotal,
|
||||||
otherFreightAmount: otherFeeTotal,
|
otherFreightAmount: this.dispatchItemForm.otherFeeTotal || '',
|
||||||
freightItems: goodsRows.map((cargo, index) => ({
|
freightItems: goodsRows.map((cargo, index) => ({
|
||||||
cargoIndex: index,
|
cargoIndex: index,
|
||||||
cargoName: cargo.cargoName || '',
|
cargoName: cargo.cargoName || '',
|
||||||
@@ -13487,7 +13440,7 @@ export default {
|
|||||||
};
|
};
|
||||||
nextRow.cargoInfo = this.formatDispatchCargoInfo({}, nextRow);
|
nextRow.cargoInfo = this.formatDispatchCargoInfo({}, nextRow);
|
||||||
nextRow.freight = this.getDispatchFeeFields({}, nextRow).freight;
|
nextRow.freight = this.getDispatchFeeFields({}, nextRow).freight;
|
||||||
nextRow.otherFeeTotal = otherFeeTotal;
|
nextRow.otherFeeTotal = this.dispatchItemForm.otherFeeTotal || '';
|
||||||
const quantityUnit = this.getDispatchQuantityUnit(nextRow);
|
const quantityUnit = this.getDispatchQuantityUnit(nextRow);
|
||||||
const otherDispatchedQuantity = this.dispatchRows.reduce((total, row, index) => {
|
const otherDispatchedQuantity = this.dispatchRows.reduce((total, row, index) => {
|
||||||
if (
|
if (
|
||||||
|
|||||||
@@ -1,102 +1,94 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="contract-attachment-section">
|
<section
|
||||||
<section
|
class="contract-manage-form__section contract-manage-form__section--panel contract-manage-form__section--attachment"
|
||||||
v-if="!dialogOnly"
|
>
|
||||||
class="contract-manage-form__section contract-manage-form__section--panel contract-manage-form__section--attachment"
|
<div class="contract-manage-form__attachment-head">
|
||||||
|
<div class="dialog-section-title">{{ title }}</div>
|
||||||
|
<el-button type="primary" :disabled="!rows.length" @click="batchDownload">批量下载</el-button>
|
||||||
|
</div>
|
||||||
|
<el-table
|
||||||
|
:data="rows"
|
||||||
|
border
|
||||||
|
class="contract-manage-form__attachment-table"
|
||||||
|
@selection-change="selected = $event"
|
||||||
>
|
>
|
||||||
<div class="contract-manage-form__attachment-head">
|
<el-table-column v-if="!readonly" type="selection" width="55" align="center" />
|
||||||
<div class="dialog-section-title">{{ title }}</div>
|
<el-table-column type="index" label="序号" width="70" align="center" />
|
||||||
<el-button type="primary" :disabled="!rows.length" @click="batchDownload"
|
<el-table-column v-if="attachmentType" label="附件类型" min-width="160" align="center">
|
||||||
>批量下载</el-button
|
<template #default="{ row }">
|
||||||
>
|
<span v-if="readonly || isRowLocked(row)">{{ row.fileType || '-' }}</span>
|
||||||
</div>
|
<el-select
|
||||||
<el-table
|
v-else
|
||||||
:data="rows"
|
:model-value="row.fileType"
|
||||||
border
|
placeholder="请选择"
|
||||||
class="contract-manage-form__attachment-table"
|
:teleported="false"
|
||||||
@selection-change="selected = $event"
|
:fit-input-width="true"
|
||||||
>
|
@update:model-value="value => updateRowFileType(row, value)"
|
||||||
<el-table-column v-if="!readonly" type="selection" width="55" align="center" />
|
>
|
||||||
<el-table-column type="index" label="序号" width="70" align="center" />
|
<el-option
|
||||||
<el-table-column v-if="attachmentType" label="附件类型" min-width="160" align="center">
|
v-for="item in contractAttachmentTypeOptions"
|
||||||
<template #default="{ row, $index }">
|
:key="item.value"
|
||||||
<span v-if="readonly || isRowLocked(row)">{{ row.fileType || '-' }}</span>
|
:label="item.label"
|
||||||
<el-select
|
:value="item.value"
|
||||||
v-else
|
|
||||||
:model-value="row.fileType"
|
|
||||||
placeholder="请选择"
|
|
||||||
style="width: 100%"
|
|
||||||
teleported
|
|
||||||
clearable
|
|
||||||
@update:model-value="value => updateRowFileType($index, value)"
|
|
||||||
>
|
|
||||||
<el-option
|
|
||||||
v-for="item in contractAttachmentTypeOptions"
|
|
||||||
:key="`${item.value}-${item.label}`"
|
|
||||||
:label="item.label"
|
|
||||||
:value="item.value"
|
|
||||||
/>
|
|
||||||
</el-select>
|
|
||||||
</template>
|
|
||||||
</el-table-column>
|
|
||||||
<el-table-column
|
|
||||||
label="文件名"
|
|
||||||
min-width="240"
|
|
||||||
align="left"
|
|
||||||
header-align="left"
|
|
||||||
show-overflow-tooltip
|
|
||||||
>
|
|
||||||
<template #default="{ row }">
|
|
||||||
<el-link type="primary" @click="preview?.(row, rows)">{{
|
|
||||||
attachmentName(row)
|
|
||||||
}}</el-link>
|
|
||||||
</template>
|
|
||||||
</el-table-column>
|
|
||||||
<el-table-column v-if="description" label="附件描述" min-width="220">
|
|
||||||
<template #default="{ row, $index }">
|
|
||||||
<span v-if="readonly || isRowLocked(row)">{{ row.description || '-' }}</span>
|
|
||||||
<el-input
|
|
||||||
v-else
|
|
||||||
:model-value="row.description"
|
|
||||||
maxlength="200"
|
|
||||||
@update:model-value="value => updateRowDescription($index, value)"
|
|
||||||
/>
|
/>
|
||||||
</template>
|
</el-select>
|
||||||
</el-table-column>
|
|
||||||
<el-table-column label="文件大小" width="120" align="center">
|
|
||||||
<template #default="{ row }">{{ formatSize(row.size) }}</template>
|
|
||||||
</el-table-column>
|
|
||||||
<el-table-column prop="uploadUserName" label="上传人" width="140" align="center" />
|
|
||||||
<el-table-column prop="uploadTime" label="上传时间" width="180" align="center" sortable />
|
|
||||||
<el-table-column v-if="!readonly" label="操作" width="100" align="center" fixed="right">
|
|
||||||
<template #default="{ row, $index }">
|
|
||||||
<span v-if="isRowLocked(row)">-</span>
|
|
||||||
<el-link v-else type="danger" @click="remove($index)">删除</el-link>
|
|
||||||
</template>
|
|
||||||
</el-table-column>
|
|
||||||
</el-table>
|
|
||||||
|
|
||||||
<div v-if="!readonly" class="contract-manage-form__attachment-upload">
|
|
||||||
<template v-if="useUploadDialog">
|
|
||||||
<el-button type="primary" plain @click="openUploadDialog">上传附件</el-button>
|
|
||||||
<span class="contract-manage-form__attachment-tip">
|
|
||||||
支持pdf、bmp、jpeg、png、jpg、doc、docx、ppt、pptx、xlsx、xls、eml、msg、zip的文件格式,单个文件不超过50M
|
|
||||||
</span>
|
|
||||||
</template>
|
</template>
|
||||||
<vehicle-attachment-upload
|
</el-table-column>
|
||||||
v-else
|
<el-table-column
|
||||||
:model-value="rows"
|
label="文件名"
|
||||||
:file-types="attachmentFileTypes"
|
min-width="240"
|
||||||
:max-size="50"
|
align="left"
|
||||||
show-tip
|
header-align="left"
|
||||||
tip="支持pdf、bmp、jpeg、png、jpg、doc、docx、ppt、pptx、xlsx、xls、eml、msg、zip的文件格式,单个文件不超过50M"
|
show-overflow-tooltip
|
||||||
:show-file-list="false"
|
>
|
||||||
button-text="上传附件"
|
<template #default="{ row }">
|
||||||
@change="handleChange"
|
<el-link type="primary" @click="preview?.(row, rows)">{{ attachmentName(row) }}</el-link>
|
||||||
@success="handleUploadSuccess"
|
</template>
|
||||||
/>
|
</el-table-column>
|
||||||
</div>
|
<el-table-column v-if="description" label="附件描述" min-width="220">
|
||||||
</section>
|
<template #default="{ row }">
|
||||||
|
<span v-if="readonly || isRowLocked(row)">{{ row.description || '-' }}</span>
|
||||||
|
<el-input
|
||||||
|
v-else
|
||||||
|
:model-value="row.description"
|
||||||
|
maxlength="200"
|
||||||
|
@update:model-value="value => updateRowDescription(row, value)"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column label="文件大小" width="120" align="center">
|
||||||
|
<template #default="{ row }">{{ formatSize(row.size) }}</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column prop="uploadUserName" label="上传人" width="140" align="center" />
|
||||||
|
<el-table-column prop="uploadTime" label="上传时间" width="180" align="center" sortable />
|
||||||
|
<el-table-column v-if="!readonly" label="操作" width="100" align="center" fixed="right">
|
||||||
|
<template #default="{ row, $index }">
|
||||||
|
<span v-if="isRowLocked(row)">-</span>
|
||||||
|
<el-link v-else type="danger" @click="remove($index)">删除</el-link>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
</el-table>
|
||||||
|
|
||||||
|
<div v-if="!readonly" class="contract-manage-form__attachment-upload">
|
||||||
|
<template v-if="useUploadDialog">
|
||||||
|
<el-button type="primary" plain @click="openUploadDialog">上传附件</el-button>
|
||||||
|
<span class="contract-manage-form__attachment-tip">
|
||||||
|
支持pdf、bmp、jpeg、png、jpg、doc、docx、ppt、pptx、xlsx、xls、eml、msg、zip的文件格式,单个文件不超过50M
|
||||||
|
</span>
|
||||||
|
</template>
|
||||||
|
<vehicle-attachment-upload
|
||||||
|
v-else
|
||||||
|
:model-value="rows"
|
||||||
|
:file-types="attachmentFileTypes"
|
||||||
|
:max-size="50"
|
||||||
|
show-tip
|
||||||
|
tip="支持pdf、bmp、jpeg、png、jpg、doc、docx、ppt、pptx、xlsx、xls、eml、msg、zip的文件格式,单个文件不超过50M"
|
||||||
|
:show-file-list="false"
|
||||||
|
button-text="上传附件"
|
||||||
|
@update:model-value="handleChange"
|
||||||
|
@change="handleChange"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
<el-dialog
|
<el-dialog
|
||||||
v-model="uploadDialogVisible"
|
v-model="uploadDialogVisible"
|
||||||
@@ -111,18 +103,8 @@
|
|||||||
<div class="contract-attachment-upload-dialog__body">
|
<div class="contract-attachment-upload-dialog__body">
|
||||||
<div class="contract-attachment-upload-dialog__field">
|
<div class="contract-attachment-upload-dialog__field">
|
||||||
<span class="contract-attachment-upload-dialog__label">附件位置</span>
|
<span class="contract-attachment-upload-dialog__label">附件位置</span>
|
||||||
<el-select
|
<el-select :model-value="attachmentLocation" disabled style="width: 100%">
|
||||||
v-model="uploadDialogLocation"
|
<el-option :label="attachmentLocation" :value="attachmentLocation" />
|
||||||
placeholder="请选择附件位置"
|
|
||||||
style="width: 100%"
|
|
||||||
@change="syncUploadDialogTypeByLocation"
|
|
||||||
>
|
|
||||||
<el-option
|
|
||||||
v-for="item in resolvedLocationOptions"
|
|
||||||
:key="item"
|
|
||||||
:label="item"
|
|
||||||
:value="item"
|
|
||||||
/>
|
|
||||||
</el-select>
|
</el-select>
|
||||||
</div>
|
</div>
|
||||||
<div class="contract-attachment-upload-dialog__field">
|
<div class="contract-attachment-upload-dialog__field">
|
||||||
@@ -162,7 +144,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
</el-dialog>
|
</el-dialog>
|
||||||
</div>
|
</section>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
@@ -172,11 +154,11 @@ import { getUploadHeaders } from '@/utils/upload';
|
|||||||
import { downloadFileByUrl } from '@/utils/util';
|
import { downloadFileByUrl } from '@/utils/util';
|
||||||
import { getDictionary as getBizDictionary } from '@/api/system/dictbiz';
|
import { getDictionary as getBizDictionary } from '@/api/system/dictbiz';
|
||||||
|
|
||||||
const CONTRACT_ATTACHMENT_TYPE_OTHER = '其他文件';
|
const CONTRACT_ATTACHMENT_TYPE_OTHER = '其它文件';
|
||||||
export const defaultContractAttachmentTypeOptions = [
|
export const defaultContractAttachmentTypeOptions = [
|
||||||
{ label: '合同文件', value: '合同文件' },
|
{ label: '合同文件', value: '合同文件' },
|
||||||
{ label: '双章归档文件', value: '双章归档文件' },
|
{ label: '双章归档文件', value: '双章归档文件' },
|
||||||
{ label: '其他文件', value: CONTRACT_ATTACHMENT_TYPE_OTHER },
|
{ label: '其它文件', value: CONTRACT_ATTACHMENT_TYPE_OTHER },
|
||||||
];
|
];
|
||||||
/** @deprecated 兼容旧引用,实际选项由业务字典 contract_attachment_type 动态加载 */
|
/** @deprecated 兼容旧引用,实际选项由业务字典 contract_attachment_type 动态加载 */
|
||||||
export const contractAttachmentTypeOptions = defaultContractAttachmentTypeOptions;
|
export const contractAttachmentTypeOptions = defaultContractAttachmentTypeOptions;
|
||||||
@@ -219,32 +201,21 @@ const normalizeAttachmentTypeText = value => {
|
|||||||
.replace(/[\s_\-—–·•()()\[\]【】{}《》<>“”"'、,,。.]/g, '');
|
.replace(/[\s_\-—–·•()()\[\]【】{}《》<>“”"'、,,。.]/g, '');
|
||||||
};
|
};
|
||||||
|
|
||||||
const isOtherAttachmentTypeOption = item => {
|
|
||||||
const text = `${item?.label || ''}${item?.value || ''}`;
|
|
||||||
return (
|
|
||||||
String(item?.value) === CONTRACT_ATTACHMENT_TYPE_OTHER ||
|
|
||||||
String(item?.value) === '其它文件' ||
|
|
||||||
text.includes('其他文件') ||
|
|
||||||
text.includes('其它文件') ||
|
|
||||||
text.includes('其他') ||
|
|
||||||
text.includes('其它')
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
const attachmentTypeKeywords = fileType => {
|
const attachmentTypeKeywords = fileType => {
|
||||||
const text = String(fileType || '');
|
if (fileType === '双章归档文件') return ['双章归档文件', '双章归档', '双章'];
|
||||||
if (text.includes('双章')) return ['双章归档文件', '双章归档', '双章'];
|
if (fileType === '合同文件') return ['合同文件', '合同'];
|
||||||
if (text.includes('合同') && !text.includes('其他') && !text.includes('其它')) {
|
return [fileType];
|
||||||
return ['合同文件', '合同'];
|
|
||||||
}
|
|
||||||
if (isOtherAttachmentTypeOption({ label: text, value: text })) return [];
|
|
||||||
return text ? [text] : [];
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const resolveOtherAttachmentType = (options = []) => {
|
const resolveOtherAttachmentType = (options = []) => {
|
||||||
const list = options.length ? options : defaultContractAttachmentTypeOptions;
|
const list = options.length ? options : defaultContractAttachmentTypeOptions;
|
||||||
const matched = list.find(isOtherAttachmentTypeOption);
|
const matched = list.find(
|
||||||
return matched?.value || CONTRACT_ATTACHMENT_TYPE_OTHER;
|
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 = (
|
export const resolveContractAttachmentType = (
|
||||||
@@ -252,27 +223,22 @@ export const resolveContractAttachmentType = (
|
|||||||
options = defaultContractAttachmentTypeOptions
|
options = defaultContractAttachmentTypeOptions
|
||||||
) => {
|
) => {
|
||||||
const list = options.length ? options : defaultContractAttachmentTypeOptions;
|
const list = options.length ? options : defaultContractAttachmentTypeOptions;
|
||||||
const otherType = resolveOtherAttachmentType(list);
|
|
||||||
const values = list.map(item => item.value);
|
const values = list.map(item => item.value);
|
||||||
if (values.includes(row.fileType)) return row.fileType;
|
if (values.includes(row.fileType)) return row.fileType;
|
||||||
const matchedByLabel = list.find(item => String(item.label) === String(row.fileType));
|
|
||||||
if (matchedByLabel) return matchedByLabel.value;
|
|
||||||
const fileName = normalizeAttachmentTypeText(attachmentName(row));
|
const fileName = normalizeAttachmentTypeText(attachmentName(row));
|
||||||
|
const otherType = resolveOtherAttachmentType(list);
|
||||||
if (!fileName) return otherType;
|
if (!fileName) return otherType;
|
||||||
const matched = list
|
const matched = list
|
||||||
.filter(item => !isOtherAttachmentTypeOption(item))
|
.filter(item => item.value !== otherType)
|
||||||
.flatMap(item =>
|
.flatMap(item =>
|
||||||
attachmentTypeKeywords(item.value)
|
attachmentTypeKeywords(item.value).map(keyword => ({
|
||||||
.concat(attachmentTypeKeywords(item.label))
|
value: item.value,
|
||||||
.map(keyword => ({
|
keyword: normalizeAttachmentTypeText(keyword),
|
||||||
value: item.value,
|
}))
|
||||||
keyword: normalizeAttachmentTypeText(keyword),
|
|
||||||
}))
|
|
||||||
)
|
)
|
||||||
.filter(item => item.keyword)
|
.filter(item => item.keyword)
|
||||||
.sort((a, b) => b.keyword.length - a.keyword.length)
|
.sort((a, b) => b.keyword.length - a.keyword.length)
|
||||||
.find(item => fileName.includes(item.keyword));
|
.find(item => fileName.includes(item.keyword));
|
||||||
// 文件名未命中任何附件类型时,默认「其他文件」
|
|
||||||
return matched?.value || otherType;
|
return matched?.value || otherType;
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -306,19 +272,16 @@ export default {
|
|||||||
lockApproved: Boolean,
|
lockApproved: Boolean,
|
||||||
markApprovedOnUpload: Boolean,
|
markApprovedOnUpload: Boolean,
|
||||||
useUploadDialog: Boolean,
|
useUploadDialog: Boolean,
|
||||||
dialogOnly: Boolean,
|
|
||||||
attachmentLocation: { type: String, default: '合同文件' },
|
attachmentLocation: { type: String, default: '合同文件' },
|
||||||
attachmentLocationOptions: { type: Array, default: null },
|
|
||||||
preview: { type: Function, default: null },
|
preview: { type: Function, default: null },
|
||||||
},
|
},
|
||||||
emits: ['update:rows', 'upload-to-location', 'upload-confirmed'],
|
emits: ['update:rows'],
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
selected: [],
|
selected: [],
|
||||||
attachmentFileTypes,
|
attachmentFileTypes,
|
||||||
contractAttachmentTypeOptions: [...defaultContractAttachmentTypeOptions],
|
contractAttachmentTypeOptions: [...defaultContractAttachmentTypeOptions],
|
||||||
uploadDialogVisible: false,
|
uploadDialogVisible: false,
|
||||||
uploadDialogLocation: '合同文件',
|
|
||||||
uploadDialogType: resolveOtherAttachmentType(defaultContractAttachmentTypeOptions),
|
uploadDialogType: resolveOtherAttachmentType(defaultContractAttachmentTypeOptions),
|
||||||
uploadDialogFiles: [],
|
uploadDialogFiles: [],
|
||||||
uploadDialogFileList: [],
|
uploadDialogFileList: [],
|
||||||
@@ -337,11 +300,6 @@ export default {
|
|||||||
defaultAttachmentType() {
|
defaultAttachmentType() {
|
||||||
return resolveOtherAttachmentType(this.contractAttachmentTypeOptions);
|
return resolveOtherAttachmentType(this.contractAttachmentTypeOptions);
|
||||||
},
|
},
|
||||||
resolvedLocationOptions() {
|
|
||||||
const options = (this.attachmentLocationOptions || []).filter(Boolean);
|
|
||||||
if (options.length) return options;
|
|
||||||
return ['合同文件', '其它附件'];
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
created() {
|
created() {
|
||||||
this.loadAttachmentTypeOptions();
|
this.loadAttachmentTypeOptions();
|
||||||
@@ -353,28 +311,12 @@ export default {
|
|||||||
.then(res => {
|
.then(res => {
|
||||||
const options = mapDictOptions(res);
|
const options = mapDictOptions(res);
|
||||||
if (options.length) {
|
if (options.length) {
|
||||||
// 字典缺少「其他文件」时补上,保证未匹配文件名可默认选中
|
this.contractAttachmentTypeOptions = options;
|
||||||
this.contractAttachmentTypeOptions = options.some(isOtherAttachmentTypeOption)
|
|
||||||
? options
|
|
||||||
: [...options, { label: '其他文件', value: CONTRACT_ATTACHMENT_TYPE_OTHER }];
|
|
||||||
if (
|
if (
|
||||||
!this.contractAttachmentTypeOptions.some(
|
!options.some(item => String(item.value) === String(this.uploadDialogType))
|
||||||
item => String(item.value) === String(this.uploadDialogType)
|
|
||||||
)
|
|
||||||
) {
|
) {
|
||||||
this.uploadDialogType = this.defaultAttachmentType;
|
this.uploadDialogType = this.defaultAttachmentType;
|
||||||
}
|
}
|
||||||
// 字典加载后,把已有行的中文类型名对齐到 dictKey,保证下拉可选中
|
|
||||||
if (this.attachmentType && (this.rows || []).length) {
|
|
||||||
const aligned = (this.rows || []).map(row => ({
|
|
||||||
...row,
|
|
||||||
fileType: resolveContractAttachmentType(row, this.contractAttachmentTypeOptions),
|
|
||||||
}));
|
|
||||||
const changed = aligned.some(
|
|
||||||
(row, index) => row.fileType !== this.rows[index]?.fileType
|
|
||||||
);
|
|
||||||
if (changed) this.update(aligned);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.catch(() => {
|
.catch(() => {
|
||||||
@@ -385,76 +327,20 @@ export default {
|
|||||||
return this.lockApproved && isAttachmentApproved(row);
|
return this.lockApproved && isAttachmentApproved(row);
|
||||||
},
|
},
|
||||||
update(rows) {
|
update(rows) {
|
||||||
this.$emit('update:rows', Array.isArray(rows) ? rows.map(item => ({ ...item })) : []);
|
this.$emit('update:rows', rows);
|
||||||
},
|
},
|
||||||
enrichUploadedRow(row, existing) {
|
updateRowFileType(row, value) {
|
||||||
const uploadUserName = this.$store.getters.userInfo?.realName || '';
|
if (this.isRowLocked(row)) return;
|
||||||
const uploadTime = this.$dayjs().format('YYYY-MM-DD HH:mm:ss');
|
row.fileType = value;
|
||||||
const next = {
|
this.update([...this.rows]);
|
||||||
...existing,
|
|
||||||
...row,
|
|
||||||
description: row.description || existing?.description || '',
|
|
||||||
uploadUserName: existing?.uploadUserName || row.uploadUserName || uploadUserName,
|
|
||||||
uploadTime: existing?.uploadTime || row.uploadTime || uploadTime,
|
|
||||||
approved: existing?.approved || row.approved || false,
|
|
||||||
};
|
|
||||||
if (this.attachmentType) {
|
|
||||||
next.fileType = existing?.fileType
|
|
||||||
? existing.fileType
|
|
||||||
: resolveContractAttachmentType(
|
|
||||||
{ ...next, fileType: '' },
|
|
||||||
this.contractAttachmentTypeOptions
|
|
||||||
);
|
|
||||||
}
|
|
||||||
if (this.markApprovedOnUpload && !existing) next.approved = true;
|
|
||||||
return next;
|
|
||||||
},
|
},
|
||||||
findExistingRow(row, rows = this.rows) {
|
updateRowDescription(row, value) {
|
||||||
const fileUrl = attachmentUrl(row);
|
if (this.isRowLocked(row)) return;
|
||||||
return (rows || []).find(item => {
|
row.description = value;
|
||||||
const sameUid = row.uid && item.uid && String(row.uid) === String(item.uid);
|
this.update([...this.rows]);
|
||||||
const sameUrl = fileUrl && attachmentUrl(item) === fileUrl;
|
|
||||||
return sameUid || sameUrl;
|
|
||||||
});
|
|
||||||
},
|
|
||||||
updateRowFileType(index, value) {
|
|
||||||
const rows = [...(this.rows || [])];
|
|
||||||
const row = rows[index];
|
|
||||||
if (!row || this.isRowLocked(row)) return;
|
|
||||||
rows[index] = { ...row, fileType: value };
|
|
||||||
this.update(rows);
|
|
||||||
},
|
|
||||||
updateRowDescription(index, value) {
|
|
||||||
const rows = [...(this.rows || [])];
|
|
||||||
const row = rows[index];
|
|
||||||
if (!row || this.isRowLocked(row)) return;
|
|
||||||
rows[index] = { ...row, description: value };
|
|
||||||
this.update(rows);
|
|
||||||
},
|
|
||||||
resolveTypeByLocation(location) {
|
|
||||||
const options = this.contractAttachmentTypeOptions || [];
|
|
||||||
const locationText = String(location || '');
|
|
||||||
if (locationText.includes('其它') || locationText.includes('其他')) {
|
|
||||||
return this.defaultAttachmentType;
|
|
||||||
}
|
|
||||||
const matched = options.find(
|
|
||||||
item =>
|
|
||||||
String(item.value) === locationText ||
|
|
||||||
String(item.label) === locationText ||
|
|
||||||
String(item.value).includes('合同') ||
|
|
||||||
String(item.label).includes('合同')
|
|
||||||
);
|
|
||||||
return matched?.value || options[0]?.value || this.defaultAttachmentType;
|
|
||||||
},
|
|
||||||
syncUploadDialogTypeByLocation() {
|
|
||||||
this.uploadDialogType = this.resolveTypeByLocation(this.uploadDialogLocation);
|
|
||||||
},
|
},
|
||||||
openUploadDialog() {
|
openUploadDialog() {
|
||||||
const options = this.resolvedLocationOptions;
|
this.uploadDialogType = this.defaultAttachmentType;
|
||||||
this.uploadDialogLocation = options.includes(this.attachmentLocation)
|
|
||||||
? this.attachmentLocation
|
|
||||||
: options[0] || this.attachmentLocation;
|
|
||||||
this.syncUploadDialogTypeByLocation();
|
|
||||||
this.uploadDialogFiles = [];
|
this.uploadDialogFiles = [];
|
||||||
this.uploadDialogFileList = [];
|
this.uploadDialogFileList = [];
|
||||||
this.uploadDialogVisible = true;
|
this.uploadDialogVisible = true;
|
||||||
@@ -462,8 +348,7 @@ export default {
|
|||||||
resetUploadDialog() {
|
resetUploadDialog() {
|
||||||
this.uploadDialogFiles = [];
|
this.uploadDialogFiles = [];
|
||||||
this.uploadDialogFileList = [];
|
this.uploadDialogFileList = [];
|
||||||
this.uploadDialogLocation = this.attachmentLocation;
|
this.uploadDialogType = this.defaultAttachmentType;
|
||||||
this.syncUploadDialogTypeByLocation();
|
|
||||||
},
|
},
|
||||||
beforeUpload(file) {
|
beforeUpload(file) {
|
||||||
const extension = String(file.name || '')
|
const extension = String(file.name || '')
|
||||||
@@ -506,18 +391,13 @@ export default {
|
|||||||
...this.uploadDialogFiles.filter(item => String(item.uid) !== String(file.uid)),
|
...this.uploadDialogFiles.filter(item => String(item.uid) !== String(file.uid)),
|
||||||
normalized,
|
normalized,
|
||||||
];
|
];
|
||||||
const locationText = String(this.uploadDialogLocation || '');
|
this.uploadDialogType = resolveContractAttachmentType(
|
||||||
if (!(locationText.includes('其它') || locationText.includes('其他'))) {
|
{
|
||||||
this.uploadDialogType = resolveContractAttachmentType(
|
...normalized,
|
||||||
{
|
fileType: '',
|
||||||
...normalized,
|
},
|
||||||
fileType: '',
|
this.contractAttachmentTypeOptions
|
||||||
},
|
);
|
||||||
this.contractAttachmentTypeOptions
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
this.uploadDialogType = this.defaultAttachmentType;
|
|
||||||
}
|
|
||||||
this.$message.success('上传成功');
|
this.$message.success('上传成功');
|
||||||
},
|
},
|
||||||
handleDialogError() {
|
handleDialogError() {
|
||||||
@@ -536,10 +416,6 @@ export default {
|
|||||||
this.$message.warning('请先上传附件');
|
this.$message.warning('请先上传附件');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (!this.uploadDialogLocation) {
|
|
||||||
this.$message.warning('请选择附件位置');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (!this.uploadDialogType) {
|
if (!this.uploadDialogType) {
|
||||||
this.$message.warning('请选择附件类型');
|
this.$message.warning('请选择附件类型');
|
||||||
return;
|
return;
|
||||||
@@ -554,39 +430,43 @@ export default {
|
|||||||
uploadTime: row.uploadTime || uploadTime,
|
uploadTime: row.uploadTime || uploadTime,
|
||||||
...(this.markApprovedOnUpload ? { approved: true } : {}),
|
...(this.markApprovedOnUpload ? { approved: true } : {}),
|
||||||
}));
|
}));
|
||||||
// 统一交给父组件按附件位置写入对应列表,避免同位置双写或跨位置不显示
|
this.update([...(this.rows || []), ...appended]);
|
||||||
this.$emit('upload-to-location', {
|
|
||||||
location: this.uploadDialogLocation,
|
|
||||||
rows: appended,
|
|
||||||
});
|
|
||||||
this.uploadDialogVisible = false;
|
this.uploadDialogVisible = false;
|
||||||
this.$emit('upload-confirmed', {
|
|
||||||
location: this.uploadDialogLocation,
|
|
||||||
rows: appended,
|
|
||||||
});
|
|
||||||
},
|
},
|
||||||
handleChange(list) {
|
handleChange(rows) {
|
||||||
if (!Array.isArray(list)) return;
|
const uploadUserName = this.$store.getters.userInfo?.realName || '';
|
||||||
// 上传组件偶发回传空列表时,禁止清空已有表格数据
|
const uploadTime = this.$dayjs().format('YYYY-MM-DD HH:mm:ss');
|
||||||
if (!list.length) return;
|
|
||||||
const existingRows = this.rows || [];
|
const existingRows = this.rows || [];
|
||||||
const nextRows = list.map(row => {
|
this.update(
|
||||||
const existing = this.findExistingRow(row, existingRows);
|
(rows || []).map(row => {
|
||||||
if (existing && this.isRowLocked(existing)) return { ...existing };
|
const fileUrl = attachmentUrl(row);
|
||||||
return this.enrichUploadedRow(row, existing);
|
const existing = existingRows.find(item => {
|
||||||
});
|
const sameUid = row.uid && item.uid && String(row.uid) === String(item.uid);
|
||||||
existingRows.forEach(item => {
|
const sameUrl = fileUrl && attachmentUrl(item) === fileUrl;
|
||||||
if (!this.isRowLocked(item)) return;
|
return sameUid || sameUrl;
|
||||||
if (this.findExistingRow(item, nextRows)) return;
|
});
|
||||||
nextRows.unshift({ ...item });
|
if (existing && this.isRowLocked(existing)) return { ...existing };
|
||||||
});
|
const next = {
|
||||||
this.update(nextRows);
|
...existing,
|
||||||
},
|
...row,
|
||||||
handleUploadSuccess(file) {
|
description: row.description || existing?.description || '',
|
||||||
const url = attachmentUrl(file);
|
uploadUserName: existing?.uploadUserName || row.uploadUserName || uploadUserName,
|
||||||
if (!file || !url) return;
|
uploadTime: existing?.uploadTime || row.uploadTime || uploadTime,
|
||||||
if (this.findExistingRow(file)) return;
|
approved: existing?.approved || row.approved || false,
|
||||||
this.update([...(this.rows || []), this.enrichUploadedRow(file)]);
|
};
|
||||||
|
if (this.attachmentType) {
|
||||||
|
next.fileType = resolveContractAttachmentType(
|
||||||
|
{
|
||||||
|
...next,
|
||||||
|
fileType: existing?.fileType || row.fileType,
|
||||||
|
},
|
||||||
|
this.contractAttachmentTypeOptions
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if (this.markApprovedOnUpload && !existing) next.approved = true;
|
||||||
|
return next;
|
||||||
|
})
|
||||||
|
);
|
||||||
},
|
},
|
||||||
remove(index) {
|
remove(index) {
|
||||||
const row = this.rows[index];
|
const row = this.rows[index];
|
||||||
@@ -594,7 +474,7 @@ export default {
|
|||||||
this.$message.warning('已审核通过的附件不允许删除');
|
this.$message.warning('已审核通过的附件不允许删除');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const rows = [...(this.rows || [])];
|
const rows = [...this.rows];
|
||||||
rows.splice(index, 1);
|
rows.splice(index, 1);
|
||||||
this.update(rows);
|
this.update(rows);
|
||||||
},
|
},
|
||||||
@@ -607,7 +487,7 @@ export default {
|
|||||||
downloadFileByUrl(url, attachmentName(row));
|
downloadFileByUrl(url, attachmentName(row));
|
||||||
},
|
},
|
||||||
batchDownload() {
|
batchDownload() {
|
||||||
(this.selected.length ? this.selected : this.rows || []).forEach(this.download);
|
(this.selected.length ? this.selected : this.rows).forEach(this.download);
|
||||||
},
|
},
|
||||||
formatSize(size) {
|
formatSize(size) {
|
||||||
const value = Number(size);
|
const value = Number(size);
|
||||||
@@ -624,7 +504,7 @@ export default {
|
|||||||
.contract-manage-form__section {
|
.contract-manage-form__section {
|
||||||
margin: 12px 0 0;
|
margin: 12px 0 0;
|
||||||
padding: 14px 16px 16px;
|
padding: 14px 16px 16px;
|
||||||
overflow: visible;
|
overflow: hidden;
|
||||||
background: #fff;
|
background: #fff;
|
||||||
border-radius: 6px;
|
border-radius: 6px;
|
||||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.04);
|
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.04);
|
||||||
@@ -647,15 +527,6 @@ export default {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.contract-manage-form__attachment-table {
|
|
||||||
:deep(.el-table__body-wrapper),
|
|
||||||
:deep(.el-table__header-wrapper),
|
|
||||||
:deep(.el-table__cell),
|
|
||||||
:deep(.cell) {
|
|
||||||
overflow: visible;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.contract-manage-form__attachment-head {
|
.contract-manage-form__attachment-head {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
|||||||
@@ -1,17 +1,7 @@
|
|||||||
<template>
|
<template>
|
||||||
<div v-loading="loading" class="master-detail">
|
<div v-loading="loading" class="master-detail">
|
||||||
<section v-if="master" class="detail-overview">
|
<section v-if="master" class="detail-overview">
|
||||||
<div class="detail-heading">
|
<div class="detail-heading"><div><h2>多联总单详情 <span>|</span> {{ master.masterNo }}</h2><el-tag :type="statusType(master.businessStatus)" class="status-text">{{ statusName(master.businessStatus) }}</el-tag><el-tag v-if="transportFlowLabel" type="info" class="transport-flow-tag">{{ transportFlowLabel }}</el-tag></div></div>
|
||||||
<div>
|
|
||||||
<h2>多联总单详情 <span>|</span> {{ master.masterNo }}</h2>
|
|
||||||
<el-tag :type="statusType(master.businessStatus)">{{
|
|
||||||
statusName(master.businessStatus)
|
|
||||||
}}</el-tag>
|
|
||||||
<el-tag v-if="transportFlowLabel" type="primary" class="transport-flow-tag">{{
|
|
||||||
transportFlowLabel
|
|
||||||
}}</el-tag>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<dl class="detail-meta">
|
<dl class="detail-meta">
|
||||||
<div>
|
<div>
|
||||||
<dt>客户</dt>
|
<dt>客户</dt>
|
||||||
@@ -142,7 +132,6 @@ export default {
|
|||||||
dateRange() { return this.master?.planStartTime && this.master?.planEndTime ? `${String(this.master.planStartTime).slice(0, 10)} ~ ${String(this.master.planEndTime).slice(0, 10)}` : '-'; },
|
dateRange() { return this.master?.planStartTime && this.master?.planEndTime ? `${String(this.master.planStartTime).slice(0, 10)} ~ ${String(this.master.planEndTime).slice(0, 10)}` : '-'; },
|
||||||
},
|
},
|
||||||
async mounted() {
|
async mounted() {
|
||||||
if (!this.id) return;
|
|
||||||
this.loading = true;
|
this.loading = true;
|
||||||
try {
|
try {
|
||||||
const res = await api.getDetail(this.id);
|
const res = await api.getDetail(this.id);
|
||||||
@@ -293,36 +282,8 @@ export default {
|
|||||||
<style scoped lang="scss">
|
<style scoped lang="scss">
|
||||||
.master-detail { padding-bottom: 20px; color: #303133; }
|
.master-detail { padding-bottom: 20px; color: #303133; }
|
||||||
.detail-overview, .execution-detail-card { margin-bottom: 8px; border: 1px solid #eff1f7; background: #fff; }
|
.detail-overview, .execution-detail-card { margin-bottom: 8px; border: 1px solid #eff1f7; background: #fff; }
|
||||||
.detail-heading {
|
.detail-heading { display: flex; align-items: center; justify-content: space-between; padding: 18px 24px; h2 { display: inline-block; margin: 0 16px 0 0; font-size: 20px; } h2 span { margin: 0 8px; color: #909399; font-weight: 400; } }
|
||||||
display: flex;
|
.transport-flow-tag { margin-left: 8px; }
|
||||||
align-items: center;
|
|
||||||
justify-content: space-between;
|
|
||||||
padding: 18px 24px;
|
|
||||||
|
|
||||||
h2 {
|
|
||||||
display: inline-block;
|
|
||||||
margin: 0 16px 0 0;
|
|
||||||
font-size: 20px;
|
|
||||||
}
|
|
||||||
|
|
||||||
h2 span {
|
|
||||||
margin: 0 8px;
|
|
||||||
color: #909399;
|
|
||||||
font-weight: 400;
|
|
||||||
}
|
|
||||||
|
|
||||||
:deep(.el-tag) {
|
|
||||||
height: 28px;
|
|
||||||
padding: 0 12px;
|
|
||||||
border: 0;
|
|
||||||
border-radius: 8px;
|
|
||||||
font-size: 13px;
|
|
||||||
line-height: 28px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.transport-flow-tag {
|
|
||||||
margin-left: 8px;
|
|
||||||
}
|
|
||||||
.detail-meta { display: grid; grid-template-columns: repeat(4, minmax(0, 1fr)); gap: 18px 24px; margin: 0; padding: 0 24px 20px; dt { margin-bottom: 6px; color: #909399; font-size: 13px; } dd { display: flex; flex-wrap: wrap; gap: 8px 16px; margin: 0; color: #303133; font-size: 14px; word-break: break-all; } :deep(.el-link) { font-size: 14px; } }
|
.detail-meta { display: grid; grid-template-columns: repeat(4, minmax(0, 1fr)); gap: 18px 24px; margin: 0; padding: 0 24px 20px; dt { margin-bottom: 6px; color: #909399; font-size: 13px; } dd { display: flex; flex-wrap: wrap; gap: 8px 16px; margin: 0; color: #303133; font-size: 14px; word-break: break-all; } :deep(.el-link) { font-size: 14px; } }
|
||||||
.route-map { display: flex; align-items: flex-start; padding: 16px 24px 20px; border-top: 1px solid #eff1f7; overflow-x: auto; }
|
.route-map { display: flex; align-items: flex-start; padding: 16px 24px 20px; border-top: 1px solid #eff1f7; overflow-x: auto; }
|
||||||
.route-map__node { display: grid; flex: 0 0 150px; justify-items: center; gap: 6px; text-align: center; strong { font-size: 16px; white-space: nowrap; } small { color: #606266; white-space: nowrap; } }
|
.route-map__node { display: grid; flex: 0 0 150px; justify-items: center; gap: 6px; text-align: center; strong { font-size: 16px; white-space: nowrap; } small { color: #606266; white-space: nowrap; } }
|
||||||
|
|||||||
@@ -156,11 +156,11 @@
|
|||||||
<el-row :gutter="16">
|
<el-row :gutter="16">
|
||||||
<template v-if="isRoad(route)">
|
<template v-if="isRoad(route)">
|
||||||
<el-col :span="6"><el-form-item label="承运商" :required="route.carrierType !== '自运'"><el-select :model-value="route.carrierType !== '自运' ? route.carrierContractId : route.carrierName" filterable clearable placeholder="请选择" :loading="carrierLoading" @change="value => handleCarrierContractChange(route, value)"><el-option v-for="item in carrierOptions" :key="carrierOptionValue(item)" :label="carrierOptionLabel(item)" :value="route.carrierType !== '自运' ? carrierOptionValue(item) : item.carrierName" /></el-select></el-form-item></el-col>
|
<el-col :span="6"><el-form-item label="承运商" :required="route.carrierType !== '自运'"><el-select :model-value="route.carrierType !== '自运' ? route.carrierContractId : route.carrierName" filterable clearable placeholder="请选择" :loading="carrierLoading" @change="value => handleCarrierContractChange(route, value)"><el-option v-for="item in carrierOptions" :key="carrierOptionValue(item)" :label="carrierOptionLabel(item)" :value="route.carrierType !== '自运' ? carrierOptionValue(item) : item.carrierName" /></el-select></el-form-item></el-col>
|
||||||
<el-col :span="6"><el-form-item label="司机" :required="route.carrierType !== '承运商'"><el-autocomplete :ref="element => setDriverInput(route.segmentNo, element)" v-model="route.driverName" :debounce="300" :fetch-suggestions="(query, cb) => fetchDriverSuggestions(route, query, cb)" clearable placeholder="请输入司机" :loading="driverLoading" @select="item => handleDriverSuggestionSelect(route, item)" /></el-form-item></el-col>
|
<el-col :span="6"><el-form-item label="司机" :required="route.carrierType !== '承运商'"><el-autocomplete :ref="element => setDriverInput(route.segmentNo, element)" v-model="route.driverName" :debounce="300" :fetch-suggestions="fetchDriverSuggestions" clearable placeholder="请输入司机" :loading="driverLoading" @select="item => handleDriverSuggestionSelect(route, item)" /></el-form-item></el-col>
|
||||||
<el-col :span="6"><el-form-item :label="route.carrierType === '承运商' ? '手机号' : '司机手机号'" :required="route.carrierType !== '承运商'"><el-input v-model="route.driverPhone" placeholder="请输入" /></el-form-item></el-col>
|
<el-col :span="6"><el-form-item :label="route.carrierType === '承运商' ? '手机号' : '司机手机号'" :required="route.carrierType !== '承运商'"><el-input v-model="route.driverPhone" placeholder="请输入" /></el-form-item></el-col>
|
||||||
<el-col :span="6"><el-form-item label="车牌号" required><el-input v-model="route.vehicleNo" placeholder="请输入" /></el-form-item></el-col>
|
<el-col :span="6"><el-form-item label="车牌号" required><el-input v-model="route.vehicleNo" placeholder="请输入" /></el-form-item></el-col>
|
||||||
<el-col v-if="route.carrierType !== '承运商'" :span="6"><el-form-item label="挂车车牌号"><el-input v-model="route.trailerVehicleNo" placeholder="请输入" /></el-form-item></el-col>
|
<el-col v-if="route.carrierType !== '承运商'" :span="6"><el-form-item label="挂车车牌号"><el-input v-model="route.trailerVehicleNo" placeholder="请输入" /></el-form-item></el-col>
|
||||||
<el-col v-if="route.carrierType !== '承运商'" :span="6"><el-form-item label="押运人"><el-autocomplete v-model="route.escortName" :debounce="300" :fetch-suggestions="(query, cb) => fetchEscortSuggestions(route, query, cb)" clearable placeholder="请输入" :loading="escortLoading" @select="item => handleEscortSuggestionSelect(route, item)" /></el-form-item></el-col>
|
<el-col v-if="route.carrierType !== '承运商'" :span="6"><el-form-item label="押运人"><el-autocomplete v-model="route.escortName" :debounce="300" :fetch-suggestions="fetchEscortSuggestions" clearable placeholder="请输入" :loading="escortLoading" @select="item => handleEscortSuggestionSelect(route, item)" /></el-form-item></el-col>
|
||||||
<el-col v-if="route.carrierType !== '承运商'" :span="6"><el-form-item label="押运人手机号"><el-input v-model="route.escortPhone" placeholder="请输入" /></el-form-item></el-col>
|
<el-col v-if="route.carrierType !== '承运商'" :span="6"><el-form-item label="押运人手机号"><el-input v-model="route.escortPhone" placeholder="请输入" /></el-form-item></el-col>
|
||||||
</template>
|
</template>
|
||||||
<template v-else>
|
<template v-else>
|
||||||
@@ -233,7 +233,7 @@
|
|||||||
|
|
||||||
<script>
|
<script>
|
||||||
import * as api from '@/api/business/master-order';
|
import * as api from '@/api/business/master-order';
|
||||||
import { fetchDriversByCarrierOrganizations } from '@/utils/carrier-org-resource';
|
import { getList as getDriverList } from '@/api/transportCapacity/driver';
|
||||||
import { isMobile } from '@/utils/validate';
|
import { isMobile } from '@/utils/validate';
|
||||||
|
|
||||||
const unwrapRecords = res => {
|
const unwrapRecords = res => {
|
||||||
@@ -290,12 +290,11 @@ export default {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
async mounted() {
|
async mounted() {
|
||||||
await Promise.all([this.load(), this.loadCarrierOptions()]);
|
await Promise.all([this.load(), this.loadCarrierOptions(), this.loadDriverOptions()]);
|
||||||
this.applySelfOperatedCarrierDefaults();
|
this.applySelfOperatedCarrierDefaults();
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
async load() {
|
async load() {
|
||||||
if (!this.id) return;
|
|
||||||
this.loading = true;
|
this.loading = true;
|
||||||
try {
|
try {
|
||||||
const res = await api.getDetail(this.id);
|
const res = await api.getDetail(this.id);
|
||||||
@@ -727,7 +726,6 @@ export default {
|
|||||||
route.carrierName = '';
|
route.carrierName = '';
|
||||||
route.carrierId = '';
|
route.carrierId = '';
|
||||||
route.carrierContractId = '';
|
route.carrierContractId = '';
|
||||||
this.clearRouteDriverVehicleFields(route);
|
|
||||||
if (value === '承运商') {
|
if (value === '承运商') {
|
||||||
route.trailerVehicleNo = '';
|
route.trailerVehicleNo = '';
|
||||||
route.escortName = '';
|
route.escortName = '';
|
||||||
@@ -747,24 +745,6 @@ export default {
|
|||||||
route.carrierContractId = route.carrierType !== '自运' ? contract?.contractId || '' : '';
|
route.carrierContractId = route.carrierType !== '自运' ? contract?.contractId || '' : '';
|
||||||
route.carrierId = route.carrierType !== '自运' ? contract?.carrierId || '' : '';
|
route.carrierId = route.carrierType !== '自运' ? contract?.carrierId || '' : '';
|
||||||
route.carrierName = contract?.carrierName || '';
|
route.carrierName = contract?.carrierName || '';
|
||||||
this.clearRouteDriverVehicleFields(route);
|
|
||||||
},
|
|
||||||
clearRouteDriverVehicleFields(route) {
|
|
||||||
if (!route) return;
|
|
||||||
route.driverId = '';
|
|
||||||
route.driverName = '';
|
|
||||||
route.driverPhone = '';
|
|
||||||
route.vehicleNo = '';
|
|
||||||
route.trailerVehicleNo = '';
|
|
||||||
route.escortName = '';
|
|
||||||
route.escortPhone = '';
|
|
||||||
this.driverOptions = [];
|
|
||||||
},
|
|
||||||
getRouteCarrierFilter(route = {}) {
|
|
||||||
return {
|
|
||||||
carrierId: route.carrierId || '',
|
|
||||||
carrierName: route.carrierName || '',
|
|
||||||
};
|
|
||||||
},
|
},
|
||||||
carrierOptionValue(item = {}) {
|
carrierOptionValue(item = {}) {
|
||||||
return item.contractId || item.id || item.value || item.carrierName || '';
|
return item.contractId || item.id || item.value || item.carrierName || '';
|
||||||
@@ -836,30 +816,18 @@ export default {
|
|||||||
route.carrierContractId = '';
|
route.carrierContractId = '';
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
async loadDriverOptions(route = {}, keyword = '') {
|
async loadDriverOptions(keyword = '') {
|
||||||
const carrier = this.getRouteCarrierFilter(route);
|
|
||||||
if (!carrier.carrierId && !carrier.carrierName) {
|
|
||||||
this.driverOptions = [];
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
this.driverLoading = true;
|
this.driverLoading = true;
|
||||||
try {
|
try {
|
||||||
this.driverOptions = await fetchDriversByCarrierOrganizations(
|
this.driverOptions = unwrapRecords(
|
||||||
{ size: 20, ...(keyword ? { driverName: keyword } : {}), posts: '司机' },
|
await getDriverList(1, 20, { driverName: keyword, posts: '司机' })
|
||||||
carrier
|
|
||||||
);
|
);
|
||||||
return this.driverOptions;
|
|
||||||
} finally {
|
} finally {
|
||||||
this.driverLoading = false;
|
this.driverLoading = false;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
fetchDriverSuggestions(route, queryString, callback) {
|
fetchDriverSuggestions(queryString, callback) {
|
||||||
const carrier = this.getRouteCarrierFilter(route);
|
this.loadDriverOptions(String(queryString || '').trim())
|
||||||
if (!carrier.carrierId && !carrier.carrierName) {
|
|
||||||
callback([]);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
this.loadDriverOptions(route, String(queryString || '').trim())
|
|
||||||
.then(() =>
|
.then(() =>
|
||||||
callback(
|
callback(
|
||||||
this.driverOptions.map(item => ({
|
this.driverOptions.map(item => ({
|
||||||
@@ -877,21 +845,13 @@ export default {
|
|||||||
const drivingVehicle = String(item.drivingVehicle || '').trim();
|
const drivingVehicle = String(item.drivingVehicle || '').trim();
|
||||||
if (drivingVehicle) route.vehicleNo = drivingVehicle;
|
if (drivingVehicle) route.vehicleNo = drivingVehicle;
|
||||||
},
|
},
|
||||||
fetchEscortSuggestions(route, queryString, callback) {
|
fetchEscortSuggestions(queryString, callback) {
|
||||||
const keyword = String(queryString || '').trim();
|
const keyword = String(queryString || '').trim();
|
||||||
const carrier = this.getRouteCarrierFilter(route);
|
|
||||||
if (!carrier.carrierId && !carrier.carrierName) {
|
|
||||||
callback([]);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
this.escortLoading = true;
|
this.escortLoading = true;
|
||||||
fetchDriversByCarrierOrganizations(
|
getDriverList(1, 20, { ...(keyword ? { driverName: keyword } : {}), posts: '押运员' })
|
||||||
{ size: 20, ...(keyword ? { driverName: keyword } : {}), posts: '押运员' },
|
.then(res => {
|
||||||
carrier
|
|
||||||
)
|
|
||||||
.then(records => {
|
|
||||||
callback(
|
callback(
|
||||||
records.map(item => ({
|
unwrapRecords(res).map(item => ({
|
||||||
...item,
|
...item,
|
||||||
value: this.driverOptionLabel(item),
|
value: this.driverOptionLabel(item),
|
||||||
}))
|
}))
|
||||||
|
|||||||
@@ -402,8 +402,8 @@
|
|||||||
>
|
>
|
||||||
</div>
|
</div>
|
||||||
<div class="map-picker-content">
|
<div class="map-picker-content">
|
||||||
<div ref="addressMap" class="address-map" />
|
|
||||||
<map-search-results :results="mapSearchResults" @select="selectAddressMapSearchResult" />
|
<map-search-results :results="mapSearchResults" @select="selectAddressMapSearchResult" />
|
||||||
|
<div ref="addressMap" class="address-map" />
|
||||||
</div>
|
</div>
|
||||||
<div class="address-map-status">
|
<div class="address-map-status">
|
||||||
{{ mapStatus
|
{{ mapStatus
|
||||||
@@ -601,7 +601,6 @@ let amapLoader;
|
|||||||
import { getList as getCommonRouteList } from '@/api/business/common-route';
|
import { getList as getCommonRouteList } from '@/api/business/common-route';
|
||||||
import * as api from '@/api/business/master-order';
|
import * as api from '@/api/business/master-order';
|
||||||
import { formatUpdateUserName } from '@/utils/audit';
|
import { formatUpdateUserName } from '@/utils/audit';
|
||||||
import { normalizeMapSearchResults } from '@/utils/map-search';
|
|
||||||
import { isMobile } from '@/utils/validate';
|
import { isMobile } from '@/utils/validate';
|
||||||
export default {
|
export default {
|
||||||
components: {
|
components: {
|
||||||
@@ -1286,8 +1285,14 @@ export default {
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
.then(result => {
|
.then(result => {
|
||||||
this.mapSearchResults = normalizeMapSearchResults(result, this.mapKeyword);
|
const geocodes = result?.geocodes || [];
|
||||||
const point = this.mapSearchResults[0]?.location;
|
this.mapSearchResults = geocodes.map((item, index) => ({
|
||||||
|
id: item.id || index,
|
||||||
|
name: item.formattedAddress || this.mapKeyword,
|
||||||
|
address: item.formattedAddress || this.mapKeyword,
|
||||||
|
location: item.location,
|
||||||
|
}));
|
||||||
|
const point = geocodes[0]?.location;
|
||||||
if (!point) return this.$message.warning('地图搜索无匹配地址');
|
if (!point) return this.$message.warning('地图搜索无匹配地址');
|
||||||
this.pickAddressMap(point, this.mapKeyword);
|
this.pickAddressMap(point, this.mapKeyword);
|
||||||
})
|
})
|
||||||
@@ -2194,15 +2199,8 @@ export default {
|
|||||||
display: flex;
|
display: flex;
|
||||||
gap: 8px;
|
gap: 8px;
|
||||||
margin-bottom: 12px;
|
margin-bottom: 12px;
|
||||||
|
|
||||||
.el-input {
|
|
||||||
flex: 1;
|
|
||||||
min-width: 0;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
.address-map {
|
.address-map {
|
||||||
width: 100%;
|
|
||||||
min-width: 0;
|
|
||||||
height: 440px;
|
height: 440px;
|
||||||
}
|
}
|
||||||
.address-map-status {
|
.address-map-status {
|
||||||
|
|||||||
@@ -931,11 +931,11 @@
|
|||||||
/><el-button type="primary" @click="searchTransportMapKeyword">搜索</el-button>
|
/><el-button type="primary" @click="searchTransportMapKeyword">搜索</el-button>
|
||||||
</div>
|
</div>
|
||||||
<div class="map-picker-content">
|
<div class="map-picker-content">
|
||||||
<div ref="transportMap" class="shipping-template-page__map" />
|
|
||||||
<map-search-results
|
<map-search-results
|
||||||
:results="transportMapSearchResults"
|
:results="transportMapSearchResults"
|
||||||
@select="selectTransportMapSearchResult"
|
@select="selectTransportMapSearchResult"
|
||||||
/>
|
/>
|
||||||
|
<div ref="transportMap" class="shipping-template-page__map" />
|
||||||
</div>
|
</div>
|
||||||
<div class="shipping-template-page__map-info">{{ transportMapStatus }}</div>
|
<div class="shipping-template-page__map-info">{{ transportMapStatus }}</div>
|
||||||
<template #footer
|
<template #footer
|
||||||
@@ -1098,7 +1098,6 @@ import {
|
|||||||
import { getDictionary as getSystemDictionary } from '@/api/system/dict';
|
import { getDictionary as getSystemDictionary } from '@/api/system/dict';
|
||||||
import { getDictionary } from '@/api/system/dictbiz';
|
import { getDictionary } from '@/api/system/dictbiz';
|
||||||
import { packageOptions } from '@/option/business/common';
|
import { packageOptions } from '@/option/business/common';
|
||||||
import { normalizeMapSearchResults } from '@/utils/map-search';
|
|
||||||
import { downloadFileByUrl, downloadXls } from '@/utils/util';
|
import { downloadFileByUrl, downloadXls } from '@/utils/util';
|
||||||
import { isMobile } from '@/utils/validate';
|
import { isMobile } from '@/utils/validate';
|
||||||
import { Location } from '@element-plus/icons-vue';
|
import { Location } from '@element-plus/icons-vue';
|
||||||
@@ -2616,7 +2615,12 @@ export default {
|
|||||||
.then(() => this.ensureTransportMapGeocoder())
|
.then(() => this.ensureTransportMapGeocoder())
|
||||||
.then(() => this.runTransportMapGeocode('location', keyword))
|
.then(() => this.runTransportMapGeocode('location', keyword))
|
||||||
.then(result => {
|
.then(result => {
|
||||||
this.transportMapSearchResults = normalizeMapSearchResults(result, keyword);
|
this.transportMapSearchResults = (result.geocodes || []).map((item, index) => ({
|
||||||
|
id: item.id || index,
|
||||||
|
name: item.formattedAddress || keyword,
|
||||||
|
address: item.formattedAddress || keyword,
|
||||||
|
location: item.location,
|
||||||
|
}));
|
||||||
const point = this.resolveTransportMapPoint(result);
|
const point = this.resolveTransportMapPoint(result);
|
||||||
if (!point) throw new Error('未找到匹配地址');
|
if (!point) throw new Error('未找到匹配地址');
|
||||||
return this.$nextTick().then(() => {
|
return this.$nextTick().then(() => {
|
||||||
@@ -3750,12 +3754,6 @@ export default {
|
|||||||
.shipping-template-page__map-toolbar {
|
.shipping-template-page__map-toolbar {
|
||||||
display: flex;
|
display: flex;
|
||||||
gap: 8px;
|
gap: 8px;
|
||||||
margin-bottom: 12px;
|
|
||||||
|
|
||||||
.el-input {
|
|
||||||
flex: 1;
|
|
||||||
min-width: 0;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
.shipping-template-page__dialog-search {
|
.shipping-template-page__dialog-search {
|
||||||
margin-bottom: 8px;
|
margin-bottom: 8px;
|
||||||
@@ -3768,9 +3766,11 @@ export default {
|
|||||||
justify-content: flex-end;
|
justify-content: flex-end;
|
||||||
}
|
}
|
||||||
.shipping-template-page__map {
|
.shipping-template-page__map {
|
||||||
|
flex: 1 1 auto;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
min-width: 0;
|
min-width: 0;
|
||||||
height: 480px;
|
height: 480px;
|
||||||
|
margin-top: 12px;
|
||||||
}
|
}
|
||||||
:deep(.shipping-template-page__map > .amap-container) {
|
:deep(.shipping-template-page__map > .amap-container) {
|
||||||
width: 100% !important;
|
width: 100% !important;
|
||||||
|
|||||||
@@ -637,14 +637,13 @@
|
|||||||
<span>|</span>
|
<span>|</span>
|
||||||
{{ detailRow.planNo || '-' }}
|
{{ detailRow.planNo || '-' }}
|
||||||
</h2>
|
</h2>
|
||||||
<span class="detail-status-text" :class="detailStatusTextClass">
|
<span class="detail-status-text is-dispatching">
|
||||||
{{ displayStatus(detailRow, 'businessStatus') }}
|
{{ displayStatus(detailRow, 'businessStatus') }}
|
||||||
</span>
|
</span>
|
||||||
<span class="detail-status-text detail-transport-type">
|
<span class="detail-status-text detail-transport-type">
|
||||||
{{
|
{{
|
||||||
detailRow.transportTypeName ||
|
detailRow.transportTypeName ||
|
||||||
formatTransportPlanDetailTransportType(detailRow.transportType) ||
|
formatTransportPlanDetailTransportType(detailRow.transportType)
|
||||||
'-'
|
|
||||||
}}
|
}}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
@@ -1232,7 +1231,7 @@
|
|||||||
{{ dispatchDialogStatusText }}
|
{{ dispatchDialogStatusText }}
|
||||||
</el-tag>
|
</el-tag>
|
||||||
<el-tag type="primary" effect="light">
|
<el-tag type="primary" effect="light">
|
||||||
{{ formatDispatchTransportType(dispatchRow.transportType) || '-' }}
|
{{ formatDispatchTransportType(dispatchRow.transportType) || '公路运输' }}
|
||||||
</el-tag>
|
</el-tag>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -1932,7 +1931,7 @@
|
|||||||
class="transport-plan-page__dispatch-quantity-hint"
|
class="transport-plan-page__dispatch-quantity-hint"
|
||||||
>
|
>
|
||||||
剩余数量:{{
|
剩余数量:{{
|
||||||
formatDispatchQuantity(dispatchItemHintRemainingQuantity(dispatchItemForm))
|
formatDispatchQuantity(dispatchItemRemainingQuantity(dispatchItemForm))
|
||||||
}}
|
}}
|
||||||
{{ dispatchItemForm.quantityUnit || '吨' }}
|
{{ dispatchItemForm.quantityUnit || '吨' }}
|
||||||
</div>
|
</div>
|
||||||
@@ -1998,11 +1997,7 @@
|
|||||||
</el-input>
|
</el-input>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
<el-form-item label="其他费用合计">
|
<el-form-item label="其他费用合计">
|
||||||
<el-input
|
<el-input v-model="dispatchItemForm.otherFeeTotal" placeholder="请输入">
|
||||||
v-model="dispatchItemForm.otherFeeTotal"
|
|
||||||
placeholder="请输入"
|
|
||||||
@input="value => handleDispatchNumberInput('otherFeeTotal', value)"
|
|
||||||
>
|
|
||||||
<template #suffix>{{ dispatchItemFreightCurrencyLabel }}</template>
|
<template #suffix>{{ dispatchItemFreightCurrencyLabel }}</template>
|
||||||
</el-input>
|
</el-input>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
@@ -2552,8 +2547,8 @@
|
|||||||
</el-button>
|
</el-button>
|
||||||
</div>
|
</div>
|
||||||
<div class="map-picker-content">
|
<div class="map-picker-content">
|
||||||
<div ref="transportAmap" class="transport-plan-page__map"></div>
|
|
||||||
<map-search-results :results="transportMapSearchResults" @select="selectTransportMapSearchResult" />
|
<map-search-results :results="transportMapSearchResults" @select="selectTransportMapSearchResult" />
|
||||||
|
<div ref="transportAmap" class="transport-plan-page__map"></div>
|
||||||
</div>
|
</div>
|
||||||
<div class="transport-plan-page__map-info">
|
<div class="transport-plan-page__map-info">
|
||||||
<span>{{ transportMapStatus }}</span>
|
<span>{{ transportMapStatus }}</span>
|
||||||
@@ -2734,14 +2729,13 @@ import {
|
|||||||
getList as getProjectList,
|
getList as getProjectList,
|
||||||
} from '@/api/business/project-apply';
|
} from '@/api/business/project-apply';
|
||||||
import { getList as getCommonRouteList } from '@/api/business/common-route';
|
import { getList as getCommonRouteList } from '@/api/business/common-route';
|
||||||
import { fetchDriversByCarrierOrganizations } from '@/utils/carrier-org-resource';
|
import { getList as getDriverList } from '@/api/transportCapacity/driver';
|
||||||
import { getDictionary } from '@/api/system/dictbiz';
|
import { getDictionary } from '@/api/system/dictbiz';
|
||||||
import { getDictionary as getSystemDictionary } from '@/api/system/dict';
|
import { getDictionary as getSystemDictionary } from '@/api/system/dict';
|
||||||
import { addressTypeOptions } from '@/option/base/common-address';
|
import { addressTypeOptions } from '@/option/base/common-address';
|
||||||
import { packageOptions } from '@/option/business/common';
|
import { packageOptions } from '@/option/business/common';
|
||||||
import { formatUpdateUserName } from '@/utils/audit';
|
import { formatUpdateUserName } from '@/utils/audit';
|
||||||
import { getToken } from '@/utils/auth';
|
import { getToken } from '@/utils/auth';
|
||||||
import { normalizeMapSearchResults } from '@/utils/map-search';
|
|
||||||
import { applyTableMenuWidth } from '@/utils/table-menu';
|
import { applyTableMenuWidth } from '@/utils/table-menu';
|
||||||
import { downloadFileByUrl, downloadXls } from '@/utils/util';
|
import { downloadFileByUrl, downloadXls } from '@/utils/util';
|
||||||
import { isMobile } from '@/utils/validate';
|
import { isMobile } from '@/utils/validate';
|
||||||
@@ -3290,23 +3284,6 @@ export default {
|
|||||||
isTransportPlanDetailLayout() {
|
isTransportPlanDetailLayout() {
|
||||||
return this.detailBox;
|
return this.detailBox;
|
||||||
},
|
},
|
||||||
detailStatusTextClass() {
|
|
||||||
const status = this.detailRow?.businessStatus;
|
|
||||||
if (status === 2 || status === '2' || status === 'completed') {
|
|
||||||
return 'status-text-success';
|
|
||||||
}
|
|
||||||
if (status === 1 || status === '1' || status === 'dispatching') {
|
|
||||||
return 'is-dispatching';
|
|
||||||
}
|
|
||||||
if (
|
|
||||||
status === 3 ||
|
|
||||||
status === '3' ||
|
|
||||||
['rejected', 'change_rejected', 'cancelled', 'withdrawn'].includes(status)
|
|
||||||
) {
|
|
||||||
return 'is-danger';
|
|
||||||
}
|
|
||||||
return 'is-info';
|
|
||||||
},
|
|
||||||
transportPlanGoodsText() {
|
transportPlanGoodsText() {
|
||||||
const text = this.formatTransportPlanGoodsInfo(this.detailRow);
|
const text = this.formatTransportPlanGoodsInfo(this.detailRow);
|
||||||
return text && text !== '-' ? text : '暂无货物信息';
|
return text && text !== '-' ? text : '暂无货物信息';
|
||||||
@@ -3549,19 +3526,17 @@ export default {
|
|||||||
},
|
},
|
||||||
dispatchItemFreightTotal() {
|
dispatchItemFreightTotal() {
|
||||||
if (this.dispatchItemForm.taskEntryMode !== 'full') {
|
if (this.dispatchItemForm.taskEntryMode !== 'full') {
|
||||||
const otherFeeRaw = this.normalizeDispatchFeeValue(this.dispatchItemForm.otherFeeTotal);
|
const otherFeeTotal = Number(this.dispatchItemForm.otherFeeTotal || 0);
|
||||||
const otherFeeTotal = Number(otherFeeRaw || 0);
|
|
||||||
const freight = this.dispatchItemFreightSubtotal;
|
const freight = this.dispatchItemFreightSubtotal;
|
||||||
const hasFreight = freight !== '';
|
const hasFreight = freight !== '';
|
||||||
const hasOtherFee = otherFeeRaw !== '';
|
const hasOtherFee = this.dispatchItemForm.otherFeeTotal !== '';
|
||||||
if (!hasFreight && !hasOtherFee) return '';
|
if (!hasFreight && !hasOtherFee) return '';
|
||||||
const total = (hasFreight ? Number(freight) : 0) + (hasOtherFee ? otherFeeTotal : 0);
|
const total = (hasFreight ? Number(freight) : 0) + (hasOtherFee ? otherFeeTotal : 0);
|
||||||
return Number.isInteger(total) ? String(total) : String(Number(total.toFixed(2)));
|
return Number.isInteger(total) ? String(total) : String(Number(total.toFixed(2)));
|
||||||
}
|
}
|
||||||
const otherFeeRaw = this.normalizeDispatchFeeValue(this.dispatchItemForm.otherFeeTotal);
|
const otherFeeTotal = Number(this.dispatchItemForm.otherFeeTotal || 0);
|
||||||
const otherFeeTotal = Number(otherFeeRaw || 0);
|
|
||||||
const freightSubtotal = this.dispatchItemFreightSubtotal;
|
const freightSubtotal = this.dispatchItemFreightSubtotal;
|
||||||
const hasOtherFee = otherFeeRaw !== '';
|
const hasOtherFee = this.dispatchItemForm.otherFeeTotal !== '';
|
||||||
if (freightSubtotal === '' && !hasOtherFee) return '';
|
if (freightSubtotal === '' && !hasOtherFee) return '';
|
||||||
const total = Number(freightSubtotal || 0) + (hasOtherFee ? otherFeeTotal : 0);
|
const total = Number(freightSubtotal || 0) + (hasOtherFee ? otherFeeTotal : 0);
|
||||||
return Number.isInteger(total) ? String(total) : String(Number(total.toFixed(2)));
|
return Number.isInteger(total) ? String(total) : String(Number(total.toFixed(2)));
|
||||||
@@ -4866,17 +4841,10 @@ export default {
|
|||||||
},
|
},
|
||||||
fetchTaskDriverSuggestions(queryString, callback) {
|
fetchTaskDriverSuggestions(queryString, callback) {
|
||||||
const keyword = String(queryString || '').trim();
|
const keyword = String(queryString || '').trim();
|
||||||
const carrier = this.getDispatchCarrierFilter();
|
|
||||||
if (!carrier.carrierId && !carrier.carrierName) {
|
|
||||||
callback([]);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
this.taskDriverLoading = true;
|
this.taskDriverLoading = true;
|
||||||
fetchDriversByCarrierOrganizations(
|
getDriverList(1, 20, { ...(keyword ? { driverName: keyword } : {}), posts: '司机' })
|
||||||
{ size: 20, ...(keyword ? { driverName: keyword } : {}), posts: '司机' },
|
.then(res => {
|
||||||
carrier
|
const records = extractRecords(res);
|
||||||
)
|
|
||||||
.then(records => {
|
|
||||||
this.taskDriverOptions = records;
|
this.taskDriverOptions = records;
|
||||||
callback(
|
callback(
|
||||||
records.map(item => ({
|
records.map(item => ({
|
||||||
@@ -4895,17 +4863,10 @@ export default {
|
|||||||
},
|
},
|
||||||
fetchTaskEscortSuggestions(queryString, callback) {
|
fetchTaskEscortSuggestions(queryString, callback) {
|
||||||
const keyword = String(queryString || '').trim();
|
const keyword = String(queryString || '').trim();
|
||||||
const carrier = this.getDispatchCarrierFilter();
|
|
||||||
if (!carrier.carrierId && !carrier.carrierName) {
|
|
||||||
callback([]);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
this.taskEscortLoading = true;
|
this.taskEscortLoading = true;
|
||||||
fetchDriversByCarrierOrganizations(
|
getDriverList(1, 20, { ...(keyword ? { driverName: keyword } : {}), posts: '押运员' })
|
||||||
{ size: 20, ...(keyword ? { driverName: keyword } : {}), posts: '押运员' },
|
.then(res => {
|
||||||
carrier
|
const records = extractRecords(res);
|
||||||
)
|
|
||||||
.then(records => {
|
|
||||||
this.taskEscortOptions = records;
|
this.taskEscortOptions = records;
|
||||||
callback(records.map(item => ({ ...item, value: item.driverName || item.name || '' })));
|
callback(records.map(item => ({ ...item, value: item.driverName || item.name || '' })));
|
||||||
})
|
})
|
||||||
@@ -5777,7 +5738,12 @@ export default {
|
|||||||
this.ensureTransportAmapGeocoder()
|
this.ensureTransportAmapGeocoder()
|
||||||
.then(() => this.runAmapGeocode('location', keyword))
|
.then(() => this.runAmapGeocode('location', keyword))
|
||||||
.then(result => {
|
.then(result => {
|
||||||
this.transportMapSearchResults = normalizeMapSearchResults(result, keyword);
|
this.transportMapSearchResults = (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);
|
const point = this.resolveMapPoint(result);
|
||||||
if (!point) {
|
if (!point) {
|
||||||
this.transportMapStatus = '未找到匹配地址';
|
this.transportMapStatus = '未找到匹配地址';
|
||||||
@@ -7275,13 +7241,11 @@ export default {
|
|||||||
freightInfo.freightItems?.[0]?.unitPrice ??
|
freightInfo.freightItems?.[0]?.unitPrice ??
|
||||||
'';
|
'';
|
||||||
const otherFeeTotal =
|
const otherFeeTotal =
|
||||||
this.normalizeDispatchFeeValue(
|
item.otherFeeTotal ??
|
||||||
item.otherFeeTotal ??
|
plan.otherFeeTotal ??
|
||||||
plan.otherFeeTotal ??
|
freightInfo.otherFeeTotal ??
|
||||||
freightInfo.otherFeeTotal ??
|
freightInfo.otherFreightAmount ??
|
||||||
freightInfo.otherFreightAmount ??
|
'';
|
||||||
''
|
|
||||||
);
|
|
||||||
const quantity = Number(
|
const quantity = Number(
|
||||||
item.quantity ||
|
item.quantity ||
|
||||||
item.cargoQuantity ||
|
item.cargoQuantity ||
|
||||||
@@ -7596,9 +7560,6 @@ export default {
|
|||||||
...baseRow,
|
...baseRow,
|
||||||
...(index >= 0 ? row : {}),
|
...(index >= 0 ? row : {}),
|
||||||
};
|
};
|
||||||
this.dispatchItemForm.otherFeeTotal = this.normalizeDispatchFeeValue(
|
|
||||||
this.dispatchItemForm.otherFeeTotal
|
|
||||||
);
|
|
||||||
this.dispatchItemForm.carrierType = this.dispatchItemForm.carrierType || '承运商';
|
this.dispatchItemForm.carrierType = this.dispatchItemForm.carrierType || '承运商';
|
||||||
this.loadDispatchCarrierOptions(this.dispatchRow).then(() => {
|
this.loadDispatchCarrierOptions(this.dispatchRow).then(() => {
|
||||||
this.applyDispatchSelfCarrierDefaults();
|
this.applyDispatchSelfCarrierDefaults();
|
||||||
@@ -7650,7 +7611,6 @@ export default {
|
|||||||
this.dispatchItemForm.carrierName = '';
|
this.dispatchItemForm.carrierName = '';
|
||||||
this.dispatchItemForm.carrierId = '';
|
this.dispatchItemForm.carrierId = '';
|
||||||
this.dispatchItemForm.carrierContractId = '';
|
this.dispatchItemForm.carrierContractId = '';
|
||||||
this.clearDispatchDriverVehicleFields();
|
|
||||||
if (nextValue === '承运商') {
|
if (nextValue === '承运商') {
|
||||||
this.dispatchItemForm.trailerVehicleNo = '';
|
this.dispatchItemForm.trailerVehicleNo = '';
|
||||||
this.dispatchItemForm.escortName = '';
|
this.dispatchItemForm.escortName = '';
|
||||||
@@ -7838,29 +7798,12 @@ export default {
|
|||||||
this.dispatchItemForm.carrierContractId = carrier?.carrierContractId || '';
|
this.dispatchItemForm.carrierContractId = carrier?.carrierContractId || '';
|
||||||
this.dispatchItemForm.carrierId = carrier?.carrierId || '';
|
this.dispatchItemForm.carrierId = carrier?.carrierId || '';
|
||||||
this.dispatchItemForm.carrierName = carrier?.carrierName || '';
|
this.dispatchItemForm.carrierName = carrier?.carrierName || '';
|
||||||
} else {
|
return;
|
||||||
this.dispatchItemForm.carrierContractId = '';
|
|
||||||
this.dispatchItemForm.carrierId = '';
|
|
||||||
this.dispatchItemForm.carrierName = carrier?.carrierName || value || '';
|
|
||||||
}
|
}
|
||||||
this.clearDispatchDriverVehicleFields();
|
this.dispatchItemForm.carrierContractId = '';
|
||||||
},
|
this.dispatchItemForm.carrierId =
|
||||||
clearDispatchDriverVehicleFields() {
|
this.dispatchItemForm.carrierType === '自运' ? '' : carrier?.id || '';
|
||||||
this.dispatchItemForm.driverId = '';
|
this.dispatchItemForm.carrierName = carrier?.carrierName || value || '';
|
||||||
this.dispatchItemForm.driverName = '';
|
|
||||||
this.dispatchItemForm.driverPhone = '';
|
|
||||||
this.dispatchItemForm.vehicleNo = '';
|
|
||||||
this.dispatchItemForm.trailerVehicleNo = '';
|
|
||||||
this.dispatchItemForm.escortName = '';
|
|
||||||
this.dispatchItemForm.escortPhone = '';
|
|
||||||
this.taskDriverOptions = [];
|
|
||||||
this.taskEscortOptions = [];
|
|
||||||
},
|
|
||||||
getDispatchCarrierFilter() {
|
|
||||||
return {
|
|
||||||
carrierId: this.dispatchItemForm.carrierId || '',
|
|
||||||
carrierName: this.dispatchItemForm.carrierName || '',
|
|
||||||
};
|
|
||||||
},
|
},
|
||||||
handleDispatchCargoQuantityInput(row, value) {
|
handleDispatchCargoQuantityInput(row, value) {
|
||||||
const text = String(value || '').replace(/[^\d.]/g, '');
|
const text = String(value || '').replace(/[^\d.]/g, '');
|
||||||
@@ -7877,11 +7820,6 @@ export default {
|
|||||||
this.dispatchItemForm[prop] =
|
this.dispatchItemForm[prop] =
|
||||||
parts.length > 1 ? `${parts[0]}.${parts.slice(1).join('').slice(0, 2)}` : parts[0];
|
parts.length > 1 ? `${parts[0]}.${parts.slice(1).join('').slice(0, 2)}` : parts[0];
|
||||||
},
|
},
|
||||||
normalizeDispatchFeeValue(value) {
|
|
||||||
if (value === undefined || value === null || value === '') return '';
|
|
||||||
if (Number(value) === -1) return '';
|
|
||||||
return value;
|
|
||||||
},
|
|
||||||
addDispatchCargoRow(index = -1) {
|
addDispatchCargoRow(index = -1) {
|
||||||
const nextRow = this.normalizeTransportCargoRow({ priceUnit: '元/吨' });
|
const nextRow = this.normalizeTransportCargoRow({ priceUnit: '元/吨' });
|
||||||
if (index > -1) this.dispatchItemCargoRows.splice(index + 1, 0, nextRow);
|
if (index > -1) this.dispatchItemCargoRows.splice(index + 1, 0, nextRow);
|
||||||
@@ -8034,12 +7972,6 @@ export default {
|
|||||||
}, 0)
|
}, 0)
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
/** 精简录入提示:可调度余量扣减当前本次数量后的剩余 */
|
|
||||||
dispatchItemHintRemainingQuantity(row = {}) {
|
|
||||||
const available = this.dispatchItemRemainingQuantity(row);
|
|
||||||
const current = this.parseDispatchQuantity(row.quantity);
|
|
||||||
return Math.max(available - current, 0);
|
|
||||||
},
|
|
||||||
pruneDispatchPendingRows(rows = []) {
|
pruneDispatchPendingRows(rows = []) {
|
||||||
const planQuantities = new Map();
|
const planQuantities = new Map();
|
||||||
this.dispatchPlanGoodsRows.forEach(goods => {
|
this.dispatchPlanGoodsRows.forEach(goods => {
|
||||||
@@ -8288,29 +8220,14 @@ export default {
|
|||||||
}),
|
}),
|
||||||
];
|
];
|
||||||
const firstGoods = goodsRows[0] || {};
|
const firstGoods = goodsRows[0] || {};
|
||||||
const quantitySum = goodsRows.reduce(
|
|
||||||
(sum, goods) => sum + this.parseDispatchQuantity(goods.quantity),
|
|
||||||
0
|
|
||||||
);
|
|
||||||
const quantityText = quantitySum
|
|
||||||
? this.formatDispatchQuantity(quantitySum)
|
|
||||||
: firstGoods.quantity || this.dispatchItemForm.quantity || '';
|
|
||||||
const otherFeeTotal = this.normalizeDispatchFeeValue(this.dispatchItemForm.otherFeeTotal);
|
|
||||||
const nextRow = {
|
const nextRow = {
|
||||||
...this.dispatchItemForm,
|
...this.dispatchItemForm,
|
||||||
goodsJson: JSON.stringify(goodsRows),
|
goodsJson: JSON.stringify(goodsRows),
|
||||||
cargoType: firstGoods.cargoType || this.dispatchItemForm.cargoType || '',
|
cargoType: firstGoods.cargoType || this.dispatchItemForm.cargoType || '',
|
||||||
cargoTypeCode: firstGoods.cargoTypeCode || this.dispatchItemForm.cargoTypeCode || '',
|
cargoTypeCode: firstGoods.cargoTypeCode || this.dispatchItemForm.cargoTypeCode || '',
|
||||||
cargoTypePath: firstGoods.cargoTypePath || this.dispatchItemForm.cargoTypePath || [],
|
cargoTypePath: firstGoods.cargoTypePath || this.dispatchItemForm.cargoTypePath || [],
|
||||||
cargoName:
|
cargoName: firstGoods.cargoName || this.dispatchItemForm.cargoName || '',
|
||||||
goodsRows
|
quantity: firstGoods.quantity || this.dispatchItemForm.quantity || '',
|
||||||
.map(goods => goods.cargoName || goods.goodsName || '')
|
|
||||||
.filter(Boolean)
|
|
||||||
.join('.') ||
|
|
||||||
firstGoods.cargoName ||
|
|
||||||
this.dispatchItemForm.cargoName ||
|
|
||||||
'',
|
|
||||||
quantity: quantityText,
|
|
||||||
quantityUnit: firstGoods.quantityUnit || this.dispatchItemForm.quantityUnit || '',
|
quantityUnit: firstGoods.quantityUnit || this.dispatchItemForm.quantityUnit || '',
|
||||||
unitPrice: firstGoods.unitPrice || '',
|
unitPrice: firstGoods.unitPrice || '',
|
||||||
priceUnit: firstGoods.priceUnit || '',
|
priceUnit: firstGoods.priceUnit || '',
|
||||||
@@ -8318,7 +8235,7 @@ export default {
|
|||||||
freightJson: JSON.stringify({
|
freightJson: JSON.stringify({
|
||||||
currency: this.dispatchContractCurrency || this.dispatchItemForm.freightCurrency || 'RMB',
|
currency: this.dispatchContractCurrency || this.dispatchItemForm.freightCurrency || 'RMB',
|
||||||
totalFreightAmount: this.dispatchItemFreightSubtotal,
|
totalFreightAmount: this.dispatchItemFreightSubtotal,
|
||||||
otherFreightAmount: otherFeeTotal,
|
otherFreightAmount: this.dispatchItemForm.otherFeeTotal || '',
|
||||||
freightItems: goodsRows.map((cargo, index) => ({
|
freightItems: goodsRows.map((cargo, index) => ({
|
||||||
cargoIndex: index,
|
cargoIndex: index,
|
||||||
cargoName: cargo.cargoName || '',
|
cargoName: cargo.cargoName || '',
|
||||||
@@ -8334,7 +8251,7 @@ export default {
|
|||||||
};
|
};
|
||||||
nextRow.cargoInfo = this.formatDispatchCargoInfo({}, nextRow);
|
nextRow.cargoInfo = this.formatDispatchCargoInfo({}, nextRow);
|
||||||
nextRow.freight = this.dispatchItemFreightSubtotal;
|
nextRow.freight = this.dispatchItemFreightSubtotal;
|
||||||
nextRow.otherFeeTotal = otherFeeTotal;
|
nextRow.otherFeeTotal = this.dispatchItemForm.otherFeeTotal || '';
|
||||||
const quantityUnit = this.getDispatchQuantityUnit(nextRow);
|
const quantityUnit = this.getDispatchQuantityUnit(nextRow);
|
||||||
const otherDispatchedQuantity = this.dispatchRows.reduce((total, row, index) => {
|
const otherDispatchedQuantity = this.dispatchRows.reduce((total, row, index) => {
|
||||||
if (
|
if (
|
||||||
@@ -9431,36 +9348,19 @@ export default {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.detail-status-text {
|
:deep(.el-tag) {
|
||||||
display: inline-flex;
|
height: 36px;
|
||||||
align-items: center;
|
padding: 0 14px;
|
||||||
height: 28px;
|
border: 0;
|
||||||
padding: 0 12px;
|
|
||||||
border-radius: 8px;
|
border-radius: 8px;
|
||||||
font-size: 13px;
|
font-size: 14px;
|
||||||
line-height: 28px;
|
line-height: 36px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.status-text-success,
|
:deep(.is-dispatching) {
|
||||||
.is-dispatching {
|
|
||||||
color: #67c23a;
|
color: #67c23a;
|
||||||
background: #e1f3d8;
|
background: #e1f3d8;
|
||||||
}
|
}
|
||||||
|
|
||||||
.detail-transport-type {
|
|
||||||
color: #409eff;
|
|
||||||
background: #ecf5ff;
|
|
||||||
}
|
|
||||||
|
|
||||||
.is-danger {
|
|
||||||
color: #f56c6c;
|
|
||||||
background: #fef0f0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.is-info {
|
|
||||||
color: #909399;
|
|
||||||
background: #f4f4f5;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
&__transport-plan-detail-overview {
|
&__transport-plan-detail-overview {
|
||||||
|
|||||||
@@ -113,12 +113,6 @@
|
|||||||
label="导入方式"
|
label="导入方式"
|
||||||
width="100"
|
width="100"
|
||||||
/>
|
/>
|
||||||
<el-table-column
|
|
||||||
v-if="columnVisible.statusName"
|
|
||||||
prop="statusName"
|
|
||||||
label="状态"
|
|
||||||
width="100"
|
|
||||||
/>
|
|
||||||
<el-table-column
|
<el-table-column
|
||||||
v-if="columnVisible.createUserName"
|
v-if="columnVisible.createUserName"
|
||||||
prop="createUserName"
|
prop="createUserName"
|
||||||
@@ -127,6 +121,12 @@
|
|||||||
>
|
>
|
||||||
<template #default="{ row }">{{ row.createUserName || '-' }}</template>
|
<template #default="{ row }">{{ row.createUserName || '-' }}</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
|
<el-table-column
|
||||||
|
v-if="columnVisible.statusName"
|
||||||
|
prop="statusName"
|
||||||
|
label="状态"
|
||||||
|
width="100"
|
||||||
|
/>
|
||||||
<el-table-column
|
<el-table-column
|
||||||
v-if="columnVisible.createTime"
|
v-if="columnVisible.createTime"
|
||||||
prop="createTime"
|
prop="createTime"
|
||||||
@@ -621,8 +621,8 @@ const columnOptions = [
|
|||||||
{ prop: 'carrierType', label: '承运类型' },
|
{ prop: 'carrierType', label: '承运类型' },
|
||||||
{ prop: 'waybillCount', label: '运单数' },
|
{ prop: 'waybillCount', label: '运单数' },
|
||||||
{ prop: 'importTypeName', label: '导入方式' },
|
{ prop: 'importTypeName', label: '导入方式' },
|
||||||
{ prop: 'statusName', label: '状态' },
|
|
||||||
{ prop: 'createUserName', label: '创建人' },
|
{ prop: 'createUserName', label: '创建人' },
|
||||||
|
{ prop: 'statusName', label: '状态' },
|
||||||
{ prop: 'createTime', label: '创建时间' },
|
{ prop: 'createTime', label: '创建时间' },
|
||||||
{ prop: 'updateTime', label: '更新时间' },
|
{ prop: 'updateTime', label: '更新时间' },
|
||||||
];
|
];
|
||||||
|
|||||||
@@ -1774,19 +1774,14 @@
|
|||||||
<div class="waybill-manage-page__waybill-heading">
|
<div class="waybill-manage-page__waybill-heading">
|
||||||
<strong>运单详情</strong>
|
<strong>运单详情</strong>
|
||||||
<span>{{ detailRow.waybillNo || '-' }}</span>
|
<span>{{ detailRow.waybillNo || '-' }}</span>
|
||||||
<el-tag :type="statusTagType(detailRow.businessStatus)">
|
<span class="detail-status-text status-text-success">{{
|
||||||
{{ displayStatus(detailRow, 'businessStatus') }}
|
displayStatus(detailRow, 'businessStatus')
|
||||||
</el-tag>
|
}}</span>
|
||||||
<el-tag type="primary">
|
<span class="detail-status-text detail-transport-type">
|
||||||
{{
|
{{ getTransportTypeLabel(waybillTransportMode(detailRow)) || '-' }}
|
||||||
detailRow.transportTypeName ||
|
</span>
|
||||||
getTransportTypeLabel(detailRow.transportType) ||
|
|
||||||
waybillTransportMode(detailRow) ||
|
|
||||||
'-'
|
|
||||||
}}
|
|
||||||
</el-tag>
|
|
||||||
<el-link
|
<el-link
|
||||||
v-if="detailRow.id && canChangeWaybillRoute(detailRow)"
|
v-if="detailRow.id"
|
||||||
class="waybill-manage-page__waybill-route-change-link"
|
class="waybill-manage-page__waybill-route-change-link"
|
||||||
type="primary"
|
type="primary"
|
||||||
@click="openWaybillRouteChangeDialog"
|
@click="openWaybillRouteChangeDialog"
|
||||||
@@ -1802,18 +1797,7 @@
|
|||||||
:class="{ 'is-plan': item[0] === 'planName' }"
|
:class="{ 'is-plan': item[0] === 'planName' }"
|
||||||
>
|
>
|
||||||
<span class="waybill-manage-page__waybill-label">{{ item[1] }}</span>
|
<span class="waybill-manage-page__waybill-label">{{ item[1] }}</span>
|
||||||
<el-link
|
<span :class="['waybill-manage-page__waybill-value', { 'is-link': item[2] }]">
|
||||||
v-if="canOpenWaybillDetailSummaryLink(item[0])"
|
|
||||||
type="primary"
|
|
||||||
class="waybill-manage-page__waybill-value is-link"
|
|
||||||
@click="handleWaybillDetailSummaryClick(item[0])"
|
|
||||||
>
|
|
||||||
{{ formatDetailValue(detailRow, item[0]) }}
|
|
||||||
</el-link>
|
|
||||||
<span
|
|
||||||
v-else
|
|
||||||
:class="['waybill-manage-page__waybill-value', { 'is-link': item[2] }]"
|
|
||||||
>
|
|
||||||
{{ formatDetailValue(detailRow, item[0]) }}
|
{{ formatDetailValue(detailRow, item[0]) }}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
@@ -1939,17 +1923,11 @@
|
|||||||
}}</strong
|
}}</strong
|
||||||
>
|
>
|
||||||
</div>
|
</div>
|
||||||
<div class="waybill-manage-page__waybill-detail-field">
|
<div
|
||||||
<span>里程(km)</span
|
v-if="!waybillIsCarrier(detailRow)"
|
||||||
><strong>{{ waybillDetailMileage(detailRow) || '-' }}</strong>
|
class="waybill-manage-page__waybill-detail-field"
|
||||||
</div>
|
>
|
||||||
<div class="waybill-manage-page__waybill-detail-field">
|
<span>里程(km)</span><strong>{{ detailRow.mileage || '-' }}</strong>
|
||||||
<span>计划发货日期</span
|
|
||||||
><strong>{{ waybillDetailEstimatedStartTime(detailRow) || '-' }}</strong>
|
|
||||||
</div>
|
|
||||||
<div class="waybill-manage-page__waybill-detail-field">
|
|
||||||
<span>计划完成日期</span
|
|
||||||
><strong>{{ waybillDetailEstimatedEndTime(detailRow) || '-' }}</strong>
|
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<template v-else-if="waybillIsNonRoadTransport(detailRow)">
|
<template v-else-if="waybillIsNonRoadTransport(detailRow)">
|
||||||
@@ -1971,32 +1949,12 @@
|
|||||||
<div class="waybill-manage-page__waybill-detail-field">
|
<div class="waybill-manage-page__waybill-detail-field">
|
||||||
<span>仓位</span><strong>{{ detailRow.cabinNo || '-' }}</strong>
|
<span>仓位</span><strong>{{ detailRow.cabinNo || '-' }}</strong>
|
||||||
</div>
|
</div>
|
||||||
<div class="waybill-manage-page__waybill-detail-field">
|
|
||||||
<span>预计发货日期</span
|
|
||||||
><strong>{{ waybillDetailEstimatedStartTime(detailRow) || '-' }}</strong>
|
|
||||||
</div>
|
|
||||||
<div class="waybill-manage-page__waybill-detail-field">
|
|
||||||
<span>预计完成日期</span
|
|
||||||
><strong>{{ waybillDetailEstimatedEndTime(detailRow) || '-' }}</strong>
|
|
||||||
</div>
|
|
||||||
</template>
|
</template>
|
||||||
<template v-else>
|
<template v-else>
|
||||||
<div class="waybill-manage-page__waybill-detail-field">
|
<div class="waybill-manage-page__waybill-detail-field">
|
||||||
<span>{{ waybillVehicleNoLabel(detailRow) }}</span>
|
<span>{{ waybillVehicleNoLabel(detailRow) }}</span>
|
||||||
<strong>{{ detailRow.vehicleNo || '-' }}</strong>
|
<strong>{{ detailRow.vehicleNo || '-' }}</strong>
|
||||||
</div>
|
</div>
|
||||||
<div class="waybill-manage-page__waybill-detail-field">
|
|
||||||
<span>里程(km)</span
|
|
||||||
><strong>{{ waybillDetailMileage(detailRow) || '-' }}</strong>
|
|
||||||
</div>
|
|
||||||
<div class="waybill-manage-page__waybill-detail-field">
|
|
||||||
<span>计划发货日期</span
|
|
||||||
><strong>{{ waybillDetailEstimatedStartTime(detailRow) || '-' }}</strong>
|
|
||||||
</div>
|
|
||||||
<div class="waybill-manage-page__waybill-detail-field">
|
|
||||||
<span>计划完成日期</span
|
|
||||||
><strong>{{ waybillDetailEstimatedEndTime(detailRow) || '-' }}</strong>
|
|
||||||
</div>
|
|
||||||
</template>
|
</template>
|
||||||
<div
|
<div
|
||||||
class="waybill-manage-page__waybill-detail-field"
|
class="waybill-manage-page__waybill-detail-field"
|
||||||
@@ -2225,11 +2183,7 @@
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</el-tab-pane>
|
</el-tab-pane>
|
||||||
<el-tab-pane
|
<el-tab-pane label="司机上传" name="driverUpload">
|
||||||
v-if="waybillDriverUploads.length"
|
|
||||||
label="司机上传"
|
|
||||||
name="driverUpload"
|
|
||||||
>
|
|
||||||
<div
|
<div
|
||||||
v-loading="waybillPunchRecordsLoading"
|
v-loading="waybillPunchRecordsLoading"
|
||||||
class="waybill-manage-page__driver-upload-grid"
|
class="waybill-manage-page__driver-upload-grid"
|
||||||
@@ -2246,6 +2200,11 @@
|
|||||||
{{ photo.label || '凭证' }}
|
{{ photo.label || '凭证' }}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<el-empty
|
||||||
|
v-if="!waybillPunchRecordsLoading && !waybillDriverUploads.length"
|
||||||
|
description="暂无司机上传数据"
|
||||||
|
:image-size="50"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
</el-tab-pane>
|
</el-tab-pane>
|
||||||
</el-tabs>
|
</el-tabs>
|
||||||
@@ -2356,7 +2315,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
</div>
|
</div>
|
||||||
<div v-if="detailPageLocked && !isPublicWaybillView" class="waybill-manage-page__detail-footer">
|
<div v-if="detailPageLocked" class="waybill-manage-page__detail-footer">
|
||||||
<el-button type="primary" @click="closeDetail">关闭</el-button>
|
<el-button type="primary" @click="closeDetail">关闭</el-button>
|
||||||
</div>
|
</div>
|
||||||
<template v-if="!detailPageLocked" #footer>
|
<template v-if="!detailPageLocked" #footer>
|
||||||
@@ -2459,6 +2418,35 @@
|
|||||||
</el-table-column>
|
</el-table-column>
|
||||||
</el-table>
|
</el-table>
|
||||||
</section-card>
|
</section-card>
|
||||||
|
<section-card title="路线编辑">
|
||||||
|
<template #extra>
|
||||||
|
<span class="waybill-manage-page__route-change-hint">拖拽调整顺序</span>
|
||||||
|
</template>
|
||||||
|
<div
|
||||||
|
v-if="waybillRouteChangeNodes.length"
|
||||||
|
class="waybill-manage-page__route-change-list"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
v-for="(node, index) in waybillRouteChangeNodes"
|
||||||
|
:key="node.key || index"
|
||||||
|
class="waybill-manage-page__route-change-item"
|
||||||
|
draggable="true"
|
||||||
|
@dragstart="handleWaybillRouteChangeDragStart(index)"
|
||||||
|
@dragover.prevent
|
||||||
|
@drop="handleWaybillRouteChangeDrop(index)"
|
||||||
|
>
|
||||||
|
<span class="waybill-manage-page__route-change-type">
|
||||||
|
{{ waybillRouteChangeTypeText(index) }}
|
||||||
|
</span>
|
||||||
|
<span>{{ node.address }}</span>
|
||||||
|
<span class="waybill-manage-page__route-change-tags">
|
||||||
|
<el-tag v-for="tag in node.tags || []" :key="tag" size="small">{{ tag }}</el-tag>
|
||||||
|
</span>
|
||||||
|
<el-icon><Rank /></el-icon>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<el-empty v-else description="暂无路线" :image-size="60" />
|
||||||
|
</section-card>
|
||||||
<el-form label-width="auto">
|
<el-form label-width="auto">
|
||||||
<el-form-item label="变更备注" style="margin-top: 8px">
|
<el-form-item label="变更备注" style="margin-top: 8px">
|
||||||
<el-input
|
<el-input
|
||||||
@@ -2876,8 +2864,8 @@
|
|||||||
</el-button>
|
</el-button>
|
||||||
</div>
|
</div>
|
||||||
<div class="map-picker-content">
|
<div class="map-picker-content">
|
||||||
<div ref="transportAmap" class="waybill-manage-page__map"></div>
|
|
||||||
<map-search-results :results="transportMapSearchResults" @select="selectTransportMapSearchResult" />
|
<map-search-results :results="transportMapSearchResults" @select="selectTransportMapSearchResult" />
|
||||||
|
<div ref="transportAmap" class="waybill-manage-page__map"></div>
|
||||||
</div>
|
</div>
|
||||||
<div class="waybill-manage-page__map-info">
|
<div class="waybill-manage-page__map-info">
|
||||||
<span>{{ transportMapStatus }}</span>
|
<span>{{ transportMapStatus }}</span>
|
||||||
@@ -3123,7 +3111,6 @@ import {
|
|||||||
getList as getProjectList,
|
getList as getProjectList,
|
||||||
} from '@/api/business/project-apply';
|
} from '@/api/business/project-apply';
|
||||||
import { getList as getLoadingList } from '@/api/business/loading-manage';
|
import { getList as getLoadingList } from '@/api/business/loading-manage';
|
||||||
import { getList as getCustomerArchiveList } from '@/api/vehicle/customer-archive';
|
|
||||||
import { getList as getCommonRouteList } from '@/api/business/common-route';
|
import { getList as getCommonRouteList } from '@/api/business/common-route';
|
||||||
import {
|
import {
|
||||||
getDetail as getShippingPlanDetail,
|
getDetail as getShippingPlanDetail,
|
||||||
@@ -3135,11 +3122,7 @@ import {
|
|||||||
getVoucherImages as getProcessConfigVoucherImages,
|
getVoucherImages as getProcessConfigVoucherImages,
|
||||||
} from '@/api/business/process-config';
|
} from '@/api/business/process-config';
|
||||||
import { getPunchRecords as getWaybillPunchRecords, locateVehicle, trackVehicle } from '@/api/business/waybill-manage';
|
import { getPunchRecords as getWaybillPunchRecords, locateVehicle, trackVehicle } from '@/api/business/waybill-manage';
|
||||||
import { getMkPublicDetail } from '@/api/mk-process';
|
|
||||||
import { getList as getDriverList } from '@/api/transportCapacity/driver';
|
import { getList as getDriverList } from '@/api/transportCapacity/driver';
|
||||||
import {
|
|
||||||
fetchDriversByCarrierOrganizations,
|
|
||||||
} from '@/utils/carrier-org-resource';
|
|
||||||
import { getDictionary } from '@/api/system/dictbiz';
|
import { getDictionary } from '@/api/system/dictbiz';
|
||||||
import { getDictionary as getSystemDictionary } from '@/api/system/dict';
|
import { getDictionary as getSystemDictionary } from '@/api/system/dict';
|
||||||
import { addressTypeOptions } from '@/option/base/common-address';
|
import { addressTypeOptions } from '@/option/base/common-address';
|
||||||
@@ -3148,12 +3131,11 @@ import { formatUpdateUserName } from '@/utils/audit';
|
|||||||
import { submitMkApprovalFlow } from '@/utils/mk-approval';
|
import { submitMkApprovalFlow } from '@/utils/mk-approval';
|
||||||
import { getToken } from '@/utils/auth';
|
import { getToken } from '@/utils/auth';
|
||||||
import { openImportDialog } from '@/utils/import-excel';
|
import { openImportDialog } from '@/utils/import-excel';
|
||||||
import { normalizeMapSearchResults } from '@/utils/map-search';
|
|
||||||
import { applyTableMenuWidth } from '@/utils/table-menu';
|
import { applyTableMenuWidth } from '@/utils/table-menu';
|
||||||
import { downloadFileByUrl, downloadXls } from '@/utils/util';
|
import { downloadFileByUrl, downloadXls } from '@/utils/util';
|
||||||
import { isMobile } from '@/utils/validate';
|
import { isMobile } from '@/utils/validate';
|
||||||
import { InfoFilled, Location, OfficeBuilding, Search, WarnTriangleFilled } from '@element-plus/icons-vue';
|
import { InfoFilled, Location, OfficeBuilding, Rank, Search, WarnTriangleFilled } from '@element-plus/icons-vue';
|
||||||
import { ElAutocomplete, ElImageViewer } from 'element-plus';
|
import { ElImageViewer } from 'element-plus';
|
||||||
import { OpenFileViewer } from '@open-file-viewer/vue';
|
import { OpenFileViewer } from '@open-file-viewer/vue';
|
||||||
import {
|
import {
|
||||||
fallbackPlugin,
|
fallbackPlugin,
|
||||||
@@ -3328,6 +3310,7 @@ export default {
|
|||||||
PageDetail,
|
PageDetail,
|
||||||
InfoFilled,
|
InfoFilled,
|
||||||
WarnTriangleFilled,
|
WarnTriangleFilled,
|
||||||
|
Rank,
|
||||||
ElImageViewer,
|
ElImageViewer,
|
||||||
OpenFileViewer,
|
OpenFileViewer,
|
||||||
},
|
},
|
||||||
@@ -3400,6 +3383,7 @@ export default {
|
|||||||
waybillRouteChangeNodes: [],
|
waybillRouteChangeNodes: [],
|
||||||
waybillRouteChangeRemark: '',
|
waybillRouteChangeRemark: '',
|
||||||
waybillRouteChangeRecords: [],
|
waybillRouteChangeRecords: [],
|
||||||
|
waybillRouteChangeDragIndex: -1,
|
||||||
waybillRouteChangeSaving: false,
|
waybillRouteChangeSaving: false,
|
||||||
waybillLocateLoading: false,
|
waybillLocateLoading: false,
|
||||||
waybillLocateInfo: null,
|
waybillLocateInfo: null,
|
||||||
@@ -3662,8 +3646,6 @@ export default {
|
|||||||
this.formPageLocked = this.isStandaloneWaybillFormPage;
|
this.formPageLocked = this.isStandaloneWaybillFormPage;
|
||||||
this.detailPageLocked = this.isStandaloneWaybillDetailPage;
|
this.detailPageLocked = this.isStandaloneWaybillDetailPage;
|
||||||
this.formModeLocked = this.$route.query.mode || '';
|
this.formModeLocked = this.$route.query.mode || '';
|
||||||
if (this.isPublicWaybillView) return;
|
|
||||||
this.setupSearchAutocompletes();
|
|
||||||
if (this.config.enableAllDept && this.isAdmin) {
|
if (this.config.enableAllDept && this.isAdmin) {
|
||||||
this.allDept = 1;
|
this.allDept = 1;
|
||||||
}
|
}
|
||||||
@@ -3727,14 +3709,8 @@ export default {
|
|||||||
isStandaloneWaybillFormPage() {
|
isStandaloneWaybillFormPage() {
|
||||||
return this.isStandaloneWaybillPage && ['add', 'edit'].includes(this.$route.query.mode);
|
return this.isStandaloneWaybillPage && ['add', 'edit'].includes(this.$route.query.mode);
|
||||||
},
|
},
|
||||||
isPublicWaybillView() {
|
|
||||||
return this.$route.path === '/business/waybill-manage/public-view';
|
|
||||||
},
|
|
||||||
isStandaloneWaybillDetailPage() {
|
isStandaloneWaybillDetailPage() {
|
||||||
return (
|
return this.standaloneDetailPage && this.$route.path === standaloneWaybillDetailRoute;
|
||||||
(this.standaloneDetailPage && this.$route.path === standaloneWaybillDetailRoute) ||
|
|
||||||
this.isPublicWaybillView
|
|
||||||
);
|
|
||||||
},
|
},
|
||||||
// 容器形态读实例创建时锁定的标志,不随 $route 翻转(详见 data 中 formPageLocked 的说明)
|
// 容器形态读实例创建时锁定的标志,不随 $route 翻转(详见 data 中 formPageLocked 的说明)
|
||||||
// 当前路由是否仍属于本实例(tab.js 按 fullPath 建实例,一个实例只对应一个 path)。
|
// 当前路由是否仍属于本实例(tab.js 按 fullPath 建实例,一个实例只对应一个 path)。
|
||||||
@@ -4322,12 +4298,8 @@ export default {
|
|||||||
) {
|
) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
// 进行中的运单隐藏取消(与 config.canCancel / isInProgressStatus 对齐)
|
|
||||||
if (typeof this.config.canCancel === 'function' && !this.config.canCancel(row)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
const status = this.statusValue(row);
|
const status = this.statusValue(row);
|
||||||
return status === 'pending';
|
return ['pending', 'processing', 'running'].includes(status);
|
||||||
},
|
},
|
||||||
canComplete(row) {
|
canComplete(row) {
|
||||||
if (
|
if (
|
||||||
@@ -4558,42 +4530,6 @@ export default {
|
|||||||
waybillIsCarrier(row = {}) {
|
waybillIsCarrier(row = {}) {
|
||||||
return String(row.carrierTypeName || row.carrierType || '').trim() === '承运商';
|
return String(row.carrierTypeName || row.carrierType || '').trim() === '承运商';
|
||||||
},
|
},
|
||||||
waybillDetailTaskInfo(row = {}) {
|
|
||||||
return this.parseJsonObject(row.taskInfoJson || row.taskInfo || {});
|
|
||||||
},
|
|
||||||
waybillDetailMileage(row = {}) {
|
|
||||||
const taskInfo = this.waybillDetailTaskInfo(row);
|
|
||||||
const goodsRows = this.parseJsonArray(row.goodsJson);
|
|
||||||
const firstGoods = goodsRows[0] || {};
|
|
||||||
return this.normalizeMileageValue(
|
|
||||||
row.mileage || taskInfo.mileage || firstGoods.mileage || ''
|
|
||||||
);
|
|
||||||
},
|
|
||||||
waybillDetailEstimatedStartTime(row = {}) {
|
|
||||||
const taskInfo = this.waybillDetailTaskInfo(row);
|
|
||||||
const value =
|
|
||||||
row.estimatedStartTime ||
|
|
||||||
row.planStartDate ||
|
|
||||||
taskInfo.estimatedStartTime ||
|
|
||||||
taskInfo.planStartDate ||
|
|
||||||
'';
|
|
||||||
return this.formatWaybillDetailDate(value);
|
|
||||||
},
|
|
||||||
waybillDetailEstimatedEndTime(row = {}) {
|
|
||||||
const taskInfo = this.waybillDetailTaskInfo(row);
|
|
||||||
const value =
|
|
||||||
row.estimatedEndTime ||
|
|
||||||
row.planEndDate ||
|
|
||||||
taskInfo.estimatedEndTime ||
|
|
||||||
taskInfo.planEndDate ||
|
|
||||||
'';
|
|
||||||
return this.formatWaybillDetailDate(value);
|
|
||||||
},
|
|
||||||
formatWaybillDetailDate(value) {
|
|
||||||
const text = String(value || '').trim();
|
|
||||||
if (!text) return '';
|
|
||||||
return text.slice(0, 10);
|
|
||||||
},
|
|
||||||
waybillVehicleNoLabel(row = {}) {
|
waybillVehicleNoLabel(row = {}) {
|
||||||
return this.waybillIsRoadTransport(row) ? '车牌号' : '船/航/班列号';
|
return this.waybillIsRoadTransport(row) ? '车牌号' : '船/航/班列号';
|
||||||
},
|
},
|
||||||
@@ -4655,19 +4591,14 @@ export default {
|
|||||||
this.waybillVoucherFolder = null;
|
this.waybillVoucherFolder = null;
|
||||||
this.waybillVoucherFolderView = false;
|
this.waybillVoucherFolderView = false;
|
||||||
this.waybillVoucherImagesLoaded = false;
|
this.waybillVoucherImagesLoaded = false;
|
||||||
const request = this.isPublicWaybillView
|
const request =
|
||||||
? getMkPublicDetail('waybill-manage', row.id)
|
typeof this.api.getDetail === 'function'
|
||||||
: typeof this.api.getDetail === 'function'
|
|
||||||
? this.api.getDetail(row.id)
|
? this.api.getDetail(row.id)
|
||||||
: Promise.resolve({ data: { data: row } });
|
: Promise.resolve({ data: { data: row } });
|
||||||
request
|
request
|
||||||
.then(res => {
|
.then(res => {
|
||||||
const detail = res?.data?.data || res?.data || row;
|
const detail = res?.data?.data || res?.data || row;
|
||||||
this.detailRow = detail;
|
this.detailRow = detail;
|
||||||
if (this.isPublicWaybillView) {
|
|
||||||
this.applyFormDetail(detail);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
this.loadWaybillPunchRecords();
|
this.loadWaybillPunchRecords();
|
||||||
this.loadWaybillVoucherImages();
|
this.loadWaybillVoucherImages();
|
||||||
if (detail.contractId) {
|
if (detail.contractId) {
|
||||||
@@ -4735,90 +4666,6 @@ export default {
|
|||||||
query: { id, name: '配载单详情' },
|
query: { id, name: '配载单详情' },
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
waybillDetailCustomerLabel(row = {}) {
|
|
||||||
return row.customer || row.fullName || row.shortName || row.customerName || row.customerCode || '';
|
|
||||||
},
|
|
||||||
waybillDetailCustomerId(row = {}) {
|
|
||||||
return row.id || row.customerId || '';
|
|
||||||
},
|
|
||||||
canOpenWaybillDetailSummaryLink(prop) {
|
|
||||||
if (this.isPublicWaybillView) return false;
|
|
||||||
const row = this.detailRow || {};
|
|
||||||
const value = this.formatDetailValue(row, prop);
|
|
||||||
if (!value || value === '-') return false;
|
|
||||||
if (prop === 'customerName') return Boolean(row.customerName);
|
|
||||||
if (prop === 'contractNo') return Boolean(row.contractId);
|
|
||||||
if (prop === 'projectName') return Boolean(row.projectId);
|
|
||||||
return false;
|
|
||||||
},
|
|
||||||
handleWaybillDetailSummaryClick(prop) {
|
|
||||||
if (prop === 'customerName') return this.openWaybillDetailCustomer();
|
|
||||||
if (prop === 'contractNo') return this.openWaybillDetailContract();
|
|
||||||
if (prop === 'projectName') return this.openWaybillDetailProject();
|
|
||||||
},
|
|
||||||
async resolveWaybillDetailCustomerId() {
|
|
||||||
const row = this.detailRow || {};
|
|
||||||
if (row.customerId) return row.customerId;
|
|
||||||
const name = String(row.customerName || '').trim();
|
|
||||||
if (!name) return '';
|
|
||||||
if (row.projectId) {
|
|
||||||
try {
|
|
||||||
const res = await getProjectDetail(row.projectId);
|
|
||||||
const customers = this.parseJsonArray(res.data?.data?.customerJson);
|
|
||||||
const matched =
|
|
||||||
customers.find(item => this.waybillDetailCustomerLabel(item) === name) ||
|
|
||||||
customers.find(item => {
|
|
||||||
const label = this.waybillDetailCustomerLabel(item);
|
|
||||||
return label && (name.includes(label) || label.includes(name));
|
|
||||||
});
|
|
||||||
const id = this.waybillDetailCustomerId(matched);
|
|
||||||
if (id) return id;
|
|
||||||
} catch (error) {
|
|
||||||
// 项目客户解析失败时回退到客商档案检索
|
|
||||||
}
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
const res = await getCustomerArchiveList(1, 20, { fullName: name });
|
|
||||||
const records = res.data?.data?.records || [];
|
|
||||||
const exact =
|
|
||||||
records.find(item => item.fullName === name || item.shortName === name) || records[0];
|
|
||||||
return exact?.id || '';
|
|
||||||
} catch (error) {
|
|
||||||
return '';
|
|
||||||
}
|
|
||||||
},
|
|
||||||
async openWaybillDetailCustomer() {
|
|
||||||
if (!this.detailRow?.customerName) return;
|
|
||||||
const customerId = await this.resolveWaybillDetailCustomerId();
|
|
||||||
if (!customerId) {
|
|
||||||
this.$message.warning('未找到对应客商档案,无法打开');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
this.$router.push({
|
|
||||||
path: '/vehicle/customer-archive/form',
|
|
||||||
query: { id: String(customerId), name: '查看客商档案', view: '1' },
|
|
||||||
});
|
|
||||||
},
|
|
||||||
openWaybillDetailContract() {
|
|
||||||
if (!this.detailRow?.contractId) {
|
|
||||||
this.$message.warning('合同信息缺失,无法打开');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
this.$router.push({
|
|
||||||
path: '/business/contract-manage/detail',
|
|
||||||
query: { id: this.detailRow.contractId, name: '合同详情' },
|
|
||||||
});
|
|
||||||
},
|
|
||||||
openWaybillDetailProject() {
|
|
||||||
if (!this.detailRow?.projectId) {
|
|
||||||
this.$message.warning('项目信息缺失,无法打开');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
this.$router.push({
|
|
||||||
path: '/business/project-apply/form',
|
|
||||||
query: { mode: 'view', id: this.detailRow.projectId, name: '查看项目管理' },
|
|
||||||
});
|
|
||||||
},
|
|
||||||
loadWaybillDetailProcessNodes(projectId) {
|
loadWaybillDetailProcessNodes(projectId) {
|
||||||
this.waybillDetailProcessNodes = [];
|
this.waybillDetailProcessNodes = [];
|
||||||
if (!projectId) return Promise.resolve([]);
|
if (!projectId) return Promise.resolve([]);
|
||||||
@@ -4989,7 +4836,7 @@ export default {
|
|||||||
: [];
|
: [];
|
||||||
},
|
},
|
||||||
openWaybillRouteChangeDialog() {
|
openWaybillRouteChangeDialog() {
|
||||||
if (!this.detailRow.id || !this.canChangeWaybillRoute(this.detailRow)) return;
|
if (!this.detailRow.id) return;
|
||||||
this.restoreWaybillRouteChangeRecords();
|
this.restoreWaybillRouteChangeRecords();
|
||||||
this.waybillRouteChangeTab = 'change';
|
this.waybillRouteChangeTab = 'change';
|
||||||
this.waybillRouteChangeRemark = '';
|
this.waybillRouteChangeRemark = '';
|
||||||
@@ -5006,11 +4853,22 @@ export default {
|
|||||||
this.waybillRouteChangeNodes = this.buildWaybillRouteNodes(this.waybillRouteChangeRows);
|
this.waybillRouteChangeNodes = this.buildWaybillRouteNodes(this.waybillRouteChangeRows);
|
||||||
this.waybillRouteChangeBox = true;
|
this.waybillRouteChangeBox = true;
|
||||||
},
|
},
|
||||||
canChangeWaybillRoute(row = {}) {
|
waybillRouteChangeTypeText(index) {
|
||||||
const status = String(this.statusValue(row) || row.businessStatus || row.status || '');
|
if (index === 0) return '起';
|
||||||
if (status === 'completed') return false;
|
if (index === this.waybillRouteChangeNodes.length - 1) return '终';
|
||||||
const statusName = String(row.businessStatusName || row.statusName || '');
|
return '经';
|
||||||
return !statusName.includes('已完成');
|
},
|
||||||
|
handleWaybillRouteChangeDragStart(index) {
|
||||||
|
this.waybillRouteChangeDragIndex = index;
|
||||||
|
},
|
||||||
|
handleWaybillRouteChangeDrop(index) {
|
||||||
|
if (this.waybillRouteChangeDragIndex < 0 || this.waybillRouteChangeDragIndex === index)
|
||||||
|
return;
|
||||||
|
const nodes = [...this.waybillRouteChangeNodes];
|
||||||
|
const [node] = nodes.splice(this.waybillRouteChangeDragIndex, 1);
|
||||||
|
nodes.splice(index, 0, node);
|
||||||
|
this.waybillRouteChangeNodes = nodes;
|
||||||
|
this.waybillRouteChangeDragIndex = -1;
|
||||||
},
|
},
|
||||||
updateWaybillRouteChangeNodes() {
|
updateWaybillRouteChangeNodes() {
|
||||||
this.waybillRouteChangeNodes = this.buildWaybillRouteNodes(this.waybillRouteChangeRows);
|
this.waybillRouteChangeNodes = this.buildWaybillRouteNodes(this.waybillRouteChangeRows);
|
||||||
@@ -5059,8 +4917,9 @@ export default {
|
|||||||
row.departureAddress !== row.originalDepartureAddress ||
|
row.departureAddress !== row.originalDepartureAddress ||
|
||||||
row.arrivalAddress !== row.originalArrivalAddress;
|
row.arrivalAddress !== row.originalArrivalAddress;
|
||||||
const routeJson = JSON.stringify(this.waybillRouteChangeNodes);
|
const routeJson = JSON.stringify(this.waybillRouteChangeNodes);
|
||||||
if (!changed && !this.waybillRouteChangeRemark) {
|
const originalRouteJson = this.detailRow.routeJson || '';
|
||||||
this.$message.warning('请修改地址或填写变更备注');
|
if (!changed && routeJson === originalRouteJson && !this.waybillRouteChangeRemark) {
|
||||||
|
this.$message.warning('请修改地址、调整路线或填写变更备注');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (typeof this.api.changeRoute !== 'function') {
|
if (typeof this.api.changeRoute !== 'function') {
|
||||||
@@ -5068,9 +4927,11 @@ export default {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const content = `${row.waybillNo || '运单'}:发货地 ${row.originalDepartureAddress || '-'} → ${
|
const content = changed
|
||||||
row.departureAddress || '-'
|
? `${row.waybillNo || '运单'}:发货地 ${row.originalDepartureAddress || '-'} → ${
|
||||||
};到货地 ${row.originalArrivalAddress || '-'} → ${row.arrivalAddress || '-'}`;
|
row.departureAddress || '-'
|
||||||
|
};到货地 ${row.originalArrivalAddress || '-'} → ${row.arrivalAddress || '-'}`
|
||||||
|
: '调整运输路线顺序';
|
||||||
const records = [
|
const records = [
|
||||||
{
|
{
|
||||||
changeTime: this.$dayjs().format('YYYY-MM-DD HH:mm:ss'),
|
changeTime: this.$dayjs().format('YYYY-MM-DD HH:mm:ss'),
|
||||||
@@ -5390,9 +5251,7 @@ export default {
|
|||||||
: defaultText;
|
: defaultText;
|
||||||
},
|
},
|
||||||
statusTagType(status) {
|
statusTagType(status) {
|
||||||
if (
|
if ([1, 'pending', 'processing', 'approved', 'change_approved'].includes(status)) {
|
||||||
[1, 'pending', 'processing', 'running', 'approved', 'change_approved'].includes(status)
|
|
||||||
) {
|
|
||||||
return 'success';
|
return 'success';
|
||||||
}
|
}
|
||||||
if ([2, 'cancelled', 'withdrawn', 'draft'].includes(status)) {
|
if ([2, 'cancelled', 'withdrawn', 'draft'].includes(status)) {
|
||||||
@@ -6468,15 +6327,6 @@ export default {
|
|||||||
this.form.carrierName = '';
|
this.form.carrierName = '';
|
||||||
this.form.carrierId = '';
|
this.form.carrierId = '';
|
||||||
this.form.carrierContractId = '';
|
this.form.carrierContractId = '';
|
||||||
this.form.driverId = '';
|
|
||||||
this.form.driverName = '';
|
|
||||||
this.form.driverPhone = '';
|
|
||||||
this.form.vehicleNo = '';
|
|
||||||
this.form.trailerVehicleNo = '';
|
|
||||||
this.form.escortName = '';
|
|
||||||
this.form.escortPhone = '';
|
|
||||||
this.taskDriverOptions = [];
|
|
||||||
this.taskEscortOptions = [];
|
|
||||||
if (nextValue === '承运商') {
|
if (nextValue === '承运商') {
|
||||||
this.form.trailerVehicleNo = '';
|
this.form.trailerVehicleNo = '';
|
||||||
this.form.escortName = '';
|
this.form.escortName = '';
|
||||||
@@ -6730,36 +6580,17 @@ export default {
|
|||||||
this.form.carrierContractId = carrier?.carrierContractId || '';
|
this.form.carrierContractId = carrier?.carrierContractId || '';
|
||||||
this.form.carrierId = carrier?.carrierId || '';
|
this.form.carrierId = carrier?.carrierId || '';
|
||||||
this.form.carrierName = carrier?.carrierName || '';
|
this.form.carrierName = carrier?.carrierName || '';
|
||||||
} else {
|
return;
|
||||||
this.form.carrierId = carrier?.carrierContractId ? '' : carrier?.id || '';
|
|
||||||
this.form.carrierName = value || '';
|
|
||||||
}
|
}
|
||||||
// 承运商变更后清空司机/车辆,避免跨组织脏数据
|
this.form.carrierId = carrier?.carrierContractId ? '' : carrier?.id || '';
|
||||||
this.form.driverId = '';
|
this.form.carrierName = value || '';
|
||||||
this.form.driverName = '';
|
|
||||||
this.form.driverPhone = '';
|
|
||||||
this.form.vehicleNo = '';
|
|
||||||
this.form.trailerVehicleNo = '';
|
|
||||||
this.form.escortName = '';
|
|
||||||
this.form.escortPhone = '';
|
|
||||||
this.taskDriverOptions = [];
|
|
||||||
this.taskEscortOptions = [];
|
|
||||||
},
|
|
||||||
getTaskCarrierFilter() {
|
|
||||||
return {
|
|
||||||
carrierId: this.form.carrierId || '',
|
|
||||||
carrierName: this.form.carrierName || '',
|
|
||||||
};
|
|
||||||
},
|
},
|
||||||
loadTaskDriverOptions() {
|
loadTaskDriverOptions() {
|
||||||
if (!this.taskInfoFormEnabled || this.taskDriverLoading) return Promise.resolve([]);
|
if (!this.taskInfoFormEnabled || this.taskDriverLoading) return Promise.resolve([]);
|
||||||
this.taskDriverLoading = true;
|
this.taskDriverLoading = true;
|
||||||
return fetchDriversByCarrierOrganizations(
|
return getDriverList(1, 9999, { posts: '司机' })
|
||||||
{ size: 9999, posts: '司机' },
|
.then(res => {
|
||||||
this.getTaskCarrierFilter()
|
this.taskDriverOptions = extractRecords(res);
|
||||||
)
|
|
||||||
.then(records => {
|
|
||||||
this.taskDriverOptions = records;
|
|
||||||
return this.taskDriverOptions;
|
return this.taskDriverOptions;
|
||||||
})
|
})
|
||||||
.finally(() => {
|
.finally(() => {
|
||||||
@@ -6768,17 +6599,10 @@ export default {
|
|||||||
},
|
},
|
||||||
fetchTaskDriverSuggestions(queryString, callback) {
|
fetchTaskDriverSuggestions(queryString, callback) {
|
||||||
const keyword = String(queryString || '').trim();
|
const keyword = String(queryString || '').trim();
|
||||||
const carrier = this.getTaskCarrierFilter();
|
|
||||||
if (!carrier.carrierId && !carrier.carrierName) {
|
|
||||||
callback([]);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
this.taskDriverLoading = true;
|
this.taskDriverLoading = true;
|
||||||
fetchDriversByCarrierOrganizations(
|
getDriverList(1, 20, { ...(keyword ? { driverName: keyword } : {}), posts: '司机' })
|
||||||
{ size: 20, ...(keyword ? { driverName: keyword } : {}), posts: '司机' },
|
.then(res => {
|
||||||
carrier
|
const records = extractRecords(res);
|
||||||
)
|
|
||||||
.then(records => {
|
|
||||||
this.taskDriverOptions = records;
|
this.taskDriverOptions = records;
|
||||||
callback(
|
callback(
|
||||||
records.map(item => ({
|
records.map(item => ({
|
||||||
@@ -6797,17 +6621,10 @@ export default {
|
|||||||
},
|
},
|
||||||
fetchTaskEscortSuggestions(queryString, callback) {
|
fetchTaskEscortSuggestions(queryString, callback) {
|
||||||
const keyword = String(queryString || '').trim();
|
const keyword = String(queryString || '').trim();
|
||||||
const carrier = this.getTaskCarrierFilter();
|
|
||||||
if (!carrier.carrierId && !carrier.carrierName) {
|
|
||||||
callback([]);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
this.taskEscortLoading = true;
|
this.taskEscortLoading = true;
|
||||||
fetchDriversByCarrierOrganizations(
|
getDriverList(1, 20, { ...(keyword ? { driverName: keyword } : {}), posts: '押运员' })
|
||||||
{ size: 20, ...(keyword ? { driverName: keyword } : {}), posts: '押运员' },
|
.then(res => {
|
||||||
carrier
|
const records = extractRecords(res);
|
||||||
)
|
|
||||||
.then(records => {
|
|
||||||
this.taskEscortOptions = records;
|
this.taskEscortOptions = records;
|
||||||
callback(records.map(item => ({ ...item, value: item.driverName || item.name || '' })));
|
callback(records.map(item => ({ ...item, value: item.driverName || item.name || '' })));
|
||||||
})
|
})
|
||||||
@@ -7947,7 +7764,12 @@ export default {
|
|||||||
this.ensureTransportAmapGeocoder()
|
this.ensureTransportAmapGeocoder()
|
||||||
.then(() => this.runAmapGeocode('location', keyword))
|
.then(() => this.runAmapGeocode('location', keyword))
|
||||||
.then(result => {
|
.then(result => {
|
||||||
this.transportMapSearchResults = normalizeMapSearchResults(result, keyword);
|
this.transportMapSearchResults = (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);
|
const point = this.resolveMapPoint(result);
|
||||||
if (!point) {
|
if (!point) {
|
||||||
this.transportMapStatus = '未找到匹配地址';
|
this.transportMapStatus = '未找到匹配地址';
|
||||||
@@ -8570,123 +8392,6 @@ export default {
|
|||||||
this.page.currentPage = 1;
|
this.page.currentPage = 1;
|
||||||
this.onLoad(this.page);
|
this.onLoad(this.page);
|
||||||
},
|
},
|
||||||
setupSearchAutocompletes() {
|
|
||||||
const fields = [
|
|
||||||
{
|
|
||||||
prop: 'projectName',
|
|
||||||
placeholder: '请选择或输入项目名称',
|
|
||||||
fetch: (queryString, callback) =>
|
|
||||||
this.fetchSearchNameSuggestions(queryString, callback, {
|
|
||||||
request: keyword =>
|
|
||||||
getProjectList(1, 20, keyword ? { projectName: keyword } : {}),
|
|
||||||
getValue: item => item.projectName || item.name || '',
|
|
||||||
}),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
prop: 'customerName',
|
|
||||||
placeholder: '请选择或输入客户名称',
|
|
||||||
fetch: (queryString, callback) =>
|
|
||||||
this.fetchSearchNameSuggestions(queryString, callback, {
|
|
||||||
request: keyword =>
|
|
||||||
getCustomerArchiveList(1, 20, {
|
|
||||||
...(keyword ? { fullName: keyword } : {}),
|
|
||||||
customerType: '客户',
|
|
||||||
}),
|
|
||||||
getValue: item => item.fullName || item.customerName || item.name || '',
|
|
||||||
}),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
prop: 'cargoType',
|
|
||||||
placeholder: '请选择或输入货物类型',
|
|
||||||
fetch: (queryString, callback) =>
|
|
||||||
this.fetchSearchNameSuggestions(queryString, callback, {
|
|
||||||
request: keyword =>
|
|
||||||
getCargoTypeList(1, 20, keyword ? { cargoName: keyword } : {}),
|
|
||||||
getValue: item => item.cargoName || item.typeName || item.name || item.label || '',
|
|
||||||
}),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
prop: 'carrierName',
|
|
||||||
placeholder: '请选择或输入承运商名称',
|
|
||||||
fetch: (queryString, callback) =>
|
|
||||||
this.fetchSearchNameSuggestions(queryString, callback, {
|
|
||||||
request: keyword =>
|
|
||||||
getCustomerArchiveList(1, 20, {
|
|
||||||
...(keyword ? { fullName: keyword } : {}),
|
|
||||||
customerType: '承运商',
|
|
||||||
}),
|
|
||||||
getValue: item =>
|
|
||||||
item.fullName || item.carrierName || item.customerName || item.name || '',
|
|
||||||
}),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
prop: 'driverName',
|
|
||||||
placeholder: '请选择或输入司机名称',
|
|
||||||
fetch: (queryString, callback) =>
|
|
||||||
this.fetchSearchNameSuggestions(queryString, callback, {
|
|
||||||
request: keyword =>
|
|
||||||
getDriverList(1, 20, {
|
|
||||||
...(keyword ? { driverName: keyword } : {}),
|
|
||||||
posts: '司机',
|
|
||||||
}),
|
|
||||||
getValue: item => item.driverName || item.name || '',
|
|
||||||
}),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
prop: 'planName',
|
|
||||||
placeholder: '请选择或输入计划名称',
|
|
||||||
fetch: (queryString, callback) =>
|
|
||||||
this.fetchSearchNameSuggestions(queryString, callback, {
|
|
||||||
request: keyword =>
|
|
||||||
getShippingPlanList(1, 20, keyword ? { planName: keyword } : {}),
|
|
||||||
getValue: item => item.planName || item.name || '',
|
|
||||||
}),
|
|
||||||
},
|
|
||||||
];
|
|
||||||
fields.forEach(field => {
|
|
||||||
const column = this.findColumn?.(this.option.column, field.prop);
|
|
||||||
if (!column) return;
|
|
||||||
column.searchPlaceholder = field.placeholder;
|
|
||||||
column.renderSearch = scope =>
|
|
||||||
this.renderSearchAutocomplete(scope, field.prop, field.placeholder, field.fetch);
|
|
||||||
});
|
|
||||||
},
|
|
||||||
renderSearchAutocomplete(scope, prop, placeholder, fetchSuggestions) {
|
|
||||||
return h(ElAutocomplete, {
|
|
||||||
modelValue: scope.row?.[prop] ?? '',
|
|
||||||
'onUpdate:modelValue': value => {
|
|
||||||
if (scope.row) scope.row[prop] = value ?? '';
|
|
||||||
},
|
|
||||||
fetchSuggestions,
|
|
||||||
debounce: 300,
|
|
||||||
clearable: true,
|
|
||||||
triggerOnFocus: true,
|
|
||||||
placeholder,
|
|
||||||
style: 'width: 100%',
|
|
||||||
valueKey: 'value',
|
|
||||||
fitInputWidth: true,
|
|
||||||
});
|
|
||||||
},
|
|
||||||
fetchSearchNameSuggestions(queryString, callback, { request, getValue }) {
|
|
||||||
const keyword = String(queryString || '').trim();
|
|
||||||
Promise.resolve(request(keyword))
|
|
||||||
.then(res => {
|
|
||||||
const records = extractRecords(res);
|
|
||||||
const seen = new Set();
|
|
||||||
const options = [];
|
|
||||||
records.forEach(item => {
|
|
||||||
const value = String(getValue(item) || '').trim();
|
|
||||||
if (!value || seen.has(value)) return;
|
|
||||||
seen.add(value);
|
|
||||||
options.push({ ...item, value });
|
|
||||||
});
|
|
||||||
callback(options);
|
|
||||||
})
|
|
||||||
.catch(error => {
|
|
||||||
window.console.log(error);
|
|
||||||
callback([]);
|
|
||||||
});
|
|
||||||
},
|
|
||||||
searchChange(params, done) {
|
searchChange(params, done) {
|
||||||
this.query = {
|
this.query = {
|
||||||
...params,
|
...params,
|
||||||
@@ -8855,7 +8560,7 @@ export default {
|
|||||||
};
|
};
|
||||||
this.mileageForm = {
|
this.mileageForm = {
|
||||||
id: row.id,
|
id: row.id,
|
||||||
mileage: this.normalizeMileageValue(row.mileage),
|
mileage: row.mileage === null || row.mileage === undefined ? '' : String(row.mileage),
|
||||||
mileageRemark: row.mileageRemark || '',
|
mileageRemark: row.mileageRemark || '',
|
||||||
};
|
};
|
||||||
this.$nextTick(() => this.$refs.mileageFormRef?.clearValidate());
|
this.$nextTick(() => this.$refs.mileageFormRef?.clearValidate());
|
||||||
@@ -8952,21 +8657,10 @@ export default {
|
|||||||
},
|
},
|
||||||
fetchReassignDriverSuggestions(queryString, callback) {
|
fetchReassignDriverSuggestions(queryString, callback) {
|
||||||
const keyword = String(queryString || '').trim();
|
const keyword = String(queryString || '').trim();
|
||||||
const row = this.reassignDrawer.row || {};
|
|
||||||
const carrier = {
|
|
||||||
carrierId: row.carrierId || '',
|
|
||||||
carrierName: row.carrierName || '',
|
|
||||||
};
|
|
||||||
if (!carrier.carrierId && !carrier.carrierName) {
|
|
||||||
callback([]);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
this.reassignDrawer.driverLoading = true;
|
this.reassignDrawer.driverLoading = true;
|
||||||
fetchDriversByCarrierOrganizations(
|
getDriverList(1, 20, { ...(keyword ? { driverName: keyword } : {}), posts: '司机' })
|
||||||
{ size: 20, ...(keyword ? { driverName: keyword } : {}), posts: '司机' },
|
.then(res => {
|
||||||
carrier
|
const records = extractRecords(res);
|
||||||
)
|
|
||||||
.then(records => {
|
|
||||||
this.reassignDriverOptions = records;
|
this.reassignDriverOptions = records;
|
||||||
callback(
|
callback(
|
||||||
records.map(item => ({
|
records.map(item => ({
|
||||||
@@ -10055,15 +9749,6 @@ export default {
|
|||||||
strong {
|
strong {
|
||||||
font-size: 20px;
|
font-size: 20px;
|
||||||
}
|
}
|
||||||
|
|
||||||
:deep(.el-tag) {
|
|
||||||
height: 28px;
|
|
||||||
padding: 0 12px;
|
|
||||||
border: 0;
|
|
||||||
border-radius: 8px;
|
|
||||||
font-size: 13px;
|
|
||||||
line-height: 28px;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
&__waybill-route-change-link {
|
&__waybill-route-change-link {
|
||||||
@@ -10100,12 +9785,7 @@ export default {
|
|||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
|
|
||||||
&.is-link {
|
&.is-link {
|
||||||
display: inline;
|
|
||||||
justify-content: flex-start;
|
|
||||||
max-width: 100%;
|
|
||||||
color: #409eff;
|
color: #409eff;
|
||||||
font-weight: 500;
|
|
||||||
vertical-align: baseline;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -70,7 +70,7 @@
|
|||||||
type="date"
|
type="date"
|
||||||
format="YYYY-MM-DD"
|
format="YYYY-MM-DD"
|
||||||
value-format="YYYY-MM-DD"
|
value-format="YYYY-MM-DD"
|
||||||
placeholder="请输入"
|
placeholder="YYYY-MM-DD"
|
||||||
/>
|
/>
|
||||||
<span>至</span>
|
<span>至</span>
|
||||||
<el-date-picker
|
<el-date-picker
|
||||||
@@ -78,7 +78,7 @@
|
|||||||
type="date"
|
type="date"
|
||||||
format="YYYY-MM-DD"
|
format="YYYY-MM-DD"
|
||||||
value-format="YYYY-MM-DD"
|
value-format="YYYY-MM-DD"
|
||||||
placeholder="请输入"
|
placeholder="YYYY-MM-DD"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
@@ -92,12 +92,12 @@
|
|||||||
</el-input>
|
</el-input>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
<el-form-item label="合同金额">
|
<el-form-item label="合同金额">
|
||||||
<el-input
|
<el-input-number
|
||||||
:model-value="contractAmountDisplay"
|
v-model="form.contractAmount"
|
||||||
|
:min="0"
|
||||||
|
:precision="2"
|
||||||
|
:controls="false"
|
||||||
placeholder="请输入"
|
placeholder="请输入"
|
||||||
clearable
|
|
||||||
@input="handleContractAmountInput"
|
|
||||||
@clear="form.contractAmount = null"
|
|
||||||
/>
|
/>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
<el-form-item label="是否范本">
|
<el-form-item label="是否范本">
|
||||||
@@ -189,11 +189,10 @@
|
|||||||
title="合同文件"
|
title="合同文件"
|
||||||
description
|
description
|
||||||
attachment-type
|
attachment-type
|
||||||
:attachment-location-options="changeAttachmentLocationOptions"
|
use-upload-dialog
|
||||||
:rows="contractFileRows"
|
:rows="contractFileRows"
|
||||||
:preview="previewAttachment"
|
:preview="previewAttachment"
|
||||||
@update:rows="contractFileRows = $event"
|
@update:rows="contractFileRows = $event"
|
||||||
@upload-to-location="handleAttachmentUploadToLocation"
|
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<section class="change-section">
|
<section class="change-section">
|
||||||
@@ -219,7 +218,7 @@
|
|||||||
</el-radio-group>
|
</el-radio-group>
|
||||||
</div>
|
</div>
|
||||||
<el-form v-if="settlementRule.autoGenerate === 1" :model="settlementRule" label-position="right" label-width="auto" class="settlement-form">
|
<el-form v-if="settlementRule.autoGenerate === 1" :model="settlementRule" label-position="right" label-width="auto" class="settlement-form">
|
||||||
<el-form-item label="账单起始日" required><el-date-picker v-model="settlementRule.billStartDate" type="date" placeholder="请选择账单起始日" format="YYYY-MM-DD" value-format="YYYY-MM-DD" /></el-form-item>
|
<el-form-item label="账单起始日期" required><el-date-picker v-model="settlementRule.billStartDate" type="date" placeholder="请选择账单起始日期" format="YYYY-MM-DD" value-format="YYYY-MM-DD" /></el-form-item>
|
||||||
<el-form-item label="结算类型" required><el-select v-model="settlementRule.settlementType" placeholder="请选择结算类型" @change="handleSettlementTypeChange"><el-option v-for="item in settlementTypeOptions" :key="item" :label="item" :value="item" /></el-select></el-form-item>
|
<el-form-item label="结算类型" required><el-select v-model="settlementRule.settlementType" placeholder="请选择结算类型" @change="handleSettlementTypeChange"><el-option v-for="item in settlementTypeOptions" :key="item" :label="item" :value="item" /></el-select></el-form-item>
|
||||||
<el-form-item v-if="showSettlementBillCycleType" label="结算周期" required><el-select v-model="settlementRule.billCycleType" placeholder="请选择结算周期" @change="handleCycleTypeChange"><el-option v-for="item in billCycleTypeOptions" :key="item" :label="item" :value="item" /></el-select></el-form-item>
|
<el-form-item v-if="showSettlementBillCycleType" label="结算周期" required><el-select v-model="settlementRule.billCycleType" placeholder="请选择结算周期" @change="handleCycleTypeChange"><el-option v-for="item in billCycleTypeOptions" :key="item" :label="item" :value="item" /></el-select></el-form-item>
|
||||||
<el-form-item v-if="showSettlementBillCutoffDay" label="账单截单日" required><el-select v-model="settlementRule.billCutoffDay" placeholder="请选择账单截单日"><el-option v-for="item in billCutoffDayOptions" :key="item.value" :label="item.label" :value="item.value" /></el-select></el-form-item>
|
<el-form-item v-if="showSettlementBillCutoffDay" label="账单截单日" required><el-select v-model="settlementRule.billCutoffDay" placeholder="请选择账单截单日"><el-option v-for="item in billCutoffDayOptions" :key="item.value" :label="item.label" :value="item.value" /></el-select></el-form-item>
|
||||||
@@ -257,12 +256,11 @@
|
|||||||
title="其它附件"
|
title="其它附件"
|
||||||
description
|
description
|
||||||
attachment-type
|
attachment-type
|
||||||
|
use-upload-dialog
|
||||||
attachment-location="其它附件"
|
attachment-location="其它附件"
|
||||||
:attachment-location-options="changeAttachmentLocationOptions"
|
|
||||||
:rows="attachments"
|
:rows="attachments"
|
||||||
:preview="previewAttachment"
|
:preview="previewAttachment"
|
||||||
@update:rows="attachments = $event"
|
@update:rows="attachments = $event"
|
||||||
@upload-to-location="handleAttachmentUploadToLocation"
|
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<section class="change-section change-reason-section">
|
<section class="change-section change-reason-section">
|
||||||
@@ -283,12 +281,11 @@
|
|||||||
title="变更材料"
|
title="变更材料"
|
||||||
description
|
description
|
||||||
attachment-type
|
attachment-type
|
||||||
|
use-upload-dialog
|
||||||
attachment-location="变更材料"
|
attachment-location="变更材料"
|
||||||
:attachment-location-options="changeAttachmentLocationOptions"
|
|
||||||
:rows="changeMaterials"
|
:rows="changeMaterials"
|
||||||
:preview="previewAttachment"
|
:preview="previewAttachment"
|
||||||
@update:rows="changeMaterials = $event"
|
@update:rows="changeMaterials = $event"
|
||||||
@upload-to-location="handleAttachmentUploadToLocation"
|
|
||||||
/>
|
/>
|
||||||
<div class="page-footer">
|
<div class="page-footer">
|
||||||
<el-button @click="$router.back()">取消</el-button>
|
<el-button @click="$router.back()">取消</el-button>
|
||||||
@@ -346,15 +343,7 @@ const normalizeOptionalPositiveInteger = value => {
|
|||||||
return Number.isInteger(number) && number > 0 ? number : null;
|
return Number.isInteger(number) && number > 0 ? number : null;
|
||||||
};
|
};
|
||||||
const normalizeOptionalAmount = value => {
|
const normalizeOptionalAmount = value => {
|
||||||
if (
|
if (value === undefined || value === null || value === '') return null;
|
||||||
value === undefined ||
|
|
||||||
value === null ||
|
|
||||||
value === '' ||
|
|
||||||
Number(value) === -1 ||
|
|
||||||
String(value) === '-1'
|
|
||||||
) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
const number = Number(value);
|
const number = Number(value);
|
||||||
return Number.isFinite(number) && number >= 0 ? Number(number.toFixed(2)) : null;
|
return Number.isFinite(number) && number >= 0 ? Number(number.toFixed(2)) : null;
|
||||||
};
|
};
|
||||||
@@ -432,22 +421,6 @@ export default {
|
|||||||
pageTitle() {
|
pageTitle() {
|
||||||
return this.$route.query.name || '合同变更';
|
return this.$route.query.name || '合同变更';
|
||||||
},
|
},
|
||||||
changeAttachmentLocationOptions() {
|
|
||||||
return ['合同文件', '其它附件', '变更材料'];
|
|
||||||
},
|
|
||||||
contractAmountDisplay() {
|
|
||||||
const value = this.form.contractAmount;
|
|
||||||
if (
|
|
||||||
value === undefined ||
|
|
||||||
value === null ||
|
|
||||||
value === '' ||
|
|
||||||
Number(value) === -1 ||
|
|
||||||
String(value) === '-1'
|
|
||||||
) {
|
|
||||||
return '';
|
|
||||||
}
|
|
||||||
return value;
|
|
||||||
},
|
|
||||||
contractCategoryOptions() {
|
contractCategoryOptions() {
|
||||||
return this.contractCategoryDictOptions.length
|
return this.contractCategoryDictOptions.length
|
||||||
? this.contractCategoryDictOptions
|
? this.contractCategoryDictOptions
|
||||||
@@ -491,13 +464,6 @@ export default {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
handleContractAmountInput(value) {
|
|
||||||
const text = String(value || '').replace(/[^\d.]/g, '');
|
|
||||||
const parts = text.split('.');
|
|
||||||
const normalized =
|
|
||||||
parts.length > 1 ? `${parts[0]}.${parts.slice(1).join('').slice(0, 2)}` : parts[0];
|
|
||||||
this.form.contractAmount = normalized === '' ? null : normalized;
|
|
||||||
},
|
|
||||||
loadDictionaries() {
|
loadDictionaries() {
|
||||||
Promise.all([
|
Promise.all([
|
||||||
getSystemDictionary({ code: 'currency_type' }),
|
getSystemDictionary({ code: 'currency_type' }),
|
||||||
@@ -653,20 +619,6 @@ export default {
|
|||||||
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() : ''; },
|
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() : ''; },
|
||||||
isAttachmentImage(row) { return ['jpg', 'jpeg', 'png', 'gif', 'bmp', 'webp'].includes(this.attachmentExtension(row)); },
|
isAttachmentImage(row) { return ['jpg', 'jpeg', 'png', 'gif', 'bmp', 'webp'].includes(this.attachmentExtension(row)); },
|
||||||
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; },
|
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; },
|
||||||
handleAttachmentUploadToLocation({ location, rows = [] }) {
|
|
||||||
if (!rows.length) return;
|
|
||||||
if (location === '合同文件') {
|
|
||||||
this.contractFileRows = [...(this.contractFileRows || []), ...rows];
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (location === '其它附件') {
|
|
||||||
this.attachments = [...(this.attachments || []), ...rows];
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (location === '变更材料') {
|
|
||||||
this.changeMaterials = [...(this.changeMaterials || []), ...rows];
|
|
||||||
}
|
|
||||||
},
|
|
||||||
handlePreviewUnsupported() { this.$message.warning('当前文件暂不支持在线预览'); },
|
handlePreviewUnsupported() { this.$message.warning('当前文件暂不支持在线预览'); },
|
||||||
handlePreviewError() { this.$message.error('附件预览失败'); },
|
handlePreviewError() { this.$message.error('附件预览失败'); },
|
||||||
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`; },
|
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`; },
|
||||||
@@ -677,7 +629,7 @@ export default {
|
|||||||
addCustomPeriodRow() { const periods = normalizeCustomPeriods(this.settlementRule.customPeriods || []); periods.push({ startDay: '', endDay: '' }); this.settlementRule.customPeriods = periods; },
|
addCustomPeriodRow() { const periods = normalizeCustomPeriods(this.settlementRule.customPeriods || []); periods.push({ startDay: '', endDay: '' }); this.settlementRule.customPeriods = periods; },
|
||||||
removeCustomPeriodRow(index) { if (index <= 0) return; const periods = [...(this.settlementRule.customPeriods || [])]; periods.splice(index, 1); this.settlementRule.customPeriods = normalizeCustomPeriods(periods); },
|
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; },
|
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; },
|
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), 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) }); await submitMkApprovalFlow({ bizType: 'contract-manage', formInstanceId: this.form.id, subjectName: this.form.contractName || '', approvalStatus: this.form.approvalStatus || 'change_rejected' }); 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) }); await submitMkApprovalFlow({ bizType: 'contract-manage', formInstanceId: this.form.id, subjectName: this.form.contractName || '', approvalStatus: this.form.approvalStatus || 'change_rejected' }); this.$message.success('变更已提交'); this.$router.back(); },
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
@@ -702,14 +654,6 @@ export default {
|
|||||||
.contract-basic-section :deep(.el-input),
|
.contract-basic-section :deep(.el-input),
|
||||||
.contract-basic-section :deep(.el-select),
|
.contract-basic-section :deep(.el-select),
|
||||||
.contract-basic-section :deep(.el-date-editor) { width: 360px; max-width: 100%; }
|
.contract-basic-section :deep(.el-date-editor) { width: 360px; max-width: 100%; }
|
||||||
.contract-basic-section :deep(.el-input-number) {
|
|
||||||
width: 360px;
|
|
||||||
max-width: 100%;
|
|
||||||
|
|
||||||
.el-input__inner {
|
|
||||||
text-align: left;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.dialog-section-title { margin-bottom: 18px; font-size: 16px; font-weight: 600; }
|
.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::before { display: inline-block; width: 4px; height: 16px; margin-right: 8px; vertical-align: -2px; background: #409eff; content: ''; }
|
||||||
.section-head { display: flex; align-items: center; justify-content: space-between; }
|
.section-head { display: flex; align-items: center; justify-content: space-between; }
|
||||||
|
|||||||
@@ -1,21 +0,0 @@
|
|||||||
<template>
|
|
||||||
<mk-public-shell biz-type="contract-manage" :get-form="getForm">
|
|
||||||
<contract-manage ref="page" />
|
|
||||||
</mk-public-shell>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
import MkPublicShell from '@/views/mk/mk-public-shell.vue';
|
|
||||||
import ContractManage from '@/views/business/contract-manage.vue';
|
|
||||||
|
|
||||||
export default {
|
|
||||||
name: 'ContractManagePublicView',
|
|
||||||
components: { MkPublicShell, ContractManage },
|
|
||||||
methods: {
|
|
||||||
getForm() {
|
|
||||||
const row = this.$refs.page?.detailRow || {};
|
|
||||||
return { ...row, subject: row.contractName || row.contractNo || '' };
|
|
||||||
},
|
|
||||||
},
|
|
||||||
};
|
|
||||||
</script>
|
|
||||||
@@ -222,7 +222,7 @@
|
|||||||
Number(detailSettlementRule.autoGenerate) === 1 ? '开启' : '关闭'
|
Number(detailSettlementRule.autoGenerate) === 1 ? '开启' : '关闭'
|
||||||
}}</el-descriptions-item>
|
}}</el-descriptions-item>
|
||||||
<template v-if="Number(detailSettlementRule.autoGenerate) === 1">
|
<template v-if="Number(detailSettlementRule.autoGenerate) === 1">
|
||||||
<el-descriptions-item label="账单起始日">{{
|
<el-descriptions-item label="账单起始日期">{{
|
||||||
displayValue(detailSettlementRule.billStartDate)
|
displayValue(detailSettlementRule.billStartDate)
|
||||||
}}</el-descriptions-item>
|
}}</el-descriptions-item>
|
||||||
<el-descriptions-item label="结算类型">{{
|
<el-descriptions-item label="结算类型">{{
|
||||||
@@ -244,7 +244,7 @@
|
|||||||
}}</el-descriptions-item
|
}}</el-descriptions-item
|
||||||
>
|
>
|
||||||
<el-descriptions-item
|
<el-descriptions-item
|
||||||
v-if="isFixedCycleSettlementType(detailSettlementRule.settlementType)"
|
v-if="detailSettlementRule.settlementType === '固定天数周期结算'"
|
||||||
label="周期天数"
|
label="周期天数"
|
||||||
>{{
|
>{{
|
||||||
detailObjectUnitValue(detailSettlementRule, 'cycleDays', '天')
|
detailObjectUnitValue(detailSettlementRule, 'cycleDays', '天')
|
||||||
@@ -345,7 +345,7 @@
|
|||||||
</section>
|
</section>
|
||||||
</div>
|
</div>
|
||||||
<div class="contract-manage-page__footer">
|
<div class="contract-manage-page__footer">
|
||||||
<el-button v-if="!isPublicViewPage" @click="closeDetail">关闭</el-button>
|
<el-button @click="closeDetail">关闭</el-button>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@@ -478,7 +478,7 @@
|
|||||||
type="date"
|
type="date"
|
||||||
format="YYYY-MM-DD"
|
format="YYYY-MM-DD"
|
||||||
value-format="YYYY-MM-DD"
|
value-format="YYYY-MM-DD"
|
||||||
placeholder="请输入"
|
placeholder="YYYY-MM-DD"
|
||||||
/>
|
/>
|
||||||
<span>至</span>
|
<span>至</span>
|
||||||
<el-date-picker
|
<el-date-picker
|
||||||
@@ -486,7 +486,7 @@
|
|||||||
type="date"
|
type="date"
|
||||||
format="YYYY-MM-DD"
|
format="YYYY-MM-DD"
|
||||||
value-format="YYYY-MM-DD"
|
value-format="YYYY-MM-DD"
|
||||||
placeholder="请输入"
|
placeholder="YYYY-MM-DD"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
@@ -498,12 +498,12 @@
|
|||||||
><template #suffix>天</template></el-input>
|
><template #suffix>天</template></el-input>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
<el-form-item label="合同金额">
|
<el-form-item label="合同金额">
|
||||||
<el-input
|
<el-input-number
|
||||||
:model-value="contractAmountDisplay"
|
v-model="form.contractAmount"
|
||||||
|
:min="0"
|
||||||
|
:precision="2"
|
||||||
|
:controls="false"
|
||||||
placeholder="请输入"
|
placeholder="请输入"
|
||||||
clearable
|
|
||||||
@input="handleContractAmountInput"
|
|
||||||
@clear="form.contractAmount = null"
|
|
||||||
/>
|
/>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
<el-form-item label="是否范本">
|
<el-form-item label="是否范本">
|
||||||
@@ -592,13 +592,12 @@
|
|||||||
title="合同文件"
|
title="合同文件"
|
||||||
description
|
description
|
||||||
attachment-type
|
attachment-type
|
||||||
:attachment-location-options="contractAttachmentLocationOptions"
|
use-upload-dialog
|
||||||
:lock-approved="isAttachmentUploadMode || hasApprovedContractFiles"
|
:lock-approved="isAttachmentUploadMode || hasApprovedContractFiles"
|
||||||
:mark-approved-on-upload="isAttachmentUploadMode"
|
:mark-approved-on-upload="isAttachmentUploadMode"
|
||||||
:rows="contractFileRows"
|
:rows="contractFileRows"
|
||||||
:preview="previewAttachment"
|
:preview="previewAttachment"
|
||||||
@update:rows="contractFileRows = $event"
|
@update:rows="contractFileRows = $event"
|
||||||
@upload-to-location="handleAttachmentUploadToLocation"
|
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<section class="contract-manage-form__section contract-manage-form__section--panel">
|
<section class="contract-manage-form__section contract-manage-form__section--panel">
|
||||||
@@ -647,7 +646,7 @@
|
|||||||
content="开启时,系统根据配置规则归集运单,定时生成结算单"
|
content="开启时,系统根据配置规则归集运单,定时生成结算单"
|
||||||
placement="top"
|
placement="top"
|
||||||
>
|
>
|
||||||
<el-icon class="contract-manage-form__fee-mode-tip"><QuestionFilled /></el-icon>
|
<el-icon class="settlement-switch-tip"><QuestionFilled /></el-icon>
|
||||||
</el-tooltip>
|
</el-tooltip>
|
||||||
<el-radio :label="0">关闭</el-radio>
|
<el-radio :label="0">关闭</el-radio>
|
||||||
</el-radio-group>
|
</el-radio-group>
|
||||||
@@ -656,13 +655,13 @@
|
|||||||
v-if="settlementRuleEnabled"
|
v-if="settlementRuleEnabled"
|
||||||
class="contract-manage-form__grid contract-manage-form__settlement-grid"
|
class="contract-manage-form__grid contract-manage-form__settlement-grid"
|
||||||
>
|
>
|
||||||
<el-form-item label="账单起始日" required>
|
<el-form-item label="账单起始日期" required>
|
||||||
<el-date-picker
|
<el-date-picker
|
||||||
v-model="settlementRuleForm.billStartDate"
|
v-model="settlementRuleForm.billStartDate"
|
||||||
type="date"
|
type="date"
|
||||||
format="YYYY-MM-DD"
|
format="YYYY-MM-DD"
|
||||||
value-format="YYYY-MM-DD"
|
value-format="YYYY-MM-DD"
|
||||||
placeholder="请选择账单起始日"
|
placeholder="请选择账单起始日期"
|
||||||
/>
|
/>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
<el-form-item label="结算类型" required>
|
<el-form-item label="结算类型" required>
|
||||||
@@ -812,13 +811,12 @@
|
|||||||
title="其它附件"
|
title="其它附件"
|
||||||
description
|
description
|
||||||
attachment-type
|
attachment-type
|
||||||
|
use-upload-dialog
|
||||||
attachment-location="其它附件"
|
attachment-location="其它附件"
|
||||||
:attachment-location-options="contractAttachmentLocationOptions"
|
|
||||||
:readonly="isAttachmentUploadMode"
|
:readonly="isAttachmentUploadMode"
|
||||||
:rows="attachmentRows"
|
:rows="attachmentRows"
|
||||||
:preview="previewAttachment"
|
:preview="previewAttachment"
|
||||||
@update:rows="attachmentRows = $event"
|
@update:rows="attachmentRows = $event"
|
||||||
@upload-to-location="handleAttachmentUploadToLocation"
|
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<section
|
<section
|
||||||
@@ -923,21 +921,6 @@
|
|||||||
@save="saveBillingPlan"
|
@save="saveBillingPlan"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<contract-attachment-section
|
|
||||||
ref="listAttachmentUploader"
|
|
||||||
dialog-only
|
|
||||||
title="合同文件"
|
|
||||||
description
|
|
||||||
attachment-type
|
|
||||||
use-upload-dialog
|
|
||||||
mark-approved-on-upload
|
|
||||||
:attachment-location-options="['合同文件', '其它附件']"
|
|
||||||
:rows="listUploadContractFileRows"
|
|
||||||
@update:rows="listUploadContractFileRows = $event"
|
|
||||||
@upload-to-location="handleListAttachmentUploadToLocation"
|
|
||||||
@upload-confirmed="handleListAttachmentUploadConfirmed"
|
|
||||||
/>
|
|
||||||
|
|
||||||
<el-dialog
|
<el-dialog
|
||||||
v-model="attachmentDocumentPreviewVisible"
|
v-model="attachmentDocumentPreviewVisible"
|
||||||
:title="attachmentPreviewFile.name || '附件预览'"
|
:title="attachmentPreviewFile.name || '附件预览'"
|
||||||
@@ -979,10 +962,44 @@
|
|||||||
@close="attachmentImagePreviewVisible = false"
|
@close="attachmentImagePreviewVisible = false"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<change-record-detail-dialog
|
<el-dialog
|
||||||
v-model="detailChangeRecordVisible"
|
v-model="detailChangeRecordVisible"
|
||||||
:rows="detailChangeRecordDetailRows"
|
title="变更记录详情"
|
||||||
/>
|
append-to-body
|
||||||
|
destroy-on-close
|
||||||
|
width="1100px"
|
||||||
|
top="10px"
|
||||||
|
class="contract-change-record-detail-dialog"
|
||||||
|
>
|
||||||
|
<div v-if="detailChangeRecord" class="contract-change-record-detail-meta">
|
||||||
|
<span>经办人:{{ detailChangeRecord.handlerUserName || '-' }}</span>
|
||||||
|
<span>变更类型:{{ detailChangeRecord.changeType || '-' }}</span>
|
||||||
|
<span>状态:{{ detailChangeRecord.statusName || detailChangeRecord.status || '-' }}</span>
|
||||||
|
</div>
|
||||||
|
<el-table :data="detailChangeRecordDetailRows" border :show-overflow-tooltip="false">
|
||||||
|
<el-table-column prop="field" label="变更字段" min-width="180" />
|
||||||
|
<el-table-column
|
||||||
|
prop="before"
|
||||||
|
label="变更前"
|
||||||
|
min-width="360"
|
||||||
|
class-name="contract-change-record-detail-value"
|
||||||
|
/>
|
||||||
|
<el-table-column
|
||||||
|
prop="after"
|
||||||
|
label="变更后"
|
||||||
|
min-width="500"
|
||||||
|
class-name="contract-change-record-detail-value"
|
||||||
|
/>
|
||||||
|
</el-table>
|
||||||
|
<el-empty
|
||||||
|
v-if="!detailChangeRecordDetailRows.length"
|
||||||
|
description="暂无变更内容"
|
||||||
|
:image-size="60"
|
||||||
|
/>
|
||||||
|
<template #footer>
|
||||||
|
<el-button type="primary" @click="detailChangeRecordVisible = false">关闭</el-button>
|
||||||
|
</template>
|
||||||
|
</el-dialog>
|
||||||
|
|
||||||
<billing-plan-editor
|
<billing-plan-editor
|
||||||
v-model="detailBillingPlanBox"
|
v-model="detailBillingPlanBox"
|
||||||
@@ -1010,7 +1027,6 @@ import NProgress from 'nprogress';
|
|||||||
import 'nprogress/nprogress.css';
|
import 'nprogress/nprogress.css';
|
||||||
import { exportBlob } from '@/api/common';
|
import { exportBlob } from '@/api/common';
|
||||||
import * as api from '@/api/business/contract-manage';
|
import * as api from '@/api/business/contract-manage';
|
||||||
import { getMkPublicDetail } from '@/api/mk-process';
|
|
||||||
import { getList as getProjectList } from '@/api/business/project-apply';
|
import { getList as getProjectList } from '@/api/business/project-apply';
|
||||||
import { getList as getCustomerArchiveList } from '@/api/vehicle/customer-archive';
|
import { getList as getCustomerArchiveList } from '@/api/vehicle/customer-archive';
|
||||||
import { getDeptTree } from '@/api/system/dept';
|
import { getDeptTree } from '@/api/system/dept';
|
||||||
@@ -1021,7 +1037,6 @@ import { getToken } from '@/utils/auth';
|
|||||||
import { downloadFileByUrl, downloadXls } from '@/utils/util';
|
import { downloadFileByUrl, downloadXls } from '@/utils/util';
|
||||||
import { submitMkApprovalFlow } from '@/utils/mk-approval';
|
import { submitMkApprovalFlow } from '@/utils/mk-approval';
|
||||||
import BillingPlanEditor from './components/billing-plan-editor.vue';
|
import BillingPlanEditor from './components/billing-plan-editor.vue';
|
||||||
import ChangeRecordDetailDialog from '@/components/change-record-detail-dialog/main.vue';
|
|
||||||
import ContractAttachmentSection, {
|
import ContractAttachmentSection, {
|
||||||
attachmentName as sharedAttachmentName,
|
attachmentName as sharedAttachmentName,
|
||||||
attachmentUrl as sharedAttachmentUrl,
|
attachmentUrl as sharedAttachmentUrl,
|
||||||
@@ -1043,15 +1058,7 @@ const normalizeOptionalInteger = (value, emptyValue = null) => {
|
|||||||
return Number.isFinite(number) ? Math.trunc(number) : emptyValue;
|
return Number.isFinite(number) ? Math.trunc(number) : emptyValue;
|
||||||
};
|
};
|
||||||
const normalizeOptionalAmount = (value, emptyValue = null) => {
|
const normalizeOptionalAmount = (value, emptyValue = null) => {
|
||||||
if (
|
if (value === undefined || value === null || value === '') return emptyValue;
|
||||||
value === undefined ||
|
|
||||||
value === null ||
|
|
||||||
value === '' ||
|
|
||||||
Number(value) === -1 ||
|
|
||||||
String(value) === '-1'
|
|
||||||
) {
|
|
||||||
return emptyValue;
|
|
||||||
}
|
|
||||||
const number = Number(value);
|
const number = Number(value);
|
||||||
return Number.isFinite(number) && number >= 0 ? Number(number.toFixed(2)) : emptyValue;
|
return Number.isFinite(number) && number >= 0 ? Number(number.toFixed(2)) : emptyValue;
|
||||||
};
|
};
|
||||||
@@ -1093,7 +1100,7 @@ const defaultForm = () => ({
|
|||||||
settlementCurrency: '',
|
settlementCurrency: '',
|
||||||
invoiceCycle: '',
|
invoiceCycle: '',
|
||||||
paymentDays: '',
|
paymentDays: '',
|
||||||
contractAmount: null,
|
contractAmount: '',
|
||||||
templateFlag: 0,
|
templateFlag: 0,
|
||||||
originalContractNo: '',
|
originalContractNo: '',
|
||||||
electronicSealFlag: 0,
|
electronicSealFlag: 0,
|
||||||
@@ -1174,14 +1181,7 @@ const attachmentViewerPlugins = [
|
|||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'ContractManage',
|
name: 'ContractManage',
|
||||||
components: {
|
components: { ContractAttachmentSection, BillingPlanEditor, ElImageViewer, OpenFileViewer, PdfPreview },
|
||||||
ContractAttachmentSection,
|
|
||||||
BillingPlanEditor,
|
|
||||||
ChangeRecordDetailDialog,
|
|
||||||
ElImageViewer,
|
|
||||||
OpenFileViewer,
|
|
||||||
PdfPreview,
|
|
||||||
},
|
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
api,
|
api,
|
||||||
@@ -1249,9 +1249,6 @@ export default {
|
|||||||
organizationLoading: false,
|
organizationLoading: false,
|
||||||
contractFileRows: [],
|
contractFileRows: [],
|
||||||
attachmentRows: [],
|
attachmentRows: [],
|
||||||
listUploadContractFileRows: [],
|
|
||||||
listUploadAttachmentRows: [],
|
|
||||||
attachmentUploadFromList: false,
|
|
||||||
attachmentImagePreviewVisible: false,
|
attachmentImagePreviewVisible: false,
|
||||||
attachmentImagePreviewUrls: [],
|
attachmentImagePreviewUrls: [],
|
||||||
attachmentImagePreviewIndex: 0,
|
attachmentImagePreviewIndex: 0,
|
||||||
@@ -1275,7 +1272,7 @@ export default {
|
|||||||
formalSettlementRuleForm: defaultSettlementRule(),
|
formalSettlementRuleForm: defaultSettlementRule(),
|
||||||
paymentRatioRows: [],
|
paymentRatioRows: [],
|
||||||
changeRecordRows: [],
|
changeRecordRows: [],
|
||||||
settlementTypeOptions: ['现结', '日结', '周结', '月结', '按固定天数周期'],
|
settlementTypeOptions: ['月结', '日结', '周结', '半月结', '固定天数周期结算'],
|
||||||
billCycleTypeOptions: ['固定截单日', '自然月', '自定义多周期'],
|
billCycleTypeOptions: ['固定截单日', '自然月', '自定义多周期'],
|
||||||
detailLoading: false,
|
detailLoading: false,
|
||||||
detailRow: {},
|
detailRow: {},
|
||||||
@@ -1319,12 +1316,7 @@ export default {
|
|||||||
return this.$route.path === '/business/contract-manage/form';
|
return this.$route.path === '/business/contract-manage/form';
|
||||||
},
|
},
|
||||||
isDetailPage() {
|
isDetailPage() {
|
||||||
return (
|
return this.$route.path === '/business/contract-manage/detail';
|
||||||
this.$route.path === '/business/contract-manage/detail' || this.isPublicViewPage
|
|
||||||
);
|
|
||||||
},
|
|
||||||
isPublicViewPage() {
|
|
||||||
return this.$route.path === '/business/contract-manage/public-view';
|
|
||||||
},
|
},
|
||||||
formMode() {
|
formMode() {
|
||||||
return this.$route.query.mode === 'edit' ? 'edit' : 'add';
|
return this.$route.query.mode === 'edit' ? 'edit' : 'add';
|
||||||
@@ -1339,23 +1331,6 @@ export default {
|
|||||||
isAttachmentUploadMode() {
|
isAttachmentUploadMode() {
|
||||||
return this.$route.query.attachmentUpload === '1';
|
return this.$route.query.attachmentUpload === '1';
|
||||||
},
|
},
|
||||||
contractAttachmentLocationOptions() {
|
|
||||||
if (this.isAttachmentUploadMode) return ['合同文件'];
|
|
||||||
return ['合同文件', '其它附件'];
|
|
||||||
},
|
|
||||||
contractAmountDisplay() {
|
|
||||||
const value = this.form.contractAmount;
|
|
||||||
if (
|
|
||||||
value === undefined ||
|
|
||||||
value === null ||
|
|
||||||
value === '' ||
|
|
||||||
Number(value) === -1 ||
|
|
||||||
String(value) === '-1'
|
|
||||||
) {
|
|
||||||
return '';
|
|
||||||
}
|
|
||||||
return value;
|
|
||||||
},
|
|
||||||
hasApprovedContractFiles() {
|
hasApprovedContractFiles() {
|
||||||
return (this.contractFileRows || []).some(
|
return (this.contractFileRows || []).some(
|
||||||
row =>
|
row =>
|
||||||
@@ -1437,7 +1412,7 @@ export default {
|
|||||||
return this.showBillCycleType && this.settlementRuleForm.billCycleType === '自定义多周期';
|
return this.showBillCycleType && this.settlementRuleForm.billCycleType === '自定义多周期';
|
||||||
},
|
},
|
||||||
showCycleDays() {
|
showCycleDays() {
|
||||||
return this.isFixedCycleSettlementType(this.settlementRuleForm.settlementType);
|
return this.settlementRuleForm.settlementType === '固定天数周期结算';
|
||||||
},
|
},
|
||||||
billCutoffDayOptions() {
|
billCutoffDayOptions() {
|
||||||
return Array.from({ length: 31 }, (_, index) => ({
|
return Array.from({ length: 31 }, (_, index) => ({
|
||||||
@@ -1498,11 +1473,9 @@ export default {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
created() {
|
created() {
|
||||||
if (!this.isPublicViewPage) {
|
this.loadProjectOptions();
|
||||||
this.loadProjectOptions();
|
this.loadSettlementDictionaries();
|
||||||
this.loadSettlementDictionaries();
|
this.loadOrganizationOptions();
|
||||||
this.loadOrganizationOptions();
|
|
||||||
}
|
|
||||||
if (this.isFormPage) this.initFormPage();
|
if (this.isFormPage) this.initFormPage();
|
||||||
if (this.isDetailPage) this.initDetailPage();
|
if (this.isDetailPage) this.initDetailPage();
|
||||||
},
|
},
|
||||||
@@ -1702,7 +1675,7 @@ export default {
|
|||||||
copyCount: normalizeOptionalInteger(detail.copyCount, ''),
|
copyCount: normalizeOptionalInteger(detail.copyCount, ''),
|
||||||
invoiceCycle: normalizeOptionalInteger(detail.invoiceCycle, ''),
|
invoiceCycle: normalizeOptionalInteger(detail.invoiceCycle, ''),
|
||||||
paymentDays: normalizeOptionalInteger(detail.paymentDays, ''),
|
paymentDays: normalizeOptionalInteger(detail.paymentDays, ''),
|
||||||
contractAmount: normalizeOptionalAmount(detail.contractAmount, null),
|
contractAmount: normalizeOptionalAmount(detail.contractAmount, ''),
|
||||||
archiveStatus: detail.archiveStatus || '未归档',
|
archiveStatus: detail.archiveStatus || '未归档',
|
||||||
feeGenerationMode:
|
feeGenerationMode:
|
||||||
detail.feeGenerationMode || (Number(detail.billingEnabled) === 0 ? 'manual' : 'system'),
|
detail.feeGenerationMode || (Number(detail.billingEnabled) === 0 ? 'manual' : 'system'),
|
||||||
@@ -1915,86 +1888,20 @@ export default {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
this.submitAction = 'attachment';
|
this.submitAction = 'attachment';
|
||||||
const contractFileRows = this.attachmentUploadFromList
|
this.api
|
||||||
? this.listUploadContractFileRows
|
|
||||||
: this.contractFileRows;
|
|
||||||
const attachmentRows = this.attachmentUploadFromList
|
|
||||||
? this.listUploadAttachmentRows
|
|
||||||
: this.attachmentRows;
|
|
||||||
return this.api
|
|
||||||
.updateAttachments({
|
.updateAttachments({
|
||||||
id: this.form.id,
|
id: this.form.id,
|
||||||
contractFileJson: JSON.stringify(contractFileRows),
|
contractFileJson: JSON.stringify(this.contractFileRows),
|
||||||
attachmentsJson: JSON.stringify(attachmentRows),
|
attachmentsJson: JSON.stringify(this.attachmentRows),
|
||||||
})
|
})
|
||||||
.then(() => {
|
.then(() => {
|
||||||
this.$message.success('附件保存成功');
|
this.$message.success('附件保存成功');
|
||||||
if (this.attachmentUploadFromList) {
|
|
||||||
this.attachmentUploadFromList = false;
|
|
||||||
this.listUploadContractFileRows = [];
|
|
||||||
this.listUploadAttachmentRows = [];
|
|
||||||
this.onLoad(this.page, this.query);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
this.closeForm();
|
this.closeForm();
|
||||||
})
|
})
|
||||||
.finally(() => {
|
.finally(() => {
|
||||||
this.submitAction = '';
|
this.submitAction = '';
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
openAttachmentUploadDialog(row = {}) {
|
|
||||||
if (!row?.id) {
|
|
||||||
this.$message.warning('合同不存在,无法上传附件');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const loading = this.$loading({
|
|
||||||
lock: true,
|
|
||||||
text: '加载中',
|
|
||||||
background: 'rgba(255, 255, 255, 0.6)',
|
|
||||||
});
|
|
||||||
this.attachmentUploadFromList = true;
|
|
||||||
this.listUploadContractFileRows = [];
|
|
||||||
this.listUploadAttachmentRows = [];
|
|
||||||
this.form = { ...defaultForm(), id: row.id };
|
|
||||||
this.api
|
|
||||||
.getDetail(row.id)
|
|
||||||
.then(res => {
|
|
||||||
const detail = res.data?.data || {};
|
|
||||||
this.form = {
|
|
||||||
...defaultForm(),
|
|
||||||
...detail,
|
|
||||||
id: detail.id || row.id,
|
|
||||||
};
|
|
||||||
this.listUploadContractFileRows = normalizeContractFileRows(
|
|
||||||
parseArray(detail.contractFileJson)
|
|
||||||
);
|
|
||||||
this.listUploadAttachmentRows = parseArray(detail.attachmentsJson);
|
|
||||||
this.$nextTick(() => {
|
|
||||||
this.$refs.listAttachmentUploader?.openUploadDialog?.();
|
|
||||||
});
|
|
||||||
})
|
|
||||||
.catch(() => {
|
|
||||||
this.attachmentUploadFromList = false;
|
|
||||||
this.$message.error('合同详情加载失败');
|
|
||||||
})
|
|
||||||
.finally(() => {
|
|
||||||
loading.close();
|
|
||||||
});
|
|
||||||
},
|
|
||||||
handleListAttachmentUploadConfirmed() {
|
|
||||||
if (!this.attachmentUploadFromList) return;
|
|
||||||
this.submitAttachmentUpload();
|
|
||||||
},
|
|
||||||
handleListAttachmentUploadToLocation({ location, rows = [] }) {
|
|
||||||
if (!rows.length) return;
|
|
||||||
if (location === '合同文件') {
|
|
||||||
this.listUploadContractFileRows = [...(this.listUploadContractFileRows || []), ...rows];
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (location === '其它附件') {
|
|
||||||
this.listUploadAttachmentRows = [...(this.listUploadAttachmentRows || []), ...rows];
|
|
||||||
}
|
|
||||||
},
|
|
||||||
loadProjectOptions() {
|
loadProjectOptions() {
|
||||||
if (this.projectLoading) return;
|
if (this.projectLoading) return;
|
||||||
this.projectLoading = true;
|
this.projectLoading = true;
|
||||||
@@ -2211,13 +2118,6 @@ export default {
|
|||||||
integerInput(prop, value) {
|
integerInput(prop, value) {
|
||||||
this.form[prop] = String(value || '').replace(/\D/g, '');
|
this.form[prop] = String(value || '').replace(/\D/g, '');
|
||||||
},
|
},
|
||||||
handleContractAmountInput(value) {
|
|
||||||
const text = String(value || '').replace(/[^\d.]/g, '');
|
|
||||||
const parts = text.split('.');
|
|
||||||
const normalized =
|
|
||||||
parts.length > 1 ? `${parts[0]}.${parts.slice(1).join('').slice(0, 2)}` : parts[0];
|
|
||||||
this.form.contractAmount = normalized === '' ? null : normalized;
|
|
||||||
},
|
|
||||||
positiveIntegerInput(prop, value) {
|
positiveIntegerInput(prop, value) {
|
||||||
const normalized = String(value || '').replace(/\D/g, '').replace(/^0+/, '');
|
const normalized = String(value || '').replace(/\D/g, '').replace(/^0+/, '');
|
||||||
this.form[prop] = normalized;
|
this.form[prop] = normalized;
|
||||||
@@ -2248,16 +2148,6 @@ export default {
|
|||||||
};
|
};
|
||||||
this.attachmentDocumentPreviewVisible = true;
|
this.attachmentDocumentPreviewVisible = true;
|
||||||
},
|
},
|
||||||
handleAttachmentUploadToLocation({ location, rows = [] }) {
|
|
||||||
if (!rows.length) return;
|
|
||||||
if (location === '合同文件') {
|
|
||||||
this.contractFileRows = [...(this.contractFileRows || []), ...rows];
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (location === '其它附件') {
|
|
||||||
this.attachmentRows = [...(this.attachmentRows || []), ...rows];
|
|
||||||
}
|
|
||||||
},
|
|
||||||
handleAttachmentPreviewUnsupported() {
|
handleAttachmentPreviewUnsupported() {
|
||||||
this.$message.warning('当前文件暂不支持在线预览');
|
this.$message.warning('当前文件暂不支持在线预览');
|
||||||
},
|
},
|
||||||
@@ -2316,12 +2206,8 @@ export default {
|
|||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
},
|
},
|
||||||
isFixedCycleSettlementType(value) {
|
|
||||||
return value === '按固定天数周期' || value === '固定天数周期结算';
|
|
||||||
},
|
|
||||||
normalizeSettlementRule(rule = {}) {
|
normalizeSettlementRule(rule = {}) {
|
||||||
const next = { ...defaultSettlementRule(), ...rule };
|
const next = { ...defaultSettlementRule(), ...rule };
|
||||||
if (next.settlementType === '固定天数周期结算') next.settlementType = '按固定天数周期';
|
|
||||||
if (next.settlementType !== '月结') {
|
if (next.settlementType !== '月结') {
|
||||||
next.billCycleType = '';
|
next.billCycleType = '';
|
||||||
next.billCutoffDay = '';
|
next.billCutoffDay = '';
|
||||||
@@ -2336,7 +2222,7 @@ export default {
|
|||||||
} else {
|
} else {
|
||||||
next.customPeriods = [];
|
next.customPeriods = [];
|
||||||
}
|
}
|
||||||
if (!this.isFixedCycleSettlementType(next.settlementType)) next.cycleDays = '';
|
if (next.settlementType !== '固定天数周期结算') next.cycleDays = '';
|
||||||
return next;
|
return next;
|
||||||
},
|
},
|
||||||
syncSettlementConfig() {
|
syncSettlementConfig() {
|
||||||
@@ -2359,7 +2245,7 @@ export default {
|
|||||||
this.settlementRuleForm.billCycleType = '';
|
this.settlementRuleForm.billCycleType = '';
|
||||||
this.settlementRuleForm.billCutoffDay = '';
|
this.settlementRuleForm.billCutoffDay = '';
|
||||||
this.settlementRuleForm.customPeriods = [];
|
this.settlementRuleForm.customPeriods = [];
|
||||||
if (!this.isFixedCycleSettlementType(value)) this.settlementRuleForm.cycleDays = '';
|
if (value !== '固定天数周期结算') this.settlementRuleForm.cycleDays = '';
|
||||||
},
|
},
|
||||||
handleBillCycleTypeChange(value) {
|
handleBillCycleTypeChange(value) {
|
||||||
if (value !== '固定截单日') this.settlementRuleForm.billCutoffDay = '';
|
if (value !== '固定截单日') this.settlementRuleForm.billCutoffDay = '';
|
||||||
@@ -2450,7 +2336,7 @@ export default {
|
|||||||
validateSettlementRule(rule, label) {
|
validateSettlementRule(rule, label) {
|
||||||
if (Number(rule.autoGenerate) !== 1) return true;
|
if (Number(rule.autoGenerate) !== 1) return true;
|
||||||
if (!rule.billStartDate || !rule.settlementType) {
|
if (!rule.billStartDate || !rule.settlementType) {
|
||||||
this.$message.warning(`${label}:请完整填写账单起始日和结算类型`);
|
this.$message.warning(`${label}:请完整填写账单起始日期和结算类型`);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
if (rule.settlementType === '月结' && !rule.billCycleType) {
|
if (rule.settlementType === '月结' && !rule.billCycleType) {
|
||||||
@@ -2468,7 +2354,7 @@ export default {
|
|||||||
if (rule.settlementType === '月结' && rule.billCycleType === '自定义多周期') {
|
if (rule.settlementType === '月结' && rule.billCycleType === '自定义多周期') {
|
||||||
return this.validateCustomPeriods(rule.customPeriods, label);
|
return this.validateCustomPeriods(rule.customPeriods, label);
|
||||||
}
|
}
|
||||||
if (this.isFixedCycleSettlementType(rule.settlementType) && !rule.cycleDays) {
|
if (rule.settlementType === '固定天数周期结算' && !rule.cycleDays) {
|
||||||
this.$message.warning(`${label}:请选择周期天数`);
|
this.$message.warning(`${label}:请选择周期天数`);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -2520,10 +2406,8 @@ export default {
|
|||||||
if (!id) return;
|
if (!id) return;
|
||||||
this.detailLoading = true;
|
this.detailLoading = true;
|
||||||
this.applyDetailState({});
|
this.applyDetailState({});
|
||||||
const request = this.isPublicViewPage
|
this.api
|
||||||
? getMkPublicDetail('contract-manage', id)
|
.getDetail(id)
|
||||||
: this.api.getDetail(id);
|
|
||||||
request
|
|
||||||
.then(res => {
|
.then(res => {
|
||||||
this.applyDetailState(res.data?.data || {});
|
this.applyDetailState(res.data?.data || {});
|
||||||
})
|
})
|
||||||
@@ -2692,7 +2576,7 @@ export default {
|
|||||||
.join(',')}`
|
.join(',')}`
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
if (config.billStartDate) parts.push(`账单起始日:${config.billStartDate}`);
|
if (config.billStartDate) parts.push(`账单起始日期:${config.billStartDate}`);
|
||||||
return parts.join(',');
|
return parts.join(',');
|
||||||
};
|
};
|
||||||
if (normalized.preSettlementConfig || normalized.formalSettlementConfig) {
|
if (normalized.preSettlementConfig || normalized.formalSettlementConfig) {
|
||||||
@@ -2788,15 +2672,6 @@ export default {
|
|||||||
},
|
},
|
||||||
detailValue(prop) {
|
detailValue(prop) {
|
||||||
const value = this.detailRow[prop];
|
const value = this.detailRow[prop];
|
||||||
if (prop === 'contractAmount') {
|
|
||||||
return value === undefined ||
|
|
||||||
value === null ||
|
|
||||||
value === '' ||
|
|
||||||
Number(value) === -1 ||
|
|
||||||
String(value) === '-1'
|
|
||||||
? '-'
|
|
||||||
: value;
|
|
||||||
}
|
|
||||||
if (
|
if (
|
||||||
prop === 'contractStage' ||
|
prop === 'contractStage' ||
|
||||||
prop === 'approvalStatus' ||
|
prop === 'approvalStatus' ||
|
||||||
@@ -2831,7 +2706,15 @@ export default {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (operation.action === 'attachmentUpload') {
|
if (operation.action === 'attachmentUpload') {
|
||||||
this.openAttachmentUploadDialog(row);
|
this.$router.push({
|
||||||
|
path: '/business/contract-manage/form',
|
||||||
|
query: {
|
||||||
|
mode: 'edit',
|
||||||
|
id: row.id,
|
||||||
|
name: '附件上传',
|
||||||
|
attachmentUpload: '1',
|
||||||
|
},
|
||||||
|
});
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const run = value => {
|
const run = value => {
|
||||||
@@ -3017,6 +2900,25 @@ export default {
|
|||||||
word-break: break-word;
|
word-break: break-word;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.contract-change-record-detail-meta {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: 8px 32px;
|
||||||
|
margin-bottom: 16px;
|
||||||
|
color: #606266;
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.contract-change-record-detail-dialog .el-dialog__body) {
|
||||||
|
max-height: 65vh;
|
||||||
|
overflow: auto;
|
||||||
|
padding-top: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.contract-change-record-detail-dialog .contract-change-record-detail-value .cell) {
|
||||||
|
white-space: pre-wrap;
|
||||||
|
word-break: break-all;
|
||||||
|
}
|
||||||
|
|
||||||
:global(.avue--collapse .contract-manage-page__footer) {
|
:global(.avue--collapse .contract-manage-page__footer) {
|
||||||
left: 60px;
|
left: 60px;
|
||||||
}
|
}
|
||||||
@@ -3052,10 +2954,6 @@ export default {
|
|||||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.04);
|
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.04);
|
||||||
}
|
}
|
||||||
|
|
||||||
&--attachment {
|
|
||||||
overflow: visible;
|
|
||||||
}
|
|
||||||
|
|
||||||
> .dialog-section-title,
|
> .dialog-section-title,
|
||||||
:deep(.dialog-section-title) {
|
:deep(.dialog-section-title) {
|
||||||
margin-bottom: 20px;
|
margin-bottom: 20px;
|
||||||
@@ -3120,6 +3018,13 @@ export default {
|
|||||||
align-items: center;
|
align-items: center;
|
||||||
gap: 20px;
|
gap: 20px;
|
||||||
margin-bottom: 16px;
|
margin-bottom: 16px;
|
||||||
|
|
||||||
|
.settlement-switch-tip {
|
||||||
|
margin: 0 4px;
|
||||||
|
color: #a8abb2;
|
||||||
|
cursor: help;
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
&__settlement-grid {
|
&__settlement-grid {
|
||||||
@@ -3177,19 +3082,11 @@ export default {
|
|||||||
min-width: 0;
|
min-width: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
:deep(.el-input-number) {
|
|
||||||
width: 360px;
|
|
||||||
max-width: 100%;
|
|
||||||
|
|
||||||
.el-input__inner {
|
|
||||||
text-align: left;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
:deep(.el-input),
|
:deep(.el-input),
|
||||||
:deep(.el-select),
|
:deep(.el-select),
|
||||||
:deep(.el-cascader),
|
:deep(.el-cascader),
|
||||||
:deep(.el-tree-select),
|
:deep(.el-tree-select),
|
||||||
|
:deep(.el-input-number),
|
||||||
:deep(.el-date-editor) {
|
:deep(.el-date-editor) {
|
||||||
width: 360px;
|
width: 360px;
|
||||||
max-width: 100%;
|
max-width: 100%;
|
||||||
|
|||||||
@@ -289,53 +289,29 @@
|
|||||||
<el-table-column label="承运类型" prop="carrierType" min-width="120" show-overflow-tooltip />
|
<el-table-column label="承运类型" prop="carrierType" min-width="120" show-overflow-tooltip />
|
||||||
<el-table-column label="发货地" prop="departureAddress" min-width="180">
|
<el-table-column label="发货地" prop="departureAddress" min-width="180">
|
||||||
<template #default="{ row }">
|
<template #default="{ row }">
|
||||||
<el-tooltip
|
<el-tooltip :content="formatFullRouteAddress(row, 'departure')" placement="top">
|
||||||
v-if="formatFullRouteAddress(row, 'departure')"
|
|
||||||
:content="formatFullRouteAddress(row, 'departure')"
|
|
||||||
placement="top"
|
|
||||||
:show-after="120"
|
|
||||||
>
|
|
||||||
<div class="loading-manage-page__address-cell">
|
<div class="loading-manage-page__address-cell">
|
||||||
{{ formatRouteAddress(row, 'departure') }}
|
{{ formatRouteAddress(row, 'departure') }}
|
||||||
</div>
|
</div>
|
||||||
</el-tooltip>
|
</el-tooltip>
|
||||||
<div v-else class="loading-manage-page__address-cell">
|
|
||||||
{{ formatRouteAddress(row, 'departure') }}
|
|
||||||
</div>
|
|
||||||
</template>
|
</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
<el-table-column label="途经地" prop="transitAddress" min-width="190">
|
<el-table-column label="途经地" prop="transitAddress" min-width="190">
|
||||||
<template #default="{ row }">
|
<template #default="{ row }">
|
||||||
<el-tooltip
|
<el-tooltip :content="formatFullRouteAddress(row, 'transit')" placement="top">
|
||||||
v-if="formatFullRouteAddress(row, 'transit')"
|
|
||||||
:content="formatFullRouteAddress(row, 'transit')"
|
|
||||||
placement="top"
|
|
||||||
:show-after="120"
|
|
||||||
>
|
|
||||||
<div class="loading-manage-page__address-cell">
|
<div class="loading-manage-page__address-cell">
|
||||||
{{ formatRouteAddress(row, 'transit') }}
|
{{ formatRouteAddress(row, 'transit') }}
|
||||||
</div>
|
</div>
|
||||||
</el-tooltip>
|
</el-tooltip>
|
||||||
<div v-else class="loading-manage-page__address-cell">
|
|
||||||
{{ formatRouteAddress(row, 'transit') }}
|
|
||||||
</div>
|
|
||||||
</template>
|
</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
<el-table-column label="到货地" prop="arrivalAddress" min-width="180">
|
<el-table-column label="到货地" prop="arrivalAddress" min-width="180">
|
||||||
<template #default="{ row }">
|
<template #default="{ row }">
|
||||||
<el-tooltip
|
<el-tooltip :content="formatFullRouteAddress(row, 'arrival')" placement="top">
|
||||||
v-if="formatFullRouteAddress(row, 'arrival')"
|
|
||||||
:content="formatFullRouteAddress(row, 'arrival')"
|
|
||||||
placement="top"
|
|
||||||
:show-after="120"
|
|
||||||
>
|
|
||||||
<div class="loading-manage-page__address-cell">
|
<div class="loading-manage-page__address-cell">
|
||||||
{{ formatRouteAddress(row, 'arrival') }}
|
{{ formatRouteAddress(row, 'arrival') }}
|
||||||
</div>
|
</div>
|
||||||
</el-tooltip>
|
</el-tooltip>
|
||||||
<div v-else class="loading-manage-page__address-cell">
|
|
||||||
{{ formatRouteAddress(row, 'arrival') }}
|
|
||||||
</div>
|
|
||||||
</template>
|
</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
<el-table-column label="承运商" prop="carrierName" min-width="160" show-overflow-tooltip />
|
<el-table-column label="承运商" prop="carrierName" min-width="160" show-overflow-tooltip />
|
||||||
@@ -410,74 +386,32 @@
|
|||||||
v-if="isStandalonePage"
|
v-if="isStandalonePage"
|
||||||
class="loading-manage-dialog loading-manage-form-dialog loading-manage-form-page"
|
class="loading-manage-dialog loading-manage-form-dialog loading-manage-form-page"
|
||||||
>
|
>
|
||||||
<div v-if="!isStandaloneDetailPage" class="archive-page-form__title">{{ formPageTitle }}</div>
|
<div class="archive-page-form__title">{{
|
||||||
|
isStandaloneDetailPage ? detailPageTitle : formPageTitle
|
||||||
|
}}</div>
|
||||||
<div v-loading="dialogLoading" class="loading-manage-dialog__body">
|
<div v-loading="dialogLoading" class="loading-manage-dialog__body">
|
||||||
<div v-if="dialogReadonly" class="loading-detail">
|
<div v-if="dialogReadonly" class="loading-detail">
|
||||||
<section-card title="配载单详情">
|
<section-card title="配载单详情">
|
||||||
<div class="loading-detail__summary">
|
<div class="loading-detail__summary">
|
||||||
|
<div class="loading-detail__heading">配载单详情</div>
|
||||||
<div>{{ dialogForm.loadingNo || '-' }}</div>
|
<div>{{ dialogForm.loadingNo || '-' }}</div>
|
||||||
<el-tag :type="detailStatusType">{{ statusText(dialogForm) }}</el-tag>
|
<div>
|
||||||
<el-tag type="primary">{{ transportTypeLabel(dialogForm.transportType) }}</el-tag>
|
<el-tag :type="detailStatusType" class="status-text">{{
|
||||||
|
statusText(dialogForm)
|
||||||
|
}}</el-tag>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<el-tag type="warning">{{ transportTypeLabel(dialogForm.transportType) }}</el-tag>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="loading-detail__task-row">
|
<div class="loading-detail__task-row">
|
||||||
<div class="loading-detail__task-main">
|
<div class="loading-detail__field">
|
||||||
<span class="loading-detail__label">任务信息</span>
|
<span class="loading-detail__label">任务信息</span
|
||||||
<div class="loading-detail__task-grid">
|
><span class="loading-detail__value">{{ taskSummary }}</span>
|
||||||
<div class="loading-detail__field">
|
|
||||||
<span class="loading-detail__label">承运商</span>
|
|
||||||
<span class="loading-detail__value">{{ detailTaskField('carrierName') }}</span>
|
|
||||||
</div>
|
|
||||||
<div class="loading-detail__field">
|
|
||||||
<span class="loading-detail__label">司机</span>
|
|
||||||
<span class="loading-detail__value">{{ detailTaskField('driverName') }}</span>
|
|
||||||
</div>
|
|
||||||
<div class="loading-detail__field">
|
|
||||||
<span class="loading-detail__label">车牌号</span>
|
|
||||||
<span class="loading-detail__value">{{ detailTaskField('vehicleNo') }}</span>
|
|
||||||
</div>
|
|
||||||
<div class="loading-detail__field">
|
|
||||||
<span class="loading-detail__label">手机号</span>
|
|
||||||
<span class="loading-detail__value">{{ detailTaskField('driverPhone') }}</span>
|
|
||||||
</div>
|
|
||||||
<div class="loading-detail__field">
|
|
||||||
<span class="loading-detail__label">里程</span>
|
|
||||||
<span class="loading-detail__value">{{
|
|
||||||
detailTaskMileage ? `${detailTaskMileage} km` : '-'
|
|
||||||
}}</span>
|
|
||||||
</div>
|
|
||||||
<div class="loading-detail__field">
|
|
||||||
<span class="loading-detail__label">预计发货日期</span>
|
|
||||||
<span class="loading-detail__value">{{
|
|
||||||
detailTaskDate('estimatedStartDate')
|
|
||||||
}}</span>
|
|
||||||
</div>
|
|
||||||
<div class="loading-detail__field">
|
|
||||||
<span class="loading-detail__label">预计完成日期</span>
|
|
||||||
<span class="loading-detail__value">{{
|
|
||||||
detailTaskDate('estimatedEndDate')
|
|
||||||
}}</span>
|
|
||||||
</div>
|
|
||||||
<template v-if="isSelfCarrierDetail">
|
|
||||||
<div class="loading-detail__field">
|
|
||||||
<span class="loading-detail__label">挂车车牌号</span>
|
|
||||||
<span class="loading-detail__value">{{
|
|
||||||
detailTaskField('trailerVehicleNo')
|
|
||||||
}}</span>
|
|
||||||
</div>
|
|
||||||
<div class="loading-detail__field">
|
|
||||||
<span class="loading-detail__label">押运人</span>
|
|
||||||
<span class="loading-detail__value">{{ detailTaskField('escortName') }}</span>
|
|
||||||
</div>
|
|
||||||
<div class="loading-detail__field">
|
|
||||||
<span class="loading-detail__label">押运人手机号</span>
|
|
||||||
<span class="loading-detail__value">{{ detailTaskField('escortPhone') }}</span>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
<div class="loading-detail__field loading-detail__attachment-field">
|
<div class="loading-detail__field">
|
||||||
<span class="loading-detail__label">附件</span>
|
<span class="loading-detail__label">附件</span
|
||||||
<span class="loading-detail__value"
|
><span class="loading-detail__value"
|
||||||
><el-link
|
><el-link
|
||||||
v-for="file in detailAttachments"
|
v-for="file in detailAttachments"
|
||||||
:key="file.url || file.name"
|
:key="file.url || file.name"
|
||||||
@@ -513,19 +447,7 @@
|
|||||||
>
|
>
|
||||||
</div>
|
</div>
|
||||||
<div class="loading-detail__step-address">
|
<div class="loading-detail__step-address">
|
||||||
<el-tooltip
|
{{ node.displayAddress || formatAddressText(node.address) }}
|
||||||
v-if="formatDetailRouteTooltip(node)"
|
|
||||||
:content="formatDetailRouteTooltip(node)"
|
|
||||||
placement="top"
|
|
||||||
:show-after="120"
|
|
||||||
>
|
|
||||||
<span class="loading-detail__step-address-text">{{
|
|
||||||
formatDetailRouteShort(node)
|
|
||||||
}}</span>
|
|
||||||
</el-tooltip>
|
|
||||||
<span v-else class="loading-detail__step-address-text">{{
|
|
||||||
formatDetailRouteShort(node)
|
|
||||||
}}</span>
|
|
||||||
</div>
|
</div>
|
||||||
<div class="loading-detail__step-tags">
|
<div class="loading-detail__step-tags">
|
||||||
<el-tag v-for="tag in node.tags || []" :key="tag" size="small">{{ tag }}</el-tag>
|
<el-tag v-for="tag in node.tags || []" :key="tag" size="small">{{ tag }}</el-tag>
|
||||||
@@ -1259,10 +1181,9 @@
|
|||||||
</el-form-item>
|
</el-form-item>
|
||||||
<el-form-item label="里程(km)">
|
<el-form-item label="里程(km)">
|
||||||
<el-input
|
<el-input
|
||||||
:model-value="normalizeLoadingMileage(dialogForm.mileage)"
|
v-model="dialogForm.mileage"
|
||||||
clearable
|
clearable
|
||||||
placeholder="请输入"
|
placeholder="请输入"
|
||||||
:disabled="dialogReadonly"
|
|
||||||
@input="handleMileageInput"
|
@input="handleMileageInput"
|
||||||
/>
|
/>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
@@ -1493,7 +1414,6 @@ import { getList as getContractList } from '@/api/business/contract-manage';
|
|||||||
import { getParentOptions as getCargoTypeOptions } from '@/api/base/cargo-type';
|
import { getParentOptions as getCargoTypeOptions } from '@/api/base/cargo-type';
|
||||||
import { getList as getCustomerList } from '@/api/vehicle/customer-archive';
|
import { getList as getCustomerList } from '@/api/vehicle/customer-archive';
|
||||||
import { getList as getDriverList } from '@/api/transportCapacity/driver';
|
import { getList as getDriverList } from '@/api/transportCapacity/driver';
|
||||||
import { fetchDriversByCarrierOrganizations } from '@/utils/carrier-org-resource';
|
|
||||||
import { getList as getProcessConfigList } from '@/api/business/process-config';
|
import { getList as getProcessConfigList } from '@/api/business/process-config';
|
||||||
import { getList as getCommonAddressList } from '@/api/base/common-address';
|
import { getList as getCommonAddressList } from '@/api/base/common-address';
|
||||||
import {
|
import {
|
||||||
@@ -1513,7 +1433,6 @@ const defaultCandidateTransportType = '';
|
|||||||
const AMAP_KEY = '653b7cf105ad7fb8ec9b2f5198ade315';
|
const AMAP_KEY = '653b7cf105ad7fb8ec9b2f5198ade315';
|
||||||
const AMAP_SECURITY_CODE = '5aab65c632e48ebae0d0e28f5b28e21a';
|
const AMAP_SECURITY_CODE = '5aab65c632e48ebae0d0e28f5b28e21a';
|
||||||
const loadingCreateWaybillsStorageKey = 'loading-manage-create-waybills';
|
const loadingCreateWaybillsStorageKey = 'loading-manage-create-waybills';
|
||||||
const loadingManageListRefreshKey = 'loading-manage-list-refresh';
|
|
||||||
let detailAmapLoader;
|
let detailAmapLoader;
|
||||||
|
|
||||||
const createSearchForm = () => ({
|
const createSearchForm = () => ({
|
||||||
@@ -1598,14 +1517,6 @@ const normalizeLoadingMileage = value => {
|
|||||||
return value;
|
return value;
|
||||||
};
|
};
|
||||||
|
|
||||||
// 提交时未填写里程传 null,禁止自动填充 -1
|
|
||||||
const toSubmitMileage = value => {
|
|
||||||
const normalized = normalizeLoadingMileage(value);
|
|
||||||
if (normalized === '') return null;
|
|
||||||
const number = Number(normalized);
|
|
||||||
return Number.isFinite(number) ? number : null;
|
|
||||||
};
|
|
||||||
|
|
||||||
const createCopiedDialogForm = (detail = {}) => {
|
const createCopiedDialogForm = (detail = {}) => {
|
||||||
const form = createDialogForm();
|
const form = createDialogForm();
|
||||||
Object.keys(form).forEach(prop => {
|
Object.keys(form).forEach(prop => {
|
||||||
@@ -1772,12 +1683,11 @@ export default {
|
|||||||
const typeMap = {
|
const typeMap = {
|
||||||
pending: 'success',
|
pending: 'success',
|
||||||
running: 'success',
|
running: 'success',
|
||||||
processing: 'success',
|
completed: 'info',
|
||||||
completed: 'primary',
|
|
||||||
cancelled: 'info',
|
cancelled: 'info',
|
||||||
draft: 'info',
|
draft: 'warning',
|
||||||
};
|
};
|
||||||
return typeMap[this.dialogForm.businessStatus] || 'warning';
|
return typeMap[this.dialogForm.businessStatus] || 'info';
|
||||||
},
|
},
|
||||||
taskSummary() {
|
taskSummary() {
|
||||||
return (
|
return (
|
||||||
@@ -1786,12 +1696,6 @@ export default {
|
|||||||
.join(' / ') || '-'
|
.join(' / ') || '-'
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
isSelfCarrierDetail() {
|
|
||||||
return String(this.dialogForm.carrierType || '').trim() === '自运';
|
|
||||||
},
|
|
||||||
detailTaskMileage() {
|
|
||||||
return normalizeLoadingMileage(this.dialogForm.mileage);
|
|
||||||
},
|
|
||||||
detailAttachments() {
|
detailAttachments() {
|
||||||
return parseJsonArray(this.dialogForm.attachmentsJson || this.dialogForm.attachmentJson).map(
|
return parseJsonArray(this.dialogForm.attachmentsJson || this.dialogForm.attachmentJson).map(
|
||||||
item =>
|
item =>
|
||||||
@@ -1849,29 +1753,12 @@ export default {
|
|||||||
deactivated() {
|
deactivated() {
|
||||||
this.closeInnerDialogs();
|
this.closeInnerDialogs();
|
||||||
},
|
},
|
||||||
activated() {
|
|
||||||
if (this.isStandalonePage) return;
|
|
||||||
if (sessionStorage.getItem(loadingManageListRefreshKey)) {
|
|
||||||
sessionStorage.removeItem(loadingManageListRefreshKey);
|
|
||||||
this.loadTable();
|
|
||||||
}
|
|
||||||
},
|
|
||||||
beforeUnmount() {
|
beforeUnmount() {
|
||||||
this.closeInnerDialogs();
|
this.closeInnerDialogs();
|
||||||
},
|
},
|
||||||
watch: {
|
watch: {
|
||||||
$route(to, from) {
|
$route(to, from) {
|
||||||
if (!this.isStandalonePage) {
|
if (!this.isStandalonePage) return;
|
||||||
// 从表单/详情返回列表时刷新,避免草稿确认后仍显示旧状态
|
|
||||||
if (
|
|
||||||
from?.path?.includes('/loading-manage/form') ||
|
|
||||||
from?.path?.includes('/loading-manage/detail')
|
|
||||||
) {
|
|
||||||
sessionStorage.removeItem(loadingManageListRefreshKey);
|
|
||||||
this.loadTable();
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
this.syncStandaloneTagTitle();
|
this.syncStandaloneTagTitle();
|
||||||
if (this.isStandaloneDetailPage) {
|
if (this.isStandaloneDetailPage) {
|
||||||
// 记录变化或上一个页面不是详情态(例如从表单页/列表页进来)时重新拉取详情
|
// 记录变化或上一个页面不是详情态(例如从表单页/列表页进来)时重新拉取详情
|
||||||
@@ -2100,157 +1987,6 @@ export default {
|
|||||||
value.includes('highway')
|
value.includes('highway')
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
// 与总单详情一致:公路地址压缩为「市 区县」
|
|
||||||
formatRoadLocation(value) {
|
|
||||||
const text = String(value || '')
|
|
||||||
.replace(/[\//\s]+/g, '')
|
|
||||||
.trim();
|
|
||||||
if (!text) return '';
|
|
||||||
|
|
||||||
const parts = this.parseWaybillAddress(text);
|
|
||||||
let city = String(parts.city || '').trim();
|
|
||||||
let district = String(parts.district || '').trim();
|
|
||||||
let detail = String(parts.detailAddress || '').trim();
|
|
||||||
|
|
||||||
// 自治州/地区/盟下还有「XX市 + 区县」时,以市级作为展示主体
|
|
||||||
if (/(?:自治州|地区|盟)$/.test(city) && detail) {
|
|
||||||
const nestedCityMatch = detail.match(/^(.+?市)/);
|
|
||||||
if (nestedCityMatch) {
|
|
||||||
city = nestedCityMatch[1];
|
|
||||||
detail = detail.slice(nestedCityMatch[1].length);
|
|
||||||
const nestedDistrictMatch = detail.match(
|
|
||||||
/^(.+?(?:自治县|自治旗|林区|矿区|新区|开发区|区|县|旗))/
|
|
||||||
);
|
|
||||||
if (nestedDistrictMatch) district = nestedDistrictMatch[1];
|
|
||||||
} else if (!district) {
|
|
||||||
const nestedDistrictMatch = detail.match(
|
|
||||||
/^(.+?(?:自治县|自治旗|林区|矿区|新区|开发区|区|县|旗))/
|
|
||||||
);
|
|
||||||
if (nestedDistrictMatch) district = nestedDistrictMatch[1];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 市级后紧跟区县,但 parse 未拆出时再补一次
|
|
||||||
if (city && !district && detail) {
|
|
||||||
const nestedDistrictMatch = detail.match(
|
|
||||||
/^(.+?(?:自治县|自治旗|林区|矿区|新区|开发区|区|县|旗))/
|
|
||||||
);
|
|
||||||
if (nestedDistrictMatch) district = nestedDistrictMatch[1];
|
|
||||||
}
|
|
||||||
|
|
||||||
if (city && district) {
|
|
||||||
// 兼容「梅县区」:命中「县」后还需保留后面的「区」
|
|
||||||
if (district.endsWith('县') && detail.startsWith(`${district}区`)) {
|
|
||||||
district = `${district}区`;
|
|
||||||
}
|
|
||||||
return `${city} ${district}`;
|
|
||||||
}
|
|
||||||
if (city) return city;
|
|
||||||
if (district) return district;
|
|
||||||
|
|
||||||
// 兜底压缩
|
|
||||||
const withoutProvince = text.replace(/^.*?(?:省|自治区|特别行政区)/, '');
|
|
||||||
const cityMatch = withoutProvince.match(/.+?(?:市|自治州|州(?!市)|盟(?!市)|地区)/);
|
|
||||||
if (!cityMatch) return text;
|
|
||||||
const fallbackCity = cityMatch[0];
|
|
||||||
const districtSource = withoutProvince.slice(fallbackCity.length);
|
|
||||||
const districtMatch = districtSource.match(
|
|
||||||
/^[^市州盟]*?(?:自治县|自治旗|林区|矿区|新区|开发区|区|县|旗)/
|
|
||||||
);
|
|
||||||
if (!districtMatch) {
|
|
||||||
// 州后面是县级市时,继续解析市 + 区
|
|
||||||
const nestedCityMatch = districtSource.match(/^(.+?市)/);
|
|
||||||
if (nestedCityMatch) {
|
|
||||||
const nestedDistrictSource = districtSource.slice(nestedCityMatch[1].length);
|
|
||||||
const nestedDistrictMatch = nestedDistrictSource.match(
|
|
||||||
/^[^市州盟]*?(?:自治县|自治旗|林区|矿区|新区|开发区|区|县|旗)/
|
|
||||||
);
|
|
||||||
if (nestedDistrictMatch) {
|
|
||||||
return `${nestedCityMatch[1]} ${nestedDistrictMatch[0]}`;
|
|
||||||
}
|
|
||||||
return nestedCityMatch[1];
|
|
||||||
}
|
|
||||||
return fallbackCity;
|
|
||||||
}
|
|
||||||
const fallbackDistrict = `${districtMatch[0]}${
|
|
||||||
districtSource.slice(districtMatch[0].length).startsWith('区') ? '区' : ''
|
|
||||||
}`;
|
|
||||||
return `${fallbackCity} ${fallbackDistrict}`;
|
|
||||||
},
|
|
||||||
isStationLikeName(value) {
|
|
||||||
const text = String(value || '').trim();
|
|
||||||
if (!text) return false;
|
|
||||||
return (
|
|
||||||
/(?:火车站|高铁站|客运站|货运站|港口|码头|机场|空港|航站楼)$/.test(text) ||
|
|
||||||
/(?<!市|州|盟|区|县|旗)站$/.test(text)
|
|
||||||
);
|
|
||||||
},
|
|
||||||
// 详情运输路线短展示:公路仅市区,非公路仅名称
|
|
||||||
formatDetailRouteShort(node = {}) {
|
|
||||||
const name = String(node.name || '').trim();
|
|
||||||
const address = String(node.address || '').trim();
|
|
||||||
const displayAddress = String(node.displayAddress || '').trim();
|
|
||||||
if (!this.isRoadTransportType(this.dialogForm.transportType)) {
|
|
||||||
if (name) return name;
|
|
||||||
if (this.isStationLikeName(displayAddress)) return displayAddress;
|
|
||||||
if (this.isStationLikeName(address)) return address;
|
|
||||||
return displayAddress || address || '-';
|
|
||||||
}
|
|
||||||
const enriched = this.enrichRouteNodeRegion(node);
|
|
||||||
// 合并节点地址与运单上的市/区字段,避免只解析到市
|
|
||||||
const region = this.mergeRouteAddressParts(enriched.cityName, enriched.districtName, '');
|
|
||||||
const source = this.mergeRouteAddressParts(
|
|
||||||
address,
|
|
||||||
displayAddress,
|
|
||||||
region,
|
|
||||||
enriched.name || name
|
|
||||||
);
|
|
||||||
const formatted = this.formatRoadLocation(source);
|
|
||||||
if (enriched.cityName && enriched.districtName) {
|
|
||||||
return `${enriched.cityName} ${enriched.districtName}`;
|
|
||||||
}
|
|
||||||
if (/(?:市|州|盟|区|县|旗)/.test(formatted)) return formatted;
|
|
||||||
return formatted || name || displayAddress || address || '-';
|
|
||||||
},
|
|
||||||
// 公路悬停展示完整地址;非公路不展示 tooltip
|
|
||||||
formatDetailRouteTooltip(node = {}) {
|
|
||||||
if (!this.isRoadTransportType(this.dialogForm.transportType)) return '';
|
|
||||||
const enriched = this.enrichRouteNodeRegion(node);
|
|
||||||
const region = this.mergeRouteAddressParts(enriched.cityName, enriched.districtName, '');
|
|
||||||
const full = this.mergeRouteAddressParts(
|
|
||||||
String(node.address || '').trim(),
|
|
||||||
String(node.displayAddress || '').trim(),
|
|
||||||
region,
|
|
||||||
String(node.name || enriched.name || '').trim()
|
|
||||||
);
|
|
||||||
const short = this.formatDetailRouteShort(node);
|
|
||||||
if (!full) return '';
|
|
||||||
return full === short ? '' : full;
|
|
||||||
},
|
|
||||||
// 按装/卸标签从已选运单回填市、区,补充 routeJson 中缺失的区县
|
|
||||||
enrichRouteNodeRegion(node = {}) {
|
|
||||||
const tags = Array.isArray(node.tags) ? node.tags : [];
|
|
||||||
const result = { cityName: '', districtName: '', name: '' };
|
|
||||||
for (const tag of tags) {
|
|
||||||
const text = String(tag || '');
|
|
||||||
const loadMatch = text.match(/^装(\d+)$/);
|
|
||||||
const unloadMatch = text.match(/^卸(\d+)$/);
|
|
||||||
const index = Number((loadMatch || unloadMatch)?.[1] || 0) - 1;
|
|
||||||
if (index < 0) continue;
|
|
||||||
const row = this.selectedWaybillRows[index];
|
|
||||||
if (!row) continue;
|
|
||||||
const prefix = loadMatch ? 'departure' : 'arrival';
|
|
||||||
result.cityName = String(
|
|
||||||
row[`${prefix}CityName`] || row[`${prefix}City`] || ''
|
|
||||||
).trim();
|
|
||||||
result.districtName = String(
|
|
||||||
row[`${prefix}DistrictName`] || row[`${prefix}District`] || ''
|
|
||||||
).trim();
|
|
||||||
result.name = String(row[`${prefix}Name`] || '').trim();
|
|
||||||
if (result.cityName || result.districtName || result.name) break;
|
|
||||||
}
|
|
||||||
return result;
|
|
||||||
},
|
|
||||||
splitText(value) {
|
splitText(value) {
|
||||||
if (!value) return [];
|
if (!value) return [];
|
||||||
return String(value)
|
return String(value)
|
||||||
@@ -2288,72 +2024,37 @@ export default {
|
|||||||
formatRouteAddress(row, type) {
|
formatRouteAddress(row, type) {
|
||||||
const prefix =
|
const prefix =
|
||||||
type === 'departure' ? 'departure' : type === 'arrival' ? 'arrival' : 'transit';
|
type === 'departure' ? 'departure' : type === 'arrival' ? 'arrival' : 'transit';
|
||||||
if (type === 'transit') {
|
if (type !== 'transit') {
|
||||||
const values = this.splitText(row.transitAddress || row.transitName || '')
|
const address = row[`${prefix}Address`];
|
||||||
.map(value => this.formatListRouteShort(row, value, value))
|
const region = this.routeProvinceName(row, prefix);
|
||||||
.filter(value => value && value !== '-');
|
const district = row[`${prefix}DistrictName`];
|
||||||
return [...new Set(values)].join(',') || '-';
|
const source = this.mergeRouteAddressParts(region, district, address);
|
||||||
|
return this.formatProvinceCityDistrict(source) || '-';
|
||||||
}
|
}
|
||||||
const name = String(row[`${prefix}Name`] || '').trim();
|
const values = [
|
||||||
const address = String(row[`${prefix}Address`] || '').trim();
|
row[`${prefix}RegionName`],
|
||||||
const cityName = String(
|
row[`${prefix}DistrictName`],
|
||||||
row[`${prefix}CityName`] || row[`${prefix}City`] || ''
|
row[`${prefix}Address`],
|
||||||
).trim();
|
]
|
||||||
const districtName = String(
|
.filter(Boolean)
|
||||||
row[`${prefix}DistrictName`] || row[`${prefix}District`] || ''
|
.flatMap(value => this.splitText(value))
|
||||||
).trim();
|
.map(value => this.formatProvinceCityDistrict(value))
|
||||||
if (
|
.filter(Boolean);
|
||||||
this.isRoadTransportType(row?.transportType) &&
|
return [...new Set(values)].join(',') || '-';
|
||||||
cityName &&
|
|
||||||
districtName
|
|
||||||
) {
|
|
||||||
return `${cityName} ${districtName}`;
|
|
||||||
}
|
|
||||||
const region = this.mergeRouteAddressParts(
|
|
||||||
this.routeProvinceName(row, prefix),
|
|
||||||
cityName,
|
|
||||||
districtName
|
|
||||||
);
|
|
||||||
const source = this.mergeRouteAddressParts(region, '', address || name);
|
|
||||||
return this.formatListRouteShort(row, name, source || address || name);
|
|
||||||
},
|
},
|
||||||
formatFullRouteAddress(row, type) {
|
formatFullRouteAddress(row, type) {
|
||||||
if (!this.isRoadTransportType(row?.transportType)) return '';
|
|
||||||
const prefix =
|
const prefix =
|
||||||
type === 'departure' ? 'departure' : type === 'arrival' ? 'arrival' : 'transit';
|
type === 'departure' ? 'departure' : type === 'arrival' ? 'arrival' : 'transit';
|
||||||
if (type === 'transit') {
|
if (type !== 'transit') {
|
||||||
const values = this.splitText(row.transitAddress || '')
|
const address = row[`${prefix}Address`];
|
||||||
.map(value => String(value || '').trim())
|
const region = this.routeProvinceName(row, prefix);
|
||||||
.filter(Boolean);
|
const district = row[`${prefix}DistrictName`];
|
||||||
const full = [...new Set(values)].join(',');
|
return this.mergeRouteAddressParts(region, district, address) || '-';
|
||||||
const short = this.formatRouteAddress(row, 'transit');
|
|
||||||
return full && full !== short ? full : '';
|
|
||||||
}
|
}
|
||||||
const name = String(row[`${prefix}Name`] || '').trim();
|
const values = [row.transitRegionName, row.transitDistrictName, row.transitAddress]
|
||||||
const address = String(row[`${prefix}Address`] || '').trim();
|
.filter(Boolean)
|
||||||
const region = this.mergeRouteAddressParts(
|
.flatMap(value => this.splitText(value));
|
||||||
this.routeProvinceName(row, prefix),
|
return [...new Set(values)].join(',') || '-';
|
||||||
row[`${prefix}CityName`] || row[`${prefix}City`],
|
|
||||||
row[`${prefix}DistrictName`] || row[`${prefix}District`]
|
|
||||||
);
|
|
||||||
const full = this.mergeRouteAddressParts(region, '', address || name);
|
|
||||||
const short = this.formatRouteAddress(row, type);
|
|
||||||
if (!full) return '';
|
|
||||||
return full === short ? '' : full;
|
|
||||||
},
|
|
||||||
// 列表短地址:公路「市 区县」,非公路仅名称
|
|
||||||
formatListRouteShort(row = {}, name = '', address = '') {
|
|
||||||
const rawName = String(name || '').trim();
|
|
||||||
const rawAddress = String(address || '').trim();
|
|
||||||
if (!this.isRoadTransportType(row?.transportType)) {
|
|
||||||
if (rawName) return rawName;
|
|
||||||
if (this.isStationLikeName(rawAddress)) return rawAddress;
|
|
||||||
return rawAddress || '-';
|
|
||||||
}
|
|
||||||
const source = rawAddress || rawName;
|
|
||||||
const formatted = this.formatRoadLocation(source);
|
|
||||||
if (/(?:市|州|盟)/.test(formatted)) return formatted;
|
|
||||||
return formatted || rawName || rawAddress || '-';
|
|
||||||
},
|
},
|
||||||
formatWaybillAddress(row, type) {
|
formatWaybillAddress(row, type) {
|
||||||
const prefix = type === 'departure' ? 'departure' : 'arrival';
|
const prefix = type === 'departure' ? 'departure' : 'arrival';
|
||||||
@@ -2579,8 +2280,7 @@ export default {
|
|||||||
this.dialogForm = row.copy
|
this.dialogForm = row.copy
|
||||||
? createCopiedDialogForm(detail)
|
? createCopiedDialogForm(detail)
|
||||||
: { ...createDialogForm(), ...detail };
|
: { ...createDialogForm(), ...detail };
|
||||||
this.applyLoadingTaskInfoFromDetail(this.dialogForm);
|
this.dialogForm.mileage = normalizeLoadingMileage(this.dialogForm.mileage);
|
||||||
this.applyLoadingMileageFromDetail(this.dialogForm);
|
|
||||||
await this.restoreWaybillRows(
|
await this.restoreWaybillRows(
|
||||||
this.dialogForm.waybillIdsJson,
|
this.dialogForm.waybillIdsJson,
|
||||||
this.dialogForm.loadingSubNos
|
this.dialogForm.loadingSubNos
|
||||||
@@ -2917,8 +2617,6 @@ export default {
|
|||||||
...this.buildQueryParams(this.candidateQuery),
|
...this.buildQueryParams(this.candidateQuery),
|
||||||
businessStatus: 'pending',
|
businessStatus: 'pending',
|
||||||
onlyUnassignedLoading: 1,
|
onlyUnassignedLoading: 1,
|
||||||
// 仅查当前账户所属组织及子组织下的运单
|
|
||||||
allDept: 0,
|
|
||||||
...(this.dialogMode === 'add' ? { transportType: 'road' } : {}),
|
...(this.dialogMode === 'add' ? { transportType: 'road' } : {}),
|
||||||
};
|
};
|
||||||
const transportType = this.getCandidateTransportTypeQuery(query.transportType);
|
const transportType = this.getCandidateTransportTypeQuery(query.transportType);
|
||||||
@@ -3013,16 +2711,12 @@ export default {
|
|||||||
.map(row => row.waybillNo)
|
.map(row => row.waybillNo)
|
||||||
.filter(Boolean)
|
.filter(Boolean)
|
||||||
.join(',');
|
.join(',');
|
||||||
const rowIds = rows
|
this.dialogForm.waybillIdsJson = JSON.stringify(
|
||||||
.map(row => row.id)
|
rows
|
||||||
.filter(Boolean)
|
.map(row => row.id)
|
||||||
.map(id => String(id));
|
.filter(Boolean)
|
||||||
// 回显失败仅有运单号时,保留原 waybillIdsJson,避免确认提交清空运单绑定
|
.map(id => String(id))
|
||||||
if (rowIds.length) {
|
);
|
||||||
this.dialogForm.waybillIdsJson = JSON.stringify(rowIds);
|
|
||||||
} else if (!String(this.dialogForm.waybillIdsJson || '').trim()) {
|
|
||||||
this.dialogForm.waybillIdsJson = '[]';
|
|
||||||
}
|
|
||||||
this.dialogForm.projectName = uniqueText(rows, 'projectName');
|
this.dialogForm.projectName = uniqueText(rows, 'projectName');
|
||||||
this.dialogForm.customerName = uniqueText(rows, 'customerName');
|
this.dialogForm.customerName = uniqueText(rows, 'customerName');
|
||||||
this.dialogForm.originalNo = uniqueText(rows, 'originalNo');
|
this.dialogForm.originalNo = uniqueText(rows, 'originalNo');
|
||||||
@@ -3149,95 +2843,29 @@ export default {
|
|||||||
remark: item.remark || '',
|
remark: item.remark || '',
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
applyLoadingMileageFromDetail(form = this.dialogForm) {
|
|
||||||
let taskMileage = '';
|
|
||||||
try {
|
|
||||||
const taskInfo = JSON.parse(form.taskInfoJson || '{}');
|
|
||||||
taskMileage = taskInfo?.mileage;
|
|
||||||
} catch (error) {
|
|
||||||
taskMileage = '';
|
|
||||||
}
|
|
||||||
form.mileage = normalizeLoadingMileage(
|
|
||||||
form.mileage !== undefined && form.mileage !== null && form.mileage !== ''
|
|
||||||
? form.mileage
|
|
||||||
: taskMileage
|
|
||||||
);
|
|
||||||
},
|
|
||||||
applyLoadingTaskInfoFromDetail(form = this.dialogForm) {
|
|
||||||
let taskInfo = {};
|
|
||||||
try {
|
|
||||||
taskInfo = JSON.parse(form.taskInfoJson || '{}') || {};
|
|
||||||
} catch (error) {
|
|
||||||
taskInfo = {};
|
|
||||||
}
|
|
||||||
if (!taskInfo || typeof taskInfo !== 'object' || Array.isArray(taskInfo)) return;
|
|
||||||
const fields = [
|
|
||||||
'carrierType',
|
|
||||||
'carrierName',
|
|
||||||
'carrierContractId',
|
|
||||||
'driverName',
|
|
||||||
'driverPhone',
|
|
||||||
'vehicleNo',
|
|
||||||
'trailerVehicleNo',
|
|
||||||
'escortName',
|
|
||||||
'escortPhone',
|
|
||||||
'estimatedStartDate',
|
|
||||||
'estimatedEndDate',
|
|
||||||
'taskRemark',
|
|
||||||
];
|
|
||||||
fields.forEach(prop => {
|
|
||||||
if (
|
|
||||||
(form[prop] === undefined || form[prop] === null || form[prop] === '') &&
|
|
||||||
taskInfo[prop] !== undefined &&
|
|
||||||
taskInfo[prop] !== null &&
|
|
||||||
taskInfo[prop] !== ''
|
|
||||||
) {
|
|
||||||
form[prop] = taskInfo[prop];
|
|
||||||
}
|
|
||||||
});
|
|
||||||
if (
|
|
||||||
(form.mileage === undefined || form.mileage === null || form.mileage === '') &&
|
|
||||||
taskInfo.mileage !== undefined
|
|
||||||
) {
|
|
||||||
form.mileage = normalizeLoadingMileage(taskInfo.mileage);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
detailTaskField(prop) {
|
|
||||||
const value = String(this.dialogForm?.[prop] ?? '').trim();
|
|
||||||
return value || '-';
|
|
||||||
},
|
|
||||||
detailTaskDate(prop) {
|
|
||||||
const value = String(this.dialogForm?.[prop] ?? '').trim();
|
|
||||||
return value ? value.slice(0, 10) : '-';
|
|
||||||
},
|
|
||||||
normalizePayload() {
|
normalizePayload() {
|
||||||
this.syncRouteAddressFields();
|
this.syncRouteAddressFields();
|
||||||
this.rebuildSummaryFromWaybills(false);
|
this.rebuildSummaryFromWaybills(false);
|
||||||
this.dialogForm.mileage = normalizeLoadingMileage(this.dialogForm.mileage);
|
this.dialogForm.mileage = normalizeLoadingMileage(this.dialogForm.mileage);
|
||||||
const mileage = toSubmitMileage(this.dialogForm.mileage);
|
|
||||||
const taskInfo = {
|
|
||||||
carrierType: this.dialogForm.carrierType,
|
|
||||||
carrierName: this.dialogForm.carrierName,
|
|
||||||
carrierContractId: this.dialogForm.carrierContractId,
|
|
||||||
driverName: this.dialogForm.driverName,
|
|
||||||
driverPhone: this.dialogForm.driverPhone,
|
|
||||||
vehicleNo: this.dialogForm.vehicleNo,
|
|
||||||
trailerVehicleNo: this.dialogForm.trailerVehicleNo,
|
|
||||||
escortName: this.dialogForm.escortName,
|
|
||||||
escortPhone: this.dialogForm.escortPhone,
|
|
||||||
estimatedStartDate: this.dialogForm.estimatedStartDate,
|
|
||||||
estimatedEndDate: this.dialogForm.estimatedEndDate,
|
|
||||||
taskRemark: this.dialogForm.taskRemark,
|
|
||||||
};
|
|
||||||
if (mileage !== null) {
|
|
||||||
taskInfo.mileage = mileage;
|
|
||||||
}
|
|
||||||
return {
|
return {
|
||||||
...this.dialogForm,
|
...this.dialogForm,
|
||||||
mileage,
|
|
||||||
routeJson: JSON.stringify(this.routeNodes),
|
routeJson: JSON.stringify(this.routeNodes),
|
||||||
goodsJson: JSON.stringify(this.cargoRows),
|
goodsJson: JSON.stringify(this.cargoRows),
|
||||||
taskInfoJson: JSON.stringify(taskInfo),
|
taskInfoJson: JSON.stringify({
|
||||||
|
carrierType: this.dialogForm.carrierType,
|
||||||
|
carrierName: this.dialogForm.carrierName,
|
||||||
|
carrierContractId: this.dialogForm.carrierContractId,
|
||||||
|
driverName: this.dialogForm.driverName,
|
||||||
|
driverPhone: this.dialogForm.driverPhone,
|
||||||
|
vehicleNo: this.dialogForm.vehicleNo,
|
||||||
|
trailerVehicleNo: this.dialogForm.trailerVehicleNo,
|
||||||
|
escortName: this.dialogForm.escortName,
|
||||||
|
escortPhone: this.dialogForm.escortPhone,
|
||||||
|
mileage: this.dialogForm.mileage,
|
||||||
|
estimatedStartDate: this.dialogForm.estimatedStartDate,
|
||||||
|
estimatedEndDate: this.dialogForm.estimatedEndDate,
|
||||||
|
taskRemark: this.dialogForm.taskRemark,
|
||||||
|
}),
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
validatePayload(draftMode = false) {
|
validatePayload(draftMode = false) {
|
||||||
@@ -3292,7 +2920,7 @@ export default {
|
|||||||
return true;
|
return true;
|
||||||
},
|
},
|
||||||
async saveDraft() {
|
async saveDraft() {
|
||||||
// 暂存不做任何业务校验,直接保存
|
if (!this.validatePayload(true)) return;
|
||||||
this.dialogSaving = 'draft';
|
this.dialogSaving = 'draft';
|
||||||
try {
|
try {
|
||||||
const res = await loadingApi.saveDraft(this.normalizePayload());
|
const res = await loadingApi.saveDraft(this.normalizePayload());
|
||||||
@@ -3300,7 +2928,7 @@ export default {
|
|||||||
...this.dialogForm,
|
...this.dialogForm,
|
||||||
...(res.data?.data || res.data || {}),
|
...(res.data?.data || res.data || {}),
|
||||||
};
|
};
|
||||||
this.applyLoadingMileageFromDetail(this.dialogForm);
|
this.dialogForm.mileage = normalizeLoadingMileage(this.dialogForm.mileage);
|
||||||
ElMessage.success('暂存成功');
|
ElMessage.success('暂存成功');
|
||||||
this.loadTable();
|
this.loadTable();
|
||||||
} finally {
|
} finally {
|
||||||
@@ -3311,11 +2939,7 @@ export default {
|
|||||||
if (!this.validatePayload(false)) return;
|
if (!this.validatePayload(false)) return;
|
||||||
this.dialogSaving = 'submit';
|
this.dialogSaving = 'submit';
|
||||||
try {
|
try {
|
||||||
const payload = {
|
const payload = this.normalizePayload();
|
||||||
...this.normalizePayload(),
|
|
||||||
// 确认生成正式配载单(待执行),覆盖草稿状态
|
|
||||||
businessStatus: 'pending',
|
|
||||||
};
|
|
||||||
if (this.dialogMode === 'reassign') {
|
if (this.dialogMode === 'reassign') {
|
||||||
await loadingApi.reassign(payload);
|
await loadingApi.reassign(payload);
|
||||||
ElMessage.success('派单成功');
|
ElMessage.success('派单成功');
|
||||||
@@ -3323,7 +2947,6 @@ export default {
|
|||||||
await loadingApi.submit(payload);
|
await loadingApi.submit(payload);
|
||||||
ElMessage.success('确认成功');
|
ElMessage.success('确认成功');
|
||||||
}
|
}
|
||||||
sessionStorage.setItem(loadingManageListRefreshKey, '1');
|
|
||||||
this.closeLoadingDialog();
|
this.closeLoadingDialog();
|
||||||
this.loadTable();
|
this.loadTable();
|
||||||
} finally {
|
} finally {
|
||||||
@@ -3409,7 +3032,6 @@ export default {
|
|||||||
this.dialogForm.carrierContractId = '';
|
this.dialogForm.carrierContractId = '';
|
||||||
this.dialogForm.carrierName = '';
|
this.dialogForm.carrierName = '';
|
||||||
this.dialogForm.carrierId = '';
|
this.dialogForm.carrierId = '';
|
||||||
this.clearDialogDriverVehicleFields();
|
|
||||||
if (this.dialogForm.carrierType === '承运商') {
|
if (this.dialogForm.carrierType === '承运商') {
|
||||||
this.dialogForm.trailerVehicleNo = '';
|
this.dialogForm.trailerVehicleNo = '';
|
||||||
this.dialogForm.escortName = '';
|
this.dialogForm.escortName = '';
|
||||||
@@ -3429,23 +3051,6 @@ export default {
|
|||||||
this.dialogForm.carrierContractId =
|
this.dialogForm.carrierContractId =
|
||||||
this.dialogForm.carrierType === '承运商' && contract?.id ? contract.id : '';
|
this.dialogForm.carrierType === '承运商' && contract?.id ? contract.id : '';
|
||||||
this.dialogForm.carrierName = contract?.carrierName || '';
|
this.dialogForm.carrierName = contract?.carrierName || '';
|
||||||
this.dialogForm.carrierId = contract?.carrierId || contract?.customerId || '';
|
|
||||||
this.clearDialogDriverVehicleFields();
|
|
||||||
},
|
|
||||||
clearDialogDriverVehicleFields() {
|
|
||||||
this.dialogForm.driverName = '';
|
|
||||||
this.dialogForm.driverPhone = '';
|
|
||||||
this.dialogForm.vehicleNo = '';
|
|
||||||
this.dialogForm.trailerVehicleNo = '';
|
|
||||||
this.dialogForm.escortName = '';
|
|
||||||
this.dialogForm.escortPhone = '';
|
|
||||||
this.driverOptions = [];
|
|
||||||
},
|
|
||||||
getDialogCarrierFilter() {
|
|
||||||
return {
|
|
||||||
carrierId: this.dialogForm.carrierId || '',
|
|
||||||
carrierName: this.dialogForm.carrierName || '',
|
|
||||||
};
|
|
||||||
},
|
},
|
||||||
handleDriverChange(value) {
|
handleDriverChange(value) {
|
||||||
const driver = this.driverOptions.find(item => (item.driverName || item.name) === value);
|
const driver = this.driverOptions.find(item => (item.driverName || item.name) === value);
|
||||||
@@ -3457,7 +3062,7 @@ export default {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
fetchDriverSuggestions(queryString, callback) {
|
fetchDriverSuggestions(queryString, callback) {
|
||||||
this.loadDialogDriverOptions(String(queryString || '').trim())
|
this.loadDriverOptions(String(queryString || '').trim())
|
||||||
.then(() =>
|
.then(() =>
|
||||||
callback(
|
callback(
|
||||||
this.driverOptions.map(item => ({
|
this.driverOptions.map(item => ({
|
||||||
@@ -3480,19 +3085,11 @@ export default {
|
|||||||
},
|
},
|
||||||
fetchEscortSuggestions(queryString, callback) {
|
fetchEscortSuggestions(queryString, callback) {
|
||||||
const keyword = String(queryString || '').trim();
|
const keyword = String(queryString || '').trim();
|
||||||
const carrier = this.getDialogCarrierFilter();
|
|
||||||
if (!carrier.carrierId && !carrier.carrierName) {
|
|
||||||
callback([]);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
this.escortLoading = true;
|
this.escortLoading = true;
|
||||||
fetchDriversByCarrierOrganizations(
|
getDriverList(1, 20, { ...(keyword ? { driverName: keyword } : {}), posts: '押运员' })
|
||||||
{ size: 20, ...(keyword ? { driverName: keyword } : {}), posts: '押运员' },
|
.then(res => {
|
||||||
carrier
|
|
||||||
)
|
|
||||||
.then(records => {
|
|
||||||
callback(
|
callback(
|
||||||
records.map(item => ({
|
unwrapRecords(res).map(item => ({
|
||||||
...item,
|
...item,
|
||||||
value: item.driverName || item.name || '',
|
value: item.driverName || item.name || '',
|
||||||
}))
|
}))
|
||||||
@@ -3512,7 +3109,6 @@ export default {
|
|||||||
handleMileageInput(value) {
|
handleMileageInput(value) {
|
||||||
this.dialogForm.mileage = String(value || '').replace(/[^\d.]/g, '');
|
this.dialogForm.mileage = String(value || '').replace(/[^\d.]/g, '');
|
||||||
},
|
},
|
||||||
normalizeLoadingMileage,
|
|
||||||
transportTypeLabel(value) {
|
transportTypeLabel(value) {
|
||||||
if (!value) return '-';
|
if (!value) return '-';
|
||||||
return (
|
return (
|
||||||
@@ -3562,29 +3158,12 @@ export default {
|
|||||||
this.driverLoading = true;
|
this.driverLoading = true;
|
||||||
try {
|
try {
|
||||||
this.driverOptions = unwrapRecords(
|
this.driverOptions = unwrapRecords(
|
||||||
await getDriverList(1, 20, { ...(keyword ? { driverName: keyword } : {}), posts: '司机' })
|
await getDriverList(1, 20, { driverName: keyword, posts: '司机' })
|
||||||
);
|
);
|
||||||
} finally {
|
} finally {
|
||||||
this.driverLoading = false;
|
this.driverLoading = false;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
async loadDialogDriverOptions(keyword = '') {
|
|
||||||
const carrier = this.getDialogCarrierFilter();
|
|
||||||
if (!carrier.carrierId && !carrier.carrierName) {
|
|
||||||
this.driverOptions = [];
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
this.driverLoading = true;
|
|
||||||
try {
|
|
||||||
this.driverOptions = await fetchDriversByCarrierOrganizations(
|
|
||||||
{ size: 20, ...(keyword ? { driverName: keyword } : {}), posts: '司机' },
|
|
||||||
carrier
|
|
||||||
);
|
|
||||||
return this.driverOptions;
|
|
||||||
} finally {
|
|
||||||
this.driverLoading = false;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
async loadCarrierOptions(keyword = '') {
|
async loadCarrierOptions(keyword = '') {
|
||||||
this.carrierLoading = true;
|
this.carrierLoading = true;
|
||||||
try {
|
try {
|
||||||
@@ -3646,39 +3225,15 @@ export default {
|
|||||||
} else {
|
} else {
|
||||||
this.dialogForm.carrierContractId = '';
|
this.dialogForm.carrierContractId = '';
|
||||||
this.dialogForm.carrierName = '';
|
this.dialogForm.carrierName = '';
|
||||||
this.dialogForm.carrierId = '';
|
|
||||||
}
|
}
|
||||||
return this.taskCarrierOptions;
|
return this.taskCarrierOptions;
|
||||||
}
|
}
|
||||||
if (this.dialogForm.carrierType === '自运' && contracts.length) {
|
if (this.dialogForm.carrierType === '自运' && contracts.length) {
|
||||||
this.taskCarrierOptions = contracts
|
this.taskCarrierOptions = contracts;
|
||||||
.map(item => ({
|
const current = contracts.find(item => item.carrierName === this.dialogForm.carrierName);
|
||||||
...item,
|
|
||||||
carrierName:
|
|
||||||
item.partyB ||
|
|
||||||
item.partyBName ||
|
|
||||||
item.contractPartyB ||
|
|
||||||
item.customerContractPartyB ||
|
|
||||||
item.carrierName ||
|
|
||||||
'',
|
|
||||||
carrierId:
|
|
||||||
item.partyBId ||
|
|
||||||
item.partyBUserId ||
|
|
||||||
item.contractPartyBId ||
|
|
||||||
item.customerContractPartyBId ||
|
|
||||||
item.carrierId ||
|
|
||||||
'',
|
|
||||||
}))
|
|
||||||
.filter(item => item.carrierName);
|
|
||||||
const current = this.taskCarrierOptions.find(
|
|
||||||
item => item.carrierName === this.dialogForm.carrierName
|
|
||||||
);
|
|
||||||
this.dialogForm.carrierContractId = '';
|
this.dialogForm.carrierContractId = '';
|
||||||
this.dialogForm.carrierName =
|
this.dialogForm.carrierName = current?.carrierName || contracts[0].carrierName || '';
|
||||||
current?.carrierName || this.taskCarrierOptions[0]?.carrierName || '';
|
return contracts;
|
||||||
this.dialogForm.carrierId =
|
|
||||||
current?.carrierId || this.taskCarrierOptions[0]?.carrierId || '';
|
|
||||||
return this.taskCarrierOptions;
|
|
||||||
}
|
}
|
||||||
if (this.dialogForm.carrierType !== '自运') {
|
if (this.dialogForm.carrierType !== '自运') {
|
||||||
this.taskCarrierOptions = [];
|
this.taskCarrierOptions = [];
|
||||||
@@ -3712,7 +3267,6 @@ export default {
|
|||||||
}));
|
}));
|
||||||
this.dialogForm.carrierContractId = '';
|
this.dialogForm.carrierContractId = '';
|
||||||
this.dialogForm.carrierName = partyBNames[0] || '';
|
this.dialogForm.carrierName = partyBNames[0] || '';
|
||||||
this.dialogForm.carrierId = '';
|
|
||||||
return this.taskCarrierOptions;
|
return this.taskCarrierOptions;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
if (requestId !== this.taskCarrierRequestId) return [];
|
if (requestId !== this.taskCarrierRequestId) return [];
|
||||||
@@ -4050,17 +3604,13 @@ export default {
|
|||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
flex-wrap: wrap;
|
flex-wrap: wrap;
|
||||||
gap: 12px;
|
gap: 24px;
|
||||||
padding: 4px 0 16px;
|
padding: 4px 0 16px;
|
||||||
|
}
|
||||||
:deep(.el-tag) {
|
.loading-detail__heading {
|
||||||
height: 28px;
|
font-size: 20px;
|
||||||
padding: 0 12px;
|
font-weight: 600;
|
||||||
border: 0;
|
margin-right: 8px;
|
||||||
border-radius: 8px;
|
|
||||||
font-size: 13px;
|
|
||||||
line-height: 28px;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
.loading-detail__route-title {
|
.loading-detail__route-title {
|
||||||
display: inline-flex;
|
display: inline-flex;
|
||||||
@@ -4073,24 +3623,9 @@ export default {
|
|||||||
}
|
}
|
||||||
.loading-detail__task-row {
|
.loading-detail__task-row {
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: minmax(0, 1fr) 220px;
|
grid-template-columns: 1fr 1fr;
|
||||||
gap: 24px;
|
gap: 24px;
|
||||||
padding-bottom: 4px;
|
padding-bottom: 4px;
|
||||||
align-items: start;
|
|
||||||
}
|
|
||||||
.loading-detail__task-main {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
gap: 12px;
|
|
||||||
min-width: 0;
|
|
||||||
}
|
|
||||||
.loading-detail__task-grid {
|
|
||||||
display: grid;
|
|
||||||
grid-template-columns: repeat(4, minmax(0, 1fr));
|
|
||||||
gap: 16px 24px;
|
|
||||||
}
|
|
||||||
.loading-detail__attachment-field {
|
|
||||||
min-width: 0;
|
|
||||||
}
|
}
|
||||||
.loading-detail__field {
|
.loading-detail__field {
|
||||||
display: flex;
|
display: flex;
|
||||||
@@ -4106,8 +3641,6 @@ export default {
|
|||||||
flex-wrap: wrap;
|
flex-wrap: wrap;
|
||||||
gap: 8px;
|
gap: 8px;
|
||||||
text-align: left;
|
text-align: left;
|
||||||
color: #303133;
|
|
||||||
word-break: break-all;
|
|
||||||
}
|
}
|
||||||
.loading-detail__steps {
|
.loading-detail__steps {
|
||||||
display: flex;
|
display: flex;
|
||||||
@@ -4164,14 +3697,6 @@ export default {
|
|||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
text-overflow: ellipsis;
|
text-overflow: ellipsis;
|
||||||
}
|
}
|
||||||
.loading-detail__step-address-text {
|
|
||||||
display: inline-block;
|
|
||||||
max-width: 100%;
|
|
||||||
overflow: hidden;
|
|
||||||
text-overflow: ellipsis;
|
|
||||||
vertical-align: bottom;
|
|
||||||
cursor: default;
|
|
||||||
}
|
|
||||||
.loading-detail__step-tags {
|
.loading-detail__step-tags {
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
@@ -4358,9 +3883,5 @@ export default {
|
|||||||
.loading-detail__bottom-grid {
|
.loading-detail__bottom-grid {
|
||||||
grid-template-columns: 1fr;
|
grid-template-columns: 1fr;
|
||||||
}
|
}
|
||||||
|
|
||||||
.loading-detail__task-grid {
|
|
||||||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -132,7 +132,7 @@
|
|||||||
/>
|
/>
|
||||||
</template>
|
</template>
|
||||||
<master-order-dispatch v-else-if="mode === 'dispatch'" :id="routeId" @back="goList" />
|
<master-order-dispatch v-else-if="mode === 'dispatch'" :id="routeId" @back="goList" />
|
||||||
<master-order-detail v-else-if="mode === 'detail'" :id="routeId" />
|
<master-order-detail v-else :id="routeId" />
|
||||||
<el-dialog v-model="confirm.visible" title="提示" width="400px"
|
<el-dialog v-model="confirm.visible" title="提示" width="400px"
|
||||||
><span>{{ confirm.message }}</span
|
><span>{{ confirm.message }}</span
|
||||||
><template #footer
|
><template #footer
|
||||||
@@ -153,10 +153,6 @@ export default {
|
|||||||
components: { MasterOrderEditor, MasterOrderDispatch, MasterOrderDetail },
|
components: { MasterOrderEditor, MasterOrderDispatch, MasterOrderDetail },
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
// 实例创建时锁定所属路由。keep-alive 缓存后 $route 会变成其它页面,
|
|
||||||
// 不能再用全局 query.mode / query.id 决定本页形态,否则打开「新增项目管理」
|
|
||||||
//(mode=add 且无 id)会落入详情分支并请求缺少 id 的详情接口。
|
|
||||||
routePathLocked: this.$route.path,
|
|
||||||
searchExpanded: false,
|
searchExpanded: false,
|
||||||
loading: false,
|
loading: false,
|
||||||
records: [],
|
records: [],
|
||||||
@@ -184,15 +180,10 @@ export default {
|
|||||||
};
|
};
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
isOwnedRoute() {
|
|
||||||
return this.$route.path === this.routePathLocked;
|
|
||||||
},
|
|
||||||
mode() {
|
mode() {
|
||||||
if (!this.isOwnedRoute) return 'list';
|
|
||||||
return this.$route.query.mode || 'list';
|
return this.$route.query.mode || 'list';
|
||||||
},
|
},
|
||||||
routeId() {
|
routeId() {
|
||||||
if (!this.isOwnedRoute) return '';
|
|
||||||
return this.$route.query.id;
|
return this.$route.query.id;
|
||||||
},
|
},
|
||||||
masterEditorTitle() {
|
masterEditorTitle() {
|
||||||
@@ -203,7 +194,6 @@ export default {
|
|||||||
'$route.query': {
|
'$route.query': {
|
||||||
immediate: true,
|
immediate: true,
|
||||||
handler() {
|
handler() {
|
||||||
if (!this.isOwnedRoute) return;
|
|
||||||
this.syncTagTitle();
|
this.syncTagTitle();
|
||||||
if (this.mode === 'list') this.load();
|
if (this.mode === 'list') this.load();
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -1,149 +0,0 @@
|
|||||||
<template>
|
|
||||||
<div ref="page" class="project-apply-public-view">
|
|
||||||
<project-apply ref="project" />
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
import ProjectApply from './project-apply.vue';
|
|
||||||
import { postMkPublicProcessMessage } from '@/api/mk-process';
|
|
||||||
|
|
||||||
export default {
|
|
||||||
name: 'ProjectApplyPublicView',
|
|
||||||
components: {
|
|
||||||
ProjectApply,
|
|
||||||
},
|
|
||||||
created() {
|
|
||||||
this.handleIframeHeight = () => this.sendIframeHeight();
|
|
||||||
this.handleProcessMessage = event => this.onProcessMessage(event);
|
|
||||||
document.addEventListener('DOMContentLoaded', this.handleIframeHeight, false);
|
|
||||||
window.addEventListener('message', this.handleProcessMessage);
|
|
||||||
},
|
|
||||||
mounted() {
|
|
||||||
document.documentElement.classList.add('mk-iframe-page');
|
|
||||||
document.body.classList.add('mk-iframe-page');
|
|
||||||
const app = document.getElementById('app');
|
|
||||||
if (app) app.classList.add('mk-iframe-page');
|
|
||||||
this.handleIframeHeight();
|
|
||||||
window.addEventListener('load', this.handleIframeHeight);
|
|
||||||
this.mkHeightTimers = [300, 800, 1600].map(delay => setTimeout(this.handleIframeHeight, delay));
|
|
||||||
if (typeof ResizeObserver === 'undefined') return;
|
|
||||||
this.mkHeightObserver = new ResizeObserver(() => this.handleIframeHeight());
|
|
||||||
this.$nextTick(() => {
|
|
||||||
if (this.$refs.page) this.mkHeightObserver.observe(this.$refs.page);
|
|
||||||
this.mkHeightObserver.observe(document.body);
|
|
||||||
});
|
|
||||||
},
|
|
||||||
beforeUnmount() {
|
|
||||||
document.removeEventListener('DOMContentLoaded', this.handleIframeHeight, false);
|
|
||||||
window.removeEventListener('load', this.handleIframeHeight);
|
|
||||||
window.removeEventListener('message', this.handleProcessMessage);
|
|
||||||
(this.mkHeightTimers || []).forEach(timer => clearTimeout(timer));
|
|
||||||
if (this.mkHeightObserver) {
|
|
||||||
this.mkHeightObserver.disconnect();
|
|
||||||
this.mkHeightObserver = null;
|
|
||||||
}
|
|
||||||
document.documentElement.classList.remove('mk-iframe-page');
|
|
||||||
document.body.classList.remove('mk-iframe-page');
|
|
||||||
const app = document.getElementById('app');
|
|
||||||
if (app) app.classList.remove('mk-iframe-page');
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
sendIframeHeight() {
|
|
||||||
this.$nextTick(() => {
|
|
||||||
window.parent.postMessage({ height: document.body.clientHeight }, '*');
|
|
||||||
});
|
|
||||||
},
|
|
||||||
onProcessMessage(event) {
|
|
||||||
const data = event && event.data;
|
|
||||||
if (!data || typeof data !== 'object') return;
|
|
||||||
if (data.height && !data.status && !data.type) return;
|
|
||||||
if (data.type === 'formValues' || data.type === 'afterSubmit') return;
|
|
||||||
const formValues = data.formValues;
|
|
||||||
if (data.status === 'submit') {
|
|
||||||
this.submitData(formValues);
|
|
||||||
} else if (data.status === 'save') {
|
|
||||||
this.saveData(formValues);
|
|
||||||
}
|
|
||||||
if (data.type === 'getFormValues') {
|
|
||||||
window.parent.postMessage({ type: 'formValues', formData: this.buildFormData() }, '*');
|
|
||||||
}
|
|
||||||
},
|
|
||||||
submitData(lbpmFormValues) {
|
|
||||||
const formData = this.buildFormData();
|
|
||||||
this.postFormData('submit', lbpmFormValues, formData);
|
|
||||||
if (!lbpmFormValues) return;
|
|
||||||
const parameters = Object.assign({}, lbpmFormValues, {
|
|
||||||
loginName: this.getLoginName(lbpmFormValues),
|
|
||||||
formInstanceId: this.getFormId(),
|
|
||||||
subject: formData.subject,
|
|
||||||
});
|
|
||||||
window.parent.postMessage({ type: 'afterSubmit', success: true, parameters }, '*');
|
|
||||||
},
|
|
||||||
saveData(lbpmFormValues) {
|
|
||||||
this.postFormData('save', lbpmFormValues, this.buildFormData());
|
|
||||||
},
|
|
||||||
postFormData(status, formValues, formData) {
|
|
||||||
postMkPublicProcessMessage('project-apply', {
|
|
||||||
status,
|
|
||||||
formValues: formValues || {},
|
|
||||||
formData,
|
|
||||||
}).catch(error => {
|
|
||||||
console.error('项目公开页提交流程数据失败:', error);
|
|
||||||
});
|
|
||||||
},
|
|
||||||
buildFormData() {
|
|
||||||
const form = this.getProjectForm();
|
|
||||||
return {
|
|
||||||
...form,
|
|
||||||
subject: form.projectName || form.projectShortName || '',
|
|
||||||
formInstanceId: this.getFormId(),
|
|
||||||
};
|
|
||||||
},
|
|
||||||
getProjectForm() {
|
|
||||||
const form = this.$refs.project && this.$refs.project.form;
|
|
||||||
if (!form) return {};
|
|
||||||
try {
|
|
||||||
return JSON.parse(JSON.stringify(form));
|
|
||||||
} catch (error) {
|
|
||||||
return { ...form };
|
|
||||||
}
|
|
||||||
},
|
|
||||||
getFormId() {
|
|
||||||
return this.$route.query.id || this.getProjectForm().id || '';
|
|
||||||
},
|
|
||||||
getLoginName(formValues = {}) {
|
|
||||||
return (
|
|
||||||
formValues.loginName ||
|
|
||||||
this.$route.query.loginName ||
|
|
||||||
this.$route.query.submitIdentity ||
|
|
||||||
''
|
|
||||||
);
|
|
||||||
},
|
|
||||||
},
|
|
||||||
};
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style lang="scss">
|
|
||||||
html.mk-iframe-page,
|
|
||||||
html.mk-iframe-page body,
|
|
||||||
html.mk-iframe-page #app,
|
|
||||||
html.mk-iframe-page #app.mk-iframe-page {
|
|
||||||
height: auto !important;
|
|
||||||
min-height: 100%;
|
|
||||||
overflow: visible;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
|
||||||
.project-apply-public-view {
|
|
||||||
min-height: 100%;
|
|
||||||
padding: 12px 0 24px;
|
|
||||||
box-sizing: border-box;
|
|
||||||
background: #f0f2f5;
|
|
||||||
|
|
||||||
:deep(.basic-container) {
|
|
||||||
padding: 0 12px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
<template>
|
<template>
|
||||||
<basic-container class="project-apply-page" :class="{ 'is-public-view': isPublicViewPage }">
|
<basic-container class="project-apply-page">
|
||||||
<avue-crud
|
<avue-crud
|
||||||
v-if="!isProjectFormPage"
|
v-if="!isProjectFormPage"
|
||||||
:option="tableOption"
|
:option="tableOption"
|
||||||
@@ -196,7 +196,7 @@
|
|||||||
<el-form-item label="项目编号" prop="projectCode">
|
<el-form-item label="项目编号" prop="projectCode">
|
||||||
<el-input
|
<el-input
|
||||||
v-model="form.projectCode"
|
v-model="form.projectCode"
|
||||||
:placeholder="dialogType === 'add' ? '若不填写,系统自动生成' : '请输入'"
|
placeholder="请输入"
|
||||||
:disabled="dialogType === 'edit'"
|
:disabled="dialogType === 'edit'"
|
||||||
/>
|
/>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
@@ -525,11 +525,6 @@
|
|||||||
</template>
|
</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
<el-table-column prop="companyNature" label="企业性质" min-width="140" align="center" />
|
<el-table-column prop="companyNature" label="企业性质" min-width="140" align="center" />
|
||||||
<el-table-column label="是否广西百强" min-width="140" align="center">
|
|
||||||
<template #default="{ row }">
|
|
||||||
{{ formatGuangxiTop100(row.guangxiTop100) }}
|
|
||||||
</template>
|
|
||||||
</el-table-column>
|
|
||||||
<el-table-column prop="legalPerson" label="法定代表人" min-width="140" align="center" />
|
<el-table-column prop="legalPerson" label="法定代表人" min-width="140" align="center" />
|
||||||
<el-table-column
|
<el-table-column
|
||||||
prop="address"
|
prop="address"
|
||||||
@@ -708,7 +703,6 @@
|
|||||||
label="变更内容"
|
label="变更内容"
|
||||||
min-width="180"
|
min-width="180"
|
||||||
align="center"
|
align="center"
|
||||||
:show-overflow-tooltip="false"
|
|
||||||
>
|
>
|
||||||
<template #default="{ row }">
|
<template #default="{ row }">
|
||||||
<el-tooltip placement="top" :show-after="200">
|
<el-tooltip placement="top" :show-after="200">
|
||||||
@@ -736,10 +730,43 @@
|
|||||||
</el-table-column>
|
</el-table-column>
|
||||||
</el-table>
|
</el-table>
|
||||||
|
|
||||||
<change-record-detail-dialog
|
<el-dialog
|
||||||
v-model="changeRecordDetailVisible"
|
v-model="changeRecordDetailVisible"
|
||||||
:rows="changeRecordDetailRows"
|
title="变更记录详情"
|
||||||
/>
|
append-to-body
|
||||||
|
destroy-on-close
|
||||||
|
width="1100px"
|
||||||
|
top="10px"
|
||||||
|
class="project-change-record-detail-dialog"
|
||||||
|
>
|
||||||
|
<div v-if="changeRecordDetail" class="project-change-record-detail-meta">
|
||||||
|
<span>变更日期:{{ changeRecordDetail.changeDate || '-' }}</span>
|
||||||
|
<span>变更账号:{{ changeRecordDetail.handler || '-' }}</span>
|
||||||
|
</div>
|
||||||
|
<el-table :data="changeRecordDetailRows" border :show-overflow-tooltip="false">
|
||||||
|
<el-table-column prop="field" label="变更字段" min-width="180" />
|
||||||
|
<el-table-column
|
||||||
|
prop="before"
|
||||||
|
label="变更前"
|
||||||
|
min-width="360"
|
||||||
|
class-name="project-change-record-detail-value"
|
||||||
|
/>
|
||||||
|
<el-table-column
|
||||||
|
prop="after"
|
||||||
|
label="变更后"
|
||||||
|
min-width="500"
|
||||||
|
class-name="project-change-record-detail-value"
|
||||||
|
/>
|
||||||
|
</el-table>
|
||||||
|
<el-empty
|
||||||
|
v-if="!changeRecordDetailRows.length"
|
||||||
|
description="暂无变更内容"
|
||||||
|
:image-size="60"
|
||||||
|
/>
|
||||||
|
<template #footer>
|
||||||
|
<el-button type="primary" @click="changeRecordDetailVisible = false">关闭</el-button>
|
||||||
|
</template>
|
||||||
|
</el-dialog>
|
||||||
|
|
||||||
<template v-if="isChangeDialog">
|
<template v-if="isChangeDialog">
|
||||||
<div class="dialog-section-title">变更原因</div>
|
<div class="dialog-section-title">变更原因</div>
|
||||||
@@ -757,13 +784,15 @@
|
|||||||
</el-form>
|
</el-form>
|
||||||
|
|
||||||
<div
|
<div
|
||||||
v-if="!isPublicViewPage"
|
|
||||||
class="project-apply-dialog__footer"
|
class="project-apply-dialog__footer"
|
||||||
:class="{ 'project-apply-dialog__footer--change': isChangeDialog }"
|
:class="{ 'project-apply-dialog__footer--change': isChangeDialog }"
|
||||||
>
|
>
|
||||||
<template v-if="isChangeDialog">
|
<template v-if="isChangeDialog">
|
||||||
<el-button v-if="!isProjectFormPage" @click="handleCancelProject">取消</el-button>
|
<el-button v-if="!isProjectFormPage" @click="handleCancelProject">取消</el-button>
|
||||||
<el-button v-if="isProjectFormPage" @click="closeProjectForm">取消</el-button>
|
<el-button v-if="isProjectFormPage" @click="closeProjectForm">取消</el-button>
|
||||||
|
<el-button type="primary" plain :loading="submitLoading" @click="saveChangeProject">
|
||||||
|
保存
|
||||||
|
</el-button>
|
||||||
<el-button type="primary" :loading="submitLoading" @click="submitChangeProject">
|
<el-button type="primary" :loading="submitLoading" @click="submitChangeProject">
|
||||||
提交
|
提交
|
||||||
</el-button>
|
</el-button>
|
||||||
@@ -880,29 +909,12 @@
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</el-dialog>
|
</el-dialog>
|
||||||
|
|
||||||
<el-dialog
|
|
||||||
v-model="publicCustomerVisible"
|
|
||||||
title="查看客商档案"
|
|
||||||
append-to-body
|
|
||||||
destroy-on-close
|
|
||||||
width="92%"
|
|
||||||
top="4vh"
|
|
||||||
class="project-apply-public-customer-dialog"
|
|
||||||
>
|
|
||||||
<customer-archive
|
|
||||||
v-if="publicCustomerVisible"
|
|
||||||
:embedded-public-id="publicCustomerId"
|
|
||||||
/>
|
|
||||||
</el-dialog>
|
|
||||||
</basic-container>
|
</basic-container>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import { exportBlob } from '@/api/common';
|
import { exportBlob } from '@/api/common';
|
||||||
import { defineAsyncComponent } from 'vue';
|
|
||||||
import * as api from '@/api/business/project-apply';
|
import * as api from '@/api/business/project-apply';
|
||||||
import { getMkPublicDetail } from '@/api/mk-process';
|
|
||||||
import {
|
import {
|
||||||
getList as getCustomerArchiveList,
|
getList as getCustomerArchiveList,
|
||||||
getDetail as getCustomerArchiveDetail,
|
getDetail as getCustomerArchiveDetail,
|
||||||
@@ -917,7 +929,6 @@ import { submitMkApprovalFlow } from '@/utils/mk-approval';
|
|||||||
import { ElImageViewer } from 'element-plus';
|
import { ElImageViewer } from 'element-plus';
|
||||||
import { OpenFileViewer } from '@open-file-viewer/vue';
|
import { OpenFileViewer } from '@open-file-viewer/vue';
|
||||||
import PdfPreview from '@/components/pdf-preview/main.vue';
|
import PdfPreview from '@/components/pdf-preview/main.vue';
|
||||||
import ChangeRecordDetailDialog from '@/components/change-record-detail-dialog/main.vue';
|
|
||||||
import {
|
import {
|
||||||
fallbackPlugin,
|
fallbackPlugin,
|
||||||
imagePlugin,
|
imagePlugin,
|
||||||
@@ -1034,10 +1045,7 @@ const majorProjectAttachmentTypeOptions = [
|
|||||||
].map(item => ({ label: item, value: item })).concat(otherAttachmentType);
|
].map(item => ({ label: item, value: item })).concat(otherAttachmentType);
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'ProjectApply',
|
|
||||||
components: {
|
components: {
|
||||||
CustomerArchive: defineAsyncComponent(() => import('@/views/vehicle/customer-archive.vue')),
|
|
||||||
ChangeRecordDetailDialog,
|
|
||||||
ElImageViewer,
|
ElImageViewer,
|
||||||
OpenFileViewer,
|
OpenFileViewer,
|
||||||
PdfPreview,
|
PdfPreview,
|
||||||
@@ -1231,8 +1239,6 @@ export default {
|
|||||||
changeRecordDetail: null,
|
changeRecordDetail: null,
|
||||||
changeRecordDetailRows: [],
|
changeRecordDetailRows: [],
|
||||||
userBox: false,
|
userBox: false,
|
||||||
publicCustomerVisible: false,
|
|
||||||
publicCustomerId: '',
|
|
||||||
userPickType: '',
|
userPickType: '',
|
||||||
userLoading: false,
|
userLoading: false,
|
||||||
userData: [],
|
userData: [],
|
||||||
@@ -1309,9 +1315,6 @@ export default {
|
|||||||
isProjectFormPage() {
|
isProjectFormPage() {
|
||||||
return this.isFormPageInstance;
|
return this.isFormPageInstance;
|
||||||
},
|
},
|
||||||
isPublicViewPage() {
|
|
||||||
return this.$route.path === '/business/project-apply/public-view';
|
|
||||||
},
|
|
||||||
projectFormContainer() {
|
projectFormContainer() {
|
||||||
// 表单页实例始终用页面容器,避免 keep-alive 失活时误切弹窗
|
// 表单页实例始终用页面容器,避免 keep-alive 失活时误切弹窗
|
||||||
return this.isFormPageInstance ? 'div' : 'el-dialog';
|
return this.isFormPageInstance ? 'div' : 'el-dialog';
|
||||||
@@ -1358,11 +1361,6 @@ export default {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
created() {
|
created() {
|
||||||
if (this.isPublicViewPage) {
|
|
||||||
this.isFormPageInstance = true;
|
|
||||||
this.openPublicProjectForm();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
this.loadDeptOptions();
|
this.loadDeptOptions();
|
||||||
this.loadCargoTypeOptions();
|
this.loadCargoTypeOptions();
|
||||||
this.loadTransportTypeOptions();
|
this.loadTransportTypeOptions();
|
||||||
@@ -1386,7 +1384,6 @@ export default {
|
|||||||
this.attachmentDocumentPreviewVisible = false;
|
this.attachmentDocumentPreviewVisible = false;
|
||||||
this.attachmentImagePreviewVisible = false;
|
this.attachmentImagePreviewVisible = false;
|
||||||
this.userBox = false;
|
this.userBox = false;
|
||||||
this.publicCustomerVisible = false;
|
|
||||||
},
|
},
|
||||||
buildTableOption() {
|
buildTableOption() {
|
||||||
return {
|
return {
|
||||||
@@ -1409,75 +1406,6 @@ export default {
|
|||||||
hasPermission(code) {
|
hasPermission(code) {
|
||||||
return this.isAdmin || this.validData(this.permission && this.permission[code], false);
|
return this.isAdmin || this.validData(this.permission && this.permission[code], false);
|
||||||
},
|
},
|
||||||
openPublicProjectForm() {
|
|
||||||
const id = this.$route.query.id;
|
|
||||||
this.dialogType = 'view';
|
|
||||||
this.dialogReadonly = true;
|
|
||||||
this.projectBox = true;
|
|
||||||
if (!id) {
|
|
||||||
this.$message.error('缺少项目ID');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
getMkPublicDetail('project-apply', id)
|
|
||||||
.then(res => {
|
|
||||||
const detail = res.data?.data || {};
|
|
||||||
this.applyPublicDictOptions(detail);
|
|
||||||
const displayDetail = { ...detail };
|
|
||||||
delete displayDetail.cargoTypeOptions;
|
|
||||||
delete displayDetail.transportTypeOptions;
|
|
||||||
delete displayDetail.settlementModeOptions;
|
|
||||||
this.applyProjectDetail(displayDetail);
|
|
||||||
})
|
|
||||||
.catch(() => {
|
|
||||||
this.$message.error('项目信息加载失败');
|
|
||||||
});
|
|
||||||
},
|
|
||||||
applyPublicDictOptions(detail = {}) {
|
|
||||||
this.cargoTypeOptions = this.resolvePublicDictOptions(
|
|
||||||
detail.cargoTypeOptions,
|
|
||||||
detail.cargoType
|
|
||||||
);
|
|
||||||
this.transportTypeOptions = this.resolvePublicDictOptions(
|
|
||||||
detail.transportTypeOptions,
|
|
||||||
detail.transportType
|
|
||||||
);
|
|
||||||
this.settlementModeOptions = this.resolvePublicDictOptions(
|
|
||||||
detail.settlementModeOptions,
|
|
||||||
detail.settlementMode
|
|
||||||
);
|
|
||||||
if (detail.businessDeptId) {
|
|
||||||
const label = detail.businessDeptName || String(detail.businessDeptId);
|
|
||||||
this.businessDeptTreeOptions = [{ label, value: detail.businessDeptId }];
|
|
||||||
this.deptOptions = [{ label, rawLabel: label, value: detail.businessDeptId }];
|
|
||||||
}
|
|
||||||
if (detail.undertakeDeptId) {
|
|
||||||
this.platformCompanyOptions = [
|
|
||||||
{
|
|
||||||
label: detail.undertakeDeptName || String(detail.undertakeDeptId),
|
|
||||||
value: detail.undertakeDeptId,
|
|
||||||
},
|
|
||||||
];
|
|
||||||
}
|
|
||||||
},
|
|
||||||
resolvePublicDictOptions(options, currentValue) {
|
|
||||||
const list = Array.isArray(options)
|
|
||||||
? options
|
|
||||||
.filter(item => item && item.value !== undefined && item.value !== null && item.value !== '')
|
|
||||||
.map(item => ({
|
|
||||||
label: item.label || String(item.value),
|
|
||||||
value: item.value,
|
|
||||||
}))
|
|
||||||
: [];
|
|
||||||
if (
|
|
||||||
currentValue !== undefined &&
|
|
||||||
currentValue !== null &&
|
|
||||||
currentValue !== '' &&
|
|
||||||
!list.some(item => String(item.value) === String(currentValue))
|
|
||||||
) {
|
|
||||||
list.unshift({ label: String(currentValue), value: currentValue });
|
|
||||||
}
|
|
||||||
return list;
|
|
||||||
},
|
|
||||||
openProjectFormPage() {
|
openProjectFormPage() {
|
||||||
const type = this.$route.query.mode || 'add';
|
const type = this.$route.query.mode || 'add';
|
||||||
const id = this.$route.query.id;
|
const id = this.$route.query.id;
|
||||||
@@ -1492,7 +1420,6 @@ export default {
|
|||||||
this.fillDefaultUsers();
|
this.fillDefaultUsers();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (!id) return;
|
|
||||||
this.api.getDetail(id).then(res => {
|
this.api.getDetail(id).then(res => {
|
||||||
const detail = res.data.data || {};
|
const detail = res.data.data || {};
|
||||||
const isChange = type === 'change';
|
const isChange = type === 'change';
|
||||||
@@ -1905,7 +1832,13 @@ export default {
|
|||||||
this.closeProjectForm();
|
this.closeProjectForm();
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
saveChangeProject() {
|
||||||
|
this.submitChangeForm(false);
|
||||||
|
},
|
||||||
submitChangeProject() {
|
submitChangeProject() {
|
||||||
|
this.submitChangeForm(true);
|
||||||
|
},
|
||||||
|
submitChangeForm(needSubmit) {
|
||||||
this.$refs.projectForm.validate(valid => {
|
this.$refs.projectForm.validate(valid => {
|
||||||
if (!valid) {
|
if (!valid) {
|
||||||
this.handleValidateFail();
|
this.handleValidateFail();
|
||||||
@@ -1913,12 +1846,19 @@ export default {
|
|||||||
}
|
}
|
||||||
if (!this.validateAttachmentFileTypes()) return;
|
if (!this.validateAttachmentFileTypes()) return;
|
||||||
this.submitLoading = true;
|
this.submitLoading = true;
|
||||||
// 变更记录仅在提交时生成,保存草稿不落记录
|
const request = needSubmit ? this.api.submitChange : this.api.saveChange;
|
||||||
this.api
|
request(this.normalizeSubmitForm({ includeChangeType: true }))
|
||||||
.submitChange(this.normalizeSubmitForm({ includeChangeType: true }))
|
|
||||||
.then(res => {
|
.then(res => {
|
||||||
if (res.data?.success === false || res.data?.data === false) {
|
if (res.data?.success === false || res.data?.data === false) {
|
||||||
this.$message.error(res.data?.msg || '提交失败,请联系管理员');
|
this.$message.error(
|
||||||
|
res.data?.msg || `${needSubmit ? '提交' : '保存'}失败,请联系管理员`
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!needSubmit) {
|
||||||
|
this.$message.success('保存成功!');
|
||||||
|
this.closeProjectForm();
|
||||||
|
this.onLoad(this.page, this.query);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const id = this.form.id;
|
const id = this.form.id;
|
||||||
@@ -2072,11 +2012,6 @@ export default {
|
|||||||
this.$message.warning('客商档案 ID 为空,无法打开');
|
this.$message.warning('客商档案 ID 为空,无法打开');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (this.isPublicViewPage) {
|
|
||||||
this.publicCustomerId = String(id);
|
|
||||||
this.publicCustomerVisible = true;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
this.$router.push({
|
this.$router.push({
|
||||||
path: '/vehicle/customer-archive/form',
|
path: '/vehicle/customer-archive/form',
|
||||||
query: { id: String(id), name: '查看客商档案', view: '1' },
|
query: { id: String(id), name: '查看客商档案', view: '1' },
|
||||||
@@ -2111,11 +2046,6 @@ export default {
|
|||||||
normalizeReadonlyAmount(value) {
|
normalizeReadonlyAmount(value) {
|
||||||
return this.normalizeOptionalSentinel(value);
|
return this.normalizeOptionalSentinel(value);
|
||||||
},
|
},
|
||||||
formatGuangxiTop100(value) {
|
|
||||||
if (value === 1 || value === '1') return '是';
|
|
||||||
if (value === 0 || value === '0') return '否';
|
|
||||||
return value || '';
|
|
||||||
},
|
|
||||||
normalizeOptionalSentinel(value, emptyValue = '') {
|
normalizeOptionalSentinel(value, emptyValue = '') {
|
||||||
return value === null || value === undefined || value === '' || Number(value) === -1
|
return value === null || value === undefined || value === '' || Number(value) === -1
|
||||||
? emptyValue
|
? emptyValue
|
||||||
@@ -2522,8 +2452,6 @@ export default {
|
|||||||
id: item.id,
|
id: item.id,
|
||||||
credit: item.maxCreditLimit || item.applyCreditLimit || '',
|
credit: item.maxCreditLimit || item.applyCreditLimit || '',
|
||||||
companyNature: item.customerNature || '',
|
companyNature: item.customerNature || '',
|
||||||
guangxiTop100:
|
|
||||||
item.guangxiTop100 === 0 || item.guangxiTop100 === 1 ? item.guangxiTop100 : null,
|
|
||||||
legalPerson: item.legalPerson || '',
|
legalPerson: item.legalPerson || '',
|
||||||
address: item.registeredAddress || '',
|
address: item.registeredAddress || '',
|
||||||
contact: defaultContact.contactName || item.principal || '',
|
contact: defaultContact.contactName || item.principal || '',
|
||||||
@@ -2666,12 +2594,6 @@ export default {
|
|||||||
this.$message.warning('项目ID为空,无法查看变更详情');
|
this.$message.warning('项目ID为空,无法查看变更详情');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (this.isPublicViewPage) {
|
|
||||||
this.changeRecordDetail = { ...row };
|
|
||||||
this.changeRecordDetailRows = this.buildChangeRecordDetailRows(this.changeRecordDetail);
|
|
||||||
this.changeRecordDetailVisible = true;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
try {
|
try {
|
||||||
if (!this.transportTypeOptions.length) {
|
if (!this.transportTypeOptions.length) {
|
||||||
await this.loadTransportTypeOptions();
|
await this.loadTransportTypeOptions();
|
||||||
@@ -2728,12 +2650,6 @@ export default {
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
.project-apply-page.is-public-view {
|
|
||||||
:deep(.el-link) {
|
|
||||||
pointer-events: auto;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.project-apply-form {
|
.project-apply-form {
|
||||||
padding: 0;
|
padding: 0;
|
||||||
|
|
||||||
@@ -2966,6 +2882,27 @@ export default {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
:deep(.project-change-record-detail-dialog .el-dialog__body) {
|
||||||
|
max-height: 65vh;
|
||||||
|
overflow: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.project-change-record-detail-dialog .project-change-record-detail-value .cell) {
|
||||||
|
white-space: pre-wrap;
|
||||||
|
word-break: break-word;
|
||||||
|
overflow: visible;
|
||||||
|
text-overflow: clip;
|
||||||
|
line-height: 1.6;
|
||||||
|
}
|
||||||
|
|
||||||
|
.project-change-record-detail-meta {
|
||||||
|
display: flex;
|
||||||
|
gap: 32px;
|
||||||
|
margin-bottom: 16px;
|
||||||
|
color: #606266;
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
:global(.project-apply-dialog .el-dialog__body) {
|
:global(.project-apply-dialog .el-dialog__body) {
|
||||||
max-height: 76vh;
|
max-height: 76vh;
|
||||||
overflow-y: auto;
|
overflow-y: auto;
|
||||||
@@ -3082,11 +3019,3 @@ export default {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
<style lang="scss">
|
|
||||||
.project-apply-public-customer-dialog .el-dialog__body {
|
|
||||||
max-height: 78vh;
|
|
||||||
overflow: auto;
|
|
||||||
padding-top: 8px;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
|
|||||||
@@ -99,25 +99,13 @@
|
|||||||
<el-table :data="attachmentRows" border @selection-change="selectedAttachments = $event">
|
<el-table :data="attachmentRows" border @selection-change="selectedAttachments = $event">
|
||||||
<el-table-column type="selection" width="55" align="center" />
|
<el-table-column type="selection" width="55" align="center" />
|
||||||
<el-table-column type="index" label="序号" width="70" align="center" />
|
<el-table-column type="index" label="序号" width="70" align="center" />
|
||||||
<el-table-column label="文件名称" min-width="240" show-overflow-tooltip>
|
<el-table-column label="文件名" min-width="240" show-overflow-tooltip>
|
||||||
<template #default="{ row }">
|
<template #default="{ row }">
|
||||||
<el-link type="primary" @click="previewAttachment(row)">
|
<el-link type="primary" @click="previewAttachment(row)">
|
||||||
{{ attachmentName(row) }}
|
{{ attachmentName(row) }}
|
||||||
</el-link>
|
</el-link>
|
||||||
</template>
|
</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
<el-table-column label="附件描述" min-width="220">
|
|
||||||
<template #default="{ row }">
|
|
||||||
<span v-if="dialogReadonly">{{ row.description || '-' }}</span>
|
|
||||||
<el-input
|
|
||||||
v-else
|
|
||||||
v-model="row.description"
|
|
||||||
maxlength="200"
|
|
||||||
placeholder="请输入"
|
|
||||||
@input="syncAttachmentsJson"
|
|
||||||
/>
|
|
||||||
</template>
|
|
||||||
</el-table-column>
|
|
||||||
<el-table-column label="文件大小" width="120" align="center">
|
<el-table-column label="文件大小" width="120" align="center">
|
||||||
<template #default="{ row }">{{ formatFileSize(row.size) }}</template>
|
<template #default="{ row }">{{ formatFileSize(row.size) }}</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
@@ -184,7 +172,7 @@
|
|||||||
title="查看临时额度申请"
|
title="查看临时额度申请"
|
||||||
append-to-body
|
append-to-body
|
||||||
destroy-on-close
|
destroy-on-close
|
||||||
:width="formDialogWidth"
|
width="96%"
|
||||||
class="temporary-credit-limit-dialog temporary-credit-limit-detail-dialog"
|
class="temporary-credit-limit-dialog temporary-credit-limit-detail-dialog"
|
||||||
>
|
>
|
||||||
<div v-loading="detailLoading" class="business-crud-page__detail-content">
|
<div v-loading="detailLoading" class="business-crud-page__detail-content">
|
||||||
@@ -216,16 +204,13 @@
|
|||||||
</div>
|
</div>
|
||||||
<el-table :data="attachmentRows" empty-text="暂无附件">
|
<el-table :data="attachmentRows" empty-text="暂无附件">
|
||||||
<el-table-column type="index" label="序号" width="70" align="center" />
|
<el-table-column type="index" label="序号" width="70" align="center" />
|
||||||
<el-table-column label="文件名称" min-width="240" show-overflow-tooltip>
|
<el-table-column label="文件名" min-width="240" show-overflow-tooltip>
|
||||||
<template #default="{ row }">
|
<template #default="{ row }">
|
||||||
<el-link type="primary" @click="previewAttachment(row)">
|
<el-link type="primary" @click="previewAttachment(row)">
|
||||||
{{ attachmentName(row) }}
|
{{ attachmentName(row) }}
|
||||||
</el-link>
|
</el-link>
|
||||||
</template>
|
</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
<el-table-column label="附件描述" min-width="220" show-overflow-tooltip>
|
|
||||||
<template #default="{ row }">{{ row.description || '-' }}</template>
|
|
||||||
</el-table-column>
|
|
||||||
<el-table-column label="文件大小" width="120" align="center">
|
<el-table-column label="文件大小" width="120" align="center">
|
||||||
<template #default="{ row }">{{ formatFileSize(row.size) }}</template>
|
<template #default="{ row }">{{ formatFileSize(row.size) }}</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
@@ -415,13 +400,6 @@ export default {
|
|||||||
detailSections() {
|
detailSections() {
|
||||||
return this.config.detailSections || [];
|
return this.config.detailSections || [];
|
||||||
},
|
},
|
||||||
formDialogWidth() {
|
|
||||||
const width = this.tableOption?.dialogWidth ?? option.dialogWidth ?? 1100;
|
|
||||||
if (typeof width === 'number' || /^\d+$/.test(String(width))) {
|
|
||||||
return `${width}px`;
|
|
||||||
}
|
|
||||||
return String(width);
|
|
||||||
},
|
|
||||||
isAdmin() {
|
isAdmin() {
|
||||||
const authority = this.userInfo?.authority;
|
const authority = this.userInfo?.authority;
|
||||||
return Array.isArray(authority)
|
return Array.isArray(authority)
|
||||||
@@ -617,12 +595,7 @@ export default {
|
|||||||
return value;
|
return value;
|
||||||
},
|
},
|
||||||
applyDetail(detail) {
|
applyDetail(detail) {
|
||||||
this.form = {
|
this.form = { ...detail };
|
||||||
...detail,
|
|
||||||
projectFundLimit: this.blankSentinelAmount(detail.projectFundLimit),
|
|
||||||
usedFundLimit: this.blankSentinelAmount(detail.usedFundLimit),
|
|
||||||
remainingFundLimit: this.blankSentinelAmount(detail.remainingFundLimit),
|
|
||||||
};
|
|
||||||
this.selectedProjectId = detail.projectId || '';
|
this.selectedProjectId = detail.projectId || '';
|
||||||
this.attachmentRows = this.parseJsonArray(detail.attachmentsJson);
|
this.attachmentRows = this.parseJsonArray(detail.attachmentsJson);
|
||||||
this.selectedAttachments = [];
|
this.selectedAttachments = [];
|
||||||
@@ -632,9 +605,6 @@ export default {
|
|||||||
const { projectQuotaInfoTitle, temporaryCreditInfoTitle, ...payload } = row || {};
|
const { projectQuotaInfoTitle, temporaryCreditInfoTitle, ...payload } = row || {};
|
||||||
return {
|
return {
|
||||||
...payload,
|
...payload,
|
||||||
projectFundLimit: this.toOptionalAmount(payload.projectFundLimit),
|
|
||||||
usedFundLimit: this.toOptionalAmount(payload.usedFundLimit),
|
|
||||||
remainingFundLimit: this.toOptionalAmount(payload.remainingFundLimit),
|
|
||||||
applyLimit: payload.applyLimit === '' ? '' : Number(payload.applyLimit),
|
applyLimit: payload.applyLimit === '' ? '' : Number(payload.applyLimit),
|
||||||
attachmentsJson: JSON.stringify(this.attachmentRows),
|
attachmentsJson: JSON.stringify(this.attachmentRows),
|
||||||
};
|
};
|
||||||
@@ -780,8 +750,8 @@ export default {
|
|||||||
projectName: project.projectName || '',
|
projectName: project.projectName || '',
|
||||||
projectCode: project.projectCode || '',
|
projectCode: project.projectCode || '',
|
||||||
undertakeDeptName: project.undertakeDeptName || '',
|
undertakeDeptName: project.undertakeDeptName || '',
|
||||||
projectFundLimit: this.blankSentinelAmount(project.fundLimit ?? project.projectFundLimit),
|
projectFundLimit: project.fundLimit || project.projectFundLimit || 0,
|
||||||
usedFundLimit: this.blankSentinelAmount(project.usedFundLimit),
|
usedFundLimit: project.usedFundLimit || 0,
|
||||||
});
|
});
|
||||||
this.form.remainingFundLimit = this.calculateRemainingFundLimit();
|
this.form.remainingFundLimit = this.calculateRemainingFundLimit();
|
||||||
getProjectDetail(project.id).then(res => {
|
getProjectDetail(project.id).then(res => {
|
||||||
@@ -789,32 +759,18 @@ export default {
|
|||||||
Object.assign(this.form, {
|
Object.assign(this.form, {
|
||||||
projectCode: detail.projectCode || this.form.projectCode,
|
projectCode: detail.projectCode || this.form.projectCode,
|
||||||
undertakeDeptName: detail.undertakeDeptName || this.form.undertakeDeptName,
|
undertakeDeptName: detail.undertakeDeptName || this.form.undertakeDeptName,
|
||||||
projectFundLimit: this.blankSentinelAmount(
|
projectFundLimit:
|
||||||
detail.fundLimit ?? detail.projectFundLimit ?? this.form.projectFundLimit
|
detail.fundLimit ?? detail.projectFundLimit ?? this.form.projectFundLimit,
|
||||||
),
|
usedFundLimit: detail.usedFundLimit ?? this.form.usedFundLimit,
|
||||||
usedFundLimit: this.blankSentinelAmount(
|
|
||||||
detail.usedFundLimit ?? this.form.usedFundLimit
|
|
||||||
),
|
|
||||||
});
|
});
|
||||||
this.form.remainingFundLimit = this.calculateRemainingFundLimit();
|
this.form.remainingFundLimit = this.calculateRemainingFundLimit();
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
blankSentinelAmount(value) {
|
|
||||||
if (value === undefined || value === null || value === '') return '';
|
|
||||||
return Number(value) === -1 ? '' : value;
|
|
||||||
},
|
|
||||||
toOptionalAmount(value) {
|
|
||||||
const amount = this.blankSentinelAmount(value);
|
|
||||||
return amount === '' ? null : amount;
|
|
||||||
},
|
|
||||||
calculateRemainingFundLimit() {
|
calculateRemainingFundLimit() {
|
||||||
const total = this.blankSentinelAmount(this.form.projectFundLimit);
|
const total = Number(this.form.projectFundLimit);
|
||||||
const used = this.blankSentinelAmount(this.form.usedFundLimit);
|
const used = Number(this.form.usedFundLimit);
|
||||||
if (total === '' || used === '') return '';
|
if (!Number.isFinite(total) || !Number.isFinite(used)) return '';
|
||||||
const totalAmount = Number(total);
|
return Math.round((total - used + Number.EPSILON) * 100) / 100;
|
||||||
const usedAmount = Number(used);
|
|
||||||
if (!Number.isFinite(totalAmount) || !Number.isFinite(usedAmount)) return '';
|
|
||||||
return Math.round((totalAmount - usedAmount + Number.EPSILON) * 100) / 100;
|
|
||||||
},
|
},
|
||||||
handleAmountInput(value) {
|
handleAmountInput(value) {
|
||||||
const text = String(value || '').replace(/[^\d.]/g, '');
|
const text = String(value || '').replace(/[^\d.]/g, '');
|
||||||
@@ -827,37 +783,24 @@ export default {
|
|||||||
const uploadTime = this.$dayjs().format('YYYY-MM-DD HH:mm:ss');
|
const uploadTime = this.$dayjs().format('YYYY-MM-DD HH:mm:ss');
|
||||||
this.attachmentRows = (list || []).map(item => ({
|
this.attachmentRows = (list || []).map(item => ({
|
||||||
...item,
|
...item,
|
||||||
description: item.description || '',
|
|
||||||
uploadUserName: item.uploadUserName || uploadUserName,
|
uploadUserName: item.uploadUserName || uploadUserName,
|
||||||
uploadTime: item.uploadTime || uploadTime,
|
uploadTime: item.uploadTime || uploadTime,
|
||||||
}));
|
}));
|
||||||
this.syncAttachmentsJson();
|
this.form.attachmentsJson = JSON.stringify(this.attachmentRows);
|
||||||
},
|
},
|
||||||
removeAttachment(index) {
|
removeAttachment(index) {
|
||||||
this.attachmentRows.splice(index, 1);
|
this.attachmentRows.splice(index, 1);
|
||||||
this.syncAttachmentsJson();
|
this.form.attachmentsJson = JSON.stringify(this.attachmentRows);
|
||||||
},
|
|
||||||
syncAttachmentsJson() {
|
|
||||||
this.form.attachmentsJson = JSON.stringify(this.attachmentRows || []);
|
|
||||||
},
|
},
|
||||||
parseJsonArray(value) {
|
parseJsonArray(value) {
|
||||||
let list = [];
|
if (Array.isArray(value)) return value;
|
||||||
if (Array.isArray(value)) {
|
if (!value) return [];
|
||||||
list = value;
|
try {
|
||||||
} else if (!value) {
|
const data = JSON.parse(value);
|
||||||
|
return Array.isArray(data) ? data : [];
|
||||||
|
} catch (error) {
|
||||||
return [];
|
return [];
|
||||||
} else {
|
|
||||||
try {
|
|
||||||
const data = JSON.parse(value);
|
|
||||||
list = Array.isArray(data) ? data : [];
|
|
||||||
} catch (error) {
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return list.map(item => ({
|
|
||||||
...item,
|
|
||||||
description: item?.description || '',
|
|
||||||
}));
|
|
||||||
},
|
},
|
||||||
attachmentName(row = {}) {
|
attachmentName(row = {}) {
|
||||||
return row.originalName || row.name || row.fileName || '附件';
|
return row.originalName || row.name || row.fileName || '附件';
|
||||||
@@ -963,8 +906,7 @@ export default {
|
|||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
.temporary-credit-limit-page {
|
.temporary-credit-limit-page {
|
||||||
&__field {
|
&__field {
|
||||||
width: 240px;
|
width: 100%;
|
||||||
max-width: 100%;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
&__attachment-head {
|
&__attachment-head {
|
||||||
@@ -1051,7 +993,7 @@ export default {
|
|||||||
align-items: center;
|
align-items: center;
|
||||||
gap: 8px;
|
gap: 8px;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
padding: 14px 0 4px;
|
padding: 14px 16px 4px;
|
||||||
margin-bottom: 0;
|
margin-bottom: 0;
|
||||||
color: #303133;
|
color: #303133;
|
||||||
font-size: 15px;
|
font-size: 15px;
|
||||||
@@ -1144,51 +1086,17 @@ export default {
|
|||||||
background: transparent !important;
|
background: transparent !important;
|
||||||
box-shadow: none !important;
|
box-shadow: none !important;
|
||||||
margin: 0 !important;
|
margin: 0 !important;
|
||||||
padding: 0 16px 8px !important;
|
padding: 0 !important;
|
||||||
border-radius: 0 !important;
|
border-radius: 0 !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 新增/编辑:input/select 与「项目名称」同宽(240px),备注 textarea 保持整行
|
|
||||||
&:not(.temporary-credit-limit-detail-dialog) {
|
|
||||||
.el-form-item__content {
|
|
||||||
min-width: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.el-form-item__content > .el-input,
|
|
||||||
.el-form-item__content > .el-select,
|
|
||||||
.el-form-item__content > .el-date-editor,
|
|
||||||
.el-form-item__content > .el-cascader,
|
|
||||||
.el-form-item__content > div > .el-input,
|
|
||||||
.el-form-item__content > div > .el-select,
|
|
||||||
.el-form-item__content > div > .el-date-editor,
|
|
||||||
.el-form-item__content > div > .el-cascader,
|
|
||||||
.temporary-credit-limit-page__field {
|
|
||||||
width: 240px !important;
|
|
||||||
max-width: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.el-form-item__content > .el-textarea,
|
|
||||||
.el-form-item__content > div > .el-textarea,
|
|
||||||
.el-form-item__content .el-textarea {
|
|
||||||
width: 100% !important;
|
|
||||||
max-width: 100% !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
.el-date-editor.el-input,
|
|
||||||
.el-date-editor.el-input__wrapper {
|
|
||||||
width: 240px !important;
|
|
||||||
max-width: 100%;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.temporary-credit-limit-dialog .business-crud-page__detail-content .el-descriptions__label {
|
.temporary-credit-limit-dialog .business-crud-page__detail-content .el-descriptions__label {
|
||||||
width: 180px !important;
|
width: 180px !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 新增/编辑/查看:宽度与高度规则统一(Avue setPx 对 '1100px' 会拼成 '1100pxpx',故 option 用数字 1100)
|
// 新增/编辑/查看:高度随内容自适应,超出时再滚动;去掉强制全屏导致的底部大片空白
|
||||||
.temporary-credit-limit-dialog.el-dialog {
|
.temporary-credit-limit-dialog.el-dialog {
|
||||||
width: 1100px !important;
|
|
||||||
margin-top: 20px !important;
|
margin-top: 20px !important;
|
||||||
margin-bottom: 20px !important;
|
margin-bottom: 20px !important;
|
||||||
height: auto !important;
|
height: auto !important;
|
||||||
|
|||||||
@@ -11,7 +11,7 @@
|
|||||||
{{ planData.businessStatusName || '-' }}
|
{{ planData.businessStatusName || '-' }}
|
||||||
</span>
|
</span>
|
||||||
<span class="detail-status-text detail-transport-type">
|
<span class="detail-status-text detail-transport-type">
|
||||||
{{ transportTypeLabel }}
|
{{ planData.transportTypeName || '公路运输' }}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="transport-plan-dispatch-page__summary-grid">
|
<div class="transport-plan-dispatch-page__summary-grid">
|
||||||
@@ -123,20 +123,16 @@
|
|||||||
</el-button>
|
</el-button>
|
||||||
</template>
|
</template>
|
||||||
<el-table
|
<el-table
|
||||||
:data="pagedDispatchList"
|
:data="dispatchList"
|
||||||
border
|
border
|
||||||
height="100%"
|
height="100%"
|
||||||
row-key="id"
|
row-key="id"
|
||||||
class="transport-plan-dispatch-page__table"
|
class="transport-plan-dispatch-page__table"
|
||||||
>
|
>
|
||||||
<el-table-column label="序号" width="70" align="center" fixed="left">
|
<el-table-column type="index" label="序号" width="70" align="center" fixed="left" />
|
||||||
<template #default="{ $index }">
|
|
||||||
{{ dispatchRowIndex($index) }}
|
|
||||||
</template>
|
|
||||||
</el-table-column>
|
|
||||||
<el-table-column label="运输方式" min-width="180" align="center" show-overflow-tooltip>
|
<el-table-column label="运输方式" min-width="180" align="center" show-overflow-tooltip>
|
||||||
<template #default="{ row }">
|
<template #default="{ row }">
|
||||||
{{ formatTransportType(row.transportTypeName || row.transportType) }}
|
{{ row.transportTypeName || '-' }}
|
||||||
</template>
|
</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
<el-table-column prop="carrierType" label="承运类型" min-width="150" align="center" show-overflow-tooltip />
|
<el-table-column prop="carrierType" label="承运类型" min-width="150" align="center" show-overflow-tooltip />
|
||||||
@@ -181,17 +177,6 @@
|
|||||||
</template>
|
</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
</el-table>
|
</el-table>
|
||||||
<div class="transport-plan-dispatch-page__pagination">
|
|
||||||
<el-pagination
|
|
||||||
v-model:current-page="dispatchPage.currentPage"
|
|
||||||
v-model:page-size="dispatchPage.pageSize"
|
|
||||||
:page-sizes="dispatchPage.pageSizes"
|
|
||||||
layout="total, sizes, prev, pager, next"
|
|
||||||
:total="dispatchList.length"
|
|
||||||
@current-change="handleDispatchCurrentChange"
|
|
||||||
@size-change="handleDispatchSizeChange"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</section-card>
|
</section-card>
|
||||||
|
|
||||||
<!-- 底部按钮 -->
|
<!-- 底部按钮 -->
|
||||||
@@ -217,7 +202,6 @@
|
|||||||
|
|
||||||
<script>
|
<script>
|
||||||
import { getDetail, dispatch } from '@/api/business/transport-plan';
|
import { getDetail, dispatch } from '@/api/business/transport-plan';
|
||||||
import { getDictionary } from '@/api/system/dictbiz';
|
|
||||||
import SectionCard from '@/components/section-card/main.vue';
|
import SectionCard from '@/components/section-card/main.vue';
|
||||||
import TransportPlanPage from './components/transport-plan-page.vue';
|
import TransportPlanPage from './components/transport-plan-page.vue';
|
||||||
import * as api from '@/api/business/transport-plan';
|
import * as api from '@/api/business/transport-plan';
|
||||||
@@ -225,12 +209,6 @@ import { config, option } from '@/option/business/transport-plan';
|
|||||||
|
|
||||||
const TRANSPORT_PLAN_QUANTITY_UNIT = '吨';
|
const TRANSPORT_PLAN_QUANTITY_UNIT = '吨';
|
||||||
|
|
||||||
const normalizeDictOptions = (list = []) =>
|
|
||||||
(Array.isArray(list) ? list : []).map(item => ({
|
|
||||||
label: item.dictValue || item.label || '',
|
|
||||||
value: item.dictKey ?? item.value ?? '',
|
|
||||||
}));
|
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'TransportPlanDispatch',
|
name: 'TransportPlanDispatch',
|
||||||
components: {
|
components: {
|
||||||
@@ -245,12 +223,6 @@ export default {
|
|||||||
dispatchList: [],
|
dispatchList: [],
|
||||||
attachmentList: [],
|
attachmentList: [],
|
||||||
transportPlanPageVisible: false,
|
transportPlanPageVisible: false,
|
||||||
transportTypeOptions: [],
|
|
||||||
dispatchPage: {
|
|
||||||
currentPage: 1,
|
|
||||||
pageSize: 10,
|
|
||||||
pageSizes: [10, 20, 50, 100],
|
|
||||||
},
|
|
||||||
api,
|
api,
|
||||||
config,
|
config,
|
||||||
option,
|
option,
|
||||||
@@ -260,15 +232,6 @@ export default {
|
|||||||
planId() {
|
planId() {
|
||||||
return this.$route.query.planId;
|
return this.$route.query.planId;
|
||||||
},
|
},
|
||||||
transportTypeLabel() {
|
|
||||||
return this.formatTransportType(
|
|
||||||
this.planData.transportTypeName || this.planData.transportType
|
|
||||||
);
|
|
||||||
},
|
|
||||||
pagedDispatchList() {
|
|
||||||
const start = (this.dispatchPage.currentPage - 1) * this.dispatchPage.pageSize;
|
|
||||||
return this.dispatchList.slice(start, start + this.dispatchPage.pageSize);
|
|
||||||
},
|
|
||||||
statusTextClass() {
|
statusTextClass() {
|
||||||
const status = this.planData.businessStatus;
|
const status = this.planData.businessStatus;
|
||||||
if (status === 2 || status === '2') return 'status-text-success';
|
if (status === 2 || status === '2') return 'status-text-success';
|
||||||
@@ -343,34 +306,13 @@ export default {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
created() {
|
created() {
|
||||||
this.loadTransportTypeOptions().finally(() => {
|
this.loadPlanData();
|
||||||
this.loadPlanData();
|
|
||||||
});
|
|
||||||
},
|
},
|
||||||
mounted() {
|
mounted() {
|
||||||
// 初始化 transport-plan-page 组件用于打开弹窗
|
// 初始化 transport-plan-page 组件用于打开弹窗
|
||||||
this.transportPlanPageVisible = true;
|
this.transportPlanPageVisible = true;
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
loadTransportTypeOptions() {
|
|
||||||
return getDictionary({ code: 'transport_type' })
|
|
||||||
.then(res => {
|
|
||||||
this.transportTypeOptions = normalizeDictOptions(res.data?.data || []);
|
|
||||||
})
|
|
||||||
.catch(() => {
|
|
||||||
this.transportTypeOptions = [];
|
|
||||||
});
|
|
||||||
},
|
|
||||||
formatTransportType(value) {
|
|
||||||
const transportType = String(value || '').trim();
|
|
||||||
if (!transportType) return '-';
|
|
||||||
// 已是中文标签时直接展示
|
|
||||||
if (/[\u4e00-\u9fff]/.test(transportType)) return transportType;
|
|
||||||
const item = this.transportTypeOptions.find(
|
|
||||||
option => String(option.value ?? '').toLowerCase() === transportType.toLowerCase()
|
|
||||||
);
|
|
||||||
return item?.label || transportType;
|
|
||||||
},
|
|
||||||
loadPlanData() {
|
loadPlanData() {
|
||||||
if (!this.planId) {
|
if (!this.planId) {
|
||||||
this.$message.error('缺少计划ID参数');
|
this.$message.error('缺少计划ID参数');
|
||||||
@@ -385,7 +327,6 @@ export default {
|
|||||||
this.planData = res.data.data || {};
|
this.planData = res.data.data || {};
|
||||||
this.attachmentList = this.parseAttachments(this.planData.attachmentsJson);
|
this.attachmentList = this.parseAttachments(this.planData.attachmentsJson);
|
||||||
this.dispatchList = this.parseDispatchList(this.planData);
|
this.dispatchList = this.parseDispatchList(this.planData);
|
||||||
this.dispatchPage.currentPage = 1;
|
|
||||||
} else {
|
} else {
|
||||||
this.$message.error(res.data?.msg || '加载数据失败');
|
this.$message.error(res.data?.msg || '加载数据失败');
|
||||||
}
|
}
|
||||||
@@ -507,30 +448,8 @@ export default {
|
|||||||
}
|
}
|
||||||
this.openPageDispatchItemDialog(-1);
|
this.openPageDispatchItemDialog(-1);
|
||||||
},
|
},
|
||||||
dispatchRowIndex(index) {
|
|
||||||
return (this.dispatchPage.currentPage - 1) * this.dispatchPage.pageSize + index + 1;
|
|
||||||
},
|
|
||||||
resolveDispatchGlobalIndex(pageIndex) {
|
|
||||||
return (this.dispatchPage.currentPage - 1) * this.dispatchPage.pageSize + pageIndex;
|
|
||||||
},
|
|
||||||
syncDispatchPage() {
|
|
||||||
const maxPage = Math.max(
|
|
||||||
1,
|
|
||||||
Math.ceil(this.dispatchList.length / this.dispatchPage.pageSize) || 1
|
|
||||||
);
|
|
||||||
if (this.dispatchPage.currentPage > maxPage) {
|
|
||||||
this.dispatchPage.currentPage = maxPage;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
handleDispatchCurrentChange(currentPage) {
|
|
||||||
this.dispatchPage.currentPage = currentPage;
|
|
||||||
},
|
|
||||||
handleDispatchSizeChange(pageSize) {
|
|
||||||
this.dispatchPage.pageSize = pageSize;
|
|
||||||
this.dispatchPage.currentPage = 1;
|
|
||||||
},
|
|
||||||
handleEdit(row, index) {
|
handleEdit(row, index) {
|
||||||
this.openPageDispatchItemDialog(this.resolveDispatchGlobalIndex(index), row);
|
this.openPageDispatchItemDialog(index, row);
|
||||||
},
|
},
|
||||||
openPageDispatchItemDialog(index = -1, row) {
|
openPageDispatchItemDialog(index = -1, row) {
|
||||||
this.$nextTick(() => {
|
this.$nextTick(() => {
|
||||||
@@ -550,7 +469,6 @@ export default {
|
|||||||
originalSave.apply(component, args);
|
originalSave.apply(component, args);
|
||||||
if (!component.dispatchItemBox) {
|
if (!component.dispatchItemBox) {
|
||||||
this.dispatchList = [...(component.dispatchRows || [])];
|
this.dispatchList = [...(component.dispatchRows || [])];
|
||||||
this.syncDispatchPage();
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
component.__dispatchListSaveSynced = true;
|
component.__dispatchListSaveSynced = true;
|
||||||
@@ -561,8 +479,7 @@ export default {
|
|||||||
cancelButtonText: '取消',
|
cancelButtonText: '取消',
|
||||||
type: 'warning',
|
type: 'warning',
|
||||||
}).then(() => {
|
}).then(() => {
|
||||||
this.dispatchList.splice(this.resolveDispatchGlobalIndex(index), 1);
|
this.dispatchList.splice(index, 1);
|
||||||
this.syncDispatchPage();
|
|
||||||
this.$message.success('删除成功');
|
this.$message.success('删除成功');
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
@@ -865,12 +782,6 @@ export default {
|
|||||||
flex: 1;
|
flex: 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
&__pagination {
|
|
||||||
display: flex;
|
|
||||||
justify-content: flex-end;
|
|
||||||
padding-top: 12px;
|
|
||||||
}
|
|
||||||
|
|
||||||
&__actions {
|
&__actions {
|
||||||
display: flex;
|
display: flex;
|
||||||
gap: 8px;
|
gap: 8px;
|
||||||
|
|||||||
@@ -1,38 +0,0 @@
|
|||||||
<template>
|
|
||||||
<mk-public-shell biz-type="waybill-manage" :get-form="getForm">
|
|
||||||
<waybill-manage-page
|
|
||||||
ref="page"
|
|
||||||
:api="api"
|
|
||||||
:config="config"
|
|
||||||
:crud-option="option"
|
|
||||||
:detail-id="detailId"
|
|
||||||
standalone-detail-page
|
|
||||||
/>
|
|
||||||
</mk-public-shell>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
import MkPublicShell from '@/views/mk/mk-public-shell.vue';
|
|
||||||
import WaybillManagePage from '@/views/business/components/waybill-manage-page.vue';
|
|
||||||
import * as api from '@/api/business/waybill-manage';
|
|
||||||
import { config, option } from '@/option/business/waybill-manage';
|
|
||||||
|
|
||||||
export default {
|
|
||||||
name: 'WaybillManagePublicView',
|
|
||||||
components: { MkPublicShell, WaybillManagePage },
|
|
||||||
data() {
|
|
||||||
return { api, config, option };
|
|
||||||
},
|
|
||||||
computed: {
|
|
||||||
detailId() {
|
|
||||||
return this.$route.query.id || '';
|
|
||||||
},
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
getForm() {
|
|
||||||
const row = this.$refs.page?.detailRow || {};
|
|
||||||
return { ...row, subject: row.waybillNo || '' };
|
|
||||||
},
|
|
||||||
},
|
|
||||||
};
|
|
||||||
</script>
|
|
||||||
@@ -1,155 +0,0 @@
|
|||||||
<template>
|
|
||||||
<div ref="page" class="mk-public-shell">
|
|
||||||
<slot />
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
import { postMkPublicProcessMessage } from '@/api/mk-process';
|
|
||||||
|
|
||||||
export default {
|
|
||||||
name: 'MkPublicShell',
|
|
||||||
props: {
|
|
||||||
bizType: {
|
|
||||||
type: String,
|
|
||||||
required: true,
|
|
||||||
},
|
|
||||||
getForm: {
|
|
||||||
type: Function,
|
|
||||||
default: null,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
created() {
|
|
||||||
this.handleIframeHeight = () => this.sendIframeHeight();
|
|
||||||
this.handleProcessMessage = event => this.onProcessMessage(event);
|
|
||||||
document.addEventListener('DOMContentLoaded', this.handleIframeHeight, false);
|
|
||||||
window.addEventListener('message', this.handleProcessMessage);
|
|
||||||
},
|
|
||||||
mounted() {
|
|
||||||
document.documentElement.classList.add('mk-iframe-page');
|
|
||||||
document.body.classList.add('mk-iframe-page');
|
|
||||||
const app = document.getElementById('app');
|
|
||||||
if (app) app.classList.add('mk-iframe-page');
|
|
||||||
this.handleIframeHeight();
|
|
||||||
window.addEventListener('load', this.handleIframeHeight);
|
|
||||||
this.mkHeightTimers = [300, 800, 1600].map(delay => setTimeout(this.handleIframeHeight, delay));
|
|
||||||
if (typeof ResizeObserver === 'undefined') return;
|
|
||||||
this.mkHeightObserver = new ResizeObserver(() => this.handleIframeHeight());
|
|
||||||
this.$nextTick(() => {
|
|
||||||
if (this.$refs.page) this.mkHeightObserver.observe(this.$refs.page);
|
|
||||||
this.mkHeightObserver.observe(document.body);
|
|
||||||
});
|
|
||||||
},
|
|
||||||
beforeUnmount() {
|
|
||||||
document.removeEventListener('DOMContentLoaded', this.handleIframeHeight, false);
|
|
||||||
window.removeEventListener('load', this.handleIframeHeight);
|
|
||||||
window.removeEventListener('message', this.handleProcessMessage);
|
|
||||||
(this.mkHeightTimers || []).forEach(timer => clearTimeout(timer));
|
|
||||||
if (this.mkHeightObserver) {
|
|
||||||
this.mkHeightObserver.disconnect();
|
|
||||||
this.mkHeightObserver = null;
|
|
||||||
}
|
|
||||||
document.documentElement.classList.remove('mk-iframe-page');
|
|
||||||
document.body.classList.remove('mk-iframe-page');
|
|
||||||
const app = document.getElementById('app');
|
|
||||||
if (app) app.classList.remove('mk-iframe-page');
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
sendIframeHeight() {
|
|
||||||
this.$nextTick(() => {
|
|
||||||
window.parent.postMessage({ height: document.body.clientHeight }, '*');
|
|
||||||
});
|
|
||||||
},
|
|
||||||
onProcessMessage(event) {
|
|
||||||
const data = event && event.data;
|
|
||||||
if (!data || typeof data !== 'object') return;
|
|
||||||
if (data.height && !data.status && !data.type) return;
|
|
||||||
if (data.type === 'formValues' || data.type === 'afterSubmit') return;
|
|
||||||
const formValues = data.formValues;
|
|
||||||
if (data.status === 'submit') {
|
|
||||||
this.submitData(formValues);
|
|
||||||
} else if (data.status === 'save') {
|
|
||||||
this.saveData(formValues);
|
|
||||||
}
|
|
||||||
if (data.type === 'getFormValues') {
|
|
||||||
window.parent.postMessage({ type: 'formValues', formData: this.buildFormData() }, '*');
|
|
||||||
}
|
|
||||||
},
|
|
||||||
submitData(lbpmFormValues) {
|
|
||||||
const formData = this.buildFormData();
|
|
||||||
this.postFormData('submit', lbpmFormValues, formData);
|
|
||||||
if (!lbpmFormValues) return;
|
|
||||||
const parameters = Object.assign({}, lbpmFormValues, {
|
|
||||||
loginName: this.getLoginName(lbpmFormValues),
|
|
||||||
formInstanceId: this.getFormId(),
|
|
||||||
subject: formData.subject,
|
|
||||||
});
|
|
||||||
window.parent.postMessage({ type: 'afterSubmit', success: true, parameters }, '*');
|
|
||||||
},
|
|
||||||
saveData(lbpmFormValues) {
|
|
||||||
this.postFormData('save', lbpmFormValues, this.buildFormData());
|
|
||||||
},
|
|
||||||
postFormData(status, formValues, formData) {
|
|
||||||
postMkPublicProcessMessage(this.bizType, {
|
|
||||||
status,
|
|
||||||
formValues: formValues || {},
|
|
||||||
formData,
|
|
||||||
}).catch(error => {
|
|
||||||
console.error('公开页提交流程数据失败:', error);
|
|
||||||
});
|
|
||||||
},
|
|
||||||
buildFormData() {
|
|
||||||
const form = this.readForm();
|
|
||||||
return {
|
|
||||||
...form,
|
|
||||||
subject: form.subject || '',
|
|
||||||
formInstanceId: this.getFormId(),
|
|
||||||
};
|
|
||||||
},
|
|
||||||
readForm() {
|
|
||||||
const form = typeof this.getForm === 'function' ? this.getForm() : {};
|
|
||||||
if (!form) return {};
|
|
||||||
try {
|
|
||||||
return JSON.parse(JSON.stringify(form));
|
|
||||||
} catch (error) {
|
|
||||||
return { ...form };
|
|
||||||
}
|
|
||||||
},
|
|
||||||
getFormId() {
|
|
||||||
return this.$route.query.id || this.readForm().id || '';
|
|
||||||
},
|
|
||||||
getLoginName(formValues = {}) {
|
|
||||||
return (
|
|
||||||
formValues.loginName ||
|
|
||||||
this.$route.query.loginName ||
|
|
||||||
this.$route.query.submitIdentity ||
|
|
||||||
''
|
|
||||||
);
|
|
||||||
},
|
|
||||||
},
|
|
||||||
};
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style lang="scss">
|
|
||||||
html.mk-iframe-page,
|
|
||||||
html.mk-iframe-page body,
|
|
||||||
html.mk-iframe-page #app,
|
|
||||||
html.mk-iframe-page #app.mk-iframe-page {
|
|
||||||
height: auto !important;
|
|
||||||
min-height: 100%;
|
|
||||||
overflow: visible;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
|
||||||
.mk-public-shell {
|
|
||||||
min-height: 100%;
|
|
||||||
padding: 12px 0 24px;
|
|
||||||
box-sizing: border-box;
|
|
||||||
background: #f0f2f5;
|
|
||||||
|
|
||||||
:deep(.basic-container) {
|
|
||||||
padding: 0 12px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
@@ -497,7 +497,6 @@ const emptyLine = () => ({
|
|||||||
localId: ++localId,
|
localId: ++localId,
|
||||||
goodsCategory: '',
|
goodsCategory: '',
|
||||||
goodsName: '',
|
goodsName: '',
|
||||||
taxClassificationCode: '',
|
|
||||||
unit: '吨',
|
unit: '吨',
|
||||||
quantity: 0,
|
quantity: 0,
|
||||||
unitPriceNoTax: 0,
|
unitPriceNoTax: 0,
|
||||||
@@ -725,11 +724,9 @@ export default {
|
|||||||
const item = this.findInvoiceItem(line);
|
const item = this.findInvoiceItem(line);
|
||||||
if (item) {
|
if (item) {
|
||||||
line.taxRate = Number(item.defaultTaxRate || 0);
|
line.taxRate = Number(item.defaultTaxRate || 0);
|
||||||
line.taxClassificationCode = item.taxClassificationCode || '';
|
|
||||||
} else if (clearInvalid) {
|
} else if (clearInvalid) {
|
||||||
line.goodsName = '';
|
line.goodsName = '';
|
||||||
line.taxRate = 0;
|
line.taxRate = 0;
|
||||||
line.taxClassificationCode = '';
|
|
||||||
}
|
}
|
||||||
this.recalculateLine(line);
|
this.recalculateLine(line);
|
||||||
},
|
},
|
||||||
@@ -737,7 +734,6 @@ export default {
|
|||||||
const names = this.invoiceItemNames(line.goodsCategory);
|
const names = this.invoiceItemNames(line.goodsCategory);
|
||||||
if (!names.some(item => item.shortName === line.goodsName)) line.goodsName = '';
|
if (!names.some(item => item.shortName === line.goodsName)) line.goodsName = '';
|
||||||
line.taxRate = 0;
|
line.taxRate = 0;
|
||||||
line.taxClassificationCode = this.findInvoiceItem(line)?.taxClassificationCode || '';
|
|
||||||
if (names.length === 1) {
|
if (names.length === 1) {
|
||||||
line.goodsName = names[0].shortName;
|
line.goodsName = names[0].shortName;
|
||||||
this.handleGoodsNameChange(line);
|
this.handleGoodsNameChange(line);
|
||||||
@@ -747,11 +743,9 @@ export default {
|
|||||||
const item = this.findInvoiceItem(line);
|
const item = this.findInvoiceItem(line);
|
||||||
if (!item) {
|
if (!item) {
|
||||||
line.taxRate = 0;
|
line.taxRate = 0;
|
||||||
line.taxClassificationCode = '';
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
line.taxRate = Number(item.defaultTaxRate || 0);
|
line.taxRate = Number(item.defaultTaxRate || 0);
|
||||||
line.taxClassificationCode = item.taxClassificationCode || '';
|
|
||||||
this.recalculateLine(line);
|
this.recalculateLine(line);
|
||||||
},
|
},
|
||||||
validateSettlements(rule, value, callback) {
|
validateSettlements(rule, value, callback) {
|
||||||
@@ -1147,7 +1141,6 @@ export default {
|
|||||||
lines: sheet.lines.map(line => ({
|
lines: sheet.lines.map(line => ({
|
||||||
goodsCategory: line.goodsCategory,
|
goodsCategory: line.goodsCategory,
|
||||||
goodsName: line.goodsName,
|
goodsName: line.goodsName,
|
||||||
taxClassificationCode: line.taxClassificationCode,
|
|
||||||
unit: line.unit,
|
unit: line.unit,
|
||||||
quantity: line.quantity,
|
quantity: line.quantity,
|
||||||
unitPriceNoTax: line.unitPriceNoTax,
|
unitPriceNoTax: line.unitPriceNoTax,
|
||||||
|
|||||||
@@ -61,7 +61,6 @@
|
|||||||
v-if="hasPermission('invoice_application_sync')"
|
v-if="hasPermission('invoice_application_sync')"
|
||||||
type="primary"
|
type="primary"
|
||||||
plain
|
plain
|
||||||
:loading="batchSyncing"
|
|
||||||
@click="handleBatchSync"
|
@click="handleBatchSync"
|
||||||
>批量同步</el-button
|
>批量同步</el-button
|
||||||
>
|
>
|
||||||
@@ -271,7 +270,6 @@ export default {
|
|||||||
rows: [],
|
rows: [],
|
||||||
selection: [],
|
selection: [],
|
||||||
loading: false,
|
loading: false,
|
||||||
batchSyncing: false,
|
|
||||||
page: { current: 1, size: 10, total: 0 },
|
page: { current: 1, size: 10, total: 0 },
|
||||||
flowDialog: { visible: false, loading: false, row: {}, records: [] },
|
flowDialog: { visible: false, loading: false, row: {}, records: [] },
|
||||||
};
|
};
|
||||||
@@ -401,31 +399,14 @@ export default {
|
|||||||
this.loadTable();
|
this.loadTable();
|
||||||
},
|
},
|
||||||
async handleBatchSync() {
|
async handleBatchSync() {
|
||||||
if (this.batchSyncing) return;
|
|
||||||
const rows = this.selection.filter(row => row.approvalStatus === 'approved');
|
const rows = this.selection.filter(row => row.approvalStatus === 'approved');
|
||||||
if (!rows.length) {
|
if (!rows.length) {
|
||||||
this.$message.warning('请选择至少一条审批通过的开票申请');
|
this.$message.warning('请选择至少一条审批通过的开票申请');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
this.batchSyncing = true;
|
await Promise.all(rows.map(row => api.syncKingdee(row.id)));
|
||||||
try {
|
this.$message.success(`已同步${rows.length}条开票申请`);
|
||||||
await Promise.all(rows.map(row => api.syncKingdee(row.id)));
|
this.loadTable();
|
||||||
this.$message.success(`已同步${rows.length}条开票申请`);
|
|
||||||
await this.loadTable();
|
|
||||||
// 附件联带同步提示:同步成功但附件未全部上传时追加 warning,重推即自动补传
|
|
||||||
const idSet = new Set(rows.map(row => String(row.id)));
|
|
||||||
const pendingCount = (this.rows || []).filter(row => idSet.has(String(row.id))
|
|
||||||
&& ['pending', 'partial'].includes(row.attachmentSyncStatus)).length;
|
|
||||||
if (pendingCount) {
|
|
||||||
this.$message({
|
|
||||||
type: 'warning',
|
|
||||||
message: `其中${pendingCount}单附件未全部上传,可重推补传`,
|
|
||||||
duration: 8000,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
} finally {
|
|
||||||
this.batchSyncing = false;
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
async handleExport() {
|
async handleExport() {
|
||||||
const data = this.unwrapData(await api.getList(1, 100000, this.query));
|
const data = this.unwrapData(await api.getList(1, 100000, this.query));
|
||||||
|
|||||||
@@ -47,7 +47,7 @@
|
|||||||
<el-col :span="6">
|
<el-col :span="6">
|
||||||
<el-form-item label="开票金额">
|
<el-form-item label="开票金额">
|
||||||
<el-input
|
<el-input
|
||||||
:model-value="form.invoiceNo ? formatMoney(form.invoiceAmount) : ''"
|
:model-value="form.kingdeeInvoicePoolId ? formatMoney(form.invoiceAmount) : ''"
|
||||||
disabled
|
disabled
|
||||||
placeholder="从金蝶票据池带出"
|
placeholder="从金蝶票据池带出"
|
||||||
/>
|
/>
|
||||||
@@ -56,7 +56,7 @@
|
|||||||
<el-col :span="6">
|
<el-col :span="6">
|
||||||
<el-form-item label="税额">
|
<el-form-item label="税额">
|
||||||
<el-input
|
<el-input
|
||||||
:model-value="form.invoiceNo ? formatMoney(form.taxAmount) : ''"
|
:model-value="form.kingdeeInvoicePoolId ? formatMoney(form.taxAmount) : ''"
|
||||||
disabled
|
disabled
|
||||||
placeholder="从金蝶票据池带出"
|
placeholder="从金蝶票据池带出"
|
||||||
/>
|
/>
|
||||||
@@ -149,13 +149,62 @@
|
|||||||
</div>
|
</div>
|
||||||
</el-form>
|
</el-form>
|
||||||
|
|
||||||
<kingdee-invoice-pool-dialog
|
<el-dialog v-model="invoiceDialog.visible" title="查询金蝶票据池" width="86%" append-to-body>
|
||||||
v-model="invoiceDialog.visible"
|
<div class="receipt-form-page__dialog-search">
|
||||||
title="查询金蝶票据池"
|
<el-input
|
||||||
:initial-invoice-no="form.invoiceNo"
|
v-model="invoiceDialog.keyword"
|
||||||
:query-api="apiQueryInvoicePool"
|
clearable
|
||||||
@confirm="handleInvoicePicked"
|
placeholder="发票号码、开票单位或受票单位"
|
||||||
/>
|
@keyup.enter="loadInvoicePool"
|
||||||
|
/>
|
||||||
|
<el-button type="primary" @click="loadInvoicePool">查询</el-button>
|
||||||
|
</div>
|
||||||
|
<el-table
|
||||||
|
v-loading="invoiceDialog.loading"
|
||||||
|
:data="invoiceDialog.rows"
|
||||||
|
border
|
||||||
|
empty-text="暂无票据数据"
|
||||||
|
highlight-current-row
|
||||||
|
@current-change="invoiceDialog.current = $event"
|
||||||
|
@row-dblclick="confirmInvoice"
|
||||||
|
>
|
||||||
|
<el-table-column prop="invoiceNo" label="发票号码" min-width="180" />
|
||||||
|
<el-table-column prop="invoiceDate" label="开票日期" min-width="120" />
|
||||||
|
<el-table-column prop="invoiceType" label="发票类型" min-width="130" />
|
||||||
|
<el-table-column prop="issuerName" label="开票单位" min-width="170" />
|
||||||
|
<el-table-column prop="receiverName" label="受票单位" min-width="170" />
|
||||||
|
<el-table-column label="开票金额" min-width="130" align="right">
|
||||||
|
<template #default="{ row }">{{ formatMoney(row.invoiceAmount) }}</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column label="税率" min-width="100" align="center">
|
||||||
|
<template #default="{ row }">{{ formatRate(row.taxRate) }}</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column label="税额" min-width="120" align="right">
|
||||||
|
<template #default="{ row }">{{ formatMoney(row.taxAmount) }}</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column prop="kingdeeBillNo" label="金蝶单据号" min-width="150" />
|
||||||
|
<el-table-column label="操作" width="100" fixed="right" align="center">
|
||||||
|
<template #default="{ row }">
|
||||||
|
<el-link type="primary" @click.stop="confirmInvoice(row)">选择</el-link>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
</el-table>
|
||||||
|
<div class="receipt-form-page__dialog-pagination">
|
||||||
|
<el-pagination
|
||||||
|
v-model:current-page="invoiceDialog.page.current"
|
||||||
|
v-model:page-size="invoiceDialog.page.size"
|
||||||
|
:total="invoiceDialog.page.total"
|
||||||
|
:page-sizes="[10, 20, 50, 100]"
|
||||||
|
layout="total, sizes, prev, pager, next, jumper"
|
||||||
|
@current-change="refreshInvoicePoolPage"
|
||||||
|
@size-change="handleInvoicePoolSizeChange"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<template #footer>
|
||||||
|
<el-button @click="invoiceDialog.visible = false">取消</el-button>
|
||||||
|
<el-button type="primary" @click="confirmInvoice()">确定</el-button>
|
||||||
|
</template>
|
||||||
|
</el-dialog>
|
||||||
|
|
||||||
<el-dialog
|
<el-dialog
|
||||||
v-model="settlementDialog.visible"
|
v-model="settlementDialog.visible"
|
||||||
@@ -210,11 +259,80 @@
|
|||||||
import { Search } from '@element-plus/icons-vue';
|
import { Search } from '@element-plus/icons-vue';
|
||||||
import { mapGetters } from 'vuex';
|
import { mapGetters } from 'vuex';
|
||||||
import * as api from '@/api/payment/invoiceReceipt';
|
import * as api from '@/api/payment/invoiceReceipt';
|
||||||
import KingdeeInvoicePoolDialog from '@/components/kingdee-invoice-pool-dialog/main.vue';
|
import { getList as getCustomerList } from '@/api/vehicle/customer-archive';
|
||||||
import VehicleAttachmentTable from '@/components/vehicle-attachment-table/main.vue';
|
import VehicleAttachmentTable from '@/components/vehicle-attachment-table/main.vue';
|
||||||
|
|
||||||
|
const mockInvoiceTypes = ['增值税专用发票', '增值税普通发票'];
|
||||||
|
const mockTaxRates = [3, 6, 9, 13];
|
||||||
|
const mockBankNames = ['中国工商银行', '中国农业银行', '中国银行', '中国建设银行', '招商银行'];
|
||||||
|
|
||||||
|
const randomDigits = length =>
|
||||||
|
Array.from({ length }, () => Math.floor(Math.random() * 10)).join('');
|
||||||
|
|
||||||
|
const formatMockDate = date => {
|
||||||
|
const year = date.getFullYear();
|
||||||
|
const month = String(date.getMonth() + 1).padStart(2, '0');
|
||||||
|
const day = String(date.getDate()).padStart(2, '0');
|
||||||
|
return `${year}-${month}-${day}`;
|
||||||
|
};
|
||||||
|
|
||||||
|
const customerName = customer =>
|
||||||
|
customer.fullName || customer.shortName || customer.customerCode || `客商${customer.id}`;
|
||||||
|
|
||||||
|
const customerEmail = (customer, index) => {
|
||||||
|
const prefix = String(customer.customerCode || customer.id || `customer${index + 1}`)
|
||||||
|
.replace(/[^a-zA-Z0-9_-]/g, '')
|
||||||
|
.toLowerCase();
|
||||||
|
return `finance@${prefix || `customer${index + 1}`}.example.com`;
|
||||||
|
};
|
||||||
|
|
||||||
|
const createInvoicePoolMockRows = (customers, counterpartyName, departmentEmail) => {
|
||||||
|
const rows = [];
|
||||||
|
const baseId = Date.now();
|
||||||
|
customers.forEach((customer, customerIndex) => {
|
||||||
|
const name = customerName(customer);
|
||||||
|
['issuer', 'receiver'].forEach((role, roleIndex) => {
|
||||||
|
const count = 3 + Math.floor(Math.random() * 5);
|
||||||
|
for (let index = 0; index < count; index += 1) {
|
||||||
|
const rowIndex = rows.length;
|
||||||
|
const invoiceDateValue = new Date();
|
||||||
|
invoiceDateValue.setDate(invoiceDateValue.getDate() - Math.floor(Math.random() * 90));
|
||||||
|
const invoiceDate = formatMockDate(invoiceDateValue);
|
||||||
|
const taxRate = mockTaxRates[(customerIndex + roleIndex + index) % mockTaxRates.length];
|
||||||
|
const invoiceAmount = Math.floor(10000 + Math.random() * 190000);
|
||||||
|
const taxAmount = Number(
|
||||||
|
((invoiceAmount * Number(taxRate)) / (100 + Number(taxRate))).toFixed(2)
|
||||||
|
);
|
||||||
|
const bankName = mockBankNames[(customerIndex + index) % mockBankNames.length];
|
||||||
|
rows.push({
|
||||||
|
id: baseId + rowIndex,
|
||||||
|
invoiceNo: `25${randomDigits(18)}`,
|
||||||
|
invoiceDate,
|
||||||
|
invoiceType: mockInvoiceTypes[(customerIndex + index) % mockInvoiceTypes.length],
|
||||||
|
taxRate,
|
||||||
|
invoiceAmount,
|
||||||
|
taxAmount,
|
||||||
|
receiverName: role === 'receiver' ? name : counterpartyName,
|
||||||
|
issuerName: role === 'issuer' ? name : counterpartyName,
|
||||||
|
bankName,
|
||||||
|
bankAccount: randomDigits(19),
|
||||||
|
issuingBank: `${bankName}营业部`,
|
||||||
|
phone: customer.contactPhone || '',
|
||||||
|
customerEmails: customerEmail(customer, customerIndex),
|
||||||
|
departmentEmails: departmentEmail || '',
|
||||||
|
attachmentsJson: '[]',
|
||||||
|
kingdeeBillNo: `KD-FP-${invoiceDate.replaceAll('-', '')}${randomDigits(5)}`,
|
||||||
|
kingdeeStatus: 'unsynced',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
return rows;
|
||||||
|
};
|
||||||
|
|
||||||
const emptyForm = () => ({
|
const emptyForm = () => ({
|
||||||
id: null,
|
id: null,
|
||||||
|
kingdeeInvoicePoolId: null,
|
||||||
invoiceNo: '',
|
invoiceNo: '',
|
||||||
invoiceDate: '',
|
invoiceDate: '',
|
||||||
invoiceType: '',
|
invoiceType: '',
|
||||||
@@ -226,6 +344,8 @@ const emptyForm = () => ({
|
|||||||
bankName: '',
|
bankName: '',
|
||||||
bankAccount: '',
|
bankAccount: '',
|
||||||
issuingBank: '',
|
issuingBank: '',
|
||||||
|
kingdeeBillNo: '',
|
||||||
|
kingdeeStatus: 'unsynced',
|
||||||
phone: '',
|
phone: '',
|
||||||
customerEmails: [],
|
customerEmails: [],
|
||||||
departmentEmails: [],
|
departmentEmails: [],
|
||||||
@@ -241,17 +361,24 @@ const emptyForm = () => ({
|
|||||||
export default {
|
export default {
|
||||||
name: 'InvoiceReceiptForm',
|
name: 'InvoiceReceiptForm',
|
||||||
components: {
|
components: {
|
||||||
KingdeeInvoicePoolDialog,
|
|
||||||
VehicleAttachmentTable,
|
VehicleAttachmentTable,
|
||||||
},
|
},
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
Search,
|
Search,
|
||||||
form: emptyForm(),
|
form: emptyForm(),
|
||||||
|
invoicePoolMockRows: [],
|
||||||
|
invoicePoolMockLoaded: false,
|
||||||
customerEmailOptions: [],
|
customerEmailOptions: [],
|
||||||
departmentEmailOptions: [],
|
departmentEmailOptions: [],
|
||||||
invoiceDialog: {
|
invoiceDialog: {
|
||||||
visible: false,
|
visible: false,
|
||||||
|
loading: false,
|
||||||
|
keyword: '',
|
||||||
|
sourceRows: [],
|
||||||
|
rows: [],
|
||||||
|
current: null,
|
||||||
|
page: { current: 1, size: 10, total: 0 },
|
||||||
},
|
},
|
||||||
settlementDialog: {
|
settlementDialog: {
|
||||||
visible: false,
|
visible: false,
|
||||||
@@ -294,12 +421,6 @@ export default {
|
|||||||
this.initialize();
|
this.initialize();
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
apiQueryInvoicePool(params) {
|
|
||||||
return api.queryInvoicePool(params);
|
|
||||||
},
|
|
||||||
handleInvoicePicked(rows) {
|
|
||||||
this.confirmInvoice(rows[0]);
|
|
||||||
},
|
|
||||||
hasPermission(code) {
|
hasPermission(code) {
|
||||||
return this.permission?.[code] !== false;
|
return this.permission?.[code] !== false;
|
||||||
},
|
},
|
||||||
@@ -345,7 +466,7 @@ export default {
|
|||||||
if (this.settlementIds.length) await this.loadReferenceInformation();
|
if (this.settlementIds.length) await this.loadReferenceInformation();
|
||||||
},
|
},
|
||||||
validateInvoice(rule, value, callback) {
|
validateInvoice(rule, value, callback) {
|
||||||
if (!value) {
|
if (!value || !this.form.kingdeeInvoicePoolId) {
|
||||||
callback(new Error('请选择金蝶票据池发票'));
|
callback(new Error('请选择金蝶票据池发票'));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -371,17 +492,99 @@ export default {
|
|||||||
}
|
}
|
||||||
callback();
|
callback();
|
||||||
},
|
},
|
||||||
openInvoiceDialog() {
|
async openInvoiceDialog() {
|
||||||
this.invoiceDialog.visible = true;
|
this.invoiceDialog.visible = true;
|
||||||
|
this.invoiceDialog.keyword = this.form.invoiceNo || '';
|
||||||
|
await this.loadInvoicePool();
|
||||||
|
},
|
||||||
|
async loadInvoicePool() {
|
||||||
|
this.invoiceDialog.loading = true;
|
||||||
|
this.invoiceDialog.page.current = 1;
|
||||||
|
this.invoiceDialog.current = null;
|
||||||
|
try {
|
||||||
|
if (!this.recordId) {
|
||||||
|
if (!this.invoicePoolMockLoaded) await this.loadInvoicePoolMockRows();
|
||||||
|
this.invoiceDialog.sourceRows = this.filterInvoicePoolMockRows(
|
||||||
|
this.invoiceDialog.keyword
|
||||||
|
);
|
||||||
|
this.refreshInvoicePoolPage();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const data = this.unwrapData(await api.getInvoicePool(this.invoiceDialog.keyword));
|
||||||
|
this.invoiceDialog.sourceRows = Array.isArray(data) ? data : data?.records || [];
|
||||||
|
this.refreshInvoicePoolPage();
|
||||||
|
} finally {
|
||||||
|
this.invoiceDialog.loading = false;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
async loadInvoicePoolMockRows() {
|
||||||
|
const pageSize = 500;
|
||||||
|
const firstData = this.unwrapData(await getCustomerList(1, pageSize, {}));
|
||||||
|
const firstRows = Array.isArray(firstData) ? firstData : firstData?.records || [];
|
||||||
|
const total = Number(firstData?.total || firstRows.length);
|
||||||
|
const pageCount = Math.ceil(total / pageSize);
|
||||||
|
const remainingPages = await Promise.all(
|
||||||
|
Array.from({ length: Math.max(pageCount - 1, 0) }, (_, index) =>
|
||||||
|
getCustomerList(index + 2, pageSize, {})
|
||||||
|
)
|
||||||
|
);
|
||||||
|
const customers = [
|
||||||
|
...firstRows,
|
||||||
|
...remainingPages.flatMap(response => {
|
||||||
|
const data = this.unwrapData(response);
|
||||||
|
return Array.isArray(data) ? data : data?.records || [];
|
||||||
|
}),
|
||||||
|
].filter(
|
||||||
|
(customer, index, rows) =>
|
||||||
|
rows.findIndex(item => String(item.id) === String(customer.id)) === index
|
||||||
|
);
|
||||||
|
const userInfo = this.$store.getters.userInfo || {};
|
||||||
|
const counterpartyName = userInfo.deptName || userInfo.dept_name || '本企业';
|
||||||
|
this.invoicePoolMockRows = createInvoicePoolMockRows(
|
||||||
|
customers,
|
||||||
|
counterpartyName,
|
||||||
|
userInfo.email || ''
|
||||||
|
);
|
||||||
|
this.invoicePoolMockLoaded = true;
|
||||||
|
},
|
||||||
|
refreshInvoicePoolPage() {
|
||||||
|
const page = this.invoiceDialog.page;
|
||||||
|
const total = this.invoiceDialog.sourceRows.length;
|
||||||
|
const maxPage = Math.max(Math.ceil(total / page.size), 1);
|
||||||
|
if (page.current > maxPage) page.current = maxPage;
|
||||||
|
const start = (page.current - 1) * page.size;
|
||||||
|
page.total = total;
|
||||||
|
this.invoiceDialog.current = null;
|
||||||
|
this.invoiceDialog.rows = this.invoiceDialog.sourceRows.slice(start, start + page.size);
|
||||||
|
},
|
||||||
|
handleInvoicePoolSizeChange() {
|
||||||
|
this.invoiceDialog.page.current = 1;
|
||||||
|
this.refreshInvoicePoolPage();
|
||||||
|
},
|
||||||
|
filterInvoicePoolMockRows(keyword) {
|
||||||
|
const normalizedKeyword = String(keyword || '')
|
||||||
|
.trim()
|
||||||
|
.toLowerCase();
|
||||||
|
if (!normalizedKeyword) return this.invoicePoolMockRows.map(item => ({ ...item }));
|
||||||
|
return this.invoicePoolMockRows
|
||||||
|
.filter(item =>
|
||||||
|
[item.invoiceNo, item.issuerName, item.receiverName].some(value =>
|
||||||
|
String(value || '')
|
||||||
|
.toLowerCase()
|
||||||
|
.includes(normalizedKeyword)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
.map(item => ({ ...item }));
|
||||||
},
|
},
|
||||||
confirmInvoice(row, options = {}) {
|
confirmInvoice(row, options = {}) {
|
||||||
const selected = row;
|
const selected = row?.id ? row : this.invoiceDialog.current;
|
||||||
if (!selected) {
|
if (!selected) {
|
||||||
this.$message.warning('请选择一张金蝶票据池发票');
|
this.$message.warning('请选择一张金蝶票据池发票');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const preserveUserInput = options.preserveUserInput === true;
|
const preserveUserInput = options.preserveUserInput === true;
|
||||||
Object.assign(this.form, {
|
Object.assign(this.form, {
|
||||||
|
kingdeeInvoicePoolId: selected.id,
|
||||||
invoiceNo: selected.invoiceNo,
|
invoiceNo: selected.invoiceNo,
|
||||||
invoiceDate: selected.invoiceDate,
|
invoiceDate: selected.invoiceDate,
|
||||||
invoiceType: selected.invoiceType,
|
invoiceType: selected.invoiceType,
|
||||||
@@ -393,6 +596,8 @@ export default {
|
|||||||
bankName: selected.bankName,
|
bankName: selected.bankName,
|
||||||
bankAccount: selected.bankAccount,
|
bankAccount: selected.bankAccount,
|
||||||
issuingBank: selected.issuingBank,
|
issuingBank: selected.issuingBank,
|
||||||
|
kingdeeBillNo: selected.kingdeeBillNo || '',
|
||||||
|
kingdeeStatus: selected.kingdeeStatus || 'unsynced',
|
||||||
phone: preserveUserInput ? this.form.phone : selected.phone || '',
|
phone: preserveUserInput ? this.form.phone : selected.phone || '',
|
||||||
customerEmails: preserveUserInput
|
customerEmails: preserveUserInput
|
||||||
? this.form.customerEmails
|
? this.form.customerEmails
|
||||||
@@ -503,6 +708,7 @@ export default {
|
|||||||
payload() {
|
payload() {
|
||||||
return {
|
return {
|
||||||
id: this.form.id,
|
id: this.form.id,
|
||||||
|
kingdeeInvoicePoolId: this.form.kingdeeInvoicePoolId,
|
||||||
invoiceNo: this.form.invoiceNo,
|
invoiceNo: this.form.invoiceNo,
|
||||||
invoiceDate: this.form.invoiceDate,
|
invoiceDate: this.form.invoiceDate,
|
||||||
invoiceType: this.form.invoiceType,
|
invoiceType: this.form.invoiceType,
|
||||||
@@ -514,6 +720,8 @@ export default {
|
|||||||
bankName: this.form.bankName,
|
bankName: this.form.bankName,
|
||||||
bankAccount: this.form.bankAccount,
|
bankAccount: this.form.bankAccount,
|
||||||
issuingBank: this.form.issuingBank,
|
issuingBank: this.form.issuingBank,
|
||||||
|
kingdeeBillNo: this.form.kingdeeBillNo,
|
||||||
|
kingdeeStatus: this.form.kingdeeStatus,
|
||||||
phone: this.form.phone,
|
phone: this.form.phone,
|
||||||
customerEmails: this.form.customerEmails.join(';'),
|
customerEmails: this.form.customerEmails.join(';'),
|
||||||
departmentEmails: this.form.departmentEmails.join(';'),
|
departmentEmails: this.form.departmentEmails.join(';'),
|
||||||
@@ -550,12 +758,13 @@ export default {
|
|||||||
this.$message.warning('请先选择金蝶票据池发票');
|
this.$message.warning('请先选择金蝶票据池发票');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const data = this.unwrapData(
|
const data = this.recordId
|
||||||
await api.queryInvoicePool({ current: 1, size: 1, invoiceNo: this.form.invoiceNo })
|
? this.unwrapData(await api.getInvoicePool(this.form.invoiceNo))
|
||||||
);
|
: this.filterInvoicePoolMockRows(this.form.invoiceNo);
|
||||||
const invoice = (data.records || []).find(item => item.invoiceNo === this.form.invoiceNo);
|
const rows = Array.isArray(data) ? data : data?.records || [];
|
||||||
|
const invoice = rows.find(item => item.invoiceNo === this.form.invoiceNo);
|
||||||
if (!invoice) {
|
if (!invoice) {
|
||||||
this.$message.warning('金蝶票池未查询到该发票');
|
this.$message.warning('金蝶票据池中未查询到该发票');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (!this.confirmInvoice(invoice, { preserveUserInput: true })) return;
|
if (!this.confirmInvoice(invoice, { preserveUserInput: true })) return;
|
||||||
@@ -618,6 +827,11 @@ export default {
|
|||||||
.receipt-form-page__dialog-search .el-input {
|
.receipt-form-page__dialog-search .el-input {
|
||||||
width: 360px;
|
width: 360px;
|
||||||
}
|
}
|
||||||
|
.receipt-form-page__dialog-pagination {
|
||||||
|
display: flex;
|
||||||
|
justify-content: flex-end;
|
||||||
|
margin-top: 12px;
|
||||||
|
}
|
||||||
.receipt-form-page :deep(.el-table) {
|
.receipt-form-page :deep(.el-table) {
|
||||||
--el-table-border-color: #eff1f7;
|
--el-table-border-color: #eff1f7;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -40,10 +40,10 @@
|
|||||||
/>
|
/>
|
||||||
</el-select>
|
</el-select>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
<el-form-item v-if="searchExpanded" label="金蝶状态">
|
<el-form-item v-if="searchExpanded" label="金蝶单据状态">
|
||||||
<el-select v-model="query.kingdeePushStatus" clearable placeholder="请选择">
|
<el-select v-model="query.kingdeeStatus" clearable placeholder="请选择">
|
||||||
<el-option
|
<el-option
|
||||||
v-for="item in kingdeePushStatusOptions"
|
v-for="item in kingdeeStatusOptions"
|
||||||
:key="item.value"
|
:key="item.value"
|
||||||
:label="item.label"
|
:label="item.label"
|
||||||
:value="item.value"
|
:value="item.value"
|
||||||
@@ -70,8 +70,7 @@
|
|||||||
v-if="hasPermission('invoice_receipt_sync')"
|
v-if="hasPermission('invoice_receipt_sync')"
|
||||||
type="primary"
|
type="primary"
|
||||||
plain
|
plain
|
||||||
:loading="pushLoading"
|
@click="handleBatchSync"
|
||||||
@click="handlePushKingdee"
|
|
||||||
>批量同步</el-button
|
>批量同步</el-button
|
||||||
>
|
>
|
||||||
<el-button v-if="hasPermission('invoice_receipt_add')" type="primary" @click="openCreate"
|
<el-button v-if="hasPermission('invoice_receipt_add')" type="primary" @click="openCreate"
|
||||||
@@ -245,7 +244,7 @@ const emptyQuery = () => ({
|
|||||||
deptName: '',
|
deptName: '',
|
||||||
deptPath: [],
|
deptPath: [],
|
||||||
approvalStatus: '',
|
approvalStatus: '',
|
||||||
kingdeePushStatus: '',
|
kingdeeStatus: '',
|
||||||
});
|
});
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
@@ -259,12 +258,11 @@ export default {
|
|||||||
query: emptyQuery(),
|
query: emptyQuery(),
|
||||||
searchExpanded: false,
|
searchExpanded: false,
|
||||||
approvalStatusOptions: api.approvalStatusOptions,
|
approvalStatusOptions: api.approvalStatusOptions,
|
||||||
kingdeePushStatusOptions: api.kingdeePushStatusOptions,
|
kingdeeStatusOptions: api.kingdeeStatusOptions,
|
||||||
columns: invoiceReceiptTableColumns,
|
columns: invoiceReceiptTableColumns,
|
||||||
rows: [],
|
rows: [],
|
||||||
selection: [],
|
selection: [],
|
||||||
loading: false,
|
loading: false,
|
||||||
pushLoading: false,
|
|
||||||
page: { current: 1, size: 10, total: 0 },
|
page: { current: 1, size: 10, total: 0 },
|
||||||
flowDialog: { visible: false, loading: false, row: {}, records: [] },
|
flowDialog: { visible: false, loading: false, row: {}, records: [] },
|
||||||
};
|
};
|
||||||
@@ -365,29 +363,15 @@ export default {
|
|||||||
this.$message.success('作废成功');
|
this.$message.success('作废成功');
|
||||||
this.loadTable();
|
this.loadTable();
|
||||||
},
|
},
|
||||||
async handlePushKingdee() {
|
async handleBatchSync() {
|
||||||
if (this.pushLoading) return;
|
|
||||||
const rows = this.selection.filter(row => row.approvalStatus === 'approved');
|
const rows = this.selection.filter(row => row.approvalStatus === 'approved');
|
||||||
if (!rows.length) {
|
if (!rows.length) {
|
||||||
this.$message.warning('请选择至少一条审批通过的收票登记');
|
this.$message.warning('请选择至少一条审批通过的收票登记');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
await this.$confirm(`确认将选中的 ${rows.length} 条收票登记同步至金蝶?`, '提示', {
|
await Promise.all(rows.map(row => api.syncKingdee(row.id)));
|
||||||
type: 'warning',
|
this.$message.success(`已同步${rows.length}条收票登记`);
|
||||||
});
|
this.loadTable();
|
||||||
this.pushLoading = true;
|
|
||||||
try {
|
|
||||||
const summary = this.unwrapData(await api.pushKingdee(rows.map(row => row.id)));
|
|
||||||
await this.loadTable();
|
|
||||||
// 附件联带同步提示:推送成功但附件未全部上传时在结果弹窗追加说明,重推即自动补传
|
|
||||||
const idSet = new Set(rows.map(row => String(row.id)));
|
|
||||||
const pendingCount = (this.rows || []).filter(row => idSet.has(String(row.id))
|
|
||||||
&& ['pending', 'partial'].includes(row.attachmentSyncStatus)).length;
|
|
||||||
const attachmentTip = pendingCount ? `\n其中${pendingCount}单附件未全部上传,可重推补传` : '';
|
|
||||||
this.$alert((summary || '推送完成') + attachmentTip, '推送结果', { confirmButtonText: '知道了' });
|
|
||||||
} finally {
|
|
||||||
this.pushLoading = false;
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
async handleExport() {
|
async handleExport() {
|
||||||
const data = this.unwrapData(await api.getList(1, 100000, this.query));
|
const data = this.unwrapData(await api.getList(1, 100000, this.query));
|
||||||
@@ -404,9 +388,8 @@ export default {
|
|||||||
当前处理人: item.currentProcessor,
|
当前处理人: item.currentProcessor,
|
||||||
创建人: item.createUserName,
|
创建人: item.createUserName,
|
||||||
创建时间: item.createTime,
|
创建时间: item.createTime,
|
||||||
金蝶单据号: item.kingdeeReceivedInvoiceNo,
|
金蝶单据号: item.kingdeeBillNo,
|
||||||
金蝶状态: item.kingdeePushStatusName,
|
金蝶单据状态: item.kingdeeStatusName,
|
||||||
失败原因: item.kingdeePushMsg,
|
|
||||||
}));
|
}));
|
||||||
const workbook = XLSX.utils.book_new();
|
const workbook = XLSX.utils.book_new();
|
||||||
XLSX.utils.book_append_sheet(workbook, XLSX.utils.json_to_sheet(exportRows), '收票登记');
|
XLSX.utils.book_append_sheet(workbook, XLSX.utils.json_to_sheet(exportRows), '收票登记');
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -1,21 +0,0 @@
|
|||||||
<template>
|
|
||||||
<mk-public-shell biz-type="payment-application" :get-form="getForm">
|
|
||||||
<payment-application-form ref="page" />
|
|
||||||
</mk-public-shell>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
import MkPublicShell from '@/views/mk/mk-public-shell.vue';
|
|
||||||
import PaymentApplicationForm from '@/views/payment/payment-application-form.vue';
|
|
||||||
|
|
||||||
export default {
|
|
||||||
name: 'PaymentApplicationPublicView',
|
|
||||||
components: { MkPublicShell, PaymentApplicationForm },
|
|
||||||
methods: {
|
|
||||||
getForm() {
|
|
||||||
const form = this.$refs.page?.form || {};
|
|
||||||
return { ...form, subject: form.paymentNo || form.projectName || '' };
|
|
||||||
},
|
|
||||||
},
|
|
||||||
};
|
|
||||||
</script>
|
|
||||||
@@ -81,18 +81,9 @@
|
|||||||
v-if="hasPermission('payment_application_sync')"
|
v-if="hasPermission('payment_application_sync')"
|
||||||
type="primary"
|
type="primary"
|
||||||
plain
|
plain
|
||||||
:loading="syncLoading"
|
|
||||||
@click="handleSync"
|
@click="handleSync"
|
||||||
>批量同步</el-button
|
>批量同步</el-button
|
||||||
>
|
>
|
||||||
<el-button
|
|
||||||
v-if="hasPermission('payment_application_sync')"
|
|
||||||
type="primary"
|
|
||||||
plain
|
|
||||||
:loading="resultSyncing"
|
|
||||||
@click="handleSyncResult"
|
|
||||||
>同步付款结果</el-button
|
|
||||||
>
|
|
||||||
<el-button
|
<el-button
|
||||||
v-if="hasPermission('payment_application_add')"
|
v-if="hasPermission('payment_application_add')"
|
||||||
type="primary"
|
type="primary"
|
||||||
@@ -273,8 +264,6 @@ export default {
|
|||||||
rows: [],
|
rows: [],
|
||||||
selection: [],
|
selection: [],
|
||||||
loading: false,
|
loading: false,
|
||||||
syncLoading: false,
|
|
||||||
resultSyncing: false,
|
|
||||||
page: { current: 1, size: 10, total: 0 },
|
page: { current: 1, size: 10, total: 0 },
|
||||||
flowDialog: { visible: false, row: {} },
|
flowDialog: { visible: false, row: {} },
|
||||||
};
|
};
|
||||||
@@ -337,7 +326,7 @@ export default {
|
|||||||
openProjectAdvance() {
|
openProjectAdvance() {
|
||||||
this.$router.push({
|
this.$router.push({
|
||||||
path: '/payment/payment-application/form',
|
path: '/payment/payment-application/form',
|
||||||
query: { mode: 'add', paymentType: 'progress_advance' },
|
query: { mode: 'add', paymentType: 'project_advance' },
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
openEdit(row) {
|
openEdit(row) {
|
||||||
@@ -406,58 +395,17 @@ export default {
|
|||||||
async handleSyncRow(row) {
|
async handleSyncRow(row) {
|
||||||
const data = this.unwrapData(await api.syncKingdee(row.id));
|
const data = this.unwrapData(await api.syncKingdee(row.id));
|
||||||
this.$message.success(`已生成金蝶付款单:${data}`);
|
this.$message.success(`已生成金蝶付款单:${data}`);
|
||||||
await this.loadTable();
|
this.loadTable();
|
||||||
this.warnAttachmentNotUploaded([row.id]);
|
|
||||||
},
|
},
|
||||||
async handleSync() {
|
async handleSync() {
|
||||||
if (this.syncLoading) return;
|
|
||||||
const rows = this.selection.filter(row => row.approvalStatus === 'approved');
|
const rows = this.selection.filter(row => row.approvalStatus === 'approved');
|
||||||
if (!rows.length) {
|
if (!rows.length) {
|
||||||
this.$message.warning('请选择至少一条审批通过的付款申请');
|
this.$message.warning('请选择至少一条审批通过的付款申请');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
this.syncLoading = true;
|
await api.syncKingdeeBatch(rows.map(row => row.id));
|
||||||
try {
|
this.$message.success(`已同步${rows.length}条付款申请`);
|
||||||
const data = this.unwrapData(await api.syncKingdeeBatch(rows.map(row => row.id))) || [];
|
this.loadTable();
|
||||||
const failedList = data.filter(item => String(item).includes('同步失败'));
|
|
||||||
if (failedList.length) {
|
|
||||||
this.$message({
|
|
||||||
type: 'warning',
|
|
||||||
message: `同步完成:成功${data.length - failedList.length}条,失败${failedList.length}条。${failedList.join(';')}`,
|
|
||||||
duration: 8000,
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
this.$message.success(`已同步${data.length}条付款申请`);
|
|
||||||
}
|
|
||||||
await this.loadTable();
|
|
||||||
this.warnAttachmentNotUploaded(rows.map(row => row.id));
|
|
||||||
} finally {
|
|
||||||
this.syncLoading = false;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
// 附件联带同步提示:同步成功但附件未全部上传时追加 warning,重推即自动补传
|
|
||||||
warnAttachmentNotUploaded(ids) {
|
|
||||||
const idSet = new Set(ids.map(String));
|
|
||||||
const pendingCount = (this.rows || []).filter(row => idSet.has(String(row.id))
|
|
||||||
&& ['pending', 'partial'].includes(row.attachmentSyncStatus)).length;
|
|
||||||
if (pendingCount) {
|
|
||||||
this.$message({
|
|
||||||
type: 'warning',
|
|
||||||
message: `其中${pendingCount}单附件未全部上传,可重推补传`,
|
|
||||||
duration: 8000,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
},
|
|
||||||
async handleSyncResult() {
|
|
||||||
if (this.resultSyncing) return;
|
|
||||||
this.resultSyncing = true;
|
|
||||||
try {
|
|
||||||
const data = this.unwrapData(await api.syncKingdeeResult());
|
|
||||||
this.$message.success(data || '金蝶付款结果回写完成');
|
|
||||||
this.loadTable();
|
|
||||||
} finally {
|
|
||||||
this.resultSyncing = false;
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
handleExport() {
|
handleExport() {
|
||||||
const headers = this.columns.map(item => item.label);
|
const headers = this.columns.map(item => item.label);
|
||||||
|
|||||||
@@ -80,11 +80,6 @@
|
|||||||
</section-card>
|
</section-card>
|
||||||
|
|
||||||
<section class="claim-record-page__table-panel">
|
<section class="claim-record-page__table-panel">
|
||||||
<div class="claim-record-page__toolbar">
|
|
||||||
<el-button :loading="refreshing" @click="handleRefreshKingdeeResult">
|
|
||||||
查询金蝶认领结果
|
|
||||||
</el-button>
|
|
||||||
</div>
|
|
||||||
<el-table v-loading="loading" :data="rows" border>
|
<el-table v-loading="loading" :data="rows" border>
|
||||||
<el-table-column type="index" label="序号" width="64" fixed="left" align="center" />
|
<el-table-column type="index" label="序号" width="64" fixed="left" align="center" />
|
||||||
<el-table-column
|
<el-table-column
|
||||||
@@ -172,7 +167,6 @@ export default {
|
|||||||
columns: receiptClaimRecordTableColumns,
|
columns: receiptClaimRecordTableColumns,
|
||||||
rows: [],
|
rows: [],
|
||||||
loading: false,
|
loading: false,
|
||||||
refreshing: false,
|
|
||||||
page: { current: 1, size: 10, total: 0 },
|
page: { current: 1, size: 10, total: 0 },
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
@@ -243,16 +237,6 @@ export default {
|
|||||||
this.$message.success(`作废成功,金蝶冲单号:${kingdeeBillNo}`);
|
this.$message.success(`作废成功,金蝶冲单号:${kingdeeBillNo}`);
|
||||||
await this.loadTable();
|
await this.loadTable();
|
||||||
},
|
},
|
||||||
async handleRefreshKingdeeResult() {
|
|
||||||
this.refreshing = true;
|
|
||||||
try {
|
|
||||||
const updated = Number(this.unwrapData(await api.queryKingdeeResult()) || 0);
|
|
||||||
this.$message.success(`金蝶认领结果回写完成,更新${updated}条`);
|
|
||||||
await this.loadTable();
|
|
||||||
} finally {
|
|
||||||
this.refreshing = false;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
hasPermission(code) {
|
hasPermission(code) {
|
||||||
return this.isAdmin || this.validData(this.permission?.[code], false);
|
return this.isAdmin || this.validData(this.permission?.[code], false);
|
||||||
},
|
},
|
||||||
@@ -297,13 +281,6 @@ export default {
|
|||||||
.claim-record-page__table-panel {
|
.claim-record-page__table-panel {
|
||||||
margin-top: 8px;
|
margin-top: 8px;
|
||||||
}
|
}
|
||||||
.claim-record-page__toolbar {
|
|
||||||
display: flex;
|
|
||||||
justify-content: flex-start;
|
|
||||||
align-items: center;
|
|
||||||
gap: 8px;
|
|
||||||
padding: 12px 0;
|
|
||||||
}
|
|
||||||
.claim-record-page__pagination {
|
.claim-record-page__pagination {
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: flex-end;
|
justify-content: flex-end;
|
||||||
|
|||||||
@@ -194,6 +194,64 @@ import { mapGetters } from 'vuex';
|
|||||||
import * as api from '@/api/payment/receiptFlow';
|
import * as api from '@/api/payment/receiptFlow';
|
||||||
import { receiptFlowTableColumns } from '@/option/payment/receiptFlow';
|
import { receiptFlowTableColumns } from '@/option/payment/receiptFlow';
|
||||||
|
|
||||||
|
const receiptFlowMockTemplates = [
|
||||||
|
{
|
||||||
|
receiptNoticeNo: 'SKTZ20260827001',
|
||||||
|
payerName: '华东供应链管理有限公司',
|
||||||
|
receiptAmount: 48000,
|
||||||
|
counterpartyName: '上海安捷运输有限公司',
|
||||||
|
counterpartyAccount: '310000000000001001',
|
||||||
|
counterpartyBank: '中国工商银行上海分行',
|
||||||
|
summary: '运输服务款',
|
||||||
|
transactionTime: '2026-08-27 09:30:00',
|
||||||
|
detailSerialNo: 'MOCK-RF-20260827-001',
|
||||||
|
sourceUpdatedTime: '2026-08-27 09:30:00',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
receiptNoticeNo: 'SKTZ20260827002',
|
||||||
|
payerName: '华东供应链管理有限公司',
|
||||||
|
receiptAmount: 26500,
|
||||||
|
counterpartyName: '江苏联运物流有限公司',
|
||||||
|
counterpartyAccount: '320100000000002002',
|
||||||
|
counterpartyBank: '中国建设银行南京分行',
|
||||||
|
summary: '物流服务款',
|
||||||
|
transactionTime: '2026-08-27 13:45:00',
|
||||||
|
detailSerialNo: 'MOCK-RF-20260827-002',
|
||||||
|
sourceUpdatedTime: '2026-08-27 13:45:00',
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
const randomDigits = length =>
|
||||||
|
Array.from({ length }, () => Math.floor(Math.random() * 10)).join('');
|
||||||
|
|
||||||
|
const formatMockDateTime = date => {
|
||||||
|
const pad = value => String(value).padStart(2, '0');
|
||||||
|
return `${date.getFullYear()}-${pad(date.getMonth() + 1)}-${pad(date.getDate())} ${pad(
|
||||||
|
date.getHours()
|
||||||
|
)}:${pad(date.getMinutes())}:${pad(date.getSeconds())}`;
|
||||||
|
};
|
||||||
|
|
||||||
|
const createReceiptFlowMockRows = () => {
|
||||||
|
const count = 20 + Math.floor(Math.random() * 31);
|
||||||
|
return Array.from({ length: count }, (_, index) => {
|
||||||
|
const template = receiptFlowMockTemplates[index % receiptFlowMockTemplates.length];
|
||||||
|
const transactionDate = new Date();
|
||||||
|
transactionDate.setDate(transactionDate.getDate() - Math.floor(Math.random() * 7));
|
||||||
|
transactionDate.setHours(8 + Math.floor(Math.random() * 10), Math.floor(Math.random() * 60), 0);
|
||||||
|
const transactionTime = formatMockDateTime(transactionDate);
|
||||||
|
const dateCode = transactionTime.slice(0, 10).replaceAll('-', '');
|
||||||
|
return {
|
||||||
|
...template,
|
||||||
|
receiptNoticeNo: `SKTZ${dateCode}${randomDigits(5)}`,
|
||||||
|
receiptAmount: Math.floor(10000 + Math.random() * 90000),
|
||||||
|
counterpartyAccount: `${template.counterpartyAccount.slice(0, -6)}${randomDigits(6)}`,
|
||||||
|
transactionTime,
|
||||||
|
detailSerialNo: `MOCK-RF-${dateCode}-${index + 1}${randomDigits(4)}`,
|
||||||
|
sourceUpdatedTime: transactionTime,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
const emptyQuery = () => ({
|
const emptyQuery = () => ({
|
||||||
receiptNoticeNo: '',
|
receiptNoticeNo: '',
|
||||||
counterpartyName: '',
|
counterpartyName: '',
|
||||||
@@ -271,8 +329,9 @@ export default {
|
|||||||
async handleSync() {
|
async handleSync() {
|
||||||
this.syncing = true;
|
this.syncing = true;
|
||||||
try {
|
try {
|
||||||
const syncedCount = Number(this.unwrapData(await api.sync()) || 0);
|
const mockRows = createReceiptFlowMockRows();
|
||||||
this.$message.success(`流水同步完成,共同步${syncedCount}条认领公告`);
|
const syncedCount = Number(this.unwrapData(await api.sync({ flows: mockRows })) || 0);
|
||||||
|
this.$message.success(`流水同步完成,共同步${syncedCount}条`);
|
||||||
await this.loadTable();
|
await this.loadTable();
|
||||||
} finally {
|
} finally {
|
||||||
this.syncing = false;
|
this.syncing = false;
|
||||||
|
|||||||
@@ -22,7 +22,6 @@
|
|||||||
type="date"
|
type="date"
|
||||||
value-format="YYYY-MM-DD"
|
value-format="YYYY-MM-DD"
|
||||||
placeholder="请选择"
|
placeholder="请选择"
|
||||||
@change="fetchBfiExchangeRate"
|
|
||||||
/>
|
/>
|
||||||
<el-select
|
<el-select
|
||||||
v-else-if="field.type === 'project' && editable"
|
v-else-if="field.type === 'project' && editable"
|
||||||
@@ -212,15 +211,8 @@
|
|||||||
</el-button>
|
</el-button>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
</el-form>
|
</el-form>
|
||||||
<el-table v-show="!detailCollapsed" :data="pagedDetails" border>
|
<el-table v-show="!detailCollapsed" :data="filteredDetails" border>
|
||||||
<el-table-column
|
<el-table-column type="index" label="序号" width="64" fixed="left" align="center" />
|
||||||
type="index"
|
|
||||||
label="序号"
|
|
||||||
width="64"
|
|
||||||
fixed="left"
|
|
||||||
align="center"
|
|
||||||
:index="detailIndexMethod"
|
|
||||||
/>
|
|
||||||
<el-table-column
|
<el-table-column
|
||||||
v-for="column in detailTableColumns"
|
v-for="column in detailTableColumns"
|
||||||
:key="column.prop"
|
:key="column.prop"
|
||||||
@@ -253,16 +245,6 @@
|
|||||||
</template>
|
</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
</el-table>
|
</el-table>
|
||||||
<div v-show="!detailCollapsed" class="formal-editor__pagination">
|
|
||||||
<el-pagination
|
|
||||||
v-model:current-page="detailPage.current"
|
|
||||||
v-model:page-size="detailPage.size"
|
|
||||||
:total="filteredDetails.length"
|
|
||||||
:page-sizes="[10, 20, 50, 100]"
|
|
||||||
layout="total, sizes, prev, pager, next, jumper"
|
|
||||||
@size-change="handleDetailPageSizeChange"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</section-card>
|
</section-card>
|
||||||
|
|
||||||
<section-card>
|
<section-card>
|
||||||
@@ -420,18 +402,8 @@
|
|||||||
<el-table-column label="操作" width="220" align="center">
|
<el-table-column label="操作" width="220" align="center">
|
||||||
<template #default="{ row, $index }">
|
<template #default="{ row, $index }">
|
||||||
<div class="formal-editor__links">
|
<div class="formal-editor__links">
|
||||||
<el-link
|
<el-link type="primary" @click="viewInvoiceAttachment(row)">查看</el-link>
|
||||||
type="primary"
|
<el-link type="primary" @click="downloadInvoiceAttachment(row)">下载</el-link>
|
||||||
:disabled="kingdeeAttachmentBusy"
|
|
||||||
@click="viewInvoiceAttachment(row)"
|
|
||||||
>查看</el-link
|
|
||||||
>
|
|
||||||
<el-link
|
|
||||||
type="primary"
|
|
||||||
:disabled="kingdeeAttachmentBusy"
|
|
||||||
@click="downloadInvoiceAttachment(row)"
|
|
||||||
>下载</el-link
|
|
||||||
>
|
|
||||||
<el-link v-if="editable" type="danger" @click="removeInvoice($index)">删除</el-link>
|
<el-link v-if="editable" type="danger" @click="removeInvoice($index)">删除</el-link>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
@@ -439,15 +411,66 @@
|
|||||||
</el-table>
|
</el-table>
|
||||||
</section-card>
|
</section-card>
|
||||||
|
|
||||||
<kingdee-invoice-pool-dialog
|
<el-dialog
|
||||||
v-model="invoiceClaimDialog.visible"
|
v-model="invoiceClaimDialog.visible"
|
||||||
title="查询可认领发票"
|
title="查询可认领发票"
|
||||||
confirm-text="确认认领"
|
width="92%"
|
||||||
multiple
|
append-to-body
|
||||||
:initial-invoice-no="String(invoiceClaimNo || '').trim()"
|
destroy-on-close
|
||||||
:query-api="invoicePoolQueryApi"
|
>
|
||||||
@confirm="handleInvoiceClaimConfirm"
|
<el-table
|
||||||
/>
|
ref="invoiceClaimTable"
|
||||||
|
v-loading="invoiceClaimDialog.loading"
|
||||||
|
:data="invoiceClaimPagedRows"
|
||||||
|
row-key="invoiceNo"
|
||||||
|
border
|
||||||
|
height="520"
|
||||||
|
@selection-change="invoiceClaimDialog.selected = $event"
|
||||||
|
>
|
||||||
|
<el-table-column
|
||||||
|
type="selection"
|
||||||
|
width="52"
|
||||||
|
fixed="left"
|
||||||
|
align="center"
|
||||||
|
reserve-selection
|
||||||
|
/>
|
||||||
|
<el-table-column type="index" label="序号" width="64" align="center" />
|
||||||
|
<el-table-column prop="invoiceNo" label="发票号" min-width="180" align="center" />
|
||||||
|
<el-table-column prop="invoiceDate" label="开票日期" min-width="130" align="center" />
|
||||||
|
<el-table-column prop="invoiceType" label="发票类型" min-width="150" align="center" />
|
||||||
|
<el-table-column prop="issuerName" label="开票单位" min-width="180" align="center" />
|
||||||
|
<el-table-column prop="receiverName" label="受票单位" min-width="180" align="center" />
|
||||||
|
<el-table-column
|
||||||
|
prop="invoiceAmount"
|
||||||
|
label="发票金额(含税)"
|
||||||
|
min-width="160"
|
||||||
|
align="right"
|
||||||
|
>
|
||||||
|
<template #default="{ row }">{{ formatMoney(row.invoiceAmount) }}</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column prop="matchedAmount" label="认领金额" min-width="140" align="right">
|
||||||
|
<template #default="{ row }">{{ formatMoney(row.matchedAmount) }}</template>
|
||||||
|
</el-table-column>
|
||||||
|
</el-table>
|
||||||
|
<div class="formal-editor__pagination">
|
||||||
|
<el-pagination
|
||||||
|
v-model:current-page="invoiceClaimDialog.page.current"
|
||||||
|
v-model:page-size="invoiceClaimDialog.page.size"
|
||||||
|
:total="invoiceClaimDialog.page.total"
|
||||||
|
:page-sizes="[10, 20, 50, 100]"
|
||||||
|
layout="total, sizes, prev, pager, next, jumper"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<template #footer>
|
||||||
|
<el-button @click="invoiceClaimDialog.visible = false">取消</el-button>
|
||||||
|
<el-button
|
||||||
|
type="primary"
|
||||||
|
:loading="invoiceClaimDialog.confirming"
|
||||||
|
@click="confirmInvoiceClaims"
|
||||||
|
>确认认领</el-button
|
||||||
|
>
|
||||||
|
</template>
|
||||||
|
</el-dialog>
|
||||||
|
|
||||||
<section-card
|
<section-card
|
||||||
v-if="readonly"
|
v-if="readonly"
|
||||||
@@ -600,7 +623,7 @@
|
|||||||
>提交</el-button
|
>提交</el-button
|
||||||
>
|
>
|
||||||
</template>
|
</template>
|
||||||
<div v-if="pageMode && !publicMode" class="formal-editor__page-actions">
|
<div v-if="pageMode" class="formal-editor__page-actions">
|
||||||
<el-button @click="visible = false">取消</el-button>
|
<el-button @click="visible = false">取消</el-button>
|
||||||
<el-button
|
<el-button
|
||||||
v-if="editable && !recordId"
|
v-if="editable && !recordId"
|
||||||
@@ -906,7 +929,6 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import { getMkPublicDetail } from '@/api/mk-process';
|
|
||||||
import {
|
import {
|
||||||
adjustDetail,
|
adjustDetail,
|
||||||
getCandidateDetails,
|
getCandidateDetails,
|
||||||
@@ -918,7 +940,6 @@ import {
|
|||||||
getNextNo,
|
getNextNo,
|
||||||
getReceiptClaims,
|
getReceiptClaims,
|
||||||
claimInvoices as claimInvoicesApi,
|
claimInvoices as claimInvoicesApi,
|
||||||
queryInvoicePool,
|
|
||||||
save,
|
save,
|
||||||
} from '@/api/settlement/formalSettlement';
|
} from '@/api/settlement/formalSettlement';
|
||||||
import {
|
import {
|
||||||
@@ -926,7 +947,6 @@ import {
|
|||||||
getDetailFees as getPreSettlementDetailFees,
|
getDetailFees as getPreSettlementDetailFees,
|
||||||
} from '@/api/settlement/preSettlement';
|
} from '@/api/settlement/preSettlement';
|
||||||
import { calculateAdjustedFee, getFeeDetail } from '@/api/settlement/receivable-payable-detail';
|
import { calculateAdjustedFee, getFeeDetail } from '@/api/settlement/receivable-payable-detail';
|
||||||
import { getBfiExchangeRate } from '@/api/base/currency';
|
|
||||||
import {
|
import {
|
||||||
createFormalSettlementForm,
|
createFormalSettlementForm,
|
||||||
formalSettlementFormFields,
|
formalSettlementFormFields,
|
||||||
@@ -943,17 +963,11 @@ import { h } from 'vue';
|
|||||||
import { InfoFilled } from '@element-plus/icons-vue';
|
import { InfoFilled } from '@element-plus/icons-vue';
|
||||||
import { mapGetters } from 'vuex';
|
import { mapGetters } from 'vuex';
|
||||||
import { downloadFileByUrl } from '@/utils/util';
|
import { downloadFileByUrl } from '@/utils/util';
|
||||||
import {
|
|
||||||
downloadKingdeeInvoiceFile,
|
|
||||||
isKingdeePathAttachment,
|
|
||||||
viewKingdeeInvoiceFile,
|
|
||||||
} from '@/utils/kingdee-attachment';
|
|
||||||
import KingdeeInvoicePoolDialog from '@/components/kingdee-invoice-pool-dialog/main.vue';
|
|
||||||
import * as XLSX from 'xlsx';
|
import * as XLSX from 'xlsx';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'FormalSettlementEditor',
|
name: 'FormalSettlementEditor',
|
||||||
components: { InfoFilled, KingdeeInvoicePoolDialog },
|
components: { InfoFilled },
|
||||||
props: {
|
props: {
|
||||||
modelValue: Boolean,
|
modelValue: Boolean,
|
||||||
recordId: [String, Number],
|
recordId: [String, Number],
|
||||||
@@ -963,10 +977,6 @@ export default {
|
|||||||
type: Object,
|
type: Object,
|
||||||
default: null,
|
default: null,
|
||||||
},
|
},
|
||||||
publicMode: {
|
|
||||||
type: Boolean,
|
|
||||||
default: false,
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
emits: ['update:modelValue', 'success'],
|
emits: ['update:modelValue', 'success'],
|
||||||
data() {
|
data() {
|
||||||
@@ -987,12 +997,13 @@ export default {
|
|||||||
invoices: [],
|
invoices: [],
|
||||||
invoiceClaimNo: '',
|
invoiceClaimNo: '',
|
||||||
invoiceClaimLoading: false,
|
invoiceClaimLoading: false,
|
||||||
// 金蝶侧发票附件查看/下载进行中(行组级 loading 守卫)
|
|
||||||
kingdeeAttachmentBusy: false,
|
|
||||||
invoiceClaimDialog: {
|
invoiceClaimDialog: {
|
||||||
visible: false,
|
visible: false,
|
||||||
selected: [],
|
loading: false,
|
||||||
confirming: false,
|
confirming: false,
|
||||||
|
rows: [],
|
||||||
|
selected: [],
|
||||||
|
page: { current: 1, size: 10, total: 0 },
|
||||||
},
|
},
|
||||||
attachmentUploadFiles: [],
|
attachmentUploadFiles: [],
|
||||||
attachmentTypeOptions: [],
|
attachmentTypeOptions: [],
|
||||||
@@ -1077,10 +1088,6 @@ export default {
|
|||||||
batchNo: '',
|
batchNo: '',
|
||||||
cargoName: '',
|
cargoName: '',
|
||||||
},
|
},
|
||||||
detailPage: {
|
|
||||||
current: 1,
|
|
||||||
size: 10,
|
|
||||||
},
|
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
@@ -1128,13 +1135,6 @@ export default {
|
|||||||
})
|
})
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
pagedDetails() {
|
|
||||||
const list = this.filteredDetails;
|
|
||||||
const size = Number(this.detailPage.size) || 10;
|
|
||||||
const current = Math.max(Number(this.detailPage.current) || 1, 1);
|
|
||||||
const start = (current - 1) * size;
|
|
||||||
return list.slice(start, start + size);
|
|
||||||
},
|
|
||||||
missingAttachmentTypeText() {
|
missingAttachmentTypeText() {
|
||||||
const missing = this.getMissingAttachmentTypes();
|
const missing = this.getMissingAttachmentTypes();
|
||||||
return missing.length ? `未上传:${missing.join('、')}` : '';
|
return missing.length ? `未上传:${missing.join('、')}` : '';
|
||||||
@@ -1146,6 +1146,11 @@ export default {
|
|||||||
});
|
});
|
||||||
return Array.from(names);
|
return Array.from(names);
|
||||||
},
|
},
|
||||||
|
invoiceClaimPagedRows() {
|
||||||
|
const { current, size } = this.invoiceClaimDialog.page;
|
||||||
|
const start = (current - 1) * size;
|
||||||
|
return this.invoiceClaimDialog.rows.slice(start, start + size);
|
||||||
|
},
|
||||||
},
|
},
|
||||||
watch: {
|
watch: {
|
||||||
modelValue: {
|
modelValue: {
|
||||||
@@ -1154,16 +1159,6 @@ export default {
|
|||||||
if (value) this.initialize();
|
if (value) this.initialize();
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
filteredDetails: {
|
|
||||||
handler(list) {
|
|
||||||
const size = Number(this.detailPage.size) || 10;
|
|
||||||
const maxPage = Math.max(1, Math.ceil((list?.length || 0) / size) || 1);
|
|
||||||
if (this.detailPage.current > maxPage) {
|
|
||||||
this.detailPage.current = maxPage;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
immediate: true,
|
|
||||||
},
|
|
||||||
summaryTotal(value) {
|
summaryTotal(value) {
|
||||||
this.form.settlementAmount = Number(value || 0).toFixed(2);
|
this.form.settlementAmount = Number(value || 0).toFixed(2);
|
||||||
this.form.localSettlementAmount = (
|
this.form.localSettlementAmount = (
|
||||||
@@ -1178,9 +1173,7 @@ export default {
|
|||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
async loadFormalDetail(id) {
|
async loadFormalDetail(id) {
|
||||||
const response = this.publicMode
|
const response = await getDetail(id);
|
||||||
? await getMkPublicDetail('formal-settlement', id)
|
|
||||||
: await getDetail(id);
|
|
||||||
const data = this.unwrapData(response);
|
const data = this.unwrapData(response);
|
||||||
this.form = {
|
this.form = {
|
||||||
...createFormalSettlementForm(),
|
...createFormalSettlementForm(),
|
||||||
@@ -1192,13 +1185,12 @@ export default {
|
|||||||
};
|
};
|
||||||
this.sources = data.sources || [];
|
this.sources = data.sources || [];
|
||||||
this.details = data.details || [];
|
this.details = data.details || [];
|
||||||
this.detailPage.current = 1;
|
|
||||||
this.summaryFees = data.summaryFees || [];
|
this.summaryFees = data.summaryFees || [];
|
||||||
// 服务端数据已刷新,费用快照与暂存调整全部作废。
|
// 服务端数据已刷新,费用快照与暂存调整全部作废。
|
||||||
this.detailFeeSnapshots = {};
|
this.detailFeeSnapshots = {};
|
||||||
this.pendingAdjustments = {};
|
this.pendingAdjustments = {};
|
||||||
this.paymentApplications = data.paymentApplications || [];
|
this.paymentApplications = data.paymentApplications || [];
|
||||||
if (this.readonly && this.form.settlementType === 'receivable' && !this.publicMode) {
|
if (this.readonly && this.form.settlementType === 'receivable') {
|
||||||
this.receiptClaims = this.unwrapData(await getReceiptClaims(id)) || [];
|
this.receiptClaims = this.unwrapData(await getReceiptClaims(id)) || [];
|
||||||
}
|
}
|
||||||
this.adjustments = data.adjustments || [];
|
this.adjustments = data.adjustments || [];
|
||||||
@@ -1237,6 +1229,7 @@ export default {
|
|||||||
this.invoices = [];
|
this.invoices = [];
|
||||||
this.invoiceClaimNo = '';
|
this.invoiceClaimNo = '';
|
||||||
this.invoiceClaimDialog.visible = false;
|
this.invoiceClaimDialog.visible = false;
|
||||||
|
this.invoiceClaimDialog.rows = [];
|
||||||
this.invoiceClaimDialog.selected = [];
|
this.invoiceClaimDialog.selected = [];
|
||||||
this.attachmentUploadFiles = [];
|
this.attachmentUploadFiles = [];
|
||||||
this.billingRuleDialog = {
|
this.billingRuleDialog = {
|
||||||
@@ -1244,23 +1237,19 @@ export default {
|
|||||||
rule: null,
|
rule: null,
|
||||||
};
|
};
|
||||||
this.detailCollapsed = false;
|
this.detailCollapsed = false;
|
||||||
this.detailPage = { current: 1, size: 10 };
|
|
||||||
this.resetDetailQuery(false);
|
this.resetDetailQuery(false);
|
||||||
if (!this.publicMode) {
|
await Promise.all([
|
||||||
await Promise.all([
|
this.loadAllContracts(),
|
||||||
this.loadAllContracts(),
|
this.loadFeeOptions(),
|
||||||
this.loadFeeOptions(),
|
this.loadFeeCategoryOptions(),
|
||||||
this.loadFeeCategoryOptions(),
|
this.loadAttachmentTypeOptions(),
|
||||||
this.loadAttachmentTypeOptions(),
|
this.loadTransportTypeOptions(),
|
||||||
this.loadTransportTypeOptions(),
|
]);
|
||||||
]);
|
|
||||||
}
|
|
||||||
if (!this.recordId) {
|
if (!this.recordId) {
|
||||||
this.form.exchangeRateDate = this.$dayjs().format('YYYY-MM-DD');
|
this.form.exchangeRateDate = this.$dayjs().format('YYYY-MM-DD');
|
||||||
if (this.initialData) await this.applyInitialData();
|
if (this.initialData) await this.applyInitialData();
|
||||||
Object.assign(this.form, newRecordAudit);
|
Object.assign(this.form, newRecordAudit);
|
||||||
await this.refreshFormalSettlementNo();
|
await this.refreshFormalSettlementNo();
|
||||||
await this.fetchBfiExchangeRate();
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
this.loading = true;
|
this.loading = true;
|
||||||
@@ -1339,7 +1328,6 @@ export default {
|
|||||||
settlementAmountTax:
|
settlementAmountTax:
|
||||||
row.settlementAmountTax ?? row.totalAmount ?? row.afterAmount ?? row.settlementAmount,
|
row.settlementAmountTax ?? row.totalAmount ?? row.afterAmount ?? row.settlementAmount,
|
||||||
}));
|
}));
|
||||||
this.detailPage.current = 1;
|
|
||||||
this.form.sourceDetailIds = this.details.map(item => item.sourceDetailId);
|
this.form.sourceDetailIds = this.details.map(item => item.sourceDetailId);
|
||||||
await this.refreshSummaryFees();
|
await this.refreshSummaryFees();
|
||||||
},
|
},
|
||||||
@@ -1385,7 +1373,6 @@ export default {
|
|||||||
sourcePreSettlementId: settlement.id,
|
sourcePreSettlementId: settlement.id,
|
||||||
}))
|
}))
|
||||||
);
|
);
|
||||||
this.detailPage.current = 1;
|
|
||||||
const summaryMap = new Map();
|
const summaryMap = new Map();
|
||||||
settlements
|
settlements
|
||||||
.flatMap(settlement => settlement.summaryFees || [])
|
.flatMap(settlement => settlement.summaryFees || [])
|
||||||
@@ -1495,7 +1482,6 @@ export default {
|
|||||||
(settlementType === 'receivable' ? contract.partyA : contract.partyB),
|
(settlementType === 'receivable' ? contract.partyA : contract.partyB),
|
||||||
settlementType,
|
settlementType,
|
||||||
settlementTypeName: settlementType === 'receivable' ? '应收' : '应付',
|
settlementTypeName: settlementType === 'receivable' ? '应收' : '应付',
|
||||||
currency: contract.settlementCurrency || 'RMB',
|
|
||||||
});
|
});
|
||||||
this.sources = [];
|
this.sources = [];
|
||||||
this.details = [];
|
this.details = [];
|
||||||
@@ -1503,23 +1489,6 @@ export default {
|
|||||||
this.form.sourcePreSettlementIds = [];
|
this.form.sourcePreSettlementIds = [];
|
||||||
this.form.sourceDetailIds = [];
|
this.form.sourceDetailIds = [];
|
||||||
if (refreshSettlementNo) this.refreshFormalSettlementNo();
|
if (refreshSettlementNo) this.refreshFormalSettlementNo();
|
||||||
if (this.form.currency !== 'RMB') this.fetchBfiExchangeRate();
|
|
||||||
},
|
|
||||||
async fetchBfiExchangeRate() {
|
|
||||||
const currency = this.form.currency;
|
|
||||||
if (!currency || currency === 'RMB') return;
|
|
||||||
if (!this.form.exchangeRateDate) return;
|
|
||||||
try {
|
|
||||||
const { data } = await getBfiExchangeRate(currency, this.form.exchangeRateDate);
|
|
||||||
const rateData = data?.data;
|
|
||||||
if (rateData?.excval != null) {
|
|
||||||
this.form.exchangeRate = Number(rateData.excval);
|
|
||||||
} else {
|
|
||||||
this.$message.warning(`未查询到 ${currency} 的BFI汇率数据`);
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
this.$message.warning('查询BFI汇率失败,请手动输入结算汇率');
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
openCandidateDialog() {
|
openCandidateDialog() {
|
||||||
this.candidate.visible = true;
|
this.candidate.visible = true;
|
||||||
@@ -1632,7 +1601,6 @@ export default {
|
|||||||
...this.details.filter(item => item.formalSettlementId),
|
...this.details.filter(item => item.formalSettlementId),
|
||||||
...existing.values(),
|
...existing.values(),
|
||||||
];
|
];
|
||||||
this.detailPage.current = 1;
|
|
||||||
this.form.sourceDetailIds = [...existing.values()].map(item => item.sourceDetailId);
|
this.form.sourceDetailIds = [...existing.values()].map(item => item.sourceDetailId);
|
||||||
this.detailCandidate.loading = true;
|
this.detailCandidate.loading = true;
|
||||||
try {
|
try {
|
||||||
@@ -1851,9 +1819,6 @@ export default {
|
|||||||
return sourceDetailId ? `src:${sourceDetailId}` : '';
|
return sourceDetailId ? `src:${sourceDetailId}` : '';
|
||||||
},
|
},
|
||||||
async loadDetailFeeRows(detail) {
|
async loadDetailFeeRows(detail) {
|
||||||
if (this.publicMode) {
|
|
||||||
return [this.normalizeAdjustRow(detail)];
|
|
||||||
}
|
|
||||||
if (detail.formalSettlementId && detail.id) {
|
if (detail.formalSettlementId && detail.id) {
|
||||||
const rows = this.unwrapData(await getDetailFees(detail.id)) || [];
|
const rows = this.unwrapData(await getDetailFees(detail.id)) || [];
|
||||||
return rows.map(item => this.normalizeAdjustRow(item));
|
return rows.map(item => this.normalizeAdjustRow(item));
|
||||||
@@ -1955,19 +1920,12 @@ export default {
|
|||||||
},
|
},
|
||||||
handleDetailQuery() {
|
handleDetailQuery() {
|
||||||
this.appliedDetailQuery = { ...this.detailQuery };
|
this.appliedDetailQuery = { ...this.detailQuery };
|
||||||
this.detailPage.current = 1;
|
|
||||||
},
|
},
|
||||||
resetDetailQuery(apply = true) {
|
resetDetailQuery(apply = true) {
|
||||||
this.detailQuery = { documentNo: '', waybillNo: '', batchNo: '', cargoName: '' };
|
this.detailQuery = { documentNo: '', waybillNo: '', batchNo: '', cargoName: '' };
|
||||||
if (apply) this.handleDetailQuery();
|
if (apply) this.handleDetailQuery();
|
||||||
else this.appliedDetailQuery = { ...this.detailQuery };
|
else this.appliedDetailQuery = { ...this.detailQuery };
|
||||||
},
|
},
|
||||||
handleDetailPageSizeChange() {
|
|
||||||
this.detailPage.current = 1;
|
|
||||||
},
|
|
||||||
detailIndexMethod(index) {
|
|
||||||
return (this.detailPage.current - 1) * this.detailPage.size + index + 1;
|
|
||||||
},
|
|
||||||
exportDetails() {
|
exportDetails() {
|
||||||
if (!this.details.length) return this.$message.warning('暂无可导出的结算明细');
|
if (!this.details.length) return this.$message.warning('暂无可导出的结算明细');
|
||||||
const rows = this.details.map((row, index) => {
|
const rows = this.details.map((row, index) => {
|
||||||
@@ -2244,19 +2202,79 @@ export default {
|
|||||||
this.adjust.saving = false;
|
this.adjust.saving = false;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
claimInvoices() {
|
||||||
|
this.invoiceClaimLoading = true;
|
||||||
|
try {
|
||||||
|
const keyword = String(this.invoiceClaimNo || '').trim();
|
||||||
|
const defaultNo = `MOCK${this.$dayjs().format('YYYYMMDD')}`;
|
||||||
|
const baseNo = (keyword || defaultNo).slice(0, 29);
|
||||||
|
const numbers = [baseNo, `${baseNo}-02`, `${baseNo}-03`];
|
||||||
|
const invoiceAmounts = [1000, 2000, 3000];
|
||||||
|
const availableAmounts = [1000, 1000, 3000];
|
||||||
|
const preferredMatchedAmounts = [1000, 1000, 2000];
|
||||||
|
let remainingAmount = Math.max(
|
||||||
|
Number(this.form.settlementAmount || this.summaryTotal || 0) -
|
||||||
|
this.invoices.reduce((total, item) => total + Number(item.matchedAmount || 0), 0),
|
||||||
|
0
|
||||||
|
);
|
||||||
|
const existingNumbers = new Set(this.invoices.map(item => String(item.invoiceNo)));
|
||||||
|
const mockRows = numbers
|
||||||
|
.map((invoiceNo, index) => {
|
||||||
|
const matchedAmount = Math.min(
|
||||||
|
preferredMatchedAmounts[index],
|
||||||
|
availableAmounts[index],
|
||||||
|
remainingAmount
|
||||||
|
);
|
||||||
|
remainingAmount = Number((remainingAmount - matchedAmount).toFixed(2));
|
||||||
|
return {
|
||||||
|
invoiceNo,
|
||||||
|
invoiceDate: this.$dayjs().subtract(index, 'day').format('YYYY-MM-DD'),
|
||||||
|
invoiceType: index === 1 ? '增值税普通发票' : '增值税专用发票',
|
||||||
|
taxRate: [3, 6, 9][index],
|
||||||
|
invoiceAmount: invoiceAmounts[index],
|
||||||
|
availableInvoiceAmount: availableAmounts[index],
|
||||||
|
matchedAmount,
|
||||||
|
attachmentJson: '',
|
||||||
|
mockData: true,
|
||||||
|
};
|
||||||
|
})
|
||||||
|
.filter(item => !existingNumbers.has(item.invoiceNo));
|
||||||
|
if (!mockRows.length) {
|
||||||
|
this.$message.warning('当前发票号码的Mock数据已认领');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.invoices = [...this.invoices, ...mockRows];
|
||||||
|
this.$message.success(`已生成${mockRows.length}条Mock发票数据`);
|
||||||
|
} finally {
|
||||||
|
this.invoiceClaimLoading = false;
|
||||||
|
}
|
||||||
|
},
|
||||||
openInvoiceClaimDialog() {
|
openInvoiceClaimDialog() {
|
||||||
|
const stamp = Date.now();
|
||||||
|
this.invoiceClaimDialog.loading = true;
|
||||||
|
this.invoiceClaimDialog.rows = Array.from({ length: 80 }, (_, index) => {
|
||||||
|
const amount = Math.floor(1000 + Math.random() * 49000);
|
||||||
|
return {
|
||||||
|
invoiceNo: `MOCK${stamp}${String(index + 1).padStart(3, '0')}`.slice(0, 32),
|
||||||
|
invoiceDate: this.$dayjs()
|
||||||
|
.subtract(Math.floor(Math.random() * 90), 'day')
|
||||||
|
.format('YYYY-MM-DD'),
|
||||||
|
invoiceType: index % 2 ? '增值税普通发票' : '增值税专用发票',
|
||||||
|
issuerName: this.form.payerName || 'Mock开票单位',
|
||||||
|
receiverName: this.form.payeeName || 'Mock受票单位',
|
||||||
|
invoiceAmount: amount,
|
||||||
|
availableInvoiceAmount: amount,
|
||||||
|
matchedAmount: amount,
|
||||||
|
taxRate: [3, 6, 9, 13][index % 4],
|
||||||
|
attachmentJson: '',
|
||||||
|
mockData: true,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
this.invoiceClaimDialog.selected = [];
|
||||||
|
this.invoiceClaimDialog.page.current = 1;
|
||||||
|
this.invoiceClaimDialog.page.total = 80;
|
||||||
this.invoiceClaimDialog.visible = true;
|
this.invoiceClaimDialog.visible = true;
|
||||||
},
|
this.invoiceClaimDialog.loading = false;
|
||||||
invoicePoolQueryApi(params) {
|
|
||||||
return queryInvoicePool(params);
|
|
||||||
},
|
|
||||||
async handleInvoiceClaimConfirm(rows) {
|
|
||||||
// 统一票池行附件字段为复数 attachmentsJson,认领请求契约是单数 attachmentJson,中转处归一
|
|
||||||
this.invoiceClaimDialog.selected = rows.map(row => ({
|
|
||||||
...row,
|
|
||||||
attachmentJson: row.attachmentJson || row.attachmentsJson,
|
|
||||||
}));
|
|
||||||
await this.confirmInvoiceClaims();
|
|
||||||
},
|
},
|
||||||
async confirmInvoiceClaims() {
|
async confirmInvoiceClaims() {
|
||||||
if (!this.invoiceClaimDialog.selected.length) {
|
if (!this.invoiceClaimDialog.selected.length) {
|
||||||
@@ -2282,11 +2300,7 @@ export default {
|
|||||||
if (Number(this.form.settlementAmount || 0) > 0) {
|
if (Number(this.form.settlementAmount || 0) > 0) {
|
||||||
remaining = Math.max(0, remaining - matchedAmount);
|
remaining = Math.max(0, remaining - matchedAmount);
|
||||||
}
|
}
|
||||||
return {
|
return { ...row, matchedAmount };
|
||||||
...row,
|
|
||||||
invoiceType: row.invoiceTypeName || row.invoiceType || '',
|
|
||||||
matchedAmount,
|
|
||||||
};
|
|
||||||
})
|
})
|
||||||
.filter(row => row.matchedAmount > 0);
|
.filter(row => row.matchedAmount > 0);
|
||||||
if (!invoices.length) {
|
if (!invoices.length) {
|
||||||
@@ -2372,38 +2386,14 @@ export default {
|
|||||||
const attachment = this.invoiceAttachment(row);
|
const attachment = this.invoiceAttachment(row);
|
||||||
return attachment.originalName || attachment.name || `${row.invoiceNo || '发票'}.pdf`;
|
return attachment.originalName || attachment.name || `${row.invoiceNo || '发票'}.pdf`;
|
||||||
},
|
},
|
||||||
async viewInvoiceAttachment(row) {
|
viewInvoiceAttachment(row) {
|
||||||
const attachment = this.invoiceAttachment(row);
|
|
||||||
// 金蝶侧附件(path 型)经后端代理 A801-2 预览,OSS 直链维持 window.open
|
|
||||||
if (isKingdeePathAttachment(attachment)) {
|
|
||||||
if (this.kingdeeAttachmentBusy) return;
|
|
||||||
this.kingdeeAttachmentBusy = true;
|
|
||||||
try {
|
|
||||||
await viewKingdeeInvoiceFile(row.invoiceNo, this.invoiceAttachmentName(row));
|
|
||||||
} finally {
|
|
||||||
this.kingdeeAttachmentBusy = false;
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const url = this.invoiceAttachmentUrl(row);
|
const url = this.invoiceAttachmentUrl(row);
|
||||||
if (!url) return this.$message.warning('当前发票暂无可查看附件');
|
if (!url) return this.$message.warning('当前Mock发票暂无可查看附件');
|
||||||
window.open(url, '_blank');
|
window.open(url, '_blank');
|
||||||
},
|
},
|
||||||
async downloadInvoiceAttachment(row) {
|
downloadInvoiceAttachment(row) {
|
||||||
const attachment = this.invoiceAttachment(row);
|
|
||||||
// 金蝶侧附件(path 型)经后端代理 A801-2 下载,OSS 直链维持前端直连下载
|
|
||||||
if (isKingdeePathAttachment(attachment)) {
|
|
||||||
if (this.kingdeeAttachmentBusy) return;
|
|
||||||
this.kingdeeAttachmentBusy = true;
|
|
||||||
try {
|
|
||||||
await downloadKingdeeInvoiceFile(row.invoiceNo, this.invoiceAttachmentName(row));
|
|
||||||
} finally {
|
|
||||||
this.kingdeeAttachmentBusy = false;
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const url = this.invoiceAttachmentUrl(row);
|
const url = this.invoiceAttachmentUrl(row);
|
||||||
if (!url) return this.$message.warning('当前发票暂无可下载附件');
|
if (!url) return this.$message.warning('当前Mock发票暂无可下载附件');
|
||||||
downloadFileByUrl(url, this.invoiceAttachmentName(row));
|
downloadFileByUrl(url, this.invoiceAttachmentName(row));
|
||||||
},
|
},
|
||||||
buildSavePayload() {
|
buildSavePayload() {
|
||||||
|
|||||||
@@ -21,7 +21,6 @@
|
|||||||
type="primary"
|
type="primary"
|
||||||
plain
|
plain
|
||||||
:disabled="selection.length !== 1"
|
:disabled="selection.length !== 1"
|
||||||
:loading="syncLoading"
|
|
||||||
@click="$emit('sync')"
|
@click="$emit('sync')"
|
||||||
>同步金蝶</el-button
|
>同步金蝶</el-button
|
||||||
>
|
>
|
||||||
@@ -133,7 +132,6 @@ export default {
|
|||||||
rows: { type: Array, default: () => [] },
|
rows: { type: Array, default: () => [] },
|
||||||
columns: { type: Array, default: () => [] },
|
columns: { type: Array, default: () => [] },
|
||||||
loading: { type: Boolean, default: false },
|
loading: { type: Boolean, default: false },
|
||||||
syncLoading: { type: Boolean, default: false },
|
|
||||||
page: { type: Object, required: true },
|
page: { type: Object, required: true },
|
||||||
hasPermission: { type: Function, required: true },
|
hasPermission: { type: Function, required: true },
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -42,7 +42,7 @@
|
|||||||
format="YYYY-MM-DD"
|
format="YYYY-MM-DD"
|
||||||
:disabled="!editable || form.currency === 'RMB'"
|
:disabled="!editable || form.currency === 'RMB'"
|
||||||
placeholder="请选择"
|
placeholder="请选择"
|
||||||
@change="handleExchangeRateDateChange"
|
@change="recalculateLocalAmount"
|
||||||
/>
|
/>
|
||||||
<el-input-number
|
<el-input-number
|
||||||
v-else-if="field.type === 'number'"
|
v-else-if="field.type === 'number'"
|
||||||
@@ -66,12 +66,6 @@
|
|||||||
<span v-else-if="field.prop === 'settlementType'" class="form-readonly">
|
<span v-else-if="field.prop === 'settlementType'" class="form-readonly">
|
||||||
{{ settlementTypeName }}
|
{{ settlementTypeName }}
|
||||||
</span>
|
</span>
|
||||||
<span v-else-if="field.prop === 'preSettlementNo'" class="form-readonly">
|
|
||||||
{{ form.preSettlementNo || (!form.id ? '系统自动生成' : '-') }}
|
|
||||||
</span>
|
|
||||||
<span v-else-if="field.prop === 'createTime'" class="form-readonly">
|
|
||||||
{{ formatCreateDate(form.createTime) }}
|
|
||||||
</span>
|
|
||||||
<span v-else-if="field.money" class="form-readonly">
|
<span v-else-if="field.money" class="form-readonly">
|
||||||
{{ formatMoney(form[field.prop], moneyCurrency(field.prop)) }}
|
{{ formatMoney(form[field.prop], moneyCurrency(field.prop)) }}
|
||||||
</span>
|
</span>
|
||||||
@@ -190,6 +184,7 @@
|
|||||||
</template>
|
</template>
|
||||||
<el-form
|
<el-form
|
||||||
:model="detailQuery"
|
:model="detailQuery"
|
||||||
|
inline
|
||||||
label-position="right"
|
label-position="right"
|
||||||
label-width="auto"
|
label-width="auto"
|
||||||
class="pre-settlement-editor__detail-filter"
|
class="pre-settlement-editor__detail-filter"
|
||||||
@@ -233,15 +228,8 @@
|
|||||||
</el-form-item>
|
</el-form-item>
|
||||||
</el-form>
|
</el-form>
|
||||||
<div v-show="!detailCollapsed">
|
<div v-show="!detailCollapsed">
|
||||||
<el-table :data="pagedDetails" border class="pre-settlement-editor__detail-table">
|
<el-table :data="filteredDetails" border class="pre-settlement-editor__detail-table">
|
||||||
<el-table-column
|
<el-table-column type="index" label="序号" width="64" fixed="left" align="center" />
|
||||||
type="index"
|
|
||||||
label="序号"
|
|
||||||
width="64"
|
|
||||||
fixed="left"
|
|
||||||
align="center"
|
|
||||||
:index="detailIndexMethod"
|
|
||||||
/>
|
|
||||||
<el-table-column
|
<el-table-column
|
||||||
v-for="column in visibleDetailColumns"
|
v-for="column in visibleDetailColumns"
|
||||||
:key="column.prop"
|
:key="column.prop"
|
||||||
@@ -312,16 +300,6 @@
|
|||||||
</template>
|
</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
</el-table>
|
</el-table>
|
||||||
<div class="pre-settlement-editor__pagination">
|
|
||||||
<el-pagination
|
|
||||||
v-model:current-page="detailPage.current"
|
|
||||||
v-model:page-size="detailPage.size"
|
|
||||||
:total="filteredDetails.length"
|
|
||||||
:page-sizes="[10, 20, 50, 100]"
|
|
||||||
layout="total, sizes, prev, pager, next, jumper"
|
|
||||||
@size-change="handleDetailPageSizeChange"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</section-card>
|
</section-card>
|
||||||
|
|
||||||
@@ -335,43 +313,13 @@
|
|||||||
<el-table :data="attachments" border @selection-change="handleAttachmentSelectionChange">
|
<el-table :data="attachments" border @selection-change="handleAttachmentSelectionChange">
|
||||||
<el-table-column type="selection" width="55" align="center" />
|
<el-table-column type="selection" width="55" align="center" />
|
||||||
<el-table-column type="index" label="序号" width="70" align="center" />
|
<el-table-column type="index" label="序号" width="70" align="center" />
|
||||||
<el-table-column label="附件类型" min-width="180" align="center">
|
<el-table-column label="文件名" min-width="260" show-overflow-tooltip>
|
||||||
<template #default="{ row }">
|
|
||||||
<el-select
|
|
||||||
v-if="editable"
|
|
||||||
v-model="row.type"
|
|
||||||
filterable
|
|
||||||
placeholder="请选择"
|
|
||||||
@change="sortAttachments"
|
|
||||||
>
|
|
||||||
<el-option
|
|
||||||
v-for="item in attachmentTypeOptions"
|
|
||||||
:key="item.value"
|
|
||||||
:label="item.label"
|
|
||||||
:value="item.value"
|
|
||||||
/>
|
|
||||||
</el-select>
|
|
||||||
<span v-else>{{ attachmentTypeName(row.type) }}</span>
|
|
||||||
</template>
|
|
||||||
</el-table-column>
|
|
||||||
<el-table-column label="文件名称" min-width="260" show-overflow-tooltip>
|
|
||||||
<template #default="{ row }">
|
<template #default="{ row }">
|
||||||
<el-link type="primary" @click="previewAttachment(row)">
|
<el-link type="primary" @click="previewAttachment(row)">
|
||||||
{{ attachmentName(row) }}
|
{{ attachmentName(row) }}
|
||||||
</el-link>
|
</el-link>
|
||||||
</template>
|
</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
<el-table-column label="附件描述" min-width="220">
|
|
||||||
<template #default="{ row }">
|
|
||||||
<span v-if="!editable">{{ row.description || '-' }}</span>
|
|
||||||
<el-input
|
|
||||||
v-else
|
|
||||||
v-model="row.description"
|
|
||||||
maxlength="200"
|
|
||||||
placeholder="请输入"
|
|
||||||
/>
|
|
||||||
</template>
|
|
||||||
</el-table-column>
|
|
||||||
<el-table-column label="文件大小" width="120" align="center">
|
<el-table-column label="文件大小" width="120" align="center">
|
||||||
<template #default="{ row }">{{ formatFileSize(row.size) }}</template>
|
<template #default="{ row }">{{ formatFileSize(row.size) }}</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
@@ -477,7 +425,7 @@
|
|||||||
提交
|
提交
|
||||||
</el-button>
|
</el-button>
|
||||||
</template>
|
</template>
|
||||||
<div v-if="pageMode && !publicMode" class="pre-settlement-editor__page-actions">
|
<div v-if="pageMode" class="pre-settlement-editor__page-actions">
|
||||||
<el-button @click="visible = false">取消</el-button>
|
<el-button @click="visible = false">取消</el-button>
|
||||||
<el-button v-if="editable" type="primary" plain :loading="saving" @click="saveDraft(false)"
|
<el-button v-if="editable" type="primary" plain :loading="saving" @click="saveDraft(false)"
|
||||||
>保存</el-button
|
>保存</el-button
|
||||||
@@ -733,113 +681,134 @@
|
|||||||
append-to-body
|
append-to-body
|
||||||
destroy-on-close
|
destroy-on-close
|
||||||
>
|
>
|
||||||
<el-table v-loading="adjustDialog.loading" :data="adjustRows" border>
|
<el-tabs v-model="adjustDialog.activeTab" class="pre-settlement-editor__adjust-tabs">
|
||||||
<el-table-column type="index" label="序号" width="64" fixed="left" align="center" />
|
<el-tab-pane label="结算明细调整" name="adjust">
|
||||||
<el-table-column prop="cargoName" label="货物名称" min-width="140" align="center" />
|
<el-table v-loading="adjustDialog.loading" :data="adjustRows" border>
|
||||||
<el-table-column prop="cargoType" label="货物类型" min-width="130" align="center" />
|
<el-table-column type="index" label="序号" width="64" fixed="left" align="center" />
|
||||||
<el-table-column label="计费规则" min-width="190" align="center">
|
<el-table-column prop="cargoName" label="货物名称" min-width="140" align="center" />
|
||||||
<template #default="{ row }">
|
<el-table-column prop="cargoType" label="货物类型" min-width="130" align="center" />
|
||||||
<div class="pre-settlement-editor__billing-rule-cell">
|
<el-table-column label="计费规则" min-width="190" align="center">
|
||||||
<span>{{ displayValue(billingRuleName(row)) }}</span>
|
<template #default="{ row }">
|
||||||
<el-icon
|
<div class="pre-settlement-editor__billing-rule-cell">
|
||||||
class="pre-settlement-editor__billing-rule-icon"
|
<span>{{ displayValue(billingRuleName(row)) }}</span>
|
||||||
title="查看计费规则详情"
|
<el-icon
|
||||||
@click="openBillingRuleInfo(row)"
|
class="pre-settlement-editor__billing-rule-icon"
|
||||||
>
|
title="查看计费规则详情"
|
||||||
<InfoFilled />
|
@click="openBillingRuleInfo(row)"
|
||||||
</el-icon>
|
>
|
||||||
</div>
|
<InfoFilled />
|
||||||
</template>
|
</el-icon>
|
||||||
</el-table-column>
|
</div>
|
||||||
<el-table-column label="运输总量" min-width="150" align="center">
|
</template>
|
||||||
<template #default="{ row }">
|
</el-table-column>
|
||||||
<span v-if="adjustDialog.readonly">{{ formatQuantity(row) }}</span>
|
<el-table-column label="运输总量" min-width="150" align="center">
|
||||||
<el-input-number
|
<template #default="{ row }">
|
||||||
v-else
|
<span v-if="adjustDialog.readonly">{{ formatQuantity(row) }}</span>
|
||||||
v-model="row.transportQuantity"
|
<el-input-number
|
||||||
:min="0"
|
v-else
|
||||||
:precision="2"
|
v-model="row.transportQuantity"
|
||||||
:controls="false"
|
:min="0"
|
||||||
@change="recalculateAdjustRow(row, 'transportQuantity')"
|
:precision="2"
|
||||||
|
:controls="false"
|
||||||
|
@change="recalculateAdjustRow(row, 'transportQuantity')"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column label="里程(KM)" min-width="140" align="center">
|
||||||
|
<template #default="{ row }">
|
||||||
|
<el-input-number
|
||||||
|
v-model="row.mileage"
|
||||||
|
:min="0"
|
||||||
|
:precision="2"
|
||||||
|
:controls="false"
|
||||||
|
:disabled="adjustDialog.readonly"
|
||||||
|
@change="recalculateAdjustRow(row, 'mileage')"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column label="运输单价" min-width="140" align="center">
|
||||||
|
<template #default="{ row }">
|
||||||
|
<el-input-number
|
||||||
|
v-model="row.unitPrice"
|
||||||
|
:min="0"
|
||||||
|
:precision="2"
|
||||||
|
:controls="false"
|
||||||
|
:disabled="adjustDialog.readonly"
|
||||||
|
@change="recalculateAdjustRow(row, 'unitPrice')"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column
|
||||||
|
v-for="feeItem in adjustFeeItemNames"
|
||||||
|
:key="feeItem"
|
||||||
|
:label="feeItem"
|
||||||
|
min-width="140"
|
||||||
|
align="center"
|
||||||
|
>
|
||||||
|
<template #default="{ row }">
|
||||||
|
<el-input-number
|
||||||
|
v-model="row.feeItems[feeItem]"
|
||||||
|
:min="0"
|
||||||
|
:precision="2"
|
||||||
|
:controls="false"
|
||||||
|
:disabled="adjustDialog.readonly"
|
||||||
|
@change="recalculateAdjustRow(row, feeItem)"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column label="结算金额(含税)" min-width="170" align="center">
|
||||||
|
<template #default="{ row }">
|
||||||
|
<el-input-number
|
||||||
|
v-model="row.settlementAmountTax"
|
||||||
|
:precision="2"
|
||||||
|
:controls="false"
|
||||||
|
:disabled="adjustDialog.readonly"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column label="备注" min-width="200" align="center">
|
||||||
|
<template #default="{ row }">
|
||||||
|
<el-input v-model="row.remark" maxlength="200" :disabled="adjustDialog.readonly" />
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
</el-table>
|
||||||
|
<el-form
|
||||||
|
v-if="!adjustDialog.readonly"
|
||||||
|
:model="adjustDialog"
|
||||||
|
label-position="right"
|
||||||
|
label-width="auto"
|
||||||
|
class="pre-settlement-editor__adjust-reason"
|
||||||
|
>
|
||||||
|
<el-form-item label="调整原因">
|
||||||
|
<el-input v-model="adjustDialog.reason" maxlength="200" show-word-limit />
|
||||||
|
</el-form-item>
|
||||||
|
</el-form>
|
||||||
|
</el-tab-pane>
|
||||||
|
<el-tab-pane label="变更记录" name="records">
|
||||||
|
<el-table :data="changeRecords" border>
|
||||||
|
<el-table-column type="index" label="序号" width="70" align="center" />
|
||||||
|
<el-table-column prop="changeTime" label="变更日期" min-width="170" align="center" />
|
||||||
|
<el-table-column prop="operatorName" label="经办人" min-width="140" align="center" />
|
||||||
|
<el-table-column prop="changeType" label="变更类型" min-width="160" align="center" />
|
||||||
|
<el-table-column
|
||||||
|
prop="changeReason"
|
||||||
|
label="变更原因"
|
||||||
|
min-width="240"
|
||||||
|
align="center"
|
||||||
|
show-overflow-tooltip
|
||||||
/>
|
/>
|
||||||
</template>
|
<el-table-column label="状态" min-width="120" align="center">
|
||||||
</el-table-column>
|
<template #default>已生效</template>
|
||||||
<el-table-column label="里程(KM)" min-width="140" align="center">
|
</el-table-column>
|
||||||
<template #default="{ row }">
|
<el-table-column label="操作" width="120" align="center" fixed="right">
|
||||||
<el-input-number
|
<template #default="{ row }">
|
||||||
v-model="row.mileage"
|
<el-link type="primary" @click="openAdjustChangeRecord(row)">查看详情</el-link>
|
||||||
:min="0"
|
</template>
|
||||||
:precision="2"
|
</el-table-column>
|
||||||
:controls="false"
|
</el-table>
|
||||||
:disabled="adjustDialog.readonly"
|
<el-empty v-if="!adjustChangeRecords.length" description="暂无变更记录" :image-size="60" />
|
||||||
@change="recalculateAdjustRow(row, 'mileage')"
|
</el-tab-pane>
|
||||||
/>
|
</el-tabs>
|
||||||
</template>
|
|
||||||
</el-table-column>
|
|
||||||
<el-table-column label="运输单价" min-width="140" align="center">
|
|
||||||
<template #default="{ row }">
|
|
||||||
<el-input-number
|
|
||||||
v-model="row.unitPrice"
|
|
||||||
:min="0"
|
|
||||||
:precision="2"
|
|
||||||
:controls="false"
|
|
||||||
:disabled="adjustDialog.readonly"
|
|
||||||
@change="recalculateAdjustRow(row, 'unitPrice')"
|
|
||||||
/>
|
|
||||||
</template>
|
|
||||||
</el-table-column>
|
|
||||||
<el-table-column
|
|
||||||
v-for="feeItem in adjustFeeItemNames"
|
|
||||||
:key="feeItem"
|
|
||||||
:label="feeItem"
|
|
||||||
min-width="140"
|
|
||||||
align="center"
|
|
||||||
>
|
|
||||||
<template #default="{ row }">
|
|
||||||
<el-input-number
|
|
||||||
v-model="row.feeItems[feeItem]"
|
|
||||||
:min="0"
|
|
||||||
:precision="2"
|
|
||||||
:controls="false"
|
|
||||||
:disabled="adjustDialog.readonly"
|
|
||||||
@change="recalculateAdjustRow(row, feeItem)"
|
|
||||||
/>
|
|
||||||
</template>
|
|
||||||
</el-table-column>
|
|
||||||
<el-table-column label="结算金额(含税)" min-width="170" align="center">
|
|
||||||
<template #default="{ row }">
|
|
||||||
<el-input-number
|
|
||||||
v-model="row.settlementAmountTax"
|
|
||||||
:precision="2"
|
|
||||||
:controls="false"
|
|
||||||
:disabled="adjustDialog.readonly"
|
|
||||||
/>
|
|
||||||
</template>
|
|
||||||
</el-table-column>
|
|
||||||
<el-table-column label="备注" min-width="200" align="center">
|
|
||||||
<template #default="{ row }">
|
|
||||||
<el-input v-model="row.remark" maxlength="200" :disabled="adjustDialog.readonly" />
|
|
||||||
</template>
|
|
||||||
</el-table-column>
|
|
||||||
</el-table>
|
|
||||||
<el-form
|
|
||||||
v-if="!adjustDialog.readonly"
|
|
||||||
:model="adjustDialog"
|
|
||||||
label-position="right"
|
|
||||||
label-width="auto"
|
|
||||||
class="pre-settlement-editor__adjust-reason"
|
|
||||||
>
|
|
||||||
<el-form-item label="调整原因">
|
|
||||||
<el-input
|
|
||||||
v-model="adjustDialog.reason"
|
|
||||||
type="textarea"
|
|
||||||
:rows="2"
|
|
||||||
maxlength="200"
|
|
||||||
show-word-limit
|
|
||||||
placeholder="请输入"
|
|
||||||
/>
|
|
||||||
</el-form-item>
|
|
||||||
</el-form>
|
|
||||||
<template #footer>
|
<template #footer>
|
||||||
<el-button @click="adjustDialog.visible = false">取消</el-button>
|
<el-button @click="adjustDialog.visible = false">取消</el-button>
|
||||||
<el-button
|
<el-button
|
||||||
@@ -853,10 +822,35 @@
|
|||||||
</template>
|
</template>
|
||||||
</el-dialog>
|
</el-dialog>
|
||||||
|
|
||||||
<change-record-detail-dialog
|
<el-dialog
|
||||||
v-model="adjustChangeRecordVisible"
|
v-model="adjustChangeRecordVisible"
|
||||||
:rows="adjustChangeRecordDetailRows"
|
title="变更记录详情"
|
||||||
/>
|
append-to-body
|
||||||
|
destroy-on-close
|
||||||
|
width="1100px"
|
||||||
|
top="10px"
|
||||||
|
class="pre-settlement-change-record-detail-dialog"
|
||||||
|
>
|
||||||
|
<div v-if="adjustChangeRecord" class="pre-settlement-change-record-detail-meta">
|
||||||
|
<span>变更日期:{{ adjustChangeRecord.changeTime || '-' }}</span>
|
||||||
|
<span>经办人:{{ adjustChangeRecord.operatorName || '-' }}</span>
|
||||||
|
<span>变更类型:{{ adjustChangeRecord.changeType || '-' }}</span>
|
||||||
|
<span>状态:已生效</span>
|
||||||
|
</div>
|
||||||
|
<el-table :data="adjustChangeRecordDetailRows" border :show-overflow-tooltip="false">
|
||||||
|
<el-table-column prop="field" label="变更字段" min-width="220" />
|
||||||
|
<el-table-column prop="before" label="变更前" min-width="330" />
|
||||||
|
<el-table-column prop="after" label="变更后" min-width="420" />
|
||||||
|
</el-table>
|
||||||
|
<el-empty
|
||||||
|
v-if="!adjustChangeRecordDetailRows.length"
|
||||||
|
description="暂无变更内容"
|
||||||
|
:image-size="60"
|
||||||
|
/>
|
||||||
|
<template #footer>
|
||||||
|
<el-button type="primary" @click="adjustChangeRecordVisible = false">关闭</el-button>
|
||||||
|
</template>
|
||||||
|
</el-dialog>
|
||||||
|
|
||||||
<el-dialog
|
<el-dialog
|
||||||
v-model="billingRuleDialog.visible"
|
v-model="billingRuleDialog.visible"
|
||||||
@@ -928,8 +922,6 @@
|
|||||||
import { h } from 'vue';
|
import { h } from 'vue';
|
||||||
import { mapGetters } from 'vuex';
|
import { mapGetters } from 'vuex';
|
||||||
import { InfoFilled } from '@element-plus/icons-vue';
|
import { InfoFilled } from '@element-plus/icons-vue';
|
||||||
import ChangeRecordDetailDialog from '@/components/change-record-detail-dialog/main.vue';
|
|
||||||
import { getMkPublicDetail } from '@/api/mk-process';
|
|
||||||
import {
|
import {
|
||||||
adjustDetail,
|
adjustDetail,
|
||||||
getCandidateDetails,
|
getCandidateDetails,
|
||||||
@@ -942,7 +934,6 @@ import {
|
|||||||
submit,
|
submit,
|
||||||
} from '@/api/settlement/preSettlement';
|
} from '@/api/settlement/preSettlement';
|
||||||
import { calculateAdjustedFee, getFeeDetail } from '@/api/settlement/receivable-payable-detail';
|
import { calculateAdjustedFee, getFeeDetail } from '@/api/settlement/receivable-payable-detail';
|
||||||
import { getBfiExchangeRate } from '@/api/base/currency';
|
|
||||||
import { getDictionary } from '@/api/system/dictbiz';
|
import { getDictionary } from '@/api/system/dictbiz';
|
||||||
import {
|
import {
|
||||||
emptyPreSettlementForm,
|
emptyPreSettlementForm,
|
||||||
@@ -969,7 +960,7 @@ import * as XLSX from 'xlsx';
|
|||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'PreSettlementEditor',
|
name: 'PreSettlementEditor',
|
||||||
components: { InfoFilled, ChangeRecordDetailDialog },
|
components: { InfoFilled },
|
||||||
props: {
|
props: {
|
||||||
modelValue: {
|
modelValue: {
|
||||||
type: Boolean,
|
type: Boolean,
|
||||||
@@ -991,10 +982,6 @@ export default {
|
|||||||
type: Object,
|
type: Object,
|
||||||
default: null,
|
default: null,
|
||||||
},
|
},
|
||||||
publicMode: {
|
|
||||||
type: Boolean,
|
|
||||||
default: false,
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
emits: ['update:modelValue', 'success'],
|
emits: ['update:modelValue', 'success'],
|
||||||
data() {
|
data() {
|
||||||
@@ -1067,14 +1054,9 @@ export default {
|
|||||||
batchNo: '',
|
batchNo: '',
|
||||||
cargoName: '',
|
cargoName: '',
|
||||||
},
|
},
|
||||||
detailPage: {
|
|
||||||
current: 1,
|
|
||||||
size: 10,
|
|
||||||
},
|
|
||||||
advances: [],
|
advances: [],
|
||||||
changeRecords: [],
|
changeRecords: [],
|
||||||
attachments: [],
|
attachments: [],
|
||||||
attachmentTypeOptions: [],
|
|
||||||
selectedAttachmentRows: [],
|
selectedAttachmentRows: [],
|
||||||
candidateDialog: {
|
candidateDialog: {
|
||||||
visible: false,
|
visible: false,
|
||||||
@@ -1097,6 +1079,7 @@ export default {
|
|||||||
loading: false,
|
loading: false,
|
||||||
saving: false,
|
saving: false,
|
||||||
readonly: false,
|
readonly: false,
|
||||||
|
activeTab: 'adjust',
|
||||||
detailId: '',
|
detailId: '',
|
||||||
sourceDetailId: '',
|
sourceDetailId: '',
|
||||||
detailLineNo: '',
|
detailLineNo: '',
|
||||||
@@ -1223,13 +1206,6 @@ export default {
|
|||||||
})
|
})
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
pagedDetails() {
|
|
||||||
const list = this.filteredDetails;
|
|
||||||
const size = Number(this.detailPage.size) || 10;
|
|
||||||
const current = Math.max(Number(this.detailPage.current) || 1, 1);
|
|
||||||
const start = (current - 1) * size;
|
|
||||||
return list.slice(start, start + size);
|
|
||||||
},
|
|
||||||
adjustFeeItemNames() {
|
adjustFeeItemNames() {
|
||||||
const names = new Set();
|
const names = new Set();
|
||||||
this.adjustRows.forEach(row => {
|
this.adjustRows.forEach(row => {
|
||||||
@@ -1256,16 +1232,6 @@ export default {
|
|||||||
this.form.settlementAmount = Number(value || 0).toFixed(2);
|
this.form.settlementAmount = Number(value || 0).toFixed(2);
|
||||||
this.recalculateLocalAmount();
|
this.recalculateLocalAmount();
|
||||||
},
|
},
|
||||||
filteredDetails: {
|
|
||||||
handler(list) {
|
|
||||||
const size = Number(this.detailPage.size) || 10;
|
|
||||||
const maxPage = Math.max(1, Math.ceil((list?.length || 0) / size) || 1);
|
|
||||||
if (this.detailPage.current > maxPage) {
|
|
||||||
this.detailPage.current = maxPage;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
immediate: true,
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
hasPermission(code) {
|
hasPermission(code) {
|
||||||
@@ -1273,18 +1239,11 @@ export default {
|
|||||||
},
|
},
|
||||||
async initialize() {
|
async initialize() {
|
||||||
this.resetEditor();
|
this.resetEditor();
|
||||||
if (!this.publicMode) {
|
await this.loadFeeOptions();
|
||||||
await this.loadFeeOptions();
|
await this.loadFeeCategoryOptions();
|
||||||
await this.loadFeeCategoryOptions();
|
await this.loadTransportTypeOptions();
|
||||||
await this.loadTransportTypeOptions();
|
|
||||||
await this.loadAttachmentTypeOptions();
|
|
||||||
}
|
|
||||||
if (this.recordId) await this.loadDetail();
|
if (this.recordId) await this.loadDetail();
|
||||||
else if (this.initialData) {
|
else if (this.initialData) await this.applyInitialData();
|
||||||
await this.applyInitialData();
|
|
||||||
this.form.exchangeRateDate = this.$dayjs().format('YYYY-MM-DD');
|
|
||||||
await this.fetchBfiExchangeRate();
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
async applyInitialData() {
|
async applyInitialData() {
|
||||||
const rows = Array.isArray(this.initialData?.rows) ? this.initialData.rows : [];
|
const rows = Array.isArray(this.initialData?.rows) ? this.initialData.rows : [];
|
||||||
@@ -1322,7 +1281,6 @@ export default {
|
|||||||
localCurrency: first.localCurrency || 'RMB',
|
localCurrency: first.localCurrency || 'RMB',
|
||||||
});
|
});
|
||||||
this.details = rows.map(row => this.normalizeSourceDetail(row));
|
this.details = rows.map(row => this.normalizeSourceDetail(row));
|
||||||
this.detailPage.current = 1;
|
|
||||||
this.loading = true;
|
this.loading = true;
|
||||||
try {
|
try {
|
||||||
await this.ensureSourceFeeSnapshots();
|
await this.ensureSourceFeeSnapshots();
|
||||||
@@ -1384,7 +1342,6 @@ export default {
|
|||||||
cargoName: '',
|
cargoName: '',
|
||||||
};
|
};
|
||||||
this.appliedDetailQuery = { ...this.detailQuery };
|
this.appliedDetailQuery = { ...this.detailQuery };
|
||||||
this.detailPage = { current: 1, size: 10 };
|
|
||||||
this.advances = [];
|
this.advances = [];
|
||||||
this.changeRecords = [];
|
this.changeRecords = [];
|
||||||
this.attachments = [];
|
this.attachments = [];
|
||||||
@@ -1393,6 +1350,7 @@ export default {
|
|||||||
this.pendingAdjustments = {};
|
this.pendingAdjustments = {};
|
||||||
this.sourceFeeSnapshots = {};
|
this.sourceFeeSnapshots = {};
|
||||||
this.detailFeeCache = {};
|
this.detailFeeCache = {};
|
||||||
|
this.adjustDialog.activeTab = 'adjust';
|
||||||
this.adjustDialog.detailId = '';
|
this.adjustDialog.detailId = '';
|
||||||
this.adjustDialog.sourceDetailId = '';
|
this.adjustDialog.sourceDetailId = '';
|
||||||
this.adjustDialog.detailLineNo = '';
|
this.adjustDialog.detailLineNo = '';
|
||||||
@@ -1412,14 +1370,11 @@ export default {
|
|||||||
async loadDetail() {
|
async loadDetail() {
|
||||||
this.loading = true;
|
this.loading = true;
|
||||||
try {
|
try {
|
||||||
const response = this.publicMode
|
const { data } = await getDetail(this.form.id || this.recordId);
|
||||||
? await getMkPublicDetail('pre-settlement', this.form.id || this.recordId)
|
const detail = data?.data || {};
|
||||||
: await getDetail(this.form.id || this.recordId);
|
|
||||||
const detail = response.data?.data || {};
|
|
||||||
this.form = { ...emptyPreSettlementForm(), ...detail };
|
this.form = { ...emptyPreSettlementForm(), ...detail };
|
||||||
this.summaryFees = (detail.summaryFees || []).map(row => ({ ...row }));
|
this.summaryFees = (detail.summaryFees || []).map(row => ({ ...row }));
|
||||||
this.details = (detail.details || []).map(row => this.normalizeSourceDetail(row));
|
this.details = (detail.details || []).map(row => this.normalizeSourceDetail(row));
|
||||||
this.detailPage.current = 1;
|
|
||||||
this.advances = (detail.advances || []).map(row => ({
|
this.advances = (detail.advances || []).map(row => ({
|
||||||
...row,
|
...row,
|
||||||
createUserName: row.createUserName || detail.createUserName,
|
createUserName: row.createUserName || detail.createUserName,
|
||||||
@@ -1427,7 +1382,6 @@ export default {
|
|||||||
}));
|
}));
|
||||||
this.changeRecords = detail.changeRecords || [];
|
this.changeRecords = detail.changeRecords || [];
|
||||||
this.attachments = this.parseAttachments(detail.attachmentsJson);
|
this.attachments = this.parseAttachments(detail.attachmentsJson);
|
||||||
this.sortAttachments();
|
|
||||||
this.selectedAttachmentRows = [];
|
this.selectedAttachmentRows = [];
|
||||||
if (
|
if (
|
||||||
detail.contractId &&
|
detail.contractId &&
|
||||||
@@ -1445,15 +1399,8 @@ export default {
|
|||||||
partyB: detail.payeeName,
|
partyB: detail.payeeName,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
if (this.publicMode && detail.detailFees) {
|
// 预加载所有明细的费用明细数据,用于拼接货物名称和类型
|
||||||
Object.entries(detail.detailFees).forEach(([detailId, feeRows]) => {
|
await this.loadAllDetailFees();
|
||||||
if (Array.isArray(feeRows) && feeRows.length) {
|
|
||||||
this.detailFeeCache[detailId] = feeRows;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
await this.loadAllDetailFees();
|
|
||||||
}
|
|
||||||
} finally {
|
} finally {
|
||||||
this.loading = false;
|
this.loading = false;
|
||||||
}
|
}
|
||||||
@@ -1581,12 +1528,7 @@ export default {
|
|||||||
this.form.settlementType = contract.settlementType || 'payable';
|
this.form.settlementType = contract.settlementType || 'payable';
|
||||||
this.form.payerName = contract.partyA;
|
this.form.payerName = contract.partyA;
|
||||||
this.form.payeeName = contract.partyB;
|
this.form.payeeName = contract.partyB;
|
||||||
this.form.currency = contract.settlementCurrency || 'RMB';
|
|
||||||
this.summaryFees = [];
|
this.summaryFees = [];
|
||||||
if (this.form.currency !== 'RMB') {
|
|
||||||
this.form.exchangeRateDate = this.$dayjs().format('YYYY-MM-DD');
|
|
||||||
this.fetchBfiExchangeRate();
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
async saveDraft(shouldSubmit) {
|
async saveDraft(shouldSubmit) {
|
||||||
await this.$refs.formRef?.validate();
|
await this.$refs.formRef?.validate();
|
||||||
@@ -1624,7 +1566,6 @@ export default {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
this.$message.success('保存成功');
|
this.$message.success('保存成功');
|
||||||
this.visible = false;
|
|
||||||
this.$emit('success', this.form.id);
|
this.$emit('success', this.form.id);
|
||||||
} finally {
|
} finally {
|
||||||
this[stateKey] = false;
|
this[stateKey] = false;
|
||||||
@@ -1860,26 +1801,6 @@ export default {
|
|||||||
.toFixed(2)
|
.toFixed(2)
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
handleExchangeRateDateChange() {
|
|
||||||
this.fetchBfiExchangeRate();
|
|
||||||
this.recalculateLocalAmount();
|
|
||||||
},
|
|
||||||
async fetchBfiExchangeRate() {
|
|
||||||
if (!this.form.currency || this.form.currency === 'RMB') return;
|
|
||||||
if (!this.form.exchangeRateDate) return;
|
|
||||||
try {
|
|
||||||
const { data } = await getBfiExchangeRate(this.form.currency, this.form.exchangeRateDate);
|
|
||||||
const rateData = data?.data;
|
|
||||||
if (rateData?.excval != null) {
|
|
||||||
this.form.exchangeRate = Number(rateData.excval);
|
|
||||||
this.recalculateLocalAmount();
|
|
||||||
} else {
|
|
||||||
this.$message.warning(`未查询到 ${this.form.currency} 的BFI汇率数据`);
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
this.$message.warning('查询BFI汇率失败,请手动输入结算汇率');
|
|
||||||
}
|
|
||||||
},
|
|
||||||
recalculateLocalAmount() {
|
recalculateLocalAmount() {
|
||||||
const rate = this.form.currency === 'RMB' ? 1 : Number(this.form.exchangeRate || 0);
|
const rate = this.form.currency === 'RMB' ? 1 : Number(this.form.exchangeRate || 0);
|
||||||
this.form.localSettlementAmount = (Number(this.form.settlementAmount || 0) * rate).toFixed(2);
|
this.form.localSettlementAmount = (Number(this.form.settlementAmount || 0) * rate).toFixed(2);
|
||||||
@@ -1974,7 +1895,6 @@ export default {
|
|||||||
},
|
},
|
||||||
handleDetailQuery() {
|
handleDetailQuery() {
|
||||||
this.appliedDetailQuery = { ...this.detailQuery };
|
this.appliedDetailQuery = { ...this.detailQuery };
|
||||||
this.detailPage.current = 1;
|
|
||||||
},
|
},
|
||||||
resetDetailQuery() {
|
resetDetailQuery() {
|
||||||
this.detailQuery = {
|
this.detailQuery = {
|
||||||
@@ -1984,13 +1904,6 @@ export default {
|
|||||||
cargoName: '',
|
cargoName: '',
|
||||||
};
|
};
|
||||||
this.appliedDetailQuery = { ...this.detailQuery };
|
this.appliedDetailQuery = { ...this.detailQuery };
|
||||||
this.detailPage.current = 1;
|
|
||||||
},
|
|
||||||
handleDetailPageSizeChange() {
|
|
||||||
this.detailPage.current = 1;
|
|
||||||
},
|
|
||||||
detailIndexMethod(index) {
|
|
||||||
return (this.detailPage.current - 1) * this.detailPage.size + index + 1;
|
|
||||||
},
|
},
|
||||||
normalizeAdjustRow(item, feeItemNames = []) {
|
normalizeAdjustRow(item, feeItemNames = []) {
|
||||||
const rawFeeItems =
|
const rawFeeItems =
|
||||||
@@ -2113,6 +2026,7 @@ export default {
|
|||||||
this.adjustDialog.visible = true;
|
this.adjustDialog.visible = true;
|
||||||
this.adjustDialog.loading = true;
|
this.adjustDialog.loading = true;
|
||||||
this.adjustDialog.readonly = readonly;
|
this.adjustDialog.readonly = readonly;
|
||||||
|
this.adjustDialog.activeTab = 'adjust';
|
||||||
this.adjustDialog.detailId = detailRow.id;
|
this.adjustDialog.detailId = detailRow.id;
|
||||||
this.adjustDialog.detailLineNo = detailRow.lineNo;
|
this.adjustDialog.detailLineNo = detailRow.lineNo;
|
||||||
this.adjustDialog.sourceDetailId =
|
this.adjustDialog.sourceDetailId =
|
||||||
@@ -2128,11 +2042,6 @@ export default {
|
|||||||
this.adjustRows = this.cloneAdjustRows(pending.rows);
|
this.adjustRows = this.cloneAdjustRows(pending.rows);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (this.publicMode) {
|
|
||||||
const cachedFees = this.detailFeeCache[detailRow.id] || [];
|
|
||||||
this.adjustRows = cachedFees.map(item => this.normalizeAdjustRow(item));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (sourceDetailId) {
|
if (sourceDetailId) {
|
||||||
if (!this.sourceFeeSnapshots[sourceDetailId]) {
|
if (!this.sourceFeeSnapshots[sourceDetailId]) {
|
||||||
this.sourceFeeSnapshots[sourceDetailId] = await this.loadSourceFeeRows(sourceDetailId);
|
this.sourceFeeSnapshots[sourceDetailId] = await this.loadSourceFeeRows(sourceDetailId);
|
||||||
@@ -2140,11 +2049,6 @@ export default {
|
|||||||
this.adjustRows = this.cloneAdjustRows(this.sourceFeeSnapshots[sourceDetailId]);
|
this.adjustRows = this.cloneAdjustRows(this.sourceFeeSnapshots[sourceDetailId]);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const cachedFees = this.detailFeeCache[detailRow.id];
|
|
||||||
if (this.publicMode && Array.isArray(cachedFees)) {
|
|
||||||
this.adjustRows = cachedFees.map(item => this.normalizeAdjustRow(item));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const response = await getDetailFees(detailRow.id);
|
const response = await getDetailFees(detailRow.id);
|
||||||
const rows = response.data?.data || response.data || [];
|
const rows = response.data?.data || response.data || [];
|
||||||
this.adjustRows = rows.map(item => this.normalizeAdjustRow(item));
|
this.adjustRows = rows.map(item => this.normalizeAdjustRow(item));
|
||||||
@@ -2333,6 +2237,10 @@ export default {
|
|||||||
return String(name || '').includes('运费') || String(name || '').includes('运输费');
|
return String(name || '').includes('运费') || String(name || '').includes('运输费');
|
||||||
},
|
},
|
||||||
async saveAdjustment() {
|
async saveAdjustment() {
|
||||||
|
if (!this.adjustDialog.reason.trim()) {
|
||||||
|
this.$message.warning('请输入调整原因');
|
||||||
|
return;
|
||||||
|
}
|
||||||
if (this.adjustRows.some(row => row.calculating)) {
|
if (this.adjustRows.some(row => row.calculating)) {
|
||||||
this.$message.warning('费用正在重新计算,请稍候');
|
this.$message.warning('费用正在重新计算,请稍候');
|
||||||
return;
|
return;
|
||||||
@@ -2511,92 +2419,13 @@ export default {
|
|||||||
handleAttachmentChange(list) {
|
handleAttachmentChange(list) {
|
||||||
const uploadUserName = this.userInfo?.realName || this.userInfo?.userName || '';
|
const uploadUserName = this.userInfo?.realName || this.userInfo?.userName || '';
|
||||||
const uploadTime = this.$dayjs().format('YYYY-MM-DD HH:mm:ss');
|
const uploadTime = this.$dayjs().format('YYYY-MM-DD HH:mm:ss');
|
||||||
const existingRows = this.attachments || [];
|
this.attachments = (list || []).map(file => ({
|
||||||
this.attachments = (list || []).map(file => {
|
...file,
|
||||||
const fileUrl = this.attachmentUrl(file);
|
uploadUserName: file.uploadUserName || uploadUserName,
|
||||||
const existing = existingRows.find(item => {
|
uploadTime: file.uploadTime || uploadTime,
|
||||||
const sameUid = file.uid && item.uid && String(file.uid) === String(item.uid);
|
}));
|
||||||
const sameUrl = fileUrl && this.attachmentUrl(item) === fileUrl;
|
|
||||||
return sameUid || sameUrl;
|
|
||||||
});
|
|
||||||
const originalName = this.attachmentName(file);
|
|
||||||
return {
|
|
||||||
...file,
|
|
||||||
type: existing?.type || file.type || this.resolveAttachmentType(originalName),
|
|
||||||
description: existing?.description || file.description || '',
|
|
||||||
uploadUserName: existing?.uploadUserName || file.uploadUserName || uploadUserName,
|
|
||||||
uploadTime: existing?.uploadTime || file.uploadTime || uploadTime,
|
|
||||||
};
|
|
||||||
});
|
|
||||||
this.sortAttachments();
|
|
||||||
this.selectedAttachmentRows = [];
|
this.selectedAttachmentRows = [];
|
||||||
},
|
},
|
||||||
async loadAttachmentTypeOptions() {
|
|
||||||
try {
|
|
||||||
const response = await getDictionary({ code: 'pre_settlement_attachment_type' });
|
|
||||||
const data = response?.data?.data || [];
|
|
||||||
this.attachmentTypeOptions = data
|
|
||||||
.map(item => ({
|
|
||||||
label: item.dictValue,
|
|
||||||
value: item.dictValue,
|
|
||||||
}))
|
|
||||||
.filter(item => item.label && item.value);
|
|
||||||
} catch (error) {
|
|
||||||
this.attachmentTypeOptions = [];
|
|
||||||
}
|
|
||||||
this.sortAttachments();
|
|
||||||
},
|
|
||||||
resolveAttachmentType(fileName) {
|
|
||||||
const normalizedName = String(fileName || '').toLocaleLowerCase();
|
|
||||||
const matchedType = [...this.attachmentTypeOptions]
|
|
||||||
.filter(item => item?.label || item?.value)
|
|
||||||
.sort(
|
|
||||||
(a, b) =>
|
|
||||||
String(b.label || b.value || '').length - String(a.label || a.value || '').length
|
|
||||||
)
|
|
||||||
.find(item => {
|
|
||||||
const typeText = String(item.label || item.value || '').toLocaleLowerCase();
|
|
||||||
return typeText && normalizedName.includes(typeText);
|
|
||||||
});
|
|
||||||
if (matchedType?.value) return matchedType.value;
|
|
||||||
const otherType = this.attachmentTypeOptions.find(item => this.isOtherAttachmentType(item));
|
|
||||||
if (otherType?.value) return otherType.value;
|
|
||||||
return '';
|
|
||||||
},
|
|
||||||
isOtherAttachmentType(item) {
|
|
||||||
const typeText = String(item?.label || item?.value || '').trim();
|
|
||||||
return (
|
|
||||||
typeText === '其他' ||
|
|
||||||
typeText === '其它' ||
|
|
||||||
typeText === '其他附件' ||
|
|
||||||
typeText === '其它附件'
|
|
||||||
);
|
|
||||||
},
|
|
||||||
getAttachmentTypeOrder(type) {
|
|
||||||
const normalizedType = String(type || '').trim();
|
|
||||||
const index = this.attachmentTypeOptions.findIndex(
|
|
||||||
item =>
|
|
||||||
String(item.value || '').trim() === normalizedType ||
|
|
||||||
String(item.label || '').trim() === normalizedType
|
|
||||||
);
|
|
||||||
return index === -1 ? Number.MAX_SAFE_INTEGER : index;
|
|
||||||
},
|
|
||||||
sortAttachments() {
|
|
||||||
this.attachments = (this.attachments || [])
|
|
||||||
.map((file, index) => ({ file, index }))
|
|
||||||
.sort((a, b) => {
|
|
||||||
const orderDifference =
|
|
||||||
this.getAttachmentTypeOrder(a.file.type) - this.getAttachmentTypeOrder(b.file.type);
|
|
||||||
return orderDifference || a.index - b.index;
|
|
||||||
})
|
|
||||||
.map(item => item.file);
|
|
||||||
},
|
|
||||||
attachmentTypeName(type) {
|
|
||||||
const option = this.attachmentTypeOptions.find(
|
|
||||||
item => String(item.value) === String(type) || String(item.label) === String(type)
|
|
||||||
);
|
|
||||||
return option?.label || type || '-';
|
|
||||||
},
|
|
||||||
handleAttachmentSelectionChange(rows) {
|
handleAttachmentSelectionChange(rows) {
|
||||||
this.selectedAttachmentRows = rows || [];
|
this.selectedAttachmentRows = rows || [];
|
||||||
},
|
},
|
||||||
@@ -2640,25 +2469,13 @@ export default {
|
|||||||
},
|
},
|
||||||
parseAttachments(value) {
|
parseAttachments(value) {
|
||||||
if (!value) return [];
|
if (!value) return [];
|
||||||
let list = [];
|
if (Array.isArray(value)) return value;
|
||||||
if (Array.isArray(value)) {
|
try {
|
||||||
list = value;
|
const parsed = JSON.parse(value);
|
||||||
} else {
|
return Array.isArray(parsed) ? parsed : [];
|
||||||
try {
|
} catch (error) {
|
||||||
const parsed = JSON.parse(value);
|
return [];
|
||||||
list = Array.isArray(parsed) ? parsed : [];
|
|
||||||
} catch (error) {
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return list.map(file => {
|
|
||||||
const originalName = this.attachmentName(file);
|
|
||||||
return {
|
|
||||||
...file,
|
|
||||||
type: file?.type || this.resolveAttachmentType(originalName),
|
|
||||||
description: file?.description || '',
|
|
||||||
};
|
|
||||||
});
|
|
||||||
},
|
},
|
||||||
parseFeeItems(value) {
|
parseFeeItems(value) {
|
||||||
if (!value) return {};
|
if (!value) return {};
|
||||||
@@ -2695,11 +2512,6 @@ export default {
|
|||||||
if (!Number.isFinite(amount)) return '-';
|
if (!Number.isFinite(amount)) return '-';
|
||||||
return `${amount.toFixed(2)} ${currency || 'RMB'}`;
|
return `${amount.toFixed(2)} ${currency || 'RMB'}`;
|
||||||
},
|
},
|
||||||
formatCreateDate(value) {
|
|
||||||
if (value === undefined || value === null || value === '') return '-';
|
|
||||||
const parsed = this.$dayjs(value);
|
|
||||||
return parsed.isValid() ? parsed.format('YYYY-MM-DD') : this.displayValue(value);
|
|
||||||
},
|
|
||||||
formatQuantity(row) {
|
formatQuantity(row) {
|
||||||
const value = row.transportQuantity;
|
const value = row.transportQuantity;
|
||||||
if (value === undefined || value === null || value === '') return '-';
|
if (value === undefined || value === null || value === '') return '-';
|
||||||
@@ -2850,43 +2662,22 @@ export default {
|
|||||||
}
|
}
|
||||||
|
|
||||||
&__detail-filter {
|
&__detail-filter {
|
||||||
display: grid;
|
display: flex;
|
||||||
grid-template-columns: repeat(4, minmax(0, 1fr)) auto;
|
flex-wrap: wrap;
|
||||||
align-items: center;
|
gap: 0 24px;
|
||||||
gap: 8px 16px;
|
|
||||||
margin-bottom: 12px;
|
margin-bottom: 12px;
|
||||||
padding: 12px 12px 4px;
|
|
||||||
background: #fff;
|
|
||||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.04);
|
|
||||||
|
|
||||||
:deep(.el-form-item) {
|
:deep(.el-form-item) {
|
||||||
display: flex;
|
|
||||||
min-width: 0;
|
|
||||||
margin-right: 0;
|
|
||||||
margin-bottom: 0;
|
margin-bottom: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
:deep(.el-form-item__content) {
|
|
||||||
flex: 1;
|
|
||||||
min-width: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
:deep(.el-input) {
|
:deep(.el-input) {
|
||||||
width: 100%;
|
width: 220px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
&__detail-filter-actions {
|
&__detail-filter-actions {
|
||||||
width: auto;
|
margin-left: auto;
|
||||||
margin-left: 0;
|
|
||||||
white-space: nowrap;
|
|
||||||
justify-self: end;
|
|
||||||
|
|
||||||
:deep(.el-form-item__content) {
|
|
||||||
flex: none;
|
|
||||||
justify-content: flex-end;
|
|
||||||
flex-wrap: nowrap;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
&__contract-filter {
|
&__contract-filter {
|
||||||
@@ -2999,6 +2790,23 @@ export default {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.pre-settlement-change-record-detail-meta {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: 8px 32px;
|
||||||
|
margin-bottom: 16px;
|
||||||
|
color: #606266;
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.pre-settlement-change-record-detail-dialog .el-dialog__body) {
|
||||||
|
padding-top: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.pre-settlement-change-record-detail-dialog .el-table .cell) {
|
||||||
|
white-space: pre-wrap;
|
||||||
|
word-break: break-all;
|
||||||
|
}
|
||||||
|
|
||||||
:deep(.pre-settlement-editor .el-dialog__body) {
|
:deep(.pre-settlement-editor .el-dialog__body) {
|
||||||
padding: 12px 16px;
|
padding: 12px 16px;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -56,7 +56,6 @@ export default {
|
|||||||
methods: {
|
methods: {
|
||||||
goBack() {
|
goBack() {
|
||||||
removeSettlementTransfer(this.$route.query.transferToken);
|
removeSettlementTransfer(this.$route.query.transferToken);
|
||||||
this.$router.$avueRouter.closeTag();
|
|
||||||
this.$router.push('/settlement/formal-settlement');
|
this.$router.push('/settlement/formal-settlement');
|
||||||
},
|
},
|
||||||
syncTagTitle() {
|
syncTagTitle() {
|
||||||
|
|||||||
@@ -1,55 +0,0 @@
|
|||||||
<template>
|
|
||||||
<mk-public-shell biz-type="formal-settlement" :get-form="getForm">
|
|
||||||
<basic-container>
|
|
||||||
<div class="mk-public-form-title">查看正式结算</div>
|
|
||||||
<formal-settlement-editor
|
|
||||||
ref="page"
|
|
||||||
:model-value="true"
|
|
||||||
page-mode
|
|
||||||
readonly
|
|
||||||
public-mode
|
|
||||||
:record-id="recordId"
|
|
||||||
/>
|
|
||||||
</basic-container>
|
|
||||||
</mk-public-shell>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
import MkPublicShell from '@/views/mk/mk-public-shell.vue';
|
|
||||||
import FormalSettlementEditor from '@/views/settlement/components/formal-settlement-editor.vue';
|
|
||||||
|
|
||||||
export default {
|
|
||||||
name: 'FormalSettlementPublicView',
|
|
||||||
components: { MkPublicShell, FormalSettlementEditor },
|
|
||||||
computed: {
|
|
||||||
recordId() {
|
|
||||||
return this.$route.query.id || '';
|
|
||||||
},
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
getForm() {
|
|
||||||
const form = this.$refs.page?.form || {};
|
|
||||||
return { ...form, subject: form.formalSettlementNo || form.contractName || '' };
|
|
||||||
},
|
|
||||||
},
|
|
||||||
};
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
|
||||||
.mk-public-form-title {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
min-height: 24px;
|
|
||||||
margin-bottom: 16px;
|
|
||||||
font-size: 18px;
|
|
||||||
font-weight: 600;
|
|
||||||
|
|
||||||
&::before {
|
|
||||||
width: 4px;
|
|
||||||
height: 20px;
|
|
||||||
margin-right: 8px;
|
|
||||||
background: #409eff;
|
|
||||||
content: '';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
@@ -64,7 +64,6 @@
|
|||||||
:loading="loading"
|
:loading="loading"
|
||||||
:page="page"
|
:page="page"
|
||||||
:has-permission="hasPermissionFn"
|
:has-permission="hasPermissionFn"
|
||||||
:sync-loading="syncLoading"
|
|
||||||
@create="openCreate"
|
@create="openCreate"
|
||||||
@payment="openPaymentDialog"
|
@payment="openPaymentDialog"
|
||||||
@sync="handleSync"
|
@sync="handleSync"
|
||||||
@@ -83,7 +82,6 @@
|
|||||||
:loading="loading"
|
:loading="loading"
|
||||||
:page="page"
|
:page="page"
|
||||||
:has-permission="hasPermissionFn"
|
:has-permission="hasPermissionFn"
|
||||||
:sync-loading="syncLoading"
|
|
||||||
@create="openCreate"
|
@create="openCreate"
|
||||||
@payment="openPaymentDialog"
|
@payment="openPaymentDialog"
|
||||||
@sync="handleSync"
|
@sync="handleSync"
|
||||||
@@ -217,7 +215,6 @@ export default {
|
|||||||
rows: [],
|
rows: [],
|
||||||
selection: [],
|
selection: [],
|
||||||
loading: false,
|
loading: false,
|
||||||
syncLoading: false,
|
|
||||||
page: { current: 1, size: 10, total: 0 },
|
page: { current: 1, size: 10, total: 0 },
|
||||||
editor: { visible: false, id: null, readonly: false },
|
editor: { visible: false, id: null, readonly: false },
|
||||||
paymentDialog: {
|
paymentDialog: {
|
||||||
@@ -384,28 +381,12 @@ export default {
|
|||||||
return this.selection[0];
|
return this.selection[0];
|
||||||
},
|
},
|
||||||
async handleSync() {
|
async handleSync() {
|
||||||
if (this.syncLoading) return;
|
|
||||||
const row = this.selectedOne('同步金蝶');
|
const row = this.selectedOne('同步金蝶');
|
||||||
if (!row) return;
|
if (!row) return;
|
||||||
|
const response = await api.syncKingdee(row.id);
|
||||||
this.syncLoading = true;
|
const data = this.unwrapData(response);
|
||||||
try {
|
this.$message.success(`同步成功,金蝶单据号:${data}`);
|
||||||
const response = await api.syncKingdee(row.id);
|
this.loadTable();
|
||||||
const data = this.unwrapData(response);
|
|
||||||
this.$message.success(`同步成功,金蝶单据号:${data}`);
|
|
||||||
await this.loadTable();
|
|
||||||
// 附件联带同步提示:同步成功但附件未全部上传时追加 warning,重推即自动补传
|
|
||||||
const freshRow = (this.rows || []).find(item => String(item.id) === String(row.id));
|
|
||||||
if (freshRow && ['pending', 'partial'].includes(freshRow.attachmentSyncStatus)) {
|
|
||||||
this.$message({
|
|
||||||
type: 'warning',
|
|
||||||
message: '该单附件未全部上传,可重推补传',
|
|
||||||
duration: 8000,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
} finally {
|
|
||||||
this.syncLoading = false;
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
async openPaymentDialog() {
|
async openPaymentDialog() {
|
||||||
if (!this.selection.length) return this.$message.warning('请至少选择一条正式结算单');
|
if (!this.selection.length) return this.$message.warning('请至少选择一条正式结算单');
|
||||||
|
|||||||
@@ -50,7 +50,6 @@ export default {
|
|||||||
},
|
},
|
||||||
goBack() {
|
goBack() {
|
||||||
removeSettlementTransfer(this.$route.query.transferToken);
|
removeSettlementTransfer(this.$route.query.transferToken);
|
||||||
this.$router.$avueRouter.closeTag();
|
|
||||||
this.$router.push('/settlement/pre-settlement');
|
this.$router.push('/settlement/pre-settlement');
|
||||||
},
|
},
|
||||||
syncTagTitle() {
|
syncTagTitle() {
|
||||||
|
|||||||
@@ -1,55 +0,0 @@
|
|||||||
<template>
|
|
||||||
<mk-public-shell biz-type="pre-settlement" :get-form="getForm">
|
|
||||||
<basic-container>
|
|
||||||
<div class="mk-public-form-title">查看预结算</div>
|
|
||||||
<pre-settlement-editor
|
|
||||||
ref="page"
|
|
||||||
:model-value="true"
|
|
||||||
page-mode
|
|
||||||
readonly
|
|
||||||
public-mode
|
|
||||||
:record-id="recordId"
|
|
||||||
/>
|
|
||||||
</basic-container>
|
|
||||||
</mk-public-shell>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
import MkPublicShell from '@/views/mk/mk-public-shell.vue';
|
|
||||||
import PreSettlementEditor from '@/views/settlement/components/pre-settlement-editor.vue';
|
|
||||||
|
|
||||||
export default {
|
|
||||||
name: 'PreSettlementPublicView',
|
|
||||||
components: { MkPublicShell, PreSettlementEditor },
|
|
||||||
computed: {
|
|
||||||
recordId() {
|
|
||||||
return this.$route.query.id || '';
|
|
||||||
},
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
getForm() {
|
|
||||||
const form = this.$refs.page?.form || {};
|
|
||||||
return { ...form, subject: form.preSettlementNo || form.contractName || '' };
|
|
||||||
},
|
|
||||||
},
|
|
||||||
};
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
|
||||||
.mk-public-form-title {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
min-height: 24px;
|
|
||||||
margin-bottom: 16px;
|
|
||||||
font-size: 18px;
|
|
||||||
font-weight: 600;
|
|
||||||
|
|
||||||
&::before {
|
|
||||||
width: 4px;
|
|
||||||
height: 20px;
|
|
||||||
margin-right: 8px;
|
|
||||||
background: #409eff;
|
|
||||||
content: '';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
@@ -497,7 +497,6 @@ export default {
|
|||||||
path: '/payment/payment-application/form',
|
path: '/payment/payment-application/form',
|
||||||
query: {
|
query: {
|
||||||
mode: 'add',
|
mode: 'add',
|
||||||
paymentType: 'progress_advance',
|
|
||||||
transferToken,
|
transferToken,
|
||||||
preSettlementIds: sourcePreSettlements
|
preSettlementIds: sourcePreSettlements
|
||||||
.map(row => row.preSettlementId)
|
.map(row => row.preSettlementId)
|
||||||
|
|||||||
@@ -183,7 +183,6 @@
|
|||||||
title="变更记录详情"
|
title="变更记录详情"
|
||||||
width="88%"
|
width="88%"
|
||||||
append-to-body
|
append-to-body
|
||||||
align-center
|
|
||||||
>
|
>
|
||||||
<el-table v-loading="changeRecordsDialog.loading" :data="changeRows" border>
|
<el-table v-loading="changeRecordsDialog.loading" :data="changeRows" border>
|
||||||
<el-table-column type="index" label="序号" width="64" align="center" />
|
<el-table-column type="index" label="序号" width="64" align="center" />
|
||||||
@@ -1154,16 +1153,28 @@ export default {
|
|||||||
minWidth: 130,
|
minWidth: 130,
|
||||||
align: 'right',
|
align: 'right',
|
||||||
};
|
};
|
||||||
const dynamicColumns = [
|
const dynamicColumns = this.isPayable
|
||||||
{
|
? [
|
||||||
label: '运输费',
|
{
|
||||||
prop: 'tableFreightAmount',
|
label: '运输费',
|
||||||
feeSummaryType: 'freight',
|
prop: 'tableFreightAmount',
|
||||||
minWidth: 130,
|
feeSummaryType: 'freight',
|
||||||
align: 'right',
|
minWidth: 130,
|
||||||
},
|
align: 'right',
|
||||||
otherFeeColumn,
|
},
|
||||||
];
|
otherFeeColumn,
|
||||||
|
]
|
||||||
|
: [
|
||||||
|
...this.tableFeeItemNames.map((name, index) => ({
|
||||||
|
label: name,
|
||||||
|
prop: `tableFeeItem${index}`,
|
||||||
|
feeItemName: name,
|
||||||
|
dynamic: true,
|
||||||
|
minWidth: 130,
|
||||||
|
align: 'right',
|
||||||
|
})),
|
||||||
|
otherFeeColumn,
|
||||||
|
];
|
||||||
if (totalIndex < 0) return [...columns, ...dynamicColumns];
|
if (totalIndex < 0) return [...columns, ...dynamicColumns];
|
||||||
columns.splice(totalIndex, 0, ...dynamicColumns);
|
columns.splice(totalIndex, 0, ...dynamicColumns);
|
||||||
return columns;
|
return columns;
|
||||||
@@ -1198,24 +1209,10 @@ export default {
|
|||||||
await this.loadTable();
|
await this.loadTable();
|
||||||
await this.openRouteDetail();
|
await this.openRouteDetail();
|
||||||
},
|
},
|
||||||
// 标签切走(keep-alive 缓存)或销毁时,收起 teleport/append-to-body 弹层,避免残留盖住其它页面
|
|
||||||
deactivated() {
|
|
||||||
this.closeInnerDialogs();
|
|
||||||
},
|
|
||||||
beforeUnmount() {
|
beforeUnmount() {
|
||||||
this.closeInnerDialogs();
|
this.clearAdjustCalculations();
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
closeInnerDialogs() {
|
|
||||||
this.closeDetailPanel();
|
|
||||||
this.closeAdjustPanel();
|
|
||||||
this.changeRecordsDialog.visible = false;
|
|
||||||
this.updateFeeDialog.visible = false;
|
|
||||||
this.transferDialog.visible = false;
|
|
||||||
this.generateDialog.visible = false;
|
|
||||||
this.previewDialog.visible = false;
|
|
||||||
this.generateContractDialog.visible = false;
|
|
||||||
},
|
|
||||||
contractCategoryName(value) {
|
contractCategoryName(value) {
|
||||||
return (
|
return (
|
||||||
this.contractCategoryOptions.find(item => String(item.value) === String(value))?.label ||
|
this.contractCategoryOptions.find(item => String(item.value) === String(value))?.label ||
|
||||||
@@ -1501,7 +1498,7 @@ export default {
|
|||||||
const res = await api.getList(this.page.current, this.page.size, params);
|
const res = await api.getList(this.page.current, this.page.size, params);
|
||||||
const data = this.unwrapPage(res);
|
const data = this.unwrapPage(res);
|
||||||
this.rows = (data.records || []).map(this.decorateRow);
|
this.rows = (data.records || []).map(this.decorateRow);
|
||||||
this.tableFeeItemNames = [];
|
this.tableFeeItemNames = this.isPayable ? [] : this.collectFeeItemNames(this.rows);
|
||||||
this.page.total = data.total || 0;
|
this.page.total = data.total || 0;
|
||||||
} finally {
|
} finally {
|
||||||
this.loading = false;
|
this.loading = false;
|
||||||
@@ -1741,7 +1738,7 @@ export default {
|
|||||||
});
|
});
|
||||||
const adjusted = {
|
const adjusted = {
|
||||||
...item,
|
...item,
|
||||||
transportQuantity: this.normalizeAdjustTransportQuantity(item),
|
transportQuantity: Number(item.transportQuantity || 0),
|
||||||
mileage:
|
mileage:
|
||||||
item.mileage === null || item.mileage === undefined || Number(item.mileage) === -1
|
item.mileage === null || item.mileage === undefined || Number(item.mileage) === -1
|
||||||
? null
|
? null
|
||||||
@@ -1770,21 +1767,6 @@ export default {
|
|||||||
feeSourceLabel(value) {
|
feeSourceLabel(value) {
|
||||||
return ['手动录入', '手动添加', '手工录入'].includes(value) ? '手动录入' : '自动生成';
|
return ['手动录入', '手动添加', '手工录入'].includes(value) ? '手动录入' : '自动生成';
|
||||||
},
|
},
|
||||||
normalizeAdjustTransportQuantity(item = {}) {
|
|
||||||
const value = item.transportQuantity;
|
|
||||||
if (value === undefined || value === null || value === '' || Number(value) === -1) {
|
|
||||||
return '';
|
|
||||||
}
|
|
||||||
const element = item.billingFactor;
|
|
||||||
// 按车辆/固定金额生成时运输量会被写成占位 1,调整弹窗无实际运输量时不展示
|
|
||||||
if (
|
|
||||||
(element === '按车辆' || element === '固定金额(整单一口价)') &&
|
|
||||||
Number(value) === 1
|
|
||||||
) {
|
|
||||||
return '';
|
|
||||||
}
|
|
||||||
return Number(value);
|
|
||||||
},
|
|
||||||
adjustBillingTypes(row) {
|
adjustBillingTypes(row) {
|
||||||
return ADJUST_BILLING_TYPE_MAP[row.billingFactor] || [];
|
return ADJUST_BILLING_TYPE_MAP[row.billingFactor] || [];
|
||||||
},
|
},
|
||||||
@@ -1912,12 +1894,7 @@ export default {
|
|||||||
model: row.model,
|
model: row.model,
|
||||||
billingFactor: row.billingFactor,
|
billingFactor: row.billingFactor,
|
||||||
billingType: row.billingType,
|
billingType: row.billingType,
|
||||||
transportQuantity:
|
transportQuantity: row.transportQuantity,
|
||||||
row.transportQuantity === '' ||
|
|
||||||
row.transportQuantity === null ||
|
|
||||||
row.transportQuantity === undefined
|
|
||||||
? null
|
|
||||||
: Number(row.transportQuantity),
|
|
||||||
priceUnit: row.priceUnit,
|
priceUnit: row.priceUnit,
|
||||||
unitPrice: row.unitPrice,
|
unitPrice: row.unitPrice,
|
||||||
mileage: row.mileage,
|
mileage: row.mileage,
|
||||||
|
|||||||
+18
-46
@@ -143,8 +143,7 @@
|
|||||||
:status="oaSyncProgressStatus"
|
:status="oaSyncProgressStatus"
|
||||||
:stroke-width="12"
|
:stroke-width="12"
|
||||||
/>
|
/>
|
||||||
<div class="oa-sync-meta" v-if="oaSyncStage === 'clear'">当前阶段:{{ oaSyncStageLabel }}</div>
|
<div class="oa-sync-meta">
|
||||||
<div class="oa-sync-meta" v-else>
|
|
||||||
当前阶段:{{ oaSyncStageLabel }},第 {{ oaSyncProgress.current || 0 }} / {{ oaSyncTotalPage }} 页
|
当前阶段:{{ oaSyncStageLabel }},第 {{ oaSyncProgress.current || 0 }} / {{ oaSyncTotalPage }} 页
|
||||||
</div>
|
</div>
|
||||||
<div class="oa-sync-stats">
|
<div class="oa-sync-stats">
|
||||||
@@ -178,7 +177,6 @@ import {
|
|||||||
getDeptTree,
|
getDeptTree,
|
||||||
syncOaCompany,
|
syncOaCompany,
|
||||||
syncOaDepartment,
|
syncOaDepartment,
|
||||||
clearNonTopDept,
|
|
||||||
} from '@/api/system/dept';
|
} from '@/api/system/dept';
|
||||||
import { getLeaderList } from '@/api/system/user';
|
import { getLeaderList } from '@/api/system/user';
|
||||||
import { getList as getCustomerArchiveList } from '@/api/vehicle/customer-archive';
|
import { getList as getCustomerArchiveList } from '@/api/vehicle/customer-archive';
|
||||||
@@ -217,7 +215,6 @@ export default {
|
|||||||
oaSyncCancelled: false,
|
oaSyncCancelled: false,
|
||||||
oaSyncStatus: 'running',
|
oaSyncStatus: 'running',
|
||||||
oaSyncStage: 'company',
|
oaSyncStage: 'company',
|
||||||
oaSyncClearNonTop: false,
|
|
||||||
oaSyncController: null,
|
oaSyncController: null,
|
||||||
oaSyncProgress: {
|
oaSyncProgress: {
|
||||||
current: 0,
|
current: 0,
|
||||||
@@ -290,9 +287,8 @@ export default {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const parentId = this.normalizeParentId(this.form?.parentId);
|
const parentId = this.normalizeParentId(this.form?.parentId);
|
||||||
// 顶级组织不选择上级时,编码不依赖上级编码
|
|
||||||
if (!parentId) {
|
if (!parentId) {
|
||||||
callback();
|
callback(new Error('请先选择上级组织'));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (!this.parentDept || String(this.parentDept.id) !== String(parentId)) {
|
if (!this.parentDept || String(this.parentDept.id) !== String(parentId)) {
|
||||||
@@ -433,6 +429,13 @@ export default {
|
|||||||
props: {
|
props: {
|
||||||
label: 'title',
|
label: 'title',
|
||||||
},
|
},
|
||||||
|
rules: [
|
||||||
|
{
|
||||||
|
required: true,
|
||||||
|
message: '请选择上级组织',
|
||||||
|
trigger: 'click',
|
||||||
|
},
|
||||||
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: '所属租户',
|
label: '所属租户',
|
||||||
@@ -517,9 +520,6 @@ export default {
|
|||||||
return '自动同步组织';
|
return '自动同步组织';
|
||||||
},
|
},
|
||||||
oaSyncStageLabel() {
|
oaSyncStageLabel() {
|
||||||
if (this.oaSyncStage === 'clear') {
|
|
||||||
return '清除非顶级组织';
|
|
||||||
}
|
|
||||||
return this.oaSyncStage === 'department' ? '同步部门' : '同步公司';
|
return this.oaSyncStage === 'department' ? '同步部门' : '同步公司';
|
||||||
},
|
},
|
||||||
oaSyncTotalPage() {
|
oaSyncTotalPage() {
|
||||||
@@ -555,34 +555,21 @@ export default {
|
|||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
handleOaOrgSync() {
|
handleOaOrgSync() {
|
||||||
this.$confirm(
|
this.$confirm('确定从OA先同步公司、再同步部门?', '提示', {
|
||||||
'是否清除非顶级组织?选择“是”将先删除顶级以外的组织后再同步;选择“否”则直接同步。',
|
confirmButtonText: '确定',
|
||||||
'提示',
|
cancelButtonText: '取消',
|
||||||
{
|
type: 'warning',
|
||||||
confirmButtonText: '是',
|
}).then(() => {
|
||||||
cancelButtonText: '否',
|
this.startOaOrgSync();
|
||||||
distinguishCancelAndClose: true,
|
});
|
||||||
closeOnClickModal: false,
|
|
||||||
type: 'warning',
|
|
||||||
}
|
|
||||||
)
|
|
||||||
.then(() => {
|
|
||||||
this.startOaOrgSync(true);
|
|
||||||
})
|
|
||||||
.catch(action => {
|
|
||||||
if (action === 'cancel') {
|
|
||||||
this.startOaOrgSync(false);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
},
|
},
|
||||||
startOaOrgSync(clearNonTop) {
|
startOaOrgSync() {
|
||||||
this.oaSyncLoading = true;
|
this.oaSyncLoading = true;
|
||||||
this.oaSyncVisible = true;
|
this.oaSyncVisible = true;
|
||||||
this.oaSyncRunning = true;
|
this.oaSyncRunning = true;
|
||||||
this.oaSyncCancelled = false;
|
this.oaSyncCancelled = false;
|
||||||
this.oaSyncStatus = 'running';
|
this.oaSyncStatus = 'running';
|
||||||
this.oaSyncClearNonTop = !!clearNonTop;
|
this.oaSyncStage = 'company';
|
||||||
this.oaSyncStage = clearNonTop ? 'clear' : 'company';
|
|
||||||
this.oaSyncController = new AbortController();
|
this.oaSyncController = new AbortController();
|
||||||
this.oaSyncProgress = {
|
this.oaSyncProgress = {
|
||||||
current: 0,
|
current: 0,
|
||||||
@@ -620,14 +607,6 @@ export default {
|
|||||||
},
|
},
|
||||||
async runOaOrgSyncPages() {
|
async runOaOrgSyncPages() {
|
||||||
try {
|
try {
|
||||||
if (this.oaSyncClearNonTop) {
|
|
||||||
this.oaSyncStage = 'clear';
|
|
||||||
await clearNonTopDept(this.oaSyncController?.signal);
|
|
||||||
if (this.oaSyncCancelled) {
|
|
||||||
this.oaSyncStatus = 'cancelled';
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
this.oaSyncStage = 'company';
|
this.oaSyncStage = 'company';
|
||||||
await this.runOaOrgSyncStage(syncOaCompany);
|
await this.runOaOrgSyncStage(syncOaCompany);
|
||||||
if (this.oaSyncCancelled) {
|
if (this.oaSyncCancelled) {
|
||||||
@@ -842,11 +821,6 @@ export default {
|
|||||||
.filter(Boolean);
|
.filter(Boolean);
|
||||||
return result.length ? result.join('、') : '-';
|
return result.length ? result.join('、') : '-';
|
||||||
},
|
},
|
||||||
normalizeTopParent(row) {
|
|
||||||
if (!this.normalizeParentId(row?.parentId)) {
|
|
||||||
row.parentId = 0;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
isRootDept(row) {
|
isRootDept(row) {
|
||||||
return row && (row.parentId === 0 || String(row.parentId) === '0');
|
return row && (row.parentId === 0 || String(row.parentId) === '0');
|
||||||
},
|
},
|
||||||
@@ -862,7 +836,6 @@ export default {
|
|||||||
row.tenantId = row.tenantId || this.userInfo.tenantId || website.tenantId;
|
row.tenantId = row.tenantId || this.userInfo.tenantId || website.tenantId;
|
||||||
row.deptCode = String(row.deptCode || '').trim();
|
row.deptCode = String(row.deptCode || '').trim();
|
||||||
row.leaderId = func.join(row.leaderId);
|
row.leaderId = func.join(row.leaderId);
|
||||||
this.normalizeTopParent(row);
|
|
||||||
if (Number(row.deptCategory) === 6 && !row.carrierCustomerId) {
|
if (Number(row.deptCategory) === 6 && !row.carrierCustomerId) {
|
||||||
this.$message.warning('请选择承运商');
|
this.$message.warning('请选择承运商');
|
||||||
loading();
|
loading();
|
||||||
@@ -892,7 +865,6 @@ export default {
|
|||||||
row.tenantId = row.tenantId || this.userInfo.tenantId || website.tenantId;
|
row.tenantId = row.tenantId || this.userInfo.tenantId || website.tenantId;
|
||||||
row.deptCode = String(row.deptCode || '').trim();
|
row.deptCode = String(row.deptCode || '').trim();
|
||||||
row.leaderId = func.join(row.leaderId);
|
row.leaderId = func.join(row.leaderId);
|
||||||
this.normalizeTopParent(row);
|
|
||||||
if (Number(row.deptCategory) === 6 && !row.carrierCustomerId) {
|
if (Number(row.deptCategory) === 6 && !row.carrierCustomerId) {
|
||||||
this.$message.warning('请选择承运商');
|
this.$message.warning('请选择承运商');
|
||||||
loading();
|
loading();
|
||||||
|
|||||||
@@ -205,7 +205,7 @@
|
|||||||
type="password"
|
type="password"
|
||||||
show-password
|
show-password
|
||||||
autocomplete="new-password"
|
autocomplete="new-password"
|
||||||
placeholder="大于8位,含字母、数字、特殊字符.!@#$%^&*"
|
placeholder="请输入登录密码"
|
||||||
/>
|
/>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
</el-col>
|
</el-col>
|
||||||
@@ -332,12 +332,7 @@
|
|||||||
</el-col>
|
</el-col>
|
||||||
<el-col :span="24">
|
<el-col :span="24">
|
||||||
<el-form-item label="新密码" prop="password">
|
<el-form-item label="新密码" prop="password">
|
||||||
<el-input
|
<el-input v-model="passwordForm.password" type="password" show-password />
|
||||||
v-model="passwordForm.password"
|
|
||||||
type="password"
|
|
||||||
show-password
|
|
||||||
placeholder="大于8位,含字母、数字、特殊字符.!@#$%^&*"
|
|
||||||
/>
|
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
</el-col>
|
</el-col>
|
||||||
<el-col :span="24">
|
<el-col :span="24">
|
||||||
@@ -429,7 +424,6 @@ import { mapGetters } from 'vuex';
|
|||||||
import { h } from 'vue';
|
import { h } from 'vue';
|
||||||
import { getToken } from '@/utils/auth';
|
import { getToken } from '@/utils/auth';
|
||||||
import { downloadXls } from '@/utils/util';
|
import { downloadXls } from '@/utils/util';
|
||||||
import { validateLoginPassword } from '@/utils/validate';
|
|
||||||
import NProgress from 'nprogress';
|
import NProgress from 'nprogress';
|
||||||
import 'nprogress/nprogress.css';
|
import 'nprogress/nprogress.css';
|
||||||
import func from '@/utils/func';
|
import func from '@/utils/func';
|
||||||
@@ -493,7 +487,8 @@ export default {
|
|||||||
passwordForm: {},
|
passwordForm: {},
|
||||||
passwordRules: {
|
passwordRules: {
|
||||||
password: [
|
password: [
|
||||||
{ required: true, validator: validateLoginPassword, trigger: 'blur' },
|
{ required: true, message: '请输入新密码', trigger: 'blur' },
|
||||||
|
{ min: 6, max: 32, message: '密码长度在6到32个字符', trigger: 'blur' },
|
||||||
],
|
],
|
||||||
password2: [
|
password2: [
|
||||||
{ required: true, message: '请再次输入新密码', trigger: 'blur' },
|
{ required: true, message: '请再次输入新密码', trigger: 'blur' },
|
||||||
@@ -502,7 +497,6 @@ export default {
|
|||||||
},
|
},
|
||||||
userRules: {
|
userRules: {
|
||||||
account: [{ required: true, message: '请输入账号名', trigger: 'blur' }],
|
account: [{ required: true, message: '请输入账号名', trigger: 'blur' }],
|
||||||
password: [{ required: true, validator: validateLoginPassword, trigger: 'blur' }],
|
|
||||||
realName: [{ required: true, message: '请输入姓名', trigger: 'blur' }],
|
realName: [{ required: true, message: '请输入姓名', trigger: 'blur' }],
|
||||||
phone: [{ required: true, message: '请输入手机号', trigger: 'blur' }],
|
phone: [{ required: true, message: '请输入手机号', trigger: 'blur' }],
|
||||||
deptId: [{ required: true, message: '请选择所属组织', trigger: 'change' }],
|
deptId: [{ required: true, message: '请选择所属组织', trigger: 'change' }],
|
||||||
|
|||||||
@@ -91,7 +91,7 @@
|
|||||||
<el-input
|
<el-input
|
||||||
v-model="passwordForm.newPassword"
|
v-model="passwordForm.newPassword"
|
||||||
type="password"
|
type="password"
|
||||||
placeholder="大于8位,含字母、数字、特殊字符.!@#$%^&*"
|
placeholder="请输入新密码"
|
||||||
show-password
|
show-password
|
||||||
>
|
>
|
||||||
<template #prefix>
|
<template #prefix>
|
||||||
@@ -143,7 +143,6 @@
|
|||||||
import { getUserInfo, updateInfo, updatePassword } from '@/api/system/user';
|
import { getUserInfo, updateInfo, updatePassword } from '@/api/system/user';
|
||||||
import md5 from 'js-md5';
|
import md5 from 'js-md5';
|
||||||
import { sensitive } from '@/utils/sensitive';
|
import { sensitive } from '@/utils/sensitive';
|
||||||
import { isValidLoginPassword, LOGIN_PASSWORD_RULE_MESSAGE } from '@/utils/validate';
|
|
||||||
import {
|
import {
|
||||||
Plus,
|
Plus,
|
||||||
Bell,
|
Bell,
|
||||||
@@ -260,18 +259,6 @@ export default {
|
|||||||
},
|
},
|
||||||
// 提交密码修改
|
// 提交密码修改
|
||||||
submitPassword() {
|
submitPassword() {
|
||||||
if (!this.passwordForm.newPassword || !this.passwordForm.newPassword1) {
|
|
||||||
this.$message.warning('请输入新密码和确认密码');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (this.passwordForm.newPassword !== this.passwordForm.newPassword1) {
|
|
||||||
this.$message.warning('两次输入的新密码不一致');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (!isValidLoginPassword(this.passwordForm.newPassword)) {
|
|
||||||
this.$message.warning(LOGIN_PASSWORD_RULE_MESSAGE);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
this.loading = true;
|
this.loading = true;
|
||||||
updatePassword(
|
updatePassword(
|
||||||
md5(this.passwordForm.oldPassword),
|
md5(this.passwordForm.oldPassword),
|
||||||
|
|||||||
@@ -129,7 +129,7 @@
|
|||||||
>
|
>
|
||||||
<el-form
|
<el-form
|
||||||
label-position="right"
|
label-position="right"
|
||||||
label-width="calc(4em + 12px)"
|
label-width="calc(7em + 24px)"
|
||||||
class="certification-audit archive-form"
|
class="certification-audit archive-form"
|
||||||
>
|
>
|
||||||
<section-card title="基础信息">
|
<section-card title="基础信息">
|
||||||
@@ -145,25 +145,19 @@
|
|||||||
</el-form-item>
|
</el-form-item>
|
||||||
</el-col>
|
</el-col>
|
||||||
<el-col :span="12">
|
<el-col :span="12">
|
||||||
<el-form-item label="外廓尺寸">
|
<el-form-item label="外廓尺寸(毫米)">
|
||||||
<div class="dimension-group">
|
<div class="dimension-group">
|
||||||
<div class="dimension-group__item">
|
<div class="dimension-group__item">
|
||||||
<span class="dimension-group__label">长</span>
|
<span class="dimension-group__label">长</span>
|
||||||
<span class="certification-audit__value">
|
<span class="certification-audit__value">{{ auditValue('outerLength') }}</span>
|
||||||
{{ auditValueWithUnit('outerLength', 'mm') }}
|
|
||||||
</span>
|
|
||||||
</div>
|
</div>
|
||||||
<div class="dimension-group__item">
|
<div class="dimension-group__item">
|
||||||
<span class="dimension-group__label">宽</span>
|
<span class="dimension-group__label">宽</span>
|
||||||
<span class="certification-audit__value">
|
<span class="certification-audit__value">{{ auditValue('outerWidth') }}</span>
|
||||||
{{ auditValueWithUnit('outerWidth', 'mm') }}
|
|
||||||
</span>
|
|
||||||
</div>
|
</div>
|
||||||
<div class="dimension-group__item">
|
<div class="dimension-group__item">
|
||||||
<span class="dimension-group__label">高</span>
|
<span class="dimension-group__label">高</span>
|
||||||
<span class="certification-audit__value">
|
<span class="certification-audit__value">{{ auditValue('outerHeight') }}</span>
|
||||||
{{ auditValueWithUnit('outerHeight', 'mm') }}
|
|
||||||
</span>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
@@ -171,17 +165,13 @@
|
|||||||
</el-row>
|
</el-row>
|
||||||
<el-row :gutter="18">
|
<el-row :gutter="18">
|
||||||
<el-col :span="6">
|
<el-col :span="6">
|
||||||
<el-form-item label="核定载质量">
|
<el-form-item label="核定载质量(KG)">
|
||||||
<span class="certification-audit__value">
|
<span class="certification-audit__value">{{ auditValue('approvedLoadKg') }}</span>
|
||||||
{{ auditValueWithUnit('approvedLoadKg', 'KG') }}
|
|
||||||
</span>
|
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
</el-col>
|
</el-col>
|
||||||
<el-col :span="6">
|
<el-col :span="6">
|
||||||
<el-form-item label="准牵引总质量">
|
<el-form-item label="准牵引总质量(KG)">
|
||||||
<span class="certification-audit__value">
|
<span class="certification-audit__value">{{ auditValue('tractionMassKg') }}</span>
|
||||||
{{ auditValueWithUnit('tractionMassKg', 'KG') }}
|
|
||||||
</span>
|
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
</el-col>
|
</el-col>
|
||||||
<el-col :span="6">
|
<el-col :span="6">
|
||||||
@@ -367,7 +357,7 @@
|
|||||||
v-if="showRejectReason"
|
v-if="showRejectReason"
|
||||||
class="certification-audit__reason"
|
class="certification-audit__reason"
|
||||||
label-position="right"
|
label-position="right"
|
||||||
label-width="calc(4em + 24px)"
|
label-width="calc(7em + 24px)"
|
||||||
>
|
>
|
||||||
<el-form-item label="驳回原因" required>
|
<el-form-item label="驳回原因" required>
|
||||||
<el-input
|
<el-input
|
||||||
@@ -437,7 +427,7 @@
|
|||||||
</el-form-item>
|
</el-form-item>
|
||||||
</el-col>
|
</el-col>
|
||||||
<el-col :span="12">
|
<el-col :span="12">
|
||||||
<el-form-item label="外廓尺寸">
|
<el-form-item label="外廓尺寸(毫米)">
|
||||||
<div class="dimension-group">
|
<div class="dimension-group">
|
||||||
<div class="dimension-group__item">
|
<div class="dimension-group__item">
|
||||||
<span class="dimension-group__label">长</span>
|
<span class="dimension-group__label">长</span>
|
||||||
@@ -446,9 +436,7 @@
|
|||||||
inputmode="numeric"
|
inputmode="numeric"
|
||||||
placeholder="请输入"
|
placeholder="请输入"
|
||||||
@input="vehicleForm.outerLength = digitsOnly($event)"
|
@input="vehicleForm.outerLength = digitsOnly($event)"
|
||||||
>
|
/>
|
||||||
<template #suffix>mm</template>
|
|
||||||
</el-input>
|
|
||||||
</div>
|
</div>
|
||||||
<div class="dimension-group__item">
|
<div class="dimension-group__item">
|
||||||
<span class="dimension-group__label">宽</span>
|
<span class="dimension-group__label">宽</span>
|
||||||
@@ -457,9 +445,7 @@
|
|||||||
inputmode="numeric"
|
inputmode="numeric"
|
||||||
placeholder="请输入"
|
placeholder="请输入"
|
||||||
@input="vehicleForm.outerWidth = digitsOnly($event)"
|
@input="vehicleForm.outerWidth = digitsOnly($event)"
|
||||||
>
|
/>
|
||||||
<template #suffix>mm</template>
|
|
||||||
</el-input>
|
|
||||||
</div>
|
</div>
|
||||||
<div class="dimension-group__item">
|
<div class="dimension-group__item">
|
||||||
<span class="dimension-group__label">高</span>
|
<span class="dimension-group__label">高</span>
|
||||||
@@ -468,9 +454,7 @@
|
|||||||
inputmode="numeric"
|
inputmode="numeric"
|
||||||
placeholder="请输入"
|
placeholder="请输入"
|
||||||
@input="vehicleForm.outerHeight = digitsOnly($event)"
|
@input="vehicleForm.outerHeight = digitsOnly($event)"
|
||||||
>
|
/>
|
||||||
<template #suffix>mm</template>
|
|
||||||
</el-input>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
@@ -478,27 +462,23 @@
|
|||||||
</el-row>
|
</el-row>
|
||||||
<el-row :gutter="18">
|
<el-row :gutter="18">
|
||||||
<el-col :span="6">
|
<el-col :span="6">
|
||||||
<el-form-item label="核定载质量" prop="approvedLoadKg">
|
<el-form-item label="核定载质量(KG)" prop="approvedLoadKg">
|
||||||
<el-input
|
<el-input
|
||||||
v-model="vehicleForm.approvedLoadKg"
|
v-model="vehicleForm.approvedLoadKg"
|
||||||
inputmode="numeric"
|
inputmode="numeric"
|
||||||
placeholder="请输入"
|
placeholder="请输入"
|
||||||
@input="vehicleForm.approvedLoadKg = digitsOnly($event)"
|
@input="vehicleForm.approvedLoadKg = digitsOnly($event)"
|
||||||
>
|
/>
|
||||||
<template #suffix>KG</template>
|
|
||||||
</el-input>
|
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
</el-col>
|
</el-col>
|
||||||
<el-col :span="6">
|
<el-col :span="6">
|
||||||
<el-form-item label="准牵引总质量" prop="tractionMassKg">
|
<el-form-item label="准牵引总质量(KG)" prop="tractionMassKg">
|
||||||
<el-input
|
<el-input
|
||||||
v-model="vehicleForm.tractionMassKg"
|
v-model="vehicleForm.tractionMassKg"
|
||||||
inputmode="numeric"
|
inputmode="numeric"
|
||||||
placeholder="请输入"
|
placeholder="请输入"
|
||||||
@input="vehicleForm.tractionMassKg = digitsOnly($event)"
|
@input="vehicleForm.tractionMassKg = digitsOnly($event)"
|
||||||
>
|
/>
|
||||||
<template #suffix>KG</template>
|
|
||||||
</el-input>
|
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
</el-col>
|
</el-col>
|
||||||
<el-col :span="6">
|
<el-col :span="6">
|
||||||
@@ -916,7 +896,6 @@ export default {
|
|||||||
energyTypeOptions: ['柴油', '汽油', '新能源', '天然气'],
|
energyTypeOptions: ['柴油', '汽油', '新能源', '天然气'],
|
||||||
baseRules: {
|
baseRules: {
|
||||||
organizationName: [{ required: true, message: '请选择所属组织', trigger: 'change' }],
|
organizationName: [{ required: true, message: '请选择所属组织', trigger: 'change' }],
|
||||||
useDepartment: [{ required: true, message: '请选择使用部门', trigger: 'change' }],
|
|
||||||
plateNo: [
|
plateNo: [
|
||||||
{ required: true, message: '请输入车牌号', trigger: 'change' },
|
{ required: true, message: '请输入车牌号', trigger: 'change' },
|
||||||
{ validator: this.validatePlateNo, trigger: 'change' },
|
{ validator: this.validatePlateNo, trigger: 'change' },
|
||||||
@@ -1007,9 +986,6 @@ export default {
|
|||||||
if (this.vehicleBox && !this.vehicleForm.id && !this.vehicleForm.organizationName) {
|
if (this.vehicleBox && !this.vehicleForm.id && !this.vehicleForm.organizationName) {
|
||||||
this.vehicleForm.organizationName = this.getCurrentOrganizationName();
|
this.vehicleForm.organizationName = this.getCurrentOrganizationName();
|
||||||
}
|
}
|
||||||
if (this.vehicleBox && !this.vehicleForm.useDepartment) {
|
|
||||||
this.vehicleForm.useDepartment = this.getCurrentOrganizationName();
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
flattenDeptOptions(tree = [], level = 0) {
|
flattenDeptOptions(tree = [], level = 0) {
|
||||||
@@ -1085,7 +1061,6 @@ export default {
|
|||||||
if (!row?.id) {
|
if (!row?.id) {
|
||||||
this.vehicleForm = emptyForm();
|
this.vehicleForm = emptyForm();
|
||||||
this.vehicleForm.organizationName = this.getCurrentOrganizationName();
|
this.vehicleForm.organizationName = this.getCurrentOrganizationName();
|
||||||
this.vehicleForm.useDepartment = this.getCurrentOrganizationName();
|
|
||||||
this.syncPlateNo(false);
|
this.syncPlateNo(false);
|
||||||
this.vehicleBox = true;
|
this.vehicleBox = true;
|
||||||
return;
|
return;
|
||||||
@@ -1095,9 +1070,6 @@ export default {
|
|||||||
...emptyForm(),
|
...emptyForm(),
|
||||||
...(res.data.data || {}),
|
...(res.data.data || {}),
|
||||||
};
|
};
|
||||||
if (!this.vehicleForm.useDepartment) {
|
|
||||||
this.vehicleForm.useDepartment = this.getCurrentOrganizationName();
|
|
||||||
}
|
|
||||||
this.splitPlateNo();
|
this.splitPlateNo();
|
||||||
this.syncPlateNo(false);
|
this.syncPlateNo(false);
|
||||||
this.vehicleBox = true;
|
this.vehicleBox = true;
|
||||||
@@ -1130,10 +1102,6 @@ export default {
|
|||||||
const value = this.certificationAuditForm[prop];
|
const value = this.certificationAuditForm[prop];
|
||||||
return value === undefined || value === null || value === '' ? '-' : value;
|
return value === undefined || value === null || value === '' ? '-' : value;
|
||||||
},
|
},
|
||||||
auditValueWithUnit(prop, unit) {
|
|
||||||
const value = this.auditValue(prop);
|
|
||||||
return value === '-' ? value : `${value}${unit}`;
|
|
||||||
},
|
|
||||||
handleCertificationApprove() {
|
handleCertificationApprove() {
|
||||||
auditCertification(this.certificationAuditForm.id, 1).then(() => {
|
auditCertification(this.certificationAuditForm.id, 1).then(() => {
|
||||||
this.$message.success('认证已通过');
|
this.$message.success('认证已通过');
|
||||||
@@ -1725,19 +1693,11 @@ export default {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.certification-audit {
|
.certification-audit,
|
||||||
:deep(.el-form-item__label) {
|
|
||||||
flex: 0 0 calc(4em + 12px) !important;
|
|
||||||
width: calc(4em + 12px) !important;
|
|
||||||
max-width: calc(4em + 12px);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.certification-audit__reason {
|
.certification-audit__reason {
|
||||||
:deep(.el-form-item__label) {
|
:deep(.el-form-item__label) {
|
||||||
flex: 0 0 calc(4em + 24px) !important;
|
flex: 0 0 calc(7em + 24px) !important;
|
||||||
width: calc(4em + 24px) !important;
|
width: calc(7em + 24px) !important;
|
||||||
max-width: calc(4em + 24px);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -564,8 +564,8 @@ export default {
|
|||||||
this.addressMapPickerVisible = true;
|
this.addressMapPickerVisible = true;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
handleAddressMapConfirm(payload) {
|
handleAddressMapConfirm(address) {
|
||||||
this.form.address = typeof payload === 'string' ? payload : payload?.address || '';
|
this.form.address = address;
|
||||||
},
|
},
|
||||||
hasPermission(code) {
|
hasPermission(code) {
|
||||||
return this.isAdmin || this.validData(this.permission[code], false);
|
return this.isAdmin || this.validData(this.permission[code], false);
|
||||||
|
|||||||
@@ -545,8 +545,8 @@ export default {
|
|||||||
this.addressMapPickerVisible = true;
|
this.addressMapPickerVisible = true;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
handleAddressMapConfirm(payload) {
|
handleAddressMapConfirm(address) {
|
||||||
this.form.address = typeof payload === 'string' ? payload : payload?.address || '';
|
this.form.address = address;
|
||||||
},
|
},
|
||||||
hasPermission(code) {
|
hasPermission(code) {
|
||||||
return this.isAdmin || this.validData(this.permission[code], false);
|
return this.isAdmin || this.validData(this.permission[code], false);
|
||||||
|
|||||||
@@ -288,8 +288,8 @@ export default {
|
|||||||
this.locationMapPickerVisible = true;
|
this.locationMapPickerVisible = true;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
handleLocationMapConfirm(payload) {
|
handleLocationMapConfirm(address) {
|
||||||
this.form.location = typeof payload === 'string' ? payload : payload?.address || '';
|
this.form.location = address;
|
||||||
},
|
},
|
||||||
hasPermission(code) {
|
hasPermission(code) {
|
||||||
return this.isAdmin || this.validData(this.permission[code], false);
|
return this.isAdmin || this.validData(this.permission[code], false);
|
||||||
|
|||||||
+1
-3
@@ -51,10 +51,8 @@ export default ({ mode, command }) => {
|
|||||||
port: 2888,
|
port: 2888,
|
||||||
proxy: {
|
proxy: {
|
||||||
'/api': {
|
'/api': {
|
||||||
// 本地网关默认 80 端口(blade-gateway bootstrap.yml);开发时需先启动网关及 blade-auth/blade-system 等服务
|
|
||||||
// 注意:本机 8080 一般是 Nacos,不是业务网关
|
|
||||||
target: 'http://localhost',
|
target: 'http://localhost',
|
||||||
// target: 'http://172.16.203.228:8000', // 仅联调远程网关时启用(且不要 rewrite)
|
//target: 'https://saber3.bladex.cn/api',
|
||||||
changeOrigin: true,
|
changeOrigin: true,
|
||||||
rewrite: path => path.replace(/^\/api/, ''),
|
rewrite: path => path.replace(/^\/api/, ''),
|
||||||
},
|
},
|
||||||
|
|||||||
+4
-7
@@ -570,22 +570,19 @@
|
|||||||
|
|
||||||
- 功能点:
|
- 功能点:
|
||||||
- 任意状态的运单均支持复制。
|
- 任意状态的运单均支持复制。
|
||||||
- 复制后运单号重新生成,业务状态与原运单保持一致。
|
- 复制后生成新的草稿运单,运单号重新生成。
|
||||||
- 复制时保留运单基础信息、货物信息、承运信息和过程配置相关信息;过程打卡记录重新初始化。
|
- 复制时保留运单基础信息、货物信息、承运信息和过程配置相关信息,状态与过程记录重新初始化。
|
||||||
- 若原运单为已完成,复制保存后按合同系统计费规则自动生成对应应收、应付明细。
|
- 复制成功后跳转编辑页面。
|
||||||
- 复制成功后跳转编辑页面(独立表单模式)或刷新列表(弹窗模式)。
|
|
||||||
|
|
||||||
- 异常与边界:
|
- 异常与边界:
|
||||||
- 原运单不存在时提示数据不存在。
|
- 原运单不存在时提示数据不存在。
|
||||||
- 复制后名称、编号或关联字段触发唯一性校验时应重新处理。
|
- 复制后名称、编号或关联字段触发唯一性校验时应重新处理。
|
||||||
- 复制失败时不影响原运单数据。
|
- 复制失败时不影响原运单数据。
|
||||||
- 合同未开启系统计费、无匹配计费方案或自有运输等场景下,按既有规则跳过对应应收/应付生成。
|
|
||||||
|
|
||||||
- 反例:
|
- 反例:
|
||||||
- 不允许复制后沿用原运单号。
|
- 不允许复制后沿用原运单号。
|
||||||
- 不允许复制后状态与原运单不一致。
|
- 不允许复制后直接生成进行中或已完成状态。
|
||||||
- 不允许复制后保留原过程节点完成记录。
|
- 不允许复制后保留原过程节点完成记录。
|
||||||
- 不允许已完成运单复制成功后遗漏按规则应生成的应收应付明细。
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user