# 项目总览文档 ## 8. showtime_weihai **类型:** Vue3 管理平台(Mapbox 增强版) **技术栈:** - Vue 3.5+ (Composition API) - Tauri 2.8+ - 跨平台桌面应用框架 - Element Plus 2.9+ - UI 组件库 - **Mapbox GL 3.18+** - 地图引擎(核心特色) - ECharts 6.0+ - 数据可视化 - face-api.js 0.22+ - 浏览器端人脸识别 - Three.js 0.180+ - 3D 图形库 - Axios 1.7+ - HTTP 请求 - Pinia 2.3+ - 状态管理 - Vue Router 4.5+ - 路由管理 - Tailwind CSS 4.1+ - 样式框架 **核心功能:** - ✅ **用户管理** - 用户列表与部门 KPI(`roles` 弹窗编辑、详情抽屉只读预览)、用户信息、支付列表 - ✅ **账户管理** - 账户列表、分组管理、订单管理 - ✅ **课程管理** - 课程列表、课程信息、课程日历 - ✅ **公司管理** - 公司信息、公司列表 - ✅ **保安管理** - 保安列表、值班管理 - ✅ **任务管理** - 任务列表、任务包管理 - ✅ **项目管理** - 项目列表 - ✅ **支付管理** - 支付录入、支付记录 - ✅ **图片标注** - Canvas 图片标注工具 - ✅ **数据看板** - ECharts 图表、数据概览、监控页面 **特色功能(Mapbox 增强):** - ✅ **Mapbox 地图展示** - 登录页 3D 地图背景 - ✅ **Mapbox 船只动画** - 船只在地图上移动的动画效果 - ✅ **地图交互** - 缩放、平移、标记点 - ✅ **3D 地形** - Mapbox 3D 地形渲染 - ✅ **实时天气信息** - 登录页显示天气数据 - ✅ **人脸识别登录** - face-api.js 浏览器端识别 - ✅ **WebGL Shader 背景** - 多种炫酷特效(音频可视化、蝙蝠群、云层、分形、Menger 海绵、地形、火车) **Mapbox 功能详解:** - 使用 Mapbox GL JS 3.18+ 最新版本 - 支持 2D/3D 地图切换 - 支持自定义地图样式 - 支持 GeoJSON 数据加载 - 支持动画路径(船只移动) - 支持地图标注和交互 **登录页特效(最全版本):** 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.vue`、`src/api/material.js`;应用内「更新说明」:`src/data/appChangelog.js`(同日组)。 | 范围 | 说明 | |------|------| | **页签与接口** | 「关联物料」:**可存储**(`category_listed` type=1)与 **可售卖**(type=2);物料列表仅 `POST /api/goods/listed`(本页已不再用 `/api/goods/search`)。中心仓可售卖池更新:`POST /api/warehouse/set_goods_pool`(`type: 2`)。分类源:`POST /api/category/listed`、`POST /api/warehouse/category_listed`。 | | **可售卖懒加载** | 树先只建分类骨架;用户点击**末级叶子分类**后再对该叶子发 **一次** `goods/listed`,避免整树批量请求。物料节点展示为「名称(编码)」(无名称则仅编码)。 | | **`goods_code_pool`** | 缺失/`null` → 内部等价全部可售(`*`);显式 `[]` → 池中无物料;`['*']`/含 `*` → 全部;非空字符串数组 → 白名单。子仓若无显式分类关联记录,`set_goods_pool` 易失败;子仓侧保存可售卖池改为按需走 **`batch_link_category`**(带池)以落库。 | | **子仓库 · 关联物料分类** | 嵌入页与主面板均可「管理关联」:可选范围 = **中心仓已关联分类**子树;结果仅写子仓 `warehouse_code`。首次进入管理态未单独配置时默认全选继承范围。`refreshWhCategoryAssoc` 支持 **仅刷 type=1 或 type=2**,改可存储不连带重载可售卖。 | | **中心仓 · 关联物料分类** | 保存后按当前编辑的 **type** 单列刷新,不联动另一侧。 | | **档案 UI** | 「子仓库」顶栏中心仓一行内展示编码、组织、责任人、状态;子仓详情区与子仓信息行展示风格一致。新建/编辑表单 **隐藏「仓库编码」**(中心仓与子仓共用表单)。责任人/联系人:`user/listed` + `dept/listed` 回补部门;下拉按部门 **分组**;选项「部门-姓名(工号)」,行内展示「姓名(工号)」。 | | **清理** | 移除本页 `logWhSell`/`debugWhSell` 与拖拽排序调试 `console.log`,不再向控制台输出上述测试信息。 | | **API 兼容** | `getMaterialList` 等对列表字段兼容解析;`normalizeWhListedCategory`、`parseCategoryListedTreePayload` 等与分类/池字段解析一致。 | | **全局分类缓存** | `src/composables/useCategoryTree.js`:登录成功后 `init()` 预拉、`setInterval` 约 60s 刷新;`fetchIfStale()` 在未过期时直接使用内存树;物料分类 **`add`/`modify`/`remove`**(`MaterialCategory.vue`、`MaterialList.vue`)成功后 `invalidate()` 立即重拉。**Token 仍有效但未走登录接口**(如刷新后首访)时在 `router/index.js` `beforeEach` 有 token 分支内补调 `init()`,避免只靠登录才预热缓存。仓库侧 **关联物料分类浏览剪枝树**、中心仓「管理关联」整树数据源、子仓管理态 **剪枝所用的全树**均复用该缓存,不再各处独立请求 `/api/category/listed`。 | | **浏览态树节点** | 「关联物料分类」浏览:`el-tree` 默认节点展示,仅 **分类名称**(不再在行内并排展示分类编码);与部门/组织侧一致。 | | **搜索框(临时)** | 仓库档案内关联物料分类 **管理关联**、`whMcFilterText` / `whSubMcFilterText` 的搜索输入在模板中 **注释隐藏**(可按需恢复)。 | | **可售卖 · 中心仓管理态** | 移除树下方「当前勾选的可售卖物料池」摘要列表(原含「全部 SKU」「调整」等行),界面只保留勾选树与取消/确认。 | | **子仓 · 管理关联加载** | `startWhSubMcManage` 仍 **并行**请求 `warehouse/category_listed`(中心仓 + 子仓各一次)以划定可选范围与子仓 baseline;全量分类树来自缓存。**等待时间主要取决于上述两条仓储接口**,与分类全局缓存是否为热数据无关(仓库维度数据不按全局缓存跳过)。 | #### 📦 物料分类全局缓存 · 预热与作废(2026-05-11) **代码路径:** `weihai/client/showtime_weihai/src/composables/useCategoryTree.js`、`src/store/modules/user.js`、`src/router/index.js`、`src/views/material/MaterialCategory.vue`、`src/views/material/MaterialList.vue`。 | 项 | 说明 | |------|------| | **语义** | 单例模块级状态:`fetchIfStale`(TTL ≈60s)、`invalidate`(CRUD 后强制重拉)、`init`/`destroy`(登录/退出)。 | | **触发点** | 登录成功:`user` store `login()` 内 `init()`。已有 token 进入应用:路由守卫在拉权限后调用 `init()`(幂等,不重复挂载定时器)。退出:`destroy()`。 | | **作废** | 分类新增/修改/删除接口返回成功后在本页作废并刷新缓存,避免他处仍读到旧树。 | #### 👥 部门管理 · 关联物料分类(同日补充)(2026-05-11) **代码路径:** `weihai/client/showtime_weihai/src/views/base/DeptManage.vue`。 | 项 | 说明 | |------|------| | **数据** | 已关联仍为 `POST /api/dept/category_listed`;全量树走 **`useCategoryTree`**,与组织/仓库侧同源缓存。 | | **UI** | 浏览与管理 **同一套外层布局**:标题行、中间 **同一 `tree-wrap`**、底部「取消/确认」仅在管理态出现;勾选框仅管理态。**搜索框模板已注释隐藏**(与组织侧一致)。 | #### 🏢 组织管理 · 关联物料分类(同日补充)(2026-05-11) **代码路径:** `weihai/client/showtime_weihai/src/views/base/OrgManage.vue`。 | 项 | 说明 | |------|------| | **交互** | 右侧不再使用「路径 + 编码」的纵向列表浏览;改为与部门一致的 **剪枝浏览树**,管理仍为全树勾选 + 确认。**搜索框模板已注释隐藏**。 | | **数据** | 去掉仅为列表展示而并行拉取的独立 `category/listed` 路径映射;剪枝/browse 与管理树均基于全局缓存与会话内 `invalidate`。 | #### 📋 物料场景 · 关联末级分类 UI(同日补充)(2026-05-11) **代码路径:** `weihai/client/showtime_weihai/src/views/material/SceneManage.vue`、`src/utils/categoryAssociationTree.js`(浏览树剪枝与孤儿补齐)。 | 项 | 说明 | |------|------| | **布局** | 「关联物料末级分类」从配置区**顶部独占横栏**移入 **`sm-fields-area` 最左列**,与中部「首选 / 公共 / 专有属性」、右侧字段池并排,行为与仓库/部门/组织侧「左树 + 右工作区」一致。 | | **可调宽分隔条** | 分类左列与中列区间增加 **`useResizablePanel`**,持久化键 **`scene_cat_col_width`**(默认 220px,约 140~400),与左侧场景列表 **`scene_sidebar_width`** 互不干扰;移动端沿用既有规则隐藏 `sm-resizer`。 | | **浏览与管理** | 浏览态:**剪枝树**(`pruneWhLinkedCategoryTree` + `mergeBrowseCategoryOrphans`)展示已关联层级;节点可点切换 **专有属性** 激活分类;行间 **×** 移除。管理态:**页内勾选全树**,不再使用分类选择 **`el-dialog`**;确认逻辑仍为仅采纳 **末端且在场景内的分类**,其余与既有 `confirmCatSelection` / 字段回填一致。 | | **滚动与顶栏文案** | 分类树在左列 **`sm-mc-tree-wrap` 内纵向滚动**;标题行(关联物料末级分类 + **管理关联 / 取消·确认**)与 **「点击分类…当前选中 / 共 N 个」** 提示固定在滚动区之上,滚动列表时仍可看到上下文。 | #### 📋 多维表衍生常量传播索引 · 配置管理二次刷新与等待提示(2026-05-09) **代码路径:** `weihai/client/showtime_weihai/src/views/material/mdt/oaConstWritePropagation.js`、`mdtConstUpstreamDeps.js`、`mdtUpstreamIndexCache.js`、`src/views/material/MultiDimensionalTable.vue`、`src/views/base/ConfigManage.vue`;应用内「更新说明」:`src/data/appChangelog.js`(同日条目)。多维表分项长文 **`weihai/client/showtime_weihai/docs/多维表格(MDT)_改动纪要.md`** 与本节互补(历史列类型、`oa_config` 产品差异等见该文档)。 | 范围 | 文件 | 变更摘要 | |------|------|----------| | **传播少 view(客户端索引)** | `oaConstWritePropagation.js`、`mdtConstUpstreamDeps.js`、`mdtUpstreamIndexCache.js` | 配置保存后仍会合并 `oa_config` listed 并扫描多维表中 `oaConstWrite` 漂移;新增按表的 **`localStorage`** 索引(键 **`mdt_const_upstream_idx_v1`**):由公式链静态推断 **`configConstantSig`** 集合,`noDeps` / `scanAll` / 补丁 **`patchSig`** 规则下 **跳过不必要的 `excel/dynamic/view`**;每次实际 view 后对应该表 **`upsert`** hint;统计 **`tablesSkippedByIndex`**。 | | **MDT 侧写回索引** | `MultiDimensionalTable.vue` | 换表 **`loadContent`**、**保存成功** 后对本表 **`upsert`** 上游 hint,避免只改绑定/公式时索引陈旧。**从未在本机打开过的表**仍依赖传播首轮 view 建索引。 | | **配置项管理列表与 UX** | `ConfigManage.vue` | 保存:`/api/config/set` → **首次 `loadCfg`** → **`propagateMdtDependentConstants`** → 传播 **`finally`** 收起橘色常驻 **`ElMessage.warning`(duration: 0)** → 若有衍生品则绿字 **`已按多维表公式同步…`** → **再次 `loadCfg`**,避免首拉早于衍生 `/config/set` 落库。 | --- #### 🔗 常量信息选择过滤 · 删除常量/删列确认(2026-05-10) **代码路径:** `weihai/client/showtime_weihai/src/views/material/mdt/LinkPicker.vue`、`mdt/oaConfigListedMerge.js`、`src/views/material/MultiDimensionalTable.vue`、`src/views/base/ConfigManage.vue`;应用内「更新说明」:`src/data/appChangelog.js`(同日组)。 | 范围 | 文件 | 变更摘要 | |------|------|----------| | **LinkPicker 不展示表写回项** | `LinkPicker.vue`、`oaConfigListedMerge.js` | 表格目标为 `oa_config` 时,在 `fetchOaConfigMergedListed` 结果上增加 `isOaConfigDescFromMdtWriteback(desc)` 过滤;与多维表写回说明 `formatOaConstWriteDesc`(`来自多维表格「…」· 列「…」`)一致,避免「表里再选由本表写回的常量」形成闭环。库内导出 **`OA_CONFIG_DESC_FROM_MDT_SUFFIX_RE`**、**`isOaConfigDescFromMdtWriteback`**。 | | **删除常量二次确认** | `ConfigManage.vue` | 非写回占位项删除前:明示页签/物料/多维表可能仍引用、删除后可能失效或异常、**系统不自动修改引用**;确认按钮「仍要删除」;取消确认单独 `try/catch`,避免未处理 rejection。 | | **删除列涉及常量时的说明** | `MultiDimensionalTable.vue` | `columnDeleteTouchesOaConst`:`ds:oa_config` 列,或数字/公式列且任一行存在 `oaConstWrite` 绑定 → 删除确认追加对配置常量与页签侧影响的文案,标题可为 **「删除列 · 将影响常量关联」**。 | | **(行为备忘)常量已删、格内仍存 PK** | `MultiDimensionalTable.vue` `resolveDsCell` | 合并 listed 中无对应记录时展示串为空字符串;格内仍有旧 PK 时可能仍渲染数据源样式壳,主数字/展示字段区域为空(直至用户重选或清格)。 | #### 👤 用户管理 · 部门 KPI 详情与控制台清理(2026-05-10) **代码路径:** `weihai/client/showtime_weihai/src/views/base/StaffManage.vue`;应用内「更新说明」:`src/data/appChangelog.js`(与同日常量/多维条目同一日组)。 | 范围 | 说明 | |------|------| | **列表与弹窗** | 「部门」列仅依据 `roles`:`dept_code + role_code` 一行对应一条;「查看部门详情」:`el-dialog` 内 KPI 合计与分段 tooltip、表格编辑每条绑定的级别与 KPI;KPI **列表头**文案为 **`kpi (单位: % )`**;保存时通过 `POST /api/user/modify` 携带完整 `roles`(字段含 `role_level`、`kpi_rate`);级别仅下限 1、无上限;KPI 合计须 **100** 仅在点击保存时用 `ElMessage` 校验。 | | **右侧详情抽屉** | 底部「部门 · 角色与 KPI」:只读表格 + KPI 汇总条(与弹窗同源 `user.roles`);只读表中 KPI **列表头**同为 **`kpi (单位: % )`**;移除任职信息与头部的「部门·角色」单行长文案以避免重复。 | | **控制台** | `loadList` 不再输出列表调试 `console.log`/`console.table`;新增用户的 OpenIM 注册改为静默 `void registerImUser(…).catch`,不再打印成功/警告。 | #### 🏢 部门管理 · 角色成员列表(2026-05-10) **代码路径:** `weihai/client/showtime_weihai/src/views/base/DeptManage.vue`;应用内「更新说明」:`src/data/appChangelog.js`(与同日子项)。 | 范围 | 说明 | |------|------| | **UI** | 第三列成员:圆头像;主行展示姓名 / 手机 / 职业性质 / 工号;级别与 KPI 只读紫色、琥珀胶囊(`n级`、`n%`);保留「移除」「关联用户」。 | | **数据** | 工号与用户主键分列解析(`pickMemberEmpCode` / `pickMemberUserCode`),避免二者混用;职业性质等与 `userListed` 回补合并。 | | **布局 / 对齐** | 成员行改用统一 `grid-template-columns`,末两列为相同 `minmax(68px, 0.76fr)`(替代 `auto`),使表头与各行「级别」「kpi」列纵向对齐;`member-col-act` 等样式括号修复已通过构建校验。 | #### 🏢 组织管理 · 关联物料分类(2026-05-10) **代码路径:** `weihai/client/showtime_weihai/src/views/base/OrgManage.vue`;应用内「更新说明」:`src/data/appChangelog.js`(同日组)。分项说明 **`weihai/client/showtime_weihai/docs/组织管理_关联物料分类.md`**。 | 范围 | 说明 | |------|------| | **接口** | 已关联:`POST /api/company/category_listed`(`company_code`);维护:`POST /api/company/link_category`、`POST /api/company/unlink_category`(`company_code` + `category_code`);分类树与路径:`POST /api/category/listed`。 | | **交互** | 右侧列表与并行树数据生成层级路径;「管理关联」内勾选树,确认 diff 后批量增删关联;新建组织无 `company_code` 仅文案,不调接口。 | | **布局与文案** | 弹窗主体固定高度、宽约 1200px、各栏内部滚动并避免横向条;列表/树加载「加载中」,提交关联遮罩「关联中」;标题行橘色操作提示。 | --- #### 📐 页签字段宽度、特征必填显隐、数字模式、公式符号、用户字段精简、财务属性 UI(2026-05-07) **代码路径:** `src/views/material/MaterialList.vue`、`src/views/material/MaterialCategory.vue`、`src/views/material/TabBuilder.vue`、`src/views/material/TabManage.vue`、`src/views/base/StaffManage.vue`、`src/views/base/BillManage.vue` | 范围 | 文件 | 变更摘要 | |------|------|----------| | **字段网格列宽调整** | `MaterialList.vue`、`MaterialCategory.vue` | 每列最小宽度阈值由 `190px` 改为 `240px`:1920 屏弹窗从 6 列→5 列(每列约 260px),2560 屏从 8 列→7 列(约 252px);分类编辑页从 7 列→6 列(约 251px)。字符长度占列规则(`totalLen > 8 → span2`)不受影响。 | | **特征必填属性显隐规则** | `TabBuilder.vue`、`TabManage.vue`、`MaterialCategory.vue` | 页签特征属性中每个「必填」属性新增独立显隐规则设置,可为必填开关本身配置触发条件,支持条件必填场景。 | | **数字特征输入/范围模式** | `TabBuilder.vue`、`TabManage.vue` | 数字类特征字段新增「输入」与「范围」两种模式选项;`MaterialList.vue`、`MaterialCategory.vue` 数字使用侧同步添加模式切换控件,可在单值输入与区间范围之间切换。 | | **计算公式符号补全** | `TabBuilder.vue`、`TabManage.vue` | 公式编辑器新增「/」(除法)和「,」(逗号,多参数函数分隔)两个运算符按钮。 | | **创建用户字段精简** | `StaffManage.vue` | 新建用户弹窗移除若干冗余字段,表单仅保留必要信息,减少填写负担。 | | **财务属性 UI 调整** | `BillManage.vue` | 财务属性页面布局与视觉细节优化。 | --- #### 🏢 组织分类票据、员工管理扩展、IM 消息修复、物料分类优化(2026-05-06) **代码路径:** `src/views/base/OrgManage.vue`、`src/views/base/BillManage.vue`、`src/views/base/StaffManage.vue`、`src/views/material/MaterialCategory.vue`、`src/store/modules/im.js`、`src/router/index.js` | 范围 | 文件 | 变更摘要 | |------|------|----------| | **组织分类关联票据** | `OrgManage.vue` | 新建/编辑组织分类弹窗新增「关联票据」区块,可分别为「可收票据类型」和「可开票据类型」添加/删除票据(多选子弹窗,从 `/api/bill/listed` 加载);票据以 `{ code, name, tax }` 结构存入 `field.in_bill` / `field.out_bill`,随 `classify/modify` 一并提交;列表名称后展示税率百分比。 | | **组织继承分类票据** | `OrgManage.vue` | 新增/编辑组织弹窗统一显示「分类票据(可勾选关联)」区块;新增时默认全不勾选,编辑时已关联票据预勾选;点击行切换勾选;保存时新勾选项调 `link_bill`、取消勾选项调 `unlink_bill`;分类删除票据后服务端级联处理组织侧同步(前端不额外处理)。 | | **组织编辑旧票据区块移除** | `OrgManage.vue` | 删除编辑组织弹窗中原有的「关联票据」手动新增区块,统一使用继承勾选方式。 | | **字典显示优化** | `OrgManage.vue` | 组织分类表格「组织类型」「组织性质」列改为显示字典 label;`loadDictItems` 支持按 label 查找;`companyTypeLabel` / `orgNatureLabel` 兼容 value/label 双向查找;`印花税率` 单位从 ‰ 改为 %。 | | **财务属性出项票据改名** | `BillManage.vue` | 统计标签、列标题、空状态文字、弹窗标题、单选项中「出项票据」全部改为「销项票据」。 | | **费用项目改名辅助核算** | `router/index.js`、`ExpenseItemManage.vue` | 路由标题及页面内所有「费用项目」文字改为「辅助核算」。 | | **物料分类不参与场景提示修正** | `MaterialCategory.vue` | 「不参与场景」checkbox 的 tooltip 从错误的「勾选后物料列表不允许在此分类下新增物料」改为「勾选后该分类不出现在物料场景的选择列表里」。 | | **物流费率组重名校验** | `MaterialCategory.vue` | 勾选「是否是物流费率组」前先调 `/api/category/check_flow_group_duplicate`;`has_duplicate=true` 时提示不可勾选并保持未勾选状态;取消勾选直接放行;树节点标签从「费率组」改为「物流费率组」。 | | **员工管理学历/职业性质** | `StaffManage.vue` | 用户信息接口新增 `education`(学历)和 `job_nature`(职业性质)顶级字段,移出原 `expand.education`;学历为下拉选择(初中/高中/大专/大本/研究生),职业性质为4选1(正式员工/劳务派遣员工/临时员工/返聘员工);列表加「学历」「职业性质」两列,删除「职务」列;编辑表单学历和职业性质上移至主信息区;hover 卡片新增学历、职业性质、备注;详情抽屉基本信息加职业性质,删除任职信息里的「职务」行。 | | **IM 消息预览修复** | `im.js` | `_doSendTextHttp` 发送后 `latestMsg` 改为带 `contentType:101` 的完整格式,`previewLatestMsg` 能正确显示文本而非「[语音]」;`_upsertConversations` 加时间戳比较,防止旧数据覆盖本地更新预览。 | | **IM 空消息重试** | `im.js` | `openChat` 读出消息列表为空时,2.5s 后自动重试 `loadMessages()`,解决 HTTP 发送后 SDK 本地库尚未同步导致点进去无消息的问题。 | --- #### 🔧 IM 免登录重连、已读 SDK 与会计科目页修复(2026-05-06 下午) **代码路径:** `src/store/modules/user.js`、`src/router/index.js`、`src/store/modules/im.js`、`src/views/base/AccSubjectManage.vue` | 范围 | 文件 | 变更摘要 | |------|------|----------| | **F5 / 带 token 直连仍初始化 IM** | `user.js`、`router/index.js` | 新增 `ensureOpenIMConnected(opts?)`:有 `userCode` 时从 store / `localStorage` 对齐 `imToken`,缺失则 `loginToOpenIM`,再 `imStore.initIM`;`imStore.ready` 为真则直接返回。`login()` 内原「静默取 imToken + initIM」改为复用该方法。路由守卫在持有 `token` 的分支中调用 `kickOpenIM()`(访问 `/login`、根路径重定向、进入管理后台及无权限页前),避免仅账号密码登录路径才连 OpenIM、刷新后进系统 IM 不上的问题。 | | **会话已读 API 参数(3.8.x)** | `im.js` | `markRead` 调用 `markConversationMessageAsRead(conversationID)`,首参为**会话 ID 字符串**;勿再传 `{ conversationID }` 对象,否则服务端不落已读、F5 后未读会回来。 | | **重复标记已读的无害告警** | `im.js` | SDK 在「已读到最新」后再次 mark 会抛出 `hasReadSeq equal max`(如 `openChat` 与展开 IM 面板各调一次 `markRead`);该文案匹配时不再 `console.warn`,其它错误仍警告,本地未读清零逻辑不变。 | | **会计科目树形表格编译错误** | `AccSubjectManage.vue` | 树形视图 `` 在 `default-expand-all` 后补闭合 `>`;此前缺 `>` 导致整页模板解析失败(与科目名称列 `fontWeight` 样式无关)。 | --- #### 🏢 组织管理重构 & KR 协作步骤 & 财务属性入口 & IM 日志清理(2026-05-04 ~ 2026-05-05) **代码路径:** `src/views/base/OrgManage.vue`、`src/views/base/BillManage.vue`、`src/views/okr/OkrObjective.vue`、`src/views/okr/OkrKeyResult.vue`、`src/views/okr/OkrReview.vue`、`src/store/modules/okr.js`、`src/data/okrMock.js`、`src/store/modules/im.js`、`src/utils/socket.js`、`src/components/ImPanel.vue`、`src/views/material/TabManage.vue`、`src/views/material/TabBuilder.vue`、`src/utils/tab.js`、`src/store/modules/layout.js`、`src/layouts/index.vue`、`src/utils/menu.js`、`src/router/index.js` | 范围 | 文件 | 变更摘要 | |------|------|----------| | **组织分类 CRUD** | `OrgManage.vue` | 原「组织树」整体注释保留至 `OrgManage.legacy.vue`;新建组织分类列表(`el-table`,无边框/斑马纹),支持新建/编辑/删除分类;`org_type` / `org_nature` 改为固定选项下拉;描述字段改为名称后的 `?` 气泡 tooltip;税务属性(销项税率/附加税率/所得税率/印花税率)在同一行展示。 | | **组织内联展开** | `OrgManage.vue` | `el-table` 展开列点击后加载该分类下的组织列表(`/api/company/listed` 传 `classify_code`);展开区背景区分(`#f4f5f9` + 阴影);新增/编辑/删除组织;表单含名称、类型、统一社会信用代码、法人、联系人等字段。 | | **关联票据(组织)** | `OrgManage.vue` | 编辑组织时可关联进项/出项票据;票据分两栏展示(进项蓝/出项橙);支持新增关联(搜索下拉弹窗)、切换启用状态、移除关联。 | | **展开防重复加载** | `OrgManage.vue` | `onExpandChange` 加 `if (orgMap[code] !== undefined) return` 防二次展开重复请求;抽出 `loadOrgs(code)` 复用;展开工具栏加刷新按钮;加载遮罩显示「加载中…」文字。 | | **财务属性入口** | `BillManage.vue`、`router/index.js` | 路由 `/base/bill` 标题改为「财务属性」,图标改为 `mdi:finance`;进入页面先展示卡片列表(仅一张「各类型成本/费用额定增值税率」),点击卡片进入原票据管理详情;卡片样式与「页签管理」阴影一致;面包屑支持返回。 | | **KR 协作步骤** | `OkrObjective.vue`、`OkrKeyResult.vue`、`okrMock.js` | KR 数据结构增加 `steps[]`(id/title/description/collaborator_code/collaborator_name/status/due_date);`OkrObjective.vue` KR 抽屉新增「协作步骤」区块(步骤卡片、添加/编辑/删除步骤、状态切换、协作人指派);KR 行显示步骤进度徽章;`OkrKeyResult.vue` 新增「我参与的」视图 tab,筛选本人被指派的步骤并卡片展示;mock CRUD:`mockAddStep` / `mockModifyStep` / `mockRemoveStep` / `mockGetMySteps`。 | | **步骤编辑 Bug 修复** | `OkrObjective.vue`、`okrMock.js` | 编辑步骤时若 `collaborator_code` 不在用户列表则清空,防止显示原始工号;`mockGetUsers` 成功后同步更新已有步骤的 `collaborator_code / collaborator_name`。 | | **TabManage/TabBuilder 防崩溃** | `TabManage.vue`、`TabBuilder.vue`、`MaterialCategory.vue` | `tmConflictLabels` / `tabBuilderConflictLabels` 计算属性加 `try/catch` 兜底返回 `new Set()`;模板中 `.has()` 调用加可选链 `?.`,防止 undefined 导致页面白屏。 | | **IM 聊天按钮尺寸** | `ImPanel.vue` | `.im-tab` 高度从 `80px` 缩小到 `52px`,宽度从 `40px` 缩小到 `36px`,视觉更紧凑。 | | **IM 调试日志注释** | `im.js`、`socket.js`、`ImPanel.vue` | 注释所有 `[IM]` / `[Socket]` / `[preview]` 前缀的 `console.log`,保留 `console.warn` / `console.error` 级别的错误提示,控制台不再产生大量正常流程日志。 | | **页签字段重名可见提示** | `TabManage.vue`、`TabBuilder.vue` | 重名字段卡片在红框基础上增加「⚠ 重名」徽标与 `el-tooltip` 说明(模板内重复 / 跨页签冲突 / 保存校验闪红);明细/表格 footer 同步展示;模板中 `tmInternalDupLabels`、`internalDupLabels` 去掉误用的 `.value`,与 Vue 自动解包一致。 | | **KR 进度展示一致** | `OkrObjective.vue`、`OkrKeyResult.vue` | 列表行 `el-progress` 改读 `kr.progress`(与 `calcKrProgress` 一致),修复「目标值小于起始值」类 KR 误用 `current_value ÷ target_value` 显示成 100% 的问题;KR 详情 `saveDrawer` 保存前回写 `progress`。 | | **OKR 数据持久与重映射** | `okr.js`、`OkrReview.vue`(消费 store) | 复盘 `reviews` 在 owner 重映射时与关联 `objective` 对齐;`_remapOwnersIfNeeded` 改为逐字段仅替换「仍不在真实用户表中的 code」,避免 F5 后用户已改的负责人被全表覆盖;`STORE_VER` 升级可清陈旧 localStorage(若当轮有调整)。 | | **顶部多页签可关规则** | `router/index.js`、`layouts/index.vue`、`menu.js`、`layout.js`、`utils/tab.js` | 仅路由名 `Index`(首页)`closable: false`,其余路由均可关闭;`router.afterEach` 与侧栏 `addTab` 统一用 `isTabClosableRouteName`;路由表移除分散的 `meta.noClosable`;`addTab` 在页签已存在时若 `closable` 变化则更新;`closeTab` 对首页二次拦截。 | --- #### 📋 OKR 列表排版修复 & 下拉选中显示修复 & 复盘负责人修复(2026-05-04) **代码路径:** `src/views/okr/OkrKeyResult.vue`、`src/views/okr/OkrObjective.vue`、`src/views/okr/OkrReview.vue`、`src/data/okrMock.js`、`src/assets/styles/variables.css` | 范围 | 文件 | 变更摘要 | |------|------|----------| | **KR 列表列宽重调** | `OkrKeyResult.vue` | 周期列 44px、部门列 52px(text-align: center),负责人 88px;进度列加宽至 162px、截止日期加宽至 118px;col-title / col-obj 保持 flex:1 / flex:0.6 弹性,右侧固定列不再拥挤。 | | **全局下拉选中空白修复** | `variables.css` | 全局 `.el-select--small .el-select__selection` 的 `overflow: hidden` 改为仅对含 `.el-tag`(多选)时生效;单选框选中文字不再被 `overflow` 裁切(Element Plus 单选占位层带 z-index:-1,外层 overflow 会令其不可见)。 | | **下拉选项 label 字段兼容** | `OkrKeyResult.vue`、`OkrObjective.vue` | 部门下拉 label 改为 `d.full_name \|\| d.dept_name`;负责人下拉 label 改为 `u.realname \|\| u.username \|\| u.user_code`;联动的 `onDrawerDeptChange` / `onAddObjDeptChange` 同步兼容。 | | **mockGetDepts 归一化** | `okrMock.js` | 先展开 `...d` 再覆盖 `dept_code` / `dept_name` / `full_name`,避免 `...d` 把已设置的键冲回接口原始值;`dept_code` 统一转字符串。 | | **mockGetUsers 归一化** | `okrMock.js` | 用户列表 normalize:`user_code` 统一 `String()`,`realname` 补 `username` 兜底;MOCK_OBJECTIVES 分配负责人改用 normalized 列表;MOCK_REVIEWS 在用户同步后按 `objective_id` 匹配更新 `owner_code / owner_name / dept_name`。 | | **OkrReview 负责人显示** | `OkrReview.vue` | `getUserInfo(userCode, fallbackName)` 增加第二参数:找不到用户时直接展示 `owner_name`(复盘记录原始字段),不再显示 `USER_HPP` 等 mock 工号;新建复盘写入的 `dept_name` 改用 `deptInfo?.full_name \|\| deptInfo?.dept_name`。 | --- #### 📋 IM 群组邀请审批流 & OKR 接入真实数据 & OKR 排版优化(2026-05-03) **代码路径:** `src/store/modules/im.js`、`src/components/ImPanel.vue`、`src/data/okrMock.js`、`src/views/okr/OkrObjective.vue`、`src/views/okr/OkrKeyResult.vue` | 范围 | 文件 | 变更摘要 | |------|------|----------| | **IM 群组邀请改为需审批** | `im.js` | `inviteToGroup` 不再直接调 `/group/invite_user_to_group`,改为向每位被邀请人发送 `[GROUP_INVITE]:JSON` 格式的 1v1 消息(含 groupID / groupName / inviterName 等信息)。新增 `acceptGroupInvite(msg)`:解析邀请消息、调 Admin API 真正入群、发回同意通知;新增 `rejectGroupInvite(msg)`:发回拒绝通知。 | | **IM 邀请卡片 UI** | `ImPanel.vue` | 识别 `[GROUP_INVITE]:` 消息并渲染交互卡片(群名、邀请人、同意/拒绝按钮);`inviteStatusMap` ref 追踪已处理状态,防止重复操作;`isGroupInviteMsg` / `parseGroupInvite` / `getInviteStatus` 等辅助函数。 | | **IM 查看群成员** | `ImPanel.vue`、`im.js` | 群聊头部新增成员按钮;点击调 `imStore.getGroupMembers(groupID)` 弹窗展示;修复 `getGroupMembers` 返回 `res?.members \|\| res?.groupMembers` 的取值层级错误(`_adminPost` 已解包 `.data`)。 | | **OKR 接入真实 API** | `okrMock.js` | `mockGetUsers` 调 `/api/user/listed`,成功后覆盖 `MOCK_USERS` 并按轮询方式将真实用户分配给 `MOCK_OBJECTIVES` 及其 `key_results` 的 `owner_code / owner_name`。`mockGetDepts` 调 `/api/company/listed` + `/api/dept/listed`,返回字段统一为 `dept_code / dept_name / full_name`;均有 mock 数据降级兜底。 | | **OKR 进度计算与单位** | `OkrObjective.vue`、`OkrKeyResult.vue` | 进度改为 `(current_value / target_value) * 100`,el-progress 百分比、主文字均以此为准;次要行显示 `current_value / target_value`(含 unit)。Mock 数据单位 `'s'` → `'秒'`,`'h'` → `'小时'`。 | | **OKR 目标列表排版** | `OkrObjective.vue` | `col-summary` 改为 flex 横排 + `mask-image` 右侧渐变遮罩展示 KR 概要 chips(不截字符而是自然溢出渐隐);`progress-wrap` gap 收至 4px,`progress-text` min-width 收至 36px;`col-summary` padding-left 加大至 20px 与进度列拉开间距;`col-owner` 100px,`col-progress` 148px。 | | **OKR KR 列表排版** | `OkrKeyResult.vue` | 各列 flex / px 比例全面重算:`col-badge` 44px,`col-title` flex:1,`col-obj` flex:0.6,`col-period` 46px,`col-dept` 52px,`col-owner` 88px,`col-progress` 162px,`col-date` 118px,`col-ops` 68px。 | --- #### 📋 物料页签 UI、控件库排版与分类字段抽屉(2026-04-06) **代码路径:** `weihai/client/showtime_weihai/src/views/material/`(及同阶段若已合并的 `src/layouts/`、`src/store/modules/layout.js`) | 范围 | 文件 | 变更摘要 | |------|------|----------| | table 控件展示名 | `TabBuilder.vue`、`TabManage.vue` | 界面与映射中文案 **「明细表格」→「明细/表格」**(控件库、字段类型名、预览与画布提示、校验与注释等)。 | | 控件库两列排版 | `TabBuilder.vue`、`TabManage.vue` | 左侧控件栏 **256px**(原 210px);`.tb-wi-label` / `.tm-wi-label` 使用 **`white-space: nowrap`**,避免「明细/表格」在两列网格内折行。 | | 注释统一 | `MaterialList.vue`、`MaterialCategory.vue` | 注释 / 样式注释中「明细表格」→「明细/表格」,与界面一致。 | | 切换分类仍显示旧「编辑字段」 | `MaterialCategory.vue` | 新增 **`resetCatFieldEditorUI()`**:`cancelBatchCatOptRules`、清空 **`catFieldDrawer`**、关闭 **`custFieldRuleDlgVisible`** / **`catOptRuleDlgVisible`**、重置 **`catOptBatchSelected`**;在 **`openForm` 开头**与 **`watch(formVisible)`**(面板关闭)调用。根因:`openForm` 深拷贝替换 `form.tabs` 后抽屉仍引用旧字段对象。 | | keep-alive / 脏页(同阶段) | `layouts/index.vue`、`store/modules/layout.js`、`MaterialCategory.vue`、`TabManage.vue`、`MaterialList.vue`、`SceneManage.vue` | 多页签缓存、Tab 未保存指示、`setPageDirty` / `onBeforeRouteLeave`、`defineOptions` 等以仓库实际提交为准。 | #### 📋 物料列表选配编辑器(2026-04-09 ~ 2026-04-10) **代码路径:** `weihai/client/showtime_weihai/src/views/material/MaterialList.vue` | 范围 | 变更摘要 | |------|----------| | 文案与布局 | 左栏「控件库」→「选项」;选配编辑区中间/右侧「控件」→「选项」。**添加选项组**入口移至左侧面板(含说明与类型下拉);画布底部独立按钮已移除。 | | Sortable 与卡顿 | 左栏克隆到画布、画布内排序;避免 `watch(components.length)` 与拖拽 splice 冲突导致 Sortable 被提前销毁;关闭弹窗与 `nextTick` 后重绑实例。 | | 新建物料 | 无分类时不显示底部「添加选配 / 移除选配」整块区域。 | | 业务逻辑 | 选配选项以 **关联物料**(`materials[]`、`goods_code`)为主,物料多选弹窗;**添加选项**后立即可「添加物料」;控件行仅「添加选项」,不再在行内「添加物料」直建物料选项。 | | 右侧面板 | 选项组型选项:右侧只编辑名称与说明,**去掉**「关联物料」区块(物料仅在中间卡片维护)。 | | 查看态 | 选中选项后展示关联物料;每个物料名单独 **带边框小块** 展示,无前缀文案;物料名称输入框约 **15 字宽(220px)**。 | | 数据 | `fields.__selection__` 结构不变;`options[].materials[]` 存 `label` 等,展示用 `label` / `name` / `goods_code` 回退。 | #### 📋 多维表格、列类型(字典 / 数据源 / 直接查表)与物料侧栏(2026-04-14 ~ 2026-04-15) **代码路径:** `weihai/client/showtime_weihai/src/views/material/MultiDimensionalTable.vue`、`src/views/material/mdt/ColConfigPanel.vue`、`LinkPicker.vue`、`FormulaEditor.vue`、`src/composables/useFormulaEngine.js`;物料分类/列表与权限:`MaterialCategory.vue`、`MaterialList.vue`、Pinia 与路由权限(以仓库实际提交为准)。 | 范围 | 变更摘要 | |------|----------| | 动态表 CRUD | `POST /api/excel/dynamic/listed`、`view`、`add`、`modify`、`remove`;数据源清单:`/api/excel/dynamic/data_source/listed`。 | | 列类型 | 基础字段;**关联多维表**(仅动态表,手动选记录);**数据源选择**(系统数据源,`dsTarget` + `dsDisplayField`);**查找引用**(经关联列 或 **直接查表** + `lookupConditions` + `lookupAggregate`);**计算公式**;**单选** / **多选**(自定义 `options` + 色板,或 **`optionSource: dict`** + **`dictKey`**,字典加载与 **TabManage** 同源 `/api/dict/listed`;单元格存字典子项 **key**)。 | | 列配置与层级 | `ColConfigPanel`:`freeze` 避免遮挡公式编辑器;面板 **贴屏幕右缘**;字典分组选择器与预览条数。 | | 表体与列操作 | 内联编辑;行右键插入/删除与删除确认;列头菜单(改列、复制列、隐藏列、按列筛选);工具栏 **筛选**、**N 列已隐藏**;列宽默认值与最小宽度调整。 | | 保存与体验 | 自动保存防抖、`isDirty`、手动保存按钮态;底部 **自动保存 Toast**;切换路由/换表未保存提示。 | | 公式 | `evaluateFormula`、`buildResolvedCells`(关联、lookup、单选/多选解析为展示值供公式引用);列定义与行数据随表内容持久化。 | | 物料与权限(同阶段) | 仓库等页签字段与其它 tab 展示对齐;仓库选择条 **横向滚动**;子仓随父仓权限展示;页面级 **查看 / 增 / 改 / 删** 与 `leader_level=99` 超级管理员绕过;`scope` 空与全量语义。 | **多维表 `content` 约定(节选):** `columns[]` 含 `id`、`name`、`type`、`width`、`hidden`、`options`、`optionSource`、`dictKey`、`lookupMode`、`directTable`、`lookupConditions`、`lookupAggregate`、`dsTarget`、`dsDisplayField`、`formula` 等;`rows[].cells` 为 `colId →` 存值。字典列与页签字段一致存 **编码**;当前页字典在进页预拉,后台字典变更后建议 **整页刷新** 以刷新缓存。 #### 📋 特征分类、配置项管理、页签字段 key 加载优化(2026-04-27 ~ 2026-04-28) **代码路径:** `src/views/material/FeatureManage.vue`、`src/views/base/ConfigManage.vue`、`src/views/material/TabManage.vue`、`TabBuilder.vue`、`MaterialList.vue`、`AssemblyManage.vue`、`src/router/index.js`、`src/data/appChangelog.js` | 范围 | 文件 | 变更摘要 | |------|------|----------| | **特征分类左侧树** | `FeatureManage.vue` | 新增可折叠/拖拽调宽的左侧分类侧栏(复用 `useResizablePanel`);分类树支持两级展开(`expandedCodes` reactive 对象驱动展开状态),支持新建/编辑/删除根分类与子分类(`/field/classify/add|modify|remove`)。新建子分类后父节点自动展开。 | | **特征归类操作** | `FeatureManage.vue` | 卡片新增 checkbox 多选 + 底部「归类」按钮 + 右上角「···→移动到…」三种归类入口;批量选中时顶部显示「批量归类 (N)」按钮;归类弹窗展示完整分类树(含未分类);归类成功后刷新列表(`/field/modify` 传 `classify_code`,`/field/listed` 传 `classify_code` 过滤)。 | | **未分类过滤** | `FeatureManage.vue` | 默认 `activeClsCode = 'unknown'`,查询时发 `{}` 由服务端返回未归类数据;点击具体分类发 `{ classify_code: code }`;去掉"全部"入口。 | | **配置项管理页** | `ConfigManage.vue`(新建) | SceneManage 风格布局:根分类横向 tabs + 子分类左侧可调宽树 + 右侧卡片网格;卡片展示 key(橙色加粗)、value(蓝色背景等宽字体加粗)、描述(截断+tooltip);支持搜索、新增/编辑/删除配置项和分类(`/config/classify/*`、`/config/*`)。路由 `/config/manage` 加入左侧导航,图标 `mdi:cog-outline`。 | | **页签字段 key 异步加载** | `TabManage.vue`、`TabBuilder.vue` | 打开字段抽屉时立即显示「加载中…」,异步调 `/field/view`(传 `field_code`)回填 key;去掉 `openFieldDrawer`/`closeFieldDrawer` 中的 key 校验,仅在点确认时验证;回填完成后重新 snapshot 防止误触"有未保存改动";`isFormDirty` 比较时剥离 key 字段。 | | **选配管理增强** | `AssemblyManage.vue`、`MaterialList.vue` | 组件库去除"多行文本";数字字段新增默认值输入;下拉/单选/多选字段支持默认值勾选(`isDefaultOpt`/`toggleDefaultOpt`);文本/计算公式字段新增显隐规则(`textLinks`/`numLinks`);移除全部字段的"必填"开关;视图模式重排版(独立 block + 多列网格 + 强制单行选项);`viewValues` 通过 watch 持久化到 `modelValue.values`;选配数据存入 `fields.__asm_manage__`。 | | **组件拖拽修复** | `AssemblyManage.vue` | 修复 SortableJS `onEnd` 误删 `evt.clone`(原始组件项)导致组件库拖入后消失的问题,组件库支持多次复用。 | --- #### 📋 明细表格列交互修正与提示优化(2026-04-24) **代码路径:** `weihai/client/showtime_weihai/src/views/material/TabBuilder.vue`、`TabManage.vue` | 范围 | 文件 | 变更摘要 | |------|------|----------| | **明细列区禁止外部拖入** | `TabBuilder.vue`、`TabManage.vue` | 将列区 Sortable 的 `group.put` 从 `['tb-fields', 'tb-table-cols']` 改为 `['tb-table-cols']`,从根本上阻止左侧控件列表拖入;删除之前通过 `onMove` 返回 `false` 拦截的错误实现及处理"拖入自动建列"的 `onAdd` handler。已有列之间的拖拽排序不受影响。 | | **列只能点击添加** | `TabBuilder.vue`、`TabManage.vue` | 移除所有"从左侧拖入创建列"的代码路径,统一通过右下角「+ 添加列」下拉菜单选择类型添加。 | | **列区提示文字优化** | `TabBuilder.vue`、`TabManage.vue` | 画布列区提示:颜色改为醒目橘色(`#f07c13`),字重加粗;末尾追加灰色小字「· 列只能点击添加,不支持从左侧拖入」,始终常驻(有列/无列均显示)。右侧属性抽屉中列列表空状态文字由「从左侧拖入…」改为「点击右下角 + 添加列 来添加」,与实际交互一致。 | **效果说明:** - 用户从左侧控件列表往明细表格列区拖拽时,Sortable.js 自动拒绝(光标显示禁止符),无需任何额外代码。 - 明细列区的橘色提示始终可见,用户可随时了解添加方式和操作限制。 --- #### 📋 物料分类交互优化与页签字段重名实时检测(2026-04-23) **代码路径:** `weihai/client/showtime_weihai/src/views/material/MaterialCategory.vue`、`TabBuilder.vue`、`TabManage.vue` | 范围 | 文件 | 变更摘要 | |------|------|----------| | **确认按钮视觉状态** | `MaterialCategory.vue` | 右下角「确认」按钮根据 `isFormDirty` 动态切换颜色:**无改动时灰色**(`#c0c4cc`),**有改动时蓝色**(`#409eff`);灰色状态下仍可点击,纯视觉提示。通过 `.btn-undirty` CSS class 实现,不影响 `disabled` 状态。 | | **脏检测范围扩展** | `MaterialCategory.vue` | `formInitialSnapshot` 与 `isFormDirty` computed 新增 `desc`(描述)、`warehouse`(仓库管理)、`featureRelList`(特征关联)字段参与快照比对,原来只检测 `name + tabs + fieldMap + rules`,现全覆盖。 | | **路由离开不拦截** | `MaterialCategory.vue` | 移除 `onBeforeRouteLeave` 中的弹窗拦截逻辑(原来离开路由会弹"有未保存改动"确认框)。原因:页面已 **keep-alive** 缓存,路由跳走再回来状态完全保留,数据不会丢失,弹框无实际意义。改为直接 `next()`,仅保留 Tab 脏状态圆点指示。 | | **页签构建器字段重名实时检测** | `TabBuilder.vue`、`MaterialCategory.vue` | `TabBuilder` 新增 `conflictLabels` prop(`Set`);三处字段卡片(顶层分组、子组普通字段、子组 table 字段)绑定 `tb-fc-conflict` class;CSS 添加红色边框 + 淡红色 glow(`border-color: #f56c6c; box-shadow: 0 0 0 2px rgba(245,108,108,.2)`)。`MaterialCategory.vue` 新增 `tabBuilderConflictLabels` computed,实时收集除当前编辑页签外所有页签(含继承、引入模板)的字段 label,传给 ``。 | | **页签模板管理字段重名实时检测** | `TabManage.vue` | 新增 `tmConflictLabels` computed:遍历 `form.value.groups` 及各组 `subgroups`,统计出现超过一次的 label 集合;三处字段卡片绑定 `tm-fc-conflict` class,CSS 同 TabBuilder。检测范围为**模板内部**重名(独立模板不做跨模板检测)。 | **效果说明:** - 用户在页签构建器中修改字段名称时,若与其他页签字段重名,**卡片立即出现红色边框**,无需等到点保存才提示。 - `TabManage` 中同一模板内有重名字段时同样实时标红,帮助工作人员快速定位问题字段。 --- #### 📋 WebSocket 实时连接、选配关联展示优化与仓库管理精简(2026-04-22) **代码路径:** `src/utils/socket.js`(新增)、`src/store/modules/user.js`、`src/router/permissions.js`、`src/views/login/Login.vue`、`src/views/material/MaterialList.vue`、`MaterialCategory.vue` | 范围 | 文件 | 变更摘要 | |------|------|----------| | **WebSocket 服务** | `src/utils/socket.js`(新增) | 单例 WebSocket 客户端,连接地址 `ws://117.133.111.165:8708/?token=&passport_id=`;每秒发送 ArrayBuffer 心跳(code=1501001);断线后 3 秒自动重连(无限次);主动调用 `disconnect()` 时不重连。导出 `connect(token, userId)`、`disconnect()`、`getSocket()`。 | | **登录后自动连接** | `src/store/modules/user.js` | `login()` 成功后调用 `socketConnect(token, userId)`;`logout()` 调用 `socketDisconnect()` 并清除 `localStorage` 中的 `userId`;`userId` 字段从响应 `user_id` 读取并持久化到 `localStorage`。 | | **页面刷新后恢复连接** | `src/router/permissions.js`、`Login.vue` | `router.beforeEach` 有 token 放行时,若 socket 未连接则补充建立;`Login.vue` `onMounted` 检测到已有 token 时同样调用 `socketConnect`,覆盖「已登录直接进入」场景。 | | **选配追加选项分组展示** | `MaterialList.vue` | 新增 `getAssemblyOptionGroups(cat, field)` helper,将 `assembly_goods` 来源的追加选项按来源物料分组;下拉单选/多选框中每组单独一行渲染(`mf-asm-group-row`),行末以方形橘色标签显示来源物料名(`mf-asm-group-source`);追加选项统一设为 `disabled`,仅可查看不可选择。 | | **多选字段自动双列** | `MaterialList.vue` | `mfFieldSpan(field)` 函数:对 `multiselect` 类型取所有选项中**最长两项**字数之和,超过 10 字则返回 `mf-field-span2`(占双列),避免选中标签被截断。 | | **特征关联字段提示** | `MaterialCategory.vue` | 「特征关联」配置区 `featureKey`(英文)上方添加 `被关联特征`,`featureLabel`(中文)上方添加 `关联选项名称`;中文标签文字改为橘色(`#e6a23c`),便于工作人员区分字段用途。 | | **仓库管理只显示父仓库** | `MaterialCategory.vue`、`MaterialList.vue` | `permittedWarehouses` / `mlPermittedWarehouses` computed 末尾追加 `.filter(w => !w.parent_code)`,过滤掉所有子仓库,仓库管理页签只呈现顶层父仓库。 | --- #### 📋 物料特征信息交互与分类字段序列化修复(2026-04-20) **代码路径:** `weihai/client/showtime_weihai/src/views/material/MaterialList.vue`、`MaterialCategory.vue` | 范围 | 变更摘要 | |------|----------| | **特征信息 tab — radio/checkbox 选项视觉区分** | 原有选项(分类自定义)显示为**实心圆**(未选灰色实心,选中蓝色实心);来自 `assembly_goods` 追加的选项显示为**空心圆**(未选灰色圆边,选中蓝色圆边+圆心蓝点)。两类选项均为圆形,通过有无填充即可区分来源。 | | **特征信息 tab — 布局** | 原有选项横排(`flex-wrap: wrap`,多个并排一行);追加选项各占独立一行(`flex-basis: 100%`);两组之间用 `mf-opts-break` 留出间距,不显示分割线。 | | **MaterialCategory — radio 选项序列化修复** | `cleanField` 函数序列化字段时,`options` 数组原本仅针对 `select / multiselect / checkbox` 类型保存,`radio` 被遗漏。导致父类保存的单选框字段在子类继承后 `getImportOptions` 返回空数组、选项显示空白。**修复**:将 `radio` 加入序列化类型列表,确保 `options: ['选项A', '选项B', ...]` 随字段一起持久化到服务端。 | | **MaterialCategory — radio 抽屉初始化修复** | `openCatFieldDrawer` 原本仅对 `select / multiselect / checkbox` 从 `optionsRaw` 初始化 `options` 数组,`radio` 字段打开抽屉时 `options` 为 `undefined`,编辑列表显示「暂无选项」。**修复**:同样对 `radio` 执行 `parseImportOptions(field.optionsRaw) → field.options` 的初始化,已有数据可正常回显与编辑。 | **关键逻辑说明:** - `isAssemblyOption(cat, field, optLabel)` 判断某选项是否来自 `assembly_goods`,返回 `true` 时模板附加 `is-assembly` class,触发空心圆样式。 - `getFieldOptionsForCat(cat, field)` 合并原有选项与追加选项并去重(同名只保留原有的),保证追加选项排在末尾。 - `cleanField` 中 `options` 字段现统一格式:`['选项1', '选项2', ...]`(纯字符串数组),`radio` 与 `select/multiselect/checkbox` 一致。 #### 📋 物料场景管理重设计(2026-04-18 ~ 2026-04-19) **代码路径:** `weihai/client/showtime_weihai/src/views/material/SceneManage.vue`、`src/utils/uploadAsset.js`、`src/config/assets.js` | 范围 | 变更摘要 | |------|----------| | **左侧树只展示 L2** | `sceneTreeData` computed 将 L3 子节点存入 `_l3Children`,树中设 `children: []`,隐藏 L3;L3 改由右侧卡片墙展示。 | | **L2 卡片墙视图** | 点击 L2 节点触发 `loadL2Scenes`,调用 `/api/scene/classify/listed` 拉取该 L2 下所有 L3 场景并填充 `l2Scenes`。右侧渲染 `sm-card-view` 网格;卡片为 2:1 信用卡比例(`aspect-ratio: 2/1`),圆角(8px)、四周阴影(`0 4px 16px rgba(0,0,0,.18)`,hover 加深)。封面图铺满卡片背景(`background-size: cover`),场景名叠加在左上角白字;无封面时卡片显示浅灰底色 + 深色文字。 | | **场景封面上传(新建)** | 新建 L3 场景弹窗增加「封面图」上传字段(隐藏 file input + 预览缩略图)。上传调用 `uploadAsset`(`POST /api/assets/upload`),将完整响应对象(`{ url, name, assets_code, type, size }`)存入 `sceneForm.cover`。`submitSceneForm` 在 `classify/add` 之后额外调用 `/api/scene/add` 写入 `cover`,避免 `cover` 被直接作为字符串传给后端(后端要求 JSON 对象)。 | | **场景封面上传(详情)** | 详情页顶部操作行加「更换封面」`el-button`(含小缩略图);右侧栏在「非公共属性」上方加独立 `sm-pool-cover` 卡片预览区(2:1 比例,hover 显示「更换」遮罩)。上传同样存完整对象至 `activeCover`;`saveConfig` 携带 `cover` 字段写入 `/api/scene/modify`。`coverUrl(cover)` 工具函数统一从对象 `cover.url` 或字符串 `cover` 中提取 URL 并调用 `getAssetUrl`。 | | **分类字段详情懒加载** | **根本原因**:`/api/category/listed` 不返回 `fields`(包含 `_tabs`、`fieldMap`),导致 `fieldPool` 始终为空,四个属性区域全空白。**修复**:新增 `ensureCatDetail(code)` — 调用 `POST /api/category/view` 拉取单个分类完整 `fields` 并回填树节点的 `tabs` / `fieldMap`;`catDetailCache`(Set)避免重复请求。新增 `ensureCatAndAncestors(code)` — 同时加载目标节点及路径上所有祖先(`inherited-extended` 页签合并需要)。`confirmCatSelection` 改为 async,确认前先 `await Promise.allSettled(...ensureCatAndAncestors)`;`selectScene` 中加载已保存 `_categories` 后同样先 await 再构建 `selectedCategories`。此模式与 `MaterialList.vue` 的 `mlEnsureNodeDetail` + `/api/category/view` 机制一致。 | | **分类/组织 tag 区域限高滚动** | `sm-cat-row-scroll`:`max-height: 82px`(约 2.5 行)、`overflow-y: auto`;自定义 4px 细滚动条(默认 `#dcdfe6`,hover `#c0c4cc`)。「添加分类」/「添加组织」按钮移至 `sm-section-hd` 标题行右侧(`sm-section-actions`),始终可见不随 tag 滚动。各区块加 `border: 1px solid #ebeef5` + `border-radius: 8px`;描述文字超长时 `text-overflow: ellipsis`;空状态显示浅灰占位文字。 | | **tooltip 条件修正** | 分类树节点「该分支下暂无末端分类」tooltip 的显示条件由 `v-else` 改为 `v-else-if="!data.isFinal && !data.hasFinalDescendant"`,避免对合法末端节点误显示警告。 | **关键数据结构说明:** - `sceneForm.cover` / `activeCover`:存储 `/api/assets/upload` 返回的完整对象 `{ url, name, assets_code, type, size }`,后端要求 JSON 类型,不能存裸 URL 字符串。 - `coverUrl(cover)`:兼容旧字符串格式与新对象格式,均调用 `getAssetUrl` 拼接完整访问路径。 - `catDetailCache`(`new Set()`):按 `category_code` 缓存已拉取详情的分类,页面生命周期内有效;切换场景不清空(分类树不变)。 #### 📋 页签管理 & 物料分类(2026-03-26 ~ 2026-03-27) **代码路径:** `weihai/client/showtime_weihai/src/views/material/` | 模块 | 文件 | 变更摘要 | |------|------|----------| | 页签管理 | `TabManage.vue` | 列表/新建/编辑/删除改为走服务端:`POST /api/tab/add`、`/api/tab/modify`、`/api/tab/remove`、`/api/tab/listed`;`fields` 为 `{ groups: [...] }`。增加**页签分类**层级:`/api/tab/classify/*`,先选分类再管理页签;`listed` 需传 `classify_code`。字段模型:`dictKey` → **`key`**,新增 **`is_private`**(私有/不对客户端展示);`createFieldFromType` / `normalizeFields` 兼容旧 `dictKey`。 | | 布局与弹窗 | `TabManage.vue` + `layouts/index.vue` | `el-dialog` 勿 `append-to=".main-container"`(首屏 Teleport 找不到节点);改为 body + `position:fixed` 限定主内容区。路由 **`router-view`** 去掉会卡死的 `transition mode="out-in"`,避免进入页签管理后子页面白屏。 | | 物料分类 | `MaterialCategory.vue` | **导入页签**:模板列表改为接口拉取(遍历分类再 `/api/tab/listed`),不再读 `localStorage`。**保存**:`fields._tabs` 等到 `/api/category/modify`。`fieldMap` / `catFieldDrawer`:**`dictKey` → `key`**,兼容 `f.key ?? f.dictKey`。**模板同步**:`openForm` 内先 **`loadImportTabList().then(...)`** 再 `checkAndSyncTemplateChanges()`,避免列表未加载完误判「模板已删除」。分类树 **`fields` 解析**:剔除 `_tabRules`,避免误计「1 个属性」。分类树节点展示服务端下发的 **`code`**(子级 code 可由后端拼接,前端只展示)。 | | 自定义页签构建器 | `TabBuilder.vue` | 与页签管理字段结构对齐:**`key`、`is_private`**,字典绑定用 **`key`**;`normalizeGroups` 迁移旧 `dictKey`;右侧抽屉增加 Key 与私有开关。 | | 后端约定(字段) | — | `source`:`custom` / `dict`;**`key`** 统一表示字典标识或自定义业务键(替代原 `dictKey`);**`is_private`** 表示仅内部可见;前端 **`id`(f_xxx)** 仍用于拖拽/公式/显隐引用,可与后端业务主键分离。 | #### 📋 物料模块补充(2026-03-28) **代码路径:** `weihai/client/showtime_weihai/src/views/material/`、`src/utils/fieldVisibility.js`、`src/router/` | 范围 | 文件 | 变更摘要 | |------|------|----------| | 特征列表 | `FeatureManage.vue` | 只读列表:`POST /api/field/listed` 空 body;卡片网格展示 key、source、links、cname/ctime、uname/utime;无增删改。 | | 路由与标题 | `router/index.js`、`router/permissions.js` | 菜单名「特征列表」;`document.title` 仅用路由 `meta.title`,不再加后缀。 | | 显隐工具 | `fieldVisibility.js` | 新增 **`countRuleRefs`**(删前统计引用条数)、**`purgeRules`**(按字段 id 清理条件并剪枝空组);规则结构仍为 `field.rules` / `group.rules` / `optionRules`,条件 `{ id, fieldId, operator, value }`。 | | 页签管理 / TabBuilder | `TabManage.vue`、`TabBuilder.vue` | 删字段/删组:始终确认;有引用时提示;确认后 **`purgeRules`**。 | | 物料分类 | `MaterialCategory.vue` | **跨页签显隐**:`globalValueMap` 汇总全部分类页签字段当前值;条件 **`fieldId` 支持 `tabCode::字段id`**,与同模板多页签引入时的 id 区分;**同时写入纯 `fieldId` 键**以兼容历史已存规则;打开显隐弹窗时 **`migrateCatConditions`** 将旧纯 id 升级为复合键;删自定义/继承追加字段与分组时 **`countRuleRefs` / `purgeRules` 传复合键+纯 id**,并清理 **`fieldRules` / `groupRules`**(`purgeCatTabRules`)。**引入页签**分类级规则存 `tab.fieldRules[fieldId]`、`tab.groupRules[groupId]`,与模板规则 **OR 合并**(`mergeTabRules`)。**自定义字段显隐**独立弹窗 `custFieldRuleDlg`,与引入页签字段弹窗同一套条件 UI。**自定义页签**标签上移除「改名」,改名在 **编辑(TabBuilder)** 内完成。**打开编辑**:`tabsInitializing` + `loadImportTabList().finally(...)`,有页签时在**内容区**显示「加载中…」,无页签直接「暂无属性页签」;避免接口失败时 `tabsInitializing` 卡死。`openForm` 对自建 tab 执行 **`cleanGroups`**,避免数字类 `defaultValue` 空字符串触发 `ElInputNumber` 告警。`normalizeTabGroups` 保证 `groups`/`extraGroups` 为数组并保留 `fieldRules`/`groupRules`。 | | 保存拦截 | `MaterialCategory.vue` | 存在 **模板已删除** 的引入页签时禁止提交并提示删除或重新引入。 | **质量说明:** 上述改动在开发过程中已做 lint 检查并修复已知逻辑问题(含复合键与旧数据兼容、加载态与 `finally`)。**打包/上线前仍建议对物料分类、页签管理、显隐规则做一次完整回归测试**,无法替代人工与联调结论。 **依赖说明:** 可选安装 `@vue-flow/core` 等用于后续业务场景可视化(思维导图式画布),与本次页签字段改造无强依赖。 #### 📋 物料场景、布局与其它(2026-03-28 ~ 2026-03-29) **代码路径:** `weihai/client/showtime_weihai/`(`src/views/material/`、`src/layouts/`、`src/router/`、`src/utils/`、`index.html`、`public/`) | 范围 | 文件 | 变更摘要 | |------|------|----------| | **业务场景(物料场景)** | `SceneManage.vue` | 由卡片列表改为:**大场景 Tab**(`/api/scene/classify/listed`,失败则仅列场景)+ **左侧场景列表** + **右侧配置**;`listed` 带 `classify_code`;**可拖拽分隔条**(`useResizablePanel`,`scene_sidebar_width`)。**关联物料末级分类**(树多选,仅 `is_final` 末端;移除分类时若属性池已用字段则 **ElMessageBox 确认**)。**字段池**:从分类 `tabs.groups` + `fieldMap` 提取,过滤 `is_private`,按 key/label **去重**(先遍历到的分类优先);**引入页签**通过 **`/api/tab/classify/listed` + `/api/tab/listed`** 建 `tabTemplateMap`,按 `refId` 补全模板 `groups`。**首选项 / 公共属性 / 特殊属性** 三区 HTML5 拖拽;chip **横向换行** + 来源分类小字;保存 `fields` JSON:**`_categories`、`_preferred`、`_public`、`_special`**(并合并保留旧键如 `attributes`/`color`)。**大场景改名**:Tab 悬停铅笔 → `/api/scene/classify/modify`;控制台 `[场景分类] 接口返回` 调试。UI:添加分类 **warning 按钮**、字段池 **橘色拖拽说明**、字段池宽度约 **300px**、大场景 Tab **min-width + 编辑图标右对齐**等。 | | 布局标签栏 | `layouts/index.vue` | 修正窄屏下 **`.tab-bar` 被 `display:none` 隐藏**的问题;**`mobile-sub-tabs`** 改为展示 **`layoutStore.tabs`**(已打开页面)而非侧栏子菜单;移动端 Tab 关闭按钮样式等。 | | 路由 | `router/index.js` | **`router.afterEach`**:`to.matched.length < 2` 时不加 Tab;**`document.title = to.meta?.title`**(无后缀);Tab 同步失败日志英文化。 | | 布局 Store | `store/modules/layout.js` | Tab 增删使用不可变更新(如 `[...this.tabs, tab]` / `filter`),保证响应式。 | | 物料分类保存 | `MaterialCategory.vue` | **`cleanField`** 序列化时保留 **`optionRules`**、**`optionsRaw`**,修复自定义页签多选 **选项级显隐** 保存后丢失。 | | 配置表(价格本) | `views/tools/PriceBook.vue` | **`syncDefaultValuesFromContent`**:从服务端返回的表格单元格同步 **`inputArgs[].default_value`**;**`loadFromServer` / `doSaveModify`** 中在 `modify` 与 **`loadWorkbookData`** 之后调用,避免参数面板与表格不同步;单元格无数据时清空默认展示。 | | 特征列表 | `FeatureManage.vue` | 卡片底部 **日期/头像/操作人** 一行移除。 | | 站点图标 | `index.html` + `public/` | **favicon** 指向 **`/favicon.png`**(与 `public` 内文件名一致即可打包带出)。 | **数据约定(业务场景 `fields` JSON,当前前端实现):** - `_categories`:关联的物料分类 `category_code` 数组。 - `_preferred` / `_public` / `_special`:已分配字段的 **code/id 列表**(顺序即拖拽顺序)。 - 与历史 API 文档中的 **`field_code` + `x` + `y`** 方案 **未对接**;若后端检索依赖该结构,需与后端对齐后改造保存格式。 **待办(产品已讨论,尚未开发):** - 场景列表支持 **树形子分类**:在「编辑/删除」旁增加 **「子分类」**;**未**把物料字段拖入首选项/公共/特殊任一池前可点,**已配置字段后**隐藏或禁用,避免与「仅末端挂物料字段」的规则冲突。需后端 **`parent_code`(或等价)** 与列表树形接口约定。 --- #### 📋 物料场景三级结构、物料列表重构、计算公式等(2026-03-29 ~ 2026-03-30) **代码路径:** `weihai/client/showtime_weihai/src/views/material/`、`src/api/material.js`、`src/utils/fieldVisibility.js`、`src/views/base/WarehouseManage.vue` | 范围 | 文件 | 变更摘要 | |------|------|----------| | **业务场景三级结构** | `SceneManage.vue` | 将「分类+场景」二级结构升级为 **L1(顶部 Tab)/ L2(侧栏)/ L3(配置实体)** 三级。L1 = 无 parent 的 classify;L2 = 有 parent 的 classify;L3 = scene 实体,绑定 L2 的 classify_code。字段配置**只允许在 L3 进行**。L2 节点显示「创建场景」按钮(可创建多个 L3,不再限制一个)。点击 L2:若无 L3 子节点则右侧显示「请先创建场景」提示;若有 L3 则自动选中第一个 L3 并展示配置。点击 L3:右侧展示该场景配置。顶部「新建场景」→「新建一类」,侧栏「新建」→「新建二类」,L2 创建按钮为「创建场景」。`saveConfig()` 成功后 `ElMessage.success('保存成功')`。 | | **业务场景关联组织** | `SceneManage.vue` | ~~右侧「关联组织」与「关联物料末级分类」并排~~ **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` → 取模板 groups;`custom` → 取节点 groups),再合并 `extraGroups`。`name` 同理从祖先链读取,使用局部变量而非修改响应式数据,避免 computed 副作用。 | | **公式字段显隐修复** | `MaterialList.vue`、`TabBuilder.vue`、`TabManage.vue` | `materialValueMap` / `previewValueMap` / `tmPreviewValueMap` 均改为**两轮求值**:第一轮填普通字段,第二轮连续 3 次计算 `formula` 类型字段(支持公式引用公式的多层传递)。公式求值函数 `evalFormulaValue` 提取到 `fieldVisibility.js` 统一 export。公式字段在物料弹窗中渲染为**只读灰底展示框**,不再是可编辑输入框。 | | **计算公式互引用与循环检测** | `TabBuilder.vue`、`TabManage.vue` | 公式编辑器新增「控件(计算公式)」按钮区(绿色,可点击引用其他公式字段)。新增 `hasCircularRef` DFS 算法:引用前实时过滤掉会导致循环的公式字段(不在按钮列表中显示);`feInsertField` 插入时二次校验;`validate()`/`saveTab()` 保存时全量检测,有循环则 `ElMessage.error` 并阻止保存。两个文件均已改动。 | | **多选字段选项级显隐** | `TabBuilder.vue`、`TabManage.vue` | 选项级显隐(眼睛图标)原只支持 `checkbox`,现扩展至 **`multiselect`**(`['checkbox','multiselect'].includes(type)`)。两处文件均已改动,提示文字同步更新。 | | **字段属性顺序与必填** | `TabBuilder.vue`、`TabManage.vue` | 右侧属性面板中 **Key 移到「字段标签」上方**;Key 和字段标签均为**必填**,保存时校验为空则阻止并提示。两处文件均已改动。 | | **`fieldVisibility.js`** | `src/utils/fieldVisibility.js` | 新增 export `evalFormulaValue(formula, valueMap)`:安全执行公式字符串(正则过滤注入,`new Function` 求值),出错返回空字符串。三处调用方统一 import 此函数。 | | **物料 CRUD 对接真实 API** | `src/api/material.js` | 全部移除 localStorage mock。`getMaterialList` → `POST /api/goods/listed`(`category_code, keyword, page, limit`);`addMaterial` → `POST /api/goods/add`(`name, desc, fields, category_codes`);`updateMaterial` → `POST /api/goods/modify`(`goods_code, name, desc, fields`);`deleteMaterial` → `POST /api/goods/remove`(`goods_code`)。fields 存储 tabValues + `_code`。 | | **仓库档案责任人显示** | `WarehouseManage.vue` | 树节点「责任人」原显示 user_code,新增 `getUserName(userCode)` 函数(复用已加载的 `userList`),改为显示 `realname` 或 `username`。 | **数据约定补充(`fields._companies` 结构):** ```json "_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.name`**(computed 副作用)。 - 公式字段值的计算依赖 `evalFormulaValue` 做 3 次迭代,支持公式链(A→B→C),但不支持真正的无限递归(DFS 已拦截循环)。 - 物料表格末尾预留空白列(`min-width:60`),后续追加字段列时在操作列前插入即可。 --- #### 📋 物料列表多分类、列表数据格式与接口对齐(2026-03-31) **代码路径:** `weihai/client/showtime_weihai/src/views/material/MaterialList.vue`、`src/api/material.js` | 范围 | 变更摘要 | |------|----------| | **新增入口** | 顶部「新增」去掉「必须选中分类」的 `disabled`。未选/非末端:弹窗无分类,用户点「+ 添加分类」在树中选末端;已选末端:自动带入该分类并预填字段默认值(`_getEffectiveFieldDefaults`)。 | | **多分类弹窗** | `materialForm.categories[]`:`category_code`、`category_name`、`name`、`activeTabCode`、`tabValues`。顶部分类用 **ElTag** 行内排列(当前选中 `type="primary"` 蓝色,其余 `info`;「+ 添加分类」`type="warning" plain` 橘色),与 SceneManage 关联分类区风格一致。 | | **分类选择弹窗** | `el-tree` 多选;`catPickerTreeData` 对非末端 `disabled`;`#default` 插槽在末端节点旁展示绿色小 **「末端」** 标签。 | | **新增/修改接口** | 仍使用 `POST /api/goods/add`、`POST /api/goods/modify`;请求体为 **`{ data: [{ category_code, name, fields }, ...] }`**;修改额外传 **`goods_code`**。已纠正此前误用的 `/api/material/新建` 等路径。 | | **列表 API 解析** | `getMaterialList`:列表数组取 `res.data.data`(与 `list` 并列兼容);**total** 改为 **`data.total \|\| data.count \|\| list.length`**,避免后端 `total: 0` 但实际有行时分页条数错误。 | | **表格列映射** | 服务端行结构为 **`goods_code` + `data[]`**(每项含 `category_code`、`category_name`、`name`、`fields`)。**物料名称**:优先 `row.data.find(当前 selectedCategoryId).name`,否则多名称 ` / ` 拼接。**物料编码**:`row.goods_code`。 | | **编辑** | `openMaterialDialog('edit')` 从 **`row.data`** 还原全部分类 tab、名称与 `fields`(兼容 `fields` 为 JSON 字符串)。 | | **默认值竞态** | `watch(catTabListsMap)`:弹窗打开且页签模板加载完成后,为各分类 `tabValues` 回填缺失的模板默认值与 overrides。 | | **校验与数字类型** | 保存前各分类「物料名称」必填;失败时 `_nameError` + 表单项红框提示并切到对应分类。`el-input-number` 用 `:model-value` + `Number(...)` 避免字符串默认值触发 `modelValue` 类型告警。 | | **调试日志** | `onMounted` 中可选请求 `goods/listed` 并 `console.log('[物料列表] 原始数据:', r)`;保存成功后可选再拉一次列表打印 **`[物料列表] 保存后最新列表:`**(便于联调,上线前可按需删除)。 | --- #### 📐 计算公式弹窗改版与 Excel / Exec 参考转换(2026-05-08) **代码路径:** `weihai/client/showtime_weihai/src/components/formula/FormulaEditorPanel.vue`、`weihai/client/showtime_weihai/src/utils/excelFormulaToReference.js`(`convertExcelFormulaToReference`)。 **嵌入页面:** `MaterialCategory.vue`(物料分类自建字段公式)、`TabBuilder.vue`、`TabManage.vue`、`AssemblyManage.vue`(选配画布公式)。 | 范围 | 变更摘要 | |------|----------| | **布局与宽度** | 弹窗约 **900px**,类名 **`fe-dialog-wide`**:`width: min(900px, 96vw)`(全局样式挂在公式面板打包处)。主体 **左右两列**:左为引用字段按钮,右为符号、数字与函数。 | | **函数区 Tab** | `FUNCTION_GROUPS` 四类由纵向堆叠改为 **`el-tabs`(数学 / 文本 / 逻辑 / 日期)**,单页内按钮区可纵向滚动(`max-height`),缩短弹窗总高度。 | | **Excel / Exec 参考** | **`el-collapse`** 默认收起;标题「从 Excel / Exec 粘贴……」后以 **橘色加粗「请点击展开」** 提示。粘贴含 `=` 的公式后可 **转为参考语法**:将形如 `字段=""`、`字段=''` 的写法迭代替换成 **`ISBLANK(字段)`**;展示只读可复制,**不混入**顶部点选编辑的公式。 | | **`MaterialCategory` 差异** | 与其它页一致套用面板; **`show-str-slash-button`** 仍为 **false**,与改版前不含 `"/"` 快捷键一致。 | **应用内「更新说明」:** 见 `src/data/appChangelog.js` 当日 **`2026-05-08`** 分组首条(图标 `mdi:view-dashboard-outline`)。 --- #### 📋 业务场景优化(2026-05-08) **代码路径:** `weihai/client/showtime_weihai/src/views/material/SceneManage.vue` | 范围 | 变更摘要 | |------|----------| | **移除关联组织** | 删除「关联组织」区块(模板、弹窗、`linkedOrgs`/`orgTree`/`orgPickerVisible` 等 state、`loadOrgTree`/`confirmOrgPicker`/`removeLinkedOrg` 等函数、`saveConfig` 中的 `_companies` 字段及 `link_company` API 调用);「关联物料末级分类」改为全宽,tag 区 `max-height` 从 82px 扩大至 120px。 | | **末级分类添加卡顿优化** | `confirmCatSelection` 改为乐观更新:点击「确认选择」立即关闭弹窗;新增分类以 `pending:true` 占位 tag(灰色 + 旋转图标)插入列表;后台异步执行 `ensureCatAndAncestors`;加载完成替换为正常 tag,加载失败标记 `error:true`(红色 tag,可点 × 移除);新增 `hasPending` computed,加载期间「保存配置」按钮禁用并显示 tooltip 提示。 | --- #### 🔧 选配管理与物料弹窗显隐/公式(2026-05-08) **代码路径:** `src/views/material/AssemblyManage.vue`、`MaterialList.vue` | 范围 | 文件 | 变更摘要 | |------|------|----------| | **计算公式点选编辑** | `AssemblyManage.vue` | 与分类页签一致:弹窗内点选画布上的数字、金额、其它计算公式与运算符,存储 `{字段 id}`;**不再**在公式中点选物料页签其它字段;试算与引用范围限于选配画布。 | | **控件库与显隐条件收窄** | `AssemblyManage.vue` | `VIS_ELIGIBLE_TYPES` 不含字典、明细表格作为新建显隐条件来源;控件库不再提供「字典」「明细/表格」拖入**新建**(历史已配置项仍可编辑)。物料页签中 `type==='dict'` 的字段仍可作为联动目标(`tabEligibleFields` 等与画布侧区分)。 | | **数字 / 公式列宽** | `AssemblyManage.vue` | 数字与计算公式字段在画布上默认占两列(`amv-field-span2`),view 模式单值数字等使用 `max-width:320px`,减轻多位数与只读公式行被裁切。 | | **选配显隐合并到物料页签** | `MaterialList.vue` | `mfAssemblyFormulaValueMap`、`mfCatTabFieldVisible`:选配内 `numLinks` / `textLinks` 与分类页签既有显隐规则 **AND** 合并;公式类联动对 `formula` 使用 `evalFormulaValue`;保存前遍历可见字段做必填校验。 | **应用内「更新说明」数据源:** `src/data/appChangelog.js`(图标与模块类型对应关系见文件头注释:仓库类 `mdi:warehouse`、业务场景 `mdi:image-multiple-outline`、页签/网格 `mdi:view-dashboard-outline`、选配画布 `mdi:cog-outline`、物料列表 `mdi:format-list-bulleted` 等)。 --- #### 🏢 部门管理与用户管理(2026-05-08) **代码路径:** `src/views/base/DeptManage.vue`、`src/views/base/StaffManage.vue` | 范围 | 变更摘要 | |------|----------| | **部门管理 · 布局** | 三列:**部门树**(可搜、子部门缩进) / **角色列表**(部门内角色、权限/编辑/删除三点菜单) / **人员列表**(依赖选中角色)。列宽可拖拽,`localStorage` 持久化。 | | **部门管理 · 角色** | 新增/编辑角色增加 **岗位类型**(`role_type`:`M` 管理岗 / `P` 专家岗);列表名称右侧 **M/P** 小 Tag(plain);有 **描述** 时悬停名称显示 Tooltip。 | | **部门管理 · 成员** | 点击角色行选中后第三列加载该角色下成员;**关联用户**弹窗:`/api/user/listed` 取用户(开发约定单次最多 500 条,支持本地关键词筛选),勾选后 **`POST /api/dept/link_user`**;行内 **级别** 1–10、**KPI%** 0–100,变更防抖后分别 **`POST /api/dept/set_role_level`**、**`POST /api/dept/set_kpi_rate`**;**移除** → **`POST /api/dept/unlink_user`**。 | | **部门管理 · 成员列表接口** | 已关联成员由前端常量 **`API_DEPT_ROLE_MEMBER_LIST`**(`DeptManage.vue` 顶)发起查询,请求体为 `{ dept_code, role_code }`。**默认值为占位路径**,须与后端「查询某部门某角色已关联用户」的真实 URL 对齐,否则第三列无法加载。 | | **用户管理 · 调试** | 仅 **`import.meta.env.DEV`**:列表 `loadList` 成功后在控制台输出 `res.data`、当前页用户深拷贝、`console.table`、首条 `Object.keys`,便于对照字段。 | --- #### 📋 业务场景关联组织树与 setup 修复(2026-04-01) **代码路径:** `weihai/client/showtime_weihai/src/views/material/SceneManage.vue` | 范围 | 变更摘要 | |------|----------| | **关联组织 UI** | 原远程搜索 `el-select` 改为 **「添加组织」+ 弹窗 `el-tree`**:`POST /api/company/listed` 树数据经 `mapOrgNodes` 映射为 `id` / `label` / `children`;**无效组织**(`is_valid` 为假)节点 **`disabled: true`**,节点模板旁红色 **「禁用」** tag。父子联动勾选(非 `check-strictly`)。 | | **最小存储集** | `getMinimalOrgSelection()` 结合 `getCheckedKeys` 与 `getHalfCheckedKeys`:父节点整棵选中时只保留 **父 `company_code`**;部分选中则继续向下落到具体子节点,与「父一条代表整棵子树」的存储约定一致。 | | **标签展示** | 已关联列表:**父级** `el-tag type="primary"`(蓝),`el-tooltip` 展示子组织名称列表;**单独勾选的叶子** `type="info"`(灰)。移除原 tag 上 **绿点切换启用**(`modify_company` 即时改 `is_valid`)的交互。 | | **接口与 `_companies`** | 弹窗 **确认**:相对原 `linkedOrgs` diff 后,新增走 `POST /api/scene/link_company`,移除走 `POST /api/scene/unlink_company`(需已有 `scene_code`)。**保存配置** 仍把当前列表写入 `fields._companies`,可含 `company_code`、`name`、`full_name`、`is_valid`、`pos` 及 **`isParent`、`childNames`**(用于父 tag 与子列表 tooltip)。 | | **树回显** | `openOrgPicker` 中 `orgPickerTreeRef.setCheckedKeys(keys, leafOnly)`:**`leafOnly` 须为 `false`**,否则仅保存父 code 时树无法正确勾选父节点。 | | **setup 报错修复** | 组织搜索框与 `el-tree` 联动使用 **`watch(orgPickerSearch, ...)`** 调用 `filter`;必须在 `vue` 的 import 中显式加入 **`watch`**,否则运行时报 **`ReferenceError: watch is not defined`**,组件 setup 失败、路由白屏。 | --- #### 📋 子组显隐/公式/必填/单位显示等综合优化(2026-04-02 ~ 2026-04-03) **代码路径:** `weihai/client/showtime_weihai/src/views/material/`、`src/utils/fieldVisibility.js` | 范围 | 文件 | 变更摘要 | |------|------|----------| | **空页签显示修复** | `MaterialList.vue` | `_getTabListForCategory` 移除 `.filter(tab => tab.groups.length > 0)`,空页签(0分组0字段)也正常显示;空页签内容区提示「该页签暂无字段」。 | | **问号说明弹窗黑底白字** | `MaterialCategory.vue`、`TabManage.vue`、`TabBuilder.vue` | `desc-popover` 统一改为深色背景(`#1d1e20`)、白色文字(`#f0f0f0`),箭头颜色同步;样式置于非 scoped `