diff --git a/backend/xt-common/src/main/java/cn/tianbaotong/fill/common/crypto/RsaCipher.java b/backend/xt-common/src/main/java/cn/tianbaotong/fill/common/crypto/RsaCipher.java index a9cd232..12f8b91 100644 --- a/backend/xt-common/src/main/java/cn/tianbaotong/fill/common/crypto/RsaCipher.java +++ b/backend/xt-common/src/main/java/cn/tianbaotong/fill/common/crypto/RsaCipher.java @@ -4,7 +4,10 @@ import javax.crypto.Cipher; import java.nio.charset.StandardCharsets; import java.security.KeyFactory; import java.security.PrivateKey; +import java.security.interfaces.RSAPrivateCrtKey; +import java.security.interfaces.RSAPublicKey; import java.security.spec.PKCS8EncodedKeySpec; +import java.security.spec.RSAPublicKeySpec; import java.util.Base64; /** @@ -40,6 +43,32 @@ public final class RsaCipher { } } + /** + * 导出对应的公钥(PEM,X.509/SPKI)。 + * + *

用于 {@code GET /api/v1/auth/public-key}:前端启动时拉一次, + * 之后用它加密登录密码。 + * + *

为什么不把公钥打包进前端:一是密钥轮换时前端要重新构建、重新发布; + * 二是打包进去意味着"改密钥"这件事横跨前后端两个仓库, + * 实际操作中一定会出现"后端换了、前端没换"导致全员登不进去。 + * 公钥是公开信息,放接口上没有任何安全损失。 + */ + public String publicKeyPem() { + try { + if (!(privateKey instanceof RSAPrivateCrtKey crt)) { + throw new IllegalStateException("私钥不是 CRT 格式,无法推导公钥"); + } + RSAPublicKey pub = (RSAPublicKey) KeyFactory.getInstance("RSA") + .generatePublic(new RSAPublicKeySpec(crt.getModulus(), crt.getPublicExponent())); + String b64 = Base64.getMimeEncoder(64, new byte[]{'\n'}) + .encodeToString(pub.getEncoded()); + return "-----BEGIN PUBLIC KEY-----\n" + b64 + "\n-----END PUBLIC KEY-----\n"; + } catch (Exception e) { + throw new IllegalStateException("导出 RSA 公钥失败", e); + } + } + /** * 解密前端传来的密码。 * diff --git a/backend/xt-server/src/main/java/cn/tianbaotong/fill/module/auth/controller/AuthController.java b/backend/xt-server/src/main/java/cn/tianbaotong/fill/module/auth/controller/AuthController.java index ed81dd0..a1dd1de 100644 --- a/backend/xt-server/src/main/java/cn/tianbaotong/fill/module/auth/controller/AuthController.java +++ b/backend/xt-server/src/main/java/cn/tianbaotong/fill/module/auth/controller/AuthController.java @@ -1,6 +1,7 @@ package cn.tianbaotong.fill.module.auth.controller; import cn.tianbaotong.fill.common.api.ApiResult; +import cn.tianbaotong.fill.common.crypto.RsaCipher; import cn.tianbaotong.fill.module.auth.dto.LoginReq; import cn.tianbaotong.fill.module.auth.dto.LoginVO; import cn.tianbaotong.fill.module.auth.service.AuthService; @@ -29,6 +30,7 @@ public class AuthController { private final AuthService authService; private final CaptchaService captchaService; + private final RsaCipher rsaCipher; @PublicApi @PostMapping("/login") @@ -44,6 +46,20 @@ public class AuthController { return ApiResult.ok(captchaService.generate()); } + /** + * 登录密码加密用的公钥。 + * + *

公钥是公开信息,无需鉴权。前端启动时拉一次并缓存, + * 这样轮换密钥不需要重新构建前端——否则"后端换了、前端没换" + * 会表现为"所有人密码都错",而且很难定位。 + */ + @PublicApi + @GetMapping("/public-key") + @Operation(summary = "获取登录加密公钥", description = "PEM 格式,前端用它与 RSA/ECB/PKCS1Padding 加密密码") + public ApiResult publicKey() { + return ApiResult.ok(rsaCipher.publicKeyPem()); + } + @GetMapping("/me") @Operation(summary = "当前用户", description = "返回用户、租户、角色、菜单与细粒度权限码") public ApiResult me() { diff --git a/backend/xt-server/src/main/java/cn/tianbaotong/fill/support/DataSeeder.java b/backend/xt-server/src/main/java/cn/tianbaotong/fill/support/DataSeeder.java index af286fd..2fd446f 100644 --- a/backend/xt-server/src/main/java/cn/tianbaotong/fill/support/DataSeeder.java +++ b/backend/xt-server/src/main/java/cn/tianbaotong/fill/support/DataSeeder.java @@ -2,6 +2,8 @@ package cn.tianbaotong.fill.support; import cn.tianbaotong.fill.common.context.LoginUser; import cn.tianbaotong.fill.common.context.UserContext; +import cn.tianbaotong.fill.module.org.entity.SysOrg; +import cn.tianbaotong.fill.module.org.mapper.OrgMapper; import cn.tianbaotong.fill.module.tenant.entity.SysTenant; import cn.tianbaotong.fill.module.tenant.mapper.TenantMapper; import cn.tianbaotong.fill.module.user.entity.SysUser; @@ -44,6 +46,7 @@ public class DataSeeder implements ApplicationRunner { private final TenantMapper tenantMapper; private final UserMapper userMapper; + private final OrgMapper orgMapper; private final RoleProvisioner roleProvisioner; private final PasswordEncoder passwordEncoder; @@ -102,6 +105,15 @@ public class DataSeeder implements ApplicationRunner { TenantScope.runAs(tid, 0L, Set.of(BuiltinRole.SCHOOL_ADMIN.getCode()), () -> { roleProvisioner.provisionBuiltinRoles(tid); + + // ⚠️ 必须放在 runAs 内部:sys_org 有 tenant_id, + // 在租户作用域外查询会触发拦截器的「默认拒绝」直接抛 40101, + // 而且它发生在启动阶段(ApplicationRunner),表现是应用起不来。 + // + // ⚠️ 也必须放在下面那个「admin 已存在就早退」之前: + // 否则第二次启动之后组织树永远不会被补上(自身已幂等,重复调用无害)。 + seedDemoOrgs(tid); + if (findUser(tid, "admin") != null) { return; } @@ -118,16 +130,76 @@ public class DataSeeder implements ApplicationRunner { }); } + /** + * 示范组织树。 + * + *

空的组织树看不出系统在做什么,所以给个有真实感的三级结构 + * (学校 → 院系 → 班级)。名字取自客户的真实班级命名习惯(24中技数控1班)。 + * + *

幂等:只要该租户已有任何组织就跳过,不覆盖用户自己建的。 + */ + private void seedDemoOrgs(final long tenantId) { + Long existing = orgMapper.selectCount(Wrappers.lambdaQuery()); + if (existing != null && existing > 0) { + return; + } + + TenantScope.runAs(tenantId, 0L, Set.of(BuiltinRole.SCHOOL_ADMIN.getCode()), () -> { + long school = insertOrg(0L, "示范职业学校", 1, "DEMO", 1); + + long mech = insertOrg(school, "智能制造系", 2, "MECH", 1); + insertOrg(mech, "24中技数控1班", 3, "24ZK1", 1); + insertOrg(mech, "24中技机电1班", 3, "24JD1", 2); + + long cs = insertOrg(school, "信息工程系", 2, "CS", 2); + insertOrg(cs, "24中技计算机1班", 3, "24JSJ1", 1); + + log.info("已为示范租户创建组织树(1 个学校 / 2 个院系 / 3 个班级)"); + }); + } + + /** + * 插入一个组织节点并回填 {@code ancestor_path}。 + * + *

⚠️ 路径形如 {@code /10/11/12/},**必须包含全部祖先**,末位才是自己。 + * 这不是显示用的字段 —— 「按院系统计」靠 {@code LIKE '/10/%'} 一次覆盖所有后代, + * 路径错了统计就会漏人/多人,而且不会报错。 + * + *

路径依赖自身 id,只能先插入再回写;上级路径从库里查(不靠入参传), + * 避免调用方漏传或传错 —— 之前就是因为把 parentPath 传成 null, + * 导致所有子节点的路径都退化成了只有自己。 + */ + private long insertOrg(Long parentId, String name, int type, String code, int sortNo) { + SysOrg o = new SysOrg(); + o.setParentId(parentId); + o.setOrgName(name); + o.setOrgType(type); + o.setOrgCode(code); + o.setSortNo(sortNo); + o.setStatus(1); + orgMapper.insert(o); + + String parentPath = "/"; + if (parentId != null && parentId != 0L) { + SysOrg parent = orgMapper.selectById(parentId); + if (parent == null || parent.getAncestorPath() == null) { + throw new IllegalStateException("上级组织的层级路径缺失:" + parentId); + } + parentPath = parent.getAncestorPath(); + } + String path = parentPath + o.getId() + "/"; + + SysOrg patch = new SysOrg(); + patch.setId(o.getId()); + patch.setAncestorPath(path); + orgMapper.updateById(patch); + return o.getId(); + } + private SysUser findUser(long tenantId, String username) { return userMapper.selectOne(Wrappers.lambdaQuery() .eq(SysUser::getTenantId, tenantId) .eq(SysUser::getUsername, username) .last("LIMIT 1")); } - - /** 保留:主应用启动时用到的常量,避免未使用告警。 */ - @SuppressWarnings("unused") - private static List noop() { - return List.of(); - } } diff --git a/frontend-pc/.agents/README.md b/frontend-pc/.agents/README.md new file mode 100644 index 0000000..65b0e12 --- /dev/null +++ b/frontend-pc/.agents/README.md @@ -0,0 +1,29 @@ +# .agents/ + +本目录为 AI 编码助手规范的**唯一来源**(Single Source of Truth),适用于 Cursor、OpenCode、Codex 等工具。 + +## 结构 + +``` +.agents/ +├── rules/ +│ └── agent.md # 唯一常驻规则(Cursor 经 AGENTS.md 内联注入) +└── skills/ # 按需加载 + ├── code-ts/SKILL.md + ├── code-vue/SKILL.md + ├── code-css/SKILL.md + ├── project-guide/SKILL.md + ├── project-crud/SKILL.md + ├── project-api/SKILL.md + └── git-commit/SKILL.md +``` + +## 工具适配 + +| 工具 | 适配方式 | +|------|----------| +| Cursor | `AGENTS.md`(内联 behavior 规则 + 技能索引)+ `.agents/skills/`(按需) | +| OpenCode | `AGENTS.md` + `opencode.json` + `.agents/rules/` + `.agents/skills/` | +| Codex | `AGENTS.md` + `.agents/skills/` | + +修改 `agent.md` 后请同步 `AGENTS.md` 的「常驻规则」一节;编码规范改对应 skill,不要重复写入 `AGENTS.md`。 diff --git a/frontend-pc/.agents/rules/agent.md b/frontend-pc/.agents/rules/agent.md new file mode 100644 index 0000000..01550ae --- /dev/null +++ b/frontend-pc/.agents/rules/agent.md @@ -0,0 +1,36 @@ +# Agent 行为准则 + +> 适用于 Cursor、OpenCode、Codex 等 AI 编码助手。Cursor 经 `AGENTS.md` 内联本节每轮注入。 + +## 通用要求 + +- 始终使用**简体中文**与用户沟通。 +- 未经用户明确要求,**不要**创建说明文档、执行 git commit/push、修改 git 配置。 +- 改动范围最小化:只改与任务直接相关的代码,不顺手重构无关模块。 +- 优先复用项目已有抽象与约定,禁止凭空引入新架构或重复造轮子。 + +## 应当 + +- 改代码前先阅读项目既有实现,保持风格一致。 +- 优先复用项目内已有组件、composables、工具函数与 API 封装。 +- 任务完成后,视情况运行 lint / typecheck / test(与改动相关时)。 +- **按需读取** skills,不要预加载全部;写代码前按任务类型加载对应 skill(见 `AGENTS.md` 技能索引)。 + +## 不应 + +- 为已自动导入的 API / 组件写冗余 import。 +- 引入与项目技术栈不符的 UI 库或全新抽象层。 +- 猜测不存在的 API 路径、响应结构或配置项;以项目代码与类型定义为准。 +- 在用户未要求时添加测试、文档或无关注释。 + +## 编码前必读 skill + +| 任务 | 技能 | +|------|------| +| 写/改 `.vue` | `code-vue` | +| 写/改样式 | `code-css` | +| 写/改 `.ts` / composable | `code-ts` | +| 项目约定、Gi 组件 | `project-guide` | +| CRUD 列表/表单 | `project-crud` | +| API / Mock | `project-api` | +| 提交代码 | `git-commit` | diff --git a/frontend-pc/.agents/skills/code-css/SKILL.md b/frontend-pc/.agents/skills/code-css/SKILL.md new file mode 100644 index 0000000..b9bcffa --- /dev/null +++ b/frontend-pc/.agents/skills/code-css/SKILL.md @@ -0,0 +1,36 @@ +--- +name: code-css +description: Vue SFC 样式块、BEM 命名、Stylelint 与 scoped 约定。编写或修改组件样式(.vue style、scss、less、css)时加载本技能。 +--- + +# CSS 规范(Vue 3) + +## SFC 样式块 + +- 位于 ` +``` + +## useTable 用法 + +```ts +import type * as T from '@/apis/system/user' +import { baseAPI } from '@/apis/system/user' +import { useDict, useTable } from '@/hooks' + +const { dictData } = useDict(['STATUS'] as const) + +const queryParams = reactive({ status: '', username: '' }) + +const { + loading, + tableData, + pagination, + selectedKeys, + search, + select, + selectAll, + fixed, // 操作列 fixed: 'right'(小屏自动取消) + onDelete, + onBatchDelete, + refresh +} = useTable({ + listAPI: (page) => baseAPI.getList({ ...page, ...queryParams }), + deleteAPI: (ids) => baseAPI.delete({ ids }), + immediate: true, + rowKey: 'id', // 默认 id + crossPageSelect: false // 跨页多选时设为 true +}) +``` + +要点: + +- `search()` 会重置到第 1 页并清空选中 +- `refresh()` 保持当前页刷新 +- `listAPI` 参数为 `{ page, size }`,需展开到接口 +- 空字符串查询条件建议转 `undefined` 再传 API + +## GiButton 预设类型 + +`add` | `edit` | `delete` | `search` | `reset` | `upload` | `import` | `export` + +列表工具栏与操作列统一使用 `GiButton`,操作列常用 `size="mini"`。 + +## 表格列(TSX) + +使用 `lang="tsx"`,从 `@arco-design/web-vue` 引入 `TableColumnData`: + +```tsx +const tableColumns: TableColumnData[] = [ + { + title: '序号', + width: 68, + align: 'center', + render: ({ rowIndex }) => {rowIndex + 1} + }, + { title: '名称', dataIndex: 'name', ellipsis: true, tooltip: true }, + { + title: '状态', + width: 100, + align: 'center', + render: ({ record }) => ( + + ) + }, + { + title: '操作', + width: 160, + align: 'center', + fixed: 'right', // 或绑定 useTable 的 fixed + render: ({ record }) => ( + + onEdit(record as T.ListItem)} /> + onDelete(record as T.ListItem)}> + + + + ) + } +] +``` + +常用单元格组件:`GiCellAvatar`、`GiCellGender`、`GiCellStatus`(从 `@/components/GiCell` 引入或在 TSX 中直接使用)。 + +## GiPageLayout + +| 属性/插槽 | 说明 | +|-----------|------| +| `#left` | 左侧栏(部门树、字典类型列表等) | +| `#header` | 顶栏(少用) | +| `margin` | 外边距模式 | +| `collapsed` | 显示折叠按钮 | +| `size` | 左侧宽度,如 `"280px"` | + +左栏选中后通过事件同步右侧列表,参考 `dict/index.vue` 的 `onDictTypeRefresh`。 + +## 表单弹窗(Modal + GiForm) + +```vue + + + +``` + +```ts +import type { FormColumnItem } from '@/components/index' +import { useResetReactive } from '@/hooks' + +const [form, resetForm] = useResetReactive({ id: '', name: '', status: '1' as Status }) +const GiFormRef = useTemplateRef>('GiFormRef') + +const add = () => { + detailId.value = '' + visible.value = true +} +const edit = async (id: string) => { + visible.value = true + detailId.value = id + Object.assign(form, (await baseAPI.getDetail({ id })).data) +} +const close = () => { + GiFormRef.value?.formRef?.resetFields() + resetForm() +} +const save = async () => { + const valid = await GiFormRef.value?.formRef?.validate() + if (valid) return false + // await baseAPI.add(form) 或 baseAPI.update(form) + emit('save-success') + return true +} + +defineExpose({ add, edit }) +``` + +`formColumns` 常用 `type`:`input`、`select`、`tree-select`、`radio-group`、`switch`、`textarea`。校验可用 `required`、`rules`;正则见 `@/utils/regexp`。 + +## 字典下拉 + +```ts +const { dictData } = useDict(['STATUS'] as const) +// 模板: +``` + +## 表格增强方案选型 + +| 需求 | 方案 | +|------|------| +| 内置斑马纹/全屏/列设置 | `GiTable` | +| 继续用 `a-table` + 工具栏/列设置 | `GiTableSetting` 包裹(见 `views/table/custom3`) | +| 简单列表 | `a-table` + `useTable`(系统模块主流) | + +## 检查清单 + +- [ ] `defineOptions({ name })` 已设置 +- [ ] `row-key="id"` 与 `useTable` 的 `rowKey` 一致 +- [ ] 删除走 `onDelete` / `onBatchDelete` 或自定义 `Popconfirm` + API +- [ ] 弹窗 `defineExpose({ add, edit })`,父组件 `useTemplateRef` 调用 +- [ ] 保存成功后 `emit('save-success')` 并触发 `search` diff --git a/frontend-pc/.agents/skills/project-guide/SKILL.md b/frontend-pc/.agents/skills/project-guide/SKILL.md new file mode 100644 index 0000000..c75edd1 --- /dev/null +++ b/frontend-pc/.agents/skills/project-guide/SKILL.md @@ -0,0 +1,171 @@ +--- +name: project-guide +description: 后台管理模板的项目总览与前端强制规范。涵盖 Vue 3 + Arco Design + Gi 组件库、目录结构、自动导入、CRUD/API 约定与代码风格。在当前仓库内开发、新增页面、查阅架构或不确定项目规范时使用。 +--- + +# 项目开发指南 + +> **适用范围**:本仓库 `src/**`、`mock/**` +> **规范来源**:`AGENTS.md` 常驻行为准则 + 本 skill(项目细节);编码规范按需读 `code-vue` / `code-css` / `code-ts` skill + +## 本项目 Agent 约定 +- 优先复用 `Gi*`、`hooks`、`getBaseApi`、现有 mock 工具;新增页面/接口对齐参考页与 `getBaseApi` 约定。 +- API / Mock 以 `src/apis`、`mock` 与类型定义为准,不要猜测路径。 +- 涉及 CRUD、API、Mock 时按需读取对应 skill;写 `.vue` / 样式 / TS 前读取 [code-vue](../code-vue/SKILL.md)、[code-css](../code-css/SKILL.md)、[code-ts](../code-ts/SKILL.md)。 +- 改动后视情况运行 `pnpm typecheck`、`pnpm lint`、`pnpm lint:style`。 + +## 技术栈 + +| 类别 | 选型 | +|------|------| +| 框架 | Vue 3.5 + TypeScript + ` +``` + +```vue + +``` + +```vue + +``` + +```vue + +``` + +#### 写法技巧 + +尽量使用三元表达式 + +```js +// 优化前 +let isEdit = true +let title = '' +if (isEdit) { + title = '编辑' +} else { + title = '新增' +} + +// 优化后 +let title = isEdit ? '编辑' : '新增' +``` + +善用 includes 方法 + +```js +// 优化前 +if (type === 1 || type === 2 || type === 3) { +} + +// 优化后,此种方式在vue模板也可使用 +if ([1, 2, 3].includes(type)) { +} +``` + +使用箭头函数简化函数 + +```js +// 优化前 +function add(num1, num2) { + return num1 + num2 +} + +// 优化后 +const add = (num1, num2) => num1 + num2 +``` + +尽量减少 if else if + +```vue + +``` + +```js +// 优化前 +const status = 200 +const message = '' +if (status === 200) { + message = '请求成功' +} else if (status === 404) { + message = '请求出错' +} else if (status === 500) { + message = '服务器错误' +} + +// 优化后 +const status = 200 +const messageMap = { + 200: '请求成功', + 404: '请求出错', + 500: '服务器错误' +} +const message = messageMap[status] +``` + +如果函数参数超过两个,建议优化 + +```vue + +``` + +#### 接口调用书写 + +**写法一** + +不需要 loading,不需要错误打印的 情况 + +```vue + + +// getUserListApi 是一个 Promise 异步函数,Promise 最后只有 成功 / 失败 两种状态 // getUserListApi 是基于 axios +封装的,在 axios 响应拦截器做了处理 // 当 res.success === false 的时候 Promise.reject(), +也就异步失败,异步失败不会往下执行 +``` + +**写法二** + +需要 loading,需要错误打印的情况 + +```vue + +``` + +**写法三** + +需要 loading, 不需要错误打印(不进行错误处理)的情况 + +```vue + + +// catch 可以省略 +``` + +#### 全局组件--命名规范 + +组件命名:**单文件组件的文件名应该要么始终是单词大写开头 (PascalCase),要么始终是横线连接 (kebab-case)** + +可参考 Vue2 官网-风格指南: https://v2.cn.vuejs.org/v2/style-guide/ + +``` +GiForm.vue +GiTable.vue +GiPageLayout.vue +``` + +#### 局部组件--命名规范 + +组件命名:**单文件组件的文件名应该要么始终是单词大写开头 (PascalCase),要么始终是横线连接 (kebab-case)** + +可参考 Vue2 官网-风格指南: https://v2.cn.vuejs.org/v2/style-guide/ + +``` +Pane1.vue +Pane2.vue +PaneQuota1.vue +PaneQuota2.vue +Step1.vue +Step2.vue +AddModal.vue +EditDrawer.vue +DetailModal.vue +``` + +#### 文件夹命名--命名规范 (采用中划线-) + +1、文件名建议只使用小写字母,不使用大写字母 + +2、名称较长时采用半角连接符(-)分隔 + +``` +home/index.vue +user/index.vue +user-detail/index.vue +``` + +#### 弹窗组件 Modal、抽屉组件 Drawer 的一般封装 + +```vue + + + +``` + +使用 + +**`模板里使用自定义组件:使用大写开头驼峰,双击好复制,对于搜索便利`** + +```vue + + + +``` + +#### CSS 命名规范 + +建议采用全小写,多单词使用-连接符(参考大部分网站,包括掘金,码云等,都是采用这个规则) + +或者采用`BEM`命名规范 [BEM 命名规范](https://getbem.com/naming/) + +**BEM 命名规范** + +```html +

+
+ + +
+
+``` + + +其他: + +位置 1: 使用 var(--margin) 全局 css 变量 + +位置 2:使用 var(--padding) 全局 css 变量 + +建议尽量使用全局 css 变量来开发,可以有效提高效率和团队协作 + + + + + +#### 其他规范 + +可参考 Vue2 官网-风格指南: https://v2.cn.vuejs.org/v2/style-guide/ , 其中一些规范也可借鉴 + +可参考 **Gi Admin Pro** 源码,如有更好的规范建议,可以联系作者本人 + +## Vue 相关 + +Vue3 官网 + +Vue-Router + +Vite + +Pinia + +## 插件推荐 + +Arco Design 组件库 + +Day.js 一个极简的 JavaScript 库,可以为现代浏览器解析、验证、操作和显示日期和时间 2K 大小 + +Lodash 一个一致性、模块化、高性能的 JavaScript 实用工具库 + +Xe-utils 一个 JavaScript 函数库、工具类 + +VueUse 一个 Vue3 Hooks 库 + +VueRequest 一个 Vue 请求库 + +V-Viewer 基于 viewer.js 的 vue 图像查看器组件,支持旋转、缩放等操作 + +Vue-Color-Kit 一个 vue3 颜色选择器组件 + +Vxe-Table + +vue3-request 一个专为 Vue 3 设计的异步请求处理库,它通过统一的 useRequest Hook 和强大的插件生态,彻底简化了异步操作和 API 调用的复杂性 + +**其他** + +VCalendar 日历组件 + +Vue Cal 日历组件 + +VueDraggablePlus 支持 Vue2 和 Vue3 的拖拽组件 + +## 推荐书籍 + +Vue 入门指南与实战案例 + +深入理解 TypeScript + +前端宝典 + +Web 开发人员需要知道的 CSS 技巧 + +阮一峰 ES6 + +阮一峰 flex 布局 + +## 开源项目集合 + +Vue3 开源项目集合 + +React 开源项目集合 + +## 工具 + +菜鸟工具 + +代码生成图片 + +JSON-TO-ANY 对象转 ts 类型 + +在线三角形样式生成器 + +iTab + +## 捐赠 + + diff --git a/frontend-pc/eslint.config.js b/frontend-pc/eslint.config.js new file mode 100644 index 0000000..e6520fe --- /dev/null +++ b/frontend-pc/eslint.config.js @@ -0,0 +1,56 @@ +import antfu from '@antfu/eslint-config' + +// https://github.com/antfu/eslint-config +export default antfu( + { + vue: true, + typescript: true, + ignores: [ + 'README.md', + 'src/types/shims-vue.d.ts' + ] + }, + { + // Remember to specify the file glob here, otherwise it might cause the vue plugin to handle non-vue files + files: ['**/*.vue'], + rules: { + 'vue/block-order': [2, { + order: [['script', 'template'], 'style'] + }], // 强制组件顶级元素的顺序 + 'vue/html-self-closing': [0, { + html: { + void: 'never', + normal: 'always', + component: 'never' + } + }], // 强制自结束样式 + 'vue/custom-event-name-casing': [2, 'kebab-case'], // 对自定义事件名称强制使用特定大小写 + 'vue/singleline-html-element-content-newline': 0, // 要求在单行元素的内容前后换行 + 'vue/first-attribute-linebreak': 0, // 强制第一个属性的位置 + 'vue/define-macros-order': [2, { + order: ['defineOptions', 'defineModel', 'defineProps', 'defineEmits', 'defineSlots'], + defineExposeLast: true + }], // 强制执行定义限制和定义弹出编译器宏的顺序 + 'vue/html-indent': 0, // 在《模板》中强制一致的缩进 + 'vue/html-closing-bracket-newline': 0 // 要求或不允许在标记的右括号前换行 + } + }, + { + // Without `files`, they are general rules for all files + rules: { + 'curly': [0, 'all'], // 对所有控制语句强制使用一致的大括号样式 + 'dot-notation': 0, // 尽可能强制使用点表示法。 在 JavaScript 中,可以使用点表示法 (foo.bar) 或方括号表示法 (foo["bar"]) 访问属性 + 'no-new': 0, // 不允许在赋值或比较之外使用 new 运算符 + // 'no-console': 2, // 禁止使用 console + 'no-process-env': 0, + 'style/arrow-parens': [2, 'always'], // 箭头函数参数需要括号 + 'style/brace-style': [2, '1tbs', { allowSingleLine: true }], // 对块执行一致的大括号样式 + 'style/comma-dangle': [2, 'never'], // 要求或不允许尾随逗号 + 'ts/consistent-type-definitions': 0, + 'ts/no-unused-expressions': 0, + 'node/prefer-global/process': 0, + 'antfu/top-level-function': 0, + 'antfu/if-newline': 0 + } + } +) diff --git a/frontend-pc/index.html b/frontend-pc/index.html new file mode 100644 index 0000000..4466c27 --- /dev/null +++ b/frontend-pc/index.html @@ -0,0 +1,35 @@ + + + + + + + + + + + + 填报通 · 管理端 + + + + +
+ +
+
+
+
+
+
+
+
+
+
+ + + diff --git a/frontend-pc/package.json b/frontend-pc/package.json new file mode 100644 index 0000000..3bb942b --- /dev/null +++ b/frontend-pc/package.json @@ -0,0 +1,67 @@ +{ + "name": "xt-frontend-pc", + "type": "module", + "version": "1.0.0", + "scripts": { + "dev": "vite", + "build": "vite build", + "build-tsc": "vue-tsc --noEmit && vite build", + "preview": "vite preview --port 5050", + "typecheck": "vue-tsc --noEmit", + "lint": "eslint .", + "lint:fix": "eslint . --fix", + "lint:style": "stylelint \"**/*.{vue,scss,css}\" --cache", + "lint:style:fix": "stylelint \"**/*.{vue,scss,css}\" --cache --fix" + }, + "dependencies": { + "@arco-design/color": "^0.4.0", + "@iconify/vue": "^5.0.0", + "@vueuse/core": "^13.6.0", + "animate.css": "^4.1.1", + "axios": "^0.27.2", + "dayjs": "^1.11.4", + "echarts": "^6.0.0", + "jsencrypt": "^3.5.4", + "lodash-es": "^4.17.21", + "mitt": "^3.0.1", + "nprogress": "^0.2.0", + "pinia": "^3.0.3", + "pinia-plugin-persistedstate": "^4.4.1", + "sortablejs": "^1.15.6", + "vue": "^3.5.18", + "vue-color-kit": "^1.0.6", + "vue-draggable-plus": "^0.6.0", + "vue-router": "^4.5.1", + "xe-utils": "^3.7.8" + }, + "devDependencies": { + "@antfu/eslint-config": "^7.0.1", + "@arco-design/web-vue": "^2.58.0", + "@iconify-json/icon-park-outline": "^1.2.4", + "@types/lodash-es": "^4.17.12", + "@types/node": "^24.2.0", + "@vitejs/plugin-vue": "^6.0.1", + "@vitejs/plugin-vue-jsx": "^5.0.1", + "@vue/tsconfig": "^0.7.0", + "eslint": "^9.5.0", + "less": "^4.4.0", + "postcss": "^8.5.6", + "postcss-html": "^1.8.0", + "sass": "^1.62.1", + "stylelint": "^16.25.0", + "stylelint-config-recess-order": "^7.4.0", + "stylelint-config-recommended-vue": "^1.6.1", + "stylelint-config-standard": "^39.0.1", + "stylelint-config-standard-scss": "^16.0.0", + "stylelint-scss": "^6.12.1", + "typescript": "^5.9.2", + "unplugin-auto-import": "^20.0.0", + "unplugin-icons": "^22.5.0", + "unplugin-vue-components": "^29.0.0", + "vite": "^7.0.6", + "vite-plugin-compression": "^0.5.1", + "vite-plugin-vue-devtools": "^8.0.5", + "vue-tsc": "^3.0.5" + }, + "description": "数据信息填报系统 · PC 管理端(管理员 / 班主任)" +} diff --git a/frontend-pc/pnpm-lock.yaml b/frontend-pc/pnpm-lock.yaml new file mode 100644 index 0000000..8c67240 --- /dev/null +++ b/frontend-pc/pnpm-lock.yaml @@ -0,0 +1,6565 @@ +lockfileVersion: '9.0' + +settings: + autoInstallPeers: true + excludeLinksFromLockfile: false + +importers: + + .: + dependencies: + '@arco-design/color': + specifier: ^0.4.0 + version: 0.4.0 + '@iconify/vue': + specifier: ^5.0.0 + version: 5.0.3(vue@3.5.23(typescript@5.9.3)) + '@vueuse/core': + specifier: ^13.6.0 + version: 13.9.0(vue@3.5.23(typescript@5.9.3)) + animate.css: + specifier: ^4.1.1 + version: 4.1.1 + axios: + specifier: ^0.27.2 + version: 0.27.2 + dayjs: + specifier: ^1.11.4 + version: 1.11.19 + echarts: + specifier: ^6.0.0 + version: 6.1.0 + jsencrypt: + specifier: ^3.5.4 + version: 3.5.4 + lodash-es: + specifier: ^4.17.21 + version: 4.17.21 + mitt: + specifier: ^3.0.1 + version: 3.0.1 + nprogress: + specifier: ^0.2.0 + version: 0.2.0 + pinia: + specifier: ^3.0.3 + version: 3.0.4(typescript@5.9.3)(vue@3.5.23(typescript@5.9.3)) + pinia-plugin-persistedstate: + specifier: ^4.4.1 + version: 4.7.1(pinia@3.0.4(typescript@5.9.3)(vue@3.5.23(typescript@5.9.3))) + sortablejs: + specifier: ^1.15.6 + version: 1.15.6 + vue: + specifier: ^3.5.18 + version: 3.5.23(typescript@5.9.3) + vue-color-kit: + specifier: ^1.0.6 + version: 1.0.6(vue@3.5.23(typescript@5.9.3)) + vue-draggable-plus: + specifier: ^0.6.0 + version: 0.6.0(@types/sortablejs@1.15.9) + vue-router: + specifier: ^4.5.1 + version: 4.6.3(vue@3.5.23(typescript@5.9.3)) + xe-utils: + specifier: ^3.7.8 + version: 3.7.9 + devDependencies: + '@antfu/eslint-config': + specifier: ^7.0.1 + version: 7.7.3(@typescript-eslint/typescript-estree@8.70.1(typescript@5.9.3))(@typescript-eslint/utils@8.70.1(eslint@9.39.1)(typescript@5.9.3))(@vue/compiler-sfc@3.5.23)(eslint@9.39.1)(typescript@5.9.3) + '@arco-design/web-vue': + specifier: ^2.58.0 + version: 2.58.0(vue@3.5.23(typescript@5.9.3)) + '@iconify-json/icon-park-outline': + specifier: ^1.2.4 + version: 1.2.4 + '@types/lodash-es': + specifier: ^4.17.12 + version: 4.17.12 + '@types/node': + specifier: ^24.2.0 + version: 24.10.0 + '@vitejs/plugin-vue': + specifier: ^6.0.1 + version: 6.0.1(vite@7.2.1(@types/node@24.10.0)(less@4.4.2)(sass@1.93.3)(yaml@2.9.1))(vue@3.5.23(typescript@5.9.3)) + '@vitejs/plugin-vue-jsx': + specifier: ^5.0.1 + version: 5.1.1(vite@7.2.1(@types/node@24.10.0)(less@4.4.2)(sass@1.93.3)(yaml@2.9.1))(vue@3.5.23(typescript@5.9.3)) + '@vue/tsconfig': + specifier: ^0.7.0 + version: 0.7.0(typescript@5.9.3)(vue@3.5.23(typescript@5.9.3)) + eslint: + specifier: ^9.5.0 + version: 9.39.1 + less: + specifier: ^4.4.0 + version: 4.4.2 + postcss: + specifier: ^8.5.6 + version: 8.5.6 + postcss-html: + specifier: ^1.8.0 + version: 1.8.0 + sass: + specifier: ^1.62.1 + version: 1.93.3 + stylelint: + specifier: ^16.25.0 + version: 16.25.0(typescript@5.9.3) + stylelint-config-recess-order: + specifier: ^7.4.0 + version: 7.4.0(stylelint-order@7.0.0(stylelint@16.25.0(typescript@5.9.3)))(stylelint@16.25.0(typescript@5.9.3)) + stylelint-config-recommended-vue: + specifier: ^1.6.1 + version: 1.6.1(postcss-html@1.8.0)(stylelint@16.25.0(typescript@5.9.3)) + stylelint-config-standard: + specifier: ^39.0.1 + version: 39.0.1(stylelint@16.25.0(typescript@5.9.3)) + stylelint-config-standard-scss: + specifier: ^16.0.0 + version: 16.0.0(postcss@8.5.6)(stylelint@16.25.0(typescript@5.9.3)) + stylelint-scss: + specifier: ^6.12.1 + version: 6.12.1(stylelint@16.25.0(typescript@5.9.3)) + typescript: + specifier: ^5.9.2 + version: 5.9.3 + unplugin-auto-import: + specifier: ^20.0.0 + version: 20.2.0(@vueuse/core@13.9.0(vue@3.5.23(typescript@5.9.3))) + unplugin-icons: + specifier: ^22.5.0 + version: 22.5.0(@vue/compiler-sfc@3.5.23) + unplugin-vue-components: + specifier: ^29.0.0 + version: 29.2.0(@babel/parser@7.28.5)(vue@3.5.23(typescript@5.9.3)) + vite: + specifier: ^7.0.6 + version: 7.2.1(@types/node@24.10.0)(less@4.4.2)(sass@1.93.3)(yaml@2.9.1) + vite-plugin-compression: + specifier: ^0.5.1 + version: 0.5.1(vite@7.2.1(@types/node@24.10.0)(less@4.4.2)(sass@1.93.3)(yaml@2.9.1)) + vite-plugin-vue-devtools: + specifier: ^8.0.5 + version: 8.0.5(vite@7.2.1(@types/node@24.10.0)(less@4.4.2)(sass@1.93.3)(yaml@2.9.1))(vue@3.5.23(typescript@5.9.3)) + vue-tsc: + specifier: ^3.0.5 + version: 3.1.3(typescript@5.9.3) + +packages: + + '@antfu/eslint-config@7.7.3': + resolution: {integrity: sha512-BtroDxTvmWtvr3yJkdWVCvwsKlnEdkreoeOyrdNezc/W5qaiQNf2xjcsQ3N5Yy0x27h+0WFfW8rG8YlVioG6dw==} + hasBin: true + peerDependencies: + '@angular-eslint/eslint-plugin': ^21.1.0 + '@angular-eslint/eslint-plugin-template': ^21.1.0 + '@angular-eslint/template-parser': ^21.1.0 + '@eslint-react/eslint-plugin': ^2.11.0 + '@next/eslint-plugin-next': '>=15.0.0' + '@prettier/plugin-xml': ^3.4.1 + '@unocss/eslint-plugin': '>=0.50.0' + astro-eslint-parser: ^1.0.2 + eslint: ^9.10.0 || ^10.0.0 + eslint-plugin-astro: ^1.2.0 + eslint-plugin-format: '>=0.1.0' + eslint-plugin-jsx-a11y: '>=6.10.2' + eslint-plugin-react-hooks: ^7.0.0 + eslint-plugin-react-refresh: ^0.5.0 + eslint-plugin-solid: ^0.14.3 + eslint-plugin-svelte: '>=2.35.1' + eslint-plugin-vuejs-accessibility: ^2.4.1 + prettier-plugin-astro: ^0.14.0 + prettier-plugin-slidev: ^1.0.5 + svelte-eslint-parser: '>=0.37.0' + peerDependenciesMeta: + '@angular-eslint/eslint-plugin': + optional: true + '@angular-eslint/eslint-plugin-template': + optional: true + '@angular-eslint/template-parser': + optional: true + '@eslint-react/eslint-plugin': + optional: true + '@next/eslint-plugin-next': + optional: true + '@prettier/plugin-xml': + optional: true + '@unocss/eslint-plugin': + optional: true + astro-eslint-parser: + optional: true + eslint-plugin-astro: + optional: true + eslint-plugin-format: + optional: true + eslint-plugin-jsx-a11y: + optional: true + eslint-plugin-react-hooks: + optional: true + eslint-plugin-react-refresh: + optional: true + eslint-plugin-solid: + optional: true + eslint-plugin-svelte: + optional: true + eslint-plugin-vuejs-accessibility: + optional: true + prettier-plugin-astro: + optional: true + prettier-plugin-slidev: + optional: true + svelte-eslint-parser: + optional: true + + '@antfu/install-pkg@1.1.0': + resolution: {integrity: sha512-MGQsmw10ZyI+EJo45CdSER4zEb+p31LpDAFp2Z3gkSd1yqVZGi0Ebx++YTEMonJy4oChEMLsxZ64j8FH6sSqtQ==} + + '@antfu/install-pkg@2.1.0': + resolution: {integrity: sha512-sdg9NxU3zR4Mnawfbc/x6GB5Wf17WYud5qOuEuxXjaKpYpMkISSJEjItGebXJ2bQ4DIcly4NYH23mtkGJjvKUw==} + + '@arco-design/color@0.4.0': + resolution: {integrity: sha512-s7p9MSwJgHeL8DwcATaXvWT3m2SigKpxx4JA1BGPHL4gfvaQsmQfrLBDpjOJFJuJ2jG2dMt3R3P8Pm9E65q18g==} + + '@arco-design/web-vue@2.58.0': + resolution: {integrity: sha512-b1vdPYOmjG5VAkVa7jlVwCb+WynBK+rnKN8zH3yKohpZObZbostRd3HgYNtjjZjGVU3OqR0Yy2FX7ftgF0bcOw==} + peerDependencies: + vue: '>=3.1.0' + + '@babel/code-frame@7.27.1': + resolution: {integrity: sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg==} + engines: {node: '>=6.9.0'} + + '@babel/compat-data@7.28.5': + resolution: {integrity: sha512-6uFXyCayocRbqhZOB+6XcuZbkMNimwfVGFji8CTZnCzOHVGvDqzvitu1re2AU5LROliz7eQPhB8CpAMvnx9EjA==} + engines: {node: '>=6.9.0'} + + '@babel/core@7.28.5': + resolution: {integrity: sha512-e7jT4DxYvIDLk1ZHmU/m/mB19rex9sv0c2ftBtjSBv+kVM/902eh0fINUzD7UwLLNR+jU585GxUJ8/EBfAM5fw==} + engines: {node: '>=6.9.0'} + + '@babel/generator@7.28.5': + resolution: {integrity: sha512-3EwLFhZ38J4VyIP6WNtt2kUdW9dokXA9Cr4IVIFHuCpZ3H8/YFOl5JjZHisrn1fATPBmKKqXzDFvh9fUwHz6CQ==} + engines: {node: '>=6.9.0'} + + '@babel/helper-annotate-as-pure@7.27.3': + resolution: {integrity: sha512-fXSwMQqitTGeHLBC08Eq5yXz2m37E4pJX1qAU1+2cNedz/ifv/bVXft90VeSav5nFO61EcNgwr0aJxbyPaWBPg==} + engines: {node: '>=6.9.0'} + + '@babel/helper-compilation-targets@7.27.2': + resolution: {integrity: sha512-2+1thGUUWWjLTYTHZWK1n8Yga0ijBz1XAhUXcKy81rd5g6yh7hGqMp45v7cadSbEHc9G3OTv45SyneRN3ps4DQ==} + engines: {node: '>=6.9.0'} + + '@babel/helper-create-class-features-plugin@7.28.5': + resolution: {integrity: sha512-q3WC4JfdODypvxArsJQROfupPBq9+lMwjKq7C33GhbFYJsufD0yd/ziwD+hJucLeWsnFPWZjsU2DNFqBPE7jwQ==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0 + + '@babel/helper-globals@7.28.0': + resolution: {integrity: sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw==} + engines: {node: '>=6.9.0'} + + '@babel/helper-member-expression-to-functions@7.28.5': + resolution: {integrity: sha512-cwM7SBRZcPCLgl8a7cY0soT1SptSzAlMH39vwiRpOQkJlh53r5hdHwLSCZpQdVLT39sZt+CRpNwYG4Y2v77atg==} + engines: {node: '>=6.9.0'} + + '@babel/helper-module-imports@7.27.1': + resolution: {integrity: sha512-0gSFWUPNXNopqtIPQvlD5WgXYI5GY2kP2cCvoT8kczjbfcfuIljTbcWrulD1CIPIX2gt1wghbDy08yE1p+/r3w==} + engines: {node: '>=6.9.0'} + + '@babel/helper-module-transforms@7.28.3': + resolution: {integrity: sha512-gytXUbs8k2sXS9PnQptz5o0QnpLL51SwASIORY6XaBKF88nsOT0Zw9szLqlSGQDP/4TljBAD5y98p2U1fqkdsw==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0 + + '@babel/helper-optimise-call-expression@7.27.1': + resolution: {integrity: sha512-URMGH08NzYFhubNSGJrpUEphGKQwMQYBySzat5cAByY1/YgIRkULnIy3tAMeszlL/so2HbeilYloUmSpd7GdVw==} + engines: {node: '>=6.9.0'} + + '@babel/helper-plugin-utils@7.27.1': + resolution: {integrity: sha512-1gn1Up5YXka3YYAHGKpbideQ5Yjf1tDa9qYcgysz+cNCXukyLl6DjPXhD3VRwSb8c0J9tA4b2+rHEZtc6R0tlw==} + engines: {node: '>=6.9.0'} + + '@babel/helper-replace-supers@7.27.1': + resolution: {integrity: sha512-7EHz6qDZc8RYS5ElPoShMheWvEgERonFCs7IAonWLLUTXW59DP14bCZt89/GKyreYn8g3S83m21FelHKbeDCKA==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0 + + '@babel/helper-skip-transparent-expression-wrappers@7.27.1': + resolution: {integrity: sha512-Tub4ZKEXqbPjXgWLl2+3JpQAYBJ8+ikpQ2Ocj/q/r0LwE3UhENh7EUabyHjz2kCEsrRY83ew2DQdHluuiDQFzg==} + engines: {node: '>=6.9.0'} + + '@babel/helper-string-parser@7.27.1': + resolution: {integrity: sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==} + engines: {node: '>=6.9.0'} + + '@babel/helper-validator-identifier@7.28.5': + resolution: {integrity: sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==} + engines: {node: '>=6.9.0'} + + '@babel/helper-validator-option@7.27.1': + resolution: {integrity: sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==} + engines: {node: '>=6.9.0'} + + '@babel/helpers@7.28.4': + resolution: {integrity: sha512-HFN59MmQXGHVyYadKLVumYsA9dBFun/ldYxipEjzA4196jpLZd8UjEEBLkbEkvfYreDqJhZxYAWFPtrfhNpj4w==} + engines: {node: '>=6.9.0'} + + '@babel/parser@7.28.5': + resolution: {integrity: sha512-KKBU1VGYR7ORr3At5HAtUQ+TV3SzRCXmA/8OdDZiLDBIZxVyzXuztPjfLd3BV1PRAQGCMWWSHYhL0F8d5uHBDQ==} + engines: {node: '>=6.0.0'} + hasBin: true + + '@babel/plugin-proposal-decorators@7.28.0': + resolution: {integrity: sha512-zOiZqvANjWDUaUS9xMxbMcK/Zccztbe/6ikvUXaG9nsPH3w6qh5UaPGAnirI/WhIbZ8m3OHU0ReyPrknG+ZKeg==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-syntax-decorators@7.27.1': + resolution: {integrity: sha512-YMq8Z87Lhl8EGkmb0MwYkt36QnxC+fzCgrl66ereamPlYToRpIk5nUjKUY3QKLWq8mwUB1BgbeXcTJhZOCDg5A==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-syntax-import-attributes@7.27.1': + resolution: {integrity: sha512-oFT0FrKHgF53f4vOsZGi2Hh3I35PfSmVs4IBFLFj4dnafP+hIWDLg3VyKmUHfLoLHlyxY4C7DGtmHuJgn+IGww==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-syntax-import-meta@7.10.4': + resolution: {integrity: sha512-Yqfm+XDx0+Prh3VSeEQCPU81yC+JWZ2pDPFSS4ZdpfZhp4MkFMaDC1UqseovEKwSUpnIL7+vK+Clp7bfh0iD7g==} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-syntax-jsx@7.27.1': + resolution: {integrity: sha512-y8YTNIeKoyhGd9O0Jiyzyyqk8gdjnumGTQPsz0xOZOQ2RmkVJeZ1vmmfIvFEKqucBG6axJGBZDE/7iI5suUI/w==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-syntax-typescript@7.27.1': + resolution: {integrity: sha512-xfYCBMxveHrRMnAWl1ZlPXOZjzkN82THFvLhQhFXFt81Z5HnN+EtUkZhv/zcKpmT3fzmWZB0ywiBrbC3vogbwQ==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-typescript@7.28.5': + resolution: {integrity: sha512-x2Qa+v/CuEoX7Dr31iAfr0IhInrVOWZU/2vJMJ00FOR/2nM0BcBEclpaf9sWCDc+v5e9dMrhSH8/atq/kX7+bA==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/template@7.27.2': + resolution: {integrity: sha512-LPDZ85aEJyYSd18/DkjNh4/y1ntkE5KwUHWTiqgRxruuZL2F1yuHligVHLvcHY2vMHXttKFpJn6LwfI7cw7ODw==} + engines: {node: '>=6.9.0'} + + '@babel/traverse@7.28.5': + resolution: {integrity: sha512-TCCj4t55U90khlYkVV/0TfkJkAkUg3jZFA3Neb7unZT8CPok7iiRfaX0F+WnqWqt7OxhOn0uBKXCw4lbL8W0aQ==} + engines: {node: '>=6.9.0'} + + '@babel/types@7.28.5': + resolution: {integrity: sha512-qQ5m48eI/MFLQ5PxQj4PFaprjyCTLI37ElWMmNs0K8Lk3dVeOdNpB3ks8jc7yM5CDmVC73eMVk/trk3fgmrUpA==} + engines: {node: '>=6.9.0'} + + '@cacheable/memoize@2.0.3': + resolution: {integrity: sha512-hl9wfQgpiydhQEIv7fkjEzTGE+tcosCXLKFDO707wYJ/78FVOlowb36djex5GdbSyeHnG62pomYLMuV/OT8Pbw==} + + '@cacheable/memory@2.0.4': + resolution: {integrity: sha512-cCmJKCKlT1t7hNBI1+gFCwmKFd9I4pS3zqBeNGXTSODnpa0EeDmORHY8oEMTuozfdg3cgsVh8ojLaPYb6eC7Cg==} + + '@cacheable/utils@2.2.0': + resolution: {integrity: sha512-7xaQayO3msdVcxXLYcLU5wDqJBNdQcPPPHr6mdTEIQI7N7TbtSVVTpWOTfjyhg0L6AQwQdq7miKdWtTDBoBldQ==} + + '@clack/core@1.5.1': + resolution: {integrity: sha512-iHTrHA8MtVuLl2TfZySmcKv1qO2PoyC9Z7pfSDozEuV5vtY3/wcOPKJXlqJ5Oq2Cx5DDGQGAMVx6HZfRRoVEbQ==} + engines: {node: '>= 20.12.0'} + + '@clack/prompts@1.8.1': + resolution: {integrity: sha512-dlT1m5e/0yUL0kRNcQn7yGLVThkgbB0Ga/1AmfDDC/8ik6AIiSf2QLQO2zPYvefsHP0aFgxO93cVLCCfDp7kzQ==} + engines: {node: '>= 20.12.0'} + + '@csstools/css-parser-algorithms@3.0.5': + resolution: {integrity: sha512-DaDeUkXZKjdGhgYaHNJTV9pV7Y9B3b644jCLs9Upc3VeNGg6LWARAT6O+Q+/COo+2gg/bM5rhpMAtf70WqfBdQ==} + engines: {node: '>=18'} + peerDependencies: + '@csstools/css-tokenizer': ^3.0.4 + + '@csstools/css-tokenizer@3.0.4': + resolution: {integrity: sha512-Vd/9EVDiu6PPJt9yAh6roZP6El1xHrdvIVGjyBsHR0RYwNHgL7FJPyIIW4fANJNG6FtyZfvlRPpFI4ZM/lubvw==} + engines: {node: '>=18'} + + '@csstools/media-query-list-parser@4.0.3': + resolution: {integrity: sha512-HAYH7d3TLRHDOUQK4mZKf9k9Ph/m8Akstg66ywKR4SFAigjs3yBiUeZtFxywiTm5moZMAp/5W/ZuFnNXXYLuuQ==} + engines: {node: '>=18'} + peerDependencies: + '@csstools/css-parser-algorithms': ^3.0.5 + '@csstools/css-tokenizer': ^3.0.4 + + '@csstools/selector-specificity@5.0.0': + resolution: {integrity: sha512-PCqQV3c4CoVm3kdPhyeZ07VmBRdH2EpMFA/pd9OASpOEC3aXNGoqPDAZ80D0cLpMBxnmk0+yNhGsEx31hq7Gtw==} + engines: {node: '>=18'} + peerDependencies: + postcss-selector-parser: ^7.0.0 + + '@dual-bundle/import-meta-resolve@4.2.1': + resolution: {integrity: sha512-id+7YRUgoUX6CgV0DtuhirQWodeeA7Lf4i2x71JS/vtA5pRb/hIGWlw+G6MeXvsM+MXrz0VAydTGElX1rAfgPg==} + + '@e18e/eslint-plugin@0.2.0': + resolution: {integrity: sha512-mXgODVwhuDjTJ+UT+XSvmMmCidtGKfrV5nMIv1UtpWex2pYLsIM3RSpT8HWIMAebS9qANbXPKlSX4BE7ZvuCgA==} + peerDependencies: + eslint: ^9.0.0 || ^10.0.0 + oxlint: ^1.41.0 + peerDependenciesMeta: + eslint: + optional: true + oxlint: + optional: true + + '@es-joy/jsdoccomment@0.86.0': + resolution: {integrity: sha512-ukZmRQ81WiTpDWO6D/cTBM7XbrNtutHKvAVnZN/8pldAwLoJArGOvkNyxPTBGsPjsoaQBJxlH+tE2TNA/92Qgw==} + engines: {node: ^20.19.0 || ^22.13.0 || >=24} + + '@es-joy/jsdoccomment@0.88.0': + resolution: {integrity: sha512-GK/HL/claLLNo5KG705auIlZMwEtmn88ofSGuLsmVZwKBqMPJhW9DiznYNq07QEqz9BPtA3LBfYImtZmhVvRAw==} + engines: {node: ^20.19.0 || ^22.13.0 || >=24} + + '@es-joy/resolve.exports@1.2.0': + resolution: {integrity: sha512-Q9hjxWI5xBM+qW2enxfe8wDKdFWMfd0Z29k5ZJnuBqD/CasY5Zryj09aCA6owbGATWz+39p5uIdaHXpopOcG8g==} + engines: {node: '>=10'} + + '@esbuild/aix-ppc64@0.25.12': + resolution: {integrity: sha512-Hhmwd6CInZ3dwpuGTF8fJG6yoWmsToE+vYgD4nytZVxcu1ulHpUQRAB1UJ8+N1Am3Mz4+xOByoQoSZf4D+CpkA==} + engines: {node: '>=18'} + cpu: [ppc64] + os: [aix] + + '@esbuild/android-arm64@0.25.12': + resolution: {integrity: sha512-6AAmLG7zwD1Z159jCKPvAxZd4y/VTO0VkprYy+3N2FtJ8+BQWFXU+OxARIwA46c5tdD9SsKGZ/1ocqBS/gAKHg==} + engines: {node: '>=18'} + cpu: [arm64] + os: [android] + + '@esbuild/android-arm@0.25.12': + resolution: {integrity: sha512-VJ+sKvNA/GE7Ccacc9Cha7bpS8nyzVv0jdVgwNDaR4gDMC/2TTRc33Ip8qrNYUcpkOHUT5OZ0bUcNNVZQ9RLlg==} + engines: {node: '>=18'} + cpu: [arm] + os: [android] + + '@esbuild/android-x64@0.25.12': + resolution: {integrity: sha512-5jbb+2hhDHx5phYR2By8GTWEzn6I9UqR11Kwf22iKbNpYrsmRB18aX/9ivc5cabcUiAT/wM+YIZ6SG9QO6a8kg==} + engines: {node: '>=18'} + cpu: [x64] + os: [android] + + '@esbuild/darwin-arm64@0.25.12': + resolution: {integrity: sha512-N3zl+lxHCifgIlcMUP5016ESkeQjLj/959RxxNYIthIg+CQHInujFuXeWbWMgnTo4cp5XVHqFPmpyu9J65C1Yg==} + engines: {node: '>=18'} + cpu: [arm64] + os: [darwin] + + '@esbuild/darwin-x64@0.25.12': + resolution: {integrity: sha512-HQ9ka4Kx21qHXwtlTUVbKJOAnmG1ipXhdWTmNXiPzPfWKpXqASVcWdnf2bnL73wgjNrFXAa3yYvBSd9pzfEIpA==} + engines: {node: '>=18'} + cpu: [x64] + os: [darwin] + + '@esbuild/freebsd-arm64@0.25.12': + resolution: {integrity: sha512-gA0Bx759+7Jve03K1S0vkOu5Lg/85dou3EseOGUes8flVOGxbhDDh/iZaoek11Y8mtyKPGF3vP8XhnkDEAmzeg==} + engines: {node: '>=18'} + cpu: [arm64] + os: [freebsd] + + '@esbuild/freebsd-x64@0.25.12': + resolution: {integrity: sha512-TGbO26Yw2xsHzxtbVFGEXBFH0FRAP7gtcPE7P5yP7wGy7cXK2oO7RyOhL5NLiqTlBh47XhmIUXuGciXEqYFfBQ==} + engines: {node: '>=18'} + cpu: [x64] + os: [freebsd] + + '@esbuild/linux-arm64@0.25.12': + resolution: {integrity: sha512-8bwX7a8FghIgrupcxb4aUmYDLp8pX06rGh5HqDT7bB+8Rdells6mHvrFHHW2JAOPZUbnjUpKTLg6ECyzvas2AQ==} + engines: {node: '>=18'} + cpu: [arm64] + os: [linux] + + '@esbuild/linux-arm@0.25.12': + resolution: {integrity: sha512-lPDGyC1JPDou8kGcywY0YILzWlhhnRjdof3UlcoqYmS9El818LLfJJc3PXXgZHrHCAKs/Z2SeZtDJr5MrkxtOw==} + engines: {node: '>=18'} + cpu: [arm] + os: [linux] + + '@esbuild/linux-ia32@0.25.12': + resolution: {integrity: sha512-0y9KrdVnbMM2/vG8KfU0byhUN+EFCny9+8g202gYqSSVMonbsCfLjUO+rCci7pM0WBEtz+oK/PIwHkzxkyharA==} + engines: {node: '>=18'} + cpu: [ia32] + os: [linux] + + '@esbuild/linux-loong64@0.25.12': + resolution: {integrity: sha512-h///Lr5a9rib/v1GGqXVGzjL4TMvVTv+s1DPoxQdz7l/AYv6LDSxdIwzxkrPW438oUXiDtwM10o9PmwS/6Z0Ng==} + engines: {node: '>=18'} + cpu: [loong64] + os: [linux] + + '@esbuild/linux-mips64el@0.25.12': + resolution: {integrity: sha512-iyRrM1Pzy9GFMDLsXn1iHUm18nhKnNMWscjmp4+hpafcZjrr2WbT//d20xaGljXDBYHqRcl8HnxbX6uaA/eGVw==} + engines: {node: '>=18'} + cpu: [mips64el] + os: [linux] + + '@esbuild/linux-ppc64@0.25.12': + resolution: {integrity: sha512-9meM/lRXxMi5PSUqEXRCtVjEZBGwB7P/D4yT8UG/mwIdze2aV4Vo6U5gD3+RsoHXKkHCfSxZKzmDssVlRj1QQA==} + engines: {node: '>=18'} + cpu: [ppc64] + os: [linux] + + '@esbuild/linux-riscv64@0.25.12': + resolution: {integrity: sha512-Zr7KR4hgKUpWAwb1f3o5ygT04MzqVrGEGXGLnj15YQDJErYu/BGg+wmFlIDOdJp0PmB0lLvxFIOXZgFRrdjR0w==} + engines: {node: '>=18'} + cpu: [riscv64] + os: [linux] + + '@esbuild/linux-s390x@0.25.12': + resolution: {integrity: sha512-MsKncOcgTNvdtiISc/jZs/Zf8d0cl/t3gYWX8J9ubBnVOwlk65UIEEvgBORTiljloIWnBzLs4qhzPkJcitIzIg==} + engines: {node: '>=18'} + cpu: [s390x] + os: [linux] + + '@esbuild/linux-x64@0.25.12': + resolution: {integrity: sha512-uqZMTLr/zR/ed4jIGnwSLkaHmPjOjJvnm6TVVitAa08SLS9Z0VM8wIRx7gWbJB5/J54YuIMInDquWyYvQLZkgw==} + engines: {node: '>=18'} + cpu: [x64] + os: [linux] + + '@esbuild/netbsd-arm64@0.25.12': + resolution: {integrity: sha512-xXwcTq4GhRM7J9A8Gv5boanHhRa/Q9KLVmcyXHCTaM4wKfIpWkdXiMog/KsnxzJ0A1+nD+zoecuzqPmCRyBGjg==} + engines: {node: '>=18'} + cpu: [arm64] + os: [netbsd] + + '@esbuild/netbsd-x64@0.25.12': + resolution: {integrity: sha512-Ld5pTlzPy3YwGec4OuHh1aCVCRvOXdH8DgRjfDy/oumVovmuSzWfnSJg+VtakB9Cm0gxNO9BzWkj6mtO1FMXkQ==} + engines: {node: '>=18'} + cpu: [x64] + os: [netbsd] + + '@esbuild/openbsd-arm64@0.25.12': + resolution: {integrity: sha512-fF96T6KsBo/pkQI950FARU9apGNTSlZGsv1jZBAlcLL1MLjLNIWPBkj5NlSz8aAzYKg+eNqknrUJ24QBybeR5A==} + engines: {node: '>=18'} + cpu: [arm64] + os: [openbsd] + + '@esbuild/openbsd-x64@0.25.12': + resolution: {integrity: sha512-MZyXUkZHjQxUvzK7rN8DJ3SRmrVrke8ZyRusHlP+kuwqTcfWLyqMOE3sScPPyeIXN/mDJIfGXvcMqCgYKekoQw==} + engines: {node: '>=18'} + cpu: [x64] + os: [openbsd] + + '@esbuild/openharmony-arm64@0.25.12': + resolution: {integrity: sha512-rm0YWsqUSRrjncSXGA7Zv78Nbnw4XL6/dzr20cyrQf7ZmRcsovpcRBdhD43Nuk3y7XIoW2OxMVvwuRvk9XdASg==} + engines: {node: '>=18'} + cpu: [arm64] + os: [openharmony] + + '@esbuild/sunos-x64@0.25.12': + resolution: {integrity: sha512-3wGSCDyuTHQUzt0nV7bocDy72r2lI33QL3gkDNGkod22EsYl04sMf0qLb8luNKTOmgF/eDEDP5BFNwoBKH441w==} + engines: {node: '>=18'} + cpu: [x64] + os: [sunos] + + '@esbuild/win32-arm64@0.25.12': + resolution: {integrity: sha512-rMmLrur64A7+DKlnSuwqUdRKyd3UE7oPJZmnljqEptesKM8wx9J8gx5u0+9Pq0fQQW8vqeKebwNXdfOyP+8Bsg==} + engines: {node: '>=18'} + cpu: [arm64] + os: [win32] + + '@esbuild/win32-ia32@0.25.12': + resolution: {integrity: sha512-HkqnmmBoCbCwxUKKNPBixiWDGCpQGVsrQfJoVGYLPT41XWF8lHuE5N6WhVia2n4o5QK5M4tYr21827fNhi4byQ==} + engines: {node: '>=18'} + cpu: [ia32] + os: [win32] + + '@esbuild/win32-x64@0.25.12': + resolution: {integrity: sha512-alJC0uCZpTFrSL0CCDjcgleBXPnCrEAhTBILpeAp7M/OFgoqtAetfBzX0xM00MUsVVPpVjlPuMbREqnZCXaTnA==} + engines: {node: '>=18'} + cpu: [x64] + os: [win32] + + '@eslint-community/eslint-plugin-eslint-comments@4.8.1': + resolution: {integrity: sha512-vafrdi15JHBFh1qWOAucyJwlEEuhdoMof28jOdKdgnwZ7bXwd6v4wzHYSmAVLwnx0yc7bqAtfJ0EZQV1WAzV/Q==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + peerDependencies: + eslint: ^6.0.0 || ^7.0.0 || ^8.0.0 || ^9.0.0 || ^10.0.0 + + '@eslint-community/eslint-utils@4.10.1': + resolution: {integrity: sha512-cuadcxVFE8sDK6iWJbs8Sn0av2Nrh2QSGQhVlBW9AaAHqHwjWsZHT8LJ4hFGPh7ASBV2deFdM7H/DPjulmh8rg==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + peerDependencies: + eslint: ^6.0.0 || ^7.0.0 || >=8.0.0 + + '@eslint-community/eslint-utils@4.9.0': + resolution: {integrity: sha512-ayVFHdtZ+hsq1t2Dy24wCmGXGe4q9Gu3smhLYALJrr473ZH27MsnSL+LKUlimp4BWJqMDMLmPpx/Q9R3OAlL4g==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + peerDependencies: + eslint: ^6.0.0 || ^7.0.0 || >=8.0.0 + + '@eslint-community/regexpp@4.12.2': + resolution: {integrity: sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew==} + engines: {node: ^12.0.0 || ^14.0.0 || >=16.0.0} + + '@eslint/compat@2.1.1': + resolution: {integrity: sha512-rMcy8GSrwNzcISX/BlTDY/GLB4eCopEuy9woIls3To+15OLxykZrxxq+WUcylCPCQ6F4MujjBM1DX5V1aqI3Vw==} + engines: {node: ^20.19.0 || ^22.13.0 || >=24} + peerDependencies: + eslint: ^8.40 || 9 || 10 + peerDependenciesMeta: + eslint: + optional: true + + '@eslint/config-array@0.21.1': + resolution: {integrity: sha512-aw1gNayWpdI/jSYVgzN5pL0cfzU02GT3NBpeT/DXbx1/1x7ZKxFPd9bwrzygx/qiwIQiJ1sw/zD8qY/kRvlGHA==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@eslint/config-helpers@0.4.2': + resolution: {integrity: sha512-gBrxN88gOIf3R7ja5K9slwNayVcZgK6SOUORm2uBzTeIEfeVaIhOpCtTox3P6R7o2jLFwLFTLnC7kU/RGcYEgw==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@eslint/config-helpers@0.5.5': + resolution: {integrity: sha512-eIJYKTCECbP/nsKaaruF6LW967mtbQbsw4JTtSVkUQc9MneSkbrgPJAbKl9nWr0ZeowV8BfsarBmPpBzGelA2w==} + engines: {node: ^20.19.0 || ^22.13.0 || >=24} + + '@eslint/core@0.17.0': + resolution: {integrity: sha512-yL/sLrpmtDaFEiUj1osRP4TI2MDz1AddJL+jZ7KSqvBuliN4xqYY54IfdN8qD8Toa6g1iloph1fxQNkjOxrrpQ==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@eslint/core@1.2.1': + resolution: {integrity: sha512-MwcE1P+AZ4C6DWlpin/OmOA54mmIZ/+xZuJiQd4SyB29oAJjN30UW9wkKNptW2ctp4cEsvhlLY/CsQ1uoHDloQ==} + engines: {node: ^20.19.0 || ^22.13.0 || >=24} + + '@eslint/eslintrc@3.3.1': + resolution: {integrity: sha512-gtF186CXhIl1p4pJNGZw8Yc6RlshoePRvE0X91oPGb3vZ8pM3qOS9W9NGPat9LziaBV7XrJWGylNQXkGcnM3IQ==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@eslint/js@9.39.1': + resolution: {integrity: sha512-S26Stp4zCy88tH94QbBv3XCuzRQiZ9yXofEILmglYTh/Ug/a9/umqvgFtYBAo3Lp0nsI/5/qH1CCrbdK3AP1Tw==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@eslint/markdown@7.5.1': + resolution: {integrity: sha512-R8uZemG9dKTbru/DQRPblbJyXpObwKzo8rv1KYGGuPUPtjM4LXBYM9q5CIZAComzZupws3tWbDwam5AFpPLyJQ==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@eslint/object-schema@2.1.7': + resolution: {integrity: sha512-VtAOaymWVfZcmZbp6E2mympDIHvyjXs/12LqWYjVw6qjrfF+VK+fyG33kChz3nnK+SU5/NeHOqrTEHS8sXO3OA==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@eslint/plugin-kit@0.4.1': + resolution: {integrity: sha512-43/qtrDUokr7LJqoF2c3+RInu/t4zfrpYdoSDfYyhg52rwLV6TnOvdG4fXm7IkSB3wErkcmJS9iEhjVtOSEjjA==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@eslint/plugin-kit@0.7.3': + resolution: {integrity: sha512-IkO+/KEUvwbVpiURZg+P7zF74z5Jxe0UgJxVni+RtoHQ6IZieXaO02kmadomap/q+l6bc/jdPGGqTjhuZnuz1Q==} + engines: {node: ^20.19.0 || ^22.13.0 || >=24} + + '@humanfs/core@0.19.1': + resolution: {integrity: sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA==} + engines: {node: '>=18.18.0'} + + '@humanfs/node@0.16.7': + resolution: {integrity: sha512-/zUx+yOsIrG4Y43Eh2peDeKCxlRt/gET6aHfaKpuq267qXdYDFViVHfMaLyygZOnl0kGWxFIgsBy8QFuTLUXEQ==} + engines: {node: '>=18.18.0'} + + '@humanwhocodes/module-importer@1.0.1': + resolution: {integrity: sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==} + engines: {node: '>=12.22'} + + '@humanwhocodes/retry@0.4.3': + resolution: {integrity: sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==} + engines: {node: '>=18.18'} + + '@iconify-json/icon-park-outline@1.2.4': + resolution: {integrity: sha512-NyZxXe2gD2TbTOyoRRMdtEJhr6i2KQCdDlYYoOn5oZLndQjwpIhw79hzeFhXvP38/o40D3gQ+l+IaSJgbB+0TQ==} + + '@iconify/types@2.0.0': + resolution: {integrity: sha512-+wluvCrRhXrhyOmRDJ3q8mux9JkKy5SJ/v8ol2tu4FVjyYvtEzkc/3pK15ET6RKg4b4w4BmTk1+gsCUhf21Ykg==} + + '@iconify/utils@3.1.7': + resolution: {integrity: sha512-JZHlwdID+dy+lTgbYC8NEC4zeugqeYsc6jewvzb4c58kHauJn+X7rNwQjxz5p2qSjqaEeQoLkCIQ9v/H4PK0/w==} + + '@iconify/vue@5.0.3': + resolution: {integrity: sha512-e9x6QZe2/HyeHUog0jzZJSZts7m/YntPYp/p4OqtZEXpHznxWnQpcX30YpuaUH43h69l3YeR1FCJ8V1t3qd7qA==} + peerDependencies: + vue: '>=3.0.0' + + '@jridgewell/gen-mapping@0.3.13': + resolution: {integrity: sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==} + + '@jridgewell/remapping@2.3.5': + resolution: {integrity: sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==} + + '@jridgewell/resolve-uri@3.1.2': + resolution: {integrity: sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==} + engines: {node: '>=6.0.0'} + + '@jridgewell/sourcemap-codec@1.5.5': + resolution: {integrity: sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==} + + '@jridgewell/trace-mapping@0.3.31': + resolution: {integrity: sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==} + + '@keyv/bigmap@1.1.0': + resolution: {integrity: sha512-MX7XIUNwVRK+hjZcAbNJ0Z8DREo+Weu9vinBOjGU1thEi9F6vPhICzBbk4CCf3eEefKRz7n6TfZXwUFZTSgj8Q==} + engines: {node: '>= 18'} + peerDependencies: + keyv: ^5.5.3 + + '@keyv/serialize@1.1.1': + resolution: {integrity: sha512-dXn3FZhPv0US+7dtJsIi2R+c7qWYiReoEh5zUntWCf4oSpMNib8FDhSoed6m3QyZdx5hK7iLFkYk3rNxwt8vTA==} + + '@nodelib/fs.scandir@2.1.5': + resolution: {integrity: sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==} + engines: {node: '>= 8'} + + '@nodelib/fs.stat@2.0.5': + resolution: {integrity: sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==} + engines: {node: '>= 8'} + + '@nodelib/fs.walk@1.2.8': + resolution: {integrity: sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==} + engines: {node: '>= 8'} + + '@ota-meshi/ast-token-store@0.3.0': + resolution: {integrity: sha512-XRO0zi2NIUKq2lUk3T1ecFSld1fMWRKE6naRFGkgkdeosx7IslyUKNv5Dcb5PJTja9tHJoFu0v/7yEpAkrkrTg==} + engines: {node: ^20.19.0 || ^22.13.0 || >=24} + + '@parcel/watcher-android-arm64@2.5.1': + resolution: {integrity: sha512-KF8+j9nNbUN8vzOFDpRMsaKBHZ/mcjEjMToVMJOhTozkDonQFFrRcfdLWn6yWKCmJKmdVxSgHiYvTCef4/qcBA==} + engines: {node: '>= 10.0.0'} + cpu: [arm64] + os: [android] + + '@parcel/watcher-darwin-arm64@2.5.1': + resolution: {integrity: sha512-eAzPv5osDmZyBhou8PoF4i6RQXAfeKL9tjb3QzYuccXFMQU0ruIc/POh30ePnaOyD1UXdlKguHBmsTs53tVoPw==} + engines: {node: '>= 10.0.0'} + cpu: [arm64] + os: [darwin] + + '@parcel/watcher-darwin-x64@2.5.1': + resolution: {integrity: sha512-1ZXDthrnNmwv10A0/3AJNZ9JGlzrF82i3gNQcWOzd7nJ8aj+ILyW1MTxVk35Db0u91oD5Nlk9MBiujMlwmeXZg==} + engines: {node: '>= 10.0.0'} + cpu: [x64] + os: [darwin] + + '@parcel/watcher-freebsd-x64@2.5.1': + resolution: {integrity: sha512-SI4eljM7Flp9yPuKi8W0ird8TI/JK6CSxju3NojVI6BjHsTyK7zxA9urjVjEKJ5MBYC+bLmMcbAWlZ+rFkLpJQ==} + engines: {node: '>= 10.0.0'} + cpu: [x64] + os: [freebsd] + + '@parcel/watcher-linux-arm-glibc@2.5.1': + resolution: {integrity: sha512-RCdZlEyTs8geyBkkcnPWvtXLY44BCeZKmGYRtSgtwwnHR4dxfHRG3gR99XdMEdQ7KeiDdasJwwvNSF5jKtDwdA==} + engines: {node: '>= 10.0.0'} + cpu: [arm] + os: [linux] + libc: [glibc] + + '@parcel/watcher-linux-arm-musl@2.5.1': + resolution: {integrity: sha512-6E+m/Mm1t1yhB8X412stiKFG3XykmgdIOqhjWj+VL8oHkKABfu/gjFj8DvLrYVHSBNC+/u5PeNrujiSQ1zwd1Q==} + engines: {node: '>= 10.0.0'} + cpu: [arm] + os: [linux] + libc: [musl] + + '@parcel/watcher-linux-arm64-glibc@2.5.1': + resolution: {integrity: sha512-LrGp+f02yU3BN9A+DGuY3v3bmnFUggAITBGriZHUREfNEzZh/GO06FF5u2kx8x+GBEUYfyTGamol4j3m9ANe8w==} + engines: {node: '>= 10.0.0'} + cpu: [arm64] + os: [linux] + libc: [glibc] + + '@parcel/watcher-linux-arm64-musl@2.5.1': + resolution: {integrity: sha512-cFOjABi92pMYRXS7AcQv9/M1YuKRw8SZniCDw0ssQb/noPkRzA+HBDkwmyOJYp5wXcsTrhxO0zq1U11cK9jsFg==} + engines: {node: '>= 10.0.0'} + cpu: [arm64] + os: [linux] + libc: [musl] + + '@parcel/watcher-linux-x64-glibc@2.5.1': + resolution: {integrity: sha512-GcESn8NZySmfwlTsIur+49yDqSny2IhPeZfXunQi48DMugKeZ7uy1FX83pO0X22sHntJ4Ub+9k34XQCX+oHt2A==} + engines: {node: '>= 10.0.0'} + cpu: [x64] + os: [linux] + libc: [glibc] + + '@parcel/watcher-linux-x64-musl@2.5.1': + resolution: {integrity: sha512-n0E2EQbatQ3bXhcH2D1XIAANAcTZkQICBPVaxMeaCVBtOpBZpWJuf7LwyWPSBDITb7In8mqQgJ7gH8CILCURXg==} + engines: {node: '>= 10.0.0'} + cpu: [x64] + os: [linux] + libc: [musl] + + '@parcel/watcher-win32-arm64@2.5.1': + resolution: {integrity: sha512-RFzklRvmc3PkjKjry3hLF9wD7ppR4AKcWNzH7kXR7GUe0Igb3Nz8fyPwtZCSquGrhU5HhUNDr/mKBqj7tqA2Vw==} + engines: {node: '>= 10.0.0'} + cpu: [arm64] + os: [win32] + + '@parcel/watcher-win32-ia32@2.5.1': + resolution: {integrity: sha512-c2KkcVN+NJmuA7CGlaGD1qJh1cLfDnQsHjE89E60vUEMlqduHGCdCLJCID5geFVM0dOtA3ZiIO8BoEQmzQVfpQ==} + engines: {node: '>= 10.0.0'} + cpu: [ia32] + os: [win32] + + '@parcel/watcher-win32-x64@2.5.1': + resolution: {integrity: sha512-9lHBdJITeNR++EvSQVUcaZoWupyHfXe1jZvGZ06O/5MflPcuPLtEphScIBL+AiCWBO46tDSHzWyD0uDmmZqsgA==} + engines: {node: '>= 10.0.0'} + cpu: [x64] + os: [win32] + + '@parcel/watcher@2.5.1': + resolution: {integrity: sha512-dfUnCxiN9H4ap84DvD2ubjw+3vUNpstxa0TneY/Paat8a3R4uQZDLSvWjmznAY/DoahqTHl9V46HF/Zs3F29pg==} + engines: {node: '>= 10.0.0'} + + '@pkgr/core@0.3.6': + resolution: {integrity: sha512-SEeaJLb3qBNF/OaXnaR1NmmBbFYk1zC0ZH/52fATcRPLFg/p791YrcyFFy44Bo9sLaGuSuLp5Q6axbb/O+v/RA==} + engines: {node: ^14.18.0 || >=16.0.0} + + '@polka/url@1.0.0-next.29': + resolution: {integrity: sha512-wwQAWhWSuHaag8c4q/KN/vCoeOJYshAIvMQwD4GpSb3OiZklFfvAgmj0VCBBImRpuF/aFgIRzllXlVX93Jevww==} + + '@rolldown/pluginutils@1.0.0-beta.29': + resolution: {integrity: sha512-NIJgOsMjbxAXvoGq/X0gD7VPMQ8j9g0BiDaNjVNVjvl+iKXxL3Jre0v31RmBYeLEmkbj2s02v8vFTbUXi5XS2Q==} + + '@rolldown/pluginutils@1.0.0-beta.47': + resolution: {integrity: sha512-8QagwMH3kNCuzD8EWL8R2YPW5e4OrHNSAHRFDdmFqEwEaD/KcNKjVoumo+gP2vW5eKB2UPbM6vTYiGZX0ixLnw==} + + '@rollup/rollup-android-arm-eabi@4.52.5': + resolution: {integrity: sha512-8c1vW4ocv3UOMp9K+gToY5zL2XiiVw3k7f1ksf4yO1FlDFQ1C2u72iACFnSOceJFsWskc2WZNqeRhFRPzv+wtQ==} + cpu: [arm] + os: [android] + + '@rollup/rollup-android-arm64@4.52.5': + resolution: {integrity: sha512-mQGfsIEFcu21mvqkEKKu2dYmtuSZOBMmAl5CFlPGLY94Vlcm+zWApK7F/eocsNzp8tKmbeBP8yXyAbx0XHsFNA==} + cpu: [arm64] + os: [android] + + '@rollup/rollup-darwin-arm64@4.52.5': + resolution: {integrity: sha512-takF3CR71mCAGA+v794QUZ0b6ZSrgJkArC+gUiG6LB6TQty9T0Mqh3m2ImRBOxS2IeYBo4lKWIieSvnEk2OQWA==} + cpu: [arm64] + os: [darwin] + + '@rollup/rollup-darwin-x64@4.52.5': + resolution: {integrity: sha512-W901Pla8Ya95WpxDn//VF9K9u2JbocwV/v75TE0YIHNTbhqUTv9w4VuQ9MaWlNOkkEfFwkdNhXgcLqPSmHy0fA==} + cpu: [x64] + os: [darwin] + + '@rollup/rollup-freebsd-arm64@4.52.5': + resolution: {integrity: sha512-QofO7i7JycsYOWxe0GFqhLmF6l1TqBswJMvICnRUjqCx8b47MTo46W8AoeQwiokAx3zVryVnxtBMcGcnX12LvA==} + cpu: [arm64] + os: [freebsd] + + '@rollup/rollup-freebsd-x64@4.52.5': + resolution: {integrity: sha512-jr21b/99ew8ujZubPo9skbrItHEIE50WdV86cdSoRkKtmWa+DDr6fu2c/xyRT0F/WazZpam6kk7IHBerSL7LDQ==} + cpu: [x64] + os: [freebsd] + + '@rollup/rollup-linux-arm-gnueabihf@4.52.5': + resolution: {integrity: sha512-PsNAbcyv9CcecAUagQefwX8fQn9LQ4nZkpDboBOttmyffnInRy8R8dSg6hxxl2Re5QhHBf6FYIDhIj5v982ATQ==} + cpu: [arm] + os: [linux] + libc: [glibc] + + '@rollup/rollup-linux-arm-musleabihf@4.52.5': + resolution: {integrity: sha512-Fw4tysRutyQc/wwkmcyoqFtJhh0u31K+Q6jYjeicsGJJ7bbEq8LwPWV/w0cnzOqR2m694/Af6hpFayLJZkG2VQ==} + cpu: [arm] + os: [linux] + libc: [musl] + + '@rollup/rollup-linux-arm64-gnu@4.52.5': + resolution: {integrity: sha512-a+3wVnAYdQClOTlyapKmyI6BLPAFYs0JM8HRpgYZQO02rMR09ZcV9LbQB+NL6sljzG38869YqThrRnfPMCDtZg==} + cpu: [arm64] + os: [linux] + libc: [glibc] + + '@rollup/rollup-linux-arm64-musl@4.52.5': + resolution: {integrity: sha512-AvttBOMwO9Pcuuf7m9PkC1PUIKsfaAJ4AYhy944qeTJgQOqJYJ9oVl2nYgY7Rk0mkbsuOpCAYSs6wLYB2Xiw0Q==} + cpu: [arm64] + os: [linux] + libc: [musl] + + '@rollup/rollup-linux-loong64-gnu@4.52.5': + resolution: {integrity: sha512-DkDk8pmXQV2wVrF6oq5tONK6UHLz/XcEVow4JTTerdeV1uqPeHxwcg7aFsfnSm9L+OO8WJsWotKM2JJPMWrQtA==} + cpu: [loong64] + os: [linux] + libc: [glibc] + + '@rollup/rollup-linux-ppc64-gnu@4.52.5': + resolution: {integrity: sha512-W/b9ZN/U9+hPQVvlGwjzi+Wy4xdoH2I8EjaCkMvzpI7wJUs8sWJ03Rq96jRnHkSrcHTpQe8h5Tg3ZzUPGauvAw==} + cpu: [ppc64] + os: [linux] + libc: [glibc] + + '@rollup/rollup-linux-riscv64-gnu@4.52.5': + resolution: {integrity: sha512-sjQLr9BW7R/ZiXnQiWPkErNfLMkkWIoCz7YMn27HldKsADEKa5WYdobaa1hmN6slu9oWQbB6/jFpJ+P2IkVrmw==} + cpu: [riscv64] + os: [linux] + libc: [glibc] + + '@rollup/rollup-linux-riscv64-musl@4.52.5': + resolution: {integrity: sha512-hq3jU/kGyjXWTvAh2awn8oHroCbrPm8JqM7RUpKjalIRWWXE01CQOf/tUNWNHjmbMHg/hmNCwc/Pz3k1T/j/Lg==} + cpu: [riscv64] + os: [linux] + libc: [musl] + + '@rollup/rollup-linux-s390x-gnu@4.52.5': + resolution: {integrity: sha512-gn8kHOrku8D4NGHMK1Y7NA7INQTRdVOntt1OCYypZPRt6skGbddska44K8iocdpxHTMMNui5oH4elPH4QOLrFQ==} + cpu: [s390x] + os: [linux] + libc: [glibc] + + '@rollup/rollup-linux-x64-gnu@4.52.5': + resolution: {integrity: sha512-hXGLYpdhiNElzN770+H2nlx+jRog8TyynpTVzdlc6bndktjKWyZyiCsuDAlpd+j+W+WNqfcyAWz9HxxIGfZm1Q==} + cpu: [x64] + os: [linux] + libc: [glibc] + + '@rollup/rollup-linux-x64-musl@4.52.5': + resolution: {integrity: sha512-arCGIcuNKjBoKAXD+y7XomR9gY6Mw7HnFBv5Rw7wQRvwYLR7gBAgV7Mb2QTyjXfTveBNFAtPt46/36vV9STLNg==} + cpu: [x64] + os: [linux] + libc: [musl] + + '@rollup/rollup-openharmony-arm64@4.52.5': + resolution: {integrity: sha512-QoFqB6+/9Rly/RiPjaomPLmR/13cgkIGfA40LHly9zcH1S0bN2HVFYk3a1eAyHQyjs3ZJYlXvIGtcCs5tko9Cw==} + cpu: [arm64] + os: [openharmony] + + '@rollup/rollup-win32-arm64-msvc@4.52.5': + resolution: {integrity: sha512-w0cDWVR6MlTstla1cIfOGyl8+qb93FlAVutcor14Gf5Md5ap5ySfQ7R9S/NjNaMLSFdUnKGEasmVnu3lCMqB7w==} + cpu: [arm64] + os: [win32] + + '@rollup/rollup-win32-ia32-msvc@4.52.5': + resolution: {integrity: sha512-Aufdpzp7DpOTULJCuvzqcItSGDH73pF3ko/f+ckJhxQyHtp67rHw3HMNxoIdDMUITJESNE6a8uh4Lo4SLouOUg==} + cpu: [ia32] + os: [win32] + + '@rollup/rollup-win32-x64-gnu@4.52.5': + resolution: {integrity: sha512-UGBUGPFp1vkj6p8wCRraqNhqwX/4kNQPS57BCFc8wYh0g94iVIW33wJtQAx3G7vrjjNtRaxiMUylM0ktp/TRSQ==} + cpu: [x64] + os: [win32] + + '@rollup/rollup-win32-x64-msvc@4.52.5': + resolution: {integrity: sha512-TAcgQh2sSkykPRWLrdyy2AiceMckNf5loITqXxFI5VuQjS5tSuw3WlwdN8qv8vzjLAUTvYaH/mVjSFpbkFbpTg==} + cpu: [x64] + os: [win32] + + '@sindresorhus/base62@1.0.0': + resolution: {integrity: sha512-TeheYy0ILzBEI/CO55CP6zJCSdSWeRtGnHy8U8dWSUH4I68iqTsy7HkMktR4xakThc9jotkPQUXT4ITdbV7cHA==} + engines: {node: '>=18'} + + '@stylistic/eslint-plugin@5.10.0': + resolution: {integrity: sha512-nPK52ZHvot8Ju/0A4ucSX1dcPV2/1clx0kLcH5wDmrE4naKso7TUC/voUyU1O9OTKTrR6MYip6LP0ogEMQ9jPQ==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + eslint: ^9.0.0 || ^10.0.0 + + '@types/debug@4.1.12': + resolution: {integrity: sha512-vIChWdVG3LG1SMxEvI/AK+FWJthlrqlTu7fbrlywTkkaONwk/UAGaULXRlf8vkzFBLVm0zkMdCquhL5aOjhXPQ==} + + '@types/estree@1.0.8': + resolution: {integrity: sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==} + + '@types/estree@1.0.9': + resolution: {integrity: sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg==} + + '@types/json-schema@7.0.15': + resolution: {integrity: sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==} + + '@types/lodash-es@4.17.12': + resolution: {integrity: sha512-0NgftHUcV4v34VhXm8QBSftKVXtbkBG3ViCjs6+eJ5a6y6Mi/jiFGPc1sC7QK+9BFhWrURE3EOggmWaSxL9OzQ==} + + '@types/lodash@4.17.20': + resolution: {integrity: sha512-H3MHACvFUEiujabxhaI/ImO6gUrd8oOurg7LQtS7mbwIXA/cUqWrvBsaeJ23aZEPk1TAYkurjfMbSELfoCXlGA==} + + '@types/mdast@4.0.4': + resolution: {integrity: sha512-kGaNbPh1k7AFzgpud/gMdvIm5xuECykRR+JnWKQno9TAXVa6WIVCGTPvYGekIDL4uwCZQSYbUxNBSb1aUo79oA==} + + '@types/ms@2.1.0': + resolution: {integrity: sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA==} + + '@types/node@24.10.0': + resolution: {integrity: sha512-qzQZRBqkFsYyaSWXuEHc2WR9c0a0CXwiE5FWUvn7ZM+vdy1uZLfCunD38UzhuB7YN/J11ndbDBcTmOdxJo9Q7A==} + + '@types/node@26.6.3': + resolution: {integrity: sha512-dsqMQQoeTLqu9wynDD00q573mNzso3IdQOAfHRJqLCcmCFPoGo9A1bDpUcv/9tnKpErQWv9uKeGfl37EIS02Yg==} + + '@types/sortablejs@1.15.9': + resolution: {integrity: sha512-7HP+rZGE2p886PKV9c9OJzLBI6BBJu1O7lJGYnPyG3fS4/duUCcngkNCjsLwIMV+WMqANe3tt4irrXHSIe68OQ==} + + '@types/unist@3.0.3': + resolution: {integrity: sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q==} + + '@types/web-bluetooth@0.0.21': + resolution: {integrity: sha512-oIQLCGWtcFZy2JW77j9k8nHzAOpqMHLQejDA48XXMWH6tjCQHz5RCFz1bzsmROyL6PUm+LLnUiI4BCn221inxA==} + + '@typescript-eslint/eslint-plugin@8.70.1': + resolution: {integrity: sha512-nDNrUQ/4ruSNYbu749TRY7cfrzPtoLHEXSNBI8aaNY32LlZCajixqRf3FqcKC4p5Cam4VOHYx/t+i5+nKXvrqA==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + '@typescript-eslint/parser': ^8.70.1 + eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 + typescript: '>=4.8.4 <6.1.0' + + '@typescript-eslint/parser@8.70.1': + resolution: {integrity: sha512-nO974WLllwhSFWQXnMLj6nDGa8f0khKEz1JzpPJ1u7Vm/4X1X6ZHajpoknU4bb41vJyMB0HHVyS2GqdhWfIXZw==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 + typescript: '>=4.8.4 <6.1.0' + + '@typescript-eslint/project-service@8.70.1': + resolution: {integrity: sha512-62xOgboPfwc3/IgPSX/W6oQR3ZbF04194FPGUGH8HL8iLFHbt/456/8Ph1wLNUgVF+s94FlHoipBsz+v7+LMnA==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + typescript: '>=4.8.4 <6.1.0' + + '@typescript-eslint/scope-manager@8.70.1': + resolution: {integrity: sha512-Pa0EeSeAusQc1WbjQMac+YfenewYTBu0KjgYvkUKwhXaHUKbFog23Dm/rp0DX/6tyYOQ3Xl1a+3EcFNZynGHCw==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@typescript-eslint/tsconfig-utils@8.70.1': + resolution: {integrity: sha512-jumze1fPI+sDOaM2TWGQdn39PDxTr7TZGeuyLkAbNyx2vtMT3uRnVKChN0hfht5V2TugphJzF6bYXvBcE09qqg==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + typescript: '>=4.8.4 <6.1.0' + + '@typescript-eslint/type-utils@8.70.1': + resolution: {integrity: sha512-7zKTnyvaVWqzLZHPFQtX1hVHqgkMC+WebPWakNCSyrQVbIP1AM0L0TlBZtACldIRb6PptI8Odk+jyZ5kP3B1VA==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 + typescript: '>=4.8.4 <6.1.0' + + '@typescript-eslint/types@8.70.1': + resolution: {integrity: sha512-Dm1ypdhhrGCTyyehxElhgJ6kgk8MVCv5qXdoOVqPr1uqk42jX8KjrZqhROvdShczA8qrDoYiOWn1ykWlx2k81Q==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@typescript-eslint/typescript-estree@8.70.1': + resolution: {integrity: sha512-TU8PwyGN0PQJUcE96mw8eCQ44SmxGdQlJmlWakHaHQ15eIuuvye5yNtmh/i6oS88jzXVQB71xdNkbkB/fMwL0g==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + typescript: '>=4.8.4 <6.1.0' + + '@typescript-eslint/utils@8.70.1': + resolution: {integrity: sha512-Esgul8MsnKnRLdYU2Eb2cRV9bS5HJYtKj1ByJnOzzG2M58DGdSUQ1jUuILxipqcpB2h9WLrbD5GijIWUjX/Tqw==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 + typescript: '>=4.8.4 <6.1.0' + + '@typescript-eslint/visitor-keys@8.70.1': + resolution: {integrity: sha512-Vwj9lUIW5Xq3wQ9w6gv3R86g1hMK8f2zNOdGTAgeXUMMXFK78G9ruCjjqutHMNJc0+CH7LYRnHeUB9IT8wFmcw==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@vitejs/plugin-vue-jsx@5.1.1': + resolution: {integrity: sha512-uQkfxzlF8SGHJJVH966lFTdjM/lGcwJGzwAHpVqAPDD/QcsqoUGa+q31ox1BrUfi+FLP2ChVp7uLXE3DkHyDdQ==} + engines: {node: ^20.19.0 || >=22.12.0} + peerDependencies: + vite: ^5.0.0 || ^6.0.0 || ^7.0.0 + vue: ^3.0.0 + + '@vitejs/plugin-vue@6.0.1': + resolution: {integrity: sha512-+MaE752hU0wfPFJEUAIxqw18+20euHHdxVtMvbFcOEpjEyfqXH/5DCoTHiVJ0J29EhTJdoTkjEv5YBKU9dnoTw==} + engines: {node: ^20.19.0 || >=22.12.0} + peerDependencies: + vite: ^5.0.0 || ^6.0.0 || ^7.0.0 + vue: ^3.2.25 + + '@vitest/eslint-plugin@1.6.27': + resolution: {integrity: sha512-X1RCAfwbatG4GFbJ/1PIHP9MSZMt0JJThqbYID+vS4L783k6QGlLPe421wQE8dHXuGCcenVLsydiM4qzsYWxhg==} + engines: {node: '>=18'} + peerDependencies: + '@typescript-eslint/eslint-plugin': '*' + eslint: '>=8.57.0' + typescript: '>=5.0.0' + vitest: '*' + peerDependenciesMeta: + '@typescript-eslint/eslint-plugin': + optional: true + typescript: + optional: true + vitest: + optional: true + + '@volar/language-core@2.4.23': + resolution: {integrity: sha512-hEEd5ET/oSmBC6pi1j6NaNYRWoAiDhINbT8rmwtINugR39loROSlufGdYMF9TaKGfz+ViGs1Idi3mAhnuPcoGQ==} + + '@volar/source-map@2.4.23': + resolution: {integrity: sha512-Z1Uc8IB57Lm6k7q6KIDu/p+JWtf3xsXJqAX/5r18hYOTpJyBn0KXUR8oTJ4WFYOcDzWC9n3IflGgHowx6U6z9Q==} + + '@volar/typescript@2.4.23': + resolution: {integrity: sha512-lAB5zJghWxVPqfcStmAP1ZqQacMpe90UrP5RJ3arDyrhy4aCUQqmxPPLB2PWDKugvylmO41ljK7vZ+t6INMTag==} + + '@vue/babel-helper-vue-transform-on@1.5.0': + resolution: {integrity: sha512-0dAYkerNhhHutHZ34JtTl2czVQHUNWv6xEbkdF5W+Yrv5pCWsqjeORdOgbtW2I9gWlt+wBmVn+ttqN9ZxR5tzA==} + + '@vue/babel-plugin-jsx@1.5.0': + resolution: {integrity: sha512-mneBhw1oOqCd2247O0Yw/mRwC9jIGACAJUlawkmMBiNmL4dGA2eMzuNZVNqOUfYTa6vqmND4CtOPzmEEEqLKFw==} + peerDependencies: + '@babel/core': ^7.0.0-0 + peerDependenciesMeta: + '@babel/core': + optional: true + + '@vue/babel-plugin-resolve-type@1.5.0': + resolution: {integrity: sha512-Wm/60o+53JwJODm4Knz47dxJnLDJ9FnKnGZJbUUf8nQRAtt6P+undLUAVU3Ha33LxOJe6IPoifRQ6F/0RrU31w==} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@vue/compiler-core@3.5.23': + resolution: {integrity: sha512-nW7THWj5HOp085ROk65LwaoxuzDsjIxr485F4iu63BoxsXoSqKqmsUUoP4A7Gl67DgIgi0zJ8JFgHfvny/74MA==} + + '@vue/compiler-dom@3.5.23': + resolution: {integrity: sha512-AT8RMw0vEzzzO0JU5gY0F6iCzaWUIh/aaRVordzMBKXRpoTllTT4kocHDssByPsvodNCfump/Lkdow2mT/O5KQ==} + + '@vue/compiler-sfc@3.5.23': + resolution: {integrity: sha512-3QTEUo4qg7FtQwaDJa8ou1CUikx5WTtZlY61rRRDu3lK2ZKrGoAGG8mvDgOpDsQ4A1bez9s+WtBB6DS2KuFCPw==} + + '@vue/compiler-ssr@3.5.23': + resolution: {integrity: sha512-Hld2xphbMjXs9Q9WKxPf2EqmE+Rq/FEDnK/wUBtmYq74HCV4XDdSCheAaB823OQXIIFGq9ig/RbAZkF9s4U0Ow==} + + '@vue/devtools-api@6.6.4': + resolution: {integrity: sha512-sGhTPMuXqZ1rVOk32RylztWkfXTRhuS7vgAKv0zjqk8gbsHkJ7xfFf+jbySxt7tWObEJwyKaHMikV/WGDiQm8g==} + + '@vue/devtools-api@7.7.7': + resolution: {integrity: sha512-lwOnNBH2e7x1fIIbVT7yF5D+YWhqELm55/4ZKf45R9T8r9dE2AIOy8HKjfqzGsoTHFbWbr337O4E0A0QADnjBg==} + + '@vue/devtools-core@8.0.5': + resolution: {integrity: sha512-dpCw8nl0GDBuiL9SaY0mtDxoGIEmU38w+TQiYEPOLhW03VDC0lfNMYXS/qhl4I0YlysGp04NLY4UNn6xgD0VIQ==} + peerDependencies: + vue: ^3.0.0 + + '@vue/devtools-kit@7.7.7': + resolution: {integrity: sha512-wgoZtxcTta65cnZ1Q6MbAfePVFxfM+gq0saaeytoph7nEa7yMXoi6sCPy4ufO111B9msnw0VOWjPEFCXuAKRHA==} + + '@vue/devtools-kit@8.0.5': + resolution: {integrity: sha512-q2VV6x1U3KJMTQPUlRMyWEKVbcHuxhqJdSr6Jtjz5uAThAIrfJ6WVZdGZm5cuO63ZnSUz0RCsVwiUUb0mDV0Yg==} + + '@vue/devtools-shared@7.7.7': + resolution: {integrity: sha512-+udSj47aRl5aKb0memBvcUG9koarqnxNM5yjuREvqwK6T3ap4mn3Zqqc17QrBFTqSMjr3HK1cvStEZpMDpfdyw==} + + '@vue/devtools-shared@8.0.5': + resolution: {integrity: sha512-bRLn6/spxpmgLk+iwOrR29KrYnJjG9DGpHGkDFG82UM21ZpJ39ztUT9OXX3g+usW7/b2z+h46I9ZiYyB07XMXg==} + + '@vue/language-core@3.1.3': + resolution: {integrity: sha512-KpR1F/eGAG9D1RZ0/T6zWJs6dh/pRLfY5WupecyYKJ1fjVmDMgTPw9wXmKv2rBjo4zCJiOSiyB8BDP1OUwpMEA==} + peerDependencies: + typescript: '*' + peerDependenciesMeta: + typescript: + optional: true + + '@vue/reactivity@3.5.23': + resolution: {integrity: sha512-ji5w0qvrPyBmBx5Ldv4QGNsw0phgRreEvjt0iUf1lei2Sm8//9ZAi78uM2ZjsT5gk0YZilLuoRCIMvtuZlHMJw==} + + '@vue/runtime-core@3.5.23': + resolution: {integrity: sha512-LMB0S6/G7mFJcpQeQaZrbsthFbWrIX8FVTzu5x9U3Ec8YW5MY1CGAnBBHNj+TPOBu3pIbtPpjrXtcaN04X+aBw==} + + '@vue/runtime-dom@3.5.23': + resolution: {integrity: sha512-r/PYc8W9THzEL0UExpTkV+d31zO+Jid/RMZIDG6aS/NekOEUHuCJkJgftySWZw7JTJO/+q9Kxkg8p+i7Q7Q+ew==} + + '@vue/server-renderer@3.5.23': + resolution: {integrity: sha512-NiWZsNCsXA20/VufcrW5u+Trt/PyFlpMmxaB2KERYM8eZgUoKUjXxJQb9ypq+LZ0Sp3XHJGNBR8DkhRnkKAMUw==} + peerDependencies: + vue: 3.5.23 + + '@vue/shared@3.5.23': + resolution: {integrity: sha512-0YZ1DYuC5o/YJPf6pFdt2KYxVGDxkDbH/1NYJnVJWUkzr8ituBEmFVQRNX2gCaAsFEjEDnLkWpgqlZA7htgS/g==} + + '@vue/tsconfig@0.7.0': + resolution: {integrity: sha512-ku2uNz5MaZ9IerPPUyOHzyjhXoX2kVJaVf7hL315DC17vS6IiZRmmCPfggNbU16QTvM80+uYYy3eYJB59WCtvg==} + peerDependencies: + typescript: 5.x + vue: ^3.4.0 + peerDependenciesMeta: + typescript: + optional: true + vue: + optional: true + + '@vueuse/core@13.9.0': + resolution: {integrity: sha512-ts3regBQyURfCE2BcytLqzm8+MmLlo5Ln/KLoxDVcsZ2gzIwVNnQpQOL/UKV8alUqjSZOlpFZcRNsLRqj+OzyA==} + peerDependencies: + vue: ^3.5.0 + + '@vueuse/metadata@13.9.0': + resolution: {integrity: sha512-1AFRvuiGphfF7yWixZa0KwjYH8ulyjDCC0aFgrGRz8+P4kvDFSdXLVfTk5xAN9wEuD1J6z4/myMoYbnHoX07zg==} + + '@vueuse/shared@13.9.0': + resolution: {integrity: sha512-e89uuTLMh0U5cZ9iDpEI2senqPGfbPRTHM/0AaQkcxnpqjkZqDYP8rpfm7edOz8s+pOCOROEy1PIveSW8+fL5g==} + peerDependencies: + vue: ^3.5.0 + + acorn-jsx@5.3.2: + resolution: {integrity: sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==} + peerDependencies: + acorn: ^6.0.0 || ^7.0.0 || ^8.0.0 + + acorn@8.15.0: + resolution: {integrity: sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==} + engines: {node: '>=0.4.0'} + hasBin: true + + acorn@8.18.0: + resolution: {integrity: sha512-lGq+9yr1/GuAWaVYIHRjvvySG5/4VfKIvC8EWxStPdcDh/Ka7FG3twP6v4d5BkravUilhIAsG4Qj83t02LWUPQ==} + engines: {node: '>=0.4.0'} + hasBin: true + + ajv@6.12.6: + resolution: {integrity: sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==} + + ajv@8.17.1: + resolution: {integrity: sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==} + + alien-signals@3.0.6: + resolution: {integrity: sha512-gCs0YqC1mkYGC6IRXsSrA62ShOSv1FlVN5tRp/Cs2vRWLK/BAeluWIdfsl253pFQPznKEvRmHhfep7crWfyfWQ==} + + animate.css@4.1.1: + resolution: {integrity: sha512-+mRmCTv6SbCmtYJCN4faJMNFVNN5EuCTTprDTAo7YzIGji2KADmakjVA3+8mVDkZ2Bf09vayB35lSQIex2+QaQ==} + + ansi-regex@5.0.1: + resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==} + engines: {node: '>=8'} + + ansi-styles@4.3.0: + resolution: {integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==} + engines: {node: '>=8'} + + ansis@4.2.0: + resolution: {integrity: sha512-HqZ5rWlFjGiV0tDm3UxxgNRqsOTniqoKZu0pIAfh7TZQMGuZK+hH0drySty0si0QXj1ieop4+SkSfPZBPPkHig==} + engines: {node: '>=14'} + + anymatch@3.1.3: + resolution: {integrity: sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==} + engines: {node: '>= 8'} + + are-docs-informative@0.0.2: + resolution: {integrity: sha512-ixiS0nLNNG5jNQzgZJNoUpBKdo9yTYZMGJ+QgT2jmjR7G7+QHRCc4v6LQ3NgE7EBJq+o0ams3waJwkrlBom8Ig==} + engines: {node: '>=14'} + + argparse@2.0.1: + resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==} + + array-union@2.1.0: + resolution: {integrity: sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==} + engines: {node: '>=8'} + + astral-regex@2.0.0: + resolution: {integrity: sha512-Z7tMw1ytTXt5jqMcOP+OQteU1VuNK9Y02uuJtKQ1Sv69jXQKKg5cibLwGJow8yzZP+eAc18EmLGPal0bp36rvQ==} + engines: {node: '>=8'} + + asynckit@0.4.0: + resolution: {integrity: sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==} + + axios@0.27.2: + resolution: {integrity: sha512-t+yRIyySRTp/wua5xEr+z1q60QmLq8ABsS5O9Me1AsE5dfKqgnCFzwiCZZ/cGNd1lq4/7akDWMxdhVlucjmnOQ==} + + b-tween@0.3.3: + resolution: {integrity: sha512-oEHegcRpA7fAuc9KC4nktucuZn2aS8htymCPcP3qkEGPqiBH+GfqtqoG2l7LxHngg6O0HFM7hOeOYExl1Oz4ZA==} + + b-validate@1.5.3: + resolution: {integrity: sha512-iCvCkGFskbaYtfQ0a3GmcQCHl/Sv1GufXFGuUQ+FE+WJa7A/espLOuFIn09B944V8/ImPj71T4+rTASxO2PAuA==} + + balanced-match@1.0.2: + resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} + + balanced-match@2.0.0: + resolution: {integrity: sha512-1ugUSr8BHXRnK23KfuYS+gVMC3LB8QGH9W1iGtDPsNWoQbgtXSExkBu2aDR4epiGWZOjZsj6lDl/N/AqqTC3UA==} + + balanced-match@4.0.4: + resolution: {integrity: sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==} + engines: {node: 18 || 20 || >=22} + + baseline-browser-mapping@2.8.25: + resolution: {integrity: sha512-2NovHVesVF5TXefsGX1yzx1xgr7+m9JQenvz6FQY3qd+YXkKkYiv+vTCc7OriP9mcDZpTC5mAOYN4ocd29+erA==} + hasBin: true + + binary-extensions@2.3.0: + resolution: {integrity: sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==} + engines: {node: '>=8'} + + birpc@2.7.0: + resolution: {integrity: sha512-tub/wFGH49vNCm0xraykcY3TcRgX/3JsALYq/Lwrtti+bTyFHkCUAWF5wgYoie8P41wYwig2mIKiqoocr1EkEQ==} + + boolbase@1.0.0: + resolution: {integrity: sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==} + + brace-expansion@1.1.12: + resolution: {integrity: sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==} + + brace-expansion@5.0.12: + resolution: {integrity: sha512-YovQ3rzhaLMIrDjNDMkNS01tea93qhEhG5xy8f6+R0l+dw3Ki+5sCoIoI942iuLZTHWogWktgwVDhU09iNEimQ==} + engines: {node: 20 || >=22} + + braces@3.0.3: + resolution: {integrity: sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==} + engines: {node: '>=8'} + + browserslist@4.27.0: + resolution: {integrity: sha512-AXVQwdhot1eqLihwasPElhX2tAZiBjWdJ9i/Zcj2S6QYIjkx62OKSfnobkriB81C3l4w0rVy3Nt4jaTBltYEpw==} + engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7} + hasBin: true + + builtin-modules@5.0.0: + resolution: {integrity: sha512-bkXY9WsVpY7CvMhKSR6pZilZu9Ln5WDrKVBUXf2S443etkmEO4V58heTecXcUIsNsi4Rx8JUO4NfX1IcQl4deg==} + engines: {node: '>=18.20'} + + bundle-name@4.1.0: + resolution: {integrity: sha512-tjwM5exMg6BGRI+kNmTntNsvdZS1X8BFYS6tnJ2hdH0kVxM6/eVZ2xy+FqStSWvYmtfFMDLIxurorHwDKfDz5Q==} + engines: {node: '>=18'} + + cac@7.0.0: + resolution: {integrity: sha512-tixWYgm5ZoOD+3g6UTea91eow5z6AAHaho3g0V9CNSNb45gM8SmflpAc+GRd1InC4AqN/07Unrgp56Y94N9hJQ==} + engines: {node: '>=20.19.0'} + + cacheable@2.1.1: + resolution: {integrity: sha512-LmF4AXiSNdiRbI2UjH8pAp9NIXxeQsTotpEaegPiDcnN0YPygDJDV3l/Urc0mL72JWdATEorKqIHEx55nDlONg==} + + call-bind-apply-helpers@1.0.2: + resolution: {integrity: sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==} + engines: {node: '>= 0.4'} + + callsites@3.1.0: + resolution: {integrity: sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==} + engines: {node: '>=6'} + + caniuse-lite@1.0.30001754: + resolution: {integrity: sha512-x6OeBXueoAceOmotzx3PO4Zpt4rzpeIFsSr6AAePTZxSkXiYDUmpypEl7e2+8NCd9bD7bXjqyef8CJYPC1jfxg==} + + ccount@2.0.1: + resolution: {integrity: sha512-eyrF0jiFpY+3drT6383f1qhkbGsLSifNAjA61IUjZjmLCWjItY6LB9ft9YhoDgwfmclB2zhu51Lc7+95b8NRAg==} + + chalk@4.1.2: + resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==} + engines: {node: '>=10'} + + change-case@5.4.4: + resolution: {integrity: sha512-HRQyTk2/YPEkt9TnUPbOpr64Uw3KOicFWPVBb+xiHvd6eBx/qPr9xqfBFDT8P2vWsvvz4jbEkfDe71W3VyNu2w==} + + character-entities@2.0.2: + resolution: {integrity: sha512-shx7oQ0Awen/BRIdkjkvz54PnEEI/EjwXDSIZp86/KKdbafHh1Df/RYGBhn4hbe2+uKC9FnT5UCEdyPz3ai9hQ==} + + chokidar@3.6.0: + resolution: {integrity: sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==} + engines: {node: '>= 8.10.0'} + + chokidar@4.0.3: + resolution: {integrity: sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==} + engines: {node: '>= 14.16.0'} + + ci-info@4.3.1: + resolution: {integrity: sha512-Wdy2Igu8OcBpI2pZePZ5oWjPC38tmDVx5WKUXKwlLYkA0ozo85sLsLvkBbBn/sZaSCMFOGZJ14fvW9t5/d7kdA==} + engines: {node: '>=8'} + + clean-regexp@1.0.0: + resolution: {integrity: sha512-GfisEZEJvzKrmGWkvfhgzcz/BllN1USeqD2V6tg14OAOgaCD2Z/PUEuxnAZ/nPvmaHRG7a8y77p1T/IRQ4D1Hw==} + engines: {node: '>=4'} + + color-convert@1.9.3: + resolution: {integrity: sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==} + + color-convert@2.0.1: + resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==} + engines: {node: '>=7.0.0'} + + color-name@1.1.3: + resolution: {integrity: sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==} + + color-name@1.1.4: + resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==} + + color-string@1.9.1: + resolution: {integrity: sha512-shrVawQFojnZv6xM40anx4CkoDP+fZsw/ZerEMsW/pyzsRbElpsL/DBVW7q3ExxwusdNXI3lXpuhEZkzs8p5Eg==} + + color@3.2.1: + resolution: {integrity: sha512-aBl7dZI9ENN6fUGC7mWpMTPNHmWUSNan9tuWN6ahh5ZLNk9baLJOnSMlrQkHcrfFgz2/RigjUVAjdx36VcemKA==} + + colord@2.9.3: + resolution: {integrity: sha512-jeC1axXpnb0/2nn/Y1LPuLdgXBLH7aDcHu4KEKfqw3CUhX7ZpfBSlPKyqXE6btIgEzfWtrX3/tyBCaCvXvMkOw==} + + combined-stream@1.0.8: + resolution: {integrity: sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==} + engines: {node: '>= 0.8'} + + comment-parser@1.4.1: + resolution: {integrity: sha512-buhp5kePrmda3vhc5B9t7pUQXAb2Tnd0qgpkIhPhkHXxJpiPJ11H0ZEU0oBpJ2QztSbzG/ZxMj/CHsYJqRHmyg==} + engines: {node: '>= 12.0.0'} + + comment-parser@1.4.6: + resolution: {integrity: sha512-ObxuY6vnbWTN6Od72xfwN9DbzC7Y2vv8u1Soi9ahRKL37gb6y1qk6/dgjs+3JWuXJHWvsg3BXIwzd/rkmAwavg==} + engines: {node: '>= 12.0.0'} + + comment-parser@1.4.7: + resolution: {integrity: sha512-0h+uSNtQGW3D98eQt3jJ8L06Fves8hncB4V/PKdw/Qb8Hnk19VaKuTr55UNRYiSoVa7WwrFls+rh3ux9agmkeQ==} + engines: {node: '>= 12.0.0'} + + compute-scroll-into-view@1.0.20: + resolution: {integrity: sha512-UCB0ioiyj8CRjtrvaceBLqqhZCVP+1B8+NWQhmdsm0VXOJtobBCf1dBQmebCCo34qZmUwZfIH2MZLqNHazrfjg==} + + concat-map@0.0.1: + resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==} + + confbox@0.1.8: + resolution: {integrity: sha512-RMtmw0iFkeR4YV+fUOSucriAQNb9g8zFR52MWCtl+cCZOFRNL6zeB395vPzFhEjjn4fMxXudmELnl/KF/WrK6w==} + + confbox@0.2.2: + resolution: {integrity: sha512-1NB+BKqhtNipMsov4xI/NnhCKp9XG9NamYp5PVm9klAT0fsrNPjaFICsCFhNhwZJKNh7zB/3q8qXz0E9oaMNtQ==} + + convert-source-map@2.0.0: + resolution: {integrity: sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==} + + copy-anything@2.0.6: + resolution: {integrity: sha512-1j20GZTsvKNkc4BY3NpMOM8tt///wY3FpIzozTOFO2ffuZcV61nojHXVKIy3WM+7ADCy5FVhdZYHYDdgTU0yJw==} + + copy-anything@4.0.5: + resolution: {integrity: sha512-7Vv6asjS4gMOuILabD3l739tsaxFQmC+a7pLZm02zyvs8p977bL3zEgq3yDk5rn9B0PbYgIv++jmHcuUab4RhA==} + engines: {node: '>=18'} + + core-js-compat@3.46.0: + resolution: {integrity: sha512-p9hObIIEENxSV8xIu+V68JjSeARg6UVMG5mR+JEUguG3sI6MsiS1njz2jHmyJDvA+8jX/sytkBHup6kxhM9law==} + + cosmiconfig@9.0.0: + resolution: {integrity: sha512-itvL5h8RETACmOTFc4UfIyB2RfEHi71Ax6E/PivVxq9NseKbOWpeyHEOIbmAw1rs8Ak0VursQNww7lf7YtUwzg==} + engines: {node: '>=14'} + peerDependencies: + typescript: '>=4.9.5' + peerDependenciesMeta: + typescript: + optional: true + + cross-spawn@7.0.6: + resolution: {integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==} + engines: {node: '>= 8'} + + css-functions-list@3.2.3: + resolution: {integrity: sha512-IQOkD3hbR5KrN93MtcYuad6YPuTSUhntLHDuLEbFWE+ff2/XSZNdZG+LcbbIW5AXKg/WFIfYItIzVoHngHXZzA==} + engines: {node: '>=12 || >=16'} + + css-tree@3.1.0: + resolution: {integrity: sha512-0eW44TGN5SQXU1mWSkKwFstI/22X2bG1nYzZTYMAWjylYURhse752YgbE4Cx46AC+bAvI+/dYTPRk1LqSUnu6w==} + engines: {node: ^10 || ^12.20.0 || ^14.13.0 || >=15.0.0} + + cssesc@3.0.0: + resolution: {integrity: sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==} + engines: {node: '>=4'} + hasBin: true + + csstype@3.1.3: + resolution: {integrity: sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==} + + dayjs@1.11.19: + resolution: {integrity: sha512-t5EcLVS6QPBNqM2z8fakk/NKel+Xzshgt8FFKAn+qwlD1pzZWxh0nVCrvFK7ZDb6XucZeF9z8C7CBWTRIVApAw==} + + debug@4.4.3: + resolution: {integrity: sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==} + engines: {node: '>=6.0'} + peerDependencies: + supports-color: '*' + peerDependenciesMeta: + supports-color: + optional: true + + decode-named-character-reference@1.2.0: + resolution: {integrity: sha512-c6fcElNV6ShtZXmsgNgFFV5tVX2PaV4g+MOAkb8eXHvn6sryJBrZa9r0zV6+dtTyoCKxtDy5tyQ5ZwQuidtd+Q==} + + deep-is@0.1.4: + resolution: {integrity: sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==} + + default-browser-id@5.0.1: + resolution: {integrity: sha512-x1VCxdX4t+8wVfd1so/9w+vQ4vx7lKd2Qp5tDRutErwmR85OgmfX7RlLRMWafRMY7hbEiXIbudNrjOAPa/hL8Q==} + engines: {node: '>=18'} + + default-browser@5.4.0: + resolution: {integrity: sha512-XDuvSq38Hr1MdN47EDvYtx3U0MTqpCEn+F6ft8z2vYDzMrvQhVp0ui9oQdqW3MvK3vqUETglt1tVGgjLuJ5izg==} + engines: {node: '>=18'} + + define-lazy-prop@3.0.0: + resolution: {integrity: sha512-N+MeXYoqr3pOgn8xfyRPREN7gHakLYjhsHhWGT3fWAiL4IkAt0iDw14QiiEm2bE30c5XX5q0FtAA3CK5f9/BUg==} + engines: {node: '>=12'} + + defu@6.1.4: + resolution: {integrity: sha512-mEQCMmwJu317oSz8CwdIOdwf3xMif1ttiM8LTufzc3g6kR+9Pe236twL8j3IYT1F7GfRgGcW6MWxzZjLIkuHIg==} + + delayed-stream@1.0.0: + resolution: {integrity: sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==} + engines: {node: '>=0.4.0'} + + dequal@2.0.3: + resolution: {integrity: sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==} + engines: {node: '>=6'} + + detect-libc@1.0.3: + resolution: {integrity: sha512-pGjwhsmsp4kL2RTz08wcOlGN83otlqHeD/Z5T8GXZB+/YcpQ/dgo+lbU8ZsGxV0HIvqqxo9l7mqYwyYMD9bKDg==} + engines: {node: '>=0.10'} + hasBin: true + + devlop@1.1.0: + resolution: {integrity: sha512-RWmIqhcFf1lRYBvNmr7qTNuyCt/7/ns2jbpp1+PalgE/rDQcBT0fioSMUpJ93irlUhC5hrg4cYqe6U+0ImW0rA==} + + diff-sequences@29.6.3: + resolution: {integrity: sha512-EjePK1srD3P08o2j4f0ExnylqRs5B9tJjcp9t1krH2qRi8CCdsYfwe9JgSLurFBWwq4uOlipzfk5fHNvwFKr8Q==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + dir-glob@3.0.1: + resolution: {integrity: sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==} + engines: {node: '>=8'} + + dom-serializer@2.0.0: + resolution: {integrity: sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg==} + + domelementtype@2.3.0: + resolution: {integrity: sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==} + + domhandler@5.0.3: + resolution: {integrity: sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w==} + engines: {node: '>= 4'} + + domutils@3.2.2: + resolution: {integrity: sha512-6kZKyUajlDuqlHKVX1w7gyslj9MPIXzIFiz/rGu35uC1wMi+kMhQwGhl4lt9unC9Vb9INnY9Z3/ZA3+FhASLaw==} + + dunder-proto@1.0.1: + resolution: {integrity: sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==} + engines: {node: '>= 0.4'} + + echarts@6.1.0: + resolution: {integrity: sha512-q0yaFPggC9FUdsWH4blavRWFmxdrIodbkoKNAjJudAI6CA9gNPxHtV2RcZNEepZVlk4yvBYkOkbk6HIVpIyHZA==} + + electron-to-chromium@1.5.245: + resolution: {integrity: sha512-rdmGfW47ZhL/oWEJAY4qxRtdly2B98ooTJ0pdEI4jhVLZ6tNf8fPtov2wS1IRKwFJT92le3x4Knxiwzl7cPPpQ==} + + emoji-regex@8.0.0: + resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==} + + empathic@2.0.0: + resolution: {integrity: sha512-i6UzDscO/XfAcNYD75CfICkmfLedpyPDdozrLMmQc5ORaQcdMoc21OnlEylMIqI7U8eniKrPMxxtj8k0vhmJhA==} + engines: {node: '>=14'} + + empathic@2.1.0: + resolution: {integrity: sha512-AnfC1ATldl49/cvZdLPDjBfrRNwbDO05aibiOtzQu3qtlbJtomNLhF30HEtn/7iBz50dlMECqATo3fG0LrdEgw==} + engines: {node: '>=14'} + + enhanced-resolve@5.18.3: + resolution: {integrity: sha512-d4lC8xfavMeBjzGr2vECC3fsGXziXZQyJxD868h2M/mBI3PwAuODxAkLkq5HYuvrPYcUtiLzsTo8U3PgX3Ocww==} + engines: {node: '>=10.13.0'} + + entities@4.5.0: + resolution: {integrity: sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==} + engines: {node: '>=0.12'} + + env-paths@2.2.1: + resolution: {integrity: sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A==} + engines: {node: '>=6'} + + errno@0.1.8: + resolution: {integrity: sha512-dJ6oBr5SQ1VSd9qkk7ByRgb/1SH4JZjCHSW/mr63/QcXO9zLVxvJ6Oy13nio03rxpSnVDDjFor75SjVeZWPW/A==} + hasBin: true + + error-ex@1.3.4: + resolution: {integrity: sha512-sqQamAnR14VgCr1A618A3sGrygcpK+HEbenA/HiEAkkUwcZIIB/tgWqHFxWgOyDh4nB4JCRimh79dR5Ywc9MDQ==} + + error-stack-parser-es@1.0.5: + resolution: {integrity: sha512-5qucVt2XcuGMcEGgWI7i+yZpmpByQ8J1lHhcL7PwqCwu9FPP3VUXzT4ltHe5i2z9dePwEHcDVOAfSnHsOlCXRA==} + + es-define-property@1.0.1: + resolution: {integrity: sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==} + engines: {node: '>= 0.4'} + + es-errors@1.3.0: + resolution: {integrity: sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==} + engines: {node: '>= 0.4'} + + es-object-atoms@1.1.1: + resolution: {integrity: sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==} + engines: {node: '>= 0.4'} + + es-set-tostringtag@2.1.0: + resolution: {integrity: sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==} + engines: {node: '>= 0.4'} + + esbuild@0.25.12: + resolution: {integrity: sha512-bbPBYYrtZbkt6Os6FiTLCTFxvq4tt3JKall1vRwshA3fdVztsLAatFaZobhkBC8/BrPetoa0oksYoKXoG4ryJg==} + engines: {node: '>=18'} + hasBin: true + + escalade@3.2.0: + resolution: {integrity: sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==} + engines: {node: '>=6'} + + escape-string-regexp@1.0.5: + resolution: {integrity: sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==} + engines: {node: '>=0.8.0'} + + escape-string-regexp@4.0.0: + resolution: {integrity: sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==} + engines: {node: '>=10'} + + escape-string-regexp@5.0.0: + resolution: {integrity: sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw==} + engines: {node: '>=12'} + + eslint-compat-utils@0.5.1: + resolution: {integrity: sha512-3z3vFexKIEnjHE3zCMRo6fn/e44U7T1khUjg+Hp0ZQMCigh28rALD0nPFBcGZuiLC5rLZa2ubQHDRln09JfU2Q==} + engines: {node: '>=12'} + peerDependencies: + eslint: '>=6.0.0' + + eslint-config-flat-gitignore@2.4.0: + resolution: {integrity: sha512-JhYC+V7qEDiCDsbJcVP8fxNc62fk4+i91YLP/YvmVHlkaQ8Xo99olYN8MO5COdxl7onGlsrfZVONe5NYsZP1UA==} + peerDependencies: + eslint: ^9.5.0 || ^10.0.0 + + eslint-flat-config-utils@3.2.0: + resolution: {integrity: sha512-PHgo1X5uqIorJONLVD9BIaOSdoYFD3z/AeJljdqDPlWVRpeCYkDbK9k0AXoYVqqNJr6FEYIEr5Rm2TSktLQcHw==} + + eslint-json-compat-utils@0.2.3: + resolution: {integrity: sha512-RbBmDFyu7FqnjE8F0ZxPNzx5UaptdeS9Uu50r7A+D7s/+FCX+ybiyViYEgFUaFIFqSWJgZRTpL5d8Kanxxl2lQ==} + engines: {node: '>=12'} + peerDependencies: + '@eslint/json': '*' + eslint: '*' + jsonc-eslint-parser: ^2.4.0 || ^3.0.0 + peerDependenciesMeta: + '@eslint/json': + optional: true + + eslint-merge-processors@2.0.0: + resolution: {integrity: sha512-sUuhSf3IrJdGooquEUB5TNpGNpBoQccbnaLHsb1XkBLUPPqCNivCpY05ZcpCOiV9uHwO2yxXEWVczVclzMxYlA==} + peerDependencies: + eslint: '*' + + eslint-plugin-antfu@3.2.3: + resolution: {integrity: sha512-U2fnz/H0gFPxpuC7QpaHa0Jv2AgCZ5hunp36SOP/yWo8yFzgvMh8X4pZ4uN4IKoqtBhk7G3HuVa93Urf51+sZg==} + peerDependencies: + eslint: '*' + + eslint-plugin-command@3.5.3: + resolution: {integrity: sha512-JIhmUOW2K2Dw1K+p4mtj5potkqTZ4ZaicrTkgCygPZCWErP2+O2F46n7ZwGKapeEjiwcVqoY8u01SxNUUXWPeQ==} + peerDependencies: + '@typescript-eslint/typescript-estree': '*' + '@typescript-eslint/utils': '*' + eslint: '*' + + eslint-plugin-depend@1.5.0: + resolution: {integrity: sha512-i3UeLYmclf1Icp35+6W7CR4Bp2PIpDgBuf/mpmXK5UeLkZlvYJ21VuQKKHHAIBKRTPivPGX/gZl5JGno1o9Y0A==} + peerDependencies: + eslint: '>=8.40.0' + + eslint-plugin-es-x@7.8.0: + resolution: {integrity: sha512-7Ds8+wAAoV3T+LAKeu39Y5BzXCrGKrcISfgKEqTS4BDN8SFEDQd0S43jiQ8vIa3wUKD07qitZdfzlenSi8/0qQ==} + engines: {node: ^14.18.0 || >=16.0.0} + peerDependencies: + eslint: '>=8' + + eslint-plugin-import-lite@0.5.2: + resolution: {integrity: sha512-XvfdWOC5dSLEI9krIPRlNmKSI2ViIE9pVylzfV9fCq0ZpDaNeUk6o0wZv0OzN83QdadgXp1NsY0qjLINxwYCsw==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + eslint: '>=9.0.0' + + eslint-plugin-jsdoc@62.9.0: + resolution: {integrity: sha512-PY7/X4jrVgoIDncUmITlUqK546Ltmx/Pd4Hdsu4CvSjryQZJI2mEV4vrdMufyTetMiZ5taNSqvK//BTgVUlNkA==} + engines: {node: ^20.19.0 || ^22.13.0 || >=24} + peerDependencies: + eslint: ^7.0.0 || ^8.0.0 || ^9.0.0 || ^10.0.0 + + eslint-plugin-jsonc@3.4.2: + resolution: {integrity: sha512-q5xzjCYFQuhFomTbctXzd3STfDJ8XduvaigjQmMEbP2gzSKrc58y3Fv6D775/cIK1/sRUn+4kpljiU2iW8k0zw==} + engines: {node: ^20.19.0 || ^22.13.0 || >=24} + peerDependencies: + eslint: '>=9.38.0' + + eslint-plugin-n@17.24.0: + resolution: {integrity: sha512-/gC7/KAYmfNnPNOb3eu8vw+TdVnV0zhdQwexsw6FLXbhzroVj20vRn2qL8lDWDGnAQ2J8DhdfvXxX9EoxvERvw==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + eslint: '>=8.23.0' + + eslint-plugin-no-only-tests@3.3.0: + resolution: {integrity: sha512-brcKcxGnISN2CcVhXJ/kEQlNa0MEfGRtwKtWA16SkqXHKitaKIMrfemJKLKX1YqDU5C/5JY3PvZXd5jEW04e0Q==} + engines: {node: '>=5.0.0'} + + eslint-plugin-perfectionist@5.12.1: + resolution: {integrity: sha512-krdSE7Dz+TCtcVr2+rfNhlOIFFBJcUdoRvjaN7JQnsaVnxJw+BZh9wMkY3MSLCJat+yW+UPTQLKS82Epc7MGZA==} + engines: {node: ^20.0.0 || >=22.0.0} + peerDependencies: + eslint: ^8.45.0 || ^9.0.0 || ^10.0.0 + peerDependenciesMeta: + eslint: + optional: true + + eslint-plugin-pnpm@1.9.1: + resolution: {integrity: sha512-8++1Egww2cqc9Wylmt7P+xXNMQVCosAZ4m4W4Gc0V5lGea8HFByHTRvUI+ZvvpO8fLXGg2O4MQc90EhuI3mMdQ==} + peerDependencies: + eslint: ^9.0.0 || ^10.0.0 + + eslint-plugin-regexp@3.3.1: + resolution: {integrity: sha512-58bGUci/AaWAo1ETj6Lsnmy6QXwnSyVONRA805828nnqp7vrTwdaQMc02vtowmYB3A5OB3SLr3r5HgmfxX4dLg==} + engines: {node: ^20.19.0 || ^22.13.0 || >=24} + peerDependencies: + eslint: '>=9.38.0' + + eslint-plugin-toml@1.5.0: + resolution: {integrity: sha512-qBjRywEkKxO2uYOjus//6GVF1r+Hg5QDkRO8RTY6XcaXgWfBU0DhwpFmJa2Ljf0Sz49r7DdZlpKwwHmJ4nmH1Q==} + engines: {node: ^20.19.0 || ^22.13.0 || >=24} + peerDependencies: + eslint: '>=9.38.0' + + eslint-plugin-unicorn@63.0.0: + resolution: {integrity: sha512-Iqecl9118uQEXYh7adylgEmGfkn5es3/mlQTLLkd4pXkIk9CTGrAbeUux+YljSa2ohXCBmQQ0+Ej1kZaFgcfkA==} + engines: {node: ^20.10.0 || >=21.0.0} + peerDependencies: + eslint: '>=9.38.0' + + eslint-plugin-unused-imports@4.4.1: + resolution: {integrity: sha512-oZGYUz1X3sRMGUB+0cZyK2VcvRX5lm/vB56PgNNcU+7ficUCKm66oZWKUubXWnOuPjQ8PvmXtCViXBMONPe7tQ==} + peerDependencies: + '@typescript-eslint/eslint-plugin': ^8.0.0-0 || ^7.0.0 || ^6.0.0 || ^5.0.0 + eslint: ^10.0.0 || ^9.0.0 || ^8.0.0 + peerDependenciesMeta: + '@typescript-eslint/eslint-plugin': + optional: true + + eslint-plugin-vue@10.11.1: + resolution: {integrity: sha512-pasVXhl7wUmIvhujoqs/m0mJ9r59QW1IR9XXYKGR0FETzSM+Hhlf4qKIYgWiDDP+9YKv4YfbwskD7LqIdVcgQw==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + '@stylistic/eslint-plugin': ^2.0.0 || ^3.0.0 || ^4.0.0 || ^5.0.0 + '@typescript-eslint/parser': ^7.0.0 || ^8.0.0 + eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 + vue-eslint-parser: ^10.3.0 + peerDependenciesMeta: + '@stylistic/eslint-plugin': + optional: true + '@typescript-eslint/parser': + optional: true + + eslint-plugin-yml@3.8.1: + resolution: {integrity: sha512-E/70psRwxz5EJ8dBtzrFfqSiiInuSytbdtFCjueb/GcKjsWzPBfxfhtQePImMAPjHwMA/VDurO7DJwStDJ4wWg==} + engines: {node: ^20.19.0 || ^22.13.0 || >=24.0.0} + peerDependencies: + eslint: '>=9.38.0' + + eslint-processor-vue-blocks@2.0.0: + resolution: {integrity: sha512-u4W0CJwGoWY3bjXAuFpc/b6eK3NQEI8MoeW7ritKj3G3z/WtHrKjkqf+wk8mPEy5rlMGS+k6AZYOw2XBoN/02Q==} + peerDependencies: + '@vue/compiler-sfc': ^3.3.0 + eslint: '>=9.0.0' + + eslint-scope@8.4.0: + resolution: {integrity: sha512-sNXOfKCn74rt8RICKMvJS7XKV/Xk9kA7DyJr8mJik3S7Cwgy3qlkkmyS2uQB3jiJg6VNdZd/pDBJu0nvG2NlTg==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + eslint-visitor-keys@3.4.3: + resolution: {integrity: sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + + eslint-visitor-keys@4.2.1: + resolution: {integrity: sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + eslint-visitor-keys@5.0.1: + resolution: {integrity: sha512-tD40eHxA35h0PEIZNeIjkHoDR4YjjJp34biM0mDvplBe//mB+IHCqHDGV7pxF+7MklTvighcCPPZC7ynWyjdTA==} + engines: {node: ^20.19.0 || ^22.13.0 || >=24} + + eslint@9.39.1: + resolution: {integrity: sha512-BhHmn2yNOFA9H9JmmIVKJmd288g9hrVRDkdoIgRCRuSySRUHH7r/DI6aAXW9T1WwUuY3DFgrcaqB+deURBLR5g==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + hasBin: true + peerDependencies: + jiti: '*' + peerDependenciesMeta: + jiti: + optional: true + + espree@10.4.0: + resolution: {integrity: sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + espree@11.2.0: + resolution: {integrity: sha512-7p3DrVEIopW1B1avAGLuCSh1jubc01H2JHc8B4qqGblmg5gI9yumBgACjWo4JlIc04ufug4xJ3SQI8HkS/Rgzw==} + engines: {node: ^20.19.0 || ^22.13.0 || >=24} + + esquery@1.6.0: + resolution: {integrity: sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg==} + engines: {node: '>=0.10'} + + esquery@1.7.0: + resolution: {integrity: sha512-Ap6G0WQwcU/LHsvLwON1fAQX9Zp0A2Y6Y/cJBl9r/JbW90Zyg4/zbG6zzKa2OTALELarYHmKu0GhpM5EO+7T0g==} + engines: {node: '>=0.10'} + + esrecurse@4.3.0: + resolution: {integrity: sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==} + engines: {node: '>=4.0'} + + estraverse@5.3.0: + resolution: {integrity: sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==} + engines: {node: '>=4.0'} + + estree-walker@2.0.2: + resolution: {integrity: sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==} + + estree-walker@3.0.3: + resolution: {integrity: sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==} + + esutils@2.0.3: + resolution: {integrity: sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==} + engines: {node: '>=0.10.0'} + + exsolve@1.0.7: + resolution: {integrity: sha512-VO5fQUzZtI6C+vx4w/4BWJpg3s/5l+6pRQEHzFRM8WFi4XffSP1Z+4qi7GbjWbvRQEbdIco5mIMq+zX4rPuLrw==} + + fast-deep-equal@3.1.3: + resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==} + + fast-glob@3.3.3: + resolution: {integrity: sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==} + engines: {node: '>=8.6.0'} + + fast-json-stable-stringify@2.1.0: + resolution: {integrity: sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==} + + fast-levenshtein@2.0.6: + resolution: {integrity: sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==} + + fast-string-truncated-width@3.0.3: + resolution: {integrity: sha512-0jjjIEL6+0jag3l2XWWizO64/aZVtpiGE3t0Zgqxv0DPuxiMjvB3M24fCyhZUO4KomJQPj3LTSUnDP3GpdwC0g==} + + fast-string-width@3.0.2: + resolution: {integrity: sha512-gX8LrtNEI5hq8DVUfRQMbr5lpaS4nMIWV+7XEbXk2b8kiQIizgnlr12B4dA3ZEx3308ze0O4Q1R+cHts8kyUJg==} + + fast-uri@3.1.0: + resolution: {integrity: sha512-iPeeDKJSWf4IEOasVVrknXpaBV0IApz/gp7S2bb7Z4Lljbl2MGJRqInZiUrQwV16cpzw/D3S5j5Julj/gT52AA==} + + fast-wrap-ansi@0.2.2: + resolution: {integrity: sha512-7F2Fl+TjRSenLqlU3UjSH0iyqopqoZIu7eZVpEirP2g1GtWa2G/ecEmBdgz31+Mxr+ELclgg6sokpSFIQiZ02Q==} + + fastest-levenshtein@1.0.16: + resolution: {integrity: sha512-eRnCtTTtGZFpQCwhJiUOuxPQWRXVKYDn0b2PeHfXL6/Zi53SLAzAHfVhVWK2AryC/WH05kGfxhFIPvTF0SXQzg==} + engines: {node: '>= 4.9.1'} + + fastq@1.19.1: + resolution: {integrity: sha512-GwLTyxkCXjXbxqIhTsMI2Nui8huMPtnxg7krajPJAjnEG/iiOS7i+zCtWGZR9G0NBKbXKh6X9m9UIsYX/N6vvQ==} + + fault@2.0.1: + resolution: {integrity: sha512-WtySTkS4OKev5JtpHXnib4Gxiurzh5NCGvWrFaZ34m6JehfTUhKZvn9njTfw48t6JumVQOmrKqpmGcdwxnhqBQ==} + + fdir@6.5.0: + resolution: {integrity: sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==} + engines: {node: '>=12.0.0'} + peerDependencies: + picomatch: ^3 || ^4 + peerDependenciesMeta: + picomatch: + optional: true + + file-entry-cache@10.1.4: + resolution: {integrity: sha512-5XRUFc0WTtUbjfGzEwXc42tiGxQHBmtbUG1h9L2apu4SulCGN3Hqm//9D6FAolf8MYNL7f/YlJl9vy08pj5JuA==} + + file-entry-cache@8.0.0: + resolution: {integrity: sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==} + engines: {node: '>=16.0.0'} + + fill-range@7.1.1: + resolution: {integrity: sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==} + engines: {node: '>=8'} + + find-up-simple@1.0.1: + resolution: {integrity: sha512-afd4O7zpqHeRyg4PfDQsXmlDe2PfdHtJt6Akt8jOWaApLOZk5JXs6VMR29lz03pRe9mpykrRCYIYxaJYcfpncQ==} + engines: {node: '>=18'} + + find-up@5.0.0: + resolution: {integrity: sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==} + engines: {node: '>=10'} + + flat-cache@4.0.1: + resolution: {integrity: sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==} + engines: {node: '>=16'} + + flat-cache@6.1.18: + resolution: {integrity: sha512-JUPnFgHMuAVmLmoH9/zoZ6RHOt5n9NlUw/sDXsTbROJ2SFoS2DS4s+swAV6UTeTbGH/CAsZIE6M8TaG/3jVxgQ==} + + flatted@3.3.3: + resolution: {integrity: sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg==} + + follow-redirects@1.15.11: + resolution: {integrity: sha512-deG2P0JfjrTxl50XGCDyfI97ZGVCxIpfKYmfyrQ54n5FO/0gfIES8C/Psl6kWVDolizcaaxZJnTS0QSMxvnsBQ==} + engines: {node: '>=4.0'} + peerDependencies: + debug: '*' + peerDependenciesMeta: + debug: + optional: true + + form-data@4.0.4: + resolution: {integrity: sha512-KrGhL9Q4zjj0kiUt5OO4Mr/A/jlI2jDYs5eHBpYHPcBEVSiipAvn2Ko2HnPe20rmcuuvMHNdZFp+4IlGTMF0Ow==} + engines: {node: '>= 6'} + + format@0.2.2: + resolution: {integrity: sha512-wzsgA6WOq+09wrU1tsJ09udeR/YZRaeArL9e1wPbFg3GG2yDnC2ldKpxs4xunpFF9DgqCqOIra3bc1HWrJ37Ww==} + engines: {node: '>=0.4.x'} + + fs-extra@10.1.0: + resolution: {integrity: sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ==} + engines: {node: '>=12'} + + fsevents@2.3.3: + resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==} + engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} + os: [darwin] + + function-bind@1.1.2: + resolution: {integrity: sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==} + + gensync@1.0.0-beta.2: + resolution: {integrity: sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==} + engines: {node: '>=6.9.0'} + + get-intrinsic@1.3.0: + resolution: {integrity: sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==} + engines: {node: '>= 0.4'} + + get-proto@1.0.1: + resolution: {integrity: sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==} + engines: {node: '>= 0.4'} + + get-tsconfig@4.13.0: + resolution: {integrity: sha512-1VKTZJCwBrvbd+Wn3AOgQP/2Av+TfTCOlE4AcRJE72W1ksZXbAx8PPBR9RzgTeSPzlPMHrbANMH3LbltH73wxQ==} + + github-slugger@2.0.0: + resolution: {integrity: sha512-IaOQ9puYtjrkq7Y0Ygl9KDZnrf/aiUJYUpVf89y8kyaxbRG7Y1SrX/jaumrv81vc61+kiMempujsM3Yw7w5qcw==} + + glob-parent@5.1.2: + resolution: {integrity: sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==} + engines: {node: '>= 6'} + + glob-parent@6.0.2: + resolution: {integrity: sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==} + engines: {node: '>=10.13.0'} + + global-modules@2.0.0: + resolution: {integrity: sha512-NGbfmJBp9x8IxyJSd1P+otYK8vonoJactOogrVfFRIAEY1ukil8RSKDz2Yo7wh1oihl51l/r6W4epkeKJHqL8A==} + engines: {node: '>=6'} + + global-prefix@3.0.0: + resolution: {integrity: sha512-awConJSVCHVGND6x3tmMaKcQvwXLhjdkmomy2W+Goaui8YPgYgXJZewhg3fWC+DlfqqQuWg8AwqjGTD2nAPVWg==} + engines: {node: '>=6'} + + globals@14.0.0: + resolution: {integrity: sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==} + engines: {node: '>=18'} + + globals@15.15.0: + resolution: {integrity: sha512-7ACyT3wmyp3I61S4fG682L0VA2RGD9otkqGJIwNUMF1SWUombIIk+af1unuDYgMm082aHYwD+mzJvv9Iu8dsgg==} + engines: {node: '>=18'} + + globals@16.5.0: + resolution: {integrity: sha512-c/c15i26VrJ4IRt5Z89DnIzCGDn9EcebibhAOjw5ibqEHsE1wLUgkPn9RDmNcUKyU87GeaL633nyJ+pplFR2ZQ==} + engines: {node: '>=18'} + + globals@17.12.0: + resolution: {integrity: sha512-cezEd/DTyyht9cvSSURyygXPfy04GtWO/5e6ZPvH7fCtjKz9PYOmuawphw1Ctd1f6C+5JypXfGD7ahNMXvevBA==} + engines: {node: '>=18'} + + globby@11.1.0: + resolution: {integrity: sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==} + engines: {node: '>=10'} + + globjoin@0.1.4: + resolution: {integrity: sha512-xYfnw62CKG8nLkZBfWbhWwDw02CHty86jfPcc2cr3ZfeuK9ysoVPPEUxf21bAD/rWAgk52SuBrLJlefNy8mvFg==} + + globrex@0.1.2: + resolution: {integrity: sha512-uHJgbwAMwNFf5mLst7IWLNg14x1CkeqglJb/K3doi4dw6q2IvAAmM/Y81kevy83wP+Sst+nutFTYOGg3d1lsxg==} + + gopd@1.2.0: + resolution: {integrity: sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==} + engines: {node: '>= 0.4'} + + graceful-fs@4.2.11: + resolution: {integrity: sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==} + + has-flag@4.0.0: + resolution: {integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==} + engines: {node: '>=8'} + + has-symbols@1.1.0: + resolution: {integrity: sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==} + engines: {node: '>= 0.4'} + + has-tostringtag@1.0.2: + resolution: {integrity: sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==} + engines: {node: '>= 0.4'} + + hasown@2.0.2: + resolution: {integrity: sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==} + engines: {node: '>= 0.4'} + + hookable@5.5.3: + resolution: {integrity: sha512-Yc+BQe8SvoXH1643Qez1zqLRmbA5rCL+sSmk6TVos0LWVfNIB7PGncdlId77WzLGSIB5KaWgTaNTs2lNVEI6VQ==} + + hookified@1.12.2: + resolution: {integrity: sha512-aokUX1VdTpI0DUsndvW+OiwmBpKCu/NgRsSSkuSY0zq8PY6Q6a+lmOfAFDXAAOtBqJELvcWY9L1EVtzjbQcMdg==} + + html-entities@2.6.0: + resolution: {integrity: sha512-kig+rMn/QOVRvr7c86gQ8lWXq+Hkv6CbAH1hLu+RG338StTpE8Z0b44SDVaqVu7HGKf27frdmUYEs9hTUX/cLQ==} + + html-tags@3.3.1: + resolution: {integrity: sha512-ztqyC3kLto0e9WbNp0aeP+M3kTt+nbaIveGmUxAtZa+8iFgKLUOD4YKM5j+f3QD89bra7UeumolZHKuOXnTmeQ==} + engines: {node: '>=8'} + + htmlparser2@8.0.2: + resolution: {integrity: sha512-GYdjWKDkbRLkZ5geuHs5NY1puJ+PXwP7+fHPRz06Eirsb9ugf6d8kkXav6ADhcODhFFPMIXyxkxSuMf3D6NCFA==} + + iconv-lite@0.6.3: + resolution: {integrity: sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==} + engines: {node: '>=0.10.0'} + + ignore@5.3.2: + resolution: {integrity: sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==} + engines: {node: '>= 4'} + + ignore@7.0.5: + resolution: {integrity: sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg==} + engines: {node: '>= 4'} + + image-size@0.5.5: + resolution: {integrity: sha512-6TDAlDPZxUFCv+fuOkIoXT/V/f3Qbq8e37p+YOiYrUv3v9cc3/6x78VdfPgFVaB9dZYeLUfKgHRebpkm/oP2VQ==} + engines: {node: '>=0.10.0'} + hasBin: true + + immutable@5.1.4: + resolution: {integrity: sha512-p6u1bG3YSnINT5RQmx/yRZBpenIl30kVxkTLDyHLIMk0gict704Q9n+thfDI7lTRm9vXdDYutVzXhzcThxTnXA==} + + import-fresh@3.3.1: + resolution: {integrity: sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==} + engines: {node: '>=6'} + + import-meta-resolve@4.2.0: + resolution: {integrity: sha512-Iqv2fzaTQN28s/FwZAoFq0ZSs/7hMAHJVX+w8PZl3cY19Pxk6jFFalxQoIfW2826i/fDLXv8IiEZRIT0lDuWcg==} + + imurmurhash@0.1.4: + resolution: {integrity: sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==} + engines: {node: '>=0.8.19'} + + indent-string@5.0.0: + resolution: {integrity: sha512-m6FAo/spmsW2Ab2fU35JTYwtOKa2yAwXSwgjSv1TJzh4Mh7mC3lzAOVLBprb72XsTrgkEIsl7YrFNAiDiRhIGg==} + engines: {node: '>=12'} + + ini@1.3.8: + resolution: {integrity: sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==} + + is-arrayish@0.2.1: + resolution: {integrity: sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==} + + is-arrayish@0.3.4: + resolution: {integrity: sha512-m6UrgzFVUYawGBh1dUsWR5M2Clqic9RVXC/9f8ceNlv2IcO9j9J/z8UoCLPqtsPBFNzEpfR3xftohbfqDx8EQA==} + + is-binary-path@2.1.0: + resolution: {integrity: sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==} + engines: {node: '>=8'} + + is-builtin-module@5.0.0: + resolution: {integrity: sha512-f4RqJKBUe5rQkJ2eJEJBXSticB3hGbN9j0yxxMQFqIW89Jp9WYFtzfTcRlstDKVUTRzSOTLKRfO9vIztenwtxA==} + engines: {node: '>=18.20'} + + is-docker@3.0.0: + resolution: {integrity: sha512-eljcgEDlEns/7AXFosB5K/2nCM4P7FQPkGc/DWLy5rmFEWvZayGrik1d9/QIY5nJ4f9YsVvBkA6kJpHn9rISdQ==} + engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + hasBin: true + + is-extglob@2.1.1: + resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==} + engines: {node: '>=0.10.0'} + + is-fullwidth-code-point@3.0.0: + resolution: {integrity: sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==} + engines: {node: '>=8'} + + is-glob@4.0.3: + resolution: {integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==} + engines: {node: '>=0.10.0'} + + is-inside-container@1.0.0: + resolution: {integrity: sha512-KIYLCCJghfHZxqjYBE7rEy0OBuTd5xCHS7tHVgvCLkx7StIoaxwNW3hCALgEUjFfeRk+MG/Qxmp/vtETEF3tRA==} + engines: {node: '>=14.16'} + hasBin: true + + is-number@7.0.0: + resolution: {integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==} + engines: {node: '>=0.12.0'} + + is-plain-object@5.0.0: + resolution: {integrity: sha512-VRSzKkbMm5jMDoKLbltAkFQ5Qr7VDiTFGXxYFXXowVj387GeGNOCsOH6Msy00SGZ3Fp84b1Naa1psqgcCIEP5Q==} + engines: {node: '>=0.10.0'} + + is-what@3.14.1: + resolution: {integrity: sha512-sNxgpk9793nzSs7bA6JQJGeIuRBQhAaNGG77kzYQgMkrID+lS6SlK07K5LaptscDlSaIgH+GPFzf+d75FVxozA==} + + is-what@5.5.0: + resolution: {integrity: sha512-oG7cgbmg5kLYae2N5IVd3jm2s+vldjxJzK1pcu9LfpGuQ93MQSzo0okvRna+7y5ifrD+20FE8FvjusyGaz14fw==} + engines: {node: '>=18'} + + is-wsl@3.1.0: + resolution: {integrity: sha512-UcVfVfaK4Sc4m7X3dUSoHoozQGBEFeDC+zVo06t98xe8CzHSZZBekNXH+tu0NalHolcJ/QAGqS46Hef7QXBIMw==} + engines: {node: '>=16'} + + isexe@2.0.0: + resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==} + + js-tokens@4.0.0: + resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==} + + js-tokens@9.0.1: + resolution: {integrity: sha512-mxa9E9ITFOt0ban3j6L5MpjwegGz6lBQmM1IJkWeBZGcMxto50+eWdjC/52xDbS2vy0k7vIMK0Fe2wfL9OQSpQ==} + + js-yaml@4.1.0: + resolution: {integrity: sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==} + hasBin: true + + jsdoc-type-pratt-parser@7.2.0: + resolution: {integrity: sha512-dh140MMgjyg3JhJZY/+iEzW+NO5xR2gpbDFKHqotCmexElVntw7GjWjt511+C/Ef02RU5TKYrJo/Xlzk+OLaTw==} + engines: {node: '>=20.0.0'} + + jsdoc-type-pratt-parser@9.2.2: + resolution: {integrity: sha512-eFY7Rlscvv/BpY6UsVsjKwMhu1tJO/F4U/vC9g3yeRy/JqszuO1UP6Vdq+85G+3FP6fEw/sWR8a5ftPFY86csw==} + engines: {node: ^22.22.2 || >=24.15.0} + + jsencrypt@3.5.4: + resolution: {integrity: sha512-kNjfYEMNASxrDGsmcSQh/rUTmcoRfSUkxnAz+MMywM8jtGu+fFEZ3nJjHM58zscVnwR0fYmG9sGkTDjqUdpiwA==} + + jsesc@3.1.0: + resolution: {integrity: sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==} + engines: {node: '>=6'} + hasBin: true + + json-buffer@3.0.1: + resolution: {integrity: sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==} + + json-parse-even-better-errors@2.3.1: + resolution: {integrity: sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==} + + json-schema-traverse@0.4.1: + resolution: {integrity: sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==} + + json-schema-traverse@1.0.0: + resolution: {integrity: sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==} + + json-stable-stringify-without-jsonify@1.0.1: + resolution: {integrity: sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==} + + json5@2.2.3: + resolution: {integrity: sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==} + engines: {node: '>=6'} + hasBin: true + + jsonc-eslint-parser@3.3.0: + resolution: {integrity: sha512-hYTGkHGNRZnXOFZ1urhINADoqDrGfpy53cjw+dxk84QE0pUDujQzeUeamNs6Mz44/TKD49z2x6/GVSu4ZrtA+Q==} + engines: {node: ^20.19.0 || ^22.13.0 || >=24} + + jsonfile@6.2.0: + resolution: {integrity: sha512-FGuPw30AdOIUTRMC2OMRtQV+jkVj2cfPqSeWXv1NEAJ1qZ5zb1X6z1mFhbfOB/iy3ssJCD+3KuZ8r8C3uVFlAg==} + + keyv@4.5.4: + resolution: {integrity: sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==} + + keyv@5.5.3: + resolution: {integrity: sha512-h0Un1ieD+HUrzBH6dJXhod3ifSghk5Hw/2Y4/KHBziPlZecrFyE9YOTPU6eOs0V9pYl8gOs86fkr/KN8lUX39A==} + + kind-of@6.0.3: + resolution: {integrity: sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==} + engines: {node: '>=0.10.0'} + + known-css-properties@0.36.0: + resolution: {integrity: sha512-A+9jP+IUmuQsNdsLdcg6Yt7voiMF/D4K83ew0OpJtpu+l34ef7LaohWV0Rc6KNvzw6ZDizkqfyB5JznZnzuKQA==} + + known-css-properties@0.37.0: + resolution: {integrity: sha512-JCDrsP4Z1Sb9JwG0aJ8Eo2r7k4Ou5MwmThS/6lcIe1ICyb7UBJKGRIUUdqc2ASdE/42lgz6zFUnzAIhtXnBVrQ==} + + kolorist@1.8.0: + resolution: {integrity: sha512-Y+60/zizpJ3HRH8DCss+q95yr6145JXZo46OTpFvDZWLfRCE4qChOyk1b26nMaNpfHHgxagk9dXT5OP0Tfe+dQ==} + + less@4.4.2: + resolution: {integrity: sha512-j1n1IuTX1VQjIy3tT7cyGbX7nvQOsFLoIqobZv4ttI5axP923gA44zUj6miiA6R5Aoms4sEGVIIcucXUbRI14g==} + engines: {node: '>=14'} + hasBin: true + + levn@0.4.1: + resolution: {integrity: sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==} + engines: {node: '>= 0.8.0'} + + lines-and-columns@1.2.4: + resolution: {integrity: sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==} + + local-pkg@1.1.2: + resolution: {integrity: sha512-arhlxbFRmoQHl33a0Zkle/YWlmNwoyt6QNZEIJcqNbdrsix5Lvc4HyyI3EnwxTYlZYc32EbYrQ8SzEZ7dqgg9A==} + engines: {node: '>=14'} + + locate-path@6.0.0: + resolution: {integrity: sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==} + engines: {node: '>=10'} + + lodash-es@4.17.21: + resolution: {integrity: sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw==} + + lodash.merge@4.6.2: + resolution: {integrity: sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==} + + lodash.truncate@4.4.2: + resolution: {integrity: sha512-jttmRe7bRse52OsWIMDLaXxWqRAmtIUccAQ3garviCqJjafXOfNMO0yMfNpdD6zbGaTU0P5Nz7e7gAT6cKmJRw==} + + longest-streak@3.1.0: + resolution: {integrity: sha512-9Ri+o0JYgehTaVBBDoMqIl8GXtbWg711O3srftcHhZ0dqnETqLaoIK0x17fUw9rFSlK/0NlsKe0Ahhyl5pXE2g==} + + lru-cache@5.1.1: + resolution: {integrity: sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==} + + magic-string@0.30.21: + resolution: {integrity: sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==} + + make-dir@2.1.0: + resolution: {integrity: sha512-LS9X+dc8KLxXCb8dni79fLIIUA5VyZoyjSMCwTluaXA0o27cCK0bhXkpgw+sTXVpPy/lSO57ilRixqk0vDmtRA==} + engines: {node: '>=6'} + + markdown-table@3.0.4: + resolution: {integrity: sha512-wiYz4+JrLyb/DqW2hkFJxP7Vd7JuTDm77fvbM8VfEQdmSMqcImWeeRbHwZjBjIFki/VaMK2BhFi7oUUZeM5bqw==} + + math-intrinsics@1.1.0: + resolution: {integrity: sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==} + engines: {node: '>= 0.4'} + + mathml-tag-names@2.1.3: + resolution: {integrity: sha512-APMBEanjybaPzUrfqU0IMU5I0AswKMH7k8OTLs0vvV4KZpExkTkY87nR/zpbuTPj+gARop7aGUbl11pnDfW6xg==} + + mdast-util-find-and-replace@3.0.2: + resolution: {integrity: sha512-Tmd1Vg/m3Xz43afeNxDIhWRtFZgM2VLyaf4vSTYwudTyeuTneoL3qtWMA5jeLyz/O1vDJmmV4QuScFCA2tBPwg==} + + mdast-util-from-markdown@2.0.2: + resolution: {integrity: sha512-uZhTV/8NBuw0WHkPTrCqDOl0zVe1BIng5ZtHoDk49ME1qqcjYmmLmOf0gELgcRMxN4w2iuIeVso5/6QymSrgmA==} + + mdast-util-frontmatter@2.0.1: + resolution: {integrity: sha512-LRqI9+wdgC25P0URIJY9vwocIzCcksduHQ9OF2joxQoyTNVduwLAFUzjoopuRJbJAReaKrNQKAZKL3uCMugWJA==} + + mdast-util-gfm-autolink-literal@2.0.1: + resolution: {integrity: sha512-5HVP2MKaP6L+G6YaxPNjuL0BPrq9orG3TsrZ9YXbA3vDw/ACI4MEsnoDpn6ZNm7GnZgtAcONJyPhOP8tNJQavQ==} + + mdast-util-gfm-footnote@2.1.0: + resolution: {integrity: sha512-sqpDWlsHn7Ac9GNZQMeUzPQSMzR6Wv0WKRNvQRg0KqHh02fpTz69Qc1QSseNX29bhz1ROIyNyxExfawVKTm1GQ==} + + mdast-util-gfm-strikethrough@2.0.0: + resolution: {integrity: sha512-mKKb915TF+OC5ptj5bJ7WFRPdYtuHv0yTRxK2tJvi+BDqbkiG7h7u/9SI89nRAYcmap2xHQL9D+QG/6wSrTtXg==} + + mdast-util-gfm-table@2.0.0: + resolution: {integrity: sha512-78UEvebzz/rJIxLvE7ZtDd/vIQ0RHv+3Mh5DR96p7cS7HsBhYIICDBCu8csTNWNO6tBWfqXPWekRuj2FNOGOZg==} + + mdast-util-gfm-task-list-item@2.0.0: + resolution: {integrity: sha512-IrtvNvjxC1o06taBAVJznEnkiHxLFTzgonUdy8hzFVeDun0uTjxxrRGVaNFqkU1wJR3RBPEfsxmU6jDWPofrTQ==} + + mdast-util-gfm@3.1.0: + resolution: {integrity: sha512-0ulfdQOM3ysHhCJ1p06l0b0VKlhU0wuQs3thxZQagjcjPrlFRqY215uZGHHJan9GEAXd9MbfPjFJz+qMkVR6zQ==} + + mdast-util-phrasing@4.1.0: + resolution: {integrity: sha512-TqICwyvJJpBwvGAMZjj4J2n0X8QWp21b9l0o7eXyVJ25YNWYbJDVIyD1bZXE6WtV6RmKJVYmQAKWa0zWOABz2w==} + + mdast-util-to-markdown@2.1.2: + resolution: {integrity: sha512-xj68wMTvGXVOKonmog6LwyJKrYXZPvlwabaryTjLh9LuvovB/KAH+kvi8Gjj+7rJjsFi23nkUxRQv1KqSroMqA==} + + mdast-util-to-string@4.0.0: + resolution: {integrity: sha512-0H44vDimn51F0YwvxSJSm0eCDOJTRlmN0R1yBh4HLj9wiV1Dn0QoXGbvFAWj2hSItVTlCmBF1hqKlIyUBVFLPg==} + + mdn-data@2.12.2: + resolution: {integrity: sha512-IEn+pegP1aManZuckezWCO+XZQDplx1366JoVhTpMpBB1sPey/SbveZQUosKiKiGYjg1wH4pMlNgXbCiYgihQA==} + + mdn-data@2.25.0: + resolution: {integrity: sha512-T2LPsjgUE/tgMmRXREVmwsux89DwWfNjiynOeXuLd2mX6jphGQ2YE3Ukz7LQ2VOFKiVZU/Ee1GqzHiipZCjymw==} + + meow@13.2.0: + resolution: {integrity: sha512-pxQJQzB6djGPXh08dacEloMFopsOqGVRKFPYvPOt9XDZ1HasbgDZA74CJGreSU4G3Ak7EFJGoiH2auq+yXISgA==} + engines: {node: '>=18'} + + merge2@1.4.1: + resolution: {integrity: sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==} + engines: {node: '>= 8'} + + micromark-core-commonmark@2.0.3: + resolution: {integrity: sha512-RDBrHEMSxVFLg6xvnXmb1Ayr2WzLAWjeSATAoxwKYJV94TeNavgoIdA0a9ytzDSVzBy2YKFK+emCPOEibLeCrg==} + + micromark-extension-frontmatter@2.0.0: + resolution: {integrity: sha512-C4AkuM3dA58cgZha7zVnuVxBhDsbttIMiytjgsM2XbHAB2faRVaHRle40558FBN+DJcrLNCoqG5mlrpdU4cRtg==} + + micromark-extension-gfm-autolink-literal@2.1.0: + resolution: {integrity: sha512-oOg7knzhicgQ3t4QCjCWgTmfNhvQbDDnJeVu9v81r7NltNCVmhPy1fJRX27pISafdjL+SVc4d3l48Gb6pbRypw==} + + micromark-extension-gfm-footnote@2.1.0: + resolution: {integrity: sha512-/yPhxI1ntnDNsiHtzLKYnE3vf9JZ6cAisqVDauhp4CEHxlb4uoOTxOCJ+9s51bIB8U1N1FJ1RXOKTIlD5B/gqw==} + + micromark-extension-gfm-strikethrough@2.1.0: + resolution: {integrity: sha512-ADVjpOOkjz1hhkZLlBiYA9cR2Anf8F4HqZUO6e5eDcPQd0Txw5fxLzzxnEkSkfnD0wziSGiv7sYhk/ktvbf1uw==} + + micromark-extension-gfm-table@2.1.1: + resolution: {integrity: sha512-t2OU/dXXioARrC6yWfJ4hqB7rct14e8f7m0cbI5hUmDyyIlwv5vEtooptH8INkbLzOatzKuVbQmAYcbWoyz6Dg==} + + micromark-extension-gfm-tagfilter@2.0.0: + resolution: {integrity: sha512-xHlTOmuCSotIA8TW1mDIM6X2O1SiX5P9IuDtqGonFhEK0qgRI4yeC6vMxEV2dgyr2TiD+2PQ10o+cOhdVAcwfg==} + + micromark-extension-gfm-task-list-item@2.1.0: + resolution: {integrity: sha512-qIBZhqxqI6fjLDYFTBIa4eivDMnP+OZqsNwmQ3xNLE4Cxwc+zfQEfbs6tzAo2Hjq+bh6q5F+Z8/cksrLFYWQQw==} + + micromark-extension-gfm@3.0.0: + resolution: {integrity: sha512-vsKArQsicm7t0z2GugkCKtZehqUm31oeGBV/KVSorWSy8ZlNAv7ytjFhvaryUiCUJYqs+NoE6AFhpQvBTM6Q4w==} + + micromark-factory-destination@2.0.1: + resolution: {integrity: sha512-Xe6rDdJlkmbFRExpTOmRj9N3MaWmbAgdpSrBQvCFqhezUn4AHqJHbaEnfbVYYiexVSs//tqOdY/DxhjdCiJnIA==} + + micromark-factory-label@2.0.1: + resolution: {integrity: sha512-VFMekyQExqIW7xIChcXn4ok29YE3rnuyveW3wZQWWqF4Nv9Wk5rgJ99KzPvHjkmPXF93FXIbBp6YdW3t71/7Vg==} + + micromark-factory-space@2.0.1: + resolution: {integrity: sha512-zRkxjtBxxLd2Sc0d+fbnEunsTj46SWXgXciZmHq0kDYGnck/ZSGj9/wULTV95uoeYiK5hRXP2mJ98Uo4cq/LQg==} + + micromark-factory-title@2.0.1: + resolution: {integrity: sha512-5bZ+3CjhAd9eChYTHsjy6TGxpOFSKgKKJPJxr293jTbfry2KDoWkhBb6TcPVB4NmzaPhMs1Frm9AZH7OD4Cjzw==} + + micromark-factory-whitespace@2.0.1: + resolution: {integrity: sha512-Ob0nuZ3PKt/n0hORHyvoD9uZhr+Za8sFoP+OnMcnWK5lngSzALgQYKMr9RJVOWLqQYuyn6ulqGWSXdwf6F80lQ==} + + micromark-util-character@2.1.1: + resolution: {integrity: sha512-wv8tdUTJ3thSFFFJKtpYKOYiGP2+v96Hvk4Tu8KpCAsTMs6yi+nVmGh1syvSCsaxz45J6Jbw+9DD6g97+NV67Q==} + + micromark-util-chunked@2.0.1: + resolution: {integrity: sha512-QUNFEOPELfmvv+4xiNg2sRYeS/P84pTW0TCgP5zc9FpXetHY0ab7SxKyAQCNCc1eK0459uoLI1y5oO5Vc1dbhA==} + + micromark-util-classify-character@2.0.1: + resolution: {integrity: sha512-K0kHzM6afW/MbeWYWLjoHQv1sgg2Q9EccHEDzSkxiP/EaagNzCm7T/WMKZ3rjMbvIpvBiZgwR3dKMygtA4mG1Q==} + + micromark-util-combine-extensions@2.0.1: + resolution: {integrity: sha512-OnAnH8Ujmy59JcyZw8JSbK9cGpdVY44NKgSM7E9Eh7DiLS2E9RNQf0dONaGDzEG9yjEl5hcqeIsj4hfRkLH/Bg==} + + micromark-util-decode-numeric-character-reference@2.0.2: + resolution: {integrity: sha512-ccUbYk6CwVdkmCQMyr64dXz42EfHGkPQlBj5p7YVGzq8I7CtjXZJrubAYezf7Rp+bjPseiROqe7G6foFd+lEuw==} + + micromark-util-decode-string@2.0.1: + resolution: {integrity: sha512-nDV/77Fj6eH1ynwscYTOsbK7rR//Uj0bZXBwJZRfaLEJ1iGBR6kIfNmlNqaqJf649EP0F3NWNdeJi03elllNUQ==} + + micromark-util-encode@2.0.1: + resolution: {integrity: sha512-c3cVx2y4KqUnwopcO9b/SCdo2O67LwJJ/UyqGfbigahfegL9myoEFoDYZgkT7f36T0bLrM9hZTAaAyH+PCAXjw==} + + micromark-util-html-tag-name@2.0.1: + resolution: {integrity: sha512-2cNEiYDhCWKI+Gs9T0Tiysk136SnR13hhO8yW6BGNyhOC4qYFnwF1nKfD3HFAIXA5c45RrIG1ub11GiXeYd1xA==} + + micromark-util-normalize-identifier@2.0.1: + resolution: {integrity: sha512-sxPqmo70LyARJs0w2UclACPUUEqltCkJ6PhKdMIDuJ3gSf/Q+/GIe3WKl0Ijb/GyH9lOpUkRAO2wp0GVkLvS9Q==} + + micromark-util-resolve-all@2.0.1: + resolution: {integrity: sha512-VdQyxFWFT2/FGJgwQnJYbe1jjQoNTS4RjglmSjTUlpUMa95Htx9NHeYW4rGDJzbjvCsl9eLjMQwGeElsqmzcHg==} + + micromark-util-sanitize-uri@2.0.1: + resolution: {integrity: sha512-9N9IomZ/YuGGZZmQec1MbgxtlgougxTodVwDzzEouPKo3qFWvymFHWcnDi2vzV1ff6kas9ucW+o3yzJK9YB1AQ==} + + micromark-util-subtokenize@2.1.0: + resolution: {integrity: sha512-XQLu552iSctvnEcgXw6+Sx75GflAPNED1qx7eBJ+wydBb2KCbRZe+NwvIEEMM83uml1+2WSXpBAcp9IUCgCYWA==} + + micromark-util-symbol@2.0.1: + resolution: {integrity: sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q==} + + micromark-util-types@2.0.2: + resolution: {integrity: sha512-Yw0ECSpJoViF1qTU4DC6NwtC4aWGt1EkzaQB8KPPyCRR8z9TWeV0HbEFGTO+ZY1wB22zmxnJqhPyTpOVCpeHTA==} + + micromark@4.0.2: + resolution: {integrity: sha512-zpe98Q6kvavpCr1NPVSCMebCKfD7CA2NqZ+rykeNhONIJBpc1tFKt9hucLGwha3jNTNI8lHpctWJWoimVF4PfA==} + + micromatch@4.0.8: + resolution: {integrity: sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==} + engines: {node: '>=8.6'} + + mime-db@1.52.0: + resolution: {integrity: sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==} + engines: {node: '>= 0.6'} + + mime-types@2.1.35: + resolution: {integrity: sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==} + engines: {node: '>= 0.6'} + + mime@1.6.0: + resolution: {integrity: sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==} + engines: {node: '>=4'} + hasBin: true + + minimatch@10.2.6: + resolution: {integrity: sha512-vpLQEs+VLCr1nU0BXS07maYoFwlDAH0gngQuuttxIwutDFEMHq2blX+8vpgxDdK3J1PwjCJiep77OitTZ4Ll1A==} + engines: {node: 18 || 20 || >=22} + + minimatch@3.1.2: + resolution: {integrity: sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==} + + mitt@3.0.1: + resolution: {integrity: sha512-vKivATfr97l2/QBCYAkXYDbrIWPM2IIKEl7YPhjCvKlG3kE2gm+uBo6nEXK3M5/Ffh/FLpKExzOQ3JJoJGFKBw==} + + mlly@1.8.0: + resolution: {integrity: sha512-l8D9ODSRWLe2KHJSifWGwBqpTZXIXTeo8mlKjY+E2HAakaTeNpqAyBZ8GSqLzHgw4XmHmC8whvpjJNMbFZN7/g==} + + module-replacements@2.11.0: + resolution: {integrity: sha512-j5sNQm3VCpQQ7nTqGeOZtoJtV3uKERgCBm9QRhmGRiXiqkf7iRFOkfxdJRZWLkqYY8PNf4cDQF/WfXUYLENrRA==} + + mrmime@2.0.1: + resolution: {integrity: sha512-Y3wQdFg2Va6etvQ5I82yUhGdsKrcYox6p7FfL1LbK2J4V01F9TGlepTIhnK24t7koZibmg82KGglhA1XK5IsLQ==} + engines: {node: '>=10'} + + ms@2.1.3: + resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} + + muggle-string@0.4.1: + resolution: {integrity: sha512-VNTrAak/KhO2i8dqqnqnAHOa3cYBwXEZe9h+D5h/1ZqFSTEFHdM65lR7RoIqq3tBBYavsOXV84NoHXZ0AkPyqQ==} + + nanoid@3.3.11: + resolution: {integrity: sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==} + engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} + hasBin: true + + nanoid@5.1.6: + resolution: {integrity: sha512-c7+7RQ+dMB5dPwwCp4ee1/iV/q2P6aK1mTZcfr1BTuVlyW9hJYiMPybJCcnBlQtuSmTIWNeazm/zqNoZSSElBg==} + engines: {node: ^18 || >=20} + hasBin: true + + natural-compare@1.4.0: + resolution: {integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==} + + natural-orderby@5.0.0: + resolution: {integrity: sha512-kKHJhxwpR/Okycz4HhQKKlhWe4ASEfPgkSWNmKFHd7+ezuQlxkA5cM3+XkBPvm1gmHen3w53qsYAv+8GwRrBlg==} + engines: {node: '>=18'} + + needle@3.3.1: + resolution: {integrity: sha512-6k0YULvhpw+RoLNiQCRKOl09Rv1dPLr8hHnVjHqdolKwDrdNyk+Hmrthi4lIGPPz3r39dLx0hsF5s40sZ3Us4Q==} + engines: {node: '>= 4.4.x'} + hasBin: true + + node-addon-api@7.1.1: + resolution: {integrity: sha512-5m3bsyrjFWE1xf7nz7YXdN4udnVtXK6/Yfgn5qnahL6bCkf2yKt4k3nuTKAtT4r3IG8JNR2ncsIMdZuAzJjHQQ==} + + node-releases@2.0.27: + resolution: {integrity: sha512-nmh3lCkYZ3grZvqcCH+fjmQ7X+H0OeZgP40OierEaAptX4XofMh5kwNbWh7lBduUzCcV/8kZ+NDLCwm2iorIlA==} + + normalize-path@3.0.0: + resolution: {integrity: sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==} + engines: {node: '>=0.10.0'} + + nprogress@0.2.0: + resolution: {integrity: sha512-I19aIingLgR1fmhftnbWWO3dXc0hSxqHQHQb3H8m+K3TnEn/iSeTZZOyvKXWqQESMwuUVnatlCnZdLBZZt2VSA==} + + nth-check@2.1.1: + resolution: {integrity: sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==} + + number-precision@1.6.0: + resolution: {integrity: sha512-05OLPgbgmnixJw+VvEh18yNPUo3iyp4BEWJcrLu4X9W05KmMifN7Mu5exYvQXqxxeNWhvIF+j3Rij+HmddM/hQ==} + + object-deep-merge@2.0.1: + resolution: {integrity: sha512-aKttDKcU3pyZqKcCkDhsMn70WmZFG2JGDQLP9EcLyTSIFQRCPWLAmBZRLJnrVUrhPG1jETEEbfdgbNtJf1LyMg==} + + ohash@2.0.11: + resolution: {integrity: sha512-RdR9FQrFwNBNXAr4GixM8YaRZRJ5PUWbKYbE5eOsrwAjJW0q2REGcf79oYPsLyskQCZG1PLN+S/K1V00joZAoQ==} + + open@10.2.0: + resolution: {integrity: sha512-YgBpdJHPyQ2UE5x+hlSXcnejzAvD0b22U2OuAP+8OnlJT+PjWPxtgmGqKKc+RgTM63U9gN0YzrYc71R2WT/hTA==} + engines: {node: '>=18'} + + optionator@0.9.4: + resolution: {integrity: sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==} + engines: {node: '>= 0.8.0'} + + p-limit@3.1.0: + resolution: {integrity: sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==} + engines: {node: '>=10'} + + p-locate@5.0.0: + resolution: {integrity: sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==} + engines: {node: '>=10'} + + package-manager-detector@1.5.0: + resolution: {integrity: sha512-uBj69dVlYe/+wxj8JOpr97XfsxH/eumMt6HqjNTmJDf/6NO9s+0uxeOneIz3AsPt2m6y9PqzDzd3ATcU17MNfw==} + + package-manager-detector@1.8.0: + resolution: {integrity: sha512-yQA4H19AmPEoMUeavPMDIe1higySl/gH/yaQrkT/s07Qp+7pp2hYz30N3z2l5BkjVkF9Ow6o0wjJamm2y7Sn0A==} + + parent-module@1.0.1: + resolution: {integrity: sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==} + engines: {node: '>=6'} + + parse-gitignore@2.0.0: + resolution: {integrity: sha512-RmVuCHWsfu0QPNW+mraxh/xjQVw/lhUCUru8Zni3Ctq3AoMhpDTq0OVdKS6iesd6Kqb7viCV3isAL43dciOSog==} + engines: {node: '>=14'} + + parse-imports-exports@0.2.4: + resolution: {integrity: sha512-4s6vd6dx1AotCx/RCI2m7t7GCh5bDRUtGNvRfHSP2wbBQdMi67pPe7mtzmgwcaQ8VKK/6IB7Glfyu3qdZJPybQ==} + + parse-json@5.2.0: + resolution: {integrity: sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==} + engines: {node: '>=8'} + + parse-node-version@1.0.1: + resolution: {integrity: sha512-3YHlOa/JgH6Mnpr05jP9eDG254US9ek25LyIxZlDItp2iJtwyaXQb57lBYLdT3MowkUFYEV2XXNAYIPlESvJlA==} + engines: {node: '>= 0.10'} + + parse-statements@1.0.11: + resolution: {integrity: sha512-HlsyYdMBnbPQ9Jr/VgJ1YF4scnldvJpJxCVx6KgqPL4dxppsWrJHCIIxQXMJrqGnsRkNPATbeMJ8Yxu7JMsYcA==} + + path-browserify@1.0.1: + resolution: {integrity: sha512-b7uo2UCUOYZcnF/3ID0lulOJi/bafxa1xPe7ZPsammBSpjSWQkjNxlt635YGS2MiR9GjvuXCtz2emr3jbsz98g==} + + path-exists@4.0.0: + resolution: {integrity: sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==} + engines: {node: '>=8'} + + path-key@3.1.1: + resolution: {integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==} + engines: {node: '>=8'} + + path-type@4.0.0: + resolution: {integrity: sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==} + engines: {node: '>=8'} + + pathe@2.0.3: + resolution: {integrity: sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==} + + perfect-debounce@1.0.0: + resolution: {integrity: sha512-xCy9V055GLEqoFaHoC1SoLIaLmWctgCUaBaWxDZ7/Zx4CTyX7cJQLJOok/orfjZAh9kEYpjJa4d0KcJmCbctZA==} + + perfect-debounce@2.0.0: + resolution: {integrity: sha512-fkEH/OBiKrqqI/yIgjR92lMfs2K8105zt/VT6+7eTjNwisrsh47CeIED9z58zI7DfKdH3uHAn25ziRZn3kgAow==} + + picocolors@1.1.1: + resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==} + + picomatch@2.3.1: + resolution: {integrity: sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==} + engines: {node: '>=8.6'} + + picomatch@4.0.3: + resolution: {integrity: sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==} + engines: {node: '>=12'} + + picomatch@4.0.7: + resolution: {integrity: sha512-qcJu88Q2IWqJsDD529JKMdwGm/dvInW4HvQnRwiH9JtihJvzGOscDtHE3x1pBKeUOTysQ8kVmLnJ2kJu7yhcGA==} + engines: {node: '>=12'} + + pify@4.0.1: + resolution: {integrity: sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==} + engines: {node: '>=6'} + + pinia-plugin-persistedstate@4.7.1: + resolution: {integrity: sha512-WHOqh2esDlR3eAaknPbqXrkkj0D24h8shrDPqysgCFR6ghqP/fpFfJmMPJp0gETHsvrh9YNNg6dQfo2OEtDnIQ==} + peerDependencies: + '@nuxt/kit': '>=3.0.0' + '@pinia/nuxt': '>=0.10.0' + pinia: '>=3.0.0' + peerDependenciesMeta: + '@nuxt/kit': + optional: true + '@pinia/nuxt': + optional: true + pinia: + optional: true + + pinia@3.0.4: + resolution: {integrity: sha512-l7pqLUFTI/+ESXn6k3nu30ZIzW5E2WZF/LaHJEpoq6ElcLD+wduZoB2kBN19du6K/4FDpPMazY2wJr+IndBtQw==} + peerDependencies: + typescript: '>=4.5.0' + vue: ^3.5.11 + peerDependenciesMeta: + typescript: + optional: true + + pkg-types@1.3.1: + resolution: {integrity: sha512-/Jm5M4RvtBFVkKWRu2BLUTNP8/M2a+UwuAX+ae4770q1qVGtfjG+WTCupoZixokjmHiry8uI+dlY8KXYV5HVVQ==} + + pkg-types@2.3.0: + resolution: {integrity: sha512-SIqCzDRg0s9npO5XQ3tNZioRY1uK06lA41ynBC1YmFTmnY6FjUjVt6s4LoADmwoig1qqD0oK8h1p/8mlMx8Oig==} + + pluralize@8.0.0: + resolution: {integrity: sha512-Nc3IT5yHzflTfbjgqWcCPpo7DaKy4FnpB0l/zCAW0Tc7jxAiuqSxHasntB3D7887LSrA93kDJ9IXovxJYxyLCA==} + engines: {node: '>=4'} + + pnpm-workspace-yaml@1.9.1: + resolution: {integrity: sha512-Xj/T2X6DNAWbtk/9UQ0/TJRswltiHZevmcMjqoL7WrpJi67lu3qaslU4+I+zJ8wtwqmuvAE0KkB8rbv2ZdogBg==} + + postcss-html@1.8.0: + resolution: {integrity: sha512-5mMeb1TgLWoRKxZ0Xh9RZDfwUUIqRrcxO2uXO+Ezl1N5lqpCiSU5Gk6+1kZediBfBHFtPCdopr2UZ2SgUsKcgQ==} + engines: {node: ^12 || >=14} + + postcss-media-query-parser@0.2.3: + resolution: {integrity: sha512-3sOlxmbKcSHMjlUXQZKQ06jOswE7oVkXPxmZdoB1r5l0q6gTFTQSHxNxOrCccElbW7dxNytifNEo8qidX2Vsig==} + + postcss-resolve-nested-selector@0.1.6: + resolution: {integrity: sha512-0sglIs9Wmkzbr8lQwEyIzlDOOC9bGmfVKcJTaxv3vMmd3uo4o4DerC3En0bnmgceeql9BfC8hRkp7cg0fjdVqw==} + + postcss-safe-parser@6.0.0: + resolution: {integrity: sha512-FARHN8pwH+WiS2OPCxJI8FuRJpTVnn6ZNFiqAM2aeW2LwTHWWmWgIyKC6cUo0L8aeKiF/14MNvnpls6R2PBeMQ==} + engines: {node: '>=12.0'} + peerDependencies: + postcss: ^8.3.3 + + postcss-safe-parser@7.0.1: + resolution: {integrity: sha512-0AioNCJZ2DPYz5ABT6bddIqlhgwhpHZ/l65YAYo0BCIn0xiDpsnTHz0gnoTGk0OXZW0JRs+cDwL8u/teRdz+8A==} + engines: {node: '>=18.0'} + peerDependencies: + postcss: ^8.4.31 + + postcss-scss@4.0.9: + resolution: {integrity: sha512-AjKOeiwAitL/MXxQW2DliT28EKukvvbEWx3LBmJIRN8KfBGZbRTxNYW0kSqi1COiTZ57nZ9NW06S6ux//N1c9A==} + engines: {node: '>=12.0'} + peerDependencies: + postcss: ^8.4.29 + + postcss-selector-parser@7.1.0: + resolution: {integrity: sha512-8sLjZwK0R+JlxlYcTuVnyT2v+htpdrjDOKuMcOVdYjt52Lh8hWRYpxBPoKx/Zg+bcjc3wx6fmQevMmUztS/ccA==} + engines: {node: '>=4'} + + postcss-selector-parser@7.1.6: + resolution: {integrity: sha512-7qASPzhKF2l2KLboRZux8CCTRMdGiV08vWmyKzPz22qZ7ZjQBOeY7rNzNoCLSUiftJ7HUq0GERHmxw/t0dCdMw==} + engines: {node: '>=4'} + + postcss-sorting@9.1.0: + resolution: {integrity: sha512-Mn8KJ45HNNG6JBpBizXcyf6LqY/qyqetGcou/nprDnFwBFBLGj0j/sNKV2lj2KMOVOwdXu14aEzqJv8CIV6e8g==} + peerDependencies: + postcss: ^8.4.20 + + postcss-value-parser@4.2.0: + resolution: {integrity: sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==} + + postcss@8.5.6: + resolution: {integrity: sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==} + engines: {node: ^10 || ^12 || >=14} + + prelude-ls@1.2.1: + resolution: {integrity: sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==} + engines: {node: '>= 0.8.0'} + + prr@1.0.1: + resolution: {integrity: sha512-yPw4Sng1gWghHQWj0B3ZggWUm4qVbPwPFcRG8KyxiU7J2OHFSoEHKS+EZ3fv5l1t9CyCiop6l/ZYeWbrgoQejw==} + + punycode@2.3.1: + resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==} + engines: {node: '>=6'} + + qified@0.5.1: + resolution: {integrity: sha512-+BtFN3dCP+IaFA6IYNOu/f/uK1B8xD2QWyOeCse0rjtAebBmkzgd2d1OAXi3ikAzJMIBSdzZDNZ3wZKEUDQs5w==} + engines: {node: '>=20'} + + quansync@0.2.11: + resolution: {integrity: sha512-AifT7QEbW9Nri4tAwR5M/uzpBuqfZf+zwaEM/QkzEjj7NBuFD2rBuy0K3dE+8wltbezDV7JMA0WfnCPYRSYbXA==} + + queue-microtask@1.2.3: + resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==} + + readdirp@3.6.0: + resolution: {integrity: sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==} + engines: {node: '>=8.10.0'} + + readdirp@4.1.2: + resolution: {integrity: sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==} + engines: {node: '>= 14.18.0'} + + refa@0.12.1: + resolution: {integrity: sha512-J8rn6v4DBb2nnFqkqwy6/NnTYMcgLA+sLr0iIO41qpv0n+ngb7ksag2tMRl0inb1bbO/esUwzW1vbJi7K0sI0g==} + engines: {node: ^12.0.0 || ^14.0.0 || >=16.0.0} + + regexp-ast-analysis@0.7.1: + resolution: {integrity: sha512-sZuz1dYW/ZsfG17WSAG7eS85r5a0dDsvg+7BiiYR5o6lKCAtUrEwdmRmaGF6rwVj3LcmAeYkOWKEPlbPzN3Y3A==} + engines: {node: ^12.0.0 || ^14.0.0 || >=16.0.0} + + regexp-tree@0.1.27: + resolution: {integrity: sha512-iETxpjK6YoRWJG5o6hXLwvjYAoW+FEZn9os0PD/b6AP6xQwsa/Y7lCVgIixBbUPMfhu+i2LtdeAqVTgGlQarfA==} + hasBin: true + + regjsparser@0.13.3: + resolution: {integrity: sha512-ycwFAS14Jw4mppvmK4GR/J6u3WpWpjkEApehuHtLc/8VpPNpDMbQ4WjqwplXifGeyKOzHSFLmSPqzksDQE2Sfg==} + hasBin: true + + require-from-string@2.0.2: + resolution: {integrity: sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==} + engines: {node: '>=0.10.0'} + + reserved-identifiers@1.2.0: + resolution: {integrity: sha512-yE7KUfFvaBFzGPs5H3Ops1RevfUEsDc5Iz65rOwWg4lE8HJSYtle77uul3+573457oHvBKuHYDl/xqUkKpEEdw==} + engines: {node: '>=18'} + + resize-observer-polyfill@1.5.1: + resolution: {integrity: sha512-LwZrotdHOo12nQuZlHEmtuXdqGoOD0OhaxopaNFxWzInpEgaLWoVuAMbTzixuosCx2nEG58ngzW3vxdWoxIgdg==} + + resolve-from@4.0.0: + resolution: {integrity: sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==} + engines: {node: '>=4'} + + resolve-from@5.0.0: + resolution: {integrity: sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==} + engines: {node: '>=8'} + + resolve-pkg-maps@1.0.0: + resolution: {integrity: sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==} + + reusify@1.1.0: + resolution: {integrity: sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==} + engines: {iojs: '>=1.0.0', node: '>=0.10.0'} + + rfdc@1.4.1: + resolution: {integrity: sha512-q1b3N5QkRUWUl7iyylaaj3kOpIT0N2i9MqIEQXP73GVsN9cw3fdx8X63cEmWhJGi2PPCF23Ijp7ktmd39rawIA==} + + rollup@4.52.5: + resolution: {integrity: sha512-3GuObel8h7Kqdjt0gxkEzaifHTqLVW56Y/bjN7PSQtkKr0w3V/QYSdt6QWYtd7A1xUtYQigtdUfgj1RvWVtorw==} + engines: {node: '>=18.0.0', npm: '>=8.0.0'} + hasBin: true + + run-applescript@7.1.0: + resolution: {integrity: sha512-DPe5pVFaAsinSaV6QjQ6gdiedWDcRCbUuiQfQa2wmWV7+xC9bGulGI8+TdRmoFkAPaBXk8CrAbnlY2ISniJ47Q==} + engines: {node: '>=18'} + + run-parallel@1.2.0: + resolution: {integrity: sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==} + + safer-buffer@2.1.2: + resolution: {integrity: sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==} + + sass@1.93.3: + resolution: {integrity: sha512-elOcIZRTM76dvxNAjqYrucTSI0teAF/L2Lv0s6f6b7FOwcwIuA357bIE871580AjHJuSvLIRUosgV+lIWx6Rgg==} + engines: {node: '>=14.0.0'} + hasBin: true + + sax@1.4.3: + resolution: {integrity: sha512-yqYn1JhPczigF94DMS+shiDMjDowYO6y9+wB/4WgO0Y19jWYk0lQ4tuG5KI7kj4FTp1wxPj5IFfcrz/s1c3jjQ==} + + scroll-into-view-if-needed@2.2.31: + resolution: {integrity: sha512-dGCXy99wZQivjmjIqihaBQNjryrz5rueJY7eHfTdyWEiR4ttYpsajb14rn9s5d4DY4EcY6+4+U/maARBXJedkA==} + + scslre@0.3.0: + resolution: {integrity: sha512-3A6sD0WYP7+QrjbfNA2FN3FsOaGGFoekCVgTyypy53gPxhbkCIjtO6YWgdrfM+n/8sI8JeXZOIxsHjMTNxQ4nQ==} + engines: {node: ^14.0.0 || >=16.0.0} + + scule@1.3.0: + resolution: {integrity: sha512-6FtHJEvt+pVMIB9IBY+IcCJ6Z5f1iQnytgyfKMhDKgmzYG+TeH/wx1y3l27rshSbLiSanrR9ffZDrEsmjlQF2g==} + + semver@5.7.2: + resolution: {integrity: sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==} + hasBin: true + + semver@6.3.1: + resolution: {integrity: sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==} + hasBin: true + + semver@7.7.3: + resolution: {integrity: sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==} + engines: {node: '>=10'} + hasBin: true + + semver@7.8.5: + resolution: {integrity: sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA==} + engines: {node: '>=10'} + hasBin: true + + shebang-command@2.0.0: + resolution: {integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==} + engines: {node: '>=8'} + + shebang-regex@3.0.0: + resolution: {integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==} + engines: {node: '>=8'} + + signal-exit@4.1.0: + resolution: {integrity: sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==} + engines: {node: '>=14'} + + simple-swizzle@0.2.4: + resolution: {integrity: sha512-nAu1WFPQSMNr2Zn9PGSZK9AGn4t/y97lEm+MXTtUDwfP0ksAIX4nO+6ruD9Jwut4C49SB1Ws+fbXsm/yScWOHw==} + + sirv@3.0.2: + resolution: {integrity: sha512-2wcC/oGxHis/BoHkkPwldgiPSYcpZK3JU28WoMVv55yHJgcZ8rlXvuG9iZggz+sU1d4bRgIGASwyWqjxu3FM0g==} + engines: {node: '>=18'} + + sisteransi@1.0.5: + resolution: {integrity: sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==} + + slash@3.0.0: + resolution: {integrity: sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==} + engines: {node: '>=8'} + + slice-ansi@4.0.0: + resolution: {integrity: sha512-qMCMfhY040cVHT43K9BFygqYbUPFZKHOg7K73mtTWJRb8pyP3fzf4Ixd5SzdEJQ6MRUg/WBnOLxghZtKKurENQ==} + engines: {node: '>=10'} + + sortablejs@1.15.6: + resolution: {integrity: sha512-aNfiuwMEpfBM/CN6LY0ibyhxPfPbyFeBTYJKCvzkJ2GkUpazIt3H+QIPAMHwqQ7tMKaHz1Qj+rJJCqljnf4p3A==} + + source-map-js@1.2.1: + resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==} + engines: {node: '>=0.10.0'} + + source-map@0.6.1: + resolution: {integrity: sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==} + engines: {node: '>=0.10.0'} + + spdx-exceptions@2.5.0: + resolution: {integrity: sha512-PiU42r+xO4UbUS1buo3LPJkjlO7430Xn5SVAhdpzzsPHsjbYVflnnFdATgabnLude+Cqu25p6N+g2lw/PFsa4w==} + + spdx-expression-parse@4.0.0: + resolution: {integrity: sha512-Clya5JIij/7C6bRR22+tnGXbc4VKlibKSVj2iHvVeX5iMW7s1SIQlqu699JkODJJIhh/pUu8L0/VLh8xflD+LQ==} + + spdx-license-ids@3.0.22: + resolution: {integrity: sha512-4PRT4nh1EImPbt2jASOKHX7PB7I+e4IWNLvkKFDxNhJlfjbYlleYQh285Z/3mPTHSAK/AvdMmw5BNNuYH8ShgQ==} + + speakingurl@14.0.1: + resolution: {integrity: sha512-1POYv7uv2gXoyGFpBCmpDVSNV74IfsWlDW216UPjbWufNf+bSU6GdbDsxdcxtfwb4xlI3yxzOTKClUosxARYrQ==} + engines: {node: '>=0.10.0'} + + string-width@4.2.3: + resolution: {integrity: sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==} + engines: {node: '>=8'} + + strip-ansi@6.0.1: + resolution: {integrity: sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==} + engines: {node: '>=8'} + + strip-indent@4.1.1: + resolution: {integrity: sha512-SlyRoSkdh1dYP0PzclLE7r0M9sgbFKKMFXpFRUMNuKhQSbC6VQIGzq3E0qsfvGJaUFJPGv6Ws1NZ/haTAjfbMA==} + engines: {node: '>=12'} + + strip-json-comments@3.1.1: + resolution: {integrity: sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==} + engines: {node: '>=8'} + + strip-literal@3.1.0: + resolution: {integrity: sha512-8r3mkIM/2+PpjHoOtiAW8Rg3jJLHaV7xPwG+YRGrv6FP0wwk/toTpATxWYOW0BKdWwl82VT2tFYi5DlROa0Mxg==} + + stylelint-config-html@1.1.0: + resolution: {integrity: sha512-IZv4IVESjKLumUGi+HWeb7skgO6/g4VMuAYrJdlqQFndgbj6WJAXPhaysvBiXefX79upBdQVumgYcdd17gCpjQ==} + engines: {node: ^12 || >=14} + peerDependencies: + postcss-html: ^1.0.0 + stylelint: '>=14.0.0' + + stylelint-config-recess-order@7.4.0: + resolution: {integrity: sha512-W3G517cBaMDYRX5Fzhro4fhRkkLafLgVSPfQnhxbiLyMnbLq47RMF/NRaOJ4OQsKWYhsIHOIc2Q3VL0X3Q7oPg==} + peerDependencies: + stylelint: '>=16.18' + stylelint-order: '>=7' + + stylelint-config-recommended-scss@16.0.2: + resolution: {integrity: sha512-aUTHhPPWCvFyWaxtckJlCPaXTDFsp4pKO8evXNCsW9OwsaUWyMd6jvcUhSmfGWPrTddvzNqK4rS/UuSLcbVGdQ==} + engines: {node: '>=20'} + peerDependencies: + postcss: ^8.3.3 + stylelint: ^16.24.0 + peerDependenciesMeta: + postcss: + optional: true + + stylelint-config-recommended-vue@1.6.1: + resolution: {integrity: sha512-lLW7hTIMBiTfjenGuDq2kyHA6fBWd/+Df7MO4/AWOxiFeXP9clbpKgg27kHfwA3H7UNMGC7aeP3mNlZB5LMmEQ==} + engines: {node: ^12 || >=14} + peerDependencies: + postcss-html: ^1.0.0 + stylelint: '>=14.0.0' + + stylelint-config-recommended@17.0.0: + resolution: {integrity: sha512-WaMSdEiPfZTSFVoYmJbxorJfA610O0tlYuU2aEwY33UQhSPgFbClrVJYWvy3jGJx+XW37O+LyNLiZOEXhKhJmA==} + engines: {node: '>=18.12.0'} + peerDependencies: + stylelint: ^16.23.0 + + stylelint-config-standard-scss@16.0.0: + resolution: {integrity: sha512-/FHECLUu+med/e6OaPFpprG86ShC4SYT7Tzb2PTVdDjJsehhFBOioSlWqYFqJxmGPIwO3AMBxNo+kY3dxrbczA==} + engines: {node: '>=20'} + peerDependencies: + postcss: ^8.3.3 + stylelint: ^16.23.1 + peerDependenciesMeta: + postcss: + optional: true + + stylelint-config-standard@39.0.1: + resolution: {integrity: sha512-b7Fja59EYHRNOTa3aXiuWnhUWXFU2Nfg6h61bLfAb5GS5fX3LMUD0U5t4S8N/4tpHQg3Acs2UVPR9jy2l1g/3A==} + engines: {node: '>=18.12.0'} + peerDependencies: + stylelint: ^16.23.0 + + stylelint-order@7.0.0: + resolution: {integrity: sha512-rSWxx0KscYfxU02wEskKXES9lkRzuuONMMNkZ7SUc6uiF3tDKm7e+sE0Ax/SBlG4TUf1sp1R6f3/SlsPGmzthg==} + engines: {node: '>=20.19.0'} + peerDependencies: + stylelint: ^16.18.0 + + stylelint-scss@6.12.1: + resolution: {integrity: sha512-UJUfBFIvXfly8WKIgmqfmkGKPilKB4L5j38JfsDd+OCg2GBdU0vGUV08Uw82tsRZzd4TbsUURVVNGeOhJVF7pA==} + engines: {node: '>=18.12.0'} + peerDependencies: + stylelint: ^16.0.2 + + stylelint@16.25.0: + resolution: {integrity: sha512-Li0avYWV4nfv1zPbdnxLYBGq4z8DVZxbRgx4Kn6V+Uftz1rMoF1qiEI3oL4kgWqyYgCgs7gT5maHNZ82Gk03vQ==} + engines: {node: '>=18.12.0'} + hasBin: true + + superjson@2.2.5: + resolution: {integrity: sha512-zWPTX96LVsA/eVYnqOM2+ofcdPqdS1dAF1LN4TS2/MWuUpfitd9ctTa87wt4xrYnZnkLtS69xpBdSxVBP5Rm6w==} + engines: {node: '>=16'} + + supports-color@7.2.0: + resolution: {integrity: sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==} + engines: {node: '>=8'} + + supports-hyperlinks@3.2.0: + resolution: {integrity: sha512-zFObLMyZeEwzAoKCyu1B91U79K2t7ApXuQfo8OuxwXLDgcKxuwM+YvcbIhm6QWqz7mHUH1TVytR1PwVVjEuMig==} + engines: {node: '>=14.18'} + + svg-tags@1.0.0: + resolution: {integrity: sha512-ovssysQTa+luh7A5Weu3Rta6FJlFBBbInjOh722LIt6klpU2/HtdUbszju/G4devcvk8PGt7FCLv5wftu3THUA==} + + synckit@0.11.13: + resolution: {integrity: sha512-eNRKgb3z66Yp3D2CixVujOUvXLFUTij/zVnV8KRyvFdQwpz7I5DS8UfRkTeLzb64u+dkzDSdelE24izu+zSSUg==} + engines: {node: ^14.18.0 || >=16.0.0} + + table@6.9.0: + resolution: {integrity: sha512-9kY+CygyYM6j02t5YFHbNz2FN5QmYGv9zAjVp4lCDjlCw7amdckXlEt/bjMhUIfj4ThGRE4gCUH5+yGnNuPo5A==} + engines: {node: '>=10.0.0'} + + tapable@2.3.0: + resolution: {integrity: sha512-g9ljZiwki/LfxmQADO3dEY1CbpmXT5Hm2fJ+QaGKwSXUylMybePR7/67YW7jOrrvjEgL1Fmz5kzyAjWVWLlucg==} + engines: {node: '>=6'} + + tinyexec@1.0.2: + resolution: {integrity: sha512-W/KYk+NFhkmsYpuHq5JykngiOCnxeVL8v8dFnqxSD8qEEdRfXk1SDM6JzNqcERbcGYj9tMrDQBYV9cjgnunFIg==} + engines: {node: '>=18'} + + tinyexec@1.3.1: + resolution: {integrity: sha512-GCvB3aoys96IuDFBMcTB46JOR6mdMtAToqwiW8JlWhsoh1mhHi/xn9ss/Dg7N555GiJyEt2qzoG/NHCwM6h1EA==} + engines: {node: '>=18'} + + tinyglobby@0.2.15: + resolution: {integrity: sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==} + engines: {node: '>=12.0.0'} + + tinyglobby@0.2.17: + resolution: {integrity: sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g==} + engines: {node: '>=12.0.0'} + + to-regex-range@5.0.1: + resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==} + engines: {node: '>=8.0'} + + to-valid-identifier@1.0.0: + resolution: {integrity: sha512-41wJyvKep3yT2tyPqX/4blcfybknGB4D+oETKLs7Q76UiPqRpUJK3hr1nxelyYO0PHKVzJwlu0aCeEAsGI6rpw==} + engines: {node: '>=20'} + + toml-eslint-parser@1.0.3: + resolution: {integrity: sha512-A5F0cM6+mDleacLIEUkmfpkBbnHJFV1d2rprHU2MXNk7mlxHq2zGojA+SRvQD1RoMo9gqjZPWEaKG4v1BQ48lw==} + engines: {node: ^20.19.0 || ^22.13.0 || >=24} + + totalist@3.0.1: + resolution: {integrity: sha512-sf4i37nQ2LBx4m3wB74y+ubopq6W/dIzXg0FDGjsYnZHVa1Da8FH853wlL2gtUhg+xJXjfk3kUZS3BRoQeoQBQ==} + engines: {node: '>=6'} + + ts-api-utils@2.5.0: + resolution: {integrity: sha512-OJ/ibxhPlqrMM0UiNHJ/0CKQkoKF243/AEmplt3qpRgkW8VG7IfOS41h7V8TjITqdByHzrjcS/2si+y4lIh8NA==} + engines: {node: '>=18.12'} + peerDependencies: + typescript: '>=4.8.4' + + ts-declaration-location@1.0.7: + resolution: {integrity: sha512-EDyGAwH1gO0Ausm9gV6T2nUvBgXT5kGoCMJPllOaooZ+4VvJiKBdZE7wK18N1deEowhcUptS+5GXZK8U/fvpwA==} + peerDependencies: + typescript: '>=4.0.0' + + tslib@2.3.0: + resolution: {integrity: sha512-N82ooyxVNm6h1riLCoyS9e3fuJ3AMG2zIZs2Gd1ATcSFjSA23Q0fzjjZeh0jbJvWVDZ0cJT8yaNNaaXHzueNjg==} + + tslib@2.8.1: + resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==} + + type-check@0.4.0: + resolution: {integrity: sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==} + engines: {node: '>= 0.8.0'} + + typescript@5.9.3: + resolution: {integrity: sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==} + engines: {node: '>=14.17'} + hasBin: true + + ufo@1.6.1: + resolution: {integrity: sha512-9a4/uxlTWJ4+a5i0ooc1rU7C7YOw3wT+UGqdeNNHWnOF9qcMBgLRS+4IYUqbczewFx4mLEig6gawh7X6mFlEkA==} + + undici-types@7.16.0: + resolution: {integrity: sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==} + + undici-types@8.9.0: + resolution: {integrity: sha512-KTDyRTYX8sWmKXAikPHHSyc63CRPETMctyjKFupcC6OBLXT3xsN0e9aF7m+mIXutFWpUXuedtowG7iLOzp0kQg==} + + unimport@5.5.0: + resolution: {integrity: sha512-/JpWMG9s1nBSlXJAQ8EREFTFy3oy6USFd8T6AoBaw1q2GGcF4R9yp3ofg32UODZlYEO5VD0EWE1RpI9XDWyPYg==} + engines: {node: '>=18.12.0'} + + unist-util-is@6.0.1: + resolution: {integrity: sha512-LsiILbtBETkDz8I9p1dQ0uyRUWuaQzd/cuEeS1hoRSyW5E5XGmTzlwY1OrNzzakGowI9Dr/I8HVaw4hTtnxy8g==} + + unist-util-stringify-position@4.0.0: + resolution: {integrity: sha512-0ASV06AAoKCDkS2+xw5RXJywruurpbC4JZSm7nr7MOt1ojAzvyyaO+UxZf18j8FCF6kmzCZKcAgN/yu2gm2XgQ==} + + unist-util-visit-parents@6.0.2: + resolution: {integrity: sha512-goh1s1TBrqSqukSc8wrjwWhL0hiJxgA8m4kFxGlQ+8FYQ3C/m11FcTs4YYem7V664AhHVvgoQLk890Ssdsr2IQ==} + + unist-util-visit@5.0.0: + resolution: {integrity: sha512-MR04uvD+07cwl/yhVuVWAtw+3GOR/knlL55Nd/wAdblk27GCVt3lqpTivy/tkJcZoNPzTwS1Y+KMojlLDhoTzg==} + + universalify@2.0.1: + resolution: {integrity: sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==} + engines: {node: '>= 10.0.0'} + + unplugin-auto-import@20.2.0: + resolution: {integrity: sha512-vfBI/SvD9hJqYNinipVOAj5n8dS8DJXFlCKFR5iLDp2SaQwsfdnfLXgZ+34Kd3YY3YEY9omk8XQg0bwos3Q8ug==} + engines: {node: '>=14'} + peerDependencies: + '@nuxt/kit': ^4.0.0 + '@vueuse/core': '*' + peerDependenciesMeta: + '@nuxt/kit': + optional: true + '@vueuse/core': + optional: true + + unplugin-icons@22.5.0: + resolution: {integrity: sha512-MBlMtT5RuMYZy4TZgqUL2OTtOdTUVsS1Mhj6G1pEzMlFJlEnq6mhUfoIt45gBWxHcsOdXJDWLg3pRZ+YmvAVWQ==} + peerDependencies: + '@svgr/core': '>=7.0.0' + '@svgx/core': ^1.0.1 + '@vue/compiler-sfc': ^3.0.2 || ^2.7.0 + svelte: ^3.0.0 || ^4.0.0 || ^5.0.0 + vue-template-compiler: ^2.6.12 + vue-template-es2015-compiler: ^1.9.0 + peerDependenciesMeta: + '@svgr/core': + optional: true + '@svgx/core': + optional: true + '@vue/compiler-sfc': + optional: true + svelte: + optional: true + vue-template-compiler: + optional: true + vue-template-es2015-compiler: + optional: true + + unplugin-utils@0.3.1: + resolution: {integrity: sha512-5lWVjgi6vuHhJ526bI4nlCOmkCIF3nnfXkCMDeMJrtdvxTs6ZFCM8oNufGTsDbKv/tJ/xj8RpvXjRuPBZJuJog==} + engines: {node: '>=20.19.0'} + + unplugin-vue-components@29.2.0: + resolution: {integrity: sha512-QxBeBdmEflgtJRgMQMc/z/JVV5lcwXN5nOy5ehX6CKDGylIu6Qn4Goy8X95S0qOxF7EdI+uNhdBd4v5i0bvzCw==} + engines: {node: '>=14'} + peerDependencies: + '@babel/parser': ^7.15.8 + '@nuxt/kit': ^3.2.2 || ^4.0.0 + vue: 2 || 3 + peerDependenciesMeta: + '@babel/parser': + optional: true + '@nuxt/kit': + optional: true + + unplugin@2.3.10: + resolution: {integrity: sha512-6NCPkv1ClwH+/BGE9QeoTIl09nuiAt0gS28nn1PvYXsGKRwM2TCbFA2QiilmehPDTXIe684k4rZI1yl3A1PCUw==} + engines: {node: '>=18.12.0'} + + update-browserslist-db@1.1.4: + resolution: {integrity: sha512-q0SPT4xyU84saUX+tomz1WLkxUbuaJnR1xWt17M7fJtEJigJeWUNGUqrauFXsHnqev9y9JTRGwk13tFBuKby4A==} + hasBin: true + peerDependencies: + browserslist: '>= 4.21.0' + + uri-js@4.4.1: + resolution: {integrity: sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==} + + util-deprecate@1.0.2: + resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==} + + verkit@0.3.2: + resolution: {integrity: sha512-zj/ob3UsvJGN0whEAKFp53REA5X66hvffVqoCtVQAakJKnKlH+/PcOfMoFwIG/o4rElqLv/ycAFlx8ZlXUorCg==} + engines: {node: '>=18.12.0'} + + vite-dev-rpc@1.1.0: + resolution: {integrity: sha512-pKXZlgoXGoE8sEKiKJSng4hI1sQ4wi5YT24FCrwrLt6opmkjlqPPVmiPWWJn8M8byMxRGzp1CrFuqQs4M/Z39A==} + peerDependencies: + vite: ^2.9.0 || ^3.0.0-0 || ^4.0.0-0 || ^5.0.0-0 || ^6.0.1 || ^7.0.0-0 + + vite-hot-client@2.1.0: + resolution: {integrity: sha512-7SpgZmU7R+dDnSmvXE1mfDtnHLHQSisdySVR7lO8ceAXvM0otZeuQQ6C8LrS5d/aYyP/QZ0hI0L+dIPrm4YlFQ==} + peerDependencies: + vite: ^2.6.0 || ^3.0.0 || ^4.0.0 || ^5.0.0-0 || ^6.0.0-0 || ^7.0.0-0 + + vite-plugin-compression@0.5.1: + resolution: {integrity: sha512-5QJKBDc+gNYVqL/skgFAP81Yuzo9R+EAf19d+EtsMF/i8kFUpNi3J/H01QD3Oo8zBQn+NzoCIFkpPLynoOzaJg==} + peerDependencies: + vite: '>=2.0.0' + + vite-plugin-inspect@11.3.3: + resolution: {integrity: sha512-u2eV5La99oHoYPHE6UvbwgEqKKOQGz86wMg40CCosP6q8BkB6e5xPneZfYagK4ojPJSj5anHCrnvC20DpwVdRA==} + engines: {node: '>=14'} + peerDependencies: + '@nuxt/kit': '*' + vite: ^6.0.0 || ^7.0.0-0 + peerDependenciesMeta: + '@nuxt/kit': + optional: true + + vite-plugin-vue-devtools@8.0.5: + resolution: {integrity: sha512-p619BlKFOqQXJ6uDWS1vUPQzuJOD6xJTfftj57JXBGoBD/yeQCowR7pnWcr/FEX4/HVkFbreI6w2uuGBmQOh6A==} + engines: {node: '>=v14.21.3'} + peerDependencies: + vite: ^6.0.0 || ^7.0.0-0 + + vite-plugin-vue-inspector@5.3.2: + resolution: {integrity: sha512-YvEKooQcSiBTAs0DoYLfefNja9bLgkFM7NI2b07bE2SruuvX0MEa9cMaxjKVMkeCp5Nz9FRIdcN1rOdFVBeL6Q==} + peerDependencies: + vite: ^3.0.0-0 || ^4.0.0-0 || ^5.0.0-0 || ^6.0.0-0 || ^7.0.0-0 + + vite@7.2.1: + resolution: {integrity: sha512-qTl3VF7BvOupTR85Zc561sPEgxyUSNSvTQ9fit7DEMP7yPgvvIGm5Zfa1dOM+kOwWGNviK9uFM9ra77+OjK7lQ==} + engines: {node: ^20.19.0 || >=22.12.0} + hasBin: true + peerDependencies: + '@types/node': ^20.19.0 || >=22.12.0 + jiti: '>=1.21.0' + less: ^4.0.0 + lightningcss: ^1.21.0 + sass: ^1.70.0 + sass-embedded: ^1.70.0 + stylus: '>=0.54.8' + sugarss: ^5.0.0 + terser: ^5.16.0 + tsx: ^4.8.1 + yaml: ^2.4.2 + peerDependenciesMeta: + '@types/node': + optional: true + jiti: + optional: true + less: + optional: true + lightningcss: + optional: true + sass: + optional: true + sass-embedded: + optional: true + stylus: + optional: true + sugarss: + optional: true + terser: + optional: true + tsx: + optional: true + yaml: + optional: true + + vscode-uri@3.1.0: + resolution: {integrity: sha512-/BpdSx+yCQGnCvecbyXdxHDkuk55/G3xwnC0GqY4gmQ3j+A+g8kzzgB4Nk/SINjqn6+waqw3EgbVF2QKExkRxQ==} + + vue-color-kit@1.0.6: + resolution: {integrity: sha512-56oSUp8hGIZ3E4ayZLqTDAb2C7VzaWAC1EVUsZEvPCD2wiiiFd2L2BiEGN1ingfz538sYWKNXxMZQGslqbMiBg==} + peerDependencies: + vue: ^3.0.5 + + vue-draggable-plus@0.6.0: + resolution: {integrity: sha512-G5TSfHrt9tX9EjdG49InoFJbt2NYk0h3kgjgKxkFWr3ulIUays0oFObr5KZ8qzD4+QnhtALiRwIqY6qul4egqw==} + peerDependencies: + '@types/sortablejs': ^1.15.0 + '@vue/composition-api': '*' + peerDependenciesMeta: + '@vue/composition-api': + optional: true + + vue-eslint-parser@10.4.1: + resolution: {integrity: sha512-Gk6gRDj0n/fkRa3C3l0bBheoBckUq/Rs0F/TvMWIS6nzzx67amAViMe9CkNgsP2tXyQONvGiHQESHwFtZ3aYDA==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 + + vue-router@4.6.3: + resolution: {integrity: sha512-ARBedLm9YlbvQomnmq91Os7ck6efydTSpRP3nuOKCvgJOHNrhRoJDSKtee8kcL1Vf7nz6U+PMBL+hTvR3bTVQg==} + peerDependencies: + vue: ^3.5.0 + + vue-tsc@3.1.3: + resolution: {integrity: sha512-StMNfZHwPIXQgY3KxPKM0Jsoc8b46mDV3Fn2UlHCBIwRJApjqrSwqeMYgWf0zpN+g857y74pv7GWuBm+UqQe1w==} + hasBin: true + peerDependencies: + typescript: '>=5.0.0' + + vue@3.5.23: + resolution: {integrity: sha512-CfvZv/vI52xUhumUvHtD6iFIS78nGWfX4IJnHfBGhpqMI0CwDq2YEngXOeaBFMRmiArcqczuVrLxurvesTYT9w==} + peerDependencies: + typescript: '*' + peerDependenciesMeta: + typescript: + optional: true + + webpack-virtual-modules@0.6.2: + resolution: {integrity: sha512-66/V2i5hQanC51vBQKPH4aI8NMAcBW59FVBs+rC7eGHupMyfn34q7rZIE+ETlJ+XTevqfUhVVBgSUNSW2flEUQ==} + + which@1.3.1: + resolution: {integrity: sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==} + hasBin: true + + which@2.0.2: + resolution: {integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==} + engines: {node: '>= 8'} + hasBin: true + + word-wrap@1.2.5: + resolution: {integrity: sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==} + engines: {node: '>=0.10.0'} + + write-file-atomic@5.0.1: + resolution: {integrity: sha512-+QU2zd6OTD8XWIJCbffaiQeH9U73qIqafo1x6V1snCWYGJf6cVE0cDR4D8xRzcEnfI21IFrUPzPGtcPf8AC+Rw==} + engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} + + wsl-utils@0.1.0: + resolution: {integrity: sha512-h3Fbisa2nKGPxCpm89Hk33lBLsnaGBvctQopaBSOW/uIs6FTe1ATyAnKFJrzVs9vpGdsTe73WF3V4lIsk4Gacw==} + engines: {node: '>=18'} + + xe-utils@3.7.9: + resolution: {integrity: sha512-LWH6M7g+TKX8P2fqGxDPgJQygiULhbMtpmfxTMxJTUjuUv9y2+I45UCcVLh5AnmJSxDV1xTxsq7G5P9Eid06JQ==} + + xml-name-validator@5.0.0: + resolution: {integrity: sha512-EvGK8EJ3DhaHfbRlETOWAS5pO9MZITeauHKJyb8wyajUfQUenkIg2MvLDTZ4T/TgIcm3HU0TFBgWWboAZ30UHg==} + engines: {node: '>=18'} + + yallist@3.1.1: + resolution: {integrity: sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==} + + yaml-eslint-parser@2.1.0: + resolution: {integrity: sha512-1zo9KRfp6vIAXBEhWAz09ex3hUwh3T+/6dGbGteHvNseA0Uk4FgQnPES55klZ6cDzSy9FpgKS0D/CoLUvCCj4w==} + engines: {node: ^20.19.0 || ^22.13.0 || >=24} + + yaml@2.8.1: + resolution: {integrity: sha512-lcYcMxX2PO9XMGvAJkJ3OsNMw+/7FKes7/hgerGUYWIoWu5j/+YQqcZr5JnPZWzOsEBgMbSbiSTn/dv/69Mkpw==} + engines: {node: '>= 14.6'} + hasBin: true + + yaml@2.9.1: + resolution: {integrity: sha512-3NxN8+78OdzbT7C/WjGsyfPAtJaN3FNDsWxv7Y7mcDsT/oOmgW8BpyQQFFBnvZE3j9Y2Sdz1ULFLezL7Eb2yFw==} + engines: {node: '>= 14.6'} + hasBin: true + + yocto-queue@0.1.0: + resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==} + engines: {node: '>=10'} + + zrender@6.1.0: + resolution: {integrity: sha512-oEGMDB6pOP2S6OwRR4PdVv610zrjnA3Bh+JnSG12fYJlBKjtNAoEb5fSUoCOOINlH96I2fU38/A2UpRKs67xYQ==} + + zwitch@2.0.4: + resolution: {integrity: sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A==} + +snapshots: + + '@antfu/eslint-config@7.7.3(@typescript-eslint/typescript-estree@8.70.1(typescript@5.9.3))(@typescript-eslint/utils@8.70.1(eslint@9.39.1)(typescript@5.9.3))(@vue/compiler-sfc@3.5.23)(eslint@9.39.1)(typescript@5.9.3)': + dependencies: + '@antfu/install-pkg': 1.1.0 + '@clack/prompts': 1.8.1 + '@e18e/eslint-plugin': 0.2.0(eslint@9.39.1) + '@eslint-community/eslint-plugin-eslint-comments': 4.8.1(eslint@9.39.1) + '@eslint/markdown': 7.5.1 + '@stylistic/eslint-plugin': 5.10.0(eslint@9.39.1) + '@typescript-eslint/eslint-plugin': 8.70.1(@typescript-eslint/parser@8.70.1(eslint@9.39.1)(typescript@5.9.3))(eslint@9.39.1)(typescript@5.9.3) + '@typescript-eslint/parser': 8.70.1(eslint@9.39.1)(typescript@5.9.3) + '@vitest/eslint-plugin': 1.6.27(@typescript-eslint/eslint-plugin@8.70.1(@typescript-eslint/parser@8.70.1(eslint@9.39.1)(typescript@5.9.3))(eslint@9.39.1)(typescript@5.9.3))(eslint@9.39.1)(typescript@5.9.3) + ansis: 4.2.0 + cac: 7.0.0 + eslint: 9.39.1 + eslint-config-flat-gitignore: 2.4.0(eslint@9.39.1) + eslint-flat-config-utils: 3.2.0 + eslint-merge-processors: 2.0.0(eslint@9.39.1) + eslint-plugin-antfu: 3.2.3(eslint@9.39.1) + eslint-plugin-command: 3.5.3(@typescript-eslint/typescript-estree@8.70.1(typescript@5.9.3))(@typescript-eslint/utils@8.70.1(eslint@9.39.1)(typescript@5.9.3))(eslint@9.39.1) + eslint-plugin-import-lite: 0.5.2(eslint@9.39.1) + eslint-plugin-jsdoc: 62.9.0(eslint@9.39.1) + eslint-plugin-jsonc: 3.4.2(eslint@9.39.1) + eslint-plugin-n: 17.24.0(eslint@9.39.1)(typescript@5.9.3) + eslint-plugin-no-only-tests: 3.3.0 + eslint-plugin-perfectionist: 5.12.1(eslint@9.39.1)(typescript@5.9.3) + eslint-plugin-pnpm: 1.9.1(eslint@9.39.1) + eslint-plugin-regexp: 3.3.1(eslint@9.39.1) + eslint-plugin-toml: 1.5.0(eslint@9.39.1) + eslint-plugin-unicorn: 63.0.0(eslint@9.39.1) + eslint-plugin-unused-imports: 4.4.1(@typescript-eslint/eslint-plugin@8.70.1(@typescript-eslint/parser@8.70.1(eslint@9.39.1)(typescript@5.9.3))(eslint@9.39.1)(typescript@5.9.3))(eslint@9.39.1) + eslint-plugin-vue: 10.11.1(@stylistic/eslint-plugin@5.10.0(eslint@9.39.1))(@typescript-eslint/parser@8.70.1(eslint@9.39.1)(typescript@5.9.3))(eslint@9.39.1)(vue-eslint-parser@10.4.1(eslint@9.39.1)) + eslint-plugin-yml: 3.8.1(eslint@9.39.1) + eslint-processor-vue-blocks: 2.0.0(@vue/compiler-sfc@3.5.23)(eslint@9.39.1) + globals: 17.12.0 + local-pkg: 1.1.2 + parse-gitignore: 2.0.0 + toml-eslint-parser: 1.0.3 + vue-eslint-parser: 10.4.1(eslint@9.39.1) + yaml-eslint-parser: 2.1.0 + transitivePeerDependencies: + - '@eslint/json' + - '@typescript-eslint/typescript-estree' + - '@typescript-eslint/utils' + - '@vue/compiler-sfc' + - oxlint + - supports-color + - typescript + - vitest + + '@antfu/install-pkg@1.1.0': + dependencies: + package-manager-detector: 1.5.0 + tinyexec: 1.0.2 + + '@antfu/install-pkg@2.1.0': + dependencies: + package-manager-detector: 1.8.0 + tinyexec: 1.3.1 + + '@arco-design/color@0.4.0': + dependencies: + color: 3.2.1 + + '@arco-design/web-vue@2.58.0(vue@3.5.23(typescript@5.9.3))': + dependencies: + '@arco-design/color': 0.4.0 + b-tween: 0.3.3 + b-validate: 1.5.3 + compute-scroll-into-view: 1.0.20 + dayjs: 1.11.19 + number-precision: 1.6.0 + resize-observer-polyfill: 1.5.1 + scroll-into-view-if-needed: 2.2.31 + vue: 3.5.23(typescript@5.9.3) + + '@babel/code-frame@7.27.1': + dependencies: + '@babel/helper-validator-identifier': 7.28.5 + js-tokens: 4.0.0 + picocolors: 1.1.1 + + '@babel/compat-data@7.28.5': {} + + '@babel/core@7.28.5': + dependencies: + '@babel/code-frame': 7.27.1 + '@babel/generator': 7.28.5 + '@babel/helper-compilation-targets': 7.27.2 + '@babel/helper-module-transforms': 7.28.3(@babel/core@7.28.5) + '@babel/helpers': 7.28.4 + '@babel/parser': 7.28.5 + '@babel/template': 7.27.2 + '@babel/traverse': 7.28.5 + '@babel/types': 7.28.5 + '@jridgewell/remapping': 2.3.5 + convert-source-map: 2.0.0 + debug: 4.4.3 + gensync: 1.0.0-beta.2 + json5: 2.2.3 + semver: 6.3.1 + transitivePeerDependencies: + - supports-color + + '@babel/generator@7.28.5': + dependencies: + '@babel/parser': 7.28.5 + '@babel/types': 7.28.5 + '@jridgewell/gen-mapping': 0.3.13 + '@jridgewell/trace-mapping': 0.3.31 + jsesc: 3.1.0 + + '@babel/helper-annotate-as-pure@7.27.3': + dependencies: + '@babel/types': 7.28.5 + + '@babel/helper-compilation-targets@7.27.2': + dependencies: + '@babel/compat-data': 7.28.5 + '@babel/helper-validator-option': 7.27.1 + browserslist: 4.27.0 + lru-cache: 5.1.1 + semver: 6.3.1 + + '@babel/helper-create-class-features-plugin@7.28.5(@babel/core@7.28.5)': + dependencies: + '@babel/core': 7.28.5 + '@babel/helper-annotate-as-pure': 7.27.3 + '@babel/helper-member-expression-to-functions': 7.28.5 + '@babel/helper-optimise-call-expression': 7.27.1 + '@babel/helper-replace-supers': 7.27.1(@babel/core@7.28.5) + '@babel/helper-skip-transparent-expression-wrappers': 7.27.1 + '@babel/traverse': 7.28.5 + semver: 6.3.1 + transitivePeerDependencies: + - supports-color + + '@babel/helper-globals@7.28.0': {} + + '@babel/helper-member-expression-to-functions@7.28.5': + dependencies: + '@babel/traverse': 7.28.5 + '@babel/types': 7.28.5 + transitivePeerDependencies: + - supports-color + + '@babel/helper-module-imports@7.27.1': + dependencies: + '@babel/traverse': 7.28.5 + '@babel/types': 7.28.5 + transitivePeerDependencies: + - supports-color + + '@babel/helper-module-transforms@7.28.3(@babel/core@7.28.5)': + dependencies: + '@babel/core': 7.28.5 + '@babel/helper-module-imports': 7.27.1 + '@babel/helper-validator-identifier': 7.28.5 + '@babel/traverse': 7.28.5 + transitivePeerDependencies: + - supports-color + + '@babel/helper-optimise-call-expression@7.27.1': + dependencies: + '@babel/types': 7.28.5 + + '@babel/helper-plugin-utils@7.27.1': {} + + '@babel/helper-replace-supers@7.27.1(@babel/core@7.28.5)': + dependencies: + '@babel/core': 7.28.5 + '@babel/helper-member-expression-to-functions': 7.28.5 + '@babel/helper-optimise-call-expression': 7.27.1 + '@babel/traverse': 7.28.5 + transitivePeerDependencies: + - supports-color + + '@babel/helper-skip-transparent-expression-wrappers@7.27.1': + dependencies: + '@babel/traverse': 7.28.5 + '@babel/types': 7.28.5 + transitivePeerDependencies: + - supports-color + + '@babel/helper-string-parser@7.27.1': {} + + '@babel/helper-validator-identifier@7.28.5': {} + + '@babel/helper-validator-option@7.27.1': {} + + '@babel/helpers@7.28.4': + dependencies: + '@babel/template': 7.27.2 + '@babel/types': 7.28.5 + + '@babel/parser@7.28.5': + dependencies: + '@babel/types': 7.28.5 + + '@babel/plugin-proposal-decorators@7.28.0(@babel/core@7.28.5)': + dependencies: + '@babel/core': 7.28.5 + '@babel/helper-create-class-features-plugin': 7.28.5(@babel/core@7.28.5) + '@babel/helper-plugin-utils': 7.27.1 + '@babel/plugin-syntax-decorators': 7.27.1(@babel/core@7.28.5) + transitivePeerDependencies: + - supports-color + + '@babel/plugin-syntax-decorators@7.27.1(@babel/core@7.28.5)': + dependencies: + '@babel/core': 7.28.5 + '@babel/helper-plugin-utils': 7.27.1 + + '@babel/plugin-syntax-import-attributes@7.27.1(@babel/core@7.28.5)': + dependencies: + '@babel/core': 7.28.5 + '@babel/helper-plugin-utils': 7.27.1 + + '@babel/plugin-syntax-import-meta@7.10.4(@babel/core@7.28.5)': + dependencies: + '@babel/core': 7.28.5 + '@babel/helper-plugin-utils': 7.27.1 + + '@babel/plugin-syntax-jsx@7.27.1(@babel/core@7.28.5)': + dependencies: + '@babel/core': 7.28.5 + '@babel/helper-plugin-utils': 7.27.1 + + '@babel/plugin-syntax-typescript@7.27.1(@babel/core@7.28.5)': + dependencies: + '@babel/core': 7.28.5 + '@babel/helper-plugin-utils': 7.27.1 + + '@babel/plugin-transform-typescript@7.28.5(@babel/core@7.28.5)': + dependencies: + '@babel/core': 7.28.5 + '@babel/helper-annotate-as-pure': 7.27.3 + '@babel/helper-create-class-features-plugin': 7.28.5(@babel/core@7.28.5) + '@babel/helper-plugin-utils': 7.27.1 + '@babel/helper-skip-transparent-expression-wrappers': 7.27.1 + '@babel/plugin-syntax-typescript': 7.27.1(@babel/core@7.28.5) + transitivePeerDependencies: + - supports-color + + '@babel/template@7.27.2': + dependencies: + '@babel/code-frame': 7.27.1 + '@babel/parser': 7.28.5 + '@babel/types': 7.28.5 + + '@babel/traverse@7.28.5': + dependencies: + '@babel/code-frame': 7.27.1 + '@babel/generator': 7.28.5 + '@babel/helper-globals': 7.28.0 + '@babel/parser': 7.28.5 + '@babel/template': 7.27.2 + '@babel/types': 7.28.5 + debug: 4.4.3 + transitivePeerDependencies: + - supports-color + + '@babel/types@7.28.5': + dependencies: + '@babel/helper-string-parser': 7.27.1 + '@babel/helper-validator-identifier': 7.28.5 + + '@cacheable/memoize@2.0.3': + dependencies: + '@cacheable/utils': 2.2.0 + + '@cacheable/memory@2.0.4': + dependencies: + '@cacheable/utils': 2.2.0 + '@keyv/bigmap': 1.1.0(keyv@5.5.3) + hookified: 1.12.2 + keyv: 5.5.3 + + '@cacheable/utils@2.2.0': + dependencies: + keyv: 5.5.3 + + '@clack/core@1.5.1': + dependencies: + fast-wrap-ansi: 0.2.2 + sisteransi: 1.0.5 + + '@clack/prompts@1.8.1': + dependencies: + '@clack/core': 1.5.1 + fast-string-width: 3.0.2 + fast-wrap-ansi: 0.2.2 + sisteransi: 1.0.5 + + '@csstools/css-parser-algorithms@3.0.5(@csstools/css-tokenizer@3.0.4)': + dependencies: + '@csstools/css-tokenizer': 3.0.4 + + '@csstools/css-tokenizer@3.0.4': {} + + '@csstools/media-query-list-parser@4.0.3(@csstools/css-parser-algorithms@3.0.5(@csstools/css-tokenizer@3.0.4))(@csstools/css-tokenizer@3.0.4)': + dependencies: + '@csstools/css-parser-algorithms': 3.0.5(@csstools/css-tokenizer@3.0.4) + '@csstools/css-tokenizer': 3.0.4 + + '@csstools/selector-specificity@5.0.0(postcss-selector-parser@7.1.0)': + dependencies: + postcss-selector-parser: 7.1.0 + + '@dual-bundle/import-meta-resolve@4.2.1': {} + + '@e18e/eslint-plugin@0.2.0(eslint@9.39.1)': + dependencies: + eslint-plugin-depend: 1.5.0(eslint@9.39.1) + optionalDependencies: + eslint: 9.39.1 + + '@es-joy/jsdoccomment@0.86.0': + dependencies: + '@types/estree': 1.0.8 + '@typescript-eslint/types': 8.70.1 + comment-parser: 1.4.6 + esquery: 1.7.0 + jsdoc-type-pratt-parser: 7.2.0 + + '@es-joy/jsdoccomment@0.88.0': + dependencies: + '@types/estree': 1.0.9 + '@typescript-eslint/types': 8.70.1 + comment-parser: 1.4.7 + esquery: 1.7.0 + jsdoc-type-pratt-parser: 7.2.0 + + '@es-joy/resolve.exports@1.2.0': {} + + '@esbuild/aix-ppc64@0.25.12': + optional: true + + '@esbuild/android-arm64@0.25.12': + optional: true + + '@esbuild/android-arm@0.25.12': + optional: true + + '@esbuild/android-x64@0.25.12': + optional: true + + '@esbuild/darwin-arm64@0.25.12': + optional: true + + '@esbuild/darwin-x64@0.25.12': + optional: true + + '@esbuild/freebsd-arm64@0.25.12': + optional: true + + '@esbuild/freebsd-x64@0.25.12': + optional: true + + '@esbuild/linux-arm64@0.25.12': + optional: true + + '@esbuild/linux-arm@0.25.12': + optional: true + + '@esbuild/linux-ia32@0.25.12': + optional: true + + '@esbuild/linux-loong64@0.25.12': + optional: true + + '@esbuild/linux-mips64el@0.25.12': + optional: true + + '@esbuild/linux-ppc64@0.25.12': + optional: true + + '@esbuild/linux-riscv64@0.25.12': + optional: true + + '@esbuild/linux-s390x@0.25.12': + optional: true + + '@esbuild/linux-x64@0.25.12': + optional: true + + '@esbuild/netbsd-arm64@0.25.12': + optional: true + + '@esbuild/netbsd-x64@0.25.12': + optional: true + + '@esbuild/openbsd-arm64@0.25.12': + optional: true + + '@esbuild/openbsd-x64@0.25.12': + optional: true + + '@esbuild/openharmony-arm64@0.25.12': + optional: true + + '@esbuild/sunos-x64@0.25.12': + optional: true + + '@esbuild/win32-arm64@0.25.12': + optional: true + + '@esbuild/win32-ia32@0.25.12': + optional: true + + '@esbuild/win32-x64@0.25.12': + optional: true + + '@eslint-community/eslint-plugin-eslint-comments@4.8.1(eslint@9.39.1)': + dependencies: + escape-string-regexp: 4.0.0 + eslint: 9.39.1 + ignore: 7.0.5 + + '@eslint-community/eslint-utils@4.10.1(eslint@9.39.1)': + dependencies: + eslint: 9.39.1 + eslint-visitor-keys: 3.4.3 + + '@eslint-community/eslint-utils@4.9.0(eslint@9.39.1)': + dependencies: + eslint: 9.39.1 + eslint-visitor-keys: 3.4.3 + + '@eslint-community/regexpp@4.12.2': {} + + '@eslint/compat@2.1.1(eslint@9.39.1)': + dependencies: + '@eslint/core': 1.2.1 + optionalDependencies: + eslint: 9.39.1 + + '@eslint/config-array@0.21.1': + dependencies: + '@eslint/object-schema': 2.1.7 + debug: 4.4.3 + minimatch: 3.1.2 + transitivePeerDependencies: + - supports-color + + '@eslint/config-helpers@0.4.2': + dependencies: + '@eslint/core': 0.17.0 + + '@eslint/config-helpers@0.5.5': + dependencies: + '@eslint/core': 1.2.1 + + '@eslint/core@0.17.0': + dependencies: + '@types/json-schema': 7.0.15 + + '@eslint/core@1.2.1': + dependencies: + '@types/json-schema': 7.0.15 + + '@eslint/eslintrc@3.3.1': + dependencies: + ajv: 6.12.6 + debug: 4.4.3 + espree: 10.4.0 + globals: 14.0.0 + ignore: 5.3.2 + import-fresh: 3.3.1 + js-yaml: 4.1.0 + minimatch: 3.1.2 + strip-json-comments: 3.1.1 + transitivePeerDependencies: + - supports-color + + '@eslint/js@9.39.1': {} + + '@eslint/markdown@7.5.1': + dependencies: + '@eslint/core': 0.17.0 + '@eslint/plugin-kit': 0.4.1 + github-slugger: 2.0.0 + mdast-util-from-markdown: 2.0.2 + mdast-util-frontmatter: 2.0.1 + mdast-util-gfm: 3.1.0 + micromark-extension-frontmatter: 2.0.0 + micromark-extension-gfm: 3.0.0 + micromark-util-normalize-identifier: 2.0.1 + transitivePeerDependencies: + - supports-color + + '@eslint/object-schema@2.1.7': {} + + '@eslint/plugin-kit@0.4.1': + dependencies: + '@eslint/core': 0.17.0 + levn: 0.4.1 + + '@eslint/plugin-kit@0.7.3': + dependencies: + '@eslint/core': 1.2.1 + levn: 0.4.1 + + '@humanfs/core@0.19.1': {} + + '@humanfs/node@0.16.7': + dependencies: + '@humanfs/core': 0.19.1 + '@humanwhocodes/retry': 0.4.3 + + '@humanwhocodes/module-importer@1.0.1': {} + + '@humanwhocodes/retry@0.4.3': {} + + '@iconify-json/icon-park-outline@1.2.4': + dependencies: + '@iconify/types': 2.0.0 + + '@iconify/types@2.0.0': {} + + '@iconify/utils@3.1.7': + dependencies: + '@antfu/install-pkg': 2.1.0 + '@iconify/types': 2.0.0 + import-meta-resolve: 4.2.0 + + '@iconify/vue@5.0.3(vue@3.5.23(typescript@5.9.3))': + dependencies: + '@iconify/types': 2.0.0 + vue: 3.5.23(typescript@5.9.3) + + '@jridgewell/gen-mapping@0.3.13': + dependencies: + '@jridgewell/sourcemap-codec': 1.5.5 + '@jridgewell/trace-mapping': 0.3.31 + + '@jridgewell/remapping@2.3.5': + dependencies: + '@jridgewell/gen-mapping': 0.3.13 + '@jridgewell/trace-mapping': 0.3.31 + + '@jridgewell/resolve-uri@3.1.2': {} + + '@jridgewell/sourcemap-codec@1.5.5': {} + + '@jridgewell/trace-mapping@0.3.31': + dependencies: + '@jridgewell/resolve-uri': 3.1.2 + '@jridgewell/sourcemap-codec': 1.5.5 + + '@keyv/bigmap@1.1.0(keyv@5.5.3)': + dependencies: + hookified: 1.12.2 + keyv: 5.5.3 + + '@keyv/serialize@1.1.1': {} + + '@nodelib/fs.scandir@2.1.5': + dependencies: + '@nodelib/fs.stat': 2.0.5 + run-parallel: 1.2.0 + + '@nodelib/fs.stat@2.0.5': {} + + '@nodelib/fs.walk@1.2.8': + dependencies: + '@nodelib/fs.scandir': 2.1.5 + fastq: 1.19.1 + + '@ota-meshi/ast-token-store@0.3.0': {} + + '@parcel/watcher-android-arm64@2.5.1': + optional: true + + '@parcel/watcher-darwin-arm64@2.5.1': + optional: true + + '@parcel/watcher-darwin-x64@2.5.1': + optional: true + + '@parcel/watcher-freebsd-x64@2.5.1': + optional: true + + '@parcel/watcher-linux-arm-glibc@2.5.1': + optional: true + + '@parcel/watcher-linux-arm-musl@2.5.1': + optional: true + + '@parcel/watcher-linux-arm64-glibc@2.5.1': + optional: true + + '@parcel/watcher-linux-arm64-musl@2.5.1': + optional: true + + '@parcel/watcher-linux-x64-glibc@2.5.1': + optional: true + + '@parcel/watcher-linux-x64-musl@2.5.1': + optional: true + + '@parcel/watcher-win32-arm64@2.5.1': + optional: true + + '@parcel/watcher-win32-ia32@2.5.1': + optional: true + + '@parcel/watcher-win32-x64@2.5.1': + optional: true + + '@parcel/watcher@2.5.1': + dependencies: + detect-libc: 1.0.3 + is-glob: 4.0.3 + micromatch: 4.0.8 + node-addon-api: 7.1.1 + optionalDependencies: + '@parcel/watcher-android-arm64': 2.5.1 + '@parcel/watcher-darwin-arm64': 2.5.1 + '@parcel/watcher-darwin-x64': 2.5.1 + '@parcel/watcher-freebsd-x64': 2.5.1 + '@parcel/watcher-linux-arm-glibc': 2.5.1 + '@parcel/watcher-linux-arm-musl': 2.5.1 + '@parcel/watcher-linux-arm64-glibc': 2.5.1 + '@parcel/watcher-linux-arm64-musl': 2.5.1 + '@parcel/watcher-linux-x64-glibc': 2.5.1 + '@parcel/watcher-linux-x64-musl': 2.5.1 + '@parcel/watcher-win32-arm64': 2.5.1 + '@parcel/watcher-win32-ia32': 2.5.1 + '@parcel/watcher-win32-x64': 2.5.1 + optional: true + + '@pkgr/core@0.3.6': {} + + '@polka/url@1.0.0-next.29': {} + + '@rolldown/pluginutils@1.0.0-beta.29': {} + + '@rolldown/pluginutils@1.0.0-beta.47': {} + + '@rollup/rollup-android-arm-eabi@4.52.5': + optional: true + + '@rollup/rollup-android-arm64@4.52.5': + optional: true + + '@rollup/rollup-darwin-arm64@4.52.5': + optional: true + + '@rollup/rollup-darwin-x64@4.52.5': + optional: true + + '@rollup/rollup-freebsd-arm64@4.52.5': + optional: true + + '@rollup/rollup-freebsd-x64@4.52.5': + optional: true + + '@rollup/rollup-linux-arm-gnueabihf@4.52.5': + optional: true + + '@rollup/rollup-linux-arm-musleabihf@4.52.5': + optional: true + + '@rollup/rollup-linux-arm64-gnu@4.52.5': + optional: true + + '@rollup/rollup-linux-arm64-musl@4.52.5': + optional: true + + '@rollup/rollup-linux-loong64-gnu@4.52.5': + optional: true + + '@rollup/rollup-linux-ppc64-gnu@4.52.5': + optional: true + + '@rollup/rollup-linux-riscv64-gnu@4.52.5': + optional: true + + '@rollup/rollup-linux-riscv64-musl@4.52.5': + optional: true + + '@rollup/rollup-linux-s390x-gnu@4.52.5': + optional: true + + '@rollup/rollup-linux-x64-gnu@4.52.5': + optional: true + + '@rollup/rollup-linux-x64-musl@4.52.5': + optional: true + + '@rollup/rollup-openharmony-arm64@4.52.5': + optional: true + + '@rollup/rollup-win32-arm64-msvc@4.52.5': + optional: true + + '@rollup/rollup-win32-ia32-msvc@4.52.5': + optional: true + + '@rollup/rollup-win32-x64-gnu@4.52.5': + optional: true + + '@rollup/rollup-win32-x64-msvc@4.52.5': + optional: true + + '@sindresorhus/base62@1.0.0': {} + + '@stylistic/eslint-plugin@5.10.0(eslint@9.39.1)': + dependencies: + '@eslint-community/eslint-utils': 4.10.1(eslint@9.39.1) + '@typescript-eslint/types': 8.70.1 + eslint: 9.39.1 + eslint-visitor-keys: 4.2.1 + espree: 10.4.0 + estraverse: 5.3.0 + picomatch: 4.0.3 + + '@types/debug@4.1.12': + dependencies: + '@types/ms': 2.1.0 + + '@types/estree@1.0.8': {} + + '@types/estree@1.0.9': {} + + '@types/json-schema@7.0.15': {} + + '@types/lodash-es@4.17.12': + dependencies: + '@types/lodash': 4.17.20 + + '@types/lodash@4.17.20': {} + + '@types/mdast@4.0.4': + dependencies: + '@types/unist': 3.0.3 + + '@types/ms@2.1.0': {} + + '@types/node@24.10.0': + dependencies: + undici-types: 7.16.0 + + '@types/node@26.6.3': + dependencies: + undici-types: 8.9.0 + + '@types/sortablejs@1.15.9': {} + + '@types/unist@3.0.3': {} + + '@types/web-bluetooth@0.0.21': {} + + '@typescript-eslint/eslint-plugin@8.70.1(@typescript-eslint/parser@8.70.1(eslint@9.39.1)(typescript@5.9.3))(eslint@9.39.1)(typescript@5.9.3)': + dependencies: + '@eslint-community/regexpp': 4.12.2 + '@typescript-eslint/parser': 8.70.1(eslint@9.39.1)(typescript@5.9.3) + '@typescript-eslint/scope-manager': 8.70.1 + '@typescript-eslint/type-utils': 8.70.1(eslint@9.39.1)(typescript@5.9.3) + '@typescript-eslint/utils': 8.70.1(eslint@9.39.1)(typescript@5.9.3) + '@typescript-eslint/visitor-keys': 8.70.1 + eslint: 9.39.1 + ignore: 7.0.5 + natural-compare: 1.4.0 + ts-api-utils: 2.5.0(typescript@5.9.3) + typescript: 5.9.3 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/parser@8.70.1(eslint@9.39.1)(typescript@5.9.3)': + dependencies: + '@typescript-eslint/scope-manager': 8.70.1 + '@typescript-eslint/types': 8.70.1 + '@typescript-eslint/typescript-estree': 8.70.1(typescript@5.9.3) + '@typescript-eslint/visitor-keys': 8.70.1 + debug: 4.4.3 + eslint: 9.39.1 + typescript: 5.9.3 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/project-service@8.70.1(typescript@5.9.3)': + dependencies: + '@typescript-eslint/tsconfig-utils': 8.70.1(typescript@5.9.3) + '@typescript-eslint/types': 8.70.1 + debug: 4.4.3 + typescript: 5.9.3 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/scope-manager@8.70.1': + dependencies: + '@typescript-eslint/types': 8.70.1 + '@typescript-eslint/visitor-keys': 8.70.1 + + '@typescript-eslint/tsconfig-utils@8.70.1(typescript@5.9.3)': + dependencies: + typescript: 5.9.3 + + '@typescript-eslint/type-utils@8.70.1(eslint@9.39.1)(typescript@5.9.3)': + dependencies: + '@typescript-eslint/types': 8.70.1 + '@typescript-eslint/typescript-estree': 8.70.1(typescript@5.9.3) + '@typescript-eslint/utils': 8.70.1(eslint@9.39.1)(typescript@5.9.3) + debug: 4.4.3 + eslint: 9.39.1 + ts-api-utils: 2.5.0(typescript@5.9.3) + typescript: 5.9.3 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/types@8.70.1': {} + + '@typescript-eslint/typescript-estree@8.70.1(typescript@5.9.3)': + dependencies: + '@typescript-eslint/project-service': 8.70.1(typescript@5.9.3) + '@typescript-eslint/tsconfig-utils': 8.70.1(typescript@5.9.3) + '@typescript-eslint/types': 8.70.1 + '@typescript-eslint/visitor-keys': 8.70.1 + debug: 4.4.3 + minimatch: 10.2.6 + semver: 7.7.3 + tinyglobby: 0.2.15 + ts-api-utils: 2.5.0(typescript@5.9.3) + typescript: 5.9.3 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/utils@8.70.1(eslint@9.39.1)(typescript@5.9.3)': + dependencies: + '@eslint-community/eslint-utils': 4.10.1(eslint@9.39.1) + '@typescript-eslint/scope-manager': 8.70.1 + '@typescript-eslint/types': 8.70.1 + '@typescript-eslint/typescript-estree': 8.70.1(typescript@5.9.3) + eslint: 9.39.1 + typescript: 5.9.3 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/visitor-keys@8.70.1': + dependencies: + '@typescript-eslint/types': 8.70.1 + eslint-visitor-keys: 5.0.1 + + '@vitejs/plugin-vue-jsx@5.1.1(vite@7.2.1(@types/node@24.10.0)(less@4.4.2)(sass@1.93.3)(yaml@2.9.1))(vue@3.5.23(typescript@5.9.3))': + dependencies: + '@babel/core': 7.28.5 + '@babel/plugin-syntax-typescript': 7.27.1(@babel/core@7.28.5) + '@babel/plugin-transform-typescript': 7.28.5(@babel/core@7.28.5) + '@rolldown/pluginutils': 1.0.0-beta.47 + '@vue/babel-plugin-jsx': 1.5.0(@babel/core@7.28.5) + vite: 7.2.1(@types/node@24.10.0)(less@4.4.2)(sass@1.93.3)(yaml@2.9.1) + vue: 3.5.23(typescript@5.9.3) + transitivePeerDependencies: + - supports-color + + '@vitejs/plugin-vue@6.0.1(vite@7.2.1(@types/node@24.10.0)(less@4.4.2)(sass@1.93.3)(yaml@2.9.1))(vue@3.5.23(typescript@5.9.3))': + dependencies: + '@rolldown/pluginutils': 1.0.0-beta.29 + vite: 7.2.1(@types/node@24.10.0)(less@4.4.2)(sass@1.93.3)(yaml@2.9.1) + vue: 3.5.23(typescript@5.9.3) + + '@vitest/eslint-plugin@1.6.27(@typescript-eslint/eslint-plugin@8.70.1(@typescript-eslint/parser@8.70.1(eslint@9.39.1)(typescript@5.9.3))(eslint@9.39.1)(typescript@5.9.3))(eslint@9.39.1)(typescript@5.9.3)': + dependencies: + '@typescript-eslint/scope-manager': 8.70.1 + '@typescript-eslint/utils': 8.70.1(eslint@9.39.1)(typescript@5.9.3) + eslint: 9.39.1 + optionalDependencies: + '@typescript-eslint/eslint-plugin': 8.70.1(@typescript-eslint/parser@8.70.1(eslint@9.39.1)(typescript@5.9.3))(eslint@9.39.1)(typescript@5.9.3) + typescript: 5.9.3 + transitivePeerDependencies: + - supports-color + + '@volar/language-core@2.4.23': + dependencies: + '@volar/source-map': 2.4.23 + + '@volar/source-map@2.4.23': {} + + '@volar/typescript@2.4.23': + dependencies: + '@volar/language-core': 2.4.23 + path-browserify: 1.0.1 + vscode-uri: 3.1.0 + + '@vue/babel-helper-vue-transform-on@1.5.0': {} + + '@vue/babel-plugin-jsx@1.5.0(@babel/core@7.28.5)': + dependencies: + '@babel/helper-module-imports': 7.27.1 + '@babel/helper-plugin-utils': 7.27.1 + '@babel/plugin-syntax-jsx': 7.27.1(@babel/core@7.28.5) + '@babel/template': 7.27.2 + '@babel/traverse': 7.28.5 + '@babel/types': 7.28.5 + '@vue/babel-helper-vue-transform-on': 1.5.0 + '@vue/babel-plugin-resolve-type': 1.5.0(@babel/core@7.28.5) + '@vue/shared': 3.5.23 + optionalDependencies: + '@babel/core': 7.28.5 + transitivePeerDependencies: + - supports-color + + '@vue/babel-plugin-resolve-type@1.5.0(@babel/core@7.28.5)': + dependencies: + '@babel/code-frame': 7.27.1 + '@babel/core': 7.28.5 + '@babel/helper-module-imports': 7.27.1 + '@babel/helper-plugin-utils': 7.27.1 + '@babel/parser': 7.28.5 + '@vue/compiler-sfc': 3.5.23 + transitivePeerDependencies: + - supports-color + + '@vue/compiler-core@3.5.23': + dependencies: + '@babel/parser': 7.28.5 + '@vue/shared': 3.5.23 + entities: 4.5.0 + estree-walker: 2.0.2 + source-map-js: 1.2.1 + + '@vue/compiler-dom@3.5.23': + dependencies: + '@vue/compiler-core': 3.5.23 + '@vue/shared': 3.5.23 + + '@vue/compiler-sfc@3.5.23': + dependencies: + '@babel/parser': 7.28.5 + '@vue/compiler-core': 3.5.23 + '@vue/compiler-dom': 3.5.23 + '@vue/compiler-ssr': 3.5.23 + '@vue/shared': 3.5.23 + estree-walker: 2.0.2 + magic-string: 0.30.21 + postcss: 8.5.6 + source-map-js: 1.2.1 + + '@vue/compiler-ssr@3.5.23': + dependencies: + '@vue/compiler-dom': 3.5.23 + '@vue/shared': 3.5.23 + + '@vue/devtools-api@6.6.4': {} + + '@vue/devtools-api@7.7.7': + dependencies: + '@vue/devtools-kit': 7.7.7 + + '@vue/devtools-core@8.0.5(vite@7.2.1(@types/node@24.10.0)(less@4.4.2)(sass@1.93.3)(yaml@2.9.1))(vue@3.5.23(typescript@5.9.3))': + dependencies: + '@vue/devtools-kit': 8.0.5 + '@vue/devtools-shared': 8.0.5 + mitt: 3.0.1 + nanoid: 5.1.6 + pathe: 2.0.3 + vite-hot-client: 2.1.0(vite@7.2.1(@types/node@24.10.0)(less@4.4.2)(sass@1.93.3)(yaml@2.9.1)) + vue: 3.5.23(typescript@5.9.3) + transitivePeerDependencies: + - vite + + '@vue/devtools-kit@7.7.7': + dependencies: + '@vue/devtools-shared': 7.7.7 + birpc: 2.7.0 + hookable: 5.5.3 + mitt: 3.0.1 + perfect-debounce: 1.0.0 + speakingurl: 14.0.1 + superjson: 2.2.5 + + '@vue/devtools-kit@8.0.5': + dependencies: + '@vue/devtools-shared': 8.0.5 + birpc: 2.7.0 + hookable: 5.5.3 + mitt: 3.0.1 + perfect-debounce: 2.0.0 + speakingurl: 14.0.1 + superjson: 2.2.5 + + '@vue/devtools-shared@7.7.7': + dependencies: + rfdc: 1.4.1 + + '@vue/devtools-shared@8.0.5': + dependencies: + rfdc: 1.4.1 + + '@vue/language-core@3.1.3(typescript@5.9.3)': + dependencies: + '@volar/language-core': 2.4.23 + '@vue/compiler-dom': 3.5.23 + '@vue/shared': 3.5.23 + alien-signals: 3.0.6 + muggle-string: 0.4.1 + path-browserify: 1.0.1 + picomatch: 4.0.3 + optionalDependencies: + typescript: 5.9.3 + + '@vue/reactivity@3.5.23': + dependencies: + '@vue/shared': 3.5.23 + + '@vue/runtime-core@3.5.23': + dependencies: + '@vue/reactivity': 3.5.23 + '@vue/shared': 3.5.23 + + '@vue/runtime-dom@3.5.23': + dependencies: + '@vue/reactivity': 3.5.23 + '@vue/runtime-core': 3.5.23 + '@vue/shared': 3.5.23 + csstype: 3.1.3 + + '@vue/server-renderer@3.5.23(vue@3.5.23(typescript@5.9.3))': + dependencies: + '@vue/compiler-ssr': 3.5.23 + '@vue/shared': 3.5.23 + vue: 3.5.23(typescript@5.9.3) + + '@vue/shared@3.5.23': {} + + '@vue/tsconfig@0.7.0(typescript@5.9.3)(vue@3.5.23(typescript@5.9.3))': + optionalDependencies: + typescript: 5.9.3 + vue: 3.5.23(typescript@5.9.3) + + '@vueuse/core@13.9.0(vue@3.5.23(typescript@5.9.3))': + dependencies: + '@types/web-bluetooth': 0.0.21 + '@vueuse/metadata': 13.9.0 + '@vueuse/shared': 13.9.0(vue@3.5.23(typescript@5.9.3)) + vue: 3.5.23(typescript@5.9.3) + + '@vueuse/metadata@13.9.0': {} + + '@vueuse/shared@13.9.0(vue@3.5.23(typescript@5.9.3))': + dependencies: + vue: 3.5.23(typescript@5.9.3) + + acorn-jsx@5.3.2(acorn@8.15.0): + dependencies: + acorn: 8.15.0 + + acorn-jsx@5.3.2(acorn@8.18.0): + dependencies: + acorn: 8.18.0 + + acorn@8.15.0: {} + + acorn@8.18.0: {} + + ajv@6.12.6: + dependencies: + fast-deep-equal: 3.1.3 + fast-json-stable-stringify: 2.1.0 + json-schema-traverse: 0.4.1 + uri-js: 4.4.1 + + ajv@8.17.1: + dependencies: + fast-deep-equal: 3.1.3 + fast-uri: 3.1.0 + json-schema-traverse: 1.0.0 + require-from-string: 2.0.2 + + alien-signals@3.0.6: {} + + animate.css@4.1.1: {} + + ansi-regex@5.0.1: {} + + ansi-styles@4.3.0: + dependencies: + color-convert: 2.0.1 + + ansis@4.2.0: {} + + anymatch@3.1.3: + dependencies: + normalize-path: 3.0.0 + picomatch: 2.3.1 + + are-docs-informative@0.0.2: {} + + argparse@2.0.1: {} + + array-union@2.1.0: {} + + astral-regex@2.0.0: {} + + asynckit@0.4.0: {} + + axios@0.27.2: + dependencies: + follow-redirects: 1.15.11 + form-data: 4.0.4 + transitivePeerDependencies: + - debug + + b-tween@0.3.3: {} + + b-validate@1.5.3: {} + + balanced-match@1.0.2: {} + + balanced-match@2.0.0: {} + + balanced-match@4.0.4: {} + + baseline-browser-mapping@2.8.25: {} + + binary-extensions@2.3.0: {} + + birpc@2.7.0: {} + + boolbase@1.0.0: {} + + brace-expansion@1.1.12: + dependencies: + balanced-match: 1.0.2 + concat-map: 0.0.1 + + brace-expansion@5.0.12: + dependencies: + balanced-match: 4.0.4 + + braces@3.0.3: + dependencies: + fill-range: 7.1.1 + + browserslist@4.27.0: + dependencies: + baseline-browser-mapping: 2.8.25 + caniuse-lite: 1.0.30001754 + electron-to-chromium: 1.5.245 + node-releases: 2.0.27 + update-browserslist-db: 1.1.4(browserslist@4.27.0) + + builtin-modules@5.0.0: {} + + bundle-name@4.1.0: + dependencies: + run-applescript: 7.1.0 + + cac@7.0.0: {} + + cacheable@2.1.1: + dependencies: + '@cacheable/memoize': 2.0.3 + '@cacheable/memory': 2.0.4 + '@cacheable/utils': 2.2.0 + hookified: 1.12.2 + keyv: 5.5.3 + qified: 0.5.1 + + call-bind-apply-helpers@1.0.2: + dependencies: + es-errors: 1.3.0 + function-bind: 1.1.2 + + callsites@3.1.0: {} + + caniuse-lite@1.0.30001754: {} + + ccount@2.0.1: {} + + chalk@4.1.2: + dependencies: + ansi-styles: 4.3.0 + supports-color: 7.2.0 + + change-case@5.4.4: {} + + character-entities@2.0.2: {} + + chokidar@3.6.0: + dependencies: + anymatch: 3.1.3 + braces: 3.0.3 + glob-parent: 5.1.2 + is-binary-path: 2.1.0 + is-glob: 4.0.3 + normalize-path: 3.0.0 + readdirp: 3.6.0 + optionalDependencies: + fsevents: 2.3.3 + + chokidar@4.0.3: + dependencies: + readdirp: 4.1.2 + + ci-info@4.3.1: {} + + clean-regexp@1.0.0: + dependencies: + escape-string-regexp: 1.0.5 + + color-convert@1.9.3: + dependencies: + color-name: 1.1.3 + + color-convert@2.0.1: + dependencies: + color-name: 1.1.4 + + color-name@1.1.3: {} + + color-name@1.1.4: {} + + color-string@1.9.1: + dependencies: + color-name: 1.1.4 + simple-swizzle: 0.2.4 + + color@3.2.1: + dependencies: + color-convert: 1.9.3 + color-string: 1.9.1 + + colord@2.9.3: {} + + combined-stream@1.0.8: + dependencies: + delayed-stream: 1.0.0 + + comment-parser@1.4.1: {} + + comment-parser@1.4.6: {} + + comment-parser@1.4.7: {} + + compute-scroll-into-view@1.0.20: {} + + concat-map@0.0.1: {} + + confbox@0.1.8: {} + + confbox@0.2.2: {} + + convert-source-map@2.0.0: {} + + copy-anything@2.0.6: + dependencies: + is-what: 3.14.1 + + copy-anything@4.0.5: + dependencies: + is-what: 5.5.0 + + core-js-compat@3.46.0: + dependencies: + browserslist: 4.27.0 + + cosmiconfig@9.0.0(typescript@5.9.3): + dependencies: + env-paths: 2.2.1 + import-fresh: 3.3.1 + js-yaml: 4.1.0 + parse-json: 5.2.0 + optionalDependencies: + typescript: 5.9.3 + + cross-spawn@7.0.6: + dependencies: + path-key: 3.1.1 + shebang-command: 2.0.0 + which: 2.0.2 + + css-functions-list@3.2.3: {} + + css-tree@3.1.0: + dependencies: + mdn-data: 2.12.2 + source-map-js: 1.2.1 + + cssesc@3.0.0: {} + + csstype@3.1.3: {} + + dayjs@1.11.19: {} + + debug@4.4.3: + dependencies: + ms: 2.1.3 + + decode-named-character-reference@1.2.0: + dependencies: + character-entities: 2.0.2 + + deep-is@0.1.4: {} + + default-browser-id@5.0.1: {} + + default-browser@5.4.0: + dependencies: + bundle-name: 4.1.0 + default-browser-id: 5.0.1 + + define-lazy-prop@3.0.0: {} + + defu@6.1.4: {} + + delayed-stream@1.0.0: {} + + dequal@2.0.3: {} + + detect-libc@1.0.3: + optional: true + + devlop@1.1.0: + dependencies: + dequal: 2.0.3 + + diff-sequences@29.6.3: {} + + dir-glob@3.0.1: + dependencies: + path-type: 4.0.0 + + dom-serializer@2.0.0: + dependencies: + domelementtype: 2.3.0 + domhandler: 5.0.3 + entities: 4.5.0 + + domelementtype@2.3.0: {} + + domhandler@5.0.3: + dependencies: + domelementtype: 2.3.0 + + domutils@3.2.2: + dependencies: + dom-serializer: 2.0.0 + domelementtype: 2.3.0 + domhandler: 5.0.3 + + dunder-proto@1.0.1: + dependencies: + call-bind-apply-helpers: 1.0.2 + es-errors: 1.3.0 + gopd: 1.2.0 + + echarts@6.1.0: + dependencies: + tslib: 2.3.0 + zrender: 6.1.0 + + electron-to-chromium@1.5.245: {} + + emoji-regex@8.0.0: {} + + empathic@2.0.0: {} + + empathic@2.1.0: {} + + enhanced-resolve@5.18.3: + dependencies: + graceful-fs: 4.2.11 + tapable: 2.3.0 + + entities@4.5.0: {} + + env-paths@2.2.1: {} + + errno@0.1.8: + dependencies: + prr: 1.0.1 + optional: true + + error-ex@1.3.4: + dependencies: + is-arrayish: 0.2.1 + + error-stack-parser-es@1.0.5: {} + + es-define-property@1.0.1: {} + + es-errors@1.3.0: {} + + es-object-atoms@1.1.1: + dependencies: + es-errors: 1.3.0 + + es-set-tostringtag@2.1.0: + dependencies: + es-errors: 1.3.0 + get-intrinsic: 1.3.0 + has-tostringtag: 1.0.2 + hasown: 2.0.2 + + esbuild@0.25.12: + optionalDependencies: + '@esbuild/aix-ppc64': 0.25.12 + '@esbuild/android-arm': 0.25.12 + '@esbuild/android-arm64': 0.25.12 + '@esbuild/android-x64': 0.25.12 + '@esbuild/darwin-arm64': 0.25.12 + '@esbuild/darwin-x64': 0.25.12 + '@esbuild/freebsd-arm64': 0.25.12 + '@esbuild/freebsd-x64': 0.25.12 + '@esbuild/linux-arm': 0.25.12 + '@esbuild/linux-arm64': 0.25.12 + '@esbuild/linux-ia32': 0.25.12 + '@esbuild/linux-loong64': 0.25.12 + '@esbuild/linux-mips64el': 0.25.12 + '@esbuild/linux-ppc64': 0.25.12 + '@esbuild/linux-riscv64': 0.25.12 + '@esbuild/linux-s390x': 0.25.12 + '@esbuild/linux-x64': 0.25.12 + '@esbuild/netbsd-arm64': 0.25.12 + '@esbuild/netbsd-x64': 0.25.12 + '@esbuild/openbsd-arm64': 0.25.12 + '@esbuild/openbsd-x64': 0.25.12 + '@esbuild/openharmony-arm64': 0.25.12 + '@esbuild/sunos-x64': 0.25.12 + '@esbuild/win32-arm64': 0.25.12 + '@esbuild/win32-ia32': 0.25.12 + '@esbuild/win32-x64': 0.25.12 + + escalade@3.2.0: {} + + escape-string-regexp@1.0.5: {} + + escape-string-regexp@4.0.0: {} + + escape-string-regexp@5.0.0: {} + + eslint-compat-utils@0.5.1(eslint@9.39.1): + dependencies: + eslint: 9.39.1 + semver: 7.7.3 + + eslint-config-flat-gitignore@2.4.0(eslint@9.39.1): + dependencies: + '@eslint/compat': 2.1.1(eslint@9.39.1) + eslint: 9.39.1 + + eslint-flat-config-utils@3.2.0: + dependencies: + '@eslint/config-helpers': 0.5.5 + pathe: 2.0.3 + + eslint-json-compat-utils@0.2.3(eslint@9.39.1)(jsonc-eslint-parser@3.3.0): + dependencies: + eslint: 9.39.1 + esquery: 1.6.0 + jsonc-eslint-parser: 3.3.0 + + eslint-merge-processors@2.0.0(eslint@9.39.1): + dependencies: + eslint: 9.39.1 + + eslint-plugin-antfu@3.2.3(eslint@9.39.1): + dependencies: + eslint: 9.39.1 + + eslint-plugin-command@3.5.3(@typescript-eslint/typescript-estree@8.70.1(typescript@5.9.3))(@typescript-eslint/utils@8.70.1(eslint@9.39.1)(typescript@5.9.3))(eslint@9.39.1): + dependencies: + '@es-joy/jsdoccomment': 0.88.0 + '@typescript-eslint/typescript-estree': 8.70.1(typescript@5.9.3) + '@typescript-eslint/utils': 8.70.1(eslint@9.39.1)(typescript@5.9.3) + eslint: 9.39.1 + + eslint-plugin-depend@1.5.0(eslint@9.39.1): + dependencies: + empathic: 2.0.0 + eslint: 9.39.1 + module-replacements: 2.11.0 + semver: 7.7.3 + + eslint-plugin-es-x@7.8.0(eslint@9.39.1): + dependencies: + '@eslint-community/eslint-utils': 4.9.0(eslint@9.39.1) + '@eslint-community/regexpp': 4.12.2 + eslint: 9.39.1 + eslint-compat-utils: 0.5.1(eslint@9.39.1) + + eslint-plugin-import-lite@0.5.2(eslint@9.39.1): + dependencies: + eslint: 9.39.1 + + eslint-plugin-jsdoc@62.9.0(eslint@9.39.1): + dependencies: + '@es-joy/jsdoccomment': 0.86.0 + '@es-joy/resolve.exports': 1.2.0 + are-docs-informative: 0.0.2 + comment-parser: 1.4.6 + debug: 4.4.3 + escape-string-regexp: 4.0.0 + eslint: 9.39.1 + espree: 11.2.0 + esquery: 1.7.0 + html-entities: 2.6.0 + object-deep-merge: 2.0.1 + parse-imports-exports: 0.2.4 + semver: 7.8.5 + spdx-expression-parse: 4.0.0 + to-valid-identifier: 1.0.0 + transitivePeerDependencies: + - supports-color + + eslint-plugin-jsonc@3.4.2(eslint@9.39.1): + dependencies: + '@eslint-community/eslint-utils': 4.9.0(eslint@9.39.1) + '@eslint/core': 1.2.1 + '@eslint/plugin-kit': 0.7.3 + '@ota-meshi/ast-token-store': 0.3.0 + diff-sequences: 29.6.3 + eslint: 9.39.1 + eslint-json-compat-utils: 0.2.3(eslint@9.39.1)(jsonc-eslint-parser@3.3.0) + jsonc-eslint-parser: 3.3.0 + natural-compare: 1.4.0 + synckit: 0.11.13 + transitivePeerDependencies: + - '@eslint/json' + + eslint-plugin-n@17.24.0(eslint@9.39.1)(typescript@5.9.3): + dependencies: + '@eslint-community/eslint-utils': 4.9.0(eslint@9.39.1) + enhanced-resolve: 5.18.3 + eslint: 9.39.1 + eslint-plugin-es-x: 7.8.0(eslint@9.39.1) + get-tsconfig: 4.13.0 + globals: 15.15.0 + globrex: 0.1.2 + ignore: 5.3.2 + semver: 7.7.3 + ts-declaration-location: 1.0.7(typescript@5.9.3) + transitivePeerDependencies: + - typescript + + eslint-plugin-no-only-tests@3.3.0: {} + + eslint-plugin-perfectionist@5.12.1(eslint@9.39.1)(typescript@5.9.3): + dependencies: + '@typescript-eslint/types': 8.70.1 + '@typescript-eslint/utils': 8.70.1(eslint@9.39.1)(typescript@5.9.3) + natural-orderby: 5.0.0 + optionalDependencies: + eslint: 9.39.1 + transitivePeerDependencies: + - supports-color + - typescript + + eslint-plugin-pnpm@1.9.1(eslint@9.39.1): + dependencies: + empathic: 2.1.0 + eslint: 9.39.1 + eslint-json-compat-utils: 0.2.3(eslint@9.39.1)(jsonc-eslint-parser@3.3.0) + jsonc-eslint-parser: 3.3.0 + pathe: 2.0.3 + pnpm-workspace-yaml: 1.9.1 + tinyglobby: 0.2.17 + yaml: 2.9.1 + yaml-eslint-parser: 2.1.0 + transitivePeerDependencies: + - '@eslint/json' + + eslint-plugin-regexp@3.3.1(eslint@9.39.1): + dependencies: + '@eslint-community/eslint-utils': 4.9.0(eslint@9.39.1) + '@eslint-community/regexpp': 4.12.2 + comment-parser: 1.4.1 + eslint: 9.39.1 + jsdoc-type-pratt-parser: 9.2.2 + refa: 0.12.1 + regexp-ast-analysis: 0.7.1 + scslre: 0.3.0 + + eslint-plugin-toml@1.5.0(eslint@9.39.1): + dependencies: + '@eslint/core': 1.2.1 + '@eslint/plugin-kit': 0.7.3 + '@ota-meshi/ast-token-store': 0.3.0 + debug: 4.4.3 + eslint: 9.39.1 + toml-eslint-parser: 1.0.3 + transitivePeerDependencies: + - supports-color + + eslint-plugin-unicorn@63.0.0(eslint@9.39.1): + dependencies: + '@babel/helper-validator-identifier': 7.28.5 + '@eslint-community/eslint-utils': 4.9.0(eslint@9.39.1) + change-case: 5.4.4 + ci-info: 4.3.1 + clean-regexp: 1.0.0 + core-js-compat: 3.46.0 + eslint: 9.39.1 + find-up-simple: 1.0.1 + globals: 16.5.0 + indent-string: 5.0.0 + is-builtin-module: 5.0.0 + jsesc: 3.1.0 + pluralize: 8.0.0 + regexp-tree: 0.1.27 + regjsparser: 0.13.3 + semver: 7.7.3 + strip-indent: 4.1.1 + + eslint-plugin-unused-imports@4.4.1(@typescript-eslint/eslint-plugin@8.70.1(@typescript-eslint/parser@8.70.1(eslint@9.39.1)(typescript@5.9.3))(eslint@9.39.1)(typescript@5.9.3))(eslint@9.39.1): + dependencies: + eslint: 9.39.1 + optionalDependencies: + '@typescript-eslint/eslint-plugin': 8.70.1(@typescript-eslint/parser@8.70.1(eslint@9.39.1)(typescript@5.9.3))(eslint@9.39.1)(typescript@5.9.3) + + eslint-plugin-vue@10.11.1(@stylistic/eslint-plugin@5.10.0(eslint@9.39.1))(@typescript-eslint/parser@8.70.1(eslint@9.39.1)(typescript@5.9.3))(eslint@9.39.1)(vue-eslint-parser@10.4.1(eslint@9.39.1)): + dependencies: + '@eslint-community/eslint-utils': 4.10.1(eslint@9.39.1) + eslint: 9.39.1 + natural-compare: 1.4.0 + nth-check: 2.1.1 + postcss-selector-parser: 7.1.6 + semver: 7.8.5 + vue-eslint-parser: 10.4.1(eslint@9.39.1) + xml-name-validator: 5.0.0 + optionalDependencies: + '@stylistic/eslint-plugin': 5.10.0(eslint@9.39.1) + '@typescript-eslint/parser': 8.70.1(eslint@9.39.1)(typescript@5.9.3) + + eslint-plugin-yml@3.8.1(eslint@9.39.1): + dependencies: + '@eslint/core': 1.2.1 + '@eslint/plugin-kit': 0.7.3 + '@ota-meshi/ast-token-store': 0.3.0 + diff-sequences: 29.6.3 + escape-string-regexp: 5.0.0 + eslint: 9.39.1 + natural-compare: 1.4.0 + yaml-eslint-parser: 2.1.0 + + eslint-processor-vue-blocks@2.0.0(@vue/compiler-sfc@3.5.23)(eslint@9.39.1): + dependencies: + '@vue/compiler-sfc': 3.5.23 + eslint: 9.39.1 + + eslint-scope@8.4.0: + dependencies: + esrecurse: 4.3.0 + estraverse: 5.3.0 + + eslint-visitor-keys@3.4.3: {} + + eslint-visitor-keys@4.2.1: {} + + eslint-visitor-keys@5.0.1: {} + + eslint@9.39.1: + dependencies: + '@eslint-community/eslint-utils': 4.9.0(eslint@9.39.1) + '@eslint-community/regexpp': 4.12.2 + '@eslint/config-array': 0.21.1 + '@eslint/config-helpers': 0.4.2 + '@eslint/core': 0.17.0 + '@eslint/eslintrc': 3.3.1 + '@eslint/js': 9.39.1 + '@eslint/plugin-kit': 0.4.1 + '@humanfs/node': 0.16.7 + '@humanwhocodes/module-importer': 1.0.1 + '@humanwhocodes/retry': 0.4.3 + '@types/estree': 1.0.8 + ajv: 6.12.6 + chalk: 4.1.2 + cross-spawn: 7.0.6 + debug: 4.4.3 + escape-string-regexp: 4.0.0 + eslint-scope: 8.4.0 + eslint-visitor-keys: 4.2.1 + espree: 10.4.0 + esquery: 1.6.0 + esutils: 2.0.3 + fast-deep-equal: 3.1.3 + file-entry-cache: 8.0.0 + find-up: 5.0.0 + glob-parent: 6.0.2 + ignore: 5.3.2 + imurmurhash: 0.1.4 + is-glob: 4.0.3 + json-stable-stringify-without-jsonify: 1.0.1 + lodash.merge: 4.6.2 + minimatch: 3.1.2 + natural-compare: 1.4.0 + optionator: 0.9.4 + transitivePeerDependencies: + - supports-color + + espree@10.4.0: + dependencies: + acorn: 8.15.0 + acorn-jsx: 5.3.2(acorn@8.15.0) + eslint-visitor-keys: 4.2.1 + + espree@11.2.0: + dependencies: + acorn: 8.18.0 + acorn-jsx: 5.3.2(acorn@8.18.0) + eslint-visitor-keys: 5.0.1 + + esquery@1.6.0: + dependencies: + estraverse: 5.3.0 + + esquery@1.7.0: + dependencies: + estraverse: 5.3.0 + + esrecurse@4.3.0: + dependencies: + estraverse: 5.3.0 + + estraverse@5.3.0: {} + + estree-walker@2.0.2: {} + + estree-walker@3.0.3: + dependencies: + '@types/estree': 1.0.8 + + esutils@2.0.3: {} + + exsolve@1.0.7: {} + + fast-deep-equal@3.1.3: {} + + fast-glob@3.3.3: + dependencies: + '@nodelib/fs.stat': 2.0.5 + '@nodelib/fs.walk': 1.2.8 + glob-parent: 5.1.2 + merge2: 1.4.1 + micromatch: 4.0.8 + + fast-json-stable-stringify@2.1.0: {} + + fast-levenshtein@2.0.6: {} + + fast-string-truncated-width@3.0.3: {} + + fast-string-width@3.0.2: + dependencies: + fast-string-truncated-width: 3.0.3 + + fast-uri@3.1.0: {} + + fast-wrap-ansi@0.2.2: + dependencies: + fast-string-width: 3.0.2 + + fastest-levenshtein@1.0.16: {} + + fastq@1.19.1: + dependencies: + reusify: 1.1.0 + + fault@2.0.1: + dependencies: + format: 0.2.2 + + fdir@6.5.0(picomatch@4.0.3): + optionalDependencies: + picomatch: 4.0.3 + + fdir@6.5.0(picomatch@4.0.7): + optionalDependencies: + picomatch: 4.0.7 + + file-entry-cache@10.1.4: + dependencies: + flat-cache: 6.1.18 + + file-entry-cache@8.0.0: + dependencies: + flat-cache: 4.0.1 + + fill-range@7.1.1: + dependencies: + to-regex-range: 5.0.1 + + find-up-simple@1.0.1: {} + + find-up@5.0.0: + dependencies: + locate-path: 6.0.0 + path-exists: 4.0.0 + + flat-cache@4.0.1: + dependencies: + flatted: 3.3.3 + keyv: 4.5.4 + + flat-cache@6.1.18: + dependencies: + cacheable: 2.1.1 + flatted: 3.3.3 + hookified: 1.12.2 + + flatted@3.3.3: {} + + follow-redirects@1.15.11: {} + + form-data@4.0.4: + dependencies: + asynckit: 0.4.0 + combined-stream: 1.0.8 + es-set-tostringtag: 2.1.0 + hasown: 2.0.2 + mime-types: 2.1.35 + + format@0.2.2: {} + + fs-extra@10.1.0: + dependencies: + graceful-fs: 4.2.11 + jsonfile: 6.2.0 + universalify: 2.0.1 + + fsevents@2.3.3: + optional: true + + function-bind@1.1.2: {} + + gensync@1.0.0-beta.2: {} + + get-intrinsic@1.3.0: + dependencies: + call-bind-apply-helpers: 1.0.2 + es-define-property: 1.0.1 + es-errors: 1.3.0 + es-object-atoms: 1.1.1 + function-bind: 1.1.2 + get-proto: 1.0.1 + gopd: 1.2.0 + has-symbols: 1.1.0 + hasown: 2.0.2 + math-intrinsics: 1.1.0 + + get-proto@1.0.1: + dependencies: + dunder-proto: 1.0.1 + es-object-atoms: 1.1.1 + + get-tsconfig@4.13.0: + dependencies: + resolve-pkg-maps: 1.0.0 + + github-slugger@2.0.0: {} + + glob-parent@5.1.2: + dependencies: + is-glob: 4.0.3 + + glob-parent@6.0.2: + dependencies: + is-glob: 4.0.3 + + global-modules@2.0.0: + dependencies: + global-prefix: 3.0.0 + + global-prefix@3.0.0: + dependencies: + ini: 1.3.8 + kind-of: 6.0.3 + which: 1.3.1 + + globals@14.0.0: {} + + globals@15.15.0: {} + + globals@16.5.0: {} + + globals@17.12.0: {} + + globby@11.1.0: + dependencies: + array-union: 2.1.0 + dir-glob: 3.0.1 + fast-glob: 3.3.3 + ignore: 5.3.2 + merge2: 1.4.1 + slash: 3.0.0 + + globjoin@0.1.4: {} + + globrex@0.1.2: {} + + gopd@1.2.0: {} + + graceful-fs@4.2.11: {} + + has-flag@4.0.0: {} + + has-symbols@1.1.0: {} + + has-tostringtag@1.0.2: + dependencies: + has-symbols: 1.1.0 + + hasown@2.0.2: + dependencies: + function-bind: 1.1.2 + + hookable@5.5.3: {} + + hookified@1.12.2: {} + + html-entities@2.6.0: {} + + html-tags@3.3.1: {} + + htmlparser2@8.0.2: + dependencies: + domelementtype: 2.3.0 + domhandler: 5.0.3 + domutils: 3.2.2 + entities: 4.5.0 + + iconv-lite@0.6.3: + dependencies: + safer-buffer: 2.1.2 + optional: true + + ignore@5.3.2: {} + + ignore@7.0.5: {} + + image-size@0.5.5: + optional: true + + immutable@5.1.4: {} + + import-fresh@3.3.1: + dependencies: + parent-module: 1.0.1 + resolve-from: 4.0.0 + + import-meta-resolve@4.2.0: {} + + imurmurhash@0.1.4: {} + + indent-string@5.0.0: {} + + ini@1.3.8: {} + + is-arrayish@0.2.1: {} + + is-arrayish@0.3.4: {} + + is-binary-path@2.1.0: + dependencies: + binary-extensions: 2.3.0 + + is-builtin-module@5.0.0: + dependencies: + builtin-modules: 5.0.0 + + is-docker@3.0.0: {} + + is-extglob@2.1.1: {} + + is-fullwidth-code-point@3.0.0: {} + + is-glob@4.0.3: + dependencies: + is-extglob: 2.1.1 + + is-inside-container@1.0.0: + dependencies: + is-docker: 3.0.0 + + is-number@7.0.0: {} + + is-plain-object@5.0.0: {} + + is-what@3.14.1: {} + + is-what@5.5.0: {} + + is-wsl@3.1.0: + dependencies: + is-inside-container: 1.0.0 + + isexe@2.0.0: {} + + js-tokens@4.0.0: {} + + js-tokens@9.0.1: {} + + js-yaml@4.1.0: + dependencies: + argparse: 2.0.1 + + jsdoc-type-pratt-parser@7.2.0: {} + + jsdoc-type-pratt-parser@9.2.2: + dependencies: + '@types/estree': 1.0.9 + '@types/node': 26.6.3 + + jsencrypt@3.5.4: {} + + jsesc@3.1.0: {} + + json-buffer@3.0.1: {} + + json-parse-even-better-errors@2.3.1: {} + + json-schema-traverse@0.4.1: {} + + json-schema-traverse@1.0.0: {} + + json-stable-stringify-without-jsonify@1.0.1: {} + + json5@2.2.3: {} + + jsonc-eslint-parser@3.3.0: + dependencies: + acorn: 8.15.0 + eslint-visitor-keys: 5.0.1 + verkit: 0.3.2 + + jsonfile@6.2.0: + dependencies: + universalify: 2.0.1 + optionalDependencies: + graceful-fs: 4.2.11 + + keyv@4.5.4: + dependencies: + json-buffer: 3.0.1 + + keyv@5.5.3: + dependencies: + '@keyv/serialize': 1.1.1 + + kind-of@6.0.3: {} + + known-css-properties@0.36.0: {} + + known-css-properties@0.37.0: {} + + kolorist@1.8.0: {} + + less@4.4.2: + dependencies: + copy-anything: 2.0.6 + parse-node-version: 1.0.1 + tslib: 2.8.1 + optionalDependencies: + errno: 0.1.8 + graceful-fs: 4.2.11 + image-size: 0.5.5 + make-dir: 2.1.0 + mime: 1.6.0 + needle: 3.3.1 + source-map: 0.6.1 + + levn@0.4.1: + dependencies: + prelude-ls: 1.2.1 + type-check: 0.4.0 + + lines-and-columns@1.2.4: {} + + local-pkg@1.1.2: + dependencies: + mlly: 1.8.0 + pkg-types: 2.3.0 + quansync: 0.2.11 + + locate-path@6.0.0: + dependencies: + p-locate: 5.0.0 + + lodash-es@4.17.21: {} + + lodash.merge@4.6.2: {} + + lodash.truncate@4.4.2: {} + + longest-streak@3.1.0: {} + + lru-cache@5.1.1: + dependencies: + yallist: 3.1.1 + + magic-string@0.30.21: + dependencies: + '@jridgewell/sourcemap-codec': 1.5.5 + + make-dir@2.1.0: + dependencies: + pify: 4.0.1 + semver: 5.7.2 + optional: true + + markdown-table@3.0.4: {} + + math-intrinsics@1.1.0: {} + + mathml-tag-names@2.1.3: {} + + mdast-util-find-and-replace@3.0.2: + dependencies: + '@types/mdast': 4.0.4 + escape-string-regexp: 5.0.0 + unist-util-is: 6.0.1 + unist-util-visit-parents: 6.0.2 + + mdast-util-from-markdown@2.0.2: + dependencies: + '@types/mdast': 4.0.4 + '@types/unist': 3.0.3 + decode-named-character-reference: 1.2.0 + devlop: 1.1.0 + mdast-util-to-string: 4.0.0 + micromark: 4.0.2 + micromark-util-decode-numeric-character-reference: 2.0.2 + micromark-util-decode-string: 2.0.1 + micromark-util-normalize-identifier: 2.0.1 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + unist-util-stringify-position: 4.0.0 + transitivePeerDependencies: + - supports-color + + mdast-util-frontmatter@2.0.1: + dependencies: + '@types/mdast': 4.0.4 + devlop: 1.1.0 + escape-string-regexp: 5.0.0 + mdast-util-from-markdown: 2.0.2 + mdast-util-to-markdown: 2.1.2 + micromark-extension-frontmatter: 2.0.0 + transitivePeerDependencies: + - supports-color + + mdast-util-gfm-autolink-literal@2.0.1: + dependencies: + '@types/mdast': 4.0.4 + ccount: 2.0.1 + devlop: 1.1.0 + mdast-util-find-and-replace: 3.0.2 + micromark-util-character: 2.1.1 + + mdast-util-gfm-footnote@2.1.0: + dependencies: + '@types/mdast': 4.0.4 + devlop: 1.1.0 + mdast-util-from-markdown: 2.0.2 + mdast-util-to-markdown: 2.1.2 + micromark-util-normalize-identifier: 2.0.1 + transitivePeerDependencies: + - supports-color + + mdast-util-gfm-strikethrough@2.0.0: + dependencies: + '@types/mdast': 4.0.4 + mdast-util-from-markdown: 2.0.2 + mdast-util-to-markdown: 2.1.2 + transitivePeerDependencies: + - supports-color + + mdast-util-gfm-table@2.0.0: + dependencies: + '@types/mdast': 4.0.4 + devlop: 1.1.0 + markdown-table: 3.0.4 + mdast-util-from-markdown: 2.0.2 + mdast-util-to-markdown: 2.1.2 + transitivePeerDependencies: + - supports-color + + mdast-util-gfm-task-list-item@2.0.0: + dependencies: + '@types/mdast': 4.0.4 + devlop: 1.1.0 + mdast-util-from-markdown: 2.0.2 + mdast-util-to-markdown: 2.1.2 + transitivePeerDependencies: + - supports-color + + mdast-util-gfm@3.1.0: + dependencies: + mdast-util-from-markdown: 2.0.2 + mdast-util-gfm-autolink-literal: 2.0.1 + mdast-util-gfm-footnote: 2.1.0 + mdast-util-gfm-strikethrough: 2.0.0 + mdast-util-gfm-table: 2.0.0 + mdast-util-gfm-task-list-item: 2.0.0 + mdast-util-to-markdown: 2.1.2 + transitivePeerDependencies: + - supports-color + + mdast-util-phrasing@4.1.0: + dependencies: + '@types/mdast': 4.0.4 + unist-util-is: 6.0.1 + + mdast-util-to-markdown@2.1.2: + dependencies: + '@types/mdast': 4.0.4 + '@types/unist': 3.0.3 + longest-streak: 3.1.0 + mdast-util-phrasing: 4.1.0 + mdast-util-to-string: 4.0.0 + micromark-util-classify-character: 2.0.1 + micromark-util-decode-string: 2.0.1 + unist-util-visit: 5.0.0 + zwitch: 2.0.4 + + mdast-util-to-string@4.0.0: + dependencies: + '@types/mdast': 4.0.4 + + mdn-data@2.12.2: {} + + mdn-data@2.25.0: {} + + meow@13.2.0: {} + + merge2@1.4.1: {} + + micromark-core-commonmark@2.0.3: + dependencies: + decode-named-character-reference: 1.2.0 + devlop: 1.1.0 + micromark-factory-destination: 2.0.1 + micromark-factory-label: 2.0.1 + micromark-factory-space: 2.0.1 + micromark-factory-title: 2.0.1 + micromark-factory-whitespace: 2.0.1 + micromark-util-character: 2.1.1 + micromark-util-chunked: 2.0.1 + micromark-util-classify-character: 2.0.1 + micromark-util-html-tag-name: 2.0.1 + micromark-util-normalize-identifier: 2.0.1 + micromark-util-resolve-all: 2.0.1 + micromark-util-subtokenize: 2.1.0 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + + micromark-extension-frontmatter@2.0.0: + dependencies: + fault: 2.0.1 + micromark-util-character: 2.1.1 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + + micromark-extension-gfm-autolink-literal@2.1.0: + dependencies: + micromark-util-character: 2.1.1 + micromark-util-sanitize-uri: 2.0.1 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + + micromark-extension-gfm-footnote@2.1.0: + dependencies: + devlop: 1.1.0 + micromark-core-commonmark: 2.0.3 + micromark-factory-space: 2.0.1 + micromark-util-character: 2.1.1 + micromark-util-normalize-identifier: 2.0.1 + micromark-util-sanitize-uri: 2.0.1 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + + micromark-extension-gfm-strikethrough@2.1.0: + dependencies: + devlop: 1.1.0 + micromark-util-chunked: 2.0.1 + micromark-util-classify-character: 2.0.1 + micromark-util-resolve-all: 2.0.1 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + + micromark-extension-gfm-table@2.1.1: + dependencies: + devlop: 1.1.0 + micromark-factory-space: 2.0.1 + micromark-util-character: 2.1.1 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + + micromark-extension-gfm-tagfilter@2.0.0: + dependencies: + micromark-util-types: 2.0.2 + + micromark-extension-gfm-task-list-item@2.1.0: + dependencies: + devlop: 1.1.0 + micromark-factory-space: 2.0.1 + micromark-util-character: 2.1.1 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + + micromark-extension-gfm@3.0.0: + dependencies: + micromark-extension-gfm-autolink-literal: 2.1.0 + micromark-extension-gfm-footnote: 2.1.0 + micromark-extension-gfm-strikethrough: 2.1.0 + micromark-extension-gfm-table: 2.1.1 + micromark-extension-gfm-tagfilter: 2.0.0 + micromark-extension-gfm-task-list-item: 2.1.0 + micromark-util-combine-extensions: 2.0.1 + micromark-util-types: 2.0.2 + + micromark-factory-destination@2.0.1: + dependencies: + micromark-util-character: 2.1.1 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + + micromark-factory-label@2.0.1: + dependencies: + devlop: 1.1.0 + micromark-util-character: 2.1.1 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + + micromark-factory-space@2.0.1: + dependencies: + micromark-util-character: 2.1.1 + micromark-util-types: 2.0.2 + + micromark-factory-title@2.0.1: + dependencies: + micromark-factory-space: 2.0.1 + micromark-util-character: 2.1.1 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + + micromark-factory-whitespace@2.0.1: + dependencies: + micromark-factory-space: 2.0.1 + micromark-util-character: 2.1.1 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + + micromark-util-character@2.1.1: + dependencies: + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + + micromark-util-chunked@2.0.1: + dependencies: + micromark-util-symbol: 2.0.1 + + micromark-util-classify-character@2.0.1: + dependencies: + micromark-util-character: 2.1.1 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + + micromark-util-combine-extensions@2.0.1: + dependencies: + micromark-util-chunked: 2.0.1 + micromark-util-types: 2.0.2 + + micromark-util-decode-numeric-character-reference@2.0.2: + dependencies: + micromark-util-symbol: 2.0.1 + + micromark-util-decode-string@2.0.1: + dependencies: + decode-named-character-reference: 1.2.0 + micromark-util-character: 2.1.1 + micromark-util-decode-numeric-character-reference: 2.0.2 + micromark-util-symbol: 2.0.1 + + micromark-util-encode@2.0.1: {} + + micromark-util-html-tag-name@2.0.1: {} + + micromark-util-normalize-identifier@2.0.1: + dependencies: + micromark-util-symbol: 2.0.1 + + micromark-util-resolve-all@2.0.1: + dependencies: + micromark-util-types: 2.0.2 + + micromark-util-sanitize-uri@2.0.1: + dependencies: + micromark-util-character: 2.1.1 + micromark-util-encode: 2.0.1 + micromark-util-symbol: 2.0.1 + + micromark-util-subtokenize@2.1.0: + dependencies: + devlop: 1.1.0 + micromark-util-chunked: 2.0.1 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + + micromark-util-symbol@2.0.1: {} + + micromark-util-types@2.0.2: {} + + micromark@4.0.2: + dependencies: + '@types/debug': 4.1.12 + debug: 4.4.3 + decode-named-character-reference: 1.2.0 + devlop: 1.1.0 + micromark-core-commonmark: 2.0.3 + micromark-factory-space: 2.0.1 + micromark-util-character: 2.1.1 + micromark-util-chunked: 2.0.1 + micromark-util-combine-extensions: 2.0.1 + micromark-util-decode-numeric-character-reference: 2.0.2 + micromark-util-encode: 2.0.1 + micromark-util-normalize-identifier: 2.0.1 + micromark-util-resolve-all: 2.0.1 + micromark-util-sanitize-uri: 2.0.1 + micromark-util-subtokenize: 2.1.0 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + transitivePeerDependencies: + - supports-color + + micromatch@4.0.8: + dependencies: + braces: 3.0.3 + picomatch: 2.3.1 + + mime-db@1.52.0: {} + + mime-types@2.1.35: + dependencies: + mime-db: 1.52.0 + + mime@1.6.0: + optional: true + + minimatch@10.2.6: + dependencies: + brace-expansion: 5.0.12 + + minimatch@3.1.2: + dependencies: + brace-expansion: 1.1.12 + + mitt@3.0.1: {} + + mlly@1.8.0: + dependencies: + acorn: 8.15.0 + pathe: 2.0.3 + pkg-types: 1.3.1 + ufo: 1.6.1 + + module-replacements@2.11.0: {} + + mrmime@2.0.1: {} + + ms@2.1.3: {} + + muggle-string@0.4.1: {} + + nanoid@3.3.11: {} + + nanoid@5.1.6: {} + + natural-compare@1.4.0: {} + + natural-orderby@5.0.0: {} + + needle@3.3.1: + dependencies: + iconv-lite: 0.6.3 + sax: 1.4.3 + optional: true + + node-addon-api@7.1.1: + optional: true + + node-releases@2.0.27: {} + + normalize-path@3.0.0: {} + + nprogress@0.2.0: {} + + nth-check@2.1.1: + dependencies: + boolbase: 1.0.0 + + number-precision@1.6.0: {} + + object-deep-merge@2.0.1: {} + + ohash@2.0.11: {} + + open@10.2.0: + dependencies: + default-browser: 5.4.0 + define-lazy-prop: 3.0.0 + is-inside-container: 1.0.0 + wsl-utils: 0.1.0 + + optionator@0.9.4: + dependencies: + deep-is: 0.1.4 + fast-levenshtein: 2.0.6 + levn: 0.4.1 + prelude-ls: 1.2.1 + type-check: 0.4.0 + word-wrap: 1.2.5 + + p-limit@3.1.0: + dependencies: + yocto-queue: 0.1.0 + + p-locate@5.0.0: + dependencies: + p-limit: 3.1.0 + + package-manager-detector@1.5.0: {} + + package-manager-detector@1.8.0: {} + + parent-module@1.0.1: + dependencies: + callsites: 3.1.0 + + parse-gitignore@2.0.0: {} + + parse-imports-exports@0.2.4: + dependencies: + parse-statements: 1.0.11 + + parse-json@5.2.0: + dependencies: + '@babel/code-frame': 7.27.1 + error-ex: 1.3.4 + json-parse-even-better-errors: 2.3.1 + lines-and-columns: 1.2.4 + + parse-node-version@1.0.1: {} + + parse-statements@1.0.11: {} + + path-browserify@1.0.1: {} + + path-exists@4.0.0: {} + + path-key@3.1.1: {} + + path-type@4.0.0: {} + + pathe@2.0.3: {} + + perfect-debounce@1.0.0: {} + + perfect-debounce@2.0.0: {} + + picocolors@1.1.1: {} + + picomatch@2.3.1: {} + + picomatch@4.0.3: {} + + picomatch@4.0.7: {} + + pify@4.0.1: + optional: true + + pinia-plugin-persistedstate@4.7.1(pinia@3.0.4(typescript@5.9.3)(vue@3.5.23(typescript@5.9.3))): + dependencies: + defu: 6.1.4 + optionalDependencies: + pinia: 3.0.4(typescript@5.9.3)(vue@3.5.23(typescript@5.9.3)) + + pinia@3.0.4(typescript@5.9.3)(vue@3.5.23(typescript@5.9.3)): + dependencies: + '@vue/devtools-api': 7.7.7 + vue: 3.5.23(typescript@5.9.3) + optionalDependencies: + typescript: 5.9.3 + + pkg-types@1.3.1: + dependencies: + confbox: 0.1.8 + mlly: 1.8.0 + pathe: 2.0.3 + + pkg-types@2.3.0: + dependencies: + confbox: 0.2.2 + exsolve: 1.0.7 + pathe: 2.0.3 + + pluralize@8.0.0: {} + + pnpm-workspace-yaml@1.9.1: + dependencies: + yaml: 2.9.1 + + postcss-html@1.8.0: + dependencies: + htmlparser2: 8.0.2 + js-tokens: 9.0.1 + postcss: 8.5.6 + postcss-safe-parser: 6.0.0(postcss@8.5.6) + + postcss-media-query-parser@0.2.3: {} + + postcss-resolve-nested-selector@0.1.6: {} + + postcss-safe-parser@6.0.0(postcss@8.5.6): + dependencies: + postcss: 8.5.6 + + postcss-safe-parser@7.0.1(postcss@8.5.6): + dependencies: + postcss: 8.5.6 + + postcss-scss@4.0.9(postcss@8.5.6): + dependencies: + postcss: 8.5.6 + + postcss-selector-parser@7.1.0: + dependencies: + cssesc: 3.0.0 + util-deprecate: 1.0.2 + + postcss-selector-parser@7.1.6: + dependencies: + cssesc: 3.0.0 + util-deprecate: 1.0.2 + + postcss-sorting@9.1.0(postcss@8.5.6): + dependencies: + postcss: 8.5.6 + + postcss-value-parser@4.2.0: {} + + postcss@8.5.6: + dependencies: + nanoid: 3.3.11 + picocolors: 1.1.1 + source-map-js: 1.2.1 + + prelude-ls@1.2.1: {} + + prr@1.0.1: + optional: true + + punycode@2.3.1: {} + + qified@0.5.1: + dependencies: + hookified: 1.12.2 + + quansync@0.2.11: {} + + queue-microtask@1.2.3: {} + + readdirp@3.6.0: + dependencies: + picomatch: 2.3.1 + + readdirp@4.1.2: {} + + refa@0.12.1: + dependencies: + '@eslint-community/regexpp': 4.12.2 + + regexp-ast-analysis@0.7.1: + dependencies: + '@eslint-community/regexpp': 4.12.2 + refa: 0.12.1 + + regexp-tree@0.1.27: {} + + regjsparser@0.13.3: + dependencies: + jsesc: 3.1.0 + + require-from-string@2.0.2: {} + + reserved-identifiers@1.2.0: {} + + resize-observer-polyfill@1.5.1: {} + + resolve-from@4.0.0: {} + + resolve-from@5.0.0: {} + + resolve-pkg-maps@1.0.0: {} + + reusify@1.1.0: {} + + rfdc@1.4.1: {} + + rollup@4.52.5: + dependencies: + '@types/estree': 1.0.8 + optionalDependencies: + '@rollup/rollup-android-arm-eabi': 4.52.5 + '@rollup/rollup-android-arm64': 4.52.5 + '@rollup/rollup-darwin-arm64': 4.52.5 + '@rollup/rollup-darwin-x64': 4.52.5 + '@rollup/rollup-freebsd-arm64': 4.52.5 + '@rollup/rollup-freebsd-x64': 4.52.5 + '@rollup/rollup-linux-arm-gnueabihf': 4.52.5 + '@rollup/rollup-linux-arm-musleabihf': 4.52.5 + '@rollup/rollup-linux-arm64-gnu': 4.52.5 + '@rollup/rollup-linux-arm64-musl': 4.52.5 + '@rollup/rollup-linux-loong64-gnu': 4.52.5 + '@rollup/rollup-linux-ppc64-gnu': 4.52.5 + '@rollup/rollup-linux-riscv64-gnu': 4.52.5 + '@rollup/rollup-linux-riscv64-musl': 4.52.5 + '@rollup/rollup-linux-s390x-gnu': 4.52.5 + '@rollup/rollup-linux-x64-gnu': 4.52.5 + '@rollup/rollup-linux-x64-musl': 4.52.5 + '@rollup/rollup-openharmony-arm64': 4.52.5 + '@rollup/rollup-win32-arm64-msvc': 4.52.5 + '@rollup/rollup-win32-ia32-msvc': 4.52.5 + '@rollup/rollup-win32-x64-gnu': 4.52.5 + '@rollup/rollup-win32-x64-msvc': 4.52.5 + fsevents: 2.3.3 + + run-applescript@7.1.0: {} + + run-parallel@1.2.0: + dependencies: + queue-microtask: 1.2.3 + + safer-buffer@2.1.2: + optional: true + + sass@1.93.3: + dependencies: + chokidar: 4.0.3 + immutable: 5.1.4 + source-map-js: 1.2.1 + optionalDependencies: + '@parcel/watcher': 2.5.1 + + sax@1.4.3: + optional: true + + scroll-into-view-if-needed@2.2.31: + dependencies: + compute-scroll-into-view: 1.0.20 + + scslre@0.3.0: + dependencies: + '@eslint-community/regexpp': 4.12.2 + refa: 0.12.1 + regexp-ast-analysis: 0.7.1 + + scule@1.3.0: {} + + semver@5.7.2: + optional: true + + semver@6.3.1: {} + + semver@7.7.3: {} + + semver@7.8.5: {} + + shebang-command@2.0.0: + dependencies: + shebang-regex: 3.0.0 + + shebang-regex@3.0.0: {} + + signal-exit@4.1.0: {} + + simple-swizzle@0.2.4: + dependencies: + is-arrayish: 0.3.4 + + sirv@3.0.2: + dependencies: + '@polka/url': 1.0.0-next.29 + mrmime: 2.0.1 + totalist: 3.0.1 + + sisteransi@1.0.5: {} + + slash@3.0.0: {} + + slice-ansi@4.0.0: + dependencies: + ansi-styles: 4.3.0 + astral-regex: 2.0.0 + is-fullwidth-code-point: 3.0.0 + + sortablejs@1.15.6: {} + + source-map-js@1.2.1: {} + + source-map@0.6.1: + optional: true + + spdx-exceptions@2.5.0: {} + + spdx-expression-parse@4.0.0: + dependencies: + spdx-exceptions: 2.5.0 + spdx-license-ids: 3.0.22 + + spdx-license-ids@3.0.22: {} + + speakingurl@14.0.1: {} + + string-width@4.2.3: + dependencies: + emoji-regex: 8.0.0 + is-fullwidth-code-point: 3.0.0 + strip-ansi: 6.0.1 + + strip-ansi@6.0.1: + dependencies: + ansi-regex: 5.0.1 + + strip-indent@4.1.1: {} + + strip-json-comments@3.1.1: {} + + strip-literal@3.1.0: + dependencies: + js-tokens: 9.0.1 + + stylelint-config-html@1.1.0(postcss-html@1.8.0)(stylelint@16.25.0(typescript@5.9.3)): + dependencies: + postcss-html: 1.8.0 + stylelint: 16.25.0(typescript@5.9.3) + + stylelint-config-recess-order@7.4.0(stylelint-order@7.0.0(stylelint@16.25.0(typescript@5.9.3)))(stylelint@16.25.0(typescript@5.9.3)): + dependencies: + stylelint: 16.25.0(typescript@5.9.3) + stylelint-order: 7.0.0(stylelint@16.25.0(typescript@5.9.3)) + + stylelint-config-recommended-scss@16.0.2(postcss@8.5.6)(stylelint@16.25.0(typescript@5.9.3)): + dependencies: + postcss-scss: 4.0.9(postcss@8.5.6) + stylelint: 16.25.0(typescript@5.9.3) + stylelint-config-recommended: 17.0.0(stylelint@16.25.0(typescript@5.9.3)) + stylelint-scss: 6.12.1(stylelint@16.25.0(typescript@5.9.3)) + optionalDependencies: + postcss: 8.5.6 + + stylelint-config-recommended-vue@1.6.1(postcss-html@1.8.0)(stylelint@16.25.0(typescript@5.9.3)): + dependencies: + postcss-html: 1.8.0 + semver: 7.7.3 + stylelint: 16.25.0(typescript@5.9.3) + stylelint-config-html: 1.1.0(postcss-html@1.8.0)(stylelint@16.25.0(typescript@5.9.3)) + stylelint-config-recommended: 17.0.0(stylelint@16.25.0(typescript@5.9.3)) + + stylelint-config-recommended@17.0.0(stylelint@16.25.0(typescript@5.9.3)): + dependencies: + stylelint: 16.25.0(typescript@5.9.3) + + stylelint-config-standard-scss@16.0.0(postcss@8.5.6)(stylelint@16.25.0(typescript@5.9.3)): + dependencies: + stylelint: 16.25.0(typescript@5.9.3) + stylelint-config-recommended-scss: 16.0.2(postcss@8.5.6)(stylelint@16.25.0(typescript@5.9.3)) + stylelint-config-standard: 39.0.1(stylelint@16.25.0(typescript@5.9.3)) + optionalDependencies: + postcss: 8.5.6 + + stylelint-config-standard@39.0.1(stylelint@16.25.0(typescript@5.9.3)): + dependencies: + stylelint: 16.25.0(typescript@5.9.3) + stylelint-config-recommended: 17.0.0(stylelint@16.25.0(typescript@5.9.3)) + + stylelint-order@7.0.0(stylelint@16.25.0(typescript@5.9.3)): + dependencies: + postcss: 8.5.6 + postcss-sorting: 9.1.0(postcss@8.5.6) + stylelint: 16.25.0(typescript@5.9.3) + + stylelint-scss@6.12.1(stylelint@16.25.0(typescript@5.9.3)): + dependencies: + css-tree: 3.1.0 + is-plain-object: 5.0.0 + known-css-properties: 0.36.0 + mdn-data: 2.25.0 + postcss-media-query-parser: 0.2.3 + postcss-resolve-nested-selector: 0.1.6 + postcss-selector-parser: 7.1.0 + postcss-value-parser: 4.2.0 + stylelint: 16.25.0(typescript@5.9.3) + + stylelint@16.25.0(typescript@5.9.3): + dependencies: + '@csstools/css-parser-algorithms': 3.0.5(@csstools/css-tokenizer@3.0.4) + '@csstools/css-tokenizer': 3.0.4 + '@csstools/media-query-list-parser': 4.0.3(@csstools/css-parser-algorithms@3.0.5(@csstools/css-tokenizer@3.0.4))(@csstools/css-tokenizer@3.0.4) + '@csstools/selector-specificity': 5.0.0(postcss-selector-parser@7.1.0) + '@dual-bundle/import-meta-resolve': 4.2.1 + balanced-match: 2.0.0 + colord: 2.9.3 + cosmiconfig: 9.0.0(typescript@5.9.3) + css-functions-list: 3.2.3 + css-tree: 3.1.0 + debug: 4.4.3 + fast-glob: 3.3.3 + fastest-levenshtein: 1.0.16 + file-entry-cache: 10.1.4 + global-modules: 2.0.0 + globby: 11.1.0 + globjoin: 0.1.4 + html-tags: 3.3.1 + ignore: 7.0.5 + imurmurhash: 0.1.4 + is-plain-object: 5.0.0 + known-css-properties: 0.37.0 + mathml-tag-names: 2.1.3 + meow: 13.2.0 + micromatch: 4.0.8 + normalize-path: 3.0.0 + picocolors: 1.1.1 + postcss: 8.5.6 + postcss-resolve-nested-selector: 0.1.6 + postcss-safe-parser: 7.0.1(postcss@8.5.6) + postcss-selector-parser: 7.1.0 + postcss-value-parser: 4.2.0 + resolve-from: 5.0.0 + string-width: 4.2.3 + supports-hyperlinks: 3.2.0 + svg-tags: 1.0.0 + table: 6.9.0 + write-file-atomic: 5.0.1 + transitivePeerDependencies: + - supports-color + - typescript + + superjson@2.2.5: + dependencies: + copy-anything: 4.0.5 + + supports-color@7.2.0: + dependencies: + has-flag: 4.0.0 + + supports-hyperlinks@3.2.0: + dependencies: + has-flag: 4.0.0 + supports-color: 7.2.0 + + svg-tags@1.0.0: {} + + synckit@0.11.13: + dependencies: + '@pkgr/core': 0.3.6 + + table@6.9.0: + dependencies: + ajv: 8.17.1 + lodash.truncate: 4.4.2 + slice-ansi: 4.0.0 + string-width: 4.2.3 + strip-ansi: 6.0.1 + + tapable@2.3.0: {} + + tinyexec@1.0.2: {} + + tinyexec@1.3.1: {} + + tinyglobby@0.2.15: + dependencies: + fdir: 6.5.0(picomatch@4.0.3) + picomatch: 4.0.3 + + tinyglobby@0.2.17: + dependencies: + fdir: 6.5.0(picomatch@4.0.7) + picomatch: 4.0.7 + + to-regex-range@5.0.1: + dependencies: + is-number: 7.0.0 + + to-valid-identifier@1.0.0: + dependencies: + '@sindresorhus/base62': 1.0.0 + reserved-identifiers: 1.2.0 + + toml-eslint-parser@1.0.3: + dependencies: + eslint-visitor-keys: 5.0.1 + + totalist@3.0.1: {} + + ts-api-utils@2.5.0(typescript@5.9.3): + dependencies: + typescript: 5.9.3 + + ts-declaration-location@1.0.7(typescript@5.9.3): + dependencies: + picomatch: 4.0.3 + typescript: 5.9.3 + + tslib@2.3.0: {} + + tslib@2.8.1: {} + + type-check@0.4.0: + dependencies: + prelude-ls: 1.2.1 + + typescript@5.9.3: {} + + ufo@1.6.1: {} + + undici-types@7.16.0: {} + + undici-types@8.9.0: {} + + unimport@5.5.0: + dependencies: + acorn: 8.15.0 + escape-string-regexp: 5.0.0 + estree-walker: 3.0.3 + local-pkg: 1.1.2 + magic-string: 0.30.21 + mlly: 1.8.0 + pathe: 2.0.3 + picomatch: 4.0.3 + pkg-types: 2.3.0 + scule: 1.3.0 + strip-literal: 3.1.0 + tinyglobby: 0.2.15 + unplugin: 2.3.10 + unplugin-utils: 0.3.1 + + unist-util-is@6.0.1: + dependencies: + '@types/unist': 3.0.3 + + unist-util-stringify-position@4.0.0: + dependencies: + '@types/unist': 3.0.3 + + unist-util-visit-parents@6.0.2: + dependencies: + '@types/unist': 3.0.3 + unist-util-is: 6.0.1 + + unist-util-visit@5.0.0: + dependencies: + '@types/unist': 3.0.3 + unist-util-is: 6.0.1 + unist-util-visit-parents: 6.0.2 + + universalify@2.0.1: {} + + unplugin-auto-import@20.2.0(@vueuse/core@13.9.0(vue@3.5.23(typescript@5.9.3))): + dependencies: + local-pkg: 1.1.2 + magic-string: 0.30.21 + picomatch: 4.0.3 + unimport: 5.5.0 + unplugin: 2.3.10 + unplugin-utils: 0.3.1 + optionalDependencies: + '@vueuse/core': 13.9.0(vue@3.5.23(typescript@5.9.3)) + + unplugin-icons@22.5.0(@vue/compiler-sfc@3.5.23): + dependencies: + '@antfu/install-pkg': 1.1.0 + '@iconify/utils': 3.1.7 + debug: 4.4.3 + local-pkg: 1.1.2 + unplugin: 2.3.10 + optionalDependencies: + '@vue/compiler-sfc': 3.5.23 + transitivePeerDependencies: + - supports-color + + unplugin-utils@0.3.1: + dependencies: + pathe: 2.0.3 + picomatch: 4.0.3 + + unplugin-vue-components@29.2.0(@babel/parser@7.28.5)(vue@3.5.23(typescript@5.9.3)): + dependencies: + chokidar: 3.6.0 + debug: 4.4.3 + local-pkg: 1.1.2 + magic-string: 0.30.21 + mlly: 1.8.0 + tinyglobby: 0.2.15 + unplugin: 2.3.10 + unplugin-utils: 0.3.1 + vue: 3.5.23(typescript@5.9.3) + optionalDependencies: + '@babel/parser': 7.28.5 + transitivePeerDependencies: + - supports-color + + unplugin@2.3.10: + dependencies: + '@jridgewell/remapping': 2.3.5 + acorn: 8.15.0 + picomatch: 4.0.3 + webpack-virtual-modules: 0.6.2 + + update-browserslist-db@1.1.4(browserslist@4.27.0): + dependencies: + browserslist: 4.27.0 + escalade: 3.2.0 + picocolors: 1.1.1 + + uri-js@4.4.1: + dependencies: + punycode: 2.3.1 + + util-deprecate@1.0.2: {} + + verkit@0.3.2: {} + + vite-dev-rpc@1.1.0(vite@7.2.1(@types/node@24.10.0)(less@4.4.2)(sass@1.93.3)(yaml@2.9.1)): + dependencies: + birpc: 2.7.0 + vite: 7.2.1(@types/node@24.10.0)(less@4.4.2)(sass@1.93.3)(yaml@2.9.1) + vite-hot-client: 2.1.0(vite@7.2.1(@types/node@24.10.0)(less@4.4.2)(sass@1.93.3)(yaml@2.9.1)) + + vite-hot-client@2.1.0(vite@7.2.1(@types/node@24.10.0)(less@4.4.2)(sass@1.93.3)(yaml@2.9.1)): + dependencies: + vite: 7.2.1(@types/node@24.10.0)(less@4.4.2)(sass@1.93.3)(yaml@2.9.1) + + vite-plugin-compression@0.5.1(vite@7.2.1(@types/node@24.10.0)(less@4.4.2)(sass@1.93.3)(yaml@2.9.1)): + dependencies: + chalk: 4.1.2 + debug: 4.4.3 + fs-extra: 10.1.0 + vite: 7.2.1(@types/node@24.10.0)(less@4.4.2)(sass@1.93.3)(yaml@2.9.1) + transitivePeerDependencies: + - supports-color + + vite-plugin-inspect@11.3.3(vite@7.2.1(@types/node@24.10.0)(less@4.4.2)(sass@1.93.3)(yaml@2.9.1)): + dependencies: + ansis: 4.2.0 + debug: 4.4.3 + error-stack-parser-es: 1.0.5 + ohash: 2.0.11 + open: 10.2.0 + perfect-debounce: 2.0.0 + sirv: 3.0.2 + unplugin-utils: 0.3.1 + vite: 7.2.1(@types/node@24.10.0)(less@4.4.2)(sass@1.93.3)(yaml@2.9.1) + vite-dev-rpc: 1.1.0(vite@7.2.1(@types/node@24.10.0)(less@4.4.2)(sass@1.93.3)(yaml@2.9.1)) + transitivePeerDependencies: + - supports-color + + vite-plugin-vue-devtools@8.0.5(vite@7.2.1(@types/node@24.10.0)(less@4.4.2)(sass@1.93.3)(yaml@2.9.1))(vue@3.5.23(typescript@5.9.3)): + dependencies: + '@vue/devtools-core': 8.0.5(vite@7.2.1(@types/node@24.10.0)(less@4.4.2)(sass@1.93.3)(yaml@2.9.1))(vue@3.5.23(typescript@5.9.3)) + '@vue/devtools-kit': 8.0.5 + '@vue/devtools-shared': 8.0.5 + sirv: 3.0.2 + vite: 7.2.1(@types/node@24.10.0)(less@4.4.2)(sass@1.93.3)(yaml@2.9.1) + vite-plugin-inspect: 11.3.3(vite@7.2.1(@types/node@24.10.0)(less@4.4.2)(sass@1.93.3)(yaml@2.9.1)) + vite-plugin-vue-inspector: 5.3.2(vite@7.2.1(@types/node@24.10.0)(less@4.4.2)(sass@1.93.3)(yaml@2.9.1)) + transitivePeerDependencies: + - '@nuxt/kit' + - supports-color + - vue + + vite-plugin-vue-inspector@5.3.2(vite@7.2.1(@types/node@24.10.0)(less@4.4.2)(sass@1.93.3)(yaml@2.9.1)): + dependencies: + '@babel/core': 7.28.5 + '@babel/plugin-proposal-decorators': 7.28.0(@babel/core@7.28.5) + '@babel/plugin-syntax-import-attributes': 7.27.1(@babel/core@7.28.5) + '@babel/plugin-syntax-import-meta': 7.10.4(@babel/core@7.28.5) + '@babel/plugin-transform-typescript': 7.28.5(@babel/core@7.28.5) + '@vue/babel-plugin-jsx': 1.5.0(@babel/core@7.28.5) + '@vue/compiler-dom': 3.5.23 + kolorist: 1.8.0 + magic-string: 0.30.21 + vite: 7.2.1(@types/node@24.10.0)(less@4.4.2)(sass@1.93.3)(yaml@2.9.1) + transitivePeerDependencies: + - supports-color + + vite@7.2.1(@types/node@24.10.0)(less@4.4.2)(sass@1.93.3)(yaml@2.9.1): + dependencies: + esbuild: 0.25.12 + fdir: 6.5.0(picomatch@4.0.3) + picomatch: 4.0.3 + postcss: 8.5.6 + rollup: 4.52.5 + tinyglobby: 0.2.15 + optionalDependencies: + '@types/node': 24.10.0 + fsevents: 2.3.3 + less: 4.4.2 + sass: 1.93.3 + yaml: 2.9.1 + + vscode-uri@3.1.0: {} + + vue-color-kit@1.0.6(vue@3.5.23(typescript@5.9.3)): + dependencies: + vue: 3.5.23(typescript@5.9.3) + + vue-draggable-plus@0.6.0(@types/sortablejs@1.15.9): + dependencies: + '@types/sortablejs': 1.15.9 + + vue-eslint-parser@10.4.1(eslint@9.39.1): + dependencies: + debug: 4.4.3 + eslint: 9.39.1 + eslint-scope: 8.4.0 + eslint-visitor-keys: 4.2.1 + espree: 10.4.0 + esquery: 1.6.0 + semver: 7.7.3 + transitivePeerDependencies: + - supports-color + + vue-router@4.6.3(vue@3.5.23(typescript@5.9.3)): + dependencies: + '@vue/devtools-api': 6.6.4 + vue: 3.5.23(typescript@5.9.3) + + vue-tsc@3.1.3(typescript@5.9.3): + dependencies: + '@volar/typescript': 2.4.23 + '@vue/language-core': 3.1.3(typescript@5.9.3) + typescript: 5.9.3 + + vue@3.5.23(typescript@5.9.3): + dependencies: + '@vue/compiler-dom': 3.5.23 + '@vue/compiler-sfc': 3.5.23 + '@vue/runtime-dom': 3.5.23 + '@vue/server-renderer': 3.5.23(vue@3.5.23(typescript@5.9.3)) + '@vue/shared': 3.5.23 + optionalDependencies: + typescript: 5.9.3 + + webpack-virtual-modules@0.6.2: {} + + which@1.3.1: + dependencies: + isexe: 2.0.0 + + which@2.0.2: + dependencies: + isexe: 2.0.0 + + word-wrap@1.2.5: {} + + write-file-atomic@5.0.1: + dependencies: + imurmurhash: 0.1.4 + signal-exit: 4.1.0 + + wsl-utils@0.1.0: + dependencies: + is-wsl: 3.1.0 + + xe-utils@3.7.9: {} + + xml-name-validator@5.0.0: {} + + yallist@3.1.1: {} + + yaml-eslint-parser@2.1.0: + dependencies: + eslint-visitor-keys: 5.0.1 + yaml: 2.8.1 + + yaml@2.8.1: {} + + yaml@2.9.1: {} + + yocto-queue@0.1.0: {} + + zrender@6.1.0: + dependencies: + tslib: 2.3.0 + + zwitch@2.0.4: {} diff --git a/frontend-pc/public/favicon.svg b/frontend-pc/public/favicon.svg new file mode 100644 index 0000000..b7fd708 --- /dev/null +++ b/frontend-pc/public/favicon.svg @@ -0,0 +1,19 @@ + + + + 填 + diff --git a/frontend-pc/public/static/css/loading.css b/frontend-pc/public/static/css/loading.css new file mode 100644 index 0000000..2cbc8f2 --- /dev/null +++ b/frontend-pc/public/static/css/loading.css @@ -0,0 +1,55 @@ +/** + * 首屏骨架样式。 + * + * 独立成文件(不经构建),因为它要在 JS 包下载、执行之前就生效 —— + * 放进 src/ 走打包的话,这段样式反而会晚于首屏出现。 + * + * 颜色用腾讯云主色 #0052D9(与 src/styles/arco-ui/xt-theme.less 一致); + * 这里不能写 var(--primary-6),因为此时 Arco 的样式表还没加载。 + */ + +body { + overflow: hidden; +} + +.init-box { + display: flex; + align-items: center; + justify-content: center; + width: 100vw; + height: 100vh; + background: #f3f3f3; +} + +.init-loading { + display: flex; + justify-content: space-between; + width: 40px; + height: 40px; +} + +.init-loading-rect { + width: 15%; + height: 100%; + background-color: #0052d9; + border-radius: 2px; + animation: init-wave 1.2s infinite ease-in-out; +} + +.init-loading-rect:nth-child(1) { animation-delay: -1.2s; } +.init-loading-rect:nth-child(2) { animation-delay: -1.1s; } +.init-loading-rect:nth-child(3) { animation-delay: -1s; } +.init-loading-rect:nth-child(4) { animation-delay: -0.9s; } +.init-loading-rect:nth-child(5) { animation-delay: -0.8s; } + +@keyframes init-wave { + 0%, + 40%, + 100% { + transform: scaleY(0.4); + } + + 20% { + transform: scaleY(1); + } +} diff --git a/frontend-pc/src/App.vue b/frontend-pc/src/App.vue new file mode 100644 index 0000000..3d51d7f --- /dev/null +++ b/frontend-pc/src/App.vue @@ -0,0 +1,32 @@ + + + + + diff --git a/frontend-pc/src/apis/area/index.ts b/frontend-pc/src/apis/area/index.ts new file mode 100644 index 0000000..f526bff --- /dev/null +++ b/frontend-pc/src/apis/area/index.ts @@ -0,0 +1,9 @@ +import type * as T from './type' +import http from '@/utils/http' + +export type * from './type' + +/** 获取地区列表 */ +export const getProvinceCityArea = (params: { type: 'province' | 'city' | 'area', code?: string }) => { + return http.get('/area/getProvinceCityArea', params) +} diff --git a/frontend-pc/src/apis/area/type.ts b/frontend-pc/src/apis/area/type.ts new file mode 100644 index 0000000..f129e36 --- /dev/null +++ b/frontend-pc/src/apis/area/type.ts @@ -0,0 +1,5 @@ +export interface AreaItem { + label: string + code: string + children?: AreaItem[] +} diff --git a/frontend-pc/src/apis/base.ts b/frontend-pc/src/apis/base.ts new file mode 100644 index 0000000..cfc05ec --- /dev/null +++ b/frontend-pc/src/apis/base.ts @@ -0,0 +1,39 @@ +import http from '@/utils/http' + +interface DefaultP { + GetListParams?: Record + GetDetailParams?: { id: string } + AddParams?: Record + UpdateParams?: Record + DeleteParams?: { ids: string[] } +} + +// 获取基础接口 +export function getBaseApi(params: { baseUrl: string }) { + const { baseUrl } = params + + const baseApi = { + // 获取分页列表 + getList(params?: P['GetListParams'] & { page?: number, size?: number }) { + return http.get>(`${baseUrl}/getList`, params) + }, + // 获取详情 + getDetail(params: P['GetDetailParams']) { + return http.get(`${baseUrl}/getDetail`, params) + }, + // 新增 + add(params: P['AddParams']) { + return http.post(`${baseUrl}/add`, params) + }, + // 修改 + update(params: P['UpdateParams']) { + return http.post(`${baseUrl}/update`, params) + }, + // 删除 + delete(params: P['DeleteParams']) { + return http.post(`${baseUrl}/delete`, params) + } + } + + return baseApi +} diff --git a/frontend-pc/src/apis/cate/index.ts b/frontend-pc/src/apis/cate/index.ts new file mode 100644 index 0000000..c1aa836 --- /dev/null +++ b/frontend-pc/src/apis/cate/index.ts @@ -0,0 +1,9 @@ +import type * as T from './type' +import http from '@/utils/http' + +export type * from './type' + +/** 获取分类树 */ +export function getCateTree() { + return http.get('/cate/getCateTree') +} diff --git a/frontend-pc/src/apis/cate/type.ts b/frontend-pc/src/apis/cate/type.ts new file mode 100644 index 0000000..8d97442 --- /dev/null +++ b/frontend-pc/src/apis/cate/type.ts @@ -0,0 +1,8 @@ +export interface CateTreeItem { + id: string + name: string + pid: string | null + type: string | null + disabled: boolean + children: CateTreeItem[] +} diff --git a/frontend-pc/src/apis/file/index.ts b/frontend-pc/src/apis/file/index.ts new file mode 100644 index 0000000..31923bb --- /dev/null +++ b/frontend-pc/src/apis/file/index.ts @@ -0,0 +1,9 @@ +import type * as T from './type' +import http from '@/utils/http' + +export type * from './type' + +/** 获取文件列表 */ +export function getFileList(params: { fileType: string | number }) { + return http.get>('/file/getFileList', params) +} diff --git a/frontend-pc/src/apis/file/type.ts b/frontend-pc/src/apis/file/type.ts new file mode 100644 index 0000000..fd58c7e --- /dev/null +++ b/frontend-pc/src/apis/file/type.ts @@ -0,0 +1,13 @@ +// 文件 +export interface FileItem { + id: string + type: string + name: string + extendName: string + src: string + updateTime: string + isDir: boolean + filePath: string + size: number + [propName: string]: any // 一个 interface 中任意属性只能有一个 +} diff --git a/frontend-pc/src/apis/person/index.ts b/frontend-pc/src/apis/person/index.ts new file mode 100644 index 0000000..8b6959d --- /dev/null +++ b/frontend-pc/src/apis/person/index.ts @@ -0,0 +1,7 @@ +import type * as T from './type' +import { getBaseApi } from '@/apis/base' + +export type * from './type' + +/** 人物模块 */ +export const baseAPI = getBaseApi({ baseUrl: '/person' }) diff --git a/frontend-pc/src/apis/person/type.ts b/frontend-pc/src/apis/person/type.ts new file mode 100644 index 0000000..bbfae6b --- /dev/null +++ b/frontend-pc/src/apis/person/type.ts @@ -0,0 +1,16 @@ +// 表格项目 +export interface ListItem { + id: string + name: string + account: string + avatar: string + gender: Gender + phone: string + email: string + createTime: string + address: string + age: number + status: Status + hobby: string[] + remark: string +} diff --git a/frontend-pc/src/apis/system/dept.ts b/frontend-pc/src/apis/system/dept.ts new file mode 100644 index 0000000..edba807 --- /dev/null +++ b/frontend-pc/src/apis/system/dept.ts @@ -0,0 +1,15 @@ +import { getBaseApi } from '@/apis/base' + +export interface ListItem { + id: string + name: string + sort: number + status: Status + createTime: string + parentId: string + children?: ListItem[] + description: string +} + +/** 部门模块 */ +export const baseAPI = getBaseApi({ baseUrl: '/system/dept' }) diff --git a/frontend-pc/src/apis/system/dict.ts b/frontend-pc/src/apis/system/dict.ts new file mode 100644 index 0000000..53fc692 --- /dev/null +++ b/frontend-pc/src/apis/system/dict.ts @@ -0,0 +1,44 @@ +import type { SelectOptionData } from '@arco-design/web-vue' +import { getBaseApi } from '@/apis/base' +import http from '@/utils/http' + +export interface ListItem { + id: string + createUserString: string + createTime: string + name: string + code: string + sort: number + status: Status + description: string +} + +export type DictDetail = ListItem + +export type DictDataItem = { + id: string + name: string + value: string | number + sort: number + status: Status + remark?: string + createTime?: string +} + +/** 字典模块 */ +export const baseAPI = getBaseApi({ baseUrl: '/system/dict' }) + +/** 字典数据列表 */ +export function getDictDataList(params: { code: string, name?: string, status?: string } & Pagination) { + return http.get>('/system/dict/getDictDataList', params) +} + +/** 字典数据详情 */ +export function getDictDataDetail(params: { id: string, code: string }) { + return http.get('/system/dict/getDictDataDetail', params) +} + +/** 获取字典数据映射 */ +export function getDictData() { + return http.get>('/system/dict/getDictData') +} diff --git a/frontend-pc/src/apis/system/menu.ts b/frontend-pc/src/apis/system/menu.ts new file mode 100644 index 0000000..689e3f6 --- /dev/null +++ b/frontend-pc/src/apis/system/menu.ts @@ -0,0 +1,39 @@ +import { getBaseApi } from '@/apis/base' +import http from '@/utils/http' + +export interface ListItem { + activeMenu: string + alwaysShow: boolean + breadcrumb: boolean + children: ListItem[] + component: string + hidden: boolean + icon: string + id: string + keepAlive: boolean + parentId: string + path: string + permission: string + redirect: string + roles: string[] + showInTabs: boolean + sort: number + status: Status + title: string + type: 1 | 2 | 3 + affix: boolean +} + +export interface MenuOptionsItem { + id: string + title: string + children: MenuOptionsItem[] +} + +/** 菜单模块 */ +export const baseAPI = getBaseApi({ baseUrl: '/system/menu' }) + +/** 获取角色分配权限菜单树 */ +export function getMenuOptions() { + return http.get('/system/menu/getMenuOptions') +} diff --git a/frontend-pc/src/apis/system/role.ts b/frontend-pc/src/apis/system/role.ts new file mode 100644 index 0000000..730d497 --- /dev/null +++ b/frontend-pc/src/apis/system/role.ts @@ -0,0 +1,23 @@ +import { getBaseApi } from '@/apis/base' +import http from '@/utils/http' + +export interface ListItem { + id: string + createUserString: string + createTime: string + disabled: boolean + name: string + code: string + sort: number + status: Status + type: number + description: string +} + +/** 角色模块 */ +export const baseAPI = getBaseApi({ baseUrl: '/system/role' }) + +/** 获取角色权限 */ +export function getRoleMenuIds(params: { role: string }) { + return http.get('/system/role/getRoleMenuIds', params) +} diff --git a/frontend-pc/src/apis/system/user.ts b/frontend-pc/src/apis/system/user.ts new file mode 100644 index 0000000..1ec6607 --- /dev/null +++ b/frontend-pc/src/apis/system/user.ts @@ -0,0 +1,24 @@ +import { getBaseApi } from '@/apis/base' + +export interface ListItem { + id: string + createUserString: string + createTime: string + disabled: boolean + deptId: string + deptName: string + username: string + nickname: string + gender: Gender + avatar: string + email: string + phone: string + status: Status + type: 1 | 2 + description: string + roleIds: string[] + roleNames: string[] +} + +/** 用户模块 */ +export const baseAPI = getBaseApi({ baseUrl: '/system/user' }) diff --git a/frontend-pc/src/apis/test/index.ts b/frontend-pc/src/apis/test/index.ts new file mode 100644 index 0000000..48b3a94 --- /dev/null +++ b/frontend-pc/src/apis/test/index.ts @@ -0,0 +1,11 @@ +import http from '@/utils/http' + +/** 请求成功的接口 */ +export function getTestSuccess() { + return http.get('/test/success') +} + +/** 请求失败的接口 */ +export function getTestFail() { + return http.get('/test/fail') +} diff --git a/frontend-pc/src/apis/user/index.ts b/frontend-pc/src/apis/user/index.ts new file mode 100644 index 0000000..ae6bd68 --- /dev/null +++ b/frontend-pc/src/apis/user/index.ts @@ -0,0 +1,24 @@ +import type * as T from './type' +import http from '@/utils/http' + +export type * from './type' + +/** 登录 */ +export function login(data: { username: string, password: string }) { + return http.post('/user/login', data) +} + +/** 退出登录 */ +export function logout() { + return http.post('/user/logout') +} + +/** 获取用户信息 */ +export const getUserInfo = () => { + return http.get('/user/getUserInfo') +} + +/** 获取用户路由信息 */ +export const getUserRoutes = () => { + return http.get('/user/getUserRoutes') +} diff --git a/frontend-pc/src/apis/user/type.ts b/frontend-pc/src/apis/user/type.ts new file mode 100644 index 0000000..387384a --- /dev/null +++ b/frontend-pc/src/apis/user/type.ts @@ -0,0 +1,34 @@ +export interface UserInfo { + id: string + nickname: string + avatar: string + roles: string[] + permissions: string[] +} + +export interface Login { + token: string +} + +export interface UserRouteItem { + activeMenu: string + alwaysShow: boolean + breadcrumb: boolean + children: UserRouteItem[] + component: string + hidden: boolean + icon: string + id: string + keepAlive: boolean + parentId: string + path: string + permission: string + redirect: string + roles: string[] + showInTabs: boolean + sort: number + status: Status + title: string + type: 1 | 2 | 3 + affix: boolean +} diff --git a/frontend-pc/src/apis/xt/index.ts b/frontend-pc/src/apis/xt/index.ts new file mode 100644 index 0000000..cefd9cc --- /dev/null +++ b/frontend-pc/src/apis/xt/index.ts @@ -0,0 +1,81 @@ +/** + * 本项目业务接口的公共出口与类型。 + * + * 与模板的 `src/apis/*` 分开,是因为两边契约不同: + * - 模板:`/user/login`、`{ success, message }`、`{ records, total }` + * - 本项目:`/auth/login`(《03-接口设计》§5)、`{ code, msg, data }`、`{ list, total }` + * 形状适配统一在 `@/utils/http` 里做,这里只负责路径与类型。 + */ +import http from '@/utils/http' + +export { http } + +/** 登录接口(§5.1) */ +export interface LoginReq { + username: string + /** RSA 公钥加密后的 Base64,不是明文 */ + password: string + /** 账号在多个租户下重名时必填 */ + tenantCode?: string + captchaKey?: string + captchaCode?: string +} + +export interface XtUserInfo { + id: number + username: string + realName: string + userType: number + avatarUrl: string | null + orgId: number | null +} + +export interface XtTenantInfo { + id: number + tenantCode: string + tenantName: string + logoUrl: string | null +} + +/** 登录 / 当前用户响应(§5.1 / §5.2) */ +export interface LoginVO { + token: string | null + expireSeconds: number + user: XtUserInfo + tenant: XtTenantInfo | null + roleCodes: string[] + /** 菜单键(决定左侧导航) */ + menus: string[] + /** 细粒度权限码(决定按钮显隐) */ + permissions: string[] +} + +/** 图形验证码 */ +export interface CaptchaVO { + captchaKey: string + /** data:image/png;base64,... */ + image: string + expireSeconds: number +} + +// ── 认证(§5.1 / §5.2 / §5.9)────────────────────────── + +/** 登录(密码需先经 {@link encryptPassword} 加密) */ +export function login(data: LoginReq) { + return http.post('/auth/login', data) +} + +/** 当前用户 + 菜单 + 权限码 */ +export function getMe() { + return http.get('/auth/me') +} + +/** 登出(服务端会把 token 加入黑名单) */ +export function logout() { + return http.post('/auth/logout') +} + +/** 图形验证码(连续失败 2 次后服务端会要求) */ +export function getCaptcha() { + return http.get('/auth/captcha') +} diff --git a/frontend-pc/src/apis/xt/org.ts b/frontend-pc/src/apis/xt/org.ts new file mode 100644 index 0000000..accbdc5 --- /dev/null +++ b/frontend-pc/src/apis/xt/org.ts @@ -0,0 +1,63 @@ +/** + * 组织架构接口(《03-接口设计》§6.1~§6.3)。 + * + * 契约差异(已在 `@/utils/http` 统一适配):后端 `{ code, msg, data }`, + * 分页 `{ list, total }`(适配层会补一份 `records`)。 + */ +import http from '@/utils/http' + +/** 组织树节点(§6.1) */ +export interface OrgTreeVO { + id: number + orgName: string + /** 1 学校 / 2 院系或专业 / 3 班级 / 4 报名点 */ + orgType: number + orgCode: string | null + leaderUserId: number | null + leaderName: string | null + sortNo: number + status: number + studentCount: number | null + children: OrgTreeVO[] +} + +/** 新增 / 修改请求(§6.2) */ +export interface OrgUpsertReq { + parentId?: number | null + orgName: string + orgType: number + orgCode: string + leaderUserId?: number | null + sortNo?: number +} + +/** + * 组织树。 + * @param withCount 是否附带每节点学生数(会多一次聚合查询,列表页按需开) + */ +export function getOrgTree(params: { withCount?: boolean, rootId?: number } = {}) { + return http.get('/orgs/tree', params) +} + +/** 新增,返回新节点 id */ +export function addOrg(data: OrgUpsertReq) { + return http.post('/orgs', data) +} + +/** 修改;变更层级时服务端会自动重算整棵子树的 ancestor_path */ +export function updateOrg(id: number, data: OrgUpsertReq) { + return http.put(`/orgs/${id}`, data) +} + +/** 删除;有下级组织或账号时返回 40015 并给出占用清单 */ +export function deleteOrg(id: number) { + return http.delete(`/orgs/${id}`) +} + +/** 组织类型 → 中文名(与 DDL 的 org_type 对齐) */ +export const ORG_TYPE_NAMES: Record = { + 1: '学校', + 2: '院系 / 专业', + 3: '班级', + 4: '报名点' +} diff --git a/frontend-pc/src/apis/xt/tenant.ts b/frontend-pc/src/apis/xt/tenant.ts new file mode 100644 index 0000000..e9ee085 --- /dev/null +++ b/frontend-pc/src/apis/xt/tenant.ts @@ -0,0 +1,83 @@ +/** + * 租户管理接口(《03-接口设计》§6.5~§6.6)。 + * + * ⚠️ 全部接口**仅平台运营可用**。学校管理员调用会得到 40301, + * 前端据此隐藏入口(见 `@/config/xt-routes` 的菜单过滤)。 + */ +import http from '@/utils/http' + +/** 租户(§6.5) */ +export interface TenantVO { + id: number + tenantCode: string + tenantName: string + tenantType: number + contactName: string | null + contactPhone: string | null + packageId: number | null + expireTime: string | null + status: number + logoUrl: string | null + createTime: string | null +} + +/** 开通租户请求(§6.6) */ +export interface TenantCreateReq { + tenantName: string + tenantCode: string + tenantType: number + contactName: string + contactPhone: string + packageId?: number + expireTime: string + adminUsername: string + adminRealName: string + adminPhone: string +} + +/** 开通结果:初始密码**只返回这一次** */ +export interface TenantCreateResult { + id: number + adminInitialPassword: string + miniProgramScene: string +} + +/** 分页查询参数(后端用 pageNum/pageSize,模板的 useTable 用 page/size) */ +export interface TenantQuery { + keyword?: string + status?: number + expireBefore?: string + pageNum?: number + pageSize?: number +} + +export function getTenantList(params: TenantQuery = {}) { + return http.get<{ list: TenantVO[], total: number }>('/tenants', params) +} + +export function getTenantDetail(id: number) { + return http.get(`/tenants/${id}`) +} + +/** + * 开通租户:一次请求完成「建租户 + 发放 6 个内置角色 + 创建首个管理员」。 + * 返回的 `adminInitialPassword` 只在这一次响应里,界面必须让用户当场记下。 + */ +export function createTenant(data: TenantCreateReq) { + return http.post('/tenants', data) +} + +/** 租户类型 → 中文名 */ +export const TENANT_TYPE_NAMES: Record = { + 1: '学校', + 2: '认定机构', + 3: '报名点', + 4: '企业' +} + +/** 租户状态 → 文案与颜色 */ +export const TENANT_STATUS: Record = { + 0: { text: '已停用', color: 'gray' }, + 1: { text: '正常', color: 'green' }, + 2: { text: '试用', color: 'orange' } +} diff --git a/frontend-pc/src/assets/images/login-hero.svg b/frontend-pc/src/assets/images/login-hero.svg new file mode 100644 index 0000000..d383a3b --- /dev/null +++ b/frontend-pc/src/assets/images/login-hero.svg @@ -0,0 +1,77 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/frontend-pc/src/assets/images/logo.svg b/frontend-pc/src/assets/images/logo.svg new file mode 100644 index 0000000..9f239d6 --- /dev/null +++ b/frontend-pc/src/assets/images/logo.svg @@ -0,0 +1,45 @@ + + + + + + + + + + + + + + + + + + + + + + + + 填 + diff --git a/frontend-pc/src/auto-import.d.ts b/frontend-pc/src/auto-import.d.ts new file mode 100644 index 0000000..72370df --- /dev/null +++ b/frontend-pc/src/auto-import.d.ts @@ -0,0 +1,78 @@ +/* eslint-disable */ +/* prettier-ignore */ +// @ts-nocheck +// noinspection JSUnusedGlobalSymbols +// Generated by unplugin-auto-import +// biome-ignore lint: disable +export {} +declare global { + const EffectScope: typeof import('vue').EffectScope + const computed: typeof import('vue').computed + const createApp: typeof import('vue').createApp + const customRef: typeof import('vue').customRef + const defineAsyncComponent: typeof import('vue').defineAsyncComponent + const defineComponent: typeof import('vue').defineComponent + const effectScope: typeof import('vue').effectScope + const getCurrentInstance: typeof import('vue').getCurrentInstance + const getCurrentScope: typeof import('vue').getCurrentScope + const getCurrentWatcher: typeof import('vue').getCurrentWatcher + const h: typeof import('vue').h + const inject: typeof import('vue').inject + const isProxy: typeof import('vue').isProxy + const isReactive: typeof import('vue').isReactive + const isReadonly: typeof import('vue').isReadonly + const isRef: typeof import('vue').isRef + const isShallow: typeof import('vue').isShallow + const markRaw: typeof import('vue').markRaw + const nextTick: typeof import('vue').nextTick + const onActivated: typeof import('vue').onActivated + const onBeforeMount: typeof import('vue').onBeforeMount + const onBeforeRouteLeave: typeof import('vue-router').onBeforeRouteLeave + const onBeforeRouteUpdate: typeof import('vue-router').onBeforeRouteUpdate + const onBeforeUnmount: typeof import('vue').onBeforeUnmount + const onBeforeUpdate: typeof import('vue').onBeforeUpdate + const onDeactivated: typeof import('vue').onDeactivated + const onErrorCaptured: typeof import('vue').onErrorCaptured + const onMounted: typeof import('vue').onMounted + const onRenderTracked: typeof import('vue').onRenderTracked + const onRenderTriggered: typeof import('vue').onRenderTriggered + const onScopeDispose: typeof import('vue').onScopeDispose + const onServerPrefetch: typeof import('vue').onServerPrefetch + const onUnmounted: typeof import('vue').onUnmounted + const onUpdated: typeof import('vue').onUpdated + const onWatcherCleanup: typeof import('vue').onWatcherCleanup + const provide: typeof import('vue').provide + const reactive: typeof import('vue').reactive + const readonly: typeof import('vue').readonly + const ref: typeof import('vue').ref + const resolveComponent: typeof import('vue').resolveComponent + const shallowReactive: typeof import('vue').shallowReactive + const shallowReadonly: typeof import('vue').shallowReadonly + const shallowRef: typeof import('vue').shallowRef + const toRaw: typeof import('vue').toRaw + const toRef: typeof import('vue').toRef + const toRefs: typeof import('vue').toRefs + const toValue: typeof import('vue').toValue + const triggerRef: typeof import('vue').triggerRef + const unref: typeof import('vue').unref + const useAttrs: typeof import('vue').useAttrs + const useCssModule: typeof import('vue').useCssModule + const useCssVars: typeof import('vue').useCssVars + const useId: typeof import('vue').useId + const useLink: typeof import('vue-router').useLink + const useModel: typeof import('vue').useModel + const useRoute: typeof import('vue-router').useRoute + const useRouter: typeof import('vue-router').useRouter + const useSlots: typeof import('vue').useSlots + const useTemplateRef: typeof import('vue').useTemplateRef + const watch: typeof import('vue').watch + const watchEffect: typeof import('vue').watchEffect + const watchPostEffect: typeof import('vue').watchPostEffect + const watchSyncEffect: typeof import('vue').watchSyncEffect +} +// for type re-export +declare global { + // @ts-ignore + export type { Component, Slot, Slots, ComponentPublicInstance, ComputedRef, DirectiveBinding, ExtractDefaultPropTypes, ExtractPropTypes, ExtractPublicPropTypes, InjectionKey, PropType, Ref, ShallowRef, MaybeRef, MaybeRefOrGetter, VNode, WritableComputedRef } from 'vue' + import('vue') +} diff --git a/frontend-pc/src/components.d.ts b/frontend-pc/src/components.d.ts new file mode 100644 index 0000000..029eb77 --- /dev/null +++ b/frontend-pc/src/components.d.ts @@ -0,0 +1,160 @@ +/* eslint-disable */ +// @ts-nocheck +// biome-ignore lint: disable +// oxlint-disable +// ------ +// Generated by unplugin-vue-components +// Read more: https://github.com/vuejs/core/pull/3399 +import { GlobalComponents } from 'vue' + +export {} + +/* prettier-ignore */ +declare module 'vue' { + export interface GlobalComponents { + AAlert: typeof import('@arco-design/web-vue')['Alert'] + AAvatar: typeof import('@arco-design/web-vue')['Avatar'] + ABadge: typeof import('@arco-design/web-vue')['Badge'] + ABreadcrumb: typeof import('@arco-design/web-vue')['Breadcrumb'] + ABreadcrumbItem: typeof import('@arco-design/web-vue')['BreadcrumbItem'] + AButton: typeof import('@arco-design/web-vue')['Button'] + ACard: typeof import('@arco-design/web-vue')['Card'] + ACol: typeof import('@arco-design/web-vue')['Col'] + AComment: typeof import('@arco-design/web-vue')['Comment'] + AConfigProvider: typeof import('@arco-design/web-vue')['ConfigProvider'] + ADatePicker: typeof import('@arco-design/web-vue')['DatePicker'] + ADescriptions: typeof import('@arco-design/web-vue')['Descriptions'] + ADescriptionsItem: typeof import('@arco-design/web-vue')['DescriptionsItem'] + ADivider: typeof import('@arco-design/web-vue')['Divider'] + ADoption: typeof import('@arco-design/web-vue')['Doption'] + ADrawer: typeof import('@arco-design/web-vue')['Drawer'] + ADropdown: typeof import('@arco-design/web-vue')['Dropdown'] + AEmpty: typeof import('@arco-design/web-vue')['Empty'] + AForm: typeof import('@arco-design/web-vue')['Form'] + AFormItem: typeof import('@arco-design/web-vue')['FormItem'] + AInput: typeof import('@arco-design/web-vue')['Input'] + AInputNumber: typeof import('@arco-design/web-vue')['InputNumber'] + AInputPassword: typeof import('@arco-design/web-vue')['InputPassword'] + AInputSearch: typeof import('@arco-design/web-vue')['InputSearch'] + ALayout: typeof import('@arco-design/web-vue')['Layout'] + ALayoutHeader: typeof import('@arco-design/web-vue')['LayoutHeader'] + ALayoutSider: typeof import('@arco-design/web-vue')['LayoutSider'] + ALink: typeof import('@arco-design/web-vue')['Link'] + AMenu: typeof import('@arco-design/web-vue')['Menu'] + AMenuItem: typeof import('@arco-design/web-vue')['MenuItem'] + AModal: typeof import('@arco-design/web-vue')['Modal'] + AOption: typeof import('@arco-design/web-vue')['Option'] + APopconfirm: typeof import('@arco-design/web-vue')['Popconfirm'] + APopover: typeof import('@arco-design/web-vue')['Popover'] + ARow: typeof import('@arco-design/web-vue')['Row'] + AScrollbar: typeof import('@arco-design/web-vue')['Scrollbar'] + ASelect: typeof import('@arco-design/web-vue')['Select'] + ASpace: typeof import('@arco-design/web-vue')['Space'] + ASpin: typeof import('@arco-design/web-vue')['Spin'] + ASplit: typeof import('@arco-design/web-vue')['Split'] + ASubMenu: typeof import('@arco-design/web-vue')['SubMenu'] + ASwitch: typeof import('@arco-design/web-vue')['Switch'] + ATable: typeof import('@arco-design/web-vue')['Table'] + ATableColumn: typeof import('@arco-design/web-vue')['TableColumn'] + ATabPane: typeof import('@arco-design/web-vue')['TabPane'] + ATabs: typeof import('@arco-design/web-vue')['Tabs'] + ATag: typeof import('@arco-design/web-vue')['Tag'] + ATooltip: typeof import('@arco-design/web-vue')['Tooltip'] + ATree: typeof import('@arco-design/web-vue')['Tree'] + GiButton: typeof import('./components/GiButton/GiButton.vue')['default'] + GiCellAvatar: typeof import('./components/GiCell/GiCellAvatar.vue')['default'] + GiCellGender: typeof import('./components/GiCell/GiCellGender.vue')['default'] + GiCellStatus: typeof import('./components/GiCell/GiCellStatus.vue')['default'] + GiCellTags: typeof import('./components/GiCell/GiCellTags.vue')['default'] + GiDot: typeof import('./components/GiDot/GiDot.tsx')['default'] + GiEditTable: typeof import('./components/GiEditTable/GiEditTable.vue')['default'] + GiFlex: typeof import('./components/GiFlex/GiFlex.vue')['default'] + GiForm: typeof import('./components/GiForm/GiForm.vue')['default'] + GiIconBox: typeof import('./components/GiIconBox/GiIconBox.vue')['default'] + GiInputSearch: typeof import('./components/GiInputSearch/GiInputSearch.vue')['default'] + GiOption: typeof import('./components/GiOption/GiOption.vue')['default'] + GiOptionItem: typeof import('./components/GiOption/GiOptionItem.vue')['default'] + GiPageLayout: typeof import('./components/GiPageLayout/GiPageLayout.vue')['default'] + GiTable: typeof import('./components/GiTable/GiTable.vue')['default'] + GiTableSetting: typeof import('./components/GiTableSetting/GiTableSetting.vue')['default'] + GiTag: typeof import('./components/GiTag/GiTag.tsx')['default'] + GiThemeBtn: typeof import('./components/GiThemeBtn/GiThemeBtn.vue')['default'] + RouterLink: typeof import('vue-router')['RouterLink'] + RouterView: typeof import('vue-router')['RouterView'] + SplitButton: typeof import('./components/GiPageLayout/SplitButton.vue')['default'] + } +} + +// For TSX support +declare global { + const AAlert: typeof import('@arco-design/web-vue')['Alert'] + const AAvatar: typeof import('@arco-design/web-vue')['Avatar'] + const ABadge: typeof import('@arco-design/web-vue')['Badge'] + const ABreadcrumb: typeof import('@arco-design/web-vue')['Breadcrumb'] + const ABreadcrumbItem: typeof import('@arco-design/web-vue')['BreadcrumbItem'] + const AButton: typeof import('@arco-design/web-vue')['Button'] + const ACard: typeof import('@arco-design/web-vue')['Card'] + const ACol: typeof import('@arco-design/web-vue')['Col'] + const AComment: typeof import('@arco-design/web-vue')['Comment'] + const AConfigProvider: typeof import('@arco-design/web-vue')['ConfigProvider'] + const ADatePicker: typeof import('@arco-design/web-vue')['DatePicker'] + const ADescriptions: typeof import('@arco-design/web-vue')['Descriptions'] + const ADescriptionsItem: typeof import('@arco-design/web-vue')['DescriptionsItem'] + const ADivider: typeof import('@arco-design/web-vue')['Divider'] + const ADoption: typeof import('@arco-design/web-vue')['Doption'] + const ADrawer: typeof import('@arco-design/web-vue')['Drawer'] + const ADropdown: typeof import('@arco-design/web-vue')['Dropdown'] + const AEmpty: typeof import('@arco-design/web-vue')['Empty'] + const AForm: typeof import('@arco-design/web-vue')['Form'] + const AFormItem: typeof import('@arco-design/web-vue')['FormItem'] + const AInput: typeof import('@arco-design/web-vue')['Input'] + const AInputNumber: typeof import('@arco-design/web-vue')['InputNumber'] + const AInputPassword: typeof import('@arco-design/web-vue')['InputPassword'] + const AInputSearch: typeof import('@arco-design/web-vue')['InputSearch'] + const ALayout: typeof import('@arco-design/web-vue')['Layout'] + const ALayoutHeader: typeof import('@arco-design/web-vue')['LayoutHeader'] + const ALayoutSider: typeof import('@arco-design/web-vue')['LayoutSider'] + const ALink: typeof import('@arco-design/web-vue')['Link'] + const AMenu: typeof import('@arco-design/web-vue')['Menu'] + const AMenuItem: typeof import('@arco-design/web-vue')['MenuItem'] + const AModal: typeof import('@arco-design/web-vue')['Modal'] + const AOption: typeof import('@arco-design/web-vue')['Option'] + const APopconfirm: typeof import('@arco-design/web-vue')['Popconfirm'] + const APopover: typeof import('@arco-design/web-vue')['Popover'] + const ARow: typeof import('@arco-design/web-vue')['Row'] + const AScrollbar: typeof import('@arco-design/web-vue')['Scrollbar'] + const ASelect: typeof import('@arco-design/web-vue')['Select'] + const ASpace: typeof import('@arco-design/web-vue')['Space'] + const ASpin: typeof import('@arco-design/web-vue')['Spin'] + const ASplit: typeof import('@arco-design/web-vue')['Split'] + const ASubMenu: typeof import('@arco-design/web-vue')['SubMenu'] + const ASwitch: typeof import('@arco-design/web-vue')['Switch'] + const ATable: typeof import('@arco-design/web-vue')['Table'] + const ATableColumn: typeof import('@arco-design/web-vue')['TableColumn'] + const ATabPane: typeof import('@arco-design/web-vue')['TabPane'] + const ATabs: typeof import('@arco-design/web-vue')['Tabs'] + const ATag: typeof import('@arco-design/web-vue')['Tag'] + const ATooltip: typeof import('@arco-design/web-vue')['Tooltip'] + const ATree: typeof import('@arco-design/web-vue')['Tree'] + const GiButton: typeof import('./components/GiButton/GiButton.vue')['default'] + const GiCellAvatar: typeof import('./components/GiCell/GiCellAvatar.vue')['default'] + const GiCellGender: typeof import('./components/GiCell/GiCellGender.vue')['default'] + const GiCellStatus: typeof import('./components/GiCell/GiCellStatus.vue')['default'] + const GiCellTags: typeof import('./components/GiCell/GiCellTags.vue')['default'] + const GiDot: typeof import('./components/GiDot/GiDot.tsx')['default'] + const GiEditTable: typeof import('./components/GiEditTable/GiEditTable.vue')['default'] + const GiFlex: typeof import('./components/GiFlex/GiFlex.vue')['default'] + const GiForm: typeof import('./components/GiForm/GiForm.vue')['default'] + const GiIconBox: typeof import('./components/GiIconBox/GiIconBox.vue')['default'] + const GiInputSearch: typeof import('./components/GiInputSearch/GiInputSearch.vue')['default'] + const GiOption: typeof import('./components/GiOption/GiOption.vue')['default'] + const GiOptionItem: typeof import('./components/GiOption/GiOptionItem.vue')['default'] + const GiPageLayout: typeof import('./components/GiPageLayout/GiPageLayout.vue')['default'] + const GiTable: typeof import('./components/GiTable/GiTable.vue')['default'] + const GiTableSetting: typeof import('./components/GiTableSetting/GiTableSetting.vue')['default'] + const GiTag: typeof import('./components/GiTag/GiTag.tsx')['default'] + const GiThemeBtn: typeof import('./components/GiThemeBtn/GiThemeBtn.vue')['default'] + const RouterLink: typeof import('vue-router')['RouterLink'] + const RouterView: typeof import('vue-router')['RouterView'] + const SplitButton: typeof import('./components/GiPageLayout/SplitButton.vue')['default'] +} \ No newline at end of file diff --git a/frontend-pc/src/components/GiButton/GiButton.vue b/frontend-pc/src/components/GiButton/GiButton.vue new file mode 100644 index 0000000..637f1f7 --- /dev/null +++ b/frontend-pc/src/components/GiButton/GiButton.vue @@ -0,0 +1,86 @@ + + + + + diff --git a/frontend-pc/src/components/GiButton/index.ts b/frontend-pc/src/components/GiButton/index.ts new file mode 100644 index 0000000..9750fe6 --- /dev/null +++ b/frontend-pc/src/components/GiButton/index.ts @@ -0,0 +1,5 @@ +import Button from './GiButton.vue' + +export type ButtonInstance = InstanceType +export * from './type' +export { Button as GiButton } diff --git a/frontend-pc/src/components/GiButton/type.ts b/frontend-pc/src/components/GiButton/type.ts new file mode 100644 index 0000000..7c6bf35 --- /dev/null +++ b/frontend-pc/src/components/GiButton/type.ts @@ -0,0 +1,5 @@ +import type { ButtonProps as ArcoButtonProps } from '@arco-design/web-vue' + +export interface ButtonProps extends Omit { + type?: 'add' | 'edit' | 'delete' | 'search' | 'reset' | 'upload' | 'import' | 'export' | '' +} diff --git a/frontend-pc/src/components/GiCell/GiCellAvatar.vue b/frontend-pc/src/components/GiCell/GiCellAvatar.vue new file mode 100644 index 0000000..5bf95dd --- /dev/null +++ b/frontend-pc/src/components/GiCell/GiCellAvatar.vue @@ -0,0 +1,31 @@ + + + + + diff --git a/frontend-pc/src/components/GiCell/GiCellGender.vue b/frontend-pc/src/components/GiCell/GiCellGender.vue new file mode 100644 index 0000000..ee22cbf --- /dev/null +++ b/frontend-pc/src/components/GiCell/GiCellGender.vue @@ -0,0 +1,28 @@ + + + + + diff --git a/frontend-pc/src/components/GiCell/GiCellStatus.vue b/frontend-pc/src/components/GiCell/GiCellStatus.vue new file mode 100644 index 0000000..918885e --- /dev/null +++ b/frontend-pc/src/components/GiCell/GiCellStatus.vue @@ -0,0 +1,24 @@ + + + + + diff --git a/frontend-pc/src/components/GiCell/GiCellTags.vue b/frontend-pc/src/components/GiCell/GiCellTags.vue new file mode 100644 index 0000000..7456aab --- /dev/null +++ b/frontend-pc/src/components/GiCell/GiCellTags.vue @@ -0,0 +1,33 @@ + + + + + diff --git a/frontend-pc/src/components/GiCell/index.ts b/frontend-pc/src/components/GiCell/index.ts new file mode 100644 index 0000000..d39267f --- /dev/null +++ b/frontend-pc/src/components/GiCell/index.ts @@ -0,0 +1,6 @@ +import GiCellAvatar from './GiCellAvatar.vue' +import GiCellGender from './GiCellGender.vue' +import GiCellStatus from './GiCellStatus.vue' +import GiCellTags from './GiCellTags.vue' + +export { GiCellAvatar, GiCellGender, GiCellStatus, GiCellTags } diff --git a/frontend-pc/src/components/GiDot/GiDot.tsx b/frontend-pc/src/components/GiDot/GiDot.tsx new file mode 100644 index 0000000..83c6119 --- /dev/null +++ b/frontend-pc/src/components/GiDot/GiDot.tsx @@ -0,0 +1,33 @@ +/** + * @file GiDot 组件 - 用于显示带有可选动画效果的状态点 + */ +import type { PropType } from 'vue' +import type { DotType, DotProps as Props } from './type' +import { defineComponent } from 'vue' +import './dot.scss' + +export default defineComponent({ + name: 'GiDot', + props: { + animation: { + type: Boolean as PropType, + default: true, + required: false + }, + type: { + type: String as PropType, + default: 'primary', + required: false, + validator: (value: string): boolean => { + return ['primary', 'success', 'warning', 'danger', 'info'].includes(value) + } + } + }, + setup(props: Props) { + return () => ( + + ) + } +}) diff --git a/frontend-pc/src/components/GiDot/dot.scss b/frontend-pc/src/components/GiDot/dot.scss new file mode 100644 index 0000000..a762721 --- /dev/null +++ b/frontend-pc/src/components/GiDot/dot.scss @@ -0,0 +1,58 @@ +.gi-dot { + display: inline-block; + width: 8px; + height: 8px; + vertical-align: middle; + background: #000; + border-radius: 50%; +} + +$types: primary, success, warning, danger, info; + +@each $i in $types { + .gi-dot-#{$i} + + { + background-color: rgb(var(--#{$i}-6)); + } + + @if ($i ==info) { + .gi-dot-#{$i} + + { + background-color: rgb(var(--gray-6)); + } + } +} + +.gi-dot-processing { + position: relative; +} + +.gi-dot-processing::after { + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; + content: ''; + background: inherit; + border-radius: 50%; + animation: gi-dot-animated 1.2s ease-in-out infinite; +} + +@keyframes gi-dot-animated { + 0% { + opacity: 1; + transform: scale(0.5); + } + + 30% { + opacity: 0.7; + } + + 100% { + opacity: 0; + transform: scale(2.5); + } +} \ No newline at end of file diff --git a/frontend-pc/src/components/GiDot/index.ts b/frontend-pc/src/components/GiDot/index.ts new file mode 100644 index 0000000..0f39877 --- /dev/null +++ b/frontend-pc/src/components/GiDot/index.ts @@ -0,0 +1,5 @@ +import Dot from './GiDot.tsx' + +export type * from './type' +export type DotInstance = InstanceType +export { Dot as GiDot } diff --git a/frontend-pc/src/components/GiDot/type.ts b/frontend-pc/src/components/GiDot/type.ts new file mode 100644 index 0000000..c066c69 --- /dev/null +++ b/frontend-pc/src/components/GiDot/type.ts @@ -0,0 +1,10 @@ +/** 点的类型 */ +export type DotType = 'primary' | 'success' | 'warning' | 'danger' | 'info' + +/** 组件属性定义 */ +export interface DotProps { + /** 点的类型 */ + type?: DotType + /** 是否启用动画效果 */ + animation?: boolean +} diff --git a/frontend-pc/src/components/GiEditTable/GiEditTable.vue b/frontend-pc/src/components/GiEditTable/GiEditTable.vue new file mode 100644 index 0000000..f6bef61 --- /dev/null +++ b/frontend-pc/src/components/GiEditTable/GiEditTable.vue @@ -0,0 +1,176 @@ + + + + + diff --git a/frontend-pc/src/components/GiEditTable/index.ts b/frontend-pc/src/components/GiEditTable/index.ts new file mode 100644 index 0000000..9a599be --- /dev/null +++ b/frontend-pc/src/components/GiEditTable/index.ts @@ -0,0 +1,5 @@ +import EditTable from './GiEditTable.vue' + +export type * from './type' +export type EditTableInstance = InstanceType +export { EditTable as GiEditTable } diff --git a/frontend-pc/src/components/GiEditTable/type.ts b/frontend-pc/src/components/GiEditTable/type.ts new file mode 100644 index 0000000..02fc519 --- /dev/null +++ b/frontend-pc/src/components/GiEditTable/type.ts @@ -0,0 +1,81 @@ +import type * as A from '@arco-design/web-vue' +import type { VNode } from 'vue' + +export type EditTableColumnItemType + = | 'input' + | 'input-number' + | 'input-tag' + | 'textarea' + | 'select' + | 'tree-select' + | 'radio' + | 'radio-group' + | 'checkbox' + | 'checkbox-group' + | 'date-picker' + | 'year-picker' + | 'quarter-picker' + | 'month-picker' + | 'week-picker' + | 'time-picker' + | 'range-picker' + | 'color-picker' + | 'rate' + | 'switch' + | 'slider' + | 'cascader' + | 'upload' + | 'auto-complete' + | 'mention' + | '' + +export type EditTableComponentProps + = & A.InputInstance['$props'] + & A.InputNumberInstance['$props'] + & A.InputTagInstance['$props'] + & A.TextareaInstance['$props'] + & A.SelectInstance['$props'] + & A.TreeSelectInstance['$props'] + & A.RadioInstance['$props'] + & A.RadioGroupInstance['$props'] + & A.CheckboxInstance['$props'] + & A.CheckboxGroupInstance['$props'] + & A.DatePickerInstance['$props'] + & A.YearPickerInstance['$props'] + & A.QuarterPickerInstance['$props'] + & A.MonthPickerInstance['$props'] + & A.WeekPickerInstance['$props'] + & A.TimePickerInstance['$props'] + & A.RangePickerInstance['$props'] + & A.ColorPickerInstance['$props'] + & A.RateInstance['$props'] + & A.SwitchInstance['$props'] + & A.SliderInstance['$props'] + & A.CascaderInstance['$props'] + & A.UploadInstance['$props'] + & A.AutoCompleteInstance['$props'] + & A.MentionInstance['$props'] + +interface EditTableColumnItemProps extends Partial> { + placeholder?: string | string[] +} + +export interface EditTableColumnItem { + type?: EditTableColumnItemType + title: string + dataIndex: string + required?: boolean + rules?: A.FormItemInstance['$props']['rules'] // 表单校验规则 + props?: EditTableColumnItemProps + columnProps?: Partial> & { title?: string | (() => VNode) } + formItemProps?: A.FormItemInstance['$props'] + slotName?: string +} + +export type EditTableCellDisabled = boolean | ((e: { row: T, rowIndex: number, col: EditTableColumnItem }) => boolean) + +export interface EditTableProps { + columns: EditTableColumnItem[] + data: T[] + cellDisabled?: EditTableCellDisabled +} diff --git a/frontend-pc/src/components/GiFlex/GiFlex.vue b/frontend-pc/src/components/GiFlex/GiFlex.vue new file mode 100644 index 0000000..b418f81 --- /dev/null +++ b/frontend-pc/src/components/GiFlex/GiFlex.vue @@ -0,0 +1,65 @@ + + + + + diff --git a/frontend-pc/src/components/GiFlex/index.ts b/frontend-pc/src/components/GiFlex/index.ts new file mode 100644 index 0000000..4293675 --- /dev/null +++ b/frontend-pc/src/components/GiFlex/index.ts @@ -0,0 +1,5 @@ +import Flex from './GiFlex.vue' + +export type FlexInstance = InstanceType +export * from './type' +export { Flex as GiFlex } diff --git a/frontend-pc/src/components/GiFlex/type.ts b/frontend-pc/src/components/GiFlex/type.ts new file mode 100644 index 0000000..715fd2f --- /dev/null +++ b/frontend-pc/src/components/GiFlex/type.ts @@ -0,0 +1,29 @@ +/** 组件属性定义 */ +export interface FlexProps { + /** 排列方向 */ + column?: boolean + /** 水平对齐方式(与 CSS justify-content 一致) */ + justify?: + | 'start' + | 'center' + | 'end' + | 'flex-start' + | 'flex-end' + | 'space-between' + | 'space-around' + | 'space-evenly' + /** 垂直对齐方式(与 CSS align-items 一致) */ + align?: + | 'start' + | 'center' + | 'end' + | 'flex-start' + | 'flex-end' + | 'baseline' + | 'stretch' + /** 是否自动换行 */ + wrap?: boolean + /** `boolean` 须写在联合类型最前,否则模板里单独写 `gap` 会被解析成 `""` 而不是 `true` */ + gap?: boolean | number | string + flex?: number | string +} diff --git a/frontend-pc/src/components/GiForm/GiForm.vue b/frontend-pc/src/components/GiForm/GiForm.vue new file mode 100644 index 0000000..b3d3462 --- /dev/null +++ b/frontend-pc/src/components/GiForm/GiForm.vue @@ -0,0 +1,273 @@ + + + + + + diff --git a/frontend-pc/src/components/GiForm/index.ts b/frontend-pc/src/components/GiForm/index.ts new file mode 100644 index 0000000..219e98e --- /dev/null +++ b/frontend-pc/src/components/GiForm/index.ts @@ -0,0 +1,5 @@ +import Form from './GiForm.vue' + +export type * from './type' +export type FormInstance = InstanceType +export { Form as GiForm } diff --git a/frontend-pc/src/components/GiForm/type.ts b/frontend-pc/src/components/GiForm/type.ts new file mode 100644 index 0000000..c37bc0b --- /dev/null +++ b/frontend-pc/src/components/GiForm/type.ts @@ -0,0 +1,299 @@ +/** + * @file GiForm 组件类型定义 + * @description 包含表单组件所需的所有类型定义,用于构建动态表单 + */ + +import type * as A from '@arco-design/web-vue' +import type { GridItemProps } from '@arco-design/web-vue' +import type { Component, VNode } from 'vue' +import type { MergeMultiple } from '@/types/tool' + +type FormItemProps = A.FormItemInstance['$props'] + +/** 支持的表单项类型 */ +export type FormColumnItemType + = | 'input' // 输入框 + | 'input-number' // 数字输入框 + | 'input-tag' // 标签输入框 + | 'input-search' // 搜索框 + | 'textarea' // 文本域 + | 'select' // 选择器 + | 'tree-select' // 树选择器 + | 'radio' // 单选框 + | 'radio-group' // 单选框组 + | 'checkbox' // 复选框 + | 'checkbox-group' // 复选框组 + | 'date-picker' // 日期选择器 + | 'year-picker' // 年份选择器 + | 'quarter-picker' // 季度选择器 + | 'month-picker' // 月份选择器 + | 'week-picker' // 周选择器 + | 'time-picker' // 时间选择器 + | 'range-picker' // 范围选择器 + | 'color-picker' // 颜色选择器 + | 'rate' // 评分 + | 'switch' // 开关 + | 'slider' // 滑块 + | 'cascader' // 级联选择器 + | 'upload' // 上传 + | 'auto-complete' // 自动完成 + | 'mention' // 提及 + | 'group-title' // 分组标题 + | 'slot' // 插槽,由父组件通过具名插槽渲染内容 + | Component + +/** 组件属性合并类型 */ +export type FormComponentProps = MergeMultiple<[ + Partial, + Partial, + Partial, + Partial, + Partial, + Partial, + Partial, + Partial, + Partial, + Partial, + Partial, + Partial, + Partial, + Partial, + Partial, + Partial, + Partial, + Partial, + Partial, + Partial, + Partial, + Partial, + Partial, + Partial, + Partial, + Partial, + Partial +]> + +/** 选项类型定义 */ +export type FormColumnItemOptions + = MergeMultiple<[A.SelectInstance['$props']['options'], A.RadioGroupInstance['$props']['options'], A.CheckboxGroupInstance['$props']['options'], A.CascaderInstance['$props']['options']]> + +/** 数据源类型定义 */ +export type FormColumnItemData + = MergeMultiple<[A.TreeSelectInstance['$props']['data'], A.AutoCompleteInstance['$props']['data'], A.MentionInstance['$props']['data']]> + +/** 自动完成组件插槽 */ +type AutoCompleteSlots = { + option: (e: { data: (string | number | A.SelectOptionData | A.SelectOptionGroup)[] }) => VNode + footer: () => VNode +} + +/** 级联选择器插槽 */ +type CascaderSlots = { + 'label': (e: { data: A.CascaderOption }) => VNode + 'prefix': () => VNode + 'arrow-icon': () => VNode + 'loading-icon': () => VNode + 'search-icon': () => VNode + 'empty': () => VNode + 'option': (e: { data: A.CascaderOption }) => VNode +} + +/** 复选框组插槽 */ +type CheckboxGroupSlots = { + checkbox: (e: { checked: boolean, disabled: string }) => VNode + label: (e: { data: A.CheckboxOption }) => VNode +} + +/** 单选框组插槽 */ +type RadioGroupSlots = { + radio: (e: { checked: boolean, disabled: string }) => VNode + label: (e: { data: any }) => VNode +} + +/** 日期选择器插槽 */ +type DatePickerSlots = { + 'prefix': () => VNode + 'suffix-icon': () => VNode + 'icon-next-double': () => VNode + 'icon-prev-double': () => VNode + 'icon-next': () => VNode + 'icon-prev': () => VNode + 'cell': (e: { data: Date }) => VNode + 'extra': () => VNode +} + +/** 输入框插槽 */ +type InputSlots = { + append: (() => VNode) | string + prepend: (() => VNode) | string + suffix: (() => VNode) | string + prefix: (() => VNode) | string +} + +/** 数字输入框插槽 */ +type InputNumberSlots = { + minus: (() => VNode) | string + plus: (() => VNode) | string + append: (() => VNode) | string + prepend: (() => VNode) | string + suffix: (() => VNode) | string +} + +/** 标签输入框插槽 */ +type InputTagSlots = { + tag: (e: { data: A.TagData }) => VNode + prefix: (() => VNode) | string + suffix: (() => VNode) | string +} + +/** 评分插槽 */ +type RateSlots = { + character: (e: { index: number }) => VNode +} + +/** 选择器插槽 */ +type SelectSlots = { + 'trigger': () => VNode + 'prefix': () => VNode + 'search-icon': () => VNode + 'loading-icon': () => VNode + 'arrow-icon': () => VNode + 'footer': () => VNode + 'header': () => VNode + 'label': (e: { data: A.SelectOptionData }) => VNode + 'option': (e: { data: A.SelectOptionData }) => VNode + 'empty': () => VNode +} + +/** 开关插槽 */ +type SwitchSlots = { + 'checked-icon': () => VNode + 'unchecked-icon': () => VNode + 'checked': () => VNode + 'unchecked': () => VNode +} + +/** 树选择器插槽 */ +type TreeSelectSlots = { + 'trigger': () => VNode + 'prefix': () => VNode + 'label': (e: { data: any }) => VNode + 'header': () => VNode + 'loader': () => VNode + 'empty': () => VNode + 'footer': () => VNode + 'tree-slot-extra': () => VNode + 'tree-slot-title': (e: { title: string }) => VNode + 'tree-slot-icon': (e: { node: A.TreeNodeData }) => VNode + 'tree-slot-switcher-icon': () => VNode +} + +/** 提及插槽 */ +type MentionSlots = { + option: (e: { data: any }) => VNode +} + +/** 组件插槽合并类型 */ +export type FormComponentSlots = MergeMultiple<[ + AutoCompleteSlots, + CascaderSlots, + CheckboxGroupSlots, + RadioGroupSlots, + DatePickerSlots, + InputSlots, + InputNumberSlots, + InputTagSlots, + RateSlots, + SelectSlots, + SwitchSlots, + TreeSelectSlots, + MentionSlots +]> + +/** 表单项插槽接口 */ +export type FormColumnItemSlots = FormComponentSlots + +/** 表单项显隐控制类型 */ +export type FormColumnItemHide = boolean | ((form: F) => boolean) +export type FormColumnItemDisabled = boolean | ((form: F) => boolean) + +/** 表单项数据请求相关类型 */ +export type FormColumnItemRequest = (form: F) => Promise +export type FormColumnItemFormat = (res: T) => FormColumnItemOptions | FormColumnItemData + +/** 表单列配置项接口 */ +export interface FormColumnItem { + /** 表单项类型 */ + type?: FormColumnItemType + /** 标签名 */ + label?: string + /** 字段名 */ + field: string + fieldName?: string + /** 自定义渲染label */ + labelRender?: (() => VNode) + /** 表单项属性 */ + props?: Partial + /** 表单项校验规则 */ + rules?: FormItemProps['rules'] + /** 是否必填 */ + required?: boolean + /** 栅格属性 */ + gridItemProps?: GridItemProps + /** 表单项属性 */ + formItemProps?: FormItemProps + /** 表单项插槽 */ + slots?: FormColumnItemSlots + /** 表单项组件插槽 */ + formItemSlots?: Record VNode)> + /** 是否隐藏(支持函数) */ + hide?: boolean | ((model: F) => boolean) + /** 是否禁用(支持函数) */ + disabled?: boolean | ((model: F) => boolean) + /** 栅格跨度 */ + span?: number | Record +} + +/** Props 类型定义 */ +export interface FormProps { + /** 表单数据对象 */ + modelValue: any + /** 表单布局方式 */ + layout?: A.FormInstance['layout'] + /** 表单尺寸 */ + size?: A.FormInstance['size'] + /** 标签布局属性 */ + labelColProps?: A.FormInstance['labelColProps'] + /** 表单项布局属性 */ + wrapperColProps?: A.FormInstance['wrapperColProps'] + /** 标签对齐方式 */ + labelAlign?: A.FormInstance['labelAlign'] + /** 是否禁用表单 */ + disabled?: A.FormInstance['disabled'] + /** 表单校验规则 */ + rules?: A.FormInstance['rules'] + /** 自动标签宽度 */ + autoLabelWidth?: A.FormInstance['autoLabelWidth'] + /** 表单ID */ + id?: A.FormInstance['id'] + /** 是否滚动到第一个错误项 */ + scrollToFirstError?: A.FormInstance['scrollToFirstError'] + /** 表单列配置 */ + columns: FormColumnItem[] + /** 栅格布局属性 */ + gridProps?: A.GridProps + /** 栅格项属性 */ + gridItemProps?: GridItemProps + /** 是否为搜索模式 */ + search?: boolean + /** 默认是否折叠 */ + defaultCollapsed?: boolean + /** 搜索按钮文字 */ + searchBtnText?: string + /** 是否隐藏折叠按钮 */ + hideFoldBtn?: boolean + /** 是否显示后缀 */ + suffix?: boolean + /** 表单控制 */ + fc?: Record +} diff --git a/frontend-pc/src/components/GiIconBox/GiIconBox.vue b/frontend-pc/src/components/GiIconBox/GiIconBox.vue new file mode 100644 index 0000000..2d184bb --- /dev/null +++ b/frontend-pc/src/components/GiIconBox/GiIconBox.vue @@ -0,0 +1,99 @@ + + + + + + diff --git a/frontend-pc/src/components/GiIconBox/index.ts b/frontend-pc/src/components/GiIconBox/index.ts new file mode 100644 index 0000000..255bffc --- /dev/null +++ b/frontend-pc/src/components/GiIconBox/index.ts @@ -0,0 +1,5 @@ +import IconBox from './GiIconBox.vue' + +export * from './type' +export type IconBoxInstance = InstanceType +export { IconBox as GiIconBox } diff --git a/frontend-pc/src/components/GiIconBox/type.ts b/frontend-pc/src/components/GiIconBox/type.ts new file mode 100644 index 0000000..180d504 --- /dev/null +++ b/frontend-pc/src/components/GiIconBox/type.ts @@ -0,0 +1,6 @@ +/** 组件属性定义 */ +export interface IconBoxProps { + color?: 'primary' | 'success' | 'warning' | 'danger' | string + size?: number + shape?: 'square' | 'round' +} diff --git a/frontend-pc/src/components/GiInputSearch/GiInputSearch.vue b/frontend-pc/src/components/GiInputSearch/GiInputSearch.vue new file mode 100644 index 0000000..fbdf1e5 --- /dev/null +++ b/frontend-pc/src/components/GiInputSearch/GiInputSearch.vue @@ -0,0 +1,50 @@ + + + + + diff --git a/frontend-pc/src/components/GiInputSearch/index.ts b/frontend-pc/src/components/GiInputSearch/index.ts new file mode 100644 index 0000000..9e3fb86 --- /dev/null +++ b/frontend-pc/src/components/GiInputSearch/index.ts @@ -0,0 +1,5 @@ +import InputSearch from './GiInputSearch.vue' + +export type InputSearchInstance = InstanceType +export * from './type' +export { InputSearch as GiInputSearch } diff --git a/frontend-pc/src/components/GiInputSearch/type.ts b/frontend-pc/src/components/GiInputSearch/type.ts new file mode 100644 index 0000000..b6cbf1c --- /dev/null +++ b/frontend-pc/src/components/GiInputSearch/type.ts @@ -0,0 +1,5 @@ +export interface InputSearchProps { + placeholder?: string + disabled?: boolean + readonly?: boolean +} diff --git a/frontend-pc/src/components/GiOption/GiOption.vue b/frontend-pc/src/components/GiOption/GiOption.vue new file mode 100644 index 0000000..8a35cb8 --- /dev/null +++ b/frontend-pc/src/components/GiOption/GiOption.vue @@ -0,0 +1,16 @@ + + + + + diff --git a/frontend-pc/src/components/GiOption/GiOptionItem.vue b/frontend-pc/src/components/GiOption/GiOptionItem.vue new file mode 100644 index 0000000..72f9550 --- /dev/null +++ b/frontend-pc/src/components/GiOption/GiOptionItem.vue @@ -0,0 +1,79 @@ + + + + + + diff --git a/frontend-pc/src/components/GiOption/index.ts b/frontend-pc/src/components/GiOption/index.ts new file mode 100644 index 0000000..811323f --- /dev/null +++ b/frontend-pc/src/components/GiOption/index.ts @@ -0,0 +1,7 @@ +import Option from './GiOption.vue' +import OptionItem from './GiOptionItem.vue' + +export type OptionInstance = InstanceType +export type OptionItemInstance = InstanceType +export * from './type' +export { Option as GiOption, OptionItem as GiOptionItem } diff --git a/frontend-pc/src/components/GiOption/type.ts b/frontend-pc/src/components/GiOption/type.ts new file mode 100644 index 0000000..bbddcce --- /dev/null +++ b/frontend-pc/src/components/GiOption/type.ts @@ -0,0 +1,11 @@ +/** 组件属性定义 */ +export interface OptionItemProps { + /** 图标名称 */ + icon?: string + /** 选项文本 */ + label?: string + /** 是否显示更多图标 */ + more?: boolean + /** 是否激活状态 */ + active?: boolean +} diff --git a/frontend-pc/src/components/GiPageLayout/GiPageLayout.vue b/frontend-pc/src/components/GiPageLayout/GiPageLayout.vue new file mode 100644 index 0000000..f2511cb --- /dev/null +++ b/frontend-pc/src/components/GiPageLayout/GiPageLayout.vue @@ -0,0 +1,192 @@ + + + + + diff --git a/frontend-pc/src/components/GiPageLayout/SplitButton.vue b/frontend-pc/src/components/GiPageLayout/SplitButton.vue new file mode 100644 index 0000000..820ab64 --- /dev/null +++ b/frontend-pc/src/components/GiPageLayout/SplitButton.vue @@ -0,0 +1,100 @@ + + + + + + diff --git a/frontend-pc/src/components/GiPageLayout/index.ts b/frontend-pc/src/components/GiPageLayout/index.ts new file mode 100644 index 0000000..7050a1b --- /dev/null +++ b/frontend-pc/src/components/GiPageLayout/index.ts @@ -0,0 +1,5 @@ +import PageLayout from './GiPageLayout.vue' + +export type PageLayoutInstance = InstanceType +export * from './type' +export { PageLayout as GiPageLayout } diff --git a/frontend-pc/src/components/GiPageLayout/type.ts b/frontend-pc/src/components/GiPageLayout/type.ts new file mode 100644 index 0000000..e25e74e --- /dev/null +++ b/frontend-pc/src/components/GiPageLayout/type.ts @@ -0,0 +1,14 @@ +import type { CSSProperties } from 'vue' + +/** 组件属性定义 */ +export interface PageLayoutProps { + size?: string + margin?: boolean + inner?: boolean + headerBordered?: boolean + leftStyle?: CSSProperties + headerStyle?: CSSProperties + bodyStyle?: CSSProperties + collapsed?: boolean + bgTransparent?: boolean +} diff --git a/frontend-pc/src/components/GiTable/GiTable.vue b/frontend-pc/src/components/GiTable/GiTable.vue new file mode 100644 index 0000000..5efa51b --- /dev/null +++ b/frontend-pc/src/components/GiTable/GiTable.vue @@ -0,0 +1,440 @@ + + + + + + diff --git a/frontend-pc/src/components/GiTable/index.ts b/frontend-pc/src/components/GiTable/index.ts new file mode 100644 index 0000000..eeee32b --- /dev/null +++ b/frontend-pc/src/components/GiTable/index.ts @@ -0,0 +1,5 @@ +import Table from './GiTable.vue' + +export type * from './type' +export type TableInstance = InstanceType +export { Table as GiTable } diff --git a/frontend-pc/src/components/GiTable/type.ts b/frontend-pc/src/components/GiTable/type.ts new file mode 100644 index 0000000..359a266 --- /dev/null +++ b/frontend-pc/src/components/GiTable/type.ts @@ -0,0 +1,53 @@ +import type { TableInstance } from '@arco-design/web-vue' + +export type TableProps = { + columns?: TableInstance['$props']['columns'] + data?: TableInstance['$props']['data'] + bordered?: TableInstance['$props']['bordered'] + hoverable?: TableInstance['$props']['hoverable'] + stripe?: TableInstance['$props']['stripe'] + size?: TableInstance['$props']['size'] + tableLayoutFixed?: TableInstance['$props']['tableLayoutFixed'] + loading?: TableInstance['$props']['loading'] + rowSelection?: TableInstance['$props']['rowSelection'] + expandable?: TableInstance['$props']['expandable'] + scroll?: TableInstance['$props']['scroll'] + pagination?: TableInstance['$props']['pagination'] + pagePosition?: TableInstance['$props']['pagePosition'] + indentSize?: TableInstance['$props']['indentSize'] + rowKey?: TableInstance['$props']['rowKey'] + showHeader?: TableInstance['$props']['showHeader'] + virtualListProps?: TableInstance['$props']['virtualListProps'] + spanMethod?: TableInstance['$props']['spanMethod'] + spanAll?: TableInstance['$props']['spanAll'] + loadMore?: TableInstance['$props']['loadMore'] + filterIconAlignLeft?: TableInstance['$props']['filterIconAlignLeft'] + hideExpandButtonOnEmpty?: TableInstance['$props']['hideExpandButtonOnEmpty'] + rowClass?: TableInstance['$props']['rowClass'] + draggable?: TableInstance['$props']['draggable'] + rowNumber?: TableInstance['$props']['rowNumber'] + columnResizable?: TableInstance['$props']['columnResizable'] + summary?: TableInstance['$props']['summary'] + summaryText?: TableInstance['$props']['summaryText'] + summarySpanMethod?: TableInstance['$props']['summarySpanMethod'] + selectedKeys?: TableInstance['$props']['selectedKeys'] + defaultSelectedKeys?: TableInstance['$props']['defaultSelectedKeys'] + expandedKeys?: TableInstance['$props']['expandedKeys'] + defaultExpandedKeys?: TableInstance['$props']['defaultExpandedKeys'] + defaultExpandAllRows?: TableInstance['$props']['defaultExpandAllRows'] + stickyHeader?: TableInstance['$props']['stickyHeader'] + scrollbar?: TableInstance['$props']['scrollbar'] + showEmptyTree?: TableInstance['$props']['showEmptyTree'] +} + +/** 列设置项类型 */ +export interface TableSettingColumnItem { + title: string + key: string + show: boolean + disabled: boolean + /** 固定左侧 */ + fixedLeft?: boolean + /** 固定右侧 */ + fixedRight?: boolean +} diff --git a/frontend-pc/src/components/GiTableSetting/GiTableSetting.vue b/frontend-pc/src/components/GiTableSetting/GiTableSetting.vue new file mode 100644 index 0000000..acebb7c --- /dev/null +++ b/frontend-pc/src/components/GiTableSetting/GiTableSetting.vue @@ -0,0 +1,291 @@ + + + + + diff --git a/frontend-pc/src/components/GiTableSetting/index.ts b/frontend-pc/src/components/GiTableSetting/index.ts new file mode 100644 index 0000000..048b334 --- /dev/null +++ b/frontend-pc/src/components/GiTableSetting/index.ts @@ -0,0 +1,5 @@ +import TableSetting from './GiTableSetting.vue' + +export type * from './type' +export type TableSettingInstance = InstanceType +export { TableSetting as GiTableSetting } diff --git a/frontend-pc/src/components/GiTableSetting/type.ts b/frontend-pc/src/components/GiTableSetting/type.ts new file mode 100644 index 0000000..53eb8f7 --- /dev/null +++ b/frontend-pc/src/components/GiTableSetting/type.ts @@ -0,0 +1,22 @@ +import type { TableInstance } from '@arco-design/web-vue' + +export interface GiTableSettingProps { + /** 工具栏标题 */ + title?: string + /** 列配置(用于列设置) */ + columns?: TableInstance['columns'] + /** 禁止控制显示隐藏的列 key */ + disabledColumnKeys?: string[] +} + +/** 列设置项类型 */ +export interface TableSettingColumnListItem { + title: string + key: string + show: boolean + disabled: boolean + /** 固定左侧 */ + fixedLeft?: boolean + /** 固定右侧 */ + fixedRight?: boolean +} diff --git a/frontend-pc/src/components/GiTableSetting/useTableColumnSetting.ts b/frontend-pc/src/components/GiTableSetting/useTableColumnSetting.ts new file mode 100644 index 0000000..5fab867 --- /dev/null +++ b/frontend-pc/src/components/GiTableSetting/useTableColumnSetting.ts @@ -0,0 +1,141 @@ +import type { TableColumnData, TableInstance } from '@arco-design/web-vue' +import type { MaybeRefOrGetter } from 'vue' +import type { TableSettingColumnListItem } from './type' +import { computed, ref, toValue, watch } from 'vue' + +export interface UseTableColumnSettingOptions { + columns: MaybeRefOrGetter + disabledColumnKeys?: MaybeRefOrGetter +} + +/** 获取列的 key */ +export function getTableColumnKey(column: TableColumnData, index?: number) { + if (column.dataIndex) return String(column.dataIndex) + if (typeof column.title === 'string' && column.title) return column.title + return `__column_${index ?? Date.now()}__` +} + +/** 表格列设置:显隐、排序、固定 */ +export function useTableColumnSetting(options: UseTableColumnSettingOptions) { + const showSettingColumnBtn = computed(() => { + const columns = Array.isArray(toValue(options.columns)) ? toValue(options.columns) : undefined + return Boolean(columns?.length) + }) + + const initialSettingColumns = computed(() => { + const raw = toValue(options.columns) + if (!raw) return [] + + const columns = Array.isArray(raw) ? raw : [] + return columns.map((column, index) => { + const key = getTableColumnKey(column, index) + const fixed = column.fixed + const disabledKeys = toValue(options.disabledColumnKeys) ?? [] + return { + key, + title: typeof column.title === 'string' ? column.title : '', + show: true, + disabled: disabledKeys.includes(key), + fixedLeft: fixed === 'left', + fixedRight: fixed === 'right' + } + }) + }) + + const settingColumnList = ref([]) + + const isColumnStructureMatch = ( + userColumns: TableSettingColumnListItem[], + initialColumns: TableSettingColumnListItem[] + ): boolean => { + if (userColumns.length === 0 || userColumns.length !== initialColumns.length) return false + const initialKeys = new Set(initialColumns.map((item) => item.key)) + const userKeys = new Set(userColumns.map((item) => item.key)) + return initialKeys.size === userKeys.size && [...initialKeys].every((key) => userKeys.has(key)) + } + + const columnMap = computed(() => { + const raw = toValue(options.columns) + if (!raw) return new Map() + const columns = Array.isArray(raw) ? raw : [] + return new Map(columns.map((col, index) => [getTableColumnKey(col, index), col])) + }) + + const currentSettingColumns = computed(() => { + const isValid = isColumnStructureMatch(settingColumnList.value, initialSettingColumns.value) + return isValid ? settingColumnList.value : initialSettingColumns.value + }) + + const resetSettingColumns = () => { + settingColumnList.value = [...initialSettingColumns.value] + } + + const ensureSettingColumnList = () => { + if (settingColumnList.value.length === 0 && initialSettingColumns.value.length > 0) { + settingColumnList.value = initialSettingColumns.value.map((item) => ({ ...item })) + } + } + + const toggleFixedLeft = (key: string) => { + ensureSettingColumnList() + settingColumnList.value = settingColumnList.value.map((item) => + item.key === key + ? { ...item, fixedLeft: !item.fixedLeft, fixedRight: false } + : item + ) + } + + const toggleFixedRight = (key: string) => { + ensureSettingColumnList() + settingColumnList.value = settingColumnList.value.map((item) => + item.key === key + ? { ...item, fixedRight: !item.fixedRight, fixedLeft: false } + : item + ) + } + + watch( + () => toValue(options.columns), + () => { + if (!isColumnStructureMatch(settingColumnList.value, initialSettingColumns.value)) { + resetSettingColumns() + } + }, + { deep: true } + ) + + const settingColumns = computed(() => { + const raw = toValue(options.columns) + if (!raw) return [] + + const shown = currentSettingColumns.value.filter((item) => item.show) + const leftFixed: typeof shown = [] + const noFixed: typeof shown = [] + const rightFixed: typeof shown = [] + for (const item of shown) { + if (item.fixedLeft) leftFixed.push(item) + else if (item.fixedRight) rightFixed.push(item) + else noFixed.push(item) + } + const ordered = [...leftFixed, ...noFixed, ...rightFixed] + + return ordered + .map((item) => { + const col = columnMap.value.get(item.key) + if (!col) return null + const fixed = item.fixedRight ? 'right' : item.fixedLeft ? 'left' : undefined + return { ...col, fixed } + }) + .filter(Boolean) as TableColumnData[] + }) + + return { + showSettingColumnBtn, + currentSettingColumns, + settingColumnList, + settingColumns, + resetSettingColumns, + toggleFixedLeft, + toggleFixedRight + } +} diff --git a/frontend-pc/src/components/GiTag/GiTag.tsx b/frontend-pc/src/components/GiTag/GiTag.tsx new file mode 100644 index 0000000..07ae1a4 --- /dev/null +++ b/frontend-pc/src/components/GiTag/GiTag.tsx @@ -0,0 +1,135 @@ +/** + * @file GiTag 组件 - 提供多样化的标签展示 + * @description 支持多种样式类型、状态、颜色和大小的标签组件 + */ + +import type { CSSProperties, PropType } from 'vue' +import type { TagProps as Props } from './type' +import { computed, defineComponent } from 'vue' +import './tag.scss' + +/** 基础颜色映射表 */ +const BASE_COLORS: Record = { + red: '#FF0000', + orangered: '#f77234', + orange: '#ff7d00', + gold: '#f7ba1e', + lime: '#9fdb1d', + green: '#00b42a', + cyan: '#14c9c9', + blue: '#3491fa', + purple: '#722ed1', + pink: '#f5319d', + gray: '#86909c' +} as const + +export default defineComponent({ + name: 'GiTag', + props: { + type: { + type: String as PropType, + default: 'light' + }, + status: { + type: String as PropType, + default: 'primary' + }, + color: { + type: String as PropType, + default: '' + }, + size: { + type: String as PropType, + default: 'small' + }, + closable: { + type: Boolean, + default: false + } + }, + emits: ['click', 'close'], + setup(props: Props, { slots, emit }) { + /** + * 将十六进制颜色转换为 RGB 对象 + * @param hex - 十六进制颜色值 + */ + const hexToRgb = (hex: string) => { + const cleanHex = hex.startsWith('#') ? hex.slice(1) : hex + return { + r: Number.parseInt(cleanHex.slice(0, 2), 16), + g: Number.parseInt(cleanHex.slice(2, 4), 16), + b: Number.parseInt(cleanHex.slice(4, 6), 16) + } + } + + /** 计算标签的 class */ + const className = computed(() => { + return [ + 'gi-tag', + props.type && `gi-tag__type--${props.type}`, + props.size && `gi-tag__size--${props.size}`, + props.status && `gi-tag__status--${props.status}` + ].filter(Boolean) + }) + + /** 计算标签的样式 */ + const calcStyle = computed((): CSSProperties => { + if (!props.color) return {} + + const color = BASE_COLORS[props.color as string] || props.color + const { r, g, b } = hexToRgb(color) + const style: CSSProperties = {} + + switch (props.type) { + case 'light': + style.color = color + style.backgroundColor = `rgba(${r}, ${g}, ${b}, 0.1)` + style['--tag-close-hover-bg-color'] = color + break + case 'dark': + style.color = '#fff' + style.backgroundColor = color + style['--tag-close-hover-color'] = color + style['--tag-close-hover-bg-color'] = 'rgba(255, 255, 255, 0.9)' + break + case 'outline': + style.color = color + style.backgroundColor = 'transparent' + style.borderColor = color + style['--tag-close-hover-bg-color'] = color + break + case 'light-outline': + style.color = color + style.backgroundColor = `rgba(${r}, ${g}, ${b}, 0.1)` + style.borderColor = `rgba(${r}, ${g}, ${b}, 0.2)` + style['--tag-close-hover-bg-color'] = color + break + } + + return style + }) + + /** 点击标签事件处理 */ + const handleClick = () => emit('click') + + /** 关闭标签事件处理 */ + const handleClose = (event: MouseEvent) => { + event.stopPropagation() + emit('close') + } + + /** 关闭图标组件 */ + const CloseIcon = props.closable && ( + + + + ) + + return () => ( + + {slots.default?.()} + {CloseIcon} + + ) + } +}) diff --git a/frontend-pc/src/components/GiTag/index.ts b/frontend-pc/src/components/GiTag/index.ts new file mode 100644 index 0000000..fe51656 --- /dev/null +++ b/frontend-pc/src/components/GiTag/index.ts @@ -0,0 +1,5 @@ +import Tag from './GiTag.tsx' + +export type TagInstance = InstanceType +export * from './type' +export { Tag as GiTag } diff --git a/frontend-pc/src/components/GiTag/tag.scss b/frontend-pc/src/components/GiTag/tag.scss new file mode 100644 index 0000000..ed27c19 --- /dev/null +++ b/frontend-pc/src/components/GiTag/tag.scss @@ -0,0 +1,166 @@ +$status: primary, success, warning, danger; + +$tag-size-mini-height: 20px; +$tag-size-small-height: 22px; +$tag-size-large-height: 24px; + +$tag-size-mini-padding: 0 6px; +$tag-size-small-padding: 0 8px; +$tag-size-large-padding: 0 10px; + +.gi-tag { + box-sizing: border-box; + display: inline-flex; + align-items: center; + justify-content: center; + height: $tag-size-small-height; + padding: $tag-size-small-padding; + font-size: 12px; + line-height: 1; + white-space: nowrap; + cursor: pointer; + border-radius: 3px; +} + +.gi-tag-close-btn { + position: relative; + box-sizing: border-box; + display: flex; + align-items: center; + justify-content: center; + width: 16px; + height: 16px; + margin-left: 4px; + cursor: pointer; + background-color: transparent; + border-radius: var(--border-radius-circle); + transition: background-color 0.1s cubic-bezier(0, 0, 1, 1); + + .close-icon { + z-index: 9; + width: 12px; + height: 12px; + } +} + +.gi-tag__size--mini { + height: $tag-size-mini-height; + padding: $tag-size-mini-padding; + + .gi-tag-close-btn { + .close-icon { + width: 10px; + height: 10px; + } + + &::before { + width: 14px; + height: 14px; + } + } +} + +.gi-tag__size--small { + height: $tag-size-small-height; + padding: $tag-size-small-padding; +} + +.gi-tag__size--large { + height: $tag-size-large-height; + padding: $tag-size-small-padding; +} + +.gi-tag__type--light { + color: #fff; + + @each $i in $status { + &.gi-tag__status--#{$i} + + { + color: rgb(var(--#{$i}-6)); + background-color: rgb(var(--#{$i}-1)); + + --tag-close-hover-color: #fff; + --tag-close-hover-bg-color: rgb(var(--#{$i}-6)); + + .gi-tag-close-btn { + &:hover { + color: var(--tag-close-hover-color); + background-color: var(--tag-close-hover-bg-color); + } + } + } + } +} + +.gi-tag__type--dark { + color: #fff; + + @each $i in $status { + &.gi-tag__status--#{$i} + + { + background-color: rgb(var(--#{$i}-6)); + + --tag-close-hover-color: rgb(var(--#{$i}-6)); + --tag-close-hover-bg-color: rgb(255 255 255 / 90%); + + .gi-tag-close-btn { + &:hover { + color: var(--tag-close-hover-color); + background-color: var(--tag-close-hover-bg-color); + } + } + } + } +} + +.gi-tag__type--outline { + background: transparent; + border-style: solid; + border-width: 1px; + + @each $i in $status { + &.gi-tag__status--#{$i} + + { + color: rgb(var(--#{$i}-6)); + border-color: rgb(var(--#{$i}-6)); + + --tag-close-hover-color: #fff; + --tag-close-hover-bg-color: rgb(var(--#{$i}-6)); + + .gi-tag-close-btn { + &:hover { + color: var(--tag-close-hover-color); + background-color: var(--tag-close-hover-bg-color); + } + } + } + } +} + +.gi-tag__type--light-outline { + border-style: solid; + border-width: 1px; + + @each $i in $status { + &.gi-tag__status--#{$i} + + { + color: rgb(var(--#{$i}-6)); + background-color: rgb(var(--#{$i}-1)); + border-color: rgb(var(--#{$i}-2)); + + --tag-close-hover-color: #fff; + --tag-close-hover-bg-color: rgb(var(--#{$i}-6)); + + .gi-tag-close-btn { + &:hover { + color: var(--tag-close-hover-color); + background-color: var(--tag-close-hover-bg-color); + } + } + } + } +} \ No newline at end of file diff --git a/frontend-pc/src/components/GiTag/type.ts b/frontend-pc/src/components/GiTag/type.ts new file mode 100644 index 0000000..91e282e --- /dev/null +++ b/frontend-pc/src/components/GiTag/type.ts @@ -0,0 +1,13 @@ +/** 组件属性定义 */ +export interface TagProps { + /** 标签类型 */ + type?: 'dark' | 'light' | 'outline' | 'light-outline' + /** 标签状态 */ + status?: 'primary' | 'success' | 'warning' | 'danger' | 'info' + /** 自定义颜色,支持预设颜色名或十六进制颜色值 */ + color?: 'red' | 'orangered' | 'orange' | 'gold' | 'lime' | 'green' | 'cyan' | 'blue' | 'purple' | 'pink' | 'gray' | string + /** 标签尺寸 */ + size?: 'mini' | 'small' | 'large' + /** 是否可关闭 */ + closable?: boolean +} diff --git a/frontend-pc/src/components/GiThemeBtn/GiThemeBtn.vue b/frontend-pc/src/components/GiThemeBtn/GiThemeBtn.vue new file mode 100644 index 0000000..19d2d05 --- /dev/null +++ b/frontend-pc/src/components/GiThemeBtn/GiThemeBtn.vue @@ -0,0 +1,16 @@ + + + diff --git a/frontend-pc/src/components/GiThemeBtn/index.ts b/frontend-pc/src/components/GiThemeBtn/index.ts new file mode 100644 index 0000000..228ec31 --- /dev/null +++ b/frontend-pc/src/components/GiThemeBtn/index.ts @@ -0,0 +1,4 @@ +import ThemeBtn from './GiThemeBtn.vue' + +export type ThemeBtnInstance = InstanceType +export { ThemeBtn as GiThemeBtn } diff --git a/frontend-pc/src/components/dialog/TreeUserList/Tree.vue b/frontend-pc/src/components/dialog/TreeUserList/Tree.vue new file mode 100644 index 0000000..3921133 --- /dev/null +++ b/frontend-pc/src/components/dialog/TreeUserList/Tree.vue @@ -0,0 +1,45 @@ + + + + + diff --git a/frontend-pc/src/components/dialog/TreeUserList/index.vue b/frontend-pc/src/components/dialog/TreeUserList/index.vue new file mode 100644 index 0000000..91f27d0 --- /dev/null +++ b/frontend-pc/src/components/dialog/TreeUserList/index.vue @@ -0,0 +1,93 @@ + + + + + diff --git a/frontend-pc/src/components/dialog/UserList/index.vue b/frontend-pc/src/components/dialog/UserList/index.vue new file mode 100644 index 0000000..9645e20 --- /dev/null +++ b/frontend-pc/src/components/dialog/UserList/index.vue @@ -0,0 +1,89 @@ + + + + + diff --git a/frontend-pc/src/components/dialog/index.ts b/frontend-pc/src/components/dialog/index.ts new file mode 100644 index 0000000..3d3ffd5 --- /dev/null +++ b/frontend-pc/src/components/dialog/index.ts @@ -0,0 +1,16 @@ +import type { ListItem as UserListItem } from '@/apis/person' +import { createSelectDialog } from '@/utils/createSelectDialog' +import TreeUserList from './TreeUserList/index.vue' +import UserList from './UserList/index.vue' + +/** 选择用户列表的对话框 */ +export const SelectUserListDialog = createSelectDialog({ + title: '选择用户', + component: UserList +}) + +/** 左树右表的对话框 */ +export const SelectTreeUserListDialog = createSelectDialog({ + title: '左树右表对话框示例', + component: TreeUserList +}) diff --git a/frontend-pc/src/components/icons/Icon403.vue b/frontend-pc/src/components/icons/Icon403.vue new file mode 100644 index 0000000..6f0abf4 --- /dev/null +++ b/frontend-pc/src/components/icons/Icon403.vue @@ -0,0 +1,625 @@ + + + + + diff --git a/frontend-pc/src/components/icons/Icon404.vue b/frontend-pc/src/components/icons/Icon404.vue new file mode 100644 index 0000000..e6972fd --- /dev/null +++ b/frontend-pc/src/components/icons/Icon404.vue @@ -0,0 +1,363 @@ + + + + + diff --git a/frontend-pc/src/components/icons/Icon500.vue b/frontend-pc/src/components/icons/Icon500.vue new file mode 100644 index 0000000..91b82a2 --- /dev/null +++ b/frontend-pc/src/components/icons/Icon500.vue @@ -0,0 +1,967 @@ + + + + + diff --git a/frontend-pc/src/components/index.ts b/frontend-pc/src/components/index.ts new file mode 100644 index 0000000..6ab124a --- /dev/null +++ b/frontend-pc/src/components/index.ts @@ -0,0 +1,20 @@ +export * from './GiButton' +export * from './GiCell' +export * from './GiCodeButton' +export * from './GiCodeView' +export * from './GiDot' +export * from './GiEditTable' +export * from './GiFlex' +export * from './GiFooter' +export * from './GiForm' +export * from './GiIconBox' +export * from './GiIconSelector' +export * from './GiIframe' +export * from './GiInputSearch' +export * from './GiOption' +export * from './GiPageLayout' +export * from './GiTable' +export * from './GiTableSetting' +export * from './GiTag' +export * from './GiThemeBtn' +export * from './GiTrend' diff --git a/frontend-pc/src/config/setting.json b/frontend-pc/src/config/setting.json new file mode 100644 index 0000000..c8cd449 --- /dev/null +++ b/frontend-pc/src/config/setting.json @@ -0,0 +1,11 @@ +{ + "themeColor": "#0052D9", + "layout": "left", + "tabStyle": "card", + "isTabVisible": true, + "transitionName": "zoom-fade", + "isTransitionEnabled": true, + "isMenuCollapsed": false, + "isMenuAccordion": false, + "isMenuDark": false +} diff --git a/frontend-pc/src/config/xt-routes.ts b/frontend-pc/src/config/xt-routes.ts new file mode 100644 index 0000000..5fcd568 --- /dev/null +++ b/frontend-pc/src/config/xt-routes.ts @@ -0,0 +1,230 @@ +/** + * 业务菜单与路由表。 + * + * 设计:**这一张表同时驱动三件事** —— 左侧菜单、路由注册、权限提示。 + * 三者若各写一份,改一处漏一处几乎必然发生(表现是"菜单点进去 404"或"有权限但不显示")。 + * + * 菜单可见性由**后端**决定:`/auth/me` 返回 `menus: string[]`(菜单键集合), + * 这里按菜单键过滤。真正的访问控制也在后端(绕过前端只会拿到 40301), + * 前端的过滤只是"不显示没权限的入口"。 + * + * ⚠️ `key` 必须与后端 `BuiltinRole` 里的菜单键逐字一致。 + */ +import type { UserRouteItem } from '@/apis/user' + +/** 单个菜单定义 */ +export interface XtMenu { + /** 菜单键,与后端一致 */ + key: string + title: string + /** iconify 图标名(icon-park-outline 集) */ + icon: string + /** 路由路径(完整,以 / 开头) */ + path: string + /** 视图路径(相对 src/views,不带扩展名);分组节点为空 */ + component?: string + /** 分组标题;null 表示不分组 */ + group: string | null + sort: number + /** 一期是否已实现。false 的走「待建」页,比直接隐藏更诚实 */ + ready: boolean + /** 待建说明 */ + note?: string +} + +/** 分组顺序(决定左侧菜单顺序) */ +const GROUPS: Array<{ id: string, title: string, icon: string, sort: number }> = [ + { id: 'collect', title: '收集管理', icon: 'icon-park-outline:application-two', sort: 10 }, + { id: 'setting', title: '配置', icon: 'icon-park-outline:setting-two', sort: 20 }, + { id: 'org', title: '组织与账号', icon: 'icon-park-outline:every-user', sort: 30 }, + { id: 'platform', title: '平台运维', icon: 'icon-park-outline:server', sort: 40 } +] + +/** 业务菜单(工作台在 baseRoutes 里,不列这里) */ +export const XT_MENUS: XtMenu[] = [ + // ── 收集管理 ────────────────────────────────────── + { + key: 'task', title: '收集任务', icon: 'icon-park-outline:list-two', + path: '/collect/task', component: 'task/index', group: 'collect', sort: 11, ready: false, + note: '建任务、配表单与材料规则、发布与暂停。对应里程碑 M1。' + }, + { + key: 'roster', title: '名单管理', icon: 'icon-park-outline:user-business', + path: '/collect/roster', component: 'roster/index', group: 'collect', sort: 12, ready: false, + note: '导入名册、报考范围确认、门禁名单维护。对应里程碑 M2。' + }, + { + key: 'join', title: '加入申请', icon: 'icon-park-outline:user-to-user-transmission', + path: '/collect/join', component: 'join/index', group: 'collect', sort: 13, ready: false, + note: '门禁未匹配的人提交加入申请,管理员在此审批。对应里程碑 M2。' + }, + { + key: 'audit', title: '审核', icon: 'icon-park-outline:check-one', + path: '/collect/audit', component: 'audit/index', group: 'collect', sort: 14, ready: false, + note: '班主任初审(右滑通过)、管理员终审、批量操作。对应里程碑 M5。' + }, + { + key: 'export', title: '导出中心', icon: 'icon-park-outline:download-two', + path: '/collect/export', component: 'export/index', group: 'collect', sort: 15, ready: false, + note: '按目录规范打包汇总,不合格文件禁止进包。对应里程碑 M6。' + }, + + // ── 配置 ───────────────────────────────────────── + { + key: 'form', title: '表单设计器', icon: 'icon-park-outline:form-one', + path: '/setting/form', component: 'form/index', group: 'setting', sort: 21, ready: false, + note: '内置字段锁定、扩展字段自由。对应里程碑 M1。' + }, + { + key: 'material', title: '材料规则', icon: 'icon-park-outline:file-editing', + path: '/setting/material', component: 'material/index', group: 'setting', sort: 22, ready: false, + note: '命名模板、别名、格式白名单、体积上限、压缩档位。对应里程碑 M1。' + }, + { + key: 'cleanup', title: '历史整理器', icon: 'icon-park-outline:magic-wand', + path: '/setting/cleanup', component: 'cleanup/index', group: 'setting', sort: 23, ready: false, + note: '把已有的乱文件夹/压缩包批量规范化并出体检报告。对应里程碑 M3。' + }, + + // ── 组织与账号 ──────────────────────────────────── + { + key: 'org', title: '组织架构', icon: 'icon-park-outline:tree-list', + path: '/org/manage', component: 'org/index', group: 'org', sort: 31, ready: true + }, + { + key: 'user', title: '账号管理', icon: 'icon-park-outline:peoples', + path: '/org/user', component: 'user/index', group: 'org', sort: 32, ready: false, + note: '账号增删改查与 Excel 批量导入(含体检报告)。属 M0 剩余任务。' + }, + { + key: 'role', title: '角色与授权', icon: 'icon-park-outline:permissions', + path: '/org/role', component: 'role/index', group: 'org', sort: 33, ready: false, + note: '内置角色的权限由代码决定、不可改;自定义角色属二期。' + }, + + // ── 平台运维 ────────────────────────────────────── + { + key: 'tenant', title: '租户管理', icon: 'icon-park-outline:school', + path: '/platform/tenant', component: 'tenant/index', group: 'platform', sort: 41, ready: true + }, + { + key: 'log', title: '操作日志', icon: 'icon-park-outline:log', + path: '/platform/log', component: 'log/index', group: 'platform', sort: 42, ready: false, + note: '登录、查看明文、下载、导出、代传、删除的审计记录。' + } +] + +/** 构造一个「待建」路由项(组件统一指向 coming-soon) */ +function placeholder(menu: XtMenu): UserRouteItem { + return { + id: menu.key, + parentId: '0', + path: menu.path, + component: 'coming-soon/index', + title: menu.title, + icon: menu.icon, + sort: menu.sort, + type: 2, + status: '1', + hidden: false, + keepAlive: false, + affix: false, + breadcrumb: true, + showInTabs: true, + alwaysShow: false, + activeMenu: '', + permission: '', + roles: [], + redirect: '', + children: [] + } as UserRouteItem +} + +/** + * 按后端下发的菜单键,生成异步路由树。 + * + * @param menuKeys `/auth/me` 返回的菜单键集合 + * @returns 分组 → 页面的两级路由树(`component: 'Layout'` 由上层 formatAsyncRoutes 处理) + */ +export function buildXtRoutes(menuKeys: string[]): UserRouteItem[] { + const keys = new Set(menuKeys) + const visible = XT_MENUS.filter((m) => keys.has(m.key)) + if (!visible.length) { + return [] + } + + const routes: UserRouteItem[] = [] + for (const g of GROUPS) { + const items = visible.filter((m) => m.group === g.id) + if (!items.length) { + continue + } + routes.push({ + id: g.id, + parentId: '0', + path: `/${g.id}`, + // 'Layout' 是约定的布局组件标识,formatAsyncRoutes 会替换成真实组件 + component: 'Layout', + title: g.title, + icon: g.icon, + sort: g.sort, + type: 1, + status: '1', + hidden: false, + keepAlive: false, + affix: false, + breadcrumb: true, + showInTabs: false, + alwaysShow: true, + activeMenu: '', + permission: '', + roles: [], + redirect: items[0].path, + children: items + .sort((a, b) => a.sort - b.sort) + .map((m) => (m.ready ? readyItem(m) : placeholder(m))) + } as UserRouteItem) + } + return routes +} + +/** 已实现页面 */ +function readyItem(menu: XtMenu): UserRouteItem { + return { + id: menu.key, + parentId: menu.group ?? '0', + path: menu.path, + component: menu.component!, + title: menu.title, + icon: menu.icon, + sort: menu.sort, + type: 2, + status: '1', + hidden: false, + keepAlive: false, + affix: false, + breadcrumb: true, + showInTabs: true, + alwaysShow: false, + activeMenu: '', + permission: '', + roles: [], + redirect: '', + children: [] + } as UserRouteItem +} + +/** 按菜单键取定义(给"待建"页显示说明用) */ +export function findMenu(key: string): XtMenu | undefined { + return XT_MENUS.find((m) => m.key === key) +} + +/** 角色码 → 中文名(与后端 BuiltinRole 对齐) */ +export const ROLE_NAMES: Record = { + PLATFORM_ADMIN: '平台运营', + SCHOOL_ADMIN: '学校管理员', + HEAD_TEACHER: '班主任', + DEPUTY_TEACHER: '副班主任', + AUDITOR: '审核员', + STUDENT: '学生' +} diff --git a/frontend-pc/src/directives/hasPerm.ts b/frontend-pc/src/directives/hasPerm.ts new file mode 100644 index 0000000..b615b59 --- /dev/null +++ b/frontend-pc/src/directives/hasPerm.ts @@ -0,0 +1,44 @@ +import type { Directive, DirectiveBinding } from 'vue' +import { hasPermOr } from '@/utils/permission' + +/** + * 权限检查函数 + * @description 检查元素是否具有指定的权限,如果没有权限则移除该元素 + * @param el - 要检查的 DOM 元素 + * @param binding - 指令绑定对象 + * @throws {Error} 当权限参数格式不正确时抛出错误 + */ +function checkPermission(el: HTMLElement, binding: DirectiveBinding) { + const { value } = binding + + // 检查权限参数是否有效 + if (!value || !Array.isArray(value) || value.length === 0) { + throw new Error('权限参数格式错误,示例:v-hasPerm="[\'home:btn:edit\',\'home:btn:delete\']"') + } + + // 检查是否具有权限 + const hasPermission = hasPermOr(value) + if (!hasPermission) { + el.parentNode?.removeChild(el) + } +} + +/** + * 权限指令 + * @description 用于控制元素的显示/隐藏,基于用户权限 + * @example + * ```vue + * + * + * ``` + */ +const hasPermDirective: Directive = { + mounted(el: HTMLElement, binding: DirectiveBinding) { + checkPermission(el, binding) + }, + updated(el: HTMLElement, binding: DirectiveBinding) { + checkPermission(el, binding) + } +} + +export default hasPermDirective diff --git a/frontend-pc/src/directives/hasRole.ts b/frontend-pc/src/directives/hasRole.ts new file mode 100644 index 0000000..0499fd6 --- /dev/null +++ b/frontend-pc/src/directives/hasRole.ts @@ -0,0 +1,44 @@ +import type { Directive, DirectiveBinding } from 'vue' +import { hasRoleOr } from '@/utils/permission' + +/** + * 角色检查函数 + * @description 检查元素是否具有指定的角色,如果没有角色则移除该元素 + * @param el - 要检查的 DOM 元素 + * @param binding - 指令绑定对象 + * @throws {Error} 当角色参数格式不正确时抛出错误 + */ +function checkRole(el: HTMLElement, binding: DirectiveBinding) { + const { value } = binding + + // 检查角色参数是否有效 + if (!value || !Array.isArray(value) || value.length === 0) { + throw new Error('角色参数格式错误,示例:v-hasRole="[\'admin\',\'user\']"') + } + + // 检查是否具有角色 + const hasRole = hasRoleOr(value) + if (!hasRole) { + el.parentNode?.removeChild(el) + } +} + +/** + * 角色权限指令 + * @description 用于控制元素的显示/隐藏,基于用户角色 + * @example + * ```vue + *
管理员可见
+ *
管理员或用户可见
+ * ``` + */ +const hasRoleDirective: Directive = { + mounted(el: HTMLElement, binding: DirectiveBinding) { + checkRole(el, binding) + }, + updated(el: HTMLElement, binding: DirectiveBinding) { + checkRole(el, binding) + } +} + +export default hasRoleDirective diff --git a/frontend-pc/src/directives/index.ts b/frontend-pc/src/directives/index.ts new file mode 100644 index 0000000..b50238e --- /dev/null +++ b/frontend-pc/src/directives/index.ts @@ -0,0 +1,10 @@ +import type { App } from 'vue' +import hasPerm from './hasPerm' +import hasRole from './hasRole' + +export default { + install(Vue: App) { + Vue.directive('hasPerm', hasPerm) + Vue.directive('hasRole', hasRole) + } +} diff --git a/frontend-pc/src/hooks/app/index.ts b/frontend-pc/src/hooks/app/index.ts new file mode 100644 index 0000000..42d70dd --- /dev/null +++ b/frontend-pc/src/hooks/app/index.ts @@ -0,0 +1,2 @@ +export * from './useDept' +export * from './useRole' diff --git a/frontend-pc/src/hooks/app/useDept.ts b/frontend-pc/src/hooks/app/useDept.ts new file mode 100644 index 0000000..94c7395 --- /dev/null +++ b/frontend-pc/src/hooks/app/useDept.ts @@ -0,0 +1,27 @@ +import type * as T from '@/apis/system/dept' +import { ref } from 'vue' +import { mapTree } from 'xe-utils' +import { baseAPI } from '@/apis/system/dept' + +/** 部门模块 */ +export function useDept(options?: { onSuccess?: () => void }) { + const loading = ref(false) + const deptList = ref([]) + + const getDeptList = async () => { + try { + loading.value = true + const res = await baseAPI.getList() + deptList.value = mapTree(res.data, (i) => { + if (i.children?.length) { + i.children = i.children.filter((i) => i.status === '1') + } + return i + }) + options?.onSuccess && options.onSuccess() + } finally { + loading.value = false + } + } + return { deptList, getDeptList, loading } +} diff --git a/frontend-pc/src/hooks/app/useRole.ts b/frontend-pc/src/hooks/app/useRole.ts new file mode 100644 index 0000000..ce70ad1 --- /dev/null +++ b/frontend-pc/src/hooks/app/useRole.ts @@ -0,0 +1,21 @@ +import type * as T from '@/apis/system/role' +import { ref } from 'vue' +import { baseAPI } from '@/apis/system/role' + +/** 角色模块 */ +export function useRole() { + const loading = ref(false) + const roleList = ref([]) + const total = ref(0) + const getRoleList = async () => { + try { + loading.value = true + const res = await baseAPI.getList({ page: 1, size: 99 }) + roleList.value = res.data.records.filter((i) => i.status === '1') + total.value = res.data.total + } finally { + loading.value = false + } + } + return { roleList, getRoleList, loading, total } +} diff --git a/frontend-pc/src/hooks/index.ts b/frontend-pc/src/hooks/index.ts new file mode 100644 index 0000000..b5656f1 --- /dev/null +++ b/frontend-pc/src/hooks/index.ts @@ -0,0 +1,11 @@ +export * from './useBreakpoint' +export * from './useChart' +export * from './useDevice' +export * from './useDict' +export * from './useLoading' +export * from './usePagination' +export * from './useRequest' +export * from './useResetReactive' +export * from './useRouteListener' +export * from './useTable' +export * from './useTheme' diff --git a/frontend-pc/src/hooks/useBreakpoint.ts b/frontend-pc/src/hooks/useBreakpoint.ts new file mode 100644 index 0000000..2c797ea --- /dev/null +++ b/frontend-pc/src/hooks/useBreakpoint.ts @@ -0,0 +1,34 @@ +import type { ColProps } from '@arco-design/web-vue' +import type { ComputedRef } from 'vue' +import { useBreakpoints } from '@vueuse/core' +import { computed } from 'vue' + +type ColBreakpoint = Pick +type Breakpoint = keyof ColBreakpoint + +// 定义断点配置 +const BREAKPOINT_CONFIG = { + xs: 576, // <576px + sm: 576, // ≥576px + md: 768, // ≥768px + lg: 992, // ≥992px + xl: 1200, // ≥1200px + xxl: 1600 // ≥1600px +} as const + +/** + * 响应式断点 Hook + * @returns {{ breakpoint: ComputedRef }} 当前激活的最大断点值 + */ +export function useBreakpoint() { + const breakpoints = useBreakpoints(BREAKPOINT_CONFIG) + + const currentBreakpoints = breakpoints.current() as ComputedRef + + const breakpoint = computed(() => { + const arr = currentBreakpoints.value + return arr.length ? arr[arr.length - 1] : 'xs' + }) + + return { breakpoint } +} diff --git a/frontend-pc/src/hooks/useChart.ts b/frontend-pc/src/hooks/useChart.ts new file mode 100644 index 0000000..2222113 --- /dev/null +++ b/frontend-pc/src/hooks/useChart.ts @@ -0,0 +1,30 @@ +import type { EChartsOption } from 'echarts' +import { computed } from 'vue' +import { useTheme } from '@/hooks' + +interface Options { + (isDark?: boolean): EChartsOption +} + +/** + * 用于集中管理 ECharts 配置,提供完整类型提示 + * @param sourceOption - ECharts 配置项 + * @returns { option: ComputedRef, theme: ComputedRef } - 返回 ECharts 配置项和主题 + */ +export function useChart(sourceOption: Options) { + const { isDark } = useTheme() + + // echarts support https://echarts.apache.org/zh/theme-builder.html + const option = computed(() => { + return sourceOption(isDark.value) + }) + + const theme = computed(() => isDark.value ? 'dark' : undefined) + + return { + /** ECharts 配置项 */ + option, + /** ECharts 主题 */ + theme + } +} diff --git a/frontend-pc/src/hooks/useDevice.ts b/frontend-pc/src/hooks/useDevice.ts new file mode 100644 index 0000000..0a8305e --- /dev/null +++ b/frontend-pc/src/hooks/useDevice.ts @@ -0,0 +1,17 @@ +import { useWindowSize } from '@vueuse/core' +import { computed } from 'vue' + +/** + * 响应式布局容器固定宽度 + * + * 大屏(>=1200px) + * 中屏(>=992px) + * 小屏(>=768px) + */ +export function useDevice() { + const { width } = useWindowSize() + const isDesktop = computed(() => width.value > 768) + const isMobile = computed(() => !isDesktop.value) + + return { isMobile, isDesktop } +} diff --git a/frontend-pc/src/hooks/useDict.ts b/frontend-pc/src/hooks/useDict.ts new file mode 100644 index 0000000..c2f0dae --- /dev/null +++ b/frontend-pc/src/hooks/useDict.ts @@ -0,0 +1,37 @@ +import type { SelectOptionData } from '@arco-design/web-vue' +import { computed } from 'vue' +import { useDictStore } from '@/stores' + +/** + * 将字符串数组转换为对象类型 + * @example + * ArrayToObject<['STATUS', 'GENDER']> // { STATUS: SelectOptionData[], GENDER: SelectOptionData[] } + */ +type ArrayToObject = { + [K in T[number]]: SelectOptionData[] +} + +/** + * 字典数据 Hook + * @description 根据传入的字典代码数组,返回对应的字典数据对象 + * @param codes - 字典代码数组,会自动推断字面量类型以提供类型提示 + * @returns 返回包含字典数据的响应式对象,对象的键为 codes 中的元素 + * @example + * const { dictData } = useDict(['STATUS', 'GENDER'] as const) + * // dictData.value.STATUS ✅ 有类型提示 + * // dictData.value.GENDER ✅ 有类型提示 + * // dictData.value.OTHER ❌ TypeScript 会报错 + */ +export function useDict(codes: T) { + const dictStore = useDictStore() + + const dictData = computed>(() => { + const result = {} as ArrayToObject + for (const code of codes) { + result[code as T[number]] = dictStore.dictData[code] || [] + } + return result + }) + + return { dictData } +} diff --git a/frontend-pc/src/hooks/useLoading.ts b/frontend-pc/src/hooks/useLoading.ts new file mode 100644 index 0000000..3e49152 --- /dev/null +++ b/frontend-pc/src/hooks/useLoading.ts @@ -0,0 +1,30 @@ +import { ref } from 'vue' + +/** + * 加载状态管理 Hook + * @description 提供加载状态管理的响应式 Hook,包含加载状态的设置、切换等功能 + * @param initValue - 初始加载状态,默认为 false + * @returns 返回包含加载状态管理和操作方法的对象 + * {{ + * loading: Ref, // 加载状态的响应式引用 + * setLoading: (value: boolean) => void, // 设置加载状态的函数 + * toggle: () => void // 切换加载状态的函数 + * }} + */ +export function useLoading(initValue = false) { + const loading = ref(initValue) + + const setLoading = (value: boolean) => { + loading.value = value + } + + const toggle = () => { + loading.value = !loading.value + } + + return { + loading, + setLoading, + toggle + } +} diff --git a/frontend-pc/src/hooks/usePagination.ts b/frontend-pc/src/hooks/usePagination.ts new file mode 100644 index 0000000..718506d --- /dev/null +++ b/frontend-pc/src/hooks/usePagination.ts @@ -0,0 +1,90 @@ +import { reactive, toRefs, watch } from 'vue' +import { useBreakpoint } from '@/hooks' + +type Callback = () => void + +type Options = { + defaultPageSize: number +} + +/** + * 分页管理 Hook + * @description 提供分页状态管理的响应式 Hook,支持响应式断点,自动适配移动端显示 + * @param callback - 分页变化时的回调函数 + * @param options - 分页配置选项,默认每页显示 10 条 + * @returns 包含分页状态和操作方法的响应式对象 + * {{ + * current: Ref, // 当前页码 + * pageSize: Ref, // 每页显示数量 + * total: Ref, // 总数据量 + * pagination: Object, // 完整的分页配置对象 + * changeCurrent: (page: number) => void, // 改变当前页码 + * changePageSize: (size: number) => void, // 改变每页显示数量 + * setTotal: (value: number) => void // 设置总数据量 + * }} + * @example + * const { pagination, setTotal } = usePagination(() => { + * // 分页变化时的回调 + * fetchData() + * }) + */ +export function usePagination(callback: Callback, options: Options = { defaultPageSize: 10 }) { + // 获取响应式断点 + const { breakpoint } = useBreakpoint() + + // 创建响应式分页配置对象 + const pagination = reactive({ + showPageSize: true, + showTotal: true, + current: 1, + pageSize: options.defaultPageSize, + total: 0, + simple: false, + onChange: (size: number) => { + pagination.current = size + callback && callback() + }, + onPageSizeChange: (size: number) => { + pagination.current = 1 + pagination.pageSize = size + callback && callback() + } + }) + + // 监听断点变化,自动适配移动端显示 + watch( + () => breakpoint.value, + () => { + // 在 xs(移动端)断点下使用简单模式 + pagination.simple = ['xs'].includes(breakpoint.value) + // 在 xs(移动端)断点下不显示总数据量 + pagination.showTotal = !['xs'].includes(breakpoint.value) + }, + { immediate: true } + ) + + // 导出便捷方法 + const changeCurrent = pagination.onChange + const changePageSize = pagination.onPageSizeChange + + /** + * 设置总数据量 + * @param value - 新的总数据量 + */ + function setTotal(value: number) { + pagination.total = value + } + + // 导出响应式引用 + const { current, pageSize, total } = toRefs(pagination) + + return { + current, + pageSize, + total, + pagination, + changeCurrent, + changePageSize, + setTotal + } +} diff --git a/frontend-pc/src/hooks/useRequest.ts b/frontend-pc/src/hooks/useRequest.ts new file mode 100644 index 0000000..96ad4c2 --- /dev/null +++ b/frontend-pc/src/hooks/useRequest.ts @@ -0,0 +1,90 @@ +import { ref } from 'vue' +import { useLoading } from '@/hooks' + +/** + * 请求配置选项 + */ +interface UseRequestOptions { + /** 默认值 */ + defaultValue?: T + /** 是否立即执行请求,默认为 true */ + immediate?: boolean + /** 初始加载状态,默认为 true */ + isLoading?: boolean + /** 请求成功回调 */ + onSuccess?: (data: T) => void + /** 请求失败回调 */ + onError?: (error: any) => void +} + +/** + * 请求管理 Hook + * @description 提供请求状态管理的响应式 Hook,支持自动/手动触发、错误处理、刷新等功能 + * @template T - 响应数据类型 + * @param api - API 请求函数,返回 Promise> + * @param options - 请求配置选项 + * @returns 返回包含请求状态和操作方法的对象 + * {{ + * loading: Ref, // 加载状态的响应式引用 + * data: Ref, // 响应数据的响应式引用 + * error: Ref, // 错误信息的响应式引用 + * run: () => Promise, // 手动触发请求的函数 + * }} + * @example + * // 基础用法 - 立即执行 + * const { loading, data } = useRequest(() => getUserInfo()) + * + * // 手动触发 + * const { loading, data, run } = useRequest(() => getUserInfo(), { immediate: false }) + * run() + * + * // 带默认值和回调 + * const { loading, data } = useRequest( + * () => getUserInfo(), + * { + * defaultValue: {}, + * onSuccess: (data) => console.log('成功', data), + * onError: (error) => console.error('失败', error) + * } + * ) + */ +export function useRequest( + api: () => Promise>, + options: UseRequestOptions = {} +) { + const { defaultValue, immediate = true, isLoading = true, onSuccess, onError } = options + const { loading, setLoading } = useLoading(isLoading) + const data = ref(defaultValue as T) + const error = ref(null) + + /** + * 执行请求 + * @description 调用 API 并更新响应数据,处理加载状态和错误 + */ + async function run() { + try { + error.value = null + setLoading(true) + const res = await api() + data.value = res.data as T + onSuccess?.(res.data) + } catch (err) { + error.value = err + onError?.(err) + } finally { + setLoading(false) + } + } + + // 立即执行请求 + if (immediate) { + run() + } + + return { + loading, + data, + error, + run + } +} diff --git a/frontend-pc/src/hooks/useResetReactive.ts b/frontend-pc/src/hooks/useResetReactive.ts new file mode 100644 index 0000000..fec7ca2 --- /dev/null +++ b/frontend-pc/src/hooks/useResetReactive.ts @@ -0,0 +1,47 @@ +import { cloneDeep } from 'lodash-es' +import { reactive } from 'vue' + +/** + * 响应式状态重置 Hook + * @description 创建一个可重置的响应式对象,支持将状态重置为初始值 + * @template T - 状态对象的类型 + * @param initialValue - 初始状态值 + * @returns [state, reset] - 返回响应式状态和重置函数的元组 + * @example + * const initialForm = { + * name: '', + * age: 0 + * } + * + * const [form, resetForm] = useResetReactive(initialForm) + * + * // 修改表单 + * form.name = 'John' + * form.age = 25 + * + * // 重置表单到初始状态 + * resetForm() + */ +export function useResetReactive(initialValue: T) { + /** + * 获取初始值的深拷贝 + * @returns 初始值的深拷贝 + */ + const getInitialValue = () => cloneDeep(initialValue) + + // 创建响应式状态 + const state = reactive(getInitialValue()) + + /** + * 重置状态到初始值 + * @description 先清空所有属性,然后用初始值重新赋值,确保引用不变 + */ + const reset = () => { + // 清空当前状态的所有属性 + Object.keys(state).forEach((key) => delete (state as any)[key]) + // 使用初始值重新赋值 + Object.assign(state, getInitialValue()) + } + + return [state, reset] as const +} diff --git a/frontend-pc/src/hooks/useRouteListener.ts b/frontend-pc/src/hooks/useRouteListener.ts new file mode 100644 index 0000000..55c5c97 --- /dev/null +++ b/frontend-pc/src/hooks/useRouteListener.ts @@ -0,0 +1,92 @@ +/** + * 路由变化监听 Hook + * @description 使用发布订阅模式管理路由变化,避免重复监听导致的性能问题 + */ + +import type { Handler } from 'mitt' +import type { RouteLocationNormalized } from 'vue-router' +import mitt from 'mitt' +import { onUnmounted, ref } from 'vue' + +// 事件类型定义 +type RouteChangeEvent = { + to: RouteLocationNormalized + from?: RouteLocationNormalized +} + +// 事件键定义 +const ROUTE_CHANGE_KEY = Symbol('ROUTE_CHANGE') + +// 事件发射器 +const emitter = mitt<{ + [ROUTE_CHANGE_KEY]: RouteChangeEvent +}>() + +// 最新路由状态 +const latestRoute = ref(null) + +/** + * 设置路由变化事件 + * @param to 目标路由 + * @param from 来源路由 + */ +export function setRouteEmitter(to: RouteLocationNormalized, from?: RouteLocationNormalized) { + const event: RouteChangeEvent = { to, from } + emitter.emit(ROUTE_CHANGE_KEY, event) + latestRoute.value = to +} + +/** + * 路由监听 Hook + * @returns 路由监听相关方法 + */ +export function useRouteListener() { + // 监听器列表 + const listeners = new Set>() + + /** + * 监听路由变化 + * @param handler 处理函数 + * @param immediate 是否立即执行 + */ + function listenerRouteChange( + handler: (event: RouteChangeEvent) => void, + immediate = true + ) { + emitter.on(ROUTE_CHANGE_KEY, handler) + listeners.add(handler) + + if (immediate && latestRoute.value) { + handler({ to: latestRoute.value }) + } + + return handler + } + + /** + * 移除路由监听器 + * @param handler 要移除的处理函数 + */ + function removeRouteListener(handler?: Handler) { + if (handler) { + emitter.off(ROUTE_CHANGE_KEY, handler) + listeners.delete(handler) + } else { + // 移除所有监听器 + listeners.forEach((listener) => { + emitter.off(ROUTE_CHANGE_KEY, listener) + }) + listeners.clear() + } + } + + // 组件卸载时清理 + onUnmounted(() => { + removeRouteListener() + }) + + return { + listenerRouteChange, + removeRouteListener + } +} diff --git a/frontend-pc/src/hooks/useTable.ts b/frontend-pc/src/hooks/useTable.ts new file mode 100644 index 0000000..d489743 --- /dev/null +++ b/frontend-pc/src/hooks/useTable.ts @@ -0,0 +1,277 @@ +import type { TableData, TableInstance } from '@arco-design/web-vue' +import { Message, Modal } from '@arco-design/web-vue' +import { useBreakpoint, usePagination } from '@/hooks' + +interface Options { + formatResult?: (data: T[]) => U[] + onSuccess?: () => void + immediate?: boolean + rowKey?: keyof T + /** 是否开启跨页多选,开启后切换分页会保留已选中的 key */ + crossPageSelect?: boolean + listAPI: (params: { page: number, size: number }) => Promise>> | Promise> + deleteAPI?: (ids: string[]) => Promise> +} + +/** 删除操作的配置选项 */ +interface DeleteOptions { + /** 确认框标题 */ + title?: string + /** 确认框内容 */ + content?: string + /** 成功提示信息 */ + successTip?: string + /** 是否显示确认框 */ + showModal?: boolean +} + +/** + * 表格数据管理 Hook + * @description 提供表格数据管理的响应式 Hook,支持分页、搜索、刷新、删除等功能 + * @template T - 原始数据类型 + * @template U - 格式化后的数据类型 + * @param options - 表格配置选项 + * @returns 表格相关的状态和方法 + * @example + * const { + * loading, + * tableData, + * pagination, + * selectedKeys, + * search, + * refresh, + * handleDelete + * } = useTable({ + * listAPI: (p) => getTableApi(p), + * formatResult: (data) => data.map(formatData), + * immediate: true, + * rowKey: 'id' + * }) + */ +export function useTable(options: Options) { + const { formatResult, onSuccess, immediate = true, rowKey = 'id', crossPageSelect = false, listAPI, deleteAPI } + = options || {} + + // 分页相关 + const { pagination, setTotal } = usePagination(() => getTableData()) + const loading = ref(false) + const tableData: Ref = ref([]) + + /** + * 获取表格数据 + * @description 调用 API 获取数据,处理分页、格式化等逻辑 + */ + async function getTableData() { + try { + loading.value = true + const res = await listAPI({ page: pagination.current, size: pagination.pageSize }) + // 处理返回的数据结构可能是分页或数组的情况 + const data = !Array.isArray(res.data) ? res.data.records : res.data + tableData.value = formatResult ? formatResult(data) : data + // 设置总数据量 + const total = !Array.isArray(res.data) ? res.data.total : data.length + setTotal(total) + onSuccess?.() + } finally { + loading.value = false + } + } + + // 首次加载数据 + immediate && getTableData() + + // 多选 + const selectedKeys = ref<(string | number)[]>([]) + + /** 当前页所有行的 key(用于跨页多选时合并) */ + function getCurrentPageKeys() { + return (tableData.value as TableData[]).map((i) => i[rowKey as string]) + } + + /** + * 行选择回调 + * @param rowKeys - 表格传入的选中行 key(可能仅为当前页,也可能含跨页;以当前页 key 为准做合并避免重复) + */ + const select: TableInstance['onSelect'] = (rowKeys) => { + if (crossPageSelect) { + const pageKeys = getCurrentPageKeys() + const rest = selectedKeys.value.filter((k) => !pageKeys.includes(k)) + // 只合并当前页的选中:避免表格传入的 rowKeys 含其它页时造成重复 + const currentPageSelected = rowKeys.filter((k) => pageKeys.includes(k)) + selectedKeys.value = [...new Set([...rest, ...currentPageSelected])] + } else { + selectedKeys.value = rowKeys + } + } + + /** + * 全选回调 + * @param checked - 是否全选 + */ + const selectAll: TableInstance['onSelectAll'] = (checked) => { + const arr = (tableData.value as TableData[]).filter((i) => !(i?.disabled ?? false)) + const pageKeys = arr.map((i) => i[rowKey as string]) + if (crossPageSelect) { + if (checked) { + const rest = selectedKeys.value.filter((k) => !pageKeys.includes(k)) + selectedKeys.value = [...rest, ...pageKeys] + } else { + selectedKeys.value = selectedKeys.value.filter((k) => !pageKeys.includes(k)) + } + } else { + selectedKeys.value = checked ? pageKeys : [] + } + } + + /** 获取选中的数据 */ + const getSelectedData = () => { + return tableData.value.filter((i) => selectedKeys.value.includes(i[rowKey as string])) + } + + /** + * 搜索 + * @description 重置页码为1并重新获取数据 + */ + const search = () => { + selectedKeys.value = [] + pagination.onChange(1) + } + + /** + * 刷新表格数据 + * @description 使用当前的分页参数重新获取数据 + */ + const refresh = () => { + getTableData() + } + + /** + * 处理删除操作 + * @description 支持删除确认框,自动处理删除后的刷新 + * @param deleteApi - 删除操作的 API 函数 + * @param options - 删除操作的配置选项 + * @returns Promise 删除操作的结果 + */ + const handleDelete = async ( + deleteApi: () => Promise>, + options?: DeleteOptions + ): Promise => { + const onDelete = async (isMultiple: boolean) => { + try { + const res = await deleteApi() + if (res.success) { + Message.success(options?.successTip || '删除成功!') + // 处理当前页码 + const deleteNum = isMultiple ? selectedKeys.value.length : 1 + const totalPage = Math.ceil((pagination.total - deleteNum) / pagination.pageSize) + if (pagination.current > totalPage) { + pagination.current = totalPage > 0 ? totalPage : 1 + } + isMultiple && (selectedKeys.value = []) + getTableData() + } + return res.success + } catch (error) { + // eslint-disable-next-line no-console + console.log('删除失败', error) + return false + } + } + + if (!(options?.showModal ?? true)) { + return onDelete(false) + } + + Modal.warning({ + title: options?.title || '提示', + content: options?.content || '是否确认删除?', + hideCancel: false, + maskClosable: false, + onBeforeOk: () => onDelete(true) + }) + } + + /** + * 删除单条数据 + * @description 需在 options 中配置 deleteAPI + * @param row - 要删除的行数据 + */ + const onDelete = (row: U) => { + if (!deleteAPI) { + Message.error('deleteAPI 没有配置') + return + } + const ids = String(row[rowKey as keyof U]) + return handleDelete(() => deleteAPI([ids]), { showModal: false }) + } + + /** + * 批量删除数据 + * @description 需在 options 中配置 deleteAPI + */ + const onBatchDelete = () => { + if (!deleteAPI) { + Message.error('deleteAPI 没有配置') + return + } + if (!selectedKeys.value.length) { + Message.warning('请选择要删除的数据') + return + } + handleDelete(() => deleteAPI(selectedKeys.value as string[]), { + title: '批量删除', + content: `确定要删除选中的 ${selectedKeys.value.length} 条数据吗?` + }) + } + + // 自定义扩展... + // 导入 + const onImport = () => { + Message.warning('点击了导入') + } + // 导出 + const onExport = () => { + Message.success('点击了导出') + } + // 下载模板 + // const onDownloadTemplate = () => {} + + // 响应式处理表格操作列固定状态 + const { breakpoint } = useBreakpoint() + const fixed = computed(() => !['xs', 'sm'].includes(breakpoint.value) ? 'right' : undefined) + + return { + /** 表格加载状态 */ + loading, + /** 表格数据 */ + tableData, + /** 获取表格数据 */ + getTableData, + /** 搜索,页码会重置为1 */ + search, + /** 分页的传参 */ + pagination, + /** 选择的行keys */ + selectedKeys, + /** 选择行 */ + select, + /** 全选行 */ + selectAll, + /** 获取选择的数据 */ + getSelectedData, + /** 处理删除、批量删除 */ + handleDelete, + /** 删除单条数据,需配置 deleteAPI */ + onDelete, + /** 批量删除数据,需配置 deleteAPI */ + onBatchDelete, + /** 刷新表格数据,页码会缓存 */ + refresh, + /** 操作列在小屏场景下不固定在右侧 */ + fixed, + /** 导入 */ + onImport, + /** 导出 */ + onExport + } +} diff --git a/frontend-pc/src/hooks/useTheme.ts b/frontend-pc/src/hooks/useTheme.ts new file mode 100644 index 0000000..e198327 --- /dev/null +++ b/frontend-pc/src/hooks/useTheme.ts @@ -0,0 +1,75 @@ +import { generate, getRgbStr } from '@arco-design/color' +import { useDark, useToggle } from '@vueuse/core' +import { computed } from 'vue' +import { useAppStore } from '@/stores' + +/** + * 主题管理 Hook + * @description 使用 vueuse 的 useDark 来管理主题状态 + */ +export function useTheme() { + const appStore = useAppStore() + + /** 是否为暗色主题 */ + const isDark = useDark({ + // 主题切换目标元素 + selector: 'body', + // 主题属性名 + attribute: 'arco-theme', + // 暗色主题值 + valueDark: 'dark', + // 亮色主题值 + valueLight: 'light', + // 本地存储键名 + storageKey: 'arco-theme', + // 初始值 + initialValue: 'light' + }) + + /** 当前主题 */ + const theme = computed(() => isDark.value ? 'dark' : 'light') + + /** 主题切换函数定义 */ + const onToggle = useToggle(isDark) + + /** 主题切换函数 */ + function toggleTheme() { + onToggle() + setThemeColor(appStore.themeColor) + } + + /** + * 设置主题色 + * 生成主题色的色阶并应用到 CSS 变量 + * @param color - 主题色值 + */ + function setThemeColor(color: string) { + if (!color) return + appStore.themeColor = color + const themeColors = generate(color, { + list: true, + dark: isDark.value + }) as string[] + themeColors.forEach((color, index) => { + document.body.style.setProperty(`--primary-${index + 1}`, getRgbStr(color)) + }) + } + + /** + * 初始化主题设置 + * 在应用启动时设置初始主题色 + */ + function initThemeColor() { + if (appStore.themeColor) { + setThemeColor(appStore.themeColor) + } + } + + return { + isDark, + theme, + toggleTheme, + setThemeColor, + initThemeColor + } +} diff --git a/frontend-pc/src/icons/custom-icons.json b/frontend-pc/src/icons/custom-icons.json new file mode 100644 index 0000000..569b97d --- /dev/null +++ b/frontend-pc/src/icons/custom-icons.json @@ -0,0 +1,197 @@ +{ + "prefix": "custom", + "icons": { + "borders": { + "body": "", + "width": 48, + "height": 48 + }, + "table-size": { + "body": "", + "width": 48, + "height": 48 + }, + "tree-add": { + "body": "", + "width": 48, + "height": 48 + }, + "tree-reduce": { + "body": "", + "width": 48, + "height": 48 + }, + "alipay": { + "body": "", + "width": 1024, + "height": 1024 + }, + "avatar-man": { + "body": "\r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n ", + "width": 1024, + "height": 1024 + }, + "avatar-woman": { + "body": "\r\n \r\n \r\n \r\n \r\n \r\n ", + "width": 1024, + "height": 1024 + }, + "backtop": { + "body": "", + "width": 1024, + "height": 1024 + }, + "file-all": { + "body": "", + "width": 1024, + "height": 1024 + }, + "file-close": { + "body": "", + "width": 1024, + "height": 1024 + }, + "file-css": { + "body": "\r\n \r\n \r\n \r\n ", + "width": 1024, + "height": 1024 + }, + "file-dir": { + "body": "", + "width": 1024, + "height": 1024 + }, + "file-excel": { + "body": "\r\n \r\n ", + "width": 1024, + "height": 1024 + }, + "file-exe": { + "body": "\r\n \r\n \r\n \r\n \r\n ", + "width": 1024, + "height": 1024 + }, + "file-html": { + "body": "\r\n \r\n \r\n \r\n ", + "width": 1024, + "height": 1024 + }, + "file-image": { + "body": "\r\n \r\n ", + "width": 1024, + "height": 1024 + }, + "file-js": { + "body": "\r\n \r\n \r\n \r\n ", + "width": 1024, + "height": 1024 + }, + "file-json": { + "body": "\r\n \r\n \r\n \r\n ", + "width": 1024, + "height": 1024 + }, + "file-music": { + "body": "\r\n \r\n ", + "width": 1024, + "height": 1024 + }, + "file-open": { + "body": "", + "width": 1024, + "height": 1024 + }, + "file-other": { + "body": "\r\n \r\n ", + "width": 1024, + "height": 1024 + }, + "file-pdf": { + "body": "\r\n \r\n ", + "width": 1024, + "height": 1024 + }, + "file-ppt": { + "body": "\r\n \r\n \r\n ", + "width": 1024, + "height": 1024 + }, + "file-rar": { + "body": "\r\n \r\n \r\n \r\n ", + "width": 1024, + "height": 1024 + }, + "file-txt": { + "body": "\r\n \r\n ", + "width": 1024, + "height": 1024 + }, + "file-video": { + "body": "\r\n \r\n \r\n ", + "width": 1024, + "height": 1024 + }, + "file-wps": { + "body": "\r\n \r\n \r\n \r\n ", + "width": 1024, + "height": 1024 + }, + "file-zip": { + "body": "", + "width": 1024, + "height": 1024 + }, + "file": { + "body": "\r\n \r\n ", + "width": 1024, + "height": 1024 + }, + "icon-msg": { + "body": "", + "width": 20, + "height": 20 + }, + "icon-notice": { + "body": "", + "width": 20, + "height": 20 + }, + "icon-num": { + "body": "", + "width": 20, + "height": 20 + }, + "icon-user": { + "body": "", + "width": 1024, + "height": 1024 + }, + "icon-wait": { + "body": "", + "width": 20, + "height": 20 + }, + "upload-file": { + "body": "\r\n ", + "width": 1228, + "height": 1024 + }, + "upload-folder": { + "body": "\r\n \r\n ", + "width": 1024, + "height": 1024 + }, + "vue": { + "body": "\r\n ", + "width": 128, + "height": 128 + }, + "wechat": { + "body": "\r\n ", + "width": 1024, + "height": 1024 + } + }, + "width": 48, + "height": 48 +} diff --git a/frontend-pc/src/icons/index.ts b/frontend-pc/src/icons/index.ts new file mode 100644 index 0000000..94b04a9 --- /dev/null +++ b/frontend-pc/src/icons/index.ts @@ -0,0 +1,6 @@ +import IconParkOutlineIcons from '@iconify-json/icon-park-outline/icons.json' +import { addCollection } from '@iconify/vue' +import CustomIcons from '@/icons/custom-icons.json' + +addCollection(IconParkOutlineIcons) +addCollection(CustomIcons) diff --git a/frontend-pc/src/layout/LayoutColumns.vue b/frontend-pc/src/layout/LayoutColumns.vue new file mode 100644 index 0000000..fbec7c7 --- /dev/null +++ b/frontend-pc/src/layout/LayoutColumns.vue @@ -0,0 +1,77 @@ + + + + + diff --git a/frontend-pc/src/layout/LayoutDefault.vue b/frontend-pc/src/layout/LayoutDefault.vue new file mode 100644 index 0000000..dc1c439 --- /dev/null +++ b/frontend-pc/src/layout/LayoutDefault.vue @@ -0,0 +1,33 @@ + + + + + diff --git a/frontend-pc/src/layout/LayoutMix.vue b/frontend-pc/src/layout/LayoutMix.vue new file mode 100644 index 0000000..725bccd --- /dev/null +++ b/frontend-pc/src/layout/LayoutMix.vue @@ -0,0 +1,165 @@ + + + + + + diff --git a/frontend-pc/src/layout/LayoutTop.vue b/frontend-pc/src/layout/LayoutTop.vue new file mode 100644 index 0000000..9fcd390 --- /dev/null +++ b/frontend-pc/src/layout/LayoutTop.vue @@ -0,0 +1,57 @@ + + + + + diff --git a/frontend-pc/src/layout/components/Asider/index.vue b/frontend-pc/src/layout/components/Asider/index.vue new file mode 100644 index 0000000..eaa748e --- /dev/null +++ b/frontend-pc/src/layout/components/Asider/index.vue @@ -0,0 +1,79 @@ + + + + + diff --git a/frontend-pc/src/layout/components/Breadcrumb/index.vue b/frontend-pc/src/layout/components/Breadcrumb/index.vue new file mode 100644 index 0000000..9b203b7 --- /dev/null +++ b/frontend-pc/src/layout/components/Breadcrumb/index.vue @@ -0,0 +1,130 @@ + + + + + + diff --git a/frontend-pc/src/layout/components/Header/index.vue b/frontend-pc/src/layout/components/Header/index.vue new file mode 100644 index 0000000..b7d31d7 --- /dev/null +++ b/frontend-pc/src/layout/components/Header/index.vue @@ -0,0 +1,46 @@ + + + + + diff --git a/frontend-pc/src/layout/components/HeaderRightBar/Notice.vue b/frontend-pc/src/layout/components/HeaderRightBar/Notice.vue new file mode 100644 index 0000000..7112e24 --- /dev/null +++ b/frontend-pc/src/layout/components/HeaderRightBar/Notice.vue @@ -0,0 +1,85 @@ + + + + + + diff --git a/frontend-pc/src/layout/components/HeaderRightBar/SettingDrawerPanel.vue b/frontend-pc/src/layout/components/HeaderRightBar/SettingDrawerPanel.vue new file mode 100644 index 0000000..d7f8d0c --- /dev/null +++ b/frontend-pc/src/layout/components/HeaderRightBar/SettingDrawerPanel.vue @@ -0,0 +1,168 @@ + + + + + + diff --git a/frontend-pc/src/layout/components/HeaderRightBar/components/LayoutModeItem.vue b/frontend-pc/src/layout/components/HeaderRightBar/components/LayoutModeItem.vue new file mode 100644 index 0000000..be6d771 --- /dev/null +++ b/frontend-pc/src/layout/components/HeaderRightBar/components/LayoutModeItem.vue @@ -0,0 +1,270 @@ + + + + + + diff --git a/frontend-pc/src/layout/components/HeaderRightBar/index.vue b/frontend-pc/src/layout/components/HeaderRightBar/index.vue new file mode 100644 index 0000000..1ab5d43 --- /dev/null +++ b/frontend-pc/src/layout/components/HeaderRightBar/index.vue @@ -0,0 +1,184 @@ + + + + + + diff --git a/frontend-pc/src/layout/components/Logo.vue b/frontend-pc/src/layout/components/Logo.vue new file mode 100644 index 0000000..d6afe62 --- /dev/null +++ b/frontend-pc/src/layout/components/Logo.vue @@ -0,0 +1,85 @@ + + + + + + diff --git a/frontend-pc/src/layout/components/Main.vue b/frontend-pc/src/layout/components/Main.vue new file mode 100644 index 0000000..dced06a --- /dev/null +++ b/frontend-pc/src/layout/components/Main.vue @@ -0,0 +1,37 @@ + + + + + + diff --git a/frontend-pc/src/layout/components/Menu/MenuIcon.vue b/frontend-pc/src/layout/components/Menu/MenuIcon.vue new file mode 100644 index 0000000..f243a09 --- /dev/null +++ b/frontend-pc/src/layout/components/Menu/MenuIcon.vue @@ -0,0 +1,21 @@ + + + + + diff --git a/frontend-pc/src/layout/components/Menu/MenuItem.vue b/frontend-pc/src/layout/components/Menu/MenuItem.vue new file mode 100644 index 0000000..7eb491d --- /dev/null +++ b/frontend-pc/src/layout/components/Menu/MenuItem.vue @@ -0,0 +1,44 @@ + + + + + diff --git a/frontend-pc/src/layout/components/MenuFoldBtn.vue b/frontend-pc/src/layout/components/MenuFoldBtn.vue new file mode 100644 index 0000000..ca8faad --- /dev/null +++ b/frontend-pc/src/layout/components/MenuFoldBtn.vue @@ -0,0 +1,92 @@ + + + + + + diff --git a/frontend-pc/src/layout/components/OneLevelMenu/index.vue b/frontend-pc/src/layout/components/OneLevelMenu/index.vue new file mode 100644 index 0000000..f6fe9b3 --- /dev/null +++ b/frontend-pc/src/layout/components/OneLevelMenu/index.vue @@ -0,0 +1,108 @@ + + + + + diff --git a/frontend-pc/src/layout/components/Tabs/MagicIcon.vue b/frontend-pc/src/layout/components/Tabs/MagicIcon.vue new file mode 100644 index 0000000..050bb5e --- /dev/null +++ b/frontend-pc/src/layout/components/Tabs/MagicIcon.vue @@ -0,0 +1,99 @@ + + + + + + diff --git a/frontend-pc/src/layout/components/Tabs/ReloadIcon.vue b/frontend-pc/src/layout/components/Tabs/ReloadIcon.vue new file mode 100644 index 0000000..23b237f --- /dev/null +++ b/frontend-pc/src/layout/components/Tabs/ReloadIcon.vue @@ -0,0 +1,46 @@ + + + + + + diff --git a/frontend-pc/src/layout/components/Tabs/index.vue b/frontend-pc/src/layout/components/Tabs/index.vue new file mode 100644 index 0000000..88e8194 --- /dev/null +++ b/frontend-pc/src/layout/components/Tabs/index.vue @@ -0,0 +1,238 @@ + + + + + + diff --git a/frontend-pc/src/layout/hooks/useLevelMenu.ts b/frontend-pc/src/layout/hooks/useLevelMenu.ts new file mode 100644 index 0000000..b604a66 --- /dev/null +++ b/frontend-pc/src/layout/hooks/useLevelMenu.ts @@ -0,0 +1,123 @@ +import type { CSSProperties } from 'vue' +import type { RouteLocationNormalizedGeneric, RouteRecordRaw } from 'vue-router' +import { eachTree, searchTree } from 'xe-utils' +import { useRouteListener } from '@/hooks' +import { useAppStore, useRouteStore } from '@/stores' +import { filterTree } from '@/utils' +import { isExternal } from '@/utils/validate' + +type Options = { + oneActivePathChange?: (path: string) => void +} + +/** 获取一级菜单,二级菜单的hooks */ +export function useLevelMenu(options?: Options) { + const route = useRoute() + const router = useRouter() + const appStore = useAppStore() + const routeStore = useRouteStore() + const { listenerRouteChange } = useRouteListener() + + const menuTheme = computed(() => appStore.isMenuDark ? 'dark' : 'light') + + // 获取菜单样式 + const getMenuStyle = computed(() => { + return { backgroundColor: menuTheme.value === 'dark' ? 'var(--color-menu-dark-bg)' : 'var(--color-menu-light-bg)' } as CSSProperties + }) + + // 克隆一份路由,避免直接操作原始路由 + const cloneRoutes = JSON.parse(JSON.stringify(routeStore.routes)) as RouteRecordRaw[] + const showMenuList = filterTree(cloneRoutes, (i) => i.meta?.hidden === false) as RouteRecordRaw[] + + // 一级菜单 + const oneLevelMenus = ref([]) + + // 二级菜单 + const twoLevelMenus = computed(() => { + const path = route.matched[0].path + return showMenuList.find((i) => i.path === path)?.children || [] + }) + + // 一级菜单选中的路由 + const oneActiveRoute = ref(null) + const oneActivePath = ref('') + const getOneActiveRoute = (to: RouteLocationNormalizedGeneric) => { + let path = to.path + if (to?.meta?.activeMenu) { + path = to.meta.activeMenu + } + const arr = searchTree(showMenuList, (i) => i.path === path) + oneActivePath.value = arr?.[0]?.path + return arr?.[0] + } + + // 二级菜单选中的路由 + const twoActivePath = ref('') + const getTwoActivePath = (to: RouteLocationNormalizedGeneric) => { + let path = to.path + if (to?.meta?.activeMenu) { + path = to.meta.activeMenu + } + return path + } + + listenerRouteChange(({ to }) => { + oneActiveRoute.value = getOneActiveRoute(to) + twoActivePath.value = getTwoActivePath(to) + }) + + // 获取一级菜单 + function getOneLevelMenus() { + const cloneRoutes = JSON.parse(JSON.stringify(routeStore.routes)) as RouteRecordRaw[] + const showMenuList = filterTree(cloneRoutes, (i) => i.meta?.hidden === false) as RouteRecordRaw[] + eachTree(showMenuList, (i) => { + if (i?.children?.length === 1 && i?.meta?.alwaysShow !== true) { + if (i.meta) { + i.meta.title = i.meta?.title || i.children?.[0]?.meta?.title + i.meta.icon = i.meta?.icon || i.children?.[0]?.meta?.icon + } + delete i.children + } + }) + oneLevelMenus.value = showMenuList + } + + // 一级菜单点击事件 + function handleOneMenuItemClick(path: string) { + if (isExternal(path)) { + window.open(path) + return + } + options?.oneActivePathChange?.(path) + oneActivePath.value = path + const obj = oneLevelMenus.value.find((i) => i.path === path) + if (obj?.redirect === 'noRedirect' || !obj?.redirect) { + router.push({ path: obj?.children?.[0]?.path }) + return + } + router.push({ path: (obj?.redirect as string) || path }) + } + + // 二级菜单点击事件 + function handleTwoMenuItemClick(path: string) { + if (isExternal(path)) { + window.open(path) + return + } + twoActivePath.value = path + router.push({ path }) + } + + return { + menuTheme, + getMenuStyle, + oneLevelMenus, + twoLevelMenus, + oneActiveRoute, + oneActivePath, + twoActivePath, + getOneLevelMenus, + handleOneMenuItemClick, + handleTwoMenuItemClick + } +} diff --git a/frontend-pc/src/layout/hooks/useMenu.ts b/frontend-pc/src/layout/hooks/useMenu.ts new file mode 100644 index 0000000..1d9bdc1 --- /dev/null +++ b/frontend-pc/src/layout/hooks/useMenu.ts @@ -0,0 +1,101 @@ +import type { CSSProperties } from 'vue' +import type { RouteRecordRaw } from 'vue-router' +import { computed } from 'vue' +import { eachTree } from 'xe-utils' +import { useDevice, useRouteListener } from '@/hooks' +import { useAppStore, useRouteStore } from '@/stores' +import { filterTree } from '@/utils' +import { isExternal } from '@/utils/validate' + +/** + * 菜单管理 Hooks + * 用于获取和处理应用程序的菜单列表 + * 提供菜单的过滤、展平处理等功能 + * @returns {object} 包含处理后菜单列表的响应式对象 + */ +export function useMenu() { + const router = useRouter() + // 路由存储实例,用于获取原始路由配置 + const routeStore = useRouteStore() + const appStore = useAppStore() + const { listenerRouteChange } = useRouteListener() + const { isDesktop } = useDevice() + + // 是否折叠菜单 + const collapsed = computed(() => + !isDesktop.value ? false : appStore.isMenuCollapsed + ) + + // 菜单触发器配置 + const menuTriggerProps = { + animationName: 'slide-dynamic-origin' + } + + // 菜单主题 + const menuTheme = computed(() => appStore.isMenuDark ? 'dark' : 'light') + + // 获取菜单样式 + const getMenuStyle = computed(() => { + return { backgroundColor: menuTheme.value === 'dark' ? 'var(--color-menu-dark-bg)' : 'var(--color-menu-light-bg)' } as CSSProperties + }) + + /** + * 处理后的菜单列表 + * 响应式计算属性,当路由配置变化时自动更新, + * > hidden:false那么代表这个路由项显示在左侧菜单栏中 + * > 子项chidren只有一个hidden:false的子元素时, 且alwaysShow为false, 那么这个子项会被展平到父项中 + * 包含以下处理逻辑: + * 1. 深拷贝原始路由配置避免直接修改 + * 2. 过滤掉设置为隐藏的菜单项 + * 3. 展平只有一个子项的菜单项(提升用户体验) + */ + const menuList = computed(() => { + // 深拷贝路由配置,防止修改原始数据 + const cloneRoutes = JSON.parse(JSON.stringify(routeStore.routes)) as RouteRecordRaw[] + + // 过滤出非隐藏的菜单(meta.hidden !== false) + const showMenuList = filterTree(cloneRoutes, (i) => i.meta?.hidden === false) as RouteRecordRaw[] + + // 遍历处理菜单树,展平只有一个子项的菜单项 + eachTree(showMenuList, (i) => { + if (i?.children?.length === 1 && i?.meta?.alwaysShow !== true) { + if (i.meta) { + i.meta.title = i.meta?.title || i.children?.[0]?.meta?.title + i.meta.icon = i.meta?.icon || i.children?.[0]?.meta?.icon + } + i.path = i.children?.[0]?.path + delete i.children + } + }) + return showMenuList + }) + + const selectedKeys = ref([]) + + function handleMenuItemClick(key: string) { + if (isExternal(key)) { + window.open(key) + return + } + selectedKeys.value = [key] + router.push({ path: key }) + } + + listenerRouteChange(({ to }) => { + if (to?.meta?.activeMenu) { + selectedKeys.value = [to.meta.activeMenu] + return + } + selectedKeys.value = [to.path] + }) + + return { + menuTheme, + getMenuStyle, + menuList, + menuTriggerProps, + selectedKeys, + collapsed, + handleMenuItemClick + } +} diff --git a/frontend-pc/src/layout/index.vue b/frontend-pc/src/layout/index.vue new file mode 100644 index 0000000..24525fb --- /dev/null +++ b/frontend-pc/src/layout/index.vue @@ -0,0 +1,45 @@ + + + + + + diff --git a/frontend-pc/src/main.ts b/frontend-pc/src/main.ts new file mode 100644 index 0000000..a4af86e --- /dev/null +++ b/frontend-pc/src/main.ts @@ -0,0 +1,39 @@ +import { Card, Drawer, Modal } from '@arco-design/web-vue' +// 额外引入 Arco Design Icon图标库 +import ArcoVueIcon from '@arco-design/web-vue/es/icon' +// ⚠️ Arco 组件样式必须显式引入: +// vite.config.ts 里 ArcoResolver 配了 sideEffect:false(样式不随组件自动引入), +// 原本靠 @arco-themes/vue-gi-demo 主题包带入;我们换成自己的主题后就没人带了。 +// 不引的后果是「组件能用但完全没样式」,页面直接散架,且构建不报错。 +import '@arco-design/web-vue/dist/arco.css' +import { createApp } from 'vue' +// 状态管理 +import pinia from '@/stores' +import App from './App.vue' +// 自定义指令 +import directives from './directives' +import router from './router' +// 导入本项目主题(内部会覆盖 Arco 默认变量) +import '@/styles/arco-ui/index.less' +// 使用动画库 +import 'animate.css/animate.min.css' +// 自定义过渡动画 +import '@/styles/css/transition.css' +// 导入全局scss主文件 +import '@/styles/index.scss' +// 注册离线图标集 +import '@/icons/index' + +// 对特定组件进行默认配置 +Card.props.bordered = false + +const app = createApp(App) +Modal._context = app._context +Drawer._context = app._context + +app.use(router) +app.use(pinia) +app.use(ArcoVueIcon) +app.use(directives) + +app.mount('#app') diff --git a/frontend-pc/src/router/guard/index.ts b/frontend-pc/src/router/guard/index.ts new file mode 100644 index 0000000..903d133 --- /dev/null +++ b/frontend-pc/src/router/guard/index.ts @@ -0,0 +1,31 @@ +/** @file 路由守卫配置模块 - 处理路由导航守卫和权限控制 */ + +import type { Router } from 'vue-router' +import { setRouteEmitter } from '@/hooks' +import { setupPermissionGuard } from './setupPermissionGuard' + +/** + * 设置页面路由守卫 + * @description 处理路由变化时的页面级操作,如路由变化事件通知 + * @param router - Vue Router 实例 + */ +export const setupPageGuard = (router: Router) => { + router.beforeEach((to, from) => { + // 触发路由变化事件,通知所有监听器 + setRouteEmitter(to, from) + }) +} + +/** + * 创建路由守卫 + * @description 统一配置所有路由守卫,包括: + * 1. 页面路由守卫 - 处理路由变化事件 + * 2. 权限路由守卫 - 处理路由访问权限 + * @param router - Vue Router 实例 + */ +export default function createRouteGuard(router: Router) { + // 设置页面路由守卫 + setupPageGuard(router) + // 设置权限路由守卫 + setupPermissionGuard(router) +} diff --git a/frontend-pc/src/router/guard/setupPermissionGuard.ts b/frontend-pc/src/router/guard/setupPermissionGuard.ts new file mode 100644 index 0000000..3cafe56 --- /dev/null +++ b/frontend-pc/src/router/guard/setupPermissionGuard.ts @@ -0,0 +1,89 @@ +/** @file 路由权限守卫模块 - 处理路由访问权限和动态路由加载 */ + +import type { RouteLocationNormalized, Router } from 'vue-router' +import { Message } from '@arco-design/web-vue' +import { useUserStore } from '@/stores' +import { getToken } from '@/utils/auth' + +/** 免登录白名单路径 */ +const WHITE_LIST = ['/login', '/404'] + +/** 是否已生成动态路由 */ +let isDynamicRoutesGenerated = false + +/** + * 处理动态路由加载 + * @param router - 路由实例 + * @param to - 目标路由 + */ +async function handleDynamicRoutes( + router: Router, + to: RouteLocationNormalized +): Promise<{ path: string, query?: any, replace: boolean }> { + const userStore = useUserStore() + + try { + await userStore.generateRoutes() + // 标记动态路由已生成 + isDynamicRoutesGenerated = true + // 确保路由添加完成后返回目标路由 + return { ...to, replace: true } + } catch (error) { + // 发生错误时重置用户状态并跳转登录页 + userStore.resetToken() + const errorMessage = error instanceof Error ? error.message : '路由守卫过程发生错误' + Message.error(errorMessage) + return { path: `/login`, query: { redirect: to.path }, replace: true } + } +} + +/** + * 初始化路由守卫 + * @param router - 路由实例 + */ +export const setupPermissionGuard = (router: Router): void => { + router.beforeEach(async (to, _from, next) => { + const token = getToken() + const userStore = useUserStore() + + // 已登录状态 + if (token) { + if (to.path === '/login') { + // 已登录时访问登录页,直接放行(通常会被重定向到首页) + next() + return + } + + // 只在首次访问或路由未生成时处理动态路由 + if (!isDynamicRoutesGenerated) { + try { + const redirectRoute = await handleDynamicRoutes(router, to) + next(redirectRoute) + } catch { + // 如果出错,清空角色信息并重定向到登录页 + userStore.resetToken() + // 重置路由生成标志 + isDynamicRoutesGenerated = false + next(`/login?redirect=${to.path}`) + } + return + } + + // 正常访问 + next() + return + } + + // 未登录状态时重置路由生成标志 + isDynamicRoutesGenerated = false + + if (WHITE_LIST.includes(to.path)) { + // 白名单路径直接访问 + next() + return + } + + // 其他路径重定向到登录页 + next('/login') + }) +} diff --git a/frontend-pc/src/router/index.ts b/frontend-pc/src/router/index.ts new file mode 100644 index 0000000..4a864b9 --- /dev/null +++ b/frontend-pc/src/router/index.ts @@ -0,0 +1,99 @@ +/** @file 路由配置模块 - 处理路由的创建、守卫和重置 */ + +import type { RouteRecordRaw } from 'vue-router' +import { createRouter, createWebHashHistory } from 'vue-router' +import createRouteGuard from '@/router/guard' +import { useRouteStore } from '@/stores' + +/** 默认布局组件 */ +const Layout = () => import('@/layout/index.vue') + +/** 基础路由配置 */ +const baseRoutes: RouteRecordRaw[] = [ + { + path: '/redirect', + component: Layout, + meta: { hidden: true }, + children: [ + { + path: '/redirect/:path(.*)', + component: () => import('@/views/redirect/index.vue') + } + ] + }, + { + path: '/login', + component: () => import('@/views/login/index.vue'), + meta: { hidden: true } + } +] + +/** 错误页面路由配置 */ +const errorRoutes: RouteRecordRaw[] = [ + { + path: '/:pathMatch(.*)*', + component: () => import('@/views/error/404.vue'), + meta: { hidden: true } + }, + { + path: '/403', + component: () => import('@/views/error/403.vue'), + meta: { hidden: true } + } +] + +/** 主页路由配置 */ +const homeRoute: RouteRecordRaw = { + path: '/', + name: 'Home', + component: Layout, + redirect: '/dashboard', + meta: { hidden: false }, + children: [ + { + path: '/dashboard', + component: () => import('@/views/dashboard/index.vue'), + name: 'Dashboard', + meta: { + title: '工作台', + icon: 'icon-park-outline:workbench', + affix: true, + hidden: false, + breadcrumb: false + } + } + ] +} + +/** 静态路由配置 */ +export const constantRoutes: RouteRecordRaw[] = [...baseRoutes, ...errorRoutes, homeRoute] + +/** 创建路由实例 */ +const router = createRouter({ + history: createWebHashHistory(import.meta.env.BASE_URL), + routes: constantRoutes, + scrollBehavior: () => ({ left: 0, top: 0 }) +}) + +// 创建路由守卫 +createRouteGuard(router) + +/** 重置路由配置 - 清除所有动态添加的路由 */ +export function resetRouter(): void { + try { + const routeStore = useRouteStore() + // 移除所有动态路由 + routeStore.asyncRoutes.forEach((route) => { + const { name } = route + if (name && router.hasRoute(name)) { + router.removeRoute(name) + } + }) + } catch (error) { + console.error('重置路由失败:', error) + // 降级处理:刷新页面以重置路由状态 + window.location.reload() + } +} + +export default router diff --git a/frontend-pc/src/stores/index.ts b/frontend-pc/src/stores/index.ts new file mode 100644 index 0000000..04e3879 --- /dev/null +++ b/frontend-pc/src/stores/index.ts @@ -0,0 +1,14 @@ +import { createPinia } from 'pinia' +import piniaPluginPersistedstate from 'pinia-plugin-persistedstate' + +export * from './useAppStore' +export * from './useDictStore' +export * from './useMenuBadgeStore' +export * from './useRouteStore' +export * from './useTabsStore' +export * from './useUserStore' + +const pinia = createPinia() +pinia.use(piniaPluginPersistedstate) + +export default pinia diff --git a/frontend-pc/src/stores/useAppStore.ts b/frontend-pc/src/stores/useAppStore.ts new file mode 100644 index 0000000..3fb6e8f --- /dev/null +++ b/frontend-pc/src/stores/useAppStore.ts @@ -0,0 +1,42 @@ +import { defineStore } from 'pinia' +import { computed, reactive, toRefs } from 'vue' +import defaultSettings from '@/config/setting.json' + +/** + * App Store 的核心设置逻辑 + */ +const storeSetup = () => { + // 初始化 App 配置 + const settingConfig = reactive({ ...defaultSettings }) as App.SettingConfig + + /** + * 计算页面切换动画类名 + * 根据配置决定是否启用动画以及使用哪种动画模式 + */ + const pageTransitionName = computed(() => + settingConfig.isTransitionEnabled ? settingConfig.transitionName : '' + ) + + /** + * 设置菜单折叠状态 + * @param collapsed - 是否折叠 + */ + const setMenuCollapse = (collapsed: boolean) => { + settingConfig.isMenuCollapsed = collapsed + } + + /** 恢复为 setting.json 中的默认配置 */ + const resetSettings = () => { + Object.assign(settingConfig, defaultSettings) + } + + return { + ...toRefs(settingConfig), + pageTransitionName, + setMenuCollapse, + resetSettings + } +} + +// 创建并导出 App Store,启用持久化存储 +export const useAppStore = defineStore('app', storeSetup, { persist: true }) diff --git a/frontend-pc/src/stores/useDictStore.ts b/frontend-pc/src/stores/useDictStore.ts new file mode 100644 index 0000000..da72542 --- /dev/null +++ b/frontend-pc/src/stores/useDictStore.ts @@ -0,0 +1,66 @@ +import type { SelectOptionData } from '@arco-design/web-vue' +import { defineStore } from 'pinia' +import { ref } from 'vue' +import { getDictData as getDictDataApi } from '@/apis/system/dict' + +/** + * 字典数据类型定义 + */ +type DictDataMap = Record + +/** + * 字典 Store 的核心设置逻辑 + * 用于管理系统字典数据的状态 + */ +const storeSetup = () => { + /** + * 存储字典数据的响应式对象 + * key: 字典类型 + * value: 字典选项数组 + */ + const dictData = ref({}) + + /** + * 获取字典数据 + * 从后端 API 获取字典数据并更新到 store 中 + * @throws 当 API 请求失败时抛出错误 + */ + const getDictData = async () => { + try { + const res = await getDictDataApi() + if (res?.data) { + dictData.value = res.data + } + } catch (error) { + console.error('获取字典数据失败:', error) + throw error + } + } + + /** + * 获取指定类型的字典数据 + * @param dictType - 字典类型 + * @returns 对应类型的字典选项数组,如果不存在则返回空数组 + */ + const getDictOptions = (dictType: string): SelectOptionData[] => { + return dictData.value[dictType] || [] + } + + /** + * 重置字典数据 + * 清空所有已加载的字典数据 + */ + const resetDict = () => { + dictData.value = {} + } + + return { + dictData, + getDictData, + getDictOptions, + resetDict + } +} + +// 创建并导出字典 Store,不启用持久化存储 +export const useDictStore = defineStore('dict', storeSetup, { persist: false }) diff --git a/frontend-pc/src/stores/useMenuBadgeStore.ts b/frontend-pc/src/stores/useMenuBadgeStore.ts new file mode 100644 index 0000000..dfddea5 --- /dev/null +++ b/frontend-pc/src/stores/useMenuBadgeStore.ts @@ -0,0 +1,34 @@ +import type { BadgeInstance } from '@arco-design/web-vue' +import { defineStore } from 'pinia' + +const storeSetup = () => { + // 菜单角标 + const badgeMap: Record = reactive({ + '/crud/index': 6, + '/form/base': 3, + '/form/step': 5, + '/table/dialog': 8 + }) + + // 菜单角标属性 + const BadgePropsMap: Record = { + '/crud/index': { + status: 'success' + }, + '/form/base': { + style: 'transform: scale(0.8);' + }, + '/form/step': { + style: { transform: 'scale(0.8)' }, + class: 'g-badge-primary' + }, + '/table/dialog': { + style: { transform: 'scale(0.8)' }, + class: 'g-badge-success' + } + } + + return { badgeMap, BadgePropsMap } +} + +export const useMenuBadgeStore = defineStore('menu-badge', storeSetup) diff --git a/frontend-pc/src/stores/useRouteStore.ts b/frontend-pc/src/stores/useRouteStore.ts new file mode 100644 index 0000000..1c19d82 --- /dev/null +++ b/frontend-pc/src/stores/useRouteStore.ts @@ -0,0 +1,177 @@ +/** + * @file 路由状态管理模块 + * @description 处理动态路由的加载、格式化和状态管理 + */ + +import type { RouteRecordRaw } from 'vue-router' +import type { UserRouteItem } from '@/apis/user' +import { defineStore } from 'pinia' +import { ref } from 'vue' +import { mapTree } from 'xe-utils' +import { constantRoutes } from '@/router' +import { transformPathToName } from '@/utils' + +/** + * 布局组件 + * 用于页面整体布局结构 + */ +const Layout = () => import('@/layout/index.vue') + +/** + * 视图模块映射 + * 自动导入 views 目录下的所有 .vue 文件 + * 用于动态加载路由组件 + */ +const modules = import.meta.glob('@/views/**/*.vue') + +/** + * 组件路径映射缓存 + * 将视图路径映射到对应的模块加载函数,避免每次遍历查找 + * key: 视图路径(如 'system/user/index') + * value: 模块加载函数 + */ +const viewPathMap = new Map Promise>() + +/** + * 初始化组件路径映射 + * @description 在模块加载时构建路径映射表,提升查找效率 + */ +function initViewPathMap() { + if (viewPathMap.size > 0) return // 已初始化则跳过 + + for (const path in modules) { + // 提取视图路径:从 'src/views/system/user/index.vue' 提取 'system/user/index' + const dir = path.split('views/')[1]?.split('.vue')[0] + if (dir) { + viewPathMap.set(dir, () => modules[path]()) + } + } +} + +// 初始化路径映射 +initViewPathMap() + +/** + * 加载视图组件 + * @description 根据路径加载对应的视图组件,使用 Map 缓存提升查找效率 + * @param {string} view - 组件路径(相对于 views 目录) + * @returns {Function|undefined} 返回组件的加载函数或 undefined + * @example + * loadView('system/user/index') + */ +export const loadView = (view: string) => { + return viewPathMap.get(view) +} + +/** + * 转换组件标识为实际组件 + * @description 将字符串类型的组件标识转换为实际的组件 + * @param {string} component - 组件标识 + * @returns {Function} 返回组件的加载函数 + * @example + * transformComponentView('Layout') + * transformComponentView('system/user/index') + */ +const transformComponentView = (component: string) => { + if (component === 'Layout') { + return Layout as never + } else { + return loadView(component) as never + } +} + +/** + * 格式化异步路由配置 + * @description 处理后端返回的菜单数据,转换为路由配置 + * @param {MenuItem[]} menus - 后端返回的菜单数据(已经过权限过滤) + * @returns {RouteRecordRaw[]} 返回格式化后的路由配置数组 + * + * @remarks + * 1. 对菜单数据进行排序(根据 sort 字段) + * 2. 将字符串类型的组件标识转换为实际组件 + * 3. 处理路由元数据(meta) + * + * @example + * const routes = formatAsyncRoutes([ + * { + * path: '/system', + * component: 'Layout', + * children: [...] + * } + * ]) + */ +const formatAsyncRoutes = (menus: UserRouteItem[]) => { + if (!menus.length) return [] + + // 对顶层菜单进行排序 + menus.sort((a, b) => (a?.sort ?? 0) - (b?.sort ?? 0)) + + // 使用 mapTree 递归处理菜单树 + const routes = mapTree(menus, (item) => { + // 对子菜单进行排序 + if (item.children && item.children.length) { + item.children.sort((a, b) => (a?.sort ?? 0) - (b?.sort ?? 0)) + } + + // 转换为路由配置 + return { + path: item.path, + name: transformPathToName(item.path), + component: item.component ? transformComponentView(item.component) : undefined, + redirect: item.redirect, + meta: { + hidden: item.hidden, // 是否在菜单中隐藏 + keepAlive: item.keepAlive, // 是否缓存该路由 + title: item.title, // 路由标题,用于显示在菜单和标签页 + icon: item.icon, // 图标 + affix: item.affix, // 是否固定在标签页 + breadcrumb: item.breadcrumb, // 是否显示在面包屑 + showInTabs: item.showInTabs, // 是否显示在标签页 + activeMenu: item.activeMenu, // 激活的菜单项 + alwaysShow: item.alwaysShow // 是否总是显示 + } + } + }) + return routes as RouteRecordRaw[] +} + +/** + * 路由状态管理的核心逻辑 + * @description 管理路由相关的状态和操作 + */ +const storeSetup = () => { + /** + * 所有路由配置 + * 包含常驻路由和动态路由 + */ + const routes = ref([]) + + /** + * 动态路由配置 + * 根据用户权限动态生成的路由 + */ + const asyncRoutes = ref([]) + + /** + * 合并路由配置 + * @description 将动态路由与常驻路由合并 + * @param {RouteRecordRaw[]} data - 动态路由配置 + */ + const setRoutes = (data: UserRouteItem[]) => { + const formattedRoutes = formatAsyncRoutes(data) + routes.value = constantRoutes.concat(formattedRoutes) + asyncRoutes.value = formattedRoutes + } + + return { + routes, + asyncRoutes, + setRoutes + } +} + +/** + * 路由状态管理 Store + * @description 创建路由相关的状态管理 store,启用持久化存储 + */ +export const useRouteStore = defineStore('route', storeSetup, { persist: true }) diff --git a/frontend-pc/src/stores/useTabsStore.ts b/frontend-pc/src/stores/useTabsStore.ts new file mode 100644 index 0000000..4165374 --- /dev/null +++ b/frontend-pc/src/stores/useTabsStore.ts @@ -0,0 +1,250 @@ +/** + * @file 标签页状态管理模块 + * @description 处理标签页(Tabs)的状态管理,包括页签的增删改查和缓存控制 + */ + +import type { RouteLocationNormalized, RouteRecordName } from 'vue-router' +import { defineStore } from 'pinia' +import { nextTick, ref } from 'vue' +import { useRouter } from 'vue-router' + +/** + * 标签页 Store 的核心设置逻辑 + * @description 管理标签页相关的状态和操作 + */ +const storeSetup = () => { + const router = useRouter() + + /** + * 标签页列表 + * 存储当前打开的所有标签页 + */ + const tabList = ref([]) + + /** + * 缓存组件列表 + * 存储需要被 keep-alive 缓存的组件名称 + */ + const cacheList = ref([]) + + /** + * 页面重载标志 + * 用于控制页面的重新加载 + */ + const reloadFlag = ref(true) + + /** + * 判断是否为首页 + */ + const isHomePath = (path: string) => path === '/' || path === '/home' + + /** + * 获取所有固定标签页 + * @description 从路由中筛选出所有 affix: true 的固定标签页,首页排在最前面 + */ + const getAffixTabs = (): RouteLocationNormalized[] => { + const tabs = router + .getRoutes() + .filter((route) => route.meta?.affix) + .map((route) => route as unknown as RouteLocationNormalized) + // 首页排在最前面 + tabs.sort((a, b) => Number(isHomePath(b.path)) - Number(isHomePath(a.path))) + return tabs + } + + /** + * 更新缓存列表 + */ + const updateCache = (name: RouteRecordName | null | undefined, keepAlive: boolean | undefined) => { + if (!name) return + const hasCache = cacheList.value.includes(name) + if (keepAlive && !hasCache) { + cacheList.value.push(name) + } else if (!keepAlive && hasCache) { + const index = cacheList.value.findIndex((n) => n === name) + if (index >= 0) cacheList.value.splice(index, 1) + } + } + + /** + * 添加标签页 + * @description 添加新的标签页,如果已存在则更新,同时处理缓存 + */ + const addTabItem = (item: RouteLocationNormalized) => { + const index = tabList.value.findIndex((i) => i.path === item.path) + + if (index >= 0) { + // 更新已存在的标签页 + if (tabList.value[index].fullPath !== item.fullPath) { + tabList.value[index] = item + } + updateCache(item.name, item.meta?.keepAlive) + } else if (item.meta?.showInTabs ?? true) { + // 添加新标签页 + tabList.value.push(item) + updateCache(item.name, item.meta?.keepAlive) + } + } + + /** + * 设置标签页标题 + */ + const setTabTitle = (title: string): boolean => { + if (!title) return false + const route = router.currentRoute.value + const path = route?.fullPath || route.path + const tab = tabList.value.find((i) => i.fullPath === path) + if (tab) { + tab.meta.title = title + return true + } + return false + } + + /** + * 删除标签页并清理缓存 + */ + const removeTabByIndex = (index: number) => { + if (index < 0 || index >= tabList.value.length) return + const item = tabList.value[index] + updateCache(item.name, false) // 删除缓存 + tabList.value.splice(index, 1) + } + + /** + * 跳转到指定标签页 + */ + const jumpToTab = (path: string) => { + const tab = tabList.value.find((t) => t.path === path) ?? tabList.value[0] + if (tab) router.push(tab.fullPath || tab.path) + } + + /** + * 获取固定标签页的缓存列表 + */ + const getAffixCacheList = (tabs: RouteLocationNormalized[]) => { + return tabs.filter((tab) => tab.name && tab.meta?.keepAlive).map((tab) => tab.name!) + } + + /** + * 统一关闭标签页方法 + * @description 根据类型关闭标签页:left(左侧)、right(右侧)、other(其他)、all(全部)、current(当前) + */ + const close = (type: string, path?: string) => { + const currentPath = router.currentRoute.value.path + const targetPath = path || currentPath + const targetIndex = tabList.value.findIndex((i) => i.path === targetPath) + + // 关闭所有 + if (type === 'all') { + const affixTabs = getAffixTabs() + tabList.value = affixTabs + cacheList.value = getAffixCacheList(affixTabs) + router.push('/') + return + } + + // 关闭当前 + if (type === 'current') { + if (targetIndex < 0 || tabList.value[targetIndex].meta?.affix) return + removeTabByIndex(targetIndex) + if (currentPath === targetPath && tabList.value.length) { + jumpToTab(tabList.value[tabList.value.length - 1].path) + } + return + } + + if (targetIndex < 0) return + + // 关闭左侧 + if (type === 'left') { + for (let i = targetIndex - 1; i >= 0; i--) { + if (!tabList.value[i].meta?.affix) removeTabByIndex(i) + } + return + } + + // 关闭右侧 + if (type === 'right') { + const currentIndex = tabList.value.findIndex((t) => t.path === currentPath) + const willDeleteCurrent = currentIndex > targetIndex && !tabList.value[currentIndex]?.meta?.affix + + for (let i = tabList.value.length - 1; i > targetIndex; i--) { + if (!tabList.value[i].meta?.affix) removeTabByIndex(i) + } + + if (willDeleteCurrent && tabList.value.length) { + jumpToTab(tabList.value[targetIndex].path) + } + return + } + + // 关闭其他 + if (type === 'other') { + for (let i = tabList.value.length - 1; i >= 0; i--) { + const item = tabList.value[i] + if (item.path !== targetPath && !item.meta?.affix) { + removeTabByIndex(i) + } + } + const currentExists = tabList.value.some((t) => t.path === currentPath) + if (!currentExists && tabList.value.length) { + jumpToTab(targetPath) + } + } + } + + /** + * 重置标签页状态 + * @description 清空标签页和缓存列表,保留固定标签页及其缓存 + */ + const reset = () => { + const affixTabs = getAffixTabs() + tabList.value = affixTabs + cacheList.value = getAffixCacheList(affixTabs) + } + + /** + * 初始化标签页 + * @description 初始化标签页状态,如果没有非固定标签页则重置 + */ + const init = () => { + if (tabList.value.some((i) => !i?.meta?.affix)) return + reset() + } + + /** + * 重新加载当前页面 + * @description 清除当前页面的缓存并重新加载 + */ + const reloadPage = () => { + const route = router.currentRoute.value + if (!route.name) return + + updateCache(route.name, false) // 删除缓存 + reloadFlag.value = false + + nextTick(() => { + reloadFlag.value = true + updateCache(route.name, route.meta?.keepAlive) // 重新添加缓存 + }) + } + + return { + tabList, + cacheList, + reloadFlag, + addTabItem, + setTabTitle, + close, + reset, + init, + reloadPage + } +} + +/** + * 标签页状态管理 Store + * @description 创建标签页相关的状态管理 store,使用 sessionStorage 进行持久化存储 + */ +export const useTabsStore = defineStore('tabs', storeSetup, { persist: true }) diff --git a/frontend-pc/src/stores/useUserStore.ts b/frontend-pc/src/stores/useUserStore.ts new file mode 100644 index 0000000..81b3578 --- /dev/null +++ b/frontend-pc/src/stores/useUserStore.ts @@ -0,0 +1,164 @@ +/** @file 用户状态管理模块 - 处理用户登录、权限和信息管理 */ + +import type { UserInfo } from '@/apis/user' +import type { LoginVO } from '@/apis/xt' +import { defineStore } from 'pinia' +import { computed, reactive, ref } from 'vue' +import { useRouter } from 'vue-router' +import { login as loginApi, logout as logoutApi, getMe } from '@/apis/xt' +import { buildXtRoutes } from '@/config/xt-routes' +import { resetRouter } from '@/router' +import { clearToken, getToken, setToken } from '@/utils/auth' +import { isHttp } from '@/utils/validate' +import { useRouteStore } from './useRouteStore' + +/** 登录参数:password 必须是 RSA 加密后的密文(见 @/utils/rsa) */ +type LoginParams = Parameters[0] + +/** + * 用户 Store 的核心设置逻辑 + * + * 与模板原版的差别只有数据来源:模板调 `/user/login` 等 mock 接口, + * 这里改调本项目的 `/auth/login`(《03-接口设计》§5)。 + * 内部状态形状保持不变,`v-hasPerm` 指令、布局、标签页都不用改。 + */ +const storeSetup = () => { + const routeStore = useRouteStore() + const router = useRouter() + + /** 用户基本信息(形状对齐模板的 UserInfo) */ + const userInfo: UserInfo = reactive({ + id: '', + nickname: '', + avatar: '', + roles: [], + permissions: [] + }) + + /** 后端返回的原始档案,用于展示租户名等模板没有的字段 */ + const profile = ref(null) + + /** 菜单键集合(决定左侧导航) */ + const menuKeys = ref([]) + + /** 用户昵称 */ + const name = computed(() => userInfo.nickname) + /** 用户头像 */ + const avatar = computed(() => userInfo.avatar) + /** 用户角色列表 */ + const roles = computed(() => userInfo.roles) + /** 用户权限列表 */ + const permissions = computed(() => userInfo.permissions) + /** 用户令牌 */ + const token = ref(getToken() || '') + /** 所属租户名(侧栏/顶栏展示用) */ + const tenantName = computed(() => profile.value?.tenant?.tenantName ?? '') + /** 主角色中文名 */ + const roleLabel = computed(() => profile.value?.roleCodes?.[0] ?? '') + + /** 重置用户令牌 - 清除令牌并重置路由权限标志 */ + const resetToken = () => { + token.value = '' + clearToken() + } + + /** 把后端返回的 LoginVO 落到模板内部的状态上 */ + const applyProfile = (vo: LoginVO) => { + profile.value = vo + userInfo.id = String(vo.user?.id ?? '') + userInfo.nickname = vo.user?.realName ?? '' + userInfo.avatar = vo.user?.avatarUrl ?? '' + userInfo.roles = vo.roleCodes ?? [] + userInfo.permissions = vo.permissions ?? [] + menuKeys.value = vo.menus ?? [] + } + + /** + * 用户登录 + * @param params 含 RSA 加密后的密码 + */ + const login = async (params: LoginParams): Promise => { + const res = await loginApi(params) + const vo = res.data + if (!vo?.token) { + throw new Error('服务端未返回 token') + } + setToken(vo.token) + token.value = vo.token + // 登录响应已带 user/tenant/roleCodes/menus/permissions,直接落库,省一次 /auth/me 往返 + applyProfile(vo) + } + + /** 用户退出 */ + const logout = async (): Promise => { + try { + await logoutApi() + } catch { + // 登出失败也要清本地状态:否则用户会卡在"点了登出但还是登录着" + } finally { + token.value = '' + resetToken() + resetRouter() + profile.value = null + menuKeys.value = [] + } + return true + } + + /** 获取用户信息(刷新页面后靠它恢复状态) */ + const fetchUserInfo = async (): Promise => { + const res = await getMe() + applyProfile(res.data) + return true + } + + /** + * 生成动态路由 + * + * 模板原版是直接取后端下发的路由树;本项目后端只下发**菜单键**, + * 所以这里在本地路由表(@/config/xt-routes)上做一次过滤。 + * 这样"有哪些页面"由前端代码掌控,"谁能看"由后端掌控,两边职责清晰。 + */ + const generateRoutes = async (): Promise => { + // 已登录但刷新过页面时,profile 是空的,先补回来 + if (!menuKeys.value.length) { + await fetchUserInfo() + } + routeStore.setRoutes(buildXtRoutes(menuKeys.value)) + routeStore.asyncRoutes.forEach((item) => { + if (!isHttp(item.path)) { + router.addRoute(item) + } + }) + return true + } + + return { + userInfo, + profile, + menuKeys, + name, + avatar, + roles, + permissions, + token, + tenantName, + roleLabel, + login, + logout, + fetchUserInfo, + resetToken, + generateRoutes + } +} + +/** + * 用户状态管理 Store + * @description 创建用户相关的状态管理 store,使用 localStorage 进行持久化存储 + */ +export const useUserStore = defineStore('user', storeSetup, { + persist: { + pick: ['token'], + storage: localStorage + } +}) diff --git a/frontend-pc/src/styles/animated.scss b/frontend-pc/src/styles/animated.scss new file mode 100644 index 0000000..168cfd0 --- /dev/null +++ b/frontend-pc/src/styles/animated.scss @@ -0,0 +1,23 @@ +@keyframes fade-up { + 0% { + opacity: 0; + transform: translateY(60px); + } + + 100% { + opacity: 1; + transform: translateY(0); + } +} + +@for $i from 0 through 12 { + .animated-fade-up-#{$i} + + { + opacity: 0; + animation-name: fade-up; + animation-duration: 0.5s; + animation-delay: calc($i/10) + s; + animation-fill-mode: forwards; + } +} \ No newline at end of file diff --git a/frontend-pc/src/styles/arco-ui/a-alert.less b/frontend-pc/src/styles/arco-ui/a-alert.less new file mode 100644 index 0000000..c77887d --- /dev/null +++ b/frontend-pc/src/styles/arco-ui/a-alert.less @@ -0,0 +1,3 @@ +.arco-alert { + flex-shrink: 0; +} \ No newline at end of file diff --git a/frontend-pc/src/styles/arco-ui/a-card.less b/frontend-pc/src/styles/arco-ui/a-card.less new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/frontend-pc/src/styles/arco-ui/a-card.less @@ -0,0 +1 @@ + diff --git a/frontend-pc/src/styles/arco-ui/a-color-picker.less b/frontend-pc/src/styles/arco-ui/a-color-picker.less new file mode 100644 index 0000000..e363ca7 --- /dev/null +++ b/frontend-pc/src/styles/arco-ui/a-color-picker.less @@ -0,0 +1,19 @@ +.arco-color-picker-panel .arco-color-picker-panel-control .arco-color-picker-input-wrapper .arco-select-view, +.arco-color-picker-panel .arco-color-picker-panel-control .arco-color-picker-input-wrapper .arco-input-wrapper { + padding-right: 0; +} + +.arco-color-picker-panel .arco-color-picker-input-alpha { + flex: none; + width: 70px; + + .arco-input-number-step { + width: 20px; + margin-left: 4px; + } +} + +.arco-color-picker-panel .arco-color-picker-input-alpha:not(.arco-input-disabled):not(.arco-input-outer-disabled):hover .arco-input-number-suffix, +.arco-color-picker-panel .arco-color-picker-input-alpha:not(.arco-input-disabled):not(.arco-input-outer-disabled):focus-within .arco-input-number-suffix { + opacity: 1; +} \ No newline at end of file diff --git a/frontend-pc/src/styles/arco-ui/a-descriptions.less b/frontend-pc/src/styles/arco-ui/a-descriptions.less new file mode 100644 index 0000000..060f7d4 --- /dev/null +++ b/frontend-pc/src/styles/arco-ui/a-descriptions.less @@ -0,0 +1,11 @@ +.arco-descriptions-layout-inline-horizontal { + &.arco-descriptions-size-medium { + .arco-descriptions-item { + padding-bottom: 8px; + } + } +} + +.arco-descriptions-layout-inline-horizontal .arco-descriptions-item-label-inline { + margin-right: 10px; +} diff --git a/frontend-pc/src/styles/arco-ui/a-form.less b/frontend-pc/src/styles/arco-ui/a-form.less new file mode 100644 index 0000000..ad79db4 --- /dev/null +++ b/frontend-pc/src/styles/arco-ui/a-form.less @@ -0,0 +1,28 @@ +// 清除默认过渡动画 +.form-blink-enter-from, +.form-blink-appear-from { + opacity: inherit; +} +.form-blink-enter-to, +.form-blink-appear-to { + opacity: inherit; +} +.form-blink-enter-active, +.form-blink-appear-active { + transition: none; + animation: none; +} + +// 重设过渡动画 +.form-blink-enter-active, +.form-blink-leave-active { + opacity: 1; + transform: scaleY(1); + transition: transform 0.3s cubic-bezier(.23, 1, .32, 1), opacity 0.1s cubic-bezier(.23, 1, .32, 1); + transform-origin: center top; +} +.form-blink-enter-from, +.form-blink-leave-active { + opacity: 0; + transform: scaleY(0); +} \ No newline at end of file diff --git a/frontend-pc/src/styles/arco-ui/a-input-number.less b/frontend-pc/src/styles/arco-ui/a-input-number.less new file mode 100644 index 0000000..c16265a --- /dev/null +++ b/frontend-pc/src/styles/arco-ui/a-input-number.less @@ -0,0 +1,70 @@ +// 线性主题优化 +.arco-input-number.arco-input-number-mode-button { + .arco-input-prepend { + .arco-btn { + border-right: 0; + } + } + + .arco-input-append { + .arco-btn { + border-left: 0; + } + } +} + +.arco-input-number-mode-embed { + padding-right: 0; + overflow: hidden; + + &.arco-input-wrapper:not(.arco-input-focus) .arco-input-number-step-button:not(.arco-input-number-step-button-disabled):hover { + background-color: var(--color-fill-2); + } + + .arco-input-number-step { + top: 0; + right: 0; + bottom: 0; + position: static; + display: flex; + flex-direction: column; + width: 34px; + height: 100%; + opacity: 1; + visibility: visible; + transform: none; + border-left: 1px solid var(--color-border-2); + } + + .arco-input-number-step-button { + display: flex; + flex: 1; + align-items: center; + justify-content: center; + width: 100%; + height: auto; + min-height: 0; + border-left: 1px solid var(--color-border-2); + + &:not(.arco-input-number-step-button-disabled) { + &:hover { + .arco-icon { + color: rgb(var(--primary-6)); + } + } + } + + &:hover, + &:active { + background-color: var(--color-fill-2); + } + } + + .arco-input-number-step-button+.arco-input-number-step-button { + border-top: 1px solid var(--color-border-2); + } + + .arco-input-number-input { + padding-right: 34px; + } +} \ No newline at end of file diff --git a/frontend-pc/src/styles/arco-ui/a-input.less b/frontend-pc/src/styles/arco-ui/a-input.less new file mode 100644 index 0000000..9d75d17 --- /dev/null +++ b/frontend-pc/src/styles/arco-ui/a-input.less @@ -0,0 +1,7 @@ +// 线性主题优化 +.arco-input-prepend { + border-right: none; +} +.arco-input-append { + border-left: none; +} diff --git a/frontend-pc/src/styles/arco-ui/a-menu.less b/frontend-pc/src/styles/arco-ui/a-menu.less new file mode 100644 index 0000000..911c5c4 --- /dev/null +++ b/frontend-pc/src/styles/arco-ui/a-menu.less @@ -0,0 +1,18 @@ +.arco-trigger-menu-item.arco-trigger-menu-selected, +.arco-trigger-menu-pop-header.arco-trigger-menu-selected { + color: rgb(var(--primary-6)); +} + +.arco-menu-icon-suffix { + transition: all 0.2s; +} + +.arco-menu-horizontal .arco-menu-pop { + display: inline-flex; +} + +.arco-menu-icon { + display: inline-flex; + justify-content: center; + align-items: center; +} \ No newline at end of file diff --git a/frontend-pc/src/styles/arco-ui/a-message.less b/frontend-pc/src/styles/arco-ui/a-message.less new file mode 100644 index 0000000..a8a04f7 --- /dev/null +++ b/frontend-pc/src/styles/arco-ui/a-message.less @@ -0,0 +1,28 @@ +.fade-message-enter-from { + opacity: 0; + transform: translate3d(0, -100%, 0); +} + +.fade-message-enter-to { + opacity: 1; + transform: translate3d(0, 0, 0); +} + +.fade-message-enter-active, +.fade-message-appear-active { + transition: opacity @transition-duration-3, transform @transition-duration-4; +} + +.fade-message-leave-from { + opacity: 1; + transform: translate3d(0, 0, 0); +} + +.fade-message-leave-to { + opacity: 0; + transform: translate3d(0, -100%, 0); +} + +.fade-message-leave-active { + position: absolute; +} diff --git a/frontend-pc/src/styles/arco-ui/a-modal.less b/frontend-pc/src/styles/arco-ui/a-modal.less new file mode 100644 index 0000000..448cc68 --- /dev/null +++ b/frontend-pc/src/styles/arco-ui/a-modal.less @@ -0,0 +1,44 @@ +.arco-modal-title .arco-icon-exclamation-circle-fill { + font-size: 24px; +} + +.arco-modal-title-icon { + display: flex; + justify-content: center; + align-items: center; + margin-right: 4px; +} + +.arco-modal-simple { + width: 90%; + max-width: 320px; + padding: 20px; + box-sizing: border-box; + border-radius: 20px; + + .arco-modal-header { + margin-bottom: 0; + + .arco-modal-title { + font-size: 18px; + } + } + + .arco-modal-body { + padding: 20px 0; + font-size: 16px; + text-align: center; + } + + .arco-modal-footer { + margin-top: 0; + display: flex; + + >.arco-btn { + flex: 1; + height: 38px; + border-radius: 100px; + font-weight: bold; + } + } +} \ No newline at end of file diff --git a/frontend-pc/src/styles/arco-ui/a-page-header.less b/frontend-pc/src/styles/arco-ui/a-page-header.less new file mode 100644 index 0000000..70c8da0 --- /dev/null +++ b/frontend-pc/src/styles/arco-ui/a-page-header.less @@ -0,0 +1,6 @@ +.arco-page-header { + padding: 8px 0; + background: var(--color-bg-1); + border-bottom: 1px solid var(--color-border-2); + box-sizing: border-box; +} \ No newline at end of file diff --git a/frontend-pc/src/styles/arco-ui/a-pagination.less b/frontend-pc/src/styles/arco-ui/a-pagination.less new file mode 100644 index 0000000..25661fd --- /dev/null +++ b/frontend-pc/src/styles/arco-ui/a-pagination.less @@ -0,0 +1,41 @@ +// 分页组件 +.arco-pagination-item { + padding: 0 6px; + border: 1px solid var(--color-border-3); + background-color: var(--color-bg-2); + border-radius: var(--border-radius-small); + display: inline-flex; + justify-content: center; + align-items: center; + transition: none; + box-sizing: border-box; + &:hover { + color: rgb(var(--primary-6)); + border: 1px solid rgb(var(--primary-6)); + background-color: rgba(var(--primary-6), 0.1); + } + &:active { + color: #fff; + background-color: rgb(var(--primary-6)); + border: 1px solid rgb(var(--primary-6)); + } +} + +.arco-pagination-item-active { + color: #fff; + background-color: rgb(var(--primary-6)); + border: 1px solid rgb(var(--primary-6)); + &:hover { + color: #fff; + background-color: rgb(var(--primary-6)); + border: 1px solid rgb(var(--primary-6)); + } +} + +.arco-pagination .arco-pagination-item-previous.arco-pagination-item-disabled, +.arco-pagination .arco-pagination-item-next.arco-pagination-item-disabled { + border-color: var(--color-border-2); + &:hover { + border-color: var(--color-border-2); + } +} diff --git a/frontend-pc/src/styles/arco-ui/a-picker.less b/frontend-pc/src/styles/arco-ui/a-picker.less new file mode 100644 index 0000000..5c5aaed --- /dev/null +++ b/frontend-pc/src/styles/arco-ui/a-picker.less @@ -0,0 +1,3 @@ +.arco-picker { + width: 100%; +} diff --git a/frontend-pc/src/styles/arco-ui/a-radio.less b/frontend-pc/src/styles/arco-ui/a-radio.less new file mode 100644 index 0000000..430e2a0 --- /dev/null +++ b/frontend-pc/src/styles/arco-ui/a-radio.less @@ -0,0 +1,3 @@ +.arco-radio-button-content { + white-space: nowrap; +} \ No newline at end of file diff --git a/frontend-pc/src/styles/arco-ui/a-scrollbar.less b/frontend-pc/src/styles/arco-ui/a-scrollbar.less new file mode 100644 index 0000000..ba3a990 --- /dev/null +++ b/frontend-pc/src/styles/arco-ui/a-scrollbar.less @@ -0,0 +1,21 @@ +.arco-scrollbar-track-direction-vertical { + width: 8px; +} + +.arco-scrollbar-thumb-direction-vertical .arco-scrollbar-thumb-bar { + width: 6px; + margin: 0 1px; +} + +.arco-scrollbar-track-direction-horizontal { + height: 8px; +} + +.arco-scrollbar-thumb-direction-horizontal .arco-scrollbar-thumb-bar { + height: 6px; + margin: 1px 0; +} + +.arco-scrollbar-thumb-bar { + background-color: var(--color-fill-3); +} \ No newline at end of file diff --git a/frontend-pc/src/styles/arco-ui/a-space.less b/frontend-pc/src/styles/arco-ui/a-space.less new file mode 100644 index 0000000..e69de29 diff --git a/frontend-pc/src/styles/arco-ui/a-steps.less b/frontend-pc/src/styles/arco-ui/a-steps.less new file mode 100644 index 0000000..2b22702 --- /dev/null +++ b/frontend-pc/src/styles/arco-ui/a-steps.less @@ -0,0 +1,5 @@ +.arco-steps-item-process .arco-steps-icon { + display: flex; + justify-content: center; + align-items: center; +} \ No newline at end of file diff --git a/frontend-pc/src/styles/arco-ui/a-switch.less b/frontend-pc/src/styles/arco-ui/a-switch.less new file mode 100644 index 0000000..1dbaa77 --- /dev/null +++ b/frontend-pc/src/styles/arco-ui/a-switch.less @@ -0,0 +1,7 @@ +.arco-switch-type-round.arco-switch-small { + border-radius: 3px; +} + +.arco-switch-type-round.arco-switch-small .arco-switch-handle { + border-radius: 2px; +} \ No newline at end of file diff --git a/frontend-pc/src/styles/arco-ui/a-table.less b/frontend-pc/src/styles/arco-ui/a-table.less new file mode 100644 index 0000000..6a38f3b --- /dev/null +++ b/frontend-pc/src/styles/arco-ui/a-table.less @@ -0,0 +1,9 @@ +.arco-table-th-title { + font-size: 13px; + font-weight: 600; +} + +.arco-table-container { + border-radius: var(--border-radius-small); + overflow: hidden; +} \ No newline at end of file diff --git a/frontend-pc/src/styles/arco-ui/a-tabs.less b/frontend-pc/src/styles/arco-ui/a-tabs.less new file mode 100644 index 0000000..895b36a --- /dev/null +++ b/frontend-pc/src/styles/arco-ui/a-tabs.less @@ -0,0 +1,17 @@ +.arco-tabs-nav-extra:not(:empty) { + margin-right: var(--padding); +} + +.arco-tabs { + background: var(--color-bg-1); +} + +.arco-tabs-nav-type-rounded .arco-tabs-tab { + margin: 4px; + padding: 2px 12px; +} + +.arco-tabs-nav-type-card .arco-tabs-tab:first-child, +.arco-tabs-nav-type-card .arco-tabs-tab:last-child { + border-radius: 0; +} \ No newline at end of file diff --git a/frontend-pc/src/styles/arco-ui/a-tree.less b/frontend-pc/src/styles/arco-ui/a-tree.less new file mode 100644 index 0000000..ef420f7 --- /dev/null +++ b/frontend-pc/src/styles/arco-ui/a-tree.less @@ -0,0 +1,23 @@ +.arco-tree-show-line .arco-tree-node-is-leaf:not(.arco-tree-node-is-tail) .arco-tree-node-indent::after, +.arco-tree-show-line .arco-tree-node-indent-block::before { + transform: translateX(0); + border-color: var(--color-border-3); + flex-shrink: 0; +} + +.arco-tree-node-minus-icon, +.arco-tree-node-plus-icon { + border: 1px solid var(--color-border-3); + box-sizing: border-box; + border-radius: 4px; +} + +.arco-tree-node-switcher-icon { + display: flex; + justify-content: center; + align-items: center; +} + +.arco-tree-node-title { + white-space: nowrap; +} \ No newline at end of file diff --git a/frontend-pc/src/styles/arco-ui/index.less b/frontend-pc/src/styles/arco-ui/index.less new file mode 100644 index 0000000..bcea62e --- /dev/null +++ b/frontend-pc/src/styles/arco-ui/index.less @@ -0,0 +1,46 @@ +// Arco 的 LESS 变量(供本项目组件覆写时引用,如 @transition-duration-3)。 +// 这是纯变量文件,不输出任何 CSS —— 原本由被移除的 @arco-themes/vue-gi-demo 带入。 +@import '@arco-design/web-vue/es/style/theme/index.less'; + +// 本项目主题:腾讯云 / TDesign 色板(覆盖 Arco 底层色板,上层自动跟随) +@import './xt-theme.less'; + +@import './a-alert.less'; +@import './a-card.less'; +@import './a-color-picker.less'; +@import './a-descriptions.less'; +@import './a-form.less'; +@import './a-input-number.less'; +@import './a-input.less'; +@import './a-menu.less'; +@import './a-message.less'; +@import './a-modal.less'; +@import './a-page-header.less'; +@import './a-pagination.less'; +@import './a-picker.less'; +@import './a-radio.less'; +@import './a-scrollbar.less'; +@import './a-space.less'; +@import './a-steps.less'; +@import './a-switch.less'; +@import './a-table.less'; +@import './a-tabs.less'; +@import './a-tree.less'; + +.app-menu-dark:extend(body[arco-theme='dark']) { + --color-menu-dark-bg: #222222; +} + +// 禁止文本复制 +.arco-link, +.arco-menu-title, +.arco-menu-item-inner, +.arco-tabs-tab-title, +.arco-breadcrumb-item, +.arco-checkbox-label { + user-select: none; +} + +.arco-menu .arco-menu-item { + display: inline-flex; +} \ No newline at end of file diff --git a/frontend-pc/src/styles/arco-ui/xt-theme.less b/frontend-pc/src/styles/arco-ui/xt-theme.less new file mode 100644 index 0000000..280d644 --- /dev/null +++ b/frontend-pc/src/styles/arco-ui/xt-theme.less @@ -0,0 +1,237 @@ +/** + * 腾讯云(TDesign)主题 + * ================================================================ + * 色值来源:TDesign 官方 light 主题 token + * https://github.com/Tencent/tdesign-common style/web/theme/_light.less + * 主色 = 腾讯蓝 #0052D9 + * + * ⚠️ 格式坑(写错整个主题不生效,且不报错): + * Arco 的颜色变量是 **逗号分隔的 RGB 三元组**,不是 #RRGGBB。 + * 定义:--arcoblue-6: 22,93,255; + * 消费:rgb(var(--primary-6)) + * 写成 `--arcoblue-6: #0052d9;` 的话,`rgb(#0052d9)` 是非法值, + * 浏览器会静默丢弃该声明 —— 表现是"主题完全不生效",但控制台没有报错。 + * + * 为什么只覆写底层色板就够了: + * Arco 的 --primary-N / --success-N / --warning-N / --danger-N / --link-N + * 分别是 --arcoblue-N / --green-N / --orange-N / --red-N 的别名, + * 而 --color-text-* / --color-border-* / --color-fill-* 又派生自 gray 色板。 + * 所以换底层色板 = 整套主题跟着换,不用逐个组件去改。 + * + * 索引对应关系(两套色板的「主色」下标不同,必须按角色映射,不能按下标硬对): + * Arco : -1 最浅底 · -5 hover · -6 主色 · -7 active · -8~-10 更深 + * TD : -1 最浅底 · -7 hover · -8 主色 · -9 active · -10 最深 + */ + +/** + * ⚠️ 必须同时写 `:root, body`,只写 `:root` 是不生效的。 + * + * Arco 把自己的色板定义在 **body** 选择器上(不是 :root)。 + * CSS 自定义属性是**按元素解析**的:body 上的 `--arcoblue-6` 会盖掉 + * 从 html(:root)继承下来的同名变量,哪怕我们的样式加载在后面。 + * 所以覆盖必须落在同一层级(body)才能赢。 + * + * 排查特征:`getComputedStyle(document.documentElement).getPropertyValue('--arcoblue-6')` + * 已经是新值,但按钮颜色没变 —— 就是这个原因。 + */ +:root, +body { + /* ── 品牌色:腾讯蓝 #0052D9 ────────────────────────── + TD brand: 1 #ecf2fe · 2 #d4e3fc · 3 #bbd3fb · 4 #96bbf8 + 5 #699ef5 · 6 #4787f0 · 7 #266fe8 · 8 #0052d9(主) + 9 #0034b5 · 10 #001f97 + Arco 9/10 由 TD 10 继续压暗推导(TD 官方只到 10 阶) */ + --arcoblue-1: 236, 242, 254; /* TD brand-1 #ecf2fe */ + --arcoblue-2: 212, 227, 252; /* TD brand-2 #d4e3fc */ + --arcoblue-3: 187, 211, 251; /* TD brand-3 #bbd3fb */ + --arcoblue-4: 150, 187, 248; /* TD brand-4 #96bbf8 */ + --arcoblue-5: 71, 135, 240; /* TD brand-6 #4787f0 — Arco 的 -5 是 hover */ + --arcoblue-6: 0, 82, 217; /* TD brand-8 #0052d9 ★ 主色 */ + --arcoblue-7: 0, 52, 181; /* TD brand-9 #0034b5 — Arco 的 -7 是 active */ + --arcoblue-8: 0, 31, 151; /* TD brand-10 #001f97 */ + --arcoblue-9: 0, 22, 122; /* 推导:由 #001f97 再压暗 */ + --arcoblue-10: 0, 18, 95; /* 推导 */ + + /* ── 成功色:TD success,主色 -5 #00a870 ───────────── */ + --green-1: 232, 248, 242; /* TD success-1 #e8f8f2 */ + --green-2: 188, 235, 220; /* TD success-2 #bcebdc */ + --green-3: 133, 219, 190; /* TD success-3 #85dbbe */ + --green-4: 103, 209, 173; /* 推导:TD 3↔4 之间,给 Arco -4 留一档 */ + --green-5: 72, 199, 156; /* TD success-4 #48c79c — hover */ + --green-6: 0, 168, 112; /* TD success-5 #00a870 ★ 主色 */ + --green-7: 7, 141, 92; /* TD success-6 #078d5c — active */ + --green-8: 6, 121, 69; /* TD success-7 #067945 */ + --green-9: 5, 99, 52; /* TD success-8 #056334 */ + --green-10: 4, 79, 42; /* TD success-9 #044f2a */ + + /* ── 警告色:TD warning,主色 -5 #ed7b2f ───────────── */ + --orange-1: 254, 243, 230; /* TD warning-1 #fef3e6 */ + --orange-2: 249, 224, 199; /* TD warning-2 #f9e0c7 */ + --orange-3: 247, 199, 151; /* TD warning-3 #f7c797 */ + --orange-4: 244, 182, 122; /* 推导:TD 3↔4 之间 */ + --orange-5: 242, 153, 95; /* TD warning-4 #f2995f — hover */ + --orange-6: 237, 123, 47; /* TD warning-5 #ed7b2f ★ 主色 */ + --orange-7: 211, 90, 33; /* TD warning-6 #d35a21 — active */ + --orange-8: 186, 67, 27; /* TD warning-7 #ba431b */ + --orange-9: 158, 54, 16; /* TD warning-8 #9e3610 */ + --orange-10: 132, 43, 11; /* TD warning-9 #842b0b */ + + /* ── 危险色:TD error,主色 -6 #e34d59(这一族与 Arco 下标一一对应)── */ + --red-1: 253, 236, 238; /* TD error-1 #fdecee */ + --red-2: 249, 215, 217; /* TD error-2 #f9d7d9 */ + --red-3: 248, 185, 190; /* TD error-3 #f8b9be */ + --red-4: 247, 141, 148; /* TD error-4 #f78d94 */ + --red-5: 243, 109, 120; /* TD error-5 #f36d78 — hover */ + --red-6: 227, 77, 89; /* TD error-6 #e34d59 ★ 主色 */ + --red-7: 201, 53, 63; /* TD error-7 #c9353f — active */ + --red-8: 177, 31, 38; /* TD error-8 #b11f26 */ + --red-9: 149, 17, 20; /* TD error-9 #951114 */ + --red-10: 104, 5, 6; /* TD error-10 #680506 */ + + /* ── 中性灰:TD gray(TD 有 14 阶,Arco 需要 10 阶,按明度对齐)── */ + --gray-1: 243, 243, 243; /* TD gray-1 #f3f3f3 */ + --gray-2: 231, 231, 231; /* TD gray-3 #e7e7e7 */ + --gray-3: 220, 220, 220; /* TD gray-4 #dcdcdc */ + --gray-4: 197, 197, 197; /* TD gray-5 #c5c5c5 */ + --gray-5: 166, 166, 166; /* TD gray-6 #a6a6a6 */ + --gray-6: 139, 139, 139; /* TD gray-7 #8b8b8b */ + --gray-7: 119, 119, 119; /* TD gray-8 #777777 */ + --gray-8: 94, 94, 94; /* TD gray-9 #5e5e5e */ + --gray-9: 56, 56, 56; /* TD gray-11 #383838 */ + --gray-10: 44, 44, 44; /* TD gray-12 #2c2c2c */ + + /* ── 文字:直接用 TD 的 rgba 写法,不经过 gray 色板 ── + (文字是最高频的视觉元素,不绕一层间接引用更可控) */ + --color-text-1: rgba(0, 0, 0, 90%); + --color-text-2: rgba(0, 0, 0, 60%); + --color-text-3: rgba(0, 0, 0, 40%); + --color-text-4: rgba(0, 0, 0, 26%); + + /* ── 描边:TD border-level-1 #e7e7e7 / level-2 #dcdcdc ── */ + --color-border-1: #f3f3f3; + --color-border-2: #e7e7e7; + --color-border-3: #dcdcdc; + --color-border-4: #c5c5c5; + + /* ── 填充 ─────────────────────────────────────────── */ + --color-fill-1: #f3f3f3; + --color-fill-2: #eee; + --color-fill-3: #e7e7e7; + --color-fill-4: #dcdcdc; + + /* ── 背景:页面 #f3f3f3、容器 #fff(TD 的 page / container)── */ + --color-bg-1: #fff; + --color-bg-2: #fff; + --color-bg-3: #fff; + --color-bg-4: #fff; + --color-bg-5: #f3f3f3; + --color-bg-white: #fff; + + /* ── 圆角:Arco 默认 2/4/8,调成更接近 TD 的 4/6/8 ── */ + --border-radius-small: 4px; + --border-radius-medium: 6px; + --border-radius-large: 8px; + + /* ── 主色 RGB 明文,供 rgb(var(--xt-primary)) 这类自定义场景用 ── */ + --xt-primary: 0, 82, 217; + --xt-primary-hover: 71, 135, 240; + --xt-primary-active: 0, 52, 181; + --xt-success: 0, 168, 112; + --xt-warning: 237, 123, 47; + --xt-danger: 227, 77, 89; + --xt-page-bg: #f3f3f3; + --xt-container-bg: #fff; + --xt-text-1: rgba(0, 0, 0, 90%); + --xt-text-2: rgba(0, 0, 0, 60%); + --xt-text-3: rgba(0, 0, 0, 40%); + + /* ──────────────────────────────────────────────────────────────── + 语义别名:显式覆盖,不走 Arco 的 --xxx-N: var(--palette-N) 间接层。 + 实测那一层在 body 上算出来的仍是 Arco 默认色,所以这里直接给值。 + ──────────────────────────────────────────────────────────────── */ + + /* 品牌(腾讯蓝) */ + --primary-1: 236, 242, 254; + --primary-2: 212, 227, 252; + --primary-3: 187, 211, 251; + --primary-4: 150, 187, 248; + --primary-5: 71, 135, 240; + --primary-6: 22,93,255; + --primary-7: 0, 52, 181; + --primary-8: 0, 31, 151; + --primary-9: 0, 22, 122; + --primary-10: 0, 18, 95; + + /* 成功 */ + --success-1: 232, 248, 242; + --success-2: 188, 235, 220; + --success-3: 133, 219, 190; + --success-4: 103, 209, 173; + --success-5: 72, 199, 156; + --success-6: 0, 168, 112; + --success-7: 7, 141, 92; + --success-8: 6, 121, 69; + --success-9: 5, 99, 52; + --success-10: 4, 79, 42; + + /* 警告 */ + --warning-1: 254, 243, 230; + --warning-2: 249, 224, 199; + --warning-3: 247, 199, 151; + --warning-4: 244, 182, 122; + --warning-5: 242, 153, 95; + --warning-6: 237, 123, 47; + --warning-7: 211, 90, 33; + --warning-8: 186, 67, 27; + --warning-9: 158, 54, 16; + --warning-10: 132, 43, 11; + + /* 危险 */ + --danger-1: 253, 236, 238; + --danger-2: 249, 215, 217; + --danger-3: 248, 185, 190; + --danger-4: 247, 141, 148; + --danger-5: 243, 109, 120; + --danger-6: 227, 77, 89; + --danger-7: 201, 53, 63; + --danger-8: 177, 31, 38; + --danger-9: 149, 17, 20; + --danger-10: 104, 5, 6; + + /* 链接色跟随品牌 */ + --link-1: 236, 242, 254; + --link-2: 212, 227, 252; + --link-3: 187, 211, 251; + --link-4: 150, 187, 248; + --link-5: 71, 135, 240; + --link-6: 22,93,255; + --link-7: 0, 52, 181; + --link-8: 0, 31, 151; + --link-9: 0, 22, 122; + --link-10: 0, 18, 95; + + /* 中性色(--color-neutral-N 由它派生,文字/描边/填充都跟着走) */ + --color-neutral-1: 243, 243, 243; + --color-neutral-2: 231, 231, 231; + --color-neutral-3: 220, 220, 220; + --color-neutral-4: 197, 197, 197; + --color-neutral-5: 166, 166, 166; + --color-neutral-6: 139, 139, 139; + --color-neutral-7: 119, 119, 119; + --color-neutral-8: 94, 94, 94; + --color-neutral-9: 56, 56, 56; + --color-neutral-10: 44, 44, 44; +} + +/** + * 暗色模式下保持同一主色体系。 + * Arco 通过 body[arco-theme='dark'] 切换,这里只覆盖品牌色与页面底色, + * 其余(文字/背景/描边)沿用 Arco 自己的暗色派生,避免手写一套不完整的暗色。 + */ +body[arco-theme='dark'] { + --arcoblue-6: 64, 128, 255; /* 暗色下主色提亮,保证对比度 */ + --arcoblue-5: 106, 161, 255; + --arcoblue-7: 22, 93, 255; + --xt-page-bg: #17171a; + --xt-container-bg: #232324; +} diff --git a/frontend-pc/src/styles/base.scss b/frontend-pc/src/styles/base.scss new file mode 100644 index 0000000..179d0c1 --- /dev/null +++ b/frontend-pc/src/styles/base.scss @@ -0,0 +1,53 @@ +* { + padding: 0; + margin: 0; +} + +html, +body, +#app { + height: 100%; + padding: 0; + margin: 0; +} + +html { + font-size: 14px; +} + +body { + --margin: 14px; + --padding: 16px; + overflow: hidden; + // font-family: "helvetica neue", helvetica, "pingfang sc", "hiragino sans gb", "microsoft yahei", "\5fae\8f6f\96c5\9ed1", "arial/sans-serif"; + color: var(--color-text-2); + // 页面底色:腾讯云 / TDesign 的 bg-color-page(--td-gray-color-1) + // 用变量而不是写死色值,暗色模式与后续换色都不用改这里 + background-color: var(--xt-page-bg, #f3f3f3); + text-rendering: optimizelegibility; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; +} + +body[arco-theme='dark'] { + background-color: #17171a; // body背景颜色-深色模式(Arco 暗色底) +} + +#app { + width: 100vw; + height: 100vh; + overflow: hidden; +} + +ul { + margin-bottom: 0; + list-style: none !important; +} + +a, +a:focus, +a:hover { + color: inherit; + text-decoration: none; + cursor: pointer; +} \ No newline at end of file diff --git a/frontend-pc/src/styles/css/transition.css b/frontend-pc/src/styles/css/transition.css new file mode 100644 index 0000000..faa4a50 --- /dev/null +++ b/frontend-pc/src/styles/css/transition.css @@ -0,0 +1,163 @@ +/* fade */ +.fade-enter-active, +.fade-leave-active { + transition: opacity 0.3s ease-in-out; +} + +.fade-enter-from, +.fade-leave-to { + opacity: 0; +} + +/* fade-slide */ +.fade-slide-leave-active, +.fade-slide-enter-active { + transition: all 0.3s; +} + +.fade-slide-enter-from { + opacity: 0; + transform: translateX(-30px); +} + +.fade-slide-leave-to { + opacity: 0; + transform: translateX(30px); +} + +/* fade-bottom */ +.fade-bottom-enter-active, +.fade-bottom-leave-active { + transition: opacity 0.25s, transform 0.3s; +} + +.fade-bottom-enter-from { + opacity: 0; + transform: translateY(-10%); +} + +.fade-bottom-leave-to { + opacity: 0; + transform: translateY(10%); +} + +/* fade-scale */ +.fade-scale-leave-active, +.fade-scale-enter-active { + transition: all 0.28s; +} + +.fade-scale-enter-from { + opacity: 0; + transform: scale(1.2); +} + +.fade-scale-leave-to { + opacity: 0; + transform: scale(0.8); +} + +/* zoom-fade */ +.zoom-fade-enter-active, +.zoom-fade-leave-active { + transition: transform 0.2s, opacity 0.3s ease-out; +} + +.zoom-fade-enter-from { + opacity: 0; + transform: scale(0.92); +} + +.zoom-fade-leave-to { + opacity: 0; + transform: scale(1.06); +} + +/* zoom-out */ +.zoom-out-enter-active, +.zoom-out-leave-active { + transition: opacity 0.1s ease-in-out, transform 0.15s ease-out; +} + +.zoom-out-enter-from, +.zoom-out-leave-to { + opacity: 0; + transform: scale(0); +} + +/* elementUI 的 Dialog 动画 */ +@keyframes dialog-fade-in { + 0% { + opacity: 0; + transform: translate3d(0, -20px, 0); + } + + 100% { + opacity: 1; + transform: translate3d(0, 0, 0); + } +} + +@keyframes dialog-fade-out { + 0% { + opacity: 1; + transform: translate3d(0, 0, 0); + } + + 100% { + opacity: 0; + transform: translate3d(0, -20px, 0); + } +} + +@keyframes modal-fade-in { + 0% { + opacity: 0; + } + + 100% { + opacity: 1; + } +} + +@keyframes modal-fade-out { + 0% { + opacity: 1; + } + + 100% { + opacity: 0; + } +} + +.el-dialog-enter-active { + animation: dialog-fade-in 0.3s; +} + +.el-dialog-leave-active { + animation: dialog-fade-out 0.3s; +} + +.el-mask-enter-active { + animation: modal-fade-in 0.3s; +} + +.el-mask-leave-active { + animation: modal-fade-out 0.3s; +} + +/* el-fade-bottom */ +.el-fade-enter-active, +.el-fade-leave-active { + transition: all 0.3s; +} + +.el-fade-enter-from { + opacity: 0; + transform: scale(1.1, 0.2); +} + +.el-fade-leave-to { + opacity: 0; + transform: scale(1.1, 0.2); +} diff --git a/frontend-pc/src/styles/editor.scss b/frontend-pc/src/styles/editor.scss new file mode 100644 index 0000000..ce5b384 --- /dev/null +++ b/frontend-pc/src/styles/editor.scss @@ -0,0 +1,37 @@ +body { + --w-e-textarea-bg-color: #fff; + --w-e-textarea-color: #333; + --w-e-textarea-border-color: #ccc; + --w-e-textarea-slight-border-color: #e8e8e8; + --w-e-textarea-slight-color: #d4d4d4; + --w-e-textarea-slight-bg-color: #f5f2f0; + --w-e-textarea-selected-border-color: #b4d5ff; + --w-e-textarea-handler-bg-color: #4290f7; + --w-e-toolbar-color: #595959; + --w-e-toolbar-bg-color: #fff; + --w-e-toolbar-active-color: #333; + --w-e-toolbar-active-bg-color: #f1f1f1; + --w-e-toolbar-disabled-color: #999; + --w-e-toolbar-border-color: #e8e8e8; + --w-e-modal-button-bg-color: #fafafa; + --w-e-modal-button-border-color: #d9d9d9; +} + +body[arco-theme='dark'] { + --w-e-textarea-bg-color: var(--color-bg-2); + --w-e-textarea-color: var(--color-text-1); + --w-e-textarea-border-color: var(--color-border); + --w-e-textarea-slight-border-color: var(--color-border-2); + --w-e-textarea-slight-color: var(--color-text-4); + --w-e-textarea-slight-bg-color: var(--color-bg-2); + --w-e-textarea-selected-border-color: var(--color-primary-light-1); + --w-e-textarea-handler-bg-color: var(--color-primary-light-4); + --w-e-toolbar-color: #ccc; + --w-e-toolbar-bg-color: var(--color-bg-2); + --w-e-toolbar-active-color: #fff; + --w-e-toolbar-active-bg-color: var(--color-secondary-active); + --w-e-toolbar-disabled-color: var(--color-text-4); + --w-e-toolbar-border-color: var(--color-border); + --w-e-modal-button-bg-color: var(--color-fill-2); + --w-e-modal-button-border-color: var(--color-border); +} diff --git a/frontend-pc/src/styles/global.scss b/frontend-pc/src/styles/global.scss new file mode 100644 index 0000000..0141919 --- /dev/null +++ b/frontend-pc/src/styles/global.scss @@ -0,0 +1,335 @@ +/* 全局样式 */ +@use './var.scss' as *; + +// hover按钮 +.g-hover-btn { + background-color: transparent; + border: 0 !important; + + &:hover { + background: var(--color-secondary-hover) !important; + } + + &:active { + background: var(--color-secondary-active) !important; + } +} + +// 卡片组件,使body的高度自动撑满 +.g-card { + box-sizing: border-box; + display: flex; + flex-direction: column; + overflow: hidden; + + .arco-card-body { + box-sizing: border-box; + display: flex; + flex: 1; + flex-direction: column; + overflow: hidden; + } +} + +// 页面卡片,右侧有返回按钮 +.g-page-card { + box-sizing: border-box; + display: flex; + flex: 1; + flex-direction: column; + margin: var(--margin); + overflow: hidden; + + .arco-card-body { + box-sizing: border-box; + display: flex; + flex: 1; + flex-direction: column; + overflow: hidden; + } +} + +.g-gradient-bg { + background-image: linear-gradient(to right top, transparent, transparent, + color-mix(in oklab, rgb(var(--primary-6)) 12%, transparent), + color-mix(in oklab, rgb(var(--primary-6)) 12%, transparent)); +} + +// 卡片标题,标题左侧的伪类样式 +.g-card-title { + .arco-card-header-title::before { + position: absolute; + top: 50%; + left: 0; + width: 4px; + height: 50%; + content: ''; + background-color: rgb(var(--warning-5)); + border-radius: 0 4px 4px 0; + transform: translateY(-50%); + } +} + +// 使用场景,页面内容超出高度会自动滚动 +.g-page { + box-sizing: border-box; + flex: 1; + padding: var(--margin); + overflow-y: auto; +} + +// 表格类名 高度自适应 +.g-table { + display: flex; + flex: 1; + flex-direction: column; + overflow: hidden; + + >.arco-spin { + height: auto; + max-height: 100%; + overflow: hidden; + } +} + +// 表格类名,高度自适应,分页始终固定在最底部 +.g-full-table { + display: flex; + flex: 1; + flex-direction: column; + overflow: hidden; + + .arco-table-container { + flex: 1; + overflow: hidden; + } + + .arco-table-scroll-y { + border-right: 1px solid var(--color-border-1); + } + + .arco-table-tr .arco-table-td:last-child, + .arco-table-tr .arco-table-th:last-child { + border-right: none; + } + + .arco-table-content { + .arco-scrollbar { + &:last-child { + flex: 1; + + &:not(.arco-scrollbar-both) { + .arco-table-element { + border-bottom: 1px solid var(--color-border-1); + } + } + } + } + } +} + +.g-full-tabs { + display: flex; + flex: 1; + flex-direction: column; + + .arco-tabs-content { + flex: 1; + padding-top: 0; + + .arco-tabs-content-list { + height: 100%; + } + + .arco-tabs-pane { + height: 100%; + overflow-y: auto; + } + } +} + +// 编辑表格,表头的必填红色星号 +.g-column-require { + .arco-table-th-title { + &::before { + padding-right: 2px; + color: red; + content: '*'; + } + } +} + +.g-full-column { + box-sizing: border-box; + display: flex; + flex: 1; + flex-direction: column; + overflow: hidden; +} + +.g-badge-primary { + .arco-badge-number { + background-color: rgb(var(--primary-5)); + } +} + +.g-badge-success { + .arco-badge-number { + background-color: rgb(var(--success-5)); + } +} + +.g-badge-warning { + .arco-badge-number { + background-color: rgb(var(--warning-5)); + } +} + +.g-row-tool { + gap: 8px; + margin-bottom: 12px; +} + +// 以下这些类名要放到下面,涉及到css权重优先级问题 +.g-mt { + margin-top: var(--margin); +} + +.g-mb { + margin-bottom: var(--margin); +} + +.g-ml { + margin-left: var(--margin); +} + +.g-mr { + margin-right: var(--margin); +} + +.g-mx { + margin-right: var(--margin); + margin-left: var(--margin); +} + +.g-my { + margin-top: var(--margin); + margin-bottom: var(--margin); +} + +.g-m0 { + margin: 0; +} + +.g-pt { + padding-top: var(--margin); +} + +.g-pb { + padding-bottom: var(--margin); +} + +.g-pl { + padding-left: var(--margin); +} + +.g-pr { + padding-right: var(--margin); +} + +.g-px { + padding-right: var(--padding); + padding-left: var(--padding); +} + +.g-py { + padding-top: var(--padding); + padding-bottom: var(--padding); +} + +.g-p0 { + padding: 0; +} + +.g-round { + border-radius: 100px; +} + +.w-full { + width: 100%; +} + +.h-full { + height: 100%; +} + +.overflow-hidden { + overflow: hidden; +} + +.flex-1 { + flex: 1; +} + +// 一般结合使用 +.flex-column { + flex-direction: column; + overflow: hidden; +} + +// 通用外边距 +.g-margin { + margin: var(--margin); +} + +// 通用内边距 +.g-padding { + padding: var(--padding); +} + +.g-select-input { + flex-shrink: 0; + width: 100px; +} + +.g-search-input { + flex-shrink: 0; + width: 250px; +} + +.g-line-1 { + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} + +.g-line-2 { + -webkit-line-clamp: 2; + line-clamp: 2; +} + +.g-line-3 { + -webkit-line-clamp: 3; + line-clamp: 3; +} + +.g-line-4 { + -webkit-line-clamp: 4; + line-clamp: 4; +} + +.g-line-5 { + -webkit-line-clamp: 5; + line-clamp: 5; +} + +.g-line-2, +.g-line-3, +.g-line-4, +.g-line-5 { + display: -webkit-box; /* 弹性伸缩盒 */ + overflow: hidden; + text-overflow: ellipsis; + word-break: break-all; + -webkit-box-orient: vertical; /* 设置伸缩盒子元素排列方式 */ +} \ No newline at end of file diff --git a/frontend-pc/src/styles/index.scss b/frontend-pc/src/styles/index.scss new file mode 100644 index 0000000..8e0471c --- /dev/null +++ b/frontend-pc/src/styles/index.scss @@ -0,0 +1,17 @@ +// 基础样式 +@use './base.scss'; + +// 全局类名样式 +@use './global.scss'; + +// 自定义原生滚动条样式 +@use './scrollbar-reset.scss'; + +// 自定义 nprogress 插件进度条颜色 +@use './nprogress.scss'; + +// 富文本的css主题颜色变量 +@use './editor.scss'; + +// 动画类名 +@use './animated.scss'; \ No newline at end of file diff --git a/frontend-pc/src/styles/mixin.scss b/frontend-pc/src/styles/mixin.scss new file mode 100644 index 0000000..0485c99 --- /dev/null +++ b/frontend-pc/src/styles/mixin.scss @@ -0,0 +1,21 @@ +@mixin scrollbar($size: 6px, $color: #d9d9d9) { + &::-webkit-scrollbar-thumb { + background-color: $color; + border-radius: $size; + } + + &::-webkit-scrollbar-thumb:hover { + background-color: $color; + border-radius: $size; + } + + &::-webkit-scrollbar { + width: $size; + height: $size; + } + + &::-webkit-scrollbar-track-piece { + background-color: rgb(0 0 0 / 0%); + border-radius: 0; + } +} diff --git a/frontend-pc/src/styles/nprogress.scss b/frontend-pc/src/styles/nprogress.scss new file mode 100644 index 0000000..000dc90 --- /dev/null +++ b/frontend-pc/src/styles/nprogress.scss @@ -0,0 +1,4 @@ +// nprogress插件进度条颜色 +#nprogress .bar { + background: rgb(var(--primary-6), 0.5) !important; //自定义颜色 +} diff --git a/frontend-pc/src/styles/scrollbar-reset.scss b/frontend-pc/src/styles/scrollbar-reset.scss new file mode 100644 index 0000000..87909f9 --- /dev/null +++ b/frontend-pc/src/styles/scrollbar-reset.scss @@ -0,0 +1,22 @@ +/* 美化滚动条 */ +::-webkit-scrollbar { + width: 6px; + height: 6px; +} + +::-webkit-scrollbar-thumb { + width: 6px; + background-color: var(--color-neutral-3); + border-radius: 6px; +} + +::-webkit-scrollbar-thumb:hover { + width: 6px; + background-color: var(--color-neutral-4); +} + +::-ms-scrollbar-thumb { + width: 6px; + background-color: var(--color-neutral-3); + border-radius: 6px; +} diff --git a/frontend-pc/src/styles/var.scss b/frontend-pc/src/styles/var.scss new file mode 100644 index 0000000..5cc6e17 --- /dev/null +++ b/frontend-pc/src/styles/var.scss @@ -0,0 +1,3 @@ +// 自定义全局scss变量 +// 建议优先使用arco内置的全局css变量来开发,可以有效提高效率和团队协作 +// $color-theme: rgb(var(--primary-6)); \ No newline at end of file diff --git a/frontend-pc/src/types/api.d.ts b/frontend-pc/src/types/api.d.ts new file mode 100644 index 0000000..fc0a469 --- /dev/null +++ b/frontend-pc/src/types/api.d.ts @@ -0,0 +1,13 @@ +/** 接口返回数据格式 */ +interface ApiRes { + code: number + data: T + message: string + success: boolean +} + +/** 分页返回的数据格式 */ +interface PageRes { + records: T + total: number +} diff --git a/frontend-pc/src/types/app.d.ts b/frontend-pc/src/types/app.d.ts new file mode 100644 index 0000000..860af56 --- /dev/null +++ b/frontend-pc/src/types/app.d.ts @@ -0,0 +1,14 @@ +declare namespace App { + /** 系统配置 */ + interface SettingConfig { + themeColor: string // 主题色 + layout: 'left' | 'mix' | 'top' | 'columns' + tabStyle: 'card' | 'card-gutter' | 'rounded' | 'custom1' | 'custom2' // 页签风格 + isTabVisible: boolean // 是否显示页签 + transitionName: 'zoom-fade' | 'slide-dynamic-origin' | 'fade-slide' | 'fade' | 'fade-bottom' | 'fade-scale' // 动画类名 + isTransitionEnabled: boolean // 是否启用页面切换动画 + isMenuCollapsed: boolean // 左侧菜单折叠状态 + isMenuAccordion: boolean // 左侧菜单手风琴效果 + isMenuDark: boolean // 菜单深色模式 + } +} diff --git a/frontend-pc/src/types/env.d.ts b/frontend-pc/src/types/env.d.ts new file mode 100644 index 0000000..72a6327 --- /dev/null +++ b/frontend-pc/src/types/env.d.ts @@ -0,0 +1,12 @@ +/// + +/** 声明环境变量的类型 */ +interface ImportMetaEnv { + readonly VITE_API_PREFIX: string + readonly VITE_API_BASE_URL: string + readonly VITE_BASE: string +} + +interface ImportMeta { + readonly env: ImportMetaEnv +} diff --git a/frontend-pc/src/types/global.d.ts b/frontend-pc/src/types/global.d.ts new file mode 100644 index 0000000..99432f0 --- /dev/null +++ b/frontend-pc/src/types/global.d.ts @@ -0,0 +1,23 @@ +interface AnyObject { + [key: string]: unknown +} + +interface Options { + value: unknown + label: string +} + +interface NodeOptions extends Options { + children?: NodeOptions[] +} + +type TimeRanger = [string, string] + +/** 系统管理通用状态 与字典 STATUS 一致,'0' 禁用 '1' 启用 */ +type Status = '0' | '1' + +/** 性别 与字典 GENDER 一致,'1' 男 '2' 女 '3' 保密 */ +type Gender = '1' | '2' | '3' + +/** 接口需要的分页参数 */ +type Pagination = { page: number, size: number } diff --git a/frontend-pc/src/types/module.d.ts b/frontend-pc/src/types/module.d.ts new file mode 100644 index 0000000..07078c0 --- /dev/null +++ b/frontend-pc/src/types/module.d.ts @@ -0,0 +1,3 @@ +declare module 'nprogress' +declare module '@arco-design/color' +declare module 'qs' diff --git a/frontend-pc/src/types/router.d.ts b/frontend-pc/src/types/router.d.ts new file mode 100644 index 0000000..4423b27 --- /dev/null +++ b/frontend-pc/src/types/router.d.ts @@ -0,0 +1,43 @@ +import 'vue-router' + +declare module 'vue-router' { + interface RouteMeta { + /** 设置该路由在侧边栏和面包屑中展示的名字 */ + title?: string + /** 设置该路由的图标 */ + icon?: string + /** 默认false, 设置true的时候该路由不会在侧边栏出现 */ + hidden?: boolean + /** 默认true, 如果设置为false, 则不会在面包屑中显示 */ + breadcrumb?: boolean + /** 默认true, 如果设置为false, 它则不会显示在Tab栏中 */ + showInTabs?: boolean + /** 默认false, 如果设置为true, 它则会固定在Tab栏中, 例如首页 */ + affix?: boolean + /** + * 当一个路由下面的children声明的路由大于1个时, 自动会变成嵌套的模式 + * 只有一个时, 会将那个子路由当做根路由显示在侧边栏 + * 若想不管路由下面的children声明的个数都显示你的根路由 + * 可以设置alwaysShow: true, 这样就会忽略之前定义的规则, 一直显示根路由 + */ + alwaysShow?: boolean + /** + * 示例: activeMenu: "/xxx/xxx" + * 当设置了该属性进入路由时, 则会高亮activeMenu属性对应的侧边栏 + * 该属性适合使用在有hidden: true属性的路由上 + */ + activeMenu?: string + /** + * 是否缓存该路由页面 + * 默认为false, 为true时代表需要缓存, 此时该路由和该页面都需要设置一致的name + */ + keepAlive?: boolean + /** 默认路由参数 */ + query?: string + noShowingChildren?: boolean + /** 设置该路由进入的权限, 支持多个权限叠加 */ + roles?: string[] + /** 排序 */ + sort?: number + } +} diff --git a/frontend-pc/src/types/shims-vue.d.ts b/frontend-pc/src/types/shims-vue.d.ts new file mode 100644 index 0000000..aafef95 --- /dev/null +++ b/frontend-pc/src/types/shims-vue.d.ts @@ -0,0 +1,8 @@ +/// + +declare module '*.vue' { + import type { DefineComponent } from 'vue' + // eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/ban-types + const component: DefineComponent<{}, {}, any> + export default component +} diff --git a/frontend-pc/src/types/tool.d.ts b/frontend-pc/src/types/tool.d.ts new file mode 100644 index 0000000..0383038 --- /dev/null +++ b/frontend-pc/src/types/tool.d.ts @@ -0,0 +1,14 @@ +// 辅助类型:获取所有类型的键的联合 +type AllKeys = T extends any ? keyof T : never + +// 辅助类型:获取属性在所有类型中的联合类型 +type UnionType = T extends any + ? K extends keyof T + ? T[K] + : never + : never + +// 合并多个类型,同名属性转为联合类型 +export type MergeMultiple = { + [K in AllKeys]?: UnionType +} diff --git a/frontend-pc/src/utils/auth.ts b/frontend-pc/src/utils/auth.ts new file mode 100644 index 0000000..6dee588 --- /dev/null +++ b/frontend-pc/src/utils/auth.ts @@ -0,0 +1,39 @@ +/** Token 存储键名 */ +const TOKEN_KEY = 'token' + +/** + * 检查用户是否已登录 + * @description 通过检查 localStorage 中是否存在 token 来判断用户登录状态 + * @returns {boolean} 是否已登录 + */ +const isLogin = () => { + return !!localStorage.getItem(TOKEN_KEY) +} + +/** + * 获取用户 Token + * @description 从 localStorage 中获取存储的 token + * @returns {string | null} 用户 token,未登录时返回 null + */ +const getToken = () => { + return localStorage.getItem(TOKEN_KEY) +} + +/** + * 设置用户 Token + * @description 将用户 token 存储到 localStorage 中 + * @param token - 用户 token + */ +const setToken = (token: string) => { + localStorage.setItem(TOKEN_KEY, token) +} + +/** + * 清除用户 Token + * @description 从 localStorage 中移除用户 token + */ +const clearToken = () => { + localStorage.removeItem(TOKEN_KEY) +} + +export { clearToken, getToken, isLogin, setToken } diff --git a/frontend-pc/src/utils/createSelectDialog.ts b/frontend-pc/src/utils/createSelectDialog.ts new file mode 100644 index 0000000..9e5cece --- /dev/null +++ b/frontend-pc/src/utils/createSelectDialog.ts @@ -0,0 +1,103 @@ +import type { Component } from 'vue' +import { Message, Modal } from '@arco-design/web-vue' +import { h, ref } from 'vue' + +interface CreateSelectDialogParams { + title: string + component: Component + componentProps?: Record + tip?: string +} + +/** + * 选择弹窗配置选项接口 + * @template T 选中数据的类型 + */ +interface SelectDialogOptions { + /** 弹窗标题(会覆盖创建参数中的title) */ + title?: string + /** 是否允许多选 */ + multiple?: boolean + /** 查询参数,通常用于初始化数据 */ + queryParams?: Record + /** 传递给组件的额外属性 */ + componentProps?: Record + /** 点击确定按钮后的回调函数 */ + onOk?: (data: T) => void + /** 点击确定前的校验函数,返回Promise决定是否允许确定 */ + onBeforeOk?: (data: T) => Promise +} + +/** + * 创建一个选择类型的弹窗工厂函数 + * + * 该函数返回一个创建特定类型选择弹窗的方法,适用于需要从列表中选择数据的场景。 + * 内部使用Vue的createVNode动态渲染组件,并通过ref获取组件实例的方法。 + * + * @template T 选中数据的类型 + * @param {CreateSelectDialogParams} params 创建弹窗所需的基本参数 + * @returns {(options: SelectDialogOptions) => void} 可配置的弹窗创建函数 + * + * @example + * // 创建一个用户选择弹窗 + * const selectUserDialog = createSelectDialog({ + * title: '选择用户', + * component: UserSelectComponent, + * tip: '请至少选择一个用户' + * }) + * + * // 打开弹窗并处理选择结果 + * selectUserDialog({ + * multiple: true, + * queryParams: { status: 'active' }, + * onOk: (selectedUsers) => { + * console.log('已选择用户:', selectedUsers) + * } + * }) + */ +export const createSelectDialog = (params: CreateSelectDialogParams) => { + return (options: SelectDialogOptions) => { + const TableRef = ref() + Modal.open({ + // 优先使用options中的title,否则使用params中的title + title: options.title || params.title, + // 动态渲染传入的组件,设置ref引用并合并属性 + content: () => h(params.component, { + ref: (e: any) => (TableRef.value = e), + multiple: options.multiple, + queryParams: options.queryParams, + ...params.componentProps, + ...options.componentProps + }), + // 设置弹窗宽度自适应 + width: 'calc(100% - 20px)', + modalStyle: { maxWidth: '1000px' }, + bodyStyle: { overflow: 'hidden', height: '500px', padding: 0 }, + onBeforeOk: async () => { + // 检查组件是否暴露了必要的getSelectedData方法 + if (!TableRef.value?.getSelectedData) { + Message.warning('组件必须暴露getSelectedData方法') + return false + } + + // 获取选中的数据 + const data = TableRef.value?.getSelectedData?.() || [] + + // 验证是否选择了数据 + if (!data.length) { + Message.warning(params.tip || '请选择数据') + return false + } + + // 如果提供了前置校验函数,则调用并根据结果决定是否继续 + if (options?.onBeforeOk) { + return await options.onBeforeOk(data) + } + + // 调用确定回调函数,传递选中的数据 + options.onOk?.(data) + return true + } + }) + } +} diff --git a/frontend-pc/src/utils/downloadFile.ts b/frontend-pc/src/utils/downloadFile.ts new file mode 100644 index 0000000..bc3baa7 --- /dev/null +++ b/frontend-pc/src/utils/downloadFile.ts @@ -0,0 +1,76 @@ +/** + * 根据文件url获取文件名 + * @param url 文件url + */ +function getFileName(url: string) { + const num = url.lastIndexOf('/') + 1 + let fileName = url.substring(num) + // 把参数和文件名分割开 + fileName = decodeURI(fileName.split('?')[0]) + return fileName +} + +/** + * 根据文件地址下载文件 + * @param {*} sUrl + */ +export function downloadByUrl({ + url, + target = '_blank', + fileName +}: { + url: string + target?: '_self' | '_blank' + fileName?: string +}): Promise { + // 是否同源 + const isSameHost = new URL(url).host === location.host + return new Promise((resolve, reject) => { + if (isSameHost) { + const link = document.createElement('a') + link.href = url + link.target = target + + if (link.download !== undefined) { + link.download = fileName || getFileName(url) + } + + if (document.createEvent) { + const e = document.createEvent('MouseEvents') + e.initEvent('click', true, true) + link.dispatchEvent(e) + return resolve(true) + } + + if (!url.includes('?')) { + url += '?download' + } + + window.open(url, target) + return resolve(true) + } else { + const canvas = document.createElement('canvas') + const img = document.createElement('img') + img.setAttribute('crossOrigin', 'Anonymous') + img.src = url + img.onload = () => { + canvas.width = img.width + canvas.height = img.height + const context = canvas.getContext('2d')! + context.drawImage(img, 0, 0, img.width, img.height) + // window.navigator.msSaveBlob(canvas.msToBlob(),'image.jpg'); + // saveAs(imageDataUrl, '附件'); + canvas.toBlob((blob) => { + const link = document.createElement('a') + if (!blob) return + link.href = window.URL.createObjectURL(blob) + link.download = getFileName(url) + link.click() + URL.revokeObjectURL(link.href) + resolve(true) + }, 'image/jpeg') + } + img.onerror = (e) => reject(e) + } + }) +} diff --git a/frontend-pc/src/utils/file.ts b/frontend-pc/src/utils/file.ts new file mode 100644 index 0000000..8848413 --- /dev/null +++ b/frontend-pc/src/utils/file.ts @@ -0,0 +1,71 @@ +/** 文件类型列表项接口 */ +export interface FileTypeListItem { + /** 显示名称 */ + name: string + /** 类型值 */ + value: string + /** 菜单图标 */ + menuIcon: string + /** 文件图标 */ + icon: string +} + +/** + * 文件类型列表 + * @description 定义系统中支持的文件类型分类 + */ +export const FILE_TYPE_LIST: FileTypeListItem[] = [ + { name: '全部', value: '0', menuIcon: 'file-all', icon: 'icon-stamp' }, + { name: '图片', value: '1', menuIcon: 'file-image', icon: 'icon-file-image' }, + { name: '文档', value: '2', menuIcon: 'file-txt', icon: 'icon-file' }, + { name: '视频', value: '3', menuIcon: 'file-video', icon: 'icon-video-camera' }, + { name: '音频', value: '4', menuIcon: 'file-music', icon: 'icon-file-audio' }, + { name: '其他', value: '5', menuIcon: 'file-other', icon: 'icon-bulb' } +] + +/** 文件扩展名与图标映射接口 */ +export interface FileExtendNameIconMap { + [key: string]: string +} + +/** + * 文件类型图标映射 + * @description 定义文件扩展名与对应图标的映射关系 + */ +export const FILE_ICON_MAP: FileExtendNameIconMap = { + // 音频文件 + mp3: 'file-music', + // 视频文件 + mp4: 'file-video', + // 文件夹 + dir: 'file-dir', + // Office 文件 + ppt: 'file-ppt', + doc: 'file-wps', + docx: 'file-wps', + xls: 'file-excel', + xlsx: 'file-excel', + // 文本文件 + txt: 'file-txt', + // 压缩文件 + rar: 'file-rar', + zip: 'file-zip', + // 网页文件 + html: 'file-html', + css: 'file-css', + js: 'file-js', + // 未知文件 + other: 'file-other' +} + +/** + * 图片文件类型列表 + * @description 系统支持的图片文件扩展名 + */ +export const IMAGE_TYPES = ['jpg', 'png', 'gif', 'jpeg'] + +/** + * Office 文件类型列表 + * @description 系统支持的 Office 文件扩展名 + */ +export const OFFICE_TYPES = ['ppt', 'pptx', 'doc', 'docx', 'xls', 'xlsx'] diff --git a/frontend-pc/src/utils/http.ts b/frontend-pc/src/utils/http.ts new file mode 100644 index 0000000..79ee043 --- /dev/null +++ b/frontend-pc/src/utils/http.ts @@ -0,0 +1,216 @@ +import type { AxiosInstance, AxiosRequestConfig, AxiosResponse } from 'axios' +import { Message, Notification } from '@arco-design/web-vue' +import axios from 'axios' +import NProgress from 'nprogress' +import router from '@/router' +import { getToken } from '@/utils/auth' +import 'nprogress/nprogress.css' + +// 配置 NProgress +NProgress.configure({ showSpinner: false }) + +/** + * 本项目后端响应契约(《03-接口设计》§1.3): + * + * { code: 0 | 4xxxx | 5xxxx, msg: string, data: T } + * + * 与模板内部的 ApiRes 契约不同,差别有三处,都在下面这层适配里统一处理: + * + * | 维度 | 本项目后端 | 模板内部约定 | + * |----------|--------------------|--------------------| + * | 成功判定 | code === 0 | success: boolean | + * | 提示文案 | msg | message | + * | 分页列表 | { list, total } | { records, total } | + * + * ⚠️ 适配放在这里而不是逐个改 api 文件:这三条是全后端统一的契约, + * 分散到每个 listAPI 去转换,漏一个就是一个"列表永远空着"的 bug。 + */ + +/** 本项目后端的成功码 */ +const CODE_SUCCESS = 0 +/** 未登录 / token 失效 */ +const CODE_UNAUTHORIZED = 40101 +/** 无权限(含跨租户) */ +const CODE_FORBIDDEN = 40301 +/** 限流 */ +const CODE_RATE_LIMITED = 42901 + +/** 传输层 HTTP 状态码文案(业务错误不走这里,见下方响应拦截器) */ +const StatusCodeMessage: Record = { + 400: '请求错误(400)', + 401: '未授权,请重新登录(401)', + 403: '拒绝访问(403)', + 404: '请求出错(404)', + 408: '请求超时(408)', + 500: '服务器错误(500)', + 501: '服务未实现(501)', + 502: '网络错误(502)', + 503: '服务不可用(503)', + 504: '网络超时(504)' +} + +/** 创建 axios 实例 */ +const http: AxiosInstance = axios.create({ + baseURL: import.meta.env.VITE_API_PREFIX, + timeout: 30 * 1000 +}) + +/** + * 把分页字段补一份 records。 + * 后端返回 { list, total },模板的 useTable / PageRes 读 records。 + * 补而不替:list 原样保留,两边都能取到,避免破坏原有调用。 + */ +function normalizePageShape(payload: unknown): unknown { + if (payload && typeof payload === 'object' && !Array.isArray(payload)) { + const obj = payload as Record + if (Array.isArray(obj.list) && typeof obj.total === 'number' && obj.records === undefined) { + return { ...obj, records: obj.list } + } + } + return payload +} + +/** 跳登录页(只跳一次,避免一个页面多个请求同时失效时反复跳) */ +let redirecting = false +function toLogin() { + if (redirecting) return + redirecting = true + const back = encodeURIComponent(router.currentRoute.value.fullPath) + router.replace(`/login?redirect=${back}`).finally(() => { + setTimeout(() => { + redirecting = false + }, 800) + }) +} + +// 请求拦截器 +http.interceptors.request.use( + (config: AxiosRequestConfig) => { + NProgress.start() + const token = getToken() + if (token) { + if (!config.headers) { + config.headers = {} + } + // 本项目后端用标准的 Authorization: Bearer(《03-接口设计》§1.2), + // 不是模板默认的 token 自定义头 + config.headers.Authorization = `Bearer ${token}` + } + return config + }, + (error) => { + NProgress.done() + return Promise.reject(error) + } +) + +// 响应拦截器 +http.interceptors.response.use( + (response: AxiosResponse) => { + NProgress.done() + const raw = response.data + + // 非信封响应(文件流等)直接放行 + if (!raw || typeof raw.code !== 'number') { + return response + } + + // 未登录:清 token 并跳登录 + if (raw.code === CODE_UNAUTHORIZED) { + toLogin() + return Promise.reject(new Error(raw.msg || '登录已失效,请重新登录')) + } + + // 业务成功:适配成模板的 ApiRes 形状 + if (raw.code === CODE_SUCCESS) { + response.data = { + code: 200, + success: true, + message: raw.msg || 'success', + data: normalizePageShape(raw.data) + } + return response + } + + // 业务失败:按文案长度选择 Message(短)或 Notification(长),与原实现一致 + const message = raw.msg || '服务器端错误' + if (message.length <= 15) { + Message.error(message) + } else { + Notification.error(message) + } + // 把后端业务码附在错误上,便于调用方按码分支(如租户重名) + const err = new Error(message) as Error & { bizCode?: number, bizData?: unknown } + err.bizCode = raw.code + err.bizData = raw.data + return Promise.reject(err) + }, + (error) => { + NProgress.done() + Message.clear() + const response = Object.assign({}, error.response) + + // 401/403/429 这三个后端用真实 HTTP 状态码(《03-接口设计》§1.3) + if (response?.status === 401) { + toLogin() + } else { + const msg = StatusCodeMessage[response?.status] || '系统异常, 请检查网络或联系管理员!' + Message.error(msg) + } + + const err = new Error(error.message) as Error & { status?: number } + err.status = response?.status + return Promise.reject(err) + } +) + +/** 封装请求方法 */ +const request = (config: AxiosRequestConfig): Promise> => { + return new Promise((resolve, reject) => { + http + .request(config) + .then((res: AxiosResponse) => resolve(res.data)) + .catch((err: { message: string }) => reject(err)) + }) +} + +/** GET 请求 */ +const get = (url: string, params?: object, config?: AxiosRequestConfig): Promise> => { + return request({ + method: 'get', + url, + params, + ...config + }) +} + +/** POST 请求 */ +const post = (url: string, params?: object, config?: AxiosRequestConfig): Promise> => { + return request({ + method: 'post', + url, + data: params, + ...config + }) +} + +/** PUT 请求(本项目后端大量使用 PUT,模板原来只有 get/post) */ +const put = (url: string, params?: object, config?: AxiosRequestConfig): Promise> => { + return request({ + method: 'put', + url, + data: params, + ...config + }) +} + +/** DELETE 请求 */ +const del = (url: string, config?: AxiosRequestConfig): Promise> => { + return request({ + method: 'delete', + url, + ...config + }) +} + +export default { get, post, put, delete: del, request } diff --git a/frontend-pc/src/utils/index.ts b/frontend-pc/src/utils/index.ts new file mode 100644 index 0000000..7383c54 --- /dev/null +++ b/frontend-pc/src/utils/index.ts @@ -0,0 +1,297 @@ +import { camelCase, upperFirst } from 'lodash-es' +import { browse, mapTree } from 'xe-utils' +import { isExternal } from '@/utils/validate' + +export * from './modules/getFieldToRangTimeForm' +export * from './modules/getRangeTimeToFieldForm' + +/** + * @description 工具函数库 + * @recommend 优先使用 lodash-es、xe-utils、dayjs 等成熟库的方法 + */ + +// =============== 类型定义 =============== + +/** 字符串去除空格的位置 */ +type TrimPosition = 'both' | 'left' | 'right' | 'all' + +/** 树形结构基础接口 */ +interface TreeNode { + children?: TreeNode[] + [key: string]: any +} + +/** 可排序的树节点 */ +interface SortableTreeNode extends TreeNode { + sort: number + children?: SortableTreeNode[] +} + +// =============== 对象操作 =============== + +/** + * 获取对象属性值(类型安全) + * @param obj - 目标对象 + * @param key - 属性键名 + * @returns 属性值 + */ +export function getProperty(obj: T, key: K): T[K] { + return obj[key] +} + +/** + * 深拷贝 + * @param data - 要拷贝的数据 + * @returns 深拷贝后的数据 + */ +export function deepClone(data: T): T { + if (typeof data !== 'object' || data === null) return data + const newData: any = Array.isArray(data) ? [] : {} + for (const key in data) { + newData[key] = typeof data[key] === 'object' ? deepClone(data[key]) : data[key] + } + return newData +} + +// =============== 字符串操作 =============== + +/** + * 去除字符串空格 + * @param str - 目标字符串 + * @param pos - 去除位置,默认两端 + * @returns 处理后的字符串 + */ +export function trim(str: string, pos: TrimPosition = 'both'): string { + const trimMap = { + both: /^\s+|\s+$/g, + left: /^\s*/, + right: /(\s*$)/g, + all: /\s+/g + } + return str.replace(trimMap[pos] || trimMap.both, '') +} + +/** + * 字符串大小写转换 + * @param str - 目标字符串 + * @param type - 转换类型:1-全大写 2-全小写 3-首字母大写 + * @returns 转换后的字符串 + */ +export function toCase(str: string, type: 1 | 2 | 3): string { + const caseMap = { + 1: () => str.toUpperCase(), + 2: () => str.toLowerCase(), + 3: () => str.charAt(0).toUpperCase() + str.slice(1).toLowerCase() + } + return caseMap[type]?.() || str +} + +/** + * 格式化电话号码 + * @param mobile - 手机号 + * @param separator - 分隔符 + * @returns 格式化后的电话号码,如:183-7983-6654 + */ +export function formatPhone(mobile: string, separator = '-'): string { + return mobile.replace(/(?=(\d{4})+$)/g, separator) +} + +/** + * 手机号脱敏处理 + * @param phone - 手机号 + * @returns 脱敏后的手机号,如:155****8810 + */ +export function hidePhone(phone: string): string { + return phone.replace(/^(\d{3})\d{4}(\d{4})$/, '$1****$2') +} + +// =============== 数字操作 =============== + +/** + * 获取指定范围内的随机整数 + * @param min - 最小值 + * @param max - 最大值 + * @returns 随机整数 + */ +export function getRandomInteger(min = 0, max: number): number { + return Math.floor(Math.random() * (max - min + 1)) + min +} + +/** + * 数组计算工具 + */ +export const arrayMath = { + /** 获取数组最大值 */ + max: (arr: number[]): number => Math.max(...arr), + /** 获取数组最小值 */ + min: (arr: number[]): number => Math.min(...arr), + /** 计算数组总和 */ + sum: (arr: number[]): number => arr.reduce((a, b) => a + b, 0), + /** 计算数组平均值 */ + average: (arr: number[]): number => arrayMath.sum(arr) / arr.length +} + +// =============== 数据验证 =============== + +/** + * 检测数据是否为空 + * @param data - 要检测的数据 + * @returns 是否为空 + */ +export function isEmpty(data: unknown): boolean { + if (data === '' || data == null || data === 'undefined' || data === 'null') return true + if (typeof data === 'object') { + return JSON.stringify(data) === '{}' + || JSON.stringify(data) === '[]' + || JSON.stringify(data) === '[{}]' + } + return false +} + +/** + * 获取数据类型 + * @param value - 要检测的值 + * @returns 数据类型字符串 + */ +export function getTypeOf(value: unknown): string { + return Object.prototype.toString.call(value).slice(8, -1).toLowerCase() +} + +// =============== 日期时间 =============== + +/** + * 判断是否为闰年 + * @param year - 年份 + * @returns 是否闰年 + */ +export function isLeapYear(year: number): boolean { + return (year % 4 === 0 && year % 100 !== 0) || year % 400 === 0 +} + +/** + * 获取问候语 + * @returns 根据当前时间返回问候语 + */ +export function goodTimeText(): string { + const hour = new Date().getHours() + if (hour < 9) return '早上好' + if (hour <= 11) return '上午好' + if (hour <= 13) return '中午好' + if (hour < 20) return '下午好' + return '晚上好' +} + +// =============== 文件处理 =============== + +/** + * 格式化文件大小 + * @param size - 文件大小(字节) + * @returns 格式化后的文件大小 + */ +export function formatFileSize(size: number): string { + const units = ['B', 'KB', 'MB', 'GB'] + let index = 0 + while (size > 1024 && index < units.length - 1) { + size /= 1024 + index++ + } + return `${Math.round(size * 100) / 100}${units[index]}` +} + +// =============== 颜色处理 =============== + +/** + * RGB转十六进制 + * @param r - 红色值(0-255) + * @param g - 绿色值(0-255) + * @param b - 蓝色值(0-255) + * @returns 十六进制颜色值 + */ +export function rgbToHex(r: number, g: number, b: number): string { + return `#${((1 << 24) + (r << 16) + (g << 8) + b).toString(16).slice(1)}` +} + +/** + * 十六进制颜色转 RGB + * @param {string} hex - 十六进制颜色,如 #377DFF、377DFF、#FFF + * @returns {string} rgb(r, g, b) 格式字符串 + */ +export function hexToRgb(hex: string): string { + hex = hex.replace(/^#/, '') + if (hex.length === 3) { + hex = hex[0] + hex[0] + hex[1] + hex[1] + hex[2] + hex[2] + } + const r = Number.parseInt(hex.substring(0, 2), 16) + const g = Number.parseInt(hex.substring(2, 4), 16) + const b = Number.parseInt(hex.substring(4, 6), 16) + + return `${r}, ${g}, ${b}` +} + +/** + * 获取随机十六进制颜色 + * @returns 随机颜色值 + */ +export function randomHex(): string { + return `#${Math.floor(Math.random() * 0xFFFFFF).toString(16).padEnd(6, '0')}` +} + +// =============== 路由工具 =============== + +/** + * 转换路由路径为名称 + * @param path - 路由路径 + * @returns 转换后的名称 + * @example + * /system => System + * /system/menu => SystemMenu + * /crud/detail => CrudDetail + */ +export function transformPathToName(path: string): string { + if (!path || isExternal(path)) return '' + return upperFirst(camelCase(path)) +} + +// =============== 树形结构处理 =============== + +/** + * 过滤树形结构 + * @param array - 树形数组 + * @param predicate - 过滤函数 + * @returns 过滤后的树形数组 + */ +export function filterTree(array: TreeNode[], predicate: (node: TreeNode) => boolean): TreeNode[] { + const filtered = array.filter(predicate) + return mapTree(filtered, (item) => { + if (item.children?.length) { + item.children = item.children.filter(predicate) + } + return item + }) +} + +/** + * 排序树形结构 + * @param array - 可排序的树形数组 + * @returns 排序后的树形数组 + */ +export function sortTree(array: SortableTreeNode[]): SortableTreeNode[] { + const sort = (a: SortableTreeNode, b: SortableTreeNode) => (a.sort ?? 0) - (b.sort ?? 0) + array.sort(sort) + return mapTree(array, (item) => { + if (item.children?.length) { + item.children.sort(sort) + } + return item + }) +} + +// =============== 环境检测 =============== + +/** + * 检测是否为移动端环境 + * @returns 是否为移动端 + */ +export function isMobile(): boolean { + return browse().isMobile +} diff --git a/frontend-pc/src/utils/mitt.ts b/frontend-pc/src/utils/mitt.ts new file mode 100644 index 0000000..2b0d8d7 --- /dev/null +++ b/frontend-pc/src/utils/mitt.ts @@ -0,0 +1,10 @@ +import mitt from 'mitt' + +type Events = { + // 自定义事件名称 + changeText: string +} + +const mittBus = mitt() + +export default mittBus diff --git a/frontend-pc/src/utils/modules/getFieldToRangTimeForm.ts b/frontend-pc/src/utils/modules/getFieldToRangTimeForm.ts new file mode 100644 index 0000000..cc9cc40 --- /dev/null +++ b/frontend-pc/src/utils/modules/getFieldToRangTimeForm.ts @@ -0,0 +1,46 @@ +/** F: 表单数据 T: 表单数据排除的属性 V: 表单数据里-时间范围属性拆分成的新属性 */ +type FormatItem = { + field: keyof T | keyof F + start: keyof V + end: keyof V +} + +/** + * 合并有时间范围的表单 + * @param form 表单数据 + * @param formatList 需要合并的时间范围属性 + * @returns 合并后的表单数据 + * @example + * const form = { + name: '李四', + startTime1: '2024-01-01', + endTime1: '2024-01-30', + startTime2: '2025-01-01', + endTime2: '2025-01-30' + } + * + * const data = getFieldToRangTimeForm(form, [ + { field: 'rangTime1', start: 'startTime1', end: 'endTime1' }, + { field: 'rangTime2', start: 'startTime2', end: 'endTime2' } + ]) + * + * // data的结果为: + * // { + // name: '李四', + // rangTime1: ['2024-01-01', '2024-01-30'], + // rangTime2: ['2025-01-01', '2025-01-30'] + // } + */ +export const getFieldToRangTimeForm = (form: F, formatList: FormatItem[]) => { + const obj = { ...form } as Omit & T + for (const i of formatList) { + const startTime = obj[i.start as never] as string + const endTime = obj[i.end as never] as string + if (startTime && endTime) { + (obj[i.field as never] as [string, string]) = [startTime, endTime] + } + delete obj[i.start as never] + delete obj[i.end as never] + } + return obj +} diff --git a/frontend-pc/src/utils/modules/getRangeTimeToFieldForm.ts b/frontend-pc/src/utils/modules/getRangeTimeToFieldForm.ts new file mode 100644 index 0000000..7a3d29e --- /dev/null +++ b/frontend-pc/src/utils/modules/getRangeTimeToFieldForm.ts @@ -0,0 +1,45 @@ +/** F: 表单数据 T: 表单数据排除的属性 V: 表单数据里-时间范围属性拆分成的新属性 */ +type FormatItem = { + field: keyof T | keyof F + start: keyof V + end: keyof V +} + +/** + * 拆分有时间范围的表单 + * @param form 表单数据 + * @param formatList 需要拆分的时间范围属性 + * @returns 时间范围转成指定字段的表单 + * @example + * const form = { + name: '张三', + rangTime1: ['2024-01-01', '2024-01-30'] as [string, string], + rangTime2: ['2025-01-01', '2025-01-30'] as [string, string] + } + * + * const data = getRangeTimeToFieldForm(form, [ + { field: 'rangTime1', start: 'startTime1', end: 'endTime1' }, + { field: 'rangTime2', start: 'startTime2', end: 'endTime2' } + ]) + * + * // data的结果为: + // { + // name: '张三', + // startTime1: '2024-01-01', + // endTime1: '2024-01-30', + // startTime2: '2025-01-01', + // endTime2: '2025-01-30' + // } + */ +export const getRangeTimeToFieldForm = (form: F, formatList: FormatItem[]) => { + const obj = { ...form } as Omit & V + for (const i of formatList) { + const value = obj[i.field as never] as [string, string] + if (Array.isArray(value)) { + delete obj[i.field as never]; + (obj[i.start] as string) = value[0] || ''; + (obj[i.end] as string) = value[1] || '' + } + } + return obj +} diff --git a/frontend-pc/src/utils/permission.ts b/frontend-pc/src/utils/permission.ts new file mode 100644 index 0000000..32108f6 --- /dev/null +++ b/frontend-pc/src/utils/permission.ts @@ -0,0 +1,80 @@ +import { useUserStore } from '@/stores' + +/** 超级管理员权限标识 */ +const SUPER_ADMIN_PERMISSION = '*:*:*' +/** 超级管理员角色标识 */ +const SUPER_ADMIN_ROLE = 'role_admin' + +/** + * 判断是否拥有指定权限 + * @description 检查用户是否拥有指定的权限标识,超级管理员拥有所有权限 + * @param permission - 权限标识 + * @returns {boolean} 是否拥有权限 + */ +export function hasPerm(permission: string): boolean { + if (!permission) return false + const permissions = useUserStore().permissions + return permissions.some((v) => v === SUPER_ADMIN_PERMISSION || v === permission) +} + +/** + * 判断是否拥有多个权限中的任意一个 + * @description 检查用户是否拥有指定权限列表中的任意一个权限 + * @param permissions - 权限标识数组 + * @returns {boolean} 是否拥有任意一个权限 + */ +export function hasPermOr(permissions: string[]): boolean { + return permissions.some((permission) => hasPerm(permission)) +} + +/** + * 判断是否拥有所有指定权限 + * @description 检查用户是否同时拥有指定权限列表中的所有权限 + * @param permissions - 权限标识数组 + * @returns {boolean} 是否拥有所有权限 + */ +export function hasPermAnd(permissions: string[]): boolean { + return permissions.every((permission) => hasPerm(permission)) +} + +/** + * 判断是否拥有指定角色 + * @description 检查用户是否拥有指定的角色标识,超级管理员拥有所有角色 + * @param role - 角色标识 + * @returns {boolean} 是否拥有角色 + */ +export function hasRole(role: string): boolean { + if (!role) return false + const roles = useUserStore().roles + return roles.some((v) => v === SUPER_ADMIN_ROLE || v === role) +} + +/** + * 判断是否拥有多个角色中的任意一个 + * @description 检查用户是否拥有指定角色列表中的任意一个角色 + * @param roles - 角色标识数组 + * @returns {boolean} 是否拥有任意一个角色 + */ +export function hasRoleOr(roles: string[]): boolean { + return roles.some((role) => hasRole(role)) +} + +/** + * 判断是否拥有所有指定角色 + * @description 检查用户是否同时拥有指定角色列表中的所有角色 + * @param roles - 角色标识数组 + * @returns {boolean} 是否拥有所有角色 + */ +export function hasRoleAnd(roles: string[]): boolean { + return roles.every((role) => hasRole(role)) +} + +/** 权限判断工具对象 */ +export default { + hasPerm, + hasPermOr, + hasPermAnd, + hasRole, + hasRoleOr, + hasRoleAnd +} diff --git a/frontend-pc/src/utils/regexp.ts b/frontend-pc/src/utils/regexp.ts new file mode 100644 index 0000000..0a4335b --- /dev/null +++ b/frontend-pc/src/utils/regexp.ts @@ -0,0 +1,127 @@ +/** + * @description 常用正则表达式集合 + */ + +/** + * 手机号码正则 + * @description 匹配中国大陆手机号码(13-19号段) + * @example + * Phone.test('13812345678') // true + * Phone.test('12345678') // false + */ +export const Phone = /^1[3-9]\d{9}$/ + +/** + * 邮箱地址正则 + * @description 匹配标准邮箱地址格式 + * @example + * Email.test('user@example.com') // true + * Email.test('invalid.email') // false + */ +export const Email = /^\w+([-+.]\w+)*@\w+([-.]\w+)*\.\w+([-.]\w+)*$/ + +/** + * 密码强度正则(6位数字) + * @description 匹配6位纯数字密码 + * @example + * Password.test('123456') // true + * Password.test('12345') // false + */ +export const Password = /^\d{6}$/ + +/** + * 6位数字验证码正则 + * @description 匹配6位纯数字验证码 + * @example + * Code_6.test('123456') // true + * Code_6.test('12345') // false + */ +export const Code_6 = /^\d{6}$/ + +/** + * 4位数字验证码正则 + * @description 匹配4位纯数字验证码 + * @example + * Code_4.test('1234') // true + * Code_4.test('123') // false + */ +export const Code_4 = /^\d{4}$/ + +/** + * URL链接正则 + * @description 匹配标准URL格式,支持http(s)和www开头的链接 + * @example + * Url.test('https://www.example.com') // true + * Url.test('http://localhost:3000') // true + * Url.test('invalid-url') // false + */ +export const Url = /^(https?:\/\/)?([a-z\d]([a-z\d-]*[a-z\d])?\.)*[a-z\d]([a-z\d-]*[a-z\d])?\.[a-z]{2,6}(\/[\w .-]*)?$/i + +/** + * 十六进制颜色值正则 + * @description 匹配3位或6位十六进制颜色值,支持可选的#前缀 + * @example + * ColorRegex.test('#333') // true + * ColorRegex.test('#8c8c8c') // true + * ColorRegex.test('invalid') // false + */ +export const ColorRegex = /^#?([a-f0-9]{6}|[a-f0-9]{3})$/i + +/** + * 中文字符正则 + * @description 匹配中文字符(不包含标点符号) + * @example + * OnlyCh.test('中文') // true + * OnlyCh.test('Chinese') // false + */ +export const OnlyCh = /^[\u4E00-\u9FA5]+$/ + +/** + * 英文字符正则 + * @description 匹配英文字母(不区分大小写) + * @example + * OnlyEn.test('English') // true + * OnlyEn.test('英文') // false + */ +export const OnlyEn = /^[a-z]+$/i + +/** + * 强密码正则(已注释,供参考) + * @description 密码为8-18位数字/字符/符号的组合 + * - 不能是纯数字 + * - 不能是纯小写字母 + * - 不能是纯大写字母 + * - 不能是纯符号 + * - 不能包含中文 + * @example + * StrongPassword.test('Abc123!@#') // true + * StrongPassword.test('12345678') // false + */ +// export const StrongPassword = /^(?![0-9]+$)(?![a-z]+$)(?![A-Z]+$)(?!([^(0-9a-zA-Z)]|[()])+$)(?!^.*[\u4E00-\u9FA5].*$)([^(0-9a-zA-Z)]|[()]|[a-z]|[A-Z]|[0-9]){8,18}$/ + +/** + * 身份证号码正则 + * @description 匹配18位身份证号码 + * @example + * IdCard.test('110101199003077758') // true + * IdCard.test('invalid') // false + */ +export const IdCard = /^[1-9]\d{5}(19|20)\d{2}(0[1-9]|1[0-2])(0[1-9]|[12]\d|3[01])\d{3}[\dX]$/i + +/** + * 金额正则 + * @description 匹配金额格式,支持两位小数 + * @example + * Money.test('123.45') // true + * Money.test('123.456') // false + */ +export const Money = /^[1-9]\d*(\.\d{1,2})?$|^0(\.\d{1,2})?$/ + +/** + * 中国大陆座机号码正则 + * @description 匹配座机号码,支持区号和分机号 + * @example + * TelPhone.test('010-12345678') // true + * TelPhone.test('010-1234-5678') // true + */ +export const TelPhone = /^(0\d{2,3}-)?\d{7,8}(-\d{1,4})?$/ diff --git a/frontend-pc/src/utils/rsa.ts b/frontend-pc/src/utils/rsa.ts new file mode 100644 index 0000000..32ac5fc --- /dev/null +++ b/frontend-pc/src/utils/rsa.ts @@ -0,0 +1,57 @@ +/** + * 登录密码加密(RSA) + * + * 公钥从后端 `GET /auth/public-key` 拉取并缓存——这样**轮换密钥不需要重新构建前端**。 + * 否则"后端换了密钥、前端还是旧的"会表现为"所有人密码都错",而且极难定位。 + * + * ⚠️ 算法必须与服务端 RsaCipher 一致:**RSA/ECB/PKCS1Padding**, + * 与 jsencrypt 的默认行为相同。换成 OAEP 会表现为"密码永远错",两边都改才能换。 + * + * ⚠️ 这不是用来替代 HTTPS 的。它的唯一目的是**防止明文密码进日志**—— + * 网关、Nginx、APM、链路追踪都会记录请求体,明文密码一旦落进日志就是永久泄露。 + */ +import JSEncrypt from 'jsencrypt' +// 直接用 http 实例,不经 @/apis/xt —— 否则会形成 +// xt/index → http → router → stores → xt/index 的循环引用 +import http from '@/utils/http' + +let cachedKey: string | null = null +let inflight: Promise | null = null + +/** 拉取公钥(并发调用只会真正请求一次) */ +export async function fetchPublicKey(): Promise { + if (cachedKey) { + return cachedKey + } + if (inflight) { + return inflight + } + inflight = http + .get('/auth/public-key') + .then((res) => { + cachedKey = res.data + return res.data + }) + .finally(() => { + inflight = null + }) + return inflight +} + +/** 加密密码,返回 Base64 密文 */ +export async function encryptPassword(plain: string): Promise { + const pem = await fetchPublicKey() + const engine = new JSEncrypt() + engine.setPublicKey(pem) + const cipher = engine.encrypt(plain) + if (!cipher) { + // jsencrypt 在密钥格式不对时返回 false 而不是抛异常,必须自己判 + throw new Error('密码加密失败,请刷新页面重试') + } + return cipher +} + +/** 密钥轮换后调用 */ +export function resetPublicKey(): void { + cachedKey = null +} diff --git a/frontend-pc/src/utils/typeof.ts b/frontend-pc/src/utils/typeof.ts new file mode 100644 index 0000000..e493a59 --- /dev/null +++ b/frontend-pc/src/utils/typeof.ts @@ -0,0 +1,179 @@ +/** + * @description 类型判断工具函数集合 + * 提供比 typeof 更准确的类型判断方法 + */ + +/** 获取值的具体类型 */ +const getType = (value: unknown): string => Object.prototype.toString.call(value) + +/** + * 判断是否为数字类型 + * @param value - 要判断的值 + * @example + * isNumber(123) // true + * isNumber('123') // false + * isNumber(NaN) // true + * isNumber(Infinity) // true + */ +export function isNumber(value: unknown): value is number { + return getType(value) === '[object Number]' +} + +/** + * 判断是否为字符串类型 + * @param value - 要判断的值 + * @example + * isString('abc') // true + * isString(123) // false + */ +export function isString(value: unknown): value is string { + return getType(value) === '[object String]' +} + +/** + * 判断是否为布尔类型 + * @param value - 要判断的值 + * @example + * isBoolean(true) // true + * isBoolean(0) // false + */ +export function isBoolean(value: unknown): value is boolean { + return getType(value) === '[object Boolean]' +} + +/** + * 判断是否为 null + * @param value - 要判断的值 + * @example + * isNull(null) // true + * isNull(undefined) // false + */ +export function isNull(value: unknown): value is null { + return getType(value) === '[object Null]' +} + +/** + * 判断是否为 undefined + * @param value - 要判断的值 + * @example + * isUndefined(undefined) // true + * isUndefined(null) // false + */ +export function isUndefined(value: unknown): value is undefined { + return getType(value) === '[object Undefined]' +} + +/** + * 判断是否为普通对象 + * @param value - 要判断的值 + * @example + * isObject({}) // true + * isObject([]) // false + * isObject(null) // false + */ +export function isObject(value: unknown): value is Record { + return getType(value) === '[object Object]' +} + +/** + * 判断是否为数组 + * @param value - 要判断的值 + * @example + * isArray([]) // true + * isArray({}) // false + * @recommend 推荐使用 Array.isArray() 代替 + */ +export function isArray(value: unknown): value is any[] { + return getType(value) === '[object Array]' +} + +/** + * 判断是否为日期对象 + * @param value - 要判断的值 + * @example + * isDate(new Date()) // true + * isDate('2024-01-01') // false + */ +export function isDate(value: unknown): value is Date { + return getType(value) === '[object Date]' +} + +/** + * 判断是否为正则表达式 + * @param value - 要判断的值 + * @example + * isRegExp(/abc/) // true + * isRegExp('/abc/') // false + */ +export function isRegExp(value: unknown): value is RegExp { + return getType(value) === '[object RegExp]' +} + +/** + * 判断是否为 Set 对象 + * @param value - 要判断的值 + * @example + * isSet(new Set()) // true + * isSet([]) // false + */ +export function isSet(value: unknown): value is Set { + return getType(value) === '[object Set]' +} + +/** + * 判断是否为 Map 对象 + * @param value - 要判断的值 + * @example + * isMap(new Map()) // true + * isMap({}) // false + */ +export function isMap(value: unknown): value is Map { + return getType(value) === '[object Map]' +} + +/** + * 判断是否为 Promise 对象 + * @param value - 要判断的值 + * @example + * isPromise(Promise.resolve()) // true + * isPromise({ then: () => {} }) // false + */ +export function isPromise(value: unknown): value is Promise { + return getType(value) === '[object Promise]' +} + +/** + * 判断是否为函数 + * @param value - 要判断的值 + * @example + * isFunction(() => {}) // true + * isFunction(class {}) // true + * isFunction({}) // false + */ +export function isFunction(value: unknown): value is (...args: any[]) => any { + return typeof value === 'function' +} + +/** + * 判断是否为有效数字(排除 NaN 和 Infinity) + * @param value - 要判断的值 + * @example + * isValidNumber(123) // true + * isValidNumber(NaN) // false + * isValidNumber(Infinity) // false + */ +export function isValidNumber(value: unknown): value is number { + return isNumber(value) && !Number.isNaN(value) && Number.isFinite(value) +} + +/** + * 判断是否为空值(null 或 undefined) + * @param value - 要判断的值 + * @example + * isNil(null) // true + * isNil(undefined) // true + * isNil('') // false + */ +export function isNil(value: unknown): value is null | undefined { + return isNull(value) || isUndefined(value) +} diff --git a/frontend-pc/src/utils/validate.ts b/frontend-pc/src/utils/validate.ts new file mode 100644 index 0000000..f6afd01 --- /dev/null +++ b/frontend-pc/src/utils/validate.ts @@ -0,0 +1,10 @@ +/** 判断 path 是否为外链 */ +export const isExternal = (path: string) => { + const reg = /^(?:https?:|mailto:|tel:)/ + return reg.test(path) +} + +/** 判断 url 是否是 http 或 https */ +export function isHttp(url: string) { + return url.includes('http://') || url.includes('https://') +} diff --git a/frontend-pc/src/views/coming-soon/index.vue b/frontend-pc/src/views/coming-soon/index.vue new file mode 100644 index 0000000..671ebe1 --- /dev/null +++ b/frontend-pc/src/views/coming-soon/index.vue @@ -0,0 +1,71 @@ + + + + + diff --git a/frontend-pc/src/views/dashboard/index.vue b/frontend-pc/src/views/dashboard/index.vue new file mode 100644 index 0000000..83d25a5 --- /dev/null +++ b/frontend-pc/src/views/dashboard/index.vue @@ -0,0 +1,253 @@ + + + + + diff --git a/frontend-pc/src/views/error/403.vue b/frontend-pc/src/views/error/403.vue new file mode 100644 index 0000000..ef06b46 --- /dev/null +++ b/frontend-pc/src/views/error/403.vue @@ -0,0 +1,11 @@ + + + + + diff --git a/frontend-pc/src/views/error/404.vue b/frontend-pc/src/views/error/404.vue new file mode 100644 index 0000000..3233907 --- /dev/null +++ b/frontend-pc/src/views/error/404.vue @@ -0,0 +1,11 @@ + + + + + diff --git a/frontend-pc/src/views/error/500.vue b/frontend-pc/src/views/error/500.vue new file mode 100644 index 0000000..53ca351 --- /dev/null +++ b/frontend-pc/src/views/error/500.vue @@ -0,0 +1,11 @@ + + + + + diff --git a/frontend-pc/src/views/error/components/ErrorPage.vue b/frontend-pc/src/views/error/components/ErrorPage.vue new file mode 100644 index 0000000..1556e9d --- /dev/null +++ b/frontend-pc/src/views/error/components/ErrorPage.vue @@ -0,0 +1,163 @@ + + + + + diff --git a/frontend-pc/src/views/login/LoginLeft.vue b/frontend-pc/src/views/login/LoginLeft.vue new file mode 100644 index 0000000..68049a3 --- /dev/null +++ b/frontend-pc/src/views/login/LoginLeft.vue @@ -0,0 +1,286 @@ + + + + + diff --git a/frontend-pc/src/views/login/LoginPalette.vue b/frontend-pc/src/views/login/LoginPalette.vue new file mode 100644 index 0000000..4677b80 --- /dev/null +++ b/frontend-pc/src/views/login/LoginPalette.vue @@ -0,0 +1,100 @@ + + + + + diff --git a/frontend-pc/src/views/login/index.vue b/frontend-pc/src/views/login/index.vue new file mode 100644 index 0000000..894e7b5 --- /dev/null +++ b/frontend-pc/src/views/login/index.vue @@ -0,0 +1,414 @@ + + + + + diff --git a/frontend-pc/src/views/org/OrgFormModal.vue b/frontend-pc/src/views/org/OrgFormModal.vue new file mode 100644 index 0000000..13a4166 --- /dev/null +++ b/frontend-pc/src/views/org/OrgFormModal.vue @@ -0,0 +1,162 @@ + + + + + diff --git a/frontend-pc/src/views/org/index.vue b/frontend-pc/src/views/org/index.vue new file mode 100644 index 0000000..0e0575b --- /dev/null +++ b/frontend-pc/src/views/org/index.vue @@ -0,0 +1,270 @@ + + + + + diff --git a/frontend-pc/src/views/redirect/index.vue b/frontend-pc/src/views/redirect/index.vue new file mode 100644 index 0000000..a475ae3 --- /dev/null +++ b/frontend-pc/src/views/redirect/index.vue @@ -0,0 +1,13 @@ + + + diff --git a/frontend-pc/src/views/tenant/index.vue b/frontend-pc/src/views/tenant/index.vue new file mode 100644 index 0000000..939df68 --- /dev/null +++ b/frontend-pc/src/views/tenant/index.vue @@ -0,0 +1,394 @@ + + + + + diff --git a/frontend-pc/stylelint.config.js b/frontend-pc/stylelint.config.js new file mode 100644 index 0000000..b73dd56 --- /dev/null +++ b/frontend-pc/stylelint.config.js @@ -0,0 +1,44 @@ +export default { + ignoreFiles: [ + 'node_modules/**/*', + 'dist/**/*', + 'public/**/*' + ], + plugins: ['stylelint-scss'], + extends: [ + 'stylelint-config-standard', // 基础 CSS 规则 + 'stylelint-config-standard-scss', // SCSS 推荐规则 + 'stylelint-config-recommended-vue/scss', // Vue + SCSS 推荐规则(自动适配 scoped) + 'stylelint-config-recess-order' // 配置 stylelint css 属性书写顺序插件 + ], + rules: { + // 禁用导致错误的规则 + 'scss/double-slash-comment-empty-line-before': null, // 禁止在双斜杠注释前有空行 + 'scss/operator-no-unspaced': null, // 运算符周围需要空格 + 'scss/load-partial-extension': null, // 禁止在@use指令中使用.scss扩展名 + 'scss/double-slash-comment-whitespace-inside': null, // 双斜杠注释内部需要空格 + 'scss/dollar-variable-empty-line-before': null, // $变量前需要空行 + 'scss/at-rule-conditional-no-parentheses': null, // @规则条件不使用括号 + 'no-descending-specificity': null, // 禁止选择器优先级降低 + 'declaration-block-no-duplicate-properties': null, // 禁止声明块中重复的属性 + 'keyframes-name-pattern': null, // 限制keyframes名称格式 + 'number-max-precision': null, // 禁用数字精度限制规则 + 'import-notation': null, // 导入语法格式 + 'font-family-no-missing-generic-family-keyword': null, // 字体族必须包含通用字体族 + 'custom-property-empty-line-before': null, // 自定义属性前空行规则 + 'declaration-empty-line-before': null, // 声明前空行规则 + // 其他基础规则 + 'no-empty-source': null, // 禁止空源码 + 'selector-class-pattern': null // 强制选择器类名的格式 + }, + overrides: [ + { + files: ['**/*.vue'], + customSyntax: 'postcss-html' + }, + { + files: ['**/*.scss'], + customSyntax: 'postcss-scss' + } + ] +} diff --git a/frontend-pc/tsconfig.json b/frontend-pc/tsconfig.json new file mode 100644 index 0000000..16f67e8 --- /dev/null +++ b/frontend-pc/tsconfig.json @@ -0,0 +1,40 @@ +{ + "extends": "@vue/tsconfig/tsconfig.json", + "compilerOptions": { + "target": "ES2020", // 指定 ECMAScript 目标版本: 'ES3' (default), 'ES5', 'ES6'/'ES2015', 'ES2016', 'ES2017', or 'ESNEXT' + "jsx": "preserve", // 指定 jsx 代码的生成: 'preserve', 'react-native', or 'react' + "jsxFactory": "h", // 当使用经典的JSX运行时编译JSX元素时,更改.js文件中调用的函数,默认:React.createElement + "jsxFragmentFactory": "Fragment", // 指定 JSX 片段工厂函数在指定了 jsxFactory 编译器选项的情况下针对 react JSX 发出时使用 指定使用模块: 'commonjs', 'amd', 'system', 'umd' or 'es2015' + "lib": [ + "ES2020", + "DOM" + ], // 指定要包含在编译中的库文件 + "baseUrl": ".", // 用于解析非相对模块名称的基目录 + "module": "ES2020", // + "moduleResolution": "bundler", + "paths": { + "@/*": [ + "src/*" + ] // 模块名到基于 baseUrl 的路径映射的列表 + }, + "resolveJsonModule": true, // 是否解析 JSON 模块 + "allowJs": true, // 允许编译 javascript 文件 + "checkJs": true, // 报告 javascript 文件中的错误 + "strict": true, // 开启所有严格的类型检查 + "noImplicitAny": false, + "sourceMap": true, // 是否生成相应的Map映射的文件,默认:false + "esModuleInterop": true, // 是否通过为所有导入模块创建命名空间对象,允许CommonJS和ES模块之间的互操作性,开启改选项时,也自动开启allowSyntheticDefaultImports选项,默认:false + "skipLibCheck": true, // 是否跳过声明文件的类型检查,这可以在编译期间以牺牲类型系统准确性为代价来节省时间,默认:false + "ignoreDeprecations": "5.0" // 选项 importsNotUsedAsValues 已弃用,并将在 TypeScript 5.5 中停止工作。指定 compilerOption 的 "ignoreprecations ": "5.0" 来消除这个错误,使用 verbatimModuleSyntax 代替 + }, + "include": [ + "src/**/*", + "src/**/*.vue", + "vite.config.ts" + ], // 指定被编译文件所在的目录 + "exclude": [ + "node_modules", + "dist", + "**/*.js" + ] // 指定不需要被编译的目录 +} diff --git a/frontend-pc/vite.config.ts b/frontend-pc/vite.config.ts new file mode 100644 index 0000000..c611e94 --- /dev/null +++ b/frontend-pc/vite.config.ts @@ -0,0 +1,115 @@ +import { fileURLToPath, URL } from 'node:url' +import vue from '@vitejs/plugin-vue' +import vueJsx from '@vitejs/plugin-vue-jsx' +import AutoImport from 'unplugin-auto-import/vite' +import IconsResolver from 'unplugin-icons/resolver' +import Icons from 'unplugin-icons/vite' +import { ArcoResolver } from 'unplugin-vue-components/resolvers' +import Components from 'unplugin-vue-components/vite' +import { defineConfig, loadEnv } from 'vite' +import vitePluginCompression from 'vite-plugin-compression' +import VueDevTools from 'vite-plugin-vue-devtools' + +export default defineConfig(({ mode }) => { + const env = loadEnv(mode, process.cwd()) as ImportMetaEnv + + return { + // 开发或生产环境服务的公共基础路径 + base: env.VITE_BASE, + // 路径别名 + resolve: { + alias: { + '~': fileURLToPath(new URL('./', import.meta.url)), + '@': fileURLToPath(new URL('./src', import.meta.url)) + }, + // 确保正确解析模块 + dedupe: ['vue', '@arco-design/web-vue'] + }, + // 依赖优化配置 + optimizeDeps: { + include: ['@arco-design/color'] + }, + // 引入scss全局样式变量 + css: { + preprocessorOptions: { + scss: { + additionalData: `@use "@/styles/var.scss" as *;` + } + } + }, + server: { + // 服务启动时是否自动打开浏览器 + open: false, + // 本地跨域代理 -> 代理到服务器的接口地址 + proxy: { + '/api': { + target: env.VITE_API_BASE_URL, // 后台服务器地址 + changeOrigin: true, // 是否允许不同源 + secure: false, // 支持https + rewrite: (path) => path.replace(/^\/api/, '/api') + } + } + }, + plugins: [ + vue(), + vueJsx(), + VueDevTools(), + AutoImport({ + // 自动导入vue相关函数,如: ref、reactive、toRef等 + imports: ['vue', 'vue-router', { + // vue 3.5.x + vue: ['useTemplateRef', 'onWatcherCleanup', 'useId'] + }], + dts: 'src/auto-import.d.ts' + }), + Components({ + // 指定组件位置,默认是 src/components 自动导入自定义组件 + dirs: ['src/components/Gi*'], + extensions: ['vue', 'tsx'], + // 配置文件生成位置 + dts: 'src/components.d.ts', + // Arco Design Vue 按需导入配置(使用官方推荐的 ArcoResolver) + resolvers: [ + ArcoResolver({ + sideEffect: false + }), + // Iconify 图标按需导入 + IconsResolver({ + prefix: 'icon', + enabledCollections: ['icon-park-outline'] + }) + ] + }), + // Iconify 图标插件配置 + Icons({ + compiler: 'vue3', + autoInstall: true, + defaultClass: 'iconify-icon', + defaultStyle: 'vertical-align: middle;' + }), + // Gzip 压缩配置 + vitePluginCompression({ + verbose: true, // 是否在控制台输出压缩结果 + disable: false, // 是否禁用压缩 + deleteOriginFile: false, // 压缩后是否删除原文件 + threshold: 10240, // 压缩阈值,单位:字节,超过此大小的文件才会被压缩(10KB) + algorithm: 'gzip', // 压缩算法,可选 'gzip' | 'brotliCompress' | 'deflate' | 'deflateRaw' + ext: '.gz', // 压缩文件扩展名 + filter: /\.(js|mjs|json|css|html|svg|ico|eot|otf|ttf|ttc|woff|woff2)$/i // 需要压缩的文件类型 + }) + ], + // 构建 + build: { + chunkSizeWarningLimit: 2000 + }, + // 生产环境优化 + esbuild: + mode === 'development' + ? undefined + : { + pure: ['console.log'], + drop: ['debugger'], + legalComments: 'none' + } + } +}) diff --git a/scripts/dev-clean-e2e.py b/scripts/dev-clean-e2e.py index 1fadfff..f63f46f 100644 --- a/scripts/dev-clean-e2e.py +++ b/scripts/dev-clean-e2e.py @@ -3,8 +3,12 @@ """ 清理 e2e-m0.py 产生的测试数据。 -E2E 每跑一次就新建一个租户(编码 e2e######)并给它建角色与账号, -反复跑会把开发库堆满。这个脚本把 e2e* 租户及其关联数据清掉。 +E2E 每跑一次会: + 1. 新建一个租户(编码 e2e######)并给它建角色与账号; + 2. **在 demo 租户下**建三个组织(学校/院系/班级,编码带时间戳)。 + +第 2 点容易被忽略——这些组织挂在正常租户下,不清掉会让组织树里 +堆满 "E2E 示范学校 586923" 这种名字。 用法 ---- @@ -23,7 +27,7 @@ if hasattr(sys.stdout, "reconfigure"): sys.stdout.reconfigure(encoding="utf-8") # 子表在前、主表在后(无外键约束,顺序只为可读性与排查方便) -STEPS = [ +TENANT_CHILD_TABLES = [ ("角色授权", "sys_role_permission"), ("用户角色", "sys_user_role"), ("登录日志", "sys_login_log"), @@ -35,6 +39,12 @@ STEPS = [ TENANT_FILTER = "tenant_code LIKE 'e2e%'" SUB_FILTER = f"tenant_id IN (SELECT id FROM sys_tenant WHERE {TENANT_FILTER})" +# demo 租户下由 E2E 建的组织:编码都是 ROOT/DEPT/CLS + 6 位时间戳 +DEMO_ORG_FILTER = ( + "org_code ~ '^(ROOT|DEPT|CLS|BAD)[0-9]{6}$' " + "OR org_name LIKE 'E2E %' OR org_name LIKE '重复编码%' OR org_name LIKE '层级颠倒%'" +) + class Db: def __init__(self, container: str, user: str, name: str): @@ -42,7 +52,7 @@ class Db: self.user = user self.name = name - def scalar_rows(self, sql: str) -> list[str]: + def rows(self, sql: str) -> list[str]: cmd = ["docker", "exec", self.container, "psql", "-U", self.user, "-d", self.name, "-v", "ON_ERROR_STOP=1", "-t", "-A", "-F", "|", "-c", sql] r = subprocess.run(cmd, capture_output=True, text=True, encoding="utf-8") @@ -62,19 +72,32 @@ def main() -> int: db = Db(args.container, args.user, args.db) - tenants = db.scalar_rows( + tenants = db.rows( f"SELECT id || '|' || tenant_code || '|' || tenant_name " f"FROM sys_tenant WHERE {TENANT_FILTER} ORDER BY id") - if not tenants: - print("没有 e2e 测试租户,无需清理") + demo_orgs = db.rows( + f"SELECT id || '|' || org_name FROM sys_org " + f"WHERE deleted = 0 AND ({DEMO_ORG_FILTER}) ORDER BY id") + + if not tenants and not demo_orgs: + print("没有 e2e 测试数据,无需清理") return 0 - print(f"找到 {len(tenants)} 个 e2e 测试租户:") - for r in tenants[:15]: - p = r.split("|") - print(" id=%-5s code=%-14s %s" % (p[0], p[1], p[2] if len(p) > 2 else "")) - if len(tenants) > 15: - print(f" ... 另有 {len(tenants) - 15} 个") + if tenants: + print(f"① e2e 测试租户 {len(tenants)} 个:") + for r in tenants[:15]: + p = r.split("|") + print(" id=%-5s code=%-14s %s" % (p[0], p[1], p[2] if len(p) > 2 else "")) + if len(tenants) > 15: + print(f" ... 另有 {len(tenants) - 15} 个") + + if demo_orgs: + print(f"\n② 其它租户下的 e2e 测试组织 {len(demo_orgs)} 个:") + for r in demo_orgs[:15]: + p = r.split("|") + print(" id=%-5s %s" % (p[0], p[1] if len(p) > 1 else "")) + if len(demo_orgs) > 15: + print(f" ... 另有 {len(demo_orgs) - 15} 个") if not args.apply: print("\n这是预览。加 --apply 才会真正删除。") @@ -82,14 +105,19 @@ def main() -> int: print("\n开始清理:") total = 0 - for label, table in STEPS: - rows = db.scalar_rows(f"DELETE FROM {table} WHERE {SUB_FILTER} RETURNING id") - total += len(rows) - print(" %-8s %-20s 删除 %d 行" % (label, table, len(rows))) - rows = db.scalar_rows(f"DELETE FROM sys_tenant WHERE {TENANT_FILTER} RETURNING id") - total += len(rows) - print(" %-8s %-20s 删除 %d 行" % ("租户", "sys_tenant", len(rows))) + for label, table in TENANT_CHILD_TABLES: + n = len(db.rows(f"DELETE FROM {table} WHERE {SUB_FILTER} RETURNING id")) + total += n + print(" %-10s %-22s 删除 %d 行" % (label, table, n)) + + n = len(db.rows(f"DELETE FROM sys_tenant WHERE {TENANT_FILTER} RETURNING id")) + total += n + print(" %-10s %-22s 删除 %d 行" % ("租户", "sys_tenant", n)) + + n = len(db.rows(f"DELETE FROM sys_org WHERE {DEMO_ORG_FILTER} RETURNING id")) + total += n + print(" %-10s %-22s 删除 %d 行" % ("测试组织", "sys_org", n)) print(f"\n完成,共删除 {total} 行。") return 0 diff --git a/scripts/extract-icons.py b/scripts/extract-icons.py new file mode 100644 index 0000000..ccd17d5 --- /dev/null +++ b/scripts/extract-icons.py @@ -0,0 +1,86 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +""" +从原型抽取 SVG 图标,生成前端图标模块。 + +为什么要有这一步 +---------------- +原型里的图标是内联 SVG sprite(统一 viewBox、1.7px 描边、currentColor)。 +如果前端换成别的图标库(Element Plus Icons / FontAwesome), +视觉上立刻就能看出"原型一个样、实现另一个样"。 + +所以:**图标以原型为准,用脚本抽出来生成代码**,而不是找一套"差不多的"。 + +用法 +---- + python scripts/extract-icons.py +""" +from __future__ import annotations + +import io +import os +import re +import sys + +if hasattr(sys.stdout, "reconfigure"): + sys.stdout.reconfigure(encoding="utf-8") + +ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) +SOURCES = [ + os.path.join(ROOT, "docs", "prototype", "admin-web.html"), + os.path.join(ROOT, "docs", "prototype", "student-app.html"), + os.path.join(ROOT, "docs", "prototype", "teacher-app.html"), +] +OUT = os.path.join(ROOT, "frontend-pc", "src", "components", "icon-defs.ts") + + +def main() -> int: + symbols: dict[str, str] = {} + for p in SOURCES: + if not os.path.exists(p): + print(f" 跳过(不存在):{p}") + continue + s = io.open(p, encoding="utf-8").read() + for m in re.finditer(r']*\bid="([^"]+)"[^>]*>(.*?)', s, re.S): + name, body = m.group(1), m.group(2) + # 先抽到的为准(admin-web 的图标集最全) + if name not in symbols: + symbols[name] = re.sub(r"\s+", " ", body).strip() + + print(f"[OK] 抽到 {len(symbols)} 个图标") + if not symbols: + print("[X] 没抽到图标,检查原型里 的写法") + return 1 + + os.makedirs(os.path.dirname(OUT), exist_ok=True) + lines = [ + "// ⚠️ 本文件由脚本生成,请勿手改!", + "// 来源 :docs/prototype/*.html 里的 ", + "// 重新生成:python scripts/extract-icons.py", + "//", + "// 图标取自原型的内联 SVG sprite(统一 24×24、1.7px 描边、currentColor),", + "// 保证前端与原型用的是同一套图形,不会出现「原型一个样、实现另一个样」。", + "", + "/** 图标名 → 内部 SVG 标记(不含 外壳) */", + "export const ICON_DEFS: Record = {", + ] + for name in sorted(symbols): + body = symbols[name].replace("\\", "\\\\").replace("'", "\\'") + lines.append(f" '{name}': '{body}',") + lines += [ + "}", + "", + "/** 所有可用图标名 */", + "export const ICON_NAMES = Object.keys(ICON_DEFS)", + "", + ] + + io.open(OUT, "w", encoding="utf-8", newline="\n").write("\n".join(lines)) + rel = os.path.relpath(OUT, ROOT) + print(f" 已生成 {rel}({os.path.getsize(OUT)} 字节)") + print(f" 图标:{', '.join(sorted(symbols))}") + return 0 + + +if __name__ == "__main__": + raise SystemExit(main())