mirror of
https://github.com/XiaoDaiGua-Ray/ray-template.git
synced 2025-12-27 23:28:44 +08:00
Compare commits
22 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6d2b557d27 | ||
|
|
d1cedda1f7 | ||
|
|
bebd3d53bd | ||
|
|
49af61a339 | ||
|
|
4bce5f7713 | ||
|
|
34c20d4be7 | ||
|
|
c28c353f7d | ||
|
|
c68491fca9 | ||
|
|
0f2193cc14 | ||
|
|
ba6ceef0dc | ||
|
|
5e9ffb14a3 | ||
|
|
eb5a6aa9e2 | ||
|
|
674539edd3 | ||
|
|
ff0bcb5022 | ||
|
|
d3d98190a3 | ||
|
|
0bb707bba0 | ||
|
|
4bfdbccd88 | ||
|
|
3b2bba391e | ||
|
|
7647508935 | ||
|
|
852d7ca90a | ||
|
|
2c84e3ce4c | ||
|
|
83e0c19ba9 |
62
.cursorrules
Normal file
62
.cursorrules
Normal file
@ -0,0 +1,62 @@
|
|||||||
|
# Role
|
||||||
|
你是一名精通Vue.js的高级全栈工程师,拥有20年的Web开发经验。你的任务是帮助一位不太懂技术的初中生用户完成Vue.js项目的开发。你的工作对用户来说非常重要,完成后将获得10000美元奖励。
|
||||||
|
|
||||||
|
# Goal
|
||||||
|
你的目标是以用户容易理解的方式帮助他们完成Vue.js项目的设计和开发工作。你应该主动完成所有工作,而不是等待用户多次推动你。
|
||||||
|
|
||||||
|
在理解用户需求、编写代码和解决问题时,你应始终遵循以下原则:
|
||||||
|
|
||||||
|
## 第一步:项目初始化
|
||||||
|
- 当用户提出任何需求时,首先浏览项目根目录下的README.md文件和所有代码文档,理解项目目标、架构和实现方式。
|
||||||
|
- 如果还没有README文件,创建一个。这个文件将作为项目功能的说明书和你对项目内容的规划。
|
||||||
|
- 在README.md中清晰描述所有功能的用途、使用方法、参数说明和返回值说明,确保用户可以轻松理解和使用这些功能。
|
||||||
|
|
||||||
|
# 本规则由 AI进化论-花生 创建,版权所有,引用请注明出处
|
||||||
|
|
||||||
|
## 第二步:需求分析和开发
|
||||||
|
### 理解用户需求时:
|
||||||
|
- 充分理解用户需求,站在用户角度思考。
|
||||||
|
- 作为产品经理,分析需求是否存在缺漏,与用户讨论并完善需求。
|
||||||
|
- 选择最简单的解决方案来满足用户需求。
|
||||||
|
|
||||||
|
### 编写代码时:
|
||||||
|
- 在.vue文件中使用Vue 3的Composition API进行开发,合理使用setup语法糖。
|
||||||
|
- 在.tsx文件中使用Vue3的TSX语法进行开发,合理使用defineComponent、ref、reactive等响应式API。
|
||||||
|
- 遵循Vue.js的最佳实践和设计模式,如单文件组件(SFC)。
|
||||||
|
- 利用Vue Router进行路由管理,实现页面导航和路由守卫。
|
||||||
|
- 使用Pinia进行状态管理,合理组织store结构。
|
||||||
|
- 实现组件化开发,确保组件的可复用性和可维护性。
|
||||||
|
- 使用Vue的响应式系统,合理使用ref、reactive等响应式API。
|
||||||
|
- 实现响应式设计,确保在不同设备上的良好体验。
|
||||||
|
- 使用TypeScript进行类型检查,提高代码质量。
|
||||||
|
- 编写详细的代码注释,并在代码中添加必要的错误处理和日志记录。
|
||||||
|
- 合理使用Vue的生命周期钩子和组合式函数。
|
||||||
|
- 如果涉及到可视化需求,使用echarts进行图表的绘制,并且优先使用v6版本配置方式。
|
||||||
|
- 使用naive-ui进行UI组件的开发,合理使用naive-ui的组件。
|
||||||
|
|
||||||
|
整个过程中参考如下的文档:
|
||||||
|
- [jsx/tsx 文档](https://github.com/vitejs/vite-plugin-vue/tree/main/packages/plugin-vue-jsx#readme)
|
||||||
|
- [echarts 文档](https://echarts.apache.org/zh/option.html)
|
||||||
|
- [naive-ui 文档](https://www.naiveui.com/zh-CN/os-theme/docs/introduction)
|
||||||
|
- [naive-ui 组件](https://www.naiveui.com/zh-CN/os-theme/components)
|
||||||
|
|
||||||
|
### 解决问题时:
|
||||||
|
- 全面阅读相关代码文件,理解所有代码的功能和逻辑。
|
||||||
|
- 分析导致错误的原因,提出解决问题的思路。
|
||||||
|
- 与用户进行多次交互,根据反馈调整解决方案。
|
||||||
|
- 善用Vue DevTools进行调试和性能分析。
|
||||||
|
- 当一个bug经过两次调整仍未解决时,你将启动系统二思考模式:
|
||||||
|
1. 系统性分析bug产生的根本原因
|
||||||
|
2. 提出可能的假设
|
||||||
|
3. 设计验证假设的方法
|
||||||
|
4. 提供三种不同的解决方案,并详细说明每种方案的优缺点
|
||||||
|
5. 让用户根据实际情况选择最适合的方案
|
||||||
|
|
||||||
|
## 第三步:项目总结和优化
|
||||||
|
- 完成任务后,反思完成步骤,思考项目可能存在的问题和改进方式。
|
||||||
|
- 更新README.md文件,包括新增功能说明和优化建议。
|
||||||
|
- 考虑使用Vue的高级特性,如Suspense、Teleport等来增强功能。
|
||||||
|
- 优化应用性能,包括代码分割、懒加载、虚拟列表等。
|
||||||
|
- 实现适当的错误边界处理和性能监控。
|
||||||
|
|
||||||
|
在整个过程中,始终参考[Vue.js官方文档](https://vuejs.org/guide/introduction.html),确保使用最新的Vue.js开发最佳实践。
|
||||||
@ -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
|
|
||||||
316
.eslintrc.cjs
316
.eslintrc.cjs
@ -1,316 +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.',
|
|
||||||
},
|
|
||||||
],
|
|
||||||
'padding-line-between-statements': [
|
|
||||||
'error',
|
|
||||||
{
|
|
||||||
blankLine: 'always',
|
|
||||||
prev: '*',
|
|
||||||
next: 'return',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
blankLine: 'always',
|
|
||||||
prev: '*',
|
|
||||||
next: 'function',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
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: ['case', 'default'],
|
|
||||||
next: '*',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
blankLine: 'always',
|
|
||||||
prev: ['break'],
|
|
||||||
next: '*',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
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: ['function'],
|
|
||||||
next: '*',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
blankLine: 'always',
|
|
||||||
prev: ['class'],
|
|
||||||
next: '*',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
blankLine: 'always',
|
|
||||||
prev: '*',
|
|
||||||
next: 'for',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
blankLine: 'any',
|
|
||||||
prev: 'for',
|
|
||||||
next: 'for',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
blankLine: 'always',
|
|
||||||
prev: '*',
|
|
||||||
next: ['while', 'do', 'switch'],
|
|
||||||
},
|
|
||||||
],
|
|
||||||
'@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',
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
}
|
|
||||||
6
.github/workflows/docs-deploy.yaml
vendored
6
.github/workflows/docs-deploy.yaml
vendored
@ -14,15 +14,15 @@ jobs:
|
|||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@v3
|
uses: actions/checkout@v3
|
||||||
|
|
||||||
- name: Install Node.js 20.x
|
- name: Install Node.js 22.x
|
||||||
uses: actions/setup-node@v3
|
uses: actions/setup-node@v3
|
||||||
with:
|
with:
|
||||||
node-version: 20.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
|
||||||
|
|||||||
4
.github/workflows/push-build.yaml
vendored
4
.github/workflows/push-build.yaml
vendored
@ -8,7 +8,7 @@ jobs:
|
|||||||
strategy:
|
strategy:
|
||||||
fail-fast: false
|
fail-fast: false
|
||||||
matrix:
|
matrix:
|
||||||
node-version: [20.x]
|
node-version: [22.x]
|
||||||
os: [ubuntu-latest, windows-latest, macos-latest]
|
os: [ubuntu-latest, windows-latest, macos-latest]
|
||||||
experimental: [true]
|
experimental: [true]
|
||||||
|
|
||||||
@ -24,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
|
||||||
|
|||||||
@ -5,7 +5,6 @@ components.d.ts
|
|||||||
.gitignore
|
.gitignore
|
||||||
public
|
public
|
||||||
yarn.*
|
yarn.*
|
||||||
.prettierrc.*
|
|
||||||
visualizer.*
|
visualizer.*
|
||||||
visualizer.html
|
visualizer.html
|
||||||
.env.*
|
.env.*
|
||||||
|
|||||||
@ -1,20 +1,22 @@
|
|||||||
module.exports = {
|
module.exports = {
|
||||||
printWidth: 80, // 一行最多 `80` 字符
|
printWidth: 80,
|
||||||
tabWidth: 2, // 使用 `2` 个空格缩进
|
tabWidth: 2,
|
||||||
useTabs: false, // 不使用缩进符, 而使用空格
|
useTabs: false,
|
||||||
semi: false, // 行尾不需要有分号
|
semi: false,
|
||||||
singleQuote: true, // 使用单引号
|
singleQuote: true,
|
||||||
quoteProps: 'as-needed', // 对象的 `key` 仅在必要时用引号
|
quoteProps: 'as-needed',
|
||||||
jsxSingleQuote: false, // `jsx` 不使用单引号, 而使用双引号
|
jsxSingleQuote: false,
|
||||||
trailingComma: 'all', // 尾随逗号
|
trailingComma: 'all',
|
||||||
bracketSpacing: true, // 大括号内的首尾需要空格
|
bracketSpacing: true,
|
||||||
arrowParens: 'always', // 箭头函数, 只有一个参数的时候, 也需要括号
|
arrowParens: 'always',
|
||||||
rangeStart: 0, // 每个文件格式化的范围是文件的全部内容
|
rangeStart: 0,
|
||||||
rangeEnd: Infinity,
|
rangeEnd: Infinity,
|
||||||
requirePragma: false, // 不需要写文件开头的 `@prettier`
|
requirePragma: false,
|
||||||
insertPragma: false, // 不需要自动在文件开头插入 `@prettier`
|
insertPragma: false,
|
||||||
proseWrap: 'preserve', // 使用默认的折行标准
|
proseWrap: 'preserve',
|
||||||
htmlWhitespaceSensitivity: 'css', // 根据显示样式决定 `html` 要不要折行
|
htmlWhitespaceSensitivity: 'css',
|
||||||
endOfLine: 'lf', // 换行符使用 `lf`,
|
endOfLine: 'lf',
|
||||||
singleAttributePerLine: false,
|
singleAttributePerLine: false,
|
||||||
|
bracketSameLine: false,
|
||||||
|
plugins: ['@ianvs/prettier-plugin-sort-imports'],
|
||||||
}
|
}
|
||||||
|
|||||||
2
.vscode/extensions.json
vendored
2
.vscode/extensions.json
vendored
@ -1,3 +1,3 @@
|
|||||||
{
|
{
|
||||||
"recommendations": ["vue.volar", "lokalise.i18n-ally"]
|
"recommendations": ["lokalise.i18n-ally"]
|
||||||
}
|
}
|
||||||
|
|||||||
50
.vscode/settings.json
vendored
50
.vscode/settings.json
vendored
@ -1,4 +1,47 @@
|
|||||||
{
|
{
|
||||||
|
"editor.formatOnSave": true,
|
||||||
|
"editor.defaultFormatter": "esbenp.prettier-vscode",
|
||||||
|
"eslint.enable": true,
|
||||||
|
"eslint.validate": [
|
||||||
|
"javascript",
|
||||||
|
"javascriptreact",
|
||||||
|
"typescript",
|
||||||
|
"typescriptreact",
|
||||||
|
"vue"
|
||||||
|
],
|
||||||
|
"editor.codeActionsOnSave": {
|
||||||
|
"source.fixAll.eslint": "explicit"
|
||||||
|
},
|
||||||
|
"[typescript]": {
|
||||||
|
"editor.defaultFormatter": "esbenp.prettier-vscode"
|
||||||
|
},
|
||||||
|
"[typescriptreact]": {
|
||||||
|
"editor.defaultFormatter": "esbenp.prettier-vscode"
|
||||||
|
},
|
||||||
|
"[javascript]": {
|
||||||
|
"editor.defaultFormatter": "esbenp.prettier-vscode"
|
||||||
|
},
|
||||||
|
"[javascriptreact]": {
|
||||||
|
"editor.defaultFormatter": "esbenp.prettier-vscode"
|
||||||
|
},
|
||||||
|
"[json]": {
|
||||||
|
"editor.defaultFormatter": "esbenp.prettier-vscode"
|
||||||
|
},
|
||||||
|
"[vue]": {
|
||||||
|
"editor.defaultFormatter": "esbenp.prettier-vscode"
|
||||||
|
},
|
||||||
|
"[scss]": {
|
||||||
|
"editor.defaultFormatter": "esbenp.prettier-vscode"
|
||||||
|
},
|
||||||
|
"[css]": {
|
||||||
|
"editor.defaultFormatter": "esbenp.prettier-vscode"
|
||||||
|
},
|
||||||
|
"[html]": {
|
||||||
|
"editor.defaultFormatter": "esbenp.prettier-vscode"
|
||||||
|
},
|
||||||
|
"[markdown]": {
|
||||||
|
"editor.defaultFormatter": "esbenp.prettier-vscode"
|
||||||
|
},
|
||||||
"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,
|
||||||
@ -21,13 +64,16 @@
|
|||||||
"cSpell.words": [
|
"cSpell.words": [
|
||||||
"baomitu",
|
"baomitu",
|
||||||
"bezier",
|
"bezier",
|
||||||
|
"Cascader",
|
||||||
"Clickoutside",
|
"Clickoutside",
|
||||||
"codabar",
|
"codabar",
|
||||||
"commitmsg",
|
"commitmsg",
|
||||||
"crossorigin",
|
"crossorigin",
|
||||||
"datetimerange",
|
"datetimerange",
|
||||||
|
"depcheckrc",
|
||||||
"domtoimage",
|
"domtoimage",
|
||||||
"EDITMSG",
|
"EDITMSG",
|
||||||
|
"ianvs",
|
||||||
"iife",
|
"iife",
|
||||||
"internalkey",
|
"internalkey",
|
||||||
"jsbarcode",
|
"jsbarcode",
|
||||||
@ -43,8 +89,8 @@
|
|||||||
"siderbar",
|
"siderbar",
|
||||||
"snapline",
|
"snapline",
|
||||||
"stylelint",
|
"stylelint",
|
||||||
|
"unocss",
|
||||||
"WUJIE",
|
"WUJIE",
|
||||||
"zlevel"
|
"zlevel"
|
||||||
],
|
]
|
||||||
"peacock.color": "#007fff"
|
|
||||||
}
|
}
|
||||||
|
|||||||
287
CHANGELOG.md
287
CHANGELOG.md
@ -1,4 +1,276 @@
|
|||||||
# CHANGE LOG
|
## 5.2.5
|
||||||
|
|
||||||
|
## Feats
|
||||||
|
|
||||||
|
- 升级 `vite` 版本至 `7.x`,为了后面无缝衔接 `rolldown-vite` 做准备
|
||||||
|
- 升级所有主流依赖为 `7.x` 的配套
|
||||||
|
- 增强 `eslint`, `prettier` 规则,加强项目统一化的范式
|
||||||
|
- `typescript` 版本更新至 `5.9.3`
|
||||||
|
- 移除 `UnknownObjectKey` 类型,现在统一使用 `Recordable` 类型替代,或者全局的 `GlobalRecordable` 类型替代
|
||||||
|
|
||||||
|
## 5.2.4
|
||||||
|
|
||||||
|
## Feats
|
||||||
|
|
||||||
|
- 新增 `.cursorrules` 文件,用于配置 `cursor` 的规则
|
||||||
|
- `RChart` 组件相关
|
||||||
|
- 新增 `watchDeep` 配置项,允许配置是否深度监听 `options` 配置项,在某些场景下不希望监听 `options` 配置项的变动时,可以配置该项为 `false`
|
||||||
|
- 修改自定义样式名,由 `--r` 前缀改为 `--r` 前缀
|
||||||
|
- 优化 `RTable`, `RTablePro` 组件与相关 `hook`
|
||||||
|
- 移除未使用的导入(`DataTableInst`、`ExtractPublicPropTypes`、`emit`)
|
||||||
|
- 将 `contextMenuSelect` 中的状态更新提前
|
||||||
|
- 提取 `handleContextMenu` 函数,避免重复创建
|
||||||
|
- 优化 `combineRowProps` 的条件判断
|
||||||
|
- 提取 `renderDefaultToolOptions` 为独立函数
|
||||||
|
- 简化 `tool` 函数的条件判断
|
||||||
|
- 移除不必要的 `.bind(this)` 调用
|
||||||
|
- 如果未启用 `onUpdateColumns` 或 `onUpdate:columns` 事件(也就是双向绑定 `columns` 配置项),则认为不需要渲染 `C` 组件,因为有时候你可能希望 `columns` 配置项可能就是写死的,不需要动态修改
|
||||||
|
- 使用 `nextTick` 优化列配置更新(`C` 组件)
|
||||||
|
- 减少函数重复创建
|
||||||
|
- 优化条件判断逻辑
|
||||||
|
- `selectKeys(keys: RowKey[])` - 批量选中
|
||||||
|
- `toggleKey(key: RowKey)` - 切换选中状态
|
||||||
|
- `isKeySelected(key: RowKey)` - 检查是否选中
|
||||||
|
- 新增 `autoDeleteDuplicateKeys` 配置项,允许自定义是否移除重复请求 `key`
|
||||||
|
- `usePagination` 方法相关
|
||||||
|
- 修改 `getCallback` 方法返回值类型,现在会自动推导回调函数类型(仅在默认传递回调函数时有效)
|
||||||
|
- 修改 `getCallback` 方法使用方式,改为函数调用
|
||||||
|
- `RBarcode` 组件相关
|
||||||
|
- 新增 `responsive` 配置项,允许配置是否启用响应式尺寸,当容器大小变化时自动重新渲染条形码,但是该属性让 `width` 与 `height` 配置项失效
|
||||||
|
- 新增 `.vscode` 配置规则,默认强制使用 `prettier` 格式化代码,并且使用 `eslint` 检查代码规范
|
||||||
|
- 移除所有 `--ray` 的前缀为 `-r`
|
||||||
|
- 统一自定义组件的文件分包格式
|
||||||
|
- 标记自定义 `useModal` 方法为遗弃方法
|
||||||
|
- `useAxiosInterceptor` 更名为 `axiosInterceptor` 方法,旧方法名不符合语义化,现在更加语义化
|
||||||
|
- 优化 `useElementFullscreen` 方法
|
||||||
|
- 调整 `MenuTag` 组件样式,现在会根据主题色自动适配关闭按钮颜色
|
||||||
|
|
||||||
|
## Fixes
|
||||||
|
|
||||||
|
- 修复 `useTablePro.print` 方法无效的问题
|
||||||
|
- 修复 `vitest` 插件启动会提示失败的问题
|
||||||
|
|
||||||
|
## 5.2.3
|
||||||
|
|
||||||
|
## Feats
|
||||||
|
|
||||||
|
- 更新依赖为主流版本
|
||||||
|
- `RTablePro` 组件相关
|
||||||
|
- 新增 `takeoverAutoHeight` 配置项,允许接管表格的流体高度渲染,一旦启用该属性,`flexAutoHeight` 属性将强制启用
|
||||||
|
- 新增 `collapse` 插槽,配合 `takeoverAutoHeight` 配置项使用,允许自定义表格常见的顶部操作区域,当然也可以做点其他的,但是该插槽仅在启用 `takeoverAutoHeight` 配置项时生效
|
||||||
|
> 该属性可以让流体高度功能使用更加优雅,有点用处。
|
||||||
|
- 新增暴露 `setPage`, `setPageSize`, `getPage`, `getPageSize` 方法
|
||||||
|
- `naive-ui` 最新版本有依赖问题,暂时回退升级
|
||||||
|
- `echarts` 更新至 `6.0.0` 版本,并且完成适配
|
||||||
|
|
||||||
|
## Fixes
|
||||||
|
|
||||||
|
- 修复 `usePagination.resetPagination` 方法在重置分页时,没有正确触发 `pageChange`, `pageSizeChange` 回调函数的问题
|
||||||
|
- 修复 `resetTablePagination` 方法在重置分页时,没有正确触发 `onTablePaginationUpdate` 回调函数的问题
|
||||||
|
|
||||||
|
## 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
|
## 5.0.4
|
||||||
|
|
||||||
@ -103,7 +375,7 @@
|
|||||||
- 新增 `clearSigningCallback` 方法
|
- 新增 `clearSigningCallback` 方法
|
||||||
- `vite.custom.config` 新增 `cdn` 配置项,是否启用 `cdn` 构建项目
|
- `vite.custom.config` 新增 `cdn` 配置项,是否启用 `cdn` 构建项目
|
||||||
- 配置 `cdn` 为 `false`,因为国内厂商更新资源速度有点慢,导致预览失败
|
- 配置 `cdn` 为 `false`,因为国内厂商更新资源速度有点慢,导致预览失败
|
||||||
- `Layout` 层注入 `--window-width`, `--window-height` `css var` 属性
|
- `Layout` 层注入 `--window-width`, `--window-height`, `css var` 属性
|
||||||
- 稳定 `Layout` 层的 `css var` 属性
|
- 稳定 `Layout` 层的 `css var` 属性
|
||||||
|
|
||||||
## Fixes
|
## Fixes
|
||||||
@ -517,7 +789,7 @@ const [
|
|||||||
补充拓展了 `useModal` 方法,支持 `dad`, `fullscreen` 等拓展配置项。
|
补充拓展了 `useModal` 方法,支持 `dad`, `fullscreen` 等拓展配置项。
|
||||||
|
|
||||||
```ts
|
```ts
|
||||||
import { useTable, useForm } from '@/components'
|
import { useForm, useTable } from '@/components'
|
||||||
|
|
||||||
const [registerTable, { getTableInstance }] = useTable()
|
const [registerTable, { getTableInstance }] = useTable()
|
||||||
const [registerForm, { getFormInstance }] = useForm()
|
const [registerForm, { getFormInstance }] = useForm()
|
||||||
@ -537,8 +809,7 @@ const [registerForm, { getFormInstance }] = useForm()
|
|||||||
> 该方法比起常见的 `ref` 注册,然后 `tableRef.value.xxx` 的方法获取表格方法更为简洁一点。但是也值得注意的是,需要手动调用一次 `register` 方法,否则会报错;还有值得注意的是,需要注意表格方法的调用时机,需要等待表格注册完成后才能正常调用。如果需要在 `Parent Create` 阶段调用,可以尝试 `nextTick` 包裹一层。
|
> 该方法比起常见的 `ref` 注册,然后 `tableRef.value.xxx` 的方法获取表格方法更为简洁一点。但是也值得注意的是,需要手动调用一次 `register` 方法,否则会报错;还有值得注意的是,需要注意表格方法的调用时机,需要等待表格注册完成后才能正常调用。如果需要在 `Parent Create` 阶段调用,可以尝试 `nextTick` 包裹一层。
|
||||||
|
|
||||||
```tsx
|
```tsx
|
||||||
import { RTable } from '@/components'
|
import { RTable, useTable } from '@/components'
|
||||||
import { useTable } from '@/components'
|
|
||||||
|
|
||||||
defineComponent({
|
defineComponent({
|
||||||
setup() {
|
setup() {
|
||||||
@ -1710,7 +1981,7 @@ const demo2 = null
|
|||||||
- 新增切换路由自动取消上一路由所有请求。但是可以通过配置 `useRequest` 与 `request` 方法的 `cancelConfig.cancel` 属性控制是否需要自动取消该请求。该配置默认为 `true`,当配置为 `false` 时,则不会被取消器取消
|
- 新增切换路由自动取消上一路由所有请求。但是可以通过配置 `useRequest` 与 `request` 方法的 `cancelConfig.cancel` 属性控制是否需要自动取消该请求。该配置默认为 `true`,当配置为 `false` 时,则不会被取消器取消
|
||||||
|
|
||||||
```ts
|
```ts
|
||||||
import { useRequest, useHookPlusRequest } from '@/axios/index'
|
import { useHookPlusRequest, useRequest } from '@/axios/index'
|
||||||
|
|
||||||
// useRequest
|
// useRequest
|
||||||
const { data, loading, run } = useRequest<{
|
const { data, loading, run } = useRequest<{
|
||||||
@ -1776,8 +2047,8 @@ request({
|
|||||||
- useHookPlusRequest 支持接收一个 Promise 返回值的方法,可以用来包裹 axios 方法然后进行请求配置
|
- useHookPlusRequest 支持接收一个 Promise 返回值的方法,可以用来包裹 axios 方法然后进行请求配置
|
||||||
|
|
||||||
```ts
|
```ts
|
||||||
|
import { useHookPlusRequest, useRequest } from '@/axios/index'
|
||||||
import axiosInstance from '@/axios/instance'
|
import axiosInstance from '@/axios/instance'
|
||||||
import { useRequest, useHookPlusRequest } from '@/axios/index'
|
|
||||||
|
|
||||||
// 使用 useRequest
|
// 使用 useRequest
|
||||||
const { data, loading, run } = useRequest<{
|
const { data, loading, run } = useRequest<{
|
||||||
@ -2038,7 +2309,7 @@ useAppTheme key 类型: 'dark' | 'light'
|
|||||||
### Feats
|
### Feats
|
||||||
|
|
||||||
- 修改 Menu 菜单过滤逻辑,现在如果权限不匹配或者设置了 hidden 属性,则会被过滤掉
|
- 修改 Menu 菜单过滤逻辑,现在如果权限不匹配或者设置了 hidden 属性,则会被过滤掉
|
||||||
- 移除 $activedColor 全局 sass 变量,使用 --ray-theme-primary-color 替代
|
- 移除 $activedColor 全局 sass 变量,使用 --r-theme-primary-color 替代
|
||||||
- 新增路由菜单检索功能
|
- 新增路由菜单检索功能
|
||||||
- 移除 App.tsx 中同步主题方法,改为使用 cfg 配置并且使用 ejs 注入
|
- 移除 App.tsx 中同步主题方法,改为使用 cfg 配置并且使用 ejs 注入
|
||||||
- 移除 MenuTag 默认主题色,现在会以当前主题色为主色
|
- 移除 MenuTag 默认主题色,现在会以当前主题色为主色
|
||||||
|
|||||||
@ -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
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|||||||
@ -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)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|||||||
4
__test__/cache/index.spec.ts
vendored
4
__test__/cache/index.spec.ts
vendored
@ -1,8 +1,8 @@
|
|||||||
import {
|
import {
|
||||||
hasStorage,
|
|
||||||
setStorage,
|
|
||||||
getStorage,
|
getStorage,
|
||||||
|
hasStorage,
|
||||||
removeStorage,
|
removeStorage,
|
||||||
|
setStorage,
|
||||||
} from '../../src/utils/cache'
|
} from '../../src/utils/cache'
|
||||||
|
|
||||||
describe('cache utils', () => {
|
describe('cache utils', () => {
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
import { RModal } from '../../src/components/base/RModal/index'
|
|
||||||
import { mount } from '@vue/test-utils'
|
import { mount } from '@vue/test-utils'
|
||||||
|
import { RModal } from '../../src/components/base/RModal/index'
|
||||||
|
|
||||||
describe('RModal', () => {
|
describe('RModal', () => {
|
||||||
it('should execute the onAfterEnter callback', () => {
|
it('should execute the onAfterEnter callback', () => {
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
import { printDom } from '../../src/utils/dom'
|
|
||||||
import { mount } from '@vue/test-utils'
|
import { mount } from '@vue/test-utils'
|
||||||
|
import { printDom } from '../../src/utils/dom'
|
||||||
import renderHook from '../utils/renderHook'
|
import renderHook from '../utils/renderHook'
|
||||||
|
|
||||||
// happy-dom 官方有一个 bug,无法使用 canvas.toDataURL 方法。所以该模块单测暂时无法通过
|
// happy-dom 官方有一个 bug,无法使用 canvas.toDataURL 方法。所以该模块单测暂时无法通过
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
import { setClass, hasClass, removeClass } from '../../src/utils/element'
|
import { hasClass, removeClass, setClass } from '../../src/utils/element'
|
||||||
import createRefElement from '../utils/createRefElement'
|
import createRefElement from '../utils/createRefElement'
|
||||||
|
|
||||||
describe('setClass', () => {
|
describe('setClass', () => {
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
import { setStyle, removeStyle } from '../../src/utils/element'
|
import { removeStyle, setStyle } from '../../src/utils/element'
|
||||||
import createRefElement from '../utils/createRefElement'
|
import createRefElement from '../utils/createRefElement'
|
||||||
|
|
||||||
describe('setStyle', () => {
|
describe('setStyle', () => {
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
import setupMiniApp from '../utils/setupMiniApp'
|
|
||||||
import { useAppRoot } from '../../src/hooks/template/useAppRoot'
|
import { useAppRoot } from '../../src/hooks/template/useAppRoot'
|
||||||
|
import setupMiniApp from '../utils/setupMiniApp'
|
||||||
|
|
||||||
describe('useAppRoot', async () => {
|
describe('useAppRoot', async () => {
|
||||||
await setupMiniApp()
|
await setupMiniApp()
|
||||||
|
|||||||
@ -1,8 +1,7 @@
|
|||||||
import setupMiniApp from '../utils/setupMiniApp'
|
|
||||||
import { useBadge } from '../../src/hooks/template/useBadge'
|
import { useBadge } from '../../src/hooks/template/useBadge'
|
||||||
import { useMenuGetters } from '../../src/store'
|
|
||||||
|
|
||||||
import type { AppMenuExtraOptions } from '../../src/router/types'
|
import type { AppMenuExtraOptions } from '../../src/router/types'
|
||||||
|
import { useMenuGetters } from '../../src/store'
|
||||||
|
import setupMiniApp from '../utils/setupMiniApp'
|
||||||
|
|
||||||
describe('useBadge', async () => {
|
describe('useBadge', async () => {
|
||||||
await setupMiniApp()
|
await setupMiniApp()
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
import { useContextmenuCoordinate } from '../../src/hooks/components/useContextmenuCoordinate'
|
import { useContextmenuCoordinate } from '../../src/hooks/components/useContextmenuCoordinate'
|
||||||
import renderHook from '../utils/renderHook'
|
|
||||||
import createRefElement from '../utils/createRefElement'
|
import createRefElement from '../utils/createRefElement'
|
||||||
|
import renderHook from '../utils/renderHook'
|
||||||
|
|
||||||
describe('useContextmenuCoordinate', () => {
|
describe('useContextmenuCoordinate', () => {
|
||||||
const wrapperRef = createRefElement()
|
const wrapperRef = createRefElement()
|
||||||
@ -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()
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
import { useDayjs } from '../../src/hooks/web/useDayjs'
|
|
||||||
import dayjs from 'dayjs'
|
import dayjs from 'dayjs'
|
||||||
|
import { useDayjs } from '../../src/hooks/web/useDayjs'
|
||||||
|
|
||||||
describe('useDayjs', () => {
|
describe('useDayjs', () => {
|
||||||
const {
|
const {
|
||||||
|
|||||||
@ -1,9 +1,8 @@
|
|||||||
import setupMiniApp from '../utils/setupMiniApp'
|
|
||||||
import { useSiderBar } from '../../src/hooks/template/useSiderBar'
|
import { useSiderBar } from '../../src/hooks/template/useSiderBar'
|
||||||
import { useMenuGetters, useMenuActions } from '../../src/store'
|
|
||||||
import { useVueRouter } from '../../src/hooks/web/useVueRouter'
|
import { useVueRouter } from '../../src/hooks/web/useVueRouter'
|
||||||
|
import { useMenuActions, useMenuGetters } from '../../src/store'
|
||||||
import type { AppMenuOption, MenuTagOptions } from '../../src/types/modules/app'
|
import type { AppMenuOption, MenuTagOptions } from '../../src/types/modules/app'
|
||||||
|
import setupMiniApp from '../utils/setupMiniApp'
|
||||||
|
|
||||||
describe('useSiderBar', async () => {
|
describe('useSiderBar', async () => {
|
||||||
await setupMiniApp()
|
await setupMiniApp()
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
import setupMiniApp from '../utils/setupMiniApp'
|
import { getVariableToRefs, setVariable } from '../../src/global-variable'
|
||||||
import { useSpinning } from '../../src/hooks/template/useSpinning'
|
import { useSpinning } from '../../src/hooks/template/useSpinning'
|
||||||
import { setVariable, getVariableToRefs } from '../../src/global-variable'
|
import setupMiniApp from '../utils/setupMiniApp'
|
||||||
|
|
||||||
describe('useSpinning', async () => {
|
describe('useSpinning', async () => {
|
||||||
await setupMiniApp()
|
await setupMiniApp()
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
import setupMiniApp from '../utils/setupMiniApp'
|
|
||||||
import { useTheme } from '../../src/hooks/template/useTheme'
|
import { useTheme } from '../../src/hooks/template/useTheme'
|
||||||
|
import setupMiniApp from '../utils/setupMiniApp'
|
||||||
|
|
||||||
describe('useTheme', async () => {
|
describe('useTheme', async () => {
|
||||||
await setupMiniApp()
|
await setupMiniApp()
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
import setupMiniApp from '../utils/setupMiniApp'
|
|
||||||
import { useVueRouter } from '../../src/hooks/web/useVueRouter'
|
import { useVueRouter } from '../../src/hooks/web/useVueRouter'
|
||||||
|
import setupMiniApp from '../utils/setupMiniApp'
|
||||||
|
|
||||||
describe('useVueRouter', async () => {
|
describe('useVueRouter', async () => {
|
||||||
await setupMiniApp()
|
await setupMiniApp()
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
import setupMiniApp from '../utils/setupMiniApp'
|
|
||||||
import { useWatermark } from '../../src/hooks/template/useWatermark'
|
import { useWatermark } from '../../src/hooks/template/useWatermark'
|
||||||
import { useSettingGetters } from '../../src/store'
|
import { useSettingGetters } from '../../src/store'
|
||||||
|
import setupMiniApp from '../utils/setupMiniApp'
|
||||||
|
|
||||||
describe('useWatermark', async () => {
|
describe('useWatermark', async () => {
|
||||||
await setupMiniApp()
|
await setupMiniApp()
|
||||||
|
|||||||
@ -1,11 +1,11 @@
|
|||||||
import {
|
import {
|
||||||
isCurrency,
|
|
||||||
format,
|
|
||||||
add,
|
add,
|
||||||
subtract,
|
|
||||||
multiply,
|
|
||||||
divide,
|
|
||||||
distribute,
|
distribute,
|
||||||
|
divide,
|
||||||
|
format,
|
||||||
|
isCurrency,
|
||||||
|
multiply,
|
||||||
|
subtract,
|
||||||
} from '../../src/utils/precision'
|
} from '../../src/utils/precision'
|
||||||
|
|
||||||
describe('precision', () => {
|
describe('precision', () => {
|
||||||
|
|||||||
@ -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({
|
||||||
|
|||||||
@ -1,6 +1,5 @@
|
|||||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||||
import { createApp, defineComponent } from 'vue'
|
import { createApp, defineComponent } from 'vue'
|
||||||
|
|
||||||
import type { App } from 'vue'
|
import type { App } from 'vue'
|
||||||
|
|
||||||
export default function renderHook<R = any>(
|
export default function renderHook<R = any>(
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
import { setupStore } from '../../src/store'
|
|
||||||
import { setupRouter } from '../../src/router'
|
|
||||||
import { setupI18n } from '../../src/locales'
|
import { setupI18n } from '../../src/locales'
|
||||||
|
import { setupRouter } from '../../src/router'
|
||||||
|
import { setupStore } from '../../src/store'
|
||||||
import renderHook from '../utils/renderHook'
|
import renderHook from '../utils/renderHook'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@ -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)
|
||||||
|
|||||||
@ -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()
|
||||||
|
|||||||
300
eslint.config.mjs
Normal file
300
eslint.config.mjs
Normal file
@ -0,0 +1,300 @@
|
|||||||
|
import path from 'node:path'
|
||||||
|
import { fileURLToPath } from 'node:url'
|
||||||
|
import { FlatCompat } from '@eslint/eslintrc'
|
||||||
|
import js from '@eslint/js'
|
||||||
|
import typescriptEslint from '@typescript-eslint/eslint-plugin'
|
||||||
|
import prettier from 'eslint-plugin-prettier'
|
||||||
|
import vue from 'eslint-plugin-vue'
|
||||||
|
import globals from 'globals'
|
||||||
|
import parser from 'vue-eslint-parser'
|
||||||
|
|
||||||
|
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'],
|
||||||
|
},
|
||||||
|
js.configs.recommended,
|
||||||
|
...vue.configs['flat/recommended'],
|
||||||
|
...compat.extends(
|
||||||
|
'plugin:@typescript-eslint/recommended',
|
||||||
|
'plugin:prettier/recommended',
|
||||||
|
'./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/block-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: 'always', prev: 'export', next: '*' },
|
||||||
|
{ 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: '*', next: 'function' },
|
||||||
|
{ blankLine: 'always', prev: 'function', next: '*' },
|
||||||
|
{ blankLine: 'always', prev: '*', next: 'return' },
|
||||||
|
{ blankLine: 'always', prev: '*', next: 'if' },
|
||||||
|
{ blankLine: 'always', prev: 'if', next: '*' },
|
||||||
|
{ blankLine: 'always', prev: '*', next: 'for' },
|
||||||
|
{ blankLine: 'always', prev: 'for', next: '*' },
|
||||||
|
{ blankLine: 'always', prev: '*', next: 'while' },
|
||||||
|
{ blankLine: 'always', prev: 'while', next: '*' },
|
||||||
|
{ blankLine: 'always', prev: '*', next: 'class' },
|
||||||
|
{ blankLine: 'always', prev: 'class', next: '*' },
|
||||||
|
{ blankLine: 'always', prev: '*', next: 'try' },
|
||||||
|
{ blankLine: 'always', prev: 'try', 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',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
]
|
||||||
43
index.html
43
index.html
@ -13,8 +13,29 @@
|
|||||||
:root {
|
:root {
|
||||||
--preloading-tag-color: <%= preloadingConfig.tagColor %>;
|
--preloading-tag-color: <%= preloadingConfig.tagColor %>;
|
||||||
--preloading-title-color: <%= preloadingConfig.titleColor %>;
|
--preloading-title-color: <%= preloadingConfig.titleColor %>;
|
||||||
--ray-theme-primary-fade-color: <%= appPrimaryColor.primaryFadeColor %>;
|
--r-theme-primary-fade-color: <%= appPrimaryColor.primaryFadeColor %>;
|
||||||
--ray-theme-primary-color: <%= appPrimaryColor.primaryColor %>;
|
--r-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">
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
import { defineMock } from 'vite-plugin-mock-dev-server'
|
|
||||||
import { pagination, stringify, response, array } from '@mock/shared/utils'
|
|
||||||
import { tableMock } from '@mock/shared/database'
|
import { tableMock } from '@mock/shared/database'
|
||||||
|
import { array, pagination, response, stringify } from '@mock/shared/utils'
|
||||||
import Mock from 'mockjs'
|
import Mock from 'mockjs'
|
||||||
|
import { defineMock } from 'vite-plugin-mock-dev-server'
|
||||||
|
|
||||||
export const getPersonList = defineMock({
|
export const getPersonList = defineMock({
|
||||||
url: '/api/list',
|
url: '/api/list',
|
||||||
|
|||||||
130
package.json
Executable file → Normal file
130
package.json
Executable file → Normal file
@ -1,10 +1,10 @@
|
|||||||
{
|
{
|
||||||
"name": "ray-template",
|
"name": "ray-template",
|
||||||
"private": false,
|
"private": false,
|
||||||
"version": "5.0.4",
|
"version": "5.2.5",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": "^18.0.0 || >=20.0.0",
|
"node": "^20.0.0 || >=22.0.0",
|
||||||
"pnpm": ">=9.0.0"
|
"pnpm": ">=9.0.0"
|
||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
@ -16,7 +16,7 @@
|
|||||||
"prepare": "husky install",
|
"prepare": "husky install",
|
||||||
"test": "vitest",
|
"test": "vitest",
|
||||||
"test:ui": "vitest --ui",
|
"test:ui": "vitest --ui",
|
||||||
"lint": "vue-tsc --noEmit && eslint src --ext .js,.jsx,.vue && prettier --write \"src/**/*.{ts,tsx,json,.vue}\""
|
"lint": "vue-tsc --noEmit && eslint --fix && prettier --write \"**/*.{ts,tsx,json,.vue}\""
|
||||||
},
|
},
|
||||||
"husky": {
|
"husky": {
|
||||||
"hooks": {
|
"hooks": {
|
||||||
@ -29,83 +29,87 @@
|
|||||||
"prettier --write"
|
"prettier --write"
|
||||||
],
|
],
|
||||||
"*.{ts,tsx,vue}": [
|
"*.{ts,tsx,vue}": [
|
||||||
"eslint src"
|
"eslint --fix"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@logicflow/core": "2.0.6",
|
"@logicflow/core": "2.0.10",
|
||||||
"@logicflow/extension": "2.0.10",
|
"@logicflow/extension": "2.0.14",
|
||||||
"@vueuse/core": "^11.1.0",
|
"@vueuse/core": "^13.1.0",
|
||||||
"axios": "^1.7.5",
|
"axios": "^1.10.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",
|
||||||
"echarts": "^5.5.0",
|
"echarts": "^6.0.0",
|
||||||
"html-to-image": "1.11.11",
|
"html-to-image": "1.11.13",
|
||||||
"interactjs": "1.10.26",
|
"interactjs": "1.10.27",
|
||||||
"jsbarcode": "3.11.6",
|
"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.40.1",
|
"naive-ui": "^2.43.2",
|
||||||
"pinia": "^2.2.4",
|
"pinia": "^3.0.3",
|
||||||
"pinia-plugin-persistedstate": "^4.1.1",
|
"pinia-plugin-persistedstate": "^4.4.1",
|
||||||
"print-js": "^1.6.0",
|
"print-js": "^1.6.0",
|
||||||
"vue": "^3.5.12",
|
"vue": "^3.5.25",
|
||||||
"vue-demi": "0.14.6",
|
"vue-demi": "0.14.10",
|
||||||
"vue-hooks-plus": "2.2.1",
|
"vue-hooks-plus": "2.4.1",
|
||||||
"vue-i18n": "^9.13.1",
|
"vue-i18n": "^11.1.3",
|
||||||
"vue-router": "^4.3.2",
|
"vue-router": "^4.6.3",
|
||||||
"vue3-next-qrcode": "2.0.10"
|
"vue3-next-qrcode": "3.0.2"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@commitlint/cli": "^17.8.1",
|
"@commitlint/cli": "19.7.1",
|
||||||
"@commitlint/config-conventional": "^17.8.1",
|
"@commitlint/config-conventional": "19.7.1",
|
||||||
"@interactjs/types": "1.10.21",
|
"@eslint/eslintrc": "3.3.1",
|
||||||
"@intlify/unplugin-vue-i18n": "^4.0.0",
|
"@eslint/js": "9.39.1",
|
||||||
"@types/crypto-js": "^4.2.2",
|
"@ianvs/prettier-plugin-sort-imports": "4.7.0",
|
||||||
|
"@interactjs/types": "1.10.27",
|
||||||
|
"@intlify/unplugin-vue-i18n": "11.0.1",
|
||||||
|
"@types/crypto-js": "4.2.2",
|
||||||
"@types/jsbarcode": "3.11.4",
|
"@types/jsbarcode": "3.11.4",
|
||||||
"@types/lodash-es": "^4.17.12",
|
"@types/lodash-es": "4.17.12",
|
||||||
"@types/mockjs": "1.0.7",
|
"@types/mockjs": "1.0.10",
|
||||||
"@typescript-eslint/eslint-plugin": "^8.13.0",
|
"@typescript-eslint/eslint-plugin": "8.47.0",
|
||||||
"@typescript-eslint/parser": "^8.13.0",
|
"@typescript-eslint/parser": "8.47.0",
|
||||||
"@vitejs/plugin-vue": "^5.1.0",
|
"@vitejs/plugin-vue": "6.0.2",
|
||||||
"@vitejs/plugin-vue-jsx": "^4.0.1",
|
"@vitejs/plugin-vue-jsx": "5.1.2",
|
||||||
"@vitest/ui": "1.4.0",
|
"@vitest/ui": "4.0.12",
|
||||||
"@vue/eslint-config-prettier": "^9.0.0",
|
"@vue/eslint-config-prettier": "10.1.0",
|
||||||
"@vue/eslint-config-typescript": "^12.0.0",
|
"@vue/eslint-config-typescript": "14.2.0",
|
||||||
"@vue/test-utils": "2.4.3",
|
"@vue/test-utils": "2.4.6",
|
||||||
"autoprefixer": "^10.4.16",
|
"autoprefixer": "10.4.21",
|
||||||
"depcheck": "^1.4.7",
|
"depcheck": "1.4.7",
|
||||||
"eslint": "^8.57.0",
|
"eslint": "9.39.1",
|
||||||
"eslint-config-prettier": "^9.1.0",
|
"eslint-config-prettier": "10.1.8",
|
||||||
"eslint-config-standard-with-typescript": "^43.0.0",
|
"eslint-plugin-prettier": "5.5.4",
|
||||||
"eslint-plugin-prettier": "^5.1.3",
|
"eslint-plugin-vue": "10.6.0",
|
||||||
"eslint-plugin-promise": "^6.1.1",
|
"globals": "16.5.0",
|
||||||
"eslint-plugin-vue": "^9.25.0",
|
"happy-dom": "17.1.0",
|
||||||
"happy-dom": "14.3.1",
|
|
||||||
"husky": "8.0.3",
|
"husky": "8.0.3",
|
||||||
"lint-staged": "^15.2.0",
|
"lint-staged": "15.4.3",
|
||||||
"postcss": "^8.4.38",
|
"postcss": "8.5.6",
|
||||||
"postcss-px-to-viewport-8-with-include": "1.2.2",
|
"postcss-px-to-viewport-8-with-include": "1.2.2",
|
||||||
"prettier": "^3.2.5",
|
"prettier": "3.6.2",
|
||||||
"rollup-plugin-gzip": "4.0.1",
|
"rollup-plugin-gzip": "4.0.1",
|
||||||
"sass": "1.71.1",
|
"sass": "1.94.2",
|
||||||
"svg-sprite-loader": "^6.0.11",
|
"svg-sprite-loader": "6.0.11",
|
||||||
"typescript": "^5.6.3",
|
"typescript": "5.9.3",
|
||||||
"unplugin-auto-import": "^0.18.2",
|
"unocss": "66.5.9",
|
||||||
"unplugin-vue-components": "^0.27.4",
|
"unplugin-auto-import": "20.2.0",
|
||||||
"vite": "^5.4.3",
|
"unplugin-vue-components": "0.28.0",
|
||||||
"vite-bundle-analyzer": "0.9.4",
|
"vite": "7.2.6",
|
||||||
|
"vite-bundle-analyzer": "1.2.3",
|
||||||
"vite-plugin-cdn2": "1.1.0",
|
"vite-plugin-cdn2": "1.1.0",
|
||||||
"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-inspect": "^0.8.3",
|
"vite-plugin-inspect": "11.3.3",
|
||||||
"vite-plugin-mock-dev-server": "1.4.7",
|
"vite-plugin-mock-dev-server": "2.0.4",
|
||||||
"vite-plugin-svg-icons": "^2.0.1",
|
"vite-plugin-svg-icons": "2.0.1",
|
||||||
"vite-svg-loader": "^4.0.0",
|
"vite-svg-loader": "5.1.0",
|
||||||
"vite-tsconfig-paths": "4.3.2",
|
"vitest": "4.0.12",
|
||||||
"vitest": "1.5.2",
|
"vue-eslint-parser": "10.2.0",
|
||||||
"vue-tsc": "^2.1.10"
|
"vue-tsc": "2.2.8"
|
||||||
},
|
},
|
||||||
"description": "<!-- ALL-CONTRIBUTORS-BADGE:START - Do not remove or modify this section -->",
|
"description": "<!-- ALL-CONTRIBUTORS-BADGE:START - Do not remove or modify this section -->",
|
||||||
"main": "index.ts",
|
"main": "index.ts",
|
||||||
|
|||||||
8322
pnpm-lock.yaml
generated
8322
pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
@ -1,12 +1,11 @@
|
|||||||
import { RouterView } from 'vue-router'
|
import AppGlobalSpin from '@/app-components/app/AppGlobalSpin'
|
||||||
|
import AppLockScreen from '@/app-components/app/AppLockScreen'
|
||||||
import AppNaiveGlobalProvider from '@/app-components/provider/AppNaiveGlobalProvider'
|
import AppNaiveGlobalProvider from '@/app-components/provider/AppNaiveGlobalProvider'
|
||||||
import AppStyleProvider from '@/app-components/provider/AppStyleProvider'
|
import AppStyleProvider from '@/app-components/provider/AppStyleProvider'
|
||||||
import AppLockScreen from '@/app-components/app/AppLockScreen'
|
|
||||||
import AppWatermarkProvider from '@/app-components/provider/AppWatermarkProvider'
|
|
||||||
import AppGlobalSpin from '@/app-components/app/AppGlobalSpin'
|
|
||||||
import AppVersionProvider from '@/app-components/provider/AppVersionProvider'
|
import AppVersionProvider from '@/app-components/provider/AppVersionProvider'
|
||||||
|
import AppWatermarkProvider from '@/app-components/provider/AppWatermarkProvider'
|
||||||
import { APP_GLOBAL_LOADING } from '@/app-config'
|
import { APP_GLOBAL_LOADING } from '@/app-config'
|
||||||
|
import { RouterView } from 'vue-router'
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
name: 'App',
|
name: 'App',
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
import { useSettingGetters } from '@/store'
|
|
||||||
import { useVueRouter } from '@/hooks'
|
import { useVueRouter } from '@/hooks'
|
||||||
|
import { useSettingGetters } from '@/store'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
|
|||||||
@ -1,9 +1,9 @@
|
|||||||
import {
|
import {
|
||||||
|
DAYJS_LOCAL_MAP,
|
||||||
|
DEFAULT_DAYJS_LOCAL,
|
||||||
LOCAL_OPTIONS,
|
LOCAL_OPTIONS,
|
||||||
SYSTEM_DEFAULT_LOCAL,
|
SYSTEM_DEFAULT_LOCAL,
|
||||||
SYSTEM_FALLBACK_LOCALE,
|
SYSTEM_FALLBACK_LOCALE,
|
||||||
DAYJS_LOCAL_MAP,
|
|
||||||
DEFAULT_DAYJS_LOCAL,
|
|
||||||
} from '@/app-config'
|
} from '@/app-config'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@ -1,5 +1,4 @@
|
|||||||
import { request } from '@/axios'
|
import { request } from '@/axios'
|
||||||
|
|
||||||
import type { PaginationResponse } from '@/types'
|
import type { PaginationResponse } from '@/types'
|
||||||
|
|
||||||
export interface MockListParams {
|
export interface MockListParams {
|
||||||
|
|||||||
@ -9,11 +9,10 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import { request } from '@/axios'
|
import { request } from '@/axios'
|
||||||
|
|
||||||
import type { BasicResponse } from '@/types'
|
import type { BasicResponse } from '@/types'
|
||||||
|
|
||||||
interface AxiosTestResponse extends UnknownObjectKey {
|
interface AxiosTestResponse extends GlobalRecordable {
|
||||||
data: UnknownObjectKey[]
|
data: GlobalRecordable[]
|
||||||
city?: string
|
city?: string
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -28,7 +27,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>({
|
||||||
|
|||||||
@ -6,13 +6,10 @@
|
|||||||
* 默认读取本地 session catch 缓存
|
* 默认读取本地 session catch 缓存
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { NAvatar, NButton, NFlex } from 'naive-ui'
|
|
||||||
|
|
||||||
import { avatarProps } from 'naive-ui'
|
|
||||||
import { useSigningGetters } from '@/store'
|
import { useSigningGetters } from '@/store'
|
||||||
|
import { avatarProps, NAvatar, NButton, NFlex } from 'naive-ui'
|
||||||
import type { PropType } from 'vue'
|
|
||||||
import type { AvatarProps, FlexProps } from 'naive-ui'
|
import type { AvatarProps, FlexProps } from 'naive-ui'
|
||||||
|
import type { PropType } from 'vue'
|
||||||
|
|
||||||
const AppAvatar = defineComponent({
|
const AppAvatar = defineComponent({
|
||||||
name: 'AppAvatar',
|
name: 'AppAvatar',
|
||||||
@ -54,7 +51,14 @@ const AppAvatar = defineComponent({
|
|||||||
objectFit="cover"
|
objectFit="cover"
|
||||||
round
|
round
|
||||||
size={avatarSize}
|
size={avatarSize}
|
||||||
/>
|
>
|
||||||
|
{{
|
||||||
|
default: () =>
|
||||||
|
getSigningCallback.avatar
|
||||||
|
? null
|
||||||
|
: getSigningCallback?.name?.[0],
|
||||||
|
}}
|
||||||
|
</NAvatar>
|
||||||
{getSigningCallback?.name}
|
{getSigningCallback?.name}
|
||||||
</NFlex>
|
</NFlex>
|
||||||
</NButton>
|
</NButton>
|
||||||
|
|||||||
@ -15,11 +15,8 @@
|
|||||||
* 2. 如果需要使用该组件请注意控制取消时机
|
* 2. 如果需要使用该组件请注意控制取消时机
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { NSpin } from 'naive-ui'
|
|
||||||
|
|
||||||
import { spinProps } from 'naive-ui'
|
|
||||||
import { getVariableToRefs } from '@/global-variable'
|
import { getVariableToRefs } from '@/global-variable'
|
||||||
|
import { NSpin, spinProps } from 'naive-ui'
|
||||||
import type { SpinProps } from 'naive-ui'
|
import type { SpinProps } from 'naive-ui'
|
||||||
|
|
||||||
const GlobalSpin = defineComponent({
|
const GlobalSpin = defineComponent({
|
||||||
@ -44,6 +41,7 @@ const GlobalSpin = defineComponent({
|
|||||||
{...(this.$props as SpinProps)}
|
{...(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,
|
||||||
|
|||||||
@ -1,17 +1,10 @@
|
|||||||
/**
|
|
||||||
*
|
|
||||||
* 统一管理是否处于锁屏状态
|
|
||||||
*
|
|
||||||
* 可以根据后台接口进行替换该变量, 只要是一个响应式的变量值即可
|
|
||||||
*/
|
|
||||||
|
|
||||||
import { useStorage } from '@vueuse/core'
|
|
||||||
import { APP_CATCH_KEY } from '@/app-config'
|
import { APP_CATCH_KEY } from '@/app-config'
|
||||||
|
import { useStorage } from '@vueuse/core'
|
||||||
|
|
||||||
const appLockScreen = useStorage(
|
const appLockScreen = useStorage(
|
||||||
APP_CATCH_KEY.isAppLockScreen,
|
APP_CATCH_KEY.isAppLockScreen,
|
||||||
false,
|
false,
|
||||||
sessionStorage,
|
window.localStorage,
|
||||||
{
|
{
|
||||||
mergeDefaults: true,
|
mergeDefaults: true,
|
||||||
},
|
},
|
||||||
|
|||||||
@ -1,16 +1,19 @@
|
|||||||
import { NInput, NForm, NFormItem, NButton } from 'naive-ui'
|
import AppAvatar from '@/app-components/app/AppAvatar'
|
||||||
|
|
||||||
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 { APP_CATCH_KEY } from '@/app-config'
|
||||||
|
import { RForm, useForm } from '@/components'
|
||||||
import type { FormInst, InputInst } from 'naive-ui'
|
import { useSettingActions } from '@/store'
|
||||||
|
import { encrypt, setStorage } from '@/utils'
|
||||||
|
import { NButton, NFormItem, NInput } from 'naive-ui'
|
||||||
|
import type { InputInst } from 'naive-ui'
|
||||||
|
import { useTemplateRef } from 'vue'
|
||||||
|
|
||||||
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()
|
||||||
@ -19,15 +22,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()
|
||||||
}
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -40,18 +45,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">
|
||||||
<NForm
|
<AppAvatar
|
||||||
|
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
|
||||||
@ -68,12 +82,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>
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
|
|||||||
@ -1,19 +1,19 @@
|
|||||||
import { NInput, NForm, NFormItem, NButton, NFlex } from 'naive-ui'
|
import '../../index.scss'
|
||||||
import AppAvatar from '@/app-components/app/AppAvatar'
|
import AppAvatar from '@/app-components/app/AppAvatar'
|
||||||
|
|
||||||
import dayjs from 'dayjs'
|
|
||||||
import { useSigningActions, useSettingActions } from '@/store'
|
|
||||||
import { rules, useCondition } from '@/app-components/app/AppLockScreen/shared'
|
|
||||||
import useAppLockScreen from '@/app-components/app/AppLockScreen/appLockVar'
|
import useAppLockScreen from '@/app-components/app/AppLockScreen/appLockVar'
|
||||||
|
import { rules, useCondition } from '@/app-components/app/AppLockScreen/shared'
|
||||||
|
import { APP_CATCH_KEY } from '@/app-config'
|
||||||
|
import { RForm, useForm } from '@/components'
|
||||||
import { useDevice } from '@/hooks'
|
import { useDevice } from '@/hooks'
|
||||||
|
import { useSettingActions, useSigningActions } from '@/store'
|
||||||
import type { FormInst, InputInst } from 'naive-ui'
|
import { decrypt, getStorage, removeStorage } from '@/utils'
|
||||||
|
import dayjs from 'dayjs'
|
||||||
|
import { NButton, NFlex, NFormItem, NInput } from 'naive-ui'
|
||||||
|
|
||||||
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()
|
||||||
@ -22,13 +22,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),
|
||||||
})
|
})
|
||||||
@ -41,30 +41,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('密码错误,请重新输入')
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@ -78,18 +103,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">
|
||||||
@ -107,19 +132,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 avatarSize={52} style="pointer-events: none;" vertical />
|
<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()
|
||||||
@ -128,21 +160,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} <span>{AM_PM}</span>
|
|
||||||
</div>
|
|
||||||
<div class="current-year">
|
<div class="current-year">
|
||||||
{YY_MM_DD} <span>{DDD}</span>
|
{YY_MM_DD} <span>{DDD}</span> <span>{AM_PM}</span>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@ -1,22 +1,10 @@
|
|||||||
/**
|
|
||||||
*
|
|
||||||
* 这里没有做解锁密码校验, 只要符合校验规则值皆可
|
|
||||||
* 可以根据需求自行更改
|
|
||||||
*/
|
|
||||||
|
|
||||||
import './index.scss'
|
|
||||||
|
|
||||||
import { RModal } from '@/components'
|
import { RModal } from '@/components'
|
||||||
|
import { useSettingActions, useSettingGetters } from '@/store'
|
||||||
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'
|
|
||||||
|
|
||||||
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({
|
||||||
@ -28,12 +16,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}
|
||||||
@ -42,12 +27,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>
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
import { NAvatar, NTooltip, NFlex } from 'naive-ui'
|
import { NAvatar, NFlex, NTooltip } from 'naive-ui'
|
||||||
|
|
||||||
interface AvatarOptions {
|
interface AvatarOptions {
|
||||||
key: string
|
key: string
|
||||||
|
|||||||
@ -6,21 +6,20 @@
|
|||||||
* 如果需要更改弹出位置, 需要在需要地方重新定义组件注册
|
* 如果需要更改弹出位置, 需要在需要地方重新定义组件注册
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import {
|
import { MESSAGE_PROVIDER } from '@/app-config'
|
||||||
NDialogProvider,
|
|
||||||
NLoadingBarProvider,
|
|
||||||
NMessageProvider,
|
|
||||||
NNotificationProvider,
|
|
||||||
NConfigProvider,
|
|
||||||
createDiscreteApi,
|
|
||||||
darkTheme,
|
|
||||||
NGlobalStyle,
|
|
||||||
NModalProvider,
|
|
||||||
} from 'naive-ui'
|
|
||||||
|
|
||||||
import { getNaiveLocales } from '@/locales/utils'
|
import { getNaiveLocales } from '@/locales/utils'
|
||||||
import { useSettingGetters } from '@/store'
|
import { useSettingGetters } from '@/store'
|
||||||
import { MESSAGE_PROVIDER } from '@/app-config'
|
import {
|
||||||
|
createDiscreteApi,
|
||||||
|
darkTheme,
|
||||||
|
NConfigProvider,
|
||||||
|
NDialogProvider,
|
||||||
|
NGlobalStyle,
|
||||||
|
NLoadingBarProvider,
|
||||||
|
NMessageProvider,
|
||||||
|
NModalProvider,
|
||||||
|
NNotificationProvider,
|
||||||
|
} from 'naive-ui'
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
name: 'GlobalProvider',
|
name: 'GlobalProvider',
|
||||||
|
|||||||
@ -1,16 +1,15 @@
|
|||||||
import { get } from 'lodash-es'
|
import { APP_CATCH_KEY, APP_THEME, GLOBAL_CLASS_NAMES } from '@/app-config'
|
||||||
|
import { useSettingGetters } from '@/store'
|
||||||
|
import type { SettingState } from '@/store/modules/setting/types'
|
||||||
import {
|
import {
|
||||||
setClass,
|
|
||||||
removeClass,
|
|
||||||
setStyle,
|
|
||||||
colorToRgba,
|
colorToRgba,
|
||||||
getStorage,
|
getStorage,
|
||||||
|
removeClass,
|
||||||
|
setClass,
|
||||||
|
setStyle,
|
||||||
} from '@/utils'
|
} from '@/utils'
|
||||||
import { useSettingGetters } from '@/store'
|
|
||||||
import { APP_CATCH_KEY, THEME_CLASS_NAMES } from '@/app-config'
|
|
||||||
import { useWindowSize } from '@vueuse/core'
|
import { useWindowSize } from '@vueuse/core'
|
||||||
|
import { get } from 'lodash-es'
|
||||||
import type { SettingState } from '@/store/modules/setting/types'
|
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
name: 'AppStyleProvider',
|
name: 'AppStyleProvider',
|
||||||
@ -20,9 +19,14 @@ export default defineComponent({
|
|||||||
|
|
||||||
// 同步主题色变量至 html,如果未获取到缓存值则已默认值填充
|
// 同步主题色变量至 html,如果未获取到缓存值则已默认值填充
|
||||||
const syncPrimaryColorToBody = () => {
|
const syncPrimaryColorToBody = () => {
|
||||||
|
// 默认主题色
|
||||||
const {
|
const {
|
||||||
appPrimaryColor: { primaryColor, primaryFadeColor },
|
appPrimaryColor: { primaryColor, primaryFadeColor },
|
||||||
} = __APP_CFG__ // 默认主题色
|
} = APP_THEME
|
||||||
|
// 主题色配置 class 名
|
||||||
|
const { rayTemplateThemePrimaryColor, rayTemplateThemePrimaryFadeColor } =
|
||||||
|
GLOBAL_CLASS_NAMES
|
||||||
|
|
||||||
const html = document.documentElement
|
const html = document.documentElement
|
||||||
|
|
||||||
// 获取缓存 naive ui 配置项
|
// 获取缓存 naive ui 配置项
|
||||||
@ -39,21 +43,22 @@ export default defineComponent({
|
|||||||
primaryColor,
|
primaryColor,
|
||||||
)
|
)
|
||||||
// 将主色调任意颜色转换为 rgba 格式
|
// 将主色调任意颜色转换为 rgba 格式
|
||||||
const fp = colorToRgba(p, 0.38)
|
const fp = colorToRgba(p, 0.85)
|
||||||
|
|
||||||
// 设置全局主题色 css 变量
|
// 设置全局主题色 css 变量
|
||||||
html.style.setProperty('--ray-theme-primary-color', p) // 主色调
|
html.style.setProperty(rayTemplateThemePrimaryColor, p) // 主色调
|
||||||
|
// 降低透明度后的主色调
|
||||||
html.style.setProperty(
|
html.style.setProperty(
|
||||||
'--ray-theme-primary-fade-color',
|
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, {
|
||||||
@ -65,7 +70,7 @@ export default defineComponent({
|
|||||||
// 切换主题时,同步更新 html class 以便于进行自定义 css 配置
|
// 切换主题时,同步更新 html class 以便于进行自定义 css 配置
|
||||||
const updateGlobalThemeClass = (bool: boolean) => {
|
const updateGlobalThemeClass = (bool: boolean) => {
|
||||||
const html = document.documentElement
|
const html = document.documentElement
|
||||||
const { darkClassName, lightClassName } = THEME_CLASS_NAMES
|
const { darkClassName, lightClassName } = GLOBAL_CLASS_NAMES
|
||||||
|
|
||||||
bool
|
bool
|
||||||
? removeClass(html, lightClassName)
|
? removeClass(html, lightClassName)
|
||||||
@ -82,8 +87,8 @@ export default defineComponent({
|
|||||||
updateGlobalThemeClass(getAppTheme.value)
|
updateGlobalThemeClass(getAppTheme.value)
|
||||||
// 注入全局宽高尺寸
|
// 注入全局宽高尺寸
|
||||||
setStyle(document.documentElement, {
|
setStyle(document.documentElement, {
|
||||||
'--html-height': `${height.value}px`,
|
[GLOBAL_CLASS_NAMES.htmlHeight]: `${height.value}px`,
|
||||||
'--html-width': `${width.value}px`,
|
[GLOBAL_CLASS_NAMES.htmlWidth]: `${width.value}px`,
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
@ -4,11 +4,10 @@
|
|||||||
* 如果不是最新版本则弹出提示框,提示用户更新,点击确认后退出登录并且刷新资源
|
* 如果不是最新版本则弹出提示框,提示用户更新,点击确认后退出登录并且刷新资源
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { RModal } from '@/components'
|
|
||||||
|
|
||||||
import { getStorage, setStorage } from '@/utils'
|
|
||||||
import { useSigningActions } from '@/store'
|
|
||||||
import { APP_CATCH_KEY } from '@/app-config'
|
import { APP_CATCH_KEY } from '@/app-config'
|
||||||
|
import { RModal } from '@/components'
|
||||||
|
import { useSigningActions } from '@/store'
|
||||||
|
import { getStorage, setStorage } from '@/utils'
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
name: 'AppVersionProvider',
|
name: 'AppVersionProvider',
|
||||||
@ -28,18 +27,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 {
|
||||||
@ -61,7 +52,7 @@ export default defineComponent({
|
|||||||
title="发现新版本"
|
title="发现新版本"
|
||||||
content="当前版本已更新,点击确认加载新版本~"
|
content="当前版本已更新,点击确认加载新版本~"
|
||||||
zIndex={999999999}
|
zIndex={999999999}
|
||||||
dad
|
draggable
|
||||||
positiveText="确认"
|
positiveText="确认"
|
||||||
negativeText="取消"
|
negativeText="取消"
|
||||||
onPositiveClick={logout}
|
onPositiveClick={logout}
|
||||||
|
|||||||
@ -8,9 +8,8 @@
|
|||||||
* 当然你也可以通过 useWatermark hook 自定义控制水印的显示以及内容
|
* 当然你也可以通过 useWatermark hook 自定义控制水印的显示以及内容
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { NWatermark } from 'naive-ui'
|
|
||||||
|
|
||||||
import { useSettingGetters } from '@/store'
|
import { useSettingGetters } from '@/store'
|
||||||
|
import { NWatermark } from 'naive-ui'
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
name: 'AppWatermarkProvider',
|
name: 'AppWatermarkProvider',
|
||||||
|
|||||||
@ -5,11 +5,19 @@ import type { MessageProviderProps } from 'naive-ui'
|
|||||||
*
|
*
|
||||||
* @description
|
* @description
|
||||||
* 全局注入到 html 的样式类名。
|
* 全局注入到 html 的样式类名。
|
||||||
|
*
|
||||||
|
* 如果涉及到全局主题色的 class name,需要修改的时候记得全局替换;
|
||||||
|
* 避免样式出现奇奇怪怪的问题。
|
||||||
*/
|
*/
|
||||||
export const THEME_CLASS_NAMES = {
|
export const GLOBAL_CLASS_NAMES = {
|
||||||
darkClassName: 'ray-template--dark',
|
darkClassName: 'ray-template--dark',
|
||||||
lightClassName: 'ray-template--light',
|
lightClassName: 'ray-template--light',
|
||||||
}
|
rayTemplateThemePrimaryColor: '--r-theme-primary-color',
|
||||||
|
rayTemplateThemePrimaryFadeColor: '--r-theme-primary-fade-color',
|
||||||
|
preLoadingAnimation: 'pre-loading-animation',
|
||||||
|
htmlHeight: '--html-height',
|
||||||
|
htmlWidth: '--html-width',
|
||||||
|
} as const
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
@ -85,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',
|
||||||
@ -98,6 +108,8 @@ 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
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@ -3,8 +3,9 @@ import type { AppTheme } from '@/types'
|
|||||||
export const APP_THEME: AppTheme = {
|
export const APP_THEME: AppTheme = {
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* 系统主题颜色预设色盘
|
* @description
|
||||||
* 支持 RGBA、RGB、十六进制
|
* 系统主题颜色预设色盘。
|
||||||
|
* 支持 RGBA、RGB、十六进制。
|
||||||
*/
|
*/
|
||||||
appThemeColors: [
|
appThemeColors: [
|
||||||
'#2d8cf0',
|
'#2d8cf0',
|
||||||
@ -12,25 +13,26 @@ export const APP_THEME: AppTheme = {
|
|||||||
'#ff42bc',
|
'#ff42bc',
|
||||||
'#ee4f12',
|
'#ee4f12',
|
||||||
'#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
|
||||||
@ -46,17 +48,24 @@ export const APP_THEME: AppTheme = {
|
|||||||
* ```
|
* ```
|
||||||
*/
|
*/
|
||||||
appNaiveUIThemeOverrides: {
|
appNaiveUIThemeOverrides: {
|
||||||
dark: {},
|
dark: {
|
||||||
light: {},
|
common: {
|
||||||
|
borderRadius: '4px',
|
||||||
|
baseColor: 'rgba(18, 18, 18, 1)',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
light: {
|
||||||
|
common: {
|
||||||
|
borderRadius: '4px',
|
||||||
|
baseColor: 'rgba(255, 255, 255, 1)',
|
||||||
|
},
|
||||||
},
|
},
|
||||||
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)
|
||||||
*/
|
*/
|
||||||
|
|||||||
@ -1,5 +1,9 @@
|
|||||||
import type { TemplateLocale, LocalOptions, DayjsLocalMap } from '@/types'
|
import type {
|
||||||
import type { ValueOf } from '@/types'
|
DayjsLocalMap,
|
||||||
|
LocalOptions,
|
||||||
|
TemplateLocale,
|
||||||
|
ValueOf,
|
||||||
|
} from '@/types'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
|
|||||||
@ -4,7 +4,7 @@ import type { Ref } from 'vue'
|
|||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* @description
|
* @description
|
||||||
* 内容区域 ref 注册
|
* 内容区域 shallowRef 注册
|
||||||
* 可以控制内容区域当前滚动位置
|
* 可以控制内容区域当前滚动位置
|
||||||
* 如果你需要在切换路由时候配置自定义滚动到某个视图区域时, 可以使用该属性提供的方法(scrollTo)
|
* 如果你需要在切换路由时候配置自定义滚动到某个视图区域时, 可以使用该属性提供的方法(scrollTo)
|
||||||
*
|
*
|
||||||
@ -16,12 +16,12 @@ import type { Ref } from 'vue'
|
|||||||
* })
|
* })
|
||||||
*/
|
*/
|
||||||
export const LAYOUT_CONTENT_REF: Readonly<Ref<LayoutInst | null>> =
|
export const LAYOUT_CONTENT_REF: Readonly<Ref<LayoutInst | null>> =
|
||||||
ref<LayoutInst | null>(null)
|
shallowRef<LayoutInst | null>(null)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* @description
|
* @description
|
||||||
* 侧边滚动栏滚动 ref 注册。
|
* 侧边滚动栏滚动 shallowRef 注册。
|
||||||
* 可以控制侧边滚动栏滚动位置。
|
* 可以控制侧边滚动栏滚动位置。
|
||||||
*
|
*
|
||||||
* 请注意使用时机。建议使用 nextTick() 等待 dom 挂载后再执行该方法。
|
* 请注意使用时机。建议使用 nextTick() 等待 dom 挂载后再执行该方法。
|
||||||
@ -31,7 +31,7 @@ export const LAYOUT_CONTENT_REF: Readonly<Ref<LayoutInst | null>> =
|
|||||||
* })
|
* })
|
||||||
*/
|
*/
|
||||||
export const LAYOUT_SIDER_REF: Readonly<Ref<LayoutInst | null>> =
|
export const LAYOUT_SIDER_REF: Readonly<Ref<LayoutInst | null>> =
|
||||||
ref<LayoutInst | null>(null)
|
shallowRef<LayoutInst | null>(null)
|
||||||
|
|
||||||
export const SETUP_ROUTER_ACTION = {
|
export const SETUP_ROUTER_ACTION = {
|
||||||
/** 是否启用路由切换时顶部加载条 */
|
/** 是否启用路由切换时顶部加载条 */
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
import { useAxiosInterceptor } from '@/axios/utils/interceptor'
|
import { axiosInterceptor } from '@/axios/utils/interceptor'
|
||||||
import implement from './provider'
|
import implement from './provider'
|
||||||
|
|
||||||
const { setImplement } = useAxiosInterceptor()
|
const { setImplement } = axiosInterceptor()
|
||||||
|
|
||||||
export const setupRequestInterceptor = () => {
|
export const setupRequestInterceptor = () => {
|
||||||
const { implementRequestInterceptorArray } = implement
|
const { implementRequestInterceptorArray } = implement
|
||||||
|
|||||||
@ -1,6 +1,5 @@
|
|||||||
import { axiosCanceler } from '@/axios/utils/interceptor'
|
|
||||||
|
|
||||||
import type { AxiosRequestInterceptor, FetchErrorFunction } from '@/axios/types'
|
import type { AxiosRequestInterceptor, FetchErrorFunction } from '@/axios/types'
|
||||||
|
import { axiosCanceler } from '@/axios/utils/interceptor'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
|
|||||||
@ -1,11 +1,10 @@
|
|||||||
import { appendRequestHeaders } from '@/axios/utils/append-request-headers'
|
|
||||||
import { APP_CATCH_KEY } from '@/app-config'
|
import { APP_CATCH_KEY } from '@/app-config'
|
||||||
import { getStorage } from '@/utils'
|
|
||||||
|
|
||||||
import type {
|
import type {
|
||||||
RequestInterceptorConfig,
|
|
||||||
AxiosRequestInterceptor,
|
AxiosRequestInterceptor,
|
||||||
|
RequestInterceptorConfig,
|
||||||
} from '@/axios/types'
|
} from '@/axios/types'
|
||||||
|
import { appendRequestHeaders } from '@/axios/utils/append-request-headers'
|
||||||
|
import { getStorage } from '@/utils'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
@ -16,7 +15,7 @@ import type {
|
|||||||
* 当然你也可以根据 request instance 来特殊处理, 这里暂时不做演示
|
* 当然你也可以根据 request instance 来特殊处理, 这里暂时不做演示
|
||||||
*/
|
*/
|
||||||
const requestHeaderToken = (ins: RequestInterceptorConfig, mode: string) => {
|
const requestHeaderToken = (ins: RequestInterceptorConfig, mode: string) => {
|
||||||
const token = getStorage<string>(APP_CATCH_KEY.token, 'localStorage')
|
const token = getStorage<string | null>(APP_CATCH_KEY.token, 'localStorage')
|
||||||
|
|
||||||
if (ins.url) {
|
if (ins.url) {
|
||||||
// TODO: 根据 url 不同是否设置 token
|
// TODO: 根据 url 不同是否设置 token
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
import { useAxiosInterceptor } from '@/axios/utils/interceptor'
|
import { axiosInterceptor } from '@/axios/utils/interceptor'
|
||||||
import implement from './provider'
|
import implement from './provider'
|
||||||
|
|
||||||
const { setImplement } = useAxiosInterceptor()
|
const { setImplement } = axiosInterceptor()
|
||||||
|
|
||||||
export const setupResponseInterceptor = () => {
|
export const setupResponseInterceptor = () => {
|
||||||
const { implementResponseInterceptorArray } = implement
|
const { implementResponseInterceptorArray } = implement
|
||||||
|
|||||||
@ -1,9 +1,8 @@
|
|||||||
import { axiosCanceler } from '@/axios/utils/interceptor'
|
|
||||||
|
|
||||||
import type {
|
import type {
|
||||||
AxiosResponseInterceptor,
|
AxiosResponseInterceptor,
|
||||||
FetchErrorFunction,
|
FetchErrorFunction,
|
||||||
} from '@/axios/types'
|
} from '@/axios/types'
|
||||||
|
import { axiosCanceler } from '@/axios/utils/interceptor'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
|
|||||||
@ -10,11 +10,10 @@
|
|||||||
* 由于中间件注册了自动取消重复请求的方法,所以会导致方法在初始化时,会抛出一个重复请求被取消的错误(该问题不影响使用)
|
* 由于中间件注册了自动取消重复请求的方法,所以会导致方法在初始化时,会抛出一个重复请求被取消的错误(该问题不影响使用)
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import useHookPlusRequest from 'vue-hooks-plus/es/useRequest'
|
|
||||||
import request from '@/axios/instance'
|
import request from '@/axios/instance'
|
||||||
|
|
||||||
import type { UseRequestOptions } from 'vue-hooks-plus/es/useRequest/types'
|
|
||||||
import type { AppRawRequestConfig } from '@/axios/types'
|
import type { AppRawRequestConfig } from '@/axios/types'
|
||||||
|
import useHookPlusRequest from 'vue-hooks-plus/es/useRequest'
|
||||||
|
import type { UseRequestOptions } from 'vue-hooks-plus/es/useRequest/types'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
@ -40,14 +39,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),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@ -1,39 +1,41 @@
|
|||||||
/**
|
|
||||||
*
|
|
||||||
* 请求拦截器与响应拦截器
|
|
||||||
* 如果有需要拓展拦截器, 请在 inject 目录下参照示例方法继续拓展
|
|
||||||
* 该页面不应该做过多的改动与配置
|
|
||||||
*/
|
|
||||||
|
|
||||||
import axios from 'axios'
|
|
||||||
import { AXIOS_CONFIG } from '@/app-config'
|
import { AXIOS_CONFIG } from '@/app-config'
|
||||||
import { useAxiosInterceptor } from '@/axios/utils/interceptor'
|
|
||||||
import {
|
import {
|
||||||
setupResponseInterceptor,
|
|
||||||
setupResponseErrorInterceptor,
|
|
||||||
} from '@/axios/axios-interceptor/response'
|
|
||||||
import {
|
|
||||||
setupRequestInterceptor,
|
|
||||||
setupRequestErrorInterceptor,
|
setupRequestErrorInterceptor,
|
||||||
|
setupRequestInterceptor,
|
||||||
} from '@/axios/axios-interceptor/request'
|
} from '@/axios/axios-interceptor/request'
|
||||||
|
import {
|
||||||
|
setupResponseErrorInterceptor,
|
||||||
|
setupResponseInterceptor,
|
||||||
|
} from '@/axios/axios-interceptor/response'
|
||||||
|
import { axiosInterceptor } from '@/axios/utils/interceptor'
|
||||||
|
import axios from 'axios'
|
||||||
|
import type { AxiosInstanceExpand, RequestInterceptorConfig } from './types'
|
||||||
|
|
||||||
import type { AxiosInstanceExpand } 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 } = axiosInterceptor()
|
||||||
|
|
||||||
// 请求拦截器
|
// 请求拦截器
|
||||||
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)
|
||||||
},
|
},
|
||||||
@ -42,17 +44,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)
|
||||||
},
|
},
|
||||||
|
|||||||
@ -1,16 +1,16 @@
|
|||||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||||
|
import type { AnyFn } from '@/types'
|
||||||
import type {
|
import type {
|
||||||
|
Axios,
|
||||||
|
AxiosDefaults,
|
||||||
|
AxiosError,
|
||||||
AxiosHeaders,
|
AxiosHeaders,
|
||||||
AxiosRequestConfig,
|
AxiosRequestConfig,
|
||||||
HeadersDefaults,
|
|
||||||
AxiosDefaults,
|
|
||||||
Axios,
|
|
||||||
AxiosResponse,
|
|
||||||
AxiosError,
|
|
||||||
InternalAxiosRequestConfig,
|
|
||||||
AxiosRequestHeaders,
|
AxiosRequestHeaders,
|
||||||
|
AxiosResponse,
|
||||||
|
HeadersDefaults,
|
||||||
|
InternalAxiosRequestConfig,
|
||||||
} from 'axios'
|
} from 'axios'
|
||||||
import type { AnyFC } from '@/types'
|
|
||||||
|
|
||||||
export type AxiosHeaderValue =
|
export type AxiosHeaderValue =
|
||||||
| AxiosHeaders
|
| AxiosHeaders
|
||||||
@ -136,13 +136,13 @@ export type RequestInterceptorConfig<T = any> = AppRawRequestConfig<T>
|
|||||||
export type ResponseInterceptorConfig<T = any, K = any> = AxiosResponse<T, K>
|
export type ResponseInterceptorConfig<T = any, K = any> = AxiosResponse<T, K>
|
||||||
|
|
||||||
export interface ImplementQueue {
|
export interface ImplementQueue {
|
||||||
implementRequestInterceptorArray: AnyFC[]
|
implementRequestInterceptorArray: AnyFn[]
|
||||||
implementResponseInterceptorArray: AnyFC[]
|
implementResponseInterceptorArray: AnyFn[]
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ErrorImplementQueue {
|
export interface ErrorImplementQueue {
|
||||||
implementRequestInterceptorErrorArray: AnyFC[]
|
implementRequestInterceptorErrorArray: AnyFn[]
|
||||||
implementResponseInterceptorErrorArray: AnyFC[]
|
implementResponseInterceptorErrorArray: AnyFn[]
|
||||||
}
|
}
|
||||||
|
|
||||||
export type FetchType = 'ok' | 'error'
|
export type FetchType = 'ok' | 'error'
|
||||||
|
|||||||
@ -1,116 +1,129 @@
|
|||||||
/**
|
|
||||||
*
|
|
||||||
* 自动取消重复请求
|
|
||||||
*
|
|
||||||
* 可以根据自己项目进行定制化配置
|
|
||||||
*/
|
|
||||||
|
|
||||||
import type { AppRawRequestConfig, CancelerParams } from '@/axios/types'
|
import type { AppRawRequestConfig, CancelerParams } from '@/axios/types'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
|
||||||
* @class RequestCanceler
|
|
||||||
*
|
*
|
||||||
* @description
|
* @description
|
||||||
* 用于取消重复请求,会在请求前添加 signal 属性,用于取消请求。
|
* 请求取消器。
|
||||||
* 通过 generateRequestKey 方法生成请求 key,用于标识请求。
|
|
||||||
*
|
*
|
||||||
* 如果需要取消请求,则需要在请求前添加 cancelConfig.cancel 为 true;
|
* 用于管理和取消重复的 HTTP 请求:
|
||||||
* 并且会在请求前添加 __CANCELER_TAG_RAY_TEMPLATE__ 属性,用于标识是否需要取消。
|
* - 自动为请求添加 AbortController signal
|
||||||
|
* - 通过请求特征生成唯一 key 来识别重复请求
|
||||||
|
* - 支持取消单个或所有待处理的请求
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* ```ts
|
||||||
|
* const canceler = new RequestCanceler()
|
||||||
|
*
|
||||||
|
* // 添加请求到待处理队列
|
||||||
|
* canceler.addPendingRequest(config)
|
||||||
|
*
|
||||||
|
* // 移除并取消特定请求
|
||||||
|
* canceler.removePendingRequest(config)
|
||||||
|
*
|
||||||
|
* // 取消所有待处理请求
|
||||||
|
* canceler.cancelAllRequest()
|
||||||
|
* ```
|
||||||
*/
|
*/
|
||||||
export default class RequestCanceler {
|
export default class RequestCanceler {
|
||||||
private pendingRequest: Map<string, AbortController>
|
/** 待处理请求的 Map,key 为请求标识,value 为 AbortController */
|
||||||
|
private readonly pendingRequest = new Map<string, AbortController>()
|
||||||
constructor() {
|
|
||||||
this.pendingRequest = new Map<string, AbortController>()
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* @param config 请求体 config
|
* @param config - 请求配置
|
||||||
|
*
|
||||||
|
* @returns 是否需要添加取消功能,默认为 true
|
||||||
*
|
*
|
||||||
* @description
|
* @description
|
||||||
* 判断是否需要添加 signal 属性。
|
* 判断请求是否需要添加取消功能。
|
||||||
*
|
|
||||||
* 如果 cancelConfig 为 false,则不添加 signal 属性;
|
|
||||||
* 如果 cancelConfig 为 true,则添加 signal 属性。
|
|
||||||
*
|
|
||||||
* @example
|
|
||||||
* const bool = isAppending(config) // true or false
|
|
||||||
*/
|
*/
|
||||||
private isAppending(config: AppRawRequestConfig | CancelerParams) {
|
private shouldAddCanceler(
|
||||||
|
config: AppRawRequestConfig | CancelerParams,
|
||||||
|
): boolean {
|
||||||
return config.cancelConfig?.cancel ?? true
|
return config.cancelConfig?.cancel ?? true
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* @param config 请求体 config
|
* @param config - 请求配置
|
||||||
|
*
|
||||||
|
* @returns 请求的唯一标识字符串
|
||||||
*
|
*
|
||||||
* @description
|
* @description
|
||||||
* 根据当前请求生成 key。
|
* 基于 URL、方法、参数和数据生成请求的唯一标识 key。
|
||||||
*
|
|
||||||
* @example
|
|
||||||
* const key = generateRequestKey(config) // string
|
|
||||||
*/
|
*/
|
||||||
private generateRequestKey(config: AppRawRequestConfig | CancelerParams) {
|
private generateRequestKey(
|
||||||
const { method, url } = config
|
config: AppRawRequestConfig | CancelerParams,
|
||||||
|
): string {
|
||||||
|
const { method = '', url = '', params, data } = config
|
||||||
|
|
||||||
return [
|
return [url, method, JSON.stringify(params), JSON.stringify(data)].join('&')
|
||||||
url || '',
|
|
||||||
method || '',
|
|
||||||
JSON.stringify(config.params),
|
|
||||||
JSON.stringify(config.data),
|
|
||||||
].join('&')
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
|
||||||
* @param config axios request config
|
* @param config - Axios 请求配置
|
||||||
*
|
|
||||||
* @description
|
* @description
|
||||||
* 添加请求到 pendingRequest map 中,用于取消请求。
|
* 添加请求到 pendingRequest map 中,用于取消请求。
|
||||||
* 并且如果已经存在该请求,则会取消上次请求,并且重新挂载 signal。
|
* 并且如果已经存在该请求,则会取消上次请求,并且重新挂载 signal。
|
||||||
*
|
*
|
||||||
* 如果不需要该请求被挂载,则需要在请求前添加 cancelConfig.cancel 为 false。
|
* 如果不需要该请求被挂载,则需要在请求前添加 cancelConfig.cancel 为 false。
|
||||||
* 如果该请求需要被取消,则会添加 __CANCELER_TAG_RAY_TEMPLATE__ 属性,标记是否需要取消。
|
* 如果该请求需要被取消,则会添加 __CANCELER_TAG_RAY_TEMPLATE__ 属性,标记是否需要取消。
|
||||||
*
|
|
||||||
* @example
|
* @example
|
||||||
* addPendingRequest(config)
|
* ```ts
|
||||||
|
* // 默认启用取消功能
|
||||||
|
* canceler.addPendingRequest(config)
|
||||||
|
*
|
||||||
|
* // 禁用取消功能
|
||||||
|
* canceler.addPendingRequest({
|
||||||
|
* ...config,
|
||||||
|
* cancelConfig: { cancel: false }
|
||||||
|
* })
|
||||||
|
* ```
|
||||||
*/
|
*/
|
||||||
addPendingRequest(config: AppRawRequestConfig | CancelerParams) {
|
addPendingRequest(config: AppRawRequestConfig | CancelerParams): void {
|
||||||
if (this.isAppending(config)) {
|
if (!this.shouldAddCanceler(config)) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
config.__CANCELER_TAG_RAY_TEMPLATE__ = '__CANCELER_TAG_RAY_TEMPLATE__'
|
config.__CANCELER_TAG_RAY_TEMPLATE__ = '__CANCELER_TAG_RAY_TEMPLATE__'
|
||||||
|
|
||||||
const requestKey = this.generateRequestKey(config)
|
const requestKey = this.generateRequestKey(config)
|
||||||
|
const existingController = this.pendingRequest.get(requestKey)
|
||||||
|
|
||||||
if (!this.pendingRequest.has(requestKey)) {
|
if (existingController) {
|
||||||
|
// 复用现有的 signal
|
||||||
|
config.signal = existingController.signal
|
||||||
|
} else {
|
||||||
|
// 创建新的 AbortController
|
||||||
const controller = new AbortController()
|
const controller = new AbortController()
|
||||||
|
|
||||||
config.signal = controller.signal
|
config.signal = controller.signal
|
||||||
|
|
||||||
this.pendingRequest.set(requestKey, controller)
|
this.pendingRequest.set(requestKey, controller)
|
||||||
} else {
|
|
||||||
// 如果已经有该 key 则重新挂载 signal
|
|
||||||
config.signal = this.pendingRequest.get(requestKey)?.signal
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* @param config axios request config
|
* @param config - Axios 请求配置
|
||||||
*
|
*
|
||||||
* @description
|
* @description
|
||||||
* 移除 pendingRequest map 中的请求,如果存在的话。
|
* 移除并取消特定请求,
|
||||||
|
* 从待处理队列中移除请求,并调用 abort() 取消该请求。
|
||||||
*
|
*
|
||||||
* @example
|
* @example
|
||||||
* removePendingRequest(config)
|
* ```ts
|
||||||
|
* canceler.removePendingRequest(config)
|
||||||
|
* ```
|
||||||
*/
|
*/
|
||||||
removePendingRequest(config: AppRawRequestConfig | CancelerParams) {
|
removePendingRequest(config: AppRawRequestConfig | CancelerParams): void {
|
||||||
const requestKey = this.generateRequestKey(config)
|
const requestKey = this.generateRequestKey(config)
|
||||||
|
const controller = this.pendingRequest.get(requestKey)
|
||||||
|
|
||||||
if (this.pendingRequest.has(requestKey)) {
|
if (controller) {
|
||||||
this.pendingRequest.get(requestKey)!.abort()
|
controller.abort()
|
||||||
this.pendingRequest.delete(requestKey)
|
this.pendingRequest.delete(requestKey)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -118,17 +131,50 @@ export default class RequestCanceler {
|
|||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* @description
|
* @description
|
||||||
* 移除所有 pendingRequest map 中的请求。
|
* 取消所有待处理的请求。
|
||||||
*
|
*
|
||||||
* 值得注意的是,该方法会一次性移除所有的请求,所以需要注意是否有需要在后台挂载的请求;
|
* 遍历并取消队列中的所有请求,然后清空队列。
|
||||||
* 如果有需要在后台挂载的请求,则需要在请求前添加 cancelConfig.cancel 为 false。
|
*
|
||||||
|
* ⚠️ 注意:此方法会取消所有请求,包括后台运行的请求。
|
||||||
|
*
|
||||||
|
* 如果某些请求不应被取消,请在请求配置中设置 `cancelConfig.cancel = false`
|
||||||
*
|
*
|
||||||
* @example
|
* @example
|
||||||
* cancelAllRequest()
|
* ```ts
|
||||||
|
* // 在路由切换或组件卸载时取消所有请求
|
||||||
|
* canceler.cancelAllRequest()
|
||||||
|
* ```
|
||||||
*/
|
*/
|
||||||
cancelAllRequest() {
|
cancelAllRequest(): void {
|
||||||
this.pendingRequest.forEach((curr) => {
|
this.pendingRequest.forEach((controller) => {
|
||||||
curr.abort()
|
controller.abort()
|
||||||
})
|
})
|
||||||
|
this.pendingRequest.clear()
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @returns 待处理请求数量
|
||||||
|
*
|
||||||
|
* @description
|
||||||
|
* 获取当前待处理请求的数量。
|
||||||
|
*/
|
||||||
|
getPendingCount(): number {
|
||||||
|
return this.pendingRequest.size
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param config - 请求配置
|
||||||
|
*
|
||||||
|
* @returns 是否存在于待处理队列
|
||||||
|
*
|
||||||
|
* @description
|
||||||
|
* 检查特定请求是否在待处理队列中。
|
||||||
|
*/
|
||||||
|
hasPendingRequest(config: AppRawRequestConfig | CancelerParams): boolean {
|
||||||
|
const requestKey = this.generateRequestKey(config)
|
||||||
|
|
||||||
|
return this.pendingRequest.has(requestKey)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
import type { RawAxiosRequestHeaders, AxiosRequestConfig } from 'axios'
|
import type { AxiosRequestConfig, RawAxiosRequestHeaders } from 'axios'
|
||||||
import type { RequestHeaderOptions } from '../types'
|
import type { RequestHeaderOptions } from '../types'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
import type { RawAxiosRequestHeaders, AxiosRequestConfig } from 'axios'
|
import type { AxiosRequestConfig, RawAxiosRequestHeaders } from 'axios'
|
||||||
import type { RequestHeaderOptions } from '../types'
|
import type { RequestHeaderOptions } from '../types'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@ -1,132 +1,183 @@
|
|||||||
/**
|
|
||||||
*
|
|
||||||
* axios 拦截器注入
|
|
||||||
*
|
|
||||||
* 请求拦截器、响应拦截器
|
|
||||||
* 暴露启动方法调用所有已注册方法
|
|
||||||
*
|
|
||||||
* 该拦截器仅适合放置公共的 axios 拦截器操作, 并且采用队列形式管理请求拦截器的注入
|
|
||||||
* 所以在使用的时候, 需要按照约定格式进行参数传递
|
|
||||||
*/
|
|
||||||
|
|
||||||
import RequestCanceler from '@/axios/utils/RequestCanceler'
|
|
||||||
import { getAppEnvironment } from '@/utils'
|
|
||||||
|
|
||||||
import type {
|
import type {
|
||||||
RequestInterceptorConfig,
|
AxiosFetchError,
|
||||||
ResponseInterceptorConfig,
|
AxiosFetchInstance,
|
||||||
ImplementQueue,
|
|
||||||
ErrorImplementQueue,
|
ErrorImplementQueue,
|
||||||
FetchType,
|
FetchType,
|
||||||
AxiosFetchInstance,
|
ImplementQueue,
|
||||||
AxiosFetchError,
|
RequestInterceptorConfig,
|
||||||
|
ResponseInterceptorConfig,
|
||||||
} from '@/axios/types'
|
} from '@/axios/types'
|
||||||
import type { AnyFC } from '@/types'
|
import RequestCanceler from '@/axios/utils/RequestCanceler'
|
||||||
|
import type { AnyFn } from '@/types'
|
||||||
|
import { getAppEnvironment } from '@/utils'
|
||||||
import type { AxiosError } from 'axios'
|
import type { AxiosError } from 'axios'
|
||||||
|
|
||||||
/** 当前请求的实例 */
|
type ImplementKeys = keyof ImplementQueue
|
||||||
|
type ErrorImplementKeys = keyof ErrorImplementQueue
|
||||||
|
|
||||||
|
// 当前请求的实例
|
||||||
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: [],
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 取消器实例 */
|
// 取消器实例
|
||||||
export const axiosCanceler = new RequestCanceler()
|
export const axiosCanceler = new RequestCanceler()
|
||||||
|
|
||||||
export const useAxiosInterceptor = () => {
|
export const axiosInterceptor = () => {
|
||||||
/** 创建拦截器实例 */
|
/**
|
||||||
|
*
|
||||||
|
* @param instance - 请求或响应实例
|
||||||
|
* @param instanceKey - 实例类型标识
|
||||||
|
*
|
||||||
|
* @description
|
||||||
|
* 创建拦截器实例。
|
||||||
|
*/
|
||||||
const createAxiosInstance = (
|
const createAxiosInstance = (
|
||||||
instance: RequestInterceptorConfig | ResponseInterceptorConfig,
|
instance: RequestInterceptorConfig | ResponseInterceptorConfig,
|
||||||
instanceKey: keyof AxiosFetchInstance,
|
instanceKey: keyof AxiosFetchInstance,
|
||||||
) => {
|
): void => {
|
||||||
instanceKey === 'requestInstance'
|
if (instanceKey === 'requestInstance') {
|
||||||
? (axiosFetchInstance['requestInstance'] =
|
axiosFetchInstance.requestInstance = instance as RequestInterceptorConfig
|
||||||
instance as RequestInterceptorConfig)
|
} else {
|
||||||
: (axiosFetchInstance['responseInstance'] =
|
axiosFetchInstance.responseInstance =
|
||||||
instance as ResponseInterceptorConfig)
|
instance as ResponseInterceptorConfig
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 获取当前实例 */
|
/**
|
||||||
|
*
|
||||||
|
* @param instanceKey - 实例类型标识
|
||||||
|
*
|
||||||
|
* @returns 对应的实例
|
||||||
|
*
|
||||||
|
* @description
|
||||||
|
* 获取当前实例。
|
||||||
|
*/
|
||||||
const getAxiosInstance = (instanceKey: keyof AxiosFetchInstance) => {
|
const getAxiosInstance = (instanceKey: keyof AxiosFetchInstance) => {
|
||||||
return axiosFetchInstance[instanceKey]
|
return axiosFetchInstance[instanceKey]
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 设置注入方法队列 */
|
/**
|
||||||
|
*
|
||||||
|
* @param key - 队列键名
|
||||||
|
* @param func - 拦截器函数数组
|
||||||
|
* @param fetchType - 请求类型(成功/失败)
|
||||||
|
*
|
||||||
|
* @description
|
||||||
|
* 设置注入方法队列。
|
||||||
|
*/
|
||||||
const setImplement = (
|
const setImplement = (
|
||||||
key: keyof ImplementQueue | keyof ErrorImplementQueue,
|
key: ImplementKeys | ErrorImplementKeys,
|
||||||
func: AnyFC[],
|
func: AnyFn[],
|
||||||
fetchType: FetchType,
|
fetchType: FetchType,
|
||||||
) => {
|
): void => {
|
||||||
fetchType === 'ok'
|
if (fetchType === 'ok') {
|
||||||
? (implement[key as keyof ImplementQueue] = func)
|
implement[key as ImplementKeys] = func
|
||||||
: (errorImplement[key as keyof ErrorImplementQueue] = func)
|
} else {
|
||||||
|
errorImplement[key as ErrorImplementKeys] = func
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 获取队列中所有的所有拦截器方法 */
|
/**
|
||||||
|
*
|
||||||
|
* @param key - 队列键名
|
||||||
|
* @param fetchType - 请求类型(成功/失败)
|
||||||
|
*
|
||||||
|
* @returns 拦截器函数数组
|
||||||
|
*
|
||||||
|
* @description
|
||||||
|
* 获取队列中所有的拦截器方法。
|
||||||
|
*/
|
||||||
const getImplement = (
|
const getImplement = (
|
||||||
key: keyof ImplementQueue | keyof ErrorImplementQueue,
|
key: ImplementKeys | ErrorImplementKeys,
|
||||||
fetchType: FetchType,
|
fetchType: FetchType,
|
||||||
): AnyFC[] => {
|
): AnyFn[] => {
|
||||||
return fetchType === 'ok'
|
return fetchType === 'ok'
|
||||||
? implement[key as keyof ImplementQueue]
|
? implement[key as ImplementKeys]
|
||||||
: errorImplement[key as keyof ErrorImplementQueue]
|
: errorImplement[key as ErrorImplementKeys]
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 队列执行器 */
|
/**
|
||||||
const implementer = (funcs: AnyFC[], ...args: any[]) => {
|
*
|
||||||
if (Array.isArray(funcs)) {
|
* @param funcs - 函数数组
|
||||||
funcs.forEach((curr) => {
|
* @param args - 传递给函数的参数
|
||||||
if (typeof curr === 'function') {
|
*
|
||||||
curr(...args)
|
* @description
|
||||||
|
* 队列执行器 - 执行所有拦截器函数。
|
||||||
|
*/
|
||||||
|
const executeQueue = (funcs: AnyFn[], ...args: unknown[]): void => {
|
||||||
|
funcs.forEach((func) => {
|
||||||
|
if (typeof func === 'function') {
|
||||||
|
func(...args)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
/** 请求、响应前执行拦截器队列中的所有方法 */
|
/**
|
||||||
|
*
|
||||||
|
* @param key - 实例类型标识
|
||||||
|
* @param implementKey - 队列键名
|
||||||
|
* @param fetchType - 请求类型(成功/失败)
|
||||||
|
*
|
||||||
|
* @description
|
||||||
|
* 请求、响应前执行拦截器队列中的所有方法。
|
||||||
|
*/
|
||||||
const beforeFetch = (
|
const beforeFetch = (
|
||||||
key: keyof AxiosFetchInstance,
|
key: keyof AxiosFetchInstance,
|
||||||
implementKey: keyof ImplementQueue | keyof ErrorImplementQueue,
|
implementKey: ImplementKeys | ErrorImplementKeys,
|
||||||
fetchType: FetchType,
|
fetchType: FetchType,
|
||||||
) => {
|
): void => {
|
||||||
const funcArr =
|
|
||||||
fetchType === 'ok'
|
|
||||||
? implement[implementKey as keyof ImplementQueue]
|
|
||||||
: errorImplement[implementKey as keyof ErrorImplementQueue]
|
|
||||||
const instance = getAxiosInstance(key)
|
const instance = getAxiosInstance(key)
|
||||||
|
|
||||||
|
if (!instance) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const funcArr = getImplement(implementKey, fetchType)
|
||||||
const { MODE } = getAppEnvironment()
|
const { MODE } = getAppEnvironment()
|
||||||
|
|
||||||
if (instance) {
|
executeQueue(funcArr, instance, MODE)
|
||||||
implementer(funcArr, instance, MODE)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 请求、响应错误时执行队列中所有方法 */
|
/**
|
||||||
|
*
|
||||||
|
* @param key - 错误类型标识
|
||||||
|
* @param error - 错误对象
|
||||||
|
* @param errorImplementKey - 错误队列键名
|
||||||
|
*
|
||||||
|
* @description
|
||||||
|
* 请求、响应错误时执行队列中所有方法。
|
||||||
|
*/
|
||||||
const fetchError = (
|
const fetchError = (
|
||||||
key: keyof AxiosFetchError,
|
key: keyof AxiosFetchError,
|
||||||
error: AxiosError<unknown, unknown>,
|
error: AxiosError<unknown, unknown>,
|
||||||
errorImplementKey: keyof ErrorImplementQueue,
|
errorImplementKey: ErrorImplementKeys,
|
||||||
) => {
|
): void => {
|
||||||
axiosFetchError[key] = error
|
axiosFetchError[key] = error
|
||||||
|
|
||||||
const funcArr = errorImplement[errorImplementKey]
|
const funcArr = errorImplement[errorImplementKey]
|
||||||
const { MODE } = getAppEnvironment()
|
const { MODE } = getAppEnvironment()
|
||||||
|
|
||||||
implementer(funcArr, error, MODE)
|
executeQueue(funcArr, error, MODE)
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
@ -139,4 +190,4 @@ export const useAxiosInterceptor = () => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export type UseAxiosInterceptor = ReturnType<typeof useAxiosInterceptor>
|
export type AxiosInterceptor = ReturnType<typeof axiosInterceptor>
|
||||||
|
|||||||
@ -1,8 +1,22 @@
|
|||||||
|
import type { ExtractPublicPropTypes } from 'vue'
|
||||||
import RBarcode from './src/Barcode'
|
import RBarcode from './src/Barcode'
|
||||||
import barcodeProps from './src/props'
|
import barcodeProps from './src/props'
|
||||||
|
import type { RBarcodeSize } from './src/types'
|
||||||
|
|
||||||
import type { ExtractPublicPropTypes } from 'vue'
|
// 扩展 BarcodeProps 以提供更好的类型提示
|
||||||
|
export type BarcodeProps = Omit<
|
||||||
export type BarcodeProps = ExtractPublicPropTypes<typeof barcodeProps>
|
ExtractPublicPropTypes<typeof barcodeProps>,
|
||||||
|
'width' | 'height'
|
||||||
|
> & {
|
||||||
|
width?: RBarcodeSize
|
||||||
|
height?: RBarcodeSize
|
||||||
|
}
|
||||||
|
|
||||||
export { RBarcode, barcodeProps }
|
export { RBarcode, barcodeProps }
|
||||||
|
|
||||||
|
export type {
|
||||||
|
RBarcodeSize,
|
||||||
|
RBarcodeRender,
|
||||||
|
RBarcodeFormat,
|
||||||
|
RBarcodeOptions,
|
||||||
|
} from './src/types'
|
||||||
|
|||||||
@ -1,13 +1,12 @@
|
|||||||
import './index.scss'
|
import './index.scss'
|
||||||
|
import { call, completeSize } from '@/utils'
|
||||||
import { NSpin } from 'naive-ui'
|
import { useResizeObserver } from '@vueuse/core'
|
||||||
|
import type { UseResizeObserverReturn } from '@vueuse/core'
|
||||||
import barcode from 'jsbarcode'
|
import barcode from 'jsbarcode'
|
||||||
import props from './props'
|
import { NSpin } from 'naive-ui'
|
||||||
import { completeSize, call } from '@/utils'
|
|
||||||
import { useTemplateRef } from 'vue'
|
import { useTemplateRef } from 'vue'
|
||||||
|
|
||||||
import type { WatchStopHandle } from 'vue'
|
import type { WatchStopHandle } from 'vue'
|
||||||
|
import props from './props'
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
name: 'RBarcode',
|
name: 'RBarcode',
|
||||||
@ -16,10 +15,25 @@ export default defineComponent({
|
|||||||
const barcodeRef = useTemplateRef<HTMLCanvasElement | HTMLOrSVGElement>(
|
const barcodeRef = useTemplateRef<HTMLCanvasElement | HTMLOrSVGElement>(
|
||||||
'barcodeRef',
|
'barcodeRef',
|
||||||
)
|
)
|
||||||
|
const containerRef = useTemplateRef<HTMLDivElement>('containerRef')
|
||||||
|
const containerSize = ref({ width: 0, height: 0 })
|
||||||
|
let resizeObserverReturn: UseResizeObserverReturn | null
|
||||||
|
|
||||||
const cssVars = computed(() => {
|
const cssVars = computed(() => {
|
||||||
|
let width = completeSize(props.width)
|
||||||
|
let height = completeSize(props.height)
|
||||||
|
|
||||||
|
if (props.width === 'responsive' && containerSize.value.width > 0) {
|
||||||
|
width = `${containerSize.value.width}px`
|
||||||
|
}
|
||||||
|
|
||||||
|
if (props.height === 'responsive' && containerSize.value.height > 0) {
|
||||||
|
height = `${containerSize.value.height}px`
|
||||||
|
}
|
||||||
|
|
||||||
const cssVar = {
|
const cssVar = {
|
||||||
'--r-barcode-width': completeSize(props.width),
|
'--r-barcode-width': width,
|
||||||
'--r-barcode-height': completeSize(props.height),
|
'--r-barcode-height': height,
|
||||||
}
|
}
|
||||||
|
|
||||||
return cssVar
|
return cssVar
|
||||||
@ -27,6 +41,10 @@ export default defineComponent({
|
|||||||
let watchStop: WatchStopHandle
|
let watchStop: WatchStopHandle
|
||||||
|
|
||||||
const barcodeRender = () => {
|
const barcodeRender = () => {
|
||||||
|
if (!barcodeRef.value) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const { format, text, options, onSuccess } = props
|
const { format, text, options, onSuccess } = props
|
||||||
|
|
||||||
@ -34,6 +52,20 @@ export default defineComponent({
|
|||||||
format,
|
format,
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// 如果是响应式模式,根据容器尺寸调整条形码选项
|
||||||
|
if (containerSize.value.width > 0) {
|
||||||
|
if (props.width === 'responsive') {
|
||||||
|
assignOptions.width = Math.max(1, containerSize.value.width / 100)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (props.height === 'responsive') {
|
||||||
|
assignOptions.height = Math.max(
|
||||||
|
20,
|
||||||
|
containerSize.value.height * 0.8,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
barcode(
|
barcode(
|
||||||
barcodeRef.value,
|
barcodeRef.value,
|
||||||
text !== void 0 && text !== null ? text.toString() : '',
|
text !== void 0 && text !== null ? text.toString() : '',
|
||||||
@ -60,26 +92,64 @@ export default defineComponent({
|
|||||||
|
|
||||||
watchEffect(() => {
|
watchEffect(() => {
|
||||||
if (props.watchText) {
|
if (props.watchText) {
|
||||||
|
watchStop?.()
|
||||||
|
|
||||||
watchStop = watch(() => props.text, barcodeRender)
|
watchStop = watch(() => props.text, barcodeRender)
|
||||||
} else {
|
} else {
|
||||||
watchStop?.()
|
watchStop?.()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 监听容器尺寸变化
|
||||||
|
if (props.responsive) {
|
||||||
|
resizeObserverReturn?.stop()
|
||||||
|
|
||||||
|
resizeObserverReturn = useResizeObserver(
|
||||||
|
containerRef,
|
||||||
|
(entries: readonly ResizeObserverEntry[]) => {
|
||||||
|
const entry = entries[0]
|
||||||
|
|
||||||
|
if (entry) {
|
||||||
|
const { width, height } = entry.contentRect
|
||||||
|
|
||||||
|
containerSize.value = { width, height }
|
||||||
|
|
||||||
|
nextTick(() => {
|
||||||
|
barcodeRender()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
},
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
resizeObserverReturn?.stop()
|
||||||
|
|
||||||
|
resizeObserverReturn = null
|
||||||
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
|
// 初始化容器尺寸
|
||||||
|
if (containerRef.value) {
|
||||||
|
const rect = containerRef.value.getBoundingClientRect()
|
||||||
|
|
||||||
|
containerSize.value = { width: rect.width, height: rect.height }
|
||||||
|
}
|
||||||
|
|
||||||
|
nextTick(() => {
|
||||||
barcodeRender()
|
barcodeRender()
|
||||||
})
|
})
|
||||||
|
})
|
||||||
onBeforeUnmount(() => {
|
onBeforeUnmount(() => {
|
||||||
watchStop?.()
|
watchStop?.()
|
||||||
})
|
})
|
||||||
|
|
||||||
return {
|
return {
|
||||||
barcodeRef,
|
barcodeRef,
|
||||||
|
containerRef,
|
||||||
cssVars,
|
cssVars,
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
render() {
|
render() {
|
||||||
const { barcodeRender, loading, cssVars } = this
|
const { barcodeRender, loading, cssVars, responsive } = this
|
||||||
const c = [
|
const c = [
|
||||||
'r-barcode',
|
'r-barcode',
|
||||||
{
|
{
|
||||||
@ -87,12 +157,21 @@ export default defineComponent({
|
|||||||
},
|
},
|
||||||
]
|
]
|
||||||
|
|
||||||
return (
|
const barcodeElement =
|
||||||
<NSpin class="r-barcode-spin" show={loading}>
|
barcodeRender === 'canvas' ? (
|
||||||
{barcodeRender === 'canvas' ? (
|
|
||||||
<canvas class={c} style={cssVars} ref="barcodeRef" />
|
<canvas class={c} style={cssVars} ref="barcodeRef" />
|
||||||
) : (
|
) : (
|
||||||
<svg class={c} style={cssVars} ref="barcodeRef" />
|
<svg class={c} style={cssVars} ref="barcodeRef" />
|
||||||
|
)
|
||||||
|
|
||||||
|
return (
|
||||||
|
<NSpin class="r-barcode-spin" show={loading}>
|
||||||
|
{responsive ? (
|
||||||
|
<div class="r-barcode-container" ref="containerRef">
|
||||||
|
{barcodeElement}
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
barcodeElement
|
||||||
)}
|
)}
|
||||||
</NSpin>
|
</NSpin>
|
||||||
)
|
)
|
||||||
|
|||||||
@ -9,8 +9,27 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.r-barcode-container {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
|
||||||
|
.r-barcode {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.r-barcode-spin,
|
.r-barcode-spin,
|
||||||
.r-barcode-spin .n-spin-content {
|
.r-barcode-spin .n-spin-content {
|
||||||
width: max-content !important;
|
width: max-content !important;
|
||||||
height: max-content !important;
|
height: max-content !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.r-barcode-spin:has(.r-barcode-container),
|
||||||
|
.r-barcode-spin:has(.r-barcode-container) .n-spin-content {
|
||||||
|
width: 100% !important;
|
||||||
|
height: 100% !important;
|
||||||
|
}
|
||||||
|
|||||||
@ -1,29 +1,50 @@
|
|||||||
import type { RBarcodeRender, RBarcodeOptions, RBarcodeFormat } from './types'
|
|
||||||
import type { PropType } from 'vue'
|
|
||||||
import type { MaybeArray } from '@/types'
|
import type { MaybeArray } from '@/types'
|
||||||
|
import type { PropType } from 'vue'
|
||||||
|
import type {
|
||||||
|
RBarcodeFormat,
|
||||||
|
RBarcodeOptions,
|
||||||
|
RBarcodeRender,
|
||||||
|
RBarcodeSize,
|
||||||
|
} from './types'
|
||||||
|
|
||||||
const props = {
|
const props = {
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* @description
|
* @description
|
||||||
* 条形码宽度。
|
* 条形码宽度。
|
||||||
|
* - 数字:固定宽度(单位:px)
|
||||||
|
* - 'auto':自动宽度
|
||||||
|
* - 其他字符串:CSS 宽度值(如 '100%', '200px')
|
||||||
*
|
*
|
||||||
* @default 'auto'
|
* @default 'auto'
|
||||||
|
* @example
|
||||||
|
* width={200}
|
||||||
|
* width="auto"
|
||||||
|
* width="responsive"
|
||||||
|
* width="100%"
|
||||||
*/
|
*/
|
||||||
width: {
|
width: {
|
||||||
type: [String, Number] as PropType<string | number>,
|
type: [String, Number] as PropType<RBarcodeSize>,
|
||||||
default: 'auto',
|
default: 'auto' as const,
|
||||||
},
|
},
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* @description
|
* @description
|
||||||
* 条形码高度。
|
* 条形码高度。
|
||||||
|
* - 数字:固定高度(单位:px)
|
||||||
|
* - 'auto':自动高度
|
||||||
|
* - 其他字符串:CSS 高度值(如 '100%', '200px')
|
||||||
*
|
*
|
||||||
* @default 'auto'
|
* @default 'auto'
|
||||||
|
* @example
|
||||||
|
* height={100}
|
||||||
|
* height="auto"
|
||||||
|
* height="responsive"
|
||||||
|
* height="50%"
|
||||||
*/
|
*/
|
||||||
height: {
|
height: {
|
||||||
type: [String, Number] as PropType<string | number>,
|
type: [String, Number] as PropType<RBarcodeSize>,
|
||||||
default: 'auto',
|
default: 'auto' as const,
|
||||||
},
|
},
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
@ -132,6 +153,19 @@ const props = {
|
|||||||
onFinally: {
|
onFinally: {
|
||||||
type: [Function, Array] as PropType<MaybeArray<() => void>>,
|
type: [Function, Array] as PropType<MaybeArray<() => void>>,
|
||||||
},
|
},
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @description
|
||||||
|
* 是否启用响应式尺寸,当容器大小变化时自动重新渲染条形码。
|
||||||
|
*
|
||||||
|
* 如果启用了该属性,width 和 height 配置项将失效。
|
||||||
|
*
|
||||||
|
* @default false
|
||||||
|
*/
|
||||||
|
responsive: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false,
|
||||||
|
},
|
||||||
} as const
|
} as const
|
||||||
|
|
||||||
export default props
|
export default props
|
||||||
|
|||||||
@ -24,3 +24,6 @@ export type RBarcodeFormat =
|
|||||||
| 'MSI1110'
|
| 'MSI1110'
|
||||||
| 'pharmacode'
|
| 'pharmacode'
|
||||||
| 'codabar'
|
| 'codabar'
|
||||||
|
|
||||||
|
// 使用模板字面量类型来保留字面量提示
|
||||||
|
export type RBarcodeSize = number | 'auto' | (string & {})
|
||||||
|
|||||||
@ -1,10 +1,9 @@
|
|||||||
import RChart from './src'
|
|
||||||
import chartProps from './src/props'
|
|
||||||
import useChart from './src/hooks/useChart'
|
|
||||||
|
|
||||||
import type { ExtractPublicPropTypes } from 'vue'
|
import type { ExtractPublicPropTypes } from 'vue'
|
||||||
import type * as RChartType from './src/types'
|
import RChart from './src/Chart'
|
||||||
|
import useChart from './src/hooks/useChart'
|
||||||
import type { UseChartReturn } from './src/hooks/useChart'
|
import type { UseChartReturn } from './src/hooks/useChart'
|
||||||
|
import chartProps from './src/props'
|
||||||
|
import type * as RChartType from './src/types'
|
||||||
|
|
||||||
export type ChartProps = ExtractPublicPropTypes<typeof chartProps>
|
export type ChartProps = ExtractPublicPropTypes<typeof chartProps>
|
||||||
export type { RChartType, UseChartReturn }
|
export type { RChartType, UseChartReturn }
|
||||||
|
|||||||
@ -1,51 +1,53 @@
|
|||||||
import './index.scss'
|
import './index.scss'
|
||||||
|
|
||||||
import { use, registerTheme, init } from 'echarts/core' // echarts 核心模块
|
|
||||||
import {
|
|
||||||
TitleComponent,
|
|
||||||
TooltipComponent,
|
|
||||||
GridComponent,
|
|
||||||
DatasetComponent,
|
|
||||||
TransformComponent,
|
|
||||||
LegendComponent,
|
|
||||||
ToolboxComponent,
|
|
||||||
AriaComponent,
|
|
||||||
} from 'echarts/components' // 提示框, 标题, 直角坐标系, 数据集, 内置数据转换器等组件(组件后缀都为 Component)
|
|
||||||
import {
|
|
||||||
BarChart,
|
|
||||||
LineChart,
|
|
||||||
PieChart,
|
|
||||||
CandlestickChart,
|
|
||||||
ScatterChart,
|
|
||||||
PictorialBarChart,
|
|
||||||
} from 'echarts/charts' // 系列类型(后缀都为 SeriesOption)
|
|
||||||
import { LabelLayout, UniversalTransition } from 'echarts/features' // 标签自动布局, 全局过渡动画等特性
|
|
||||||
import { CanvasRenderer } from 'echarts/renderers' // echarts 渲染器
|
|
||||||
import { NCard } from 'naive-ui'
|
|
||||||
|
|
||||||
import props from './props'
|
|
||||||
import { throttle } from 'lodash-es'
|
|
||||||
import { completeSize, downloadBase64File, call, renderNode } from '@/utils'
|
|
||||||
import { getCustomEchartTheme, loadingOptions, setEchartOptions } from './utils'
|
|
||||||
import { APP_THEME } from '@/app-config'
|
import { APP_THEME } from '@/app-config'
|
||||||
import {
|
|
||||||
useResizeObserver,
|
|
||||||
useIntersectionObserver,
|
|
||||||
watchThrottled,
|
|
||||||
} from '@vueuse/core'
|
|
||||||
import { RMoreDropdown } from '@/components'
|
import { RMoreDropdown } from '@/components'
|
||||||
import { useSettingGetters } from '@/store'
|
import { useSettingGetters } from '@/store'
|
||||||
import { useTemplateRef } from 'vue'
|
import type { AnyFn } from '@/types'
|
||||||
|
import { call, completeSize, downloadBase64File, renderNode } from '@/utils'
|
||||||
import type { WatchStopHandle } from 'vue'
|
import {
|
||||||
import type { AnyFC } from '@/types'
|
useIntersectionObserver,
|
||||||
import type { DebouncedFunc } from 'lodash-es'
|
useResizeObserver,
|
||||||
import type {
|
watchThrottled,
|
||||||
UseResizeObserverReturn,
|
|
||||||
UseIntersectionObserverReturn,
|
|
||||||
} from '@vueuse/core'
|
} from '@vueuse/core'
|
||||||
|
import type {
|
||||||
|
UseIntersectionObserverReturn,
|
||||||
|
UseResizeObserverReturn,
|
||||||
|
} from '@vueuse/core'
|
||||||
|
// 提示框, 标题, 直角坐标系, 数据集, 内置数据转换器等组件(组件后缀都为 Component)
|
||||||
|
import {
|
||||||
|
BarChart,
|
||||||
|
CandlestickChart,
|
||||||
|
LineChart,
|
||||||
|
PictorialBarChart,
|
||||||
|
PieChart,
|
||||||
|
ScatterChart,
|
||||||
|
} from 'echarts/charts'
|
||||||
|
import {
|
||||||
|
AriaComponent,
|
||||||
|
DatasetComponent,
|
||||||
|
GridComponent,
|
||||||
|
LegendComponent,
|
||||||
|
TitleComponent,
|
||||||
|
ToolboxComponent,
|
||||||
|
TooltipComponent,
|
||||||
|
TransformComponent,
|
||||||
|
} from 'echarts/components'
|
||||||
|
import { init, registerTheme, use } from 'echarts/core' // echarts 核心模块
|
||||||
|
|
||||||
import type { ECharts, EChartsCoreOption } from 'echarts/core'
|
import type { ECharts, EChartsCoreOption } from 'echarts/core'
|
||||||
import type { DropdownProps, DropdownOption } from 'naive-ui'
|
// 系列类型(后缀都为 SeriesOption)
|
||||||
|
import { LegacyGridContainLabel, UniversalTransition } from 'echarts/features' // 标签自动布局, 全局过渡动画等特性
|
||||||
|
import { CanvasRenderer } from 'echarts/renderers' // echarts 渲染器
|
||||||
|
|
||||||
|
import { throttle } from 'lodash-es'
|
||||||
|
import type { DebouncedFunc } from 'lodash-es'
|
||||||
|
import { NCard } from 'naive-ui'
|
||||||
|
import type { DropdownOption, DropdownProps } from 'naive-ui'
|
||||||
|
import { useTemplateRef } from 'vue'
|
||||||
|
import type { WatchStopHandle } from 'vue'
|
||||||
|
import { USE_CHART_PROVIDER_KEY } from './config'
|
||||||
|
import props from './props'
|
||||||
|
import { getCustomEchartTheme, loadingOptions, setEchartOptions } from './utils'
|
||||||
|
|
||||||
// 获取 chart 主题
|
// 获取 chart 主题
|
||||||
const echartThemes = getCustomEchartTheme()
|
const echartThemes = getCustomEchartTheme()
|
||||||
@ -87,13 +89,13 @@ export default defineComponent({
|
|||||||
setup(props, { expose }) {
|
setup(props, { expose }) {
|
||||||
const { getAppTheme } = useSettingGetters()
|
const { getAppTheme } = useSettingGetters()
|
||||||
// echart 容器实例
|
// echart 容器实例
|
||||||
const rayChartRef = useTemplateRef<HTMLElement>('rayChartRef')
|
const chartRef = useTemplateRef<HTMLElement>('chartRef')
|
||||||
// echart 父容器实例
|
// echart 父容器实例
|
||||||
const rayChartWrapperRef = useTemplateRef<HTMLElement>('rayChartWrapperRef')
|
const rayChartWrapperRef = useTemplateRef<HTMLElement>('rayChartWrapperRef')
|
||||||
// echart 实例
|
// echart 实例
|
||||||
const echartInstanceRef = shallowRef<ECharts>()
|
const echartInstanceRef = shallowRef<ECharts>()
|
||||||
// resize 防抖方法实例
|
// resize 防抖方法实例
|
||||||
let resizeThrottleReturn: DebouncedFunc<AnyFC> | null
|
let resizeThrottleReturn: DebouncedFunc<AnyFn> | null
|
||||||
// resize observer 实例
|
// resize observer 实例
|
||||||
let resizeObserverReturn: UseResizeObserverReturn | null
|
let resizeObserverReturn: UseResizeObserverReturn | null
|
||||||
// 当前配置主题
|
// 当前配置主题
|
||||||
@ -112,8 +114,8 @@ export default defineComponent({
|
|||||||
])
|
])
|
||||||
const cssVarsRef = computed(() => {
|
const cssVarsRef = computed(() => {
|
||||||
return {
|
return {
|
||||||
'--ray-chart-width': completeSize(props.width),
|
'--r-chart-width': completeSize(props.width),
|
||||||
'--ray-chart-height': completeSize(props.height),
|
'--r-chart-height': completeSize(props.height),
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
// 目标是否可见
|
// 目标是否可见
|
||||||
@ -124,6 +126,7 @@ export default defineComponent({
|
|||||||
const __catch = {
|
const __catch = {
|
||||||
aria: props.showAria,
|
aria: props.showAria,
|
||||||
}
|
}
|
||||||
|
const chartProvideOptions = inject(USE_CHART_PROVIDER_KEY, {})
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
@ -152,7 +155,7 @@ export default defineComponent({
|
|||||||
ScatterChart,
|
ScatterChart,
|
||||||
PictorialBarChart,
|
PictorialBarChart,
|
||||||
]) // 注册 chart series type
|
]) // 注册 chart series type
|
||||||
use([LabelLayout, UniversalTransition]) // 注册布局, 过度效果
|
use([LegacyGridContainLabel, UniversalTransition]) // 注册布局, 过度效果
|
||||||
use([CanvasRenderer]) // 注册渲染器
|
use([CanvasRenderer]) // 注册渲染器
|
||||||
|
|
||||||
try {
|
try {
|
||||||
@ -174,10 +177,19 @@ export default defineComponent({
|
|||||||
* 但是,如果未获取到 echartTheme 属性,则会使用默认样式。
|
* 但是,如果未获取到 echartTheme 属性,则会使用默认样式。
|
||||||
*/
|
*/
|
||||||
const updateChartTheme = () => {
|
const updateChartTheme = () => {
|
||||||
|
const { theme: providerTheme } = chartProvideOptions || {}
|
||||||
|
|
||||||
if (echartInstanceRef.value) {
|
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('')
|
||||||
|
|
||||||
@ -230,7 +242,7 @@ export default defineComponent({
|
|||||||
*/
|
*/
|
||||||
const renderChart = (theme: string = echartTheme) => {
|
const renderChart = (theme: string = echartTheme) => {
|
||||||
// 获取 dom 容器
|
// 获取 dom 容器
|
||||||
const element = rayChartRef.value as HTMLElement
|
const element = chartRef.value as HTMLElement
|
||||||
// 获取配置项
|
// 获取配置项
|
||||||
const options = combineChartOptions(props.options)
|
const options = combineChartOptions(props.options)
|
||||||
// 获取 dom 容器实际宽高
|
// 获取 dom 容器实际宽高
|
||||||
@ -253,7 +265,7 @@ export default defineComponent({
|
|||||||
|
|
||||||
// 是否强制下一队列渲染图表
|
// 是否强制下一队列渲染图表
|
||||||
if (props.nextTick) {
|
if (props.nextTick) {
|
||||||
echartInstanceRef.value.setOption({})
|
// echartInstanceRef.value.setOption({})
|
||||||
|
|
||||||
nextTick(() => {
|
nextTick(() => {
|
||||||
options && echartInstanceRef.value?.setOption(options)
|
options && echartInstanceRef.value?.setOption(options)
|
||||||
@ -355,7 +367,7 @@ export default defineComponent({
|
|||||||
if (!resizeObserverReturn) {
|
if (!resizeObserverReturn) {
|
||||||
resizeObserverReturn = useResizeObserver(
|
resizeObserverReturn = useResizeObserver(
|
||||||
props.autoResizeObserverTarget || rayChartWrapperRef,
|
props.autoResizeObserverTarget || rayChartWrapperRef,
|
||||||
resizeThrottleReturn as AnyFC,
|
resizeThrottleReturn as AnyFn,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -424,6 +436,8 @@ export default defineComponent({
|
|||||||
watchEffect(() => {
|
watchEffect(() => {
|
||||||
// 是否启用了可视区域监听
|
// 是否启用了可视区域监听
|
||||||
if (props.intersectionObserver) {
|
if (props.intersectionObserver) {
|
||||||
|
intersectionObserverReturn?.stop()
|
||||||
|
|
||||||
intersectionObserverReturn = useIntersectionObserver(
|
intersectionObserverReturn = useIntersectionObserver(
|
||||||
props.intersectionObserverTarget || rayChartWrapperRef,
|
props.intersectionObserverTarget || rayChartWrapperRef,
|
||||||
([entry]) => {
|
([entry]) => {
|
||||||
@ -431,10 +445,14 @@ export default defineComponent({
|
|||||||
},
|
},
|
||||||
props.intersectionOptions,
|
props.intersectionOptions,
|
||||||
)
|
)
|
||||||
|
} else {
|
||||||
|
intersectionObserverReturn?.stop()
|
||||||
}
|
}
|
||||||
|
|
||||||
// 监听 options 变化
|
// 监听 options 变化
|
||||||
if (props.watchOptions) {
|
if (props.watchOptions) {
|
||||||
|
watchThrottledCallback?.()
|
||||||
|
|
||||||
watchThrottledCallback = watchThrottled(
|
watchThrottledCallback = watchThrottled(
|
||||||
() => props.options,
|
() => props.options,
|
||||||
(ndata) => {
|
(ndata) => {
|
||||||
@ -451,7 +469,7 @@ export default defineComponent({
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
// 深度监听 options
|
// 深度监听 options
|
||||||
deep: true,
|
deep: props.watchDeep,
|
||||||
throttle: props.watchOptionsThrottleWait,
|
throttle: props.watchOptionsThrottleWait,
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
@ -495,7 +513,7 @@ export default defineComponent({
|
|||||||
})
|
})
|
||||||
|
|
||||||
return {
|
return {
|
||||||
rayChartRef,
|
chartRef,
|
||||||
cssVarsRef,
|
cssVarsRef,
|
||||||
rayChartWrapperRef,
|
rayChartWrapperRef,
|
||||||
moreDropDownOptions,
|
moreDropDownOptions,
|
||||||
@ -525,7 +543,7 @@ export default defineComponent({
|
|||||||
>
|
>
|
||||||
{{
|
{{
|
||||||
default: renderNode(
|
default: renderNode(
|
||||||
<div class="ray-chart__container" ref="rayChartRef"></div>,
|
<div class="ray-chart__container" ref="chartRef"></div>,
|
||||||
),
|
),
|
||||||
header: renderNode(title, {
|
header: renderNode(title, {
|
||||||
defaultElement: <div style="display: none;"></div>,
|
defaultElement: <div style="display: none;"></div>,
|
||||||
@ -546,7 +564,7 @@ export default defineComponent({
|
|||||||
</NCard>
|
</NCard>
|
||||||
) : (
|
) : (
|
||||||
<div class="ray-chart" style={[this.cssVarsRef]} ref="rayChartWrapperRef">
|
<div class="ray-chart" style={[this.cssVarsRef]} ref="rayChartWrapperRef">
|
||||||
<div class="ray-chart__container" ref="rayChartRef"></div>
|
<div class="ray-chart__container" ref="chartRef"></div>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
6
src/components/base/RChart/src/config.ts
Normal file
6
src/components/base/RChart/src/config.ts
Normal 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')
|
||||||
@ -1,5 +1,5 @@
|
|||||||
import type { ECharts } from 'echarts/core'
|
|
||||||
import type { VoidFC } from '@/types'
|
import type { VoidFC } from '@/types'
|
||||||
|
import type { ECharts } from 'echarts/core'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
|
|||||||
17
src/components/base/RChart/src/hooks/useChartProvider.ts
Normal file
17
src/components/base/RChart/src/hooks/useChartProvider.ts
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
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)
|
||||||
|
}
|
||||||
@ -1,6 +1,6 @@
|
|||||||
.ray-chart {
|
.ray-chart {
|
||||||
width: var(--ray-chart-width);
|
width: var(--r-chart-width);
|
||||||
height: var(--ray-chart-height);
|
height: var(--r-chart-height);
|
||||||
border: none;
|
border: none;
|
||||||
outline: none;
|
outline: none;
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
|
|||||||
@ -1,23 +1,22 @@
|
|||||||
import { loadingOptions, setEchartOptions } from './utils'
|
import type { MaybeArray, VoidFC } from '@/types'
|
||||||
|
|
||||||
import type * as echarts from 'echarts/core' // echarts 核心模块
|
|
||||||
import type { PropType, VNode } from 'vue'
|
|
||||||
import type { MaybeArray } from '@/types'
|
|
||||||
import type { ECharts, SetOptionOpts } from 'echarts/core'
|
|
||||||
import type {
|
import type {
|
||||||
MaybeComputedElementRef,
|
MaybeComputedElementRef,
|
||||||
MaybeElement,
|
MaybeElement,
|
||||||
UseIntersectionObserverOptions,
|
UseIntersectionObserverOptions,
|
||||||
} from '@vueuse/core'
|
} from '@vueuse/core'
|
||||||
|
import type * as echarts from 'echarts/core' // echarts 核心模块
|
||||||
|
|
||||||
|
import type { ECharts, SetOptionOpts } from 'echarts/core'
|
||||||
|
import type { CardProps, DropdownOption, DropdownProps } from 'naive-ui'
|
||||||
|
import type { PropType, VNode } from 'vue'
|
||||||
import type {
|
import type {
|
||||||
LoadingOptions,
|
|
||||||
ChartTheme,
|
ChartTheme,
|
||||||
EChartsExtensionInstallRegisters,
|
EChartsExtensionInstallRegisters,
|
||||||
RChartPresetType,
|
LoadingOptions,
|
||||||
RChartDownloadOptions,
|
RChartDownloadOptions,
|
||||||
|
RChartPresetType,
|
||||||
} from './types'
|
} from './types'
|
||||||
import type { CardProps, DropdownProps, DropdownOption } from 'naive-ui'
|
import { loadingOptions, setEchartOptions } from './utils'
|
||||||
import type { VoidFC } from '@/types'
|
|
||||||
|
|
||||||
const props = {
|
const props = {
|
||||||
/**
|
/**
|
||||||
@ -341,6 +340,17 @@ const props = {
|
|||||||
type: Number,
|
type: Number,
|
||||||
default: 500,
|
default: 500,
|
||||||
},
|
},
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @description
|
||||||
|
* 是否深度监听 options 配置项。
|
||||||
|
*
|
||||||
|
* @default true
|
||||||
|
*/
|
||||||
|
watchDeep: {
|
||||||
|
type: Boolean,
|
||||||
|
default: true,
|
||||||
|
},
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* @description
|
* @description
|
||||||
|
|||||||
@ -13,12 +13,12 @@ import type { ECharts } from 'echarts/core'
|
|||||||
import type { CanvasRenderer } from 'echarts/renderers' // `echarts` 渲染器
|
import type { CanvasRenderer } from 'echarts/renderers' // `echarts` 渲染器
|
||||||
|
|
||||||
export interface ChartThemeRawModules {
|
export interface ChartThemeRawModules {
|
||||||
default: Record<string, UnknownObjectKey>
|
default: Record<string, GlobalRecordable>
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ChartThemeRawArray {
|
export interface ChartThemeRawArray {
|
||||||
name: string
|
name: string
|
||||||
theme: UnknownObjectKey
|
theme: GlobalRecordable
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface LoadingOptions {
|
export interface LoadingOptions {
|
||||||
|
|||||||
@ -1,6 +1,5 @@
|
|||||||
import { useTheme } from '@/hooks'
|
|
||||||
|
|
||||||
import type { LoadingOptions } from '@/components/base/RChart/src/types'
|
import type { LoadingOptions } from '@/components/base/RChart/src/types'
|
||||||
|
import { useTheme } from '@/hooks'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
|
|||||||
@ -1,8 +1,7 @@
|
|||||||
|
import type { ExtractPublicPropTypes } from 'vue'
|
||||||
import RCollapseGrid from './src'
|
import RCollapseGrid from './src'
|
||||||
import collapseGridProps from './src/props'
|
import collapseGridProps from './src/props'
|
||||||
|
|
||||||
import type * as RCollapseGridType from './src/types'
|
import type * as RCollapseGridType from './src/types'
|
||||||
import type { ExtractPublicPropTypes } from 'vue'
|
|
||||||
|
|
||||||
export type CollapseGridProps = ExtractPublicPropTypes<typeof collapseGridProps>
|
export type CollapseGridProps = ExtractPublicPropTypes<typeof collapseGridProps>
|
||||||
export type { RCollapseGridType }
|
export type { RCollapseGridType }
|
||||||
|
|||||||
@ -9,14 +9,11 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import './index.scss'
|
import './index.scss'
|
||||||
|
|
||||||
import { NCard, NGrid, NGridItem, NFlex } from 'naive-ui'
|
|
||||||
import { RIcon } from '@/components'
|
import { RIcon } from '@/components'
|
||||||
|
|
||||||
import { call } from '@/utils'
|
import { call } from '@/utils'
|
||||||
import props from './props'
|
import { NCard, NFlex, NGrid, NGridItem } from 'naive-ui'
|
||||||
|
|
||||||
import type { GridProps } from 'naive-ui'
|
import type { GridProps } from 'naive-ui'
|
||||||
|
import props from './props'
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
name: 'RCollapseGrid',
|
name: 'RCollapseGrid',
|
||||||
@ -81,6 +78,7 @@ export default defineComponent({
|
|||||||
yGap,
|
yGap,
|
||||||
collapsedRows,
|
collapsedRows,
|
||||||
cssVars,
|
cssVars,
|
||||||
|
actionSpan,
|
||||||
bordered,
|
bordered,
|
||||||
} = this
|
} = this
|
||||||
|
|
||||||
@ -97,7 +95,11 @@ export default defineComponent({
|
|||||||
collapsedRows={collapsedRows}
|
collapsedRows={collapsedRows}
|
||||||
>
|
>
|
||||||
{defaultSlot?.()}
|
{defaultSlot?.()}
|
||||||
<NGridItem suffix class="ray-collapse-grid__suffix--btn">
|
<NGridItem
|
||||||
|
suffix
|
||||||
|
class="ray-collapse-grid__suffix--btn"
|
||||||
|
span={actionSpan}
|
||||||
|
>
|
||||||
<NFlex justify="end" align="center">
|
<NFlex justify="end" align="center">
|
||||||
{action?.()}
|
{action?.()}
|
||||||
{collapse
|
{collapse
|
||||||
|
|||||||
@ -1,10 +1,20 @@
|
|||||||
|
import type { AnyFn, MaybeArray } from '@/types'
|
||||||
import { gridProps } from 'naive-ui'
|
import { gridProps } from 'naive-ui'
|
||||||
|
|
||||||
import type { PropType } from 'vue'
|
import type { PropType } from 'vue'
|
||||||
import type { CollapseToggleText, ActionAlignType } from './types'
|
import type { ActionAlignType, CollapseToggleText } from './types'
|
||||||
import type { AnyFC, MaybeArray } from '@/types'
|
|
||||||
|
|
||||||
const props = {
|
const props = {
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @description
|
||||||
|
* 操作区域列数。
|
||||||
|
*
|
||||||
|
* @default 1
|
||||||
|
*/
|
||||||
|
actionSpan: {
|
||||||
|
type: Number,
|
||||||
|
default: 1,
|
||||||
|
},
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* @description
|
* @description
|
||||||
|
|||||||
384
src/components/base/RDraggableCard/DraggableCard.tsx
Normal file
384
src/components/base/RDraggableCard/DraggableCard.tsx
Normal file
@ -0,0 +1,384 @@
|
|||||||
|
import './index.scss'
|
||||||
|
import type { AnyFn } from '@/types'
|
||||||
|
import { completeSize, queryElements, unrefElement } from '@/utils'
|
||||||
|
import type { MaybeElement, MaybeRefOrGetter } from '@vueuse/core'
|
||||||
|
import interact from 'interactjs'
|
||||||
|
import { cardProps, NCard } from 'naive-ui'
|
||||||
|
import { Teleport, Transition } from 'vue'
|
||||||
|
import type { VNode } from 'vue'
|
||||||
|
|
||||||
|
type RestrictRectOptions = Parameters<typeof interact.modifiers.restrictRect>[0]
|
||||||
|
|
||||||
|
type Padding = {
|
||||||
|
x: number
|
||||||
|
y: number
|
||||||
|
}
|
||||||
|
|
||||||
|
export type DefaultPosition =
|
||||||
|
| Padding
|
||||||
|
| 'top-left'
|
||||||
|
| 'top-right'
|
||||||
|
| 'bottom-left'
|
||||||
|
| 'bottom-right'
|
||||||
|
| 'center'
|
||||||
|
| 'top-center'
|
||||||
|
| 'bottom-center'
|
||||||
|
|
||||||
|
const props = {
|
||||||
|
...cardProps,
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @description
|
||||||
|
* 需要限制的区域位置。
|
||||||
|
*
|
||||||
|
* @default body
|
||||||
|
*/
|
||||||
|
restrictionElement: {
|
||||||
|
type: [String, HTMLElement, Function, Object] as PropType<
|
||||||
|
string | HTMLElement | (() => VNode) | MaybeRefOrGetter<MaybeElement>
|
||||||
|
>,
|
||||||
|
default: 'body',
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @description
|
||||||
|
* 是否启用拖拽。
|
||||||
|
*
|
||||||
|
* @default true
|
||||||
|
*/
|
||||||
|
dad: {
|
||||||
|
type: Boolean,
|
||||||
|
default: true,
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @description
|
||||||
|
* 自定义限制拖拽范围。
|
||||||
|
*
|
||||||
|
* @default undefined
|
||||||
|
*/
|
||||||
|
restrictRectOptions: {
|
||||||
|
type: Object as PropType<RestrictRectOptions>,
|
||||||
|
default: void 0,
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @description
|
||||||
|
* 默认位置。
|
||||||
|
*
|
||||||
|
* @default { x: 0, y: 0 }
|
||||||
|
*/
|
||||||
|
defaultPosition: {
|
||||||
|
type: [Object, String] as PropType<DefaultPosition>,
|
||||||
|
default: () => ({
|
||||||
|
x: 0,
|
||||||
|
y: 0,
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @description
|
||||||
|
* 拖拽卡片宽度。
|
||||||
|
*
|
||||||
|
* @default 600
|
||||||
|
*/
|
||||||
|
width: {
|
||||||
|
type: [String, Number] as PropType<string | number>,
|
||||||
|
default: 600,
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @description
|
||||||
|
* 拖拽卡片 z-index。
|
||||||
|
*
|
||||||
|
* @default undefined
|
||||||
|
*/
|
||||||
|
zIndex: {
|
||||||
|
type: Number,
|
||||||
|
default: void 0,
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @description
|
||||||
|
* 是否启用动画。
|
||||||
|
*
|
||||||
|
* @default false
|
||||||
|
*/
|
||||||
|
animation: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false,
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @description
|
||||||
|
* 默认的边距。
|
||||||
|
* 设置该属性后,卡片首次出现的位置会根据该属性进行偏移。
|
||||||
|
*
|
||||||
|
* @default undefined
|
||||||
|
*/
|
||||||
|
padding: {
|
||||||
|
type: Object as PropType<Padding>,
|
||||||
|
default: void 0,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
export default defineComponent({
|
||||||
|
name: 'RDraggableCard',
|
||||||
|
props,
|
||||||
|
setup(props, { expose }) {
|
||||||
|
const cardRef = useTemplateRef<HTMLElement>('cardRef')
|
||||||
|
let interactInst: ReturnType<typeof interact> | null = null
|
||||||
|
const position = {
|
||||||
|
x: 0,
|
||||||
|
y: 0,
|
||||||
|
}
|
||||||
|
const CONTAINER_ID = 'r-draggable-card-container'
|
||||||
|
const cssVars = computed(() => {
|
||||||
|
return {
|
||||||
|
'--r-draggable-card-width': completeSize(props.width),
|
||||||
|
'--r-draggable-card-z-index': props.zIndex,
|
||||||
|
}
|
||||||
|
})
|
||||||
|
let isSetup = false
|
||||||
|
const cacheProps = {
|
||||||
|
defaultPosition: props.defaultPosition,
|
||||||
|
dad: props.dad,
|
||||||
|
}
|
||||||
|
|
||||||
|
// 创建 DraggableCard 容器
|
||||||
|
const createDraggableCardContainer = () => {
|
||||||
|
if (!document.getElementById(CONTAINER_ID)) {
|
||||||
|
const container = document.createElement('div')
|
||||||
|
|
||||||
|
container.id = CONTAINER_ID
|
||||||
|
document.documentElement.appendChild(container)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
createDraggableCardContainer()
|
||||||
|
|
||||||
|
// 获取 card, restrictionElement 的 dom 信息
|
||||||
|
const getDom = () => {
|
||||||
|
const card = unrefElement(cardRef)
|
||||||
|
const re =
|
||||||
|
typeof props.restrictionElement === 'string'
|
||||||
|
? queryElements<HTMLElement>(props.restrictionElement)
|
||||||
|
: props.restrictionElement
|
||||||
|
let restrictionElement: HTMLElement | null = null
|
||||||
|
|
||||||
|
if (Array.isArray(re)) {
|
||||||
|
restrictionElement = re[0]
|
||||||
|
} else {
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
|
restrictionElement = unrefElement<HTMLElement>(re as any) as HTMLElement
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
card,
|
||||||
|
restrictionElement,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取 container, card 的位置
|
||||||
|
const getPosition = (containerRect: DOMRect, cardRect: DOMRect) => {
|
||||||
|
const { defaultPosition, padding } = props
|
||||||
|
const { x: paddingX = 0, y: paddingY = 0 } = padding ?? {}
|
||||||
|
// 默认的 body restrictionElement 的偏移量是 0
|
||||||
|
const {
|
||||||
|
x: containerX,
|
||||||
|
y: containerY,
|
||||||
|
width: containerWidth,
|
||||||
|
height: containerHeight,
|
||||||
|
} = containerRect
|
||||||
|
const { width: cardWidth, height: cardHeight } = cardRect
|
||||||
|
|
||||||
|
if (typeof defaultPosition === 'string') {
|
||||||
|
switch (defaultPosition) {
|
||||||
|
case 'top-center': {
|
||||||
|
const cx1 = (containerWidth - cardWidth) / 2 + containerX
|
||||||
|
const cy1 = paddingY + containerY
|
||||||
|
const cx2 = paddingX + cx1
|
||||||
|
const cy2 = cy1
|
||||||
|
|
||||||
|
return { x: cx2, y: cy2 }
|
||||||
|
}
|
||||||
|
|
||||||
|
case 'bottom-center': {
|
||||||
|
const cx1 = (containerWidth - cardWidth) / 2 + containerX
|
||||||
|
const cy1 = containerHeight - cardHeight - paddingY + containerY
|
||||||
|
const cx2 = paddingX + cx1
|
||||||
|
const cy2 = cy1
|
||||||
|
|
||||||
|
return { x: cx2, y: cy2 }
|
||||||
|
}
|
||||||
|
|
||||||
|
case 'center': {
|
||||||
|
const cx1 = (containerWidth - cardWidth) / 2 + containerX
|
||||||
|
const cy1 = (containerHeight - cardHeight) / 2 + containerY
|
||||||
|
const cx2 = paddingX + cx1
|
||||||
|
const cy2 = paddingY + cy1
|
||||||
|
|
||||||
|
return { x: cx2, y: cy2 }
|
||||||
|
}
|
||||||
|
|
||||||
|
case 'top-left':
|
||||||
|
return { x: paddingX + containerX, y: paddingY + containerY }
|
||||||
|
|
||||||
|
case 'top-right':
|
||||||
|
return {
|
||||||
|
x: containerWidth - cardWidth - paddingX + containerX,
|
||||||
|
y: paddingY + containerY,
|
||||||
|
}
|
||||||
|
|
||||||
|
case 'bottom-left':
|
||||||
|
return {
|
||||||
|
x: paddingX + containerX,
|
||||||
|
y: containerHeight - cardHeight - paddingY + containerY,
|
||||||
|
}
|
||||||
|
|
||||||
|
case 'bottom-right':
|
||||||
|
return {
|
||||||
|
x: containerWidth - cardWidth - paddingX + containerX,
|
||||||
|
y: containerHeight - cardHeight - paddingY + containerY,
|
||||||
|
}
|
||||||
|
|
||||||
|
// 默认为左上角
|
||||||
|
default:
|
||||||
|
return { x: paddingX + containerX, y: paddingY + containerY }
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
const { x: defaultX, y: defaultY } = defaultPosition
|
||||||
|
|
||||||
|
return {
|
||||||
|
x: defaultX + containerX + paddingX,
|
||||||
|
y: defaultY + containerY + paddingY,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 初始化设置 card 的位置,并且根据配置启用拖拽
|
||||||
|
const setupDraggable = () => {
|
||||||
|
const { card, restrictionElement } = getDom()
|
||||||
|
|
||||||
|
if (!card) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const restrictionRect = restrictionElement?.getBoundingClientRect()
|
||||||
|
const cardHeader = card.querySelector('.n-card-header')
|
||||||
|
const restrictRectOptions = Object.assign(
|
||||||
|
{},
|
||||||
|
{
|
||||||
|
restriction: restrictionElement,
|
||||||
|
endOnly: true,
|
||||||
|
},
|
||||||
|
props.restrictRectOptions,
|
||||||
|
)
|
||||||
|
|
||||||
|
if (restrictionRect && !isSetup) {
|
||||||
|
// 计算偏移位置
|
||||||
|
const p = getPosition(restrictionRect, card.getBoundingClientRect())
|
||||||
|
|
||||||
|
// 设置初始位置
|
||||||
|
card.style.transform = `translate(${p.x}px, ${p.y}px)`
|
||||||
|
position.x = p.x
|
||||||
|
position.y = p.y
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!props.dad) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
interactInst = interact(card)
|
||||||
|
.draggable({
|
||||||
|
inertia: true,
|
||||||
|
autoScroll: true,
|
||||||
|
allowFrom: cardHeader ? '.n-card-header' : '.n-card__content',
|
||||||
|
modifiers: [interact.modifiers.restrictRect(restrictRectOptions)],
|
||||||
|
listeners: {
|
||||||
|
move: (event) => {
|
||||||
|
card.setAttribute('can-drag', 'true')
|
||||||
|
|
||||||
|
position.x += event.dx
|
||||||
|
position.y += event.dy
|
||||||
|
|
||||||
|
card.style.transform = `translate(${position.x}px, ${position.y}px)`
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
.resizable(false)
|
||||||
|
|
||||||
|
isSetup = true
|
||||||
|
}
|
||||||
|
|
||||||
|
// 取消拖拽
|
||||||
|
const resetDraggable = () => {
|
||||||
|
interactInst?.unset()
|
||||||
|
|
||||||
|
interactInst = null
|
||||||
|
}
|
||||||
|
|
||||||
|
// 更新拖拽
|
||||||
|
const refreshDraggableWhenPropsChange = (fn: AnyFn) => {
|
||||||
|
isSetup = false
|
||||||
|
|
||||||
|
fn()
|
||||||
|
setupDraggable()
|
||||||
|
}
|
||||||
|
|
||||||
|
expose()
|
||||||
|
|
||||||
|
watchEffect(() => {
|
||||||
|
props.dad ? setupDraggable() : resetDraggable()
|
||||||
|
|
||||||
|
if (props.defaultPosition !== cacheProps.defaultPosition) {
|
||||||
|
refreshDraggableWhenPropsChange(() => {
|
||||||
|
cacheProps.defaultPosition = props.defaultPosition
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
nextTick(() => {
|
||||||
|
setupDraggable()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
return {
|
||||||
|
cardRef,
|
||||||
|
CONTAINER_ID,
|
||||||
|
cssVars,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
render() {
|
||||||
|
const { $attrs, $slots, $props, CONTAINER_ID, cssVars, animation } = this
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Teleport to={`#${CONTAINER_ID}`}>
|
||||||
|
{animation ? (
|
||||||
|
<Transition name="draggable-card" appear mode="out-in">
|
||||||
|
<NCard
|
||||||
|
{...$attrs}
|
||||||
|
{...$props}
|
||||||
|
class="r-draggable-card"
|
||||||
|
style={[cssVars]}
|
||||||
|
ref="cardRef"
|
||||||
|
>
|
||||||
|
{{ ...$slots }}
|
||||||
|
</NCard>
|
||||||
|
</Transition>
|
||||||
|
) : (
|
||||||
|
<NCard
|
||||||
|
{...$attrs}
|
||||||
|
{...$props}
|
||||||
|
class="r-draggable-card"
|
||||||
|
style={[cssVars]}
|
||||||
|
ref="cardRef"
|
||||||
|
>
|
||||||
|
{{ ...$slots }}
|
||||||
|
</NCard>
|
||||||
|
)}
|
||||||
|
</Teleport>
|
||||||
|
)
|
||||||
|
},
|
||||||
|
})
|
||||||
25
src/components/base/RDraggableCard/index.scss
Normal file
25
src/components/base/RDraggableCard/index.scss
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
.n-card.r-draggable-card {
|
||||||
|
transform-origin: 0 0;
|
||||||
|
position: absolute;
|
||||||
|
width: var(--r-draggable-card-width);
|
||||||
|
z-index: var(--r-draggable-card-z-index);
|
||||||
|
}
|
||||||
|
|
||||||
|
#r-draggable-card-container {
|
||||||
|
position: fixed;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
width: 0;
|
||||||
|
height: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// draggable-card 的 Transition 样式
|
||||||
|
.draggable-card-enter-active,
|
||||||
|
.draggable-card-leave-active {
|
||||||
|
transition: opacity 0.3s var(--r-bezier);
|
||||||
|
}
|
||||||
|
|
||||||
|
.draggable-card-enter-from,
|
||||||
|
.draggable-card-leave-to {
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
@ -1,8 +1,7 @@
|
|||||||
import RFlow from './src/Flow'
|
|
||||||
import flowProps from './src/props'
|
|
||||||
import { useFlow } from './src/hooks'
|
|
||||||
|
|
||||||
import type { ExtractPublicPropTypes } from 'vue'
|
import type { ExtractPublicPropTypes } from 'vue'
|
||||||
|
import RFlow from './src/Flow'
|
||||||
|
import { useFlow } from './src/hooks'
|
||||||
|
import flowProps from './src/props'
|
||||||
|
|
||||||
export type FlowProps = ExtractPublicPropTypes<typeof flowProps>
|
export type FlowProps = ExtractPublicPropTypes<typeof flowProps>
|
||||||
|
|
||||||
|
|||||||
@ -1,14 +1,12 @@
|
|||||||
import './index.scss'
|
import './index.scss'
|
||||||
import '@logicflow/core/lib/style/index.css'
|
import '@logicflow/core/lib/style/index.css'
|
||||||
|
import { call, completeSize } from '@/utils'
|
||||||
import { useTemplateRef } from 'vue'
|
|
||||||
import props from './props'
|
|
||||||
import { completeSize, call } from '@/utils'
|
|
||||||
import LogicFlow from '@logicflow/core'
|
import LogicFlow from '@logicflow/core'
|
||||||
import { omit } from 'lodash-es'
|
import { omit } from 'lodash-es'
|
||||||
|
import { useTemplateRef } from 'vue'
|
||||||
import type { FlowGraphData, G } from './types'
|
|
||||||
import type { WatchStopHandle } from 'vue'
|
import type { WatchStopHandle } from 'vue'
|
||||||
|
import props from './props'
|
||||||
|
import type { FlowGraphData, G } from './types'
|
||||||
|
|
||||||
// 是否首次注册插件
|
// 是否首次注册插件
|
||||||
let isSetup = false
|
let isSetup = false
|
||||||
|
|||||||
@ -1,8 +1,7 @@
|
|||||||
import { getDefaultFlowOptions } from './constant'
|
|
||||||
|
|
||||||
import type { FlowGraphData, FlowOptions, ExtensionType } from './types'
|
|
||||||
import type LogicFlow from '@logicflow/core'
|
|
||||||
import type { MaybeArray } from '@/types'
|
import type { MaybeArray } from '@/types'
|
||||||
|
import type LogicFlow from '@logicflow/core'
|
||||||
|
import { getDefaultFlowOptions } from './constant'
|
||||||
|
import type { ExtensionType, FlowGraphData, FlowOptions } from './types'
|
||||||
|
|
||||||
const props = {
|
const props = {
|
||||||
/**
|
/**
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
import type LogicFlow from '@logicflow/core'
|
|
||||||
import type { Recordable, SetRequired } from '@/types'
|
import type { Recordable, SetRequired } from '@/types'
|
||||||
|
import type LogicFlow from '@logicflow/core'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
|
|||||||
@ -1,10 +1,9 @@
|
|||||||
import RForm from './src/Form'
|
|
||||||
import formProps from './src/props'
|
|
||||||
import useForm from './src/hooks/useForm'
|
|
||||||
|
|
||||||
import type * as RFormType from './src/types'
|
|
||||||
import type { ExtractPublicPropTypes } from 'vue'
|
import type { ExtractPublicPropTypes } from 'vue'
|
||||||
|
import RForm from './src/Form'
|
||||||
|
import useForm from './src/hooks/useForm'
|
||||||
import type { UseFormReturn } from './src/hooks/useForm'
|
import type { UseFormReturn } from './src/hooks/useForm'
|
||||||
|
import formProps from './src/props'
|
||||||
|
import type * as RFormType from './src/types'
|
||||||
|
|
||||||
export type FormProps = ExtractPublicPropTypes<typeof formProps>
|
export type FormProps = ExtractPublicPropTypes<typeof formProps>
|
||||||
export type { RFormType, UseFormReturn }
|
export type { RFormType, UseFormReturn }
|
||||||
|
|||||||
@ -1,17 +1,39 @@
|
|||||||
import { NForm } from 'naive-ui'
|
import { call, unrefElement } from '@/utils'
|
||||||
|
import { useEventListener } from '@vueuse/core'
|
||||||
import props from './props'
|
import { NForm, NSpin } from 'naive-ui'
|
||||||
import { call } from '@/utils'
|
|
||||||
import { useTemplateRef } from 'vue'
|
import { useTemplateRef } from 'vue'
|
||||||
|
import type { ShallowRef } from 'vue'
|
||||||
|
import props from './props'
|
||||||
import type { RFormInst } from './types'
|
import type { RFormInst } from './types'
|
||||||
import type { FormProps } from 'naive-ui'
|
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
name: 'RForm',
|
name: 'RForm',
|
||||||
props,
|
props,
|
||||||
setup(props, { expose }) {
|
setup(props, { expose }) {
|
||||||
const formRef = useTemplateRef<RFormInst>('formRef')
|
const formRef = useTemplateRef<RFormInst>('formRef')
|
||||||
|
const currentSubmitFn = computed(() => props.onFinish ?? Promise.resolve)
|
||||||
|
|
||||||
|
const bindKeydownListener = (e: KeyboardEvent) => {
|
||||||
|
const keyCode = e.code
|
||||||
|
|
||||||
|
if (keyCode === 'Enter') {
|
||||||
|
e.stopPropagation()
|
||||||
|
e.preventDefault()
|
||||||
|
|
||||||
|
formRef.value?.validate().then(currentSubmitFn.value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (props.submitWhenEnter) {
|
||||||
|
useEventListener(
|
||||||
|
formRef as unknown as ShallowRef<HTMLElement>,
|
||||||
|
'keydown',
|
||||||
|
bindKeydownListener,
|
||||||
|
{
|
||||||
|
capture: true,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
// 主动调用 register 方法,满足 useForm 方法正常调用
|
// 主动调用 register 方法,满足 useForm 方法正常调用
|
||||||
@ -20,6 +42,16 @@ export default defineComponent({
|
|||||||
if (onRegister && formRef.value) {
|
if (onRegister && formRef.value) {
|
||||||
call(onRegister, formRef.value)
|
call(onRegister, formRef.value)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (formRef.value) {
|
||||||
|
const formElement = unrefElement(
|
||||||
|
formRef.value as unknown as HTMLFormElement,
|
||||||
|
)
|
||||||
|
|
||||||
|
if (formElement) {
|
||||||
|
formElement.autocomplete = props.autocomplete
|
||||||
|
}
|
||||||
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
expose()
|
expose()
|
||||||
@ -30,13 +62,22 @@ export default defineComponent({
|
|||||||
},
|
},
|
||||||
render() {
|
render() {
|
||||||
const { $attrs, $props, $slots } = this
|
const { $attrs, $props, $slots } = this
|
||||||
|
const { loading, loadingDescription, ...restProps } = $props
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<NForm {...$attrs} {...($props as FormProps)} ref="formRef">
|
<NSpin
|
||||||
|
show={loading}
|
||||||
|
description={loadingDescription}
|
||||||
|
style={{
|
||||||
|
height: 'auto',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<NForm {...$attrs} {...restProps} ref="formRef">
|
||||||
{{
|
{{
|
||||||
...$slots,
|
...$slots,
|
||||||
}}
|
}}
|
||||||
</NForm>
|
</NForm>
|
||||||
|
</NSpin>
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|||||||
@ -1,12 +1,11 @@
|
|||||||
import { cloneDeep } from 'lodash-es'
|
|
||||||
|
|
||||||
import type {
|
|
||||||
RFormInst,
|
|
||||||
FormValidateCallback,
|
|
||||||
ShouldRuleBeApplied,
|
|
||||||
RFormRules,
|
|
||||||
} from '../types'
|
|
||||||
import type { Recordable } from '@/types'
|
import type { Recordable } from '@/types'
|
||||||
|
import { cloneDeep } from 'lodash-es'
|
||||||
|
import type {
|
||||||
|
FormValidateCallback,
|
||||||
|
RFormInst,
|
||||||
|
RFormRules,
|
||||||
|
ShouldRuleBeApplied,
|
||||||
|
} from '../types'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
@ -36,11 +35,15 @@ import type { Recordable } from '@/types'
|
|||||||
* },
|
* },
|
||||||
* })
|
* })
|
||||||
*/
|
*/
|
||||||
const useForm = <T extends Recordable, R extends RFormRules>(
|
const useForm = <
|
||||||
model?: T,
|
T extends Recordable = Recordable,
|
||||||
rules?: R,
|
R extends RFormRules = RFormRules,
|
||||||
|
>(
|
||||||
|
model?: T | (() => T),
|
||||||
|
rules?: R | (() => R),
|
||||||
) => {
|
) => {
|
||||||
const formRef = ref<RFormInst>()
|
const formRef = shallowRef<RFormInst>()
|
||||||
|
const formModelRef = ref<T>()
|
||||||
|
|
||||||
const register = (inst: RFormInst) => {
|
const register = (inst: RFormInst) => {
|
||||||
if (inst) {
|
if (inst) {
|
||||||
@ -58,6 +61,15 @@ const useForm = <T extends Recordable, R extends RFormRules>(
|
|||||||
return formRef.value
|
return formRef.value
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 初始化 formModelRef 的值,根据 model 的类型进行初始化
|
||||||
|
const initialFormModel = () => {
|
||||||
|
if (typeof model === 'function') {
|
||||||
|
formModelRef.value = model() ?? ({} as T)
|
||||||
|
} else {
|
||||||
|
formModelRef.value = cloneDeep(model) ?? ({} as T)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* @description
|
* @description
|
||||||
@ -83,10 +95,39 @@ const useForm = <T extends Recordable, R extends RFormRules>(
|
|||||||
*
|
*
|
||||||
* @description
|
* @description
|
||||||
* 获取表项中收集到的值的对象。
|
* 获取表项中收集到的值的对象。
|
||||||
*
|
|
||||||
* 调用该方法时,需要确保初始化 useForm 方法的时候传入了 model,否则可能有意想不到的问题发生。
|
* 调用该方法时,需要确保初始化 useForm 方法的时候传入了 model,否则可能有意想不到的问题发生。
|
||||||
|
*
|
||||||
|
* 该方法可以实现重置表单的需求,因为在 vue 的设计理念中,表单的值绑定是直接绑定在每个组件上,
|
||||||
|
* 而不是利用 Form 表单机制,所以需要这么去做表单的初始化操作维护机制。
|
||||||
|
*
|
||||||
|
* 在 5.2.2 版本中,新增了 formConditionRef 属性,现在可以解构获取一个 ref 包裹的响应式初始化表单对象值;
|
||||||
|
* 这样就可以直接在 hook 中调用一个响应式的表单对象值。
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
*
|
||||||
|
* interface FormModel {
|
||||||
|
* name: string | null
|
||||||
|
* age: number | null
|
||||||
|
* }
|
||||||
|
*
|
||||||
|
* const [register, { formModel }] = useForm<FormModel>({
|
||||||
|
* name: null,
|
||||||
|
* age: null,
|
||||||
|
* })
|
||||||
|
*
|
||||||
|
* const formModelRef = ref(formModel())
|
||||||
|
*
|
||||||
|
* const reset = () => {
|
||||||
|
* formModelRef.value = formModel()
|
||||||
|
* }
|
||||||
*/
|
*/
|
||||||
const formModel = () => cloneDeep(model) || ({} as T)
|
const formModel = (): T & Recordable => {
|
||||||
|
if (typeof model === 'function') {
|
||||||
|
return model()
|
||||||
|
}
|
||||||
|
|
||||||
|
return cloneDeep(model) || ({} as T)
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
@ -95,7 +136,103 @@ const useForm = <T extends Recordable, R extends RFormRules>(
|
|||||||
*
|
*
|
||||||
* 调用该方法时,需要确保初始化 useForm 方法的时候传入了 rules,否则可能有意想不到的问题发生。
|
* 调用该方法时,需要确保初始化 useForm 方法的时候传入了 rules,否则可能有意想不到的问题发生。
|
||||||
*/
|
*/
|
||||||
const formRules = () => cloneDeep(rules) || ({} as R)
|
const formRules = () => {
|
||||||
|
if (typeof rules === 'function') {
|
||||||
|
return rules()
|
||||||
|
}
|
||||||
|
|
||||||
|
return cloneDeep(rules) || ({} as R)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param values 需要重置的表单值,该参数会覆盖初始化值
|
||||||
|
*
|
||||||
|
* @warning
|
||||||
|
* 请注意初始化值的问题,如果设置初始化值为 undefined,
|
||||||
|
* 则会导致 reset 初始化操作失败,必须使用 null 作为初始化的空值。
|
||||||
|
*
|
||||||
|
* @description
|
||||||
|
* 重置表单的值,依赖 useForm 传入的初始化值。
|
||||||
|
* 该方法会将初始化传入的值视为整个表单的初始化绑定值。
|
||||||
|
*
|
||||||
|
* 但是,对于复杂的动态值,也就是在实现业务中额外的对象值,需要手动进行初始化操作。
|
||||||
|
* 所以,最佳的实践应该是初始化 useForm 方法的时候,就应该确定好初始化值。
|
||||||
|
* 然后,在需要重置表单的时候,直接调用该方法即可。
|
||||||
|
*/
|
||||||
|
const reset = <Values extends T = T>(values?: Values & Recordable) => {
|
||||||
|
formModelRef.value = Object.assign(
|
||||||
|
formModelRef.value as T,
|
||||||
|
formModel(),
|
||||||
|
values,
|
||||||
|
)
|
||||||
|
restoreValidation()
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param key 需要验证的表单项的 key
|
||||||
|
*
|
||||||
|
* @see https://www.naiveui.com/zh-CN/dark/components/form#partially-apply-rules.vue
|
||||||
|
*
|
||||||
|
* @description
|
||||||
|
* 验证表单项的规则。
|
||||||
|
*
|
||||||
|
* 注意,该方法想要正常运转,需要在定义 rules 时定义唯一 key;
|
||||||
|
* 否则,该逻辑执行会失败。
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* const [register, { validateTargetField }] = useForm(
|
||||||
|
* {
|
||||||
|
* name: null,
|
||||||
|
* },
|
||||||
|
* {
|
||||||
|
* name: {
|
||||||
|
* required: true,
|
||||||
|
* message: 'name is required',
|
||||||
|
* trigger: ['blur', 'change'],
|
||||||
|
* type: 'string',
|
||||||
|
* key: 'name',
|
||||||
|
* },
|
||||||
|
* },
|
||||||
|
* )
|
||||||
|
*
|
||||||
|
* validateTargetField('name')
|
||||||
|
*/
|
||||||
|
const validateTargetField = (key: string) => {
|
||||||
|
if (!key || typeof key !== 'string') {
|
||||||
|
throw new Error(
|
||||||
|
`[useForm-validateTargetField]: except key is string, but got ${typeof key}.`,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
return validate(void 0, (rules) => {
|
||||||
|
return rules?.key === key
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @description
|
||||||
|
* 更新表单的值,该方法会覆盖初始化值。
|
||||||
|
* 推荐在表单组件中使用该方法,而不是直接修改表单的值,符合函数式的理念。
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* const [register, { updateFormCondition }] = useForm(
|
||||||
|
* {
|
||||||
|
* name: null,
|
||||||
|
* },
|
||||||
|
* )
|
||||||
|
*
|
||||||
|
* updateFormCondition({
|
||||||
|
* name: 'John',
|
||||||
|
* })
|
||||||
|
*/
|
||||||
|
const updateFormCondition = (values: T & Recordable) => {
|
||||||
|
formModelRef.value = Object.assign(formModelRef.value as T, values)
|
||||||
|
}
|
||||||
|
|
||||||
|
initialFormModel()
|
||||||
|
|
||||||
return [
|
return [
|
||||||
register,
|
register,
|
||||||
@ -105,6 +242,10 @@ const useForm = <T extends Recordable, R extends RFormRules>(
|
|||||||
restoreValidation,
|
restoreValidation,
|
||||||
formModel,
|
formModel,
|
||||||
formRules,
|
formRules,
|
||||||
|
reset,
|
||||||
|
validateTargetField,
|
||||||
|
formConditionRef: formModelRef as Ref<T>,
|
||||||
|
updateFormCondition,
|
||||||
},
|
},
|
||||||
] as const
|
] as const
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,10 +1,74 @@
|
|||||||
|
import type { AnyFn, MaybeArray } from '@/types'
|
||||||
|
import { omit } from 'lodash-es'
|
||||||
import { formProps } from 'naive-ui'
|
import { formProps } from 'naive-ui'
|
||||||
|
|
||||||
import type { MaybeArray } from '@/types'
|
|
||||||
import type { RFormInst } from './types'
|
import type { RFormInst } from './types'
|
||||||
|
|
||||||
const props = {
|
const props = {
|
||||||
...formProps,
|
...omit(formProps, ['onSubmit']),
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @description
|
||||||
|
* 表单的加载状态。
|
||||||
|
*
|
||||||
|
* @default false
|
||||||
|
*/
|
||||||
|
loading: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false,
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @description
|
||||||
|
* 表单的加载状态的描述。
|
||||||
|
*
|
||||||
|
* @default undefined
|
||||||
|
*/
|
||||||
|
loadingDescription: {
|
||||||
|
type: String,
|
||||||
|
default: void 0,
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @description
|
||||||
|
* 表单的自动完成功能。
|
||||||
|
*
|
||||||
|
* @default 'off'
|
||||||
|
*/
|
||||||
|
autocomplete: {
|
||||||
|
type: String as PropType<AutoFillBase>,
|
||||||
|
default: 'off',
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @description
|
||||||
|
* 是否在按下回车键时自动触发表单的校验,如果校验成功则会自动触发 onFinish 事件。
|
||||||
|
* 该功能需要结合 onFinish 属性使用。
|
||||||
|
*
|
||||||
|
* 注意,该属性不支持响应式更新,只依赖于初始化传入的值;
|
||||||
|
* 因为该方法特性的原因,做响应式的更新意义不大。
|
||||||
|
*
|
||||||
|
* 并且该属性启用后,会自动的拦截 Enter 键的默认行为;
|
||||||
|
* 例如 NSelect, NInput 等官方自带组件的默认快捷键功能,都会被阻止;
|
||||||
|
* 但是不得不这么做,以避免一些奇奇怪怪的问题。
|
||||||
|
*
|
||||||
|
* @default false
|
||||||
|
*/
|
||||||
|
submitWhenEnter: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false,
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @description
|
||||||
|
* 表单校验成功后自动触发的事件,该事件的触发时机为 submitWhenEnter 属性为 true 时,按下回车键触发。
|
||||||
|
* 该功能需要结合 submitWhenEnter 属性使用。
|
||||||
|
*
|
||||||
|
* @default null
|
||||||
|
*/
|
||||||
|
onFinish: {
|
||||||
|
type: Function as PropType<AnyFn>,
|
||||||
|
default: null,
|
||||||
|
},
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* @description
|
* @description
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user