3866 lines
221 KiB
Markdown
Executable File
3866 lines
221 KiB
Markdown
Executable File
# 项目总览文档
|
||
|
||
|
||
## 8. showtime_weihai
|
||
|
||
**类型:** Vue3 管理平台(Mapbox 增强版)
|
||
|
||
**技术栈:**
|
||
- Vue 3.5+ (Composition API)
|
||
- Tauri 2.8+ - 跨平台桌面应用框架
|
||
- Element Plus 2.9+ - UI 组件库
|
||
- **Mapbox GL 3.18+** - 地图引擎(核心特色)
|
||
- ECharts 6.0+ - 数据可视化
|
||
- face-api.js 0.22+ - 浏览器端人脸识别
|
||
- Three.js 0.180+ - 3D 图形库
|
||
- Axios 1.7+ - HTTP 请求
|
||
- Pinia 2.3+ - 状态管理
|
||
- Vue Router 4.5+ - 路由管理
|
||
- Tailwind CSS 4.1+ - 样式框架
|
||
|
||
**核心功能:**
|
||
- ✅ **用户管理** - 用户列表与部门 KPI(`roles` 弹窗编辑、详情抽屉只读预览)、用户信息、支付列表
|
||
- ✅ **账户管理** - 账户列表、分组管理、订单管理
|
||
- ✅ **课程管理** - 课程列表、课程信息、课程日历
|
||
- ✅ **公司管理** - 公司信息、公司列表
|
||
- ✅ **保安管理** - 保安列表、值班管理
|
||
- ✅ **任务管理** - 任务列表、任务包管理
|
||
- ✅ **项目管理** - 项目列表
|
||
- ✅ **支付管理** - 支付录入、支付记录
|
||
- ✅ **图片标注** - Canvas 图片标注工具
|
||
- ✅ **数据看板** - ECharts 图表、数据概览、监控页面
|
||
|
||
**特色功能(Mapbox 增强):**
|
||
- ✅ **Mapbox 地图展示** - 登录页 3D 地图背景
|
||
- ✅ **Mapbox 船只动画** - 船只在地图上移动的动画效果
|
||
- ✅ **地图交互** - 缩放、平移、标记点
|
||
- ✅ **3D 地形** - Mapbox 3D 地形渲染
|
||
- ✅ **实时天气信息** - 登录页显示天气数据
|
||
- ✅ **人脸识别登录** - face-api.js 浏览器端识别
|
||
- ✅ **WebGL Shader 背景** - 多种炫酷特效(音频可视化、蝙蝠群、云层、分形、Menger 海绵、地形、火车)
|
||
|
||
**Mapbox 功能详解:**
|
||
- 使用 Mapbox GL JS 3.18+ 最新版本
|
||
- 支持 2D/3D 地图切换
|
||
- 支持自定义地图样式
|
||
- 支持 GeoJSON 数据加载
|
||
- 支持动画路径(船只移动)
|
||
- 支持地图标注和交互
|
||
|
||
**登录页特效(最全版本):**
|
||
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` | 树形视图 `<el-table>` 在 `default-expand-all` 后补闭合 `>`;此前缺 `>` 导致整页模板解析失败(与科目名称列 `fontWeight` 样式无关)。 |
|
||
|
||
---
|
||
|
||
#### 🏢 组织管理重构 & KR 协作步骤 & 财务属性入口 & IM 日志清理(2026-05-04 ~ 2026-05-05)
|
||
|
||
**代码路径:** `src/views/base/OrgManage.vue`、`src/views/base/BillManage.vue`、`src/views/okr/OkrObjective.vue`、`src/views/okr/OkrKeyResult.vue`、`src/views/okr/OkrReview.vue`、`src/store/modules/okr.js`、`src/data/okrMock.js`、`src/store/modules/im.js`、`src/utils/socket.js`、`src/components/ImPanel.vue`、`src/views/material/TabManage.vue`、`src/views/material/TabBuilder.vue`、`src/utils/tab.js`、`src/store/modules/layout.js`、`src/layouts/index.vue`、`src/utils/menu.js`、`src/router/index.js`
|
||
|
||
| 范围 | 文件 | 变更摘要 |
|
||
|------|------|----------|
|
||
| **组织分类 CRUD** | `OrgManage.vue` | 原「组织树」整体注释保留至 `OrgManage.legacy.vue`;新建组织分类列表(`el-table`,无边框/斑马纹),支持新建/编辑/删除分类;`org_type` / `org_nature` 改为固定选项下拉;描述字段改为名称后的 `?` 气泡 tooltip;税务属性(销项税率/附加税率/所得税率/印花税率)在同一行展示。 |
|
||
| **组织内联展开** | `OrgManage.vue` | `el-table` 展开列点击后加载该分类下的组织列表(`/api/company/listed` 传 `classify_code`);展开区背景区分(`#f4f5f9` + 阴影);新增/编辑/删除组织;表单含名称、类型、统一社会信用代码、法人、联系人等字段。 |
|
||
| **关联票据(组织)** | `OrgManage.vue` | 编辑组织时可关联进项/出项票据;票据分两栏展示(进项蓝/出项橙);支持新增关联(搜索下拉弹窗)、切换启用状态、移除关联。 |
|
||
| **展开防重复加载** | `OrgManage.vue` | `onExpandChange` 加 `if (orgMap[code] !== undefined) return` 防二次展开重复请求;抽出 `loadOrgs(code)` 复用;展开工具栏加刷新按钮;加载遮罩显示「加载中…」文字。 |
|
||
| **财务属性入口** | `BillManage.vue`、`router/index.js` | 路由 `/base/bill` 标题改为「财务属性」,图标改为 `mdi:finance`;进入页面先展示卡片列表(仅一张「各类型成本/费用额定增值税率」),点击卡片进入原票据管理详情;卡片样式与「页签管理」阴影一致;面包屑支持返回。 |
|
||
| **KR 协作步骤** | `OkrObjective.vue`、`OkrKeyResult.vue`、`okrMock.js` | KR 数据结构增加 `steps[]`(id/title/description/collaborator_code/collaborator_name/status/due_date);`OkrObjective.vue` KR 抽屉新增「协作步骤」区块(步骤卡片、添加/编辑/删除步骤、状态切换、协作人指派);KR 行显示步骤进度徽章;`OkrKeyResult.vue` 新增「我参与的」视图 tab,筛选本人被指派的步骤并卡片展示;mock CRUD:`mockAddStep` / `mockModifyStep` / `mockRemoveStep` / `mockGetMySteps`。 |
|
||
| **步骤编辑 Bug 修复** | `OkrObjective.vue`、`okrMock.js` | 编辑步骤时若 `collaborator_code` 不在用户列表则清空,防止显示原始工号;`mockGetUsers` 成功后同步更新已有步骤的 `collaborator_code / collaborator_name`。 |
|
||
| **TabManage/TabBuilder 防崩溃** | `TabManage.vue`、`TabBuilder.vue`、`MaterialCategory.vue` | `tmConflictLabels` / `tabBuilderConflictLabels` 计算属性加 `try/catch` 兜底返回 `new Set()`;模板中 `.has()` 调用加可选链 `?.`,防止 undefined 导致页面白屏。 |
|
||
| **IM 聊天按钮尺寸** | `ImPanel.vue` | `.im-tab` 高度从 `80px` 缩小到 `52px`,宽度从 `40px` 缩小到 `36px`,视觉更紧凑。 |
|
||
| **IM 调试日志注释** | `im.js`、`socket.js`、`ImPanel.vue` | 注释所有 `[IM]` / `[Socket]` / `[preview]` 前缀的 `console.log`,保留 `console.warn` / `console.error` 级别的错误提示,控制台不再产生大量正常流程日志。 |
|
||
| **页签字段重名可见提示** | `TabManage.vue`、`TabBuilder.vue` | 重名字段卡片在红框基础上增加「⚠ 重名」徽标与 `el-tooltip` 说明(模板内重复 / 跨页签冲突 / 保存校验闪红);明细/表格 footer 同步展示;模板中 `tmInternalDupLabels`、`internalDupLabels` 去掉误用的 `.value`,与 Vue 自动解包一致。 |
|
||
| **KR 进度展示一致** | `OkrObjective.vue`、`OkrKeyResult.vue` | 列表行 `el-progress` 改读 `kr.progress`(与 `calcKrProgress` 一致),修复「目标值小于起始值」类 KR 误用 `current_value ÷ target_value` 显示成 100% 的问题;KR 详情 `saveDrawer` 保存前回写 `progress`。 |
|
||
| **OKR 数据持久与重映射** | `okr.js`、`OkrReview.vue`(消费 store) | 复盘 `reviews` 在 owner 重映射时与关联 `objective` 对齐;`_remapOwnersIfNeeded` 改为逐字段仅替换「仍不在真实用户表中的 code」,避免 F5 后用户已改的负责人被全表覆盖;`STORE_VER` 升级可清陈旧 localStorage(若当轮有调整)。 |
|
||
| **顶部多页签可关规则** | `router/index.js`、`layouts/index.vue`、`menu.js`、`layout.js`、`utils/tab.js` | 仅路由名 `Index`(首页)`closable: false`,其余路由均可关闭;`router.afterEach` 与侧栏 `addTab` 统一用 `isTabClosableRouteName`;路由表移除分散的 `meta.noClosable`;`addTab` 在页签已存在时若 `closable` 变化则更新;`closeTab` 对首页二次拦截。 |
|
||
|
||
---
|
||
|
||
#### 📋 OKR 列表排版修复 & 下拉选中显示修复 & 复盘负责人修复(2026-05-04)
|
||
|
||
**代码路径:** `src/views/okr/OkrKeyResult.vue`、`src/views/okr/OkrObjective.vue`、`src/views/okr/OkrReview.vue`、`src/data/okrMock.js`、`src/assets/styles/variables.css`
|
||
|
||
| 范围 | 文件 | 变更摘要 |
|
||
|------|------|----------|
|
||
| **KR 列表列宽重调** | `OkrKeyResult.vue` | 周期列 44px、部门列 52px(text-align: center),负责人 88px;进度列加宽至 162px、截止日期加宽至 118px;col-title / col-obj 保持 flex:1 / flex:0.6 弹性,右侧固定列不再拥挤。 |
|
||
| **全局下拉选中空白修复** | `variables.css` | 全局 `.el-select--small .el-select__selection` 的 `overflow: hidden` 改为仅对含 `.el-tag`(多选)时生效;单选框选中文字不再被 `overflow` 裁切(Element Plus 单选占位层带 z-index:-1,外层 overflow 会令其不可见)。 |
|
||
| **下拉选项 label 字段兼容** | `OkrKeyResult.vue`、`OkrObjective.vue` | 部门下拉 label 改为 `d.full_name \|\| d.dept_name`;负责人下拉 label 改为 `u.realname \|\| u.username \|\| u.user_code`;联动的 `onDrawerDeptChange` / `onAddObjDeptChange` 同步兼容。 |
|
||
| **mockGetDepts 归一化** | `okrMock.js` | 先展开 `...d` 再覆盖 `dept_code` / `dept_name` / `full_name`,避免 `...d` 把已设置的键冲回接口原始值;`dept_code` 统一转字符串。 |
|
||
| **mockGetUsers 归一化** | `okrMock.js` | 用户列表 normalize:`user_code` 统一 `String()`,`realname` 补 `username` 兜底;MOCK_OBJECTIVES 分配负责人改用 normalized 列表;MOCK_REVIEWS 在用户同步后按 `objective_id` 匹配更新 `owner_code / owner_name / dept_name`。 |
|
||
| **OkrReview 负责人显示** | `OkrReview.vue` | `getUserInfo(userCode, fallbackName)` 增加第二参数:找不到用户时直接展示 `owner_name`(复盘记录原始字段),不再显示 `USER_HPP` 等 mock 工号;新建复盘写入的 `dept_name` 改用 `deptInfo?.full_name \|\| deptInfo?.dept_name`。 |
|
||
|
||
---
|
||
|
||
#### 📋 IM 群组邀请审批流 & OKR 接入真实数据 & OKR 排版优化(2026-05-03)
|
||
|
||
**代码路径:** `src/store/modules/im.js`、`src/components/ImPanel.vue`、`src/data/okrMock.js`、`src/views/okr/OkrObjective.vue`、`src/views/okr/OkrKeyResult.vue`
|
||
|
||
| 范围 | 文件 | 变更摘要 |
|
||
|------|------|----------|
|
||
| **IM 群组邀请改为需审批** | `im.js` | `inviteToGroup` 不再直接调 `/group/invite_user_to_group`,改为向每位被邀请人发送 `[GROUP_INVITE]:JSON` 格式的 1v1 消息(含 groupID / groupName / inviterName 等信息)。新增 `acceptGroupInvite(msg)`:解析邀请消息、调 Admin API 真正入群、发回同意通知;新增 `rejectGroupInvite(msg)`:发回拒绝通知。 |
|
||
| **IM 邀请卡片 UI** | `ImPanel.vue` | 识别 `[GROUP_INVITE]:` 消息并渲染交互卡片(群名、邀请人、同意/拒绝按钮);`inviteStatusMap` ref 追踪已处理状态,防止重复操作;`isGroupInviteMsg` / `parseGroupInvite` / `getInviteStatus` 等辅助函数。 |
|
||
| **IM 查看群成员** | `ImPanel.vue`、`im.js` | 群聊头部新增成员按钮;点击调 `imStore.getGroupMembers(groupID)` 弹窗展示;修复 `getGroupMembers` 返回 `res?.members \|\| res?.groupMembers` 的取值层级错误(`_adminPost` 已解包 `.data`)。 |
|
||
| **OKR 接入真实 API** | `okrMock.js` | `mockGetUsers` 调 `/api/user/listed`,成功后覆盖 `MOCK_USERS` 并按轮询方式将真实用户分配给 `MOCK_OBJECTIVES` 及其 `key_results` 的 `owner_code / owner_name`。`mockGetDepts` 调 `/api/company/listed` + `/api/dept/listed`,返回字段统一为 `dept_code / dept_name / full_name`;均有 mock 数据降级兜底。 |
|
||
| **OKR 进度计算与单位** | `OkrObjective.vue`、`OkrKeyResult.vue` | 进度改为 `(current_value / target_value) * 100`,el-progress 百分比、主文字均以此为准;次要行显示 `current_value / target_value`(含 unit)。Mock 数据单位 `'s'` → `'秒'`,`'h'` → `'小时'`。 |
|
||
| **OKR 目标列表排版** | `OkrObjective.vue` | `col-summary` 改为 flex 横排 + `mask-image` 右侧渐变遮罩展示 KR 概要 chips(不截字符而是自然溢出渐隐);`progress-wrap` gap 收至 4px,`progress-text` min-width 收至 36px;`col-summary` padding-left 加大至 20px 与进度列拉开间距;`col-owner` 100px,`col-progress` 148px。 |
|
||
| **OKR KR 列表排版** | `OkrKeyResult.vue` | 各列 flex / px 比例全面重算:`col-badge` 44px,`col-title` flex:1,`col-obj` flex:0.6,`col-period` 46px,`col-dept` 52px,`col-owner` 88px,`col-progress` 162px,`col-date` 118px,`col-ops` 68px。 |
|
||
|
||
---
|
||
|
||
#### 📋 物料页签 UI、控件库排版与分类字段抽屉(2026-04-06)
|
||
|
||
**代码路径:** `weihai/client/showtime_weihai/src/views/material/`(及同阶段若已合并的 `src/layouts/`、`src/store/modules/layout.js`)
|
||
|
||
| 范围 | 文件 | 变更摘要 |
|
||
|------|------|----------|
|
||
| table 控件展示名 | `TabBuilder.vue`、`TabManage.vue` | 界面与映射中文案 **「明细表格」→「明细/表格」**(控件库、字段类型名、预览与画布提示、校验与注释等)。 |
|
||
| 控件库两列排版 | `TabBuilder.vue`、`TabManage.vue` | 左侧控件栏 **256px**(原 210px);`.tb-wi-label` / `.tm-wi-label` 使用 **`white-space: nowrap`**,避免「明细/表格」在两列网格内折行。 |
|
||
| 注释统一 | `MaterialList.vue`、`MaterialCategory.vue` | 注释 / 样式注释中「明细表格」→「明细/表格」,与界面一致。 |
|
||
| 切换分类仍显示旧「编辑字段」 | `MaterialCategory.vue` | 新增 **`resetCatFieldEditorUI()`**:`cancelBatchCatOptRules`、清空 **`catFieldDrawer`**、关闭 **`custFieldRuleDlgVisible`** / **`catOptRuleDlgVisible`**、重置 **`catOptBatchSelected`**;在 **`openForm` 开头**与 **`watch(formVisible)`**(面板关闭)调用。根因:`openForm` 深拷贝替换 `form.tabs` 后抽屉仍引用旧字段对象。 |
|
||
| keep-alive / 脏页(同阶段) | `layouts/index.vue`、`store/modules/layout.js`、`MaterialCategory.vue`、`TabManage.vue`、`MaterialList.vue`、`SceneManage.vue` | 多页签缓存、Tab 未保存指示、`setPageDirty` / `onBeforeRouteLeave`、`defineOptions` 等以仓库实际提交为准。 |
|
||
|
||
#### 📋 物料列表选配编辑器(2026-04-09 ~ 2026-04-10)
|
||
|
||
**代码路径:** `weihai/client/showtime_weihai/src/views/material/MaterialList.vue`
|
||
|
||
| 范围 | 变更摘要 |
|
||
|------|----------|
|
||
| 文案与布局 | 左栏「控件库」→「选项」;选配编辑区中间/右侧「控件」→「选项」。**添加选项组**入口移至左侧面板(含说明与类型下拉);画布底部独立按钮已移除。 |
|
||
| Sortable 与卡顿 | 左栏克隆到画布、画布内排序;避免 `watch(components.length)` 与拖拽 splice 冲突导致 Sortable 被提前销毁;关闭弹窗与 `nextTick` 后重绑实例。 |
|
||
| 新建物料 | 无分类时不显示底部「添加选配 / 移除选配」整块区域。 |
|
||
| 业务逻辑 | 选配选项以 **关联物料**(`materials[]`、`goods_code`)为主,物料多选弹窗;**添加选项**后立即可「添加物料」;控件行仅「添加选项」,不再在行内「添加物料」直建物料选项。 |
|
||
| 右侧面板 | 选项组型选项:右侧只编辑名称与说明,**去掉**「关联物料」区块(物料仅在中间卡片维护)。 |
|
||
| 查看态 | 选中选项后展示关联物料;每个物料名单独 **带边框小块** 展示,无前缀文案;物料名称输入框约 **15 字宽(220px)**。 |
|
||
| 数据 | `fields.__selection__` 结构不变;`options[].materials[]` 存 `label` 等,展示用 `label` / `name` / `goods_code` 回退。 |
|
||
|
||
#### 📋 多维表格、列类型(字典 / 数据源 / 直接查表)与物料侧栏(2026-04-14 ~ 2026-04-15)
|
||
|
||
**代码路径:** `weihai/client/showtime_weihai/src/views/material/MultiDimensionalTable.vue`、`src/views/material/mdt/ColConfigPanel.vue`、`LinkPicker.vue`、`FormulaEditor.vue`、`src/composables/useFormulaEngine.js`;物料分类/列表与权限:`MaterialCategory.vue`、`MaterialList.vue`、Pinia 与路由权限(以仓库实际提交为准)。
|
||
|
||
| 范围 | 变更摘要 |
|
||
|------|----------|
|
||
| 动态表 CRUD | `POST /api/excel/dynamic/listed`、`view`、`add`、`modify`、`remove`;数据源清单:`/api/excel/dynamic/data_source/listed`。 |
|
||
| 列类型 | 基础字段;**关联多维表**(仅动态表,手动选记录);**数据源选择**(系统数据源,`dsTarget` + `dsDisplayField`);**查找引用**(经关联列 或 **直接查表** + `lookupConditions` + `lookupAggregate`);**计算公式**;**单选** / **多选**(自定义 `options` + 色板,或 **`optionSource: dict`** + **`dictKey`**,字典加载与 **TabManage** 同源 `/api/dict/listed`;单元格存字典子项 **key**)。 |
|
||
| 列配置与层级 | `ColConfigPanel`:`freeze` 避免遮挡公式编辑器;面板 **贴屏幕右缘**;字典分组选择器与预览条数。 |
|
||
| 表体与列操作 | 内联编辑;行右键插入/删除与删除确认;列头菜单(改列、复制列、隐藏列、按列筛选);工具栏 **筛选**、**N 列已隐藏**;列宽默认值与最小宽度调整。 |
|
||
| 保存与体验 | 自动保存防抖、`isDirty`、手动保存按钮态;底部 **自动保存 Toast**;切换路由/换表未保存提示。 |
|
||
| 公式 | `evaluateFormula`、`buildResolvedCells`(关联、lookup、单选/多选解析为展示值供公式引用);列定义与行数据随表内容持久化。 |
|
||
| 物料与权限(同阶段) | 仓库等页签字段与其它 tab 展示对齐;仓库选择条 **横向滚动**;子仓随父仓权限展示;页面级 **查看 / 增 / 改 / 删** 与 `leader_level=99` 超级管理员绕过;`scope` 空与全量语义。 |
|
||
|
||
**多维表 `content` 约定(节选):** `columns[]` 含 `id`、`name`、`type`、`width`、`hidden`、`options`、`optionSource`、`dictKey`、`lookupMode`、`directTable`、`lookupConditions`、`lookupAggregate`、`dsTarget`、`dsDisplayField`、`formula` 等;`rows[].cells` 为 `colId →` 存值。字典列与页签字段一致存 **编码**;当前页字典在进页预拉,后台字典变更后建议 **整页刷新** 以刷新缓存。
|
||
|
||
#### 📋 特征分类、配置项管理、页签字段 key 加载优化(2026-04-27 ~ 2026-04-28)
|
||
|
||
**代码路径:** `src/views/material/FeatureManage.vue`、`src/views/base/ConfigManage.vue`、`src/views/material/TabManage.vue`、`TabBuilder.vue`、`MaterialList.vue`、`AssemblyManage.vue`、`src/router/index.js`、`src/data/appChangelog.js`
|
||
|
||
| 范围 | 文件 | 变更摘要 |
|
||
|------|------|----------|
|
||
| **特征分类左侧树** | `FeatureManage.vue` | 新增可折叠/拖拽调宽的左侧分类侧栏(复用 `useResizablePanel`);分类树支持两级展开(`expandedCodes` reactive 对象驱动展开状态),支持新建/编辑/删除根分类与子分类(`/field/classify/add|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<string>`);三处字段卡片(顶层分组、子组普通字段、子组 table 字段)绑定 `tb-fc-conflict` class;CSS 添加红色边框 + 淡红色 glow(`border-color: #f56c6c; box-shadow: 0 0 0 2px rgba(245,108,108,.2)`)。`MaterialCategory.vue` 新增 `tabBuilderConflictLabels` computed,实时收集除当前编辑页签外所有页签(含继承、引入模板)的字段 label,传给 `<TabBuilder :conflict-labels="...">`。 |
|
||
| **页签模板管理字段重名实时检测** | `TabManage.vue` | 新增 `tmConflictLabels` computed:遍历 `form.value.groups` 及各组 `subgroups`,统计出现超过一次的 label 集合;三处字段卡片绑定 `tm-fc-conflict` class,CSS 同 TabBuilder。检测范围为**模板内部**重名(独立模板不做跨模板检测)。 |
|
||
|
||
**效果说明:**
|
||
- 用户在页签构建器中修改字段名称时,若与其他页签字段重名,**卡片立即出现红色边框**,无需等到点保存才提示。
|
||
- `TabManage` 中同一模板内有重名字段时同样实时标红,帮助工作人员快速定位问题字段。
|
||
|
||
---
|
||
|
||
#### 📋 WebSocket 实时连接、选配关联展示优化与仓库管理精简(2026-04-22)
|
||
|
||
**代码路径:** `src/utils/socket.js`(新增)、`src/store/modules/user.js`、`src/router/permissions.js`、`src/views/login/Login.vue`、`src/views/material/MaterialList.vue`、`MaterialCategory.vue`
|
||
|
||
| 范围 | 文件 | 变更摘要 |
|
||
|------|------|----------|
|
||
| **WebSocket 服务** | `src/utils/socket.js`(新增) | 单例 WebSocket 客户端,连接地址 `ws://117.133.111.165:8708/?token=&passport_id=`;每秒发送 ArrayBuffer 心跳(code=1501001);断线后 3 秒自动重连(无限次);主动调用 `disconnect()` 时不重连。导出 `connect(token, userId)`、`disconnect()`、`getSocket()`。 |
|
||
| **登录后自动连接** | `src/store/modules/user.js` | `login()` 成功后调用 `socketConnect(token, userId)`;`logout()` 调用 `socketDisconnect()` 并清除 `localStorage` 中的 `userId`;`userId` 字段从响应 `user_id` 读取并持久化到 `localStorage`。 |
|
||
| **页面刷新后恢复连接** | `src/router/permissions.js`、`Login.vue` | `router.beforeEach` 有 token 放行时,若 socket 未连接则补充建立;`Login.vue` `onMounted` 检测到已有 token 时同样调用 `socketConnect`,覆盖「已登录直接进入」场景。 |
|
||
| **选配追加选项分组展示** | `MaterialList.vue` | 新增 `getAssemblyOptionGroups(cat, field)` helper,将 `assembly_goods` 来源的追加选项按来源物料分组;下拉单选/多选框中每组单独一行渲染(`mf-asm-group-row`),行末以方形橘色标签显示来源物料名(`mf-asm-group-source`);追加选项统一设为 `disabled`,仅可查看不可选择。 |
|
||
| **多选字段自动双列** | `MaterialList.vue` | `mfFieldSpan(field)` 函数:对 `multiselect` 类型取所有选项中**最长两项**字数之和,超过 10 字则返回 `mf-field-span2`(占双列),避免选中标签被截断。 |
|
||
| **特征关联字段提示** | `MaterialCategory.vue` | 「特征关联」配置区 `featureKey`(英文)上方添加 `<span class="fr-field-hint">被关联特征</span>`,`featureLabel`(中文)上方添加 `<span class="fr-field-hint">关联选项名称</span>`;中文标签文字改为橘色(`#e6a23c`),便于工作人员区分字段用途。 |
|
||
| **仓库管理只显示父仓库** | `MaterialCategory.vue`、`MaterialList.vue` | `permittedWarehouses` / `mlPermittedWarehouses` computed 末尾追加 `.filter(w => !w.parent_code)`,过滤掉所有子仓库,仓库管理页签只呈现顶层父仓库。 |
|
||
|
||
---
|
||
|
||
#### 📋 物料特征信息交互与分类字段序列化修复(2026-04-20)
|
||
|
||
**代码路径:** `weihai/client/showtime_weihai/src/views/material/MaterialList.vue`、`MaterialCategory.vue`
|
||
|
||
| 范围 | 变更摘要 |
|
||
|------|----------|
|
||
| **特征信息 tab — radio/checkbox 选项视觉区分** | 原有选项(分类自定义)显示为**实心圆**(未选灰色实心,选中蓝色实心);来自 `assembly_goods` 追加的选项显示为**空心圆**(未选灰色圆边,选中蓝色圆边+圆心蓝点)。两类选项均为圆形,通过有无填充即可区分来源。 |
|
||
| **特征信息 tab — 布局** | 原有选项横排(`flex-wrap: wrap`,多个并排一行);追加选项各占独立一行(`flex-basis: 100%`);两组之间用 `mf-opts-break` 留出间距,不显示分割线。 |
|
||
| **MaterialCategory — radio 选项序列化修复** | `cleanField` 函数序列化字段时,`options` 数组原本仅针对 `select / multiselect / checkbox` 类型保存,`radio` 被遗漏。导致父类保存的单选框字段在子类继承后 `getImportOptions` 返回空数组、选项显示空白。**修复**:将 `radio` 加入序列化类型列表,确保 `options: ['选项A', '选项B', ...]` 随字段一起持久化到服务端。 |
|
||
| **MaterialCategory — radio 抽屉初始化修复** | `openCatFieldDrawer` 原本仅对 `select / multiselect / checkbox` 从 `optionsRaw` 初始化 `options` 数组,`radio` 字段打开抽屉时 `options` 为 `undefined`,编辑列表显示「暂无选项」。**修复**:同样对 `radio` 执行 `parseImportOptions(field.optionsRaw) → field.options` 的初始化,已有数据可正常回显与编辑。 |
|
||
|
||
**关键逻辑说明:**
|
||
- `isAssemblyOption(cat, field, optLabel)` 判断某选项是否来自 `assembly_goods`,返回 `true` 时模板附加 `is-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 `<style>` 块确保穿透 Element Plus 浮层。 |
|
||
| **创建/编辑物料问号说明** | `MaterialList.vue` | 字段标签行新增 `el-popover` 问号图标(`QuestionFilled`),普通组和子组字段均有;样式复用 `desc-popover` 黑底白字。 |
|
||
| **必填字段开关** | `TabManage.vue`、`TabBuilder.vue` | 字段编辑抽屉「私有字段」下方新增「必填字段」`el-switch`,绑定 `field.required`;公式字段不显示该开关。`createFieldFromType` / `normalizeField` / `cleanField` 均补充 `required: false` 默认值,序列化时保留。 |
|
||
| **必填校验 - 创建/编辑物料** | `MaterialList.vue` | `submitMaterial` 保存前遍历所有分类的 tab 字段(含子组),必填且为空时切换至对应分类 tab 并提示字段名称,阻止保存;字段标签旁显示红色 `*`。 |
|
||
| **必填校验 - 物料分类** | `MaterialCategory.vue` | `submitForm` 保存前检查自定义页签(`type` 为空或 `'custom'`)的 `field.defaultValue`,及继承扩展页签(`inherited-extended`)的 override / 父级默认值;为空时切换页签并提示,阻止保存;所有字段标签(含继承/引入/自定义/子组,共 15+ 处)显示红色 `*`。 |
|
||
| **`cleanField` 数据保全** | `MaterialCategory.vue` | `cleanField` 补充 `required`、`showUnit` 字段;`cleanGroups` 新增对 `subgroups` 的递归 `cleanField`,防止子组字段属性在保存时被丢弃。 |
|
||
| **自定义页签 `type` 补全** | `MaterialCategory.vue` | `confirmBuilderDialog` 保存自定义页签时补写 `type: 'custom'`,修复保存后必填校验 `tabType === 'custom'` 不匹配的问题。 |
|
||
| **选项改名同步显隐规则** | `TabManage.vue`、`TabBuilder.vue` | 选项输入框新增 `@focus` 捕捉旧值,`@change` 时遍历所有字段(含子组)的 `rules` 条件,将 `eq`/`neq` 条件值及 `contains`/`not_contains` 数组中的旧值替换为新值;同步 `optionRules` 的 key。 |
|
||
| **选项删除清理规则** | `TabManage.vue`、`TabBuilder.vue` | `removeOption` 删除选项时同步清理 `optionRules[deletedVal]` 及所有字段规则中引用该选项值的条件(`eq`/`neq` 置空,`contains`/`not_contains` 过滤)。 |
|
||
| **MaterialCategory 选项文本框规则同步** | `MaterialCategory.vue` | `catFieldDrawer` 的 `optionsRaw` 文本框新增 `@focus` 记录旧选项、`@change` 时 `syncCatFieldOptRules()`:检测被删除的选项并清理相关规则条件和 `optionRules` key。 |
|
||
| **单位标签显示开关(showUnit)** | `TabManage.vue`、`TabBuilder.vue`、`MaterialCategory.vue`、`MaterialList.vue` | 字段编辑器公式/数字/金额三类单位输入行下方新增「名称后显示单位」开关(有单位时才出现),默认开启。关闭后标签不再拼接 `(单位)`。`createFieldFromType` / `normalizeField` / `cleanField` 均补充 `showUnit: true` 默认值。所有渲染处改为 `field.unit && field.showUnit !== false` 判断(共 22 处替换)。数字/金额输入框右侧始终显示单位(不受 `showUnit` 控制):MaterialCategory 补 2 处 `ifi-input-unit-wrap` wrapper,MaterialList/预览本已有。 |
|
||
|
||
#### 📋 复查修复(2026-04-04)
|
||
|
||
**代码路径:** `weihai/client/showtime_weihai/src/views/material/`
|
||
|
||
| 修复点 | 文件 | 问题描述 | 修复方式 |
|
||
|--------|------|----------|----------|
|
||
| **必填校验未切换到对应页签** | `MaterialList.vue` | `submitMaterial` 在发现必填字段为空时,只切换了分类 `activeCatCode`,未切换到该字段所在页签,用户无法直观定位。 | 改为按「分类 → 页签 → 字段」三层遍历,命中时同步设置 `cat.activeTabCode = tab.code`,再 `nextTick` 提示。 |
|
||
| **TabManage `normalizeFields` 未处理子组** | `TabManage.vue` | 打开编辑时 `normalizeFields` 只对 `g.fields` 归一化(补全 `required`、`showUnit`、`options` 迁移等),`g.subgroups[].fields` 未处理,历史子组字段缺少这些属性。 | 抽出 `normalizeOneField` 函数,同时对主字段和子组字段调用,确保两层字段都被归一化。 |
|
||
| **`catFieldDrawer` 中 `amount` 类型用错控件** | `MaterialCategory.vue` | 自定义页签字段编辑抽屉里,`number` 有 `el-input-number`,`amount` 却因缺少判断落入 `el-input`(纯文本),精度和单位均未展示。 | 将条件改为 `number \|\| amount`,使用 `ifi-input-unit-wrap` + `el-input-number`(金额加 `:precision`)+ 单位显示。 |
|
||
| **继承页签 number/amount override 用错控件** | `MaterialCategory.vue` | 继承页签(`inherited-extended`)中数字/金额字段的 override 输入使用通用 `el-input`,无精度控制和单位展示,包含主组和子组两处。 | 在两处 `v-else` 前各加 `v-else-if="number\|\|amount"` 分支,改用 `el-input-number` + `ifi-unit`,通过 `@update:model-value` 写入 `setInhOverride`。 |
|
||
|
||
---
|
||
|
||
|
||
### 管理后台首页 Dashboard 开发记录
|
||
|
||
#### 📐 首页布局(4行)
|
||
|
||
| 行 | 列数 | 组件 | 说明 |
|
||
|---|---|---|---|
|
||
| 第一行 | 4列等宽 | PurchaseTrendCard / SalesAmountCard / InventoryWarning / EventTimeline | 数据卡片区 |
|
||
| 第二行 | 3列(1/3+1/3+1/3) | RankingChart / 内容待定 / CategoryRoseChart | 图表区 |
|
||
| 第三行 | 全宽 | OrderGanttChart | 订单甘特图 |
|
||
| 第四行 | 1/4 + 3/4 | ChordChart / SankeyChart | 仓库关系 + 资金流向 |
|
||
|
||
#### 🧩 组件清单
|
||
|
||
**InventoryWarning(库存预警)**
|
||
- 2行×6列电池条,固定 `height: 52px`(不能用 flex:1,否则第2页单行时会变高)
|
||
- 18条数据分2页,4秒自动轮播,左右滑动动画
|
||
- `.grid-wrapper` 用 `position: relative + overflow: hidden` + `.inventory-grid` 用 `position: absolute` 实现动画不撑大容器
|
||
- 圆点翻页固定 `height: 20px`
|
||
|
||
**RankingChart(排行榜)**
|
||
- 商品排行 / 机构排行切换,柱状图 / 折线图切换
|
||
- 折线图:`symbol: 'none'` 无圆点,渐变色线条和面积
|
||
- 商品排行:青蓝渐变(`#36d1dc → #5b86e5`);机构排行:橙金渐变(`#f7971e → #ffd200`)
|
||
|
||
**CategoryRoseChart(品类分布对比)**
|
||
- 两个南丁格尔图并排(`roseType: 'area'`)
|
||
- 左侧采购分布,右侧销售分布,竖排文字标识,相同品类颜色一致
|
||
|
||
**OrderGanttChart(订单甘特图)**
|
||
- 左侧仓库列表点击筛选,甘特条渐变色+圆角+阴影
|
||
- 订单号24位(7位随机前缀+17位编号),蓝色可点击,下划线用 `graphic + convertToPixel` 手动绘制,`labelWidth: 148`
|
||
- 今日红色虚线标记,Tooltip 需要 null 检查防 markLine 触发报错
|
||
- 日期选择器用 `Date` 对象,`xAxis.min/max` 用 `new Date().getTime()`
|
||
|
||
**ChordChart(仓库调货关系)**
|
||
- ECharts `graph` 类型,`layout: circular`
|
||
- 圆心用 `graphic` 显示总调货量(蓝色半透明底+文字)
|
||
- 节点标签:仓库名(加粗)+ ↑调出(红)↓调入(绿),节点大小随吞吐量动态变化
|
||
- ⚠️ 待优化:底部节点标签偏移,考虑改用 `force` 弹力布局
|
||
|
||
**SankeyChart(资金流向分析)**
|
||
- ECharts `sankey` 类型,`left/right: 12%` 防标签裁切
|
||
- 节点颜色与 `NODE_COLORS` 映射,Tooltip 显示流向金额
|
||
|
||
#### 📦 Mock 数据(adminDashboard.js)新增
|
||
|
||
| 导出名 | 说明 |
|
||
|---|---|
|
||
| `categoryRoseData` | 品类采购/销售分布(8个品类) |
|
||
| `warehouseTransferData` | 仓库间调货量(6仓库,9条流向) |
|
||
| `fundFlowData` | 资金流向桑基图节点和连线 |
|
||
| `ganttOrders` | 订单号改为24位(7位随机前缀+17位) |
|
||
|
||
#### 🔧 开发规范(重要)
|
||
|
||
**容器高度铁律:**
|
||
- 任何组件根元素:`width:100%; height:100%; overflow:hidden`
|
||
- flex子元素撑满:`flex:1; min-height:0`(`min-height:0` 必须加)
|
||
- 动画切换:用 `position:relative + overflow:hidden` 包裹,子元素 `position:absolute`
|
||
- ECharts初始化:`onMounted + nextTick`,销毁:`onUnmounted` 中 `dispose()` + 移除 resize 监听
|
||
|
||
**全局字体:**
|
||
- 中文:`var(--body-font-family)` → `DingTalk_JinBuTi_Regular`
|
||
- 数字:`var(--number-font-family)` → `DS-DIGIT`
|
||
|
||
#### ⚠️ 已知问题
|
||
|
||
| 问题 | 原因 | 状态 |
|
||
|---|---|---|
|
||
| InventoryWarning 第2页电池条变高 | 用了 `flex:1` | ✅ 已修复(固定 `height:52px`) |
|
||
| Gantt tooltip 报错 `undefined.events` | markLine 触发无 null 检查 | ✅ 已修复 |
|
||
| Mapbox resize 报错 | 卸载后未移除 resize 监听 | ✅ 已修复 |
|
||
| ChordChart 底部节点标签偏移 | circular 布局边距不足 | ⚠️ 待优化 |
|
||
|
||
#### 🗺️ 路由结构(当前完整)
|
||
|
||
```
|
||
/ (首页 - 仪表盘)
|
||
/purchase (采购)
|
||
├── /purchase/order 采购订单
|
||
├── /purchase/order/:orderNo 采购订单详情(hidden,不加入Tab栏)
|
||
├── /purchase/supplier 供应商管理
|
||
├── /purchase/warning 采购预警
|
||
├── /purchase/batch 批次管理
|
||
├── /purchase/alloc-priority 备货配额优先级 ← 新增
|
||
└── /purchase/flow 采购流程看板 ← 新增
|
||
/inventory (库存)
|
||
├── /inventory/overview 库存总览(含商品多仓对比Tab)
|
||
├── /inventory/transfer 仓库调拨
|
||
├── /inventory/warning 库存预警
|
||
└── /inventory/batch 批次查询
|
||
/sales (销售)
|
||
├── /sales/order 销售订单
|
||
├── /sales/customer 客户管理
|
||
├── /sales/stats 销售统计
|
||
└── /sales/return 销售退货
|
||
/material (物料管理)
|
||
├── /material/list 物料列表
|
||
├── /material/category 分类管理
|
||
├── /material/scene 场景管理 ← 新增(2026-03-07)
|
||
├── /material/feature 特征管理 ← 新增(占位,2026-03-07)
|
||
└── /material/template 模板管理 ← 新增(占位,2026-03-07)
|
||
/finance (财务)
|
||
├── /finance/overview 收支概览
|
||
├── /finance/fundflow 资金流向
|
||
├── /finance/payable 应付账款
|
||
└── /finance/receivable 应收账款
|
||
/base (基础数据) ← 新增模块
|
||
├── /base/dict 数据字典(19个字典,分组管理)
|
||
├── /base/org 组织单元(树形层级,增删改)
|
||
├── /base/warehouse 仓库档案(5个仓库,增删改+详情)
|
||
└── /base/bill 票据管理 ← 新增(2026-03-08)
|
||
/login (地图登录页)
|
||
```
|
||
|
||
**对应 view 文件目录:**
|
||
- `src/views/purchase/` — 4个页面(均为占位页)
|
||
- `src/views/inventory/` — 4个页面(均为占位页)
|
||
- `src/views/sales/` — 4个页面(均为占位页)
|
||
- `src/views/finance/` — 1个页面(占位页)
|
||
|
||
#### 🐛 路由过渡动画修复
|
||
|
||
**问题:** 管理后台内部页面跳转时,触发了"地图→管理后台"的 `page-fade-dashboard` 动画
|
||
|
||
**根本原因:**
|
||
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`
|
||
- 进入 `/login` → `page-fade-map`
|
||
- 管理后台内部跳转 → `''`(无动画)
|
||
|
||
---
|
||
|
||
### 采购模块已实现功能
|
||
|
||
#### 📄 采购订单页(`src/views/purchase/PurchaseOrder.vue`)
|
||
|
||
**功能:**
|
||
- 顶部4张统计卡片:总数 / 待审核 / 审核中 / 本月采购总额
|
||
- 搜索筛选:订单号模糊搜索、供应商/采购组织/状态下拉(选即生效)、日期范围
|
||
- 表格:采购号(蓝色可点击)、供应商、组织、仓库、商品、数量(DS-DIGIT字体)、总额、票据类型、状态标签、操作列(左对齐:详情/审核/归档/复制/删除)
|
||
- 创建弹窗功能:
|
||
- **① 采购组织自动推荐**:选商品后自动出现黄色推荐条,显示推荐组织+票据类型+首选供应商,点"一键填入"自动填充空白字段(不覆盖已填内容)
|
||
- **② 买手分层定价**:费用估算面板中,按数量自动判断大(≥500件)/中(100~500件)/小(<100件)买手,显示三档价格对比,当前档高亮蓝色
|
||
- **费用估算面板**:选完供应商+商品+数量+票据类型后自动计算货款、增值税、预估运费、综合到手成本
|
||
- 未填字段时显示灰色引导提示
|
||
|
||
**数据来源:** `src/api/purchase.js`(mock,联调时替换)
|
||
|
||
---
|
||
|
||
#### 🏢 供应商管理页(`src/views/purchase/SupplierManage.vue`)
|
||
|
||
**功能:**
|
||
- 顶部4张统计卡片:总数 / 已核准(绿)/ 潜在(橙)/ 已退出(灰)
|
||
- 两个 Tab 切换:
|
||
- **供应商档案 Tab**:规范字段表格(名称/简称/分类/管理组织/纳税类别/结算方式/账期/联系电话/状态)
|
||
- 工具栏:名称搜索 + 分类/状态筛选 + **新增供应商**按钮
|
||
- 操作列:报价 / 编辑 / 删除
|
||
- 分页:支持10/20/50条/页
|
||
- **新增/编辑弹窗**:供应商名称\*、简称、分类\*、管理组织\*、纳税类别、国家/地区、交易币种、结算方式、账期、发运方式、联系电话、电子邮箱、注册地址、供应商状态、备注
|
||
- **报价明细抽屉**:沿用原有发票兼容性矩阵 + 报价对比表(按名称匹配 mockSuppliers)
|
||
- 删除:二次确认弹窗
|
||
- **仓库调拨费率 Tab**:5仓库×5仓库调拨费率矩阵,按票据类型分列
|
||
- 移动端:全屏抽屉+返回按钮
|
||
|
||
**数据来源:**
|
||
- `mockVendors`(`src/api/purchase.js`):供应商档案 mock,规范字段,支持 getVendorList / addVendor / updateVendor / deleteVendor
|
||
- `mockSuppliers`(`src/api/purchase.js`):采购定价数据,供报价抽屉使用,不参与档案 CRUD
|
||
- 字典引用:`dictVendorClasses / dictCurrencies / dictSettleMethods / dictShipMethods / dictCountries`(`src/api/material.js`)
|
||
|
||
---
|
||
|
||
#### 🔔 采购预警页(`src/views/purchase/PurchaseWarning.vue`)
|
||
|
||
**核心逻辑:** 当前库存 ÷ 日均销量 = 预计断货天数
|
||
|
||
**功能:**
|
||
- 顶部4张风险卡片:紧急(≤3天,红) / 预警(≤7天,橙) / 充足(绿) / 预警商品预估采购额(蓝),点击卡片直接筛选
|
||
- 筛选栏:风险等级/仓库/品类,选即生效
|
||
- 预警表:行颜色编码(红/黄/白),断货倒计时、建议采购量、参考价区间、预估费用、最优供应商
|
||
- 点击行展开右侧抽屉:商品详情+近6个月采购价走势折线图(渐变色)
|
||
- "一键采购"按钮:跳转采购订单页并预填商品/供应商/数量/仓库
|
||
- 移动端全屏抽屉,顶部 padding 留出 tab 栏高度(56px)
|
||
|
||
**首页大屏联动:** `InventoryWarning.vue` 的红色"X 紧急"badge 可点击,跳转到采购预警页
|
||
|
||
**数据来源:** `src/api/purchaseWarning.js`(mock,联调时替换)
|
||
|
||
---
|
||
|
||
#### 📦 API 文件说明
|
||
|
||
| 文件 | 内容 |
|
||
|---|---|
|
||
| `src/api/purchase.js` | 采购订单CRUD、供应商数据、定价矩阵、分层定价、推荐规则、调拨费率 |
|
||
| `src/api/purchaseWarning.js` | 预警数据、断货计算、价格走势、统计概览 |
|
||
|
||
**注意:** 联调时只需替换各文件中的 mock 函数为 `request(...)` 调用,页面代码无需改动。
|
||
|
||
---
|
||
|
||
---
|
||
|
||
#### 📦 批次管理页(`src/views/purchase/BatchManage.vue`)
|
||
|
||
**功能:**
|
||
- 顶部4张统计卡片:总批次数 / 在途批次 / 部分到库 / 在途件数合计
|
||
- 搜索筛选:批次号模糊搜索、商品名称模糊搜索、仓库下拉、状态下拉(选即生效)
|
||
- 表格列:批次号 / 关联采购单 / 商品名称 / 目标仓库 / 采购总量 / 在途数量(橙色)/ 在库数量(绿色)/ 到货进度条 / 预计到货日 / 状态标签
|
||
- **到货进度条**:`el-progress`,按到库百分比自动变色(<30% 红 / 30~80% 橙 / ≥100% 绿)
|
||
- **行颜色编码**:在途批次浅黄色背景 / 部分到库浅蓝色背景
|
||
- 点击行展开右侧抽屉(异步加载详情):
|
||
- **🆕 7阶段批次流程条**:采购下单→供应商发货→在途运输→到货待分配→分配入库中→入库完成→财务结算,已完成绿色打勾,当前阶段蓝色高亮,未来灰色
|
||
- 基本信息(含 🆕 **采购类型** 标签:统筹采购/独立采购)
|
||
- 到货进度条
|
||
- **🆕 分仓分配明细卡片**(统筹采购时显示):每个仓库一张卡,含优先级圆圈(P1绿/P2橙/P3灰)、发票兼容性标签、到货进度条、已到/应到件数
|
||
- 物流时间线(补充财务结算节点)
|
||
- 分页:默认每页15条
|
||
- 移动端:全屏抽屉 + 顶部返回按钮
|
||
|
||
**新增数据(`src/api/purchase.js`):**
|
||
- `BATCH_STAGES`:7阶段定义常量(导出)
|
||
- `mockBatches` 每条新增 `batchStage`(当前阶段1-7)和 `isCoordinated`(是否统筹采购)
|
||
- `genWarehouseAlloc(batch)`:根据批次数据自动生成3仓库分配明细
|
||
- `getBatchDetail(id)`:返回批次 + 分仓分配详情
|
||
|
||
**数据来源:** `src/api/purchase.js`
|
||
|
||
---
|
||
|
||
#### 🏗️ 基础数据模块(`/base`)
|
||
|
||
**组织单元管理页(`src/views/base/OrgManage.vue`):**
|
||
- 顶部4张统计卡片:总数 / 公司/总部 / 分公司 / 已停用
|
||
- 工具栏:名称搜索 + 组织形态/状态筛选 + 新增按钮
|
||
- 树形表格(默认全展开):名称+图标 / 编码 / 简称 / 形态标签 / 上级组织 / 职能标签(销售/采购/库存/财务/工厂)/ 负责人 / 电话 / 纳税类型 / 状态
|
||
- 操作列:编辑 / 添加子级 / 删除(有子组织时拒绝删除)
|
||
- 新增/编辑弹窗:名称\*、编码\*、简称、组织形态\*、上级组织、负责人、联系人、电话、地址、纳税人名称/识别号/类型、状态、职能多选(销售/采购/库存/会计主体/工厂)、描述
|
||
- **mock 数据**:5条,与项目全局使用的组织名完全对应(XS威海总部为根,4个分公司为子节点)
|
||
|
||
**仓库档案管理页(`src/views/base/WarehouseManage.vue`):**
|
||
- 顶部4张统计卡片:总数 / 顶级仓库 / 已启用 / 已停用
|
||
- **左右分栏布局**(2026-05-08 重构):
|
||
- 左侧:中心仓(父级)列表,顶部搜索框,hover 显示三点菜单(添加子仓库/编辑/删除),不占仓库名宽度;底部「新增中心仓」快捷按钮
|
||
- 中间:可左右拖拽分隔条,宽度持久化至 `localStorage`(key: `wh_sidebar_width`)
|
||
- 右侧:选中中心仓的子仓库(门店仓)列表,带表头(仓库名称/编码/所属组织/责任人);编码蓝色、组织绿色、责任人橙色;奇偶行交替背景色;hover 显示编辑/删除操作;右上角「+ 添加子仓库」按钮
|
||
- 新增/编辑弹窗:名称\*、编码、所属组织\*、责任人、联系人、电话、国家/地区、行政区域、能力范围、联系地址、仓库描述、状态
|
||
|
||
**移动端适配:**
|
||
- 组织管理:≤768px 统计卡片 2 列、工具栏纵向排列、表单全宽、弹窗 95% 宽;≤900px 自动隐藏"联系电话"列
|
||
- 仓库档案:≤768px 同上,抽屉全屏,功能开关格从3列降为2列
|
||
|
||
**数据来源:** `src/api/base.js`
|
||
- `mockOrgs`:组织 mock + getOrgList / getOrgTree / addOrg / updateOrg / deleteOrg
|
||
- `mockWarehouses`:仓库 mock + getWarehouseList / addWarehouse / updateWarehouse / deleteWarehouse
|
||
- `getOrgNameOptions()` / `getWarehouseNameOptions()`:供其他模块下拉引用
|
||
|
||
---
|
||
|
||
**部门管理页(`src/views/base/DeptManage.vue`):**
|
||
- ✅ **已对接真实 API**(部门/角色 CRUD;成员关联见下)
|
||
- **三列布局(2026-05)**:左 **部门树**(搜索、展开子部门) / 中 **角色列表**(当前选中部门下的角色,hover 三点菜单:权限配置、编辑、删除;点选整行用于右侧人员上下文) / 右 **人员列表**(仅当选中某一角色时加载)。中间、右侧与左侧之间 **可拖拽分隔条**,宽度持久化。
|
||
- **角色**:新增/编辑含 **岗位类型** `role_type`(`M` 管理岗 / `P` 专家岗);列表名称旁 **M/P** Tag;有 **描述** 时悬停名称展示 Tooltip。
|
||
- **成员(选中角色后)**:**关联用户** 弹窗从全库用户挑选(`/api/user/listed`,前端单次最多取 500 条并支持本地筛选),提交 **`POST /api/dept/link_user`**;行内 **职位级别** 1–10、**KPI 比例** 0–100(%),分别调用 **`POST /api/dept/set_role_level`**、**`POST /api/dept/set_kpi_rate`**(输入防抖后保存);**移除** → **`POST /api/dept/unlink_user`**。已关联成员 **列表查询** 使用源码常量 **`API_DEPT_ROLE_MEMBER_LIST`**,须与后端真实「按 `dept_code` + `role_code` 查询成员」接口路径一致(默认值为占位)。
|
||
- **部门/角色接口**:`POST /api/dept/listed`、`/dept/add`、`/dept/modify`、`/dept/remove`;`POST /api/role/listed`、`/role/add`、`/role/modify`、`/role/remove`、`/role/set_power` 等(与权限抽屉一致)。
|
||
- **新增/编辑部门弹窗**:部门全称、简称、编码、部门性质、状态、责任人/分管领导(`/api/user/listed` 下拉联动电话)、职责描述等(同前版字段,略)。
|
||
- 移动端:小屏三列纵向堆叠;统计卡片与列表适配。
|
||
|
||
**用户管理页(`src/views/base/StaffManage.vue`):**
|
||
- ✅ **已对接真实 API**(2026-03-08,原名"员工档案"改为"用户管理")
|
||
- **开发调试(2026-05-08)**:仅在 **`npm run dev`** 构建下,列表加载成功后在浏览器控制台打印 **`/api/user/listed`** 返回与当前页用户数据,便于查看全部参数字段。
|
||
- 顶部统计卡片:员工总数 / 在职 / 总计 / 已禁用
|
||
- 工具栏:姓名/账户搜索 + 新增按钮
|
||
- 表格字段:员工姓名 / 编号 / 账户 / 性别 / 所属组织(显示名称)/ 部门(显示名称)/ 职务 / 手机号 / 入职日期 / 状态
|
||
- 点击行展开详情抽屉:基本信息(编号/账户/性别/生日/手机/邮箱)+ 任职信息(所属组织名/部门名/职务/入职日期/状态)+ 备注
|
||
- 操作列:编辑 / 删除
|
||
- 新增/编辑弹窗:
|
||
- **所属组织**:下拉选择(来自 `/api/company/listed`)
|
||
- **部门**:树形下拉(`el-tree-select`,根据所属组织联动加载 `/api/dept/listed`,含子部门层级)
|
||
- **API 字段**:`user_code`、`company_code`、`dept_code`、`username`、`password`、`code`、`mark`、`realname`、`birthday`、`gender`(0女/1男)、`phone`、`email`、`leader_level`(职务)、`join_time`、`is_valid`(0禁/1启)、`pos`
|
||
- 接口:`POST /api/user/listed`、`/user/add`、`/user/modify`、`/user/remove`
|
||
- 表格/抽屉中所属组织和部门显示名称;找不到对应数据显示"需要调整"
|
||
|
||
**银行账户页(`src/views/base/BankAccountManage.vue`):**
|
||
- ✅ **已对接真实 API**(2026-03-08,原名"企业银行账户"改为"银行账户")
|
||
- 顶部统计卡片:账户总数 / 基本账户 / 纳税登记 / 已启用
|
||
- 工具栏:账户名称/账号搜索 + 账户类型筛选 + 新增按钮
|
||
- 表格字段:账户名称 / 账户编码 / 所属组织(显示名称)/ 银行账号 / 开户银行 / 账户类型Tag / 纳税登记 / 币种 / 状态
|
||
- 点击行展开详情抽屉:基本信息 / 银行信息 / 账户属性(分三组展示)
|
||
- 操作列:编辑 / 删除
|
||
- 新增/编辑弹窗:账户名称\*、编码、所属组织\*(下拉)、开户名称\*、银行名称、银行账号\*、开户银行、账号省份/城市、银联号、账户类型、银行性质、开户日期、纳税登记(0否/1是)、货币种类(文本)、排序、状态、备注
|
||
- **API 字段**:`bank_code`、`company_code`、`name`、`code`、`account_name`、`bank_name`、`account_bank`、`account_number`、`bank_union_code`、`account_province`、`account_city`、`account_type`、`account_property`、`active_date`、`is_tax_check`、`currency_variety`、`mark`、`is_valid`、`pos`
|
||
- 接口:`POST /api/bank/listed`(空body)、`/bank/add`、`/bank/modify`、`/bank/remove`
|
||
- 移动端适配:弹窗 92vw,圆角滚动
|
||
|
||
**会计科目页(`src/views/base/AccSubjectManage.vue`):**
|
||
- ✅ **已对接真实 API**(2026-03-09)
|
||
- 顶部统计卡片:按 `elements` 字段统计资产/负债/权益/收入/费用/成本六要素数量(各自使用规范颜色)
|
||
- 工具栏:名称/编码搜索 + 会计要素筛选 + 树形/列表切换 + 新增按钮
|
||
- 树形视图:树状展开,一级科目加粗,列:科目名 / 编码 / 会计要素Tag / 余额方向(借蓝/贷红)/ 现金分类 / 数量核算 / 状态
|
||
- 列表视图:同字段但展平
|
||
- 操作列:添加子级(仅顶级显示,`_level===0`)/ 编辑 / 删除
|
||
- 新增/编辑弹窗:有上级时显示只读上级科目;编码\*、名称\*、会计要素\*、余额方向\*(debit/credit)、现金分类、赤字检查、数量核算(0/1)、排序、状态、科目说明
|
||
- **API 字段**:`ledger_code`、`parent_code`(子科目才传)、`name`、`code`、`elements`、`balance`、`cash_classify`、`deficit_check_type`、`amount_is_check`、`mark`、`is_valid`、`pos`
|
||
- 接口:`POST /api/ledger/listed`、`/ledger/add`、`/ledger/modify`、`/ledger/remove`
|
||
- `is_valid` 兼容布尔/数字/字符串三种格式
|
||
- mock 数据已屏蔽(`base.js` 中注释)
|
||
|
||
**费用项目页(`src/views/base/ExpenseItemManage.vue`):**
|
||
- ✅ **已对接真实 API**(2026-03-09)
|
||
- 顶部统计卡片:总数 / 顶级分类 / 已启用 / 已停用
|
||
- 工具栏:名称/编码搜索 + 费用业务筛选 + 新增按钮
|
||
- 表格:树形展开,列:名称 / 编码 / 费用业务Tag / 使用业务 / 状态 / 备注
|
||
- 操作列:添加子项(仅顶级显示,`_level===0`)/ 编辑 / 删除
|
||
- 新增/编辑弹窗:有上级时显示只读上级项目;名称\*、编码、费用业务(6种枚举)、使用业务(文本)、排序、状态、备注
|
||
- **API 字段**:`expense_code`、`parent_code`(子项目才传)、`name`、`code`、`expense_type`、`business`、`mark`、`is_valid`、`pos`
|
||
- 接口:`POST /api/expense/listed`、`/expense/add`、`/expense/modify`、`/expense/remove`
|
||
- `is_valid` 兼容布尔/数字/字符串三种格式
|
||
- mock 数据已屏蔽(`base.js` 中注释)
|
||
|
||
**路由(均在 `/base` 模块下):**
|
||
- `/base/dict` → MaterialDict.vue(字典管理)
|
||
- `/base/org` → OrgManage.vue(组织列表)
|
||
- `/base/dept` → DeptManage.vue(部门管理)
|
||
- `/base/staff` → StaffManage.vue(用户管理,原员工档案)
|
||
- `/base/bank-account` → BankAccountManage.vue(银行账户,原企业银行账户)
|
||
- `/base/acc-subject` → AccSubjectManage.vue(会计科目)
|
||
- `/base/expense-item` → ExpenseItemManage.vue(费用项目)
|
||
|
||
#### 📌 2026-03-08 ~ 2026-03-09 基础数据模块 API 对接更新
|
||
|
||
| # | 模块 | 文件 | 说明 |
|
||
|---|---|---|---|
|
||
| 1 | 用户管理 | `StaffManage.vue` | 页面标题改为"用户管理";"职位级别"改为"职务";所属组织改为下拉(`/company/listed`);部门改为树形下拉(`el-tree-select`,联动所属组织);表格/抽屉显示名称而非 code;接入 `/user/remove`;找不到关联数据显示"需要调整" |
|
||
| 2 | 部门管理 | `DeptManage.vue` | 重写为左右双栏布局;左侧企业树,右侧部门列表;表格树形展开;"子部门"按钮仅顶级显示;`parent_code` 仅子部门传递;接入完整 CRUD |
|
||
| 3 | 银行账户 | `BankAccountManage.vue` | 页面标题改为"银行账户";全字段对齐 API;所属组织改为下拉;货币种类改为单文本输入;纳税登记改为 0/1 下拉;接入完整 CRUD |
|
||
| 4 | 组织列表 | `OrgManage.vue` | 表头"编码"改为"企业代码" |
|
||
| 5 | 会计科目 | `AccSubjectManage.vue` | 全字段对齐 API(elements/balance/cash_classify等);子科目不显示"添加子级"按钮;`is_valid` 兼容布尔/数字/字符串;mock 数据屏蔽 |
|
||
| 6 | 费用项目 | `ExpenseItemManage.vue` | 全字段对齐 API(expense_type/business等);子项目不显示"添加子项"按钮;统计改为总数/顶级/启用/停用;`is_valid` 兼容布尔/数字/字符串;mock 数据屏蔽 |
|
||
| 7 | 全局 `is_valid` 修复 | 所有 base 模块 | API 返回布尔 `true/false` 时状态显示错误,统一改为 `isValid(v)` 函数兼容布尔/数字/字符串三种格式 |
|
||
| 8 | base.js mock 屏蔽 | `src/api/base.js` | 部门/员工/银行账户/会计科目/费用项目 5 个模块 mock 数据已用 `/* */` 注释屏蔽;仓库/组织 mock 保留(WarehouseManage 仍在使用) |
|
||
| 9 | 物料分类属性扩展 | `MaterialCategory.vue` | `AVAILABLE_DICTS` 新增:部门/用户·人员/银行账户/会计科目/费用项目 5 个基础数据字典选项 |
|
||
| 10 | 物料属性动态关联 | `MaterialList.vue` | 页面加载时从各 API 拉取基础数据存入 `dynamicDictMap`;部门/会计科目/费用项目使用 `el-tree-select` 树形下拉;编辑物料时自动过滤已删除属性(情况1)、自动清空并提示已失效的引用值(情况2);详情抽屉显示名称而非 code,失效值显示橙色"⚠ 数据已失效" |
|
||
|
||
---
|
||
|
||
#### 👥 客户管理页(`src/views/sales/CustomerManage.vue`)
|
||
|
||
**功能:**
|
||
- 顶部概览带(深色渐变):客户总数 / A级客户数 / 累计销售额 / 累计订单数
|
||
- 工具栏:名称搜索 + 分类/级别/业务员筛选 + **新增客户**按钮
|
||
- 表格(规范字段):名称 / 级别徽标(A红/B橙/C灰)/ 客户分类 / 行业 / 联系人 / 联系电话 / 负责业务员 / 结算方式 / 账期 / 累计销售额
|
||
- 操作列:详情 / 编辑 / 删除
|
||
- 分页:10/20/50 条/页
|
||
- **详情抽屉**:头像+级别+业务员、业绩数字(销售额/订单数/账期)、基础信息(编码/分类/类型/行业/组织/联系方式/地址)、交易设置(币种/结算/账期)、备注
|
||
- **新增/编辑弹窗**:客户名称\*、简称、分类\*、类型、级别\*、行业、管理组织\*、业务员、联系人、电话、邮箱、国家/地区、地址、币种、结算方式、账期、状态、备注
|
||
- 删除:二次确认弹窗
|
||
|
||
**数据来源:**
|
||
- `mockCustomers`(`src/api/sales.js`):扩展为规范字段(code/simpleName/customerClass/customerType/industry/org/currency/settleMethod/email/country/remark),支持 getCustomerList / addCustomer / updateCustomer / deleteCustomer
|
||
- 字典引用:`dictCustomerClasses / dictCustomerTypes / dictCustomerIndustries / dictCurrencies / dictSettleMethods / dictCountries`(`src/api/material.js`)
|
||
|
||
---
|
||
|
||
#### 📊 采购模块完整页面清单
|
||
|
||
| 页面 | 路由 | 文件 | 状态 |
|
||
|---|---|---|---|
|
||
| 采购订单 | `/purchase/order` | `PurchaseOrder.vue` | ✅ 完成 |
|
||
| 采购订单详情 | `/purchase/order/:orderNo` | `PurchaseOrderDetail.vue` | ✅ 完成 |
|
||
| 供应商管理 | `/purchase/supplier` | `SupplierManage.vue` | ✅ 完成 |
|
||
| 采购预警 | `/purchase/warning` | `PurchaseWarning.vue` | ✅ 完成 |
|
||
| 批次管理 | `/purchase/batch` | `BatchManage.vue` | ✅ 完成 |
|
||
| 备货配额优先级 | `/purchase/alloc-priority` | `AllocPriority.vue` | ✅ 完成(2026-03-01)|
|
||
| 采购流程看板 | `/purchase/flow` | `PurchaseFlowDashboard.vue` | ✅ 完成(2026-03-01)|
|
||
|
||
---
|
||
|
||
#### 📄 采购订单详情页(`src/views/purchase/PurchaseOrderDetail.vue`)
|
||
|
||
**功能:**
|
||
- 顶部操作栏:返回按钮 + 订单号 + 状态/票据标签 + 发布/审核/开单/提交/关闭等操作按钮
|
||
- 基本信息区:4列网格,含采购组织、交易类型、供应链单位、单据日期、采购部门、汇率、币种、付款状态等
|
||
- Tab 区域:
|
||
- **物料行**:序号/行号/物料描述/编码/名称/SKU/数量/单价/金额/税率/仓库
|
||
- **🆕 分仓分配 Tab**:
|
||
- 顶部蓝色统筹采购头(统筹组织名称+类型标签、总量/已分配/待分配件数)
|
||
- 分配明细表(优先级圆圈P1绿/P2橙/P3灰、仓库、接货组织、组织类型、票据类型、发票兼容性标签、分配数量、占比、分配金额、分配状态、预计到货)
|
||
- 底部黄色说明条(优先级排序规则说明)
|
||
- 费用行 / 费用付款 / 付款计划 / 执行 / 附件(占位)
|
||
- 右侧面板:
|
||
- 采购供应商付款金额汇总
|
||
- **🆕 分仓分配汇总卡**:每仓库一行,展示名称+状态标签+彩色进度条+占比
|
||
- 存量信息柱状图 / 近期价格走势折线图
|
||
|
||
**导航方式:**
|
||
- 采购订单列表中点击**订单号**(蓝色链接)跳转
|
||
- 采购订单列表中点击**整行**也跳转(同时高亮该行)
|
||
- 点击**"详情"操作按钮**跳转
|
||
- 路由设置 `meta.hidden: true`,**不会在 Tab 栏生成持久标签**
|
||
|
||
**新增数据(`src/api/purchase.js` → `getPurchaseOrderDetail`):**
|
||
- `allocSummary`:统筹采购头信息(coordinator/coordinatorOrgType/totalQty/allocatedQty/pendingQty)
|
||
- `warehouseAllocation`:3仓库分配明细数组(含 priority/compatibleLevel/compatibleTag/allocQty/allocRatio/status 等)
|
||
|
||
**数据来源:** `src/api/purchase.js` → `getPurchaseOrderDetail(orderNo)`
|
||
|
||
---
|
||
|
||
---
|
||
|
||
#### 🔢 备货配额优先级页(`src/views/purchase/AllocPriority.vue`)
|
||
|
||
> 新增于 2026-03-01,对应思维导图"备货配额优先级"
|
||
|
||
**功能:**
|
||
- 顶部说明栏:介绍配额规则作用,标注"仅查看"状态
|
||
- **接货组织视角 Tab**(默认):
|
||
- 左侧4张接货组织卡片(T1-13%一般纳税人 / T2-9%一般纳税人 / T3-小规模3% / TX-免税组织),点击切换
|
||
- 右侧优先级列表:每条含优先级数字圆圈(1绿/2浅绿/3橙/4红)、供货组织标识、兼容性标签、详细税差说明、适配度百分比条
|
||
- 底部黄色提示:税差大时建议调整接货组织
|
||
- **全矩阵视图 Tab**:
|
||
- 接货组织(行)× 供货组织(列)完整优先级数字矩阵
|
||
- 单元格颜色:绿=完全匹配/浅绿=较优/橙=部分兼容/红=低优先
|
||
- 底部图例
|
||
|
||
**数据:** 全部硬编码在组件内(`RECEIVE_ORGS` / `SUPPLY_ORGS` / `PRIORITY_RULES` / `FULL_MATRIX`),无需 API
|
||
|
||
---
|
||
|
||
#### 📋 采购流程看板(`src/views/purchase/PurchaseFlowDashboard.vue`)
|
||
|
||
> 新增于 2026-03-01,对应思维导图"系统流程图"(图3)
|
||
|
||
**功能:**
|
||
- **顶部8步流程条**:采购申请→审批中→已批准→供应商确认→在途运输→到货待分配→分配入库→财务结算
|
||
- 每个阶段卡片显示当前数量角标
|
||
- 点击卡片过滤下方订单,再次点击取消
|
||
- 颜色区分:申请/审批=红/橙,在途=蓝,入库/结算=绿
|
||
- **6个统计卡片**:申请/审批 / 已批/确认 / 在途/到货 / 入库/结算 / 加急订单 / 已逾期
|
||
- **筛选栏**:阶段标签(可关闭)+ 文字搜索(订单号/商品/供应商)+ "仅看加急"复选框
|
||
- **订单卡片列表**:
|
||
- 左侧4px彩色竖条(对应当前阶段颜色)
|
||
- 订单号/加急标签/逾期标签/日期
|
||
- 商品·供应商·采购组织
|
||
- 数量/金额/仓库/票据类型
|
||
- 右侧:当前阶段图标标签 + 8个阶段进度点(已过绿/当前彩/未来灰)+ 到期日
|
||
- 加急订单红色边框;逾期订单半透明
|
||
|
||
**新增数据(`src/api/purchase.js`):**
|
||
- `FLOW_STAGES`:8阶段定义(key/label/color/bg/icon),导出
|
||
- `ORDER_STAGE_MAP`:订单号→当前阶段的 mock 映射
|
||
- `getFlowDashboard()`:返回 orders(含flowStage/isUrgent/overdue)+ stageCounts(各阶段计数)
|
||
|
||
---
|
||
|
||
#### ⚖️ 备货配额优先级页(`src/views/purchase/AllocPriority.vue`)
|
||
|
||
**新增于 2026-03-01**
|
||
|
||
**功能:**
|
||
- 顶部说明栏:介绍配额优先级规则用途,标注"仅查看"状态
|
||
- 两个 Tab 视图切换:
|
||
- **接货组织视角 Tab**(默认):
|
||
- 左侧 4 张接货组织类型卡片(T1/T2/T3/TX),点击切换
|
||
- 右侧展示该组织的完整优先级列表,每条包含:优先级圆圈(1绿→4红)、供货组织标识+名称+兼容标签、详细说明(税差计算)、适配度百分比进度条
|
||
- 底部黄色提示:税差大时建议提前调整接货组织
|
||
- **全矩阵视图 Tab**:接货组织(行)× 供货组织(列)完整优先级矩阵,格子颜色绿/浅绿/橙/红对应匹配程度,底部图例说明
|
||
|
||
**核心数据(组件内定义):**
|
||
- `RECEIVE_ORGS`:T1/T2/T3/TX 四种接货组织(含颜色/税率/说明)
|
||
- `SUPPLY_ORGS`:S13/S9/S6/S3/SJD 五种供货票据类型
|
||
- `PRIORITY_RULES`:每种接货组织的 5 条优先级规则(含适配度分数/理由/兼容标签)
|
||
- `FULL_MATRIX`:接货×供货的完整优先级数字矩阵
|
||
|
||
**路由:** `/purchase/alloc-priority`,菜单出现在批次管理之后
|
||
|
||
---
|
||
|
||
#### 📋 采购流程看板(`src/views/purchase/PurchaseFlowDashboard.vue`)
|
||
|
||
**新增于 2026-03-01**
|
||
|
||
**功能:**
|
||
- **顶部 8 步流程条**:采购申请→审批中→已批准→供应商确认→在途运输→到货待分配→分配入库→财务结算,每个阶段有专属图标+颜色+数量角标,点击任意阶段过滤下方订单,再次点击取消筛选
|
||
- **6 张统计卡片**:申请/审批、已批/确认、在途/到货、入库/结算(各阶段合计)+ 加急订单 + 已逾期
|
||
- **筛选栏**:当前筛选阶段标签(可关闭)+ 文字搜索(订单号/商品/供应商)+ 仅看加急开关
|
||
- **订单卡片列表**:
|
||
- 左侧竖色条对应当前阶段颜色
|
||
- 主体:订单号+加急/逾期标签、商品+供应商+组织、数量+金额+仓库+票据类型
|
||
- 右侧:当前阶段标签(彩色圆角框)+ 8个阶段进度点(已过绿/当前彩/未来灰)+ 到期日
|
||
- 加急订单红色边框,逾期订单半透明
|
||
|
||
**新增数据(`src/api/purchase.js`):**
|
||
- `FLOW_STAGES`:8阶段定义(label/color/bg/icon),已导出
|
||
- `ORDER_STAGE_MAP`:订单号→当前阶段映射(mock)
|
||
- `getFlowDashboard()`:返回所有订单(附 flowStage/isUrgent/overdue)+ stageCounts 统计
|
||
|
||
**路由:** `/purchase/flow`,菜单出现在备货配额优先级之后
|
||
|
||
---
|
||
|
||
#### 🗂️ 物料管理模块
|
||
|
||
**页面清单:**
|
||
|
||
| 页面 | 路由 | 文件 | 状态 |
|
||
|---|---|---|---|
|
||
| 物料列表 | `/material/list` | `MaterialList.vue` | ✅ 完成 |
|
||
| 分类管理 | `/material/category` | `MaterialCategory.vue` | ✅ 完成 |
|
||
| 基础字典 | `/material/dict` | `MaterialDict.vue` | ✅ 完成 |
|
||
|
||
**物料列表页(`src/views/material/MaterialList.vue`)功能:**
|
||
- 顶部筛选栏:关键字搜索、启用状态下拉、查询/重置按钮
|
||
- **动态快捷筛选条**:根据当前选中分类中标记了「快捷筛选」的属性动态生成过滤控件(最多5个),控件类型按属性数据类型自适应:字典/枚举/布尔→下拉选择、文本→输入框、数字→范围输入、日期→日期范围选择器;含搜索/重置按钮
|
||
- 左侧分类树:多级树形结构、关键字过滤、点击分类自动过滤右侧列表;底部固定「未分类」节点
|
||
- 右侧操作栏:新增、批量归类、高级筛选(右侧抽屉)、**导出下拉**(导出选中 / 导出当前列表)、分类管理
|
||
- 右侧表格列:
|
||
- 勾选列 + **物料编码**(独立列)+ **物料名称**(独立列,fixed left)
|
||
- **分类动态属性列**:根据左侧选中分类自动生成对应属性列(包含继承属性),未选分类时不显示额外列
|
||
- **自定义属性列**:有自定义属性显示「查看」按钮(点击打开右侧抽屉),无则显示「无」
|
||
- 操作列:修改 / 删除(fixed right)
|
||
- 点击行弹出**详情抽屉**(右侧):
|
||
- **基本信息**:物料编码、物料名称、归属分类(显示分类路径名称)
|
||
- **分类属性**:该分类所有属性(含继承)及对应填写值,有值显示值,未填显示「--」
|
||
- **自定义属性**:自定义属性列表,无则显示「暂无」
|
||
- ✅ **新增物料**(简化流程,600px宽弹窗):
|
||
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)` — 新增,存储 `name`、`code`、`categoryId`、`categoryAttrs`、`hasCustomAttributes`、`customAttributes`
|
||
- `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 文件(与「新增根分类」并排)
|
||
- 分类 API:`addCategory` / `updateCategory` / `deleteCategory` / `deleteCategoryAll` / `getCategoryTreeInfo`
|
||
- 移动端适配:弹窗 `calc(100vw - 24px)` 宽,`88vh` 最大高度,圆角,内容可滚动
|
||
|
||
**字典管理页(`src/views/material/MaterialDict.vue`)功能(已对接真实接口):**
|
||
- 页面/菜单标题改为"**字典管理**"
|
||
- **左侧字典列表**:调 `POST /api/dict/listed`(body `{ dict_code: '' }`)获取字典列表,平铺展示;鼠标悬停显示"+"按钮快速新增子值
|
||
- **右侧条目表格**:展示选中字典的 `children[]`,支持树形展开(`row-key="dict_code"`);第一层条目显示"添加 / 编辑",子层只显示"编辑"
|
||
- ✅ **新增字典**:调 `POST /api/dict/add`(带 `classify_code: 'RY9A77ABKR'`、`value`、`label`、`desc`)
|
||
- ✅ **新增字典值**:调 `POST /api/dict/add`(带 `parent_code` = 父字典 `dict_code`、`value`、`label`)
|
||
- ✅ **编辑字典/值**:弹窗表单,调 `POST /api/dict/modify`(带 `dict_code`、`classify_code`、`value`、`label`、`desc`)
|
||
- **移动端适配**:`isMobile`(`window.innerWidth ≤ 600`)控制左右面板切换;移动端点击字典条目进入详情,顶部左箭头返回列表;弹窗宽度 `92vw`,`class="dict-dialog"` 圆角滚动样式
|
||
|
||
---
|
||
|
||
#### 🏷️ 登录页便签(NoticeBoard)更新记录
|
||
|
||
**当前方案(便签条式):**
|
||
- 两条横向便签图片(`/icons/add.png` 黄色 / `/icons/update.png` 蓝色),图片分辨率 3345×1409
|
||
- 左侧深色标签区域竖排文字("新增功能" / "更新内容"),点击整条展开/折叠(width 动画)
|
||
- 折叠时:只露出左侧约 32px 深色标签部分,竖排文字仍可见
|
||
- 展开时:完整便签条滑出,显示 2 条更新内容(圆点 + 文字 + 日期)
|
||
- `position: absolute`(不用 fixed),避免父级 `overflow: hidden` 裁剪
|
||
- 移动端隐藏(`@media max-width: 640px`)
|
||
|
||
**文件:** `src/views/login/components/NoticeBoard.vue`
|
||
|
||
---
|
||
|
||
---
|
||
|
||
#### 📊 库存总览页(`src/views/inventory/InventoryOverview.vue`)
|
||
|
||
**功能(两个 Tab):**
|
||
|
||
**总览 Tab(原有):**
|
||
- 顶部4张统计卡片:在管仓库数 / 在库总量 / 库存总金额 / 库存预警数
|
||
- 各仓库库存占比饼图(ECharts)
|
||
- 各品类库存占比饼图(ECharts)
|
||
- 仓库 × 品类 明细矩阵表(颜色热力图,值越大越深蓝)
|
||
- 近30天各仓库库存趋势折线图(ECharts,面积渐变)
|
||
|
||
**🆕 商品库存分布 Tab(2026-03-01 新增):**
|
||
- 品类筛选下拉 + 图例说明(充足绿/低于安全库存橙/已断货红/在途蓝)
|
||
- **分组柱状图**:横轴=商品名称,每组5根柱=5个仓库,悬停显示具体数量
|
||
- **商品 × 仓库 明细矩阵表**:
|
||
- 行:商品(含品类列)
|
||
- 列:5个仓库 + 在库合计 + 在途合计
|
||
- 单元格颜色底色(充足/低/断货)+ 3px彩色底部条
|
||
- 在途数量显示为蓝色 `+N途`
|
||
- 点击品类筛选后同步刷新柱状图和矩阵
|
||
|
||
**新增数据(`src/api/inventory.js`):**
|
||
- `mockProductMatrix`:8商品 × 5仓库,每条含 qty/safeQty/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 function`(`properties.value.map` 改正) |
|
||
|
||
##### 路由更新(`router/index.js`)
|
||
|
||
```
|
||
/material/scene → SceneManage.vue(场景管理)
|
||
/material/feature → FeatureManage.vue(特征管理,占位)
|
||
/material/template → TemplateManage.vue(模板管理,占位)
|
||
/base/bill → BillManage.vue(票据管理)
|
||
```
|
||
|
||
#### 📌 2026-03-13 功能更新
|
||
|
||
| # | 功能 | 文件 | 说明 |
|
||
|---|---|---|---|
|
||
| 1 | 分类管理改为左右双栏布局 | `MaterialCategory.vue` | 桌面端左侧分类树 + 右侧内联表单面板,移动端保留弹窗;右侧空状态占位图,面板顶部带关闭按钮 |
|
||
| 2 | 分类拖拽换父级 | `MaterialCategory.vue` | 拖动分类节点到其他分类可改变父级关系,自动调用 `category/modify`(只传 `category_code` + `parent_code`) |
|
||
| 3 | 删除分类简化 | `MaterialCategory.vue` | 去掉"归入未分类/转移/取消"三选一弹窗,改为简洁确认框,子分类一并删除 |
|
||
| 4 | 移除"未分类"节点 | `MaterialCategory.vue` + `MaterialList.vue` | 分类树和物料列表侧边栏均不再显示"未分类"固定节点,相关样式一并清除 |
|
||
| 5 | 部门管理责任人/分管领导改下拉 | `DeptManage.vue` | 从文本输入改为关联 `/api/user/listed` 的人员下拉,选择后自动填充对应电话,提交时同步写入手机号 |
|
||
| 6 | 字典排序改为↑↓按钮 | `MaterialDict.vue` | 废弃 Sortable.js 拖拽方案(与 el-table 树形展开存在根本兼容问题),顶层字典行和子字典行统一使用↑↓移动按钮;到顶/到底时对应按钮自动禁用 |
|
||
|
||
---
|
||
|
||
#### 📌 2026-03-16 功能更新 — 全局序号(pos)字段补全
|
||
|
||
##### 新增 pos 字段的模块
|
||
|
||
| # | 模块 | 文件 | 改动内容 |
|
||
|---|---|---|---|
|
||
| 1 | **费用管理** | `ExpenseItemManage.vue` | 表单标签"排序"→"序号";表格新增"序号"列(灰色小字) |
|
||
| 2 | **会计管理** | `AccSubjectManage.vue` | 表单标签"排序"→"序号";表格新增"序号"列 |
|
||
| 3 | **银行账户** | `BankAccountManage.vue` | 表单标签"排序"→"序号";表格新增"序号"列 |
|
||
| 4 | **场景管理** | `SceneManage.vue` | `form` 加 `pos` 字段;弹窗加"序号"输入框(`el-input-number`);新增/修改接口带 `pos`;场景卡片名称旁显示序号 |
|
||
| 5 | **分类管理** | `MaterialCategory.vue` | `form` 加 `pos` 字段;弹窗加"序号"输入框;新增/修改接口带 `pos`;`mapApiNode` 保留 `pos`;树节点显示序号(灰色小字) |
|
||
| 6 | **物料管理** | `MaterialList.vue` | `addForm`/`editForm` 加 `pos` 字段;新增/编辑弹窗加"序号"输入框;物料列表表格新增"序号"列;`handleEdit` 赋值 `pos` |
|
||
|
||
##### 统一规范
|
||
|
||
- **表单控件**:`el-input-number`,最小值 0,整数,默认值 0
|
||
- **列表显示**:`font-size:12px; color:#c0c4cc`(灰色小字,宽度 65px,居中)
|
||
- **接口传值**:统一转为字符串 `String(form.pos)` 发送;编辑回填时用 `Number(row.pos) || 0`
|
||
- **标签统一**:全部使用"序号"(不再使用"排序")
|
||
|
||
---
|
||
|
||
#### 📌 2026-03-17 功能更新 — 分类管理优化 & Bug 修复
|
||
|
||
##### MaterialCategory.vue
|
||
|
||
| # | 改动 | 说明 |
|
||
|---|---|---|
|
||
| 1 | **分类 code 层级路径显示** | 树节点名称后的括号内容改为按层级拼接 `code` 字段(如 `1`、`6-3`),而非 `category_code`。`mapApiNode` 新增 `parentPath` 参数,递归构建 `codePath`,子节点路径为 `父code-自身code` |
|
||
| 2 | **修复 map 传 index 的 bug** | 顶层调用从 `list.map(mapApiNode)` 改为 `list.map(n => mapApiNode(n))`,避免数组 index 被误传为 `parentPath`,导致显示 `1-X4IOMAFPDU` 这类错误 |
|
||
| 3 | **修复 category_code 传错** | `mapApiNode` 的 `id` 字段还原使用 `n.category_code`(用于 API 请求),`codePath` 单独使用 `n.code` 字段。之前错误将 `id` 改为 `n.code`,导致编辑时 `category_code` 发送的是 `"6"` 这种序号值而非真实 code |
|
||
| 4 | **移除场景排他性过滤** | 删除 `availableSceneOptions` 中"已被其他分类占用的场景置灰"逻辑,修复同一场景无法被多个分类选用的 bug。正确逻辑:一个场景可以有多个分类,一个分类只属于一个场景 |
|
||
| 5 | **树节点显示所属场景名** | 根分类名称后新增场景小标签,颜色与场景标识色一致(带透明背景+边框),子分类不显示(继承父分类,保持简洁) |
|
||
| 6 | **console.log 改为打印原始数据** | `loadCategoryTree` 中的打印移到 `map` 处理之前,打印服务器原始 `res.data`,方便调试对比 |
|
||
|
||
##### SceneManage.vue
|
||
|
||
| # | 改动 | 说明 |
|
||
|---|---|---|
|
||
| 1 | **console.log 改为打印原始数据** | `loadScenes` 中的打印移到 `map` 处理之前,打印服务器原始 `res.data` |
|
||
|
||
##### 待对接(等服务器)
|
||
|
||
- **价格本保存接口**:采用整体 JSON 方案,服务器需提供保存/读取接口,`content` 字段类型 `longtext`,前端负责 Univer 工作簿序列化/反序列化
|
||
|
||
---
|
||
|
||
#### 📌 2026-03-20 ~ 2026-03-21 功能更新
|
||
|
||
##### 全局清理:移除所有弹窗中的"序号"字段
|
||
|
||
| # | 文件 | 说明 |
|
||
|---|---|---|
|
||
| 1 | `BankAccountManage.vue` | 新增/编辑弹窗去掉序号输入框 |
|
||
| 2 | `MaterialList.vue` | 新增/编辑弹窗去掉序号输入框 |
|
||
| 3 | `MaterialCategory.vue` | 新增/编辑弹窗去掉序号输入框 |
|
||
| 4 | `PriceBook.vue` | 保存弹窗去掉序号输入框 |
|
||
| 5 | `OrgManage.vue` | 新增/编辑弹窗去掉序号输入框 |
|
||
| 6 | `DeptManage.vue` | 新增/编辑弹窗去掉序号输入框 |
|
||
| 7 | `WarehouseManage.vue` | 新增/编辑弹窗去掉序号输入框 |
|
||
| 8 | `ExpenseItemManage.vue` | 新增/编辑弹窗去掉序号输入框 |
|
||
| 9 | `AccSubjectManage.vue` | 新增/编辑弹窗去掉序号输入框 |
|
||
| 10 | `BillManage.vue` | 新增/编辑弹窗去掉序号输入框 |
|
||
|
||
> 统一规范:序号改由拖拽排序自动维护,新建默认 `pos: 0`
|
||
|
||
---
|
||
|
||
##### 组织管理(`OrgManage.vue`)
|
||
|
||
| # | 改动 | 说明 |
|
||
|---|---|---|
|
||
| 1 | **进出项显示逻辑** | ≤6条:直接展开显示,蓝色(进)/橙色(出)颜色区分;>6条:折叠+Tooltip悬停 |
|
||
| 2 | **移除"进项"/"出项"文字标签** | 依靠颜色区分,每条后面追加`(进)`或`(出)`文字后缀增强可读性 |
|
||
| 3 | **新增"开票额度(月)"字段** | 弹窗中替换原序号位置,数字输入,绑定`bill_invoice_limit`字段 |
|
||
| 4 | **列表显示月额度标签** | 树节点显示橘色`¥X/月`标签(`bill_invoice_limit != null`时显示) |
|
||
| 5 | **API字段对齐** | 新增/修改接口均携带`bill_invoice_limit`参数 |
|
||
|
||
---
|
||
|
||
##### 部门管理(`DeptManage.vue`)
|
||
|
||
| # | 改动 | 说明 |
|
||
|---|---|---|
|
||
| 1 | **表单布局调整** | 责任人/责任人电话固定一行,分管领导/领导电话固定一行,不受条件渲染影响 |
|
||
|
||
---
|
||
|
||
##### 物料管理菜单重命名 & 路由精简
|
||
|
||
| # | 改动 | 说明 |
|
||
|---|---|---|
|
||
| 1 | **"分类管理"→"物料分类"** | 路由title、页面标题、提示文字全部同步 |
|
||
| 2 | **"场景管理"→"销售场景"** | 路由title、页面标题、提示文字全部同步 |
|
||
| 3 | **删除"模板管理"路由** | `/material/template` 及对应菜单条目移除 |
|
||
|
||
---
|
||
|
||
##### 物料分类(`MaterialCategory.vue`)属性选择改版
|
||
|
||
| # | 改动 | 说明 |
|
||
|---|---|---|
|
||
| 1 | **"自定义属性"→"从特征选择"** | 添加属性弹窗去掉自定义属性Tab,新增"从特征选择"Tab,调用`/api/field/listed`加载 |
|
||
| 2 | **特征类型角标** | 列表中每条特征显示类型徽标(带颜色/背景),便于区分 |
|
||
| 3 | **多选支持** | 字典属性:`el-tree` `show-checkbox`;特征属性:行内`el-checkbox`,支持同时选多条批量添加 |
|
||
| 4 | **三段式弹窗布局** | 顶部固定"分类名称",中部可滚动"属性区",底部预留等高空白区 |
|
||
|
||
---
|
||
|
||
##### 销售场景(`SceneManage.vue`)属性选择改版
|
||
|
||
| # | 改动 | 说明 |
|
||
|---|---|---|
|
||
| 1 | **"自定义属性"→"从特征选择"** | 同物料分类,新增"从特征选择"Tab,带类型徽标显示 |
|
||
| 2 | **多选支持** | 字典和特征均支持多选批量添加 |
|
||
| 3 | **移除场景特定提示文字** | 去掉"暂无分类关联此场景,请在「物料分类」中设置",改为简洁通用提示 |
|
||
|
||
---
|
||
|
||
##### 特征管理(`FeatureManage.vue`)重大重构
|
||
|
||
**新增特征类型:**
|
||
- `image`(图片)、`file`(文件)、`link`(链接)
|
||
- `multiselect`(下拉多选,区别于`select`下拉单选)
|
||
- 修复`Select`被误解析为HTML元素的问题,改用`Filter`图标
|
||
|
||
**特征分类系统(全新):**
|
||
|
||
| # | 改动 | 说明 |
|
||
|---|---|---|
|
||
| 1 | **左侧分类树** | 将类型筛选栏替换为`el-tree`层级分类树 |
|
||
| 2 | **分类CRUD** | 支持新建根分类/子分类、编辑、删除,调用`/api/field/classify/*`接口 |
|
||
| 3 | **右侧按分类加载** | 点击分类后右侧加载该分类下的特征,`/api/field/listed`传`classify_code` |
|
||
| 4 | **新建特征需先选分类** | "新增特征"按钮在未选分类时禁用 |
|
||
| 5 | **拖拽排序保留** | 特征列表拖拽排序在分类上下文内生效,调用`/api/field/batch_pos` |
|
||
| 6 | **服务器嵌套树修复** | 服务器返回已嵌套的树结构,修复`buildClassifyTree`逻辑(原来用`children:[]`覆盖服务器数据),改为递归保留服务器`children` |
|
||
| 7 | **侧栏UI优化** | 侧栏宽260px;移除_count数量角标;添加橘色警告提示:"点击分类查看特征;行内+可添加子分类";"暂无分类"文字改橘色警告色、去掉图标 |
|
||
| 8 | **新接口集成** | `/api/field/classify/add`(根/子)、`/api/field/classify/modify`、`/api/field/classify/remove`、`/api/field/classify/listed` |
|
||
|
||
---
|
||
|
||
##### 配置表(`PriceBook.vue`)
|
||
|
||
| # | 改动 | 说明 |
|
||
|---|---|---|
|
||
| 1 | **加载遮罩** | Univer编辑器初始化期间显示全屏遮罩"编辑器加载中,请稍候...",避免用户以为页面卡住 |
|
||
| 2 | **"保存到服务器"→"保存"** | 按钮文案简化 |
|
||
| 3 | **左侧列表拖拽排序** | 引入Sortable.js,整行可拖,拖后调用`/api/excel/batch_pos`保存顺序 |
|
||
|
||
---
|
||
|
||
##### 当前路由结构(更新)
|
||
|
||
```
|
||
/material (物料管理)
|
||
├── /material/list 物料列表
|
||
├── /material/category 物料分类(原"分类管理")
|
||
├── /material/scene 销售场景(原"场景管理")
|
||
└── /material/feature 特征管理(已实现完整分类+特征CRUD)
|
||
// 模板管理已移除
|
||
```
|
||
|
||
---
|
||
|
||
#### 📌 2026-03-22 ~ 2026-03-23 页签管理 & 物料分类重大重构
|
||
|
||
##### TabManage.vue(页签管理)
|
||
|
||
| # | 改动 | 说明 |
|
||
|---|---|---|
|
||
| 1 | **字段列表→5列卡片网格** | 字段展示方式由纵向列表改为 5 列卡片网格,卡片带 `box-shadow`,字段类型徽标改为蓝色 |
|
||
| 2 | **字段编辑改为右侧抽屉** | 从内联展开改为右侧滑出抽屉,点击字段卡片打开,关闭按钮为红色"关闭"文字 |
|
||
| 3 | **添加字段即开抽屉** | 点击"添加字段"后新字段立即在抽屉中打开编辑 |
|
||
| 4 | **整个卡片可拖拽** | 拖动范围从小拖手改为整个字段卡片 |
|
||
| 5 | **描述`?`图标下移** | 从卡片头部移至卡片底部,解决与操作按钮的遮挡问题 |
|
||
| 6 | **主保存按钮禁用逻辑** | 字段抽屉打开时,主对话框"确认保存"按钮灰色不可点,并显示提示文字 |
|
||
|
||
##### MaterialCategory.vue(物料分类)
|
||
|
||
###### 页签系统重构
|
||
|
||
| # | 改动 | 说明 |
|
||
|---|---|---|
|
||
| 1 | **移除"添加规则"** | 删除字段层面的规则管理,改为页签层面的可见性规则 |
|
||
| 2 | **"添加选项卡"拆为两个按钮** | 新增"导入页签"(引用 TabManage 模板)和"添加页签"(自定义) |
|
||
| 3 | **导入页签** | 从 TabManage 模板导入,存储引用 `refId`、覆盖值 `overrides`、快照 `snapshot`;字段以卡片输入模式显示 |
|
||
| 4 | **默认值提示 + 还原** | 导入页签的每个字段下方始终显示模板默认值;若已覆盖则显示"还原"按钮,点击弹确认框后回滚 |
|
||
| 5 | **导入页签空组也显示** | 即便分组内无字段也显示分组,显示"该分组暂无字段" |
|
||
| 6 | **页签药丸 UI** | 导入页签:蓝色↓图标,隐藏"复制到"和"改名",仅留"删除";自建页签:隐藏"复制到",保留"改名"和"删除" |
|
||
| 7 | **导入弹窗优化** | 已导入模板排底部(加分隔线),添加模板数量汇总,超长列表有滚动条 |
|
||
|
||
###### 自建页签
|
||
|
||
| # | 改动 | 说明 |
|
||
|---|---|---|
|
||
| 1 | **字段管理改为右侧抽屉** | 与 TabManage 一致,抽屉内容完整(类型/描述/默认值/字典等) |
|
||
| 2 | **实际使用模式** | 自建字段以卡片输入形式展示(与导入页签一致)而非定义模式 |
|
||
| 3 | **统一字段卡片 UI** | 所有字段(自建/导入/字典/特征)使用同一套 `imported-field-item` 卡片样式 |
|
||
| 4 | **移除类型徽标和分区标题** | 删除"文本框"/"字典"/"特征"等徽标,删除"字典/特征属性"分区标题 |
|
||
| 5 | **移除"筛选"复选框** | 字段卡片不再显示快捷筛选开关 |
|
||
| 6 | **删除按钮默认隐藏** | 自建/旧字段卡片的"删除"按钮默认隐藏,悬停时才出现 |
|
||
| 7 | **主确认按钮禁用逻辑** | 同 TabManage,抽屉打开时主"确认"按钮灰色并显示提示 |
|
||
|
||
###### 多选字段区分
|
||
|
||
| # | 改动 | 说明 |
|
||
|---|---|---|
|
||
| 1 | **`multiselect` 保持下拉多选** | 使用 `el-select multiple` |
|
||
| 2 | **`checkbox` 改为内联多选框** | 使用 `el-checkbox-group`,选项横向排列,类似图3效果 |
|
||
|
||
###### 页签可见性规则(新功能)
|
||
|
||
| # | 改动 | 说明 |
|
||
|---|---|---|
|
||
| 1 | **新增"规则"入口按钮** | 页签导航栏添加"规则"按钮,打开规则配置弹窗 |
|
||
| 2 | **规则弹窗** | 左侧规则列表 + 右侧编辑区;规则定义:类型(显示/隐藏)、目标页签(多选)、逻辑(AND/OR)、条件(字段+运算符+值) |
|
||
| 3 | **帮助说明** | 弹窗标题旁`?`按钮显示规则逻辑说明 Popover |
|
||
| 4 | **当前页签提示** | 规则列表上方显示当前打开的页签名;添加规则时自动预选当前非继承页签为目标 |
|
||
| 5 | **数据持久化** | `form.tabRules` 随分类数据一并通过 `/api/category/modify` 保存 |
|
||
|
||
---
|
||
|
||
#### 📌 2026-03-24 字段边界感 & 自建页签分组
|
||
|
||
| # | 改动 | 文件 | 说明 |
|
||
|---|---|---|---|
|
||
| 1 | **多选框/下拉加边框容器** | `MaterialCategory.vue` | `el-checkbox-group` 添加 `border: 1px solid #dcdfe6 / border-radius / padding`,与 `el-input`、`el-select` 统一边界感 |
|
||
| 2 | **自建页签支持分组** | `MaterialCategory.vue` | 数据结构从 `fields[]` 升级为 `groups[{ id, name, fields[] }]`;新建页签自动创建"默认分组" |
|
||
| 3 | **分组管理 UI** | `MaterialCategory.vue` | 每个分组头部有"改名"/"+ 字段"/"删除组"三个操作;空分组显示"该分组暂无字段" |
|
||
| 4 | **底部按钮更新** | `MaterialCategory.vue` | 新增"添加分组"按钮;"添加字段"默认归入最后一组;保留"从字典/特征添加" |
|
||
| 5 | **旧数据兼容迁移** | `MaterialCategory.vue` | `openForm` 时自动将旧 `fields[]` 格式迁移到 `groups[0]` 内,无需手动处理 |
|
||
| 6 | **字段计数更新** | `MaterialCategory.vue` | `ownTabFieldCount` 改为累计所有分组内字段数;`allFieldGroups` 改为遍历 groups 收集字段 |
|
||
|
||
---
|
||
|
||
#### 📌 2026-03-25 ~ 2026-03-26 页签管理重大功能 & 分类继承体系升级
|
||
|
||
##### TabManage.vue
|
||
|
||
| # | 改动 | 说明 |
|
||
|---|---|---|
|
||
| 1 | **新增「计算字段」类型** | 字段类型下拉新增 `formula`(计算字段),选择后出现公式编辑器区域 |
|
||
| 2 | **公式编辑器 UI** | 「引用字段」区:自动列出同页签内所有 `number` 类型字段,点击按钮插入 `{fieldId}` 引用;「公式」输入框:支持 `+ - * / ( )` 四则运算;「预览」区:公式翻译为 `[字段名]` 可读形式,点「用默认值测试」按钮用字段实际 `defaultValue` 计算结果 |
|
||
| 3 | **公式插入光标定位** | 点击引用字段按钮时,将 `{fieldId}` 插入公式输入框当前光标位置(使用 `selectionStart/End` 精确定位) |
|
||
| 4 | **`hasNoDefault` 不校验计算字段** | `type === 'formula'` 时跳过默认值校验,计算字段无需默认值 |
|
||
| 5 | **字段卡片显示** | 计算字段类型标签显示橙色斜体「计算字段」,卡片底部显示 `ƒ(x)` 徽标 |
|
||
| 6 | **修复 `form.groups` → `form.value.groups`** | `formulaRefFields`、`formulaDisplayExpr`、`testFormulaPreview` 三处函数中 ref 未用 `.value` 访问导致崩溃,已修复 |
|
||
| 7 | **测试预览用真实默认值** | `testFormulaPreview` 改为用字段实际 `defaultValue` 计算,而非之前的假值 1,2,3... |
|
||
| 8 | **保存前变更确认** | 编辑已有模板保存时,检测字段类型变更和字段删除;若有则弹确认框列出具体变更(「X 类型:文本 → 数字」/ 「X 已删除」),用户确认后才保存,取消则中止 |
|
||
| 9 | **`saveTab` 改为 `async`** | 保存函数改为 async 以支持 `await ElMessageBox.confirm` |
|
||
| 10 | **`@change` 副作用包 `nextTick`** | 类型下拉 `@change` 中修改 `defaultValue`/`formula` 的副作用延迟到 `nextTick`,消除 El Select 内部 `onMounted` 生命周期警告 |
|
||
| 11 | **`FIELD_TYPE_MAP` 更新** | 新增 `formula: '计算字段'` 映射 |
|
||
|
||
##### MaterialCategory.vue — 模板变更检测全面升级
|
||
|
||
| # | 改动 | 说明 |
|
||
|---|---|---|
|
||
| 1 | **所有变更字段都打 ⚑** | 之前只有有 override 的字段才打 ⚑;现在无论有无 override,只要模板默认值变了就打 ⚑,加入 `tplChangedFields`,统一弹 alert 通知用户 |
|
||
| 2 | **`snapshotTypes` 类型快照** | 导入模板时同步记录每个字段的类型(`tab.snapshotTypes[fieldId] = type`);`checkAndSyncTemplateChanges` 检测类型变更 |
|
||
| 3 | **类型变更处理** | 检测到字段类型变更时:清除旧 override(值与新类型不兼容),弹右上角 `ElNotification` 列出「页签名-字段名:旧类型 → 新类型」,更新 snapshotTypes |
|
||
| 4 | **默认值变更分两种处理** | 有 override:`changedFields.add` + `overrideConflicts++`,snapshot 不更新(持续提示直到用户处理并保存);无 override:自动更新 snapshot,`autoUpdated++` |
|
||
| 5 | **alert 合并显示** | 统一弹一个 `ElMessageBox.alert`,区分「有自定义值字段已标注 ⚑ 请处理」和「字段默认值已自动同步无需操作」两种描述 |
|
||
| 6 | **silentSaveSnapshot 只保存无冲突字段** | 有 override 冲突的字段 snapshot 不动,只保存自动更新的 snapshot;下次打开仍能检测到冲突,持续提示 |
|
||
| 7 | **「查看更新」按钮(无 override)** | `hasTplChanged && !hasOverride` 时显示「查看更新」按钮;点击弹 `ElMessageBox.alert` 告知「默认值已更新为 X,已自动同步」,确认后清 ⚑ |
|
||
| 8 | **「还原至新默认」两选项保留** | `hasTplChanged && hasOverride` 时显示「还原至新默认」;弹确认框两个选择:「同步当前值」(清 override)/ 「仅更新默认提示」(只清 ⚑ 保留 override) |
|
||
| 9 | **快照在模板同步后才存** | `openForm` 的 `nextTick` 改为先跑 `checkAndSyncTemplateChanges`,再存 `formInitialSnapshot`;避免自动修改 `form.tabs` 后快照与实际状态不符,误触 dirty 警告 |
|
||
|
||
##### MaterialCategory.vue — 继承模板页签可编辑(层层传递默认值)
|
||
|
||
| # | 改动 | 说明 |
|
||
|---|---|---|
|
||
| 1 | **继承 imported tab 改为可编辑** | 之前继承引入模板页签是灰色锁定不可改;现在改为蓝色 bar(和继承自定义页签一致),每个字段可为本分类设置默认值 |
|
||
| 2 | **`getParentEffectiveDefault`** | 新增函数:返回父类有效默认值(父类 override → 模板 defaultValue 优先级),作为子类字段的「父类默认」基线 |
|
||
| 3 | **`formatParentEffectiveDefault`** | 格式化父类有效默认值用于显示(null/空/数组等特殊情况处理) |
|
||
| 4 | **子类 override 存储** | 复用 `inherited-extended` 数据结构(`ensureInhExtended` / `getInhOverride` / `setInhOverride` / `hasInhOverride`),存在 `form.tabs[tabCode].overrides` 中 |
|
||
| 5 | **字段底部显示「父类默认」** | 替代原来的「默认」标签,显示父类有效默认值;子类有 override 时显示「还原」按钮,点击清除 override |
|
||
| 6 | **`collectInheritedData` 合并修复** | 遇到 `inherited-extended` 覆盖已有 `imported` tab 时,保留 `imported` 类型,合并 overrides(子类 override 覆盖父类 override),而非直接覆盖整个 tab;确保孙类也能识别为 imported 类型并可继续编辑 |
|
||
| 7 | **三级继承示例** | A(根)引入模板设默认「valueA」→ B 继承设「valueB」→ C 继承时看到「父类默认: valueB」,可再设「valueC」;`getParentEffectiveDefault` 取最近一层有效值 |
|
||
|
||
##### MaterialCategory.vue — 未保存变更警告
|
||
|
||
| # | 改动 | 说明 |
|
||
|---|---|---|
|
||
| 1 | **`formInitialSnapshot`** | 打开表单后(模板同步完成后)存 `JSON.stringify({ name, tabs, fieldMap, rules })` 快照 |
|
||
| 2 | **`isFormDirty` computed** | 对比当前 form 与快照,不相同则为 dirty |
|
||
| 3 | **`guardDirty(action)`** | 切换分类前先检查 `isFormDirty`;若 dirty,弹确认框「放弃改动并继续 / 留下来保存」;用户确认后执行 action,取消则留在当前分类 |
|
||
| 4 | **三入口统一拦截** | `handleAddRoot`、`handleAddChild`、`handleEdit` 均套 `guardDirty` |
|
||
| 5 | **保存后跳过检查** | `submitForm` 成功后清空 `formInitialSnapshot.value = ''`,使后续内部调用 `handleEdit(refreshed)` 时 `isFormDirty` 返回 false,不误触警告 |
|
||
|
||
##### MaterialCategory.vue — 计算字段运行时显示
|
||
|
||
| # | 改动 | 说明 |
|
||
|---|---|---|
|
||
| 1 | **`evalFormula`** | 新增公式安全求值函数:将 `{fieldId}` 替换为实际值,用白名单正则校验,`Function` 构造器执行,处理除零和语法错误 |
|
||
| 2 | **`buildImportedValueMap`** | 收集引入页签内各字段当前值(override 优先,否则 defaultValue)构建 valueMap |
|
||
| 3 | **formula 字段只读展示** | 引入页签中 `type === 'formula'` 的字段不显示输入框,显示黄底带 `ƒ` 图标的只读框,值为实时计算结果 |
|
||
| 4 | **`getFormulaValue`** | 辅助函数:字段 type 为 formula 时调用 evalFormula 计算并返回结果或「公式错误」 |
|
||
| 5 | **CSS 样式** | `.ifi-formula-result`(黄底虚线边框)、`.ifi-formula-icon`(橙色 ƒ 字)、`.ifi-formula-val`(等宽字体数字) |
|
||
|
||
---
|
||
|
||
##### 模板数据 JSON 格式(服务端接口参考)
|
||
|
||
```json
|
||
[
|
||
{
|
||
"id": "tab_1774361234567",
|
||
"name": "基本信息",
|
||
"desc": "模板说明",
|
||
"createdAt": 1774361234567,
|
||
"groups": [
|
||
{
|
||
"id": "g_1774361234568",
|
||
"name": "规格",
|
||
"fields": [
|
||
{
|
||
"id": "f_1774361234569_abc",
|
||
"label": "颜色",
|
||
"source": "custom",
|
||
"type": "select",
|
||
"optionsRaw": "红,绿,蓝",
|
||
"defaultValue": "红",
|
||
"rules": []
|
||
},
|
||
{
|
||
"id": "f_1774361234571_ghi",
|
||
"label": "总重",
|
||
"source": "custom",
|
||
"type": "formula",
|
||
"formula": "{f_xxx} + {f_yyy}",
|
||
"defaultValue": null,
|
||
"rules": []
|
||
},
|
||
{
|
||
"id": "f_1774361234572_jkl",
|
||
"label": "产地",
|
||
"source": "dict",
|
||
"dictKey": "origin_dict_key",
|
||
"defaultValue": "cn",
|
||
"rules": []
|
||
}
|
||
]
|
||
}
|
||
]
|
||
}
|
||
]
|
||
```
|
||
|
||
`source` 为 `"custom"` 时,`type` 可为:`text / number / date / select / multiselect / checkbox / textarea / formula`;`source` 为 `"dict"` 时,有 `dictKey` 字段。`formula` 类型字段额外有 `formula` 字符串,用 `{fieldId}` 引用同页签内其他数字字段。
|
||
|
||
---
|
||
|
||
#### 📌 待办
|
||
|
||
- [ ] 第二行第二列内容待定
|
||
- [ ] ChordChart 改用弹力布局(解决标签偏移)
|
||
- [ ] 接口联调(替换所有 mock 数据)
|
||
- [x] ~~订单号点击跳转具体订单详情页~~ ✅ 已完成(含整行点击跳转)
|
||
- [x] ~~采购订单详情加分仓分配~~ ✅ 2026-03-01 完成
|
||
- [x] ~~批次管理加7阶段流程追踪~~ ✅ 2026-03-01 完成
|
||
- [x] ~~供应商发票兼容性矩阵~~ ✅ 2026-03-01 完成
|
||
- [x] ~~库存总览商品多仓对比视图~~ ✅ 2026-03-01 完成
|
||
- [x] ~~备货配额优先级配置页~~ ✅ 2026-03-01 完成
|
||
- [x] ~~采购流程看板~~ ✅ 2026-03-01 完成
|
||
- [ ] 移动端底部导航同步新增菜单(备货配额优先级 / 采购流程看板)
|
||
- [ ] 库存/销售/财务各子页面内容实现
|
||
- [x] ~~物料分类管理(增删改查)~~ ✅ 2026-03-05 完成
|
||
- [x] ~~基础字典管理(增删改查)~~ ✅ 2026-03-05 完成
|
||
- [x] ~~场景管理页(新增/编辑/删除+属性管理)~~ ✅ 2026-03-07 完成
|
||
- [x] ~~分类关联场景(单选,子分类继承,排他性)~~ ✅ 2026-03-07 完成
|
||
- [x] ~~物料继承场景属性~~ ✅ 2026-03-07 完成
|
||
- [x] ~~票据管理页(列表+新增+编辑)~~ ✅ 2026-03-08 完成
|
||
- [x] ~~物料分类 CRUD 对接真实 API~~ ✅ 2026-03-08 完成
|
||
- [x] ~~场景管理 CRUD 对接真实 API~~ ✅ 2026-03-08 完成
|
||
- [ ] 特征管理页实现
|
||
- [ ] 模板管理页实现
|
||
- [ ] 物料管理与采购/库存/销售模块数据联动
|
||
- [ ] 价格本计算引擎(预估成本/定价成本/卖价体系)
|
||
- [ ] ERP 数据接入替换 mock 数据
|
||
|
||
#### 📱 移动端底部导航更新
|
||
|
||
`src/layouts/index.vue` 中 `bottomNavItems` 硬编码,新增菜单后需手动同步。
|
||
|
||
**当前5项(与路由一一对应):**
|
||
|
||
| 顺序 | name | title | icon |
|
||
|---|---|---|---|
|
||
| 1 | `Root` | 首页 | `material-symbols:home` |
|
||
| 2 | `Purchase` | 采购 | `mdi:cart-outline` |
|
||
| 3 | `Inventory` | 库存 | `mdi:warehouse` |
|
||
| 4 | `Sales` | 销售 | `mdi:chart-line` |
|
||
| 5 | `Finance` | 财务 | `mdi:currency-cny` |
|
||
|
||
**注意:** `handleBottomNavClick` 复用 `handleMainMenuClick`,通用逻辑无需单独维护。后续新增主菜单时记得同步更新 `bottomNavItems`。
|
||
|
||
---
|
||
|
||
### Mapbox 配置指南
|
||
|
||
**配置文件位置:** `src/views/login/components/MapboxBackground.vue`(第17-78行)
|
||
|
||
#### 📍 图标配置
|
||
|
||
在 `mapIcons` 数组中配置地图上的图标:
|
||
|
||
```javascript
|
||
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` 数组中配置地图上的文字:
|
||
|
||
```javascript
|
||
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(当前代码使用):**
|
||
|
||
```javascript
|
||
// 在 Login.vue 中调用
|
||
mapboxRef.value.showIcon() // 显示船图标
|
||
mapboxRef.value.hideIcon() // 隐藏船图标
|
||
mapboxRef.value.showNumber() // 显示数字(带动画)
|
||
mapboxRef.value.hideNumber() // 隐藏数字
|
||
```
|
||
|
||
**新的通用 API(更灵活):**
|
||
|
||
```javascript
|
||
// 显示/隐藏所有配置的元素
|
||
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:添加多个船只和标签**
|
||
|
||
```javascript
|
||
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:大屏数据展示(多个动态数字)**
|
||
|
||
```javascript
|
||
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` 字段:
|
||
```javascript
|
||
coordinates: {
|
||
lng: 122.24, // 经度(东西方向,数字越大越往东)
|
||
lat: 37.48 // 纬度(南北方向,数字越大越往北)
|
||
}
|
||
```
|
||
|
||
#### 💡 最佳实践
|
||
|
||
1. **图标命名**:使用语义化的 ID,如 `ship-1`、`warehouse-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(按钮、表单等)
|
||
- 不需要跟随地图倾斜的内容
|
||
|
||
#### 📦 基础用法
|
||
|
||
```vue
|
||
<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'`(垂直布局)**:图标在上,文字在下
|
||
- 适合:紧凑显示位置名称、标签
|
||
- 示例:港口名称、码头标识
|
||
```vue
|
||
<MapOverlay
|
||
icon="/src/assets/icons/6.png"
|
||
text="威海港"
|
||
layout="vertical"
|
||
:gap="6"
|
||
/>
|
||
```
|
||
|
||
- **`'horizontal'`(水平布局)**:图标在左,文字在右
|
||
- 适合:显示详细信息、多项数据组合
|
||
- 示例:带图标的信息卡片、数据展示
|
||
```vue
|
||
<MapOverlay
|
||
icon="/src/assets/icons/6.png"
|
||
text="威海港 | 停泊: 8艘 | 货物: 15,680吨"
|
||
layout="horizontal"
|
||
:gap="10"
|
||
/>
|
||
```
|
||
|
||
#### 💬 详情按钮功能
|
||
|
||
组件支持在文字上悬停时显示"查看详情"按钮(仅在 >1024px 屏幕显示):
|
||
|
||
**启用方式:**
|
||
```vue
|
||
<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. **弹出消息(默认)**
|
||
```vue
|
||
<MapOverlay
|
||
:enable-popover="true"
|
||
detail-action="message"
|
||
detail-message="威海港详情:当前停泊8艘船只"
|
||
/>
|
||
```
|
||
|
||
2. **跳转路由**
|
||
```vue
|
||
<MapOverlay
|
||
:enable-popover="true"
|
||
detail-action="route"
|
||
detail-route="/"
|
||
/>
|
||
```
|
||
注:路由路径需要是项目中已存在的路由,如 `/`(首页)、`/courses/coursesInfo` 等
|
||
|
||
**事件处理示例:**
|
||
```js
|
||
const handleDetailClick = (data) => {
|
||
console.log('查看详情:', data.text)
|
||
console.log('坐标:', data.coordinates)
|
||
console.log('行为:', data.action) // 'message' 或 'route'
|
||
// 自定义额外处理逻辑
|
||
}
|
||
```
|
||
|
||
#### 🔧 技术实现细节
|
||
|
||
**密码哈希存储:**
|
||
- 使用 bcrypt 算法(rounds=10)
|
||
- 字段名:`password_hash`(不是 `password`)
|
||
- 在线生成工具:https://bcrypt-generator.com/
|
||
|
||
**路由守卫调整:**
|
||
- 移除了登录后强制跳转到 `/login` 的逻辑
|
||
- 允许用户正常访问首页 `/` 和其他管理页面
|
||
- 登录后的默认页面由登录逻辑控制
|
||
|
||
**Vite 代理配置(开发环境):**
|
||
```js
|
||
proxy: {
|
||
'/api/qianyi': {
|
||
target: 'http://115.190.83.144:8080', // Rust 服务器
|
||
changeOrigin: true,
|
||
secure: false
|
||
}
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
### 📦 采购管理系统需求分析
|
||
|
||
#### 🎯 系统概述
|
||
|
||
**项目目标:** 开发一个智能采购管理系统,基于历史销售数据和业务预测,自动计算最优采购数量和采购时机。
|
||
|
||
**核心价值:**
|
||
- ✅ **降低库存成本** - 避免过度采购导致资金占用
|
||
- ✅ **防止缺货** - 确保库存能满足销售需求
|
||
- ✅ **智能预警** - 提前提醒采购时机
|
||
- ✅ **多维度管理** - 支持多仓库、多组织类型、多供应商
|
||
|
||
**技术架构:**
|
||
- **前端**:Vue 3 + Element Plus(威海地图项目集成)
|
||
- **后端**:Rust 服务器(qianyi 模块)+ Go 服务器备选
|
||
- **数据库**:MySQL(qianyi 数据库)
|
||
- **计算引擎**:后端实时计算 + 定时任务分析
|
||
|
||
---
|
||
|
||
#### 📊 核心业务逻辑
|
||
|
||
**1. 采购数量计算公式(核心)**
|
||
|
||
```
|
||
本次采购数量 = 趋势预测采购备货数量(本组织类型)
|
||
+ 需求预测采购备货数量(本组织类型)
|
||
- 在途数量(属于本组织类型)
|
||
- (在库数量(属于本组织类型且未过期) - 往期需求型预测保留期内数量)
|
||
```
|
||
|
||
**2. 关键概念解析**
|
||
|
||
| 概念 | 说明 | 数据来源 |
|
||
|------|------|---------|
|
||
| **趋势预测采购备货数量** | 基于往年同期"零售端"销量,预测未来一段时间的需求 | 历史销售记录(去年今日 + 额定库存容量时间) |
|
||
| **需求预测采购备货数量** | 基于客户订单预测,未来一段时间的确定性需求 | 客户订单、合同、需求预测 |
|
||
| **在途数量** | 已采购但未入库的商品数量 | 采购订单记录(下单未到货) |
|
||
| **在库数量** | 已入库且未过期的商品数量 | 库存记录(过滤过期商品) |
|
||
| **往期需求型预测保留期内数量** | 为重要客户保留的商品(信誉高的客户) | 客户信用等级 + 保留期配置 |
|
||
|
||
**3. 采购时机判断**
|
||
|
||
```
|
||
商品预计售罄时间 = 根据当前库存 + 在途数量 + 预测销量动态计算
|
||
必要采购时间 = 商品预计售罄时间 - 采购提前期
|
||
采购提醒时间 = 必要采购时间 - 采购提醒提前期
|
||
```
|
||
|
||
**示例:**
|
||
- 采购提前期:10天
|
||
- 采购提醒提前期:5天
|
||
- 如果预计 2026.2.10 售罄
|
||
- 则 2026.1.31 是必要采购时间
|
||
- 则 2026.1.26 开始提醒采购
|
||
|
||
---
|
||
|
||
#### 🏢 多维度管理
|
||
|
||
**1. 仓库维度(4个中心库)**
|
||
- 人和中心库
|
||
- 青岛中心库
|
||
- 宁波中心库
|
||
- 舟山中心库
|
||
|
||
**2. 组织类型维度(9种)**
|
||
|
||
| 组织类型 | 税率 | 开票类型 | 业务特点 |
|
||
|---------|------|---------|---------|
|
||
| 13%一般纳税人(非小微) | 13% | 专票 | 主要销售对象,进项充足 |
|
||
| 13%一般纳税人(小微) | 13% | 专票 | 小微企业,税收优惠 |
|
||
| 9%一般纳税人(非小微) | 9% | 专票 | 特定行业税率 |
|
||
| 9%一般纳税人(小微) | 9% | 专票 | - |
|
||
| 6%一般纳税人(非小微) | 6% | 专票 | - |
|
||
| 6%一般纳税人(小微) | 6% | 专票 | - |
|
||
| 小规模纳税人(非小微) | 3% | 普票 | 小规模企业 |
|
||
| 小规模纳税人(小微) | 3% | 普票 | 小微小规模 |
|
||
| 收据组织 | 0% | 收据 | 个人卡、无票交易 |
|
||
|
||
**说明:**
|
||
- 每个仓库的每种组织类型独立统计库存
|
||
- 采购需求按"仓库 + 组织类型"维度计算
|
||
- 不同组织类型的商品可以内部交易/借用
|
||
|
||
**3. 批次管理(双维度)**
|
||
|
||
**财务维度批次:**
|
||
- 商品归属于**源采购组织**(实际付款方)
|
||
- 用于财务核算、税务申报
|
||
- 字段:批次号、归属组织、归属仓库、在途/在库数量
|
||
|
||
**业务维度批次:**
|
||
- 商品可供**多个组织类型**使用
|
||
- 用于业务统计、库存查询
|
||
- 字段:仓库 + 组织类型 → 在途/在库数量
|
||
|
||
**示例(批次号 12345):**
|
||
|
||
财务维度:
|
||
```
|
||
归属组织:浩海船舶配套设备有限公司(13%一般纳税人非小微)
|
||
所处仓库:人和中心库
|
||
在途:80件,在库:195件
|
||
```
|
||
|
||
业务维度:
|
||
```
|
||
人和中心库:
|
||
- 13%一般纳税人(非小微):在途0, 在库140
|
||
- 13%一般纳税人(小微):在途0, 在库20
|
||
- 小规模纳税人(非小微):在途0, 在库5
|
||
- 收据组织:在途0, 在库25
|
||
|
||
总计:195件(与财务维度一致)
|
||
```
|
||
|
||
---
|
||
|
||
#### 💰 成本管理
|
||
|
||
**1. 采购成本构成**
|
||
|
||
```
|
||
单位商品采购总成本 = 商品本身成本 + 运费成本 + 公关维持成本 - 保底返利
|
||
```
|
||
|
||
**2. 运费成本率(供应商 + 仓库维度)**
|
||
|
||
| 供应商 | 仓库 | 票据类型 | 运费成本率 |
|
||
|--------|------|---------|-----------|
|
||
| 甲 | 人和中心库 | 13%专票 | 1% |
|
||
| 甲 | 人和中心库 | 9%专票 | 0% |
|
||
| 甲 | 青岛中心库 | 13%专票 | 1% |
|
||
|
||
**3. 公关维持成本(供应商 + 票据类型维度)**
|
||
|
||
从历史数据分析得出,维持供应商关系的隐性成本:
|
||
- 13%专票:1%
|
||
- 其他类型:0%
|
||
|
||
**4. 调货成本率(仓库间调货)**
|
||
|
||
当直采仓库无法满足需求,需要从其他仓库调货时产生的成本。
|
||
|
||
---
|
||
|
||
#### 🔄 业务流程
|
||
|
||
**完整采购流程:**
|
||
|
||
```
|
||
1. [系统监控] 实时监控各仓库各组织类型的库存水平
|
||
↓
|
||
2. [预警触发] 库存容量 < 采购提前期内预测销量
|
||
↓
|
||
3. [提前提醒] 提前5天(可配置)提醒采购员
|
||
↓
|
||
4. [计算采购量] 根据公式计算本次采购数量
|
||
↓
|
||
5. [选择供应商] 根据价格、账期、可达性选择最优供应商
|
||
↓
|
||
6. [下达订单] 生成采购订单(含批次号)
|
||
↓
|
||
7. [商品在途] 订单下达后,在途数量增加
|
||
↓
|
||
8. [到货入库] 商品到货,在库数量增加,在途数量减少
|
||
↓
|
||
9. [开始新周期] 库存容量更新,系统继续监控
|
||
```
|
||
|
||
**动态调整因素:**
|
||
- ⚡ **暴卖** → 库存快速下降 → 提前采购
|
||
- 📦 **退货** → 库存增加 → 延后采购
|
||
- ❌ **客户取消订单** → 需求预测下降
|
||
- 📈 **新增大订单** → 需求预测上升
|
||
|
||
---
|
||
|
||
#### 🗃️ 数据库设计(待实现)
|
||
|
||
**核心表结构:**
|
||
|
||
**1. 商品档案表(products)**
|
||
```sql
|
||
- 商品ID
|
||
- 商品名称
|
||
- 规格型号
|
||
- 宏观采购到货时间间隔(如:10天)
|
||
- 库存有效期(如:3650天)
|
||
- 额定库存容量时间(如:90天)
|
||
- 采购提醒提前期(如:5天)
|
||
```
|
||
|
||
**2. 批次表(batches)- 财务维度**
|
||
```sql
|
||
- 批次号(主键)
|
||
- 商品ID
|
||
- 归属组织ID
|
||
- 归属组织类型
|
||
- 当前仓库
|
||
- 在途数量
|
||
- 在库数量
|
||
- 售出数量
|
||
- 创建时间
|
||
```
|
||
|
||
**3. 批次业务分布表(batch_business)- 业务维度**
|
||
```sql
|
||
- ID
|
||
- 批次号(关联 batches)
|
||
- 仓库
|
||
- 组织类型
|
||
- 在途数量
|
||
- 在库数量
|
||
- 更新时间
|
||
```
|
||
|
||
**4. 历史销售记录表(sales_history)**
|
||
```sql
|
||
- 销售ID
|
||
- 商品ID
|
||
- 仓库
|
||
- 组织类型
|
||
- 销售数量
|
||
- 销售日期
|
||
- 客户类型(零售端/客户订单)
|
||
```
|
||
|
||
**5. 供应商表(suppliers)**
|
||
```sql
|
||
- 供应商ID
|
||
- 供应商名称
|
||
- 可提供票据类型
|
||
- 公关维持成本费率(JSON)
|
||
- 运费成本率配置(JSON)
|
||
```
|
||
|
||
**6. 供应商商品政策表(supplier_policies)**
|
||
```sql
|
||
- 政策ID
|
||
- 供应商ID
|
||
- 商品ID
|
||
- 网价
|
||
- 采购时下浮方式/比例/金额
|
||
- 保底返利配置
|
||
- 账期优惠月数
|
||
- 支持承兑付款
|
||
- 延迟返利月数
|
||
```
|
||
|
||
**7. 仓库表(warehouses)**
|
||
```sql
|
||
- 仓库ID
|
||
- 仓库名称
|
||
- 仓库类型(中心库/二级库)
|
||
- 地理位置(经纬度)
|
||
- 是否可直采
|
||
```
|
||
|
||
**8. 仓库调货成本表(warehouse_transfer_costs)**
|
||
```sql
|
||
- 调出仓库
|
||
- 调入仓库
|
||
- 票据类型
|
||
- 调货成本率
|
||
```
|
||
|
||
**9. 采购订单表(purchase_orders)**
|
||
```sql
|
||
- 订单ID
|
||
- 批次号
|
||
- 商品ID
|
||
- 供应商ID
|
||
- 源采购组织
|
||
- 目标仓库
|
||
- 采购数量
|
||
- 采购单价
|
||
- 运费
|
||
- 订单状态(待发货/在途/已到货)
|
||
- 预计到货时间
|
||
- 实际到货时间
|
||
```
|
||
|
||
**10. 客户需求预测表(customer_demands)**
|
||
```sql
|
||
- 需求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: 必要采购时间
|
||
```
|
||
|
||
**计算步骤:**
|
||
|
||
```javascript
|
||
// 步骤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. 趋势预测算法(基于历史数据)**
|
||
|
||
```javascript
|
||
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. 需求预测算法(基于客户订单)**
|
||
|
||
```javascript
|
||
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 应用场景:**
|
||
```vue
|
||
<!-- 人和中心库 - 显示库存预警 -->
|
||
<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. 数据模拟(开发阶段)**
|
||
|
||
在没有真实销售记录的情况下,需要生成模拟数据:
|
||
|
||
```javascript
|
||
// 生成模拟历史销售数据(去年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. 定时任务(每日计算)**
|
||
|
||
```javascript
|
||
// 每天凌晨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
|
||
// 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` 事件
|
||
|
||
**使用示例:**
|
||
```vue
|
||
<MapOverlay
|
||
:map="mapboxRef.map"
|
||
:coordinates="{ lng: 122.11, lat: 37.50 }"
|
||
:visible="showShipChart"
|
||
text="威海港 | 停泊: 8艘"
|
||
layout="horizontal"
|
||
@detail-click="handleDetailClick"
|
||
/>
|
||
|
||
<script setup>
|
||
const handleDetailClick = (data) => {
|
||
console.log('用户点击查看详情:', data)
|
||
// data = { coordinates: {lng, lat}, text: '威海港 | 停泊: 8艘' }
|
||
// 这里可以打开详情页、弹出对话框等
|
||
}
|
||
</script>
|
||
```
|
||
|
||
#### 🆚 Symbol Layer vs MapOverlay
|
||
|
||
| 特性 | Symbol Layer | MapOverlay (DOM) |
|
||
|------|--------------|------------------|
|
||
| 样式灵活性 | ⭐⭐ 受限 | ⭐⭐⭐⭐⭐ 完全自由 |
|
||
| 更新闪烁 | ⚠️ 高频更新会闪烁 | ✅ 无闪烁 |
|
||
| CSS 动画 | ⭐⭐ 简单动画 | ⭐⭐⭐⭐⭐ 任意动画 |
|
||
| 地图倾斜 | ✅ 跟随倾斜 | ❌ 始终水平 |
|
||
| 性能 | ⭐⭐⭐⭐⭐ GPU 加速 | ⭐⭐⭐⭐ DOM 渲染 |
|
||
| 用户交互 | ⭐⭐ 有限 | ⭐⭐⭐⭐⭐ 完整 DOM 事件 |
|
||
|
||
#### 💡 使用建议
|
||
|
||
**使用 MapOverlay(DOM)适合:**
|
||
- 需要频繁更新内容(避免闪烁)
|
||
- 需要复杂样式(渐变、阴影、CSS 动画)
|
||
- 需要用户交互(点击、悬停、输入)
|
||
- 需要显示表单、按钮等复杂 UI
|
||
|
||
**使用 Symbol Layer 适合:**
|
||
- 需要跟随地图倾斜(3D 效果)
|
||
- 大量标记点(性能更好)
|
||
- 简单图标和文字
|
||
- 内容基本不变
|
||
|
||
#### ⚙️ 响应式显示
|
||
|
||
MapOverlay 不会自动隐藏,需要手动控制:
|
||
|
||
```vue
|
||
<!-- ≤1024 自动隐藏(跟船图标、ECharts 图表一致) -->
|
||
<MapOverlay
|
||
:visible="showShipChart && isLargeScreen"
|
||
...
|
||
/>
|
||
|
||
<!-- 始终显示(不受屏幕宽度限制) -->
|
||
<MapOverlay
|
||
:visible="isLoggedIn"
|
||
...
|
||
/>
|
||
```
|
||
|
||
**推荐做法:** 跟其他地图元素保持一致,使用 `showShipChart && isLargeScreen`
|
||
|
||
#### 🎬 顺序显示多组元素
|
||
|
||
使用 `delay` 参数实现动画的顺序播放:
|
||
|
||
```vue
|
||
<!-- 第一组:威海港(立即) -->
|
||
<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/qianyi` → `http://115.190.83.144:8080`
|
||
- **热更新**:Vite HMR
|
||
- **类型检查**:TypeScript strict mode
|
||
|
||
**移动端适配:**
|
||
- **断点**:
|
||
- 移动端:< 640px (sm)
|
||
- 平板:640px - 1024px
|
||
- 桌面:≥ 1024px (lg)
|
||
- **页面容器**:桌面固定,移动端可滚动
|
||
- **数据面板**:桌面横向,移动端纵向
|
||
- **天气组件**:响应式字体大小
|
||
- **登录提示**:响应式字体和间距
|
||
|
||
**动画时序:**
|
||
1. **加载动画**:0-100% 进度条(2 秒)
|
||
2. **地图淡入**:opacity 0 → 1(0.8 秒)
|
||
3. **地图 3D 转换**:2 秒平滑过渡
|
||
4. **数据面板滑入**:地图转换 1.7 秒后开始(800ms 滑入 + 回弹)
|
||
5. **整体流程**:约 4-5 秒完成所有动画
|
||
|
||
**技术特点:**
|
||
- **无 StrictMode**:避免地图重复初始化
|
||
- **可选链**:防止 ref null 报错
|
||
- **定时器管理**:登录表单自动关闭(10 秒)
|
||
- **动画优化**:使用 CSS transition + cubic-bezier 缓动
|
||
- **类型安全**:完整的 TypeScript 类型定义
|
||
- **错误处理**:Axios 拦截器统一处理 401 错误
|
||
|
||
**开发命令:**
|
||
```bash
|
||
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. 初始化项目
|
||
```bash
|
||
# 创建项目目录
|
||
mkdir server-go
|
||
cd server-go
|
||
|
||
# 初始化 Go 模块
|
||
go mod init qianyi-server
|
||
```
|
||
|
||
### 2. 安装依赖
|
||
```bash
|
||
# 一次性安装所有依赖
|
||
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. 运行开发服务器
|
||
```bash
|
||
# 直接运行(不编译,适合开发)
|
||
go run main.go
|
||
|
||
# 指定文件运行
|
||
go run .
|
||
```
|
||
|
||
### 4. 编译程序
|
||
```bash
|
||
# 编译当前平台(Windows 生成 .exe)
|
||
go build -o qianyi.exe main.go
|
||
|
||
# 或者简写(自动使用包名)
|
||
go build -o qianyi.exe
|
||
|
||
# 编译优化版本(减小体积)
|
||
go build -ldflags="-s -w" -o qianyi.exe
|
||
```
|
||
|
||
### 5. 跨平台编译
|
||
```bash
|
||
# 编译 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. 查看依赖
|
||
```bash
|
||
# 查看依赖树
|
||
go list -m all
|
||
|
||
# 更新依赖
|
||
go get -u
|
||
go mod tidy
|
||
```
|
||
|
||
### 7. 常用命令
|
||
```bash
|
||
go version # 查看 Go 版本
|
||
go env # 查看环境变量
|
||
go fmt ./... # 格式化代码
|
||
go vet ./... # 静态检查
|
||
go test ./... # 运行测试
|
||
```
|
||
|
||
---
|
||
|
||
**项目结构:**
|
||
```
|
||
server-go/
|
||
├── main.go # 主入口文件
|
||
├── go.mod # 依赖管理文件(自动生成)
|
||
├── go.sum # 依赖校验文件(自动生成)
|
||
├── config/
|
||
│ └── config.go # 配置加载
|
||
├── database/
|
||
│ └── database.go # 数据库连接
|
||
├── handlers/
|
||
│ └── auth.go # 登录处理器
|
||
├── models/
|
||
│ └── user.go # 用户模型
|
||
├── routes/
|
||
│ └── routes.go # 路由注册
|
||
└── utils/
|
||
└── jwt.go # JWT 工具
|
||
```
|
||
|
||
---
|
||
|
||
**数据库配置(与 Rust 共用):**
|
||
- Host: `115.190.83.144`
|
||
- Port: `3306`
|
||
- User: `root`
|
||
- Password: `123a456b`
|
||
- Database: `qianyi`
|
||
|
||
---
|
||
|
||
**已实现的接口(2/23):**
|
||
- ✅ `POST /qianyi/login` - 管理员登录(用户名 + 密码)
|
||
- ✅ `POST /qianyi/admin/register` - 管理员注册(仅开发环境)
|
||
|
||
**待实现的接口(21/23):**
|
||
- ⏳ `POST /auth/wechat_login` - 微信小程序登录
|
||
- ⏳ 课程管理(5个接口)
|
||
- ⏳ 报名管理(4个接口)
|
||
- ⏳ 热门内容(2个接口)
|
||
- ⏳ 动态内容(2个接口)
|
||
- ⏳ 事件动态(1个接口)
|
||
- ⏳ 活动照片(6个接口)
|
||
- ⏳ 小程序报名(2个接口)
|
||
|
||
---
|
||
|
||
**服务器配置:**
|
||
- 端口:8082
|
||
- CORS:已启用(支持跨域)
|
||
- JWT Secret:`qianyi-secret-key-change-this-in-production`
|
||
- JWT 过期时间:24 小时(86400 秒)
|
||
|
||
---
|
||
|
||
**Go vs Rust 对比:**
|
||
|
||
| 特性 | Go | Rust |
|
||
|------|-----|------|
|
||
| 编译速度 | ⚡⚡⚡ 快 | ⚡ 慢 |
|
||
| 运行性能 | ⚡⚡ 快 | ⚡⚡⚡ 极快 |
|
||
| 内存安全 | 垃圾回收 | 所有权系统 |
|
||
| 学习曲线 | ⭐⭐ 简单 | ⭐⭐⭐⭐ 困难 |
|
||
| 错误处理 | `if err != nil` | `Result<T, E>` |
|
||
| 并发模型 | Goroutine | async/await |
|
||
| 编译产物 | 单文件 .exe | 单文件 .exe |
|
||
| 部署方式 | 直接运行 | 直接运行 |
|
||
|
||
---
|
||
|
||
**说明:**
|
||
Go 实现的 qianyi 后端服务器,与 Rust 版本共用同一个 MySQL 数据库。使用 Gin 框架构建 RESTful API,支持 JWT 认证和 BCrypt 密码加密。编译后生成单个可执行文件,无需运行时环境,部署简单。目前已实现管理员登录接口,其他 22 个接口待开发。
|
||
|
||
---
|
||
|
||
## 🚨 数据库安全事件(2026-01-23)
|
||
|
||
### **事件概述**
|
||
- **时间**:2026年1月23日
|
||
- **影响**:qianyi 数据库所有表被删除
|
||
- **攻击方式**:数据库外网暴露 + 弱密码
|
||
- **数据恢复**:无备份,完全重建
|
||
|
||
### **已采取的恢复措施**
|
||
|
||
#### 1. **数据库重建(已完成)**
|
||
创建了 14 个核心表:
|
||
```sql
|
||
-- 核心表
|
||
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`
|
||
|
||
**请求体:**
|
||
```json
|
||
{
|
||
"username": "admin",
|
||
"password": "your_password",
|
||
"email": "admin@qianyi.com"
|
||
}
|
||
```
|
||
|
||
**响应体:**
|
||
```json
|
||
{
|
||
"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 像素,模糊、有噪点
|
||
|
||
步骤 2:AI 超分放大(Upscayl/Real-ESRGAN)
|
||
处理: 64×64 → 256×256 (4x)
|
||
效果: AI 智能补充细节、减少噪点
|
||
|
||
步骤 3:高质量缩小(本工具)
|
||
处理: 256×256 → 64×64 (Lanczos)
|
||
效果: 保留增强细节,超采样降噪
|
||
|
||
最终:原尺寸(64×64)+ 更清晰
|
||
```
|
||
|
||
**技术原理:**
|
||
- **超采样(Supersampling)**:图形学中的抗锯齿技术,通过高分辨率渲染后降采样减少锯齿
|
||
- **AI 超分增强**:Real-ESRGAN 在放大过程中智能补充细节、修复模糊
|
||
- **Lanczos 重采样**:数学上最优的图像缩放算法,保留频率信息最完整
|
||
- **信息保留**:4x 放大后缩小,相当于 16 次采样平均,有效降噪
|
||
|
||
**GUI 界面功能:**
|
||
- 📂 **文件夹选择器** - 可视化浏览选择输入/输出文件夹
|
||
- ⚙️ **参数配置面板**:
|
||
- **放大倍数**(2x/4x/8x)- 需与 Upscayl 设置保持一致
|
||
- **输出质量**(80-100%)- 滑块控制 PNG/JPG 压缩质量
|
||
- 📊 **实时进度** - 进度条和详细处理日志
|
||
- 📝 **日志窗口** - 显示每张图片的处理详情(原尺寸→目标尺寸)
|
||
- 🎯 **批量处理** - 一键处理整个文件夹
|
||
|
||
**使用流程:**
|
||
|
||
**完整工作流(配合 Upscayl):**
|
||
|
||
1. **使用 Upscayl 放大图片**:
|
||
- 打开 Upscayl(独立工具)
|
||
- 选择原始图集(例如:`110000_0.png`,2048×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.png` → `110000_0_enhanced.png`
|
||
|
||
**性能指标:**
|
||
- **处理速度**:约 0.5-2 秒/图(2048×2048 图片)
|
||
- **内存占用**:约 100-500 MB(取决于图片尺寸)
|
||
- **批量处理**:约 100-200 张/分钟(取决于尺寸)
|
||
- **清晰度提升**:主观评价提升 20-40%
|
||
- **文件大小**:质量 95% 时约等于或略小于原图
|
||
|
||
**技术特点:**
|
||
- **无需 GPU**:纯 CPU 处理(Pillow + NumPy)
|
||
- **无需模型**:不依赖 AI 模型文件,仅做缩放处理
|
||
- **轻量级**:仅需 Python + Pillow(总计约 10-20 MB)
|
||
- **跨平台**:Windows/macOS/Linux 通用
|
||
- **零配置**:首次运行自动安装依赖
|
||
|
||
**启动脚本说明:**
|
||
|
||
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\
|
||
```
|
||
|
||
**依赖安装:**
|
||
```bash
|
||
pip install Pillow
|
||
```
|
||
|
||
**说明:**
|
||
轻量级图片清晰度增强工具,专为"保持原尺寸但提升清晰度"的需求设计。配合 Upscayl 使用,通过超采样技术实现原尺寸下的智能降噪和细节增强。特别适合老游戏资源的现代化改造,如石器时代动画帧处理。提供友好的 GUI 界面和命令行版本,支持批量处理,完全免费开源。
|
||
|
||
**与 Unity Gacha 的完整工作流:**
|
||
```
|
||
Unity Gacha → 导出原图(2048×2048)
|
||
↓
|
||
Upscayl → AI 放大到 8K(8192×8192)
|
||
↓
|
||
ImageEnhancer → 超采样缩回 2K(2048×2048,高清)
|
||
↓
|
||
Unity Gacha → 加载高清图集用于游戏
|
||
```
|
||
|
||
这套工作流可将石器时代的像素风格资源无损提升到高清质量,同时保持原始尺寸和像素风格。
|
||
|
||
---
|
||
|
||
## showtime_weihai 前端 dist 与 Nginx 同机部署(Ubuntu)
|
||
|
||
以下流程适用于:**前端静态资源由 Nginx 提供,Java 后端监听本机端口(例如 8707),前后端部署在同一台服务器**。与仅在本机 `npm run dev` 时使用 `vite.config.js` 里 `proxy` 转发到远程网关的场景不同;**生产构建的 `dist` 不会包含 Vite 开发代理**,浏览器只会请求当前访问站点同源下的路径(如 `/api/...`),因此**线上必须**由 Nginx(或等价网关)把 `/api` 转到后端。
|
||
|
||
### 1. 前端工程是否需要改配置
|
||
|
||
- **`vite.config.js` 中的 `server.port`、`proxy`**:仅影响开发服务器,**不参与** `npm run build` 产物。
|
||
- **`src/utils/request.js`**:在普通浏览器环境下若 `baseURL` 为空,请求为**相对路径** `/api/...`,与 Nginx 反代同源方案一致时,**一般无需为上线改前端**。
|
||
- 若页面部署在**子路径**(如 `/app/`),需同步修改 Vite 的 `base` 与 Nginx 的 `location`,此处不展开。
|
||
|
||
### 2. 在服务器上定位 Nginx 配置与网页目录
|
||
|
||
```bash
|
||
ls /etc/nginx
|
||
ls /etc/nginx/sites-enabled
|
||
cat /etc/nginx/sites-enabled/default
|
||
```
|
||
|
||
关注 **`root ...;`**:静态文件应上传到该路径(Ubuntu 默认示例常为 `/var/www/html`)。可用下面命令快速查找 `root`:
|
||
|
||
```bash
|
||
grep -r "root " /etc/nginx/sites-enabled/
|
||
```
|
||
|
||
### 3. 推荐的 `server` 片段(HTTP 示例)
|
||
|
||
在 `server { }` 内保证:**先写** `location /api/`,**再写** `location /`,避免前缀匹配被宽泛规则抢先。
|
||
|
||
```nginx
|
||
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` 的行为对齐时尝试。
|
||
|
||
修改后执行:
|
||
|
||
```bash
|
||
nginx -t && systemctl reload nginx
|
||
```
|
||
|
||
### 4. 本地构建与上传到服务器
|
||
|
||
1. 在项目目录执行:`npm run build`,得到 `dist` 目录。
|
||
2. 将 **`dist` 目录内的全部内容**(`index.html`、`assets/` 等)上传到 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`**(及正确的路径拼接方式),**不要**再指向旧网关,否则与「前后端同机」架构不一致。
|
||
|
||
---
|