Compare commits
26 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 96b675b3be | |||
| 6067d24e3b | |||
| e051327f2d | |||
| f7759fd9e7 | |||
| 4287133f84 | |||
| 4b01316f64 | |||
| 0b170fe5c1 | |||
| 4f29b1e0eb | |||
| 6db599400e | |||
| c5a74acd00 | |||
| f23d65e41e | |||
| 6b0cb37699 | |||
| c867c5d7a5 | |||
| 903e0a4d7b | |||
| 6461c3d80f | |||
| 8380351529 | |||
| f8a238d0c0 | |||
| 5c6fcbe951 | |||
| 7997089b16 | |||
| 089b74479b | |||
| cf542313f7 | |||
| a19097d1be | |||
| 87aa64d0c1 | |||
| fbad28b717 | |||
| 19972aa8d8 | |||
| 5569f373c5 |
@@ -124,3 +124,19 @@
|
||||
另:自定义组件接模板字符串时,切出的模板里 `row.content` 要替换成 `content`(插槽作用域不存在于独立组件里),否则报 `Cannot read properties of undefined (reading 'content')`。
|
||||
- 校验:编译三个 .vue 的 template + scss 全过;临时验证页 `__route-change-verify.html` 与 8899 端口静态服务已删除/停止。
|
||||
- 未处理(等用户确认):备注列的 `show-overflow-tooltip` 保持原样(属全站约定),用户若要求连备注一起去掉再单独改。
|
||||
|
||||
## 16:45 付款申请表单「结算单号」字段交互歧义修复(方案 A)
|
||||
- 现象(用户截图圈出):`/payment/payment-application/form` 结算单号是 `el-select multiple`,placeholder 写「请输入」但**打不进去字**,必须先猜到要点右边「选择」才有效。
|
||||
- 根因(双重的):
|
||||
1. 该 `el-select` 的 `:model-value` 绑只读 computed `referenceSettlementNos`(= 已选单号本身),**options 就是它自己** → 点开下拉、点选项,值不变;代码里 `handleSettlementSelectVisible` 甚至主动 blur 掉下拉。真正改值的只有右侧「选择」按钮。
|
||||
2. placeholder 写「请输入」但控件不可输入(未开 `filterable`),且 `.el-tag__close{display:none}` 藏了删除叉 → 视觉上更像输入框。全站规矩是「请输入/请选择」二选一,这里是选择语义。
|
||||
- 落地(只改 `views/payment/payment-application-form.vue`,全站 `openReference`/`referenceSettlementNos` 无第二处引用):
|
||||
- 模板:`el-select` → `el-input readonly` + 外层 `el-tooltip`,`:model-value="referenceDisplayText"`,`placeholder="请输入"`,`:disabled="readonly"`,**`@click="!readonly && openReference()"` 让整框可点开弹窗**;右侧「选择」按钮保留。
|
||||
- 新增 computed:`referenceSettlementCount`、`referenceDisplayText`(单张显示单号;**多张只显示 `已选 N 张结算单`**,完整单号靠 tooltip,明细看下方「结算信息」表)。
|
||||
- 删除 `handleSettlementSelectVisible` 方法与 `settlementNoSelectRef`;样式 `.payment-form-page__settlement-select{,:deep(...)}` → `.payment-form-page__settlement-input`(+ `cursor:pointer` / disabled 时 `not-allowed`)。
|
||||
- ⚠️ 踩坑:第一版把「共 N 张」做成 `el-input` 的 `#suffix` 插槽 → span=6 的窄列里后缀挤占宽度,单号被截成 `JS202610`,比不改还差。**窄列(≤span6)不要在输入框内塞后缀/前缀,把信息编进 `:model-value` 文本本身**。
|
||||
- 验证(项目根临时 html + `vue/dist/vue.esm-bundler.js`,用完即删):
|
||||
- ⚠️ 临时页必须 import `vue/dist/vue.esm-bundler.js`,默认 `vue` 是 runtime-only,字符串 template 报 “runtime compilation is not supported”。
|
||||
- 4 态实测:空值 placeholder 正确 / 单张显单号 / 多张显「已选 3 张结算单」且不截断 / 查看态 disabled 且按钮隐藏;输入框宽 132px、行高 32px。
|
||||
- 点击探针验证:可编辑态点 `.el-input` 命中 1 次(el-tooltip 不拦截 click),查看态命中 0 次 ✓。
|
||||
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
# 2026-10-15
|
||||
|
||||
## 10:46 付款申请表单「结算单号」修复丢失 → 重放并提交
|
||||
- 现象:用户反馈昨天改好的「结算单号」(只读输入框 + 整框可点开选择弹窗)今天又变回旧的 `el-select` 写法。
|
||||
- 根因:昨天的改动**一直没有 commit**,是纯工作区改动;用户 pull(HEAD 到 9919fb9,两个 settlement 样式提交包含该文件旧版)后被覆盖。教训同 2026-09-18「漏提交新文件」一役:**本仓库改动必须尽快显式 commit**,`git status` 干净 ≠ 改动还在。
|
||||
- 处理:按昨天方案 A 原样重放 4 处修改(模板 el-select→el-input readonly+tooltip+整框可点、新增 `referenceSettlementCount`/`referenceDisplayText` computed、删 `handleSettlementSelectVisible`、样式类改名 `__settlement-input` + cursor 规则),curl 自检 vue/style 均 200。
|
||||
- **这次只提交该单文件**:`git commit -m ":bug: ..." -- src/views/payment/payment-application-form.vue` → `db03ced`(`.workbuddy/memory` 不入库,留在工作区)。尚未 push,等用户确认。
|
||||
@@ -66,7 +66,7 @@
|
||||
## 表单 / 自检 / 导出
|
||||
- placeholder 只写 `请输入`/`请选择`;例外:`请输入或选择车辆`、日期区间 `起`/`止`、示例值。校验 message 须带字段名。
|
||||
- `business-crud-page.vue`:option 经 `cloneOption` 克隆,独立表单页走 `PageAvueForm`。
|
||||
- 自检:`curl "localhost:2889/src/xxx.vue"` 与 `"...?vue&type=style&index=0&scoped=true&lang.scss"`(200 / 500)。`vite build` 在 `facelogin.vue` 被沙箱中断,不能作唯一校验。dev `VITE_APP_API=/api` 代理 `172.16.203.228:8000`。
|
||||
- 自检:`curl "localhost:2888/src/xxx.vue"` 与 `"...?vue&type=style&index=0&scoped=true&lang.scss"`(200 / 500)。`vite build` 在 `facelogin.vue` 被沙箱中断,不能作唯一校验。dev `VITE_APP_API=/api` 代理 `172.16.203.228:8000`。
|
||||
- 导出:前端 `exportColumns` 后端未用,以 `XxxExportExcel.java` 为准;BladeX `BeanUtil` 类型不兼容静默跳过(`Date` → `LocalDateTime` 丢值,用 `DateUtil.fromDate`)。
|
||||
|
||||
## Element Plus 图标两种用法的机制差异(易误判)
|
||||
|
||||
Generated
+415
-95
File diff suppressed because it is too large
Load Diff
@@ -1,78 +0,0 @@
|
||||
import request from '@/axios';
|
||||
|
||||
const baseUrl = '/blade-transport/hcs';
|
||||
|
||||
// 合规推送涉及 HCS 远程多次调用(风险校验+附件转传+起草),可达 20s+,覆盖全局 10s 超时
|
||||
export const repush = (bizType, id) =>
|
||||
request({
|
||||
url: `${baseUrl}/sync/${bizType}/${id}`,
|
||||
method: 'post',
|
||||
timeout: 60000,
|
||||
});
|
||||
|
||||
// 批量推送为逐条同步推送,耗时随勾选行数线性增长,进一步放宽超时
|
||||
export const repushBatch = (bizType, ids) =>
|
||||
request({
|
||||
url: `${baseUrl}/sync/${bizType}/batch`,
|
||||
method: 'post',
|
||||
data: ids,
|
||||
timeout: 300000,
|
||||
});
|
||||
|
||||
export const getTemplates = () =>
|
||||
request({
|
||||
url: `${baseUrl}/master-data/template`,
|
||||
method: 'get',
|
||||
});
|
||||
|
||||
export const refreshMasterData = () =>
|
||||
request({
|
||||
url: `${baseUrl}/master-data/refresh`,
|
||||
method: 'post',
|
||||
timeout: 60000,
|
||||
});
|
||||
|
||||
export const getRisk = customerCode =>
|
||||
request({
|
||||
url: `${baseUrl}/risk`,
|
||||
method: 'get',
|
||||
params: { customerCode },
|
||||
timeout: 30000,
|
||||
});
|
||||
|
||||
export const uploadAttachment = formData =>
|
||||
request({
|
||||
url: `${baseUrl}/attachment/upload`,
|
||||
method: 'post',
|
||||
data: formData,
|
||||
timeout: 60000,
|
||||
});
|
||||
|
||||
export const downloadAttachment = (fdId, name) =>
|
||||
request({
|
||||
url: `${baseUrl}/attachment/download`,
|
||||
method: 'get',
|
||||
params: { fdId, name },
|
||||
responseType: 'blob',
|
||||
timeout: 120000,
|
||||
});
|
||||
|
||||
export const applyContractSpec = data =>
|
||||
request({
|
||||
url: `${baseUrl}/contract/spec-apply`,
|
||||
method: 'post',
|
||||
data,
|
||||
timeout: 60000,
|
||||
});
|
||||
|
||||
export const confirmContractSpec = (id, result) =>
|
||||
request({
|
||||
url: `${baseUrl}/contract/spec/confirm`,
|
||||
method: 'post',
|
||||
params: { id, result },
|
||||
});
|
||||
|
||||
export const unwrapData = response => {
|
||||
const data = response?.data?.data ?? response?.data;
|
||||
return data && typeof data === 'object' && 'data' in data ? data.data : data;
|
||||
};
|
||||
@@ -10,6 +10,13 @@ export const getReferenceAmount = (paymentType, referenceId, excludeId) =>
|
||||
method: 'get',
|
||||
params: { paymentType, referenceId, excludeId: excludeId || undefined },
|
||||
});
|
||||
/** 按合同查询可选项目预付核销列表(审批通过且仍有可核销余额) */
|
||||
export const getProjectAdvanceWriteOffOptions = params =>
|
||||
request({
|
||||
url: `${baseUrl}/project-advance-write-off-options`,
|
||||
method: 'get',
|
||||
params,
|
||||
});
|
||||
export const save = data => request({ url: `${baseUrl}/save`, method: 'post', data });
|
||||
export const remove = id => request({ url: `${baseUrl}/remove`, method: 'post', params: { id } });
|
||||
export const submit = data => request({ url: `${baseUrl}/submit`, method: 'post', data });
|
||||
|
||||
@@ -24,6 +24,8 @@ export const getContractList = (current, size, params) =>
|
||||
|
||||
export const getFeeOptions = () => request({ url: `${baseUrl}/fee-options`, method: 'get' });
|
||||
|
||||
export const getNextNo = () => request({ url: `${baseUrl}/next-no`, method: 'get' });
|
||||
|
||||
export const getCandidateDetails = (current, size, params) =>
|
||||
request({
|
||||
url: `${baseUrl}/candidate-details`,
|
||||
|
||||
@@ -0,0 +1,55 @@
|
||||
import request from '@/axios';
|
||||
|
||||
const baseUrl = '/blade-transport/settlement-import';
|
||||
|
||||
export const getOptions = () => request({ url: `${baseUrl}/options`, method: 'get' });
|
||||
|
||||
export const getNextCode = () => request({ url: `${baseUrl}/next-code`, method: 'get' });
|
||||
|
||||
export const getList = (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 exportTemplate = () =>
|
||||
request({
|
||||
url: `${baseUrl}/export-template`,
|
||||
method: 'get',
|
||||
responseType: 'blob',
|
||||
});
|
||||
|
||||
export const parseFile = file => {
|
||||
const data = new FormData();
|
||||
data.append('file', file);
|
||||
return request({
|
||||
url: `${baseUrl}/parse`,
|
||||
method: 'post',
|
||||
data,
|
||||
timeout: 120000,
|
||||
});
|
||||
};
|
||||
|
||||
export const validate = data =>
|
||||
request({ url: `${baseUrl}/validate`, method: 'post', data, timeout: 120000 });
|
||||
|
||||
export const exportFailures = data =>
|
||||
request({
|
||||
url: `${baseUrl}/export-failures`,
|
||||
method: 'post',
|
||||
data,
|
||||
responseType: 'blob',
|
||||
timeout: 120000,
|
||||
});
|
||||
|
||||
export const saveDraft = data =>
|
||||
request({ url: `${baseUrl}/draft`, method: 'post', data, timeout: 120000 });
|
||||
|
||||
export const confirm = data =>
|
||||
request({ url: `${baseUrl}/confirm`, method: 'post', data, timeout: 120000 });
|
||||
|
||||
export const voidBatch = data => request({ url: `${baseUrl}/void`, method: 'post', data });
|
||||
@@ -1,4 +1,4 @@
|
||||
export const transportTypeOptions = [
|
||||
export const transportTypeOptions = [
|
||||
{ label: '公路运输', value: '公路运输' },
|
||||
{ label: '铁路运输', value: '铁路运输' },
|
||||
{ label: '水路运输', value: '水路运输' },
|
||||
@@ -143,12 +143,6 @@ export const contractArchiveStatusOptions = [
|
||||
{ label: '已归档', value: '已归档' },
|
||||
];
|
||||
|
||||
export const hcsSyncStatusOptions = [
|
||||
{ label: '待推送', value: 'unsynced' },
|
||||
{ label: '已推送', value: 'synced' },
|
||||
{ label: '推送失败', value: 'failed' },
|
||||
];
|
||||
|
||||
export const nodeOptions = [
|
||||
{ label: '接单', value: '接单' },
|
||||
{ label: '到场', value: '到场' },
|
||||
|
||||
@@ -7,7 +7,6 @@ import {
|
||||
createCrudOption,
|
||||
effectiveTypeOptions,
|
||||
nonNegativeRule,
|
||||
hcsSyncStatusOptions,
|
||||
selectRule,
|
||||
signTypeOptions,
|
||||
textRule,
|
||||
@@ -607,19 +606,6 @@ export const option = createCrudOption([
|
||||
addDisplay: false,
|
||||
editDisplay: false,
|
||||
},
|
||||
{
|
||||
label: '合规状态',
|
||||
prop: 'hcsSyncStatus',
|
||||
type: 'select',
|
||||
search: true,
|
||||
searchOrder: 6,
|
||||
searchPlaceholder: '请选择',
|
||||
slot: true,
|
||||
dicData: hcsSyncStatusOptions,
|
||||
minWidth: 110,
|
||||
addDisplay: false,
|
||||
editDisplay: false,
|
||||
},
|
||||
{
|
||||
label: '当前节点',
|
||||
prop: 'currentNode',
|
||||
|
||||
@@ -3,7 +3,6 @@ import {
|
||||
businessModeOptions,
|
||||
businessTypeOptions,
|
||||
createCrudOption,
|
||||
hcsSyncStatusOptions,
|
||||
nonNegativeRule,
|
||||
projectApprovalStatusOptions,
|
||||
projectSourceOptions,
|
||||
@@ -406,19 +405,6 @@ export const option = createCrudOption([
|
||||
addDisplay: false,
|
||||
editDisplay: false,
|
||||
},
|
||||
{
|
||||
label: '合规状态',
|
||||
prop: 'hcsSyncStatus',
|
||||
type: 'select',
|
||||
search: true,
|
||||
searchPlaceholder: '请选择',
|
||||
searchOrder: 1,
|
||||
slot: true,
|
||||
dicData: hcsSyncStatusOptions,
|
||||
minWidth: 110,
|
||||
addDisplay: false,
|
||||
editDisplay: false,
|
||||
},
|
||||
{
|
||||
label: '生效类型',
|
||||
prop: 'effectiveType',
|
||||
|
||||
@@ -35,8 +35,8 @@ export const sourceColumns = [
|
||||
];
|
||||
|
||||
export const detailColumns = [
|
||||
{ label: '单据号', prop: 'documentNo', minWidth: 180 },
|
||||
{ label: '运单号', prop: 'waybillNo', minWidth: 160 },
|
||||
{ label: '单据号', prop: 'documentNo', minWidth: 180, link: true },
|
||||
{ label: '运单号', prop: 'waybillNo', minWidth: 160, link: true },
|
||||
{ label: '车号', prop: 'vehicleNo', minWidth: 130 },
|
||||
{ label: '发货地址', prop: 'departureAddress', minWidth: 180 },
|
||||
{ label: '到货地址', prop: 'arrivalAddress', minWidth: 180 },
|
||||
|
||||
@@ -40,7 +40,7 @@ export const settlementSummaryColumns = [
|
||||
|
||||
export const settlementDetailColumns = [
|
||||
{ label: '单据号', prop: 'documentNo', minWidth: 180, link: true },
|
||||
{ label: '运单号', prop: 'waybillNo', minWidth: 160 },
|
||||
{ label: '运单号', prop: 'waybillNo', minWidth: 160, link: true },
|
||||
{ label: '车号', prop: 'vehicleNo', minWidth: 130 },
|
||||
{ label: '发货地址', prop: 'departureAddress', minWidth: 180 },
|
||||
{ label: '到货地址', prop: 'arrivalAddress', minWidth: 180 },
|
||||
|
||||
@@ -119,6 +119,21 @@ export default [
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
path: '/settlement/settlement-import/form',
|
||||
component: Layout,
|
||||
children: [
|
||||
{
|
||||
path: '',
|
||||
name: '新增导入结算单',
|
||||
meta: {
|
||||
keepAlive: false,
|
||||
activeMenu: '/settlement/settlement-import',
|
||||
},
|
||||
component: loadView(() => import('@/views/settlement/settlement-import-form.vue')),
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
path: '/settlement/transport-reconciliation/form',
|
||||
component: Layout,
|
||||
|
||||
+65
-13
@@ -61,9 +61,18 @@ export const MK_BIZ = {
|
||||
|
||||
const MK_SWITCH_NAME = '开启MK';
|
||||
|
||||
async function loadMkTemplateList() {
|
||||
/** 进程内缓存,避免每次提交都打字典;登录后同一会话开关一般不变 */
|
||||
let mkEnabledCache = null;
|
||||
let mkEnabledPromise = null;
|
||||
let mkTemplateListCache = null;
|
||||
|
||||
async function loadMkTemplateList(force = false) {
|
||||
if (!force && mkTemplateListCache) {
|
||||
return mkTemplateListCache;
|
||||
}
|
||||
const res = await getDictionary({ code: 'mk_template' });
|
||||
return res?.data?.data || [];
|
||||
mkTemplateListCache = res?.data?.data || [];
|
||||
return mkTemplateListCache;
|
||||
}
|
||||
|
||||
/** 业务字典 mk_template:名称「开启MK」且键值为 1 时才请求 MK */
|
||||
@@ -72,10 +81,37 @@ export function isMkEnabled(list = []) {
|
||||
return String(matched?.dictKey ?? '').trim() === '1';
|
||||
}
|
||||
|
||||
/** 加载业务字典并判断是否开启 MK */
|
||||
export async function loadMkEnabled() {
|
||||
const list = await loadMkTemplateList();
|
||||
return isMkEnabled(list);
|
||||
/** 清除 MK 开关缓存(字典变更后可调用) */
|
||||
export function clearMkEnabledCache() {
|
||||
mkEnabledCache = null;
|
||||
mkEnabledPromise = null;
|
||||
mkTemplateListCache = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 加载业务字典并判断是否开启 MK
|
||||
* @param {boolean} [force=false] 是否强制刷新缓存
|
||||
*/
|
||||
export async function loadMkEnabled(force = false) {
|
||||
if (!force && mkEnabledCache !== null) {
|
||||
return mkEnabledCache;
|
||||
}
|
||||
if (!force && mkEnabledPromise) {
|
||||
return mkEnabledPromise;
|
||||
}
|
||||
mkEnabledPromise = loadMkTemplateList(force)
|
||||
.then(list => {
|
||||
mkEnabledCache = isMkEnabled(list);
|
||||
return mkEnabledCache;
|
||||
})
|
||||
.catch(error => {
|
||||
mkEnabledCache = false;
|
||||
throw error;
|
||||
})
|
||||
.finally(() => {
|
||||
mkEnabledPromise = null;
|
||||
});
|
||||
return mkEnabledPromise;
|
||||
}
|
||||
|
||||
export async function resolveMkTemplateCode(dictName, list) {
|
||||
@@ -99,19 +135,22 @@ export function isMkApprovedStatus(bizType, approvalStatus) {
|
||||
}
|
||||
|
||||
/**
|
||||
* 提交 MK 审核流
|
||||
* 提交 MK 审核流。未开启 MK 时直接跳过,不请求流程实例相关接口。
|
||||
* @param {Object} options
|
||||
* @param {string} options.bizType 业务类型
|
||||
* @param {string|number} options.bizId 业务主键(后端据此生成/复用 formInstanceId)
|
||||
* @param {string|number} options.bizId 业务主键
|
||||
* @param {string} [options.subjectName] 标题后缀
|
||||
* @param {string} [options.approvalStatus] 提交前的审批状态(须在业务 submit 改状态前取值;通过态强制新建实例)
|
||||
* @param {string} [options.approvalStatus] 提交前的审批状态
|
||||
* @param {boolean} [options.mkEnabled] 调用方已知开关时传入,false 时立即跳过
|
||||
* @returns {Promise<boolean>} true=已发起/复用 MK 流程,false=未开启或已跳过
|
||||
*/
|
||||
export async function submitMkApprovalFlow({
|
||||
bizType,
|
||||
bizId,
|
||||
subjectName = '',
|
||||
approvalStatus = '',
|
||||
}) {
|
||||
mkEnabled,
|
||||
} = {}) {
|
||||
const conf = MK_BIZ[bizType];
|
||||
if (!conf) {
|
||||
return Promise.reject(new Error(`不支持的MK业务类型:${bizType}`));
|
||||
@@ -119,17 +158,29 @@ export async function submitMkApprovalFlow({
|
||||
if (bizId == null || bizId === '') {
|
||||
return Promise.reject(new Error('业务id不能为空'));
|
||||
}
|
||||
|
||||
// 调用方显式关闭,或字典未开启:不请求 hasMkProcessInstance / processSubmit 等 MK 接口
|
||||
if (mkEnabled === false) {
|
||||
return false;
|
||||
}
|
||||
const enabled = mkEnabled === true ? true : await loadMkEnabled();
|
||||
if (!enabled) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const list = await loadMkTemplateList();
|
||||
if (!isMkEnabled(list)) {
|
||||
return;
|
||||
mkEnabledCache = false;
|
||||
return false;
|
||||
}
|
||||
|
||||
const bizIdText = String(bizId);
|
||||
const forceNewFormInstance = isMkApprovedStatus(bizType, approvalStatus);
|
||||
// 审核通过后再提交必须新建流程;其余场景已有有效实例则跳过
|
||||
if (!forceNewFormInstance) {
|
||||
const existsRes = await hasMkProcessInstance({ bizId: bizIdText });
|
||||
if (existsRes?.data?.data) {
|
||||
return;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
if ((conf.rejected || []).includes(approvalStatus)) {
|
||||
@@ -139,11 +190,12 @@ export async function submitMkApprovalFlow({
|
||||
const subject = subjectName
|
||||
? `${conf.subjectPrefix}:${subjectName}`
|
||||
: `${conf.subjectPrefix}:${bizIdText}`;
|
||||
return processSubmit({
|
||||
await processSubmit({
|
||||
templateCode,
|
||||
bizId: bizIdText,
|
||||
subject,
|
||||
bizType,
|
||||
forceNewFormInstance,
|
||||
});
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -1,3 +1,35 @@
|
||||
/**
|
||||
* 合并列表搜索条件:新条件覆盖旧条件,本次显式清空的字段从查询中移除。
|
||||
*/
|
||||
export const applyIncomingSearch = (current = {}, incoming = {}) => {
|
||||
const merged = { ...current, ...incoming };
|
||||
Object.keys(incoming || {}).forEach(key => {
|
||||
const value = incoming[key];
|
||||
if (
|
||||
value === '' ||
|
||||
value === null ||
|
||||
value === undefined ||
|
||||
(Array.isArray(value) && value.length === 0)
|
||||
) {
|
||||
delete merged[key];
|
||||
}
|
||||
});
|
||||
return merged;
|
||||
};
|
||||
|
||||
/**
|
||||
* 启停状态下拉「全部」在数字类型下会被转成 0/-1,不能当作查询条件。
|
||||
* 状态 0 表示草稿等有效值的页面不要调用。
|
||||
*/
|
||||
export const omitInvalidListStatus = (params = {}) => {
|
||||
const values = { ...params };
|
||||
const status = values.status;
|
||||
if (status === 0 || status === '0' || status === -1 || status === '-1') {
|
||||
delete values.status;
|
||||
}
|
||||
return values;
|
||||
};
|
||||
|
||||
export const normalizeSearchRangeParams = (params = {}, rangeMap = {}) => {
|
||||
const values = { ...params };
|
||||
|
||||
|
||||
@@ -0,0 +1,51 @@
|
||||
import { getList as getReceivablePayableDetailList } from '@/api/settlement/receivable-payable-detail';
|
||||
import { getList as getWaybillList } from '@/api/business/waybill-manage';
|
||||
|
||||
// 结算明细的「单据号」是应收应付明细单据号(应收 YS…/应付 YF…),按结算类型落到对应菜单页
|
||||
const SETTLEMENT_DETAIL_PATHS = {
|
||||
receivable: '/settlement/receivable-detail',
|
||||
payable: '/settlement/payable-detail',
|
||||
};
|
||||
|
||||
const unwrapPage = response => {
|
||||
const body = response?.data || response || {};
|
||||
return body?.data || body;
|
||||
};
|
||||
|
||||
const firstRecordId = data => (Array.isArray(data) ? data : data?.records || [])[0]?.id || '';
|
||||
|
||||
export const settlementDetailPath = settlementType =>
|
||||
SETTLEMENT_DETAIL_PATHS[settlementType] || SETTLEMENT_DETAIL_PATHS.payable;
|
||||
|
||||
/**
|
||||
* 结算明细行「单据号」对应的应收应付明细ID:已落库的明细自带 sourceDetailId,未落库的行按单据号回查。
|
||||
*/
|
||||
export const resolveReceivablePayableDetailId = async (row = {}, settlementType) => {
|
||||
if (row.sourceDetailId) return row.sourceDetailId;
|
||||
if (!row.documentNo) return '';
|
||||
try {
|
||||
const data = unwrapPage(
|
||||
await getReceivablePayableDetailList(1, 1, {
|
||||
documentNo: row.documentNo,
|
||||
settlementType,
|
||||
})
|
||||
);
|
||||
return firstRecordId(data);
|
||||
} catch (error) {
|
||||
return '';
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* 结算明细行「运单号」对应的运单ID:总单来源的明细没有 waybillId(运单号列存的是总单号),按运单号回查。
|
||||
*/
|
||||
export const resolveWaybillId = async (row = {}) => {
|
||||
if (row.waybillId) return row.waybillId;
|
||||
if (!row.waybillNo) return '';
|
||||
try {
|
||||
const data = unwrapPage(await getWaybillList(1, 1, { waybillNo: row.waybillNo }));
|
||||
return firstRecordId(data);
|
||||
} catch (error) {
|
||||
return '';
|
||||
}
|
||||
};
|
||||
@@ -150,6 +150,7 @@ import { mapGetters } from 'vuex';
|
||||
import { downloadXls } from '@/utils/util';
|
||||
import { openImportDialog } from '@/utils/import-excel';
|
||||
import { formatUpdateUserName } from '@/utils/audit';
|
||||
import { omitInvalidListStatus } from '@/utils/search-range';
|
||||
import { getToken, isAdministrator } from '@/utils/auth';
|
||||
import { getCoordinateValidationMessage, normalizeCoordinateInput } from '@/utils/coordinate';
|
||||
import AddressMapPicker from '@/components/address-map-picker/main.vue';
|
||||
@@ -1182,7 +1183,7 @@ export default {
|
||||
},
|
||||
onLoad(page, params = {}) {
|
||||
this.loading = true;
|
||||
getList(page.currentPage, page.pageSize, { ...params, ...this.query })
|
||||
getList(page.currentPage, page.pageSize, omitInvalidListStatus({ ...params, ...this.query }))
|
||||
.then(res => {
|
||||
const data = res.data.data;
|
||||
this.page.total = data.total;
|
||||
|
||||
@@ -104,6 +104,7 @@ import { mapGetters } from 'vuex';
|
||||
import { downloadXls } from '@/utils/util';
|
||||
import { openImportDialog } from '@/utils/import-excel';
|
||||
import { formatUpdateUserName } from '@/utils/audit';
|
||||
import { omitInvalidListStatus } from '@/utils/search-range';
|
||||
import { getToken } from '@/utils/auth';
|
||||
import NProgress from 'nprogress';
|
||||
import 'nprogress/nprogress.css';
|
||||
@@ -507,7 +508,7 @@ export default {
|
||||
this.onLoad(this.page, this.query);
|
||||
},
|
||||
buildQuery(params = {}) {
|
||||
const values = { ...params, ...this.query };
|
||||
const values = omitInvalidListStatus({ ...params, ...this.query });
|
||||
const rangeMap = {
|
||||
effectiveDateRange: ['effectiveDateStart', 'effectiveDateEnd'],
|
||||
expiryDateRange: ['expiryDateStart', 'expiryDateEnd'],
|
||||
|
||||
@@ -63,6 +63,7 @@
|
||||
|
||||
<script>
|
||||
import { getList, getDetail, submit, remove, changeStatus } from '@/api/base/customer-type';
|
||||
import { omitInvalidListStatus } from '@/utils/search-range';
|
||||
import { mapGetters } from 'vuex';
|
||||
|
||||
export default {
|
||||
@@ -290,7 +291,7 @@ export default {
|
||||
},
|
||||
onLoad(page, params = {}) {
|
||||
this.loading = true;
|
||||
getList(page.currentPage, page.pageSize, Object.assign(params, this.query)).then(res => {
|
||||
getList(page.currentPage, page.pageSize, omitInvalidListStatus(Object.assign(params, this.query))).then(res => {
|
||||
const data = res.data.data;
|
||||
this.page.total = data.total;
|
||||
this.data = data.records;
|
||||
|
||||
@@ -97,6 +97,7 @@ import { exportBlob } from '@/api/common';
|
||||
import { getDictionaryAll } from '@/api/system/dictbiz';
|
||||
import { getDeptTree } from '@/api/system/dept';
|
||||
import { downloadXls } from '@/utils/util';
|
||||
import { omitInvalidListStatus } from '@/utils/search-range';
|
||||
import { getToken } from '@/utils/auth';
|
||||
import { Download } from '@element-plus/icons-vue';
|
||||
import { mapGetters } from 'vuex';
|
||||
@@ -569,7 +570,7 @@ export default {
|
||||
},
|
||||
onLoad(page, params = {}) {
|
||||
this.loading = true;
|
||||
getList(page.currentPage, page.pageSize, Object.assign(params, this.query)).then(res => {
|
||||
getList(page.currentPage, page.pageSize, omitInvalidListStatus(Object.assign(params, this.query))).then(res => {
|
||||
const data = res.data.data;
|
||||
this.page.total = data.total;
|
||||
this.data = data.records;
|
||||
|
||||
@@ -82,6 +82,7 @@
|
||||
import { mapGetters } from 'vuex';
|
||||
import { changeStatus, getDetail, getList, remove, submit } from '@/api/base/measurement-unit';
|
||||
import { createOption } from '@/option/base/measurement-unit';
|
||||
import { omitInvalidListStatus } from '@/utils/search-range';
|
||||
import { isAdministrator } from '@/utils/auth';
|
||||
|
||||
export default {
|
||||
@@ -218,7 +219,7 @@ export default {
|
||||
},
|
||||
onLoad(page = this.page, params = this.query) {
|
||||
this.loading = true;
|
||||
return getList(page.currentPage, page.pageSize, params)
|
||||
return getList(page.currentPage, page.pageSize, omitInvalidListStatus(params))
|
||||
.then(res => {
|
||||
const pageData = res.data.data || {};
|
||||
this.data = pageData.records || [];
|
||||
|
||||
@@ -270,6 +270,7 @@ import { mapGetters } from 'vuex';
|
||||
import { downloadXls } from '@/utils/util';
|
||||
import { openImportDialog } from '@/utils/import-excel';
|
||||
import { formatUpdateUserName } from '@/utils/audit';
|
||||
import { omitInvalidListStatus } from '@/utils/search-range';
|
||||
import { getToken, isAdministrator } from '@/utils/auth';
|
||||
import { getCoordinateValidationMessage, normalizeCoordinateInput } from '@/utils/coordinate';
|
||||
import AddressMapPicker from '@/components/address-map-picker/main.vue';
|
||||
@@ -1636,7 +1637,7 @@ export default {
|
||||
},
|
||||
onLoad(page, params = {}) {
|
||||
this.loading = true;
|
||||
getList(page.currentPage, page.pageSize, { ...params, ...this.query })
|
||||
getList(page.currentPage, page.pageSize, omitInvalidListStatus({ ...params, ...this.query }))
|
||||
.then(res => {
|
||||
const data = res.data.data;
|
||||
this.page.total = data.total;
|
||||
|
||||
@@ -150,6 +150,7 @@ import { mapGetters } from 'vuex';
|
||||
import { downloadXls } from '@/utils/util';
|
||||
import { openImportDialog } from '@/utils/import-excel';
|
||||
import { formatUpdateUserName } from '@/utils/audit';
|
||||
import { omitInvalidListStatus } from '@/utils/search-range';
|
||||
import { getToken, isAdministrator } from '@/utils/auth';
|
||||
import { getCoordinateValidationMessage, normalizeCoordinateInput } from '@/utils/coordinate';
|
||||
import AddressMapPicker from '@/components/address-map-picker/main.vue';
|
||||
@@ -1157,7 +1158,7 @@ export default {
|
||||
},
|
||||
onLoad(page, params = {}) {
|
||||
this.loading = true;
|
||||
getList(page.currentPage, page.pageSize, { ...params, ...this.query })
|
||||
getList(page.currentPage, page.pageSize, omitInvalidListStatus({ ...params, ...this.query }))
|
||||
.then(res => {
|
||||
const data = res.data.data;
|
||||
this.page.total = data.total;
|
||||
|
||||
@@ -396,8 +396,11 @@ export default {
|
||||
return this.selectionList.map(item => item.id).join(',');
|
||||
},
|
||||
},
|
||||
created() {
|
||||
this.applyDefaultDeptSearch();
|
||||
created() {
|
||||
// 超管默认查全部组织,不预填组织筛选;普通用户预填当前组织。
|
||||
if (!this.isAdmin) {
|
||||
this.applyDefaultDeptSearch();
|
||||
}
|
||||
this.initDeptTree();
|
||||
},
|
||||
methods: {
|
||||
@@ -455,10 +458,23 @@ export default {
|
||||
normalizeListQuery(params = {}) {
|
||||
// 搜索条件优先于缓存的 query,避免默认组织 ID 覆盖用户新选择的组织。
|
||||
const query = { ...this.query, ...params };
|
||||
const rawDeptId = query.deptId ?? query.searchDeptId;
|
||||
// 本次搜索显式带了组织字段时,以本次为准(含清空);否则沿用缓存。
|
||||
const hasDeptInParams =
|
||||
Object.prototype.hasOwnProperty.call(params, 'deptId') ||
|
||||
Object.prototype.hasOwnProperty.call(params, 'searchDeptId');
|
||||
const rawDeptId = hasDeptInParams
|
||||
? params.deptId ?? params.searchDeptId
|
||||
: query.deptId ?? query.searchDeptId;
|
||||
delete query.searchDeptId;
|
||||
if (rawDeptId !== undefined && rawDeptId !== null && String(rawDeptId).trim() !== '') {
|
||||
query.deptId = String(rawDeptId).split(',')[0].trim();
|
||||
// 级联可能回传路径数组,统一取末级并保持字符串,避免雪花 ID 精度丢失。
|
||||
const deptValue = Array.isArray(rawDeptId) ? rawDeptId.at(-1) : rawDeptId;
|
||||
query.deptId = String(deptValue ?? '')
|
||||
.split(',')[0]
|
||||
.trim();
|
||||
if (!query.deptId) {
|
||||
delete query.deptId;
|
||||
}
|
||||
} else {
|
||||
delete query.deptId;
|
||||
}
|
||||
@@ -466,6 +482,23 @@ export default {
|
||||
return query;
|
||||
},
|
||||
applyDefaultDeptSearch() {
|
||||
// 超管默认不限组织,避免预填组织后新增落在其他组织时列表看不见。
|
||||
if (this.isAdmin) {
|
||||
const deptColumn = this.findColumn(this.option.column, 'searchDeptId');
|
||||
if (deptColumn) {
|
||||
deptColumn.searchValue = undefined;
|
||||
}
|
||||
const nextQuery = { ...this.query };
|
||||
delete nextQuery.deptId;
|
||||
delete nextQuery.searchDeptId;
|
||||
this.query = nextQuery;
|
||||
this.$nextTick(() => {
|
||||
if (this.$refs.crud?.searchForm) {
|
||||
this.$refs.crud.searchForm.searchDeptId = undefined;
|
||||
}
|
||||
});
|
||||
return;
|
||||
}
|
||||
const deptId = this.defaultDeptId();
|
||||
const deptColumn = this.findColumn(this.option.column, 'searchDeptId');
|
||||
if (!deptId || !deptColumn) return;
|
||||
@@ -477,6 +510,11 @@ export default {
|
||||
}
|
||||
});
|
||||
},
|
||||
resolveDeptName(deptId) {
|
||||
if (!deptId) return '';
|
||||
const matched = this.deptOptions.find(item => String(item.value) === String(deptId));
|
||||
return matched ? String(matched.label || '').trim() : '';
|
||||
},
|
||||
resetAddressQuery() {
|
||||
this.addressQuery = {
|
||||
addressName: '',
|
||||
@@ -933,7 +971,8 @@ export default {
|
||||
}
|
||||
api.submit(submitRow).then(
|
||||
() => {
|
||||
this.onLoad(this.page);
|
||||
this.page.currentPage = 1;
|
||||
this.onLoad(this.page, this.query);
|
||||
this.$message({ type: 'success', message: '操作成功!' });
|
||||
done();
|
||||
},
|
||||
@@ -951,7 +990,7 @@ export default {
|
||||
}
|
||||
api.submit(submitRow).then(
|
||||
() => {
|
||||
this.onLoad(this.page);
|
||||
this.onLoad(this.page, this.query);
|
||||
this.$message({ type: 'success', message: '操作成功!' });
|
||||
done();
|
||||
},
|
||||
@@ -1006,7 +1045,13 @@ export default {
|
||||
beforeOpen(done, type) {
|
||||
this.dialogReadonly = type === 'view';
|
||||
if (type === 'add') {
|
||||
this.form = {};
|
||||
const form = {};
|
||||
// 超管若当前按某组织筛选,新增默认归属该组织,避免保存后被筛选条件滤掉。
|
||||
if (this.isAdmin && this.query.deptId) {
|
||||
form.deptId = String(this.query.deptId);
|
||||
form.deptName = this.resolveDeptName(form.deptId);
|
||||
}
|
||||
this.form = form;
|
||||
done();
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -203,8 +203,6 @@ export const resolveAttachmentTypeDictCode = (location, fallback = CONTRACT_ATTA
|
||||
export const defaultContractAttachmentTypeOptions = [
|
||||
{ label: '用印材料', value: '用印材料' },
|
||||
{ label: '双章归档文件', value: '双章归档文件' },
|
||||
// 高风险/黑名单客商合同必传,后端按 fileType=特批说明 校验与转传特批(与后端 HcsSyncConstant.ATTACHMENT_TYPE_SPEC_EXPLANATION 对齐)
|
||||
{ label: '特批说明', value: '特批说明' },
|
||||
{ label: '其他文件', value: CONTRACT_ATTACHMENT_TYPE_OTHER },
|
||||
];
|
||||
export const defaultContractSupportingAttachmentTypeOptions = [
|
||||
|
||||
@@ -1,271 +0,0 @@
|
||||
<template>
|
||||
<section class="contract-manage-form__section contract-manage-form__section--panel hcs-contract-panel">
|
||||
<div class="contract-manage-form__attachment-head">
|
||||
<div class="dialog-section-title">合规信息</div>
|
||||
<div>
|
||||
<el-button
|
||||
v-if="canApplySpec"
|
||||
type="warning"
|
||||
plain
|
||||
:loading="specApplying"
|
||||
@click="openSpecDialog"
|
||||
>
|
||||
发起特批
|
||||
</el-button>
|
||||
<el-button
|
||||
v-if="isSpecProcessing"
|
||||
type="primary"
|
||||
plain
|
||||
@click="confirmSpecResult"
|
||||
>
|
||||
确认特批结果
|
||||
</el-button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<el-descriptions :column="3" class="contract-manage-detail__descriptions">
|
||||
<el-descriptions-item label="合规同步状态">
|
||||
<el-tooltip
|
||||
v-if="hcsSyncMsg"
|
||||
placement="top"
|
||||
:content="hcsSyncMsg"
|
||||
>
|
||||
<el-tag :type="syncTagType" class="status-text">{{ syncLabel }}</el-tag>
|
||||
</el-tooltip>
|
||||
<el-tag v-else :type="syncTagType" class="status-text">{{ syncLabel }}</el-tag>
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="合规合同ID">{{
|
||||
contract.hcsFdId || '-'
|
||||
}}</el-descriptions-item>
|
||||
<el-descriptions-item label="客商风险">
|
||||
<template v-if="contract.hcsRiskLevel || contract.hcsBlackStatus">
|
||||
<el-tag
|
||||
v-if="isBlacklist"
|
||||
type="danger"
|
||||
class="status-text"
|
||||
>黑名单</el-tag
|
||||
>
|
||||
<span v-if="contract.hcsRiskLevel" class="hcs-contract-panel__risk">{{
|
||||
`风险等级:${contract.hcsRiskLevel}`
|
||||
}}</span>
|
||||
<span v-if="!contract.hcsRiskLevel && !isBlacklist">-</span>
|
||||
</template>
|
||||
<span v-else>-</span>
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="特批状态">
|
||||
<el-tag v-if="specLabel" :type="specTagType" class="status-text">{{ specLabel }}</el-tag>
|
||||
<span v-else>-</span>
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="特批说明" :span="2">{{
|
||||
contract.hcsSpecMsg || '-'
|
||||
}}</el-descriptions-item>
|
||||
</el-descriptions>
|
||||
|
||||
<el-dialog
|
||||
v-model="specDialogVisible"
|
||||
title="发起合同特批"
|
||||
width="640px"
|
||||
append-to-body
|
||||
destroy-on-close
|
||||
class="hcs-contract-panel__spec-dialog"
|
||||
@closed="resetSpecDialog"
|
||||
>
|
||||
<el-alert
|
||||
title="高风险客商合同需经集团特批后方可签约推送;请上传特批申请材料及内部审批流程附件。"
|
||||
type="warning"
|
||||
:closable="false"
|
||||
class="hcs-contract-panel__spec-alert"
|
||||
/>
|
||||
<el-upload
|
||||
drag
|
||||
multiple
|
||||
:show-file-list="false"
|
||||
:http-request="uploadMaterial"
|
||||
:on-error="() => uploadFailed()"
|
||||
class="hcs-contract-panel__spec-upload"
|
||||
>
|
||||
<el-icon class="el-icon--upload"><upload-filled /></el-icon>
|
||||
<div class="el-upload__text">将特批材料拖到此处,或<em>点击上传到合同系统</em></div>
|
||||
</el-upload>
|
||||
<el-table :data="materials" border class="hcs-contract-panel__spec-table">
|
||||
<el-table-column type="index" label="序号" width="70" align="center" />
|
||||
<el-table-column prop="name" label="文件名" min-width="220" show-overflow-tooltip />
|
||||
<el-table-column prop="fdId" label="合规附件ID" min-width="240" show-overflow-tooltip />
|
||||
<el-table-column label="操作" width="90" align="center" fixed="right">
|
||||
<template #default="{ $index }">
|
||||
<el-link type="danger" @click="materials.splice($index, 1)">删除</el-link>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
<template #footer>
|
||||
<el-button type="primary" :loading="specApplying" @click="submitSpec">确认发起</el-button>
|
||||
<el-button @click="specDialogVisible = false">取消</el-button>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</section>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { UploadFilled } from '@element-plus/icons-vue';
|
||||
import {
|
||||
applyContractSpec,
|
||||
confirmContractSpec,
|
||||
uploadAttachment,
|
||||
} from '@/api/business/hcs';
|
||||
|
||||
const SYNC_STATUS_OPTIONS = [
|
||||
{ label: '待推送', value: 'unsynced', type: 'info' },
|
||||
{ label: '已推送', value: 'synced', type: 'success' },
|
||||
{ label: '推送失败', value: 'failed', type: 'danger' },
|
||||
];
|
||||
|
||||
const SPEC_STATUS_OPTIONS = [
|
||||
{ label: '待特批', value: 'need', type: 'warning' },
|
||||
{ label: '特批中', value: 'processing', type: 'warning' },
|
||||
{ label: '特批通过', value: 'approved', type: 'success' },
|
||||
{ label: '特批驳回', value: 'rejected', type: 'danger' },
|
||||
];
|
||||
|
||||
export default {
|
||||
name: 'HcsContractPanel',
|
||||
components: { UploadFilled },
|
||||
props: {
|
||||
// 合同详情行(须含 id/hcsSyncStatus/hcsFdId/hcsRiskLevel/hcsBlackStatus/hcsSpecStatus 等)
|
||||
contract: { type: Object, required: true },
|
||||
},
|
||||
emits: ['refresh'],
|
||||
data() {
|
||||
return {
|
||||
specDialogVisible: false,
|
||||
specApplying: false,
|
||||
materials: [],
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
syncLabel() {
|
||||
return SYNC_STATUS_OPTIONS.find(item => item.value === this.contract.hcsSyncStatus)?.label || '-';
|
||||
},
|
||||
syncTagType() {
|
||||
return SYNC_STATUS_OPTIONS.find(item => item.value === this.contract.hcsSyncStatus)?.type || 'info';
|
||||
},
|
||||
hcsSyncMsg() {
|
||||
return this.contract.hcsSyncMsg || '';
|
||||
},
|
||||
isBlacklist() {
|
||||
return String(this.contract.hcsBlackStatus || '').toLowerCase() === 'yes';
|
||||
},
|
||||
specLabel() {
|
||||
return SPEC_STATUS_OPTIONS.find(item => item.value === this.contract.hcsSpecStatus)?.label || '';
|
||||
},
|
||||
specTagType() {
|
||||
return SPEC_STATUS_OPTIONS.find(item => item.value === this.contract.hcsSpecStatus)?.type || 'info';
|
||||
},
|
||||
isSpecProcessing() {
|
||||
return this.contract.hcsSpecStatus === 'processing';
|
||||
},
|
||||
canApplySpec() {
|
||||
return ['need', 'rejected'].includes(this.contract.hcsSpecStatus);
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
openSpecDialog() {
|
||||
this.specDialogVisible = true;
|
||||
},
|
||||
resetSpecDialog() {
|
||||
this.materials = [];
|
||||
},
|
||||
// 自定义上传:直传合同合规系统(C103-2),成功记录合规附件ID
|
||||
uploadMaterial(options) {
|
||||
const file = options.file;
|
||||
if (file.size / 1024 / 1024 > 50) {
|
||||
this.$message.warning('单文件大小不能超过 50MB');
|
||||
return;
|
||||
}
|
||||
const formData = new FormData();
|
||||
formData.append('fdFile', file);
|
||||
uploadAttachment(formData)
|
||||
.then(response => {
|
||||
const fdId = response?.data?.data || response?.data;
|
||||
if (!fdId || typeof fdId !== 'string') {
|
||||
this.$message.error(response?.data?.msg || '上传合同系统失败');
|
||||
return;
|
||||
}
|
||||
this.materials.push({ fdId, name: file.name });
|
||||
this.$message.success(`已上传合同系统:${file.name}`);
|
||||
})
|
||||
.catch(error => {
|
||||
this.$message.error(error?.response?.data?.msg || '上传合同系统失败');
|
||||
});
|
||||
},
|
||||
uploadFailed() {
|
||||
this.$message.error('上传合同系统失败');
|
||||
},
|
||||
submitSpec() {
|
||||
if (!this.materials.length) {
|
||||
this.$message.warning('请先上传特批申请材料');
|
||||
return;
|
||||
}
|
||||
this.specApplying = true;
|
||||
applyContractSpec({
|
||||
contractId: this.contract.id,
|
||||
attachments: this.materials.map(item => ({ fdId: item.fdId, name: item.name })),
|
||||
})
|
||||
.then(response => {
|
||||
if (response?.data?.success === false) {
|
||||
this.$message.error(response?.data?.msg || '特批发起失败');
|
||||
return;
|
||||
}
|
||||
this.$message.success(response?.data?.data || '特批发起成功,等待集团审批');
|
||||
this.specDialogVisible = false;
|
||||
this.$emit('refresh');
|
||||
})
|
||||
.catch(error => {
|
||||
this.$message.error(error?.response?.data?.msg || '特批发起失败');
|
||||
})
|
||||
.finally(() => {
|
||||
this.specApplying = false;
|
||||
});
|
||||
},
|
||||
confirmSpecResult() {
|
||||
this.$prompt('请输入集团特批结果(approved=通过 / rejected=驳回)', '确认特批结果', {
|
||||
confirmButtonText: '确定',
|
||||
cancelButtonText: '取消',
|
||||
inputPattern: /^(approved|rejected)$/,
|
||||
inputErrorMessage: '请输入 approved 或 rejected',
|
||||
}).then(({ value }) => {
|
||||
confirmContractSpec(this.contract.id, value).then(() => {
|
||||
this.$message.success('特批结果已确认');
|
||||
this.$emit('refresh');
|
||||
});
|
||||
});
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.hcs-contract-panel__risk {
|
||||
margin-left: 8px;
|
||||
color: #606266;
|
||||
}
|
||||
|
||||
.hcs-contract-panel__spec-alert {
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
.hcs-contract-panel__spec-upload {
|
||||
margin-bottom: 12px;
|
||||
|
||||
:deep(.el-upload) {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
:deep(.el-upload-dragger) {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
.hcs-contract-panel__spec-table {
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
</style>
|
||||
@@ -332,7 +332,7 @@
|
||||
|
||||
<script>
|
||||
import * as api from '@/api/business/contract-manage';
|
||||
import { submitMkApprovalFlow } from '@/utils/mk-approval';
|
||||
import { loadMkEnabled, submitMkApprovalFlow } from '@/utils/mk-approval';
|
||||
import BillingPlanEditor from './components/billing-plan-editor.vue';
|
||||
import ContractAttachmentSection, {
|
||||
isContractSealMaterialLocation,
|
||||
@@ -738,7 +738,7 @@ export default {
|
||||
removeCustomPeriodRow(index) { if (index <= 0) return; const periods = [...(this.settlementRule.customPeriods || [])]; periods.splice(index, 1); this.settlementRule.customPeriods = normalizeCustomPeriods(periods); },
|
||||
validateCustomPeriods(periods = [], label) { const rows = normalizeCustomPeriods(periods); if (!rows.length) { this.$message.warning(`${label}:请至少配置一段自定义周期`); return false; } for (let index = 0; index < rows.length; index += 1) { const row = rows[index]; const startDay = Number(row.startDay); const endDay = Number(row.endDay); if (!Number.isFinite(startDay) || startDay < 1 || startDay > 31) { this.$message.warning(`${label}:请选择第${index + 1}行运单区间开始日`); return false; } if (!Number.isFinite(endDay) || endDay < 1 || endDay > 31) { this.$message.warning(`${label}:请选择第${index + 1}行运单区间结束日`); return false; } if (endDay < startDay) { this.$message.warning(`${label}:第${index + 1}行结束日不能早于开始日`); return false; } if (index > 0 && startDay !== Number(rows[index - 1].endDay) + 1) { this.$message.warning(`${label}:自定义多周期区间必须连续,不允许重叠或存在日期缺口`); return false; } } return true; },
|
||||
validateSettlementRule(rule, label) { if (Number(rule.autoGenerate) !== 1) return true; if (!rule.billStartDate || !rule.settlementType) { this.$message.warning(`${label}:请完整填写账单起始日和结算类型`); return false; } if (rule.settlementType === '月结' && !rule.billCycleType) { this.$message.warning(`${label}:请选择结算周期`); return false; } if (rule.settlementType === '月结' && rule.billCycleType === '固定截单日' && !rule.billCutoffDay) { this.$message.warning(`${label}:请选择账单截单日`); return false; } if (rule.settlementType === '月结' && rule.billCycleType === '自定义多周期') return this.validateCustomPeriods(rule.customPeriods, label); if (rule.settlementType === '固定天数周期结算' && !rule.cycleDays) { this.$message.warning(`${label}:请选择周期天数`); return false; } return true; },
|
||||
async submit() { await this.$refs.formRef.validate(); const total = this.paymentRatioRows.reduce((sum, row) => sum + Number(row.ratioLimit || 0), 0); if (this.paymentRatioRows.length && Math.abs(total - 100) > 0.0001) { this.$message.warning('付款比例上限合计必须等于100%'); return; } if (this.settlementConfigTab === 'pre') this.preSettlementConfig = { ...this.settlementRule }; else this.formalSettlementConfig = { ...this.settlementRule }; if (!this.validateSettlementRule(this.preSettlementConfig, '预结算配置') || !this.validateSettlementRule(this.formalSettlementConfig, '正式结算配置')) return; const settlementRule = { preSettlementConfig: this.preSettlementConfig, formalSettlementConfig: this.formalSettlementConfig }; await api.submitChange({ ...this.form, settlementCurrency: String(this.form.settlementCurrency || '').trim() || 'RMB', copyCount: normalizeOptionalPositiveInteger(this.form.copyCount), 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', bizId: 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', bizId: this.form.id, subjectName: this.form.contractName || '', approvalStatus: this.form.approvalStatus || 'change_rejected', mkEnabled: await loadMkEnabled().catch(() => false) }); this.$message.success('变更已提交'); this.$router.back(); },
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
@@ -32,14 +32,6 @@
|
||||
>
|
||||
批量导出
|
||||
</el-button>
|
||||
<el-button
|
||||
type="warning"
|
||||
plain
|
||||
:loading="hcsRepushingId === 'batch'"
|
||||
@click="handleHcsBatchRepush"
|
||||
>
|
||||
<el-icon class="el-icon--left"><Position /></el-icon>批量推送合规
|
||||
</el-button>
|
||||
</template>
|
||||
|
||||
<template #header>
|
||||
@@ -80,13 +72,6 @@
|
||||
</el-tag>
|
||||
</template>
|
||||
|
||||
<template #hcsSyncStatus="{ row }">
|
||||
<el-tag v-if="row.hcsSyncStatus" :type="hcsSyncTagType(row.hcsSyncStatus)" class="status-text">
|
||||
{{ hcsSyncLabel(row.hcsSyncStatus) }}
|
||||
</el-tag>
|
||||
<span v-else>-</span>
|
||||
</template>
|
||||
|
||||
<template #menu="{ row }">
|
||||
<el-link
|
||||
v-if="hasPermission(`${config.permission}_view`)"
|
||||
@@ -107,9 +92,6 @@
|
||||
>
|
||||
{{ operation.label }}
|
||||
</el-link>
|
||||
<el-link v-if="canHcsRepush(row)" type="warning" @click="handleHcsRepush(row)">
|
||||
{{ hcsRepushingId === row.id ? '推送中…' : '推送合规' }}
|
||||
</el-link>
|
||||
<el-link v-if="canDelete(row)" type="danger" @click="removeRow(row)">删除</el-link>
|
||||
</template>
|
||||
</avue-crud>
|
||||
@@ -198,18 +180,9 @@
|
||||
<el-descriptions-item label="其他事项说明" :span="3">{{
|
||||
detailValue('remark')
|
||||
}}</el-descriptions-item>
|
||||
<el-descriptions-item v-if="detailRiskTipText" label="风险客商提示" :span="3">
|
||||
<span style="color: #f56c6c">{{ detailRiskTipText }}</span>
|
||||
</el-descriptions-item>
|
||||
</el-descriptions>
|
||||
</section>
|
||||
|
||||
<hcs-contract-panel
|
||||
v-if="!isPublicViewPage"
|
||||
:contract="detailRow"
|
||||
@refresh="initDetailPage"
|
||||
/>
|
||||
|
||||
<contract-attachment-section
|
||||
title="用印材料"
|
||||
description
|
||||
@@ -475,7 +448,6 @@
|
||||
clearable
|
||||
:loading="contractPartyLoading"
|
||||
@visible-change="visible => visible && loadContractPartyOptions()"
|
||||
@change="queryPartyRisk('partyA')"
|
||||
>
|
||||
<el-option
|
||||
v-for="item in contractPartyAOptions"
|
||||
@@ -493,7 +465,6 @@
|
||||
clearable
|
||||
:loading="contractPartyLoading"
|
||||
@visible-change="visible => visible && loadContractPartyOptions()"
|
||||
@change="queryPartyRisk('partyB')"
|
||||
>
|
||||
<el-option
|
||||
v-for="item in contractPartyBOptions"
|
||||
@@ -648,11 +619,6 @@
|
||||
placeholder="请输入其他事项说明"
|
||||
/>
|
||||
</el-form-item>
|
||||
<!-- 风险客商提示(基本信息备注后):外部客商高风险/黑名单时展示,文案由后端 riskTip 拼装 -->
|
||||
<div v-if="partyRiskText" class="contract-manage-form__risk-tip">
|
||||
<div class="contract-manage-form__risk-tip-title">风险客商提示:</div>
|
||||
<div class="contract-manage-form__risk-tip-content">{{ partyRiskText }}</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<contract-attachment-section
|
||||
@@ -1080,8 +1046,6 @@ import NProgress from 'nprogress';
|
||||
import 'nprogress/nprogress.css';
|
||||
import { exportBlob } from '@/api/common';
|
||||
import * as api from '@/api/business/contract-manage';
|
||||
import { repush as hcsRepush, repushBatch as hcsRepushBatch, getRisk } from '@/api/business/hcs';
|
||||
import { Position } from '@element-plus/icons-vue';
|
||||
import { getMkPublicDetail } from '@/api/mk-process';
|
||||
import { getList as getProjectList } from '@/api/business/project-apply';
|
||||
import { getList as getCustomerArchiveList } from '@/api/vehicle/customer-archive';
|
||||
@@ -1102,7 +1066,6 @@ import ContractAttachmentSection, {
|
||||
isContractSupportingAttachmentLocation,
|
||||
normalizeContractFileRows,
|
||||
} from './components/contract-attachment-section.vue';
|
||||
import HcsContractPanel from './components/hcs-contract-panel.vue';
|
||||
import PdfPreview from '@/components/pdf-preview/main.vue';
|
||||
import { ElImageViewer, ElCascader } from 'element-plus';
|
||||
import { OpenFileViewer } from '@open-file-viewer/vue';
|
||||
@@ -1275,11 +1238,9 @@ export default {
|
||||
ContractAttachmentSection,
|
||||
BillingPlanEditor,
|
||||
ChangeRecordDetailDialog,
|
||||
HcsContractPanel,
|
||||
ElImageViewer,
|
||||
OpenFileViewer,
|
||||
PdfPreview,
|
||||
Position,
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
@@ -1355,9 +1316,6 @@ export default {
|
||||
contractCategoryDictOptions: [],
|
||||
contractPartyAOptions: [],
|
||||
contractPartyBOptions: [],
|
||||
// 合规客商风险(C101-1 展示用):甲方/乙方选择后即时查询
|
||||
contractPartyCodeMap: {},
|
||||
partyRisk: {},
|
||||
contractPartyLoading: false,
|
||||
contractPartyRequestId: 0,
|
||||
organizationOptions: [],
|
||||
@@ -1396,8 +1354,6 @@ export default {
|
||||
billCycleTypeOptions: ['固定截单日', '自然月', '自定义多周期'],
|
||||
detailLoading: false,
|
||||
detailRow: {},
|
||||
// 合规(HCS)推送/对账状态
|
||||
hcsRepushingId: null,
|
||||
detailContractFileRows: [],
|
||||
detailAttachmentRows: [],
|
||||
detailBillingPlanRows: [],
|
||||
@@ -1574,32 +1530,6 @@ export default {
|
||||
const endDate = this.detailRow.endDate || '-';
|
||||
return `${startDate} 至 ${endDate}`;
|
||||
},
|
||||
// 高风险/黑名单客商签约提示(C101-1,签约前提示;强制拦截在后端提交校验与推送链路)
|
||||
partyRiskText() {
|
||||
const parts = [];
|
||||
['partyA', 'partyB'].forEach(prop => {
|
||||
const risk = this.partyRisk[prop];
|
||||
if (!risk?.risky) return;
|
||||
// riskTip 由后端按风险项拼装(【公司名称】:黑名单;涉诉;被执行人等),缺失时兜底通用提示
|
||||
parts.push(
|
||||
risk.riskTip ||
|
||||
`${prop === 'partyA' ? '甲方' : '乙方'}[${this.form[prop] || ''}]为高风险/黑名单客商`
|
||||
);
|
||||
});
|
||||
if (!parts.length) return '';
|
||||
return `${parts.join(';')},按集团规定不可直接签约,提交前须上传「特批说明」类型附件,审批通过后将自动发起合同特批`;
|
||||
},
|
||||
// 详情风险客商提示(基本信息备注后展示):按推送留痕 hcs_risk_level/hcs_black_status 判定(甲乙取高/任一),文案与后端 riskTip 一致
|
||||
detailRiskTipText() {
|
||||
const riskLevel = String(this.detailRow.hcsRiskLevel || '').trim();
|
||||
const blacklist = String(this.detailRow.hcsBlackStatus || '').toLowerCase() === 'yes';
|
||||
if (riskLevel !== '2' && !blacklist) return '';
|
||||
const parties = [this.detailRow.partyA, this.detailRow.partyB]
|
||||
.map(name => String(name || '').trim())
|
||||
.filter(Boolean)
|
||||
.join('、');
|
||||
return `【${parties || '相对方'}】:被列入外部黑名单;涉贪污受贿裁判相关提及方(3年以上及其他);被执行人信息;买卖合同纠纷;劳动纠纷;重大纠纷`;
|
||||
},
|
||||
detailLegalSealText() {
|
||||
const value = this.detailRow.legalSealFlag;
|
||||
if (value === undefined || value === null || value === '') return '-';
|
||||
@@ -1721,78 +1651,6 @@ export default {
|
||||
if (stage === 'terminated') return 'danger';
|
||||
return 'info';
|
||||
},
|
||||
// ==================== 合规(HCS)推送/对账(照“同步金蝶”按钮模式) ====================
|
||||
hcsSyncLabel(value) {
|
||||
return { unsynced: '待推送', synced: '已推送', failed: '推送失败' }[value] || value;
|
||||
},
|
||||
hcsSyncTagType(value) {
|
||||
return { unsynced: 'info', synced: 'success', failed: 'danger' }[value] || 'info';
|
||||
},
|
||||
canHcsRepush(row) {
|
||||
return (
|
||||
['approved', 'change_approved'].includes(row.approvalStatus) &&
|
||||
row.hcsSyncStatus !== 'synced'
|
||||
);
|
||||
},
|
||||
handleHcsRepush(row) {
|
||||
if (this.hcsRepushingId) return;
|
||||
this.$confirm('确定推送该数据到合同合规系统?', '提示', {
|
||||
confirmButtonText: '确定',
|
||||
cancelButtonText: '取消',
|
||||
type: 'warning',
|
||||
}).then(() => {
|
||||
this.hcsRepushingId = row.id;
|
||||
hcsRepush('contract', row.id)
|
||||
.then(res => {
|
||||
if (res.data?.success === false) {
|
||||
this.$message.error(res.data?.msg || '推送合规失败');
|
||||
return;
|
||||
}
|
||||
this.$message.success(res.data?.data || '推送合规成功');
|
||||
this.onLoad(this.page, this.query);
|
||||
})
|
||||
.catch(error => {
|
||||
this.$message.error(error?.response?.data?.msg || '推送合规失败');
|
||||
})
|
||||
.finally(() => {
|
||||
this.hcsRepushingId = null;
|
||||
});
|
||||
});
|
||||
},
|
||||
handleHcsBatchRepush() {
|
||||
if (this.hcsRepushingId) return;
|
||||
if (!this.selectionList.length) {
|
||||
this.$message.warning('请选择至少一条数据');
|
||||
return;
|
||||
}
|
||||
const invalidCount = this.selectionList.filter(item => !this.canHcsRepush(item)).length;
|
||||
if (invalidCount) {
|
||||
this.$message.warning(`选中数据中有${invalidCount}条不符合推送条件(仅审批通过且未同步的可推送),请重新选择`);
|
||||
return;
|
||||
}
|
||||
this.$confirm(`确定将选中的${this.selectionList.length}条数据推送到合同合规系统?`, '提示', {
|
||||
confirmButtonText: '确定',
|
||||
cancelButtonText: '取消',
|
||||
type: 'warning',
|
||||
}).then(() => {
|
||||
this.hcsRepushingId = 'batch';
|
||||
hcsRepushBatch('contract', this.selectionList.map(item => item.id))
|
||||
.then(res => {
|
||||
if (res.data?.success === false) {
|
||||
this.$message.error(res.data?.msg || '批量推送合规失败');
|
||||
return;
|
||||
}
|
||||
this.$message.success(res.data?.data || '批量推送合规完成');
|
||||
this.onLoad(this.page, this.query);
|
||||
})
|
||||
.catch(error => {
|
||||
this.$message.error(error?.response?.data?.msg || '批量推送合规失败');
|
||||
})
|
||||
.finally(() => {
|
||||
this.hcsRepushingId = null;
|
||||
});
|
||||
});
|
||||
},
|
||||
canEdit(row) {
|
||||
return (
|
||||
this.hasPermission(`${this.config.permission}_edit`) &&
|
||||
@@ -2128,7 +1986,9 @@ export default {
|
||||
bizType: 'contract-manage',
|
||||
bizId: id,
|
||||
subjectName: saved.contractName || submit.contractName || '',
|
||||
approvalStatus: previousApprovalStatus || saved.approvalStatus || submit.approvalStatus || '',
|
||||
approvalStatus:
|
||||
previousApprovalStatus || saved.approvalStatus || submit.approvalStatus || '',
|
||||
mkEnabled: this.mkEnabled,
|
||||
})
|
||||
);
|
||||
}
|
||||
@@ -2167,6 +2027,7 @@ export default {
|
||||
bizId: payload.id || this.form.id,
|
||||
subjectName: payload.contractName || this.form.contractName || '',
|
||||
approvalStatus: this.form.approvalStatus || 'change_rejected',
|
||||
mkEnabled: this.mkEnabled,
|
||||
}).then(() => {
|
||||
this.$message.success('已重新提交变更审批');
|
||||
this.closeForm();
|
||||
@@ -2351,21 +2212,8 @@ export default {
|
||||
])
|
||||
.then(responses => {
|
||||
if (requestId !== this.contractPartyRequestId) return;
|
||||
const records = responses.flatMap(extractRecords);
|
||||
// 名称→客商编码映射,供 C101-1 风险查询使用(§9-U6 编码体系联调确认)
|
||||
const codeMap = {};
|
||||
records.forEach(item => {
|
||||
const code = item.customerCode;
|
||||
if (!code) return;
|
||||
[item.fullName, item.shortName, item.customerName]
|
||||
.map(name => String(name || '').trim())
|
||||
.filter(Boolean)
|
||||
.forEach(name => {
|
||||
codeMap[name] = code;
|
||||
});
|
||||
});
|
||||
this.contractPartyCodeMap = codeMap;
|
||||
const names = records
|
||||
const names = responses
|
||||
.flatMap(extractRecords)
|
||||
.map(item => item.fullName || item.shortName || item.customerName || item.customerCode)
|
||||
.map(name => String(name || '').trim())
|
||||
.filter(Boolean);
|
||||
@@ -2378,31 +2226,6 @@ export default {
|
||||
if (requestId === this.contractPartyRequestId) this.contractPartyLoading = false;
|
||||
});
|
||||
},
|
||||
// C101-1 客商风险查询(展示用,强校验在后端提交/推送链路):失败静默不阻塞填写
|
||||
queryPartyRisk(prop) {
|
||||
const name = this.form[prop];
|
||||
const code = name ? this.contractPartyCodeMap[name] : null;
|
||||
if (!code) {
|
||||
this.partyRisk = { ...this.partyRisk, [prop]: null };
|
||||
return;
|
||||
}
|
||||
getRisk(code)
|
||||
.then(res => {
|
||||
const risk = res?.data?.data ?? res?.data ?? {};
|
||||
this.partyRisk = {
|
||||
...this.partyRisk,
|
||||
[prop]: {
|
||||
customerKind: risk.customerKind || '',
|
||||
// risky 由后端统一判定:外部客商高风险(等级=2)或黑名单;内部组织客商默认无风险恒为 false
|
||||
risky: risk.risky === true,
|
||||
riskTip: risk.riskTip || '',
|
||||
},
|
||||
};
|
||||
})
|
||||
.catch(() => {
|
||||
this.partyRisk = { ...this.partyRisk, [prop]: null };
|
||||
});
|
||||
},
|
||||
loadOrganizationOptions() {
|
||||
if (this.organizationLoading) return;
|
||||
this.organizationLoading = true;
|
||||
@@ -3181,6 +3004,7 @@ export default {
|
||||
bizId: row.id,
|
||||
subjectName: row.contractName || '',
|
||||
approvalStatus: row.approvalStatus || '',
|
||||
mkEnabled: this.mkEnabled,
|
||||
})
|
||||
)
|
||||
: request();
|
||||
@@ -3284,29 +3108,6 @@ export default {
|
||||
vertical-align: bottom;
|
||||
}
|
||||
|
||||
.contract-manage-form__risk-alert {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
// 风险客商提示条(基本信息备注后):橙黄背景 + 红色文字(设计稿 2026-10)
|
||||
.contract-manage-form__risk-tip {
|
||||
margin: 0 0 18px;
|
||||
padding: 10px 16px;
|
||||
background: #faecd8;
|
||||
border-radius: 4px;
|
||||
|
||||
.contract-manage-form__risk-tip-title {
|
||||
color: #f56c6c;
|
||||
font-weight: 600;
|
||||
line-height: 22px;
|
||||
}
|
||||
|
||||
.contract-manage-form__risk-tip-content {
|
||||
color: #f56c6c;
|
||||
line-height: 22px;
|
||||
}
|
||||
}
|
||||
|
||||
.contract-manage-page {
|
||||
&__expiry-search {
|
||||
display: flex;
|
||||
@@ -3541,7 +3342,7 @@ export default {
|
||||
|
||||
:deep(.el-form-item) {
|
||||
align-items: center;
|
||||
margin-bottom: 24px;
|
||||
//margin-bottom: 24px;
|
||||
}
|
||||
|
||||
:deep(.el-form-item__label) {
|
||||
|
||||
@@ -1,100 +0,0 @@
|
||||
<template>
|
||||
<basic-container class="hcs-master-data-page">
|
||||
<div class="hcs-master-data-page__head">
|
||||
<div class="dialog-section-title">合规主数据(HCS)</div>
|
||||
<el-button type="primary" :loading="refreshing" @click="handleRefresh">刷新缓存</el-button>
|
||||
</div>
|
||||
|
||||
<el-tabs v-model="activeTab">
|
||||
<el-tab-pane label="合同模板" name="template" />
|
||||
</el-tabs>
|
||||
|
||||
<el-table v-loading="loading" :data="templates" border>
|
||||
<el-table-column type="index" label="序号" width="70" align="center" />
|
||||
<el-table-column prop="fdId" label="模板ID(fdTemplateId)" min-width="320" show-overflow-tooltip />
|
||||
<el-table-column prop="fdName" label="模板名称" min-width="220" show-overflow-tooltip />
|
||||
<el-table-column prop="fdStatus" label="状态" width="100" align="center" />
|
||||
<el-table-column prop="fdContractCharacter" label="合同性质" min-width="140" align="center" />
|
||||
<el-table-column label="创建时间" min-width="170" align="center">
|
||||
<template #default="{ row }">{{ formatTime(row.fdCreateTime) }}</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
|
||||
<el-alert
|
||||
class="hcs-master-data-page__tip"
|
||||
title="合同类别与合规模板的对应关系由 Nacos 配置 thirdParty.hcs.contract.template-mapping 维护;此列表用于核对映射的模板ID 是否存在。"
|
||||
type="info"
|
||||
:closable="false"
|
||||
/>
|
||||
</basic-container>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { getTemplates, refreshMasterData } from '@/api/business/hcs';
|
||||
|
||||
export default {
|
||||
name: 'HcsMasterData',
|
||||
data() {
|
||||
return {
|
||||
activeTab: 'template',
|
||||
loading: false,
|
||||
refreshing: false,
|
||||
templates: [],
|
||||
};
|
||||
},
|
||||
created() {
|
||||
this.loadTemplates();
|
||||
},
|
||||
methods: {
|
||||
loadTemplates() {
|
||||
this.loading = true;
|
||||
getTemplates()
|
||||
.then(response => {
|
||||
const data = response?.data?.data ?? response?.data;
|
||||
this.templates = Array.isArray(data) ? data : [];
|
||||
})
|
||||
.catch(() => {
|
||||
this.$message.error('合规模板列表加载失败');
|
||||
})
|
||||
.finally(() => {
|
||||
this.loading = false;
|
||||
});
|
||||
},
|
||||
handleRefresh() {
|
||||
this.refreshing = true;
|
||||
refreshMasterData()
|
||||
.then(response => {
|
||||
this.$message.success(response?.data?.data || '合规主数据缓存已刷新');
|
||||
this.loadTemplates();
|
||||
})
|
||||
.catch(error => {
|
||||
this.$message.error(error?.response?.data?.msg || '刷新失败');
|
||||
})
|
||||
.finally(() => {
|
||||
this.refreshing = false;
|
||||
});
|
||||
},
|
||||
formatTime(value) {
|
||||
if (!value) return '-';
|
||||
return this.$dayjs(Number(value)).format('YYYY-MM-DD HH:mm:ss');
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.hcs-master-data-page__head {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
margin-bottom: 8px;
|
||||
|
||||
.dialog-section-title {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.hcs-master-data-page__tip {
|
||||
margin-top: 12px;
|
||||
}
|
||||
</style>
|
||||
@@ -79,7 +79,7 @@
|
||||
<p>{{ timeRange(row) }}</p>
|
||||
<p>{{ goodsSummary(row) }}</p>
|
||||
<p>创建人:{{ row.createUserName || '-' }}</p>
|
||||
<p>创建时间:{{ row.createTime || '-' }}</p>
|
||||
<p>创建时间:{{ formatDate(row.createTime) }}</p>
|
||||
</section>
|
||||
<section class="route-progress">
|
||||
<div class="route-track">
|
||||
@@ -155,6 +155,7 @@
|
||||
|
||||
<script>
|
||||
import * as api from '@/api/business/master-order';
|
||||
import { splitRegionTokens } from '@/utils/region-address';
|
||||
import TableHeaderTools from '@/components/table-header-tools/main.vue';
|
||||
import MasterOrderEditor from './components/master-order-editor.vue';
|
||||
import MasterOrderDispatch from './components/master-order-dispatch.vue';
|
||||
@@ -332,6 +333,10 @@ export default {
|
||||
? `${String(row.planStartTime).slice(0, 10)} - ${String(row.planEndTime).slice(0, 10)}`
|
||||
: '-';
|
||||
},
|
||||
// 创建时间只展示日期。
|
||||
formatDate(value) {
|
||||
return value ? String(value).slice(0, 10) : '-';
|
||||
},
|
||||
goodsSummary(row) {
|
||||
const item = (row.goods || [])[0] || {};
|
||||
if (!item.cargoName && !item.cargoType) return '-';
|
||||
@@ -391,6 +396,26 @@ export default {
|
||||
}`;
|
||||
return `${city} ${district}`;
|
||||
},
|
||||
// 悬停气泡里的地址:起讫点若只保存了名称或详细地址的一半,气泡就会显示成
|
||||
// “柳南区”“安徽”这类残缺地址,故把两个字段互为补充后展示。
|
||||
completeAddress(name, address) {
|
||||
const rawName = String(name || '').trim();
|
||||
const rawAddress = String(address || '').trim();
|
||||
if (!rawName) return rawAddress;
|
||||
if (!rawAddress) return rawName;
|
||||
// 详细地址已含省市,本身就是完整地址,不再拼接名称,避免重复卡片上已有的站点名。
|
||||
if (/(?:省|自治区|特别行政区|市)/.test(rawAddress)) return rawAddress;
|
||||
const normalizedName = rawName.replace(/\s+/g, '');
|
||||
const normalizedAddress = rawAddress.replace(/\s+/g, '');
|
||||
if (normalizedAddress.includes(normalizedName)) return rawAddress;
|
||||
if (normalizedName.includes(normalizedAddress)) return rawName;
|
||||
// “珠海1号港口”与“珠海港口1号”这类同字异序写法只保留一个。
|
||||
if ([...normalizedName].sort().join('') === [...normalizedAddress].sort().join('')) {
|
||||
return normalizedName.length >= normalizedAddress.length ? rawName : rawAddress;
|
||||
}
|
||||
// 区域在前、详细地址在后。
|
||||
return splitRegionTokens(rawName) ? `${rawName}${rawAddress}` : `${rawAddress}${rawName}`;
|
||||
},
|
||||
routeNodeName(value, address, transportType, region = {}) {
|
||||
const text = String(value || '').trim();
|
||||
if (!text) return '-';
|
||||
@@ -457,15 +482,12 @@ export default {
|
||||
if (this.isStationLikeName(rawAddress) && !this.isStationLikeName(rawName)) {
|
||||
return {
|
||||
name: rawAddress,
|
||||
address: rawName || regionText || rawAddress,
|
||||
address: this.completeAddress(rawName, rawAddress) || regionText || rawAddress,
|
||||
};
|
||||
}
|
||||
const displayName = rawName || rawAddress || '-';
|
||||
const tipAddress =
|
||||
(rawAddress && rawAddress !== displayName ? rawAddress : '') ||
|
||||
regionText ||
|
||||
rawAddress ||
|
||||
displayName;
|
||||
this.completeAddress(rawName, rawAddress) || regionText || rawAddress || displayName;
|
||||
return { name: displayName, address: tipAddress };
|
||||
}
|
||||
return {
|
||||
@@ -475,7 +497,7 @@ export default {
|
||||
siteCode,
|
||||
nextTransportType,
|
||||
}),
|
||||
address: rawAddress || rawName || regionText || '-',
|
||||
address: this.completeAddress(rawName, rawAddress) || regionText || '-',
|
||||
};
|
||||
},
|
||||
routeNodes(row = {}) {
|
||||
|
||||
@@ -416,6 +416,7 @@ import { getList as getProjectList } from '@/api/business/project-apply';
|
||||
import { getDeptTree } from '@/api/system/dept';
|
||||
import { exportBlob } from '@/api/common';
|
||||
import { config, option } from '@/option/business/process-config';
|
||||
import { applyIncomingSearch } from '@/utils/search-range';
|
||||
import { getToken, isAdministrator } from '@/utils/auth';
|
||||
import { downloadXls } from '@/utils/util';
|
||||
import { mapGetters } from 'vuex';
|
||||
@@ -603,7 +604,8 @@ export default {
|
||||
});
|
||||
},
|
||||
normalizeListQuery(params = {}) {
|
||||
const query = { ...params, ...this.query };
|
||||
// 新条件覆盖旧条件,清空的筛选项不再沿用上一次的值。
|
||||
const query = applyIncomingSearch(this.query, params);
|
||||
const rawDeptId = query.deptId ?? query.searchDeptId;
|
||||
delete query.searchDeptId;
|
||||
if (rawDeptId !== undefined && rawDeptId !== null && String(rawDeptId).trim() !== '') {
|
||||
|
||||
@@ -52,32 +52,12 @@
|
||||
>
|
||||
批量删除
|
||||
</el-button>
|
||||
<el-button
|
||||
type="warning"
|
||||
plain
|
||||
v-if="!readonlyScope"
|
||||
:loading="hcsRepushingId === 'batch'"
|
||||
@click="handleHcsBatchRepush"
|
||||
>
|
||||
<el-icon class="el-icon--left"><Position /></el-icon>批量推送合规
|
||||
</el-button>
|
||||
</template>
|
||||
|
||||
<template #approvalStatus="{ row }">
|
||||
{{ displayStatus(row, 'approvalStatus') }}
|
||||
</template>
|
||||
|
||||
<template #hcsSyncStatus="{ row }">
|
||||
<el-tag
|
||||
v-if="row.hcsSyncStatus"
|
||||
:type="hcsSyncTagType(row.hcsSyncStatus)"
|
||||
class="status-text"
|
||||
>
|
||||
{{ hcsSyncLabel(row.hcsSyncStatus) }}
|
||||
</el-tag>
|
||||
<span v-else>-</span>
|
||||
</template>
|
||||
|
||||
<template #header>
|
||||
<div class="project-apply-page__risk-search">
|
||||
<span class="project-apply-page__risk-label">
|
||||
@@ -148,13 +128,6 @@
|
||||
>
|
||||
{{ operation.label }}
|
||||
</el-link>
|
||||
<el-link
|
||||
v-if="canHcsRepush(row)"
|
||||
type="warning"
|
||||
@click="handleHcsRepush(row)"
|
||||
>
|
||||
{{ hcsRepushingId === row.id ? '推送中…' : '推送合规' }}
|
||||
</el-link>
|
||||
<el-link type="danger" v-if="canDelete(row)" @click="rowDel(row)"> 删除 </el-link>
|
||||
</template>
|
||||
</avue-crud>
|
||||
@@ -943,8 +916,6 @@
|
||||
import { exportBlob } from '@/api/common';
|
||||
import { defineAsyncComponent } from 'vue';
|
||||
import * as api from '@/api/business/project-apply';
|
||||
import { repush as hcsRepush, repushBatch as hcsRepushBatch } from '@/api/business/hcs';
|
||||
import { Position } from '@element-plus/icons-vue';
|
||||
import { getMkPublicDetail } from '@/api/mk-process';
|
||||
import {
|
||||
getList as getCustomerArchiveList,
|
||||
@@ -1089,7 +1060,6 @@ export default {
|
||||
ElImageViewer,
|
||||
OpenFileViewer,
|
||||
PdfPreview,
|
||||
Position,
|
||||
},
|
||||
data() {
|
||||
const validateAmount = (rule, value, callback) => {
|
||||
@@ -1151,8 +1121,6 @@ export default {
|
||||
{ label: '高风险', value: 'high', statKey: 'high' },
|
||||
{ label: '中风险', value: 'medium', statKey: 'medium' },
|
||||
],
|
||||
// 合规(HCS)推送状态
|
||||
hcsRepushingId: null,
|
||||
page: {
|
||||
pageSize: 10,
|
||||
pageSizes: [10, 20, 50, 100],
|
||||
@@ -1644,79 +1612,6 @@ export default {
|
||||
return this.canOperation(operation, row);
|
||||
});
|
||||
},
|
||||
// ==================== 合规(HCS)推送/对账(照“同步金蝶”按钮模式) ====================
|
||||
hcsSyncLabel(value) {
|
||||
return { unsynced: '待推送', synced: '已推送', failed: '推送失败' }[value] || value;
|
||||
},
|
||||
hcsSyncTagType(value) {
|
||||
return { unsynced: 'info', synced: 'success', failed: 'danger' }[value] || 'info';
|
||||
},
|
||||
canHcsRepush(row) {
|
||||
return (
|
||||
!this.readonlyScope &&
|
||||
['approved', 'change_approved'].includes(row.approvalStatus) &&
|
||||
row.hcsSyncStatus !== 'synced'
|
||||
);
|
||||
},
|
||||
handleHcsRepush(row) {
|
||||
if (this.hcsRepushingId) return;
|
||||
this.$confirm('确定推送该数据到合同合规系统?', '提示', {
|
||||
confirmButtonText: '确定',
|
||||
cancelButtonText: '取消',
|
||||
type: 'warning',
|
||||
}).then(() => {
|
||||
this.hcsRepushingId = row.id;
|
||||
hcsRepush('project', row.id)
|
||||
.then(res => {
|
||||
if (res.data?.success === false) {
|
||||
this.$message.error(res.data?.msg || '推送合规失败');
|
||||
return;
|
||||
}
|
||||
this.$message.success(res.data?.data || '推送合规成功');
|
||||
this.onLoad(this.page, this.query);
|
||||
})
|
||||
.catch(error => {
|
||||
this.$message.error(error?.response?.data?.msg || '推送合规失败');
|
||||
})
|
||||
.finally(() => {
|
||||
this.hcsRepushingId = null;
|
||||
});
|
||||
});
|
||||
},
|
||||
handleHcsBatchRepush() {
|
||||
if (this.hcsRepushingId) return;
|
||||
if (!this.selectionList.length) {
|
||||
this.$message.warning('请选择至少一条数据');
|
||||
return;
|
||||
}
|
||||
const invalidCount = this.selectionList.filter(item => !this.canHcsRepush(item)).length;
|
||||
if (invalidCount) {
|
||||
this.$message.warning(`选中数据中有${invalidCount}条不符合推送条件(仅审批通过且未同步的可推送),请重新选择`);
|
||||
return;
|
||||
}
|
||||
this.$confirm(`确定将选中的${this.selectionList.length}条数据推送到合同合规系统?`, '提示', {
|
||||
confirmButtonText: '确定',
|
||||
cancelButtonText: '取消',
|
||||
type: 'warning',
|
||||
}).then(() => {
|
||||
this.hcsRepushingId = 'batch';
|
||||
hcsRepushBatch('project', this.selectionList.map(item => item.id))
|
||||
.then(res => {
|
||||
if (res.data?.success === false) {
|
||||
this.$message.error(res.data?.msg || '批量推送合规失败');
|
||||
return;
|
||||
}
|
||||
this.$message.success(res.data?.data || '批量推送合规完成');
|
||||
this.onLoad(this.page, this.query);
|
||||
})
|
||||
.catch(error => {
|
||||
this.$message.error(error?.response?.data?.msg || '批量推送合规失败');
|
||||
})
|
||||
.finally(() => {
|
||||
this.hcsRepushingId = null;
|
||||
});
|
||||
});
|
||||
},
|
||||
canOperation(operation, row) {
|
||||
const permission = operation.permission || `${this.config.permission}_${operation.action}`;
|
||||
if (!this.hasPermission(permission) || this.readonlyScope || row.readonly) return false;
|
||||
@@ -1855,6 +1750,7 @@ export default {
|
||||
bizId: row.id,
|
||||
subjectName: row.projectName || '',
|
||||
approvalStatus: row.approvalStatus || '',
|
||||
mkEnabled: this.mkEnabled,
|
||||
}).then(() => request())
|
||||
: request();
|
||||
afterMk.then(res => {
|
||||
@@ -2061,6 +1957,7 @@ export default {
|
||||
bizId: id,
|
||||
subjectName: name,
|
||||
approvalStatus: status,
|
||||
mkEnabled: this.mkEnabled,
|
||||
}).then(() => {
|
||||
this.$message.success('提交成功!');
|
||||
this.closeProjectForm();
|
||||
@@ -2132,6 +2029,7 @@ export default {
|
||||
bizId: id,
|
||||
subjectName: this.form.projectName || '',
|
||||
approvalStatus: this.form.approvalStatus || 'change_rejected',
|
||||
mkEnabled: this.mkEnabled,
|
||||
}).then(() => {
|
||||
this.$message.success('提交成功!');
|
||||
this.closeProjectForm();
|
||||
@@ -2608,8 +2506,9 @@ export default {
|
||||
const list = res.data.data || [];
|
||||
this.platformCompanyOptions = list.map(item => ({
|
||||
label: item.deptName || item.fullName || item.title || item.name,
|
||||
value: item.id,
|
||||
}));
|
||||
// 雪花 ID 必须以字符串绑定,避免 JS Number 精度丢失导致多选提交异常
|
||||
value: item.id == null || item.id === '' ? '' : String(item.id),
|
||||
})).filter(item => item.value);
|
||||
this.ensureCurrentPlatformOption();
|
||||
});
|
||||
},
|
||||
|
||||
@@ -140,7 +140,7 @@ import * as api from '@/api/payment/billPayment';
|
||||
import * as billLedgerApi from '@/api/payment/billLedger';
|
||||
import VehicleAttachmentTable from '@/components/vehicle-attachment-table/main.vue';
|
||||
import { getMkPublicDetail } from '@/api/mk-process';
|
||||
import { submitMkApprovalFlow } from '@/utils/mk-approval';
|
||||
import { loadMkEnabled, submitMkApprovalFlow } from '@/utils/mk-approval';
|
||||
|
||||
const emptyForm = () => ({
|
||||
id: null,
|
||||
@@ -348,6 +348,7 @@ export default {
|
||||
bizId: this.form.id,
|
||||
subjectName: this.form.paymentNo || this.form.billNo || '',
|
||||
approvalStatus: previousApprovalStatus,
|
||||
mkEnabled: await loadMkEnabled().catch(() => false),
|
||||
});
|
||||
this.$message.success('提交成功');
|
||||
this.goBack();
|
||||
|
||||
@@ -132,6 +132,7 @@ export default {
|
||||
bizId: row.id,
|
||||
subjectName: row.paymentNo || row.billNo || '',
|
||||
approvalStatus: previousApprovalStatus,
|
||||
mkEnabled: this.mkEnabled,
|
||||
});
|
||||
this.$message.success('提交成功');
|
||||
this.loadTable();
|
||||
|
||||
@@ -492,7 +492,7 @@ import { getDictionary } from '@/api/system/dictbiz';
|
||||
import { invoiceApplicationDetailColumns } from '@/option/payment/invoiceApplication';
|
||||
import VehicleAttachmentTable from '@/components/vehicle-attachment-table/main.vue';
|
||||
import { getMkPublicDetail } from '@/api/mk-process';
|
||||
import { submitMkApprovalFlow } from '@/utils/mk-approval';
|
||||
import { loadMkEnabled, submitMkApprovalFlow } from '@/utils/mk-approval';
|
||||
|
||||
let localId = 0;
|
||||
const emptyLine = () => ({
|
||||
@@ -1208,6 +1208,7 @@ export default {
|
||||
bizId: this.form.id,
|
||||
subjectName: this.form.applicationNo || '',
|
||||
approvalStatus: previousApprovalStatus,
|
||||
mkEnabled: await loadMkEnabled().catch(() => false),
|
||||
});
|
||||
this.$message.success('提交成功');
|
||||
this.goBack();
|
||||
|
||||
@@ -384,6 +384,7 @@ export default {
|
||||
bizId: row.id,
|
||||
subjectName: row.applicationNo || '',
|
||||
approvalStatus: previousApprovalStatus,
|
||||
mkEnabled: this.mkEnabled,
|
||||
});
|
||||
this.$message.success('提交成功');
|
||||
this.loadTable();
|
||||
@@ -504,6 +505,16 @@ export default {
|
||||
tip: row.kingdeeLockFailReason || '系统将自动补偿重锁',
|
||||
};
|
||||
}
|
||||
// 金蝶解锁终态标识:删单解锁的源单已作废,此标签用于区分人工作废与金蝶删单
|
||||
if (row.kingdeeUnlockTime) {
|
||||
const isDelete = row.kingdeeUnlockOperationName === '金蝶删单';
|
||||
return {
|
||||
show: true,
|
||||
type: isDelete ? 'danger' : 'warning',
|
||||
label: row.kingdeeUnlockOperationName || '金蝶解锁',
|
||||
tip: `${row.kingdeeUnlockOperator || '-'} 于 ${this.$dayjs(row.kingdeeUnlockTime).format('YYYY-MM-DD HH:mm:ss')} 解锁`,
|
||||
};
|
||||
}
|
||||
return { show: false };
|
||||
},
|
||||
displayValue(value) {
|
||||
|
||||
@@ -219,7 +219,7 @@ import * as api from '@/api/payment/invoiceReceipt';
|
||||
import KingdeeInvoicePoolDialog from '@/components/kingdee-invoice-pool-dialog/main.vue';
|
||||
import VehicleAttachmentTable from '@/components/vehicle-attachment-table/main.vue';
|
||||
import { getMkPublicDetail } from '@/api/mk-process';
|
||||
import { submitMkApprovalFlow } from '@/utils/mk-approval';
|
||||
import { loadMkEnabled, submitMkApprovalFlow } from '@/utils/mk-approval';
|
||||
|
||||
const emptyForm = () => ({
|
||||
id: null,
|
||||
@@ -571,6 +571,7 @@ export default {
|
||||
bizId: this.form.id,
|
||||
subjectName: this.form.invoiceNo || '',
|
||||
approvalStatus: previousApprovalStatus,
|
||||
mkEnabled: await loadMkEnabled().catch(() => false),
|
||||
});
|
||||
this.$message.success('提交成功');
|
||||
this.goBack();
|
||||
|
||||
@@ -341,6 +341,7 @@ export default {
|
||||
bizId: row.id,
|
||||
subjectName: row.invoiceNo || '',
|
||||
approvalStatus: previousApprovalStatus,
|
||||
mkEnabled: this.mkEnabled,
|
||||
});
|
||||
this.$message.success('提交成功');
|
||||
this.loadTable();
|
||||
|
||||
@@ -36,25 +36,20 @@
|
||||
<el-col v-if="isSettlementRelatedType" :span="6"
|
||||
><el-form-item label="结算单号" prop="referenceId">
|
||||
<div class="payment-form-page__settlement-nos">
|
||||
<el-select
|
||||
ref="settlementNoSelectRef"
|
||||
:model-value="referenceSettlementNos"
|
||||
multiple
|
||||
collapse-tags
|
||||
collapse-tags-tooltip
|
||||
:max-collapse-tags="1"
|
||||
:disabled="readonly"
|
||||
placeholder="请输入"
|
||||
class="payment-form-page__settlement-select"
|
||||
@visible-change="handleSettlementSelectVisible"
|
||||
<el-tooltip
|
||||
:content="referenceLabel"
|
||||
placement="top"
|
||||
:disabled="!referenceLabel || referenceSettlementCount < 2"
|
||||
>
|
||||
<el-option
|
||||
v-for="item in referenceSettlementLinks"
|
||||
:key="`${item.id || item.settlementNo}`"
|
||||
:label="item.settlementNo"
|
||||
:value="item.settlementNo"
|
||||
<el-input
|
||||
:model-value="referenceDisplayText"
|
||||
readonly
|
||||
:disabled="readonly"
|
||||
placeholder="请输入"
|
||||
class="payment-form-page__settlement-input"
|
||||
@click="!readonly && openReference()"
|
||||
/>
|
||||
</el-select>
|
||||
</el-tooltip>
|
||||
<el-button
|
||||
v-if="!readonly"
|
||||
class="payment-form-page__settlement-select-btn"
|
||||
@@ -223,11 +218,6 @@
|
||||
show-word-limit /></el-form-item
|
||||
></el-col>
|
||||
</el-row>
|
||||
<!-- 风险客商提示(基本信息备注后,同合同新增页):收款方外部客商高风险/黑名单时展示,文案由后端 riskTip 拼装 -->
|
||||
<div v-if="payeeRiskText" class="payment-form-page__risk-tip">
|
||||
<div class="payment-form-page__risk-tip-title">风险客商提示:</div>
|
||||
<div class="payment-form-page__risk-tip-content">{{ payeeRiskText }}</div>
|
||||
</div>
|
||||
</section-card>
|
||||
<section-card v-if="isSettlementRelatedType" title="结算信息">
|
||||
<el-table :data="settlementRows" border
|
||||
@@ -674,7 +664,6 @@ import * as billLedgerApi from '@/api/payment/billLedger';
|
||||
import { getDictionary } from '@/api/system/dictbiz';
|
||||
import { getList as getProjectList } from '@/api/business/project-apply';
|
||||
import { getDetail as getContractDetail } from '@/api/business/contract-manage';
|
||||
import { getRisk as getCustomerRisk } from '@/api/business/hcs';
|
||||
import * as formalApi from '@/api/settlement/formalSettlement';
|
||||
import * as preApi from '@/api/settlement/preSettlement';
|
||||
import {
|
||||
@@ -684,7 +673,7 @@ import {
|
||||
import { readSettlementTransfer, removeSettlementTransfer } from '@/utils/settlement-transfer';
|
||||
import { formatContractOptionLabel } from '@/utils/contract';
|
||||
import { downloadFileByUrl } from '@/utils/util';
|
||||
import { submitMkApprovalFlow } from '@/utils/mk-approval';
|
||||
import { loadMkEnabled, submitMkApprovalFlow } from '@/utils/mk-approval';
|
||||
import {
|
||||
downloadKingdeeInvoiceFile,
|
||||
isKingdeePathAttachment,
|
||||
@@ -699,6 +688,9 @@ const attachmentViewerPlugins = [
|
||||
fallbackPlugin(),
|
||||
];
|
||||
|
||||
const paymentApplicationListRoute = '/payment/payment-application';
|
||||
const paymentApplicationListRefreshKey = 'payment-application-list-refresh';
|
||||
|
||||
const ATTACHMENT_MATERIALS = {
|
||||
weighingSlip: { key: 'weighingSlip', label: '磅单', keywords: ['磅单'] },
|
||||
entrustOrder: { key: 'entrustOrder', label: '委托单', keywords: ['委托单'] },
|
||||
@@ -823,11 +815,6 @@ export default {
|
||||
contractPaymentRatioLimit: null,
|
||||
contractPaymentRatioExceeded: false,
|
||||
payeeCustomerLevel: '',
|
||||
// 收款方 C101-1 风险查询留档:risky=高风险/黑名单,tip=后端拼装提示文案
|
||||
payeeRisky: false,
|
||||
payeeRiskTip: '',
|
||||
payeeRiskLevel: '',
|
||||
payeeBlackStatus: '',
|
||||
attachmentRuleLoading: false,
|
||||
attachmentImagePreviewVisible: false,
|
||||
attachmentImagePreviewUrls: [],
|
||||
@@ -963,6 +950,14 @@ export default {
|
||||
referenceSettlementNos() {
|
||||
return this.referenceSettlementLinks.map(item => item.settlementNo).filter(Boolean);
|
||||
},
|
||||
referenceSettlementCount() {
|
||||
return this.referenceSettlementNos.length;
|
||||
},
|
||||
// 结算单号展示:单张显示单号;多张仅提示数量,明细在下方「结算信息」表,悬停可看全部单号
|
||||
referenceDisplayText() {
|
||||
if (this.referenceSettlementCount > 1) return `已选 ${this.referenceSettlementCount} 张结算单`;
|
||||
return this.referenceLabel;
|
||||
},
|
||||
settlementRows() {
|
||||
if (this.referenceRows.length) {
|
||||
return this.referenceRows.map(item => ({
|
||||
@@ -991,22 +986,11 @@ export default {
|
||||
];
|
||||
},
|
||||
highRiskPayee() {
|
||||
// 收款方 C101-1 高风险/黑名单,或客商等级 D/E/F(本地评级口径),命中须上传特批附件
|
||||
if (this.payeeRisky) return true;
|
||||
const level = String(this.payeeCustomerLevel || '')
|
||||
.trim()
|
||||
.toUpperCase();
|
||||
return ['D', 'D级', 'E', 'E级', 'F', 'F级'].includes(level) || level.includes('高风险');
|
||||
},
|
||||
// 高风险/黑名单收款方提示(C101-1,同合同新增页风险客商提示口径;强制拦截在后端提交校验与金蝶推送门禁)
|
||||
payeeRiskText() {
|
||||
if (!this.payeeRisky) return '';
|
||||
const levelMap = { 0: '低风险', 1: '中风险', 2: '高风险' };
|
||||
const level = levelMap[this.payeeRiskLevel] || '未知';
|
||||
const blacklist = String(this.payeeBlackStatus || '').trim().toLowerCase() === 'yes' ? '是' : '否';
|
||||
const tip = this.payeeRiskTip || `收款方[${this.form.payeeName || ''}]为高风险/黑名单客商`;
|
||||
return `风险等级:${level}|黑名单:${blacklist}。${tip},按集团规定不可直接付款,提交前须上传「特批附件」类型附件,审批通过后将自动发起付款特批`;
|
||||
},
|
||||
requiredAttachmentMaterials() {
|
||||
if (this.form.paymentType === 'project_advance') {
|
||||
return [ATTACHMENT_MATERIALS.contract];
|
||||
@@ -1249,7 +1233,7 @@ export default {
|
||||
];
|
||||
}
|
||||
const referenceRow = this.createReferenceRow(data);
|
||||
this.referenceRows = referenceRow.settlementNo ? [referenceRow] : [];
|
||||
this.referenceRows = this.restoreReferenceRows(data, referenceRow);
|
||||
this.paymentRecords = data.paymentRecords || [];
|
||||
},
|
||||
async initialize() {
|
||||
@@ -1272,7 +1256,7 @@ export default {
|
||||
invoices: data.invoices || [],
|
||||
};
|
||||
const referenceRow = this.createReferenceRow(data);
|
||||
this.referenceRows = referenceRow.settlementNo ? [referenceRow] : [];
|
||||
this.referenceRows = this.restoreReferenceRows(data, referenceRow);
|
||||
this.paymentRecords = data.paymentRecords || [];
|
||||
if (this.form.paymentType === 'project_advance') {
|
||||
await this.loadProjectOptions();
|
||||
@@ -1490,6 +1474,33 @@ export default {
|
||||
this.$refs.formRef?.clearValidate();
|
||||
});
|
||||
},
|
||||
restoreReferenceRows(data, fallbackRow) {
|
||||
const relations = Array.isArray(data?.settlements) ? data.settlements : [];
|
||||
const rows = relations
|
||||
.map(item => {
|
||||
const isPre = item.preSettlementId !== undefined && item.preSettlementId !== null && item.preSettlementId !== '';
|
||||
const settlementNo = item.preSettlementNo || item.formalSettlementNo || '';
|
||||
if (!settlementNo) return null;
|
||||
return {
|
||||
id: isPre ? item.preSettlementId : item.formalSettlementId,
|
||||
settlementNo,
|
||||
referenceType: isPre ? 'pre' : 'formal',
|
||||
settlementAmount: Number(Number(item.settlementAmount || 0).toFixed(2)),
|
||||
payableAmount: Number(Number(item.payableAmount ?? item.settlementAmount ?? 0).toFixed(2)),
|
||||
appliedAmount: Number(Number(item.appliedAmount || 0).toFixed(2)),
|
||||
fixedAppliedAmount: this.readonly,
|
||||
};
|
||||
})
|
||||
.filter(Boolean);
|
||||
if (rows.length > 1) {
|
||||
this.form.preSettlementIds = rows.filter(item => item.referenceType === 'pre').map(item => item.id);
|
||||
this.form.settlementIds = rows.filter(item => item.referenceType === 'formal').map(item => item.id);
|
||||
if (this.form.preSettlementIds.length) this.form.preSettlementId = this.form.preSettlementIds[0];
|
||||
if (this.form.settlementIds.length) this.form.settlementId = this.form.settlementIds[0];
|
||||
return rows;
|
||||
}
|
||||
return fallbackRow?.settlementNo ? [fallbackRow] : [];
|
||||
},
|
||||
createReferenceRow(row) {
|
||||
const settlementAmount = Number(row.settlementAmount || 0);
|
||||
const appliedAmount = Number(row.advanceAppliedAmount || row.appliedPaymentAmount || 0);
|
||||
@@ -1498,11 +1509,13 @@ export default {
|
||||
id: row.preSettlementId || row.settlementId || row.id,
|
||||
settlementNo: row.preSettlementNo || row.formalSettlementNo || row.settlementNo || '',
|
||||
referenceType: isPre ? 'pre' : 'formal',
|
||||
settlementAmount,
|
||||
payableAmount:
|
||||
row.payableAmount === undefined || row.payableAmount === null
|
||||
settlementAmount: Number(Number(settlementAmount || 0).toFixed(2)),
|
||||
payableAmount: Number(
|
||||
(row.payableAmount === undefined || row.payableAmount === null
|
||||
? Math.max(0, settlementAmount - appliedAmount)
|
||||
: Number(row.payableAmount || 0),
|
||||
: Number(row.payableAmount || 0)
|
||||
).toFixed(2)
|
||||
),
|
||||
};
|
||||
},
|
||||
createInvoiceRows(invoices = [], settlementNo = '') {
|
||||
@@ -1560,9 +1573,24 @@ export default {
|
||||
}
|
||||
const first = rows[0];
|
||||
this.form.invoices = [];
|
||||
this.referenceRows = rows.map(item =>
|
||||
this.createReferenceRow({ ...item, referenceType: 'pre' })
|
||||
const paymentType =
|
||||
this.form.paymentType === 'settlement_payment' ? 'settlement_payment' : 'progress_advance';
|
||||
const amountResponses = await Promise.all(
|
||||
rows.map(item => {
|
||||
const referenceId = item.preSettlementId || item.id;
|
||||
if (!this.isValidReferenceId(referenceId)) return Promise.resolve(null);
|
||||
return api.getReferenceAmount(paymentType, referenceId, this.form.id).catch(() => null);
|
||||
})
|
||||
);
|
||||
this.referenceRows = rows.map((item, index) => {
|
||||
const amount = this.unwrapData(amountResponses[index]) || {};
|
||||
return this.createReferenceRow({
|
||||
...item,
|
||||
referenceType: 'pre',
|
||||
settlementAmount: amount.settlementAmount ?? item.settlementAmount,
|
||||
payableAmount: amount.payableAmount,
|
||||
});
|
||||
});
|
||||
const settlementAmount = this.referenceRows.reduce(
|
||||
(total, item) => total + Number(item.settlementAmount || 0),
|
||||
0
|
||||
@@ -1573,8 +1601,6 @@ export default {
|
||||
);
|
||||
const paymentRatio = Number(this.form.paymentRatio || 0);
|
||||
// 尾款付款场景下选择预结算单时保留尾款付款类型,不强制改为进度预付
|
||||
const paymentType =
|
||||
this.form.paymentType === 'settlement_payment' ? 'settlement_payment' : 'progress_advance';
|
||||
this.amountSyncing = true;
|
||||
Object.assign(this.form, {
|
||||
paymentType,
|
||||
@@ -1989,10 +2015,6 @@ export default {
|
||||
const preserveCurrentValue = autoSelectDefault === false;
|
||||
this.receiptAccountOptions = [];
|
||||
this.payeeCustomerLevel = '';
|
||||
this.payeeRisky = false;
|
||||
this.payeeRiskTip = '';
|
||||
this.payeeRiskLevel = '';
|
||||
this.payeeBlackStatus = '';
|
||||
if (!partyBName) {
|
||||
if (!preserveCurrentValue) this.clearReceiptAccount();
|
||||
return;
|
||||
@@ -2024,19 +2046,6 @@ export default {
|
||||
}
|
||||
const detail = this.unwrapData(await getCustomerArchiveDetail(customer.id));
|
||||
this.payeeCustomerLevel = detail.customerLevel || '';
|
||||
// 收款方实时查 C101-1 客商风险(后端对内部组织客商默认返回无风险):高风险/黑名单在
|
||||
// 基本信息"风险客商提示"条常驻展示并要求特批附件;查询失败不阻断填写,提交时后端强校验兜底
|
||||
if (detail.customerCode) {
|
||||
try {
|
||||
const risk = this.unwrapData(await getCustomerRisk(detail.customerCode)) || {};
|
||||
this.payeeRisky = Boolean(risk.risky);
|
||||
this.payeeRiskTip = risk.riskTip || '';
|
||||
this.payeeRiskLevel = risk.fdRiskLevel || '';
|
||||
this.payeeBlackStatus = risk.fdBlackStatus || '';
|
||||
} catch (error) {
|
||||
// 忽略风险查询异常,提交时后端 C101-1 强校验兜底
|
||||
}
|
||||
}
|
||||
this.receiptAccountOptions = (detail.receiptAccounts || [])
|
||||
.filter(item => item.accountName || item.accountHolderName || item.bankAccount)
|
||||
.map(item => ({
|
||||
@@ -2326,13 +2335,6 @@ export default {
|
||||
}
|
||||
return Promise.all(loaders);
|
||||
},
|
||||
handleSettlementSelectVisible(visible) {
|
||||
// 结算单号仅做展示,通过「选择」按钮维护,避免下拉改值
|
||||
if (!visible) return;
|
||||
this.$nextTick(() => {
|
||||
this.$refs.settlementNoSelectRef?.blur?.();
|
||||
});
|
||||
},
|
||||
handleReferenceSearch() {
|
||||
this.formalPage.current = 1;
|
||||
this.prePage.current = 1;
|
||||
@@ -2745,15 +2747,6 @@ export default {
|
||||
// 提交前静默保存,避免同时弹出「保存成功」与材料缺失 toast
|
||||
const saved = await this.saveDraft({ showSuccess: false });
|
||||
if (!saved) return;
|
||||
// 高风险/黑名单收款方必须上传特批附件(与后端提交强校验同口径)
|
||||
if (
|
||||
this.missingAttachmentMaterials.some(
|
||||
item => item.key === ATTACHMENT_MATERIALS.specialApproval.key
|
||||
)
|
||||
) {
|
||||
this.$message.warning('收款方为高风险/黑名单客商,必须上传「特批附件」类型附件后再提交审批');
|
||||
return;
|
||||
}
|
||||
const previousApprovalStatus = this.form.approvalStatus || '';
|
||||
await api.submit({ id: this.form.id });
|
||||
await submitMkApprovalFlow({
|
||||
@@ -2761,14 +2754,17 @@ export default {
|
||||
bizId: this.form.id,
|
||||
subjectName: this.form.paymentNo || '',
|
||||
approvalStatus: previousApprovalStatus,
|
||||
mkEnabled: await loadMkEnabled().catch(() => false),
|
||||
});
|
||||
this.$message.success('提交成功');
|
||||
// 通知列表页(keep-alive)重新拉取数据
|
||||
sessionStorage.setItem(paymentApplicationListRefreshKey, '1');
|
||||
this.goBack();
|
||||
},
|
||||
goBack() {
|
||||
removeSettlementTransfer(this.$route.query.transferToken);
|
||||
this.$router.$avueRouter?.closeTag?.();
|
||||
this.$router.push('/payment/payment-application');
|
||||
this.$router.push(paymentApplicationListRoute);
|
||||
},
|
||||
formatMoney(value) {
|
||||
const amount = Number(value);
|
||||
@@ -2782,26 +2778,6 @@ export default {
|
||||
.payment-form-page__form {
|
||||
padding-bottom: 72px;
|
||||
}
|
||||
|
||||
// 风险客商提示条(基本信息备注后,同合同新增页):橙黄背景 + 红色文字
|
||||
.payment-form-page__risk-tip {
|
||||
margin: 0 0 18px;
|
||||
padding: 10px 16px;
|
||||
background: #faecd8;
|
||||
border-radius: 4px;
|
||||
|
||||
.payment-form-page__risk-tip-title {
|
||||
color: #f56c6c;
|
||||
font-weight: 600;
|
||||
line-height: 22px;
|
||||
}
|
||||
|
||||
.payment-form-page__risk-tip-content {
|
||||
color: #f56c6c;
|
||||
line-height: 22px;
|
||||
}
|
||||
}
|
||||
|
||||
.payment-form-page__settlement-nos {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
@@ -2809,15 +2785,15 @@ export default {
|
||||
width: 100%;
|
||||
min-height: 32px;
|
||||
}
|
||||
.payment-form-page__settlement-select {
|
||||
.payment-form-page__settlement-input {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
}
|
||||
.payment-form-page__settlement-select :deep(.el-select__wrapper) {
|
||||
width: 100%;
|
||||
.payment-form-page__settlement-input :deep(.el-input__inner) {
|
||||
cursor: pointer;
|
||||
}
|
||||
.payment-form-page__settlement-select :deep(.el-tag__close) {
|
||||
display: none;
|
||||
.payment-form-page__settlement-input.is-disabled :deep(.el-input__inner) {
|
||||
cursor: not-allowed;
|
||||
}
|
||||
.payment-form-page__settlement-select-btn {
|
||||
flex-shrink: 0;
|
||||
|
||||
@@ -152,6 +152,9 @@ import organizationSearch from '@/mixins/organization-search';
|
||||
import { getMKApprovalUrl } from '@/api/system/business-process';
|
||||
import { loadMkEnabled, submitMkApprovalFlow } from '@/utils/mk-approval';
|
||||
|
||||
const paymentApplicationListRoute = '/payment/payment-application';
|
||||
const paymentApplicationListRefreshKey = 'payment-application-list-refresh';
|
||||
|
||||
export default {
|
||||
name: 'PaymentApplication',
|
||||
mixins: [organizationSearch],
|
||||
@@ -193,7 +196,28 @@ export default {
|
||||
this.mkEnabled = false;
|
||||
});
|
||||
},
|
||||
activated() {
|
||||
this.consumeListRefreshFlag();
|
||||
},
|
||||
watch: {
|
||||
$route(to, from) {
|
||||
if (
|
||||
to.path === paymentApplicationListRoute &&
|
||||
from?.path?.includes('/payment-application/form')
|
||||
) {
|
||||
this.consumeListRefreshFlag();
|
||||
}
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
/** 表单提交成功后带标记返回时,重新拉取列表 */
|
||||
consumeListRefreshFlag() {
|
||||
if (!sessionStorage.getItem(paymentApplicationListRefreshKey)) {
|
||||
return;
|
||||
}
|
||||
sessionStorage.removeItem(paymentApplicationListRefreshKey);
|
||||
this.onLoad(this.page);
|
||||
},
|
||||
hasPermission(code) {
|
||||
return this.permission?.[code] !== false;
|
||||
},
|
||||
@@ -296,6 +320,7 @@ export default {
|
||||
bizId: row.id,
|
||||
subjectName: row.paymentNo || '',
|
||||
approvalStatus: previousApprovalStatus,
|
||||
mkEnabled: this.mkEnabled,
|
||||
});
|
||||
this.$message.success('提交成功');
|
||||
this.refreshChange();
|
||||
@@ -441,6 +466,16 @@ export default {
|
||||
tip: row.kingdeeLockFailReason || '系统将自动补偿重锁',
|
||||
};
|
||||
}
|
||||
// 金蝶解锁终态标识:后端按 unlocked 锁记录回填(付款申请不作废,仅此标识可区分金蝶删单与推送失败)
|
||||
if (row.kingdeeUnlockTime) {
|
||||
const isDelete = row.kingdeeUnlockOperationName === '金蝶删单';
|
||||
return {
|
||||
show: true,
|
||||
type: isDelete ? 'danger' : 'warning',
|
||||
label: row.kingdeeUnlockOperationName || '金蝶解锁',
|
||||
tip: `${row.kingdeeUnlockOperator || '-'} 于 ${this.$dayjs(row.kingdeeUnlockTime).format('YYYY-MM-DD HH:mm:ss')} 解锁`,
|
||||
};
|
||||
}
|
||||
return { show: false };
|
||||
},
|
||||
displayValue(value) {
|
||||
|
||||
@@ -46,6 +46,9 @@
|
||||
<el-table-column label="金蝶未认领金额" min-width="140" align="right">
|
||||
<template #default="{ row }">{{ formatMoney(row.unclaimedAmount) }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="剩余可认领金额" min-width="140" align="right">
|
||||
<template #default="{ row }">{{ formatMoney(flowRemaining(row)) }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="summary" label="摘要" min-width="180" show-overflow-tooltip />
|
||||
</el-table>
|
||||
<el-row :gutter="32" class="receipt-claim-batch-form-page__claim-info">
|
||||
@@ -298,10 +301,21 @@ export default {
|
||||
0
|
||||
);
|
||||
},
|
||||
flowRemaining(flow) {
|
||||
return Math.max(Number(flow.unclaimedAmount || 0) - Number(flow.claimedAmount || 0), 0);
|
||||
},
|
||||
maxAllocation(row) {
|
||||
const current = Number(row.settlement.allocations?.[row.flow.id] || 0);
|
||||
const otherTotal = this.settlementAllocatedTotal(row.settlement) - current;
|
||||
return Math.max(this.settlementRemaining(row.settlement) - otherTotal, 0);
|
||||
// 流水维度:该流水其余分摊已占用部分同样挤占剩余可认领金额
|
||||
const flowOtherTotal = this.flowAllocatedTotal(row.flow.id) - current;
|
||||
return Math.max(
|
||||
Math.min(
|
||||
this.settlementRemaining(row.settlement) - otherTotal,
|
||||
this.flowRemaining(row.flow) - flowOtherTotal
|
||||
),
|
||||
0
|
||||
);
|
||||
},
|
||||
handleAllocationChange(settlement) {
|
||||
const total = this.settlementAllocatedTotal(settlement);
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
label-width="auto"
|
||||
>
|
||||
<el-row :gutter="24">
|
||||
<el-col v-for="field in fields" :key="field.prop" :span="6">
|
||||
<el-col v-for="field in visibleFields" :key="field.prop" :span="6">
|
||||
<el-form-item :label="field.label" :prop="field.prop">
|
||||
<el-date-picker
|
||||
v-if="field.type === 'date' && editable"
|
||||
@@ -64,6 +64,12 @@
|
||||
:precision="2"
|
||||
:controls="false"
|
||||
/>
|
||||
<span
|
||||
v-else-if="field.prop === 'formalSettlementNo'"
|
||||
:class="{ 'form-readonly--code': form.formalSettlementNo }"
|
||||
>
|
||||
{{ form.formalSettlementNo || (form.id ? '-' : '系统自动生成') }}
|
||||
</span>
|
||||
<span v-else-if="field.money">{{ formatMoney(form[field.prop]) }}</span>
|
||||
<span v-else-if="field.type === 'contract'">{{
|
||||
displayValue(form.contractName)
|
||||
@@ -246,24 +252,36 @@
|
||||
:index="detailIndexMethod"
|
||||
/>
|
||||
<el-table-column
|
||||
v-for="column in detailTableColumns"
|
||||
:key="column.prop"
|
||||
v-bind="column"
|
||||
v-for="item in detailDisplayColumns"
|
||||
:key="item.key"
|
||||
:label="item.kind === 'fee' ? item.name : item.column.label"
|
||||
:prop="item.kind === 'fee' ? undefined : item.column.prop"
|
||||
:min-width="item.kind === 'fee' ? 130 : item.column.minWidth"
|
||||
align="center"
|
||||
show-overflow-tooltip
|
||||
>
|
||||
<template #default="{ row }">
|
||||
<span v-if="column.money">{{ formatMoney(row[column.prop]) }}</span>
|
||||
<span v-else-if="column.prop === 'transportQuantity'">
|
||||
{{ formatDetailTransportQuantity(row[column.prop]) }}
|
||||
<span v-if="item.kind === 'fee'">
|
||||
{{ formatMoney(parseFeeItems(row.feeItemsJson || row.feeItems)[item.name]) }}
|
||||
</span>
|
||||
<span v-else-if="column.prop === 'mileage'">
|
||||
{{ formatDetailMileage(row[column.prop]) }}
|
||||
<el-link
|
||||
v-else-if="item.column.link && row[item.column.prop] && !publicMode"
|
||||
type="primary"
|
||||
@click.stop="handleDetailLink(row, item.column)"
|
||||
>
|
||||
{{ row[item.column.prop] }}
|
||||
</el-link>
|
||||
<span v-else-if="item.column.money">{{ formatMoney(row[item.column.prop]) }}</span>
|
||||
<span v-else-if="item.column.prop === 'transportQuantity'">
|
||||
{{ formatDetailTransportQuantity(row[item.column.prop]) }}
|
||||
</span>
|
||||
<span v-else-if="column.prop === 'transportType'">
|
||||
{{ transportTypeName(row[column.prop]) }}
|
||||
<span v-else-if="item.column.prop === 'mileage'">
|
||||
{{ formatDetailMileage(row[item.column.prop]) }}
|
||||
</span>
|
||||
<span v-else>{{ displayValue(row[column.prop]) }}</span>
|
||||
<span v-else-if="item.column.prop === 'transportType'">
|
||||
{{ transportTypeName(row[item.column.prop]) }}
|
||||
</span>
|
||||
<span v-else>{{ displayValue(row[item.column.prop]) }}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column v-if="editable" label="操作" width="150" fixed="right" align="center">
|
||||
@@ -378,6 +396,16 @@
|
||||
</div>
|
||||
</section-card>
|
||||
|
||||
<project-advance-write-off
|
||||
ref="projectAdvanceWriteOffRef"
|
||||
v-model="projectAdvanceWriteOffs"
|
||||
:editable="editable"
|
||||
:contract-id="form.contractId"
|
||||
:settlement-amount="form.settlementAmount"
|
||||
settlement-type="formal"
|
||||
:settlement-id="form.id"
|
||||
/>
|
||||
|
||||
<section-card title="发票信息">
|
||||
<div class="formal-editor__invoice-claim">
|
||||
<el-form inline label-position="right" label-width="auto" @submit.prevent>
|
||||
@@ -987,6 +1015,11 @@ import {
|
||||
getDetailFees as getPreSettlementDetailFees,
|
||||
} from '@/api/settlement/preSettlement';
|
||||
import { calculateAdjustedFee, getFeeDetail } from '@/api/settlement/receivable-payable-detail';
|
||||
import {
|
||||
resolveReceivablePayableDetailId,
|
||||
resolveWaybillId,
|
||||
settlementDetailPath,
|
||||
} from '@/utils/settlement-detail-link';
|
||||
import { getBfiExchangeRate } from '@/api/base/currency';
|
||||
import {
|
||||
createFormalSettlementForm,
|
||||
@@ -1004,6 +1037,7 @@ import { h } from 'vue';
|
||||
import { InfoFilled } from '@element-plus/icons-vue';
|
||||
import { mapGetters } from 'vuex';
|
||||
import { formatContractOptionLabel } from '@/utils/contract';
|
||||
import { normalizeCurrencyCode } from '@/utils/currency';
|
||||
import { downloadFileByUrl } from '@/utils/util';
|
||||
import {
|
||||
downloadKingdeeInvoiceFile,
|
||||
@@ -1011,12 +1045,16 @@ import {
|
||||
viewKingdeeInvoiceFile,
|
||||
} from '@/utils/kingdee-attachment';
|
||||
import KingdeeInvoicePoolDialog from '@/components/kingdee-invoice-pool-dialog/main.vue';
|
||||
import ProjectAdvanceWriteOff from './project-advance-write-off.vue';
|
||||
import * as XLSX from 'xlsx';
|
||||
import { isAdministrator } from '@/utils/auth';
|
||||
|
||||
// 结算币种为本位币(人民币)时汇率恒为1,汇率日期、结算汇率与按汇率换算的本位币合计都不展示
|
||||
const LOCAL_CURRENCY_HIDDEN_FIELDS = ['exchangeRateDate', 'exchangeRate', 'localSettlementAmount'];
|
||||
|
||||
export default {
|
||||
name: 'FormalSettlementEditor',
|
||||
components: { InfoFilled, KingdeeInvoicePoolDialog },
|
||||
components: { InfoFilled, KingdeeInvoicePoolDialog, ProjectAdvanceWriteOff },
|
||||
props: {
|
||||
modelValue: Boolean,
|
||||
recordId: [String, Number],
|
||||
@@ -1045,6 +1083,7 @@ export default {
|
||||
receiptClaims: [],
|
||||
adjustments: [],
|
||||
changeRecords: [],
|
||||
projectAdvanceWriteOffs: [],
|
||||
attachments: [],
|
||||
selectedAttachmentRows: [],
|
||||
invoices: [],
|
||||
@@ -1183,6 +1222,34 @@ export default {
|
||||
title() {
|
||||
return this.readonly ? '查看正式结算单' : this.recordId ? '编辑正式结算单' : '新增正式结算单';
|
||||
},
|
||||
detailFeeItemNames() {
|
||||
const names = new Set();
|
||||
this.details.forEach(row => {
|
||||
Object.keys(this.parseFeeItems(row.feeItemsJson || row.feeItems)).forEach(name =>
|
||||
names.add(name)
|
||||
);
|
||||
});
|
||||
return Array.from(names);
|
||||
},
|
||||
detailDisplayColumns() {
|
||||
const columns = this.detailTableColumns;
|
||||
const unitPriceIndex = columns.findIndex(column => column.prop === 'unitPrice');
|
||||
const beforeColumns = unitPriceIndex < 0 ? columns : columns.slice(0, unitPriceIndex + 1);
|
||||
const afterColumns = unitPriceIndex < 0 ? [] : columns.slice(unitPriceIndex + 1);
|
||||
return [
|
||||
...beforeColumns.map(column => ({ kind: 'field', key: column.prop, column })),
|
||||
...this.detailFeeItemNames.map(name => ({ kind: 'fee', key: `fee-${name}`, name })),
|
||||
...afterColumns.map(column => ({ kind: 'field', key: column.prop, column })),
|
||||
];
|
||||
},
|
||||
// 币种为空按人民币处理,与后端预结算/正式结算对空币种的默认取值一致
|
||||
isLocalCurrencySettlement() {
|
||||
return normalizeCurrencyCode(this.form.currency) === 'RMB';
|
||||
},
|
||||
visibleFields() {
|
||||
if (!this.isLocalCurrencySettlement) return this.fields;
|
||||
return this.fields.filter(field => !LOCAL_CURRENCY_HIDDEN_FIELDS.includes(field.prop));
|
||||
},
|
||||
summaryTotal() {
|
||||
return this.summaryFees.reduce((total, row) => total + Number(row.settlementAmount || 0), 0);
|
||||
},
|
||||
@@ -1275,6 +1342,13 @@ export default {
|
||||
}
|
||||
this.adjustments = data.adjustments || [];
|
||||
this.changeRecords = data.changeRecords || [];
|
||||
this.projectAdvanceWriteOffs = (data.projectAdvanceWriteOffs || []).map(row => ({
|
||||
...row,
|
||||
writeOffAmount: Number(row.writeOffAmount || 0),
|
||||
projectAdvanceAmount: Number(row.projectAdvanceAmount || 0),
|
||||
availableWriteOffAmount: Number(row.availableWriteOffAmount || 0),
|
||||
createTime: row.paymentCreateTime || row.createTime,
|
||||
}));
|
||||
this.attachments = this.parseAttachments(data.attachmentsJson);
|
||||
this.selectedAttachmentRows = [];
|
||||
this.invoices = data.invoices || [];
|
||||
@@ -1305,6 +1379,7 @@ export default {
|
||||
this.receiptClaims = [];
|
||||
this.adjustments = [];
|
||||
this.changeRecords = [];
|
||||
this.projectAdvanceWriteOffs = [];
|
||||
this.attachments = [];
|
||||
this.selectedAttachmentRows = [];
|
||||
this.invoices = [];
|
||||
@@ -2031,6 +2106,21 @@ export default {
|
||||
this.appliedDetailQuery = { ...this.detailQuery };
|
||||
this.detailPage.current = 1;
|
||||
},
|
||||
// 结算明细的「单据号」跳到该应收应付单据在应收明细/应付明细页的详情,「运单号」跳到运单详情
|
||||
async handleDetailLink(row, column) {
|
||||
if (column.prop === 'documentNo') {
|
||||
const settlementType = row.settlementType || this.form.settlementType || 'payable';
|
||||
const detailId = await resolveReceivablePayableDetailId(row, settlementType);
|
||||
if (!detailId) return this.$message.info('当前记录未找到对应的应收/应付明细');
|
||||
this.$router.push({ path: settlementDetailPath(settlementType), query: { detailId } });
|
||||
return;
|
||||
}
|
||||
if (column.prop === 'waybillNo') {
|
||||
const waybillId = await resolveWaybillId(row);
|
||||
if (!waybillId) return this.$message.info('当前记录未找到对应的运单详情');
|
||||
this.$router.push({ path: '/business/waybill-manage/detail', query: { id: waybillId } });
|
||||
}
|
||||
},
|
||||
resetDetailQuery(apply = true) {
|
||||
this.detailQuery = { documentNo: '', waybillNo: '', batchNo: '', cargoName: '' };
|
||||
if (apply) this.handleDetailQuery();
|
||||
@@ -2549,6 +2639,7 @@ export default {
|
||||
matchedAmount: Number(row.matchedAmount || 0),
|
||||
attachmentJson: row.attachmentJson || '',
|
||||
})),
|
||||
projectAdvanceWriteOffs: this.$refs.projectAdvanceWriteOffRef?.toSavePayload?.() || [],
|
||||
remark: this.form.remark,
|
||||
};
|
||||
},
|
||||
@@ -2557,6 +2648,9 @@ export default {
|
||||
},
|
||||
async handleDraftSave() {
|
||||
// 保存草稿:不做表单校验,直接落草稿并返回列表刷新
|
||||
if (this.$refs.projectAdvanceWriteOffRef?.validateBeforeSave?.() === false) {
|
||||
return;
|
||||
}
|
||||
this.draftSaving = true;
|
||||
try {
|
||||
await save({
|
||||
@@ -2584,6 +2678,9 @@ export default {
|
||||
return this.$message.warning('请完善手工费用的费用类型和费用项');
|
||||
}
|
||||
if (!this.validateInvoices()) return;
|
||||
if (this.$refs.projectAdvanceWriteOffRef?.validateBeforeSave?.() === false) {
|
||||
return;
|
||||
}
|
||||
// 附件类型仅做提示,不阻断正式结算单新增/保存提交。
|
||||
// this.validateAttachments();
|
||||
this.saving = true;
|
||||
@@ -2864,6 +2961,9 @@ export default {
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.form-readonly--code {
|
||||
color: var(--el-color-primary);
|
||||
}
|
||||
.formal-editor--page {
|
||||
padding-bottom: 72px;
|
||||
}
|
||||
|
||||
@@ -295,6 +295,16 @@ export default {
|
||||
tip: row.kingdeeLockFailReason || '系统将自动补偿重锁',
|
||||
};
|
||||
}
|
||||
// 金蝶解锁终态标识:删单解锁的源单已作废,此标签用于区分人工作废与金蝶删单
|
||||
if (row.kingdeeUnlockTime) {
|
||||
const isDelete = row.kingdeeUnlockOperationName === '金蝶删单';
|
||||
return {
|
||||
show: true,
|
||||
type: isDelete ? 'danger' : 'warning',
|
||||
label: row.kingdeeUnlockOperationName || '金蝶解锁',
|
||||
tip: `${row.kingdeeUnlockOperator || '-'} 于 ${this.$dayjs(row.kingdeeUnlockTime).format('YYYY-MM-DD HH:mm:ss')} 解锁`,
|
||||
};
|
||||
}
|
||||
return { show: false };
|
||||
},
|
||||
displayValue(value) {
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
class="pre-settlement-editor__form"
|
||||
>
|
||||
<el-row :gutter="24">
|
||||
<el-col v-for="field in formFields" :key="field.prop" :span="field.span || 6">
|
||||
<el-col v-for="field in visibleFormFields" :key="field.prop" :span="field.span || 6">
|
||||
<el-form-item :label="field.label" :prop="field.prop">
|
||||
<el-input
|
||||
v-if="field.type === 'contract'"
|
||||
@@ -40,7 +40,7 @@
|
||||
type="date"
|
||||
value-format="YYYY-MM-DD"
|
||||
format="YYYY-MM-DD"
|
||||
:disabled="!editable || form.currency === 'RMB'"
|
||||
:disabled="!editable"
|
||||
placeholder="请选择"
|
||||
@change="handleExchangeRateDateChange"
|
||||
/>
|
||||
@@ -50,7 +50,7 @@
|
||||
:min="0"
|
||||
:precision="2"
|
||||
:controls="false"
|
||||
:disabled="!editable || form.currency === 'RMB'"
|
||||
:disabled="!editable"
|
||||
@change="recalculateLocalAmount"
|
||||
/>
|
||||
<el-input
|
||||
@@ -66,7 +66,11 @@
|
||||
<span v-else-if="field.prop === 'settlementType'" class="form-readonly">
|
||||
{{ settlementTypeName }}
|
||||
</span>
|
||||
<span v-else-if="field.prop === 'preSettlementNo'" class="form-readonly">
|
||||
<span
|
||||
v-else-if="field.prop === 'preSettlementNo'"
|
||||
class="form-readonly"
|
||||
:class="{ 'form-readonly--code': form.preSettlementNo }"
|
||||
>
|
||||
{{ form.preSettlementNo || (!form.id ? '系统自动生成' : '-') }}
|
||||
</span>
|
||||
<span v-else-if="field.prop === 'createTime'" class="form-readonly">
|
||||
@@ -243,60 +247,52 @@
|
||||
:index="detailIndexMethod"
|
||||
/>
|
||||
<el-table-column
|
||||
v-for="column in visibleDetailColumns"
|
||||
:key="column.prop"
|
||||
:label="column.label"
|
||||
:prop="column.prop"
|
||||
:min-width="column.minWidth"
|
||||
v-for="item in detailDisplayColumns"
|
||||
:key="item.key"
|
||||
:label="item.kind === 'fee' ? item.name : item.column.label"
|
||||
:prop="item.kind === 'fee' ? undefined : item.column.prop"
|
||||
:min-width="item.kind === 'fee' ? 130 : item.column.minWidth"
|
||||
align="center"
|
||||
show-overflow-tooltip
|
||||
>
|
||||
<template #default="{ row }">
|
||||
<span v-if="item.kind === 'fee'">
|
||||
{{
|
||||
formatMoney(
|
||||
parseFeeItems(row.feeItemsJson)[item.name],
|
||||
row.currency || form.currency
|
||||
)
|
||||
}}
|
||||
</span>
|
||||
<el-link
|
||||
v-if="column.link && row[column.prop]"
|
||||
v-else-if="item.column.link && row[item.column.prop] && !publicMode"
|
||||
type="primary"
|
||||
@click="openAdjustDialog(row, true)"
|
||||
@click.stop="handleDetailLink(row, item.column)"
|
||||
>
|
||||
{{ row[column.prop] }}
|
||||
{{ row[item.column.prop] }}
|
||||
</el-link>
|
||||
<span v-else-if="column.money && column.prop === 'unitPrice'">
|
||||
<span v-else-if="item.column.money && item.column.prop === 'unitPrice'">
|
||||
{{ formatUnitPrice(row) }}
|
||||
</span>
|
||||
<span v-else-if="column.money">
|
||||
{{ formatMoney(row[column.prop], row.currency || form.currency) }}
|
||||
<span v-else-if="item.column.money">
|
||||
{{ formatMoney(row[item.column.prop], row.currency || form.currency) }}
|
||||
</span>
|
||||
<span v-else-if="column.prop === 'transportQuantityText'">
|
||||
<span v-else-if="item.column.prop === 'transportQuantityText'">
|
||||
{{ formatQuantity(row) }}
|
||||
</span>
|
||||
<span v-else-if="pageMode && column.prop === 'mileage'">
|
||||
{{ formatDetailMileage(row[column.prop]) }}
|
||||
<span v-else-if="item.column.prop === 'mileage'">
|
||||
{{ formatDetailMileage(row[item.column.prop]) }}
|
||||
</span>
|
||||
<span v-else-if="column.prop === 'transportType'">
|
||||
{{ transportTypeName(row[column.prop]) }}
|
||||
<span v-else-if="item.column.prop === 'transportType'">
|
||||
{{ transportTypeName(row[item.column.prop]) }}
|
||||
</span>
|
||||
<span v-else-if="column.prop === 'cargoName'">
|
||||
<span v-else-if="item.column.prop === 'cargoName'">
|
||||
{{ formatCargoNames(row) }}
|
||||
</span>
|
||||
<span v-else-if="column.prop === 'cargoType'">
|
||||
<span v-else-if="item.column.prop === 'cargoType'">
|
||||
{{ formatCargoTypes(row) }}
|
||||
</span>
|
||||
<span v-else>{{ displayValue(row[column.prop]) }}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
v-for="feeItem in detailFeeItemNames"
|
||||
:key="`fee-${feeItem}`"
|
||||
:label="feeItem"
|
||||
min-width="130"
|
||||
align="center"
|
||||
>
|
||||
<template #default="{ row }">
|
||||
{{
|
||||
formatMoney(
|
||||
parseFeeItems(row.feeItemsJson)[feeItem],
|
||||
row.currency || form.currency
|
||||
)
|
||||
}}
|
||||
<span v-else>{{ displayValue(row[item.column.prop]) }}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="操作" :width="editable ? 150 : 90" fixed="right" align="center">
|
||||
@@ -421,6 +417,16 @@
|
||||
</div>
|
||||
</section-card>
|
||||
|
||||
<project-advance-write-off
|
||||
ref="projectAdvanceWriteOffRef"
|
||||
v-model="projectAdvanceWriteOffs"
|
||||
:editable="editable"
|
||||
:contract-id="form.contractId"
|
||||
:settlement-amount="form.settlementAmount"
|
||||
settlement-type="pre"
|
||||
:settlement-id="form.id"
|
||||
/>
|
||||
|
||||
<section-card v-if="form.id && readonly" title="预付信息">
|
||||
<el-table :data="advances" border>
|
||||
<el-table-column type="index" label="序号" width="64" align="center" />
|
||||
@@ -776,12 +782,13 @@
|
||||
</el-table-column>
|
||||
<el-table-column label="里程(KM)" min-width="140" align="center">
|
||||
<template #default="{ row }">
|
||||
<span v-if="adjustDialog.readonly">{{ formatDetailMileage(row.mileage) }}</span>
|
||||
<el-input-number
|
||||
v-else
|
||||
v-model="row.mileage"
|
||||
:min="0"
|
||||
:precision="2"
|
||||
:controls="false"
|
||||
:disabled="adjustDialog.readonly"
|
||||
@change="recalculateAdjustRow(row, 'mileage')"
|
||||
/>
|
||||
</template>
|
||||
@@ -939,19 +946,27 @@ import { h } from 'vue';
|
||||
import { mapGetters } from 'vuex';
|
||||
import { InfoFilled } from '@element-plus/icons-vue';
|
||||
import ChangeRecordDetailDialog from '@/components/change-record-detail-dialog/main.vue';
|
||||
import ProjectAdvanceWriteOff from './project-advance-write-off.vue';
|
||||
import { getMkPublicDetail } from '@/api/mk-process';
|
||||
import {
|
||||
adjustDetail,
|
||||
getCandidateDetails,
|
||||
getContractList,
|
||||
getContractOptions,
|
||||
getDetail,
|
||||
getDetailFees,
|
||||
getFeeOptions,
|
||||
getNextNo,
|
||||
removeDetail,
|
||||
save,
|
||||
submit,
|
||||
} from '@/api/settlement/preSettlement';
|
||||
import { calculateAdjustedFee, getFeeDetail } from '@/api/settlement/receivable-payable-detail';
|
||||
import {
|
||||
resolveReceivablePayableDetailId,
|
||||
resolveWaybillId,
|
||||
settlementDetailPath,
|
||||
} from '@/utils/settlement-detail-link';
|
||||
import { getBfiExchangeRate } from '@/api/base/currency';
|
||||
import { getDictionary } from '@/api/system/dictbiz';
|
||||
import {
|
||||
@@ -974,14 +989,18 @@ import {
|
||||
settlementSummaryColumns,
|
||||
} from '@/option/settlement/preSettlementTable';
|
||||
import { formatContractOptionLabel } from '@/utils/contract';
|
||||
import { normalizeCurrencyCode } from '@/utils/currency';
|
||||
import { downloadFileByUrl } from '@/utils/util';
|
||||
import { submitMkApprovalFlow } from '@/utils/mk-approval';
|
||||
import { loadMkEnabled, submitMkApprovalFlow } from '@/utils/mk-approval';
|
||||
import * as XLSX from 'xlsx';
|
||||
import { isAdministrator } from '@/utils/auth';
|
||||
|
||||
// 结算币种为本位币(人民币)时汇率恒为1,汇率日期、结算汇率与按汇率换算的本位币合计都不展示
|
||||
const LOCAL_CURRENCY_HIDDEN_FIELDS = ['exchangeRateDate', 'exchangeRate', 'localSettlementAmount'];
|
||||
|
||||
export default {
|
||||
name: 'PreSettlementEditor',
|
||||
components: { InfoFilled, ChangeRecordDetailDialog },
|
||||
components: { InfoFilled, ChangeRecordDetailDialog, ProjectAdvanceWriteOff },
|
||||
props: {
|
||||
modelValue: {
|
||||
type: Boolean,
|
||||
@@ -1084,6 +1103,7 @@ export default {
|
||||
size: 10,
|
||||
},
|
||||
advances: [],
|
||||
projectAdvanceWriteOffs: [],
|
||||
changeRecords: [],
|
||||
attachments: [],
|
||||
attachmentTypeOptions: [],
|
||||
@@ -1188,6 +1208,14 @@ export default {
|
||||
settlementTypeName() {
|
||||
return this.form.settlementType === 'receivable' ? '应收' : '应付';
|
||||
},
|
||||
// 币种为空按人民币处理,与后端 normalizeRate 的取默认值一致
|
||||
isLocalCurrencySettlement() {
|
||||
return normalizeCurrencyCode(this.form.currency) === 'RMB';
|
||||
},
|
||||
visibleFormFields() {
|
||||
if (!this.isLocalCurrencySettlement) return this.formFields;
|
||||
return this.formFields.filter(field => !LOCAL_CURRENCY_HIDDEN_FIELDS.includes(field.prop));
|
||||
},
|
||||
contractDialogCategory() {
|
||||
return '承运商合同';
|
||||
},
|
||||
@@ -1209,6 +1237,17 @@ export default {
|
||||
});
|
||||
return Array.from(names);
|
||||
},
|
||||
detailDisplayColumns() {
|
||||
const columns = this.visibleDetailColumns;
|
||||
const unitPriceIndex = columns.findIndex(column => column.prop === 'unitPrice');
|
||||
const beforeColumns = unitPriceIndex < 0 ? columns : columns.slice(0, unitPriceIndex + 1);
|
||||
const afterColumns = unitPriceIndex < 0 ? [] : columns.slice(unitPriceIndex + 1);
|
||||
return [
|
||||
...beforeColumns.map(column => ({ kind: 'field', key: column.prop, column })),
|
||||
...this.detailFeeItemNames.map(name => ({ kind: 'fee', key: `fee-${name}`, name })),
|
||||
...afterColumns.map(column => ({ kind: 'field', key: column.prop, column })),
|
||||
];
|
||||
},
|
||||
// 按 preSettlementDetailId 分组的明细数据(用于拼接货物名称和类型)
|
||||
groupedDetails() {
|
||||
const groups = {};
|
||||
@@ -1283,6 +1322,11 @@ export default {
|
||||
},
|
||||
async initialize() {
|
||||
this.resetEditor();
|
||||
// 新增页在保存前没有创建人/创建日期,先按当前登录用户与当前时间展示将要落库的值
|
||||
if (!this.recordId) {
|
||||
this.form.createUserName = this.userInfo?.realName || this.userInfo?.userName || '';
|
||||
this.form.createTime = this.$dayjs().format('YYYY-MM-DD HH:mm:ss');
|
||||
}
|
||||
if (!this.publicMode) {
|
||||
await this.loadFeeOptions();
|
||||
await this.loadFeeCategoryOptions();
|
||||
@@ -1293,15 +1337,36 @@ export default {
|
||||
else if (this.initialData) {
|
||||
await this.applyInitialData();
|
||||
this.form.exchangeRateDate = this.$dayjs().format('YYYY-MM-DD');
|
||||
await this.refreshPreSettlementNo();
|
||||
await this.fetchBfiExchangeRate();
|
||||
} else if (!this.publicMode) {
|
||||
await this.refreshPreSettlementNo();
|
||||
}
|
||||
},
|
||||
async refreshPreSettlementNo() {
|
||||
if (this.form.id) return;
|
||||
const response = await getNextNo();
|
||||
this.form.preSettlementNo = response.data?.data || '';
|
||||
},
|
||||
async applyInitialData() {
|
||||
const rows = Array.isArray(this.initialData?.rows) ? this.initialData.rows : [];
|
||||
if (!rows.length) return;
|
||||
const first = rows[0];
|
||||
const settlementType = this.initialData.settlementType || first.settlementType || 'payable';
|
||||
const contractId = first.contractId || '';
|
||||
let contractPartyA = first.payerName || '';
|
||||
let contractPartyB = first.payeeName || '';
|
||||
if (contractId && (!contractPartyA || !contractPartyB)) {
|
||||
try {
|
||||
const { data } = await getContractOptions(first.contractNo || '');
|
||||
const matched = (data?.data || []).find(item => String(item.id) === String(contractId));
|
||||
contractPartyA = contractPartyA || matched?.partyA || '';
|
||||
contractPartyB = contractPartyB || matched?.partyB || '';
|
||||
} catch (error) {
|
||||
contractPartyA = '';
|
||||
contractPartyB = '';
|
||||
}
|
||||
}
|
||||
if (!this.contractOptions.some(item => String(item.id) === String(contractId))) {
|
||||
this.contractOptions.push({
|
||||
id: contractId,
|
||||
@@ -1312,8 +1377,8 @@ export default {
|
||||
deptId: first.deptId,
|
||||
deptName: first.deptName,
|
||||
settlementType,
|
||||
partyA: first.payerName,
|
||||
partyB: first.payeeName,
|
||||
partyA: contractPartyA,
|
||||
partyB: contractPartyB,
|
||||
});
|
||||
}
|
||||
this.handleContractChange(contractId);
|
||||
@@ -1325,8 +1390,8 @@ export default {
|
||||
projectName: first.projectName || this.form.projectName,
|
||||
deptId: first.deptId || this.form.deptId,
|
||||
deptName: first.deptName || this.form.deptName,
|
||||
payerName: first.payerName || this.form.payerName,
|
||||
payeeName: first.payeeName || this.form.payeeName,
|
||||
payerName: this.form.payerName || contractPartyA,
|
||||
payeeName: this.form.payeeName || contractPartyB,
|
||||
settlementType,
|
||||
currency: first.currency || 'RMB',
|
||||
localCurrency: first.localCurrency || 'RMB',
|
||||
@@ -1396,6 +1461,7 @@ export default {
|
||||
this.appliedDetailQuery = { ...this.detailQuery };
|
||||
this.detailPage = { current: 1, size: 10 };
|
||||
this.advances = [];
|
||||
this.projectAdvanceWriteOffs = [];
|
||||
this.changeRecords = [];
|
||||
this.attachments = [];
|
||||
this.selectedAttachmentRows = [];
|
||||
@@ -1435,6 +1501,13 @@ export default {
|
||||
createUserName: row.createUserName || detail.createUserName,
|
||||
createTime: row.createTime || detail.createTime,
|
||||
}));
|
||||
this.projectAdvanceWriteOffs = (detail.projectAdvanceWriteOffs || []).map(row => ({
|
||||
...row,
|
||||
writeOffAmount: Number(row.writeOffAmount || 0),
|
||||
projectAdvanceAmount: Number(row.projectAdvanceAmount || 0),
|
||||
availableWriteOffAmount: Number(row.availableWriteOffAmount || 0),
|
||||
createTime: row.paymentCreateTime || row.createTime,
|
||||
}));
|
||||
this.changeRecords = detail.changeRecords || [];
|
||||
this.attachments = this.parseAttachments(detail.attachmentsJson);
|
||||
this.sortAttachments();
|
||||
@@ -1579,6 +1652,7 @@ export default {
|
||||
this.form.payerName = '';
|
||||
this.form.payeeName = '';
|
||||
this.summaryFees = [];
|
||||
this.projectAdvanceWriteOffs = [];
|
||||
return;
|
||||
}
|
||||
this.form.contractId = contract.id;
|
||||
@@ -1589,10 +1663,12 @@ export default {
|
||||
this.form.deptId = contract.deptId;
|
||||
this.form.deptName = contract.deptName;
|
||||
this.form.settlementType = contract.settlementType || 'payable';
|
||||
this.form.payerName = contract.partyA;
|
||||
this.form.payeeName = contract.partyB;
|
||||
// 部分候选合同只带 id/名称,缺甲方乙方;此时保留已带出的收付方,不要清成 undefined
|
||||
if (contract.partyA) this.form.payerName = contract.partyA;
|
||||
if (contract.partyB) this.form.payeeName = contract.partyB;
|
||||
this.form.currency = contract.settlementCurrency || 'RMB';
|
||||
this.summaryFees = [];
|
||||
this.projectAdvanceWriteOffs = [];
|
||||
if (this.form.currency !== 'RMB') {
|
||||
this.form.exchangeRateDate = this.$dayjs().format('YYYY-MM-DD');
|
||||
this.fetchBfiExchangeRate();
|
||||
@@ -1613,6 +1689,9 @@ export default {
|
||||
this.$message.warning(`请完善结算合计第${invalidManualFeeIndex + 1}行的费用类型和费用项`);
|
||||
return;
|
||||
}
|
||||
if (this.$refs.projectAdvanceWriteOffRef?.validateBeforeSave?.() === false) {
|
||||
return;
|
||||
}
|
||||
const stateKey = shouldSubmit ? 'submitting' : 'saving';
|
||||
this[stateKey] = true;
|
||||
try {
|
||||
@@ -1623,13 +1702,14 @@ export default {
|
||||
if (shouldSubmit) {
|
||||
const previousApprovalStatus = this.form.approvalStatus || '';
|
||||
await submit({ id: this.form.id });
|
||||
await submitMkApprovalFlow({
|
||||
const mkStarted = await submitMkApprovalFlow({
|
||||
bizType: 'pre-settlement',
|
||||
bizId: this.form.id,
|
||||
subjectName: this.form.preSettlementNo || this.form.contractName || '',
|
||||
approvalStatus: previousApprovalStatus,
|
||||
mkEnabled: await loadMkEnabled().catch(() => false),
|
||||
});
|
||||
this.$message.success('审批流程已发起');
|
||||
this.$message.success(mkStarted ? '审批流程已发起' : '提交成功');
|
||||
this.visible = false;
|
||||
this.$emit('success', this.form.id);
|
||||
return;
|
||||
@@ -1659,6 +1739,7 @@ export default {
|
||||
remark: row.remark,
|
||||
manualFlag: row.manualFlag || 0,
|
||||
})),
|
||||
projectAdvanceWriteOffs: this.$refs.projectAdvanceWriteOffRef?.toSavePayload?.() || [],
|
||||
};
|
||||
},
|
||||
addSummaryFee() {
|
||||
@@ -1715,7 +1796,7 @@ export default {
|
||||
return option?.dictValue || value;
|
||||
},
|
||||
formatDetailMileage(value) {
|
||||
if (value === undefined || value === null || value === '' || Number(value) === -1) return '';
|
||||
if (value === undefined || value === null || value === '' || Number(value) === -1) return '-';
|
||||
return value;
|
||||
},
|
||||
removeSummaryFee(index) {
|
||||
@@ -1987,6 +2068,21 @@ export default {
|
||||
this.appliedDetailQuery = { ...this.detailQuery };
|
||||
this.detailPage.current = 1;
|
||||
},
|
||||
// 结算明细的「单据号」跳到该应收应付单据在应收明细/应付明细页的详情,「运单号」跳到运单详情
|
||||
async handleDetailLink(row, column) {
|
||||
if (column.prop === 'documentNo') {
|
||||
const settlementType = row.settlementType || this.form.settlementType || 'payable';
|
||||
const detailId = await resolveReceivablePayableDetailId(row, settlementType);
|
||||
if (!detailId) return this.$message.info('当前记录未找到对应的应收/应付明细');
|
||||
this.$router.push({ path: settlementDetailPath(settlementType), query: { detailId } });
|
||||
return;
|
||||
}
|
||||
if (column.prop === 'waybillNo') {
|
||||
const waybillId = await resolveWaybillId(row);
|
||||
if (!waybillId) return this.$message.info('当前记录未找到对应的运单详情');
|
||||
this.$router.push({ path: '/business/waybill-manage/detail', query: { id: waybillId } });
|
||||
}
|
||||
},
|
||||
resetDetailQuery() {
|
||||
this.detailQuery = {
|
||||
documentNo: '',
|
||||
@@ -2992,6 +3088,10 @@ export default {
|
||||
line-height: 32px;
|
||||
color: #303133;
|
||||
}
|
||||
|
||||
.form-readonly--code {
|
||||
color: var(--el-color-primary);
|
||||
}
|
||||
}
|
||||
|
||||
.dialog-section-title {
|
||||
|
||||
@@ -0,0 +1,300 @@
|
||||
<template>
|
||||
<section-card title="项目预付核销">
|
||||
<template v-if="editable" #extra>
|
||||
<el-button type="primary" :disabled="!contractId" @click="openSelectDialog">选择</el-button>
|
||||
</template>
|
||||
<el-table :data="rows" border>
|
||||
<el-table-column type="index" label="序号" width="64" align="center" />
|
||||
<el-table-column
|
||||
prop="paymentNo"
|
||||
label="预付单号"
|
||||
min-width="160"
|
||||
align="center"
|
||||
show-overflow-tooltip
|
||||
/>
|
||||
<el-table-column label="项目预付金额" min-width="140" align="center">
|
||||
<template #default="{ row }">{{ formatMoney(row.projectAdvanceAmount) }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="可核销金额" min-width="140" align="center">
|
||||
<template #default="{ row }">{{ formatMoney(row.availableWriteOffAmount) }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="本次核销金额" min-width="160" align="center">
|
||||
<template #default="{ row }">
|
||||
<el-input-number
|
||||
v-if="editable"
|
||||
v-model="row.writeOffAmount"
|
||||
:min="0"
|
||||
:precision="2"
|
||||
:controls="false"
|
||||
class="project-advance-write-off__amount"
|
||||
@change="handleAmountChange(row)"
|
||||
/>
|
||||
<span v-else>{{ formatMoney(row.writeOffAmount) }}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
prop="kingdeeBillNo"
|
||||
label="金蝶预付单号"
|
||||
min-width="160"
|
||||
align="center"
|
||||
show-overflow-tooltip
|
||||
>
|
||||
<template #default="{ row }">{{ row.kingdeeBillNo || '-' }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
prop="createUserName"
|
||||
label="创建人"
|
||||
min-width="120"
|
||||
align="center"
|
||||
show-overflow-tooltip
|
||||
>
|
||||
<template #default="{ row }">{{ row.createUserName || '-' }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="createTime" label="创建时间" min-width="170" align="center">
|
||||
<template #default="{ row }">{{ formatDateTime(row.createTime || row.paymentCreateTime) }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column v-if="editable" label="操作" width="90" align="center" fixed="right">
|
||||
<template #default="{ $index }">
|
||||
<el-link type="danger" @click="removeRow($index)">删除</el-link>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
|
||||
<el-dialog
|
||||
v-model="dialog.visible"
|
||||
title="选择项目预付单"
|
||||
width="960px"
|
||||
append-to-body
|
||||
destroy-on-close
|
||||
class="dialog-form-title"
|
||||
@closed="handleDialogClosed"
|
||||
>
|
||||
<el-table
|
||||
v-loading="dialog.loading"
|
||||
:data="dialog.rows"
|
||||
border
|
||||
@selection-change="handleSelectionChange"
|
||||
>
|
||||
<el-table-column type="selection" width="55" align="center" :selectable="rowSelectable" />
|
||||
<el-table-column type="index" label="序号" width="64" align="center" />
|
||||
<el-table-column
|
||||
prop="paymentNo"
|
||||
label="预付单号"
|
||||
min-width="160"
|
||||
align="center"
|
||||
show-overflow-tooltip
|
||||
/>
|
||||
<el-table-column label="项目预付金额" min-width="140" align="center">
|
||||
<template #default="{ row }">{{ formatMoney(row.projectAdvanceAmount) }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="可核销金额" min-width="140" align="center">
|
||||
<template #default="{ row }">{{ formatMoney(row.availableWriteOffAmount) }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
prop="kingdeeBillNo"
|
||||
label="金蝶预付单号"
|
||||
min-width="160"
|
||||
align="center"
|
||||
show-overflow-tooltip
|
||||
>
|
||||
<template #default="{ row }">{{ row.kingdeeBillNo || '-' }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
prop="createUserName"
|
||||
label="创建人"
|
||||
min-width="120"
|
||||
align="center"
|
||||
show-overflow-tooltip
|
||||
>
|
||||
<template #default="{ row }">{{ row.createUserName || '-' }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="createTime" label="创建时间" min-width="170" align="center">
|
||||
<template #default="{ row }">{{ formatDateTime(row.createTime) }}</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
<template #footer>
|
||||
<el-button @click="dialog.visible = false">取消</el-button>
|
||||
<el-button type="primary" :disabled="!dialog.selection.length" @click="confirmSelect">
|
||||
确定
|
||||
</el-button>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</section-card>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { getProjectAdvanceWriteOffOptions } from '@/api/payment/paymentApplication';
|
||||
|
||||
export default {
|
||||
name: 'ProjectAdvanceWriteOff',
|
||||
props: {
|
||||
modelValue: {
|
||||
type: Array,
|
||||
default: () => [],
|
||||
},
|
||||
editable: {
|
||||
type: Boolean,
|
||||
default: true,
|
||||
},
|
||||
contractId: {
|
||||
type: [String, Number],
|
||||
default: '',
|
||||
},
|
||||
settlementAmount: {
|
||||
type: [String, Number],
|
||||
default: 0,
|
||||
},
|
||||
settlementType: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
settlementId: {
|
||||
type: [String, Number],
|
||||
default: '',
|
||||
},
|
||||
},
|
||||
emits: ['update:modelValue'],
|
||||
data() {
|
||||
return {
|
||||
dialog: {
|
||||
visible: false,
|
||||
loading: false,
|
||||
rows: [],
|
||||
selection: [],
|
||||
},
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
rows: {
|
||||
get() {
|
||||
return Array.isArray(this.modelValue) ? this.modelValue : [];
|
||||
},
|
||||
set(value) {
|
||||
this.$emit('update:modelValue', value);
|
||||
},
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
formatMoney(value) {
|
||||
const amount = Number(value);
|
||||
return Number.isFinite(amount) ? amount.toFixed(2) : '0.00';
|
||||
},
|
||||
formatDateTime(value) {
|
||||
if (!value) return '-';
|
||||
return this.$dayjs(value).format('YYYY-MM-DD HH:mm:ss');
|
||||
},
|
||||
money(value) {
|
||||
const amount = Number(value);
|
||||
return Number.isFinite(amount) ? Number(amount.toFixed(2)) : 0;
|
||||
},
|
||||
openSelectDialog() {
|
||||
if (!this.contractId) {
|
||||
this.$message.warning('请先选择合同');
|
||||
return;
|
||||
}
|
||||
this.dialog.visible = true;
|
||||
this.loadOptions();
|
||||
},
|
||||
async loadOptions() {
|
||||
this.dialog.loading = true;
|
||||
try {
|
||||
const res = await getProjectAdvanceWriteOffOptions({
|
||||
contractId: this.contractId,
|
||||
excludeSettlementType: this.settlementType,
|
||||
excludeSettlementId: this.settlementId || undefined,
|
||||
});
|
||||
this.dialog.rows = res?.data?.data || [];
|
||||
} finally {
|
||||
this.dialog.loading = false;
|
||||
}
|
||||
},
|
||||
rowSelectable(row) {
|
||||
const selectedIds = new Set(this.rows.map(item => String(item.paymentApplicationId)));
|
||||
return !selectedIds.has(String(row.paymentApplicationId));
|
||||
},
|
||||
handleSelectionChange(list) {
|
||||
this.dialog.selection = list || [];
|
||||
},
|
||||
confirmSelect() {
|
||||
const nextRows = [...this.rows];
|
||||
const existed = new Set(nextRows.map(item => String(item.paymentApplicationId)));
|
||||
this.dialog.selection.forEach(item => {
|
||||
const id = String(item.paymentApplicationId);
|
||||
if (existed.has(id)) return;
|
||||
nextRows.push({
|
||||
paymentApplicationId: item.paymentApplicationId,
|
||||
paymentNo: item.paymentNo,
|
||||
projectAdvanceAmount: this.money(item.projectAdvanceAmount),
|
||||
availableWriteOffAmount: this.money(item.availableWriteOffAmount),
|
||||
writeOffAmount: this.money(item.availableWriteOffAmount),
|
||||
kingdeeBillNo: item.kingdeeBillNo || '',
|
||||
createUserName: item.createUserName || '',
|
||||
createTime: item.createTime,
|
||||
paymentCreateTime: item.createTime,
|
||||
});
|
||||
});
|
||||
this.rows = nextRows;
|
||||
this.dialog.visible = false;
|
||||
this.validateTotal();
|
||||
},
|
||||
handleDialogClosed() {
|
||||
this.dialog.rows = [];
|
||||
this.dialog.selection = [];
|
||||
},
|
||||
handleAmountChange(row) {
|
||||
const available = this.money(row.availableWriteOffAmount);
|
||||
let amount = this.money(row.writeOffAmount);
|
||||
if (amount < 0) amount = 0;
|
||||
if (amount > available) {
|
||||
amount = available;
|
||||
this.$message.warning('本次核销金额不能超过可核销金额');
|
||||
}
|
||||
row.writeOffAmount = amount;
|
||||
this.validateTotal();
|
||||
},
|
||||
validateTotal() {
|
||||
const total = this.rows.reduce((sum, row) => sum + this.money(row.writeOffAmount), 0);
|
||||
const settlementAmount = this.money(this.settlementAmount);
|
||||
if (settlementAmount > 0 && total > settlementAmount) {
|
||||
this.$message.warning('项目预付核销合计不能超过结算单金额');
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
},
|
||||
validateBeforeSave() {
|
||||
for (const row of this.rows) {
|
||||
const amount = this.money(row.writeOffAmount);
|
||||
if (amount <= 0) {
|
||||
this.$message.warning(`预付单${row.paymentNo || ''}本次核销金额必须大于0`);
|
||||
return false;
|
||||
}
|
||||
if (amount > this.money(row.availableWriteOffAmount)) {
|
||||
this.$message.warning(`预付单${row.paymentNo || ''}本次核销金额不能超过可核销金额`);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return this.validateTotal();
|
||||
},
|
||||
removeRow(index) {
|
||||
const nextRows = [...this.rows];
|
||||
nextRows.splice(index, 1);
|
||||
this.rows = nextRows;
|
||||
},
|
||||
toSavePayload() {
|
||||
return this.rows.map(row => ({
|
||||
paymentApplicationId: row.paymentApplicationId,
|
||||
writeOffAmount: this.money(row.writeOffAmount),
|
||||
}));
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.project-advance-write-off__amount {
|
||||
width: 100%;
|
||||
}
|
||||
:deep(.project-advance-write-off__amount .el-input__inner) {
|
||||
text-align: right;
|
||||
}
|
||||
</style>
|
||||
File diff suppressed because it is too large
Load Diff
@@ -368,6 +368,7 @@ export default {
|
||||
bizId: row.id,
|
||||
subjectName: row.formalSettlementNo || '',
|
||||
approvalStatus: previousApprovalStatus,
|
||||
mkEnabled: this.mkEnabled,
|
||||
});
|
||||
this.$message.success('提交成功');
|
||||
this.loadTable();
|
||||
|
||||
@@ -661,6 +661,7 @@ export default {
|
||||
bizId: row.id,
|
||||
subjectName: row.preSettlementNo || '',
|
||||
approvalStatus: previousApprovalStatus,
|
||||
mkEnabled: this.mkEnabled,
|
||||
});
|
||||
this.$message.success('提交成功');
|
||||
this.loadTable();
|
||||
|
||||
@@ -520,7 +520,7 @@
|
||||
:index="transferIndexMethod"
|
||||
/>
|
||||
<el-table-column
|
||||
v-for="column in tableColumns.slice(0, 13)"
|
||||
v-for="column in transferTableColumns"
|
||||
:key="column.prop"
|
||||
:prop="column.prop"
|
||||
:label="column.label"
|
||||
@@ -1174,6 +1174,13 @@ export default {
|
||||
columns.splice(totalIndex, 0, ...dynamicColumns);
|
||||
return columns;
|
||||
},
|
||||
// 批量转结算弹窗只展示前 13 列。预结算单号、正式结算单号正是本操作要生成的单号,
|
||||
// 候选行上必为空,故不占列
|
||||
transferTableColumns() {
|
||||
return this.tableColumns
|
||||
.filter(column => !['preSettlementNo', 'formalSettlementNo'].includes(column.prop))
|
||||
.slice(0, 13);
|
||||
},
|
||||
columnSettingOptions() {
|
||||
return this.displayTableColumns.map(column => ({
|
||||
prop: column.prop,
|
||||
@@ -1712,11 +1719,12 @@ export default {
|
||||
this.$router.push({ path, query: { detailNo } });
|
||||
},
|
||||
async openRouteDetail(detailId = this.$route.query.detailId) {
|
||||
if (
|
||||
!detailId ||
|
||||
this.settlementType !== 'payable' ||
|
||||
this.$route.path !== '/settlement/payable-detail'
|
||||
) {
|
||||
// 结算明细点「单据号」会带 detailId 跳到本页;只有当前停在应收/应付明细页时才自动展开详情
|
||||
const routePath =
|
||||
this.settlementType === 'payable'
|
||||
? '/settlement/payable-detail'
|
||||
: '/settlement/receivable-detail';
|
||||
if (!detailId || this.$route.path !== routePath) {
|
||||
return;
|
||||
}
|
||||
let row = this.rows.find(item => String(item.id) === String(detailId));
|
||||
@@ -1729,7 +1737,9 @@ export default {
|
||||
if (data.records?.[0]) row = this.decorateRow(data.records[0]);
|
||||
}
|
||||
if (!row) {
|
||||
this.$message.info('当前记录未找到对应的应付明细');
|
||||
this.$message.info(
|
||||
`当前记录未找到对应的${this.settlementType === 'payable' ? '应付' : '应收'}明细`
|
||||
);
|
||||
return;
|
||||
}
|
||||
await this.openDetailDialog(row);
|
||||
@@ -2681,12 +2691,12 @@ export default {
|
||||
return `${Number(value).toFixed(2)} ${currency}`;
|
||||
},
|
||||
formatDetailCell(row, prop) {
|
||||
if (prop === 'mileage' && Number(row?.[prop]) === -1) {
|
||||
return '-';
|
||||
}
|
||||
if (
|
||||
prop === 'mileage' &&
|
||||
(row?.[prop] === null ||
|
||||
row?.[prop] === undefined ||
|
||||
row?.[prop] === '' ||
|
||||
Number(row[prop]) === -1)
|
||||
(row?.[prop] === null || row?.[prop] === undefined || row?.[prop] === '')
|
||||
) {
|
||||
return '';
|
||||
}
|
||||
|
||||
@@ -0,0 +1,49 @@
|
||||
<template>
|
||||
<basic-container class="settlement-import-form-page">
|
||||
<div class="settlement-import-form-page__title">{{ pageTitle }}</div>
|
||||
<settlement-import-page />
|
||||
</basic-container>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { computed } from 'vue';
|
||||
import { useRoute } from 'vue-router';
|
||||
import SettlementImportPage from './components/settlement-import-page.vue';
|
||||
|
||||
const route = useRoute();
|
||||
const pageTitle = computed(() => (route.query.id ? '编辑导入结算单' : '新增导入结算单'));
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.settlement-import-form-page {
|
||||
:deep(.basic-container__card) {
|
||||
background: #fff;
|
||||
}
|
||||
|
||||
:deep(.basic-container__card > .el-card__body) {
|
||||
padding: 0;
|
||||
background: #fff;
|
||||
}
|
||||
}
|
||||
|
||||
.settlement-import-form-page__title {
|
||||
position: relative;
|
||||
padding: 16px 24px 12px 36px;
|
||||
border-bottom: 1px solid #eff1f7;
|
||||
background: #fff;
|
||||
color: #303133;
|
||||
font-size: 18px;
|
||||
font-weight: 600;
|
||||
|
||||
&::before {
|
||||
position: absolute;
|
||||
top: 16px;
|
||||
left: 24px;
|
||||
width: 4px;
|
||||
height: 22px;
|
||||
border-radius: 2px;
|
||||
background: #409eff;
|
||||
content: '';
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,531 @@
|
||||
<template>
|
||||
<basic-container class="settlement-import-page">
|
||||
<section v-show="searchVisible" class="settlement-import-page__search">
|
||||
<el-form :model="query" label-position="right" label-width="160px" @submit.prevent>
|
||||
<div class="settlement-import-page__search-grid">
|
||||
<el-form-item v-for="field in visibleSearchFields" :key="field.prop" :label="field.label">
|
||||
<el-select
|
||||
v-if="field.type === 'select'"
|
||||
v-model="query[field.prop]"
|
||||
clearable
|
||||
filterable
|
||||
allow-create
|
||||
default-first-option
|
||||
placeholder="请选择"
|
||||
>
|
||||
<el-option
|
||||
v-for="item in field.options"
|
||||
:key="item.value"
|
||||
:label="item.label"
|
||||
:value="item.value"
|
||||
/>
|
||||
</el-select>
|
||||
<el-input v-else v-model="query[field.prop]" clearable placeholder="请输入" />
|
||||
</el-form-item>
|
||||
<div class="settlement-import-page__search-actions">
|
||||
<el-button type="primary" @click="handleSearch">查询</el-button>
|
||||
<el-button @click="resetSearch">重置</el-button>
|
||||
<el-button text :icon="searchExpanded ? ArrowUp : ArrowDown" @click="toggleSearch">
|
||||
{{ searchExpanded ? '收起' : '展开' }}
|
||||
</el-button>
|
||||
</div>
|
||||
</div>
|
||||
</el-form>
|
||||
</section>
|
||||
|
||||
<section class="settlement-import-page__table-panel">
|
||||
<div class="settlement-import-page__toolbar">
|
||||
<div class="settlement-import-page__toolbar-left">
|
||||
<el-button
|
||||
v-if="hasPermission('settlement_import_add')"
|
||||
type="primary"
|
||||
@click="openCreate"
|
||||
>
|
||||
新增
|
||||
</el-button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<el-table v-loading="loading" :data="rows" border class="settlement-import-page__table">
|
||||
<el-table-column type="index" label="序号" width="64" align="center" />
|
||||
<el-table-column prop="batchNo" label="导入批次号" min-width="160">
|
||||
<template #default="{ row }">
|
||||
<el-link type="primary" @click="openDetail(row)">{{ row.batchNo || '-' }}</el-link>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="settlementTypeName" label="导入类型" min-width="100">
|
||||
<template #default="{ row }">
|
||||
{{ row.settlementTypeName || settlementTypeLabel(row.settlementType) }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="settlementBillTypeName" label="结算单" min-width="110">
|
||||
<template #default="{ row }">
|
||||
{{ row.settlementBillTypeName || settlementBillTypeLabel(row.settlementBillType) }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="projectName" label="项目" min-width="140" show-overflow-tooltip />
|
||||
<el-table-column prop="customerName" label="客户" min-width="140" show-overflow-tooltip />
|
||||
<el-table-column prop="carrierName" label="承运商" min-width="140" show-overflow-tooltip />
|
||||
<el-table-column label="明细行数" width="100" align="center">
|
||||
<template #default="{ row }">
|
||||
{{ row.waybillCount ?? row.detailCount ?? 0 }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="createTime" label="创建时间" min-width="170" />
|
||||
<el-table-column prop="createUserName" label="创建人" min-width="120">
|
||||
<template #default="{ row }">{{ row.createUserName || '-' }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="操作" width="160" fixed="right">
|
||||
<template #default="{ row }">
|
||||
<div class="settlement-import-page__row-actions">
|
||||
<el-link type="primary" @click="openDetail(row)">详情</el-link>
|
||||
<el-link
|
||||
v-if="row.importStatus !== 'voided' && hasPermission('settlement_import_void')"
|
||||
type="danger"
|
||||
@click="openVoid(row)"
|
||||
>
|
||||
作废
|
||||
</el-link>
|
||||
</div>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
|
||||
<div class="settlement-import-page__pagination">
|
||||
<el-pagination
|
||||
background
|
||||
v-model:current-page="page.current"
|
||||
v-model:page-size="page.size"
|
||||
layout="total, sizes, prev, pager, next, jumper"
|
||||
:page-sizes="[10, 20, 50, 100]"
|
||||
:total="page.total"
|
||||
@current-change="loadTable"
|
||||
@size-change="handleSizeChange"
|
||||
/>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<el-dialog v-model="detailVisible" title="导入结算单详情" width="640px" append-to-body>
|
||||
<el-descriptions :column="2" border>
|
||||
<el-descriptions-item label="导入批次号">{{ detail.batchNo || '-' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="导入类型">
|
||||
{{ detail.settlementTypeName || settlementTypeLabel(detail.settlementType) }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="结算单">
|
||||
{{ detail.settlementBillTypeName || settlementBillTypeLabel(detail.settlementBillType) }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="运单类型">
|
||||
{{ detail.waybillModeName || waybillModeLabel(detail.waybillMode) }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="项目">{{ detail.projectName || '-' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="客户">{{ detail.customerName || '-' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="客户合同">
|
||||
{{ detail.customerContractName || '-' }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="承运商">{{ detail.carrierName || '-' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="承运商合同">
|
||||
{{ detail.carrierContractName || '-' }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="明细行数">
|
||||
{{ detail.waybillCount ?? detail.detailCount ?? 0 }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="导入状态">
|
||||
{{ detail.importStatusName || detail.importStatus || '-' }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="创建人">{{ detail.createUserName || '-' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="创建时间">{{ detail.createTime || '-' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="备注" :span="2">{{ detail.remark || '-' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="关联结算单" :span="2">
|
||||
<el-link
|
||||
v-if="detail.formalSettlementId"
|
||||
type="primary"
|
||||
@click="goFormalSettlement(detail)"
|
||||
>
|
||||
{{ detail.formalSettlementNo || detail.formalSettlementId }}
|
||||
</el-link>
|
||||
<el-link
|
||||
v-else-if="detail.preSettlementId"
|
||||
type="primary"
|
||||
@click="goPreSettlement(detail)"
|
||||
>
|
||||
{{ detail.preSettlementNo || detail.preSettlementId }}
|
||||
</el-link>
|
||||
<span v-else>-</span>
|
||||
</el-descriptions-item>
|
||||
</el-descriptions>
|
||||
<template #footer>
|
||||
<el-button @click="detailVisible = false">关闭</el-button>
|
||||
<el-button
|
||||
v-if="detail.importStatus === 'draft'"
|
||||
type="primary"
|
||||
@click="editDraft(detail)"
|
||||
>
|
||||
编辑草稿
|
||||
</el-button>
|
||||
</template>
|
||||
</el-dialog>
|
||||
|
||||
<el-dialog v-model="voidVisible" title="作废导入结算单" width="480px" append-to-body>
|
||||
<el-form
|
||||
ref="voidFormRef"
|
||||
:model="voidForm"
|
||||
:rules="voidRules"
|
||||
label-position="right"
|
||||
label-width="auto"
|
||||
>
|
||||
<el-form-item label="作废原因" prop="reason">
|
||||
<el-input
|
||||
v-model="voidForm.reason"
|
||||
type="textarea"
|
||||
:rows="4"
|
||||
maxlength="200"
|
||||
show-word-limit
|
||||
placeholder="请输入作废原因"
|
||||
/>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<el-button @click="voidVisible = false">取消</el-button>
|
||||
<el-button type="danger" :loading="voidLoading" @click="submitVoid">确认作废</el-button>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</basic-container>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { ArrowDown, ArrowUp } from '@element-plus/icons-vue';
|
||||
import { mapGetters } from 'vuex';
|
||||
import * as api from '@/api/settlement/settlementImport';
|
||||
|
||||
const emptyQuery = () => ({
|
||||
batchNo: '',
|
||||
settlementType: '',
|
||||
projectName: '',
|
||||
customerName: '',
|
||||
carrierName: '',
|
||||
});
|
||||
|
||||
const SETTLEMENT_TYPE_OPTIONS = [
|
||||
{ label: '全部', value: '' },
|
||||
{ label: '应收正式结算单', value: 'receivable' },
|
||||
{ label: '应付正式结算单', value: 'payable' },
|
||||
];
|
||||
|
||||
const SETTLEMENT_IMPORT_LIST_REFRESH_KEY = 'settlement-import-list-refresh';
|
||||
|
||||
export default {
|
||||
name: 'SettlementImport',
|
||||
data() {
|
||||
return {
|
||||
ArrowDown,
|
||||
ArrowUp,
|
||||
searchVisible: true,
|
||||
searchExpanded: false,
|
||||
loading: false,
|
||||
query: emptyQuery(),
|
||||
rows: [],
|
||||
page: { current: 1, size: 10, total: 0 },
|
||||
batchNoOptions: [],
|
||||
detailVisible: false,
|
||||
detail: {},
|
||||
voidVisible: false,
|
||||
voidLoading: false,
|
||||
voidForm: { id: null, reason: '' },
|
||||
voidRules: {
|
||||
reason: [
|
||||
{ required: true, message: '请输入作废原因', trigger: 'blur' },
|
||||
{ max: 200, message: '作废原因不能超过200个字', trigger: 'blur' },
|
||||
],
|
||||
},
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
...mapGetters(['permission', 'userInfo']),
|
||||
isAdmin() {
|
||||
const authority = this.userInfo?.authority || '';
|
||||
return String(authority).includes('admin');
|
||||
},
|
||||
searchFields() {
|
||||
return [
|
||||
{
|
||||
prop: 'batchNo',
|
||||
label: '导入批次号',
|
||||
type: 'select',
|
||||
options: this.batchNoOptions,
|
||||
},
|
||||
{
|
||||
prop: 'settlementType',
|
||||
label: '导入类型',
|
||||
type: 'select',
|
||||
options: SETTLEMENT_TYPE_OPTIONS,
|
||||
},
|
||||
{ prop: 'projectName', label: '项目', type: 'input' },
|
||||
{ prop: 'customerName', label: '客户', type: 'input' },
|
||||
{ prop: 'carrierName', label: '承运商', type: 'input' },
|
||||
];
|
||||
},
|
||||
visibleSearchFields() {
|
||||
return this.searchExpanded ? this.searchFields : this.searchFields.slice(0, 4);
|
||||
},
|
||||
},
|
||||
created() {
|
||||
this.loadOptions();
|
||||
this.loadTable();
|
||||
},
|
||||
activated() {
|
||||
this.consumeListRefresh();
|
||||
},
|
||||
watch: {
|
||||
$route(to, from) {
|
||||
if (
|
||||
to.path === '/settlement/settlement-import' &&
|
||||
from?.path === '/settlement/settlement-import/form'
|
||||
) {
|
||||
this.consumeListRefresh();
|
||||
}
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
consumeListRefresh() {
|
||||
if (sessionStorage.getItem(SETTLEMENT_IMPORT_LIST_REFRESH_KEY) !== '1') {
|
||||
return;
|
||||
}
|
||||
sessionStorage.removeItem(SETTLEMENT_IMPORT_LIST_REFRESH_KEY);
|
||||
this.page.current = 1;
|
||||
this.loadOptions();
|
||||
this.loadTable();
|
||||
},
|
||||
hasPermission(code) {
|
||||
return this.isAdmin || this.validData(this.permission?.[code], false);
|
||||
},
|
||||
settlementTypeLabel(value) {
|
||||
return (
|
||||
SETTLEMENT_TYPE_OPTIONS.find(item => item.value === value)?.label || value || '-'
|
||||
);
|
||||
},
|
||||
settlementBillTypeLabel(value) {
|
||||
if (value === 'pre') return '预结算单';
|
||||
return '正式结算单';
|
||||
},
|
||||
waybillModeLabel(value) {
|
||||
if (value === 'history') return '历史运单';
|
||||
return '新运单';
|
||||
},
|
||||
unwrapData(response) {
|
||||
const body = response?.data || response || {};
|
||||
return body?.data !== undefined ? body.data : body;
|
||||
},
|
||||
async loadOptions() {
|
||||
try {
|
||||
const response = await api.getOptions();
|
||||
const options = this.unwrapData(response) || {};
|
||||
const batchNos = Array.isArray(options.batchNos) ? options.batchNos : [];
|
||||
this.batchNoOptions = [
|
||||
{ label: '全部', value: '' },
|
||||
...batchNos.map(item => {
|
||||
if (typeof item === 'string') {
|
||||
return { label: item, value: item };
|
||||
}
|
||||
const value = item.id ?? item.value ?? item.name ?? item.batchNo ?? '';
|
||||
const label = item.name ?? item.label ?? item.batchNo ?? value;
|
||||
return { label: String(label || ''), value: String(value || '') };
|
||||
}),
|
||||
];
|
||||
} catch (error) {
|
||||
this.batchNoOptions = [{ label: '全部', value: '' }];
|
||||
}
|
||||
},
|
||||
async loadTable() {
|
||||
this.loading = true;
|
||||
try {
|
||||
const response = await api.getList(this.page.current, this.page.size, {
|
||||
batchNo: this.query.batchNo || undefined,
|
||||
settlementType: this.query.settlementType || undefined,
|
||||
projectName: this.query.projectName || undefined,
|
||||
customerName: this.query.customerName || undefined,
|
||||
carrierName: this.query.carrierName || undefined,
|
||||
});
|
||||
const data = this.unwrapData(response) || {};
|
||||
this.rows = data.records || [];
|
||||
this.page.total = Number(data.total || 0);
|
||||
} finally {
|
||||
this.loading = false;
|
||||
}
|
||||
},
|
||||
handleSearch() {
|
||||
this.page.current = 1;
|
||||
this.loadTable();
|
||||
},
|
||||
resetSearch() {
|
||||
this.query = emptyQuery();
|
||||
this.handleSearch();
|
||||
},
|
||||
toggleSearch() {
|
||||
this.searchExpanded = !this.searchExpanded;
|
||||
},
|
||||
handleSizeChange() {
|
||||
this.page.current = 1;
|
||||
this.loadTable();
|
||||
},
|
||||
openCreate() {
|
||||
this.$router.push({ path: '/settlement/settlement-import/form' });
|
||||
},
|
||||
async openDetail(row) {
|
||||
if (!row?.id) return;
|
||||
try {
|
||||
const response = await api.getDetail(row.id);
|
||||
this.detail = this.unwrapData(response) || { ...row };
|
||||
} catch (error) {
|
||||
this.detail = { ...row };
|
||||
}
|
||||
this.detailVisible = true;
|
||||
},
|
||||
editDraft(row) {
|
||||
this.detailVisible = false;
|
||||
this.$router.push({
|
||||
path: '/settlement/settlement-import/form',
|
||||
query: { id: row.id },
|
||||
});
|
||||
},
|
||||
goFormalSettlement(row) {
|
||||
if (!row?.formalSettlementId) return;
|
||||
this.detailVisible = false;
|
||||
this.$router.push({
|
||||
path: '/settlement/formal-settlement/form',
|
||||
query: {
|
||||
mode: 'view',
|
||||
id: row.formalSettlementId,
|
||||
name: '正式结算详情',
|
||||
},
|
||||
});
|
||||
},
|
||||
goPreSettlement(row) {
|
||||
if (!row?.preSettlementId) return;
|
||||
this.detailVisible = false;
|
||||
this.$router.push({
|
||||
path: '/settlement/pre-settlement/form',
|
||||
query: {
|
||||
mode: 'view',
|
||||
id: row.preSettlementId,
|
||||
name: '预结算详情',
|
||||
},
|
||||
});
|
||||
},
|
||||
openVoid(row) {
|
||||
this.voidForm = { id: row.id, reason: '' };
|
||||
this.voidVisible = true;
|
||||
this.$nextTick(() => this.$refs.voidFormRef?.clearValidate());
|
||||
},
|
||||
async submitVoid() {
|
||||
await this.$refs.voidFormRef?.validate();
|
||||
this.voidLoading = true;
|
||||
try {
|
||||
await api.voidBatch({
|
||||
id: this.voidForm.id,
|
||||
reason: String(this.voidForm.reason || '').trim(),
|
||||
});
|
||||
this.$message.success('作废成功');
|
||||
this.voidVisible = false;
|
||||
this.loadTable();
|
||||
this.loadOptions();
|
||||
} finally {
|
||||
this.voidLoading = false;
|
||||
}
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.settlement-import-page__search {
|
||||
padding: 12px 12px 4px;
|
||||
background: #fff;
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.04);
|
||||
}
|
||||
|
||||
.settlement-import-page__search-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(4, minmax(0, 1fr));
|
||||
gap: 8px 24px;
|
||||
align-items: start;
|
||||
}
|
||||
|
||||
.settlement-import-page__search :deep(.el-form-item) {
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.settlement-import-page__search :deep(.el-form-item__label) {
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.settlement-import-page__search :deep(.el-input),
|
||||
.settlement-import-page__search :deep(.el-select) {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.settlement-import-page__search-actions {
|
||||
grid-column: 1 / -1;
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.settlement-import-page__table-panel {
|
||||
margin-top: 8px;
|
||||
background: #fff;
|
||||
}
|
||||
|
||||
.settlement-import-page__toolbar {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 12px 0 0;
|
||||
}
|
||||
|
||||
.settlement-import-page__toolbar-left {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.settlement-import-page__table {
|
||||
--el-table-border-color: #eff1f7;
|
||||
}
|
||||
|
||||
.settlement-import-page__table :deep(.el-table__body tr:nth-child(even) > td.el-table__cell),
|
||||
.settlement-import-page__table
|
||||
:deep(.el-table__body tr:nth-child(even) > td.el-table-fixed-column--left),
|
||||
.settlement-import-page__table
|
||||
:deep(.el-table__body tr:nth-child(even) > td.el-table-fixed-column--right) {
|
||||
background: #fafafa;
|
||||
}
|
||||
|
||||
.settlement-import-page__row-actions {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.settlement-import-page__pagination {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
padding: 12px 0;
|
||||
}
|
||||
|
||||
.settlement-import-page :deep(.basic-container__card > .el-card__body) {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
@media (max-width: 1200px) {
|
||||
.settlement-import-page__search-grid {
|
||||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 760px) {
|
||||
.settlement-import-page__search-grid {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -165,6 +165,9 @@
|
||||
<span class="oa-sync-stat__value oa-sync-stat__value--skip">{{ oaSyncProgress.skipped }}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="oaSyncProgress.dumpFilePath" class="oa-sync-meta">
|
||||
原始数据 JSON:{{ oaSyncProgress.dumpFilePath }}
|
||||
</div>
|
||||
</div>
|
||||
<template #footer>
|
||||
<span class="dialog-footer">
|
||||
@@ -253,6 +256,7 @@ export default {
|
||||
total: 0,
|
||||
synced: 0,
|
||||
skipped: 0,
|
||||
dumpFilePath: '',
|
||||
},
|
||||
parentId: 0,
|
||||
page: {
|
||||
@@ -658,7 +662,7 @@ export default {
|
||||
methods: {
|
||||
handleOaOrgFullSync() {
|
||||
this.$confirm(
|
||||
'全量同步将拉取 OA 全部公司/部门;已存在且 OA 更新时间一致的记录会跳过。是否清除后同步?选择“是”将保留顶级「外部组织」与集团锚点(id=135/GWWL),删除其余组织后再同步;选择“否”则直接全量同步。',
|
||||
'全量同步将拉取 OA 公司/部门(仅保留二级公司 oaId=2、135 及其子公司与部门);已存在且 OA 更新时间一致的记录会跳过。是否清除后同步?选择“是”将保留顶级「外部组织」与集团锚点(id=1/GWWL),删除其余组织后再同步;选择“否”则直接全量同步。',
|
||||
'全量同步',
|
||||
{
|
||||
confirmButtonText: '是',
|
||||
@@ -709,6 +713,7 @@ export default {
|
||||
total: 0,
|
||||
synced: 0,
|
||||
skipped: 0,
|
||||
dumpFilePath: '',
|
||||
};
|
||||
this.runOaOrgSyncPages();
|
||||
},
|
||||
@@ -742,6 +747,9 @@ export default {
|
||||
this.oaSyncProgress.total = page.total || 0;
|
||||
this.oaSyncProgress.synced += page.syncedCount || 0;
|
||||
this.oaSyncProgress.skipped += page.skippedCount || 0;
|
||||
if (page.dumpFilePath) {
|
||||
this.oaSyncProgress.dumpFilePath = page.dumpFilePath;
|
||||
}
|
||||
if (page.finished) {
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -364,6 +364,9 @@
|
||||
<span class="oa-sync-stat__value oa-sync-stat__value--skip">{{ oaSyncProgress.skipped }}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="oaSyncProgress.dumpFilePath" class="oa-sync-meta">
|
||||
原始数据 JSON:{{ oaSyncProgress.dumpFilePath }}
|
||||
</div>
|
||||
</div>
|
||||
<template #footer>
|
||||
<span class="dialog-footer">
|
||||
@@ -450,6 +453,7 @@ export default {
|
||||
total: 0,
|
||||
synced: 0,
|
||||
skipped: 0,
|
||||
dumpFilePath: '',
|
||||
},
|
||||
page: {
|
||||
pageSize: 10,
|
||||
@@ -712,6 +716,7 @@ export default {
|
||||
total: 0,
|
||||
synced: 0,
|
||||
skipped: 0,
|
||||
dumpFilePath: '',
|
||||
};
|
||||
this.runOaSyncPages();
|
||||
},
|
||||
@@ -746,6 +751,9 @@ export default {
|
||||
this.oaSyncProgress.total = page.total || 0;
|
||||
this.oaSyncProgress.synced += page.syncedCount || 0;
|
||||
this.oaSyncProgress.skipped += page.skippedCount || 0;
|
||||
if (page.dumpFilePath) {
|
||||
this.oaSyncProgress.dumpFilePath = page.dumpFilePath;
|
||||
}
|
||||
if (page.finished) {
|
||||
this.oaSyncStatus = 'done';
|
||||
break;
|
||||
|
||||
@@ -158,7 +158,7 @@ import { exportBlob } from '@/api/common';
|
||||
import { downloadXls } from '@/utils/util';
|
||||
import { openImportDialog } from '@/utils/import-excel';
|
||||
import { getToken, isAdministrator } from '@/utils/auth';
|
||||
import { normalizeSearchRangeParams } from '@/utils/search-range';
|
||||
import { applyIncomingSearch, normalizeSearchRangeParams } from '@/utils/search-range';
|
||||
import VehicleAttachmentTable from '@/components/vehicle-attachment-table/main.vue';
|
||||
import { mapGetters } from 'vuex';
|
||||
import { excelOption, exportColumns, option } from '@/option/vehicle/accident-record';
|
||||
@@ -550,7 +550,7 @@ export default {
|
||||
};
|
||||
},
|
||||
buildQuery(params = {}) {
|
||||
return normalizeSearchRangeParams({ ...params, ...this.query }, createTimeRangeMap);
|
||||
return normalizeSearchRangeParams(applyIncomingSearch(this.query, params), createTimeRangeMap);
|
||||
},
|
||||
handleTemplate() {
|
||||
exportBlob(
|
||||
|
||||
@@ -468,7 +468,11 @@ import {
|
||||
publish,
|
||||
changeStatus,
|
||||
} from '@/api/vehicle/credit-score-quantification';
|
||||
import { normalizeSearchRangeParams } from '@/utils/search-range';
|
||||
import {
|
||||
applyIncomingSearch,
|
||||
normalizeSearchRangeParams,
|
||||
omitInvalidListStatus,
|
||||
} from '@/utils/search-range';
|
||||
import { mapGetters } from 'vuex';
|
||||
import { isAdministrator } from '@/utils/auth';
|
||||
|
||||
@@ -1324,7 +1328,9 @@ export default {
|
||||
});
|
||||
},
|
||||
buildQuery(params = {}) {
|
||||
return normalizeSearchRangeParams({ ...params, ...this.query }, createTimeRangeMap);
|
||||
return omitInvalidListStatus(
|
||||
normalizeSearchRangeParams(applyIncomingSearch(this.query, params), createTimeRangeMap)
|
||||
);
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
@@ -208,7 +208,7 @@
|
||||
v-if="
|
||||
hasPermission('customer_archive_sync') &&
|
||||
row.approvalStatus === 'approved' &&
|
||||
row.kingdeeStatus === 'failed'
|
||||
!['synced', 'exist'].includes(row.kingdeeStatus)
|
||||
"
|
||||
@click="handleSyncKingdee(row)"
|
||||
>
|
||||
@@ -486,7 +486,7 @@
|
||||
:options="deptTree"
|
||||
:props="deptCascaderProps"
|
||||
disabled
|
||||
placeholder="桂物物流集团"
|
||||
placeholder="广西现代物流集团有限公司"
|
||||
/>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
@@ -1588,7 +1588,7 @@ const customerArchiveListRoute = '/vehicle/customer-archive';
|
||||
const customerArchiveFormRoute = '/vehicle/customer-archive/form';
|
||||
const customerArchiveListRefreshKey = 'customer-archive-list-refresh';
|
||||
/** 客商所属组织固定为顶层组织 */
|
||||
const FIXED_OWNER_DEPT_NAME = '桂物物流集团';
|
||||
const FIXED_OWNER_DEPT_NAME = '广西现代物流集团有限公司';
|
||||
|
||||
const createTimeRangeMap = {
|
||||
createTimeRange: ['createTimeStart', 'createTimeEnd'],
|
||||
@@ -1791,6 +1791,8 @@ export default {
|
||||
},
|
||||
/** 业务字典 mk_template「开启MK」=1 */
|
||||
mkEnabled: false,
|
||||
/** 是否已完成 MK 开关加载,避免提交时误判 */
|
||||
_mkEnabledResolved: false,
|
||||
formRules: {
|
||||
customerCode: [{ max: 20, message: '客商编号最多20个字符', trigger: 'blur' }],
|
||||
fullName: [{ required: true, message: '请输入客商名称', trigger: 'blur' }],
|
||||
@@ -1871,7 +1873,7 @@ export default {
|
||||
editBtn: false,
|
||||
selection: true,
|
||||
dialogClickModal: false,
|
||||
menuWidth: 320,
|
||||
menuWidth: 400,
|
||||
column: [
|
||||
{
|
||||
label: '客商编号',
|
||||
@@ -2041,7 +2043,6 @@ export default {
|
||||
search: true,
|
||||
searchOrder: 6,
|
||||
searchValue: '',
|
||||
dataType: 'number',
|
||||
minWidth: 100,
|
||||
dicData: [
|
||||
{ label: '全部', value: '' },
|
||||
@@ -2340,7 +2341,13 @@ export default {
|
||||
sessionStorage.setItem(customerArchiveListRefreshKey, '1');
|
||||
},
|
||||
hasPermission(code) {
|
||||
return this.isAdmin || this.validData(this.permission[code], false);
|
||||
if (this.isAdmin) {
|
||||
return true;
|
||||
}
|
||||
const permission = this.permission || {};
|
||||
const value = permission[code];
|
||||
// 不依赖 Avue validData,避免权限值为 true 以外的真值时被误判
|
||||
return value === true || value === 'true' || value === 1 || value === '1';
|
||||
},
|
||||
mergeSelectOptions(target, values) {
|
||||
const list = Array.isArray(values) ? values : values ? [values] : [];
|
||||
@@ -3100,12 +3107,15 @@ export default {
|
||||
});
|
||||
},
|
||||
initMkEnabled() {
|
||||
this._mkEnabledResolved = false;
|
||||
loadMkEnabled()
|
||||
.then(enabled => {
|
||||
this.mkEnabled = !!enabled;
|
||||
this._mkEnabledResolved = true;
|
||||
})
|
||||
.catch(() => {
|
||||
this.mkEnabled = false;
|
||||
this._mkEnabledResolved = true;
|
||||
});
|
||||
},
|
||||
initScoreQuantificationOptions() {
|
||||
@@ -3205,7 +3215,7 @@ export default {
|
||||
);
|
||||
},
|
||||
/**
|
||||
* 客商所属组织固定为顶层「桂物物流集团」,表单不可编辑。
|
||||
* 客商所属组织固定为顶层「广西现代物流集团有限公司」,表单不可编辑。
|
||||
*/
|
||||
applyFixedOwnerDept() {
|
||||
if (!this.isArchivePage || this.readonly) {
|
||||
@@ -4481,15 +4491,34 @@ export default {
|
||||
});
|
||||
},
|
||||
/**
|
||||
* 列表 / 新增 / 编辑提交共用:开启 MK 时提交审核流,并更新客商审批状态
|
||||
* 列表 / 新增 / 编辑提交共用:
|
||||
* 未开启 MK 时仅走本地提交审批(与新增一致,不请求 MK 流程);
|
||||
* 开启 MK 时先提交审核流,再更新客商审批状态。
|
||||
*/
|
||||
submitCustomerApproval(id, fullName = '', approvalStatus = '') {
|
||||
return submitMkApprovalFlow({
|
||||
bizType: 'customer-archive',
|
||||
bizId: id,
|
||||
subjectName: fullName,
|
||||
approvalStatus,
|
||||
}).then(() => submitApproval(id));
|
||||
const submitLocal = () => submitApproval(id);
|
||||
const run = enabled => {
|
||||
this.mkEnabled = !!enabled;
|
||||
this._mkEnabledResolved = true;
|
||||
if (!this.mkEnabled) {
|
||||
return submitLocal();
|
||||
}
|
||||
return submitMkApprovalFlow({
|
||||
bizType: 'customer-archive',
|
||||
bizId: id,
|
||||
subjectName: fullName,
|
||||
approvalStatus,
|
||||
mkEnabled: true,
|
||||
}).then(submitLocal);
|
||||
};
|
||||
|
||||
// 与列表按钮一致:以页面 mkEnabled 为准,未开启时不请求 MK
|
||||
if (this._mkEnabledResolved) {
|
||||
return run(this.mkEnabled);
|
||||
}
|
||||
return loadMkEnabled()
|
||||
.then(enabled => run(enabled))
|
||||
.catch(() => run(false));
|
||||
},
|
||||
openReviewScore() {
|
||||
const scores = this.archiveForm.scores || [];
|
||||
@@ -5267,19 +5296,9 @@ export default {
|
||||
this.onLoad(this.page);
|
||||
},
|
||||
searchChange(params, done) {
|
||||
// 搜索区所属组织级联返回的是 ID 路径数组,取末级 ID 转成部门名称传给后端(与列表接口 deptName 字段一致)
|
||||
if (Array.isArray(params.deptName)) {
|
||||
const path = params.deptName.filter(
|
||||
item => item !== undefined && item !== null && item !== ''
|
||||
);
|
||||
const deptId = path.length ? path[path.length - 1] : '';
|
||||
params.deptName = deptId
|
||||
? this.deptOptions.find(item => String(item.value) === String(deptId))?.rawLabel || ''
|
||||
: '';
|
||||
}
|
||||
this.query = this.buildQuery(params);
|
||||
this.page.currentPage = 1;
|
||||
this.onLoad(this.page);
|
||||
this.onLoad(this.page, this.query);
|
||||
done();
|
||||
},
|
||||
selectionChange(list) {
|
||||
@@ -5340,7 +5359,38 @@ export default {
|
||||
};
|
||||
},
|
||||
buildQuery(params = {}) {
|
||||
return normalizeSearchRangeParams({ ...params, ...this.query }, createTimeRangeMap);
|
||||
// 新条件覆盖旧条件;本次显式清空的字段必须从查询里去掉,避免第二次搜索仍带着上一次的客商名称。
|
||||
const merged = { ...this.query, ...params };
|
||||
Object.keys(params).forEach(key => {
|
||||
const value = params[key];
|
||||
if (
|
||||
value === '' ||
|
||||
value === null ||
|
||||
value === undefined ||
|
||||
(Array.isArray(value) && value.length === 0)
|
||||
) {
|
||||
delete merged[key];
|
||||
}
|
||||
});
|
||||
if (Array.isArray(merged.deptName)) {
|
||||
const path = merged.deptName.filter(
|
||||
item => item !== undefined && item !== null && item !== ''
|
||||
);
|
||||
const deptId = path.length ? path[path.length - 1] : '';
|
||||
merged.deptName = deptId
|
||||
? this.deptOptions.find(item => String(item.value) === String(deptId))?.rawLabel || ''
|
||||
: '';
|
||||
if (!merged.deptName) {
|
||||
delete merged.deptName;
|
||||
}
|
||||
}
|
||||
const values = normalizeSearchRangeParams(merged, createTimeRangeMap);
|
||||
// 状态下拉「全部」会被数字类型转成 0/-1,二次搜索会把列表滤空。
|
||||
const status = values.status;
|
||||
if (status === 0 || status === '0' || status === -1 || status === '-1') {
|
||||
delete values.status;
|
||||
}
|
||||
return values;
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
@@ -220,7 +220,7 @@ import { downloadXls } from '@/utils/util';
|
||||
import { handleImportExcel } from '@/utils/import-excel';
|
||||
import { getToken, isAdministrator } from '@/utils/auth';
|
||||
import { getUploadHeaders } from '@/utils/upload';
|
||||
import { normalizeSearchRangeParams } from '@/utils/search-range';
|
||||
import { applyIncomingSearch, normalizeSearchRangeParams } from '@/utils/search-range';
|
||||
import { mapGetters } from 'vuex';
|
||||
import { ElLoading } from 'element-plus';
|
||||
import { excelOption, exportColumns, option } from '@/option/vehicle/insurance-record';
|
||||
@@ -409,7 +409,7 @@ export default {
|
||||
: integerPart;
|
||||
},
|
||||
buildQuery(params = {}) {
|
||||
return normalizeSearchRangeParams({ ...params, ...this.query }, createTimeRangeMap);
|
||||
return normalizeSearchRangeParams(applyIncomingSearch(this.query, params), createTimeRangeMap);
|
||||
},
|
||||
validateRow(row) {
|
||||
if (row.startDate && row.endDate && row.endDate < row.startDate) {
|
||||
|
||||
@@ -195,7 +195,7 @@ import { downloadXls } from '@/utils/util';
|
||||
import { openImportDialog } from '@/utils/import-excel';
|
||||
import { formatUpdateUserName } from '@/utils/audit';
|
||||
import { getToken, isAdministrator } from '@/utils/auth';
|
||||
import { normalizeSearchRangeParams } from '@/utils/search-range';
|
||||
import { applyIncomingSearch, normalizeSearchRangeParams } from '@/utils/search-range';
|
||||
import AddressMapPicker from '@/components/address-map-picker/main.vue';
|
||||
import VehicleAttachmentTable from '@/components/vehicle-attachment-table/main.vue';
|
||||
import { mapGetters } from 'vuex';
|
||||
@@ -903,7 +903,7 @@ export default {
|
||||
};
|
||||
},
|
||||
buildQuery(params = {}) {
|
||||
return normalizeSearchRangeParams({ ...params, ...this.query }, createTimeRangeMap);
|
||||
return normalizeSearchRangeParams(applyIncomingSearch(this.query, params), createTimeRangeMap);
|
||||
},
|
||||
handleTemplate() {
|
||||
exportBlob(
|
||||
|
||||
@@ -168,7 +168,7 @@ import { downloadXls } from '@/utils/util';
|
||||
import { openImportDialog } from '@/utils/import-excel';
|
||||
import { formatUpdateUserName } from '@/utils/audit';
|
||||
import { getToken, isAdministrator } from '@/utils/auth';
|
||||
import { normalizeSearchRangeParams } from '@/utils/search-range';
|
||||
import { applyIncomingSearch, normalizeSearchRangeParams } from '@/utils/search-range';
|
||||
import AddressMapPicker from '@/components/address-map-picker/main.vue';
|
||||
import VehicleAttachmentTable from '@/components/vehicle-attachment-table/main.vue';
|
||||
import { mapGetters } from 'vuex';
|
||||
@@ -371,9 +371,8 @@ export default {
|
||||
placeholder: '日期时间选择器',
|
||||
},
|
||||
{
|
||||
label: '里程/航程数(公里)',
|
||||
label: '里程/航程数',
|
||||
prop: 'mileage',
|
||||
renderHeader: () => '里程/航程数',
|
||||
type: 'input',
|
||||
minWidth: 130,
|
||||
slot: true,
|
||||
@@ -391,6 +390,7 @@ export default {
|
||||
],
|
||||
value: '公里',
|
||||
hide: true,
|
||||
display: false,
|
||||
},
|
||||
{
|
||||
label: '备注',
|
||||
@@ -565,12 +565,10 @@ export default {
|
||||
const mileageUnit = vehicleType === '船舶' ? '海里' : '公里';
|
||||
const vehicleTypeColumn = this.findColumn(this.option.column, 'vehicleType');
|
||||
const vehicleNoColumn = this.findColumn(this.option.column, 'vehicleNo');
|
||||
const mileageColumn = this.findColumn(this.option.column, 'mileage');
|
||||
const mileageUnitColumn = this.findColumn(this.option.column, 'mileageUnit');
|
||||
vehicleNoColumn.placeholder =
|
||||
vehicleType === '船舶' ? '请选择船号' : '请选择车牌号';
|
||||
vehicleTypeColumn.disabled = Boolean(this.form.id);
|
||||
mileageColumn.label = `里程/航程数(${mileageUnit})`;
|
||||
mileageUnitColumn.value = mileageUnit;
|
||||
this.form.mileageUnit = mileageUnit;
|
||||
if (!this.isDetailLoading && oldValue && vehicleType !== oldValue) {
|
||||
@@ -833,7 +831,7 @@ export default {
|
||||
};
|
||||
},
|
||||
buildQuery(params = {}) {
|
||||
return normalizeSearchRangeParams({ ...params, ...this.query }, createTimeRangeMap);
|
||||
return normalizeSearchRangeParams(applyIncomingSearch(this.query, params), createTimeRangeMap);
|
||||
},
|
||||
handleTemplate() {
|
||||
exportBlob(
|
||||
|
||||
@@ -210,7 +210,7 @@ import { exportBlob } from '@/api/common';
|
||||
import { downloadXls } from '@/utils/util';
|
||||
import { openImportDialog } from '@/utils/import-excel';
|
||||
import { getToken, isAdministrator } from '@/utils/auth';
|
||||
import { normalizeSearchRangeParams } from '@/utils/search-range';
|
||||
import { applyIncomingSearch, normalizeSearchRangeParams } from '@/utils/search-range';
|
||||
import VehicleAttachmentTable from '@/components/vehicle-attachment-table/main.vue';
|
||||
import { mapGetters } from 'vuex';
|
||||
import { excelOption, exportColumns, option } from '@/option/vehicle/mileage-record';
|
||||
@@ -607,7 +607,7 @@ export default {
|
||||
},
|
||||
buildQuery(params = {}) {
|
||||
return normalizeSearchRangeParams(
|
||||
{ ...params, ...this.query, vehicleType: '车辆' },
|
||||
{ ...applyIncomingSearch(this.query, params), vehicleType: '车辆' },
|
||||
createTimeRangeMap
|
||||
);
|
||||
},
|
||||
|
||||
@@ -155,7 +155,7 @@ import { exportBlob } from '@/api/common';
|
||||
import { downloadXls } from '@/utils/util';
|
||||
import { openImportDialog } from '@/utils/import-excel';
|
||||
import { getToken, isAdministrator } from '@/utils/auth';
|
||||
import { normalizeSearchRangeParams } from '@/utils/search-range';
|
||||
import { applyIncomingSearch, normalizeSearchRangeParams } from '@/utils/search-range';
|
||||
import VehicleAttachmentTable from '@/components/vehicle-attachment-table/main.vue';
|
||||
import { mapGetters } from 'vuex';
|
||||
import { excelOption, exportColumns, option } from '@/option/vehicle/tire-replacement-record';
|
||||
@@ -500,7 +500,7 @@ export default {
|
||||
};
|
||||
},
|
||||
buildQuery(params = {}) {
|
||||
return normalizeSearchRangeParams({ ...params, ...this.query }, createTimeRangeMap);
|
||||
return normalizeSearchRangeParams(applyIncomingSearch(this.query, params), createTimeRangeMap);
|
||||
},
|
||||
handleTemplate() {
|
||||
exportBlob(
|
||||
|
||||
@@ -130,7 +130,7 @@ import { exportBlob } from '@/api/common';
|
||||
import { downloadXls } from '@/utils/util';
|
||||
import { openImportDialog } from '@/utils/import-excel';
|
||||
import { getToken, isAdministrator } from '@/utils/auth';
|
||||
import { normalizeSearchRangeParams } from '@/utils/search-range';
|
||||
import { applyIncomingSearch, normalizeSearchRangeParams } from '@/utils/search-range';
|
||||
import VehicleAttachmentTable from '@/components/vehicle-attachment-table/main.vue';
|
||||
import { mapGetters } from 'vuex';
|
||||
import { excelOption, exportColumns, option } from '@/option/vehicle/transport-change-record';
|
||||
@@ -478,7 +478,7 @@ export default {
|
||||
};
|
||||
},
|
||||
buildQuery(params = {}) {
|
||||
return normalizeSearchRangeParams({ ...params, ...this.query }, createTimeRangeMap);
|
||||
return normalizeSearchRangeParams(applyIncomingSearch(this.query, params), createTimeRangeMap);
|
||||
},
|
||||
handleTemplate() {
|
||||
exportBlob(
|
||||
|
||||
@@ -197,7 +197,7 @@ import { exportBlob } from '@/api/common';
|
||||
import { downloadXls } from '@/utils/util';
|
||||
import { openImportDialog } from '@/utils/import-excel';
|
||||
import { getToken, isAdministrator } from '@/utils/auth';
|
||||
import { normalizeSearchRangeParams } from '@/utils/search-range';
|
||||
import { applyIncomingSearch, normalizeSearchRangeParams } from '@/utils/search-range';
|
||||
import AddressMapPicker from '@/components/address-map-picker/main.vue';
|
||||
import VehicleAttachmentTable from '@/components/vehicle-attachment-table/main.vue';
|
||||
import { mapGetters } from 'vuex';
|
||||
@@ -673,7 +673,7 @@ export default {
|
||||
};
|
||||
},
|
||||
buildQuery(params = {}) {
|
||||
return normalizeSearchRangeParams({ ...params, ...this.query }, createTimeRangeMap);
|
||||
return normalizeSearchRangeParams(applyIncomingSearch(this.query, params), createTimeRangeMap);
|
||||
},
|
||||
handleTemplate() {
|
||||
exportBlob(
|
||||
|
||||
+22
-22
@@ -49,28 +49,28 @@ export default ({ mode, command }) => {
|
||||
__VUE_I18N_LEGACY_API__: true,
|
||||
__INTLIFY_PROD_DEVTOOLS__: false,
|
||||
},
|
||||
server: {
|
||||
port: 2888,
|
||||
proxy: {
|
||||
'/api': {
|
||||
// 本地网关默认 80 端口(blade-gateway bootstrap.yml);开发时需先启动网关及 blade-auth/blade-system 等服务
|
||||
// 注意:本机 8080 一般是 Nacos,不是业务网关
|
||||
target: 'http://localhost',
|
||||
// target: 'http://172.16.203.228:8000', // 仅联调远程网关时启用(且不要 rewrite)
|
||||
changeOrigin: true,
|
||||
rewrite: path => path.replace(/^\/api/, ''),
|
||||
},
|
||||
},
|
||||
},
|
||||
//server: {
|
||||
// port: 2889,
|
||||
// proxy: {
|
||||
// '/api': {
|
||||
// target: 'http://172.16.203.221:8000',
|
||||
// changeOrigin: true,
|
||||
// },
|
||||
// },
|
||||
//},
|
||||
server: {
|
||||
port: 2888,
|
||||
proxy: {
|
||||
'/api': {
|
||||
// 本地网关默认 80 端口(blade-gateway bootstrap.yml);开发时需先启动网关及 blade-auth/blade-system 等服务
|
||||
// 注意:本机 8080 一般是 Nacos,不是业务网关
|
||||
target: 'http://localhost',
|
||||
// target: 'http://172.16.203.228:8000', // 仅联调远程网关时启用(且不要 rewrite)
|
||||
changeOrigin: true,
|
||||
rewrite: path => path.replace(/^\/api/, ''),
|
||||
},
|
||||
},
|
||||
},
|
||||
// server: {
|
||||
// port: 2889,
|
||||
// proxy: {
|
||||
// '/api': {
|
||||
// target: 'http://172.16.203.228:8000',
|
||||
// changeOrigin: true,
|
||||
// },
|
||||
// },
|
||||
// },
|
||||
resolve: {
|
||||
alias: {
|
||||
'~': resolve(__dirname, './'),
|
||||
|
||||
Reference in New Issue
Block a user