From 746f5d85ecea5d41bd734d18b9bbca6a5e119457 Mon Sep 17 00:00:00 2001 From: zhonghua Date: Wed, 1 Apr 2026 19:30:33 +0800 Subject: [PATCH] =?UTF-8?q?=E6=9A=82=E5=AD=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ...6d-backend-frontend-migration-inventory.md | 185 ++ docs/migration/default-role-matrix.md | 40 + docs/migration/permission-inventory.md | 91 + docs/migration/permission-system.md | 456 +++ docs/migration/rbac-audit.sql | 159 + docs/migration/verify-matrix.md | 54 + .../common/enums/AI3DGenerateTypeEnum.java | 32 - .../common/enums/AI3DTaskStatusEnum.java | 33 - .../controller/AI3DTaskController.java | 92 - .../controller/AnalyticsController.java | 50 + .../controller/ContestController.java | 1 + .../controller/ContestJudgeController.java | 12 +- .../controller/ContestNoticeController.java | 8 +- .../ContestPresetCommentController.java | 1 + .../controller/ContestReviewController.java | 8 +- .../controller/ContestWorkController.java | 1 + .../controller/HomeworkController.java | 2 + .../creation/controller/MenuController.java | 7 +- .../creation/controller/PublicController.java | 76 + .../creation/dto/ai3d/AI3DTaskQueryDTO.java | 24 - .../creation/dto/ai3d/CreateAI3DTaskDTO.java | 26 - .../creation/dto/menu/CreateMenuDTO.java | 3 + .../creation/dto/menu/UpdateMenuDTO.java | 3 + .../lesingle/creation/entity/AI3DTask.java | 100 - .../com/lesingle/creation/entity/Menu.java | 6 + .../creation/entity/UserWorkFavorite.java | 33 + .../creation/entity/UserWorkLike.java | 33 + .../creation/mapper/AnalyticsMapper.java | 236 ++ .../mapper/UserWorkFavoriteMapper.java | 65 + ...askMapper.java => UserWorkLikeMapper.java} | 9 +- .../creation/service/AI3DTaskService.java | 62 - .../creation/service/AnalyticsService.java | 12 + .../creation/service/InteractionService.java | 23 + .../creation/service/MenuService.java | 9 + .../service/impl/AI3DTaskServiceImpl.java | 190 -- .../service/impl/AnalyticsServiceImpl.java | 266 ++ .../service/impl/AuthServiceImpl.java | 40 +- .../service/impl/InteractionServiceImpl.java | 218 ++ .../service/impl/MenuServiceImpl.java | 319 +- .../service/impl/RoleServiceImpl.java | 55 +- .../service/impl/TenantServiceImpl.java | 80 +- .../service/impl/UserDetailsServiceImpl.java | 28 +- .../lesingle/creation/vo/ai3d/AI3DTaskVO.java | 56 - .../vo/analytics/AnalyticsOverviewVO.java | 62 + .../vo/analytics/AnalyticsReviewVO.java | 48 + .../creation/vo/menu/MenuDetailVO.java | 3 + .../lesingle/creation/vo/menu/MenuTreeVO.java | 3 + .../vo/publicwork/InteractionStatusVO.java | 16 + .../creation/vo/publicwork/MyFavoritesVO.java | 44 + .../vo/publicwork/ToggleFavoriteVO.java | 16 + .../creation/vo/publicwork/ToggleLikeVO.java | 16 + .../V30__create_ugc_user_work_interaction.sql | 31 + ...1__init_portal_roles_permissions_menus.sql | 209 ++ .../migration/V32__rbac_align_all_tenants.sql | 203 ++ ...c_permission_code_aliases_and_menu_fix.sql | 139 + .../V34__split_menu_scene_portal_tenant.sql | 28 + ..._super_admin_all_permissions_and_menus.sql | 49 + .../V36__align_super_tenant_flag_by_type.sql | 13 + .../V37__align_super_tenant_super_code.sql | 14 + java-frontend/package.json | 2 + java-frontend/scripts/extract-perms.cjs | 60 + java-frontend/src/api/ai-3d.ts | 127 - java-frontend/src/api/analytics.ts | 63 + java-frontend/src/api/menus.ts | 10 +- java-frontend/src/api/public.ts | 14 +- java-frontend/src/components/ModelViewer.vue | 45 - java-frontend/src/layouts/BasicLayout.vue | 273 +- java-frontend/src/router/index.ts | 59 +- java-frontend/src/stores/auth.ts | 121 +- java-frontend/src/utils/menu.ts | 25 +- .../activities/components/ReviewWorkModal.vue | 51 +- .../src/views/analytics/Overview.vue | 239 ++ java-frontend/src/views/analytics/Review.vue | 220 ++ java-frontend/src/views/contests/Guidance.vue | 39 - .../contests/components/SubmitWorkDrawer.vue | 303 +- .../contests/components/ViewWorkDrawer.vue | 95 +- .../contests/components/WorkDetailModal.vue | 72 - java-frontend/src/views/model/ModelViewer.vue | 2360 -------------- java-frontend/src/views/public/Gallery.vue | 43 +- .../src/views/public/mine/Favorites.vue | 139 + java-frontend/src/views/public/mine/Index.vue | 19 +- java-frontend/src/views/public/mine/Works.vue | 123 - .../src/views/public/works/Detail.vue | 119 +- .../src/views/system/roles/Index.vue | 39 +- .../src/views/system/tenants/Index.vue | 5 +- .../src/views/workbench/ai-3d/Generate.vue | 1081 ------- .../src/views/workbench/ai-3d/History.vue | 961 ------ .../src/views/workbench/ai-3d/Index.vue | 2822 ----------------- java-frontend/tsconfig.node.tsbuildinfo | 2 +- java-frontend/tsconfig.tsbuildinfo | 2 +- java-frontend/vite.config.js | 38 + 91 files changed, 4485 insertions(+), 9184 deletions(-) create mode 100644 docs/migration/781416d-backend-frontend-migration-inventory.md create mode 100644 docs/migration/default-role-matrix.md create mode 100644 docs/migration/permission-inventory.md create mode 100644 docs/migration/permission-system.md create mode 100644 docs/migration/rbac-audit.sql create mode 100644 docs/migration/verify-matrix.md delete mode 100644 java-backend/src/main/java/com/lesingle/creation/common/enums/AI3DGenerateTypeEnum.java delete mode 100644 java-backend/src/main/java/com/lesingle/creation/common/enums/AI3DTaskStatusEnum.java delete mode 100644 java-backend/src/main/java/com/lesingle/creation/controller/AI3DTaskController.java create mode 100644 java-backend/src/main/java/com/lesingle/creation/controller/AnalyticsController.java delete mode 100644 java-backend/src/main/java/com/lesingle/creation/dto/ai3d/AI3DTaskQueryDTO.java delete mode 100644 java-backend/src/main/java/com/lesingle/creation/dto/ai3d/CreateAI3DTaskDTO.java delete mode 100644 java-backend/src/main/java/com/lesingle/creation/entity/AI3DTask.java create mode 100644 java-backend/src/main/java/com/lesingle/creation/entity/UserWorkFavorite.java create mode 100644 java-backend/src/main/java/com/lesingle/creation/entity/UserWorkLike.java create mode 100644 java-backend/src/main/java/com/lesingle/creation/mapper/AnalyticsMapper.java create mode 100644 java-backend/src/main/java/com/lesingle/creation/mapper/UserWorkFavoriteMapper.java rename java-backend/src/main/java/com/lesingle/creation/mapper/{AI3DTaskMapper.java => UserWorkLikeMapper.java} (50%) delete mode 100644 java-backend/src/main/java/com/lesingle/creation/service/AI3DTaskService.java create mode 100644 java-backend/src/main/java/com/lesingle/creation/service/AnalyticsService.java create mode 100644 java-backend/src/main/java/com/lesingle/creation/service/InteractionService.java delete mode 100644 java-backend/src/main/java/com/lesingle/creation/service/impl/AI3DTaskServiceImpl.java create mode 100644 java-backend/src/main/java/com/lesingle/creation/service/impl/AnalyticsServiceImpl.java create mode 100644 java-backend/src/main/java/com/lesingle/creation/service/impl/InteractionServiceImpl.java delete mode 100644 java-backend/src/main/java/com/lesingle/creation/vo/ai3d/AI3DTaskVO.java create mode 100644 java-backend/src/main/java/com/lesingle/creation/vo/analytics/AnalyticsOverviewVO.java create mode 100644 java-backend/src/main/java/com/lesingle/creation/vo/analytics/AnalyticsReviewVO.java create mode 100644 java-backend/src/main/java/com/lesingle/creation/vo/publicwork/InteractionStatusVO.java create mode 100644 java-backend/src/main/java/com/lesingle/creation/vo/publicwork/MyFavoritesVO.java create mode 100644 java-backend/src/main/java/com/lesingle/creation/vo/publicwork/ToggleFavoriteVO.java create mode 100644 java-backend/src/main/java/com/lesingle/creation/vo/publicwork/ToggleLikeVO.java create mode 100644 java-backend/src/main/resources/db/migration/V30__create_ugc_user_work_interaction.sql create mode 100644 java-backend/src/main/resources/db/migration/V31__init_portal_roles_permissions_menus.sql create mode 100644 java-backend/src/main/resources/db/migration/V32__rbac_align_all_tenants.sql create mode 100644 java-backend/src/main/resources/db/migration/V33__rbac_permission_code_aliases_and_menu_fix.sql create mode 100644 java-backend/src/main/resources/db/migration/V34__split_menu_scene_portal_tenant.sql create mode 100644 java-backend/src/main/resources/db/migration/V35__grant_portal_super_admin_all_permissions_and_menus.sql create mode 100644 java-backend/src/main/resources/db/migration/V36__align_super_tenant_flag_by_type.sql create mode 100644 java-backend/src/main/resources/db/migration/V37__align_super_tenant_super_code.sql create mode 100644 java-frontend/scripts/extract-perms.cjs delete mode 100644 java-frontend/src/api/ai-3d.ts create mode 100644 java-frontend/src/api/analytics.ts delete mode 100644 java-frontend/src/components/ModelViewer.vue create mode 100644 java-frontend/src/views/analytics/Overview.vue create mode 100644 java-frontend/src/views/analytics/Review.vue delete mode 100644 java-frontend/src/views/model/ModelViewer.vue create mode 100644 java-frontend/src/views/public/mine/Favorites.vue delete mode 100644 java-frontend/src/views/public/mine/Works.vue delete mode 100644 java-frontend/src/views/workbench/ai-3d/Generate.vue delete mode 100644 java-frontend/src/views/workbench/ai-3d/History.vue delete mode 100644 java-frontend/src/views/workbench/ai-3d/Index.vue create mode 100644 java-frontend/vite.config.js diff --git a/docs/migration/781416d-backend-frontend-migration-inventory.md b/docs/migration/781416d-backend-frontend-migration-inventory.md new file mode 100644 index 0000000..1d2ce43 --- /dev/null +++ b/docs/migration/781416d-backend-frontend-migration-inventory.md @@ -0,0 +1,185 @@ +# 迁移对照清单(merge commit `781416d`) + +> 范围口径:仅统计 `781416d^1..781416d` 引入的变更,且只关注 `backend/` 与 `frontend/` 目录。 +> 迁移目标:在新工程 `java-backend/`(Spring Boot 3.2 + MyBatis-Plus + Flyway)与 `java-frontend/`(Vue3 + Vite + Ant Design Vue)实现等价功能。 + +--- + +## 1. 总览(按类别) + +### 1.1 DB(Prisma → Flyway / Java 实体) + +- **副本变更文件** + - `backend/prisma/schema.prisma`(本次仅 1 行变更,但它是 DB 迁移的入口文件,需核对差异对应的表/字段) +- **Java 目标** + - `java-backend/src/main/resources/db/migration/`(新增增量脚本) + - `java-backend/src/main/java/com/lesingle/creation/entity/`(实体字段对齐) + - `java-backend/src/main/java/com/lesingle/creation/mapper/`(必要查询/统计 SQL、交互表读写) + +### 1.2 API(后端接口变更/新增) + +#### A. Analytics(租户端数据统计看板) + +- **副本新增** + - `backend/src/contests/analytics/analytics.controller.ts` + - `backend/src/contests/analytics/analytics.service.ts` + - `backend/src/contests/analytics/analytics.module.ts` +- **接口** + - `GET /api/analytics/overview` + - `GET /api/analytics/review` +- **设计依据** + - `docs/design/org-admin/data-analytics-dashboard.md` +- **Java 目标** + - `java-backend/.../controller/AnalyticsController.java` + - `java-backend/.../service/AnalyticsService.java`(+ impl) + - `java-backend/.../mapper/AnalyticsMapper.java`(聚合统计 SQL:趋势/漏斗/对比/工作量/奖项分布) + +#### B. Public Interaction(公众端点赞/收藏/交互状态/我的收藏) + +- **副本新增** + - `backend/src/public/interaction.service.ts` +- **副本改动接入点** + - `backend/src/public/public.controller.ts` + - `backend/src/public/public.service.ts` + - `backend/src/public/public.module.ts` + - `backend/src/public/gallery.service.ts` +- **接口(设计稿口径)** + - `POST /api/public/works/:id/like` + - `POST /api/public/works/:id/favorite` + - `GET /api/public/works/:id/interaction` + - `GET /api/public/mine/favorites` +- **设计依据** + - `docs/design/public/like-favorite.md` +- **Java 目标** + - `java-backend/.../controller/PublicInteractionController.java`(或并入现有 public controller) + - `java-backend/.../service/PublicInteractionService.java` + - `java-backend/.../mapper/UserWorkLikeMapper.java`、`UserWorkFavoriteMapper.java` + - `java-backend/.../mapper/UserWorkMapper.java`(冗余计数 `like_count`/`favorite_count` 更新) + - 事务:toggle 需要「记录表 + 冗余计数」同事务 + +#### C. Contest / Work / Review / Registration / Result / Notice 联动调整 + +- **副本改动文件** + - `backend/src/contests/contests/contests.controller.ts` + - `backend/src/contests/contests/contests.service.ts` + - `backend/src/contests/registrations/registrations.controller.ts` + - `backend/src/contests/registrations/registrations.service.ts` + - `backend/src/contests/works/dto/query-work.dto.ts` + - `backend/src/contests/works/works.service.ts` + - `backend/src/contests/reviews/reviews.service.ts` + - `backend/src/contests/results/dto/auto-set-awards.dto.ts` + - `backend/src/contests/results/dto/set-award.dto.ts` + - `backend/src/contests/results/results.service.ts` + - `backend/src/contests/notices/dto/query-notice.dto.ts` + - `backend/src/contests/notices/notices.service.ts` +- **Java 目标** + - 对齐到 `java-backend/.../controller/*Contest*.java`、`*Work*.java`、`*Review*.java`、`*Registration*.java`、`*Result*.java`、`*Notice*.java` + - 重点关注:DTO 结构变化、筛选字段、批量操作、发布/撤销流程、进度统计口径 + +#### D. Content / Tag / Gallery / Content Review(超管端内容管理) + +- **副本改动文件** + - `backend/src/public/content-review.controller.ts` + - `backend/src/public/content-review.service.ts` + - `backend/src/public/tags.controller.ts` + - `backend/src/public/tags.service.ts` + - `backend/src/public/gallery.service.ts`(含推荐作品/广场联动) +- **Java 目标** + - `java-backend/.../controller/ContentReviewController.java` + - `java-backend/.../controller/TagsController.java` + - `java-backend/.../controller/GalleryController.java`(或 public/gallery controller) + - 对齐:批量审核、撤销审核、推荐作品列表、标签颜色/排序/使用次数 + +#### E. Tenant / User(租户/用户联动修复) + +- **副本改动文件** + - `backend/src/tenants/tenants.controller.ts` + - `backend/src/tenants/tenants.service.ts` + - `backend/src/users/users.controller.ts` + - `backend/src/users/users.service.ts` +- **Java 目标** + - `java-backend/.../controller/TenantController.java`、`UserController.java` 等 + - 重点关注:超管跨租户逻辑、403 修复、重置密码/查询条件差异 + +### 1.3 前端(页面 / API / 路由布局) + +#### A. Analytics 页面与 API + +- **副本新增** + - `frontend/src/api/analytics.ts` + - `frontend/src/views/analytics/Overview.vue` + - `frontend/src/views/analytics/Review.vue` +- **Java 前端目标** + - `java-frontend/src/api/analytics.ts` + - `java-frontend/src/views/analytics/Overview.vue` + - `java-frontend/src/views/analytics/Review.vue` + - `java-frontend/src/router/*` + 菜单系统接入「数据统计」 + +#### B. 公众端:点赞/收藏/我的收藏 + 布局响应式 + +- **副本新增/改动** + - `frontend/src/views/public/mine/Favorites.vue`(新增:我的收藏) + - `frontend/src/views/public/mine/Index.vue`(新增入口) + - `frontend/src/views/public/Gallery.vue`(卡片支持点赞交互/推荐位联动) + - `frontend/src/views/public/works/Detail.vue`(详情交互栏) + - `frontend/src/layouts/PublicLayout.vue`(桌面端顶部导航等) + - `frontend/src/api/public.ts`(新增 interaction API) +- **Java 前端目标** + - 同路径迁移到 `java-frontend/src/views/public/...`、`java-frontend/src/layouts/...`、`java-frontend/src/api/public.ts` + +#### C. 机构端/超管端页面大规模优化(活动全链路 + 内容管理) + +- **副本改动页面(高风险联动点)** + - 内容管理:`frontend/src/views/content/TagManagement.vue`、`WorkManagement.vue`、`WorkReview.vue` + - 活动管理:`frontend/src/views/contests/*`(Create/Index/judges/notices/registrations/results/reviews/works 等) + - 系统:`frontend/src/views/system/tenants/Index.vue`、`system/users/Index.vue`、`system/tenant-info/Index.vue`(新增机构信息页) + - 工作台:`frontend/src/views/workbench/TenantDashboard.vue`(新增租户端工作台首页) + - 登录:`frontend/src/views/auth/Login.vue`(冲突修复) +- **Java 前端目标** + - 对应迁移到 `java-frontend/src/views/...`,并对齐与 Java 后端接口/权限点 + +### 1.4 菜单与路由(动态菜单/初始化同步) + +- **副本后端菜单** + - `backend/data/menus.json`(菜单注册:含「数据统计」「我的收藏」等入口变化) + - `backend/scripts/init-menus.ts`(初始化同步逻辑变更) +- **副本前端菜单/路由** + - `frontend/src/router/index.ts` + - `frontend/src/utils/menu.ts` +- **Java 目标** + - 如果 Java 前端菜单来自后端:对齐 `java-backend` 的菜单接口与初始化数据(或迁移 `menus.json` 到 Java 的菜单种子机制) + - 如果 Java 前端本地生成菜单:对齐 `java-frontend` 的路由与 menu 构建逻辑 + +--- + +## 2. 迁移对照表(文件级) + +> 说明:下面按文件列出“副本变更 → 迁移类别 → Java 目标 → 验收点”。 +> 状态:`TODO` 表示尚未迁移;完成迁移后在对应条目补充“已迁移”的 commit/说明(不要改动本文件的口径范围)。 + +| 副本变更文件 | 类别 | Java 目标(建议) | 验收点 | +|---|---|---|---| +| `backend/src/contests/analytics/analytics.controller.ts` | API | `java-backend/.../controller/AnalyticsController.java` | overview/review 两接口路径/参数/结构一致 | +| `backend/src/contests/analytics/analytics.service.ts` | API/SQL | `java-backend/.../service/AnalyticsService*` + `AnalyticsMapper` | 指标口径一致,SQL 参数化且含 tenant 过滤 | +| `backend/src/public/interaction.service.ts` | API/DB | `PublicInteractionService*` + like/favorite mapper | toggle 事务一致性、计数正确、并发安全 | +| `backend/src/public/public.controller.ts` | API | public 端 controller(整合交互相关路由) | 路由与鉴权一致 | +| `backend/src/public/gallery.service.ts` | API | gallery service/mapper | recommended 列表与交互状态返回一致 | +| `backend/src/public/content-review.*` | API/DB | content review controller/service/mapper | 批量通过/拒绝/撤销,列表筛选一致 | +| `backend/src/public/tags.*` | API/DB | tags controller/service/mapper | 标签颜色/排序/使用次数、CRUD 一致 | +| `backend/prisma/schema.prisma` | DB | Flyway 脚本 + entity 对齐 | 表/字段/索引对齐(逻辑删除与租户字段) | +| `backend/data/menus.json` | 菜单 | Java 菜单种子/初始化 | 菜单能驱动路由与权限展示 | +| `frontend/src/api/analytics.ts` | 前端/API | `java-frontend/src/api/analytics.ts` | 请求路径/参数/响应适配 | +| `frontend/src/views/analytics/*` | 前端 | `java-frontend/src/views/analytics/*` | 看板可正常加载、图表展示 | +| `frontend/src/api/public.ts` | 前端/API | `java-frontend/src/api/public.ts` | like/favorite/interaction/favorites API 可用 | +| `frontend/src/views/public/mine/Favorites.vue` | 前端 | `java-frontend/.../Favorites.vue` | 我的收藏可分页、跳转作品详情正常 | +| `frontend/src/layouts/PublicLayout.vue` | 前端/布局 | `java-frontend/.../PublicLayout.vue` | 桌面端导航/响应式不回归 | +| `frontend/src/router/index.ts`、`frontend/src/utils/menu.ts` | 路由/菜单 | `java-frontend` 对应路由与菜单构建 | 「数据统计」「我的收藏」入口可达、权限控制正确 | + +--- + +## 3. 关键注意事项(迁移时强约束) + +- **多租户**:所有业务数据查询必须带 `tenantId`;超管可跨租户(Java 端以 `UserPrincipal` 判定)。 +- **统计 SQL 安全**:副本工程存在拼接 `IN (ids.join(','))` 的风险,Java 端必须用 MyBatis 动态 SQL 参数化。 +- **交互 toggle 事务**:点赞/收藏需要「记录表 + 冗余计数」同事务,并防止并发下计数异常。 + diff --git a/docs/migration/default-role-matrix.md b/docs/migration/default-role-matrix.md new file mode 100644 index 0000000..3916d95 --- /dev/null +++ b/docs/migration/default-role-matrix.md @@ -0,0 +1,40 @@ +# 默认角色/菜单/权限 最小可用集(全租户) + +本文件用于指导 Flyway “全租户补齐”迁移脚本的默认授权策略,目标是让任一租户创建后即可登录、看到菜单、并具备最小可用功能入口。 + +## 角色(role.code) +- `super_admin`:平台超管(通常仅超级租户/运维使用) +- `tenant_admin`:租户管理员(每个租户至少 1 个) +- `school_admin`:学校管理端角色 +- `teacher`:教师端角色 +- `student`:学生端角色 +- `judge`:评委端角色 + +> 注:目前历史迁移仅对 `tenant_id=1` 初始化了 `school_admin/teacher/student/judge`,但线上实际登录已出现 `tenant_admin`,因此迁移需对全租户补齐该角色。 + +## 菜单可见性(t_auth_role_menu) + +### tenant_admin(租户管理员) +- **默认策略**:可见全部管理端菜单,但**不包含平台级租户管理**(避免非平台人员管理其他租户)。\n + - ✅ 包含:除 `permission='super_admin'` 以及 `permission LIKE 'tenant:%'` 的菜单外的所有 `t_auth_menu`。\n + - ✅ 祖先节点补齐:即使祖先菜单未直接授权,也应在接口层补齐返回。\n + - ✅ 说明:最终可见菜单仍以 `t_auth_role_menu` 为准;如需更精细裁剪,可在系统中二次授权。 + +### school_admin / teacher / student / judge +- **默认策略**:优先按 `tenant_id=1` 的同名角色授权模板复制到其他租户(同 role.code)。\n + - 复制范围:`t_auth_role_menu` 中 `role_id` 对应 `tenant_id=1` 且 `role.code IN ('school_admin','teacher','student','judge')` 的记录。\n + - 如果模板不存在,则降级为:仅授予 `workbench` + 与角色端相关的一级菜单(例如 `activities`/`homework`)。 + +## 权限(t_auth_role_permission) + +### 权限来源 +- 以 `tenant_id=1` 为“权限码模板”:将 `t_auth_permission(tenant_id=1)` 中的权限码复制到所有租户(缺失则补齐)。\n + - 原因:权限表按租户隔离,但权限码是全局统一的前后端契约;菜单/路由/按钮依赖这些 code。 + +### tenant_admin(建议最小集) +- **系统管理(租户内)**:`user:*`、`role:*`、`permission:*`、`menu:*`、`dict:*`、`config:*`、`log:*`\n +- **业务最小可用**:建议至少包含 `contest:*`、`registration:*`、`judge:*`、`notice:*`、`homework:*`、`work:*`、`activity:*`、`review:score`、以及前端提取的权限码(见 `docs/migration/permission-inventory.md`)。 + +### 其他角色 +- 默认按 `tenant_id=1` 角色模板复制 `t_auth_role_permission`;不存在模板时至少保证页面可进入(满足路由 `meta.permissions`)。\n + diff --git a/docs/migration/permission-inventory.md b/docs/migration/permission-inventory.md new file mode 100644 index 0000000..eca0e82 --- /dev/null +++ b/docs/migration/permission-inventory.md @@ -0,0 +1,91 @@ +# 前端权限码清单(自动提取) + +来源: +- `v-permission`(按钮/操作级) +- 路由 `meta.permissions`(页面/路由级) +- 逻辑校验 `hasPermission/hasAnyPermission` + +提取脚本:`java-frontend/scripts/extract-perms.cjs` + +## v-permission(按钮/操作) +- `class:create` +- `class:delete` +- `class:update` +- `config:create` +- `config:delete` +- `config:update` +- `contest:create` +- `contest:delete` +- `contest:publish` +- `contest:update` +- `department:create` +- `department:delete` +- `department:update` +- `dict:create` +- `dict:delete` +- `dict:update` +- `grade:create` +- `grade:delete` +- `grade:update` +- `homework:create` +- `homework:delete` +- `homework:read` +- `homework:update` +- `judge:create` +- `judge:delete` +- `judge:read` +- `judge:update` +- `log:delete` +- `menu:create` +- `menu:delete` +- `menu:update` +- `notice:create` +- `notice:delete` +- `notice:update` +- `role:create` +- `role:delete` +- `role:update` +- `school:create` +- `school:update` +- `student:create` +- `student:delete` +- `student:update` +- `teacher:create` +- `teacher:delete` +- `teacher:update` +- `tenant:create` +- `tenant:delete` +- `tenant:update` + +## route.meta.permissions(页面/路由) +- `activity:read` +- `contest:create` +- `contest:read` +- `contest:update` +- `homework:read` +- `registration:read` +- `review:score` +- `work:read` + +## logic checks(脚本/逻辑判断) +- `ai-3d:create` +- `class:create` +- `class:delete` +- `config:create` +- `department:create` +- `department:delete` +- `dict:create` +- `grade:create` +- `grade:delete` +- `menu:create` +- `menu:read` +- `permission:read` +- `role:create` +- `role:read` +- `school:create` +- `school:update` +- `student:create` +- `student:delete` +- `teacher:create` +- `teacher:delete` + diff --git a/docs/migration/permission-system.md b/docs/migration/permission-system.md new file mode 100644 index 0000000..1fb00df --- /dev/null +++ b/docs/migration/permission-system.md @@ -0,0 +1,456 @@ +# 超管端权限系统设计文档 + +> 整理日期:2026-04-01 +> 目的:为后端 Java 转写提供权限逻辑参考,涵盖机构管理、用户中心、系统设置三大模块 + +--- + +## 1. 整体架构 + +### 1.1 多租户 RBAC 模型 + +系统采用 **多租户 + 角色权限(RBAC)** 架构,核心数据模型关系如下: + +``` +Tenant (租户) + ├── TenantMenu (M2M) ──→ Menu (菜单模板,全局共享) + ├── User (用户) + │ └── UserRole (M2M) ──→ Role (角色,租户隔离) + │ └── RolePermission (M2M) ──→ Permission (权限,租户隔离) + └── Config (系统配置,租户隔离) +``` + +### 1.2 核心模型字段 + +#### Tenant(租户) + +| 字段 | 类型 | 说明 | +|------|------|------| +| id | Int (PK) | 主键 | +| code | String (UNIQUE) | 租户编码,用于登录 URL | +| domain | String (UNIQUE) | 子域名访问 | +| isSuper | Int | 0=普通租户,1=超级租户 | +| validState | Int | 1=启用,2=禁用 | +| creator / modifier | String | 审计字段 | + +#### User(用户) + +| 字段 | 类型 | 说明 | +|------|------|------| +| id | Int (PK) | 主键 | +| tenantId | Int (FK) | 所属租户 | +| username | String | 租户内唯一 | +| email / phone | String | 租户内唯一 | +| userType | String | adult / child | +| userSource | String | admin_created / self_registered / child_migrated | +| validState | Int | 1=启用,2=禁用 | + +#### Role(角色)—— 租户隔离 + +| 字段 | 类型 | 说明 | +|------|------|------| +| id | Int (PK) | 主键 | +| tenantId | Int (FK) | 所属租户 | +| code | String | 租户内唯一,`super_admin` 为特殊角色 | +| name | String | 角色名称 | + +#### Permission(权限)—— 租户隔离 + +| 字段 | 类型 | 说明 | +|------|------|------| +| id | Int (PK) | 主键 | +| tenantId | Int (FK) | 所属租户 | +| code | String | 权限码,格式:`{resource}:{action}` | +| resource | String | 资源标识 | +| action | String | 操作标识 | + +唯一约束:`(tenantId, resource, action)` + +#### Menu(菜单)—— 全局共享 + +| 字段 | 类型 | 说明 | +|------|------|------| +| id | Int (PK) | 主键 | +| parentId | Int | 父级菜单,支持多级嵌套 | +| permission | String | 关联的权限码,控制前端可见性 | +| validState | Int | 1=启用,2=禁用 | + +#### TenantMenu(租户-菜单关联)—— 中间表 + +| 字段 | 类型 | 说明 | +|------|------|------| +| tenantId | Int (FK) | 租户 ID | +| menuId | Int (FK) | 菜单 ID | + +--- + +## 2. 鉴权链路 + +每个请求依次经过四层守卫,任一层失败即拒绝: + +``` +请求进入 + │ + ▼ +┌─────────────────────────────────────────────┐ +│ ① JwtAuthGuard │ +│ 校验 JWT Token,解出 {userId, tenantId} │ +│ 文件: auth/guards/jwt-auth.guard.ts │ +└──────────────────┬──────────────────────────┘ + ▼ +┌─────────────────────────────────────────────┐ +│ ② TenantGuard │ +│ 提取租户上下文(优先级从高到低): │ +│ 1. Header: x-tenant-id(直接 ID) │ +│ 2. Header: x-tenant-code(按 code 查询) │ +│ 3. 子域名解析 │ +│ 4. JWT 中的 tenantId │ +│ 校验:租户存在 && validState = 1 │ +│ 注入:request.tenantId, request.tenant │ +│ 文件: tenants/guards/tenant.guard.ts │ +└──────────────────┬──────────────────────────┘ + ▼ +┌─────────────────────────────────────────────┐ +│ ③ RolesGuard(可选,按需装饰) │ +│ @Roles('super_admin', 'tenant_admin') │ +│ OR 逻辑:用户拥有任一指定角色即通过 │ +│ 文件: auth/guards/roles.guard.ts │ +└──────────────────┬──────────────────────────┘ + ▼ +┌─────────────────────────────────────────────┐ +│ ④ PermissionsGuard(可选,按需装饰) │ +│ @RequirePermission('tenant:create') │ +│ OR 逻辑:用户拥有任一指定权限即通过 │ +│ ⚠️ 重要:super_admin 角色直接放行, │ +│ 不检查具体权限 │ +│ 文件: auth/guards/permissions.guard.ts │ +└─────────────────────────────────────────────┘ +``` + +### 权限解析路径 + +``` +User → UserRole → Role + ├── Role.code == 'super_admin' → 跳过权限检查,直接放行 + └── RolePermission → Permission.code → 与接口要求的权限码比对 +``` + +--- + +## 3. 模块一:机构管理 + +### 3.1 文件位置 + +| 层级 | 文件路径 | +|------|----------| +| Controller | `backend/src/tenants/tenants.controller.ts` | +| Service | `backend/src/tenants/tenants.service.ts` | +| 前端页面 | `frontend/src/views/system/tenants/Index.vue` | +| 前端 API | `frontend/src/api/tenants.ts` | + +### 3.2 接口权限矩阵 + +| 接口路径 | 方法 | 权限码 | 额外校验 | +|----------|------|--------|----------| +| `/tenants` | GET | `tenant:read` | 列表自动排除内部租户 | +| `/tenants` | POST | `tenant:create` | `checkSuperTenant()` | +| `/tenants/:id` | GET | `tenant:read` | — | +| `/tenants/:id` | PATCH | `tenant:update` | `checkSuperTenant()` | +| `/tenants/:id/status` | PATCH | `tenant:update` | `checkSuperTenant()` + 禁止禁用超级租户自身 | +| `/tenants/:id/menus` | GET | `tenant:read` | 查看该租户已分配的菜单树 | +| `/tenants/:id` | DELETE | `tenant:delete` | `checkSuperTenant()` + 禁止删除超级租户 | + +### 3.3 核心逻辑 + +#### checkSuperTenant() + +所有写操作(创建、编辑、删除、启禁用)前,必须校验当前登录用户所属租户的 `isSuper = 1`。非超级租户用户无法执行任何机构管理写操作。 + +``` +伪代码: +function checkSuperTenant(tenantId): + tenant = findById(tenantId) + if tenant.isSuper != 1: + throw ForbiddenException("仅超级租户可执行此操作") +``` + +#### 内部租户隐藏 + +列表查询自动排除以下 code 的租户,这些是系统内部使用的特殊租户: +- `super` — 超级租户 +- `public` — 公众端 +- `school` — 学校 +- `teacher` — 教师 +- `student` — 学生 +- `judge` — 评委 + +#### 菜单分配 + +创建或编辑机构时,可通过 `TenantMenu` 中间表选择该机构可使用的菜单。前端以 checkbox 树形组件展示所有菜单模板供勾选。 + +#### 安全兜底 + +- 禁止禁用超级租户自身(防止系统锁死) +- 禁止删除超级租户 + +--- + +## 4. 模块二:用户中心 + +### 4.1 文件位置 + +| 层级 | 文件路径 | +|------|----------| +| Controller | `backend/src/users/users.controller.ts` | +| Service | `backend/src/users/users.service.ts` | +| 前端页面 | `frontend/src/views/system/users/Index.vue` | +| 前端 API | `frontend/src/api/users.ts` | + +### 4.2 用户分类(超管跨租户视角) + +超管端通过 `userType` 查询参数,按租户类型将用户分为四类: + +| userType 参数值 | 租户筛选条件 | 含义 | +|----------------|-------------|------| +| `platform` | `Tenant.isSuper = 1` | 运营团队(超级租户下的用户) | +| `org` | `Tenant.isSuper = 0 AND code NOT IN ('public', 'judge')` | 机构用户 | +| `judge` | `Tenant.code = 'judge'` | 评委用户 | +| `public` | `Tenant.code = 'public'` | 公众用户 | + +对应实现:`users.service.ts` 的 `buildTenantConditionByUserType()` 方法。 + +### 4.3 接口权限说明 + +用户中心接口**未使用 `@RequirePermission` 装饰器**,权限校验在 Service 层隐式完成: + +| 操作 | 权限控制方式 | 说明 | +|------|-------------|------| +| 跨租户查看用户列表 | Service 层判断 `isSuper` | 仅超级租户用户可跨租户查询 | +| 用户统计 | Service 层判断 `isSuper` | 仅超级租户用户 | +| 创建用户 | Service 层校验 | 超管可在任意租户创建用户 | +| 编辑用户 | Service 层校验 | 超管可编辑任意租户的用户 | +| 启/禁用用户 | Service 层校验 | 禁止禁用租户内最后一个管理员 | +| 删除用户 | Service 层校验 | 超管可删除任意租户用户 | +| 分配角色 | 创建/编辑时指定 | 角色来源:目标用户所属租户的角色列表 | + +### 4.4 核心逻辑 + +#### 跨租户查询 + +超级租户用户(`isSuper = 1`)可查看所有租户的用户,普通租户用户仅能查看和管理本租户用户。 + +#### 账号保护 + +禁止禁用某个租户内的最后一个管理员账号,防止该租户被锁死无法登录管理。 + +``` +伪代码: +function disableUser(userId): + user = findById(userId) + adminCount = countAdminsInTenant(user.tenantId) + if adminCount <= 1 && user.isAdmin: + throw BadRequestException("不能禁用租户内最后一个管理员") +``` + +#### 角色分配 + +创建或编辑用户时,角色选择列表来源于**目标用户所属租户**的角色,而非当前操作者所属租户。即超管在给某机构创建用户时,可分配的角色是该机构下的角色。 + +--- + +## 5. 模块三:系统设置 + +系统设置包含多个子模块,权限策略各不相同。 + +### 5.1 菜单管理 + +| 层级 | 文件路径 | +|------|----------| +| Controller | `backend/src/menus/menus.controller.ts` | +| Service | `backend/src/menus/menus.service.ts` | +| 前端页面 | `frontend/src/views/system/menus/Index.vue` | + +**特点:菜单是全局模板,不按租户隔离,仅超管可管理。** + +#### 用户菜单加载逻辑(findUserMenus) + +``` +输入:userId, tenantId + │ + ├── 用户角色包含 super_admin? + │ ├── 是 → 返回所有有效菜单 + │ └── 否 ↓ + │ + ├── 获取用户所有权限码列表 + │ User → UserRole → Role → RolePermission → Permission.code + │ + ├── 获取租户已分配的菜单 ID 列表 + │ TenantMenu WHERE tenantId = ? + │ + └── 过滤逻辑: + 菜单在租户分配范围内 + AND (菜单无 permission 字段 OR 用户拥有该 permission) + AND (菜单有 path OR 存在可见的子菜单) +``` + +### 5.2 角色管理 + +| 层级 | 文件路径 | +|------|----------| +| Controller | `backend/src/roles/roles.controller.ts` | +| Service | `backend/src/roles/roles.service.ts` | +| 前端页面 | `frontend/src/views/system/roles/Index.vue` | + +**特点:角色按租户隔离,所有操作限定在当前租户内。** + +| 操作 | 守卫 | 说明 | +|------|------|------| +| 查看角色列表 | JwtAuthGuard | 仅返回当前租户的角色 | +| 创建角色 | JwtAuthGuard | 在当前租户内创建 | +| 编辑角色 | JwtAuthGuard | 更新基本信息 + RolePermission 关联 | +| 删除角色 | JwtAuthGuard | 同时清理 UserRole、RolePermission 关联记录 | + +### 5.3 权限管理 + +| 层级 | 文件路径 | +|------|----------| +| Controller | `backend/src/permissions/permissions.controller.ts` | +| Service | `backend/src/permissions/permissions.service.ts` | +| 前端页面 | `frontend/src/views/system/permissions/Index.vue` | + +**特点:仅 `super_admin` 角色可创建/编辑/删除权限。** + +| 操作 | 守卫 | 角色要求 | +|------|------|---------| +| 查看权限列表 | JwtAuthGuard | 所有登录用户可查看 | +| 创建权限 | `@Roles('super_admin')` | 仅 super_admin | +| 编辑权限 | `@Roles('super_admin')` | 仅 super_admin | +| 删除权限 | `@Roles('super_admin')` | 仅 super_admin | + +#### 权限码规范 + +格式:`{resource}:{action}` + +| 资源 | 权限码 | +|------|--------| +| 租户 | `tenant:create` `tenant:read` `tenant:update` `tenant:delete` | +| 用户 | `user:create` `user:read` `user:update` `user:delete` `user:password:update` | +| 角色 | `role:create` `role:read` `role:update` `role:delete` `role:assign` | +| 菜单 | `menu:create` `menu:read` `menu:update` `menu:delete` | +| 字典 | `dict:create` `dict:read` `dict:update` `dict:delete` | +| 配置 | `config:create` `config:read` `config:update` `config:delete` | +| 日志 | `log:read` `log:delete` | +| 活动 | `contest:*` | +| 报名 | `registration:*` | +| 作品 | `work:*` | +| 评审 | `review:*` | +| 成果 | `result:*` | +| 公告 | `notice:*` | + +### 5.4 其他超管专属子模块 + +| 子模块 | 权限码前缀 | 说明 | +|--------|-----------|------| +| 数据字典 | `dict:*` | 全局字典维护 | +| 系统配置 | `config:*` | 按租户隔离的配置项 | +| 日志记录 | `log:*` | 系统操作日志 | + +--- + +## 6. 前端权限控制 + +### 6.1 Auth Store + +文件:`frontend/src/stores/auth.ts` + +| 方法 | 说明 | +|------|------| +| `isSuperAdmin()` | 判断当前用户角色列表是否包含 `super_admin` | +| `hasPermission(code)` | super_admin 直接返回 true;否则检查 `user.permissions[]` | +| `hasAnyPermission(codes[])` | OR 逻辑,满足任一权限即可 | +| `hasRole(role)` | 检查角色列表 | +| `fetchUserMenus()` | 调用 `GET /menus/user-menus`,后端按权限过滤后返回菜单树 | + +### 6.2 权限指令 v-permission + +文件:`frontend/src/directives/permission.ts` + +```html + +新建机构 + + +删除 + + +操作 + + +高级操作 +``` + +| 修饰符 | 行为 | +|--------|------| +| (无) | 禁用元素,灰显不可点击 | +| `.hide` | 从 DOM 隐藏 | +| `.all` | 要求满足全部权限(默认为 OR) | + +--- + +## 7. 菜单可见性分配 + +### 超级租户可见菜单 + +- 活动监管 +- 内容管理 +- 活动管理 +- 机构管理 +- 用户中心 +- 系统设置(全部子项) +- 我的评审 + +### 普通租户默认可见菜单 + +- 工作台 +- 学校管理 +- 我的评审 +- 活动管理 +- 系统设置(仅:用户管理、角色管理) + +--- + +## 8. 数据初始化脚本 + +位于 `backend/scripts/`,Java 端需等价实现: + +| 脚本 | 执行顺序 | 功能 | +|------|---------|------| +| `init-menus.ts` | 1 | 从 `data/menus.json` 创建菜单树,超级租户分配全部菜单 | +| `init-super-tenant.ts` | 2 | 创建超级租户(code=super, isSuper=1)+ 管理员账号 | +| `init-admin-permissions.ts` | 3 | 创建全部权限记录(~90+),赋给 super_admin 角色 | + +### 初始超管账号 + +| 字段 | 值 | +|------|-----| +| 租户 Code | `super` | +| 用户名 | `admin` | +| 密码 | `admin@super`(bcrypt 加密,10 轮 salt) | +| 角色 | `super_admin` | + +--- + +## 9. Java 转写注意事项 + +| 序号 | 要点 | 说明 | +|------|------|------| +| 1 | **super_admin 跳过权限检查** | PermissionsGuard 中判断角色包含 `super_admin` 即放行,不查权限表。Java 端的拦截器/AOP 需等价实现 | +| 2 | **租户上下文必须注入每个请求** | TenantGuard 从 header → subdomain → JWT 多来源提取 tenantId,校验租户有效后注入请求上下文 | +| 3 | **checkSuperTenant 是额外校验** | 机构管理的写接口除了权限码校验外,还需 Service 层验证操作者所属租户 `isSuper=1` | +| 4 | **菜单全局 + TenantMenu 分配** | Menu 表不带 tenantId,通过 TenantMenu 中间表控制各租户的菜单范围 | +| 5 | **权限和角色按租户隔离** | Permission 和 Role 表都有 tenantId,查询时必须带租户条件 | +| 6 | **用户中心无装饰器权限** | 用户管理接口未使用 `@RequirePermission`,权限在 Service 层隐式判断,Java 端需在 Service 层实现相同逻辑 | +| 7 | **账号保护** | 禁止禁用租户最后一个管理员、禁止删除/禁用超级租户自身 | +| 8 | **密码加密** | bcrypt,10 轮 salt,Java 端使用 `BCryptPasswordEncoder` | +| 9 | **权限码格式** | 统一使用 `resource:action` 格式,与前端指令对应 | +| 10 | **菜单加载有 super_admin 快速路径** | super_admin 跳过 TenantMenu 和 permission 过滤,直接返回所有菜单 | diff --git a/docs/migration/rbac-audit.sql b/docs/migration/rbac-audit.sql new file mode 100644 index 0000000..d0676de --- /dev/null +++ b/docs/migration/rbac-audit.sql @@ -0,0 +1,159 @@ +-- ========================================================= +-- RBAC/菜单/权限 数据审计(只读) +-- 目标:快速定位“已授权但菜单为空 / 权限缺失 / 绑定缺失”等问题 +-- 说明:不做任何写操作,可在生产环境安全执行 +-- ========================================================= + +-- 0) 基础:租户列表 +SELECT id, code, name, tenant_type, is_super, valid_state, deleted +FROM t_sys_tenant +WHERE deleted = 0 +ORDER BY id; + +-- 1) 角色存在但未做菜单授权(role_menu 为空) +-- 重点:tenant_admin / school_admin / teacher / student / judge 等关键角色 +SELECT + r.tenant_id, + t.code AS tenant_code, + r.id AS role_id, + r.code AS role_code, + r.name AS role_name +FROM t_auth_role r +JOIN t_sys_tenant t ON t.id = r.tenant_id AND t.deleted = 0 +LEFT JOIN t_auth_role_menu rm ON rm.role_id = r.id +WHERE r.deleted = 0 + AND r.valid_state = 1 +GROUP BY r.tenant_id, t.code, r.id, r.code, r.name +HAVING COUNT(rm.id) = 0 +ORDER BY r.tenant_id, r.code; + +-- 2) 菜单表中声明的 permission 在某些租户的 permission 表缺失 +-- 由于 t_auth_menu 无 tenant_id,permission code 应在每个租户的 t_auth_permission 中都存在(除 super_admin 这种全局码也同理) +SELECT + t.id AS tenant_id, + t.code AS tenant_code, + m.permission AS menu_permission_code +FROM t_sys_tenant t +JOIN ( + SELECT DISTINCT permission + FROM t_auth_menu + WHERE deleted = 0 + AND valid_state = 1 + AND permission IS NOT NULL + AND permission <> '' +) m ON 1 = 1 +LEFT JOIN t_auth_permission p + ON p.tenant_id = t.id + AND p.code = m.permission + AND p.deleted = 0 + AND p.valid_state = 1 +WHERE t.deleted = 0 + AND t.valid_state = 1 + AND p.id IS NULL +ORDER BY t.id, m.permission; + +-- 3) 关键前端权限码在各租户 permission 表缺失 +-- 来自 docs/migration/permission-inventory.md(前端提取) +SELECT + t.id AS tenant_id, + t.code AS tenant_code, + req.code AS required_code +FROM t_sys_tenant t +JOIN ( + SELECT 'activity:read' AS code UNION ALL + SELECT 'ai-3d:create' UNION ALL + SELECT 'class:create' UNION ALL + SELECT 'class:delete' UNION ALL + SELECT 'class:update' UNION ALL + SELECT 'config:create' UNION ALL + SELECT 'config:delete' UNION ALL + SELECT 'config:update' UNION ALL + SELECT 'contest:create' UNION ALL + SELECT 'contest:delete' UNION ALL + SELECT 'contest:publish' UNION ALL + SELECT 'contest:read' UNION ALL + SELECT 'contest:update' UNION ALL + SELECT 'department:create' UNION ALL + SELECT 'department:delete' UNION ALL + SELECT 'department:update' UNION ALL + SELECT 'dict:create' UNION ALL + SELECT 'dict:delete' UNION ALL + SELECT 'dict:update' UNION ALL + SELECT 'grade:create' UNION ALL + SELECT 'grade:delete' UNION ALL + SELECT 'grade:update' UNION ALL + SELECT 'homework:create' UNION ALL + SELECT 'homework:delete' UNION ALL + SELECT 'homework:read' UNION ALL + SELECT 'homework:update' UNION ALL + SELECT 'judge:create' UNION ALL + SELECT 'judge:delete' UNION ALL + SELECT 'judge:read' UNION ALL + SELECT 'judge:update' UNION ALL + SELECT 'log:delete' UNION ALL + SELECT 'menu:create' UNION ALL + SELECT 'menu:delete' UNION ALL + SELECT 'menu:read' UNION ALL + SELECT 'menu:update' UNION ALL + SELECT 'notice:create' UNION ALL + SELECT 'notice:delete' UNION ALL + SELECT 'notice:update' UNION ALL + SELECT 'permission:read' UNION ALL + SELECT 'registration:read' UNION ALL + SELECT 'review:score' UNION ALL + SELECT 'role:create' UNION ALL + SELECT 'role:delete' UNION ALL + SELECT 'role:read' UNION ALL + SELECT 'role:update' UNION ALL + SELECT 'school:create' UNION ALL + SELECT 'school:update' UNION ALL + SELECT 'student:create' UNION ALL + SELECT 'student:delete' UNION ALL + SELECT 'student:update' UNION ALL + SELECT 'teacher:create' UNION ALL + SELECT 'teacher:delete' UNION ALL + SELECT 'teacher:update' UNION ALL + SELECT 'tenant:create' UNION ALL + SELECT 'tenant:delete' UNION ALL + SELECT 'tenant:update' UNION ALL + SELECT 'work:read' +) req ON 1 = 1 +LEFT JOIN t_auth_permission p + ON p.tenant_id = t.id + AND p.code = req.code + AND p.deleted = 0 + AND p.valid_state = 1 +WHERE t.deleted = 0 + AND t.valid_state = 1 + AND p.id IS NULL +ORDER BY t.id, req.code; + +-- 4) 角色拥有菜单,但缺少对应 permission(可能导致“菜单看得到但点进去 403 / 按钮缺失”) +-- 规则:若菜单 permission 非空,则角色至少要拥有该 permission(role_permission) +SELECT + r.tenant_id, + t.code AS tenant_code, + r.id AS role_id, + r.code AS role_code, + m.id AS menu_id, + m.name AS menu_name, + m.permission AS menu_permission_code +FROM t_auth_role r +JOIN t_sys_tenant t ON t.id = r.tenant_id AND t.deleted = 0 +JOIN t_auth_role_menu rm ON rm.role_id = r.id +JOIN t_auth_menu m ON m.id = rm.menu_id AND m.deleted = 0 AND m.valid_state = 1 +LEFT JOIN t_auth_permission p + ON p.tenant_id = r.tenant_id + AND p.code = m.permission + AND p.deleted = 0 + AND p.valid_state = 1 +LEFT JOIN t_auth_role_permission rp + ON rp.role_id = r.id + AND rp.permission_id = p.id +WHERE r.deleted = 0 + AND r.valid_state = 1 + AND m.permission IS NOT NULL + AND m.permission <> '' + AND (p.id IS NULL OR rp.id IS NULL) +ORDER BY r.tenant_id, r.code, m.id; + diff --git a/docs/migration/verify-matrix.md b/docs/migration/verify-matrix.md new file mode 100644 index 0000000..e004fb7 --- /dev/null +++ b/docs/migration/verify-matrix.md @@ -0,0 +1,54 @@ +# 验收矩阵(菜单 × 按钮 × 接口) + +目标:验证“菜单可见性(role_menu)”“按钮显示(v-permission)”“接口鉴权(@PreAuthorize)”三者一致,且多租户不串台。 + +## 预置条件 +- 执行 Flyway 迁移:`V32__rbac_align_all_tenants.sql`、`V33__rbac_permission_code_aliases_and_menu_fix.sql` +- 确认每个租户至少存在角色:`tenant_admin`(以及按需:`school_admin/teacher/student/judge`) +- 给测试用户绑定目标租户与角色(`t_auth_user_role`) + +## 用例 + +### A. 菜单可见性(GET /api/menus/user-menus) +- **tenant_admin**:登录后菜单应非空;不应出现“只剩空白页面/无菜单”。\n +- **school_admin/teacher/student/judge**:登录后菜单与 tenant_id=1 模板一致(允许租户裁剪)。\n +- **租户隔离**:同一 userId 若历史绑定了其他租户角色,仍只返回当前 token 的 tenantId 对应角色菜单。\n + +### B. 路由访问(路由 meta.permissions) +抽查以下路由(以实际菜单为准):\n +- `contests/create`:需 `contest:create`\n +- `contests/:id`:需 `contest:read` 或 `activity:read`\n +- `analytics/overview`:需 `contest:read`\n +- `activities/review/:id`:需 `review:score`(兼容 `contest:review:score`)\n + +期望:\n +- **有权限**:可进入页面\n +- **无权限**:跳转到 `403`\n + +### C. 按钮显示(v-permission) +抽查页面:\n +- 活动列表:`contest:create`、`contest:update`、`contest:delete`、`contest:publish`\n +- 评委管理:`judge:create/read/update/delete`\n +- 公告管理:`notice:create/update/delete`\n +- 系统管理:`role:*`、`menu:*`、`dict:*`、`config:*`、`tenant:*`(平台超管)\n + +期望:\n +- **有权限**:按钮可见且可用\n +- **无权限**:按钮不可见(或禁用,按业务约定)\n + +### D. 接口鉴权(后端 @PreAuthorize) +抽查接口:\n +- 公告:`POST/PUT/DELETE /api/contests/notices...` 需 `notice:*`(兼容 `contest:notice:*`)\n +- 评委:`POST/PUT/DELETE /api/contests/judges...` 需 `judge:*`(兼容 `contest:judge:*`)\n +- 评分:`POST /api/contests/reviews/score` 需 `review:score`(兼容 `contest:review:score`)\n + +期望:\n +- **有权限**:200\n +- **无权限**:403\n + +## 常用排查 +- 执行只读审计:`docs/migration/rbac-audit.sql`\n + - 角色无菜单授权(role_menu 为空)\n + - 菜单 permission 在租户权限表缺失\n + - 角色有菜单但缺少对应 permission 绑定\n + diff --git a/java-backend/src/main/java/com/lesingle/creation/common/enums/AI3DGenerateTypeEnum.java b/java-backend/src/main/java/com/lesingle/creation/common/enums/AI3DGenerateTypeEnum.java deleted file mode 100644 index e5050a4..0000000 --- a/java-backend/src/main/java/com/lesingle/creation/common/enums/AI3DGenerateTypeEnum.java +++ /dev/null @@ -1,32 +0,0 @@ -package com.lesingle.creation.common.enums; - -import lombok.AllArgsConstructor; -import lombok.Getter; - -/** - * AI3D 生成类型枚举 - */ -@Getter -@AllArgsConstructor -public enum AI3DGenerateTypeEnum { - - NORMAL("Normal", "带纹理"), - GEOMETRY("Geometry", "白模"), - LOW_POLY("LowPoly", "低多边形"), - SKETCH("Sketch", "草图"); - - private final String code; - private final String desc; - - public static AI3DGenerateTypeEnum getByCode(String code) { - if (code == null) { - return NORMAL; - } - for (AI3DGenerateTypeEnum type : values()) { - if (type.getCode().equals(code)) { - return type; - } - } - return NORMAL; - } -} diff --git a/java-backend/src/main/java/com/lesingle/creation/common/enums/AI3DTaskStatusEnum.java b/java-backend/src/main/java/com/lesingle/creation/common/enums/AI3DTaskStatusEnum.java deleted file mode 100644 index a61677b..0000000 --- a/java-backend/src/main/java/com/lesingle/creation/common/enums/AI3DTaskStatusEnum.java +++ /dev/null @@ -1,33 +0,0 @@ -package com.lesingle.creation.common.enums; - -import lombok.AllArgsConstructor; -import lombok.Getter; - -/** - * AI3D 任务状态枚举 - */ -@Getter -@AllArgsConstructor -public enum AI3DTaskStatusEnum { - - PENDING("pending", "待处理"), - PROCESSING("processing", "处理中"), - COMPLETED("completed", "已完成"), - FAILED("failed", "失败"), - TIMEOUT("timeout", "超时"); - - private final String code; - private final String desc; - - public static AI3DTaskStatusEnum getByCode(String code) { - if (code == null) { - return PENDING; - } - for (AI3DTaskStatusEnum status : values()) { - if (status.getCode().equals(code)) { - return status; - } - } - return PENDING; - } -} diff --git a/java-backend/src/main/java/com/lesingle/creation/controller/AI3DTaskController.java b/java-backend/src/main/java/com/lesingle/creation/controller/AI3DTaskController.java deleted file mode 100644 index 46d55cd..0000000 --- a/java-backend/src/main/java/com/lesingle/creation/controller/AI3DTaskController.java +++ /dev/null @@ -1,92 +0,0 @@ -package com.lesingle.creation.controller; - -import com.baomidou.mybatisplus.extension.plugins.pagination.Page; -import com.lesingle.creation.common.core.Result; -import com.lesingle.creation.common.security.UserPrincipal; -import com.lesingle.creation.dto.ai3d.AI3DTaskQueryDTO; -import com.lesingle.creation.dto.ai3d.CreateAI3DTaskDTO; -import com.lesingle.creation.service.AI3DTaskService; -import com.lesingle.creation.vo.ai3d.AI3DTaskVO; -import io.swagger.v3.oas.annotations.Operation; -import io.swagger.v3.oas.annotations.Parameter; -import io.swagger.v3.oas.annotations.tags.Tag; -import lombok.RequiredArgsConstructor; -import org.springframework.security.access.prepost.PreAuthorize; -import org.springframework.security.core.annotation.AuthenticationPrincipal; -import org.springframework.validation.annotation.Validated; -import org.springframework.web.bind.annotation.*; - -/** - * AI 3D 生成控制器 - */ -@Tag(name = "AI 3D 生成") -@RestController -@RequestMapping("/api/ai-3d") -@RequiredArgsConstructor -public class AI3DTaskController { - - private final AI3DTaskService ai3dTaskService; - - @PostMapping - @Operation(summary = "创建 AI 3D 任务") - public Result create( - @AuthenticationPrincipal UserPrincipal userPrincipal, - @RequestBody @Validated CreateAI3DTaskDTO dto) { - Long tenantId = userPrincipal.getTenantId(); - Long userId = userPrincipal.getUserId(); - AI3DTaskVO result = ai3dTaskService.create(dto, tenantId, userId); - return Result.success(result); - } - - @GetMapping("/{id}") - @Operation(summary = "获取任务详情") - public Result getDetail( - @AuthenticationPrincipal UserPrincipal userPrincipal, - @PathVariable Long id) { - Long tenantId = userPrincipal.getTenantId(); - AI3DTaskVO result = ai3dTaskService.getDetail(id, tenantId); - return Result.success(result); - } - - @GetMapping("/page") - @Operation(summary = "分页查询任务列表") - public Result> pageQuery( - @AuthenticationPrincipal UserPrincipal userPrincipal, - @ModelAttribute AI3DTaskQueryDTO queryDTO) { - Long tenantId = userPrincipal.getTenantId(); - Long userId = userPrincipal.getUserId(); // 查询自己的任务 - Page result = ai3dTaskService.pageQuery(queryDTO, tenantId, userId); - return Result.success(result); - } - - @PutMapping("/{id}/cancel") - @Operation(summary = "取消任务") - public Result cancel( - @AuthenticationPrincipal UserPrincipal userPrincipal, - @PathVariable Long id) { - Long tenantId = userPrincipal.getTenantId(); - ai3dTaskService.cancel(id, tenantId); - return Result.success(null); - } - - @PutMapping("/{id}/retry") - @Operation(summary = "重试失败的任务") - public Result retry( - @AuthenticationPrincipal UserPrincipal userPrincipal, - @PathVariable Long id) { - Long tenantId = userPrincipal.getTenantId(); - AI3DTaskVO result = ai3dTaskService.retry(id, tenantId); - return Result.success(result); - } - - @DeleteMapping("/{id}") - @Operation(summary = "删除 AI 3D 任务") - @PreAuthorize("hasAuthority('ai-3d:delete')") - public Result delete( - @AuthenticationPrincipal UserPrincipal userPrincipal, - @PathVariable Long id) { - Long tenantId = userPrincipal.getTenantId(); - ai3dTaskService.delete(id, tenantId); - return Result.success(null); - } -} diff --git a/java-backend/src/main/java/com/lesingle/creation/controller/AnalyticsController.java b/java-backend/src/main/java/com/lesingle/creation/controller/AnalyticsController.java new file mode 100644 index 0000000..2458a62 --- /dev/null +++ b/java-backend/src/main/java/com/lesingle/creation/controller/AnalyticsController.java @@ -0,0 +1,50 @@ +package com.lesingle.creation.controller; + +import com.lesingle.creation.common.core.Result; +import com.lesingle.creation.common.security.UserPrincipal; +import com.lesingle.creation.service.AnalyticsService; +import com.lesingle.creation.vo.analytics.AnalyticsOverviewVO; +import com.lesingle.creation.vo.analytics.AnalyticsReviewVO; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; +import lombok.RequiredArgsConstructor; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.security.core.annotation.AuthenticationPrincipal; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; + +/** + * 数据统计接口(租户端) + */ +@Tag(name = "数据统计") +@RestController +@RequestMapping("/api/analytics") +@RequiredArgsConstructor +public class AnalyticsController { + + private final AnalyticsService analyticsService; + + @GetMapping("/overview") + @Operation(summary = "运营概览") + @PreAuthorize("hasAuthority('contest:read')") + public Result getOverview( + @AuthenticationPrincipal UserPrincipal userPrincipal, + @RequestParam(required = false) Long contestId, + @RequestParam(required = false) String timeRange) { + Long tenantId = userPrincipal.getTenantId(); + return Result.success(analyticsService.getOverview(tenantId, contestId, timeRange)); + } + + @GetMapping("/review") + @Operation(summary = "评审分析") + @PreAuthorize("hasAuthority('contest:read')") + public Result getReview( + @AuthenticationPrincipal UserPrincipal userPrincipal, + @RequestParam(required = false) Long contestId) { + Long tenantId = userPrincipal.getTenantId(); + return Result.success(analyticsService.getReviewAnalysis(tenantId, contestId)); + } +} + diff --git a/java-backend/src/main/java/com/lesingle/creation/controller/ContestController.java b/java-backend/src/main/java/com/lesingle/creation/controller/ContestController.java index 7ab6054..1bb90de 100644 --- a/java-backend/src/main/java/com/lesingle/creation/controller/ContestController.java +++ b/java-backend/src/main/java/com/lesingle/creation/controller/ContestController.java @@ -60,6 +60,7 @@ public class ContestController { @GetMapping("/my-contests") @Operation(summary = "我参与的活动列表") + @PreAuthorize("hasAuthority('activity:read')") public Result> myContests( @AuthenticationPrincipal UserPrincipal userPrincipal, ContestQueryDTO queryDTO) { diff --git a/java-backend/src/main/java/com/lesingle/creation/controller/ContestJudgeController.java b/java-backend/src/main/java/com/lesingle/creation/controller/ContestJudgeController.java index d4f2c03..d282000 100644 --- a/java-backend/src/main/java/com/lesingle/creation/controller/ContestJudgeController.java +++ b/java-backend/src/main/java/com/lesingle/creation/controller/ContestJudgeController.java @@ -32,7 +32,7 @@ public class ContestJudgeController { @PostMapping @Operation(summary = "创建评委") - @PreAuthorize("hasAuthority('contest:judge:create')") + @PreAuthorize("hasAnyAuthority('judge:create','contest:judge:create')") public Result create( @AuthenticationPrincipal UserPrincipal userPrincipal, @RequestBody @Validated CreateJudgeDTO dto) { @@ -54,7 +54,7 @@ public class ContestJudgeController { @PutMapping("/{id:\\d+}") @Operation(summary = "更新评委") - @PreAuthorize("hasAuthority('contest:judge:update')") + @PreAuthorize("hasAnyAuthority('judge:update','contest:judge:update')") public Result update( @PathVariable Long id, @RequestBody @Validated UpdateJudgeDTO dto) { @@ -64,7 +64,7 @@ public class ContestJudgeController { @DeleteMapping("/{id:\\d+}") @Operation(summary = "删除评委") - @PreAuthorize("hasAuthority('contest:judge:delete')") + @PreAuthorize("hasAnyAuthority('judge:delete','contest:judge:delete')") public Result delete(@PathVariable Long id) { judgeService.delete(id); return Result.success(null); @@ -88,7 +88,7 @@ public class ContestJudgeController { @PostMapping("/freeze") @Operation(summary = "冻结评委") - @PreAuthorize("hasAuthority('contest:judge:update')") + @PreAuthorize("hasAnyAuthority('judge:update','contest:judge:update')") public Result freeze(@RequestBody Map params) { Long id = params.get("id"); JudgeVO result = judgeService.freeze(id); @@ -97,7 +97,7 @@ public class ContestJudgeController { @PostMapping("/unfreeze") @Operation(summary = "解冻评委") - @PreAuthorize("hasAuthority('contest:judge:update')") + @PreAuthorize("hasAnyAuthority('judge:update','contest:judge:update')") public Result unfreeze(@RequestBody Map params) { Long id = params.get("id"); JudgeVO result = judgeService.unfreeze(id); @@ -106,7 +106,7 @@ public class ContestJudgeController { @PostMapping("/batch-delete") @Operation(summary = "批量删除评委") - @PreAuthorize("hasAuthority('contest:judge:delete')") + @PreAuthorize("hasAnyAuthority('judge:delete','contest:judge:delete')") public Result batchDelete(@RequestBody List ids) { judgeService.batchDelete(ids); return Result.success(null); diff --git a/java-backend/src/main/java/com/lesingle/creation/controller/ContestNoticeController.java b/java-backend/src/main/java/com/lesingle/creation/controller/ContestNoticeController.java index c688701..215d86f 100644 --- a/java-backend/src/main/java/com/lesingle/creation/controller/ContestNoticeController.java +++ b/java-backend/src/main/java/com/lesingle/creation/controller/ContestNoticeController.java @@ -31,7 +31,7 @@ public class ContestNoticeController { @PostMapping @Operation(summary = "创建公告") - @PreAuthorize("hasAuthority('contest:notice:create')") + @PreAuthorize("hasAnyAuthority('notice:create','contest:notice:create')") public Result create( @AuthenticationPrincipal UserPrincipal userPrincipal, @RequestBody @Validated CreateNoticeDTO dto) { @@ -53,7 +53,7 @@ public class ContestNoticeController { @PutMapping("/{id:\\d+}") @Operation(summary = "更新公告") - @PreAuthorize("hasAuthority('contest:notice:update')") + @PreAuthorize("hasAnyAuthority('notice:update','contest:notice:update')") public Result update( @PathVariable Long id, @RequestBody @Validated UpdateNoticeDTO dto) { @@ -63,7 +63,7 @@ public class ContestNoticeController { @DeleteMapping("/{id:\\d+}") @Operation(summary = "删除公告") - @PreAuthorize("hasAuthority('contest:notice:delete')") + @PreAuthorize("hasAnyAuthority('notice:delete','contest:notice:delete')") public Result delete(@PathVariable Long id) { noticeService.delete(id); return Result.success(null); @@ -71,7 +71,7 @@ public class ContestNoticeController { @PostMapping("/{id:\\d+}/publish") @Operation(summary = "发布公告") - @PreAuthorize("hasAuthority('contest:notice:publish')") + @PreAuthorize("hasAnyAuthority('notice:publish','contest:notice:publish')") public Result publish(@PathVariable Long id) { NoticeVO result = noticeService.publish(id); return Result.success(result); diff --git a/java-backend/src/main/java/com/lesingle/creation/controller/ContestPresetCommentController.java b/java-backend/src/main/java/com/lesingle/creation/controller/ContestPresetCommentController.java index 95451db..e927e17 100644 --- a/java-backend/src/main/java/com/lesingle/creation/controller/ContestPresetCommentController.java +++ b/java-backend/src/main/java/com/lesingle/creation/controller/ContestPresetCommentController.java @@ -82,6 +82,7 @@ public class ContestPresetCommentController { */ @GetMapping("/judge/contests") @Operation(summary = "评委参与的活动列表") + @PreAuthorize("hasAnyAuthority('review:score','contest:review:score')") public Result> getJudgeContests( @AuthenticationPrincipal UserPrincipal userPrincipal) { Long judgeId = userPrincipal.getUserId(); diff --git a/java-backend/src/main/java/com/lesingle/creation/controller/ContestReviewController.java b/java-backend/src/main/java/com/lesingle/creation/controller/ContestReviewController.java index 8cc8136..c6d0f2e 100644 --- a/java-backend/src/main/java/com/lesingle/creation/controller/ContestReviewController.java +++ b/java-backend/src/main/java/com/lesingle/creation/controller/ContestReviewController.java @@ -54,7 +54,7 @@ public class ContestReviewController { @PostMapping("/score") @Operation(summary = "评分") - @PreAuthorize("hasAuthority('contest:review:score')") + @PreAuthorize("hasAnyAuthority('review:score','contest:review:score')") public Result score( @AuthenticationPrincipal UserPrincipal userPrincipal, @RequestBody @Validated CreateScoreDTO dto) { @@ -66,7 +66,7 @@ public class ContestReviewController { @PutMapping("/score/{scoreId}") @Operation(summary = "更新评分") - @PreAuthorize("hasAuthority('contest:review:score')") + @PreAuthorize("hasAnyAuthority('review:score','contest:review:score')") public Result updateScore( @AuthenticationPrincipal UserPrincipal userPrincipal, @PathVariable Long scoreId, @@ -89,7 +89,7 @@ public class ContestReviewController { @GetMapping("/my-assignments") @Operation(summary = "获取评委待评审作品列表") - @PreAuthorize("hasAuthority('contest:review:score')") + @PreAuthorize("hasAnyAuthority('review:score','contest:review:score')") public Result> getMyAssignments( @AuthenticationPrincipal UserPrincipal userPrincipal) { Long judgeId = userPrincipal.getUserId(); @@ -189,6 +189,7 @@ public class ContestReviewController { @GetMapping("/judge/contests") @Operation(summary = "获取评委参与的活动列表") + @PreAuthorize("hasAuthority('contest:review:score')") public Result> getJudgeContests( @AuthenticationPrincipal UserPrincipal userPrincipal) { Long judgeId = userPrincipal.getUserId(); @@ -200,6 +201,7 @@ public class ContestReviewController { @GetMapping("/judge/contests/{contestId}/works") @Operation(summary = "获取评委在某个活动下的作品列表") + @PreAuthorize("hasAuthority('contest:review:score')") public Result> getJudgeContestWorks( @AuthenticationPrincipal UserPrincipal userPrincipal, diff --git a/java-backend/src/main/java/com/lesingle/creation/controller/ContestWorkController.java b/java-backend/src/main/java/com/lesingle/creation/controller/ContestWorkController.java index 7854a52..d9df589 100644 --- a/java-backend/src/main/java/com/lesingle/creation/controller/ContestWorkController.java +++ b/java-backend/src/main/java/com/lesingle/creation/controller/ContestWorkController.java @@ -132,6 +132,7 @@ public class ContestWorkController { @GetMapping("/guided") @Operation(summary = "查询教师指导的作品列表") + @PreAuthorize("hasAuthority('activity:read')") public Result> getGuidedWorks( @AuthenticationPrincipal UserPrincipal userPrincipal, @ModelAttribute WorkQueryDTO queryDTO) { diff --git a/java-backend/src/main/java/com/lesingle/creation/controller/HomeworkController.java b/java-backend/src/main/java/com/lesingle/creation/controller/HomeworkController.java index 89d1e8d..8a94d3b 100644 --- a/java-backend/src/main/java/com/lesingle/creation/controller/HomeworkController.java +++ b/java-backend/src/main/java/com/lesingle/creation/controller/HomeworkController.java @@ -80,6 +80,7 @@ public class HomeworkController { @GetMapping("/{id}") @Operation(summary = "获取作业详情") + @PreAuthorize("hasAuthority('homework:read')") public Result getDetail( @AuthenticationPrincipal UserPrincipal userPrincipal, @PathVariable Long id) { @@ -90,6 +91,7 @@ public class HomeworkController { @GetMapping("/page") @Operation(summary = "分页查询作业列表") + @PreAuthorize("hasAuthority('homework:read')") public Result> pageQuery( @AuthenticationPrincipal UserPrincipal userPrincipal, @ModelAttribute HomeworkQueryDTO queryDTO) { diff --git a/java-backend/src/main/java/com/lesingle/creation/controller/MenuController.java b/java-backend/src/main/java/com/lesingle/creation/controller/MenuController.java index 6fc23be..6d410bf 100644 --- a/java-backend/src/main/java/com/lesingle/creation/controller/MenuController.java +++ b/java-backend/src/main/java/com/lesingle/creation/controller/MenuController.java @@ -42,8 +42,11 @@ public class MenuController { @GetMapping @Operation(summary = "菜单树") @PreAuthorize("hasAuthority('menu:read')") - public Result> tree() { - List result = menuService.tree(); + public Result> tree( + @AuthenticationPrincipal UserPrincipal userPrincipal, + @RequestParam(required = false) String scene) { + Long tenantId = userPrincipal != null ? userPrincipal.getTenantId() : null; + List result = menuService.tree(scene, tenantId); return Result.success(result); } diff --git a/java-backend/src/main/java/com/lesingle/creation/controller/PublicController.java b/java-backend/src/main/java/com/lesingle/creation/controller/PublicController.java index 0a538c2..586499e 100644 --- a/java-backend/src/main/java/com/lesingle/creation/controller/PublicController.java +++ b/java-backend/src/main/java/com/lesingle/creation/controller/PublicController.java @@ -8,9 +8,14 @@ import com.lesingle.creation.dto.publicuser.PublicLoginDTO; import com.lesingle.creation.dto.publicuser.PublicRegisterDTO; import com.lesingle.creation.dto.publicuser.PublicUserUpdateDTO; import com.lesingle.creation.service.PublicService; +import com.lesingle.creation.service.InteractionService; import com.lesingle.creation.vo.child.ChildVO; import com.lesingle.creation.vo.publicuser.PublicUserVO; import com.lesingle.creation.vo.publicuser.LoginResponseVO; +import com.lesingle.creation.vo.publicwork.InteractionStatusVO; +import com.lesingle.creation.vo.publicwork.MyFavoritesVO; +import com.lesingle.creation.vo.publicwork.ToggleFavoriteVO; +import com.lesingle.creation.vo.publicwork.ToggleLikeVO; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.Parameter; import io.swagger.v3.oas.annotations.tags.Tag; @@ -21,6 +26,7 @@ import org.springframework.security.core.annotation.AuthenticationPrincipal; import org.springframework.web.bind.annotation.*; import java.util.List; +import java.util.Map; /** * 公共接口控制器 @@ -33,6 +39,7 @@ import java.util.List; public class PublicController { private final PublicService publicService; + private final InteractionService interactionService; // ==================== 注册 & 登录(公开接口) ==================== @@ -173,4 +180,73 @@ public class PublicController { public Result getActivityDetail(@PathVariable Long id) { return Result.success(publicService.getActivityDetail(id)); } + + // ==================== 点赞/收藏(需要登录) ==================== + + @PostMapping("/works/{id}/like") + @Operation(summary = "点赞/取消点赞(toggle)") + @PreAuthorize("isAuthenticated()") + public Result toggleLike( + @AuthenticationPrincipal UserPrincipal userPrincipal, + @PathVariable("id") Long workId) { + return Result.success(interactionService.toggleLike(userPrincipal.getUserId(), workId)); + } + + @PostMapping("/works/{id}/favorite") + @Operation(summary = "收藏/取消收藏(toggle)") + @PreAuthorize("isAuthenticated()") + public Result toggleFavorite( + @AuthenticationPrincipal UserPrincipal userPrincipal, + @PathVariable("id") Long workId) { + return Result.success(interactionService.toggleFavorite(userPrincipal.getUserId(), workId)); + } + + @GetMapping("/works/{id}/interaction") + @Operation(summary = "查询当前用户对作品的交互状态") + @PreAuthorize("isAuthenticated()") + public Result getInteraction( + @AuthenticationPrincipal UserPrincipal userPrincipal, + @PathVariable("id") Long workId) { + return Result.success(interactionService.getInteractionStatus(userPrincipal.getUserId(), workId)); + } + + @GetMapping("/mine/favorites") + @Operation(summary = "我的收藏列表") + @PreAuthorize("isAuthenticated()") + public Result myFavorites( + @AuthenticationPrincipal UserPrincipal userPrincipal, + @RequestParam(required = false) Integer page, + @RequestParam(required = false) Integer pageSize) { + return Result.success(interactionService.getMyFavorites( + userPrincipal.getTenantId(), + userPrincipal.getUserId(), + page, + pageSize + )); + } + + @PostMapping("/works/interaction/batch") + @Operation(summary = "批量查询交互状态") + @PreAuthorize("isAuthenticated()") + public Result> batchInteraction( + @AuthenticationPrincipal UserPrincipal userPrincipal, + @RequestBody BatchInteractionRequest body) { + return Result.success(interactionService.batchGetInteractionStatus( + userPrincipal.getTenantId(), + userPrincipal.getUserId(), + body.getWorkIds() + )); + } + + public static class BatchInteractionRequest { + private List workIds; + + public List getWorkIds() { + return workIds; + } + + public void setWorkIds(List workIds) { + this.workIds = workIds; + } + } } diff --git a/java-backend/src/main/java/com/lesingle/creation/dto/ai3d/AI3DTaskQueryDTO.java b/java-backend/src/main/java/com/lesingle/creation/dto/ai3d/AI3DTaskQueryDTO.java deleted file mode 100644 index 52fb7d0..0000000 --- a/java-backend/src/main/java/com/lesingle/creation/dto/ai3d/AI3DTaskQueryDTO.java +++ /dev/null @@ -1,24 +0,0 @@ -package com.lesingle.creation.dto.ai3d; - -import io.swagger.v3.oas.annotations.media.Schema; -import lombok.Data; - -/** - * AI 3D 任务查询 DTO - */ -@Data -@Schema(description = "AI 3D 任务查询参数") -public class AI3DTaskQueryDTO { - - @Schema(description = "页码", example = "1") - private Integer pageNum = 1; - - @Schema(description = "每页数量", example = "10") - private Integer pageSize = 10; - - @Schema(description = "任务状态:pending/processing/completed/failed/timeout") - private String status; - - @Schema(description = "输入类型:text/image") - private String inputType; -} diff --git a/java-backend/src/main/java/com/lesingle/creation/dto/ai3d/CreateAI3DTaskDTO.java b/java-backend/src/main/java/com/lesingle/creation/dto/ai3d/CreateAI3DTaskDTO.java deleted file mode 100644 index 7d60519..0000000 --- a/java-backend/src/main/java/com/lesingle/creation/dto/ai3d/CreateAI3DTaskDTO.java +++ /dev/null @@ -1,26 +0,0 @@ -package com.lesingle.creation.dto.ai3d; - -import io.swagger.v3.oas.annotations.media.Schema; -import lombok.Data; - -import jakarta.validation.constraints.NotBlank; -import jakarta.validation.constraints.NotNull; - -/** - * 创建 AI 3D 任务请求 DTO - */ -@Data -@Schema(description = "创建 AI 3D 任务请求") -public class CreateAI3DTaskDTO { - - @NotBlank(message = "输入内容不能为空") - @Schema(description = "输入类型:text/image", example = "text") - private String inputType; - - @NotBlank(message = "输入内容不能为空") - @Schema(description = "输入内容:文字描述或图片 URL", example = "一只可爱的小猫") - private String inputContent; - - @Schema(description = "生成类型:Normal/Geometry/LowPoly/Sketch", example = "Normal") - private String generateType = "Normal"; -} diff --git a/java-backend/src/main/java/com/lesingle/creation/dto/menu/CreateMenuDTO.java b/java-backend/src/main/java/com/lesingle/creation/dto/menu/CreateMenuDTO.java index c3affa9..0889493 100644 --- a/java-backend/src/main/java/com/lesingle/creation/dto/menu/CreateMenuDTO.java +++ b/java-backend/src/main/java/com/lesingle/creation/dto/menu/CreateMenuDTO.java @@ -12,6 +12,9 @@ import jakarta.validation.constraints.NotBlank; @Schema(description = "创建菜单请求") public class CreateMenuDTO { + @Schema(description = "菜单场景:portal-平台端,tenant-租户端", example = "tenant") + private String scene; + @NotBlank(message = "菜单名称不能为空") @Schema(description = "菜单名称", example = "用户管理") private String name; diff --git a/java-backend/src/main/java/com/lesingle/creation/dto/menu/UpdateMenuDTO.java b/java-backend/src/main/java/com/lesingle/creation/dto/menu/UpdateMenuDTO.java index 7401aff..5d929fa 100644 --- a/java-backend/src/main/java/com/lesingle/creation/dto/menu/UpdateMenuDTO.java +++ b/java-backend/src/main/java/com/lesingle/creation/dto/menu/UpdateMenuDTO.java @@ -10,6 +10,9 @@ import lombok.Data; @Schema(description = "更新菜单请求") public class UpdateMenuDTO { + @Schema(description = "菜单场景:portal-平台端,tenant-租户端") + private String scene; + @Schema(description = "菜单名称") private String name; diff --git a/java-backend/src/main/java/com/lesingle/creation/entity/AI3DTask.java b/java-backend/src/main/java/com/lesingle/creation/entity/AI3DTask.java deleted file mode 100644 index 2269bcc..0000000 --- a/java-backend/src/main/java/com/lesingle/creation/entity/AI3DTask.java +++ /dev/null @@ -1,100 +0,0 @@ -package com.lesingle.creation.entity; - -import com.baomidou.mybatisplus.annotation.TableField; -import com.baomidou.mybatisplus.annotation.TableName; -import com.lesingle.creation.common.base.BaseEntity; -import lombok.Data; -import lombok.EqualsAndHashCode; - -/** - * AI 3D 任务表实体类 - */ -@Data -@EqualsAndHashCode(callSuper = true) -@TableName("t_biz_ai3d_task") -public class AI3DTask extends BaseEntity { - - /** - * 租户 ID - */ - @TableField("tenant_id") - private Long tenantId; - - /** - * 用户 ID - */ - @TableField("user_id") - private Long userId; - - /** - * 输入类型:text/image - */ - @TableField("input_type") - private String inputType; - - /** - * 输入内容:文字描述或图片 URL - */ - @TableField("input_content") - private String inputContent; - - /** - * 生成类型:Normal/Geometry/LowPoly/Sketch - */ - @TableField("generate_type") - private String generateType; - - /** - * 任务状态:pending/processing/completed/failed/timeout - */ - @TableField("status") - private String status; - - /** - * 生成的 3D 模型 URL(单结果) - */ - @TableField("result_url") - private String resultUrl; - - /** - * 预览图 URL(单结果) - */ - @TableField("preview_url") - private String previewUrl; - - /** - * 生成的 3D 模型 URL 数组(多结果,JSON 格式) - */ - @TableField("result_urls") - private String resultUrls; - - /** - * 预览图 URL 数组(多结果,JSON 格式) - */ - @TableField("preview_urls") - private String previewUrls; - - /** - * 失败错误信息 - */ - @TableField("error_message") - private String errorMessage; - - /** - * 外部 AI 服务任务 ID - */ - @TableField("external_task_id") - private String externalTaskId; - - /** - * 已重试次数 - */ - @TableField("retry_count") - private Integer retryCount; - - /** - * 完成时间 - */ - @TableField("complete_time") - private java.time.LocalDateTime completeTime; -} diff --git a/java-backend/src/main/java/com/lesingle/creation/entity/Menu.java b/java-backend/src/main/java/com/lesingle/creation/entity/Menu.java index cd9e89d..d5dce01 100644 --- a/java-backend/src/main/java/com/lesingle/creation/entity/Menu.java +++ b/java-backend/src/main/java/com/lesingle/creation/entity/Menu.java @@ -18,6 +18,12 @@ import java.util.List; @TableName("t_auth_menu") public class Menu extends BaseEntity { + /** + * 菜单场景:portal-平台端,tenant-租户端 + */ + @TableField("scene") + private String scene; + /** * 菜单名称 */ diff --git a/java-backend/src/main/java/com/lesingle/creation/entity/UserWorkFavorite.java b/java-backend/src/main/java/com/lesingle/creation/entity/UserWorkFavorite.java new file mode 100644 index 0000000..7f2a51f --- /dev/null +++ b/java-backend/src/main/java/com/lesingle/creation/entity/UserWorkFavorite.java @@ -0,0 +1,33 @@ +package com.lesingle.creation.entity; + +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableField; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import lombok.Data; + +import java.time.LocalDateTime; + +/** + * UGC 作品收藏明细 + */ +@Data +@TableName("user_work_favorites") +public class UserWorkFavorite { + + @TableId(type = IdType.AUTO) + private Long id; + + @TableField("tenant_id") + private Long tenantId; + + @TableField("user_id") + private Long userId; + + @TableField("work_id") + private Long workId; + + @TableField("create_time") + private LocalDateTime createTime; +} + diff --git a/java-backend/src/main/java/com/lesingle/creation/entity/UserWorkLike.java b/java-backend/src/main/java/com/lesingle/creation/entity/UserWorkLike.java new file mode 100644 index 0000000..642e1a2 --- /dev/null +++ b/java-backend/src/main/java/com/lesingle/creation/entity/UserWorkLike.java @@ -0,0 +1,33 @@ +package com.lesingle.creation.entity; + +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableField; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import lombok.Data; + +import java.time.LocalDateTime; + +/** + * UGC 作品点赞明细 + */ +@Data +@TableName("user_work_likes") +public class UserWorkLike { + + @TableId(type = IdType.AUTO) + private Long id; + + @TableField("tenant_id") + private Long tenantId; + + @TableField("user_id") + private Long userId; + + @TableField("work_id") + private Long workId; + + @TableField("create_time") + private LocalDateTime createTime; +} + diff --git a/java-backend/src/main/java/com/lesingle/creation/mapper/AnalyticsMapper.java b/java-backend/src/main/java/com/lesingle/creation/mapper/AnalyticsMapper.java new file mode 100644 index 0000000..116ca92 --- /dev/null +++ b/java-backend/src/main/java/com/lesingle/creation/mapper/AnalyticsMapper.java @@ -0,0 +1,236 @@ +package com.lesingle.creation.mapper; + +import org.apache.ibatis.annotations.Mapper; +import org.apache.ibatis.annotations.Param; +import org.apache.ibatis.annotations.Select; + +import java.time.LocalDateTime; +import java.util.List; + +@Mapper +public interface AnalyticsMapper { + + @Select(""" + SELECT id + FROM t_biz_contest + WHERE deleted = 0 + AND contest_state = 'published' + AND ( + contest_tenants IS NULL + OR JSON_LENGTH(contest_tenants) = 0 + OR JSON_CONTAINS(contest_tenants, JSON_ARRAY(#{tenantId})) + ) + """) + List selectVisibleContestIds(@Param("tenantId") Long tenantId); + + @Select(""" + + """) + List selectRegistrationsByMonth( + @Param("tenantId") Long tenantId, + @Param("contestIds") List contestIds, + @Param("startTime") LocalDateTime startTime); + + @Select(""" + + """) + List selectWorksByMonth( + @Param("tenantId") Long tenantId, + @Param("contestIds") List contestIds, + @Param("startTime") LocalDateTime startTime); + + @Select(""" + + """) + List selectRegistrationAggByContest( + @Param("tenantId") Long tenantId, + @Param("contestIds") List contestIds); + + @Select(""" + + """) + List selectWorkAggByContest( + @Param("tenantId") Long tenantId, + @Param("contestIds") List contestIds); + + @Select(""" + + """) + Long countPendingAssignments(@Param("contestIds") List contestIds); + + @Select(""" + + """) + Long countRecentScores(@Param("contestIds") List contestIds, @Param("startTime") LocalDateTime startTime); + + @Select(""" + + """) + Double selectAvgReviewDays(@Param("contestIds") List contestIds); + + @Select(""" + + """) + Double selectAvgScoreStddev(@Param("contestIds") List contestIds); + + @Select(""" + + """) + List selectJudgeBase(@Param("contestIds") List contestIds); + + @Select(""" + + """) + List selectJudgeAgg(@Param("contestIds") List contestIds); + + @Select(""" + + """) + List selectAwardDistribution( + @Param("tenantId") Long tenantId, + @Param("contestIds") List contestIds); + + record MonthCountRow(String month, Long cnt) {} + + record RegistrationAggRow(Long contestId, Long total, Long passed) {} + + record WorkAggRow(Long contestId, Long worksTotal, Long reviewed, Long awarded, Double avgScore) {} + + record JudgeBaseRow(Long judgeId, String judgeName, Long contestCount) {} + + record JudgeAggRow(Long judgeId, Long assignedCount, Long scoredCount, Double avgScore, Double scoreStddev) {} + + record AwardGroupRow(String awardName, Long cnt) {} +} + diff --git a/java-backend/src/main/java/com/lesingle/creation/mapper/UserWorkFavoriteMapper.java b/java-backend/src/main/java/com/lesingle/creation/mapper/UserWorkFavoriteMapper.java new file mode 100644 index 0000000..5b6fe67 --- /dev/null +++ b/java-backend/src/main/java/com/lesingle/creation/mapper/UserWorkFavoriteMapper.java @@ -0,0 +1,65 @@ +package com.lesingle.creation.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.lesingle.creation.entity.UserWorkFavorite; +import org.apache.ibatis.annotations.Mapper; +import org.apache.ibatis.annotations.Param; +import org.apache.ibatis.annotations.Select; + +import java.time.LocalDateTime; +import java.util.List; + +@Mapper +public interface UserWorkFavoriteMapper extends BaseMapper { + + @Select(""" + SELECT + f.id AS favoriteId, + f.work_id AS workId, + f.create_time AS favoriteTime, + w.title AS workTitle, + w.cover_url AS coverUrl, + w.like_count AS likeCount, + w.view_count AS viewCount, + w.favorite_count AS favoriteCount, + u.id AS creatorId, + u.nickname AS creatorNickname, + u.avatar AS creatorAvatar + FROM user_work_favorites f + JOIN user_works w ON w.id = f.work_id AND w.is_deleted = 0 AND w.status = 'published' + JOIN t_sys_user u ON u.id = w.user_id + WHERE f.tenant_id = #{tenantId} + AND f.user_id = #{userId} + ORDER BY f.create_time DESC + LIMIT #{limit} OFFSET #{offset} + """) + List selectMyFavorites( + @Param("tenantId") Long tenantId, + @Param("userId") Long userId, + @Param("offset") long offset, + @Param("limit") long limit); + + @Select(""" + SELECT COUNT(*) + FROM user_work_favorites f + JOIN user_works w ON w.id = f.work_id AND w.is_deleted = 0 AND w.status = 'published' + WHERE f.tenant_id = #{tenantId} + AND f.user_id = #{userId} + """) + Long countMyFavorites(@Param("tenantId") Long tenantId, @Param("userId") Long userId); + + record MyFavoriteRow( + Long favoriteId, + Long workId, + LocalDateTime favoriteTime, + String workTitle, + String coverUrl, + Integer likeCount, + Integer viewCount, + Integer favoriteCount, + Long creatorId, + String creatorNickname, + String creatorAvatar + ) {} +} + diff --git a/java-backend/src/main/java/com/lesingle/creation/mapper/AI3DTaskMapper.java b/java-backend/src/main/java/com/lesingle/creation/mapper/UserWorkLikeMapper.java similarity index 50% rename from java-backend/src/main/java/com/lesingle/creation/mapper/AI3DTaskMapper.java rename to java-backend/src/main/java/com/lesingle/creation/mapper/UserWorkLikeMapper.java index 83ac3b3..adc444e 100644 --- a/java-backend/src/main/java/com/lesingle/creation/mapper/AI3DTaskMapper.java +++ b/java-backend/src/main/java/com/lesingle/creation/mapper/UserWorkLikeMapper.java @@ -1,13 +1,10 @@ package com.lesingle.creation.mapper; import com.baomidou.mybatisplus.core.mapper.BaseMapper; -import com.lesingle.creation.entity.AI3DTask; +import com.lesingle.creation.entity.UserWorkLike; import org.apache.ibatis.annotations.Mapper; -/** - * AI 3D 任务 Mapper 接口 - */ @Mapper -public interface AI3DTaskMapper extends BaseMapper { - +public interface UserWorkLikeMapper extends BaseMapper { } + diff --git a/java-backend/src/main/java/com/lesingle/creation/service/AI3DTaskService.java b/java-backend/src/main/java/com/lesingle/creation/service/AI3DTaskService.java deleted file mode 100644 index 96220a1..0000000 --- a/java-backend/src/main/java/com/lesingle/creation/service/AI3DTaskService.java +++ /dev/null @@ -1,62 +0,0 @@ -package com.lesingle.creation.service; - -import com.baomidou.mybatisplus.extension.plugins.pagination.Page; -import com.baomidou.mybatisplus.extension.service.IService; -import com.lesingle.creation.dto.ai3d.AI3DTaskQueryDTO; -import com.lesingle.creation.dto.ai3d.CreateAI3DTaskDTO; -import com.lesingle.creation.entity.AI3DTask; -import com.lesingle.creation.vo.ai3d.AI3DTaskVO; - -/** - * AI 3D 任务服务接口 - */ -public interface AI3DTaskService extends IService { - - /** - * 创建 AI 3D 任务 - * @param dto 创建任务请求 - * @param tenantId 租户 ID - * @param userId 用户 ID - * @return 任务详情 - */ - AI3DTaskVO create(CreateAI3DTaskDTO dto, Long tenantId, Long userId); - - /** - * 获取任务详情 - * @param id 任务 ID - * @param tenantId 租户 ID - * @return 任务详情 - */ - AI3DTaskVO getDetail(Long id, Long tenantId); - - /** - * 分页查询任务列表 - * @param queryDTO 查询参数 - * @param tenantId 租户 ID - * @param userId 用户 ID(可选,查询自己的任务) - * @return 任务列表 - */ - Page pageQuery(AI3DTaskQueryDTO queryDTO, Long tenantId, Long userId); - - /** - * 取消任务 - * @param id 任务 ID - * @param tenantId 租户 ID - */ - void cancel(Long id, Long tenantId); - - /** - * 重试失败的任务 - * @param id 任务 ID - * @param tenantId 租户 ID - * @return 任务详情 - */ - AI3DTaskVO retry(Long id, Long tenantId); - - /** - * 删除 AI 3D 任务 - * @param id 任务 ID - * @param tenantId 租户 ID - */ - void delete(Long id, Long tenantId); -} diff --git a/java-backend/src/main/java/com/lesingle/creation/service/AnalyticsService.java b/java-backend/src/main/java/com/lesingle/creation/service/AnalyticsService.java new file mode 100644 index 0000000..7f96763 --- /dev/null +++ b/java-backend/src/main/java/com/lesingle/creation/service/AnalyticsService.java @@ -0,0 +1,12 @@ +package com.lesingle.creation.service; + +import com.lesingle.creation.vo.analytics.AnalyticsOverviewVO; +import com.lesingle.creation.vo.analytics.AnalyticsReviewVO; + +public interface AnalyticsService { + + AnalyticsOverviewVO getOverview(Long tenantId, Long contestId, String timeRange); + + AnalyticsReviewVO getReviewAnalysis(Long tenantId, Long contestId); +} + diff --git a/java-backend/src/main/java/com/lesingle/creation/service/InteractionService.java b/java-backend/src/main/java/com/lesingle/creation/service/InteractionService.java new file mode 100644 index 0000000..25c6f47 --- /dev/null +++ b/java-backend/src/main/java/com/lesingle/creation/service/InteractionService.java @@ -0,0 +1,23 @@ +package com.lesingle.creation.service; + +import com.lesingle.creation.vo.publicwork.InteractionStatusVO; +import com.lesingle.creation.vo.publicwork.MyFavoritesVO; +import com.lesingle.creation.vo.publicwork.ToggleFavoriteVO; +import com.lesingle.creation.vo.publicwork.ToggleLikeVO; + +import java.util.Map; +import java.util.List; + +public interface InteractionService { + + ToggleLikeVO toggleLike(Long userId, Long workId); + + ToggleFavoriteVO toggleFavorite(Long userId, Long workId); + + InteractionStatusVO getInteractionStatus(Long userId, Long workId); + + MyFavoritesVO getMyFavorites(Long tenantId, Long userId, Integer page, Integer pageSize); + + Map batchGetInteractionStatus(Long tenantId, Long userId, List workIds); +} + diff --git a/java-backend/src/main/java/com/lesingle/creation/service/MenuService.java b/java-backend/src/main/java/com/lesingle/creation/service/MenuService.java index 847b3af..55c6ddb 100644 --- a/java-backend/src/main/java/com/lesingle/creation/service/MenuService.java +++ b/java-backend/src/main/java/com/lesingle/creation/service/MenuService.java @@ -30,6 +30,15 @@ public interface MenuService extends IService { */ List tree(); + /** + * 获取菜单树(按场景过滤) + * + * @param scene 菜单场景:portal|tenant(为空则按租户类型推断) + * @param tenantId 当前租户ID(用于默认场景推断) + * @return 菜单树 + */ + List tree(String scene, Long tenantId); + /** * 获取用户菜单(根据用户角色权限) * diff --git a/java-backend/src/main/java/com/lesingle/creation/service/impl/AI3DTaskServiceImpl.java b/java-backend/src/main/java/com/lesingle/creation/service/impl/AI3DTaskServiceImpl.java deleted file mode 100644 index 49d3d1c..0000000 --- a/java-backend/src/main/java/com/lesingle/creation/service/impl/AI3DTaskServiceImpl.java +++ /dev/null @@ -1,190 +0,0 @@ -package com.lesingle.creation.service.impl; - -import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; -import com.baomidou.mybatisplus.extension.plugins.pagination.Page; -import com.lesingle.creation.dto.ai3d.AI3DTaskQueryDTO; -import com.lesingle.creation.dto.ai3d.CreateAI3DTaskDTO; -import com.lesingle.creation.entity.AI3DTask; -import com.lesingle.creation.common.exception.BusinessException; -import com.lesingle.creation.mapper.AI3DTaskMapper; -import com.lesingle.creation.service.AI3DTaskService; -import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; -import com.lesingle.creation.vo.ai3d.AI3DTaskVO; -import lombok.RequiredArgsConstructor; -import lombok.extern.slf4j.Slf4j; -import org.springframework.stereotype.Service; -import org.springframework.transaction.annotation.Transactional; - -import java.time.LocalDateTime; -import java.util.List; -import java.util.stream.Collectors; - -/** - * AI 3D 任务服务实现类 - */ -@Slf4j -@Service -@RequiredArgsConstructor -public class AI3DTaskServiceImpl extends ServiceImpl - implements AI3DTaskService { - - @Override - @Transactional(rollbackFor = Exception.class) - public AI3DTaskVO create(CreateAI3DTaskDTO dto, Long tenantId, Long userId) { - AI3DTask task = new AI3DTask(); - task.setTenantId(tenantId); - task.setUserId(userId); - task.setInputType(dto.getInputType()); - task.setInputContent(dto.getInputContent()); - task.setGenerateType(dto.getGenerateType()); - task.setStatus("pending"); - task.setRetryCount(0); - - this.save(task); - log.info("创建 AI 3D 任务成功,ID={}, 输入类型={}", task.getId(), dto.getInputType()); - - // TODO: 调用腾讯混元 AI 服务进行 3D 生成 - // 这里应该异步调用外部 AI 服务,并在完成后更新任务状态 - - return convertToVO(task); - } - - @Override - public AI3DTaskVO getDetail(Long id, Long tenantId) { - AI3DTask task = this.getById(id); - if (task == null || !task.getTenantId().equals(tenantId)) { - throw new BusinessException("任务不存在"); - } - - return convertToVO(task); - } - - @Override - public Page pageQuery(AI3DTaskQueryDTO queryDTO, Long tenantId, Long userId) { - Page page = new Page<>(queryDTO.getPageNum(), queryDTO.getPageSize()); - - LambdaQueryWrapper wrapper = new LambdaQueryWrapper() - .eq(AI3DTask::getTenantId, tenantId) - .eq(AI3DTask::getDeleted, 0) - .eq(queryDTO.getStatus() != null, AI3DTask::getStatus, queryDTO.getStatus()) - .eq(queryDTO.getInputType() != null, AI3DTask::getInputType, queryDTO.getInputType()) - .eq(userId != null, AI3DTask::getUserId, userId) - .orderByDesc(AI3DTask::getCreateTime); - - Page resultPage = this.page(page, wrapper); - - List voList = resultPage.getRecords().stream() - .map(this::convertToVO) - .collect(Collectors.toList()); - - Page voPage = new Page<>( - resultPage.getCurrent(), - resultPage.getSize(), - resultPage.getTotal() - ); - voPage.setRecords(voList); - return voPage; - } - - @Override - @Transactional(rollbackFor = Exception.class) - public void cancel(Long id, Long tenantId) { - AI3DTask task = this.getById(id); - if (task == null || !task.getTenantId().equals(tenantId)) { - throw new BusinessException("任务不存在"); - } - - // 只有 pending/processing 状态的任务可以取消 - if (!"pending".equals(task.getStatus()) && !"processing".equals(task.getStatus())) { - throw new BusinessException("当前状态无法取消任务"); - } - - task.setStatus("timeout"); - this.updateById(task); - log.info("取消 AI 3D 任务成功,ID={}", id); - } - - @Override - @Transactional(rollbackFor = Exception.class) - public AI3DTaskVO retry(Long id, Long tenantId) { - AI3DTask task = this.getById(id); - if (task == null || !task.getTenantId().equals(tenantId)) { - throw new BusinessException("任务不存在"); - } - - // 只有 failed 状态的任务可以重试 - if (!"failed".equals(task.getStatus())) { - throw new BusinessException("只有失败的任务可以重试"); - } - - // 限制最大重试次数 - if (task.getRetryCount() >= 3) { - throw new BusinessException("已达到最大重试次数"); - } - - task.setStatus("pending"); - task.setRetryCount(task.getRetryCount() + 1); - task.setErrorMessage(null); - this.updateById(task); - log.info("重试 AI 3D 任务成功,ID={}, 重试次数={}", id, task.getRetryCount()); - - // TODO: 重新调用 AI 服务 - - return convertToVO(task); - } - - @Override - @Transactional(rollbackFor = Exception.class) - public void delete(Long id, Long tenantId) { - AI3DTask task = this.getById(id); - if (task == null || !task.getTenantId().equals(tenantId)) { - throw new BusinessException("任务不存在"); - } - - task.setDeleted(1); - this.updateById(task); - log.info("删除 AI 3D 任务成功,ID={}", id); - } - - /** - * 更新任务状态(供异步回调使用) - */ - @Transactional(rollbackFor = Exception.class) - public void updateTaskStatus(Long taskId, String status, String resultUrl, - String previewUrl, String errorMessage) { - AI3DTask task = this.getById(taskId); - if (task != null) { - task.setStatus(status); - if ("completed".equals(status)) { - task.setResultUrl(resultUrl); - task.setPreviewUrl(previewUrl); - task.setCompleteTime(LocalDateTime.now()); - } else if ("failed".equals(status)) { - task.setErrorMessage(errorMessage); - } - this.updateById(task); - log.info("更新 AI 3D 任务状态,ID={}, 状态={}", taskId, status); - } - } - - // ========== 转换方法 ========== - - private AI3DTaskVO convertToVO(AI3DTask task) { - AI3DTaskVO vo = new AI3DTaskVO(); - vo.setId(task.getId()); - vo.setInputType(task.getInputType()); - vo.setInputContent(task.getInputContent()); - vo.setGenerateType(task.getGenerateType()); - vo.setStatus(task.getStatus()); - vo.setResultUrl(task.getResultUrl()); - vo.setPreviewUrl(task.getPreviewUrl()); - vo.setResultUrls(task.getResultUrls()); - vo.setPreviewUrls(task.getPreviewUrls()); - vo.setErrorMessage(task.getErrorMessage()); - vo.setExternalTaskId(task.getExternalTaskId()); - vo.setRetryCount(task.getRetryCount()); - vo.setCreateTime(task.getCreateTime()); - vo.setCompleteTime(task.getCompleteTime()); - return vo; - } -} diff --git a/java-backend/src/main/java/com/lesingle/creation/service/impl/AnalyticsServiceImpl.java b/java-backend/src/main/java/com/lesingle/creation/service/impl/AnalyticsServiceImpl.java new file mode 100644 index 0000000..8324e59 --- /dev/null +++ b/java-backend/src/main/java/com/lesingle/creation/service/impl/AnalyticsServiceImpl.java @@ -0,0 +1,266 @@ +package com.lesingle.creation.service.impl; + +import com.lesingle.creation.mapper.AnalyticsMapper; +import com.lesingle.creation.mapper.ContestMapper; +import com.lesingle.creation.service.AnalyticsService; +import com.lesingle.creation.vo.analytics.AnalyticsOverviewVO; +import com.lesingle.creation.vo.analytics.AnalyticsReviewVO; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; + +import java.math.BigDecimal; +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.util.*; +import java.util.function.Function; +import java.util.stream.Collectors; + +@Service +@RequiredArgsConstructor +public class AnalyticsServiceImpl implements AnalyticsService { + + private final AnalyticsMapper analyticsMapper; + private final ContestMapper contestMapper; + + @Override + public AnalyticsOverviewVO getOverview(Long tenantId, Long contestId, String timeRange) { + // timeRange 目前仅用于兼容参数(副本工程前端会传/保留),统计范围先按“最近6个月趋势 + 全量汇总”实现 + List visibleContestIds = analyticsMapper.selectVisibleContestIds(tenantId); + if (contestId != null) { + visibleContestIds = visibleContestIds.stream().filter(id -> Objects.equals(id, contestId)).toList(); + } + + AnalyticsOverviewVO vo = new AnalyticsOverviewVO(); + + if (visibleContestIds.isEmpty()) { + vo.setSummary(emptySummary()); + vo.setFunnel(emptyFunnel()); + vo.setMonthlyTrend(buildEmptyMonthsTrend()); + vo.setContestComparison(Collections.emptyList()); + return vo; + } + + Map regAgg = analyticsMapper + .selectRegistrationAggByContest(tenantId, visibleContestIds) + .stream() + .collect(Collectors.toMap(AnalyticsMapper.RegistrationAggRow::contestId, Function.identity())); + + Map workAgg = analyticsMapper + .selectWorkAggByContest(tenantId, visibleContestIds) + .stream() + .collect(Collectors.toMap(AnalyticsMapper.WorkAggRow::contestId, Function.identity())); + + long totalRegistrations = regAgg.values().stream().mapToLong(r -> Optional.ofNullable(r.total()).orElse(0L)).sum(); + long passedRegistrations = regAgg.values().stream().mapToLong(r -> Optional.ofNullable(r.passed()).orElse(0L)).sum(); + + long totalWorks = workAgg.values().stream().mapToLong(w -> Optional.ofNullable(w.worksTotal()).orElse(0L)).sum(); + long reviewedWorks = workAgg.values().stream().mapToLong(w -> Optional.ofNullable(w.reviewed()).orElse(0L)).sum(); + long awardedWorks = workAgg.values().stream().mapToLong(w -> Optional.ofNullable(w.awarded()).orElse(0L)).sum(); + + AnalyticsOverviewVO.Summary summary = new AnalyticsOverviewVO.Summary(); + summary.setTotalContests(visibleContestIds.size()); + summary.setTotalRegistrations(totalRegistrations); + summary.setPassedRegistrations(passedRegistrations); + summary.setTotalWorks(totalWorks); + summary.setReviewedWorks(reviewedWorks); + summary.setAwardedWorks(awardedWorks); + vo.setSummary(summary); + + AnalyticsOverviewVO.Funnel funnel = new AnalyticsOverviewVO.Funnel(); + funnel.setRegistered(totalRegistrations); + funnel.setPassed(passedRegistrations); + funnel.setSubmitted(totalWorks); + funnel.setReviewed(reviewedWorks); + funnel.setAwarded(awardedWorks); + vo.setFunnel(funnel); + + // 月度趋势(最近6个月) + LocalDate firstDayOfSixMonthsAgo = LocalDate.now().minusMonths(5).withDayOfMonth(1); + LocalDateTime startTime = firstDayOfSixMonthsAgo.atStartOfDay(); + + Map regByMonth = analyticsMapper.selectRegistrationsByMonth(tenantId, visibleContestIds, startTime) + .stream() + .collect(Collectors.toMap(AnalyticsMapper.MonthCountRow::month, r -> Optional.ofNullable(r.cnt()).orElse(0L))); + + Map worksByMonth = analyticsMapper.selectWorksByMonth(tenantId, visibleContestIds, startTime) + .stream() + .collect(Collectors.toMap(AnalyticsMapper.MonthCountRow::month, r -> Optional.ofNullable(r.cnt()).orElse(0L))); + + List monthlyTrend = new ArrayList<>(); + for (int i = 0; i < 6; i++) { + LocalDate d = firstDayOfSixMonthsAgo.plusMonths(i); + String m = String.format("%d-%02d", d.getYear(), d.getMonthValue()); + AnalyticsOverviewVO.MonthlyTrendItem item = new AnalyticsOverviewVO.MonthlyTrendItem(); + item.setMonth(m); + item.setRegistrations(regByMonth.getOrDefault(m, 0L)); + item.setWorks(worksByMonth.getOrDefault(m, 0L)); + monthlyTrend.add(item); + } + vo.setMonthlyTrend(monthlyTrend); + + // 活动名称(避免一条条查询) + Map contestNames = contestMapper.selectBatchIds(visibleContestIds) + .stream() + .collect(Collectors.toMap(c -> c.getId(), c -> c.getContestName(), (a, b) -> a)); + + // 活动对比 + List comparison = new ArrayList<>(); + for (Long cid : visibleContestIds) { + AnalyticsMapper.RegistrationAggRow r = regAgg.get(cid); + AnalyticsMapper.WorkAggRow w = workAgg.get(cid); + + long regTotal = r != null && r.total() != null ? r.total() : 0L; + long regPassed = r != null && r.passed() != null ? r.passed() : 0L; + long worksTotal2 = w != null && w.worksTotal() != null ? w.worksTotal() : 0L; + long worksReviewed2 = w != null && w.reviewed() != null ? w.reviewed() : 0L; + long worksAwarded2 = w != null && w.awarded() != null ? w.awarded() : 0L; + + AnalyticsOverviewVO.ContestComparisonItem row = new AnalyticsOverviewVO.ContestComparisonItem(); + row.setContestId(cid); + row.setContestName(contestNames.getOrDefault(cid, "-")); + row.setRegistrations(regTotal); + row.setPassRate(regTotal > 0 ? (int) Math.round(regPassed * 100.0 / regTotal) : 0); + row.setSubmitRate(regPassed > 0 ? (int) Math.round(worksTotal2 * 100.0 / regPassed) : 0); + row.setReviewRate(worksTotal2 > 0 ? (int) Math.round(worksReviewed2 * 100.0 / worksTotal2) : 0); + row.setAwardRate(worksTotal2 > 0 ? (int) Math.round(worksAwarded2 * 100.0 / worksTotal2) : 0); + row.setAvgScore(w != null && w.avgScore() != null ? round2(w.avgScore()) : null); + comparison.add(row); + } + vo.setContestComparison(comparison); + + return vo; + } + + @Override + public AnalyticsReviewVO getReviewAnalysis(Long tenantId, Long contestId) { + List visibleContestIds = analyticsMapper.selectVisibleContestIds(tenantId); + if (contestId != null) { + visibleContestIds = visibleContestIds.stream().filter(id -> Objects.equals(id, contestId)).toList(); + } + + AnalyticsReviewVO vo = new AnalyticsReviewVO(); + + if (visibleContestIds.isEmpty()) { + vo.setEfficiency(emptyEfficiency()); + vo.setJudgeWorkload(Collections.emptyList()); + vo.setAwardDistribution(Collections.emptyList()); + return vo; + } + + // 效率指标 + long pendingAssignments = Optional.ofNullable(analyticsMapper.countPendingAssignments(visibleContestIds)).orElse(0L); + LocalDateTime thirtyDaysAgo = LocalDateTime.now().minusDays(30); + long recentScoreCount = Optional.ofNullable(analyticsMapper.countRecentScores(visibleContestIds, thirtyDaysAgo)).orElse(0L); + + double avgReviewDays = Optional.ofNullable(analyticsMapper.selectAvgReviewDays(visibleContestIds)).orElse(0d); + double avgScoreStddev = Optional.ofNullable(analyticsMapper.selectAvgScoreStddev(visibleContestIds)).orElse(0d); + + AnalyticsReviewVO.Efficiency efficiency = new AnalyticsReviewVO.Efficiency(); + efficiency.setAvgReviewDays(round1(avgReviewDays)); + efficiency.setDailyReviewCount(round1(recentScoreCount / 30.0)); + efficiency.setPendingAssignments(pendingAssignments); + efficiency.setAvgScoreStddev(round1(avgScoreStddev)); + vo.setEfficiency(efficiency); + + // 评委工作量 + Map judgeBase = analyticsMapper.selectJudgeBase(visibleContestIds) + .stream() + .collect(Collectors.toMap(AnalyticsMapper.JudgeBaseRow::judgeId, Function.identity())); + + Map judgeAgg = analyticsMapper.selectJudgeAgg(visibleContestIds) + .stream() + .collect(Collectors.toMap(AnalyticsMapper.JudgeAggRow::judgeId, Function.identity(), (a, b) -> a)); + + List judgeWorkload = new ArrayList<>(); + for (Map.Entry entry : judgeBase.entrySet()) { + Long judgeId = entry.getKey(); + AnalyticsMapper.JudgeBaseRow base = entry.getValue(); + AnalyticsMapper.JudgeAggRow agg = judgeAgg.get(judgeId); + + long assigned = agg != null && agg.assignedCount() != null ? agg.assignedCount() : 0L; + long scored = agg != null && agg.scoredCount() != null ? agg.scoredCount() : 0L; + + AnalyticsReviewVO.JudgeWorkloadItem item = new AnalyticsReviewVO.JudgeWorkloadItem(); + item.setJudgeId(judgeId); + item.setJudgeName(base.judgeName() != null ? base.judgeName() : "-"); + item.setContestCount(base.contestCount() != null ? base.contestCount().intValue() : 0); + item.setAssignedCount(assigned); + item.setScoredCount(scored); + item.setCompletionRate(assigned > 0 ? (int) Math.round(scored * 100.0 / assigned) : 0); + item.setAvgScore(agg != null && agg.avgScore() != null ? round2(agg.avgScore()) : null); + item.setScoreStddev(agg != null && agg.scoreStddev() != null ? round2(agg.scoreStddev()) : 0d); + judgeWorkload.add(item); + } + judgeWorkload.sort(Comparator.comparing(AnalyticsReviewVO.JudgeWorkloadItem::getAssignedCount).reversed()); + vo.setJudgeWorkload(judgeWorkload); + + // 奖项分布 + List groups = analyticsMapper.selectAwardDistribution(tenantId, visibleContestIds); + long total = groups.stream().mapToLong(g -> Optional.ofNullable(g.cnt()).orElse(0L)).sum(); + + List awardDistribution = new ArrayList<>(); + for (AnalyticsMapper.AwardGroupRow g : groups) { + AnalyticsReviewVO.AwardDistributionItem item = new AnalyticsReviewVO.AwardDistributionItem(); + item.setAwardName(g.awardName()); + item.setCount(Optional.ofNullable(g.cnt()).orElse(0L)); + item.setPercentage(total > 0 ? (int) Math.round(item.getCount() * 100.0 / total) : 0); + awardDistribution.add(item); + } + vo.setAwardDistribution(awardDistribution); + + return vo; + } + + private static AnalyticsOverviewVO.Summary emptySummary() { + AnalyticsOverviewVO.Summary s = new AnalyticsOverviewVO.Summary(); + s.setTotalContests(0); + s.setTotalRegistrations(0L); + s.setPassedRegistrations(0L); + s.setTotalWorks(0L); + s.setReviewedWorks(0L); + s.setAwardedWorks(0L); + return s; + } + + private static AnalyticsOverviewVO.Funnel emptyFunnel() { + AnalyticsOverviewVO.Funnel f = new AnalyticsOverviewVO.Funnel(); + f.setRegistered(0L); + f.setPassed(0L); + f.setSubmitted(0L); + f.setReviewed(0L); + f.setAwarded(0L); + return f; + } + + private static List buildEmptyMonthsTrend() { + LocalDate firstDay = LocalDate.now().minusMonths(5).withDayOfMonth(1); + List list = new ArrayList<>(); + for (int i = 0; i < 6; i++) { + LocalDate d = firstDay.plusMonths(i); + AnalyticsOverviewVO.MonthlyTrendItem item = new AnalyticsOverviewVO.MonthlyTrendItem(); + item.setMonth(String.format("%d-%02d", d.getYear(), d.getMonthValue())); + item.setRegistrations(0L); + item.setWorks(0L); + list.add(item); + } + return list; + } + + private static AnalyticsReviewVO.Efficiency emptyEfficiency() { + AnalyticsReviewVO.Efficiency e = new AnalyticsReviewVO.Efficiency(); + e.setAvgReviewDays(0d); + e.setDailyReviewCount(0d); + e.setPendingAssignments(0L); + e.setAvgScoreStddev(0d); + return e; + } + + private static double round1(double v) { + return BigDecimal.valueOf(v).setScale(1, BigDecimal.ROUND_HALF_UP).doubleValue(); + } + + private static double round2(double v) { + return BigDecimal.valueOf(v).setScale(2, BigDecimal.ROUND_HALF_UP).doubleValue(); + } +} + diff --git a/java-backend/src/main/java/com/lesingle/creation/service/impl/AuthServiceImpl.java b/java-backend/src/main/java/com/lesingle/creation/service/impl/AuthServiceImpl.java index 71e3a3e..db2b7f0 100644 --- a/java-backend/src/main/java/com/lesingle/creation/service/impl/AuthServiceImpl.java +++ b/java-backend/src/main/java/com/lesingle/creation/service/impl/AuthServiceImpl.java @@ -206,11 +206,11 @@ public class AuthServiceImpl implements AuthService { } // 获取用户角色列表 - List roles = getUserRoles(user.getId()); + List roles = getUserRoles(user.getId(), tenantId); vo.setRoles(roles); // 获取用户权限列表 - List permissions = getUserPermissions(user.getId()); + List permissions = getUserPermissions(user.getId(), tenantId); vo.setPermissions(permissions); return vo; @@ -219,7 +219,7 @@ public class AuthServiceImpl implements AuthService { /** * 获取用户角色列表 */ - private List getUserRoles(Long userId) { + private List getUserRoles(Long userId, Long tenantId) { // 查询用户关联的角色 List userRoles = userRoleMapper.selectList( new LambdaQueryWrapper() @@ -235,20 +235,23 @@ public class AuthServiceImpl implements AuthService { .map(UserRole::getRoleId) .collect(Collectors.toList()); - // 查询角色详情 - List roles = roleMapper.selectBatchIds(roleIds); + // 查询角色详情(仅当前租户有效角色,避免跨租户角色串台) + List roles = roleMapper.selectList( + new LambdaQueryWrapper() + .in(Role::getId, roleIds) + .eq(Role::getTenantId, tenantId) + .eq(Role::getDeleted, 0) + .eq(Role::getValidState, 1) + ); // 返回角色编码列表(只返回有效的角色) - return roles.stream() - .filter(role -> role.getValidState() == 1) - .map(Role::getCode) - .collect(Collectors.toList()); + return roles.stream().map(Role::getCode).collect(Collectors.toList()); } /** * 获取用户权限列表 */ - private List getUserPermissions(Long userId) { + private List getUserPermissions(Long userId, Long tenantId) { // 查询用户关联的角色 List userRoles = userRoleMapper.selectList( new LambdaQueryWrapper() @@ -264,10 +267,25 @@ public class AuthServiceImpl implements AuthService { .map(UserRole::getRoleId) .collect(Collectors.toList()); + // 仅保留当前租户有效角色(避免跨租户角色导致权限串台) + List tenantRoleIds = roleMapper.selectList( + new LambdaQueryWrapper() + .in(Role::getId, roleIds) + .eq(Role::getTenantId, tenantId) + .eq(Role::getDeleted, 0) + .eq(Role::getValidState, 1) + ).stream() + .map(Role::getId) + .collect(Collectors.toList()); + + if (tenantRoleIds.isEmpty()) { + return new ArrayList<>(); + } + // 查询角色关联的权限 List rolePermissions = rolePermissionMapper.selectList( new LambdaQueryWrapper() - .in(RolePermission::getRoleId, roleIds) + .in(RolePermission::getRoleId, tenantRoleIds) ); if (rolePermissions.isEmpty()) { diff --git a/java-backend/src/main/java/com/lesingle/creation/service/impl/InteractionServiceImpl.java b/java-backend/src/main/java/com/lesingle/creation/service/impl/InteractionServiceImpl.java new file mode 100644 index 0000000..76ca162 --- /dev/null +++ b/java-backend/src/main/java/com/lesingle/creation/service/impl/InteractionServiceImpl.java @@ -0,0 +1,218 @@ +package com.lesingle.creation.service.impl; + +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper; +import com.lesingle.creation.common.exception.BusinessException; +import com.lesingle.creation.entity.UserWork; +import com.lesingle.creation.entity.UserWorkFavorite; +import com.lesingle.creation.entity.UserWorkLike; +import com.lesingle.creation.mapper.UserWorkFavoriteMapper; +import com.lesingle.creation.mapper.UserWorkLikeMapper; +import com.lesingle.creation.mapper.UserWorkMapper; +import com.lesingle.creation.service.InteractionService; +import com.lesingle.creation.vo.publicwork.InteractionStatusVO; +import com.lesingle.creation.vo.publicwork.MyFavoritesVO; +import com.lesingle.creation.vo.publicwork.ToggleFavoriteVO; +import com.lesingle.creation.vo.publicwork.ToggleLikeVO; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.Set; +import java.util.HashMap; +import java.util.HashSet; + +@Service +@RequiredArgsConstructor +public class InteractionServiceImpl implements InteractionService { + + private final UserWorkMapper userWorkMapper; + private final UserWorkLikeMapper userWorkLikeMapper; + private final UserWorkFavoriteMapper userWorkFavoriteMapper; + + @Override + @Transactional(rollbackFor = Exception.class) + public ToggleLikeVO toggleLike(Long userId, Long workId) { + UserWork work = ensureWorkExists(workId); + Long tenantId = work.getTenantId(); + + UserWorkLike existing = userWorkLikeMapper.selectOne(new LambdaQueryWrapper() + .eq(UserWorkLike::getTenantId, tenantId) + .eq(UserWorkLike::getUserId, userId) + .eq(UserWorkLike::getWorkId, workId) + .last("LIMIT 1")); + + ToggleLikeVO vo = new ToggleLikeVO(); + if (existing != null) { + userWorkLikeMapper.deleteById(existing.getId()); + userWorkMapper.update(null, new LambdaUpdateWrapper() + .eq(UserWork::getId, workId) + .setSql("like_count = IF(like_count > 0, like_count - 1, 0)")); + vo.setLiked(false); + } else { + UserWorkLike like = new UserWorkLike(); + like.setTenantId(tenantId); + like.setUserId(userId); + like.setWorkId(workId); + userWorkLikeMapper.insert(like); + + userWorkMapper.update(null, new LambdaUpdateWrapper() + .eq(UserWork::getId, workId) + .setSql("like_count = like_count + 1")); + vo.setLiked(true); + } + + UserWork updated = userWorkMapper.selectById(workId); + vo.setLikeCount(Optional.ofNullable(updated.getLikeCount()).orElse(0)); + return vo; + } + + @Override + @Transactional(rollbackFor = Exception.class) + public ToggleFavoriteVO toggleFavorite(Long userId, Long workId) { + UserWork work = ensureWorkExists(workId); + Long tenantId = work.getTenantId(); + + UserWorkFavorite existing = userWorkFavoriteMapper.selectOne(new LambdaQueryWrapper() + .eq(UserWorkFavorite::getTenantId, tenantId) + .eq(UserWorkFavorite::getUserId, userId) + .eq(UserWorkFavorite::getWorkId, workId) + .last("LIMIT 1")); + + ToggleFavoriteVO vo = new ToggleFavoriteVO(); + if (existing != null) { + userWorkFavoriteMapper.deleteById(existing.getId()); + userWorkMapper.update(null, new LambdaUpdateWrapper() + .eq(UserWork::getId, workId) + .setSql("favorite_count = IF(favorite_count > 0, favorite_count - 1, 0)")); + vo.setFavorited(false); + } else { + UserWorkFavorite fav = new UserWorkFavorite(); + fav.setTenantId(tenantId); + fav.setUserId(userId); + fav.setWorkId(workId); + userWorkFavoriteMapper.insert(fav); + + userWorkMapper.update(null, new LambdaUpdateWrapper() + .eq(UserWork::getId, workId) + .setSql("favorite_count = favorite_count + 1")); + vo.setFavorited(true); + } + + UserWork updated = userWorkMapper.selectById(workId); + vo.setFavoriteCount(Optional.ofNullable(updated.getFavoriteCount()).orElse(0)); + return vo; + } + + @Override + public InteractionStatusVO getInteractionStatus(Long userId, Long workId) { + UserWork work = ensureWorkExists(workId); + Long tenantId = work.getTenantId(); + + boolean liked = userWorkLikeMapper.selectCount(new LambdaQueryWrapper() + .eq(UserWorkLike::getTenantId, tenantId) + .eq(UserWorkLike::getUserId, userId) + .eq(UserWorkLike::getWorkId, workId)) > 0; + + boolean favorited = userWorkFavoriteMapper.selectCount(new LambdaQueryWrapper() + .eq(UserWorkFavorite::getTenantId, tenantId) + .eq(UserWorkFavorite::getUserId, userId) + .eq(UserWorkFavorite::getWorkId, workId)) > 0; + + InteractionStatusVO vo = new InteractionStatusVO(); + vo.setLiked(liked); + vo.setFavorited(favorited); + return vo; + } + + @Override + public MyFavoritesVO getMyFavorites(Long tenantId, Long userId, Integer page, Integer pageSize) { + int p = page == null || page < 1 ? 1 : page; + int ps = pageSize == null || pageSize < 1 ? 12 : pageSize; + + long offset = (long) (p - 1) * ps; + List rows = userWorkFavoriteMapper.selectMyFavorites(tenantId, userId, offset, ps); + long total = Optional.ofNullable(userWorkFavoriteMapper.countMyFavorites(tenantId, userId)).orElse(0L); + + List list = new ArrayList<>(); + for (UserWorkFavoriteMapper.MyFavoriteRow r : rows) { + MyFavoritesVO.Creator creator = new MyFavoritesVO.Creator(); + creator.setId(r.creatorId()); + creator.setNickname(r.creatorNickname()); + creator.setAvatar(r.creatorAvatar()); + + MyFavoritesVO.Work work = new MyFavoritesVO.Work(); + work.setId(r.workId()); + work.setTitle(r.workTitle()); + work.setCoverUrl(r.coverUrl()); + work.setLikeCount(Optional.ofNullable(r.likeCount()).orElse(0)); + work.setViewCount(Optional.ofNullable(r.viewCount()).orElse(0)); + work.setFavoriteCount(Optional.ofNullable(r.favoriteCount()).orElse(0)); + work.setCreator(creator); + + MyFavoritesVO.Item item = new MyFavoritesVO.Item(); + item.setId(r.favoriteId()); + item.setWorkId(r.workId()); + item.setCreateTime(r.favoriteTime()); + item.setWork(work); + + list.add(item); + } + + MyFavoritesVO vo = new MyFavoritesVO(); + vo.setList(list); + vo.setTotal(total); + vo.setPage(p); + vo.setPageSize(ps); + return vo; + } + + @Override + public Map batchGetInteractionStatus(Long tenantId, Long userId, List workIds) { + if (workIds == null || workIds.isEmpty()) { + return new HashMap<>(); + } + + List likes = userWorkLikeMapper.selectList(new LambdaQueryWrapper() + .eq(UserWorkLike::getTenantId, tenantId) + .eq(UserWorkLike::getUserId, userId) + .in(UserWorkLike::getWorkId, workIds) + ); + List favorites = userWorkFavoriteMapper.selectList(new LambdaQueryWrapper() + .eq(UserWorkFavorite::getTenantId, tenantId) + .eq(UserWorkFavorite::getUserId, userId) + .in(UserWorkFavorite::getWorkId, workIds) + ); + + Set likedSet = new HashSet<>(); + for (UserWorkLike l : likes) likedSet.add(l.getWorkId()); + + Set favoritedSet = new HashSet<>(); + for (UserWorkFavorite f : favorites) favoritedSet.add(f.getWorkId()); + + Map result = new HashMap<>(); + for (Long id : workIds) { + InteractionStatusVO vo = new InteractionStatusVO(); + vo.setLiked(likedSet.contains(id)); + vo.setFavorited(favoritedSet.contains(id)); + result.put(id, vo); + } + return result; + } + + private UserWork ensureWorkExists(Long workId) { + UserWork work = userWorkMapper.selectById(workId); + if (work == null || work.getIsDeleted() != null && work.getIsDeleted() == 1) { + throw new BusinessException("作品不存在或未发布"); + } + if (!"published".equals(work.getStatus())) { + throw new BusinessException("作品不存在或未发布"); + } + return work; + } +} + diff --git a/java-backend/src/main/java/com/lesingle/creation/service/impl/MenuServiceImpl.java b/java-backend/src/main/java/com/lesingle/creation/service/impl/MenuServiceImpl.java index 4201df2..129dbf4 100644 --- a/java-backend/src/main/java/com/lesingle/creation/service/impl/MenuServiceImpl.java +++ b/java-backend/src/main/java/com/lesingle/creation/service/impl/MenuServiceImpl.java @@ -39,6 +39,8 @@ public class MenuServiceImpl extends ServiceImpl implements Me private final PermissionMapper permissionMapper; private final RoleMenuMapper roleMenuMapper; private final RoleMapper roleMapper; + private final TenantMenuMapper tenantMenuMapper; + private final TenantMapper tenantMapper; @Override @Transactional(rollbackFor = Exception.class) @@ -47,6 +49,7 @@ public class MenuServiceImpl extends ServiceImpl implements Me // 创建菜单 Menu menu = new Menu(); + menu.setScene(StringUtils.hasText(dto.getScene()) ? dto.getScene() : "tenant"); menu.setName(dto.getName()); menu.setPath(dto.getPath()); menu.setIcon(dto.getIcon()); @@ -65,11 +68,18 @@ public class MenuServiceImpl extends ServiceImpl implements Me @Override public List tree() { - log.info("查询菜单树"); + return tree(null, null); + } - // 查询所有菜单 + @Override + public List tree(String scene, Long tenantId) { + String resolvedScene = resolveScene(scene, tenantId); + log.info("查询菜单树,scene: {}, tenantId: {}", resolvedScene, tenantId); + + // 查询指定场景下的菜单 LambdaQueryWrapper wrapper = new LambdaQueryWrapper<>(); wrapper.eq(Menu::getValidState, 1) + .eq(Menu::getScene, resolvedScene) .orderByAsc(Menu::getSort, Menu::getId); List allMenus = menuMapper.selectList(wrapper); @@ -82,6 +92,11 @@ public class MenuServiceImpl extends ServiceImpl implements Me public List getUserMenus(Long userId, Long tenantId) { log.info("查询用户菜单,用户 ID: {}, 租户 ID: {}", userId, tenantId); + // 租户端:菜单仅由机构开通(TenantMenu)决定,不受角色权限二次裁剪 + if (!isSuperTenant(tenantId)) { + return getTenantEnabledMenusOnly(tenantId); + } + // 1. 查询用户关联的角色 List userRoles = userRoleMapper.selectList( new LambdaQueryWrapper() @@ -99,18 +114,31 @@ public class MenuServiceImpl extends ServiceImpl implements Me .map(UserRole::getRoleId) .collect(Collectors.toList()); + // 2.1 仅保留当前租户下的有效角色(避免跨租户角色串台) + List tenantRoles = roleMapper.selectList( + new LambdaQueryWrapper() + .in(Role::getId, roleIds) + .eq(Role::getTenantId, tenantId) + .eq(Role::getDeleted, 0) + .eq(Role::getValidState, 1) + ); + + if (tenantRoles.isEmpty()) { + log.warn("用户在当前租户下没有有效角色,用户 ID: {}, 租户 ID: {}", userId, tenantId); + return new ArrayList<>(); + } + + List tenantRoleIds = tenantRoles.stream().map(Role::getId).collect(Collectors.toList()); + // 3. 判断是否超级管理员(角色 code=super_admin 或权限 code=super_admin 兜底) boolean hasSuperAdmin = false; - List roles = roleMapper.selectBatchIds(roleIds); - if (roles != null) { - hasSuperAdmin = roles.stream().anyMatch(r -> "super_admin".equals(r.getCode())); - } + hasSuperAdmin = tenantRoles.stream().anyMatch(r -> "super_admin".equals(r.getCode())); if (!hasSuperAdmin) { // 兜底:如果权限中包含 super_admin,也视为超管 List rolePermissionsForSuper = rolePermissionMapper.selectList( new LambdaQueryWrapper() - .in(RolePermission::getRoleId, roleIds) + .in(RolePermission::getRoleId, tenantRoleIds) ); if (!rolePermissionsForSuper.isEmpty()) { List permissionIds = rolePermissionsForSuper.stream() @@ -123,62 +151,160 @@ public class MenuServiceImpl extends ServiceImpl implements Me } } - // 4. 查询菜单(超管返回所有;非超管按角色-菜单关联取并集 + 补齐祖先节点) - LambdaQueryWrapper wrapper = new LambdaQueryWrapper<>(); - wrapper.eq(Menu::getValidState, 1) + // 4) super_admin 快速路径:跳过 TenantMenu 与 permission 过滤,直接返回全部有效菜单 + LambdaQueryWrapper allValidWrapper = new LambdaQueryWrapper<>(); + allValidWrapper.eq(Menu::getValidState, 1) .orderByAsc(Menu::getSort, Menu::getId); + List allValidMenus = menuMapper.selectList(allValidWrapper); - List allMenus; if (hasSuperAdmin) { - // 超管:查询所有菜单 - log.info("用户是超级管理员,返回所有菜单"); - allMenus = menuMapper.selectList(wrapper); - } else { - // 非超管:按角色-菜单关联表控制可见性 - List roleMenus = roleMenuMapper.selectList( - new LambdaQueryWrapper() - .in(RoleMenu::getRoleId, roleIds) - ); - if (roleMenus.isEmpty()) { - log.warn("用户角色未配置任何菜单可见性,用户 ID: {}", userId); - return new ArrayList<>(); - } - - Set visibleMenuIds = roleMenus.stream() - .map(RoleMenu::getMenuId) - .collect(Collectors.toSet()); - - // 拉取全量菜单用于补齐祖先节点,然后再裁剪 - List all = menuMapper.selectList(wrapper); - Map menuMap = new HashMap<>(); - for (Menu m : all) { - menuMap.put(m.getId(), m); - } - - Set withAncestors = new HashSet<>(visibleMenuIds); - for (Long mid : visibleMenuIds) { - Menu cur = menuMap.get(mid); - while (cur != null) { - Long pid = cur.getParentId(); - if (pid == null || pid == 0L) { - break; - } - if (withAncestors.add(pid)) { - cur = menuMap.get(pid); - } else { - // 已存在,继续向上也可能已处理过 - cur = menuMap.get(pid); - } - } - } - - allMenus = all.stream() - .filter(m -> withAncestors.contains(m.getId())) - .collect(Collectors.toList()); + log.info("用户是 super_admin,返回全部有效菜单(跳过 TenantMenu 与 permission 过滤)"); + return buildTree(allValidMenus, 0L); } - // 5. 构建树形结构 - return buildTree(allMenus, 0L); + // 5) 平台端非 super_admin:按文档 findUserMenus 逻辑过滤(TenantMenu 范围 + permission 可见性) + // 5.1 获取用户权限码列表 + Set userPermissionCodes = new HashSet<>(); + List rolePermissions = rolePermissionMapper.selectList( + new LambdaQueryWrapper() + .in(RolePermission::getRoleId, tenantRoleIds) + ); + if (rolePermissions != null && !rolePermissions.isEmpty()) { + List permissionIds = rolePermissions.stream() + .map(RolePermission::getPermissionId) + .distinct() + .collect(Collectors.toList()); + List permissions = permissionMapper.selectBatchIds(permissionIds); + if (permissions != null) { + userPermissionCodes.addAll(permissions.stream() + .filter(p -> p != null && p.getValidState() != null && p.getValidState() == 1) + .map(Permission::getCode) + .filter(StringUtils::hasText) + .collect(Collectors.toSet())); + } + } + + // 5.2 获取租户已分配菜单 ID 列表 + List tenantMenus = tenantMenuMapper.selectList( + new LambdaQueryWrapper() + .eq(TenantMenu::getTenantId, tenantId) + ); + if (tenantMenus == null || tenantMenus.isEmpty()) { + log.warn("租户未开通任何菜单(tenant_menu 为空),tenantId: {}", tenantId); + return new ArrayList<>(); + } + Set tenantMenuIds = tenantMenus.stream() + .map(TenantMenu::getMenuId) + .collect(Collectors.toSet()); + + // 5.3 过滤:在租户菜单范围内 AND(无 permission 或用户拥有 permission) + Map menuMap = new HashMap<>(); + for (Menu m : allValidMenus) { + menuMap.put(m.getId(), m); + } + + Set visibleMenuIds = new HashSet<>(); + for (Menu m : allValidMenus) { + if (m == null) { + continue; + } + if (!tenantMenuIds.contains(m.getId())) { + continue; + } + String perm = m.getPermission(); + if (!StringUtils.hasText(perm) || userPermissionCodes.contains(perm)) { + visibleMenuIds.add(m.getId()); + } + } + + if (visibleMenuIds.isEmpty()) { + return new ArrayList<>(); + } + + // 5.4 补齐祖先节点(用于构建树) + Set withAncestors = new HashSet<>(visibleMenuIds); + for (Long mid : visibleMenuIds) { + Menu cur = menuMap.get(mid); + while (cur != null) { + Long pid = cur.getParentId(); + if (pid == null || pid == 0L) { + break; + } + withAncestors.add(pid); + cur = menuMap.get(pid); + } + } + + List visibleMenus = allValidMenus.stream() + .filter(m -> withAncestors.contains(m.getId())) + .collect(Collectors.toList()); + + return buildTree(visibleMenus, 0L); + } + + private boolean isSuperTenant(Long tenantId) { + if (tenantId == null) { + return false; + } + Tenant tenant = tenantMapper.selectById(tenantId); + if (tenant == null) { + return false; + } + boolean isSuper = tenant.getIsSuper() != null && tenant.getIsSuper() == 1; + boolean isPlatformType = "platform".equals(tenant.getTenantType()); + boolean isSuperCode = "super".equals(tenant.getCode()); + return isSuper || isPlatformType || isSuperCode; + } + + /** + * 租户端菜单:只按 TenantMenu 开通范围返回(不做 permission 裁剪) + */ + private List getTenantEnabledMenusOnly(Long tenantId) { + if (tenantId == null) { + return new ArrayList<>(); + } + + List tenantMenus = tenantMenuMapper.selectList( + new LambdaQueryWrapper() + .eq(TenantMenu::getTenantId, tenantId) + ); + if (tenantMenus == null || tenantMenus.isEmpty()) { + log.warn("租户未开通任何菜单(tenant_menu 为空),tenantId: {}", tenantId); + return new ArrayList<>(); + } + + Set enabledMenuIds = tenantMenus.stream() + .map(TenantMenu::getMenuId) + .collect(Collectors.toSet()); + + // 拉取全部有效菜单(全局模板),再按 enabledMenuIds 裁剪并补齐祖先节点 + List allValidMenus = menuMapper.selectList(new LambdaQueryWrapper() + .eq(Menu::getValidState, 1) + .orderByAsc(Menu::getSort, Menu::getId)); + + Map menuMap = new HashMap<>(); + for (Menu m : allValidMenus) { + menuMap.put(m.getId(), m); + } + + Set withAncestors = new HashSet<>(enabledMenuIds); + for (Long mid : enabledMenuIds) { + Menu cur = menuMap.get(mid); + while (cur != null) { + Long pid = cur.getParentId(); + if (pid == null || pid == 0L) { + break; + } + withAncestors.add(pid); + cur = menuMap.get(pid); + } + } + + List visibleMenus = allValidMenus.stream() + .filter(m -> withAncestors.contains(m.getId())) + .collect(Collectors.toList()); + + return buildTree(visibleMenus, 0L); } @Override @@ -206,6 +332,9 @@ public class MenuServiceImpl extends ServiceImpl implements Me // 更新菜单信息 Menu menu = new Menu(); menu.setId(id); + if (StringUtils.hasText(dto.getScene())) { + menu.setScene(dto.getScene()); + } menu.setName(dto.getName()); menu.setPath(dto.getPath()); menu.setIcon(dto.getIcon()); @@ -266,6 +395,7 @@ public class MenuServiceImpl extends ServiceImpl implements Me */ private MenuDetailVO convertToDetailVO(Menu menu) { MenuDetailVO vo = new MenuDetailVO(); + vo.setScene(menu.getScene()); vo.setId(menu.getId()); vo.setName(menu.getName()); vo.setPath(menu.getPath()); @@ -287,6 +417,7 @@ public class MenuServiceImpl extends ServiceImpl implements Me */ private MenuTreeVO convertToTreeVO(Menu menu) { MenuTreeVO vo = new MenuTreeVO(); + vo.setScene(menu.getScene()); vo.setId(menu.getId()); vo.setName(menu.getName()); vo.setPath(menu.getPath()); @@ -297,4 +428,76 @@ public class MenuServiceImpl extends ServiceImpl implements Me vo.setSort(menu.getSort()); return vo; } + + private String resolveScene(String scene, Long tenantId) { + if (StringUtils.hasText(scene)) { + return scene; + } + if (tenantId == null) { + return "tenant"; + } + Tenant tenant = tenantMapper.selectById(tenantId); + if (tenant != null) { + // 兜底:即使历史数据未回填 is_super,只要 tenant_type=platform 也视为平台租户 + boolean isSuper = tenant.getIsSuper() != null && tenant.getIsSuper() == 1; + boolean isPlatformType = "platform".equals(tenant.getTenantType()); + // 文档约定:超管租户 code=super + boolean isSuperCode = "super".equals(tenant.getCode()); + if (isSuper || isPlatformType || isSuperCode) { + return "portal"; + } + } + return "tenant"; + } + + private List getTenantUserMenus(Long tenantId) { + if (tenantId == null) { + return new ArrayList<>(); + } + + // 1) 查询租户已开通菜单 + List tenantMenus = tenantMenuMapper.selectList( + new LambdaQueryWrapper() + .eq(TenantMenu::getTenantId, tenantId) + ); + if (tenantMenus == null || tenantMenus.isEmpty()) { + log.warn("租户未开通任何菜单(tenant_menu 为空),tenantId: {}", tenantId); + return new ArrayList<>(); + } + + Set enabledMenuIds = tenantMenus.stream() + .map(TenantMenu::getMenuId) + .collect(Collectors.toSet()); + + // 2) 拉取租户端全量菜单,用于补齐祖先节点,再按 enabledMenuIds 裁剪 + LambdaQueryWrapper wrapper = new LambdaQueryWrapper<>(); + wrapper.eq(Menu::getValidState, 1) + .eq(Menu::getScene, "tenant") + .orderByAsc(Menu::getSort, Menu::getId); + List allTenantMenus = menuMapper.selectList(wrapper); + + Map menuMap = new HashMap<>(); + for (Menu m : allTenantMenus) { + menuMap.put(m.getId(), m); + } + + Set withAncestors = new HashSet<>(enabledMenuIds); + for (Long mid : enabledMenuIds) { + Menu cur = menuMap.get(mid); + while (cur != null) { + Long pid = cur.getParentId(); + if (pid == null || pid == 0L) { + break; + } + withAncestors.add(pid); + cur = menuMap.get(pid); + } + } + + List visible = allTenantMenus.stream() + .filter(m -> withAncestors.contains(m.getId())) + .collect(Collectors.toList()); + + return buildTree(visible, 0L); + } } diff --git a/java-backend/src/main/java/com/lesingle/creation/service/impl/RoleServiceImpl.java b/java-backend/src/main/java/com/lesingle/creation/service/impl/RoleServiceImpl.java index ca6c45c..24e7005 100644 --- a/java-backend/src/main/java/com/lesingle/creation/service/impl/RoleServiceImpl.java +++ b/java-backend/src/main/java/com/lesingle/creation/service/impl/RoleServiceImpl.java @@ -14,6 +14,7 @@ import com.lesingle.creation.mapper.PermissionMapper; import com.lesingle.creation.mapper.RoleMenuMapper; import com.lesingle.creation.mapper.RoleMapper; import com.lesingle.creation.mapper.RolePermissionMapper; +import com.lesingle.creation.mapper.TenantMapper; import com.lesingle.creation.service.RoleService; import com.lesingle.creation.vo.role.RoleDetailVO; import com.lesingle.creation.vo.role.RoleListVO; @@ -39,6 +40,7 @@ public class RoleServiceImpl extends ServiceImpl implements Ro private final RolePermissionMapper rolePermissionMapper; private final PermissionMapper permissionMapper; private final RoleMenuMapper roleMenuMapper; + private final TenantMapper tenantMapper; @Override @Transactional(rollbackFor = Exception.class) @@ -79,6 +81,9 @@ public class RoleServiceImpl extends ServiceImpl implements Ro // 如果提供了菜单 ID,创建菜单关联(用于菜单可见性授权) if (!CollectionUtils.isEmpty(dto.getMenuIds())) { + if (!isPortalTenant(tenantId)) { + throw new BusinessException("当前租户不支持角色菜单授权,请在机构管理中配置可用菜单"); + } List roleMenus = dto.getMenuIds().stream() .distinct() .map(menuId -> { @@ -190,6 +195,9 @@ public class RoleServiceImpl extends ServiceImpl implements Ro // 如果提供了 menuIds,更新菜单关联(菜单可见性授权) if (dto.getMenuIds() != null) { + if (!isPortalTenant(tenantId)) { + throw new BusinessException("当前租户不支持角色菜单授权,请在机构管理中配置可用菜单"); + } LambdaQueryWrapper rmWrapper = new LambdaQueryWrapper<>(); rmWrapper.eq(RoleMenu::getRoleId, id); roleMenuMapper.delete(rmWrapper); @@ -209,6 +217,13 @@ public class RoleServiceImpl extends ServiceImpl implements Ro } } + // 普通租户:清理历史 role_menu,避免遗留数据造成误解(租户端菜单不依赖 role_menu) + if (!isPortalTenant(tenantId)) { + LambdaQueryWrapper rmWrapper = new LambdaQueryWrapper<>(); + rmWrapper.eq(RoleMenu::getRoleId, id); + roleMenuMapper.delete(rmWrapper); + } + return convertToDetailVO(roleMapper.selectById(id)); } @@ -291,21 +306,39 @@ public class RoleServiceImpl extends ServiceImpl implements Ro vo.setPermissionNames(names); } - // 获取角色菜单(用于菜单授权回显) - LambdaQueryWrapper rmWrapper = new LambdaQueryWrapper<>(); - rmWrapper.eq(RoleMenu::getRoleId, role.getId()); - List roleMenus = roleMenuMapper.selectList(rmWrapper); - List menuIds = roleMenus == null - ? new ArrayList<>() - : roleMenus.stream() - .map(RoleMenu::getMenuId) - .distinct() - .collect(Collectors.toList()); - vo.setMenuIds(menuIds); + // 获取角色菜单(仅平台端用于菜单授权回显;租户端角色不参与菜单可见性) + if (isPortalTenant(role.getTenantId())) { + LambdaQueryWrapper rmWrapper = new LambdaQueryWrapper<>(); + rmWrapper.eq(RoleMenu::getRoleId, role.getId()); + List roleMenus = roleMenuMapper.selectList(rmWrapper); + List menuIds = roleMenus == null + ? new ArrayList<>() + : roleMenus.stream() + .map(RoleMenu::getMenuId) + .distinct() + .collect(Collectors.toList()); + vo.setMenuIds(menuIds); + } else { + vo.setMenuIds(new ArrayList<>()); + } return vo; } + private boolean isPortalTenant(Long tenantId) { + if (tenantId == null) { + return false; + } + var tenant = tenantMapper.selectById(tenantId); + if (tenant == null) { + return false; + } + boolean isSuper = tenant.getIsSuper() != null && tenant.getIsSuper() == 1; + boolean isPlatformType = "platform".equals(tenant.getTenantType()); + boolean isSuperCode = "super".equals(tenant.getCode()); + return isSuper || isPlatformType || isSuperCode; + } + /** * 转换为列表 VO */ diff --git a/java-backend/src/main/java/com/lesingle/creation/service/impl/TenantServiceImpl.java b/java-backend/src/main/java/com/lesingle/creation/service/impl/TenantServiceImpl.java index a264552..545301c 100644 --- a/java-backend/src/main/java/com/lesingle/creation/service/impl/TenantServiceImpl.java +++ b/java-backend/src/main/java/com/lesingle/creation/service/impl/TenantServiceImpl.java @@ -6,13 +6,16 @@ import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; import com.lesingle.creation.common.exception.BusinessException; import com.lesingle.creation.dto.tenant.CreateTenantDTO; import com.lesingle.creation.dto.tenant.UpdateTenantDTO; +import com.lesingle.creation.entity.Menu; import com.lesingle.creation.entity.Tenant; import com.lesingle.creation.entity.TenantMenu; import com.lesingle.creation.entity.User; +import com.lesingle.creation.mapper.MenuMapper; import com.lesingle.creation.mapper.TenantMapper; import com.lesingle.creation.mapper.UserMapper; import com.lesingle.creation.service.TenantMenuService; import com.lesingle.creation.service.TenantService; +import com.lesingle.creation.vo.menu.MenuTreeVO; import com.lesingle.creation.vo.tenant.TenantDetailVO; import com.lesingle.creation.vo.tenant.TenantListVO; import lombok.RequiredArgsConstructor; @@ -22,7 +25,11 @@ import org.springframework.transaction.annotation.Transactional; import org.springframework.util.CollectionUtils; import java.util.ArrayList; +import java.util.HashMap; +import java.util.HashSet; import java.util.List; +import java.util.Map; +import java.util.Set; import java.util.stream.Collectors; /** @@ -36,6 +43,7 @@ public class TenantServiceImpl extends ServiceImpl impleme private final TenantMapper tenantMapper; private final TenantMenuService tenantMenuService; private final UserMapper userMapper; + private final MenuMapper menuMapper; @Override @Transactional(rollbackFor = Exception.class) @@ -240,8 +248,76 @@ public class TenantServiceImpl extends ServiceImpl impleme throw new BusinessException("租户不存在"); } - // TODO: 实现获取租户菜单树逻辑 - return new ArrayList<>(); + // 1) 读取租户已开通菜单 + List tenantMenus = tenantMenuService.list( + new LambdaQueryWrapper() + .eq(TenantMenu::getTenantId, tenantId) + ); + if (tenantMenus == null || tenantMenus.isEmpty()) { + return new ArrayList<>(); + } + + Set enabledMenuIds = tenantMenus.stream() + .map(TenantMenu::getMenuId) + .collect(Collectors.toSet()); + + // 2) 拉取租户端全量菜单(scene=tenant),用于补齐祖先节点再裁剪 + List allTenantMenus = menuMapper.selectList( + new LambdaQueryWrapper() + .eq(Menu::getValidState, 1) + .eq(Menu::getScene, "tenant") + .orderByAsc(Menu::getSort, Menu::getId) + ); + + Map menuMap = new HashMap<>(); + for (Menu m : allTenantMenus) { + menuMap.put(m.getId(), m); + } + + Set withAncestors = new HashSet<>(enabledMenuIds); + for (Long mid : enabledMenuIds) { + Menu cur = menuMap.get(mid); + while (cur != null) { + Long pid = cur.getParentId(); + if (pid == null || pid == 0L) { + break; + } + withAncestors.add(pid); + cur = menuMap.get(pid); + } + } + + List visible = allTenantMenus.stream() + .filter(m -> withAncestors.contains(m.getId())) + .collect(Collectors.toList()); + + return buildMenuTreeVO(visible, 0L); + } + + private List buildMenuTreeVO(List menus, Long parentId) { + return menus.stream() + .filter(menu -> { + Long pid = menu.getParentId(); + if (pid == null) { + pid = 0L; + } + return pid.equals(parentId); + }) + .map(menu -> { + MenuTreeVO vo = new MenuTreeVO(); + vo.setScene(menu.getScene()); + vo.setId(menu.getId()); + vo.setName(menu.getName()); + vo.setPath(menu.getPath()); + vo.setIcon(menu.getIcon()); + vo.setComponent(menu.getComponent()); + vo.setParentId(menu.getParentId()); + vo.setPermission(menu.getPermission()); + vo.setSort(menu.getSort()); + vo.setChildren(buildMenuTreeVO(menus, menu.getId())); + return vo; + }) + .collect(Collectors.toList()); } /** diff --git a/java-backend/src/main/java/com/lesingle/creation/service/impl/UserDetailsServiceImpl.java b/java-backend/src/main/java/com/lesingle/creation/service/impl/UserDetailsServiceImpl.java index f106269..0f9b669 100644 --- a/java-backend/src/main/java/com/lesingle/creation/service/impl/UserDetailsServiceImpl.java +++ b/java-backend/src/main/java/com/lesingle/creation/service/impl/UserDetailsServiceImpl.java @@ -88,6 +88,7 @@ public class UserDetailsServiceImpl implements UserDetailsService { // 查询租户信息 Tenant tenant = tenantMapper.selectById(user.getTenantId()); String tenantCode = tenant != null ? tenant.getCode() : "default"; + boolean isSuperTenant = tenant != null && tenant.getIsSuper() != null && tenant.getIsSuper() == 1; // 查询用户角色 List userRoles = userRoleMapper.selectList(new LambdaQueryWrapper() @@ -101,16 +102,37 @@ public class UserDetailsServiceImpl implements UserDetailsService { List roles = roleMapper.selectList( new LambdaQueryWrapper() .in(Role::getId, roleIds) - .eq(Role::getDeleted, 0)); + .eq(Role::getTenantId, user.getTenantId()) + .eq(Role::getDeleted, 0) + .eq(Role::getValidState, 1)); for (Role role : roles) { if (StringUtils.hasText(role.getCode())) { authorityStrings.add("ROLE_" + role.getCode()); } } + // 只使用当前租户有效角色ID,避免跨租户角色串台 + List tenantRoleIds = roles.stream().map(Role::getId).distinct().collect(Collectors.toList()); + if (tenantRoleIds.isEmpty()) { + authorityStrings.addAll(permissionCodes); + List authorities = authorityStrings.stream() + .map(SimpleGrantedAuthority::new) + .collect(Collectors.toList()); + UserPrincipal userPrincipal = new UserPrincipal( + user.getId(), + user.getUsername(), + user.getPassword(), + authorities, + user.getTenantId(), + tenantCode, + isSuperTenant + ); + return userPrincipal; + } + List rolePermissions = rolePermissionMapper.selectList( new LambdaQueryWrapper() - .in(RolePermission::getRoleId, roleIds) + .in(RolePermission::getRoleId, tenantRoleIds) ); if (!rolePermissions.isEmpty()) { @@ -144,7 +166,7 @@ public class UserDetailsServiceImpl implements UserDetailsService { authorities, user.getTenantId(), tenantCode, - "platform".equals(tenantCode) // platform 租户为超级租户 + isSuperTenant ); log.debug("用户加载成功:{}, 租户 ID: {}", user.getUsername(), user.getTenantId()); diff --git a/java-backend/src/main/java/com/lesingle/creation/vo/ai3d/AI3DTaskVO.java b/java-backend/src/main/java/com/lesingle/creation/vo/ai3d/AI3DTaskVO.java deleted file mode 100644 index 8fd3957..0000000 --- a/java-backend/src/main/java/com/lesingle/creation/vo/ai3d/AI3DTaskVO.java +++ /dev/null @@ -1,56 +0,0 @@ -package com.lesingle.creation.vo.ai3d; - -import io.swagger.v3.oas.annotations.media.Schema; -import lombok.Data; - -import java.time.LocalDateTime; - -/** - * AI 3D 任务 VO - */ -@Data -@Schema(description = "AI 3D 任务响应") -public class AI3DTaskVO { - - @Schema(description = "任务 ID") - private Long id; - - @Schema(description = "输入类型:text/image") - private String inputType; - - @Schema(description = "输入内容") - private String inputContent; - - @Schema(description = "生成类型:Normal/Geometry/LowPoly/Sketch") - private String generateType; - - @Schema(description = "任务状态:pending/processing/completed/failed/timeout") - private String status; - - @Schema(description = "生成的 3D 模型 URL") - private String resultUrl; - - @Schema(description = "预览图 URL") - private String previewUrl; - - @Schema(description = "生成的 3D 模型 URL 数组(JSON)") - private String resultUrls; - - @Schema(description = "预览图 URL 数组(JSON)") - private String previewUrls; - - @Schema(description = "错误信息") - private String errorMessage; - - @Schema(description = "外部 AI 服务任务 ID") - private String externalTaskId; - - @Schema(description = "已重试次数") - private Integer retryCount; - - @Schema(description = "创建时间") - private LocalDateTime createTime; - - @Schema(description = "完成时间") - private LocalDateTime completeTime; -} diff --git a/java-backend/src/main/java/com/lesingle/creation/vo/analytics/AnalyticsOverviewVO.java b/java-backend/src/main/java/com/lesingle/creation/vo/analytics/AnalyticsOverviewVO.java new file mode 100644 index 0000000..3a632b0 --- /dev/null +++ b/java-backend/src/main/java/com/lesingle/creation/vo/analytics/AnalyticsOverviewVO.java @@ -0,0 +1,62 @@ +package com.lesingle.creation.vo.analytics; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +import java.util.List; + +@Data +@Schema(description = "运营概览") +public class AnalyticsOverviewVO { + + @Schema(description = "汇总指标") + private Summary summary; + + @Schema(description = "漏斗数据") + private Funnel funnel; + + @Schema(description = "月度趋势") + private List monthlyTrend; + + @Schema(description = "活动对比") + private List contestComparison; + + @Data + public static class Summary { + private Integer totalContests; + private Long totalRegistrations; + private Long passedRegistrations; + private Long totalWorks; + private Long reviewedWorks; + private Long awardedWorks; + } + + @Data + public static class Funnel { + private Long registered; + private Long passed; + private Long submitted; + private Long reviewed; + private Long awarded; + } + + @Data + public static class MonthlyTrendItem { + private String month; + private Long registrations; + private Long works; + } + + @Data + public static class ContestComparisonItem { + private Long contestId; + private String contestName; + private Long registrations; + private Integer passRate; + private Integer submitRate; + private Integer reviewRate; + private Integer awardRate; + private Double avgScore; + } +} + diff --git a/java-backend/src/main/java/com/lesingle/creation/vo/analytics/AnalyticsReviewVO.java b/java-backend/src/main/java/com/lesingle/creation/vo/analytics/AnalyticsReviewVO.java new file mode 100644 index 0000000..204d198 --- /dev/null +++ b/java-backend/src/main/java/com/lesingle/creation/vo/analytics/AnalyticsReviewVO.java @@ -0,0 +1,48 @@ +package com.lesingle.creation.vo.analytics; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +import java.util.List; + +@Data +@Schema(description = "评审分析") +public class AnalyticsReviewVO { + + @Schema(description = "评审效率") + private Efficiency efficiency; + + @Schema(description = "评委工作量") + private List judgeWorkload; + + @Schema(description = "奖项分布") + private List awardDistribution; + + @Data + public static class Efficiency { + private Double avgReviewDays; + private Double dailyReviewCount; + private Long pendingAssignments; + private Double avgScoreStddev; + } + + @Data + public static class JudgeWorkloadItem { + private Long judgeId; + private String judgeName; + private Integer contestCount; + private Long assignedCount; + private Long scoredCount; + private Integer completionRate; + private Double avgScore; + private Double scoreStddev; + } + + @Data + public static class AwardDistributionItem { + private String awardName; + private Long count; + private Integer percentage; + } +} + diff --git a/java-backend/src/main/java/com/lesingle/creation/vo/menu/MenuDetailVO.java b/java-backend/src/main/java/com/lesingle/creation/vo/menu/MenuDetailVO.java index d6cf813..a8b6d45 100644 --- a/java-backend/src/main/java/com/lesingle/creation/vo/menu/MenuDetailVO.java +++ b/java-backend/src/main/java/com/lesingle/creation/vo/menu/MenuDetailVO.java @@ -13,6 +13,9 @@ import java.util.List; @Schema(description = "菜单详情响应") public class MenuDetailVO { + @Schema(description = "菜单场景:portal-平台端,tenant-租户端") + private String scene; + @Schema(description = "菜单 ID") private Long id; diff --git a/java-backend/src/main/java/com/lesingle/creation/vo/menu/MenuTreeVO.java b/java-backend/src/main/java/com/lesingle/creation/vo/menu/MenuTreeVO.java index 38ebdf2..d4ce417 100644 --- a/java-backend/src/main/java/com/lesingle/creation/vo/menu/MenuTreeVO.java +++ b/java-backend/src/main/java/com/lesingle/creation/vo/menu/MenuTreeVO.java @@ -13,6 +13,9 @@ import java.util.List; @Schema(description = "菜单树响应") public class MenuTreeVO { + @Schema(description = "菜单场景:portal-平台端,tenant-租户端") + private String scene; + @Schema(description = "菜单 ID") private Long id; diff --git a/java-backend/src/main/java/com/lesingle/creation/vo/publicwork/InteractionStatusVO.java b/java-backend/src/main/java/com/lesingle/creation/vo/publicwork/InteractionStatusVO.java new file mode 100644 index 0000000..52ff616 --- /dev/null +++ b/java-backend/src/main/java/com/lesingle/creation/vo/publicwork/InteractionStatusVO.java @@ -0,0 +1,16 @@ +package com.lesingle.creation.vo.publicwork; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +@Data +@Schema(description = "作品交互状态") +public class InteractionStatusVO { + + @Schema(description = "是否已点赞") + private Boolean liked; + + @Schema(description = "是否已收藏") + private Boolean favorited; +} + diff --git a/java-backend/src/main/java/com/lesingle/creation/vo/publicwork/MyFavoritesVO.java b/java-backend/src/main/java/com/lesingle/creation/vo/publicwork/MyFavoritesVO.java new file mode 100644 index 0000000..2177a42 --- /dev/null +++ b/java-backend/src/main/java/com/lesingle/creation/vo/publicwork/MyFavoritesVO.java @@ -0,0 +1,44 @@ +package com.lesingle.creation.vo.publicwork; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +import java.time.LocalDateTime; +import java.util.List; + +@Data +@Schema(description = "我的收藏列表") +public class MyFavoritesVO { + + private List list; + private Long total; + private Integer page; + private Integer pageSize; + + @Data + public static class Item { + private Long id; + private Long workId; + private LocalDateTime createTime; + private Work work; + } + + @Data + public static class Work { + private Long id; + private String title; + private String coverUrl; + private Integer likeCount; + private Integer viewCount; + private Integer favoriteCount; + private Creator creator; + } + + @Data + public static class Creator { + private Long id; + private String nickname; + private String avatar; + } +} + diff --git a/java-backend/src/main/java/com/lesingle/creation/vo/publicwork/ToggleFavoriteVO.java b/java-backend/src/main/java/com/lesingle/creation/vo/publicwork/ToggleFavoriteVO.java new file mode 100644 index 0000000..ce4f130 --- /dev/null +++ b/java-backend/src/main/java/com/lesingle/creation/vo/publicwork/ToggleFavoriteVO.java @@ -0,0 +1,16 @@ +package com.lesingle.creation.vo.publicwork; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +@Data +@Schema(description = "收藏/取消收藏响应") +public class ToggleFavoriteVO { + + @Schema(description = "是否已收藏") + private Boolean favorited; + + @Schema(description = "最新收藏数") + private Integer favoriteCount; +} + diff --git a/java-backend/src/main/java/com/lesingle/creation/vo/publicwork/ToggleLikeVO.java b/java-backend/src/main/java/com/lesingle/creation/vo/publicwork/ToggleLikeVO.java new file mode 100644 index 0000000..b89638a --- /dev/null +++ b/java-backend/src/main/java/com/lesingle/creation/vo/publicwork/ToggleLikeVO.java @@ -0,0 +1,16 @@ +package com.lesingle.creation.vo.publicwork; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +@Data +@Schema(description = "点赞/取消点赞响应") +public class ToggleLikeVO { + + @Schema(description = "是否已点赞") + private Boolean liked; + + @Schema(description = "最新点赞数") + private Integer likeCount; +} + diff --git a/java-backend/src/main/resources/db/migration/V30__create_ugc_user_work_interaction.sql b/java-backend/src/main/resources/db/migration/V30__create_ugc_user_work_interaction.sql new file mode 100644 index 0000000..6f6db35 --- /dev/null +++ b/java-backend/src/main/resources/db/migration/V30__create_ugc_user_work_interaction.sql @@ -0,0 +1,31 @@ +-- UGC 作品点赞/收藏明细表(对齐 Nest/Prisma 的 interaction 能力) +-- 说明: +-- 1) 计数冗余字段已在 user_works.like_count / favorite_count 中存在,本迁移只补齐明细记录表 +-- 2) 明细表增加 tenant_id,便于多租户隔离与统计 + +CREATE TABLE IF NOT EXISTS `user_work_likes` ( + `id` BIGINT NOT NULL AUTO_INCREMENT COMMENT '主键', + `tenant_id` BIGINT NOT NULL COMMENT '租户 ID', + `user_id` BIGINT NOT NULL COMMENT '点赞用户 ID(t_sys_user.id)', + `work_id` BIGINT NOT NULL COMMENT '作品 ID(user_works.id)', + `create_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', + PRIMARY KEY (`id`), + UNIQUE KEY `uk_tenant_user_work` (`tenant_id`, `user_id`, `work_id`), + KEY `idx_tenant_work` (`tenant_id`, `work_id`), + KEY `idx_tenant_user` (`tenant_id`, `user_id`), + KEY `idx_create_time` (`create_time`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='UGC 作品点赞明细'; + +CREATE TABLE IF NOT EXISTS `user_work_favorites` ( + `id` BIGINT NOT NULL AUTO_INCREMENT COMMENT '主键', + `tenant_id` BIGINT NOT NULL COMMENT '租户 ID', + `user_id` BIGINT NOT NULL COMMENT '收藏用户 ID(t_sys_user.id)', + `work_id` BIGINT NOT NULL COMMENT '作品 ID(user_works.id)', + `create_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', + PRIMARY KEY (`id`), + UNIQUE KEY `uk_tenant_user_work` (`tenant_id`, `user_id`, `work_id`), + KEY `idx_tenant_work` (`tenant_id`, `work_id`), + KEY `idx_tenant_user` (`tenant_id`, `user_id`), + KEY `idx_create_time` (`create_time`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='UGC 作品收藏明细'; + diff --git a/java-backend/src/main/resources/db/migration/V31__init_portal_roles_permissions_menus.sql b/java-backend/src/main/resources/db/migration/V31__init_portal_roles_permissions_menus.sql new file mode 100644 index 0000000..518904e --- /dev/null +++ b/java-backend/src/main/resources/db/migration/V31__init_portal_roles_permissions_menus.sql @@ -0,0 +1,209 @@ +-- ============================================ +-- 初始化多端角色(学校管理端/教师端/学生端/评委端)& 补齐菜单/路由使用的权限码(幂等) +-- 说明: +-- 1) 前端菜单转换为路由时,会把 menu.permission 写入 route.meta.permissions +-- 因此必须保证这些 permission code 在 t_auth_permission 中存在,并绑定到角色 +-- 2) 本脚本尽量保持幂等:INSERT ... ON DUPLICATE KEY UPDATE +-- ============================================ + +-- 1. 补齐前端路由/菜单用到但历史脚本未覆盖的权限码(tenant_id=1) +INSERT INTO `t_auth_permission` +(`tenant_id`, `name`, `code`, `resource`, `action`, `description`, `valid_state`) +VALUES +-- 菜单层(用于菜单/路由 meta 权限校验) +(1, '报名管理', 'contest:registration', 'contest', 'registration', '报名管理菜单权限', 1), +(1, '作品管理', 'contest:work', 'contest', 'work', '作品管理菜单权限', 1), +(1, '评审管理', 'contest:review', 'contest', 'review', '评审管理菜单权限', 1), +(1, '评委管理', 'contest:judge', 'contest', 'judge', '评委管理菜单权限', 1), +(1, '评审结果', 'contest:result', 'contest', 'result', '评审结果菜单权限', 1), +(1, '评审规则', 'contest:rule', 'contest', 'rule', '评审规则菜单权限', 1), +(1, '公告管理', 'contest:notice', 'contest', 'notice', '公告管理菜单权限', 1), + +-- 路由层(baseRoutes 中使用) +(1, '活动访问', 'activity:read', 'activity', 'read', '访问活动相关页面权限', 1), +(1, '报名查询', 'registration:read', 'registration', 'read', '查询报名相关数据权限', 1), +(1, '作品查询', 'work:read', 'work', 'read', '查询作品相关数据权限', 1), +(1, '作业查询', 'homework:read', 'homework', 'read', '查询作业相关数据权限', 1), +(1, '评审评分', 'review:score', 'review', 'score', '评审评分页面权限(兼容前端路由)', 1) +ON DUPLICATE KEY UPDATE + `name` = VALUES(`name`), + `resource` = VALUES(`resource`), + `action` = VALUES(`action`), + `description` = VALUES(`description`), + `valid_state` = 1, + `deleted` = 0; + +-- 2. 初始化四端角色(tenant_id=1,幂等) +INSERT INTO `t_auth_role` (`tenant_id`, `name`, `code`, `description`, `valid_state`) +VALUES +(1, '学校管理员', 'school_admin', '学校管理端角色', 1), +(1, '教师', 'teacher', '教师端角色', 1), +(1, '学生', 'student', '学生端角色', 1), +(1, '评委', 'judge', '评委端角色', 1) +ON DUPLICATE KEY UPDATE + `name` = VALUES(`name`), + `description` = VALUES(`description`), + `valid_state` = 1, + `deleted` = 0; + +-- 3. 增量补齐多端菜单(tenant_id=1) +-- 新增一级菜单:活动中心(id=30) +INSERT INTO t_auth_menu (id, name, path, icon, component, parent_id, permission, sort, valid_state) +VALUES (30, '活动中心', 'activities', 'Calendar', NULL, 0, NULL, 5, 1) +ON DUPLICATE KEY UPDATE `name` = VALUES(`name`); + +-- 活动中心子菜单 +INSERT INTO t_auth_menu (name, path, icon, component, parent_id, permission, sort, valid_state) +VALUES +('我的指导', 'guidance', 'Bulb', 'activities/Guidance', 30, 'activity:read', 1, 1), +('评审任务', 'review', 'Audit', 'activities/Review', 30, 'contest:review:score', 2, 1), +('预设评语', 'preset-comments', 'Message', 'activities/PresetComments', 30, 'contest:preset-comment:create', 3, 1) +ON DUPLICATE KEY UPDATE `name` = VALUES(`name`); + +-- 新增一级菜单:作业中心(id=31) +INSERT INTO t_auth_menu (id, name, path, icon, component, parent_id, permission, sort, valid_state) +VALUES (31, '作业中心', 'homework', 'Book', 'homework/Index', 0, 'homework:read', 6, 1) +ON DUPLICATE KEY UPDATE `name` = VALUES(`name`); + +-- 作业中心子菜单 +INSERT INTO t_auth_menu (name, path, icon, component, parent_id, permission, sort, valid_state) +VALUES +('学生作业', 'students', 'Team', 'homework/StudentList', 31, 'homework:read', 1, 1), +('提交记录', 'submissions', 'FileText', 'homework/Submissions', 31, 'homework:read', 2, 1), +('评审规则', 'review-rules', 'Setting', 'homework/ReviewRules', 31, 'homework:review-rule:create', 3, 1) +ON DUPLICATE KEY UPDATE `name` = VALUES(`name`); + +-- 4. 角色 - 权限绑定(tenant_id=1,幂等) +-- 约定:按角色 code + 权限 code 做绑定,避免硬编码 id + +-- 4.1 学校管理员:学校/组织/人员全量 + 活动管理常用 + 作业查询 +INSERT INTO `t_auth_role_permission` (`role_id`, `permission_id`) +SELECT r.id, p.id +FROM `t_auth_role` r +JOIN `t_auth_permission` p ON p.tenant_id = r.tenant_id +WHERE r.tenant_id = 1 + AND r.code = 'school_admin' + AND p.deleted = 0 + AND p.valid_state = 1 + AND p.code IN ( + 'school:create','school:read','school:update','school:delete', + 'department:create','department:read','department:update','department:delete', + 'grade:create','grade:read','grade:update','grade:delete', + 'class:create','class:read','class:update','class:delete', + 'teacher:create','teacher:read','teacher:update','teacher:delete', + 'student:create','student:read','student:update','student:delete', + 'contest:read','contest:create','contest:update','contest:publish', + 'contest:registration','contest:work','contest:review','contest:judge','contest:result','contest:rule','contest:notice', + 'registration:read','work:read','homework:read','activity:read', + 'contest:notice:create','contest:notice:update','contest:notice:delete','contest:notice:publish', + 'contest:judge:create','contest:judge:update','contest:judge:delete', + 'contest:review-rule:create','contest:review-rule:update','contest:review-rule:delete', + 'contest:review:assign' + ) +ON DUPLICATE KEY UPDATE `role_id` = VALUES(`role_id`); + +-- 4.2 教师:活动访问 + 作业访问 + 指导作品查询 +INSERT INTO `t_auth_role_permission` (`role_id`, `permission_id`) +SELECT r.id, p.id +FROM `t_auth_role` r +JOIN `t_auth_permission` p ON p.tenant_id = r.tenant_id +WHERE r.tenant_id = 1 + AND r.code = 'teacher' + AND p.deleted = 0 + AND p.valid_state = 1 + AND p.code IN ( + 'activity:read','contest:read', + 'homework:read','homework:review','homework:review-rule:create','homework:review-rule:update','homework:review-rule:delete', + 'work:read' + ) +ON DUPLICATE KEY UPDATE `role_id` = VALUES(`role_id`); + +-- 4.3 学生:活动访问 + 报名/作品提交 + 作业访问 +INSERT INTO `t_auth_role_permission` (`role_id`, `permission_id`) +SELECT r.id, p.id +FROM `t_auth_role` r +JOIN `t_auth_permission` p ON p.tenant_id = r.tenant_id +WHERE r.tenant_id = 1 + AND r.code = 'student' + AND p.deleted = 0 + AND p.valid_state = 1 + AND p.code IN ( + 'activity:read','contest:read', + 'contest:register', + 'contest:work:submit','contest:work:update', + 'homework:read', + 'registration:read','work:read' + ) +ON DUPLICATE KEY UPDATE `role_id` = VALUES(`role_id`); + +-- 4.4 评委:评审评分 + 预设评语 + 活动访问 +INSERT INTO `t_auth_role_permission` (`role_id`, `permission_id`) +SELECT r.id, p.id +FROM `t_auth_role` r +JOIN `t_auth_permission` p ON p.tenant_id = r.tenant_id +WHERE r.tenant_id = 1 + AND r.code = 'judge' + AND p.deleted = 0 + AND p.valid_state = 1 + AND p.code IN ( + 'activity:read','contest:read', + 'contest:review:score', + 'contest:preset-comment:create','contest:preset-comment:delete', + 'review:score' + ) +ON DUPLICATE KEY UPDATE `role_id` = VALUES(`role_id`); + +-- 5. 角色 - 菜单可见性绑定(tenant_id=1,幂等) + +-- 5.1 学校管理员:工作台 + 活动管理 + 学校管理 + 活动中心 + 作业中心 +INSERT INTO `t_auth_role_menu` (`role_id`, `menu_id`) +SELECT r.id, m.id +FROM `t_auth_role` r +JOIN `t_auth_menu` m ON m.deleted = 0 +WHERE r.tenant_id = 1 + AND r.code = 'school_admin' + AND ( + m.id IN (1,2,12,30,31) + OR m.parent_id IN (1,2,12,30,31) + ) +ON DUPLICATE KEY UPDATE `role_id` = VALUES(`role_id`); + +-- 5.2 教师:工作台 + 活动中心 + 作业中心 +INSERT INTO `t_auth_role_menu` (`role_id`, `menu_id`) +SELECT r.id, m.id +FROM `t_auth_role` r +JOIN `t_auth_menu` m ON m.deleted = 0 +WHERE r.tenant_id = 1 + AND r.code = 'teacher' + AND ( + m.id IN (1,30,31) + OR m.parent_id IN (1,30,31) + ) +ON DUPLICATE KEY UPDATE `role_id` = VALUES(`role_id`); + +-- 5.3 学生:工作台 + 作业中心 +INSERT INTO `t_auth_role_menu` (`role_id`, `menu_id`) +SELECT r.id, m.id +FROM `t_auth_role` r +JOIN `t_auth_menu` m ON m.deleted = 0 +WHERE r.tenant_id = 1 + AND r.code = 'student' + AND ( + m.id IN (1,31) + OR m.parent_id IN (1,31) + ) +ON DUPLICATE KEY UPDATE `role_id` = VALUES(`role_id`); + +-- 5.4 评委:工作台 + 活动中心 +INSERT INTO `t_auth_role_menu` (`role_id`, `menu_id`) +SELECT r.id, m.id +FROM `t_auth_role` r +JOIN `t_auth_menu` m ON m.deleted = 0 +WHERE r.tenant_id = 1 + AND r.code = 'judge' + AND ( + m.id IN (1,30) + OR m.parent_id IN (1,30) + ) +ON DUPLICATE KEY UPDATE `role_id` = VALUES(`role_id`); + diff --git a/java-backend/src/main/resources/db/migration/V32__rbac_align_all_tenants.sql b/java-backend/src/main/resources/db/migration/V32__rbac_align_all_tenants.sql new file mode 100644 index 0000000..2e9ee4c --- /dev/null +++ b/java-backend/src/main/resources/db/migration/V32__rbac_align_all_tenants.sql @@ -0,0 +1,203 @@ +-- ========================================================= +-- 全租户 RBAC/菜单可见性 补齐与对齐(幂等) +-- 目标: +-- 1) 以 tenant_id=1 作为“权限/角色模板”,将权限码补齐到所有租户 +-- 2) 补齐关键角色(school_admin/teacher/student/judge/tenant_admin)到所有租户 +-- 3) 复制模板角色的 role_permission / role_menu 到所有租户(同 role.code) +-- 4) 为 tenant_admin 生成默认:role_menu(显式菜单可见性)与 role_permission(接口/按钮权限) +-- 说明: +-- - t_auth_menu 为全局菜单表(无 tenant_id),role_menu 仅记录 menu_id +-- - t_auth_permission 按租户隔离(tenant_id),必须保证所有租户都具备前后端用到的 code +-- - 本迁移只做“增量补齐”,不做删除;支持重复执行 +-- ========================================================= + +-- 1) 权限码补齐:将 tenant_id=1 的权限复制到所有租户(按 code 对齐) +INSERT INTO t_auth_permission +(`tenant_id`, `name`, `code`, `resource`, `action`, `description`, `valid_state`, `create_by`, `deleted`) +SELECT + t.id AS tenant_id, + p.name, + p.code, + p.resource, + p.action, + p.description, + 1 AS valid_state, + 'system' AS create_by, + 0 AS deleted +FROM ( + SELECT id + FROM t_sys_tenant + WHERE deleted = 0 AND valid_state = 1 +) t +JOIN t_auth_permission p + ON p.tenant_id = 1 + AND p.deleted = 0 + AND p.valid_state = 1 +ON DUPLICATE KEY UPDATE + `name` = VALUES(`name`), + `resource` = VALUES(`resource`), + `action` = VALUES(`action`), + `description` = VALUES(`description`), + `valid_state` = 1, + `deleted` = 0; + +-- 2) 关键角色补齐:复制 tenant_id=1 的 school_admin/teacher/student/judge 到所有租户 +INSERT INTO t_auth_role +(`tenant_id`, `name`, `code`, `description`, `valid_state`, `create_by`, `deleted`) +SELECT + t.id AS tenant_id, + r.name, + r.code, + r.description, + 1 AS valid_state, + 'system' AS create_by, + 0 AS deleted +FROM ( + SELECT id + FROM t_sys_tenant + WHERE deleted = 0 AND valid_state = 1 +) t +JOIN t_auth_role r + ON r.tenant_id = 1 + AND r.deleted = 0 + AND r.valid_state = 1 + AND r.code IN ('school_admin','teacher','student','judge') +ON DUPLICATE KEY UPDATE + `name` = VALUES(`name`), + `description` = VALUES(`description`), + `valid_state` = 1, + `deleted` = 0; + +-- 3) tenant_admin 角色补齐:每个租户至少一个 +INSERT INTO t_auth_role +(`tenant_id`, `name`, `code`, `description`, `valid_state`, `create_by`, `deleted`) +SELECT + t.id AS tenant_id, + '租户管理员' AS name, + 'tenant_admin' AS code, + '租户管理端角色' AS description, + 1 AS valid_state, + 'system' AS create_by, + 0 AS deleted +FROM ( + SELECT id + FROM t_sys_tenant + WHERE deleted = 0 AND valid_state = 1 +) t +ON DUPLICATE KEY UPDATE + `name` = VALUES(`name`), + `description` = VALUES(`description`), + `valid_state` = 1, + `deleted` = 0; + +-- 4) 复制模板角色-权限(role_permission):按 role.code + permission.code 对齐 +-- 4.1 school_admin/teacher/student/judge +INSERT INTO t_auth_role_permission (`role_id`, `permission_id`) +SELECT + rt.id AS role_id, + pt.id AS permission_id +FROM ( + SELECT id + FROM t_sys_tenant + WHERE deleted = 0 AND valid_state = 1 +) t +JOIN t_auth_role rt + ON rt.tenant_id = t.id + AND rt.deleted = 0 + AND rt.valid_state = 1 +JOIN t_auth_role r1 + ON r1.tenant_id = 1 + AND r1.code = rt.code + AND r1.deleted = 0 + AND r1.valid_state = 1 + AND r1.code IN ('school_admin','teacher','student','judge') +JOIN t_auth_role_permission rp1 + ON rp1.role_id = r1.id +JOIN t_auth_permission p1 + ON p1.id = rp1.permission_id + AND p1.deleted = 0 + AND p1.valid_state = 1 +JOIN t_auth_permission pt + ON pt.tenant_id = t.id + AND pt.code = p1.code + AND pt.deleted = 0 + AND pt.valid_state = 1 +ON DUPLICATE KEY UPDATE `role_id` = VALUES(`role_id`); + +-- 4.2 tenant_admin:默认授予“除 super_admin / tenant:* 外”的全部权限(租户内管理+业务入口) +INSERT INTO t_auth_role_permission (`role_id`, `permission_id`) +SELECT + rta.id AS role_id, + p.id AS permission_id +FROM ( + SELECT id + FROM t_sys_tenant + WHERE deleted = 0 AND valid_state = 1 +) t +JOIN t_auth_role rta + ON rta.tenant_id = t.id + AND rta.code = 'tenant_admin' + AND rta.deleted = 0 + AND rta.valid_state = 1 +JOIN t_auth_permission p + ON p.tenant_id = t.id + AND p.deleted = 0 + AND p.valid_state = 1 + AND p.code <> 'super_admin' + AND p.code NOT LIKE 'tenant:%' +ON DUPLICATE KEY UPDATE `role_id` = VALUES(`role_id`); + +-- 5) 复制模板角色-菜单(role_menu):按 role.code 复制 tenant_id=1 的授权 +INSERT INTO t_auth_role_menu (`role_id`, `menu_id`) +SELECT + rt.id AS role_id, + rm1.menu_id +FROM ( + SELECT id + FROM t_sys_tenant + WHERE deleted = 0 AND valid_state = 1 +) t +JOIN t_auth_role rt + ON rt.tenant_id = t.id + AND rt.deleted = 0 + AND rt.valid_state = 1 +JOIN t_auth_role r1 + ON r1.tenant_id = 1 + AND r1.code = rt.code + AND r1.deleted = 0 + AND r1.valid_state = 1 + AND r1.code IN ('school_admin','teacher','student','judge') +JOIN t_auth_role_menu rm1 + ON rm1.role_id = r1.id +ON DUPLICATE KEY UPDATE `role_id` = VALUES(`role_id`); + +-- 6) tenant_admin 默认菜单可见性:全量管理端菜单(显式授权),排除平台级租户管理与 super_admin 专属菜单 +INSERT INTO t_auth_role_menu (`role_id`, `menu_id`) +SELECT + rta.id AS role_id, + m.id AS menu_id +FROM ( + SELECT id + FROM t_sys_tenant + WHERE deleted = 0 AND valid_state = 1 +) t +JOIN t_auth_role rta + ON rta.tenant_id = t.id + AND rta.code = 'tenant_admin' + AND rta.deleted = 0 + AND rta.valid_state = 1 +JOIN t_auth_menu m + ON m.deleted = 0 + AND m.valid_state = 1 +WHERE + -- 目录/公共菜单允许为空 + ( + m.permission IS NULL + OR m.permission = '' + OR ( + m.permission <> 'super_admin' + AND m.permission NOT LIKE 'tenant:%' + ) + ) +ON DUPLICATE KEY UPDATE `role_id` = VALUES(`role_id`); + diff --git a/java-backend/src/main/resources/db/migration/V33__rbac_permission_code_aliases_and_menu_fix.sql b/java-backend/src/main/resources/db/migration/V33__rbac_permission_code_aliases_and_menu_fix.sql new file mode 100644 index 0000000..d59ee51 --- /dev/null +++ b/java-backend/src/main/resources/db/migration/V33__rbac_permission_code_aliases_and_menu_fix.sql @@ -0,0 +1,139 @@ +-- ========================================================= +-- 权限码别名补齐 + 菜单 permission 修正(全租户,幂等) +-- 场景: +-- - 历史存在 contest:judge:* / contest:notice:* / contest:review:score 等权限码 +-- - 前端与部分登录返回权限码使用 judge:* / notice:* / review:score +-- 目标: +-- 1) 为所有租户补齐 judge:* / notice:* / review:score 等权限码 +-- 2) 若角色已绑定旧码,则自动补绑新码(保证按钮可见 + 路由校验通过) +-- 3) 修正菜单表中旧的 menu.permission(全局菜单表,无 tenant_id) +-- ========================================================= + +-- 0) 只处理有效租户 +-- (注意:t_auth_menu 为全局表,不受此过滤影响) + +-- 1) 补齐新权限码到所有租户(缺失则插入) +INSERT INTO t_auth_permission +(`tenant_id`, `name`, `code`, `resource`, `action`, `description`, `valid_state`, `create_by`, `deleted`) +SELECT + t.id AS tenant_id, + x.name, + x.code, + x.resource, + x.action, + x.description, + 1 AS valid_state, + 'system' AS create_by, + 0 AS deleted +FROM ( + SELECT id + FROM t_sys_tenant + WHERE deleted = 0 AND valid_state = 1 +) t +JOIN ( + SELECT '评委查询' AS name, 'judge:read' AS code, 'judge' AS resource, 'read' AS action, '查询评委权限' AS description + UNION ALL SELECT '评委创建','judge:create','judge','create','创建评委权限' + UNION ALL SELECT '评委更新','judge:update','judge','update','更新评委权限' + UNION ALL SELECT '评委删除','judge:delete','judge','delete','删除评委权限' + UNION ALL SELECT '公告查询','notice:read','notice','read','查询公告权限' + UNION ALL SELECT '公告创建','notice:create','notice','create','创建公告权限' + UNION ALL SELECT '公告更新','notice:update','notice','update','更新公告权限' + UNION ALL SELECT '公告删除','notice:delete','notice','delete','删除公告权限' + UNION ALL SELECT '公告发布','notice:publish','notice','publish','发布公告权限' + UNION ALL SELECT '评审评分','review:score','review','score','评审评分权限' +) x ON 1 = 1 +LEFT JOIN t_auth_permission p + ON p.tenant_id = t.id + AND p.code = x.code + AND p.deleted = 0 +WHERE p.id IS NULL +ON DUPLICATE KEY UPDATE + `name` = VALUES(`name`), + `resource` = VALUES(`resource`), + `action` = VALUES(`action`), + `description` = VALUES(`description`), + `valid_state` = 1, + `deleted` = 0; + +-- 2) 角色权限“旧码 -> 新码”自动补绑(保证前端按钮/路由用的新码能命中) +-- 2.1 contest:judge:* -> judge:* +INSERT INTO t_auth_role_permission (`role_id`, `permission_id`) +SELECT + rp.role_id, + p_new.id AS permission_id +FROM t_auth_role_permission rp +JOIN t_auth_permission p_old ON p_old.id = rp.permission_id AND p_old.deleted = 0 +JOIN t_auth_permission p_new + ON p_new.tenant_id = p_old.tenant_id + AND p_new.deleted = 0 + AND ( + (p_old.code = 'contest:judge:create' AND p_new.code = 'judge:create') + OR (p_old.code = 'contest:judge:update' AND p_new.code = 'judge:update') + OR (p_old.code = 'contest:judge:delete' AND p_new.code = 'judge:delete') + ) +ON DUPLICATE KEY UPDATE `role_id` = VALUES(`role_id`); + +-- 2.2 contest:notice:* -> notice:* +INSERT INTO t_auth_role_permission (`role_id`, `permission_id`) +SELECT + rp.role_id, + p_new.id AS permission_id +FROM t_auth_role_permission rp +JOIN t_auth_permission p_old ON p_old.id = rp.permission_id AND p_old.deleted = 0 +JOIN t_auth_permission p_new + ON p_new.tenant_id = p_old.tenant_id + AND p_new.deleted = 0 + AND ( + (p_old.code = 'contest:notice:create' AND p_new.code = 'notice:create') + OR (p_old.code = 'contest:notice:update' AND p_new.code = 'notice:update') + OR (p_old.code = 'contest:notice:delete' AND p_new.code = 'notice:delete') + OR (p_old.code = 'contest:notice:publish' AND p_new.code = 'notice:publish') + ) +ON DUPLICATE KEY UPDATE `role_id` = VALUES(`role_id`); + +-- 2.3 contest:review:score -> review:score +INSERT INTO t_auth_role_permission (`role_id`, `permission_id`) +SELECT + rp.role_id, + p_new.id AS permission_id +FROM t_auth_role_permission rp +JOIN t_auth_permission p_old ON p_old.id = rp.permission_id AND p_old.deleted = 0 +JOIN t_auth_permission p_new + ON p_new.tenant_id = p_old.tenant_id + AND p_new.deleted = 0 + AND p_old.code = 'contest:review:score' + AND p_new.code = 'review:score' +ON DUPLICATE KEY UPDATE `role_id` = VALUES(`role_id`); + +-- 2.4 tenant_admin 兜底补绑(若 tenant_admin 已存在,但早于新码插入) +INSERT INTO t_auth_role_permission (`role_id`, `permission_id`) +SELECT + r.id AS role_id, + p.id AS permission_id +FROM t_auth_role r +JOIN t_sys_tenant t ON t.id = r.tenant_id AND t.deleted = 0 AND t.valid_state = 1 +JOIN t_auth_permission p + ON p.tenant_id = r.tenant_id + AND p.deleted = 0 + AND p.valid_state = 1 + AND p.code IN ( + 'judge:read','judge:create','judge:update','judge:delete', + 'notice:read','notice:create','notice:update','notice:delete','notice:publish', + 'review:score' + ) +WHERE r.deleted = 0 AND r.valid_state = 1 AND r.code = 'tenant_admin' +ON DUPLICATE KEY UPDATE `role_id` = VALUES(`role_id`); + +-- 3) 修正菜单 permission(全局菜单表) +UPDATE t_auth_menu +SET permission = 'judge:read' +WHERE deleted = 0 AND permission = 'contest:judge'; + +UPDATE t_auth_menu +SET permission = 'notice:read' +WHERE deleted = 0 AND permission = 'contest:notice'; + +UPDATE t_auth_menu +SET permission = 'review:score' +WHERE deleted = 0 AND permission = 'contest:review:score'; + diff --git a/java-backend/src/main/resources/db/migration/V34__split_menu_scene_portal_tenant.sql b/java-backend/src/main/resources/db/migration/V34__split_menu_scene_portal_tenant.sql new file mode 100644 index 0000000..ca9c953 --- /dev/null +++ b/java-backend/src/main/resources/db/migration/V34__split_menu_scene_portal_tenant.sql @@ -0,0 +1,28 @@ +-- ========================================================= +-- 菜单场景拆分:平台端(portal) vs 租户端(tenant) +-- 目标: +-- 1) t_auth_menu 增加 scene 字段,避免平台端/租户端菜单混用导致授权错乱 +-- 2) 将 system 菜单树标记为 portal,其余默认 tenant +-- 3) 清理租户菜单关联表中误选的 portal 菜单(避免租户端看到平台端菜单) +-- 说明: +-- - t_auth_menu 为全局表(无 tenant_id) +-- - t_auth_tenant_menu 为租户开通菜单表 +-- ========================================================= + +-- 1) 增加 scene 字段(若已存在则跳过:MySQL 8 不支持 IF NOT EXISTS for ADD COLUMN,保持一次性迁移即可) +ALTER TABLE t_auth_menu + ADD COLUMN scene VARCHAR(16) NOT NULL DEFAULT 'tenant' COMMENT '菜单场景:portal-平台端,tenant-租户端'; + +-- 2) 回填:system 菜单树标记为 portal(id=19 为 system 根菜单) +UPDATE t_auth_menu +SET scene = 'portal' +WHERE deleted = 0 + AND (id = 19 OR parent_id = 19); + +-- 3) 清理:租户菜单关联表移除 portal 菜单(只清理已存在的错误数据) +DELETE tm +FROM t_auth_tenant_menu tm +JOIN t_auth_menu m ON m.id = tm.menu_id +WHERE m.deleted = 0 + AND m.scene = 'portal'; + diff --git a/java-backend/src/main/resources/db/migration/V35__grant_portal_super_admin_all_permissions_and_menus.sql b/java-backend/src/main/resources/db/migration/V35__grant_portal_super_admin_all_permissions_and_menus.sql new file mode 100644 index 0000000..6a8da62 --- /dev/null +++ b/java-backend/src/main/resources/db/migration/V35__grant_portal_super_admin_all_permissions_and_menus.sql @@ -0,0 +1,49 @@ +-- ========================================================= +-- 超管端(平台租户)super_admin 兜底授权(全量按钮/接口 + portal 菜单) +-- 目标: +-- 1) 对所有 is_super=1 的租户下 code='super_admin' 角色,补齐全部有效权限 +-- 2) 对上述角色,补齐全部 portal 菜单可见性(t_auth_menu.scene='portal') +-- 说明: +-- - 幂等:使用 ON DUPLICATE KEY UPDATE +-- - 若某些租户未初始化对应权限码,本脚本不会创建权限,只做“已有权限”的全量绑定 +-- ========================================================= + +-- 1) super_admin 绑定全部有效权限 +INSERT INTO t_auth_role_permission (`role_id`, `permission_id`) +SELECT + r.id AS role_id, + p.id AS permission_id +FROM t_auth_role r +JOIN t_sys_tenant t + ON t.id = r.tenant_id + AND t.deleted = 0 + AND t.valid_state = 1 + AND t.is_super = 1 +JOIN t_auth_permission p + ON p.tenant_id = r.tenant_id + AND p.deleted = 0 + AND p.valid_state = 1 +WHERE r.deleted = 0 + AND r.valid_state = 1 + AND r.code = 'super_admin' +ON DUPLICATE KEY UPDATE `role_id` = VALUES(`role_id`); + +-- 2) super_admin 绑定全部 portal 菜单可见性 +INSERT INTO t_auth_role_menu (`role_id`, `menu_id`) +SELECT + r.id AS role_id, + m.id AS menu_id +FROM t_auth_role r +JOIN t_sys_tenant t + ON t.id = r.tenant_id + AND t.deleted = 0 + AND t.valid_state = 1 + AND t.is_super = 1 +JOIN t_auth_menu m + ON m.deleted = 0 + AND m.scene = 'portal' +WHERE r.deleted = 0 + AND r.valid_state = 1 + AND r.code = 'super_admin' +ON DUPLICATE KEY UPDATE `role_id` = VALUES(`role_id`); + diff --git a/java-backend/src/main/resources/db/migration/V36__align_super_tenant_flag_by_type.sql b/java-backend/src/main/resources/db/migration/V36__align_super_tenant_flag_by_type.sql new file mode 100644 index 0000000..7d3854a --- /dev/null +++ b/java-backend/src/main/resources/db/migration/V36__align_super_tenant_flag_by_type.sql @@ -0,0 +1,13 @@ +-- ========================================================= +-- 对齐超级租户标识:tenant_type='platform' 兜底设置 is_super=1 +-- 背景:历史环境可能出现平台租户编码不为 'platform'(如 'super'), +-- 但 tenant_type 仍为 platform,导致 is_super 未正确标记。 +-- ========================================================= + +UPDATE t_sys_tenant +SET is_super = 1 +WHERE deleted = 0 + AND valid_state = 1 + AND tenant_type = 'platform' + AND (is_super IS NULL OR is_super = 0); + diff --git a/java-backend/src/main/resources/db/migration/V37__align_super_tenant_super_code.sql b/java-backend/src/main/resources/db/migration/V37__align_super_tenant_super_code.sql new file mode 100644 index 0000000..cdc3856 --- /dev/null +++ b/java-backend/src/main/resources/db/migration/V37__align_super_tenant_super_code.sql @@ -0,0 +1,14 @@ +-- ========================================================= +-- 对齐超管租户(文档约定 code=super) +-- - 确保 code='super' 的租户 is_super=1 +-- - tenant_type 兜底设置为 platform(便于统一判断) +-- ========================================================= + +UPDATE t_sys_tenant +SET + is_super = 1, + tenant_type = 'platform' +WHERE deleted = 0 + AND valid_state = 1 + AND code = 'super'; + diff --git a/java-frontend/package.json b/java-frontend/package.json index 6bb3db2..9c12632 100644 --- a/java-frontend/package.json +++ b/java-frontend/package.json @@ -20,10 +20,12 @@ "ant-design-vue": "^4.1.1", "axios": "^1.6.7", "dayjs": "^1.11.10", + "echarts": "^5.6.0", "pinia": "^2.1.7", "three": "^0.182.0", "vee-validate": "^4.12.4", "vue": "^3.4.21", + "vue-echarts": "^6.7.3", "vue-router": "^4.3.0", "zod": "^3.22.4" }, diff --git a/java-frontend/scripts/extract-perms.cjs b/java-frontend/scripts/extract-perms.cjs new file mode 100644 index 0000000..d74e455 --- /dev/null +++ b/java-frontend/scripts/extract-perms.cjs @@ -0,0 +1,60 @@ +const fs = require("fs"); +const path = require("path"); + +const root = path.resolve(__dirname, "..", "src"); +const exts = new Set([".ts", ".vue"]); + +const codes = { + v_permission: new Set(), + route_meta_permissions: new Set(), + logic_checks: new Set(), +}; + +const reVPermission = /v-permission(?:\.[a-zA-Z]+)?\s*=\s*(?:"([^"]+)"|'([^']+)')/g; +const rePermissionCode = /['"`]([a-zA-Z0-9_-]+:[a-zA-Z0-9_:-]+|super_admin)['"`]/g; +const reMetaPermissions = /permissions\s*:\s*\[([^\]]+)\]/g; +const reHasPermission = /has(?:Any)?Permission\(\s*['"`]([a-zA-Z0-9_-]+:[a-zA-Z0-9_:-]+|super_admin)['"`]/g; + +function walk(dir) { + for (const ent of fs.readdirSync(dir, { withFileTypes: true })) { + const p = path.join(dir, ent.name); + if (ent.isDirectory()) { + walk(p); + continue; + } + if (!exts.has(path.extname(ent.name))) continue; + + const s = fs.readFileSync(p, "utf8"); + + for (const m of s.matchAll(reVPermission)) { + const val = m[1] || m[2] || ""; + for (const c of val.matchAll(rePermissionCode)) { + codes.v_permission.add(c[1]); + } + } + + for (const m of s.matchAll(reMetaPermissions)) { + const inner = m[1] || ""; + for (const c of inner.matchAll(rePermissionCode)) { + codes.route_meta_permissions.add(c[1]); + } + } + + for (const m of s.matchAll(reHasPermission)) { + codes.logic_checks.add(m[1]); + } + } +} + +walk(root); + +const sort = (set) => Array.from(set).sort(); + +const result = { + v_permission: sort(codes.v_permission), + route_meta_permissions: sort(codes.route_meta_permissions), + logic_checks: sort(codes.logic_checks), +}; + +console.log(JSON.stringify(result, null, 2)); + diff --git a/java-frontend/src/api/ai-3d.ts b/java-frontend/src/api/ai-3d.ts deleted file mode 100644 index 2447d0c..0000000 --- a/java-frontend/src/api/ai-3d.ts +++ /dev/null @@ -1,127 +0,0 @@ -import request from "@/utils/request"; -import type { PaginationParams } from "@/types/api"; - -// ==================== AI 3D 任务相关类型 ==================== - -/** - * AI 3D 任务状态 - */ -export type AI3DTaskStatus = - | "pending" - | "processing" - | "completed" - | "failed" - | "timeout"; - -/** - * AI 3D 任务输入类型 - */ -export type AI3DInputType = "text" | "image"; - -/** - * AI 3D 任务 - */ -export interface AI3DTask { - id: number; - tenantId: number; - userId: number; - inputType: AI3DInputType; - inputContent: string; - status: AI3DTaskStatus; - resultUrl?: string; - previewUrl?: string; - // 多结果支持(文生3D会生成4个不同角度的模型) - resultUrls?: string[]; - previewUrls?: string[]; - errorMessage?: string; - externalTaskId?: string; - retryCount: number; - createTime: string; - completeTime?: string; - // 队列位置(仅 pending 状态时返回) - queuePosition?: number; -} - -/** - * 模型生成类型 - */ -export type AI3DGenerateType = "Normal" | "LowPoly" | "Geometry" | "Sketch"; - -/** - * 创建任务参数 - */ -export interface CreateAI3DTaskParams { - inputType: AI3DInputType; - inputContent: string; - /** 模型生成类型:Normal-带纹理, LowPoly-低多边形, Geometry-白模, Sketch-草图 */ - generateType?: AI3DGenerateType; - /** 模型面数:10000-1500000,默认500000 */ - faceCount?: number; -} - -/** - * 查询任务参数 - */ -export interface QueryAI3DTaskParams extends PaginationParams { - status?: AI3DTaskStatus; -} - -/** - * 任务列表响应 - */ -export interface AI3DTaskListResponse { - list: AI3DTask[]; - total: number; - page: number; - pageSize: number; -} - -// ==================== API 接口 ==================== - -/** - * 创建生成任务 - * POST /api/ai-3d/generate - */ -export function createAI3DTask(data: CreateAI3DTaskParams) { - return request.post("/api/ai-3d/generate", data); -} - -/** - * 获取任务列表 - * GET /api/ai-3d/tasks - */ -export function getAI3DTasks(params?: QueryAI3DTaskParams) { - return request.get("/api/ai-3d/page", { params }); -} - -/** - * 获取任务详情 - * GET /api/ai-3d/:id - */ -export function getAI3DTask(id: number) { - return request.get(`/api/ai-3d/${id}`); -} - -/** - * 重试任务 - * POST /api/ai-3d/:id/retry - */ -export function retryAI3DTask(id: number) { - return request.put(`/api/ai-3d/${id}/retry`); -} - -/** - * 删除任务 - * DELETE /api/ai-3d/:id - */ -export function deleteAI3DTask(id: number) { - return request.delete(`/api/ai-3d/${id}`); -} - -/** - * 取消任务 - * PUT /api/ai-3d/:id/cancel - */ -export function cancelAI3DTask(id: number) { - return request.put(`/api/ai-3d/${id}/cancel`); -} diff --git a/java-frontend/src/api/analytics.ts b/java-frontend/src/api/analytics.ts new file mode 100644 index 0000000..1412609 --- /dev/null +++ b/java-frontend/src/api/analytics.ts @@ -0,0 +1,63 @@ +import request from '@/utils/request' + +export interface OverviewData { + summary: { + totalContests: number + totalRegistrations: number + passedRegistrations: number + totalWorks: number + reviewedWorks: number + awardedWorks: number + } + funnel: { + registered: number + passed: number + submitted: number + reviewed: number + awarded: number + } + monthlyTrend: Array<{ month: string; registrations: number; works: number }> + contestComparison: Array<{ + contestId: number + contestName: string + registrations: number + passRate: number + submitRate: number + reviewRate: number + awardRate: number + avgScore: number | null + }> +} + +export interface ReviewData { + efficiency: { + avgReviewDays: number + dailyReviewCount: number + pendingAssignments: number + avgScoreStddev: number + } + judgeWorkload: Array<{ + judgeId: number + judgeName: string + contestCount: number + assignedCount: number + scoredCount: number + completionRate: number + avgScore: number | null + scoreStddev: number + }> + awardDistribution: Array<{ + awardName: string + count: number + percentage: number + }> +} + +export const analyticsApi = { + getOverview: (params?: { timeRange?: string; contestId?: number }): Promise => + request.get('/analytics/overview', { params }), + + getReview: (params?: { contestId?: number }): Promise => + request.get('/analytics/review', { params }), +} + diff --git a/java-frontend/src/api/menus.ts b/java-frontend/src/api/menus.ts index eaa6595..e3e58e8 100644 --- a/java-frontend/src/api/menus.ts +++ b/java-frontend/src/api/menus.ts @@ -25,6 +25,7 @@ export interface CreateMenuForm { component?: string; parentId?: number; permission?: string; + scene?: string; sort?: number; } @@ -35,12 +36,17 @@ export interface UpdateMenuForm { component?: string; parentId?: number; permission?: string; + scene?: string; sort?: number; } +export type MenuScene = "portal" | "tenant"; + // 获取菜单列表(树形结构) -export async function getMenusList(): Promise { - const response = await request.get("/api/menus"); +export async function getMenusList(scene?: MenuScene): Promise { + const response = await request.get("/api/menus", { + params: scene ? { scene } : undefined, + }); return response; } diff --git a/java-frontend/src/api/public.ts b/java-frontend/src/api/public.ts index a555486..5c93681 100644 --- a/java-frontend/src/api/public.ts +++ b/java-frontend/src/api/public.ts @@ -2,7 +2,7 @@ import axios from "axios"; // 公众端专用 axios 实例 const publicApi = axios.create({ - baseURL: import.meta.env.VITE_API_BASE_URL || "/api", + baseURL: import.meta.env.VITE_API_BASE_URL || "", timeout: 15000, }); @@ -260,6 +260,18 @@ export const publicMineApi = { publicApi.get("/api/public/mine/works", { params }), }; +// ==================== 点赞 & 收藏 ==================== + +export const publicInteractionApi = { + like: (workId: number) => publicApi.post(`/api/public/works/${workId}/like`), + favorite: (workId: number) => + publicApi.post(`/api/public/works/${workId}/favorite`), + getInteraction: (workId: number) => + publicApi.get(`/api/public/works/${workId}/interaction`), + myFavorites: (params?: { page?: number; pageSize?: number }) => + publicApi.get("/api/public/mine/favorites", { params }), +}; + // ==================== 用户作品库 ==================== export interface UserWork { diff --git a/java-frontend/src/components/ModelViewer.vue b/java-frontend/src/components/ModelViewer.vue deleted file mode 100644 index e5bbaec..0000000 --- a/java-frontend/src/components/ModelViewer.vue +++ /dev/null @@ -1,45 +0,0 @@ - - - diff --git a/java-frontend/src/layouts/BasicLayout.vue b/java-frontend/src/layouts/BasicLayout.vue index d849147..f528a3c 100644 --- a/java-frontend/src/layouts/BasicLayout.vue +++ b/java-frontend/src/layouts/BasicLayout.vue @@ -1,11 +1,6 @@ @@ -69,20 +88,27 @@ diff --git a/java-frontend/src/views/system/tenants/Index.vue b/java-frontend/src/views/system/tenants/Index.vue index a8ef765..e1feb02 100644 --- a/java-frontend/src/views/system/tenants/Index.vue +++ b/java-frontend/src/views/system/tenants/Index.vue @@ -171,9 +171,6 @@ import { } from '@/api/tenants' import { menusApi, type Menu } from '@/api/menus' import { useListRequest } from '@/composables/useListRequest' -import { useAuthStore } from '@/stores/auth' - -const authStore = useAuthStore() const submitLoading = ref(false) const detailLoading = ref(false) @@ -291,7 +288,7 @@ const handleResetSearch = () => { const fetchAllMenus = async () => { menusLoading.value = true try { - allMenus.value = await menusApi.getList() + allMenus.value = await menusApi.getList('tenant') } catch { message.error('获取菜单列表失败') } finally { diff --git a/java-frontend/src/views/workbench/ai-3d/Generate.vue b/java-frontend/src/views/workbench/ai-3d/Generate.vue deleted file mode 100644 index 3535a6b..0000000 --- a/java-frontend/src/views/workbench/ai-3d/Generate.vue +++ /dev/null @@ -1,1081 +0,0 @@ - - - - - diff --git a/java-frontend/src/views/workbench/ai-3d/History.vue b/java-frontend/src/views/workbench/ai-3d/History.vue deleted file mode 100644 index d10c30a..0000000 --- a/java-frontend/src/views/workbench/ai-3d/History.vue +++ /dev/null @@ -1,961 +0,0 @@ - - - - - diff --git a/java-frontend/src/views/workbench/ai-3d/Index.vue b/java-frontend/src/views/workbench/ai-3d/Index.vue deleted file mode 100644 index bdf2aa2..0000000 --- a/java-frontend/src/views/workbench/ai-3d/Index.vue +++ /dev/null @@ -1,2822 +0,0 @@ - - - - - - - - diff --git a/java-frontend/tsconfig.node.tsbuildinfo b/java-frontend/tsconfig.node.tsbuildinfo index 99239ea..b2e4b83 100644 --- a/java-frontend/tsconfig.node.tsbuildinfo +++ b/java-frontend/tsconfig.node.tsbuildinfo @@ -1 +1 @@ -{"fileNames":["../node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/lib/lib.d.ts","../node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/lib/lib.es5.d.ts","../node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/lib/lib.es2015.d.ts","../node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/lib/lib.es2016.d.ts","../node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/lib/lib.es2017.d.ts","../node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/lib/lib.es2018.d.ts","../node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/lib/lib.es2019.d.ts","../node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/lib/lib.es2020.d.ts","../node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/lib/lib.dom.d.ts","../node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/lib/lib.webworker.importscripts.d.ts","../node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/lib/lib.scripthost.d.ts","../node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/lib/lib.es2015.core.d.ts","../node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/lib/lib.es2015.collection.d.ts","../node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/lib/lib.es2015.generator.d.ts","../node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/lib/lib.es2015.iterable.d.ts","../node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/lib/lib.es2015.promise.d.ts","../node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/lib/lib.es2015.proxy.d.ts","../node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/lib/lib.es2015.reflect.d.ts","../node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/lib/lib.es2015.symbol.d.ts","../node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/lib/lib.es2015.symbol.wellknown.d.ts","../node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/lib/lib.es2016.array.include.d.ts","../node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/lib/lib.es2016.intl.d.ts","../node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/lib/lib.es2017.arraybuffer.d.ts","../node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/lib/lib.es2017.date.d.ts","../node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/lib/lib.es2017.object.d.ts","../node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/lib/lib.es2017.sharedmemory.d.ts","../node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/lib/lib.es2017.string.d.ts","../node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/lib/lib.es2017.intl.d.ts","../node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/lib/lib.es2017.typedarrays.d.ts","../node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/lib/lib.es2018.asyncgenerator.d.ts","../node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/lib/lib.es2018.asynciterable.d.ts","../node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/lib/lib.es2018.intl.d.ts","../node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/lib/lib.es2018.promise.d.ts","../node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/lib/lib.es2018.regexp.d.ts","../node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/lib/lib.es2019.array.d.ts","../node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/lib/lib.es2019.object.d.ts","../node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/lib/lib.es2019.string.d.ts","../node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/lib/lib.es2019.symbol.d.ts","../node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/lib/lib.es2019.intl.d.ts","../node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/lib/lib.es2020.bigint.d.ts","../node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/lib/lib.es2020.date.d.ts","../node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/lib/lib.es2020.promise.d.ts","../node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/lib/lib.es2020.sharedmemory.d.ts","../node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/lib/lib.es2020.string.d.ts","../node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/lib/lib.es2020.symbol.wellknown.d.ts","../node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/lib/lib.es2020.intl.d.ts","../node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/lib/lib.es2020.number.d.ts","../node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/lib/lib.esnext.disposable.d.ts","../node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/lib/lib.esnext.float16.d.ts","../node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/lib/lib.decorators.d.ts","../node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/lib/lib.decorators.legacy.d.ts","./node_modules/@types/node/compatibility/iterators.d.ts","./node_modules/@types/node/globals.typedarray.d.ts","./node_modules/@types/node/buffer.buffer.d.ts","./node_modules/@types/node/globals.d.ts","./node_modules/@types/node/web-globals/abortcontroller.d.ts","./node_modules/@types/node/web-globals/blob.d.ts","./node_modules/@types/node/web-globals/console.d.ts","./node_modules/@types/node/web-globals/crypto.d.ts","./node_modules/@types/node/web-globals/domexception.d.ts","./node_modules/@types/node/web-globals/encoding.d.ts","./node_modules/@types/node/web-globals/events.d.ts","../node_modules/.pnpm/buffer@5.7.1/node_modules/buffer/index.d.ts","./node_modules/undici-types/utility.d.ts","./node_modules/undici-types/header.d.ts","./node_modules/undici-types/readable.d.ts","./node_modules/undici-types/fetch.d.ts","./node_modules/undici-types/formdata.d.ts","./node_modules/undici-types/connector.d.ts","./node_modules/undici-types/client-stats.d.ts","./node_modules/undici-types/client.d.ts","./node_modules/undici-types/errors.d.ts","./node_modules/undici-types/dispatcher.d.ts","./node_modules/undici-types/global-dispatcher.d.ts","./node_modules/undici-types/global-origin.d.ts","./node_modules/undici-types/pool-stats.d.ts","./node_modules/undici-types/pool.d.ts","./node_modules/undici-types/handlers.d.ts","./node_modules/undici-types/balanced-pool.d.ts","./node_modules/undici-types/h2c-client.d.ts","./node_modules/undici-types/agent.d.ts","./node_modules/undici-types/mock-interceptor.d.ts","./node_modules/undici-types/mock-call-history.d.ts","./node_modules/undici-types/mock-agent.d.ts","./node_modules/undici-types/mock-client.d.ts","./node_modules/undici-types/mock-pool.d.ts","./node_modules/undici-types/snapshot-agent.d.ts","./node_modules/undici-types/mock-errors.d.ts","./node_modules/undici-types/proxy-agent.d.ts","./node_modules/undici-types/env-http-proxy-agent.d.ts","./node_modules/undici-types/retry-handler.d.ts","./node_modules/undici-types/retry-agent.d.ts","./node_modules/undici-types/api.d.ts","./node_modules/undici-types/cache-interceptor.d.ts","./node_modules/undici-types/interceptors.d.ts","./node_modules/undici-types/util.d.ts","./node_modules/undici-types/cookies.d.ts","./node_modules/undici-types/patch.d.ts","./node_modules/undici-types/websocket.d.ts","./node_modules/undici-types/eventsource.d.ts","./node_modules/undici-types/diagnostics-channel.d.ts","./node_modules/undici-types/content-type.d.ts","./node_modules/undici-types/cache.d.ts","./node_modules/undici-types/index.d.ts","./node_modules/@types/node/web-globals/fetch.d.ts","./node_modules/@types/node/web-globals/importmeta.d.ts","./node_modules/@types/node/web-globals/messaging.d.ts","./node_modules/@types/node/web-globals/navigator.d.ts","./node_modules/@types/node/web-globals/performance.d.ts","./node_modules/@types/node/web-globals/storage.d.ts","./node_modules/@types/node/web-globals/streams.d.ts","./node_modules/@types/node/web-globals/timers.d.ts","./node_modules/@types/node/web-globals/url.d.ts","./node_modules/@types/node/assert.d.ts","./node_modules/@types/node/assert/strict.d.ts","./node_modules/@types/node/async_hooks.d.ts","./node_modules/@types/node/buffer.d.ts","./node_modules/@types/node/child_process.d.ts","./node_modules/@types/node/cluster.d.ts","./node_modules/@types/node/console.d.ts","./node_modules/@types/node/constants.d.ts","./node_modules/@types/node/crypto.d.ts","./node_modules/@types/node/dgram.d.ts","./node_modules/@types/node/diagnostics_channel.d.ts","./node_modules/@types/node/dns.d.ts","./node_modules/@types/node/dns/promises.d.ts","./node_modules/@types/node/domain.d.ts","./node_modules/@types/node/events.d.ts","./node_modules/@types/node/fs.d.ts","./node_modules/@types/node/fs/promises.d.ts","./node_modules/@types/node/http.d.ts","./node_modules/@types/node/http2.d.ts","./node_modules/@types/node/https.d.ts","./node_modules/@types/node/inspector.d.ts","./node_modules/@types/node/inspector.generated.d.ts","./node_modules/@types/node/inspector/promises.d.ts","./node_modules/@types/node/module.d.ts","./node_modules/@types/node/net.d.ts","./node_modules/@types/node/os.d.ts","./node_modules/@types/node/path.d.ts","./node_modules/@types/node/path/posix.d.ts","./node_modules/@types/node/path/win32.d.ts","./node_modules/@types/node/perf_hooks.d.ts","./node_modules/@types/node/process.d.ts","./node_modules/@types/node/punycode.d.ts","./node_modules/@types/node/querystring.d.ts","./node_modules/@types/node/quic.d.ts","./node_modules/@types/node/readline.d.ts","./node_modules/@types/node/readline/promises.d.ts","./node_modules/@types/node/repl.d.ts","./node_modules/@types/node/sea.d.ts","./node_modules/@types/node/sqlite.d.ts","./node_modules/@types/node/stream.d.ts","./node_modules/@types/node/stream/consumers.d.ts","./node_modules/@types/node/stream/promises.d.ts","./node_modules/@types/node/stream/web.d.ts","./node_modules/@types/node/string_decoder.d.ts","./node_modules/@types/node/test.d.ts","./node_modules/@types/node/test/reporters.d.ts","./node_modules/@types/node/timers.d.ts","./node_modules/@types/node/timers/promises.d.ts","./node_modules/@types/node/tls.d.ts","./node_modules/@types/node/trace_events.d.ts","./node_modules/@types/node/tty.d.ts","./node_modules/@types/node/url.d.ts","./node_modules/@types/node/util.d.ts","./node_modules/@types/node/util/types.d.ts","./node_modules/@types/node/v8.d.ts","./node_modules/@types/node/vm.d.ts","./node_modules/@types/node/wasi.d.ts","./node_modules/@types/node/worker_threads.d.ts","./node_modules/@types/node/zlib.d.ts","./node_modules/@types/node/index.d.ts","../node_modules/.pnpm/@types+estree@1.0.8/node_modules/@types/estree/index.d.ts","../node_modules/.pnpm/rollup@4.53.3/node_modules/rollup/dist/rollup.d.ts","../node_modules/.pnpm/rollup@4.53.3/node_modules/rollup/dist/parseast.d.ts","../node_modules/.pnpm/vite@5.4.21_@types+node@20.19.25_sass@1.94.1_terser@5.44.1/node_modules/vite/types/hmrpayload.d.ts","../node_modules/.pnpm/vite@5.4.21_@types+node@20.19.25_sass@1.94.1_terser@5.44.1/node_modules/vite/types/customevent.d.ts","../node_modules/.pnpm/vite@5.4.21_@types+node@20.19.25_sass@1.94.1_terser@5.44.1/node_modules/vite/types/hot.d.ts","../node_modules/.pnpm/vite@5.4.21_@types+node@20.19.25_sass@1.94.1_terser@5.44.1/node_modules/vite/dist/node/types.d-agj9qkwt.d.ts","../node_modules/.pnpm/esbuild@0.21.5/node_modules/esbuild/lib/main.d.ts","../node_modules/.pnpm/source-map-js@1.2.1/node_modules/source-map-js/source-map.d.ts","../node_modules/.pnpm/postcss@8.5.6/node_modules/postcss/lib/previous-map.d.ts","../node_modules/.pnpm/postcss@8.5.6/node_modules/postcss/lib/input.d.ts","../node_modules/.pnpm/postcss@8.5.6/node_modules/postcss/lib/css-syntax-error.d.ts","../node_modules/.pnpm/postcss@8.5.6/node_modules/postcss/lib/declaration.d.ts","../node_modules/.pnpm/postcss@8.5.6/node_modules/postcss/lib/root.d.ts","../node_modules/.pnpm/postcss@8.5.6/node_modules/postcss/lib/warning.d.ts","../node_modules/.pnpm/postcss@8.5.6/node_modules/postcss/lib/lazy-result.d.ts","../node_modules/.pnpm/postcss@8.5.6/node_modules/postcss/lib/no-work-result.d.ts","../node_modules/.pnpm/postcss@8.5.6/node_modules/postcss/lib/processor.d.ts","../node_modules/.pnpm/postcss@8.5.6/node_modules/postcss/lib/result.d.ts","../node_modules/.pnpm/postcss@8.5.6/node_modules/postcss/lib/document.d.ts","../node_modules/.pnpm/postcss@8.5.6/node_modules/postcss/lib/rule.d.ts","../node_modules/.pnpm/postcss@8.5.6/node_modules/postcss/lib/node.d.ts","../node_modules/.pnpm/postcss@8.5.6/node_modules/postcss/lib/comment.d.ts","../node_modules/.pnpm/postcss@8.5.6/node_modules/postcss/lib/container.d.ts","../node_modules/.pnpm/postcss@8.5.6/node_modules/postcss/lib/at-rule.d.ts","../node_modules/.pnpm/postcss@8.5.6/node_modules/postcss/lib/list.d.ts","../node_modules/.pnpm/postcss@8.5.6/node_modules/postcss/lib/postcss.d.ts","../node_modules/.pnpm/postcss@8.5.6/node_modules/postcss/lib/postcss.d.mts","../node_modules/.pnpm/vite@5.4.21_@types+node@20.19.25_sass@1.94.1_terser@5.44.1/node_modules/vite/dist/node/runtime.d.ts","../node_modules/.pnpm/vite@5.4.21_@types+node@20.19.25_sass@1.94.1_terser@5.44.1/node_modules/vite/types/importglob.d.ts","../node_modules/.pnpm/vite@5.4.21_@types+node@20.19.25_sass@1.94.1_terser@5.44.1/node_modules/vite/types/metadata.d.ts","../node_modules/.pnpm/vite@5.4.21_@types+node@20.19.25_sass@1.94.1_terser@5.44.1/node_modules/vite/dist/node/index.d.ts","../node_modules/.pnpm/@babel+types@7.28.5/node_modules/@babel/types/lib/index.d.ts","../node_modules/.pnpm/@vue+shared@3.5.24/node_modules/@vue/shared/dist/shared.d.ts","../node_modules/.pnpm/@babel+parser@7.28.5/node_modules/@babel/parser/typings/babel-parser.d.ts","../node_modules/.pnpm/@vue+compiler-core@3.5.24/node_modules/@vue/compiler-core/dist/compiler-core.d.ts","../node_modules/.pnpm/magic-string@0.30.21/node_modules/magic-string/dist/magic-string.es.d.mts","../node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/lib/typescript.d.ts","../node_modules/.pnpm/@vue+compiler-sfc@3.5.24/node_modules/@vue/compiler-sfc/dist/compiler-sfc.d.ts","../node_modules/.pnpm/vue@3.5.24_typescript@5.9.3/node_modules/vue/compiler-sfc/index.d.mts","../node_modules/.pnpm/@vitejs+plugin-vue@5.2.4_vi_e70672b9796244f49d066a536954e836/node_modules/@vitejs/plugin-vue/dist/index.d.mts","./vite.config.ts","./node_modules/@types/connect/index.d.ts","./node_modules/@types/body-parser/index.d.ts","./node_modules/@types/estree/index.d.ts","./node_modules/@types/event-emitter/index.d.ts","./node_modules/@types/send/index.d.ts","./node_modules/@types/qs/index.d.ts","./node_modules/@types/range-parser/index.d.ts","./node_modules/@types/express-serve-static-core/index.d.ts","./node_modules/@types/http-errors/index.d.ts","./node_modules/@types/serve-static/index.d.ts","./node_modules/@types/express/index.d.ts","../node_modules/.pnpm/@types+body-parser@1.19.6/node_modules/@types/body-parser/index.d.ts","../node_modules/.pnpm/@types+send@1.2.1/node_modules/@types/send/index.d.ts","../node_modules/.pnpm/@types+qs@6.14.0/node_modules/@types/qs/index.d.ts","../node_modules/.pnpm/@types+range-parser@1.2.7/node_modules/@types/range-parser/index.d.ts","../node_modules/.pnpm/@types+express-serve-static-core@4.19.7/node_modules/@types/express-serve-static-core/index.d.ts","../node_modules/.pnpm/@types+http-errors@2.0.5/node_modules/@types/http-errors/index.d.ts","../node_modules/.pnpm/@types+mime@1.3.5/node_modules/@types/mime/index.d.ts","../node_modules/.pnpm/@types+send@0.17.6/node_modules/@types/send/index.d.ts","../node_modules/.pnpm/@types+serve-static@1.15.10/node_modules/@types/serve-static/index.d.ts","../node_modules/.pnpm/@types+express@4.17.25/node_modules/@types/express/index.d.ts","../node_modules/.pnpm/@types+multer@2.0.0/node_modules/@types/multer/index.d.ts","../node_modules/.pnpm/@types+adm-zip@0.5.7/node_modules/@types/adm-zip/util.d.ts","../node_modules/.pnpm/@types+adm-zip@0.5.7/node_modules/@types/adm-zip/index.d.ts","../node_modules/.pnpm/@types+babel__generator@7.27.0/node_modules/@types/babel__generator/index.d.ts","../node_modules/.pnpm/@types+babel__template@7.4.4/node_modules/@types/babel__template/index.d.ts","../node_modules/.pnpm/@types+babel__traverse@7.28.0/node_modules/@types/babel__traverse/index.d.ts","../node_modules/.pnpm/@types+babel__core@7.20.5/node_modules/@types/babel__core/index.d.ts","../node_modules/.pnpm/@types+bcrypt@5.0.2/node_modules/@types/bcrypt/index.d.ts","../node_modules/.pnpm/@types+json-schema@7.0.15/node_modules/@types/json-schema/index.d.ts","../node_modules/.pnpm/@types+eslint@9.6.1/node_modules/@types/eslint/use-at-your-own-risk.d.ts","../node_modules/.pnpm/@types+eslint@9.6.1/node_modules/@types/eslint/index.d.ts","../node_modules/.pnpm/@types+eslint-scope@3.7.7/node_modules/@types/eslint-scope/index.d.ts","../node_modules/.pnpm/@types+graceful-fs@4.1.9/node_modules/@types/graceful-fs/index.d.ts","../node_modules/.pnpm/@types+istanbul-lib-coverage@2.0.6/node_modules/@types/istanbul-lib-coverage/index.d.ts","../node_modules/.pnpm/@types+istanbul-lib-report@3.0.3/node_modules/@types/istanbul-lib-report/index.d.ts","../node_modules/.pnpm/@types+istanbul-reports@3.0.4/node_modules/@types/istanbul-reports/index.d.ts","../node_modules/.pnpm/@jest+expect-utils@29.7.0/node_modules/@jest/expect-utils/build/index.d.ts","../node_modules/.pnpm/chalk@4.1.2/node_modules/chalk/index.d.ts","../node_modules/.pnpm/@sinclair+typebox@0.27.8/node_modules/@sinclair/typebox/typebox.d.ts","../node_modules/.pnpm/@jest+schemas@29.6.3/node_modules/@jest/schemas/build/index.d.ts","../node_modules/.pnpm/pretty-format@29.7.0/node_modules/pretty-format/build/index.d.ts","../node_modules/.pnpm/jest-diff@29.7.0/node_modules/jest-diff/build/index.d.ts","../node_modules/.pnpm/jest-matcher-utils@29.7.0/node_modules/jest-matcher-utils/build/index.d.ts","../node_modules/.pnpm/expect@29.7.0/node_modules/expect/build/index.d.ts","../node_modules/.pnpm/@types+jest@29.5.14/node_modules/@types/jest/index.d.ts","../node_modules/.pnpm/@types+jsonwebtoken@9.0.5/node_modules/@types/jsonwebtoken/index.d.ts","../node_modules/.pnpm/@types+ms@2.1.0/node_modules/@types/ms/index.d.ts","../node_modules/.pnpm/@types+passport@1.0.17/node_modules/@types/passport/index.d.ts","../node_modules/.pnpm/@types+jsonwebtoken@9.0.10/node_modules/@types/jsonwebtoken/index.d.ts","../node_modules/.pnpm/@types+passport-strategy@0.2.38/node_modules/@types/passport-strategy/index.d.ts","../node_modules/.pnpm/@types+passport-jwt@4.0.1/node_modules/@types/passport-jwt/index.d.ts","../node_modules/.pnpm/@types+passport-local@1.0.38/node_modules/@types/passport-local/index.d.ts","../node_modules/.pnpm/@types+semver@7.7.1/node_modules/@types/semver/functions/inc.d.ts","../node_modules/.pnpm/@types+semver@7.7.1/node_modules/@types/semver/classes/semver.d.ts","../node_modules/.pnpm/@types+semver@7.7.1/node_modules/@types/semver/functions/parse.d.ts","../node_modules/.pnpm/@types+semver@7.7.1/node_modules/@types/semver/functions/valid.d.ts","../node_modules/.pnpm/@types+semver@7.7.1/node_modules/@types/semver/functions/clean.d.ts","../node_modules/.pnpm/@types+semver@7.7.1/node_modules/@types/semver/functions/diff.d.ts","../node_modules/.pnpm/@types+semver@7.7.1/node_modules/@types/semver/functions/major.d.ts","../node_modules/.pnpm/@types+semver@7.7.1/node_modules/@types/semver/functions/minor.d.ts","../node_modules/.pnpm/@types+semver@7.7.1/node_modules/@types/semver/functions/patch.d.ts","../node_modules/.pnpm/@types+semver@7.7.1/node_modules/@types/semver/functions/prerelease.d.ts","../node_modules/.pnpm/@types+semver@7.7.1/node_modules/@types/semver/functions/compare.d.ts","../node_modules/.pnpm/@types+semver@7.7.1/node_modules/@types/semver/functions/rcompare.d.ts","../node_modules/.pnpm/@types+semver@7.7.1/node_modules/@types/semver/functions/compare-loose.d.ts","../node_modules/.pnpm/@types+semver@7.7.1/node_modules/@types/semver/functions/compare-build.d.ts","../node_modules/.pnpm/@types+semver@7.7.1/node_modules/@types/semver/functions/sort.d.ts","../node_modules/.pnpm/@types+semver@7.7.1/node_modules/@types/semver/functions/rsort.d.ts","../node_modules/.pnpm/@types+semver@7.7.1/node_modules/@types/semver/functions/gt.d.ts","../node_modules/.pnpm/@types+semver@7.7.1/node_modules/@types/semver/functions/lt.d.ts","../node_modules/.pnpm/@types+semver@7.7.1/node_modules/@types/semver/functions/eq.d.ts","../node_modules/.pnpm/@types+semver@7.7.1/node_modules/@types/semver/functions/neq.d.ts","../node_modules/.pnpm/@types+semver@7.7.1/node_modules/@types/semver/functions/gte.d.ts","../node_modules/.pnpm/@types+semver@7.7.1/node_modules/@types/semver/functions/lte.d.ts","../node_modules/.pnpm/@types+semver@7.7.1/node_modules/@types/semver/functions/cmp.d.ts","../node_modules/.pnpm/@types+semver@7.7.1/node_modules/@types/semver/functions/coerce.d.ts","../node_modules/.pnpm/@types+semver@7.7.1/node_modules/@types/semver/classes/comparator.d.ts","../node_modules/.pnpm/@types+semver@7.7.1/node_modules/@types/semver/classes/range.d.ts","../node_modules/.pnpm/@types+semver@7.7.1/node_modules/@types/semver/functions/satisfies.d.ts","../node_modules/.pnpm/@types+semver@7.7.1/node_modules/@types/semver/ranges/max-satisfying.d.ts","../node_modules/.pnpm/@types+semver@7.7.1/node_modules/@types/semver/ranges/min-satisfying.d.ts","../node_modules/.pnpm/@types+semver@7.7.1/node_modules/@types/semver/ranges/to-comparators.d.ts","../node_modules/.pnpm/@types+semver@7.7.1/node_modules/@types/semver/ranges/min-version.d.ts","../node_modules/.pnpm/@types+semver@7.7.1/node_modules/@types/semver/ranges/valid.d.ts","../node_modules/.pnpm/@types+semver@7.7.1/node_modules/@types/semver/ranges/outside.d.ts","../node_modules/.pnpm/@types+semver@7.7.1/node_modules/@types/semver/ranges/gtr.d.ts","../node_modules/.pnpm/@types+semver@7.7.1/node_modules/@types/semver/ranges/ltr.d.ts","../node_modules/.pnpm/@types+semver@7.7.1/node_modules/@types/semver/ranges/intersects.d.ts","../node_modules/.pnpm/@types+semver@7.7.1/node_modules/@types/semver/ranges/simplify.d.ts","../node_modules/.pnpm/@types+semver@7.7.1/node_modules/@types/semver/ranges/subset.d.ts","../node_modules/.pnpm/@types+semver@7.7.1/node_modules/@types/semver/internals/identifiers.d.ts","../node_modules/.pnpm/@types+semver@7.7.1/node_modules/@types/semver/index.d.ts","../node_modules/.pnpm/@types+stack-utils@2.0.3/node_modules/@types/stack-utils/index.d.ts","../node_modules/.pnpm/@types+uuid@10.0.0/node_modules/@types/uuid/index.d.ts","../node_modules/.pnpm/@types+validator@13.15.10/node_modules/@types/validator/lib/isboolean.d.ts","../node_modules/.pnpm/@types+validator@13.15.10/node_modules/@types/validator/lib/isemail.d.ts","../node_modules/.pnpm/@types+validator@13.15.10/node_modules/@types/validator/lib/isfqdn.d.ts","../node_modules/.pnpm/@types+validator@13.15.10/node_modules/@types/validator/lib/isiban.d.ts","../node_modules/.pnpm/@types+validator@13.15.10/node_modules/@types/validator/lib/isiso31661alpha2.d.ts","../node_modules/.pnpm/@types+validator@13.15.10/node_modules/@types/validator/lib/isiso4217.d.ts","../node_modules/.pnpm/@types+validator@13.15.10/node_modules/@types/validator/lib/isiso6391.d.ts","../node_modules/.pnpm/@types+validator@13.15.10/node_modules/@types/validator/lib/istaxid.d.ts","../node_modules/.pnpm/@types+validator@13.15.10/node_modules/@types/validator/lib/isurl.d.ts","../node_modules/.pnpm/@types+validator@13.15.10/node_modules/@types/validator/index.d.ts","../node_modules/.pnpm/@types+yargs-parser@21.0.3/node_modules/@types/yargs-parser/index.d.ts","../node_modules/.pnpm/@types+yargs@17.0.35/node_modules/@types/yargs/index.d.ts"],"fileIdsList":[[54,117,125,129,131,132,134,135,136,148,173,216],[54,117,125,129,131,132,134,135,136,148,173],[54,117,125,129,132,134,135,136,148],[54,117,125,128,129,131,132,134,135,136,148,173,220,221,222],[54,117,125,129,132,134,135,136,148,217,223,225],[54,114,115,117,125,129,132,134,135,136,148],[54,116,117,125,129,132,134,135,136,148],[117,125,129,132,134,135,136,148],[54,117,125,129,132,134,135,136,148,156],[54,117,118,123,125,128,129,132,134,135,136,138,148,153,165],[54,117,118,119,125,128,129,132,134,135,136,148],[54,117,120,125,129,132,134,135,136,148,166],[54,117,121,122,125,129,132,134,135,136,139,148],[54,117,122,125,129,132,134,135,136,148,153,162],[54,117,123,125,128,129,132,134,135,136,138,148],[54,116,117,124,125,129,132,134,135,136,148],[54,117,125,126,129,132,134,135,136,148],[54,117,125,127,128,129,132,134,135,136,148],[54,116,117,125,128,129,132,134,135,136,148],[54,117,125,128,129,130,132,134,135,136,148,153,165],[54,117,125,128,129,130,132,134,135,136,148,153,156],[54,104,117,125,128,129,131,132,134,135,136,138,148,153,165],[54,117,125,128,129,131,132,134,135,136,138,148,153,162,165],[54,117,125,129,131,132,133,134,135,136,148,153,162,165],[52,53,54,55,56,57,58,59,60,61,62,105,106,107,108,109,110,111,112,113,114,115,116,117,118,119,120,121,122,123,124,125,126,127,128,129,130,131,132,133,134,135,136,137,138,139,140,141,142,143,144,145,146,147,148,149,150,151,152,153,154,155,156,157,158,159,160,161,162,163,164,165,166,167,168,169,170,171,172],[54,117,125,128,129,132,134,135,136,148],[54,117,125,129,132,134,136,148],[54,117,125,129,132,134,135,136,137,148,165],[54,117,125,128,129,132,134,135,136,138,148,153],[54,117,125,129,132,134,135,136,139,148],[54,117,125,129,132,134,135,136,140,148],[54,117,125,128,129,132,134,135,136,143,148],[54,114,115,116,117,118,119,120,121,122,123,124,125,126,127,128,129,130,131,132,133,134,135,136,137,138,139,140,141,142,143,144,145,146,147,148,149,150,151,152,153,154,155,156,157,158,159,160,161,162,163,164,165,166,167,168,169,170,171,172],[54,117,125,129,132,134,135,136,145,148],[54,117,125,129,132,134,135,136,146,148],[54,117,122,125,129,132,134,135,136,138,148,156],[54,117,125,128,129,132,134,135,136,148,149],[54,117,125,129,132,134,135,136,148,150,166,169],[54,117,125,128,129,132,134,135,136,148,153,155,156],[54,117,125,129,132,134,135,136,148,154,156],[54,117,125,129,132,134,135,136,148,156,166],[54,117,125,129,132,134,135,136,148,157],[54,114,117,125,129,132,134,135,136,148,153,159],[54,117,125,129,132,134,135,136,148,153,158],[54,117,125,128,129,132,134,135,136,148,160,161],[54,117,125,129,132,134,135,136,148,160,161],[54,117,122,125,129,132,134,135,136,138,148,153,162],[54,117,125,129,132,134,135,136,148,163],[54,117,125,129,132,134,135,136,138,148,164],[54,117,125,129,131,132,134,135,136,146,148,165],[54,117,125,129,132,134,135,136,148,166,167],[54,117,122,125,129,132,134,135,136,148,167],[54,117,125,129,132,134,135,136,148,153,168],[54,117,125,129,132,134,135,136,137,148,169],[54,117,125,129,132,134,135,136,148,170],[54,117,120,125,129,132,134,135,136,148],[54,117,122,125,129,132,134,135,136,148],[54,117,125,129,132,134,135,136,148,166],[54,104,117,125,129,132,134,135,136,148],[54,117,125,129,132,134,135,136,148,165],[54,117,125,129,132,134,135,136,148,171],[54,117,125,129,132,134,135,136,143,148],[54,117,125,129,132,134,135,136,148,161],[54,104,117,125,128,129,130,132,134,135,136,143,148,153,156,165,168,169,171],[54,117,125,129,132,134,135,136,148,153,172],[54,117,125,129,132,134,135,136,148,153,173],[54,117,125,129,131,132,134,135,136,148,173,224],[54,70,73,76,77,117,125,129,132,134,135,136,148,165],[54,73,117,125,129,132,134,135,136,148,153,165],[54,73,77,117,125,129,132,134,135,136,148,165],[54,117,125,129,132,134,135,136,148,153],[54,67,117,125,129,132,134,135,136,148],[54,71,117,125,129,132,134,135,136,148],[54,69,70,73,117,125,129,132,134,135,136,148,165],[54,117,125,129,132,134,135,136,138,148,162],[54,117,125,129,132,134,135,136,148,173],[54,67,117,125,129,132,134,135,136,148,173],[54,69,73,117,125,129,132,134,135,136,138,148,165],[54,64,65,66,68,72,117,125,128,129,132,134,135,136,148,153,165],[54,73,81,89,117,125,129,132,134,135,136,148],[54,65,71,117,125,129,132,134,135,136,148],[54,73,98,99,117,125,129,132,134,135,136,148],[54,65,68,73,117,125,129,132,134,135,136,148,156,165,173],[54,73,117,125,129,132,134,135,136,148],[54,69,73,117,125,129,132,134,135,136,148,165],[54,64,117,125,129,132,134,135,136,148],[54,67,68,69,71,72,73,74,75,77,78,79,80,81,82,83,84,85,86,87,88,89,90,91,92,93,94,95,96,97,99,100,101,102,103,117,125,129,132,134,135,136,148],[54,73,91,94,117,125,129,132,134,135,136,148],[54,73,81,82,83,117,125,129,132,134,135,136,148],[54,71,73,82,84,117,125,129,132,134,135,136,148],[54,72,117,125,129,132,134,135,136,148],[54,65,67,73,117,125,129,132,134,135,136,148],[54,73,77,82,84,117,125,129,132,134,135,136,148],[54,77,117,125,129,132,134,135,136,148],[54,71,73,76,117,125,129,132,134,135,136,148,165],[54,65,69,73,81,117,125,129,132,134,135,136,148],[54,73,91,117,125,129,132,134,135,136,148],[54,84,117,125,129,132,134,135,136,148],[54,67,73,98,117,125,129,132,134,135,136,148,156,171,173],[54,117,125,129,132,134,135,136,140,148,205,214],[54,117,125,129,132,134,135,136,148,206],[54,117,125,129,132,134,135,136,148,255],[54,117,125,129,132,134,135,136,148,173,238],[54,117,125,129,132,134,135,136,148,206,208,240,241,242],[54,117,125,129,132,134,135,136,148,206,208],[54,117,125,129,131,132,134,135,136,148,216],[54,117,125,129,132,134,135,136,148,174,175,247],[54,117,125,129,132,134,135,136,148,174,175,245,246],[54,117,125,129,132,134,135,136,148,247],[54,117,125,129,132,134,135,136,148,217,221,223,225,231,235],[54,117,125,129,132,134,135,136,148,250],[54,117,125,129,132,134,135,136,148,251],[54,117,125,129,132,134,135,136,148,257,260],[54,117,122,125,129,132,134,135,136,148,173,263],[54,117,122,125,129,132,134,135,136,148,173],[54,117,125,129,132,134,135,136,148,153,236],[54,117,125,129,132,134,135,136,148,265,266],[54,117,125,129,132,134,135,136,148,236,264,266],[54,117,125,129,132,134,135,136,148,226,236,264],[54,117,125,129,131,132,134,135,136,148,236],[54,117,125,129,132,134,135,136,148,270,308],[54,117,125,129,132,134,135,136,148,270,293,308],[54,117,125,129,132,134,135,136,148,269,308],[54,117,125,129,132,134,135,136,148,308],[54,117,125,129,132,134,135,136,148,270],[54,117,125,129,132,134,135,136,148,270,294,308],[54,117,125,129,132,134,135,136,148,269,270,271,272,273,274,275,276,277,278,279,280,281,282,283,284,285,286,287,288,289,290,291,292,293,294,295,296,297,298,299,300,301,302,303,304,305,306,307],[54,117,125,129,132,134,135,136,148,294,308],[54,117,125,129,132,134,135,136,148,153,173,233],[54,117,125,129,131,132,134,135,136,148,173,224,234],[54,117,125,129,132,134,135,136,148,311,312,313,314,315,316,317,318,319],[54,117,125,129,132,134,135,136,148,321],[54,117,125,129,132,134,135,136,148,205,213],[54,117,125,129,132,134,135,136,148,206,207,208],[54,117,125,129,132,134,135,136,148,201,206,208,209,210,211],[54,117,125,129,132,134,135,136,148,253,259],[54,117,125,129,132,134,135,136,148,257],[54,117,125,129,132,134,135,136,148,254,258],[54,117,125,129,132,134,135,136,148,197],[54,117,125,129,132,134,135,136,148,195,197],[54,117,125,129,132,134,135,136,148,186,194,195,196,198,200],[54,117,125,129,132,134,135,136,148,184],[54,117,125,129,132,134,135,136,148,187,192,197,200],[54,117,125,129,132,134,135,136,148,183,200],[54,117,125,129,132,134,135,136,148,187,188,191,192,193,200],[54,117,125,129,132,134,135,136,148,187,188,189,191,192,200],[54,117,125,129,132,134,135,136,148,184,185,186,187,188,192,193,194,196,197,198,200],[54,117,125,129,132,134,135,136,148,200],[54,117,125,129,132,134,135,136,148,182,184,185,186,187,188,189,191,192,193,194,195,196,197,198,199],[54,117,125,129,132,134,135,136,148,182,200],[54,117,125,129,132,134,135,136,148,187,189,190,192,193,200],[54,117,125,129,132,134,135,136,148,191,200],[54,117,125,129,132,134,135,136,148,192,193,197,200],[54,117,125,129,132,134,135,136,148,185,195],[54,117,125,129,132,134,135,136,148,256],[54,117,125,129,132,134,135,136,148,175,204],[54,117,125,129,132,134,135,136,148,174,175],[54,117,125,128,129,131,132,133,134,135,136,138,148,153,162,165,172,173,175,176,177,178,179,180,181,201,202,203,204],[54,117,125,129,132,134,135,136,148,177,178,179,180],[54,117,125,129,132,134,135,136,148,177,178,179],[54,117,125,129,132,134,135,136,148,177],[54,117,125,129,132,134,135,136,148,178],[54,117,125,129,132,134,135,136,148,175],[54,117,125,129,132,134,135,136,148,212]],"fileInfos":[{"version":"a7297ff837fcdf174a9524925966429eb8e5feecc2cc55cc06574e6b092c1eaa","impliedFormat":1},{"version":"c430d44666289dae81f30fa7b2edebf186ecc91a2d4c71266ea6ae76388792e1","affectsGlobalScope":true,"impliedFormat":1},{"version":"45b7ab580deca34ae9729e97c13cfd999df04416a79116c3bfb483804f85ded4","impliedFormat":1},{"version":"3facaf05f0c5fc569c5649dd359892c98a85557e3e0c847964caeb67076f4d75","impliedFormat":1},{"version":"e44bb8bbac7f10ecc786703fe0a6a4b952189f908707980ba8f3c8975a760962","impliedFormat":1},{"version":"5e1c4c362065a6b95ff952c0eab010f04dcd2c3494e813b493ecfd4fcb9fc0d8","impliedFormat":1},{"version":"68d73b4a11549f9c0b7d352d10e91e5dca8faa3322bfb77b661839c42b1ddec7","impliedFormat":1},{"version":"5efce4fc3c29ea84e8928f97adec086e3dc876365e0982cc8479a07954a3efd4","impliedFormat":1},{"version":"080941d9f9ff9307f7e27a83bcd888b7c8270716c39af943532438932ec1d0b9","affectsGlobalScope":true,"impliedFormat":1},{"version":"80e18897e5884b6723488d4f5652167e7bb5024f946743134ecc4aa4ee731f89","affectsGlobalScope":true,"impliedFormat":1},{"version":"cd034f499c6cdca722b60c04b5b1b78e058487a7085a8e0d6fb50809947ee573","affectsGlobalScope":true,"impliedFormat":1},{"version":"c57796738e7f83dbc4b8e65132f11a377649c00dd3eee333f672b8f0a6bea671","affectsGlobalScope":true,"impliedFormat":1},{"version":"dc2df20b1bcdc8c2d34af4926e2c3ab15ffe1160a63e58b7e09833f616efff44","affectsGlobalScope":true,"impliedFormat":1},{"version":"515d0b7b9bea2e31ea4ec968e9edd2c39d3eebf4a2d5cbd04e88639819ae3b71","affectsGlobalScope":true,"impliedFormat":1},{"version":"0559b1f683ac7505ae451f9a96ce4c3c92bdc71411651ca6ddb0e88baaaad6a3","affectsGlobalScope":true,"impliedFormat":1},{"version":"0dc1e7ceda9b8b9b455c3a2d67b0412feab00bd2f66656cd8850e8831b08b537","affectsGlobalScope":true,"impliedFormat":1},{"version":"ce691fb9e5c64efb9547083e4a34091bcbe5bdb41027e310ebba8f7d96a98671","affectsGlobalScope":true,"impliedFormat":1},{"version":"8d697a2a929a5fcb38b7a65594020fcef05ec1630804a33748829c5ff53640d0","affectsGlobalScope":true,"impliedFormat":1},{"version":"4ff2a353abf8a80ee399af572debb8faab2d33ad38c4b4474cff7f26e7653b8d","affectsGlobalScope":true,"impliedFormat":1},{"version":"fb0f136d372979348d59b3f5020b4cdb81b5504192b1cacff5d1fbba29378aa1","affectsGlobalScope":true,"impliedFormat":1},{"version":"d15bea3d62cbbdb9797079416b8ac375ae99162a7fba5de2c6c505446486ac0a","affectsGlobalScope":true,"impliedFormat":1},{"version":"68d18b664c9d32a7336a70235958b8997ebc1c3b8505f4f1ae2b7e7753b87618","affectsGlobalScope":true,"impliedFormat":1},{"version":"eb3d66c8327153d8fa7dd03f9c58d351107fe824c79e9b56b462935176cdf12a","affectsGlobalScope":true,"impliedFormat":1},{"version":"38f0219c9e23c915ef9790ab1d680440d95419ad264816fa15009a8851e79119","affectsGlobalScope":true,"impliedFormat":1},{"version":"69ab18c3b76cd9b1be3d188eaf8bba06112ebbe2f47f6c322b5105a6fbc45a2e","affectsGlobalScope":true,"impliedFormat":1},{"version":"a680117f487a4d2f30ea46f1b4b7f58bef1480456e18ba53ee85c2746eeca012","affectsGlobalScope":true,"impliedFormat":1},{"version":"2f11ff796926e0832f9ae148008138ad583bd181899ab7dd768a2666700b1893","affectsGlobalScope":true,"impliedFormat":1},{"version":"4de680d5bb41c17f7f68e0419412ca23c98d5749dcaaea1896172f06435891fc","affectsGlobalScope":true,"impliedFormat":1},{"version":"954296b30da6d508a104a3a0b5d96b76495c709785c1d11610908e63481ee667","affectsGlobalScope":true,"impliedFormat":1},{"version":"ac9538681b19688c8eae65811b329d3744af679e0bdfa5d842d0e32524c73e1c","affectsGlobalScope":true,"impliedFormat":1},{"version":"0a969edff4bd52585473d24995c5ef223f6652d6ef46193309b3921d65dd4376","affectsGlobalScope":true,"impliedFormat":1},{"version":"9e9fbd7030c440b33d021da145d3232984c8bb7916f277e8ffd3dc2e3eae2bdb","affectsGlobalScope":true,"impliedFormat":1},{"version":"811ec78f7fefcabbda4bfa93b3eb67d9ae166ef95f9bff989d964061cbf81a0c","affectsGlobalScope":true,"impliedFormat":1},{"version":"717937616a17072082152a2ef351cb51f98802fb4b2fdabd32399843875974ca","affectsGlobalScope":true,"impliedFormat":1},{"version":"d7e7d9b7b50e5f22c915b525acc5a49a7a6584cf8f62d0569e557c5cfc4b2ac2","affectsGlobalScope":true,"impliedFormat":1},{"version":"71c37f4c9543f31dfced6c7840e068c5a5aacb7b89111a4364b1d5276b852557","affectsGlobalScope":true,"impliedFormat":1},{"version":"576711e016cf4f1804676043e6a0a5414252560eb57de9faceee34d79798c850","affectsGlobalScope":true,"impliedFormat":1},{"version":"89c1b1281ba7b8a96efc676b11b264de7a8374c5ea1e6617f11880a13fc56dc6","affectsGlobalScope":true,"impliedFormat":1},{"version":"74f7fa2d027d5b33eb0471c8e82a6c87216223181ec31247c357a3e8e2fddc5b","affectsGlobalScope":true,"impliedFormat":1},{"version":"d6d7ae4d1f1f3772e2a3cde568ed08991a8ae34a080ff1151af28b7f798e22ca","affectsGlobalScope":true,"impliedFormat":1},{"version":"063600664504610fe3e99b717a1223f8b1900087fab0b4cad1496a114744f8df","affectsGlobalScope":true,"impliedFormat":1},{"version":"934019d7e3c81950f9a8426d093458b65d5aff2c7c1511233c0fd5b941e608ab","affectsGlobalScope":true,"impliedFormat":1},{"version":"52ada8e0b6e0482b728070b7639ee42e83a9b1c22d205992756fe020fd9f4a47","affectsGlobalScope":true,"impliedFormat":1},{"version":"3bdefe1bfd4d6dee0e26f928f93ccc128f1b64d5d501ff4a8cf3c6371200e5e6","affectsGlobalScope":true,"impliedFormat":1},{"version":"59fb2c069260b4ba00b5643b907ef5d5341b167e7d1dbf58dfd895658bda2867","affectsGlobalScope":true,"impliedFormat":1},{"version":"639e512c0dfc3fad96a84caad71b8834d66329a1f28dc95e3946c9b58176c73a","affectsGlobalScope":true,"impliedFormat":1},{"version":"368af93f74c9c932edd84c58883e736c9e3d53cec1fe24c0b0ff451f529ceab1","affectsGlobalScope":true,"impliedFormat":1},{"version":"51ad4c928303041605b4d7ae32e0c1ee387d43a24cd6f1ebf4a2699e1076d4fa","affectsGlobalScope":true,"impliedFormat":1},{"version":"196cb558a13d4533a5163286f30b0509ce0210e4b316c56c38d4c0fd2fb38405","affectsGlobalScope":true,"impliedFormat":1},{"version":"8e7f8264d0fb4c5339605a15daadb037bf238c10b654bb3eee14208f860a32ea","affectsGlobalScope":true,"impliedFormat":1},{"version":"782dec38049b92d4e85c1585fbea5474a219c6984a35b004963b00beb1aab538","affectsGlobalScope":true,"impliedFormat":1},{"version":"d153a11543fd884b596587ccd97aebbeed950b26933ee000f94009f1ab142848","affectsGlobalScope":true,"impliedFormat":1},{"version":"0ccdaa19852d25ecd84eec365c3bfa16e7859cadecf6e9ca6d0dbbbee439743f","affectsGlobalScope":true,"impliedFormat":1},{"version":"438b41419b1df9f1fbe33b5e1b18f5853432be205991d1b19f5b7f351675541e","affectsGlobalScope":true,"impliedFormat":1},{"version":"096116f8fedc1765d5bd6ef360c257b4a9048e5415054b3bf3c41b07f8951b0b","affectsGlobalScope":true,"impliedFormat":1},{"version":"e5e01375c9e124a83b52ee4b3244ed1a4d214a6cfb54ac73e164a823a4a7860a","affectsGlobalScope":true,"impliedFormat":1},{"version":"f90ae2bbce1505e67f2f6502392e318f5714bae82d2d969185c4a6cecc8af2fc","affectsGlobalScope":true,"impliedFormat":1},{"version":"4b58e207b93a8f1c88bbf2a95ddc686ac83962b13830fe8ad3f404ffc7051fb4","affectsGlobalScope":true,"impliedFormat":1},{"version":"1fefabcb2b06736a66d2904074d56268753654805e829989a46a0161cd8412c5","affectsGlobalScope":true,"impliedFormat":1},{"version":"9798340ffb0d067d69b1ae5b32faa17ab31b82466a3fc00d8f2f2df0c8554aaa","affectsGlobalScope":true,"impliedFormat":1},{"version":"c18a99f01eb788d849ad032b31cafd49de0b19e083fe775370834c5675d7df8e","affectsGlobalScope":true,"impliedFormat":1},{"version":"5247874c2a23b9a62d178ae84f2db6a1d54e6c9a2e7e057e178cc5eea13757fc","affectsGlobalScope":true,"impliedFormat":1},{"version":"8e9c23ba78aabc2e0a27033f18737a6df754067731e69dc5f52823957d60a4b6","impliedFormat":1},{"version":"cdcf9ea426ad970f96ac930cd176d5c69c6c24eebd9fc580e1572d6c6a88f62c","impliedFormat":1},{"version":"23cd712e2ce083d68afe69224587438e5914b457b8acf87073c22494d706a3d0","impliedFormat":1},{"version":"487b694c3de27ddf4ad107d4007ad304d29effccf9800c8ae23c2093638d906a","impliedFormat":1},{"version":"3a80bc85f38526ca3b08007ee80712e7bb0601df178b23fbf0bf87036fce40ce","impliedFormat":1},{"version":"ccf4552357ce3c159ef75f0f0114e80401702228f1898bdc9402214c9499e8c0","impliedFormat":1},{"version":"c6fd2c5a395f2432786c9cb8deb870b9b0e8ff7e22c029954fabdd692bff6195","impliedFormat":1},{"version":"68834d631c8838c715f225509cfc3927913b9cc7a4870460b5b60c8dbdb99baf","impliedFormat":1},{"version":"2931540c47ee0ff8a62860e61782eb17b155615db61e36986e54645ec67f67c2","impliedFormat":1},{"version":"ccab02f3920fc75c01174c47fcf67882a11daf16baf9e81701d0a94636e94556","impliedFormat":1},{"version":"f6faf5f74e4c4cc309a6c6a6c4da02dbb840be5d3e92905a23dcd7b2b0bd1986","impliedFormat":1},{"version":"ea6bc8de8b59f90a7a3960005fd01988f98fd0784e14bc6922dde2e93305ec7d","impliedFormat":1},{"version":"36107995674b29284a115e21a0618c4c2751b32a8766dd4cb3ba740308b16d59","impliedFormat":1},{"version":"914a0ae30d96d71915fc519ccb4efbf2b62c0ddfb3a3fc6129151076bc01dc60","impliedFormat":1},{"version":"33e981bf6376e939f99bd7f89abec757c64897d33c005036b9a10d9587d80187","impliedFormat":1},{"version":"7fd1b31fd35876b0aa650811c25ec2c97a3c6387e5473eb18004bed86cdd76b6","impliedFormat":1},{"version":"b41767d372275c154c7ea6c9d5449d9a741b8ce080f640155cc88ba1763e35b3","impliedFormat":1},{"version":"3bacf516d686d08682751a3bd2519ea3b8041a164bfb4f1d35728993e70a2426","impliedFormat":1},{"version":"7fb266686238369442bd1719bc0d7edd0199da4fb8540354e1ff7f16669b4323","impliedFormat":1},{"version":"0a60a292b89ca7218b8616f78e5bbd1c96b87e048849469cccb4355e98af959a","impliedFormat":1},{"version":"0b6e25234b4eec6ed96ab138d96eb70b135690d7dd01f3dd8a8ab291c35a683a","impliedFormat":1},{"version":"9666f2f84b985b62400d2e5ab0adae9ff44de9b2a34803c2c5bd3c8325b17dc0","impliedFormat":1},{"version":"40cd35c95e9cf22cfa5bd84e96408b6fcbca55295f4ff822390abb11afbc3dca","impliedFormat":1},{"version":"b1616b8959bf557feb16369c6124a97a0e74ed6f49d1df73bb4b9ddf68acf3f3","impliedFormat":1},{"version":"5b03a034c72146b61573aab280f295b015b9168470f2df05f6080a2122f9b4df","impliedFormat":1},{"version":"40b463c6766ca1b689bfcc46d26b5e295954f32ad43e37ee6953c0a677e4ae2b","impliedFormat":1},{"version":"249b9cab7f5d628b71308c7d9bb0a808b50b091e640ba3ed6e2d0516f4a8d91d","impliedFormat":1},{"version":"80aae6afc67faa5ac0b32b5b8bc8cc9f7fa299cff15cf09cc2e11fd28c6ae29e","impliedFormat":1},{"version":"f473cd2288991ff3221165dcf73cd5d24da30391f87e85b3dd4d0450c787a391","impliedFormat":1},{"version":"499e5b055a5aba1e1998f7311a6c441a369831c70905cc565ceac93c28083d53","impliedFormat":1},{"version":"54c3e2371e3d016469ad959697fd257e5621e16296fa67082c2575d0bf8eced0","impliedFormat":1},{"version":"beb8233b2c220cfa0feea31fbe9218d89fa02faa81ef744be8dce5acb89bb1fd","impliedFormat":1},{"version":"c183b931b68ad184bc8e8372bf663f3d33304772fb482f29fb91b3c391031f3e","impliedFormat":1},{"version":"5d0375ca7310efb77e3ef18d068d53784faf62705e0ad04569597ae0e755c401","impliedFormat":1},{"version":"59af37caec41ecf7b2e76059c9672a49e682c1a2aa6f9d7dc78878f53aa284d6","impliedFormat":1},{"version":"addf417b9eb3f938fddf8d81e96393a165e4be0d4a8b6402292f9c634b1cb00d","impliedFormat":1},{"version":"48cc3ec153b50985fb95153258a710782b25975b10dd4ac8a4f3920632d10790","impliedFormat":1},{"version":"adf27937dba6af9f08a68c5b1d3fce0ca7d4b960c57e6d6c844e7d1a8e53adae","impliedFormat":1},{"version":"e1528ca65ac90f6fa0e4a247eb656b4263c470bb22d9033e466463e13395e599","impliedFormat":1},{"version":"2e85db9e6fd73cfa3d7f28e0ab6b55417ea18931423bd47b409a96e4a169e8e6","impliedFormat":1},{"version":"c46e079fe54c76f95c67fb89081b3e399da2c7d109e7dca8e4b58d83e332e605","impliedFormat":1},{"version":"866078923a56d026e39243b4392e282c1c63159723996fa89243140e1388a98d","impliedFormat":1},{"version":"dd0109710de4cd93e245121ab86d8c66d20f3ead80074b68e9c3e349c4f53342","affectsGlobalScope":true,"impliedFormat":1},{"version":"b3275d55fac10b799c9546804126239baf020d220136163f763b55a74e50e750","affectsGlobalScope":true,"impliedFormat":1},{"version":"fa68a0a3b7cb32c00e39ee3cd31f8f15b80cac97dce51b6ee7fc14a1e8deb30b","affectsGlobalScope":true,"impliedFormat":1},{"version":"1cf059eaf468efcc649f8cf6075d3cb98e9a35a0fe9c44419ec3d2f5428d7123","affectsGlobalScope":true,"impliedFormat":1},{"version":"6c36e755bced82df7fb6ce8169265d0a7bb046ab4e2cb6d0da0cb72b22033e89","affectsGlobalScope":true,"impliedFormat":1},{"version":"e7721c4f69f93c91360c26a0a84ee885997d748237ef78ef665b153e622b36c1","affectsGlobalScope":true,"impliedFormat":1},{"version":"7a93de4ff8a63bafe62ba86b89af1df0ccb5e40bb85b0c67d6bbcfdcf96bf3d4","affectsGlobalScope":true,"impliedFormat":1},{"version":"90e85f9bc549dfe2b5749b45fe734144e96cd5d04b38eae244028794e142a77e","affectsGlobalScope":true,"impliedFormat":1},{"version":"e0a5deeb610b2a50a6350bd23df6490036a1773a8a71d70f2f9549ab009e67ee","affectsGlobalScope":true,"impliedFormat":1},{"version":"435b3711465425770ed2ee2f1cf00ce071835265e0851a7dc4600ab4b007550e","impliedFormat":1},{"version":"7e49f52a159435fc8df4de9dc377ef5860732ca2dc9efec1640531d3cf5da7a3","impliedFormat":1},{"version":"dd4bde4bdc2e5394aed6855e98cf135dfdf5dd6468cad842e03116d31bbcc9bc","impliedFormat":1},{"version":"4d4e879009a84a47c05350b8dca823036ba3a29a3038efed1be76c9f81e45edf","affectsGlobalScope":true,"impliedFormat":1},{"version":"cf83d90d5faf27b994c2e79af02e32b555dbfe42cd9bd1571445f2168d1f4e2d","impliedFormat":1},{"version":"9ba13b47cb450a438e3076c4a3f6afb9dc85e17eae50f26d4b2d72c0688c9251","impliedFormat":1},{"version":"b64cd4401633ea4ecadfd700ddc8323a13b63b106ac7127c1d2726f32424622c","impliedFormat":1},{"version":"37c6e5fe5715814412b43cc9b50b24c67a63c4e04e753e0d1305970d65417a60","impliedFormat":1},{"version":"0e28335ac43f4d94dd2fe6d9e6fa6813570640839addd10d309d7985f33a6308","impliedFormat":1},{"version":"ee0e4946247f842c6dd483cbb60a5e6b484fee07996e3a7bc7343dfb68a04c5d","impliedFormat":1},{"version":"ef051f42b7e0ef5ca04552f54c4552eac84099d64b6c5ad0ef4033574b6035b8","impliedFormat":1},{"version":"853a43154f1d01b0173d9cbd74063507ece57170bad7a3b68f3fa1229ad0a92f","impliedFormat":1},{"version":"56231e3c39a031bfb0afb797690b20ed4537670c93c0318b72d5180833d98b72","impliedFormat":1},{"version":"5cc7c39031bfd8b00ad58f32143d59eb6ffc24f5d41a20931269011dccd36c5e","impliedFormat":1},{"version":"b0b69c61b0f0ec8ca15db4c8c41f6e77f4cacb784d42bca948f42dea33e8757e","affectsGlobalScope":true,"impliedFormat":1},{"version":"f96a48183254c00d24575401f1a761b4ce4927d927407e7862a83e06ce5d6964","impliedFormat":1},{"version":"cc25940cfb27aa538e60d465f98bb5068d4d7d33131861ace43f04fe6947d68f","impliedFormat":1},{"version":"ac86245c2f31335bfd52cbe7fc760f9fc4f165387875869a478a6d9616a95e72","impliedFormat":1},{"version":"01ff95aa1443e3f7248974e5a771f513cb2ac158c8898f470a1792f817bee497","impliedFormat":1},{"version":"9d96a7ce809392ff2cb99691acf7c62e632fe56897356ba013b689277aca3619","impliedFormat":1},{"version":"42a05d8f239f74587d4926aba8cc54792eed8e8a442c7adc9b38b516642aadfe","impliedFormat":1},{"version":"5d21b58d60383cc6ab9ad3d3e265d7d25af24a2c9b506247e0e50b0a884920be","impliedFormat":1},{"version":"101f482fd48cb4c7c0468dcc6d62c843d842977aea6235644b1edd05e81fbf22","impliedFormat":1},{"version":"ae6757460f37078884b1571a3de3ebaf724d827d7e1d53626c02b3c2a408ac63","affectsGlobalScope":true,"impliedFormat":1},{"version":"27c0a08e343c6a0ae17bd13ba6d44a9758236dc904cd5e4b43456996cd51f520","impliedFormat":1},{"version":"3ef397f12387eff17f550bc484ea7c27d21d43816bbe609d495107f44b97e933","impliedFormat":1},{"version":"1023282e2ba810bc07905d3668349fbd37a26411f0c8f94a70ef3c05fe523fcf","impliedFormat":1},{"version":"b214ebcf76c51b115453f69729ee8aa7b7f8eccdae2a922b568a45c2d7ff52f7","impliedFormat":1},{"version":"429c9cdfa7d126255779efd7e6d9057ced2d69c81859bbab32073bad52e9ba76","impliedFormat":1},{"version":"6f80e51ba310608cd71bcdc09a171d7bbfb3b316048601c9ec215ce16a8dcfbc","impliedFormat":1},{"version":"70ac82add6c6f5c8a4a1db5390e75ead5a0cf5cd50216cf22769870c7026c786","affectsGlobalScope":true,"impliedFormat":1},{"version":"7f2c62938251b45715fd2a9887060ec4fbc8724727029d1cbce373747252bdd7","impliedFormat":1},{"version":"e3ace08b6bbd84655d41e244677b474fd995923ffef7149ddb68af8848b60b05","impliedFormat":1},{"version":"132580b0e86c48fab152bab850fc57a4b74fe915c8958d2ccb052b809a44b61c","impliedFormat":1},{"version":"af4ab0aa8908fc9a655bb833d3bc28e117c4f0e1038c5a891546158beb25accb","impliedFormat":1},{"version":"69c9a5a9392e8564bd81116e1ed93b13205201fb44cb35a7fde8c9f9e21c4b23","impliedFormat":1},{"version":"5f8fc37f8434691ffac1bfd8fc2634647da2c0e84253ab5d2dd19a7718915b35","impliedFormat":1},{"version":"5981c2340fd8b076cae8efbae818d42c11ffc615994cb060b1cd390795f1be2b","impliedFormat":1},{"version":"2ca2bca6845a7234eff5c3d192727a068fca72ac565f3c819c6b04ccc83dadc0","impliedFormat":1},{"version":"ed4f674fc8c0c993cc7e145069ac44129e03519b910c62be206a0cc777bdc60b","affectsGlobalScope":true,"impliedFormat":1},{"version":"0250da3eb85c99624f974e77ef355cdf86f43980251bc371475c2b397ba55bcd","impliedFormat":1},{"version":"f1c93e046fb3d9b7f8249629f4b63dc068dd839b824dd0aa39a5e68476dc9420","impliedFormat":1},{"version":"3d3a5f27ffbc06c885dd4d5f9ee20de61faf877fe2c3a7051c4825903d9a7fdc","impliedFormat":1},{"version":"12806f9f085598ef930edaf2467a5fa1789a878fba077cd27e85dc5851e11834","impliedFormat":1},{"version":"17d06eb5709839c7ce719f0c38ada6f308fb433f2cd6d8c87b35856e07400950","impliedFormat":1},{"version":"a43fe41c33d0a192a0ecaf9b92e87bef3709c9972e6d53c42c49251ccb962d69","impliedFormat":1},{"version":"a177959203c017fad3ecc4f3d96c8757a840957a4959a3ae00dab9d35961ca6c","affectsGlobalScope":true,"impliedFormat":1},{"version":"6fc727ccf9b36e257ff982ea0badeffbfc2c151802f741bddff00c6af3b784cf","impliedFormat":1},{"version":"2a00d005e3af99cd1cfa75220e60c61b04bfb6be7ca7453bfe2ef6cca37cc03c","impliedFormat":1},{"version":"4844a4c9b4b1e812b257676ed8a80b3f3be0e29bf05e742cc2ea9c3c6865e6c6","impliedFormat":1},{"version":"064878a60367e0407c42fb7ba02a2ea4d83257357dc20088e549bd4d89433e9c","impliedFormat":1},{"version":"14d4bd22d1b05824971b98f7e91b2484c90f1a684805c330476641417c3d9735","impliedFormat":1},{"version":"586eaf66bace2e731cee0ddfbfac326ad74a83c1acfeac4afb2db85ad23226c7","impliedFormat":1},{"version":"b484ec11ba00e3a2235562a41898d55372ccabe607986c6fa4f4aba72093749f","impliedFormat":1},{"version":"d1a14d87cedcf4f0b8173720d6eb29cc02878bf2b6dabf9c9d9cee742f275368","impliedFormat":1},{"version":"e60efae9fe48a2955f66bf4cbf0f082516185b877daf50d9c5e2a009660a7714","impliedFormat":1},{"version":"041a7781b9127ab568d2cdcce62c58fdea7c7407f40b8c50045d7866a2727130","impliedFormat":1},{"version":"b37f83e7deea729aa9ce5593f78905afb45b7532fdff63041d374f60059e7852","impliedFormat":1},{"version":"e1cb68f3ef3a8dd7b2a9dfb3de482ed6c0f1586ba0db4e7d73c1d2147b6ffc51","impliedFormat":1},{"version":"55cdbeebe76a1fa18bbd7e7bf73350a2173926bd3085bb050cf5a5397025ee4e","impliedFormat":1},{"version":"151ff381ef9ff8da2da9b9663ebf657eac35c4c9a19183420c05728f31a6761d","impliedFormat":1},{"version":"6cd8f2410e4cf6d7870f018b38dcf1ac4771f06b363b5d71831d924cda3c488d","affectsGlobalScope":true,"impliedFormat":1},{"version":"a660aa95476042d3fdcc1343cf6bb8fdf24772d31712b1db321c5a4dcc325434","impliedFormat":1},{"version":"282f98006ed7fa9bb2cd9bdbe2524595cfc4bcd58a0bb3232e4519f2138df811","impliedFormat":1},{"version":"6222e987b58abfe92597e1273ad7233626285bc2d78409d4a7b113d81a83496b","impliedFormat":1},{"version":"cbe726263ae9a7bf32352380f7e8ab66ee25b3457137e316929269c19e18a2be","impliedFormat":1},{"version":"8b96046bf5fb0a815cba6b0880d9f97b7f3a93cf187e8dcfe8e2792e97f38f87","impliedFormat":99},{"version":"bacf2c84cf448b2cd02c717ad46c3d7fd530e0c91282888c923ad64810a4d511","affectsGlobalScope":true,"impliedFormat":1},{"version":"402e5c534fb2b85fa771170595db3ac0dd532112c8fa44fc23f233bc6967488b","impliedFormat":1},{"version":"8885cf05f3e2abf117590bbb951dcf6359e3e5ac462af1c901cfd24c6a6472e2","impliedFormat":1},{"version":"333caa2bfff7f06017f114de738050dd99a765c7eb16571c6d25a38c0d5365dc","impliedFormat":1},{"version":"e61df3640a38d535fd4bc9f4a53aef17c296b58dc4b6394fd576b808dd2fe5e6","impliedFormat":1},{"version":"459920181700cec8cbdf2a5faca127f3f17fd8dd9d9e577ed3f5f3af5d12a2e4","impliedFormat":1},{"version":"4719c209b9c00b579553859407a7e5dcfaa1c472994bd62aa5dd3cc0757eb077","impliedFormat":1},{"version":"7ec359bbc29b69d4063fe7dad0baaf35f1856f914db16b3f4f6e3e1bca4099fa","impliedFormat":1},{"version":"70790a7f0040993ca66ab8a07a059a0f8256e7bb57d968ae945f696cbff4ac7a","impliedFormat":1},{"version":"d1b9a81e99a0050ca7f2d98d7eedc6cda768f0eb9fa90b602e7107433e64c04c","impliedFormat":1},{"version":"a022503e75d6953d0e82c2c564508a5c7f8556fad5d7f971372d2d40479e4034","impliedFormat":1},{"version":"b215c4f0096f108020f666ffcc1f072c81e9f2f95464e894a5d5f34c5ea2a8b1","impliedFormat":1},{"version":"644491cde678bd462bb922c1d0cfab8f17d626b195ccb7f008612dc31f445d2d","impliedFormat":1},{"version":"dfe54dab1fa4961a6bcfba68c4ca955f8b5bbeb5f2ab3c915aa7adaa2eabc03a","impliedFormat":1},{"version":"1251d53755b03cde02466064260bb88fd83c30006a46395b7d9167340bc59b73","impliedFormat":1},{"version":"47865c5e695a382a916b1eedda1b6523145426e48a2eae4647e96b3b5e52024f","impliedFormat":1},{"version":"4cdf27e29feae6c7826cdd5c91751cc35559125e8304f9e7aed8faef97dcf572","impliedFormat":1},{"version":"331b8f71bfae1df25d564f5ea9ee65a0d847c4a94baa45925b6f38c55c7039bf","impliedFormat":1},{"version":"2a771d907aebf9391ac1f50e4ad37952943515eeea0dcc7e78aa08f508294668","impliedFormat":1},{"version":"0146fd6262c3fd3da51cb0254bb6b9a4e42931eb2f56329edd4c199cb9aaf804","impliedFormat":1},{"version":"183f480885db5caa5a8acb833c2be04f98056bdcc5fb29e969ff86e07efe57ab","impliedFormat":99},{"version":"82e687ebd99518bc63ea04b0c3810fb6e50aa6942decd0ca6f7a56d9b9a212a6","impliedFormat":99},{"version":"7f698624bbbb060ece7c0e51b7236520ebada74b747d7523c7df376453ed6fea","impliedFormat":1},{"version":"8f07f2b6514744ac96e51d7cb8518c0f4de319471237ea10cf688b8d0e9d0225","impliedFormat":1},{"version":"257b83faa134d971c738a6b9e4c47e59bb7b23274719d92197580dd662bfafc3","impliedFormat":99},{"version":"c2c2a861a338244d7dd700d0c52a78916b4bb75b98fc8ca5e7c501899fc03796","impliedFormat":1},{"version":"f468b74459f1ad4473b36a36d49f2b255f3c6b5d536c81239c2b2971df089eaf","impliedFormat":1},{"version":"adb467429462e3891de5bb4a82a4189b92005d61c7f9367c089baf03997c104e","impliedFormat":1},{"version":"7f108fc2f0dd96e1ce5e6942c555538afc4d836d4e22b60cdbe034caaac7b521","impliedFormat":1},{"version":"2be2227c3810dfd84e46674fd33b8d09a4a28ad9cb633ed536effd411665ea1e","impliedFormat":99},{"version":"e134052a6b1ded61693b4037f615dc72f14e2881e79c1ddbff6c514c8a516b05","impliedFormat":1},{"version":"83eeb5fc6bc433785dec98525eb003a02134024a8630134ecc67404d0075c26e","impliedFormat":1},{"version":"3feec212c0aeb91e5a6e62caaf9f128954590210f8c302910ea377c088f6b61a","impliedFormat":99},{"version":"bbdfaf7d9b20534c5df1e1b937a20f17ca049d603a2afe072983bf7aff2279f5","impliedFormat":99},{"version":"97ab2abbd3d680156129967147b157dfb2eeb90b0ba60238627a59a697c6f59c","signature":"4b96dd19fd2949d28ce80e913412b0026dc421e5bf6c31d87c7b5eb11b5753b4"},{"version":"104c67f0da1bdf0d94865419247e20eded83ce7f9911a1aa75fc675c077ca66e","impliedFormat":1},{"version":"cc0d0b339f31ce0ab3b7a5b714d8e578ce698f1e13d7f8c60bfb766baeb1d35c","impliedFormat":1},{"version":"151ff381ef9ff8da2da9b9663ebf657eac35c4c9a19183420c05728f31a6761d","impliedFormat":1},{"version":"2dc1bcd6a132924a89f965b60edd5b5333aaa7eb28ba5af44a0a0a328756e9ad","impliedFormat":1},{"version":"d34aa8df2d0b18fb56b1d772ff9b3c7aea7256cf0d692f969be6e1d27b74d660","impliedFormat":1},{"version":"baac9896d29bcc55391d769e408ff400d61273d832dd500f21de766205255acb","impliedFormat":1},{"version":"2f5747b1508ccf83fad0c251ba1e5da2f5a30b78b09ffa1cfaf633045160afed","impliedFormat":1},{"version":"6823ccc7b5b77bbf898d878dbcad18aa45e0fa96bdd0abd0de98d514845d9ed9","affectsGlobalScope":true,"impliedFormat":1},{"version":"b71c603a539078a5e3a039b20f2b0a0d1708967530cf97dec8850a9ca45baa2b","impliedFormat":1},{"version":"168d88e14e0d81fe170e0dadd38ae9d217476c11435ea640ddb9b7382bdb6c1f","impliedFormat":1},{"version":"8e04cf0688e0d921111659c2b55851957017148fa7b977b02727477d155b3c47","impliedFormat":1},{"version":"cc0d0b339f31ce0ab3b7a5b714d8e578ce698f1e13d7f8c60bfb766baeb1d35c","impliedFormat":1},{"version":"d34aa8df2d0b18fb56b1d772ff9b3c7aea7256cf0d692f969be6e1d27b74d660","impliedFormat":1},{"version":"baac9896d29bcc55391d769e408ff400d61273d832dd500f21de766205255acb","impliedFormat":1},{"version":"2f5747b1508ccf83fad0c251ba1e5da2f5a30b78b09ffa1cfaf633045160afed","impliedFormat":1},{"version":"94ee9ee71018d54902c3fe6730090a8a421dcad95fc372d9b69a6d5351194885","affectsGlobalScope":true,"impliedFormat":1},{"version":"b71c603a539078a5e3a039b20f2b0a0d1708967530cf97dec8850a9ca45baa2b","impliedFormat":1},{"version":"d3f2d715f57df3f04bf7b16dde01dec10366f64fce44503c92b8f78f614c1769","impliedFormat":1},{"version":"cb90077223cc1365fa21ef0911a1f9b8f2f878943523d97350dc557973ca3823","impliedFormat":1},{"version":"18f1541b81b80d806120a3489af683edfb811deb91aeca19735d9bb2613e6311","impliedFormat":1},{"version":"232f118ae64ab84dcd26ddb60eaed5a6e44302d36249abf05e9e3fc2cbb701a2","impliedFormat":1},{"version":"58564964bef3ffbd810241a8bd1c3a54347dd8adf04e1077ba49051009d3007d","affectsGlobalScope":true,"impliedFormat":1},{"version":"9063bd95bf4fe06fe71b4f1abd4dce7d41b684d9036ff4afae6e079eb252b19e","impliedFormat":1},{"version":"bb0c5cb27578678fe7a8479404efd988ceab67ff93238baf16cef721610658f7","impliedFormat":1},{"version":"b6d03c9cfe2cf0ba4c673c209fcd7c46c815b2619fd2aad59fc4229aaef2ed43","impliedFormat":1},{"version":"670a76db379b27c8ff42f1ba927828a22862e2ab0b0908e38b671f0e912cc5ed","impliedFormat":1},{"version":"13b77ab19ef7aadd86a1e54f2f08ea23a6d74e102909e3c00d31f231ed040f62","impliedFormat":1},{"version":"069bebfee29864e3955378107e243508b163e77ab10de6a5ee03ae06939f0bb9","impliedFormat":1},{"version":"160b24efb5a868df9c54f337656b4ef55fcbe0548fe15408e1c0630ec559c559","impliedFormat":1},{"version":"f3d8c757e148ad968f0d98697987db363070abada5f503da3c06aefd9d4248c1","impliedFormat":1},{"version":"a4a39b5714adfcadd3bbea6698ca2e942606d833bde62ad5fb6ec55f5e438ff8","impliedFormat":1},{"version":"bbc1d029093135d7d9bfa4b38cbf8761db505026cc458b5e9c8b74f4000e5e75","impliedFormat":1},{"version":"1f68ab0e055994eb337b67aa87d2a15e0200951e9664959b3866ee6f6b11a0fe","impliedFormat":1},{"version":"afe73051ff6a03a9565cbd8ebb0e956ee3df5e913ad5c1ded64218aabfa3dcb5","impliedFormat":1},{"version":"035a5df183489c2e22f3cf59fc1ed2b043d27f357eecc0eb8d8e840059d44245","impliedFormat":1},{"version":"a4809f4d92317535e6b22b01019437030077a76fec1d93b9881c9ed4738fcc54","impliedFormat":1},{"version":"5f53fa0bd22096d2a78533f94e02c899143b8f0f9891a46965294ee8b91a9434","impliedFormat":1},{"version":"cdcc132f207d097d7d3aa75615ab9a2e71d6a478162dde8b67f88ea19f3e54de","impliedFormat":1},{"version":"0d14fa22c41fdc7277e6f71473b20ebc07f40f00e38875142335d5b63cdfc9d2","impliedFormat":1},{"version":"c085e9aa62d1ae1375794c1fb927a445fa105fed891a7e24edbb1c3300f7384a","impliedFormat":1},{"version":"f315e1e65a1f80992f0509e84e4ae2df15ecd9ef73df975f7c98813b71e4c8da","impliedFormat":1},{"version":"5b9586e9b0b6322e5bfbd2c29bd3b8e21ab9d871f82346cb71020e3d84bae73e","impliedFormat":1},{"version":"3e70a7e67c2cb16f8cd49097360c0309fe9d1e3210ff9222e9dac1f8df9d4fb6","impliedFormat":1},{"version":"ab68d2a3e3e8767c3fba8f80de099a1cfc18c0de79e42cb02ae66e22dfe14a66","impliedFormat":1},{"version":"d96cc6598148bf1a98fb2e8dcf01c63a4b3558bdaec6ef35e087fd0562eb40ec","impliedFormat":1},{"version":"f8db4fea512ab759b2223b90ecbbe7dae919c02f8ce95ec03f7fb1cf757cfbeb","affectsGlobalScope":true,"impliedFormat":1},{"version":"0bf811dcbddc95e2551f704cfd2afc267bf619f8b8f2b7bdbb94df96ec3cbfe3","impliedFormat":1},{"version":"fb893a0dfc3c9fb0f9ca93d0648694dd95f33cbad2c0f2c629f842981dfd4e2e","impliedFormat":1},{"version":"ff155930718467b27e379e4a195e4607ce277f805cad9d2fa5f4fd5dec224df6","affectsGlobalScope":true,"impliedFormat":1},{"version":"95da3c365e3d45709ad6e0b4daa5cdaf05e9076ba3c201e8f8081dd282c02f57","impliedFormat":1},{"version":"03c92769f389dbd9e45232f7eb01c3e0f482b62555aaf2029dcbf380d5cee9e4","impliedFormat":1},{"version":"32d7f70fd3498bc76a46dab8b03af4215f445f490f8e213c80cf06b636a4e413","impliedFormat":1},{"version":"17668c1aab598920796050ee5a00d961ede5e92595f6ac8908a975ed75a537e5","impliedFormat":1},{"version":"ce6a3f09b8db73a7e9701aca91a04b4fabaf77436dd35b24482f9ee816016b17","impliedFormat":1},{"version":"20e086e5b64fdd52396de67761cc0e94693494deadb731264aac122adf08de3f","impliedFormat":1},{"version":"6e78f75403b3ec65efb41c70d392aeda94360f11cedc9fb2c039c9ea23b30962","impliedFormat":1},{"version":"c863198dae89420f3c552b5a03da6ed6d0acfa3807a64772b895db624b0de707","impliedFormat":1},{"version":"8b03a5e327d7db67112ebbc93b4f744133eda2c1743dbb0a990c61a8007823ef","impliedFormat":1},{"version":"42fad1f540271e35ca37cecda12c4ce2eef27f0f5cf0f8dd761d723c744d3159","impliedFormat":1},{"version":"ff3743a5de32bee10906aff63d1de726f6a7fd6ee2da4b8229054dfa69de2c34","impliedFormat":1},{"version":"83acd370f7f84f203e71ebba33ba61b7f1291ca027d7f9a662c6307d74e4ac22","impliedFormat":1},{"version":"1445cec898f90bdd18b2949b9590b3c012f5b7e1804e6e329fb0fe053946d5ec","impliedFormat":1},{"version":"0e5318ec2275d8da858b541920d9306650ae6ac8012f0e872fe66eb50321a669","impliedFormat":1},{"version":"cf530297c3fb3a92ec9591dd4fa229d58b5981e45fe6702a0bd2bea53a5e59be","impliedFormat":1},{"version":"c1f6f7d08d42148ddfe164d36d7aba91f467dbcb3caa715966ff95f55048b3a4","impliedFormat":1},{"version":"eefd2bbc8edb14c3bd1246794e5c070a80f9b8f3730bd42efb80df3cc50b9039","impliedFormat":1},{"version":"0c1ee27b8f6a00097c2d6d91a21ee4d096ab52c1e28350f6362542b55380059a","impliedFormat":1},{"version":"7677d5b0db9e020d3017720f853ba18f415219fb3a9597343b1b1012cfd699f7","impliedFormat":1},{"version":"bc1c6bc119c1784b1a2be6d9c47addec0d83ef0d52c8fbe1f14a51b4dfffc675","impliedFormat":1},{"version":"52cf2ce99c2a23de70225e252e9822a22b4e0adb82643ab0b710858810e00bf1","impliedFormat":1},{"version":"770625067bb27a20b9826255a8d47b6b5b0a2d3dfcbd21f89904c731f671ba77","impliedFormat":1},{"version":"d1ed6765f4d7906a05968fb5cd6d1db8afa14dbe512a4884e8ea5c0f5e142c80","impliedFormat":1},{"version":"799c0f1b07c092626cf1efd71d459997635911bb5f7fc1196efe449bba87e965","impliedFormat":1},{"version":"2a184e4462b9914a30b1b5c41cf80c6d3428f17b20d3afb711fff3f0644001fd","impliedFormat":1},{"version":"9eabde32a3aa5d80de34af2c2206cdc3ee094c6504a8d0c2d6d20c7c179503cc","impliedFormat":1},{"version":"397c8051b6cfcb48aa22656f0faca2553c5f56187262135162ee79d2b2f6c966","impliedFormat":1},{"version":"a8ead142e0c87dcd5dc130eba1f8eeed506b08952d905c47621dc2f583b1bff9","impliedFormat":1},{"version":"a02f10ea5f73130efca046429254a4e3c06b5475baecc8f7b99a0014731be8b3","impliedFormat":1},{"version":"c2576a4083232b0e2d9bd06875dd43d371dee2e090325a9eac0133fd5650c1cb","impliedFormat":1},{"version":"4c9a0564bb317349de6a24eb4efea8bb79898fa72ad63a1809165f5bd42970dd","impliedFormat":1},{"version":"f40ac11d8859092d20f953aae14ba967282c3bb056431a37fced1866ec7a2681","impliedFormat":1},{"version":"cc11e9e79d4746cc59e0e17473a59d6f104692fd0eeea1bdb2e206eabed83b03","impliedFormat":1},{"version":"b444a410d34fb5e98aa5ee2b381362044f4884652e8bc8a11c8fe14bbd85518e","impliedFormat":1},{"version":"c35808c1f5e16d2c571aa65067e3cb95afeff843b259ecfa2fc107a9519b5392","impliedFormat":1},{"version":"14d5dc055143e941c8743c6a21fa459f961cbc3deedf1bfe47b11587ca4b3ef5","impliedFormat":1},{"version":"a3ad4e1fc542751005267d50a6298e6765928c0c3a8dce1572f2ba6ca518661c","impliedFormat":1},{"version":"f237e7c97a3a89f4591afd49ecb3bd8d14f51a1c4adc8fcae3430febedff5eb6","impliedFormat":1},{"version":"3ffdfbec93b7aed71082af62b8c3e0cc71261cc68d796665faa1e91604fbae8f","impliedFormat":1},{"version":"662201f943ed45b1ad600d03a90dffe20841e725203ced8b708c91fcd7f9379a","impliedFormat":1},{"version":"c9ef74c64ed051ea5b958621e7fb853fe3b56e8787c1587aefc6ea988b3c7e79","impliedFormat":1},{"version":"2462ccfac5f3375794b861abaa81da380f1bbd9401de59ffa43119a0b644253d","impliedFormat":1},{"version":"34baf65cfee92f110d6653322e2120c2d368ee64b3c7981dff08ed105c4f19b0","impliedFormat":1},{"version":"a56fe175741cc8841835eb72e61fa5a34adcbc249ede0e3494c229f0750f6b85","impliedFormat":1},{"version":"ab82804a14454734010dcdcd43f564ff7b0389bee4c5692eec76ff5b30d4cf66","impliedFormat":1},{"version":"f874ea4d0091b0a44362a5f74d26caab2e66dec306c2bf7e8965f5106e784c3b","impliedFormat":1},{"version":"c6cdcd12d577032b84eed1de4d2de2ae343463701a25961b202cff93989439fb","impliedFormat":1},{"version":"3dc633586d48fcd04a4f8acdbf7631b8e4a334632f252d5707e04b299069721e","impliedFormat":1},{"version":"3322858f01c0349ee7968a5ce93a1ca0c154c4692aa8f1721dc5192a9191a168","impliedFormat":1},{"version":"6dde0a77adad4173a49e6de4edd6ef70f5598cbebb5c80d76c111943854636ca","impliedFormat":1},{"version":"09acacae732e3cc67a6415026cfae979ebe900905500147a629837b790a366b3","impliedFormat":1},{"version":"f7b622759e094a3c2e19640e0cb233b21810d2762b3e894ef7f415334125eb22","impliedFormat":1},{"version":"99236ea5c4c583082975823fd19bcce6a44963c5c894e20384bc72e7eccf9b03","impliedFormat":1},{"version":"f6688a02946a3f7490aa9e26d76d1c97a388e42e77388cbab010b69982c86e9e","impliedFormat":1},{"version":"9f642953aba68babd23de41de85d4e97f0c39ef074cb8ab8aa7d55237f62aff6","impliedFormat":1},{"version":"159d95163a0ed369175ae7838fa21a9e9e703de5fdb0f978721293dd403d9f4a","impliedFormat":1},{"version":"bae8d023ef6b23df7da26f51cea44321f95817c190342a36882e93b80d07a960","impliedFormat":1},{"version":"26a770cec4bd2e7dbba95c6e536390fffe83c6268b78974a93727903b515c4e7","impliedFormat":1}],"root":[215],"options":{"allowSyntheticDefaultImports":true,"composite":true,"module":99,"skipLibCheck":true},"referencedMap":[[217,1],[216,2],[218,3],[219,3],[223,4],[226,5],[224,3],[114,6],[115,6],[116,7],[54,8],[117,9],[118,10],[119,11],[52,3],[120,12],[121,13],[122,14],[123,15],[124,16],[125,17],[126,17],[127,18],[128,19],[129,20],[130,21],[55,3],[53,3],[131,22],[132,23],[133,24],[173,25],[134,26],[135,27],[136,26],[137,28],[138,29],[139,30],[140,31],[141,31],[142,31],[143,32],[144,33],[145,34],[146,35],[147,36],[148,37],[149,37],[150,38],[151,3],[152,3],[153,39],[154,40],[155,39],[156,41],[157,42],[158,43],[159,44],[160,45],[161,46],[162,47],[163,48],[164,49],[165,50],[166,51],[167,52],[168,53],[169,54],[170,55],[56,26],[57,3],[58,56],[59,57],[60,3],[61,58],[62,3],[105,59],[106,60],[107,61],[108,61],[109,62],[110,3],[111,9],[112,63],[113,60],[171,64],[172,65],[221,3],[222,3],[220,66],[225,67],[81,68],[93,69],[79,70],[94,71],[103,72],[70,73],[71,74],[69,75],[102,76],[97,77],[101,78],[73,79],[90,80],[72,81],[100,82],[67,83],[68,77],[74,84],[75,3],[80,85],[78,84],[65,86],[104,87],[95,88],[84,89],[83,84],[85,90],[88,91],[82,92],[86,93],[98,76],[76,94],[77,95],[89,96],[66,71],[92,97],[91,84],[87,98],[96,3],[64,3],[99,99],[215,100],[208,101],[206,3],[253,3],[256,102],[255,3],[239,103],[238,3],[243,104],[240,101],[241,105],[242,101],[244,76],[227,106],[248,107],[247,108],[246,109],[174,3],[231,4],[236,110],[249,76],[232,3],[250,3],[251,111],[252,112],[261,113],[245,3],[265,114],[262,115],[233,3],[263,3],[237,116],[267,117],[268,118],[266,119],[264,120],[229,3],[230,3],[293,121],[294,122],[270,123],[273,124],[291,121],[292,121],[282,121],[281,125],[279,121],[274,121],[287,121],[285,121],[289,121],[269,121],[286,121],[290,121],[275,121],[276,121],[288,121],[271,121],[277,121],[278,121],[280,121],[284,121],[295,126],[283,121],[272,121],[308,127],[307,3],[302,126],[304,128],[303,126],[296,126],[297,126],[299,126],[301,126],[305,128],[306,128],[298,128],[300,128],[234,129],[228,71],[235,130],[309,3],[310,3],[320,131],[311,3],[312,3],[313,3],[314,3],[315,3],[316,3],[317,3],[318,3],[319,3],[321,3],[322,132],[214,133],[209,134],[212,135],[207,3],[63,3],[254,3],[181,3],[260,136],[258,137],[259,138],[210,3],[198,139],[196,140],[197,141],[185,142],[186,140],[193,143],[184,144],[189,145],[199,3],[190,146],[195,147],[201,148],[200,149],[183,150],[191,151],[192,152],[187,153],[194,139],[188,154],[257,155],[176,156],[175,157],[182,3],[1,3],[50,3],[51,3],[9,3],[13,3],[12,3],[3,3],[14,3],[15,3],[16,3],[17,3],[18,3],[19,3],[20,3],[21,3],[4,3],[22,3],[23,3],[5,3],[24,3],[28,3],[25,3],[26,3],[27,3],[29,3],[30,3],[31,3],[6,3],[32,3],[33,3],[34,3],[35,3],[7,3],[39,3],[36,3],[37,3],[38,3],[40,3],[8,3],[41,3],[46,3],[47,3],[42,3],[43,3],[44,3],[45,3],[2,3],[48,3],[49,3],[11,3],[10,3],[211,3],[205,158],[202,159],[180,160],[178,161],[177,3],[179,162],[203,3],[204,163],[213,164]],"latestChangedDtsFile":"./vite.config.d.ts","version":"5.9.3"} \ No newline at end of file +{"fileNames":["../node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/lib/lib.d.ts","../node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/lib/lib.es5.d.ts","../node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/lib/lib.es2015.d.ts","../node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/lib/lib.es2016.d.ts","../node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/lib/lib.es2017.d.ts","../node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/lib/lib.es2018.d.ts","../node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/lib/lib.es2019.d.ts","../node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/lib/lib.es2020.d.ts","../node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/lib/lib.dom.d.ts","../node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/lib/lib.webworker.importscripts.d.ts","../node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/lib/lib.scripthost.d.ts","../node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/lib/lib.es2015.core.d.ts","../node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/lib/lib.es2015.collection.d.ts","../node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/lib/lib.es2015.generator.d.ts","../node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/lib/lib.es2015.iterable.d.ts","../node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/lib/lib.es2015.promise.d.ts","../node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/lib/lib.es2015.proxy.d.ts","../node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/lib/lib.es2015.reflect.d.ts","../node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/lib/lib.es2015.symbol.d.ts","../node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/lib/lib.es2015.symbol.wellknown.d.ts","../node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/lib/lib.es2016.array.include.d.ts","../node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/lib/lib.es2016.intl.d.ts","../node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/lib/lib.es2017.arraybuffer.d.ts","../node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/lib/lib.es2017.date.d.ts","../node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/lib/lib.es2017.object.d.ts","../node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/lib/lib.es2017.sharedmemory.d.ts","../node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/lib/lib.es2017.string.d.ts","../node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/lib/lib.es2017.intl.d.ts","../node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/lib/lib.es2017.typedarrays.d.ts","../node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/lib/lib.es2018.asyncgenerator.d.ts","../node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/lib/lib.es2018.asynciterable.d.ts","../node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/lib/lib.es2018.intl.d.ts","../node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/lib/lib.es2018.promise.d.ts","../node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/lib/lib.es2018.regexp.d.ts","../node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/lib/lib.es2019.array.d.ts","../node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/lib/lib.es2019.object.d.ts","../node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/lib/lib.es2019.string.d.ts","../node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/lib/lib.es2019.symbol.d.ts","../node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/lib/lib.es2019.intl.d.ts","../node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/lib/lib.es2020.bigint.d.ts","../node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/lib/lib.es2020.date.d.ts","../node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/lib/lib.es2020.promise.d.ts","../node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/lib/lib.es2020.sharedmemory.d.ts","../node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/lib/lib.es2020.string.d.ts","../node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/lib/lib.es2020.symbol.wellknown.d.ts","../node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/lib/lib.es2020.intl.d.ts","../node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/lib/lib.es2020.number.d.ts","../node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/lib/lib.decorators.d.ts","../node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/lib/lib.decorators.legacy.d.ts","../node_modules/.pnpm/@types+node@20.19.25/node_modules/@types/node/compatibility/disposable.d.ts","../node_modules/.pnpm/@types+node@20.19.25/node_modules/@types/node/compatibility/indexable.d.ts","../node_modules/.pnpm/@types+node@20.19.25/node_modules/@types/node/compatibility/iterators.d.ts","../node_modules/.pnpm/@types+node@20.19.25/node_modules/@types/node/compatibility/index.d.ts","../node_modules/.pnpm/@types+node@20.19.25/node_modules/@types/node/globals.typedarray.d.ts","../node_modules/.pnpm/@types+node@20.19.25/node_modules/@types/node/buffer.buffer.d.ts","../node_modules/.pnpm/@types+node@20.19.25/node_modules/@types/node/globals.d.ts","../node_modules/.pnpm/@types+node@20.19.25/node_modules/@types/node/web-globals/abortcontroller.d.ts","../node_modules/.pnpm/@types+node@20.19.25/node_modules/@types/node/web-globals/domexception.d.ts","../node_modules/.pnpm/@types+node@20.19.25/node_modules/@types/node/web-globals/events.d.ts","../node_modules/.pnpm/buffer@5.7.1/node_modules/buffer/index.d.ts","../node_modules/.pnpm/undici-types@6.21.0/node_modules/undici-types/header.d.ts","../node_modules/.pnpm/undici-types@6.21.0/node_modules/undici-types/readable.d.ts","../node_modules/.pnpm/undici-types@6.21.0/node_modules/undici-types/file.d.ts","../node_modules/.pnpm/undici-types@6.21.0/node_modules/undici-types/fetch.d.ts","../node_modules/.pnpm/undici-types@6.21.0/node_modules/undici-types/formdata.d.ts","../node_modules/.pnpm/undici-types@6.21.0/node_modules/undici-types/connector.d.ts","../node_modules/.pnpm/undici-types@6.21.0/node_modules/undici-types/client.d.ts","../node_modules/.pnpm/undici-types@6.21.0/node_modules/undici-types/errors.d.ts","../node_modules/.pnpm/undici-types@6.21.0/node_modules/undici-types/dispatcher.d.ts","../node_modules/.pnpm/undici-types@6.21.0/node_modules/undici-types/global-dispatcher.d.ts","../node_modules/.pnpm/undici-types@6.21.0/node_modules/undici-types/global-origin.d.ts","../node_modules/.pnpm/undici-types@6.21.0/node_modules/undici-types/pool-stats.d.ts","../node_modules/.pnpm/undici-types@6.21.0/node_modules/undici-types/pool.d.ts","../node_modules/.pnpm/undici-types@6.21.0/node_modules/undici-types/handlers.d.ts","../node_modules/.pnpm/undici-types@6.21.0/node_modules/undici-types/balanced-pool.d.ts","../node_modules/.pnpm/undici-types@6.21.0/node_modules/undici-types/agent.d.ts","../node_modules/.pnpm/undici-types@6.21.0/node_modules/undici-types/mock-interceptor.d.ts","../node_modules/.pnpm/undici-types@6.21.0/node_modules/undici-types/mock-agent.d.ts","../node_modules/.pnpm/undici-types@6.21.0/node_modules/undici-types/mock-client.d.ts","../node_modules/.pnpm/undici-types@6.21.0/node_modules/undici-types/mock-pool.d.ts","../node_modules/.pnpm/undici-types@6.21.0/node_modules/undici-types/mock-errors.d.ts","../node_modules/.pnpm/undici-types@6.21.0/node_modules/undici-types/proxy-agent.d.ts","../node_modules/.pnpm/undici-types@6.21.0/node_modules/undici-types/env-http-proxy-agent.d.ts","../node_modules/.pnpm/undici-types@6.21.0/node_modules/undici-types/retry-handler.d.ts","../node_modules/.pnpm/undici-types@6.21.0/node_modules/undici-types/retry-agent.d.ts","../node_modules/.pnpm/undici-types@6.21.0/node_modules/undici-types/api.d.ts","../node_modules/.pnpm/undici-types@6.21.0/node_modules/undici-types/interceptors.d.ts","../node_modules/.pnpm/undici-types@6.21.0/node_modules/undici-types/util.d.ts","../node_modules/.pnpm/undici-types@6.21.0/node_modules/undici-types/cookies.d.ts","../node_modules/.pnpm/undici-types@6.21.0/node_modules/undici-types/patch.d.ts","../node_modules/.pnpm/undici-types@6.21.0/node_modules/undici-types/websocket.d.ts","../node_modules/.pnpm/undici-types@6.21.0/node_modules/undici-types/eventsource.d.ts","../node_modules/.pnpm/undici-types@6.21.0/node_modules/undici-types/filereader.d.ts","../node_modules/.pnpm/undici-types@6.21.0/node_modules/undici-types/diagnostics-channel.d.ts","../node_modules/.pnpm/undici-types@6.21.0/node_modules/undici-types/content-type.d.ts","../node_modules/.pnpm/undici-types@6.21.0/node_modules/undici-types/cache.d.ts","../node_modules/.pnpm/undici-types@6.21.0/node_modules/undici-types/index.d.ts","../node_modules/.pnpm/@types+node@20.19.25/node_modules/@types/node/web-globals/fetch.d.ts","../node_modules/.pnpm/@types+node@20.19.25/node_modules/@types/node/assert.d.ts","../node_modules/.pnpm/@types+node@20.19.25/node_modules/@types/node/assert/strict.d.ts","../node_modules/.pnpm/@types+node@20.19.25/node_modules/@types/node/async_hooks.d.ts","../node_modules/.pnpm/@types+node@20.19.25/node_modules/@types/node/buffer.d.ts","../node_modules/.pnpm/@types+node@20.19.25/node_modules/@types/node/child_process.d.ts","../node_modules/.pnpm/@types+node@20.19.25/node_modules/@types/node/cluster.d.ts","../node_modules/.pnpm/@types+node@20.19.25/node_modules/@types/node/console.d.ts","../node_modules/.pnpm/@types+node@20.19.25/node_modules/@types/node/constants.d.ts","../node_modules/.pnpm/@types+node@20.19.25/node_modules/@types/node/crypto.d.ts","../node_modules/.pnpm/@types+node@20.19.25/node_modules/@types/node/dgram.d.ts","../node_modules/.pnpm/@types+node@20.19.25/node_modules/@types/node/diagnostics_channel.d.ts","../node_modules/.pnpm/@types+node@20.19.25/node_modules/@types/node/dns.d.ts","../node_modules/.pnpm/@types+node@20.19.25/node_modules/@types/node/dns/promises.d.ts","../node_modules/.pnpm/@types+node@20.19.25/node_modules/@types/node/domain.d.ts","../node_modules/.pnpm/@types+node@20.19.25/node_modules/@types/node/events.d.ts","../node_modules/.pnpm/@types+node@20.19.25/node_modules/@types/node/fs.d.ts","../node_modules/.pnpm/@types+node@20.19.25/node_modules/@types/node/fs/promises.d.ts","../node_modules/.pnpm/@types+node@20.19.25/node_modules/@types/node/http.d.ts","../node_modules/.pnpm/@types+node@20.19.25/node_modules/@types/node/http2.d.ts","../node_modules/.pnpm/@types+node@20.19.25/node_modules/@types/node/https.d.ts","../node_modules/.pnpm/@types+node@20.19.25/node_modules/@types/node/inspector.generated.d.ts","../node_modules/.pnpm/@types+node@20.19.25/node_modules/@types/node/module.d.ts","../node_modules/.pnpm/@types+node@20.19.25/node_modules/@types/node/net.d.ts","../node_modules/.pnpm/@types+node@20.19.25/node_modules/@types/node/os.d.ts","../node_modules/.pnpm/@types+node@20.19.25/node_modules/@types/node/path.d.ts","../node_modules/.pnpm/@types+node@20.19.25/node_modules/@types/node/perf_hooks.d.ts","../node_modules/.pnpm/@types+node@20.19.25/node_modules/@types/node/process.d.ts","../node_modules/.pnpm/@types+node@20.19.25/node_modules/@types/node/punycode.d.ts","../node_modules/.pnpm/@types+node@20.19.25/node_modules/@types/node/querystring.d.ts","../node_modules/.pnpm/@types+node@20.19.25/node_modules/@types/node/readline.d.ts","../node_modules/.pnpm/@types+node@20.19.25/node_modules/@types/node/readline/promises.d.ts","../node_modules/.pnpm/@types+node@20.19.25/node_modules/@types/node/repl.d.ts","../node_modules/.pnpm/@types+node@20.19.25/node_modules/@types/node/sea.d.ts","../node_modules/.pnpm/@types+node@20.19.25/node_modules/@types/node/stream.d.ts","../node_modules/.pnpm/@types+node@20.19.25/node_modules/@types/node/stream/promises.d.ts","../node_modules/.pnpm/@types+node@20.19.25/node_modules/@types/node/stream/consumers.d.ts","../node_modules/.pnpm/@types+node@20.19.25/node_modules/@types/node/stream/web.d.ts","../node_modules/.pnpm/@types+node@20.19.25/node_modules/@types/node/string_decoder.d.ts","../node_modules/.pnpm/@types+node@20.19.25/node_modules/@types/node/test.d.ts","../node_modules/.pnpm/@types+node@20.19.25/node_modules/@types/node/timers.d.ts","../node_modules/.pnpm/@types+node@20.19.25/node_modules/@types/node/timers/promises.d.ts","../node_modules/.pnpm/@types+node@20.19.25/node_modules/@types/node/tls.d.ts","../node_modules/.pnpm/@types+node@20.19.25/node_modules/@types/node/trace_events.d.ts","../node_modules/.pnpm/@types+node@20.19.25/node_modules/@types/node/tty.d.ts","../node_modules/.pnpm/@types+node@20.19.25/node_modules/@types/node/url.d.ts","../node_modules/.pnpm/@types+node@20.19.25/node_modules/@types/node/util.d.ts","../node_modules/.pnpm/@types+node@20.19.25/node_modules/@types/node/v8.d.ts","../node_modules/.pnpm/@types+node@20.19.25/node_modules/@types/node/vm.d.ts","../node_modules/.pnpm/@types+node@20.19.25/node_modules/@types/node/wasi.d.ts","../node_modules/.pnpm/@types+node@20.19.25/node_modules/@types/node/worker_threads.d.ts","../node_modules/.pnpm/@types+node@20.19.25/node_modules/@types/node/zlib.d.ts","../node_modules/.pnpm/@types+node@20.19.25/node_modules/@types/node/index.d.ts","../node_modules/.pnpm/@types+estree@1.0.8/node_modules/@types/estree/index.d.ts","../node_modules/.pnpm/rollup@4.53.3/node_modules/rollup/dist/rollup.d.ts","../node_modules/.pnpm/rollup@4.53.3/node_modules/rollup/dist/parseast.d.ts","../node_modules/.pnpm/vite@5.4.21_@types+node@20.19.25_sass@1.94.1_terser@5.44.1/node_modules/vite/types/hmrpayload.d.ts","../node_modules/.pnpm/vite@5.4.21_@types+node@20.19.25_sass@1.94.1_terser@5.44.1/node_modules/vite/types/customevent.d.ts","../node_modules/.pnpm/vite@5.4.21_@types+node@20.19.25_sass@1.94.1_terser@5.44.1/node_modules/vite/types/hot.d.ts","../node_modules/.pnpm/vite@5.4.21_@types+node@20.19.25_sass@1.94.1_terser@5.44.1/node_modules/vite/dist/node/types.d-agj9qkwt.d.ts","../node_modules/.pnpm/esbuild@0.21.5/node_modules/esbuild/lib/main.d.ts","../node_modules/.pnpm/source-map-js@1.2.1/node_modules/source-map-js/source-map.d.ts","../node_modules/.pnpm/postcss@8.5.6/node_modules/postcss/lib/previous-map.d.ts","../node_modules/.pnpm/postcss@8.5.6/node_modules/postcss/lib/input.d.ts","../node_modules/.pnpm/postcss@8.5.6/node_modules/postcss/lib/css-syntax-error.d.ts","../node_modules/.pnpm/postcss@8.5.6/node_modules/postcss/lib/declaration.d.ts","../node_modules/.pnpm/postcss@8.5.6/node_modules/postcss/lib/root.d.ts","../node_modules/.pnpm/postcss@8.5.6/node_modules/postcss/lib/warning.d.ts","../node_modules/.pnpm/postcss@8.5.6/node_modules/postcss/lib/lazy-result.d.ts","../node_modules/.pnpm/postcss@8.5.6/node_modules/postcss/lib/no-work-result.d.ts","../node_modules/.pnpm/postcss@8.5.6/node_modules/postcss/lib/processor.d.ts","../node_modules/.pnpm/postcss@8.5.6/node_modules/postcss/lib/result.d.ts","../node_modules/.pnpm/postcss@8.5.6/node_modules/postcss/lib/document.d.ts","../node_modules/.pnpm/postcss@8.5.6/node_modules/postcss/lib/rule.d.ts","../node_modules/.pnpm/postcss@8.5.6/node_modules/postcss/lib/node.d.ts","../node_modules/.pnpm/postcss@8.5.6/node_modules/postcss/lib/comment.d.ts","../node_modules/.pnpm/postcss@8.5.6/node_modules/postcss/lib/container.d.ts","../node_modules/.pnpm/postcss@8.5.6/node_modules/postcss/lib/at-rule.d.ts","../node_modules/.pnpm/postcss@8.5.6/node_modules/postcss/lib/list.d.ts","../node_modules/.pnpm/postcss@8.5.6/node_modules/postcss/lib/postcss.d.ts","../node_modules/.pnpm/postcss@8.5.6/node_modules/postcss/lib/postcss.d.mts","../node_modules/.pnpm/vite@5.4.21_@types+node@20.19.25_sass@1.94.1_terser@5.44.1/node_modules/vite/dist/node/runtime.d.ts","../node_modules/.pnpm/vite@5.4.21_@types+node@20.19.25_sass@1.94.1_terser@5.44.1/node_modules/vite/types/importglob.d.ts","../node_modules/.pnpm/vite@5.4.21_@types+node@20.19.25_sass@1.94.1_terser@5.44.1/node_modules/vite/types/metadata.d.ts","../node_modules/.pnpm/vite@5.4.21_@types+node@20.19.25_sass@1.94.1_terser@5.44.1/node_modules/vite/dist/node/index.d.ts","../node_modules/.pnpm/@babel+types@7.28.5/node_modules/@babel/types/lib/index.d.ts","../node_modules/.pnpm/@vue+shared@3.5.24/node_modules/@vue/shared/dist/shared.d.ts","../node_modules/.pnpm/@babel+parser@7.28.5/node_modules/@babel/parser/typings/babel-parser.d.ts","../node_modules/.pnpm/@vue+compiler-core@3.5.24/node_modules/@vue/compiler-core/dist/compiler-core.d.ts","../node_modules/.pnpm/magic-string@0.30.21/node_modules/magic-string/dist/magic-string.es.d.mts","../node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/lib/typescript.d.ts","../node_modules/.pnpm/@vue+compiler-sfc@3.5.24/node_modules/@vue/compiler-sfc/dist/compiler-sfc.d.ts","../node_modules/.pnpm/vue@3.5.24_typescript@5.9.3/node_modules/vue/compiler-sfc/index.d.mts","../node_modules/.pnpm/@vitejs+plugin-vue@5.2.4_vi_e70672b9796244f49d066a536954e836/node_modules/@vitejs/plugin-vue/dist/index.d.mts","./vite.config.ts","../node_modules/.pnpm/@types+adm-zip@0.5.7/node_modules/@types/adm-zip/util.d.ts","../node_modules/.pnpm/@types+adm-zip@0.5.7/node_modules/@types/adm-zip/index.d.ts","../node_modules/.pnpm/@types+babel__generator@7.27.0/node_modules/@types/babel__generator/index.d.ts","../node_modules/.pnpm/@types+babel__template@7.4.4/node_modules/@types/babel__template/index.d.ts","../node_modules/.pnpm/@types+babel__traverse@7.28.0/node_modules/@types/babel__traverse/index.d.ts","../node_modules/.pnpm/@types+babel__core@7.20.5/node_modules/@types/babel__core/index.d.ts","../node_modules/.pnpm/@types+bcrypt@5.0.2/node_modules/@types/bcrypt/index.d.ts","../node_modules/.pnpm/@types+connect@3.4.38/node_modules/@types/connect/index.d.ts","../node_modules/.pnpm/@types+body-parser@1.19.6/node_modules/@types/body-parser/index.d.ts","../node_modules/.pnpm/@types+json-schema@7.0.15/node_modules/@types/json-schema/index.d.ts","../node_modules/.pnpm/@types+eslint@9.6.1/node_modules/@types/eslint/use-at-your-own-risk.d.ts","../node_modules/.pnpm/@types+eslint@9.6.1/node_modules/@types/eslint/index.d.ts","../node_modules/.pnpm/@types+eslint-scope@3.7.7/node_modules/@types/eslint-scope/index.d.ts","../node_modules/.pnpm/@types+event-emitter@0.3.5/node_modules/@types/event-emitter/index.d.ts","../node_modules/.pnpm/@types+send@1.2.1/node_modules/@types/send/index.d.ts","../node_modules/.pnpm/@types+qs@6.14.0/node_modules/@types/qs/index.d.ts","../node_modules/.pnpm/@types+range-parser@1.2.7/node_modules/@types/range-parser/index.d.ts","../node_modules/.pnpm/@types+express-serve-static-core@4.19.7/node_modules/@types/express-serve-static-core/index.d.ts","../node_modules/.pnpm/@types+http-errors@2.0.5/node_modules/@types/http-errors/index.d.ts","../node_modules/.pnpm/@types+mime@1.3.5/node_modules/@types/mime/index.d.ts","../node_modules/.pnpm/@types+send@0.17.6/node_modules/@types/send/index.d.ts","../node_modules/.pnpm/@types+serve-static@1.15.10/node_modules/@types/serve-static/index.d.ts","../node_modules/.pnpm/@types+express@4.17.25/node_modules/@types/express/index.d.ts","../node_modules/.pnpm/@types+graceful-fs@4.1.9/node_modules/@types/graceful-fs/index.d.ts","../node_modules/.pnpm/@types+istanbul-lib-coverage@2.0.6/node_modules/@types/istanbul-lib-coverage/index.d.ts","../node_modules/.pnpm/@types+istanbul-lib-report@3.0.3/node_modules/@types/istanbul-lib-report/index.d.ts","../node_modules/.pnpm/@types+istanbul-reports@3.0.4/node_modules/@types/istanbul-reports/index.d.ts","../node_modules/.pnpm/@jest+expect-utils@29.7.0/node_modules/@jest/expect-utils/build/index.d.ts","../node_modules/.pnpm/chalk@4.1.2/node_modules/chalk/index.d.ts","../node_modules/.pnpm/@sinclair+typebox@0.27.8/node_modules/@sinclair/typebox/typebox.d.ts","../node_modules/.pnpm/@jest+schemas@29.6.3/node_modules/@jest/schemas/build/index.d.ts","../node_modules/.pnpm/pretty-format@29.7.0/node_modules/pretty-format/build/index.d.ts","../node_modules/.pnpm/jest-diff@29.7.0/node_modules/jest-diff/build/index.d.ts","../node_modules/.pnpm/jest-matcher-utils@29.7.0/node_modules/jest-matcher-utils/build/index.d.ts","../node_modules/.pnpm/expect@29.7.0/node_modules/expect/build/index.d.ts","../node_modules/.pnpm/@types+jest@29.5.14/node_modules/@types/jest/index.d.ts","../node_modules/.pnpm/@types+jsonwebtoken@9.0.5/node_modules/@types/jsonwebtoken/index.d.ts","../node_modules/.pnpm/@types+ms@2.1.0/node_modules/@types/ms/index.d.ts","../node_modules/.pnpm/@types+multer@2.0.0/node_modules/@types/multer/index.d.ts","../node_modules/.pnpm/@types+passport@1.0.17/node_modules/@types/passport/index.d.ts","../node_modules/.pnpm/@types+jsonwebtoken@9.0.10/node_modules/@types/jsonwebtoken/index.d.ts","../node_modules/.pnpm/@types+passport-strategy@0.2.38/node_modules/@types/passport-strategy/index.d.ts","../node_modules/.pnpm/@types+passport-jwt@4.0.1/node_modules/@types/passport-jwt/index.d.ts","../node_modules/.pnpm/@types+passport-local@1.0.38/node_modules/@types/passport-local/index.d.ts","../node_modules/.pnpm/@types+semver@7.7.1/node_modules/@types/semver/functions/inc.d.ts","../node_modules/.pnpm/@types+semver@7.7.1/node_modules/@types/semver/classes/semver.d.ts","../node_modules/.pnpm/@types+semver@7.7.1/node_modules/@types/semver/functions/parse.d.ts","../node_modules/.pnpm/@types+semver@7.7.1/node_modules/@types/semver/functions/valid.d.ts","../node_modules/.pnpm/@types+semver@7.7.1/node_modules/@types/semver/functions/clean.d.ts","../node_modules/.pnpm/@types+semver@7.7.1/node_modules/@types/semver/functions/diff.d.ts","../node_modules/.pnpm/@types+semver@7.7.1/node_modules/@types/semver/functions/major.d.ts","../node_modules/.pnpm/@types+semver@7.7.1/node_modules/@types/semver/functions/minor.d.ts","../node_modules/.pnpm/@types+semver@7.7.1/node_modules/@types/semver/functions/patch.d.ts","../node_modules/.pnpm/@types+semver@7.7.1/node_modules/@types/semver/functions/prerelease.d.ts","../node_modules/.pnpm/@types+semver@7.7.1/node_modules/@types/semver/functions/compare.d.ts","../node_modules/.pnpm/@types+semver@7.7.1/node_modules/@types/semver/functions/rcompare.d.ts","../node_modules/.pnpm/@types+semver@7.7.1/node_modules/@types/semver/functions/compare-loose.d.ts","../node_modules/.pnpm/@types+semver@7.7.1/node_modules/@types/semver/functions/compare-build.d.ts","../node_modules/.pnpm/@types+semver@7.7.1/node_modules/@types/semver/functions/sort.d.ts","../node_modules/.pnpm/@types+semver@7.7.1/node_modules/@types/semver/functions/rsort.d.ts","../node_modules/.pnpm/@types+semver@7.7.1/node_modules/@types/semver/functions/gt.d.ts","../node_modules/.pnpm/@types+semver@7.7.1/node_modules/@types/semver/functions/lt.d.ts","../node_modules/.pnpm/@types+semver@7.7.1/node_modules/@types/semver/functions/eq.d.ts","../node_modules/.pnpm/@types+semver@7.7.1/node_modules/@types/semver/functions/neq.d.ts","../node_modules/.pnpm/@types+semver@7.7.1/node_modules/@types/semver/functions/gte.d.ts","../node_modules/.pnpm/@types+semver@7.7.1/node_modules/@types/semver/functions/lte.d.ts","../node_modules/.pnpm/@types+semver@7.7.1/node_modules/@types/semver/functions/cmp.d.ts","../node_modules/.pnpm/@types+semver@7.7.1/node_modules/@types/semver/functions/coerce.d.ts","../node_modules/.pnpm/@types+semver@7.7.1/node_modules/@types/semver/classes/comparator.d.ts","../node_modules/.pnpm/@types+semver@7.7.1/node_modules/@types/semver/classes/range.d.ts","../node_modules/.pnpm/@types+semver@7.7.1/node_modules/@types/semver/functions/satisfies.d.ts","../node_modules/.pnpm/@types+semver@7.7.1/node_modules/@types/semver/ranges/max-satisfying.d.ts","../node_modules/.pnpm/@types+semver@7.7.1/node_modules/@types/semver/ranges/min-satisfying.d.ts","../node_modules/.pnpm/@types+semver@7.7.1/node_modules/@types/semver/ranges/to-comparators.d.ts","../node_modules/.pnpm/@types+semver@7.7.1/node_modules/@types/semver/ranges/min-version.d.ts","../node_modules/.pnpm/@types+semver@7.7.1/node_modules/@types/semver/ranges/valid.d.ts","../node_modules/.pnpm/@types+semver@7.7.1/node_modules/@types/semver/ranges/outside.d.ts","../node_modules/.pnpm/@types+semver@7.7.1/node_modules/@types/semver/ranges/gtr.d.ts","../node_modules/.pnpm/@types+semver@7.7.1/node_modules/@types/semver/ranges/ltr.d.ts","../node_modules/.pnpm/@types+semver@7.7.1/node_modules/@types/semver/ranges/intersects.d.ts","../node_modules/.pnpm/@types+semver@7.7.1/node_modules/@types/semver/ranges/simplify.d.ts","../node_modules/.pnpm/@types+semver@7.7.1/node_modules/@types/semver/ranges/subset.d.ts","../node_modules/.pnpm/@types+semver@7.7.1/node_modules/@types/semver/internals/identifiers.d.ts","../node_modules/.pnpm/@types+semver@7.7.1/node_modules/@types/semver/index.d.ts","../node_modules/.pnpm/@types+stack-utils@2.0.3/node_modules/@types/stack-utils/index.d.ts","../node_modules/.pnpm/@types+uuid@10.0.0/node_modules/@types/uuid/index.d.ts","../node_modules/.pnpm/@types+validator@13.15.10/node_modules/@types/validator/lib/isboolean.d.ts","../node_modules/.pnpm/@types+validator@13.15.10/node_modules/@types/validator/lib/isemail.d.ts","../node_modules/.pnpm/@types+validator@13.15.10/node_modules/@types/validator/lib/isfqdn.d.ts","../node_modules/.pnpm/@types+validator@13.15.10/node_modules/@types/validator/lib/isiban.d.ts","../node_modules/.pnpm/@types+validator@13.15.10/node_modules/@types/validator/lib/isiso31661alpha2.d.ts","../node_modules/.pnpm/@types+validator@13.15.10/node_modules/@types/validator/lib/isiso4217.d.ts","../node_modules/.pnpm/@types+validator@13.15.10/node_modules/@types/validator/lib/isiso6391.d.ts","../node_modules/.pnpm/@types+validator@13.15.10/node_modules/@types/validator/lib/istaxid.d.ts","../node_modules/.pnpm/@types+validator@13.15.10/node_modules/@types/validator/lib/isurl.d.ts","../node_modules/.pnpm/@types+validator@13.15.10/node_modules/@types/validator/index.d.ts","../node_modules/.pnpm/@types+yargs-parser@21.0.3/node_modules/@types/yargs-parser/index.d.ts","../node_modules/.pnpm/@types+yargs@17.0.35/node_modules/@types/yargs/index.d.ts"],"fileIdsList":[[55,102,123,182,191],[55,102,183],[55,102],[55,102,222],[55,102,114,150,193],[55,102,183,185,195,196,197],[55,102,183,185],[55,102,150],[55,102,116,150,200],[55,102,116,150],[55,102,151,152,204],[55,102,151,152,202,203],[55,102,204],[55,102,113,116,150,207,208,209],[55,102,201,208,210,214],[55,102,114,150],[55,102,217],[55,102,218],[55,102,224,227],[55,102,107,150,230],[55,102,107,150],[55,102,132,215],[55,99,102],[55,101,102],[102],[55,102,107,135],[55,102,103,108,113,121,132,143],[55,102,103,104,113,121],[50,51,52,55,102],[55,102,105,144],[55,102,106,107,114,122],[55,102,107,132,140],[55,102,108,110,113,121],[55,101,102,109],[55,102,110,111],[55,102,112,113],[55,101,102,113],[55,102,113,114,115,132,143],[55,102,113,114,115,128,132,135],[55,102,110,113,116,121,132,143],[55,102,113,114,116,117,121,132,140,143],[55,102,116,118,132,140,143],[53,54,55,56,57,58,59,98,99,100,101,102,103,104,105,106,107,108,109,110,111,112,113,114,115,116,117,118,119,120,121,122,123,124,125,126,127,128,129,130,131,132,133,134,135,136,137,138,139,140,141,142,143,144,145,146,147,148,149],[55,102,113,119],[55,102,120,143,148],[55,102,110,113,121,132],[55,102,122],[55,102,123],[55,101,102,124],[55,99,100,101,102,103,104,105,106,107,108,109,110,111,112,113,114,115,116,117,118,119,120,121,122,123,124,125,126,127,128,129,130,131,132,133,134,135,136,137,138,139,140,141,142,143,144,145,146,147,148,149],[55,102,126],[55,102,127],[55,102,113,128,129],[55,102,128,130,144,146],[55,102,113,132,133,135],[55,102,134,135],[55,102,132,133],[55,102,135],[55,102,136],[55,99,102,132,137],[55,102,113,138,139],[55,102,138,139],[55,102,107,121,132,140],[55,102,141],[55,102,121,142],[55,102,116,127,143],[55,102,107,144],[55,102,132,145],[55,102,120,146],[55,102,147],[55,97,102],[55,97,102,113,115,124,132,135,143,146,148],[55,102,132,149],[55,102,233,234],[55,102,215,232,234],[55,102,215,232],[55,102,116,215],[55,102,238,276],[55,102,238,261,276],[55,102,237,276],[55,102,276],[55,102,238],[55,102,238,262,276],[55,102,237,238,239,240,241,242,243,244,245,246,247,248,249,250,251,252,253,254,255,256,257,258,259,260,261,262,263,264,265,266,267,268,269,270,271,272,273,274,275],[55,102,262,276],[55,102,114,132,150,212],[55,102,114,132,150],[55,102,116,150,211,213],[55,102,279,280,281,282,283,284,285,286,287],[55,102,289],[55,102,182,190],[55,102,183,184,185],[55,102,178,183,185,186,187,188],[55,102,220,226],[55,102,224],[55,102,221,225],[55,102,174],[55,102,172,174],[55,102,163,171,172,173,175,177],[55,102,161],[55,102,164,169,174,177],[55,102,160,177],[55,102,164,165,168,169,170,177],[55,102,164,165,166,168,169,177],[55,102,161,162,163,164,165,169,170,171,173,174,175,177],[55,102,177],[55,102,159,161,162,163,164,165,166,168,169,170,171,172,173,174,175,176],[55,102,159,177],[55,102,164,166,167,169,170,177],[55,102,168,177],[55,102,169,170,174,177],[55,102,162,172],[55,102,223],[55,102,152,181],[55,102,151,152],[55,69,73,102,143],[55,69,102,132,143],[55,64,102],[55,66,69,102,140,143],[55,102,121,140],[55,64,102,150],[55,66,69,102,121,143],[55,61,62,65,68,102,113,132,143],[55,69,76,102],[55,61,67,102],[55,69,90,91,102],[55,65,69,102,135,143,150],[55,90,102,150],[55,63,64,102,150],[55,69,102],[55,63,64,65,66,67,68,69,70,71,73,74,75,76,77,78,79,80,81,82,83,84,85,86,87,88,89,91,92,93,94,95,96,102],[55,69,84,102],[55,69,76,77,102],[55,67,69,77,78,102],[55,68,102],[55,61,64,69,102],[55,69,73,77,78,102],[55,73,102],[55,67,69,72,102,143],[55,61,66,69,76,102],[55,102,132],[55,64,69,90,102,148,150],[55,102,113,114,116,117,118,121,132,140,143,149,150,152,153,154,155,156,157,158,178,179,180,181],[55,102,154,155,156,157],[55,102,154,155,156],[55,102,154],[55,102,155],[55,102,152],[55,102,189]],"fileInfos":[{"version":"a7297ff837fcdf174a9524925966429eb8e5feecc2cc55cc06574e6b092c1eaa","impliedFormat":1},{"version":"c430d44666289dae81f30fa7b2edebf186ecc91a2d4c71266ea6ae76388792e1","affectsGlobalScope":true,"impliedFormat":1},{"version":"45b7ab580deca34ae9729e97c13cfd999df04416a79116c3bfb483804f85ded4","impliedFormat":1},{"version":"3facaf05f0c5fc569c5649dd359892c98a85557e3e0c847964caeb67076f4d75","impliedFormat":1},{"version":"e44bb8bbac7f10ecc786703fe0a6a4b952189f908707980ba8f3c8975a760962","impliedFormat":1},{"version":"5e1c4c362065a6b95ff952c0eab010f04dcd2c3494e813b493ecfd4fcb9fc0d8","impliedFormat":1},{"version":"68d73b4a11549f9c0b7d352d10e91e5dca8faa3322bfb77b661839c42b1ddec7","impliedFormat":1},{"version":"5efce4fc3c29ea84e8928f97adec086e3dc876365e0982cc8479a07954a3efd4","impliedFormat":1},{"version":"080941d9f9ff9307f7e27a83bcd888b7c8270716c39af943532438932ec1d0b9","affectsGlobalScope":true,"impliedFormat":1},{"version":"80e18897e5884b6723488d4f5652167e7bb5024f946743134ecc4aa4ee731f89","affectsGlobalScope":true,"impliedFormat":1},{"version":"cd034f499c6cdca722b60c04b5b1b78e058487a7085a8e0d6fb50809947ee573","affectsGlobalScope":true,"impliedFormat":1},{"version":"c57796738e7f83dbc4b8e65132f11a377649c00dd3eee333f672b8f0a6bea671","affectsGlobalScope":true,"impliedFormat":1},{"version":"dc2df20b1bcdc8c2d34af4926e2c3ab15ffe1160a63e58b7e09833f616efff44","affectsGlobalScope":true,"impliedFormat":1},{"version":"515d0b7b9bea2e31ea4ec968e9edd2c39d3eebf4a2d5cbd04e88639819ae3b71","affectsGlobalScope":true,"impliedFormat":1},{"version":"0559b1f683ac7505ae451f9a96ce4c3c92bdc71411651ca6ddb0e88baaaad6a3","affectsGlobalScope":true,"impliedFormat":1},{"version":"0dc1e7ceda9b8b9b455c3a2d67b0412feab00bd2f66656cd8850e8831b08b537","affectsGlobalScope":true,"impliedFormat":1},{"version":"ce691fb9e5c64efb9547083e4a34091bcbe5bdb41027e310ebba8f7d96a98671","affectsGlobalScope":true,"impliedFormat":1},{"version":"8d697a2a929a5fcb38b7a65594020fcef05ec1630804a33748829c5ff53640d0","affectsGlobalScope":true,"impliedFormat":1},{"version":"4ff2a353abf8a80ee399af572debb8faab2d33ad38c4b4474cff7f26e7653b8d","affectsGlobalScope":true,"impliedFormat":1},{"version":"fb0f136d372979348d59b3f5020b4cdb81b5504192b1cacff5d1fbba29378aa1","affectsGlobalScope":true,"impliedFormat":1},{"version":"d15bea3d62cbbdb9797079416b8ac375ae99162a7fba5de2c6c505446486ac0a","affectsGlobalScope":true,"impliedFormat":1},{"version":"68d18b664c9d32a7336a70235958b8997ebc1c3b8505f4f1ae2b7e7753b87618","affectsGlobalScope":true,"impliedFormat":1},{"version":"eb3d66c8327153d8fa7dd03f9c58d351107fe824c79e9b56b462935176cdf12a","affectsGlobalScope":true,"impliedFormat":1},{"version":"38f0219c9e23c915ef9790ab1d680440d95419ad264816fa15009a8851e79119","affectsGlobalScope":true,"impliedFormat":1},{"version":"69ab18c3b76cd9b1be3d188eaf8bba06112ebbe2f47f6c322b5105a6fbc45a2e","affectsGlobalScope":true,"impliedFormat":1},{"version":"a680117f487a4d2f30ea46f1b4b7f58bef1480456e18ba53ee85c2746eeca012","affectsGlobalScope":true,"impliedFormat":1},{"version":"2f11ff796926e0832f9ae148008138ad583bd181899ab7dd768a2666700b1893","affectsGlobalScope":true,"impliedFormat":1},{"version":"4de680d5bb41c17f7f68e0419412ca23c98d5749dcaaea1896172f06435891fc","affectsGlobalScope":true,"impliedFormat":1},{"version":"954296b30da6d508a104a3a0b5d96b76495c709785c1d11610908e63481ee667","affectsGlobalScope":true,"impliedFormat":1},{"version":"ac9538681b19688c8eae65811b329d3744af679e0bdfa5d842d0e32524c73e1c","affectsGlobalScope":true,"impliedFormat":1},{"version":"0a969edff4bd52585473d24995c5ef223f6652d6ef46193309b3921d65dd4376","affectsGlobalScope":true,"impliedFormat":1},{"version":"9e9fbd7030c440b33d021da145d3232984c8bb7916f277e8ffd3dc2e3eae2bdb","affectsGlobalScope":true,"impliedFormat":1},{"version":"811ec78f7fefcabbda4bfa93b3eb67d9ae166ef95f9bff989d964061cbf81a0c","affectsGlobalScope":true,"impliedFormat":1},{"version":"717937616a17072082152a2ef351cb51f98802fb4b2fdabd32399843875974ca","affectsGlobalScope":true,"impliedFormat":1},{"version":"d7e7d9b7b50e5f22c915b525acc5a49a7a6584cf8f62d0569e557c5cfc4b2ac2","affectsGlobalScope":true,"impliedFormat":1},{"version":"71c37f4c9543f31dfced6c7840e068c5a5aacb7b89111a4364b1d5276b852557","affectsGlobalScope":true,"impliedFormat":1},{"version":"576711e016cf4f1804676043e6a0a5414252560eb57de9faceee34d79798c850","affectsGlobalScope":true,"impliedFormat":1},{"version":"89c1b1281ba7b8a96efc676b11b264de7a8374c5ea1e6617f11880a13fc56dc6","affectsGlobalScope":true,"impliedFormat":1},{"version":"74f7fa2d027d5b33eb0471c8e82a6c87216223181ec31247c357a3e8e2fddc5b","affectsGlobalScope":true,"impliedFormat":1},{"version":"d6d7ae4d1f1f3772e2a3cde568ed08991a8ae34a080ff1151af28b7f798e22ca","affectsGlobalScope":true,"impliedFormat":1},{"version":"063600664504610fe3e99b717a1223f8b1900087fab0b4cad1496a114744f8df","affectsGlobalScope":true,"impliedFormat":1},{"version":"934019d7e3c81950f9a8426d093458b65d5aff2c7c1511233c0fd5b941e608ab","affectsGlobalScope":true,"impliedFormat":1},{"version":"52ada8e0b6e0482b728070b7639ee42e83a9b1c22d205992756fe020fd9f4a47","affectsGlobalScope":true,"impliedFormat":1},{"version":"3bdefe1bfd4d6dee0e26f928f93ccc128f1b64d5d501ff4a8cf3c6371200e5e6","affectsGlobalScope":true,"impliedFormat":1},{"version":"59fb2c069260b4ba00b5643b907ef5d5341b167e7d1dbf58dfd895658bda2867","affectsGlobalScope":true,"impliedFormat":1},{"version":"639e512c0dfc3fad96a84caad71b8834d66329a1f28dc95e3946c9b58176c73a","affectsGlobalScope":true,"impliedFormat":1},{"version":"368af93f74c9c932edd84c58883e736c9e3d53cec1fe24c0b0ff451f529ceab1","affectsGlobalScope":true,"impliedFormat":1},{"version":"8e7f8264d0fb4c5339605a15daadb037bf238c10b654bb3eee14208f860a32ea","affectsGlobalScope":true,"impliedFormat":1},{"version":"782dec38049b92d4e85c1585fbea5474a219c6984a35b004963b00beb1aab538","affectsGlobalScope":true,"impliedFormat":1},{"version":"70521b6ab0dcba37539e5303104f29b721bfb2940b2776da4cc818c07e1fefc1","affectsGlobalScope":true,"impliedFormat":1},{"version":"ab41ef1f2cdafb8df48be20cd969d875602483859dc194e9c97c8a576892c052","affectsGlobalScope":true,"impliedFormat":1},{"version":"d153a11543fd884b596587ccd97aebbeed950b26933ee000f94009f1ab142848","affectsGlobalScope":true,"impliedFormat":1},{"version":"21d819c173c0cf7cc3ce57c3276e77fd9a8a01d35a06ad87158781515c9a438a","impliedFormat":1},{"version":"98cffbf06d6bab333473c70a893770dbe990783904002c4f1a960447b4b53dca","affectsGlobalScope":true,"impliedFormat":1},{"version":"ba481bca06f37d3f2c137ce343c7d5937029b2468f8e26111f3c9d9963d6568d","affectsGlobalScope":true,"impliedFormat":1},{"version":"6d9ef24f9a22a88e3e9b3b3d8c40ab1ddb0853f1bfbd5c843c37800138437b61","affectsGlobalScope":true,"impliedFormat":1},{"version":"1db0b7dca579049ca4193d034d835f6bfe73096c73663e5ef9a0b5779939f3d0","affectsGlobalScope":true,"impliedFormat":1},{"version":"9798340ffb0d067d69b1ae5b32faa17ab31b82466a3fc00d8f2f2df0c8554aaa","affectsGlobalScope":true,"impliedFormat":1},{"version":"f26b11d8d8e4b8028f1c7d618b22274c892e4b0ef5b3678a8ccbad85419aef43","affectsGlobalScope":true,"impliedFormat":1},{"version":"8e9c23ba78aabc2e0a27033f18737a6df754067731e69dc5f52823957d60a4b6","impliedFormat":1},{"version":"5929864ce17fba74232584d90cb721a89b7ad277220627cc97054ba15a98ea8f","impliedFormat":1},{"version":"763fe0f42b3d79b440a9b6e51e9ba3f3f91352469c1e4b3b67bfa4ff6352f3f4","impliedFormat":1},{"version":"25c8056edf4314820382a5fdb4bb7816999acdcb929c8f75e3f39473b87e85bc","impliedFormat":1},{"version":"c464d66b20788266e5353b48dc4aa6bc0dc4a707276df1e7152ab0c9ae21fad8","impliedFormat":1},{"version":"78d0d27c130d35c60b5e5566c9f1e5be77caf39804636bc1a40133919a949f21","impliedFormat":1},{"version":"c6fd2c5a395f2432786c9cb8deb870b9b0e8ff7e22c029954fabdd692bff6195","impliedFormat":1},{"version":"1d6e127068ea8e104a912e42fc0a110e2aa5a66a356a917a163e8cf9a65e4a75","impliedFormat":1},{"version":"5ded6427296cdf3b9542de4471d2aa8d3983671d4cac0f4bf9c637208d1ced43","impliedFormat":1},{"version":"7f182617db458e98fc18dfb272d40aa2fff3a353c44a89b2c0ccb3937709bfb5","impliedFormat":1},{"version":"cadc8aced301244057c4e7e73fbcae534b0f5b12a37b150d80e5a45aa4bebcbd","impliedFormat":1},{"version":"385aab901643aa54e1c36f5ef3107913b10d1b5bb8cbcd933d4263b80a0d7f20","impliedFormat":1},{"version":"9670d44354bab9d9982eca21945686b5c24a3f893db73c0dae0fd74217a4c219","impliedFormat":1},{"version":"0b8a9268adaf4da35e7fa830c8981cfa22adbbe5b3f6f5ab91f6658899e657a7","impliedFormat":1},{"version":"11396ed8a44c02ab9798b7dca436009f866e8dae3c9c25e8c1fbc396880bf1bb","impliedFormat":1},{"version":"ba7bc87d01492633cb5a0e5da8a4a42a1c86270e7b3d2dea5d156828a84e4882","impliedFormat":1},{"version":"4893a895ea92c85345017a04ed427cbd6a1710453338df26881a6019432febdd","impliedFormat":1},{"version":"c21dc52e277bcfc75fac0436ccb75c204f9e1b3fa5e12729670910639f27343e","impliedFormat":1},{"version":"13f6f39e12b1518c6650bbb220c8985999020fe0f21d818e28f512b7771d00f9","impliedFormat":1},{"version":"9b5369969f6e7175740bf51223112ff209f94ba43ecd3bb09eefff9fd675624a","impliedFormat":1},{"version":"4fe9e626e7164748e8769bbf74b538e09607f07ed17c2f20af8d680ee49fc1da","impliedFormat":1},{"version":"24515859bc0b836719105bb6cc3d68255042a9f02a6022b3187948b204946bd2","impliedFormat":1},{"version":"ea0148f897b45a76544ae179784c95af1bd6721b8610af9ffa467a518a086a43","impliedFormat":1},{"version":"24c6a117721e606c9984335f71711877293a9651e44f59f3d21c1ea0856f9cc9","impliedFormat":1},{"version":"dd3273ead9fbde62a72949c97dbec2247ea08e0c6952e701a483d74ef92d6a17","impliedFormat":1},{"version":"405822be75ad3e4d162e07439bac80c6bcc6dbae1929e179cf467ec0b9ee4e2e","impliedFormat":1},{"version":"0db18c6e78ea846316c012478888f33c11ffadab9efd1cc8bcc12daded7a60b6","impliedFormat":1},{"version":"e61be3f894b41b7baa1fbd6a66893f2579bfad01d208b4ff61daef21493ef0a8","impliedFormat":1},{"version":"bd0532fd6556073727d28da0edfd1736417a3f9f394877b6d5ef6ad88fba1d1a","impliedFormat":1},{"version":"89167d696a849fce5ca508032aabfe901c0868f833a8625d5a9c6e861ef935d2","impliedFormat":1},{"version":"615ba88d0128ed16bf83ef8ccbb6aff05c3ee2db1cc0f89ab50a4939bfc1943f","impliedFormat":1},{"version":"a4d551dbf8746780194d550c88f26cf937caf8d56f102969a110cfaed4b06656","impliedFormat":1},{"version":"8bd86b8e8f6a6aa6c49b71e14c4ffe1211a0e97c80f08d2c8cc98838006e4b88","impliedFormat":1},{"version":"317e63deeb21ac07f3992f5b50cdca8338f10acd4fbb7257ebf56735bf52ab00","impliedFormat":1},{"version":"4732aec92b20fb28c5fe9ad99521fb59974289ed1e45aecb282616202184064f","impliedFormat":1},{"version":"2e85db9e6fd73cfa3d7f28e0ab6b55417ea18931423bd47b409a96e4a169e8e6","impliedFormat":1},{"version":"c46e079fe54c76f95c67fb89081b3e399da2c7d109e7dca8e4b58d83e332e605","impliedFormat":1},{"version":"bf67d53d168abc1298888693338cb82854bdb2e69ef83f8a0092093c2d562107","impliedFormat":1},{"version":"2cbe0621042e2a68c7cbce5dfed3906a1862a16a7d496010636cdbdb91341c0f","affectsGlobalScope":true,"impliedFormat":1},{"version":"e2677634fe27e87348825bb041651e22d50a613e2fdf6a4a3ade971d71bac37e","impliedFormat":1},{"version":"7394959e5a741b185456e1ef5d64599c36c60a323207450991e7a42e08911419","impliedFormat":1},{"version":"8c0bcd6c6b67b4b503c11e91a1fb91522ed585900eab2ab1f61bba7d7caa9d6f","impliedFormat":1},{"version":"8cd19276b6590b3ebbeeb030ac271871b9ed0afc3074ac88a94ed2449174b776","affectsGlobalScope":true,"impliedFormat":1},{"version":"696eb8d28f5949b87d894b26dc97318ef944c794a9a4e4f62360cd1d1958014b","impliedFormat":1},{"version":"3f8fa3061bd7402970b399300880d55257953ee6d3cd408722cb9ac20126460c","impliedFormat":1},{"version":"35ec8b6760fd7138bbf5809b84551e31028fb2ba7b6dc91d95d098bf212ca8b4","affectsGlobalScope":true,"impliedFormat":1},{"version":"5524481e56c48ff486f42926778c0a3cce1cc85dc46683b92b1271865bcf015a","impliedFormat":1},{"version":"68bd56c92c2bd7d2339457eb84d63e7de3bd56a69b25f3576e1568d21a162398","affectsGlobalScope":true,"impliedFormat":1},{"version":"3e93b123f7c2944969d291b35fed2af79a6e9e27fdd5faa99748a51c07c02d28","impliedFormat":1},{"version":"9d19808c8c291a9010a6c788e8532a2da70f811adb431c97520803e0ec649991","impliedFormat":1},{"version":"87aad3dd9752067dc875cfaa466fc44246451c0c560b820796bdd528e29bef40","impliedFormat":1},{"version":"4aacb0dd020eeaef65426153686cc639a78ec2885dc72ad220be1d25f1a439df","impliedFormat":1},{"version":"f0bd7e6d931657b59605c44112eaf8b980ba7f957a5051ed21cb93d978cf2f45","impliedFormat":1},{"version":"8db0ae9cb14d9955b14c214f34dae1b9ef2baee2fe4ce794a4cd3ac2531e3255","affectsGlobalScope":true,"impliedFormat":1},{"version":"15fc6f7512c86810273af28f224251a5a879e4261b4d4c7e532abfbfc3983134","impliedFormat":1},{"version":"58adba1a8ab2d10b54dc1dced4e41f4e7c9772cbbac40939c0dc8ce2cdb1d442","impliedFormat":1},{"version":"2fd4c143eff88dabb57701e6a40e02a4dbc36d5eb1362e7964d32028056a782b","impliedFormat":1},{"version":"714435130b9015fae551788df2a88038471a5a11eb471f27c4ede86552842bc9","impliedFormat":1},{"version":"855cd5f7eb396f5f1ab1bc0f8580339bff77b68a770f84c6b254e319bbfd1ac7","impliedFormat":1},{"version":"5650cf3dace09e7c25d384e3e6b818b938f68f4e8de96f52d9c5a1b3db068e86","impliedFormat":1},{"version":"1354ca5c38bd3fd3836a68e0f7c9f91f172582ba30ab15bb8c075891b91502b7","affectsGlobalScope":true,"impliedFormat":1},{"version":"27fdb0da0daf3b337c5530c5f266efe046a6ceb606e395b346974e4360c36419","impliedFormat":1},{"version":"2d2fcaab481b31a5882065c7951255703ddbe1c0e507af56ea42d79ac3911201","impliedFormat":1},{"version":"afbe24ab0d74694372baa632ecb28bb375be53f3be53f9b07ecd7fc994907de5","impliedFormat":1},{"version":"ca867399f7db82df981d6915bcbb2d81131d7d1ef683bc782b59f71dda59bc85","affectsGlobalScope":true,"impliedFormat":1},{"version":"00877fef624f3171c2e44944fb63a55e2a9f9120d7c8b5eb4181c263c9a077cf","affectsGlobalScope":true,"impliedFormat":1},{"version":"9e043a1bc8fbf2a255bccf9bf27e0f1caf916c3b0518ea34aa72357c0afd42ec","impliedFormat":1},{"version":"b4f70ec656a11d570e1a9edce07d118cd58d9760239e2ece99306ee9dfe61d02","impliedFormat":1},{"version":"3bc2f1e2c95c04048212c569ed38e338873f6a8593930cf5a7ef24ffb38fc3b6","impliedFormat":1},{"version":"6e70e9570e98aae2b825b533aa6292b6abd542e8d9f6e9475e88e1d7ba17c866","impliedFormat":1},{"version":"f9d9d753d430ed050dc1bf2667a1bab711ccbb1c1507183d794cc195a5b085cc","impliedFormat":1},{"version":"9eece5e586312581ccd106d4853e861aaaa1a39f8e3ea672b8c3847eedd12f6e","impliedFormat":1},{"version":"47ab634529c5955b6ad793474ae188fce3e6163e3a3fb5edd7e0e48f14435333","impliedFormat":1},{"version":"37ba7b45141a45ce6e80e66f2a96c8a5ab1bcef0fc2d0f56bb58df96ec67e972","impliedFormat":1},{"version":"45650f47bfb376c8a8ed39d4bcda5902ab899a3150029684ee4c10676d9fbaee","impliedFormat":1},{"version":"0225ecb9ed86bdb7a2c7fd01f1556906902929377b44483dc4b83e03b3ef227d","affectsGlobalScope":true,"impliedFormat":1},{"version":"74cf591a0f63db318651e0e04cb55f8791385f86e987a67fd4d2eaab8191f730","impliedFormat":1},{"version":"5eab9b3dc9b34f185417342436ec3f106898da5f4801992d8ff38ab3aff346b5","impliedFormat":1},{"version":"12ed4559eba17cd977aa0db658d25c4047067444b51acfdcbf38470630642b23","affectsGlobalScope":true,"impliedFormat":1},{"version":"f3ffabc95802521e1e4bcba4c88d8615176dc6e09111d920c7a213bdda6e1d65","impliedFormat":1},{"version":"f9ab232778f2842ffd6955f88b1049982fa2ecb764d129ee4893cbc290f41977","impliedFormat":1},{"version":"ae56f65caf3be91108707bd8dfbccc2a57a91feb5daabf7165a06a945545ed26","impliedFormat":1},{"version":"a136d5de521da20f31631a0a96bf712370779d1c05b7015d7019a9b2a0446ca9","impliedFormat":1},{"version":"c3b41e74b9a84b88b1dca61ec39eee25c0dbc8e7d519ba11bb070918cfacf656","affectsGlobalScope":true,"impliedFormat":1},{"version":"4737a9dc24d0e68b734e6cfbcea0c15a2cfafeb493485e27905f7856988c6b29","affectsGlobalScope":true,"impliedFormat":1},{"version":"36d8d3e7506b631c9582c251a2c0b8a28855af3f76719b12b534c6edf952748d","impliedFormat":1},{"version":"1ca69210cc42729e7ca97d3a9ad48f2e9cb0042bada4075b588ae5387debd318","impliedFormat":1},{"version":"f5ebe66baaf7c552cfa59d75f2bfba679f329204847db3cec385acda245e574e","impliedFormat":1},{"version":"ed59add13139f84da271cafd32e2171876b0a0af2f798d0c663e8eeb867732cf","affectsGlobalScope":true,"impliedFormat":1},{"version":"05db535df8bdc30d9116fe754a3473d1b6479afbc14ae8eb18b605c62677d518","impliedFormat":1},{"version":"b1810689b76fd473bd12cc9ee219f8e62f54a7d08019a235d07424afbf074d25","impliedFormat":1},{"version":"151ff381ef9ff8da2da9b9663ebf657eac35c4c9a19183420c05728f31a6761d","impliedFormat":1},{"version":"6cd8f2410e4cf6d7870f018b38dcf1ac4771f06b363b5d71831d924cda3c488d","affectsGlobalScope":true,"impliedFormat":1},{"version":"a660aa95476042d3fdcc1343cf6bb8fdf24772d31712b1db321c5a4dcc325434","impliedFormat":1},{"version":"282f98006ed7fa9bb2cd9bdbe2524595cfc4bcd58a0bb3232e4519f2138df811","impliedFormat":1},{"version":"6222e987b58abfe92597e1273ad7233626285bc2d78409d4a7b113d81a83496b","impliedFormat":1},{"version":"cbe726263ae9a7bf32352380f7e8ab66ee25b3457137e316929269c19e18a2be","impliedFormat":1},{"version":"8b96046bf5fb0a815cba6b0880d9f97b7f3a93cf187e8dcfe8e2792e97f38f87","impliedFormat":99},{"version":"bacf2c84cf448b2cd02c717ad46c3d7fd530e0c91282888c923ad64810a4d511","affectsGlobalScope":true,"impliedFormat":1},{"version":"402e5c534fb2b85fa771170595db3ac0dd532112c8fa44fc23f233bc6967488b","impliedFormat":1},{"version":"8885cf05f3e2abf117590bbb951dcf6359e3e5ac462af1c901cfd24c6a6472e2","impliedFormat":1},{"version":"333caa2bfff7f06017f114de738050dd99a765c7eb16571c6d25a38c0d5365dc","impliedFormat":1},{"version":"e61df3640a38d535fd4bc9f4a53aef17c296b58dc4b6394fd576b808dd2fe5e6","impliedFormat":1},{"version":"459920181700cec8cbdf2a5faca127f3f17fd8dd9d9e577ed3f5f3af5d12a2e4","impliedFormat":1},{"version":"4719c209b9c00b579553859407a7e5dcfaa1c472994bd62aa5dd3cc0757eb077","impliedFormat":1},{"version":"7ec359bbc29b69d4063fe7dad0baaf35f1856f914db16b3f4f6e3e1bca4099fa","impliedFormat":1},{"version":"70790a7f0040993ca66ab8a07a059a0f8256e7bb57d968ae945f696cbff4ac7a","impliedFormat":1},{"version":"d1b9a81e99a0050ca7f2d98d7eedc6cda768f0eb9fa90b602e7107433e64c04c","impliedFormat":1},{"version":"a022503e75d6953d0e82c2c564508a5c7f8556fad5d7f971372d2d40479e4034","impliedFormat":1},{"version":"b215c4f0096f108020f666ffcc1f072c81e9f2f95464e894a5d5f34c5ea2a8b1","impliedFormat":1},{"version":"644491cde678bd462bb922c1d0cfab8f17d626b195ccb7f008612dc31f445d2d","impliedFormat":1},{"version":"dfe54dab1fa4961a6bcfba68c4ca955f8b5bbeb5f2ab3c915aa7adaa2eabc03a","impliedFormat":1},{"version":"1251d53755b03cde02466064260bb88fd83c30006a46395b7d9167340bc59b73","impliedFormat":1},{"version":"47865c5e695a382a916b1eedda1b6523145426e48a2eae4647e96b3b5e52024f","impliedFormat":1},{"version":"4cdf27e29feae6c7826cdd5c91751cc35559125e8304f9e7aed8faef97dcf572","impliedFormat":1},{"version":"331b8f71bfae1df25d564f5ea9ee65a0d847c4a94baa45925b6f38c55c7039bf","impliedFormat":1},{"version":"2a771d907aebf9391ac1f50e4ad37952943515eeea0dcc7e78aa08f508294668","impliedFormat":1},{"version":"0146fd6262c3fd3da51cb0254bb6b9a4e42931eb2f56329edd4c199cb9aaf804","impliedFormat":1},{"version":"183f480885db5caa5a8acb833c2be04f98056bdcc5fb29e969ff86e07efe57ab","impliedFormat":99},{"version":"82e687ebd99518bc63ea04b0c3810fb6e50aa6942decd0ca6f7a56d9b9a212a6","impliedFormat":99},{"version":"7f698624bbbb060ece7c0e51b7236520ebada74b747d7523c7df376453ed6fea","impliedFormat":1},{"version":"8f07f2b6514744ac96e51d7cb8518c0f4de319471237ea10cf688b8d0e9d0225","impliedFormat":1},{"version":"257b83faa134d971c738a6b9e4c47e59bb7b23274719d92197580dd662bfafc3","impliedFormat":99},{"version":"c2c2a861a338244d7dd700d0c52a78916b4bb75b98fc8ca5e7c501899fc03796","impliedFormat":1},{"version":"f468b74459f1ad4473b36a36d49f2b255f3c6b5d536c81239c2b2971df089eaf","impliedFormat":1},{"version":"adb467429462e3891de5bb4a82a4189b92005d61c7f9367c089baf03997c104e","impliedFormat":1},{"version":"7f108fc2f0dd96e1ce5e6942c555538afc4d836d4e22b60cdbe034caaac7b521","impliedFormat":1},{"version":"2be2227c3810dfd84e46674fd33b8d09a4a28ad9cb633ed536effd411665ea1e","impliedFormat":99},{"version":"e134052a6b1ded61693b4037f615dc72f14e2881e79c1ddbff6c514c8a516b05","impliedFormat":1},{"version":"83eeb5fc6bc433785dec98525eb003a02134024a8630134ecc67404d0075c26e","impliedFormat":1},{"version":"3feec212c0aeb91e5a6e62caaf9f128954590210f8c302910ea377c088f6b61a","impliedFormat":99},{"version":"bbdfaf7d9b20534c5df1e1b937a20f17ca049d603a2afe072983bf7aff2279f5","impliedFormat":99},{"version":"1f26c35ddbf1f120a0bf7ab58a35f8aff8cfba44d98a24db5a7379bbfcb384d0","signature":"f1a1b21a223c18a29308ebff0b002317e4bb8aa5e350164f8c8c3b8bde33a535"},{"version":"9063bd95bf4fe06fe71b4f1abd4dce7d41b684d9036ff4afae6e079eb252b19e","impliedFormat":1},{"version":"bb0c5cb27578678fe7a8479404efd988ceab67ff93238baf16cef721610658f7","impliedFormat":1},{"version":"b6d03c9cfe2cf0ba4c673c209fcd7c46c815b2619fd2aad59fc4229aaef2ed43","impliedFormat":1},{"version":"670a76db379b27c8ff42f1ba927828a22862e2ab0b0908e38b671f0e912cc5ed","impliedFormat":1},{"version":"13b77ab19ef7aadd86a1e54f2f08ea23a6d74e102909e3c00d31f231ed040f62","impliedFormat":1},{"version":"069bebfee29864e3955378107e243508b163e77ab10de6a5ee03ae06939f0bb9","impliedFormat":1},{"version":"160b24efb5a868df9c54f337656b4ef55fcbe0548fe15408e1c0630ec559c559","impliedFormat":1},{"version":"104c67f0da1bdf0d94865419247e20eded83ce7f9911a1aa75fc675c077ca66e","impliedFormat":1},{"version":"cc0d0b339f31ce0ab3b7a5b714d8e578ce698f1e13d7f8c60bfb766baeb1d35c","impliedFormat":1},{"version":"f3d8c757e148ad968f0d98697987db363070abada5f503da3c06aefd9d4248c1","impliedFormat":1},{"version":"a4a39b5714adfcadd3bbea6698ca2e942606d833bde62ad5fb6ec55f5e438ff8","impliedFormat":1},{"version":"bbc1d029093135d7d9bfa4b38cbf8761db505026cc458b5e9c8b74f4000e5e75","impliedFormat":1},{"version":"1f68ab0e055994eb337b67aa87d2a15e0200951e9664959b3866ee6f6b11a0fe","impliedFormat":1},{"version":"2dc1bcd6a132924a89f965b60edd5b5333aaa7eb28ba5af44a0a0a328756e9ad","impliedFormat":1},{"version":"d34aa8df2d0b18fb56b1d772ff9b3c7aea7256cf0d692f969be6e1d27b74d660","impliedFormat":1},{"version":"baac9896d29bcc55391d769e408ff400d61273d832dd500f21de766205255acb","impliedFormat":1},{"version":"2f5747b1508ccf83fad0c251ba1e5da2f5a30b78b09ffa1cfaf633045160afed","impliedFormat":1},{"version":"94ee9ee71018d54902c3fe6730090a8a421dcad95fc372d9b69a6d5351194885","affectsGlobalScope":true,"impliedFormat":1},{"version":"b71c603a539078a5e3a039b20f2b0a0d1708967530cf97dec8850a9ca45baa2b","impliedFormat":1},{"version":"d3f2d715f57df3f04bf7b16dde01dec10366f64fce44503c92b8f78f614c1769","impliedFormat":1},{"version":"cb90077223cc1365fa21ef0911a1f9b8f2f878943523d97350dc557973ca3823","impliedFormat":1},{"version":"18f1541b81b80d806120a3489af683edfb811deb91aeca19735d9bb2613e6311","impliedFormat":1},{"version":"232f118ae64ab84dcd26ddb60eaed5a6e44302d36249abf05e9e3fc2cbb701a2","impliedFormat":1},{"version":"afe73051ff6a03a9565cbd8ebb0e956ee3df5e913ad5c1ded64218aabfa3dcb5","impliedFormat":1},{"version":"035a5df183489c2e22f3cf59fc1ed2b043d27f357eecc0eb8d8e840059d44245","impliedFormat":1},{"version":"a4809f4d92317535e6b22b01019437030077a76fec1d93b9881c9ed4738fcc54","impliedFormat":1},{"version":"5f53fa0bd22096d2a78533f94e02c899143b8f0f9891a46965294ee8b91a9434","impliedFormat":1},{"version":"cdcc132f207d097d7d3aa75615ab9a2e71d6a478162dde8b67f88ea19f3e54de","impliedFormat":1},{"version":"0d14fa22c41fdc7277e6f71473b20ebc07f40f00e38875142335d5b63cdfc9d2","impliedFormat":1},{"version":"c085e9aa62d1ae1375794c1fb927a445fa105fed891a7e24edbb1c3300f7384a","impliedFormat":1},{"version":"f315e1e65a1f80992f0509e84e4ae2df15ecd9ef73df975f7c98813b71e4c8da","impliedFormat":1},{"version":"5b9586e9b0b6322e5bfbd2c29bd3b8e21ab9d871f82346cb71020e3d84bae73e","impliedFormat":1},{"version":"3e70a7e67c2cb16f8cd49097360c0309fe9d1e3210ff9222e9dac1f8df9d4fb6","impliedFormat":1},{"version":"ab68d2a3e3e8767c3fba8f80de099a1cfc18c0de79e42cb02ae66e22dfe14a66","impliedFormat":1},{"version":"d96cc6598148bf1a98fb2e8dcf01c63a4b3558bdaec6ef35e087fd0562eb40ec","impliedFormat":1},{"version":"f8db4fea512ab759b2223b90ecbbe7dae919c02f8ce95ec03f7fb1cf757cfbeb","affectsGlobalScope":true,"impliedFormat":1},{"version":"0bf811dcbddc95e2551f704cfd2afc267bf619f8b8f2b7bdbb94df96ec3cbfe3","impliedFormat":1},{"version":"fb893a0dfc3c9fb0f9ca93d0648694dd95f33cbad2c0f2c629f842981dfd4e2e","impliedFormat":1},{"version":"58564964bef3ffbd810241a8bd1c3a54347dd8adf04e1077ba49051009d3007d","affectsGlobalScope":true,"impliedFormat":1},{"version":"ff155930718467b27e379e4a195e4607ce277f805cad9d2fa5f4fd5dec224df6","affectsGlobalScope":true,"impliedFormat":1},{"version":"95da3c365e3d45709ad6e0b4daa5cdaf05e9076ba3c201e8f8081dd282c02f57","impliedFormat":1},{"version":"03c92769f389dbd9e45232f7eb01c3e0f482b62555aaf2029dcbf380d5cee9e4","impliedFormat":1},{"version":"32d7f70fd3498bc76a46dab8b03af4215f445f490f8e213c80cf06b636a4e413","impliedFormat":1},{"version":"17668c1aab598920796050ee5a00d961ede5e92595f6ac8908a975ed75a537e5","impliedFormat":1},{"version":"ce6a3f09b8db73a7e9701aca91a04b4fabaf77436dd35b24482f9ee816016b17","impliedFormat":1},{"version":"20e086e5b64fdd52396de67761cc0e94693494deadb731264aac122adf08de3f","impliedFormat":1},{"version":"6e78f75403b3ec65efb41c70d392aeda94360f11cedc9fb2c039c9ea23b30962","impliedFormat":1},{"version":"c863198dae89420f3c552b5a03da6ed6d0acfa3807a64772b895db624b0de707","impliedFormat":1},{"version":"8b03a5e327d7db67112ebbc93b4f744133eda2c1743dbb0a990c61a8007823ef","impliedFormat":1},{"version":"42fad1f540271e35ca37cecda12c4ce2eef27f0f5cf0f8dd761d723c744d3159","impliedFormat":1},{"version":"ff3743a5de32bee10906aff63d1de726f6a7fd6ee2da4b8229054dfa69de2c34","impliedFormat":1},{"version":"83acd370f7f84f203e71ebba33ba61b7f1291ca027d7f9a662c6307d74e4ac22","impliedFormat":1},{"version":"1445cec898f90bdd18b2949b9590b3c012f5b7e1804e6e329fb0fe053946d5ec","impliedFormat":1},{"version":"0e5318ec2275d8da858b541920d9306650ae6ac8012f0e872fe66eb50321a669","impliedFormat":1},{"version":"cf530297c3fb3a92ec9591dd4fa229d58b5981e45fe6702a0bd2bea53a5e59be","impliedFormat":1},{"version":"c1f6f7d08d42148ddfe164d36d7aba91f467dbcb3caa715966ff95f55048b3a4","impliedFormat":1},{"version":"eefd2bbc8edb14c3bd1246794e5c070a80f9b8f3730bd42efb80df3cc50b9039","impliedFormat":1},{"version":"0c1ee27b8f6a00097c2d6d91a21ee4d096ab52c1e28350f6362542b55380059a","impliedFormat":1},{"version":"7677d5b0db9e020d3017720f853ba18f415219fb3a9597343b1b1012cfd699f7","impliedFormat":1},{"version":"bc1c6bc119c1784b1a2be6d9c47addec0d83ef0d52c8fbe1f14a51b4dfffc675","impliedFormat":1},{"version":"52cf2ce99c2a23de70225e252e9822a22b4e0adb82643ab0b710858810e00bf1","impliedFormat":1},{"version":"770625067bb27a20b9826255a8d47b6b5b0a2d3dfcbd21f89904c731f671ba77","impliedFormat":1},{"version":"d1ed6765f4d7906a05968fb5cd6d1db8afa14dbe512a4884e8ea5c0f5e142c80","impliedFormat":1},{"version":"799c0f1b07c092626cf1efd71d459997635911bb5f7fc1196efe449bba87e965","impliedFormat":1},{"version":"2a184e4462b9914a30b1b5c41cf80c6d3428f17b20d3afb711fff3f0644001fd","impliedFormat":1},{"version":"9eabde32a3aa5d80de34af2c2206cdc3ee094c6504a8d0c2d6d20c7c179503cc","impliedFormat":1},{"version":"397c8051b6cfcb48aa22656f0faca2553c5f56187262135162ee79d2b2f6c966","impliedFormat":1},{"version":"a8ead142e0c87dcd5dc130eba1f8eeed506b08952d905c47621dc2f583b1bff9","impliedFormat":1},{"version":"a02f10ea5f73130efca046429254a4e3c06b5475baecc8f7b99a0014731be8b3","impliedFormat":1},{"version":"c2576a4083232b0e2d9bd06875dd43d371dee2e090325a9eac0133fd5650c1cb","impliedFormat":1},{"version":"4c9a0564bb317349de6a24eb4efea8bb79898fa72ad63a1809165f5bd42970dd","impliedFormat":1},{"version":"f40ac11d8859092d20f953aae14ba967282c3bb056431a37fced1866ec7a2681","impliedFormat":1},{"version":"cc11e9e79d4746cc59e0e17473a59d6f104692fd0eeea1bdb2e206eabed83b03","impliedFormat":1},{"version":"b444a410d34fb5e98aa5ee2b381362044f4884652e8bc8a11c8fe14bbd85518e","impliedFormat":1},{"version":"c35808c1f5e16d2c571aa65067e3cb95afeff843b259ecfa2fc107a9519b5392","impliedFormat":1},{"version":"14d5dc055143e941c8743c6a21fa459f961cbc3deedf1bfe47b11587ca4b3ef5","impliedFormat":1},{"version":"a3ad4e1fc542751005267d50a6298e6765928c0c3a8dce1572f2ba6ca518661c","impliedFormat":1},{"version":"f237e7c97a3a89f4591afd49ecb3bd8d14f51a1c4adc8fcae3430febedff5eb6","impliedFormat":1},{"version":"3ffdfbec93b7aed71082af62b8c3e0cc71261cc68d796665faa1e91604fbae8f","impliedFormat":1},{"version":"662201f943ed45b1ad600d03a90dffe20841e725203ced8b708c91fcd7f9379a","impliedFormat":1},{"version":"c9ef74c64ed051ea5b958621e7fb853fe3b56e8787c1587aefc6ea988b3c7e79","impliedFormat":1},{"version":"2462ccfac5f3375794b861abaa81da380f1bbd9401de59ffa43119a0b644253d","impliedFormat":1},{"version":"34baf65cfee92f110d6653322e2120c2d368ee64b3c7981dff08ed105c4f19b0","impliedFormat":1},{"version":"a56fe175741cc8841835eb72e61fa5a34adcbc249ede0e3494c229f0750f6b85","impliedFormat":1},{"version":"ab82804a14454734010dcdcd43f564ff7b0389bee4c5692eec76ff5b30d4cf66","impliedFormat":1},{"version":"f874ea4d0091b0a44362a5f74d26caab2e66dec306c2bf7e8965f5106e784c3b","impliedFormat":1},{"version":"c6cdcd12d577032b84eed1de4d2de2ae343463701a25961b202cff93989439fb","impliedFormat":1},{"version":"3dc633586d48fcd04a4f8acdbf7631b8e4a334632f252d5707e04b299069721e","impliedFormat":1},{"version":"3322858f01c0349ee7968a5ce93a1ca0c154c4692aa8f1721dc5192a9191a168","impliedFormat":1},{"version":"6dde0a77adad4173a49e6de4edd6ef70f5598cbebb5c80d76c111943854636ca","impliedFormat":1},{"version":"09acacae732e3cc67a6415026cfae979ebe900905500147a629837b790a366b3","impliedFormat":1},{"version":"f7b622759e094a3c2e19640e0cb233b21810d2762b3e894ef7f415334125eb22","impliedFormat":1},{"version":"99236ea5c4c583082975823fd19bcce6a44963c5c894e20384bc72e7eccf9b03","impliedFormat":1},{"version":"f6688a02946a3f7490aa9e26d76d1c97a388e42e77388cbab010b69982c86e9e","impliedFormat":1},{"version":"9f642953aba68babd23de41de85d4e97f0c39ef074cb8ab8aa7d55237f62aff6","impliedFormat":1},{"version":"159d95163a0ed369175ae7838fa21a9e9e703de5fdb0f978721293dd403d9f4a","impliedFormat":1},{"version":"bae8d023ef6b23df7da26f51cea44321f95817c190342a36882e93b80d07a960","impliedFormat":1},{"version":"26a770cec4bd2e7dbba95c6e536390fffe83c6268b78974a93727903b515c4e7","impliedFormat":1}],"root":[192],"options":{"allowSyntheticDefaultImports":true,"composite":true,"module":99,"skipLibCheck":true},"referencedMap":[[192,1],[185,2],[183,3],[220,3],[223,4],[222,3],[194,5],[193,3],[198,6],[195,2],[196,7],[197,2],[199,8],[201,9],[200,10],[205,11],[204,12],[203,13],[151,3],[206,3],[210,14],[215,15],[216,16],[211,3],[217,3],[218,17],[219,18],[228,19],[202,3],[233,20],[229,21],[212,3],[230,3],[231,22],[99,23],[100,23],[101,24],[55,25],[102,26],[103,27],[104,28],[50,3],[53,29],[51,3],[52,3],[105,30],[106,31],[107,32],[108,33],[109,34],[110,35],[111,35],[112,36],[113,37],[114,38],[115,39],[56,3],[54,3],[116,40],[117,41],[118,42],[150,43],[119,44],[120,45],[121,46],[122,47],[123,48],[124,49],[125,50],[126,51],[127,52],[128,53],[129,53],[130,54],[131,3],[132,55],[134,56],[133,57],[135,58],[136,59],[137,60],[138,61],[139,62],[140,63],[141,64],[142,65],[143,66],[144,67],[145,68],[146,69],[147,70],[57,3],[58,3],[59,3],[98,71],[148,72],[149,73],[235,74],[236,75],[234,76],[232,77],[208,3],[209,3],[261,78],[262,79],[238,80],[241,81],[259,78],[260,78],[250,78],[249,82],[247,78],[242,78],[255,78],[253,78],[257,78],[237,78],[254,78],[258,78],[243,78],[244,78],[256,78],[239,78],[245,78],[246,78],[248,78],[252,78],[263,83],[251,78],[240,78],[276,84],[275,3],[270,83],[272,85],[271,83],[264,83],[265,83],[267,83],[269,83],[273,85],[274,85],[266,85],[268,85],[213,86],[207,87],[214,88],[277,3],[278,3],[288,89],[279,3],[280,3],[281,3],[282,3],[283,3],[284,3],[285,3],[286,3],[287,3],[289,3],[290,90],[191,91],[186,92],[189,93],[184,3],[60,3],[221,3],[158,3],[227,94],[225,95],[226,96],[187,3],[175,97],[173,98],[174,99],[162,100],[163,98],[170,101],[161,102],[166,103],[176,3],[167,104],[172,105],[178,106],[177,107],[160,108],[168,109],[169,110],[164,111],[171,97],[165,112],[224,113],[153,114],[152,115],[159,3],[1,3],[48,3],[49,3],[9,3],[13,3],[12,3],[3,3],[14,3],[15,3],[16,3],[17,3],[18,3],[19,3],[20,3],[21,3],[4,3],[22,3],[23,3],[5,3],[24,3],[28,3],[25,3],[26,3],[27,3],[29,3],[30,3],[31,3],[6,3],[32,3],[33,3],[34,3],[35,3],[7,3],[39,3],[36,3],[37,3],[38,3],[40,3],[8,3],[41,3],[46,3],[47,3],[42,3],[43,3],[44,3],[45,3],[2,3],[11,3],[10,3],[188,3],[76,116],[86,117],[75,116],[96,118],[67,119],[66,120],[95,8],[89,121],[94,122],[69,123],[83,124],[68,125],[92,126],[64,127],[63,8],[93,128],[65,129],[70,130],[71,3],[74,130],[61,3],[97,131],[87,132],[78,133],[79,134],[81,135],[77,136],[80,137],[90,8],[72,138],[73,139],[82,140],[62,141],[85,132],[84,130],[88,3],[91,142],[182,143],[179,144],[157,145],[155,146],[154,3],[156,147],[180,3],[181,148],[190,149]],"latestChangedDtsFile":"./vite.config.d.ts","version":"5.9.3"} \ No newline at end of file diff --git a/java-frontend/tsconfig.tsbuildinfo b/java-frontend/tsconfig.tsbuildinfo index 6b48206..c947f9a 100644 --- a/java-frontend/tsconfig.tsbuildinfo +++ b/java-frontend/tsconfig.tsbuildinfo @@ -1 +1 @@ -{"root":["./src/main.ts","./src/vite-env.d.ts","./src/api/ai-3d.ts","./src/api/auth.ts","./src/api/classes.ts","./src/api/config.ts","./src/api/contests.ts","./src/api/departments.ts","./src/api/dict.ts","./src/api/grades.ts","./src/api/homework.ts","./src/api/judges-management.ts","./src/api/logs.ts","./src/api/menus.ts","./src/api/permissions.ts","./src/api/roles.ts","./src/api/schools.ts","./src/api/students.ts","./src/api/teachers.ts","./src/api/tenants.ts","./src/api/upload.ts","./src/api/users.ts","./src/composables/uselistrequest.ts","./src/composables/usesimplelistrequest.ts","./src/directives/permission.ts","./src/router/index.ts","./src/stores/auth.ts","./src/types/api.ts","./src/types/auth.ts","./src/types/router.ts","./src/utils/auth.ts","./src/utils/avatar.ts","./src/utils/menu.ts","./src/utils/request.ts","./src/app.vue","./src/components/modelviewer.vue","./src/components/richtexteditor.vue","./src/layouts/basiclayout.vue","./src/layouts/emptylayout.vue","./src/views/activities/comments.vue","./src/views/activities/guidance.vue","./src/views/activities/review.vue","./src/views/activities/reviewdetail.vue","./src/views/activities/components/reviewworkmodal.vue","./src/views/auth/login.vue","./src/views/contests/activities.vue","./src/views/contests/create.vue","./src/views/contests/detail.vue","./src/views/contests/guidance.vue","./src/views/contests/index.vue","./src/views/contests/registerindividual.vue","./src/views/contests/registerteam.vue","./src/views/contests/components/addjudgedrawer.vue","./src/views/contests/components/addparticipantdrawer.vue","./src/views/contests/components/addteacherdrawer.vue","./src/views/contests/components/submitworkdrawer.vue","./src/views/contests/components/viewworkdrawer.vue","./src/views/contests/components/workdetailmodal.vue","./src/views/contests/judges/index.vue","./src/views/contests/notices/index.vue","./src/views/contests/registrations/index.vue","./src/views/contests/registrations/records.vue","./src/views/contests/results/detail.vue","./src/views/contests/results/index.vue","./src/views/contests/reviews/index.vue","./src/views/contests/reviews/progress.vue","./src/views/contests/reviews/progressdetail.vue","./src/views/contests/reviews/tasks.vue","./src/views/contests/works/index.vue","./src/views/contests/works/worksdetail.vue","./src/views/error/403.vue","./src/views/error/404.vue","./src/views/homework/index.vue","./src/views/homework/reviewrules.vue","./src/views/homework/studentdetail.vue","./src/views/homework/studentlist.vue","./src/views/homework/submissions.vue","./src/views/model/modelviewer.vue","./src/views/school/classes/index.vue","./src/views/school/departments/index.vue","./src/views/school/grades/index.vue","./src/views/school/schools/index.vue","./src/views/school/students/index.vue","./src/views/school/teachers/index.vue","./src/views/system/config/index.vue","./src/views/system/dict/index.vue","./src/views/system/logs/index.vue","./src/views/system/menus/index.vue","./src/views/system/permissions/index.vue","./src/views/system/roles/index.vue","./src/views/system/tenants/index.vue","./src/views/system/users/index.vue","./src/views/workbench/index.vue","./src/views/workbench/ai-3d/generate.vue","./src/views/workbench/ai-3d/history.vue","./src/views/workbench/ai-3d/index.vue"],"errors":true,"version":"5.9.3"} \ No newline at end of file +{"root":["./src/main.ts","./src/vite-env.d.ts","./src/api/analytics.ts","./src/api/auth.ts","./src/api/classes.ts","./src/api/config.ts","./src/api/contests.ts","./src/api/departments.ts","./src/api/dict.ts","./src/api/grades.ts","./src/api/homework.ts","./src/api/judges-management.ts","./src/api/logs.ts","./src/api/menus.ts","./src/api/permissions.ts","./src/api/preset-comments.ts","./src/api/public.ts","./src/api/roles.ts","./src/api/schools.ts","./src/api/students.ts","./src/api/teachers.ts","./src/api/tenants.ts","./src/api/upload.ts","./src/api/users.ts","./src/composables/uselistrequest.ts","./src/composables/usesimplelistrequest.ts","./src/directives/permission.ts","./src/router/index.ts","./src/stores/auth.ts","./src/types/api.ts","./src/types/auth.ts","./src/types/router.ts","./src/utils/auth.ts","./src/utils/avatar.ts","./src/utils/menu.ts","./src/utils/request.ts","./src/app.vue","./src/components/richtexteditor.vue","./src/layouts/basiclayout.vue","./src/layouts/emptylayout.vue","./src/layouts/publiclayout.vue","./src/views/activities/comments.vue","./src/views/activities/guidance.vue","./src/views/activities/presetcomments.vue","./src/views/activities/review.vue","./src/views/activities/reviewdetail.vue","./src/views/activities/components/reviewworkmodal.vue","./src/views/analytics/overview.vue","./src/views/analytics/review.vue","./src/views/auth/login.vue","./src/views/content/tagmanagement.vue","./src/views/content/workmanagement.vue","./src/views/content/workreview.vue","./src/views/contests/activities.vue","./src/views/contests/create.vue","./src/views/contests/detail.vue","./src/views/contests/guidance.vue","./src/views/contests/index.vue","./src/views/contests/registerindividual.vue","./src/views/contests/registerteam.vue","./src/views/contests/superdetail.vue","./src/views/contests/components/addjudgedrawer.vue","./src/views/contests/components/addparticipantdrawer.vue","./src/views/contests/components/addteacherdrawer.vue","./src/views/contests/components/submitworkdrawer.vue","./src/views/contests/components/viewworkdrawer.vue","./src/views/contests/components/workdetailmodal.vue","./src/views/contests/judges/index.vue","./src/views/contests/notices/index.vue","./src/views/contests/registrations/index.vue","./src/views/contests/registrations/records.vue","./src/views/contests/results/detail.vue","./src/views/contests/results/index.vue","./src/views/contests/reviews/index.vue","./src/views/contests/reviews/progress.vue","./src/views/contests/reviews/progressdetail.vue","./src/views/contests/reviews/tasks.vue","./src/views/contests/works/index.vue","./src/views/contests/works/worksdetail.vue","./src/views/error/403.vue","./src/views/error/404.vue","./src/views/homework/index.vue","./src/views/homework/reviewrules.vue","./src/views/homework/studentdetail.vue","./src/views/homework/studentlist.vue","./src/views/homework/submissions.vue","./src/views/public/activities.vue","./src/views/public/activitydetail.vue","./src/views/public/gallery.vue","./src/views/public/login.vue","./src/views/public/components/workselector.vue","./src/views/public/create/generating.vue","./src/views/public/create/index.vue","./src/views/public/mine/children.vue","./src/views/public/mine/favorites.vue","./src/views/public/mine/index.vue","./src/views/public/mine/registrations.vue","./src/views/public/works/detail.vue","./src/views/public/works/index.vue","./src/views/public/works/publish.vue","./src/views/school/classes/index.vue","./src/views/school/departments/index.vue","./src/views/school/grades/index.vue","./src/views/school/schools/index.vue","./src/views/school/students/index.vue","./src/views/school/teachers/index.vue","./src/views/system/config/index.vue","./src/views/system/dict/index.vue","./src/views/system/logs/index.vue","./src/views/system/menus/index.vue","./src/views/system/permissions/index.vue","./src/views/system/public-users/index.vue","./src/views/system/roles/index.vue","./src/views/system/tenants/index.vue","./src/views/system/users/index.vue","./src/views/workbench/index.vue"],"errors":true,"version":"5.9.3"} \ No newline at end of file diff --git a/java-frontend/vite.config.js b/java-frontend/vite.config.js new file mode 100644 index 0000000..3ddbdb3 --- /dev/null +++ b/java-frontend/vite.config.js @@ -0,0 +1,38 @@ +import { defineConfig } from "vite"; +import vue from "@vitejs/plugin-vue"; +import { resolve } from "path"; +// 根据环境设置 base 路径 +var getBase = function (mode) { + switch (mode) { + case "test": + return "/web-test/"; + case "production": + return "/web/"; + default: + return "/"; + } +}; +// https://vitejs.dev/config/ +export default defineConfig(function (_a) { + var mode = _a.mode; + return { + base: getBase(mode), + plugins: [vue()], + resolve: { + alias: { + "@": resolve(__dirname, "src"), + }, + }, + server: { + port: 3000, + host: "0.0.0.0", + proxy: { + "/api": { + target: "http://localhost:8580", + changeOrigin: true, + // rewrite: (path) => path.replace(/^\/api/, ''), + }, + }, + }, + }; +});