253 Commits

Author SHA1 Message Date
a0142bd3c8 chore(deps): update dependency @types/node to v24.3.3 2025-09-14 08:54:20 +08:00
866699665d chore(deps): update lint dependencies 2025-09-09 13:57:12 +08:00
严浩
b2ce4dde42 build: 更新 oxlint 命令参数
- 在 package.json 中更新了 lint:oxlint 脚本的命令参数
- 新增了 -D correctness 参数,以提高代码正确性检查的严格度
- 添加了 --ignore-path .gitignore 参数,忽略 .gitignore 中的文件
2025-09-09 11:49:09 +08:00
严浩
5e79ffe9be build(deps): 更新 eslint-plugin-unicorn 到 61.0.2
- 将 eslint-plugin-unicorn 从 60.0.0 升级到 61.0.2
- 修改 playwright 配置中的命令从 npm 到 pnpm
- 移除了多余的空格和换行符
2025-09-09 10:24:30 +08:00
严浩
46ca762a96 refactor(fake): 优化 XML 标签内容提取正则表达式
- 修改了 pickTag 函数中的正则表达式,以更准确地匹配标签内容
- 将 [\s\S]*? 替换为 [sS]*?,以提高匹配精度
- 此修改解决了潜在的命名空间与嵌套同名标签的复杂情形下的匹配问题
2025-09-09 09:31:00 +08:00
474a690417 chore(deps): update dependency @vitest/eslint-plugin to v1.3.8 2025-09-08 14:39:29 +08:00
38b9c28404 chore(deps): update dependency @types/node to v24.3.1 2025-09-08 08:49:34 +08:00
99dcb7723c chore(deps): update yanhao98/composite-actions digest to 25eb4dc 2025-09-08 08:49:10 +08:00
47390ab1f2 chore(deps): update dependency @intlify/unplugin-vue-i18n to v11 2025-09-08 00:47:12 +08:00
3af468f18e chore(deps): update dependency @types/three to ^0.180.0 2025-09-05 03:14:40 +08:00
严浩
cc0e5679a9 feat(xml): 增加 XML 解析和处理功能
- 添加了处理 XML 请求和响应的工具函数
- 实现了 GET 和 POST 请求的 XML 解析和返回
- 更新了 API 页面,增加了 XML POST 请求和解析的示例
- 优化了 XML 生成和转义逻辑,防止二次转义
2025-09-02 16:19:31 +08:00
严浩
b9e6a139b7 feat(fake-server): 模拟 XML 数据并增强 API 页面解析功能
- 新增 XML 模拟数据路由,提供示例 XML 文档
- 在 API 页面中集成 XML 请求和解析功能
- 使用 Axios 请求 XML 数据,并通过 DOMParser 解析
- 展示 XML 字符串和解析后的结果
2025-09-02 15:43:09 +08:00
严浩
f0a65d16f7 build(deps): pnpm dedupe
- 将 @primevue/auto-import-resolver 版本从 ^4.3.7 升级到 ^4.3.8
- 将 @babel/parser 版本从 7.27.7 升级到 7.28.3
2025-09-02 09:44:42 +08:00
严浩
119006133f build(deps): 更新项目依赖版本
- 更新 @pinia/colada 从 0.17.2 到 0.17.3
- 更新 @primevue/icons 从 4.3.7 到 4.3.8
- 更新 @vueuse/core 从 13.7.0 到 13.9.0
- 更新 p5 从 2.0.4 到 2.0.5
- 更新 primevue 从 4.3.7 到 4.3.8
- 更新 vue 从 3.5.18 到 3.5.20
- 更新 vue-data-ui 从 3.0.14 到 3.0.15
- 更新 @playwright/test 从 1.54.2 到 1.55.0
- 更新 @primevue/auto-import-resolver 从 4.3.7 到 4.3.8
- 更新 @primevue/metadata 从 4.3.7 到 4.3.8
- 更新 @vitejs/plugin-vue-jsx 从 5.0.1 到 5.1.1
- 更新 @vitest/eslint-plugin 从 1.3.5 到 1.3.6
- 更新 @vue/tsconfig 从 0.8.0 到 0.8.1
- 更新 eslint 从 9.33.0 到 9.34.0
- 更新 lint-staged 从 16.1.5 到 16.1.6
- 更新 unplugin-auto-import 从 20.0.0 到 20.1.0
- 更新 vite-plugin-static-copy 从 3.1.1 到 3.1.2
- 更新 vite-plugin-vue-devtools 从 8.0.0 到 8.0.1
- 更新 vue-component-type-helpers 从 3.0.5 到 3.0.6
- 更新 vue-tsc 从 3.0.5 到 3.0.6
2025-09-02 09:43:55 +08:00
严浩
9e82ae98e6 fix(vite): 更新 fake-server 插件配置
- 修改 fake-server 插件的 enableProd 参数为 !true,禁用生产环境中的 fake API
- 在 vite 配置中添加 eruda,用于调试
- 注释掉部分配置选项,保留作为参考
2025-09-02 09:32:29 +08:00
严浩
04e7376085 build(dependencies): 升级 pnpm 并添加图像优化插件
- 将 pnpm 版本从 10.15.0 升级到 10.15.1
- 添加 vite-plugin-image-optimizer 依赖
- 在 Vite 插件中集成图像优化功能
2025-09-02 09:03:50 +08:00
严浩
d83813f8f9 refactor: rolldown-vite 2025-09-01 18:36:46 +08:00
8b749beaf3 chore(deps): update dependency unplugin-auto-import to v20.1.0 2025-08-31 11:04:37 +08:00
adc95bf985 chore(deps): update dependency @vitejs/plugin-vue-jsx to v5.1.1 2025-08-31 11:02:07 +08:00
7cf0d22b24 chore(deps): update lint dependencies to v1.13.0 2025-08-29 22:16:53 +08:00
3e52fc491d chore(deps): update dependency @vue/tsconfig to ^0.8.0 2025-08-29 22:13:19 +08:00
9929a92a38 chore(deps): update dependency @vitejs/plugin-vue-jsx to v5.1.0 2025-08-29 11:23:41 +08:00
cc309e6fa8 chore(deps): update dependency vite-plugin-checker to v0.10.3 2025-08-29 11:20:49 +08:00
176f497f4d chore(deps): update all non-major dependencies 2025-08-27 17:12:33 +08:00
f25746c592 chore(deps): update dependency vite-plugin-vue-devtools to v8.0.1 2025-08-27 14:51:09 +08:00
63ea355c64 chore(deps): update dependency vite-plugin-static-copy to v3.1.2 2025-08-27 01:24:30 +08:00
98d697d735 chore(deps): update dependency vite-plugin-vue-meta-layouts to ^0.6.0 2025-08-26 23:01:27 +08:00
beaae87063 chore(deps): update dependency @types/node to v22.18.0 2025-08-26 19:50:48 +08:00
dbd10e0fe4 chore(deps): update dependency eslint to v9.34.0 2025-08-23 06:27:14 +08:00
47d933923c chore(deps): update playwright packages to v1.55.0 2025-08-21 05:48:49 +08:00
严浩
02e61dbcdd build(test): 配置 Vitest 并调整代码检查范围
- 更新 package.json 中 lint-staged 配置,扩大代码检查范围
- 修改 vitest.config.ts,优化 Vitest 配置参数
2025-08-20 13:59:34 +08:00
严浩
cd1735d6b7 build: 升级 Node.js 版本
- 将 .npmrc 文件中的 use-node-version 从 22.15.0 修改为 22.18.0
- 此次升级旨在使用最新版本的 Node.js,以获得更好的性能和安全性
2025-08-20 12:52:45 +08:00
严浩
642b8dbfba build: 升级 pnpm 包管理器版本
- 将 pnpm 版本从 10.14.0 升级到 10.15.0
2025-08-20 11:32:11 +08:00
严浩
85ab75a9e9 build(deps): 更新依赖并移除冗余包
- 更新 vitest、@vitest/eslint-plugin 等包至最新版本
- 移除 @napi-rs/wasm-runtime、@oxc-project/runtime 等冗余包
- 更新 lightningcss 相关包至 1.30.1 版本
- 移除 rolldown、rolldown-vite 等未使用的包
- 更新 renovate.json,添加 pnpmDedupe 选项
2025-08-20 10:03:56 +08:00
严浩
c762405682 build(deps): 更新 Vite 版本
- 在 pnpm overrides 中添加 Vite,确保使用正确的版本
2025-08-19 18:27:59 +08:00
严浩
868972e889 style: 更新 .editorconfig 文件配置
- 扩展配置到更多文件类型,包括 CSS 预处理器文件
- 添加 end_of_line 和 max_line_length 设置
- 统一使用 LF 换行符
- 设置最大行长度为 120 字符
2025-08-19 16:24:22 +08:00
严浩
e4287bb0e1 chore: 整理 2025-08-19 16:20:33 +08:00
严浩
0585f9abdb build: 添加 create-vue 依赖 2025-08-19 09:31:50 +08:00
严浩
4f5910147c build: 移除 tsconfig.app.json 中的 lib 配置项 2025-08-19 09:30:02 +08:00
严浩
95a3676fba build(deps): 降级 @types/node 和 @vue/tsconfig 版本
- 将 @types/node 版本从 ^24.3.0 降级到 ^22.17.2
- 将 @vue/tsconfig 版本从 ^0.8.0 降级到 ^0.7.0
2025-08-18 11:41:38 +08:00
严浩
46e5fd3e49 refactor(shadcn): 优化 Button 组件并更新相关依赖
- 更新 @types/node 版本至 24.3.0
- 更新 @vue/tsconfig 版本至 0.8.0
- 更新 eslint-plugin-oxlint 和 oxlint 版本至 1.12.0
- 优化 HCesiumManager 中的卫星实体创建逻辑
- 修复 ShadcnVue 页面中的 Button 组件用法
- 优化 Button 组件的 Props 类型定义
- 更新 buttonVariants 的导入路径
2025-08-18 10:18:03 +08:00
3b25b0dc7b chore(deps): update lint dependencies to v1.12.0 2025-08-17 20:50:04 +08:00
f4fea38dd5 chore(deps): update dependency @types/node to v24.3.0 2025-08-15 19:23:12 +08:00
严浩
ee58de3146 test(config): 更新 Playwright 配置以适应 VSCode 环境
- 添加代码判断是否在 VSCode 中运行,并设置相应的 baseURL
- 修改 webServer 配置,仅在 VSCode 环境中启动本地服务器
- 优化配置文件结构,提高可读性和维护性
2025-08-15 16:33:27 +08:00
严浩
b4c2711da6 build(deps): 更新项目依赖版本 2025-08-15 16:19:45 +08:00
0e97f523d8 chore(deps): update primevue pkgs 2025-08-14 18:53:32 +08:00
严浩
1d4f3b601a refactor(dev): 将 $__DEV__ 重命名为 __DEV__ 2025-08-14 14:31:19 +08:00
严浩
231249b357 ci: 更新 Playwright 测试工作流
- 在运行测试前添加环境变量输出,提高调试可读性
- 删除测试中不必要的日志输出
2025-08-14 14:18:10 +08:00
严浩
3ce9c29302 chore(deps): 更新依赖
更新了多个开发依赖项,包括 Vite 及其相关插件、unplugin 系列工具和 CI 工作流中使用的 composite-actions。

主要变更包括:
- 将 pnpm 版本升级至 10.14.0。
- 升级 Vite、@vitejs/plugin-vue 等核心构建工具。
- 升级 unplugin-auto-import, unplugin-vue-components, unplugin-vue-router 等插件。
- 更新 CI 工作流中的 GitHub Actions 版本以保持同步。
- 重新生成了因依赖更新而变化的 `typed-router.d.ts` 类型文件。
2025-08-14 09:57:47 +08:00
c632abf8a4 chore(deps): update dependency @types/node to ^22.17.1 2025-08-09 03:14:55 +08:00
67e4f3fe44 chore(deps): update dependency @types/three to ^0.179.0 2025-08-07 06:26:25 +08:00
1184096973 chore(deps): update dependency tdesign-icons-vue-next to ^0.3.7 2025-08-06 11:12:36 +08:00
6274d4f8aa chore(deps): update dependency unplugin-vue-router to ^0.15.0 2025-08-04 21:52:30 +08:00
e04af8a897 chore(deps): update playwright packages 2025-08-03 19:08:12 +08:00
c8a8444999 chore(deps): update lint dependencies 2025-08-02 03:17:45 +08:00
2374bd3eb5 chore(deps): update dependency unplugin-icons to ^22.2.0 2025-08-02 00:23:54 +08:00
c7986cb759 chore(deps): update dependency vite-plugin-checker to ^0.10.2 2025-07-31 03:15:59 +08:00
17b57099ab chore(deps): update dependency @types/node to ^22.17.0 2025-07-30 01:21:04 +08:00
严浩
8e8a272f2e chore(types): remove unnecessary export statements from global and unocss type definitions 2025-07-29 09:20:31 +08:00
严浩
8ca42a4cdd chore(deps): update eslint-plugin-unicorn to ^60.0.0 and vite-plugin-vue-devtools to ^8.0.0 2025-07-29 09:14:15 +08:00
8826aae6fa chore(deps): update lint dependencies 2025-07-26 00:51:11 +08:00
6c1433d39f chore(deps): update dependency vite-plugin-checker to ^0.10.1 2025-07-21 18:39:02 +08:00
7a1db29f4e chore(deps): update lint dependencies to ^1.7.0 2025-07-20 18:49:48 +08:00
ecdcf90dee chore(deps): update dependency @types/node to ^22.16.5 2025-07-19 13:00:18 +08:00
f214c7a567 chore(deps): update primevue pkgs 2025-07-16 18:41:09 +08:00
2de56fd2f8 chore(deps): update dependency @types/node to ^22.16.4 2025-07-15 16:17:33 +08:00
4d6e3149c6 chore(deps): update dependency eslint to ^9.31.0 2025-07-15 03:18:35 +08:00
483e2995db chore(deps): update playwright packages 2025-07-12 05:57:03 +08:00
383d8deead chore(deps): update dependency @types/node to ^22.16.3 2025-07-11 03:15:34 +08:00
fcf09d887b chore(deps): update dependency @types/three to ^0.178.1 2025-07-11 02:48:12 +08:00
145baf5e16 chore(deps): update dependency vite-plugin-checker to ^0.10.0 2025-07-10 18:56:25 +08:00
866145591f chore(deps): update lint dependencies to ^1.6.0 2025-07-10 16:30:47 +08:00
33528d64a7 chore(deps): update dependency @types/node to ^22.16.2 2025-07-09 08:59:11 +08:00
30559d749e chore(deps): update vite packages 2025-07-07 08:42:16 +08:00
57d07d3cd0 chore(deps): update yanhao98/composite-actions digest to b4a2caa 2025-07-07 07:29:42 +08:00
11f4587681 chore(deps): update all non-major dependencies 2025-07-06 16:21:06 +08:00
669cd7070f chore(deps): update lint dependencies 2025-07-04 13:41:31 +08:00
严浩
5487dc321e feat: enhance responsive design for sidebar and drawer in AppLayout 2025-07-04 12:34:55 +08:00
严浩
ec4906f441 feat: update layout configuration to use naive-ui/AppLayout 2025-07-04 12:06:40 +08:00
严浩
ad8c187edd chore: update GitHub Copilot instructions to define code generation standards 2025-07-04 10:15:09 +08:00
严浩
997df3a4d4 chore(deps): add naive-ui and vfonts dependencies; update styles and router for Naive UI components 2025-07-04 10:14:58 +08:00
42560a4f2c chore(deps): update dependency @splinetool/runtime to ^1.10.19 2025-07-02 12:00:38 +08:00
8f9593957a chore(deps): update lint dependencies to ^1.5.0 2025-07-02 09:31:33 +08:00
0d26da85a1 chore(deps): update dependency vue-tsc to v3 2025-07-02 09:29:20 +08:00
a831d12cf8 chore(deps): update dependency @types/node to ^22.16.0 2025-07-02 07:14:42 +08:00
55a634db68 chore(deps): update dependency @faker-js/faker to ^9.9.0 2025-07-02 04:58:04 +08:00
ec7c877c93 chore(deps): update lint dependencies 2025-07-02 04:53:09 +08:00
382c0b79d0 chore(deps): update dependency cesium to ^1.131.0 2025-07-02 01:56:02 +08:00
167e9a55c1 chore(deps): update dependency @types/three to ^0.178.0 2025-07-02 01:51:49 +08:00
3dcba6a1ef chore(deps): update dependency vue-component-type-helpers to v3 2025-07-01 23:38:51 +08:00
416faf42bf chore(deps): update dependency @splinetool/runtime to ^1.10.18 2025-07-01 18:18:56 +08:00
25f923e0c9 chore(deps): update mcr.microsoft.com/playwright docker tag to v1.53.2 2025-07-01 05:26:59 +08:00
624984e8c0 chore(deps): update dependency @playwright/test to ^1.53.2 2025-07-01 03:11:36 +08:00
8cd623996a chore(deps): update dependency @splinetool/runtime to ^1.10.17 2025-07-01 00:38:27 +08:00
c96a6ba984 chore(deps): update dependency three to ^0.178.0 2025-06-30 22:28:23 +08:00
6159caec26 chore(deps): update dependency alova to ^3.3.4 2025-06-30 22:23:13 +08:00
4181110167 chore(deps): update lint dependencies to ^1.4.0 2025-06-30 16:53:57 +08:00
d359929003 chore(deps): update dependency pinia-plugin-persistedstate to ^4.4.1 2025-06-30 16:49:10 +08:00
6f2550a933 chore(deps): update dependency unplugin-vue-markdown to ^29.1.0 2025-06-30 11:44:28 +08:00
393cc3d9df chore(deps): update dependency @vitejs/plugin-vue-jsx to ^5.0.1 2025-06-30 11:39:54 +08:00
7d1a250e06 chore(deps): update dependency unplugin-vue-markdown to v29 2025-06-30 08:43:26 +08:00
0636256a32 chore(deps): update dependency unplugin-vue-router to ^0.14.0 2025-06-30 06:17:41 +08:00
e45fb54e92 chore(deps): update dependency vue-data-ui to ^2.12.7 2025-06-30 03:13:16 +08:00
c8db9d36da chore(deps): update dependency unplugin-vue-router to ^0.13.0 2025-06-29 17:04:01 +08:00
333d2e47fd chore(deps): update dependency unplugin-vue-components to ^28.8.0 2025-06-29 11:18:35 +08:00
c81b42a81d chore(deps): update dependency pinia-plugin-persistedstate to ^4.4.0 2025-06-28 21:46:11 +08:00
e9bfa60ea1 chore(deps): update dependency @types/node to ^22.15.34 2025-06-28 16:08:49 +08:00
d102a29f04 chore(deps): update vite packages 2025-06-28 08:16:20 +08:00
6d7171dfba chore(deps): update dependency eslint to ^9.30.0 2025-06-28 05:42:22 +08:00
e8d6ca5262 chore(deps): update dependency lucide-vue-next to ^0.525.0 2025-06-28 03:12:15 +08:00
43ef9854b8 chore(deps): update dependency primelocale to ^2.1.4 2025-06-28 01:44:50 +08:00
070cfbad51 chore(deps): update dependency @splinetool/runtime to ^1.10.16 2025-06-28 01:39:29 +08:00
2863d607cf chore(deps): update dependency vue-data-ui to ^2.12.6 2025-06-27 22:45:08 +08:00
c04cbc444f chore(deps): update dependency lucide-vue-next to ^0.524.0 2025-06-27 20:16:29 +08:00
ae189c4add chore(deps): update dependency prettier to v3.6.2 2025-06-27 12:02:33 +08:00
6d603e0bc2 chore(deps): update dependency eslint-plugin-import-x to ^4.16.1 2025-06-27 11:57:14 +08:00
0a95e603bc chore(deps): update dependency @vitest/eslint-plugin to ^1.3.3 2025-06-27 06:13:23 +08:00
0f88d0c35a chore(deps): update pnpm to v10.12.4 2025-06-27 03:10:23 +08:00
bf563c3741 chore(deps): update dependency @splinetool/runtime to ^1.10.15 2025-06-26 21:36:12 +08:00
f49a425870 chore(deps): update dependency unocss to v66.3.2 2025-06-26 13:24:59 +08:00
34af83bc6f chore(deps): update dependency vite-plugin-purgecss-updated-v5 to ^1.2.6 2025-06-26 10:55:13 +08:00
0a43e45020 chore(deps): update dependency @eslint/compat to ^1.3.1 2025-06-25 22:30:19 +08:00
4389195b6f chore(deps): update dependency vite-plugin-static-copy to ^3.1.0 2025-06-25 20:09:27 +08:00
83b0171226 chore(deps): update dependency unocss to v66.3.1 2025-06-25 20:06:49 +08:00
5ac854cb87 chore(deps): update dependency vite-plugin-purgecss-updated-v5 to ^1.2.5 2025-06-25 17:21:29 +08:00
ba7f565e6a chore(deps): update dependency prettier to v3.6.1 2025-06-25 17:18:45 +08:00
f48b00feb4 chore(deps): update dependency eslint-plugin-import-x to ^4.16.0 2025-06-25 14:09:26 +08:00
8d7795e53d chore(deps): update dependency @types/node to ^22.15.33 2025-06-25 02:45:14 +08:00
69fc6b50d8 chore(deps): update all non-major dependencies 2025-06-25 00:26:18 +08:00
e2394c9bd8 chore(deps): update dependency lucide-vue-next to ^0.523.0 2025-06-24 22:03:09 +08:00
31c56c7a4a chore(deps): update dependency @unhead/vue to ^2.0.11 2025-06-24 22:00:25 +08:00
bff4f8d890 chore(deps): update pnpm to v10.12.3 2025-06-24 18:55:39 +08:00
abd974191c chore(deps): update dependency vue-data-ui to ^2.12.5 2025-06-24 13:51:35 +08:00
ed1e88bd55 chore(deps): update lint dependencies to ^1.3.0 2025-06-23 15:58:56 +08:00
严浩
62f92f1fb4 chore: remove copilot instructions file [skip ci] 2025-06-23 14:00:35 +08:00
9424bb23d4 chore(deps): update dependency vue-data-ui to ^2.12.3 2025-06-23 13:33:52 +08:00
c371bc04b5 chore(deps): update dependency prettier to v3.6.0 2025-06-23 11:29:12 +08:00
4a9069e13c chore(deps): update all non-major dependencies 2025-06-23 11:20:36 +08:00
85e6733e53 chore(deps): update lint dependencies 2025-06-22 16:28:20 +08:00
严浩
c9efa9ddf1 chore(config): comment out minifyInternalExports and manualChunks options 2025-06-22 14:40:48 +08:00
严浩
d5001e0e05 chore(deps): remove terser dependency 2025-06-21 23:23:33 +08:00
caa4ae66cc chore(deps): update dependency lucide-vue-next to ^0.522.0 2025-06-21 16:29:18 +08:00
9635c2005f chore(deps): update dependency lucide-vue-next to ^0.519.0 2025-06-21 05:57:23 +08:00
dc6b3506fb chore(deps): update dependency lucide-vue-next to ^0.518.0 2025-06-19 16:22:11 +08:00
f4a64498bf chore(deps): update playwright packages 2025-06-19 03:14:26 +08:00
dfa4a92d54 chore(deps): update dependency lucide-vue-next to ^0.517.0 2025-06-18 16:46:22 +08:00
86d85d1166 chore(deps): update dependency @primeuix/themes to ^1.1.2 2025-06-17 18:48:57 +08:00
1be504569c chore(deps): update dependency lucide-vue-next to ^0.516.0 2025-06-17 05:31:23 +08:00
943993920b chore(deps): update dependency @types/node to ^22.15.32 2025-06-16 19:27:50 +08:00
2abff2791f chore(deps): update dependency vite-plugin-vue-devtools to ^7.7.7 2025-06-15 19:35:09 +08:00
25d3cd09a1 chore(deps): update dependency vite-plugin-static-copy to ^3.0.2 2025-06-14 18:39:03 +08:00
e1a60fd9bc chore(deps): update dependency lucide-vue-next to ^0.515.0 2025-06-13 17:36:02 +08:00
267b00d77a chore(deps): update dependency @pinia/colada to ^0.17.1 2025-06-13 17:31:21 +08:00
63fc76dc29 chore(deps): update dependency primelocale to ^2.1.3 2025-06-12 19:29:20 +08:00
ec5239ab29 chore(deps): update lint dependencies to v1 2025-06-12 09:35:17 +08:00
f4a475d2d6 chore(deps): update dependency @types/node to ^22.15.31 2025-06-11 06:03:59 +08:00
2cb4dd0b18 chore(deps): update playwright packages 2025-06-11 03:15:17 +08:00
c7e7ccc14c chore(deps): update dependency lucide-vue-next to ^0.514.0 2025-06-11 03:10:30 +08:00
4c27108d68 chore(deps): update all non-major dependencies 2025-06-10 01:07:02 +08:00
严浩
0883cb8df5 fix: update layout titles to object format for consistency 2025-06-10 00:44:57 +08:00
严浩
65cd87264f feat: update multiple dependencies 2025-06-10 00:35:25 +08:00
00debaa771 chore(deps): update vite packages 2025-06-08 02:46:13 +08:00
5490cc7531 chore(deps): update dependency @types/node to ^22.15.30 2025-06-06 03:11:52 +08:00
e2a58765f4 chore(deps): update dependency @types/three to ^0.177.0 2025-06-04 00:37:04 +08:00
严浩
12d5af969c fix: disable specific eslint-plugin-import-x rules and update router import 2025-06-03 22:54:15 +08:00
12d1cc4228 chore(deps): update lint dependencies 2025-06-03 10:13:15 +08:00
c29b9da955 chore(deps): update dependency three to ^0.177.0 2025-06-01 05:53:14 +08:00
bc45b0cb22 chore(deps): update dependency @types/node to ^22.15.29 2025-05-31 03:14:38 +08:00
严浩
fa48449105 fix: update vite-plugin-image-tools to version 2.0.1 and configure additional options 2025-05-30 11:59:35 +08:00
b0b687ec01 chore(deps): update unplugin packages 2025-05-26 13:50:08 +08:00
严浩
e8803a9f18 feat: add vue-data-ui dependency and create vue-data-ui page 2025-05-26 12:57:24 +08:00
c3690aa235 chore(deps): update v0.x 2025-05-26 11:57:58 +08:00
376b5a2d54 chore(deps): update lint dependencies to ^0.16.12 2025-05-26 11:28:02 +08:00
2d42497a41 chore(deps): update lint dependencies 2025-05-26 09:52:25 +08:00
e1e14200cd chore(deps): update all non-major dependencies 2025-05-25 23:29:38 +08:00
15eeb6f5ee chore(deps): update vite packages 2025-05-22 11:03:48 +08:00
f7a70cbf85 chore(deps): update dependency @types/node to ^22.15.21 2025-05-21 20:01:34 +08:00
e71936c3c4 chore(deps): update lint dependencies 2025-05-21 02:16:43 +08:00
1dfca31c33 chore(deps): update lint dependencies to ^0.16.11 2025-05-19 14:11:17 +08:00
e1c8663d47 Merge pull request 'chore(deps): update dependency @types/node to ^22.15.19' (#253) from renovate/types into main
[skip ci]
2025-05-19 13:45:48 +08:00
98f5c40034 chore(deps): update dependency @types/node to ^22.15.19 2025-05-19 08:55:23 +08:00
3bb892a507 chore(deps): update dependency eslint-plugin-perfectionist to ^4.13.0 2025-05-15 21:35:19 +08:00
cbc0da3ac8 chore(deps): update dependency vite-plugin-image-tools to ^1.2.2 2025-05-15 18:52:35 +08:00
严浩
7cc05ab132 feat: .gitattributes.MD 2025-05-15 16:49:02 +08:00
a65ca6a6b8 chore(deps): update dependency @types/node to ^22.15.18 2025-05-14 13:23:59 +08:00
d12c00cb4b chore(deps): update vite packages 2025-05-13 07:56:51 +08:00
58e6119436 chore(deps): update dependency eslint-plugin-import-x to ^4.11.1 2025-05-12 20:19:14 +08:00
0b195d16a1 chore(deps): update lint dependencies to ^0.16.10 2025-05-12 02:49:37 +08:00
4b73f0e310 Merge pull request 'chore(deps): update yanhao98/composite-actions digest to 3bf0746' (#247) from renovate/github-actions into main
[no ci]
2025-05-12 00:58:01 +08:00
3c2145a626 chore(deps): update yanhao98/composite-actions digest to 3bf0746 2025-05-11 22:10:33 +08:00
f432045ffe chore(deps): update dependency vite-plugin-static-copy to v3 2025-05-11 20:18:27 +08:00
0cd7080cee chore(deps): update dependency lint-staged to v16 2025-05-11 03:32:51 +08:00
05cc3f929a chore(deps): update v0.x 2025-05-10 01:57:03 +08:00
053501eff7 chore(deps): update dependency @types/node to ^22.15.17 2025-05-09 18:55:25 +08:00
严浩
ab70388a53 fix: 格式化 PinPuYi.vue 2025-05-09 16:51:03 +08:00
严浩
f34202febb feat: 频谱仪: 添加幅度菜单和单位支持 2025-05-09 16:48:07 +08:00
aafff004f4 chore(deps): update dependency vite to ^6.3.5 2025-05-08 21:51:14 +08:00
4c034e2f9c Merge pull request 'chore(deps): update lint dependencies' (#239) from renovate/lint into main
[skip ci ]
2025-05-08 20:38:38 +08:00
0d535f80d5 chore(deps): update lint dependencies 2025-05-08 19:36:43 +08:00
323fc6cce0 chore(deps): update dependency lucide-vue-next to ^0.508.0 2025-05-08 18:42:28 +08:00
60272a8f0a chore(deps): update dependency @types/node to ^22.15.16 2025-05-08 18:42:12 +08:00
d74b6fe015 chore(deps): update dependency unplugin-auto-import to ^19.2.0 2025-05-08 18:38:44 +08:00
d1980c567c chore(deps): update primevue pkgs 2025-05-08 18:38:28 +08:00
严浩
cef9ed6381 refactor: 优化频谱仪状态管理代码格式,提升可读性 2025-05-08 18:15:46 +08:00
严浩
b850ae82ae feat: 添加跨度菜单列表及其项目,更新频谱仪状态管理 2025-05-08 18:10:27 +08:00
严浩
243b2bbd6b fix: 移除不必要的 source.fixAll 配置 2025-05-08 14:07:40 +08:00
cd63d1d30b chore(deps): update dependency @types/node to ^22.15.14 2025-05-07 18:13:45 +08:00
0f9865d4d5 chore(deps): update dependency eslint-plugin-unicorn to ^59.0.1 2025-05-07 03:14:48 +08:00
1a6e5d0240 chore(deps): update lint dependencies to ^0.16.9 2025-05-06 13:50:00 +08:00
e70834af8f chore(deps): update dependency @types/node to ^22.15.11 2025-05-06 11:06:18 +08:00
bf10aa44da chore(deps): update lint dependencies 2025-05-06 08:18:40 +08:00
3e217c0db7 chore(deps): update dependency @types/node to ^22.15.7 2025-05-06 05:30:58 +08:00
cff5c098d8 chore(deps): update all non-major dependencies 2025-05-05 00:38:44 +08:00
严浩
7344359d1e chore(deps): 更新 eslint-plugin-unicorn 到 v59.0.0 2025-05-04 21:25:48 +08:00
5b42efc166 chore(deps): update vite packages 2025-05-03 16:52:33 +08:00
严浩
e5dd7aa70e chore: configure commitlint 2025-05-03 16:00:52 +08:00
ce914d3275 chore(deps): update dependency npm-run-all2 to v8 2025-05-03 03:28:48 +08:00
严浩
695e583136 feat: 添加对 .md 文件的类型声明 2025-05-02 23:10:38 +08:00
6f06a36e44 chore(deps): update lint dependencies to ^0.16.8 2025-04-29 18:49:09 +08:00
cf4ea2da16 chore(deps): update dependency @types/node to ^22.15.3 2025-04-29 13:04:42 +08:00
8aa35a14fb chore(deps): update dependency p5 to v2 2025-04-29 11:56:25 +08:00
6b52b7e87c chore(deps): update lint dependencies 2025-04-28 20:02:41 +08:00
严浩
6299b6d45f PinPuYi 2025-04-28 17:44:11 +08:00
严浩
5fe08b0741 PinPuYi 2025-04-28 17:02:47 +08:00
严浩
1ca2a17e4f 频谱仪: 屏幕照片 2025-04-28 15:41:26 +08:00
严浩
a44a455eca fix: 修改频率菜单和状态变量名称为中文 2025-04-28 14:05:24 +08:00
严浩
dd1af6964d feat(deps): add vite-plugin-image-tools dependency 2025-04-27 22:59:23 +08:00
0ceaa5a583 chore(deps): update dependency @intlify/unplugin-vue-i18n to ^6.0.8 2025-04-27 18:21:19 +08:00
严浩
c4a50b3642 临时屏幕.png 2025-04-27 18:08:35 +08:00
严浩
12bbc843fb PinPuYi.vue 2025-04-27 17:26:55 +08:00
严浩
e2c2cb165a PinPuYi.vue 2025-04-27 17:20:41 +08:00
严浩
c6f27856ef refactor(ci): split surge cleanup into separate job 2025-04-27 14:10:56 +08:00
608cd08ca5 chore(deps): update types 2025-04-25 20:22:47 +08:00
严浩
6b6ddeb23b PinPuYi.vue 2025-04-25 18:28:25 +08:00
严浩
1c73834dc8 PinPuYi.vue 2025-04-25 16:49:26 +08:00
严浩
e10538baca Add Helvetica-Light-05 font files in TTF, WOFF, and WOFF2 formats 2025-04-25 12:04:05 +08:00
严浩
e9c59a3634 PinPuYi.vue 2025-04-24 19:08:05 +08:00
严浩
2d6831fcb0 style(PinPuYi.vue): adjust image styling for better responsiveness 2025-04-24 17:51:46 +08:00
严浩
4164134a4e PinPuYi.vue 2025-04-24 17:50:59 +08:00
严浩
6c8529185a chore(vite.config): add unplugin-vue-router data loaders to optimizeDeps 2025-04-24 17:32:07 +08:00
严浩
6a615bcc43 chore(gitattributes): add .spline file type as binary 2025-04-24 17:27:04 +08:00
09e727dad5 chore(deps): update dependency tdesign-icons-vue-next to ^0.3.6 2025-04-24 10:39:43 +08:00
严浩
5a16b62008 chore(docs): add .gitattributes section to README and remove redundant content 2025-04-24 10:29:35 +08:00
4d6380f141 chore(deps): update dependency eslint-plugin-perfectionist to ^4.12.2 2025-04-24 07:34:15 +08:00
11acb951d4 chore(deps): update dependency three to ^0.176.0 2025-04-23 18:15:02 +08:00
94472a4d8d chore(deps): update lint dependencies 2025-04-22 11:33:38 +08:00
74d4f87f4b chore(deps): update v0.x 2025-04-22 09:35:50 +08:00
67cee3b845 chore(deps): update lint dependencies to ^0.16.7 2025-04-22 09:35:13 +08:00
71b768b3a2 chore(deps): update devdependencies 2025-04-22 08:42:06 +08:00
2cc080c579 chore(deps): update vite packages 2025-04-21 13:40:13 +08:00
5354f942e6 chore(deps): update dependency @intlify/unplugin-vue-i18n to ^6.0.7 2025-04-21 10:36:33 +08:00
e0445a2737 chore(deps): update playwright packages 2025-04-21 01:45:58 +08:00
8e9e4ca392 chore(deps): update dependency @types/node to ^22.14.1 2025-04-20 23:25:18 +08:00
102 changed files with 11052 additions and 5444 deletions

View File

@@ -1,6 +1,8 @@
[*.{js,jsx,mjs,cjs,ts,tsx,mts,cts,vue}]
[*.{js,jsx,mjs,cjs,ts,tsx,mts,cts,vue,css,scss,sass,less,styl}]
charset = utf-8
indent_size = 2
indent_style = space
insert_final_newline = true
trim_trailing_whitespace = true
end_of_line = lf
max_line_length = 120

1
.gitattributes vendored
View File

@@ -39,6 +39,7 @@ commit-msg text eol=lf
*.woff2 binary
*.eot binary
*.otf binary
*.spline binary
# Add more binary...

21
.gitattributes.MD Normal file
View File

@@ -0,0 +1,21 @@
### .gitattributes
如果你先推送了一个.jpg文件然后再推送包含.gitattributes文件的更新Git不会自动重新处理之前的.jpg文件的属性。为使.gitattributes中的新规则生效你可以通过以下步骤重新应用设置
```bash
1. 删除本地缓存的.jpg文件git rm --cached <file>.jpg
2. 重新添加该文件git add <file>.jpg
3. 再次提交并推送git commit -m "Apply .gitattributes changes" && git push
```
这样Git将按照.gitattributes中的新规则处理该文件。
要验证.gitattributes文件中的二进制配置是否生效可以使用以下方法
```bash
1. 推送后验证:先按照之前步骤重新添加并推送.jpg文件。
2. 查看差异diff执行 git diff检查文件是否有文本形式的改动。如果是二进制文件Git不会显示具体内容的差异。
3. Git日志验证执行 git log -p <file>.jpg 查看提交的改动记录,确认文件未受行尾或编码处理的影响。
```
如果以上测试显示该文件未发生不必要的改动,说明.gitattributes配置已生效。

View File

@@ -1,4 +1,11 @@
# Project Conventions and Technical Guidelines
---
description:
globs:
alwaysApply: true
---
# GitHub Copilot Instructions
本文件定义了项目的代码生成规范GitHub Copilot 和其他 AI 助手应遵循这些指令。
This document outlines the core technical choices, coding conventions, and configuration details for this project. Adhering to these guidelines ensures consistency and leverages the project's setup effectively.

View File

@@ -1,3 +0,0 @@
```bash
ln -s .github/copilot-instructions.md .roorules
```

View File

@@ -11,7 +11,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: 🛠️ 设置Node环境
uses: yanhao98/composite-actions/setup-node-environment@4470aa136359d05ae3b086d37cfaa33305448a5b
uses: yanhao98/composite-actions/setup-node-environment@25eb4dc0c134cc9df2b7c569aa54140a366b45a8
- name: 🔍 静态代码分析
run: pnpm run lint
- name: 📦 构建项目

View File

@@ -7,6 +7,13 @@ env:
on:
push:
workflow_dispatch:
inputs:
run_cleanup:
description: '是否运行 Surge 清理 Job'
required: false
type: boolean
default: true
jobs:
surge:
@@ -15,7 +22,7 @@ jobs:
url: ${{ steps.surge_deploy.outputs.url }}
steps:
- name: ⚙️ 设置 Node 环境
uses: yanhao98/composite-actions/setup-node-environment@4470aa136359d05ae3b086d37cfaa33305448a5b
uses: yanhao98/composite-actions/setup-node-environment@25eb4dc0c134cc9df2b7c569aa54140a366b45a8
- name: 🔨 构建项目
run: pnpm run build-only
env:
@@ -23,22 +30,31 @@ jobs:
- name: 🚀 部署到 Surge
id: surge_deploy
if: ${{ github.actor != 'nektos/act' }} # https://nektosact.com/usage/index.html#skipping-steps
uses: yanhao98/composite-actions/deploy-dist-to-surge@4470aa136359d05ae3b086d37cfaa33305448a5b
uses: yanhao98/composite-actions/deploy-dist-to-surge@25eb4dc0c134cc9df2b7c569aa54140a366b45a8
playwright:
needs: surge
runs-on: ubuntu-latest
container: mcr.microsoft.com/playwright:v1.51.1-noble
container: mcr.microsoft.com/playwright:v1.55.0-noble
steps:
- name: ⚙️ 设置 Node 环境
uses: yanhao98/composite-actions/setup-node-environment@4470aa136359d05ae3b086d37cfaa33305448a5b
uses: yanhao98/composite-actions/setup-node-environment@25eb4dc0c134cc9df2b7c569aa54140a366b45a8
# - name: 📥 安装 Playwright 浏览器
# run: pnpm exec playwright install --with-deps
- name: ▶️ 运行 Playwright 测试
run: npx playwright test
run: |
echo "BASE_URL: ${{ needs.surge.outputs.url }}"
echo "───────────────────────────────"
npx playwright test
env:
BASE_URL: ${{ needs.surge.outputs.url }}
cleanup_surge:
runs-on: ubuntu-latest
needs: [surge, playwright]
if: github.event_name == 'push' || github.event.inputs.run_cleanup == true
steps:
- name: 🧹 清理 Surge 部署
run: npx surge teardown ${{ needs.surge.outputs.url }} --token ${{ env.SURGE_TOKEN}}
run: npx surge teardown ${{ needs.surge.outputs.url }} --token ${{ env.SURGE_TOKEN }}
env:
SURGE_TOKEN: d843de16b331c626f10771245c56ed93

View File

@@ -14,7 +14,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: ⚙️ 设置 Node 环境
uses: yanhao98/composite-actions/setup-node-environment@4470aa136359d05ae3b086d37cfaa33305448a5b
uses: yanhao98/composite-actions/setup-node-environment@25eb4dc0c134cc9df2b7c569aa54140a366b45a8
- name: 📥 拉取 Vercel 环境信息
run: pnpm dlx vercel pull --yes --environment=production --token=${{ secrets.VERCEL_TOKEN }}

View File

@@ -1,5 +1,6 @@
# 此钩子在 pre-commit 钩子成功完成后,用于检查提交消息。
echo "📝 [Commit-msg] 正在运行 commit-msg 钩子..."
# 在这里添加你的 commit message 验证逻辑,例如 commitlint
echo "当前提交的文件是:$1"
# npx --no -- commitlint --edit "$1"
pnpm exec commitlint --edit $1
echo "✅ [Commit-msg] commit-msg 钩子完成!"

4
.npmrc
View File

@@ -5,9 +5,9 @@ registry=https://registry.npmjs.org/
# registry=https://nexus.oo1.dev/repository/npm/
# https://pnpm.io/zh/npmrc#node-mirrorltreleasedir
use-node-version=22.14.0
use-node-version=22.18.0
node-mirror:release=https://npmmirror.com/mirrors/node/ # pnpm config set node-mirror:release=https://npmmirror.com/mirrors/node/
node-mirror:rc=https://npmmirror.com/mirrors/node-rc/
node-mirror:nightly=https://npmmirror.com/mirrors/node-nightly/
shamefully-hoist=true
# shamefully-hoist=true

View File

@@ -4,7 +4,6 @@
"dbaeumer.vscode-eslint",
"esbenp.prettier-vscode",
"simonhe.common-intellisense",
"antfu.file-nesting",
"oxc.oxc-vscode"
]
}

View File

@@ -27,8 +27,7 @@
"source.fixAll.eslint": "never",
"source.fixAll.stylelint": "never",
"source.fixAll.oxc": "never",
"source.organizeImports": "never",
"source.fixAll": "never"
"source.organizeImports": "never"
},
"editor.formatOnSave": false,
"oxc.enable": true,

33
.vscode/tasks.json vendored Normal file
View File

@@ -0,0 +1,33 @@
{
"version": "2.0.0",
"tasks": [
{
"label": "🚀 dev",
"type": "shell",
"command": "pnpm run dev",
"isBackground": true,
"problemMatcher": {
"owner": "vite",
"pattern": {
"regexp": "."
},
"background": {
"activeOnStart": true,
"beginsPattern": ".*VITE.*",
"endsPattern": ".*ready in.*"
}
},
"group": {
"kind": "build",
"isDefault": true
},
"presentation": {
"reveal": "always",
"panel": "new"
},
"runOptions": {
"instanceLimit": 1
}
}
]
}

View File

@@ -15,33 +15,9 @@ pnpm install --registry=https://nexus.oo1.dev/repository/npm
pnpm run dev
```
## Tips
### .gitattributes
如果你先推送了一个.jpg文件然后再推送包含.gitattributes文件的更新Git不会自动重新处理之前的.jpg文件的属性。为使.gitattributes中的新规则生效你可以通过以下步骤重新应用设置
```bash
1. 删除本地缓存的.jpg文件git rm --cached <file>.jpg
2. 重新添加该文件git add <file>.jpg
3. 再次提交并推送git commit -m "Apply .gitattributes changes" && git push
```
这样Git将按照.gitattributes中的新规则处理该文件。
要验证.gitattributes文件中的二进制配置是否生效可以使用以下方法
```bash
1. 推送后验证:先按照之前步骤重新添加并推送.jpg文件。
2. 查看差异diff执行 git diff检查文件是否有文本形式的改动。如果是二进制文件Git不会显示具体内容的差异。
3. Git日志验证执行 git log -p <file>.jpg 查看提交的改动记录,确认文件未受行尾或编码处理的影响。
```
如果以上测试显示该文件未发生不必要的改动,说明.gitattributes配置已生效。
## Links
- https://github.com/hyoban-template/shadcn-vue-unocss-starter$0
- https://github.com/hyoban-template/shadcn-vue-unocss-starter
- [Performance API优化页面性能](https://juejin.cn/post/7238779568478552122)
- [vitepress-theme-demoblock](https://www.npmjs.com/package/vitepress-theme-demoblock)
- [Vite PWA](https://vite-pwa-org-zh.netlify.app/guide/)
@@ -53,10 +29,12 @@ pnpm run dev
---
- [如何建立一个最小重现](https://antfu.me/posts/why-reproductions-are-required-zh#如何建立一个最小重现)
- https://biomejs.dev/zh-cn/internals/language-support/
- https://github.dev/antfu-collective/vitesse/
- [Vue3 入门指南与实战案例](https://vue3.chengpeiquan.com/)
- [如何建立一个最小重现](https://antfu.me/posts/why-reproductions-are-required-zh#如何建立一个最小重现)
---
- [primevue-scopedtokens](https://primevue.org/theming/styled/#scopedtokens)
+ https://www.npmjs.com/package/npkill

9
commitlint.config.ts Normal file
View File

@@ -0,0 +1,9 @@
import type { UserConfig } from '@commitlint/types';
const Configuration: UserConfig = {
extends: ['@commitlint/config-conventional'],
formatter: '@commitlint/format',
};
export default Configuration;

View File

@@ -1,9 +1,8 @@
import { test } from '@playwright/test';
import { expect, test } from '@playwright/test';
import { consola } from 'consola';
test('页面加载正常', async ({ page }, testInfo) => {
await page.goto('/Home');
consola.info(`page.url() === ${page.url()}`);
// await expect(page.getByRole('link', { name: '中文-页面.page.vue' })).toBeVisible();
@@ -15,4 +14,5 @@ test('页面加载正常', async ({ page }, testInfo) => {
// 获取元素的文本内容
const innerText = await commitElement.textContent();
consola.debug(`Commit 文本内容: "${innerText}"`);
expect(innerText).toContain('commit:');
});

9
env.d.ts vendored Normal file
View File

@@ -0,0 +1,9 @@
/// <reference types="vite/client" />
/// <reference types="vitest" />
/// <reference types="vite-plugin-vue-layouts/client" />
/// <reference types="vite-plugin-vue-meta-layouts/client" />
/* /// <reference types="vite-plugin-pwa/client" /> */
/// <reference types="unplugin-vue-macros/macros-global" />
/// <reference types="unplugin-vue-router/client" />
/// <reference types="unplugin-icons/types/vue" />
/// <reference types="@intlify/unplugin-vue-i18n/messages" />

View File

@@ -6,8 +6,9 @@ import pluginVitest from '@vitest/eslint-plugin';
import skipFormatting from '@vue/eslint-config-prettier/skip-formatting';
import { configureVueProject, defineConfigWithVueTs, vueTsConfigs } from '@vue/eslint-config-typescript';
import { flatConfigs as eslintPluginImportX_flatConfigs } from 'eslint-plugin-import-x';
import oxlint from 'eslint-plugin-oxlint';
import pluginOxlint from 'eslint-plugin-oxlint';
import perfectionist from 'eslint-plugin-perfectionist';
import pluginPlaywright from 'eslint-plugin-playwright';
import eslintPluginUnicorn from 'eslint-plugin-unicorn';
import pluginVue from 'eslint-plugin-vue';
@@ -16,15 +17,13 @@ configureVueProject({ scriptLangs: ['ts', 'tsx', 'js', 'jsx'] });
const _ignores = [
// >>>
// eslint-disable-next-line unicorn/no-await-expression-member
(await import('@eslint/compat')).includeIgnoreFile(
// eslint-disable-next-line unicorn/import-style, unicorn/no-await-expression-member
// eslint-disable-next-line unicorn/import-style
(await import('node:path')).default.resolve(import.meta.dirname, '.gitignore'),
),
// <<<
// >>>
// eslint-disable-next-line unicorn/no-await-expression-member
(await import('eslint/config')).globalIgnores([
'**/dist/**',
'**/dist-ssr/**',
@@ -44,6 +43,7 @@ const _ignores = [
];
export default defineConfigWithVueTs(
// >>> create vue >>>
{
name: 'app/files-to-lint',
files: ['**/*.{ts,mts,tsx,vue}'],
@@ -59,8 +59,13 @@ export default defineConfigWithVueTs(
files: ['src/**/__tests__/*'],
},
...oxlint.configs['flat/recommended'],
{
...pluginPlaywright.configs['flat/recommended'],
files: ['e2e/playwright/**/*.{test,spec}.{js,ts,jsx,tsx}'],
},
...pluginOxlint.configs['flat/recommended'],
skipFormatting,
// <<< create vue <<<
// region >> eslint-plugin-unicorn >>
eslintPluginUnicorn.configs.recommended,
@@ -72,6 +77,7 @@ export default defineConfigWithVueTs(
'unicorn/no-useless-spread': 'off',
'unicorn/prevent-abbreviations': 'off',
'unicorn/relative-url-style': 'off', // [plugin:vite:import-glob] Invalid glob: "imgs/*.png" (resolved: "imgs/*.png"). It must start with '/' or './'
'unicorn/no-await-expression-member': 'off',
},
},
// endregion <<< eslint-plugin-unicorn <<<
@@ -84,6 +90,8 @@ export default defineConfigWithVueTs(
'import-x/newline-after-import': 'error',
'import-x/first': 'error',
'import-x/no-named-as-default': 'off',
'import-x/no-duplicates': 'off',
'import-x/named': 'off',
},
},
// endregion <<< eslint-plugin-import-x <<<
@@ -99,6 +107,8 @@ export default defineConfigWithVueTs(
'perfectionist/sort-imports': ['error'],
'perfectionist/sort-modules': 'off',
'perfectionist/sort-object-types': 'off',
'perfectionist/sort-enums': 'off',
'perfectionist/sort-union-types': 'off',
},
},
// endregion <<< eslint-plugin-perfectionist <<<

120
fake/xml.fake.ts Normal file
View File

@@ -0,0 +1,120 @@
import { defineFakeRoute } from 'vite-plugin-fake-server/client';
// 简单的实体转义,避免在 XML 中出现非法字符;先替换 & 再替换其他符号,防止二次转义
function esc(s: string) {
return s
.replaceAll('&', '&amp;')
.replaceAll('<', '&lt;')
.replaceAll('>', '&gt;')
.replaceAll('"', '&quot;')
.replaceAll("'", '&apos;');
}
// 从原始 XML 文本中提取某个标签的文本内容(弱解析,仅用于演示)
function pickTag(xml: string, tag: string) {
// 说明使用非贪婪匹配并允许标签上带属性N.B. 未处理命名空间与嵌套同名标签的复杂情形
const re = new RegExp(`<${tag}(?:\n|\r|s|>|/)[^>]*>([sS]*?)</${tag}>`, 'i');
const m = xml.match(re);
return m?.[1]?.trim() ?? '';
}
// 通过 rawResponse 返回 XML 文本,便于在浏览器端演示 XML 解析
export default defineFakeRoute([
{
method: 'GET',
url: '/xml/sample',
rawResponse(req, res) {
// 通过查询参数自定义返回的 XML 字段
// 支持的参数to, from, heading, body, id, createdAt
const url = new URL(req.url!, 'http://localhost');
const q = url.searchParams;
const to = q.get('to') ?? 'George';
const from = q.get('from') ?? 'John';
const heading = q.get('heading') ?? 'Reminder';
const body = q.get('body') ?? "Don't forget the meeting at 3 PM today.";
const id = q.get('id') ?? '42';
const createdAt = q.get('createdAt') ?? '2025-09-02T10:00:00Z';
const xml = `<?xml version="1.0" encoding="UTF-8"?>
<note>
<to>${esc(to)}</to>
<from>${esc(from)}</from>
<heading>${esc(heading)}</heading>
<body>${esc(body)}</body>
<meta>
<id>${esc(id)}</id>
<createdAt>${esc(createdAt)}</createdAt>
</meta>
</note>`;
res.writeHead(200, { 'Content-Type': 'application/xml; charset=UTF-8' });
res.end(xml);
},
},
{
method: 'POST',
url: '/xml/submit',
rawResponse(req, res) {
const chunks: Buffer[] = [];
req.on('data', (chunk) => chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk)));
req.on('end', () => {
const contentType = String(req.headers['content-type'] || '').toLowerCase();
const raw = Buffer.concat(chunks).toString('utf8');
// 默认值,与 GET 一致
let to = 'George';
let from = 'John';
let heading = 'Reminder';
let body = "Don't forget the meeting at 3 PM today.";
let id = '42';
let createdAt = new Date().toISOString();
try {
if (contentType.includes('application/json')) {
const data = JSON.parse(raw || '{}');
to = data.to ?? to;
from = data.from ?? from;
heading = data.heading ?? heading;
body = data.body ?? body;
id = data.id ?? id;
createdAt = data.createdAt ?? createdAt;
} else if (contentType.includes('application/xml') || contentType.includes('text/xml')) {
// 解析 XML 请求体中常用字段(演示用,未处理命名空间/CDATA 等复杂场景)
to = pickTag(raw, 'to') || to;
from = pickTag(raw, 'from') || from;
heading = pickTag(raw, 'heading') || heading;
body = pickTag(raw, 'body') || body;
id = pickTag(raw, 'id') || id;
createdAt = pickTag(raw, 'createdAt') || createdAt;
} else if (contentType.includes('application/x-www-form-urlencoded')) {
// 简单解析表单key=value&…;此处演示用途,未处理复杂编码场景
const params = new URLSearchParams(raw);
to = params.get('to') ?? to;
from = params.get('from') ?? from;
heading = params.get('heading') ?? heading;
body = params.get('body') ?? body;
id = params.get('id') ?? id;
createdAt = params.get('createdAt') ?? createdAt;
}
} catch {
// 忽略解析错误,保持默认值
}
const xml = `<?xml version="1.0" encoding="UTF-8"?>
<note>
<to>${esc(to)}</to>
<from>${esc(from)}</from>
<heading>${esc(heading)}</heading>
<body>${esc(body)}</body>
<meta>
<id>${esc(id)}</id>
<createdAt>${esc(createdAt)}</createdAt>
</meta>
</note>`;
res.writeHead(200, { 'Content-Type': 'application/xml; charset=UTF-8' });
res.end(xml);
});
},
},
]);

View File

@@ -1,5 +1,5 @@
{
"packageManager": "pnpm@10.8.0",
"packageManager": "pnpm@10.15.1",
"name": "vue-ts-example",
"version": "0.0.0",
"private": true,
@@ -9,15 +9,17 @@
"all": "run-p build-only format type-check lint",
"build": "run-p type-check \"build-only {@}\" --",
"build-only": "vite build",
"preview": "vite preview --port 4173",
"lint-format": "run-p lint:oxlint lint:eslint format",
"format": "prettier --write src/",
"type-check": "vue-tsc --build",
"lint": "run-s lint:*",
"_oxlint_cfg": "oxlint . --fix --ignore-path=.gitignore --print-config",
"__oxlint_-D": "oxlint . --fix --deny=correctness",
"lint:oxlint": "oxlint --fix",
"lint:oxlint": "oxlint . --fix -D correctness --ignore-path .gitignore",
"lint:eslint": "eslint . --fix",
"prepare": "husky",
"test:unit": "vitest",
"playwright": "playwright test",
"playwright:headless": "HEADLESS=true playwright test",
"playwright:ui": "playwright test --ui",
@@ -30,7 +32,7 @@
"knip": "pnpm dlx knip"
},
"lint-staged": {
"src/**/*.{js,jsx,ts,tsx,vue}": [
"{src,e2e}/**/*.{js,jsx,ts,tsx,vue}": [
"prettier --write",
"eslint --fix",
"oxlint --fix"
@@ -39,126 +41,141 @@
"pnpm": {
"overrides": {
"vite": "$vite",
"vue-tsc": "$vue-tsc",
"@primevue/auto-import-resolver": "$primevue"
}
},
"dependencies": {
"@alova/adapter-axios": "^2.0.13",
"@formkit/auto-animate": "^0.8.2",
"@intlify/unplugin-vue-i18n": "^6.0.6",
"@pinia/colada": "^0.14.2",
"@primeuix/themes": "^1.0.3",
"@splinetool/runtime": "^1.9.82",
"@alova/adapter-axios": "^2.0.16",
"@ant-design/icons-vue": "^7.0.1",
"@formkit/auto-animate": "^0.8.4",
"@intlify/unplugin-vue-i18n": "^11.0.0",
"@pinia/colada": "^0.17.3",
"@primeuix/themes": "^1.2.3",
"@primevue/icons": "^4.3.8",
"@splinetool/runtime": "^1.10.53",
"@types/p5": "^1.7.6",
"@types/sortablejs": "^1.15.8",
"@unhead/vue": "^2.0.5",
"@unhead/vue": "^2.0.14",
"@vant/use": "^1.6.0",
"@vueuse/core": "^13.1.0",
"alova": "^3.2.10",
"@vueuse/core": "^13.9.0",
"alova": "^3.3.4",
"ant-design-vue": "~4.2.6",
"axios": "^1.8.4",
"cesium": "^1.128.0",
"axios": "^1.11.0",
"cesium": "^1.132.0",
"class-variance-authority": "^0.7.1",
"clsx": "^2.1.1",
"consola": "^3.4.2",
"dayjs": "^1.11.13",
"deep-freeze-es6": "^4.0.0",
"jsencrypt": "^3.3.2",
"lucide-vue-next": "^0.487.0",
"dayjs": "^1.11.18",
"deep-freeze-es6": "^4.0.1",
"jsencrypt": "^3.5.4",
"lucide-vue-next": "^0.542.0",
"mitt": "^3.0.1",
"nprogress": "^0.2.0",
"p5": "^1.11.3",
"p5": "^2.0.5",
"page-stack-vue3": "^2.5.6",
"pinia": "^3.0.1",
"pinia-plugin-persistedstate": "^4.2.0",
"plotly.js-dist-min": "^3.0.1",
"pinia": "^3.0.3",
"pinia-plugin-persistedstate": "^4.5.0",
"plotly.js-dist-min": "^3.1.0",
"primeicons": "^7.0.0",
"primelocale": "^2.1.2",
"primevue": "^4.3.3",
"radash": "^12.1.0",
"primelocale": "^2.1.7",
"primevue": "^4.3.8",
"radash": "^12.1.1",
"radix-vue": "^1.9.17",
"reka-ui": "^2.2.0",
"satellite.js": "^6.0.0",
"reka-ui": "^2.5.0",
"satellite.js": "^6.0.1",
"sortablejs": "^1.15.6",
"tailwind-merge": "^3.2.0",
"tdesign-icons-vue-next": "^0.3.5",
"three": "^0.175.0",
"tailwind-merge": "^3.3.1",
"tdesign-icons-vue-next": "^0.3.7",
"three": "^0.179.1",
"ts-enum-util": "^4.1.0",
"utils4u": "^4.2.3",
"vant": "^4.9.18",
"vue": "^3.5.13",
"vant": "^4.9.21",
"vite-plugin-image-optimizer": "^2.0.2",
"vue": "^3.5.20",
"vue-data-ui": "^3.0.15",
"vue-draggable-plus": "^0.6.0",
"vue-i18n": "^11.1.3",
"vue-i18n": "^11.1.11",
"vue-page-stack": "^3.2.0",
"vue-router": "^4.5.0",
"vuetify": "^3.8.2"
"vue-router": "^4.5.1"
},
"devDependencies": {
"@eslint/compat": "^1.2.8",
"@faker-js/faker": "^9.6.0",
"@iconify-json/carbon": "^1.2.8",
"@iconify-json/logos": "^1.2.4",
"@commitlint/cli": "^19.8.1",
"@commitlint/config-conventional": "^19.8.1",
"@eslint/compat": "^1.3.2",
"@faker-js/faker": "^10.0.0",
"@iconify-json/carbon": "^1.2.13",
"@iconify-json/logos": "^1.2.9",
"@iconify-json/mdi": "^1.2.3",
"@iconify/utils": "^2.3.0",
"@playwright/test": "^1.51.1",
"@primevue/auto-import-resolver": "^4.3.3",
"@tsconfig/node22": "^22.0.1",
"@iconify/utils": "^3.0.1",
"@playwright/test": "^1.55.0",
"@prettier/plugin-oxc": "^0.0.4",
"@primevue/auto-import-resolver": "^4.3.8",
"@primevue/metadata": "^4.3.8",
"@tsconfig/node22": "^22.0.2",
"@types/archiver": "^6.0.3",
"@types/jsdom": "^21.1.7",
"@types/mockjs": "^1.0.10",
"@types/node": "^22.14.0",
"@types/node": "^24.3.0",
"@types/nprogress": "^0.2.3",
"@types/plotly.js-dist-min": "^2.3.4",
"@types/three": "^0.175.0",
"@types/three": "^0.180.0",
"@unocss/preset-attributify": "^66.5.0",
"@unocss/reset": "^66.5.0",
"@vant/auto-import-resolver": "^1.3.0",
"@vitejs/plugin-vue": "^5.2.3",
"@vitejs/plugin-vue-jsx": "^4.1.2",
"@vitest/eslint-plugin": "^1.1.40",
"@vitejs/plugin-vue": "^6.0.1",
"@vitejs/plugin-vue-jsx": "^5.1.1",
"@vitest/eslint-plugin": "^1.3.6",
"@vue/eslint-config-prettier": "^10.2.0",
"@vue/eslint-config-typescript": "^14.5.0",
"@vue/eslint-config-typescript": "^14.6.0",
"@vue/test-utils": "^2.4.6",
"@vue/tsconfig": "^0.7.0",
"@vue/tsconfig": "^0.8.1",
"archiver": "^7.0.1",
"create-vue": "3.18.0",
"depcheck": "^1.4.7",
"eruda": "^3.4.1",
"eslint": "^9.24.0",
"eslint-plugin-import-x": "^4.10.2",
"eslint-plugin-oxlint": "^0.16.6",
"eslint-plugin-perfectionist": "^4.11.0",
"eslint-plugin-unicorn": "^58.0.0",
"eslint-plugin-vue": "^10.0.0",
"eruda": "^3.4.3",
"eslint": "^9.34.0",
"eslint-plugin-import-x": "^4.16.1",
"eslint-plugin-oxlint": "^1.14.0",
"eslint-plugin-perfectionist": "^4.15.0",
"eslint-plugin-playwright": "^2.2.2",
"eslint-plugin-unicorn": "^61.0.2",
"eslint-plugin-vue": "^10.4.0",
"husky": "^9.1.7",
"less": "^4.3.0",
"lint-staged": "^15.5.0",
"jiti": "^2.5.1",
"jsdom": "^26.1.0",
"less": "^4.4.1",
"lint-staged": "^16.1.6",
"mockjs": "^1.1.0",
"npm-run-all2": "^7.0.2",
"oxlint": "^0.16.6",
"prettier": "3.5.3",
"sass-embedded": "^1.86.3",
"terser": "^5.39.0",
"typescript": "~5.8.3",
"unocss": "66.1.0-beta.10",
"unocss-preset-animations": "^1.1.1",
"naive-ui": "^2.42.0",
"npm-run-all2": "^8.0.4",
"oxlint": "~1.14.0",
"prettier": "3.6.2",
"spritesmith": "^3.5.1",
"typescript": "~5.9.2",
"unocss": "66.5.0",
"unocss-preset-animations": "^1.2.1",
"unocss-preset-chinese": "^0.3.3",
"unocss-preset-shadcn": "^0.5.0",
"unplugin-auto-import": "^19.1.2",
"unplugin-icons": "^22.1.0",
"unplugin-vue-components": "^28.5.0",
"unplugin-auto-import": "^20.1.0",
"unplugin-icons": "^22.2.0",
"unplugin-vue-components": "^29.0.0",
"unplugin-vue-macros": "^2.14.5",
"unplugin-vue-markdown": "^28.3.1",
"unplugin-vue-router": "^0.12.0",
"vite": "^6.2.6",
"vite-plugin-checker": "^0.9.1",
"unplugin-vue-markdown": "^29.1.0",
"unplugin-vue-router": "^0.15.0",
"vfonts": "^0.0.3",
"vite": "npm:rolldown-vite@^7.1.5",
"vite-plugin-checker": "^0.10.3",
"vite-plugin-fake-server": "^2.2.0",
"vite-plugin-purgecss-updated-v5": "^1.2.4",
"vite-plugin-singlefile": "^2.2.0",
"vite-plugin-static-copy": "^2.3.1",
"vite-plugin-vue-devtools": "^7.7.2",
"vite-plugin-image-tools": "^3.0.0",
"vite-plugin-purgecss-updated-v5": "^1.2.6",
"vite-plugin-singlefile": "^2.3.0",
"vite-plugin-static-copy": "^3.1.2",
"vite-plugin-vue-devtools": "^8.0.1",
"vite-plugin-vue-layouts": "^0.11.0",
"vite-plugin-vue-meta-layouts": "^0.5.1",
"vite-plugin-vuetify": "^2.1.1",
"vite-plugin-webfont-dl": "^3.10.4",
"vue-component-type-helpers": "^2.2.8",
"vue-tsc": "^2.2.8"
"vite-plugin-vue-meta-layouts": "^0.6.0",
"vite-plugin-webfont-dl": "^3.11.1",
"vitest": "^3.2.4",
"vue-component-type-helpers": "^3.0.6",
"vue-tsc": "^3.0.6"
}
}

View File

@@ -1,6 +1,9 @@
import { defineConfig, devices } from '@playwright/test';
import process from 'node:process';
const runningInVSCode = process.env.TERM_PROGRAM === 'vscode';
const baseURL = runningInVSCode ? 'http://localhost:4173' : process.env.BASE_URL || 'https://vue-ts-example.oo1.dev';
/**
* Read environment variables from file.
* https://github.com/motdotla/dotenv
@@ -58,7 +61,7 @@ export default defineConfig({
reporter: 'html',
/* Retry on CI only */
retries: process.env.CI ? 2 : 0,
testDir: './tests/playwright',
testDir: './e2e/playwright',
/* Maximum time one test can run for. */
timeout: 30 * 1000,
/* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */
@@ -66,7 +69,7 @@ export default defineConfig({
/* Maximum time each action such as `click()` can take. Defaults to 0 (no limit). */
actionTimeout: 0,
/* Base URL to use in actions like `await page.goto('/')`. */
baseURL: process.env.BASE_URL || 'https://vue-ts-example.oo1.dev',
baseURL,
/* Only on CI systems run the tests headless */
headless: !!process.env.CI || process.env.HEADLESS === 'true',
@@ -76,16 +79,13 @@ export default defineConfig({
},
// /* Run your local dev server before starting the tests */
// webServer: {
// /**
// * Use the dev server by default for faster feedback loop.
// * Use the preview server on CI for more realistic testing.
// * Playwright will re-use the local server if there is already a dev-server running.
// */
// command: process.env.CI ? 'npm run preview' : 'npm run dev',
// port: process.env.CI ? 4173 : 5173,
// reuseExistingServer: !process.env.CI,
// },
webServer: runningInVSCode
? {
command: 'pnpm run build-only; pnpm run preview',
port: 4173,
reuseExistingServer: true,
}
: undefined,
/* Folder for test artifacts such as screenshots, videos, traces, etc. */
// outputDir: 'test-results/',

9168
pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,4 +1,5 @@
{
"$schema": "https://docs.renovatebot.com/renovate-schema.json",
"extends": ["https://git.1-h.cc/examples/renovate-example/raw/branch/main/default.json5", ":automergeMinor"]
"extends": ["https://git.1-h.cc/examples/renovate-example/raw/branch/main/default.json5", ":automergeMinor"],
"postUpdateOptions": ["pnpmDedupe"]
}

View File

@@ -15,9 +15,11 @@ const themeConfig = computed(() => {
</script>
<template>
<a-config-provider :theme="themeConfig">
<RouterView />
</a-config-provider>
<n-config-provider preflight-style-disabled>
<a-config-provider :theme="themeConfig">
<RouterView />
</a-config-provider>
</n-config-provider>
<DynamicDialog /> <ConfirmDialog /> <Toast />
</template>

View File

@@ -72,9 +72,9 @@ const orbitRadii = new Array(SPHERE_COUNT)
const thetas = new Array(SPHERE_COUNT).fill(0).map(() => randRange(PI2));
const phis = new Array(SPHERE_COUNT).fill(0).map(() => randRange(PI2));
const positions: [number, number, number][] = orbitRadii.map((rad, i) => [
rad * cos(thetas[i]) * sin(phis[i]),
rad * sin(thetas[i]) * sin(phis[i]),
rad * cos(phis[i]),
rad * cos(thetas[i]!) * sin(phis[i]!),
rad * sin(thetas[i]!) * sin(phis[i]!),
rad * cos(phis[i]!),
]);
const sphereGeometry = new SphereGeometry(SPHERE_SCALE_COEFF);
@@ -93,7 +93,7 @@ const bgMaterial = getGradientMaterial(
BG_COLOR_BOTTOM_ORANGISH,
BG_COLOR_TOP_ORANGISH,
);
bgMaterial.uniforms.uTemperatureVariancePeriod.value = new Vector3(0, 0, 0.1);
bgMaterial.uniforms.uTemperatureVariancePeriod!.value = new Vector3(0, 0, 0.1);
function seededRandom(a: number) {
return function () {
@@ -234,17 +234,17 @@ function createScene() {
const thetas = new Array(SPHERE_COUNT).fill(0).map(() => randRange(PI2));
const phis = new Array(SPHERE_COUNT).fill(0).map(() => randRange(PI2));
const positions = orbitRadii.map((rad, i) => [
rad * cos(thetas[i]) * sin(phis[i]),
rad * sin(thetas[i]) * sin(phis[i]),
rad * cos(phis[i]),
rad * cos(thetas[i]!) * sin(phis[i]!),
rad * sin(thetas[i]!) * sin(phis[i]!),
rad * cos(phis[i]!),
]);
for (let i = 0; i < SPHERE_COUNT; i++) {
const sphere = new Mesh(sphereGeometry, sphereMaterial);
const [x, y, z] = positions[i];
const [x, y, z] = positions[i]!;
const scaleVector = sizes[i];
sphere.scale.set(scaleVector, scaleVector, scaleVector);
sphere.position.set(x, y, z);
sphere.scale.set(scaleVector!, scaleVector!, scaleVector!);
sphere.position.set(x!, y!, z!);
spheres.push(sphere);
scene.add(sphere);
}
@@ -258,11 +258,11 @@ function animate() {
const elapsed = clock.getElapsedTime();
const temperature = sin(elapsed * 0.5) * 0.5 + 0.5;
bgMaterial.uniforms.uTemperature.value = temperature;
bgMaterial.uniforms.uElapsedTime.value = elapsed;
bgMaterial.uniforms.uTemperature!.value = temperature;
bgMaterial.uniforms.uElapsedTime!.value = elapsed;
sphereMaterial.uniforms.uTemperature.value = temperature;
sphereMaterial.uniforms.uElapsedTime.value = elapsed;
sphereMaterial.uniforms.uTemperature!.value = temperature;
sphereMaterial.uniforms.uElapsedTime!.value = elapsed;
// Floating effect for spheres
spheres.forEach((sphere, index) => {
@@ -270,7 +270,7 @@ function animate() {
const floatFactor = 2; // Adjust this value to control float intensity
const speed = 0.3; // Adjust this value to control float speed
const floatY = sin(elapsed * speed + index) * floatFactor;
sphere.position.y = basePosition[1] + floatY;
sphere.position.y = basePosition![1] + floatY;
});
renderer.render(scene, camera);

View File

@@ -0,0 +1,58 @@
export enum E_选项卡菜单 {
= '幅度单位列表',
= '幅度菜单列表',
= '跨度菜单列表',
= '频率单位列表',
= '频率菜单列表',
}
export enum E_幅度菜单列表的项目 {
RefLevel = 'Ref Level',
// Attenuation = 'Attenuation', // 如果衰减也需要类似交互,则取消注释
}
export enum E_幅度单位 {
dBm = 'dBm',
mV = 'mV',
uA = 'µA', // 使用 uA 表示微安
uV = 'µV', // 使用 uV 表示微伏
}
export enum E_跨度菜单列表的项目 {
FullSpan = 'Full Span',
LastSpan = 'Last Span',
ZeroSpan = 'Zero Span',
}
/* 值是在屏幕上显示的值。 */
export enum E_选项卡菜单Freq菜单列表的项目 {
CenterFreq = 'Center',
StartFreq = 'Start',
StopFreq = 'Stop',
// CFStep = 'CFStep',
// FreqOffset = 'FreqOffset',
// SingnalTrack = 'SingnalTrack',
// ScaleType = 'ScaleType',
}
export enum E_Freq单位 {
GHz = 'GHz',
Hz = 'Hz',
kHz = 'kHz',
MHz = 'MHz',
}
export enum E_数字键盘按键 {
Dot = '.',
Num0 = 0,
Num1 = 1,
Num2 = 2,
Num3 = 3,
Num4 = 4,
Num5 = 5,
Num6 = 6,
Num7 = 7,
Num8 = 8,
Num9 = 9,
PlusMinus = '+/-',
}

View File

@@ -0,0 +1,13 @@
@font-face {
font-family: 'Helvetica Custom';
font-style: normal;
font-weight: 400; /* 400 通常代表 'normal' 或 'regular' */
src: url('fonts/Helvetica-Light-05.eot');
src:
url('fonts/Helvetica-Light-05.eot?#iefix') format('embedded-opentype'),
url('fonts/Helvetica-Light-05.woff2') format('woff2'),
url('fonts/Helvetica-Light-05.woff') format('woff'),
url('fonts/Helvetica-Light-05.ttf') format('truetype'),
url('fonts/Helvetica-Light-05.svg#Helvetica') format('svg');
font-display: swap; /* 推荐 */
}

View File

@@ -0,0 +1,805 @@
<!-- 频谱仪 -->
<script setup lang="ts">
import { $enum } from 'ts-enum-util';
import {
E_Freq单位,
E_幅度单位,
E_幅度菜单列表的项目,
E_数字键盘按键,
E_跨度菜单列表的项目,
E_选项卡菜单,
E_选项卡菜单Freq菜单列表的项目,
} from './CONST';
import 设备照片 from './pin-pu-yi-bg.png';
const state = reactive({
选项卡当前显示: E_选项卡菜单.频率菜单列表,
频谱仪状态: {
频率输入状态: {
选中的频率菜单列表的项目: null as E_选项卡菜单Freq菜单列表的项目 | null,
输入的值临时输入字符串: null as null | string, // 用于暂存用户输入
},
幅度输入状态: {
选中的幅度菜单列表的项目: null as E_幅度菜单列表的项目 | null,
输入的值临时输入字符串: null as null | string, // 用于暂存用户输入
refLevel: {
value: 0,
unit: E_幅度单位.dBm,
},
// attenuation: { value: 0, unit: E_幅度单位.dB }, // 如果需要衰减值
},
center: {
value: 0,
unit: E_Freq单位.Hz,
},
start: {
value: 0,
unit: E_Freq单位.Hz,
},
stop: {
value: 0,
unit: E_Freq单位.Hz,
},
span: {
currentSpan: {
start: 0,
stop: 0,
unit: E_Freq单位.Hz,
},
lastSpan: {
start: 0,
stop: 0,
unit: E_Freq单位.Hz,
},
fullSpan: {
start: 0,
stop: 3,
unit: E_Freq单位.GHz,
},
zeroSpan: false,
},
},
});
const 选项卡标题 = computed(() =>
$enum.mapValue(state.选项卡当前显示).with({
[E_选项卡菜单.频率菜单列表]: 'Freq/Channel',
[E_选项卡菜单.频率单位列表]: '',
[E_选项卡菜单.跨度菜单列表]: 'Span',
[E_选项卡菜单.幅度菜单列表]: 'Amplitude',
[E_选项卡菜单.幅度单位列表]: '',
}),
);
function 执行点击屏幕右边的按钮(按钮序号: number) {
console.group('🔘 点击屏幕右边的按钮', {
按钮序号,
选项卡当前显示: state.选项卡当前显示,
});
$enum.visitValue(state.选项卡当前显示).with({
[E_选项卡菜单.频率菜单列表]: () => {
switch (按钮序号) {
case 1: {
state.频谱仪状态.频率输入状态.选中的频率菜单列表的项目 = E_选项卡菜单Freq菜单列表的项目.CenterFreq;
console.debug('设置 Freq 输入状态为 CenterFreq');
break;
}
case 2: {
state.频谱仪状态.频率输入状态.选中的频率菜单列表的项目 = E_选项卡菜单Freq菜单列表的项目.StartFreq;
console.debug('设置 Freq 输入状态为 StartFreq');
break;
}
case 3: {
state.频谱仪状态.频率输入状态.选中的频率菜单列表的项目 = E_选项卡菜单Freq菜单列表的项目.StopFreq;
console.debug('设置 Freq 输入状态为 StopFreq');
break;
}
default: {
console.debug('频率菜单列表 - 这个按键还没有实现');
break;
}
}
},
[E_选项卡菜单.频率单位列表]: () => {
let selectedUnit: E_Freq单位 | null = null;
switch (按钮序号) {
case 1: {
selectedUnit = E_Freq单位.GHz;
break;
}
case 2: {
selectedUnit = E_Freq单位.MHz;
break;
}
case 3: {
selectedUnit = E_Freq单位.kHz;
break;
}
case 4: {
selectedUnit = E_Freq单位.Hz;
break;
}
default: {
console.warn('无效的频率单位按钮序号:', 按钮序号);
console.groupEnd();
return;
}
}
const tempInputString = state.频谱仪状态.频率输入状态.输入的值临时输入字符串;
const selectedFreqItem = state.频谱仪状态.频率输入状态.选中的频率菜单列表的项目;
// 必须有临时输入值和选中的 Freq 项目才能确认
if (!tempInputString || !selectedFreqItem) {
console.warn('频率输入未完成或未选择项目,无法确认单位。');
console.groupEnd();
return;
}
const inputValue = Number.parseFloat(tempInputString);
// 校验解析结果
if (Number.isNaN(inputValue)) {
console.warn('无法将临时输入解析为有效数字 (频率):', tempInputString);
console.groupEnd();
return;
}
console.debug('确认频率单位:', selectedUnit, '值为:', inputValue);
switch (selectedFreqItem) {
case E_选项卡菜单Freq菜单列表的项目.CenterFreq: {
state.频谱仪状态.center.value = inputValue;
state.频谱仪状态.center.unit = selectedUnit;
console.debug('更新 CenterFreq:', state.频谱仪状态.center);
break;
}
case E_选项卡菜单Freq菜单列表的项目.StartFreq: {
state.频谱仪状态.start.value = inputValue;
state.频谱仪状态.start.unit = selectedUnit;
console.debug('更新 StartFreq:', state.频谱仪状态.start);
break;
}
case E_选项卡菜单Freq菜单列表的项目.StopFreq: {
state.频谱仪状态.stop.value = inputValue;
state.频谱仪状态.stop.unit = selectedUnit;
console.debug('更新 StopFreq:', state.频谱仪状态.stop);
break;
}
}
state.频谱仪状态.频率输入状态.选中的频率菜单列表的项目 = null;
state.频谱仪状态.频率输入状态.输入的值临时输入字符串 = null;
state.选项卡当前显示 = E_选项卡菜单.频率菜单列表;
console.debug('频率输入状态已重置, 切换回频率菜单列表');
},
[E_选项卡菜单.跨度菜单列表]: () => {
switch (按钮序号) {
case 1: {
// Full Span
state.频谱仪状态.span.lastSpan.start = state.频谱仪状态.start.value;
state.频谱仪状态.span.lastSpan.stop = state.频谱仪状态.stop.value;
state.频谱仪状态.span.lastSpan.unit = state.频谱仪状态.stop.unit;
state.频谱仪状态.start.value = state.频谱仪状态.span.fullSpan.start;
state.频谱仪状态.start.unit = state.频谱仪状态.span.fullSpan.unit;
state.频谱仪状态.stop.value = state.频谱仪状态.span.fullSpan.stop;
state.频谱仪状态.stop.unit = state.频谱仪状态.span.fullSpan.unit;
state.频谱仪状态.center.value = (state.频谱仪状态.start.value + state.频谱仪状态.stop.value) / 2;
state.频谱仪状态.center.unit = state.频谱仪状态.span.fullSpan.unit;
state.频谱仪状态.span.zeroSpan = false;
console.debug('设置为 Full Span');
break;
}
case 2: {
// Zero Span
state.频谱仪状态.span.lastSpan.start = state.频谱仪状态.start.value;
state.频谱仪状态.span.lastSpan.stop = state.频谱仪状态.stop.value;
state.频谱仪状态.span.lastSpan.unit = state.频谱仪状态.stop.unit;
state.频谱仪状态.start.value = state.频谱仪状态.center.value;
state.频谱仪状态.start.unit = state.频谱仪状态.center.unit;
state.频谱仪状态.stop.value = state.频谱仪状态.center.value;
state.频谱仪状态.stop.unit = state.频谱仪状态.center.unit;
state.频谱仪状态.span.zeroSpan = true;
console.debug('设置为 Zero Span');
break;
}
case 3: {
// Last Span
state.频谱仪状态.start.value = state.频谱仪状态.span.lastSpan.start;
state.频谱仪状态.start.unit = state.频谱仪状态.span.lastSpan.unit;
state.频谱仪状态.stop.value = state.频谱仪状态.span.lastSpan.stop;
state.频谱仪状态.stop.unit = state.频谱仪状态.span.lastSpan.unit;
state.频谱仪状态.center.value = (state.频谱仪状态.start.value + state.频谱仪状态.stop.value) / 2;
state.频谱仪状态.center.unit = state.频谱仪状态.span.lastSpan.unit;
state.频谱仪状态.span.zeroSpan = false;
console.debug('恢复到 Last Span');
break;
}
default: {
console.debug('跨度菜单列表 - 这个按键还没有实现');
break;
}
}
},
[E_选项卡菜单.幅度菜单列表]: () => {
switch (按钮序号) {
case 1: {
// Ref Level
state.频谱仪状态.幅度输入状态.选中的幅度菜单列表的项目 = E_幅度菜单列表的项目.RefLevel;
state.频谱仪状态.幅度输入状态.输入的值临时输入字符串 = null;
console.debug('设置 幅度输入状态为 RefLevel');
break;
}
case 2: {
// Attenuation
console.debug('点击了 幅度菜单列表 - Attenuation (功能待实现)');
break;
}
default: {
console.debug('幅度菜单列表 - 这个按键还没有实现');
break;
}
}
},
[E_选项卡菜单.幅度单位列表]: () => {
let selectedUnit: E_幅度单位 | null = null;
switch (按钮序号) {
case 1: {
selectedUnit = E_幅度单位.dBm;
break;
}
case 2: {
selectedUnit = E_幅度单位.mV;
break;
}
case 3: {
selectedUnit = E_幅度单位.uV;
break;
}
case 4: {
selectedUnit = E_幅度单位.uA;
break;
}
default: {
console.warn('无效的幅度单位按钮序号:', 按钮序号);
console.groupEnd();
return;
}
}
const tempInputString = state.频谱仪状态.幅度输入状态.输入的值临时输入字符串;
const selectedAmpItem = state.频谱仪状态.幅度输入状态.选中的幅度菜单列表的项目;
if (!tempInputString || !selectedAmpItem) {
console.warn('幅度输入未完成或未选择项目,无法确认单位。');
console.groupEnd();
return;
}
const inputValue = Number.parseFloat(tempInputString);
if (Number.isNaN(inputValue)) {
console.warn('无法将临时输入解析为有效数字 (幅度):', tempInputString);
console.groupEnd();
return;
}
console.debug('确认幅度单位:', selectedUnit, '值为:', inputValue);
if (selectedAmpItem === E_幅度菜单列表的项目.RefLevel) {
state.频谱仪状态.幅度输入状态.refLevel.value = inputValue;
state.频谱仪状态.幅度输入状态.refLevel.unit = selectedUnit;
console.debug('更新 RefLevel:', state.频谱仪状态.幅度输入状态.refLevel);
}
state.频谱仪状态.幅度输入状态.选中的幅度菜单列表的项目 = null;
state.频谱仪状态.幅度输入状态.输入的值临时输入字符串 = null;
state.选项卡当前显示 = E_选项卡菜单.幅度菜单列表;
console.debug('幅度输入状态已重置, 切换回幅度菜单列表');
},
});
console.groupEnd();
}
function 执行点击数字按钮(按钮值: E_数字键盘按键) {
console.group('🔢 点击数字按钮', { 按钮值 });
const isFreqInputActive = !!state.频谱仪状态.频率输入状态.选中的频率菜单列表的项目;
const isAmpInputActive = !!state.频谱仪状态.幅度输入状态.选中的幅度菜单列表的项目;
if (!isFreqInputActive && !isAmpInputActive) {
console.warn('请先选择一个频率或幅度菜单项');
console.groupEnd();
return;
}
let 当前临时输入: string;
let 更新临时输入回调: (newVal: null | string) => void;
let 后续操作菜单: E_选项卡菜单;
if (isFreqInputActive) {
当前临时输入 = state.频谱仪状态.频率输入状态.输入的值临时输入字符串 ?? '';
更新临时输入回调 = (newVal) => {
state.频谱仪状态.频率输入状态.输入的值临时输入字符串 = newVal;
};
后续操作菜单 = E_选项卡菜单.频率单位列表;
} else {
// isAmpInputActive
当前临时输入 = state.频谱仪状态.幅度输入状态.输入的值临时输入字符串 ?? '';
更新临时输入回调 = (newVal) => {
state.频谱仪状态.幅度输入状态.输入的值临时输入字符串 = newVal;
};
后续操作菜单 = E_选项卡菜单.幅度单位列表;
}
switch (按钮值) {
case E_数字键盘按键.Dot: {
// 只有在当前临时输入不包含小数点时才允许添加
if (当前临时输入.includes('.')) {
console.debug('已存在小数点,忽略本次输入');
} else {
// 如果当前为空,则为 '0.',否则追加 '.'
更新临时输入回调(当前临时输入 ? `${当前临时输入}.` : '0.');
console.debug(
'输入小数点,临时字符串:',
isFreqInputActive
? state.频谱仪状态.频率输入状态.输入的值临时输入字符串
: state.频谱仪状态.幅度输入状态.输入的值临时输入字符串,
);
}
break;
}
case E_数字键盘按键.Num0:
case E_数字键盘按键.Num1:
case E_数字键盘按键.Num2:
case E_数字键盘按键.Num3:
case E_数字键盘按键.Num4:
case E_数字键盘按键.Num5:
case E_数字键盘按键.Num6:
case E_数字键盘按键.Num7:
case E_数字键盘按键.Num8:
case E_数字键盘按键.Num9: {
if (当前临时输入 === '0') {
更新临时输入回调(String(按钮值));
} else if (当前临时输入 === '-0') {
更新临时输入回调(`-${String(按钮值)}`);
} else {
更新临时输入回调(当前临时输入 + String(按钮值));
}
console.debug(
'输入数字,临时字符串:',
isFreqInputActive
? state.频谱仪状态.频率输入状态.输入的值临时输入字符串
: state.频谱仪状态.幅度输入状态.输入的值临时输入字符串,
);
break;
}
case E_数字键盘按键.PlusMinus: {
if (当前临时输入.startsWith('-')) {
更新临时输入回调(当前临时输入.slice(1));
} else if (当前临时输入 && 当前临时输入 !== '0' && 当前临时输入 !== '0.') {
更新临时输入回调(`-${当前临时输入}`);
} else if (当前临时输入 === '' || 当前临时输入 === '0' || 当前临时输入 === '0.') {
更新临时输入回调('-');
} else {
console.debug('无法切换正负号');
}
console.debug(
'切换正负号,临时字符串:',
isFreqInputActive
? state.频谱仪状态.频率输入状态.输入的值临时输入字符串
: state.频谱仪状态.幅度输入状态.输入的值临时输入字符串,
);
break;
}
default: {
console.warn('未知的数字键盘按键:', 按钮值);
}
}
state.选项卡当前显示 = 后续操作菜单;
console.groupEnd();
}
const img_modules = import.meta.glob<{ default: string }>('./imgs/*.png', {
eager: true,
});
const 屏幕照片 = ref(img_modules['./imgs/屏幕_0.png']!.default);
watch(
() => state.频谱仪状态.center.value,
() => {
屏幕照片.value = img_modules['./imgs/屏幕_1.png']!.default;
},
);
watch(
() => state.频谱仪状态.start.value,
() => {
屏幕照片.value = img_modules['./imgs/屏幕_2.png']!.default;
},
);
watch(
() => state.频谱仪状态.stop.value,
() => {
屏幕照片.value = img_modules['./imgs/屏幕_3.png']!.default;
},
);
watchEffect(() => {
console.debug(`屏幕照片.value :>> `, 屏幕照片.value);
});
</script>
<template>
<!-- <pre
v-if="__DEV__"
class="fixed bottom-10 right-10 top-10 z-[9999] overflow-auto overflow-y-auto bg-white p-[10px] text-[12px] text-black"
>{{ { 选项卡菜单7个: $enum(E_选项卡菜单).getValues(), ...state } }}</pre
> -->
<div class="wrp relative">
<img :src="设备照片" alt="频谱仪设备" />
<div
class="absolute left-[640px] top-[174px] flex w-[55px] flex-col gap-y-[4px] overflow-visible text-black"
data-box="屏幕右边的按钮"
>
<!-- 上下还有一个 ESC / RETURN -->
<ul class="flex h-[300px] flex-col justify-around gap-y-[4px] overflow-visible" data-box="屏幕右边按钮列表">
<template v-for="n in 7" :key="`按钮${n}`">
<li
class="lh-[1.2] flex flex-1 flex-col gap-y-[0px] overflow-visible rounded-[2px] text-[12px]"
@click="执行点击屏幕右边的按钮(n)"
>
<button class="ppy-btn !h-full !w-full"></button>
</li>
</template>
</ul>
</div>
<div class="absolute left-[195px] top-[140px] h-[350px] w-[430px] bg-black" data-box="屏幕">
<img :src="屏幕照片" alt="频谱仪屏幕" class="absolute left-0 top-0 !h-[330px] !w-[340px]" />
<!-- -->
<!-- 屏幕左上角 Ref Level -->
<div class="absolute left-[5px] top-[20px] text-left text-[10px] text-white" data-box="屏幕左上角RefLevel">
<span>
Ref {{ state.频谱仪状态.幅度输入状态.refLevel.value }}
{{ state.频谱仪状态.幅度输入状态.refLevel.unit }}
</span>
</div>
<div class="absolute bottom-0 left-0 right-[85px] flex justify-between text-white" data-box="下面左Start 右Stop">
<div class="flex-1 text-left">
<span>Start </span>
<span>{{ state.频谱仪状态.start.value }}</span>
<span>{{ state.频谱仪状态.start.unit }}</span>
</div>
<div class="flex-1 text-right">
<span>Stop </span>
<span>{{ state.频谱仪状态.stop.value }}</span>
<span>{{ state.频谱仪状态.stop.unit }}</span>
</div>
</div>
<div
v-if="
state.频谱仪状态.频率输入状态.选中的频率菜单列表的项目 ||
state.频谱仪状态.幅度输入状态.选中的幅度菜单列表的项目
"
class="lh-[1.2] absolute left-[40px] top-[140px] flex flex-col gap-y-[0px] bg-gray-500/100 text-white"
data-box="正在输入的值"
>
<!-- 标题:显示当前选中的输入项 -->
<div v-if="state.频谱仪状态.频率输入状态.选中的频率菜单列表的项目">
{{ state.频谱仪状态.频率输入状态.选中的频率菜单列表的项目 }}
</div>
<div v-else-if="state.频谱仪状态.幅度输入状态.选中的幅度菜单列表的项目">
{{ state.频谱仪状态.幅度输入状态.选中的幅度菜单列表的项目 }}
</div>
<!-- 值:优先显示临时输入字符串,如果为空,则显示已确认的值 -->
<div>
<template
v-if="
state.频谱仪状态.频率输入状态.选中的频率菜单列表的项目 &&
state.频谱仪状态.频率输入状态.输入的值临时输入字符串 !== null
"
>
{{ state.频谱仪状态.频率输入状态.输入的值临时输入字符串 }}
</template>
<template
v-else-if="
state.频谱仪状态.幅度输入状态.选中的幅度菜单列表的项目 &&
state.频谱仪状态.幅度输入状态.输入的值临时输入字符串 !== null
"
>
{{ state.频谱仪状态.幅度输入状态.输入的值临时输入字符串 }}
</template>
<!-- 已确认的频率值 -->
<template
v-else-if="
state.频谱仪状态.频率输入状态.选中的频率菜单列表的项目 === E_选项卡菜单Freq菜单列表的项目.CenterFreq
"
>
{{ state.频谱仪状态.center.value }}
{{ state.频谱仪状态.center.unit }}
</template>
<template
v-else-if="
state.频谱仪状态.频率输入状态.选中的频率菜单列表的项目 === E_选项卡菜单Freq菜单列表的项目.StartFreq
"
>
{{ state.频谱仪状态.start.value }} {{ state.频谱仪状态.start.unit }}
</template>
<template
v-else-if="
state.频谱仪状态.频率输入状态.选中的频率菜单列表的项目 === E_选项卡菜单Freq菜单列表的项目.StopFreq
"
>
{{ state.频谱仪状态.stop.value }} {{ state.频谱仪状态.stop.unit }}
</template>
<!-- 已确认的幅度值 -->
<template
v-else-if="state.频谱仪状态.幅度输入状态.选中的幅度菜单列表的项目 === E_幅度菜单列表的项目.RefLevel"
>
{{ state.频谱仪状态.幅度输入状态.refLevel.value }}
{{ state.频谱仪状态.幅度输入状态.refLevel.unit }}
</template>
<template v-else>
0.000000000
<!-- 默认或回退显示 -->
</template>
</div>
</div>
<div
class="absolute bottom-[15px] right-0 top-0 flex w-[80px] flex-col gap-y-[4px] text-black"
data-box="屏幕右侧的选项卡"
>
<div class="menu-item lh-[30px] h-[30px] overflow-hidden bg-[#AFAFAF] text-center" data-box="菜单标题">
{{ 选项卡标题 }}
</div>
<ul
v-if="state.选项卡当前显示 === E_选项卡菜单.频率菜单列表"
class="flex h-[300px] flex-col justify-around gap-y-[4px]"
data-box="频率菜单列表"
>
<li class="menu-item lh-[1.2] flex flex-1 flex-col gap-y-[0px] overflow-hidden bg-[#AFAFAF]">
<span>Center Freq</span>
{{ state.频谱仪状态.center.value }}
{{ state.频谱仪状态.center.unit }}
</li>
<li class="menu-item lh-[1.2] flex flex-1 flex-col gap-y-[0px] overflow-hidden bg-[#AFAFAF]">
<span>Start Freq</span>
{{ state.频谱仪状态.start.value }}
{{ state.频谱仪状态.start.unit }}
</li>
<li class="menu-item lh-[1.2] flex flex-1 flex-col gap-y-[0px] overflow-hidden bg-[#AFAFAF]">
<span>Stop Freq</span>
{{ state.频谱仪状态.stop.value }}
{{ state.频谱仪状态.stop.unit }}
</li>
<li class="menu-item lh-[1.2] flex flex-1 flex-col gap-y-[0px] overflow-hidden bg-[#AFAFAF]">
<span>#CF Step</span>
<span>0.0000 hz</span>
<span>X</span>
</li>
<li class="menu-item lh-[1.2] flex flex-1 flex-col gap-y-[0px] overflow-hidden bg-[#AFAFAF]">
<span>#Freq Offset</span>
<span>0.0000 Hz</span>
</li>
<li class="menu-item lh-[1.2] flex flex-1 flex-col gap-y-[0px] overflow-hidden bg-[#AFAFAF]">
<span>#Singnal Track</span>
</li>
<li class="menu-item lh-[1.2] flex flex-1 flex-col gap-y-[0px] overflow-hidden bg-[#AFAFAF]">
<span>#Scale Type</span>
</li>
</ul>
<ul
v-if="state.选项卡当前显示 === E_选项卡菜单.频率单位列表"
class="flex flex-1 flex-col justify-around gap-y-[4px]"
data-box="FreqChannel单位列表"
>
<li class="menu-item lh-[1.2] flex flex-1 flex-col gap-y-[0px] overflow-hidden bg-[#AFAFAF]">
<span>GHz</span>
</li>
<li class="menu-item lh-[1.2] flex flex-1 flex-col gap-y-[0px] overflow-hidden bg-[#AFAFAF]">
<span>MHz</span>
</li>
<li class="menu-item lh-[1.2] flex flex-1 flex-col gap-y-[0px] overflow-hidden bg-[#AFAFAF]">
<span>KHz</span>
</li>
<li class="menu-item lh-[1.2] flex flex-1 flex-col gap-y-[0px] overflow-hidden bg-[#AFAFAF]">
<span>Hz</span>
</li>
<li class="menu-item lh-[1.2] flex flex-1 flex-col gap-y-[0px] overflow-hidden bg-[#AFAFAF]"></li>
<li class="menu-item lh-[1.2] flex flex-1 flex-col gap-y-[0px] overflow-hidden bg-[#AFAFAF]"></li>
<li class="menu-item lh-[1.2] flex flex-1 flex-col gap-y-[0px] overflow-hidden bg-[#AFAFAF]"></li>
</ul>
<ul
v-if="state.选项卡当前显示 === E_选项卡菜单.跨度菜单列表"
class="flex h-[300px] flex-col justify-around gap-y-[4px]"
data-box="跨度菜单列表"
>
<li class="menu-item lh-[1.2] flex flex-1 flex-col gap-y-[0px] overflow-hidden bg-[#AFAFAF]">
<span>{{ E_跨度菜单列表的项目.FullSpan }}</span>
</li>
<li class="menu-item lh-[1.2] flex flex-1 flex-col gap-y-[0px] overflow-hidden bg-[#AFAFAF]">
<span>{{ E_跨度菜单列表的项目.ZeroSpan }}</span>
</li>
<li class="menu-item lh-[1.2] flex flex-1 flex-col gap-y-[0px] overflow-hidden bg-[#AFAFAF]">
<span>{{ E_跨度菜单列表的项目.LastSpan }}</span>
</li>
<li class="menu-item lh-[1.2] flex flex-1 flex-col gap-y-[0px] overflow-hidden bg-[#AFAFAF]"></li>
<li class="menu-item lh-[1.2] flex flex-1 flex-col gap-y-[0px] overflow-hidden bg-[#AFAFAF]"></li>
<li class="menu-item lh-[1.2] flex flex-1 flex-col gap-y-[0px] overflow-hidden bg-[#AFAFAF]"></li>
<li class="menu-item lh-[1.2] flex flex-1 flex-col gap-y-[0px] overflow-hidden bg-[#AFAFAF]"></li>
</ul>
<ul
v-if="state.选项卡当前显示 === E_选项卡菜单.幅度菜单列表"
class="flex h-[300px] flex-col justify-around gap-y-[4px]"
data-box="幅度菜单列表"
>
<li class="menu-item lh-[1.2] flex flex-1 flex-col gap-y-[0px] overflow-hidden bg-[#AFAFAF]">
<span>Ref Level</span>
<span>
{{ state.频谱仪状态.幅度输入状态.refLevel.value }}
{{ state.频谱仪状态.幅度输入状态.refLevel.unit }}
</span>
</li>
<li class="menu-item lh-[1.2] flex flex-1 flex-col gap-y-[0px] overflow-hidden bg-[#AFAFAF]">
<span>#Attenuation</span>
<!-- <span>TODO</span> -->
</li>
<li class="menu-item lh-[1.2] flex flex-1 flex-col gap-y-[0px] overflow-hidden bg-[#AFAFAF]"></li>
<li class="menu-item lh-[1.2] flex flex-1 flex-col gap-y-[0px] overflow-hidden bg-[#AFAFAF]"></li>
<li class="menu-item lh-[1.2] flex flex-1 flex-col gap-y-[0px] overflow-hidden bg-[#AFAFAF]"></li>
<li class="menu-item lh-[1.2] flex flex-1 flex-col gap-y-[0px] overflow-hidden bg-[#AFAFAF]"></li>
<li class="menu-item lh-[1.2] flex flex-1 flex-col gap-y-[0px] overflow-hidden bg-[#AFAFAF]"></li>
</ul>
<ul
v-if="state.选项卡当前显示 === E_选项卡菜单.幅度单位列表"
class="flex h-[300px] flex-col justify-around gap-y-[4px]"
data-box="幅度单位列表"
>
<li class="menu-item lh-[1.2] flex flex-1 flex-col gap-y-[0px] overflow-hidden bg-[#AFAFAF]">
<span>{{ E_幅度单位.dBm }}</span>
</li>
<li class="menu-item lh-[1.2] flex flex-1 flex-col gap-y-[0px] overflow-hidden bg-[#AFAFAF]">
<span>{{ E_幅度单位.mV }}</span>
</li>
<li class="menu-item lh-[1.2] flex flex-1 flex-col gap-y-[0px] overflow-hidden bg-[#AFAFAF]">
<span>{{ E_幅度单位.uV }}</span>
</li>
<li class="menu-item lh-[1.2] flex flex-1 flex-col gap-y-[0px] overflow-hidden bg-[#AFAFAF]">
<span>{{ E_幅度单位.uA }}</span>
</li>
<li class="menu-item lh-[1.2] flex flex-1 flex-col gap-y-[0px] overflow-hidden bg-[#AFAFAF]"></li>
<li class="menu-item lh-[1.2] flex flex-1 flex-col gap-y-[0px] overflow-hidden bg-[#AFAFAF]"></li>
<li class="menu-item lh-[1.2] flex flex-1 flex-col gap-y-[0px] overflow-hidden bg-[#AFAFAF]"></li>
</ul>
</div>
</div>
<div class="ppy-btns absolute left-[768px] top-[120px] h-[415px] !overflow-visible" data-box="机器右边按键">
<!-- 左上角. 长的 -->
<div class="flex flex-col gap-y-[18px] !overflow-visible">
<button class="ppy-btn !w-[62px]" @click="state.选项卡当前显示 = E_选项卡菜单.频率菜单列表">
FREQ<br />Channel
</button>
<button class="ppy-btn !w-[62px]" @click="state.选项卡当前显示 = E_选项卡菜单.跨度菜单列表">
SPAN<br />X Scale
</button>
<button class="ppy-btn !w-[62px]" @click="state.选项卡当前显示 = E_选项卡菜单.幅度菜单列表">
AMPLITUDE<br />Y Scale
</button>
<button class="ppy-btn !w-[62px] opacity-0">#</button>
<button class="ppy-btn !w-[62px] opacity-0">#</button>
</div>
<div class="mt-[20px] grid grid-cols-3 gap-[16.5px] !overflow-visible" data-box="数字键盘区域">
<button class="ppy-btn" @click="执行点击数字按钮(E_数字键盘按键.Num7)">7</button>
<button class="ppy-btn" @click="执行点击数字按钮(E_数字键盘按键.Num8)">8</button>
<button class="ppy-btn" @click="执行点击数字按钮(E_数字键盘按键.Num9)">9</button>
<button class="ppy-btn" @click="执行点击数字按钮(E_数字键盘按键.Num4)">4</button>
<button class="ppy-btn" @click="执行点击数字按钮(E_数字键盘按键.Num5)">5</button>
<button class="ppy-btn" @click="执行点击数字按钮(E_数字键盘按键.Num6)">6</button>
<button class="ppy-btn" @click="执行点击数字按钮(E_数字键盘按键.Num1)">1</button>
<button class="ppy-btn" @click="执行点击数字按钮(E_数字键盘按键.Num2)">2</button>
<button class="ppy-btn" @click="执行点击数字按钮(E_数字键盘按键.Num3)">3</button>
<button class="ppy-btn" @click="执行点击数字按钮(E_数字键盘按键.Num0)">0</button>
<button class="ppy-btn" @click="执行点击数字按钮(E_数字键盘按键.Dot)">.</button>
<button class="ppy-btn" @click="执行点击数字按钮(E_数字键盘按键.PlusMinus)">+/-</button>
</div>
</div>
</div>
</template>
<style scoped>
@import url('./Helvetica.css');
.wrp {
font-family: 'Helvetica Custom';
}
/* .wrp * {
overflow: hidden;
} */
img[alt='频谱仪设备'] {
width: calc(2554px * 0.5);
max-width: fit-content;
height: calc(1626px * 0.5);
max-height: fit-content;
}
.ppy-btn {
display: flex; /* 使用 flex 居中文本 */
align-items: center;
justify-content: center;
width: 36px;
height: 30px;
padding-left: 2px;
overflow: hidden;
font-size: 10px;
line-height: 1.2;
color: #333; /* 深灰色文字 */
text-align: left;
text-decoration: none;
background-color: #e0e0e0; /* 按钮浅灰色背景 */
border: 1px solid green;
border: 1px solid #b0b0b0; /* 添加边框 */
border-radius: 4px;
box-shadow:
1px 1px 2px rgb(0 0 0 / 20%),
/* 轻微外阴影 */ -1px -1px 2px rgb(255 255 255 / 80%); /* 轻微外高光 */
transition: all 0.1s ease-in-out; /* 平滑过渡效果 */
/* width: 100%; */
/* 宽度占满 grid 单元格 */
/* height: 100%; */
/* 高度占满 grid 单元格 */
/* font-size: 20px; */
/* 调整字体大小 */
/* font-weight: bold; */
/* 字体加粗 */
/* border-radius: 4px; */
/* 按钮圆角 */
}
.ppy-btn:active {
background-color: #d5d5d5;
border-color: #a0a0a0;
box-shadow:
inset 1px 1px 3px rgb(0 0 0 / 30%),
inset -1px -1px 3px rgb(255 255 255 / 60%);
transform: scale(0.98);
}
.menu-item {
/* @apply bg-gray-200/50 p-[2px]; */
font-size: 12px;
/* border: 1px solid white; */
}
[alt='频谱仪设备'] {
opacity: 1;
transition: opacity 0.3s;
}
/* li,
.ppy-btns,
[data-box='屏幕'],
[data-box='菜单标题'] {
box-shadow: 0 0 0 1px rgba(255, 0, 0, 0.5);
} */
</style>

Binary file not shown.

File diff suppressed because it is too large Load Diff

After

Width:  |  Height:  |  Size: 447 KiB

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 705 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 810 KiB

View File

@@ -25,7 +25,7 @@ export class HCesiumManager {
try {
this.viewer = new Cesium.Viewer(container, VIEWER_OPTIONS_FN());
if ($__DEV__) Object.assign(globalThis, { viewer: this.viewer });
if (__DEV__) Object.assign(globalThis, { viewer: this.viewer });
configureTimeLine(this.viewer);

View File

@@ -6,9 +6,9 @@ import type { I卫星 } from './HCesiumManager.types';
import { type OrbitCalculationResult, SatelliteCalculator } from '../calculators/SatelliteCalculator';
interface ManagedSatelliteEntities {
coverageEntity?: Cesium.Entity;
coverageEntity?: Cesium.Entity | null;
mainEntity: Cesium.Entity;
orbitEntity?: Cesium.Entity;
orbitEntity?: Cesium.Entity | null;
}
export class HCesiumSatelliteManager {
@@ -108,8 +108,8 @@ export class HCesiumSatelliteManager {
// 存储实体引用
this.currentSatelliteEntities.set(id, {
mainEntity: addedMainEntity, // 存储实际添加成功的实体
orbitEntity: addedOrbitEntity ?? undefined,
coverageEntity: addedCoverageEntity ?? undefined,
orbitEntity: addedOrbitEntity,
coverageEntity: addedCoverageEntity,
});
}
@@ -124,21 +124,10 @@ export class HCesiumSatelliteManager {
options: I卫星,
): Cesium.Entity {
// 动态轨迹路径 (Path) - 注意:这与完整轨道线 (Polyline) 不同
const path: Cesium.PathGraphics.ConstructorOptions = {
resolution: 1,
material: new Cesium.PolylineGlowMaterialProperty({
glowPower: 0.15,
color: randomBaseColor,
}),
width: 2,
leadTime: (options.orbitDurationSeconds ?? 3600 * 2) / 2, // 默认1小时
trailTime: (options.orbitDurationSeconds ?? 3600 * 2) / 2, // 默认1小时
};
return new Cesium.Entity({
const entityOptions: Cesium.Entity.ConstructorOptions = {
id,
name,
path: options.showPath ? path : undefined, // 根据 options.showPath 控制是否显示路径
position: sampledPositionProperty, // 使用计算好的位置属性
orientation: new Cesium.VelocityOrientationProperty(sampledPositionProperty),
point: {
@@ -157,7 +146,22 @@ export class HCesiumSatelliteManager {
fillColor: Cesium.Color.WHITE,
outlineColor: Cesium.Color.BLACK,
},
});
};
if (options.showPath) {
entityOptions.path = {
resolution: 1,
material: new Cesium.PolylineGlowMaterialProperty({
glowPower: 0.15,
color: randomBaseColor,
}),
width: 2,
leadTime: (options.orbitDurationSeconds ?? 3600 * 2) / 2, // 默认1小时
trailTime: (options.orbitDurationSeconds ?? 3600 * 2) / 2, // 默认1小时
};
}
return new Cesium.Entity(entityOptions);
}
/**

View File

@@ -6,7 +6,7 @@ import { HCesiumManager } from './managers/HCesiumManager';
export function useHCesiumManager(containerId: string) {
const hCesiumViewerManager = ref(new HCesiumManager());
// 可以在开发模式下暴露 manager 实例,方便调试
if ($__DEV__) Object.assign(globalThis, { hCesiumViewerManager });
if (__DEV__) Object.assign(globalThis, { hCesiumViewerManager });
onMounted(() => {
hCesiumViewerManager.value.init(containerId);

View File

@@ -18,32 +18,32 @@ const readyPromise = new Promise<void>((resolve) => {
});
const 频谱瀑布图Layout = {
title: '频谱瀑布图',
title: { text: '频谱瀑布图' },
xaxis: {
title: '频率 (Hz)',
title: { text: '频率 (Hz)' },
// range: [0, 22_050],
showgrid: false,
tickformat: ',d', // 设置为带逗号的整数格式 (推荐,更易读)
},
yaxis: {
title: '时间步',
title: { text: '时间步' },
showgrid: false,
tickformat: ',d', // 设置为带逗号的整数格式 (推荐,更易读)
},
margin: { l: 60, r: 40, b: 40, t: 60 },
};
} satisfies Partial<import('plotly.js-dist-min').Layout>;
const 频谱图Layout = {
title: '频谱图',
title: { text: '频谱图' },
xaxis: {
title: '频率 (Hz)',
title: { text: '频率 (Hz)' },
// range: [0, 22_050],
showgrid: true,
gridcolor: '#eee',
tickformat: ',d', // 设置为带逗号的整数格式 (推荐,更易读)
},
yaxis: {
title: '幅度 (dB)',
title: { text: '幅度 (dB)' },
showgrid: true, // 显示网格线
gridcolor: '#eee',
tickformat: ',d', // 设置为带逗号的整数格式 (推荐,更易读)

View File

@@ -1,5 +1,5 @@
<script setup lang="ts">
const { isActive: isCounting, remaining: countdownTime, start: startCountdown } = useCountdown($__DEV__ ? 3 : 60);
const { isActive: isCounting, remaining: countdownTime, start: startCountdown } = useCountdown(__DEV__ ? 3 : 60);
const isSending = ref(false);
const sendSms = async () => {

View File

@@ -0,0 +1,321 @@
<script setup lang="ts">
import type { MenuOption } from 'naive-ui';
import { createGetRoutes } from '@/plugins/router';
const router = useRouter();
// 响应式断点检测
const isMobile = ref(false);
const isTablet = ref(false);
// 检测屏幕尺寸
const updateScreenSize = () => {
const width = window.innerWidth;
const wasMobile = isMobile.value;
isMobile.value = width < 768;
isTablet.value = width >= 768 && width < 1024;
// 当从移动端切换到桌面端时,关闭抽屉并重置折叠状态
if (wasMobile && !isMobile.value) {
drawerVisible.value = false;
collapsed.value = false;
}
// 当从桌面端切换到移动端时,关闭抽屉
else if (!wasMobile && isMobile.value) {
drawerVisible.value = false;
}
};
// 侧边栏状态管理
const collapsed = ref(false);
const drawerVisible = ref(false);
// 初始化屏幕尺寸检测
onMounted(() => {
updateScreenSize();
window.addEventListener('resize', updateScreenSize);
// 移动端默认收起侧边栏
if (isMobile.value) {
collapsed.value = true;
drawerVisible.value = false;
}
});
onUnmounted(() => {
window.removeEventListener('resize', updateScreenSize);
});
// 菜单项类型定义
type MenuItemWithRoute = MenuOption & {
routeName?: string;
parentId?: string;
originalPath?: string;
};
// 生成菜单项
const menuOptions = computed(() => {
let flatArray: MenuItemWithRoute[] = createGetRoutes(router)()
.filter((route) => !route.path.includes('/:'))
.filter((route) => !route.meta.hidden)
.map((route) => ({
key: route.path,
label: route.meta.title || `${(route.name as string) || route.path}`,
routeName: route.name as string,
}));
flatArray = flatArray.map((item) => {
const originalPath = item.key as string; // 保存原始路径
let id = item.key as string;
if (flatArray.some((item) => (item.key as string).startsWith(`${id}/`))) {
id = `${id}/index`;
}
// 去掉最前面的 /
id = id.replace(/^\//, '');
let parentId = id.replace(/\/[^/]+$/, '');
if (parentId === id) {
parentId = '_ROOT_';
}
return {
...item,
key: id,
parentId,
originalPath, // 保存原始路径用于后续映射
};
});
const groupItems: MenuItemWithRoute[] = [];
for (const flatArrayItem of flatArray) {
if (!groupItems.some((item) => item.key === flatArrayItem.parentId) && flatArrayItem.parentId !== '_ROOT_') {
let groupItemParentId = flatArrayItem.parentId!.replace(/\/[^/]+$/, '');
if (groupItemParentId === flatArrayItem.parentId) groupItemParentId = '_ROOT_';
groupItems.push({
key: flatArrayItem.parentId!,
label: `Group ${flatArrayItem.parentId}`,
parentId: groupItemParentId,
});
}
}
const tree = arrayToTree([...flatArray, ...groupItems], {
id: 'key',
parentId: 'parentId',
rootId: '_ROOT_',
});
// 递归转换树形结构为 naive-ui menu 格式
function convertToMenuOptions(tree: MenuItemWithRoute[]): MenuOption[] {
return tree.map((item) => {
const menuItem: MenuOption = {
key: item.key,
label: item.label,
};
if (item.children && item.children.length > 0) {
menuItem.children = convertToMenuOptions(item.children);
} else if (item.routeName) {
// 叶子节点,存储路由映射
menuRouteMap.set(item.key as string, item.routeName);
// 同时存储路径到 key 的映射(用于高亮显示)
if (item.originalPath) {
pathToKeyMap.set(item.originalPath, item.key as string);
}
(menuItem as MenuItemWithRoute).routeName = item.routeName;
}
return menuItem;
});
}
// 清空之前的映射
menuRouteMap.clear();
pathToKeyMap.clear();
const result = convertToMenuOptions(tree);
// 菜单生成后,重新设置当前选中的菜单项
nextTick(() => {
const currentPath = router.currentRoute.value.path;
const menuKey = pathToKeyMap.get(currentPath);
if (menuKey) {
selectedKey.value = menuKey;
} else {
const pathWithoutSlash = currentPath.replace(/^\//, '');
selectedKey.value = pathWithoutSlash;
}
});
return result;
});
// 当前选中的菜单项
const selectedKey = ref<string>();
// 存储菜单项与路由名称的映射
const menuRouteMap = new Map<string, string>();
// 存储路由路径与菜单 key 的映射(用于高亮显示)
const pathToKeyMap = new Map<string, string>();
// 处理菜单点击
const handleMenuSelect = (key: string, item: MenuOption) => {
const routeName = menuRouteMap.get(key) || (item as MenuItemWithRoute).routeName;
if (routeName) {
router.push({ name: routeName as never });
// 移动端点击菜单项后自动收起侧边栏
if (isMobile.value) {
drawerVisible.value = false;
}
}
};
// 监听路由变化,更新选中的菜单项
watch(
() => router.currentRoute.value.path,
(newPath) => {
// 使用路径到 key 的映射来找到对应的菜单项
const menuKey = pathToKeyMap.get(newPath);
if (menuKey) {
selectedKey.value = menuKey;
} else {
// 如果没有找到精确匹配,尝试去掉前面的 / 再匹配
const pathWithoutSlash = newPath.replace(/^\//, '');
selectedKey.value = pathWithoutSlash;
}
},
{ immediate: true },
);
// 切换侧边栏状态
const toggleSidebar = () => {
if (isMobile.value) {
// 移动端使用抽屉模式
drawerVisible.value = !drawerVisible.value;
} else {
// 桌面端使用折叠模式
collapsed.value = !collapsed.value;
}
};
</script>
<template>
<n-layout :has-sider="!isMobile">
<!-- 移动端抽屉 -->
<n-drawer
v-if="isMobile"
v-model:show="drawerVisible"
:width="280"
placement="left"
:trap-focus="false"
:block-scroll="false"
>
<n-drawer-content title="菜单" :native-scrollbar="false">
<n-menu :options="menuOptions" :value="selectedKey" @update:value="handleMenuSelect" />
</n-drawer-content>
</n-drawer>
<!-- 桌面端侧边栏 -->
<n-layout-sider
v-if="!isMobile"
:collapsed="collapsed"
:native-scrollbar="false"
bordered
collapse-mode="width"
:collapsed-width="64"
:width="240"
show-trigger
@collapse="collapsed = true"
@expand="collapsed = false"
>
<n-menu
:collapsed="collapsed"
:collapsed-width="64"
:collapsed-icon-size="22"
:options="menuOptions"
:value="selectedKey"
@update:value="handleMenuSelect"
/>
</n-layout-sider>
<n-layout>
<n-layout-header
bordered
:style="{
height: '64px',
padding: isMobile ? '0 16px' : '0 24px',
display: 'flex',
alignItems: 'center',
}"
>
<n-button
quaternary
@click="toggleSidebar"
:style="{
marginRight: isMobile ? '8px' : '12px',
padding: isMobile ? '8px' : '6px',
}"
:size="isMobile ? 'medium' : 'small'"
>
<template #icon>
<n-icon :size="isMobile ? 20 : 18">
<svg viewBox="0 0 24 24">
<path
v-if="!isMobile && collapsed"
fill="currentColor"
d="M3 18h18v-2H3v2zm0-5h18v-2H3v2zm0-7v2h18V6H3z"
/>
<path
v-else-if="!isMobile && !collapsed"
fill="currentColor"
d="M3 18h13v-2H3v2zm0-5h10v-2H3v2zm0-7v2h13V6H3zm18 9.59L17.42 12 21 8.41 19.59 7l-5 5 5 5L21 15.59z"
/>
<!-- 移动端始终显示菜单图标 -->
<path v-else fill="currentColor" d="M3 18h18v-2H3v2zm0-5h18v-2H3v2zm0-7v2h18V6H3z" />
</svg>
</n-icon>
</template>
</n-button>
<span
:style="{
fontSize: isMobile ? '16px' : '18px',
fontWeight: '500',
overflow: 'hidden',
textOverflow: 'ellipsis',
whiteSpace: 'nowrap',
}"
>
Vue TS Example
</span>
</n-layout-header>
<n-layout-content
:content-style="{
padding: isMobile ? '16px' : '24px',
minHeight: 'calc(100vh - 64px - 72px)', // 减去头部和底部高度
}"
>
<router-view />
</n-layout-content>
<n-layout-footer
bordered
:style="{
padding: isMobile ? '16px' : '24px',
textAlign: 'center',
}"
>
<span
:style="{
color: 'var(--n-text-color-disabled)',
fontSize: isMobile ? '12px' : '14px',
}"
>
© 2025 Vue TS Example. All rights reserved.
</span>
</n-layout-footer>
</n-layout>
</n-layout>
</template>

View File

@@ -1,605 +0,0 @@
<script setup lang="ts">
import { $t, updatePreset, updateSurfacePalette } from '@primeuix/themes';
import Aura from '@primeuix/themes/aura';
import Lara from '@primeuix/themes/lara';
import { ref } from 'vue';
import { useLayout } from './composables/layout';
const { isDarkTheme, layoutConfig } = useLayout();
const presets = {
Aura,
Lara,
};
const preset = ref(layoutConfig.preset);
const presetOptions = ref(Object.keys(presets));
const menuMode = ref(layoutConfig.menuMode);
const menuModeOptions = ref([
{ label: 'Static', value: 'static' },
{ label: 'Overlay', value: 'overlay' },
]);
const primaryColors = ref([
{ name: 'noir', palette: {} },
{
name: 'emerald',
palette: {
50: '#ecfdf5',
100: '#d1fae5',
200: '#a7f3d0',
300: '#6ee7b7',
400: '#34d399',
500: '#10b981',
600: '#059669',
700: '#047857',
800: '#065f46',
900: '#064e3b',
950: '#022c22',
},
},
{
name: 'green',
palette: {
50: '#f0fdf4',
100: '#dcfce7',
200: '#bbf7d0',
300: '#86efac',
400: '#4ade80',
500: '#22c55e',
600: '#16a34a',
700: '#15803d',
800: '#166534',
900: '#14532d',
950: '#052e16',
},
},
{
name: 'lime',
palette: {
50: '#f7fee7',
100: '#ecfccb',
200: '#d9f99d',
300: '#bef264',
400: '#a3e635',
500: '#84cc16',
600: '#65a30d',
700: '#4d7c0f',
800: '#3f6212',
900: '#365314',
950: '#1a2e05',
},
},
{
name: 'orange',
palette: {
50: '#fff7ed',
100: '#ffedd5',
200: '#fed7aa',
300: '#fdba74',
400: '#fb923c',
500: '#f97316',
600: '#ea580c',
700: '#c2410c',
800: '#9a3412',
900: '#7c2d12',
950: '#431407',
},
},
{
name: 'amber',
palette: {
50: '#fffbeb',
100: '#fef3c7',
200: '#fde68a',
300: '#fcd34d',
400: '#fbbf24',
500: '#f59e0b',
600: '#d97706',
700: '#b45309',
800: '#92400e',
900: '#78350f',
950: '#451a03',
},
},
{
name: 'yellow',
palette: {
50: '#fefce8',
100: '#fef9c3',
200: '#fef08a',
300: '#fde047',
400: '#facc15',
500: '#eab308',
600: '#ca8a04',
700: '#a16207',
800: '#854d0e',
900: '#713f12',
950: '#422006',
},
},
{
name: 'teal',
palette: {
50: '#f0fdfa',
100: '#ccfbf1',
200: '#99f6e4',
300: '#5eead4',
400: '#2dd4bf',
500: '#14b8a6',
600: '#0d9488',
700: '#0f766e',
800: '#115e59',
900: '#134e4a',
950: '#042f2e',
},
},
{
name: 'cyan',
palette: {
50: '#ecfeff',
100: '#cffafe',
200: '#a5f3fc',
300: '#67e8f9',
400: '#22d3ee',
500: '#06b6d4',
600: '#0891b2',
700: '#0e7490',
800: '#155e75',
900: '#164e63',
950: '#083344',
},
},
{
name: 'sky',
palette: {
50: '#f0f9ff',
100: '#e0f2fe',
200: '#bae6fd',
300: '#7dd3fc',
400: '#38bdf8',
500: '#0ea5e9',
600: '#0284c7',
700: '#0369a1',
800: '#075985',
900: '#0c4a6e',
950: '#082f49',
},
},
{
name: 'blue',
palette: {
50: '#eff6ff',
100: '#dbeafe',
200: '#bfdbfe',
300: '#93c5fd',
400: '#60a5fa',
500: '#3b82f6',
600: '#2563eb',
700: '#1d4ed8',
800: '#1e40af',
900: '#1e3a8a',
950: '#172554',
},
},
{
name: 'indigo',
palette: {
50: '#eef2ff',
100: '#e0e7ff',
200: '#c7d2fe',
300: '#a5b4fc',
400: '#818cf8',
500: '#6366f1',
600: '#4f46e5',
700: '#4338ca',
800: '#3730a3',
900: '#312e81',
950: '#1e1b4b',
},
},
{
name: 'violet',
palette: {
50: '#f5f3ff',
100: '#ede9fe',
200: '#ddd6fe',
300: '#c4b5fd',
400: '#a78bfa',
500: '#8b5cf6',
600: '#7c3aed',
700: '#6d28d9',
800: '#5b21b6',
900: '#4c1d95',
950: '#2e1065',
},
},
{
name: 'purple',
palette: {
50: '#faf5ff',
100: '#f3e8ff',
200: '#e9d5ff',
300: '#d8b4fe',
400: '#c084fc',
500: '#a855f7',
600: '#9333ea',
700: '#7e22ce',
800: '#6b21a8',
900: '#581c87',
950: '#3b0764',
},
},
{
name: 'fuchsia',
palette: {
50: '#fdf4ff',
100: '#fae8ff',
200: '#f5d0fe',
300: '#f0abfc',
400: '#e879f9',
500: '#d946ef',
600: '#c026d3',
700: '#a21caf',
800: '#86198f',
900: '#701a75',
950: '#4a044e',
},
},
{
name: 'pink',
palette: {
50: '#fdf2f8',
100: '#fce7f3',
200: '#fbcfe8',
300: '#f9a8d4',
400: '#f472b6',
500: '#ec4899',
600: '#db2777',
700: '#be185d',
800: '#9d174d',
900: '#831843',
950: '#500724',
},
},
{
name: 'rose',
palette: {
50: '#fff1f2',
100: '#ffe4e6',
200: '#fecdd3',
300: '#fda4af',
400: '#fb7185',
500: '#f43f5e',
600: '#e11d48',
700: '#be123c',
800: '#9f1239',
900: '#881337',
950: '#4c0519',
},
},
]);
const surfaces = ref([
{
name: 'slate',
palette: {
0: '#ffffff',
50: '#f8fafc',
100: '#f1f5f9',
200: '#e2e8f0',
300: '#cbd5e1',
400: '#94a3b8',
500: '#64748b',
600: '#475569',
700: '#334155',
800: '#1e293b',
900: '#0f172a',
950: '#020617',
},
},
{
name: 'gray',
palette: {
0: '#ffffff',
50: '#f9fafb',
100: '#f3f4f6',
200: '#e5e7eb',
300: '#d1d5db',
400: '#9ca3af',
500: '#6b7280',
600: '#4b5563',
700: '#374151',
800: '#1f2937',
900: '#111827',
950: '#030712',
},
},
{
name: 'zinc',
palette: {
0: '#ffffff',
50: '#fafafa',
100: '#f4f4f5',
200: '#e4e4e7',
300: '#d4d4d8',
400: '#a1a1aa',
500: '#71717a',
600: '#52525b',
700: '#3f3f46',
800: '#27272a',
900: '#18181b',
950: '#09090b',
},
},
{
name: 'neutral',
palette: {
0: '#ffffff',
50: '#fafafa',
100: '#f5f5f5',
200: '#e5e5e5',
300: '#d4d4d4',
400: '#a3a3a3',
500: '#737373',
600: '#525252',
700: '#404040',
800: '#262626',
900: '#171717',
950: '#0a0a0a',
},
},
{
name: 'stone',
palette: {
0: '#ffffff',
50: '#fafaf9',
100: '#f5f5f4',
200: '#e7e5e4',
300: '#d6d3d1',
400: '#a8a29e',
500: '#78716c',
600: '#57534e',
700: '#44403c',
800: '#292524',
900: '#1c1917',
950: '#0c0a09',
},
},
{
name: 'soho',
palette: {
0: '#ffffff',
50: '#f4f4f4',
100: '#e8e9e9',
200: '#d2d2d4',
300: '#bbbcbe',
400: '#a5a5a9',
500: '#8e8f93',
600: '#77787d',
700: '#616268',
800: '#4a4b52',
900: '#34343d',
950: '#1d1e27',
},
},
{
name: 'viva',
palette: {
0: '#ffffff',
50: '#f3f3f3',
100: '#e7e7e8',
200: '#cfd0d0',
300: '#b7b8b9',
400: '#9fa1a1',
500: '#87898a',
600: '#6e7173',
700: '#565a5b',
800: '#3e4244',
900: '#262b2c',
950: '#0e1315',
},
},
{
name: 'ocean',
palette: {
0: '#ffffff',
50: '#fbfcfc',
100: '#F7F9F8',
200: '#EFF3F2',
300: '#DADEDD',
400: '#B1B7B6',
500: '#828787',
600: '#5F7274',
700: '#415B61',
800: '#29444E',
900: '#183240',
950: '#0c1920',
},
},
]);
function applyTheme(type: string, color: any) {
if (type === 'primary') {
updatePreset(getPresetExt());
} else if (type === 'surface') {
updateSurfacePalette(color.palette);
}
}
function getPresetExt() {
const color = primaryColors.value.find((c) => c.name === layoutConfig.primary)!;
return color.name === 'noir'
? {
semantic: {
colorScheme: {
dark: {
highlight: {
background: '{primary.50}',
color: '{primary.950}',
focusBackground: '{primary.300}',
focusColor: '{primary.950}',
},
primary: {
activeColor: '{primary.300}',
color: '{primary.50}',
contrastColor: '{primary.950}',
hoverColor: '{primary.200}',
},
},
light: {
highlight: {
background: '{primary.950}',
color: '#ffffff',
focusBackground: '{primary.700}',
focusColor: '#ffffff',
},
primary: {
activeColor: '{primary.700}',
color: '{primary.950}',
contrastColor: '#ffffff',
hoverColor: '{primary.800}',
},
},
},
primary: {
50: '{surface.50}',
100: '{surface.100}',
200: '{surface.200}',
300: '{surface.300}',
400: '{surface.400}',
500: '{surface.500}',
600: '{surface.600}',
700: '{surface.700}',
800: '{surface.800}',
900: '{surface.900}',
950: '{surface.950}',
},
},
}
: {
semantic: {
colorScheme: {
dark: {
highlight: {
background: 'color-mix(in srgb, {primary.400}, transparent 84%)',
color: 'rgba(255,255,255,.87)',
focusBackground: 'color-mix(in srgb, {primary.400}, transparent 76%)',
focusColor: 'rgba(255,255,255,.87)',
},
primary: {
activeColor: '{primary.200}',
color: '{primary.400}',
contrastColor: '{surface.900}',
hoverColor: '{primary.300}',
},
},
light: {
highlight: {
background: '{primary.50}',
color: '{primary.700}',
focusBackground: '{primary.100}',
focusColor: '{primary.800}',
},
primary: {
activeColor: '{primary.700}',
color: '{primary.500}',
contrastColor: '#ffffff',
hoverColor: '{primary.600}',
},
},
},
primary: color.palette,
},
};
}
function onMenuModeChange() {
layoutConfig.menuMode = menuMode.value;
}
function onPresetChange() {
layoutConfig.preset = preset.value;
const presetValue = presets[preset.value as never];
const surfacePalette = surfaces.value.find((s) => s.name === layoutConfig.surface)?.palette;
$t().preset(presetValue).preset(getPresetExt()).surfacePalette(surfacePalette).use({ useDefaultOptions: true });
}
function updateColors(type: string, color: any) {
if (type === 'primary') {
layoutConfig.primary = color.name;
} else if (type === 'surface') {
layoutConfig.surface = color.name;
}
applyTheme(type, color);
}
</script>
<template>
<div
class="config-panel hidden absolute top-[3.25rem] right-0 w-64 p-4 bg-surface-0 dark:bg-surface-900 border border-surface rounded-border origin-top shadow-[0px_3px_5px_rgba(0,0,0,0.02),0px_0px_2px_rgba(0,0,0,0.05),0px_1px_4px_rgba(0,0,0,0.08)]"
>
<div class="flex flex-col gap-4">
<div>
<span class="text-sm text-muted-color font-semibold">Primary</span>
<div class="pt-2 flex gap-2 flex-wrap justify-between">
<button
v-for="primaryColor of primaryColors"
:key="primaryColor.name"
type="button"
:title="primaryColor.name"
@click="updateColors('primary', primaryColor)"
:class="[
'border-none w-5 h-5 rounded-full p-0 cursor-pointer outline-none outline-offset-1',
{ 'outline-primary': layoutConfig.primary === primaryColor.name },
]"
:style="{
backgroundColor: `${primaryColor.name === 'noir' ? 'var(--text-color)' : primaryColor.palette['500']}`,
}"
></button>
</div>
</div>
<div>
<span class="text-sm text-muted-color font-semibold">Surface</span>
<div class="pt-2 flex gap-2 flex-wrap justify-between">
<button
v-for="surface of surfaces"
:key="surface.name"
type="button"
:title="surface.name"
@click="updateColors('surface', surface)"
:class="[
'border-none w-5 h-5 rounded-full p-0 cursor-pointer outline-none outline-offset-1',
{
'outline-primary': layoutConfig.surface
? layoutConfig.surface === surface.name
: isDarkTheme
? surface.name === 'zinc'
: surface.name === 'slate',
},
]"
:style="{ backgroundColor: `${surface.palette['500']}` }"
></button>
</div>
</div>
<div class="flex flex-col gap-2">
<span class="text-sm text-muted-color font-semibold">Presets</span>
<SelectButton v-model="preset" @change="onPresetChange" :options="presetOptions" :allowEmpty="false" />
</div>
<div class="flex flex-col gap-2">
<span class="text-sm text-muted-color font-semibold">Menu Mode</span>
<SelectButton
v-model="menuMode"
@change="onMenuModeChange"
:options="menuModeOptions"
:allowEmpty="false"
optionLabel="label"
optionValue="value"
/>
</div>
</div>
</div>
</template>

View File

@@ -1,14 +0,0 @@
<script setup lang="ts"></script>
<template>
<div class="layout-footer">
SAKAI by
<a
href="https://primevue.org"
target="_blank"
rel="noopener noreferrer"
class="text-primary font-bold hover:underline"
>PrimeVue</a
>
</div>
</template>

View File

@@ -1,79 +0,0 @@
<script setup lang="ts">
import './styles/layout.scss';
import { computed, ref, watch } from 'vue';
import AppFooter from './AppFooter.vue';
import AppSidebar from './AppSidebar.vue';
import AppTopbar from './AppTopbar.vue';
import { useLayout } from './composables/layout';
const { isSidebarActive, layoutConfig, layoutState } = useLayout();
const outsideClickListener = ref(null as null | Parameters<typeof document.addEventListener>[1]);
watch(isSidebarActive, (newValue) => {
if (newValue) {
bindOutsideClickListener();
} else {
unbindOutsideClickListener();
}
});
const containerClass = computed(() => {
return {
'layout-mobile-active': layoutState.staticMenuMobileActive,
'layout-overlay': layoutConfig.menuMode === 'overlay',
'layout-overlay-active': layoutState.overlayMenuActive,
'layout-static': layoutConfig.menuMode === 'static',
'layout-static-inactive': layoutState.staticMenuDesktopInactive && layoutConfig.menuMode === 'static',
};
});
function bindOutsideClickListener() {
if (!outsideClickListener.value) {
outsideClickListener.value = (event) => {
if (isOutsideClicked(event)) {
layoutState.overlayMenuActive = false;
layoutState.staticMenuMobileActive = false;
layoutState.menuHoverActive = false;
}
};
document.addEventListener('click', outsideClickListener.value);
}
}
function isOutsideClicked(event: Event) {
const sidebarEl = document.querySelector('.layout-sidebar')!;
const topbarEl = document.querySelector('.layout-menu-button')!;
return !(
sidebarEl.isSameNode(event.target as never) ||
sidebarEl.contains(event.target as never) ||
topbarEl.isSameNode(event.target as never) ||
topbarEl.contains(event.target as never)
);
}
function unbindOutsideClickListener() {
if (outsideClickListener.value) {
document.removeEventListener('click', outsideClickListener.value);
outsideClickListener.value = null;
}
}
</script>
<template>
<div class="layout-wrapper" :class="containerClass">
<app-topbar></app-topbar>
<app-sidebar></app-sidebar>
<div class="layout-main-container">
<div class="layout-main">
<router-view></router-view>
</div>
<app-footer></app-footer>
</div>
<div class="layout-mask animate-fadein"></div>
</div>
<Toast />
</template>

View File

@@ -1,76 +0,0 @@
<script setup lang="ts">
import type { MenuItem } from 'primevue/menuitem';
import { createGetRoutes } from '@/plugins/router';
const router = useRouter();
type MenuItemWithRoute = MenuItem & { routeName?: string };
const menuItems = computed(() => {
let flatArray: MenuItemWithRoute[] = createGetRoutes(router)()
.filter((route) => !route.path.includes('/:'))
.filter((route) => !route.meta.hidden)
.map((route) => ({
id: route.path,
label: route.meta.title || `${(route.name as string) || route.path}`,
routeName: route.name as string,
}));
flatArray = flatArray.map((item /* index */) => {
let id = item.id;
if (flatArray.some((item) => item.id.startsWith(`${id}/`))) {
id = `${id}/index`;
}
// 去掉最前面的 /
id = id.replace(/^\//, '');
let parentId = id.replace(/\/[^/]+$/, '');
if (parentId === id) {
parentId = '_ROOT_';
}
return {
...item,
parentId,
};
});
const groupItems: Record<string, string>[] = [];
for (const flatArrayItem of flatArray) {
if (
!groupItems.some((item) => item.id === flatArrayItem.parentId) && //
flatArrayItem.parentId !== '_ROOT_'
) {
let groupItemParentId = flatArrayItem.parentId.replace(/\/[^/]+$/, '');
if (groupItemParentId === flatArrayItem.parentId) groupItemParentId = '_ROOT_';
groupItems.push({
id: flatArrayItem.parentId,
label: `Group ${flatArrayItem.parentId}`,
parentId: groupItemParentId,
});
}
}
console.debug(`groupItems :>>`, groupItems);
const tree = arrayToTree([...flatArray, ...groupItems], { id: 'id', parentId: 'parentId', rootId: '_ROOT_' });
// 递归把 children 改为 items
function _convertChildrenToItems(tree: MenuItemWithRoute[]) {
return tree.map((item) => {
if (item.children.length > 0) {
item.items = _convertChildrenToItems(item.children);
} else {
item.command = (/* event */) => {
router.push({ name: item.routeName as never });
};
}
delete item.children;
return item;
});
}
return _convertChildrenToItems(tree);
});
</script>
<template>
<PanelMenu :model="menuItems" />
</template>
<style lang="scss" scoped></style>

View File

@@ -1,11 +0,0 @@
<script setup lang="ts">
import AppMenu from './AppMenu.vue';
</script>
<template>
<div class="layout-sidebar">
<app-menu></app-menu>
</div>
</template>
<style lang="scss" scoped></style>

View File

@@ -1,104 +0,0 @@
<script setup lang="ts">
import AppConfigurator from './AppConfigurator.vue';
import { useLayout } from './composables/layout';
const { isDarkTheme, toggleDarkMode, toggleMenu } = useLayout();
</script>
<template>
<div class="layout-topbar">
<div class="layout-topbar-logo-container">
<button class="layout-menu-button layout-topbar-action" @click="toggleMenu">
<i class="pi pi-bars"></i>
</button>
<router-link to="/" class="layout-topbar-logo">
<svg viewBox="0 0 54 40" fill="none" xmlns="http://www.w3.org/2000/svg">
<path
fill-rule="evenodd"
clip-rule="evenodd"
d="M17.1637 19.2467C17.1566 19.4033 17.1529 19.561 17.1529 19.7194C17.1529 25.3503 21.7203 29.915 27.3546 29.915C32.9887 29.915 37.5561 25.3503 37.5561 19.7194C37.5561 19.5572 37.5524 19.3959 37.5449 19.2355C38.5617 19.0801 39.5759 18.9013 40.5867 18.6994L40.6926 18.6782C40.7191 19.0218 40.7326 19.369 40.7326 19.7194C40.7326 27.1036 34.743 33.0896 27.3546 33.0896C19.966 33.0896 13.9765 27.1036 13.9765 19.7194C13.9765 19.374 13.9896 19.0316 14.0154 18.6927L14.0486 18.6994C15.0837 18.9062 16.1223 19.0886 17.1637 19.2467ZM33.3284 11.4538C31.6493 10.2396 29.5855 9.52381 27.3546 9.52381C25.1195 9.52381 23.0524 10.2421 21.3717 11.4603C20.0078 11.3232 18.6475 11.1387 17.2933 10.907C19.7453 8.11308 23.3438 6.34921 27.3546 6.34921C31.36 6.34921 34.9543 8.10844 37.4061 10.896C36.0521 11.1292 34.692 11.3152 33.3284 11.4538ZM43.826 18.0518C43.881 18.6003 43.9091 19.1566 43.9091 19.7194C43.9091 28.8568 36.4973 36.2642 27.3546 36.2642C18.2117 36.2642 10.8 28.8568 10.8 19.7194C10.8 19.1615 10.8276 18.61 10.8816 18.0663L7.75383 17.4411C7.66775 18.1886 7.62354 18.9488 7.62354 19.7194C7.62354 30.6102 16.4574 39.4388 27.3546 39.4388C38.2517 39.4388 47.0855 30.6102 47.0855 19.7194C47.0855 18.9439 47.0407 18.1789 46.9536 17.4267L43.826 18.0518ZM44.2613 9.54743L40.9084 10.2176C37.9134 5.95821 32.9593 3.1746 27.3546 3.1746C21.7442 3.1746 16.7856 5.96385 13.7915 10.2305L10.4399 9.56057C13.892 3.83178 20.1756 0 27.3546 0C34.5281 0 40.8075 3.82591 44.2613 9.54743Z"
fill="var(--primary-color)"
/>
<mask
id="mask0_1413_1551"
style="mask-type: alpha"
maskUnits="userSpaceOnUse"
x="0"
y="8"
width="54"
height="11"
>
<path
d="M27 18.3652C10.5114 19.1944 0 8.88892 0 8.88892C0 8.88892 16.5176 14.5866 27 14.5866C37.4824 14.5866 54 8.88892 54 8.88892C54 8.88892 43.4886 17.5361 27 18.3652Z"
fill="var(--primary-color)"
/>
</mask>
<g mask="url(#mask0_1413_1551)">
<path
d="M-4.673e-05 8.88887L3.73084 -1.91434L-8.00806 17.0473L-4.673e-05 8.88887ZM27 18.3652L26.4253 6.95109L27 18.3652ZM54 8.88887L61.2673 17.7127L50.2691 -1.91434L54 8.88887ZM-4.673e-05 8.88887C-8.00806 17.0473 -8.00469 17.0505 -8.00132 17.0538C-8.00018 17.055 -7.99675 17.0583 -7.9944 17.0607C-7.98963 17.0653 -7.98474 17.0701 -7.97966 17.075C-7.96949 17.0849 -7.95863 17.0955 -7.94707 17.1066C-7.92401 17.129 -7.89809 17.1539 -7.86944 17.1812C-7.8122 17.236 -7.74377 17.3005 -7.66436 17.3743C-7.50567 17.5218 -7.30269 17.7063 -7.05645 17.9221C-6.56467 18.3532 -5.89662 18.9125 -5.06089 19.5534C-3.39603 20.83 -1.02575 22.4605 1.98012 24.0457C7.97874 27.2091 16.7723 30.3226 27.5746 29.7793L26.4253 6.95109C20.7391 7.23699 16.0326 5.61231 12.6534 3.83024C10.9703 2.94267 9.68222 2.04866 8.86091 1.41888C8.45356 1.10653 8.17155 0.867278 8.0241 0.738027C7.95072 0.673671 7.91178 0.637576 7.90841 0.634492C7.90682 0.63298 7.91419 0.639805 7.93071 0.65557C7.93897 0.663455 7.94952 0.673589 7.96235 0.686039C7.96883 0.692262 7.97582 0.699075 7.98338 0.706471C7.98719 0.710167 7.99113 0.714014 7.99526 0.718014C7.99729 0.720008 8.00047 0.723119 8.00148 0.724116C8.00466 0.727265 8.00796 0.730446 -4.673e-05 8.88887ZM27.5746 29.7793C37.6904 29.2706 45.9416 26.3684 51.6602 23.6054C54.5296 22.2191 56.8064 20.8465 58.4186 19.7784C59.2265 19.2431 59.873 18.7805 60.3494 18.4257C60.5878 18.2482 60.7841 18.0971 60.9374 17.977C61.014 17.9169 61.0799 17.8645 61.1349 17.8203C61.1624 17.7981 61.1872 17.7781 61.2093 17.7602C61.2203 17.7512 61.2307 17.7427 61.2403 17.7348C61.2452 17.7308 61.2499 17.727 61.2544 17.7233C61.2566 17.7215 61.2598 17.7188 61.261 17.7179C61.2642 17.7153 61.2673 17.7127 54 8.88887C46.7326 0.0650536 46.7357 0.0625219 46.7387 0.0600241C46.7397 0.0592345 46.7427 0.0567658 46.7446 0.0551857C46.7485 0.0520238 46.7521 0.0489887 46.7557 0.0460799C46.7628 0.0402623 46.7694 0.0349487 46.7753 0.0301318C46.7871 0.0204986 46.7966 0.0128495 46.8037 0.00712562C46.818 -0.00431848 46.8228 -0.00808311 46.8184 -0.00463784C46.8096 0.00228345 46.764 0.0378652 46.6828 0.0983779C46.5199 0.219675 46.2165 0.439161 45.7812 0.727519C44.9072 1.30663 43.5257 2.14765 41.7061 3.02677C38.0469 4.79468 32.7981 6.63058 26.4253 6.95109L27.5746 29.7793ZM54 8.88887C50.2691 -1.91433 50.27 -1.91467 50.271 -1.91498C50.2712 -1.91506 50.272 -1.91535 50.2724 -1.9155C50.2733 -1.91581 50.274 -1.91602 50.2743 -1.91616C50.2752 -1.91643 50.275 -1.91636 50.2738 -1.91595C50.2714 -1.91515 50.2652 -1.91302 50.2552 -1.9096C50.2351 -1.90276 50.1999 -1.89078 50.1503 -1.874C50.0509 -1.84043 49.8938 -1.78773 49.6844 -1.71863C49.2652 -1.58031 48.6387 -1.377 47.8481 -1.13035C46.2609 -0.635237 44.0427 0.0249875 41.5325 0.6823C36.215 2.07471 30.6736 3.15796 27 3.15796V26.0151C33.8087 26.0151 41.7672 24.2495 47.3292 22.7931C50.2586 22.026 52.825 21.2618 54.6625 20.6886C55.5842 20.4011 56.33 20.1593 56.8551 19.986C57.1178 19.8993 57.3258 19.8296 57.4735 19.7797C57.5474 19.7548 57.6062 19.7348 57.6493 19.72C57.6709 19.7127 57.6885 19.7066 57.7021 19.7019C57.7089 19.6996 57.7147 19.6976 57.7195 19.696C57.7219 19.6952 57.7241 19.6944 57.726 19.6938C57.7269 19.6934 57.7281 19.693 57.7286 19.6929C57.7298 19.6924 57.7309 19.692 54 8.88887ZM27 3.15796C23.3263 3.15796 17.7849 2.07471 12.4674 0.6823C9.95717 0.0249875 7.73904 -0.635237 6.15184 -1.13035C5.36118 -1.377 4.73467 -1.58031 4.3155 -1.71863C4.10609 -1.78773 3.94899 -1.84043 3.84961 -1.874C3.79994 -1.89078 3.76474 -1.90276 3.74471 -1.9096C3.73469 -1.91302 3.72848 -1.91515 3.72613 -1.91595C3.72496 -1.91636 3.72476 -1.91643 3.72554 -1.91616C3.72593 -1.91602 3.72657 -1.91581 3.72745 -1.9155C3.72789 -1.91535 3.72874 -1.91506 3.72896 -1.91498C3.72987 -1.91467 3.73084 -1.91433 -4.673e-05 8.88887C-3.73093 19.692 -3.72983 19.6924 -3.72868 19.6929C-3.72821 19.693 -3.72698 19.6934 -3.72603 19.6938C-3.72415 19.6944 -3.72201 19.6952 -3.71961 19.696C-3.71482 19.6976 -3.70901 19.6996 -3.7022 19.7019C-3.68858 19.7066 -3.67095 19.7127 -3.6494 19.72C-3.60629 19.7348 -3.54745 19.7548 -3.47359 19.7797C-3.32589 19.8296 -3.11788 19.8993 -2.85516 19.986C-2.33008 20.1593 -1.58425 20.4011 -0.662589 20.6886C1.17485 21.2618 3.74125 22.026 6.67073 22.7931C12.2327 24.2495 20.1913 26.0151 27 26.0151V3.15796Z"
fill="var(--primary-color)"
/>
</g>
</svg>
<span>SAKAI</span>
</router-link>
</div>
<div class="layout-topbar-actions">
<div class="layout-config-menu">
<button type="button" class="layout-topbar-action" @click="toggleDarkMode">
<i :class="['pi', { 'pi-moon': isDarkTheme, 'pi-sun': !isDarkTheme }]"></i>
</button>
<div class="relative">
<button
v-styleclass="{
selector: '@next',
enterFromClass: 'hidden',
enterActiveClass: 'animate-scalein',
leaveToClass: 'hidden',
leaveActiveClass: 'animate-fadeout',
hideOnOutsideClick: true,
}"
type="button"
class="layout-topbar-action layout-topbar-action-highlight"
>
<i class="pi pi-palette"></i>
</button>
<AppConfigurator />
</div>
</div>
<button
class="layout-topbar-menu-button layout-topbar-action"
v-styleclass="{
selector: '@next',
enterFromClass: 'hidden',
enterActiveClass: 'animate-scalein',
leaveToClass: 'hidden',
leaveActiveClass: 'animate-fadeout',
hideOnOutsideClick: true,
}"
>
<i class="pi pi-ellipsis-v"></i>
</button>
<div class="layout-topbar-menu hidden lg:block">
<div class="layout-topbar-menu-content">
<button type="button" class="layout-topbar-action">
<i class="pi pi-calendar"></i>
<span>Calendar</span>
</button>
<button type="button" class="layout-topbar-action">
<i class="pi pi-inbox"></i>
<span>Messages</span>
</button>
<button type="button" class="layout-topbar-action">
<i class="pi pi-user"></i>
<span>Profile</span>
</button>
</div>
</div>
</div>
</div>
</template>

View File

@@ -1,2 +0,0 @@
- https://primevue.org/templates/sakai/
- https://sakai.primevue.org/

View File

@@ -15,7 +15,7 @@ const getStoredMenuState = (): boolean => {
};
const layoutState = reactive({
activeMenuItem: null,
activeMenuItem: null as Record<string, never> | null,
configSidebarVisible: false,
menuHoverActive: false,
overlayMenuActive: false,

View File

@@ -1,26 +0,0 @@
html {
height: 100%;
// font-size: 14px;
}
body {
font-family: 'Lato', sans-serif;
color: var(--text-color);
background-color: var(--surface-ground);
margin: 0;
padding: 0;
min-height: 100%;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
a {
text-decoration: none;
}
.layout-wrapper {
min-height: 100vh;
@supports (min-height: 100dvh) {
min-height: 100dvh;
}
}

View File

@@ -1,8 +0,0 @@
.layout-footer {
display: flex;
align-items: center;
justify-content: center;
padding: 1rem 0 1rem 0;
gap: 0.5rem;
border-top: 1px solid var(--surface-border);
}

View File

@@ -1,17 +0,0 @@
.layout-main-container {
display: flex;
flex-direction: column;
min-height: 100vh;
justify-content: space-between;
padding: 6rem 2rem 0 2rem;
transition: margin-left var(--layout-section-transition-duration);
@supports (min-height: 100dvh) {
min-height: 100dvh;
}
}
.layout-main {
flex: 1 1 auto;
padding-bottom: 2rem;
position: relative;
}

View File

@@ -1,164 +0,0 @@
@use 'mixins' as *;
.layout-sidebar {
position: fixed;
width: 20rem;
height: calc(100vh - 8rem);
z-index: 999;
overflow-y: auto;
user-select: none;
top: 6rem;
left: 2rem;
transition:
transform var(--layout-section-transition-duration),
left var(--layout-section-transition-duration);
background-color: var(--surface-overlay);
border-radius: var(--content-border-radius);
padding: 0.5rem 1.5rem;
@supports (height: 100dvh) {
height: calc(100dvh - 8rem);
}
}
.layout-menu {
margin: 0;
padding: 0;
list-style-type: none;
.layout-root-menuitem {
> .layout-menuitem-root-text {
font-size: 0.857rem;
text-transform: uppercase;
font-weight: 700;
color: var(--text-color);
margin: 0.75rem 0;
}
> a {
display: none;
}
}
a {
user-select: none;
&.active-menuitem {
> .layout-submenu-toggler {
transform: rotate(-180deg);
}
}
}
li.active-menuitem {
> a {
.layout-submenu-toggler {
transform: rotate(-180deg);
}
}
}
ul {
margin: 0;
padding: 0;
list-style-type: none;
a {
display: flex;
align-items: center;
position: relative;
outline: 0 none;
color: var(--text-color);
cursor: pointer;
padding: 0.75rem 1rem;
border-radius: var(--content-border-radius);
transition:
background-color var(--element-transition-duration),
box-shadow var(--element-transition-duration);
.layout-menuitem-icon {
margin-right: 0.5rem;
}
.layout-submenu-toggler {
font-size: 75%;
margin-left: auto;
transition: transform var(--element-transition-duration);
}
&.active-route {
font-weight: 700;
color: var(--primary-color);
}
&:hover {
background-color: var(--surface-hover);
}
&:focus {
@include focused-inset();
}
}
ul {
overflow: hidden;
border-radius: var(--content-border-radius);
li {
a {
margin-left: 1rem;
}
li {
a {
margin-left: 2rem;
}
li {
a {
margin-left: 2.5rem;
}
li {
a {
margin-left: 3rem;
}
li {
a {
margin-left: 3.5rem;
}
li {
a {
margin-left: 4rem;
}
}
}
}
}
}
}
}
}
}
.layout-submenu-enter-from,
.layout-submenu-leave-to {
max-height: 0;
}
.layout-submenu-enter-to,
.layout-submenu-leave-from {
max-height: 1000px;
}
.layout-submenu-leave-active {
overflow: hidden;
transition: max-height 0.45s cubic-bezier(0, 1, 0, 1);
}
.layout-submenu-enter-active {
overflow: hidden;
transition: max-height 1s ease-in-out;
}

View File

@@ -1,15 +0,0 @@
@mixin focused() {
outline-width: var(--focus-ring-width);
outline-style: var(--focus-ring-style);
outline-color: var(--focus-ring-color);
outline-offset: var(--focus-ring-offset);
box-shadow: var(--focus-ring-shadow);
transition:
box-shadow var(--transition-duration),
outline-color var(--transition-duration);
}
@mixin focused-inset() {
outline-offset: -1px;
box-shadow: inset var(--focus-ring-shadow);
}

View File

@@ -1,48 +0,0 @@
.preloader {
position: fixed;
z-index: 999999;
background: #edf1f5;
width: 100%;
height: 100%;
}
.preloader-content {
border: 0 solid transparent;
border-radius: 50%;
width: 150px;
height: 150px;
position: absolute;
top: calc(50vh - 75px);
left: calc(50vw - 75px);
}
.preloader-content:before,
.preloader-content:after {
content: '';
border: 1em solid var(--primary-color);
border-radius: 50%;
width: inherit;
height: inherit;
position: absolute;
top: 0;
left: 0;
animation: loader 2s linear infinite;
opacity: 0;
}
.preloader-content:before {
animation-delay: 0.5s;
}
@keyframes loader {
0% {
transform: scale(0);
opacity: 0;
}
50% {
opacity: 1;
}
100% {
transform: scale(1);
opacity: 0;
}
}

View File

@@ -1,118 +0,0 @@
@media screen and (min-width: 1960px) {
.layout-main,
.landing-wrapper {
width: 1504px;
margin-left: auto !important;
margin-right: auto !important;
}
}
@media (min-width: 992px) {
.layout-wrapper {
&.layout-overlay {
.layout-main-container {
margin-left: 0;
padding-left: 2rem;
}
.layout-sidebar {
transform: translateX(-100%);
left: 0;
top: 0;
height: 100vh;
border-top-left-radius: 0;
border-bottom-left-radius: 0;
border-right: 1px solid var(--surface-border);
transition:
transform 0.4s cubic-bezier(0.05, 0.74, 0.2, 0.99),
left 0.4s cubic-bezier(0.05, 0.74, 0.2, 0.99);
box-shadow:
0px 3px 5px rgba(0, 0, 0, 0.02),
0px 0px 2px rgba(0, 0, 0, 0.05),
0px 1px 4px rgba(0, 0, 0, 0.08);
@supports (height: 100dvh) {
height: 100dvh;
}
}
&.layout-overlay-active {
.layout-sidebar {
transform: translateX(0);
}
}
}
&.layout-static {
.layout-main-container {
margin-left: 22rem;
}
&.layout-static-inactive {
.layout-sidebar {
transform: translateX(-100%);
left: 0;
}
.layout-main-container {
margin-left: 0;
padding-left: 2rem;
}
}
}
.layout-mask {
display: none;
}
}
}
@media (max-width: 991px) {
.blocked-scroll {
overflow: hidden;
}
.layout-wrapper {
.layout-main-container {
margin-left: 0;
padding-left: 2rem;
}
.layout-sidebar {
transform: translateX(-100%);
left: 0;
top: 0;
height: 100vh;
border-top-left-radius: 0;
border-bottom-left-radius: 0;
transition:
transform 0.4s cubic-bezier(0.05, 0.74, 0.2, 0.99),
left 0.4s cubic-bezier(0.05, 0.74, 0.2, 0.99);
@supports (height: 100dvh) {
height: 100dvh;
}
}
.layout-mask {
display: none;
position: fixed;
top: 0;
left: 0;
z-index: 998;
width: 100%;
height: 100%;
background-color: var(--maskbg);
}
&.layout-mobile-active {
.layout-sidebar {
transform: translateX(0);
}
.layout-mask {
display: block;
}
}
}
}

View File

@@ -1,202 +0,0 @@
@use 'mixins' as *;
.layout-topbar {
position: fixed;
height: 4rem;
z-index: 997;
left: 0;
top: 0;
width: 100%;
padding: 0 2rem;
background-color: var(--surface-card);
transition: left var(--layout-section-transition-duration);
display: flex;
align-items: center;
.layout-topbar-logo-container {
width: 20rem;
display: flex;
align-items: center;
}
.layout-topbar-logo {
display: inline-flex;
align-items: center;
font-size: 1.5rem;
border-radius: var(--content-border-radius);
color: var(--text-color);
font-weight: 500;
gap: 0.5rem;
svg {
width: 3rem;
}
&:focus-visible {
@include focused();
}
}
.layout-topbar-action {
display: inline-flex;
justify-content: center;
align-items: center;
color: var(--text-color-secondary);
border-radius: 50%;
width: 2.5rem;
height: 2.5rem;
color: var(--text-color);
transition: background-color var(--element-transition-duration);
cursor: pointer;
&:hover {
background-color: var(--surface-hover);
}
&:focus-visible {
@include focused();
}
i {
font-size: 1.25rem;
}
span {
font-size: 1rem;
display: none;
}
&.layout-topbar-action-highlight {
background-color: var(--primary-color);
color: var(--primary-contrast-color);
}
}
.layout-menu-button {
margin-right: 0.5rem;
}
.layout-topbar-menu-button {
display: none;
}
.layout-topbar-actions {
margin-left: auto;
display: flex;
gap: 1rem;
}
.layout-topbar-menu-content {
display: flex;
gap: 1rem;
}
.layout-config-menu {
display: flex;
gap: 1rem;
}
}
@media (max-width: 991px) {
.layout-topbar {
padding: 0 2rem;
.layout-topbar-logo-container {
width: auto;
}
.layout-menu-button {
margin-left: 0;
margin-right: 0.5rem;
}
.layout-topbar-menu-button {
display: inline-flex;
}
.layout-topbar-menu {
position: absolute;
background-color: var(--surface-overlay);
transform-origin: top;
box-shadow:
0px 3px 5px rgba(0, 0, 0, 0.02),
0px 0px 2px rgba(0, 0, 0, 0.05),
0px 1px 4px rgba(0, 0, 0, 0.08);
border-radius: var(--content-border-radius);
padding: 1rem;
right: 2rem;
top: 4rem;
min-width: 15rem;
border: 1px solid var(--surface-border);
.layout-topbar-menu-content {
gap: 0.5rem;
}
.layout-topbar-action {
display: flex;
width: 100%;
height: auto;
justify-content: flex-start;
border-radius: var(--content-border-radius);
padding: 0.5rem 1rem;
i {
font-size: 1rem;
margin-right: 0.5rem;
}
span {
font-weight: medium;
display: block;
}
}
}
.layout-topbar-menu-content {
flex-direction: column;
}
}
}
.config-panel {
.config-panel-label {
font-size: 0.875rem;
color: var(--text-secondary-color);
font-weight: 600;
line-height: 1;
}
.config-panel-colors {
> div {
padding-top: 0.5rem;
display: flex;
gap: 0.5rem;
flex-wrap: wrap;
justify-content: space-between;
button {
border: none;
width: 1.25rem;
height: 1.25rem;
border-radius: 50%;
padding: 0;
cursor: pointer;
outline-color: transparent;
outline-width: 2px;
outline-style: solid;
outline-offset: 1px;
&.active-color {
outline-color: var(--primary-color);
}
}
}
}
.config-panel-settings {
display: flex;
flex-direction: column;
gap: 0.5rem;
}
}

View File

@@ -1,68 +0,0 @@
h1,
h2,
h3,
h4,
h5,
h6 {
margin: 1.5rem 0 1rem 0;
font-family: inherit;
font-weight: 700;
line-height: 1.5;
color: var(--text-color);
&:first-child {
margin-top: 0;
}
}
h1 {
font-size: 2.5rem;
}
h2 {
font-size: 2rem;
}
h3 {
font-size: 1.75rem;
}
h4 {
font-size: 1.5rem;
}
h5 {
font-size: 1.25rem;
}
h6 {
font-size: 1rem;
}
mark {
background: #fff8e1;
padding: 0.25rem 0.4rem;
border-radius: var(--content-border-radius);
font-family: monospace;
}
blockquote {
margin: 1rem 0;
padding: 0 2rem;
border-left: 4px solid #90a4ae;
}
hr {
border-top: solid var(--surface-border);
border-width: 1px 0 0 0;
margin: 1rem 0;
}
p {
margin: 0 0 1rem 0;
line-height: 1.5;
&:last-child {
margin-bottom: 0;
}
}

View File

@@ -1,25 +0,0 @@
/* Utils */
.clearfix:after {
content: ' ';
display: block;
clear: both;
}
.card {
background: var(--surface-card);
padding: 2rem;
margin-bottom: 2rem;
border-radius: var(--content-border-radius);
&:last-child {
margin-bottom: 0;
}
}
.p-toast {
&.p-toast-top-right,
&.p-toast-top-left,
&.p-toast-top-center {
top: 100px;
}
}

View File

@@ -1,13 +0,0 @@
@use './variables/_common';
@use './variables/_light';
@use './variables/_dark';
@use './_mixins';
@use './_preloading';
@use './_core';
@use './_main';
@use './_topbar';
@use './_menu';
@use './_footer';
@use './_responsive';
@use './_utils';
@use './_typography';

View File

@@ -1,20 +0,0 @@
:root {
--primary-color: var(--p-primary-color);
--primary-contrast-color: var(--p-primary-contrast-color);
--text-color: var(--p-text-color);
--text-color-secondary: var(--p-text-muted-color);
--surface-border: var(--p-content-border-color);
--surface-card: var(--p-content-background);
--surface-hover: var(--p-content-hover-background);
--surface-overlay: var(--p-overlay-popover-background);
--transition-duration: var(--p-transition-duration);
--maskbg: var(--p-mask-background);
--content-border-radius: var(--p-content-border-radius);
--layout-section-transition-duration: 0.2s;
--element-transition-duration: var(--p-transition-duration);
--focus-ring-width: var(--p-focus-ring-width);
--focus-ring-style: var(--p-focus-ring-style);
--focus-ring-color: var(--p-focus-ring-color);
--focus-ring-offset: var(--p-focus-ring-offset);
--focus-ring-shadow: var(--p-focus-ring-shadow);
}

View File

@@ -1,5 +0,0 @@
:root[class*='app-dark'] {
--surface-ground: var(--p-surface-950);
--code-background: var(--p-surface-800);
--code-color: var(--p-surface-100);
}

View File

@@ -1,5 +0,0 @@
:root {
--surface-ground: var(--p-surface-100);
--code-background: var(--p-surface-900);
--code-color: var(--p-surface-200);
}

View File

@@ -1,7 +1,12 @@
import './styles';
import { LogLevels } from 'consola';
import App from './App.vue';
import { setupPlugins } from './plugins';
const autoInstallModules = import.meta.glob('./plugins/*.ts', { eager: true });
setupPlugins(createApp(App), autoInstallModules).mount('#app');
consola.level = LogLevels.verbose;

View File

@@ -1,4 +1,6 @@
<script setup lang="ts">
import axios from 'axios';
const baseURL = '/fake-api';
let fakeApiResult = $ref<null | Record<string, unknown>>(null);
@@ -9,6 +11,124 @@ onMounted(() => {
.then((json) => (fakeApiResult = json));
});
// 使用 axios 请求 XML并演示以文本获取后用 DOMParser 解析
// 选择此方式的原因:可控性更强,便于处理编码、容错与字段抽取策略
let xmlText = $ref<string | null>(null);
let xmlParsed = $ref<Record<string, string> | null>(null);
let xmlPostText = $ref<string | null>(null);
let xmlPostParsed = $ref<Record<string, string> | null>(null);
let xmlPostXmlText = $ref<string | null>(null);
let xmlPostXmlParsed = $ref<Record<string, string> | null>(null);
onMounted(async () => {
try {
const res = await axios.get<string>(`${baseURL}/xml/sample`, {
responseType: 'text',
headers: { Accept: 'application/xml' },
transformResponse: [(data: string) => data],
params: {
to: 'Alice',
from: 'Bob',
heading: 'Greeting',
body: 'Hello from API.page.vue',
id: '1001',
createdAt: new Date().toISOString(),
},
});
xmlText = res.data;
// 将 XML 字符串解析为 Document再选择性抽取业务所需字段
const doc = new DOMParser().parseFromString(res.data, 'application/xml');
const pick = (selector: string) => doc.querySelector(selector)?.textContent?.trim() ?? '';
xmlParsed = {
to: pick('to'),
from: pick('from'),
heading: pick('heading'),
body: pick('body'),
id: pick('meta > id'),
createdAt: pick('meta > createdAt'),
};
// 备用方案:直接请求为 Document部分运行环境可能不支持
// const docRes = await axios.get<Document>(`${baseURL}/xml/sample`, { responseType: 'document' });
// console.log('Document:', docRes.data);
} catch (error) {
console.error('XML 请求失败: ', error);
}
});
// 演示 POST 提交 JSON由服务端返回 XML再解析
onMounted(async () => {
try {
const res = await axios.post<string>(
`${baseURL}/xml/submit`,
{
to: 'Tom',
from: 'Jerry',
heading: 'PostXML',
body: 'This is a POST body to XML service',
id: '9001',
createdAt: new Date().toISOString(),
},
{
headers: { 'Content-Type': 'application/json', Accept: 'application/xml' },
responseType: 'text',
transformResponse: [(data: string) => data],
},
);
xmlPostText = res.data;
const doc = new DOMParser().parseFromString(res.data, 'application/xml');
const pick = (selector: string) => doc.querySelector(selector)?.textContent?.trim() ?? '';
xmlPostParsed = {
to: pick('to'),
from: pick('from'),
heading: pick('heading'),
body: pick('body'),
id: pick('meta > id'),
createdAt: pick('meta > createdAt'),
};
} catch (error) {
console.error('XML POST 请求失败: ', error);
}
});
// 演示 POST 以 XML 请求体提交,服务端解析 XML 并返回规范化的 XML
onMounted(async () => {
try {
const payload = `<?xml version="1.0" encoding="UTF-8"?>
<note>
<to>XML-Client</to>
<from>Browser</from>
<heading>XMLSubmit</heading>
<body>Send by XML body</body>
<meta>
<id>777</id>
<createdAt>${new Date().toISOString()}</createdAt>
</meta>
</note>`;
const res = await axios.post<string>(`${baseURL}/xml/submit`, payload, {
headers: { 'Content-Type': 'application/xml', Accept: 'application/xml' },
responseType: 'text',
transformResponse: [(data: string) => data],
});
xmlPostXmlText = res.data;
const doc = new DOMParser().parseFromString(res.data, 'application/xml');
const pick = (selector: string) => doc.querySelector(selector)?.textContent?.trim() ?? '';
xmlPostXmlParsed = {
to: pick('to'),
from: pick('from'),
heading: pick('heading'),
body: pick('body'),
id: pick('meta > id'),
createdAt: pick('meta > createdAt'),
};
} catch (error) {
console.error('XML POST(XML body) 请求失败: ', error);
}
});
/* fetch('https://jsonplaceholder.typicode.com/posts/1')
.then((response) => response.json())
.then((json) => console.log(json)); */
@@ -22,6 +142,23 @@ onMounted(() => {
<template>
<pre>{{ JSON.stringify(fakeApiResult, null, 2) }}</pre>
<!-- <div>{{ npmRegistryApiResult?.['_id'] }}</div> -->
<h3>XML 字符串</h3>
<pre class="break-all whitespace-pre-wrap">{{ xmlText }}</pre>
<h3>解析后的结果</h3>
<pre>{{ xmlParsed }}</pre>
<h3>POST 返回的 XML 字符串</h3>
<pre class="break-all whitespace-pre-wrap">{{ xmlPostText }}</pre>
<h3>POST 解析后的结果</h3>
<pre>{{ xmlPostParsed }}</pre>
<h3>POST(XML body) 返回的 XML 字符串</h3>
<pre class="break-all whitespace-pre-wrap">{{ xmlPostXmlText }}</pre>
<h3>POST(XML body) 解析后的结果</h3>
<pre>{{ xmlPostXmlParsed }}</pre>
</template>
<style scoped></style>

View File

@@ -32,7 +32,7 @@ function getItemById(id: number | string): ComponentItem | undefined {
*/
function addToStartList(item: ComponentItem): boolean {
const newStartIndex = getItemIndex(item);
const endIndex = 流程终点列表.value.length > 0 ? getItemIndex(流程终点列表.value[0]) : -1;
const endIndex = 流程终点列表.value.length > 0 ? getItemIndex(流程终点列表.value[0]!) : -1;
// 约束检查:如果终点已存在,则新起点必须在终点之前
if (endIndex !== -1 && newStartIndex >= endIndex) {
@@ -54,7 +54,7 @@ function addToStartList(item: ComponentItem): boolean {
*/
function addToEndList(item: ComponentItem): boolean {
const newEndIndex = getItemIndex(item);
const startIndex = 流程起点列表.value.length > 0 ? getItemIndex(流程起点列表.value[0]) : -1;
const startIndex = 流程起点列表.value.length > 0 ? getItemIndex(流程起点列表.value[0]!) : -1;
// 约束检查:如果起点已存在,则新终点必须在起点之后
if (startIndex !== -1 && newEndIndex <= startIndex) {
@@ -227,13 +227,13 @@ const 完整流程节点 = computed(() => {
const nodes: ComponentItem[] = [];
// 添加起点
if (流程起点列表.value.length > 0) {
nodes.push(流程起点列表.value[0]);
nodes.push(流程起点列表.value[0]!);
}
// 添加中间节点
if (流程起点列表.value.length > 0 && 流程终点列表.value.length > 0) {
const startIndex = getItemIndex(流程起点列表.value[0]);
const endIndex = getItemIndex(流程终点列表.value[0]);
const startIndex = getItemIndex(流程起点列表.value[0]!);
const endIndex = getItemIndex(流程终点列表.value[0]!);
if (startIndex !== -1 && endIndex !== -1 && startIndex < endIndex - 1) {
nodes.push(...组件列表.value.slice(startIndex + 1, endIndex));
@@ -242,7 +242,7 @@ const 完整流程节点 = computed(() => {
// 添加终点
if (流程终点列表.value.length > 0) {
nodes.push(流程终点列表.value[0]);
nodes.push(流程终点列表.value[0]!);
}
return nodes;

View File

@@ -0,0 +1,3 @@
# 测试
这个文件是被 import 的

View File

@@ -0,0 +1,11 @@
<script setup lang="ts">
import MD from './MDPageImportMD.md';
</script>
<template>
<div>
<MD />
</div>
</template>
<style scoped></style>

View File

@@ -0,0 +1,13 @@
<!--
http://localhost:4730/Page/PinPuYi
-->
<script setup lang="ts">
definePage({ meta: { title: '频谱仪', hidden: !__DEV__ } });
</script>
<template>
<template v-if="true">
<PinPuYi />
</template>
<template v-else> 🤡 </template>
</template>

View File

@@ -20,7 +20,7 @@ function generateFakeSpectrogramData(len = 30, baseLevel = -90, noiseRange = 30)
}
// 定时更新数据以模拟实时效果
let intervalId: null | number = null;
let intervalId: null | NodeJS.Timeout = null;
onMounted(() => {
// 模拟: 每x秒更新一次数据并通过 ref 发送

View File

@@ -79,10 +79,10 @@ const sketch = (p: P5) => {
// 更新和显示所有粒子
// 从后向前遍历数组,这样可以在遍历过程中安全地删除元素
for (let i = particles.length - 1; i >= 0; i--) {
particles[i].update();
particles[i].display();
particles[i]!.update();
particles[i]!.display();
// 如果粒子生命周期结束,则从数组中移除
if (particles[i].isDead()) {
if (particles[i]!.isDead()) {
particles.splice(i, 1);
}
}

View File

@@ -0,0 +1,352 @@
<script setup lang="ts">
import { VueUiXy, type VueUiXyConfig, type VueUiXyDatasetItem } from 'vue-data-ui';
import 'vue-data-ui/style.css'; // If you are using multiple components, place styles import in your main
const config = computed<VueUiXyConfig>(() => {
return {
theme: undefined,
responsive: false,
customPalette: [],
useCssAnimation: true,
downsample: {
threshold: 500,
},
chart: {
fontFamily: 'inherit',
backgroundColor: '#FFFFFFff',
color: '#1A1A1Aff',
height: 600,
width: 1000,
zoom: {
show: true,
color: '#CCCCCCff',
highlightColor: '#4A4A4A',
fontSize: 14,
useResetSlot: false,
startIndex: null,
endIndex: null,
enableRangeHandles: true,
enableSelectionDrag: true,
minimap: {
show: true,
smooth: false,
selectedColor: '#1F77B4',
selectedColorOpacity: 0.2,
lineColor: '#1A1A1A',
selectionRadius: 2,
indicatorColor: '#1A1A1A',
verticalHandles: false,
},
},
padding: {
top: 36,
right: 24,
bottom: 48,
left: 48,
},
highlighter: {
color: '#1A1A1Aff',
opacity: 5,
useLine: false,
lineDasharray: 2,
lineWidth: 1,
},
highlightArea: {
show: false,
from: 0,
to: 0,
color: '#CCCCCCff',
opacity: 20,
caption: {
text: 'Caption',
fontSize: 20,
color: '#1A1A1Aff',
bold: false,
offsetY: 0,
width: 'auto',
padding: 3,
textAlign: 'center',
},
},
timeTag: {
show: false,
backgroundColor: '#e1e5e8ff',
color: '#1A1A1Aff',
fontSize: 12,
circleMarker: {
radius: 3,
color: '#1A1A1Aff',
},
},
grid: {
stroke: '#e1e5e8ff',
showVerticalLines: false,
showHorizontalLines: false,
position: 'middle',
frame: {
show: false,
stroke: '#E1E5E8ff',
strokeWidth: 2,
strokeLinecap: 'round',
strokeLinejoin: 'round',
strokeDasharray: 0,
},
labels: {
show: true,
color: '#1A1A1Aff',
fontSize: 16,
axis: {
yLabel: '',
yLabelOffsetX: 0,
xLabel: '',
xLabelOffsetY: 14,
fontSize: 12,
},
zeroLine: {
show: true,
},
xAxis: {
showBaseline: true,
},
yAxis: {
showBaseline: true,
commonScaleSteps: 10,
useIndividualScale: false,
stacked: false,
gap: 12,
labelWidth: 40,
formatter: null,
scaleMin: null,
scaleMax: null,
groupColor: '#1A1A1A',
scaleLabelOffsetX: 0,
scaleValueOffsetX: 0,
},
xAxisLabels: {
color: '#1A1A1Aff',
show: true,
values: [],
fontSize: 18,
showOnlyFirstAndLast: false,
showOnlyAtModulo: false,
modulo: 12,
yOffset: 8,
rotation: 0,
},
},
},
comments: {
show: true,
showInTooltip: true,
width: 200,
offsetX: 0,
offsetY: 0,
},
labels: {
fontSize: 16,
prefix: '',
suffix: '',
},
legend: {
color: '#1A1A1Aff',
show: true,
fontSize: 16,
},
title: {
text: 'Title',
color: '#1A1A1Aff',
fontSize: 20,
bold: true,
textAlign: 'center',
paddingLeft: 0,
paddingRight: 0,
subtitle: {
color: '#CCCCCCff',
text: '',
fontSize: 16,
bold: false,
},
show: true,
},
tooltip: {
show: true,
color: '#1A1A1Aff',
backgroundColor: '#FFFFFFff',
fontSize: 14,
customFormat: null,
borderRadius: 4,
borderColor: '#e1e5e8',
borderWidth: 1,
backgroundOpacity: 30,
position: 'center',
offsetY: 24,
showTimeLabel: true,
showValue: true,
showPercentage: false,
roundingValue: 0,
roundingPercentage: 0,
},
userOptions: {
show: true,
showOnChartHover: false,
keepStateOnChartLeave: true,
position: 'right',
buttons: {
tooltip: true,
pdf: true,
csv: true,
img: true,
table: true,
labels: true,
fullscreen: true,
sort: false,
stack: true,
animation: false,
annotator: true,
},
buttonTitles: {
open: 'Open options',
close: 'Close options',
tooltip: 'Toggle tooltip',
pdf: 'Download PDF',
csv: 'Download CSV',
img: 'Download PNG',
table: 'Toggle table',
labels: 'Toggle labels',
fullscreen: 'Toggle fullscreen',
stack: 'Toggle stack mode',
annotator: 'Toggle annotator',
},
print: {
allowTaint: false,
backgroundColor: '#FFFFFFff',
useCORS: false,
onclone: null,
scale: 2,
logging: false,
},
},
},
bar: {
borderRadius: 2,
useGradient: true,
periodGap: 0.1,
border: {
useSerieColor: false,
strokeWidth: 1,
stroke: '#FFFFFFff',
},
labels: {
show: true,
offsetY: -8,
rounding: 0,
color: '#1A1A1Aff',
formatter: null,
},
serieName: {
show: false,
offsetY: -6,
useAbbreviation: true,
abbreviationSize: 3,
useSerieColor: true,
color: '#1A1A1Aff',
bold: false,
},
},
line: {
radius: 6,
useGradient: false,
strokeWidth: 2,
cutNullValues: false,
dot: {
hideAboveMaxSerieLength: 62,
useSerieColor: false,
fill: '#FFFFFF',
strokeWidth: 2,
},
labels: {
show: true,
offsetY: -16,
rounding: 0,
color: '#1A1A1Aff',
formatter: null,
},
area: {
useGradient: true,
opacity: 20,
},
tag: {
followValue: true,
formatter: null,
fontSize: 14,
},
},
plot: {
radius: 6,
useGradient: true,
dot: {
useSerieColor: true,
fill: '#FFFFFF',
strokeWidth: 0.5,
},
labels: {
show: true,
offsetY: -8,
rounding: 0,
color: '#1A1A1Aff',
formatter: null,
},
tag: {
followValue: true,
formatter: null,
fontSize: 14,
},
},
table: {
responsiveBreakpoint: 400,
rounding: 0,
sparkline: true,
showSum: true,
columnNames: {
period: 'Period',
total: 'Total',
},
th: {
backgroundColor: '#FAFAFAff',
color: '#1A1A1Aff',
outline: '',
},
td: {
backgroundColor: '#FAFAFAff',
color: '#1A1A1Aff',
outline: '',
},
},
showTable: false,
};
});
const dataset = computed<VueUiXyDatasetItem[]>(() => {
return [
{
name: 'Serie name',
series: [1, 9, 7, 2, 12, 16, 17, 30, 16, 23],
color: '#1f77b4',
type: 'line',
shape: 'circle',
useArea: false,
useProgression: false,
dataLabels: true,
smooth: true,
dashed: false,
useTag: 'none',
},
];
});
</script>
<template>
<!-- Using a wrapper is optional -->
<div :style="{ width: '600px' }">
<VueUiXy :config="config" :dataset="dataset" />
</div>
</template>

View File

@@ -1,12 +1,12 @@
<!-- https://github.com/intlify/vue-i18n/blob/master/examples/type-safe/type-annotation/src/components/en-US.json -->
<i18n lang="json">
<i18n lang="json5">
{
"en": {
"unplugin-hello": "Hello, unplugin-vue-i18n!"
en: {
'unplugin-hello': 'Hello, unplugin-vue-i18n!',
},
zh: {
'unplugin-hello': '你好unplugin-vue-i18n',
},
"zh": {
"unplugin-hello": "你好unplugin-vue-i18n"
}
}
</i18n>

View File

@@ -27,15 +27,7 @@ import { Input as ShaInput } from '@/shadcn/components/ui/input';
<ShadcnButton variant="ghost">Ghost</ShadcnButton>
<ShadcnButton variant="link">Link</ShadcnButton>
</div>
<div class="demo-block">
<VBtn>VBtn</VBtn>
<v-btn variant="outlined"> Button </v-btn>
<v-btn variant="tonal"> Button </v-btn>
<v-btn variant="text"> Button </v-btn>
<v-btn variant="plain"> Button </v-btn>
</div>
</div>
<div class="demo-block">
<a-input placeholder="Ant Design" />
<InputText placeholder="Primevue" />

View File

@@ -0,0 +1,11 @@
<script setup lang="ts"></script>
<template>
<n-card>
<n-divider title-placement="left">
<n-text depth="3"> Naive UI Components Divider </n-text>
</n-divider>
</n-card>
</template>
<style scoped></style>

View File

@@ -45,7 +45,7 @@ export const openDialog = async () => {
});
await nextTick();
// if ($__DEV__) return;
// if (__DEV__) return;
await new Promise((resolve) => setTimeout(resolve, 300));
DialogService.open(dialogContent, {

View File

@@ -8,7 +8,7 @@ const onClick = () => {
<template>
<div>
<ShadcnButton :onClick>ShadcnButton</ShadcnButton>
<ShadcnButton as="button" :onClick>ShadcnButton</ShadcnButton>
</div>
</template>

View File

@@ -28,7 +28,7 @@ defineOptions({
},
});
const refresh = () => {
state.list.splice(0, state.list.length);
state.list.splice(0);
state.page = 0;
state.complete = false;
loadMore();
@@ -53,7 +53,7 @@ async function loadMore() {
});
const data = await response.json();
state.list = [...state.list, ...data];
if ($__DEV__) await new Promise((resolve) => setTimeout(resolve, 500));
if (__DEV__) await new Promise((resolve) => setTimeout(resolve, 500));
state.complete = state.list.length >= 5;
} catch (error) {
state.error = error;

View File

@@ -152,7 +152,7 @@ line2 += '9';
// '',
// );
// if ($__DEV__) {
// if (__DEV__) {
// const tle = TLE_LIST[0] as string;
// const line1 = tle.split('\n')[1] as string;
// const line2 = tle.split('\n')[2] as string;
@@ -160,7 +160,7 @@ line2 += '9';
// twoline2satrecFake(line1, line2);
// }
// if ($__DEV__) {
// if (__DEV__) {
// const satrec2 = twoline2satrec(
// `1 62949 25070.91668981 .00039463 00000+0 99294-3 `,
// `2 53.1596 120.9032 0001355 101.1211 35.9659 15.39574303 `,

View File

@@ -66,7 +66,7 @@ onMounted(async () => {
const satelliteCheckboxOptions = computed(() =>
satelliteState.satellites.map((sat) => ({
// 从 TLE 字符串的第一行提取名称作为标签
label: sat.tle.split('\n')[0].trim(),
label: sat.tle.split('\n')[0]!.trim(),
value: sat.id,
})),
);

View File

@@ -2,7 +2,7 @@ import { autoAnimatePlugin } from '@formkit/auto-animate/vue';
import { createHead } from '@unhead/vue/client';
export function install({ app }: { app: import('vue').App<Element> }) {
app.config.globalProperties.$__DEV__ = $__DEV__;
app.config.globalProperties.__DEV__ = __DEV__;
app.use(autoAnimatePlugin); // v-auto-animate="{ duration: 100 }"

View File

@@ -1,7 +1,7 @@
import { DataLoaderPlugin } from 'unplugin-vue-router/data-loaders';
import { setupLayouts } from 'virtual:meta-layouts';
// import { createGetRoutes, setupLayouts } from 'virtual:generated-layouts';
import { createRouter, createWebHistory } from 'vue-router/auto';
import { createRouter, createWebHistory } from 'vue-router';
import { handleHotUpdate, routes } from 'vue-router/auto-routes';
const setupLayoutsResult = setupLayouts(routes);
@@ -14,7 +14,7 @@ const router = createRouter({
strict: true,
});
if (import.meta.hot) handleHotUpdate(router);
if ($__DEV__) Object.assign(globalThis, { router });
if (__DEV__) Object.assign(globalThis, { router });
router.onError((error) => {
console.debug('🚨 [router error]:', error);
});

View File

@@ -1,11 +0,0 @@
import { createVuetify } from 'vuetify';
import 'vuetify/styles';
export function install({ app }: { app: import('vue').App<Element> }) {
const vuetify = createVuetify({
// components,
// directives,
});
app.use(vuetify);
}

View File

@@ -1,24 +1,26 @@
<script setup lang="ts">
import type { HTMLAttributes } from 'vue'
import type { PrimitiveProps } from "reka-ui"
import type { HTMLAttributes } from "vue"
import type { ButtonVariants } from "."
import { Primitive } from "reka-ui"
import { cn } from '@/shadcn/lib/utils'
import { Primitive, type PrimitiveProps } from 'reka-ui'
import { type ButtonVariants, buttonVariants } from '.'
import { buttonVariants } from "."
interface Props extends PrimitiveProps {
variant?: ButtonVariants['variant']
size?: ButtonVariants['size']
class?: HTMLAttributes['class']
variant?: ButtonVariants["variant"]
size?: ButtonVariants["size"]
class?: HTMLAttributes["class"]
}
const props = withDefaults(defineProps<Props>(), {
as: 'button',
as: "button",
})
</script>
<template>
<Primitive
:as="as"
:as-child="asChild"
:as="props.as"
:as-child="props.asChild"
:class="cn(buttonVariants({ variant, size }), props.class)"
>
<slot />

View File

@@ -1,33 +1,34 @@
import { cva, type VariantProps } from 'class-variance-authority'
import type { VariantProps } from "class-variance-authority"
import { cva } from "class-variance-authority"
export { default as Button } from './Button.vue'
export { default as Button } from "./Button.vue"
export const buttonVariants = cva(
'inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0',
"inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0",
{
variants: {
variant: {
default: 'bg-primary text-primary-foreground shadow hover:bg-primary/90',
default: "bg-primary text-primary-foreground shadow hover:bg-primary/90",
destructive:
'bg-destructive text-destructive-foreground shadow-sm hover:bg-destructive/90',
"bg-destructive text-destructive-foreground shadow-sm hover:bg-destructive/90",
outline:
'border border-input bg-background shadow-sm hover:bg-accent hover:text-accent-foreground',
"border border-input bg-background shadow-sm hover:bg-accent hover:text-accent-foreground",
secondary:
'bg-secondary text-secondary-foreground shadow-sm hover:bg-secondary/80',
ghost: 'hover:bg-accent hover:text-accent-foreground',
link: 'text-primary underline-offset-4 hover:underline',
"bg-secondary text-secondary-foreground shadow-sm hover:bg-secondary/80",
ghost: "hover:bg-accent hover:text-accent-foreground",
link: "text-primary underline-offset-4 hover:underline",
},
size: {
default: 'h-9 px-4 py-2',
xs: 'h-7 rounded px-2',
sm: 'h-8 rounded-md px-3 text-xs',
lg: 'h-10 rounded-md px-8',
icon: 'h-9 w-9',
default: "h-9 px-4 py-2",
xs: "h-7 rounded px-2",
sm: "h-8 rounded-md px-3 text-xs",
lg: "h-10 rounded-md px-8",
icon: "h-9 w-9",
},
},
defaultVariants: {
variant: 'default',
size: 'default',
variant: "default",
size: "default",
},
},
)

View File

@@ -4,8 +4,8 @@ import { cn } from '@/shadcn/lib/utils'
import { useVModel } from '@vueuse/core'
const props = defineProps<{
defaultValue?: string | number
modelValue?: string | number
defaultValue?: string | number | undefined
modelValue?: string | number | undefined
class?: HTMLAttributes['class']
}>()

View File

@@ -13,4 +13,9 @@ import 'primeicons/primeicons.css';
import './reset/reset-primevue.css';
import './reset/reset-antdv.less';
// 通用字体
import 'vfonts/Lato.css';
// 等宽字体
import 'vfonts/FiraCode.css';
//
import 'virtual:uno.css';

9
src/types/global.ts Normal file
View File

@@ -0,0 +1,9 @@
declare global {
const __DEV__: boolean;
}
declare module 'vue' {
export interface ComponentCustomProperties {
__DEV__: boolean;
}
}

View File

@@ -16,5 +16,3 @@ declare module 'vue' {
interface HTMLAttributes
extends Partial<Record<import('@unocss/preset-attributify').AttributifyNames, boolean | string>> {}
}
export {};

View File

@@ -1,12 +1,4 @@
declare global {
const $__DEV__: boolean;
}
declare module 'vue' {
export interface ComponentCustomProperties {
$__DEV__: boolean;
}
declare module '*.vue' {
import type { ComponentOptions } from 'vue';
const Component: ComponentOptions;
@@ -19,5 +11,3 @@ declare module '*.md' {
const Component: ComponentOptions;
export default Component;
}
export {};

View File

@@ -1,6 +1,7 @@
{
"extends": "@vue/tsconfig/tsconfig.dom.json", // https://github.com/vuejs/create-vue/blob/main/template/tsconfig/base/tsconfig.app.json
"include": [
"env.d.ts",
"./typed-router.d.ts",
"./auto-imports.d.ts",
"./components.d.ts",
@@ -9,26 +10,15 @@
"src/**/*",
"src/**/*.vue"
],
"exclude": [
"src/**/__tests__/*",
],
"exclude": ["src/**/__tests__/*"],
"compilerOptions": {
"lib": ["ES2022", "DOM", "DOM.Iterable"],
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo",
"lib": ["ES2022", "DOM", "DOM.Iterable"],
"allowJs": true, // 允许编译 JavaScript 文件
"checkJs": true, // 启用 JavaScript 文件的类型检查
"types": [
"vite/client",
// "vitest",
"vite-plugin-vue-layouts/client",
"vite-plugin-vue-meta-layouts/client",
// "vite-plugin-pwa/client",
"unplugin-vue-macros/macros-global",
"unplugin-vue-router/client",
"unplugin-icons/types/vue",
"@intlify/unplugin-vue-i18n/messages"
],
"paths": {
"@/*": ["./src/*"]
}

View File

@@ -1,6 +1,16 @@
{
"files": [],
"references": [{ "path": "./tsconfig.app.json" }, { "path": "./tsconfig.node.json" }],
"references": [
{
"path": "./tsconfig.app.json"
},
{
"path": "./tsconfig.node.json"
},
{
"path": "./tsconfig.vitest.json"
}
],
"compilerOptions": {
// https://www.shadcn-vue.com/docs/components-json.html#aliases
// A fallback to tsconfig.app.json if no paths were found in tsconfig.json

11
tsconfig.vitest.json Normal file
View File

@@ -0,0 +1,11 @@
{
"extends": "./tsconfig.app.json",
"include": ["src/**/__tests__/*", "env.d.ts"],
"exclude": [],
"compilerOptions": {
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.vitest.tsbuildinfo",
"lib": [],
"types": ["node", "jsdom"]
}
}

168
typed-router.d.ts vendored
View File

@@ -34,9 +34,12 @@ declare module 'vue-router/auto-routes' {
'PageIframePageSpectrogram': RouteRecordInfo<'PageIframePageSpectrogram', '/Page/iframe-page/Spectrogram', Record<never, never>, Record<never, never>>,
'PageJSPage': RouteRecordInfo<'PageJSPage', '/Page/JSPage', Record<never, never>, Record<never, never>>,
'PageMDPage': RouteRecordInfo<'PageMDPage', '/Page/MDPage', Record<never, never>, Record<never, never>>,
'PageMDPageImportMD': RouteRecordInfo<'PageMDPageImportMD', '/Page/MDPageImportMD', Record<never, never>, Record<never, never>>,
'PageP5Js': RouteRecordInfo<'PageP5Js', '/Page/p5_js', Record<never, never>, Record<never, never>>,
'PagePinPuYi': RouteRecordInfo<'PagePinPuYi', '/Page/PinPuYi', Record<never, never>, Record<never, never>>,
'PageStyle': RouteRecordInfo<'PageStyle', '/Page/Style', Record<never, never>, Record<never, never>>,
'PageViteAssets': RouteRecordInfo<'PageViteAssets', '/Page/vite-assets', Record<never, never>, Record<never, never>>,
'PageVueDataUi': RouteRecordInfo<'PageVueDataUi', '/Page/vue-data-ui', Record<never, never>, Record<never, never>>,
'PkgsUsageI18n': RouteRecordInfo<'PkgsUsageI18n', '/PkgsUsage/I18n', Record<never, never>, Record<never, never>>,
'PkgsUsageTsEnumUtil': RouteRecordInfo<'PkgsUsageTsEnumUtil', '/PkgsUsage/ts-enum-util', Record<never, never>, Record<never, never>>,
'UIComponentsAntdV': RouteRecordInfo<'UIComponentsAntdV', '/UI-components/AntdV', Record<never, never>, Record<never, never>>,
@@ -44,10 +47,175 @@ declare module 'vue-router/auto-routes' {
'UIComponentsInfiniteLoading': RouteRecordInfo<'UIComponentsInfiniteLoading', '/UI-components/infinite-loading', Record<never, never>, Record<never, never>>,
'UIComponentsInfiniteLoadingDetail': RouteRecordInfo<'UIComponentsInfiniteLoadingDetail', '/UI-components/infinite-loading/detail', Record<never, never>, Record<never, never>>,
'UIComponentsInspiraUI': RouteRecordInfo<'UIComponentsInspiraUI', '/UI-components/InspiraUI', Record<never, never>, Record<never, never>>,
'UIComponentsNaiveUI': RouteRecordInfo<'UIComponentsNaiveUI', '/UI-components/NaiveUI', Record<never, never>, Record<never, never>>,
'UIComponentsPrimeVue': RouteRecordInfo<'UIComponentsPrimeVue', '/UI-components/PrimeVue', Record<never, never>, Record<never, never>>,
'UIComponentsShadcnVue': RouteRecordInfo<'UIComponentsShadcnVue', '/UI-components/ShadcnVue', Record<never, never>, Record<never, never>>,
'VueMacrosDefineRender': RouteRecordInfo<'VueMacrosDefineRender', '/VueMacros/DefineRender', Record<never, never>, Record<never, never>>,
'VueMacrosReactivityTransform': RouteRecordInfo<'VueMacrosReactivityTransform', '/VueMacros/ReactivityTransform', Record<never, never>, Record<never, never>>,
'VueMacrosReusableTemplate': RouteRecordInfo<'VueMacrosReusableTemplate', '/VueMacros/ReusableTemplate', Record<never, never>, Record<never, never>>,
}
/**
* Route file to route info map by unplugin-vue-router.
* Used by the volar plugin to automatically type useRoute()
*
* Each key is a file path relative to the project root with 2 properties:
* - routes: union of route names of the possible routes when in this page (passed to useRoute<...>())
* - views: names of nested views (can be passed to <RouterView name="...">)
*
* @internal
*/
export interface _RouteFileInfoMap {
'src/pages/index.page.vue': {
routes: 'Root'
views: never
}
'src/pages/[...path].page.vue': {
routes: '$Path'
views: never
}
'src/pages/cesium-viewer.page.vue': {
routes: 'CesiumViewer'
views: never
}
'src/pages/data-loaders.[id]/index.page.vue': {
routes: 'DataLoadersId'
views: never
}
'src/pages/data-loaders.[id]/sub-1.[userId].page.vue': {
routes: 'DataLoadersIdSub1UserId'
views: never
}
'src/pages/FlowbiteSidebar.page.vue': {
routes: 'FlowbiteSidebar'
views: never
}
'src/pages/Home.page.vue': {
routes: 'Home'
views: never
}
'src/pages/Page/API.page.vue': {
routes: 'PageAPI'
views: never
}
'src/pages/Page/Dom-Draggable.page.vue': {
routes: 'PageDomDraggable'
views: never
}
'src/pages/Page/fonts.page.vue': {
routes: 'PageFonts'
views: never
}
'src/pages/Page/Icons.page.vue': {
routes: 'PageIcons'
views: never
}
'src/pages/Page/iframe-page/Iframe-PlotlyJs.page.vue': {
routes: 'PageIframePageIframePlotlyJs'
views: never
}
'src/pages/Page/iframe-page/IframeConstellationDiagram.page.vue': {
routes: 'PageIframePageIframeConstellationDiagram'
views: never
}
'src/pages/Page/iframe-page/Spectrogram.page.vue': {
routes: 'PageIframePageSpectrogram'
views: never
}
'src/pages/Page/JSPage/index.page.vue': {
routes: 'PageJSPage'
views: never
}
'src/pages/Page/MDPage.page.md': {
routes: 'PageMDPage'
views: never
}
'src/pages/Page/MDPageImportMD.page.vue': {
routes: 'PageMDPageImportMD'
views: never
}
'src/pages/Page/p5_js/index.page.vue': {
routes: 'PageP5Js'
views: never
}
'src/pages/Page/PinPuYi.page.vue': {
routes: 'PagePinPuYi'
views: never
}
'src/pages/Page/Style/index.page.vue': {
routes: 'PageStyle'
views: never
}
'src/pages/Page/vite-assets/index.page.vue': {
routes: 'PageViteAssets'
views: never
}
'src/pages/Page/vue-data-ui.page.vue': {
routes: 'PageVueDataUi'
views: never
}
'src/pages/PkgsUsage/I18n.page.vue': {
routes: 'PkgsUsageI18n'
views: never
}
'src/pages/PkgsUsage/ts-enum-util.page.vue': {
routes: 'PkgsUsageTsEnumUtil'
views: never
}
'src/pages/UI-components/AntdV/index.page.vue': {
routes: 'UIComponentsAntdV'
views: never
}
'src/pages/UI-components/Components/index.page.vue': {
routes: 'UIComponentsComponents'
views: never
}
'src/pages/UI-components/infinite-loading/index.page.vue': {
routes: 'UIComponentsInfiniteLoading'
views: never
}
'src/pages/UI-components/infinite-loading/detail.page.vue': {
routes: 'UIComponentsInfiniteLoadingDetail'
views: never
}
'src/pages/UI-components/InspiraUI/index.page.vue': {
routes: 'UIComponentsInspiraUI'
views: never
}
'src/pages/UI-components/NaiveUI/index.page.vue': {
routes: 'UIComponentsNaiveUI'
views: never
}
'src/pages/UI-components/PrimeVue/index.page.vue': {
routes: 'UIComponentsPrimeVue'
views: never
}
'src/pages/UI-components/ShadcnVue/index.page.vue': {
routes: 'UIComponentsShadcnVue'
views: never
}
'src/pages/VueMacros/DefineRender.page.vue': {
routes: 'VueMacrosDefineRender'
views: never
}
'src/pages/VueMacros/ReactivityTransform.page.vue': {
routes: 'VueMacrosReactivityTransform'
views: never
}
'src/pages/VueMacros/ReusableTemplate.page.vue': {
routes: 'VueMacrosReusableTemplate'
views: never
}
}
/**
* Get a union of possible route names in a certain route component file.
* Used by the volar plugin to automatically type useRoute()
*
* @internal
*/
export type _RouteNamesForFilePath<FilePath extends string> =
_RouteFileInfoMap extends Record<FilePath, infer Info>
? Info['routes']
: keyof RouteNamedMap
}

View File

@@ -11,11 +11,11 @@ import Vue from '@vitejs/plugin-vue';
import VueJsx from '@vitejs/plugin-vue-jsx';
import path from 'node:path';
import UnoCSS from 'unocss/vite';
import UnpluginAutoImport from 'unplugin-auto-import/vite';
import AutoImport from 'unplugin-auto-import/vite';
import { FileSystemIconLoader } from 'unplugin-icons/loaders';
import IconsResolver from 'unplugin-icons/resolver';
import Icons from 'unplugin-icons/vite';
import { AntDesignVueResolver, TDesignResolver } from 'unplugin-vue-components/resolvers';
import { AntDesignVueResolver, NaiveUiResolver, TDesignResolver } from 'unplugin-vue-components/resolvers';
import Components from 'unplugin-vue-components/vite';
import VueMacros from 'unplugin-vue-macros/vite';
import Markdown from 'unplugin-vue-markdown/vite';
@@ -25,12 +25,12 @@ import { createUtils4uAutoImports } from 'utils4u/auto-imports';
import { type PluginOption } from 'vite';
import { checker } from 'vite-plugin-checker';
import { vitePluginFakeServer } from 'vite-plugin-fake-server';
import { ViteImageOptimizer } from 'vite-plugin-image-optimizer';
import pluginPurgeCss from 'vite-plugin-purgecss-updated-v5';
import { viteSingleFile } from 'vite-plugin-singlefile';
import { viteStaticCopy } from 'vite-plugin-static-copy';
import VueDevTools from 'vite-plugin-vue-devtools';
import MetaLayouts from 'vite-plugin-vue-meta-layouts';
import vuetify from 'vite-plugin-vuetify';
import { ViteWebfontDownload } from 'vite-plugin-webfont-dl';
import { viteArchiverPlugin } from './vite.config.plugin.archiver';
@@ -66,7 +66,8 @@ export function Plugins() {
// https://github.com/dishait/vite-plugin-vue-meta-layouts
MetaLayouts({
defaultLayout: 'sakai-vue/AppLayout',
// defaultLayout: 'sakai-vue/AppLayout',
defaultLayout: 'naive-ui/AppLayout',
skipTopLevelRouteLayout: false, // 打开修复 https://github.com/JohnCampionJr/vite-plugin-vue-layouts/issues/134默认为 false 关闭
}),
@@ -79,11 +80,8 @@ export function Plugins() {
headEnabled: true,
}),
// https://github.com/vuetifyjs/vuetify-loader/tree/master/packages/vite-plugin#automatic-imports
vuetify({ autoImport: true /* styles: 'none' */ }), // Enabled by default
// https://github.com/antfu/unplugin-auto-import
UnpluginAutoImport({
AutoImport({
dirs: [
// 'src/composables',
'src/stores',
@@ -101,6 +99,7 @@ export function Plugins() {
{
'consola/browser': ['consola'],
'vue-router/auto': ['useLink'],
'naive-ui': ['useDialog', 'useMessage', 'useNotification', 'useLoadingBar'],
},
],
resolvers: [TDesignResolver({ esm: true, library: 'mobile-vue' }), VantResolver({ importStyle: true })],
@@ -127,6 +126,7 @@ export function Plugins() {
TDesignResolver({ esm: true, library: 'mobile-vue' }),
VantResolver({ importStyle: true }),
PrimeVueResolver(/* { components: { prefix: 'P' } } */),
NaiveUiResolver(),
],
}),
@@ -166,7 +166,7 @@ export function Plugins() {
// https://github.com/condorheroblog/vite-plugin-fake-server?tab=readme-ov-file#usage
vitePluginFakeServer({
basename: 'fake-api',
enableProd: true,
enableProd: !true,
include: 'fake',
}),
@@ -194,6 +194,10 @@ export function Plugins() {
{ dest: cesiumBaseUrl, src: `${cesiumSource}/Widgets` },
],
}),
// https://github.com/FatehAK/vite-plugin-image-optimizer?tab=readme-ov-file#default-configuration
ViteImageOptimizer({
/* pass your config */
}),
);
// 检查是否在VS Code终端中运行

Some files were not shown because too many files have changed in this diff Show More