mirror of
https://github.com/XiaoDaiGua-Ray/ray-template.git
synced 2025-10-08 00:05:31 +08:00
Compare commits
263 Commits
Author | SHA1 | Date | |
---|---|---|---|
|
49af61a339 | ||
|
4bce5f7713 | ||
|
34c20d4be7 | ||
|
c28c353f7d | ||
|
c68491fca9 | ||
|
0f2193cc14 | ||
|
ba6ceef0dc | ||
|
5e9ffb14a3 | ||
|
eb5a6aa9e2 | ||
|
674539edd3 | ||
|
ff0bcb5022 | ||
|
d3d98190a3 | ||
|
0bb707bba0 | ||
|
4bfdbccd88 | ||
|
3b2bba391e | ||
|
7647508935 | ||
|
852d7ca90a | ||
|
2c84e3ce4c | ||
|
83e0c19ba9 | ||
|
ff1a67c843 | ||
|
8f3969268a | ||
|
7783872ef6 | ||
|
707300774d | ||
|
2f42571b3b | ||
|
73792144a8 | ||
|
11cbf8bca3 | ||
|
f9473114e7 | ||
|
7394e0bf30 | ||
|
d12fcd18b6 | ||
|
60ed09a0c5 | ||
|
fa8d52601f | ||
|
3f7e3722fd | ||
|
ab6593f022 | ||
|
d306ac8804 | ||
|
8405cc5709 | ||
|
6975af2368 | ||
|
b0cd545c99 | ||
|
7ca9663cb1 | ||
|
85f0d43d7e | ||
|
09315473a3 | ||
|
52dc6038da | ||
|
27db2293f3 | ||
|
5b035815eb | ||
|
b06006b442 | ||
|
557ab0467a | ||
|
e5c16b3497 | ||
|
f97b25e626 | ||
|
8097296f8f | ||
|
43df660ab6 | ||
|
4e14f6a02d | ||
|
472518af99 | ||
|
d9ea6cbdf5 | ||
|
01287743e7 | ||
|
41dce4920f | ||
|
1ee3ce0500 | ||
|
ae67b7b8b9 | ||
|
87d0a7a4af | ||
|
683367cfd8 | ||
|
a6f7843ff9 | ||
|
e95c06b009 | ||
|
85f7f28dc4 | ||
|
7c429338d4 | ||
|
a0f7763778 | ||
|
03628890cb | ||
|
3e36144cf9 | ||
|
5c52f2f88c | ||
|
bed8432cda | ||
|
c21df42187 | ||
|
3bf297d81c | ||
|
70eee28aa8 | ||
|
e20dbb4cd2 | ||
|
f28e343e6c | ||
|
8db423a018 | ||
|
0c1842cbbd | ||
|
9a4e2e723d | ||
|
3fa9478ee4 | ||
|
25599cea24 | ||
|
a00e3ca557 | ||
|
8ced024a94 | ||
|
af1eeac035 | ||
|
cc0577eb8f | ||
|
bb61081ddd | ||
|
a3341450f1 | ||
|
b97e2ee701 | ||
|
b41d0490ca | ||
|
43bf252d84 | ||
|
8c1e215013 | ||
|
79056e8846 | ||
|
5660530d8a | ||
|
57ed469543 | ||
|
c2041b2bb6 | ||
|
77a4fcb9a2 | ||
|
f60a107997 | ||
|
17e60fdf51 | ||
|
f663cf5b41 | ||
|
740d415a3f | ||
|
8b56327eaa | ||
|
9d38f99af2 | ||
|
d98a42380d | ||
|
f5ae01f33a | ||
|
c1205d9912 | ||
|
ade9a03f1e | ||
|
45be191112 | ||
|
8a6f983bf3 | ||
|
7d8b1b123f | ||
|
6649579a0c | ||
|
0b4a0cc237 | ||
|
0c9105b54e | ||
|
7c51a5c78f | ||
|
49fc22d601 | ||
|
66cf689d70 | ||
|
e7733fa105 | ||
|
c67f5d6734 | ||
|
e9caa52401 | ||
|
998b07cdd7 | ||
|
fdff8c7fe7 | ||
|
89618e402f | ||
|
30e6d6dddb | ||
|
2c24ae5d94 | ||
|
5299a0245f | ||
|
c304f37146 | ||
|
f272832a8c | ||
|
9e3cbac091 | ||
|
429e51fc30 | ||
|
cda4216493 | ||
|
38069b5b8c | ||
|
e76723adb6 | ||
|
3767861fe4 | ||
|
416fbd7990 | ||
|
23e80137b7 | ||
|
e521b7643c | ||
|
b1dfa53d7f | ||
|
6c389eb496 | ||
|
94f975eaff | ||
|
335e456688 | ||
|
711be2006b | ||
|
9168cd876e | ||
|
aff8437089 | ||
|
04daa39e49 | ||
|
32183d0ab9 | ||
|
f7887989f9 | ||
|
36d646d8ba | ||
|
a840693455 | ||
|
f6c343911e | ||
|
b05edfeaeb | ||
|
9e3f199d21 | ||
|
e81bb3f9a5 | ||
|
1f2ae05ca4 | ||
|
d698db587f | ||
|
0c7adb9584 | ||
|
fac9f9413d | ||
|
3fb016513b | ||
|
43be9bc3f2 | ||
|
a82b28fb93 | ||
|
353bc7d809 | ||
|
5e0c85c7f5 | ||
|
7a96b58541 | ||
|
4960eb8175 | ||
|
4d4d5451dc | ||
|
405e62c762 | ||
|
d2773d62bc | ||
|
4331b6a3b1 | ||
|
6acc80f18a | ||
|
689813f1a0 | ||
|
ccc4d6c710 | ||
|
d6be8fe9fe | ||
|
4dd2024bec | ||
|
b1890df8f6 | ||
|
2690e71713 | ||
|
99cafd8cb1 | ||
|
17c5ca7e50 | ||
|
6f98e8fb0d | ||
|
1be73da92a | ||
|
72996dbee7 | ||
|
811bd0ca1c | ||
|
a9c7e6589f | ||
|
018f4a9aca | ||
|
3b0dcfc8e2 | ||
|
5eb201b25b | ||
|
be406cc026 | ||
|
660b61340d | ||
|
d203ff9d95 | ||
|
f1134ed5b6 | ||
|
6b8921ee7b | ||
|
1b5edfb479 | ||
|
3e6109695e | ||
|
97b05f52a5 | ||
|
449dd2004b | ||
|
6cd5a1cf3e | ||
|
8693a4098a | ||
|
c51767df55 | ||
|
502672eef9 | ||
|
fd71ac7773 | ||
|
ef60abb3af | ||
|
56fc982dd7 | ||
|
50196fac8f | ||
|
03f6ae3f9f | ||
|
a69a032517 | ||
|
74da406808 | ||
|
08f236dd0a | ||
|
11b8a6a73a | ||
|
b2156a4c1d | ||
|
1643ee392e | ||
|
f49f606f7d | ||
|
977331ecb3 | ||
|
1e7a0ef1cf | ||
|
20757214d8 | ||
|
8277cbc4b8 | ||
|
7255a8d835 | ||
|
1cced633f2 | ||
|
f3a3f50f44 | ||
|
01d44d46f3 | ||
|
dd757d24a4 | ||
|
918cec5f4c | ||
|
6b0dd35975 | ||
|
4204463db2 | ||
|
b988cd6b94 | ||
|
ed31025352 | ||
|
5502cd690d | ||
|
ca9bbf7673 | ||
|
17b0c27d96 | ||
|
232f1efeb9 | ||
|
b3ceff8dfe | ||
|
7a0abd33b9 | ||
|
48ad65e3b1 | ||
|
76ec304ca7 | ||
|
047c46f2a2 | ||
|
a6628f821d | ||
|
0887bf18cd | ||
|
bbd4765d67 | ||
|
6577ac75c4 | ||
|
580c8a7a9c | ||
|
03186cda69 | ||
|
028892daa9 | ||
|
cec5166970 | ||
|
6acc7b6500 | ||
|
f49bb2a08c | ||
|
dbb27902aa | ||
|
f21eeddd7f | ||
|
9041f61f2b | ||
|
d736fc9b03 | ||
|
0411ec7277 | ||
|
5d4e23149d | ||
|
80d683a912 | ||
|
6498898a43 | ||
|
5daa7d67a4 | ||
|
ba9fb7ed9d | ||
|
8d8653ec36 | ||
|
ed9fb0eb23 | ||
|
a82c042fe9 | ||
|
2845796e38 | ||
|
2a64d07a42 | ||
|
4e6f70280c | ||
|
25645e1e52 | ||
|
eaf6f16132 | ||
|
54d370c7a0 | ||
|
d1cb4ddb41 | ||
|
936ddf3c14 | ||
|
a010bd1793 | ||
|
c17f941169 | ||
|
14b96fccac | ||
|
d5de28340b | ||
|
f5ce953b83 |
5
.dockerignore
Normal file
5
.dockerignore
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
node_modules
|
||||||
|
.git
|
||||||
|
.gitignore
|
||||||
|
*.md
|
||||||
|
dist
|
@ -1,6 +1,4 @@
|
|||||||
#生产环境
|
#生产环境
|
||||||
NODE_ENV = 'production'
|
|
||||||
|
|
||||||
VITE_APP_URL = '/'
|
VITE_APP_URL = '/'
|
||||||
|
|
||||||
# office 服务代理地址
|
# office 服务代理地址
|
||||||
|
170
.eslintrc.cjs
170
.eslintrc.cjs
@ -1,170 +0,0 @@
|
|||||||
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',
|
|
||||||
],
|
|
||||||
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',
|
|
||||||
},
|
|
||||||
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` 调用父类的构造函数
|
|
||||||
curly: [2, 'all'], // `if`、`else` 强制使用 `{}` 包裹
|
|
||||||
'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': 2, // 禁止不必要的 `bool` 转换
|
|
||||||
'no-extra-parens': 0, // 禁止非必要的括号
|
|
||||||
semi: ['error', 'never', { beforeStatementContinuationChars: 'always' }],
|
|
||||||
'no-fallthrough': 1, // 禁止 `switch` 穿透
|
|
||||||
'no-func-assign': 2, // 禁止重复的函数声明
|
|
||||||
'no-implicit-coercion': 1, // 禁止隐式转换
|
|
||||||
'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, // 强制使用箭头函数作为回调
|
|
||||||
'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: true,
|
|
||||||
globals: ['RouterView'],
|
|
||||||
},
|
|
||||||
],
|
|
||||||
'vue/no-unused-refs': ['error'],
|
|
||||||
},
|
|
||||||
}
|
|
14
.gitattributes
vendored
Normal file
14
.gitattributes
vendored
Normal 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
38
.github/workflows/docs-deploy.yaml
vendored
Normal 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
47
.github/workflows/push-build.yaml
vendored
Normal 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
5
.gitignore
vendored
@ -14,8 +14,8 @@ dist
|
|||||||
dist/
|
dist/
|
||||||
*.local
|
*.local
|
||||||
visualizer.*
|
visualizer.*
|
||||||
components.d.ts
|
.eslintcache
|
||||||
auto-imports.d.ts
|
.history
|
||||||
|
|
||||||
# Editor directories and files
|
# Editor directories and files
|
||||||
.idea
|
.idea
|
||||||
@ -27,3 +27,4 @@ auto-imports.d.ts
|
|||||||
*.sw?
|
*.sw?
|
||||||
yarn-*.*
|
yarn-*.*
|
||||||
yarn.*
|
yarn.*
|
||||||
|
pnpm.*
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
#!/usr/bin/env sh
|
#!/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
8
.husky/common.sh
Normal 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
|
@ -1,4 +1,7 @@
|
|||||||
#!/usr/bin/env sh
|
#!/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"
|
7
.npmignore
Normal file
7
.npmignore
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
node_modules/*
|
||||||
|
dist/*
|
||||||
|
visualizer.html
|
||||||
|
.idea
|
||||||
|
auto-imports.d.ts
|
||||||
|
components.d.ts
|
||||||
|
visualizer.html
|
4
.npmrc
Normal file
4
.npmrc
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
package-lock=true
|
||||||
|
prefer-offline=true
|
||||||
|
save-exact=true
|
||||||
|
engine-strict=true
|
@ -1,15 +1,12 @@
|
|||||||
dist
|
dist/*
|
||||||
node_modules
|
node_modules/*
|
||||||
auto-imports.d.ts
|
auto-imports.d.ts
|
||||||
components.d.ts
|
components.d.ts
|
||||||
.gitignore
|
.gitignore
|
||||||
.vscode
|
|
||||||
public
|
public
|
||||||
yarn.*
|
yarn.*
|
||||||
vite-env.*
|
|
||||||
.prettierrc.*
|
.prettierrc.*
|
||||||
.eslintrc
|
|
||||||
visualizer.*
|
visualizer.*
|
||||||
visualizer.html
|
visualizer.html
|
||||||
.env.*
|
.env.*
|
||||||
src/locales/lang
|
*-lock.yaml
|
@ -8,7 +8,6 @@ module.exports = {
|
|||||||
jsxSingleQuote: false, // `jsx` 不使用单引号, 而使用双引号
|
jsxSingleQuote: false, // `jsx` 不使用单引号, 而使用双引号
|
||||||
trailingComma: 'all', // 尾随逗号
|
trailingComma: 'all', // 尾随逗号
|
||||||
bracketSpacing: true, // 大括号内的首尾需要空格
|
bracketSpacing: true, // 大括号内的首尾需要空格
|
||||||
jsxBracketSameLine: false, // `jsx` 标签的反尖括号需要换行
|
|
||||||
arrowParens: 'always', // 箭头函数, 只有一个参数的时候, 也需要括号
|
arrowParens: 'always', // 箭头函数, 只有一个参数的时候, 也需要括号
|
||||||
rangeStart: 0, // 每个文件格式化的范围是文件的全部内容
|
rangeStart: 0, // 每个文件格式化的范围是文件的全部内容
|
||||||
rangeEnd: Infinity,
|
rangeEnd: Infinity,
|
||||||
@ -16,5 +15,6 @@ module.exports = {
|
|||||||
insertPragma: false, // 不需要自动在文件开头插入 `@prettier`
|
insertPragma: false, // 不需要自动在文件开头插入 `@prettier`
|
||||||
proseWrap: 'preserve', // 使用默认的折行标准
|
proseWrap: 'preserve', // 使用默认的折行标准
|
||||||
htmlWhitespaceSensitivity: 'css', // 根据显示样式决定 `html` 要不要折行
|
htmlWhitespaceSensitivity: 'css', // 根据显示样式决定 `html` 要不要折行
|
||||||
endOfLine: 'lf', // 换行符使用 `lf`
|
endOfLine: 'lf', // 换行符使用 `lf`,
|
||||||
|
singleAttributePerLine: false,
|
||||||
}
|
}
|
||||||
|
45
.vscode/settings.json
vendored
45
.vscode/settings.json
vendored
@ -1,11 +1,52 @@
|
|||||||
{
|
{
|
||||||
|
"editor.formatOnSave": true,
|
||||||
"i18n-ally.localesPaths": ["src/locales/lang"],
|
"i18n-ally.localesPaths": ["src/locales/lang"],
|
||||||
"i18n-ally.keystyle": "nested",
|
"i18n-ally.keystyle": "nested",
|
||||||
"i18n-ally.sortKeys": true,
|
"i18n-ally.sortKeys": true,
|
||||||
"i18n-ally.namespace": true,
|
"i18n-ally.namespace": true,
|
||||||
"i18n-ally.pathMatcher": "{locale}/{namespaces}.{ext}",
|
"i18n-ally.pathMatcher": "{locale}/{namespaces}.{ext}",
|
||||||
"i18n-ally.enabledParsers": ["json"],
|
"i18n-ally.enabledParsers": ["json"],
|
||||||
"i18n-ally.sourceLanguage": "en",
|
"i18n-ally.sourceLanguage": "zh-CN",
|
||||||
"i18n-ally.displayLanguage": "zh-CN",
|
"i18n-ally.displayLanguage": "zh-CN",
|
||||||
"i18n-ally.enabledFrameworks": ["vue", "react"]
|
"i18n-ally.enabledFrameworks": ["vue", "react"],
|
||||||
|
"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"
|
||||||
|
]
|
||||||
}
|
}
|
||||||
|
2140
CHANGELOG.md
2140
CHANGELOG.md
File diff suppressed because it is too large
Load Diff
221
CODE_OF_CONDUCT.md
Normal file
221
CODE_OF_CONDUCT.md
Normal 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
|
@ -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
11
Dockerfile
Normal 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
21
LICENSE
Normal 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.
|
22
README.commit.md
Normal file
22
README.commit.md
Normal 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`文件中进行配置。
|
274
README.md
274
README.md
@ -1,230 +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">
|
||||||
|
|
||||||
[](#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/#/)
|
`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.
|
||||||
- [点击预览(加速地址)](https://ray-template.yunkuangao.com/#/)
|
|
||||||
|
|
||||||
## 文档地址
|
## ✨ Features
|
||||||
|
|
||||||
- [文档](https://xiaodaigua-ray.github.io/ray-template-doc/)
|
- `New technology stack:` using ts(x), vite5. x, vue3. x, pinia and other front-end cutting-edge technology development
|
||||||
- [文档(加速地址)](https://ray-template.yunkuangao.com/ray-template-doc/)
|
- `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
|
||||||
|
|
||||||
- 主题切换
|
- [Change Log](https://github.com/XiaoDaiGua-Ray/xiaodaigua-ray.github.io/blob/main/CHANGELOG.md)
|
||||||
- 任意深度页面缓存
|
|
||||||
- 系统配置化
|
|
||||||
- 锁屏
|
|
||||||
- 自动化路由
|
|
||||||
- 带有拓展功能的表格
|
|
||||||
- 封装 `axios` 自动取消重复请求
|
|
||||||
- 动态菜单(多级菜单)
|
|
||||||
- 主题色切换
|
|
||||||
- 错误页
|
|
||||||
- 面包屑
|
|
||||||
- 标签页
|
|
||||||
- 国际化(允许按模块管理语言包)
|
|
||||||
- 权限路由
|
|
||||||
- 动态切换主题、贴花的 `EChart` 图
|
|
||||||
- 最佳构建体验
|
|
||||||
- 体积分析
|
|
||||||
- 还有一些不值一提的小东西...
|
|
||||||
|
|
||||||
## 未来
|
## 🪴 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.0` `pinia` `tsx` 进行开发。
|
### Get Project
|
||||||
> 使用 `naive ui` 作为组件库。
|
|
||||||
> 预设了最佳构建体验的配置与常用搬砖工具。意在提供一个简洁、快速上手的模板。
|
|
||||||
|
|
||||||
## 提示
|
|
||||||
|
|
||||||
> 项目默认启用严格模式 `eslint`,但是由于 `vite-plugin-eslint` 插件优先级最高,所以如果出现自动导入类型错误提示,请优先解决其他问题。
|
|
||||||
> 建议开启 `vscode` 保存自动修复功能。
|
|
||||||
|
|
||||||
## 版本说明
|
|
||||||
|
|
||||||
> 做了一些大的改动升级,让模板更加好用了一点,默认主题色也做了变更更好看了一点。啰嗦两句,好像也没啥其他的了...
|
|
||||||
|
|
||||||
## 拉取依赖
|
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
# yarn
|
# github
|
||||||
|
git clone https://github.com/XiaoDaiGua-Ray/ray-template.git
|
||||||
yarn
|
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### Pull dependencies
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
# npm
|
pnpm i
|
||||||
|
|
||||||
npm install
|
|
||||||
```
|
```
|
||||||
|
|
||||||
## 启动项目
|
### Test project
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
# yarn
|
|
||||||
|
|
||||||
yarn dev
|
pnpm test
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### Startup project
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
# npm
|
pnpm dev
|
||||||
|
|
||||||
npm run dev
|
|
||||||
```
|
```
|
||||||
|
|
||||||
## 项目打包
|
### Build project
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
# yarn
|
pnpm build
|
||||||
|
|
||||||
yarn build
|
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### Preview project
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
# npm
|
pnpm preview
|
||||||
|
|
||||||
npm run build
|
|
||||||
```
|
```
|
||||||
|
|
||||||
## 预览项目
|
### Report project
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
# yarn
|
pnpm report
|
||||||
|
|
||||||
yarn preview
|
|
||||||
```
|
```
|
||||||
|
|
||||||
```sh
|
### Development
|
||||||
# npm
|
|
||||||
|
|
||||||
npm run preview
|
Just delete the files under `views/demo`, `router/modules/demo` to get a clean project template.
|
||||||
```
|
|
||||||
|
|
||||||
## 体积分析
|
## 🪴 Project Activities
|
||||||
|
|
||||||
```sh
|

|
||||||
# yarn
|
|
||||||
|
|
||||||
yarn report
|
### Contributors
|
||||||
```
|
|
||||||
|
|
||||||
```sh
|
Thanks for all their contributions 🐝 !
|
||||||
# npm
|
|
||||||
|
|
||||||
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/) `全局状态管理器`
|
| [<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 |
|
||||||
- [@vueuse](https://vueuse.org/) `vue3 hooks`
|
| :--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: |
|
||||||
- [vue-router](https://router.vuejs.org/zh/) `router`
|
| not support | last 2 versions | last 2 versions | last 2 versions | last 2 versions |
|
||||||
- [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/) `拓展方法`
|
|
||||||
- 还有一些后续补充的,懒得写了。。。自己看项目依赖页面
|
|
||||||
|
|
||||||
## 基础组件
|
## 📄 License
|
||||||
|
|
||||||
- `RayIcon` `svg icon`
|
[MIT License](https://github.com/XiaoDaiGua-Ray/ray-template/blob/main/LICENSE) © 2022-PRESENT [Ray](https://github.com/XiaoDaiGua-Ray)
|
||||||
- `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!
|
|
||||||
|
154
README.zh-CN.md
Normal file
154
README.zh-CN.md
Normal 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` 下的文件即可得到一个干净的项目模板。
|
||||||
|
|
||||||
|
## 🪴 项目活动
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
### 贡献者
|
||||||
|
|
||||||
|
感谢他们的所做的一切贡献 🐝 !
|
||||||
|
|
||||||
|
<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)
|
16
__test__/app/prefixCacheKey.spec.ts
Normal file
16
__test__/app/prefixCacheKey.spec.ts
Normal 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)
|
||||||
|
})
|
||||||
|
})
|
18
__test__/basic/arrayBufferToBase64Image.spec.ts
Normal file
18
__test__/basic/arrayBufferToBase64Image.spec.ts
Normal 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)
|
||||||
|
})
|
||||||
|
})
|
27
__test__/basic/callWithAsyncErrorHandling.spec.ts
Normal file
27
__test__/basic/callWithAsyncErrorHandling.spec.ts
Normal 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)
|
||||||
|
})
|
||||||
|
})
|
27
__test__/basic/callWithErrorHandling.spec.ts
Normal file
27
__test__/basic/callWithErrorHandling.spec.ts
Normal 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)
|
||||||
|
})
|
||||||
|
})
|
7
__test__/basic/detectOperatingSystem.spec.ts
Normal file
7
__test__/basic/detectOperatingSystem.spec.ts
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
import { detectOperatingSystem } from '../../src/utils/basic'
|
||||||
|
|
||||||
|
describe('detectOperatingSystem', () => {
|
||||||
|
it('should return Unknown', () => {
|
||||||
|
expect(detectOperatingSystem()).toBe('Unknown')
|
||||||
|
})
|
||||||
|
})
|
33
__test__/basic/downloadAnyFile.spec.ts
Normal file
33
__test__/basic/downloadAnyFile.spec.ts
Normal 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()
|
||||||
|
// })
|
||||||
|
})
|
12
__test__/basic/downloadBase64File.spec.ts
Normal file
12
__test__/basic/downloadBase64File.spec.ts
Normal 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)
|
||||||
|
})
|
||||||
|
})
|
17
__test__/basic/equalRouterPath.spec.ts
Normal file
17
__test__/basic/equalRouterPath.spec.ts
Normal 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)
|
||||||
|
})
|
||||||
|
})
|
21
__test__/basic/getAppEnvironment.spec.ts
Normal file
21
__test__/basic/getAppEnvironment.spec.ts
Normal 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)
|
||||||
|
})
|
||||||
|
})
|
33
__test__/basic/isAsyncFunction.spec.ts
Normal file
33
__test__/basic/isAsyncFunction.spec.ts
Normal 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)
|
||||||
|
})
|
||||||
|
})
|
33
__test__/basic/isPromise.spec.ts
Normal file
33
__test__/basic/isPromise.spec.ts
Normal 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)
|
||||||
|
})
|
||||||
|
})
|
48
__test__/basic/isValueType.spec.ts
Normal file
48
__test__/basic/isValueType.spec.ts
Normal 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)
|
||||||
|
})
|
||||||
|
})
|
20
__test__/basic/uuid.spec.ts
Normal file
20
__test__/basic/uuid.spec.ts
Normal 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
117
__test__/cache/index.spec.ts
vendored
Normal 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)
|
||||||
|
})
|
||||||
|
})
|
36
__test__/components/modal.spec.ts
Normal file
36
__test__/components/modal.spec.ts
Normal 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)
|
||||||
|
})
|
||||||
|
})
|
49
__test__/dom/printDom.spec.tsx
Normal file
49
__test__/dom/printDom.spec.tsx
Normal 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', () => {})
|
||||||
|
})
|
19
__test__/element/autoPrefixStyle.spec.ts
Normal file
19
__test__/element/autoPrefixStyle.spec.ts
Normal 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',
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
68
__test__/element/classMethods.spec.tsx
Normal file
68
__test__/element/classMethods.spec.tsx
Normal 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)
|
||||||
|
})
|
||||||
|
})
|
23
__test__/element/colorToRgba.spec.ts
Normal file
23
__test__/element/colorToRgba.spec.ts
Normal 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')
|
||||||
|
})
|
||||||
|
})
|
17
__test__/element/completeSize.spec.ts
Normal file
17
__test__/element/completeSize.spec.ts
Normal 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')
|
||||||
|
})
|
||||||
|
})
|
52
__test__/element/queryElements.spec.ts
Normal file
52
__test__/element/queryElements.spec.ts
Normal 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)
|
||||||
|
})
|
||||||
|
})
|
71
__test__/element/styleMethods.spec.ts
Normal file
71
__test__/element/styleMethods.spec.ts
Normal 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')
|
||||||
|
})
|
||||||
|
})
|
18
__test__/hooks/useAppNavigation.spec.ts
Normal file
18
__test__/hooks/useAppNavigation.spec.ts
Normal 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()
|
||||||
|
})
|
||||||
|
})
|
51
__test__/hooks/useAppRoot.spec.ts
Normal file
51
__test__/hooks/useAppRoot.spec.ts
Normal 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)
|
||||||
|
})
|
||||||
|
})
|
67
__test__/hooks/useBadge.spec.ts
Normal file
67
__test__/hooks/useBadge.spec.ts
Normal 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)
|
||||||
|
})
|
||||||
|
})
|
50
__test__/hooks/useContextmenuCoordinate.spec.tsx
Normal file
50
__test__/hooks/useContextmenuCoordinate.spec.tsx
Normal 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)
|
||||||
|
})
|
||||||
|
})
|
101
__test__/hooks/useDayjs.spec.ts
Normal file
101
__test__/hooks/useDayjs.spec.ts
Normal 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)
|
||||||
|
})
|
||||||
|
})
|
116
__test__/hooks/useDevice.spec.ts
Normal file
116
__test__/hooks/useDevice.spec.ts
Normal 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)
|
||||||
|
})
|
||||||
|
})
|
54
__test__/hooks/useElementFullscreen.spec.ts
Normal file
54
__test__/hooks/useElementFullscreen.spec.ts
Normal 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)
|
||||||
|
})
|
||||||
|
})
|
66
__test__/hooks/usePagination.spec.ts
Normal file
66
__test__/hooks/usePagination.spec.ts
Normal 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)
|
||||||
|
})
|
||||||
|
})
|
167
__test__/hooks/useSiderBar.spec.ts
Normal file
167
__test__/hooks/useSiderBar.spec.ts
Normal 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)
|
||||||
|
})
|
||||||
|
})
|
38
__test__/hooks/useSpinning.spec.ts
Normal file
38
__test__/hooks/useSpinning.spec.ts
Normal 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)
|
||||||
|
})
|
||||||
|
})
|
46
__test__/hooks/useTheme.spec.ts
Normal file
46
__test__/hooks/useTheme.spec.ts
Normal 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')
|
||||||
|
})
|
||||||
|
})
|
15
__test__/hooks/useVueRouter.spec.ts
Normal file
15
__test__/hooks/useVueRouter.spec.ts
Normal 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)
|
||||||
|
})
|
||||||
|
})
|
33
__test__/hooks/useWatermark.spec.ts
Normal file
33
__test__/hooks/useWatermark.spec.ts
Normal 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)
|
||||||
|
})
|
||||||
|
})
|
82
__test__/precision/index.spec.ts
Normal file
82
__test__/precision/index.spec.ts
Normal 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])
|
||||||
|
})
|
||||||
|
})
|
16
__test__/utils/canUseDom.ts
Normal file
16
__test__/utils/canUseDom.ts
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
/**
|
||||||
|
*
|
||||||
|
* @description
|
||||||
|
* 判断是否可以操作 DOM。
|
||||||
|
*
|
||||||
|
* 如果可以操作 DOM,则返回 true,否则返回 false。
|
||||||
|
*/
|
||||||
|
const canUseDom = () => {
|
||||||
|
return !!(
|
||||||
|
typeof window !== 'undefined' &&
|
||||||
|
window.document &&
|
||||||
|
window.document.createElement
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default canUseDom
|
36
__test__/utils/createRefElement.tsx
Normal file
36
__test__/utils/createRefElement.tsx
Normal 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
|
15
__test__/utils/isBrowser.ts
Normal file
15
__test__/utils/isBrowser.ts
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
/**
|
||||||
|
*
|
||||||
|
* @description
|
||||||
|
* 获取当前环境是否为浏览器环境。
|
||||||
|
*
|
||||||
|
* 如果是浏览器环境,则返回 true,否则返回 false。
|
||||||
|
*/
|
||||||
|
const isBrowser = () =>
|
||||||
|
!!(
|
||||||
|
typeof window !== 'undefined' &&
|
||||||
|
window.document &&
|
||||||
|
window.document.createElement
|
||||||
|
)
|
||||||
|
|
||||||
|
export default isBrowser
|
34
__test__/utils/renderHook.ts
Normal file
34
__test__/utils/renderHook.ts
Normal 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 }]
|
||||||
|
}
|
27
__test__/utils/setupMiniApp.ts
Normal file
27
__test__/utils/setupMiniApp.ts
Normal 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 应用环境。
|
||||||
|
* 该方法会初始化 store、router、i18n 等环境。
|
||||||
|
*
|
||||||
|
* @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
17
__test__/utils/sleep.ts
Normal 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
27
__test__/vue/call.spec.ts
Normal 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)
|
||||||
|
})
|
||||||
|
})
|
7
__test__/vue/effectDispose.spec.ts
Normal file
7
__test__/vue/effectDispose.spec.ts
Normal 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)
|
||||||
|
})
|
||||||
|
})
|
14
__test__/vue/renderNode.spec.tsx
Normal file
14
__test__/vue/renderNode.spec.tsx
Normal 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')
|
||||||
|
})
|
||||||
|
})
|
287
auto-imports.d.ts
vendored
287
auto-imports.d.ts
vendored
@ -1,287 +0,0 @@
|
|||||||
// Generated by 'unplugin-auto-import'
|
|
||||||
export {}
|
|
||||||
declare global {
|
|
||||||
const EffectScope: typeof import('vue')['EffectScope']
|
|
||||||
const acceptHMRUpdate: typeof import('pinia')['acceptHMRUpdate']
|
|
||||||
const asyncComputed: typeof import('@vueuse/core')['asyncComputed']
|
|
||||||
const autoResetRef: typeof import('@vueuse/core')['autoResetRef']
|
|
||||||
const computed: typeof import('vue')['computed']
|
|
||||||
const computedAsync: typeof import('@vueuse/core')['computedAsync']
|
|
||||||
const computedEager: typeof import('@vueuse/core')['computedEager']
|
|
||||||
const computedInject: typeof import('@vueuse/core')['computedInject']
|
|
||||||
const computedWithControl: typeof import('@vueuse/core')['computedWithControl']
|
|
||||||
const controlledComputed: typeof import('@vueuse/core')['controlledComputed']
|
|
||||||
const controlledRef: typeof import('@vueuse/core')['controlledRef']
|
|
||||||
const createApp: typeof import('vue')['createApp']
|
|
||||||
const createEventHook: typeof import('@vueuse/core')['createEventHook']
|
|
||||||
const createGlobalState: typeof import('@vueuse/core')['createGlobalState']
|
|
||||||
const createInjectionState: typeof import('@vueuse/core')['createInjectionState']
|
|
||||||
const createPinia: typeof import('pinia')['createPinia']
|
|
||||||
const createReactiveFn: typeof import('@vueuse/core')['createReactiveFn']
|
|
||||||
const createSharedComposable: typeof import('@vueuse/core')['createSharedComposable']
|
|
||||||
const createUnrefFn: typeof import('@vueuse/core')['createUnrefFn']
|
|
||||||
const customRef: typeof import('vue')['customRef']
|
|
||||||
const debouncedRef: typeof import('@vueuse/core')['debouncedRef']
|
|
||||||
const debouncedWatch: typeof import('@vueuse/core')['debouncedWatch']
|
|
||||||
const defineAsyncComponent: typeof import('vue')['defineAsyncComponent']
|
|
||||||
const defineComponent: typeof import('vue')['defineComponent']
|
|
||||||
const defineStore: typeof import('pinia')['defineStore']
|
|
||||||
const eagerComputed: typeof import('@vueuse/core')['eagerComputed']
|
|
||||||
const effectScope: typeof import('vue')['effectScope']
|
|
||||||
const extendRef: typeof import('@vueuse/core')['extendRef']
|
|
||||||
const getActivePinia: typeof import('pinia')['getActivePinia']
|
|
||||||
const getCurrentInstance: typeof import('vue')['getCurrentInstance']
|
|
||||||
const getCurrentScope: typeof import('vue')['getCurrentScope']
|
|
||||||
const h: typeof import('vue')['h']
|
|
||||||
const ignorableWatch: typeof import('@vueuse/core')['ignorableWatch']
|
|
||||||
const inject: typeof import('vue')['inject']
|
|
||||||
const isDefined: typeof import('@vueuse/core')['isDefined']
|
|
||||||
const isProxy: typeof import('vue')['isProxy']
|
|
||||||
const isReactive: typeof import('vue')['isReactive']
|
|
||||||
const isReadonly: typeof import('vue')['isReadonly']
|
|
||||||
const isRef: typeof import('vue')['isRef']
|
|
||||||
const makeDestructurable: typeof import('@vueuse/core')['makeDestructurable']
|
|
||||||
const mapActions: typeof import('pinia')['mapActions']
|
|
||||||
const mapGetters: typeof import('pinia')['mapGetters']
|
|
||||||
const mapState: typeof import('pinia')['mapState']
|
|
||||||
const mapStores: typeof import('pinia')['mapStores']
|
|
||||||
const mapWritableState: typeof import('pinia')['mapWritableState']
|
|
||||||
const markRaw: typeof import('vue')['markRaw']
|
|
||||||
const nextTick: typeof import('vue')['nextTick']
|
|
||||||
const onActivated: typeof import('vue')['onActivated']
|
|
||||||
const onBeforeMount: typeof import('vue')['onBeforeMount']
|
|
||||||
const onBeforeRouteLeave: typeof import('vue-router')['onBeforeRouteLeave']
|
|
||||||
const onBeforeRouteUpdate: typeof import('vue-router')['onBeforeRouteUpdate']
|
|
||||||
const onBeforeUnmount: typeof import('vue')['onBeforeUnmount']
|
|
||||||
const onBeforeUpdate: typeof import('vue')['onBeforeUpdate']
|
|
||||||
const onClickOutside: typeof import('@vueuse/core')['onClickOutside']
|
|
||||||
const onDeactivated: typeof import('vue')['onDeactivated']
|
|
||||||
const onErrorCaptured: typeof import('vue')['onErrorCaptured']
|
|
||||||
const onKeyStroke: typeof import('@vueuse/core')['onKeyStroke']
|
|
||||||
const onLongPress: typeof import('@vueuse/core')['onLongPress']
|
|
||||||
const onMounted: typeof import('vue')['onMounted']
|
|
||||||
const onRenderTracked: typeof import('vue')['onRenderTracked']
|
|
||||||
const onRenderTriggered: typeof import('vue')['onRenderTriggered']
|
|
||||||
const onScopeDispose: typeof import('vue')['onScopeDispose']
|
|
||||||
const onServerPrefetch: typeof import('vue')['onServerPrefetch']
|
|
||||||
const onStartTyping: typeof import('@vueuse/core')['onStartTyping']
|
|
||||||
const onUnmounted: typeof import('vue')['onUnmounted']
|
|
||||||
const onUpdated: typeof import('vue')['onUpdated']
|
|
||||||
const pausableWatch: typeof import('@vueuse/core')['pausableWatch']
|
|
||||||
const provide: typeof import('vue')['provide']
|
|
||||||
const reactify: typeof import('@vueuse/core')['reactify']
|
|
||||||
const reactifyObject: typeof import('@vueuse/core')['reactifyObject']
|
|
||||||
const reactive: typeof import('vue')['reactive']
|
|
||||||
const reactiveComputed: typeof import('@vueuse/core')['reactiveComputed']
|
|
||||||
const reactiveOmit: typeof import('@vueuse/core')['reactiveOmit']
|
|
||||||
const reactivePick: typeof import('@vueuse/core')['reactivePick']
|
|
||||||
const readonly: typeof import('vue')['readonly']
|
|
||||||
const ref: typeof import('vue')['ref']
|
|
||||||
const refAutoReset: typeof import('@vueuse/core')['refAutoReset']
|
|
||||||
const refDebounced: typeof import('@vueuse/core')['refDebounced']
|
|
||||||
const refDefault: typeof import('@vueuse/core')['refDefault']
|
|
||||||
const refThrottled: typeof import('@vueuse/core')['refThrottled']
|
|
||||||
const refWithControl: typeof import('@vueuse/core')['refWithControl']
|
|
||||||
const resolveComponent: typeof import('vue')['resolveComponent']
|
|
||||||
const resolveDirective: typeof import('vue')['resolveDirective']
|
|
||||||
const resolveRef: typeof import('@vueuse/core')['resolveRef']
|
|
||||||
const resolveUnref: typeof import('@vueuse/core')['resolveUnref']
|
|
||||||
const setActivePinia: typeof import('pinia')['setActivePinia']
|
|
||||||
const setMapStoreSuffix: typeof import('pinia')['setMapStoreSuffix']
|
|
||||||
const shallowReactive: typeof import('vue')['shallowReactive']
|
|
||||||
const shallowReadonly: typeof import('vue')['shallowReadonly']
|
|
||||||
const shallowRef: typeof import('vue')['shallowRef']
|
|
||||||
const storeToRefs: typeof import('pinia')['storeToRefs']
|
|
||||||
const syncRef: typeof import('@vueuse/core')['syncRef']
|
|
||||||
const syncRefs: typeof import('@vueuse/core')['syncRefs']
|
|
||||||
const templateRef: typeof import('@vueuse/core')['templateRef']
|
|
||||||
const throttledRef: typeof import('@vueuse/core')['throttledRef']
|
|
||||||
const throttledWatch: typeof import('@vueuse/core')['throttledWatch']
|
|
||||||
const toRaw: typeof import('vue')['toRaw']
|
|
||||||
const toReactive: typeof import('@vueuse/core')['toReactive']
|
|
||||||
const toRef: typeof import('vue')['toRef']
|
|
||||||
const toRefs: typeof import('vue')['toRefs']
|
|
||||||
const triggerRef: typeof import('vue')['triggerRef']
|
|
||||||
const tryOnBeforeMount: typeof import('@vueuse/core')['tryOnBeforeMount']
|
|
||||||
const tryOnBeforeUnmount: typeof import('@vueuse/core')['tryOnBeforeUnmount']
|
|
||||||
const tryOnMounted: typeof import('@vueuse/core')['tryOnMounted']
|
|
||||||
const tryOnScopeDispose: typeof import('@vueuse/core')['tryOnScopeDispose']
|
|
||||||
const tryOnUnmounted: typeof import('@vueuse/core')['tryOnUnmounted']
|
|
||||||
const unref: typeof import('vue')['unref']
|
|
||||||
const unrefElement: typeof import('@vueuse/core')['unrefElement']
|
|
||||||
const until: typeof import('@vueuse/core')['until']
|
|
||||||
const useActiveElement: typeof import('@vueuse/core')['useActiveElement']
|
|
||||||
const useArrayEvery: typeof import('@vueuse/core')['useArrayEvery']
|
|
||||||
const useArrayFilter: typeof import('@vueuse/core')['useArrayFilter']
|
|
||||||
const useArrayFind: typeof import('@vueuse/core')['useArrayFind']
|
|
||||||
const useArrayFindIndex: typeof import('@vueuse/core')['useArrayFindIndex']
|
|
||||||
const useArrayFindLast: typeof import('@vueuse/core')['useArrayFindLast']
|
|
||||||
const useArrayJoin: typeof import('@vueuse/core')['useArrayJoin']
|
|
||||||
const useArrayMap: typeof import('@vueuse/core')['useArrayMap']
|
|
||||||
const useArrayReduce: typeof import('@vueuse/core')['useArrayReduce']
|
|
||||||
const useArraySome: typeof import('@vueuse/core')['useArraySome']
|
|
||||||
const useArrayUnique: typeof import('@vueuse/core')['useArrayUnique']
|
|
||||||
const useAsyncQueue: typeof import('@vueuse/core')['useAsyncQueue']
|
|
||||||
const useAsyncState: typeof import('@vueuse/core')['useAsyncState']
|
|
||||||
const useAttrs: typeof import('vue')['useAttrs']
|
|
||||||
const useBase64: typeof import('@vueuse/core')['useBase64']
|
|
||||||
const useBattery: typeof import('@vueuse/core')['useBattery']
|
|
||||||
const useBluetooth: typeof import('@vueuse/core')['useBluetooth']
|
|
||||||
const useBreakpoints: typeof import('@vueuse/core')['useBreakpoints']
|
|
||||||
const useBroadcastChannel: typeof import('@vueuse/core')['useBroadcastChannel']
|
|
||||||
const useBrowserLocation: typeof import('@vueuse/core')['useBrowserLocation']
|
|
||||||
const useCached: typeof import('@vueuse/core')['useCached']
|
|
||||||
const useClipboard: typeof import('@vueuse/core')['useClipboard']
|
|
||||||
const useCloned: typeof import('@vueuse/core')['useCloned']
|
|
||||||
const useColorMode: typeof import('@vueuse/core')['useColorMode']
|
|
||||||
const useConfirmDialog: typeof import('@vueuse/core')['useConfirmDialog']
|
|
||||||
const useCounter: typeof import('@vueuse/core')['useCounter']
|
|
||||||
const useCssModule: typeof import('vue')['useCssModule']
|
|
||||||
const useCssVar: typeof import('@vueuse/core')['useCssVar']
|
|
||||||
const useCssVars: typeof import('vue')['useCssVars']
|
|
||||||
const useCurrentElement: typeof import('@vueuse/core')['useCurrentElement']
|
|
||||||
const useCycleList: typeof import('@vueuse/core')['useCycleList']
|
|
||||||
const useDark: typeof import('@vueuse/core')['useDark']
|
|
||||||
const useDateFormat: typeof import('@vueuse/core')['useDateFormat']
|
|
||||||
const useDebounce: typeof import('@vueuse/core')['useDebounce']
|
|
||||||
const useDebounceFn: typeof import('@vueuse/core')['useDebounceFn']
|
|
||||||
const useDebouncedRefHistory: typeof import('@vueuse/core')['useDebouncedRefHistory']
|
|
||||||
const useDeviceMotion: typeof import('@vueuse/core')['useDeviceMotion']
|
|
||||||
const useDeviceOrientation: typeof import('@vueuse/core')['useDeviceOrientation']
|
|
||||||
const useDevicePixelRatio: typeof import('@vueuse/core')['useDevicePixelRatio']
|
|
||||||
const useDevicesList: typeof import('@vueuse/core')['useDevicesList']
|
|
||||||
const useDialog: typeof import('naive-ui')['useDialog']
|
|
||||||
const useDisplayMedia: typeof import('@vueuse/core')['useDisplayMedia']
|
|
||||||
const useDocumentVisibility: typeof import('@vueuse/core')['useDocumentVisibility']
|
|
||||||
const useDraggable: typeof import('@vueuse/core')['useDraggable']
|
|
||||||
const useDropZone: typeof import('@vueuse/core')['useDropZone']
|
|
||||||
const useElementBounding: typeof import('@vueuse/core')['useElementBounding']
|
|
||||||
const useElementByPoint: typeof import('@vueuse/core')['useElementByPoint']
|
|
||||||
const useElementHover: typeof import('@vueuse/core')['useElementHover']
|
|
||||||
const useElementSize: typeof import('@vueuse/core')['useElementSize']
|
|
||||||
const useElementVisibility: typeof import('@vueuse/core')['useElementVisibility']
|
|
||||||
const useEventBus: typeof import('@vueuse/core')['useEventBus']
|
|
||||||
const useEventListener: typeof import('@vueuse/core')['useEventListener']
|
|
||||||
const useEventSource: typeof import('@vueuse/core')['useEventSource']
|
|
||||||
const useEyeDropper: typeof import('@vueuse/core')['useEyeDropper']
|
|
||||||
const useFavicon: typeof import('@vueuse/core')['useFavicon']
|
|
||||||
const useFetch: typeof import('@vueuse/core')['useFetch']
|
|
||||||
const useFileDialog: typeof import('@vueuse/core')['useFileDialog']
|
|
||||||
const useFileSystemAccess: typeof import('@vueuse/core')['useFileSystemAccess']
|
|
||||||
const useFocus: typeof import('@vueuse/core')['useFocus']
|
|
||||||
const useFocusWithin: typeof import('@vueuse/core')['useFocusWithin']
|
|
||||||
const useFps: typeof import('@vueuse/core')['useFps']
|
|
||||||
const useFullscreen: typeof import('@vueuse/core')['useFullscreen']
|
|
||||||
const useGamepad: typeof import('@vueuse/core')['useGamepad']
|
|
||||||
const useGeolocation: typeof import('@vueuse/core')['useGeolocation']
|
|
||||||
const useI18n: typeof import('vue-i18n')['useI18n']
|
|
||||||
const useIdle: typeof import('@vueuse/core')['useIdle']
|
|
||||||
const useImage: typeof import('@vueuse/core')['useImage']
|
|
||||||
const useInfiniteScroll: typeof import('@vueuse/core')['useInfiniteScroll']
|
|
||||||
const useIntersectionObserver: typeof import('@vueuse/core')['useIntersectionObserver']
|
|
||||||
const useInterval: typeof import('@vueuse/core')['useInterval']
|
|
||||||
const useIntervalFn: typeof import('@vueuse/core')['useIntervalFn']
|
|
||||||
const useKeyModifier: typeof import('@vueuse/core')['useKeyModifier']
|
|
||||||
const useLastChanged: typeof import('@vueuse/core')['useLastChanged']
|
|
||||||
const useLink: typeof import('vue-router')['useLink']
|
|
||||||
const useLoadingBar: typeof import('naive-ui')['useLoadingBar']
|
|
||||||
const useLocalStorage: typeof import('@vueuse/core')['useLocalStorage']
|
|
||||||
const useMagicKeys: typeof import('@vueuse/core')['useMagicKeys']
|
|
||||||
const useManualRefHistory: typeof import('@vueuse/core')['useManualRefHistory']
|
|
||||||
const useMediaControls: typeof import('@vueuse/core')['useMediaControls']
|
|
||||||
const useMediaQuery: typeof import('@vueuse/core')['useMediaQuery']
|
|
||||||
const useMemoize: typeof import('@vueuse/core')['useMemoize']
|
|
||||||
const useMemory: typeof import('@vueuse/core')['useMemory']
|
|
||||||
const useMessage: typeof import('naive-ui')['useMessage']
|
|
||||||
const useMounted: typeof import('@vueuse/core')['useMounted']
|
|
||||||
const useMouse: typeof import('@vueuse/core')['useMouse']
|
|
||||||
const useMouseInElement: typeof import('@vueuse/core')['useMouseInElement']
|
|
||||||
const useMousePressed: typeof import('@vueuse/core')['useMousePressed']
|
|
||||||
const useMutationObserver: typeof import('@vueuse/core')['useMutationObserver']
|
|
||||||
const useNavigatorLanguage: typeof import('@vueuse/core')['useNavigatorLanguage']
|
|
||||||
const useNetwork: typeof import('@vueuse/core')['useNetwork']
|
|
||||||
const useNotification: typeof import('naive-ui')['useNotification']
|
|
||||||
const useNow: typeof import('@vueuse/core')['useNow']
|
|
||||||
const useObjectUrl: typeof import('@vueuse/core')['useObjectUrl']
|
|
||||||
const useOffsetPagination: typeof import('@vueuse/core')['useOffsetPagination']
|
|
||||||
const useOnline: typeof import('@vueuse/core')['useOnline']
|
|
||||||
const usePageLeave: typeof import('@vueuse/core')['usePageLeave']
|
|
||||||
const useParallax: typeof import('@vueuse/core')['useParallax']
|
|
||||||
const usePermission: typeof import('@vueuse/core')['usePermission']
|
|
||||||
const usePointer: typeof import('@vueuse/core')['usePointer']
|
|
||||||
const usePointerLock: typeof import('@vueuse/core')['usePointerLock']
|
|
||||||
const usePointerSwipe: typeof import('@vueuse/core')['usePointerSwipe']
|
|
||||||
const usePreferredColorScheme: typeof import('@vueuse/core')['usePreferredColorScheme']
|
|
||||||
const usePreferredContrast: typeof import('@vueuse/core')['usePreferredContrast']
|
|
||||||
const usePreferredDark: typeof import('@vueuse/core')['usePreferredDark']
|
|
||||||
const usePreferredLanguages: typeof import('@vueuse/core')['usePreferredLanguages']
|
|
||||||
const usePreferredReducedMotion: typeof import('@vueuse/core')['usePreferredReducedMotion']
|
|
||||||
const usePrevious: typeof import('@vueuse/core')['usePrevious']
|
|
||||||
const useRafFn: typeof import('@vueuse/core')['useRafFn']
|
|
||||||
const useRefHistory: typeof import('@vueuse/core')['useRefHistory']
|
|
||||||
const useResizeObserver: typeof import('@vueuse/core')['useResizeObserver']
|
|
||||||
const useRoute: typeof import('vue-router')['useRoute']
|
|
||||||
const useRouter: typeof import('vue-router')['useRouter']
|
|
||||||
const useScreenOrientation: typeof import('@vueuse/core')['useScreenOrientation']
|
|
||||||
const useScreenSafeArea: typeof import('@vueuse/core')['useScreenSafeArea']
|
|
||||||
const useScriptTag: typeof import('@vueuse/core')['useScriptTag']
|
|
||||||
const useScroll: typeof import('@vueuse/core')['useScroll']
|
|
||||||
const useScrollLock: typeof import('@vueuse/core')['useScrollLock']
|
|
||||||
const useSessionStorage: typeof import('@vueuse/core')['useSessionStorage']
|
|
||||||
const useShare: typeof import('@vueuse/core')['useShare']
|
|
||||||
const useSlots: typeof import('vue')['useSlots']
|
|
||||||
const useSorted: typeof import('@vueuse/core')['useSorted']
|
|
||||||
const useSpeechRecognition: typeof import('@vueuse/core')['useSpeechRecognition']
|
|
||||||
const useSpeechSynthesis: typeof import('@vueuse/core')['useSpeechSynthesis']
|
|
||||||
const useStepper: typeof import('@vueuse/core')['useStepper']
|
|
||||||
const useStorage: typeof import('@vueuse/core')['useStorage']
|
|
||||||
const useStorageAsync: typeof import('@vueuse/core')['useStorageAsync']
|
|
||||||
const useStyleTag: typeof import('@vueuse/core')['useStyleTag']
|
|
||||||
const useSupported: typeof import('@vueuse/core')['useSupported']
|
|
||||||
const useSwipe: typeof import('@vueuse/core')['useSwipe']
|
|
||||||
const useTemplateRefsList: typeof import('@vueuse/core')['useTemplateRefsList']
|
|
||||||
const useTextDirection: typeof import('@vueuse/core')['useTextDirection']
|
|
||||||
const useTextSelection: typeof import('@vueuse/core')['useTextSelection']
|
|
||||||
const useTextareaAutosize: typeof import('@vueuse/core')['useTextareaAutosize']
|
|
||||||
const useThrottle: typeof import('@vueuse/core')['useThrottle']
|
|
||||||
const useThrottleFn: typeof import('@vueuse/core')['useThrottleFn']
|
|
||||||
const useThrottledRefHistory: typeof import('@vueuse/core')['useThrottledRefHistory']
|
|
||||||
const useTimeAgo: typeof import('@vueuse/core')['useTimeAgo']
|
|
||||||
const useTimeout: typeof import('@vueuse/core')['useTimeout']
|
|
||||||
const useTimeoutFn: typeof import('@vueuse/core')['useTimeoutFn']
|
|
||||||
const useTimeoutPoll: typeof import('@vueuse/core')['useTimeoutPoll']
|
|
||||||
const useTimestamp: typeof import('@vueuse/core')['useTimestamp']
|
|
||||||
const useTitle: typeof import('@vueuse/core')['useTitle']
|
|
||||||
const useToNumber: typeof import('@vueuse/core')['useToNumber']
|
|
||||||
const useToString: typeof import('@vueuse/core')['useToString']
|
|
||||||
const useToggle: typeof import('@vueuse/core')['useToggle']
|
|
||||||
const useTransition: typeof import('@vueuse/core')['useTransition']
|
|
||||||
const useUrlSearchParams: typeof import('@vueuse/core')['useUrlSearchParams']
|
|
||||||
const useUserMedia: typeof import('@vueuse/core')['useUserMedia']
|
|
||||||
const useVModel: typeof import('@vueuse/core')['useVModel']
|
|
||||||
const useVModels: typeof import('@vueuse/core')['useVModels']
|
|
||||||
const useVibrate: typeof import('@vueuse/core')['useVibrate']
|
|
||||||
const useVirtualList: typeof import('@vueuse/core')['useVirtualList']
|
|
||||||
const useWakeLock: typeof import('@vueuse/core')['useWakeLock']
|
|
||||||
const useWebNotification: typeof import('@vueuse/core')['useWebNotification']
|
|
||||||
const useWebSocket: typeof import('@vueuse/core')['useWebSocket']
|
|
||||||
const useWebWorker: typeof import('@vueuse/core')['useWebWorker']
|
|
||||||
const useWebWorkerFn: typeof import('@vueuse/core')['useWebWorkerFn']
|
|
||||||
const useWindowFocus: typeof import('@vueuse/core')['useWindowFocus']
|
|
||||||
const useWindowScroll: typeof import('@vueuse/core')['useWindowScroll']
|
|
||||||
const useWindowSize: typeof import('@vueuse/core')['useWindowSize']
|
|
||||||
const watch: typeof import('vue')['watch']
|
|
||||||
const watchArray: typeof import('@vueuse/core')['watchArray']
|
|
||||||
const watchAtMost: typeof import('@vueuse/core')['watchAtMost']
|
|
||||||
const watchDebounced: typeof import('@vueuse/core')['watchDebounced']
|
|
||||||
const watchEffect: typeof import('vue')['watchEffect']
|
|
||||||
const watchIgnorable: typeof import('@vueuse/core')['watchIgnorable']
|
|
||||||
const watchOnce: typeof import('@vueuse/core')['watchOnce']
|
|
||||||
const watchPausable: typeof import('@vueuse/core')['watchPausable']
|
|
||||||
const watchPostEffect: typeof import('vue')['watchPostEffect']
|
|
||||||
const watchSyncEffect: typeof import('vue')['watchSyncEffect']
|
|
||||||
const watchThrottled: typeof import('@vueuse/core')['watchThrottled']
|
|
||||||
const watchTriggerable: typeof import('@vueuse/core')['watchTriggerable']
|
|
||||||
const watchWithFilter: typeof import('@vueuse/core')['watchWithFilter']
|
|
||||||
const whenever: typeof import('@vueuse/core')['whenever']
|
|
||||||
}
|
|
151
cfg.ts
151
cfg.ts
@ -1,151 +0,0 @@
|
|||||||
/**
|
|
||||||
*
|
|
||||||
* @author Ray <https://github.com/XiaoDaiGua-Ray>
|
|
||||||
*
|
|
||||||
* @date 2023-04-06
|
|
||||||
*
|
|
||||||
* @workspace ray-template
|
|
||||||
*
|
|
||||||
* @remark 今天也是元气满满撸代码的一天
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
*
|
|
||||||
* 系统配置文件入口
|
|
||||||
*
|
|
||||||
* 配置范围:
|
|
||||||
* - 构建: 开发构建、打包构建、预览构建、体积分析构建等
|
|
||||||
* - 系统: 根路由、标题、浏览器标题、别名等
|
|
||||||
* - 请求: 代理配置
|
|
||||||
*
|
|
||||||
* 如果需要新增相关内容, 需要在 src/types/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_PRIMARY_COLOR } from './src/appConfig/designConfig'
|
|
||||||
import {
|
|
||||||
PRE_LOADING_CONFIG,
|
|
||||||
ROOT_ROUTE,
|
|
||||||
SIDE_BAR_LOGO,
|
|
||||||
} from './src/appConfig/appConfig'
|
|
||||||
|
|
||||||
import type { AppConfigExport } from './src/types/cfg'
|
|
||||||
|
|
||||||
const config: AppConfigExport = {
|
|
||||||
/** 公共基础路径配置, 如果为空则会默认以 '/' 填充 */
|
|
||||||
base: '/ray-template/',
|
|
||||||
/** 配置首屏加载信息 */
|
|
||||||
preloadingConfig: PRE_LOADING_CONFIG,
|
|
||||||
/** 默认主题色(不可省略, 必填), 也用于 ejs 注入 */
|
|
||||||
appPrimaryColor: 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',
|
|
||||||
'./src/styles/theme.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
|
|
@ -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 = {
|
module.exports = {
|
||||||
|
ignores: [(commit) => commit.includes('init')],
|
||||||
extends: ['@commitlint/config-conventional'],
|
extends: ['@commitlint/config-conventional'],
|
||||||
rules: {
|
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': [
|
'type-enum': [
|
||||||
2,
|
2,
|
||||||
'always',
|
'always',
|
||||||
[
|
[
|
||||||
'bug',
|
'update',
|
||||||
'feat',
|
|
||||||
'fix',
|
'fix',
|
||||||
|
'feat',
|
||||||
|
'chore',
|
||||||
'docs',
|
'docs',
|
||||||
'style',
|
|
||||||
'refactor',
|
'refactor',
|
||||||
'test',
|
'test',
|
||||||
'chore',
|
'style',
|
||||||
'revert',
|
'version',
|
||||||
'merge',
|
'build',
|
||||||
|
'plugin',
|
||||||
],
|
],
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
14
components.d.ts
vendored
14
components.d.ts
vendored
@ -1,14 +0,0 @@
|
|||||||
// generated by unplugin-vue-components
|
|
||||||
// We suggest you to commit this file into source control
|
|
||||||
// Read more: https://github.com/vuejs/core/pull/3399
|
|
||||||
import '@vue/runtime-core'
|
|
||||||
|
|
||||||
export {}
|
|
||||||
|
|
||||||
declare module '@vue/runtime-core' {
|
|
||||||
export interface GlobalComponents {
|
|
||||||
RouterLink: typeof import('vue-router')['RouterLink']
|
|
||||||
RouterView: typeof import('vue-router')['RouterView']
|
|
||||||
TransitionComponent: typeof import('./src/components/RayTransitionComponent/TransitionComponent.vue')['default']
|
|
||||||
}
|
|
||||||
}
|
|
14
docker-compose.yml
Normal file
14
docker-compose.yml
Normal 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
365
eslint.config.mjs
Normal 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',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
]
|
49
index.html
49
index.html
@ -1,9 +1,12 @@
|
|||||||
<!DOCTYPE html>
|
<!doctype html>
|
||||||
<html lang="en">
|
<html lang="en">
|
||||||
<head>
|
<head>
|
||||||
<meta charset="UTF-8" />
|
<meta charset="UTF-8" />
|
||||||
<link rel="icon" type="image/svg+xml" href="/ray.svg" />
|
<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>
|
<title>Vite + Vue + TS</title>
|
||||||
</head>
|
</head>
|
||||||
<style>
|
<style>
|
||||||
@ -12,6 +15,27 @@
|
|||||||
--preloading-title-color: <%= preloadingConfig.titleColor %>;
|
--preloading-title-color: <%= preloadingConfig.titleColor %>;
|
||||||
--ray-theme-primary-fade-color: <%= appPrimaryColor.primaryFadeColor %>;
|
--ray-theme-primary-fade-color: <%= appPrimaryColor.primaryFadeColor %>;
|
||||||
--ray-theme-primary-color: <%= appPrimaryColor.primaryColor %>;
|
--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 {
|
#pre-loading-animation {
|
||||||
@ -20,13 +44,9 @@
|
|||||||
right: 0;
|
right: 0;
|
||||||
top: 0;
|
top: 0;
|
||||||
bottom: 0;
|
bottom: 0;
|
||||||
background-color: #ffffff;
|
|
||||||
color: var(--preloading-title-color);
|
color: var(--preloading-title-color);
|
||||||
text-align: center;
|
text-align: center;
|
||||||
}
|
background-color: var(--global-loading-bg-color);
|
||||||
|
|
||||||
.ray-template--dark #pre-loading-animation {
|
|
||||||
background-color: #2a3146;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#pre-loading-animation .pre-loading-animation__wrapper {
|
#pre-loading-animation .pre-loading-animation__wrapper {
|
||||||
@ -41,8 +61,9 @@
|
|||||||
#pre-loading-animation
|
#pre-loading-animation
|
||||||
.pre-loading-animation__wrapper
|
.pre-loading-animation__wrapper
|
||||||
.pre-loading-animation__wrapper-title {
|
.pre-loading-animation__wrapper-title {
|
||||||
font-size: 30px;
|
font-size: 32px;
|
||||||
padding-bottom: 48px;
|
padding-bottom: 48px;
|
||||||
|
font-weight: 500;
|
||||||
}
|
}
|
||||||
|
|
||||||
.pre-loading-animation__wrapper-loading {
|
.pre-loading-animation__wrapper-loading {
|
||||||
@ -91,6 +112,18 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</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>
|
<body>
|
||||||
<div id="app"></div>
|
<div id="app"></div>
|
||||||
<div id="pre-loading-animation">
|
<div id="pre-loading-animation">
|
||||||
|
42
mock/demo/person.mock.ts
Normal file
42
mock/demo/person.mock.ts
Normal 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
19
mock/shared/database.ts
Normal 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
57
mock/shared/utils.ts
Normal 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
|
||||||
|
}
|
171
package.json
Normal file → Executable file
171
package.json
Normal file → Executable file
@ -1,105 +1,134 @@
|
|||||||
{
|
{
|
||||||
"name": "ray-template",
|
"name": "ray-template",
|
||||||
"private": false,
|
"private": false,
|
||||||
"version": "3.3.3",
|
"version": "5.2.2",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
|
"engines": {
|
||||||
|
"node": "^18.0.0 || ^20.0.0 || >=22.0.0",
|
||||||
|
"pnpm": ">=9.0.0"
|
||||||
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "vite",
|
"dev": "vite",
|
||||||
"build": "vue-tsc --noEmit && vite build --mode production",
|
"build": "vue-tsc --noEmit && vite build",
|
||||||
"preview": "vite preview",
|
"preview": "vite preview",
|
||||||
"test": "vue-tsc --noEmit && vite build --mode test",
|
|
||||||
"dev-build": "vue-tsc --noEmit && vite build --mode development",
|
"dev-build": "vue-tsc --noEmit && vite build --mode development",
|
||||||
"report": "vue-tsc --noEmit && vite build --mode report",
|
"report": "vite build --mode report",
|
||||||
"prepare": "husky install"
|
"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": {
|
"lint-staged": {
|
||||||
"src/**/*.{vue,jsx,ts,tsx,json}": [
|
"*.{ts,tsx,json}": [
|
||||||
"prettier --write",
|
"prettier --write"
|
||||||
"eslint",
|
],
|
||||||
"git add"
|
"*.{ts,tsx,vue}": [
|
||||||
|
"eslint --fix"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@vueuse/core": "^9.1.0",
|
"@logicflow/core": "2.0.10",
|
||||||
"axios": "^1.2.0",
|
"@logicflow/extension": "2.0.14",
|
||||||
"crypto-js": "^4.1.1",
|
"@vueuse/core": "^13.1.0",
|
||||||
|
"axios": "^1.9.0",
|
||||||
|
"clipboard": "^2.0.11",
|
||||||
|
"crypto-js": "4.2.0",
|
||||||
"currency.js": "^2.0.4",
|
"currency.js": "^2.0.4",
|
||||||
"dayjs": "^1.11.7",
|
"dayjs": "^1.11.13",
|
||||||
"echarts": "^5.4.0",
|
"echarts": "^5.6.0",
|
||||||
|
"html-to-image": "1.11.13",
|
||||||
|
"interactjs": "1.10.27",
|
||||||
|
"jsbarcode": "3.11.6",
|
||||||
"lodash-es": "^4.17.21",
|
"lodash-es": "^4.17.21",
|
||||||
"naive-ui": "^2.34.3",
|
"mockjs": "1.1.0",
|
||||||
"pinia": "^2.0.17",
|
"naive-ui": "^2.42.0",
|
||||||
"pinia-plugin-persistedstate": "^2.4.0",
|
"pinia": "^3.0.3",
|
||||||
|
"pinia-plugin-persistedstate": "^4.4.1",
|
||||||
"print-js": "^1.6.0",
|
"print-js": "^1.6.0",
|
||||||
"qrcode.vue": "^3.3.4",
|
"vue": "^3.5.17",
|
||||||
"sass": "^1.54.3",
|
"vue-demi": "0.14.10",
|
||||||
"screenfull": "^6.0.2",
|
"vue-hooks-plus": "2.4.0",
|
||||||
"vue": "^3.2.37",
|
"vue-i18n": "^9.13.1",
|
||||||
"vue-i18n": "^9.2.2",
|
"vue-router": "^4.5.1",
|
||||||
"vue-router": "^4.1.3",
|
"vue3-next-qrcode": "3.0.2"
|
||||||
"vuedraggable": "^4.1.0",
|
|
||||||
"xlsx": "^0.18.5"
|
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@babel/core": "^7.20.2",
|
"@commitlint/cli": "19.7.1",
|
||||||
"@babel/eslint-parser": "^7.19.1",
|
"@commitlint/config-conventional": "19.7.1",
|
||||||
"@commitlint/cli": "^17.4.2",
|
"@eslint/js": "9.28.0",
|
||||||
"@commitlint/config-conventional": "^17.4.2",
|
"@interactjs/types": "1.10.27",
|
||||||
"@intlify/unplugin-vue-i18n": "^0.5.0",
|
"@intlify/unplugin-vue-i18n": "4.0.0",
|
||||||
"@types/crypto-js": "^4.1.1",
|
"@types/crypto-js": "4.2.2",
|
||||||
"@types/scrollreveal": "^0.0.8",
|
"@types/jsbarcode": "3.11.4",
|
||||||
"@typescript-eslint/eslint-plugin": "^5.42.1",
|
"@types/lodash-es": "4.17.12",
|
||||||
"@typescript-eslint/parser": "^5.42.1",
|
"@types/mockjs": "1.0.10",
|
||||||
"@vitejs/plugin-vue": "^4.2.3",
|
"@typescript-eslint/eslint-plugin": "8.24.0",
|
||||||
"@vitejs/plugin-vue-jsx": "^3.0.1",
|
"@typescript-eslint/parser": "8.24.0",
|
||||||
"autoprefixer": "^10.4.8",
|
"@vitejs/plugin-vue": "5.2.3",
|
||||||
"depcheck": "^1.4.3",
|
"@vitejs/plugin-vue-jsx": "4.1.2",
|
||||||
"eslint": "^8.0.1",
|
"@vitest/ui": "2.1.8",
|
||||||
"eslint-config-prettier": "^8.5.0",
|
"@vue/eslint-config-prettier": "10.1.0",
|
||||||
"eslint-config-standard-with-typescript": "^23.0.0",
|
"@vue/eslint-config-typescript": "14.2.0",
|
||||||
"eslint-plugin-import": "^2.25.2",
|
"@vue/test-utils": "2.4.6",
|
||||||
"eslint-plugin-n": "^15.0.0",
|
"autoprefixer": "10.4.21",
|
||||||
"eslint-plugin-prettier": "^4.2.1",
|
"depcheck": "1.4.7",
|
||||||
"eslint-plugin-promise": "^6.0.0",
|
"eslint": "9.20.1",
|
||||||
"eslint-plugin-react": "^7.31.10",
|
"eslint-config-prettier": "10.1.2",
|
||||||
"eslint-plugin-vue": "^9.7.0",
|
"eslint-plugin-prettier": "5.2.6",
|
||||||
"husky": "^8.0.3",
|
"eslint-plugin-vue": "9.32.0",
|
||||||
"lint-staged": "^13.1.0",
|
"globals": "16.0.0",
|
||||||
"postcss": "^8.1.0",
|
"happy-dom": "17.1.0",
|
||||||
"postcss-px-to-viewport": "^1.1.1",
|
"husky": "8.0.3",
|
||||||
"prettier": "^2.7.1",
|
"lint-staged": "15.4.3",
|
||||||
"rollup-plugin-visualizer": "^5.8.3",
|
"postcss": "8.5.4",
|
||||||
"svg-sprite-loader": "^6.0.11",
|
"postcss-px-to-viewport-8-with-include": "1.2.2",
|
||||||
"typescript": "*",
|
"prettier": "3.5.3",
|
||||||
"unplugin-auto-import": "^0.11.0",
|
"rollup-plugin-gzip": "4.0.1",
|
||||||
"unplugin-vue-components": "^0.22.0",
|
"sass": "1.86.3",
|
||||||
"vite": "^4.3.8",
|
"svg-sprite-loader": "6.0.11",
|
||||||
"vite-plugin-compression": "^0.5.1",
|
"typescript": "5.8.3",
|
||||||
"vite-plugin-ejs": "^1.6.4",
|
"unocss": "66.3.3",
|
||||||
"vite-plugin-eslint": "^1.8.1",
|
"unplugin-auto-import": "19.1.2",
|
||||||
"vite-plugin-imp": "^2.3.1",
|
"unplugin-vue-components": "0.28.0",
|
||||||
"vite-plugin-inspect": "^0.7.26",
|
"vite": "6.3.5",
|
||||||
"vite-plugin-svg-icons": "^2.0.1",
|
"vite-bundle-analyzer": "0.16.0",
|
||||||
"vite-svg-loader": "^3.4.0",
|
"vite-plugin-cdn2": "1.1.0",
|
||||||
"vue-tsc": "^1.0.9"
|
"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 -->",
|
"description": "<!-- ALL-CONTRIBUTORS-BADGE:START - Do not remove or modify this section -->",
|
||||||
"main": "index.js",
|
"main": "index.ts",
|
||||||
"repository": {
|
"repository": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "git+https://github.com/XiaoDaiGua-Ray/xiaodaigua-ray.github.io.git"
|
"url": "git+https://github.com/XiaoDaiGua-Ray/xiaodaigua-ray.github.io.git"
|
||||||
},
|
},
|
||||||
"keywords": [
|
"keywords": [
|
||||||
"ray-template",
|
"ray-template",
|
||||||
"vue3.2模板",
|
|
||||||
"vue3-tsx-vite-pinia",
|
"vue3-tsx-vite-pinia",
|
||||||
"ray template",
|
"ray template",
|
||||||
"Ray Template",
|
"vite",
|
||||||
|
"vue3",
|
||||||
"admin template",
|
"admin template",
|
||||||
"中后台模板"
|
"中后台模板"
|
||||||
],
|
],
|
||||||
"author": "Ray",
|
"license": "MIT",
|
||||||
"license": "ISC",
|
"author": {
|
||||||
|
"name": "Ray",
|
||||||
|
"url": "https://github.com/XiaoDaiGua-Ray"
|
||||||
|
},
|
||||||
"bugs": {
|
"bugs": {
|
||||||
"url": "https://github.com/XiaoDaiGua-Ray/xiaodaigua-ray.github.io/issues"
|
"url": "https://github.com/XiaoDaiGua-Ray/xiaodaigua-ray.github.io/issues"
|
||||||
},
|
},
|
||||||
|
10101
pnpm-lock.yaml
generated
Normal file
10101
pnpm-lock.yaml
generated
Normal file
File diff suppressed because it is too large
Load Diff
@ -8,24 +8,32 @@ module.exports = {
|
|||||||
'ff > 31',
|
'ff > 31',
|
||||||
'ie >= 8',
|
'ie >= 8',
|
||||||
'last 10 versions',
|
'last 10 versions',
|
||||||
|
'not dead',
|
||||||
],
|
],
|
||||||
grid: true,
|
grid: true,
|
||||||
},
|
},
|
||||||
// 'postcss-px-to-viewport': {
|
// 为了适配 postcss8.x 版本的转换库
|
||||||
// /** 视窗的宽度(设计稿的宽度) */
|
'postcss-px-to-viewport-8-with-include': {
|
||||||
// viewportWidth: 1920,
|
// 横屏时使用的视口宽度
|
||||||
// /** 视窗的高度(设计稿高度, 一般无需指定) */
|
landscapeWidth: 1920,
|
||||||
// viewportHeight: 1080,
|
// 视窗的宽度(设计稿的宽度)
|
||||||
// /** 指定 px 转换为视窗单位值的小数位数 */
|
viewportWidth: 1920,
|
||||||
// unitPrecision: 3,
|
// 指定 px 转换为视窗单位值的小数位数
|
||||||
// /** 指定需要转换成的视窗单位 */
|
unitPrecision: 3,
|
||||||
// viewportUnit: 'vw',
|
// 指定需要转换成的视窗单位
|
||||||
// /** 指定不转换为视窗单位的类 */
|
viewportUnit: 'vw',
|
||||||
// selectorBlackList: ['.ignore'],
|
// 制定字体转换单位
|
||||||
// /** 小于或等于 1px 不转换为视窗单位 */
|
fontViewportUnit: 'vw',
|
||||||
// minPixelValue: 1,
|
// 指定不转换为视窗单位的类
|
||||||
// /** 允许在媒体查询中转换 px */
|
selectorBlackList: ['.ignore'],
|
||||||
// mediaQuery: false,
|
// 小于或等于 1px 不转换为视窗单位
|
||||||
// },
|
minPixelValue: 1,
|
||||||
|
// 允许在媒体查询中转换 px
|
||||||
|
mediaQuery: false,
|
||||||
|
// 忽略某些文件夹下的文件或特定文件,例如 'node_modules' 下的文件
|
||||||
|
exclude: /node_modules/,
|
||||||
|
// 指定一个空的文件夹,避免影响到无需转换的文件
|
||||||
|
include: [],
|
||||||
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
101
src/App.tsx
101
src/App.tsx
@ -1,96 +1,29 @@
|
|||||||
import RayGlobalProvider from '@/components/RayGlobalProvider/index'
|
|
||||||
import { RouterView } from 'vue-router'
|
import { RouterView } from 'vue-router'
|
||||||
import GlobalSpin from '@/spin/index'
|
import AppNaiveGlobalProvider from '@/app-components/provider/AppNaiveGlobalProvider'
|
||||||
import LockScreen from '@/components/AppComponents/AppLockScreen/index'
|
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'
|
||||||
|
|
||||||
import { getCache } from '@/utils/cache'
|
import { APP_GLOBAL_LOADING } from '@/app-config'
|
||||||
import { get } from 'lodash-es'
|
|
||||||
import { useSetting } from '@/store'
|
|
||||||
import { addClass, removeClass, addStyle, colorToRgba } from '@/utils/element'
|
|
||||||
|
|
||||||
const App = defineComponent({
|
export default defineComponent({
|
||||||
name: 'App',
|
name: 'App',
|
||||||
setup() {
|
|
||||||
const settingStore = useSetting()
|
|
||||||
|
|
||||||
const { themeValue } = storeToRefs(settingStore)
|
|
||||||
|
|
||||||
/** 同步主题色变量至 body, 如果未获取到缓存值则已默认值填充 */
|
|
||||||
const syncPrimaryColorToBody = () => {
|
|
||||||
const {
|
|
||||||
appPrimaryColor: { primaryColor, primaryFadeColor },
|
|
||||||
} = __APP_CFG__ // 默认主题色
|
|
||||||
const body = document.body
|
|
||||||
|
|
||||||
const primaryColorOverride = getCache('piniaSettingStore', 'localStorage')
|
|
||||||
const _p = get(
|
|
||||||
primaryColorOverride,
|
|
||||||
'primaryColorOverride.common.primaryColor',
|
|
||||||
)
|
|
||||||
const _fp = colorToRgba(_p, 0.3)
|
|
||||||
|
|
||||||
/** 设置全局主题色 css 变量 */
|
|
||||||
body.style.setProperty('--ray-theme-primary-color', _p || primaryColor)
|
|
||||||
body.style.setProperty(
|
|
||||||
'--ray-theme-primary-fade-color',
|
|
||||||
_fp || primaryFadeColor,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
/** 隐藏加载动画 */
|
|
||||||
const hiddenLoadingAnimation = () => {
|
|
||||||
/** pre-loading-animation 是默认 id */
|
|
||||||
const el = document.getElementById('pre-loading-animation')
|
|
||||||
|
|
||||||
if (el) {
|
|
||||||
addStyle(el, {
|
|
||||||
display: 'none',
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
syncPrimaryColorToBody()
|
|
||||||
hiddenLoadingAnimation()
|
|
||||||
|
|
||||||
/** 切换主题时, 同步更新 body class 以便于进行自定义 css 配置 */
|
|
||||||
watch(
|
|
||||||
() => themeValue.value,
|
|
||||||
(newData) => {
|
|
||||||
/**
|
|
||||||
*
|
|
||||||
* 初始化时根据当前主题色进行初始化 body 的 class 属性
|
|
||||||
*
|
|
||||||
* 根据 themeValue 进行初始化
|
|
||||||
*/
|
|
||||||
const body = document.body
|
|
||||||
const darkClassName = 'ray-template--dark'
|
|
||||||
const lightClassName = 'ray-template--light'
|
|
||||||
|
|
||||||
newData
|
|
||||||
? removeClass(body, lightClassName)
|
|
||||||
: removeClass(body, darkClassName)
|
|
||||||
|
|
||||||
addClass(body, newData ? darkClassName : lightClassName)
|
|
||||||
},
|
|
||||||
{
|
|
||||||
immediate: true,
|
|
||||||
},
|
|
||||||
)
|
|
||||||
},
|
|
||||||
render() {
|
render() {
|
||||||
return (
|
return (
|
||||||
<RayGlobalProvider>
|
<AppNaiveGlobalProvider>
|
||||||
<LockScreen />
|
<AppVersionProvider />
|
||||||
|
<AppLockScreen />
|
||||||
<GlobalSpin>
|
<AppStyleProvider />
|
||||||
|
<AppWatermarkProvider />
|
||||||
|
<AppGlobalSpin>
|
||||||
{{
|
{{
|
||||||
default: () => <RouterView />,
|
default: () => <RouterView />,
|
||||||
description: () => 'lodaing...',
|
description: () => APP_GLOBAL_LOADING,
|
||||||
}}
|
}}
|
||||||
</GlobalSpin>
|
</AppGlobalSpin>
|
||||||
</RayGlobalProvider>
|
</AppNaiveGlobalProvider>
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
export default App
|
|
||||||
|
14
src/__ray-template/README.md
Normal file
14
src/__ray-template/README.md
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
# \_\_ray-template
|
||||||
|
|
||||||
|
该包用于管理一些模板的管理,例如:
|
||||||
|
|
||||||
|
- validAppRootPath: 检查模板 `appRootPath` 是否配置正确
|
||||||
|
- validLocal: 检查模板 `localConfig` 是否配置正确
|
||||||
|
|
||||||
|
## 拓展
|
||||||
|
|
||||||
|
当你需要在做一些定制化操作的时候,可以尝试在这个包里做一些事情。
|
||||||
|
|
||||||
|
最后在 `main.ts` 中导入并且调用即可。
|
||||||
|
|
||||||
|
> 出于一些考虑,并没有做自动化导入调用,所以需要自己手动来。(好吧,其实就是我懒-,-)
|
16
src/__ray-template/index.ts
Normal file
16
src/__ray-template/index.ts
Normal 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()
|
||||||
|
}
|
28
src/__ray-template/valid/valid-app-root-path.ts
Normal file
28
src/__ray-template/valid/valid-app-root-path.ts
Normal 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.`,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
99
src/__ray-template/valid/valid-local.ts
Normal file
99
src/__ray-template/valid/valid-local.ts
Normal 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()
|
||||||
|
}
|
27
src/api/demo/mock/person.ts
Normal file
27
src/api/demo/mock/person.ts
Normal 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,
|
||||||
|
})
|
||||||
|
}
|
45
src/api/demo/test.ts
Normal file
45
src/api/demo/test.ts
Normal file
@ -0,0 +1,45 @@
|
|||||||
|
/**
|
||||||
|
*
|
||||||
|
* 该方法演示如何封装一个通用请求方法
|
||||||
|
*
|
||||||
|
* 步骤:
|
||||||
|
* 1. 定义一个方法(见下面的 demo 方法)
|
||||||
|
* 2. 暴露该函数
|
||||||
|
* 3. 如果该方法在 setup 环境中使用,则可以使用 useHookPlusRequest 包裹该方法,即可便捷使用该请求函数。如果请求方法在非 setup 环境中使用,直接使用即可
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { request } from '@/axios'
|
||||||
|
|
||||||
|
import type { BasicResponse } from '@/types'
|
||||||
|
|
||||||
|
interface AxiosTestResponse extends UnknownObjectKey {
|
||||||
|
data: UnknownObjectKey[]
|
||||||
|
city?: string
|
||||||
|
}
|
||||||
|
|
||||||
|
interface JSONPlaceholder {
|
||||||
|
completed: boolean
|
||||||
|
id: number
|
||||||
|
title: string
|
||||||
|
userId: number
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @returns 测试
|
||||||
|
*
|
||||||
|
* @method get
|
||||||
|
*/
|
||||||
|
export const getWeather = (city: string) => {
|
||||||
|
return request<AxiosTestResponse>({
|
||||||
|
url: `https://www.tianqiapi.com/api?version=v9&appid=23035354&appsecret=8YvlPNrz&city=${city}`,
|
||||||
|
method: 'get',
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
export const getTypicCode = () => {
|
||||||
|
return request<JSONPlaceholder>({
|
||||||
|
url: 'https://jsonplaceholder.typicode.com/todos/1',
|
||||||
|
method: 'get',
|
||||||
|
})
|
||||||
|
}
|
6
src/app-components/README.md
Normal file
6
src/app-components/README.md
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
## 描述
|
||||||
|
|
||||||
|
该包存放与模板深度绑定的组件:
|
||||||
|
|
||||||
|
- app:存放与模板数据绑定的组件
|
||||||
|
- provider:存放模板注入类组件
|
72
src/app-components/app/AppAvatar/index.tsx
Normal file
72
src/app-components/app/AppAvatar/index.tsx
Normal file
@ -0,0 +1,72 @@
|
|||||||
|
/**
|
||||||
|
*
|
||||||
|
* 系统管理员头像与名称
|
||||||
|
*
|
||||||
|
* 头像展示基于 naive ui Avatar 组件, 继承该组件所有属性与方法
|
||||||
|
* 默认读取本地 session catch 缓存
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { NAvatar, NButton, NFlex } from 'naive-ui'
|
||||||
|
|
||||||
|
import { avatarProps } from 'naive-ui'
|
||||||
|
import { useSigningGetters } from '@/store'
|
||||||
|
|
||||||
|
import type { PropType } from 'vue'
|
||||||
|
import type { AvatarProps, FlexProps } from 'naive-ui'
|
||||||
|
|
||||||
|
const AppAvatar = defineComponent({
|
||||||
|
name: 'AppAvatar',
|
||||||
|
props: {
|
||||||
|
...avatarProps,
|
||||||
|
cursor: {
|
||||||
|
type: String,
|
||||||
|
default: 'auto',
|
||||||
|
},
|
||||||
|
spaceSize: {
|
||||||
|
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() {
|
||||||
|
const { getSigningCallback } = useSigningGetters()
|
||||||
|
|
||||||
|
return {
|
||||||
|
getSigningCallback,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
render() {
|
||||||
|
const { getSigningCallback, avatarSize, spaceSize, $props, vertical } = this
|
||||||
|
|
||||||
|
return (
|
||||||
|
<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>
|
||||||
|
)
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
export default AppAvatar
|
@ -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 组件
|
* 基于 Naive UI Spin 组件
|
||||||
*
|
*
|
||||||
* 使用方法
|
* 使用方法
|
||||||
* 1. import { setSpin } from '@/spin'
|
* 1. import { setVariable } from '@/hooks'
|
||||||
* 2. setSpin(true) | setSpin(false)
|
* 2. setVariable('globalSpinning', true) | setVariable('globalSpinning', false)
|
||||||
*
|
*
|
||||||
* 仅需按照上述步骤实现全屏加载动画
|
* 仅需按照上述步骤实现全屏加载动画
|
||||||
*
|
*
|
||||||
@ -29,9 +18,9 @@
|
|||||||
import { NSpin } from 'naive-ui'
|
import { NSpin } from 'naive-ui'
|
||||||
|
|
||||||
import { spinProps } 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({
|
const GlobalSpin = defineComponent({
|
||||||
name: 'GlobalSpin',
|
name: 'GlobalSpin',
|
||||||
@ -40,8 +29,9 @@ const GlobalSpin = defineComponent({
|
|||||||
},
|
},
|
||||||
setup() {
|
setup() {
|
||||||
const overrides = {
|
const overrides = {
|
||||||
opacitySpinning: '0',
|
opacitySpinning: '0.3',
|
||||||
}
|
}
|
||||||
|
const spinValue = getVariableToRefs('globalSpinning')
|
||||||
|
|
||||||
return {
|
return {
|
||||||
spinValue,
|
spinValue,
|
||||||
@ -51,11 +41,14 @@ const GlobalSpin = defineComponent({
|
|||||||
render() {
|
render() {
|
||||||
return (
|
return (
|
||||||
<NSpin
|
<NSpin
|
||||||
{...this.$props}
|
{...(this.$props as SpinProps)}
|
||||||
show={this.spinValue}
|
show={this.spinValue}
|
||||||
themeOverrides={this.overrides}
|
themeOverrides={this.overrides}
|
||||||
|
style="height: var(--html-height)"
|
||||||
>
|
>
|
||||||
{{ ...this.$slots }}
|
{{
|
||||||
|
...this.$slots,
|
||||||
|
}}
|
||||||
</NSpin>
|
</NSpin>
|
||||||
)
|
)
|
||||||
},
|
},
|
26
src/app-components/app/AppLockScreen/appLockVar.ts
Normal file
26
src/app-components/app/AppLockScreen/appLockVar.ts
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
import { useStorage } from '@vueuse/core'
|
||||||
|
import { APP_CATCH_KEY } from '@/app-config'
|
||||||
|
|
||||||
|
const appLockScreen = useStorage(
|
||||||
|
APP_CATCH_KEY.isAppLockScreen,
|
||||||
|
false,
|
||||||
|
window.localStorage,
|
||||||
|
{
|
||||||
|
mergeDefaults: true,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
const useAppLockScreen = () => {
|
||||||
|
const setLockAppScreen = (bool: boolean) => {
|
||||||
|
appLockScreen.value = bool
|
||||||
|
}
|
||||||
|
|
||||||
|
const getLockAppScreen = () => appLockScreen.value
|
||||||
|
|
||||||
|
return {
|
||||||
|
setLockAppScreen,
|
||||||
|
getLockAppScreen,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default useAppLockScreen
|
@ -0,0 +1,101 @@
|
|||||||
|
import { NInput, NFormItem, NButton } from 'naive-ui'
|
||||||
|
import AppAvatar from '@/app-components/app/AppAvatar'
|
||||||
|
import { RForm } from '@/components'
|
||||||
|
|
||||||
|
import useAppLockScreen from '@/app-components/app/AppLockScreen/appLockVar'
|
||||||
|
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 { InputInst } from 'naive-ui'
|
||||||
|
|
||||||
|
const LockScreen = defineComponent({
|
||||||
|
name: 'LockScreen',
|
||||||
|
setup() {
|
||||||
|
const [register, { validate }] = useForm()
|
||||||
|
const inputInstRef = useTemplateRef<InputInst | null>('inputInstRef')
|
||||||
|
|
||||||
|
const { setLockAppScreen } = useAppLockScreen()
|
||||||
|
const { updateSettingState } = useSettingActions()
|
||||||
|
|
||||||
|
const state = reactive({
|
||||||
|
lockCondition: useCondition(),
|
||||||
|
})
|
||||||
|
|
||||||
|
const lockScreen = () => {
|
||||||
|
validate().then(() => {
|
||||||
|
setLockAppScreen(true)
|
||||||
|
updateSettingState('lockScreenSwitch', false)
|
||||||
|
setStorage(
|
||||||
|
APP_CATCH_KEY.appLockScreenPasswordKey,
|
||||||
|
encrypt(state.lockCondition.lockPassword),
|
||||||
|
'localStorage',
|
||||||
|
)
|
||||||
|
|
||||||
|
state.lockCondition = useCondition()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
nextTick(() => {
|
||||||
|
inputInstRef.value?.focus()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
return {
|
||||||
|
...toRefs(state),
|
||||||
|
lockScreen,
|
||||||
|
register,
|
||||||
|
inputInstRef,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
render() {
|
||||||
|
const { register } = this
|
||||||
|
|
||||||
|
return (
|
||||||
|
<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>
|
||||||
|
)
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
export default LockScreen
|
@ -0,0 +1,193 @@
|
|||||||
|
import '../../index.scss'
|
||||||
|
|
||||||
|
import { NInput, NFormItem, NButton, NFlex } from 'naive-ui'
|
||||||
|
import AppAvatar from '@/app-components/app/AppAvatar'
|
||||||
|
import { RForm } from '@/components'
|
||||||
|
|
||||||
|
import dayjs from 'dayjs'
|
||||||
|
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'
|
||||||
|
|
||||||
|
export default defineComponent({
|
||||||
|
name: 'UnlockScreen',
|
||||||
|
setup() {
|
||||||
|
const [register, { validate }] = useForm()
|
||||||
|
|
||||||
|
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 = 'YYYY-MM-DD'
|
||||||
|
const DDD_FORMAT = 'ddd'
|
||||||
|
|
||||||
|
const state = reactive({
|
||||||
|
lockCondition: useCondition(),
|
||||||
|
HH_MM: dayjs().format(HH_MM_FORMAT),
|
||||||
|
AM_PM: dayjs().format(AM_PM_FORMAT),
|
||||||
|
YY_MM_DD: dayjs().format(YY_MM_DD_FORMAT),
|
||||||
|
DDD: dayjs().format(DDD_FORMAT),
|
||||||
|
})
|
||||||
|
const dayInterval = setInterval(() => {
|
||||||
|
state.HH_MM = dayjs().format(HH_MM_FORMAT)
|
||||||
|
state.AM_PM = dayjs().format(AM_PM_FORMAT)
|
||||||
|
}, 6_000)
|
||||||
|
const yearInterval = setInterval(() => {
|
||||||
|
state.YY_MM_DD = dayjs().format(YY_MM_DD_FORMAT)
|
||||||
|
state.DDD = dayjs().format(DDD_FORMAT)
|
||||||
|
}, 86_400_000)
|
||||||
|
|
||||||
|
const toSigningFn = () => {
|
||||||
|
removeStorage(APP_CATCH_KEY.appLockScreenPasswordKey, 'localStorage')
|
||||||
|
updateSettingState('lockScreenSwitch', false)
|
||||||
|
setTimeout(() => {
|
||||||
|
logout()
|
||||||
|
}, 100)
|
||||||
|
}
|
||||||
|
|
||||||
|
const backToSigning = () => {
|
||||||
|
window.$dialog.warning({
|
||||||
|
title: '警告',
|
||||||
|
content: '是否返回到登陆页并且重新登录',
|
||||||
|
positiveText: '确定',
|
||||||
|
negativeText: '重新登录',
|
||||||
|
onPositiveClick: toSigningFn,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const unlockScreen = () => {
|
||||||
|
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)
|
||||||
|
updateSettingState('lockScreenSwitch', false)
|
||||||
|
removeStorage(APP_CATCH_KEY.appLockScreenPasswordKey, 'localStorage')
|
||||||
|
|
||||||
|
state.lockCondition = useCondition()
|
||||||
|
} else {
|
||||||
|
window.$message.warning('密码错误,请重新输入')
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
onBeforeUnmount(() => {
|
||||||
|
clearInterval(dayInterval)
|
||||||
|
clearInterval(yearInterval)
|
||||||
|
})
|
||||||
|
|
||||||
|
return {
|
||||||
|
...toRefs(state),
|
||||||
|
backToSigning,
|
||||||
|
unlockScreen,
|
||||||
|
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__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="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} <span>{DDD}</span> <span>{AM_PM}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
},
|
||||||
|
})
|
107
src/app-components/app/AppLockScreen/index.scss
Normal file
107
src/app-components/app/AppLockScreen/index.scss
Normal file
@ -0,0 +1,107 @@
|
|||||||
|
.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'] {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
& form[class*='n-form'] {
|
||||||
|
margin: 24px 0px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
& .app-lock-screen__unlock {
|
||||||
|
.app-lock-screen__unlock__content {
|
||||||
|
position: relative;
|
||||||
|
@include flexCenter;
|
||||||
|
flex-direction: column;
|
||||||
|
|
||||||
|
& .app-lock-screen__unlock__content-wrapper {
|
||||||
|
position: fixed;
|
||||||
|
inset: 0px;
|
||||||
|
|
||||||
|
& .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;
|
||||||
|
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: bolder;
|
||||||
|
z-index: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
& .app-lock-screen__unlock__content-input {
|
||||||
|
width: 260px;
|
||||||
|
z-index: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
& .app-lock-screen__unlock__content-date {
|
||||||
|
position: fixed;
|
||||||
|
width: 100%;
|
||||||
|
font-size: 3rem;
|
||||||
|
text-align: center;
|
||||||
|
font-weight: 500;
|
||||||
|
bottom: 24px;
|
||||||
|
z-index: 1;
|
||||||
|
|
||||||
|
& .current-year,
|
||||||
|
& .current-date span {
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
40
src/app-components/app/AppLockScreen/index.tsx
Normal file
40
src/app-components/app/AppLockScreen/index.tsx
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
import { RModal } from '@/components'
|
||||||
|
import LockScreen from './components/LockScreen'
|
||||||
|
|
||||||
|
import { useSettingGetters, useSettingActions } from '@/store'
|
||||||
|
|
||||||
|
const AppLockScreen = defineComponent({
|
||||||
|
name: 'AppLockScreen',
|
||||||
|
setup() {
|
||||||
|
const { updateSettingState } = useSettingActions()
|
||||||
|
const { getLockScreenSwitch } = useSettingGetters()
|
||||||
|
const lockScreenSwitchRef = computed({
|
||||||
|
get: () => getLockScreenSwitch.value,
|
||||||
|
set: (val) => {
|
||||||
|
updateSettingState('lockScreenSwitch', val)
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
return {
|
||||||
|
lockScreenSwitchRef,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
render() {
|
||||||
|
return (
|
||||||
|
<RModal
|
||||||
|
v-model:show={this.lockScreenSwitchRef}
|
||||||
|
transformOrigin="center"
|
||||||
|
show
|
||||||
|
autoFocus={false}
|
||||||
|
maskClosable={false}
|
||||||
|
closeOnEsc={false}
|
||||||
|
preset="dialog"
|
||||||
|
title="锁定屏幕"
|
||||||
|
>
|
||||||
|
<LockScreen />
|
||||||
|
</RModal>
|
||||||
|
)
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
export default AppLockScreen
|
20
src/app-components/app/AppLockScreen/shared.ts
Normal file
20
src/app-components/app/AppLockScreen/shared.ts
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
import type { InputInst } from 'naive-ui'
|
||||||
|
import type { Ref } from 'vue'
|
||||||
|
|
||||||
|
/** 统一的校验锁屏密码校验规则 */
|
||||||
|
export const rules = {
|
||||||
|
lockPassword: {
|
||||||
|
required: true,
|
||||||
|
message: '请输入正确格式密码',
|
||||||
|
min: 6,
|
||||||
|
max: 12,
|
||||||
|
trigger: ['input'],
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 锁屏密码参数 */
|
||||||
|
export const useCondition = () => {
|
||||||
|
return {
|
||||||
|
lockPassword: null,
|
||||||
|
}
|
||||||
|
}
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user