From e22f6550ae0c63b951103fdf4e4d2b9d83c5b01e Mon Sep 17 00:00:00 2001 From: hongawen <83944980@qq.com> Date: Wed, 15 Apr 2026 09:35:54 +0800 Subject: [PATCH] =?UTF-8?q?refactor(projects):=20=E9=A1=B5=E9=9D=A2?= =?UTF-8?q?=E5=B8=83=E5=B1=80=E8=B0=83=E6=95=B4=E4=B8=BArdms=E9=A3=8E?= =?UTF-8?q?=E6=A0=BC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- AGENTS.md | 224 ++++++++++ route.json | 393 ++++++++++++++++++ src/layouts/base-layout/index.vue | 26 +- src/layouts/context/index.ts | 7 +- src/layouts/modules/global-logo/index.vue | 9 +- src/layouts/modules/global-menu/index.vue | 7 +- .../modules/horizontal-mix-menu.vue | 232 ++++++++++- src/layouts/modules/global-sider/index.vue | 4 +- src/layouts/modules/theme-drawer/index.vue | 2 - .../theme-drawer/modules/dark-mode.vue | 2 +- .../modules/theme-drawer/modules/page-fun.vue | 23 +- src/store/modules/app/index.ts | 4 +- src/store/modules/theme/index.ts | 12 +- src/theme/settings.ts | 2 +- src/typings/api/system-manage.d.ts | 26 +- src/typings/components.d.ts | 1 + .../role/modules/role-resource-panel.vue | 21 +- .../system/user-management-relation/index.vue | 103 ++--- .../modules/relation-operate-dialog.vue | 12 +- .../modules/relation-search.vue | 8 +- src/views/system/user/index.vue | 60 +-- 21 files changed, 990 insertions(+), 188 deletions(-) create mode 100644 AGENTS.md create mode 100644 route.json diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 0000000..0dd7087 --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,224 @@ +# AGENTS.md + +本文件为后续编码代理提供 `cn-rdms-web` 的稳定仓库上下文。 +在修改代码前请先阅读。 + +## 适用范围 + +本说明适用于以 `C:\code\gitea\rdms\cn-rdms-web` 为根目录的整个仓库。 + +描述仓库现状时,以当前代码、当前配置、当前文档中可直接验证的事实为准;除非用户明确要求,不引入历史实现、过渡方案或猜测来解释当前行为。 + +默认回答保持精简,优先给结论、改动点、验证方式和必要风险;如果用户只要求分析、审阅或方案,就停留在分析层,不主动扩展到实现层。 + +## 交互与执行原则 + +- 进入实施阶段前,先说明目标、涉及模块、预计改动点和验证方式。 +- 先定义验证方式,再执行修改和校验;如果没有实际运行命令,需要明确说明只做了静态检查。 +- 只在当前任务需要的最小范围内改动,避免把无关重构混入同一次修改。 + +## 项目概览 + +- 应用类型:RDMS 系统的 Vue 3 后台前端 +- 包管理器:`pnpm` +- 运行时与工具链:Vite 7、TypeScript、Pinia、Element Plus、UnoCSS +- 工作区包位于 `packages/*`,通过 `@sa/*` 引用 +- Node 版本要求:`>=20.19.0` +- pnpm 版本要求:`>=8.7.0` + +## 项目骨架主线 + +当前项目不是边写边拼的页面集合,而是已经形成闭环的后台前端骨架。后续改动优先顺着这几条主线做,而不是平行再起一套: + +- 路由来源统一:页面文件与自定义路由作为源头,经 `elegant-router` 生成路由产物,再由 `build/plugins/router.ts` 集中补齐 `meta` +- 权限入口统一:常量路由和权限路由明确分流,`route store` 负责初始化、菜单生成、缓存路由和面包屑 +- 请求入口统一:所有业务请求默认走 `src/service/request/index.ts` +- 页面套路统一:列表页通常拆为搜索区、表格区、操作弹层/抽屉和 `modules/*` 子组件 +- 衍生资产统一:页面资源白名单从路由结构生成,不手工维护第二份页面清单 + +## 环境与构建说明 + +- Vite 路径别名:`@` -> `src` +- Vite 路径别名:`~` -> 仓库根目录 +- 开发服务器默认端口:`9527` +- 预览服务器默认端口:`9725` +- 环境文件包括 `.env`、`.env.dev`、`.env.prod` + +## 关键目录与文件 + +- `src/views`:业务页面 +- `src/components`:共享组件 +- `src/layouts`:应用壳、头部、侧栏、菜单、标签页、主题抽屉 +- `src/store/modules`:Pinia 模块,包含 app、auth、route、tab、theme +- `src/service/api`:接口封装与请求参数拼装 +- `src/service/request`:统一请求实例、鉴权头、加密、错误处理、token 刷新 +- `src/router/routes`:自定义路由定义 +- `src/router/elegant`:自动生成的路由产物 +- `src/theme/settings.ts`:默认主题与布局设置 +- `build/plugins/router.ts`:elegant-router 配置与路由元信息生成逻辑 +- `src/hooks/common/table.ts`:列表页表格 hook 主入口 +- `src/hooks/common/form.ts`:表单校验与表单实例 hook +- `src/styles/scss/element-plus.scss`:当前项目表格、弹层、按钮、表单密度与公共壳样式标准 +- `packages/*`:项目内本地共享库 +- `docs/`:当前工作上下文的一部分,做架构级、权限级、页面规范级改动前优先查阅 + +## 生成文件 + +除非有非常明确的理由并且同步维护生成流程,否则不要手工修改生成文件。 + +- `src/router/elegant/imports.ts` +- `src/router/elegant/routes.ts` +- `src/router/elegant/transform.ts` +- `src/typings/elegant-router.d.ts` +- `src/typings/components.d.ts` +- `docs/frontend-page-resource-manifest.json` + +如果路由生成产物过期或不一致,执行 `pnpm gen-route`。 +如果页面资源清单需要同步,执行 `pnpm gen:page-resource-manifest`。 + +## 路由与导航开发口径 + +- 新增业务页面时,优先通过页面文件与 `build/plugins/router.ts` 补齐路由,不要手工在多个位置重复注册同一页面。 +- 路由 `meta` 的中心落点是 `build/plugins/router.ts`;新增业务页的 `icon`、`order`、`roles`、`keepAlive` 优先在那里集中维护。 +- 当前代码链路仍保留 `i18nKey` 兼容字段,但它是兼容保留项,不是新增业务页面必须补齐的默认要求。 +- `meta.constant = true` 的路由属于常量路由;其余默认属于权限路由。 +- 常量路由维护入口优先是 `build/plugins/router.ts` 和 `src/router/routes/custom-routes.ts`,不要把常量路由散落到业务页面逻辑里。 +- 菜单图标约定属于路由契约的一部分:`meta.icon` 表示 Iconify 图标,`meta.localIcon` 表示本地 SVG 图标;不要混用字段语义。 + +## 分层职责约束 + +### `src/views` + +- 页面层负责页面编排、交互状态、表单行为和对 store/service 的组合调用。 +- 不要在页面组件里散落 URL 拼接、token 注入、统一错误提示或权限路由推导逻辑。 + +### `src/components` + +- 共享组件负责可复用 UI 或局部业务部件。 +- 不要把只服务于单个页面的复杂流程长期堆在公共组件目录中。 + +### `src/service/api` + +- API 层负责接口封装、请求参数归一化、查询字符串拼装和返回类型对齐。 +- 不要在 `views`、`store` 或 `components` 中重复手写同一接口地址和参数序列化逻辑。 + +### `src/service/request` + +- 请求层负责统一请求实例、鉴权头、接口加密、成功码判定、token 刷新和通用错误处理。 +- 除非任务明确需要,不要平行引入新的 `axios`/`fetch` 调用链绕开现有封装。 + +### `src/store/modules` + +- Store 负责跨页面共享状态,例如认证、路由、标签页、主题、布局和全局 UI 状态。 +- 临时性的页面局部状态优先留在页面组件或 composable 中,不要无边界堆进全局 store。 + +### `src/router` 与 `build/plugins/router.ts` + +- 路由、菜单、权限标识、首页配置和路由元信息优先沿用当前 elegant-router 与 route store 链路。 +- 不要只在页面里临时写条件分支来替代正式的路由、菜单或权限配置。 + +### `src/layouts` 与 `src/theme` + +- 布局壳和主题设置是全局行为源头,相关改动要同时检查布局组件、theme store 和默认设置。 +- 不要在业务页面里复制一套平行的布局状态或主题状态。 + +## 业务页面开发风格 + +- 页面组件保持“编排层薄”。页面文件主要负责搜索参数、表格 hook、列定义、弹层开关、接口调用编排,不把大量表单细节和重复交互直接堆在页面根组件里。 +- 列表页优先拆出同目录下的 `modules/*` 子组件,例如搜索组件、操作弹层、详情抽屉、资源面板等。 +- 系统管理下现有 `user`、`role`、`menu`、`dict` 页面可以作为参考实现,新增同类页面优先沿用它们的拆分方式。 +- 搜索组件优先复用 `src/components/custom/table-search-panel.vue` 作为外壳。搜索模块本身应尽量只接收 `model`,只向外发出 `reset` / `search`,不直接承载列表请求逻辑。 +- 列表能力优先复用 `src/hooks/common/table.ts` 中的 `useUIPaginatedTable`、`useTableOperate`、`defaultTransform`。 +- 表单能力优先复用 `src/hooks/common/form.ts` 中的 `useForm`、`useFormRules`。 +- 当前项目的真实业务口径是“内网中文优先”。新增业务页不必为了形式强行补全国际化键;但如果是在已有大量 `$t(...)` 的页面或模块内继续开发,优先保持该局部代码风格一致,不要半页中文直写、半页国际化混用。 + +## 表格、搜索区与操作列约束 + +- 搜索区按钮组保持在最右侧;存在折叠项时,按钮顺序保持为“展开/收起 -> 重置 -> 查询”。 +- 不要在每个页面重新拼一套搜索区骨架,优先延续 `TableSearchPanel` 的结构和交互。 +- 表格操作列优先复用 `src/components/custom/business-table-action-cell.tsx`。 +- 操作数 `<= 2` 时默认直出;操作数 `> 2` 时优先收敛为 `1 个直出主按钮 + 1 个更多按钮`。 +- 表格、按钮、弹层、表单的尺寸和间距标准优先由 `src/styles/scss/element-plus.scss` 和公共组件承接,不在业务页面散落写新的局部尺寸作为事实标准。 + +## 表单与弹层约束 + +- 新增、编辑能力优先沿用 `ElDialog / ElDrawer / ElForm / ElScrollbar / #footer` 这一套标准组合,不额外创造新的弹层交互模型。 +- 轻中量表单优先复用 `src/components/custom/business-form-dialog.vue`;字段较多、需要保留列表上下文或承载重型控件时,再考虑 `src/components/custom/business-form-drawer.vue`。 +- 表单分组优先复用 `src/components/custom/business-form-section.vue`。 +- 现有公共壳组件已内置尺寸预设:`dialog` 的 `sm/md/lg` 对应 `520px/640px/720px`,`drawer` 的 `md/lg/xl` 对应 `480px/720px/960px`;优先使用预设值而不是页面内重复硬编码宽度。 +- 常规 CRUD 表单优先使用 `label-position="top"`、`ElRow + ElCol` 双列布局、`gutter=16`;普通字段优先 `span=12`,长文本或重量级字段优先 `span=24`。 +- 底部按钮顺序固定为“取消 -> 确认”,并保持右对齐。 +- 单选组和开关类字段优先复用仓库既有样式钩子,例如 `business-form-radio-group`、`business-form-switch-field`。 + +## 接口、路由与权限约束 + +- 默认沿用 `src/service/request/index.ts` 中现有请求链路,不要另造一套鉴权、加密、错误处理或 token 刷新机制。 +- 接口前缀、服务常量优先复用现有常量定义,例如 `src/constants/service.ts`。 +- 后端契约变化时,至少同步检查 `src/service/api/*`、`src/typings/api/*`、相关页面调用和说明文档是否一致。 +- 涉及路由、菜单、权限的改动时,同时检查 `build/plugins/router.ts`、`src/router/routes/*`、`src/store/modules/route/*` 和相关文档。 +- 对于可再生的路由产物,优先修改源配置并执行 `pnpm gen-route`,不要把手工修补生成文件当成常规方案。 + +## 页面资源与菜单目录约束 + +- 页面组件键、页面资源、菜单目录是三层不同概念,不要把它们当成同一个值。 +- `component` 决定“渲染哪个页面组件”;菜单目录决定“挂在哪个业务目录下”和最终 URL;页面资源主要用于从白名单中选择并回填组件信息。 +- 不要因为组件键是 `view.system_dict`,就推导它只能挂在 `/system/dict`;同一个页面组件允许挂在新的业务目录下复用。 +- 页面资源白名单中的标准路径是参考路径,不应反向覆盖当前菜单树已经确定的最终 URL。 +- 涉及菜单编辑器或页面资源选择逻辑时,优先保证“组件可解析、资源合法、最终 URL 由菜单树决定”,不要强绑页面资源标准路径和父级目录前缀。 + +## 代码约定 + +- 优先使用现有别名导入(`@/...`、`~/...`),避免过长的相对路径。 +- 保持与 TypeScript 严格模式兼容。 +- 遵循仓库现有的 Vue SFC 风格:`script setup`、类型化 store、职责单一的小型 composable/helper。 +- 修改界面时优先延续 `src/layouts` 和 `src/theme` 中已有的 UI 模式,不要平行引入另一套设计体系。 +- 注释保持克制,只在代码本身不够直观时补充必要说明。 + +## 注释与编码 + +- 新增或修改代码时,关键分支、关键约束和非直观实现可以补充简洁中文注释。 +- 不要为了省事删除原有有效注释,也不要添加没有信息量的注释。 +- 写入中文内容时保持 UTF-8 编码,并自行确认显示正常;不要用改成英文来规避编码问题。 + +## 校验建议 + +对有实际影响的代码改动,优先执行: + +- `pnpm typecheck` +- `pnpm lint` + +如果改动涉及路由,额外执行: + +- `pnpm gen-route` + +如果改动影响页面资源清单、菜单资源选择或页面白名单,额外执行: + +- `pnpm gen:page-resource-manifest` + +静态校验时,至少自查以下几点: + +- 调用链是否闭环,改动是否落在正确的分层位置 +- 路由、菜单、权限标识、主题状态或资源注册是否前后一致 +- 改动范围是否控制在当前任务所需的最小集合内 +- 文档、类型定义、接口封装或生成产物是否需要同步更新 + +## 提交与脚本约束 + +- `pre-commit` 会执行 `pnpm typecheck && pnpm lint && git diff --exit-code`,因此“代码能跑”不等于“可以提交”。 +- `pnpm lint` 实际会执行 `eslint . --fix`;提交失败后要检查是否有被自动修复但尚未重新暂存的文件。 +- 提交规范说明以 `docs/前端提交规范与示例.md` 为准;最稳妥的提交方式是执行 `pnpm commit:zh`,按交互选择 `type`、`scope` 和 `description`。 +- `commit-msg` 钩子会校验 Conventional Commits;推荐使用 `pnpm commit:zh` 生成提交信息。 +- 如果手动提交,执行 `git commit -m "type(scope): 描述"`,并确保 `type`、`scope`、描述写法与 `docs/前端提交规范与示例.md` 保持一致。 +- 提交信息基础格式遵循 `type(scope): 描述`。 +- 写 Node ESM 脚本时,避免沿用 `__filename`、`__dirname` 这类下划线悬挂命名。 +- 能并发的批量异步任务优先 `Promise.all(...)`,不要默认在循环体里直接 `await`。 +- 手写 `new Promise(...)` 时优先使用 block 写法,不要把 executor 写成隐式返回值的单表达式箭头函数。 +- 一个函数如果开始同时承担“判断 + 转换 + 组装 + 递归”,优先拆 helper,避免把复杂度堆到单个函数里。 + +## 代理工作说明 + +- 编辑前先检查当前 `git diff`,仓库中可能已经存在用户进行中的修改。 +- 在工作树不干净时,不要回退与当前任务无关的变更。 +- 修改布局或主题行为时,同时检查 `src/layouts/*` 和 `src/store/modules/theme/*`,因为相关逻辑分散在界面层和状态层。 +- 修改路由或菜单时,同时检查 `build/plugins/router.ts` 和 `src/router/routes/*`。 +- 做架构级、权限级或页面规范级修改前,优先查阅 `docs/` 中现有说明,避免与当前文档约定冲突。 diff --git a/route.json b/route.json new file mode 100644 index 0000000..3175188 --- /dev/null +++ b/route.json @@ -0,0 +1,393 @@ +{ + "code": 0, + "msg": "", + "data": { + "routes": [ + { + "id": "900000", + "name": "system", + "path": "/system", + "component": "layout.base", + "redirect": "/system/user", + "props": null, + "meta": { + "title": "权限中心", + "i18nKey": null, + "icon": "carbon:cloud-service-management", + "localIcon": null, + "order": 9, + "keepAlive": false, + "hideInMenu": false, + "activeMenu": null, + "multiTab": null, + "fixedIndexInTab": null + }, + "children": [ + { + "id": "900001", + "name": "system_user", + "path": "/system/user", + "component": "view.system_user", + "redirect": null, + "props": null, + "meta": { + "title": "用户管理", + "i18nKey": null, + "icon": "ic:round-manage-accounts", + "localIcon": null, + "order": 1, + "keepAlive": false, + "hideInMenu": false, + "activeMenu": null, + "multiTab": null, + "fixedIndexInTab": null + }, + "children": null + }, + { + "id": "900002", + "name": "system_role", + "path": "/system/role", + "component": "view.system_role", + "redirect": null, + "props": null, + "meta": { + "title": "角色管理", + "i18nKey": null, + "icon": "carbon:user-role", + "localIcon": null, + "order": 2, + "keepAlive": false, + "hideInMenu": false, + "activeMenu": null, + "multiTab": null, + "fixedIndexInTab": null + }, + "children": null + }, + { + "id": "2036723586735665153", + "name": "system_post", + "path": "/system/post", + "component": "view.system_post", + "redirect": null, + "props": null, + "meta": { + "title": "岗位管理", + "i18nKey": null, + "icon": "mdi:account-group-outline", + "localIcon": null, + "order": 3, + "keepAlive": false, + "hideInMenu": false, + "activeMenu": null, + "multiTab": null, + "fixedIndexInTab": null + }, + "children": null + }, + { + "id": "900003", + "name": "system_menu", + "path": "/system/menu", + "component": "view.system_menu", + "redirect": null, + "props": null, + "meta": { + "title": "菜单管理", + "i18nKey": null, + "icon": "material-symbols:route", + "localIcon": null, + "order": 4, + "keepAlive": false, + "hideInMenu": false, + "activeMenu": null, + "multiTab": null, + "fixedIndexInTab": null + }, + "children": null + } + ] + }, + { + "id": "1", + "name": "legacy-system", + "path": "/legacy-system", + "component": "layout.base", + "redirect": "/legacy-system/dict", + "props": null, + "meta": { + "title": "系统管理", + "i18nKey": null, + "icon": "ep:tools", + "localIcon": null, + "order": 10, + "keepAlive": true, + "hideInMenu": false, + "activeMenu": null, + "multiTab": null, + "fixedIndexInTab": null + }, + "children": [ + { + "id": "900004", + "name": "system_dict", + "path": "/legacy-system/dict", + "component": "view.system_dict", + "redirect": null, + "props": null, + "meta": { + "title": "字典管理", + "i18nKey": null, + "icon": "mdi:book-open-page-variant-outline", + "localIcon": null, + "order": 4, + "keepAlive": true, + "hideInMenu": false, + "activeMenu": null, + "multiTab": null, + "fixedIndexInTab": null + }, + "children": null + }, + { + "id": "108", + "name": "legacy_system_log", + "path": "/legacy-system/log", + "component": "layout.base", + "redirect": "/legacy-system/log/operate-log", + "props": null, + "meta": { + "title": "审计日志", + "i18nKey": null, + "icon": "ep:document-copy", + "localIcon": null, + "order": 9, + "keepAlive": true, + "hideInMenu": false, + "activeMenu": null, + "multiTab": null, + "fixedIndexInTab": null + }, + "children": [ + { + "id": "500", + "name": "SystemOperateLog", + "path": "/legacy-system/log/operate-log", + "component": "system/operatelog/index", + "redirect": null, + "props": null, + "meta": { + "title": "操作日志", + "i18nKey": null, + "icon": "ep:position", + "localIcon": null, + "order": 1, + "keepAlive": true, + "hideInMenu": false, + "activeMenu": null, + "multiTab": null, + "fixedIndexInTab": null + }, + "children": null + }, + { + "id": "501", + "name": "SystemLoginLog", + "path": "/legacy-system/log/login-log", + "component": "system/loginlog/index", + "redirect": null, + "props": null, + "meta": { + "title": "登录日志", + "i18nKey": null, + "icon": "ep:promotion", + "localIcon": null, + "order": 2, + "keepAlive": true, + "hideInMenu": false, + "activeMenu": null, + "multiTab": null, + "fixedIndexInTab": null + }, + "children": null + } + ] + } + ] + }, + { + "id": "2", + "name": "infra", + "path": "/infra", + "component": "layout.base", + "redirect": "/infra/log", + "props": null, + "meta": { + "title": "基础设施", + "i18nKey": null, + "icon": "ep:monitor", + "localIcon": null, + "order": 20, + "keepAlive": true, + "hideInMenu": false, + "activeMenu": null, + "multiTab": null, + "fixedIndexInTab": null + }, + "children": [ + { + "id": "1083", + "name": "infra_log", + "path": "/infra/log", + "component": "layout.base", + "redirect": "/infra/log/api-access-log", + "props": null, + "meta": { + "title": "API 日志", + "i18nKey": null, + "icon": "fa:tasks", + "localIcon": null, + "order": 4, + "keepAlive": true, + "hideInMenu": false, + "activeMenu": null, + "multiTab": null, + "fixedIndexInTab": null + }, + "children": [ + { + "id": "1078", + "name": "InfraApiAccessLog", + "path": "/infra/log/api-access-log", + "component": "infra/apiAccessLog/index", + "redirect": null, + "props": null, + "meta": { + "title": "访问日志", + "i18nKey": null, + "icon": "ep:place", + "localIcon": null, + "order": 1, + "keepAlive": true, + "hideInMenu": false, + "activeMenu": null, + "multiTab": null, + "fixedIndexInTab": null + }, + "children": null + }, + { + "id": "1084", + "name": "InfraApiErrorLog", + "path": "/infra/log/api-error-log", + "component": "infra/apiErrorLog/index", + "redirect": null, + "props": null, + "meta": { + "title": "错误日志", + "i18nKey": null, + "icon": "ep:warning-filled", + "localIcon": null, + "order": 2, + "keepAlive": true, + "hideInMenu": false, + "activeMenu": null, + "multiTab": null, + "fixedIndexInTab": null + }, + "children": null + } + ] + }, + { + "id": "1243", + "name": "infra_file", + "path": "/infra/file", + "component": "layout.base", + "redirect": "/infra/file/file-config", + "props": null, + "meta": { + "title": "文件管理", + "i18nKey": null, + "icon": "ep:files", + "localIcon": null, + "order": 6, + "keepAlive": true, + "hideInMenu": false, + "activeMenu": null, + "multiTab": null, + "fixedIndexInTab": null + }, + "children": [ + { + "id": "1237", + "name": "InfraFileConfig", + "path": "/infra/file/file-config", + "component": "infra/fileConfig/index", + "redirect": null, + "props": null, + "meta": { + "title": "文件配置", + "i18nKey": null, + "icon": "fa-solid:file-signature", + "localIcon": null, + "order": 0, + "keepAlive": true, + "hideInMenu": false, + "activeMenu": null, + "multiTab": null, + "fixedIndexInTab": null + }, + "children": null + }, + { + "id": "1090", + "name": "InfraFile", + "path": "/infra/file/file", + "component": "infra/file/index", + "redirect": null, + "props": null, + "meta": { + "title": "文件列表", + "i18nKey": null, + "icon": "ep:upload-filled", + "localIcon": null, + "order": 5, + "keepAlive": true, + "hideInMenu": false, + "activeMenu": null, + "multiTab": null, + "fixedIndexInTab": null + }, + "children": null + } + ] + }, + { + "id": "106", + "name": "InfraConfig", + "path": "/infra/config", + "component": "infra/config/index", + "redirect": null, + "props": null, + "meta": { + "title": "配置管理", + "i18nKey": null, + "icon": "fa:connectdevelop", + "localIcon": null, + "order": 8, + "keepAlive": true, + "hideInMenu": false, + "activeMenu": null, + "multiTab": null, + "fixedIndexInTab": null + }, + "children": null + } + ] + } + ], + "home": "system_user" + } +} diff --git a/src/layouts/base-layout/index.vue b/src/layouts/base-layout/index.vue index 14355e2..d1cfb82 100644 --- a/src/layouts/base-layout/index.vue +++ b/src/layouts/base-layout/index.vue @@ -16,18 +16,18 @@ defineOptions({ name: 'BaseLayout' }); const appStore = useAppStore(); const themeStore = useThemeStore(); -const { childLevelMenus, isActiveFirstLevelMenuHasChildren } = setupMixMenuContext(); +const { childLevelMenus } = setupMixMenuContext(); const GlobalMenu = defineAsyncComponent(() => import('../modules/global-menu/index.vue')); const layoutMode = computed(() => { const vertical: LayoutMode = 'vertical'; const horizontal: LayoutMode = 'horizontal'; - return themeStore.layout.mode.includes(vertical) ? vertical : horizontal; + return themeStore.layoutMode.includes(vertical) ? vertical : horizontal; }); const headerProps = computed(() => { - const { mode, reverseHorizontalMix } = themeStore.layout; + const mode = themeStore.layoutMode; const headerPropsConfig: Record = { vertical: { @@ -48,31 +48,26 @@ const headerProps = computed(() => { 'horizontal-mix': { showLogo: true, showMenu: true, - showMenuToggler: reverseHorizontalMix && isActiveFirstLevelMenuHasChildren.value + showMenuToggler: false } }; return headerPropsConfig[mode]; }); -const siderVisible = computed(() => themeStore.layout.mode !== 'horizontal'); +const siderVisible = computed(() => themeStore.layoutMode !== 'horizontal'); -const isVerticalMix = computed(() => themeStore.layout.mode === 'vertical-mix'); +const isVerticalMix = computed(() => themeStore.layoutMode === 'vertical-mix'); -const isHorizontalMix = computed(() => themeStore.layout.mode === 'horizontal-mix'); +const isHorizontalMix = computed(() => themeStore.layoutMode === 'horizontal-mix'); const siderWidth = computed(() => getSiderWidth()); const siderCollapsedWidth = computed(() => getSiderCollapsedWidth()); function getSiderWidth() { - const { reverseHorizontalMix } = themeStore.layout; const { width, mixWidth, mixChildMenuWidth } = themeStore.sider; - if (isHorizontalMix.value && reverseHorizontalMix) { - return isActiveFirstLevelMenuHasChildren.value ? width : 0; - } - let w = isVerticalMix.value || isHorizontalMix.value ? mixWidth : width; if (isVerticalMix.value && appStore.mixSiderFixed && childLevelMenus.value.length) { @@ -83,13 +78,8 @@ function getSiderWidth() { } function getSiderCollapsedWidth() { - const { reverseHorizontalMix } = themeStore.layout; const { collapsedWidth, mixCollapsedWidth, mixChildMenuWidth } = themeStore.sider; - if (isHorizontalMix.value && reverseHorizontalMix) { - return isActiveFirstLevelMenuHasChildren.value ? collapsedWidth : 0; - } - let w = isVerticalMix.value || isHorizontalMix.value ? mixCollapsedWidth : collapsedWidth; if (isVerticalMix.value && appStore.mixSiderFixed && childLevelMenus.value.length) { @@ -110,7 +100,7 @@ function getSiderCollapsedWidth() { :full-content="appStore.fullContent" :fixed-top="themeStore.fixedHeaderAndTab" :header-height="themeStore.header.height" - :tab-visible="themeStore.tab.visible" + :tab-visible="themeStore.tabVisible" :tab-height="themeStore.tab.height" :content-class="appStore.contentXScrollable ? 'overflow-x-hidden' : ''" :sider-visible="siderVisible" diff --git a/src/layouts/context/index.ts b/src/layouts/context/index.ts index 481d4ee..56ff229 100644 --- a/src/layouts/context/index.ts +++ b/src/layouts/context/index.ts @@ -8,7 +8,6 @@ import { useRouterPush } from '@/hooks/common/router'; export const { setupStore: setupMixMenuContext, useStore: useMixMenuContext } = useContext('mix-menu', useMixMenu); function useMixMenu() { - const route = useRoute(); const routeStore = useRouteStore(); const { selectedKey } = useMenu(); @@ -19,9 +18,9 @@ function useMixMenu() { } function getActiveFirstLevelMenuKey() { - const [firstLevelRouteName] = selectedKey.value.split('_'); + const [firstLevelMenuKey = ''] = routeStore.getSelectedMenuKeyPath(selectedKey.value); - setActiveFirstLevelMenuKey(firstLevelRouteName); + setActiveFirstLevelMenuKey(firstLevelMenuKey); } const allMenus = computed(() => routeStore.menus); @@ -49,7 +48,7 @@ function useMixMenu() { }); watch( - () => route.name, + [selectedKey, allMenus], () => { getActiveFirstLevelMenuKey(); }, diff --git a/src/layouts/modules/global-logo/index.vue b/src/layouts/modules/global-logo/index.vue index 70dfce7..28052eb 100644 --- a/src/layouts/modules/global-logo/index.vue +++ b/src/layouts/modules/global-logo/index.vue @@ -14,9 +14,12 @@ withDefaults(defineProps(), {