221 KiB
Executable File
项目总览文档
8. showtime_weihai
类型: Vue3 管理平台(Mapbox 增强版)
技术栈:
- Vue 3.5+ (Composition API)
- Tauri 2.8+ - 跨平台桌面应用框架
- Element Plus 2.9+ - UI 组件库
- Mapbox GL 3.18+ - 地图引擎(核心特色)
- ECharts 6.0+ - 数据可视化
- face-api.js 0.22+ - 浏览器端人脸识别
- Three.js 0.180+ - 3D 图形库
- Axios 1.7+ - HTTP 请求
- Pinia 2.3+ - 状态管理
- Vue Router 4.5+ - 路由管理
- Tailwind CSS 4.1+ - 样式框架
核心功能:
- ✅ 用户管理 - 用户列表与部门 KPI(
roles弹窗编辑、详情抽屉只读预览)、用户信息、支付列表 - ✅ 账户管理 - 账户列表、分组管理、订单管理
- ✅ 课程管理 - 课程列表、课程信息、课程日历
- ✅ 公司管理 - 公司信息、公司列表
- ✅ 保安管理 - 保安列表、值班管理
- ✅ 任务管理 - 任务列表、任务包管理
- ✅ 项目管理 - 项目列表
- ✅ 支付管理 - 支付录入、支付记录
- ✅ 图片标注 - Canvas 图片标注工具
- ✅ 数据看板 - ECharts 图表、数据概览、监控页面
特色功能(Mapbox 增强):
- ✅ Mapbox 地图展示 - 登录页 3D 地图背景
- ✅ Mapbox 船只动画 - 船只在地图上移动的动画效果
- ✅ 地图交互 - 缩放、平移、标记点
- ✅ 3D 地形 - Mapbox 3D 地形渲染
- ✅ 实时天气信息 - 登录页显示天气数据
- ✅ 人脸识别登录 - face-api.js 浏览器端识别
- ✅ WebGL Shader 背景 - 多种炫酷特效(音频可视化、蝙蝠群、云层、分形、Menger 海绵、地形、火车)
Mapbox 功能详解:
- 使用 Mapbox GL JS 3.18+ 最新版本
- 支持 2D/3D 地图切换
- 支持自定义地图样式
- 支持 GeoJSON 数据加载
- 支持动画路径(船只移动)
- 支持地图标注和交互
登录页特效(最全版本):
- Mapbox 地图背景 - 3D 地图 + 船只动画
- Shader 音频可视化 - 音频频谱动画
- Shader 蝙蝠群 - 蝙蝠飞行效果
- Shader 云层 - 动态云层渲染
- Shader 分形 - 数学分形图案
- Shader Menger 海绵 - 3D 分形立方体
- Shader 地形 - 程序化地形生成
- Shader 火车 - 火车行驶场景
- 天气信息 - 实时天气显示
平台支持(Tauri):
- ✅ Windows 桌面应用(.exe + NSIS + MSI)
- ✅ macOS 桌面应用(.dmg + .app)
- ✅ Linux 桌面应用(.deb + .AppImage)
- ✅ Web 浏览器版本
- ✅ Android 移动应用(.apk)
- ✅ iOS 移动应用(.ipa)
主要页面:
- 登录页:Mapbox 地图 + 船只动画 + 人脸识别 + 天气信息
- 仪表盘:ECharts 图表 + 数据概览
- 用户管理:用户 CRUD + 权限管理
- 课程管理:课程 CRUD + 日历视图 + 报名统计
- 图片标注:Canvas 标注工具(矩形、多边形、关键点)
- 监控页:实时数据监控
技术特点:
- 完整的 Mapbox 集成(地图渲染、船只动画、3D 地形)
- 丰富的登录页特效(8+ 种 Shader 背景可选)
- ECharts 数据可视化
- 课程日历组件
- 报名统计图表
- WebSocket 实时通信
与其他平台的区别:
- ✅ 最完整的功能集 - 包含所有功能模块
- ✅ Mapbox 地图 - 独有的地图展示和船只动画
- ✅ 最多的登录特效 - 8+ 种 WebGL Shader 背景
- ✅ 课程管理模块 - 课程日历、报名统计
说明: 功能最完整的管理平台版本,威海项目专用。最大的特色是集成了 Mapbox 地图引擎,登录页有酷炫的 3D 地图背景和船只动画效果。包含完整的课程管理、用户管理、数据可视化功能。提供 8 种以上的 WebGL Shader 登录背景特效可选,视觉效果最丰富。
变更摘录维护说明: 原 docs/project_overview.md 中的按日摘录已与本节 showtime_weihai 变更摘录 合并;docs 内同名文件仅保留指向本文件的说明,避免两处重复维护。请勿在仓库根目录再建仅大小写不同的 project_overview.md:在 Windows 默认文件系统上常与 PROJECT_OVERVIEW.md 视为同一文件,整文件写入会覆盖总览。
🏭 仓库管理 · 档案 / 关联物料与可售卖物料池(2026-05-11)
代码路径: weihai/client/showtime_weihai/src/views/base/WarehouseManage.vue、src/api/material.js;应用内「更新说明」:src/data/appChangelog.js(同日组)。
| 范围 | 说明 |
|---|---|
| 页签与接口 | 「关联物料」:可存储(category_listed type=1)与 可售卖(type=2);物料列表仅 POST /api/goods/listed(本页已不再用 /api/goods/search)。中心仓可售卖池更新:POST /api/warehouse/set_goods_pool(type: 2)。分类源:POST /api/category/listed、POST /api/warehouse/category_listed。 |
| 可售卖懒加载 | 树先只建分类骨架;用户点击末级叶子分类后再对该叶子发 一次 goods/listed,避免整树批量请求。物料节点展示为「名称(编码)」(无名称则仅编码)。 |
goods_code_pool |
缺失/null → 内部等价全部可售(*);显式 [] → 池中无物料;['*']/含 * → 全部;非空字符串数组 → 白名单。子仓若无显式分类关联记录,set_goods_pool 易失败;子仓侧保存可售卖池改为按需走 batch_link_category(带池)以落库。 |
| 子仓库 · 关联物料分类 | 嵌入页与主面板均可「管理关联」:可选范围 = 中心仓已关联分类子树;结果仅写子仓 warehouse_code。首次进入管理态未单独配置时默认全选继承范围。refreshWhCategoryAssoc 支持 仅刷 type=1 或 type=2,改可存储不连带重载可售卖。 |
| 中心仓 · 关联物料分类 | 保存后按当前编辑的 type 单列刷新,不联动另一侧。 |
| 档案 UI | 「子仓库」顶栏中心仓一行内展示编码、组织、责任人、状态;子仓详情区与子仓信息行展示风格一致。新建/编辑表单 隐藏「仓库编码」(中心仓与子仓共用表单)。责任人/联系人:user/listed + dept/listed 回补部门;下拉按部门 分组;选项「部门-姓名(工号)」,行内展示「姓名(工号)」。 |
| 清理 | 移除本页 logWhSell/debugWhSell 与拖拽排序调试 console.log,不再向控制台输出上述测试信息。 |
| API 兼容 | getMaterialList 等对列表字段兼容解析;normalizeWhListedCategory、parseCategoryListedTreePayload 等与分类/池字段解析一致。 |
| 全局分类缓存 | src/composables/useCategoryTree.js:登录成功后 init() 预拉、setInterval 约 60s 刷新;fetchIfStale() 在未过期时直接使用内存树;物料分类 add/modify/remove(MaterialCategory.vue、MaterialList.vue)成功后 invalidate() 立即重拉。Token 仍有效但未走登录接口(如刷新后首访)时在 router/index.js beforeEach 有 token 分支内补调 init(),避免只靠登录才预热缓存。仓库侧 关联物料分类浏览剪枝树、中心仓「管理关联」整树数据源、子仓管理态 剪枝所用的全树均复用该缓存,不再各处独立请求 /api/category/listed。 |
| 浏览态树节点 | 「关联物料分类」浏览:el-tree 默认节点展示,仅 分类名称(不再在行内并排展示分类编码);与部门/组织侧一致。 |
| 搜索框(临时) | 仓库档案内关联物料分类 管理关联、whMcFilterText / whSubMcFilterText 的搜索输入在模板中 注释隐藏(可按需恢复)。 |
| 可售卖 · 中心仓管理态 | 移除树下方「当前勾选的可售卖物料池」摘要列表(原含「全部 SKU」「调整」等行),界面只保留勾选树与取消/确认。 |
| 子仓 · 管理关联加载 | startWhSubMcManage 仍 并行请求 warehouse/category_listed(中心仓 + 子仓各一次)以划定可选范围与子仓 baseline;全量分类树来自缓存。等待时间主要取决于上述两条仓储接口,与分类全局缓存是否为热数据无关(仓库维度数据不按全局缓存跳过)。 |
📦 物料分类全局缓存 · 预热与作废(2026-05-11)
代码路径: weihai/client/showtime_weihai/src/composables/useCategoryTree.js、src/store/modules/user.js、src/router/index.js、src/views/material/MaterialCategory.vue、src/views/material/MaterialList.vue。
| 项 | 说明 |
|---|---|
| 语义 | 单例模块级状态:fetchIfStale(TTL ≈60s)、invalidate(CRUD 后强制重拉)、init/destroy(登录/退出)。 |
| 触发点 | 登录成功:user store login() 内 init()。已有 token 进入应用:路由守卫在拉权限后调用 init()(幂等,不重复挂载定时器)。退出:destroy()。 |
| 作废 | 分类新增/修改/删除接口返回成功后在本页作废并刷新缓存,避免他处仍读到旧树。 |
👥 部门管理 · 关联物料分类(同日补充)(2026-05-11)
代码路径: weihai/client/showtime_weihai/src/views/base/DeptManage.vue。
| 项 | 说明 |
|---|---|
| 数据 | 已关联仍为 POST /api/dept/category_listed;全量树走 useCategoryTree,与组织/仓库侧同源缓存。 |
| UI | 浏览与管理 同一套外层布局:标题行、中间 同一 tree-wrap、底部「取消/确认」仅在管理态出现;勾选框仅管理态。搜索框模板已注释隐藏(与组织侧一致)。 |
🏢 组织管理 · 关联物料分类(同日补充)(2026-05-11)
代码路径: weihai/client/showtime_weihai/src/views/base/OrgManage.vue。
| 项 | 说明 |
|---|---|
| 交互 | 右侧不再使用「路径 + 编码」的纵向列表浏览;改为与部门一致的 剪枝浏览树,管理仍为全树勾选 + 确认。搜索框模板已注释隐藏。 |
| 数据 | 去掉仅为列表展示而并行拉取的独立 category/listed 路径映射;剪枝/browse 与管理树均基于全局缓存与会话内 invalidate。 |
📋 物料场景 · 关联末级分类 UI(同日补充)(2026-05-11)
代码路径: weihai/client/showtime_weihai/src/views/material/SceneManage.vue、src/utils/categoryAssociationTree.js(浏览树剪枝与孤儿补齐)。
| 项 | 说明 |
|---|---|
| 布局 | 「关联物料末级分类」从配置区顶部独占横栏移入 sm-fields-area 最左列,与中部「首选 / 公共 / 专有属性」、右侧字段池并排,行为与仓库/部门/组织侧「左树 + 右工作区」一致。 |
| 可调宽分隔条 | 分类左列与中列区间增加 useResizablePanel,持久化键 scene_cat_col_width(默认 220px,约 140~400),与左侧场景列表 scene_sidebar_width 互不干扰;移动端沿用既有规则隐藏 sm-resizer。 |
| 浏览与管理 | 浏览态:剪枝树(pruneWhLinkedCategoryTree + mergeBrowseCategoryOrphans)展示已关联层级;节点可点切换 专有属性 激活分类;行间 × 移除。管理态:页内勾选全树,不再使用分类选择 el-dialog;确认逻辑仍为仅采纳 末端且在场景内的分类,其余与既有 confirmCatSelection / 字段回填一致。 |
| 滚动与顶栏文案 | 分类树在左列 sm-mc-tree-wrap 内纵向滚动;标题行(关联物料末级分类 + 管理关联 / 取消·确认)与 「点击分类…当前选中 / 共 N 个」 提示固定在滚动区之上,滚动列表时仍可看到上下文。 |
📋 多维表衍生常量传播索引 · 配置管理二次刷新与等待提示(2026-05-09)
代码路径: weihai/client/showtime_weihai/src/views/material/mdt/oaConstWritePropagation.js、mdtConstUpstreamDeps.js、mdtUpstreamIndexCache.js、src/views/material/MultiDimensionalTable.vue、src/views/base/ConfigManage.vue;应用内「更新说明」:src/data/appChangelog.js(同日条目)。多维表分项长文 weihai/client/showtime_weihai/docs/多维表格(MDT)_改动纪要.md 与本节互补(历史列类型、oa_config 产品差异等见该文档)。
| 范围 | 文件 | 变更摘要 |
|---|---|---|
| 传播少 view(客户端索引) | oaConstWritePropagation.js、mdtConstUpstreamDeps.js、mdtUpstreamIndexCache.js |
配置保存后仍会合并 oa_config listed 并扫描多维表中 oaConstWrite 漂移;新增按表的 localStorage 索引(键 mdt_const_upstream_idx_v1):由公式链静态推断 configConstantSig 集合,noDeps / scanAll / 补丁 patchSig 规则下 跳过不必要的 excel/dynamic/view;每次实际 view 后对应该表 upsert hint;统计 tablesSkippedByIndex。 |
| MDT 侧写回索引 | MultiDimensionalTable.vue |
换表 loadContent、保存成功 后对本表 upsert 上游 hint,避免只改绑定/公式时索引陈旧。从未在本机打开过的表仍依赖传播首轮 view 建索引。 |
| 配置项管理列表与 UX | ConfigManage.vue |
保存:/api/config/set → 首次 loadCfg → propagateMdtDependentConstants → 传播 finally 收起橘色常驻 ElMessage.warning(duration: 0) → 若有衍生品则绿字 已按多维表公式同步… → 再次 loadCfg,避免首拉早于衍生 /config/set 落库。 |
🔗 常量信息选择过滤 · 删除常量/删列确认(2026-05-10)
代码路径: weihai/client/showtime_weihai/src/views/material/mdt/LinkPicker.vue、mdt/oaConfigListedMerge.js、src/views/material/MultiDimensionalTable.vue、src/views/base/ConfigManage.vue;应用内「更新说明」:src/data/appChangelog.js(同日组)。
| 范围 | 文件 | 变更摘要 |
|---|---|---|
| LinkPicker 不展示表写回项 | LinkPicker.vue、oaConfigListedMerge.js |
表格目标为 oa_config 时,在 fetchOaConfigMergedListed 结果上增加 isOaConfigDescFromMdtWriteback(desc) 过滤;与多维表写回说明 formatOaConstWriteDesc(来自多维表格「…」· 列「…」)一致,避免「表里再选由本表写回的常量」形成闭环。库内导出 OA_CONFIG_DESC_FROM_MDT_SUFFIX_RE、isOaConfigDescFromMdtWriteback。 |
| 删除常量二次确认 | ConfigManage.vue |
非写回占位项删除前:明示页签/物料/多维表可能仍引用、删除后可能失效或异常、系统不自动修改引用;确认按钮「仍要删除」;取消确认单独 try/catch,避免未处理 rejection。 |
| 删除列涉及常量时的说明 | MultiDimensionalTable.vue |
columnDeleteTouchesOaConst:ds:oa_config 列,或数字/公式列且任一行存在 oaConstWrite 绑定 → 删除确认追加对配置常量与页签侧影响的文案,标题可为 「删除列 · 将影响常量关联」。 |
| (行为备忘)常量已删、格内仍存 PK | MultiDimensionalTable.vue resolveDsCell |
合并 listed 中无对应记录时展示串为空字符串;格内仍有旧 PK 时可能仍渲染数据源样式壳,主数字/展示字段区域为空(直至用户重选或清格)。 |
👤 用户管理 · 部门 KPI 详情与控制台清理(2026-05-10)
代码路径: weihai/client/showtime_weihai/src/views/base/StaffManage.vue;应用内「更新说明」:src/data/appChangelog.js(与同日常量/多维条目同一日组)。
| 范围 | 说明 |
|---|---|
| 列表与弹窗 | 「部门」列仅依据 roles:dept_code + role_code 一行对应一条;「查看部门详情」:el-dialog 内 KPI 合计与分段 tooltip、表格编辑每条绑定的级别与 KPI;KPI 列表头文案为 kpi (单位: % );保存时通过 POST /api/user/modify 携带完整 roles(字段含 role_level、kpi_rate);级别仅下限 1、无上限;KPI 合计须 100 仅在点击保存时用 ElMessage 校验。 |
| 右侧详情抽屉 | 底部「部门 · 角色与 KPI」:只读表格 + KPI 汇总条(与弹窗同源 user.roles);只读表中 KPI 列表头同为 kpi (单位: % );移除任职信息与头部的「部门·角色」单行长文案以避免重复。 |
| 控制台 | loadList 不再输出列表调试 console.log/console.table;新增用户的 OpenIM 注册改为静默 void registerImUser(…).catch,不再打印成功/警告。 |
🏢 部门管理 · 角色成员列表(2026-05-10)
代码路径: weihai/client/showtime_weihai/src/views/base/DeptManage.vue;应用内「更新说明」:src/data/appChangelog.js(与同日子项)。
| 范围 | 说明 |
|---|---|
| UI | 第三列成员:圆头像;主行展示姓名 / 手机 / 职业性质 / 工号;级别与 KPI 只读紫色、琥珀胶囊(n级、n%);保留「移除」「关联用户」。 |
| 数据 | 工号与用户主键分列解析(pickMemberEmpCode / pickMemberUserCode),避免二者混用;职业性质等与 userListed 回补合并。 |
| 布局 / 对齐 | 成员行改用统一 grid-template-columns,末两列为相同 minmax(68px, 0.76fr)(替代 auto),使表头与各行「级别」「kpi」列纵向对齐;member-col-act 等样式括号修复已通过构建校验。 |
🏢 组织管理 · 关联物料分类(2026-05-10)
代码路径: weihai/client/showtime_weihai/src/views/base/OrgManage.vue;应用内「更新说明」:src/data/appChangelog.js(同日组)。分项说明 weihai/client/showtime_weihai/docs/组织管理_关联物料分类.md。
| 范围 | 说明 |
|---|---|
| 接口 | 已关联:POST /api/company/category_listed(company_code);维护:POST /api/company/link_category、POST /api/company/unlink_category(company_code + category_code);分类树与路径:POST /api/category/listed。 |
| 交互 | 右侧列表与并行树数据生成层级路径;「管理关联」内勾选树,确认 diff 后批量增删关联;新建组织无 company_code 仅文案,不调接口。 |
| 布局与文案 | 弹窗主体固定高度、宽约 1200px、各栏内部滚动并避免横向条;列表/树加载「加载中」,提交关联遮罩「关联中」;标题行橘色操作提示。 |
📐 页签字段宽度、特征必填显隐、数字模式、公式符号、用户字段精简、财务属性 UI(2026-05-07)
代码路径: src/views/material/MaterialList.vue、src/views/material/MaterialCategory.vue、src/views/material/TabBuilder.vue、src/views/material/TabManage.vue、src/views/base/StaffManage.vue、src/views/base/BillManage.vue
| 范围 | 文件 | 变更摘要 |
|---|---|---|
| 字段网格列宽调整 | MaterialList.vue、MaterialCategory.vue |
每列最小宽度阈值由 190px 改为 240px:1920 屏弹窗从 6 列→5 列(每列约 260px),2560 屏从 8 列→7 列(约 252px);分类编辑页从 7 列→6 列(约 251px)。字符长度占列规则(totalLen > 8 → span2)不受影响。 |
| 特征必填属性显隐规则 | TabBuilder.vue、TabManage.vue、MaterialCategory.vue |
页签特征属性中每个「必填」属性新增独立显隐规则设置,可为必填开关本身配置触发条件,支持条件必填场景。 |
| 数字特征输入/范围模式 | TabBuilder.vue、TabManage.vue |
数字类特征字段新增「输入」与「范围」两种模式选项;MaterialList.vue、MaterialCategory.vue 数字使用侧同步添加模式切换控件,可在单值输入与区间范围之间切换。 |
| 计算公式符号补全 | TabBuilder.vue、TabManage.vue |
公式编辑器新增「/」(除法)和「,」(逗号,多参数函数分隔)两个运算符按钮。 |
| 创建用户字段精简 | StaffManage.vue |
新建用户弹窗移除若干冗余字段,表单仅保留必要信息,减少填写负担。 |
| 财务属性 UI 调整 | BillManage.vue |
财务属性页面布局与视觉细节优化。 |
🏢 组织分类票据、员工管理扩展、IM 消息修复、物料分类优化(2026-05-06)
代码路径: src/views/base/OrgManage.vue、src/views/base/BillManage.vue、src/views/base/StaffManage.vue、src/views/material/MaterialCategory.vue、src/store/modules/im.js、src/router/index.js
| 范围 | 文件 | 变更摘要 |
|---|---|---|
| 组织分类关联票据 | OrgManage.vue |
新建/编辑组织分类弹窗新增「关联票据」区块,可分别为「可收票据类型」和「可开票据类型」添加/删除票据(多选子弹窗,从 /api/bill/listed 加载);票据以 { code, name, tax } 结构存入 field.in_bill / field.out_bill,随 classify/modify 一并提交;列表名称后展示税率百分比。 |
| 组织继承分类票据 | OrgManage.vue |
新增/编辑组织弹窗统一显示「分类票据(可勾选关联)」区块;新增时默认全不勾选,编辑时已关联票据预勾选;点击行切换勾选;保存时新勾选项调 link_bill、取消勾选项调 unlink_bill;分类删除票据后服务端级联处理组织侧同步(前端不额外处理)。 |
| 组织编辑旧票据区块移除 | OrgManage.vue |
删除编辑组织弹窗中原有的「关联票据」手动新增区块,统一使用继承勾选方式。 |
| 字典显示优化 | OrgManage.vue |
组织分类表格「组织类型」「组织性质」列改为显示字典 label;loadDictItems 支持按 label 查找;companyTypeLabel / orgNatureLabel 兼容 value/label 双向查找;印花税率 单位从 ‰ 改为 %。 |
| 财务属性出项票据改名 | BillManage.vue |
统计标签、列标题、空状态文字、弹窗标题、单选项中「出项票据」全部改为「销项票据」。 |
| 费用项目改名辅助核算 | router/index.js、ExpenseItemManage.vue |
路由标题及页面内所有「费用项目」文字改为「辅助核算」。 |
| 物料分类不参与场景提示修正 | MaterialCategory.vue |
「不参与场景」checkbox 的 tooltip 从错误的「勾选后物料列表不允许在此分类下新增物料」改为「勾选后该分类不出现在物料场景的选择列表里」。 |
| 物流费率组重名校验 | MaterialCategory.vue |
勾选「是否是物流费率组」前先调 /api/category/check_flow_group_duplicate;has_duplicate=true 时提示不可勾选并保持未勾选状态;取消勾选直接放行;树节点标签从「费率组」改为「物流费率组」。 |
| 员工管理学历/职业性质 | StaffManage.vue |
用户信息接口新增 education(学历)和 job_nature(职业性质)顶级字段,移出原 expand.education;学历为下拉选择(初中/高中/大专/大本/研究生),职业性质为4选1(正式员工/劳务派遣员工/临时员工/返聘员工);列表加「学历」「职业性质」两列,删除「职务」列;编辑表单学历和职业性质上移至主信息区;hover 卡片新增学历、职业性质、备注;详情抽屉基本信息加职业性质,删除任职信息里的「职务」行。 |
| IM 消息预览修复 | im.js |
_doSendTextHttp 发送后 latestMsg 改为带 contentType:101 的完整格式,previewLatestMsg 能正确显示文本而非「[语音]」;_upsertConversations 加时间戳比较,防止旧数据覆盖本地更新预览。 |
| IM 空消息重试 | im.js |
openChat 读出消息列表为空时,2.5s 后自动重试 loadMessages(),解决 HTTP 发送后 SDK 本地库尚未同步导致点进去无消息的问题。 |
🔧 IM 免登录重连、已读 SDK 与会计科目页修复(2026-05-06 下午)
代码路径: src/store/modules/user.js、src/router/index.js、src/store/modules/im.js、src/views/base/AccSubjectManage.vue
| 范围 | 文件 | 变更摘要 |
|---|---|---|
| F5 / 带 token 直连仍初始化 IM | user.js、router/index.js |
新增 ensureOpenIMConnected(opts?):有 userCode 时从 store / localStorage 对齐 imToken,缺失则 loginToOpenIM,再 imStore.initIM;imStore.ready 为真则直接返回。login() 内原「静默取 imToken + initIM」改为复用该方法。路由守卫在持有 token 的分支中调用 kickOpenIM()(访问 /login、根路径重定向、进入管理后台及无权限页前),避免仅账号密码登录路径才连 OpenIM、刷新后进系统 IM 不上的问题。 |
| 会话已读 API 参数(3.8.x) | im.js |
markRead 调用 markConversationMessageAsRead(conversationID),首参为会话 ID 字符串;勿再传 { conversationID } 对象,否则服务端不落已读、F5 后未读会回来。 |
| 重复标记已读的无害告警 | im.js |
SDK 在「已读到最新」后再次 mark 会抛出 hasReadSeq equal max(如 openChat 与展开 IM 面板各调一次 markRead);该文案匹配时不再 console.warn,其它错误仍警告,本地未读清零逻辑不变。 |
| 会计科目树形表格编译错误 | AccSubjectManage.vue |
树形视图 <el-table> 在 default-expand-all 后补闭合 >;此前缺 > 导致整页模板解析失败(与科目名称列 fontWeight 样式无关)。 |
🏢 组织管理重构 & KR 协作步骤 & 财务属性入口 & IM 日志清理(2026-05-04 ~ 2026-05-05)
代码路径: src/views/base/OrgManage.vue、src/views/base/BillManage.vue、src/views/okr/OkrObjective.vue、src/views/okr/OkrKeyResult.vue、src/views/okr/OkrReview.vue、src/store/modules/okr.js、src/data/okrMock.js、src/store/modules/im.js、src/utils/socket.js、src/components/ImPanel.vue、src/views/material/TabManage.vue、src/views/material/TabBuilder.vue、src/utils/tab.js、src/store/modules/layout.js、src/layouts/index.vue、src/utils/menu.js、src/router/index.js
| 范围 | 文件 | 变更摘要 |
|---|---|---|
| 组织分类 CRUD | OrgManage.vue |
原「组织树」整体注释保留至 OrgManage.legacy.vue;新建组织分类列表(el-table,无边框/斑马纹),支持新建/编辑/删除分类;org_type / org_nature 改为固定选项下拉;描述字段改为名称后的 ? 气泡 tooltip;税务属性(销项税率/附加税率/所得税率/印花税率)在同一行展示。 |
| 组织内联展开 | OrgManage.vue |
el-table 展开列点击后加载该分类下的组织列表(/api/company/listed 传 classify_code);展开区背景区分(#f4f5f9 + 阴影);新增/编辑/删除组织;表单含名称、类型、统一社会信用代码、法人、联系人等字段。 |
| 关联票据(组织) | OrgManage.vue |
编辑组织时可关联进项/出项票据;票据分两栏展示(进项蓝/出项橙);支持新增关联(搜索下拉弹窗)、切换启用状态、移除关联。 |
| 展开防重复加载 | OrgManage.vue |
onExpandChange 加 if (orgMap[code] !== undefined) return 防二次展开重复请求;抽出 loadOrgs(code) 复用;展开工具栏加刷新按钮;加载遮罩显示「加载中…」文字。 |
| 财务属性入口 | BillManage.vue、router/index.js |
路由 /base/bill 标题改为「财务属性」,图标改为 mdi:finance;进入页面先展示卡片列表(仅一张「各类型成本/费用额定增值税率」),点击卡片进入原票据管理详情;卡片样式与「页签管理」阴影一致;面包屑支持返回。 |
| KR 协作步骤 | OkrObjective.vue、OkrKeyResult.vue、okrMock.js |
KR 数据结构增加 steps[](id/title/description/collaborator_code/collaborator_name/status/due_date);OkrObjective.vue KR 抽屉新增「协作步骤」区块(步骤卡片、添加/编辑/删除步骤、状态切换、协作人指派);KR 行显示步骤进度徽章;OkrKeyResult.vue 新增「我参与的」视图 tab,筛选本人被指派的步骤并卡片展示;mock CRUD:mockAddStep / mockModifyStep / mockRemoveStep / mockGetMySteps。 |
| 步骤编辑 Bug 修复 | OkrObjective.vue、okrMock.js |
编辑步骤时若 collaborator_code 不在用户列表则清空,防止显示原始工号;mockGetUsers 成功后同步更新已有步骤的 collaborator_code / collaborator_name。 |
| TabManage/TabBuilder 防崩溃 | TabManage.vue、TabBuilder.vue、MaterialCategory.vue |
tmConflictLabels / tabBuilderConflictLabels 计算属性加 try/catch 兜底返回 new Set();模板中 .has() 调用加可选链 ?.,防止 undefined 导致页面白屏。 |
| IM 聊天按钮尺寸 | ImPanel.vue |
.im-tab 高度从 80px 缩小到 52px,宽度从 40px 缩小到 36px,视觉更紧凑。 |
| IM 调试日志注释 | im.js、socket.js、ImPanel.vue |
注释所有 [IM] / [Socket] / [preview] 前缀的 console.log,保留 console.warn / console.error 级别的错误提示,控制台不再产生大量正常流程日志。 |
| 页签字段重名可见提示 | TabManage.vue、TabBuilder.vue |
重名字段卡片在红框基础上增加「⚠ 重名」徽标与 el-tooltip 说明(模板内重复 / 跨页签冲突 / 保存校验闪红);明细/表格 footer 同步展示;模板中 tmInternalDupLabels、internalDupLabels 去掉误用的 .value,与 Vue 自动解包一致。 |
| KR 进度展示一致 | OkrObjective.vue、OkrKeyResult.vue |
列表行 el-progress 改读 kr.progress(与 calcKrProgress 一致),修复「目标值小于起始值」类 KR 误用 current_value ÷ target_value 显示成 100% 的问题;KR 详情 saveDrawer 保存前回写 progress。 |
| OKR 数据持久与重映射 | okr.js、OkrReview.vue(消费 store) |
复盘 reviews 在 owner 重映射时与关联 objective 对齐;_remapOwnersIfNeeded 改为逐字段仅替换「仍不在真实用户表中的 code」,避免 F5 后用户已改的负责人被全表覆盖;STORE_VER 升级可清陈旧 localStorage(若当轮有调整)。 |
| 顶部多页签可关规则 | router/index.js、layouts/index.vue、menu.js、layout.js、utils/tab.js |
仅路由名 Index(首页)closable: false,其余路由均可关闭;router.afterEach 与侧栏 addTab 统一用 isTabClosableRouteName;路由表移除分散的 meta.noClosable;addTab 在页签已存在时若 closable 变化则更新;closeTab 对首页二次拦截。 |
📋 OKR 列表排版修复 & 下拉选中显示修复 & 复盘负责人修复(2026-05-04)
代码路径: src/views/okr/OkrKeyResult.vue、src/views/okr/OkrObjective.vue、src/views/okr/OkrReview.vue、src/data/okrMock.js、src/assets/styles/variables.css
| 范围 | 文件 | 变更摘要 |
|---|---|---|
| KR 列表列宽重调 | OkrKeyResult.vue |
周期列 44px、部门列 52px(text-align: center),负责人 88px;进度列加宽至 162px、截止日期加宽至 118px;col-title / col-obj 保持 flex:1 / flex:0.6 弹性,右侧固定列不再拥挤。 |
| 全局下拉选中空白修复 | variables.css |
全局 .el-select--small .el-select__selection 的 overflow: hidden 改为仅对含 .el-tag(多选)时生效;单选框选中文字不再被 overflow 裁切(Element Plus 单选占位层带 z-index:-1,外层 overflow 会令其不可见)。 |
| 下拉选项 label 字段兼容 | OkrKeyResult.vue、OkrObjective.vue |
部门下拉 label 改为 d.full_name || d.dept_name;负责人下拉 label 改为 u.realname || u.username || u.user_code;联动的 onDrawerDeptChange / onAddObjDeptChange 同步兼容。 |
| mockGetDepts 归一化 | okrMock.js |
先展开 ...d 再覆盖 dept_code / dept_name / full_name,避免 ...d 把已设置的键冲回接口原始值;dept_code 统一转字符串。 |
| mockGetUsers 归一化 | okrMock.js |
用户列表 normalize:user_code 统一 String(),realname 补 username 兜底;MOCK_OBJECTIVES 分配负责人改用 normalized 列表;MOCK_REVIEWS 在用户同步后按 objective_id 匹配更新 owner_code / owner_name / dept_name。 |
| OkrReview 负责人显示 | OkrReview.vue |
getUserInfo(userCode, fallbackName) 增加第二参数:找不到用户时直接展示 owner_name(复盘记录原始字段),不再显示 USER_HPP 等 mock 工号;新建复盘写入的 dept_name 改用 deptInfo?.full_name || deptInfo?.dept_name。 |
📋 IM 群组邀请审批流 & OKR 接入真实数据 & OKR 排版优化(2026-05-03)
代码路径: src/store/modules/im.js、src/components/ImPanel.vue、src/data/okrMock.js、src/views/okr/OkrObjective.vue、src/views/okr/OkrKeyResult.vue
| 范围 | 文件 | 变更摘要 |
|---|---|---|
| IM 群组邀请改为需审批 | im.js |
inviteToGroup 不再直接调 /group/invite_user_to_group,改为向每位被邀请人发送 [GROUP_INVITE]:JSON 格式的 1v1 消息(含 groupID / groupName / inviterName 等信息)。新增 acceptGroupInvite(msg):解析邀请消息、调 Admin API 真正入群、发回同意通知;新增 rejectGroupInvite(msg):发回拒绝通知。 |
| IM 邀请卡片 UI | ImPanel.vue |
识别 [GROUP_INVITE]: 消息并渲染交互卡片(群名、邀请人、同意/拒绝按钮);inviteStatusMap ref 追踪已处理状态,防止重复操作;isGroupInviteMsg / parseGroupInvite / getInviteStatus 等辅助函数。 |
| IM 查看群成员 | ImPanel.vue、im.js |
群聊头部新增成员按钮;点击调 imStore.getGroupMembers(groupID) 弹窗展示;修复 getGroupMembers 返回 res?.members || res?.groupMembers 的取值层级错误(_adminPost 已解包 .data)。 |
| OKR 接入真实 API | okrMock.js |
mockGetUsers 调 /api/user/listed,成功后覆盖 MOCK_USERS 并按轮询方式将真实用户分配给 MOCK_OBJECTIVES 及其 key_results 的 owner_code / owner_name。mockGetDepts 调 /api/company/listed + /api/dept/listed,返回字段统一为 dept_code / dept_name / full_name;均有 mock 数据降级兜底。 |
| OKR 进度计算与单位 | OkrObjective.vue、OkrKeyResult.vue |
进度改为 (current_value / target_value) * 100,el-progress 百分比、主文字均以此为准;次要行显示 current_value / target_value(含 unit)。Mock 数据单位 's' → '秒','h' → '小时'。 |
| OKR 目标列表排版 | OkrObjective.vue |
col-summary 改为 flex 横排 + mask-image 右侧渐变遮罩展示 KR 概要 chips(不截字符而是自然溢出渐隐);progress-wrap gap 收至 4px,progress-text min-width 收至 36px;col-summary padding-left 加大至 20px 与进度列拉开间距;col-owner 100px,col-progress 148px。 |
| OKR KR 列表排版 | OkrKeyResult.vue |
各列 flex / px 比例全面重算:col-badge 44px,col-title flex:1,col-obj flex:0.6,col-period 46px,col-dept 52px,col-owner 88px,col-progress 162px,col-date 118px,col-ops 68px。 |
📋 物料页签 UI、控件库排版与分类字段抽屉(2026-04-06)
代码路径: weihai/client/showtime_weihai/src/views/material/(及同阶段若已合并的 src/layouts/、src/store/modules/layout.js)
| 范围 | 文件 | 变更摘要 |
|---|---|---|
| table 控件展示名 | TabBuilder.vue、TabManage.vue |
界面与映射中文案 「明细表格」→「明细/表格」(控件库、字段类型名、预览与画布提示、校验与注释等)。 |
| 控件库两列排版 | TabBuilder.vue、TabManage.vue |
左侧控件栏 256px(原 210px);.tb-wi-label / .tm-wi-label 使用 white-space: nowrap,避免「明细/表格」在两列网格内折行。 |
| 注释统一 | MaterialList.vue、MaterialCategory.vue |
注释 / 样式注释中「明细表格」→「明细/表格」,与界面一致。 |
| 切换分类仍显示旧「编辑字段」 | MaterialCategory.vue |
新增 resetCatFieldEditorUI():cancelBatchCatOptRules、清空 catFieldDrawer、关闭 custFieldRuleDlgVisible / catOptRuleDlgVisible、重置 catOptBatchSelected;在 openForm 开头与 watch(formVisible)(面板关闭)调用。根因:openForm 深拷贝替换 form.tabs 后抽屉仍引用旧字段对象。 |
| keep-alive / 脏页(同阶段) | layouts/index.vue、store/modules/layout.js、MaterialCategory.vue、TabManage.vue、MaterialList.vue、SceneManage.vue |
多页签缓存、Tab 未保存指示、setPageDirty / onBeforeRouteLeave、defineOptions 等以仓库实际提交为准。 |
📋 物料列表选配编辑器(2026-04-09 ~ 2026-04-10)
代码路径: weihai/client/showtime_weihai/src/views/material/MaterialList.vue
| 范围 | 变更摘要 |
|---|---|
| 文案与布局 | 左栏「控件库」→「选项」;选配编辑区中间/右侧「控件」→「选项」。添加选项组入口移至左侧面板(含说明与类型下拉);画布底部独立按钮已移除。 |
| Sortable 与卡顿 | 左栏克隆到画布、画布内排序;避免 watch(components.length) 与拖拽 splice 冲突导致 Sortable 被提前销毁;关闭弹窗与 nextTick 后重绑实例。 |
| 新建物料 | 无分类时不显示底部「添加选配 / 移除选配」整块区域。 |
| 业务逻辑 | 选配选项以 关联物料(materials[]、goods_code)为主,物料多选弹窗;添加选项后立即可「添加物料」;控件行仅「添加选项」,不再在行内「添加物料」直建物料选项。 |
| 右侧面板 | 选项组型选项:右侧只编辑名称与说明,去掉「关联物料」区块(物料仅在中间卡片维护)。 |
| 查看态 | 选中选项后展示关联物料;每个物料名单独 带边框小块 展示,无前缀文案;物料名称输入框约 15 字宽(220px)。 |
| 数据 | fields.__selection__ 结构不变;options[].materials[] 存 label 等,展示用 label / name / goods_code 回退。 |
📋 多维表格、列类型(字典 / 数据源 / 直接查表)与物料侧栏(2026-04-14 ~ 2026-04-15)
代码路径: weihai/client/showtime_weihai/src/views/material/MultiDimensionalTable.vue、src/views/material/mdt/ColConfigPanel.vue、LinkPicker.vue、FormulaEditor.vue、src/composables/useFormulaEngine.js;物料分类/列表与权限:MaterialCategory.vue、MaterialList.vue、Pinia 与路由权限(以仓库实际提交为准)。
| 范围 | 变更摘要 |
|---|---|
| 动态表 CRUD | POST /api/excel/dynamic/listed、view、add、modify、remove;数据源清单:/api/excel/dynamic/data_source/listed。 |
| 列类型 | 基础字段;关联多维表(仅动态表,手动选记录);数据源选择(系统数据源,dsTarget + dsDisplayField);查找引用(经关联列 或 直接查表 + lookupConditions + lookupAggregate);计算公式;单选 / 多选(自定义 options + 色板,或 optionSource: dict + dictKey,字典加载与 TabManage 同源 /api/dict/listed;单元格存字典子项 key)。 |
| 列配置与层级 | ColConfigPanel:freeze 避免遮挡公式编辑器;面板 贴屏幕右缘;字典分组选择器与预览条数。 |
| 表体与列操作 | 内联编辑;行右键插入/删除与删除确认;列头菜单(改列、复制列、隐藏列、按列筛选);工具栏 筛选、N 列已隐藏;列宽默认值与最小宽度调整。 |
| 保存与体验 | 自动保存防抖、isDirty、手动保存按钮态;底部 自动保存 Toast;切换路由/换表未保存提示。 |
| 公式 | evaluateFormula、buildResolvedCells(关联、lookup、单选/多选解析为展示值供公式引用);列定义与行数据随表内容持久化。 |
| 物料与权限(同阶段) | 仓库等页签字段与其它 tab 展示对齐;仓库选择条 横向滚动;子仓随父仓权限展示;页面级 查看 / 增 / 改 / 删 与 leader_level=99 超级管理员绕过;scope 空与全量语义。 |
多维表 content 约定(节选): columns[] 含 id、name、type、width、hidden、options、optionSource、dictKey、lookupMode、directTable、lookupConditions、lookupAggregate、dsTarget、dsDisplayField、formula 等;rows[].cells 为 colId → 存值。字典列与页签字段一致存 编码;当前页字典在进页预拉,后台字典变更后建议 整页刷新 以刷新缓存。
📋 特征分类、配置项管理、页签字段 key 加载优化(2026-04-27 ~ 2026-04-28)
代码路径: src/views/material/FeatureManage.vue、src/views/base/ConfigManage.vue、src/views/material/TabManage.vue、TabBuilder.vue、MaterialList.vue、AssemblyManage.vue、src/router/index.js、src/data/appChangelog.js
| 范围 | 文件 | 变更摘要 |
|---|---|---|
| 特征分类左侧树 | FeatureManage.vue |
新增可折叠/拖拽调宽的左侧分类侧栏(复用 useResizablePanel);分类树支持两级展开(expandedCodes reactive 对象驱动展开状态),支持新建/编辑/删除根分类与子分类(`/field/classify/add |
| 特征归类操作 | FeatureManage.vue |
卡片新增 checkbox 多选 + 底部「归类」按钮 + 右上角「···→移动到…」三种归类入口;批量选中时顶部显示「批量归类 (N)」按钮;归类弹窗展示完整分类树(含未分类);归类成功后刷新列表(/field/modify 传 classify_code,/field/listed 传 classify_code 过滤)。 |
| 未分类过滤 | FeatureManage.vue |
默认 activeClsCode = 'unknown',查询时发 {} 由服务端返回未归类数据;点击具体分类发 { classify_code: code };去掉"全部"入口。 |
| 配置项管理页 | ConfigManage.vue(新建) |
SceneManage 风格布局:根分类横向 tabs + 子分类左侧可调宽树 + 右侧卡片网格;卡片展示 key(橙色加粗)、value(蓝色背景等宽字体加粗)、描述(截断+tooltip);支持搜索、新增/编辑/删除配置项和分类(/config/classify/*、/config/*)。路由 /config/manage 加入左侧导航,图标 mdi:cog-outline。 |
| 页签字段 key 异步加载 | TabManage.vue、TabBuilder.vue |
打开字段抽屉时立即显示「加载中…」,异步调 /field/view(传 field_code)回填 key;去掉 openFieldDrawer/closeFieldDrawer 中的 key 校验,仅在点确认时验证;回填完成后重新 snapshot 防止误触"有未保存改动";isFormDirty 比较时剥离 key 字段。 |
| 选配管理增强 | AssemblyManage.vue、MaterialList.vue |
组件库去除"多行文本";数字字段新增默认值输入;下拉/单选/多选字段支持默认值勾选(isDefaultOpt/toggleDefaultOpt);文本/计算公式字段新增显隐规则(textLinks/numLinks);移除全部字段的"必填"开关;视图模式重排版(独立 block + 多列网格 + 强制单行选项);viewValues 通过 watch 持久化到 modelValue.values;选配数据存入 fields.__asm_manage__。 |
| 组件拖拽修复 | AssemblyManage.vue |
修复 SortableJS onEnd 误删 evt.clone(原始组件项)导致组件库拖入后消失的问题,组件库支持多次复用。 |
📋 明细表格列交互修正与提示优化(2026-04-24)
代码路径: weihai/client/showtime_weihai/src/views/material/TabBuilder.vue、TabManage.vue
| 范围 | 文件 | 变更摘要 |
|---|---|---|
| 明细列区禁止外部拖入 | TabBuilder.vue、TabManage.vue |
将列区 Sortable 的 group.put 从 ['tb-fields', 'tb-table-cols'] 改为 ['tb-table-cols'],从根本上阻止左侧控件列表拖入;删除之前通过 onMove 返回 false 拦截的错误实现及处理"拖入自动建列"的 onAdd handler。已有列之间的拖拽排序不受影响。 |
| 列只能点击添加 | TabBuilder.vue、TabManage.vue |
移除所有"从左侧拖入创建列"的代码路径,统一通过右下角「+ 添加列」下拉菜单选择类型添加。 |
| 列区提示文字优化 | TabBuilder.vue、TabManage.vue |
画布列区提示:颜色改为醒目橘色(#f07c13),字重加粗;末尾追加灰色小字「· 列只能点击添加,不支持从左侧拖入」,始终常驻(有列/无列均显示)。右侧属性抽屉中列列表空状态文字由「从左侧拖入…」改为「点击右下角 + 添加列 来添加」,与实际交互一致。 |
效果说明:
- 用户从左侧控件列表往明细表格列区拖拽时,Sortable.js 自动拒绝(光标显示禁止符),无需任何额外代码。
- 明细列区的橘色提示始终可见,用户可随时了解添加方式和操作限制。
📋 物料分类交互优化与页签字段重名实时检测(2026-04-23)
代码路径: weihai/client/showtime_weihai/src/views/material/MaterialCategory.vue、TabBuilder.vue、TabManage.vue
| 范围 | 文件 | 变更摘要 |
|---|---|---|
| 确认按钮视觉状态 | MaterialCategory.vue |
右下角「确认」按钮根据 isFormDirty 动态切换颜色:无改动时灰色(#c0c4cc),有改动时蓝色(#409eff);灰色状态下仍可点击,纯视觉提示。通过 .btn-undirty CSS class 实现,不影响 disabled 状态。 |
| 脏检测范围扩展 | MaterialCategory.vue |
formInitialSnapshot 与 isFormDirty computed 新增 desc(描述)、warehouse(仓库管理)、featureRelList(特征关联)字段参与快照比对,原来只检测 name + tabs + fieldMap + rules,现全覆盖。 |
| 路由离开不拦截 | MaterialCategory.vue |
移除 onBeforeRouteLeave 中的弹窗拦截逻辑(原来离开路由会弹"有未保存改动"确认框)。原因:页面已 keep-alive 缓存,路由跳走再回来状态完全保留,数据不会丢失,弹框无实际意义。改为直接 next(),仅保留 Tab 脏状态圆点指示。 |
| 页签构建器字段重名实时检测 | TabBuilder.vue、MaterialCategory.vue |
TabBuilder 新增 conflictLabels prop(Set<string>);三处字段卡片(顶层分组、子组普通字段、子组 table 字段)绑定 tb-fc-conflict class;CSS 添加红色边框 + 淡红色 glow(border-color: #f56c6c; box-shadow: 0 0 0 2px rgba(245,108,108,.2))。MaterialCategory.vue 新增 tabBuilderConflictLabels computed,实时收集除当前编辑页签外所有页签(含继承、引入模板)的字段 label,传给 <TabBuilder :conflict-labels="...">。 |
| 页签模板管理字段重名实时检测 | TabManage.vue |
新增 tmConflictLabels computed:遍历 form.value.groups 及各组 subgroups,统计出现超过一次的 label 集合;三处字段卡片绑定 tm-fc-conflict class,CSS 同 TabBuilder。检测范围为模板内部重名(独立模板不做跨模板检测)。 |
效果说明:
- 用户在页签构建器中修改字段名称时,若与其他页签字段重名,卡片立即出现红色边框,无需等到点保存才提示。
TabManage中同一模板内有重名字段时同样实时标红,帮助工作人员快速定位问题字段。
📋 WebSocket 实时连接、选配关联展示优化与仓库管理精简(2026-04-22)
代码路径: src/utils/socket.js(新增)、src/store/modules/user.js、src/router/permissions.js、src/views/login/Login.vue、src/views/material/MaterialList.vue、MaterialCategory.vue
| 范围 | 文件 | 变更摘要 |
|---|---|---|
| WebSocket 服务 | src/utils/socket.js(新增) |
单例 WebSocket 客户端,连接地址 ws://117.133.111.165:8708/?token=&passport_id=;每秒发送 ArrayBuffer 心跳(code=1501001);断线后 3 秒自动重连(无限次);主动调用 disconnect() 时不重连。导出 connect(token, userId)、disconnect()、getSocket()。 |
| 登录后自动连接 | src/store/modules/user.js |
login() 成功后调用 socketConnect(token, userId);logout() 调用 socketDisconnect() 并清除 localStorage 中的 userId;userId 字段从响应 user_id 读取并持久化到 localStorage。 |
| 页面刷新后恢复连接 | src/router/permissions.js、Login.vue |
router.beforeEach 有 token 放行时,若 socket 未连接则补充建立;Login.vue onMounted 检测到已有 token 时同样调用 socketConnect,覆盖「已登录直接进入」场景。 |
| 选配追加选项分组展示 | MaterialList.vue |
新增 getAssemblyOptionGroups(cat, field) helper,将 assembly_goods 来源的追加选项按来源物料分组;下拉单选/多选框中每组单独一行渲染(mf-asm-group-row),行末以方形橘色标签显示来源物料名(mf-asm-group-source);追加选项统一设为 disabled,仅可查看不可选择。 |
| 多选字段自动双列 | MaterialList.vue |
mfFieldSpan(field) 函数:对 multiselect 类型取所有选项中最长两项字数之和,超过 10 字则返回 mf-field-span2(占双列),避免选中标签被截断。 |
| 特征关联字段提示 | MaterialCategory.vue |
「特征关联」配置区 featureKey(英文)上方添加 <span class="fr-field-hint">被关联特征</span>,featureLabel(中文)上方添加 <span class="fr-field-hint">关联选项名称</span>;中文标签文字改为橘色(#e6a23c),便于工作人员区分字段用途。 |
| 仓库管理只显示父仓库 | MaterialCategory.vue、MaterialList.vue |
permittedWarehouses / mlPermittedWarehouses computed 末尾追加 .filter(w => !w.parent_code),过滤掉所有子仓库,仓库管理页签只呈现顶层父仓库。 |
📋 物料特征信息交互与分类字段序列化修复(2026-04-20)
代码路径: weihai/client/showtime_weihai/src/views/material/MaterialList.vue、MaterialCategory.vue
| 范围 | 变更摘要 |
|---|---|
| 特征信息 tab — radio/checkbox 选项视觉区分 | 原有选项(分类自定义)显示为实心圆(未选灰色实心,选中蓝色实心);来自 assembly_goods 追加的选项显示为空心圆(未选灰色圆边,选中蓝色圆边+圆心蓝点)。两类选项均为圆形,通过有无填充即可区分来源。 |
| 特征信息 tab — 布局 | 原有选项横排(flex-wrap: wrap,多个并排一行);追加选项各占独立一行(flex-basis: 100%);两组之间用 mf-opts-break 留出间距,不显示分割线。 |
| MaterialCategory — radio 选项序列化修复 | cleanField 函数序列化字段时,options 数组原本仅针对 select / multiselect / checkbox 类型保存,radio 被遗漏。导致父类保存的单选框字段在子类继承后 getImportOptions 返回空数组、选项显示空白。修复:将 radio 加入序列化类型列表,确保 options: ['选项A', '选项B', ...] 随字段一起持久化到服务端。 |
| MaterialCategory — radio 抽屉初始化修复 | openCatFieldDrawer 原本仅对 select / multiselect / checkbox 从 optionsRaw 初始化 options 数组,radio 字段打开抽屉时 options 为 undefined,编辑列表显示「暂无选项」。修复:同样对 radio 执行 parseImportOptions(field.optionsRaw) → field.options 的初始化,已有数据可正常回显与编辑。 |
关键逻辑说明:
isAssemblyOption(cat, field, optLabel)判断某选项是否来自assembly_goods,返回true时模板附加is-assemblyclass,触发空心圆样式。getFieldOptionsForCat(cat, field)合并原有选项与追加选项并去重(同名只保留原有的),保证追加选项排在末尾。cleanField中options字段现统一格式:['选项1', '选项2', ...](纯字符串数组),radio与select/multiselect/checkbox一致。
📋 物料场景管理重设计(2026-04-18 ~ 2026-04-19)
代码路径: weihai/client/showtime_weihai/src/views/material/SceneManage.vue、src/utils/uploadAsset.js、src/config/assets.js
| 范围 | 变更摘要 |
|---|---|
| 左侧树只展示 L2 | sceneTreeData computed 将 L3 子节点存入 _l3Children,树中设 children: [],隐藏 L3;L3 改由右侧卡片墙展示。 |
| L2 卡片墙视图 | 点击 L2 节点触发 loadL2Scenes,调用 /api/scene/classify/listed 拉取该 L2 下所有 L3 场景并填充 l2Scenes。右侧渲染 sm-card-view 网格;卡片为 2:1 信用卡比例(aspect-ratio: 2/1),圆角(8px)、四周阴影(0 4px 16px rgba(0,0,0,.18),hover 加深)。封面图铺满卡片背景(background-size: cover),场景名叠加在左上角白字;无封面时卡片显示浅灰底色 + 深色文字。 |
| 场景封面上传(新建) | 新建 L3 场景弹窗增加「封面图」上传字段(隐藏 file input + 预览缩略图)。上传调用 uploadAsset(POST /api/assets/upload),将完整响应对象({ url, name, assets_code, type, size })存入 sceneForm.cover。submitSceneForm 在 classify/add 之后额外调用 /api/scene/add 写入 cover,避免 cover 被直接作为字符串传给后端(后端要求 JSON 对象)。 |
| 场景封面上传(详情) | 详情页顶部操作行加「更换封面」el-button(含小缩略图);右侧栏在「非公共属性」上方加独立 sm-pool-cover 卡片预览区(2:1 比例,hover 显示「更换」遮罩)。上传同样存完整对象至 activeCover;saveConfig 携带 cover 字段写入 /api/scene/modify。coverUrl(cover) 工具函数统一从对象 cover.url 或字符串 cover 中提取 URL 并调用 getAssetUrl。 |
| 分类字段详情懒加载 | 根本原因:/api/category/listed 不返回 fields(包含 _tabs、fieldMap),导致 fieldPool 始终为空,四个属性区域全空白。修复:新增 ensureCatDetail(code) — 调用 POST /api/category/view 拉取单个分类完整 fields 并回填树节点的 tabs / fieldMap;catDetailCache(Set)避免重复请求。新增 ensureCatAndAncestors(code) — 同时加载目标节点及路径上所有祖先(inherited-extended 页签合并需要)。confirmCatSelection 改为 async,确认前先 await Promise.allSettled(...ensureCatAndAncestors);selectScene 中加载已保存 _categories 后同样先 await 再构建 selectedCategories。此模式与 MaterialList.vue 的 mlEnsureNodeDetail + /api/category/view 机制一致。 |
| 分类/组织 tag 区域限高滚动 | sm-cat-row-scroll:max-height: 82px(约 2.5 行)、overflow-y: auto;自定义 4px 细滚动条(默认 #dcdfe6,hover #c0c4cc)。「添加分类」/「添加组织」按钮移至 sm-section-hd 标题行右侧(sm-section-actions),始终可见不随 tag 滚动。各区块加 border: 1px solid #ebeef5 + border-radius: 8px;描述文字超长时 text-overflow: ellipsis;空状态显示浅灰占位文字。 |
| tooltip 条件修正 | 分类树节点「该分支下暂无末端分类」tooltip 的显示条件由 v-else 改为 v-else-if="!data.isFinal && !data.hasFinalDescendant",避免对合法末端节点误显示警告。 |
关键数据结构说明:
sceneForm.cover/activeCover:存储/api/assets/upload返回的完整对象{ url, name, assets_code, type, size },后端要求 JSON 类型,不能存裸 URL 字符串。coverUrl(cover):兼容旧字符串格式与新对象格式,均调用getAssetUrl拼接完整访问路径。catDetailCache(new Set()):按category_code缓存已拉取详情的分类,页面生命周期内有效;切换场景不清空(分类树不变)。
📋 页签管理 & 物料分类(2026-03-26 ~ 2026-03-27)
代码路径: weihai/client/showtime_weihai/src/views/material/
| 模块 | 文件 | 变更摘要 |
|---|---|---|
| 页签管理 | TabManage.vue |
列表/新建/编辑/删除改为走服务端:POST /api/tab/add、/api/tab/modify、/api/tab/remove、/api/tab/listed;fields 为 { groups: [...] }。增加页签分类层级:/api/tab/classify/*,先选分类再管理页签;listed 需传 classify_code。字段模型:dictKey → key,新增 is_private(私有/不对客户端展示);createFieldFromType / normalizeFields 兼容旧 dictKey。 |
| 布局与弹窗 | TabManage.vue + layouts/index.vue |
el-dialog 勿 append-to=".main-container"(首屏 Teleport 找不到节点);改为 body + position:fixed 限定主内容区。路由 router-view 去掉会卡死的 transition mode="out-in",避免进入页签管理后子页面白屏。 |
| 物料分类 | MaterialCategory.vue |
导入页签:模板列表改为接口拉取(遍历分类再 /api/tab/listed),不再读 localStorage。保存:fields._tabs 等到 /api/category/modify。fieldMap / catFieldDrawer:dictKey → key,兼容 f.key ?? f.dictKey。模板同步:openForm 内先 loadImportTabList().then(...) 再 checkAndSyncTemplateChanges(),避免列表未加载完误判「模板已删除」。分类树 fields 解析:剔除 _tabRules,避免误计「1 个属性」。分类树节点展示服务端下发的 code(子级 code 可由后端拼接,前端只展示)。 |
| 自定义页签构建器 | TabBuilder.vue |
与页签管理字段结构对齐:key、is_private,字典绑定用 key;normalizeGroups 迁移旧 dictKey;右侧抽屉增加 Key 与私有开关。 |
| 后端约定(字段) | — | source:custom / dict;key 统一表示字典标识或自定义业务键(替代原 dictKey);is_private 表示仅内部可见;前端 id(f_xxx) 仍用于拖拽/公式/显隐引用,可与后端业务主键分离。 |
📋 物料模块补充(2026-03-28)
代码路径: weihai/client/showtime_weihai/src/views/material/、src/utils/fieldVisibility.js、src/router/
| 范围 | 文件 | 变更摘要 |
|---|---|---|
| 特征列表 | FeatureManage.vue |
只读列表:POST /api/field/listed 空 body;卡片网格展示 key、source、links、cname/ctime、uname/utime;无增删改。 |
| 路由与标题 | router/index.js、router/permissions.js |
菜单名「特征列表」;document.title 仅用路由 meta.title,不再加后缀。 |
| 显隐工具 | fieldVisibility.js |
新增 countRuleRefs(删前统计引用条数)、purgeRules(按字段 id 清理条件并剪枝空组);规则结构仍为 field.rules / group.rules / optionRules,条件 { id, fieldId, operator, value }。 |
| 页签管理 / TabBuilder | TabManage.vue、TabBuilder.vue |
删字段/删组:始终确认;有引用时提示;确认后 purgeRules。 |
| 物料分类 | MaterialCategory.vue |
跨页签显隐:globalValueMap 汇总全部分类页签字段当前值;条件 fieldId 支持 tabCode::字段id,与同模板多页签引入时的 id 区分;同时写入纯 fieldId 键以兼容历史已存规则;打开显隐弹窗时 migrateCatConditions 将旧纯 id 升级为复合键;删自定义/继承追加字段与分组时 countRuleRefs / purgeRules 传复合键+纯 id,并清理 fieldRules / groupRules(purgeCatTabRules)。引入页签分类级规则存 tab.fieldRules[fieldId]、tab.groupRules[groupId],与模板规则 OR 合并(mergeTabRules)。自定义字段显隐独立弹窗 custFieldRuleDlg,与引入页签字段弹窗同一套条件 UI。自定义页签标签上移除「改名」,改名在 编辑(TabBuilder) 内完成。打开编辑:tabsInitializing + loadImportTabList().finally(...),有页签时在内容区显示「加载中…」,无页签直接「暂无属性页签」;避免接口失败时 tabsInitializing 卡死。openForm 对自建 tab 执行 cleanGroups,避免数字类 defaultValue 空字符串触发 ElInputNumber 告警。normalizeTabGroups 保证 groups/extraGroups 为数组并保留 fieldRules/groupRules。 |
| 保存拦截 | MaterialCategory.vue |
存在 模板已删除 的引入页签时禁止提交并提示删除或重新引入。 |
质量说明: 上述改动在开发过程中已做 lint 检查并修复已知逻辑问题(含复合键与旧数据兼容、加载态与 finally)。打包/上线前仍建议对物料分类、页签管理、显隐规则做一次完整回归测试,无法替代人工与联调结论。
依赖说明: 可选安装 @vue-flow/core 等用于后续业务场景可视化(思维导图式画布),与本次页签字段改造无强依赖。
📋 物料场景、布局与其它(2026-03-28 ~ 2026-03-29)
代码路径: weihai/client/showtime_weihai/(src/views/material/、src/layouts/、src/router/、src/utils/、index.html、public/)
| 范围 | 文件 | 变更摘要 |
|---|---|---|
| 业务场景(物料场景) | SceneManage.vue |
由卡片列表改为:大场景 Tab(/api/scene/classify/listed,失败则仅列场景)+ 左侧场景列表 + 右侧配置;listed 带 classify_code;可拖拽分隔条(useResizablePanel,scene_sidebar_width)。关联物料末级分类(树多选,仅 is_final 末端;移除分类时若属性池已用字段则 ElMessageBox 确认)。字段池:从分类 tabs.groups + fieldMap 提取,过滤 is_private,按 key/label 去重(先遍历到的分类优先);引入页签通过 /api/tab/classify/listed + /api/tab/listed 建 tabTemplateMap,按 refId 补全模板 groups。首选项 / 公共属性 / 特殊属性 三区 HTML5 拖拽;chip 横向换行 + 来源分类小字;保存 fields JSON:_categories、_preferred、_public、_special(并合并保留旧键如 attributes/color)。大场景改名:Tab 悬停铅笔 → /api/scene/classify/modify;控制台 [场景分类] 接口返回 调试。UI:添加分类 warning 按钮、字段池 橘色拖拽说明、字段池宽度约 300px、大场景 Tab min-width + 编辑图标右对齐等。 |
| 布局标签栏 | layouts/index.vue |
修正窄屏下 .tab-bar 被 display:none 隐藏的问题;mobile-sub-tabs 改为展示 layoutStore.tabs(已打开页面)而非侧栏子菜单;移动端 Tab 关闭按钮样式等。 |
| 路由 | router/index.js |
router.afterEach:to.matched.length < 2 时不加 Tab;document.title = to.meta?.title(无后缀);Tab 同步失败日志英文化。 |
| 布局 Store | store/modules/layout.js |
Tab 增删使用不可变更新(如 [...this.tabs, tab] / filter),保证响应式。 |
| 物料分类保存 | MaterialCategory.vue |
cleanField 序列化时保留 optionRules、optionsRaw,修复自定义页签多选 选项级显隐 保存后丢失。 |
| 配置表(价格本) | views/tools/PriceBook.vue |
syncDefaultValuesFromContent:从服务端返回的表格单元格同步 inputArgs[].default_value;loadFromServer / doSaveModify 中在 modify 与 loadWorkbookData 之后调用,避免参数面板与表格不同步;单元格无数据时清空默认展示。 |
| 特征列表 | FeatureManage.vue |
卡片底部 日期/头像/操作人 一行移除。 |
| 站点图标 | index.html + public/ |
favicon 指向 /favicon.png(与 public 内文件名一致即可打包带出)。 |
数据约定(业务场景 fields JSON,当前前端实现):
_categories:关联的物料分类category_code数组。_preferred/_public/_special:已分配字段的 code/id 列表(顺序即拖拽顺序)。- 与历史 API 文档中的
field_code+x+y方案 未对接;若后端检索依赖该结构,需与后端对齐后改造保存格式。
待办(产品已讨论,尚未开发):
- 场景列表支持 树形子分类:在「编辑/删除」旁增加 「子分类」;未把物料字段拖入首选项/公共/特殊任一池前可点,已配置字段后隐藏或禁用,避免与「仅末端挂物料字段」的规则冲突。需后端
parent_code(或等价) 与列表树形接口约定。
📋 物料场景三级结构、物料列表重构、计算公式等(2026-03-29 ~ 2026-03-30)
代码路径: weihai/client/showtime_weihai/src/views/material/、src/api/material.js、src/utils/fieldVisibility.js、src/views/base/WarehouseManage.vue
| 范围 | 文件 | 变更摘要 |
|---|---|---|
| 业务场景三级结构 | SceneManage.vue |
将「分类+场景」二级结构升级为 L1(顶部 Tab)/ L2(侧栏)/ L3(配置实体) 三级。L1 = 无 parent 的 classify;L2 = 有 parent 的 classify;L3 = scene 实体,绑定 L2 的 classify_code。字段配置只允许在 L3 进行。L2 节点显示「创建场景」按钮(可创建多个 L3,不再限制一个)。点击 L2:若无 L3 子节点则右侧显示「请先创建场景」提示;若有 L3 则自动选中第一个 L3 并展示配置。点击 L3:右侧展示该场景配置。顶部「新建场景」→「新建一类」,侧栏「新建」→「新建二类」,L2 创建按钮为「创建场景」。saveConfig() 成功后 ElMessage.success('保存成功')。 |
| 业务场景关联组织 | SceneManage.vue |
linkedOrgs/orgTree 等)、全部 org 函数及 fields._companies 存储,「关联物料末级分类」扩展为全宽显示。 |
| 业务场景树样式 | SceneManage.vue |
侧栏改为 el-tree 组件,自定义节点模板(Folder/FolderOpened/Document 图标),样式与 MaterialList.vue 左侧分类树保持一致。:deep() 适配 Element Plus el-tree 内部选中高亮。 |
| 物料列表改为表格 | MaterialList.vue |
卡片网格布局 → el-table 表格。列:物料名称(160px)、物料编码(150px)、状态(启用/停用 tag)、添加人 cname、添加时间 ctime(前10位)、修改人 uname、修改时间 utime(前10位)、操作(编辑+删除)、末尾预留空白列(min-width:60)。点击行触发编辑弹窗,操作列阻止冒泡。 |
| 继承页签数据修复 | MaterialList.vue |
currentTabList computed 中 inherited-extended 类型 tab 的 groups 之前取 t.snapshot?.groups 为空导致 tab 被过滤。修复:新增 _findPathTo + _resolveInheritedGroups,沿祖先链向上找到原始定义(imported → 取模板 groups;custom → 取节点 groups),再合并 extraGroups。name 同理从祖先链读取,使用局部变量而非修改响应式数据,避免 computed 副作用。 |
| 公式字段显隐修复 | MaterialList.vue、TabBuilder.vue、TabManage.vue |
materialValueMap / previewValueMap / tmPreviewValueMap 均改为两轮求值:第一轮填普通字段,第二轮连续 3 次计算 formula 类型字段(支持公式引用公式的多层传递)。公式求值函数 evalFormulaValue 提取到 fieldVisibility.js 统一 export。公式字段在物料弹窗中渲染为只读灰底展示框,不再是可编辑输入框。 |
| 计算公式互引用与循环检测 | TabBuilder.vue、TabManage.vue |
公式编辑器新增「控件(计算公式)」按钮区(绿色,可点击引用其他公式字段)。新增 hasCircularRef DFS 算法:引用前实时过滤掉会导致循环的公式字段(不在按钮列表中显示);feInsertField 插入时二次校验;validate()/saveTab() 保存时全量检测,有循环则 ElMessage.error 并阻止保存。两个文件均已改动。 |
| 多选字段选项级显隐 | TabBuilder.vue、TabManage.vue |
选项级显隐(眼睛图标)原只支持 checkbox,现扩展至 multiselect(['checkbox','multiselect'].includes(type))。两处文件均已改动,提示文字同步更新。 |
| 字段属性顺序与必填 | TabBuilder.vue、TabManage.vue |
右侧属性面板中 Key 移到「字段标签」上方;Key 和字段标签均为必填,保存时校验为空则阻止并提示。两处文件均已改动。 |
fieldVisibility.js |
src/utils/fieldVisibility.js |
新增 export evalFormulaValue(formula, valueMap):安全执行公式字符串(正则过滤注入,new Function 求值),出错返回空字符串。三处调用方统一 import 此函数。 |
| 物料 CRUD 对接真实 API | src/api/material.js |
全部移除 localStorage mock。getMaterialList → POST /api/goods/listed(category_code, keyword, page, limit);addMaterial → POST /api/goods/add(name, desc, fields, category_codes);updateMaterial → POST /api/goods/modify(goods_code, name, desc, fields);deleteMaterial → POST /api/goods/remove(goods_code)。fields 存储 tabValues + _code。 |
| 仓库档案责任人显示 | WarehouseManage.vue |
树节点「责任人」原显示 user_code,新增 getUserName(userCode) 函数(复用已加载的 userList),改为显示 realname 或 username。 |
数据约定补充(fields._companies 结构):
"_companies": [
{ "company_code": "XX001", "name": "…", "is_valid": "1", "pos": 1, "isParent": true, "childNames": ["子A", "子B"] }
]
存入 scene 的 fields JSON。2026-04-01:组织与场景关系以 link_company / unlink_company(弹窗确认、tag 删除)同步;前端已移除 tag 上绿点 + modify_company 切换启用。isParent / childNames 等为前端展示父 tag 与子列表 tooltip 的可选持久化字段。
已知注意事项:
inherited-extendedtab 的name不存储在子分类节点自身,必须沿祖先链读取,不可修改响应式t.name(computed 副作用)。- 公式字段值的计算依赖
evalFormulaValue做 3 次迭代,支持公式链(A→B→C),但不支持真正的无限递归(DFS 已拦截循环)。 - 物料表格末尾预留空白列(
min-width:60),后续追加字段列时在操作列前插入即可。
📋 物料列表多分类、列表数据格式与接口对齐(2026-03-31)
代码路径: weihai/client/showtime_weihai/src/views/material/MaterialList.vue、src/api/material.js
| 范围 | 变更摘要 |
|---|---|
| 新增入口 | 顶部「新增」去掉「必须选中分类」的 disabled。未选/非末端:弹窗无分类,用户点「+ 添加分类」在树中选末端;已选末端:自动带入该分类并预填字段默认值(_getEffectiveFieldDefaults)。 |
| 多分类弹窗 | materialForm.categories[]:category_code、category_name、name、activeTabCode、tabValues。顶部分类用 ElTag 行内排列(当前选中 type="primary" 蓝色,其余 info;「+ 添加分类」type="warning" plain 橘色),与 SceneManage 关联分类区风格一致。 |
| 分类选择弹窗 | el-tree 多选;catPickerTreeData 对非末端 disabled;#default 插槽在末端节点旁展示绿色小 「末端」 标签。 |
| 新增/修改接口 | 仍使用 POST /api/goods/add、POST /api/goods/modify;请求体为 { data: [{ category_code, name, fields }, ...] };修改额外传 goods_code。已纠正此前误用的 /api/material/新建 等路径。 |
| 列表 API 解析 | getMaterialList:列表数组取 res.data.data(与 list 并列兼容);total 改为 data.total || data.count || list.length,避免后端 total: 0 但实际有行时分页条数错误。 |
| 表格列映射 | 服务端行结构为 goods_code + data[](每项含 category_code、category_name、name、fields)。物料名称:优先 row.data.find(当前 selectedCategoryId).name,否则多名称 / 拼接。物料编码:row.goods_code。 |
| 编辑 | openMaterialDialog('edit') 从 row.data 还原全部分类 tab、名称与 fields(兼容 fields 为 JSON 字符串)。 |
| 默认值竞态 | watch(catTabListsMap):弹窗打开且页签模板加载完成后,为各分类 tabValues 回填缺失的模板默认值与 overrides。 |
| 校验与数字类型 | 保存前各分类「物料名称」必填;失败时 _nameError + 表单项红框提示并切到对应分类。el-input-number 用 :model-value + Number(...) 避免字符串默认值触发 modelValue 类型告警。 |
| 调试日志 | onMounted 中可选请求 goods/listed 并 console.log('[物料列表] 原始数据:', r);保存成功后可选再拉一次列表打印 [物料列表] 保存后最新列表:(便于联调,上线前可按需删除)。 |
📐 计算公式弹窗改版与 Excel / Exec 参考转换(2026-05-08)
代码路径: weihai/client/showtime_weihai/src/components/formula/FormulaEditorPanel.vue、weihai/client/showtime_weihai/src/utils/excelFormulaToReference.js(convertExcelFormulaToReference)。
嵌入页面: MaterialCategory.vue(物料分类自建字段公式)、TabBuilder.vue、TabManage.vue、AssemblyManage.vue(选配画布公式)。
| 范围 | 变更摘要 |
|---|---|
| 布局与宽度 | 弹窗约 900px,类名 fe-dialog-wide:width: min(900px, 96vw)(全局样式挂在公式面板打包处)。主体 左右两列:左为引用字段按钮,右为符号、数字与函数。 |
| 函数区 Tab | FUNCTION_GROUPS 四类由纵向堆叠改为 el-tabs(数学 / 文本 / 逻辑 / 日期),单页内按钮区可纵向滚动(max-height),缩短弹窗总高度。 |
| Excel / Exec 参考 | el-collapse 默认收起;标题「从 Excel / Exec 粘贴……」后以 橘色加粗「请点击展开」 提示。粘贴含 = 的公式后可 转为参考语法:将形如 字段=""、字段='' 的写法迭代替换成 ISBLANK(字段);展示只读可复制,不混入顶部点选编辑的公式。 |
MaterialCategory 差异 |
与其它页一致套用面板; show-str-slash-button 仍为 false,与改版前不含 "/" 快捷键一致。 |
应用内「更新说明」: 见 src/data/appChangelog.js 当日 2026-05-08 分组首条(图标 mdi:view-dashboard-outline)。
📋 业务场景优化(2026-05-08)
代码路径: weihai/client/showtime_weihai/src/views/material/SceneManage.vue
| 范围 | 变更摘要 |
|---|---|
| 移除关联组织 | 删除「关联组织」区块(模板、弹窗、linkedOrgs/orgTree/orgPickerVisible 等 state、loadOrgTree/confirmOrgPicker/removeLinkedOrg 等函数、saveConfig 中的 _companies 字段及 link_company API 调用);「关联物料末级分类」改为全宽,tag 区 max-height 从 82px 扩大至 120px。 |
| 末级分类添加卡顿优化 | confirmCatSelection 改为乐观更新:点击「确认选择」立即关闭弹窗;新增分类以 pending:true 占位 tag(灰色 + 旋转图标)插入列表;后台异步执行 ensureCatAndAncestors;加载完成替换为正常 tag,加载失败标记 error:true(红色 tag,可点 × 移除);新增 hasPending computed,加载期间「保存配置」按钮禁用并显示 tooltip 提示。 |
🔧 选配管理与物料弹窗显隐/公式(2026-05-08)
代码路径: src/views/material/AssemblyManage.vue、MaterialList.vue
| 范围 | 文件 | 变更摘要 |
|---|---|---|
| 计算公式点选编辑 | AssemblyManage.vue |
与分类页签一致:弹窗内点选画布上的数字、金额、其它计算公式与运算符,存储 {字段 id};不再在公式中点选物料页签其它字段;试算与引用范围限于选配画布。 |
| 控件库与显隐条件收窄 | AssemblyManage.vue |
VIS_ELIGIBLE_TYPES 不含字典、明细表格作为新建显隐条件来源;控件库不再提供「字典」「明细/表格」拖入新建(历史已配置项仍可编辑)。物料页签中 type==='dict' 的字段仍可作为联动目标(tabEligibleFields 等与画布侧区分)。 |
| 数字 / 公式列宽 | AssemblyManage.vue |
数字与计算公式字段在画布上默认占两列(amv-field-span2),view 模式单值数字等使用 max-width:320px,减轻多位数与只读公式行被裁切。 |
| 选配显隐合并到物料页签 | MaterialList.vue |
mfAssemblyFormulaValueMap、mfCatTabFieldVisible:选配内 numLinks / textLinks 与分类页签既有显隐规则 AND 合并;公式类联动对 formula 使用 evalFormulaValue;保存前遍历可见字段做必填校验。 |
应用内「更新说明」数据源: src/data/appChangelog.js(图标与模块类型对应关系见文件头注释:仓库类 mdi:warehouse、业务场景 mdi:image-multiple-outline、页签/网格 mdi:view-dashboard-outline、选配画布 mdi:cog-outline、物料列表 mdi:format-list-bulleted 等)。
🏢 部门管理与用户管理(2026-05-08)
代码路径: src/views/base/DeptManage.vue、src/views/base/StaffManage.vue
| 范围 | 变更摘要 |
|---|---|
| 部门管理 · 布局 | 三列:部门树(可搜、子部门缩进) / 角色列表(部门内角色、权限/编辑/删除三点菜单) / 人员列表(依赖选中角色)。列宽可拖拽,localStorage 持久化。 |
| 部门管理 · 角色 | 新增/编辑角色增加 岗位类型(role_type:M 管理岗 / P 专家岗);列表名称右侧 M/P 小 Tag(plain);有 描述 时悬停名称显示 Tooltip。 |
| 部门管理 · 成员 | 点击角色行选中后第三列加载该角色下成员;关联用户弹窗:/api/user/listed 取用户(开发约定单次最多 500 条,支持本地关键词筛选),勾选后 POST /api/dept/link_user;行内 级别 1–10、KPI% 0–100,变更防抖后分别 POST /api/dept/set_role_level、POST /api/dept/set_kpi_rate;移除 → POST /api/dept/unlink_user。 |
| 部门管理 · 成员列表接口 | 已关联成员由前端常量 API_DEPT_ROLE_MEMBER_LIST(DeptManage.vue 顶)发起查询,请求体为 { dept_code, role_code }。默认值为占位路径,须与后端「查询某部门某角色已关联用户」的真实 URL 对齐,否则第三列无法加载。 |
| 用户管理 · 调试 | 仅 import.meta.env.DEV:列表 loadList 成功后在控制台输出 res.data、当前页用户深拷贝、console.table、首条 Object.keys,便于对照字段。 |
📋 业务场景关联组织树与 setup 修复(2026-04-01)
代码路径: weihai/client/showtime_weihai/src/views/material/SceneManage.vue
| 范围 | 变更摘要 |
|---|---|
| 关联组织 UI | 原远程搜索 el-select 改为 「添加组织」+ 弹窗 el-tree:POST /api/company/listed 树数据经 mapOrgNodes 映射为 id / label / children;无效组织(is_valid 为假)节点 disabled: true,节点模板旁红色 「禁用」 tag。父子联动勾选(非 check-strictly)。 |
| 最小存储集 | getMinimalOrgSelection() 结合 getCheckedKeys 与 getHalfCheckedKeys:父节点整棵选中时只保留 父 company_code;部分选中则继续向下落到具体子节点,与「父一条代表整棵子树」的存储约定一致。 |
| 标签展示 | 已关联列表:父级 el-tag type="primary"(蓝),el-tooltip 展示子组织名称列表;单独勾选的叶子 type="info"(灰)。移除原 tag 上 绿点切换启用(modify_company 即时改 is_valid)的交互。 |
接口与 _companies |
弹窗 确认:相对原 linkedOrgs diff 后,新增走 POST /api/scene/link_company,移除走 POST /api/scene/unlink_company(需已有 scene_code)。保存配置 仍把当前列表写入 fields._companies,可含 company_code、name、full_name、is_valid、pos 及 isParent、childNames(用于父 tag 与子列表 tooltip)。 |
| 树回显 | openOrgPicker 中 orgPickerTreeRef.setCheckedKeys(keys, leafOnly):leafOnly 须为 false,否则仅保存父 code 时树无法正确勾选父节点。 |
| setup 报错修复 | 组织搜索框与 el-tree 联动使用 watch(orgPickerSearch, ...) 调用 filter;必须在 vue 的 import 中显式加入 watch,否则运行时报 ReferenceError: watch is not defined,组件 setup 失败、路由白屏。 |
📋 子组显隐/公式/必填/单位显示等综合优化(2026-04-02 ~ 2026-04-03)
代码路径: weihai/client/showtime_weihai/src/views/material/、src/utils/fieldVisibility.js
| 范围 | 文件 | 变更摘要 |
|---|---|---|
| 空页签显示修复 | MaterialList.vue |
_getTabListForCategory 移除 .filter(tab => tab.groups.length > 0),空页签(0分组0字段)也正常显示;空页签内容区提示「该页签暂无字段」。 |
| 问号说明弹窗黑底白字 | MaterialCategory.vue、TabManage.vue、TabBuilder.vue |
desc-popover 统一改为深色背景(#1d1e20)、白色文字(#f0f0f0),箭头颜色同步;样式置于非 scoped <style> 块确保穿透 Element Plus 浮层。 |
| 创建/编辑物料问号说明 | MaterialList.vue |
字段标签行新增 el-popover 问号图标(QuestionFilled),普通组和子组字段均有;样式复用 desc-popover 黑底白字。 |
| 必填字段开关 | TabManage.vue、TabBuilder.vue |
字段编辑抽屉「私有字段」下方新增「必填字段」el-switch,绑定 field.required;公式字段不显示该开关。createFieldFromType / normalizeField / cleanField 均补充 required: false 默认值,序列化时保留。 |
| 必填校验 - 创建/编辑物料 | MaterialList.vue |
submitMaterial 保存前遍历所有分类的 tab 字段(含子组),必填且为空时切换至对应分类 tab 并提示字段名称,阻止保存;字段标签旁显示红色 *。 |
| 必填校验 - 物料分类 | MaterialCategory.vue |
submitForm 保存前检查自定义页签(type 为空或 'custom')的 field.defaultValue,及继承扩展页签(inherited-extended)的 override / 父级默认值;为空时切换页签并提示,阻止保存;所有字段标签(含继承/引入/自定义/子组,共 15+ 处)显示红色 *。 |
cleanField 数据保全 |
MaterialCategory.vue |
cleanField 补充 required、showUnit 字段;cleanGroups 新增对 subgroups 的递归 cleanField,防止子组字段属性在保存时被丢弃。 |
自定义页签 type 补全 |
MaterialCategory.vue |
confirmBuilderDialog 保存自定义页签时补写 type: 'custom',修复保存后必填校验 tabType === 'custom' 不匹配的问题。 |
| 选项改名同步显隐规则 | TabManage.vue、TabBuilder.vue |
选项输入框新增 @focus 捕捉旧值,@change 时遍历所有字段(含子组)的 rules 条件,将 eq/neq 条件值及 contains/not_contains 数组中的旧值替换为新值;同步 optionRules 的 key。 |
| 选项删除清理规则 | TabManage.vue、TabBuilder.vue |
removeOption 删除选项时同步清理 optionRules[deletedVal] 及所有字段规则中引用该选项值的条件(eq/neq 置空,contains/not_contains 过滤)。 |
| MaterialCategory 选项文本框规则同步 | MaterialCategory.vue |
catFieldDrawer 的 optionsRaw 文本框新增 @focus 记录旧选项、@change 时 syncCatFieldOptRules():检测被删除的选项并清理相关规则条件和 optionRules key。 |
| 单位标签显示开关(showUnit) | TabManage.vue、TabBuilder.vue、MaterialCategory.vue、MaterialList.vue |
字段编辑器公式/数字/金额三类单位输入行下方新增「名称后显示单位」开关(有单位时才出现),默认开启。关闭后标签不再拼接 (单位)。createFieldFromType / normalizeField / cleanField 均补充 showUnit: true 默认值。所有渲染处改为 field.unit && field.showUnit !== false 判断(共 22 处替换)。数字/金额输入框右侧始终显示单位(不受 showUnit 控制):MaterialCategory 补 2 处 ifi-input-unit-wrap wrapper,MaterialList/预览本已有。 |
📋 复查修复(2026-04-04)
代码路径: weihai/client/showtime_weihai/src/views/material/
| 修复点 | 文件 | 问题描述 | 修复方式 |
|---|---|---|---|
| 必填校验未切换到对应页签 | MaterialList.vue |
submitMaterial 在发现必填字段为空时,只切换了分类 activeCatCode,未切换到该字段所在页签,用户无法直观定位。 |
改为按「分类 → 页签 → 字段」三层遍历,命中时同步设置 cat.activeTabCode = tab.code,再 nextTick 提示。 |
TabManage normalizeFields 未处理子组 |
TabManage.vue |
打开编辑时 normalizeFields 只对 g.fields 归一化(补全 required、showUnit、options 迁移等),g.subgroups[].fields 未处理,历史子组字段缺少这些属性。 |
抽出 normalizeOneField 函数,同时对主字段和子组字段调用,确保两层字段都被归一化。 |
catFieldDrawer 中 amount 类型用错控件 |
MaterialCategory.vue |
自定义页签字段编辑抽屉里,number 有 el-input-number,amount 却因缺少判断落入 el-input(纯文本),精度和单位均未展示。 |
将条件改为 number || amount,使用 ifi-input-unit-wrap + el-input-number(金额加 :precision)+ 单位显示。 |
| 继承页签 number/amount override 用错控件 | MaterialCategory.vue |
继承页签(inherited-extended)中数字/金额字段的 override 输入使用通用 el-input,无精度控制和单位展示,包含主组和子组两处。 |
在两处 v-else 前各加 v-else-if="number||amount" 分支,改用 el-input-number + ifi-unit,通过 @update:model-value 写入 setInhOverride。 |
管理后台首页 Dashboard 开发记录
📐 首页布局(4行)
| 行 | 列数 | 组件 | 说明 |
|---|---|---|---|
| 第一行 | 4列等宽 | PurchaseTrendCard / SalesAmountCard / InventoryWarning / EventTimeline | 数据卡片区 |
| 第二行 | 3列(1/3+1/3+1/3) | RankingChart / 内容待定 / CategoryRoseChart | 图表区 |
| 第三行 | 全宽 | OrderGanttChart | 订单甘特图 |
| 第四行 | 1/4 + 3/4 | ChordChart / SankeyChart | 仓库关系 + 资金流向 |
🧩 组件清单
InventoryWarning(库存预警)
- 2行×6列电池条,固定
height: 52px(不能用 flex:1,否则第2页单行时会变高) - 18条数据分2页,4秒自动轮播,左右滑动动画
.grid-wrapper用position: relative + overflow: hidden+.inventory-grid用position: absolute实现动画不撑大容器- 圆点翻页固定
height: 20px
RankingChart(排行榜)
- 商品排行 / 机构排行切换,柱状图 / 折线图切换
- 折线图:
symbol: 'none'无圆点,渐变色线条和面积 - 商品排行:青蓝渐变(
#36d1dc → #5b86e5);机构排行:橙金渐变(#f7971e → #ffd200)
CategoryRoseChart(品类分布对比)
- 两个南丁格尔图并排(
roseType: 'area') - 左侧采购分布,右侧销售分布,竖排文字标识,相同品类颜色一致
OrderGanttChart(订单甘特图)
- 左侧仓库列表点击筛选,甘特条渐变色+圆角+阴影
- 订单号24位(7位随机前缀+17位编号),蓝色可点击,下划线用
graphic + convertToPixel手动绘制,labelWidth: 148 - 今日红色虚线标记,Tooltip 需要 null 检查防 markLine 触发报错
- 日期选择器用
Date对象,xAxis.min/max用new Date().getTime()
ChordChart(仓库调货关系)
- ECharts
graph类型,layout: circular - 圆心用
graphic显示总调货量(蓝色半透明底+文字) - 节点标签:仓库名(加粗)+ ↑调出(红)↓调入(绿),节点大小随吞吐量动态变化
- ⚠️ 待优化:底部节点标签偏移,考虑改用
force弹力布局
SankeyChart(资金流向分析)
- ECharts
sankey类型,left/right: 12%防标签裁切 - 节点颜色与
NODE_COLORS映射,Tooltip 显示流向金额
📦 Mock 数据(adminDashboard.js)新增
| 导出名 | 说明 |
|---|---|
categoryRoseData |
品类采购/销售分布(8个品类) |
warehouseTransferData |
仓库间调货量(6仓库,9条流向) |
fundFlowData |
资金流向桑基图节点和连线 |
ganttOrders |
订单号改为24位(7位随机前缀+17位) |
🔧 开发规范(重要)
容器高度铁律:
- 任何组件根元素:
width:100%; height:100%; overflow:hidden - flex子元素撑满:
flex:1; min-height:0(min-height:0必须加) - 动画切换:用
position:relative + overflow:hidden包裹,子元素position:absolute - ECharts初始化:
onMounted + nextTick,销毁:onUnmounted中dispose()+ 移除 resize 监听
全局字体:
- 中文:
var(--body-font-family)→DingTalk_JinBuTi_Regular - 数字:
var(--number-font-family)→DS-DIGIT
⚠️ 已知问题
| 问题 | 原因 | 状态 |
|---|---|---|
| InventoryWarning 第2页电池条变高 | 用了 flex:1 |
✅ 已修复(固定 height:52px) |
Gantt tooltip 报错 undefined.events |
markLine 触发无 null 检查 | ✅ 已修复 |
| Mapbox resize 报错 | 卸载后未移除 resize 监听 | ✅ 已修复 |
| ChordChart 底部节点标签偏移 | circular 布局边距不足 | ⚠️ 待优化 |
🗺️ 路由结构(当前完整)
/ (首页 - 仪表盘)
/purchase (采购)
├── /purchase/order 采购订单
├── /purchase/order/:orderNo 采购订单详情(hidden,不加入Tab栏)
├── /purchase/supplier 供应商管理
├── /purchase/warning 采购预警
├── /purchase/batch 批次管理
├── /purchase/alloc-priority 备货配额优先级 ← 新增
└── /purchase/flow 采购流程看板 ← 新增
/inventory (库存)
├── /inventory/overview 库存总览(含商品多仓对比Tab)
├── /inventory/transfer 仓库调拨
├── /inventory/warning 库存预警
└── /inventory/batch 批次查询
/sales (销售)
├── /sales/order 销售订单
├── /sales/customer 客户管理
├── /sales/stats 销售统计
└── /sales/return 销售退货
/material (物料管理)
├── /material/list 物料列表
├── /material/category 分类管理
├── /material/scene 场景管理 ← 新增(2026-03-07)
├── /material/feature 特征管理 ← 新增(占位,2026-03-07)
└── /material/template 模板管理 ← 新增(占位,2026-03-07)
/finance (财务)
├── /finance/overview 收支概览
├── /finance/fundflow 资金流向
├── /finance/payable 应付账款
└── /finance/receivable 应收账款
/base (基础数据) ← 新增模块
├── /base/dict 数据字典(19个字典,分组管理)
├── /base/org 组织单元(树形层级,增删改)
├── /base/warehouse 仓库档案(5个仓库,增删改+详情)
└── /base/bill 票据管理 ← 新增(2026-03-08)
/login (地图登录页)
对应 view 文件目录:
src/views/purchase/— 4个页面(均为占位页)src/views/inventory/— 4个页面(均为占位页)src/views/sales/— 4个页面(均为占位页)src/views/finance/— 1个页面(占位页)
🐛 路由过渡动画修复
问题: 管理后台内部页面跳转时,触发了"地图→管理后台"的 page-fade-dashboard 动画
根本原因:
App.vue用了:key="route.path",每次路由变化强制重新挂载组件- transition name 直接读
route.meta.transition,进入首页就读到page-fade-dashboard
修复方案(App.vue):
- 去掉
:key="route.path",让 Vue 自己判断组件是否真的变化 - 用
router.beforeEach动态控制动画名:- 从
/login进入管理后台 →page-fade-dashboard - 进入
/login→page-fade-map - 管理后台内部跳转 →
''(无动画)
- 从
采购模块已实现功能
📄 采购订单页(src/views/purchase/PurchaseOrder.vue)
功能:
- 顶部4张统计卡片:总数 / 待审核 / 审核中 / 本月采购总额
- 搜索筛选:订单号模糊搜索、供应商/采购组织/状态下拉(选即生效)、日期范围
- 表格:采购号(蓝色可点击)、供应商、组织、仓库、商品、数量(DS-DIGIT字体)、总额、票据类型、状态标签、操作列(左对齐:详情/审核/归档/复制/删除)
- 创建弹窗功能:
- ① 采购组织自动推荐:选商品后自动出现黄色推荐条,显示推荐组织+票据类型+首选供应商,点"一键填入"自动填充空白字段(不覆盖已填内容)
- ② 买手分层定价:费用估算面板中,按数量自动判断大(≥500件)/中(100~500件)/小(<100件)买手,显示三档价格对比,当前档高亮蓝色
- 费用估算面板:选完供应商+商品+数量+票据类型后自动计算货款、增值税、预估运费、综合到手成本
- 未填字段时显示灰色引导提示
数据来源: src/api/purchase.js(mock,联调时替换)
🏢 供应商管理页(src/views/purchase/SupplierManage.vue)
功能:
- 顶部4张统计卡片:总数 / 已核准(绿)/ 潜在(橙)/ 已退出(灰)
- 两个 Tab 切换:
- 供应商档案 Tab:规范字段表格(名称/简称/分类/管理组织/纳税类别/结算方式/账期/联系电话/状态)
- 工具栏:名称搜索 + 分类/状态筛选 + 新增供应商按钮
- 操作列:报价 / 编辑 / 删除
- 分页:支持10/20/50条/页
- 新增/编辑弹窗:供应商名称*、简称、分类*、管理组织*、纳税类别、国家/地区、交易币种、结算方式、账期、发运方式、联系电话、电子邮箱、注册地址、供应商状态、备注
- 报价明细抽屉:沿用原有发票兼容性矩阵 + 报价对比表(按名称匹配 mockSuppliers)
- 删除:二次确认弹窗
- 仓库调拨费率 Tab:5仓库×5仓库调拨费率矩阵,按票据类型分列
- 供应商档案 Tab:规范字段表格(名称/简称/分类/管理组织/纳税类别/结算方式/账期/联系电话/状态)
- 移动端:全屏抽屉+返回按钮
数据来源:
mockVendors(src/api/purchase.js):供应商档案 mock,规范字段,支持 getVendorList / addVendor / updateVendor / deleteVendormockSuppliers(src/api/purchase.js):采购定价数据,供报价抽屉使用,不参与档案 CRUD- 字典引用:
dictVendorClasses / dictCurrencies / dictSettleMethods / dictShipMethods / dictCountries(src/api/material.js)
🔔 采购预警页(src/views/purchase/PurchaseWarning.vue)
核心逻辑: 当前库存 ÷ 日均销量 = 预计断货天数
功能:
- 顶部4张风险卡片:紧急(≤3天,红) / 预警(≤7天,橙) / 充足(绿) / 预警商品预估采购额(蓝),点击卡片直接筛选
- 筛选栏:风险等级/仓库/品类,选即生效
- 预警表:行颜色编码(红/黄/白),断货倒计时、建议采购量、参考价区间、预估费用、最优供应商
- 点击行展开右侧抽屉:商品详情+近6个月采购价走势折线图(渐变色)
- "一键采购"按钮:跳转采购订单页并预填商品/供应商/数量/仓库
- 移动端全屏抽屉,顶部 padding 留出 tab 栏高度(56px)
首页大屏联动: InventoryWarning.vue 的红色"X 紧急"badge 可点击,跳转到采购预警页
数据来源: src/api/purchaseWarning.js(mock,联调时替换)
📦 API 文件说明
| 文件 | 内容 |
|---|---|
src/api/purchase.js |
采购订单CRUD、供应商数据、定价矩阵、分层定价、推荐规则、调拨费率 |
src/api/purchaseWarning.js |
预警数据、断货计算、价格走势、统计概览 |
注意: 联调时只需替换各文件中的 mock 函数为 request(...) 调用,页面代码无需改动。
📦 批次管理页(src/views/purchase/BatchManage.vue)
功能:
- 顶部4张统计卡片:总批次数 / 在途批次 / 部分到库 / 在途件数合计
- 搜索筛选:批次号模糊搜索、商品名称模糊搜索、仓库下拉、状态下拉(选即生效)
- 表格列:批次号 / 关联采购单 / 商品名称 / 目标仓库 / 采购总量 / 在途数量(橙色)/ 在库数量(绿色)/ 到货进度条 / 预计到货日 / 状态标签
- 到货进度条:
el-progress,按到库百分比自动变色(<30% 红 / 30~80% 橙 / ≥100% 绿) - 行颜色编码:在途批次浅黄色背景 / 部分到库浅蓝色背景
- 点击行展开右侧抽屉(异步加载详情):
- 🆕 7阶段批次流程条:采购下单→供应商发货→在途运输→到货待分配→分配入库中→入库完成→财务结算,已完成绿色打勾,当前阶段蓝色高亮,未来灰色
- 基本信息(含 🆕 采购类型 标签:统筹采购/独立采购)
- 到货进度条
- 🆕 分仓分配明细卡片(统筹采购时显示):每个仓库一张卡,含优先级圆圈(P1绿/P2橙/P3灰)、发票兼容性标签、到货进度条、已到/应到件数
- 物流时间线(补充财务结算节点)
- 分页:默认每页15条
- 移动端:全屏抽屉 + 顶部返回按钮
新增数据(src/api/purchase.js):
BATCH_STAGES:7阶段定义常量(导出)mockBatches每条新增batchStage(当前阶段1-7)和isCoordinated(是否统筹采购)genWarehouseAlloc(batch):根据批次数据自动生成3仓库分配明细getBatchDetail(id):返回批次 + 分仓分配详情
数据来源: src/api/purchase.js
🏗️ 基础数据模块(/base)
组织单元管理页(src/views/base/OrgManage.vue):
- 顶部4张统计卡片:总数 / 公司/总部 / 分公司 / 已停用
- 工具栏:名称搜索 + 组织形态/状态筛选 + 新增按钮
- 树形表格(默认全展开):名称+图标 / 编码 / 简称 / 形态标签 / 上级组织 / 职能标签(销售/采购/库存/财务/工厂)/ 负责人 / 电话 / 纳税类型 / 状态
- 操作列:编辑 / 添加子级 / 删除(有子组织时拒绝删除)
- 新增/编辑弹窗:名称*、编码*、简称、组织形态*、上级组织、负责人、联系人、电话、地址、纳税人名称/识别号/类型、状态、职能多选(销售/采购/库存/会计主体/工厂)、描述
- mock 数据:5条,与项目全局使用的组织名完全对应(XS威海总部为根,4个分公司为子节点)
仓库档案管理页(src/views/base/WarehouseManage.vue):
- 顶部4张统计卡片:总数 / 顶级仓库 / 已启用 / 已停用
- 左右分栏布局(2026-05-08 重构):
- 左侧:中心仓(父级)列表,顶部搜索框,hover 显示三点菜单(添加子仓库/编辑/删除),不占仓库名宽度;底部「新增中心仓」快捷按钮
- 中间:可左右拖拽分隔条,宽度持久化至
localStorage(key:wh_sidebar_width) - 右侧:选中中心仓的子仓库(门店仓)列表,带表头(仓库名称/编码/所属组织/责任人);编码蓝色、组织绿色、责任人橙色;奇偶行交替背景色;hover 显示编辑/删除操作;右上角「+ 添加子仓库」按钮
- 新增/编辑弹窗:名称*、编码、所属组织*、责任人、联系人、电话、国家/地区、行政区域、能力范围、联系地址、仓库描述、状态
移动端适配:
- 组织管理:≤768px 统计卡片 2 列、工具栏纵向排列、表单全宽、弹窗 95% 宽;≤900px 自动隐藏"联系电话"列
- 仓库档案:≤768px 同上,抽屉全屏,功能开关格从3列降为2列
数据来源: src/api/base.js
mockOrgs:组织 mock + getOrgList / getOrgTree / addOrg / updateOrg / deleteOrgmockWarehouses:仓库 mock + getWarehouseList / addWarehouse / updateWarehouse / deleteWarehousegetOrgNameOptions()/getWarehouseNameOptions():供其他模块下拉引用
部门管理页(src/views/base/DeptManage.vue):
- ✅ 已对接真实 API(部门/角色 CRUD;成员关联见下)
- 三列布局(2026-05):左 部门树(搜索、展开子部门) / 中 角色列表(当前选中部门下的角色,hover 三点菜单:权限配置、编辑、删除;点选整行用于右侧人员上下文) / 右 人员列表(仅当选中某一角色时加载)。中间、右侧与左侧之间 可拖拽分隔条,宽度持久化。
- 角色:新增/编辑含 岗位类型
role_type(M管理岗 /P专家岗);列表名称旁 M/P Tag;有 描述 时悬停名称展示 Tooltip。 - 成员(选中角色后):关联用户 弹窗从全库用户挑选(
/api/user/listed,前端单次最多取 500 条并支持本地筛选),提交POST /api/dept/link_user;行内 职位级别 1–10、KPI 比例 0–100(%),分别调用POST /api/dept/set_role_level、POST /api/dept/set_kpi_rate(输入防抖后保存);移除 →POST /api/dept/unlink_user。已关联成员 列表查询 使用源码常量API_DEPT_ROLE_MEMBER_LIST,须与后端真实「按dept_code+role_code查询成员」接口路径一致(默认值为占位)。 - 部门/角色接口:
POST /api/dept/listed、/dept/add、/dept/modify、/dept/remove;POST /api/role/listed、/role/add、/role/modify、/role/remove、/role/set_power等(与权限抽屉一致)。 - 新增/编辑部门弹窗:部门全称、简称、编码、部门性质、状态、责任人/分管领导(
/api/user/listed下拉联动电话)、职责描述等(同前版字段,略)。 - 移动端:小屏三列纵向堆叠;统计卡片与列表适配。
用户管理页(src/views/base/StaffManage.vue):
- ✅ 已对接真实 API(2026-03-08,原名"员工档案"改为"用户管理")
- 开发调试(2026-05-08):仅在
npm run dev构建下,列表加载成功后在浏览器控制台打印/api/user/listed返回与当前页用户数据,便于查看全部参数字段。 - 顶部统计卡片:员工总数 / 在职 / 总计 / 已禁用
- 工具栏:姓名/账户搜索 + 新增按钮
- 表格字段:员工姓名 / 编号 / 账户 / 性别 / 所属组织(显示名称)/ 部门(显示名称)/ 职务 / 手机号 / 入职日期 / 状态
- 点击行展开详情抽屉:基本信息(编号/账户/性别/生日/手机/邮箱)+ 任职信息(所属组织名/部门名/职务/入职日期/状态)+ 备注
- 操作列:编辑 / 删除
- 新增/编辑弹窗:
- 所属组织:下拉选择(来自
/api/company/listed) - 部门:树形下拉(
el-tree-select,根据所属组织联动加载/api/dept/listed,含子部门层级)
- 所属组织:下拉选择(来自
- API 字段:
user_code、company_code、dept_code、username、password、code、mark、realname、birthday、gender(0女/1男)、phone、email、leader_level(职务)、join_time、is_valid(0禁/1启)、pos - 接口:
POST /api/user/listed、/user/add、/user/modify、/user/remove - 表格/抽屉中所属组织和部门显示名称;找不到对应数据显示"需要调整"
银行账户页(src/views/base/BankAccountManage.vue):
- ✅ 已对接真实 API(2026-03-08,原名"企业银行账户"改为"银行账户")
- 顶部统计卡片:账户总数 / 基本账户 / 纳税登记 / 已启用
- 工具栏:账户名称/账号搜索 + 账户类型筛选 + 新增按钮
- 表格字段:账户名称 / 账户编码 / 所属组织(显示名称)/ 银行账号 / 开户银行 / 账户类型Tag / 纳税登记 / 币种 / 状态
- 点击行展开详情抽屉:基本信息 / 银行信息 / 账户属性(分三组展示)
- 操作列:编辑 / 删除
- 新增/编辑弹窗:账户名称*、编码、所属组织*(下拉)、开户名称*、银行名称、银行账号*、开户银行、账号省份/城市、银联号、账户类型、银行性质、开户日期、纳税登记(0否/1是)、货币种类(文本)、排序、状态、备注
- API 字段:
bank_code、company_code、name、code、account_name、bank_name、account_bank、account_number、bank_union_code、account_province、account_city、account_type、account_property、active_date、is_tax_check、currency_variety、mark、is_valid、pos - 接口:
POST /api/bank/listed(空body)、/bank/add、/bank/modify、/bank/remove - 移动端适配:弹窗 92vw,圆角滚动
会计科目页(src/views/base/AccSubjectManage.vue):
- ✅ 已对接真实 API(2026-03-09)
- 顶部统计卡片:按
elements字段统计资产/负债/权益/收入/费用/成本六要素数量(各自使用规范颜色) - 工具栏:名称/编码搜索 + 会计要素筛选 + 树形/列表切换 + 新增按钮
- 树形视图:树状展开,一级科目加粗,列:科目名 / 编码 / 会计要素Tag / 余额方向(借蓝/贷红)/ 现金分类 / 数量核算 / 状态
- 列表视图:同字段但展平
- 操作列:添加子级(仅顶级显示,
_level===0)/ 编辑 / 删除 - 新增/编辑弹窗:有上级时显示只读上级科目;编码*、名称*、会计要素*、余额方向*(debit/credit)、现金分类、赤字检查、数量核算(0/1)、排序、状态、科目说明
- API 字段:
ledger_code、parent_code(子科目才传)、name、code、elements、balance、cash_classify、deficit_check_type、amount_is_check、mark、is_valid、pos - 接口:
POST /api/ledger/listed、/ledger/add、/ledger/modify、/ledger/remove is_valid兼容布尔/数字/字符串三种格式- mock 数据已屏蔽(
base.js中注释)
费用项目页(src/views/base/ExpenseItemManage.vue):
- ✅ 已对接真实 API(2026-03-09)
- 顶部统计卡片:总数 / 顶级分类 / 已启用 / 已停用
- 工具栏:名称/编码搜索 + 费用业务筛选 + 新增按钮
- 表格:树形展开,列:名称 / 编码 / 费用业务Tag / 使用业务 / 状态 / 备注
- 操作列:添加子项(仅顶级显示,
_level===0)/ 编辑 / 删除 - 新增/编辑弹窗:有上级时显示只读上级项目;名称*、编码、费用业务(6种枚举)、使用业务(文本)、排序、状态、备注
- API 字段:
expense_code、parent_code(子项目才传)、name、code、expense_type、business、mark、is_valid、pos - 接口:
POST /api/expense/listed、/expense/add、/expense/modify、/expense/remove is_valid兼容布尔/数字/字符串三种格式- mock 数据已屏蔽(
base.js中注释)
路由(均在 /base 模块下):
/base/dict→ MaterialDict.vue(字典管理)/base/org→ OrgManage.vue(组织列表)/base/dept→ DeptManage.vue(部门管理)/base/staff→ StaffManage.vue(用户管理,原员工档案)/base/bank-account→ BankAccountManage.vue(银行账户,原企业银行账户)/base/acc-subject→ AccSubjectManage.vue(会计科目)/base/expense-item→ ExpenseItemManage.vue(费用项目)
📌 2026-03-08 ~ 2026-03-09 基础数据模块 API 对接更新
| # | 模块 | 文件 | 说明 |
|---|---|---|---|
| 1 | 用户管理 | StaffManage.vue |
页面标题改为"用户管理";"职位级别"改为"职务";所属组织改为下拉(/company/listed);部门改为树形下拉(el-tree-select,联动所属组织);表格/抽屉显示名称而非 code;接入 /user/remove;找不到关联数据显示"需要调整" |
| 2 | 部门管理 | DeptManage.vue |
重写为左右双栏布局;左侧企业树,右侧部门列表;表格树形展开;"子部门"按钮仅顶级显示;parent_code 仅子部门传递;接入完整 CRUD |
| 3 | 银行账户 | BankAccountManage.vue |
页面标题改为"银行账户";全字段对齐 API;所属组织改为下拉;货币种类改为单文本输入;纳税登记改为 0/1 下拉;接入完整 CRUD |
| 4 | 组织列表 | OrgManage.vue |
表头"编码"改为"企业代码" |
| 5 | 会计科目 | AccSubjectManage.vue |
全字段对齐 API(elements/balance/cash_classify等);子科目不显示"添加子级"按钮;is_valid 兼容布尔/数字/字符串;mock 数据屏蔽 |
| 6 | 费用项目 | ExpenseItemManage.vue |
全字段对齐 API(expense_type/business等);子项目不显示"添加子项"按钮;统计改为总数/顶级/启用/停用;is_valid 兼容布尔/数字/字符串;mock 数据屏蔽 |
| 7 | 全局 is_valid 修复 |
所有 base 模块 | API 返回布尔 true/false 时状态显示错误,统一改为 isValid(v) 函数兼容布尔/数字/字符串三种格式 |
| 8 | base.js mock 屏蔽 | src/api/base.js |
部门/员工/银行账户/会计科目/费用项目 5 个模块 mock 数据已用 /* */ 注释屏蔽;仓库/组织 mock 保留(WarehouseManage 仍在使用) |
| 9 | 物料分类属性扩展 | MaterialCategory.vue |
AVAILABLE_DICTS 新增:部门/用户·人员/银行账户/会计科目/费用项目 5 个基础数据字典选项 |
| 10 | 物料属性动态关联 | MaterialList.vue |
页面加载时从各 API 拉取基础数据存入 dynamicDictMap;部门/会计科目/费用项目使用 el-tree-select 树形下拉;编辑物料时自动过滤已删除属性(情况1)、自动清空并提示已失效的引用值(情况2);详情抽屉显示名称而非 code,失效值显示橙色"⚠ 数据已失效" |
👥 客户管理页(src/views/sales/CustomerManage.vue)
功能:
- 顶部概览带(深色渐变):客户总数 / A级客户数 / 累计销售额 / 累计订单数
- 工具栏:名称搜索 + 分类/级别/业务员筛选 + 新增客户按钮
- 表格(规范字段):名称 / 级别徽标(A红/B橙/C灰)/ 客户分类 / 行业 / 联系人 / 联系电话 / 负责业务员 / 结算方式 / 账期 / 累计销售额
- 操作列:详情 / 编辑 / 删除
- 分页:10/20/50 条/页
- 详情抽屉:头像+级别+业务员、业绩数字(销售额/订单数/账期)、基础信息(编码/分类/类型/行业/组织/联系方式/地址)、交易设置(币种/结算/账期)、备注
- 新增/编辑弹窗:客户名称*、简称、分类*、类型、级别*、行业、管理组织*、业务员、联系人、电话、邮箱、国家/地区、地址、币种、结算方式、账期、状态、备注
- 删除:二次确认弹窗
数据来源:
mockCustomers(src/api/sales.js):扩展为规范字段(code/simpleName/customerClass/customerType/industry/org/currency/settleMethod/email/country/remark),支持 getCustomerList / addCustomer / updateCustomer / deleteCustomer- 字典引用:
dictCustomerClasses / dictCustomerTypes / dictCustomerIndustries / dictCurrencies / dictSettleMethods / dictCountries(src/api/material.js)
📊 采购模块完整页面清单
| 页面 | 路由 | 文件 | 状态 |
|---|---|---|---|
| 采购订单 | /purchase/order |
PurchaseOrder.vue |
✅ 完成 |
| 采购订单详情 | /purchase/order/:orderNo |
PurchaseOrderDetail.vue |
✅ 完成 |
| 供应商管理 | /purchase/supplier |
SupplierManage.vue |
✅ 完成 |
| 采购预警 | /purchase/warning |
PurchaseWarning.vue |
✅ 完成 |
| 批次管理 | /purchase/batch |
BatchManage.vue |
✅ 完成 |
| 备货配额优先级 | /purchase/alloc-priority |
AllocPriority.vue |
✅ 完成(2026-03-01) |
| 采购流程看板 | /purchase/flow |
PurchaseFlowDashboard.vue |
✅ 完成(2026-03-01) |
📄 采购订单详情页(src/views/purchase/PurchaseOrderDetail.vue)
功能:
- 顶部操作栏:返回按钮 + 订单号 + 状态/票据标签 + 发布/审核/开单/提交/关闭等操作按钮
- 基本信息区:4列网格,含采购组织、交易类型、供应链单位、单据日期、采购部门、汇率、币种、付款状态等
- Tab 区域:
- 物料行:序号/行号/物料描述/编码/名称/SKU/数量/单价/金额/税率/仓库
- 🆕 分仓分配 Tab:
- 顶部蓝色统筹采购头(统筹组织名称+类型标签、总量/已分配/待分配件数)
- 分配明细表(优先级圆圈P1绿/P2橙/P3灰、仓库、接货组织、组织类型、票据类型、发票兼容性标签、分配数量、占比、分配金额、分配状态、预计到货)
- 底部黄色说明条(优先级排序规则说明)
- 费用行 / 费用付款 / 付款计划 / 执行 / 附件(占位)
- 右侧面板:
- 采购供应商付款金额汇总
- 🆕 分仓分配汇总卡:每仓库一行,展示名称+状态标签+彩色进度条+占比
- 存量信息柱状图 / 近期价格走势折线图
导航方式:
- 采购订单列表中点击订单号(蓝色链接)跳转
- 采购订单列表中点击整行也跳转(同时高亮该行)
- 点击**"详情"操作按钮**跳转
- 路由设置
meta.hidden: true,不会在 Tab 栏生成持久标签
新增数据(src/api/purchase.js → getPurchaseOrderDetail):
allocSummary:统筹采购头信息(coordinator/coordinatorOrgType/totalQty/allocatedQty/pendingQty)warehouseAllocation:3仓库分配明细数组(含 priority/compatibleLevel/compatibleTag/allocQty/allocRatio/status 等)
数据来源: src/api/purchase.js → getPurchaseOrderDetail(orderNo)
🔢 备货配额优先级页(src/views/purchase/AllocPriority.vue)
新增于 2026-03-01,对应思维导图"备货配额优先级"
功能:
- 顶部说明栏:介绍配额规则作用,标注"仅查看"状态
- 接货组织视角 Tab(默认):
- 左侧4张接货组织卡片(T1-13%一般纳税人 / T2-9%一般纳税人 / T3-小规模3% / TX-免税组织),点击切换
- 右侧优先级列表:每条含优先级数字圆圈(1绿/2浅绿/3橙/4红)、供货组织标识、兼容性标签、详细税差说明、适配度百分比条
- 底部黄色提示:税差大时建议调整接货组织
- 全矩阵视图 Tab:
- 接货组织(行)× 供货组织(列)完整优先级数字矩阵
- 单元格颜色:绿=完全匹配/浅绿=较优/橙=部分兼容/红=低优先
- 底部图例
数据: 全部硬编码在组件内(RECEIVE_ORGS / SUPPLY_ORGS / PRIORITY_RULES / FULL_MATRIX),无需 API
📋 采购流程看板(src/views/purchase/PurchaseFlowDashboard.vue)
新增于 2026-03-01,对应思维导图"系统流程图"(图3)
功能:
- 顶部8步流程条:采购申请→审批中→已批准→供应商确认→在途运输→到货待分配→分配入库→财务结算
- 每个阶段卡片显示当前数量角标
- 点击卡片过滤下方订单,再次点击取消
- 颜色区分:申请/审批=红/橙,在途=蓝,入库/结算=绿
- 6个统计卡片:申请/审批 / 已批/确认 / 在途/到货 / 入库/结算 / 加急订单 / 已逾期
- 筛选栏:阶段标签(可关闭)+ 文字搜索(订单号/商品/供应商)+ "仅看加急"复选框
- 订单卡片列表:
- 左侧4px彩色竖条(对应当前阶段颜色)
- 订单号/加急标签/逾期标签/日期
- 商品·供应商·采购组织
- 数量/金额/仓库/票据类型
- 右侧:当前阶段图标标签 + 8个阶段进度点(已过绿/当前彩/未来灰)+ 到期日
- 加急订单红色边框;逾期订单半透明
新增数据(src/api/purchase.js):
FLOW_STAGES:8阶段定义(key/label/color/bg/icon),导出ORDER_STAGE_MAP:订单号→当前阶段的 mock 映射getFlowDashboard():返回 orders(含flowStage/isUrgent/overdue)+ stageCounts(各阶段计数)
⚖️ 备货配额优先级页(src/views/purchase/AllocPriority.vue)
新增于 2026-03-01
功能:
- 顶部说明栏:介绍配额优先级规则用途,标注"仅查看"状态
- 两个 Tab 视图切换:
- 接货组织视角 Tab(默认):
- 左侧 4 张接货组织类型卡片(T1/T2/T3/TX),点击切换
- 右侧展示该组织的完整优先级列表,每条包含:优先级圆圈(1绿→4红)、供货组织标识+名称+兼容标签、详细说明(税差计算)、适配度百分比进度条
- 底部黄色提示:税差大时建议提前调整接货组织
- 全矩阵视图 Tab:接货组织(行)× 供货组织(列)完整优先级矩阵,格子颜色绿/浅绿/橙/红对应匹配程度,底部图例说明
- 接货组织视角 Tab(默认):
核心数据(组件内定义):
RECEIVE_ORGS:T1/T2/T3/TX 四种接货组织(含颜色/税率/说明)SUPPLY_ORGS:S13/S9/S6/S3/SJD 五种供货票据类型PRIORITY_RULES:每种接货组织的 5 条优先级规则(含适配度分数/理由/兼容标签)FULL_MATRIX:接货×供货的完整优先级数字矩阵
路由: /purchase/alloc-priority,菜单出现在批次管理之后
📋 采购流程看板(src/views/purchase/PurchaseFlowDashboard.vue)
新增于 2026-03-01
功能:
- 顶部 8 步流程条:采购申请→审批中→已批准→供应商确认→在途运输→到货待分配→分配入库→财务结算,每个阶段有专属图标+颜色+数量角标,点击任意阶段过滤下方订单,再次点击取消筛选
- 6 张统计卡片:申请/审批、已批/确认、在途/到货、入库/结算(各阶段合计)+ 加急订单 + 已逾期
- 筛选栏:当前筛选阶段标签(可关闭)+ 文字搜索(订单号/商品/供应商)+ 仅看加急开关
- 订单卡片列表:
- 左侧竖色条对应当前阶段颜色
- 主体:订单号+加急/逾期标签、商品+供应商+组织、数量+金额+仓库+票据类型
- 右侧:当前阶段标签(彩色圆角框)+ 8个阶段进度点(已过绿/当前彩/未来灰)+ 到期日
- 加急订单红色边框,逾期订单半透明
新增数据(src/api/purchase.js):
FLOW_STAGES:8阶段定义(label/color/bg/icon),已导出ORDER_STAGE_MAP:订单号→当前阶段映射(mock)getFlowDashboard():返回所有订单(附 flowStage/isUrgent/overdue)+ stageCounts 统计
路由: /purchase/flow,菜单出现在备货配额优先级之后
🗂️ 物料管理模块
页面清单:
| 页面 | 路由 | 文件 | 状态 |
|---|---|---|---|
| 物料列表 | /material/list |
MaterialList.vue |
✅ 完成 |
| 分类管理 | /material/category |
MaterialCategory.vue |
✅ 完成 |
| 基础字典 | /material/dict |
MaterialDict.vue |
✅ 完成 |
物料列表页(src/views/material/MaterialList.vue)功能:
- 顶部筛选栏:关键字搜索、启用状态下拉、查询/重置按钮
- 动态快捷筛选条:根据当前选中分类中标记了「快捷筛选」的属性动态生成过滤控件(最多5个),控件类型按属性数据类型自适应:字典/枚举/布尔→下拉选择、文本→输入框、数字→范围输入、日期→日期范围选择器;含搜索/重置按钮
- 左侧分类树:多级树形结构、关键字过滤、点击分类自动过滤右侧列表;底部固定「未分类」节点
- 右侧操作栏:新增、批量归类、高级筛选(右侧抽屉)、导出下拉(导出选中 / 导出当前列表)、分类管理
- 右侧表格列:
- 勾选列 + 物料编码(独立列)+ 物料名称(独立列,fixed left)
- 分类动态属性列:根据左侧选中分类自动生成对应属性列(包含继承属性),未选分类时不显示额外列
- 自定义属性列:有自定义属性显示「查看」按钮(点击打开右侧抽屉),无则显示「无」
- 操作列:修改 / 删除(fixed right)
- 点击行弹出详情抽屉(右侧):
- 基本信息:物料编码、物料名称、归属分类(显示分类路径名称)
- 分类属性:该分类所有属性(含继承)及对应填写值,有值显示值,未填显示「--」
- 自定义属性:自定义属性列表,无则显示「暂无」
- ✅ 新增物料(简化流程,600px宽弹窗):
- 物料名称(必填)
- 物料编码(必填)
- 归属分类(树形下拉,必填,支持筛选)
- 选完分类后动态显示该分类的属性填写区(含继承属性,按数据类型渲染不同控件:字典/枚举→下拉、布尔→下拉是/否、数字→数字输入、日期→日期选择、文本→输入)
- 自定义属性(开关控制,启用后可动态增删属性行)
- ✅ 修改物料(与新增保持同步,600px宽弹窗):预填物料编码、名称、分类,分类属性值回填,自定义属性回填
- ✅ 删除物料:二次确认弹窗(el-popconfirm),删除后刷新列表
- ✅ 自定义属性查看:点击表格「查看」按钮,右侧抽屉展示全部自定义属性(布尔显示是/否)
- ✅ 导出:下拉菜单提供「导出选中」(至少勾选一条)和「导出当前列表」,导出为本地 JSON 文件
- 分页:支持10/20/50/100条切换,显示总数
- 移动端适配:弹窗全屏宽带边距,顶部移动端 header(返回按钮+保存按钮),表单 label-position top
数据来源: src/api/material.js
getMaterialList(params, quickFilterValues, quickFilterAttrs)— 列表查询,支持关键字、分类、快捷属性筛选(数字范围、日期范围、文本模糊、枚举精确匹配)getMaterialDetail(id)— 详情addMaterial(data)— 新增,存储name、code、categoryId、categoryAttrs、hasCustomAttributes、customAttributesupdateMaterial(id, data)— 修改deleteMaterial(id)— 删除DICT_MAP— 导出的字典数据映射,供动态属性下拉选项使用- 物料数据结构:
{ name, code, categoryId, categoryAttrs: { [attrId]: value }, hasCustomAttributes, customAttributes: [{ name, type, value }] } - 分类数据结构:
{ id, label, children, attributes: [{ id, name, source, dictKey|dataType, enumOptions, quickFilter }] }
分类管理页(src/views/material/MaterialCategory.vue)功能:
- 树形结构展示全部分类,树节点显示属性数量角标
- ✅ 新增/编辑分类(680px弹窗):
- 分类名称输入
- 继承属性区(子分类可见):只读显示从父级继承的属性,带锁图标和来源分类标注
- 自有属性区:「物料名称」作为系统默认属性(不可删除),用户可继续添加属性:
- 从字典选择:从预置字典列表选择(2列网格展示,已添加的显示「已添加」),自动关联字典选项
- 自定义属性:自定义名称 + 数据类型(text/number/date/enum),枚举类型可编辑选项列表
- 每个属性可勾选「快捷筛选」(最多5个,超出提示)
- ✅ 删除分类:弹窗显示影响范围,三种处理方式(归入未分类/取消/转移)
- ✅ 导出分类:顶部按钮,导出完整分类树为 JSON 文件(与「新增根分类」并排)
- 分类 API:
addCategory/updateCategory/deleteCategory/deleteCategoryAll/getCategoryTreeInfo - 移动端适配:弹窗
calc(100vw - 24px)宽,88vh最大高度,圆角,内容可滚动
字典管理页(src/views/material/MaterialDict.vue)功能(已对接真实接口):
- 页面/菜单标题改为"字典管理"
- 左侧字典列表:调
POST /api/dict/listed(body{ dict_code: '' })获取字典列表,平铺展示;鼠标悬停显示"+"按钮快速新增子值 - 右侧条目表格:展示选中字典的
children[],支持树形展开(row-key="dict_code");第一层条目显示"添加 / 编辑",子层只显示"编辑" - ✅ 新增字典:调
POST /api/dict/add(带classify_code: 'RY9A77ABKR'、value、label、desc) - ✅ 新增字典值:调
POST /api/dict/add(带parent_code= 父字典dict_code、value、label) - ✅ 编辑字典/值:弹窗表单,调
POST /api/dict/modify(带dict_code、classify_code、value、label、desc) - 移动端适配:
isMobile(window.innerWidth ≤ 600)控制左右面板切换;移动端点击字典条目进入详情,顶部左箭头返回列表;弹窗宽度92vw,class="dict-dialog"圆角滚动样式
🏷️ 登录页便签(NoticeBoard)更新记录
当前方案(便签条式):
- 两条横向便签图片(
/icons/add.png黄色 //icons/update.png蓝色),图片分辨率 3345×1409 - 左侧深色标签区域竖排文字("新增功能" / "更新内容"),点击整条展开/折叠(width 动画)
- 折叠时:只露出左侧约 32px 深色标签部分,竖排文字仍可见
- 展开时:完整便签条滑出,显示 2 条更新内容(圆点 + 文字 + 日期)
position: absolute(不用 fixed),避免父级overflow: hidden裁剪- 移动端隐藏(
@media max-width: 640px)
文件: src/views/login/components/NoticeBoard.vue
📊 库存总览页(src/views/inventory/InventoryOverview.vue)
功能(两个 Tab):
总览 Tab(原有):
- 顶部4张统计卡片:在管仓库数 / 在库总量 / 库存总金额 / 库存预警数
- 各仓库库存占比饼图(ECharts)
- 各品类库存占比饼图(ECharts)
- 仓库 × 品类 明细矩阵表(颜色热力图,值越大越深蓝)
- 近30天各仓库库存趋势折线图(ECharts,面积渐变)
🆕 商品库存分布 Tab(2026-03-01 新增):
- 品类筛选下拉 + 图例说明(充足绿/低于安全库存橙/已断货红/在途蓝)
- 分组柱状图:横轴=商品名称,每组5根柱=5个仓库,悬停显示具体数量
- 商品 × 仓库 明细矩阵表:
- 行:商品(含品类列)
- 列:5个仓库 + 在库合计 + 在途合计
- 单元格颜色底色(充足/低/断货)+ 3px彩色底部条
- 在途数量显示为蓝色
+N途
- 点击品类筛选后同步刷新柱状图和矩阵
新增数据(src/api/inventory.js):
mockProductMatrix:8商品 × 5仓库,每条含 qty/safeQty/inTransitPRODUCTS:商品列表常量getProductStockMatrix():获取商品矩阵 API
📌 2026-03-01 新增功能汇总
| # | 功能 | 文件 | 说明 |
|---|---|---|---|
| 1 | 采购订单详情加分仓分配 | PurchaseOrderDetail.vue |
新增Tab+右侧汇总卡,统筹采购多仓分配可视化 |
| 2 | 批次管理加7阶段流程 | BatchManage.vue |
抽屉加流程条+分仓分配卡片 |
| 3 | 供应商抽屉加发票兼容矩阵 | SupplierManage.vue |
T1/T2/T3/TX × 票据类型兼容性自动分析 |
| 4 | 库存总览加商品多仓对比 | InventoryOverview.vue |
新增"商品库存分布"Tab,分组柱状图+矩阵表 |
| 5 | 备货配额优先级配置页 | AllocPriority.vue |
新页面,接货组织视角+全矩阵视图 |
| 6 | 采购流程看板 | PurchaseFlowDashboard.vue |
新页面,8阶段流程+卡片列表+多维筛选 |
📌 2026-03-05 新增功能汇总
| # | 功能 | 文件 | 说明 |
|---|---|---|---|
| 1 | 物料分类管理页 | MaterialCategory.vue |
树形结构,统一删除弹窗(子分类+物料影响范围提示,支持归未分类/转移/取消) |
| 2 | 基础字典管理页 | MaterialDict.vue |
左侧10个字典分类,右侧表格行内编辑,支持增删改 |
| 3 | 字典 CRUD API | material.js |
getDictItems/addDictItem/updateDictItem/deleteDictItem + dictMeta 元信息 |
| 4 | 分类级联删除 API | material.js |
deleteCategoryAll(级联删子树)/ getCategoryTreeInfo(查影响范围) |
| 5 | 未分类节点 | material.js + 两个Vue文件 |
分类树底部固定「未分类」节点(id: '__unclassified__'),物料列表可单独筛选,分类管理页无操作按钮 |
📌 2026-03-07 ~ 2026-03-08 物料管理模块重大更新
新增页面
| 页面 | 路由 | 文件 | 说明 |
|---|---|---|---|
| 场景管理 | /material/scene |
SceneManage.vue |
场景 CRUD,卡片列表,属性管理,关联分类展示 |
| 特征管理 | /material/feature |
FeatureManage.vue |
占位页(开发中) |
| 模板管理 | /material/template |
TemplateManage.vue |
占位页(开发中) |
| 票据管理 | /base/bill |
BillManage.vue |
票据 CRUD,对接 /api/bill/listed、/bill/add、/bill/modify |
物料分类与场景关系重构
设计变更:
- 一个物料属于一个分类
- 一个分类对应一个场景(单选,
sceneId),子分类自动继承父分类的场景 - 场景与分类的关系:一个场景可被多个分类关联,但一个分类只能关联一个场景
- 排他性:根分类一旦关联某场景,该场景对其他根分类置灰不可选
MaterialCategory.vue 改动:
- 新增"所属场景"单选下拉(仅根分类可选,子分类显示继承信息)
- 字典选择从网格按钮改为
el-tree树形选择器,支持搜索,已添加的字典标记禁用 - 弹窗尺寸适配:PC 640px,Pad 88vw,移动端全宽
availableSceneOptions计算属性实现场景排他性过滤
MaterialList.vue 改动:
- 物料继承分类关联场景的属性(
collectSceneAttrs) dynamicCategoryAttrs合并分类属性 + 场景属性,去重展示
物料分类 API 对接(2026-03-08)
接口: /api/category/listed、/api/category/add、/api/category/modify、/api/category/remove
字段映射:
| API 字段 | 前端字段 | 说明 |
|---|---|---|
category_code |
id |
分类唯一标识 |
name |
label |
分类名称 |
scene_code |
sceneId |
关联场景 code |
parent_code |
parentId |
父分类 code(新增子分类时传) |
fields |
attributes |
JSON 序列化的属性集合(字典引用 + 自定义属性) |
改动文件:
MaterialCategory.vue:移除所有api/material.js本地分类函数依赖,新增loadCategoryTree()从/api/category/listed加载,提交时调用真实 API,删除调用/api/category/remove,加载状态v-loadingMaterialList.vue:同步移除旧分类函数依赖,新增loadCategoryTreeFromApi(),侧边栏快速操作(新增/重命名/删除)全部换成真实 API
场景管理 API 对接(2026-03-08)
接口: /api/scene/listed、/api/scene/add、/api/scene/modify、/api/scene/remove
字段映射:
| API 字段 | 前端字段 | 说明 |
|---|---|---|
scene_code |
id |
场景唯一标识 |
name |
name |
场景名称 |
desc |
desc |
场景描述 |
fields |
attributes + color |
JSON 序列化:{ attributes: [...], color: '#xxx' } |
is_valid |
— | 状态(1启用/0禁用) |
改动文件:
SceneManage.vue:移除 localStorage 持久化(showtime_scenes),改为调用真实 API;新增loadCategoryTree()从/api/category/listed加载分类树用于反向查询关联分类MaterialCategory.vue:场景选项从 localStorage 改为调用/api/scene/listed加载,openForm改为 asyncMaterialList.vue:移除loadScenesFromStorage(),新增sceneCache+loadSceneCache()从/api/scene/listed加载,onMounted时同步加载
其他基础数据改动
| # | 页面 | 改动 |
|---|---|---|
| 1 | StaffManage.vue |
表格列宽调整:员工姓名固定 120px,入职日期 130px 并格式化为 YYYY-MM-DD,账户/部门/职务/手机号适当加宽 |
| 2 | AccSubjectManage.vue |
修复 TypeError: properties.map is not a function(properties.value.map 改正) |
路由更新(router/index.js)
/material/scene → SceneManage.vue(场景管理)
/material/feature → FeatureManage.vue(特征管理,占位)
/material/template → TemplateManage.vue(模板管理,占位)
/base/bill → BillManage.vue(票据管理)
📌 2026-03-13 功能更新
| # | 功能 | 文件 | 说明 |
|---|---|---|---|
| 1 | 分类管理改为左右双栏布局 | MaterialCategory.vue |
桌面端左侧分类树 + 右侧内联表单面板,移动端保留弹窗;右侧空状态占位图,面板顶部带关闭按钮 |
| 2 | 分类拖拽换父级 | MaterialCategory.vue |
拖动分类节点到其他分类可改变父级关系,自动调用 category/modify(只传 category_code + parent_code) |
| 3 | 删除分类简化 | MaterialCategory.vue |
去掉"归入未分类/转移/取消"三选一弹窗,改为简洁确认框,子分类一并删除 |
| 4 | 移除"未分类"节点 | MaterialCategory.vue + MaterialList.vue |
分类树和物料列表侧边栏均不再显示"未分类"固定节点,相关样式一并清除 |
| 5 | 部门管理责任人/分管领导改下拉 | DeptManage.vue |
从文本输入改为关联 /api/user/listed 的人员下拉,选择后自动填充对应电话,提交时同步写入手机号 |
| 6 | 字典排序改为↑↓按钮 | MaterialDict.vue |
废弃 Sortable.js 拖拽方案(与 el-table 树形展开存在根本兼容问题),顶层字典行和子字典行统一使用↑↓移动按钮;到顶/到底时对应按钮自动禁用 |
📌 2026-03-16 功能更新 — 全局序号(pos)字段补全
新增 pos 字段的模块
| # | 模块 | 文件 | 改动内容 |
|---|---|---|---|
| 1 | 费用管理 | ExpenseItemManage.vue |
表单标签"排序"→"序号";表格新增"序号"列(灰色小字) |
| 2 | 会计管理 | AccSubjectManage.vue |
表单标签"排序"→"序号";表格新增"序号"列 |
| 3 | 银行账户 | BankAccountManage.vue |
表单标签"排序"→"序号";表格新增"序号"列 |
| 4 | 场景管理 | SceneManage.vue |
form 加 pos 字段;弹窗加"序号"输入框(el-input-number);新增/修改接口带 pos;场景卡片名称旁显示序号 |
| 5 | 分类管理 | MaterialCategory.vue |
form 加 pos 字段;弹窗加"序号"输入框;新增/修改接口带 pos;mapApiNode 保留 pos;树节点显示序号(灰色小字) |
| 6 | 物料管理 | MaterialList.vue |
addForm/editForm 加 pos 字段;新增/编辑弹窗加"序号"输入框;物料列表表格新增"序号"列;handleEdit 赋值 pos |
统一规范
- 表单控件:
el-input-number,最小值 0,整数,默认值 0 - 列表显示:
font-size:12px; color:#c0c4cc(灰色小字,宽度 65px,居中) - 接口传值:统一转为字符串
String(form.pos)发送;编辑回填时用Number(row.pos) || 0 - 标签统一:全部使用"序号"(不再使用"排序")
📌 2026-03-17 功能更新 — 分类管理优化 & Bug 修复
MaterialCategory.vue
| # | 改动 | 说明 |
|---|---|---|
| 1 | 分类 code 层级路径显示 | 树节点名称后的括号内容改为按层级拼接 code 字段(如 1、6-3),而非 category_code。mapApiNode 新增 parentPath 参数,递归构建 codePath,子节点路径为 父code-自身code |
| 2 | 修复 map 传 index 的 bug | 顶层调用从 list.map(mapApiNode) 改为 list.map(n => mapApiNode(n)),避免数组 index 被误传为 parentPath,导致显示 1-X4IOMAFPDU 这类错误 |
| 3 | 修复 category_code 传错 | mapApiNode 的 id 字段还原使用 n.category_code(用于 API 请求),codePath 单独使用 n.code 字段。之前错误将 id 改为 n.code,导致编辑时 category_code 发送的是 "6" 这种序号值而非真实 code |
| 4 | 移除场景排他性过滤 | 删除 availableSceneOptions 中"已被其他分类占用的场景置灰"逻辑,修复同一场景无法被多个分类选用的 bug。正确逻辑:一个场景可以有多个分类,一个分类只属于一个场景 |
| 5 | 树节点显示所属场景名 | 根分类名称后新增场景小标签,颜色与场景标识色一致(带透明背景+边框),子分类不显示(继承父分类,保持简洁) |
| 6 | console.log 改为打印原始数据 | loadCategoryTree 中的打印移到 map 处理之前,打印服务器原始 res.data,方便调试对比 |
SceneManage.vue
| # | 改动 | 说明 |
|---|---|---|
| 1 | console.log 改为打印原始数据 | loadScenes 中的打印移到 map 处理之前,打印服务器原始 res.data |
待对接(等服务器)
- 价格本保存接口:采用整体 JSON 方案,服务器需提供保存/读取接口,
content字段类型longtext,前端负责 Univer 工作簿序列化/反序列化
📌 2026-03-20 ~ 2026-03-21 功能更新
全局清理:移除所有弹窗中的"序号"字段
| # | 文件 | 说明 |
|---|---|---|
| 1 | BankAccountManage.vue |
新增/编辑弹窗去掉序号输入框 |
| 2 | MaterialList.vue |
新增/编辑弹窗去掉序号输入框 |
| 3 | MaterialCategory.vue |
新增/编辑弹窗去掉序号输入框 |
| 4 | PriceBook.vue |
保存弹窗去掉序号输入框 |
| 5 | OrgManage.vue |
新增/编辑弹窗去掉序号输入框 |
| 6 | DeptManage.vue |
新增/编辑弹窗去掉序号输入框 |
| 7 | WarehouseManage.vue |
新增/编辑弹窗去掉序号输入框 |
| 8 | ExpenseItemManage.vue |
新增/编辑弹窗去掉序号输入框 |
| 9 | AccSubjectManage.vue |
新增/编辑弹窗去掉序号输入框 |
| 10 | BillManage.vue |
新增/编辑弹窗去掉序号输入框 |
统一规范:序号改由拖拽排序自动维护,新建默认
pos: 0
组织管理(OrgManage.vue)
| # | 改动 | 说明 |
|---|---|---|
| 1 | 进出项显示逻辑 | ≤6条:直接展开显示,蓝色(进)/橙色(出)颜色区分;>6条:折叠+Tooltip悬停 |
| 2 | 移除"进项"/"出项"文字标签 | 依靠颜色区分,每条后面追加(进)或(出)文字后缀增强可读性 |
| 3 | 新增"开票额度(月)"字段 | 弹窗中替换原序号位置,数字输入,绑定bill_invoice_limit字段 |
| 4 | 列表显示月额度标签 | 树节点显示橘色¥X/月标签(bill_invoice_limit != null时显示) |
| 5 | API字段对齐 | 新增/修改接口均携带bill_invoice_limit参数 |
部门管理(DeptManage.vue)
| # | 改动 | 说明 |
|---|---|---|
| 1 | 表单布局调整 | 责任人/责任人电话固定一行,分管领导/领导电话固定一行,不受条件渲染影响 |
物料管理菜单重命名 & 路由精简
| # | 改动 | 说明 |
|---|---|---|
| 1 | "分类管理"→"物料分类" | 路由title、页面标题、提示文字全部同步 |
| 2 | "场景管理"→"销售场景" | 路由title、页面标题、提示文字全部同步 |
| 3 | 删除"模板管理"路由 | /material/template 及对应菜单条目移除 |
物料分类(MaterialCategory.vue)属性选择改版
| # | 改动 | 说明 |
|---|---|---|
| 1 | "自定义属性"→"从特征选择" | 添加属性弹窗去掉自定义属性Tab,新增"从特征选择"Tab,调用/api/field/listed加载 |
| 2 | 特征类型角标 | 列表中每条特征显示类型徽标(带颜色/背景),便于区分 |
| 3 | 多选支持 | 字典属性:el-tree show-checkbox;特征属性:行内el-checkbox,支持同时选多条批量添加 |
| 4 | 三段式弹窗布局 | 顶部固定"分类名称",中部可滚动"属性区",底部预留等高空白区 |
销售场景(SceneManage.vue)属性选择改版
| # | 改动 | 说明 |
|---|---|---|
| 1 | "自定义属性"→"从特征选择" | 同物料分类,新增"从特征选择"Tab,带类型徽标显示 |
| 2 | 多选支持 | 字典和特征均支持多选批量添加 |
| 3 | 移除场景特定提示文字 | 去掉"暂无分类关联此场景,请在「物料分类」中设置",改为简洁通用提示 |
特征管理(FeatureManage.vue)重大重构
新增特征类型:
image(图片)、file(文件)、link(链接)multiselect(下拉多选,区别于select下拉单选)- 修复
Select被误解析为HTML元素的问题,改用Filter图标
特征分类系统(全新):
| # | 改动 | 说明 |
|---|---|---|
| 1 | 左侧分类树 | 将类型筛选栏替换为el-tree层级分类树 |
| 2 | 分类CRUD | 支持新建根分类/子分类、编辑、删除,调用/api/field/classify/*接口 |
| 3 | 右侧按分类加载 | 点击分类后右侧加载该分类下的特征,/api/field/listed传classify_code |
| 4 | 新建特征需先选分类 | "新增特征"按钮在未选分类时禁用 |
| 5 | 拖拽排序保留 | 特征列表拖拽排序在分类上下文内生效,调用/api/field/batch_pos |
| 6 | 服务器嵌套树修复 | 服务器返回已嵌套的树结构,修复buildClassifyTree逻辑(原来用children:[]覆盖服务器数据),改为递归保留服务器children |
| 7 | 侧栏UI优化 | 侧栏宽260px;移除_count数量角标;添加橘色警告提示:"点击分类查看特征;行内+可添加子分类";"暂无分类"文字改橘色警告色、去掉图标 |
| 8 | 新接口集成 | /api/field/classify/add(根/子)、/api/field/classify/modify、/api/field/classify/remove、/api/field/classify/listed |
配置表(PriceBook.vue)
| # | 改动 | 说明 |
|---|---|---|
| 1 | 加载遮罩 | Univer编辑器初始化期间显示全屏遮罩"编辑器加载中,请稍候...",避免用户以为页面卡住 |
| 2 | "保存到服务器"→"保存" | 按钮文案简化 |
| 3 | 左侧列表拖拽排序 | 引入Sortable.js,整行可拖,拖后调用/api/excel/batch_pos保存顺序 |
当前路由结构(更新)
/material (物料管理)
├── /material/list 物料列表
├── /material/category 物料分类(原"分类管理")
├── /material/scene 销售场景(原"场景管理")
└── /material/feature 特征管理(已实现完整分类+特征CRUD)
// 模板管理已移除
📌 2026-03-22 ~ 2026-03-23 页签管理 & 物料分类重大重构
TabManage.vue(页签管理)
| # | 改动 | 说明 |
|---|---|---|
| 1 | 字段列表→5列卡片网格 | 字段展示方式由纵向列表改为 5 列卡片网格,卡片带 box-shadow,字段类型徽标改为蓝色 |
| 2 | 字段编辑改为右侧抽屉 | 从内联展开改为右侧滑出抽屉,点击字段卡片打开,关闭按钮为红色"关闭"文字 |
| 3 | 添加字段即开抽屉 | 点击"添加字段"后新字段立即在抽屉中打开编辑 |
| 4 | 整个卡片可拖拽 | 拖动范围从小拖手改为整个字段卡片 |
| 5 | 描述?图标下移 |
从卡片头部移至卡片底部,解决与操作按钮的遮挡问题 |
| 6 | 主保存按钮禁用逻辑 | 字段抽屉打开时,主对话框"确认保存"按钮灰色不可点,并显示提示文字 |
MaterialCategory.vue(物料分类)
页签系统重构
| # | 改动 | 说明 |
|---|---|---|
| 1 | 移除"添加规则" | 删除字段层面的规则管理,改为页签层面的可见性规则 |
| 2 | "添加选项卡"拆为两个按钮 | 新增"导入页签"(引用 TabManage 模板)和"添加页签"(自定义) |
| 3 | 导入页签 | 从 TabManage 模板导入,存储引用 refId、覆盖值 overrides、快照 snapshot;字段以卡片输入模式显示 |
| 4 | 默认值提示 + 还原 | 导入页签的每个字段下方始终显示模板默认值;若已覆盖则显示"还原"按钮,点击弹确认框后回滚 |
| 5 | 导入页签空组也显示 | 即便分组内无字段也显示分组,显示"该分组暂无字段" |
| 6 | 页签药丸 UI | 导入页签:蓝色↓图标,隐藏"复制到"和"改名",仅留"删除";自建页签:隐藏"复制到",保留"改名"和"删除" |
| 7 | 导入弹窗优化 | 已导入模板排底部(加分隔线),添加模板数量汇总,超长列表有滚动条 |
自建页签
| # | 改动 | 说明 |
|---|---|---|
| 1 | 字段管理改为右侧抽屉 | 与 TabManage 一致,抽屉内容完整(类型/描述/默认值/字典等) |
| 2 | 实际使用模式 | 自建字段以卡片输入形式展示(与导入页签一致)而非定义模式 |
| 3 | 统一字段卡片 UI | 所有字段(自建/导入/字典/特征)使用同一套 imported-field-item 卡片样式 |
| 4 | 移除类型徽标和分区标题 | 删除"文本框"/"字典"/"特征"等徽标,删除"字典/特征属性"分区标题 |
| 5 | 移除"筛选"复选框 | 字段卡片不再显示快捷筛选开关 |
| 6 | 删除按钮默认隐藏 | 自建/旧字段卡片的"删除"按钮默认隐藏,悬停时才出现 |
| 7 | 主确认按钮禁用逻辑 | 同 TabManage,抽屉打开时主"确认"按钮灰色并显示提示 |
多选字段区分
| # | 改动 | 说明 |
|---|---|---|
| 1 | multiselect 保持下拉多选 |
使用 el-select multiple |
| 2 | checkbox 改为内联多选框 |
使用 el-checkbox-group,选项横向排列,类似图3效果 |
页签可见性规则(新功能)
| # | 改动 | 说明 |
|---|---|---|
| 1 | 新增"规则"入口按钮 | 页签导航栏添加"规则"按钮,打开规则配置弹窗 |
| 2 | 规则弹窗 | 左侧规则列表 + 右侧编辑区;规则定义:类型(显示/隐藏)、目标页签(多选)、逻辑(AND/OR)、条件(字段+运算符+值) |
| 3 | 帮助说明 | 弹窗标题旁?按钮显示规则逻辑说明 Popover |
| 4 | 当前页签提示 | 规则列表上方显示当前打开的页签名;添加规则时自动预选当前非继承页签为目标 |
| 5 | 数据持久化 | form.tabRules 随分类数据一并通过 /api/category/modify 保存 |
📌 2026-03-24 字段边界感 & 自建页签分组
| # | 改动 | 文件 | 说明 |
|---|---|---|---|
| 1 | 多选框/下拉加边框容器 | MaterialCategory.vue |
el-checkbox-group 添加 border: 1px solid #dcdfe6 / border-radius / padding,与 el-input、el-select 统一边界感 |
| 2 | 自建页签支持分组 | MaterialCategory.vue |
数据结构从 fields[] 升级为 groups[{ id, name, fields[] }];新建页签自动创建"默认分组" |
| 3 | 分组管理 UI | MaterialCategory.vue |
每个分组头部有"改名"/"+ 字段"/"删除组"三个操作;空分组显示"该分组暂无字段" |
| 4 | 底部按钮更新 | MaterialCategory.vue |
新增"添加分组"按钮;"添加字段"默认归入最后一组;保留"从字典/特征添加" |
| 5 | 旧数据兼容迁移 | MaterialCategory.vue |
openForm 时自动将旧 fields[] 格式迁移到 groups[0] 内,无需手动处理 |
| 6 | 字段计数更新 | MaterialCategory.vue |
ownTabFieldCount 改为累计所有分组内字段数;allFieldGroups 改为遍历 groups 收集字段 |
📌 2026-03-25 ~ 2026-03-26 页签管理重大功能 & 分类继承体系升级
TabManage.vue
| # | 改动 | 说明 |
|---|---|---|
| 1 | 新增「计算字段」类型 | 字段类型下拉新增 formula(计算字段),选择后出现公式编辑器区域 |
| 2 | 公式编辑器 UI | 「引用字段」区:自动列出同页签内所有 number 类型字段,点击按钮插入 {fieldId} 引用;「公式」输入框:支持 + - * / ( ) 四则运算;「预览」区:公式翻译为 [字段名] 可读形式,点「用默认值测试」按钮用字段实际 defaultValue 计算结果 |
| 3 | 公式插入光标定位 | 点击引用字段按钮时,将 {fieldId} 插入公式输入框当前光标位置(使用 selectionStart/End 精确定位) |
| 4 | hasNoDefault 不校验计算字段 |
type === 'formula' 时跳过默认值校验,计算字段无需默认值 |
| 5 | 字段卡片显示 | 计算字段类型标签显示橙色斜体「计算字段」,卡片底部显示 ƒ(x) 徽标 |
| 6 | 修复 form.groups → form.value.groups |
formulaRefFields、formulaDisplayExpr、testFormulaPreview 三处函数中 ref 未用 .value 访问导致崩溃,已修复 |
| 7 | 测试预览用真实默认值 | testFormulaPreview 改为用字段实际 defaultValue 计算,而非之前的假值 1,2,3... |
| 8 | 保存前变更确认 | 编辑已有模板保存时,检测字段类型变更和字段删除;若有则弹确认框列出具体变更(「X 类型:文本 → 数字」/ 「X 已删除」),用户确认后才保存,取消则中止 |
| 9 | saveTab 改为 async |
保存函数改为 async 以支持 await ElMessageBox.confirm |
| 10 | @change 副作用包 nextTick |
类型下拉 @change 中修改 defaultValue/formula 的副作用延迟到 nextTick,消除 El Select 内部 onMounted 生命周期警告 |
| 11 | FIELD_TYPE_MAP 更新 |
新增 formula: '计算字段' 映射 |
MaterialCategory.vue — 模板变更检测全面升级
| # | 改动 | 说明 |
|---|---|---|
| 1 | 所有变更字段都打 ⚑ | 之前只有有 override 的字段才打 ⚑;现在无论有无 override,只要模板默认值变了就打 ⚑,加入 tplChangedFields,统一弹 alert 通知用户 |
| 2 | snapshotTypes 类型快照 |
导入模板时同步记录每个字段的类型(tab.snapshotTypes[fieldId] = type);checkAndSyncTemplateChanges 检测类型变更 |
| 3 | 类型变更处理 | 检测到字段类型变更时:清除旧 override(值与新类型不兼容),弹右上角 ElNotification 列出「页签名-字段名:旧类型 → 新类型」,更新 snapshotTypes |
| 4 | 默认值变更分两种处理 | 有 override:changedFields.add + overrideConflicts++,snapshot 不更新(持续提示直到用户处理并保存);无 override:自动更新 snapshot,autoUpdated++ |
| 5 | alert 合并显示 | 统一弹一个 ElMessageBox.alert,区分「有自定义值字段已标注 ⚑ 请处理」和「字段默认值已自动同步无需操作」两种描述 |
| 6 | silentSaveSnapshot 只保存无冲突字段 | 有 override 冲突的字段 snapshot 不动,只保存自动更新的 snapshot;下次打开仍能检测到冲突,持续提示 |
| 7 | 「查看更新」按钮(无 override) | hasTplChanged && !hasOverride 时显示「查看更新」按钮;点击弹 ElMessageBox.alert 告知「默认值已更新为 X,已自动同步」,确认后清 ⚑ |
| 8 | 「还原至新默认」两选项保留 | hasTplChanged && hasOverride 时显示「还原至新默认」;弹确认框两个选择:「同步当前值」(清 override)/ 「仅更新默认提示」(只清 ⚑ 保留 override) |
| 9 | 快照在模板同步后才存 | openForm 的 nextTick 改为先跑 checkAndSyncTemplateChanges,再存 formInitialSnapshot;避免自动修改 form.tabs 后快照与实际状态不符,误触 dirty 警告 |
MaterialCategory.vue — 继承模板页签可编辑(层层传递默认值)
| # | 改动 | 说明 |
|---|---|---|
| 1 | 继承 imported tab 改为可编辑 | 之前继承引入模板页签是灰色锁定不可改;现在改为蓝色 bar(和继承自定义页签一致),每个字段可为本分类设置默认值 |
| 2 | getParentEffectiveDefault |
新增函数:返回父类有效默认值(父类 override → 模板 defaultValue 优先级),作为子类字段的「父类默认」基线 |
| 3 | formatParentEffectiveDefault |
格式化父类有效默认值用于显示(null/空/数组等特殊情况处理) |
| 4 | 子类 override 存储 | 复用 inherited-extended 数据结构(ensureInhExtended / getInhOverride / setInhOverride / hasInhOverride���,存在 form.tabs[tabCode].overrides 中 |
| 5 | 字段底部显示「父类默认」 | 替代原来的「默认」标签,显示父类有效默认值;子类有 override 时显示「还原」按钮,点击清除 override |
| 6 | collectInheritedData 合并修复 |
遇到 inherited-extended 覆盖已有 imported tab 时,保留 imported 类型,合并 overrides(子类 override 覆盖父类 override),而非直接覆盖整个 tab;确保孙类也能识别为 imported 类型并可继续编辑 |
| 7 | 三级继承示例 | A(根)引入模板设默认「valueA」→ B 继承设「valueB」→ C 继承时看到「父类默认: valueB」,可再设「valueC」;getParentEffectiveDefault 取最近一层有效值 |
MaterialCategory.vue — 未保存变更警告
| # | 改动 | 说明 |
|---|---|---|
| 1 | formInitialSnapshot |
打开表单后(模板同步完成后)存 JSON.stringify({ name, tabs, fieldMap, rules }) 快照 |
| 2 | isFormDirty computed |
对比当前 form 与快照,不相同则为 dirty |
| 3 | guardDirty(action) |
切换分类前先检查 isFormDirty;若 dirty,弹确认框「放弃改动并继续 / 留下来保存」;用户确认后执行 action,取消则留在当前分类 |
| 4 | 三入口统一拦截 | handleAddRoot、handleAddChild、handleEdit 均套 guardDirty |
| 5 | 保存后跳过检查 | submitForm 成功后清空 formInitialSnapshot.value = '',使后续内部调用 handleEdit(refreshed) 时 isFormDirty 返回 false,不误触警告 |
MaterialCategory.vue — 计算字段运行时显示
| # | 改动 | 说明 |
|---|---|---|
| 1 | evalFormula |
新增公式安全求值函数:将 {fieldId} 替换为实际值,用白名单正则校验,Function 构造器执行,处理除零和语法错误 |
| 2 | buildImportedValueMap |
收集引入页签内各字段当前值(override 优先,否则 defaultValue)构建 valueMap |
| 3 | formula 字段只读展示 | 引入页签中 type === 'formula' 的字段不显示输入框,显示黄底带 ƒ 图标的只读框,值为实时计算结果 |
| 4 | getFormulaValue |
辅助函数:字段 type 为 formula 时调用 evalFormula 计算并返回结果或「公式错误」 |
| 5 | CSS 样式 | .ifi-formula-result(黄底虚线边框)、.ifi-formula-icon(橙色 ƒ 字)、.ifi-formula-val(等宽字体数字) |
模板数据 JSON 格式(服务端接口参考)
[
{
"id": "tab_1774361234567",
"name": "基本信息",
"desc": "模板说明",
"createdAt": 1774361234567,
"groups": [
{
"id": "g_1774361234568",
"name": "规格",
"fields": [
{
"id": "f_1774361234569_abc",
"label": "颜色",
"source": "custom",
"type": "select",
"optionsRaw": "红,绿,蓝",
"defaultValue": "红",
"rules": []
},
{
"id": "f_1774361234571_ghi",
"label": "总重",
"source": "custom",
"type": "formula",
"formula": "{f_xxx} + {f_yyy}",
"defaultValue": null,
"rules": []
},
{
"id": "f_1774361234572_jkl",
"label": "产地",
"source": "dict",
"dictKey": "origin_dict_key",
"defaultValue": "cn",
"rules": []
}
]
}
]
}
]
source 为 "custom" 时,type 可为:text / number / date / select / multiselect / checkbox / textarea / formula;source 为 "dict" 时,有 dictKey 字段。formula 类型字段额外有 formula 字符串,用 {fieldId} 引用同页签内其他数字字段。
📌 待办
- 第二行第二列内容待定
- ChordChart 改用弹力布局(解决标签偏移)
- 接口联调(替换所有 mock 数据)
订单号点击跳转具体订单详情页✅ 已完成(含整行点击跳转)采购订单详情加分仓分配✅ 2026-03-01 完成批次管理加7阶段流程追踪✅ 2026-03-01 完成供应商发票兼容性矩阵✅ 2026-03-01 完成库存总览商品多仓对比视图✅ 2026-03-01 完成备货配额优先级配置页✅ 2026-03-01 完成采购流程看板✅ 2026-03-01 完成- 移动端底部导航同步新增菜单(备货配额优先级 / 采购流程看板)
- 库存/销售/财务各子页面内容实现
物料分类管理(增删改查)✅ 2026-03-05 完成基础字典管理(增删改查)✅ 2026-03-05 完成场景管理页(新增/编辑/删除+属性管理)✅ 2026-03-07 完成分类关联场景(单选,子分类继承,排他性)✅ 2026-03-07 完成物料继承场景属性✅ 2026-03-07 完成票据管理页(列表+新增+编辑)✅ 2026-03-08 完成物料分类 CRUD 对接真实 API✅ 2026-03-08 完成场景管理 CRUD 对接真实 API✅ 2026-03-08 完成- 特征管理页实现
- 模板管理页实现
- 物料管理与采购/库存/销售模块数据联动
- 价格本计算引擎(预估成本/定价成本/卖价体系)
- ERP 数据接入替换 mock 数据
📱 移动端底部导航更新
src/layouts/index.vue 中 bottomNavItems 硬编码,新增菜单后需手动同步。
当前5项(与路由一一对应):
| 顺序 | name | title | icon |
|---|---|---|---|
| 1 | Root |
首页 | material-symbols:home |
| 2 | Purchase |
采购 | mdi:cart-outline |
| 3 | Inventory |
库存 | mdi:warehouse |
| 4 | Sales |
销售 | mdi:chart-line |
| 5 | Finance |
财务 | mdi:currency-cny |
注意: handleBottomNavClick 复用 handleMainMenuClick,通用逻辑无需单独维护。后续新增主菜单时记得同步更新 bottomNavItems。
Mapbox 配置指南
配置文件位置: src/views/login/components/MapboxBackground.vue(第17-78行)
📍 图标配置
在 mapIcons 数组中配置地图上的图标:
const mapIcons = [
{
id: 'ship', // 唯一ID,用于标识图层
name: '船只图标', // 名称(用于日志)
image: '/src/assets/icons/8.png', // 图标路径
coordinates: { lng: 122.24, lat: 37.48 }, // 地理坐标
size: 0.3, // 图标大小(缩放比例)
anchor: 'center', // 锚点位置
allowOverlap: true // 是否允许重叠
}
]
配置项说明:
| 字段 | 类型 | 必填 | 说明 | 示例 |
|---|---|---|---|---|
id |
string | ✅ | 唯一标识符 | 'ship', 'warehouse' |
name |
string | ✅ | 图标名称(日志用) | '船只图标', '仓库' |
image |
string | ✅ | 图片路径 | '/src/assets/icons/8.png' |
coordinates |
object | ✅ | 地理坐标 | { lng: 122.24, lat: 37.48 } |
size |
number | ✅ | 大小(缩放比例) | 0.3 (30%大小) |
anchor |
string | ❌ | 锚点位置 | 'center', 'top', 'bottom' |
allowOverlap |
boolean | ❌ | 允许重叠(默认true) | true, false |
📝 文字配置
在 mapTexts 数组中配置地图上的文字:
const mapTexts = [
{
id: 'total-amount', // 唯一ID
name: '总金额', // 名称(用于日志)
content: 15992458.89, // 显示内容(字符串或数字)
coordinates: { lng: 122.09, lat: 37.52 }, // 地理坐标
size: 32, // 字体大小(像素)
color: '#ffffff', // 文字颜色
haloColor: '#667eea', // 描边颜色(光晕效果)
haloWidth: 2, // 描边宽度
pitchAlignment: 'map', // 倾斜对齐方式
rotationAlignment: 'map', // 旋转对齐方式
anchor: 'center', // 锚点
offset: [0, 0], // 偏移量 [x, y]
animation: {
enabled: true, // 是否启用动画
startValue: 12000000, // 起始值
duration: 2000, // 动画时长(毫秒)
interval: 100 // 更新间隔(毫秒)
}
}
]
配置项说明:
| 字段 | 类型 | 必填 | 说明 | 示例 |
|---|---|---|---|---|
id |
string | ✅ | 唯一标识符 | 'total-amount', 'ship-name' |
name |
string | ✅ | 文字名称(日志用) | '总金额', '船名' |
content |
string/number | ✅ | 显示内容 | '威海号', 15992458.89 |
coordinates |
object | ✅ | 地理坐标 | { lng: 122.09, lat: 37.52 } |
size |
number | ✅ | 字体大小(像素) | 32, 20 |
color |
string | ✅ | 文字颜色(HEX) | '#ffffff', '#667eea' |
haloColor |
string | ✅ | 描边颜色(HEX) | '#667eea', '#000000' |
haloWidth |
number | ✅ | 描边宽度(像素) | 2, 1 |
pitchAlignment |
string | ❌ | 倾斜对齐(默认'map') |
'map' (跟随倾斜), 'viewport' (水平) |
rotationAlignment |
string | ❌ | 旋转对齐(默认'map') |
'map' (跟随旋转), 'viewport' (不旋转) |
anchor |
string | ❌ | 锚点位置(默认'center') |
'center', 'left', 'right' |
offset |
array | ❌ | 偏移量(默认[0,0]) |
[10, -5] |
animation |
object | ❌ | 动画配置(仅数字) | 见下表 |
动画配置项(仅当 content 为数字时有效):
| 字段 | 类型 | 必填 | 说明 | 示例 |
|---|---|---|---|---|
enabled |
boolean | ✅ | 是否启用动画 | true, false |
startValue |
number | ✅ | 起始值 | 12000000 |
duration |
number | ✅ | 动画时长(毫秒) | 2000 (2秒) |
interval |
number | ❌ | 更新间隔(毫秒,默认100) | 100 (每100ms更新) |
🔌 API 调用方法
兼容 API(当前代码使用):
// 在 Login.vue 中调用
mapboxRef.value.showIcon() // 显示船图标
mapboxRef.value.hideIcon() // 隐藏船图标
mapboxRef.value.showNumber() // 显示数字(带动画)
mapboxRef.value.hideNumber() // 隐藏数字
新的通用 API(更灵活):
// 显示/隐藏所有配置的元素
mapboxRef.value.showAllMapElements()
mapboxRef.value.hideAllMapElements()
// 显示/隐藏单个元素(需要传入配置对象)
const warehouseIcon = mapIcons.find(icon => icon.id === 'warehouse')
mapboxRef.value.showSingleIcon(warehouseIcon)
mapboxRef.value.hideSingleIcon(warehouseIcon)
const shipName = mapTexts.find(text => text.id === 'ship-name')
mapboxRef.value.showSingleText(shipName)
mapboxRef.value.hideSingleText(shipName)
// 访问配置数组(只读)
console.log(mapboxRef.value.mapIcons)
console.log(mapboxRef.value.mapTexts)
暴露的完整 API 列表:
| API | 说明 | 类型 |
|---|---|---|
showIcon() |
显示船图标(兼容API) | 函数 |
hideIcon() |
隐藏船图标(兼容API) | 函数 |
showNumber() |
显示数字(兼容API) | 函数 |
hideNumber() |
隐藏数字(兼容API) | 函数 |
showAllMapElements() |
显示所有元素 | 函数 |
hideAllMapElements() |
隐藏所有元素 | 函数 |
showSingleIcon(config) |
显示单个图标 | 函数 |
hideSingleIcon(config) |
隐藏单个图标 | 函数 |
showSingleText(config) |
显示单个文字 | 函数 |
hideSingleText(config) |
隐藏单个文字 | 函数 |
getShipScreenPosition() |
获取船的屏幕坐标 | 函数 |
transitionTo3D() |
地图转换到3D | 函数 |
transitionTo2D() |
地图转换到2D | 函数 |
isMapReady |
地图是否准备好 | 响应式变量 |
mapIcons |
图标配置数组 | 只读数组 |
mapTexts |
文字配置数组 | 只读数组 |
🎨 样式效果说明
倾斜对齐方式对比:
pitchAlignment: 'map'(跟随地图倾斜)
- ✅ 3D 立体感强,科技感强
- ✅ 与地图融为一体
- ✅ 适合大屏数据展示
- ⚠️ Symbol Layer 高频更新时会闪烁
pitchAlignment: 'viewport'(始终水平)
- ✅ 清晰易读
- ✅ 传统大屏风格
- ❌ 悬浮感,与地图分离
数字递增动画效果:
- 从
startValue跳跃式增长到content值 - 使用
easeOutCubic缓动(开始快,结尾慢) - 每
interval毫秒更新一次(默认 100ms) - 自动添加千分位逗号和 2 位小数(如:
15,992,458.89) - 适合大屏数据展示的"科技感"效果
⚙️ 响应式行为
- 屏幕宽度 > 1024px:显示所有图标和文字
- 屏幕宽度 ≤ 1024px:隐藏所有图标和文字(移动端、平板竖屏)
📚 使用示例
示例 1:添加多个船只和标签
const mapIcons = [
{
id: 'ship-1',
name: '货船A',
image: '/src/assets/icons/8.png',
coordinates: { lng: 122.24, lat: 37.48 },
size: 0.3,
anchor: 'center',
allowOverlap: true
},
{
id: 'ship-2',
name: '货船B',
image: '/src/assets/icons/8.png',
coordinates: { lng: 122.3, lat: 37.5 },
size: 0.25,
anchor: 'center',
allowOverlap: true
}
]
const mapTexts = [
{
id: 'ship-1-name',
name: '货船A名称',
content: '威海号',
coordinates: { lng: 122.24, lat: 37.485 },
size: 16,
color: '#ffffff',
haloColor: '#000000',
haloWidth: 1,
pitchAlignment: 'viewport',
rotationAlignment: 'viewport',
anchor: 'bottom',
offset: [0, -10]
}
]
示例 2:大屏数据展示(多个动态数字)
const mapTexts = [
{
id: 'total-revenue',
name: '总收入',
content: 15992458.89,
coordinates: { lng: 122.05, lat: 37.55 },
size: 28,
color: '#4ade80',
haloColor: '#000000',
haloWidth: 2,
pitchAlignment: 'map',
rotationAlignment: 'map',
anchor: 'center',
offset: [0, 0],
animation: {
enabled: true,
startValue: 10000000,
duration: 3000,
interval: 150
}
},
{
id: 'total-orders',
name: '总订单数',
content: 8567,
coordinates: { lng: 122.15, lat: 37.55 },
size: 28,
color: '#60a5fa',
haloColor: '#000000',
haloWidth: 2,
pitchAlignment: 'map',
rotationAlignment: 'map',
anchor: 'center',
offset: [0, 0],
animation: {
enabled: true,
startValue: 5000,
duration: 2000,
interval: 100
}
}
]
🐛 常见问题
Q: 文字频繁更新时闪烁?
A: Symbol Layer 不适合高频更新。解决方案:
- 降低更新频率(
interval: 500或更高) - 或使用 HTML Marker(需要修改实现)
Q: 图标不显示?
A: 检查:
- 图片路径是否正确
- 图片是否存在于
src/assets/icons/目录 - 控制台是否有错误日志
- 屏幕宽度是否 > 1024px
Q: 数字动画不生效?
A: 确保:
content字段是数字类型,不是字符串animation.enabled = truestartValue < content(递增动画)
Q: 如何调整元素位置?
A: 修改配置中的 coordinates 字段:
coordinates: {
lng: 122.24, // 经度(东西方向,数字越大越往东)
lat: 37.48 // 纬度(南北方向,数字越大越往北)
}
💡 最佳实践
- 图标命名:使用语义化的 ID,如
ship-1、warehouse-main - 颜色选择:使用对比度高的颜色和描边,确保可见性
- 动画时长:2-3 秒的动画效果最佳(
duration: 2000-3000) - 更新频率:建议
interval >= 100ms,避免闪烁 - 坐标精度:使用小数点后 2 位精度即可(如
122.24) - 图片优化:使用 PNG 格式,透明背景,分辨率适中(512×512)
MapOverlay 组件 - DOM 叠加层
组件位置: src/views/login/components/MapOverlay.vue
🎯 组件特点
MapOverlay 是一个在地图上叠加 DOM 元素的组件,使用地图经纬度坐标定位,但在地图外层渲染为普通 HTML。
核心优势:
- ✅ 基于地图坐标 - 输入经纬度,自动转换为屏幕像素坐标
- ✅ DOM 渲染,无闪烁 - 不是 Mapbox Symbol Layer,避免更新时闪烁
- ✅ 完全自定义样式 - 支持任意 CSS、渐变、阴影、动画
- ✅ 自动跟随地图 - 地图移动/缩放时自动更新位置
- ✅ 灵活布局 - 支持图标、文字或两者组合
适用场景:
- 需要频繁更新的内容(避免 Symbol Layer 闪烁)
- 需要复杂样式的元素(渐变、阴影、CSS 动画)
- 需要用户交互的 UI(按钮、表单等)
- 不需要跟随地图倾斜的内容
📦 基础用法
<template>
<MapboxBackground ref="mapboxRef" />
<!-- 示例1:只显示图标 -->
<MapOverlay
:map="mapboxRef?.map"
:coordinates="{ lng: 122.24, lat: 37.48 }"
:visible="isLoggedIn"
icon="/src/assets/icons/8.png"
:icon-width="80"
/>
<!-- 示例2:只显示文字(纯白色,无描边)-->
<MapOverlay
:map="mapboxRef?.map"
:coordinates="{ lng: 122.09, lat: 37.52 }"
:visible="isLoggedIn"
text="25,680,924.50"
:font-size="28"
text-color="#ffffff"
font-weight="700"
/>
<!-- 示例3:图标 + 文字组合(垂直排列:图标在上,文字在下) -->
<MapOverlay
:map="mapboxRef?.map"
:coordinates="{ lng: 122.15, lat: 37.52 }"
:visible="isLoggedIn"
icon="/src/assets/icons/6.png"
:icon-width="48"
text="威海仓库"
:font-size="16"
text-color="#ffffff"
font-weight="600"
layout="vertical"
:gap="6"
/>
<!-- 示例4:图标 + 信息(水平排列:图标在左,信息在右,启用详情按钮) -->
<MapOverlay
:map="mapboxRef?.map"
:coordinates="{ lng: 122.11, lat: 37.50 }"
:visible="isLoggedIn"
icon="/src/assets/icons/6.png"
:icon-width="40"
text="威海港 | 停泊: 8艘 | 货物: 15,680吨"
:font-size="14"
text-color="#ffffff"
font-weight="600"
layout="horizontal"
:gap="10"
:enable-popover="true"
@detail-click="handleDetailClick"
/>
<!-- 示例5:顺序显示多组(使用 delay 参数 + 水平布局) -->
<!-- 第一组:威海港(图标+信息,立即显示,启用详情按钮) -->
<MapOverlay
:map="mapboxRef?.map"
:coordinates="{ lng: 122.11, lat: 37.50 }"
:visible="showShipChart && isLargeScreen"
:delay="0"
icon="/src/assets/icons/6.png"
:icon-width="40"
text="威海港 | 停泊: 8艘 | 货物: 15,680吨"
:font-size="14"
text-color="#ffffff"
font-weight="600"
layout="horizontal"
:gap="10"
anchor="center"
:enable-popover="true"
@detail-click="handleDetailClick"
/>
<!-- 第二组:刘公岛码头(延迟1秒显示,启用详情按钮) -->
<MapOverlay
:map="mapboxRef?.map"
:coordinates="{ lng: 122.07, lat: 37.51 }"
:visible="showShipChart && isLargeScreen"
:delay="1000"
icon="/src/assets/icons/9.png"
:icon-width="40"
text="刘公岛码头 | 停泊: 5艘 | 货物: 8,920吨"
:font-size="14"
text-color="#ffffff"
font-weight="600"
layout="horizontal"
:gap="10"
anchor="center"
:enable-popover="true"
@detail-click="handleDetailClick"
/>
<!-- 第三组:纯数字显示(延迟2秒显示) -->
<MapOverlay
:map="mapboxRef?.map"
:coordinates="{ lng: 122.15, lat: 37.52 }"
:visible="showShipChart && isLargeScreen"
:delay="2000"
text="总货运量: 25,680,924.50 吨"
:font-size="20"
text-color="#ffffff"
font-weight="700"
anchor="center"
/>
</template>
说明:
- 使用
delay参数控制显示顺序 - 每组可以包含图标、文字或两者组合
delay="0"立即显示,delay="1000"延迟1秒,以此类推
📝 主要 Props
| Props | 类型 | 必填 | 说明 |
|---|---|---|---|
map |
Object | ✅ | Mapbox 地图实例 |
coordinates |
Object | ✅ | 地理坐标 {lng, lat} |
visible |
Boolean | - | 是否显示(默认false) |
delay |
Number | - | 延迟显示时间(毫秒,默认0) |
icon |
String | - | 图标路径 |
iconWidth |
Number | - | 图标宽度(默认64px) |
text |
String/Number | - | 文字内容 |
fontSize |
Number | - | 字体大小(默认16px) |
textColor |
String | - | 文字颜色(默认#ffffff) |
textStroke |
String | - | 文字描边颜色(默认transparent) |
textStrokeWidth |
Number | - | 描边宽度(默认0) |
fontWeight |
String/Number | - | 字体粗细(默认bold) |
layout |
String | - | 布局:'vertical' | 'horizontal' |
gap |
Number | - | 元素间距(默认8px) |
anchor |
String | - | 锚点位置(默认'center') |
offset |
Array | - | 偏移量[x, y](默认[0, 0]) |
background |
String | - | 背景颜色 |
padding |
Number | - | 内边距(默认0) |
borderRadius |
Number | - | 圆角(默认0) |
shadow |
String | - | CSS box-shadow |
zIndex |
Number | - | 层级(默认100) |
enablePopover |
Boolean | - | 是否启用详情按钮(默认false) |
detailAction |
String | - | 详情按钮行为:'message' | 'route' |
detailMessage |
String | - | 当 action='message' 时的消息内容 |
detailRoute |
String | - | 当 action='route' 时的跳转路径 |
🎨 布局方式说明
layout 属性控制图标和文字的排列方向:
-
'vertical'(垂直布局):图标在上,文字在下- 适合:紧凑显示位置名称、标签
- 示例:港口名称、码头标识
<MapOverlay icon="/src/assets/icons/6.png" text="威海港" layout="vertical" :gap="6" /> -
'horizontal'(水平布局):图标在左,文字在右- 适合:显示详细信息、多项数据组合
- 示例:带图标的信息卡片、数据展示
<MapOverlay icon="/src/assets/icons/6.png" text="威海港 | 停泊: 8艘 | 货物: 15,680吨" layout="horizontal" :gap="10" />
💬 详情按钮功能
组件支持在文字上悬停时显示"查看详情"按钮(仅在 >1024px 屏幕显示):
启用方式:
<MapOverlay
:map="mapboxRef?.map"
:coordinates="{ lng: 122.11, lat: 37.50 }"
text="威海港 | 停泊: 8艘"
:enable-popover="true"
@detail-click="handleDetailClick"
/>
说明:
enablePopover:设置为true启用详情按钮(默认false)- 按钮固定显示在文字右侧下方,位置稳定,易于点击
- 鼠标悬停在文字上时显示按钮,离开文字 200ms 后自动隐藏
- 按钮采用 Element UI 蓝色风格
- 文字自动添加阴影效果,提升可读性
两种点击行为:
- 弹出消息(默认)
<MapOverlay
:enable-popover="true"
detail-action="message"
detail-message="威海港详情:当前停泊8艘船只"
/>
- 跳转路由
<MapOverlay
:enable-popover="true"
detail-action="route"
detail-route="/"
/>
注:路由路径需要是项目中已存在的路由,如 /(首页)、/courses/coursesInfo 等
事件处理示例:
const handleDetailClick = (data) => {
console.log('查看详情:', data.text)
console.log('坐标:', data.coordinates)
console.log('行为:', data.action) // 'message' 或 'route'
// 自定义额外处理逻辑
}
🔧 技术实现细节
密码哈希存储:
- 使用 bcrypt 算法(rounds=10)
- 字段名:
password_hash(不是password) - 在线生成工具:https://bcrypt-generator.com/
路由守卫调整:
- 移除了登录后强制跳转到
/login的逻辑 - 允许用户正常访问首页
/和其他管理页面 - 登录后的默认页面由登录逻辑控制
Vite 代理配置(开发环境):
proxy: {
'/api/qianyi': {
target: 'http://115.190.83.144:8080', // Rust 服务器
changeOrigin: true,
secure: false
}
}
📦 采购管理系统需求分析
🎯 系统概述
项目目标: 开发一个智能采购管理系统,基于历史销售数据和业务预测,自动计算最优采购数量和采购时机。
核心价值:
- ✅ 降低库存成本 - 避免过度采购导致资金占用
- ✅ 防止缺货 - 确保库存能满足销售需求
- ✅ 智能预警 - 提前提醒采购时机
- ✅ 多维度管理 - 支持多仓库、多组织类型、多供应商
技术架构:
- 前端:Vue 3 + Element Plus(威海地图项目集成)
- 后端:Rust 服务器(qianyi 模块)+ Go 服务器备选
- 数据库:MySQL(qianyi 数据库)
- 计算引擎:后端实时计算 + 定时任务分析
📊 核心业务逻辑
1. 采购数量计算公式(核心)
本次采购数量 = 趋势预测采购备货数量(本组织类型)
+ 需求预测采购备货数量(本组织类型)
- 在途数量(属于本组织类型)
- (在库数量(属于本组织类型且未过期) - 往期需求型预测保留期内数量)
2. 关键概念解析
| 概念 | 说明 | 数据来源 |
|---|---|---|
| 趋势预测采购备货数量 | 基于往年同期"零售端"销量,预测未来一段时间的需求 | 历史销售记录(去年今日 + 额定库存容量时间) |
| 需求预测采购备货数量 | 基于客户订单预测,未来一段时间的确定性需求 | 客户订单、合同、需求预测 |
| 在途数量 | 已采购但未入库的商品数量 | 采购订单记录(下单未到货) |
| 在库数量 | 已入库且未过期的商品数量 | 库存记录(过滤过期商品) |
| 往期需求型预测保留期内数量 | 为重要客户保留的商品(信誉高的客户) | 客户信用等级 + 保留期配置 |
3. 采购时机判断
商品预计售罄时间 = 根据当前库存 + 在途数量 + 预测销量动态计算
必要采购时间 = 商品预计售罄时间 - 采购提前期
采购提醒时间 = 必要采购时间 - 采购提醒提前期
示例:
- 采购提前期:10天
- 采购提醒提前期:5天
- 如果预计 2026.2.10 售罄
- 则 2026.1.31 是必要采购时间
- 则 2026.1.26 开始提醒采购
🏢 多维度管理
1. 仓库维度(4个中心库)
- 人和中心库
- 青岛中心库
- 宁波中心库
- 舟山中心库
2. 组织类型维度(9种)
| 组织类型 | 税率 | 开票类型 | 业务特点 |
|---|---|---|---|
| 13%一般纳税人(非小微) | 13% | 专票 | 主要销售对象,进项充足 |
| 13%一般纳税人(小微) | 13% | 专票 | 小微企业,税收优惠 |
| 9%一般纳税人(非小微) | 9% | 专票 | 特定行业税率 |
| 9%一般纳税人(小微) | 9% | 专票 | - |
| 6%一般纳税人(非小微) | 6% | 专票 | - |
| 6%一般纳税人(小微) | 6% | 专票 | - |
| 小规模纳税人(非小微) | 3% | 普票 | 小规模企业 |
| 小规模纳税人(小微) | 3% | 普票 | 小微小规模 |
| 收据组织 | 0% | 收据 | 个人卡、无票交易 |
说明:
- 每个仓库的每种组织类型独立统计库存
- 采购需求按"仓库 + 组织类型"维度计算
- 不同组织类型的商品可以内部交易/借用
3. 批次管理(双维度)
财务维度批次:
- 商品归属于源采购组织(实际付款方)
- 用于财务核算、税务申报
- 字段:批次号、归属组织、归属仓库、在途/在库数量
业务维度批次:
- 商品可供多个组织类型使用
- 用于业务统计、库存查询
- 字段:仓库 + 组织类型 → 在途/在库数量
示例(批次号 12345):
财务维度:
归属组织:浩海船舶配套设备有限公司(13%一般纳税人非小微)
所处仓库:人和中心库
在途:80件,在库:195件
业务维度:
人和中心库:
- 13%一般纳税人(非小微):在途0, 在库140
- 13%一般纳税人(小微):在途0, 在库20
- 小规模纳税人(非小微):在途0, 在库5
- 收据组织:在途0, 在库25
总计:195件(与财务维度一致)
💰 成本管理
1. 采购成本构成
单位商品采购总成本 = 商品本身成本 + 运费成本 + 公关维持成本 - 保底返利
2. 运费成本率(供应商 + 仓库维度)
| 供应商 | 仓库 | 票据类型 | 运费成本率 |
|---|---|---|---|
| 甲 | 人和中心库 | 13%专票 | 1% |
| 甲 | 人和中心库 | 9%专票 | 0% |
| 甲 | 青岛中心库 | 13%专票 | 1% |
3. 公关维持成本(供应商 + 票据类型维度)
从历史数据分析得出,维持供应商关系的隐性成本:
- 13%专票:1%
- 其他类型:0%
4. 调货成本率(仓库间调货)
当直采仓库无法满足需求,需要从其他仓库调货时产生的成本。
🔄 业务流程
完整采购流程:
1. [系统监控] 实时监控各仓库各组织类型的库存水平
↓
2. [预警触发] 库存容量 < 采购提前期内预测销量
↓
3. [提前提醒] 提前5天(可配置)提醒采购员
↓
4. [计算采购量] 根据公式计算本次采购数量
↓
5. [选择供应商] 根据价格、账期、可达性选择最优供应商
↓
6. [下达订单] 生成采购订单(含批次号)
↓
7. [商品在途] 订单下达后,在途数量增加
↓
8. [到货入库] 商品到货,在库数量增加,在途数量减少
↓
9. [开始新周期] 库存容量更新,系统继续监控
动态调整因素:
- ⚡ 暴卖 → 库存快速下降 → 提前采购
- 📦 退货 → 库存增加 → 延后采购
- ❌ 客户取消订单 → 需求预测下降
- 📈 新增大订单 → 需求预测上升
🗃️ 数据库设计(待实现)
核心表结构:
1. 商品档案表(products)
- 商品ID
- 商品名称
- 规格型号
- 宏观采购到货时间间隔(如:10天)
- 库存有效期(如:3650天)
- 额定库存容量时间(如:90天)
- 采购提醒提前期(如:5天)
2. 批次表(batches)- 财务维度
- 批次号(主键)
- 商品ID
- 归属组织ID
- 归属组织类型
- 当前仓库
- 在途数量
- 在库数量
- 售出数量
- 创建时间
3. 批次业务分布表(batch_business)- 业务维度
- ID
- 批次号(关联 batches)
- 仓库
- 组织类型
- 在途数量
- 在库数量
- 更新时间
4. 历史销售记录表(sales_history)
- 销售ID
- 商品ID
- 仓库
- 组织类型
- 销售数量
- 销售日期
- 客户类型(零售端/客户订单)
5. 供应商表(suppliers)
- 供应商ID
- 供应商名称
- 可提供票据类型
- 公关维持成本费率(JSON)
- 运费成本率配置(JSON)
6. 供应商商品政策表(supplier_policies)
- 政策ID
- 供应商ID
- 商品ID
- 网价
- 采购时下浮方式/比例/金额
- 保底返利配置
- 账期优惠月数
- 支持承兑付款
- 延迟返利月数
7. 仓库表(warehouses)
- 仓库ID
- 仓库名称
- 仓库类型(中心库/二级库)
- 地理位置(经纬度)
- 是否可直采
8. 仓库调货成本表(warehouse_transfer_costs)
- 调出仓库
- 调入仓库
- 票据类型
- 调货成本率
9. 采购订单表(purchase_orders)
- 订单ID
- 批次号
- 商品ID
- 供应商ID
- 源采购组织
- 目标仓库
- 采购数量
- 采购单价
- 运费
- 订单状态(待发货/在途/已到货)
- 预计到货时间
- 实际到货时间
10. 客户需求预测表(customer_demands)
- 需求ID
- 客户ID
- 商品ID
- 预测数量
- 预测交货时间
- 客户信用等级
- 保留期天数
- 创建时间
🧮 后端计算逻辑
核心计算接口(需要开发):
1. 采购需求计算接口
GET /api/weihai/purchase/calculate-demand
参数:
- warehouse_id: 仓库ID
- organization_type: 组织类型
- product_id: 商品ID
返回:
- trend_forecast_qty: 趋势预测备货数量
- demand_forecast_qty: 需求预测备货数量
- in_transit_qty: 在途数量
- in_stock_qty: 在库数量
- reserved_qty: 保留期内数量
- recommend_purchase_qty: 建议采购数量
- estimated_stockout_date: 预计售罄时间
- purchase_remind_date: 采购提醒时间
- must_purchase_date: 必要采购时间
计算步骤:
// 步骤1:计算趋势预测需求
const trendForecast = await calculateTrendForecast({
productId,
warehouseId,
organizationType,
capacityDays: 90, // 额定库存容量时间
historicalDataRange: 365 // 取去年同期数据
})
// 步骤2:计算需求预测需求
const demandForecast = await calculateDemandForecast({
productId,
warehouseId,
organizationType,
capacityDays: 90,
customerOrders: [] // 客户订单预测
})
// 步骤3:查询在途数量
const inTransit = await getInTransitQty({
productId,
warehouseId,
organizationType
})
// 步骤4:查询在库数量(排除过期)
const inStock = await getInStockQty({
productId,
warehouseId,
organizationType,
expiryDays: 3650 // 库存有效期
})
// 步骤5:查询保留期内数量
const reserved = await getReservedQty({
productId,
warehouseId,
organizationType
})
// 步骤6:计算建议采购数量
const recommendQty = trendForecast + demandForecast - inTransit - (inStock - reserved)
// 步骤7:计算采购时机
const estimatedStockoutDate = calculateStockoutDate(inStock, inTransit, dailySales)
const mustPurchaseDate = estimatedStockoutDate - purchaseLeadTime
const remindDate = mustPurchaseDate - remindLeadTime
2. 趋势预测算法(基于历史数据)
async function calculateTrendForecast(params) {
// 查询去年同期销售数据
const lastYearSales = await querySalesHistory({
productId: params.productId,
startDate: getLastYearDate(today),
endDate: getLastYearDate(today + params.capacityDays),
warehouseId: params.warehouseId,
organizationType: params.organizationType,
customerType: '零售端' // 只统计零售销售
})
// 计算日均销量
const avgDailySales = lastYearSales.totalQty / params.capacityDays
// 预测未来需求(可以加入增长率等因素)
const forecastQty = avgDailySales * params.capacityDays
return Math.ceil(forecastQty) // 向上取整
}
3. 需求预测算法(基于客户订单)
async function calculateDemandForecast(params) {
// 查询未来 N 天的客户订单预测
const customerDemands = await queryCustomerDemands({
productId: params.productId,
startDate: today,
endDate: today + params.capacityDays,
warehouseId: params.warehouseId,
organizationType: params.organizationType
})
return customerDemands.totalQty
}
🎨 前端展示规划
1. 采购管理主页(Dashboard)
- 📊 待采购商品列表(按紧急程度排序)
- ⏰ 采购提醒时间线
- 📈 库存预警统计
- 🏢 各仓库库存分布热力图
2. 商品采购详情页
- 📦 当前库存状态(在途/在库/已售)
- 📈 历史销售趋势图表
- 🔮 未来需求预测曲线
- 💰 最优供应商推荐(价格、账期、可达性综合评分)
- 🧮 采购数量计算明细
3. 地图可视化(Mapbox 集成)
- 🗺️ 各仓库位置标注(MapOverlay 组件)
- 📊 各仓库库存状态气泡图
- 🚚 调货路线可视化
- 📍 点击仓库查看详情
MapOverlay 应用场景:
<!-- 人和中心库 - 显示库存预警 -->
<MapOverlay
:map="mapboxRef.map"
:coordinates="{ lng: 122.11, lat: 37.50 }"
icon="/src/assets/icons/warehouse.png"
text="人和中心库 | 库存: 195件 | 预警: 5天后需采购"
layout="horizontal"
:enable-popover="true"
detail-action="route"
detail-route="/purchase/warehouse-detail?id=1"
/>
🔧 技术实现要点
1. 数据模拟(开发阶段)
在没有真实销售记录的情况下,需要生成模拟数据:
// 生成模拟历史销售数据(去年365天)
async function generateMockSalesData() {
const products = ['A', 'B', 'C'] // 商品列表
const warehouses = ['人和', '青岛', '宁波', '舟山']
const orgTypes = ['13%一般纳税人(非小微)', '小规模纳税人(小微)', '收据组织']
// 生成去年每天的销售记录
for (let day = 0; day < 365; day++) {
const date = getDateBefore(day)
for (const product of products) {
for (const warehouse of warehouses) {
for (const orgType of orgTypes) {
// 模拟日销量(可以加入季节性波动)
const dailySales = Math.floor(Math.random() * 5) + 1 // 1-5件/天
await insertSalesHistory({
productId: product,
warehouseId: warehouse,
organizationType: orgType,
quantity: dailySales,
saleDate: date,
customerType: '零售端'
})
}
}
}
}
}
2. 定时任务(每日计算)
// 每天凌晨2点执行
cron.schedule('0 2 * * *', async () => {
console.log('🕐 开始每日采购需求计算...')
// 1. 获取所有需要监控的商品
const products = await getAllProducts()
for (const product of products) {
// 2. 计算每个仓库每个组织类型的采购需求
for (const warehouse of warehouses) {
for (const orgType of organizationTypes) {
const demand = await calculatePurchaseDemand({
productId: product.id,
warehouseId: warehouse.id,
organizationType: orgType
})
// 3. 如果触发采购提醒,发送通知
if (demand.daysUntilRemind <= 0) {
await sendPurchaseReminder({
product,
warehouse,
orgType,
recommendQty: demand.recommend_purchase_qty,
mustPurchaseDate: demand.must_purchase_date
})
}
}
}
}
console.log('✅ 每日采购需求计算完成')
})
3. 接口设计(后端 Rust/Go)
// Rust 服务器新增模块:/api/weihai
pub fn create_weihai_routes(pool: MySqlPool) -> Router {
Router::new()
// 采购管理
.route("/purchase/calculate-demand", get(calculate_purchase_demand))
.route("/purchase/recommend-suppliers", get(recommend_suppliers))
.route("/purchase/create-order", post(create_purchase_order))
// 库存管理
.route("/inventory/status", get(get_inventory_status))
.route("/inventory/batches", get(get_batch_list))
// 数据分析
.route("/analysis/sales-trend", get(get_sales_trend))
.route("/analysis/demand-forecast", get(get_demand_forecast))
.layer(Extension(pool))
}
📱 前端页面结构
/purchase (采购管理)
├── /dashboard # 采购主页(待采购列表、提醒)
├── /demand-analysis # 需求分析(趋势图表)
├── /create-order # 创建采购订单
├── /order-list # 采购订单列表
└── /supplier-management # 供应商管理
/inventory (库存管理)
├── /overview # 库存总览(各仓库分布)
├── /batch-detail # 批次详情
└── /transfer # 库存调拨
/map (地图可视化)
└── /warehouse-map # 仓库地图(集成到登录页)
⚙️ 配置管理
系统需要的配置项(存储在数据库或配置文件):
1. 商品维度配置
- 采购提前期(天)
- 库存有效期(天)
- 额定库存容量时间(天)
- 采购提醒提前期(天)
2. 供应商维度配置
- 公关维持成本费率(按票据类型)
- 运费成本率(按仓库 + 票据类型)
- 可提供票据类型列表
- 可达仓库列表
3. 仓库维度配置
- 仓库间调货成本率矩阵
- 仓库经纬度(用于地图展示)
- 是否支持直采
4. 组织维度配置
- 组织类型列表及税率
- 源采购组织授权
🚀 开发路线图
阶段1:基础数据准备(当前)
- ✅ 数据库表设计
- ⏳ 生成模拟历史销售数据
- ⏳ 配置供应商、仓库、商品档案
阶段2:核心计算引擎
- ⏳ 趋势预测算法
- ⏳ 需求预测算法
- ⏳ 采购数量计算
- ⏳ 采购时机判断
阶段3:前端页面开发
- ⏳ 采购主页(Dashboard)
- ⏳ 库存总览
- ⏳ 地图可视化集成
阶段4:高级功能
- ⏳ 供应商智能推荐
- ⏳ 成本优化算法
- ⏳ 采购预警通知
- ⏳ Excel 导入导出
📌 当前状态
- 📄 需求文档:已完成(采购需求汇总(王浩版2026.1.29).xlsx)
- 🗄️ 数据库设计:待设计(上述表结构为初步方案)
- 💻 后端接口:未开始
- 🎨 前端页面:未开始
- 📊 模拟数据:待生成
下一步建议:
- 设计并创建采购相关数据库表
- 生成模拟历史销售数据(365天)
- 开发采购需求计算接口
- 开发前端采购主页
🎯 悬停弹窗功能
当文字内容存在时,鼠标悬停到文字上会自动显示一个悬浮弹窗,包含"查看详情"按钮。
特性:
- 自动触发:鼠标移入文字时显示,移出时隐藏
- 渐入动画:0.2秒 fadeIn 动画
- 紫色渐变按钮:与项目主题一致
- 事件传递:点击按钮会触发
detail-click事件
使用示例:
<MapOverlay
:map="mapboxRef.map"
:coordinates="{ lng: 122.11, lat: 37.50 }"
:visible="showShipChart"
text="威海港 | 停泊: 8艘"
layout="horizontal"
@detail-click="handleDetailClick"
/>
<script setup>
const handleDetailClick = (data) => {
console.log('用户点击查看详情:', data)
// data = { coordinates: {lng, lat}, text: '威海港 | 停泊: 8艘' }
// 这里可以打开详情页、弹出对话框等
}
</script>
🆚 Symbol Layer vs MapOverlay
| 特性 | Symbol Layer | MapOverlay (DOM) |
|---|---|---|
| 样式灵活性 | ⭐⭐ 受限 | ⭐⭐⭐⭐⭐ 完全自由 |
| 更新闪烁 | ⚠️ 高频更新会闪烁 | ✅ 无闪烁 |
| CSS 动画 | ⭐⭐ 简单动画 | ⭐⭐⭐⭐⭐ 任意动画 |
| 地图倾斜 | ✅ 跟随倾斜 | ❌ 始终水平 |
| 性能 | ⭐⭐⭐⭐⭐ GPU 加速 | ⭐⭐⭐⭐ DOM 渲染 |
| 用户交互 | ⭐⭐ 有限 | ⭐⭐⭐⭐⭐ 完整 DOM 事件 |
💡 使用建议
使用 MapOverlay(DOM)适合:
- 需要频繁更新内容(避免闪烁)
- 需要复杂样式(渐变、阴影、CSS 动画)
- 需要用户交互(点击、悬停、输入)
- 需要显示表单、按钮等复杂 UI
使用 Symbol Layer 适合:
- 需要跟随地图倾斜(3D 效果)
- 大量标记点(性能更好)
- 简单图标和文字
- 内容基本不变
⚙️ 响应式显示
MapOverlay 不会自动隐藏,需要手动控制:
<!-- ≤1024 自动隐藏(跟船图标、ECharts 图表一致) -->
<MapOverlay
:visible="showShipChart && isLargeScreen"
...
/>
<!-- 始终显示(不受屏幕宽度限制) -->
<MapOverlay
:visible="isLoggedIn"
...
/>
推荐做法: 跟其他地图元素保持一致,使用 showShipChart && isLargeScreen
🎬 顺序显示多组元素
使用 delay 参数实现动画的顺序播放:
<!-- 第一组:威海港(立即) -->
<MapOverlay
:delay="0"
icon="/src/assets/icons/6.png"
text="威海港"
layout="vertical"
/>
<!-- 第二组:数字(1秒后) -->
<MapOverlay
:delay="1000"
text="25,680,924.50"
/>
<!-- 第三组:刘公岛(2秒后) -->
<MapOverlay
:delay="2000"
icon="/src/assets/icons/9.png"
text="刘公岛码头"
layout="vertical"
/>
效果: 第一组显示 → 1秒后第二组显示 → 再1秒后第三组显示
9. react/showtime
类型: React 数字孪生展示平台
技术栈:
- React 19.2+ (Hooks)
- TypeScript 5.9+
- Vite 7.2+ - 构建工具
- Tailwind CSS 4.1+ - 样式框架
- HeroUI 2.8+ - UI 组件库
- Mapbox GL 3.18+ - 地图引擎
- React Hot Toast 2.x - 通知组件
- Axios - HTTP 请求
- React Router DOM 7.13+ - 路由管理
- Lucide React - 图标库
核心功能:
- ✅ Mapbox 3D 地图展示 - 威海地区 3D 地图背景
- ✅ 2D→3D 转换动画 - 地图从平面到立体的流畅过渡(2 秒动画)
- ✅ 登录系统 - 密码登录 + 短信登录双模式
- ✅ 数据面板展示 - 左右各 3 个信息框,滑入动画 + 回弹效果
- ✅ 实时天气信息 - 和风天气 API 集成
- ✅ 加载进度条 - 系统初始化进度展示
- ✅ 点击提示 - "请点击登录" 呼吸动画
- ✅ 自动关闭 - 登录表单 10 秒无操作自动关闭
- ✅ 响应式设计 - 支持桌面/平板/手机(移动端可滚动)
页面流程(无 Token):
- 加载阶段:紫色渐变背景 + 进度条(0-100%)+ "正在初始化系统..."
- 地图展示:Mapbox 2D 地图淡入 + 天气信息(右上角)
- 登录提示:中心显示"请点击登录"(闪烁呼吸动画)
- 点击交互:点击屏幕任意位置,弹出登录表单
- 登录表单:白色毛玻璃卡片,支持密码/短信登录切换
- 10 秒自动关闭:无操作自动隐藏登录表单,恢复到提示状态
- 登录成功:表单消失 → 地图 2D→3D 转换(2 秒)→ 数据面板滑入(1.7 秒后)
- 数据展示:左侧 3 个 + 右侧 3 个面板,从两侧滑入带回弹
页面流程(有 Token):
- 加载阶段:同上
- 地图展示:Mapbox 2D 地图淡入
- 自动转换:地图准备好后立即执行 2D→3D 转换(无需登录)
- 数据展示:数据面板自动滑入
登录功能:
- 密码登录:用户名 + 密码
- 短信登录:手机号(11 位验证)+ 6 位验证码
- 验证码发送:60 秒倒计时,手机号格式验证
- 表单验证:实时输入验证,错误提示
- Token 管理:登录成功后保存到 localStorage
- API 集成:调用 qianyi 后端(http://115.190.83.144:8080)
地图特色功能:
- 2D 初始状态:pitch 0°, bearing 0°, zoom 11
- 3D 转换动画:pitch → 45°, bearing → -17.6°, 持续 2 秒
- 自定义地图样式:深色科技风(深海蓝水域 + 紫色道路)
- 地图控制:禁用交互(背景用),隐藏 logo 和版权信息
- 响应式:自动适配窗口大小
数据面板设计:
- 布局:左右各占 25% 宽度,中间 50% 留白展示地图
- 透明背景:rgba(102, 126, 234, 0.05) + 毛玻璃效果
- 动画效果:
- 左侧面板:从左向右滑入,cubic-bezier(0.68, -0.55, 0.265, 1.55) 回弹
- 右侧面板:从右向左滑入,同样回弹效果
- 延迟:每个面板延迟 100ms,形成渐进动画
- 时长:800ms 滑入动画
- 移动端适配:
- 垂直排列(非横向)
- 全宽显示(100%)
- 支持滚动查看全部 6 个面板
- 底部预留 80px,确保第 6 个面板完全可见
天气信息组件:
- 位置:右上角固定定位
- 内容:日期 + 时间 + 天气图标 + 温度 + 天气文字
- 数据源:和风天气 API(威海,每 30 分钟更新)
- 样式:白色半透明毛玻璃 + 圆角胶囊
- 响应式:移动端缩小字体,隐藏天气文字
通知系统:
- 组件:react-hot-toast
- 位置:顶部居中
- 样式:白色背景 + 扁平设计(上下 10px 内边距)
- 时长:3 秒自动消失
- 图标:成功(绿色✅)、错误(红色❌)
- 用途:登录成功/失败、验证码发送、表单验证错误
API 接口(qianyi 后端):
POST /api/qianyi/login- 密码登录POST /api/qianyi/sms-login- 短信登录POST /api/qianyi/send-code- 发送验证码GET /api/qianyi/user-info- 获取用户信息
状态管理:
- React Context - 用户状态(token, userInfo)
- localStorage - 持久化存储(token, username, userId, userCode, isAdmin)
- useRef - 地图实例引用、登录表单定时器
路由配置:
/- MapHome 首页(默认路由)- 其他路由待扩展
开发配置:
- 端口:3001
- 代理:
/api/qianyi→http://115.190.83.144:8080 - 热更新:Vite HMR
- 类型检查:TypeScript strict mode
移动端适配:
- 断点:
- 移动端:< 640px (sm)
- 平板:640px - 1024px
- 桌面:≥ 1024px (lg)
- 页面容器:桌面固定,移动端可滚动
- 数据面板:桌面横向,移动端纵向
- 天气组件:响应式字体大小
- 登录提示:响应式字体和间距
动画时序:
- 加载动画:0-100% 进度条(2 秒)
- 地图淡入:opacity 0 → 1(0.8 秒)
- 地图 3D 转换:2 秒平滑过渡
- 数据面板滑入:地图转换 1.7 秒后开始(800ms 滑入 + 回弹)
- 整体流程:约 4-5 秒完成所有动画
技术特点:
- 无 StrictMode:避免地图重复初始化
- 可选链:防止 ref null 报错
- 定时器管理:登录表单自动关闭(10 秒)
- 动画优化:使用 CSS transition + cubic-bezier 缓动
- 类型安全:完整的 TypeScript 类型定义
- 错误处理:Axios 拦截器统一处理 401 错误
开发命令:
npm run dev # 启动开发服务器(3001 端口)
npm run build # 构建生产版本
npm run preview # 预览生产构建
依赖包:
@heroui/react^2.8.8 - UI 组件库mapbox-gl^3.x - 地图引擎react-hot-toast^2.x - 通知组件axios- HTTP 请求lucide-react- 图标库tailwindcss^4.1.18 - 样式框架@tailwindcss/vite- Tailwind Vite 插件framer-motion- 动画库(HeroUI 依赖)
说明: React 实现的数字孪生展示平台,专为 qianyi 项目设计。核心特色是 Mapbox 地图的 2D→3D 流畅转换动画和数据面板的滑入回弹效果。使用最新的 React 19 + Tailwind CSS v4 + HeroUI 技术栈,实现了完整的登录系统和响应式布局。支持密码和短信双登录模式,10 秒无操作自动关闭登录表单。移动端完美适配,支持滚动查看所有数据面板。
10. server-go
类型: Go 后端服务器(qianyi 平台)
技术栈:
- Go 1.25+
- Gin 1.11+ - Web 框架
- MySQL - 数据库
- JWT - Token 认证
- BCrypt - 密码加密
Go 基础命令:
1. 初始化项目
# 创建项目目录
mkdir server-go
cd server-go
# 初始化 Go 模块
go mod init qianyi-server
2. 安装依赖
# 一次性安装所有依赖
go get github.com/gin-gonic/gin
go get github.com/go-sql-driver/mysql
go get github.com/golang-jwt/jwt/v5
go get github.com/BurntSushi/toml
go get golang.org/x/crypto/bcrypt
# 或者运行 go mod tidy 自动安装
go mod tidy
3. 运行开发服务器
# 直接运行(不编译,适合开发)
go run main.go
# 指定文件运行
go run .
4. 编译程序
# 编译当前平台(Windows 生成 .exe)
go build -o qianyi.exe main.go
# 或者简写(自动使用包名)
go build -o qianyi.exe
# 编译优化版本(减小体积)
go build -ldflags="-s -w" -o qianyi.exe
5. 跨平台编译
# 编译 Linux 版本(在 Windows 上)
SET GOOS=linux
SET GOARCH=amd64
go build -o qianyi main.go
# 编译 macOS 版本(在 Windows 上)
SET GOOS=darwin
SET GOARCH=amd64
go build -o qianyi main.go
# 编译 Windows 版本(在 Linux/Mac 上)
GOOS=windows GOARCH=amd64 go build -o qianyi.exe main.go
6. 查看依赖
# 查看依赖树
go list -m all
# 更新依赖
go get -u
go mod tidy
7. 常用命令
go version # 查看 Go 版本
go env # 查看环境变量
go fmt ./... # 格式化代码
go vet ./... # 静态检查
go test ./... # 运行测试
项目结构:
server-go/
├── main.go # 主入口文件
├── go.mod # 依赖管理文件(自动生成)
├── go.sum # 依赖校验文件(自动生成)
├── config/
│ └── config.go # 配置加载
├── database/
│ └── database.go # 数据库连接
├── handlers/
│ └── auth.go # 登录处理器
├── models/
│ └── user.go # 用户模型
├── routes/
│ └── routes.go # 路由注册
└── utils/
└── jwt.go # JWT 工具
数据库配置(与 Rust 共用):
- Host:
115.190.83.144 - Port:
3306 - User:
root - Password:
123a456b - Database:
qianyi
已实现的接口(2/23):
- ✅
POST /qianyi/login- 管理员登录(用户名 + 密码) - ✅
POST /qianyi/admin/register- 管理员注册(仅开发环境)
待实现的接口(21/23):
- ⏳
POST /auth/wechat_login- 微信小程序登录 - ⏳ 课程管理(5个接口)
- ⏳ 报名管理(4个接口)
- ⏳ 热门内容(2个接口)
- ⏳ 动态内容(2个接口)
- ⏳ 事件动态(1个接口)
- ⏳ 活动照片(6个接口)
- ⏳ 小程序报名(2个接口)
服务器配置:
- 端口:8082
- CORS:已启用(支持跨域)
- JWT Secret:
qianyi-secret-key-change-this-in-production - JWT 过期时间:24 小时(86400 秒)
Go vs Rust 对比:
| 特性 | Go | Rust |
|---|---|---|
| 编译速度 | ⚡⚡⚡ 快 | ⚡ 慢 |
| 运行性能 | ⚡⚡ 快 | ⚡⚡⚡ 极快 |
| 内存安全 | 垃圾回收 | 所有权系统 |
| 学习曲线 | ⭐⭐ 简单 | ⭐⭐⭐⭐ 困难 |
| 错误处理 | if err != nil |
Result<T, E> |
| 并发模型 | Goroutine | async/await |
| 编译产物 | 单文件 .exe | 单文件 .exe |
| 部署方式 | 直接运行 | 直接运行 |
说明: Go 实现的 qianyi 后端服务器,与 Rust 版本共用同一个 MySQL 数据库。使用 Gin 框架构建 RESTful API,支持 JWT 认证和 BCrypt 密码加密。编译后生成单个可执行文件,无需运行时环境,部署简单。目前已实现管理员登录接口,其他 22 个接口待开发。
🚨 数据库安全事件(2026-01-23)
事件概述
- 时间:2026年1月23日
- 影响:qianyi 数据库所有表被删除
- 攻击方式:数据库外网暴露 + 弱密码
- 数据恢复:无备份,完全重建
已采取的恢复措施
1. 数据库重建(已完成)
创建了 14 个核心表:
-- 核心表
1. admins -- 管理员表
2. courses -- 课程表
3. registrations -- 报名记录表
4. mini_users -- 小程序用户表
-- 内容表
5. hot_cards -- 热门卡片表
6. feeds -- 动态内容表
7. event_timeline -- 事件时间线表
-- 照片表(6个)
8. creative_art_photos -- 儿童创意美术照片
9. competition_training_photos -- 竞赛特训照片
10. color_cube_photos -- 色彩魔方照片
11. pen_art_photos -- 笔尖艺术照片
12. nature_sketching_photos -- 自然写生照片
13. creative_workshop_photos -- 创意工坊照片
重建脚本: c:\AI\重建qianyi数据库.sql
2. 新增管理员注册接口
为了快速恢复管理员账号,在 Rust 服务器中新增注册接口:
接口地址: POST /api/qianyi/admin/register
请求体:
{
"username": "admin",
"password": "your_password",
"email": "admin@qianyi.com"
}
响应体:
{
"success": true,
"message": "注册成功",
"token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
"user": {
"id": 1,
"username": "admin",
"email": "admin@qianyi.com",
"phone": null,
"is_admin": true,
"created_at": "2026-01-23T09:00:00Z"
}
}
使用工具: c:\AI\创建管理员账号.html(独立HTML页面)
代码变更:
- 文件:
server/src/qianyi/mod.rs(新增路由) - 文件:
server/src/qianyi/handlers.rs(新增admin_register函数)
3. 安全加固建议
紧急措施(必须执行):
- ✅ 修改数据库密码为强密码(16位以上,包含大小写+数字+符号)
- ✅ 禁止数据库外网访问(
bind-address = 127.0.0.1) - ✅ 删除远程 root 权限
- ⏳ 配置防火墙规则(只允许本地访问 3306 端口)
- ⏳ 设置每日自动备份
长期措施:
- 使用专用数据库账号(不使用 root)
- 启用 MySQL 审计日志
- 定期检查日志文件
- 配置备份策略(每日全量 + 实时增量)
- 生产环境禁用注册接口
安全加固指南: c:\AI\数据库安全加固指南.md
经验教训
问题根源:
- ❌ 数据库暴露在公网(
0.0.0.0:3306) - ❌ 使用弱密码(
123a456b) - ❌ 未配置防火墙
- ❌ 没有定期备份
改进措施:
- ✅ 禁止数据库外网访问
- ✅ 使用强密码
- ✅ 配置防火墙规则
- ✅ 建立备份机制
- ✅ 新增快速恢复工具
数据恢复状态:
- ✅ 数据库结构:完全恢复
- ✅ 管理员账号:可快速创建
- ⚠️ 业务数据:需要重新录入(课程、报名、照片等)
快速参考
创建管理员账号:
- 确保 Rust 服务器运行(
cargo run --features qianyi) - 打开
c:\AI\创建管理员账号.html - 填写用户名、密码、邮箱
- 点击"创建账号"
- 使用创建的账号登录系统
数据库连接信息:
- Host:
115.190.83.144(⚠️ 已禁止外网访问,改为本地) - Port:
3306 - User:
root - Password:
已修改为强密码(不再使用123a456b) - Database:
qianyi
重要文件:
- 数据库重建脚本:
c:\AI\重建qianyi数据库.sql - 安全加固指南:
c:\AI\数据库安全加固指南.md - 管理员注册页面:
c:\AI\创建管理员账号.html - 表结构文档:
c:\AI\数据库表结构文档.md
11. ImageEnhancer
类型: Python 图片清晰度增强工具
技术栈:
- Python 3.7+
- Pillow (PIL) - 图片处理库
- Tkinter - GUI 界面(Python 内置)
核心功能:
- ✅ 超采样降噪 - 通过"放大→增强→缩小"的超采样技术提升图片清晰度
- ✅ 保持原尺寸 - 输出图片尺寸与原图完全相同,只提升清晰度
- ✅ 批量处理 - 支持整个文件夹批量处理,自动遍历所有图片
- ✅ 图形界面 - 简洁友好的 GUI 界面,拖拽式操作
- ✅ 命令行版本 - 同时提供命令行版本供脚本化使用
- ✅ 高质量算法 - 使用 Lanczos 重采样算法(业界最高质量)
工作原理(超采样技术):
步骤 1:原图(模糊)
输入: 64×64 像素,模糊、有噪点
步骤 2:AI 超分放大(Upscayl/Real-ESRGAN)
处理: 64×64 → 256×256 (4x)
效果: AI 智能补充细节、减少噪点
步骤 3:高质量缩小(本工具)
处理: 256×256 → 64×64 (Lanczos)
效果: 保留增强细节,超采样降噪
最终:原尺寸(64×64)+ 更清晰
技术原理:
- 超采样(Supersampling):图形学中的抗锯齿技术,通过高分辨率渲染后降采样减少锯齿
- AI 超分增强:Real-ESRGAN 在放大过程中智能补充细节、修复模糊
- Lanczos 重采样:数学上最优的图像缩放算法,保留频率信息最完整
- 信息保留:4x 放大后缩小,相当于 16 次采样平均,有效降噪
GUI 界面功能:
- 📂 文件夹选择器 - 可视化浏览选择输入/输出文件夹
- ⚙️ 参数配置面板:
- 放大倍数(2x/4x/8x)- 需与 Upscayl 设置保持一致
- 输出质量(80-100%)- 滑块控制 PNG/JPG 压缩质量
- 📊 实时进度 - 进度条和详细处理日志
- 📝 日志窗口 - 显示每张图片的处理详情(原尺寸→目标尺寸)
- 🎯 批量处理 - 一键处理整个文件夹
使用流程:
完整工作流(配合 Upscayl):
-
使用 Upscayl 放大图片:
- 打开 Upscayl(独立工具)
- 选择原始图集(例如:
110000_0.png,2048×2048) - 模型选择:GENERAL PHOTO (REAL-ESRGAN)
- 放大倍数:4x
- 点击 Upscayl 处理,得到 8192×8192 高清大图
-
使用 ImageEnhancer 缩小回原尺寸:
- 双击
start_gui.bat启动本工具 - Step 1:选择 Upscayl 输出文件夹
- Step 2:选择目标输出文件夹
- Step 3:设置倍数为 4x(与 Upscayl 一致)
- 点击"开始处理"
- 得到 2048×2048 清晰图片(尺寸不变,清晰度提升)
- 双击
-
结果对比:
- 原图:2048×2048,模糊、有噪点
- 处理后:2048×2048,清晰、少噪点、细节丰富
适用场景:
- ✅ 游戏素材清晰化 - 老游戏资源现代化(石器时代、魔兽世界等)
- ✅ 像素画增强 - 保持原尺寸,减少锯齿和噪点
- ✅ 老照片修复 - 去模糊、去噪点、还原细节
- ✅ UI 图标优化 - 提升图标清晰度,不改变尺寸
- ✅ 表情包清晰化 - 社交媒体图片质量提升
- ✅ 扫描文档增强 - 扫描件去模糊
配置参数说明:
Upscayl 放大倍数:
2x- 适合轻度模糊(处理快)4x- 推荐(平衡效果和速度)8x- 最强效果(需要强大 GPU)
输出质量:
80-90- 较小文件,轻微质量损失95- 推荐(平衡质量和文件大小)100- 最高质量,文件较大
输出文件命名:
- 格式:
原文件名_enhanced.png - 例如:
110000_0.png→110000_0_enhanced.png
性能指标:
- 处理速度:约 0.5-2 秒/图(2048×2048 图片)
- 内存占用:约 100-500 MB(取决于图片尺寸)
- 批量处理:约 100-200 张/分钟(取决于尺寸)
- 清晰度提升:主观评价提升 20-40%
- 文件大小:质量 95% 时约等于或略小于原图
技术特点:
- 无需 GPU:纯 CPU 处理(Pillow + NumPy)
- 无需模型:不依赖 AI 模型文件,仅做缩放处理
- 轻量级:仅需 Python + Pillow(总计约 10-20 MB)
- 跨平台:Windows/macOS/Linux 通用
- 零配置:首次运行自动安装依赖
启动脚本说明:
-
start_gui.bat(推荐):
- 启动图形界面版本
- 可视化操作
- 实时进度显示
- 适合大多数用户
-
start.bat(命令行):
- 启动命令行版本
- 适合脚本化批量处理
- 适合服务器部署
-
debug_start.bat(调试):
- 显示详细检查步骤
- 用于排查问题
- 窗口保持打开
与 Unity Gacha 项目的配合:
- Unity 导出石器时代动画图集(2048×2048)
- 使用 Upscayl 放大到 8192×8192
- 使用 ImageEnhancer 缩小回 2048×2048
- 得到清晰的原尺寸图集,可直接用于游戏
处理流程示例:
1. Unity Gacha 导出 → Assets/GeneratedAtlas/ani/110000_0.png (2048×2048)
2. Upscayl 放大 → 110000_0_upscaled.png (8192×8192)
3. ImageEnhancer 超采样 → 110000_0_enhanced.png (2048×2048, 清晰)
4. 替换原图 → 游戏使用高清资源
常见问题:
Q: 为什么不直接用 Upscayl 的原尺寸增强? A: AI 超分模型的原理是"放大+增强",无法直接原地增强。超采样是业界标准的原尺寸增强方法。
Q: 和 Photoshop 锐化有什么区别? A: Photoshop 锐化只是强调边缘,AI 超采样能真正补充细节并降噪。
Q: 处理后文件会变大吗? A: 不会。质量 95% 时文件大小约等于原图,有时甚至更小(PNG 压缩)。
Q: 可以处理哪些格式? A: 支持 PNG、JPG、JPEG、BMP、TIFF 等常见图片格式。
Q: 需要 GPU 吗? A: 不需要。本工具纯 CPU 处理,任何电脑都能运行。AI 增强部分由 Upscayl 完成(Upscayl 可选 GPU/CPU)。
项目位置:
C:\AI\ImageEnhancer\
依赖安装:
pip install Pillow
说明: 轻量级图片清晰度增强工具,专为"保持原尺寸但提升清晰度"的需求设计。配合 Upscayl 使用,通过超采样技术实现原尺寸下的智能降噪和细节增强。特别适合老游戏资源的现代化改造,如石器时代动画帧处理。提供友好的 GUI 界面和命令行版本,支持批量处理,完全免费开源。
与 Unity Gacha 的完整工作流:
Unity Gacha → 导出原图(2048×2048)
↓
Upscayl → AI 放大到 8K(8192×8192)
↓
ImageEnhancer → 超采样缩回 2K(2048×2048,高清)
↓
Unity Gacha → 加载高清图集用于游戏
这套工作流可将石器时代的像素风格资源无损提升到高清质量,同时保持原始尺寸和像素风格。
showtime_weihai 前端 dist 与 Nginx 同机部署(Ubuntu)
以下流程适用于:前端静态资源由 Nginx 提供,Java 后端监听本机端口(例如 8707),前后端部署在同一台服务器。与仅在本机 npm run dev 时使用 vite.config.js 里 proxy 转发到远程网关的场景不同;生产构建的 dist 不会包含 Vite 开发代理,浏览器只会请求当前访问站点同源下的路径(如 /api/...),因此线上必须由 Nginx(或等价网关)把 /api 转到后端。
1. 前端工程是否需要改配置
vite.config.js中的server.port、proxy:仅影响开发服务器,不参与npm run build产物。src/utils/request.js:在普通浏览器环境下若baseURL为空,请求为相对路径/api/...,与 Nginx 反代同源方案一致时,一般无需为上线改前端。- 若页面部署在子路径(如
/app/),需同步修改 Vite 的base与 Nginx 的location,此处不展开。
2. 在服务器上定位 Nginx 配置与网页目录
ls /etc/nginx
ls /etc/nginx/sites-enabled
cat /etc/nginx/sites-enabled/default
关注 root ...;:静态文件应上传到该路径(Ubuntu 默认示例常为 /var/www/html)。可用下面命令快速查找 root:
grep -r "root " /etc/nginx/sites-enabled/
3. 推荐的 server 片段(HTTP 示例)
在 server { } 内保证:先写 location /api/,再写 location /,避免前缀匹配被宽泛规则抢先。
server {
listen 80 default_server;
listen [::]:80 default_server;
root /var/www/html;
index index.html index.htm;
server_name _;
location /api/ {
proxy_pass http://127.0.0.1:8707;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
}
location / {
try_files $uri $uri/ /index.html;
}
}
说明:
try_files ... /index.html:单页应用(Vue Router history 等)刷新子路由时避免直接 404。proxy_pass:http://127.0.0.1:8707(无末尾/):请求/api/xxx会原样转到http://127.0.0.1:8707/api/xxx,适合后端路径带/api前缀。http://127.0.0.1:8707/(有末尾/):会去掉location匹配的前缀,适合后端没有/api、需与开发环境rewrite去掉/api的行为对齐时尝试。
修改后执行:
nginx -t && systemctl reload nginx
4. 本地构建与上传到服务器
- 在项目目录执行:
npm run build,得到dist目录。 - 将
dist目录内的全部内容(index.html、assets/等)上传到 Nginxroot所指目录(如/var/www/html/),不要只上传一个名为dist的文件夹却期望根路径直接访问(除非 Nginx 的root刻意指向该子目录)。 - 工具可选用 WinSCP(图形界面)、scp(命令行)等。WinSCP 右侧应进入
/var/www/html(或运维指定的root),左侧进入本地dist,再上传。
5. 页面能打开但登录失败时的排查顺序
- 浏览器 F12 → 网络,查看登录请求的 URL、HTTP 状态码、响应体(业务
code/message)。 - 502 / 504:多为后端未监听或端口不一致,在服务器上确认 Java 是否在 8707 监听。
- 404:多为路径前缀与后端不一致,按上文调整
proxy_pass是否带末尾/后重载 Nginx。 - 查看 Nginx 错误日志:
tail -30 /var/log/nginx/error.log。
6. 与「仅前端 + 远程网关」旧配置的对比
若旧环境 Nginx 将 /api/ 代理到 https://gateway... 并去掉 /api 前缀,而当前后端已改为本机 Java,则应使用 http://127.0.0.1:8707(及正确的路径拼接方式),不要再指向旧网关,否则与「前后端同机」架构不一致。