Compare commits

...

209 Commits
v4.1.3 ... main

Author SHA1 Message Date
XiaoDaiGua-Ray
49af61a339 version: v5.2.2 2025-08-09 19:04:29 +08:00
XiaoDaiGua-Ray
4bce5f7713 feat: 修改版本信息 2025-06-26 17:50:40 +08:00
XiaoDaiGua-Ray
34c20d4be7 fix: 修复一些问题 2025-06-26 17:48:07 +08:00
XiaoDaiGua-Ray
c28c353f7d version: v5.2.1 2025-06-26 17:45:31 +08:00
XiaoDaiGua-Ray
c68491fca9 version: v5.2.0 2025-06-06 21:28:56 +08:00
XiaoDaiGua-Ray
0f2193cc14 fix: 修复 vite 打包时,分包策略问题导致构建循环引用问题 2025-05-06 09:01:11 +08:00
XiaoDaiGua-Ray
ba6ceef0dc fix: 修复构建导致报错 2025-04-30 17:50:45 +08:00
XiaoDaiGua-Ray
5e9ffb14a3 version: 5.1.0 发布 2025-04-29 21:20:54 +08:00
XiaoDaiGua-Ray
eb5a6aa9e2 version: v5.1.0 2025-02-25 17:38:30 +08:00
XiaoDaiGua-Ray
674539edd3 fix: 一些已知问题修复 2025-01-19 10:38:13 +08:00
XiaoDaiGua-Ray
ff0bcb5022
Merge pull request #30 from admover/patch-3
Update test.ts
2025-01-15 20:17:19 +08:00
admover
d3d98190a3
Update test.ts
拼写修正
2025-01-15 17:36:05 +08:00
XiaoDaiGua-Ray
0bb707bba0 version: v5.0.10 2025-01-15 16:32:36 +08:00
XiaoDaiGua-Ray
4bfdbccd88 version: v5.0.9 2025-01-03 21:44:32 +08:00
XiaoDaiGua-Ray
3b2bba391e version: v5.0.8 2024-12-20 18:23:55 +08:00
XiaoDaiGua-Ray
7647508935 version: v5.0.7 2024-12-07 01:04:16 +08:00
XiaoDaiGua-Ray
852d7ca90a version: v5.0.6 2024-11-23 12:42:28 +08:00
XiaoDaiGua-Ray
2c84e3ce4c version: v5.0.5 2024-11-14 21:10:46 +08:00
XiaoDaiGua-Ray
83e0c19ba9 fix: 修复 cicd 部署问题 2024-11-09 14:58:54 +08:00
XiaoDaiGua-Ray
ff1a67c843 version: v5.0.4 2024-11-09 14:55:41 +08:00
XiaoDaiGua-Ray
8f3969268a version: v5.0.3 2024-10-27 17:01:53 +08:00
XiaoDaiGua-Ray
7783872ef6 version: v5.0.2 2024-10-23 20:48:01 +08:00
XiaoDaiGua-Ray
707300774d version: v5.0.1 2024-10-23 11:31:25 +08:00
XiaoDaiGua-Ray
2f42571b3b version: 5.0.0 2024-10-20 01:15:12 +08:00
XiaoDaiGua-Ray
73792144a8 fix: fix demo bug 2024-10-16 09:54:02 +08:00
XiaoDaiGua-Ray
11cbf8bca3 version: v4.9.7 2024-10-16 09:51:56 +08:00
XiaoDaiGua-Ray
f9473114e7
Merge pull request #27 from XiaoDaiGua-Ray/ray-template-electron
version: v4.9.6
2024-09-27 16:01:05 +08:00
XiaoDaiGua-Ray
7394e0bf30 version: v4.9.6 2024-09-27 15:59:01 +08:00
XiaoDaiGua-Ray
d12fcd18b6 version: v4.9.5 2024-09-18 14:41:15 +08:00
XiaoDaiGua-Ray
60ed09a0c5 version: v4.9.4 2024-08-31 14:51:22 +08:00
XiaoDaiGua-Ray
fa8d52601f version: v4.9.3 2024-08-23 17:45:19 +08:00
XiaoDaiGua-Ray
3f7e3722fd version: v4.9.2 2024-07-27 12:40:41 +08:00
XiaoDaiGua-Ray
ab6593f022 version: v4.9.1 2024-07-27 12:16:29 +08:00
XiaoDaiGua-Ray
d306ac8804 version: v4.9.0 2024-07-09 16:29:04 +08:00
XiaoDaiGua-Ray
8405cc5709 version: v4.8.9 2024-06-28 16:38:13 +08:00
XiaoDaiGua-Ray
6975af2368 version: v4.8.8 2024-06-24 10:25:48 +08:00
XiaoDaiGua-Ray
b0cd545c99 version: v4.8.7 2024-06-11 10:35:01 +08:00
XiaoDaiGua-Ray
7ca9663cb1 version: v4.8.6 2024-05-27 17:45:28 +08:00
XiaoDaiGua-Ray
85f0d43d7e fix: 一些bug修复 2024-05-27 17:40:02 +08:00
XiaoDaiGua-Ray
09315473a3 fix: 修复workflow错误 2024-05-17 15:33:12 +08:00
XiaoDaiGua-Ray
52dc6038da fix: 修复useTheme.spec.ts单测失败问题 2024-05-17 15:30:08 +08:00
XiaoDaiGua-Ray
27db2293f3 version: v4.8.5 2024-05-17 15:26:25 +08:00
XiaoDaiGua-Ray
5b035815eb version: v4.8.4 2024-05-09 17:29:28 +08:00
XiaoDaiGua-Ray
b06006b442 update: 添加macOS环境 2024-05-08 16:37:20 +08:00
XiaoDaiGua-Ray
557ab0467a version: v4.8.3 2024-05-04 18:31:57 +08:00
XiaoDaiGua-Ray
e5c16b3497 update: update workflows 2024-05-02 10:29:33 +08:00
XiaoDaiGua-Ray
f97b25e626 update: update engines.node version 2024-05-02 10:24:53 +08:00
XiaoDaiGua-Ray
8097296f8f fix: 修复workflow指定node版本错误语法 2024-05-02 10:22:46 +08:00
XiaoDaiGua-Ray
43df660ab6 update: 更新workflow配置,手动指定node版本 2024-05-02 10:16:33 +08:00
XiaoDaiGua-Ray
4e14f6a02d update: update the pnpm-lock.yaml file 2024-04-30 00:49:45 +08:00
XiaoDaiGua-Ray
472518af99 fix: 修复docs-deploy.yaml文件中的错误 2024-04-30 00:12:27 +08:00
XiaoDaiGua-Ray
d9ea6cbdf5 feat: 新增prettierignore文件忽略配置 2024-04-30 00:06:46 +08:00
XiaoDaiGua-Ray
01287743e7 update: 更新cicd配置文件node版本 2024-04-29 23:40:41 +08:00
XiaoDaiGua-Ray
41dce4920f fix: 修复baseURL配置错误问题 2024-04-29 23:33:39 +08:00
XiaoDaiGua-Ray
1ee3ce0500 version: v4.8.2 2024-04-29 23:29:11 +08:00
XiaoDaiGua-Ray
ae67b7b8b9 feat: 新增jsbarcode cdn配置 2024-04-14 23:52:00 +08:00
XiaoDaiGua-Ray
87d0a7a4af v4.8.1 2024-04-14 23:47:27 +08:00
XiaoDaiGua-Ray
683367cfd8
Merge pull request #25 from zhengxiongwei520/main
fix: 修复RSegment组件Popover warning提示
2024-04-14 23:03:32 +08:00
bearer
a6f7843ff9 fix: 修改warning提示 2024-04-14 22:50:00 +08:00
XiaoDaiGua-Ray
e95c06b009 version: v4.8.0 2024-04-13 15:24:51 +08:00
XiaoDaiGua-Ray
85f7f28dc4 refactor: 移除登陆时全屏加载动画的触发 2024-04-07 17:14:05 +08:00
XiaoDaiGua-Ray
7c429338d4 version: v4.7.5 2024-04-07 17:07:50 +08:00
XiaoDaiGua-Ray
a0f7763778 version: v4.7.4 2024-03-29 16:25:09 +08:00
XiaoDaiGua-Ray
03628890cb version: v4.7.3 2024-03-24 15:31:06 +08:00
XiaoDaiGua-Ray
3e36144cf9 docs: 更新README.md和README.zh-CN.md文件 2024-03-23 11:36:39 +08:00
XiaoDaiGua-Ray
5c52f2f88c fix: 修复PrintDomOptions类型错误问题 2024-03-23 11:29:41 +08:00
XiaoDaiGua-Ray
bed8432cda version: v4.7.2 2024-03-23 11:25:28 +08:00
XiaoDaiGua-Ray
c21df42187 feat: 移除getDependenciesVersion自定义方法 2024-03-17 15:36:10 +08:00
XiaoDaiGua-Ray
3bf297d81c version: v4.7.1 2024-03-17 15:25:49 +08:00
XiaoDaiGua-Ray
70eee28aa8 version: v4.7.0 2024-03-09 23:39:59 +08:00
XiaoDaiGua-Ray
e20dbb4cd2 version: v4.6.4 2024-02-29 17:03:17 +08:00
XiaoDaiGua-Ray
f28e343e6c fix: 修复 cdn 配置导致构建生产环境错误 2024-02-28 10:47:54 +08:00
XiaoDaiGua-Ray
8db423a018 version: v4.6.4-beta1.1 2024-02-28 10:31:25 +08:00
XiaoDaiGua-Ray
0c1842cbbd fix: 修复构建类型错误导致失败问题 2024-02-21 15:38:39 +08:00
XiaoDaiGua-Ray
9a4e2e723d version: 4.6.4-beta1.0 2024-02-21 15:08:08 +08:00
XiaoDaiGua-Ray
3fa9478ee4 version: 4.6.3 2024-02-09 14:05:09 +08:00
XiaoDaiGua-Ray
25599cea24 version: 4.6.2 2024-01-31 17:21:41 +08:00
XiaoDaiGua-Ray
a00e3ca557 version: 4.6.2-beta1.2 2024-01-28 15:40:04 +08:00
XiaoDaiGua-Ray
8ced024a94 version: 4.6.2-beta1.1 2024-01-26 23:32:53 +08:00
XiaoDaiGua-Ray
af1eeac035 fix: fix changelog title 2024-01-25 17:52:37 +08:00
XiaoDaiGua-Ray
cc0577eb8f version: v4.6.2-beta 2024-01-25 17:12:32 +08:00
XiaoDaiGua-Ray
bb61081ddd feat: 调整 reload 方法位置,现在调整为在 MenuTag 2024-01-22 17:56:06 +08:00
XiaoDaiGua-Ray
a3341450f1 version: 4.6.1 2024-01-22 17:28:16 +08:00
XiaoDaiGua-Ray
b97e2ee701 fix: 修复 closeAll 方法在特殊情况下不能正确的关闭标签页并且跳转至 root path 的问题 2024-01-18 15:02:36 +08:00
XiaoDaiGua-Ray
b41d0490ca fix: 修复面包屑 fullPath 在平级模式下追加时为绑定问题 2024-01-18 14:41:36 +08:00
XiaoDaiGua-Ray
43bf252d84 version: v4.6.0正式发布 2024-01-18 14:36:33 +08:00
XiaoDaiGua-Ray
8c1e215013 fix: 修复构建输出与预览环境不一致的问题,修复构建页面崩溃问题 2024-01-12 21:55:26 +08:00
XiaoDaiGua-Ray
79056e8846 fix: 更新@vueuse版本至vue3.4.7适配版本 2024-01-12 21:26:13 +08:00
XiaoDaiGua-Ray
5660530d8a fix: 修复构建vue cdn包时,引入的包名错误 2024-01-12 21:17:53 +08:00
XiaoDaiGua-Ray
57ed469543 version: 4.6.0 2024-01-12 21:07:34 +08:00
XiaoDaiGua-Ray
c2041b2bb6
Merge pull request #24 from yunkuangao/main
fix(docs): 修复文档问题
2024-01-11 09:01:56 +07:00
yun
77a4fcb9a2
fix(docs): 修复文档说明 2024-01-10 11:03:23 +08:00
yun
f60a107997
Merge remote-tracking branch 'upstream/main' 2024-01-10 10:59:09 +08:00
yun
17e60fdf51
fix(ci): 修复失效的链接 2024-01-10 10:56:49 +08:00
XiaoDaiGua-Ray
f663cf5b41 update: 补充v4.5.0版本新增一些功能,修改了一些bug 2024-01-01 22:04:22 +08:00
XiaoDaiGua-Ray
740d415a3f version: 4.5.0 2023-12-29 23:24:18 +08:00
XiaoDaiGua-Ray
8b56327eaa
Merge pull request #22 from yunkuangao/main
Update docs-deploy.yaml
2023-12-25 09:46:08 +08:00
yun
9d38f99af2
Update docs-deploy.yaml
delete if trigger when pull request closed
2023-12-25 09:08:43 +08:00
XiaoDaiGua-Ray
d98a42380d version: 4.4.7 2023-12-22 21:50:25 +08:00
XiaoDaiGua-Ray
f5ae01f33a
Merge pull request #21 from yunkuangao/main
fix(ci)/add CODE_OF_CONDUCT.md
2023-12-20 21:42:31 +08:00
yun
c1205d9912 fix(ci): 修复在pull request时触发docs-deploy的问题 2023-12-20 09:24:00 +08:00
yun
ade9a03f1e docs: 添加贡献者公约 2023-12-20 09:11:29 +08:00
XiaoDaiGua-Ray
45be191112 version: v4.4.6 2023-12-15 22:26:36 +08:00
XiaoDaiGua-Ray
8a6f983bf3 fix: 修复因为cdn切换导致一些资源加载失败的问题导致页面无法加载的问题 2023-12-15 15:36:15 +08:00
XiaoDaiGua-Ray
7d8b1b123f version: v4.4.5 2023-12-15 14:12:35 +08:00
XiaoDaiGua-Ray
6649579a0c feat: 更新了一些插件 2023-12-15 11:24:55 +08:00
XiaoDaiGua-Ray
0b4a0cc237 update: 更新dts的一些细节 2023-12-15 11:24:23 +08:00
XiaoDaiGua-Ray
0c9105b54e update: 更新router包一些小细节,和一些注释 2023-12-15 11:23:51 +08:00
XiaoDaiGua-Ray
7c51a5c78f update: 更新echart主题的默认背景色为transparent 2023-12-15 11:23:04 +08:00
XiaoDaiGua-Ray
49fc22d601 version: v4.4.4发布 2023-12-14 20:34:47 +08:00
XiaoDaiGua-Ray
66cf689d70 chore: 更新commitlint配置,添加husky配置,实现commitlint检查提交信息 2023-12-14 20:34:47 +08:00
XiaoDaiGua-Ray
e7733fa105
Merge pull request #19 from yunkuangao/main
feat: 允许自动根据main分支更新构建发布流程
2023-12-13 16:06:37 +08:00
yun
c67f5d6734 修复docs-deploy.yaml 2023-12-13 11:09:59 +08:00
yun
e9caa52401 修复docs-deploy.yaml 2023-12-13 11:08:53 +08:00
yun
998b07cdd7 添加document deploy的ci 2023-12-13 11:05:48 +08:00
XiaoDaiGua-Ray
fdff8c7fe7 feat: 优化menu store路由刷新、跳转功能 2023-12-10 14:53:27 +08:00
XiaoDaiGua-Ray
89618e402f feat: 更正配置文件命名 2023-12-06 16:10:30 +08:00
XiaoDaiGua-Ray
30e6d6dddb feat: 更新详情页面demo展示 2023-12-06 12:33:20 +08:00
XiaoDaiGua-Ray
2c24ae5d94 fix: 修复构建右键菜单demo时类型错误导致构建失败 2023-12-06 12:30:27 +08:00
XiaoDaiGua-Ray
5299a0245f version: v4.4.3 2023-12-06 12:28:56 +08:00
XiaoDaiGua-Ray
c304f37146 feat: v4.4.2版本细节更新 2023-12-03 18:08:12 +08:00
XiaoDaiGua-Ray
f272832a8c version: v4.4.2 2023-12-01 23:18:55 +08:00
XiaoDaiGua-Ray
9e3cbac091 v4.4.1 2023-11-29 23:26:39 +08:00
XiaoDaiGua-Ray
429e51fc30 feat: v4.4.0细节补充 2023-11-25 11:42:07 +08:00
XiaoDaiGua-Ray
cda4216493 v4.4.0 2023-11-25 11:36:32 +08:00
XiaoDaiGua-Ray
38069b5b8c
Merge pull request #16 from yunkuangao/main
fix: Create Dockerfile and docker-compose.yml
2023-11-22 15:40:03 +08:00
yun
e76723adb6 Merge remote-tracking branch 'upstream/main' 2023-11-22 11:40:49 +08:00
yun
3767861fe4 修复docker 2023-11-22 11:39:16 +08:00
yun
416fbd7990 添加docker 2023-11-22 11:01:14 +08:00
XiaoDaiGua-Ray
23e80137b7
Merge pull request #15 from yunkuangao/main
fix: 修复node低版本导致CI报错问题
2023-11-20 10:02:14 +08:00
yun
e521b7643c 触发GitHub action 2023-11-20 09:28:35 +08:00
yun
b1dfa53d7f 删除ci的node的版本16.x 2023-11-20 09:27:15 +08:00
XiaoDaiGua-Ray
6c389eb496 version: v4.3.4 2023-11-19 14:37:04 +08:00
XiaoDaiGua-Ray
94f975eaff version: v4.3.3 2023-11-17 16:03:07 +08:00
XiaoDaiGua-Ray
335e456688 feat: v4.3.2细节补充 2023-11-17 13:30:59 +08:00
XiaoDaiGua-Ray
711be2006b fix: v4.3.2 bug fixed 2023-11-15 10:07:34 +08:00
XiaoDaiGua-Ray
9168cd876e version: v4.3.2 2023-11-14 10:42:48 +08:00
XiaoDaiGua-Ray
aff8437089 v4.3.1 2023-11-11 00:20:10 +08:00
XiaoDaiGua-Ray
04daa39e49 update: update app version 2023-11-06 17:18:20 +08:00
XiaoDaiGua-Ray
32183d0ab9 v4.3.0 2023-11-06 17:17:44 +08:00
XiaoDaiGua-Ray
f7887989f9 delete: 避免重复检查 2023-11-02 17:52:34 +08:00
XiaoDaiGua-Ray
36d646d8ba v4.2.9 2023-11-02 17:51:12 +08:00
XiaoDaiGua-Ray
a840693455 v4.2.8 2023-10-27 14:55:25 +08:00
XiaoDaiGua-Ray
f6c343911e v4.2.7 2023-10-25 17:27:50 +08:00
XiaoDaiGua-Ray
b05edfeaeb v4.2.6 2023-10-24 17:42:01 +08:00
XiaoDaiGua-Ray
9e3f199d21 version: v4.2.5 2023-10-22 00:11:20 +08:00
XiaoDaiGua-Ray
e81bb3f9a5 v4.2.4 2023-10-21 14:37:33 +08:00
XiaoDaiGua-Ray
1f2ae05ca4 feat: v4.2.3发布 2023-10-14 17:18:47 +08:00
XiaoDaiGua-Ray
d698db587f fix: 修复useWindowSize未找到bug 2023-10-11 17:21:14 +08:00
XiaoDaiGua-Ray
0c7adb9584 feat: v4.2.2一些细节补充与优化 2023-10-11 17:17:14 +08:00
XiaoDaiGua-Ray
fac9f9413d 4.2.2 2023-10-10 15:06:30 +08:00
XiaoDaiGua-Ray
3fb016513b v4.2.2 2023-10-10 15:05:48 +08:00
yun
43be9bc3f2
Merge pull request #14 from ray-template/ray-template-dev
Merge pull request #13 from ray-template/main
2023-09-28 12:13:36 +08:00
yun
a82b28fb93
Merge pull request #13 from ray-template/main
sync main to dev
2023-09-28 11:41:59 +08:00
XiaoDaiGua-Ray
353bc7d809 readme: update README.md and add english README.md 2023-09-25 16:13:28 +08:00
XiaoDaiGua-Ray
5e0c85c7f5 fix: 修复baseURL 2023-09-22 22:20:19 +08:00
XiaoDaiGua-Ray
7a96b58541 4.2.1 2023-09-22 21:50:55 +08:00
XiaoDaiGua-Ray
4960eb8175 v4.2.1发布 2023-09-22 21:50:24 +08:00
XiaoDaiGua-Ray
4d4d5451dc v4.2.0 2023-09-16 00:03:05 +08:00
XiaoDaiGua-Ray
405e62c762 v4.2.0 2023-09-15 23:59:28 +08:00
XiaoDaiGua-Ray
d2773d62bc v4.2.0 2023-09-15 23:57:39 +08:00
XiaoDaiGua-Ray
4331b6a3b1 v4.2.0 2023-09-15 23:57:14 +08:00
XiaoDaiGua-Ray
6acc80f18a v4.1.9部分细节补充 2023-09-08 15:13:36 +08:00
XiaoDaiGua-Ray
689813f1a0 feat: v4.1.9补充qrcode组件 2023-09-04 15:01:17 +08:00
XiaoDaiGua-Ray
ccc4d6c710 4.1.9 2023-08-31 15:30:17 +08:00
XiaoDaiGua-Ray
d6be8fe9fe v4.1.9 2023-08-31 15:29:46 +08:00
XiaoDaiGua-Ray
4dd2024bec fix: 修复RayCollapseGrid组件一些小问题 2023-08-29 10:30:21 +08:00
XiaoDaiGua-Ray
b1890df8f6 v4.1.8细节更新 2023-08-25 17:01:35 +08:00
XiaoDaiGua-Ray
2690e71713 v4.1.8一些细节补充 2023-08-25 16:49:16 +08:00
XiaoDaiGua-Ray
99cafd8cb1 4.1.8 2023-08-20 14:57:22 +08:00
XiaoDaiGua-Ray
17c5ca7e50 v4.1.8版本发布 2023-08-20 14:56:51 +08:00
XiaoDaiGua-Ray
6f98e8fb0d v4.1.7细节更新 2023-08-14 17:42:05 +08:00
XiaoDaiGua-Ray
1be73da92a Merge branch 'ray-template-dev' 2023-08-11 22:53:14 +08:00
XiaoDaiGua-Ray
72996dbee7
Merge pull request #12 from XiaoDaiGua-Ray/ray-template-dev
Ray template dev
2023-08-11 22:51:55 +08:00
XiaoDaiGua-Ray
811bd0ca1c 4.1.7 2023-08-11 22:50:38 +08:00
XiaoDaiGua-Ray
a9c7e6589f 4.1.7一些小细节更新 2023-08-11 22:48:39 +08:00
XiaoDaiGua-Ray
018f4a9aca mock 2023-08-11 21:50:58 +08:00
XiaoDaiGua-Ray
3b0dcfc8e2 4.1.6 2023-08-09 16:44:20 +08:00
XiaoDaiGua-Ray
5eb201b25b v4.1.6 2023-08-09 16:43:59 +08:00
XiaoDaiGua-Ray
be406cc026 v4.1.5一些小细节更新 2023-08-06 16:48:38 +08:00
XiaoDaiGua-Ray
660b61340d Merge branch 'main' into ray-template-dev 2023-08-04 10:10:55 +08:00
XiaoDaiGua-Ray
d203ff9d95 check eslint plugin 2023-08-04 09:51:44 +08:00
XiaoDaiGua-Ray
f1134ed5b6 4.1.5 2023-08-03 16:59:11 +08:00
XiaoDaiGua-Ray
6b8921ee7b v4.1.5 2023-08-03 16:54:13 +08:00
XiaoDaiGua-Ray
1b5edfb479 add eslint check 2023-08-03 11:11:21 +08:00
XiaoDaiGua-Ray
3e6109695e add eslint 2023-08-03 10:31:19 +08:00
XiaoDaiGua-Ray
97b05f52a5
Merge pull request #9 from yunkuangao/main
Fix: eol and ci
2023-08-03 10:11:53 +08:00
yun
449dd2004b Merge remote-tracking branch 'origin/main' 2023-08-03 10:04:18 +08:00
yun
6cd5a1cf3e 添加fail-fast和experimental,使单个job错误时不影响其他job运行 2023-08-03 10:02:44 +08:00
yun
8693a4098a 设置仓库的换行符为lf 2023-08-03 10:02:44 +08:00
yun
c51767df55 将pnpm升级到8,版本7有问题 2023-08-03 10:02:44 +08:00
yun
502672eef9 添加fail-fast和experimental,使单个job错误时不影响其他job运行 2023-08-03 09:56:30 +08:00
yun
fd71ac7773 设置仓库的换行符为lf 2023-08-03 09:54:08 +08:00
yun
ef60abb3af 将pnpm升级到8,版本7有问题 2023-08-03 09:53:19 +08:00
XiaoDaiGua-Ray
56fc982dd7 eslint bug fixed 2023-08-02 18:00:03 +08:00
XiaoDaiGua-Ray
50196fac8f eslint delete 2023-08-02 17:53:59 +08:00
XiaoDaiGua-Ray
03f6ae3f9f fixed some bugs 2023-08-02 17:32:27 +08:00
XiaoDaiGua-Ray
a69a032517 bug fixed: 修复组件潜在的修改props属性的问题 2023-08-02 15:44:59 +08:00
XiaoDaiGua-Ray
74da406808 update eslint rules 2023-08-02 15:30:31 +08:00
XiaoDaiGua-Ray
08f236dd0a
Merge pull request #8 from yunkuangao/main
add action for build test
2023-08-02 15:08:07 +08:00
yun
11b8a6a73a 添加 linux windows macos
node 16.x 18.x
的ci测试
2023-08-02 14:00:49 +08:00
yun
b2156a4c1d Merge remote-tracking branch 'upstream/main' 2023-08-02 13:57:15 +08:00
XiaoDaiGua-Ray
1643ee392e 临时提交任务13:51 2023-08-02 13:51:27 +08:00
yun
f49f606f7d 添加构建测试ci 2023-08-01 21:05:33 +08:00
XiaoDaiGua-Ray
977331ecb3 update: markdown file 2023-08-01 17:06:29 +08:00
XiaoDaiGua-Ray
1e7a0ef1cf 4.1.4 2023-08-01 16:23:51 +08:00
XiaoDaiGua-Ray
20757214d8 v4.1.4 2023-08-01 16:20:43 +08:00
ray_wuhao
8277cbc4b8 update 2023-07-23 13:26:55 +08:00
ray_wuhao
7255a8d835 add: v4.1.3细节补充 2023-07-23 13:21:29 +08:00
616 changed files with 39540 additions and 10177 deletions

5
.dockerignore Normal file
View File

@ -0,0 +1,5 @@
node_modules
.git
.gitignore
*.md
dist

View File

@ -1,6 +1,4 @@
#生产环境
NODE_ENV = 'production'
VITE_APP_URL = '/'
# office 服务代理地址

View File

@ -1,16 +0,0 @@
dist/*
node_modules/*
auto-imports.d.ts
components.d.ts
.gitignore
.vscode
public
yarn.*
vite-env.*
.prettierrc.*
visualizer.*
visualizer.html
.env.*
src/locales/lang
.depcheckrc
src/components/RayChart/theme

View File

@ -1,198 +0,0 @@
/* eslint-env node */
module.exports = {
root: true,
env: {
browser: true,
node: true,
},
extends: [
'eslint-config-prettier',
'eslint:recommended',
'plugin:@typescript-eslint/recommended',
'plugin:vue/vue3-recommended',
'plugin:vue/vue3-essential',
'plugin:prettier/recommended',
'prettier',
],
parser: 'vue-eslint-parser',
parserOptions: {
ecmaVersion: 2020,
sourceType: 'module',
parser: '@typescript-eslint/parser',
ecmaFeatures: {
jsx: true,
tsx: true,
},
},
plugins: ['vue', '@typescript-eslint', 'prettier'],
globals: {
defineProps: 'readonly',
defineEmits: 'readonly',
defineExpose: 'readonly',
withDefaults: 'readonly',
defineOptions: 'readonly',
},
rules: {
'@typescript-eslint/no-explicit-any': [
'error',
{
ignoreRestArgs: true,
},
],
'prettier/prettier': 'error',
'no-unused-vars': 'off',
'@typescript-eslint/no-unused-vars': 'off',
'@typescript-eslint/ban-types': 'off',
'@typescript-eslint/explicit-module-boundary-types': 'off',
'@typescript-eslint/no-var-requires': 'off',
'@typescript-eslint/no-non-null-assertion': 'off',
'@typescript-eslint/no-non-null-asserted-optional-chain': 'off',
'@typescript-eslint/consistent-type-imports': [
'error',
{ disallowTypeAnnotations: false },
], // 强制导入类型显示标注 `import type xxx from 'xxx'`
'@typescript-eslint/no-empty-interface': [
'error',
{
allowSingleExtends: true,
},
],
'accessor-pairs': 2, // 强制同时存在 `get` 与 `set`
'constructor-super': 0, // 强制子类构造函数中使用 `super` 调用父类的构造函数
'default-case': 2, // `switch` 中强制含有 `default`
eqeqeq: [2, 'allow-null'], // 强制使用严格判断 `===`
'no-alert': 0, // 禁止使用 `alert`、`confirm`
'no-array-constructor': 2, // 禁止使用数组构造器
'no-bitwise': 0, // 禁止使用按位运算符
'no-caller': 1, // 禁止使用 `arguments.caller`、`arguments.callee`
'no-catch-shadow': 2, // 禁止 `catch` 子句参数与外部作用域变量同名
'no-class-assign': 2, // 禁止给类赋值
'no-cond-assign': 2, // 禁止在条件表达式中使用赋值语句
'no-const-assign': 2, // 禁止修改 `const` 声明的变量
'no-constant-condition': 2, // 禁止在条件中使用常量表达式 `if(true)`、`if(1)`
'no-dupe-keys': 2, // 在创建对象字面量时不允许 `key` 重复
'no-dupe-args': 2, // 函数参数不能重复
'no-duplicate-case': 2, // `switch` 中的 `case` 标签不能重复
'no-eval': 1, // 禁止使用 `eval`
'no-ex-assign': 2, // 禁止给 `catch` 语句中的异常参数赋值
'no-extend-native': 2, // 禁止扩展 `native` 对象
'no-extra-bind': 2, // 禁止不必要的函数绑定
'no-extra-boolean-cast': [
'error',
{
enforceForLogicalOperands: true,
},
], // 禁止不必要的 `bool` 转换
'no-extra-parens': 0, // 禁止非必要的括号
semi: ['error', 'never', { beforeStatementContinuationChars: 'always' }],
'no-fallthrough': 1, // 禁止 `switch` 穿透
'no-func-assign': 2, // 禁止重复的函数声明
'no-implicit-coercion': [
'error',
{
allow: ['!!', '~'],
},
], // 禁止隐式转换
'no-implied-eval': 2, // 禁止使用隐式 `eval`
'no-invalid-regexp': 2, // 禁止无效的正则表达式
'no-invalid-this': 2, // 禁止无效的 `this`
'no-irregular-whitespace': 2, // 禁止含有不合法空格
'no-iterator': 2, // 禁止使用 `__iterator__ ` 属性
'no-label-var': 2, // `label` 名不能与 `var` 声明的变量名相同
'no-labels': 2, // 禁止标签声明
'no-lone-blocks': 2, // 禁止不必要的嵌套块
'no-multi-spaces': 1, // 禁止使用多余的空格
'no-multiple-empty-lines': [1, { max: 2 }], // 空行最多不能超过 `2` 行
'no-new-func': 1, // 禁止使用 `new Function`
'no-new-object': 2, // 禁止使用 `new Object`
'no-new-require': 2, // 禁止使用 `new require`
'no-sparse-arrays': 2, // 禁止稀疏数组
'no-trailing-spaces': 1, // 一行结束后面不要有空格
'no-unreachable': 2, // 禁止有无法执行的代码
'no-unused-expressions': [
'error',
{
allowShortCircuit: true,
allowTernary: true,
allowTaggedTemplates: true,
enforceForJSX: true,
},
], // 禁止无用的表达式
'no-useless-call': 2, // 禁止不必要的 `call` 和 `apply`
'no-var': 'error', // 禁用 `var`
'no-with': 2, // 禁用 `with`
'no-undef': 0,
'use-isnan': 2, // 强制使用 isNaN 判断 NaN
'no-multi-assign': 2, // 禁止连续声明变量
'prefer-arrow-callback': 2, // 强制使用箭头函数作为回调
curly: ['error', 'all'],
'vue/multi-word-component-names': [
'error',
{
ignores: [],
},
],
'vue/no-use-v-if-with-v-for': [
'error',
{
allowUsingIterationVar: false,
},
],
'vue/require-v-for-key': ['error'],
'vue/require-valid-default-prop': ['error'],
'no-use-before-define': [
'error',
{
functions: true,
classes: true,
variables: false,
allowNamedExports: false,
},
],
'vue/component-definition-name-casing': ['error', 'PascalCase'],
'vue/html-closing-bracket-newline': [
'error',
{
singleline: 'never',
multiline: 'always',
},
],
'vue/v-on-event-hyphenation': ['error', 'never'],
'vue/component-tags-order': [
'error',
{
order: ['template', 'script', 'style'],
},
],
'vue/no-v-html': ['error'],
'vue/no-v-text': ['error'],
'vue/component-api-style': [
'error',
['script-setup', 'composition', 'composition-vue2'],
],
'vue/component-name-in-template-casing': [
'error',
'PascalCase',
{
registeredComponentsOnly: false,
},
],
'vue/no-unused-refs': ['error'],
'vue/prop-name-casing': ['error', 'camelCase'],
'vue/component-options-name-casing': ['error', 'PascalCase'],
'vue/attribute-hyphenation': [
'error',
'never',
{
ignore: [],
},
],
'vue/no-restricted-static-attribute': [
'error',
{
key: 'key',
message: 'Disallow using key as a custom attribute',
},
],
},
}

2
.evnrc Normal file
View File

@ -0,0 +1,2 @@
layout shell zsh
layout_fnm

14
.gitattributes vendored Normal file
View File

@ -0,0 +1,14 @@
# 将换行符设置为lf
* text eol=lf
# 将静态资源文件以二进制形式处理
*.png binary
*.jpg binary
*.jpeg binary
*.gif binary
*.svg binary
*.webp binary
*.mp4 binary
*.mov binary
*.avi binary
*.mp3 binary
*.wav binary

38
.github/workflows/docs-deploy.yaml vendored Normal file
View File

@ -0,0 +1,38 @@
name: ray-template documents deploy
on:
push:
pull_request:
types:
- closed
jobs:
build-and-deploy:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v3
- name: Install Node.js 22.x
uses: actions/setup-node@v3
with:
node-version: 22.x
- uses: pnpm/action-setup@v2
name: Install pnpm
with:
version: 9
run_install: false
- name: Install dependencies
run: pnpm install
- name: Pnpm build
run: pnpm build
- name: Deploy to dist branch
uses: JamesIves/github-pages-deploy-action@4.1.4
with:
branch: dist
folder: dist/production

47
.github/workflows/push-build.yaml vendored Normal file
View File

@ -0,0 +1,47 @@
on:
- push
- pull_request
jobs:
cache-and-install:
runs-on: ${{ matrix.os }}
strategy:
fail-fast: false
matrix:
node-version: [22.x]
os: [ubuntu-latest, windows-latest, macos-latest]
experimental: [true]
steps:
- name: Checkout
uses: actions/checkout@v3
- name: Install Node.js ${{ matrix.node-version }}
uses: actions/setup-node@v3
with:
node-version: ${{ matrix.node-version }}
- uses: pnpm/action-setup@v2
name: Install pnpm
with:
version: 9
run_install: false
- name: Get pnpm store directory
shell: bash
run: |
echo "STORE_PATH=$(pnpm store path --silent)" >> $GITHUB_ENV
- uses: actions/cache@v3
name: Setup pnpm cache
with:
path: ${{ env.STORE_PATH }}
key: ${{ runner.os }}-pnpm-store-${{ hashFiles('**/pnpm-lock.yaml') }}
restore-keys: |
${{ runner.os }}-pnpm-store-
- name: Install dependencies
run: pnpm install
- name: Pnpm build
run: pnpm build

5
.gitignore vendored
View File

@ -14,8 +14,8 @@ dist
dist/
*.local
visualizer.*
components.d.ts
auto-imports.d.ts
.eslintcache
.history
# Editor directories and files
.idea
@ -28,4 +28,3 @@ auto-imports.d.ts
yarn-*.*
yarn.*
pnpm.*
pnpm-lock.yaml

View File

@ -1,4 +1,4 @@
#!/usr/bin/env sh
. "$(dirname "$0")/_/husky.sh"
. "$(dirname -- "$0")/_/husky.sh"
yarn lint-staged --allow-empty "$1"
npx --no-install commitlint --edit "$1"

8
.husky/common.sh Normal file
View File

@ -0,0 +1,8 @@
#!/bin/sh
command_exists () {
command -v "$1" >/dev/null 2>&1
}
if command_exists winpty && test -t 1; then
exec < /dev/tty
fi

View File

@ -1,4 +1,7 @@
#!/usr/bin/env sh
. "$(dirname "$0")/_/husky.sh"
. "$(dirname -- "$0")/_/husky.sh"
. "$(dirname "$0")/common.sh"
yarn lint-staged --allow-empty "$1"
[ -n "$CI" ] && exit 0
pnpm lint-staged --allow-empty "$1"

View File

@ -1,9 +1,6 @@
node_modules/*
dist/*
yarn.lock
yarn-error.log
visualizer.html
pnpm-lock.yaml
.idea
auto-imports.d.ts
components.d.ts

5
.npmrc
View File

@ -1,7 +1,4 @@
package-lock=false
package-lock=true
prefer-offline=true
save-exact=true
engine-strict=true
engines={
"pnpm": ">=8.6.6"
}

1
.nvmrc Normal file
View File

@ -0,0 +1 @@
v22.12.0

View File

@ -8,4 +8,5 @@ yarn.*
.prettierrc.*
visualizer.*
visualizer.html
.env.*
.env.*
*-lock.yaml

View File

@ -8,7 +8,6 @@ module.exports = {
jsxSingleQuote: false, // `jsx` 不使用单引号, 而使用双引号
trailingComma: 'all', // 尾随逗号
bracketSpacing: true, // 大括号内的首尾需要空格
jsxBracketSameLine: false, // `jsx` 标签的反尖括号需要换行
arrowParens: 'always', // 箭头函数, 只有一个参数的时候, 也需要括号
rangeStart: 0, // 每个文件格式化的范围是文件的全部内容
rangeEnd: Infinity,
@ -16,5 +15,6 @@ module.exports = {
insertPragma: false, // 不需要自动在文件开头插入 `@prettier`
proseWrap: 'preserve', // 使用默认的折行标准
htmlWhitespaceSensitivity: 'css', // 根据显示样式决定 `html` 要不要折行
endOfLine: 'lf', // 换行符使用 `lf`
endOfLine: 'lf', // 换行符使用 `lf`,
singleAttributePerLine: false,
}

42
.vscode/settings.json vendored
View File

@ -1,4 +1,5 @@
{
"editor.formatOnSave": true,
"i18n-ally.localesPaths": ["src/locales/lang"],
"i18n-ally.keystyle": "nested",
"i18n-ally.sortKeys": true,
@ -8,5 +9,44 @@
"i18n-ally.sourceLanguage": "zh-CN",
"i18n-ally.displayLanguage": "zh-CN",
"i18n-ally.enabledFrameworks": ["vue", "react"],
"typescript.tsdk": "node_modules/typescript/lib"
"typescript.tsdk": "node_modules/typescript/lib",
"alias-skip.mappings": {
"@": "/src",
"@use-utils": "/src/utils",
"@use-api": "/src/axios/api",
"@use-images": "/src/assets/images",
"@mock": "/mock"
},
"alias-skip.allowedsuffix": ["ts", "tsx"],
"alias-skip.rootpath": "package.json",
"cSpell.words": [
"baomitu",
"bezier",
"Cascader",
"Clickoutside",
"codabar",
"commitmsg",
"crossorigin",
"datetimerange",
"domtoimage",
"EDITMSG",
"iife",
"internalkey",
"jsbarcode",
"linebreak",
"logicflow",
"macarons",
"menutag",
"ndata",
"persistedstate",
"pharmacode",
"Popselect",
"precommit",
"siderbar",
"snapline",
"stylelint",
"unocss",
"WUJIE",
"zlevel"
]
}

File diff suppressed because it is too large Load Diff

221
CODE_OF_CONDUCT.md Normal file
View File

@ -0,0 +1,221 @@
# Contributor Covenant Code of Conduct
## Our Pledge
We as members, contributors, and leaders pledge to make participation in our
community a harassment-free experience for everyone, regardless of age, body
size, visible or invisible disability, ethnicity, sex characteristics, gender
identity and expression, level of experience, education, socio-economic status,
nationality, personal appearance, race, caste, color, religion, or sexual
identity and orientation.
We pledge to act and interact in ways that contribute to an open, welcoming,
diverse, inclusive, and healthy community.
## Our Standards
Examples of behavior that contributes to a positive environment for our
community include:
* Demonstrating empathy and kindness toward other people
* Being respectful of differing opinions, viewpoints, and experiences
* Giving and gracefully accepting constructive feedback
* Accepting responsibility and apologizing to those affected by our mistakes,
and learning from the experience
* Focusing on what is best not just for us as individuals, but for the overall
community
Examples of unacceptable behavior include:
* The use of sexualized language or imagery, and sexual attention or advances of
any kind
* Trolling, insulting or derogatory comments, and personal or political attacks
* Public or private harassment
* Publishing others' private information, such as a physical or email address,
without their explicit permission
* Other conduct which could reasonably be considered inappropriate in a
professional setting
## Enforcement Responsibilities
Community leaders are responsible for clarifying and enforcing our standards of
acceptable behavior and will take appropriate and fair corrective action in
response to any behavior that they deem inappropriate, threatening, offensive,
or harmful.
Community leaders have the right and responsibility to remove, edit, or reject
comments, commits, code, wiki edits, issues, and other contributions that are
not aligned to this Code of Conduct, and will communicate reasons for moderation
decisions when appropriate.
## Scope
This Code of Conduct applies within all community spaces, and also applies when
an individual is officially representing the community in public spaces.
Examples of representing our community include using an official email address,
posting via an official social media account, or acting as an appointed
representative at an online or offline event.
## Enforcement
Instances of abusive, harassing, or otherwise unacceptable behavior may be
reported to the community leaders responsible for enforcement at
<443547225@qq.com>.
All complaints will be reviewed and investigated promptly and fairly.
All community leaders are obligated to respect the privacy and security of the
reporter of any incident.
## Enforcement Guidelines
Community leaders will follow these Community Impact Guidelines in determining
the consequences for any action they deem in violation of this Code of Conduct:
### 1. Correction
**Community Impact**: Use of inappropriate language or other behavior deemed
unprofessional or unwelcome in the community.
**Consequence**: A private, written warning from community leaders, providing
clarity around the nature of the violation and an explanation of why the
behavior was inappropriate. A public apology may be requested.
### 2. Warning
**Community Impact**: A violation through a single incident or series of
actions.
**Consequence**: A warning with consequences for continued behavior. No
interaction with the people involved, including unsolicited interaction with
those enforcing the Code of Conduct, for a specified period of time. This
includes avoiding interactions in community spaces as well as external channels
like social media. Violating these terms may lead to a temporary or permanent
ban.
### 3. Temporary Ban
**Community Impact**: A serious violation of community standards, including
sustained inappropriate behavior.
**Consequence**: A temporary ban from any sort of interaction or public
communication with the community for a specified period of time. No public or
private interaction with the people involved, including unsolicited interaction
with those enforcing the Code of Conduct, is allowed during this period.
Violating these terms may lead to a permanent ban.
### 4. Permanent Ban
**Community Impact**: Demonstrating a pattern of violation of community
standards, including sustained inappropriate behavior, harassment of an
individual, or aggression toward or disparagement of classes of individuals.
**Consequence**: A permanent ban from any sort of public interaction within the
community.
## Attribution
This Code of Conduct is adapted from the [Contributor Covenant][homepage],
version 2.1, available at
[https://www.contributor-covenant.org/version/2/1/code_of_conduct.html][v2.1].
Community Impact Guidelines were inspired by
[Mozilla's code of conduct enforcement ladder][Mozilla CoC].
For answers to common questions about this code of conduct, see the FAQ at
[https://www.contributor-covenant.org/faq][FAQ]. Translations are available at
[https://www.contributor-covenant.org/translations][translations].
[homepage]: https://www.contributor-covenant.org
[v2.1]: https://www.contributor-covenant.org/version/2/1/code_of_conduct.html
[Mozilla CoC]: https://github.com/mozilla/diversity
[FAQ]: https://www.contributor-covenant.org/faq
[translations]: https://www.contributor-covenant.org/translations
# 贡献者公约
## 我们的承诺
身为社区成员、贡献者和领袖,我们承诺使社区参与者不受骚扰,无论其年龄、体型、可见或不可见的缺陷、族裔、性征、性别认同和表达、经验水平、教育程度、社会与经济地位、国籍、相貌、种族、种姓、肤色、宗教信仰、性倾向或性取向如何。
我们承诺以有助于建立开放、友善、多样化、包容、健康社区的方式行事和互动。
## 我们的准则
有助于为我们的社区创造积极环境的行为例子包括但不限于:
* 表现出对他人的同情和善意
* 尊重不同的主张、观点和感受
* 提出和大方接受建设性意见
* 承担责任并向受我们错误影响的人道歉
* 注重社区共同诉求,而非个人得失
不当行为例子包括:
* 使用情色化的语言或图像,及性引诱或挑逗
* 嘲弄、侮辱或诋毁性评论,以及人身或政治攻击
* 公开或私下的骚扰行为
* 未经他人明确许可,公布他人的私人信息,如物理或电子邮件地址
* 其他有理由认定为违反职业操守的不当行为
## 责任和权力
社区领袖有责任解释和落实我们所认可的行为准则,并妥善公正地对他们认为不当、威胁、冒犯或有害的任何行为采取纠正措施。
社区领导有权力和责任删除、编辑或拒绝或拒绝与本行为准则不相符的评论comment、提交commits、代码、维基wiki编辑、议题issues或其他贡献并在适当时机知采取措施的理由。
## 适用范围
本行为准则适用于所有社区场合,也适用于在公共场所代表社区时的个人。
代表社区的情形包括使用官方电子邮件地址、通过官方社交媒体帐户发帖或在线上或线下活动中担任指定代表。
## 监督
辱骂、骚扰或其他不可接受的行为可通过 <443547225@qq.com> 向负责监督的社区领袖报告。
所有投诉都将得到及时和公平的审查和调查。
所有社区领袖都有义务尊重任何事件报告者的隐私和安全。
## 处理方针
社区领袖将遵循下列社区处理方针来明确他们所认定违反本行为准则的行为的处理方式:
### 1. 纠正
**社区影响**:使用不恰当的语言或其他在社区中被认定为不符合职业道德或不受欢迎的行为。
**处理意见**:由社区领袖发出非公开的书面警告,明确说明违规行为的性质,并解释举止如何不妥。或将要求公开道歉。
### 2. 警告
**社区影响**:单个或一系列违规行为。
**处理意见**:警告并对连续性行为进行处理。在指定时间内,不得与相关人员互动,包括主动与行为准则执行者互动。这包括避免在社区场所和外部渠道中的互动。违反这些条款可能会导致临时或永久封禁。
### 3. 临时封禁
**社区影响**: 严重违反社区准则,包括持续的不当行为。
**处理意见**: 在指定时间内,暂时禁止与社区进行任何形式的互动或公开交流。在此期间,不得与相关人员进行公开或私下互动,包括主动与行为准则执行者互动。违反这些条款可能会导致永久封禁。
### 4. 永久封禁
**社区影响**:行为模式表现出违反社区准则,包括持续的不当行为、骚扰个人或攻击或贬低某个类别的个体。
**处理意见**:永久禁止在社区内进行任何形式的公开互动。
## 参见
本行为准则改编自 [Contributor Covenant][homepage] 2.1 版, 参见 [https://www.contributor-covenant.org/version/2/1/code_of_conduct.html][v2.1]。
社区处理方针灵感来源于 [Mozilla's code of conduct enforcement ladder][Mozilla CoC]。
有关本行为准则的常见问题的答案,参见 [https://www.contributor-covenant.org/faq][FAQ]。
其他语言翻译参见 [https://www.contributor-covenant.org/translations][translations]。
[homepage]: https://www.contributor-covenant.org
[v2.1]: https://www.contributor-covenant.org/version/2/1/code_of_conduct.html
[Mozilla CoC]: https://github.com/mozilla/diversity
[FAQ]: https://www.contributor-covenant.org/faq
[translations]: https://www.contributor-covenant.org/translations

View File

@ -1,21 +0,0 @@
## 常见问题
### 路由
#### 缓存失效
> 如果出现缓存配置不生效的情况可以按照如下方法进行排查
- 查看 APP_KEEP_ALIVE setupKeepAlive 属性是否配置为 true
- 查看每个组件的 `name` 是否唯一,[`KeepAlive`](https://cn.vuejs.org/guide/built-ins/keep-alive.html) 组件重度依赖组件 `name` 作为唯一标识。详情可以查看官方文档
- 查看该页面的路由配置是否正确,比如:`path` 是否按照模板约定方式进行配置
#### 自动导入失败
> 模板采用自动导入路由模块方式。如果发现路由导入有误、或者导入报错,请查看文件命名是否有误。
### 国际化
#### 国际化切换错误、警告
> 模板二次封装 [`useI18n`](https://github.com/XiaoDaiGua-Ray/ray-template/blob/main/src/locales/useI18n.ts) 方法,首选该方法作为国际化语言切换方法。

11
Dockerfile Normal file
View File

@ -0,0 +1,11 @@
FROM debian:11
COPY . /app
WORKDIR /app
RUN apt-get update
RUN apt-get install -y wget curl make sudo unzip
RUN curl -fsSL https://deb.nodesource.com/setup_18.x | sudo -E bash -
RUN apt-get install -y nodejs
RUN npm i -g pnpm
RUN pnpm install
EXPOSE 9527
CMD [ "pnpm", "dev" ]

21
LICENSE Normal file
View File

@ -0,0 +1,21 @@
MIT License
Copyright (c) 2020-present, Ray
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

161
MANUAL.md
View File

@ -1,161 +0,0 @@
## Ray Template 使用手册
## 前言
> `Ray Template` 默认使用 `pnpm` 作为包管理器,并且默认启用严格模式的 `eslint`。在导入模块的时候除 `.ts` `.tsx` `.d.ts` 文件等不需要手动补全后缀名,其余的模块导入应该手动补全所有后缀名。
### 使用
#### 依赖安装
```sh
# yarn
yarn
# npm
npm i
```
#### 启动项目
```sh
# yarn
yarn dev
# npm
npm run dev
```
#### 构建项目
```sh
# yarn
yarn build
# npm
npm run build
```
### 国际化
#### Tip
- 每个新的语言包文件的文件名视为 path 开头menu.json => menu.xxx
#### 新增语言包、新增语言模块
> 项目国际化管理模块,统一放置于 `src/locales` 下。
##### 文件包
- lang
- helper.ts
- index.ts
- useI18n.ts
> 项目中使用 t locale 方法时使用模板提供方法useI18n.ts
```tsx
// 引入包
import { useI18n } from '@/locales/useI18n'
const { t, locale } = useI18n()
/**
*
* t: 用于绑定国际化 path
* locale: 用于切换系统语言。参数 key 必须与 lang 包中的语言包一致
*/
const demo = () => <span>{t('demo.demo')}</span>
locale('zh-CN')
locale('en-US')
```
##### 新增语言包
> 我们举例新增台湾地区语言包。
- `src/locales/lang` 文件下创建对应新语言包文件夹与语言文件
- zh-TW 文件夹
- zh-TW.ts 文件
- 创建与原有语言格式一样的文件夹(可以直接 cv 过去)
- 配置语言下拉项LOCAL_OPTIONS
- 配置 dayjs 国际化映射DAYJS_LOCAL_MAP
> 具体注意事项看注释。
##### 最后
> 按照上述步骤操作后,就已经给模板添加了一个新的语言包了。可以在页面的右上角国际化下拉框中看到新增的下拉选项,点击切换后,即可切换到对应的语言了。
### 路由
#### Tip
- 在该模板中,路由 path 属性视 `/` 开头的路由为根路由
- 路由会在初始化的时候将所有路由进行提升(全部提升为顶级路由),所以注意第一条 Tip
- 路由模块会影响菜单的输出显示(菜单模块与路由模块进行拆分开来的)
- 具体路由支持配置属性看 router/README.md 文件
#### 文件包
- constant 文件放置一些公共东西
- helper 文件放置 router 的一些 hook 方法
- modules 页面路由入口modules 文件中每一个 xxx.ts 文件都会被视为是一个路由模块)
- utils router 拓展方法
- ...
##### 新增路由页面
- modules 中添加一个新的模块log.ts
- 配置路由的相关信息
- views 中创建 log 相关的页面信息
```ts
// 辅助函数,配合 i18n-ally 插件使用
import { t } from '@/locales/useI18n'
// 路由配置类型提示
import type { AppRouteRecordRaw } from '@/router/type'
const log: AppRouteRecordRaw = {
path: '/log',
name: 'Log',
component: () => import('@/views/log/index.vue'),
meta: {
i18nKey: t('menu.Log'),
icon: 'log',
order: 3,
},
children: [
{
path: 'my-log',
name: 'MyLog',
component: () => import('@/views/my-log/index.vue'),
meta: {
i18nKey: t('menu.MyLog'),
order: 0,
},
},
{
path: 'group-log',
name: 'MyLog',
component: () => import('@/views/group-log/index.vue'),
meta: {
i18nKey: t('menu.GroupLog'),
order: 0,
},
},
],
}
export default log
```
##### 最后
> 打开浏览器可以看到页面菜单上已经有一个日志菜单。
#### 未完待续。。。后续慢慢更新该手册

22
README.commit.md Normal file
View File

@ -0,0 +1,22 @@
# commit 规范
## commit message 格式
在提交代码时,`commit message` 遵循以下格式:
- feat: 新功能`feature`
- fix: 修补 `bug`
- update: 更新代码
- docs: 文档documentation
- style: 格式(不影响代码运行的变动)
- refactor: 重构即不是新增功能也不是修改bug的代码变动
- test: 增加测试
- chore: 构建过程或辅助工具的变动
- revert: 撤销
- merge: 合并分支
- perf: 优化相关,比如提升性能、体验
- build: 构建
- plugin: 插件更新
- publish: 发布
当你需要定制化自己的`commit message`格式时,可以在`commitlint.config.cjs`文件中进行配置。

276
README.md
View File

@ -1,232 +1,154 @@
# `Ray Template`
<div align="center">
<a href="https://github.com/XiaoDaiGua-Ray/ray-template">
<img
alt="Ray Template"
width="200"
height="200"
src="https://avatars.githubusercontent.com/u/51957438?v=4"
/>
</a>
<br />
<br />
<a href="https://nodejs.org/en/about/previous-releases"><img src="https://img.shields.io/node/v/vite.svg" alt="node compatibility"></a>
<a href="https://github.com/XiaoDaiGua-Ray/ray-template/blob/main/LICENSE"
><img
src="https://img.shields.io/github/license/XiaoDaiGua-Ray/ray-template"
alt="LICENSE"
/></a>
<a href="#badge"><img src="https://img.shields.io/github/languages/top/XiaoDaiGua-Ray/ray-template" alt="language"></a>
<a href="https://www.npmjs.com/package/ray-template"><img src="https://img.shields.io/npm/v/ray-template" alt="npm package"></a>
</div>
<!-- ALL-CONTRIBUTORS-BADGE:START - Do not remove or modify this section -->
<div align="center">
[![All Contributors](https://img.shields.io/badge/all_contributors-1-orange.svg?style=flat-square)](#contributors-)
# Ray Template
<!-- ALL-CONTRIBUTORS-BADGE:END -->
English | [简体中文](https://github.com/XiaoDaiGua-Ray/ray-template/blob/main/README.zh-CN.md)
## 感谢
A `completely free`, `efficient`, `feature complete` and based on vite5. x & ts(x) & pinia & vue3. x and other latest technology in the background template.
> 感谢 <https://me.yka.moe/> 对于本人的支持。
</div>
## 预览地址
## 🌻 Intro
- [点击预览](https://xiaodaigua-ray.github.io/ray-template/#/)
- [点击预览(加速地址)](https://ray-template.yunkuangao.com/#/)
`Ray Template` uses cutting-edge front-end technology, abandoning complexity and bloat, using modular design, decoupling data, methods and views, focusing on business development. Provide rich configuration and rich template `Hooks`, support personalized customization, to meet your project needs.
## 文档地址
## ✨ Features
- [文档](https://xiaodaigua-ray.github.io/ray-template-doc/)
- [文档(加速地址)](https://ray-template.yunkuangao.com/ray-template-doc/)
- `New technology stack:` using ts(x), vite5. x, vue3. x, pinia and other front-end cutting-edge technology development
- `Theme:` configurable theme
- `Internationalization:` built-in perfect internationalization solution
- `Permissions:` built-in perfect dynamic routing permission generation solution
- `Components:` secondary encapsulation of multiple common components
- `Toolkit:` common tool function packaging
- `Cache:` arbitrary depth page caching
- `Modular design:` decoupling management data, methods, views, rest assured secondary development
- `Configurable:` support rich configuration items
- `Code style:` built-in prettier, eslint and other code style tools
- `Multi-terminal adaptation:` support pc, phone, pad
- `Documentation:` complete documentation
- `Mock data:` built-in Mock data solution
- `Axios request:` the plug-in design is used to encapsulate the axios library interceptor twice, which makes the interceptor more flexible
- `SVG:` built-in svg icon solution
- `Hooks:` based on the template characteristics of the encapsulated hooks to make it easier to use some functions of the template
- `TypeScript:` provide a complete type
- `Vitest:` built-in vitest test solution
## 更新日志
## 👀 Preview
- [日志](https://github.com/XiaoDaiGua-Ray/xiaodaigua-ray.github.io/blob/main/CHANGELOG.md)
- [Preview](https://xiaodaigua-ray.github.io/ray-template/#/)
## 常见问题
## 📌 Documentation
- [常见问题](https://github.com/XiaoDaiGua-Ray/ray-template/blob/main/COMMONPROBLEM.md)
- [Documentation](https://xiaodaigua-ray.github.io/ray-template-doc/)
## 功能
## 🔋 Change Log
- 主题切换
- 任意深度页面缓存
- 系统配置化
- 锁屏
- 自动化路由
- 带有拓展功能的表格
- 封装 `axios` 自动取消重复请求,暴露拦截器注册器
- 全局菜单搜索
- 动态菜单(多级菜单)
- 主题色切换
- 错误页
- 面包屑
- 标签页
- 国际化(允许按模块管理语言包)
- 权限路由
- 动态切换主题、贴花的 `EChart`
- 最佳构建体验
- 体积分析
- 还有一些不值一提的小东西...
- [Change Log](https://github.com/XiaoDaiGua-Ray/xiaodaigua-ray.github.io/blob/main/CHANGELOG.md)
## 未来
## 🪴 Prepare
> 根据个人时间空余情况,会不定时对该模板进行更新和迭代。希望将该工具的功能不断补全(虽然现在已经是足够日常开发和使用),将该模板打造为一个更加健全的中后台模板。如果你有好的想法和建议,可以直接联系我或者直接提 `issues` 即可。
- [Node](http://nodejs.org/) and [git](https://git-scm.com/) - project development environment
- [Vite](https://vitejs.dev/) - familiar with vite features
- [Vue3](https://v3.vuejs.org/) - familiar with Vue basic syntax
- [TypeScript](https://www.typescriptlang.org/) - familiar with TypeScript basic syntax
- [ES6+](http://es6.ruanyifeng.com/) - familiar with es6 basic syntax
- [Vue-Hooks-Plus](https://inhiblabcore.github.io/docs/hooks/) - familiar with vue-hooks-plus useRequest method basic use
- [Vue-Router-Next](https://next.router.vuejs.org/) - familiar with vue-router4.x basic use
- [Naive-UI](https://www.naiveui.com) - naive ui basic use
- [Mock.js](https://github.com/nuysoft/Mock) - mockjs basic syntax
- [Pinia](https://pinia.vuejs.org/zh/introduction.html) - state manager pinia usage
- [TSX](https://github.com/vuejs/babel-plugin-jsx/blob/main/packages/babel-plugin-jsx/README-zh_CN.md) - tsx basic syntax
- [Vitest](https://cn.vitest.dev/guide/) - vitest basic use
## 前言
## 📦 Setup
> 该项目模板采用 `vue3.x` `vite4.x` `pinia` `tsx` 进行开发。
> 使用 `naive ui` 作为组件库。
> 预设了最佳构建体验的配置与常用搬砖工具。意在提供一个简洁、快速上手的模板。
> 该模板不支持移动端设备。
## 提示
> 项目默认启用严格模式 `eslint`,但是由于 `vite-plugin-eslint` 插件优先级最高,所以如果出现自动导入类型错误提示,请优先解决其他问题。
> 建议开启 `vscode` 保存自动修复功能。
## 版本说明
> 做了一些大的改动升级,让模板更加好用了一点,默认主题色也做了变更更好看了一点。啰嗦两句,好像也没啥其他的了...
## 拉取依赖
### Get Project
```sh
# yarn
yarn
# github
git clone https://github.com/XiaoDaiGua-Ray/ray-template.git
```
### Pull dependencies
```sh
# npm
npm install
pnpm i
```
## 启动项目
### Test project
```sh
# yarn
yarn dev
pnpm test
```
### Startup project
```sh
# npm
npm run dev
pnpm dev
```
## 项目打包
### Build project
```sh
# yarn
yarn build
pnpm build
```
### Preview project
```sh
# npm
npm run build
pnpm preview
```
## 预览项目
### Report project
```sh
# yarn
yarn preview
pnpm report
```
```sh
# npm
### Development
npm run preview
```
Just delete the files under `views/demo`, `router/modules/demo` to get a clean project template.
## 体积分析
## 🪴 Project Activities
```sh
# yarn
![Alt](https://repobeats.axiom.co/api/embed/fab6071297ab281913a42f07a2779b488cfd62b8.svg 'Repobeats analytics image')
yarn report
```
### Contributors
```sh
# npm
Thanks for all their contributions 🐝 !
npm run report
```
<a href="https://github.com/XiaoDaiGua-Ray/ray-template/graphs/contributors">
<img src="https://contrib.rocks/image?repo=XiaoDaiGua-Ray/ray-template" />
</a>
## 项目依赖
## Browser Support
- [pinia](https://pinia.vuejs.org/) `全局状态管理器`
- [@vueuse](https://vueuse.org/) `vue3 hooks`
- [vue-router](https://router.vuejs.org/zh/) `router`
- [axios](http://axios-js.com/zh-cn/docs/index.html) `ajax request`
- [vue-i18n](https://kazupon.github.io/vue-i18n/zh/introduction.html) `国际化`
- [scrollreveal.js](https://scrollrevealjs.org/) `滚动加载动画`(暂时移除)
- [crypto-js](https://github.com/brix/crypto-js) `加密`
- [vite-svg-loader](https://github.com/jpkleemans/vite-svg-loader) `svg组件化`
- [vite-plugin-svg-icons](https://github.com/vbenjs/vite-plugin-svg-icons/blob/main/README.zh_CN.md) `svg雪碧图`
- [echarts5](https://echarts.apache.org/examples/zh/index.html#chart-type-line) `可视化`
- [lodash-es](https://www.lodashjs.com/) `拓展方法`
- 还有一些后续补充的,懒得写了。。。自己看项目依赖页面
| [<img src="https://raw.githubusercontent.com/alrra/browser-logos/master/src/edge/edge_48x48.png" alt=" Edge" width="24px" height="24px" />](http://godban.github.io/browsers-support-badges/)</br>IE | [<img src="https://raw.githubusercontent.com/alrra/browser-logos/master/src/edge/edge_48x48.png" alt=" Edge" width="24px" height="24px" />](http://godban.github.io/browsers-support-badges/)</br>Edge | [<img src="https://raw.githubusercontent.com/alrra/browser-logos/master/src/firefox/firefox_48x48.png" alt="Firefox" width="24px" height="24px" />](http://godban.github.io/browsers-support-badges/)</br>Firefox | [<img src="https://raw.githubusercontent.com/alrra/browser-logos/master/src/chrome/chrome_48x48.png" alt="Chrome" width="24px" height="24px" />](http://godban.github.io/browsers-support-badges/)</br>Chrome | [<img src="https://raw.githubusercontent.com/alrra/browser-logos/master/src/safari/safari_48x48.png" alt="Safari" width="24px" height="24px" />](http://godban.github.io/browsers-support-badges/)</br>Safari |
| :--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: |
| not support | last 2 versions | last 2 versions | last 2 versions | last 2 versions |
## 基础组件
## 📄 License
- `RayIcon` `svg icon`
- `RayChart` 基于 `echarts5.x` 封装可视化组件
- `RayTransitionComponent` 带过渡动画路由组件,效果与 `RouterView` 相同
- `RayTable` 基于 `Naive UI DataTable` 组件封装,实现了一些小功能
- `RayCollapseGrid` 基于 `Naive UI NGrid` 组件封装的可折叠操作栏
## 项目结构
```
- locales: 国际化多语言入口(本项目采用 json 格式)
- assets: 项目静态资源入口
- component: 全局共用组件
- icons: 项目svg图标资源需要配合 RayIcon 组件使用
- language: 国际化
- layout: 全局页面结构入口
- router: 路由表
- store: 全局状态管理入口
- styles: 全局公共样式入口
- types: 全局 type
- utils: 工具包
- views: 页面入口
- vite-plugin: 插件注册
```
## 浏览器支持
> 仅支持现代浏览器,不支持 `IE`
## 最后,希望大家搬砖愉快
## 贡献者
<!-- ALL-CONTRIBUTORS-LIST:START - Do not remove or modify this section -->
<!-- prettier-ignore-start -->
<!-- markdownlint-disable -->
<table>
<tbody>
<tr>
<td align="center" valign="top" width="14.28%"><a href="https://heartofyun.com"><img src="https://avatars.githubusercontent.com/u/40163747?v=4?s=100" width="100px;" alt="Cloud"/><br /><sub><b>Cloud</b></sub></a><br /><a href="#tool-yunkuangao" title="Tools">🔧</a></td>
</tr>
</tbody>
</table>
<!-- markdownlint-restore -->
<!-- prettier-ignore-end -->
<!-- ALL-CONTRIBUTORS-LIST:END -->
## Contributors ✨
Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/docs/en/emoji-key)):
<!-- ALL-CONTRIBUTORS-LIST:START - Do not remove or modify this section -->
<!-- prettier-ignore-start -->
<!-- markdownlint-disable -->
<!-- markdownlint-restore -->
<!-- prettier-ignore-end -->
<!-- ALL-CONTRIBUTORS-LIST:END -->
This project follows the [all-contributors](https://github.com/all-contributors/all-contributors) specification. Contributions of any kind welcome!
[MIT License](https://github.com/XiaoDaiGua-Ray/ray-template/blob/main/LICENSE) © 2022-PRESENT [Ray](https://github.com/XiaoDaiGua-Ray)

154
README.zh-CN.md Normal file
View File

@ -0,0 +1,154 @@
<div align="center">
<a href="https://github.com/XiaoDaiGua-Ray/ray-template">
<img
alt="Ray Template"
width="200"
height="200"
src="https://avatars.githubusercontent.com/u/51957438?v=4"
/>
</a>
<br />
<br />
<a href="https://nodejs.org/en/about/previous-releases"><img src="https://img.shields.io/node/v/vite.svg" alt="node compatibility"></a>
<a href="https://github.com/XiaoDaiGua-Ray/ray-template/blob/main/LICENSE"
><img
src="https://img.shields.io/github/license/XiaoDaiGua-Ray/ray-template"
alt="LICENSE"
/></a>
<a href="#badge"><img src="https://img.shields.io/github/languages/top/XiaoDaiGua-Ray/ray-template" alt="language"></a>
<a href="https://www.npmjs.com/package/ray-template"><img src="https://img.shields.io/npm/v/ray-template" alt="npm package"></a>
</div>
<div align="center">
# Ray Template
简体中文 | [English](https://github.com/XiaoDaiGua-Ray/ray-template/blob/main/README.md)
一个 `完全免费``高效``特性完整` 并且基于 vite5.x & ts(x) & pinia & vue3.x 等最新技术的中后台模板。
</div>
## 🌻 简介
`Ray Template`采用前沿前端技术,摒弃繁杂与臃肿,采用模块化设计,解耦数据、方法和视图,专注业务开发。提供丰富配置和丰富的模板 `Hooks`,支持个性化定制,满足你的项目需求。
## ✨ 特性
- `全新技术栈:`使用 ts(x), vite5.x, vue3.x, pinia 等前端前沿技术开发
- `主题:`可配置的主题
- `国际化:`内置完善的国际化方案
- `权限:`内置完善的动态路由权限生成方案
- `组件:`二次封装了多个常用的组件
- `工具包:`常用的工具函数封装
- `缓存:`任意深度页面缓存
- `模块化设计:`解耦管理的数据、方法、视图,放心二次开发
- `配置化:`支持丰富的配置项
- `代码风格:`内置 prettier, eslint 等代码风格工具
- `多端适配:`支持 pc, phone, pad
- `文档:`完善的文档
- `Mock 数据:`内置 Mock 数据方案
- `Axios 请求:`采用插件式设计二次封装 axios 库拦截器,让拦截器更加灵活
- `SVG`内置 svg icon 解决方案
- `Hooks`基于模板特性封装的 hooks 让你更加方便的使用模板一些功能
- `TypeScript`提供完整的类型
- `Vitest`内置 vitest 测试方案
## 👀 预览地址
- [点击预览](https://xiaodaigua-ray.github.io/ray-template/#/)
## 📌 文档地址
- [文档](https://xiaodaigua-ray.github.io/ray-template-doc/)
## 🔋 更新日志
- [更新日志](https://github.com/XiaoDaiGua-Ray/xiaodaigua-ray.github.io/blob/main/CHANGELOG.md)
## 🪴 准备
- [Node](http://nodejs.org/) 和 [git](https://git-scm.com/) - 项目开发环境
- [Vite](https://vitejs.dev/) - 熟悉 vite 特性
- [Vue3](https://v3.vuejs.org/) - 熟悉 Vue 基础语法
- [TypeScript](https://www.typescriptlang.org/) - 熟悉 TypeScript 基本语法
- [ES6+](http://es6.ruanyifeng.com/) - 熟悉 es6 基本语法
- [Vue-Hooks-Plus](https://inhiblabcore.github.io/docs/hooks/) - 熟悉 vue-hooks-plus useRequest 方法的基本使用
- [Vue-Router-Next](https://next.router.vuejs.org/) - 熟悉 vue-router4.x 基本使用
- [Naive-UI](https://www.naiveui.com) - naive ui 基本使用
- [Mock.js](https://github.com/nuysoft/Mock) - mockjs 基本语法
- [Pinia](https://pinia.vuejs.org/zh/introduction.html) - 状态管理器 pinia 使用
- [TSX](https://github.com/vuejs/babel-plugin-jsx/blob/main/packages/babel-plugin-jsx/README-zh_CN.md) - tsx 基本语法
- [Vitest](https://cn.vitest.dev/guide/) - vitest 基本使用
## 📦 起步
### 获取项目
```sh
# github
git clone https://github.com/XiaoDaiGua-Ray/ray-template.git
```
### 拉取依赖
```sh
pnpm i
```
### 测试项目
```sh
pnpm test
```
### 启动项目
```sh
pnpm dev
```
### 项目打包
```sh
pnpm build
```
### 预览项目
```sh
pnpm preview
```
### 体积分析
```sh
pnpm report
```
### 快速开发
只需要删除 `views/demo`, `router/modules/demo` 下的文件即可得到一个干净的项目模板。
## 🪴 项目活动
![Alt](https://repobeats.axiom.co/api/embed/fab6071297ab281913a42f07a2779b488cfd62b8.svg 'Repobeats analytics image')
### 贡献者
感谢他们的所做的一切贡献 🐝
<a href="https://github.com/XiaoDaiGua-Ray/ray-template/graphs/contributors">
<img src="https://contrib.rocks/image?repo=XiaoDaiGua-Ray/ray-template" />
</a>
## 浏览器支持
| [<img src="https://raw.githubusercontent.com/alrra/browser-logos/master/src/edge/edge_48x48.png" alt=" Edge" width="24px" height="24px" />](http://godban.github.io/browsers-support-badges/)</br>IE | [<img src="https://raw.githubusercontent.com/alrra/browser-logos/master/src/edge/edge_48x48.png" alt=" Edge" width="24px" height="24px" />](http://godban.github.io/browsers-support-badges/)</br>Edge | [<img src="https://raw.githubusercontent.com/alrra/browser-logos/master/src/firefox/firefox_48x48.png" alt="Firefox" width="24px" height="24px" />](http://godban.github.io/browsers-support-badges/)</br>Firefox | [<img src="https://raw.githubusercontent.com/alrra/browser-logos/master/src/chrome/chrome_48x48.png" alt="Chrome" width="24px" height="24px" />](http://godban.github.io/browsers-support-badges/)</br>Chrome | [<img src="https://raw.githubusercontent.com/alrra/browser-logos/master/src/safari/safari_48x48.png" alt="Safari" width="24px" height="24px" />](http://godban.github.io/browsers-support-badges/)</br>Safari |
| :--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: |
| not support | last 2 versions | last 2 versions | last 2 versions | last 2 versions |
## 📄 证书
[MIT License](https://github.com/XiaoDaiGua-Ray/ray-template/blob/main/LICENSE) © 2022-PRESENT [Ray](https://github.com/XiaoDaiGua-Ray)

View File

@ -0,0 +1,16 @@
import { prefixCacheKey } from '../../src/utils/app/prefix-cache-key'
describe('prefixCacheKey', () => {
it('should return the key with the default prefix', () => {
const key = 'signing'
expect(prefixCacheKey(key)).toBe(key)
})
it('should return the key with the custom prefix', () => {
const key = 'signing'
const customPrefix = 'ray-'
expect(prefixCacheKey(key, { customPrefix })).toBe(customPrefix + key)
})
})

View File

@ -0,0 +1,18 @@
import { arrayBufferToBase64Image } from '../../src/utils/basic'
describe('arrayBufferToBase64Image', () => {
const arrayBuffer = new ArrayBuffer(8)
const base64ImagePrefix = 'data:image/png;base64,'
it('should convert array buffer to base64 image', () => {
const base64Image = arrayBufferToBase64Image(arrayBuffer)
expect(base64Image).toBe(`${base64ImagePrefix}AAAAAAAAAAA=`)
})
it('should convert array buffer to base64 image with prefix', () => {
const base64Image = arrayBufferToBase64Image(arrayBuffer)
expect(base64Image.startsWith(base64ImagePrefix)).toBe(true)
})
})

View File

@ -0,0 +1,27 @@
import { callWithAsyncErrorHandling } from '../../src/utils/basic'
describe('callWithAsyncErrorHandling', () => {
it('should call the function and return the result', () => {
const fn = (x: number) => x
const callbackFn = () => {}
expect(callWithAsyncErrorHandling(fn, callbackFn, [1])).resolves.toBe(1)
})
it('should call the callback function when the function throws an error', () => {
let callbackFnExecuted = 1
const fn = () => {
throw new Error('test error')
}
const callbackFn = () => {
callbackFnExecuted = 2
}
callWithAsyncErrorHandling(fn, callbackFn)
expect(callbackFnExecuted).toBe(2)
})
})

View File

@ -0,0 +1,27 @@
import { callWithErrorHandling } from '../../src/utils/basic'
describe('callWithErrorHandling', () => {
it('should call the function and return the result', () => {
const fn = (x: number) => x
const callbackFn = () => {}
expect(callWithErrorHandling(fn, callbackFn, [1])).toBe(1)
})
it('should call the callback function when the function throws an error', () => {
let callbackFnExecuted = 1
const fn = () => {
throw new Error('test error')
}
const callbackFn = () => {
callbackFnExecuted = 2
}
callWithErrorHandling(fn, callbackFn)
expect(callbackFnExecuted).toBe(2)
})
})

View File

@ -0,0 +1,7 @@
import { detectOperatingSystem } from '../../src/utils/basic'
describe('detectOperatingSystem', () => {
it('should return Unknown', () => {
expect(detectOperatingSystem()).toBe('Unknown')
})
})

View File

@ -0,0 +1,33 @@
import { downloadAnyFile } from '../../src/utils/basic'
describe('downloadAnyFile', () => {
it('should download data when data is a string', () => {
const data = 'test data'
const fileName = 'test.txt'
expect(downloadAnyFile(data, fileName)).resolves.toBeUndefined()
})
// it('should download data when data is a ArrayBuffer', () => {
// const data = new ArrayBuffer(8)
// const fileName = 'test.txt'
// expect(downloadAnyFile(data, fileName)).resolves.toBeUndefined()
// })
// it('should download data when data is a Blob', () => {
// const data = new Blob(['hello', 'world'], {
// type: 'text/plain',
// })
// const fileName = 'test.txt'
// expect(downloadAnyFile(data, fileName)).resolves.toBeUndefined()
// })
// it('should download data when data is a File', () => {
// const data = new File(['hello', 'world'], 'test.txt')
// const fileName = 'test.txt'
// expect(downloadAnyFile(data, fileName)).resolves.toBeUndefined()
// })
})

View File

@ -0,0 +1,12 @@
import { downloadBase64File } from '../../src/utils/basic'
describe('downloadBase64File', () => {
const base64 =
'data:image/png;base64,AAABAAEAEBAQAAEABAAoAQAAFgAAACgAAAAQAAAAIAAAAAEABAAAAAAAgAAAAAAAAAAAAAAAE'
it('download base64 to file', () => {
const result = downloadBase64File(base64, 'test.png')
expect(result).toBe(void 0)
})
})

View File

@ -0,0 +1,17 @@
import { equalRouterPath } from '../../src/utils/basic'
describe('equalRouterPath', () => {
it('compare paths with parameters', () => {
const p1 = '/a?b=1'
const p2 = '/a?b=2'
expect(equalRouterPath(p1, p2)).toBe(true)
})
it('compare paths', () => {
const p1 = '/a'
const p2 = '/a/'
expect(equalRouterPath(p1, p2)).toBe(true)
})
})

View File

@ -0,0 +1,21 @@
import { getAppEnvironment } from '../../src/utils/basic'
describe('getAppEnvironment', () => {
it('should return MODE is test', () => {
const { MODE } = getAppEnvironment()
expect(MODE).toBe('test')
})
it('SSR should be false', () => {
const { SSR } = getAppEnvironment()
expect(SSR).toBe(false)
})
it('deconstruction value should be undefined', () => {
const { UNDEFINED_MODE } = getAppEnvironment()
expect(UNDEFINED_MODE).toBe(void 0)
})
})

View File

@ -0,0 +1,33 @@
import { isAsyncFunction } from '../../src/utils/basic'
describe('isAsyncFunction', () => {
it('should return true if the function is async', () => {
const asyncFn = async () => {}
expect(isAsyncFunction(asyncFn)).toBe(true)
})
it('should return false if the function is not async', () => {
const syncFn = () => {}
expect(isAsyncFunction(syncFn)).toBe(false)
})
it('should return false if the function is not a function', () => {
const notFn = 'not a function'
expect(isAsyncFunction(notFn)).toBe(false)
})
it('should return false if the function is a class', () => {
class MyClass {}
expect(isAsyncFunction(MyClass)).toBe(false)
})
it('should return false if the function is a Promise', () => {
const promise = Promise.resolve('')
expect(isAsyncFunction(promise)).toBe(false)
})
})

View File

@ -0,0 +1,33 @@
import { isPromise } from '../../src/utils/basic'
describe('isPromise', () => {
it('should return true if the value is a Promise', () => {
const promise = Promise.resolve('')
expect(isPromise(promise)).toBe(true)
})
it('should return false if the value is not a Promise', () => {
const notPromise = 'not a Promise'
expect(isPromise(notPromise)).toBe(false)
})
it('should return false if the value is a class', () => {
class MyClass {}
expect(isPromise(MyClass)).toBe(false)
})
it('should return false if the value is a function', () => {
const fn = () => {}
expect(isPromise(fn)).toBe(false)
})
it('should return true if the value is an async function', () => {
const asyncFn = async () => {}
expect(isPromise(asyncFn)).toBe(true)
})
})

View File

@ -0,0 +1,48 @@
import { isValueType } from '../../src/utils/basic'
describe('isValueType', () => {
it('should return true for string', () => {
expect(isValueType<string>('string', 'String')).toBe(true)
})
it('should return true for number', () => {
expect(isValueType<number>(123, 'Number')).toBe(true)
})
it('should return true for array', () => {
expect(isValueType<unknown[]>([], 'Array')).toBe(true)
})
it('should return true for null', () => {
expect(isValueType<null>(null, 'Null')).toBe(true)
})
it('should return true for undefined', () => {
expect(isValueType<undefined>(void 0, 'Undefined')).toBe(true)
})
it('should return true for object', () => {
expect(isValueType<object>({}, 'Object')).toBe(true)
})
it('should return true for Map', () => {
expect(isValueType<Map<unknown, unknown>>(new Map(), 'Map')).toBe(true)
})
it('should return true for Set', () => {
expect(isValueType<Set<unknown>>(new Set(), 'Set')).toBe(true)
})
it('should return true for Date', () => {
expect(isValueType<Date>(new Date(), 'Date')).toBe(true)
})
it('should return true for RegExp', () => {
expect(isValueType<RegExp>(/a/i, 'RegExp')).toBe(true)
})
it('should return false for Function', () => {
// eslint-disable-next-line @typescript-eslint/no-unsafe-function-type
expect(isValueType<Function>(/a/i, 'Function')).toBe(false)
})
})

View File

@ -0,0 +1,20 @@
import { uuid } from '../../src/utils/basic'
describe('uuid', () => {
it('should return String', () => {
expectTypeOf(uuid()).toEqualTypeOf<string>()
})
it('the return value should be unique', () => {
const uuid1 = uuid()
const uuid2 = uuid()
expect(uuid1).not.toBe(uuid2)
})
it('should return a string with length 36', () => {
const uid = uuid(36)
expect(uid.length).toBe(36)
})
})

117
__test__/cache/index.spec.ts vendored Normal file
View File

@ -0,0 +1,117 @@
import {
hasStorage,
setStorage,
getStorage,
removeStorage,
} from '../../src/utils/cache'
describe('cache utils', () => {
const __DEMO__KEY = '__DEMO__KEY'
const __DEMO__VALUE = '__DEMO__VALUE'
const __PRE__KEY = '__PRE__KEY'
it('use setStorage set cache in localStorage and sessionStorage', () => {
setStorage(__DEMO__KEY, __DEMO__VALUE, 'sessionStorage')
setStorage(__DEMO__KEY, __DEMO__VALUE, 'localStorage')
expect(hasStorage(__DEMO__KEY, 'sessionStorage')).toBe(true)
expect(hasStorage(__DEMO__KEY, 'localStorage')).toBe(true)
})
it('use getStorage get cache', () => {
expect(getStorage(__DEMO__KEY, 'sessionStorage')).toBe(__DEMO__VALUE)
expect(getStorage(__DEMO__KEY, 'localStorage')).toBe(__DEMO__VALUE)
})
it('use removeStorage remove cache', () => {
removeStorage(__DEMO__KEY, 'sessionStorage')
removeStorage(__DEMO__KEY, 'localStorage')
expect(hasStorage(__DEMO__KEY, 'sessionStorage')).toBe(false)
expect(hasStorage(__DEMO__KEY, 'localStorage')).toBe(false)
})
it('use removeStorage remove all localStorage and sessionStorage cache', () => {
setStorage(__DEMO__KEY, __DEMO__VALUE, 'sessionStorage')
setStorage(__DEMO__KEY, __DEMO__VALUE, 'localStorage')
removeStorage('__all_sessionStorage__', 'sessionStorage')
removeStorage('__all_localStorage__', 'localStorage')
expect(hasStorage(__DEMO__KEY, 'sessionStorage')).toBe(false)
expect(hasStorage(__DEMO__KEY, 'localStorage')).toBe(false)
})
it('use removeStorage remove all cache', () => {
setStorage(__DEMO__KEY, __DEMO__VALUE, 'sessionStorage')
setStorage(__DEMO__KEY, __DEMO__VALUE, 'localStorage')
removeStorage('__all__', 'all')
expect(hasStorage(__DEMO__KEY, 'sessionStorage')).toBe(false)
expect(hasStorage(__DEMO__KEY, 'localStorage')).toBe(false)
})
it('setStorage with prefix', () => {
setStorage(__DEMO__KEY, __DEMO__VALUE, 'sessionStorage', {
prefix: true,
prefixKey: __PRE__KEY,
})
setStorage(__DEMO__KEY, __DEMO__VALUE, 'localStorage', {
prefix: true,
prefixKey: __PRE__KEY,
})
expect(
hasStorage(__DEMO__KEY, 'sessionStorage', {
prefix: true,
prefixKey: __PRE__KEY,
}),
).toBe(true)
expect(
hasStorage(__DEMO__KEY, 'localStorage', {
prefix: true,
prefixKey: __PRE__KEY,
}),
).toBe(true)
})
it('getStorage with prefix', () => {
expect(
getStorage(__DEMO__KEY, 'sessionStorage', {
prefix: true,
prefixKey: __PRE__KEY,
}),
).toBe(__DEMO__VALUE)
expect(
getStorage(__DEMO__KEY, 'localStorage', {
prefix: true,
prefixKey: __PRE__KEY,
}),
).toBe(__DEMO__VALUE)
})
it('removeStorage with prefix', () => {
removeStorage(__DEMO__KEY, 'sessionStorage', {
prefix: true,
prefixKey: __PRE__KEY,
})
removeStorage(__DEMO__KEY, 'localStorage', {
prefix: true,
prefixKey: __PRE__KEY,
})
expect(
hasStorage(__DEMO__KEY, 'sessionStorage', {
prefix: true,
prefixKey: __PRE__KEY,
}),
).toBe(false)
expect(
hasStorage(__DEMO__KEY, 'localStorage', {
prefix: true,
prefixKey: __PRE__KEY,
}),
).toBe(false)
})
})

View File

@ -0,0 +1,36 @@
import { RModal } from '../../src/components/base/RModal/index'
import { mount } from '@vue/test-utils'
describe('RModal', () => {
it('should execute the onAfterEnter callback', () => {
mount(RModal, {
props: {
show: true,
onAfterEnter: () => {
assert(true)
},
},
slots: {
default: h('div', 'Hello World'),
},
})
})
it('should render a modal', async () => {
const wrapper = mount(RModal, {
props: {
show: true,
},
slots: {
default: h('div', 'Hello World'),
},
})
const classStr = 'n-modal-container'
const modal = document.body.querySelector(`.${classStr}`)
const modalClassList = Array.from(modal?.classList || [])
expect(modalClassList.length).not.toBe(0)
expect(modalClassList.includes(classStr)).toBe(true)
})
})

View File

@ -0,0 +1,49 @@
import { printDom } from '../../src/utils/dom'
import { mount } from '@vue/test-utils'
import renderHook from '../utils/renderHook'
// happy-dom 官方有一个 bug无法使用 canvas.toDataURL 方法。所以该模块单测暂时无法通过
describe('printDom', () => {
// let count = 1
// const domRef = ref<HTMLElement>()
// const canvas = document.createElement('canvas')
// canvas.width = 100
// canvas.height = 100
// console.log('canvas.toDataURL result', canvas.toDataURL)
// const wrapper = mount(
// defineComponent({
// setup() {
// const print = () => {
// count = 2
// printDom(canvas, {
// domToImageOptions: {
// created: () => {
// count = 2
// },
// },
// })
// }
// return {
// domRef,
// print,
// }
// },
// render() {
// const { print } = this
// return (
// <>
// <div ref="domRef">print html</div>
// <button onClick={print.bind(this)}>print</button>
// </>
// )
// },
// }),
// )
// it('print dom', () => {
// const button = wrapper.find('button')
// button.trigger('click')
// expect(count).toBe(2)
// })
it('print dom', () => {})
})

View File

@ -0,0 +1,19 @@
import { autoPrefixStyle } from '../../src/utils/element'
describe('autoPrefixStyle', () => {
it('should be defined', () => {
expect(autoPrefixStyle).toBeDefined()
})
it('should complete css prefix', () => {
const result = autoPrefixStyle('transform')
expect(result).toEqual({
webkitTransform: 'transform',
mozTransform: 'transform',
msTransform: 'transform',
oTransform: 'transform',
transform: 'transform',
})
})
})

View File

@ -0,0 +1,68 @@
import { setClass, hasClass, removeClass } from '../../src/utils/element'
import createRefElement from '../utils/createRefElement'
describe('setClass', () => {
const wrapper = createRefElement()
const CLASS_NAME = 'test'
const CLASS_NAME_2 = 'test2'
it('set ref element class', () => {
setClass(wrapper.element, CLASS_NAME)
const classList = Array.from(wrapper.element.classList)
expect(classList.includes(CLASS_NAME)).toBe(true)
})
it('set ref element class with multiple class names', () => {
setClass(wrapper.element, `${CLASS_NAME} ${CLASS_NAME_2}`)
const classList = Array.from(wrapper.element.classList)
expect(classList.includes(CLASS_NAME)).toBe(true)
expect(classList.includes(CLASS_NAME_2)).toBe(true)
})
it('set ref element class with multiple class names use array params', () => {
setClass(wrapper.element, [CLASS_NAME, CLASS_NAME_2])
const classList = Array.from(wrapper.element.classList)
expect(classList.includes(CLASS_NAME)).toBe(true)
expect(classList.includes(CLASS_NAME_2)).toBe(true)
})
it('get ref element class', () => {
setClass(wrapper.element, CLASS_NAME)
const hasClassResult = hasClass(wrapper.element, CLASS_NAME)
expect(hasClassResult.value).toBe(true)
})
it('get ref element class with multiple class names', () => {
setClass(wrapper.element, `${CLASS_NAME} ${CLASS_NAME_2}`)
const hasClassResult = hasClass(wrapper.element, CLASS_NAME)
expect(hasClassResult.value).toBe(true)
})
it('get ref element class with multiple class names use array params', () => {
setClass(wrapper.element, [CLASS_NAME, CLASS_NAME_2])
const hasClassResult = hasClass(wrapper.element, CLASS_NAME)
expect(hasClassResult.value).toBe(true)
})
it('remove ref element class', () => {
setClass(wrapper.element, CLASS_NAME)
removeClass(wrapper.element, CLASS_NAME)
const classList = Array.from(wrapper.element.classList)
expect(classList.includes(CLASS_NAME)).toBe(false)
})
})

View File

@ -0,0 +1,23 @@
import { colorToRgba } from '../../src/utils/element'
describe('colorToRgba', () => {
it('should be defined', () => {
expect(colorToRgba).toBeDefined()
})
it('should return rgba color', () => {
expect(colorToRgba('rgb(255, 255, 255)', 0.5)).toBe(
'rgba(255, 255, 255, 0.5)',
)
expect(colorToRgba('rgba(255, 255, 255, 0.5)', 0.5)).toBe(
'rgba(255, 255, 255, 0.5)',
)
expect(colorToRgba('#fff', 0.1)).toBe('rgba(255, 255, 255, 0.1)')
expect(colorToRgba('#000000', 0.1)).toBe('rgba(0, 0, 0, 0.1)')
expect(colorToRgba('#fffffafa', 0.1)).toBe('rgba(255, 255, 250, 0.98)')
})
it('should return input color', () => {
expect(colorToRgba('hi')).toBe('hi')
})
})

View File

@ -0,0 +1,17 @@
import { completeSize } from '../../src/utils/element'
describe('completeSize', () => {
it('should be defined', () => {
expect(completeSize).toBeDefined()
})
it('should return size', () => {
expect(completeSize('100px')).toBe('100px')
expect(completeSize('100%')).toBe('100%')
expect(completeSize('100vw')).toBe('100vw')
})
it('should return default size', () => {
expect(completeSize(0)).toBe('0px')
})
})

View File

@ -0,0 +1,52 @@
import { queryElements } from '../../src/utils/element'
describe('queryElements', () => {
const div = document.createElement('div')
const CLASS_NAME = 'demo'
const ATTR_KEY = 'attr_key'
const ATTR_VALUE = 'attr_value'
it('should be defined', () => {
expect(queryElements).toBeDefined()
})
it('should return empty array', () => {
const el = queryElements('.demo')
expect(el?.length).toBe(0)
})
it('should return element list', () => {
div.parentNode?.removeChild(div)
div.classList.add(CLASS_NAME)
document.body.appendChild(div)
const el = queryElements('.demo')
expect(el?.length).toBe(1)
})
it('should return default element', () => {
div.parentNode?.removeChild(div)
const el = queryElements('.demo', {
defaultElement: document.body,
})
expect(el?.length).toBe(1)
})
it('should return element list by attr', () => {
div.parentNode?.removeChild(div)
div.setAttribute(ATTR_KEY, ATTR_VALUE)
document.body.appendChild(div)
const el = queryElements(`attr:${ATTR_KEY}`)
const el2 = queryElements(`attr:${ATTR_KEY}=${ATTR_VALUE}`)
expect(el?.length).toBe(1)
expect(el2?.length).toBe(1)
})
})

View File

@ -0,0 +1,71 @@
import { setStyle, removeStyle } from '../../src/utils/element'
import createRefElement from '../utils/createRefElement'
describe('setStyle', () => {
const div = document.createElement('div')
const removeKeys = ['width', 'height']
const wrapper = createRefElement()
document.body.appendChild(div)
it('should be defined', () => {
expect(setStyle).toBeDefined()
})
it('should set style', () => {
removeStyle(div, removeKeys)
setStyle(div, {
width: '100px',
height: '100px',
})
expect(div.style.width).toBe('100px')
expect(div.style.height).toBe('100px')
})
it('should set style with string', () => {
removeStyle(div, removeKeys)
setStyle(div, 'width: 100px; height: 100px;')
expect(div.style.width).toBe('100px')
expect(div.style.height).toBe('100px')
})
it('should set style with string array', () => {
removeStyle(div, removeKeys)
setStyle(div, ['width: 100px', 'height: 100px'])
expect(div.style.width).toBe('100px')
expect(div.style.height).toBe('100px')
})
it('should set style with css variable', () => {
removeStyle(div, ['--width', '--height'])
setStyle(div, {
'--width': '100px',
'--height': '100px',
})
expect(div.style.getPropertyValue('--width')).toBe('100px')
expect(div.style.getPropertyValue('--height')).toBe('100px')
})
it('should set style to ref element', () => {
const element = wrapper.vm.domRef as HTMLElement
const style = element.style
removeStyle(element, removeKeys)
setStyle(element, {
width: '100px',
height: '100px',
})
expect(style.width).toBe('100px')
expect(style.height).toBe('100px')
})
})

View File

@ -0,0 +1,18 @@
import { useAppNavigation } from '../../src/hooks/template/useAppNavigation'
import { useMenuGetters } from '../../src/store'
import setupMiniApp from '../utils/setupMiniApp'
describe('useAppNavigation', async () => {
await setupMiniApp()
const { navigationTo } = useAppNavigation()
const { getMenuOptions } = useMenuGetters()
it('navigationTo', () => {
const [dashboard] = getMenuOptions.value
expect(navigationTo(dashboard.fullPath)).toBeUndefined()
expect(navigationTo(dashboard)).toBeUndefined()
expect(navigationTo(0)).toBeUndefined()
})
})

View File

@ -0,0 +1,51 @@
import setupMiniApp from '../utils/setupMiniApp'
import { useAppRoot } from '../../src/hooks/template/useAppRoot'
describe('useAppRoot', async () => {
await setupMiniApp()
const { setRootRoute } = useAppRoot()
it(`should return '/test' and 'test'`, () => {
setRootRoute({
path: '/test',
name: 'test',
})
const { getRootPath, getRootName } = useAppRoot()
expect(getRootPath.value).toBe('/test')
expect(getRootName.value).toBe('test')
})
it(`should be returned a object like: {path: /test2, name: test2}`, () => {
const baseRootRoute = {
path: '/test2',
name: 'test2',
}
setRootRoute(baseRootRoute)
const { getRootRoute } = useAppRoot()
expect(getRootRoute.value).toEqual(baseRootRoute)
})
it('should update root route when setRootRoute is called', () => {
const baseRootRoute = {
path: '/test3',
name: 'test3',
}
setRootRoute({
path: '/test3',
name: 'test3',
})
const { getRootPath, getRootName, getRootRoute } = useAppRoot()
expect(getRootPath.value).toBe('/test3')
expect(getRootName.value).toBe('test3')
expect(getRootRoute.value).toEqual(baseRootRoute)
})
})

View File

@ -0,0 +1,67 @@
import setupMiniApp from '../utils/setupMiniApp'
import { useBadge } from '../../src/hooks/template/useBadge'
import { useMenuGetters } from '../../src/store'
import type { AppMenuExtraOptions } from '../../src/router/types'
describe('useBadge', async () => {
await setupMiniApp()
const { show, hidden, update } = useBadge()
const { getMenuOptions } = useMenuGetters()
const baseBadge = (extra: AppMenuExtraOptions) =>
Object.assign(
{},
{
label: 'new',
type: 'error',
} as AppMenuExtraOptions,
extra,
)
it('should hide badge', () => {
const [dashboard] = getMenuOptions.value
update(
dashboard,
baseBadge({
show: false,
label: 'new',
}),
)
hidden(dashboard)
expect(dashboard.meta.extra?.show).toBe(false)
})
it('should show badge', () => {
const [dashboard] = getMenuOptions.value
update(
dashboard,
baseBadge({
show: true,
label: 'new',
}),
)
show(dashboard)
expect(dashboard.meta.extra?.show).toBe(true)
})
it('should show badge with new label', () => {
const [dashboard] = getMenuOptions.value
const label = 'update new'
update(
dashboard,
baseBadge({
show: true,
label,
}),
)
expect(dashboard.meta.extra?.label).toBe(label)
})
})

View File

@ -0,0 +1,50 @@
import { useContextmenuCoordinate } from '../../src/hooks/components/useContextmenuCoordinate'
import renderHook from '../utils/renderHook'
import createRefElement from '../utils/createRefElement'
describe('useContextmenuCoordinate', () => {
const wrapperRef = createRefElement()
const [result] = renderHook(() =>
useContextmenuCoordinate(wrapperRef.element),
)
it('should be defined', () => {
expect(useContextmenuCoordinate).toBeDefined()
})
it('should update show value to true when contextmenu event is triggered', async () => {
wrapperRef.element.dispatchEvent(new MouseEvent('contextmenu'))
await nextTick()
expect(result.show.value).toBe(true)
})
it('should update show value when calling updateShow method', async () => {
result.updateShow(false)
await nextTick()
expect(result.show.value).toBe(false)
result.updateShow(true)
await nextTick()
expect(result.show.value).toBe(true)
})
it('should get the clientX and clientY value when contextmenu event is triggered', async () => {
const event = new MouseEvent('contextmenu', {
clientX: 100,
clientY: 200,
})
wrapperRef.element.dispatchEvent(event)
await nextTick()
expect(result.x.value).toBe(100)
expect(result.y.value).toBe(200)
})
})

View File

@ -0,0 +1,101 @@
import { useDayjs } from '../../src/hooks/web/useDayjs'
import dayjs from 'dayjs'
describe('useDayjs', () => {
const {
locale,
getStartAndEndOfDay,
format,
isDayjs,
daysDiff,
isDateInRange,
} = useDayjs()
it('check whether the locale method runs properly', () => {
const m = {
locale,
}
const localSpy = vi.spyOn(m, 'locale')
m.locale('en-US')
m.locale('zh-CN')
expect(localSpy).toHaveBeenCalledTimes(2)
})
it('gets Returns the current date, start time, and end time of the current date ', () => {
const formatOptions = {
format: 'YYYY/M/DD HH:mm:ss',
}
const formatOptions2 = {
format: 'YYYY/M/DD',
}
const {
today,
startOfDay,
endOfDay,
formatToday,
formatStartOfDay,
formatEndOfDay,
} = getStartAndEndOfDay(formatOptions)
const _today = dayjs(new Date()).format(formatOptions2.format)
const _startOfDay = dayjs(new Date().setHours(0, 0, 0, 0)).format(
formatOptions.format,
)
const _endOfDay = dayjs(new Date().setHours(23, 59, 59, 999)).format(
formatOptions.format,
)
expect(format(today, formatOptions2)).toBe(_today)
expect(format(startOfDay, formatOptions)).toBe(_startOfDay)
expect(format(endOfDay, formatOptions)).toBe(_endOfDay)
expect(format(formatToday, formatOptions2)).toBe(_today)
expect(formatStartOfDay).toBe(_startOfDay)
expect(formatEndOfDay).toBe(_endOfDay)
})
it('check format method', () => {
const formatOptions1 = {
format: 'YYYY/M/DD HH:mm:ss',
}
const formatOptions2 = {
format: 'YYYY/M/DD',
}
const formatOptions3 = {
format: 'YYYY-MM-DD HH:mm:ss',
}
const formatOptions4 = {
format: 'YYYY-MM-DD',
}
const date = new Date('2022-01-11 00:00:00')
expect(format(date, formatOptions1)).toBe('2022/1/11 00:00:00')
expect(format(date, formatOptions2)).toBe('2022/1/11')
expect(format(date, formatOptions3)).toBe('2022-01-11 00:00:00')
expect(format(date, formatOptions4)).toBe('2022-01-11')
})
it('check isDayjs object', () => {
const { today } = getStartAndEndOfDay()
expect(isDayjs(new Date())).toBe(false)
expect(isDayjs(today)).toBe(true)
})
it('check daysDiff method', () => {
expect(daysDiff('2022-01-11', '2022-01-12')).toBe(1)
expect(daysDiff('2021-01-11', '2022-01-12')).toBe(366)
expect(daysDiff('2023-01-11', '2022-01-12')).toBe(-364)
})
it('check isDateInRange method', () => {
const range = {
start: '2023-01-15',
end: '2023-01-20',
}
expect(isDateInRange('2023-01-16', range)).toBe(true)
expect(isDateInRange('2023-01-15', range)).toBe(false)
expect(isDateInRange('2023-01-20', range)).toBe(false)
})
})

View File

@ -0,0 +1,116 @@
import { useDevice } from '../../src/hooks/web/useDevice'
describe('useDevice', () => {
const addEventListenerSpy = vi.spyOn(window, 'addEventListener')
const matchMediaSpy = vi
.spyOn(window, 'matchMedia')
.mockImplementation((query) => ({
matches: false,
media: query,
onchange: null,
addListener: vi.fn(),
removeListener: vi.fn(),
addEventListener: vi.fn(),
removeEventListener: vi.fn(),
dispatchEvent: vi.fn(),
}))
beforeEach(() => {
addEventListenerSpy.mockClear()
matchMediaSpy.mockClear()
})
afterAll(() => {
addEventListenerSpy.mockRestore()
matchMediaSpy.mockRestore()
})
it('should be defined', () => {
expect(useDevice).toBeDefined()
})
it('should work', () => {
const { width, height } = useDevice({
initialWidth: 100,
initialHeight: 200,
})
expect(width.value).toBe(window.innerWidth)
expect(height.value).toBe(window.innerHeight)
})
it('should exclude scrollbar', () => {
const { width, height } = useDevice({
initialWidth: 100,
initialHeight: 200,
includeScrollbar: false,
})
expect(width.value).toBe(window.document.documentElement.clientWidth)
expect(height.value).toBe(window.document.documentElement.clientHeight)
})
it('sets handler for window resize event', async () => {
useDevice({
initialWidth: 100,
initialHeight: 200,
listenOrientation: false,
})
await nextTick()
expect(addEventListenerSpy).toHaveBeenCalledOnce()
const call = addEventListenerSpy.mock.calls[0]
expect(call[0]).toEqual('resize')
expect(call[2]).toEqual({
passive: true,
})
})
it('sets handler for window.matchMedia("(orientation: portrait)") change event', async () => {
useDevice({
initialWidth: 100,
initialHeight: 200,
})
await nextTick()
expect(addEventListenerSpy).toHaveBeenCalledTimes(1)
expect(matchMediaSpy).toHaveBeenCalledTimes(1)
const call = matchMediaSpy.mock.calls[0]
expect(call[0]).toEqual('(orientation: portrait)')
})
it('should update width and height on window resize', async () => {
const { width, height } = useDevice({
initialWidth: 100,
initialHeight: 200,
})
window.innerWidth = 300
window.innerHeight = 400
window.dispatchEvent(new Event('resize'))
await nextTick()
expect(width.value).toBe(300)
expect(height.value).toBe(400)
})
it('should update isTabletOrSmaller on window resize', async () => {
const { isTabletOrSmaller } = useDevice()
window.innerWidth = 300
window.dispatchEvent(new Event('resize'))
await nextTick()
expect(isTabletOrSmaller.value).toBe(true)
})
})

View File

@ -0,0 +1,54 @@
import { useElementFullscreen } from '../../src/hooks/web/useElementFullscreen'
describe('useElementFullscreen', async () => {
const div = document.createElement('div')
const transition = 'all 0.3s var(--r-bezier)'
const __ID__ = '__ID__'
div.setAttribute('id', __ID__)
document.body.appendChild(div)
const resetDivStyle = () => {
const element = document.getElementById(__ID__)
if (element) {
element.style.transition = ''
}
}
const { enter, exit, toggleFullscreen } = useElementFullscreen(div)
it('should enter fullscreen', async () => {
resetDivStyle()
enter()
await nextTick()
expect(div.style.transition).toBe(transition)
})
it('should exit fullscreen', async () => {
resetDivStyle()
exit()
await nextTick()
expect(div.style.transition).toBe('')
})
it('should toggle fullscreen', async () => {
resetDivStyle()
enter()
enter() // 为了兼容测试环境,故而调用两次
await nextTick()
expect(div.style.transition).toBe(transition)
toggleFullscreen()
await nextTick()
expect(!div.style.transition).not.toBe(true)
})
})

View File

@ -0,0 +1,66 @@
import { usePagination } from '../../src/hooks/web/usePagination'
describe('usePagination', () => {
let count = 0
const defaultOptions = {
itemCount: 200,
page: 1,
pageSize: 10,
}
const [
_,
{
getItemCount,
getCallback,
getPage,
getPageSize,
getPagination,
setItemCount,
setPage,
setPageSize,
},
] = usePagination(() => {
count++
}, defaultOptions)
it('should get current itemCount', () => {
setItemCount(200)
expect(getItemCount()).toBe(200)
setItemCount(100)
expect(getItemCount()).toBe(100)
})
it('should get current page', () => {
setPage(1)
expect(getPage()).toBe(1)
})
it('should get current pageSize', () => {
setPageSize(10)
expect(getPageSize()).toBe(10)
})
it('should get current pagination', () => {
setItemCount(200)
expect(getPagination()).toMatchObject(defaultOptions)
})
it('should update count when page or pageSize changes', () => {
count = 0
setPage(2)
expect(count).toBe(1)
setPageSize(20)
expect(count).toBe(2)
})
})

View File

@ -0,0 +1,167 @@
import setupMiniApp from '../utils/setupMiniApp'
import { useSiderBar } from '../../src/hooks/template/useSiderBar'
import { useMenuGetters, useMenuActions } from '../../src/store'
import { useVueRouter } from '../../src/hooks/web/useVueRouter'
import type { AppMenuOption, MenuTagOptions } from '../../src/types/modules/app'
describe('useSiderBar', async () => {
await setupMiniApp()
const { setMenuTagOptions, resolveOption } = useMenuActions()
const {
close,
closeAll,
closeRight,
closeLeft,
closeOther,
getCurrentTagIndex,
checkCloseRight,
checkCloseLeft,
} = useSiderBar()
const updateMenuTagOptions = () => {
const { router } = useVueRouter()
const routes = router.getRoutes() as unknown as AppMenuOption[]
routes.forEach((curr) =>
setMenuTagOptions(
resolveOption({
...curr,
fullPath: curr.path,
}),
true,
),
)
}
it('should close target tag', async () => {
updateMenuTagOptions()
const { getMenuOptions, getMenuTagOptions } = useMenuGetters()
const [dashboard] = getMenuOptions.value
const beforeIndex = getMenuTagOptions.value.findIndex(
(curr) => curr.fullPath === dashboard.fullPath,
)
close(dashboard.fullPath)
await nextTick()
const afterIndex = getMenuTagOptions.value.findIndex(
(curr) => curr.fullPath === dashboard.fullPath,
)
expect(beforeIndex).not.toBe(afterIndex)
})
it('should close all tags', async () => {
updateMenuTagOptions()
const { getMenuTagOptions } = useMenuGetters()
closeAll()
await nextTick()
const afterLength = getMenuTagOptions.value.length
expect(afterLength).toBe(1)
})
it('should close right tags', async () => {
updateMenuTagOptions()
const { getMenuOptions, getMenuTagOptions } = useMenuGetters()
const [dashboard] = getMenuOptions.value
const beforeIndex = getMenuTagOptions.value.findIndex(
(curr) => curr.fullPath === dashboard.fullPath,
)
expect(!!getMenuTagOptions.value[beforeIndex + 1]).toBe(true)
closeRight(dashboard.fullPath)
await nextTick()
const afterIndex = getMenuTagOptions.value.findIndex(
(curr) => curr.fullPath === dashboard.fullPath,
)
expect(getMenuTagOptions.value[afterIndex + 1]).toBe(void 0)
})
it('should close left tags', async () => {
updateMenuTagOptions()
const { getMenuOptions, getMenuTagOptions } = useMenuGetters()
const [dashboard] = getMenuOptions.value
closeLeft(dashboard.fullPath)
await nextTick()
const afterIndex = getMenuTagOptions.value.findIndex(
(curr) => curr.fullPath === dashboard.fullPath,
)
expect(getMenuTagOptions.value[afterIndex - 1]).toBe(void 0)
})
it('should get current tag index', async () => {
updateMenuTagOptions()
const { getMenuOptions, getMenuTagOptions } = useMenuGetters()
const [dashboard] = getMenuOptions.value
const index = getMenuOptions.value.findIndex(
(curr) => curr.fullPath === dashboard.fullPath,
)
expect(getCurrentTagIndex()).toBe(index)
})
it('should close other tags', async () => {
updateMenuTagOptions()
const { getMenuOptions, getMenuTagOptions } = useMenuGetters()
const [dashboard] = getMenuOptions.value
closeOther(dashboard.fullPath)
await nextTick()
expect(getMenuTagOptions.value[0].fullPath).toBe(dashboard.fullPath)
expect(getMenuTagOptions.value.length).toBe(1)
})
it('check menuTagOptions left or right can close', async () => {
updateMenuTagOptions()
const { getMenuOptions, getMenuTagOptions } = useMenuGetters()
const [f, s] = getMenuOptions.value
closeRight(f.fullPath)
await nextTick()
const canClose = checkCloseRight(f.fullPath)
expect(canClose).toBe(false)
updateMenuTagOptions()
closeLeft(f.fullPath)
await nextTick()
const canCloseLeft = checkCloseLeft(f.fullPath)
expect(canCloseLeft).toBe(false)
updateMenuTagOptions()
expect(checkCloseRight(s.fullPath)).toBe(true)
expect(checkCloseLeft(s.fullPath)).toBe(true)
})
})

View File

@ -0,0 +1,38 @@
import setupMiniApp from '../utils/setupMiniApp'
import { useSpinning } from '../../src/hooks/template/useSpinning'
import { setVariable, getVariableToRefs } from '../../src/global-variable'
describe('useSpinning', async () => {
await setupMiniApp()
const { reload, openSpin, closeSpin } = useSpinning()
const globalMainLayoutLoad = getVariableToRefs('globalMainLayoutLoad')
it('should open spinning', () => {
openSpin()
expect(globalMainLayoutLoad.value).toBe(true)
})
it('should close spinning', () => {
openSpin()
expect(globalMainLayoutLoad.value).toBe(true)
closeSpin()
expect(globalMainLayoutLoad.value).toBe(true)
})
it('should reload', () => {
const wait = 1000
reload(wait)
expect(globalMainLayoutLoad.value).toBe(false)
setTimeout(() => {
expect(globalMainLayoutLoad.value).toBe(true)
}, wait)
})
})

View File

@ -0,0 +1,46 @@
import setupMiniApp from '../utils/setupMiniApp'
import { useTheme } from '../../src/hooks/template/useTheme'
describe('useTheme', async () => {
await setupMiniApp()
const { darkTheme, lightTheme, toggleTheme, getAppTheme } = useTheme()
it('should change to dark theme', () => {
darkTheme()
expect(getAppTheme().theme).toBe(true)
})
it('should change to light theme', () => {
lightTheme()
expect(getAppTheme().theme).toBe(false)
})
it('should toggle theme', () => {
lightTheme()
expect(getAppTheme().theme).toBe(false)
toggleTheme()
expect(getAppTheme().theme).toBe(true)
})
it('should return current theme', () => {
darkTheme()
const { theme: _darkTheme, themeLabel: _darkThemeLabel } = getAppTheme()
expect(_darkTheme).toBe(true)
expect(_darkThemeLabel).toBe('Dark')
lightTheme()
const { theme: __lightTheme, themeLabel: __lightThemeLabel } = getAppTheme()
expect(__lightTheme).toBe(false)
expect(__lightThemeLabel).toBe('Light')
})
})

View File

@ -0,0 +1,15 @@
import setupMiniApp from '../utils/setupMiniApp'
import { useVueRouter } from '../../src/hooks/web/useVueRouter'
describe('useVueRouter', async () => {
await setupMiniApp()
const { router } = useVueRouter()
it('should get push and replace methods', () => {
const { push, replace } = router
assert.isFunction(push)
assert.isFunction(replace)
})
})

View File

@ -0,0 +1,33 @@
import setupMiniApp from '../utils/setupMiniApp'
import { useWatermark } from '../../src/hooks/template/useWatermark'
import { useSettingGetters } from '../../src/store'
describe('useWatermark', async () => {
await setupMiniApp()
const { setWatermarkContent, showWatermark, hiddenWatermark } = useWatermark()
it('should set watermark content', () => {
const { getWatermarkConfig } = useSettingGetters()
const watermarkConfig = getWatermarkConfig.value
const content = 'Ray Template Yes!'
setWatermarkContent(content)
expect(watermarkConfig.content).toBe(content)
})
it('should update watermark', () => {
showWatermark()
const { getWatermarkSwitch: show } = useSettingGetters()
expect(show.value).toBe(true)
hiddenWatermark()
const { getWatermarkSwitch: hidden } = useSettingGetters()
expect(hidden.value).toBe(false)
})
})

View File

@ -0,0 +1,82 @@
import {
isCurrency,
format,
add,
subtract,
multiply,
divide,
distribute,
} from '../../src/utils/precision'
describe('precision', () => {
it('check value is currency object', () => {
expect(isCurrency(1)).toBeFalsy()
expect(isCurrency('1')).toBeFalsy()
expect(isCurrency({})).toBeFalsy()
expect(isCurrency({ s: 1 })).toBeFalsy()
expect(isCurrency(add(1, 1))).toBeTruthy()
})
it('format value', () => {
expect(format(1)).toBe(1)
expect(
format(1.1, {
type: 'number',
}),
).toBe(1.1)
expect(
format(1.11, {
type: 'string',
precision: 2,
}),
).toBe('1.11')
expect(format(add(1, 1))).toBe(2)
expect(format(add(0.1, 0.2))).toBe(0.3)
})
it('add value', () => {
expect(format(add(1, 1))).toBe(2)
expect(format(add(0.1, 0.2))).toBe(0.3)
expect(format(add(0.1, 0.2, 0.3))).toBe(0.6)
expect(format(add(0.1, 0.2, 0.3, 0.4))).toBe(1)
expect(format(add(0.1, 0.2, 0.3, 0.4, 0.5))).toBe(1.5)
})
it('subtract value', () => {
expect(format(subtract(1, 1))).toBe(0)
expect(format(subtract(0.3, 0.2))).toBe(0.1)
expect(format(subtract(0.6, 0.3, 0.2))).toBe(0.1)
expect(format(subtract(1, 0.5, 0.4, 0.3, 0.2))).toBe(-0.4)
})
it('multiply value', () => {
expect(format(multiply(1, 1))).toBe(1)
expect(format(multiply(0.1, 0.2))).toBe(0.02)
expect(format(multiply(0.1, 0.2, 0.3))).toBe(0.006)
expect(format(multiply(0.1, 0.2, 0.3, 0.4))).toBe(0.0024)
expect(format(multiply(0.1, 0.2, 0.3, 0.4, 0.5))).toBe(0.0012)
})
it('divide value', () => {
expect(format(divide(1, 1))).toBe(1)
expect(format(divide(0.1, 0.2))).toBe(0.5)
expect(
format(divide(0.1, 0.2, 0.3), {
precision: 2,
}),
).toBe(1.67)
})
it('distribute value', () => {
expect(distribute(1, 1)).toEqual([1])
expect(distribute(1, 0)).toEqual([1])
expect(distribute(0, 3)).toEqual([0, 0, 0])
expect(distribute(10, 3)).toEqual([3.33333334, 3.33333333, 3.33333333])
expect(
distribute(20, 3, {
precision: 4,
}),
).toEqual([6.6667, 6.6667, 6.6666])
expect(distribute(add(20, 1), 3)).toEqual([7, 7, 7])
})
})

View File

@ -0,0 +1,16 @@
/**
*
* @description
* DOM
*
* DOM true false
*/
const canUseDom = () => {
return !!(
typeof window !== 'undefined' &&
window.document &&
window.document.createElement
)
}
export default canUseDom

View File

@ -0,0 +1,36 @@
import { mount } from '@vue/test-utils'
/**
*
* @param slots
*
* @description
* ref domRef
*
*
* @example
* const wrapper = createRefElement({ default: () => <div>hello</div> })
*
* const text = wrapper.find('div').text() // hello
*/
// eslint-disable-next-line @typescript-eslint/no-unsafe-function-type
const createRefElement = (slots?: Record<string, Function>) => {
const wrapper = mount(
defineComponent({
setup() {
const domRef = ref<HTMLElement>()
return {
domRef,
}
},
render() {
return <div ref="domRef">{{ ...slots }}</div>
},
}),
)
return wrapper
}
export default createRefElement

View File

@ -0,0 +1,15 @@
/**
*
* @description
*
*
* true false
*/
const isBrowser = () =>
!!(
typeof window !== 'undefined' &&
window.document &&
window.document.createElement
)
export default isBrowser

View File

@ -0,0 +1,34 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
import { createApp, defineComponent } from 'vue'
import type { App } from 'vue'
export default function renderHook<R = any>(
renderFC: () => R,
): [
R,
App<Element>,
{
act?: (fn: () => void) => void
},
] {
let result: any
let act: ((fn: () => void) => void) | undefined
const app = createApp(
defineComponent({
setup() {
result = renderFC()
act = (fn: () => void) => {
fn()
}
return () => {}
},
}),
)
app.mount(document.createElement('div'))
return [result, app, { act }]
}

View File

@ -0,0 +1,27 @@
import { setupStore } from '../../src/store'
import { setupRouter } from '../../src/router'
import { setupI18n } from '../../src/locales'
import renderHook from '../utils/renderHook'
/**
*
* @description
* mini ray template
* storerouteri18n
*
* @example
* const { app } = await setupMiniApp()
*/
const setupMiniApp = async () => {
const [_, app] = renderHook(() => {})
setupStore(app)
setupRouter(app)
await setupI18n(app)
return {
app,
}
}
export default setupMiniApp

17
__test__/utils/sleep.ts Normal file
View File

@ -0,0 +1,17 @@
/**
*
* @param timer
*
* @description
*
*
* @example
* await sleep(1000)
*/
const sleep = (timer: number) => {
return new Promise((resolve) => {
setTimeout(resolve, timer)
})
}
export default sleep

27
__test__/vue/call.spec.ts Normal file
View File

@ -0,0 +1,27 @@
import { call } from '../../src/utils/vue/call'
describe('call', () => {
it('should be executed once', () => {
const fn = vi.fn()
call(() => fn())
expect(fn).toHaveBeenCalledTimes(1)
})
it('should be executed with an argument', () => {
const fn = vi.fn()
call((a: number) => fn(a), 1)
expect(fn).toHaveBeenCalledWith(1)
})
it('should be executed with multiple arguments', () => {
const fn = vi.fn()
call((a: number, b: number) => fn(a, b), 1, 2)
expect(fn).toHaveBeenCalledWith(1, 2)
})
})

View File

@ -0,0 +1,7 @@
import { effectDispose } from '../../src/utils/vue/effect-dispose'
describe('effectDispose', () => {
it('should return false if getCurrentScope is null', () => {
expect(effectDispose(() => {})).toBe(false)
})
})

View File

@ -0,0 +1,14 @@
import { renderNode } from '../../src/utils/vue/render-node'
import createRefElement from '../utils/createRefElement'
describe('renderNode', () => {
it('should render string', () => {
const wrapper = createRefElement({
// eslint-disable-next-line @typescript-eslint/no-unsafe-function-type
default: renderNode('hello world') as Function,
})
const text = wrapper.text()
expect(text).toBe('hello world')
})
})

146
cfg.ts
View File

@ -1,146 +0,0 @@
/**
*
* @author Ray <https://github.com/XiaoDaiGua-Ray>
*
* @date 2023-04-06
*
* @workspace ray-template
*
* @remark
*/
/**
*
*
*
* :
* - 构建: 开发构建
* - 系统: 根路由
* - 请求: 代理配置
*
* , src/types/modules/cfg.ts
* ```
* interface Config // config 内容类型配置
*
* interface AppConfig // __APP_CFG__ 内容配置
* ```
*
* __APP_CFG__
* ```
*
*
* const { appPrimaryColor } = __APP_CFG__
*
* , __APP_CFG__ appPrimaryColor
* __APP_CFG__ `window` (vite define window )
* ```
*/
import path from 'node:path'
import {
HTMLTitlePlugin,
buildOptions,
mixinCSSPlugin,
} from './vite-plugin/index'
import { APP_THEME } from './src/appConfig/designConfig'
import { PRE_LOADING_CONFIG, SIDE_BAR_LOGO } from './src/appConfig/appConfig'
import type { AppConfigExport } from '@/types/modules/cfg'
const config: AppConfigExport = {
/** 公共基础路径配置, 如果为空则会默认以 '/' 填充 */
base: '/ray-template/',
/** 配置首屏加载信息 */
preloadingConfig: PRE_LOADING_CONFIG,
/** 默认主题色(不可省略, 必填), 也用于 ejs 注入 */
appPrimaryColor: APP_THEME.APP_PRIMARY_COLOR,
sideBarLogo: SIDE_BAR_LOGO,
/**
*
* css
*
* :
* - ./src/styles/mixins.scss
* - ./src/styles/setting.scss
* - ./src/styles/theme.scss
*
* , css
*/
mixinCSS: mixinCSSPlugin([
'./src/styles/mixins.scss',
'./src/styles/setting.scss',
]),
/**
*
*
*
* ,
*/
copyright: 'Copyright © 2022-present Ray',
/**
*
*
*/
title: HTMLTitlePlugin('Ray Template'),
/**
*
* HMR ()
*/
server: {
host: '0.0.0.0',
port: 9527,
open: false,
https: false,
strictPort: false,
fs: {
strict: false,
allow: [],
},
proxy: {
'/api': {
target: 'url',
changeOrigin: true,
rewrite: (path) => path.replace(/^\/api/, ''),
},
'/office': {
target: 'https://office.yka.one/',
changeOrigin: true,
rewrite: (path) => path.replace(/^\/office/, ''),
},
},
},
/**
*
*
*/
buildOptions: buildOptions,
/**
*
*
* - `@`: `src`
* - `@use-utils`: `src/utils`
* - `@use-api`: `src/axios/api`
* - `@use-images`: `src/assets/images`
*/
alias: [
{
find: '@',
replacement: path.resolve(__dirname, './src'),
},
{
find: '@use-utils',
replacement: path.resolve(__dirname, './src/utils'),
},
{
find: '@use-api',
replacement: path.resolve(__dirname, './src/axios/api'),
},
{
find: '@use-images',
replacement: path.resolve(__dirname, './src/assets/images'),
},
],
}
export default config

View File

@ -1,20 +1,40 @@
// update: 更新代码 | Update code
// fix: 修复 bug | Fix bug
// feat: 新功能 | New feature
// chore: 构建过程或辅助工具的变动 | Build process or auxiliary tool changes
// docs: 文档 | Documentation
// refactor: 重构(即不是新增功能,也不是修改 bug 的代码变动) | Refactor (i.e. code changes that are neither new features nor bug fixes)
// test: 增加测试 | Add test
// style: 代码格式(不影响功能,例如空格、分号等格式修正) | Code format (no functional impact, such as space, semicolon, etc.)
// version: 更新迭代 package.json 版本号 | Update the package.json version number
// build: 构建 | Build
// plugin: 更新插件版本 | Update plugin version
module.exports = {
ignores: [(commit) => commit.includes('init')],
extends: ['@commitlint/config-conventional'],
rules: {
'body-leading-blank': [2, 'always'],
'footer-leading-blank': [1, 'always'],
'header-max-length': [2, 'always', 108],
'subject-empty': [2, 'never'],
'type-empty': [2, 'never'],
'subject-case': [0],
'type-enum': [
2,
'always',
[
'bug',
'feat',
'update',
'fix',
'feat',
'chore',
'docs',
'style',
'refactor',
'test',
'chore',
'revert',
'merge',
'style',
'version',
'build',
'plugin',
],
],
},

14
docker-compose.yml Normal file
View File

@ -0,0 +1,14 @@
version: '3'
services:
ray-template:
build: .
container_name: ray-template
restart: unless-stopped
environment:
- TZ=Asia/Shanghai
ports:
- "9527:9527"
# if you want to persist
# volumes:
# - ./app:/app

365
eslint.config.mjs Normal file
View File

@ -0,0 +1,365 @@
import vue from 'eslint-plugin-vue'
import typescriptEslint from '@typescript-eslint/eslint-plugin'
import prettier from 'eslint-plugin-prettier'
import globals from 'globals'
import parser from 'vue-eslint-parser'
import path from 'node:path'
import { fileURLToPath } from 'node:url'
import js from '@eslint/js'
import { FlatCompat } from '@eslint/eslintrc'
const __filename = fileURLToPath(import.meta.url)
const __dirname = path.dirname(__filename)
const compat = new FlatCompat({
baseDirectory: __dirname,
recommendedConfig: js.configs.recommended,
allConfig: js.configs.all,
})
export default [
{
ignores: [
'**/node_modules/',
'**/dist/',
'dist/*',
'node_modules/*',
'**/auto-imports.d.ts',
'**/components.d.ts',
'**/.gitignore',
'**/.vscode',
'**/public',
'**/yarn.*',
'**/vite-env.*',
'**/.prettierrc.*',
'**/visualizer.*',
'**/visualizer.html',
'**/.env.*',
'src/locales/lang',
'**/.depcheckrc',
'src/app-config/echart-themes/**/*.json',
'**/*.md',
'src/icons/*.svg',
],
},
{
files: ['**/*.js', '**/*.ts', '**/*.jsx', '**/*.tsx', '**/*.vue'],
},
...compat.extends(
'eslint-config-prettier',
'eslint:recommended',
'plugin:@typescript-eslint/recommended',
'plugin:vue/vue3-recommended',
'plugin:vue/vue3-essential',
'plugin:prettier/recommended',
'prettier',
'./unplugin/.eslintrc-auto-import.json',
),
{
plugins: {
vue,
'@typescript-eslint': typescriptEslint,
prettier,
},
languageOptions: {
globals: {
...globals.browser,
...globals.node,
defineProps: 'readonly',
defineEmits: 'readonly',
defineExpose: 'readonly',
withDefaults: 'readonly',
defineOptions: 'readonly',
defineModel: 'readonly',
},
parser: parser,
ecmaVersion: 2020,
sourceType: 'module',
parserOptions: {
parser: '@typescript-eslint/parser',
ecmaFeatures: {
jsx: true,
tsx: true,
},
},
},
rules: {
'no-undefined': ['error'],
'linebreak-style': ['error', 'unix'],
'@typescript-eslint/no-explicit-any': [
'error',
{
ignoreRestArgs: true,
},
],
'prettier/prettier': 'error',
'no-unused-vars': 'off',
'@typescript-eslint/no-unused-vars': 'off',
'@typescript-eslint/ban-types': 'off',
'@typescript-eslint/explicit-module-boundary-types': 'off',
'@typescript-eslint/no-var-requires': 'off',
'@typescript-eslint/no-non-null-assertion': 'off',
'@typescript-eslint/no-non-null-asserted-optional-chain': 'off',
'@typescript-eslint/consistent-type-imports': [
'error',
{
disallowTypeAnnotations: false,
},
],
'@typescript-eslint/no-empty-interface': [
'error',
{
allowSingleExtends: true,
},
],
'accessor-pairs': 2,
'constructor-super': 0,
'default-case': 2,
eqeqeq: [2, 'allow-null'],
'no-alert': 0,
'no-array-constructor': 2,
'no-bitwise': 0,
'no-caller': 1,
'no-catch-shadow': 2,
'no-class-assign': 2,
'no-cond-assign': 2,
'no-const-assign': 2,
'no-constant-condition': 2,
'no-dupe-keys': 2,
'no-dupe-args': 2,
'no-duplicate-case': 2,
'no-eval': 1,
'no-ex-assign': 2,
'no-extend-native': 2,
'no-extra-bind': 2,
'no-extra-boolean-cast': [
'error',
{
enforceForLogicalOperands: true,
},
],
'no-extra-parens': 0,
semi: [
'error',
'never',
{
beforeStatementContinuationChars: 'always',
},
],
'no-fallthrough': 1,
'no-func-assign': 2,
'no-implicit-coercion': [
'error',
{
allow: ['!!', '~'],
},
],
'no-implied-eval': 2,
'no-invalid-regexp': 2,
'no-invalid-this': 2,
'no-irregular-whitespace': 2,
'no-iterator': 2,
'no-label-var': 2,
'no-labels': 2,
'no-lone-blocks': 2,
'no-multi-spaces': 1,
'no-multiple-empty-lines': [
'error',
{
max: 2,
},
],
'no-new-func': 2,
'no-new-object': 2,
'no-new-require': 2,
'no-sparse-arrays': 2,
'no-trailing-spaces': 1,
'no-unreachable': 2,
'no-unused-expressions': [
'error',
{
allowShortCircuit: true,
allowTernary: true,
allowTaggedTemplates: true,
enforceForJSX: true,
},
],
'no-useless-call': 2,
'no-var': 'error',
'no-with': 2,
'use-isnan': 2,
'no-multi-assign': 2,
'prefer-arrow-callback': 2,
curly: ['error', 'all'],
'vue/multi-word-component-names': [
'error',
{
ignores: [],
},
],
'vue/no-use-v-if-with-v-for': [
'error',
{
allowUsingIterationVar: false,
},
],
'vue/require-v-for-key': ['error'],
'vue/require-valid-default-prop': ['error'],
'vue/component-definition-name-casing': ['error', 'PascalCase'],
'vue/html-closing-bracket-newline': [
'error',
{
singleline: 'never',
multiline: 'always',
},
],
'vue/v-on-event-hyphenation': ['error', 'never'],
'vue/component-tags-order': [
'error',
{
order: ['template', 'script', 'style'],
},
],
'vue/no-v-html': ['error'],
'vue/no-v-text': ['error'],
'vue/component-api-style': [
'error',
['script-setup', 'composition', 'composition-vue2'],
],
'vue/component-name-in-template-casing': [
'error',
'PascalCase',
{
registeredComponentsOnly: false,
},
],
'vue/no-unused-refs': ['error'],
'vue/prop-name-casing': ['error', 'camelCase'],
'vue/component-options-name-casing': ['error', 'PascalCase'],
'vue/attribute-hyphenation': [
'error',
'never',
{
ignore: [],
},
],
'vue/no-restricted-static-attribute': [
'error',
{
key: 'key',
message: 'Disallow using key as a custom attribute',
},
],
'no-restricted-syntax': [
'error',
{
selector: "CallExpression[callee.property.name='deprecated']",
message: 'Using deprecated API is not allowed.',
},
],
'padding-line-between-statements': [
'error',
{
blankLine: 'always',
prev: ['import'],
next: '*',
},
{
blankLine: 'any',
prev: 'import',
next: 'import',
},
{
blankLine: 'always',
prev: '*',
next: 'export',
},
{
blankLine: 'any',
prev: 'export',
next: 'export',
},
{
blankLine: 'always',
prev: ['const', 'let', 'var'],
next: '*',
},
{
blankLine: 'any',
prev: ['const', 'let', 'var'],
next: ['const', 'let', 'var'],
},
{
blankLine: 'always',
prev: 'directive',
next: '*',
},
{
blankLine: 'any',
prev: 'directive',
next: 'directive',
},
{
blankLine: 'always',
prev: '*',
next: [
'if',
'class',
'for',
'do',
'while',
'switch',
'try',
'with',
'function',
'block',
'block-like',
'break',
'case',
'continue',
'return',
'throw',
'debugger',
],
},
{
blankLine: 'always',
prev: [
'if',
'class',
'for',
'do',
'while',
'switch',
'try',
'with',
'function',
'block',
'block-like',
'break',
'case',
'continue',
'return',
'throw',
'debugger',
],
next: '*',
},
],
'@typescript-eslint/no-unused-expressions': [
'error',
{
allowShortCircuit: true,
allowTernary: true,
allowTaggedTemplates: true,
},
],
'@typescript-eslint/no-empty-object-type': [
'error',
{
allowInterfaces: 'with-single-extends',
allowObjectTypes: 'always',
},
],
},
},
]

View File

@ -1,9 +1,12 @@
<!DOCTYPE html>
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="/ray.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta
name="viewport"
content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no"
/>
<title>Vite + Vue + TS</title>
</head>
<style>
@ -12,6 +15,27 @@
--preloading-title-color: <%= preloadingConfig.titleColor %>;
--ray-theme-primary-fade-color: <%= appPrimaryColor.primaryFadeColor %>;
--ray-theme-primary-color: <%= appPrimaryColor.primaryColor %>;
--global-loading-bg-color: #ffffff;
}
@media (prefers-color-scheme: dark) {
#pre-loading-animation {
background-color: var(--global-loading-bg-color);
}
}
@media (prefers-color-scheme: light) {
#pre-loading-animation {
background-color: var(--global-loading-bg-color);
}
}
html.dark #pre-loading-animation {
background-color: var(--global-loading-bg-color);
}
html.light #pre-loading-animation {
background-color: var(--global-loading-bg-color);
}
#pre-loading-animation {
@ -20,13 +44,9 @@
right: 0;
top: 0;
bottom: 0;
background-color: #ffffff;
color: var(--preloading-title-color);
text-align: center;
}
.ray-template--dark #pre-loading-animation {
background-color: #2a3146;
background-color: var(--global-loading-bg-color);
}
#pre-loading-animation .pre-loading-animation__wrapper {
@ -41,8 +61,9 @@
#pre-loading-animation
.pre-loading-animation__wrapper
.pre-loading-animation__wrapper-title {
font-size: 30px;
font-size: 32px;
padding-bottom: 48px;
font-weight: 500;
}
.pre-loading-animation__wrapper-loading {
@ -91,6 +112,18 @@
}
}
</style>
<script>
;(function () {
const html = document.documentElement
const store = window.localStorage.getItem('piniaSettingStore')
const { _appTheme = false } = store ? JSON.parse(store) : {}
const loadingBgColor = _appTheme ? '#1c1e23' : '#ffffff'
html.classList.add(_appTheme ? 'dark' : 'light')
html.style.setProperty('--global-loading-bg-color', loadingBgColor)
html.style.setProperty('background-color', loadingBgColor)
})()
</script>
<body>
<div id="app"></div>
<div id="pre-loading-animation">

42
mock/demo/person.mock.ts Normal file
View File

@ -0,0 +1,42 @@
import { defineMock } from 'vite-plugin-mock-dev-server'
import { pagination, stringify, response, array } from '@mock/shared/utils'
import { tableMock } from '@mock/shared/database'
import Mock from 'mockjs'
export const getPersonList = defineMock({
url: '/api/list',
method: 'GET',
delay: 500,
response: (req, res) => {
const {
query: { page, pageSize, email },
} = req
let list = array(100).map(() => tableMock())
let length = list.length
if (!page || !pageSize) {
res.end(
stringify(
response(list, 200, '请求成功', {
total: length,
}),
),
)
} else {
list = pagination(page, pageSize, list)
if (email) {
list = list.filter((curr) => curr.email.includes(email))
length = list.length
}
res.end(
stringify(
response(list, 200, '请求成功', {
total: length,
}),
),
)
}
},
})

19
mock/shared/database.ts Normal file
View File

@ -0,0 +1,19 @@
import Mock from 'mockjs'
/**
*
* @param option
*
*
*/
export function tableMock(option?: object) {
return {
...option,
id: Mock.Random.guid(),
address: Mock.Random.county(true),
email: Mock.Random.email(),
name: Mock.Random.cname(),
age: Mock.Random.integer(18, 60),
createDate: Mock.Random.date(),
}
}

57
mock/shared/utils.ts Normal file
View File

@ -0,0 +1,57 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
export function array(length: number) {
return new Array(length).fill(0)
}
/**
*
* @param pageCurrent
* @param pageSize
* @param array
*
* mock
*/
export function pagination<T = any>(
pageCurrent: number,
pageSize: number,
array: T[],
): T[] {
const offset = (pageCurrent - 1) * Number(pageSize)
return offset + Number(pageSize) >= array.length
? array.slice(offset, array.length)
: array.slice(offset, offset + Number(pageSize))
}
/**
*
* @param value
*
* json
*/
export function stringify<T = unknown>(value: T) {
return JSON.stringify(value)
}
/**
*
* @param res
* @param code
* @param msg
*/
export function response<T = any>(
res: T,
code: number,
msg: string,
params?: object,
) {
const basic = {
code,
msg,
data: res,
...params,
}
return basic
}

173
package.json Normal file → Executable file
View File

@ -1,93 +1,113 @@
{
"name": "ray-template",
"private": false,
"version": "4.1.3",
"version": "5.2.2",
"type": "module",
"engines": {
"node": "^18.0.0 || ^20.0.0 || >=22.0.0",
"pnpm": ">=9.0.0"
},
"scripts": {
"dev": "vite",
"build": "vue-tsc --noEmit && vite build --mode production",
"build": "vue-tsc --noEmit && vite build",
"preview": "vite preview",
"test": "vue-tsc --noEmit && vite build --mode test",
"dev-build": "vue-tsc --noEmit && vite build --mode development",
"report": "vue-tsc --noEmit && vite build --mode report",
"prepare": "husky install"
"report": "vite build --mode report",
"prepare": "husky install",
"test": "vitest",
"test:ui": "vitest --ui",
"lint": "vue-tsc --noEmit && eslint --fix && prettier --write \"**/*.{ts,tsx,json,.vue}\""
},
"husky": {
"hooks": {
"pre-commit": "lint-staged",
"commit-msg": "commitlint -E HUSKY_GIT_PARAMS"
}
},
"lint-staged": {
"src/**/*.{vue,jsx,ts,tsx,json}": [
"prettier --write",
"eslint",
"git add"
"*.{ts,tsx,json}": [
"prettier --write"
],
"*.{ts,tsx,vue}": [
"eslint --fix"
]
},
"dependencies": {
"@vueuse/core": "^9.1.0",
"axios": "^1.2.0",
"@logicflow/core": "2.0.10",
"@logicflow/extension": "2.0.14",
"@vueuse/core": "^13.1.0",
"axios": "^1.9.0",
"clipboard": "^2.0.11",
"crypto-js": "^4.1.1",
"crypto-js": "4.2.0",
"currency.js": "^2.0.4",
"dayjs": "^1.11.7",
"echarts": "^5.4.0",
"dayjs": "^1.11.13",
"echarts": "^5.6.0",
"html-to-image": "1.11.13",
"interactjs": "1.10.27",
"jsbarcode": "3.11.6",
"lodash-es": "^4.17.21",
"naive-ui": "^2.34.4",
"pinia": "^2.1.4",
"pinia-plugin-persistedstate": "^3.1.0",
"mockjs": "1.1.0",
"naive-ui": "^2.42.0",
"pinia": "^3.0.3",
"pinia-plugin-persistedstate": "^4.4.1",
"print-js": "^1.6.0",
"qrcode.vue": "^3.3.4",
"sass": "^1.54.3",
"screenfull": "^6.0.2",
"vue": "^3.3.4",
"vue-hooks-plus": "1.7.6",
"vue-i18n": "^9.2.2",
"vue-router": "^4.2.4",
"vuedraggable": "^4.1.0",
"xlsx": "^0.18.5"
"vue": "^3.5.17",
"vue-demi": "0.14.10",
"vue-hooks-plus": "2.4.0",
"vue-i18n": "^9.13.1",
"vue-router": "^4.5.1",
"vue3-next-qrcode": "3.0.2"
},
"devDependencies": {
"@babel/core": "^7.20.2",
"@babel/eslint-parser": "^7.19.1",
"@commitlint/cli": "^17.4.2",
"@commitlint/config-conventional": "^17.4.2",
"@intlify/unplugin-vue-i18n": "^0.12.1",
"@types/crypto-js": "^4.1.1",
"@types/lodash-es": "^4.17.7",
"@types/scrollreveal": "^0.0.8",
"@typescript-eslint/eslint-plugin": "^5.61.0",
"@typescript-eslint/parser": "^5.61.0",
"@vitejs/plugin-vue": "^4.2.3",
"@vitejs/plugin-vue-jsx": "^3.0.1",
"@vue-hooks-plus/resolvers": "1.2.4",
"@vue/eslint-config-prettier": "^7.1.0",
"@vue/eslint-config-typescript": "^11.0.3",
"autoprefixer": "^10.4.8",
"depcheck": "^1.4.3",
"eslint": "^8.44.0",
"eslint-config-prettier": "^8.8.0",
"eslint-config-standard-with-typescript": "^23.0.0",
"eslint-plugin-import": "^2.25.2",
"eslint-plugin-n": "^15.0.0",
"eslint-plugin-prettier": "^4.2.1",
"eslint-plugin-promise": "^6.0.0",
"eslint-plugin-react": "^7.31.10",
"eslint-plugin-vue": "^9.15.1",
"husky": "^8.0.3",
"lint-staged": "^13.1.0",
"postcss": "^8.1.0",
"postcss-px-to-viewport": "^1.1.1",
"prettier": "^2.7.1",
"rollup-plugin-visualizer": "^5.8.3",
"svg-sprite-loader": "^6.0.11",
"typescript": "^5.0.2",
"unplugin-auto-import": "^0.15.0",
"unplugin-vue-components": "^0.25.1",
"vite": "^4.3.9",
"vite-plugin-compression": "^0.5.1",
"vite-plugin-ejs": "^1.6.4",
"vite-plugin-eslint": "^1.8.1",
"vite-plugin-imp": "^2.3.1",
"vite-plugin-inspect": "^0.7.26",
"vite-plugin-svg-icons": "^2.0.1",
"vite-svg-loader": "^3.4.0",
"vue-tsc": "^1.8.4"
"@commitlint/cli": "19.7.1",
"@commitlint/config-conventional": "19.7.1",
"@eslint/js": "9.28.0",
"@interactjs/types": "1.10.27",
"@intlify/unplugin-vue-i18n": "4.0.0",
"@types/crypto-js": "4.2.2",
"@types/jsbarcode": "3.11.4",
"@types/lodash-es": "4.17.12",
"@types/mockjs": "1.0.10",
"@typescript-eslint/eslint-plugin": "8.24.0",
"@typescript-eslint/parser": "8.24.0",
"@vitejs/plugin-vue": "5.2.3",
"@vitejs/plugin-vue-jsx": "4.1.2",
"@vitest/ui": "2.1.8",
"@vue/eslint-config-prettier": "10.1.0",
"@vue/eslint-config-typescript": "14.2.0",
"@vue/test-utils": "2.4.6",
"autoprefixer": "10.4.21",
"depcheck": "1.4.7",
"eslint": "9.20.1",
"eslint-config-prettier": "10.1.2",
"eslint-plugin-prettier": "5.2.6",
"eslint-plugin-vue": "9.32.0",
"globals": "16.0.0",
"happy-dom": "17.1.0",
"husky": "8.0.3",
"lint-staged": "15.4.3",
"postcss": "8.5.4",
"postcss-px-to-viewport-8-with-include": "1.2.2",
"prettier": "3.5.3",
"rollup-plugin-gzip": "4.0.1",
"sass": "1.86.3",
"svg-sprite-loader": "6.0.11",
"typescript": "5.8.3",
"unocss": "66.3.3",
"unplugin-auto-import": "19.1.2",
"unplugin-vue-components": "0.28.0",
"vite": "6.3.5",
"vite-bundle-analyzer": "0.16.0",
"vite-plugin-cdn2": "1.1.0",
"vite-plugin-ejs": "1.7.0",
"vite-plugin-eslint": "1.8.1",
"vite-plugin-inspect": "0.8.4",
"vite-plugin-mock-dev-server": "1.8.3",
"vite-plugin-svg-icons": "2.0.1",
"vite-svg-loader": "5.1.0",
"vitest": "2.1.8",
"vue-eslint-parser": "9.4.3",
"vue-tsc": "2.2.8"
},
"description": "<!-- ALL-CONTRIBUTORS-BADGE:START - Do not remove or modify this section -->",
"main": "index.ts",
@ -97,15 +117,18 @@
},
"keywords": [
"ray-template",
"vue3.2模板",
"vue3-tsx-vite-pinia",
"ray template",
"Ray Template",
"vite",
"vue3",
"admin template",
"中后台模板"
],
"author": "Ray",
"license": "ISC",
"license": "MIT",
"author": {
"name": "Ray",
"url": "https://github.com/XiaoDaiGua-Ray"
},
"bugs": {
"url": "https://github.com/XiaoDaiGua-Ray/xiaodaigua-ray.github.io/issues"
},

10101
pnpm-lock.yaml generated Normal file

File diff suppressed because it is too large Load Diff

View File

@ -8,24 +8,32 @@ module.exports = {
'ff > 31',
'ie >= 8',
'last 10 versions',
'not dead',
],
grid: true,
},
// 'postcss-px-to-viewport': {
// /** 视窗的宽度(设计稿的宽度) */
// viewportWidth: 1920,
// /** 视窗的高度(设计稿高度, 一般无需指定) */
// viewportHeight: 1080,
// /** 指定 px 转换为视窗单位值的小数位数 */
// unitPrecision: 3,
// /** 指定需要转换成的视窗单位 */
// viewportUnit: 'vw',
// /** 指定不转换为视窗单位的类 */
// selectorBlackList: ['.ignore'],
// /** 小于或等于 1px 不转换为视窗单位 */
// minPixelValue: 1,
// /** 允许在媒体查询中转换 px */
// mediaQuery: false,
// },
// 为了适配 postcss8.x 版本的转换库
'postcss-px-to-viewport-8-with-include': {
// 横屏时使用的视口宽度
landscapeWidth: 1920,
// 视窗的宽度(设计稿的宽度)
viewportWidth: 1920,
// 指定 px 转换为视窗单位值的小数位数
unitPrecision: 3,
// 指定需要转换成的视窗单位
viewportUnit: 'vw',
// 制定字体转换单位
fontViewportUnit: 'vw',
// 指定不转换为视窗单位的类
selectorBlackList: ['.ignore'],
// 小于或等于 1px 不转换为视窗单位
minPixelValue: 1,
// 允许在媒体查询中转换 px
mediaQuery: false,
// 忽略某些文件夹下的文件或特定文件,例如 'node_modules' 下的文件
exclude: /node_modules/,
// 指定一个空的文件夹,避免影响到无需转换的文件
include: [],
},
},
}

View File

@ -1,26 +1,29 @@
import { RouterView } from 'vue-router'
import AppNaiveGlobalProvider from '@/app-components/provider/AppNaiveGlobalProvider/index'
import AppStyleProvider from '@/app-components/provider/AppStyleProvider/index'
import GlobalSpin from '@/spin/index'
import LockScreen from '@/app-components/app/AppLockScreen/index'
import AppNaiveGlobalProvider from '@/app-components/provider/AppNaiveGlobalProvider'
import AppStyleProvider from '@/app-components/provider/AppStyleProvider'
import AppLockScreen from '@/app-components/app/AppLockScreen'
import AppWatermarkProvider from '@/app-components/provider/AppWatermarkProvider'
import AppGlobalSpin from '@/app-components/app/AppGlobalSpin'
import AppVersionProvider from '@/app-components/provider/AppVersionProvider'
const App = defineComponent({
import { APP_GLOBAL_LOADING } from '@/app-config'
export default defineComponent({
name: 'App',
render() {
return (
<AppNaiveGlobalProvider>
<LockScreen />
<AppVersionProvider />
<AppLockScreen />
<AppStyleProvider />
<GlobalSpin>
<AppWatermarkProvider />
<AppGlobalSpin>
{{
default: () => <RouterView />,
description: () => 'lodaing...',
description: () => APP_GLOBAL_LOADING,
}}
</GlobalSpin>
</AppGlobalSpin>
</AppNaiveGlobalProvider>
)
},
})
export default App

View File

@ -0,0 +1,14 @@
# \_\_ray-template
该包用于管理一些模板的管理,例如:
- validAppRootPath: 检查模板 `appRootPath` 是否配置正确
- validLocal: 检查模板 `localConfig` 是否配置正确
## 拓展
当你需要在做一些定制化操作的时候,可以尝试在这个包里做一些事情。
最后在 `main.ts` 中导入并且调用即可。
> 出于一些考虑,并没有做自动化导入调用,所以需要自己手动来。(好吧,其实就是我懒--

View File

@ -0,0 +1,16 @@
import { validAppRootPath } from './valid/valid-app-root-path'
import { validLocal } from './valid/valid-local'
/**
*
* @description
* ray-template
*/
export const setupRayTemplateCore = async () => {
if (!__DEV__) {
return
}
await validAppRootPath()
await validLocal()
}

View File

@ -0,0 +1,28 @@
import { useSettingGetters } from '@/store'
import { useVueRouter } from '@/hooks'
/**
*
* @description
* appRootRoute.path
*
* : [src/store/modules/setting/index.ts] appRootRoute
*
* getRoutes
*/
export const validAppRootPath = async () => {
const { getAppRootRoute } = useSettingGetters()
const {
router: { getRoutes },
} = useVueRouter()
const find = getRoutes().find(
(curr) => curr.path === getAppRootRoute.value.path,
)
if (!find) {
throw new Error(
`[validAppRootPath]: 'store setting appRootRoute path: ' '${getAppRootRoute.value.path}' not found in router, please check the 'appRootRoute' setting in the store setting module.`,
)
}
}

View File

@ -0,0 +1,99 @@
import {
LOCAL_OPTIONS,
SYSTEM_DEFAULT_LOCAL,
SYSTEM_FALLBACK_LOCALE,
DAYJS_LOCAL_MAP,
DEFAULT_DAYJS_LOCAL,
} from '@/app-config'
/**
*
* @description
* LOCAL_OPTIONS key
*/
const getLocalOptionKeys = () => {
return LOCAL_OPTIONS.map((curr) => curr.key)
}
/**
*
* @description
* SYSTEM_DEFAULT_LOCAL LOCAL_OPTIONS
*/
const validSystemDefaultLocal = () => {
const localOptionKeys = getLocalOptionKeys()
if (!localOptionKeys.includes(SYSTEM_DEFAULT_LOCAL)) {
throw new Error(
`[validLocal validSystemDefaultLocal:] SYSTEM_DEFAULT_LOCAL: '${SYSTEM_DEFAULT_LOCAL}' is not in LOCAL_OPTIONS: [${localOptionKeys.join(
', ',
)}]`,
)
}
}
/**
*
* @description
* SYSTEM_FALLBACK_LOCALE LOCAL_OPTIONS
*/
const validSystemFallbackLocale = () => {
const localOptionKeys = getLocalOptionKeys()
if (!localOptionKeys.includes(SYSTEM_FALLBACK_LOCALE)) {
throw new Error(
`[validLocal validSystemFallbackLocale:] SYSTEM_FALLBACK_LOCALE: '${SYSTEM_FALLBACK_LOCALE}' is not in LOCAL_OPTIONS: [${localOptionKeys.join(
', ',
)}]`,
)
}
}
/**
*
* @description
* DAYJS_LOCAL_MAP LOCAL_OPTIONS
*/
const validDayjsLocalMap = () => {
const localOptionKeys = getLocalOptionKeys() as string[]
const dayjsLocalKeys = Object.keys(DAYJS_LOCAL_MAP)
dayjsLocalKeys.forEach((key) => {
if (!localOptionKeys.includes(key)) {
throw new Error(
`[validLocal validDayjsLocalMap:] DAYJS_LOCAL_MAP: '${key}' is not in LOCAL_OPTIONS: [${localOptionKeys.join(
', ',
)}]`,
)
}
})
}
/**
*
* @description
* DEFAULT_DAYJS_LOCAL DAYJS_LOCAL_MAP
*/
const validDefaultDayjsLocal = () => {
const dayjsLocalKeys = Object.values(DAYJS_LOCAL_MAP)
if (!dayjsLocalKeys.includes(DEFAULT_DAYJS_LOCAL)) {
throw new Error(
`[validLocal validDefaultDayjsLocal:] DEFAULT_DAYJS_LOCAL: '${DEFAULT_DAYJS_LOCAL}' is not in DAYJS_LOCAL_MAP: [${dayjsLocalKeys.join(
', ',
)}]`,
)
}
}
/**
*
* @description
* localConfig
*/
export const validLocal = async () => {
validSystemDefaultLocal()
validSystemFallbackLocale()
validDayjsLocalMap()
validDefaultDayjsLocal()
}

View File

@ -0,0 +1,27 @@
import { request } from '@/axios'
import type { PaginationResponse } from '@/types'
export interface MockListParams {
page: number
pageSize: number
}
export interface Person {
name: string
id: string
age: number
address: string
createDate: string
email: string | null
}
export const getPersonList = (
params: Partial<Pick<MockListParams & Person, 'email' | 'pageSize' | 'page'>>,
) => {
return request<PaginationResponse<Person[]>>({
url: '/api/list',
method: 'get',
params,
})
}

View File

@ -1,14 +1,3 @@
/**
*
* @author Ray <https://github.com/XiaoDaiGua-Ray>
*
* @date 2023-03-31
*
* @workspace ray-template
*
* @remark
*/
/**
*
*
@ -19,9 +8,9 @@
* 3. setup 使使 useHookPlusRequest 便使 setup 使使
*/
import { request } from '@/axios/index'
import { request } from '@/axios'
import type { AxiosResponseBody } from '@/types/modules/axios'
import type { BasicResponse } from '@/types'
interface AxiosTestResponse extends UnknownObjectKey {
data: UnknownObjectKey[]
@ -39,7 +28,7 @@ interface JSONPlaceholder {
*
* @returns
*
* @medthod get
* @method get
*/
export const getWeather = (city: string) => {
return request<AxiosTestResponse>({
@ -48,7 +37,7 @@ export const getWeather = (city: string) => {
})
}
export const getTypicode = () => {
export const getTypicCode = () => {
return request<JSONPlaceholder>({
url: 'https://jsonplaceholder.typicode.com/todos/1',
method: 'get',

View File

@ -3,4 +3,4 @@
该包存放与模板深度绑定的组件:
- app存放与模板数据绑定的组件
- sys:存放模板注入类组件
- provider:存放模板注入类组件

View File

@ -1,14 +1,3 @@
/**
*
* @author Ray <https://github.com/XiaoDaiGua-Ray>
*
* @date 2023-05-31
*
* @workspace ray-template
*
* @remark
*/
/**
*
*
@ -17,70 +6,65 @@
* session catch
*/
import './index.scss'
import { NAvatar, NButton, NFlex } from 'naive-ui'
import { NAvatar, NSpace } from 'naive-ui'
import { avatarProps, spaceProps } from 'naive-ui'
import { APP_CATCH_KEY } from '@/appConfig/appConfig'
import { getStorage } from '@/utils/cache'
import { avatarProps } from 'naive-ui'
import { useSigningGetters } from '@/store'
import type { PropType } from 'vue'
import type { AvatarProps, SpaceProps } from 'naive-ui'
import type { SigninCallback } from '@/store/modules/signin/type'
import type { AvatarProps, FlexProps } from 'naive-ui'
const AppAvatar = defineComponent({
name: 'AppAvatar',
props: {
...avatarProps,
...spaceProps,
cursor: {
type: String,
default: 'auto',
},
spaceSize: {
type: [String, Number] as PropType<SpaceProps['size']>,
type: [String, Number, Array] as PropType<FlexProps['size']>,
default: 'medium',
},
avatarSize: {
type: [String, Number] as PropType<AvatarProps['size']>,
default: 'medium',
},
vertical: {
type: Boolean,
default: false,
},
},
setup(props) {
const signin = getStorage<SigninCallback>(APP_CATCH_KEY.signin)
const cssVars = computed(() => {
const vars = {
'--app-avatar-cursor': props.cursor,
}
return vars
})
setup() {
const { getSigningCallback } = useSigningGetters()
return {
signin,
cssVars,
getSigningCallback,
}
},
render() {
const { getSigningCallback, avatarSize, spaceSize, $props, vertical } = this
return (
<NSpace
class="app-avatar"
{...this.$props}
wrapItem={false}
style={this.cssVars}
size={this.spaceSize}
>
<NAvatar
// eslint-disable-next-line prettier/prettier, @typescript-eslint/no-explicit-any
{...(this.$props as any)}
src={this.signin?.avatar}
objectFit="cover"
round
size={this.avatarSize}
/>
<div class="app-avatar__name">{this.signin?.name}</div>
</NSpace>
<NButton quaternary strong focusable={false}>
<NFlex align="center" size={spaceSize} vertical={vertical}>
<NAvatar
{...($props as AvatarProps)}
src={getSigningCallback?.avatar}
objectFit="cover"
round
size={avatarSize}
>
{{
default: () =>
getSigningCallback.avatar
? null
: getSigningCallback?.name?.[0],
}}
</NAvatar>
{getSigningCallback?.name}
</NFlex>
</NButton>
)
},
})

View File

@ -1,14 +1,3 @@
/**
*
* @author Ray <https://github.com/XiaoDaiGua-Ray>
*
* @date 2023-01-18
*
* @workspace ray-template
*
* @remark
*/
/**
*
*
@ -16,8 +5,8 @@
* Naive UI Spin
*
* 使
* 1. import { setSpin } from '@/spin'
* 2. setSpin(true) | setSpin(false)
* 1. import { setVariable } from '@/hooks'
* 2. setVariable('globalSpinning', true) | setVariable('globalSpinning', false)
*
*
*
@ -29,9 +18,9 @@
import { NSpin } from 'naive-ui'
import { spinProps } from 'naive-ui'
import { spinValue } from './hook'
import { getVariableToRefs } from '@/global-variable'
export { setSpin } from './hook'
import type { SpinProps } from 'naive-ui'
const GlobalSpin = defineComponent({
name: 'GlobalSpin',
@ -42,6 +31,7 @@ const GlobalSpin = defineComponent({
const overrides = {
opacitySpinning: '0.3',
}
const spinValue = getVariableToRefs('globalSpinning')
return {
spinValue,
@ -51,11 +41,14 @@ const GlobalSpin = defineComponent({
render() {
return (
<NSpin
{...this.$props}
{...(this.$props as SpinProps)}
show={this.spinValue}
themeOverrides={this.overrides}
style="height: var(--html-height)"
>
{{ ...this.$slots }}
{{
...this.$slots,
}}
</NSpin>
)
},

View File

@ -1,24 +1,14 @@
/**
*
* @author Ray <https://github.com/XiaoDaiGua-Ray>
*
* @date 2023-06-20
*
* @workspace ray-template
*
* @remark
*/
import { useStorage } from '@vueuse/core'
import { APP_CATCH_KEY } from '@/app-config'
/**
*
*
*
* ,
*/
const appLockScreen = useStorage('isAppLockScreen', false, sessionStorage, {
mergeDefaults: true,
})
const appLockScreen = useStorage(
APP_CATCH_KEY.isAppLockScreen,
false,
window.localStorage,
{
mergeDefaults: true,
},
)
const useAppLockScreen = () => {
const setLockAppScreen = (bool: boolean) => {

View File

@ -1,47 +1,41 @@
/**
*
* @author Ray <https://github.com/XiaoDaiGua-Ray>
*
* @date 2023-06-20
*
* @workspace ray-template
*
* @remark
*/
import { NInput, NFormItem, NButton } from 'naive-ui'
import AppAvatar from '@/app-components/app/AppAvatar'
import { RForm } from '@/components'
/** 锁屏界面 */
import { NInput, NForm, NFormItem, NButton, NSpace } from 'naive-ui'
import AppAvatar from '@/app-components/app/AppAvatar/index'
import { useSetting } from '@/store'
import useAppLockScreen from '@/app-components/app/AppLockScreen/appLockVar'
import { rules, useCondition } from '@/app-components/app/AppLockScreen/hook'
import { rules, useCondition } from '@/app-components/app/AppLockScreen/shared'
import { useSettingActions } from '@/store'
import { useTemplateRef } from 'vue'
import { useForm } from '@/components'
import { APP_CATCH_KEY } from '@/app-config'
import { setStorage, encrypt } from '@/utils'
import type { FormInst, InputInst } from 'naive-ui'
import type { InputInst } from 'naive-ui'
const LockScreen = defineComponent({
name: 'LockScreen',
setup() {
const formInstRef = ref<FormInst | null>(null)
const inputInstRef = ref<InputInst | null>(null)
const [register, { validate }] = useForm()
const inputInstRef = useTemplateRef<InputInst | null>('inputInstRef')
const { setLockAppScreen } = useAppLockScreen()
const { changeSwitcher } = useSetting()
const { updateSettingState } = useSettingActions()
const state = reactive({
lockCondition: useCondition(),
})
/** 锁屏 */
const lockScreen = () => {
formInstRef.value?.validate((error) => {
if (!error) {
setLockAppScreen(true)
changeSwitcher(true, 'lockScreenSwitch')
validate().then(() => {
setLockAppScreen(true)
updateSettingState('lockScreenSwitch', false)
setStorage(
APP_CATCH_KEY.appLockScreenPasswordKey,
encrypt(state.lockCondition.lockPassword),
'localStorage',
)
state.lockCondition = useCondition()
}
state.lockCondition = useCondition()
})
}
@ -54,35 +48,51 @@ const LockScreen = defineComponent({
return {
...toRefs(state),
lockScreen,
formInstRef,
register,
inputInstRef,
}
},
render() {
const { register } = this
return (
<div class="app-lock-screen__input">
<AppAvatar vertical align="center" avatarSize={52} />
<NForm
ref="formInstRef"
model={this.lockCondition}
rules={rules}
labelPlacement="left"
>
<NFormItem path="lockPassword">
<NInput
ref="inputInstRef"
v-model:value={this.lockCondition.lockPassword}
type="password"
placeholder="请输入锁屏密码"
clearable
minlength={6}
maxlength={12}
/>
</NFormItem>
<NButton type="primary" onClick={this.lockScreen.bind(this)}>
</NButton>
</NForm>
<div class="app-lock-screen__content">
<div class="app-lock-screen__input">
<AppAvatar
avatarSize={52}
style="pointer-events: none;margin: 24px 0;"
vertical
/>
<RForm
ref="formInstRef"
model={this.lockCondition}
rules={rules}
labelPlacement="left"
onRegister={register}
>
<NFormItem path="lockPassword">
<NInput
ref="inputInstRef"
v-model:value={this.lockCondition.lockPassword}
type="password"
placeholder="请输入锁屏密码"
clearable
showPasswordOn="click"
minlength={6}
maxlength={12}
onKeydown={(e: KeyboardEvent) => {
if (e.code === 'Enter') {
this.lockScreen()
}
}}
autofocus
/>
</NFormItem>
<NButton type="primary" onClick={this.lockScreen.bind(this)}>
</NButton>
</RForm>
</div>
</div>
)
},

View File

@ -1,45 +1,37 @@
/**
*
* @author Ray <https://github.com/XiaoDaiGua-Ray>
*
* @date 2023-06-20
*
* @workspace ray-template
*
* @remark
*/
import '../../index.scss'
/** 解锁界面 */
import { NInput, NForm, NFormItem, NButton, NSpace } from 'naive-ui'
import AppAvatar from '@/app-components/app/AppAvatar/index'
import { NInput, NFormItem, NButton, NFlex } from 'naive-ui'
import AppAvatar from '@/app-components/app/AppAvatar'
import { RForm } from '@/components'
import dayjs from 'dayjs'
import { useSetting, useSignin } from '@/store'
import { rules, useCondition } from '@/app-components/app/AppLockScreen/hook'
import { useSigningActions, useSettingActions } from '@/store'
import { rules, useCondition } from '@/app-components/app/AppLockScreen/shared'
import useAppLockScreen from '@/app-components/app/AppLockScreen/appLockVar'
import { useDevice } from '@/hooks'
import { useForm } from '@/components'
import { APP_CATCH_KEY } from '@/app-config'
import { removeStorage, decrypt, getStorage } from '@/utils'
import type { FormInst, InputInst } from 'naive-ui'
const UnlockScreen = defineComponent({
export default defineComponent({
name: 'UnlockScreen',
setup() {
const formRef = ref<FormInst | null>(null)
const inputInstRef = ref<InputInst | null>(null)
const [register, { validate }] = useForm()
const { logout } = useSignin()
const { changeSwitcher } = useSetting()
const { logout } = useSigningActions()
const { updateSettingState } = useSettingActions()
const { setLockAppScreen } = useAppLockScreen()
const { isTabletOrSmaller } = useDevice()
const HH_MM_FORMAT = 'HH:mm'
const AM_PM_FORMAT = 'A'
const YY_MM_DD_FORMAT = 'YY年MM月DD日'
const YY_MM_DD_FORMAT = 'YYYY-MM-DD'
const DDD_FORMAT = 'ddd'
const state = reactive({
lockCondition: useCondition(),
HH_MM: dayjs().format(HH_MM_FORMAT),
AM_PM: dayjs().locale('en').format(AM_PM_FORMAT),
AM_PM: dayjs().format(AM_PM_FORMAT),
YY_MM_DD: dayjs().format(YY_MM_DD_FORMAT),
DDD: dayjs().format(DDD_FORMAT),
})
@ -52,30 +44,55 @@ const UnlockScreen = defineComponent({
state.DDD = dayjs().format(DDD_FORMAT)
}, 86_400_000)
/** 退出登陆并且回到登陆页 */
const backToSignin = () => {
const toSigningFn = () => {
removeStorage(APP_CATCH_KEY.appLockScreenPasswordKey, 'localStorage')
updateSettingState('lockScreenSwitch', false)
setTimeout(() => {
logout()
}, 100)
}
const backToSigning = () => {
window.$dialog.warning({
title: '警告',
content: '是否返回到登陆页?',
content: '是否返回到登陆页并且重新登录',
positiveText: '确定',
negativeText: '取消',
onPositiveClick: () => {
logout()
setTimeout(() => {
changeSwitcher(false, 'lockScreenSwitch')
})
},
negativeText: '重新登录',
onPositiveClick: toSigningFn,
})
}
/** 解锁 */
const unlockScreen = () => {
formRef.value?.validate((error) => {
if (!error) {
const catchPassword = getStorage<string>(
APP_CATCH_KEY.appLockScreenPasswordKey,
'localStorage',
)
if (!catchPassword) {
window.$dialog.warning({
title: '警告',
content: () => '检测到锁屏密码被修改,请重新登录',
closable: false,
maskClosable: false,
closeOnEsc: false,
positiveText: '重新登录',
onPositiveClick: toSigningFn,
})
return
}
const dCatchPassword = decrypt(catchPassword)
validate().then(() => {
if (dCatchPassword === state.lockCondition.lockPassword) {
setLockAppScreen(false)
changeSwitcher(false, 'lockScreenSwitch')
updateSettingState('lockScreenSwitch', false)
removeStorage(APP_CATCH_KEY.appLockScreenPasswordKey, 'localStorage')
state.lockCondition = useCondition()
} else {
window.$message.warning('密码错误,请重新输入')
}
})
}
@ -87,60 +104,86 @@ const UnlockScreen = defineComponent({
return {
...toRefs(state),
backToSignin,
backToSigning,
unlockScreen,
formRef,
inputInstRef,
isTabletOrSmaller,
register,
}
},
render() {
const { isTabletOrSmaller } = this
const { HH_MM, AM_PM, YY_MM_DD, DDD } = this
const hmSplit = HH_MM.split(':')
const { unlockScreen, backToSigning, register } = this
return (
<div class="app-lock-screen__unlock">
<div class="app-lock-screen__unlock__content">
<div class="app-lock-screen__unlock__content-bg">
<div class="left">{this.HH_MM?.split(':')[0]}</div>
<div class="right">{this.HH_MM?.split(':')[1]}</div>
</div>
<div class="app-lock-screen__unlock__content-avatar">
<AppAvatar vertical align="center" avatarSize={52} />
</div>
<div class="app-lock-screen__unlock__content-input">
<NForm ref="formRef" model={this.lockCondition} rules={rules}>
<NFormItem path="lockPassword">
<NInput
ref="inputInstRef"
v-model:value={this.lockCondition.lockPassword}
type="password"
placeholder="请输入解锁密码"
clearable
minlength={6}
maxlength={12}
/>
</NFormItem>
<NSpace justify="space-between">
<NButton
type="primary"
text
onClick={this.backToSignin.bind(this)}
>
</NButton>
<NButton
type="primary"
text
onClick={this.unlockScreen.bind(this)}
>
</NButton>
</NSpace>
</NForm>
</div>
<div class="app-lock-screen__unlock__content-date">
<div class="current-date">
{this.HH_MM}&nbsp;<span>{this.AM_PM}</span>
<div class="app-lock-screen__content app-lock-screen__content--full">
<div class="app-lock-screen__unlock">
<div class="app-lock-screen__unlock__content">
<div class="app-lock-screen__unlock__content-wrapper">
<div
class={[
'app-lock-screen__unlock__content-bg__wrapper',
'app-lock-screen__unlock__content-bg',
isTabletOrSmaller
? 'app-lock-screen__unlock__content-bg--smaller'
: '',
]}
>
<div class="left">{hmSplit[0]}</div>
<div class="right">{hmSplit[1]}</div>
</div>
</div>
<div class="current-year">
{this.YY_MM_DD}&nbsp;<span>{this.DDD}</span>
<div class="app-lock-screen__unlock__content-avatar">
<AppAvatar
avatarSize={52}
style="pointer-events: none;"
vertical
/>
</div>
<div class="app-lock-screen__unlock__content-input">
<RForm
onRegister={register}
model={this.lockCondition}
rules={rules}
>
<NFormItem path="lockPassword">
<NInput
autofocus
v-model:value={this.lockCondition.lockPassword}
type="password"
placeholder="请输入解锁密码"
clearable
minlength={6}
onKeydown={(e: KeyboardEvent) => {
if (e.code === 'Enter') {
unlockScreen()
}
}}
/>
</NFormItem>
<NFlex justify="space-between">
<NButton
type="primary"
text
onClick={backToSigning.bind(this)}
>
</NButton>
<NButton
type="primary"
text
onClick={unlockScreen.bind(this)}
>
</NButton>
</NFlex>
</RForm>
</div>
<div class="app-lock-screen__unlock__content-date">
<div class="current-year">
{YY_MM_DD}&nbsp;<span>{DDD}</span>&nbsp;<span>{AM_PM}</span>
</div>
</div>
</div>
</div>
@ -148,5 +191,3 @@ const UnlockScreen = defineComponent({
)
},
})
export default UnlockScreen

View File

@ -1,10 +1,16 @@
.app-lock-screen__content {
&.app-lock-screen__content--full {
width: 100%;
height: var(--html-height);
@include flexCenter;
}
& .app-lock-screen__input {
& button[class*="n-button"] {
& button[class*='n-button'] {
width: 100%;
}
& form[class*="n-form"] {
& form[class*='n-form'] {
margin: 24px 0px;
}
}
@ -12,35 +18,54 @@
& .app-lock-screen__unlock {
.app-lock-screen__unlock__content {
position: relative;
width: 100%;
height: 100%;
@include flexCenter;
flex-direction: column;
& .app-lock-screen__unlock__content-bg {
position: absolute;
width: 100%;
height: 100%;
@include flexCenter;
font-size: 220px;
gap: 80px;
z-index: 0;
& .app-lock-screen__unlock__content-wrapper {
position: fixed;
inset: 0px;
& .left,
& .right {
& .app-lock-screen__unlock__content-bg__wrapper {
width: 100%;
height: 100%;
background-color: rgb(16, 16, 20);
}
& .app-lock-screen__unlock__content-bg {
position: absolute;
width: 100%;
height: 100%;
@include flexCenter;
border-radius: 30px;
background-color: #141313;
font-weight: 700;
padding: 80px;
filter: blur(4px);
font-size: 16.67rem;
gap: 80px;
z-index: 0;
&.app-lock-screen__unlock__content-bg--smaller {
& .left,
& .right {
padding: 0px;
font-size: 90px;
padding: 24px;
border-radius: 4px;
}
}
& .left,
& .right {
@include flexCenter;
border-radius: 30px;
background-color: #141313;
font-weight: 700;
padding: 80px;
filter: blur(4px);
}
}
}
& .app-lock-screen__unlock__content-avatar {
margin-top: 5px;
color: #bababa;
font-weight: 500;
font-weight: bolder;
z-index: 1;
}
@ -60,9 +85,23 @@
& .current-year,
& .current-date span {
font-size: 1.5rem;
font-size: 1.875rem;
line-height: 2.25rem;
}
}
}
}
}
.ray-template--light {
.app-lock-screen__unlock__content-bg__wrapper {
background-color: #fff !important;
}
.app-lock-screen__unlock__content-bg {
& .left,
& .right {
background-color: rgba(244, 244, 245, 1) !important;
}
}
}

View File

@ -1,57 +1,38 @@
/**
*
* @author Ray <https://github.com/XiaoDaiGua-Ray>
*
* @date 2023-05-13
*
* @workspace ray-template
*
* @remark
*/
/**
*
* ,
*
*/
import './index.scss'
import { NModal } from 'naive-ui'
import { RModal } from '@/components'
import LockScreen from './components/LockScreen'
import UnlockScreen from './components/UnlockScreen'
import { useSetting } from '@/store'
import useAppLockScreen from '@/app-components/app/AppLockScreen/appLockVar'
import { useSettingGetters, useSettingActions } from '@/store'
const AppLockScreen = defineComponent({
name: 'AppLockScreen',
setup() {
const settingStore = useSetting()
const { lockScreenSwitch } = storeToRefs(settingStore)
const { getLockAppScreen } = useAppLockScreen()
const { updateSettingState } = useSettingActions()
const { getLockScreenSwitch } = useSettingGetters()
const lockScreenSwitchRef = computed({
get: () => getLockScreenSwitch.value,
set: (val) => {
updateSettingState('lockScreenSwitch', val)
},
})
return {
lockScreenSwitch,
getLockAppScreen,
lockScreenSwitchRef,
}
},
render() {
return (
<NModal
v-model:show={this.lockScreenSwitch}
<RModal
v-model:show={this.lockScreenSwitchRef}
transformOrigin="center"
show
autoFocus={false}
maskClosable={false}
closeOnEsc={false}
preset={!this.getLockAppScreen() ? 'dialog' : undefined}
preset="dialog"
title="锁定屏幕"
>
<div class="app-lock-screen__content">
{!this.getLockAppScreen() ? <LockScreen /> : <UnlockScreen />}
</div>
</NModal>
<LockScreen />
</RModal>
)
},
})

View File

@ -1,14 +1,3 @@
/**
*
* @author Ray <https://github.com/XiaoDaiGua-Ray>
*
* @date 2023-06-20
*
* @workspace ray-template
*
* @remark
*/
import type { InputInst } from 'naive-ui'
import type { Ref } from 'vue'

View File

@ -1,4 +1,4 @@
import { NAvatar, NTooltip, NSpace } from 'naive-ui'
import { NAvatar, NTooltip, NFlex } from 'naive-ui'
interface AvatarOptions {
key: string
@ -7,45 +7,33 @@ interface AvatarOptions {
icon: string
}
const RayLink = defineComponent({
name: 'RayLink',
export default defineComponent({
name: 'AppShareLink',
setup() {
const avatarOptions: AvatarOptions[] = [
{
key: 'yunhome',
src: 'https://yunkuangao.me/',
tooltip: '云之家',
icon: 'https://yunkuangao.me/wp-content/uploads/2022/05/cropped-cropped-QQ%E5%9B%BE%E7%89%8720220511113928.jpg',
},
{
key: 'yun-cloud-images',
src: 'https://yunkuangao.com/',
tooltip: '云图床',
icon: 'https://yunkuangao.me/wp-content/uploads/2022/05/cropped-cropped-QQ%E5%9B%BE%E7%89%8720220511113928.jpg',
},
{
key: 'ray-js-note',
src: 'https://note.youdao.com/s/ObWEe2BB',
tooltip: 'Ray的前端学习笔记',
icon: 'https://usc1.contabostorage.com/c2e495d7890844d392e8ec0c6e5d77eb:image/longmao.jpeg',
icon: 'https://avatars.githubusercontent.com/u/51957438?v=4',
},
{
key: 'ray-js-cover',
src: 'https://note.youdao.com/s/IC8xKPdB',
tooltip: 'Ray的面试题总结',
icon: 'https://usc1.contabostorage.com/c2e495d7890844d392e8ec0c6e5d77eb:image/longmao.jpeg',
icon: 'https://avatars.githubusercontent.com/u/51957438?v=4',
},
{
key: 'ray-template-doc',
src: 'https://xiaodaigua-ray.github.io/ray-template-doc/',
tooltip: 'Ray Template Doc',
icon: 'https://usc1.contabostorage.com/c2e495d7890844d392e8ec0c6e5d77eb:image/longmao.jpeg',
icon: 'https://avatars.githubusercontent.com/u/51957438?v=4',
},
{
key: 'ray-template-doc-out',
src: 'https://ray-template.yunkuangao.com/',
tooltip: 'Ray Template Doc (国内地址)',
icon: 'https://usc1.contabostorage.com/c2e495d7890844d392e8ec0c6e5d77eb:image/longmao.jpeg',
icon: 'https://avatars.githubusercontent.com/u/51957438?v=4',
},
]
@ -60,9 +48,9 @@ const RayLink = defineComponent({
},
render() {
return (
<NSpace>
<NFlex>
{this.avatarOptions.map((curr) => (
<NTooltip>
<NTooltip key={curr.key}>
{{
trigger: () => (
<NAvatar
@ -80,16 +68,7 @@ const RayLink = defineComponent({
}}
</NTooltip>
))}
</NSpace>
</NFlex>
)
},
})
export default RayLink
/**
*
*
*
* ,
*/

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