20260722222510
This commit is contained in:
1 parent
d04fad24ed
commit
59b3ccfb1c
36 files changed
+2753
-3064
No files matched your search
@@ -0,0 +1,190 @@
|
||||
---
|
||||
name: AppTabs 操作按钮组与应用内最大化
|
||||
overview: 在页签栏 AppTabs 最左侧新增「一体的、带分割线」的三图标按钮组(返回控制台 / 刷新当前路由 / 留空占位),最右侧新增「应用内最大化」按钮;最大化时隐藏侧边栏+顶栏+页签栏,内容区占满窗口,并提供退出浮窗。
|
||||
design:
|
||||
styleKeywords:
|
||||
- 企业级扁平
|
||||
- 一体化分段工具栏
|
||||
- 竖向分割线
|
||||
- 蓝色主色高亮
|
||||
- 轻量 hover
|
||||
- 桌面端
|
||||
fontSystem:
|
||||
fontFamily: PingFang SC
|
||||
heading:
|
||||
size: 14px
|
||||
weight: 600
|
||||
subheading:
|
||||
size: 13px
|
||||
weight: 500
|
||||
body:
|
||||
size: 12px
|
||||
weight: 400
|
||||
colorSystem:
|
||||
primary:
|
||||
- "#1677ff"
|
||||
- "#0958d9"
|
||||
background:
|
||||
- "#ffffff"
|
||||
- "#f5f7fa"
|
||||
text:
|
||||
- "#1f2329"
|
||||
- "#646a73"
|
||||
functional:
|
||||
- "#e5e8eb"
|
||||
- "#f2f3f5"
|
||||
- "#e6f4ff"
|
||||
- "#c9cdd4"
|
||||
todos:
|
||||
- id: store-state
|
||||
content: 在 app store 新增 contentMaximized 与 routeRefreshKey 状态及切换/刷新方法(不持久化)
|
||||
status: completed
|
||||
- id: apptabs-buttons
|
||||
content: AppTabs 新增左侧三按钮一体化组(返回/刷新/占位+分割线)与右侧最大化按钮并接线
|
||||
status: completed
|
||||
dependencies:
|
||||
- store-state
|
||||
- id: default-layout
|
||||
content: DefaultLayout 应用内最大化隐藏三块布局、加浮动退出按钮、路由刷新 key 接入
|
||||
status: completed
|
||||
dependencies:
|
||||
- store-state
|
||||
- id: verify
|
||||
content: 校验返回/刷新/最大化/退出交互与各页面正常且未触发整页刷新
|
||||
status: completed
|
||||
dependencies:
|
||||
- apptabs-buttons
|
||||
- default-layout
|
||||
---
|
||||
|
||||
## 用户需求
|
||||
|
||||
在页签栏 `AppTabs` 上新增操作按钮:
|
||||
|
||||
- 最左侧新增一组「一体化的三个图标按钮」,按钮之间以分割线分隔,视觉上是一个整体。
|
||||
- 第 1 个(最左):返回控制台按钮,点击跳转到 `/dashboard`(工作台)。
|
||||
- 第 2 个(中间):刷新按钮,要求**重新挂载当前路由组件**(各页面 `onMounted` 重新执行、重新拉数据),**不能**刷新整站(`router.go(0)` / `location.reload()` 禁止)。
|
||||
- 第 3 个(最右):暂不确定功能,先**留空占位**——保留按钮槽位与分割线,禁用状态、无图标。
|
||||
- 最右侧新增一个「收缩全屏」按钮,用户确认行为为**应用内最大化**:隐藏侧边栏 + 顶栏 + 页签栏,让内容区占满整个窗口(仍是网页,不调用浏览器 Fullscreen API)。该按钮放在 `AppTabs` 最右侧。
|
||||
|
||||
## 产品概述
|
||||
|
||||
为内部管理系统页签栏补充快捷操作:左侧一组统一风格的工具按钮(返回控制台 / 刷新路由 / 占位),右侧一个「应用内最大化」开关。最大化后,侧边栏、顶栏、页签栏全部收起,仅留内容区;因承载开关的页签栏随之隐藏,需在内容区右上角浮动一个「退出最大化」按钮用于恢复。
|
||||
|
||||
## 核心特性
|
||||
|
||||
- 左侧三按钮为「一体容器 + 分割线」的视觉整体,风格对齐现有 `tab-nav-button`。
|
||||
- 返回控制台:`router.push('/dashboard')`。
|
||||
- 刷新路由:通过 `appStore.routeRefreshKey` 自增,驱动 `DefaultLayout` 中 `<component :key>` 变化以重挂载当前路由(非整页刷新)。
|
||||
- 应用内最大化:由 `appStore.contentMaximized` 控制,`DefaultLayout` 用 `v-show` 收起三块布局,内容区右上角出现浮动退出按钮。
|
||||
- 占位按钮:禁用、无图标、保留分割线,后续可随时接入功能。
|
||||
|
||||
## 技术栈
|
||||
|
||||
- 前端:Vue 3 `<script setup>` + Vite + antdv-next + Pinia(沿用现有项目栈)
|
||||
- 图标:`@lucide/vue`(复用现有图标习惯,新增 `House` / `RotateCw` / `Maximize2` / `Minimize2`)
|
||||
- 样式:组件内 `scoped lang="scss"`,复用全局 `--fms-*` 主题变量,遵循「少量 CSS / 公用集中 / 不兼容移动端」CSS 规范
|
||||
|
||||
## 实现方案
|
||||
|
||||
### 策略
|
||||
|
||||
1. **状态下沉到 `useAppStore`**:新增 `contentMaximized`(`ref(false)`)+ `toggleContentMaximized()`,以及 `routeRefreshKey`(`ref(0)`)+ `refreshRoute()`(自增计数)。两者均**不加入** `persist.pick`(当前只持久化 `locale`),避免刷新/最大化状态被持久化。
|
||||
2. **`AppTabs` 接线**:
|
||||
|
||||
- 标签轨道前插入「左侧操作组」`.tab-actions`(一体化容器,含 3 个按钮 + 2 条竖向分割线)。按钮 1 调 `router.push('/dashboard')`;按钮 2 调 `appStore.refreshRoute()`;按钮 3 禁用、无图标。
|
||||
- 右滚动按钮后插入「最大化」按钮,调 `appStore.toggleContentMaximized()`,图标按 `contentMaximized` 在 `Maximize2` / `Minimize2` 间切换(退出时页签栏已隐藏,此图标仅作状态提示,真正退出走浮动按钮)。
|
||||
|
||||
3. **`DefaultLayout` 收起布局 + 路由刷新**:
|
||||
|
||||
- `AppSidebar` / `AppToolbar` / `AppTabs` 包裹 `v-show="!appStore.contentMaximized"`(用 `v-show` 而非 `v-if`,保留页签与折叠状态,避免重挂载开销)。
|
||||
- 内容区 `<component :is="Component" :key="route.fullPath + ':' + appStore.routeRefreshKey">`——`routeRefreshKey` 变化即强制重挂载当前路由组件,达成「刷新当前路由」。
|
||||
- 内容区右上角新增 `fixed` 浮动「退出最大化」按钮(`v-if="appStore.contentMaximized"`),点击 `toggleContentMaximized()` 恢复。
|
||||
|
||||
### 关键技术决策
|
||||
|
||||
- **刷新用 key 自增而非整页 reload**:`keep-alive` 下组件按 `:key` 缓存,`route.fullPath + ':' + routeRefreshKey` 变化会创建新实例并触发 `onMounted`,等价于「重进当前页」而不重载网站;原有过渡/缓存结构不变。
|
||||
- **最大化用 `v-show`**:与 `v-if` 相比不销毁 `AppTabs`/`AppSidebar`,保留 `visitedTabs`、`sidebarCollapsed` 等内存状态,退出后零重新加载;`display:none` 不影响键盘/焦点无障碍(内部按钮仍可达)。
|
||||
- **退出入口与开关解耦**:因最大化时 `AppTabs` 被隐藏,退出动作改由内容区浮动按钮承载,保证「进入必有出口」。
|
||||
- **风格对齐**:左侧操作组直接复用 `tab-nav-button` 的尺寸/圆角/配色(26×28、radius 4、`--tabs-idle-bg`/`--fms-text-secondary`),仅在外层加统一个边框与 `1px` 分割线,满足「一体 + 分割线」。
|
||||
|
||||
### 性能与可靠性
|
||||
|
||||
- 路由刷新为低频用户操作:每次刷新在 `keep-alive` 中产生一个新缓存槽。`keep-alive` 当前为裸标签(无 `max`),理论上可无限增长;本场景为人工触发、频率极低,可接受。若后续需要可加 `:max` 限制缓存数——本次不改以避免影响现有缓存语义。
|
||||
- `v-show` 切换为纯 CSS `display` 切换,无重渲染、无额外网络请求;最大化状态不持久化,刷新页面后默认恢复常规布局。
|
||||
- 不改通用接口、不改其他页面、不改权限逻辑;仅触及 `app.js` / `DefaultLayout.vue` / `AppTabs.vue` 三处。
|
||||
|
||||
## 实现注意
|
||||
|
||||
- `app.js` 的 `persist.pick` 保持 `["locale"]` 不变,新增状态不持久化。
|
||||
- `DefaultLayout.vue` 当前无 `<script setup>`,需补 `import { useRoute } from 'vue-router'` 与 `import { useAppStore }` 并取 `route`/`appStore`,再改 `:key` 与加 `v-show`/浮动按钮。
|
||||
- 返回控制台按钮在 `/dashboard` 时点击应无副作用(vue-router 对相同路径 `push` 静默处理)。
|
||||
- 左侧操作组与右侧最大化按钮均需 `aria-label`,占位按钮加 `disabled` 与 `title="敬请期待"`。
|
||||
- 样式保持 `scoped`,与现有 `tab-header` 高度(34px)、间距(gap 4px)一致;分割线用 `border-left` + `margin`。
|
||||
|
||||
## 架构设计
|
||||
|
||||
```mermaid
|
||||
flowchart TD
|
||||
A[AppTabs 左侧操作组] -->|返回控制台| B[router.push /dashboard]
|
||||
A -->|刷新路由| C[appStore.refreshRoute 自增 routeRefreshKey]
|
||||
A -->|占位 禁用| Z[无操作]
|
||||
D[AppTabs 右侧最大化按钮] -->|toggleContentMaximized| E[appStore.contentMaximized=true]
|
||||
E --> F[DefaultLayout v-show 隐藏 Sidebar/Toolbar/Tabs]
|
||||
E --> G[内容区浮动 退出最大化 按钮显示]
|
||||
G -->|toggleContentMaximized| H[恢复常规布局]
|
||||
C --> I[DefaultLayout :key 变化 重挂载路由组件]
|
||||
```
|
||||
|
||||
## 目录结构
|
||||
|
||||
```
|
||||
fms-vue/src/
|
||||
├── stores/
|
||||
│ └── app.js # [MODIFY] 新增 contentMaximized/toggleContentMaximized 与
|
||||
│ # routeRefreshKey/refreshRoute;persist.pick 仍为 ["locale"]。
|
||||
├── layouts/
|
||||
│ ├── DefaultLayout.vue # [MODIFY] 引入 useRoute/useAppStore;三块布局加
|
||||
│ # v-show="!contentMaximized";<component :key> 加入
|
||||
│ # routeRefreshKey;内容区加浮动退出最大化按钮。
|
||||
│ └── components/
|
||||
│ └── AppTabs.vue # [MODIFY] 标签轨道前加左侧一体三按钮组(返回/刷新/占位+分割线);
|
||||
│ # 右滚动按钮后加最大化按钮;import 新图标并接线。
|
||||
```
|
||||
|
||||
## 关键代码结构
|
||||
|
||||
```js
|
||||
// src/stores/app.js 新增状态
|
||||
const contentMaximized = ref(false);
|
||||
const routeRefreshKey = ref(0);
|
||||
function toggleContentMaximized() { contentMaximized.value = !contentMaximized.value; }
|
||||
function refreshRoute() { routeRefreshKey.value++; }
|
||||
// 返回中需导出 contentMaximized, routeRefreshKey, toggleContentMaximized, refreshRoute
|
||||
|
||||
// DefaultLayout.vue 中 <component> 的 key
|
||||
<component :is="Component" :key="route.fullPath + ':' + appStore.routeRefreshKey" />
|
||||
```
|
||||
|
||||
## 设计风格
|
||||
|
||||
延续现有 FMS 企业级扁平风格(白底、蓝色主色 #1677ff、细边框、轻量 hover)。在 34px 高的页签栏内新增两类控件,与既有 `tab-nav-button` 视觉一致,不引入新色彩或拟物风格。
|
||||
|
||||
## 左侧一体化按钮组
|
||||
|
||||
- 一个统一的外层容器 `.tab-actions`:浅边框、4px 圆角、与页签栏同高,内含 3 个等宽图标按钮,按钮之间以 1px 竖向分割线隔开,整体观感如一个「分段工具栏」。
|
||||
- 按钮 1(House 图标):返回控制台;hover 时文字/图标转主色蓝、背景转 `--tabs-active-bg`。
|
||||
- 按钮 2(RotateCw 图标):刷新当前路由;hover 态同上。
|
||||
- 按钮 3(占位):禁用态、无图标、保留分割线,视觉稍淡(opacity 0.4),`title="敬请期待"`。
|
||||
- 图标尺寸 14px(对齐 `.lucide` 习惯),按钮 26×28,圆角 4px。
|
||||
|
||||
## 右侧最大化按钮
|
||||
|
||||
- 位于右滚动箭头之后,独立图标按钮(Maximize2 / 退出时 Minimize2),样式复用 `tab-nav-button`。
|
||||
- 最大化激活后:侧边栏、顶栏、页签栏整体 `display:none`,内容区占满窗口;内容区右上角浮现一个 fixed 圆角胶囊「退出最大化」按钮(退出箭头图标 + 文字),hover 浅色背景,点击恢复三块布局。
|
||||
|
||||
## 交互与响应式
|
||||
|
||||
- 纯桌面端,不做移动端媒体查询(遵循 CSS 规范第 4 条)。
|
||||
- 所有按钮带 `a-tooltip` 文字提示与 `aria-label`;hover/active 有平滑颜色过渡(0.15s ease)。
|
||||
- 分割线与按钮间距使用现有 `gap:4px` 节奏,保持与页签栏其他元素对齐。
|
||||
@@ -0,0 +1,121 @@
|
||||
---
|
||||
name: 模块管理树节点点击懒加载
|
||||
overview: 将模块管理从“挂载时一次性加载全部表”改为“挂载时仅加载树(b_module),点击树节点时才懒加载该模块的字段/权限/翻译/用户分配数据”。
|
||||
todos:
|
||||
- id: split-load
|
||||
content: 拆分 loadAll 为 loadModules 与 loadModuleDetail,新增翻译条件过滤器
|
||||
status: pending
|
||||
- id: wire-lazy
|
||||
content: 将树选中/刷新、onMounted、保存/删除后刷新接入懒加载,保留 draft 与级联逻辑
|
||||
status: pending
|
||||
dependencies:
|
||||
- split-load
|
||||
- id: verify-scopes
|
||||
content: 回归校验新建/取消/保存/删除/刷新在各场景下数据正确且不触发全量加载
|
||||
status: pending
|
||||
dependencies:
|
||||
- wire-lazy
|
||||
---
|
||||
|
||||
## 用户需求
|
||||
|
||||
模块管理页面进入时,不要立即把所有模块的全部数据一次性拉取下来;应当在用户点击(或右键)左侧树节点时,才加载该节点对应模块的详情数据。
|
||||
|
||||
## 产品概述
|
||||
|
||||
模块管理采用「左侧模块树 + 右侧详情」布局。当前实现在页面挂载时即通过 `loadAll()` 一次性并发加载 6 张表(b_module / b_module_field / b_module_power / b_i18n / b_user_module / b_user_power)的全部数据并缓存,再按选中节点从内存过滤。需要改为:挂载时只加载树所需的 b_module;选中某个节点时才按该模块懒加载其字段、权限、翻译及用户分配数据。
|
||||
|
||||
## 核心特性
|
||||
|
||||
- 页面挂载仅加载模块树(b_module),用于渲染树、上级模块选项与编码重复校验。
|
||||
- 点击/右键树节点时才加载该模块的字段定义、操作权限、多语言翻译、用户模块/权限分配,并构建编辑草稿。
|
||||
- 保存、删除、刷新后只刷新树与「当前选中模块」的详情,不再全量加载。
|
||||
- 新建模块、取消编辑保持原交互;保存/删除的级联逻辑在懒加载下依然正确。
|
||||
|
||||
## 技术栈
|
||||
|
||||
- 前端:Vue 3 `<script setup>` + Vite + antdv-next + Pinia(沿用现有项目栈)
|
||||
- 数据访问:复用通用端点 `loadDataApi(viewName, searchCondition, orderBy)`(POST `/api/data/loaddata`),不新增后端 URL
|
||||
- 复用现有组件 `ModuleTreePanel` / `ModuleBasicInfoPanel` / `ModuleFieldDefinitionPanel` / `ModulePowerPanel`,仅调整 `index.vue` 的加载编排
|
||||
|
||||
## 实现方案
|
||||
|
||||
### 策略
|
||||
|
||||
将当前的 `loadAll()` 拆分为「树加载」与「模块详情懒加载」两步:
|
||||
|
||||
1. `loadModules()`:仅 `loadDataApi("b_module", null, "b_xh ASC")` 填充 `data.modules`(树、上级选项、编码校验所需)。
|
||||
2. `loadModuleDetail(moduleCode)`:先定位 `data.modules` 中的模块,再按 `b_module_id` 条件并发加载 `b_module_field`、`b_module_power`、`b_i18n`(按模块编码过滤)、`b_user_module`;随后以已加载的权限 id 列表为条件加载 `b_user_power`。加载完成后调用现有 `selectModule(moduleCode)` 从已加载的 `data` 构建 `draft`。
|
||||
3. 树节点 `@select` / 右键 `@select` 改为调用 `loadModuleDetail`;`@refresh`、保存后、删除后统一复用 `loadModules()` +(必要时)`loadModuleDetail(selectedKey)`。
|
||||
|
||||
### 关键技术决策
|
||||
|
||||
- **翻译按模块过滤**:多语言键规则为 `module.<code>`、`field.<code>.<字段>`、`power.<code>.<权限>`。用条件 `b_key = 'module.<code>' OR b_key LIKE 'field.<code>.%' OR b_key LIKE 'power.<code>.%'` 在后端过滤,避免一次性加载全量 b_i18n。`.%` 后缀确保不会被 `m_page` 误匹配到 `m_page_x` 等。code 来自数据库且为小写字母数字下划线,仍做单引号转义防御。
|
||||
- **用户分配按当前模块加载**:`b_user_module` 用 `b_module_id = <id>` 过滤;`b_user_power` 依赖权限 id,故在 powers 加载完成后用 `b_power_id IN (...)` 二次加载。保证 `saveModule` 的权限回收与 `deleteModule` 的级联删除逻辑数据完整。
|
||||
- **保留草稿构建逻辑**:`selectModule` 仅负责从已加载的 `data`(fields/powers/translations)按模块过滤并构建 `draft`,不含网络请求,最小化改动面;`resetDraft` 直接复用它从已加载数据重建。
|
||||
- **不回归**:`parentOptions`/`directoryParentOptions`/`validate()` 依赖 `data.modules`(已加载);新建、取消、保存、删除交互保持不变。
|
||||
|
||||
### 性能与可靠性
|
||||
|
||||
- 挂载请求由 6 个并发降为 1 个(b_module),点击节点时按模块加载 4~5 个按需查询,随模块规模线性而非全量。
|
||||
- `loading` 复用现有 `a-spin` 同时驱动树刷新按钮与右侧工作区,加载期间提供反馈。
|
||||
- 对 `handleSelect` 增加 `loading` 守卫,避免并发重复加载。
|
||||
- bigint 的 `b_module_id`/`b_power_id` 以字符串拼接条件(与原 `diffRows` 中 `String(item.b_id)` 处理一致),避免精度问题。
|
||||
|
||||
## 实现注意
|
||||
|
||||
- 仅修改 `fms-vue/src/views/module-management/index.vue`,不触碰 `api.js`、通用端点、全局组件与其他页面。
|
||||
- `data` 响应式结构(modules/fields/powers/translations/userModules/userPowers)保持不变;`clearDraft` 仍负责清空选中态与详情。
|
||||
- 删除当前模块后 `selectedKey` 对应的模块已从 `data.modules` 移除,应走 `clearDraft` 分支;删除非当前模块则重新加载当前详情。
|
||||
- 新建模块期间刷新:只刷新树(loadModules),不要因 `selectedKey` 不存在而清空新建草稿。
|
||||
|
||||
## 架构设计
|
||||
|
||||
```mermaid
|
||||
flowchart TD
|
||||
A[onMounted] --> B[loadModules 仅 b_module]
|
||||
B --> C[渲染模块树]
|
||||
D[点击/右键树节点] --> E[loadModuleDetail 按模块懒加载]
|
||||
E --> F[并发: b_module_field / b_module_power / b_i18n / b_user_module]
|
||||
F --> G[按权限id加载 b_user_power]
|
||||
G --> H[selectModule 构建 draft]
|
||||
H --> I[右侧展示详情]
|
||||
J[保存/删除/刷新] --> B
|
||||
J --> K{有选中且非新建?}
|
||||
K -->|是| E
|
||||
K -->|否| L[clearDraft]
|
||||
```
|
||||
|
||||
## 目录结构
|
||||
|
||||
```
|
||||
fms-vue/src/views/module-management/
|
||||
└── index.vue # [MODIFY] 模块管理主视图。拆分 loadAll 为 loadModules(仅加载 b_module 树数据)与
|
||||
# loadModuleDetail(按选中模块懒加载字段/权限/翻译/用户分配并构建 draft);
|
||||
# 新增 translationSearchCondition 按模块编码过滤 b_i18n;
|
||||
# 树 @select/@refresh、onMounted、saveModule/deleteModule 末尾统一调用新加载函数;
|
||||
# 保留 selectModule/resetDraft/clearDraft 与级联保存删除逻辑。
|
||||
```
|
||||
|
||||
## 关键代码结构
|
||||
|
||||
```js
|
||||
// 按模块编码过滤多语言键(精确前缀,避免误匹配)
|
||||
function translationSearchCondition(code) {
|
||||
const c = String(code).replace(/'/g, "''");
|
||||
return `b_key = 'module.${c}' OR b_key LIKE 'field.${c}.%' OR b_key LIKE 'power.${c}.%'`;
|
||||
}
|
||||
|
||||
// 树加载:仅 b_module
|
||||
async function loadModules() { /* loadDataApi("b_module", null, "b_xh ASC") -> data.modules */ }
|
||||
|
||||
// 详情懒加载:选中节点时按模块加载字段/权限/翻译/用户分配
|
||||
async function loadModuleDetail(moduleCode) {
|
||||
const module = data.modules.find((i) => i.b_code === moduleCode);
|
||||
if (!module) return;
|
||||
// 并发加载 b_module_field(b_module_id=id)、b_module_power(b_module_id=id)、
|
||||
// b_i18n(translationSearchCondition(code))、b_user_module(b_module_id=id)
|
||||
// 再以权限 id 列表加载 b_user_power(b_power_id IN (...))
|
||||
// 写入 data 后调用 selectModule(moduleCode)
|
||||
}
|
||||
```
|
||||
Reference in new issue
Block a user