diff --git a/src/api/business/contract-manage.js b/src/api/business/contract-manage.js index 872a5ea..b7ce897 100644 --- a/src/api/business/contract-manage.js +++ b/src/api/business/contract-manage.js @@ -36,6 +36,8 @@ export const approve = id => url: `${baseUrl}/approve`, method: 'post', params: { id }, + // 审批通过后同步推送合规(附件转传可达 20s+),覆盖全局 10s 超时 + timeout: 120000, }); export const reject = id => diff --git a/src/api/business/hcs.js b/src/api/business/hcs.js new file mode 100644 index 0000000..47012a2 --- /dev/null +++ b/src/api/business/hcs.js @@ -0,0 +1,96 @@ +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, + }); + +// 客商风险批量查询(客商列表页展示用):入参编号数组,后端逐个查 C101-1,单个失败跳过 +export const getRiskBatch = customerCodes => + request({ + url: `${baseUrl}/risk/batch`, + method: 'get', + params: { customerCodes: (customerCodes || []).join(',') }, + timeout: 60000, + }); + +// 客商风险按名称查询(客商档案表单展示用):名称反查编号后口径同编号查询 +export const getRiskByName = customerName => + request({ + url: `${baseUrl}/risk/by-name`, + method: 'get', + params: { customerName }, + 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; +}; diff --git a/src/api/business/loading-manage.js b/src/api/business/loading-manage.js index 5d116bc..2fef6a5 100644 --- a/src/api/business/loading-manage.js +++ b/src/api/business/loading-manage.js @@ -24,11 +24,16 @@ export const getDetail = id => }, }); -export const getCarrierContracts = projectIds => +export const getCarrierContracts = (projectIds, waybillIds = []) => request({ url: `${baseUrl}/carrier-contracts`, method: 'get', - params: { projectIds: (projectIds || []).join(',') }, + params: { + projectIds: (projectIds || []).join(','), + ...(waybillIds || []).filter(Boolean).length + ? { waybillIds: (waybillIds || []).filter(Boolean).join(',') } + : {}, + }, }); export const saveDraft = row => diff --git a/src/api/business/project-apply.js b/src/api/business/project-apply.js index 83ac915..bf12e6a 100644 --- a/src/api/business/project-apply.js +++ b/src/api/business/project-apply.js @@ -43,6 +43,8 @@ export const approve = id => url: `${baseUrl}/approve`, method: 'post', params: { id }, + // 审批通过后同步推送合规(远程多次调用),覆盖全局 10s 超时 + timeout: 60000, }); export const reject = id => diff --git a/src/api/business/waybill-manage.js b/src/api/business/waybill-manage.js index ac31975..e90982c 100644 --- a/src/api/business/waybill-manage.js +++ b/src/api/business/waybill-manage.js @@ -14,6 +14,13 @@ export const cancel = api.cancel; export const reassign = api.reassign; export const complete = api.complete; export const batchComplete = api.batchComplete; +/** 确认回单:回单节点配成「需业务员确认」时用;后端校验司机打卡节点齐全且无未了结异常后才置完成 */ +export const confirmReturn = id => + request({ + url: `${baseUrl}/confirm-return`, + method: 'post', + params: { id }, + }); export const changeRoute = data => request({ url: `${baseUrl}/change-route`, diff --git a/src/api/common.js b/src/api/common.js index 854e4d2..7e03551 100644 --- a/src/api/common.js +++ b/src/api/common.js @@ -1,6 +1,9 @@ import request from '@/axios'; import { ElLoading, ElMessage } from 'element-plus'; +/** 审核按钮统一超时:审批通过后可能同步推送金蝶等耗时操作,覆盖全局 10s */ +export const APPROVE_TIMEOUT = 30000; + /** * 文件流返回 * @param url 接口地址 diff --git a/src/api/payment/invoiceApplication.js b/src/api/payment/invoiceApplication.js index 717703d..af6fb87 100644 --- a/src/api/payment/invoiceApplication.js +++ b/src/api/payment/invoiceApplication.js @@ -1,4 +1,5 @@ import request from '@/axios'; +import { APPROVE_TIMEOUT } from '@/api/common'; const baseUrl = '/blade-transport/invoice-application'; @@ -18,7 +19,8 @@ export const getReceiverInformation = settlementIds => 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 }); -export const approve = data => request({ url: `${baseUrl}/approve`, method: 'post', data }); +export const approve = data => + request({ url: `${baseUrl}/approve`, method: 'post', data, timeout: APPROVE_TIMEOUT }); export const returnBill = data => request({ url: `${baseUrl}/return`, method: 'post', data }); export const voidBill = data => request({ url: `${baseUrl}/void`, method: 'post', data }); export const syncKingdee = id => diff --git a/src/api/payment/invoiceReceipt.js b/src/api/payment/invoiceReceipt.js index 071eff1..8db2dfb 100644 --- a/src/api/payment/invoiceReceipt.js +++ b/src/api/payment/invoiceReceipt.js @@ -1,4 +1,5 @@ import request from '@/axios'; +import { APPROVE_TIMEOUT } from '@/api/common'; const baseUrl = '/blade-transport/invoice-receipt'; @@ -31,7 +32,8 @@ export const getReferenceInformation = settlementIds => 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 }); -export const approve = data => request({ url: `${baseUrl}/approve`, method: 'post', data }); +export const approve = data => + request({ url: `${baseUrl}/approve`, method: 'post', data, timeout: APPROVE_TIMEOUT }); export const returnBill = data => request({ url: `${baseUrl}/return`, method: 'post', data }); export const voidBill = data => request({ url: `${baseUrl}/void`, method: 'post', data }); export const pushKingdee = ids => diff --git a/src/api/payment/paymentApplication.js b/src/api/payment/paymentApplication.js index 59c9aa1..e464552 100644 --- a/src/api/payment/paymentApplication.js +++ b/src/api/payment/paymentApplication.js @@ -1,4 +1,5 @@ import request from '@/axios'; +import { APPROVE_TIMEOUT } from '@/api/common'; const baseUrl = '/blade-transport/payment-application'; export const getList = (current, size, params) => @@ -20,7 +21,8 @@ export const getProjectAdvanceWriteOffOptions = 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 }); -export const approve = data => request({ url: `${baseUrl}/approve`, method: 'post', data }); +export const approve = data => + request({ url: `${baseUrl}/approve`, method: 'post', data, timeout: APPROVE_TIMEOUT }); export const returnBill = data => request({ url: `${baseUrl}/return`, method: 'post', data }); export const voidBill = data => request({ url: `${baseUrl}/void`, method: 'post', data }); // 同步金蝶涉及多次金蝶远程接口调用(A103-2 预检 + A103-1/A103-3 推送),单笔可达 20s+,覆盖全局 10s 超时 diff --git a/src/api/settlement/formalSettlement.js b/src/api/settlement/formalSettlement.js index ea4cb14..2d980d4 100644 --- a/src/api/settlement/formalSettlement.js +++ b/src/api/settlement/formalSettlement.js @@ -1,4 +1,5 @@ import request from '@/axios'; +import { APPROVE_TIMEOUT } from '@/api/common'; const baseUrl = '/blade-transport/formal-settlement'; @@ -27,7 +28,8 @@ export const getCandidateDetails = (current, size, 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 }); -export const approve = data => request({ url: `${baseUrl}/approve`, method: 'post', data }); +export const approve = data => + request({ url: `${baseUrl}/approve`, method: 'post', data, timeout: APPROVE_TIMEOUT }); export const returnBill = data => request({ url: `${baseUrl}/return`, method: 'post', data }); export const voidBill = data => request({ url: `${baseUrl}/void`, method: 'post', data }); // 同步金蝶涉及多次金蝶远程接口调用(A102-4 预检 + A102-2 保存),可达 20s+,覆盖全局 10s 超时 diff --git a/src/api/settlement/preSettlement.js b/src/api/settlement/preSettlement.js index e35f2a6..9aa2755 100644 --- a/src/api/settlement/preSettlement.js +++ b/src/api/settlement/preSettlement.js @@ -1,4 +1,5 @@ import request from '@/axios'; +import { APPROVE_TIMEOUT } from '@/api/common'; const baseUrl = '/blade-transport/pre-settlement'; @@ -42,7 +43,8 @@ export const removeDetail = (id, detailId) => export const submit = data => request({ url: `${baseUrl}/submit`, method: 'post', data }); -export const approve = data => request({ url: `${baseUrl}/approve`, method: 'post', data }); +export const approve = data => + request({ url: `${baseUrl}/approve`, method: 'post', data, timeout: APPROVE_TIMEOUT }); export const returnBill = data => request({ url: `${baseUrl}/return`, method: 'post', data }); diff --git a/src/api/system/business-process.js b/src/api/system/business-process.js index 5d0f502..0ea48d5 100644 --- a/src/api/system/business-process.js +++ b/src/api/system/business-process.js @@ -1,5 +1,8 @@ import request from '@/axios'; +/** 提交审核流(MK)统一超时:客商/项目/合同/预结算/正式结算/付款/开票/收票/汇票付款 */ +export const MK_SUBMIT_TIMEOUT = 30000; + /** * 调用 MK processSubmit 提交审核流 * @param {Object} data @@ -15,6 +18,7 @@ export const processSubmit = data => { url: '/blade-system/businessProcess/processSubmit', method: 'post', data, + timeout: MK_SUBMIT_TIMEOUT, }); }; @@ -28,6 +32,7 @@ export const processDelete = data => { url: '/blade-system/businessProcess/processDelete', method: 'post', data, + timeout: MK_SUBMIT_TIMEOUT, }); }; diff --git a/src/api/system/dept.js b/src/api/system/dept.js index c7f22de..2964557 100644 --- a/src/api/system/dept.js +++ b/src/api/system/dept.js @@ -139,3 +139,10 @@ export const getPlatformCompanySelect = () => { method: 'get', }); }; + +export const getBusinessDeptSelect = () => { + return request({ + url: '/blade-system/dept/business-dept-select', + method: 'get', + }); +}; diff --git a/src/api/system/roleDataScope.js b/src/api/system/roleDataScope.js new file mode 100644 index 0000000..5d0e4f1 --- /dev/null +++ b/src/api/system/roleDataScope.js @@ -0,0 +1,19 @@ +import request from '@/axios'; + +export const getRoleMenuDataScopeTree = roleId => { + return request({ + url: '/blade-system/role-menu-data-scope/tree', + method: 'get', + params: { + roleId, + }, + }); +}; + +export const submitRoleMenuDataScope = data => { + return request({ + url: '/blade-system/role-menu-data-scope/submit', + method: 'post', + data, + }); +}; diff --git a/src/axios.js b/src/axios.js index a3de4a3..455eb3d 100644 --- a/src/axios.js +++ b/src/axios.js @@ -105,12 +105,27 @@ const isJsonContentType = res => { return contentType.includes('application/json') || contentType.includes('text/json'); }; +/** + * 是否为可按文本读取的 Blob / Blob-like(含跨 realm)。 + * 禁止对普通业务 JSON 对象调用 .text()。 + */ +const isBlobLikeData = value => + value != null && + typeof value === 'object' && + typeof value.size === 'number' && + typeof value.type === 'string' && + typeof value.text === 'function'; + /** * 判断响应体是否已是解析好的对象(无需再解析)。 * 注意 Blob 也是 object,必须排除,否则会把待解析的文件流误判为已完成。 */ const isPlainObject = value => - value !== null && typeof value === 'object' && !(value instanceof Blob); + value !== null && + typeof value === 'object' && + !(value instanceof Blob) && + !isBlobLikeData(value) && + !(value instanceof ArrayBuffer); //跨域请求,允许保存cookie axios.defaults.withCredentials = true; // NProgress Configuration @@ -190,15 +205,20 @@ axios.interceptors.response.use( // 用户就会以为系统崩了——而后端其实返回了明确的业务提示(如「未读取到数据行…」)。 // 这里按 Content-Type 判断:JSON 才解析回对象;真·文件流(Excel 等)保持原样不动。 // 兼容 Blob(浏览器 responseType='blob')与 string(非浏览器环境)两种形态。 + // 仅对真正的 Blob/Blob-like 调 .text(),避免业务 JSON 已解析后出现 + // 「e.data.text is not a function」。 if (isJsonContentType(res) && !isPlainObject(res.data)) { try { let text = ''; - if (typeof res.data?.text === 'function') { - text = await res.data.text(); - } else if (typeof res.data === 'string') { - text = res.data; - } else if (typeof res.data?.arrayBuffer === 'function') { - text = new TextDecoder().decode(await res.data.arrayBuffer()); + const raw = res.data; + if (isBlobLikeData(raw) || (typeof Blob !== 'undefined' && raw instanceof Blob)) { + text = await raw.text(); + } else if (typeof raw === 'string') { + text = raw; + } else if (raw && typeof raw.arrayBuffer === 'function') { + text = new TextDecoder().decode(await raw.arrayBuffer()); + } else if (raw instanceof ArrayBuffer) { + text = new TextDecoder().decode(raw); } const parsed = typeof text === 'string' && text ? JSON.parse(text) : null; if (parsed && typeof parsed === 'object') { @@ -208,10 +228,11 @@ axios.interceptors.response.use( // 声明是 JSON 却解析失败:保持原样,交给下游按文件处理 } } - //获取状态信息 - const status = res.data.error_code || res.data.code || res.status; + // 获取状态信息:文件流没有 code/msg,必须退回 HTTP status,禁止读 Blob 上的幽灵字段 + const payload = isPlainObject(res.data) ? res.data : {}; + const status = Number(payload.error_code || payload.code || res.status); const statusWhiteList = website.statusWhiteList || []; - const message = res.data.msg || res.data.error_description || '系统错误'; + const message = payload.msg || payload.error_description || '系统错误'; //如果在白名单里则自行catch逻辑处理 if (statusWhiteList.includes(status)) return Promise.reject(res); diff --git a/src/mixins/organization-search.js b/src/mixins/organization-search.js index 5842986..fd06b59 100644 --- a/src/mixins/organization-search.js +++ b/src/mixins/organization-search.js @@ -12,6 +12,8 @@ import { h } from 'vue'; * 1. 混入本 mixin:mixins: [organizationSearch] * 2. option 中「所属组织」列加上 searchslot: true(prop 为 organizationName 或 deptName 均可) * 3. 在 created / 初始化时调用 this.loadOrganizationOptions() + * 4. 需要「未分配组织」筛选项的页面在 data 里置 enableUnassignedOrgFilter: true; + * 该项只追加进搜索树 organizationSearchTreeOptions,表单级联 organizationTreeOptions 不含哨兵 * * 说明: * - 自动识别 organizationName / deptName 两种 prop,并挂载 column.renderSearch @@ -26,6 +28,12 @@ import { h } from 'vue'; */ const ORG_PROPS = ['organizationName', 'deptName']; +// 「未分配组织」筛选项的哨兵值:不是真实组织名,选中后提交给后端的就是这串字面量。 +// 后端 TransportBusiConstant.UNASSIGNED_ORG_FILTER、DriverMapper.xml / TransportVehicleMapper.xml +// 的 organizationName 分支以及司机、车辆导出查询都按它改判为 organization_name = ''; +// 后端换成空串会让整段组织条件不生效,筛选静默失效。前端与后端这几处必须同步修改。 +const UNASSIGNED_ORG_FILTER = '__UNASSIGNED_ORG__'; + export default { data() { return { @@ -34,9 +42,18 @@ export default { organizationTreeOptions: [], organizationTreeFlatOptions: [], organizationLoading: false, + // 组织筛选是否追加「未分配组织」一项;页面在 data 里覆盖为 true 才生效 + enableUnassignedOrgFilter: false, }; }, computed: { + // 搜索栏级联的选项:开关打开时在真实组织最前面追加「未分配组织」。 + // 不能直接改 organizationTreeOptions——司机/车辆页的编辑表单也用这棵树选组织。 + organizationSearchTreeOptions() { + return this.enableUnassignedOrgFilter + ? [this.unassignedOrgOption(), ...this.organizationTreeOptions] + : this.organizationTreeOptions; + }, // 所属组织级联配置:checkStrictly 允许选中任意层级,不必选到末级 organizationCascaderProps() { return { @@ -61,7 +78,7 @@ export default { ? this.excludeExternalOrganization(source) : source; this.organizationTreeOptions = this.normalizeOrgTree(tree); - this.organizationTreeFlatOptions = this.flattenOrgTree(this.organizationTreeOptions); + this.organizationTreeFlatOptions = this.flattenOrgTree(this.organizationSearchTreeOptions); const option = this.tableOption || this.option; if (!option) return; ORG_PROPS.forEach(prop => { @@ -100,12 +117,20 @@ export default { }; }); }, + // 哨兵节点的 id 与 rawLabel 都取哨兵值,resolveOrgName 取到的仍是哨兵而不是某个真实组织名 + unassignedOrgOption() { + return { id: UNASSIGNED_ORG_FILTER, label: '未分配组织', rawLabel: UNASSIGNED_ORG_FILTER }; + }, flattenOrgTree(tree = []) { return (tree || []).flatMap(item => [ { id: String(item.id), label: item.label, rawLabel: item.rawLabel }, ...this.flattenOrgTree(item.children || []), ]); }, + // 组织列渲染用:空串或纯空白即「未分配组织」(组织列 NOT NULL,无组织存空串) + isBlankOrganizationName(value) { + return String(value ?? '').trim() === ''; + }, // 兼容两种入参:级联路径数组(取末级)或单个部门 id findOrgById(id) { const value = Array.isArray(id) ? id.at(-1) : id; @@ -155,7 +180,7 @@ export default { // 级联单选:modelValue 必须是 id 路径数组,否则控件内部状态会错乱导致无法再次选中 if (scope.row) scope.row[prop] = Array.isArray(value) ? value : []; }, - options: this.organizationTreeOptions, + options: this.organizationSearchTreeOptions, props: { label: 'label', value: 'id', diff --git a/src/option/business/common.js b/src/option/business/common.js index 8221c47..209aed5 100644 --- a/src/option/business/common.js +++ b/src/option/business/common.js @@ -1,4 +1,4 @@ -export const transportTypeOptions = [ +export const transportTypeOptions = [ { label: '公路运输', value: '公路运输' }, { label: '铁路运输', value: '铁路运输' }, { label: '水路运输', value: '水路运输' }, @@ -42,6 +42,7 @@ export const waybillStatusOptions = [ { label: '草稿', value: 'draft' }, { label: '待执行', value: 'pending' }, { label: '进行中', value: 'running' }, + { label: '待回单确认', value: 'pending_return_confirm' }, { label: '已完成', value: 'completed' }, { label: '已取消', value: 'cancelled' }, ]; @@ -143,6 +144,17 @@ export const contractArchiveStatusOptions = [ { label: '已归档', value: '已归档' }, ]; +export const hcsSyncStatusOptions = [ + { label: '待推送', value: 'unsynced' }, + { label: '已推送', value: 'synced' }, + { label: '推送失败', value: 'failed' }, +]; + +export const yesNoOptions = [ + { label: '是', value: true }, + { label: '否', value: false }, +]; + export const nodeOptions = [ { label: '接单', value: '接单' }, { label: '到场', value: '到场' }, diff --git a/src/option/business/contract-manage.js b/src/option/business/contract-manage.js index e59adf3..9efc345 100644 --- a/src/option/business/contract-manage.js +++ b/src/option/business/contract-manage.js @@ -7,9 +7,11 @@ import { createCrudOption, effectiveTypeOptions, nonNegativeRule, + hcsSyncStatusOptions, selectRule, signTypeOptions, textRule, + yesNoOptions, } from './common'; const contractSettlementModeOptions = [ @@ -606,6 +608,50 @@ 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: '金蝶合同ID', + prop: 'kingdeeId', + minWidth: 130, + addDisplay: false, + editDisplay: false, + }, + { + label: '是否同步金蝶', + prop: 'kingdeeSynced', + type: 'select', + search: true, + searchPlaceholder: '请选择', + searchOrder: 7, + dicData: yesNoOptions, + hide: true, + addDisplay: false, + editDisplay: false, + }, + { + label: '是否同步合规', + prop: 'hcsSynced', + type: 'select', + search: true, + searchPlaceholder: '请选择', + searchOrder: 8, + dicData: yesNoOptions, + hide: true, + addDisplay: false, + editDisplay: false, + }, { label: '当前节点', prop: 'currentNode', diff --git a/src/option/business/project-apply.js b/src/option/business/project-apply.js index 502032e..08c0eef 100644 --- a/src/option/business/project-apply.js +++ b/src/option/business/project-apply.js @@ -3,6 +3,7 @@ import { businessModeOptions, businessTypeOptions, createCrudOption, + hcsSyncStatusOptions, nonNegativeRule, projectApprovalStatusOptions, projectSourceOptions, @@ -10,6 +11,7 @@ import { selectRule, settlementModeOptions, textRule, + yesNoOptions, } from './common'; const projectEffectiveTypeOptions = [ @@ -406,6 +408,57 @@ 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: '金蝶项目ID', + prop: 'kingdeeId', + minWidth: 130, + addDisplay: false, + editDisplay: false, + }, + { + label: '金蝶项目编号', + prop: 'kingdeeNumber', + minWidth: 130, + addDisplay: false, + editDisplay: false, + }, + { + label: '是否同步金蝶', + prop: 'kingdeeSynced', + type: 'select', + search: true, + searchPlaceholder: '请选择', + searchOrder: 2, + dicData: yesNoOptions, + hide: true, + addDisplay: false, + editDisplay: false, + }, + { + label: '是否同步合规', + prop: 'hcsSynced', + type: 'select', + search: true, + searchPlaceholder: '请选择', + searchOrder: 3, + dicData: yesNoOptions, + hide: true, + addDisplay: false, + editDisplay: false, + }, { label: '生效类型', prop: 'effectiveType', diff --git a/src/option/business/waybill-manage.js b/src/option/business/waybill-manage.js index 9f617d3..e6c519f 100644 --- a/src/option/business/waybill-manage.js +++ b/src/option/business/waybill-manage.js @@ -471,7 +471,7 @@ export const config = { endDateRange: ['endDateStart', 'endDateEnd'], createTimeRange: ['createTimeStart', 'createTimeEnd'], }, - actions: ['copy', 'cancel', 'reassign', 'complete', 'batchComplete'], + actions: ['copy', 'cancel', 'reassign', 'complete', 'batchComplete', 'confirmReturn'], detailButton: true, statusProp: 'businessStatus', statusTextProp: 'businessStatusName', @@ -483,6 +483,8 @@ export const config = { row.requireAccept === true || row.requireAccept === 1 || requiresDriverAcceptConfirmation(row); + // 待回单确认由末位打卡产生,司机必然已接单 + if (status === 'pending_return_confirm') return '待回单确认'; // 需司机接单且尚未接单 → 待执行(含库中仍为 running 的历史数据展示校正) if (needAccept && !hasDriverAcceptRecord(row) && !terminalStatuses.includes(status)) { return '待执行'; @@ -504,7 +506,7 @@ export const config = { return !isDriverRejectedWaybill(row); }, canCancel(row) { - // 进行中的运单不允许取消 + // 进行中的运单不允许取消;待回单确认仍可取消(与页面 canCancel 一致) return !isInProgressStatus(row, row.businessStatusName); }, deleteStatus: ['draft'], diff --git a/src/option/settlement/formalSettlementForm.js b/src/option/settlement/formalSettlementForm.js index 4b51186..5afba6b 100644 --- a/src/option/settlement/formalSettlementForm.js +++ b/src/option/settlement/formalSettlementForm.js @@ -8,6 +8,8 @@ export const formalSettlementFormFields = [ { label: '收款方', prop: 'payeeName', readonly: true }, { label: '结算类型', prop: 'settlementTypeName', readonly: true }, { label: '结算金额', prop: 'settlementAmount', readonly: true, money: true }, + { label: '业务模式', prop: 'businessModel', type: 'select', optionsKey: 'businessModelOptions' }, + { label: '运输方式', prop: 'transportType', type: 'select', optionsKey: 'transportTypeOptions' }, { label: '汇率日期', prop: 'exchangeRateDate', type: 'date', required: true }, { label: '结算汇率', prop: 'exchangeRate', type: 'number', required: true }, { label: '本位币合计', prop: 'localSettlementAmount', readonly: true, money: true }, @@ -25,6 +27,8 @@ export const createFormalSettlementForm = () => ({ settlementType: '', sourcePreSettlementIds: [], sourceDetailIds: [], + businessModel: '', + transportType: '', exchangeRateDate: '', exchangeRate: 1, createUserName: '', diff --git a/src/option/settlement/formalSettlementTable.js b/src/option/settlement/formalSettlementTable.js index f53ee46..93393b9 100644 --- a/src/option/settlement/formalSettlementTable.js +++ b/src/option/settlement/formalSettlementTable.js @@ -8,6 +8,8 @@ export const formalSettlementTableColumns = [ { label: '合同编号', prop: 'contractNo', minWidth: 150 }, { label: '合同名称', prop: 'contractName', minWidth: 170 }, { label: '原币结算金额', prop: 'settlementAmount', minWidth: 145, money: true }, + { label: '业务模式', prop: 'businessModelName', minWidth: 120 }, + { label: '运输方式', prop: 'transportTypeName', minWidth: 120 }, { label: '本位币结算金额', prop: 'localSettlementAmount', minWidth: 155, money: true }, { label: '结算汇率', prop: 'exchangeRate', minWidth: 110 }, { label: '申请付款金额(含预付)', prop: 'appliedPaymentAmount', minWidth: 185, money: true }, diff --git a/src/option/settlement/preSettlementForm.js b/src/option/settlement/preSettlementForm.js index ef70be7..64754ba 100644 --- a/src/option/settlement/preSettlementForm.js +++ b/src/option/settlement/preSettlementForm.js @@ -12,6 +12,8 @@ export const preSettlementFormFields = [ { label: '付款方', prop: 'payerName', readonly: true }, { label: '结算类型', prop: 'settlementType', readonly: true }, { label: '结算金额', prop: 'settlementAmount', readonly: true, money: true }, + { label: '业务模式', prop: 'businessModel', type: 'select', optionsKey: 'businessModelOptions' }, + { label: '运输方式', prop: 'transportType', type: 'select', optionsKey: 'transportTypeOptions' }, { label: '汇率日期', prop: 'exchangeRateDate', type: 'date' }, { label: '结算汇率', prop: 'exchangeRate', type: 'number', class: 'exchange-rate-input' }, { label: '本位币合计', prop: 'localSettlementAmount', readonly: true, money: true }, @@ -64,6 +66,8 @@ export const emptyPreSettlementForm = () => ({ settlementType: 'payable', currency: 'RMB', settlementAmount: 0, + businessModel: '', + transportType: '', localCurrency: 'RMB', localSettlementAmount: 0, exchangeRateDate: '', diff --git a/src/option/settlement/preSettlementTable.js b/src/option/settlement/preSettlementTable.js index e3c6e55..b253079 100644 --- a/src/option/settlement/preSettlementTable.js +++ b/src/option/settlement/preSettlementTable.js @@ -8,6 +8,8 @@ export const preSettlementTableColumns = [ { label: '合同编号', prop: 'contractNo', minWidth: 140 }, { label: '合同名称', prop: 'contractName', minWidth: 160 }, { label: '原币结算金额', prop: 'settlementAmount', minWidth: 140, money: true, precision: 2 }, + { label: '业务模式', prop: 'businessModelName', minWidth: 120 }, + { label: '运输方式', prop: 'transportTypeName', minWidth: 120 }, { label: '本位币结算金额', prop: 'localSettlementAmount', minWidth: 150, money: true, precision: 2 }, { label: '结算汇率', prop: 'exchangeRate', minWidth: 110, precision: 2 }, { diff --git a/src/option/system/mpUser.js b/src/option/system/mpUser.js index 994f76f..7236a26 100644 --- a/src/option/system/mpUser.js +++ b/src/option/system/mpUser.js @@ -14,6 +14,11 @@ const validatePhone = (rule, value, callback) => { callback(new Error('请输入手机号')); return; } + // 详情回显为脱敏手机号(含 *)时放行,未修改则提交时剔除该字段 + if (phone.includes('*')) { + callback(); + return; + } if (!/^1\d{10}$/.test(phone)) { callback(new Error('请输入正确的手机号')); return; @@ -121,6 +126,62 @@ export default { order: 60, rules: [{ required: true, message: '请选择小程序身份', trigger: 'change' }], }, + { + label: '所属组织', + prop: 'deptId', + formslot: true, + hide: true, + span: 24, + order: 55, + // 所属组织可为空:为空表示未分配组织,账号的数据权限为空集 + }, + { + label: '所属组织', + prop: 'deptName', + minWidth: 180, + overHidden: true, + addDisplay: false, + editDisplay: false, + viewDisplay: false, + order: 54, + }, + { + label: '数据权限范围', + prop: 'dataScopeRange', + type: 'radio', + span: 24, + hide: true, + order: 53, + value: 1, + dicData: [ + { label: '仅所属组织', value: 1 }, + { label: '全部组织', value: 2 }, + { label: '自定义', value: 3 }, + ], + rules: [{ required: true, message: '请选择数据权限范围', trigger: 'change' }], + }, + { + label: '数据层级范围', + prop: 'dataLevelRange', + type: 'radio', + span: 24, + hide: true, + order: 52, + value: 1, + dicData: [ + { label: '包含下级', value: 1 }, + { label: '仅本级', value: 2 }, + ], + rules: [{ required: true, message: '请选择数据层级范围', trigger: 'change' }], + }, + { + label: '数据权限', + prop: 'dataScopeDeptIds', + formslot: true, + span: 24, + hide: true, + order: 51, + }, { label: '性别', prop: 'sex', diff --git a/src/option/system/user.js b/src/option/system/user.js index cd0a0ee..b450ba5 100644 --- a/src/option/system/user.js +++ b/src/option/system/user.js @@ -315,29 +315,6 @@ export const userOption = safe => { { label: '承运商', value: 2 }, ], }, - { - label: '数据权限范围', - prop: 'dataScopeRange', - type: 'radio', - span: 24, - value: 1, - dicData: [ - { label: '仅所属组织', value: 1 }, - { label: '全部组织', value: 2 }, - { label: '自定义', value: 3 }, - ], - }, - { - label: '数据层级范围', - prop: 'dataLevelRange', - type: 'radio', - span: 24, - value: 1, - dicData: [ - { label: '包含下级', value: 1 }, - { label: '仅本级', value: 2 }, - ], - }, { label: '备注', prop: 'remark', diff --git a/src/option/transportCapacity/transport-vehicle.js b/src/option/transportCapacity/transport-vehicle.js index ffe1bae..c5762d7 100644 --- a/src/option/transportCapacity/transport-vehicle.js +++ b/src/option/transportCapacity/transport-vehicle.js @@ -48,6 +48,7 @@ export const option = { searchOrder: 1, searchslot: true, slot: true, + overHidden: true, minWidth: 150, }, { @@ -55,6 +56,8 @@ export const option = { prop: 'useDepartment', search: true, searchOrder: 3, + slot: true, + overHidden: true, minWidth: 140, }, { diff --git a/src/styles/element-ui.scss b/src/styles/element-ui.scss index d8f73fe..d01acd2 100644 --- a/src/styles/element-ui.scss +++ b/src/styles/element-ui.scss @@ -622,7 +622,7 @@ background: #fff; } -// 合同管理新增/编辑弹窗(business-crud-page 封装 → avue-crud,弹窗带 avue-crud__dialog class) +// 合同管理新增/编辑弹窗(avue-crud,弹窗带 avue-crud__dialog class) // 复刻 waybill-manage 方案:接近全屏、压过库内置 .avue-dialog 的 calc(100% - 200px), // 并解开 body 的 200px 上限、抵消全局 footer 的 -30px 上移 .contract-manage-dialog.el-dialog { diff --git a/src/utils/carrier-org-resource.js b/src/utils/carrier-org-resource.js index 65ffbc5..5185d0d 100644 --- a/src/utils/carrier-org-resource.js +++ b/src/utils/carrier-org-resource.js @@ -26,6 +26,35 @@ const uniqueOrgNames = (values = []) => { return result; }; +// 候选司机取数上限:超出后只能靠关键字缩小范围 +const DRIVER_CANDIDATE_MAX_SIZE = 200; + +/** + * 派单候选司机:租户内的司机档案,按岗位等条件过滤。 + * + * 与组织、承运商都无关——司机档案与组织树、承运商之间没有外键,`organization_name` + * 只是自由文本,用它做匹配会让小程序自动建档的司机永远进不了候选。 + */ +export const fetchDriverCandidates = async (query = {}) => { + const params = { ...query }; + const size = Math.min(Math.max(Number(params.size) || 50, 50), DRIVER_CANDIDATE_MAX_SIZE); + delete params.size; + const res = await getDriverList(1, size, params); + return extractRecords(res).slice(0, size); +}; + +/** + * 司机候选项的展示文本:姓名(手机尾号后 4 位)。 + * + * 只用于下拉渲染。写回表单的仍是姓名本身,别把带尾号的文本当值用。 + */ +export const driverOptionText = (item = {}) => { + const name = normalizeOrgName(item.driverName || item.name); + const mobile = String(item.mobile || item.phone || item.driverPhone || '').trim(); + if (!name) return ''; + return mobile.length >= 4 ? `${name}(${mobile.slice(-4)})` : name; +}; + /** * 解析承运商客商档案上联系人的所属组织(branchName),无联系人时回退客商所属组织。 */ @@ -72,13 +101,6 @@ export const resolveCarrierOrganizationNames = async ({ carrierId, carrierName } return orgNames; }; -export const clearCarrierOrganizationCache = (carrier = {}) => { - const id = String(carrier.carrierId || '').trim(); - const name = normalizeOrgName(carrier.carrierName); - if (id) orgNameCache.delete(id); - if (name) orgNameCache.delete(name); -}; - export const matchOrganizationName = (value, orgNames = []) => { const text = normalizeOrgName(value); if (!text || !orgNames.length) return false; @@ -90,34 +112,9 @@ const filterByOrganizations = (records = [], orgNames = []) => { return records.filter(item => matchOrganizationName(item.organizationName, orgNames)); }; -/** - * 按承运商联系人所属组织筛选司机。 - * 未选择承运商时返回空列表(需先选承运商)。 - */ -export const fetchDriversByCarrierOrganizations = async ( - query = {}, - { carrierId, carrierName } = {} -) => { - if (!String(carrierId || '').trim() && !normalizeOrgName(carrierName)) { - return []; - } - const orgNames = await resolveCarrierOrganizationNames({ carrierId, carrierName }); - if (!orgNames.length) return []; - - const size = Math.max(Number(query.size) || 50, 50); - const params = { ...query }; - delete params.size; - // 单组织时用后端模糊条件缩小范围;多组织再前端精确过滤 - if (orgNames.length === 1) { - params.organizationName = orgNames[0]; - } - const res = await getDriverList(1, Math.min(size * 5, 200), params); - return filterByOrganizations(extractRecords(res), orgNames).slice(0, size); -}; - /** * 按承运商联系人所属组织筛选车辆。 - * 未选择承运商时返回空列表。 + * 未选择承运商时返回空列表(需先选承运商)。 */ export const fetchVehiclesByCarrierOrganizations = async ( query = {}, diff --git a/src/utils/cert-audit.js b/src/utils/cert-audit.js index 331eced..d88959f 100644 --- a/src/utils/cert-audit.js +++ b/src/utils/cert-audit.js @@ -106,6 +106,9 @@ const CERT_SNAPSHOT_GROUPS = { fields: [ { key: 'realName', label: '真实姓名' }, { key: 'idCardNo', label: '身份证号' }, + { key: 'birthday', label: '出生年月', type: TYPE_DATE }, + { key: 'addressRegion', label: '住址' }, + { key: 'address', label: '详细地址' }, { key: 'idCardFrontUrl', label: '身份证正面照', type: TYPE_IMAGE }, { key: 'idCardBackUrl', label: '身份证反面照', type: TYPE_IMAGE }, { key: 'idCardValidStart', label: '身份证有效期起', type: TYPE_DATE }, diff --git a/src/utils/import-excel.js b/src/utils/import-excel.js index 4d69c81..1bd5e02 100644 --- a/src/utils/import-excel.js +++ b/src/utils/import-excel.js @@ -7,7 +7,7 @@ import 'nprogress/nprogress.css'; const excelPattern = /\.(xls|xlsx)$/; /** 是否为浏览器 Blob(含跨 realm 的 Blob-like) */ -const isBlobLike = value => +export const isBlobLike = value => value != null && typeof value === 'object' && typeof value.size === 'number' && @@ -15,14 +15,14 @@ const isBlobLike = value => (typeof value.text === 'function' || typeof value.arrayBuffer === 'function'); /** axios 拦截器已把 JSON Blob 解析成的业务对象 */ -const isBusinessJson = value => +export const isBusinessJson = value => value != null && typeof value === 'object' && !isBlobLike(value) && !(value instanceof ArrayBuffer) && ('code' in value || 'success' in value || 'msg' in value); -const isExcelResponse = res => { +export const isExcelResponse = res => { // 已是业务 JSON(导入成功/失败提示)时,绝不能当失败明细 Excel 处理 if (isBusinessJson(res?.data)) { return false; @@ -54,7 +54,7 @@ const isExcelResponse = res => { * 提前解析成普通对象;此处需同时兼容 Blob / 字符串 / 已解析对象,避免再调 * `blob.text()` 时出现「blob.text is not a function」。 */ -const parseBlobJson = async data => { +export const parseBlobJson = async data => { if (data == null) { return {}; } @@ -88,7 +88,7 @@ const parseBlobJson = async data => { * * 这里补上这一步:从错误响应的 Blob 里取出后端 `msg`,取不到再退回通用文案。 */ -const resolveErrorMessage = async error => { +export const resolveImportErrorMessage = async error => { const payload = error?.response?.data ?? error?.data; if (isBusinessJson(payload) && payload.msg) { return payload.msg; @@ -109,6 +109,26 @@ const resolveErrorMessage = async error => { return fallback; }; +/** + * 统一消费导入接口响应:成功 JSON / 失败 Excel 流。 + * 兼容 axios 已把 JSON Blob 解析成对象的情况,禁止再对普通对象调 .text()。 + */ +export const consumeImportResponse = async (res, options = {}) => { + const { businessName = '导入', dayjsFormat } = options; + if (isExcelResponse(res)) { + return { + kind: 'excel', + blob: res?.data, + fileName: `${businessName}导入失败明细${dayjsFormat || ''}.xlsx`, + }; + } + const result = isBusinessJson(res?.data) ? res.data : await parseBlobJson(res?.data); + if (Number(result?.code) !== 200) { + throw new Error(result?.msg || '导入失败'); + } + return { kind: 'success', result }; +}; + /** * 加工后端返回的导入失败明细 Excel。 * 若传入 failDetailDecorator(函数),则动态加载 exceljs 读取 blob 流, @@ -235,7 +255,7 @@ export const handleImportExcel = async (vm, option, column, businessName, onSucc // 拦截器已提示过的错误(如后端 400 返回的业务提示)不再重复弹一次, // 只保留一次清晰的中文提示。 if (!error?._notified) { - vm.$message.error(await resolveErrorMessage(error)); + vm.$message.error(await resolveImportErrorMessage(error)); } notifyUploadError(option, error); } finally { diff --git a/src/utils/settlement-adjust-rule.js b/src/utils/settlement-adjust-rule.js index 066cda3..7d58540 100644 --- a/src/utils/settlement-adjust-rule.js +++ b/src/utils/settlement-adjust-rule.js @@ -3,10 +3,27 @@ export function isFreightFeeName(name) { return text.includes('运费') || text.includes('运输费'); } +function parseBillingRules(row = {}) { + const rule = row.billingRule || {}; + const nested = Array.isArray(rule.rules) ? rule.rules : []; + if (nested.length) return nested; + const raw = row.billingRulesJson ?? row.billingRules; + if (Array.isArray(raw)) return raw.filter(item => item && typeof item === 'object'); + if (typeof raw === 'string' && raw.trim()) { + try { + const parsed = JSON.parse(raw); + return Array.isArray(parsed) ? parsed.filter(item => item && typeof item === 'object') : []; + } catch (error) { + return []; + } + } + return []; +} + /** 运输单价对应的运费计费类型。 */ export function freightBillingType(row = {}) { const rule = row.billingRule || {}; - const rules = Array.isArray(rule.rules) ? rule.rules : []; + const rules = parseBillingRules(row); const freightRule = rules.find( item => isFreightFeeName(item?.feeItem) || isFreightFeeName(item?.feeType) ); diff --git a/src/utils/settlement-change-record.js b/src/utils/settlement-change-record.js new file mode 100644 index 0000000..b59b1dc --- /dev/null +++ b/src/utils/settlement-change-record.js @@ -0,0 +1,6 @@ +/** 审批通过、驳回等审批流转类变更内容,不在结算单变更记录中展示。 */ +export function isApprovalSettlementChange(content) { + const text = String(content || '').trim(); + if (!text) return false; + return text.includes('审批通过') || text.includes('审批驳回') || text === '已驳回'; +} diff --git a/src/views/authority/role.vue b/src/views/authority/role.vue index e24252c..f17267c 100644 --- a/src/views/authority/role.vue +++ b/src/views/authority/role.vue @@ -50,7 +50,7 @@ title="角色权限配置" append-to-body v-model="box" - width="345px" + width="960px" @close="handleDialogClose" > @@ -100,41 +100,26 @@ align="middle" style="margin-bottom: 12px; background: #f5f7fa; padding: 6px 10px; border-radius: 4px" > - - - - - - - - 全选 - 反选 - + 勾选多个菜单后点击「设置」,可批量配置数据权限;设置后菜单旁展示当前范围 + 设置 + @@ -188,6 +173,45 @@ + + + + + + + + + 所在部门及下级 + 全部组织 + 本人创建 + 自定义组织 + + + + + + + + @@ -196,37 +220,44 @@ import { add, getList, getRole, - getRoleAlias, getRoleTreeById, grant, grantTree, remove, update, } from '@/api/system/role'; +import { getRoleMenuDataScopeTree, submitRoleMenuDataScope } from '@/api/system/roleDataScope'; +import { getDeptTree } from '@/api/system/dept'; import { mapGetters } from 'vuex'; import website from '@/config/website'; -import { validatenull } from '@/utils/validate'; export default { data() { return { form: {}, box: false, + dataScopeSettingBox: false, props: { label: 'title', value: 'key', }, menuGrantList: [], - dataScopeGrantList: [], apiScopeGrantList: [], apiGrantList: [], menuTreeObj: [], - dataScopeTreeObj: [], apiScopeTreeObj: [], menuLinked: false, - dataScopeLinked: false, apiScopeLinked: false, currentRoleIds: [], + currentRoleName: '', + roleDataScopeMenuTree: [], + roleDataScopeMap: {}, + deptTree: [], + dataScopeSetting: { + roleName: '', + scopeType: 2, + menuIds: [], + }, selectionList: [], query: {}, loading: true, @@ -309,32 +340,19 @@ export default { ], }, { - label: '角色别名', - labelTip: '用于权限校验与标注的属性,可手动录入也可点击现有别名快速录入', + label: '角色标识', + labelTip: '用于权限校验与标注的属性', prop: 'roleAlias', search: true, span: 24, rules: [ { required: true, - message: '请输入角色别名', + message: '请输入角色标识', trigger: 'blur', }, ], }, - { - label: '现有别名', - labelTip: '当前系统已存在的角色别名,点击可快速录入', - prop: 'currentAlias', - type: 'radio', - dicData: [], - props: { - label: 'roleName', - value: 'roleAlias', - }, - hide: true, - span: 24, - }, { label: '角色排序', prop: 'sort', @@ -378,14 +396,6 @@ export default { return ids; }, }, - watch: { - 'form.currentAlias'() { - const alias = this.form.currentAlias; - if (!validatenull(alias)) { - this.form.roleAlias = alias; - } - }, - }, methods: { initData(roleId) { getRoleTreeById(roleId).then(res => { @@ -452,9 +462,9 @@ export default { }, submit() { const menuList = this.$refs.treeMenu.getCheckedKeys(); - const dataScopeList = this.$refs.treeDataScope.getCheckedKeys(); const apiScopeList = this.$refs.treeApiScope.getCheckedKeys(); - grant(this.currentRoleIds, menuList, dataScopeList, apiScopeList).then(() => { + // 数据权限已改为角色菜单配置;传 null 跳过原生 dataScope 更新,避免误清历史配置 + grant(this.currentRoleIds, menuList, null, apiScopeList).then(() => { this.box = false; this.$message({ type: 'success', @@ -465,6 +475,84 @@ export default { this.onLoad(this.page); }); }, + getMenuScopeLabel(menuId) { + const scope = this.roleDataScopeMap[String(menuId)]; + return scope ? scope.scopeTypeName : ''; + }, + loadRoleDataScopeTree(roleId) { + return getRoleMenuDataScopeTree(roleId).then(res => { + const data = res.data.data || {}; + this.currentRoleName = data.roleName || ''; + this.roleDataScopeMenuTree = data.menuTree || []; + const scopeMap = {}; + (data.scopes || []).forEach(item => { + scopeMap[String(item.menuId)] = item; + }); + this.roleDataScopeMap = scopeMap; + }); + }, + openDataScopeSetting() { + const tree = this.$refs.treeRoleDataScope; + if (!tree) { + return; + } + const menuIds = tree.getCheckedKeys(); + if (!menuIds || menuIds.length === 0) { + this.$message.warning('请先勾选要设置的菜单'); + return; + } + this.dataScopeSetting = { + roleName: this.currentRoleName, + scopeType: 2, + menuIds, + }; + const loadDept = + this.deptTree && this.deptTree.length + ? Promise.resolve() + : getDeptTree(this.userInfo.tenantId).then(res => { + this.deptTree = res.data.data || []; + }); + loadDept.then(() => { + this.dataScopeSettingBox = true; + this.$nextTick(() => { + if (this.$refs.treeCustomDept) { + this.$refs.treeCustomDept.setCheckedKeys([]); + } + }); + }); + }, + submitDataScopeSetting() { + const roleId = this.currentRoleIds && this.currentRoleIds[0]; + if (!roleId) { + this.$message.warning('角色信息异常,请重新打开权限配置'); + return; + } + const scopeType = Number(this.dataScopeSetting.scopeType || 2); + let deptIds = []; + if (scopeType === 4) { + deptIds = this.$refs.treeCustomDept ? this.$refs.treeCustomDept.getCheckedKeys() : []; + if (!deptIds.length) { + this.$message.warning('自定义组织时请勾选组织'); + return; + } + } + submitRoleMenuDataScope({ + roleId, + menuIds: this.dataScopeSetting.menuIds, + scopeType, + deptIds, + }).then(() => { + this.$message.success('数据权限设置成功'); + this.dataScopeSettingBox = false; + this.loadRoleDataScopeTree(roleId).then(() => { + this.$nextTick(() => { + if (this.$refs.treeRoleDataScope) { + this.$refs.treeRoleDataScope.setCheckedKeys([]); + } + }); + }); + }); + }, rowSave(row, done, loading) { add(this.withTenantId(row)).then( () => { @@ -547,10 +635,6 @@ export default { this.form.parentId = ''; } } - getRoleAlias().then(res => { - const column = this.findColumn(this.option.column, 'currentAlias'); - column.dicData = res.data.data; - }); done(); }, handleRole() { @@ -563,58 +647,52 @@ export default { return; } this.menuTreeObj = []; - this.dataScopeTreeObj = []; this.apiScopeTreeObj = []; - // 保存当前要配置权限的角色ID this.currentRoleIds = this.idsArray; - grantTree().then(res => { - this.menuGrantList = res.data.data.menu; - this.dataScopeGrantList = res.data.data.dataScope; - this.apiScopeGrantList = res.data.data.apiScope; - getRole(this.ids).then(res => { - this.menuTreeObj = res.data.data.menu; - this.dataScopeTreeObj = res.data.data.dataScope; - this.apiScopeTreeObj = res.data.data.apiScope; - this.box = true; - this.$nextTick(() => { - this.applyCheckedKeys('treeMenu', this.menuTreeObj); - this.applyCheckedKeys('treeDataScope', this.dataScopeTreeObj); - this.applyCheckedKeys('treeApiScope', this.apiScopeTreeObj); - }); + this.currentRoleName = (this.selectionList[0] && this.selectionList[0].roleName) || ''; + const roleId = this.currentRoleIds[0]; + Promise.all([grantTree(), getRole(this.ids), this.loadRoleDataScopeTree(roleId)]).then(results => { + const grantRes = results[0]; + const roleRes = results[1]; + this.menuGrantList = grantRes.data.data.menu; + this.apiScopeGrantList = grantRes.data.data.apiScope; + this.menuTreeObj = roleRes.data.data.menu; + this.apiScopeTreeObj = roleRes.data.data.apiScope; + this.box = true; + this.$nextTick(() => { + this.applyCheckedKeys('treeMenu', this.menuTreeObj); + this.applyCheckedKeys('treeApiScope', this.apiScopeTreeObj); }); }); }, handleRowRole(row) { this.menuTreeObj = []; - this.dataScopeTreeObj = []; this.apiScopeTreeObj = []; - // 保存当前要配置权限的角色ID(单个行操作) this.currentRoleIds = [row.id]; - grantTree().then(res => { - this.menuGrantList = res.data.data.menu; - this.dataScopeGrantList = res.data.data.dataScope; - this.apiScopeGrantList = res.data.data.apiScope; - getRole(row.id).then(res => { - this.menuTreeObj = res.data.data.menu; - this.dataScopeTreeObj = res.data.data.dataScope; - this.apiScopeTreeObj = res.data.data.apiScope; - this.box = true; - this.$nextTick(() => { - this.applyCheckedKeys('treeMenu', this.menuTreeObj); - this.applyCheckedKeys('treeDataScope', this.dataScopeTreeObj); - this.applyCheckedKeys('treeApiScope', this.apiScopeTreeObj); - }); + this.currentRoleName = row.roleName || ''; + Promise.all([grantTree(), getRole(row.id), this.loadRoleDataScopeTree(row.id)]).then(results => { + const grantRes = results[0]; + const roleRes = results[1]; + this.menuGrantList = grantRes.data.data.menu; + this.apiScopeGrantList = grantRes.data.data.apiScope; + this.menuTreeObj = roleRes.data.data.menu; + this.apiScopeTreeObj = roleRes.data.data.apiScope; + this.box = true; + this.$nextTick(() => { + this.applyCheckedKeys('treeMenu', this.menuTreeObj); + this.applyCheckedKeys('treeApiScope', this.apiScopeTreeObj); }); }); }, handleDialogClose() { - // 对话框关闭时清空选择状态和权限树数据 this.menuTreeObj = []; - this.dataScopeTreeObj = []; this.apiScopeTreeObj = []; this.menuLinked = false; - this.dataScopeLinked = false; this.apiScopeLinked = false; + this.roleDataScopeMenuTree = []; + this.roleDataScopeMap = {}; + this.currentRoleName = ''; + this.dataScopeSettingBox = false; this.selectionClear(); }, handleDelete() { @@ -659,3 +737,15 @@ export default { }, }; + + diff --git a/src/views/business/components/business-crud-page.vue b/src/views/business/components/business-crud-page.vue deleted file mode 100644 index eb23da3..0000000 --- a/src/views/business/components/business-crud-page.vue +++ /dev/null @@ -1,16453 +0,0 @@ - -