Files
weihai_erp/PROJECT_OVERVIEW.md
T
xiaojibeier 67d1da71a8 2
2026-07-06 03:48:13 +08:00

221 KiB
Executable File
Raw Blame History

项目总览文档

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+ - 样式框架

核心功能:

  • 用户管理 - 用户列表与部门 KPIroles 弹窗编辑、详情抽屉只读预览)、用户信息、支付列表
  • 账户管理 - 账户列表、分组管理、订单管理
  • 课程管理 - 课程列表、课程信息、课程日历
  • 公司管理 - 公司信息、公司列表
  • 保安管理 - 保安列表、值班管理
  • 任务管理 - 任务列表、任务包管理
  • 项目管理 - 项目列表
  • 支付管理 - 支付录入、支付记录
  • 图片标注 - Canvas 图片标注工具
  • 数据看板 - ECharts 图表、数据概览、监控页面

特色功能(Mapbox 增强):

  • Mapbox 地图展示 - 登录页 3D 地图背景
  • Mapbox 船只动画 - 船只在地图上移动的动画效果
  • 地图交互 - 缩放、平移、标记点
  • 3D 地形 - Mapbox 3D 地形渲染
  • 实时天气信息 - 登录页显示天气数据
  • 人脸识别登录 - face-api.js 浏览器端识别
  • WebGL Shader 背景 - 多种炫酷特效(音频可视化、蝙蝠群、云层、分形、Menger 海绵、地形、火车)

Mapbox 功能详解:

  • 使用 Mapbox GL JS 3.18+ 最新版本
  • 支持 2D/3D 地图切换
  • 支持自定义地图样式
  • 支持 GeoJSON 数据加载
  • 支持动画路径(船只移动)
  • 支持地图标注和交互

登录页特效(最全版本):

  1. Mapbox 地图背景 - 3D 地图 + 船只动画
  2. Shader 音频可视化 - 音频频谱动画
  3. Shader 蝙蝠群 - 蝙蝠飞行效果
  4. Shader 云层 - 动态云层渲染
  5. Shader 分形 - 数学分形图案
  6. Shader Menger 海绵 - 3D 分形立方体
  7. Shader 地形 - 程序化地形生成
  8. Shader 火车 - 火车行驶场景
  9. 天气信息 - 实时天气显示

平台支持(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.vuesrc/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_pooltype: 2)。分类源:POST /api/category/listedPOST /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 等对列表字段兼容解析;normalizeWhListedCategoryparseCategoryListedTreePayload 等与分类/池字段解析一致。
全局分类缓存 src/composables/useCategoryTree.js:登录成功后 init() 预拉、setInterval 约 60s 刷新;fetchIfStale() 在未过期时直接使用内存树;物料分类 add/modify/removeMaterialCategory.vueMaterialList.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.jssrc/store/modules/user.jssrc/router/index.jssrc/views/material/MaterialCategory.vuesrc/views/material/MaterialList.vue

说明
语义 单例模块级状态:fetchIfStaleTTL ≈60s)、invalidateCRUD 后强制重拉)、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.vuesrc/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.jsmdtConstUpstreamDeps.jsmdtUpstreamIndexCache.jssrc/views/material/MultiDimensionalTable.vuesrc/views/base/ConfigManage.vue;应用内「更新说明」:src/data/appChangelog.js(同日条目)。多维表分项长文 weihai/client/showtime_weihai/docs/多维表格(MDT)_改动纪要.md 与本节互补(历史列类型、oa_config 产品差异等见该文档)。

范围 文件 变更摘要
传播少 view(客户端索引) oaConstWritePropagation.jsmdtConstUpstreamDeps.jsmdtUpstreamIndexCache.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首次 loadCfgpropagateMdtDependentConstants → 传播 finally 收起橘色常驻 ElMessage.warningduration: 0 → 若有衍生品则绿字 已按多维表公式同步…再次 loadCfg,避免首拉早于衍生 /config/set 落库。

🔗 常量信息选择过滤 · 删除常量/删列确认(2026-05-10

代码路径: weihai/client/showtime_weihai/src/views/material/mdt/LinkPicker.vuemdt/oaConfigListedMerge.jssrc/views/material/MultiDimensionalTable.vuesrc/views/base/ConfigManage.vue;应用内「更新说明」:src/data/appChangelog.js(同日组)。

范围 文件 变更摘要
LinkPicker 不展示表写回项 LinkPicker.vueoaConfigListedMerge.js 表格目标为 oa_config 时,在 fetchOaConfigMergedListed 结果上增加 isOaConfigDescFromMdtWriteback(desc) 过滤;与多维表写回说明 formatOaConstWriteDesc来自多维表格「…」· 列「…」)一致,避免「表里再选由本表写回的常量」形成闭环。库内导出 OA_CONFIG_DESC_FROM_MDT_SUFFIX_REisOaConfigDescFromMdtWriteback
删除常量二次确认 ConfigManage.vue 非写回占位项删除前:明示页签/物料/多维表可能仍引用、删除后可能失效或异常、系统不自动修改引用;确认按钮「仍要删除」;取消确认单独 try/catch,避免未处理 rejection。
删除列涉及常量时的说明 MultiDimensionalTable.vue columnDeleteTouchesOaConstds: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(与同日常量/多维条目同一日组)。

范围 说明
列表与弹窗 「部门」列仅依据 rolesdept_code + role_code 一行对应一条;「查看部门详情」:el-dialog 内 KPI 合计与分段 tooltip、表格编辑每条绑定的级别与 KPI;KPI 列表头文案为 kpi (单位: % ;保存时通过 POST /api/user/modify 携带完整 roles(字段含 role_levelkpi_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_listedcompany_code);维护:POST /api/company/link_categoryPOST /api/company/unlink_categorycompany_code + category_code);分类树与路径:POST /api/category/listed
交互 右侧列表与并行树数据生成层级路径;「管理关联」内勾选树,确认 diff 后批量增删关联;新建组织无 company_code 仅文案,不调接口。
布局与文案 弹窗主体固定高度、宽约 1200px、各栏内部滚动并避免横向条;列表/树加载「加载中」,提交关联遮罩「关联中」;标题行橘色操作提示。

📐 页签字段宽度、特征必填显隐、数字模式、公式符号、用户字段精简、财务属性 UI(2026-05-07

代码路径: src/views/material/MaterialList.vuesrc/views/material/MaterialCategory.vuesrc/views/material/TabBuilder.vuesrc/views/material/TabManage.vuesrc/views/base/StaffManage.vuesrc/views/base/BillManage.vue

范围 文件 变更摘要
字段网格列宽调整 MaterialList.vueMaterialCategory.vue 每列最小宽度阈值由 190px 改为 240px:1920 屏弹窗从 6 列→5 列(每列约 260px),2560 屏从 8 列→7 列(约 252px);分类编辑页从 7 列→6 列(约 251px)。字符长度占列规则(totalLen > 8 → span2)不受影响。
特征必填属性显隐规则 TabBuilder.vueTabManage.vueMaterialCategory.vue 页签特征属性中每个「必填」属性新增独立显隐规则设置,可为必填开关本身配置触发条件,支持条件必填场景。
数字特征输入/范围模式 TabBuilder.vueTabManage.vue 数字类特征字段新增「输入」与「范围」两种模式选项;MaterialList.vueMaterialCategory.vue 数字使用侧同步添加模式切换控件,可在单值输入与区间范围之间切换。
计算公式符号补全 TabBuilder.vueTabManage.vue 公式编辑器新增「/」(除法)和「,」(逗号,多参数函数分隔)两个运算符按钮。
创建用户字段精简 StaffManage.vue 新建用户弹窗移除若干冗余字段,表单仅保留必要信息,减少填写负担。
财务属性 UI 调整 BillManage.vue 财务属性页面布局与视觉细节优化。

🏢 组织分类票据、员工管理扩展、IM 消息修复、物料分类优化(2026-05-06)

代码路径: src/views/base/OrgManage.vuesrc/views/base/BillManage.vuesrc/views/base/StaffManage.vuesrc/views/material/MaterialCategory.vuesrc/store/modules/im.jssrc/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.jsExpenseItemManage.vue 路由标题及页面内所有「费用项目」文字改为「辅助核算」。
物料分类不参与场景提示修正 MaterialCategory.vue 「不参与场景」checkbox 的 tooltip 从错误的「勾选后物料列表不允许在此分类下新增物料」改为「勾选后该分类不出现在物料场景的选择列表里」。
物流费率组重名校验 MaterialCategory.vue 勾选「是否是物流费率组」前先调 /api/category/check_flow_group_duplicatehas_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.jssrc/router/index.jssrc/store/modules/im.jssrc/views/base/AccSubjectManage.vue

范围 文件 变更摘要
F5 / 带 token 直连仍初始化 IM user.jsrouter/index.js 新增 ensureOpenIMConnected(opts?):有 userCode 时从 store / localStorage 对齐 imToken,缺失则 loginToOpenIM,再 imStore.initIMimStore.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.vuesrc/views/base/BillManage.vuesrc/views/okr/OkrObjective.vuesrc/views/okr/OkrKeyResult.vuesrc/views/okr/OkrReview.vuesrc/store/modules/okr.jssrc/data/okrMock.jssrc/store/modules/im.jssrc/utils/socket.jssrc/components/ImPanel.vuesrc/views/material/TabManage.vuesrc/views/material/TabBuilder.vuesrc/utils/tab.jssrc/store/modules/layout.jssrc/layouts/index.vuesrc/utils/menu.jssrc/router/index.js

范围 文件 变更摘要
组织分类 CRUD OrgManage.vue 原「组织树」整体注释保留至 OrgManage.legacy.vue;新建组织分类列表(el-table,无边框/斑马纹),支持新建/编辑/删除分类;org_type / org_nature 改为固定选项下拉;描述字段改为名称后的 ? 气泡 tooltip;税务属性(销项税率/附加税率/所得税率/印花税率)在同一行展示。
组织内联展开 OrgManage.vue el-table 展开列点击后加载该分类下的组织列表(/api/company/listedclassify_code);展开区背景区分(#f4f5f9 + 阴影);新增/编辑/删除组织;表单含名称、类型、统一社会信用代码、法人、联系人等字段。
关联票据(组织) OrgManage.vue 编辑组织时可关联进项/出项票据;票据分两栏展示(进项蓝/出项橙);支持新增关联(搜索下拉弹窗)、切换启用状态、移除关联。
展开防重复加载 OrgManage.vue onExpandChangeif (orgMap[code] !== undefined) return 防二次展开重复请求;抽出 loadOrgs(code) 复用;展开工具栏加刷新按钮;加载遮罩显示「加载中…」文字。
财务属性入口 BillManage.vuerouter/index.js 路由 /base/bill 标题改为「财务属性」,图标改为 mdi:finance;进入页面先展示卡片列表(仅一张「各类型成本/费用额定增值税率」),点击卡片进入原票据管理详情;卡片样式与「页签管理」阴影一致;面包屑支持返回。
KR 协作步骤 OkrObjective.vueOkrKeyResult.vueokrMock.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.vueokrMock.js 编辑步骤时若 collaborator_code 不在用户列表则清空,防止显示原始工号;mockGetUsers 成功后同步更新已有步骤的 collaborator_code / collaborator_name
TabManage/TabBuilder 防崩溃 TabManage.vueTabBuilder.vueMaterialCategory.vue tmConflictLabels / tabBuilderConflictLabels 计算属性加 try/catch 兜底返回 new Set();模板中 .has() 调用加可选链 ?.,防止 undefined 导致页面白屏。
IM 聊天按钮尺寸 ImPanel.vue .im-tab 高度从 80px 缩小到 52px,宽度从 40px 缩小到 36px,视觉更紧凑。
IM 调试日志注释 im.jssocket.jsImPanel.vue 注释所有 [IM] / [Socket] / [preview] 前缀的 console.log,保留 console.warn / console.error 级别的错误提示,控制台不再产生大量正常流程日志。
页签字段重名可见提示 TabManage.vueTabBuilder.vue 重名字段卡片在红框基础上增加「⚠ 重名」徽标与 el-tooltip 说明(模板内重复 / 跨页签冲突 / 保存校验闪红);明细/表格 footer 同步展示;模板中 tmInternalDupLabelsinternalDupLabels 去掉误用的 .value,与 Vue 自动解包一致。
KR 进度展示一致 OkrObjective.vueOkrKeyResult.vue 列表行 el-progress 改读 kr.progress(与 calcKrProgress 一致),修复「目标值小于起始值」类 KR 误用 current_value ÷ target_value 显示成 100% 的问题;KR 详情 saveDrawer 保存前回写 progress
OKR 数据持久与重映射 okr.jsOkrReview.vue(消费 store 复盘 reviews 在 owner 重映射时与关联 objective 对齐;_remapOwnersIfNeeded 改为逐字段仅替换「仍不在真实用户表中的 code」,避免 F5 后用户已改的负责人被全表覆盖;STORE_VER 升级可清陈旧 localStorage(若当轮有调整)。
顶部多页签可关规则 router/index.jslayouts/index.vuemenu.jslayout.jsutils/tab.js 仅路由名 Index(首页)closable: false,其余路由均可关闭;router.afterEach 与侧栏 addTab 统一用 isTabClosableRouteName;路由表移除分散的 meta.noClosableaddTab 在页签已存在时若 closable 变化则更新;closeTab 对首页二次拦截。

📋 OKR 列表排版修复 & 下拉选中显示修复 & 复盘负责人修复(2026-05-04

代码路径: src/views/okr/OkrKeyResult.vuesrc/views/okr/OkrObjective.vuesrc/views/okr/OkrReview.vuesrc/data/okrMock.jssrc/assets/styles/variables.css

范围 文件 变更摘要
KR 列表列宽重调 OkrKeyResult.vue 周期列 44px、部门列 52pxtext-align: center),负责人 88px;进度列加宽至 162px、截止日期加宽至 118pxcol-title / col-obj 保持 flex:1 / flex:0.6 弹性,右侧固定列不再拥挤。
全局下拉选中空白修复 variables.css 全局 .el-select--small .el-select__selectionoverflow: hidden 改为仅对含 .el-tag(多选)时生效;单选框选中文字不再被 overflow 裁切(Element Plus 单选占位层带 z-index:-1,外层 overflow 会令其不可见)。
下拉选项 label 字段兼容 OkrKeyResult.vueOkrObjective.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 用户列表 normalizeuser_code 统一 String()realnameusername 兜底;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.jssrc/components/ImPanel.vuesrc/data/okrMock.jssrc/views/okr/OkrObjective.vuesrc/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.vueim.js 群聊头部新增成员按钮;点击调 imStore.getGroupMembers(groupID) 弹窗展示;修复 getGroupMembers 返回 res?.members || res?.groupMembers 的取值层级错误(_adminPost 已解包 .data)。
OKR 接入真实 API okrMock.js mockGetUsers/api/user/listed,成功后覆盖 MOCK_USERS 并按轮询方式将真实用户分配给 MOCK_OBJECTIVES 及其 key_resultsowner_code / owner_namemockGetDepts/api/company/listed + /api/dept/listed,返回字段统一为 dept_code / dept_name / full_name;均有 mock 数据降级兜底。
OKR 进度计算与单位 OkrObjective.vueOkrKeyResult.vue 进度改为 (current_value / target_value) * 100el-progress 百分比、主文字均以此为准;次要行显示 current_value / target_value(含 unit)。Mock 数据单位 's''秒''h''小时'
OKR 目标列表排版 OkrObjective.vue col-summary 改为 flex 横排 + mask-image 右侧渐变遮罩展示 KR 概要 chips(不截字符而是自然溢出渐隐);progress-wrap gap 收至 4pxprogress-text min-width 收至 36pxcol-summary padding-left 加大至 20px 与进度列拉开间距;col-owner 100pxcol-progress 148px。
OKR KR 列表排版 OkrKeyResult.vue 各列 flex / px 比例全面重算:col-badge 44pxcol-title flex:1col-obj flex:0.6col-period 46pxcol-dept 52pxcol-owner 88pxcol-progress 162pxcol-date 118pxcol-ops 68px。

📋 物料页签 UI、控件库排版与分类字段抽屉(2026-04-06)

代码路径: weihai/client/showtime_weihai/src/views/material/(及同阶段若已合并的 src/layouts/src/store/modules/layout.js

范围 文件 变更摘要
table 控件展示名 TabBuilder.vueTabManage.vue 界面与映射中文案 「明细表格」→「明细/表格」(控件库、字段类型名、预览与画布提示、校验与注释等)。
控件库两列排版 TabBuilder.vueTabManage.vue 左侧控件栏 256px(原 210px);.tb-wi-label / .tm-wi-label 使用 white-space: nowrap,避免「明细/表格」在两列网格内折行。
注释统一 MaterialList.vueMaterialCategory.vue 注释 / 样式注释中「明细表格」→「明细/表格」,与界面一致。
切换分类仍显示旧「编辑字段」 MaterialCategory.vue 新增 resetCatFieldEditorUI()cancelBatchCatOptRules、清空 catFieldDrawer、关闭 custFieldRuleDlgVisible / catOptRuleDlgVisible、重置 catOptBatchSelected;在 openForm 开头watch(formVisible)(面板关闭)调用。根因:openForm 深拷贝替换 form.tabs 后抽屉仍引用旧字段对象。
keep-alive / 脏页(同阶段) layouts/index.vuestore/modules/layout.jsMaterialCategory.vueTabManage.vueMaterialList.vueSceneManage.vue 多页签缓存、Tab 未保存指示、setPageDirty / onBeforeRouteLeavedefineOptions 等以仓库实际提交为准。

📋 物料列表选配编辑器(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.vuesrc/views/material/mdt/ColConfigPanel.vueLinkPicker.vueFormulaEditor.vuesrc/composables/useFormulaEngine.js;物料分类/列表与权限:MaterialCategory.vueMaterialList.vue、Pinia 与路由权限(以仓库实际提交为准)。

范围 变更摘要
动态表 CRUD POST /api/excel/dynamic/listedviewaddmodifyremove;数据源清单:/api/excel/dynamic/data_source/listed
列类型 基础字段;关联多维表(仅动态表,手动选记录);数据源选择(系统数据源,dsTarget + dsDisplayField);查找引用(经关联列 或 直接查表 + lookupConditions + lookupAggregate);计算公式单选 / 多选(自定义 options + 色板,或 optionSource: dict + dictKey,字典加载与 TabManage 同源 /api/dict/listed;单元格存字典子项 key)。
列配置与层级 ColConfigPanelfreeze 避免遮挡公式编辑器;面板 贴屏幕右缘;字典分组选择器与预览条数。
表体与列操作 内联编辑;行右键插入/删除与删除确认;列头菜单(改列、复制列、隐藏列、按列筛选);工具栏 筛选N 列已隐藏;列宽默认值与最小宽度调整。
保存与体验 自动保存防抖、isDirty、手动保存按钮态;底部 自动保存 Toast;切换路由/换表未保存提示。
公式 evaluateFormulabuildResolvedCells(关联、lookup、单选/多选解析为展示值供公式引用);列定义与行数据随表内容持久化。
物料与权限(同阶段) 仓库等页签字段与其它 tab 展示对齐;仓库选择条 横向滚动;子仓随父仓权限展示;页面级 查看 / 增 / 改 / 删leader_level=99 超级管理员绕过;scope 空与全量语义。

多维表 content 约定(节选): columns[]idnametypewidthhiddenoptionsoptionSourcedictKeylookupModedirectTablelookupConditionslookupAggregatedsTargetdsDisplayFieldformula 等;rows[].cellscolId → 存值。字典列与页签字段一致存 编码;当前页字典在进页预拉,后台字典变更后建议 整页刷新 以刷新缓存。

📋 特征分类、配置项管理、页签字段 key 加载优化(2026-04-27 ~ 2026-04-28

代码路径: src/views/material/FeatureManage.vuesrc/views/base/ConfigManage.vuesrc/views/material/TabManage.vueTabBuilder.vueMaterialList.vueAssemblyManage.vuesrc/router/index.jssrc/data/appChangelog.js

范围 文件 变更摘要
特征分类左侧树 FeatureManage.vue 新增可折叠/拖拽调宽的左侧分类侧栏(复用 useResizablePanel);分类树支持两级展开(expandedCodes reactive 对象驱动展开状态),支持新建/编辑/删除根分类与子分类(`/field/classify/add
特征归类操作 FeatureManage.vue 卡片新增 checkbox 多选 + 底部「归类」按钮 + 右上角「···→移动到…」三种归类入口;批量选中时顶部显示「批量归类 (N)」按钮;归类弹窗展示完整分类树(含未分类);归类成功后刷新列表(/field/modifyclassify_code/field/listedclassify_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.vueTabBuilder.vue 打开字段抽屉时立即显示「加载中…」,异步调 /field/view(传 field_code)回填 key;去掉 openFieldDrawer/closeFieldDrawer 中的 key 校验,仅在点确认时验证;回填完成后重新 snapshot 防止误触"有未保存改动";isFormDirty 比较时剥离 key 字段。
选配管理增强 AssemblyManage.vueMaterialList.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.vueTabManage.vue

范围 文件 变更摘要
明细列区禁止外部拖入 TabBuilder.vueTabManage.vue 将列区 Sortable 的 group.put['tb-fields', 'tb-table-cols'] 改为 ['tb-table-cols'],从根本上阻止左侧控件列表拖入;删除之前通过 onMove 返回 false 拦截的错误实现及处理"拖入自动建列"的 onAdd handler。已有列之间的拖拽排序不受影响。
列只能点击添加 TabBuilder.vueTabManage.vue 移除所有"从左侧拖入创建列"的代码路径,统一通过右下角「+ 添加列」下拉菜单选择类型添加。
列区提示文字优化 TabBuilder.vueTabManage.vue 画布列区提示:颜色改为醒目橘色(#f07c13),字重加粗;末尾追加灰色小字「· 列只能点击添加,不支持从左侧拖入」,始终常驻(有列/无列均显示)。右侧属性抽屉中列列表空状态文字由「从左侧拖入…」改为「点击右下角 + 添加列 来添加」,与实际交互一致。

效果说明:

  • 用户从左侧控件列表往明细表格列区拖拽时,Sortable.js 自动拒绝(光标显示禁止符),无需任何额外代码。
  • 明细列区的橘色提示始终可见,用户可随时了解添加方式和操作限制。

📋 物料分类交互优化与页签字段重名实时检测(2026-04-23)

代码路径: weihai/client/showtime_weihai/src/views/material/MaterialCategory.vueTabBuilder.vueTabManage.vue

范围 文件 变更摘要
确认按钮视觉状态 MaterialCategory.vue 右下角「确认」按钮根据 isFormDirty 动态切换颜色:无改动时灰色#c0c4cc),有改动时蓝色#409eff);灰色状态下仍可点击,纯视觉提示。通过 .btn-undirty CSS class 实现,不影响 disabled 状态。
脏检测范围扩展 MaterialCategory.vue formInitialSnapshotisFormDirty computed 新增 desc(描述)、warehouse(仓库管理)、featureRelList(特征关联)字段参与快照比对,原来只检测 name + tabs + fieldMap + rules,现全覆盖。
路由离开不拦截 MaterialCategory.vue 移除 onBeforeRouteLeave 中的弹窗拦截逻辑(原来离开路由会弹"有未保存改动"确认框)。原因:页面已 keep-alive 缓存,路由跳走再回来状态完全保留,数据不会丢失,弹框无实际意义。改为直接 next(),仅保留 Tab 脏状态圆点指示。
页签构建器字段重名实时检测 TabBuilder.vueMaterialCategory.vue TabBuilder 新增 conflictLabels propSet<string>);三处字段卡片(顶层分组、子组普通字段、子组 table 字段)绑定 tb-fc-conflict classCSS 添加红色边框 + 淡红色 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 classCSS 同 TabBuilder。检测范围为模板内部重名(独立模板不做跨模板检测)。

效果说明:

  • 用户在页签构建器中修改字段名称时,若与其他页签字段重名,卡片立即出现红色边框,无需等到点保存才提示。
  • TabManage 中同一模板内有重名字段时同样实时标红,帮助工作人员快速定位问题字段。

📋 WebSocket 实时连接、选配关联展示优化与仓库管理精简(2026-04-22)

代码路径: src/utils/socket.js(新增)、src/store/modules/user.jssrc/router/permissions.jssrc/views/login/Login.vuesrc/views/material/MaterialList.vueMaterialCategory.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 中的 userIduserId 字段从响应 user_id 读取并持久化到 localStorage
页面刷新后恢复连接 src/router/permissions.jsLogin.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.vueMaterialList.vue permittedWarehouses / mlPermittedWarehouses computed 末尾追加 .filter(w => !w.parent_code),过滤掉所有子仓库,仓库管理页签只呈现顶层父仓库。

📋 物料特征信息交互与分类字段序列化修复(2026-04-20)

代码路径: weihai/client/showtime_weihai/src/views/material/MaterialList.vueMaterialCategory.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 / checkboxoptionsRaw 初始化 options 数组,radio 字段打开抽屉时 optionsundefined,编辑列表显示「暂无选项」。修复:同样对 radio 执行 parseImportOptions(field.optionsRaw) → field.options 的初始化,已有数据可正常回显与编辑。

关键逻辑说明:

  • isAssemblyOption(cat, field, optLabel) 判断某选项是否来自 assembly_goods,返回 true 时模板附加 is-assembly class,触发空心圆样式。
  • getFieldOptionsForCat(cat, field) 合并原有选项与追加选项并去重(同名只保留原有的),保证追加选项排在末尾。
  • cleanFieldoptions 字段现统一格式:['选项1', '选项2', ...](纯字符串数组),radioselect/multiselect/checkbox 一致。

📋 物料场景管理重设计(2026-04-18 ~ 2026-04-19

代码路径: weihai/client/showtime_weihai/src/views/material/SceneManage.vuesrc/utils/uploadAsset.jssrc/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 + 预览缩略图)。上传调用 uploadAssetPOST /api/assets/upload),将完整响应对象({ url, name, assets_code, type, size })存入 sceneForm.coversubmitSceneFormclassify/add 之后额外调用 /api/scene/add 写入 cover,避免 cover 被直接作为字符串传给后端(后端要求 JSON 对象)。
场景封面上传(详情) 详情页顶部操作行加「更换封面」el-button(含小缩略图);右侧栏在「非公共属性」上方加独立 sm-pool-cover 卡片预览区(2:1 比例,hover 显示「更换」遮罩)。上传同样存完整对象至 activeCoversaveConfig 携带 cover 字段写入 /api/scene/modifycoverUrl(cover) 工具函数统一从对象 cover.url 或字符串 cover 中提取 URL 并调用 getAssetUrl
分类字段详情懒加载 根本原因/api/category/listed 不返回 fields(包含 _tabsfieldMap),导致 fieldPool 始终为空,四个属性区域全空白。修复:新增 ensureCatDetail(code) — 调用 POST /api/category/view 拉取单个分类完整 fields 并回填树节点的 tabs / fieldMapcatDetailCacheSet)避免重复请求。新增 ensureCatAndAncestors(code) — 同时加载目标节点及路径上所有祖先(inherited-extended 页签合并需要)。confirmCatSelection 改为 async,确认前先 await Promise.allSettled(...ensureCatAndAncestors)selectScene 中加载已保存 _categories 后同样先 await 再构建 selectedCategories。此模式与 MaterialList.vuemlEnsureNodeDetail + /api/category/view 机制一致。
分类/组织 tag 区域限高滚动 sm-cat-row-scrollmax-height: 82px(约 2.5 行)、overflow-y: auto;自定义 4px 细滚动条(默认 #dcdfe6hover #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 拼接完整访问路径。
  • catDetailCachenew 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/listedfields{ groups: [...] }。增加页签分类层级:/api/tab/classify/*,先选分类再管理页签;listed 需传 classify_code。字段模型:dictKeykey,新增 is_private(私有/不对客户端展示);createFieldFromType / normalizeFields 兼容旧 dictKey
布局与弹窗 TabManage.vue + layouts/index.vue el-dialogappend-to=".main-container"(首屏 Teleport 找不到节点);改为 body + position:fixed 限定主内容区。路由 router-view 去掉会卡死的 transition mode="out-in",避免进入页签管理后子页面白屏。
物料分类 MaterialCategory.vue 导入页签:模板列表改为接口拉取(遍历分类再 /api/tab/listed),不再读 localStorage保存fields._tabs 等到 /api/category/modifyfieldMap / catFieldDrawerdictKeykey,兼容 f.key ?? f.dictKey模板同步openForm 内先 loadImportTabList().then(...)checkAndSyncTemplateChanges(),避免列表未加载完误判「模板已删除」。分类树 fields 解析:剔除 _tabRules,避免误计「1 个属性」。分类树节点展示服务端下发的 code(子级 code 可由后端拼接,前端只展示)。
自定义页签构建器 TabBuilder.vue 与页签管理字段结构对齐:keyis_private,字典绑定用 keynormalizeGroups 迁移旧 dictKey;右侧抽屉增加 Key 与私有开关。
后端约定(字段) sourcecustom / dictkey 统一表示字典标识或自定义业务键(替代原 dictKey);is_private 表示仅内部可见;前端 idf_xxx 仍用于拖拽/公式/显隐引用,可与后端业务主键分离。

📋 物料模块补充(2026-03-28

代码路径: weihai/client/showtime_weihai/src/views/material/src/utils/fieldVisibility.jssrc/router/

范围 文件 变更摘要
特征列表 FeatureManage.vue 只读列表:POST /api/field/listed 空 body;卡片网格展示 key、source、links、cname/ctime、uname/utime;无增删改。
路由与标题 router/index.jsrouter/permissions.js 菜单名「特征列表」;document.title 仅用路由 meta.title,不再加后缀。
显隐工具 fieldVisibility.js 新增 countRuleRefs(删前统计引用条数)、purgeRules(按字段 id 清理条件并剪枝空组);规则结构仍为 field.rules / group.rules / optionRules,条件 { id, fieldId, operator, value }
页签管理 / TabBuilder TabManage.vueTabBuilder.vue 删字段/删组:始终确认;有引用时提示;确认后 purgeRules
物料分类 MaterialCategory.vue 跨页签显隐globalValueMap 汇总全部分类页签字段当前值;条件 fieldId 支持 tabCode::字段id,与同模板多页签引入时的 id 区分;同时写入纯 fieldId以兼容历史已存规则;打开显隐弹窗时 migrateCatConditions 将旧纯 id 升级为复合键;删自定义/继承追加字段与分组时 countRuleRefs / purgeRules 传复合键+纯 id,并清理 fieldRules / groupRulespurgeCatTabRules)。引入页签分类级规则存 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.htmlpublic/

范围 文件 变更摘要
业务场景(物料场景) SceneManage.vue 由卡片列表改为:大场景 Tab/api/scene/classify/listed,失败则仅列场景)+ 左侧场景列表 + 右侧配置listedclassify_code可拖拽分隔条useResizablePanelscene_sidebar_width)。关联物料末级分类(树多选,仅 is_final 末端;移除分类时若属性池已用字段则 ElMessageBox 确认)。字段池:从分类 tabs.groups + fieldMap 提取,过滤 is_private,按 key/label 去重(先遍历到的分类优先);引入页签通过 /api/tab/classify/listed + /api/tab/listedtabTemplateMap,按 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-bardisplay:none 隐藏的问题;mobile-sub-tabs 改为展示 layoutStore.tabs(已打开页面)而非侧栏子菜单;移动端 Tab 关闭按钮样式等。
路由 router/index.js router.afterEachto.matched.length < 2 时不加 Tabdocument.title = to.meta?.title(无后缀);Tab 同步失败日志英文化。
布局 Store store/modules/layout.js Tab 增删使用不可变更新(如 [...this.tabs, tab] / filter),保证响应式。
物料分类保存 MaterialCategory.vue cleanField 序列化时保留 optionRulesoptionsRaw,修复自定义页签多选 选项级显隐 保存后丢失。
配置表(价格本) views/tools/PriceBook.vue syncDefaultValuesFromContent:从服务端返回的表格单元格同步 inputArgs[].default_valueloadFromServer / doSaveModify 中在 modifyloadWorkbookData 之后调用,避免参数面板与表格不同步;单元格无数据时清空默认展示。
特征列表 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.jssrc/utils/fieldVisibility.jssrc/views/base/WarehouseManage.vue

范围 文件 变更摘要
业务场景三级结构 SceneManage.vue 将「分类+场景」二级结构升级为 L1(顶部 Tab)/ L2(侧栏)/ L3(配置实体) 三级。L1 = 无 parent 的 classifyL2 = 有 parent 的 classifyL3 = scene 实体,绑定 L2 的 classify_code。字段配置只允许在 L3 进行。L2 节点显示「创建场景」按钮(可创建多个 L3,不再限制一个)。点击 L2:若无 L3 子节点则右侧显示「请先创建场景」提示;若有 L3 则自动选中第一个 L3 并展示配置。点击 L3:右侧展示该场景配置。顶部「新建场景」→「新建一类」,侧栏「新建」→「新建二类」,L2 创建按钮为「创建场景」。saveConfig() 成功后 ElMessage.success('保存成功')
业务场景关联组织 SceneManage.vue 右侧「关联组织」与「关联物料末级分类」并排 2026-05-08 已移除:删除关联组织整个区块、弹窗、state(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 → 取模板 groupscustom → 取节点 groups),再合并 extraGroupsname 同理从祖先链读取,使用局部变量而非修改响应式数据,避免 computed 副作用。
公式字段显隐修复 MaterialList.vueTabBuilder.vueTabManage.vue materialValueMap / previewValueMap / tmPreviewValueMap 均改为两轮求值:第一轮填普通字段,第二轮连续 3 次计算 formula 类型字段(支持公式引用公式的多层传递)。公式求值函数 evalFormulaValue 提取到 fieldVisibility.js 统一 export。公式字段在物料弹窗中渲染为只读灰底展示框,不再是可编辑输入框。
计算公式互引用与循环检测 TabBuilder.vueTabManage.vue 公式编辑器新增「控件(计算公式)」按钮区(绿色,可点击引用其他公式字段)。新增 hasCircularRef DFS 算法:引用前实时过滤掉会导致循环的公式字段(不在按钮列表中显示);feInsertField 插入时二次校验;validate()/saveTab() 保存时全量检测,有循环则 ElMessage.error 并阻止保存。两个文件均已改动。
多选字段选项级显隐 TabBuilder.vueTabManage.vue 选项级显隐(眼睛图标)原只支持 checkbox,现扩展至 multiselect['checkbox','multiselect'].includes(type))。两处文件均已改动,提示文字同步更新。
字段属性顺序与必填 TabBuilder.vueTabManage.vue 右侧属性面板中 Key 移到「字段标签」上方Key 和字段标签均为必填,保存时校验为空则阻止并提示。两处文件均已改动。
fieldVisibility.js src/utils/fieldVisibility.js 新增 export evalFormulaValue(formula, valueMap):安全执行公式字符串(正则过滤注入,new Function 求值),出错返回空字符串。三处调用方统一 import 此函数。
物料 CRUD 对接真实 API src/api/material.js 全部移除 localStorage mock。getMaterialListPOST /api/goods/listedcategory_code, keyword, page, limit);addMaterialPOST /api/goods/addname, desc, fields, category_codes);updateMaterialPOST /api/goods/modifygoods_code, name, desc, fields);deleteMaterialPOST /api/goods/removegoods_code)。fields 存储 tabValues + _code
仓库档案责任人显示 WarehouseManage.vue 树节点「责任人」原显示 user_code,新增 getUserName(userCode) 函数(复用已加载的 userList),改为显示 realnameusername

数据约定补充(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-extended tab 的 name 不存储在子分类节点自身,必须沿祖先链读取,不可修改响应式 t.namecomputed 副作用)。
  • 公式字段值的计算依赖 evalFormulaValue 做 3 次迭代,支持公式链(A→B→C),但不支持真正的无限递归(DFS 已拦截循环)。
  • 物料表格末尾预留空白列(min-width:60),后续追加字段列时在操作列前插入即可。

📋 物料列表多分类、列表数据格式与接口对齐(2026-03-31)

代码路径: weihai/client/showtime_weihai/src/views/material/MaterialList.vuesrc/api/material.js

范围 变更摘要
新增入口 顶部「新增」去掉「必须选中分类」的 disabled。未选/非末端:弹窗无分类,用户点「+ 添加分类」在树中选末端;已选末端:自动带入该分类并预填字段默认值(_getEffectiveFieldDefaults)。
多分类弹窗 materialForm.categories[]category_codecategory_namenameactiveTabCodetabValues。顶部分类用 ElTag 行内排列(当前选中 type="primary" 蓝色,其余 info;「+ 添加分类」type="warning" plain 橘色),与 SceneManage 关联分类区风格一致。
分类选择弹窗 el-tree 多选;catPickerTreeData 对非末端 disabled#default 插槽在末端节点旁展示绿色小 「末端」 标签。
新增/修改接口 仍使用 POST /api/goods/addPOST /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_codecategory_namenamefields)。物料名称:优先 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/listedconsole.log('[物料列表] 原始数据:', r);保存成功后可选再拉一次列表打印 [物料列表] 保存后最新列表:(便于联调,上线前可按需删除)。

📐 计算公式弹窗改版与 Excel / Exec 参考转换(2026-05-08

代码路径: weihai/client/showtime_weihai/src/components/formula/FormulaEditorPanel.vueweihai/client/showtime_weihai/src/utils/excelFormulaToReference.jsconvertExcelFormulaToReference)。

嵌入页面: MaterialCategory.vue(物料分类自建字段公式)、TabBuilder.vueTabManage.vueAssemblyManage.vue(选配画布公式)。

范围 变更摘要
布局与宽度 弹窗约 900px,类名 fe-dialog-widewidth: 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.vueMaterialList.vue

范围 文件 变更摘要
计算公式点选编辑 AssemblyManage.vue 与分类页签一致:弹窗内点选画布上的数字、金额、其它计算公式与运算符,存储 {字段 id}不再在公式中点选物料页签其它字段;试算与引用范围限于选配画布。
控件库与显隐条件收窄 AssemblyManage.vue VIS_ELIGIBLE_TYPES 不含字典、明细表格作为新建显隐条件来源;控件库不再提供「字典」「明细/表格」拖入新建(历史已配置项仍可编辑)。物料页签中 type==='dict' 的字段仍可作为联动目标(tabEligibleFields 等与画布侧区分)。
数字 / 公式列宽 AssemblyManage.vue 数字与计算公式字段在画布上默认占两列(amv-field-span2),view 模式单值数字等使用 max-width:320px,减轻多位数与只读公式行被裁切。
选配显隐合并到物料页签 MaterialList.vue mfAssemblyFormulaValueMapmfCatTabFieldVisible:选配内 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.vuesrc/views/base/StaffManage.vue

范围 变更摘要
部门管理 · 布局 三列:部门树(可搜、子部门缩进) / 角色列表(部门内角色、权限/编辑/删除三点菜单) / 人员列表(依赖选中角色)。列宽可拖拽,localStorage 持久化。
部门管理 · 角色 新增/编辑角色增加 岗位类型role_typeM 管理岗 / P 专家岗);列表名称右侧 M/P 小 Tagplain);有 描述 时悬停名称显示 Tooltip。
部门管理 · 成员 点击角色行选中后第三列加载该角色下成员;关联用户弹窗:/api/user/listed 取用户(开发约定单次最多 500 条,支持本地关键词筛选),勾选后 POST /api/dept/link_user;行内 级别 110、KPI% 0100,变更防抖后分别 POST /api/dept/set_role_levelPOST /api/dept/set_kpi_rate移除POST /api/dept/unlink_user
部门管理 · 成员列表接口 已关联成员由前端常量 API_DEPT_ROLE_MEMBER_LISTDeptManage.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-treePOST /api/company/listed 树数据经 mapOrgNodes 映射为 id / label / children无效组织is_valid 为假)节点 disabled: true,节点模板旁红色 「禁用」 tag。父子联动勾选(非 check-strictly)。
最小存储集 getMinimalOrgSelection() 结合 getCheckedKeysgetHalfCheckedKeys:父节点整棵选中时只保留 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_codenamefull_nameis_validposisParentchildNames(用于父 tag 与子列表 tooltip)。
树回显 openOrgPickerorgPickerTreeRef.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.vueTabManage.vueTabBuilder.vue desc-popover 统一改为深色背景(#1d1e20)、白色文字(#f0f0f0),箭头颜色同步;样式置于非 scoped <style> 块确保穿透 Element Plus 浮层。
创建/编辑物料问号说明 MaterialList.vue 字段标签行新增 el-popover 问号图标(QuestionFilled),普通组和子组字段均有;样式复用 desc-popover 黑底白字。
必填字段开关 TabManage.vueTabBuilder.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 补充 requiredshowUnit 字段;cleanGroups 新增对 subgroups 的递归 cleanField,防止子组字段属性在保存时被丢弃。
自定义页签 type 补全 MaterialCategory.vue confirmBuilderDialog 保存自定义页签时补写 type: 'custom',修复保存后必填校验 tabType === 'custom' 不匹配的问题。
选项改名同步显隐规则 TabManage.vueTabBuilder.vue 选项输入框新增 @focus 捕捉旧值,@change 时遍历所有字段(含子组)的 rules 条件,将 eq/neq 条件值及 contains/not_contains 数组中的旧值替换为新值;同步 optionRules 的 key。
选项删除清理规则 TabManage.vueTabBuilder.vue removeOption 删除选项时同步清理 optionRules[deletedVal] 及所有字段规则中引用该选项值的条件(eq/neq 置空,contains/not_contains 过滤)。
MaterialCategory 选项文本框规则同步 MaterialCategory.vue catFieldDraweroptionsRaw 文本框新增 @focus 记录旧选项、@changesyncCatFieldOptRules():检测被删除的选项并清理相关规则条件和 optionRules key。
单位标签显示开关(showUnit TabManage.vueTabBuilder.vueMaterialCategory.vueMaterialList.vue 字段编辑器公式/数字/金额三类单位输入行下方新增「名称后显示单位」开关(有单位时才出现),默认开启。关闭后标签不再拼接 (单位)createFieldFromType / normalizeField / cleanField 均补充 showUnit: true 默认值。所有渲染处改为 field.unit && field.showUnit !== false 判断(共 22 处替换)。数字/金额输入框右侧始终显示单位(不受 showUnit 控制):MaterialCategory 补 2 处 ifi-input-unit-wrap wrapperMaterialList/预览本已有。

📋 复查修复(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 归一化(补全 requiredshowUnitoptions 迁移等),g.subgroups[].fields 未处理,历史子组字段缺少这些属性。 抽出 normalizeOneField 函数,同时对主字段和子组字段调用,确保两层字段都被归一化。
catFieldDraweramount 类型用错控件 MaterialCategory.vue 自定义页签字段编辑抽屉里,numberel-input-numberamount 却因缺少判断落入 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-wrapperposition: relative + overflow: hidden + .inventory-gridposition: 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/maxnew 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:0min-height:0 必须加)
  • 动画切换:用 position:relative + overflow:hidden 包裹,子元素 position:absolute
  • ECharts初始化:onMounted + nextTick,销毁:onUnmounteddispose() + 移除 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 动画

根本原因:

  1. App.vue 用了 :key="route.path",每次路由变化强制重新挂载组件
  2. transition name 直接读 route.meta.transition,进入首页就读到 page-fade-dashboard

修复方案(App.vue):

  • 去掉 :key="route.path",让 Vue 自己判断组件是否真的变化
  • router.beforeEach 动态控制动画名:
    • /login 进入管理后台 → page-fade-dashboard
    • 进入 /loginpage-fade-map
    • 管理后台内部跳转 → ''(无动画)

采购模块已实现功能

📄 采购订单页(src/views/purchase/PurchaseOrder.vue

功能:

  • 顶部4张统计卡片:总数 / 待审核 / 审核中 / 本月采购总额
  • 搜索筛选:订单号模糊搜索、供应商/采购组织/状态下拉(选即生效)、日期范围
  • 表格:采购号(蓝色可点击)、供应商、组织、仓库、商品、数量(DS-DIGIT字体)、总额、票据类型、状态标签、操作列(左对齐:详情/审核/归档/复制/删除)
  • 创建弹窗功能:
    • ① 采购组织自动推荐:选商品后自动出现黄色推荐条,显示推荐组织+票据类型+首选供应商,点"一键填入"自动填充空白字段(不覆盖已填内容)
    • ② 买手分层定价:费用估算面板中,按数量自动判断大(≥500件)/中(100~500件)/小(<100件)买手,显示三档价格对比,当前档高亮蓝色
    • 费用估算面板:选完供应商+商品+数量+票据类型后自动计算货款、增值税、预估运费、综合到手成本
    • 未填字段时显示灰色引导提示

数据来源: src/api/purchase.jsmock,联调时替换)


🏢 供应商管理页(src/views/purchase/SupplierManage.vue

功能:

  • 顶部4张统计卡片:总数 / 已核准(绿)/ 潜在(橙)/ 已退出(灰)
  • 两个 Tab 切换:
    • 供应商档案 Tab:规范字段表格(名称/简称/分类/管理组织/纳税类别/结算方式/账期/联系电话/状态)
      • 工具栏:名称搜索 + 分类/状态筛选 + 新增供应商按钮
      • 操作列:报价 / 编辑 / 删除
      • 分页:支持10/20/50条/页
      • 新增/编辑弹窗:供应商名称*、简称、分类*、管理组织*、纳税类别、国家/地区、交易币种、结算方式、账期、发运方式、联系电话、电子邮箱、注册地址、供应商状态、备注
      • 报价明细抽屉:沿用原有发票兼容性矩阵 + 报价对比表(按名称匹配 mockSuppliers
      • 删除:二次确认弹窗
    • 仓库调拨费率 Tab:5仓库×5仓库调拨费率矩阵,按票据类型分列
  • 移动端:全屏抽屉+返回按钮

数据来源:

  • mockVendorssrc/api/purchase.js):供应商档案 mock,规范字段,支持 getVendorList / addVendor / updateVendor / deleteVendor
  • mockSupplierssrc/api/purchase.js):采购定价数据,供报价抽屉使用,不参与档案 CRUD
  • 字典引用:dictVendorClasses / dictCurrencies / dictSettleMethods / dictShipMethods / dictCountriessrc/api/material.js

🔔 采购预警页(src/views/purchase/PurchaseWarning.vue

核心逻辑: 当前库存 ÷ 日均销量 = 预计断货天数

功能:

  • 顶部4张风险卡片:紧急(≤3天,红) / 预警(≤7天,橙) / 充足(绿) / 预警商品预估采购额(蓝),点击卡片直接筛选
  • 筛选栏:风险等级/仓库/品类,选即生效
  • 预警表:行颜色编码(红/黄/白),断货倒计时、建议采购量、参考价区间、预估费用、最优供应商
  • 点击行展开右侧抽屉:商品详情+近6个月采购价走势折线图(渐变色)
  • "一键采购"按钮:跳转采购订单页并预填商品/供应商/数量/仓库
  • 移动端全屏抽屉,顶部 padding 留出 tab 栏高度(56px)

首页大屏联动: InventoryWarning.vue 的红色"X 紧急"badge 可点击,跳转到采购预警页

数据来源: src/api/purchaseWarning.jsmock,联调时替换)


📦 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_STAGES7阶段定义常量(导出)
  • 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 显示三点菜单(添加子仓库/编辑/删除),不占仓库名宽度;底部「新增中心仓」快捷按钮
    • 中间:可左右拖拽分隔条,宽度持久化至 localStoragekey: wh_sidebar_width
    • 右侧:选中中心仓的子仓库(门店仓)列表,带表头(仓库名称/编码/所属组织/责任人);编码蓝色、组织绿色、责任人橙色;奇偶行交替背景色;hover 显示编辑/删除操作;右上角「+ 添加子仓库」按钮
  • 新增/编辑弹窗:名称*、编码、所属组织*、责任人、联系人、电话、国家/地区、行政区域、能力范围、联系地址、仓库描述、状态

移动端适配:

  • 组织管理:≤768px 统计卡片 2 列、工具栏纵向排列、表单全宽、弹窗 95% 宽;≤900px 自动隐藏"联系电话"列
  • 仓库档案:≤768px 同上,抽屉全屏,功能开关格从3列降为2列

数据来源: src/api/base.js

  • mockOrgs:组织 mock + getOrgList / getOrgTree / addOrg / updateOrg / deleteOrg
  • mockWarehouses:仓库 mock + getWarehouseList / addWarehouse / updateWarehouse / deleteWarehouse
  • getOrgNameOptions() / getWarehouseNameOptions():供其他模块下拉引用

部门管理页(src/views/base/DeptManage.vue):

  • 已对接真实 API(部门/角色 CRUD;成员关联见下)
  • 三列布局(2026-05:左 部门树(搜索、展开子部门) / 中 角色列表(当前选中部门下的角色,hover 三点菜单:权限配置、编辑、删除;点选整行用于右侧人员上下文) / 右 人员列表(仅当选中某一角色时加载)。中间、右侧与左侧之间 可拖拽分隔条,宽度持久化。
  • 角色:新增/编辑含 岗位类型 role_typeM 管理岗 / P 专家岗);列表名称旁 M/P Tag;有 描述 时悬停名称展示 Tooltip。
  • 成员(选中角色后)关联用户 弹窗从全库用户挑选(/api/user/listed,前端单次最多取 500 条并支持本地筛选),提交 POST /api/dept/link_user;行内 职位级别 110、KPI 比例 0100%),分别调用 POST /api/dept/set_role_levelPOST /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/removePOST /api/role/listed/role/add/role/modify/role/remove/role/set_power 等(与权限抽屉一致)。
  • 新增/编辑部门弹窗:部门全称、简称、编码、部门性质、状态、责任人/分管领导(/api/user/listed 下拉联动电话)、职责描述等(同前版字段,略)。
  • 移动端:小屏三列纵向堆叠;统计卡片与列表适配。

用户管理页(src/views/base/StaffManage.vue):

  • 已对接真实 API2026-03-08,原名"员工档案"改为"用户管理"
  • 开发调试(2026-05-08:仅在 npm run dev 构建下,列表加载成功后在浏览器控制台打印 /api/user/listed 返回与当前页用户数据,便于查看全部参数字段。
  • 顶部统计卡片:员工总数 / 在职 / 总计 / 已禁用
  • 工具栏:姓名/账户搜索 + 新增按钮
  • 表格字段:员工姓名 / 编号 / 账户 / 性别 / 所属组织(显示名称)/ 部门(显示名称)/ 职务 / 手机号 / 入职日期 / 状态
  • 点击行展开详情抽屉:基本信息(编号/账户/性别/生日/手机/邮箱)+ 任职信息(所属组织名/部门名/职务/入职日期/状态)+ 备注
  • 操作列:编辑 / 删除
  • 新增/编辑弹窗:
    • 所属组织:下拉选择(来自 /api/company/listed
    • 部门:树形下拉(el-tree-select,根据所属组织联动加载 /api/dept/listed,含子部门层级)
  • API 字段user_codecompany_codedept_codeusernamepasswordcodemarkrealnamebirthdaygender0女/1男)、phoneemailleader_level(职务)、join_timeis_valid0禁/1启)、pos
  • 接口:POST /api/user/listed/user/add/user/modify/user/remove
  • 表格/抽屉中所属组织和部门显示名称;找不到对应数据显示"需要调整"

银行账户页(src/views/base/BankAccountManage.vue):

  • 已对接真实 API2026-03-08,原名"企业银行账户"改为"银行账户"
  • 顶部统计卡片:账户总数 / 基本账户 / 纳税登记 / 已启用
  • 工具栏:账户名称/账号搜索 + 账户类型筛选 + 新增按钮
  • 表格字段:账户名称 / 账户编码 / 所属组织(显示名称)/ 银行账号 / 开户银行 / 账户类型Tag / 纳税登记 / 币种 / 状态
  • 点击行展开详情抽屉:基本信息 / 银行信息 / 账户属性(分三组展示)
  • 操作列:编辑 / 删除
  • 新增/编辑弹窗:账户名称*、编码、所属组织*(下拉)、开户名称*、银行名称、银行账号*、开户银行、账号省份/城市、银联号、账户类型、银行性质、开户日期、纳税登记(0否/1是)、货币种类(文本)、排序、状态、备注
  • API 字段bank_codecompany_codenamecodeaccount_namebank_nameaccount_bankaccount_numberbank_union_codeaccount_provinceaccount_cityaccount_typeaccount_propertyactive_dateis_tax_checkcurrency_varietymarkis_validpos
  • 接口:POST /api/bank/listed(空body)、/bank/add/bank/modify/bank/remove
  • 移动端适配:弹窗 92vw,圆角滚动

会计科目页(src/views/base/AccSubjectManage.vue):

  • 已对接真实 API2026-03-09
  • 顶部统计卡片:按 elements 字段统计资产/负债/权益/收入/费用/成本六要素数量(各自使用规范颜色)
  • 工具栏:名称/编码搜索 + 会计要素筛选 + 树形/列表切换 + 新增按钮
  • 树形视图:树状展开,一级科目加粗,列:科目名 / 编码 / 会计要素Tag / 余额方向(借蓝/贷红)/ 现金分类 / 数量核算 / 状态
  • 列表视图:同字段但展平
  • 操作列:添加子级(仅顶级显示,_level===0/ 编辑 / 删除
  • 新增/编辑弹窗:有上级时显示只读上级科目;编码*、名称*、会计要素*、余额方向*(debit/credit)、现金分类、赤字检查、数量核算(0/1)、排序、状态、科目说明
  • API 字段ledger_codeparent_code(子科目才传)、namecodeelementsbalancecash_classifydeficit_check_typeamount_is_checkmarkis_validpos
  • 接口:POST /api/ledger/listed/ledger/add/ledger/modify/ledger/remove
  • is_valid 兼容布尔/数字/字符串三种格式
  • mock 数据已屏蔽(base.js 中注释)

费用项目页(src/views/base/ExpenseItemManage.vue):

  • 已对接真实 API2026-03-09
  • 顶部统计卡片:总数 / 顶级分类 / 已启用 / 已停用
  • 工具栏:名称/编码搜索 + 费用业务筛选 + 新增按钮
  • 表格:树形展开,列:名称 / 编码 / 费用业务Tag / 使用业务 / 状态 / 备注
  • 操作列:添加子项(仅顶级显示,_level===0/ 编辑 / 删除
  • 新增/编辑弹窗:有上级时显示只读上级项目;名称*、编码、费用业务(6种枚举)、使用业务(文本)、排序、状态、备注
  • API 字段expense_codeparent_code(子项目才传)、namecodeexpense_typebusinessmarkis_validpos
  • 接口: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 全字段对齐 APIelements/balance/cash_classify等);子科目不显示"添加子级"按钮;is_valid 兼容布尔/数字/字符串;mock 数据屏蔽
6 费用项目 ExpenseItemManage.vue 全字段对齐 APIexpense_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 条/页
  • 详情抽屉:头像+级别+业务员、业绩数字(销售额/订单数/账期)、基础信息(编码/分类/类型/行业/组织/联系方式/地址)、交易设置(币种/结算/账期)、备注
  • 新增/编辑弹窗:客户名称*、简称、分类*、类型、级别*、行业、管理组织*、业务员、联系人、电话、邮箱、国家/地区、地址、币种、结算方式、账期、状态、备注
  • 删除:二次确认弹窗

数据来源:

  • mockCustomerssrc/api/sales.js):扩展为规范字段(code/simpleName/customerClass/customerType/industry/org/currency/settleMethod/email/country/remark),支持 getCustomerList / addCustomer / updateCustomer / deleteCustomer
  • 字典引用:dictCustomerClasses / dictCustomerTypes / dictCustomerIndustries / dictCurrencies / dictSettleMethods / dictCountriessrc/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.jsgetPurchaseOrderDetail):

  • allocSummary:统筹采购头信息(coordinator/coordinatorOrgType/totalQty/allocatedQty/pendingQty
  • warehouseAllocation:3仓库分配明细数组(含 priority/compatibleLevel/compatibleTag/allocQty/allocRatio/status 等)

数据来源: src/api/purchase.jsgetPurchaseOrderDetail(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_STAGES8阶段定义(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:接货组织(行)× 供货组织(列)完整优先级矩阵,格子颜色绿/浅绿/橙/红对应匹配程度,底部图例说明

核心数据(组件内定义):

  • RECEIVE_ORGST1/T2/T3/TX 四种接货组织(含颜色/税率/说明)
  • SUPPLY_ORGSS13/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_STAGES8阶段定义(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宽弹窗):
    1. 物料名称(必填)
    2. 物料编码(必填)
    3. 归属分类(树形下拉,必填,支持筛选)
    4. 选完分类后动态显示该分类的属性填写区(含继承属性,按数据类型渲染不同控件:字典/枚举→下拉、布尔→下拉是/否、数字→数字输入、日期→日期选择、文本→输入)
    5. 自定义属性(开关控制,启用后可动态增删属性行)
  • 修改物料(与新增保持同步,600px宽弹窗):预填物料编码、名称、分类,分类属性值回填,自定义属性回填
  • 删除物料:二次确认弹窗(el-popconfirm),删除后刷新列表
  • 自定义属性查看:点击表格「查看」按钮,右侧抽屉展示全部自定义属性(布尔显示是/否)
  • 导出:下拉菜单提供「导出选中」(至少勾选一条)和「导出当前列表」,导出为本地 JSON 文件
  • 分页:支持10/20/50/100条切换,显示总数
  • 移动端适配:弹窗全屏宽带边距,顶部移动端 header(返回按钮+保存按钮),表单 label-position top

数据来源: src/api/material.js

  • getMaterialList(params, quickFilterValues, quickFilterAttrs) — 列表查询,支持关键字、分类、快捷属性筛选(数字范围、日期范围、文本模糊、枚举精确匹配)
  • getMaterialDetail(id) — 详情
  • addMaterial(data) — 新增,存储 namecodecategoryIdcategoryAttrshasCustomAttributescustomAttributes
  • updateMaterial(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 文件(与「新增根分类」并排)
  • 分类 APIaddCategory / updateCategory / deleteCategory / deleteCategoryAll / getCategoryTreeInfo
  • 移动端适配:弹窗 calc(100vw - 24px) 宽,88vh 最大高度,圆角,内容可滚动

字典管理页(src/views/material/MaterialDict.vue)功能(已对接真实接口):

  • 页面/菜单标题改为"字典管理"
  • 左侧字典列表:调 POST /api/dict/listedbody { dict_code: '' })获取字典列表,平铺展示;鼠标悬停显示"+"按钮快速新增子值
  • 右侧条目表格:展示选中字典的 children[],支持树形展开(row-key="dict_code");第一层条目显示"添加 / 编辑",子层只显示"编辑"
  • 新增字典:调 POST /api/dict/add(带 classify_code: 'RY9A77ABKR'valuelabeldesc
  • 新增字典值:调 POST /api/dict/add(带 parent_code = 父字典 dict_codevaluelabel
  • 编辑字典/值:弹窗表单,调 POST /api/dict/modify(带 dict_codeclassify_codevaluelabeldesc
  • 移动端适配isMobilewindow.innerWidth ≤ 600)控制左右面板切换;移动端点击字典条目进入详情,顶部左箭头返回列表;弹窗宽度 92vwclass="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,面积渐变)

🆕 商品库存分布 Tab2026-03-01 新增):

  • 品类筛选下拉 + 图例说明(充足绿/低于安全库存橙/已断货红/在途蓝)
  • 分组柱状图:横轴=商品名称,每组5根柱=5个仓库,悬停显示具体数量
  • 商品 × 仓库 明细矩阵表
    • 行:商品(含品类列)
    • 列:5个仓库 + 在库合计 + 在途合计
    • 单元格颜色底色(充足/低/断货)+ 3px彩色底部条
    • 在途数量显示为蓝色 +N途
  • 点击品类筛选后同步刷新柱状图和矩阵

新增数据(src/api/inventory.js):

  • mockProductMatrix:8商品 × 5仓库,每条含 qty/safeQty/inTransit
  • PRODUCTS:商品列表常量
  • 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-loading
  • MaterialList.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 改为 async
  • MaterialList.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 functionproperties.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 formpos 字段;弹窗加"序号"输入框(el-input-number);新增/修改接口带 pos;场景卡片名称旁显示序号
5 分类管理 MaterialCategory.vue formpos 字段;弹窗加"序号"输入框;新增/修改接口带 posmapApiNode 保留 pos;树节点显示序号(灰色小字)
6 物料管理 MaterialList.vue addForm/editFormpos 字段;新增/编辑弹窗加"序号"输入框;物料列表表格新增"序号"列;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 字段(如 16-3),而非 category_codemapApiNode 新增 parentPath 参数,递归构建 codePath,子节点路径为 父code-自身code
2 修复 map 传 index 的 bug 顶层调用从 list.map(mapApiNode) 改为 list.map(n => mapApiNode(n)),避免数组 index 被误传为 parentPath,导致显示 1-X4IOMAFPDU 这类错误
3 修复 category_code 传错 mapApiNodeid 字段还原使用 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/listedclassify_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-inputel-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.groupsform.value.groups formulaRefFieldsformulaDisplayExprtestFormulaPreview 三处函数中 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 默认值变更分两种处理 有 overridechangedFields.add + overrideConflicts++,snapshot 不更新(持续提示直到用户处理并保存);无 override:自动更新 snapshotautoUpdated++
5 alert 合并显示 统一弹一个 ElMessageBox.alert,区分「有自定义值字段已标注 ⚑ 请处理」和「字段默认值已自动同步无需操作」两种描述
6 silentSaveSnapshot 只保存无冲突字段 有 override 冲突的字段 snapshot 不动,只保存自动更新的 snapshot;下次打开仍能检测到冲突,持续提示
7 「查看更新」按钮(无 override) hasTplChanged && !hasOverride 时显示「查看更新」按钮;点击弹 ElMessageBox.alert 告知「默认值已更新为 X,已自动同步」,确认后清 ⚑
8 「还原至新默认」两选项保留 hasTplChanged && hasOverride 时显示「还原至新默认」;弹确认框两个选择:「同步当前值」(清 override)/ 「仅更新默认提示」(只清 ⚑ 保留 override)
9 快照在模板同步后才存 openFormnextTick 改为先跑 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 三入口统一拦截 handleAddRoothandleAddChildhandleEdit 均套 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 / formulasource"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.vuebottomNavItems 硬编码,新增菜单后需手动同步。

当前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 不适合高频更新。解决方案:

  1. 降低更新频率(interval: 500 或更高)
  2. 或使用 HTML Marker(需要修改实现)

Q: 图标不显示?
A: 检查:

  1. 图片路径是否正确
  2. 图片是否存在于 src/assets/icons/ 目录
  3. 控制台是否有错误日志
  4. 屏幕宽度是否 > 1024px

Q: 数字动画不生效?
A: 确保:

  1. content 字段是数字类型,不是字符串
  2. animation.enabled = true
  3. startValue < content(递增动画)

Q: 如何调整元素位置?
A: 修改配置中的 coordinates 字段:

coordinates: { 
  lng: 122.24,  // 经度(东西方向,数字越大越往东)
  lat: 37.48    // 纬度(南北方向,数字越大越往北)
}

💡 最佳实践

  1. 图标命名:使用语义化的 ID,如 ship-1warehouse-main
  2. 颜色选择:使用对比度高的颜色和描边,确保可见性
  3. 动画时长2-3 秒的动画效果最佳(duration: 2000-3000
  4. 更新频率:建议 interval >= 100ms,避免闪烁
  5. 坐标精度:使用小数点后 2 位精度即可(如 122.24
  6. 图片优化:使用 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 蓝色风格
  • 文字自动添加阴影效果,提升可读性

两种点击行为:

  1. 弹出消息(默认)
<MapOverlay
  :enable-popover="true"
  detail-action="message"
  detail-message="威海港详情当前停泊8艘船只"
/>
  1. 跳转路由
<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'
  // 自定义额外处理逻辑
}

🔧 技术实现细节

密码哈希存储:

路由守卫调整:

  • 移除了登录后强制跳转到 /login 的逻辑
  • 允许用户正常访问首页 / 和其他管理页面
  • 登录后的默认页面由登录逻辑控制

Vite 代理配置(开发环境):

proxy: {
  '/api/qianyi': {
    target: 'http://115.190.83.144:8080',   // Rust 服务器
    changeOrigin: true,
    secure: false
  }
}

📦 采购管理系统需求分析

🎯 系统概述

项目目标: 开发一个智能采购管理系统,基于历史销售数据和业务预测,自动计算最优采购数量和采购时机。

核心价值:

  • 降低库存成本 - 避免过度采购导致资金占用
  • 防止缺货 - 确保库存能满足销售需求
  • 智能预警 - 提前提醒采购时机
  • 多维度管理 - 支持多仓库、多组织类型、多供应商

技术架构:

  • 前端Vue 3 + Element Plus(威海地图项目集成)
  • 后端Rust 服务器(qianyi 模块)+ Go 服务器备选
  • 数据库MySQLqianyi 数据库)
  • 计算引擎:后端实时计算 + 定时任务分析

📊 核心业务逻辑

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
  • 🗄️ 数据库设计:待设计(上述表结构为初步方案)
  • 💻 后端接口:未开始
  • 🎨 前端页面:未开始
  • 📊 模拟数据:待生成

下一步建议:

  1. 设计并创建采购相关数据库表
  2. 生成模拟历史销售数据(365天)
  3. 开发采购需求计算接口
  4. 开发前端采购主页

🎯 悬停弹窗功能

当文字内容存在时,鼠标悬停到文字上会自动显示一个悬浮弹窗,包含"查看详情"按钮。

特性:

  • 自动触发:鼠标移入文字时显示,移出时隐藏
  • 渐入动画: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 事件

💡 使用建议

使用 MapOverlayDOM)适合:

  • 需要频繁更新内容(避免闪烁)
  • 需要复杂样式(渐变、阴影、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):

  1. 加载阶段:紫色渐变背景 + 进度条(0-100%)+ "正在初始化系统..."
  2. 地图展示Mapbox 2D 地图淡入 + 天气信息(右上角)
  3. 登录提示:中心显示"请点击登录"(闪烁呼吸动画)
  4. 点击交互:点击屏幕任意位置,弹出登录表单
  5. 登录表单:白色毛玻璃卡片,支持密码/短信登录切换
  6. 10 秒自动关闭:无操作自动隐藏登录表单,恢复到提示状态
  7. 登录成功:表单消失 → 地图 2D→3D 转换(2 秒)→ 数据面板滑入(1.7 秒后)
  8. 数据展示:左侧 3 个 + 右侧 3 个面板,从两侧滑入带回弹

页面流程(有 Token):

  1. 加载阶段:同上
  2. 地图展示Mapbox 2D 地图淡入
  3. 自动转换:地图准备好后立即执行 2D→3D 转换(无需登录)
  4. 数据展示:数据面板自动滑入

登录功能:

  • 密码登录:用户名 + 密码
  • 短信登录:手机号(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/qianyihttp://115.190.83.144:8080
  • 热更新Vite HMR
  • 类型检查TypeScript strict mode

移动端适配:

  • 断点
    • 移动端:< 640px (sm)
    • 平板:640px - 1024px
    • 桌面:≥ 1024px (lg)
  • 页面容器:桌面固定,移动端可滚动
  • 数据面板:桌面横向,移动端纵向
  • 天气组件:响应式字体大小
  • 登录提示:响应式字体和间距

动画时序:

  1. 加载动画0-100% 进度条(2 秒)
  2. 地图淡入opacity 0 → 10.8 秒)
  3. 地图 3D 转换2 秒平滑过渡
  4. 数据面板滑入:地图转换 1.7 秒后开始(800ms 滑入 + 回弹)
  5. 整体流程:约 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 Secretqianyi-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. 安全加固建议

紧急措施(必须执行):

  1. 修改数据库密码为强密码(16位以上,包含大小写+数字+符号)
  2. 禁止数据库外网访问(bind-address = 127.0.0.1
  3. 删除远程 root 权限
  4. 配置防火墙规则(只允许本地访问 3306 端口)
  5. 设置每日自动备份

长期措施:

  1. 使用专用数据库账号(不使用 root
  2. 启用 MySQL 审计日志
  3. 定期检查日志文件
  4. 配置备份策略(每日全量 + 实时增量)
  5. 生产环境禁用注册接口

安全加固指南: c:\AI\数据库安全加固指南.md

经验教训

问题根源:

  1. 数据库暴露在公网(0.0.0.0:3306
  2. 使用弱密码(123a456b
  3. 未配置防火墙
  4. 没有定期备份

改进措施:

  1. 禁止数据库外网访问
  2. 使用强密码
  3. 配置防火墙规则
  4. 建立备份机制
  5. 新增快速恢复工具

数据恢复状态:

  • 数据库结构:完全恢复
  • 管理员账号:可快速创建
  • ⚠️ 业务数据:需要重新录入(课程、报名、照片等)

快速参考

创建管理员账号:

  1. 确保 Rust 服务器运行(cargo run --features qianyi
  2. 打开 c:\AI\创建管理员账号.html
  3. 填写用户名、密码、邮箱
  4. 点击"创建账号"
  5. 使用创建的账号登录系统

数据库连接信息:

  • 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 像素,模糊、有噪点
  
步骤 2AI 超分放大(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):

  1. 使用 Upscayl 放大图片

    • 打开 Upscayl(独立工具)
    • 选择原始图集(例如:110000_0.png2048×2048
    • 模型选择:GENERAL PHOTO (REAL-ESRGAN)
    • 放大倍数:4x
    • 点击 Upscayl 处理,得到 8192×8192 高清大图
  2. 使用 ImageEnhancer 缩小回原尺寸

    • 双击 start_gui.bat 启动本工具
    • Step 1:选择 Upscayl 输出文件夹
    • Step 2:选择目标输出文件夹
    • Step 3:设置倍数为 4x(与 Upscayl 一致)
    • 点击"开始处理"
    • 得到 2048×2048 清晰图片(尺寸不变,清晰度提升
  3. 结果对比

    • 原图:2048×2048,模糊、有噪点
    • 处理后:2048×2048,清晰、少噪点、细节丰富

适用场景:

  • 游戏素材清晰化 - 老游戏资源现代化(石器时代、魔兽世界等)
  • 像素画增强 - 保持原尺寸,减少锯齿和噪点
  • 老照片修复 - 去模糊、去噪点、还原细节
  • UI 图标优化 - 提升图标清晰度,不改变尺寸
  • 表情包清晰化 - 社交媒体图片质量提升
  • 扫描文档增强 - 扫描件去模糊

配置参数说明:

Upscayl 放大倍数:

  • 2x - 适合轻度模糊(处理快)
  • 4x - 推荐(平衡效果和速度)
  • 8x - 最强效果(需要强大 GPU

输出质量:

  • 80-90 - 较小文件,轻微质量损失
  • 95 - 推荐(平衡质量和文件大小)
  • 100 - 最高质量,文件较大

输出文件命名:

  • 格式:原文件名_enhanced.png
  • 例如:110000_0.png110000_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 通用
  • 零配置:首次运行自动安装依赖

启动脚本说明:

  1. start_gui.bat(推荐):

    • 启动图形界面版本
    • 可视化操作
    • 实时进度显示
    • 适合大多数用户
  2. start.bat(命令行):

    • 启动命令行版本
    • 适合脚本化批量处理
    • 适合服务器部署
  3. 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 放大到 8K8192×8192
     ↓
ImageEnhancer → 超采样缩回 2K2048×2048,高清)
     ↓
Unity Gacha → 加载高清图集用于游戏

这套工作流可将石器时代的像素风格资源无损提升到高清质量,同时保持原始尺寸和像素风格。


showtime_weihai 前端 dist 与 Nginx 同机部署(Ubuntu

以下流程适用于:前端静态资源由 Nginx 提供,Java 后端监听本机端口(例如 8707),前后端部署在同一台服务器。与仅在本机 npm run dev 时使用 vite.config.jsproxy 转发到远程网关的场景不同;生产构建的 dist 不会包含 Vite 开发代理,浏览器只会请求当前访问站点同源下的路径(如 /api/...),因此线上必须由 Nginx(或等价网关)把 /api 转到后端。

1. 前端工程是否需要改配置

  • vite.config.js 中的 server.portproxy:仅影响开发服务器,不参与 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. 本地构建与上传到服务器

  1. 在项目目录执行:npm run build,得到 dist 目录。
  2. dist 目录内的全部内容index.htmlassets/ 等)上传到 Nginx root 所指目录(如 /var/www/html/),不要只上传一个名为 dist 的文件夹却期望根路径直接访问(除非 Nginx 的 root 刻意指向该子目录)。
  3. 工具可选用 WinSCP(图形界面)、scp(命令行)等。WinSCP 右侧应进入 /var/www/html(或运维指定的 root),左侧进入本地 dist,再上传。

5. 页面能打开但登录失败时的排查顺序

  1. 浏览器 F12 → 网络,查看登录请求的 URL、HTTP 状态码、响应体(业务 code / message)。
  2. 502 / 504:多为后端未监听或端口不一致,在服务器上确认 Java 是否在 8707 监听。
  3. 404:多为路径前缀与后端不一致,按上文调整 proxy_pass 是否带末尾 / 后重载 Nginx。
  4. 查看 Nginx 错误日志:tail -30 /var/log/nginx/error.log

6. 与「仅前端 + 远程网关」旧配置的对比

若旧环境 Nginx 将 /api/ 代理到 https://gateway... 并去掉 /api 前缀,而当前后端已改为本机 Java,则应使用 http://127.0.0.1:8707(及正确的路径拼接方式),不要再指向旧网关,否则与「前后端同机」架构不一致。