Compare commits

...

59 Commits
v4.8.0 ... main

Author SHA1 Message Date
XiaoDaiGua-Ray
49af61a339 version: v5.2.2 2025-08-09 19:04:29 +08:00
XiaoDaiGua-Ray
4bce5f7713 feat: 修改版本信息 2025-06-26 17:50:40 +08:00
XiaoDaiGua-Ray
34c20d4be7 fix: 修复一些问题 2025-06-26 17:48:07 +08:00
XiaoDaiGua-Ray
c28c353f7d version: v5.2.1 2025-06-26 17:45:31 +08:00
XiaoDaiGua-Ray
c68491fca9 version: v5.2.0 2025-06-06 21:28:56 +08:00
XiaoDaiGua-Ray
0f2193cc14 fix: 修复 vite 打包时,分包策略问题导致构建循环引用问题 2025-05-06 09:01:11 +08:00
XiaoDaiGua-Ray
ba6ceef0dc fix: 修复构建导致报错 2025-04-30 17:50:45 +08:00
XiaoDaiGua-Ray
5e9ffb14a3 version: 5.1.0 发布 2025-04-29 21:20:54 +08:00
XiaoDaiGua-Ray
eb5a6aa9e2 version: v5.1.0 2025-02-25 17:38:30 +08:00
XiaoDaiGua-Ray
674539edd3 fix: 一些已知问题修复 2025-01-19 10:38:13 +08:00
XiaoDaiGua-Ray
ff0bcb5022
Merge pull request #30 from admover/patch-3
Update test.ts
2025-01-15 20:17:19 +08:00
admover
d3d98190a3
Update test.ts
拼写修正
2025-01-15 17:36:05 +08:00
XiaoDaiGua-Ray
0bb707bba0 version: v5.0.10 2025-01-15 16:32:36 +08:00
XiaoDaiGua-Ray
4bfdbccd88 version: v5.0.9 2025-01-03 21:44:32 +08:00
XiaoDaiGua-Ray
3b2bba391e version: v5.0.8 2024-12-20 18:23:55 +08:00
XiaoDaiGua-Ray
7647508935 version: v5.0.7 2024-12-07 01:04:16 +08:00
XiaoDaiGua-Ray
852d7ca90a version: v5.0.6 2024-11-23 12:42:28 +08:00
XiaoDaiGua-Ray
2c84e3ce4c version: v5.0.5 2024-11-14 21:10:46 +08:00
XiaoDaiGua-Ray
83e0c19ba9 fix: 修复 cicd 部署问题 2024-11-09 14:58:54 +08:00
XiaoDaiGua-Ray
ff1a67c843 version: v5.0.4 2024-11-09 14:55:41 +08:00
XiaoDaiGua-Ray
8f3969268a version: v5.0.3 2024-10-27 17:01:53 +08:00
XiaoDaiGua-Ray
7783872ef6 version: v5.0.2 2024-10-23 20:48:01 +08:00
XiaoDaiGua-Ray
707300774d version: v5.0.1 2024-10-23 11:31:25 +08:00
XiaoDaiGua-Ray
2f42571b3b version: 5.0.0 2024-10-20 01:15:12 +08:00
XiaoDaiGua-Ray
73792144a8 fix: fix demo bug 2024-10-16 09:54:02 +08:00
XiaoDaiGua-Ray
11cbf8bca3 version: v4.9.7 2024-10-16 09:51:56 +08:00
XiaoDaiGua-Ray
f9473114e7
Merge pull request #27 from XiaoDaiGua-Ray/ray-template-electron
version: v4.9.6
2024-09-27 16:01:05 +08:00
XiaoDaiGua-Ray
7394e0bf30 version: v4.9.6 2024-09-27 15:59:01 +08:00
XiaoDaiGua-Ray
d12fcd18b6 version: v4.9.5 2024-09-18 14:41:15 +08:00
XiaoDaiGua-Ray
60ed09a0c5 version: v4.9.4 2024-08-31 14:51:22 +08:00
XiaoDaiGua-Ray
fa8d52601f version: v4.9.3 2024-08-23 17:45:19 +08:00
XiaoDaiGua-Ray
3f7e3722fd version: v4.9.2 2024-07-27 12:40:41 +08:00
XiaoDaiGua-Ray
ab6593f022 version: v4.9.1 2024-07-27 12:16:29 +08:00
XiaoDaiGua-Ray
d306ac8804 version: v4.9.0 2024-07-09 16:29:04 +08:00
XiaoDaiGua-Ray
8405cc5709 version: v4.8.9 2024-06-28 16:38:13 +08:00
XiaoDaiGua-Ray
6975af2368 version: v4.8.8 2024-06-24 10:25:48 +08:00
XiaoDaiGua-Ray
b0cd545c99 version: v4.8.7 2024-06-11 10:35:01 +08:00
XiaoDaiGua-Ray
7ca9663cb1 version: v4.8.6 2024-05-27 17:45:28 +08:00
XiaoDaiGua-Ray
85f0d43d7e fix: 一些bug修复 2024-05-27 17:40:02 +08:00
XiaoDaiGua-Ray
09315473a3 fix: 修复workflow错误 2024-05-17 15:33:12 +08:00
XiaoDaiGua-Ray
52dc6038da fix: 修复useTheme.spec.ts单测失败问题 2024-05-17 15:30:08 +08:00
XiaoDaiGua-Ray
27db2293f3 version: v4.8.5 2024-05-17 15:26:25 +08:00
XiaoDaiGua-Ray
5b035815eb version: v4.8.4 2024-05-09 17:29:28 +08:00
XiaoDaiGua-Ray
b06006b442 update: 添加macOS环境 2024-05-08 16:37:20 +08:00
XiaoDaiGua-Ray
557ab0467a version: v4.8.3 2024-05-04 18:31:57 +08:00
XiaoDaiGua-Ray
e5c16b3497 update: update workflows 2024-05-02 10:29:33 +08:00
XiaoDaiGua-Ray
f97b25e626 update: update engines.node version 2024-05-02 10:24:53 +08:00
XiaoDaiGua-Ray
8097296f8f fix: 修复workflow指定node版本错误语法 2024-05-02 10:22:46 +08:00
XiaoDaiGua-Ray
43df660ab6 update: 更新workflow配置,手动指定node版本 2024-05-02 10:16:33 +08:00
XiaoDaiGua-Ray
4e14f6a02d update: update the pnpm-lock.yaml file 2024-04-30 00:49:45 +08:00
XiaoDaiGua-Ray
472518af99 fix: 修复docs-deploy.yaml文件中的错误 2024-04-30 00:12:27 +08:00
XiaoDaiGua-Ray
d9ea6cbdf5 feat: 新增prettierignore文件忽略配置 2024-04-30 00:06:46 +08:00
XiaoDaiGua-Ray
01287743e7 update: 更新cicd配置文件node版本 2024-04-29 23:40:41 +08:00
XiaoDaiGua-Ray
41dce4920f fix: 修复baseURL配置错误问题 2024-04-29 23:33:39 +08:00
XiaoDaiGua-Ray
1ee3ce0500 version: v4.8.2 2024-04-29 23:29:11 +08:00
XiaoDaiGua-Ray
ae67b7b8b9 feat: 新增jsbarcode cdn配置 2024-04-14 23:52:00 +08:00
XiaoDaiGua-Ray
87d0a7a4af v4.8.1 2024-04-14 23:47:27 +08:00
XiaoDaiGua-Ray
683367cfd8
Merge pull request #25 from zhengxiongwei520/main
fix: 修复RSegment组件Popover warning提示
2024-04-14 23:03:32 +08:00
bearer
a6f7843ff9 fix: 修改warning提示 2024-04-14 22:50:00 +08:00
384 changed files with 18267 additions and 11840 deletions

View File

@ -1,18 +0,0 @@
dist/*
node_modules/*
auto-imports.d.ts
components.d.ts
.gitignore
.vscode
public
yarn.*
vite-env.*
.prettierrc.*
visualizer.*
visualizer.html
.env.*
src/locales/lang
.depcheckrc
src/app-config/echart-themes/**/*.json
*.md
src/icons/*.svg

View File

@ -1,213 +0,0 @@
/* eslint-env node */
module.exports = {
root: true,
env: {
browser: true,
node: true,
},
ignorePatterns: ['node_modules/', 'dist/'],
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',
],
parser: 'vue-eslint-parser',
parserOptions: {
ecmaVersion: 2020,
sourceType: 'module',
parser: '@typescript-eslint/parser',
ecmaFeatures: {
jsx: true,
tsx: true,
},
},
plugins: ['vue', '@typescript-eslint', 'prettier'],
globals: {
defineProps: 'readonly',
defineEmits: 'readonly',
defineExpose: 'readonly',
withDefaults: 'readonly',
defineOptions: 'readonly',
defineModel: 'readonly',
},
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,
},
], // 强制导入类型显示标注 `import type xxx from 'xxx'`
'@typescript-eslint/no-empty-interface': [
'error',
{
allowSingleExtends: true,
},
],
'accessor-pairs': 2, // 强制同时存在 `get` 与 `set`
'constructor-super': 0, // 强制子类构造函数中使用 `super` 调用父类的构造函数
'default-case': 2, // `switch` 中强制含有 `default`
eqeqeq: [2, 'allow-null'], // 强制使用严格判断 `===`
'no-alert': 0, // 禁止使用 `alert`、`confirm`
'no-array-constructor': 2, // 禁止使用数组构造器
'no-bitwise': 0, // 禁止使用按位运算符
'no-caller': 1, // 禁止使用 `arguments.caller`、`arguments.callee`
'no-catch-shadow': 2, // 禁止 `catch` 子句参数与外部作用域变量同名
'no-class-assign': 2, // 禁止给类赋值
'no-cond-assign': 2, // 禁止在条件表达式中使用赋值语句
'no-const-assign': 2, // 禁止修改 `const` 声明的变量
'no-constant-condition': 2, // 禁止在条件中使用常量表达式 `if(true)`、`if(1)`
'no-dupe-keys': 2, // 在创建对象字面量时不允许 `key` 重复
'no-dupe-args': 2, // 函数参数不能重复
'no-duplicate-case': 2, // `switch` 中的 `case` 标签不能重复
'no-eval': 1, // 禁止使用 `eval`
'no-ex-assign': 2, // 禁止给 `catch` 语句中的异常参数赋值
'no-extend-native': 2, // 禁止扩展 `native` 对象
'no-extra-bind': 2, // 禁止不必要的函数绑定
'no-extra-boolean-cast': [
'error',
{
enforceForLogicalOperands: true,
},
], // 禁止不必要的 `bool` 转换
'no-extra-parens': 0, // 禁止非必要的括号
semi: [
'error',
'never',
{
beforeStatementContinuationChars: 'always',
},
],
'no-fallthrough': 1, // 禁止 `switch` 穿透
'no-func-assign': 2, // 禁止重复的函数声明
'no-implicit-coercion': [
'error',
{
allow: ['!!', '~'],
},
], // 禁止隐式转换
'no-implied-eval': 2, // 禁止使用隐式 `eval`
'no-invalid-regexp': 2, // 禁止无效的正则表达式
'no-invalid-this': 2, // 禁止无效的 `this`
'no-irregular-whitespace': 2, // 禁止含有不合法空格
'no-iterator': 2, // 禁止使用 `__iterator__ ` 属性
'no-label-var': 2, // `label` 名不能与 `var` 声明的变量名相同
'no-labels': 2, // 禁止标签声明
'no-lone-blocks': 2, // 禁止不必要的嵌套块
'no-multi-spaces': 1, // 禁止使用多余的空格
'no-multiple-empty-lines': [
'error',
{
max: 2,
},
], // 空行最多不能超过 `2` 行
'no-new-func': 2, // 禁止使用 `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`
'use-isnan': 2, // 强制使用 isNaN 判断 NaN
'no-multi-assign': 2, // 禁止连续声明变量
'prefer-arrow-callback': 2, // 强制使用箭头函数作为回调
curly: ['error', 'all'],
'vue/multi-word-component-names': [
'error',
{
ignores: [],
},
],
'vue/no-use-v-if-with-v-for': [
'error',
{
allowUsingIterationVar: false,
},
],
'vue/require-v-for-key': ['error'],
'vue/require-valid-default-prop': ['error'],
'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.',
},
],
},
}

12
.gitattributes vendored
View File

@ -1,2 +1,14 @@
# 将换行符设置为lf # 将换行符设置为lf
* text eol=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

View File

@ -1,4 +1,3 @@
name: ray-template documents deploy name: ray-template documents deploy
on: on:
@ -15,15 +14,15 @@ jobs:
- name: Checkout - name: Checkout
uses: actions/checkout@v3 uses: actions/checkout@v3
- name: Install Node.js 18.x - name: Install Node.js 22.x
uses: actions/setup-node@v3 uses: actions/setup-node@v3
with: with:
node-version: 18.x node-version: 22.x
- uses: pnpm/action-setup@v2 - uses: pnpm/action-setup@v2
name: Install pnpm name: Install pnpm
with: with:
version: 8 version: 9
run_install: false run_install: false
- name: Install dependencies - name: Install dependencies
@ -36,4 +35,4 @@ jobs:
uses: JamesIves/github-pages-deploy-action@4.1.4 uses: JamesIves/github-pages-deploy-action@4.1.4
with: with:
branch: dist branch: dist
folder: dist/production-dist folder: dist/production

View File

@ -2,14 +2,13 @@ on:
- push - push
- pull_request - pull_request
jobs: jobs:
cache-and-install: cache-and-install:
runs-on: ${{ matrix.os }} runs-on: ${{ matrix.os }}
strategy: strategy:
fail-fast: false fail-fast: false
matrix: matrix:
node-version: [ 18.x ] node-version: [22.x]
os: [ubuntu-latest, windows-latest, macos-latest] os: [ubuntu-latest, windows-latest, macos-latest]
experimental: [true] experimental: [true]
@ -25,7 +24,7 @@ jobs:
- uses: pnpm/action-setup@v2 - uses: pnpm/action-setup@v2
name: Install pnpm name: Install pnpm
with: with:
version: 8 version: 9
run_install: false run_install: false
- name: Get pnpm store directory - name: Get pnpm store directory

1
.gitignore vendored
View File

@ -15,6 +15,7 @@ dist/
*.local *.local
visualizer.* visualizer.*
.eslintcache .eslintcache
.history
# Editor directories and files # Editor directories and files
.idea .idea

2
.nvmrc
View File

@ -1 +1 @@
v18.18.2 v22.12.0

View File

@ -9,3 +9,4 @@ yarn.*
visualizer.* visualizer.*
visualizer.html visualizer.html
.env.* .env.*
*-lock.yaml

View File

@ -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,
@ -17,6 +16,5 @@ module.exports = {
proseWrap: 'preserve', // 使用默认的折行标准 proseWrap: 'preserve', // 使用默认的折行标准
htmlWhitespaceSensitivity: 'css', // 根据显示样式决定 `html` 要不要折行 htmlWhitespaceSensitivity: 'css', // 根据显示样式决定 `html` 要不要折行
endOfLine: 'lf', // 换行符使用 `lf`, endOfLine: 'lf', // 换行符使用 `lf`,
bracketLine: false,
singleAttributePerLine: false, singleAttributePerLine: false,
} }

13
.vscode/settings.json vendored
View File

@ -1,4 +1,5 @@
{ {
"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,
@ -19,25 +20,33 @@
"alias-skip.allowedsuffix": ["ts", "tsx"], "alias-skip.allowedsuffix": ["ts", "tsx"],
"alias-skip.rootpath": "package.json", "alias-skip.rootpath": "package.json",
"cSpell.words": [ "cSpell.words": [
"baomitu",
"bezier", "bezier",
"Cascader",
"Clickoutside", "Clickoutside",
"codabar",
"commitmsg", "commitmsg",
"crossorigin",
"datetimerange", "datetimerange",
"domtoimage", "domtoimage",
"EDITMSG", "EDITMSG",
"iife", "iife",
"internalkey", "internalkey",
"jsbarcode",
"linebreak", "linebreak",
"logicflow",
"macarons", "macarons",
"menutag", "menutag",
"ndata", "ndata",
"persistedstate", "persistedstate",
"pharmacode",
"Popselect", "Popselect",
"precommit", "precommit",
"siderbar", "siderbar",
"snapline",
"stylelint", "stylelint",
"unocss",
"WUJIE", "WUJIE",
"zlevel" "zlevel"
], ]
"peacock.color": "#007fff"
} }

View File

@ -1,4 +1,628 @@
# CHANGE LOG ## 5.2.2
## Feats
- `RForm` 组件相关
- 新增 `submitWhenEnter` 配置项,允许在按下回车键时自动触发表单的校验,如果校验成功则会自动触发 `onFinish` 事件
- 新增 `onFinish` 配置项,允许在表单校验成功后自动触发的事件
- 新增 `autocomplete` 配置项,允许配置表单的自动完成功能,默认配置为 `off`
- 新增 `loading` 配置项,允许配置表单的加载状态
- 新增 `loadingDescription` 配置项,允许配置表单的加载状态的描述
- `useForm` 相关
- 新增 `validateTargetField` 方法,允许验证指定表单项的规则
- 初始化方法现在支持传入函数,允许动态获取表单的初始化值与规则
- `formModel` 方法现在会默认联合 `Recordable` 类型,获取初始化类型中未获取到的类型时,默认推到为 `any` 类型
- 新增了 `formConditionRef` 属性,现在可以在内部解构获取一个 `ref` 包裹的响应式初始化表单对象值
- 新增了 `updateFormCondition` 方法,允许更新表单的值,该方法会覆盖初始化值
- 更新依赖为主流版本
- 新增 `unocss` 原子化样式库,但是不推荐全量使用,仅作为一些简单的样式片段使用,否则在调试的时候将会是灾难
> 新增 `unocss` 后,在使用 `ProTable` 组件的流体高度最外层父元素配置时,可以便捷的配置 `h-full` 即可。
## 5.2.1
## Feats
- `RTablePro` 组件相关
- 新增 `runAsyncTableRequest` 方法,与 `runTableRequest` 方法功能一致,但是返回 `Promise` 对象
- 现在不允许使用 `useTemplateRef` 方法注册 `dom` 模板引用,约定强制使用 `useTablePro` 方法的 `register` 方法注册 `hook` 使用相关方法
- `useTablePro` 方法新增 `getTableProConfig` 方法,与 `useTable` 方法的 `getTableConfig` 方法功能一致,获取 `RTablePro` 组件额外注入配置
- `useTable` 方法新增 `getTableConfig` 方法,获取 `RTable` 组件额外注入配置
- 更新包为主流版本
- `vue-router` 因为在 [4.4.1](https://github.com/vuejs/router/blob/main/packages/router/CHANGELOG.md#441-2024-07-31) 版本中有破坏性的更新,所以在 `jsx` 函数式组件使用 `this.$route`, `this.$router` 会提示类型报错,所以现在强制约定需要使用 `useRoute`, `useRouter` 方法显示的声明与使用
- 更新 `naive-ui` 版本至 `2.42.0`
- 更新 `vue` 版本至 `3.5.17`
- `useForm` 方法新增 `reset` 方法,允许重置表单值,该方法依赖 `useForm` 方法的初始化 `formModel` 参数,所以请确保初始化 `formModel` 参数
## 5.2.0
一些破坏性更新,请谨慎更新。
## Feats
- 更新 `vue` 版本至 `3.5.16`
- 更新 `vite` 版本至 `6.3.5`
- `RTablePro` 组件相关
- `runTableRequest` 方法现在支持传递 `reset` 参数,配置是否重置分页请求
- `runTableRequest` 方法新增 `excludeParams` 配置项,允许排除指定的请求参数
- `onTablePaginationUpdate` 方法参数返回值由返回函数改为直接返回值
- 新增 `paginationPrefix` 配置项,允许自定义分页器前缀,在国际化需求可能会有用
- 新增 `flexAutoHeight` 配置项,默认关闭,允许配置表格是否自动继承高度,但是要结合 `css flex` 属性使用
> 如果你是使用 `NFlex` 组件结合 `RTablePro` 或者 `RTable` 组件使用,需要配置 `Flex` 组件的 `vertical` 属性,并且设置 `class``flex-vertical`,即可便捷实现该效果。否则你需要设置 `css flex` 相关属性(可以参考 Demo2的示例。
```tsx
import { RTablePro } from '@/components'
import { NFlex } from 'naive-ui'
const Demo1 = () => {
return (
<NFlex vertical class="flex-vertical">
<RTablePro flexAutoHeight />
</NFlex>
)
}
const Demo2 = () => {
return (
<div
class="flex-vertical"
style="height: 100%; display: flex; flex-direction: column;"
>
<RTablePro flexAutoHeight />
</div>
)
}
```
- 新增 `getDateByNaiveDatePicker` 方法,便捷获取 `naive-ui``DatePicker` 组件的日期值
- `Recordable` 类型新增 `symbol`, `number` 类型作为 `key` 支持
- `RCollapse` 组件相关
- 默认配置 `responsive` 配置项为 `screen` 响应模式
- 默认配置 `cols` 配置项为 `4 xs:1 s:2 m:2 l:4 xl:4 2xl:6`,虽然目前的预设已经足够使用,但你也可以高度自定义需求
- `types`
- 新增 `GlobalDataTableColumns` 类型,用于声明全局 `DataTableColumns` 类型
- 新增 `GlobalRecordable` 类型,用于声明全局 `Recordable` 类型
## Fixes
- 修复 `RTablePro` 组件 `print` 方法打印内容错误的问题
## 5.1.0
## Feats
- 更新 `vite` 版本至 `5.3.3`
## Fixes
- 修复 `chunksCopilot` 方法判断不准确导致 `node_modules` 库被拆分到 `hooks` 分包重复的问题
## 5.0.10
## Feats
- `RDraggableCard` 组件现在不会在抛出获取 `dom` 失败的异常,因为可能存在异步组件加载的可能
- `RModal`, `useModal` 方法,移除 `dad` 相关所有配置,使用 `draggable` 配置项替代
- 刷新的样式现在会跟随主题变化
- 锁屏密码现在会进行加密存储,并且会进行校验处理了
- 新增 `decrypt`, `decrypt` 方法,放置于 `utils/c` 包中
## Fixes
- 修复因为错误的注册全局事件,导致事件污染的问题,但是默认的 `ctrl + k`, `cmd + k` 快捷键依旧保留为全局按键
## 5.0.9
## Feats
- `RDraggableCard` 组件
- 新增 `restrictionElement` 配置项,允许设置拖拽限制元素
- 新增 `padding` 配置项,允许配置元素初始化位置的间隔值
- `defaultPosition` 配置项新增 `top-left`, `top-right`, `bottom-left`, `bottom-right` 配置项,允许配置元素初始化位置
- `RTablePro` 组件
- 现在会自动删除重复的请求参数
- 暴露 `resetTablePagination` 方法,允许手动重置表格分页
- `logout` 方法现在会在执行的时候,清空所有的 `router-route`
- 更新依赖为主流版本
## 5.0.8
## Feats
- 修改 `menuTagOptions` 的缓存方式,现在会缓存至 `sessionStorage` 中,兼容可能多系统版本部署与多开系统页面标签页冲突的问题
- 新增 `RDraggableCard` 组件
- 更新 `vite` 版本至 `6.0.4`
## Fixes
- 修复 `updateObjectValue` 方法对于对象值判断不准确的问题
- 修复 `SettingDrawer` 组件初始化时,没有正确初始化 `settingStore` 的问题
- 修复 `RTable` 组件在未设置 `title``tool``false` 时,导致 `headerStyle` 样式会高一些的问题
## 5.0.7
## Feats
- 更新 `vue` 版本至 `3.5.13`
- 更新 `vite` 版本至 `6.0.3`
- 更新 `naive-ui` 版本至 `2.40.3`
- 更新包依赖为主流依赖
- 更新 `eslint` 版本至 `9.11.0`,并且同步修改 `eslint` 相关配置方式,使用 `eslint.config.mjs` 文件替代
- 更新默认 `node` 版本至 `22.11.0`
- `RCollapseGrid` 组件新增 `actionSpan` 配置项,配置操作区域列数
- `usePagination` 方法新增 `pageChange`, `pageSizeChange` 回调函数,允许在更新分页页码与每页条数的时候,执行自定义回调;用于取代被移除的 `onUpdatePage`, `onUpdatePageSize` 方法
- 移除 `appNaiveUIThemeOverridesCommon` 配置项,现在统一使用 `appNaiveUIThemeOverrides` 配置项
- 优化整体风格样式
## Fixes
- 修复 `useDomToImage` 方法的类型推导问题
- 修复主题切换时,`naive-ui` 主题色覆盖不生效的问题
## 5.0.6
## Feats
- 新增 `useChartProvider` 方法,允许注入 `RCharts` 组件配置
- 更新 `echarts` 版本至 `5.5.1`
- 更新 `vue` 版本至 `3.5.13`
- 更新 `@vueuse/core` 版本至 `11.2.0`
- 修改 `SettingDrawer` 组件的 `defaultOptions` 配置项管理方式,现在迁移至 `store.setting` 包中
- 重构 `cache` 工具模块,更有好的类型推导、更少的代码量
- 重构 `precision` 工具模块,更好的类型推导、更少的代码量
- 重写 `updateObjectValue` 方法,现在类型提示更加准确
- 全局使用 `useTemplateRef`, `shallowRef` 方法替代 `ref` 注册模板引用,减少不必要的响应式代理
- 优化 `MenuTag` 组件的关闭按钮样式
- `LockScreen` 组件新增头像展示
- `AppAvatar` 组件现在默认获取 `avatar` 字段为空的时候,展示名字的首字
- 优化 `UnlockScreen` 组件样式,现在会根据主题自动调整背景颜色
- 优化内容区域过度动画效果
## Fixes
- 修复 `404` 页面【返回】按钮不能准确返回的问题
- 修复 `usePagination.getCallback` 方法类型丢失问题;修复该方法获取实时回调不准确的问题
- 修复初始化时,菜单滚动条不能准确滚动到当前激活项的问题
- 修复 `UnlockScreen` 组件在白色主题下,导致样式显示差异问题,现在统一为黑色主题配置覆盖
- 修复 `LockScreen` 组件在退出锁屏时,没有及时更新 `localStorage` 缓存的问题
- 修复 `setupDayjs` 初始化不准确的问题
## 5.0.5
## Feats
- 新增 `GLOBAL_CLASS_NAMES` 配置项
- 新增 `canSkipRoute` 方法,用于初始化系统菜单时,自动获取可跳转的路由,避免权限系统列表中无权限路由跳转导致异常的问题
- 优化 `useElementFullscreen` 方法的过渡效果
- `useElementFullscreen` 新增 `isFullscreen` 属性,标识当前元素是否处于网页全屏状态
## Fixes
- 修复锁屏逻辑问题
- 修复菜单有时候不能正常的展开被激活项的问题
- 修复 `useSiderBar``close` 问题
## 5.0.4
`ts` 版本与 `eslint` 解析插件版本更新至最新版,并且同步解决了以前历史遗留的一些问题。
并且,在该版本做了一些全局注入方式调整,请谨慎更新。
## Feats
- 移除 `vite-plugin-compression` 插件,使用 `rollup-plugin-gzip` 代替
- 更新 `@vitejs/plugin-vue-jsx` 版本至 `4.0.1`
- 优化注释
- 默认设置 `ContentWrapper``content-wrapper` 内容展示区域的宽高为 `100%`,继承父容器的宽高;该样式会自动的计算,也就是说会自动的适配不同尺寸的屏幕输出与判断是否显示 `FeatureWrapper`, `FooterWrapper`
- 更新 `@typescript-eslint/eslint-plugin`, `@typescript-eslint/parser` 版本至 `8.13.0`
- 更新 `typescript` 版本至 `5.6.3`
- 更新 `vue-tsc` 版本至 `2.1.10`
- 更新 `pnpm` 包管理器版本至 `9.12.3`
- 新增 `RFlow` 基础流程图组件(后期有时间会逐步加强该组件)
- 移除 `useElementFullscreen` 方法 `currentWindowSize` 返回值
- 新增 `--html-height`, `--html-width` 的全局 `css var` 属性,实时获取浏览器的尺寸
- 样式注入现在由注入至 `body` 改为注入至 `html`,避免 `teleport` 传送至 `body` 外的元素不能使用全局样式的问题
- `useBadge.show` 方法新增 `extraOption` 配置项,允许在显示 `badge` 到时候,传入额外的 `options` 配置项
## Fixes
- 修复菜单折叠后,`SiderBarLogo` 标题样式丢失问题
- 修复 `useModal` 因为 `Omit` 原因导致类型丢失问题,现在直接使用 `ModalProps` 作为类型
- 修复 `useModal` 创建全屏 `card` 的时候,内容区域边距样式被覆盖的问题,现在会尊重原有的 `card` 样式
## 5.0.3
个性化配置能力再次提升。
## Feats
- `SettingDrawer` 组件重构
## 5.0.2
## Feats
- 暴露 `setupAppMenu` 方法,提供自定义菜单渲染时机能力
## Fixes
- 修复 `MenuTag` 右键菜单不能被选中的问题
- 修复 `Menu` 设置 `iconSize`, `iconCollapseSize` 等属性,不能及时刷新渲染的问题,现在新增手动刷新操作按钮
> 菜单渲染是很复杂、很耗时的操作,所以在权衡以后还是考虑使用手动刷新操作方式更新菜单状态。
## 5.0.1
本次更新灵感来自于 `vben admin 5` 项目。
## Feats
- 更新了 `Menu`, `MenuTag`, `SiderBar` 样式细节
- 调整 `components`, `components-pro` 分包,现在都统一到 `components` 下,以 `base`, `pro` 进行区分,避免导出导入麻烦
- `MenuTag` 新增了一些操作方式
- `SiderBarLogo` 样式调整
## Fixes
- 新增 `tableFlexHeight` 配置项属性修复 `RTable` 组件因为不再透传至 `Table` 导致设置 `flexHeight` 进行动态高度设置失败的问题
- 修复 `RTable` 错误的 `title` 属性注入问题
- 修复 `MenuTag` 频繁右键会导致闪烁出现右键菜单的问题
## 5.0.0
## Feats
- `RTable` 组件现在默认 `attrs` 透传不会再透传至 `Table` 而是透传至容器 `Card`
- `AppAvatar` 组件获取用户信息由 `localStorage` 缓存获取变更为 `getSigningCallback` 响应式获取
- 移除 `SUPER_ADMIN` 默认值
- 移除 `APP_KEEP_ALIVE` 配置项,迁移至 `settingStore`
- 移除 `SIDE_BAR_LOGO_DEFAULT` 配置项,迁移至 `settingStore`
- 移除 `APP_MENU_CONFIG` 配置项,迁移至 `settingStore`
- 移除所有的 `ray-template` 相关注释信息
- `SettingDrawer` 新增更多配置项,允许通过配置入口配置更多的配置项
- 新增 `DatePicker`, `InputNumber` 样式,默认设置 `width``100%`
- 新增 `updateObjectValue` 方法,并且全局替换重复类似方法
- `useTheme` 新增 `syncSystemTheme` 方法,允许同步系统主题
- 新增 `ThemeSegment` 主题切换组件
- 移除 `watchOnce` 方法引入,使用原生 `watch` 方法替代
- 取消 `SettingDrawer` 自动聚焦可选中元素
## 4.9.7
从该版本开始,默认关闭了 `cdn` 构建生产环境;因为国内厂商真心不给力。
## Feats
- 更新 `vue` 版本至 `3.5.12`
- 更新 `@vueuse/core` 版本至 `11.1.0`
- 更新 `naive-ui` 版本至 `2.40.1`
- 更新 `MenuTag` 一些东西,移除了无用的 `ref` 注册,简化了代码;现在允许出现 `rootPath` 标签的关闭按钮了
- `useElementFullscreen` 新增 `currentWindowSize` 返回项,获取当前的窗口尺寸
- `RCollapseGrid` 组件默认开启 `bordered`
- `RCollapseGrid onUpdateValue` 方法更名为 `onUpdateOpen`
- `RTable` 组件默认开启 `wrapperBordered`
- `RTable` 组件默认开启 `bordered`
- 新增 `clearSigningCallback` 方法
- `vite.custom.config` 新增 `cdn` 配置项,是否启用 `cdn` 构建项目
- 配置 `cdn``false`,因为国内厂商更新资源速度有点慢,导致预览失败
- `Layout` 层注入 `--window-width`, `--window-height`, `css var` 属性
- 稳定 `Layout` 层的 `css var` 属性
## Fixes
- 修复 `MenuTag` 鼠标移入并且加右键菜单操作时,会导致关闭按钮图标消失的问题
- 修复 `useElementFullscreen` 方法在窗口尺寸更新时,全屏元素失败的问题
- 修复 `MenuTag` 注入 `MENU_TAG_DATA` 错误的问题
- 修复 `Layout Content` 默认高度获取失败问题
- 修复 `RCollapseGrid` 组件自定义 `collapse` 插槽折叠失败的问题
## 4.9.6
由于 `cdn` 厂商更新 `cdn` 资源太慢的缘故,所以目前 `vue` 的版本只有 `3.5.3` 会导致构建线上生产环境报错的问题。
## Feats
- 更新 `vue` 版本至 `3.5.8`
- `useDomToImage` 相关
- 使用 `html-to-image` 替换 `dom-to-image` 底层依赖库
- 同步补全 `html-to-image` 所有新特性至该方法
- 同步修改 `printDom` 方法
- 类型提示现在会更加友好,`create` 方法会根据 `imageType` 自动推导转换结果的类型
- 移除 `DomToImageResult` 类型
## Fixes
- 修复 `RCollapseGrid` 组件折叠失败的问题
## 4.9.5
天元突破,红莲螺岩。
兼容 `vue3.5` 版本的更新。
## Feats
- 更新脚手架依赖为主流依赖
- 更新 `vue` 版本至 `3.5.6`
- 更新 `vite` 版本至 `5.4.3`
- 更新 `pinia-plugin-persistedstate` 版本至 `4.0.1`,并且兼容破坏性更新改动
- `RChart` 组件相关
- 小重构该组件,移除多个 `echart` 缓存,现在有且仅有一个
- 减少 `watch` 监听项
- 使用 `useTemplateRef` 方法替代 `ref` 注册 `dom`
- 现在预设 `card` 时,`chart` 图会更加的醒目一些
- 优化 `demo` 展示
- 现在会拦截 `aria` 属性,现在仅允许通过 `showAria` 配置项管理无障碍模式
- 优化无障碍模式渲染,现在不会重新渲染整个图表,而是通过 `setOptions` 方式更新图表
- `useChart` 方法相关
- `isDispose` 方法更名为 `isDisposed`
## Fixes
- 修复 `useChart` 方法相关方法中 `dispose` 方法执行不生效的问题
- 修复 `RChart``loading` 不能跟随主题变化的问题
## 4.9.4
## Feats
- `cdn` 相关的一些配置
- 将 `cdn` 源更新为 `baomitu`,为了避免因为墙的影响导致加载缓慢的问题
- 新增一些插件的 `cdn` 配置
- `useTable`, `useTablePro` 中的 `print` 方法新增传递 `options` 配置项,配置打印,允许配置 `dom-to-image``print-js`,详情可以查看 `PrintDomOptions` 类型说明。
- `RCollapse` 组件新增 `collapse` 插槽,允许自定义【展开】与【收起】状态
## Fixes
- 修复 `useTablePro``print` 方法丢失的问题
## 4.9.3
## Feats
- 更新 `vue` 版本至 `3.4.38`
- 更新 `vite` 版本至 `5.4.1`
- 调整 `RCollapseGrid` 支持 `actionAlign` 配置型,配置按钮垂直方向,默认为 `end`
- `MenuTag` 组件
- 调整 `MenuTag` 滚动条样式,现在将它隐藏了
- 优化关闭按钮样式
- `RTable` 新增 `renderWrapperHeader` 配置项,配置外层容器 `header` 是否渲染
- `postcss` 配置 `not dead`,忽略兼容已经无需兼容的浏览器
- `RChart` 组件 `setOptions` 方法配置项默认不启用 `merge` 模式
- 调整 `header` 的样式,增加了一点点间隙
- `useDevice` 新增 `observer` 配置项,可以自定义观察回调
- 新增 `components` 包,助力简化业务开发
- 新增 `RTablePro` 组件,大幅简化中后台带有过滤请求条件的表格显示业务
- 新增 `RCollapse` 组件,允许折叠过滤条件
## Fixes
- 移除 `postcss-px-to-viewport-8-plugin` 插件,使用 `postcss-px-to-viewport-8-with-include` 替换,修复 `include` 失效问题
- 修复 `useElementFullscreen` 在退出时,没有正确的回滚 `zIndex` 的问题
- 修复 `RChart` 配置 `setChartOptions` 不生效的问题
## 4.9.2
## Feats
- `useDevice` 支持 `observer` 配置项
- `postcss` 自动尺寸转换插件配置更新
## Fixes
- 修复菜单在小尺寸屏幕并且处于折叠状态,导致显示不完整的问题
## 4.9.1
更新核心依赖版本为主流版本。
## Feats
- 更新 `axios` 版本至 `1.7.2`
- 更新 `vue-hooks-plus` 版本至 `2.2.1`
- 更新 `naive-ui` 版本至 `2.39.0`
- 更新 `vue` 版本至 `3.4.34`
- 更新 `vite` 版本至 `5.3.5`
- 更新 `@vitejs/plugin-vue` 版本至 `5.1.0`
- 更新 `@vitejs/plugin-vue-jsx` 版本至 `4.0.0`
- 调整解锁锁屏头像样式
- `RModal` 在设置为拖拽时,如果未设置头(也就是 `title`)属性,则会失效
## Fixes
- 修复 `__APP_CFG__` 类型丢失问题
- 修复 `RTable` 设置 `tool``false` 时,单独设置 `striped`, `bordered` 时不生效的问题
## 4.9.0
主要修复了一个歧义问题,就是新开页面输入 `url`,或者是 `window.open` 打开当前系统的页面时,会导致初始化异常的问题。这是因为以前的 `appMenu`, `appSigning` 相关的缓存都是防止与 `sessionStorage`,但是该缓存并不能共享,所以导致了这个问题。在该版本中将该缓存调整为了 `localStorage`
## Feats
- 移除 `vite-plugin-imp` 插件
- 更新 `vue` 版本至 `3.4.31`
- 更新 `vite` 版本至 `5.3.3`
- 将 `appPiniaMenuStore`, `appPiniaSigningStore` 缓存由 `sessionStorage` 更改为 `localStorage` 缓存
- 现在在模拟退出的时候,会清理 `appPiniaMenuStore`, `appPiniaSigningStore``localStorage` 缓存
- 将 `route` 待提取字段单独抽离,统一维护为 `pickRouteRecordNormalizedConstant`
## Fixes
- 修复 `RTable` 自定义 `tool` 时抛出的优化警告问题
- 修复在复制 `url` 打开新标签页输入网址后(包括新开页面),会导致项目初始化异常的问题
## 4.8.9
## Fixes
- 修复 `useCheckedRowKeys``getRows` 方法在异步分页获取数据的时候,会导致切换页面后勾选项丢失问题
## 4.8.8
## Feats
- 更新 `vite` 版本至 `5.3.1`
- 更新 `vue` 版本至 `3.4.30`
- `FetchErrorFunction` 类型在你自定义请求拦截器插件处理错误请求的时候,可能会用上
## Fixes
- 修复 `useCheckedRowKeys``getRows` 方法类型丢失问题
## 4.8.7
## Feats
- 更新 `vite` 版本至 `5.2.12`
- 更新 `vue` 版本至 `3.4.27`
- 更新 `vue-hooks-plus` 版本至 `2.2.0`
- 更新 `@types/lodash-es` 版本至 `4.17.12`
- `FetchFunction` 移除,使用 `AxiosResponseInterceptor`, `AxiosRequestInterceptor` 替代插件类型推导
- `echarts macarons theme` 优化
- `RChart` 组件的默认 `loading` 效果支持自动跟随主题切换
- `SIDE_BAR_LOGO` 更名为 `SIDE_BAR_LOGO_DEFAULT`,现在支持在 `setting store` 中动态更新,以满足更广泛的需求
## Fixes
- 修复 `primaryColorOverride` 配置项不生效问题
- 修复面包屑在 `sameLevel` 的情况下,可能会被覆盖问题
## 4.8.6
## Fixes
- 修复动态路由的情况下,始终会被跳转到 `404` 页面的问题
- 修复 `RIcon` 组件 `color` 配置项无效的问题
- 修复 `svg-icon` 页面可能会在更新 `icon` 后出现报错的问题
- 修复 `updateDocumentTitle` 方法可能会更新标题出错的问题
- 修复菜单的折叠问题
## 4.8.5
## Feats
- 更新 `vue-router` 版本至 `4.3.2`
- 更新 `vue-hooks-plus` 版本至 `2.0.3`
- `usePagination` 方法
- 新增 `resetPagination` 方法,用于重置分页器
- `RBarcode` 组件,默认启用 `watchText` 配置项,用于监听 `text` 内容变化
- `RMoreDropdown` 组件,新增 `icon` 自定义配置图标,依赖 `RIcon` 组件实现
- `buildOptions` 方法现在会自动根据 `mode` 进行自动构建生成包了
- `RTable hooks`
- `UseCheckedRowKeysOptions`
- 新增 `table column` 参数,自动获取当前表格列配置是否启用了选项模式(单选、多选)
- `selectKey` 方法现在在单选模式下,只会选择一条数据,最后一次选择的会覆盖上次的
- `useTable`
- 新增 `print` 方法
- `useTheme` 方法
- `changeDarkTheme` 更名为 `darkTheme`
- `changeLightTheme` 更名为 `lightTheme`
- `getAppTheme` 方法返回值做了准确的 `ts` 签名,并且新增 `themeLabelI18n` 字段
## Fixes
- 修复 `vitest` 设置 `threads` 导致报错 `Module did not self-register...` 的问题
- 修复 `vue-router warning` 抛出 `can no longer be used directly inside...` 的警告问题
- 修复 `pre-commit` 错误的 `vue-tsc` 检查报错
- `signing logout` 方法现在会正确的清理 `menuTagOptions`
## 4.8.4
由于 `node canvas` 本身的特性(环境问题很多),故在 `v4.8.4` 版本予以移除 `RQRCode` 组件,使用 `vue3-next-qrcode` 替代。所有的使用方法保持一致。
## Feats
- 新增 `package.json``lint` 命令,用于自动修复 `eslint, prettier` 问题
```sh
pnpm lint
```
- `useModal` 方法在 `preset = card` 并且设置了 `fullscreen` 时,现在会自动启用滚动条
- `pre-commit` 新增 `vue-tsc` 检查
## 4.8.3
## Feats
- 更新 `vue-i18n` 版本至 `9.13.1`
- 更新 `naive-ui` 版本至 `2.38.2`
- 更新 `vite` 版本至 `5.2.11`
- 更新 `vue` 版本至 `3.4.26`
## Fixes
- 同步修复 `useModal` 组件在 `preset = card` 时,不能正确显示内容的问题
## 4.8.2
破坏性更新!请仔细阅读更新日志!
更新默认 `node` 版本为 `v20.12.0`
并且对于历史一些模块的分包进行了优化,现在更加语意化与清晰。
重构 `axios` 拦截器,使用插件式设计思想,更加强大的拓展性、可维护性!
## Feats
- 更新默认 `node` 版本为 `v20.12.0`
- `RBarcode` 相关
- 新增 `onSuccess`, `onError`, `onFinally` 三个渲染回调
- 新增 `watchText` 配置项,当 `text` 内容变化后,主动更新条形码,默认不启用
- 更新 `vue-tsc` 版本至 `2.0.11`
- `hooks`
- `template`
- 新增 `useContentScroll` 方法,用于滚动 `LayoutContent` 内容区域
- 新增 `useSiderScroll` 方法,用于滚动 `LayoutSider` 侧边栏区域,也就是菜单区域
- `web`
- `usePagination` 类型修复,所有 `get` 方法都会准确的返回对应的类型
- `SiderBarLogo` 组件
- 支持未配置 `icon` 时,允许截取 `title` 第一个字符作为占位
- `router`
- `router types` 包,补充类型,获得更好的类型提示与配置提示
- `combine-raw-route-modules` 方法现在会检查路由配置的 `name`, `path` 属性是否出现重复,如果重复,会直接抛出异常中断流程
- 移除 `name` 配置项作为必选项
- 标记 `components` 配置项为 `deprecated` 描述项
- 增强 `AppRouteRecordRaw` 类型,现在当你显式配置了 `meta.keepAlive``true` 的时候,会提示你 `name` 必填
- `app-config`
- 提供 `MessageProver` 配置
- 限制最大消息数量为 5 条
- 默认不启用手动关闭消息功能
- `RTable` 新增 `cardProps` 配置项,配置外层容器。
- 更新 `vue` 版本至 `3.4.25`
- 更新 `vite` 版本至 `5.2.10`
- 更新 `vite-bundle-analyzer` 版本至 `0.9.4`,新增汇总模式
- 移除 `naive-ui` 自动导入 `hooks`
- `Search` 组件
- 优化分包逻辑
- 现在允许在搜索菜单初始位置进行上下键切换
- 优化搜索菜单样式
- 新增 `positionSelectedMenuItem` 方法,用于定位选中菜单项
- `hooks`
- `template`
- 新增 `useContentScroll` 方法,用于滚动 `LayoutContent` 内容区域
- 新增 `useSiderScroll` 方法,用于滚动 `LayoutSider` 侧边栏区域,也就是菜单区域
- 重写 `AppAvatar` 组件
- `axios`
- `inject` 包重命名为 `axios-interceptor`,并且拆分分包逻辑,现在以插件形式管理拦截器逻辑与注入逻辑
- 新增 `padding-line-between-statements` 规范,强制语句之间的空行
## Fixes
- 修复 `SIDE_BAR_LOGO` 未配置 `icon` 时,侧边栏不显示的问题
- 修复 `RTable` 在配置 `style.height` 时,样式会错乱的问题
- 修复 `MenuTag` 在初始化位置时不能准确滚动到当前标签页的问题
## 4.8.1
## Feats
- 新增 `RBarcode` 条形码组件,基于 `JsBarcode` 二次封装
## Fixes
- 修复 `RSegment` 分段器设置 `popover` 后警告的问题
## 4.8.0 ## 4.8.0

View File

@ -48,7 +48,7 @@ A `completely free`, `efficient`, `feature complete` and based on vite5. x & ts(
- `Multi-terminal adaptation:` support pc, phone, pad - `Multi-terminal adaptation:` support pc, phone, pad
- `Documentation:` complete documentation - `Documentation:` complete documentation
- `Mock data:` built-in Mock data solution - `Mock data:` built-in Mock data solution
- `Axios request:` secondary encapsulation of axios library, support: cancel, jitter, automatic repeat cancellation and other functions - `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 - `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 - `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 - `TypeScript:` provide a complete type
@ -57,12 +57,10 @@ A `completely free`, `efficient`, `feature complete` and based on vite5. x & ts(
## 👀 Preview ## 👀 Preview
- [Preview](https://xiaodaigua-ray.github.io/ray-template/#/) - [Preview](https://xiaodaigua-ray.github.io/ray-template/#/)
- [Preview(Acceleration address)](https://ray-template.yunkuangao.com/#/)
## 📌 Documentation ## 📌 Documentation
- [Documentation](https://xiaodaigua-ray.github.io/ray-template-doc/) - [Documentation](https://xiaodaigua-ray.github.io/ray-template-doc/)
- [Documentation(Acceleration address)](https://ray-template.yunkuangao.com/ray-template-doc/)
## 🔋 Change Log ## 🔋 Change Log
@ -90,9 +88,6 @@ A `completely free`, `efficient`, `feature complete` and based on vite5. x & ts(
```sh ```sh
# github # github
git clone https://github.com/XiaoDaiGua-Ray/ray-template.git git clone https://github.com/XiaoDaiGua-Ray/ray-template.git
# If your download speed is very slow, you can switch to the proxy address below
git clone https://mirror.ghproxy.com/https://github.com/XiaoDaiGua-Ray/ray-template.git
``` ```
### Pull dependencies ### Pull dependencies

View File

@ -48,7 +48,7 @@
- `多端适配:`支持 pc, phone, pad - `多端适配:`支持 pc, phone, pad
- `文档:`完善的文档 - `文档:`完善的文档
- `Mock 数据:`内置 Mock 数据方案 - `Mock 数据:`内置 Mock 数据方案
- `Axios 请求:`二次封装 axios 库,支持:取消、防抖、自动重复取消等功能 - `Axios 请求:`采用插件式设计二次封装 axios 库拦截器,让拦截器更加灵活
- `SVG`内置 svg icon 解决方案 - `SVG`内置 svg icon 解决方案
- `Hooks`基于模板特性封装的 hooks 让你更加方便的使用模板一些功能 - `Hooks`基于模板特性封装的 hooks 让你更加方便的使用模板一些功能
- `TypeScript`提供完整的类型 - `TypeScript`提供完整的类型
@ -57,12 +57,10 @@
## 👀 预览地址 ## 👀 预览地址
- [点击预览](https://xiaodaigua-ray.github.io/ray-template/#/) - [点击预览](https://xiaodaigua-ray.github.io/ray-template/#/)
- [点击预览(加速地址)](https://ray-template.yunkuangao.com/#/)
## 📌 文档地址 ## 📌 文档地址
- [文档](https://xiaodaigua-ray.github.io/ray-template-doc/) - [文档](https://xiaodaigua-ray.github.io/ray-template-doc/)
- [文档(加速地址)](https://ray-template.yunkuangao.com/ray-template-doc/)
## 🔋 更新日志 ## 🔋 更新日志
@ -90,9 +88,6 @@
```sh ```sh
# github # github
git clone https://github.com/XiaoDaiGua-Ray/ray-template.git git clone https://github.com/XiaoDaiGua-Ray/ray-template.git
# 如果你的下载速度很慢,可以切换到下面的代理地址
git clone https://mirror.ghproxy.com/https://github.com/XiaoDaiGua-Ray/ray-template.git
``` ```
### 拉取依赖 ### 拉取依赖

View File

@ -3,6 +3,7 @@ import { callWithAsyncErrorHandling } from '../../src/utils/basic'
describe('callWithAsyncErrorHandling', () => { describe('callWithAsyncErrorHandling', () => {
it('should call the function and return the result', () => { it('should call the function and return the result', () => {
const fn = (x: number) => x const fn = (x: number) => x
const callbackFn = () => {} const callbackFn = () => {}
expect(callWithAsyncErrorHandling(fn, callbackFn, [1])).resolves.toBe(1) expect(callWithAsyncErrorHandling(fn, callbackFn, [1])).resolves.toBe(1)
@ -14,6 +15,7 @@ describe('callWithAsyncErrorHandling', () => {
const fn = () => { const fn = () => {
throw new Error('test error') throw new Error('test error')
} }
const callbackFn = () => { const callbackFn = () => {
callbackFnExecuted = 2 callbackFnExecuted = 2
} }

View File

@ -3,6 +3,7 @@ import { callWithErrorHandling } from '../../src/utils/basic'
describe('callWithErrorHandling', () => { describe('callWithErrorHandling', () => {
it('should call the function and return the result', () => { it('should call the function and return the result', () => {
const fn = (x: number) => x const fn = (x: number) => x
const callbackFn = () => {} const callbackFn = () => {}
expect(callWithErrorHandling(fn, callbackFn, [1])).toBe(1) expect(callWithErrorHandling(fn, callbackFn, [1])).toBe(1)
@ -14,6 +15,7 @@ describe('callWithErrorHandling', () => {
const fn = () => { const fn = () => {
throw new Error('test error') throw new Error('test error')
} }
const callbackFn = () => { const callbackFn = () => {
callbackFnExecuted = 2 callbackFnExecuted = 2
} }

View File

@ -42,6 +42,7 @@ describe('isValueType', () => {
}) })
it('should return false for Function', () => { it('should return false for Function', () => {
// eslint-disable-next-line @typescript-eslint/no-unsafe-function-type
expect(isValueType<Function>(/a/i, 'Function')).toBe(false) expect(isValueType<Function>(/a/i, 'Function')).toBe(false)
}) })
}) })

View File

@ -14,6 +14,7 @@ describe('uuid', () => {
it('should return a string with length 36', () => { it('should return a string with length 36', () => {
const uid = uuid(36) const uid = uuid(36)
expect(uid.length).toBe(36) expect(uid.length).toBe(36)
}) })
}) })

View File

@ -1,4 +1,4 @@
import { RModal } from '../../src/components/RModal/index' import { RModal } from '../../src/components/base/RModal/index'
import { mount } from '@vue/test-utils' import { mount } from '@vue/test-utils'
describe('RModal', () => { describe('RModal', () => {

View File

@ -1,61 +0,0 @@
import { RQRCode } from '../../src/components/RQRCode/index'
import { mount } from '@vue/test-utils'
describe('RQRCode', () => {
it('should render a qr code', () => {
const wrapper = mount(RQRCode, {
props: {
text: 'hi',
},
})
expect(wrapper.find('img').exists()).toBe(true)
})
it('should execute the callback', () => {
let successValue: 1
let errorValue: -1
const _success = vitest.fn()
const _error = vitest.fn()
_success.mockReturnValue(1)
_error.mockReturnValue(-1)
mount(RQRCode, {
props: {
text: 'hi',
onSuccess: () => {
successValue = _success()
expect(successValue).toBe(1)
},
onError: () => {
errorValue = _error()
expect(errorValue).toBe(-1)
},
},
})
})
it('should execute the onReload function', async () => {
let count = 0
const wrapper = mount(RQRCode, {
props: {
text: 'hi',
status: 'error',
onReload: () => {
count = 1
},
},
})
const btn = wrapper.find('.n-button')
btn.trigger('click')
expect(count).toBe(1)
})
})

View File

@ -39,6 +39,7 @@ describe('useContextmenuCoordinate', () => {
clientX: 100, clientX: 100,
clientY: 200, clientY: 200,
}) })
wrapperRef.element.dispatchEvent(event) wrapperRef.element.dispatchEvent(event)
await nextTick() await nextTick()

View File

@ -17,8 +17,8 @@ describe('useDayjs', () => {
} }
const localSpy = vi.spyOn(m, 'locale') const localSpy = vi.spyOn(m, 'locale')
m.locale('en') m.locale('en-US')
m.locale('zh-cn') m.locale('zh-CN')
expect(localSpy).toHaveBeenCalledTimes(2) expect(localSpy).toHaveBeenCalledTimes(2)
}) })

View File

@ -63,12 +63,4 @@ describe('usePagination', () => {
expect(count).toBe(2) expect(count).toBe(2)
}) })
it('should get callback', () => {
count = 0
getCallback()
expect(count).toBe(1)
})
}) })

View File

@ -4,23 +4,22 @@ import { useTheme } from '../../src/hooks/template/useTheme'
describe('useTheme', async () => { describe('useTheme', async () => {
await setupMiniApp() await setupMiniApp()
const { changeDarkTheme, changeLightTheme, toggleTheme, getAppTheme } = const { darkTheme, lightTheme, toggleTheme, getAppTheme } = useTheme()
useTheme()
it('should change to dark theme', () => { it('should change to dark theme', () => {
changeDarkTheme() darkTheme()
expect(getAppTheme().theme).toBe(true) expect(getAppTheme().theme).toBe(true)
}) })
it('should change to light theme', () => { it('should change to light theme', () => {
changeLightTheme() lightTheme()
expect(getAppTheme().theme).toBe(false) expect(getAppTheme().theme).toBe(false)
}) })
it('should toggle theme', () => { it('should toggle theme', () => {
changeLightTheme() lightTheme()
expect(getAppTheme().theme).toBe(false) expect(getAppTheme().theme).toBe(false)
@ -30,18 +29,18 @@ describe('useTheme', async () => {
}) })
it('should return current theme', () => { it('should return current theme', () => {
changeDarkTheme() darkTheme()
const { theme: darkTheme, themeLabel: darkThemeLabel } = getAppTheme() const { theme: _darkTheme, themeLabel: _darkThemeLabel } = getAppTheme()
expect(darkTheme).toBe(true) expect(_darkTheme).toBe(true)
expect(darkThemeLabel).toBe('暗色') expect(_darkThemeLabel).toBe('Dark')
changeLightTheme() lightTheme()
const { theme: lightTheme, themeLabel: lightThemeLabel } = getAppTheme() const { theme: __lightTheme, themeLabel: __lightThemeLabel } = getAppTheme()
expect(lightTheme).toBe(false) expect(__lightTheme).toBe(false)
expect(lightThemeLabel).toBe('明亮') expect(__lightThemeLabel).toBe('Light')
}) })
}) })

View File

@ -13,6 +13,7 @@ import { mount } from '@vue/test-utils'
* *
* const text = wrapper.find('div').text() // hello * const text = wrapper.find('div').text() // hello
*/ */
// eslint-disable-next-line @typescript-eslint/no-unsafe-function-type
const createRefElement = (slots?: Record<string, Function>) => { const createRefElement = (slots?: Record<string, Function>) => {
const wrapper = mount( const wrapper = mount(
defineComponent({ defineComponent({

View File

@ -3,6 +3,7 @@ import { call } from '../../src/utils/vue/call'
describe('call', () => { describe('call', () => {
it('should be executed once', () => { it('should be executed once', () => {
const fn = vi.fn() const fn = vi.fn()
call(() => fn()) call(() => fn())
expect(fn).toHaveBeenCalledTimes(1) expect(fn).toHaveBeenCalledTimes(1)
@ -10,6 +11,7 @@ describe('call', () => {
it('should be executed with an argument', () => { it('should be executed with an argument', () => {
const fn = vi.fn() const fn = vi.fn()
call((a: number) => fn(a), 1) call((a: number) => fn(a), 1)
expect(fn).toHaveBeenCalledWith(1) expect(fn).toHaveBeenCalledWith(1)

View File

@ -4,6 +4,7 @@ import createRefElement from '../utils/createRefElement'
describe('renderNode', () => { describe('renderNode', () => {
it('should render string', () => { it('should render string', () => {
const wrapper = createRefElement({ const wrapper = createRefElement({
// eslint-disable-next-line @typescript-eslint/no-unsafe-function-type
default: renderNode('hello world') as Function, default: renderNode('hello world') as Function,
}) })
const text = wrapper.text() const text = wrapper.text()

365
eslint.config.mjs Normal file
View File

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

View File

@ -1,4 +1,4 @@
<!DOCTYPE html> <!doctype html>
<html lang="en"> <html lang="en">
<head> <head>
<meta charset="UTF-8" /> <meta charset="UTF-8" />
@ -15,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 {
@ -23,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 {
@ -95,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">

View File

@ -1,14 +1,3 @@
/**
*
* @author Ray <https://github.com/XiaoDaiGua-Ray>
*
* @date 2023-08-11
*
* @workspace ray-template
*
* @remark
*/
import Mock from 'mockjs' import Mock from 'mockjs'
/** /**

View File

@ -1,14 +1,3 @@
/**
*
* @author Ray <https://github.com/XiaoDaiGua-Ray>
*
* @date 2023-08-11
*
* @workspace ray-template
*
* @remark
*/
/* eslint-disable @typescript-eslint/no-explicit-any */ /* eslint-disable @typescript-eslint/no-explicit-any */
export function array(length: number) { export function array(length: number) {

View File

@ -1,11 +1,11 @@
{ {
"name": "ray-template", "name": "ray-template",
"private": false, "private": false,
"version": "4.8.0", "version": "5.2.2",
"type": "module", "type": "module",
"engines": { "engines": {
"node": "^18.0.0 || >=20.0.0", "node": "^18.0.0 || ^20.0.0 || >=22.0.0",
"pnpm": ">=8.0.0" "pnpm": ">=9.0.0"
}, },
"scripts": { "scripts": {
"dev": "vite", "dev": "vite",
@ -15,7 +15,8 @@
"report": "vite build --mode report", "report": "vite build --mode report",
"prepare": "husky install", "prepare": "husky install",
"test": "vitest", "test": "vitest",
"test:ui": "vitest --ui" "test:ui": "vitest --ui",
"lint": "vue-tsc --noEmit && eslint --fix && prettier --write \"**/*.{ts,tsx,json,.vue}\""
}, },
"husky": { "husky": {
"hooks": { "hooks": {
@ -24,86 +25,89 @@
} }
}, },
"lint-staged": { "lint-staged": {
"*.{js,json}": [ "*.{ts,tsx,json}": [
"prettier --write" "prettier --write"
], ],
"*.ts?(x)": [ "*.{ts,tsx,vue}": [
"eslint src", "eslint --fix"
"prettier --parser=typescript --write"
] ]
}, },
"dependencies": { "dependencies": {
"@vueuse/core": "^10.9.0", "@logicflow/core": "2.0.10",
"awesome-qr": "2.1.5-rc.0", "@logicflow/extension": "2.0.14",
"axios": "^1.6.7", "@vueuse/core": "^13.1.0",
"axios": "^1.9.0",
"clipboard": "^2.0.11", "clipboard": "^2.0.11",
"crypto-js": "4.2.0",
"currency.js": "^2.0.4", "currency.js": "^2.0.4",
"dayjs": "^1.11.10", "dayjs": "^1.11.13",
"dom-to-image": "2.6.0", "echarts": "^5.6.0",
"echarts": "^5.5.0", "html-to-image": "1.11.13",
"interactjs": "1.10.26", "interactjs": "1.10.27",
"jsbarcode": "3.11.6",
"lodash-es": "^4.17.21", "lodash-es": "^4.17.21",
"mockjs": "1.1.0", "mockjs": "1.1.0",
"naive-ui": "^2.38.1", "naive-ui": "^2.42.0",
"pinia": "^2.1.7", "pinia": "^3.0.3",
"pinia-plugin-persistedstate": "^3.2.0", "pinia-plugin-persistedstate": "^4.4.1",
"print-js": "^1.6.0", "print-js": "^1.6.0",
"vue": "^3.4.21", "vue": "^3.5.17",
"vue-demi": "0.14.6", "vue-demi": "0.14.10",
"vue-hooks-plus": "1.8.8", "vue-hooks-plus": "2.4.0",
"vue-i18n": "^9.9.0", "vue-i18n": "^9.13.1",
"vue-router": "^4.3.0" "vue-router": "^4.5.1",
"vue3-next-qrcode": "3.0.2"
}, },
"devDependencies": { "devDependencies": {
"@commitlint/cli": "^17.7.1", "@commitlint/cli": "19.7.1",
"@commitlint/config-conventional": "^17.7.0", "@commitlint/config-conventional": "19.7.1",
"@interactjs/types": "1.10.21", "@eslint/js": "9.28.0",
"@intlify/unplugin-vue-i18n": "^2.0.0", "@interactjs/types": "1.10.27",
"@types/crypto-js": "^4.1.1", "@intlify/unplugin-vue-i18n": "4.0.0",
"@types/dom-to-image": "2.6.7", "@types/crypto-js": "4.2.2",
"@types/lodash-es": "^4.17.11", "@types/jsbarcode": "3.11.4",
"@types/mockjs": "1.0.7", "@types/lodash-es": "4.17.12",
"@typescript-eslint/eslint-plugin": "^6.5.0", "@types/mockjs": "1.0.10",
"@typescript-eslint/parser": "^6.5.0", "@typescript-eslint/eslint-plugin": "8.24.0",
"@vitejs/plugin-vue": "^5.0.4", "@typescript-eslint/parser": "8.24.0",
"@vitejs/plugin-vue-jsx": "^3.1.0", "@vitejs/plugin-vue": "5.2.3",
"@vitest/ui": "1.4.0", "@vitejs/plugin-vue-jsx": "4.1.2",
"@vue/eslint-config-prettier": "^9.0.0", "@vitest/ui": "2.1.8",
"@vue/eslint-config-typescript": "^12.0.0", "@vue/eslint-config-prettier": "10.1.0",
"@vue/test-utils": "2.4.3", "@vue/eslint-config-typescript": "14.2.0",
"autoprefixer": "^10.4.15", "@vue/test-utils": "2.4.6",
"depcheck": "^1.4.5", "autoprefixer": "10.4.21",
"eslint": "^8.56.0", "depcheck": "1.4.7",
"eslint-config-prettier": "^9.1.0", "eslint": "9.20.1",
"eslint-config-standard-with-typescript": "^43.0.0", "eslint-config-prettier": "10.1.2",
"eslint-plugin-prettier": "^5.1.3", "eslint-plugin-prettier": "5.2.6",
"eslint-plugin-promise": "^6.1.1", "eslint-plugin-vue": "9.32.0",
"eslint-plugin-vue": "^9.18.1", "globals": "16.0.0",
"happy-dom": "14.3.1", "happy-dom": "17.1.0",
"husky": "8.0.3", "husky": "8.0.3",
"lint-staged": "^15.1.0", "lint-staged": "15.4.3",
"postcss": "^8.4.31", "postcss": "8.5.4",
"postcss-px-to-viewport-8-plugin": "1.2.3", "postcss-px-to-viewport-8-with-include": "1.2.2",
"prettier": "^3.2.5", "prettier": "3.5.3",
"sass": "1.71.1", "rollup-plugin-gzip": "4.0.1",
"svg-sprite-loader": "^6.0.11", "sass": "1.86.3",
"typescript": "^5.2.2", "svg-sprite-loader": "6.0.11",
"unplugin-auto-import": "^0.17.5", "typescript": "5.8.3",
"unplugin-vue-components": "^0.26.0", "unocss": "66.3.3",
"vite": "^5.2.8", "unplugin-auto-import": "19.1.2",
"vite-bundle-analyzer": "0.8.1", "unplugin-vue-components": "0.28.0",
"vite": "6.3.5",
"vite-bundle-analyzer": "0.16.0",
"vite-plugin-cdn2": "1.1.0", "vite-plugin-cdn2": "1.1.0",
"vite-plugin-compression": "^0.5.1", "vite-plugin-ejs": "1.7.0",
"vite-plugin-ejs": "^1.7.0",
"vite-plugin-eslint": "1.8.1", "vite-plugin-eslint": "1.8.1",
"vite-plugin-imp": "^2.4.0", "vite-plugin-inspect": "0.8.4",
"vite-plugin-inspect": "^0.8.3", "vite-plugin-mock-dev-server": "1.8.3",
"vite-plugin-mock-dev-server": "1.4.7", "vite-plugin-svg-icons": "2.0.1",
"vite-plugin-svg-icons": "^2.0.1", "vite-svg-loader": "5.1.0",
"vite-svg-loader": "^4.0.0", "vitest": "2.1.8",
"vite-tsconfig-paths": "4.3.2", "vue-eslint-parser": "9.4.3",
"vitest": "1.4.0", "vue-tsc": "2.2.8"
"vue-tsc": "^1.8.27"
}, },
"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.ts", "main": "index.ts",

13965
pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff

View File

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

View File

@ -1,14 +1,3 @@
/**
*
* @author Ray <https://github.com/XiaoDaiGua-Ray>
*
* @date 2023-03-31
*
* @workspace ray-template
*
* @remark
*/
/** /**
* *
* *
@ -39,7 +28,7 @@ interface JSONPlaceholder {
* *
* @returns * @returns
* *
* @medthod get * @method get
*/ */
export const getWeather = (city: string) => { export const getWeather = (city: string) => {
return request<AxiosTestResponse>({ return request<AxiosTestResponse>({

View File

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

View File

@ -1,14 +1,3 @@
/**
*
* @author Ray <https://github.com/XiaoDaiGua-Ray>
*
* @date 2023-01-18
*
* @workspace ray-template
*
* @remark
*/
/** /**
* *
* *
@ -31,6 +20,8 @@ import { NSpin } from 'naive-ui'
import { spinProps } from 'naive-ui' import { spinProps } from 'naive-ui'
import { getVariableToRefs } from '@/global-variable' import { getVariableToRefs } from '@/global-variable'
import type { SpinProps } from 'naive-ui'
const GlobalSpin = defineComponent({ const GlobalSpin = defineComponent({
name: 'GlobalSpin', name: 'GlobalSpin',
props: { props: {
@ -50,9 +41,10 @@ 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,

View File

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

View File

@ -1,30 +1,22 @@
/** import { NInput, NFormItem, NButton } from 'naive-ui'
*
* @author Ray <https://github.com/XiaoDaiGua-Ray>
*
* @date 2023-06-20
*
* @workspace ray-template
*
* @remark
*/
/** 锁屏界面 */
import { NInput, NForm, NFormItem, NButton } from 'naive-ui'
import AppAvatar from '@/app-components/app/AppAvatar' import AppAvatar from '@/app-components/app/AppAvatar'
import { RForm } from '@/components'
import useAppLockScreen from '@/app-components/app/AppLockScreen/appLockVar' import useAppLockScreen from '@/app-components/app/AppLockScreen/appLockVar'
import { rules, useCondition } from '@/app-components/app/AppLockScreen/shared' import { rules, useCondition } from '@/app-components/app/AppLockScreen/shared'
import { useSettingGetters, useSettingActions } from '@/store' import { useSettingActions } from '@/store'
import { useTemplateRef } from 'vue'
import { useForm } from '@/components'
import { APP_CATCH_KEY } from '@/app-config'
import { setStorage, encrypt } from '@/utils'
import type { FormInst, InputInst } from 'naive-ui' import type { InputInst } from 'naive-ui'
const LockScreen = defineComponent({ const LockScreen = defineComponent({
name: 'LockScreen', name: 'LockScreen',
setup() { setup() {
const formInstRef = ref<FormInst | null>(null) const [register, { validate }] = useForm()
const inputInstRef = ref<InputInst | null>(null) const inputInstRef = useTemplateRef<InputInst | null>('inputInstRef')
const { setLockAppScreen } = useAppLockScreen() const { setLockAppScreen } = useAppLockScreen()
const { updateSettingState } = useSettingActions() const { updateSettingState } = useSettingActions()
@ -33,15 +25,17 @@ const LockScreen = defineComponent({
lockCondition: useCondition(), lockCondition: useCondition(),
}) })
/** 锁屏 */
const lockScreen = () => { const lockScreen = () => {
formInstRef.value?.validate((error) => { validate().then(() => {
if (!error) {
setLockAppScreen(true) setLockAppScreen(true)
updateSettingState('lockScreenSwitch', true) updateSettingState('lockScreenSwitch', false)
setStorage(
APP_CATCH_KEY.appLockScreenPasswordKey,
encrypt(state.lockCondition.lockPassword),
'localStorage',
)
state.lockCondition = useCondition() state.lockCondition = useCondition()
}
}) })
} }
@ -54,19 +48,27 @@ const LockScreen = defineComponent({
return { return {
...toRefs(state), ...toRefs(state),
lockScreen, lockScreen,
formInstRef, register,
inputInstRef, inputInstRef,
} }
}, },
render() { render() {
const { register } = this
return ( return (
<div class="app-lock-screen__content">
<div class="app-lock-screen__input"> <div class="app-lock-screen__input">
<AppAvatar vertical align="center" avatarSize={52} /> <AppAvatar
<NForm avatarSize={52}
style="pointer-events: none;margin: 24px 0;"
vertical
/>
<RForm
ref="formInstRef" ref="formInstRef"
model={this.lockCondition} model={this.lockCondition}
rules={rules} rules={rules}
labelPlacement="left" labelPlacement="left"
onRegister={register}
> >
<NFormItem path="lockPassword"> <NFormItem path="lockPassword">
<NInput <NInput
@ -83,12 +85,14 @@ const LockScreen = defineComponent({
this.lockScreen() this.lockScreen()
} }
}} }}
autofocus
/> />
</NFormItem> </NFormItem>
<NButton type="primary" onClick={this.lockScreen.bind(this)}> <NButton type="primary" onClick={this.lockScreen.bind(this)}>
</NButton> </NButton>
</NForm> </RForm>
</div>
</div> </div>
) )
}, },

View File

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

View File

@ -1,4 +1,10 @@
.app-lock-screen__content { .app-lock-screen__content {
&.app-lock-screen__content--full {
width: 100%;
height: var(--html-height);
@include flexCenter;
}
& .app-lock-screen__input { & .app-lock-screen__input {
& button[class*='n-button'] { & button[class*='n-button'] {
width: 100%; width: 100%;
@ -30,7 +36,7 @@
width: 100%; width: 100%;
height: 100%; height: 100%;
@include flexCenter; @include flexCenter;
font-size: 320px; font-size: 16.67rem;
gap: 80px; gap: 80px;
z-index: 0; z-index: 0;
@ -79,9 +85,23 @@
& .current-year, & .current-year,
& .current-date span { & .current-date span {
font-size: 1.5rem; font-size: 1.875rem;
line-height: 2.25rem;
} }
} }
} }
} }
} }
.ray-template--light {
.app-lock-screen__unlock__content-bg__wrapper {
background-color: #fff !important;
}
.app-lock-screen__unlock__content-bg {
& .left,
& .right {
background-color: rgba(244, 244, 245, 1) !important;
}
}
}

View File

@ -1,33 +1,11 @@
/**
*
* @author Ray <https://github.com/XiaoDaiGua-Ray>
*
* @date 2023-05-13
*
* @workspace ray-template
*
* @remark
*/
/**
*
* ,
*
*/
import './index.scss'
import { RModal } from '@/components' import { RModal } from '@/components'
import LockScreen from './components/LockScreen' import LockScreen from './components/LockScreen'
import UnlockScreen from './components/UnlockScreen'
import useAppLockScreen from '@/app-components/app/AppLockScreen/appLockVar'
import { useSettingGetters, useSettingActions } from '@/store' import { useSettingGetters, useSettingActions } from '@/store'
const AppLockScreen = defineComponent({ const AppLockScreen = defineComponent({
name: 'AppLockScreen', name: 'AppLockScreen',
setup() { setup() {
const { getLockAppScreen } = useAppLockScreen()
const { updateSettingState } = useSettingActions() const { updateSettingState } = useSettingActions()
const { getLockScreenSwitch } = useSettingGetters() const { getLockScreenSwitch } = useSettingGetters()
const lockScreenSwitchRef = computed({ const lockScreenSwitchRef = computed({
@ -39,12 +17,9 @@ const AppLockScreen = defineComponent({
return { return {
lockScreenSwitchRef, lockScreenSwitchRef,
getLockAppScreen,
} }
}, },
render() { render() {
const { getLockAppScreen } = this
return ( return (
<RModal <RModal
v-model:show={this.lockScreenSwitchRef} v-model:show={this.lockScreenSwitchRef}
@ -53,12 +28,10 @@ const AppLockScreen = defineComponent({
autoFocus={false} autoFocus={false}
maskClosable={false} maskClosable={false}
closeOnEsc={false} closeOnEsc={false}
preset={!getLockAppScreen() ? 'dialog' : void 0} preset="dialog"
title="锁定屏幕" title="锁定屏幕"
> >
<div class="app-lock-screen__content"> <LockScreen />
{!getLockAppScreen() ? <LockScreen /> : <UnlockScreen />}
</div>
</RModal> </RModal>
) )
}, },

View File

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

View File

@ -1,8 +1,3 @@
/**
*
*
*/
import { NAvatar, NTooltip, NFlex } from 'naive-ui' import { NAvatar, NTooltip, NFlex } from 'naive-ui'
interface AvatarOptions { interface AvatarOptions {

View File

@ -1,14 +1,3 @@
/**
*
* @author Ray <https://github.com/XiaoDaiGua-Ray>
*
* @date 2023-06-14
*
* @workspace ray-template
*
* @remark
*/
/** /**
* *
* naive ui * naive ui
@ -31,6 +20,7 @@ import {
import { getNaiveLocales } from '@/locales/utils' import { getNaiveLocales } from '@/locales/utils'
import { useSettingGetters } from '@/store' import { useSettingGetters } from '@/store'
import { MESSAGE_PROVIDER } from '@/app-config'
export default defineComponent({ export default defineComponent({
name: 'GlobalProvider', name: 'GlobalProvider',
@ -92,7 +82,7 @@ export default defineComponent({
dateLocale={localePackage.dateLocal} dateLocale={localePackage.dateLocal}
> >
<NLoadingBarProvider> <NLoadingBarProvider>
<NMessageProvider> <NMessageProvider {...MESSAGE_PROVIDER}>
<NDialogProvider> <NDialogProvider>
<NModalProvider> <NModalProvider>
<NNotificationProvider> <NNotificationProvider>

View File

@ -1,14 +1,3 @@
/**
*
* @author Ray <https://github.com/XiaoDaiGua-Ray>
*
* @date 2023-07-21
*
* @workspace ray-template
*
* @remark
*/
/** /**
* *
* *

View File

@ -1,14 +1,3 @@
/**
*
* @author Ray <https://github.com/XiaoDaiGua-Ray>
*
* @date 2023-07-08
*
* @workspace ray-template
*
* @remark
*/
import { get } from 'lodash-es' import { get } from 'lodash-es'
import { import {
setClass, setClass,
@ -18,7 +7,8 @@ import {
getStorage, getStorage,
} from '@/utils' } from '@/utils'
import { useSettingGetters } from '@/store' import { useSettingGetters } from '@/store'
import { APP_CATCH_KEY } from '@/app-config' import { APP_CATCH_KEY, GLOBAL_CLASS_NAMES, APP_THEME } from '@/app-config'
import { useWindowSize } from '@vueuse/core'
import type { SettingState } from '@/store/modules/setting/types' import type { SettingState } from '@/store/modules/setting/types'
@ -26,40 +16,50 @@ export default defineComponent({
name: 'AppStyleProvider', name: 'AppStyleProvider',
setup(_, { expose }) { setup(_, { expose }) {
const { getAppTheme } = useSettingGetters() const { getAppTheme } = useSettingGetters()
const { height, width } = useWindowSize()
/** 同步主题色变量至 body, 如果未获取到缓存值则已默认值填充 */ // 同步主题色变量至 html如果未获取到缓存值则已默认值填充
const syncPrimaryColorToBody = () => { const syncPrimaryColorToBody = () => {
// 默认主题色
const { const {
appPrimaryColor: { primaryColor, primaryFadeColor }, appPrimaryColor: { primaryColor, primaryFadeColor },
} = __APP_CFG__ // 默认主题色 } = APP_THEME
const body = document.body // 主题色配置 class 名
const { rayTemplateThemePrimaryColor, rayTemplateThemePrimaryFadeColor } =
GLOBAL_CLASS_NAMES
const html = document.documentElement
// 获取缓存 naive ui 配置项
const primaryColorOverride = getStorage<SettingState>( const primaryColorOverride = getStorage<SettingState>(
APP_CATCH_KEY.appPiniaSettingStore, APP_CATCH_KEY.appPiniaSettingStore,
'localStorage', 'localStorage',
) // 获取缓存 naive ui 配置项 )
if (primaryColorOverride) { if (primaryColorOverride) {
// 获取主色调
const p = get( const p = get(
primaryColorOverride, primaryColorOverride,
'primaryColorOverride.common.primaryColor', 'primaryColorOverride.common.primaryColor',
primaryColor, primaryColor,
) // 获取主色调 )
const fp = colorToRgba(p, 0.38) // 将主色调任意颜色转换为 rgba 格式 // 将主色调任意颜色转换为 rgba 格式
const fp = colorToRgba(p, 0.85)
/** 设置全局主题色 css 变量 */ // 设置全局主题色 css 变量
body.style.setProperty('--ray-theme-primary-color', p) // 主色调 html.style.setProperty(rayTemplateThemePrimaryColor, p) // 主色调
body.style.setProperty( // 降低透明度后的主色调
'--ray-theme-primary-fade-color', html.style.setProperty(
rayTemplateThemePrimaryFadeColor,
fp || primaryFadeColor, fp || primaryFadeColor,
) // 降低透明度后的主色调 )
} }
} }
/** 隐藏加载动画 */ // 隐藏加载动画
const hiddenLoadingAnimation = () => { const hiddenLoadingAnimation = () => {
/** pre-loading-animation 是默认 id */ // pre-loading-animation 是默认 id
const el = document.getElementById('pre-loading-animation') const el = document.getElementById(GLOBAL_CLASS_NAMES.preLoadingAnimation)
if (el) { if (el) {
setStyle(el, { setStyle(el, {
@ -68,38 +68,30 @@ export default defineComponent({
} }
} }
/** 切换主题时, 同步更新 body class 以便于进行自定义 css 配置 */ // 切换主题时,同步更新 html class 以便于进行自定义 css 配置
const updateGlobalThemeClass = (bool: boolean) => { const updateGlobalThemeClass = (bool: boolean) => {
/** const html = document.documentElement
* const { darkClassName, lightClassName } = GLOBAL_CLASS_NAMES
* body class
*
* getAppTheme
*/
const body = document.body
const darkClassName = 'ray-template--dark' // 暗色类名
const lightClassName = 'ray-template--light' // 明亮色类名
bool bool
? removeClass(body, lightClassName) ? removeClass(html, lightClassName)
: removeClass(body, darkClassName) : removeClass(html, darkClassName)
setClass(body, bool ? darkClassName : lightClassName) setClass(html, bool ? darkClassName : lightClassName)
} }
syncPrimaryColorToBody() syncPrimaryColorToBody()
hiddenLoadingAnimation() hiddenLoadingAnimation()
// 当切换主题时,更新 body 当前的注入 class watchEffect(() => {
watch( // 当切换主题时,更新 html 当前的注入 class
() => getAppTheme.value, updateGlobalThemeClass(getAppTheme.value)
(ndata) => { // 注入全局宽高尺寸
updateGlobalThemeClass(ndata) setStyle(document.documentElement, {
}, [GLOBAL_CLASS_NAMES.htmlHeight]: `${height.value}px`,
{ [GLOBAL_CLASS_NAMES.htmlWidth]: `${width.value}px`,
immediate: true, })
}, })
)
expose() expose()
}, },

View File

@ -1,14 +1,3 @@
/**
*
* @author Ray <https://github.com/XiaoDaiGua-Ray>
*
* @date 2024-01-01
*
* @workspace ray-template
*
* @remark
*/
/** /**
* *
* *
@ -39,18 +28,10 @@ export default defineComponent({
if (version !== cacheVersion) { if (version !== cacheVersion) {
modalShow.value = true modalShow.value = true
setStorage<string>( setStorage(APP_CATCH_KEY.appVersionProvider, version, 'localStorage')
APP_CATCH_KEY.appVersionProvider,
version,
'localStorage',
)
} }
} else { } else {
setStorage<string>( setStorage(APP_CATCH_KEY.appVersionProvider, version, 'localStorage')
APP_CATCH_KEY.appVersionProvider,
version,
'localStorage',
)
} }
return { return {
@ -72,7 +53,7 @@ export default defineComponent({
title="发现新版本" title="发现新版本"
content="当前版本已更新,点击确认加载新版本~" content="当前版本已更新,点击确认加载新版本~"
zIndex={999999999} zIndex={999999999}
dad draggable
positiveText="确认" positiveText="确认"
negativeText="取消" negativeText="取消"
onPositiveClick={logout} onPositiveClick={logout}

View File

@ -1,14 +1,3 @@
/**
*
* @author Ray <https://github.com/XiaoDaiGua-Ray>
*
* @date 2023-10-21
*
* @workspace ray-template
*
* @remark
*/
/** /**
* *
* *
@ -37,7 +26,7 @@ export default defineComponent({
const { getWatermarkConfig, getWatermarkSwitch } = this const { getWatermarkConfig, getWatermarkSwitch } = this
return getWatermarkSwitch ? ( return getWatermarkSwitch ? (
<NWatermark cross fullscreen {...getWatermarkConfig} /> <NWatermark {...getWatermarkConfig} fullscreen />
) : null ) : null
}, },
}) })

View File

@ -1,23 +1,42 @@
import type { AppMenuConfig, PreloadingConfig } from '@/types'
import type { MessageProviderProps } from 'naive-ui'
/** /**
* *
* @author Ray <https://github.com/XiaoDaiGua-Ray> * @description
* html
* *
* @date 2023-05-23 * class name
* *
* @workspace ray-template
*
* @remark
*/ */
export const GLOBAL_CLASS_NAMES = {
/** 系统配置 */ darkClassName: 'ray-template--dark',
lightClassName: 'ray-template--light',
import type { LayoutSideBarLogo, PreloadingConfig } from '@/types' rayTemplateThemePrimaryColor: '--ray-theme-primary-color',
import type { AppMenuConfig, AppKeepAlive } from '@/types' rayTemplateThemePrimaryFadeColor: '--ray-theme-primary-fade-color',
preLoadingAnimation: 'pre-loading-animation',
htmlHeight: '--html-height',
htmlWidth: '--html-width',
} as const
/** /**
* *
* @description
* MessageProver
* Message
*
* @see https://www.naiveui.com/zh-CN/dark/components/message#MessageProvider-Props
*/
export const MESSAGE_PROVIDER: MessageProviderProps = {
max: 5,
closable: false,
}
/**
*
* @description
* spin * spin
* *
* *
* v4.7.5 * v4.7.5
*/ */
@ -25,25 +44,11 @@ export const LAYOUT_CONTENT_SPIN_WHEN_ROUTE_CHANGE = false
/** /**
* *
* Spin * @description
* Spin
*/ */
export const APP_GLOBAL_LOADING = 'loading' export const APP_GLOBAL_LOADING = 'loading'
/**
*
*
*
* :
* - setupKeepAlive: 是否启用系统页面缓存, false
* - keepAliveExclude: 排除哪些页面不缓存
* - maxKeepAliveLength: 最大缓存页面数量
*/
export const APP_KEEP_ALIVE: Readonly<AppKeepAlive> = {
setupKeepAlive: true,
keepAliveExclude: [],
maxKeepAliveLength: 5,
}
/** /**
* *
* *
@ -57,61 +62,26 @@ export const PRE_LOADING_CONFIG: PreloadingConfig = {
/** /**
* *
* icon: LOGO , `RIcon` () * @description
* title: LOGO * key
* url: 点击跳转地址, , * key 使 getStorage setStorage key
* jumpType: 跳转类型(station: 项目内跳转, outsideStation: 新页面打开) * APP_CATCH_KEY 使
* *
* , LOGO * cache.ts
*/
export const SIDE_BAR_LOGO: LayoutSideBarLogo | undefined = {
icon: 'ray',
title: 'Ray Template',
url: '/dashboard',
jumpType: 'station',
}
/**
* *
* *
*
* menuCollapsedWidth menuCollapsedMode width
*
* menuCollapsedMode:
* - transform: 边栏将只会移动它的位置而不会改变宽度
* - width: Sider
* menuCollapsedIconSize
* menuCollapsedIndent
* menuAccordion
*/
export const APP_MENU_CONFIG: Readonly<AppMenuConfig> = {
menuCollapsedWidth: 64,
menuCollapsedMode: 'width',
menuCollapsedIconSize: 22,
menuCollapsedIndent: 24,
menuAccordion: false,
}
/**
*
* key
* key 使 getStorage setStorage key
* APP_CATCH_KEY 使
*
* cache.ts
*
*
* *
* @example * @example
* export const APP_CATCH_KEY_PREFIX = 'ray-template:' * export const APP_CATCH_KEY_PREFIX = 'ray-template:'
* *
* 'ray-template:signing' // 会自动拼接为 * key: ray-template:signing
*/ */
export const APP_CATCH_KEY_PREFIX = '' export const APP_CATCH_KEY_PREFIX = ''
/** /**
* *
* key * @description
* key
* *
* : * :
* - signing: 登陆信息缓存 key * - signing: 登陆信息缓存 key
@ -123,6 +93,8 @@ export const APP_CATCH_KEY_PREFIX = ''
* - appPiniaMenuStore: pinia menu store key * - appPiniaMenuStore: pinia menu store key
* - appPiniaSigningStore: pinia signing store key * - appPiniaSigningStore: pinia signing store key
* - appVersionProvider: 版本信息缓存 key * - appVersionProvider: 版本信息缓存 key
* - appMenuTagOptions: 标签页菜单列表
* - appLockScreenPasswordKey: 锁屏密码缓存 key
*/ */
export const APP_CATCH_KEY = { export const APP_CATCH_KEY = {
signing: 'signing', signing: 'signing',
@ -136,13 +108,16 @@ export const APP_CATCH_KEY = {
appVersionProvider: 'appVersionProvider', appVersionProvider: 'appVersionProvider',
isAppLockScreen: 'isAppLockScreen', isAppLockScreen: 'isAppLockScreen',
appGlobalSearchOptions: 'appGlobalSearchOptions', appGlobalSearchOptions: 'appGlobalSearchOptions',
appMenuTagOptions: 'appMenuTagOptions',
appLockScreenPasswordKey: 'appLockScreenPasswordKey',
} as const } as const
/** /**
* *
* * @description
* `transform` *
* : `transform-fade-bottom` * transform
* 例如: transform-fade-bottom
*/ */
export const CONTENT_TRANSITION_OPTIONS = [ export const CONTENT_TRANSITION_OPTIONS = [
{ {

View File

@ -1,50 +1,38 @@
/**
*
* @author Ray <https://github.com/XiaoDaiGua-Ray>
*
* @date 2023-05-19
*
* @workspace ray-template
*
* @remark
*/
/** 系统颜色风格配置入口 */
import type { AppTheme } from '@/types' import type { AppTheme } from '@/types'
export const APP_THEME: AppTheme = { export const APP_THEME: AppTheme = {
/** /**
* *
* * @description
* RGBARGB *
* RGBARGB
*/ */
appThemeColors: [ appThemeColors: [
'#2d8cf0', '#2d8cf0',
'#3f9eff', '#3f9eff',
'#ff42bc', '#ff42bc',
'#ee4f12', '#ee4f12',
'#a6e4f7',
'#dbcb02', '#dbcb02',
'#18A058', '#18a058',
], ],
/** 系统主题色 */ // 系统主题色
appPrimaryColor: { appPrimaryColor: {
/** 主题色 */ // 主题色
primaryColor: '#2d8cf0', primaryColor: '#2d8cf0',
/** 主题辅助色(用于整体 hover、active 等之类颜色) */ // 主题辅助色(用于整体 hover、active 等之类颜色)
primaryFadeColor: 'rgba(45, 140, 240, 0.3)', primaryFadeColor: 'rgba(45, 140, 240, 0.85)',
}, },
/** /**
* *
* naive-ui * @description
* : <https://www.naiveui.com/zh-CN/dark/docs/customize-theme> * naive-ui
* : <https://www.naiveui.com/zh-CN/dark/docs/customize-theme>。
* *
* : * :
* - appPrimaryColor common * - appPrimaryColor common
* *
* , *
* , peers * peers
* : <https://www.naiveui.com/zh-CN/dark/docs/customize-theme#%E4%BD%BF%E7%94%A8-peers-%E4%B8%BB%E9%A2%98%E5%8F%98%E9%87%8F> * : <https://www.naiveui.com/zh-CN/dark/docs/customize-theme#%E4%BD%BF%E7%94%A8-peers-%E4%B8%BB%E9%A2%98%E5%8F%98%E9%87%8F>
* *
* @example * @example
@ -60,17 +48,24 @@ export const APP_THEME: AppTheme = {
* ``` * ```
*/ */
appNaiveUIThemeOverrides: { appNaiveUIThemeOverrides: {
dark: {}, dark: {
light: {}, common: {
borderRadius: '4px',
baseColor: 'rgb(18, 18, 18)',
},
},
light: {
common: {
borderRadius: '4px',
baseColor: 'rgb(255, 255, 255)',
},
}, },
appNaiveUIThemeOverridesCommon: {
dark: {},
light: {},
}, },
/** /**
* *
* echart * @description
* json xxx-dark.json * echart
* json xxx-dark.json
* *
* [](https://xiaodaigua-ray.github.io/ray-template-doc/ray-template-docs/advanced/echart-themes.html) * [](https://xiaodaigua-ray.github.io/ray-template-doc/ray-template-docs/advanced/echart-themes.html)
*/ */

View File

@ -7,7 +7,24 @@
"#f7c5a0", "#f7c5a0",
"#d4a4eb", "#d4a4eb",
"#d2f5a6", "#d2f5a6",
"#76f2f2" "#76f2f2",
"#9b8bba",
"#e098c7",
"#8fd3e8",
"#71669e",
"#cc70af",
"#7cb4cc",
"#7EC4FF",
"#5FCBB0",
"#49C4BF",
"#F0C9CA",
"#34DC90",
"#3295E0",
"#EAB62E",
"#76C3F3",
"#2DC2C0",
"#FCC43F",
"#84CFFF"
], ],
"backgroundColor": "transparent", "backgroundColor": "transparent",
"textStyle": {}, "textStyle": {},
@ -297,6 +314,9 @@
"legend": { "legend": {
"textStyle": { "textStyle": {
"color": "#999999" "color": "#999999"
},
"pageTextStyle": {
"color": "#999999"
} }
}, },
"tooltip": { "tooltip": {

View File

@ -19,7 +19,13 @@
"#c9ab00", "#c9ab00",
"#7eb00a", "#7eb00a",
"#6f5553", "#6f5553",
"#c14089" "#c14089",
"#516b91",
"#59c4e6",
"#edafda",
"#93b7e3",
"#a5e7f0",
"#cbb0e3"
], ],
"backgroundColor": "transparent", "backgroundColor": "transparent",
"textStyle": {}, "textStyle": {},

View File

@ -1,16 +1,3 @@
/**
*
* @author Ray <https://github.com/XiaoDaiGua-Ray>
*
* @date 2023-05-19
*
* @workspace ray-template
*
* @remark
*/
/** 国际化相关配置 */
import type { TemplateLocale, LocalOptions, DayjsLocalMap } from '@/types' import type { TemplateLocale, LocalOptions, DayjsLocalMap } from '@/types'
import type { ValueOf } from '@/types' import type { ValueOf } from '@/types'

View File

@ -1,14 +1,3 @@
/**
*
* @author Ray <https://github.com/XiaoDaiGua-Ray>
*
* @date 2023-06-12
*
* @workspace ray-template
*
* @remark
*/
/** /**
* *
* *
@ -19,4 +8,12 @@ export const APP_REGEX: Record<string, RegExp> = {
/** css 尺寸单位匹配 */ /** css 尺寸单位匹配 */
cssUnit: cssUnit:
/^\d+(\.\d+)?(px|em|rem|%|vw|vh|vmin|vmax|cm|mm|in|pt|pc|ch|ex|q|s|ms|deg|rad|turn|grad|hz|khz|dpi|dpcm|dppx|fr|auto)$/, /^\d+(\.\d+)?(px|em|rem|%|vw|vh|vmin|vmax|cm|mm|in|pt|pc|ch|ex|q|s|ms|deg|rad|turn|grad|hz|khz|dpi|dpcm|dppx|fr|auto)$/,
/**
*
* @description
* css auto, unset, fit-content, max-content, min-content, initial, inherit, revert, revert-layer, -webkit-fill-available,
* -webkit-max-content, -webkit-min-content, -webkit-revert, -webkit-revert-layer, -webkit-fill-available
*/
cssSize:
/^auto|unset|fit-content|max-content|min-content|initial|inherit|revert|revert-layer|[-\w]+-webkit-fill-available$/,
} }

View File

@ -1,14 +1,3 @@
/**
*
* @author Ray <https://github.com/XiaoDaiGua-Ray>
*
* @date 2023-06-02
*
* @workspace ray-template
*
* @remark
*/
import type { AxiosConfig } from '@/types' import type { AxiosConfig } from '@/types'
/** axios 相关配置 */ /** axios 相关配置 */

View File

@ -1,34 +1,37 @@
/**
*
* @author Ray <https://github.com/XiaoDaiGua-Ray>
*
* @date 2023-05-19
*
* @workspace ray-template
*
* @remark
*/
/** vue-router 相关配置入口 */
import type { LayoutInst } from 'naive-ui' import type { LayoutInst } from 'naive-ui'
import type { Ref } from 'vue'
/** /**
* *
* ref * @description
* shallowRef
* *
* , 使(scrollTo) * , 使(scrollTo)
* *
* *
* , , dom * , , dom
* @example * @example
* ```ts
* nextTick().then(() => { * nextTick().then(() => {
* LAYOUT_CONTENT_REF.value?.scrollTo() * LAYOUT_CONTENT_REF.value?.scrollTo()
* }) * })
* ```
*/ */
export const LAYOUT_CONTENT_REF = ref<LayoutInst | null>(null) export const LAYOUT_CONTENT_REF: Readonly<Ref<LayoutInst | null>> =
shallowRef<LayoutInst | null>(null)
/**
*
* @description
* shallowRef
*
*
* 使使 nextTick() dom
* @example
* nextTick().then(() => {
* LAYOUT_SIDER_REF.value?.scrollTo({ top: 0 })
* })
*/
export const LAYOUT_SIDER_REF: Readonly<Ref<LayoutInst | null>> =
shallowRef<LayoutInst | null>(null)
export const SETUP_ROUTER_ACTION = { export const SETUP_ROUTER_ACTION = {
/** 是否启用路由切换时顶部加载条 */ /** 是否启用路由切换时顶部加载条 */
@ -39,6 +42,7 @@ export const SETUP_ROUTER_ACTION = {
/** /**
* *
* @description
* () * ()
* *
* *
@ -50,7 +54,8 @@ export const WHITE_ROUTES: string[] = ['RLogin', 'ErrorPage', 'RayTemplateDoc']
/** /**
* *
* @description
* *
* , * ,
*/ */
export const SUPER_ADMIN: (string | number)[] = ['admin'] export const SUPER_ADMIN: (string | number)[] = []

View File

@ -1,14 +1,3 @@
/**
*
* @author Ray <https://github.com/XiaoDaiGua-Ray>
*
* @date 2023-10-23
*
* @workspace ray-template
*
* @remark
*/
import { useAxiosInterceptor } from '@/axios/utils/interceptor' import { useAxiosInterceptor } from '@/axios/utils/interceptor'
import implement from './provider' import implement from './provider'

View File

@ -0,0 +1,30 @@
import { axiosCanceler } from '@/axios/utils/interceptor'
import type { AxiosRequestInterceptor, FetchErrorFunction } from '@/axios/types'
/**
*
* @param ins
* @param mode
*
* @description
*
*/
const injectRequestCanceler: AxiosRequestInterceptor = (ins, mode) => {
axiosCanceler.removePendingRequest(ins) // 检查是否存在重复请求, 若存在则取消已发的请求
axiosCanceler.addPendingRequest(ins) // 把当前的请求信息添加到 pendingRequest 表中
}
/**
*
* @param error
* @param mode
*
* @description
*
*/
const requestErrorCanceler: FetchErrorFunction = (error, mode) => {
axiosCanceler.removePendingRequest(error) // 移除请求拦截器
}
export { injectRequestCanceler, requestErrorCanceler }

View File

@ -0,0 +1,42 @@
import { appendRequestHeaders } from '@/axios/utils/append-request-headers'
import { APP_CATCH_KEY } from '@/app-config'
import { getStorage } from '@/utils'
import type {
RequestInterceptorConfig,
AxiosRequestInterceptor,
} from '@/axios/types'
/**
*
* token token key value
*
*
*
* request instance ,
*/
const requestHeaderToken = (ins: RequestInterceptorConfig, mode: string) => {
const token = getStorage<string | null>(APP_CATCH_KEY.token, 'localStorage')
if (ins.url) {
// TODO: 根据 url 不同是否设置 token
}
return {
key: 'X-TOKEN',
value: token,
}
}
/** 注入请求头信息 */
const injectRequestHeaders: AxiosRequestInterceptor = (ins, mode) => {
appendRequestHeaders(ins, [
requestHeaderToken(ins, mode),
{
key: 'Demo-Header-Key',
value: 'Demo Header Value',
},
])
}
export { injectRequestHeaders }

View File

@ -0,0 +1,28 @@
/**
*
*
* ,
* ,
*
* , 便
*
* injectRequestCanceler requestErrorCanceler axios request interceptor
*/
import { injectRequestCanceler, requestErrorCanceler } from './plugins/cancel'
import { injectRequestHeaders } from './plugins/request-headers'
/**
*
*
*
*/
export default {
// 请求正常
implementRequestInterceptorArray: [
injectRequestHeaders,
injectRequestCanceler,
],
// 请求错误
implementRequestInterceptorErrorArray: [requestErrorCanceler],
}

View File

@ -1,14 +1,3 @@
/**
*
* @author Ray <https://github.com/XiaoDaiGua-Ray>
*
* @date 2023-10-23
*
* @workspace ray-template
*
* @remark
*/
import { useAxiosInterceptor } from '@/axios/utils/interceptor' import { useAxiosInterceptor } from '@/axios/utils/interceptor'
import implement from './provider' import implement from './provider'

View File

@ -0,0 +1,32 @@
import { axiosCanceler } from '@/axios/utils/interceptor'
import type {
AxiosResponseInterceptor,
FetchErrorFunction,
} from '@/axios/types'
/**
*
* @param ins
* @param mode
*
* @description
*
*/
const injectResponseCanceler: AxiosResponseInterceptor = (ins, mode) => {
axiosCanceler.removePendingRequest(ins)
}
/**
*
* @param error
* @param mode
*
* @description
*
*/
const responseErrorCanceler: FetchErrorFunction = (error, mode) => {
axiosCanceler.removePendingRequest(error)
}
export { injectResponseCanceler, responseErrorCanceler }

View File

@ -0,0 +1,24 @@
/**
*
*
* ,
* ,
*
* , 便
*
* injectResponseCanceler responseErrorCanceler axios response interceptor
*/
import { injectResponseCanceler, responseErrorCanceler } from './plugins/cancel'
/**
*
*
*
*/
export default {
// 响应正常
implementResponseInterceptorArray: [injectResponseCanceler],
// 响应错误
implementResponseInterceptorErrorArray: [responseErrorCanceler],
}

View File

@ -1,14 +1,3 @@
/**
*
* @author Ray <https://github.com/XiaoDaiGua-Ray>
*
* @date 2023-07-11
*
* @workspace ray-template
*
* @remark
*/
/** /**
* *
* vue-hook-plus axios * vue-hook-plus axios
@ -51,14 +40,14 @@ function useRequest<
fetchOptions: AppRawRequestConfig<Response>, fetchOptions: AppRawRequestConfig<Response>,
option?: UseRequestOptions<Response, HookPlusParams, HookPlusPlugin>, option?: UseRequestOptions<Response, HookPlusParams, HookPlusPlugin>,
) { ) {
const fc = () => { const fn = () => {
const cb = request<Response>(fetchOptions) const cb = request<Response>(fetchOptions)
return cb return cb
} }
const hooks = useHookPlusRequest<Response, HookPlusParams>( const hooks = useHookPlusRequest<Response, HookPlusParams>(
fc, fn,
Object.assign({}, option), Object.assign({}, option),
) )

View File

@ -1,105 +0,0 @@
/**
*
* @author Ray <https://github.com/XiaoDaiGua-Ray>
*
* @date 2023-06-06
*
* @workspace ray-template
*
* @remark
*/
/**
*
*
* ,
* ,
*
* , 便
*
* injectRequestCanceler requestErrorCanceler axios request interceptor
*/
import { axiosCanceler } from '@/axios/utils/interceptor'
import { appendRequestHeaders } from '@/axios/utils/axios-copilot'
import { APP_CATCH_KEY } from '@/app-config'
import { getStorage } from '@/utils'
import type {
RequestInterceptorConfig,
FetchFunction,
FetchErrorFunction,
} from '@/axios/types'
import type { Recordable } from '@/types'
/**
*
* token token key value
*
*
*
* request instance ,
*/
const requestHeaderToken = (ins: RequestInterceptorConfig, mode: string) => {
const token = getStorage<string>(APP_CATCH_KEY.token)
if (ins.url) {
// TODO: 根据 url 不同是否设置 token
}
return {
key: 'X-TOKEN',
value: token,
}
}
/** 注入请求头信息 */
const injectRequestHeaders: FetchFunction = (ins, mode) => {
appendRequestHeaders(ins, [
requestHeaderToken(ins, mode),
{
key: 'Demo-Header-Key',
value: 'Demo Header Value',
},
])
}
/**
*
* @param ins
* @param mode
*
* @description
*
*/
const injectRequestCanceler: FetchFunction = (ins, mode) => {
axiosCanceler.removePendingRequest(ins) // 检查是否存在重复请求, 若存在则取消已发的请求
axiosCanceler.addPendingRequest(ins) // 把当前的请求信息添加到 pendingRequest 表中
}
/**
*
* @param error
* @param mode
*
* @description
*
*/
const requestErrorCanceler: FetchErrorFunction = (error, mode) => {
axiosCanceler.removePendingRequest(error) // 移除请求拦截器
}
/**
*
*
*
*/
export default {
// 请求正常
implementRequestInterceptorArray: [
injectRequestHeaders,
injectRequestCanceler,
],
// 请求错误
implementRequestInterceptorErrorArray: [requestErrorCanceler],
}

View File

@ -1,66 +0,0 @@
/**
*
* @author Ray <https://github.com/XiaoDaiGua-Ray>
*
* @date 2023-06-06
*
* @workspace ray-template
*
* @remark
*/
/**
*
*
* ,
* ,
*
* , 便
*
* injectResponseCanceler responseErrorCanceler axios response interceptor
*/
import { axiosCanceler } from '@/axios/utils/interceptor'
import type {
ResponseInterceptorConfig,
FetchFunction,
FetchErrorFunction,
} from '@/axios/types'
import type { Recordable } from '@/types'
/**
*
* @param ins
* @param mode
*
* @description
*
*/
const injectResponseCanceler: FetchFunction = (ins, mode) => {
axiosCanceler.removePendingRequest(ins)
}
/**
*
* @param error
* @param mode
*
* @description
*
*/
const responseErrorCanceler: FetchErrorFunction = (error, mode) => {
axiosCanceler.removePendingRequest(error)
}
/**
*
*
*
*/
export default {
// 响应正常
implementResponseInterceptorArray: [injectResponseCanceler],
// 响应错误
implementResponseInterceptorErrorArray: [responseErrorCanceler],
}

View File

@ -1,50 +1,42 @@
/**
*
* @author Ray <https://github.com/XiaoDaiGua-Ray>
*
* @date 2022-11-18
*
* @workspace ray-template
*
* @remark
*/
/**
*
*
* , inject
*
*/
import axios from 'axios' import axios from 'axios'
import { AXIOS_CONFIG } from '@/app-config' import { AXIOS_CONFIG } from '@/app-config'
import { useAxiosInterceptor } from '@/axios/utils/interceptor' import { useAxiosInterceptor } from '@/axios/utils/interceptor'
import { import {
setupResponseInterceptor, setupResponseInterceptor,
setupResponseErrorInterceptor, setupResponseErrorInterceptor,
} from '@/axios/inject/response' } from '@/axios/axios-interceptor/response'
import { import {
setupRequestInterceptor, setupRequestInterceptor,
setupRequestErrorInterceptor, setupRequestErrorInterceptor,
} from '@/axios/inject/request' } from '@/axios/axios-interceptor/request'
import type { AxiosInstanceExpand } from './types' import type { AxiosInstanceExpand, RequestInterceptorConfig } from './types'
// 创建 axios 实例
const server: AxiosInstanceExpand = axios.create(AXIOS_CONFIG) const server: AxiosInstanceExpand = axios.create(AXIOS_CONFIG)
// 获取拦截器实例
const { createAxiosInstance, beforeFetch, fetchError } = useAxiosInterceptor() const { createAxiosInstance, beforeFetch, fetchError } = useAxiosInterceptor()
// 请求拦截器 // 请求拦截器
server.interceptors.request.use( server.interceptors.request.use(
(request) => { (request) => {
createAxiosInstance(request, 'requestInstance') // 生成 request instance // 生成 request instance
setupRequestInterceptor() // 初始化拦截器所有已注入方法 createAxiosInstance(
beforeFetch('requestInstance', 'implementRequestInterceptorArray', 'ok') // 执行拦截器所有已注入方法 request as RequestInterceptorConfig<unknown>,
'requestInstance',
)
// 初始化拦截器所有已注入方法
setupRequestInterceptor()
// 执行拦截器所有已注入方法
beforeFetch('requestInstance', 'implementRequestInterceptorArray', 'ok')
return request return request
}, },
(error) => { (error) => {
setupRequestErrorInterceptor() // 初始化拦截器所有已注入方法(错误状态) // 初始化拦截器所有已注入方法(错误状态)
fetchError('requestError', error, 'implementRequestInterceptorErrorArray') // 执行所有已注入方法 setupRequestErrorInterceptor()
// 执行所有已注入方法
fetchError('requestError', error, 'implementRequestInterceptorErrorArray')
return Promise.reject(error) return Promise.reject(error)
}, },
@ -53,17 +45,22 @@ server.interceptors.request.use(
// 响应拦截器 // 响应拦截器
server.interceptors.response.use( server.interceptors.response.use(
(response) => { (response) => {
createAxiosInstance(response, 'responseInstance') // 创建响应实例 // 创建响应实例
setupResponseInterceptor() // 注入响应成功待执行队列 createAxiosInstance(response, 'responseInstance')
beforeFetch('responseInstance', 'implementResponseInterceptorArray', 'ok') // 执行响应成功拦截器 // 注入响应成功待执行队列
setupResponseInterceptor()
// 执行响应成功拦截器
beforeFetch('responseInstance', 'implementResponseInterceptorArray', 'ok')
const { data } = response const { data } = response
return Promise.resolve(data) return Promise.resolve(data)
}, },
(error) => { (error) => {
setupResponseErrorInterceptor() // 注入响应失败待执行队列 // 注入响应失败待执行队列
fetchError('responseError', error, 'implementResponseInterceptorErrorArray') // 执行响应失败后拦截器 setupResponseErrorInterceptor()
// 执行响应失败后拦截器
fetchError('responseError', error, 'implementResponseInterceptorErrorArray')
return Promise.reject(error) return Promise.reject(error)
}, },

View File

@ -7,6 +7,8 @@ import type {
Axios, Axios,
AxiosResponse, AxiosResponse,
AxiosError, AxiosError,
InternalAxiosRequestConfig,
AxiosRequestHeaders,
} from 'axios' } from 'axios'
import type { AnyFC } from '@/types' import type { AnyFC } from '@/types'
@ -24,12 +26,45 @@ export interface RequestHeaderOptions {
} }
export interface CancelConfig { export interface CancelConfig {
/**
*
* @description
*
*/
cancel?: boolean cancel?: boolean
} }
export interface AppRawRequestConfig<T = any> extends AxiosRequestConfig<T> { export interface AppRawRequestConfig<T = any> extends AxiosRequestConfig<T> {
/**
*
* @description
*
*/
cancelConfig?: CancelConfig cancelConfig?: CancelConfig
/**
*
* @description
*
*/
__CANCELER_TAG_RAY_TEMPLATE__?: '__CANCELER_TAG_RAY_TEMPLATE__' __CANCELER_TAG_RAY_TEMPLATE__?: '__CANCELER_TAG_RAY_TEMPLATE__'
/**
*
* @description
*
*
*
* @default true
*/
responseErrorMessage?: boolean
/**
*
* @description
*
*
*
* @default false
*/
responseSuccessMessage?: boolean
} }
export interface CancelerParams<T = any, D = any> export interface CancelerParams<T = any, D = any>
@ -110,18 +145,8 @@ export interface ErrorImplementQueue {
implementResponseInterceptorErrorArray: AnyFC[] implementResponseInterceptorErrorArray: AnyFC[]
} }
export type FetchFunction = <T = any, K = any>(
ins: RequestInterceptorConfig<T> & ResponseInterceptorConfig<T, K>,
mode: string,
) => void
export type FetchType = 'ok' | 'error' export type FetchType = 'ok' | 'error'
export type FetchErrorFunction<T = any, D = any> = (
error: AxiosError<T, D>,
mode: string,
) => void
export interface AxiosFetchInstance { export interface AxiosFetchInstance {
requestInstance: RequestInterceptorConfig | null requestInstance: RequestInterceptorConfig | null
responseInstance: ResponseInterceptorConfig | null responseInstance: ResponseInterceptorConfig | null
@ -131,3 +156,54 @@ export interface AxiosFetchError<T = any> {
requestError: T | null requestError: T | null
responseError: T | null responseError: T | null
} }
interface AxiosResponseConfigInterceptor<T = any>
extends AppRawRequestConfig<T> {
headers: AxiosRequestHeaders
}
type AxiosResponseOmit<T = any, D = any> = Omit<AxiosResponse<T, D>, 'config'>
type AxiosResponseInterceptorIns<T = any, D = any> = AxiosResponseOmit<T, D> & {
config: AxiosResponseConfigInterceptor<T>
}
/**
*
* @param ins current response instance
* @param mode current environment mode
*
* @description
* axios response interceptor.
*/
export type AxiosResponseInterceptor<T = any, K = any> = (
ins: AxiosResponseInterceptorIns<T, K>,
mode: string,
) => void
/**
*
* @param ins current request instance
* @param mode current environment mode
*
* @description
* axios request interceptor.
*/
export type AxiosRequestInterceptor<T = any> = (
ins: RequestInterceptorConfig<T>,
mode: string,
) => void
/**
*
* @param error current request error instance
* @param mode current environment mode
*
* @description
* axios request and response error interceptor.
* if you need create plugin with error interceptor, you can use this function type.
*/
export type FetchErrorFunction<T = any, D = any> = (
error: AxiosError<T, D>,
mode: string,
) => void

View File

@ -1,14 +1,3 @@
/**
*
* @author Ray <https://github.com/XiaoDaiGua-Ray>
*
* @date 2023-02-27
*
* @workspace ray-template
*
* @remark
*/
/** /**
* *
* *
@ -18,6 +7,17 @@
import type { AppRawRequestConfig, CancelerParams } from '@/axios/types' import type { AppRawRequestConfig, CancelerParams } from '@/axios/types'
/**
*
* @class RequestCanceler
*
* @description
* signal
* generateRequestKey key
*
* cancelConfig.cancel true
* __CANCELER_TAG_RAY_TEMPLATE__
*/
export default class RequestCanceler { export default class RequestCanceler {
private pendingRequest: Map<string, AbortController> private pendingRequest: Map<string, AbortController>
@ -25,7 +25,19 @@ export default class RequestCanceler {
this.pendingRequest = new Map<string, AbortController>() this.pendingRequest = new Map<string, AbortController>()
} }
/** 是否需要加入取消请求表中 */ /**
*
* @param config config
*
* @description
* signal
*
* cancelConfig false signal
* cancelConfig true signal
*
* @example
* const bool = isAppending(config) // true or false
*/
private isAppending(config: AppRawRequestConfig | CancelerParams) { private isAppending(config: AppRawRequestConfig | CancelerParams) {
return config.cancelConfig?.cancel ?? true return config.cancelConfig?.cancel ?? true
} }
@ -33,9 +45,12 @@ export default class RequestCanceler {
/** /**
* *
* @param config config * @param config config
* @returns key
* *
* @remark config request key * @description
* key
*
* @example
* const key = generateRequestKey(config) // string
*/ */
private generateRequestKey(config: AppRawRequestConfig | CancelerParams) { private generateRequestKey(config: AppRawRequestConfig | CancelerParams) {
const { method, url } = config const { method, url } = config
@ -52,7 +67,15 @@ export default class RequestCanceler {
* *
* @param config axios request config * @param config axios request config
* *
* @remark signal , * @description
* pendingRequest map
* signal
*
* cancelConfig.cancel false
* __CANCELER_TAG_RAY_TEMPLATE__
*
* @example
* addPendingRequest(config)
*/ */
addPendingRequest(config: AppRawRequestConfig | CancelerParams) { addPendingRequest(config: AppRawRequestConfig | CancelerParams) {
if (this.isAppending(config)) { if (this.isAppending(config)) {
@ -77,7 +100,11 @@ export default class RequestCanceler {
* *
* @param config axios request config * @param config axios request config
* *
* @remark , map generateRequestKey value * @description
* pendingRequest map
*
* @example
* removePendingRequest(config)
*/ */
removePendingRequest(config: AppRawRequestConfig | CancelerParams) { removePendingRequest(config: AppRawRequestConfig | CancelerParams) {
const requestKey = this.generateRequestKey(config) const requestKey = this.generateRequestKey(config)
@ -88,7 +115,17 @@ export default class RequestCanceler {
} }
} }
/** 取消所有请求 */ /**
*
* @description
* pendingRequest map
*
*
* cancelConfig.cancel false
*
* @example
* cancelAllRequest()
*/
cancelAllRequest() { cancelAllRequest() {
this.pendingRequest.forEach((curr) => { this.pendingRequest.forEach((curr) => {
curr.abort() curr.abort()

View File

@ -0,0 +1,31 @@
import type { RawAxiosRequestHeaders, AxiosRequestConfig } from 'axios'
import type { RequestHeaderOptions } from '../types'
/**
*
* @param instance axios instance
* @param options axios headers options
*
* @description
* axios
*
* @example
* appendRequestHeaders(inst, [
* {
* key: 'Demo-Header-Key',
* value: 'Demo Header Value',
* }
* ])
*/
export const appendRequestHeaders = <T = unknown>(
instance: AxiosRequestConfig<T>,
options: RequestHeaderOptions[],
) => {
if (instance) {
const requestHeaders = instance.headers as RawAxiosRequestHeaders
options.forEach((curr) => {
requestHeaders[curr.key] = curr.value
})
}
}

View File

@ -1,16 +1,3 @@
/**
*
* @author Ray <https://github.com/XiaoDaiGua-Ray>
*
* @date 2023-06-02
*
* @workspace ray-template
*
* @remark
*/
/** axios 拦截器工具 */
import type { RawAxiosRequestHeaders, AxiosRequestConfig } from 'axios' import type { RawAxiosRequestHeaders, AxiosRequestConfig } from 'axios'
import type { RequestHeaderOptions } from '../types' import type { RequestHeaderOptions } from '../types'

View File

@ -1,25 +1,3 @@
/**
*
* @author Ray <https://github.com/XiaoDaiGua-Ray>
*
* @date 2023-06-05
*
* @workspace ray-template
*
* @remark
*/
/**
*
* axios
*
*
*
*
* axios ,
* 使,
*/
import RequestCanceler from '@/axios/utils/RequestCanceler' import RequestCanceler from '@/axios/utils/RequestCanceler'
import { getAppEnvironment } from '@/utils' import { getAppEnvironment } from '@/utils'
@ -35,30 +13,36 @@ import type {
import type { AnyFC } from '@/types' import type { AnyFC } from '@/types'
import type { AxiosError } from 'axios' import type { AxiosError } from 'axios'
/** 当前请求的实例 */ // 当前请求的实例
const axiosFetchInstance: AxiosFetchInstance = { const axiosFetchInstance: AxiosFetchInstance = {
requestInstance: null, requestInstance: null,
responseInstance: null, responseInstance: null,
} }
/** 请求失败返回值 */ // 请求失败返回值
const axiosFetchError: AxiosFetchError<AxiosError<unknown, unknown>> = { const axiosFetchError: AxiosFetchError<AxiosError<unknown, unknown>> = {
requestError: null, requestError: null,
responseError: null, responseError: null,
} }
/** 请求队列(区分 resolve 与 reject 状态) */ // 请求队列(区分 resolve 与 reject 状态)
const implement: ImplementQueue = { const implement: ImplementQueue = {
implementRequestInterceptorArray: [], implementRequestInterceptorArray: [],
implementResponseInterceptorArray: [], implementResponseInterceptorArray: [],
} }
// 请求失败队列
const errorImplement: ErrorImplementQueue = { const errorImplement: ErrorImplementQueue = {
implementRequestInterceptorErrorArray: [], implementRequestInterceptorErrorArray: [],
implementResponseInterceptorErrorArray: [], implementResponseInterceptorErrorArray: [],
} }
/** 取消器实例 */
type ImplementKeys = keyof ImplementQueue
type ErrorImplementKeys = keyof ErrorImplementQueue
// 取消器实例
export const axiosCanceler = new RequestCanceler() export const axiosCanceler = new RequestCanceler()
export const useAxiosInterceptor = () => { export const useAxiosInterceptor = () => {
/** 创建拦截器实例 */ // 创建拦截器实例
const createAxiosInstance = ( const createAxiosInstance = (
instance: RequestInterceptorConfig | ResponseInterceptorConfig, instance: RequestInterceptorConfig | ResponseInterceptorConfig,
instanceKey: keyof AxiosFetchInstance, instanceKey: keyof AxiosFetchInstance,
@ -70,29 +54,33 @@ export const useAxiosInterceptor = () => {
instance as ResponseInterceptorConfig) instance as ResponseInterceptorConfig)
} }
/** 获取当前实例 */ // 获取当前实例
const getAxiosInstance = (instanceKey: keyof AxiosFetchInstance) => { const getAxiosInstance = (instanceKey: keyof AxiosFetchInstance) => {
return axiosFetchInstance[instanceKey] return axiosFetchInstance[instanceKey]
} }
/** 设置注入方法队列 */ // 设置注入方法队列
const setImplement = ( const setImplement = (
key: keyof ImplementQueue | keyof ErrorImplementQueue, key: ImplementKeys | ErrorImplementKeys,
func: AnyFC[], func: AnyFC[],
fetchType: FetchType, fetchType: FetchType,
) => { ) => {
fetchType === 'ok' ? (implement[key] = func) : (errorImplement[key] = func) fetchType === 'ok'
? (implement[key as ImplementKeys] = func)
: (errorImplement[key as ErrorImplementKeys] = func)
} }
/** 获取队列中所有的所有拦截器方法 */ // 获取队列中所有的所有拦截器方法
const getImplement = ( const getImplement = (
key: keyof ImplementQueue | keyof ErrorImplementQueue, key: ImplementKeys | ErrorImplementKeys,
fetchType: FetchType, fetchType: FetchType,
): AnyFC[] => { ): AnyFC[] => {
return fetchType === 'ok' ? implement[key] : errorImplement[key] return fetchType === 'ok'
? implement[key as ImplementKeys]
: errorImplement[key as ErrorImplementKeys]
} }
/** 队列执行器 */ // 队列执行器
const implementer = (funcs: AnyFC[], ...args: any[]) => { const implementer = (funcs: AnyFC[], ...args: any[]) => {
if (Array.isArray(funcs)) { if (Array.isArray(funcs)) {
funcs.forEach((curr) => { funcs.forEach((curr) => {
@ -103,16 +91,16 @@ export const useAxiosInterceptor = () => {
} }
} }
/** 请求、响应前执行拦截器队列中的所有方法 */ // 请求、响应前执行拦截器队列中的所有方法
const beforeFetch = ( const beforeFetch = (
key: keyof AxiosFetchInstance, key: keyof AxiosFetchInstance,
implementKey: keyof ImplementQueue | keyof ErrorImplementQueue, implementKey: ImplementKeys | ErrorImplementKeys,
fetchType: FetchType, fetchType: FetchType,
) => { ) => {
const funcArr = const funcArr =
fetchType === 'ok' fetchType === 'ok'
? implement[implementKey] ? implement[implementKey as ImplementKeys]
: errorImplement[implementKey] : errorImplement[implementKey as ErrorImplementKeys]
const instance = getAxiosInstance(key) const instance = getAxiosInstance(key)
const { MODE } = getAppEnvironment() const { MODE } = getAppEnvironment()
@ -121,11 +109,11 @@ export const useAxiosInterceptor = () => {
} }
} }
/** 请求、响应错误时执行队列中所有方法 */ // 请求、响应错误时执行队列中所有方法
const fetchError = ( const fetchError = (
key: keyof AxiosFetchError, key: keyof AxiosFetchError,
error: AxiosError<unknown, unknown>, error: AxiosError<unknown, unknown>,
errorImplementKey: keyof ErrorImplementQueue, errorImplementKey: ErrorImplementKeys,
) => { ) => {
axiosFetchError[key] = error axiosFetchError[key] = error

View File

@ -1,99 +0,0 @@
/**
*
* @author Ray <https://github.com/XiaoDaiGua-Ray>
*
* @date 2022-12-27
*
* @workspace ray-template
*
* @remark
*/
/**
*
* <https://www.naiveui.com/zh-CN/dark/components/grid>
*
*
* 使
*
* `NGrid` `NGridItem` , 使使 `NGridItem`
*/
import './index.scss'
import { NCard, NGrid, NGridItem, NFlex } from 'naive-ui'
import { RIcon } from '@/components'
import { call } from '@/utils'
import props from './props'
export default defineComponent({
name: 'RCollapseGrid',
props,
setup(props) {
const modelCollapsed = ref(!props.open)
const collapseClick = () => {
modelCollapsed.value = !modelCollapsed.value
const { onUpdateValue, 'onUpdate:value': _onUpdateValue } = props
if (onUpdateValue) {
call(onUpdateValue, modelCollapsed.value)
}
if (_onUpdateValue) {
call(_onUpdateValue, modelCollapsed.value)
}
}
const CollapseIcon = () => (
<div class="collapse-icon" onClick={collapseClick.bind(this)}>
<span>
{modelCollapsed.value
? props.collapseToggleText[0]
: props.collapseToggleText[1]}
</span>
<RIcon
customClassName={`collapse-icon--arrow ${
modelCollapsed.value ? '' : 'collapse-icon--arrow__expanded'
}`}
name="expanded"
size="14"
/>
</div>
)
return {
modelCollapsed,
collapseClick,
CollapseIcon,
}
},
render() {
return (
<NCard bordered={this.bordered}>
{{
default: () => (
<NGrid
class="ray-collapse-grid"
{...this.$props}
collapsed={this.modelCollapsed}
xGap={this.xGap || 12}
yGap={this.yGap || 18}
collapsedRows={this.collapsedRows}
>
{this.$slots.default?.()}
<NGridItem suffix class="ray-collapse-grid__suffix--btn">
<NFlex justify="end" align="center">
{this.$slots.action?.()}
{this.CollapseIcon()}
</NFlex>
</NGridItem>
</NGrid>
),
}}
</NCard>
)
},
})

View File

@ -1,51 +0,0 @@
/**
*
* @author Ray <https://github.com/XiaoDaiGua-Ray>
*
* @date 2024-03-27
*
* @workspace ray-template
*
* @remark
*/
import { NForm } from 'naive-ui'
import props from './props'
import { call } from '@/utils'
import type { RFormInst } from './types'
export default defineComponent({
name: 'RForm',
props,
setup(props, { expose }) {
const formRef = ref<RFormInst>()
onMounted(() => {
// 主动调用 register 方法,满足 useForm 方法正常调用
const { onRegister } = props
if (onRegister && formRef.value) {
call(onRegister, formRef.value)
}
})
expose()
return {
formRef,
}
},
render() {
const { $attrs, $props, $slots } = this
return (
<NForm {...$attrs} {...$props} ref="formRef">
{{
...$slots,
}}
</NForm>
)
},
})

View File

@ -1,113 +0,0 @@
import { cloneDeep } from 'lodash-es'
import type {
RFormInst,
FormValidateCallback,
ShouldRuleBeApplied,
RFormRules,
} from '../types'
/**
*
* @description
* RForm
* 便
*
* @warning
* register 使
* 使 hooks register
* create hook使 nextTick
*
* @example
* defineComponent({
* setup() {
* const [register, { ...Hooks }] = useForm()
*
* return {
* register,
* ...Hooks,
* }
* },
* render() {
* const { register, ...Hooks } = this
*
* return <RForm onRegister={register} />
* },
* })
*/
const useForm = <T extends Record<string, unknown>, R extends RFormRules>(
model?: T,
rules?: R,
) => {
const formRef = ref<RFormInst>()
const register = (inst: RFormInst) => {
if (inst) {
formRef.value = inst
}
}
const getFormInstance = () => {
if (!formRef.value) {
throw new Error(
'[useForm]: form instance is not ready yet. if you are using useForm, please make sure you have called register method in onRegister event.',
)
}
return formRef.value
}
/**
*
* @description
* Promise rejection FormValidationError[]
*
* @see https://www.naiveui.com/zh-CN/dark/components/form#inline.vue
*/
const validate = (
callback?: FormValidateCallback,
shouldRuleBeApplied?: ShouldRuleBeApplied,
) => getFormInstance().validate.call(null, callback, shouldRuleBeApplied)
/**
*
* @description
*
*
* @see https://www.naiveui.com/zh-CN/dark/components/form#Form-Methods
*/
const restoreValidation = () => getFormInstance().restoreValidation.call(null)
/**
*
* @description
*
*
* useForm model
*/
const formModel = () => cloneDeep(model) || ({} as T)
/**
*
* @description
*
*
* useForm rules
*/
const formRules = () => cloneDeep(rules) || ({} as R)
return [
register,
{
getFormInstance,
validate,
restoreValidation,
formModel,
formRules,
},
] as const
}
export type UseFormReturn = ReturnType<typeof useForm>
export default useForm

View File

@ -1,24 +0,0 @@
import { formProps } from 'naive-ui'
import type { MaybeArray } from '@/types'
import type { RFormInst } from './types'
const props = {
...formProps,
/**
*
* @description
* RForm
* useForm register 使便使 hooks
*
* @default null
*/
onRegister: {
type: [Function, Array] as PropType<
MaybeArray<(formInst: RFormInst) => void>
>,
default: null,
},
} as const
export default props

View File

@ -1,14 +0,0 @@
/**
*
* @author Ray <https://github.com/XiaoDaiGua-Ray>
*
* @date 2023-10-03
*
* @workspace ray-template
*
* @remark
*/
export interface RIframeInst {
iframe: Ref<HTMLIFrameElement>
}

View File

@ -1,119 +0,0 @@
/**
*
* @author Ray <https://github.com/XiaoDaiGua-Ray>
*
* @date 2023-11-22
*
* @workspace ray-template
*
* @remark
*/
import './index.scss'
import { NModal } from 'naive-ui'
import props from './props'
import { completeSize, uuid } from '@/utils'
import { setupInteract } from './utils'
import {
FULLSCREEN_CARD_TYPE_CLASS,
R_MODAL_CLASS,
CSS_VARS_KEYS,
} from './constant'
import type interact from 'interactjs'
export default defineComponent({
name: 'RModal',
props,
setup(props) {
const cssVars = computed(() => ({
[CSS_VARS_KEYS['width']]: completeSize(props.width ?? 600),
[CSS_VARS_KEYS['cardWidth']]: completeSize(props.cardWidth ?? 600),
[CSS_VARS_KEYS['dialogWidth']]: completeSize(props.dialogWidth ?? 446),
}))
const uuidEl = uuid()
let intractable: null | ReturnType<typeof interact>
// 记录拖拽的位置
const position = {
x: 0,
y: 0,
}
// 当前是否为预设 card 类型并且设置了 fullscreen
const isFullscreenCardType = computed(
() => props.preset === 'card' && props.fullscreen,
)
watch(
() => props.show,
(ndata) => {
if (
ndata &&
props.dad &&
(props.preset === 'card' || props.preset === 'dialog')
) {
nextTick(() => {
const target = document.getElementById(uuidEl)
if (target) {
setupInteract(target, {
preset: props.preset,
x: position.x,
y: position.y,
dargCallback: (x, y) => {
position.x = x
position.y = y
},
}).then((res) => {
intractable = res
})
}
if (props.memo && target) {
target.style.transform = `translate(${position.x}px, ${position.y}px)`
}
})
} else {
intractable?.unset()
intractable = null
}
},
{
immediate: true,
},
)
return {
cssVars,
isFullscreenCardType,
uuidEl,
}
},
render() {
const { $props, $slots, $attrs } = this
const { preset, ...$otherProps } = $props
const { cssVars, uuidEl, isFullscreenCardType } = this
return (
<NModal
class={[
R_MODAL_CLASS,
isFullscreenCardType ? FULLSCREEN_CARD_TYPE_CLASS : '',
]}
style={[cssVars, isFullscreenCardType ? `height: 100vh` : '']}
preset={preset}
{...{
id: uuidEl,
}}
{...$otherProps}
{...$attrs}
>
{{
...$slots,
}}
</NModal>
)
},
})

View File

@ -1,101 +0,0 @@
import interact from 'interactjs'
import type { ModalProps } from 'naive-ui'
import type { RModalProps } from './types'
interface SetupDraggableOptions {
scheduler?: (event: Interact.DragEvent) => void
}
interface SetupInteractOptions {
preset: ModalProps['preset']
memo?: RModalProps['memo']
x: number
y: number
dargCallback?: (x: number, y: number, event: Interact.DragEvent) => void
}
/**
*
* @param bindModal modal
* @param preset
*
* @description
*
* card, dialog
*
* 30ms
*/
export const setupDraggable = (
bindModal: HTMLElement,
preset: ModalProps['preset'],
options?: SetupDraggableOptions,
): Promise<ReturnType<typeof interact>> => {
const { scheduler } = options ?? {}
return new Promise((resolve) => {
setTimeout(() => {
const allowFromStr =
preset === 'card' ? '.n-card-header__main' : '.n-dialog__title'
if (bindModal) {
const dad = interact(bindModal)
.draggable({
inertia: true,
autoScroll: true,
allowFrom: allowFromStr,
modifiers: [
interact.modifiers.restrictRect({
restriction: 'parent',
endOnly: true,
}),
],
listeners: {
move: (event) => {
scheduler?.(event)
},
},
})
.resizable(false)
resolve(dad)
}
}, 30)
})
}
export const setupInteract = (
target: HTMLElement | string,
options: SetupInteractOptions,
): Promise<ReturnType<typeof interact>> => {
const _target =
typeof target === 'string'
? (document.querySelector(target) as HTMLElement)
: target
return new Promise((resolve, reject) => {
if (_target) {
_target.setAttribute('can-drag', 'true')
const { preset, dargCallback } = options
let { x, y } = options
setupDraggable(_target, preset, {
scheduler: (event) => {
const target = event.target
x += event.dx
y += event.dy
target.style.transform = `translate(${x}px, ${y}px)`
dargCallback?.(x, y, event)
},
}).then((res) => {
resolve(res)
})
} else {
reject()
}
})
}

View File

@ -1,10 +0,0 @@
import RQRCode from './src'
import qrcodeProps from './src/props'
import type * as RQRCodeType from './src/types'
import type { ExtractPublicPropTypes } from 'vue'
export type QRCodeProps = ExtractPublicPropTypes<typeof qrcodeProps>
export type { RQRCodeType }
export { RQRCode, qrcodeProps }

View File

@ -1,37 +0,0 @@
.ray-qrcode {
position: relative;
display: inline-flex;
& .ray-qrcode__error {
position: absolute;
width: 100%;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
z-index: 10;
background-color: rgba(0, 0, 0, 0.7);
width: 100%;
height: 100%;
@include flexCenter;
flex-direction: column;
gap: 18px 0;
border-radius: 3px;
& .ray-qrcode__error-content {
text-align: center;
font-size: 18px;
font-weight: 500;
color: #ffffff;
}
}
}
.ray-qrcode {
&.ray-qrcode--loading img {
filter: blur(5px);
}
&.ray-qrcode--error img {
filter: blur(5px);
}
}

View File

@ -1,203 +0,0 @@
/**
*
* @author Ray <https://github.com/XiaoDaiGua-Ray>
*
* @date 2023-08-29
*
* @workspace ray-template
*
* @remark
*/
import './index.scss'
import { NButton, NSpin } from 'naive-ui'
import { RIcon } from '@/components'
import props from './props'
import { AwesomeQR } from 'awesome-qr'
import { isValueType, downloadAnyFile, call } from '@/utils'
import type {
QRCodeRenderResponse,
GIFBuffer,
DownloadFilenameType,
} from './types'
import type { WatchStopHandle } from 'vue'
const readGIFAsArrayBuffer = (url: string): Promise<GIFBuffer> => {
return new Promise((resolve, reject) => {
const xhr = new XMLHttpRequest()
xhr.responseType = 'blob'
xhr.onload = () => {
const reader = new FileReader()
reader.onloadend = () => {
resolve(reader.result)
}
reader.onerror = (e) => {
reject(e)
}
reader.onabort = (e) => {
reject(e)
}
reader.readAsArrayBuffer(xhr.response)
}
xhr.open('GET', url)
xhr.send()
})
}
export default defineComponent({
name: 'RayQRcode',
props,
setup(props, ctx) {
const { expose } = ctx
const qrcodeURL = ref<QRCodeRenderResponse>()
let gifBuffer: GIFBuffer
let watchCallback!: WatchStopHandle
const getGIFImageByURL = async () => {
const { gifBackgroundURL } = props
if (!gifBackgroundURL) {
return
}
try {
gifBuffer = await readGIFAsArrayBuffer(gifBackgroundURL)
} catch (e) {
console.error(e)
}
}
const renderQRCode = () => {
const { gifBackground, ...ops } = props
new AwesomeQR({
...ops,
gifBackground: (gifBuffer as ArrayBuffer) ?? void 0,
})
.draw()
.then((res) => {
const { onSuccess } = props
if (onSuccess) {
call(onSuccess, res)
}
qrcodeURL.value = res
})
.catch((err) => {
const { onError } = props
if (onError) {
call(onError, err)
}
})
}
const errorActionClick = () => {
if (ctx.slots.errorAction) {
return
}
const { onReload } = props
if (onReload) {
call(onReload)
}
}
const downloadQRCode = (fileName?: DownloadFilenameType) => {
if (qrcodeURL.value && isValueType<string>(qrcodeURL.value, 'String')) {
return downloadAnyFile(
qrcodeURL.value,
fileName || new Date().getTime() + '.png',
)
} else {
return Promise.reject()
}
}
watchEffect(() => {
if (props.watchText) {
watchCallback = watch(
() => props.text,
() => renderQRCode(),
)
} else {
watchCallback?.()
}
})
expose({
downloadQRCode,
})
onMounted(async () => {
await getGIFImageByURL()
renderQRCode()
})
onBeforeUnmount(() => {
watchCallback?.()
})
return {
qrcodeURL,
errorActionClick,
}
},
render() {
const {
qrcodeURL,
status,
loadingDescription,
errorDescription,
$slots,
errorActionDescription,
} = this
const { errorActionClick } = this
return (
<div class={['ray-qrcode', `ray-qrcode--${status}`]}>
<NSpin show={status === 'loading'} description={loadingDescription}>
<img class="r-qr-code__image" src={qrcodeURL as string | undefined} />
</NSpin>
{status === 'error' ? (
<div class="ray-qrcode__error">
<div class="ray-qrcode__error-content">
{isValueType<string>(errorDescription, 'String')
? errorDescription
: () => errorDescription}
</div>
<div
class="ray-qrcode__error-btn"
onClick={errorActionClick.bind(this)}
>
{$slots.errorAction ? (
$slots.errorAction()
) : (
<>
<NButton text type="primary" color="#ffffff">
{{
default: () => errorActionDescription,
icon: () => (
<RIcon name="reload" size="16" color="#ffffff" />
),
}}
</NButton>
</>
)}
</div>
</div>
) : null}
</div>
)
},
})

View File

@ -1,310 +0,0 @@
/**
*
* @author Ray <https://github.com/XiaoDaiGua-Ray>
*
* @date 2023-08-29
*
* @workspace ray-template
*
* @remark
*/
import type { QRCodeStatus, QRCodeLevel } from './types'
import type { PropType, VNode } from 'vue'
import type { MaybeArray } from '@/types'
import type { Options } from 'awesome-qr'
const props = {
loadingDescription: {
/**
*
* Loading status description label
*
* @default undefined
*/
type: String,
},
watchText: {
/**
*
* Auto watch QR code text
* If update text, then re-render QR code
*
* @default true
*/
type: Boolean,
default: true,
},
status: {
/**
*
* QR code status
*
* @default undefined
*/
type: String as PropType<QRCodeStatus>,
},
errorDescription: {
/**
*
* QR code error description label
*
* @default
*/
type: [String, Object] as PropType<string | VNode>,
default: '二维码已过期',
},
errorActionDescription: {
/**
*
* QR code error action description label
*
* @default
*/
type: String,
default: '重新加载',
},
text: {
/**
*
* Text to be encoded in the QR code
*/
type: String,
required: true,
},
size: {
/**
*
* Size of the QR code in pixel.
*
* @default 160
*/
type: Number,
default: 160,
},
margin: {
/**
*
* Size of margins around the QR code body in pixel.
*
* @default 12
*/
type: Number,
default: 12,
},
correctLevel: {
/**
*
* Error correction level of the QR code
* Accepts a value provided by _QRErrorCorrectLevel_
*
* @default 1
*/
type: Number as PropType<QRCodeLevel>,
default: 1,
validator: (value: unknown) => [0, 1, 2, 3].includes(value as number),
},
maskPattern: {
/**
*
* Specify the mask pattern to be used in QR code encoding
* Accepts a value provided by _QRMaskPattern_
*/
type: Number,
},
version: {
/**
*
* Specify the version to be used in QR code encoding
* Accepts an integer in range [1, 40]
*/
type: Number,
},
components: {
/**
*
* Options to control components in the QR code.
*
* @default {data:{scale...},...}
*/
type: Object as PropType<Options['components']>,
default: () => ({
data: {
scale: 1,
},
timing: {
scale: 1,
protectors: false,
},
alignment: {
scale: 1,
protectors: false,
},
cornerAlignment: {
scale: 1,
protectors: true,
},
}),
},
colorDark: {
/**
*
* Color of the blocks on the QR code
* Accepts a CSS &lt;color&gt;
*
* @default #000000
*/
type: String,
default: '#000000',
},
colorLight: {
/**
*
* Color of the blocks on the QR code
* Accepts a CSS &lt;color&gt;
*
* @default #ffffff
*/
type: String,
default: '#ffffff',
},
autoColor: {
/**
*
* Automatically calculate the _colorLight_ value from the QR code's background
*
* @default true
*/
type: Boolean,
default: true,
},
backgroundImage: {
/**
*
* Background image to be used in the QR code
* Accepts a `data:` string in web browsers or a Buffer in Node.js
*
* @default undefined
*/
type: String,
},
backgroundDimming: {
/**
*
* Color of the dimming mask above the background image
* Accepts a CSS &lt;color&gt;
*
* @default rgba(0, 0, 0, 0)
*/
type: String,
default: 'rgba(0, 0, 0, 0)',
},
gifBackgroundURL: {
/**
*
* GIF background image to be used in the QR code
*
* @default undefined
*/
type: String,
},
gifBackground: {
/**
*
* GIF background image to be used in the QR code
*
* @default undefined
*/
type: ArrayBuffer,
},
whiteMargin: {
/**
*
* Use a white margin instead of a transparent one which reveals the background of the QR code on margins
*
* @default true
*/
type: Boolean,
default: true,
},
logoImage: {
/**
*
* Logo image to be displayed at the center of the QR code
* Accepts a `data:` string in web browsers or a Buffer in Node.js
* When set to `undefined` or `null`, the logo is disabled
*
* @default undefined
*/
type: String,
},
logoScale: {
/**
*
* Ratio of the logo size to the QR code size
*
* @default 0.4
*/
type: Number,
default: 0.4,
},
logoMargin: {
/**
*
* Size of margins around the logo image in pixels
*
* @default 6
*/
type: Number,
default: 6,
},
logoCornerRadius: {
/**
* Corner radius of the logo image in pixels.
*
* @default 8
*/
type: Number,
default: 8,
},
dotScale: {
/**
*
* Ratio of the real size to the full size of the blocks.
* This can be helpful when you want to make more parts of the background visible.
*
* @default 1
*/
type: Number,
default: 1,
},
onSuccess: {
/**
*
* When the QR code is successfully generated, this callback is called
*/
type: [Function, Array] as PropType<
MaybeArray<(dataURL: ArrayBuffer | string | undefined) => void>
>,
default: null,
},
onError: {
/**
*
* When the QR code generation fails, this callback is called
*/
type: [Function, Array] as PropType<MaybeArray<(e: unknown) => void>>,
default: null,
},
onReload: {
/**
*
* When reload button is clicked, this callback is called
* This method will not execute if the errorAction slot is used
*/
type: [Function, Array] as PropType<MaybeArray<() => void>>,
default: null,
},
} as const
export default props

View File

@ -1,32 +0,0 @@
/**
*
* @author Ray <https://github.com/XiaoDaiGua-Ray>
*
* @date 2023-08-29
*
* @workspace ray-template
*
* @remark
*/
export type QRCodeStatus = 'error' | 'success' | 'loading'
export type QRCodeLevel = 0 | 1 | 2 | 3
export type QRCodeRenderResponse = string | ArrayBuffer | Buffer | undefined
export type QRCodeInst = {
/**
*
* @param fileName file name
*
* .png
*/
downloadQRCode: (fileName?: DownloadFilenameType) => void
}
export type GIFBuffer = string | ArrayBuffer | null
export type DefaultDownloadImageType = 'png' | 'jpg' | 'jpeg' | 'webp'
export type DownloadFilenameType = `${string}.${DefaultDownloadImageType}`

View File

@ -1,44 +0,0 @@
<template>
<!-- 这是一个魔法注释删不的如果删了会出现一个异常提示不信你试试 -->
<RouterView v-slot="{ Component, route }">
<template v-if="Component">
<Transition
:name="transitionPropName"
:mode="transitionMode"
:appear="transitionAppear"
>
<Suspense>
<KeepAlive
v-if="setupKeepAlive"
:max="maxKeepAliveLength"
:include="getKeepAliveInclude"
:exclude="keepAliveExclude"
>
<Component :is="Component" :key="route.fullPath" />
</KeepAlive>
<Component :is="Component" v-else :key="route.fullPath" />
</Suspense>
</Transition>
</template>
</RouterView>
</template>
<script lang="ts" setup>
import { useKeepAliveGetters } from '@/store'
import { APP_KEEP_ALIVE } from '@/app-config'
import props from './props'
import type { TransitionProps } from './types'
/**
*
* 使用宏编译模式时可以使用 defineOptions 声明组件选项
* 常用方法即是声明该组件的 name inheritAttrs 等属性
*/
defineOptions({
name: 'RTransitionComponent',
})
withDefaults(defineProps<TransitionProps>(), props)
const { getKeepAliveInclude } = useKeepAliveGetters()
const { setupKeepAlive, maxKeepAliveLength, keepAliveExclude } = APP_KEEP_ALIVE
</script>

View File

@ -0,0 +1,8 @@
import RBarcode from './src/Barcode'
import barcodeProps from './src/props'
import type { ExtractPublicPropTypes } from 'vue'
export type BarcodeProps = ExtractPublicPropTypes<typeof barcodeProps>
export { RBarcode, barcodeProps }

View File

@ -0,0 +1,100 @@
import './index.scss'
import { NSpin } from 'naive-ui'
import barcode from 'jsbarcode'
import props from './props'
import { completeSize, call } from '@/utils'
import { useTemplateRef } from 'vue'
import type { WatchStopHandle } from 'vue'
export default defineComponent({
name: 'RBarcode',
props,
setup(props) {
const barcodeRef = useTemplateRef<HTMLCanvasElement | HTMLOrSVGElement>(
'barcodeRef',
)
const cssVars = computed(() => {
const cssVar = {
'--r-barcode-width': completeSize(props.width),
'--r-barcode-height': completeSize(props.height),
}
return cssVar
})
let watchStop: WatchStopHandle
const barcodeRender = () => {
try {
const { format, text, options, onSuccess } = props
const assignOptions = Object.assign({}, options, {
format,
})
barcode(
barcodeRef.value,
text !== void 0 && text !== null ? text.toString() : '',
assignOptions,
)
if (onSuccess) {
call(onSuccess, text, format, options)
}
} catch (e) {
const { onError } = props
if (onError) {
call(onError, e as Error)
}
} finally {
const { onFinally } = props
if (onFinally) {
call(onFinally)
}
}
}
watchEffect(() => {
if (props.watchText) {
watchStop = watch(() => props.text, barcodeRender)
} else {
watchStop?.()
}
})
onMounted(() => {
barcodeRender()
})
onBeforeUnmount(() => {
watchStop?.()
})
return {
barcodeRef,
cssVars,
}
},
render() {
const { barcodeRender, loading, cssVars } = this
const c = [
'r-barcode',
{
'r-barcode--loading': loading,
},
]
return (
<NSpin class="r-barcode-spin" show={loading}>
{barcodeRender === 'canvas' ? (
<canvas class={c} style={cssVars} ref="barcodeRef" />
) : (
<svg class={c} style={cssVars} ref="barcodeRef" />
)}
</NSpin>
)
},
})

View File

@ -0,0 +1,16 @@
.r-barcode {
position: relative;
width: var(--r-barcode-width);
height: var(--r-barcode-height);
transition: filter 0.3s var(--r-bezier);
&.r-barcode--loading {
filter: blur(4px);
}
}
.r-barcode-spin,
.r-barcode-spin .n-spin-content {
width: max-content !important;
height: max-content !important;
}

View File

@ -0,0 +1,137 @@
import type { RBarcodeRender, RBarcodeOptions, RBarcodeFormat } from './types'
import type { PropType } from 'vue'
import type { MaybeArray } from '@/types'
const props = {
/**
*
* @description
*
*
* @default 'auto'
*/
width: {
type: [String, Number] as PropType<string | number>,
default: 'auto',
},
/**
*
* @description
*
*
* @default 'auto'
*/
height: {
type: [String, Number] as PropType<string | number>,
default: 'auto',
},
/**
*
* @description
* loading
*
* @default false
*/
loading: {
type: Boolean,
default: false,
},
/**
*
* @description
*
* svg canvas
*
* @default 'canvas'
*/
barcodeRender: {
type: String as PropType<RBarcodeRender>,
default: 'canvas',
validator: (value: RBarcodeRender) => ['canvas', 'svg'].includes(value),
},
/**
*
* @description
*
*
* @default undefined
*/
text: {
type: String,
},
/**
*
* @description
*
*
* @see https://github.com/lindell/JsBarcode/wiki/Options
*
* @default {}
*/
options: {
type: Object as PropType<RBarcodeOptions>,
default: () => ({}),
},
/**
*
* @description
*
*
* @default 'CODE128'
*/
format: {
type: String as PropType<RBarcodeFormat>,
default: () => 'CODE128',
},
/**
*
* @description
* text text
*
* @default true
*/
watchText: {
type: Boolean,
default: true,
},
/**
*
* @description
*
*
* @default undefined
*/
onSuccess: {
type: [Function, Array] as PropType<
MaybeArray<
(
currentText: string | undefined,
format: RBarcodeFormat,
option: RBarcodeOptions,
) => void
>
>,
},
/**
*
* @description
*
*
* @default undefined
*/
onError: {
type: [Function, Array] as PropType<MaybeArray<(err: Error) => void>>,
},
/**
*
* @description
*
*
* @default undefined
*/
onFinally: {
type: [Function, Array] as PropType<MaybeArray<() => void>>,
},
} as const
export default props

View File

@ -0,0 +1,26 @@
import type JsBarcode from 'jsbarcode'
export type RBarcodeRender = 'canvas' | 'svg'
export type RBarcodeOptions = JsBarcode.Options
export type RBarcodeFormat =
| 'CODE39'
| 'CODE128'
| 'CODE128A'
| 'CODE128B'
| 'CODE128C'
| 'EAN13'
| 'EAN8'
| 'EAN5'
| 'EAN2'
| 'UPC'
| 'ITF14'
| 'ITF'
| 'MSI'
| 'MSI10'
| 'MSI11'
| 'MSI1010'
| 'MSI1110'
| 'pharmacode'
| 'codabar'

View File

@ -0,0 +1,6 @@
import type { InjectionKey } from 'vue'
import type { ChartProviderOptions } from './hooks/useChartProvider'
export const USE_CHART_PROVIDER_KEY: InjectionKey<
Partial<ChartProviderOptions>
> = Symbol('USE_CHART_PROVIDER_KEY')

View File

@ -79,10 +79,9 @@ const useChart = () => {
* *
* @description * @description
* chart * chart
* true, false * true false
*/ */
const isDispose = () => const isDisposed = () => !!getChartInstance().echartInst?.isDisposed()
!(echartInst && getChartInstance().echartInst.getDom())
/** /**
* *
@ -102,7 +101,7 @@ const useChart = () => {
register, register,
{ {
getChartInstance, getChartInstance,
isDispose, isDisposed,
dispose, dispose,
render, render,
}, },

View File

@ -0,0 +1,18 @@
import { USE_CHART_PROVIDER_KEY } from '../config'
import type { ChartTheme } from '../types'
export interface ChartProviderOptions {
theme: ChartTheme
}
/**
*
* @param
*
* @description
* chart
*/
export const useChartProvider = (options: Partial<ChartProviderOptions>) => {
provide(USE_CHART_PROVIDER_KEY, options)
}

View File

@ -26,7 +26,7 @@ import { NCard } from 'naive-ui'
import props from './props' import props from './props'
import { throttle } from 'lodash-es' import { throttle } from 'lodash-es'
import { completeSize, downloadBase64File, call, renderNode } from '@/utils' import { completeSize, downloadBase64File, call, renderNode } from '@/utils'
import { setupChartTheme } from './utils' import { getCustomEchartTheme, loadingOptions, setEchartOptions } from './utils'
import { APP_THEME } from '@/app-config' import { APP_THEME } from '@/app-config'
import { import {
useResizeObserver, useResizeObserver,
@ -35,6 +35,8 @@ import {
} from '@vueuse/core' } from '@vueuse/core'
import { RMoreDropdown } from '@/components' import { RMoreDropdown } from '@/components'
import { useSettingGetters } from '@/store' import { useSettingGetters } from '@/store'
import { useTemplateRef } from 'vue'
import { USE_CHART_PROVIDER_KEY } from './config'
import type { WatchStopHandle } from 'vue' import type { WatchStopHandle } from 'vue'
import type { AnyFC } from '@/types' import type { AnyFC } from '@/types'
@ -46,15 +48,8 @@ import type {
import type { ECharts, EChartsCoreOption } from 'echarts/core' import type { ECharts, EChartsCoreOption } from 'echarts/core'
import type { DropdownProps, DropdownOption } from 'naive-ui' import type { DropdownProps, DropdownOption } from 'naive-ui'
// setOption 默认配置项
const defaultChartOptions = {
notMerge: false,
lazyUpdate: true,
silent: false,
replaceMerge: [],
}
// 获取 chart 主题 // 获取 chart 主题
const echartThemes = setupChartTheme() const echartThemes = getCustomEchartTheme()
// download 下载功能 key // download 下载功能 key
const __CHART_DOWN_LOAD_CHART__ = '__R_CHART_DOWN_LOAD_CHART__' const __CHART_DOWN_LOAD_CHART__ = '__R_CHART_DOWN_LOAD_CHART__'
@ -92,14 +87,21 @@ export default defineComponent({
props, props,
setup(props, { expose }) { setup(props, { expose }) {
const { getAppTheme } = useSettingGetters() const { getAppTheme } = useSettingGetters()
const rayChartRef = ref<HTMLElement>() // echart 容器实例 // echart 容器实例
const rayChartWrapperRef = ref<HTMLElement>() // echart 父容器实例 const rayChartRef = useTemplateRef<HTMLElement>('rayChartRef')
const echartInstanceRef = ref<ECharts>() // echart 实例 // echart 父容器实例
let resizeThrottleReturn: DebouncedFunc<AnyFC> | null // resize 防抖方法实例 const rayChartWrapperRef = useTemplateRef<HTMLElement>('rayChartWrapperRef')
let resizeObserverReturn: UseResizeObserverReturn | null // resize observer 实例 // echart 实例
const { echartTheme } = APP_THEME // 当前配置主题 const echartInstanceRef = shallowRef<ECharts>()
let watchThrottledCallback: WatchStopHandle | null // watch props 回调 // resize 防抖方法实例
let echartInst: ECharts | null // 无代理响应式代理缓存 echart inst let resizeThrottleReturn: DebouncedFunc<AnyFC> | null
// resize observer 实例
let resizeObserverReturn: UseResizeObserverReturn | null
// 当前配置主题
const { echartTheme } = APP_THEME
// watch props 回调
let watchThrottledCallback: WatchStopHandle | null
// 下拉框配置项
const moreDropDownOptions = computed<DropdownProps['options']>(() => [ const moreDropDownOptions = computed<DropdownProps['options']>(() => [
{ {
label: '下载图片', label: '下载图片',
@ -108,22 +110,30 @@ export default defineComponent({
echartInstanceRef.value && echartInstanceRef.value.getDom() echartInstanceRef.value && echartInstanceRef.value.getDom()
), ),
}, },
]) // 下拉框配置项 ])
const cssVarsRef = computed(() => { const cssVarsRef = computed(() => {
return { return {
'--ray-chart-width': completeSize(props.width), '--ray-chart-width': completeSize(props.width),
'--ray-chart-height': completeSize(props.height), '--ray-chart-height': completeSize(props.height),
} }
}) })
const targetIsVisible = ref(false) // 目标是否可见 // 目标是否可见
let intersectionObserverReturn: UseIntersectionObserverReturn | null // intersectionObserver 实例 const targetIsVisible = ref(false)
// intersectionObserver 实例
let intersectionObserverReturn: UseIntersectionObserverReturn | null
// 缓存一些配置信息
const __catch = {
aria: props.showAria,
}
const chartProvideOptions = inject(USE_CHART_PROVIDER_KEY, {})
/** /**
* *
* `echart` , , * @description
* echart
* *
* `echart` * echart
* *
*/ */
const registerChartCore = async () => { const registerChartCore = async () => {
use([ use([
@ -156,19 +166,29 @@ export default defineComponent({
/** /**
* *
* chart * @description
* chart
* *
* theme autoChangeTheme * theme autoChangeTheme
* theme default chart * theme default chart
* *
* Boolean(theme) false echartTheme * Boolean(theme) false echartTheme
* echartTheme 使 * echartTheme 使
*/ */
const updateChartTheme = () => { const updateChartTheme = () => {
if (echartInst?.getDom()) { const { theme: providerTheme } = chartProvideOptions || {}
if (echartInstanceRef.value) {
destroyChart() destroyChart()
} }
// 如果配置了全局配置主题,则忽略后面所有逻辑
if (providerTheme) {
renderChart(providerTheme)
return
}
if (props.theme === 'default') { if (props.theme === 'default') {
props.autoChangeTheme ? renderChart('dark') : renderChart('') props.autoChangeTheme ? renderChart('dark') : renderChart('')
@ -199,66 +219,61 @@ export default defineComponent({
*/ */
const combineChartOptions = (ops: EChartsCoreOption) => { const combineChartOptions = (ops: EChartsCoreOption) => {
let options = unref(ops) let options = unref(ops)
const assign = (opts: object) => Object.assign({}, options, opts) const assign = (opts: object) => Object.assign({}, options, opts)
if (props.showAria) { // 拦截 aria 配置项
options = assign({ options = assign({
aria: { aria: {
enabled: true, enabled: props.showAria,
decal: { decal: {
show: true, show: props.showAria,
}, },
}, },
}) })
}
return options return options
} }
/** /**
* *
* `echart` * @description
* * echart
*
* 使, `legend`
*/ */
const renderChart = (theme: string = echartTheme) => { const renderChart = (theme: string = echartTheme) => {
/** 获取 dom 容器 */ // 获取 dom 容器
const element = rayChartRef.value as HTMLElement const element = rayChartRef.value as HTMLElement
/** 获取配置项 */ // 获取配置项
const options = combineChartOptions(props.options) const options = combineChartOptions(props.options)
/** 获取 dom 容器实际宽高 */ // 获取 dom 容器实际宽高
const { height, width } = element.getBoundingClientRect() const { height, width } = element.getBoundingClientRect()
const { onSuccess, onError } = props const { onSuccess, onError } = props
try { try {
/** 注册 chart */ // 注册 chart
echartInst = init(element, theme, { echartInstanceRef.value = init(element, theme, {
/** 如果款度为 0, 则以 200px 填充 */ // 如果款度为 0则以 200px 填充
width: width === 0 ? 200 : void 0, width: width === 0 ? 200 : void 0,
/** 如果高度为 0, 则以 200px 填充 */ // 如果高度为 0则以 200px 填充
height: height === 0 ? 200 : void 0, height: height === 0 ? 200 : void 0,
}) })
echartInstanceRef.value = echartInst
// 渲染成功回调 // 渲染成功回调
if (onSuccess) { if (onSuccess) {
call(onSuccess, echartInst) call(onSuccess, echartInstanceRef.value)
} }
// 是否强制下一队列渲染图表 // 是否强制下一队列渲染图表
if (props.nextTick) { if (props.nextTick) {
echartInst.setOption({}) echartInstanceRef.value.setOption({})
nextTick(() => { nextTick(() => {
options && echartInst?.setOption(options) options && echartInstanceRef.value?.setOption(options)
}) })
} else { } else {
options && echartInst?.setOption(options) options && echartInstanceRef.value?.setOption(options)
} }
} catch (e) { } catch (e) {
/** 渲染失败回调 */ // 渲染失败回调
if (onError) { if (onError) {
call(onError) call(onError)
} }
@ -279,26 +294,29 @@ export default defineComponent({
* chart * chart
* true, false * true, false
*/ */
const isDispose = () => !(echartInst && echartInst.getDom()) const isDisposed = () => {
return !!echartInstanceRef.value?.isDisposed()
}
/** /**
* *
* chart , * @description
* chart
*/ */
const destroyChart = () => { const destroyChart = () => {
if (!isDispose()) { if (!isDisposed()) {
echartInst!.clear() echartInstanceRef.value?.dispose()
echartInst!.dispose()
echartInstanceRef.value = void 0
echartInst = null
} }
} }
/** 重置 echarts 尺寸 */ /**
*
* @description
* echarts
*/
const resizeChart = () => { const resizeChart = () => {
if (echartInst) { if (echartInstanceRef.value) {
echartInst.resize() echartInstanceRef.value.resize()
} }
} }
@ -307,15 +325,16 @@ export default defineComponent({
* @param key moreDropDownOptions key * @param key moreDropDownOptions key
* @param option moreDropDownOptions current click option * @param option moreDropDownOptions current click option
* *
* card * @description
* * card
*
*/ */
const dropdownSelect = (key: string | number, option: DropdownOption) => { const dropdownSelect = (key: string | number, option: DropdownOption) => {
if (key === __CHART_DOWN_LOAD_CHART__ && !isDispose()) { if (key === __CHART_DOWN_LOAD_CHART__ && !isDisposed()) {
const { filename, ...args } = props.downloadOptions const { filename, ...args } = props.downloadOptions
downloadBase64File( downloadBase64File(
echartInst!.getDataURL(args), echartInstanceRef.value!.getDataURL(args),
filename ?? `${new Date().getTime()}`, filename ?? `${new Date().getTime()}`,
) )
} }
@ -327,6 +346,11 @@ export default defineComponent({
} }
} }
/**
*
* @description
* chart
*/
const mount = () => { const mount = () => {
// 注册事件 // 注册事件
if (props.autoResize) { if (props.autoResize) {
@ -348,7 +372,7 @@ export default defineComponent({
} }
// 避免重复渲染 // 避免重复渲染
if (echartInst?.getDom()) { if (echartInstanceRef.value?.getDom()) {
return return
} }
@ -357,7 +381,7 @@ export default defineComponent({
return return
} }
// 渲染 chart // 根据主题渲染 chart
updateChartTheme() updateChartTheme()
// 初始化完成后移除 intersectionObserver 监听 // 初始化完成后移除 intersectionObserver 监听
@ -366,21 +390,16 @@ export default defineComponent({
// 注册 register用于 useChart hook // 注册 register用于 useChart hook
const { onRegister } = props const { onRegister } = props
if (onRegister && echartInst) { if (onRegister && echartInstanceRef.value) {
call(onRegister, echartInst, mount, unmount) call(onRegister, echartInstanceRef.value, mount, unmount)
} }
} }
if (props.intersectionObserver) { /**
intersectionObserverReturn = useIntersectionObserver( *
props.intersectionObserverTarget || rayChartWrapperRef, * @description
([entry]) => { * chart
targetIsVisible.value = entry.isIntersecting */
},
props.intersectionOptions,
)
}
const unmount = () => { const unmount = () => {
// 卸载 echarts // 卸载 echarts
destroyChart() destroyChart()
@ -395,15 +414,17 @@ export default defineComponent({
resizeObserverReturn = null resizeObserverReturn = null
} }
/** 监听全局主题变化, 然后重新渲染对应主题 echarts */ // 监听全局主题变化,然后重新渲染对应主题 echarts
watch( watch(
() => getAppTheme.value, () => getAppTheme.value,
() => { () => {
/** /**
* *
* Q: 为什么需要重新卸载再渲染 * @description
* A: 因为 echarts * Q: 为什么需要重新卸载再渲染
* A: 虽然原型上有 setTheme , ECharts 访 * A: 因为 echarts
* setTheme ECharts 访
*
*/ */
if (props.autoChangeTheme) { if (props.autoChangeTheme) {
destroyChart() destroyChart()
@ -411,21 +432,19 @@ export default defineComponent({
} }
}, },
) )
/**
*
*
*
*
*/
watch(
() => props.showAria,
() => {
destroyChart()
updateChartTheme()
},
)
watchEffect(() => { watchEffect(() => {
/** 监听 options 变化 */ // 是否启用了可视区域监听
if (props.intersectionObserver) {
intersectionObserverReturn = useIntersectionObserver(
props.intersectionObserverTarget || rayChartWrapperRef,
([entry]) => {
targetIsVisible.value = entry.isIntersecting
},
props.intersectionOptions,
)
}
// 监听 options 变化
if (props.watchOptions) { if (props.watchOptions) {
watchThrottledCallback = watchThrottled( watchThrottledCallback = watchThrottled(
() => props.options, () => props.options,
@ -434,11 +453,12 @@ export default defineComponent({
const options = combineChartOptions(ndata) const options = combineChartOptions(ndata)
const setOpt = Object.assign( const setOpt = Object.assign(
{}, {},
setEchartOptions(),
props.setChartOptions, props.setChartOptions,
defaultChartOptions,
) )
// 如果 options 发生变动更新 echarts // 如果 options 发生变动更新 echarts
echartInst?.setOption(options, setOpt) echartInstanceRef.value?.setOption(options, setOpt)
}, },
{ {
// 深度监听 options // 深度监听 options
@ -452,11 +472,20 @@ export default defineComponent({
// 监听 loading 变化 // 监听 loading 变化
props.loading props.loading
? echartInst?.showLoading(props.loadingOptions) ? echartInstanceRef.value?.showLoading(
: echartInst?.hideLoading() loadingOptions(props.loadingOptions),
)
: echartInstanceRef.value?.hideLoading()
// 贴花是否启用
if (props.showAria !== __catch.aria && echartInstanceRef.value) {
echartInstanceRef.value.setOption(combineChartOptions(props.options))
__catch.aria = props.showAria
}
// 当前图表容器是否处于可见状态,如果可见则渲染图表 // 当前图表容器是否处于可见状态,如果可见则渲染图表
if (targetIsVisible.value) { if (targetIsVisible.value && !isDisposed()) {
mount() mount()
} }
}) })
@ -503,6 +532,7 @@ export default defineComponent({
style={[this.cssVarsRef]} style={[this.cssVarsRef]}
contentStyle={contentStyle} contentStyle={contentStyle}
bordered={bordered} bordered={bordered}
embedded
> >
{{ {{
default: renderNode( default: renderNode(
@ -516,7 +546,7 @@ export default defineComponent({
<RMoreDropdown <RMoreDropdown
iconSize={18} iconSize={18}
cursor="pointer" cursor="pointer"
options={dropdownOptions ?? moreDropDownOptions} options={dropdownOptions || moreDropDownOptions}
trigger="click" trigger="click"
onSelect={dropdownSelect.bind(this)} onSelect={dropdownSelect.bind(this)}
placement="bottom-end" placement="bottom-end"

View File

@ -1,4 +1,4 @@
import { loadingOptions } from './utils' import { loadingOptions, setEchartOptions } from './utils'
import type * as echarts from 'echarts/core' // echarts 核心模块 import type * as echarts from 'echarts/core' // echarts 核心模块
import type { PropType, VNode } from 'vue' import type { PropType, VNode } from 'vue'
@ -161,7 +161,7 @@ const props = {
* @default 100% * @default 100%
*/ */
width: { width: {
type: String, type: [String, Number] as PropType<string | number>,
default: '100%', default: '100%',
}, },
/** /**
@ -174,7 +174,7 @@ const props = {
* @default 100% * @default 100%
*/ */
height: { height: {
type: String, type: [String, Number] as PropType<string | number>,
default: '100%', default: '100%',
}, },
/** /**
@ -192,7 +192,8 @@ const props = {
* *
* @description * @description
* chart * chart
* options aria * aria enabled decal.show
* options
* *
* @default false * @default false
*/ */
@ -344,6 +345,7 @@ const props = {
* *
* @description * @description
* *
*
* *
* @default true * @default true
*/ */
@ -356,16 +358,11 @@ const props = {
* @description * @description
* setOptions * setOptions
* *
* @default {notMerge:false,lazyUpdate:true,silent:false,replaceMerge:[]} * @default {notMerge:true,lazyUpdate:true,silent:false,replaceMerge:[]}
*/ */
setChartOptions: { setChartOptions: {
type: Object as PropType<SetOptionOpts>, type: Object as PropType<SetOptionOpts>,
default: () => ({ default: () => setEchartOptions(),
notMerge: false,
lazyUpdate: true,
silent: false,
replaceMerge: [],
}),
}, },
/** /**
* *

View File

@ -1,19 +1,7 @@
/**
*
* @author Ray <https://github.com/XiaoDaiGua-Ray>
*
* @date 2023-07-22
*
* @workspace ray-template
*
* @remark
*/
import type { import type {
ChartThemeRawArray, ChartThemeRawArray,
ChartThemeRawModules, ChartThemeRawModules,
LoadingOptions, } from '@/components/base/RChart/src/types'
} from '@/components/RChart/src/types'
/** /**
* *
@ -30,7 +18,7 @@ import type {
* 3. json * 3. json
* 4. echart-themes json * 4. echart-themes json
*/ */
export const setupChartTheme = () => { export const getCustomEchartTheme = () => {
// 获取所有主题 // 获取所有主题
const themeRawModules: Record<string, ChartThemeRawModules> = const themeRawModules: Record<string, ChartThemeRawModules> =
import.meta.glob('@/app-config/echart-themes/**/*.json', { import.meta.glob('@/app-config/echart-themes/**/*.json', {
@ -55,35 +43,3 @@ export const setupChartTheme = () => {
return rawThemes return rawThemes
} }
/**
*
* @param options
*
* @description
* chart
*
* @see https://echarts.apache.org/zh/api.html#echartsInstance.showLoading
*
* @example
* const options = loadingOptions({ ...LoadingOptions })
*/
export const loadingOptions = (options?: LoadingOptions) =>
Object.assign(
{},
{
text: 'loading',
color: '#c23531',
textColor: '#000',
maskColor: 'rgba(255, 255, 255, 0.9)',
zlevel: 0,
fontSize: 12,
showSpinner: true,
spinnerRadius: 10,
lineWidth: 5,
fontWeight: 'normal',
fontStyle: 'normal',
fontFamily: 'sans-serif',
},
options,
)

View File

@ -0,0 +1,3 @@
export { getCustomEchartTheme } from './get-custom-echart-theme'
export { loadingOptions } from './loading-options'
export { setEchartOptions } from './set-echart-options'

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