diff --git a/README.en-US.md b/README.en-US.md index f169ac4..a36ad42 100644 --- a/README.en-US.md +++ b/README.en-US.md @@ -51,7 +51,6 @@ http://localhost:8848/ ├── .codex/skills/ # Repository-local skill for keeping README.md in sync ├── .husky/ # Git hooks for lint-staged and commitlint ├── build/ # Vite plugin, CDN, compression, and build helpers -├── mock/ # Template mock data for login and async routes ├── public/ # Static assets and runtime platform config ├── src/ │ ├── api/ # HTTP API wrappers; business APIs live in access.ts @@ -65,7 +64,7 @@ http://localhost:8848/ │ ├── store/ # Pinia stores │ ├── style/ # Global styles and themes │ ├── utils/ # Auth, HTTP, storage, message, progress, and tree helpers -│ └── views/ # Pages; business pages live in employees, roles, stores +│ └── views/ # Pages; business pages live in employees, roles, stores, permissions ├── types/ # Global TypeScript declarations ├── AGENTS.md # Agent entrypoint that delegates to RTK.md ├── RTK.md # Repository collaboration rules @@ -96,7 +95,7 @@ Development mode proxies `/api` to `VITE_API_PROXY_TARGET`, defaulting to `http: Login uses `POST /api/auth/admin/login`, then the app validates the token through `GET /api/auth/me` and loads menus/actions through `GET /api/permissions/me`. The backend does not expose refresh-token; local `401` or token expiry clears auth state and redirects to `/login`. -List search, reset, pagination, status changes, deletes, and save refreshes should go through the API layer. Store and role pages re-fetch their list before local narrowing because those endpoints do not define keyword filters; employee list requests send `page`, `pageSize`, `storeId`, `status`, and `keyword` as needed. +List search, reset, pagination, status changes, deletes, and save refreshes should go through the API layer. Store, role, and employee list requests send their current filters and pagination to the backend. ## Documentation Sync Rule diff --git a/README.md b/README.md index 0747b1c..56fed31 100644 --- a/README.md +++ b/README.md @@ -51,7 +51,6 @@ http://localhost:8848/ ├── .husky/ # Git hooks,提交前执行 lint-staged,提交信息走 commitlint ├── .vscode/ # VS Code 推荐配置与 Vue 代码片段 ├── build/ # Vite 插件、CDN、压缩、构建信息与工具函数 -├── mock/ # pure-admin 模板保留的 mock 登录/路由数据 ├── public/ # 静态资源与运行时平台配置 ├── src/ # 前端源码 │ ├── api/ # HTTP API 封装,业务接口集中在 access.ts @@ -65,7 +64,7 @@ http://localhost:8848/ │ ├── store/ # Pinia store,含用户、权限、标签页、主题等模块 │ ├── style/ # 全局样式、主题、暗色模式、侧边栏和过渡样式 │ ├── utils/ # 鉴权、HTTP、缓存、消息、进度条、树处理等工具 -│ └── views/ # 页面视图,当前业务页在 employees、roles、stores +│ └── views/ # 页面视图,当前业务页在 employees、roles、stores、permissions ├── types/ # 全局类型声明、组件声明、路由声明和 Vue shim ├── AGENTS.md # Codex/Agent 入口规则,转到 RTK.md ├── RTK.md # 本仓库协作规则与 README 同步要求 @@ -91,7 +90,7 @@ http://localhost:8848/ | `pnpm preview:build` | 先构建再预览 | 一步完成打包验收 | | `pnpm typecheck` | 执行 `tsc --noEmit` 和 `vue-tsc --noEmit --skipLibCheck` | 类型检查 | | `pnpm lint` | 依次执行 ESLint、Prettier、Stylelint 自动修复 | 提交前整理代码 | -| `pnpm lint:eslint` | 对 `src`、`mock`、`build` 下 JS/TS/Vue 做 ESLint 修复 | 脚本或逻辑代码调整后 | +| `pnpm lint:eslint` | 对 `src`、`build` 下 JS/TS/Vue 做 ESLint 修复 | 脚本或逻辑代码调整后 | | `pnpm lint:prettier` | 格式化 `src` 下常见源码与文档格式 | 样式统一 | | `pnpm lint:stylelint` | 修复 HTML/Vue/CSS/SCSS 样式规则 | 样式文件调整后 | | `pnpm svgo` | 递归压缩 SVG | SVG 资源变更后 | @@ -102,9 +101,10 @@ http://localhost:8848/ - `src/views/stores/index.vue`: 门店管理,筛选、重置、启停、删除后都会重新调用接口,支持新增和编辑。 - `src/views/roles/index.vue`: 角色管理,搜索、重置、删除和保存后都会重新调用接口,支持角色编码校验。 - `src/views/employees/index.vue`: 员工管理,门店/状态/关键词筛选、重置、分页、启停、删除和保存后都会重新调用接口。 -- `src/api/access.ts`: 门店、角色、员工接口类型与 HTTP 方法封装。 -- `src/api/user.ts`: 登录、当前用户、当前权限菜单接口封装。 -- `src/router/modules/employees.ts`: 权限管理菜单入口,挂载门店、角色、员工三个页面。 +- `src/views/permissions/index.vue`: 权限策略,支持查看角色权限、按角色勾选权限点并保存到后端。 +- `src/api/access.ts`: 门店、角色、员工、权限策略和角色权限分配接口类型与 HTTP 方法封装。 +- `src/api/user.ts`: 登录、当前用户和当前权限菜单接口封装。 +- `src/router/modules/employees.ts`: 权限管理菜单入口,挂载门店、角色、员工、权限策略四个页面。 - `src/store/modules/user.ts`: 保存 JWT 登录态、当前用户、权限码和后端菜单动作权限。 ## 后端对接 @@ -119,12 +119,13 @@ http://localhost:8848/ - `POST /api/auth/admin/login` - `GET /api/auth/me` - `GET /api/permissions/me` -- `GET /api/stores`,管理列表会携带 `includeInactive`,搜索/重置会重新请求接口后按当前条件收敛结果 +- `GET /api/permissions/policies` +- `GET /api/stores`,门店管理列表会携带 `page`、`pageSize`,筛选时会携带 `status`、`keyword` - `GET /api/stores/:id` - `POST /api/stores` - `PATCH /api/stores/:id` - `DELETE /api/stores/:id` -- `GET /api/roles`,搜索/重置会重新请求接口后按关键词收敛结果 +- `GET /api/roles`,角色管理列表会携带 `page`、`pageSize`,筛选时会携带 `keyword`、`isSystem` - `GET /api/roles/:id` - `POST /api/roles` - `PATCH /api/roles/:id` @@ -136,7 +137,7 @@ http://localhost:8848/ - `PATCH /api/employees/:id/status` - `DELETE /api/employees/:id` -接口响应统一在 `src/api/access.ts` 中使用 `ApiResult` 或 `PaginatedData` 描述,页面层只消费 `result.data`,避免在视图里重复拼接接口路径。列表搜索、重置、分页和状态变更后的刷新都应通过接口层完成,不直接依赖页面内存里的旧列表。 +接口响应统一在 `src/api/access.ts` 中使用 `ApiResult` 或 `PaginatedData` 描述,页面层只消费 `result.data`,避免在视图里重复拼接接口路径。门店、角色、员工列表的搜索、重置、分页和状态变更后的刷新都应通过接口层完成,不直接依赖页面内存里的旧列表。 ## 登录与鉴权流程 @@ -144,7 +145,7 @@ http://localhost:8848/ 2. 前端保存后端返回的 `data.token`,并按 `expiresIn` 换算本地过期时间。 3. 登录成功后立即调用 `GET /api/auth/me` 校验当前账号,再调用 `GET /api/permissions/me` 获取权限码、菜单和动作权限。 4. 路由守卫会在刷新页面后重新拉取当前用户和权限,未登录或 token 过期会跳回 `/login`。 -5. 菜单按路由 `meta.permission` 与后端权限码过滤,按钮按 `store:manage`、`role:manage`、`employee:manage` 等权限码显隐。 +5. 菜单按路由 `meta.menuKey`、`meta.permission` 与 `/api/permissions/me` 返回的后端菜单过滤;按钮按后端菜单 `actions` 控制新增、编辑、启停和删除显隐。 6. 后端当前没有 refresh-token 接口,收到 `401` 或本地 token 过期时直接清理登录态并要求重新登录。 ## 配置说明 diff --git a/build/plugins.ts b/build/plugins.ts index 93f5981..a069cf3 100644 --- a/build/plugins.ts +++ b/build/plugins.ts @@ -11,7 +11,6 @@ import removeNoMatch from "vite-plugin-router-warn"; import { visualizer } from "rollup-plugin-visualizer"; import removeConsole from "vite-plugin-remove-console"; import { codeInspectorPlugin } from "code-inspector-plugin"; -import { vitePluginFakeServer } from "vite-plugin-fake-server"; export function getPluginsList( VITE_CDN: boolean, @@ -40,13 +39,6 @@ export function getPluginsList( * vite-plugin-router-warn只在开发环境下启用,只处理vue-router文件并且只在服务启动或重启时运行一次,性能消耗可忽略不计 */ removeNoMatch(), - // mock支持 - vitePluginFakeServer({ - logger: false, - include: "mock", - infixName: false, - enableProd: true - }), // svg组件化支持 svgLoader(), // 自动按需加载图标 diff --git a/mock/asyncRoutes.ts b/mock/asyncRoutes.ts deleted file mode 100644 index 2a99183..0000000 --- a/mock/asyncRoutes.ts +++ /dev/null @@ -1,69 +0,0 @@ -// 模拟后端动态生成路由 -import { defineFakeRoute } from "vite-plugin-fake-server/client"; - -/** - * roles:页面级别权限,这里模拟二种 "admin"、"common" - * admin:管理员角色 - * common:普通角色 - */ -const permissionRouter = { - path: "/permission", - meta: { - title: "权限管理", - icon: "ep:lollipop", - rank: 10 - }, - children: [ - { - path: "/permission/page/index", - name: "PermissionPage", - meta: { - title: "页面权限", - roles: ["admin", "common"] - } - }, - { - path: "/permission/button", - meta: { - title: "按钮权限", - roles: ["admin", "common"] - }, - children: [ - { - path: "/permission/button/router", - component: "permission/button/index", - name: "PermissionButtonRouter", - meta: { - title: "路由返回按钮权限", - auths: [ - "permission:btn:add", - "permission:btn:edit", - "permission:btn:delete" - ] - } - }, - { - path: "/permission/button/login", - component: "permission/button/perms", - name: "PermissionButtonLogin", - meta: { - title: "登录接口返回按钮权限" - } - } - ] - } - ] -}; - -export default defineFakeRoute([ - { - url: "/get-async-routes", - method: "get", - response: () => { - return { - success: true, - data: [permissionRouter] - }; - } - } -]); diff --git a/mock/login.ts b/mock/login.ts deleted file mode 100644 index 55897d8..0000000 --- a/mock/login.ts +++ /dev/null @@ -1,42 +0,0 @@ -// 根据角色动态生成路由 -import { defineFakeRoute } from "vite-plugin-fake-server/client"; - -export default defineFakeRoute([ - { - url: "/login", - method: "post", - response: ({ body }) => { - if (body.username === "admin") { - return { - success: true, - data: { - avatar: "https://avatars.githubusercontent.com/u/44761321", - username: "admin", - nickname: "小铭", - // 一个用户可能有多个角色 - roles: ["admin"], - // 按钮级别权限 - permissions: ["*:*:*"], - accessToken: "eyJhbGciOiJIUzUxMiJ9.admin", - refreshToken: "eyJhbGciOiJIUzUxMiJ9.adminRefresh", - expires: "2030/10/30 00:00:00" - } - }; - } else { - return { - success: true, - data: { - avatar: "https://avatars.githubusercontent.com/u/52823142", - username: "common", - nickname: "小林", - roles: ["common"], - permissions: ["permission:btn:add", "permission:btn:edit"], - accessToken: "eyJhbGciOiJIUzUxMiJ9.common", - refreshToken: "eyJhbGciOiJIUzUxMiJ9.commonRefresh", - expires: "2030/10/30 00:00:00" - } - }; - } - } - } -]); diff --git a/mock/refreshToken.ts b/mock/refreshToken.ts deleted file mode 100644 index 34d0e87..0000000 --- a/mock/refreshToken.ts +++ /dev/null @@ -1,27 +0,0 @@ -import { defineFakeRoute } from "vite-plugin-fake-server/client"; - -// 模拟刷新token接口 -export default defineFakeRoute([ - { - url: "/refresh-token", - method: "post", - response: ({ body }) => { - if (body.refreshToken) { - return { - success: true, - data: { - accessToken: "eyJhbGciOiJIUzUxMiJ9.newAdmin", - refreshToken: "eyJhbGciOiJIUzUxMiJ9.newAdminRefresh", - // `expires`选择这种日期格式是为了方便调试,后端直接设置时间戳或许更方便(每次都应该递增)。如果后端返回的是时间戳格式,前端开发请来到这个目录`src/utils/auth.ts`,把第`38`行的代码换成expires = data.expires即可。 - expires: "2030/10/30 23:59:59" - } - }; - } else { - return { - success: false, - data: {} - }; - } - } - } -]); diff --git a/package.json b/package.json index 3404689..9723b28 100644 --- a/package.json +++ b/package.json @@ -14,7 +14,7 @@ "typecheck": "tsc --noEmit && vue-tsc --noEmit --skipLibCheck", "svgo": "svgo -f . -r", "clean:cache": "rimraf .eslintcache && rimraf pnpm-lock.yaml && rimraf node_modules && pnpm store prune && pnpm install", - "lint:eslint": "eslint --cache --max-warnings 0 \"{src,mock,build}/**/*.{vue,js,ts,tsx}\" --fix", + "lint:eslint": "eslint --cache --max-warnings 0 \"{src,build}/**/*.{vue,js,ts,tsx}\" --fix", "lint:prettier": "prettier --write \"src/**/*.{js,ts,json,tsx,css,scss,vue,html,md}\"", "lint:stylelint": "stylelint --cache --fix \"**/*.{html,vue,css,scss}\" --cache-location node_modules/.cache/stylelint/", "lint": "pnpm lint:eslint && pnpm lint:prettier && pnpm lint:stylelint", @@ -79,7 +79,6 @@ "@commitlint/config-conventional": "^20.0.0", "@commitlint/types": "^20.0.0", "@eslint/js": "^9.38.0", - "@faker-js/faker": "^10.1.0", "@iconify/json": "^2.2.400", "@iconify/vue": "4.2.0", "@tailwindcss/vite": "^4.1.16", @@ -122,7 +121,6 @@ "vite": "^7.1.12", "vite-plugin-cdn-import": "^1.0.1", "vite-plugin-compression": "^0.5.1", - "vite-plugin-fake-server": "^2.2.0", "vite-plugin-remove-console": "^2.2.0", "vite-plugin-router-warn": "^1.0.0", "vite-svg-loader": "^5.1.0", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 2b5b1a0..c1b2b2d 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -93,9 +93,6 @@ importers: '@eslint/js': specifier: ^9.38.0 version: 9.38.0 - '@faker-js/faker': - specifier: ^10.1.0 - version: 10.1.0 '@iconify/json': specifier: ^2.2.400 version: 2.2.400 @@ -222,9 +219,6 @@ importers: vite-plugin-compression: specifier: ^0.5.1 version: 0.5.1(vite@7.1.12(@types/node@20.19.23)(jiti@2.6.1)(lightningcss@1.30.2)(sass@1.93.2)(yaml@2.8.1)) - vite-plugin-fake-server: - specifier: ^2.2.0 - version: 2.2.0 vite-plugin-remove-console: specifier: ^2.2.0 version: 2.2.0 @@ -497,252 +491,126 @@ packages: peerDependencies: vue: ^3.2.0 - '@esbuild/aix-ppc64@0.24.2': - resolution: {integrity: sha512-thpVCb/rhxE/BnMLQ7GReQLLN8q9qbHmI55F4489/ByVg2aQaQ6kbcLb6FHkocZzQhxc4gx0sCk0tJkKBFzDhA==} - engines: {node: '>=18'} - cpu: [ppc64] - os: [aix] - '@esbuild/aix-ppc64@0.25.11': resolution: {integrity: sha512-Xt1dOL13m8u0WE8iplx9Ibbm+hFAO0GsU2P34UNoDGvZYkY8ifSiy6Zuc1lYxfG7svWE2fzqCUmFp5HCn51gJg==} engines: {node: '>=18'} cpu: [ppc64] os: [aix] - '@esbuild/android-arm64@0.24.2': - resolution: {integrity: sha512-cNLgeqCqV8WxfcTIOeL4OAtSmL8JjcN6m09XIgro1Wi7cF4t/THaWEa7eL5CMoMBdjoHOTh/vwTO/o2TRXIyzg==} - engines: {node: '>=18'} - cpu: [arm64] - os: [android] - '@esbuild/android-arm64@0.25.11': resolution: {integrity: sha512-9slpyFBc4FPPz48+f6jyiXOx/Y4v34TUeDDXJpZqAWQn/08lKGeD8aDp9TMn9jDz2CiEuHwfhRmGBvpnd/PWIQ==} engines: {node: '>=18'} cpu: [arm64] os: [android] - '@esbuild/android-arm@0.24.2': - resolution: {integrity: sha512-tmwl4hJkCfNHwFB3nBa8z1Uy3ypZpxqxfTQOcHX+xRByyYgunVbZ9MzUUfb0RxaHIMnbHagwAxuTL+tnNM+1/Q==} - engines: {node: '>=18'} - cpu: [arm] - os: [android] - '@esbuild/android-arm@0.25.11': resolution: {integrity: sha512-uoa7dU+Dt3HYsethkJ1k6Z9YdcHjTrSb5NUy66ZfZaSV8hEYGD5ZHbEMXnqLFlbBflLsl89Zke7CAdDJ4JI+Gg==} engines: {node: '>=18'} cpu: [arm] os: [android] - '@esbuild/android-x64@0.24.2': - resolution: {integrity: sha512-B6Q0YQDqMx9D7rvIcsXfmJfvUYLoP722bgfBlO5cGvNVb5V/+Y7nhBE3mHV9OpxBf4eAS2S68KZztiPaWq4XYw==} - engines: {node: '>=18'} - cpu: [x64] - os: [android] - '@esbuild/android-x64@0.25.11': resolution: {integrity: sha512-Sgiab4xBjPU1QoPEIqS3Xx+R2lezu0LKIEcYe6pftr56PqPygbB7+szVnzoShbx64MUupqoE0KyRlN7gezbl8g==} engines: {node: '>=18'} cpu: [x64] os: [android] - '@esbuild/darwin-arm64@0.24.2': - resolution: {integrity: sha512-kj3AnYWc+CekmZnS5IPu9D+HWtUI49hbnyqk0FLEJDbzCIQt7hg7ucF1SQAilhtYpIujfaHr6O0UHlzzSPdOeA==} - engines: {node: '>=18'} - cpu: [arm64] - os: [darwin] - '@esbuild/darwin-arm64@0.25.11': resolution: {integrity: sha512-VekY0PBCukppoQrycFxUqkCojnTQhdec0vevUL/EDOCnXd9LKWqD/bHwMPzigIJXPhC59Vd1WFIL57SKs2mg4w==} engines: {node: '>=18'} cpu: [arm64] os: [darwin] - '@esbuild/darwin-x64@0.24.2': - resolution: {integrity: sha512-WeSrmwwHaPkNR5H3yYfowhZcbriGqooyu3zI/3GGpF8AyUdsrrP0X6KumITGA9WOyiJavnGZUwPGvxvwfWPHIA==} - engines: {node: '>=18'} - cpu: [x64] - os: [darwin] - '@esbuild/darwin-x64@0.25.11': resolution: {integrity: sha512-+hfp3yfBalNEpTGp9loYgbknjR695HkqtY3d3/JjSRUyPg/xd6q+mQqIb5qdywnDxRZykIHs3axEqU6l1+oWEQ==} engines: {node: '>=18'} cpu: [x64] os: [darwin] - '@esbuild/freebsd-arm64@0.24.2': - resolution: {integrity: sha512-UN8HXjtJ0k/Mj6a9+5u6+2eZ2ERD7Edt1Q9IZiB5UZAIdPnVKDoG7mdTVGhHJIeEml60JteamR3qhsr1r8gXvg==} - engines: {node: '>=18'} - cpu: [arm64] - os: [freebsd] - '@esbuild/freebsd-arm64@0.25.11': resolution: {integrity: sha512-CmKjrnayyTJF2eVuO//uSjl/K3KsMIeYeyN7FyDBjsR3lnSJHaXlVoAK8DZa7lXWChbuOk7NjAc7ygAwrnPBhA==} engines: {node: '>=18'} cpu: [arm64] os: [freebsd] - '@esbuild/freebsd-x64@0.24.2': - resolution: {integrity: sha512-TvW7wE/89PYW+IevEJXZ5sF6gJRDY/14hyIGFXdIucxCsbRmLUcjseQu1SyTko+2idmCw94TgyaEZi9HUSOe3Q==} - engines: {node: '>=18'} - cpu: [x64] - os: [freebsd] - '@esbuild/freebsd-x64@0.25.11': resolution: {integrity: sha512-Dyq+5oscTJvMaYPvW3x3FLpi2+gSZTCE/1ffdwuM6G1ARang/mb3jvjxs0mw6n3Lsw84ocfo9CrNMqc5lTfGOw==} engines: {node: '>=18'} cpu: [x64] os: [freebsd] - '@esbuild/linux-arm64@0.24.2': - resolution: {integrity: sha512-7HnAD6074BW43YvvUmE/35Id9/NB7BeX5EoNkK9obndmZBUk8xmJJeU7DwmUeN7tkysslb2eSl6CTrYz6oEMQg==} - engines: {node: '>=18'} - cpu: [arm64] - os: [linux] - '@esbuild/linux-arm64@0.25.11': resolution: {integrity: sha512-Qr8AzcplUhGvdyUF08A1kHU3Vr2O88xxP0Tm8GcdVOUm25XYcMPp2YqSVHbLuXzYQMf9Bh/iKx7YPqECs6ffLA==} engines: {node: '>=18'} cpu: [arm64] os: [linux] - '@esbuild/linux-arm@0.24.2': - resolution: {integrity: sha512-n0WRM/gWIdU29J57hJyUdIsk0WarGd6To0s+Y+LwvlC55wt+GT/OgkwoXCXvIue1i1sSNWblHEig00GBWiJgfA==} - engines: {node: '>=18'} - cpu: [arm] - os: [linux] - '@esbuild/linux-arm@0.25.11': resolution: {integrity: sha512-TBMv6B4kCfrGJ8cUPo7vd6NECZH/8hPpBHHlYI3qzoYFvWu2AdTvZNuU/7hsbKWqu/COU7NIK12dHAAqBLLXgw==} engines: {node: '>=18'} cpu: [arm] os: [linux] - '@esbuild/linux-ia32@0.24.2': - resolution: {integrity: sha512-sfv0tGPQhcZOgTKO3oBE9xpHuUqguHvSo4jl+wjnKwFpapx+vUDcawbwPNuBIAYdRAvIDBfZVvXprIj3HA+Ugw==} - engines: {node: '>=18'} - cpu: [ia32] - os: [linux] - '@esbuild/linux-ia32@0.25.11': resolution: {integrity: sha512-TmnJg8BMGPehs5JKrCLqyWTVAvielc615jbkOirATQvWWB1NMXY77oLMzsUjRLa0+ngecEmDGqt5jiDC6bfvOw==} engines: {node: '>=18'} cpu: [ia32] os: [linux] - '@esbuild/linux-loong64@0.24.2': - resolution: {integrity: sha512-CN9AZr8kEndGooS35ntToZLTQLHEjtVB5n7dl8ZcTZMonJ7CCfStrYhrzF97eAecqVbVJ7APOEe18RPI4KLhwQ==} - engines: {node: '>=18'} - cpu: [loong64] - os: [linux] - '@esbuild/linux-loong64@0.25.11': resolution: {integrity: sha512-DIGXL2+gvDaXlaq8xruNXUJdT5tF+SBbJQKbWy/0J7OhU8gOHOzKmGIlfTTl6nHaCOoipxQbuJi7O++ldrxgMw==} engines: {node: '>=18'} cpu: [loong64] os: [linux] - '@esbuild/linux-mips64el@0.24.2': - resolution: {integrity: sha512-iMkk7qr/wl3exJATwkISxI7kTcmHKE+BlymIAbHO8xanq/TjHaaVThFF6ipWzPHryoFsesNQJPE/3wFJw4+huw==} - engines: {node: '>=18'} - cpu: [mips64el] - os: [linux] - '@esbuild/linux-mips64el@0.25.11': resolution: {integrity: sha512-Osx1nALUJu4pU43o9OyjSCXokFkFbyzjXb6VhGIJZQ5JZi8ylCQ9/LFagolPsHtgw6himDSyb5ETSfmp4rpiKQ==} engines: {node: '>=18'} cpu: [mips64el] os: [linux] - '@esbuild/linux-ppc64@0.24.2': - resolution: {integrity: sha512-shsVrgCZ57Vr2L8mm39kO5PPIb+843FStGt7sGGoqiiWYconSxwTiuswC1VJZLCjNiMLAMh34jg4VSEQb+iEbw==} - engines: {node: '>=18'} - cpu: [ppc64] - os: [linux] - '@esbuild/linux-ppc64@0.25.11': resolution: {integrity: sha512-nbLFgsQQEsBa8XSgSTSlrnBSrpoWh7ioFDUmwo158gIm5NNP+17IYmNWzaIzWmgCxq56vfr34xGkOcZ7jX6CPw==} engines: {node: '>=18'} cpu: [ppc64] os: [linux] - '@esbuild/linux-riscv64@0.24.2': - resolution: {integrity: sha512-4eSFWnU9Hhd68fW16GD0TINewo1L6dRrB+oLNNbYyMUAeOD2yCK5KXGK1GH4qD/kT+bTEXjsyTCiJGHPZ3eM9Q==} - engines: {node: '>=18'} - cpu: [riscv64] - os: [linux] - '@esbuild/linux-riscv64@0.25.11': resolution: {integrity: sha512-HfyAmqZi9uBAbgKYP1yGuI7tSREXwIb438q0nqvlpxAOs3XnZ8RsisRfmVsgV486NdjD7Mw2UrFSw51lzUk1ww==} engines: {node: '>=18'} cpu: [riscv64] os: [linux] - '@esbuild/linux-s390x@0.24.2': - resolution: {integrity: sha512-S0Bh0A53b0YHL2XEXC20bHLuGMOhFDO6GN4b3YjRLK//Ep3ql3erpNcPlEFed93hsQAjAQDNsvcK+hV90FubSw==} - engines: {node: '>=18'} - cpu: [s390x] - os: [linux] - '@esbuild/linux-s390x@0.25.11': resolution: {integrity: sha512-HjLqVgSSYnVXRisyfmzsH6mXqyvj0SA7pG5g+9W7ESgwA70AXYNpfKBqh1KbTxmQVaYxpzA/SvlB9oclGPbApw==} engines: {node: '>=18'} cpu: [s390x] os: [linux] - '@esbuild/linux-x64@0.24.2': - resolution: {integrity: sha512-8Qi4nQcCTbLnK9WoMjdC9NiTG6/E38RNICU6sUNqK0QFxCYgoARqVqxdFmWkdonVsvGqWhmm7MO0jyTqLqwj0Q==} - engines: {node: '>=18'} - cpu: [x64] - os: [linux] - '@esbuild/linux-x64@0.25.11': resolution: {integrity: sha512-HSFAT4+WYjIhrHxKBwGmOOSpphjYkcswF449j6EjsjbinTZbp8PJtjsVK1XFJStdzXdy/jaddAep2FGY+wyFAQ==} engines: {node: '>=18'} cpu: [x64] os: [linux] - '@esbuild/netbsd-arm64@0.24.2': - resolution: {integrity: sha512-wuLK/VztRRpMt9zyHSazyCVdCXlpHkKm34WUyinD2lzK07FAHTq0KQvZZlXikNWkDGoT6x3TD51jKQ7gMVpopw==} - engines: {node: '>=18'} - cpu: [arm64] - os: [netbsd] - '@esbuild/netbsd-arm64@0.25.11': resolution: {integrity: sha512-hr9Oxj1Fa4r04dNpWr3P8QKVVsjQhqrMSUzZzf+LZcYjZNqhA3IAfPQdEh1FLVUJSiu6sgAwp3OmwBfbFgG2Xg==} engines: {node: '>=18'} cpu: [arm64] os: [netbsd] - '@esbuild/netbsd-x64@0.24.2': - resolution: {integrity: sha512-VefFaQUc4FMmJuAxmIHgUmfNiLXY438XrL4GDNV1Y1H/RW3qow68xTwjZKfj/+Plp9NANmzbH5R40Meudu8mmw==} - engines: {node: '>=18'} - cpu: [x64] - os: [netbsd] - '@esbuild/netbsd-x64@0.25.11': resolution: {integrity: sha512-u7tKA+qbzBydyj0vgpu+5h5AeudxOAGncb8N6C9Kh1N4n7wU1Xw1JDApsRjpShRpXRQlJLb9wY28ELpwdPcZ7A==} engines: {node: '>=18'} cpu: [x64] os: [netbsd] - '@esbuild/openbsd-arm64@0.24.2': - resolution: {integrity: sha512-YQbi46SBct6iKnszhSvdluqDmxCJA+Pu280Av9WICNwQmMxV7nLRHZfjQzwbPs3jeWnuAhE9Jy0NrnJ12Oz+0A==} - engines: {node: '>=18'} - cpu: [arm64] - os: [openbsd] - '@esbuild/openbsd-arm64@0.25.11': resolution: {integrity: sha512-Qq6YHhayieor3DxFOoYM1q0q1uMFYb7cSpLD2qzDSvK1NAvqFi8Xgivv0cFC6J+hWVw2teCYltyy9/m/14ryHg==} engines: {node: '>=18'} cpu: [arm64] os: [openbsd] - '@esbuild/openbsd-x64@0.24.2': - resolution: {integrity: sha512-+iDS6zpNM6EnJyWv0bMGLWSWeXGN/HTaF/LXHXHwejGsVi+ooqDfMCCTerNFxEkM3wYVcExkeGXNqshc9iMaOA==} - engines: {node: '>=18'} - cpu: [x64] - os: [openbsd] - '@esbuild/openbsd-x64@0.25.11': resolution: {integrity: sha512-CN+7c++kkbrckTOz5hrehxWN7uIhFFlmS/hqziSFVWpAzpWrQoAG4chH+nN3Be+Kzv/uuo7zhX716x3Sn2Jduw==} engines: {node: '>=18'} @@ -755,48 +623,24 @@ packages: cpu: [arm64] os: [openharmony] - '@esbuild/sunos-x64@0.24.2': - resolution: {integrity: sha512-hTdsW27jcktEvpwNHJU4ZwWFGkz2zRJUz8pvddmXPtXDzVKTTINmlmga3ZzwcuMpUvLw7JkLy9QLKyGpD2Yxig==} - engines: {node: '>=18'} - cpu: [x64] - os: [sunos] - '@esbuild/sunos-x64@0.25.11': resolution: {integrity: sha512-nq2xdYaWxyg9DcIyXkZhcYulC6pQ2FuCgem3LI92IwMgIZ69KHeY8T4Y88pcwoLIjbed8n36CyKoYRDygNSGhA==} engines: {node: '>=18'} cpu: [x64] os: [sunos] - '@esbuild/win32-arm64@0.24.2': - resolution: {integrity: sha512-LihEQ2BBKVFLOC9ZItT9iFprsE9tqjDjnbulhHoFxYQtQfai7qfluVODIYxt1PgdoyQkz23+01rzwNwYfutxUQ==} - engines: {node: '>=18'} - cpu: [arm64] - os: [win32] - '@esbuild/win32-arm64@0.25.11': resolution: {integrity: sha512-3XxECOWJq1qMZ3MN8srCJ/QfoLpL+VaxD/WfNRm1O3B4+AZ/BnLVgFbUV3eiRYDMXetciH16dwPbbHqwe1uU0Q==} engines: {node: '>=18'} cpu: [arm64] os: [win32] - '@esbuild/win32-ia32@0.24.2': - resolution: {integrity: sha512-q+iGUwfs8tncmFC9pcnD5IvRHAzmbwQ3GPS5/ceCyHdjXubwQWI12MKWSNSMYLJMq23/IUCvJMS76PDqXe1fxA==} - engines: {node: '>=18'} - cpu: [ia32] - os: [win32] - '@esbuild/win32-ia32@0.25.11': resolution: {integrity: sha512-3ukss6gb9XZ8TlRyJlgLn17ecsK4NSQTmdIXRASVsiS2sQ6zPPZklNJT5GR5tE/MUarymmy8kCEf5xPCNCqVOA==} engines: {node: '>=18'} cpu: [ia32] os: [win32] - '@esbuild/win32-x64@0.24.2': - resolution: {integrity: sha512-7VTgWzgMGvup6aSqDPLiW5zHaxYJGTO4OokMjIlrCtf+VpEL+cXKtCvg723iguPYI5oaUNdS+/V7OU2gvXVWEg==} - engines: {node: '>=18'} - cpu: [x64] - os: [win32] - '@esbuild/win32-x64@0.25.11': resolution: {integrity: sha512-D7Hpz6A2L4hzsRpPaCYkQnGOotdUpDzSGRIv9I+1ITdHROSFUWW95ZPZWQmGka1Fg7W3zFJowyn9WGwMJ0+KPA==} engines: {node: '>=18'} @@ -841,10 +685,6 @@ packages: resolution: {integrity: sha512-sB5uyeq+dwCWyPi31B2gQlVlo+j5brPlWx4yZBrEaRo/nhdDE8Xke1gsGgtiBdaBTxuTkceLVuVt/pclrasb0A==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - '@faker-js/faker@10.1.0': - resolution: {integrity: sha512-C3mrr3b5dRVlKPJdfrAXS8+dq+rq8Qm5SNRazca0JKgw1HQERFmrVb0towvMmw5uu8hHKNiQasMaR/tydf3Zsg==} - engines: {node: ^20.19.0 || ^22.13.0 || ^23.5.0 || >=24.0.0, npm: '>=10'} - '@floating-ui/core@1.7.3': resolution: {integrity: sha512-sGnvb5dmrJaKEZ+LDIpguvdX3bDlEllmv4/ClQ9awcmCZrlx5jQyyMWFM5kBI+EyNOCDDiKk8il0zeuX3Zlg/w==} @@ -966,42 +806,36 @@ packages: engines: {node: '>= 10.0.0'} cpu: [arm] os: [linux] - libc: [glibc] '@parcel/watcher-linux-arm-musl@2.5.1': resolution: {integrity: sha512-6E+m/Mm1t1yhB8X412stiKFG3XykmgdIOqhjWj+VL8oHkKABfu/gjFj8DvLrYVHSBNC+/u5PeNrujiSQ1zwd1Q==} engines: {node: '>= 10.0.0'} cpu: [arm] os: [linux] - libc: [musl] '@parcel/watcher-linux-arm64-glibc@2.5.1': resolution: {integrity: sha512-LrGp+f02yU3BN9A+DGuY3v3bmnFUggAITBGriZHUREfNEzZh/GO06FF5u2kx8x+GBEUYfyTGamol4j3m9ANe8w==} engines: {node: '>= 10.0.0'} cpu: [arm64] os: [linux] - libc: [glibc] '@parcel/watcher-linux-arm64-musl@2.5.1': resolution: {integrity: sha512-cFOjABi92pMYRXS7AcQv9/M1YuKRw8SZniCDw0ssQb/noPkRzA+HBDkwmyOJYp5wXcsTrhxO0zq1U11cK9jsFg==} engines: {node: '>= 10.0.0'} cpu: [arm64] os: [linux] - libc: [musl] '@parcel/watcher-linux-x64-glibc@2.5.1': resolution: {integrity: sha512-GcESn8NZySmfwlTsIur+49yDqSny2IhPeZfXunQi48DMugKeZ7uy1FX83pO0X22sHntJ4Ub+9k34XQCX+oHt2A==} engines: {node: '>= 10.0.0'} cpu: [x64] os: [linux] - libc: [glibc] '@parcel/watcher-linux-x64-musl@2.5.1': resolution: {integrity: sha512-n0E2EQbatQ3bXhcH2D1XIAANAcTZkQICBPVaxMeaCVBtOpBZpWJuf7LwyWPSBDITb7In8mqQgJ7gH8CILCURXg==} engines: {node: '>= 10.0.0'} cpu: [x64] os: [linux] - libc: [musl] '@parcel/watcher-win32-arm64@2.5.1': resolution: {integrity: sha512-RFzklRvmc3PkjKjry3hLF9wD7ppR4AKcWNzH7kXR7GUe0Igb3Nz8fyPwtZCSquGrhU5HhUNDr/mKBqj7tqA2Vw==} @@ -1102,67 +936,56 @@ packages: resolution: {integrity: sha512-PsNAbcyv9CcecAUagQefwX8fQn9LQ4nZkpDboBOttmyffnInRy8R8dSg6hxxl2Re5QhHBf6FYIDhIj5v982ATQ==} cpu: [arm] os: [linux] - libc: [glibc] '@rollup/rollup-linux-arm-musleabihf@4.52.5': resolution: {integrity: sha512-Fw4tysRutyQc/wwkmcyoqFtJhh0u31K+Q6jYjeicsGJJ7bbEq8LwPWV/w0cnzOqR2m694/Af6hpFayLJZkG2VQ==} cpu: [arm] os: [linux] - libc: [musl] '@rollup/rollup-linux-arm64-gnu@4.52.5': resolution: {integrity: sha512-a+3wVnAYdQClOTlyapKmyI6BLPAFYs0JM8HRpgYZQO02rMR09ZcV9LbQB+NL6sljzG38869YqThrRnfPMCDtZg==} cpu: [arm64] os: [linux] - libc: [glibc] '@rollup/rollup-linux-arm64-musl@4.52.5': resolution: {integrity: sha512-AvttBOMwO9Pcuuf7m9PkC1PUIKsfaAJ4AYhy944qeTJgQOqJYJ9oVl2nYgY7Rk0mkbsuOpCAYSs6wLYB2Xiw0Q==} cpu: [arm64] os: [linux] - libc: [musl] '@rollup/rollup-linux-loong64-gnu@4.52.5': resolution: {integrity: sha512-DkDk8pmXQV2wVrF6oq5tONK6UHLz/XcEVow4JTTerdeV1uqPeHxwcg7aFsfnSm9L+OO8WJsWotKM2JJPMWrQtA==} cpu: [loong64] os: [linux] - libc: [glibc] '@rollup/rollup-linux-ppc64-gnu@4.52.5': resolution: {integrity: sha512-W/b9ZN/U9+hPQVvlGwjzi+Wy4xdoH2I8EjaCkMvzpI7wJUs8sWJ03Rq96jRnHkSrcHTpQe8h5Tg3ZzUPGauvAw==} cpu: [ppc64] os: [linux] - libc: [glibc] '@rollup/rollup-linux-riscv64-gnu@4.52.5': resolution: {integrity: sha512-sjQLr9BW7R/ZiXnQiWPkErNfLMkkWIoCz7YMn27HldKsADEKa5WYdobaa1hmN6slu9oWQbB6/jFpJ+P2IkVrmw==} cpu: [riscv64] os: [linux] - libc: [glibc] '@rollup/rollup-linux-riscv64-musl@4.52.5': resolution: {integrity: sha512-hq3jU/kGyjXWTvAh2awn8oHroCbrPm8JqM7RUpKjalIRWWXE01CQOf/tUNWNHjmbMHg/hmNCwc/Pz3k1T/j/Lg==} cpu: [riscv64] os: [linux] - libc: [musl] '@rollup/rollup-linux-s390x-gnu@4.52.5': resolution: {integrity: sha512-gn8kHOrku8D4NGHMK1Y7NA7INQTRdVOntt1OCYypZPRt6skGbddska44K8iocdpxHTMMNui5oH4elPH4QOLrFQ==} cpu: [s390x] os: [linux] - libc: [glibc] '@rollup/rollup-linux-x64-gnu@4.52.5': resolution: {integrity: sha512-hXGLYpdhiNElzN770+H2nlx+jRog8TyynpTVzdlc6bndktjKWyZyiCsuDAlpd+j+W+WNqfcyAWz9HxxIGfZm1Q==} cpu: [x64] os: [linux] - libc: [glibc] '@rollup/rollup-linux-x64-musl@4.52.5': resolution: {integrity: sha512-arCGIcuNKjBoKAXD+y7XomR9gY6Mw7HnFBv5Rw7wQRvwYLR7gBAgV7Mb2QTyjXfTveBNFAtPt46/36vV9STLNg==} cpu: [x64] os: [linux] - libc: [musl] '@rollup/rollup-openharmony-arm64@4.52.5': resolution: {integrity: sha512-QoFqB6+/9Rly/RiPjaomPLmR/13cgkIGfA40LHly9zcH1S0bN2HVFYk3a1eAyHQyjs3ZJYlXvIGtcCs5tko9Cw==} @@ -1230,28 +1053,24 @@ packages: engines: {node: '>= 10'} cpu: [arm64] os: [linux] - libc: [glibc] '@tailwindcss/oxide-linux-arm64-musl@4.1.16': resolution: {integrity: sha512-H81UXMa9hJhWhaAUca6bU2wm5RRFpuHImrwXBUvPbYb+3jo32I9VIwpOX6hms0fPmA6f2pGVlybO6qU8pF4fzQ==} engines: {node: '>= 10'} cpu: [arm64] os: [linux] - libc: [musl] '@tailwindcss/oxide-linux-x64-gnu@4.1.16': resolution: {integrity: sha512-ZGHQxDtFC2/ruo7t99Qo2TTIvOERULPl5l0K1g0oK6b5PGqjYMga+FcY1wIUnrUxY56h28FxybtDEla+ICOyew==} engines: {node: '>= 10'} cpu: [x64] os: [linux] - libc: [glibc] '@tailwindcss/oxide-linux-x64-musl@4.1.16': resolution: {integrity: sha512-Oi1tAaa0rcKf1Og9MzKeINZzMLPbhxvm7rno5/zuP1WYmpiG0bEHq4AcRUiG2165/WUzvxkW4XDYCscZWbTLZw==} engines: {node: '>= 10'} cpu: [x64] os: [linux] - libc: [musl] '@tailwindcss/oxide-wasm32-wasi@4.1.16': resolution: {integrity: sha512-B01u/b8LteGRwucIBmCQ07FVXLzImWESAIMcUU6nvFt/tYsQ6IHz8DmZ5KtvmwxD+iTYBtM1xwoGXswnlu9v0Q==} @@ -1629,9 +1448,6 @@ packages: engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7} hasBin: true - bundle-import@0.0.2: - resolution: {integrity: sha512-XB3T6xlgqJHThyr2luo3pNAVhfN/Y2qFEsblrzUO5QZLpJtesget8jmGDImSairScy80ZKBDVcRdFzTzWv3v8A==} - c12@3.3.1: resolution: {integrity: sha512-LcWQ01LT9tkoUINHgpIOv3mMs+Abv7oVCrtpMRi1PaapVEpWoMga5WuT7/DqFTu7URP9ftbOmimNw1KNIGh9DQ==} peerDependencies: @@ -1987,11 +1803,6 @@ packages: resolution: {integrity: sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==} engines: {node: '>= 0.4'} - esbuild@0.24.2: - resolution: {integrity: sha512-+9egpBW8I3CD5XPe0n6BfT5fxLzxrlDzqydF3aviG+9ni1lDC/OvMHcxqEFV0+LANZG5R1bFMWfUrjVsdwxJvA==} - engines: {node: '>=18'} - hasBin: true - esbuild@0.25.11: resolution: {integrity: sha512-KohQwyzrKTQmhXDW1PjCv3Tyspn9n5GcY2RTDqeORIdIJY8yKIF7sTSopFmn/wpMPW4rdPXI0UE5LJLuq3bx0Q==} engines: {node: '>=18'} @@ -2213,9 +2024,6 @@ packages: resolution: {integrity: sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==} engines: {node: '>= 0.4'} - get-tsconfig@4.13.0: - resolution: {integrity: sha512-1VKTZJCwBrvbd+Wn3AOgQP/2Av+TfTCOlE4AcRJE72W1ksZXbAx8PPBR9RzgTeSPzlPMHrbANMH3LbltH73wxQ==} - giget@2.0.0: resolution: {integrity: sha512-L5bGsVkxJbJgdnwyuheIunkGatUF/zssUoxxjACCseZYAVbaqdh9Tsmmlkl8vYan09H7sbvKt4pS8GqKLBrEzA==} hasBin: true @@ -2223,6 +2031,7 @@ packages: git-raw-commits@4.0.0: resolution: {integrity: sha512-ICsMM1Wk8xSGMowkOmPrzo2Fgmfo4bMHLNX6ytHjajRJUqvHOw/TFapQ+QG75c3X/tTDDhOSRPGC52dDbNM8FQ==} engines: {node: '>=16'} + deprecated: This package is no longer maintained. For the JavaScript API, please use @conventional-changelog/git-client instead. hasBin: true glob-parent@5.1.2: @@ -2236,6 +2045,7 @@ packages: glob@11.0.3: resolution: {integrity: sha512-2Nim7dha1KVkaiF4q6Dj+ngPPMdfvLJEOpZk/jKiUAkqKebpGAWQXAq9z1xu9HKu5lWfqw/FASuccEjyznjPaA==} engines: {node: 20 || >=22} + deprecated: Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me hasBin: true global-directory@4.0.1: @@ -2334,9 +2144,6 @@ packages: resolution: {integrity: sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==} engines: {node: '>=6'} - import-from-string@0.0.5: - resolution: {integrity: sha512-z59WIHImWhnGVswc0JoyI10Qn4A8xQw7OKrCFRQHvzGZhhEixX13OtXP9ud3Xjpn16CUoYfh5mTu3tnNODiSAw==} - import-meta-resolve@4.2.0: resolution: {integrity: sha512-Iqv2fzaTQN28s/FwZAoFq0ZSs/7hMAHJVX+w8PZl3cY19Pxk6jFFalxQoIfW2826i/fDLXv8IiEZRIT0lDuWcg==} @@ -2530,28 +2337,24 @@ packages: engines: {node: '>= 12.0.0'} cpu: [arm64] os: [linux] - libc: [glibc] lightningcss-linux-arm64-musl@1.30.2: resolution: {integrity: sha512-5Vh9dGeblpTxWHpOx8iauV02popZDsCYMPIgiuw97OJ5uaDsL86cnqSFs5LZkG3ghHoX5isLgWzMs+eD1YzrnA==} engines: {node: '>= 12.0.0'} cpu: [arm64] os: [linux] - libc: [musl] lightningcss-linux-x64-gnu@1.30.2: resolution: {integrity: sha512-Cfd46gdmj1vQ+lR6VRTTadNHu6ALuw2pKR9lYq4FnhvgBc4zWY1EtZcAc6EffShbb1MFrIPfLDXD6Xprbnni4w==} engines: {node: '>= 12.0.0'} cpu: [x64] os: [linux] - libc: [glibc] lightningcss-linux-x64-musl@1.30.2: resolution: {integrity: sha512-XJaLUUFXb6/QG2lGIW6aIk6jKdtjtcffUT0NKvIqhSBY3hh9Ch+1LCeH80dR9q9LBjG3ewbDjnumefsLsP6aiA==} engines: {node: '>= 12.0.0'} cpu: [x64] os: [linux] - libc: [musl] lightningcss-win32-arm64-msvc@1.30.2: resolution: {integrity: sha512-FZn+vaj7zLv//D/192WFFVA0RgHawIcHqLX9xuWiQt7P0PtdFEVaxgF9rjM/IRYHQXNnk61/H/gb2Ei+kUQ4xQ==} @@ -2850,9 +2653,6 @@ packages: resolution: {integrity: sha512-ypGJsmGtdXUOeM5u93TyeIEfEhM6s+ljAhrk5vAvSx8uyY/02OvrZnA0YNGUrPXfpJMgI1ODd3nwz8Npx4O4cg==} engines: {node: 20 || >=22} - path-to-regexp@8.3.0: - resolution: {integrity: sha512-7jdwVIRtsP8MYpdXSwOS0YdD0Du+qOoF/AEPIt88PcCFrZCzx41oxku1jD88hZBwbNUIEfpqvuhjFaMAqMTWnA==} - path-type@4.0.0: resolution: {integrity: sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==} engines: {node: '>=8'} @@ -3192,9 +2992,6 @@ packages: resolution: {integrity: sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==} engines: {node: '>=8'} - resolve-pkg-maps@1.0.0: - resolution: {integrity: sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==} - responsive-storage@2.2.0: resolution: {integrity: sha512-94W5Chr2F5kDBT6J+OCOeJguEkSTDc3jPOUQXYmzNG64DCNl5p7hoBDF7bx7u6EXAEcpUKF64OZR4b7Nn8h/Gg==} @@ -3633,9 +3430,6 @@ packages: peerDependencies: vite: '>=2.0.0' - vite-plugin-fake-server@2.2.0: - resolution: {integrity: sha512-RP691997Q207nenNMhg7cvYyBXZyjqXwApTBa+a9kHmILgcAU2w4TMRDiAhIU0HPLAAR5MHlWTEpxA9Tbf+v8g==} - vite-plugin-remove-console@2.2.0: resolution: {integrity: sha512-qgjh5pz75MdE9Kzs8J0kBwaCfifHV0ezRbB9rpGsIOxam+ilcGV7WOk91vFJXquzRmiKrFh3Hxlh0JJWAmXTbQ==} @@ -4178,156 +3972,81 @@ snapshots: dependencies: vue: 3.5.22(typescript@5.9.3) - '@esbuild/aix-ppc64@0.24.2': - optional: true - '@esbuild/aix-ppc64@0.25.11': optional: true - '@esbuild/android-arm64@0.24.2': - optional: true - '@esbuild/android-arm64@0.25.11': optional: true - '@esbuild/android-arm@0.24.2': - optional: true - '@esbuild/android-arm@0.25.11': optional: true - '@esbuild/android-x64@0.24.2': - optional: true - '@esbuild/android-x64@0.25.11': optional: true - '@esbuild/darwin-arm64@0.24.2': - optional: true - '@esbuild/darwin-arm64@0.25.11': optional: true - '@esbuild/darwin-x64@0.24.2': - optional: true - '@esbuild/darwin-x64@0.25.11': optional: true - '@esbuild/freebsd-arm64@0.24.2': - optional: true - '@esbuild/freebsd-arm64@0.25.11': optional: true - '@esbuild/freebsd-x64@0.24.2': - optional: true - '@esbuild/freebsd-x64@0.25.11': optional: true - '@esbuild/linux-arm64@0.24.2': - optional: true - '@esbuild/linux-arm64@0.25.11': optional: true - '@esbuild/linux-arm@0.24.2': - optional: true - '@esbuild/linux-arm@0.25.11': optional: true - '@esbuild/linux-ia32@0.24.2': - optional: true - '@esbuild/linux-ia32@0.25.11': optional: true - '@esbuild/linux-loong64@0.24.2': - optional: true - '@esbuild/linux-loong64@0.25.11': optional: true - '@esbuild/linux-mips64el@0.24.2': - optional: true - '@esbuild/linux-mips64el@0.25.11': optional: true - '@esbuild/linux-ppc64@0.24.2': - optional: true - '@esbuild/linux-ppc64@0.25.11': optional: true - '@esbuild/linux-riscv64@0.24.2': - optional: true - '@esbuild/linux-riscv64@0.25.11': optional: true - '@esbuild/linux-s390x@0.24.2': - optional: true - '@esbuild/linux-s390x@0.25.11': optional: true - '@esbuild/linux-x64@0.24.2': - optional: true - '@esbuild/linux-x64@0.25.11': optional: true - '@esbuild/netbsd-arm64@0.24.2': - optional: true - '@esbuild/netbsd-arm64@0.25.11': optional: true - '@esbuild/netbsd-x64@0.24.2': - optional: true - '@esbuild/netbsd-x64@0.25.11': optional: true - '@esbuild/openbsd-arm64@0.24.2': - optional: true - '@esbuild/openbsd-arm64@0.25.11': optional: true - '@esbuild/openbsd-x64@0.24.2': - optional: true - '@esbuild/openbsd-x64@0.25.11': optional: true '@esbuild/openharmony-arm64@0.25.11': optional: true - '@esbuild/sunos-x64@0.24.2': - optional: true - '@esbuild/sunos-x64@0.25.11': optional: true - '@esbuild/win32-arm64@0.24.2': - optional: true - '@esbuild/win32-arm64@0.25.11': optional: true - '@esbuild/win32-ia32@0.24.2': - optional: true - '@esbuild/win32-ia32@0.25.11': optional: true - '@esbuild/win32-x64@0.24.2': - optional: true - '@esbuild/win32-x64@0.25.11': optional: true @@ -4377,8 +4096,6 @@ snapshots: '@eslint/core': 0.16.0 levn: 0.4.1 - '@faker-js/faker@10.1.0': {} - '@floating-ui/core@1.7.3': dependencies: '@floating-ui/utils': 0.2.10 @@ -5187,11 +4904,6 @@ snapshots: node-releases: 2.0.26 update-browserslist-db: 1.1.4(browserslist@4.27.0) - bundle-import@0.0.2: - dependencies: - get-tsconfig: 4.13.0 - import-from-string: 0.0.5 - c12@3.3.1: dependencies: chokidar: 4.0.3 @@ -5581,34 +5293,6 @@ snapshots: has-tostringtag: 1.0.2 hasown: 2.0.2 - esbuild@0.24.2: - optionalDependencies: - '@esbuild/aix-ppc64': 0.24.2 - '@esbuild/android-arm': 0.24.2 - '@esbuild/android-arm64': 0.24.2 - '@esbuild/android-x64': 0.24.2 - '@esbuild/darwin-arm64': 0.24.2 - '@esbuild/darwin-x64': 0.24.2 - '@esbuild/freebsd-arm64': 0.24.2 - '@esbuild/freebsd-x64': 0.24.2 - '@esbuild/linux-arm': 0.24.2 - '@esbuild/linux-arm64': 0.24.2 - '@esbuild/linux-ia32': 0.24.2 - '@esbuild/linux-loong64': 0.24.2 - '@esbuild/linux-mips64el': 0.24.2 - '@esbuild/linux-ppc64': 0.24.2 - '@esbuild/linux-riscv64': 0.24.2 - '@esbuild/linux-s390x': 0.24.2 - '@esbuild/linux-x64': 0.24.2 - '@esbuild/netbsd-arm64': 0.24.2 - '@esbuild/netbsd-x64': 0.24.2 - '@esbuild/openbsd-arm64': 0.24.2 - '@esbuild/openbsd-x64': 0.24.2 - '@esbuild/sunos-x64': 0.24.2 - '@esbuild/win32-arm64': 0.24.2 - '@esbuild/win32-ia32': 0.24.2 - '@esbuild/win32-x64': 0.24.2 - esbuild@0.25.11: optionalDependencies: '@esbuild/aix-ppc64': 0.25.11 @@ -5867,10 +5551,6 @@ snapshots: dunder-proto: 1.0.1 es-object-atoms: 1.1.1 - get-tsconfig@4.13.0: - dependencies: - resolve-pkg-maps: 1.0.0 - giget@2.0.0: dependencies: citty: 0.1.6 @@ -5986,11 +5666,6 @@ snapshots: parent-module: 1.0.1 resolve-from: 4.0.0 - import-from-string@0.0.5: - dependencies: - esbuild: 0.24.2 - import-meta-resolve: 4.2.0 - import-meta-resolve@4.2.0: {} imurmurhash@0.1.4: {} @@ -6418,8 +6093,6 @@ snapshots: lru-cache: 11.2.2 minipass: 7.1.2 - path-to-regexp@8.3.0: {} - path-type@4.0.0: {} pathe@2.0.3: {} @@ -6714,8 +6387,6 @@ snapshots: resolve-from@5.0.0: {} - resolve-pkg-maps@1.0.0: {} - responsive-storage@2.2.0: {} restore-cursor@5.1.0: @@ -7238,14 +6909,6 @@ snapshots: magic-string: 0.25.9 vite: 7.1.12(@types/node@20.19.23)(jiti@2.6.1)(lightningcss@1.30.2)(sass@1.93.2)(yaml@2.8.1) - vite-plugin-fake-server@2.2.0: - dependencies: - bundle-import: 0.0.2 - chokidar: 4.0.3 - path-to-regexp: 8.3.0 - picocolors: 1.1.1 - tinyglobby: 0.2.15 - vite-plugin-remove-console@2.2.0: {} vite-plugin-router-warn@1.0.0: {} diff --git a/public/platform-config.json b/public/platform-config.json index 714bca6..d9905dc 100644 --- a/public/platform-config.json +++ b/public/platform-config.json @@ -19,7 +19,6 @@ "ShowLogo": true, "ShowModel": "smart", "MenuArrowIconNoTransition": false, - "CachingAsyncRoutes": false, "TooltipEffect": "light", "ResponsiveStorageNameSpace": "responsive-", "MenuSearchHistory": 6 diff --git a/src/api/access.ts b/src/api/access.ts index 1be01b4..b83b8dd 100644 --- a/src/api/access.ts +++ b/src/api/access.ts @@ -58,17 +58,74 @@ export interface Role extends RoleOption { updatedAt: string; } -/** 员工列表是服务端分页,筛选条件统一通过查询参数传递。 */ -export interface EmployeeListParams { - storeId?: number; - status?: EmployeeStatus; - keyword?: string; +export interface PermissionPolicyMenu { + key: string; + title: string; + actions: string[]; +} + +export interface PermissionPolicy { + roleId: number; + roleCode: string; + roleName: string; + roleDescription: string | null; + isSystem: boolean; + editable: boolean; + scope: string; + permissions: string[]; + menus: PermissionPolicyMenu[]; +} + +export interface PermissionDefinition { + code: string; + title: string; + description: string; + groupKey: string; + groupTitle: string; +} + +export interface PermissionDefinitionGroup { + key: string; + title: string; + permissions: PermissionDefinition[]; +} + +export interface PermissionDefinitionMenu { + key: string; + title: string; + icon?: string; + permission: string; + actions: string[]; + actionLabels: Record; +} + +export interface PermissionDefinitions { + permissions: PermissionDefinition[]; + groups: PermissionDefinitionGroup[]; + menus: PermissionDefinitionMenu[]; +} + +/** 列表接口是服务端分页,筛选条件统一通过查询参数传递。 */ +export interface PageParams { page: number; pageSize: number; } -export interface StoreListParams { +export interface EmployeeListParams extends PageParams { + storeId?: number; + status?: EmployeeStatus; + keyword?: string; +} + +export interface StoreListParams extends PageParams { includeInactive?: boolean; + status?: StoreStatus; + keyword?: string; +} + +export interface RoleListParams extends PageParams { + keyword?: string; + isSystem?: boolean; } export interface StorePayload { @@ -111,17 +168,42 @@ export interface PaginatedData { pagination: Pagination; } +export type PermissionPolicyResult = ApiResult; +export type PermissionDefinitionsResult = ApiResult; + /** 门店接口:管理门店基础资料,并给员工下拉选项提供数据源。 */ -export const listStores = (params?: StoreListParams) => { +export const listStoreOptions = () => { + return http.request>("get", `${API_PREFIX}/stores`); +}; + +export const listAllStores = ( + params?: Pick +) => { return http.request>("get", `${API_PREFIX}/stores`, { params }); }; +export const listStores = (params: StoreListParams) => { + return http.request>>( + "get", + `${API_PREFIX}/stores`, + { params } + ); +}; + export const listRoles = () => { return http.request>("get", `${API_PREFIX}/roles`); }; +export const listRolePage = (params: RoleListParams) => { + return http.request>>( + "get", + `${API_PREFIX}/roles`, + { params } + ); +}; + export const getStore = (id: number) => { return http.request>("get", `${API_PREFIX}/stores/${id}`); }; @@ -204,3 +286,31 @@ export const updateEmployeeStatus = (id: number, status: EmployeeStatus) => { export const deleteEmployee = (id: number) => { return http.request("delete", `${API_PREFIX}/employees/${id}`); }; + +/** 权限接口:读取权限定义、查看角色策略,并把权限点分配给角色。 */ +export const getPermissionPolicies = () => { + return http.request( + "get", + `${API_PREFIX}/permissions/policies` + ); +}; + +export const getPermissionDefinitions = () => { + return http.request( + "get", + `${API_PREFIX}/permissions/definitions` + ); +}; + +export const updateRolePermissions = ( + roleId: number, + permissions: string[] +) => { + return http.request>( + "put", + `${API_PREFIX}/permissions/roles/${roleId}`, + { + data: { permissions } + } + ); +}; diff --git a/src/api/routes.ts b/src/api/routes.ts deleted file mode 100644 index 501ea3c..0000000 --- a/src/api/routes.ts +++ /dev/null @@ -1,10 +0,0 @@ -import { http } from "@/utils/http"; - -type Result = { - success: boolean; - data: Array; -}; - -export const getAsyncRoutes = () => { - return http.request("get", "/get-async-routes"); -}; diff --git a/src/components/ReAuth/src/auth.tsx b/src/components/ReAuth/src/auth.tsx index d2cf9b3..b0e11a9 100644 --- a/src/components/ReAuth/src/auth.tsx +++ b/src/components/ReAuth/src/auth.tsx @@ -1,5 +1,5 @@ import { defineComponent, Fragment } from "vue"; -import { hasAuth } from "@/router/utils"; +import { hasPerms } from "@/utils/auth"; export default defineComponent({ name: "Auth", @@ -12,7 +12,7 @@ export default defineComponent({ setup(props, { slots }) { return () => { if (!slots) return null; - return hasAuth(props.value) ? ( + return hasPerms(props.value) ? ( {slots.default?.()} ) : null; }; diff --git a/src/directives/auth/index.ts b/src/directives/auth/index.ts index 2fc6490..66a7040 100644 --- a/src/directives/auth/index.ts +++ b/src/directives/auth/index.ts @@ -1,14 +1,14 @@ -import { hasAuth } from "@/router/utils"; +import { hasPerms } from "@/utils/auth"; import type { Directive, DirectiveBinding } from "vue"; export const auth: Directive = { mounted(el: HTMLElement, binding: DirectiveBinding>) { const { value } = binding; if (value) { - !hasAuth(value) && el.parentNode?.removeChild(el); + !hasPerms(value) && el.parentNode?.removeChild(el); } else { throw new Error( - "[Directive: auth]: need auths! Like v-auth=\"['btn.add','btn.edit']\"" + "[Directive: auth]: need permissions! Like v-auth=\"['store:manage']\"" ); } } diff --git a/src/layout/types.ts b/src/layout/types.ts index a2f21f0..4230885 100644 --- a/src/layout/types.ts +++ b/src/layout/types.ts @@ -30,6 +30,14 @@ export const routerArrays: Array = [ title: "员工管理", icon: "ep/user-filled" } + }, + { + path: "/permissions", + name: "PermissionPolicies", + meta: { + title: "权限策略", + icon: "ep/key" + } } ]; @@ -40,7 +48,6 @@ export type routeMetaType = { savedPosition?: boolean; permission?: string | Array; menuKey?: string; - auths?: Array; }; export type RouteConfigs = { diff --git a/src/router/index.ts b/src/router/index.ts index 3d2aaf5..c6c8f54 100644 --- a/src/router/index.ts +++ b/src/router/index.ts @@ -173,7 +173,13 @@ router.beforeEach(async (to: ToRouteType, _from, next) => { return; } - if (!hasRoutePermission(to.meta, useUserStoreHook().permissions ?? [])) { + if ( + !hasRoutePermission( + to.meta, + useUserStoreHook().permissions ?? [], + useUserStoreHook().permissionMenus ?? [] + ) + ) { next({ path: "/access-denied" }); return; } diff --git a/src/router/modules/employees.ts b/src/router/modules/employees.ts index c0e0545..f5259ff 100644 --- a/src/router/modules/employees.ts +++ b/src/router/modules/employees.ts @@ -3,7 +3,7 @@ const Layout = () => import("@/layout/index.vue"); /** * 权限管理业务菜单。 * - * 三个子页面都是静态路由,菜单展示顺序由这里的 children 决定; + * 子页面都是静态路由,菜单展示顺序由这里的 children 决定; * 默认访问该模块时进入员工管理,保证和登录/登出后的主工作流一致。 */ export default { @@ -49,6 +49,17 @@ export default { permission: ["employee:view:all", "employee:view:store"], keepAlive: true } + }, + { + path: "/permissions", + name: "PermissionPolicies", + component: () => import("@/views/permissions/index.vue"), + meta: { + title: "权限策略", + menuKey: "permissions", + permission: "permission:view", + keepAlive: true + } } ] } satisfies RouteConfigsTable; diff --git a/src/router/utils.ts b/src/router/utils.ts index b17b428..0a8ecdb 100644 --- a/src/router/utils.ts +++ b/src/router/utils.ts @@ -9,25 +9,16 @@ import { router } from "./index"; import { isProxy, toRaw } from "vue"; import { useTimeoutFn } from "@vueuse/core"; import { - isString, cloneDeep, isAllEmpty, intersection, - storageLocal, - isIncludeAllChildren + storageLocal } from "@pureadmin/utils"; -import { getConfig } from "@/config"; import { buildHierarchyTree } from "@/utils/tree"; -import { userKey, type DataInfo } from "@/utils/auth"; -import { type menuType, routerArrays } from "@/layout/types"; +import { userKey, type DataInfo, type PermissionMenuInfo } from "@/utils/auth"; +import type { menuType } from "@/layout/types"; import { useMultiTagsStoreHook } from "@/store/modules/multiTags"; import { usePermissionStoreHook } from "@/store/modules/permission"; -const IFrame = () => import("@/layout/frame.vue"); -// https://cn.vitejs.dev/guide/features.html#glob-import -const modulesRoutes = import.meta.glob("/src/views/**/*.{vue,tsx}"); - -// 动态路由 -import { getAsyncRoutes } from "@/api/routes"; function handRank(routeInfo: any) { const { name, path, parentId, meta } = routeInfo; @@ -81,26 +72,49 @@ function isOneOfArray(a: Array, b: Array) { : true; } -function hasRoutePermission(meta: CustomizeRouteMeta, permissions: string[]) { - const required = meta?.permission; +function hasWildcardPermission(permissions: string[]) { + return permissions.includes("*") || permissions.includes("*:*:*"); +} - if (!required) return true; - if (permissions.includes("*") || permissions.includes("*:*:*")) return true; +function hasMenuAccess( + meta: CustomizeRouteMeta, + permissions: string[], + permissionMenus: PermissionMenuInfo[] = [] +) { + if (!meta?.menuKey) return true; + if (hasWildcardPermission(permissions)) return true; + + return permissionMenus.some(menu => menu.key === meta.menuKey); +} + +function hasRoutePermission( + meta: CustomizeRouteMeta, + permissions: string[], + permissionMenus: PermissionMenuInfo[] = [] +) { + const required = meta?.permission; + const hasMenu = hasMenuAccess(meta, permissions, permissionMenus); + + if (!required) return hasMenu; + if (hasWildcardPermission(permissions)) return true; const requiredList = Array.isArray(required) ? required : [required]; - return requiredList.some(permission => permissions.includes(permission)); + return ( + hasMenu && requiredList.some(permission => permissions.includes(permission)) + ); } -/** 从localStorage里取出当前登录用户权限码,过滤无权限菜单 */ +/** 从 localStorage 取出后端菜单和权限码,过滤无权限菜单 */ function filterNoPermissionTree(data: RouteComponent[]) { const userInfo = storageLocal().getItem>(userKey); const currentRoles = userInfo?.roles ?? []; const currentPermissions = userInfo?.permissions ?? []; + const currentMenus = userInfo?.permissionMenus ?? []; const newTree = cloneDeep(data).filter((v: any) => { return ( isOneOfArray(v.meta?.roles, currentRoles) && - hasRoutePermission(v.meta, currentPermissions) + hasRoutePermission(v.meta, currentPermissions, currentMenus) ); }); newTree.forEach( @@ -169,78 +183,6 @@ function addPathMatch() { } } -/** 处理动态路由(后端返回的路由) */ -function handleAsyncRoutes(routeList) { - if (routeList.length === 0) { - usePermissionStoreHook().handleWholeMenus(routeList); - } else { - formatFlatteningRoutes(addAsyncRoutes(routeList)).map( - (v: RouteRecordRaw) => { - // 防止重复添加路由 - if ( - router.options.routes[0].children.findIndex( - value => value.path === v.path - ) !== -1 - ) { - return; - } else { - // 切记将路由push到routes后还需要使用addRoute,这样路由才能正常跳转 - router.options.routes[0].children.push(v); - // 最终路由进行升序 - ascending(router.options.routes[0].children); - if (!router.hasRoute(v?.name)) router.addRoute(v); - const flattenRouters: any = router - .getRoutes() - .find(n => n.path === "/"); - // 保持router.options.routes[0].children与path为"/"的children一致,防止数据不一致导致异常 - flattenRouters.children = router.options.routes[0].children; - router.addRoute(flattenRouters); - } - } - ); - usePermissionStoreHook().handleWholeMenus(routeList); - } - if (!useMultiTagsStoreHook().getMultiTagsCache) { - useMultiTagsStoreHook().handleTags("equal", [ - ...routerArrays, - ...usePermissionStoreHook().flatteningRoutes.filter( - v => v?.meta?.fixedTag - ) - ]); - } - addPathMatch(); -} - -/** 初始化路由(`new Promise` 写法防止在异步请求中造成无限循环)*/ -function initRouter() { - if (getConfig()?.CachingAsyncRoutes) { - // 开启动态路由缓存本地localStorage - const key = "async-routes"; - const asyncRouteList = storageLocal().getItem(key) as any; - if (asyncRouteList && asyncRouteList?.length > 0) { - return new Promise(resolve => { - handleAsyncRoutes(asyncRouteList); - resolve(router); - }); - } else { - return new Promise(resolve => { - getAsyncRoutes().then(({ data }) => { - handleAsyncRoutes(cloneDeep(data)); - storageLocal().setItem(key, data); - resolve(router); - }); - }); - } - } else { - return new Promise(resolve => { - getAsyncRoutes().then(({ data }) => { - handleAsyncRoutes(cloneDeep(data)); - resolve(router); - }); - }); - } -} - /** * 将多级嵌套路由处理成一维数组 * @param routesList 传入路由 @@ -320,35 +262,6 @@ function handleAliveRoute({ name }: ToRouteType, mode?: string) { } } -/** 过滤后端传来的动态路由 重新生成规范路由 */ -function addAsyncRoutes(arrRoutes: Array) { - if (!arrRoutes || !arrRoutes.length) return; - const modulesRoutesKeys = Object.keys(modulesRoutes); - arrRoutes.forEach((v: RouteRecordRaw) => { - // 将backstage属性加入meta,标识此路由为后端返回路由 - v.meta.backstage = true; - // 父级的redirect属性取值:如果子级存在且父级的redirect属性不存在,默认取第一个子级的path;如果子级存在且父级的redirect属性存在,取存在的redirect属性,会覆盖默认值 - if (v?.children && v.children.length && !v.redirect) - v.redirect = v.children[0].path; - // 父级的name属性取值:如果子级存在且父级的name属性不存在,默认取第一个子级的name;如果子级存在且父级的name属性存在,取存在的name属性,会覆盖默认值(注意:测试中发现父级的name不能和子级name重复,如果重复会造成重定向无效(跳转404),所以这里给父级的name起名的时候后面会自动加上`Parent`,避免重复) - if (v?.children && v.children.length && !v.name) - v.name = (v.children[0].name as string) + "Parent"; - if (v.meta?.frameSrc) { - v.component = IFrame; - } else { - // 对后端传component组件路径和不传做兼容(如果后端传component组件路径,那么path可以随便写,如果不传,component组件路径会跟path保持一致) - const index = v?.component - ? modulesRoutesKeys.findIndex(ev => ev.includes(v.component as any)) - : modulesRoutesKeys.findIndex(ev => ev.includes(v.path)); - v.component = modulesRoutes[modulesRoutesKeys[index]]; - } - if (v?.children && v.children.length) { - addAsyncRoutes(v.children); - } - }); - return arrRoutes; -} - /** 获取路由历史模式 https://next.router.vuejs.org/zh/guide/essentials/history-mode.html */ function getHistoryMode(routerHistory): RouterHistory { // len为1 代表只有历史模式 为2 代表历史模式中存在base参数 https://next.router.vuejs.org/zh/api/#%E5%8F%82%E6%95%B0-1 @@ -372,23 +285,6 @@ function getHistoryMode(routerHistory): RouterHistory { } } -/** 获取当前页面按钮级别的权限 */ -function getAuths(): Array { - return router.currentRoute.value.meta.auths as Array; -} - -/** 是否有按钮级别的权限(根据路由`meta`中的`auths`字段进行判断)*/ -function hasAuth(value: string | Array): boolean { - if (!value) return false; - /** 从当前路由的`meta`字段里获取按钮级别的所有自定义`code`值 */ - const metaAuths = getAuths(); - if (!metaAuths) return false; - const isAuths = isString(value) - ? metaAuths.includes(value) - : isIncludeAllChildren(value, metaAuths); - return isAuths ? true : false; -} - function handleTopMenu(route) { if (route?.children && route.children.length > 1) { if (route.redirect) { @@ -411,17 +307,13 @@ function getTopMenu(tag = false): menuType { } export { - hasAuth, - getAuths, ascending, filterTree, - initRouter, getTopMenu, addPathMatch, isOneOfArray, hasRoutePermission, getHistoryMode, - addAsyncRoutes, getParentPaths, findRouteByPath, handleAliveRoute, diff --git a/src/utils/auth.ts b/src/utils/auth.ts index 2d00371..e95a209 100644 --- a/src/utils/auth.ts +++ b/src/utils/auth.ts @@ -100,6 +100,15 @@ export const hasPerms = (value: string | Array): boolean => { : isIncludeAllChildren(value, permissions); }; +/** 是否拥有后端返回的菜单入口。 */ +export const hasMenuAccess = (menuKey: string): boolean => { + if (hasPerms("*")) return true; + + const menus = getUserInfo()?.permissionMenus ?? []; + + return menus.some(item => item.key === menuKey); +}; + /** 是否拥有后端菜单动作权限,用于比权限码更细的按钮显隐。 */ export const hasMenuAction = (menuKey: string, action: string): boolean => { if (hasPerms("*")) return true; diff --git a/src/utils/sso.ts b/src/utils/sso.ts index d4a34f9..957dc38 100644 --- a/src/utils/sso.ts +++ b/src/utils/sso.ts @@ -2,7 +2,7 @@ import { removeToken, setToken, type DataInfo } from "./auth"; import { subBefore, getQueryMap } from "@pureadmin/utils"; /** - * 简版前端单点登录,根据实际业务自行编写,平台启动后本地可以跳后面这个链接进行测试 http://localhost:8848/#/permission/page/index?username=sso&roles=admin&accessToken=eyJhbGciOiJIUzUxMiJ9.admin + * 简版前端单点登录,根据实际业务自行编写,平台启动后本地可以跳后面这个链接进行测试 http://localhost:8848/#/employees?username=sso&roles=admin&accessToken=eyJhbGciOiJIUzUxMiJ9.admin * 划重点: * 判断是否为单点登录,不为则直接返回不再进行任何逻辑处理,下面是单点登录后的逻辑处理 * 1.清空本地旧信息; diff --git a/src/views/employees/index.vue b/src/views/employees/index.vue index 9d20ee8..f4fb442 100644 --- a/src/views/employees/index.vue +++ b/src/views/employees/index.vue @@ -12,7 +12,7 @@ import { getEmployee, listEmployees, listRoles, - listStores, + listStoreOptions, updateEmployee, updateEmployeeStatus, type Employee, @@ -21,7 +21,7 @@ import { type RoleOption, type StoreOption } from "@/api/access"; -import { hasPerms } from "@/utils/auth"; +import { hasMenuAction, hasPerms } from "@/utils/auth"; import Plus from "~icons/ep/plus"; import Search from "~icons/ep/search"; @@ -107,7 +107,12 @@ const inactiveCount = computed( () => employees.value.filter(item => item.status === "INACTIVE").length ); const dialogTitle = computed(() => (form.id ? "编辑员工" : "新增员工")); -const canManageEmployees = computed(() => hasPerms("employee:manage")); +const canCreateEmployee = computed(() => hasMenuAction("employees", "create")); +const canUpdateEmployee = computed(() => hasMenuAction("employees", "update")); +const canDeleteEmployee = computed(() => hasMenuAction("employees", "delete")); +const canOperateEmployee = computed( + () => canUpdateEmployee.value || canDeleteEmployee.value +); const canViewAllEmployees = computed(() => hasPerms("employee:view:all")); function getErrorMessage(error: unknown, fallback: string) { @@ -158,15 +163,17 @@ function buildPayload(): EmployeePayload { /** 门店和角色是员工表单的基础字典,打开弹窗前必须先加载。 */ async function fetchCatalog() { const shouldLoadStores = - canViewAllEmployees.value || canManageEmployees.value; - const shouldLoadRoles = canManageEmployees.value; + canViewAllEmployees.value || + canCreateEmployee.value || + canUpdateEmployee.value; + const shouldLoadRoles = canCreateEmployee.value || canUpdateEmployee.value; if (!shouldLoadStores && !shouldLoadRoles) return; catalogLoading.value = true; try { const [storeResult, roleResult] = await Promise.all([ - shouldLoadStores ? listStores() : Promise.resolve({ data: [] }), + shouldLoadStores ? listStoreOptions() : Promise.resolve({ data: [] }), shouldLoadRoles ? listRoles() : Promise.resolve({ data: [] }) ]); stores.value = storeResult.data; @@ -227,14 +234,14 @@ function handleSizeChange(pageSize: number) { } function openCreateDialog() { - if (!canManageEmployees.value) return; + if (!canCreateEmployee.value) return; resetFormState(); dialogVisible.value = true; } /** 编辑前重新拉详情,确保角色绑定不是来自列表摘要的过期数据。 */ async function openEditDialog(row: Employee) { - if (!canManageEmployees.value) return; + if (!canUpdateEmployee.value) return; try { const result = await getEmployee(row.id); const employee = result.data; @@ -255,7 +262,7 @@ async function openEditDialog(row: Employee) { } async function submitForm() { - if (!canManageEmployees.value) return; + if (form.id ? !canUpdateEmployee.value : !canCreateEmployee.value) return; await formRef.value?.validate(); submitLoading.value = true; @@ -280,7 +287,7 @@ async function submitForm() { } async function toggleStatus(row: Employee) { - if (!canManageEmployees.value) return; + if (!canUpdateEmployee.value) return; const nextStatus: EmployeeStatus = row.status === "ACTIVE" ? "INACTIVE" : "ACTIVE"; const action = nextStatus === "ACTIVE" ? "启用" : "停用"; @@ -306,7 +313,7 @@ async function toggleStatus(row: Employee) { } async function removeEmployee(row: Employee) { - if (!canManageEmployees.value) return; + if (!canDeleteEmployee.value) return; try { await ElMessageBox.confirm( `删除后员工「${row.name}」会被软删除并停用,确认继续?`, @@ -345,7 +352,7 @@ onMounted(async () => {

员工管理

{