mirror of
https://github.com/XiaoDaiGua-Ray/ray-template.git
synced 2025-10-06 06:40:02 +08:00
Compare commits
23 Commits
Author | SHA1 | Date | |
---|---|---|---|
|
49af61a339 | ||
|
4bce5f7713 | ||
|
34c20d4be7 | ||
|
c28c353f7d | ||
|
c68491fca9 | ||
|
0f2193cc14 | ||
|
ba6ceef0dc | ||
|
5e9ffb14a3 | ||
|
eb5a6aa9e2 | ||
|
674539edd3 | ||
|
ff0bcb5022 | ||
|
d3d98190a3 | ||
|
0bb707bba0 | ||
|
4bfdbccd88 | ||
|
3b2bba391e | ||
|
7647508935 | ||
|
852d7ca90a | ||
|
2c84e3ce4c | ||
|
83e0c19ba9 | ||
|
ff1a67c843 | ||
|
8f3969268a | ||
|
7783872ef6 | ||
|
707300774d |
@ -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
|
301
.eslintrc.cjs
301
.eslintrc.cjs
@ -1,301 +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'],
|
||||
},
|
||||
],
|
||||
},
|
||||
}
|
6
.github/workflows/docs-deploy.yaml
vendored
6
.github/workflows/docs-deploy.yaml
vendored
@ -14,15 +14,15 @@ jobs:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Install Node.js 20.x
|
||||
- name: Install Node.js 22.x
|
||||
uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: 20.x
|
||||
node-version: 22.x
|
||||
|
||||
- uses: pnpm/action-setup@v2
|
||||
name: Install pnpm
|
||||
with:
|
||||
version: 8
|
||||
version: 9
|
||||
run_install: false
|
||||
|
||||
- name: Install dependencies
|
||||
|
4
.github/workflows/push-build.yaml
vendored
4
.github/workflows/push-build.yaml
vendored
@ -8,7 +8,7 @@ jobs:
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
node-version: [20.x]
|
||||
node-version: [22.x]
|
||||
os: [ubuntu-latest, windows-latest, macos-latest]
|
||||
experimental: [true]
|
||||
|
||||
@ -24,7 +24,7 @@ jobs:
|
||||
- uses: pnpm/action-setup@v2
|
||||
name: Install pnpm
|
||||
with:
|
||||
version: 8
|
||||
version: 9
|
||||
run_install: false
|
||||
|
||||
- name: Get pnpm store directory
|
||||
|
8
.vscode/settings.json
vendored
8
.vscode/settings.json
vendored
@ -1,4 +1,5 @@
|
||||
{
|
||||
"editor.formatOnSave": true,
|
||||
"i18n-ally.localesPaths": ["src/locales/lang"],
|
||||
"i18n-ally.keystyle": "nested",
|
||||
"i18n-ally.sortKeys": true,
|
||||
@ -21,6 +22,7 @@
|
||||
"cSpell.words": [
|
||||
"baomitu",
|
||||
"bezier",
|
||||
"Cascader",
|
||||
"Clickoutside",
|
||||
"codabar",
|
||||
"commitmsg",
|
||||
@ -32,6 +34,7 @@
|
||||
"internalkey",
|
||||
"jsbarcode",
|
||||
"linebreak",
|
||||
"logicflow",
|
||||
"macarons",
|
||||
"menutag",
|
||||
"ndata",
|
||||
@ -40,9 +43,10 @@
|
||||
"Popselect",
|
||||
"precommit",
|
||||
"siderbar",
|
||||
"snapline",
|
||||
"stylelint",
|
||||
"unocss",
|
||||
"WUJIE",
|
||||
"zlevel"
|
||||
],
|
||||
"peacock.color": "#007fff"
|
||||
]
|
||||
}
|
||||
|
274
CHANGELOG.md
274
CHANGELOG.md
@ -1,4 +1,272 @@
|
||||
# CHANGE LOG
|
||||
## 5.2.2
|
||||
|
||||
## Feats
|
||||
|
||||
- `RForm` 组件相关
|
||||
- 新增 `submitWhenEnter` 配置项,允许在按下回车键时自动触发表单的校验,如果校验成功则会自动触发 `onFinish` 事件
|
||||
- 新增 `onFinish` 配置项,允许在表单校验成功后自动触发的事件
|
||||
- 新增 `autocomplete` 配置项,允许配置表单的自动完成功能,默认配置为 `off`
|
||||
- 新增 `loading` 配置项,允许配置表单的加载状态
|
||||
- 新增 `loadingDescription` 配置项,允许配置表单的加载状态的描述
|
||||
- `useForm` 相关
|
||||
- 新增 `validateTargetField` 方法,允许验证指定表单项的规则
|
||||
- 初始化方法现在支持传入函数,允许动态获取表单的初始化值与规则
|
||||
- `formModel` 方法现在会默认联合 `Recordable` 类型,获取初始化类型中未获取到的类型时,默认推到为 `any` 类型
|
||||
- 新增了 `formConditionRef` 属性,现在可以在内部解构获取一个 `ref` 包裹的响应式初始化表单对象值
|
||||
- 新增了 `updateFormCondition` 方法,允许更新表单的值,该方法会覆盖初始化值
|
||||
- 更新依赖为主流版本
|
||||
- 新增 `unocss` 原子化样式库,但是不推荐全量使用,仅作为一些简单的样式片段使用,否则在调试的时候将会是灾难
|
||||
> 新增 `unocss` 后,在使用 `ProTable` 组件的流体高度最外层父元素配置时,可以便捷的配置 `h-full` 即可。
|
||||
|
||||
## 5.2.1
|
||||
|
||||
## Feats
|
||||
|
||||
- `RTablePro` 组件相关
|
||||
- 新增 `runAsyncTableRequest` 方法,与 `runTableRequest` 方法功能一致,但是返回 `Promise` 对象
|
||||
- 现在不允许使用 `useTemplateRef` 方法注册 `dom` 模板引用,约定强制使用 `useTablePro` 方法的 `register` 方法注册 `hook` 使用相关方法
|
||||
- `useTablePro` 方法新增 `getTableProConfig` 方法,与 `useTable` 方法的 `getTableConfig` 方法功能一致,获取 `RTablePro` 组件额外注入配置
|
||||
- `useTable` 方法新增 `getTableConfig` 方法,获取 `RTable` 组件额外注入配置
|
||||
- 更新包为主流版本
|
||||
- `vue-router` 因为在 [4.4.1](https://github.com/vuejs/router/blob/main/packages/router/CHANGELOG.md#441-2024-07-31) 版本中有破坏性的更新,所以在 `jsx` 函数式组件使用 `this.$route`, `this.$router` 会提示类型报错,所以现在强制约定需要使用 `useRoute`, `useRouter` 方法显示的声明与使用
|
||||
- 更新 `naive-ui` 版本至 `2.42.0`
|
||||
- 更新 `vue` 版本至 `3.5.17`
|
||||
- `useForm` 方法新增 `reset` 方法,允许重置表单值,该方法依赖 `useForm` 方法的初始化 `formModel` 参数,所以请确保初始化 `formModel` 参数
|
||||
|
||||
## 5.2.0
|
||||
|
||||
一些破坏性更新,请谨慎更新。
|
||||
|
||||
## Feats
|
||||
|
||||
- 更新 `vue` 版本至 `3.5.16`
|
||||
- 更新 `vite` 版本至 `6.3.5`
|
||||
- `RTablePro` 组件相关
|
||||
- `runTableRequest` 方法现在支持传递 `reset` 参数,配置是否重置分页请求
|
||||
- `runTableRequest` 方法新增 `excludeParams` 配置项,允许排除指定的请求参数
|
||||
- `onTablePaginationUpdate` 方法参数返回值由返回函数改为直接返回值
|
||||
- 新增 `paginationPrefix` 配置项,允许自定义分页器前缀,在国际化需求可能会有用
|
||||
- 新增 `flexAutoHeight` 配置项,默认关闭,允许配置表格是否自动继承高度,但是要结合 `css flex` 属性使用
|
||||
|
||||
> 如果你是使用 `NFlex` 组件结合 `RTablePro` 或者 `RTable` 组件使用,需要配置 `Flex` 组件的 `vertical` 属性,并且设置 `class` 为 `flex-vertical`,即可便捷实现该效果。否则你需要设置 `css flex` 相关属性(可以参考 Demo2)的示例。
|
||||
|
||||
```tsx
|
||||
import { RTablePro } from '@/components'
|
||||
import { NFlex } from 'naive-ui'
|
||||
|
||||
const Demo1 = () => {
|
||||
return (
|
||||
<NFlex vertical class="flex-vertical">
|
||||
<RTablePro flexAutoHeight />
|
||||
</NFlex>
|
||||
)
|
||||
}
|
||||
|
||||
const Demo2 = () => {
|
||||
return (
|
||||
<div
|
||||
class="flex-vertical"
|
||||
style="height: 100%; display: flex; flex-direction: column;"
|
||||
>
|
||||
<RTablePro flexAutoHeight />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
- 新增 `getDateByNaiveDatePicker` 方法,便捷获取 `naive-ui` 的 `DatePicker` 组件的日期值
|
||||
- `Recordable` 类型新增 `symbol`, `number` 类型作为 `key` 支持
|
||||
- `RCollapse` 组件相关
|
||||
- 默认配置 `responsive` 配置项为 `screen` 响应模式
|
||||
- 默认配置 `cols` 配置项为 `4 xs:1 s:2 m:2 l:4 xl:4 2xl:6`,虽然目前的预设已经足够使用,但你也可以高度自定义需求
|
||||
- `types` 包
|
||||
- 新增 `GlobalDataTableColumns` 类型,用于声明全局 `DataTableColumns` 类型
|
||||
- 新增 `GlobalRecordable` 类型,用于声明全局 `Recordable` 类型
|
||||
|
||||
## Fixes
|
||||
|
||||
- 修复 `RTablePro` 组件 `print` 方法打印内容错误的问题
|
||||
|
||||
## 5.1.0
|
||||
|
||||
## Feats
|
||||
|
||||
- 更新 `vite` 版本至 `5.3.3`
|
||||
|
||||
## Fixes
|
||||
|
||||
- 修复 `chunksCopilot` 方法判断不准确导致 `node_modules` 库被拆分到 `hooks` 分包重复的问题
|
||||
|
||||
## 5.0.10
|
||||
|
||||
## Feats
|
||||
|
||||
- `RDraggableCard` 组件现在不会在抛出获取 `dom` 失败的异常,因为可能存在异步组件加载的可能
|
||||
- `RModal`, `useModal` 方法,移除 `dad` 相关所有配置,使用 `draggable` 配置项替代
|
||||
- 刷新的样式现在会跟随主题变化
|
||||
- 锁屏密码现在会进行加密存储,并且会进行校验处理了
|
||||
- 新增 `decrypt`, `decrypt` 方法,放置于 `utils/c` 包中
|
||||
|
||||
## Fixes
|
||||
|
||||
- 修复因为错误的注册全局事件,导致事件污染的问题,但是默认的 `ctrl + k`, `cmd + k` 快捷键依旧保留为全局按键
|
||||
|
||||
## 5.0.9
|
||||
|
||||
## Feats
|
||||
|
||||
- `RDraggableCard` 组件
|
||||
- 新增 `restrictionElement` 配置项,允许设置拖拽限制元素
|
||||
- 新增 `padding` 配置项,允许配置元素初始化位置的间隔值
|
||||
- `defaultPosition` 配置项新增 `top-left`, `top-right`, `bottom-left`, `bottom-right` 配置项,允许配置元素初始化位置
|
||||
- `RTablePro` 组件
|
||||
- 现在会自动删除重复的请求参数
|
||||
- 暴露 `resetTablePagination` 方法,允许手动重置表格分页
|
||||
- `logout` 方法现在会在执行的时候,清空所有的 `router-route`
|
||||
- 更新依赖为主流版本
|
||||
|
||||
## 5.0.8
|
||||
|
||||
## Feats
|
||||
|
||||
- 修改 `menuTagOptions` 的缓存方式,现在会缓存至 `sessionStorage` 中,兼容可能多系统版本部署与多开系统页面标签页冲突的问题
|
||||
- 新增 `RDraggableCard` 组件
|
||||
- 更新 `vite` 版本至 `6.0.4`
|
||||
|
||||
## Fixes
|
||||
|
||||
- 修复 `updateObjectValue` 方法对于对象值判断不准确的问题
|
||||
- 修复 `SettingDrawer` 组件初始化时,没有正确初始化 `settingStore` 的问题
|
||||
- 修复 `RTable` 组件在未设置 `title` 与 `tool` 为 `false` 时,导致 `headerStyle` 样式会高一些的问题
|
||||
|
||||
## 5.0.7
|
||||
|
||||
## Feats
|
||||
|
||||
- 更新 `vue` 版本至 `3.5.13`
|
||||
- 更新 `vite` 版本至 `6.0.3`
|
||||
- 更新 `naive-ui` 版本至 `2.40.3`
|
||||
- 更新包依赖为主流依赖
|
||||
- 更新 `eslint` 版本至 `9.11.0`,并且同步修改 `eslint` 相关配置方式,使用 `eslint.config.mjs` 文件替代
|
||||
- 更新默认 `node` 版本至 `22.11.0`
|
||||
- `RCollapseGrid` 组件新增 `actionSpan` 配置项,配置操作区域列数
|
||||
- `usePagination` 方法新增 `pageChange`, `pageSizeChange` 回调函数,允许在更新分页页码与每页条数的时候,执行自定义回调;用于取代被移除的 `onUpdatePage`, `onUpdatePageSize` 方法
|
||||
- 移除 `appNaiveUIThemeOverridesCommon` 配置项,现在统一使用 `appNaiveUIThemeOverrides` 配置项
|
||||
- 优化整体风格样式
|
||||
|
||||
## Fixes
|
||||
|
||||
- 修复 `useDomToImage` 方法的类型推导问题
|
||||
- 修复主题切换时,`naive-ui` 主题色覆盖不生效的问题
|
||||
|
||||
## 5.0.6
|
||||
|
||||
## Feats
|
||||
|
||||
- 新增 `useChartProvider` 方法,允许注入 `RCharts` 组件配置
|
||||
- 更新 `echarts` 版本至 `5.5.1`
|
||||
- 更新 `vue` 版本至 `3.5.13`
|
||||
- 更新 `@vueuse/core` 版本至 `11.2.0`
|
||||
- 修改 `SettingDrawer` 组件的 `defaultOptions` 配置项管理方式,现在迁移至 `store.setting` 包中
|
||||
- 重构 `cache` 工具模块,更有好的类型推导、更少的代码量
|
||||
- 重构 `precision` 工具模块,更好的类型推导、更少的代码量
|
||||
- 重写 `updateObjectValue` 方法,现在类型提示更加准确
|
||||
- 全局使用 `useTemplateRef`, `shallowRef` 方法替代 `ref` 注册模板引用,减少不必要的响应式代理
|
||||
- 优化 `MenuTag` 组件的关闭按钮样式
|
||||
- `LockScreen` 组件新增头像展示
|
||||
- `AppAvatar` 组件现在默认获取 `avatar` 字段为空的时候,展示名字的首字
|
||||
- 优化 `UnlockScreen` 组件样式,现在会根据主题自动调整背景颜色
|
||||
- 优化内容区域过度动画效果
|
||||
|
||||
## Fixes
|
||||
|
||||
- 修复 `404` 页面【返回】按钮不能准确返回的问题
|
||||
- 修复 `usePagination.getCallback` 方法类型丢失问题;修复该方法获取实时回调不准确的问题
|
||||
- 修复初始化时,菜单滚动条不能准确滚动到当前激活项的问题
|
||||
- 修复 `UnlockScreen` 组件在白色主题下,导致样式显示差异问题,现在统一为黑色主题配置覆盖
|
||||
- 修复 `LockScreen` 组件在退出锁屏时,没有及时更新 `localStorage` 缓存的问题
|
||||
- 修复 `setupDayjs` 初始化不准确的问题
|
||||
|
||||
## 5.0.5
|
||||
|
||||
## Feats
|
||||
|
||||
- 新增 `GLOBAL_CLASS_NAMES` 配置项
|
||||
- 新增 `canSkipRoute` 方法,用于初始化系统菜单时,自动获取可跳转的路由,避免权限系统列表中无权限路由跳转导致异常的问题
|
||||
- 优化 `useElementFullscreen` 方法的过渡效果
|
||||
- `useElementFullscreen` 新增 `isFullscreen` 属性,标识当前元素是否处于网页全屏状态
|
||||
|
||||
## Fixes
|
||||
|
||||
- 修复锁屏逻辑问题
|
||||
- 修复菜单有时候不能正常的展开被激活项的问题
|
||||
- 修复 `useSiderBar` 的 `close` 问题
|
||||
|
||||
## 5.0.4
|
||||
|
||||
将 `ts` 版本与 `eslint` 解析插件版本更新至最新版,并且同步解决了以前历史遗留的一些问题。
|
||||
|
||||
并且,在该版本做了一些全局注入方式调整,请谨慎更新。
|
||||
|
||||
## Feats
|
||||
|
||||
- 移除 `vite-plugin-compression` 插件,使用 `rollup-plugin-gzip` 代替
|
||||
- 更新 `@vitejs/plugin-vue-jsx` 版本至 `4.0.1`
|
||||
- 优化注释
|
||||
- 默认设置 `ContentWrapper` 的 `content-wrapper` 内容展示区域的宽高为 `100%`,继承父容器的宽高;该样式会自动的计算,也就是说会自动的适配不同尺寸的屏幕输出与判断是否显示 `FeatureWrapper`, `FooterWrapper`
|
||||
- 更新 `@typescript-eslint/eslint-plugin`, `@typescript-eslint/parser` 版本至 `8.13.0`
|
||||
- 更新 `typescript` 版本至 `5.6.3`
|
||||
- 更新 `vue-tsc` 版本至 `2.1.10`
|
||||
- 更新 `pnpm` 包管理器版本至 `9.12.3`
|
||||
- 新增 `RFlow` 基础流程图组件(后期有时间会逐步加强该组件)
|
||||
- 移除 `useElementFullscreen` 方法 `currentWindowSize` 返回值
|
||||
- 新增 `--html-height`, `--html-width` 的全局 `css var` 属性,实时获取浏览器的尺寸
|
||||
- 样式注入现在由注入至 `body` 改为注入至 `html`,避免 `teleport` 传送至 `body` 外的元素不能使用全局样式的问题
|
||||
- `useBadge.show` 方法新增 `extraOption` 配置项,允许在显示 `badge` 到时候,传入额外的 `options` 配置项
|
||||
|
||||
## Fixes
|
||||
|
||||
- 修复菜单折叠后,`SiderBarLogo` 标题样式丢失问题
|
||||
- 修复 `useModal` 因为 `Omit` 原因导致类型丢失问题,现在直接使用 `ModalProps` 作为类型
|
||||
- 修复 `useModal` 创建全屏 `card` 的时候,内容区域边距样式被覆盖的问题,现在会尊重原有的 `card` 样式
|
||||
|
||||
## 5.0.3
|
||||
|
||||
个性化配置能力再次提升。
|
||||
|
||||
## Feats
|
||||
|
||||
- `SettingDrawer` 组件重构
|
||||
|
||||
## 5.0.2
|
||||
|
||||
## Feats
|
||||
|
||||
- 暴露 `setupAppMenu` 方法,提供自定义菜单渲染时机能力
|
||||
|
||||
## Fixes
|
||||
|
||||
- 修复 `MenuTag` 右键菜单不能被选中的问题
|
||||
- 修复 `Menu` 设置 `iconSize`, `iconCollapseSize` 等属性,不能及时刷新渲染的问题,现在新增手动刷新操作按钮
|
||||
|
||||
> 菜单渲染是很复杂、很耗时的操作,所以在权衡以后还是考虑使用手动刷新操作方式更新菜单状态。
|
||||
|
||||
## 5.0.1
|
||||
|
||||
本次更新灵感来自于 `vben admin 5` 项目。
|
||||
|
||||
## Feats
|
||||
|
||||
- 更新了 `Menu`, `MenuTag`, `SiderBar` 样式细节
|
||||
- 调整 `components`, `components-pro` 分包,现在都统一到 `components` 下,以 `base`, `pro` 进行区分,避免导出导入麻烦
|
||||
- `MenuTag` 新增了一些操作方式
|
||||
- `SiderBarLogo` 样式调整
|
||||
|
||||
## Fixes
|
||||
|
||||
- 新增 `tableFlexHeight` 配置项属性修复 `RTable` 组件因为不再透传至 `Table` 导致设置 `flexHeight` 进行动态高度设置失败的问题
|
||||
- 修复 `RTable` 错误的 `title` 属性注入问题
|
||||
- 修复 `MenuTag` 频繁右键会导致闪烁出现右键菜单的问题
|
||||
|
||||
## 5.0.0
|
||||
|
||||
@ -37,7 +305,7 @@
|
||||
- 新增 `clearSigningCallback` 方法
|
||||
- `vite.custom.config` 新增 `cdn` 配置项,是否启用 `cdn` 构建项目
|
||||
- 配置 `cdn` 为 `false`,因为国内厂商更新资源速度有点慢,导致预览失败
|
||||
- `Layout` 层注入 `--window-width`, `--window-height` `css var` 属性
|
||||
- `Layout` 层注入 `--window-width`, `--window-height`, `css var` 属性
|
||||
- 稳定 `Layout` 层的 `css var` 属性
|
||||
|
||||
## Fixes
|
||||
@ -123,7 +391,7 @@
|
||||
- `RChart` 组件 `setOptions` 方法配置项默认不启用 `merge` 模式
|
||||
- 调整 `header` 的样式,增加了一点点间隙
|
||||
- `useDevice` 新增 `observer` 配置项,可以自定义观察回调
|
||||
- 新增 `components-pro` 包,助力简化业务开发
|
||||
- 新增 `components` 包,助力简化业务开发
|
||||
- 新增 `RTablePro` 组件,大幅简化中后台带有过滤请求条件的表格显示业务
|
||||
- 新增 `RCollapse` 组件,允许折叠过滤条件
|
||||
|
||||
|
@ -3,6 +3,7 @@ import { callWithAsyncErrorHandling } from '../../src/utils/basic'
|
||||
describe('callWithAsyncErrorHandling', () => {
|
||||
it('should call the function and return the result', () => {
|
||||
const fn = (x: number) => x
|
||||
|
||||
const callbackFn = () => {}
|
||||
|
||||
expect(callWithAsyncErrorHandling(fn, callbackFn, [1])).resolves.toBe(1)
|
||||
@ -14,6 +15,7 @@ describe('callWithAsyncErrorHandling', () => {
|
||||
const fn = () => {
|
||||
throw new Error('test error')
|
||||
}
|
||||
|
||||
const callbackFn = () => {
|
||||
callbackFnExecuted = 2
|
||||
}
|
||||
|
@ -3,6 +3,7 @@ import { callWithErrorHandling } from '../../src/utils/basic'
|
||||
describe('callWithErrorHandling', () => {
|
||||
it('should call the function and return the result', () => {
|
||||
const fn = (x: number) => x
|
||||
|
||||
const callbackFn = () => {}
|
||||
|
||||
expect(callWithErrorHandling(fn, callbackFn, [1])).toBe(1)
|
||||
@ -14,6 +15,7 @@ describe('callWithErrorHandling', () => {
|
||||
const fn = () => {
|
||||
throw new Error('test error')
|
||||
}
|
||||
|
||||
const callbackFn = () => {
|
||||
callbackFnExecuted = 2
|
||||
}
|
||||
|
@ -42,6 +42,7 @@ describe('isValueType', () => {
|
||||
})
|
||||
|
||||
it('should return false for Function', () => {
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-function-type
|
||||
expect(isValueType<Function>(/a/i, 'Function')).toBe(false)
|
||||
})
|
||||
})
|
||||
|
@ -14,6 +14,7 @@ describe('uuid', () => {
|
||||
|
||||
it('should return a string with length 36', () => {
|
||||
const uid = uuid(36)
|
||||
|
||||
expect(uid.length).toBe(36)
|
||||
})
|
||||
})
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { RModal } from '../../src/components/RModal/index'
|
||||
import { RModal } from '../../src/components/base/RModal/index'
|
||||
import { mount } from '@vue/test-utils'
|
||||
|
||||
describe('RModal', () => {
|
||||
|
@ -39,6 +39,7 @@ describe('useContextmenuCoordinate', () => {
|
||||
clientX: 100,
|
||||
clientY: 200,
|
||||
})
|
||||
|
||||
wrapperRef.element.dispatchEvent(event)
|
||||
|
||||
await nextTick()
|
||||
|
@ -17,8 +17,8 @@ describe('useDayjs', () => {
|
||||
}
|
||||
const localSpy = vi.spyOn(m, 'locale')
|
||||
|
||||
m.locale('en')
|
||||
m.locale('zh-cn')
|
||||
m.locale('en-US')
|
||||
m.locale('zh-CN')
|
||||
|
||||
expect(localSpy).toHaveBeenCalledTimes(2)
|
||||
})
|
||||
|
@ -13,6 +13,7 @@ import { mount } from '@vue/test-utils'
|
||||
*
|
||||
* const text = wrapper.find('div').text() // hello
|
||||
*/
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-function-type
|
||||
const createRefElement = (slots?: Record<string, Function>) => {
|
||||
const wrapper = mount(
|
||||
defineComponent({
|
||||
|
@ -3,6 +3,7 @@ import { call } from '../../src/utils/vue/call'
|
||||
describe('call', () => {
|
||||
it('should be executed once', () => {
|
||||
const fn = vi.fn()
|
||||
|
||||
call(() => fn())
|
||||
|
||||
expect(fn).toHaveBeenCalledTimes(1)
|
||||
@ -10,6 +11,7 @@ describe('call', () => {
|
||||
|
||||
it('should be executed with an argument', () => {
|
||||
const fn = vi.fn()
|
||||
|
||||
call((a: number) => fn(a), 1)
|
||||
|
||||
expect(fn).toHaveBeenCalledWith(1)
|
||||
|
@ -4,6 +4,7 @@ import createRefElement from '../utils/createRefElement'
|
||||
describe('renderNode', () => {
|
||||
it('should render string', () => {
|
||||
const wrapper = createRefElement({
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-function-type
|
||||
default: renderNode('hello world') as Function,
|
||||
})
|
||||
const text = wrapper.text()
|
||||
|
365
eslint.config.mjs
Normal file
365
eslint.config.mjs
Normal file
@ -0,0 +1,365 @@
|
||||
import vue from 'eslint-plugin-vue'
|
||||
import typescriptEslint from '@typescript-eslint/eslint-plugin'
|
||||
import prettier from 'eslint-plugin-prettier'
|
||||
import globals from 'globals'
|
||||
import parser from 'vue-eslint-parser'
|
||||
import path from 'node:path'
|
||||
import { fileURLToPath } from 'node:url'
|
||||
import js from '@eslint/js'
|
||||
import { FlatCompat } from '@eslint/eslintrc'
|
||||
|
||||
const __filename = fileURLToPath(import.meta.url)
|
||||
const __dirname = path.dirname(__filename)
|
||||
const compat = new FlatCompat({
|
||||
baseDirectory: __dirname,
|
||||
recommendedConfig: js.configs.recommended,
|
||||
allConfig: js.configs.all,
|
||||
})
|
||||
|
||||
export default [
|
||||
{
|
||||
ignores: [
|
||||
'**/node_modules/',
|
||||
'**/dist/',
|
||||
'dist/*',
|
||||
'node_modules/*',
|
||||
'**/auto-imports.d.ts',
|
||||
'**/components.d.ts',
|
||||
'**/.gitignore',
|
||||
'**/.vscode',
|
||||
'**/public',
|
||||
'**/yarn.*',
|
||||
'**/vite-env.*',
|
||||
'**/.prettierrc.*',
|
||||
'**/visualizer.*',
|
||||
'**/visualizer.html',
|
||||
'**/.env.*',
|
||||
'src/locales/lang',
|
||||
'**/.depcheckrc',
|
||||
'src/app-config/echart-themes/**/*.json',
|
||||
'**/*.md',
|
||||
'src/icons/*.svg',
|
||||
],
|
||||
},
|
||||
{
|
||||
files: ['**/*.js', '**/*.ts', '**/*.jsx', '**/*.tsx', '**/*.vue'],
|
||||
},
|
||||
...compat.extends(
|
||||
'eslint-config-prettier',
|
||||
'eslint:recommended',
|
||||
'plugin:@typescript-eslint/recommended',
|
||||
'plugin:vue/vue3-recommended',
|
||||
'plugin:vue/vue3-essential',
|
||||
'plugin:prettier/recommended',
|
||||
'prettier',
|
||||
'./unplugin/.eslintrc-auto-import.json',
|
||||
),
|
||||
{
|
||||
plugins: {
|
||||
vue,
|
||||
'@typescript-eslint': typescriptEslint,
|
||||
prettier,
|
||||
},
|
||||
languageOptions: {
|
||||
globals: {
|
||||
...globals.browser,
|
||||
...globals.node,
|
||||
defineProps: 'readonly',
|
||||
defineEmits: 'readonly',
|
||||
defineExpose: 'readonly',
|
||||
withDefaults: 'readonly',
|
||||
defineOptions: 'readonly',
|
||||
defineModel: 'readonly',
|
||||
},
|
||||
parser: parser,
|
||||
ecmaVersion: 2020,
|
||||
sourceType: 'module',
|
||||
parserOptions: {
|
||||
parser: '@typescript-eslint/parser',
|
||||
ecmaFeatures: {
|
||||
jsx: true,
|
||||
tsx: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
rules: {
|
||||
'no-undefined': ['error'],
|
||||
'linebreak-style': ['error', 'unix'],
|
||||
'@typescript-eslint/no-explicit-any': [
|
||||
'error',
|
||||
{
|
||||
ignoreRestArgs: true,
|
||||
},
|
||||
],
|
||||
'prettier/prettier': 'error',
|
||||
'no-unused-vars': 'off',
|
||||
'@typescript-eslint/no-unused-vars': 'off',
|
||||
'@typescript-eslint/ban-types': 'off',
|
||||
'@typescript-eslint/explicit-module-boundary-types': 'off',
|
||||
'@typescript-eslint/no-var-requires': 'off',
|
||||
'@typescript-eslint/no-non-null-assertion': 'off',
|
||||
'@typescript-eslint/no-non-null-asserted-optional-chain': 'off',
|
||||
'@typescript-eslint/consistent-type-imports': [
|
||||
'error',
|
||||
{
|
||||
disallowTypeAnnotations: false,
|
||||
},
|
||||
],
|
||||
'@typescript-eslint/no-empty-interface': [
|
||||
'error',
|
||||
{
|
||||
allowSingleExtends: true,
|
||||
},
|
||||
],
|
||||
'accessor-pairs': 2,
|
||||
'constructor-super': 0,
|
||||
'default-case': 2,
|
||||
eqeqeq: [2, 'allow-null'],
|
||||
'no-alert': 0,
|
||||
'no-array-constructor': 2,
|
||||
'no-bitwise': 0,
|
||||
'no-caller': 1,
|
||||
'no-catch-shadow': 2,
|
||||
'no-class-assign': 2,
|
||||
'no-cond-assign': 2,
|
||||
'no-const-assign': 2,
|
||||
'no-constant-condition': 2,
|
||||
'no-dupe-keys': 2,
|
||||
'no-dupe-args': 2,
|
||||
'no-duplicate-case': 2,
|
||||
'no-eval': 1,
|
||||
'no-ex-assign': 2,
|
||||
'no-extend-native': 2,
|
||||
'no-extra-bind': 2,
|
||||
'no-extra-boolean-cast': [
|
||||
'error',
|
||||
{
|
||||
enforceForLogicalOperands: true,
|
||||
},
|
||||
],
|
||||
'no-extra-parens': 0,
|
||||
semi: [
|
||||
'error',
|
||||
'never',
|
||||
{
|
||||
beforeStatementContinuationChars: 'always',
|
||||
},
|
||||
],
|
||||
'no-fallthrough': 1,
|
||||
'no-func-assign': 2,
|
||||
'no-implicit-coercion': [
|
||||
'error',
|
||||
{
|
||||
allow: ['!!', '~'],
|
||||
},
|
||||
],
|
||||
'no-implied-eval': 2,
|
||||
'no-invalid-regexp': 2,
|
||||
'no-invalid-this': 2,
|
||||
'no-irregular-whitespace': 2,
|
||||
'no-iterator': 2,
|
||||
'no-label-var': 2,
|
||||
'no-labels': 2,
|
||||
'no-lone-blocks': 2,
|
||||
'no-multi-spaces': 1,
|
||||
'no-multiple-empty-lines': [
|
||||
'error',
|
||||
{
|
||||
max: 2,
|
||||
},
|
||||
],
|
||||
'no-new-func': 2,
|
||||
'no-new-object': 2,
|
||||
'no-new-require': 2,
|
||||
'no-sparse-arrays': 2,
|
||||
'no-trailing-spaces': 1,
|
||||
'no-unreachable': 2,
|
||||
'no-unused-expressions': [
|
||||
'error',
|
||||
{
|
||||
allowShortCircuit: true,
|
||||
allowTernary: true,
|
||||
allowTaggedTemplates: true,
|
||||
enforceForJSX: true,
|
||||
},
|
||||
],
|
||||
'no-useless-call': 2,
|
||||
'no-var': 'error',
|
||||
'no-with': 2,
|
||||
'use-isnan': 2,
|
||||
'no-multi-assign': 2,
|
||||
'prefer-arrow-callback': 2,
|
||||
curly: ['error', 'all'],
|
||||
'vue/multi-word-component-names': [
|
||||
'error',
|
||||
{
|
||||
ignores: [],
|
||||
},
|
||||
],
|
||||
'vue/no-use-v-if-with-v-for': [
|
||||
'error',
|
||||
{
|
||||
allowUsingIterationVar: false,
|
||||
},
|
||||
],
|
||||
'vue/require-v-for-key': ['error'],
|
||||
'vue/require-valid-default-prop': ['error'],
|
||||
'vue/component-definition-name-casing': ['error', 'PascalCase'],
|
||||
'vue/html-closing-bracket-newline': [
|
||||
'error',
|
||||
{
|
||||
singleline: 'never',
|
||||
multiline: 'always',
|
||||
},
|
||||
],
|
||||
'vue/v-on-event-hyphenation': ['error', 'never'],
|
||||
'vue/component-tags-order': [
|
||||
'error',
|
||||
{
|
||||
order: ['template', 'script', 'style'],
|
||||
},
|
||||
],
|
||||
'vue/no-v-html': ['error'],
|
||||
'vue/no-v-text': ['error'],
|
||||
'vue/component-api-style': [
|
||||
'error',
|
||||
['script-setup', 'composition', 'composition-vue2'],
|
||||
],
|
||||
'vue/component-name-in-template-casing': [
|
||||
'error',
|
||||
'PascalCase',
|
||||
{
|
||||
registeredComponentsOnly: false,
|
||||
},
|
||||
],
|
||||
'vue/no-unused-refs': ['error'],
|
||||
'vue/prop-name-casing': ['error', 'camelCase'],
|
||||
'vue/component-options-name-casing': ['error', 'PascalCase'],
|
||||
'vue/attribute-hyphenation': [
|
||||
'error',
|
||||
'never',
|
||||
{
|
||||
ignore: [],
|
||||
},
|
||||
],
|
||||
'vue/no-restricted-static-attribute': [
|
||||
'error',
|
||||
{
|
||||
key: 'key',
|
||||
message: 'Disallow using key as a custom attribute',
|
||||
},
|
||||
],
|
||||
'no-restricted-syntax': [
|
||||
'error',
|
||||
{
|
||||
selector: "CallExpression[callee.property.name='deprecated']",
|
||||
message: 'Using deprecated API is not allowed.',
|
||||
},
|
||||
],
|
||||
'padding-line-between-statements': [
|
||||
'error',
|
||||
{
|
||||
blankLine: 'always',
|
||||
prev: ['import'],
|
||||
next: '*',
|
||||
},
|
||||
{
|
||||
blankLine: 'any',
|
||||
prev: 'import',
|
||||
next: 'import',
|
||||
},
|
||||
{
|
||||
blankLine: 'always',
|
||||
prev: '*',
|
||||
next: 'export',
|
||||
},
|
||||
{
|
||||
blankLine: 'any',
|
||||
prev: 'export',
|
||||
next: 'export',
|
||||
},
|
||||
{
|
||||
blankLine: 'always',
|
||||
prev: ['const', 'let', 'var'],
|
||||
next: '*',
|
||||
},
|
||||
{
|
||||
blankLine: 'any',
|
||||
prev: ['const', 'let', 'var'],
|
||||
next: ['const', 'let', 'var'],
|
||||
},
|
||||
{
|
||||
blankLine: 'always',
|
||||
prev: 'directive',
|
||||
next: '*',
|
||||
},
|
||||
{
|
||||
blankLine: 'any',
|
||||
prev: 'directive',
|
||||
next: 'directive',
|
||||
},
|
||||
{
|
||||
blankLine: 'always',
|
||||
prev: '*',
|
||||
next: [
|
||||
'if',
|
||||
'class',
|
||||
'for',
|
||||
'do',
|
||||
'while',
|
||||
'switch',
|
||||
'try',
|
||||
'with',
|
||||
'function',
|
||||
'block',
|
||||
'block-like',
|
||||
'break',
|
||||
'case',
|
||||
'continue',
|
||||
'return',
|
||||
'throw',
|
||||
'debugger',
|
||||
],
|
||||
},
|
||||
{
|
||||
blankLine: 'always',
|
||||
prev: [
|
||||
'if',
|
||||
'class',
|
||||
'for',
|
||||
'do',
|
||||
'while',
|
||||
'switch',
|
||||
'try',
|
||||
'with',
|
||||
'function',
|
||||
'block',
|
||||
'block-like',
|
||||
'break',
|
||||
'case',
|
||||
'continue',
|
||||
'return',
|
||||
'throw',
|
||||
'debugger',
|
||||
],
|
||||
next: '*',
|
||||
},
|
||||
],
|
||||
'@typescript-eslint/no-unused-expressions': [
|
||||
'error',
|
||||
{
|
||||
allowShortCircuit: true,
|
||||
allowTernary: true,
|
||||
allowTaggedTemplates: true,
|
||||
},
|
||||
],
|
||||
'@typescript-eslint/no-empty-object-type': [
|
||||
'error',
|
||||
{
|
||||
allowInterfaces: 'with-single-extends',
|
||||
allowObjectTypes: 'always',
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
]
|
39
index.html
39
index.html
@ -15,6 +15,27 @@
|
||||
--preloading-title-color: <%= preloadingConfig.titleColor %>;
|
||||
--ray-theme-primary-fade-color: <%= appPrimaryColor.primaryFadeColor %>;
|
||||
--ray-theme-primary-color: <%= appPrimaryColor.primaryColor %>;
|
||||
--global-loading-bg-color: #ffffff;
|
||||
}
|
||||
|
||||
@media (prefers-color-scheme: dark) {
|
||||
#pre-loading-animation {
|
||||
background-color: var(--global-loading-bg-color);
|
||||
}
|
||||
}
|
||||
|
||||
@media (prefers-color-scheme: light) {
|
||||
#pre-loading-animation {
|
||||
background-color: var(--global-loading-bg-color);
|
||||
}
|
||||
}
|
||||
|
||||
html.dark #pre-loading-animation {
|
||||
background-color: var(--global-loading-bg-color);
|
||||
}
|
||||
|
||||
html.light #pre-loading-animation {
|
||||
background-color: var(--global-loading-bg-color);
|
||||
}
|
||||
|
||||
#pre-loading-animation {
|
||||
@ -23,13 +44,9 @@
|
||||
right: 0;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
background-color: #ffffff;
|
||||
color: var(--preloading-title-color);
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.ray-template--dark #pre-loading-animation {
|
||||
background-color: #2a3146;
|
||||
background-color: var(--global-loading-bg-color);
|
||||
}
|
||||
|
||||
#pre-loading-animation .pre-loading-animation__wrapper {
|
||||
@ -95,6 +112,18 @@
|
||||
}
|
||||
}
|
||||
</style>
|
||||
<script>
|
||||
;(function () {
|
||||
const html = document.documentElement
|
||||
const store = window.localStorage.getItem('piniaSettingStore')
|
||||
const { _appTheme = false } = store ? JSON.parse(store) : {}
|
||||
const loadingBgColor = _appTheme ? '#1c1e23' : '#ffffff'
|
||||
|
||||
html.classList.add(_appTheme ? 'dark' : 'light')
|
||||
html.style.setProperty('--global-loading-bg-color', loadingBgColor)
|
||||
html.style.setProperty('background-color', loadingBgColor)
|
||||
})()
|
||||
</script>
|
||||
<body>
|
||||
<div id="app"></div>
|
||||
<div id="pre-loading-animation">
|
||||
|
129
package.json
129
package.json
@ -1,11 +1,11 @@
|
||||
{
|
||||
"name": "ray-template",
|
||||
"private": false,
|
||||
"version": "5.0.0",
|
||||
"version": "5.2.2",
|
||||
"type": "module",
|
||||
"engines": {
|
||||
"node": "^18.0.0 || >=20.0.0",
|
||||
"pnpm": ">=8.0.0"
|
||||
"node": "^18.0.0 || ^20.0.0 || >=22.0.0",
|
||||
"pnpm": ">=9.0.0"
|
||||
},
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
@ -16,7 +16,7 @@
|
||||
"prepare": "husky install",
|
||||
"test": "vitest",
|
||||
"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": {
|
||||
"hooks": {
|
||||
@ -29,82 +29,85 @@
|
||||
"prettier --write"
|
||||
],
|
||||
"*.{ts,tsx,vue}": [
|
||||
"eslint src"
|
||||
"eslint --fix"
|
||||
]
|
||||
},
|
||||
"dependencies": {
|
||||
"@vueuse/core": "^11.1.0",
|
||||
"axios": "^1.7.5",
|
||||
"@logicflow/core": "2.0.10",
|
||||
"@logicflow/extension": "2.0.14",
|
||||
"@vueuse/core": "^13.1.0",
|
||||
"axios": "^1.9.0",
|
||||
"clipboard": "^2.0.11",
|
||||
"crypto-js": "4.2.0",
|
||||
"currency.js": "^2.0.4",
|
||||
"dayjs": "^1.11.10",
|
||||
"echarts": "^5.5.0",
|
||||
"html-to-image": "1.11.11",
|
||||
"interactjs": "1.10.26",
|
||||
"dayjs": "^1.11.13",
|
||||
"echarts": "^5.6.0",
|
||||
"html-to-image": "1.11.13",
|
||||
"interactjs": "1.10.27",
|
||||
"jsbarcode": "3.11.6",
|
||||
"lodash-es": "^4.17.21",
|
||||
"mockjs": "1.1.0",
|
||||
"naive-ui": "^2.40.1",
|
||||
"pinia": "^2.2.4",
|
||||
"pinia-plugin-persistedstate": "^4.1.1",
|
||||
"naive-ui": "^2.42.0",
|
||||
"pinia": "^3.0.3",
|
||||
"pinia-plugin-persistedstate": "^4.4.1",
|
||||
"print-js": "^1.6.0",
|
||||
"vue": "^3.5.12",
|
||||
"vue-demi": "0.14.6",
|
||||
"vue-hooks-plus": "2.2.1",
|
||||
"vue": "^3.5.17",
|
||||
"vue-demi": "0.14.10",
|
||||
"vue-hooks-plus": "2.4.0",
|
||||
"vue-i18n": "^9.13.1",
|
||||
"vue-router": "^4.3.2",
|
||||
"vue3-next-qrcode": "2.0.10"
|
||||
"vue-router": "^4.5.1",
|
||||
"vue3-next-qrcode": "3.0.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@commitlint/cli": "^17.8.1",
|
||||
"@commitlint/config-conventional": "^17.8.1",
|
||||
"@interactjs/types": "1.10.21",
|
||||
"@intlify/unplugin-vue-i18n": "^4.0.0",
|
||||
"@types/crypto-js": "^4.2.2",
|
||||
"@types/dom-to-image": "2.6.7",
|
||||
"@commitlint/cli": "19.7.1",
|
||||
"@commitlint/config-conventional": "19.7.1",
|
||||
"@eslint/js": "9.28.0",
|
||||
"@interactjs/types": "1.10.27",
|
||||
"@intlify/unplugin-vue-i18n": "4.0.0",
|
||||
"@types/crypto-js": "4.2.2",
|
||||
"@types/jsbarcode": "3.11.4",
|
||||
"@types/lodash-es": "^4.17.12",
|
||||
"@types/mockjs": "1.0.7",
|
||||
"@typescript-eslint/eslint-plugin": "^6.21.0",
|
||||
"@typescript-eslint/parser": "^6.21.0",
|
||||
"@vitejs/plugin-vue": "^5.1.0",
|
||||
"@vitejs/plugin-vue-jsx": "^4.0.0",
|
||||
"@vitest/ui": "1.4.0",
|
||||
"@vue/eslint-config-prettier": "^9.0.0",
|
||||
"@vue/eslint-config-typescript": "^12.0.0",
|
||||
"@vue/test-utils": "2.4.3",
|
||||
"autoprefixer": "^10.4.16",
|
||||
"depcheck": "^1.4.7",
|
||||
"eslint": "^8.57.0",
|
||||
"eslint-config-prettier": "^9.1.0",
|
||||
"eslint-config-standard-with-typescript": "^43.0.0",
|
||||
"eslint-plugin-prettier": "^5.1.3",
|
||||
"eslint-plugin-promise": "^6.1.1",
|
||||
"eslint-plugin-vue": "^9.25.0",
|
||||
"happy-dom": "14.3.1",
|
||||
"@types/lodash-es": "4.17.12",
|
||||
"@types/mockjs": "1.0.10",
|
||||
"@typescript-eslint/eslint-plugin": "8.24.0",
|
||||
"@typescript-eslint/parser": "8.24.0",
|
||||
"@vitejs/plugin-vue": "5.2.3",
|
||||
"@vitejs/plugin-vue-jsx": "4.1.2",
|
||||
"@vitest/ui": "2.1.8",
|
||||
"@vue/eslint-config-prettier": "10.1.0",
|
||||
"@vue/eslint-config-typescript": "14.2.0",
|
||||
"@vue/test-utils": "2.4.6",
|
||||
"autoprefixer": "10.4.21",
|
||||
"depcheck": "1.4.7",
|
||||
"eslint": "9.20.1",
|
||||
"eslint-config-prettier": "10.1.2",
|
||||
"eslint-plugin-prettier": "5.2.6",
|
||||
"eslint-plugin-vue": "9.32.0",
|
||||
"globals": "16.0.0",
|
||||
"happy-dom": "17.1.0",
|
||||
"husky": "8.0.3",
|
||||
"lint-staged": "^15.2.0",
|
||||
"postcss": "^8.4.38",
|
||||
"lint-staged": "15.4.3",
|
||||
"postcss": "8.5.4",
|
||||
"postcss-px-to-viewport-8-with-include": "1.2.2",
|
||||
"prettier": "^3.2.5",
|
||||
"sass": "1.71.1",
|
||||
"svg-sprite-loader": "^6.0.11",
|
||||
"typescript": "^5.2.2",
|
||||
"unplugin-auto-import": "^0.18.2",
|
||||
"unplugin-vue-components": "^0.27.4",
|
||||
"vite": "^5.4.3",
|
||||
"vite-bundle-analyzer": "0.9.4",
|
||||
"prettier": "3.5.3",
|
||||
"rollup-plugin-gzip": "4.0.1",
|
||||
"sass": "1.86.3",
|
||||
"svg-sprite-loader": "6.0.11",
|
||||
"typescript": "5.8.3",
|
||||
"unocss": "66.3.3",
|
||||
"unplugin-auto-import": "19.1.2",
|
||||
"unplugin-vue-components": "0.28.0",
|
||||
"vite": "6.3.5",
|
||||
"vite-bundle-analyzer": "0.16.0",
|
||||
"vite-plugin-cdn2": "1.1.0",
|
||||
"vite-plugin-compression": "^0.5.1",
|
||||
"vite-plugin-ejs": "^1.7.0",
|
||||
"vite-plugin-ejs": "1.7.0",
|
||||
"vite-plugin-eslint": "1.8.1",
|
||||
"vite-plugin-inspect": "^0.8.3",
|
||||
"vite-plugin-mock-dev-server": "1.4.7",
|
||||
"vite-plugin-svg-icons": "^2.0.1",
|
||||
"vite-svg-loader": "^4.0.0",
|
||||
"vite-tsconfig-paths": "4.3.2",
|
||||
"vitest": "1.5.2",
|
||||
"vue-tsc": "^2.0.13"
|
||||
"vite-plugin-inspect": "0.8.4",
|
||||
"vite-plugin-mock-dev-server": "1.8.3",
|
||||
"vite-plugin-svg-icons": "2.0.1",
|
||||
"vite-svg-loader": "5.1.0",
|
||||
"vitest": "2.1.8",
|
||||
"vue-eslint-parser": "9.4.3",
|
||||
"vue-tsc": "2.2.8"
|
||||
},
|
||||
"description": "<!-- ALL-CONTRIBUTORS-BADGE:START - Do not remove or modify this section -->",
|
||||
"main": "index.ts",
|
||||
|
13780
pnpm-lock.yaml
generated
13780
pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
@ -28,7 +28,7 @@ interface JSONPlaceholder {
|
||||
*
|
||||
* @returns 测试
|
||||
*
|
||||
* @medthod get
|
||||
* @method get
|
||||
*/
|
||||
export const getWeather = (city: string) => {
|
||||
return request<AxiosTestResponse>({
|
||||
|
@ -46,7 +46,7 @@ const AppAvatar = defineComponent({
|
||||
const { getSigningCallback, avatarSize, spaceSize, $props, vertical } = this
|
||||
|
||||
return (
|
||||
<NButton quaternary strong>
|
||||
<NButton quaternary strong focusable={false}>
|
||||
<NFlex align="center" size={spaceSize} vertical={vertical}>
|
||||
<NAvatar
|
||||
{...($props as AvatarProps)}
|
||||
@ -54,7 +54,14 @@ const AppAvatar = defineComponent({
|
||||
objectFit="cover"
|
||||
round
|
||||
size={avatarSize}
|
||||
/>
|
||||
>
|
||||
{{
|
||||
default: () =>
|
||||
getSigningCallback.avatar
|
||||
? null
|
||||
: getSigningCallback?.name?.[0],
|
||||
}}
|
||||
</NAvatar>
|
||||
{getSigningCallback?.name}
|
||||
</NFlex>
|
||||
</NButton>
|
||||
|
@ -44,6 +44,7 @@ const GlobalSpin = defineComponent({
|
||||
{...(this.$props as SpinProps)}
|
||||
show={this.spinValue}
|
||||
themeOverrides={this.overrides}
|
||||
style="height: var(--html-height)"
|
||||
>
|
||||
{{
|
||||
...this.$slots,
|
||||
|
@ -1,17 +1,10 @@
|
||||
/**
|
||||
*
|
||||
* 统一管理是否处于锁屏状态
|
||||
*
|
||||
* 可以根据后台接口进行替换该变量, 只要是一个响应式的变量值即可
|
||||
*/
|
||||
|
||||
import { useStorage } from '@vueuse/core'
|
||||
import { APP_CATCH_KEY } from '@/app-config'
|
||||
|
||||
const appLockScreen = useStorage(
|
||||
APP_CATCH_KEY.isAppLockScreen,
|
||||
false,
|
||||
sessionStorage,
|
||||
window.localStorage,
|
||||
{
|
||||
mergeDefaults: true,
|
||||
},
|
||||
|
@ -1,16 +1,22 @@
|
||||
import { NInput, NForm, NFormItem, NButton } from 'naive-ui'
|
||||
import { NInput, NFormItem, NButton } from 'naive-ui'
|
||||
import AppAvatar from '@/app-components/app/AppAvatar'
|
||||
import { RForm } from '@/components'
|
||||
|
||||
import useAppLockScreen from '@/app-components/app/AppLockScreen/appLockVar'
|
||||
import { rules, useCondition } from '@/app-components/app/AppLockScreen/shared'
|
||||
import { useSettingGetters, useSettingActions } from '@/store'
|
||||
import { useSettingActions } from '@/store'
|
||||
import { useTemplateRef } from 'vue'
|
||||
import { useForm } from '@/components'
|
||||
import { APP_CATCH_KEY } from '@/app-config'
|
||||
import { setStorage, encrypt } from '@/utils'
|
||||
|
||||
import type { FormInst, InputInst } from 'naive-ui'
|
||||
import type { InputInst } from 'naive-ui'
|
||||
|
||||
const LockScreen = defineComponent({
|
||||
name: 'LockScreen',
|
||||
setup() {
|
||||
const formInstRef = ref<FormInst | null>(null)
|
||||
const inputInstRef = ref<InputInst | null>(null)
|
||||
const [register, { validate }] = useForm()
|
||||
const inputInstRef = useTemplateRef<InputInst | null>('inputInstRef')
|
||||
|
||||
const { setLockAppScreen } = useAppLockScreen()
|
||||
const { updateSettingState } = useSettingActions()
|
||||
@ -19,15 +25,17 @@ const LockScreen = defineComponent({
|
||||
lockCondition: useCondition(),
|
||||
})
|
||||
|
||||
/** 锁屏 */
|
||||
const lockScreen = () => {
|
||||
formInstRef.value?.validate((error) => {
|
||||
if (!error) {
|
||||
validate().then(() => {
|
||||
setLockAppScreen(true)
|
||||
updateSettingState('lockScreenSwitch', true)
|
||||
updateSettingState('lockScreenSwitch', false)
|
||||
setStorage(
|
||||
APP_CATCH_KEY.appLockScreenPasswordKey,
|
||||
encrypt(state.lockCondition.lockPassword),
|
||||
'localStorage',
|
||||
)
|
||||
|
||||
state.lockCondition = useCondition()
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
@ -40,18 +48,27 @@ const LockScreen = defineComponent({
|
||||
return {
|
||||
...toRefs(state),
|
||||
lockScreen,
|
||||
formInstRef,
|
||||
register,
|
||||
inputInstRef,
|
||||
}
|
||||
},
|
||||
render() {
|
||||
const { register } = this
|
||||
|
||||
return (
|
||||
<div class="app-lock-screen__content">
|
||||
<div class="app-lock-screen__input">
|
||||
<NForm
|
||||
<AppAvatar
|
||||
avatarSize={52}
|
||||
style="pointer-events: none;margin: 24px 0;"
|
||||
vertical
|
||||
/>
|
||||
<RForm
|
||||
ref="formInstRef"
|
||||
model={this.lockCondition}
|
||||
rules={rules}
|
||||
labelPlacement="left"
|
||||
onRegister={register}
|
||||
>
|
||||
<NFormItem path="lockPassword">
|
||||
<NInput
|
||||
@ -68,12 +85,14 @@ const LockScreen = defineComponent({
|
||||
this.lockScreen()
|
||||
}
|
||||
}}
|
||||
autofocus
|
||||
/>
|
||||
</NFormItem>
|
||||
<NButton type="primary" onClick={this.lockScreen.bind(this)}>
|
||||
锁屏
|
||||
</NButton>
|
||||
</NForm>
|
||||
</RForm>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
},
|
||||
|
@ -1,19 +1,22 @@
|
||||
import { NInput, NForm, NFormItem, NButton, NFlex } from 'naive-ui'
|
||||
import '../../index.scss'
|
||||
|
||||
import { NInput, NFormItem, NButton, NFlex } from 'naive-ui'
|
||||
import AppAvatar from '@/app-components/app/AppAvatar'
|
||||
import { RForm } from '@/components'
|
||||
|
||||
import dayjs from 'dayjs'
|
||||
import { useSigningActions, useSettingActions } from '@/store'
|
||||
import { rules, useCondition } from '@/app-components/app/AppLockScreen/shared'
|
||||
import useAppLockScreen from '@/app-components/app/AppLockScreen/appLockVar'
|
||||
import { useDevice } from '@/hooks'
|
||||
|
||||
import type { FormInst, InputInst } from 'naive-ui'
|
||||
import { useForm } from '@/components'
|
||||
import { APP_CATCH_KEY } from '@/app-config'
|
||||
import { removeStorage, decrypt, getStorage } from '@/utils'
|
||||
|
||||
export default defineComponent({
|
||||
name: 'UnlockScreen',
|
||||
setup() {
|
||||
const formRef = ref<FormInst | null>(null)
|
||||
const inputInstRef = ref<InputInst | null>(null)
|
||||
const [register, { validate }] = useForm()
|
||||
|
||||
const { logout } = useSigningActions()
|
||||
const { updateSettingState } = useSettingActions()
|
||||
@ -22,13 +25,13 @@ export default defineComponent({
|
||||
|
||||
const HH_MM_FORMAT = 'HH:mm'
|
||||
const AM_PM_FORMAT = 'A'
|
||||
const YY_MM_DD_FORMAT = 'YY年MM月DD日'
|
||||
const YY_MM_DD_FORMAT = 'YYYY-MM-DD'
|
||||
const DDD_FORMAT = 'ddd'
|
||||
|
||||
const state = reactive({
|
||||
lockCondition: useCondition(),
|
||||
HH_MM: dayjs().format(HH_MM_FORMAT),
|
||||
AM_PM: dayjs().locale('en').format(AM_PM_FORMAT),
|
||||
AM_PM: dayjs().format(AM_PM_FORMAT),
|
||||
YY_MM_DD: dayjs().format(YY_MM_DD_FORMAT),
|
||||
DDD: dayjs().format(DDD_FORMAT),
|
||||
})
|
||||
@ -41,30 +44,55 @@ export default defineComponent({
|
||||
state.DDD = dayjs().format(DDD_FORMAT)
|
||||
}, 86_400_000)
|
||||
|
||||
/** 退出登陆并且回到登陆页 */
|
||||
const toSigningFn = () => {
|
||||
removeStorage(APP_CATCH_KEY.appLockScreenPasswordKey, 'localStorage')
|
||||
updateSettingState('lockScreenSwitch', false)
|
||||
setTimeout(() => {
|
||||
logout()
|
||||
}, 100)
|
||||
}
|
||||
|
||||
const backToSigning = () => {
|
||||
window.$dialog.warning({
|
||||
title: '警告',
|
||||
content: '是否返回到登陆页?',
|
||||
content: '是否返回到登陆页并且重新登录',
|
||||
positiveText: '确定',
|
||||
negativeText: '取消',
|
||||
onPositiveClick: () => {
|
||||
logout()
|
||||
setTimeout(() => {
|
||||
updateSettingState('lockScreenSwitch', false)
|
||||
})
|
||||
},
|
||||
negativeText: '重新登录',
|
||||
onPositiveClick: toSigningFn,
|
||||
})
|
||||
}
|
||||
|
||||
/** 解锁 */
|
||||
const unlockScreen = () => {
|
||||
formRef.value?.validate((error) => {
|
||||
if (!error) {
|
||||
const catchPassword = getStorage<string>(
|
||||
APP_CATCH_KEY.appLockScreenPasswordKey,
|
||||
'localStorage',
|
||||
)
|
||||
|
||||
if (!catchPassword) {
|
||||
window.$dialog.warning({
|
||||
title: '警告',
|
||||
content: () => '检测到锁屏密码被修改,请重新登录',
|
||||
closable: false,
|
||||
maskClosable: false,
|
||||
closeOnEsc: false,
|
||||
positiveText: '重新登录',
|
||||
onPositiveClick: toSigningFn,
|
||||
})
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
const dCatchPassword = decrypt(catchPassword)
|
||||
|
||||
validate().then(() => {
|
||||
if (dCatchPassword === state.lockCondition.lockPassword) {
|
||||
setLockAppScreen(false)
|
||||
updateSettingState('lockScreenSwitch', false)
|
||||
removeStorage(APP_CATCH_KEY.appLockScreenPasswordKey, 'localStorage')
|
||||
|
||||
state.lockCondition = useCondition()
|
||||
} else {
|
||||
window.$message.warning('密码错误,请重新输入')
|
||||
}
|
||||
})
|
||||
}
|
||||
@ -78,18 +106,18 @@ export default defineComponent({
|
||||
...toRefs(state),
|
||||
backToSigning,
|
||||
unlockScreen,
|
||||
formRef,
|
||||
inputInstRef,
|
||||
isTabletOrSmaller,
|
||||
register,
|
||||
}
|
||||
},
|
||||
render() {
|
||||
const { isTabletOrSmaller } = this
|
||||
const { HH_MM, AM_PM, YY_MM_DD, DDD } = this
|
||||
const hmSplit = HH_MM.split(':')
|
||||
const { unlockScreen, backToSigning } = this
|
||||
const { unlockScreen, backToSigning, register } = this
|
||||
|
||||
return (
|
||||
<div class="app-lock-screen__content app-lock-screen__content--full">
|
||||
<div class="app-lock-screen__unlock">
|
||||
<div class="app-lock-screen__unlock__content">
|
||||
<div class="app-lock-screen__unlock__content-wrapper">
|
||||
@ -107,19 +135,26 @@ export default defineComponent({
|
||||
</div>
|
||||
</div>
|
||||
<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 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">
|
||||
<NInput
|
||||
ref="inputInstRef"
|
||||
autofocus
|
||||
v-model:value={this.lockCondition.lockPassword}
|
||||
type="password"
|
||||
placeholder="请输入解锁密码"
|
||||
clearable
|
||||
minlength={6}
|
||||
maxlength={12}
|
||||
onKeydown={(e: KeyboardEvent) => {
|
||||
if (e.code === 'Enter') {
|
||||
unlockScreen()
|
||||
@ -128,21 +163,27 @@ export default defineComponent({
|
||||
/>
|
||||
</NFormItem>
|
||||
<NFlex justify="space-between">
|
||||
<NButton type="primary" text onClick={backToSigning.bind(this)}>
|
||||
<NButton
|
||||
type="primary"
|
||||
text
|
||||
onClick={backToSigning.bind(this)}
|
||||
>
|
||||
返回登陆
|
||||
</NButton>
|
||||
<NButton type="primary" text onClick={unlockScreen.bind(this)}>
|
||||
<NButton
|
||||
type="primary"
|
||||
text
|
||||
onClick={unlockScreen.bind(this)}
|
||||
>
|
||||
进入系统
|
||||
</NButton>
|
||||
</NFlex>
|
||||
</NForm>
|
||||
</RForm>
|
||||
</div>
|
||||
<div class="app-lock-screen__unlock__content-date">
|
||||
<div class="current-date">
|
||||
{HH_MM} <span>{AM_PM}</span>
|
||||
</div>
|
||||
<div class="current-year">
|
||||
{YY_MM_DD} <span>{DDD}</span>
|
||||
{YY_MM_DD} <span>{DDD}</span> <span>{AM_PM}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -1,4 +1,10 @@
|
||||
.app-lock-screen__content {
|
||||
&.app-lock-screen__content--full {
|
||||
width: 100%;
|
||||
height: var(--html-height);
|
||||
@include flexCenter;
|
||||
}
|
||||
|
||||
& .app-lock-screen__input {
|
||||
& button[class*='n-button'] {
|
||||
width: 100%;
|
||||
@ -30,7 +36,7 @@
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
@include flexCenter;
|
||||
font-size: 320px;
|
||||
font-size: 16.67rem;
|
||||
gap: 80px;
|
||||
z-index: 0;
|
||||
|
||||
@ -79,9 +85,23 @@
|
||||
|
||||
& .current-year,
|
||||
& .current-date span {
|
||||
font-size: 1.5rem;
|
||||
font-size: 1.875rem;
|
||||
line-height: 2.25rem;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.ray-template--light {
|
||||
.app-lock-screen__unlock__content-bg__wrapper {
|
||||
background-color: #fff !important;
|
||||
}
|
||||
|
||||
.app-lock-screen__unlock__content-bg {
|
||||
& .left,
|
||||
& .right {
|
||||
background-color: rgba(244, 244, 245, 1) !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,22 +1,11 @@
|
||||
/**
|
||||
*
|
||||
* 这里没有做解锁密码校验, 只要符合校验规则值皆可
|
||||
* 可以根据需求自行更改
|
||||
*/
|
||||
|
||||
import './index.scss'
|
||||
|
||||
import { RModal } from '@/components'
|
||||
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({
|
||||
name: 'AppLockScreen',
|
||||
setup() {
|
||||
const { getLockAppScreen } = useAppLockScreen()
|
||||
const { updateSettingState } = useSettingActions()
|
||||
const { getLockScreenSwitch } = useSettingGetters()
|
||||
const lockScreenSwitchRef = computed({
|
||||
@ -28,12 +17,9 @@ const AppLockScreen = defineComponent({
|
||||
|
||||
return {
|
||||
lockScreenSwitchRef,
|
||||
getLockAppScreen,
|
||||
}
|
||||
},
|
||||
render() {
|
||||
const { getLockAppScreen } = this
|
||||
|
||||
return (
|
||||
<RModal
|
||||
v-model:show={this.lockScreenSwitchRef}
|
||||
@ -42,12 +28,10 @@ const AppLockScreen = defineComponent({
|
||||
autoFocus={false}
|
||||
maskClosable={false}
|
||||
closeOnEsc={false}
|
||||
preset={!getLockAppScreen() ? 'dialog' : void 0}
|
||||
preset="dialog"
|
||||
title="锁定屏幕"
|
||||
>
|
||||
<div class="app-lock-screen__content">
|
||||
{!getLockAppScreen() ? <LockScreen /> : <UnlockScreen />}
|
||||
</div>
|
||||
<LockScreen />
|
||||
</RModal>
|
||||
)
|
||||
},
|
||||
|
@ -7,7 +7,8 @@ import {
|
||||
getStorage,
|
||||
} from '@/utils'
|
||||
import { useSettingGetters } from '@/store'
|
||||
import { APP_CATCH_KEY } from '@/app-config'
|
||||
import { APP_CATCH_KEY, GLOBAL_CLASS_NAMES, APP_THEME } from '@/app-config'
|
||||
import { useWindowSize } from '@vueuse/core'
|
||||
|
||||
import type { SettingState } from '@/store/modules/setting/types'
|
||||
|
||||
@ -15,40 +16,50 @@ export default defineComponent({
|
||||
name: 'AppStyleProvider',
|
||||
setup(_, { expose }) {
|
||||
const { getAppTheme } = useSettingGetters()
|
||||
const { height, width } = useWindowSize()
|
||||
|
||||
/** 同步主题色变量至 body, 如果未获取到缓存值则已默认值填充 */
|
||||
// 同步主题色变量至 html,如果未获取到缓存值则已默认值填充
|
||||
const syncPrimaryColorToBody = () => {
|
||||
// 默认主题色
|
||||
const {
|
||||
appPrimaryColor: { primaryColor, primaryFadeColor },
|
||||
} = __APP_CFG__ // 默认主题色
|
||||
const body = document.body
|
||||
} = APP_THEME
|
||||
// 主题色配置 class 名
|
||||
const { rayTemplateThemePrimaryColor, rayTemplateThemePrimaryFadeColor } =
|
||||
GLOBAL_CLASS_NAMES
|
||||
|
||||
const html = document.documentElement
|
||||
|
||||
// 获取缓存 naive ui 配置项
|
||||
const primaryColorOverride = getStorage<SettingState>(
|
||||
APP_CATCH_KEY.appPiniaSettingStore,
|
||||
'localStorage',
|
||||
) // 获取缓存 naive ui 配置项
|
||||
)
|
||||
|
||||
if (primaryColorOverride) {
|
||||
// 获取主色调
|
||||
const p = get(
|
||||
primaryColorOverride,
|
||||
'primaryColorOverride.common.primaryColor',
|
||||
primaryColor,
|
||||
) // 获取主色调
|
||||
const fp = colorToRgba(p, 0.38) // 将主色调任意颜色转换为 rgba 格式
|
||||
)
|
||||
// 将主色调任意颜色转换为 rgba 格式
|
||||
const fp = colorToRgba(p, 0.85)
|
||||
|
||||
/** 设置全局主题色 css 变量 */
|
||||
body.style.setProperty('--ray-theme-primary-color', p) // 主色调
|
||||
body.style.setProperty(
|
||||
'--ray-theme-primary-fade-color',
|
||||
// 设置全局主题色 css 变量
|
||||
html.style.setProperty(rayTemplateThemePrimaryColor, p) // 主色调
|
||||
// 降低透明度后的主色调
|
||||
html.style.setProperty(
|
||||
rayTemplateThemePrimaryFadeColor,
|
||||
fp || primaryFadeColor,
|
||||
) // 降低透明度后的主色调
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/** 隐藏加载动画 */
|
||||
// 隐藏加载动画
|
||||
const hiddenLoadingAnimation = () => {
|
||||
/** pre-loading-animation 是默认 id */
|
||||
const el = document.getElementById('pre-loading-animation')
|
||||
// pre-loading-animation 是默认 id
|
||||
const el = document.getElementById(GLOBAL_CLASS_NAMES.preLoadingAnimation)
|
||||
|
||||
if (el) {
|
||||
setStyle(el, {
|
||||
@ -57,38 +68,30 @@ export default defineComponent({
|
||||
}
|
||||
}
|
||||
|
||||
/** 切换主题时, 同步更新 body class 以便于进行自定义 css 配置 */
|
||||
// 切换主题时,同步更新 html class 以便于进行自定义 css 配置
|
||||
const updateGlobalThemeClass = (bool: boolean) => {
|
||||
/**
|
||||
*
|
||||
* 初始化时根据当前主题色进行初始化 body 的 class 属性
|
||||
*
|
||||
* 根据 getAppTheme 进行初始化
|
||||
*/
|
||||
const body = document.body
|
||||
const darkClassName = 'ray-template--dark' // 暗色类名
|
||||
const lightClassName = 'ray-template--light' // 明亮色类名
|
||||
const html = document.documentElement
|
||||
const { darkClassName, lightClassName } = GLOBAL_CLASS_NAMES
|
||||
|
||||
bool
|
||||
? removeClass(body, lightClassName)
|
||||
: removeClass(body, darkClassName)
|
||||
? removeClass(html, lightClassName)
|
||||
: removeClass(html, darkClassName)
|
||||
|
||||
setClass(body, bool ? darkClassName : lightClassName)
|
||||
setClass(html, bool ? darkClassName : lightClassName)
|
||||
}
|
||||
|
||||
syncPrimaryColorToBody()
|
||||
hiddenLoadingAnimation()
|
||||
|
||||
// 当切换主题时,更新 body 当前的注入 class
|
||||
watch(
|
||||
() => getAppTheme.value,
|
||||
(ndata) => {
|
||||
updateGlobalThemeClass(ndata)
|
||||
},
|
||||
{
|
||||
immediate: true,
|
||||
},
|
||||
)
|
||||
watchEffect(() => {
|
||||
// 当切换主题时,更新 html 当前的注入 class
|
||||
updateGlobalThemeClass(getAppTheme.value)
|
||||
// 注入全局宽高尺寸
|
||||
setStyle(document.documentElement, {
|
||||
[GLOBAL_CLASS_NAMES.htmlHeight]: `${height.value}px`,
|
||||
[GLOBAL_CLASS_NAMES.htmlWidth]: `${width.value}px`,
|
||||
})
|
||||
})
|
||||
|
||||
expose()
|
||||
},
|
||||
|
@ -28,18 +28,10 @@ export default defineComponent({
|
||||
if (version !== cacheVersion) {
|
||||
modalShow.value = true
|
||||
|
||||
setStorage<string>(
|
||||
APP_CATCH_KEY.appVersionProvider,
|
||||
version,
|
||||
'localStorage',
|
||||
)
|
||||
setStorage(APP_CATCH_KEY.appVersionProvider, version, 'localStorage')
|
||||
}
|
||||
} else {
|
||||
setStorage<string>(
|
||||
APP_CATCH_KEY.appVersionProvider,
|
||||
version,
|
||||
'localStorage',
|
||||
)
|
||||
setStorage(APP_CATCH_KEY.appVersionProvider, version, 'localStorage')
|
||||
}
|
||||
|
||||
return {
|
||||
@ -61,7 +53,7 @@ export default defineComponent({
|
||||
title="发现新版本"
|
||||
content="当前版本已更新,点击确认加载新版本~"
|
||||
zIndex={999999999}
|
||||
dad
|
||||
draggable
|
||||
positiveText="确认"
|
||||
negativeText="取消"
|
||||
onPositiveClick={logout}
|
||||
|
@ -26,7 +26,7 @@ export default defineComponent({
|
||||
const { getWatermarkConfig, getWatermarkSwitch } = this
|
||||
|
||||
return getWatermarkSwitch ? (
|
||||
<NWatermark cross fullscreen {...getWatermarkConfig} />
|
||||
<NWatermark {...getWatermarkConfig} fullscreen />
|
||||
) : null
|
||||
},
|
||||
})
|
||||
|
@ -1,6 +1,24 @@
|
||||
import type { AppMenuConfig, PreloadingConfig } from '@/types'
|
||||
import type { MessageProviderProps } from 'naive-ui'
|
||||
|
||||
/**
|
||||
*
|
||||
* @description
|
||||
* 全局注入到 html 的样式类名。
|
||||
*
|
||||
* 如果涉及到全局主题色的 class name,需要修改的时候记得全局替换;
|
||||
* 避免样式出现奇奇怪怪的问题。
|
||||
*/
|
||||
export const GLOBAL_CLASS_NAMES = {
|
||||
darkClassName: 'ray-template--dark',
|
||||
lightClassName: 'ray-template--light',
|
||||
rayTemplateThemePrimaryColor: '--ray-theme-primary-color',
|
||||
rayTemplateThemePrimaryFadeColor: '--ray-theme-primary-fade-color',
|
||||
preLoadingAnimation: 'pre-loading-animation',
|
||||
htmlHeight: '--html-height',
|
||||
htmlWidth: '--html-width',
|
||||
} as const
|
||||
|
||||
/**
|
||||
*
|
||||
* @description
|
||||
@ -75,6 +93,8 @@ export const APP_CATCH_KEY_PREFIX = ''
|
||||
* - appPiniaMenuStore: pinia menu store key
|
||||
* - appPiniaSigningStore: pinia signing store key
|
||||
* - appVersionProvider: 版本信息缓存 key
|
||||
* - appMenuTagOptions: 标签页菜单列表
|
||||
* - appLockScreenPasswordKey: 锁屏密码缓存 key
|
||||
*/
|
||||
export const APP_CATCH_KEY = {
|
||||
signing: 'signing',
|
||||
@ -88,6 +108,8 @@ export const APP_CATCH_KEY = {
|
||||
appVersionProvider: 'appVersionProvider',
|
||||
isAppLockScreen: 'isAppLockScreen',
|
||||
appGlobalSearchOptions: 'appGlobalSearchOptions',
|
||||
appMenuTagOptions: 'appMenuTagOptions',
|
||||
appLockScreenPasswordKey: 'appLockScreenPasswordKey',
|
||||
} as const
|
||||
|
||||
/**
|
||||
|
@ -3,35 +3,36 @@ import type { AppTheme } from '@/types'
|
||||
export const APP_THEME: AppTheme = {
|
||||
/**
|
||||
*
|
||||
* 系统主题颜色预设色盘
|
||||
* 支持 RGBA、RGB、十六进制
|
||||
* @description
|
||||
* 系统主题颜色预设色盘。
|
||||
* 支持 RGBA、RGB、十六进制。
|
||||
*/
|
||||
appThemeColors: [
|
||||
'#2d8cf0',
|
||||
'#3f9eff',
|
||||
'#ff42bc',
|
||||
'#ee4f12',
|
||||
'#a6e4f7',
|
||||
'#dbcb02',
|
||||
'#18A058',
|
||||
'#18a058',
|
||||
],
|
||||
/** 系统主题色 */
|
||||
// 系统主题色
|
||||
appPrimaryColor: {
|
||||
/** 主题色 */
|
||||
// 主题色
|
||||
primaryColor: '#2d8cf0',
|
||||
/** 主题辅助色(用于整体 hover、active 等之类颜色) */
|
||||
primaryFadeColor: 'rgba(45, 140, 240, 0.3)',
|
||||
// 主题辅助色(用于整体 hover、active 等之类颜色)
|
||||
primaryFadeColor: 'rgba(45, 140, 240, 0.85)',
|
||||
},
|
||||
/**
|
||||
*
|
||||
* 配置系统 naive-ui 主题色
|
||||
* 官网文档地址: <https://www.naiveui.com/zh-CN/dark/docs/customize-theme>
|
||||
* @description
|
||||
* 配置系统 naive-ui 主题色。
|
||||
* 官网文档地址: <https://www.naiveui.com/zh-CN/dark/docs/customize-theme>。
|
||||
*
|
||||
* 注意:
|
||||
* - 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>
|
||||
*
|
||||
* @example
|
||||
@ -47,17 +48,24 @@ export const APP_THEME: AppTheme = {
|
||||
* ```
|
||||
*/
|
||||
appNaiveUIThemeOverrides: {
|
||||
dark: {},
|
||||
light: {},
|
||||
dark: {
|
||||
common: {
|
||||
borderRadius: '4px',
|
||||
baseColor: 'rgb(18, 18, 18)',
|
||||
},
|
||||
},
|
||||
light: {
|
||||
common: {
|
||||
borderRadius: '4px',
|
||||
baseColor: 'rgb(255, 255, 255)',
|
||||
},
|
||||
},
|
||||
appNaiveUIThemeOverridesCommon: {
|
||||
dark: {},
|
||||
light: {},
|
||||
},
|
||||
/**
|
||||
*
|
||||
* 配置 echart 主题颜色
|
||||
* 约定配置时以:主题名称为文件名,其文件夹下两个主题风格的 json 文件。并且暗色主题必须为 xxx-dark.json
|
||||
* @description
|
||||
* 配置 echart 主题颜色。
|
||||
* 约定配置时以:主题名称为文件名,其文件夹下两个主题风格的 json 文件。并且暗色主题必须为 xxx-dark.json。
|
||||
*
|
||||
* [文档地址](https://xiaodaigua-ray.github.io/ray-template-doc/ray-template-docs/advanced/echart-themes.html)
|
||||
*/
|
||||
|
@ -8,4 +8,12 @@ export const APP_REGEX: Record<string, RegExp> = {
|
||||
/** css 尺寸单位匹配 */
|
||||
cssUnit:
|
||||
/^\d+(\.\d+)?(px|em|rem|%|vw|vh|vmin|vmax|cm|mm|in|pt|pc|ch|ex|q|s|ms|deg|rad|turn|grad|hz|khz|dpi|dpcm|dppx|fr|auto)$/,
|
||||
/**
|
||||
*
|
||||
* @description
|
||||
* 匹配 css 单位是否为 auto, unset, fit-content, max-content, min-content, initial, inherit, revert, revert-layer, -webkit-fill-available,
|
||||
* -webkit-max-content, -webkit-min-content, -webkit-revert, -webkit-revert-layer, -webkit-fill-available。
|
||||
*/
|
||||
cssSize:
|
||||
/^auto|unset|fit-content|max-content|min-content|initial|inherit|revert|revert-layer|[-\w]+-webkit-fill-available$/,
|
||||
}
|
||||
|
@ -4,7 +4,7 @@ import type { Ref } from 'vue'
|
||||
/**
|
||||
*
|
||||
* @description
|
||||
* 内容区域 ref 注册
|
||||
* 内容区域 shallowRef 注册
|
||||
* 可以控制内容区域当前滚动位置
|
||||
* 如果你需要在切换路由时候配置自定义滚动到某个视图区域时, 可以使用该属性提供的方法(scrollTo)
|
||||
*
|
||||
@ -16,12 +16,12 @@ import type { Ref } from 'vue'
|
||||
* })
|
||||
*/
|
||||
export const LAYOUT_CONTENT_REF: Readonly<Ref<LayoutInst | null>> =
|
||||
ref<LayoutInst | null>(null)
|
||||
shallowRef<LayoutInst | null>(null)
|
||||
|
||||
/**
|
||||
*
|
||||
* @description
|
||||
* 侧边滚动栏滚动 ref 注册。
|
||||
* 侧边滚动栏滚动 shallowRef 注册。
|
||||
* 可以控制侧边滚动栏滚动位置。
|
||||
*
|
||||
* 请注意使用时机。建议使用 nextTick() 等待 dom 挂载后再执行该方法。
|
||||
@ -31,7 +31,7 @@ export const LAYOUT_CONTENT_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 = {
|
||||
/** 是否启用路由切换时顶部加载条 */
|
||||
|
@ -16,7 +16,7 @@ import type {
|
||||
* 当然你也可以根据 request instance 来特殊处理, 这里暂时不做演示
|
||||
*/
|
||||
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) {
|
||||
// TODO: 根据 url 不同是否设置 token
|
||||
|
@ -40,14 +40,14 @@ function useRequest<
|
||||
fetchOptions: AppRawRequestConfig<Response>,
|
||||
option?: UseRequestOptions<Response, HookPlusParams, HookPlusPlugin>,
|
||||
) {
|
||||
const fc = () => {
|
||||
const fn = () => {
|
||||
const cb = request<Response>(fetchOptions)
|
||||
|
||||
return cb
|
||||
}
|
||||
|
||||
const hooks = useHookPlusRequest<Response, HookPlusParams>(
|
||||
fc,
|
||||
fn,
|
||||
Object.assign({}, option),
|
||||
)
|
||||
|
||||
|
@ -1,10 +1,3 @@
|
||||
/**
|
||||
*
|
||||
* 请求拦截器与响应拦截器
|
||||
* 如果有需要拓展拦截器, 请在 inject 目录下参照示例方法继续拓展
|
||||
* 该页面不应该做过多的改动与配置
|
||||
*/
|
||||
|
||||
import axios from 'axios'
|
||||
import { AXIOS_CONFIG } from '@/app-config'
|
||||
import { useAxiosInterceptor } from '@/axios/utils/interceptor'
|
||||
@ -17,23 +10,33 @@ import {
|
||||
setupRequestErrorInterceptor,
|
||||
} from '@/axios/axios-interceptor/request'
|
||||
|
||||
import type { AxiosInstanceExpand } from './types'
|
||||
import type { AxiosInstanceExpand, RequestInterceptorConfig } from './types'
|
||||
|
||||
// 创建 axios 实例
|
||||
const server: AxiosInstanceExpand = axios.create(AXIOS_CONFIG)
|
||||
// 获取拦截器实例
|
||||
const { createAxiosInstance, beforeFetch, fetchError } = useAxiosInterceptor()
|
||||
|
||||
// 请求拦截器
|
||||
server.interceptors.request.use(
|
||||
(request) => {
|
||||
createAxiosInstance(request, 'requestInstance') // 生成 request instance
|
||||
setupRequestInterceptor() // 初始化拦截器所有已注入方法
|
||||
beforeFetch('requestInstance', 'implementRequestInterceptorArray', 'ok') // 执行拦截器所有已注入方法
|
||||
// 生成 request instance
|
||||
createAxiosInstance(
|
||||
request as RequestInterceptorConfig<unknown>,
|
||||
'requestInstance',
|
||||
)
|
||||
// 初始化拦截器所有已注入方法
|
||||
setupRequestInterceptor()
|
||||
// 执行拦截器所有已注入方法
|
||||
beforeFetch('requestInstance', 'implementRequestInterceptorArray', 'ok')
|
||||
|
||||
return request
|
||||
},
|
||||
(error) => {
|
||||
setupRequestErrorInterceptor() // 初始化拦截器所有已注入方法(错误状态)
|
||||
fetchError('requestError', error, 'implementRequestInterceptorErrorArray') // 执行所有已注入方法
|
||||
// 初始化拦截器所有已注入方法(错误状态)
|
||||
setupRequestErrorInterceptor()
|
||||
// 执行所有已注入方法
|
||||
fetchError('requestError', error, 'implementRequestInterceptorErrorArray')
|
||||
|
||||
return Promise.reject(error)
|
||||
},
|
||||
@ -42,17 +45,22 @@ server.interceptors.request.use(
|
||||
// 响应拦截器
|
||||
server.interceptors.response.use(
|
||||
(response) => {
|
||||
createAxiosInstance(response, 'responseInstance') // 创建响应实例
|
||||
setupResponseInterceptor() // 注入响应成功待执行队列
|
||||
beforeFetch('responseInstance', 'implementResponseInterceptorArray', 'ok') // 执行响应成功拦截器
|
||||
// 创建响应实例
|
||||
createAxiosInstance(response, 'responseInstance')
|
||||
// 注入响应成功待执行队列
|
||||
setupResponseInterceptor()
|
||||
// 执行响应成功拦截器
|
||||
beforeFetch('responseInstance', 'implementResponseInterceptorArray', 'ok')
|
||||
|
||||
const { data } = response
|
||||
|
||||
return Promise.resolve(data)
|
||||
},
|
||||
(error) => {
|
||||
setupResponseErrorInterceptor() // 注入响应失败待执行队列
|
||||
fetchError('responseError', error, 'implementResponseInterceptorErrorArray') // 执行响应失败后拦截器
|
||||
// 注入响应失败待执行队列
|
||||
setupResponseErrorInterceptor()
|
||||
// 执行响应失败后拦截器
|
||||
fetchError('responseError', error, 'implementResponseInterceptorErrorArray')
|
||||
|
||||
return Promise.reject(error)
|
||||
},
|
||||
|
@ -1,14 +1,3 @@
|
||||
/**
|
||||
*
|
||||
* axios 拦截器注入
|
||||
*
|
||||
* 请求拦截器、响应拦截器
|
||||
* 暴露启动方法调用所有已注册方法
|
||||
*
|
||||
* 该拦截器仅适合放置公共的 axios 拦截器操作, 并且采用队列形式管理请求拦截器的注入
|
||||
* 所以在使用的时候, 需要按照约定格式进行参数传递
|
||||
*/
|
||||
|
||||
import RequestCanceler from '@/axios/utils/RequestCanceler'
|
||||
import { getAppEnvironment } from '@/utils'
|
||||
|
||||
@ -24,31 +13,36 @@ import type {
|
||||
import type { AnyFC } from '@/types'
|
||||
import type { AxiosError } from 'axios'
|
||||
|
||||
/** 当前请求的实例 */
|
||||
// 当前请求的实例
|
||||
const axiosFetchInstance: AxiosFetchInstance = {
|
||||
requestInstance: null,
|
||||
responseInstance: null,
|
||||
}
|
||||
/** 请求失败返回值 */
|
||||
// 请求失败返回值
|
||||
const axiosFetchError: AxiosFetchError<AxiosError<unknown, unknown>> = {
|
||||
requestError: null,
|
||||
responseError: null,
|
||||
}
|
||||
/** 请求队列(区分 resolve 与 reject 状态) */
|
||||
// 请求队列(区分 resolve 与 reject 状态)
|
||||
const implement: ImplementQueue = {
|
||||
implementRequestInterceptorArray: [],
|
||||
implementResponseInterceptorArray: [],
|
||||
}
|
||||
// 请求失败队列
|
||||
const errorImplement: ErrorImplementQueue = {
|
||||
implementRequestInterceptorErrorArray: [],
|
||||
implementResponseInterceptorErrorArray: [],
|
||||
}
|
||||
|
||||
/** 取消器实例 */
|
||||
type ImplementKeys = keyof ImplementQueue
|
||||
|
||||
type ErrorImplementKeys = keyof ErrorImplementQueue
|
||||
|
||||
// 取消器实例
|
||||
export const axiosCanceler = new RequestCanceler()
|
||||
|
||||
export const useAxiosInterceptor = () => {
|
||||
/** 创建拦截器实例 */
|
||||
// 创建拦截器实例
|
||||
const createAxiosInstance = (
|
||||
instance: RequestInterceptorConfig | ResponseInterceptorConfig,
|
||||
instanceKey: keyof AxiosFetchInstance,
|
||||
@ -60,29 +54,33 @@ export const useAxiosInterceptor = () => {
|
||||
instance as ResponseInterceptorConfig)
|
||||
}
|
||||
|
||||
/** 获取当前实例 */
|
||||
// 获取当前实例
|
||||
const getAxiosInstance = (instanceKey: keyof AxiosFetchInstance) => {
|
||||
return axiosFetchInstance[instanceKey]
|
||||
}
|
||||
|
||||
/** 设置注入方法队列 */
|
||||
// 设置注入方法队列
|
||||
const setImplement = (
|
||||
key: keyof ImplementQueue | keyof ErrorImplementQueue,
|
||||
key: ImplementKeys | ErrorImplementKeys,
|
||||
func: AnyFC[],
|
||||
fetchType: FetchType,
|
||||
) => {
|
||||
fetchType === 'ok' ? (implement[key] = func) : (errorImplement[key] = func)
|
||||
fetchType === 'ok'
|
||||
? (implement[key as ImplementKeys] = func)
|
||||
: (errorImplement[key as ErrorImplementKeys] = func)
|
||||
}
|
||||
|
||||
/** 获取队列中所有的所有拦截器方法 */
|
||||
// 获取队列中所有的所有拦截器方法
|
||||
const getImplement = (
|
||||
key: keyof ImplementQueue | keyof ErrorImplementQueue,
|
||||
key: ImplementKeys | ErrorImplementKeys,
|
||||
fetchType: FetchType,
|
||||
): AnyFC[] => {
|
||||
return fetchType === 'ok' ? implement[key] : errorImplement[key]
|
||||
return fetchType === 'ok'
|
||||
? implement[key as ImplementKeys]
|
||||
: errorImplement[key as ErrorImplementKeys]
|
||||
}
|
||||
|
||||
/** 队列执行器 */
|
||||
// 队列执行器
|
||||
const implementer = (funcs: AnyFC[], ...args: any[]) => {
|
||||
if (Array.isArray(funcs)) {
|
||||
funcs.forEach((curr) => {
|
||||
@ -93,16 +91,16 @@ export const useAxiosInterceptor = () => {
|
||||
}
|
||||
}
|
||||
|
||||
/** 请求、响应前执行拦截器队列中的所有方法 */
|
||||
// 请求、响应前执行拦截器队列中的所有方法
|
||||
const beforeFetch = (
|
||||
key: keyof AxiosFetchInstance,
|
||||
implementKey: keyof ImplementQueue | keyof ErrorImplementQueue,
|
||||
implementKey: ImplementKeys | ErrorImplementKeys,
|
||||
fetchType: FetchType,
|
||||
) => {
|
||||
const funcArr =
|
||||
fetchType === 'ok'
|
||||
? implement[implementKey]
|
||||
: errorImplement[implementKey]
|
||||
? implement[implementKey as ImplementKeys]
|
||||
: errorImplement[implementKey as ErrorImplementKeys]
|
||||
const instance = getAxiosInstance(key)
|
||||
const { MODE } = getAppEnvironment()
|
||||
|
||||
@ -111,11 +109,11 @@ export const useAxiosInterceptor = () => {
|
||||
}
|
||||
}
|
||||
|
||||
/** 请求、响应错误时执行队列中所有方法 */
|
||||
// 请求、响应错误时执行队列中所有方法
|
||||
const fetchError = (
|
||||
key: keyof AxiosFetchError,
|
||||
error: AxiosError<unknown, unknown>,
|
||||
errorImplementKey: keyof ErrorImplementQueue,
|
||||
errorImplementKey: ErrorImplementKeys,
|
||||
) => {
|
||||
axiosFetchError[key] = error
|
||||
|
||||
|
@ -1,41 +0,0 @@
|
||||
import { RCollapseGrid, RForm } from '@/components'
|
||||
|
||||
import { collapseGridProps, formProps } from '@/components'
|
||||
|
||||
import type { FormProps, GridProps } from 'naive-ui'
|
||||
|
||||
/**
|
||||
*
|
||||
* @description
|
||||
* 基于 RCollapseGrid 和 RForm 的折叠表单组件;
|
||||
* 同样也继承了 RCollapseGrid 组件的缺点,必须使用 NFormItemGi, NGridItem 组件包裹表单项。
|
||||
*/
|
||||
export default defineComponent({
|
||||
name: 'RCollapse',
|
||||
props: Object.assign({}, formProps, {
|
||||
...collapseGridProps,
|
||||
open: {
|
||||
type: Boolean,
|
||||
default: true,
|
||||
},
|
||||
cols: {
|
||||
type: Number,
|
||||
default: 4,
|
||||
},
|
||||
bordered: {
|
||||
type: Boolean,
|
||||
default: true,
|
||||
},
|
||||
}),
|
||||
render() {
|
||||
const { $slots, $props } = this
|
||||
const { labelPlacement, showFeedback, ...rest } = $props as FormProps &
|
||||
GridProps
|
||||
|
||||
return (
|
||||
<RForm {...rest} labelPlacement="top" showFeedback={false}>
|
||||
<RCollapseGrid {...rest}>{$slots}</RCollapseGrid>
|
||||
</RForm>
|
||||
)
|
||||
},
|
||||
})
|
@ -1,75 +0,0 @@
|
||||
import type { TableProps, RTableInst } from '@/components'
|
||||
import type { UsePaginationReturn } from '@/hooks'
|
||||
import type { Recordable } from '@/types'
|
||||
|
||||
export type FormatRangeTime = {
|
||||
/**
|
||||
*
|
||||
* @description
|
||||
* 请求参数中需要格式化的时间参数字段。
|
||||
*/
|
||||
key: string | number
|
||||
/**
|
||||
*
|
||||
* @description
|
||||
* 后端接口中需要的起止时间参数字段。
|
||||
*/
|
||||
target: [string | number, string | number]
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @description
|
||||
* Pagination 分页配置。
|
||||
*/
|
||||
export type TablePagination = Pick<
|
||||
UsePaginationReturn[1],
|
||||
'getItemCount' | 'getPage' | 'getPageSize'
|
||||
>
|
||||
|
||||
export interface TableRequestConfig<Params = Recordable> {
|
||||
/**
|
||||
*
|
||||
* @description
|
||||
* 请求参数。
|
||||
* @default undefined
|
||||
*/
|
||||
params?: Params
|
||||
/**
|
||||
*
|
||||
* @description
|
||||
* 格式化时间范围,该字段用于格式化时间范围。
|
||||
* 并且仅支持 NDatePicker range 模式。
|
||||
* 如果在请求的参数中,有时间段的字段,且需要格式化时间段,则需要配置该字段。
|
||||
*
|
||||
* @default undefined
|
||||
*/
|
||||
formatRangeTime?: FormatRangeTime[]
|
||||
}
|
||||
|
||||
export type TableProProps = Omit<TableProps, 'pagination'>
|
||||
|
||||
export interface TableProInst extends Omit<RTableInst, 'getTableInstance'> {
|
||||
/**
|
||||
*
|
||||
* @description
|
||||
* 获取 pagination 更新值。
|
||||
*/
|
||||
getTablePagination: () => TablePagination
|
||||
/**
|
||||
*
|
||||
* @description
|
||||
* 手动触发表格请求,用于手动刷新表格。
|
||||
*/
|
||||
runTableRequest: (extraConfig?: TableRequestConfig) => void
|
||||
/**
|
||||
*
|
||||
* @param extraConfig 额外请求合并配置项
|
||||
*
|
||||
* @description
|
||||
* 获取当前内部表格请求参数。
|
||||
*/
|
||||
getCurrentTableRequestParams: <T = Recordable>(
|
||||
extraConfig?: TableRequestConfig<T>,
|
||||
) => TableRequestConfig<T>['params'] & Recordable
|
||||
}
|
@ -1,4 +0,0 @@
|
||||
import RCollapse from './RCollapse/Collapse'
|
||||
|
||||
export * from './RTablePro'
|
||||
export { RCollapse }
|
@ -1,42 +0,0 @@
|
||||
import { NForm } from 'naive-ui'
|
||||
|
||||
import props from './props'
|
||||
import { call } from '@/utils'
|
||||
import { useTemplateRef } from 'vue'
|
||||
|
||||
import type { RFormInst } from './types'
|
||||
import type { FormProps } from 'naive-ui'
|
||||
|
||||
export default defineComponent({
|
||||
name: 'RForm',
|
||||
props,
|
||||
setup(props, { expose }) {
|
||||
const formRef = useTemplateRef<RFormInst>('formRef')
|
||||
|
||||
onMounted(() => {
|
||||
// 主动调用 register 方法,满足 useForm 方法正常调用
|
||||
const { onRegister } = props
|
||||
|
||||
if (onRegister && formRef.value) {
|
||||
call(onRegister, formRef.value)
|
||||
}
|
||||
})
|
||||
|
||||
expose()
|
||||
|
||||
return {
|
||||
formRef,
|
||||
}
|
||||
},
|
||||
render() {
|
||||
const { $attrs, $props, $slots } = this
|
||||
|
||||
return (
|
||||
<NForm {...$attrs} {...($props as FormProps)} ref="formRef">
|
||||
{{
|
||||
...$slots,
|
||||
}}
|
||||
</NForm>
|
||||
)
|
||||
},
|
||||
})
|
@ -1,114 +0,0 @@
|
||||
import { cloneDeep } from 'lodash-es'
|
||||
|
||||
import type {
|
||||
RFormInst,
|
||||
FormValidateCallback,
|
||||
ShouldRuleBeApplied,
|
||||
RFormRules,
|
||||
} from '../types'
|
||||
import type { Recordable } from '@/types'
|
||||
|
||||
/**
|
||||
*
|
||||
* @description
|
||||
* 获取 RForm 实例。
|
||||
* 让你能够便捷的调用相关的一些已封装方法。
|
||||
*
|
||||
* @warning
|
||||
* 值得注意的是,必须手动调用 register 方法,否则不能正常使用。
|
||||
* 在使用 相关 hooks 的时候,需要注意生命周期,确保 register 方法已经被调用与表格实例已经被初始化;
|
||||
* 不要在父组件 create 阶段就去调用 hook,如果需要,请使用 nextTick 包裹。
|
||||
*
|
||||
* @example
|
||||
* defineComponent({
|
||||
* setup() {
|
||||
* const [register, { ...Hooks }] = useForm()
|
||||
*
|
||||
* return {
|
||||
* register,
|
||||
* ...Hooks,
|
||||
* }
|
||||
* },
|
||||
* render() {
|
||||
* const { register, ...Hooks } = this
|
||||
*
|
||||
* return <RForm onRegister={register} />
|
||||
* },
|
||||
* })
|
||||
*/
|
||||
const useForm = <T extends Recordable, R extends RFormRules>(
|
||||
model?: T,
|
||||
rules?: R,
|
||||
) => {
|
||||
const formRef = ref<RFormInst>()
|
||||
|
||||
const register = (inst: RFormInst) => {
|
||||
if (inst) {
|
||||
formRef.value = inst
|
||||
}
|
||||
}
|
||||
|
||||
const getFormInstance = () => {
|
||||
if (!formRef.value) {
|
||||
throw new Error(
|
||||
'[useForm]: form instance is not ready yet. if you are using useForm, please make sure you have called register method in onRegister event.',
|
||||
)
|
||||
}
|
||||
|
||||
return formRef.value
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @description
|
||||
* 验证表单,Promise rejection 的返回值类型是 FormValidationError[]。
|
||||
*
|
||||
* @see https://www.naiveui.com/zh-CN/dark/components/form#inline.vue
|
||||
*/
|
||||
const validate = (
|
||||
callback?: FormValidateCallback,
|
||||
shouldRuleBeApplied?: ShouldRuleBeApplied,
|
||||
) => getFormInstance().validate.call(null, callback, shouldRuleBeApplied)
|
||||
|
||||
/**
|
||||
*
|
||||
* @description
|
||||
* 还原表单到未验证状态。
|
||||
*
|
||||
* @see https://www.naiveui.com/zh-CN/dark/components/form#Form-Methods
|
||||
*/
|
||||
const restoreValidation = () => getFormInstance().restoreValidation.call(null)
|
||||
|
||||
/**
|
||||
*
|
||||
* @description
|
||||
* 获取表项中收集到的值的对象。
|
||||
*
|
||||
* 调用该方法时,需要确保初始化 useForm 方法的时候传入了 model,否则可能有意想不到的问题发生。
|
||||
*/
|
||||
const formModel = () => cloneDeep(model) || ({} as T)
|
||||
|
||||
/**
|
||||
*
|
||||
* @description
|
||||
* 获取验证表单项的规则。
|
||||
*
|
||||
* 调用该方法时,需要确保初始化 useForm 方法的时候传入了 rules,否则可能有意想不到的问题发生。
|
||||
*/
|
||||
const formRules = () => cloneDeep(rules) || ({} as R)
|
||||
|
||||
return [
|
||||
register,
|
||||
{
|
||||
getFormInstance,
|
||||
validate,
|
||||
restoreValidation,
|
||||
formModel,
|
||||
formRules,
|
||||
},
|
||||
] as const
|
||||
}
|
||||
|
||||
export type UseFormReturn = ReturnType<typeof useForm>
|
||||
|
||||
export default useForm
|
@ -1,24 +0,0 @@
|
||||
import { formProps } from 'naive-ui'
|
||||
|
||||
import type { MaybeArray } from '@/types'
|
||||
import type { RFormInst } from './types'
|
||||
|
||||
const props = {
|
||||
...formProps,
|
||||
/**
|
||||
*
|
||||
* @description
|
||||
* RForm 注册挂载成功后触发的事件。
|
||||
* 可以结合 useForm 方法中的 register 方法使用,然后便捷的使用 hooks。
|
||||
*
|
||||
* @default null
|
||||
*/
|
||||
onRegister: {
|
||||
type: [Function, Array] as PropType<
|
||||
MaybeArray<(formInst: RFormInst) => void>
|
||||
>,
|
||||
default: null,
|
||||
},
|
||||
} as const
|
||||
|
||||
export default props
|
@ -1,101 +0,0 @@
|
||||
import interact from 'interactjs'
|
||||
|
||||
import type { ModalProps } from 'naive-ui'
|
||||
import type { RModalProps } from './types'
|
||||
|
||||
interface SetupDraggableOptions {
|
||||
scheduler?: (event: Interact.DragEvent) => void
|
||||
}
|
||||
|
||||
interface SetupInteractOptions {
|
||||
preset: ModalProps['preset']
|
||||
memo?: RModalProps['memo']
|
||||
x: number
|
||||
y: number
|
||||
dargCallback?: (x: number, y: number, event: Interact.DragEvent) => void
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param bindModal modal 预设元素
|
||||
* @param preset 预设类型
|
||||
*
|
||||
* @description
|
||||
* 根据预设模态框设置拖拽效果
|
||||
* 但是该效果有且仅有 card, dialog 有效
|
||||
*
|
||||
* 默认添加 30ms 延迟,避免诡异问题
|
||||
*/
|
||||
export const setupDraggable = (
|
||||
bindModal: HTMLElement,
|
||||
preset: ModalProps['preset'],
|
||||
options?: SetupDraggableOptions,
|
||||
): Promise<ReturnType<typeof interact>> => {
|
||||
const { scheduler } = options ?? {}
|
||||
|
||||
return new Promise((resolve) => {
|
||||
setTimeout(() => {
|
||||
const allowFromStr =
|
||||
preset === 'card' ? '.n-card-header__main' : '.n-dialog__title'
|
||||
|
||||
if (bindModal) {
|
||||
const dad = interact(bindModal)
|
||||
.draggable({
|
||||
inertia: true,
|
||||
autoScroll: true,
|
||||
allowFrom: allowFromStr,
|
||||
modifiers: [
|
||||
interact.modifiers.restrictRect({
|
||||
restriction: 'parent',
|
||||
endOnly: true,
|
||||
}),
|
||||
],
|
||||
listeners: {
|
||||
move: (event) => {
|
||||
scheduler?.(event)
|
||||
},
|
||||
},
|
||||
})
|
||||
.resizable(false)
|
||||
|
||||
resolve(dad)
|
||||
}
|
||||
}, 30)
|
||||
})
|
||||
}
|
||||
|
||||
export const setupInteract = (
|
||||
target: HTMLElement | string,
|
||||
options: SetupInteractOptions,
|
||||
): Promise<ReturnType<typeof interact>> => {
|
||||
const _target =
|
||||
typeof target === 'string'
|
||||
? (document.querySelector(target) as HTMLElement)
|
||||
: target
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
if (_target) {
|
||||
_target.setAttribute('can-drag', 'true')
|
||||
|
||||
const { preset, dargCallback } = options
|
||||
let { x, y } = options
|
||||
|
||||
setupDraggable(_target, preset, {
|
||||
scheduler: (event) => {
|
||||
const target = event.target
|
||||
|
||||
x += event.dx
|
||||
y += event.dy
|
||||
|
||||
target.style.transform = `translate(${x}px, ${y}px)`
|
||||
|
||||
dargCallback?.(x, y, event)
|
||||
},
|
||||
}).then((res) => {
|
||||
resolve(res)
|
||||
})
|
||||
} else {
|
||||
reject()
|
||||
}
|
||||
})
|
||||
}
|
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')
|
18
src/components/base/RChart/src/hooks/useChartProvider.ts
Normal file
18
src/components/base/RChart/src/hooks/useChartProvider.ts
Normal file
@ -0,0 +1,18 @@
|
||||
import { USE_CHART_PROVIDER_KEY } from '../config'
|
||||
|
||||
import type { ChartTheme } from '../types'
|
||||
|
||||
export interface ChartProviderOptions {
|
||||
theme: ChartTheme
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param 配置项
|
||||
*
|
||||
* @description
|
||||
* 全局 chart 配置项。
|
||||
*/
|
||||
export const useChartProvider = (options: Partial<ChartProviderOptions>) => {
|
||||
provide(USE_CHART_PROVIDER_KEY, options)
|
||||
}
|
@ -36,6 +36,7 @@ import {
|
||||
import { RMoreDropdown } from '@/components'
|
||||
import { useSettingGetters } from '@/store'
|
||||
import { useTemplateRef } from 'vue'
|
||||
import { USE_CHART_PROVIDER_KEY } from './config'
|
||||
|
||||
import type { WatchStopHandle } from 'vue'
|
||||
import type { AnyFC } from '@/types'
|
||||
@ -124,6 +125,7 @@ export default defineComponent({
|
||||
const __catch = {
|
||||
aria: props.showAria,
|
||||
}
|
||||
const chartProvideOptions = inject(USE_CHART_PROVIDER_KEY, {})
|
||||
|
||||
/**
|
||||
*
|
||||
@ -174,10 +176,19 @@ export default defineComponent({
|
||||
* 但是,如果未获取到 echartTheme 属性,则会使用默认样式。
|
||||
*/
|
||||
const updateChartTheme = () => {
|
||||
const { theme: providerTheme } = chartProvideOptions || {}
|
||||
|
||||
if (echartInstanceRef.value) {
|
||||
destroyChart()
|
||||
}
|
||||
|
||||
// 如果配置了全局配置主题,则忽略后面所有逻辑
|
||||
if (providerTheme) {
|
||||
renderChart(providerTheme)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
if (props.theme === 'default') {
|
||||
props.autoChangeTheme ? renderChart('dark') : renderChart('')
|
||||
|
@ -1,7 +1,7 @@
|
||||
import type {
|
||||
ChartThemeRawArray,
|
||||
ChartThemeRawModules,
|
||||
} from '@/components/RChart/src/types'
|
||||
} from '@/components/base/RChart/src/types'
|
||||
|
||||
/**
|
||||
*
|
@ -1,6 +1,6 @@
|
||||
import { useTheme } from '@/hooks'
|
||||
|
||||
import type { LoadingOptions } from '@/components/RChart/src/types'
|
||||
import type { LoadingOptions } from '@/components/base/RChart/src/types'
|
||||
|
||||
/**
|
||||
*
|
@ -81,6 +81,7 @@ export default defineComponent({
|
||||
yGap,
|
||||
collapsedRows,
|
||||
cssVars,
|
||||
actionSpan,
|
||||
bordered,
|
||||
} = this
|
||||
|
||||
@ -97,7 +98,11 @@ export default defineComponent({
|
||||
collapsedRows={collapsedRows}
|
||||
>
|
||||
{defaultSlot?.()}
|
||||
<NGridItem suffix class="ray-collapse-grid__suffix--btn">
|
||||
<NGridItem
|
||||
suffix
|
||||
class="ray-collapse-grid__suffix--btn"
|
||||
span={actionSpan}
|
||||
>
|
||||
<NFlex justify="end" align="center">
|
||||
{action?.()}
|
||||
{collapse
|
@ -5,6 +5,17 @@ import type { CollapseToggleText, ActionAlignType } from './types'
|
||||
import type { AnyFC, MaybeArray } from '@/types'
|
||||
|
||||
const props = {
|
||||
/**
|
||||
*
|
||||
* @description
|
||||
* 操作区域列数。
|
||||
*
|
||||
* @default 1
|
||||
*/
|
||||
actionSpan: {
|
||||
type: Number,
|
||||
default: 1,
|
||||
},
|
||||
/**
|
||||
*
|
||||
* @description
|
388
src/components/base/RDraggableCard/DraggableCard.tsx
Normal file
388
src/components/base/RDraggableCard/DraggableCard.tsx
Normal file
@ -0,0 +1,388 @@
|
||||
import './index.scss'
|
||||
|
||||
import { NCard } from 'naive-ui'
|
||||
import { Teleport, Transition } from 'vue'
|
||||
|
||||
import interact from 'interactjs'
|
||||
import { cardProps } from 'naive-ui'
|
||||
import { unrefElement, completeSize, queryElements } from '@/utils'
|
||||
|
||||
import type { VNode } from 'vue'
|
||||
import type { MaybeElement, MaybeRefOrGetter } from '@vueuse/core'
|
||||
import type { AnyFC } from '@/types'
|
||||
|
||||
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: AnyFC) => {
|
||||
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;
|
||||
}
|
9
src/components/base/RFlow/index.ts
Normal file
9
src/components/base/RFlow/index.ts
Normal file
@ -0,0 +1,9 @@
|
||||
import RFlow from './src/Flow'
|
||||
import flowProps from './src/props'
|
||||
import { useFlow } from './src/hooks'
|
||||
|
||||
import type { ExtractPublicPropTypes } from 'vue'
|
||||
|
||||
export type FlowProps = ExtractPublicPropTypes<typeof flowProps>
|
||||
|
||||
export { RFlow, flowProps, useFlow }
|
155
src/components/base/RFlow/src/Flow.tsx
Normal file
155
src/components/base/RFlow/src/Flow.tsx
Normal file
@ -0,0 +1,155 @@
|
||||
import './index.scss'
|
||||
import '@logicflow/core/lib/style/index.css'
|
||||
|
||||
import { useTemplateRef } from 'vue'
|
||||
import props from './props'
|
||||
import { completeSize, call } from '@/utils'
|
||||
import LogicFlow from '@logicflow/core'
|
||||
import { omit } from 'lodash-es'
|
||||
|
||||
import type { FlowGraphData, G } from './types'
|
||||
import type { WatchStopHandle } from 'vue'
|
||||
|
||||
// 是否首次注册插件
|
||||
let isSetup = false
|
||||
|
||||
export default defineComponent({
|
||||
name: 'RFlow',
|
||||
props,
|
||||
setup(props) {
|
||||
// 流程图 dom 实例
|
||||
const flowDomRef = useTemplateRef<HTMLElement>('flowDomRef')
|
||||
// css 变量
|
||||
const cssVars = computed(() => {
|
||||
const { width, height } = props
|
||||
const cssVar = {
|
||||
'--r-flow-width': completeSize(width),
|
||||
'--r-flow-height': completeSize(height),
|
||||
}
|
||||
|
||||
return cssVar
|
||||
})
|
||||
// 流程图实例
|
||||
const logicFlowInstRef = shallowRef<LogicFlow>()
|
||||
// 需要禁用的流程图配置项
|
||||
const readonlyOptions = {
|
||||
nodeTextEdit: false,
|
||||
edgeTextEdit: false,
|
||||
textEdit: false,
|
||||
}
|
||||
// watchData 回调
|
||||
let watchDataStop: WatchStopHandle
|
||||
// 默认流程图数据
|
||||
const defaultGraphData = {
|
||||
nodes: [],
|
||||
edges: [],
|
||||
}
|
||||
const cacheProps = {
|
||||
readonly: props.readonly,
|
||||
}
|
||||
|
||||
// 注册流程图插件
|
||||
const registerExtension = () => {
|
||||
if (!isSetup) {
|
||||
props.use?.filter(Boolean).forEach((curr) => LogicFlow.use(curr))
|
||||
|
||||
isSetup = true
|
||||
}
|
||||
}
|
||||
|
||||
// 动态根据 readonly 配置项修改流程图配置
|
||||
const updateFlowConfig = (bool: boolean) => {
|
||||
if (!logicFlowInstRef.value) {
|
||||
return
|
||||
}
|
||||
|
||||
const ops = Object.entries(readonlyOptions).reduce(
|
||||
(acc, [key]) => {
|
||||
acc[key as keyof typeof acc] = !bool
|
||||
|
||||
return acc
|
||||
},
|
||||
{} as typeof readonlyOptions,
|
||||
)
|
||||
|
||||
// 单独处理 isSilentMode 配置项
|
||||
Object.assign(readonlyOptions, ops, {
|
||||
isSilentMode: bool,
|
||||
})
|
||||
logicFlowInstRef.value.updateEditConfig(readonlyOptions)
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param graphData 流程图数据
|
||||
*
|
||||
* @description
|
||||
* 初始化流程图。
|
||||
*
|
||||
* 会自动忽略 container 属性,即使是配置了。
|
||||
*/
|
||||
const setupFlowRender = (graphData?: FlowGraphData) => {
|
||||
registerExtension()
|
||||
|
||||
if (!flowDomRef.value) {
|
||||
return
|
||||
}
|
||||
|
||||
const { options, readonly } = props
|
||||
|
||||
// 初始化流程图实例
|
||||
logicFlowInstRef.value = new LogicFlow({
|
||||
container: unref(flowDomRef.value),
|
||||
...omit(options, 'container'),
|
||||
})
|
||||
|
||||
// 渲染
|
||||
logicFlowInstRef.value.render((graphData || defaultGraphData) as G)
|
||||
// 是否处于只读模式,如果是只读模式,则覆盖 options 配置项为 readonlyOptions
|
||||
updateFlowConfig(readonly)
|
||||
}
|
||||
|
||||
watchEffect(() => {
|
||||
if (props.watchData) {
|
||||
watchDataStop = watch(
|
||||
() => props.data,
|
||||
(ndata) => {
|
||||
if (logicFlowInstRef.value) {
|
||||
ndata && logicFlowInstRef.value.renderRawData(ndata as G)
|
||||
} else {
|
||||
setupFlowRender(ndata)
|
||||
}
|
||||
},
|
||||
)
|
||||
} else {
|
||||
watchDataStop?.()
|
||||
}
|
||||
|
||||
if (props.readonly !== cacheProps.readonly) {
|
||||
updateFlowConfig(props.readonly)
|
||||
|
||||
cacheProps.readonly = props.readonly
|
||||
}
|
||||
})
|
||||
|
||||
onMounted(() => {
|
||||
setupFlowRender()
|
||||
|
||||
const { onRegister } = props
|
||||
|
||||
if (onRegister && logicFlowInstRef.value) {
|
||||
call(onRegister, logicFlowInstRef.value)
|
||||
}
|
||||
})
|
||||
|
||||
return {
|
||||
flowDomRef,
|
||||
cssVars,
|
||||
}
|
||||
},
|
||||
render() {
|
||||
const { cssVars } = this
|
||||
|
||||
return <div class="r-flow" style={[cssVars]} ref="flowDomRef"></div>
|
||||
},
|
||||
})
|
17
src/components/base/RFlow/src/constant.ts
Normal file
17
src/components/base/RFlow/src/constant.ts
Normal file
@ -0,0 +1,17 @@
|
||||
import type { FlowOptions } from './types'
|
||||
|
||||
/**
|
||||
*
|
||||
* @description
|
||||
* RFlow 默认初始化流程图配置项。
|
||||
* 默认,使用组件内置的 container,所以会自动忽略该属性,即使是传递了。
|
||||
*/
|
||||
export const getDefaultFlowOptions = (): FlowOptions => {
|
||||
return {
|
||||
grid: true,
|
||||
partial: false,
|
||||
keyboard: {
|
||||
enabled: true,
|
||||
},
|
||||
}
|
||||
}
|
1
src/components/base/RFlow/src/hooks/index.ts
Normal file
1
src/components/base/RFlow/src/hooks/index.ts
Normal file
@ -0,0 +1 @@
|
||||
export { useFlow } from './useFlow'
|
47
src/components/base/RFlow/src/hooks/useFlow.ts
Normal file
47
src/components/base/RFlow/src/hooks/useFlow.ts
Normal file
@ -0,0 +1,47 @@
|
||||
import type LogicFlow from '@logicflow/core'
|
||||
|
||||
export const useFlow = () => {
|
||||
let flowInst: LogicFlow
|
||||
|
||||
/**
|
||||
*
|
||||
* @param inst flow instance
|
||||
*
|
||||
* @description
|
||||
* 注册当前 flow 实例,用于使用 useFlow hook。
|
||||
*/
|
||||
const register = (inst: LogicFlow) => {
|
||||
if (inst) {
|
||||
flowInst = inst
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @description
|
||||
* 获取当前 flow 实例。
|
||||
*
|
||||
* 如果未在调用前注册 onRegister 事件,则会抛出异常。
|
||||
*
|
||||
* @example
|
||||
* const [register, { getFlowInstance }] = useFlow()
|
||||
*
|
||||
* const inst = getFlowInstance()
|
||||
*/
|
||||
const getFlowInstance = () => {
|
||||
if (!flowInst) {
|
||||
throw new Error(
|
||||
'[useFlow]: flow instance is not ready yet. if you are using useFlow, please make sure you have called register method in onRegister event.',
|
||||
)
|
||||
}
|
||||
|
||||
return flowInst
|
||||
}
|
||||
|
||||
return [
|
||||
register,
|
||||
{
|
||||
getFlowInstance,
|
||||
},
|
||||
] as const
|
||||
}
|
10
src/components/base/RFlow/src/index.scss
Normal file
10
src/components/base/RFlow/src/index.scss
Normal file
@ -0,0 +1,10 @@
|
||||
.r-flow {
|
||||
width: var(--r-flow-width);
|
||||
height: var(--r-flow-height);
|
||||
}
|
||||
|
||||
.lf-text-input,
|
||||
.lf-control-text,
|
||||
.lf-menu-item {
|
||||
color: initial;
|
||||
}
|
104
src/components/base/RFlow/src/props.ts
Normal file
104
src/components/base/RFlow/src/props.ts
Normal file
@ -0,0 +1,104 @@
|
||||
import { getDefaultFlowOptions } from './constant'
|
||||
|
||||
import type { FlowGraphData, FlowOptions, ExtensionType } from './types'
|
||||
import type LogicFlow from '@logicflow/core'
|
||||
import type { MaybeArray } from '@/types'
|
||||
|
||||
const props = {
|
||||
/**
|
||||
*
|
||||
* @description
|
||||
* 自定义全局插件。
|
||||
* 需要在流程图初始化之前注册,否则无效。
|
||||
*
|
||||
* 需要在引入插件之前,导入对应的基础 css 文件。
|
||||
*
|
||||
* @see https://site.logic-flow.cn/tutorial/extension/intro
|
||||
*/
|
||||
use: {
|
||||
type: Array as PropType<ExtensionType[]>,
|
||||
default: void 0,
|
||||
},
|
||||
/**
|
||||
*
|
||||
* @description
|
||||
* 是否主动监听 data 变化,重新渲染流程图。
|
||||
*/
|
||||
watchData: {
|
||||
type: Boolean,
|
||||
default: true,
|
||||
},
|
||||
/**
|
||||
*
|
||||
* @description
|
||||
* 流程图是否可以编辑与操作。
|
||||
*
|
||||
* 该配置项会覆盖 options 配置项,拥有最高的优先级。
|
||||
*
|
||||
* @default false
|
||||
*/
|
||||
readonly: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
/**
|
||||
*
|
||||
* @description
|
||||
* 流程图宽度。
|
||||
*
|
||||
* @default '100%'
|
||||
*/
|
||||
width: {
|
||||
type: [String, Number] as PropType<string | number>,
|
||||
default: '100%',
|
||||
},
|
||||
/**
|
||||
*
|
||||
* @description
|
||||
* 流程图高度。
|
||||
*
|
||||
* @default '100%'
|
||||
*/
|
||||
height: {
|
||||
type: [String, Number] as PropType<string | number>,
|
||||
default: '100%',
|
||||
},
|
||||
/**
|
||||
*
|
||||
* @description
|
||||
* 流程图数据。
|
||||
*
|
||||
* @default undefined
|
||||
*/
|
||||
data: {
|
||||
type: Object as PropType<FlowGraphData>,
|
||||
default: void 0,
|
||||
},
|
||||
/**
|
||||
*
|
||||
* @description
|
||||
* 流程图配置项。
|
||||
*
|
||||
* @default undefined
|
||||
*/
|
||||
options: {
|
||||
type: Object as PropType<FlowOptions>,
|
||||
default: getDefaultFlowOptions(),
|
||||
},
|
||||
/**
|
||||
*
|
||||
* @description
|
||||
* RFlow 注册挂载成功后触发的事件。
|
||||
* 可以结合 useFlow 方法中的 register 方法使用,然后便捷的使用 hooks。
|
||||
*
|
||||
* @default undefined
|
||||
*/
|
||||
onRegister: {
|
||||
type: [Function, Array] as PropType<
|
||||
MaybeArray<(flowInst: LogicFlow) => void>
|
||||
>,
|
||||
default: void 0,
|
||||
},
|
||||
}
|
||||
|
||||
export default props
|
61
src/components/base/RFlow/src/types.ts
Normal file
61
src/components/base/RFlow/src/types.ts
Normal file
@ -0,0 +1,61 @@
|
||||
import type LogicFlow from '@logicflow/core'
|
||||
import type { Recordable, SetRequired } from '@/types'
|
||||
|
||||
/**
|
||||
*
|
||||
* @description
|
||||
* 因为 Omit 剔除属性,会导致类型提示丢失。
|
||||
* 所以,这里手动的声明 Options 所需属性,避免这个问题。
|
||||
*
|
||||
* 后期改动的时候,需要关注官方的文档变更情况,及时的同步跟新。
|
||||
*/
|
||||
type OptionsPickKeys =
|
||||
| 'width'
|
||||
| 'height'
|
||||
| 'background'
|
||||
| 'grid'
|
||||
| 'partial'
|
||||
| 'keyboard'
|
||||
| 'style'
|
||||
| 'edgeType'
|
||||
| 'adjustEdge'
|
||||
| 'textMode'
|
||||
| 'edgeTextMode'
|
||||
| 'nodeTextMode'
|
||||
| 'allowRotate'
|
||||
| 'allowResize'
|
||||
| 'isSilentMode'
|
||||
| 'stopScrollGraph'
|
||||
| 'stopZoomGraph'
|
||||
| 'stopMoveGraph'
|
||||
| 'animation'
|
||||
| 'history'
|
||||
| 'outline'
|
||||
| 'snapline'
|
||||
| 'textEdit'
|
||||
| 'guards'
|
||||
| 'overlapMode'
|
||||
| 'plugins'
|
||||
| 'pluginsOptions'
|
||||
| 'disabledPlugins'
|
||||
| 'disabledTools'
|
||||
| 'idGenerator'
|
||||
| 'edgeGenerator'
|
||||
| 'customTrajectory'
|
||||
|
||||
export type G = Parameters<LogicFlow['render']>[0]
|
||||
|
||||
export type NodeConfig = SetRequired<NonNullable<G['nodes']>[0], 'id'> &
|
||||
Recordable
|
||||
|
||||
export type EdgeConfig = SetRequired<NonNullable<G['edges']>[0], 'id'> &
|
||||
Recordable
|
||||
|
||||
export interface FlowGraphData {
|
||||
nodes?: NodeConfig[]
|
||||
edges?: EdgeConfig[]
|
||||
}
|
||||
|
||||
export type FlowOptions = Pick<LogicFlow['options'], OptionsPickKeys>
|
||||
|
||||
export type ExtensionType = Parameters<typeof LogicFlow.use>[0]
|
85
src/components/base/RForm/src/Form.tsx
Normal file
85
src/components/base/RForm/src/Form.tsx
Normal file
@ -0,0 +1,85 @@
|
||||
import { NForm, NSpin } from 'naive-ui'
|
||||
|
||||
import props from './props'
|
||||
import { call, unrefElement } from '@/utils'
|
||||
import { useTemplateRef } from 'vue'
|
||||
import { useEventListener } from '@vueuse/core'
|
||||
|
||||
import type { RFormInst } from './types'
|
||||
import type { ShallowRef } from 'vue'
|
||||
|
||||
export default defineComponent({
|
||||
name: 'RForm',
|
||||
props,
|
||||
setup(props, { expose }) {
|
||||
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(() => {
|
||||
// 主动调用 register 方法,满足 useForm 方法正常调用
|
||||
const { onRegister } = props
|
||||
|
||||
if (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()
|
||||
|
||||
return {
|
||||
formRef,
|
||||
}
|
||||
},
|
||||
render() {
|
||||
const { $attrs, $props, $slots } = this
|
||||
const { loading, loadingDescription, ...restProps } = $props
|
||||
|
||||
return (
|
||||
<NSpin
|
||||
show={loading}
|
||||
description={loadingDescription}
|
||||
style={{
|
||||
height: 'auto',
|
||||
}}
|
||||
>
|
||||
<NForm {...$attrs} {...restProps} ref="formRef">
|
||||
{{
|
||||
...$slots,
|
||||
}}
|
||||
</NForm>
|
||||
</NSpin>
|
||||
)
|
||||
},
|
||||
})
|
256
src/components/base/RForm/src/hooks/useForm.ts
Normal file
256
src/components/base/RForm/src/hooks/useForm.ts
Normal file
@ -0,0 +1,256 @@
|
||||
import { cloneDeep } from 'lodash-es'
|
||||
|
||||
import type {
|
||||
RFormInst,
|
||||
FormValidateCallback,
|
||||
ShouldRuleBeApplied,
|
||||
RFormRules,
|
||||
} from '../types'
|
||||
import type { Recordable } from '@/types'
|
||||
|
||||
/**
|
||||
*
|
||||
* @description
|
||||
* 获取 RForm 实例。
|
||||
* 让你能够便捷的调用相关的一些已封装方法。
|
||||
*
|
||||
* @warning
|
||||
* 值得注意的是,必须手动调用 register 方法,否则不能正常使用。
|
||||
* 在使用 相关 hooks 的时候,需要注意生命周期,确保 register 方法已经被调用与表格实例已经被初始化;
|
||||
* 不要在父组件 create 阶段就去调用 hook,如果需要,请使用 nextTick 包裹。
|
||||
*
|
||||
* @example
|
||||
* defineComponent({
|
||||
* setup() {
|
||||
* const [register, { ...Hooks }] = useForm()
|
||||
*
|
||||
* return {
|
||||
* register,
|
||||
* ...Hooks,
|
||||
* }
|
||||
* },
|
||||
* render() {
|
||||
* const { register, ...Hooks } = this
|
||||
*
|
||||
* return <RForm onRegister={register} />
|
||||
* },
|
||||
* })
|
||||
*/
|
||||
const useForm = <
|
||||
T extends Recordable = Recordable,
|
||||
R extends RFormRules = RFormRules,
|
||||
>(
|
||||
model?: T | (() => T),
|
||||
rules?: R | (() => R),
|
||||
) => {
|
||||
const formRef = shallowRef<RFormInst>()
|
||||
const formModelRef = ref<T>()
|
||||
|
||||
const register = (inst: RFormInst) => {
|
||||
if (inst) {
|
||||
formRef.value = inst
|
||||
}
|
||||
}
|
||||
|
||||
const getFormInstance = () => {
|
||||
if (!formRef.value) {
|
||||
throw new Error(
|
||||
'[useForm]: form instance is not ready yet. if you are using useForm, please make sure you have called register method in onRegister event.',
|
||||
)
|
||||
}
|
||||
|
||||
return formRef.value
|
||||
}
|
||||
|
||||
// 初始化 formModelRef 的值,根据 model 的类型进行初始化
|
||||
const initialFormModel = () => {
|
||||
if (typeof model === 'function') {
|
||||
formModelRef.value = model() ?? ({} as T)
|
||||
} else {
|
||||
formModelRef.value = cloneDeep(model) ?? ({} as T)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @description
|
||||
* 验证表单,Promise rejection 的返回值类型是 FormValidationError[]。
|
||||
*
|
||||
* @see https://www.naiveui.com/zh-CN/dark/components/form#inline.vue
|
||||
*/
|
||||
const validate = (
|
||||
callback?: FormValidateCallback,
|
||||
shouldRuleBeApplied?: ShouldRuleBeApplied,
|
||||
) => getFormInstance().validate.call(null, callback, shouldRuleBeApplied)
|
||||
|
||||
/**
|
||||
*
|
||||
* @description
|
||||
* 还原表单到未验证状态。
|
||||
*
|
||||
* @see https://www.naiveui.com/zh-CN/dark/components/form#Form-Methods
|
||||
*/
|
||||
const restoreValidation = () => getFormInstance().restoreValidation.call(null)
|
||||
|
||||
/**
|
||||
*
|
||||
* @description
|
||||
* 获取表项中收集到的值的对象。
|
||||
* 调用该方法时,需要确保初始化 useForm 方法的时候传入了 model,否则可能有意想不到的问题发生。
|
||||
*
|
||||
* 该方法可以实现重置表单的需求,因为在 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 = (): T & Recordable => {
|
||||
if (typeof model === 'function') {
|
||||
return model()
|
||||
}
|
||||
|
||||
return cloneDeep(model) || ({} as T)
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @description
|
||||
* 获取验证表单项的规则。
|
||||
*
|
||||
* 调用该方法时,需要确保初始化 useForm 方法的时候传入了 rules,否则可能有意想不到的问题发生。
|
||||
*/
|
||||
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 [
|
||||
register,
|
||||
{
|
||||
getFormInstance,
|
||||
validate,
|
||||
restoreValidation,
|
||||
formModel,
|
||||
formRules,
|
||||
reset,
|
||||
validateTargetField,
|
||||
formConditionRef: formModelRef as Ref<T>,
|
||||
updateFormCondition,
|
||||
},
|
||||
] as const
|
||||
}
|
||||
|
||||
export type UseFormReturn = ReturnType<typeof useForm>
|
||||
|
||||
export default useForm
|
89
src/components/base/RForm/src/props.ts
Normal file
89
src/components/base/RForm/src/props.ts
Normal file
@ -0,0 +1,89 @@
|
||||
import { formProps } from 'naive-ui'
|
||||
import { omit } from 'lodash-es'
|
||||
|
||||
import type { MaybeArray, AnyFC } from '@/types'
|
||||
import type { RFormInst } from './types'
|
||||
|
||||
const props = {
|
||||
...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<AnyFC>,
|
||||
default: null,
|
||||
},
|
||||
/**
|
||||
*
|
||||
* @description
|
||||
* RForm 注册挂载成功后触发的事件。
|
||||
* 可以结合 useForm 方法中的 register 方法使用,然后便捷的使用 hooks。
|
||||
*
|
||||
* @default null
|
||||
*/
|
||||
onRegister: {
|
||||
type: [Function, Array] as PropType<
|
||||
MaybeArray<(formInst: RFormInst) => void>
|
||||
>,
|
||||
default: null,
|
||||
},
|
||||
} as const
|
||||
|
||||
export default props
|
@ -4,14 +4,12 @@ import { NModal } from 'naive-ui'
|
||||
|
||||
import props from './props'
|
||||
import { completeSize, uuid } from '@/utils'
|
||||
import { setupInteract } from './utils'
|
||||
import {
|
||||
FULLSCREEN_CARD_TYPE_CLASS,
|
||||
R_MODAL_CLASS,
|
||||
CSS_VARS_KEYS,
|
||||
} from './constant'
|
||||
|
||||
import type interact from 'interactjs'
|
||||
import type { ModalProps } from 'naive-ui'
|
||||
|
||||
export default defineComponent({
|
||||
@ -24,57 +22,11 @@ export default defineComponent({
|
||||
[CSS_VARS_KEYS['dialogWidth']]: completeSize(props.dialogWidth ?? 446),
|
||||
}))
|
||||
const uuidEl = uuid()
|
||||
let intractable: null | ReturnType<typeof interact>
|
||||
// 记录拖拽的位置
|
||||
const position = {
|
||||
x: 0,
|
||||
y: 0,
|
||||
}
|
||||
// 当前是否为预设 card 类型并且设置了 fullscreen
|
||||
const isFullscreenCardType = computed(
|
||||
() => props.preset === 'card' && props.fullscreen,
|
||||
)
|
||||
|
||||
watch(
|
||||
() => props.show,
|
||||
(ndata) => {
|
||||
if (
|
||||
ndata &&
|
||||
props.dad &&
|
||||
(props.preset === 'card' || props.preset === 'dialog')
|
||||
) {
|
||||
nextTick(() => {
|
||||
const target = document.getElementById(uuidEl)
|
||||
|
||||
if (target) {
|
||||
setupInteract(target, {
|
||||
preset: props.preset,
|
||||
x: position.x,
|
||||
y: position.y,
|
||||
dargCallback: (x, y) => {
|
||||
position.x = x
|
||||
position.y = y
|
||||
},
|
||||
}).then((res) => {
|
||||
intractable = res
|
||||
})
|
||||
}
|
||||
|
||||
if (props.memo && target) {
|
||||
target.style.transform = `translate(${position.x}px, ${position.y}px)`
|
||||
}
|
||||
})
|
||||
} else {
|
||||
intractable?.unset()
|
||||
|
||||
intractable = null
|
||||
}
|
||||
},
|
||||
{
|
||||
immediate: true,
|
||||
},
|
||||
)
|
||||
|
||||
return {
|
||||
cssVars,
|
||||
isFullscreenCardType,
|
@ -1,16 +1,13 @@
|
||||
import { useModal as useNaiveModal, NScrollbar } from 'naive-ui'
|
||||
import { setupInteract } from '../utils'
|
||||
import { queryElements, setStyle, completeSize, setClass } from '@/utils'
|
||||
import { R_MODAL_CLASS, CSS_VARS_KEYS } from '../constant'
|
||||
|
||||
import type { RModalProps } from '../types'
|
||||
|
||||
interface UseModalCreateOptions extends Omit<RModalProps, 'memo'> {}
|
||||
|
||||
const useModal = () => {
|
||||
const { create: naiveCreate, destroyAll: naiveDestroyAll } = useNaiveModal()
|
||||
|
||||
const create = (options: UseModalCreateOptions) => {
|
||||
const create = (options: RModalProps) => {
|
||||
const { content, ...rest } = options
|
||||
let contentNode = content
|
||||
|
||||
@ -23,11 +20,11 @@ const useModal = () => {
|
||||
color: 'rgba(0, 0, 0, 0)',
|
||||
colorHover: 'rgba(0, 0, 0, 0)',
|
||||
},
|
||||
trigger: 'none',
|
||||
trigger: 'hover',
|
||||
style: {
|
||||
width: 'auto',
|
||||
height:
|
||||
'calc(100vh - 29px - var(--n-padding-bottom) - var(--n-padding-bottom) - var(--n-padding-top))',
|
||||
maxHeight:
|
||||
'calc(var(--html-height) - 29px - var(--n-padding-bottom) - var(--n-padding-bottom) - var(--n-padding-top))',
|
||||
},
|
||||
},
|
||||
{
|
||||
@ -37,7 +34,7 @@ const useModal = () => {
|
||||
)
|
||||
}
|
||||
|
||||
const { preset, dad, fullscreen, width, cardWidth, dialogWidth } = options
|
||||
const { preset, fullscreen, width, cardWidth, dialogWidth } = options
|
||||
const modalReactive = naiveCreate({
|
||||
...rest,
|
||||
content: contentNode,
|
||||
@ -57,28 +54,8 @@ const useModal = () => {
|
||||
return
|
||||
}
|
||||
|
||||
// 是否启用拖拽
|
||||
if (dad) {
|
||||
setupInteract(modalElement, {
|
||||
preset,
|
||||
x: 0,
|
||||
y: 0,
|
||||
})
|
||||
}
|
||||
|
||||
// preset 为 card,fullscreen 为 true 时,最大化 modal
|
||||
if (fullscreen && preset === 'card') {
|
||||
const cardContentElement =
|
||||
modalElement.querySelector<HTMLElement>('.n-card__content')
|
||||
|
||||
if (cardContentElement) {
|
||||
setStyle(cardContentElement, {
|
||||
maxHeight: `calc(100vh - 9px - var(--n-padding-bottom) - var(--n-padding-bottom) - var(--n-padding-top))`,
|
||||
overflowY: 'hidden',
|
||||
padding: '0',
|
||||
})
|
||||
}
|
||||
|
||||
setStyle(modalElement, {
|
||||
width: '100%',
|
||||
height: '100vh',
|
@ -4,6 +4,11 @@
|
||||
// 当设置全屏时,启用滚动
|
||||
& .n-card__content {
|
||||
overflow: scroll;
|
||||
max-height: calc(
|
||||
var(--html-height) - var(--n-padding-bottom) - var(--n-padding-bottom) - var(
|
||||
--n-padding-top
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -3,17 +3,6 @@ import type { PropType } from 'vue'
|
||||
|
||||
const props = {
|
||||
...modalProps,
|
||||
/**
|
||||
*
|
||||
* @description
|
||||
* 是否记住上一次的位置。
|
||||
*
|
||||
* @default true
|
||||
*/
|
||||
memo: {
|
||||
type: Boolean,
|
||||
default: true,
|
||||
},
|
||||
/**
|
||||
*
|
||||
* @description
|
||||
@ -58,18 +47,6 @@ const props = {
|
||||
type: [String, Number] as PropType<string | number>,
|
||||
default: 446,
|
||||
},
|
||||
/**
|
||||
*
|
||||
* @description
|
||||
* 是否启用拖拽。
|
||||
* 当启用拖拽时,可以通过拖拽 header 部分控制模态框。
|
||||
*
|
||||
* @default false
|
||||
*/
|
||||
dad: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
}
|
||||
|
||||
export default props
|
@ -1,14 +1,6 @@
|
||||
import type { ModalOptions as NaiveModalOptions } from 'naive-ui'
|
||||
|
||||
export interface RModalProps extends NaiveModalOptions {
|
||||
/**
|
||||
*
|
||||
* @description
|
||||
* 是否记住上一次的位置。
|
||||
*
|
||||
* @default true
|
||||
*/
|
||||
memo?: boolean
|
||||
/**
|
||||
*
|
||||
* @description
|
||||
@ -41,13 +33,4 @@ export interface RModalProps extends NaiveModalOptions {
|
||||
* @default 446
|
||||
*/
|
||||
dialogWidth?: number | string
|
||||
/**
|
||||
*
|
||||
* @description
|
||||
* 是否启用拖拽。
|
||||
* 当启用拖拽时,可以通过拖拽 header 部分控制模态框。
|
||||
*
|
||||
* @default false
|
||||
*/
|
||||
dad?: boolean
|
||||
}
|
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