From 6de87ed4e2e052e684fb27740b060a3544d9e2bc Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?=E8=B5=B5=E5=BF=A0=E6=9E=97?= <170083662@qq.com>
Date: Wed, 14 Oct 2026 12:44:56 +0800
Subject: [PATCH] =?UTF-8?q?style(contract):=20=E4=BC=98=E5=8C=96=E5=90=88?=
=?UTF-8?q?=E5=90=8C=E7=AE=A1=E7=90=86=E9=A1=B5=E9=9D=A2=E8=A1=A8=E5=8D=95?=
=?UTF-8?q?=E6=A0=B7=E5=BC=8F=E4=B8=8E=E5=B8=83=E5=B1=80?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
- 调整合同管理备注输入框行数和最大长度
- 细化合同变更页面表单项间距和对齐方式
- 统一弹窗表单项的底部间距样式,改为注释禁用
- 调整 port-terminal 和 region 组件的表单项底部间距
- 修改 vite 配置中 API 请求代理地址
- 优化凭证执行详情搜索表单列宽布局
- 在运单管理页面更新型号字段为非必填,调整变更记录内容格式化显示
- 引入并使用 formatRouteChangeContent 工具函数
- 统一明细按钮显示逻辑,草稿状态也显示详情按钮
- 新增路由变更相关工具函数与样式支持
---
.workbuddy/memory/MEMORY.md | 36 ++
src/components/table-header-tools/main.vue | 4 +
src/option/payment/paymentApplication.js | 185 +++++-
src/styles/element-ui.scss | 2 +-
src/views/base/port-terminal.vue | 2 +-
src/views/base/region.vue | 2 +-
.../components/business-crud-page.vue | 12 +-
.../components/master-order-detail.vue | 4 +-
.../components/master-order-dispatch.vue | 16 +-
.../components/shipping-template-page.vue | 72 ++-
.../components/voucher-execution-detail.vue | 4 +-
.../components/waybill-manage-page.vue | 19 +-
src/views/business/contract-manage-change.vue | 4 +-
src/views/business/contract-manage.vue | 4 +-
src/views/business/loading-manage.vue | 9 +-
src/views/payment/bill-ledger.vue | 4 +
src/views/payment/bill-payment.vue | 4 +
.../payment/components/bill-ledger-table.vue | 28 +-
.../payment/components/bill-payment-table.vue | 29 +-
src/views/payment/invoice-application.vue | 36 +-
src/views/payment/invoice-receipt.vue | 36 +-
src/views/payment/payment-application.vue | 547 +++++++-----------
src/views/payment/receipt-claim-record.vue | 30 +-
src/views/payment/receipt-flow.vue | 60 +-
.../components/formal-settlement-editor.vue | 89 ++-
vite.config.mjs | 2 +-
26 files changed, 748 insertions(+), 492 deletions(-)
diff --git a/.workbuddy/memory/MEMORY.md b/.workbuddy/memory/MEMORY.md
index 550000a..d509e8c 100644
--- a/.workbuddy/memory/MEMORY.md
+++ b/.workbuddy/memory/MEMORY.md
@@ -22,6 +22,36 @@
**坑三:路由表重复定义 → 内容区整页空白(2026-09-18)。** `/business/contract-manage/detail` 在 `router/views/index.js` 定义两次、子路由同名 `合同详情`。重名 `addRoute` 会 `removeRoute(name)` 移除旧子记录,但**不从父记录 children 里摘除**,第一条 Layout 壳记录留在 matcher 且靠前 → `resolve()` 命中它 → `$route.matched.length === 1` → 二级 router-view 无匹配 → 侧栏/标签正常但内容空白。修法:删重复定义。排查:扫「顶层 path + 首个子路由 name」重复项。
+## 列表页右上角按钮组(table-header-tools 标准件)
+⚠️ **2026-10-14 修正:最终方向是手写列表页改用 `avue-crud`**。用户要求与其它模块"完全一致",而 avue 的列显隐抽屉(`el-drawer`+表格,隐藏/冻结/过滤/排序,行拖拽)是 avue-crud 内部子组件(`inject: ['crud']`),脱离 avue-crud 拿不出来;第 4 键也是 avue 的"表格↔卡片视图"切换。所以 `table-header-tools` 只是过渡仿制件,**新页面不要再接**,直接写 avue-crud。
+
+手写 `el-table` 列表页若暂时保留,右上角统一用 `src/components/table-header-tools/main.vue`,复刻 avue-crud 默认 4 键:刷新 `el-icon-refresh` / 列设置 `el-icon-operation` / 搜索显隐 `el-icon-search` / 表格尺寸 `el-icon-grid`(default→large→small 循环)。
+
+- 接入范式:``;配套:搜索区 `v-if="searchVisible"`、el-table `:size="tableSize"`、列用 computed `visibleColumns = columns.filter(c => visibleColumnKeys.includes(c.prop))`。⚠️ 不能 `v-for` + `v-if` 同元素(Vue3 v-if 优先级更高,取不到 column 变量)。
+- 表格在子组件时(如 `payment/components/bill-*-table.vue`):子组件加 props `searchVisible` + emits `refresh`/`update:searchVisible`,父页面 `v-model:search-visible` + `@refresh`,搜索子组件加 `v-if="searchVisible"`。
+- `icon="el-icon-xxx"` 字符串**依赖 Avue 的全局 el-icon 映射**(`main.js` 只注册 EP 的 PascalCase 图标),临时验证页没 `app.use(Avue)` 会渲染成空圆按钮 —— 别误判成组件 bug。
+- 组件已加 `deactivated()` 关列设置弹窗(防 keep-alive 下 Teleport 弹窗残留)。
+- 已接入(过渡态,待逐页改 avue):business/{master-order,loading-manage,voucher-manage}、settlement、payment 模块手写页。
+
+## avue-crud 列表页改造要点(payment 模块样板,2026-10-14)
+- **列显隐抽屉**:点第 2 键弹出,标题"列显隐"(语言包 `crud.showTitle`),表头 列名/隐藏/冻结/过滤/排序。直接读写 avue crud 的列配置,页面无需实现。
+- **第 4 键**:`handleGridShow()` = 表格 ↔ 卡片视图(`tableCard` 按列通用渲染),页面无需写卡片模板。
+- `searchOrder` 是**降序**排序(值越大越靠前):`list.sort((a,b) => (b.searchOrder||0) - (a.searchOrder||0))`。首次改造踩过坑。
+- `searchIndex: 4` + `searchIcon: true` → 默认只显示前 4 个搜索项 + "展开/收起"(columnLen > searchIndex 时才出现该按钮)。
+- **搜索项 label 与表格列 label 不同**(列「收款方」/搜索「客户名称」):单独定义 hide 列 + `searchProp` 指向后端字段(avue 在 search-change 里自动改名)。
+- 搜索专用列加 `hide: true` + `showColumn: false`,否则会混进列显隐抽屉。
+- ⚠️ **column 数组顺序 = 表格列顺序**:搜索专用列别插在表格列中间(会把表格列顶到前面);deptName 这种"既搜索又展示"的列要放回表格列的正确位置。
+- 分页:`page: { currentPage, pageSize, pageSizes, total }`;`on-load` 由 avue 内部(table-page)在 created / 翻页 / 改页容量时触发,`current-change`/`size-change` 里**只改 page 不要再调 onLoad**(否则双请求)。
+- `onLoad(page)` 只用 `this.query`(avue 传的 page 对象含 total/pageSizes 等分页字段,别 spread 进查询参数)。
+- 搜索参数转换放 `searchChange(params, done)`:日期区间数组拆成 `xxxStartDate/xxxEndDate`;所属组织级联用 `normalizeOrganizationSearch(params, 'deptName')`(见 `mixins/organization-search.js`,option 里该列加 `searchslot: true`)。
+- 单元格空值统一 `-`:列级 `formatter: (row, value) => ...`;金额 `Number(v||0).toFixed(2)`;状态/链接用 `slot: true` + `#prop` 插槽。
+- 操作列用 `#menu` 插槽(`menuWidth: 320`),`addBtn/viewBtn/delBtn/editBtn: false` 关掉 avue 自带按钮。
+
+## 地址/行政区划展示(全站既有规则)
+「省/市/区县」都要空格分段,但**省与自治区不重复展示**:`上海市浦东新区`→`上海市 浦东新区`,`广西壮族自治区南宁市邕宁区`→`南宁市 邕宁区`(同 `formatRoadLocation` / `formatRouteCityDistrict` / 总单列表 `formatRoadAddress` 一致)。
+- 统一入口:`src/utils/region-address.js` 的 `formatRegionAddress(value)`(按后缀切分行政区划,`州+市`、`县+区` 连写合并;**只有整串都是行政区划**才重排,站点名「南宁站/广州南站」与含门牌的详细地址原样返回)。v-for 列表里用它做展示时,直接把函数挂进 `methods` 用 ES6 简写(`formatRegionAddress,`)。
+- ⚠️ 路线节点的名称字段来自后端 `routeJson`:`routes[i].departureName` = 第 i 段**到货地/途经地**(不是本段发货地),末段 = 最终收货地;`master.departureName/arrivalName` 才是总发货/总收货。总单调度/详情都按「上一个节点的 departureName」拼分段起点。
+
## 样式硬规则
- 底栏 `flex; justify-content:flex-end`,**不写 gap**(靠 EP 默认 `.el-button+.el-button{margin-left:12px}`);浮动底栏必须 `position:fixed`(祖先 overflow:hidden 使 sticky 失效)+ `:global(.avue--collapse .x){left:60px}`、`:global(.avue-layout--horizontal .x){left:0}`,`:global()` 要包整个选择器。
- 按钮层级:次要=无 type;中间步骤=`primary plain`;主操作=`primary`(禁绿、禁两个蓝实心)。顺序 `[辅助][取消][保存草稿][提交]`。
@@ -33,3 +63,9 @@
- `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`。
- 导出:前端 `exportColumns` 后端未用,以 `XxxExportExcel.java` 为准;BladeX `BeanUtil` 类型不兼容静默跳过(`Date` → `LocalDateTime` 丢值,用 `DateUtil.fromDate`)。
+
+## Element Plus 图标两种用法的机制差异(易误判)
+`main.js` 里 `for (...ElementPlusIconsVue) app.component(key, component)` 全局注册了全部图标,因此模板里可以写标签形式 ``(靠全局注册解析)。
+但 `:icon="OfficeBuilding"` 是**表达式**,编译成 `_ctx.OfficeBuilding`,只会从 data / props / computed / setup 返回值里取 —— **组件对象必须放进 `data()`**;放进 `components: {}` 注册里模板取不到,会静默渲染不出图标(不报错,只是空白)。
+项目既有惯例:需要 `:icon` 的图标一律 `import` 后在 `data()` 里 `return { Xxx }`(transport-plan-page、waybill-manage-page、business-crud-page 都是这么写的)。
+注:标签形式(如 ``)不依赖 data,放不放 data 都能显示;仅 `:icon` 必须放 data。
diff --git a/src/components/table-header-tools/main.vue b/src/components/table-header-tools/main.vue
index 8bfb63a..f338b90 100644
--- a/src/components/table-header-tools/main.vue
+++ b/src/components/table-header-tools/main.vue
@@ -84,6 +84,10 @@ export default {
columnSettingVisible: false,
};
},
+ deactivated() {
+ // keep-alive 缓存页面时关闭列设置弹窗,避免 Teleport 到 body 的弹窗切页后残留
+ this.columnSettingVisible = false;
+ },
methods: {
toggleTableSize() {
const index = TABLE_SIZE_OPTIONS.indexOf(this.tableSize);
diff --git a/src/option/payment/paymentApplication.js b/src/option/payment/paymentApplication.js
index ed650f7..66cfeb5 100644
--- a/src/option/payment/paymentApplication.js
+++ b/src/option/payment/paymentApplication.js
@@ -1,20 +1,165 @@
-export const paymentApplicationTableColumns = [
- { prop: 'paymentNo', label: '单据号', minWidth: 140, link: true },
- { prop: 'paymentTypeName', label: '付款类型', minWidth: 110 },
- { prop: 'projectName', label: '所属项目', minWidth: 140 },
- { prop: 'deptName', label: '所属组织', minWidth: 120 },
- { prop: 'contractNo', label: '合同编号', minWidth: 130 },
- { prop: 'contractName', label: '合同名称', minWidth: 140 },
- { prop: 'payerName', label: '付款方', minWidth: 130 },
- { prop: 'payeeName', label: '收款方', minWidth: 130 },
- { prop: 'appliedAmount', label: '申请付款金额', minWidth: 140, money: true },
- { prop: 'paidAmount', label: '已付款金额', minWidth: 130, money: true },
- { prop: 'matchedInvoiceAmount', label: '匹配发票金额', minWidth: 130, money: true },
- { prop: 'applicantName', label: '申请人', minWidth: 100 },
- { prop: 'applyDate', label: '申请日期', minWidth: 120 },
- { prop: 'approvalStatusName', label: '审核状态', minWidth: 110, status: true },
- { prop: 'currentNode', label: '当前节点', minWidth: 120 },
- { prop: 'currentProcessor', label: '当前处理人', minWidth: 120 },
- { prop: 'kingdeeBillNo', label: '金蝶单据号', minWidth: 130 },
- { prop: 'kingdeeStatusName', label: '金蝶单据状态', minWidth: 130 },
-];
+/**
+ * 付款申请列表(avue-crud)配置
+ * ------------------------------------------------------------------
+ * 列表页与其它模块统一使用 avue-crud,头部 4 个按钮(刷新 / 列显隐 / 搜索显隐 /
+ * 表格卡片切换)由 avue-crud 自带,不做二次封装。
+ *
+ * 搜索项与表格列 label 不一致的(如表格列「收款方」/ 搜索项「客户名称」),
+ * 单独定义一个 hide 的搜索列,并用 searchProp 映射到后端字段。
+ *
+ * ⚠️ avue 的 searchOrder 为降序排序,值越大越靠前(见 header-search2.js:
+ * `list.sort((a, b) => (b.searchOrder || 0) - (a.searchOrder || 0))`)。
+ */
+
+const textFormatter = (row, value) =>
+ value === null || value === undefined || value === '' ? '-' : value;
+
+const moneyFormatter = (row, value) => Number(value || 0).toFixed(2);
+
+// 仅用于搜索、不在表格中出现的列
+const searchOnlyColumn = ({ label, prop, searchOrder, searchProp, dicData }) => {
+ const column = {
+ label,
+ prop,
+ search: true,
+ searchOrder,
+ searchPlaceholder: '请输入',
+ hide: true,
+ showColumn: false,
+ addDisplay: false,
+ editDisplay: false,
+ viewDisplay: false,
+ };
+ if (searchProp) column.searchProp = searchProp;
+ if (dicData) {
+ column.type = 'select';
+ column.searchPlaceholder = '请选择';
+ column.dicData = dicData;
+ }
+ return column;
+};
+
+export const paymentApplicationOption = ({
+ paymentTypeOptions = [],
+ approvalStatusOptions = [],
+ kingdeeStatusOptions = [],
+} = {}) => ({
+ height: 'auto',
+ calcHeight: 32,
+ tip: false,
+ searchBtnText: '查询',
+ emptyBtnText: '重置',
+ searchShow: true,
+ searchIcon: true,
+ searchLabelWidth: 88,
+ searchMenuSpan: 24,
+ searchIndex: 4,
+ searchMenuPosition: 'right',
+ border: true,
+ index: true,
+ indexLabel: '序号',
+ indexWidth: 70,
+ indexFixed: 'left',
+ selection: true,
+ selectionWidth: 52,
+ selectionFixed: 'left',
+ addBtn: false,
+ viewBtn: false,
+ delBtn: false,
+ editBtn: false,
+ copyBtn: false,
+ menuWidth: 320,
+ menuFixed: 'right',
+ dialogClickModal: false,
+ column: [
+ // ---------- 搜索项(前 4 项默认展示,其余折叠;searchOrder 越大越靠前) ----------
+ {
+ label: '单据号',
+ prop: 'paymentNo',
+ slot: true,
+ search: true,
+ searchOrder: 9,
+ searchPlaceholder: '请输入',
+ minWidth: 150,
+ },
+ {
+ label: '日期',
+ prop: 'applyDateRange',
+ type: 'daterange',
+ searchType: 'daterange',
+ search: true,
+ searchOrder: 8,
+ hide: true,
+ showColumn: false,
+ addDisplay: false,
+ editDisplay: false,
+ viewDisplay: false,
+ },
+ searchOnlyColumn({
+ label: '客户名称',
+ prop: 'payeeNameSearch',
+ searchOrder: 7,
+ searchProp: 'payeeName',
+ }),
+ searchOnlyColumn({
+ label: '项目名称',
+ prop: 'projectNameSearch',
+ searchOrder: 6,
+ searchProp: 'projectName',
+ }),
+ searchOnlyColumn({
+ label: '关联结算单',
+ prop: 'settlementNo',
+ searchOrder: 4,
+ }),
+ searchOnlyColumn({
+ label: '付款类型',
+ prop: 'paymentType',
+ searchOrder: 3,
+ dicData: paymentTypeOptions,
+ }),
+ searchOnlyColumn({
+ label: '审核状态',
+ prop: 'approvalStatus',
+ searchOrder: 2,
+ dicData: approvalStatusOptions,
+ }),
+ searchOnlyColumn({
+ label: '金蝶单据状态',
+ prop: 'kingdeeStatus',
+ searchOrder: 1,
+ dicData: kingdeeStatusOptions,
+ }),
+ // ---------- 表格列 ----------
+ { label: '付款类型', prop: 'paymentTypeName', minWidth: 110, formatter: textFormatter },
+ { label: '所属项目', prop: 'projectName', minWidth: 140, formatter: textFormatter },
+ {
+ label: '所属组织',
+ prop: 'deptName',
+ search: true,
+ searchOrder: 5,
+ searchslot: true,
+ minWidth: 120,
+ formatter: textFormatter,
+ },
+ { label: '合同编号', prop: 'contractNo', minWidth: 130, formatter: textFormatter },
+ { label: '合同名称', prop: 'contractName', minWidth: 140, formatter: textFormatter },
+ { label: '付款方', prop: 'payerName', minWidth: 130, formatter: textFormatter },
+ { label: '收款方', prop: 'payeeName', minWidth: 130, formatter: textFormatter },
+ { label: '申请付款金额', prop: 'appliedAmount', minWidth: 140, formatter: moneyFormatter },
+ { label: '已付款金额', prop: 'paidAmount', minWidth: 130, formatter: moneyFormatter },
+ {
+ label: '匹配发票金额',
+ prop: 'matchedInvoiceAmount',
+ minWidth: 130,
+ formatter: moneyFormatter,
+ },
+ { label: '申请人', prop: 'applicantName', minWidth: 100, formatter: textFormatter },
+ { label: '申请日期', prop: 'applyDate', minWidth: 120, formatter: textFormatter },
+ { label: '审核状态', prop: 'approvalStatusName', slot: true, minWidth: 110 },
+ { label: '当前节点', prop: 'currentNode', minWidth: 120, formatter: textFormatter },
+ { label: '当前处理人', prop: 'currentProcessor', minWidth: 120, formatter: textFormatter },
+ { label: '金蝶单据号', prop: 'kingdeeBillNo', minWidth: 130, formatter: textFormatter },
+ { label: '金蝶单据状态', prop: 'kingdeeStatusName', minWidth: 130, formatter: textFormatter },
+ ],
+});
diff --git a/src/styles/element-ui.scss b/src/styles/element-ui.scss
index 41d6a63..d8f73fe 100644
--- a/src/styles/element-ui.scss
+++ b/src/styles/element-ui.scss
@@ -367,7 +367,7 @@
// 4) 弹窗内表单项上下间距:预留绝对定位校验提示(约 14~18px)的可视空间
.el-dialog .el-form-item {
- margin-bottom: 24px;
+ //margin-bottom: 24px;
}
// 校验提示浮在控件下方,提高层级避免被下一行输入框背景遮挡
diff --git a/src/views/base/port-terminal.vue b/src/views/base/port-terminal.vue
index 8c0c318..5d4e631 100644
--- a/src/views/base/port-terminal.vue
+++ b/src/views/base/port-terminal.vue
@@ -1721,7 +1721,7 @@ export default {
.el-form-item--small.el-form-item,
.el-form-item--large.el-form-item,
.el-form-item--medium.el-form-item {
- margin-bottom: 24px;
+ margin-bottom: 20px;
}
}
diff --git a/src/views/base/region.vue b/src/views/base/region.vue
index 41be753..d724223 100644
--- a/src/views/base/region.vue
+++ b/src/views/base/region.vue
@@ -787,6 +787,6 @@ export default {
margin-bottom: 12px !important;
}
.el-form-item{
- margin-bottom: 24px !important;
+ margin-bottom: 22px !important;
}
diff --git a/src/views/business/components/business-crud-page.vue b/src/views/business/components/business-crud-page.vue
index 077b574..5eef92c 100644
--- a/src/views/business/components/business-crud-page.vue
+++ b/src/views/business/components/business-crud-page.vue
@@ -2993,9 +2993,10 @@
-
- {{ row.content || '-' }}
-
+
@@ -5282,6 +5283,7 @@ import { formatContractOptionLabel } from '@/utils/contract';
import { openImportDialog } from '@/utils/import-excel';
import { normalizeMapSearchResults, searchAmapPlaceText } from '@/utils/map-search';
import { loadMeasurementUnitOptions } from '@/utils/measurement-unit';
+import { formatRouteChangeContent } from '@/utils/route-change';
import { applyTableMenuWidth } from '@/utils/table-menu';
import { downloadFileByUrl, downloadXls } from '@/utils/util';
import { isMobile } from '@/utils/validate';
@@ -6628,6 +6630,7 @@ export default {
},
methods: {
formatContractOptionLabel,
+ formatRouteChangeContent,
initStandaloneFormPage() {
if (!this.isStandaloneFormPage) return;
const mode = this.$route.query.mode === 'edit' ? 'edit' : 'add';
@@ -15898,6 +15901,9 @@ export default {
&__route-change-record-content {
white-space: pre-wrap;
+ overflow-wrap: anywhere;
+ word-break: break-word;
+ line-height: 22px;
}
@media (max-width: 1200px) {
diff --git a/src/views/business/components/master-order-detail.vue b/src/views/business/components/master-order-detail.vue
index f881fc3..f40e339 100644
--- a/src/views/business/components/master-order-detail.vue
+++ b/src/views/business/components/master-order-detail.vue
@@ -59,7 +59,7 @@
-
{{ index ? '经' : '起' }}{{ segmentLocationName(route, 'departure') }}{{ route.departureAddress || '-' }}{{ index ? `已到达 ${quantity(segments[index - 1].arrivedQuantity)}/${quantity(master.totalQuantity)} ${plannedUnit(segments[index - 1])}` : `已调度 ${quantity(route.dispatchedQuantity)}/${quantity(master.totalQuantity)} ${plannedUnit(route)}` }}已调度 {{ quantity(route.dispatchedQuantity) }}/{{ quantity(master.totalQuantity) }} {{ plannedUnit(route) }}
终{{ segmentLocationName(route, 'arrival') }}{{ route.arrivalAddress || '-' }}已到达 {{ quantity(route.arrivedQuantity) }}/{{ quantity(master.totalQuantity) }} {{ plannedUnit(route) }}
+ {{ index ? '经' : '起' }}{{ segmentLocationName(route, 'departure') }}{{ formatRegionAddress(route.departureAddress) || '-' }}{{ index ? `已到达 ${quantity(segments[index - 1].arrivedQuantity)}/${quantity(master.totalQuantity)} ${plannedUnit(segments[index - 1])}` : `已调度 ${quantity(route.dispatchedQuantity)}/${quantity(master.totalQuantity)} ${plannedUnit(route)}` }}已调度 {{ quantity(route.dispatchedQuantity) }}/{{ quantity(master.totalQuantity) }} {{ plannedUnit(route) }}
终{{ segmentLocationName(route, 'arrival') }}{{ formatRegionAddress(route.arrivalAddress) || '-' }}已到达 {{ quantity(route.arrivedQuantity) }}/{{ quantity(master.totalQuantity) }} {{ plannedUnit(route) }}
@@ -110,6 +110,7 @@ import { getList as getCustomerArchiveList } from '@/api/vehicle/customer-archiv
import { CaretBottom, CaretTop } from '@element-plus/icons-vue';
import { ElImageViewer } from 'element-plus';
import { OpenFileViewer } from '@open-file-viewer/vue';
+import { formatRegionAddress } from '@/utils/region-address';
import { fallbackPlugin, imagePlugin, officePlugin, pdfPlugin, textPlugin } from '@open-file-viewer/core';
import '@open-file-viewer/core/style.css';
import pdfWorkerSrc from 'pdfjs-dist/build/pdf.worker.mjs?url';
@@ -164,6 +165,7 @@ export default {
} finally { this.loading = false; }
},
methods: {
+ formatRegionAddress,
quantity(value) { const number = Number(value || 0); return Number.isInteger(number) ? number : number.toFixed(3).replace(/\.?0+$/, ''); },
segmentNumber(route, index) { return String(route.segmentNo || '').replace(/^段/, '') || index + 1; },
isSegmentExpanded(segmentNo) { return Boolean(this.expandedSegments[segmentNo]); },
diff --git a/src/views/business/components/master-order-dispatch.vue b/src/views/business/components/master-order-dispatch.vue
index 191179f..0a5888f 100644
--- a/src/views/business/components/master-order-dispatch.vue
+++ b/src/views/business/components/master-order-dispatch.vue
@@ -21,10 +21,10 @@
{{ index === 0 ? '起' : '经' }}
- {{ route.departureName || '-' }}
+ {{ dispatchLocationName(route, 'departure') }}
- {{ route.departureAddress || '-' }}
+ {{ formatRegionAddress(route.departureAddress) || '-' }}
{{ contactText(route.departureContact, route.departurePhone) }}
@@ -34,10 +34,10 @@
终
- {{ route.arrivalName || '-' }}
+ {{ dispatchLocationName(route, 'arrival') }}
- {{ route.arrivalAddress || '-' }}
+ {{ formatRegionAddress(route.arrivalAddress) || '-' }}
{{ contactText(route.arrivalContact, route.arrivalPhone) }}
@@ -324,6 +324,7 @@ import {
resolvePriceUnitByQuantityUnit,
} from '@/utils/currency';
import { loadMeasurementUnitOptions } from '@/utils/measurement-unit';
+import { formatRegionAddress } from '@/utils/region-address';
import { isMobile } from '@/utils/validate';
const unwrapRecords = res => {
@@ -851,7 +852,12 @@ export default {
const mileage = Number(value);
return Number.isFinite(mileage) && mileage >= 0 ? String(Math.trunc(mileage)) : '';
},
- addressText(name, address) { return [name, address].filter(Boolean).join(' / ') || '-'; },
+ formatRegionAddress,
+ addressText(name, address) {
+ // 名称与详细地址都要按「市 区县」展示;两者格式化后相同时只展示一次
+ const values = [this.formatRegionAddress(name), this.formatRegionAddress(address)].filter(Boolean);
+ return [...new Set(values)].join(' / ') || '-';
+ },
contactText(name, phone) { return [name, phone].filter(Boolean).join(' - ') || '-'; },
dateStartLabel(route) { return route.documentType === '计划单' ? '计划开始日期' : '预计发货日期'; },
dateEndLabel(route) { return route.documentType === '计划单' ? '计划结束日期' : '预计完成日期'; },
diff --git a/src/views/business/components/shipping-template-page.vue b/src/views/business/components/shipping-template-page.vue
index a2d2901..21f0015 100644
--- a/src/views/business/components/shipping-template-page.vue
+++ b/src/views/business/components/shipping-template-page.vue
@@ -153,11 +153,24 @@
@click="handleTransportSecondaryAddressInputClick('departure')"
>
-
+
+
+
+
联系人
联系方式
-
-
- 选择
-
-
@@ -197,25 +200,28 @@
@click="handleTransportSecondaryAddressInputClick('arrival')"
>
-
+
+
+
+
联系人
联系方式
-
-
- 选择
-
-
@@ -1122,7 +1128,7 @@ import { loadMeasurementUnitOptions } from '@/utils/measurement-unit';
import { normalizeMapSearchResults, searchAmapPlaceText } from '@/utils/map-search';
import { downloadFileByUrl, downloadXls } from '@/utils/util';
import { isMobile } from '@/utils/validate';
-import { Location } from '@element-plus/icons-vue';
+import { Location, OfficeBuilding } from '@element-plus/icons-vue';
import { ElImageViewer } from 'element-plus';
import { OpenFileViewer } from '@open-file-viewer/vue';
import {
@@ -1273,6 +1279,7 @@ export default {
data() {
return {
Location,
+ OfficeBuilding,
form: {},
query: {},
loading: true,
@@ -3421,10 +3428,25 @@ export default {
.shipping-template-page__route-row {
display: grid;
grid-template-columns:
- minmax(160px, 1fr) minmax(220px, 2fr) auto minmax(140px, 1fr) auto minmax(160px, 1fr)
- auto;
+ minmax(160px, 1fr) minmax(220px, 2fr) 40px auto minmax(140px, 1fr) auto
+ minmax(160px, 1fr);
gap: 8px;
align-items: center;
+
+ .shipping-template-page__address-pick-btn {
+ :deep(.el-icon) {
+ font-size: 22px;
+ }
+ }
+}
+.shipping-template-page__map-suffix {
+ cursor: pointer;
+ color: #909399;
+ font-size: 18px;
+
+ &:hover {
+ color: #409eff;
+ }
}
.shipping-template-page__address-map-input {
cursor: pointer;
diff --git a/src/views/business/components/voucher-execution-detail.vue b/src/views/business/components/voucher-execution-detail.vue
index 0de4939..8b5623f 100644
--- a/src/views/business/components/voucher-execution-detail.vue
+++ b/src/views/business/components/voucher-execution-detail.vue
@@ -1,7 +1,7 @@
-
+
@@ -196,7 +196,7 @@ watch(
}
&__search-grid {
display: grid;
- grid-template-columns: 360px 360px 1fr;
+ grid-template-columns: 220px 220px 1fr;
gap: 8px 24px;
}
&__search-actions {
diff --git a/src/views/business/components/waybill-manage-page.vue b/src/views/business/components/waybill-manage-page.vue
index 5886577..6bd03c8 100644
--- a/src/views/business/components/waybill-manage-page.vue
+++ b/src/views/business/components/waybill-manage-page.vue
@@ -1334,7 +1334,7 @@
:disabled="dialogReadonly"
/>
-
+
-
- {{ row.content || '-' }}
-
+
@@ -3666,6 +3667,7 @@ import { wgs84ToGcj02 } from '@/utils/coordinate';
import { openImportDialog } from '@/utils/import-excel';
import { normalizeMapSearchResults, searchAmapPlaceText } from '@/utils/map-search';
import { loadMeasurementUnitOptions } from '@/utils/measurement-unit';
+import { formatRouteChangeContent } from '@/utils/route-change';
import { applyTableMenuWidth } from '@/utils/table-menu';
import { downloadFileByUrl, downloadXls } from '@/utils/util';
import { isMobile } from '@/utils/validate';
@@ -4753,6 +4755,7 @@ export default {
},
methods: {
formatContractOptionLabel,
+ formatRouteChangeContent,
// 页面被 keep-alive 缓存/卸载时调用,关闭本页所有 append-to-body 的弹窗,
// 避免 Teleport 出去的 DOM 残留在 body 上盖住后续页面(详见 data 里的说明)。
closeInnerDialogs() {
@@ -5218,10 +5221,11 @@ export default {
return true;
},
showDetailButton(row) {
+ // 草稿与其它状态统一走「详情」(独立详情页 / 详情弹窗),
+ // 不再对草稿单独降级为 avue 的「查看」表单弹窗,避免同一列出现两种弹窗形态。
if (!this.config.detailButton || !this.hasPermission(`${this.config.permission}_view`))
return false;
- const status = this.statusValue(row);
- return status !== 'draft';
+ return true;
},
formatDetailValue(row, prop) {
if (!row) return '-';
@@ -11844,6 +11848,9 @@ export default {
&__route-change-record-content {
white-space: pre-wrap;
+ overflow-wrap: anywhere;
+ word-break: break-word;
+ line-height: 22px;
}
&__common-address-toolbar {
diff --git a/src/views/business/contract-manage-change.vue b/src/views/business/contract-manage-change.vue
index 5507c25..016efb8 100644
--- a/src/views/business/contract-manage-change.vue
+++ b/src/views/business/contract-manage-change.vue
@@ -754,7 +754,7 @@ export default {
.contract-basic-section__change-type { grid-column: 1 / -1; }
.contract-basic-section__remark {
width: 100%;
- margin-bottom: 24px !important;
+ margin-bottom: 23px !important;
&:last-child {
margin-bottom: 0 !important;
@@ -763,7 +763,7 @@ export default {
.contract-basic-section__date-range { display: grid; grid-template-columns: minmax(0, 1fr) auto minmax(0, 1fr); align-items: center; gap: 8px; width: 360px; max-width: 100%; }
.contract-basic-section__date-range span { color: #606266; }
.contract-basic-section__date-range :deep(.el-date-editor) { width: auto; min-width: 0; }
-.contract-basic-section :deep(.el-form-item) { align-items: center; margin-bottom: 24px; }
+.contract-basic-section :deep(.el-form-item) { align-items: center; }
.contract-basic-section :deep(.el-form-item__label) { flex: 0 0 96px !important; width: 96px !important; height: auto; padding-right: 12px; white-space: normal; word-break: break-all; line-height: 18px; }
.contract-basic-section :deep(.el-form-item__content) { min-width: 0; }
.contract-basic-section :deep(.el-input),
diff --git a/src/views/business/contract-manage.vue b/src/views/business/contract-manage.vue
index 098377a..088ea83 100644
--- a/src/views/business/contract-manage.vue
+++ b/src/views/business/contract-manage.vue
@@ -613,8 +613,8 @@
diff --git a/src/views/business/loading-manage.vue b/src/views/business/loading-manage.vue
index de5e41b..26d3785 100644
--- a/src/views/business/loading-manage.vue
+++ b/src/views/business/loading-manage.vue
@@ -1685,9 +1685,7 @@
label="变更人"
min-width="120" />
- {{ row.content || '-' }}
-
{{ formatRouteChangeContent(row.content) }}
@@ -124,6 +127,7 @@ export default {
return {
query: emptyQuery(),
counts: { all: 0, within30: 0, within90: 0, over90: 0 },
+ searchVisible: true,
rows: [],
loading: false,
page: { current: 1, size: 10, total: 0 },
diff --git a/src/views/payment/bill-payment.vue b/src/views/payment/bill-payment.vue
index 1f63ccb..d44acbd 100644
--- a/src/views/payment/bill-payment.vue
+++ b/src/views/payment/bill-payment.vue
@@ -1,6 +1,7 @@
@@ -47,6 +50,7 @@ export default {
data() {
return {
query: emptyQuery(),
+ searchVisible: true,
rows: [],
loading: false,
page: { current: 1, size: 10, total: 0 },
diff --git a/src/views/payment/components/bill-ledger-table.vue b/src/views/payment/components/bill-ledger-table.vue
index 02ef19e..b92fb21 100644
--- a/src/views/payment/components/bill-ledger-table.vue
+++ b/src/views/payment/components/bill-ledger-table.vue
@@ -4,11 +4,19 @@
新增
+
-
+
import { mapGetters } from 'vuex';
import { billLedgerTableColumns } from '@/option/payment/billLedger';
+import TableHeaderTools from '@/components/table-header-tools/main.vue';
export default {
name: 'BillLedgerTable',
+ components: { TableHeaderTools },
props: {
rows: { type: Array, default: () => [] },
loading: { type: Boolean, default: false },
page: { type: Object, required: true },
+ searchVisible: { type: Boolean, default: true },
},
- emits: ['add', 'view', 'edit', 'delete', 'page-change', 'size-change'],
+ emits: ['add', 'view', 'edit', 'delete', 'page-change', 'size-change', 'refresh', 'update:searchVisible'],
data() {
- return { columns: billLedgerTableColumns };
+ return {
+ columns: billLedgerTableColumns,
+ visibleColumnKeys: billLedgerTableColumns.map(column => column.prop),
+ tableSize: 'default',
+ };
},
computed: {
...mapGetters(['permission']),
+ visibleColumns() {
+ return this.columns.filter(column => this.visibleColumnKeys.includes(column.prop));
+ },
},
methods: {
hasPermission(code) {
@@ -96,7 +114,7 @@ export default {
.bill-ledger-table__toolbar {
display: flex;
align-items: center;
- justify-content: flex-start;
+ justify-content: space-between;
padding: 12px 0;
}
.bill-ledger-table__links {
diff --git a/src/views/payment/components/bill-payment-table.vue b/src/views/payment/components/bill-payment-table.vue
index 6e2a664..46c8f75 100644
--- a/src/views/payment/components/bill-payment-table.vue
+++ b/src/views/payment/components/bill-payment-table.vue
@@ -4,11 +4,19 @@
新增
+
-
+
import { mapGetters } from 'vuex';
import { billPaymentTableColumns } from '@/option/payment/billPayment';
+import TableHeaderTools from '@/components/table-header-tools/main.vue';
import { isAdministrator } from '@/utils/auth';
export default {
name: 'BillPaymentTable',
+ components: { TableHeaderTools },
props: {
rows: { type: Array, default: () => [] },
loading: { type: Boolean, default: false },
page: { type: Object, required: true },
mkEnabled: { type: Boolean, default: false },
+ searchVisible: { type: Boolean, default: true },
},
emits: [
'add',
@@ -137,12 +148,21 @@ export default {
'void',
'page-change',
'size-change',
+ 'refresh',
+ 'update:searchVisible',
],
data() {
- return { columns: billPaymentTableColumns };
+ return {
+ columns: billPaymentTableColumns,
+ visibleColumnKeys: billPaymentTableColumns.map(column => column.prop),
+ tableSize: 'default',
+ };
},
computed: {
...mapGetters(['permission', 'userInfo']),
+ visibleColumns() {
+ return this.columns.filter(column => this.visibleColumnKeys.includes(column.prop));
+ },
isAdmin() {
return isAdministrator(this.userInfo);
},
@@ -173,7 +193,8 @@ export default {
}
.bill-payment-table__toolbar {
display: flex;
- justify-content: flex-start;
+ align-items: center;
+ justify-content: space-between;
padding: 12px 0;
}
.bill-payment-table__links {
diff --git a/src/views/payment/invoice-application.vue b/src/views/payment/invoice-application.vue
index 27e91cb..4dcdbbc 100644
--- a/src/views/payment/invoice-application.vue
+++ b/src/views/payment/invoice-application.vue
@@ -1,6 +1,6 @@
-
+
@@ -78,18 +78,26 @@
>导出
-
-
-
-
-
+
-
+
diff --git a/src/views/payment/receipt-claim-record.vue b/src/views/payment/receipt-claim-record.vue
index 5b4bca3..1d68dad 100644
--- a/src/views/payment/receipt-claim-record.vue
+++ b/src/views/payment/receipt-claim-record.vue
@@ -1,6 +1,6 @@
-
+
@@ -80,10 +80,19 @@
-
+
+
({
@@ -156,16 +166,19 @@ const emptyQuery = () => ({
export default {
name: 'ReceiptClaimRecord',
- components: { SectionCard },
+ components: { SectionCard, TableHeaderTools },
data() {
return {
ArrowDown,
ArrowUp,
query: emptyQuery(),
searchExpanded: false,
+ searchVisible: true,
+ tableSize: 'default',
claimStatusOptions: api.claimStatusOptions,
kingdeeBillStatusOptions: api.kingdeeBillStatusOptions,
columns: receiptClaimRecordTableColumns,
+ visibleColumnKeys: receiptClaimRecordTableColumns.map(column => column.prop),
rows: [],
loading: false,
page: { current: 1, size: 10, total: 0 },
@@ -173,6 +186,9 @@ export default {
},
computed: {
...mapGetters(['permission', 'userInfo']),
+ visibleColumns() {
+ return this.columns.filter(column => this.visibleColumnKeys.includes(column.prop));
+ },
isAdmin() {
return isAdministrator(this.userInfo);
},
@@ -279,6 +295,12 @@ export default {
.claim-record-page__table-panel {
margin-top: 8px;
}
+.claim-record-page__toolbar {
+ display: flex;
+ justify-content: flex-end;
+ align-items: center;
+ padding: 12px 0;
+}
.claim-record-page__pagination {
display: flex;
justify-content: flex-end;
diff --git a/src/views/payment/receipt-flow.vue b/src/views/payment/receipt-flow.vue
index c5d5685..fbd5915 100644
--- a/src/views/payment/receipt-flow.vue
+++ b/src/views/payment/receipt-flow.vue
@@ -1,6 +1,6 @@
-
+
@@ -52,33 +52,43 @@
-
- 手动同步流水
-
-
- 批量认领
-
+
+
+ 手动同步流水
+
+
+ 批量认领
+
+
+
({
export default {
name: 'ReceiptFlow',
+ components: { TableHeaderTools },
data() {
return {
query: emptyQuery(),
claimStatusOptions: api.claimStatusOptions,
columns: receiptFlowTableColumns,
+ visibleColumnKeys: receiptFlowTableColumns.map(column => column.prop),
+ searchVisible: true,
+ tableSize: 'default',
rows: [],
selectedRows: [],
loading: false,
@@ -281,6 +296,9 @@ export default {
},
computed: {
...mapGetters(['permission', 'userInfo']),
+ visibleColumns() {
+ return this.columns.filter(column => this.visibleColumnKeys.includes(column.prop));
+ },
isAdmin() {
return isAdministrator(this.userInfo);
},
@@ -436,11 +454,15 @@ export default {
}
.receipt-flow-page__toolbar {
display: flex;
- justify-content: flex-start;
+ justify-content: space-between;
align-items: center;
gap: 8px;
padding: 12px 0;
}
+.receipt-flow-page__toolbar-left {
+ display: flex;
+ gap: 8px;
+}
.receipt-flow-page__pagination {
display: flex;
justify-content: flex-end;
diff --git a/src/views/settlement/components/formal-settlement-editor.vue b/src/views/settlement/components/formal-settlement-editor.vue
index 9e65d8a..a52229b 100644
--- a/src/views/settlement/components/formal-settlement-editor.vue
+++ b/src/views/settlement/components/formal-settlement-editor.vue
@@ -175,6 +175,17 @@
+
+ 导出
+
+
+ 选择结算明细
+
{{ detailCollapsed ? '展开明细' : '收起明细' }}
@@ -182,33 +193,46 @@
-
+
-
+
-
+
-
+
查询
重置
-
- 导出
-
-
- 选择结算明细
-
@@ -991,6 +1015,7 @@ import { mapGetters } from 'vuex';
import { formatContractOptionLabel } from '@/utils/contract';
import { downloadFileByUrl } from '@/utils/util';
import * as XLSX from 'xlsx';
+import { isAdministrator } from '@/utils/auth';
export default {
name: 'FormalSettlementEditor',
@@ -1126,7 +1151,7 @@ export default {
};
},
computed: {
- ...mapGetters(['userInfo']),
+ ...mapGetters(['permission', 'userInfo']),
visible: {
get() {
return this.modelValue;
@@ -1154,6 +1179,9 @@ export default {
editable() {
return !this.readonly;
},
+ isAdmin() {
+ return isAdministrator(this.userInfo);
+ },
title() {
return this.readonly ? '查看正式结算单' : this.recordId ? '编辑正式结算单' : '新增正式结算单';
},
@@ -1225,6 +1253,9 @@ export default {
},
methods: {
formatContractOptionLabel,
+ hasPermission(code) {
+ return this.isAdmin || this.validData(this.permission?.[code], false);
+ },
async loadFormalDetail(id) {
const response = this.publicMode
? await getMkPublicDetail('formal-settlement', id)
@@ -2829,20 +2860,24 @@ export default {
}
.formal-editor__actions {
display: flex;
+ flex-wrap: wrap;
align-items: center;
gap: 8px;
}
.formal-editor__detail-filter {
display: grid;
- grid-template-columns: repeat(4, minmax(0, 1fr));
+ grid-template-columns: repeat(4, minmax(0, 1fr)) auto;
+ align-items: center;
gap: 8px 16px;
- margin-bottom: 8px;
+ margin-bottom: 12px;
padding: 12px 12px 4px;
background: #fff;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.04);
}
.formal-editor__detail-filter-actions {
- grid-column: 1 / -1;
+ width: auto;
+ margin-left: 0;
+ white-space: nowrap;
justify-self: end;
}
.formal-editor__page-actions {
@@ -2965,9 +3000,29 @@ export default {
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.04);
}
+/* 结算明细筛选区:覆盖全局 el-form-item 24px 行距,保证筛选控件与「查询/重置」同行居中对齐 */
+.formal-editor__detail-filter :deep(.el-form-item) {
+ display: flex;
+ min-width: 0;
+ margin-right: 0;
+ margin-bottom: 0;
+}
+.formal-editor__detail-filter :deep(.el-form-item__content) {
+ flex: 1;
+ min-width: 0;
+}
+.formal-editor__detail-filter :deep(.el-input) {
+ width: 100%;
+}
+.formal-editor__detail-filter-actions :deep(.el-form-item__content) {
+ flex: none;
+ justify-content: flex-end;
+ flex-wrap: nowrap;
+}
+
@media (max-width: 1200px) {
.formal-editor__detail-filter {
- grid-template-columns: repeat(2, minmax(0, 1fr));
+ grid-template-columns: repeat(2, minmax(0, 1fr)) auto;
}
}
diff --git a/vite.config.mjs b/vite.config.mjs
index a7036fd..f757736 100644
--- a/vite.config.mjs
+++ b/vite.config.mjs
@@ -64,7 +64,7 @@ export default ({ mode, command }) => {
port: 2889,
proxy: {
'/api': {
- target: 'http://172.16.203.228:8000',
+ target: 'http://172.16.203.221:8000',
changeOrigin: true,
},
},