feat(loading-manage): 配载管理详情及重新派单改用独立页面

- 新增 `/business/loading-manage/detail` 路由,详情页由独立页面承载
- 统一将新增、编辑、详情和重新派单均采用独立表单页/详情页显示
- 修改组件内逻辑,新增 `isStandalonePage` 计算属性统一控制页面与弹窗切换
- 修改列表页链接及操作行为,详情和重新派单按钮跳转至对应独立页面
- 去除旧详情弹窗模式,相关样式调整支持独立页面自然滚动
- 添加路由监听,支持页面刷新和切换时正确初始化详情或表单数据
- 兼容老链接 `/business/loading-manage?detailId=x` 自动跳详情新路由
- 修正弹窗底栏样式,去除写死间距,统一由全局规则控制
- 代码中禁止顶层 scoped style 使用 `//` 注释,改用 `/* */` 避免编译错误
This commit is contained in:
2026-09-17 22:40:41 +08:00
parent 3d6e297147
commit 5bb072223f
5 changed files with 185 additions and 155 deletions
+32 -113
View File
@@ -1,119 +1,38 @@
# 项目长期记忆(tms-erp-web-ws / Saber3 客商模块
# 项目长期记忆(tms-erp-web-ws / Saber3
## 设计约定(已确认)
配套后端:`/Users/gxwebsoft/JAVA/tms-api`(不是 tms-erp-api-ws,后者功能滞后)。
### 表单 placeholder 统一为「请输入 / 请选择」(不带字段名
- **规范**:输入框 placeholder 只写动作词 `请输入` / `请选择`,**后面不跟字段名称**(label 已说明是什么字段,重复无意义)
- 已生效页面:waybill-manage、transport-plan、shipping-template、temporary-credit-limit(搜索栏 + 弹窗表单)、**driver.vue**2026-09-09 补齐 18 处)
- **3 类例外必须保留,不要误简化**:
1. 复合操作:如 `请输入或选择车辆`(既可键盘输入又可点按钮弹窗选车),丢掉会损失语义
2. 日期区间的 `起` / `止`:是起止标记,不是字段提示
3. 默认值说明:如资格证号 `默认身份证号`、车牌号 `如:京A12345`,属于示例/默认值提示,非「请输入」格式
- **校验错误提示 message 保持带字段名**(如「请选择准驾车型」「请输入档案编号」),与 placeholder 定位不同——报错必须指明具体哪个字段,不能只说「请选择」。
- Avue 配置化表单:在 option 各列加 `placeholder`;搜索栏可用通用工具 `withSearchPlaceholders([...])`(见 temporary-credit-limit.js)。
- 批量改法:Python 脚本按**完整 placeholder 字符串**做精确替换,每规则校验 `count > 0` 否则中断,避免误伤;改完用 `@vue/compiler-sfc` 的 parse + compileTemplate + compileScript 验证(脚本须放项目根目录下跑,放 /tmp 解析不到 node_modules)。
## 页面结构:列表 + 独立表单页 + 独立详情页(同一组件分流
- `src/router/views/index.js` 里三条静态/菜单路由指向同一个 .vue:`/xxx`(后端菜单)、`/xxx/form``/xxx/detail`form/detail 都是 `component: Layout` + 空 children`meta: { keepAlive: false }`detail 额外带 `activeMenu: '/xxx'`
- 组件内按 `$route.path` 分流(如 `isFormPage` / `isDetailPage` / `isListPage`),列表部分统一 `v-if="!isStandalonePage"` 隐藏搜索栏/工具栏/表格/分页
- 路由记录不同但组件相同 → Vue 复用实例,所以**必须**在 `watch: { $route(to, from) }` 里重新 init`mounted` 只覆盖「直接打开/刷新」场景。切换详情与表单时不要只比 id,还要看当前 `dialogMode` 是否匹配。
- 跳转:`this.$router.push({ path: '/xxx/form', query: { mode, id, name: '新增xxx' } })`;标签标题由 avue-router 取 `query.name`。关闭:`this.$router.$avueRouter?.closeTag?.()` + push 列表
- 已按此模式:contract-manageform+detail)、waybill-manage、loading-manage(新增/编辑/重新派单/详情,2026-09-17 完成)
- 底栏文案:只读详情页用「关闭」;表单页沿用「关闭/暂存/确认」
### 弹窗分组:统一用全局 `<section-card>`
- 弹窗 body 背景由 `src/styles/element-ui.scss` 全局 `.el-dialog__body { background:#f5f6fa; padding:16px }` 统一灰底
- 每组 = 白底卡片:4px 主色竖条 + 8px 间距 + 圆角 6px + 淡阴影。组件全局注册 `<section-card title="...">`,支持 `#title` / `#extra` slot
- 仅 3 项的小分组用 `el-col :span="8"` 均分一行
## 样式硬规则
- 底部操作栏:`display:flex; justify-content:flex-end;`**不写 gap**(间距由 Element Plus 默认 `.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;中间步骤 = `type="primary" plain`;主操作 = `type="primary"`(禁绿色 success,禁两个蓝实心)。顺序 `[辅助工具][取消][保存草稿][提交]`
- ⚠️ **`<style scoped lang="scss">` 顶层禁止 `//` 行注释**Vite5 + sass modern-compiler + scoped 会报 `Unexpected '/'.`,样式模块直接 HTTP 500(块内的 `//` 正常)。顶层统一用 `/* */`
- 独立页顶部标题用 `.archive-page-form__title`(全局:18px/600 + 4px 主色竖条),或 business-crud-page 的 `formPageTitle` prop。
- 弹窗分组统一全局 `<section-card>`(白卡 + 4px 主色竖条);弹窗灰底 `#f5f6fa` 由全局 `.el-dialog__body` 提供。
- 上传证件区:`width:100%; max-width:240px` + `.el-upload{height:151px}`,禁止写死 px。
- 其他全站细则(表格线 #EFF1F7、偶数行 #FAFAFA、搜索栏 8px 行距 / label≥160px / `searchIndex:4`、操作列宽度与纯文字链接、表格内嵌控件全宽、`.el-tag.status-text` 文字化)见 `src/styles/element-ui.scss` 与 AGENTS.md 4.4。
### Avue 内置弹窗用 section-carddialogCustomClass 透明化 avue-form
- option 顶层加 `dialogCustomClass:'xxx-dialog'`,再在 `element-ui.scss` 写三条:
1. `.xxx-dialog .avue-form { background:transparent; box-shadow:none; padding:0 }`
2. `.xxx-dialog .avue-form__group > .el-col > .el-form-item { margin-bottom:0 }`
3. `.xxx-dialog .avue-dialog__footer, .avue-crud__dialog .xxx-dialog .avue-dialog__footer { margin-top:0 !important }`
## 表单文案
- placeholder 只写 `请输入` / `请选择`,不带字段名;必须保留的例外:`请输入或选择车辆`、日期区间 `起/止`、示例值(`如:京A12345`)。
- 校验 message 必须带字段名(`请选择准驾车型`)。
- Avue 配置化表单:option 各列加 `placeholder`;搜索栏可用 `withSearchPlaceholders([...])`
- 存量的独立页 `max-height:78vh` 内嵌滚动盒写法是历史遗留,新做页面应让内容随 `#avue-view`(自身 `overflow-y:auto`)自然滚动 + 固定底栏。
### 弹窗底部操作栏浮动(全局 CSS
- `.el-dialog__body { max-height:calc(100vh - 200px); overflow-y:auto; overflow-x:hidden }`
- `.el-dialog__footer { margin:0; padding:12px 20px; background:#fff; border-top:1px solid #ebeef5; box-shadow:0 -2px 8px rgba(0,0,0,.04) }`
- Avue 内置弹窗 footer 真实 class 是 `.avue-dialog__footer`,须 `position:sticky; bottom:0; flex:none; background:#fff` 贴底
## 复用与工具
- `business/components/business-crud-page.vue`option 由 `crudOption` prop 经 `cloneOption` 克隆;独立表单页走 `PageAvueForm`;自定义详情弹窗 `detailButton + detailSections`
- SFC 自检:项目根目录写临时脚本用 `@vue/compiler-sfc` 的 parse + compileScript + compileTemplate(脚本必须放根目录,放 /tmp 找不到 node_modules)。
- 更快的改后自检(dev server 已在 2889 时):`curl "localhost:2889/src/xxx.vue"`(模板+脚本)与 `curl "localhost:2889/src/xxx.vue?vue&type=style&index=0&scoped=true&lang.scss"`(样式),HTTP 200 即编译通过、500 会返回带堆栈的错误页
- ⚠️ `vite build` 会在 `src/page/login/facelogin.vue` 处被 WorkBuddy 沙箱的敏感内容保护中断(环境问题,非代码问题),不能作为唯一校验手段。
- dev`VITE_APP_API=/api`vite 代理 `172.16.203.228:8000`;改 .env / vite.config.mjs 须重启。
### 上传证件区域尺寸(transportCapacity
- 身份证/行驶证/道路运输证等目标比例 **240×151px**1.586:1),ship 为 248px 上限
- ⚠️ **不能写死 `width:240px`**:弹窗 `width:90%` + 列 `el-col :span=6` 是弹性的,固定 px 只在特定分辨率/系统缩放下恰好等于列宽(本机对齐、他人错位)。统一写法:
```scss
.xxx-uploader--large { display:block; width:100%; max-width:240px; flex:none; margin:0; }
.xxx-uploader--large .el-upload { width:100%; height:151px; }
```
2026-09-11 已修正 driver / vehicle / ship 三处。
### 上传图片点击放大预览
- `src/components/image-upload-field/main.vue` 用 `<el-image preview-src-list fit="contain" preview-teleported :z-index="3000" @click.stop>`,阻止冒泡到 el-upload。
### 独立表单页标题 `archive-page-form__title`
- 样式全局化到 `src/styles/element-ui.scss`18px/600/#303133 + margin-bottom:20px + 4px 主色竖条 + 8px 间距。
- business-crud-page 系列通过 `formPageTitle` prop 注入;loading-manage / master-order 手写前置标题。
### 表格列内嵌输入控件全宽
- `element-ui.scss` 全局兜底:`.el-table .el-table__body td.el-table__cell .cell > .el-input-number/.el-input/.el-select/.el-date-editor/.el-cascader { width:100% }`。
### 业务状态标签文字化
- 全站"状态语义"的 `el-tag` 加 `class="status-text"`,转为普通文字,不带颜色/背景/边框/圆点。
- 实现:`element-ui.scss` 末尾 `.el-tag.status-text { ... }`。
### 弹窗 / 独立表单页底栏按钮统一规则
- **排序**:从左到右按次→主排(次要居左、主操作居右),主操作永远放最后。
- 顺序模板:`[可选辅助工具(如同步/导入)][取消][保存草稿][提交/确认]`
- **容器布局(2026-08-27 改为「去 gap」)**`display:flex; justify-content:flex-end;`**不写 `gap`**)。按钮间距统一由 Element Plus 全局默认的相邻按钮 `.el-button+.el-button{margin-left:12px}` 提供,全站恒为 12px。原各 footer 写死的 `gap:8/12px` 已全部移除。`src/styles/element-ui.scss` 里 `.el-dialog__footer .el-button + .el-button` 也已从 `8px` 改 `12px`,与之一致。弹窗关闭按钮继承 Element Plus 自带 `.el-dialog__footer` 全局规则(见上条),独立页用 `.archive-form__footer` 配 `.archive-page-form & { position:fixed; right:0; left:230px; bottom:0; z-index:10; margin:0; padding:12px 24px; border-top:1px solid #eff1f7; background:#fff; box-shadow:0 -2px 8px rgba(0,0,0,.06); }`。
- **⚠️ 浮动底部必须用 `position:fixed`,不能用 `position:sticky`**:本项目布局 `.app-main` / `.basic-container` 祖先带 `overflow:hidden`,会导致 `sticky` 永不触发(footer 不吸底)。固定写法照搬 customer-archive
```scss
.xxx-actions {
position: fixed; right: 0; left: 230px; bottom: 0; margin: 0; z-index: 10;
/* flex-end + padding + border-top + 白底 + 上投影 */
}
:global(.avue--collapse .xxx-actions) { left: 60px; } /* 侧栏折叠 */
:global(.avue-layout--horizontal .xxx-actions) { left: 0; } /* 横向布局 */
```
注意 `:global()` 要把「整个选择器」包进去(customer-archive 写法),不要只包前缀,否则 scoped 会在中间选择器上追加 data 属性导致不匹配。
- **颜色层级 3 档**
| 语义 | type | 备注 |
| --- | --- | --- |
| 次要(取消 / 关闭) | 不写 type(默认) | 灰描边 |
- **全站底部操作栏「返回」已统一改为「取消」**:独立表单页返回列表、弹窗关闭、主从编辑器返回等场景一律写「取消」;仅只读查看 / 纯关闭场景保留「关闭」。
| 中间步骤(保存草稿 / 按匹配结果更新) | `type="primary" plain` | 蓝描边 |
| 主操作(提交 / 完成对账 / 复评确认) | `type="primary"` | 蓝实心 |
- **禁区**
- 保存草稿和主操作不允许同为 `type="primary"`,否则两个蓝实心无视觉层级。
- 主操作禁止用 `type="success"`(绿)。全站统一深蓝实心。
- **生效页面(已按规则统一)**:
- `business/project-apply.vue`footer + 变更 footer
- `vehicle/customer-archive.vue` 全部 5 处 footer`archive-form__footer` 主表单页 + `contact-dialog`/`receipt-dialog`/`invoice-dialog` 子弹窗 + `score-detail-dialog` 含总分合计)
- `business/components/business-crud-page.vue` 的 PageAvueForm 独立表单页(`#menu-form-before` slot 新增 `取消` 按钮,shipping-template 模式验证通过;影响 `/business/waybill-manage/form`、`/business/transport-plan/form`、`/business/shipping-template/form` 三处独立表单页)
- `business/components/master-order-editor.vue` 主 `<footer>`(取消 default + 暂存/确认创建 plain + 创建并调度 primary`position:fixed` 浮动,含 `avue--collapse`/`avue-layout--horizontal` 左偏移)
- **payment 模块**表单页(`__actions` 均 `flex-end` + `position:fixed` 浮动,含 `avue--collapse`/`avue-layout--horizontal` 左偏移):
- `payment-application-form.vue``[取消][保存 plain][提交]`
- `invoice-application-form.vue` / `invoice-receipt-form.vue``[取消][同步 plain][保存 plain][提交]`
- `bill-ledger-form.vue``[取消][确认 primary]`
- `bill-payment-form.vue``[取消][保存 plain][提交]`
- `receipt-flow-form.vue``[取消][确认 primary]`
- `receipt-claim-record-form.vue`(单「关闭」按钮,居中→右对齐浮动)
- **settlement 模块**
- `settlement/components/pre-settlement-editor.vue`(弹窗 + pageMode 两处 footer:保存加 `plain``&__page-actions` 加 `position:fixed` 浮动,含 `avue--collapse`/`avue-layout--horizontal` 左偏移)
- `settlement/components/formal-settlement-editor.vue``.formal-editor__page-actions` 加 `position:fixed` 浮动,含 `avue--collapse`/`avue-layout--horizontal` 左偏移;按钮已 `[取消][提交]` 合规)
- `settlement/receivable-payable-detail.vue`(生成费用弹窗「上一步」改 `plain`
- **已合规未动**`settlement/components/transport-reconciliation-editor.vue``[取消][保存草稿 plain][按匹配结果更新账单 plain][完成对账 primary]`)、`settlement/components/settlement-adjustment-editor.vue`(弹窗 `[取消][保存 primary]`)、`business/temporary-credit-limit.vue`。
- **Avue-form footer 结构注意**`#menu-form-before` slot 内容在 Avue 内置按钮之**左**;`#menu-form` slot 在 Avue 内置按钮之**右**。要实现 `[次要][Avue 主操作]` 顺序,把次要按钮放进 `#menu-form-before`,主操作保留 Avue 内置即可。
- **Avue 表单 footer 按钮去图标(统一纯文字)**:Avue 的 `avue-form` 提交/清空按钮内置图标(`submitIcon: 'el-icon-check'`、`emptyIcon: 'el-icon-delete'`),且 Avue 源码用 `|| 'el-icon-xxx'` 兜底,无法通过 option 配置去掉。已在 `business-crud-page.vue` 样式里用 `:global(.business-crud-form-page-dialog .avue-form__menu .el-icon){display:none}` 统一隐藏,仅作用于独立表单页(PageAvueForm 容器),不影响其它弹窗。新增/编辑弹窗内的 Avue 表单若也要去图标,复用此选择器即可。
- **复杂 footer 例外**:当 footer 需要在按钮组左侧展示「总分合计 / 数量统计」等聚合信息时,块用 `flex:1` 推自己到最左,按钮组按上述规则排在右半边(参考 `score-detail-dialog`)。
## 关键文件
- `src/components/section-card/main.vue`
- `src/styles/element-ui.scss`
- `src/views/vehicle/customer-archive.vue`
- `src/views/transportCapacity/{driver,vehicle,ship}.vue`
- `src/views/business/project-apply.vue`
## 环境注意
- Vite 代理跨域:`axios.withCredentials=true`,改 `.env`/`vite.config.mjs` 须重启 dev。
## business-crud-page 复用模式
- option 由 prop `crudOption` 经 `cloneOption` 克隆。
- 独立表单页走 `PageAvueForm`,不包 el-dialog。
- 自定义详情弹窗:`config.detailButton=true` + `detailSections:[{title, fields}]`。
## 配套后端工程与导出字段约定
- **本前端工程 tms-erp-web-ws 对应的后端是 `/Users/gxwebsoft/JAVA/tms-api`**(不是 `tms-erp-api-ws`,后者功能滞后,排查时不要看错仓库)。
- 所有列表导出(批量导出/下载模板)由后端 FastExcel 生成,前端 `exportColumns` 参数后端未使用 —— 导出列以 `XxxExportExcel.java` 为准,前端改 option 不影响导出。
- BladeX `org.springblade.core.tool.utils.BeanUtil` **继承 Spring `BeanUtils`**:字段类型不兼容时**静默跳过**(实体 `Date createTime` → Excel `LocalDateTime createTime` 会丢值)。所以 Excel 导出的审计时间必须在 Service 里手动赋值,并用 `DateUtil.fromDate(...)` 做 Date→LocalDateTime 转换(`Func.isEmpty` 判空)。参考写法见 Waybill/InsuranceRecord 的 `exportXxx()`。
- 排查导出文件问题:用 Python 标准库 `zipfile` + 解析 `xl/worksheets/sheet1.xml`、`xl/styles.xml` 直接看单元格有无 `<v>` 与 numFmt,无需装 openpyxl。
## 导出(后端 FastExcel
- 前端 `exportColumns` 后端未使用,导出列以 `XxxExportExcel.java` 为准
- BladeX `BeanUtil`(继承 Spring BeanUtils)字段类型不兼容时**静默跳过**:实体 `Date createTime` → Excel `LocalDateTime` 会丢值,须在 Service 手动 `DateUtil.fromDate(...)` 赋值(参考 Waybill / InsuranceRecord)。
- 排查导出文件用 Python `zipfile``xl/worksheets/sheet1.xml` 看有无 `<v>` 与 numFmt,无需 openpyxl。