import { auditColumns, createCrudOption, phoneRule, planStatusOptions, selectRule, textRule, withSearchPlaceholders, } from './common'; const transportPlanDataSourceOptions = [ { label: '批量导入', value: '批量导入' }, { label: '手工创建', value: '手工创建' }, { label: '外部系统', value: '外部系统' }, ]; const listDicFormatter = res => { const data = res?.data || res; if (Array.isArray(data)) return data; if (Array.isArray(data?.records)) return data.records; if (Array.isArray(data?.data)) return data.data; if (Array.isArray(data?.data?.records)) return data.data.records; return []; }; const projectNameDicFormatter = res => { const list = listDicFormatter(res); // 过滤掉 projectName 为空的记录 return list.filter(item => item && item.projectName && String(item.projectName).trim()); }; const parseGoodsRows = value => { if (!value) return []; if (Array.isArray(value)) return value; if (typeof value === 'object') return [value]; try { const data = JSON.parse(value); return Array.isArray(data) ? data : [data]; } catch (error) { return []; } }; const formatGoodsField = (row, propList) => { const values = propList.map(prop => row[prop]).filter(Boolean); if (values.length) return values.join('、'); return parseGoodsRows(row.goodsJson) .map(item => propList.map(prop => item?.[prop]).find(Boolean)) .filter(Boolean) .join('、'); }; const getGoodsQuantity = item => { const value = item?.quantity ?? item?.cargoQuantity ?? item?.goodsQuantity ?? 0; const number = Number(String(value).replace(/,/g, '')); return Number.isFinite(number) ? number : 0; }; const formatGoodsQuantity = value => { const number = Math.round((value + Number.EPSILON) * 1000) / 1000; return Number.isInteger(number) ? String(number) : String(number).replace(/\.?0+$/, ''); }; const getSecondCargoTypeName = item => item?.secondCargoTypeName || item?.secondCargoType || item?.cargoType || item?.goodsType || item?.type || item?.cargoTypeName || item?.firstCargoTypeName || '货物'; const formatGoodsInfo = row => { const rows = [row.goodsJson, row.goodsList, row.goodsRows, row.cargoList, row.cargoRows, row.goods] .map(source => parseGoodsRows(source)) .filter(source => source.length) .sort((left, right) => right.length - left.length)[0] || []; if (!rows.length) return row.goodsInfo || ''; const groups = rows.reduce((result, item = {}) => { const hasGoodsInfo = [ item.cargoName, item.goodsName, item.name, item.cargoType, item.secondCargoTypeName, item.goodsType, item.type, item.quantity, item.cargoQuantity, item.goodsQuantity, item.quantityUnit, item.goodsQuantityUnit, item.cargoUnit, item.unit, ].some(Boolean); if (!hasGoodsInfo) return result; const unit = String( item.quantityUnit || item.goodsQuantityUnit || item.cargoUnit || item.unit || row.quantityUnit || row.goodsQuantityUnit || row.cargoUnit || row.unit || '' ).trim(); if (!result[unit]) { result[unit] = { typeName: getSecondCargoTypeName(item), count: 0, quantity: 0, }; } result[unit].count += 1; result[unit].quantity += getGoodsQuantity(item); return result; }, {}); const text = Object.entries(groups) .map(([unit, group]) => { const quantity = formatGoodsQuantity(group.quantity); return `${group.typeName}等${group.count}种货物 | ${quantity}${unit ? ` ${unit}` : ''}`; }) .join('; '); return text || row.goodsInfo || ''; }; const parseDispatchRows = row => { const sources = [ row.dispatchRows, row.dispatchList, row.dispatchRecords, row.waybillList, row.waybillRows, ]; for (const source of sources) { const rows = parseGoodsRows(source); if (rows.length) return rows; } return []; }; const formatDispatchQuantity = value => { const number = Number(value || 0); if (!Number.isFinite(number)) return '0'; const fixed = Math.round((number + Number.EPSILON) * 1000) / 1000; return Number.isInteger(fixed) ? String(fixed) : String(fixed).replace(/\.?0+$/, ''); }; const formatDispatchProgress = row => { const goodsRows = parseGoodsRows(row.goodsJson); const totalRows = goodsRows.length ? goodsRows : [row]; const dispatchRows = parseDispatchRows(row); const summary = new Map(); const add = (item, field) => { const unit = item.quantityUnit || item.goodsQuantityUnit || item.unit || '吨'; const quantity = Number(item.quantity || item.cargoQuantity || item.goodsQuantity || 0); if (!summary.has(unit)) summary.set(unit, { total: 0, assigned: 0 }); if (Number.isFinite(quantity)) summary.get(unit)[field] += quantity; }; totalRows.forEach(item => add(item, 'total')); dispatchRows.forEach(item => add(item, 'assigned')); if (!dispatchRows.length) { const assigned = Number(row.dispatchedQuantity || row.dispatchQuantity || 0); if (Number.isFinite(assigned)) { const unit = row.quantityUnit || row.goodsQuantityUnit || row.unit || '吨'; if (!summary.has(unit)) summary.set(unit, { total: 0, assigned: 0 }); summary.get(unit).assigned = assigned; } } const lines = Array.from(summary.entries()).map( ([unit, item]) => `${formatDispatchQuantity(item.assigned)}/${formatDispatchQuantity(item.total)}${unit}` ); return lines.length ? lines.join('\n') : row.dispatchProgressName || row.dispatchProgress || row.progress || ''; }; const auditColumn = prop => ({ ...auditColumns.find(item => item.prop === prop) }); export const config = { title: '运输计划', permission: 'transport_plan', exportUrl: '/blade-transport/transport-plan/export-transport-plan', exportName: '运输计划', enableProjectSelect: true, projectQueryParams: { approvalStatuses: 'approved,change_approved', }, contractQueryParams: { contractCategory: '客户合同', }, templateCreateTarget: 'transport-plan', enableAttachmentTable: true, enableTransportPlanForm: true, enableTransportPlanCreateActions: true, enableTransportPlanImport: true, fixedTransportAddressType: true, transportPlanImportTitle: '导入运输计划', transportPlanTemplateUrl: '/blade-transport/transport-plan/export-template', attachmentTitle: '附件', searchRangeMap: { planStartDateRange: ['planStartDateStart', 'planStartDateEnd'], planEndDateRange: ['planEndDateStart', 'planEndDateEnd'], }, actions: ['copy', 'complete'], statusProp: 'businessStatus', statusTextProp: 'businessStatusName', deleteStatus: ['draft', 'waiting_dispatch'], editStatus: ['draft', 'waiting_dispatch', 'dispatching'], detailButton: true, detailSections: [ { title: '基本信息', fields: [ ['planNo', '计划单号'], ['planName', '计划名称'], ['projectName', '项目名称'], ['customerName', '客户名称'], ['transportType', '运输方式'], ['businessStatus', '调度状态'], ['planStartDate', '计划开始日期'], ['planEndDate', '计划结束日期'], ['dataSource', '数据来源'], ], }, { title: '收发货信息', fields: [ ['departureName', '发货地'], ['departureContact', '发货联系人'], ['departureAddress', '发货地址'], ['departurePhone', '发货联系方式'], ['arrivalName', '收货地'], ['arrivalContact', '收货联系人'], ['arrivalAddress', '到货地址'], ['arrivalPhone', '收货联系方式'], ], }, { title: '其它信息', fields: [ ['remark', '备注'], ['updateUserName', '更新人'], ['updateTime', '更新时间'], ['createTime', '创建时间'], ], }, ], operations: [ { action: 'dispatch', label: '调度', statusProp: 'businessStatus', status: ['waiting_dispatch', 'dispatching'], permission: 'transport_plan_edit', confirm: '确定调度该运输计划?', }, ], }; export const option = { ...createCrudOption( withSearchPlaceholders([ { label: '', prop: 'basicInfoTitle', formslot: true, span: 24, order: 400, hide: true, labelWidth: 0, }, { label: '计划单号', prop: 'planNo', search: true, searchOrder: 13, span: 6, order: 370, minWidth: 150, disabled: true, placeholder: '系统自动生成', }, { label: '计划名称', prop: 'planName', search: true, searchOrder: 12, span: 6, order: 380, minWidth: 150, rules: textRule('计划名称', 50, true), }, { label: '运输方式', prop: 'transportType', type: 'select', search: true, searchOrder: 10, span: 6, order: 360, dicUrl: '/blade-system/dict-biz/dictionary?code=transport_type', props: { label: 'dictValue', value: 'dictKey', }, minWidth: 130, rules: selectRule('运输类型'), }, { label: '货物信息', prop: 'goodsInfo', formatter: row => formatGoodsInfo(row), minWidth: 320, overHidden: false, showOverflowTooltip: false, addDisplay: false, editDisplay: false, viewDisplay: false, }, { label: '调度进度', prop: 'dispatchProgress', formatter: row => formatDispatchProgress(row), minWidth: 120, overHidden: false, showOverflowTooltip: false, addDisplay: false, editDisplay: false, viewDisplay: false, }, { label: '发货地址', prop: 'departureAddress', formslot: true, search: true, searchOrder: 7, span: 24, order: 310, minWidth: 220, rules: textRule('发货地址', 255, true), }, { label: '到货地址', prop: 'arrivalAddress', formslot: true, search: true, searchOrder: 6, span: 24, order: 300, minWidth: 220, rules: textRule('收货地址', 255, true), }, { label: '项目名称', prop: 'projectName', formslot: true, search: true, searchOrder: 11, searchType: 'select', span: 6, order: 400, dicUrl: '/blade-transport/project-apply/list?current=1&size=9999', dicFormatter: projectNameDicFormatter, props: { label: 'projectName', value: 'projectName', }, filterable: true, minWidth: 150, rules: textRule('项目', 100, true), }, { label: '客户名称', prop: 'customerName', search: true, searchOrder: 5, minWidth: 150, addDisplay: false, editDisplay: false, }, { label: '计划开始日期', prop: 'planStartDate', type: 'date', format: 'YYYY-MM-DD', valueFormat: 'YYYY-MM-DD', span: 6, order: 350, minWidth: 150, rules: [{ required: true, message: '请选择计划开始日期', trigger: 'change' }], }, { label: '计划结束日期', prop: 'planEndDate', type: 'date', format: 'YYYY-MM-DD', valueFormat: 'YYYY-MM-DD', span: 6, order: 340, minWidth: 150, rules: [{ required: true, message: '请选择计划结束日期', trigger: 'change' }], }, { label: '数据来源', prop: 'dataSource', type: 'select', search: true, searchOrder: 1, dicData: transportPlanDataSourceOptions, minWidth: 120, addDisplay: false, editDisplay: false, }, { label: '货物类型', prop: 'cargoType', search: true, searchOrder: 8, formatter: row => formatGoodsField(row, ['cargoType', 'goodsType']), minWidth: 130, addDisplay: false, editDisplay: false, viewDisplay: false, }, { label: '货物名称', prop: 'cargoName', search: true, searchOrder: 9, formatter: row => formatGoodsField(row, ['cargoName', 'goodsName']), minWidth: 150, addDisplay: false, editDisplay: false, viewDisplay: false, }, { label: '备注', prop: 'remark', type: 'textarea', minRows: 2, span: 24, order: 330, minWidth: 180, maxlength: 200, showWordLimit: true, rules: textRule('备注', 200), }, { ...auditColumn('updateUserName'), }, { ...auditColumn('createTime'), }, { ...auditColumn('updateTime'), }, { label: '调度状态', prop: 'businessStatus', type: 'select', slot: true, search: true, searchLabel: '状态', searchOrder: 2, dicData: planStatusOptions, minWidth: 82, fixed: 'right', addDisplay: false, editDisplay: false, }, { label: '', prop: 'shippingInfoTitle', formslot: true, span: 24, order: 320, hide: true, labelWidth: 0, }, { label: '项目ID', prop: 'projectId', hide: true, display: false, }, { label: '客户合同', prop: 'contractName', formslot: true, hide: true, span: 6, order: 390, minWidth: 160, rules: textRule('客户合同', 100, true), }, { label: '客户合同ID', prop: 'contractId', hide: true, display: false, }, { label: '发货地', prop: 'departureName', hide: true, display: false, minWidth: 140, rules: textRule('发货地', 100, true), }, { label: '收货地', prop: 'arrivalName', hide: true, display: false, minWidth: 140, rules: textRule('收货地', 100, true), }, { label: '发货联系人', prop: 'departureContact', hide: true, display: false, rules: textRule('发货联系人', 50), }, { label: '发货联系方式', prop: 'departurePhone', hide: true, display: false, rules: [phoneRule('发货联系方式'), ...textRule('发货联系方式', 50)], }, { label: '收货联系人', prop: 'arrivalContact', hide: true, display: false, rules: textRule('收货联系人', 50), }, { label: '收货联系方式', prop: 'arrivalPhone', hide: true, display: false, rules: [phoneRule('收货联系方式'), ...textRule('收货联系方式', 50)], }, { label: '', prop: 'goodsInfoTitle', formslot: true, span: 24, order: 280, hide: true, labelWidth: 0, }, { label: '计划开始日期', prop: 'planStartDateRange', type: 'date', format: 'YYYY-MM-DD', valueFormat: 'YYYY-MM-DD', search: true, searchRange: true, searchOrder: 4, hide: true, addDisplay: false, editDisplay: false, viewDisplay: false, }, { label: '计划结束日期', prop: 'planEndDateRange', type: 'date', format: 'YYYY-MM-DD', valueFormat: 'YYYY-MM-DD', search: true, searchRange: true, searchOrder: 3, hide: true, addDisplay: false, editDisplay: false, viewDisplay: false, }, { label: '所属组织', prop: 'deptName', hide: true, minWidth: 150, addDisplay: false, editDisplay: false, }, { label: '', prop: 'goodsJson', type: 'textarea', formslot: true, minRows: 2, span: 24, order: 270, hide: true, labelWidth: '0px', className: 'transport-plan-page__full-form-item', }, { label: '', prop: 'attachmentTitle', formslot: true, span: 24, order: 265, hide: true, labelWidth: 0, }, { label: '', prop: 'attachmentsJson', type: 'textarea', formslot: true, minRows: 2, span: 24, order: 260, hide: true, labelWidth: '0px', className: 'transport-plan-page__full-form-item', }, ]) ), // label 固定宽度(容纳 6 汉字),覆盖 createCrudOption 默认 labelWidth:'auto' labelWidth: 100, dialogWidth: '96%', dialogTop: '4vh', dialogCustomClass: 'transport-plan-dialog', };