Compare commits

..

No commits in common. "main" and "v5.0.1" have entirely different histories.
main ... v5.0.1

190 changed files with 8629 additions and 13083 deletions

18
.eslintignore Normal file
View File

@ -0,0 +1,18 @@
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 Normal file
View File

@ -0,0 +1,301 @@
/* 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'],
},
],
},
}

View File

@ -14,15 +14,15 @@ jobs:
- name: Checkout
uses: actions/checkout@v3
- name: Install Node.js 22.x
- name: Install Node.js 20.x
uses: actions/setup-node@v3
with:
node-version: 22.x
node-version: 20.x
- uses: pnpm/action-setup@v2
name: Install pnpm
with:
version: 9
version: 8
run_install: false
- name: Install dependencies

View File

@ -8,7 +8,7 @@ jobs:
strategy:
fail-fast: false
matrix:
node-version: [22.x]
node-version: [20.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: 9
version: 8
run_install: false
- name: Get pnpm store directory

2
.nvmrc
View File

@ -1 +1 @@
v22.12.0
v20.12.0

View File

@ -1,5 +1,4 @@
{
"editor.formatOnSave": true,
"i18n-ally.localesPaths": ["src/locales/lang"],
"i18n-ally.keystyle": "nested",
"i18n-ally.sortKeys": true,
@ -22,7 +21,6 @@
"cSpell.words": [
"baomitu",
"bezier",
"Cascader",
"Clickoutside",
"codabar",
"commitmsg",
@ -34,7 +32,6 @@
"internalkey",
"jsbarcode",
"linebreak",
"logicflow",
"macarons",
"menutag",
"ndata",
@ -43,10 +40,9 @@
"Popselect",
"precommit",
"siderbar",
"snapline",
"stylelint",
"unocss",
"WUJIE",
"zlevel"
]
],
"peacock.color": "#007fff"
}

View File

@ -1,255 +1,4 @@
## 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` 等属性,不能及时刷新渲染的问题,现在新增手动刷新操作按钮
> 菜单渲染是很复杂、很耗时的操作,所以在权衡以后还是考虑使用手动刷新操作方式更新菜单状态。
# CHANGE LOG
## 5.0.1
@ -305,7 +54,7 @@ const Demo2 = () => {
- 新增 `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

View File

@ -3,7 +3,6 @@ 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)
@ -15,7 +14,6 @@ describe('callWithAsyncErrorHandling', () => {
const fn = () => {
throw new Error('test error')
}
const callbackFn = () => {
callbackFnExecuted = 2
}

View File

@ -3,7 +3,6 @@ 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)
@ -15,7 +14,6 @@ describe('callWithErrorHandling', () => {
const fn = () => {
throw new Error('test error')
}
const callbackFn = () => {
callbackFnExecuted = 2
}

View File

@ -42,7 +42,6 @@ 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)
})
})

View File

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

View File

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

View File

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

View File

@ -13,7 +13,6 @@ 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({

View File

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

View File

@ -4,7 +4,6 @@ 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()

View File

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

View File

@ -15,27 +15,6 @@
--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 {
@ -44,9 +23,13 @@
right: 0;
top: 0;
bottom: 0;
background-color: #ffffff;
color: var(--preloading-title-color);
text-align: center;
background-color: var(--global-loading-bg-color);
}
.ray-template--dark #pre-loading-animation {
background-color: #2a3146;
}
#pre-loading-animation .pre-loading-animation__wrapper {
@ -112,18 +95,6 @@
}
}
</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">

View File

@ -1,11 +1,11 @@
{
"name": "ray-template",
"private": false,
"version": "5.2.2",
"version": "5.0.1",
"type": "module",
"engines": {
"node": "^18.0.0 || ^20.0.0 || >=22.0.0",
"pnpm": ">=9.0.0"
"node": "^18.0.0 || >=20.0.0",
"pnpm": ">=8.0.0"
},
"scripts": {
"dev": "vite",
@ -16,7 +16,7 @@
"prepare": "husky install",
"test": "vitest",
"test:ui": "vitest --ui",
"lint": "vue-tsc --noEmit && eslint --fix && prettier --write \"**/*.{ts,tsx,json,.vue}\""
"lint": "vue-tsc --noEmit && eslint src --ext .js,.jsx,.vue && prettier --write \"src/**/*.{ts,tsx,json,.vue}\""
},
"husky": {
"hooks": {
@ -29,85 +29,82 @@
"prettier --write"
],
"*.{ts,tsx,vue}": [
"eslint --fix"
"eslint src"
]
},
"dependencies": {
"@logicflow/core": "2.0.10",
"@logicflow/extension": "2.0.14",
"@vueuse/core": "^13.1.0",
"axios": "^1.9.0",
"@vueuse/core": "^11.1.0",
"axios": "^1.7.5",
"clipboard": "^2.0.11",
"crypto-js": "4.2.0",
"currency.js": "^2.0.4",
"dayjs": "^1.11.13",
"echarts": "^5.6.0",
"html-to-image": "1.11.13",
"interactjs": "1.10.27",
"dayjs": "^1.11.10",
"echarts": "^5.5.0",
"html-to-image": "1.11.11",
"interactjs": "1.10.26",
"jsbarcode": "3.11.6",
"lodash-es": "^4.17.21",
"mockjs": "1.1.0",
"naive-ui": "^2.42.0",
"pinia": "^3.0.3",
"pinia-plugin-persistedstate": "^4.4.1",
"naive-ui": "^2.40.1",
"pinia": "^2.2.4",
"pinia-plugin-persistedstate": "^4.1.1",
"print-js": "^1.6.0",
"vue": "^3.5.17",
"vue-demi": "0.14.10",
"vue-hooks-plus": "2.4.0",
"vue": "^3.5.12",
"vue-demi": "0.14.6",
"vue-hooks-plus": "2.2.1",
"vue-i18n": "^9.13.1",
"vue-router": "^4.5.1",
"vue3-next-qrcode": "3.0.2"
"vue-router": "^4.3.2",
"vue3-next-qrcode": "2.0.10"
},
"devDependencies": {
"@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",
"@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",
"@types/jsbarcode": "3.11.4",
"@types/lodash-es": "4.17.12",
"@types/mockjs": "1.0.10",
"@typescript-eslint/eslint-plugin": "8.24.0",
"@typescript-eslint/parser": "8.24.0",
"@vitejs/plugin-vue": "5.2.3",
"@vitejs/plugin-vue-jsx": "4.1.2",
"@vitest/ui": "2.1.8",
"@vue/eslint-config-prettier": "10.1.0",
"@vue/eslint-config-typescript": "14.2.0",
"@vue/test-utils": "2.4.6",
"autoprefixer": "10.4.21",
"depcheck": "1.4.7",
"eslint": "9.20.1",
"eslint-config-prettier": "10.1.2",
"eslint-plugin-prettier": "5.2.6",
"eslint-plugin-vue": "9.32.0",
"globals": "16.0.0",
"happy-dom": "17.1.0",
"@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",
"husky": "8.0.3",
"lint-staged": "15.4.3",
"postcss": "8.5.4",
"lint-staged": "^15.2.0",
"postcss": "^8.4.38",
"postcss-px-to-viewport-8-with-include": "1.2.2",
"prettier": "3.5.3",
"rollup-plugin-gzip": "4.0.1",
"sass": "1.86.3",
"svg-sprite-loader": "6.0.11",
"typescript": "5.8.3",
"unocss": "66.3.3",
"unplugin-auto-import": "19.1.2",
"unplugin-vue-components": "0.28.0",
"vite": "6.3.5",
"vite-bundle-analyzer": "0.16.0",
"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",
"vite-plugin-cdn2": "1.1.0",
"vite-plugin-ejs": "1.7.0",
"vite-plugin-compression": "^0.5.1",
"vite-plugin-ejs": "^1.7.0",
"vite-plugin-eslint": "1.8.1",
"vite-plugin-inspect": "0.8.4",
"vite-plugin-mock-dev-server": "1.8.3",
"vite-plugin-svg-icons": "2.0.1",
"vite-svg-loader": "5.1.0",
"vitest": "2.1.8",
"vue-eslint-parser": "9.4.3",
"vue-tsc": "2.2.8"
"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"
},
"description": "<!-- ALL-CONTRIBUTORS-BADGE:START - Do not remove or modify this section -->",
"main": "index.ts",

14086
pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff

View File

@ -28,7 +28,7 @@ interface JSONPlaceholder {
*
* @returns
*
* @method get
* @medthod get
*/
export const getWeather = (city: string) => {
return request<AxiosTestResponse>({

View File

@ -54,14 +54,7 @@ const AppAvatar = defineComponent({
objectFit="cover"
round
size={avatarSize}
>
{{
default: () =>
getSigningCallback.avatar
? null
: getSigningCallback?.name?.[0],
}}
</NAvatar>
/>
{getSigningCallback?.name}
</NFlex>
</NButton>

View File

@ -44,7 +44,6 @@ const GlobalSpin = defineComponent({
{...(this.$props as SpinProps)}
show={this.spinValue}
themeOverrides={this.overrides}
style="height: var(--html-height)"
>
{{
...this.$slots,

View File

@ -1,10 +1,17 @@
/**
*
*
*
* ,
*/
import { useStorage } from '@vueuse/core'
import { APP_CATCH_KEY } from '@/app-config'
const appLockScreen = useStorage(
APP_CATCH_KEY.isAppLockScreen,
false,
window.localStorage,
sessionStorage,
{
mergeDefaults: true,
},

View File

@ -1,22 +1,16 @@
import { NInput, NFormItem, NButton } from 'naive-ui'
import AppAvatar from '@/app-components/app/AppAvatar'
import { RForm } from '@/components'
import { NInput, NForm, NFormItem, NButton } from 'naive-ui'
import useAppLockScreen from '@/app-components/app/AppLockScreen/appLockVar'
import { rules, useCondition } from '@/app-components/app/AppLockScreen/shared'
import { useSettingActions } from '@/store'
import { useTemplateRef } from 'vue'
import { useForm } from '@/components'
import { APP_CATCH_KEY } from '@/app-config'
import { setStorage, encrypt } from '@/utils'
import { useSettingGetters, useSettingActions } from '@/store'
import type { InputInst } from 'naive-ui'
import type { FormInst, InputInst } from 'naive-ui'
const LockScreen = defineComponent({
name: 'LockScreen',
setup() {
const [register, { validate }] = useForm()
const inputInstRef = useTemplateRef<InputInst | null>('inputInstRef')
const formInstRef = ref<FormInst | null>(null)
const inputInstRef = ref<InputInst | null>(null)
const { setLockAppScreen } = useAppLockScreen()
const { updateSettingState } = useSettingActions()
@ -25,17 +19,15 @@ const LockScreen = defineComponent({
lockCondition: useCondition(),
})
/** 锁屏 */
const lockScreen = () => {
validate().then(() => {
setLockAppScreen(true)
updateSettingState('lockScreenSwitch', false)
setStorage(
APP_CATCH_KEY.appLockScreenPasswordKey,
encrypt(state.lockCondition.lockPassword),
'localStorage',
)
formInstRef.value?.validate((error) => {
if (!error) {
setLockAppScreen(true)
updateSettingState('lockScreenSwitch', true)
state.lockCondition = useCondition()
state.lockCondition = useCondition()
}
})
}
@ -48,51 +40,40 @@ const LockScreen = defineComponent({
return {
...toRefs(state),
lockScreen,
register,
formInstRef,
inputInstRef,
}
},
render() {
const { register } = this
return (
<div class="app-lock-screen__content">
<div class="app-lock-screen__input">
<AppAvatar
avatarSize={52}
style="pointer-events: none;margin: 24px 0;"
vertical
/>
<RForm
ref="formInstRef"
model={this.lockCondition}
rules={rules}
labelPlacement="left"
onRegister={register}
>
<NFormItem path="lockPassword">
<NInput
ref="inputInstRef"
v-model:value={this.lockCondition.lockPassword}
type="password"
placeholder="请输入锁屏密码"
clearable
showPasswordOn="click"
minlength={6}
maxlength={12}
onKeydown={(e: KeyboardEvent) => {
if (e.code === 'Enter') {
this.lockScreen()
}
}}
autofocus
/>
</NFormItem>
<NButton type="primary" onClick={this.lockScreen.bind(this)}>
</NButton>
</RForm>
</div>
<div class="app-lock-screen__input">
<NForm
ref="formInstRef"
model={this.lockCondition}
rules={rules}
labelPlacement="left"
>
<NFormItem path="lockPassword">
<NInput
ref="inputInstRef"
v-model:value={this.lockCondition.lockPassword}
type="password"
placeholder="请输入锁屏密码"
clearable
showPasswordOn="click"
minlength={6}
maxlength={12}
onKeydown={(e: KeyboardEvent) => {
if (e.code === 'Enter') {
this.lockScreen()
}
}}
/>
</NFormItem>
<NButton type="primary" onClick={this.lockScreen.bind(this)}>
</NButton>
</NForm>
</div>
)
},

View File

@ -1,22 +1,19 @@
import '../../index.scss'
import { NInput, NFormItem, NButton, NFlex } from 'naive-ui'
import { NInput, NForm, NFormItem, NButton, NFlex } from 'naive-ui'
import AppAvatar from '@/app-components/app/AppAvatar'
import { RForm } from '@/components'
import dayjs from 'dayjs'
import { useSigningActions, useSettingActions } from '@/store'
import { rules, useCondition } from '@/app-components/app/AppLockScreen/shared'
import useAppLockScreen from '@/app-components/app/AppLockScreen/appLockVar'
import { useDevice } from '@/hooks'
import { useForm } from '@/components'
import { APP_CATCH_KEY } from '@/app-config'
import { removeStorage, decrypt, getStorage } from '@/utils'
import type { FormInst, InputInst } from 'naive-ui'
export default defineComponent({
name: 'UnlockScreen',
setup() {
const [register, { validate }] = useForm()
const formRef = ref<FormInst | null>(null)
const inputInstRef = ref<InputInst | null>(null)
const { logout } = useSigningActions()
const { updateSettingState } = useSettingActions()
@ -25,13 +22,13 @@ export default defineComponent({
const HH_MM_FORMAT = 'HH:mm'
const AM_PM_FORMAT = 'A'
const YY_MM_DD_FORMAT = 'YYYY-MM-DD'
const YY_MM_DD_FORMAT = 'YY年MM月DD日'
const DDD_FORMAT = 'ddd'
const state = reactive({
lockCondition: useCondition(),
HH_MM: dayjs().format(HH_MM_FORMAT),
AM_PM: dayjs().format(AM_PM_FORMAT),
AM_PM: dayjs().locale('en').format(AM_PM_FORMAT),
YY_MM_DD: dayjs().format(YY_MM_DD_FORMAT),
DDD: dayjs().format(DDD_FORMAT),
})
@ -44,55 +41,30 @@ 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: toSigningFn,
negativeText: '取消',
onPositiveClick: () => {
logout()
setTimeout(() => {
updateSettingState('lockScreenSwitch', false)
})
},
})
}
/** 解锁 */
const unlockScreen = () => {
const catchPassword = getStorage<string>(
APP_CATCH_KEY.appLockScreenPasswordKey,
'localStorage',
)
if (!catchPassword) {
window.$dialog.warning({
title: '警告',
content: () => '检测到锁屏密码被修改,请重新登录',
closable: false,
maskClosable: false,
closeOnEsc: false,
positiveText: '重新登录',
onPositiveClick: toSigningFn,
})
return
}
const dCatchPassword = decrypt(catchPassword)
validate().then(() => {
if (dCatchPassword === state.lockCondition.lockPassword) {
formRef.value?.validate((error) => {
if (!error) {
setLockAppScreen(false)
updateSettingState('lockScreenSwitch', false)
removeStorage(APP_CATCH_KEY.appLockScreenPasswordKey, 'localStorage')
state.lockCondition = useCondition()
} else {
window.$message.warning('密码错误,请重新输入')
}
})
}
@ -106,84 +78,71 @@ 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, register } = this
const { unlockScreen, backToSigning } = this
return (
<div class="app-lock-screen__content app-lock-screen__content--full">
<div class="app-lock-screen__unlock">
<div class="app-lock-screen__unlock__content">
<div class="app-lock-screen__unlock__content-wrapper">
<div
class={[
'app-lock-screen__unlock__content-bg__wrapper',
'app-lock-screen__unlock__content-bg',
isTabletOrSmaller
? 'app-lock-screen__unlock__content-bg--smaller'
: '',
]}
>
<div class="left">{hmSplit[0]}</div>
<div class="right">{hmSplit[1]}</div>
</div>
<div class="app-lock-screen__unlock">
<div class="app-lock-screen__unlock__content">
<div class="app-lock-screen__unlock__content-wrapper">
<div
class={[
'app-lock-screen__unlock__content-bg__wrapper',
'app-lock-screen__unlock__content-bg',
isTabletOrSmaller
? 'app-lock-screen__unlock__content-bg--smaller'
: '',
]}
>
<div class="left">{hmSplit[0]}</div>
<div class="right">{hmSplit[1]}</div>
</div>
<div class="app-lock-screen__unlock__content-avatar">
<AppAvatar
avatarSize={52}
style="pointer-events: none;"
vertical
/>
</div>
<div class="app-lock-screen__unlock__content-avatar">
<AppAvatar avatarSize={52} style="pointer-events: none;" vertical />
</div>
<div class="app-lock-screen__unlock__content-input">
<NForm ref="formRef" model={this.lockCondition} rules={rules}>
<NFormItem path="lockPassword">
<NInput
ref="inputInstRef"
v-model:value={this.lockCondition.lockPassword}
type="password"
placeholder="请输入解锁密码"
clearable
minlength={6}
maxlength={12}
onKeydown={(e: KeyboardEvent) => {
if (e.code === 'Enter') {
unlockScreen()
}
}}
/>
</NFormItem>
<NFlex justify="space-between">
<NButton type="primary" text onClick={backToSigning.bind(this)}>
</NButton>
<NButton type="primary" text onClick={unlockScreen.bind(this)}>
</NButton>
</NFlex>
</NForm>
</div>
<div class="app-lock-screen__unlock__content-date">
<div class="current-date">
{HH_MM}&nbsp;<span>{AM_PM}</span>
</div>
<div class="app-lock-screen__unlock__content-input">
<RForm
onRegister={register}
model={this.lockCondition}
rules={rules}
>
<NFormItem path="lockPassword">
<NInput
autofocus
v-model:value={this.lockCondition.lockPassword}
type="password"
placeholder="请输入解锁密码"
clearable
minlength={6}
onKeydown={(e: KeyboardEvent) => {
if (e.code === 'Enter') {
unlockScreen()
}
}}
/>
</NFormItem>
<NFlex justify="space-between">
<NButton
type="primary"
text
onClick={backToSigning.bind(this)}
>
</NButton>
<NButton
type="primary"
text
onClick={unlockScreen.bind(this)}
>
</NButton>
</NFlex>
</RForm>
</div>
<div class="app-lock-screen__unlock__content-date">
<div class="current-year">
{YY_MM_DD}&nbsp;<span>{DDD}</span>&nbsp;<span>{AM_PM}</span>
</div>
<div class="current-year">
{YY_MM_DD}&nbsp;<span>{DDD}</span>
</div>
</div>
</div>

View File

@ -1,10 +1,4 @@
.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%;
@ -36,7 +30,7 @@
width: 100%;
height: 100%;
@include flexCenter;
font-size: 16.67rem;
font-size: 320px;
gap: 80px;
z-index: 0;
@ -85,23 +79,9 @@
& .current-year,
& .current-date span {
font-size: 1.875rem;
line-height: 2.25rem;
font-size: 1.5rem;
}
}
}
}
}
.ray-template--light {
.app-lock-screen__unlock__content-bg__wrapper {
background-color: #fff !important;
}
.app-lock-screen__unlock__content-bg {
& .left,
& .right {
background-color: rgba(244, 244, 245, 1) !important;
}
}
}

View File

@ -1,11 +1,22 @@
/**
*
* ,
*
*/
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({
@ -17,9 +28,12 @@ const AppLockScreen = defineComponent({
return {
lockScreenSwitchRef,
getLockAppScreen,
}
},
render() {
const { getLockAppScreen } = this
return (
<RModal
v-model:show={this.lockScreenSwitchRef}
@ -28,10 +42,12 @@ const AppLockScreen = defineComponent({
autoFocus={false}
maskClosable={false}
closeOnEsc={false}
preset="dialog"
preset={!getLockAppScreen() ? 'dialog' : void 0}
title="锁定屏幕"
>
<LockScreen />
<div class="app-lock-screen__content">
{!getLockAppScreen() ? <LockScreen /> : <UnlockScreen />}
</div>
</RModal>
)
},

View File

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

View File

@ -28,10 +28,18 @@ export default defineComponent({
if (version !== cacheVersion) {
modalShow.value = true
setStorage(APP_CATCH_KEY.appVersionProvider, version, 'localStorage')
setStorage<string>(
APP_CATCH_KEY.appVersionProvider,
version,
'localStorage',
)
}
} else {
setStorage(APP_CATCH_KEY.appVersionProvider, version, 'localStorage')
setStorage<string>(
APP_CATCH_KEY.appVersionProvider,
version,
'localStorage',
)
}
return {
@ -53,7 +61,7 @@ export default defineComponent({
title="发现新版本"
content="当前版本已更新,点击确认加载新版本~"
zIndex={999999999}
draggable
dad
positiveText="确认"
negativeText="取消"
onPositiveClick={logout}

View File

@ -26,7 +26,7 @@ export default defineComponent({
const { getWatermarkConfig, getWatermarkSwitch } = this
return getWatermarkSwitch ? (
<NWatermark {...getWatermarkConfig} fullscreen />
<NWatermark cross fullscreen {...getWatermarkConfig} />
) : null
},
})

View File

@ -1,24 +1,6 @@
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
@ -93,8 +75,6 @@ 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',
@ -108,8 +88,6 @@ export const APP_CATCH_KEY = {
appVersionProvider: 'appVersionProvider',
isAppLockScreen: 'isAppLockScreen',
appGlobalSearchOptions: 'appGlobalSearchOptions',
appMenuTagOptions: 'appMenuTagOptions',
appLockScreenPasswordKey: 'appLockScreenPasswordKey',
} as const
/**

View File

@ -3,36 +3,35 @@ import type { AppTheme } from '@/types'
export const APP_THEME: AppTheme = {
/**
*
* @description
*
* RGBARGB
*
* RGBARGB
*/
appThemeColors: [
'#2d8cf0',
'#3f9eff',
'#ff42bc',
'#ee4f12',
'#a6e4f7',
'#dbcb02',
'#18a058',
'#18A058',
],
// 系统主题色
/** 系统主题色 */
appPrimaryColor: {
// 主题色
/** 主题色 */
primaryColor: '#2d8cf0',
// 主题辅助色(用于整体 hover、active 等之类颜色)
primaryFadeColor: 'rgba(45, 140, 240, 0.85)',
/** 主题辅助色(用于整体 hover、active 等之类颜色) */
primaryFadeColor: 'rgba(45, 140, 240, 0.3)',
},
/**
*
* @description
* naive-ui
* : <https://www.naiveui.com/zh-CN/dark/docs/customize-theme>。
* 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
@ -48,24 +47,17 @@ export const APP_THEME: AppTheme = {
* ```
*/
appNaiveUIThemeOverrides: {
dark: {
common: {
borderRadius: '4px',
baseColor: 'rgb(18, 18, 18)',
},
},
light: {
common: {
borderRadius: '4px',
baseColor: 'rgb(255, 255, 255)',
},
},
dark: {},
light: {},
},
appNaiveUIThemeOverridesCommon: {
dark: {},
light: {},
},
/**
*
* @description
* echart
* json xxx-dark.json
* echart
* json xxx-dark.json
*
* [](https://xiaodaigua-ray.github.io/ray-template-doc/ray-template-docs/advanced/echart-themes.html)
*/

View File

@ -4,7 +4,7 @@ import type { Ref } from 'vue'
/**
*
* @description
* shallowRef
* ref
*
* , 使(scrollTo)
*
@ -16,12 +16,12 @@ import type { Ref } from 'vue'
* })
*/
export const LAYOUT_CONTENT_REF: Readonly<Ref<LayoutInst | null>> =
shallowRef<LayoutInst | null>(null)
ref<LayoutInst | null>(null)
/**
*
* @description
* shallowRef
* ref
*
*
* 使使 nextTick() dom
@ -31,7 +31,7 @@ export const LAYOUT_CONTENT_REF: Readonly<Ref<LayoutInst | null>> =
* })
*/
export const LAYOUT_SIDER_REF: Readonly<Ref<LayoutInst | null>> =
shallowRef<LayoutInst | null>(null)
ref<LayoutInst | null>(null)
export const SETUP_ROUTER_ACTION = {
/** 是否启用路由切换时顶部加载条 */

View File

@ -16,7 +16,7 @@ import type {
* request instance ,
*/
const requestHeaderToken = (ins: RequestInterceptorConfig, mode: string) => {
const token = getStorage<string | null>(APP_CATCH_KEY.token, 'localStorage')
const token = getStorage<string>(APP_CATCH_KEY.token, 'localStorage')
if (ins.url) {
// TODO: 根据 url 不同是否设置 token

View File

@ -40,14 +40,14 @@ function useRequest<
fetchOptions: AppRawRequestConfig<Response>,
option?: UseRequestOptions<Response, HookPlusParams, HookPlusPlugin>,
) {
const fn = () => {
const fc = () => {
const cb = request<Response>(fetchOptions)
return cb
}
const hooks = useHookPlusRequest<Response, HookPlusParams>(
fn,
fc,
Object.assign({}, option),
)

View File

@ -1,3 +1,10 @@
/**
*
*
* , inject
*
*/
import axios from 'axios'
import { AXIOS_CONFIG } from '@/app-config'
import { useAxiosInterceptor } from '@/axios/utils/interceptor'
@ -10,33 +17,23 @@ import {
setupRequestErrorInterceptor,
} from '@/axios/axios-interceptor/request'
import type { AxiosInstanceExpand, RequestInterceptorConfig } from './types'
import type { AxiosInstanceExpand } from './types'
// 创建 axios 实例
const server: AxiosInstanceExpand = axios.create(AXIOS_CONFIG)
// 获取拦截器实例
const { createAxiosInstance, beforeFetch, fetchError } = useAxiosInterceptor()
// 请求拦截器
server.interceptors.request.use(
(request) => {
// 生成 request instance
createAxiosInstance(
request as RequestInterceptorConfig<unknown>,
'requestInstance',
)
// 初始化拦截器所有已注入方法
setupRequestInterceptor()
// 执行拦截器所有已注入方法
beforeFetch('requestInstance', 'implementRequestInterceptorArray', 'ok')
createAxiosInstance(request, 'requestInstance') // 生成 request instance
setupRequestInterceptor() // 初始化拦截器所有已注入方法
beforeFetch('requestInstance', 'implementRequestInterceptorArray', 'ok') // 执行拦截器所有已注入方法
return request
},
(error) => {
// 初始化拦截器所有已注入方法(错误状态)
setupRequestErrorInterceptor()
// 执行所有已注入方法
fetchError('requestError', error, 'implementRequestInterceptorErrorArray')
setupRequestErrorInterceptor() // 初始化拦截器所有已注入方法(错误状态)
fetchError('requestError', error, 'implementRequestInterceptorErrorArray') // 执行所有已注入方法
return Promise.reject(error)
},
@ -45,22 +42,17 @@ 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)
},

View File

@ -1,3 +1,14 @@
/**
*
* axios
*
*
*
*
* axios ,
* 使,
*/
import RequestCanceler from '@/axios/utils/RequestCanceler'
import { getAppEnvironment } from '@/utils'
@ -13,36 +24,31 @@ 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,
@ -54,33 +60,29 @@ export const useAxiosInterceptor = () => {
instance as ResponseInterceptorConfig)
}
// 获取当前实例
/** 获取当前实例 */
const getAxiosInstance = (instanceKey: keyof AxiosFetchInstance) => {
return axiosFetchInstance[instanceKey]
}
// 设置注入方法队列
/** 设置注入方法队列 */
const setImplement = (
key: ImplementKeys | ErrorImplementKeys,
key: keyof ImplementQueue | keyof ErrorImplementQueue,
func: AnyFC[],
fetchType: FetchType,
) => {
fetchType === 'ok'
? (implement[key as ImplementKeys] = func)
: (errorImplement[key as ErrorImplementKeys] = func)
fetchType === 'ok' ? (implement[key] = func) : (errorImplement[key] = func)
}
// 获取队列中所有的所有拦截器方法
/** 获取队列中所有的所有拦截器方法 */
const getImplement = (
key: ImplementKeys | ErrorImplementKeys,
key: keyof ImplementQueue | keyof ErrorImplementQueue,
fetchType: FetchType,
): AnyFC[] => {
return fetchType === 'ok'
? implement[key as ImplementKeys]
: errorImplement[key as ErrorImplementKeys]
return fetchType === 'ok' ? implement[key] : errorImplement[key]
}
// 队列执行器
/** 队列执行器 */
const implementer = (funcs: AnyFC[], ...args: any[]) => {
if (Array.isArray(funcs)) {
funcs.forEach((curr) => {
@ -91,16 +93,16 @@ export const useAxiosInterceptor = () => {
}
}
// 请求、响应前执行拦截器队列中的所有方法
/** 请求、响应前执行拦截器队列中的所有方法 */
const beforeFetch = (
key: keyof AxiosFetchInstance,
implementKey: ImplementKeys | ErrorImplementKeys,
implementKey: keyof ImplementQueue | keyof ErrorImplementQueue,
fetchType: FetchType,
) => {
const funcArr =
fetchType === 'ok'
? implement[implementKey as ImplementKeys]
: errorImplement[implementKey as ErrorImplementKeys]
? implement[implementKey]
: errorImplement[implementKey]
const instance = getAxiosInstance(key)
const { MODE } = getAppEnvironment()
@ -109,11 +111,11 @@ export const useAxiosInterceptor = () => {
}
}
// 请求、响应错误时执行队列中所有方法
/** 请求、响应错误时执行队列中所有方法 */
const fetchError = (
key: keyof AxiosFetchError,
error: AxiosError<unknown, unknown>,
errorImplementKey: ErrorImplementKeys,
errorImplementKey: keyof ErrorImplementQueue,
) => {
axiosFetchError[key] = error

View File

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

View File

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

View File

@ -36,7 +36,6 @@ 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'
@ -125,7 +124,6 @@ export default defineComponent({
const __catch = {
aria: props.showAria,
}
const chartProvideOptions = inject(USE_CHART_PROVIDER_KEY, {})
/**
*
@ -176,19 +174,10 @@ 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('')

View File

@ -81,7 +81,6 @@ export default defineComponent({
yGap,
collapsedRows,
cssVars,
actionSpan,
bordered,
} = this
@ -98,11 +97,7 @@ export default defineComponent({
collapsedRows={collapsedRows}
>
{defaultSlot?.()}
<NGridItem
suffix
class="ray-collapse-grid__suffix--btn"
span={actionSpan}
>
<NGridItem suffix class="ray-collapse-grid__suffix--btn">
<NFlex justify="end" align="center">
{action?.()}
{collapse

View File

@ -5,17 +5,6 @@ import type { CollapseToggleText, ActionAlignType } from './types'
import type { AnyFC, MaybeArray } from '@/types'
const props = {
/**
*
* @description
*
*
* @default 1
*/
actionSpan: {
type: Number,
default: 1,
},
/**
*
* @description

View File

@ -1,388 +0,0 @@
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>
)
},
})

View File

@ -1,25 +0,0 @@
.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;
}

View File

@ -1,9 +0,0 @@
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 }

View File

@ -1,155 +0,0 @@
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>
},
})

View File

@ -1,17 +0,0 @@
import type { FlowOptions } from './types'
/**
*
* @description
* RFlow
* 使 container使
*/
export const getDefaultFlowOptions = (): FlowOptions => {
return {
grid: true,
partial: false,
keyboard: {
enabled: true,
},
}
}

View File

@ -1 +0,0 @@
export { useFlow } from './useFlow'

View File

@ -1,47 +0,0 @@
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
}

View File

@ -1,10 +0,0 @@
.r-flow {
width: var(--r-flow-width);
height: var(--r-flow-height);
}
.lf-text-input,
.lf-control-text,
.lf-menu-item {
color: initial;
}

View File

@ -1,104 +0,0 @@
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

View File

@ -1,61 +0,0 @@
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]

View File

@ -1,41 +1,17 @@
import { NForm, NSpin } from 'naive-ui'
import { NForm } from 'naive-ui'
import props from './props'
import { call, unrefElement } from '@/utils'
import { call } from '@/utils'
import { useTemplateRef } from 'vue'
import { useEventListener } from '@vueuse/core'
import type { RFormInst } from './types'
import type { ShallowRef } from 'vue'
import type { FormProps } from 'naive-ui'
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 方法正常调用
@ -44,16 +20,6 @@ export default defineComponent({
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()
@ -64,22 +30,13 @@ export default defineComponent({
},
render() {
const { $attrs, $props, $slots } = this
const { loading, loadingDescription, ...restProps } = $props
return (
<NSpin
show={loading}
description={loadingDescription}
style={{
height: 'auto',
<NForm {...$attrs} {...($props as FormProps)} ref="formRef">
{{
...$slots,
}}
>
<NForm {...$attrs} {...restProps} ref="formRef">
{{
...$slots,
}}
</NForm>
</NSpin>
</NForm>
)
},
})

View File

@ -36,15 +36,11 @@ import type { Recordable } from '@/types'
* },
* })
*/
const useForm = <
T extends Recordable = Recordable,
R extends RFormRules = RFormRules,
>(
model?: T | (() => T),
rules?: R | (() => R),
const useForm = <T extends Recordable, R extends RFormRules>(
model?: T,
rules?: R,
) => {
const formRef = shallowRef<RFormInst>()
const formModelRef = ref<T>()
const formRef = ref<RFormInst>()
const register = (inst: RFormInst) => {
if (inst) {
@ -62,15 +58,6 @@ const useForm = <
return formRef.value
}
// 初始化 formModelRef 的值,根据 model 的类型进行初始化
const initialFormModel = () => {
if (typeof model === 'function') {
formModelRef.value = model() ?? ({} as T)
} else {
formModelRef.value = cloneDeep(model) ?? ({} as T)
}
}
/**
*
* @description
@ -96,39 +83,10 @@ const useForm = <
*
* @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)
}
const formModel = () => cloneDeep(model) || ({} as T)
/**
*
@ -137,103 +95,7 @@ const useForm = <
*
* 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()
const formRules = () => cloneDeep(rules) || ({} as R)
return [
register,
@ -243,10 +105,6 @@ const useForm = <
restoreValidation,
formModel,
formRules,
reset,
validateTargetField,
formConditionRef: formModelRef as Ref<T>,
updateFormCondition,
},
] as const
}

View File

@ -1,75 +1,10 @@
import { formProps } from 'naive-ui'
import { omit } from 'lodash-es'
import type { MaybeArray, AnyFC } from '@/types'
import type { MaybeArray } 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,
},
...formProps,
/**
*
* @description

View File

@ -4,12 +4,14 @@ 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({
@ -22,11 +24,57 @@ 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,

View File

@ -1,13 +1,16 @@
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: RModalProps) => {
const create = (options: UseModalCreateOptions) => {
const { content, ...rest } = options
let contentNode = content
@ -20,11 +23,11 @@ const useModal = () => {
color: 'rgba(0, 0, 0, 0)',
colorHover: 'rgba(0, 0, 0, 0)',
},
trigger: 'hover',
trigger: 'none',
style: {
width: 'auto',
maxHeight:
'calc(var(--html-height) - 29px - var(--n-padding-bottom) - var(--n-padding-bottom) - var(--n-padding-top))',
height:
'calc(100vh - 29px - var(--n-padding-bottom) - var(--n-padding-bottom) - var(--n-padding-top))',
},
},
{
@ -34,7 +37,7 @@ const useModal = () => {
)
}
const { preset, fullscreen, width, cardWidth, dialogWidth } = options
const { preset, dad, fullscreen, width, cardWidth, dialogWidth } = options
const modalReactive = naiveCreate({
...rest,
content: contentNode,
@ -54,8 +57,28 @@ const useModal = () => {
return
}
// 是否启用拖拽
if (dad) {
setupInteract(modalElement, {
preset,
x: 0,
y: 0,
})
}
// preset 为 cardfullscreen 为 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',

View File

@ -4,11 +4,6 @@
// 当设置全屏时启用滚动
& .n-card__content {
overflow: scroll;
max-height: calc(
var(--html-height) - var(--n-padding-bottom) - var(--n-padding-bottom) - var(
--n-padding-top
)
);
}
}

View File

@ -3,6 +3,17 @@ import type { PropType } from 'vue'
const props = {
...modalProps,
/**
*
* @description
*
*
* @default true
*/
memo: {
type: Boolean,
default: true,
},
/**
*
* @description
@ -47,6 +58,18 @@ const props = {
type: [String, Number] as PropType<string | number>,
default: 446,
},
/**
*
* @description
*
* header
*
* @default false
*/
dad: {
type: Boolean,
default: false,
},
}
export default props

View File

@ -1,6 +1,14 @@
import type { ModalOptions as NaiveModalOptions } from 'naive-ui'
export interface RModalProps extends NaiveModalOptions {
/**
*
* @description
*
*
* @default true
*/
memo?: boolean
/**
*
* @description
@ -33,4 +41,13 @@ export interface RModalProps extends NaiveModalOptions {
* @default 446
*/
dialogWidth?: number | string
/**
*
* @description
*
* header
*
* @default false
*/
dad?: boolean
}

View File

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

View File

@ -94,6 +94,7 @@ export default defineComponent({
return (
<NTabs
{...($props as TabsProps)}
ref="segmentRef"
style={[cssVars]}
class="r-segment"
type="segment"

View File

@ -61,36 +61,6 @@ export default defineComponent({
pick(props, 'striped', 'bordered'),
),
)
// 默认设置 card header style
const cardHeaderStyle = computed(() => {
const { title, tool, cardProps } = props
const { headerStyle = {} } = cardProps ?? {}
if (!title && !tool) {
return Object.assign(
{},
{
paddingTop: '0px',
},
headerStyle,
)
}
return headerStyle
})
// 如果启用了 flexAutoHeight 属性,则自动继承高度
const flexAutoHeightStyle = computed(() => {
const { flexAutoHeight } = props
if (!flexAutoHeight) {
return null
}
return {
height: '100%',
flex: 1,
}
})
/**
*
@ -158,7 +128,6 @@ export default defineComponent({
if (onUpdateColumns) {
call(onUpdateColumns, options)
}
if ($onUpdateColumns) {
call($onUpdateColumns, options)
}
@ -181,9 +150,9 @@ export default defineComponent({
const keys = Object.keys(propsPopselectValue.value)
keys.forEach((key) => {
propsPopselectValue.value[
key as keyof typeof propsPopselectValue.value
] = value.includes(key as PropsComponentPopselectKeys)
propsPopselectValue.value[key] = value.includes(
key as PropsComponentPopselectKeys,
)
})
}
@ -261,8 +230,6 @@ export default defineComponent({
tool,
wrapperRef,
propsPopselectValue,
cardHeaderStyle,
flexAutoHeightStyle,
}
},
render() {
@ -275,8 +242,6 @@ export default defineComponent({
uuidWrapper,
privateReactive,
propsPopselectValue,
cardHeaderStyle,
flexAutoHeightStyle,
} = this
const { class: className, ...restAttrs } = $attrs
const { tool, combineRowProps, contextMenuSelect } = this
@ -288,15 +253,12 @@ export default defineComponent({
title,
tableFlexHeight,
cardProps,
flexAutoHeight,
flexHeight,
...restProps
} = $props
const { headerStyle, ...restCardProps } = cardProps ?? {}
} = $props as ExtractPublicPropTypes<typeof props>
return (
<NCard
{...restCardProps}
{...cardProps}
{...{
id: uuidWrapper,
}}
@ -304,8 +266,6 @@ export default defineComponent({
ref="wrapperRef"
bordered={wrapperBordered}
class={className}
// flexAutoHeight 具有更高的优先级
style={Object.assign({}, cardHeaderStyle, flexAutoHeightStyle)}
>
{{
default: () => (
@ -316,14 +276,12 @@ export default defineComponent({
}}
{...(restProps as DataTableProps)}
{...propsPopselectValue}
flexHeight={flexAutoHeight ? true : flexHeight}
rowProps={combineRowProps.bind(this)}
size={privateReactive.size}
ref="rTableInst"
style={{
height: flexAutoHeight
? '100%'
: tableFlexHeight !== null && tableFlexHeight !== void 0
height:
tableFlexHeight !== null && tableFlexHeight !== void 0
? completeSize(tableFlexHeight)
: null,
}}

View File

@ -113,6 +113,7 @@ export default defineComponent({
return true
})
// eslint-disable-next-line @typescript-eslint/no-explicit-any
return cloneColumns.map((curr, idx) => {
const { key, title, children, fixed, isResizable, ...args } =
curr as C
@ -169,7 +170,7 @@ export default defineComponent({
}
}) as C[]
},
// eslint-disable-next-line @typescript-eslint/no-empty-function
set: () => {},
})
@ -190,9 +191,8 @@ export default defineComponent({
}
const fixedClick: FixedClick = (type, option, index) => {
const key = `${type}FixedActivated` as const
const otherKey =
`${type === 'left' ? 'right' : 'left'}FixedActivated` as const
const key = `${type}FixedActivated`
const otherKey = `${type === 'left' ? 'right' : 'left'}FixedActivated`
option[otherKey] = false
option[key] = !option[key]

View File

@ -41,13 +41,13 @@ import type { PrintDomOptions } from '@/utils'
* })
*/
const useTable = () => {
const tableRef = shallowRef<RTableInst>()
const extraRef = shallowRef<TableProvider>({} as TableProvider)
const tableRef = ref<RTableInst>()
let extra = {} as TableProvider
const register: UseTableRegister = (inst, extraInfo) => {
if (inst) {
tableRef.value = inst
extraRef.value = extraInfo
extra = extraInfo
}
}
@ -140,7 +140,7 @@ const useTable = () => {
*
*/
const print = (options?: PrintDomOptions) => {
const { uuidWrapper } = extraRef.value ?? {}
const { uuidWrapper } = extra
if (uuidWrapper) {
const tableWrapperElement = document.getElementById(uuidWrapper)
@ -149,23 +149,9 @@ const useTable = () => {
}
}
/**
*
* @description
*
*
* @see https://www.naiveui.com/zh-CN/dark/components/data-table#DataTable-Methods
*/
const filter = (filters: FilterState | null) =>
getTableInstance().filter.call(null, filters)
/**
*
* @description
* Table
*/
const getTableConfig = () => extraRef.value
return [
register,
{
@ -179,7 +165,6 @@ const useTable = () => {
sort,
print,
filter,
getTableConfig,
},
] as const
}

View File

@ -238,17 +238,6 @@ const props = {
type: [Function, Array] as PropType<MaybeArray<UseTableRegister>>,
default: null,
},
/**
*
* @description
*
*
* @default false
*/
flexAutoHeight: {
type: Boolean,
default: false,
},
} as const
export default props

View File

@ -54,7 +54,6 @@ export interface RTableInst extends Omit<DataTableInst, 'clearFilter'> {
*
*/
print: (options?: PrintDomOptions) => void
config: TableProvider
}
export type OverridesTableColumn<T = Recordable> = C | DataTableColumn<T>

View File

@ -31,9 +31,8 @@ import type { TransitionProps } from './types'
/**
*
* @description
* 使用宏编译模式时可以使用 defineOptions 声明组件选项
* 常用方法即是声明该组件的 name inheritAttrs 等属性
* 使用宏编译模式时可以使用 defineOptions 声明组件选项
* 常用方法即是声明该组件的 name inheritAttrs 等属性
*/
defineOptions({
name: 'RTransitionComponent',

View File

@ -1,5 +1,4 @@
import RCollapse from '../components/pro/RCollapse/Collapse'
import RDraggableCard from '../components/base/RDraggableCard/DraggableCard'
// 导出所有自定义组件
export * from './base/RChart'
@ -14,8 +13,7 @@ export * from './base/RForm'
export * from './base/RSegment'
export * from './base/RBarcode'
export * from '../components/pro/RTablePro'
export * from './base/RFlow'
export { RCollapse, RDraggableCard }
export { RCollapse }
// 导出自定义组件类型
export type * from './base/RChart/src/types'
@ -27,10 +25,3 @@ export type * from './base/RForm/src/types'
export type * from './base/RModal/src/types'
export type * from './base/RSegment/src/types'
export type * from './base/RBarcode/src/types'
export type {
NodeConfig,
EdgeConfig,
FlowGraphData,
FlowOptions,
} from './base/RFlow/src/types'
export type { DefaultPosition } from './base/RDraggableCard/DraggableCard'

View File

@ -3,27 +3,7 @@ import { RCollapseGrid, RForm } from '@/components'
import formProps from '../../base/RForm/src/props'
import collapseGridProps from '../../base/RCollapseGrid/src/props'
import type { GridProps } from 'naive-ui'
export const collapseProps = Object.assign({}, formProps, {
...collapseGridProps,
open: {
type: Boolean,
default: true,
},
cols: {
type: [Number, String] as PropType<GridProps['cols']>,
default: '4 xs:1 s:2 m:2 l:4 xl:4 2xl:6',
},
bordered: {
type: Boolean,
default: true,
},
responsive: {
type: String as PropType<GridProps['responsive']>,
default: 'screen',
},
})
import type { FormProps, GridProps } from 'naive-ui'
/**
*
@ -33,10 +13,25 @@ export const collapseProps = Object.assign({}, formProps, {
*/
export default defineComponent({
name: 'RCollapse',
props: collapseProps,
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
const { labelPlacement, showFeedback, ...rest } = $props as FormProps &
GridProps
return (
<RForm {...rest} labelPlacement="top" showFeedback={false}>

View File

@ -3,23 +3,9 @@ import tableProProps from './src/props'
import { useTablePro } from './src/hooks/useTablePro'
import type { ExtractPropTypes } from 'vue'
import type {
BasePagination,
TablePagination,
FormatRangeTime,
TableRequestConfig,
TableProFieldNames,
} from './src/types'
type TableProProps = ExtractPropTypes<typeof tableProProps>
export type {
TableProProps,
BasePagination,
TablePagination,
FormatRangeTime,
TableRequestConfig,
TableProFieldNames,
}
export type { TableProProps }
export { RTablePro, useTablePro, tableProProps }

View File

@ -1,20 +1,30 @@
import { RTable } from '../../../base/RTable'
import { RTable } from '@/components'
import props from './props'
import useTable from '../../../base/RTable/src/hooks/useTable'
import { call, removeDuplicateKeys } from '@/utils'
import { useTable } from '@/components'
import { call } from '@/utils'
import { usePagination } from '@/hooks'
import { omit } from 'lodash-es'
import type { TablePagination, TableRequestConfig, TableProInst } from './types'
import type { RTableInst } from '../../..'
export default defineComponent({
name: 'RTablePro',
props,
setup(props, ctx) {
const { expose } = ctx
const [register, tableFns] = useTable()
setup(props) {
const [
register,
{
clearFilters,
clearSorter,
downloadCsv,
filters,
page,
scrollTo,
sort,
print,
filter,
},
] = useTable()
const [
paginationRef,
{
@ -26,20 +36,16 @@ export default defineComponent({
getItemCount,
},
] = usePagination(void 0, {
prefix: props.paginationPrefix,
prefix: (info) => `${info.itemCount}`,
})
const tableRequestRef = computed(() => props.request)
// 获取最新 statistics 和 pagination 值
const update = (): TablePagination => {
const page = getPage()
const pageSize = getPageSize()
const itemCount = getItemCount()
return {
page,
pageSize,
itemCount,
getItemCount,
getPage,
getPageSize,
}
}
@ -56,8 +62,7 @@ export default defineComponent({
const combineRequestParams = (extraConfig?: TableRequestConfig) => {
const config = Object.assign({}, props.requestConfig, extraConfig)
const { formatRangeTime, excludeParams } = config
let params = config.params || {}
const { params, formatRangeTime } = config
// 转换时间范围,该功能仅支持 NDatePicker range 模式参数
if (formatRangeTime?.length && params) {
@ -79,13 +84,6 @@ export default defineComponent({
})
}
params = removeDuplicateKeys(params)
// 排除指定的请求参数
if (excludeParams) {
params = omit(params, excludeParams)
}
const requestParams = Object.assign({}, params, {
page: getPage(),
pageSize: getPageSize(),
@ -94,33 +92,21 @@ export default defineComponent({
return requestParams
}
// 同步执行 request 请求,允许重置 pagination 请求,返回 Promise 对象
const runResetPaginationRequest: TableProInst['runTableRequest'] = (
extraConfig,
reset = true,
) => {
if (reset) {
resetPagination()
}
// 会重置 pagination 的请求
const runResetPaginationRequest = (extraConfig?: TableRequestConfig) => {
resetPagination()
const requestParams = combineRequestParams(extraConfig)
tableRequestRef.value?.(requestParams)
}
// 异步执行 request 请求,允许重置 pagination 请求,返回 Promise 对象
const runResetPaginationRequestAsync: TableProInst['runAsyncTableRequest'] =
(extraConfig, reset = true) => {
return new Promise((resolve, reject) => {
try {
runResetPaginationRequest(extraConfig, reset)
// 不会重置 pagination 的请求
const runRequest = (extraConfig?: TableRequestConfig) => {
const requestParams = combineRequestParams(extraConfig)
resolve(void 0)
} catch (e) {
reject(e)
}
})
}
tableRequestRef.value?.(requestParams)
}
watchEffect(() => {
setItemCount(props.paginationCount)
@ -128,7 +114,7 @@ export default defineComponent({
const { manual } = props
if (!manual) {
runResetPaginationRequest(void 0, false)
runRequest()
}
emitTableUpdate()
@ -140,16 +126,22 @@ export default defineComponent({
if (onRegister) {
call(onRegister, {
...(tableFns as unknown as RTableInst),
getTablePagination: update,
runTableRequest: runResetPaginationRequest,
runAsyncTableRequest: runResetPaginationRequestAsync,
getCurrentTableRequestParams: combineRequestParams,
resetTablePagination: resetPagination,
clearFilters,
clearSorter,
downloadCsv,
filters,
page,
scrollTo,
sort,
print,
filter,
getCurrentTableRequestParams:
combineRequestParams as TableProInst['getCurrentTableRequestParams'],
})
}
})
expose()
return {
register,
@ -158,7 +150,9 @@ export default defineComponent({
},
render() {
const { register, $props, paginationRef, $slots } = this
const { onRegister, showPagination, ...rest } = $props
const { onRegister, showPagination, ...rest } = $props as ExtractPropTypes<
typeof props
>
return (
<RTable

View File

@ -1,8 +1,7 @@
import { printDom } from '@/utils'
import type { Recordable } from '@/types'
import type { TableProInst, TableRequestConfig } from '../types'
import type {
RTableInst,
CsvOptionsType,
FilterState,
ScrollToOptions,
@ -50,9 +49,6 @@ export const useTablePro = () => {
getTableProInstance().getTablePagination.call(null)
/**
*
* @param extraConfig
* @param reset
*
* @description
*
@ -62,13 +58,9 @@ export const useTablePro = () => {
* TableRequestConfig props tableRequestConfig
* pagination
*/
const runTableRequest = <
T extends Recordable,
ExcludeParams extends keyof T = keyof T,
>(
extraConfig?: TableRequestConfig<T, ExcludeParams>,
reset?: boolean,
) => getTableProInstance().runTableRequest.call(null, extraConfig, reset)
const runTableRequest = <T extends Recordable>(
extraConfig?: TableRequestConfig<T>,
) => getTableProInstance().runTableRequest.call(null, extraConfig)
/**
*
@ -89,8 +81,6 @@ export const useTablePro = () => {
const clearSorter = () => getTableProInstance().clearSorter.call(null)
/**
*
* @param options CSV
*
* @description
* CSV
@ -101,8 +91,6 @@ export const useTablePro = () => {
getTableProInstance().downloadCsv.call(null, options)
/**
*
* @param filters
*
* @description
*
@ -113,8 +101,6 @@ export const useTablePro = () => {
getTableProInstance().filters.call(null, filters)
/**
*
* @param page
*
* @description
* page
@ -124,8 +110,6 @@ export const useTablePro = () => {
const page = (page: number) => getTableProInstance().page.call(null, page)
/**
*
* @param options
*
* @description
*
@ -137,9 +121,6 @@ export const useTablePro = () => {
getTableProInstance().scrollTo(options as any)
/**
*
* @param columnKey
* @param order
*
* @description
*
@ -150,74 +131,27 @@ export const useTablePro = () => {
getTableProInstance().sort.call(null, columnKey, order)
/**
*
* @param options
*
* @description
*
*/
const print = (options?: PrintDomOptions) => {
const { config } = getTableProInstance()
const { uuidWrapper } = config ?? {}
if (uuidWrapper) {
const tableWrapperElement = document.getElementById(uuidWrapper)
printDom(tableWrapperElement, options)
}
}
const print = (options?: PrintDomOptions) =>
getTableProInstance().print.call(null, options)
/**
*
* @param extraConfig
* @param excludeParams
*
* @description
*
*/
const getCurrentTableRequestParams = <
T extends Recordable,
ExcludeParams extends keyof T = keyof T,
>(
extraConfig?: TableRequestConfig<T, ExcludeParams>,
const getCurrentTableRequestParams = <T = Recordable>(
extraConfig?: TableRequestConfig<T>,
): T & Recordable =>
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
getTableProInstance().getCurrentTableRequestParams.call(null, extraConfig)
/**
*
* @param extraConfig
*
* @description
*
*/
const resetTablePagination = () =>
getTableProInstance().resetTablePagination.call(null)
/**
*
* @param extraConfig
* @param reset
*
* @description
*
*/
const runAsyncTableRequest = <
T extends Recordable,
ExcludeParams extends keyof T = keyof T,
>(
extraConfig?: TableRequestConfig<T, ExcludeParams>,
reset?: boolean,
) => getTableProInstance().runAsyncTableRequest.call(null, extraConfig, reset)
/**
*
* @description
* TablePro
*/
const getTableProConfig = () => getTableProInstance().config
return [
register,
{
@ -233,9 +167,6 @@ export const useTablePro = () => {
runTableRequest,
print,
getCurrentTableRequestParams,
resetTablePagination,
runAsyncTableRequest,
getTableProConfig,
},
] as const
}

View File

@ -2,12 +2,7 @@ import { tableProps } from '@/components'
import { omit } from 'lodash-es'
import type { PropType } from 'vue'
import type {
TableProInst,
TableRequestConfig,
PaginationPrefix,
TablePaginationUpdate,
} from './types'
import type { TableProInst, TablePagination, TableRequestConfig } from './types'
import type { AnyFC } from '@/types'
const props = {
@ -67,7 +62,7 @@ const props = {
* @default undefined
*/
onTablePaginationUpdate: {
type: Function as PropType<TablePaginationUpdate>,
type: Function as PropType<(pagination: TablePagination) => void>,
},
/**
*
@ -75,10 +70,11 @@ const props = {
*
*
*
* @default undefined
* @default {}
*/
requestConfig: {
type: Object as PropType<TableRequestConfig>,
default: () => ({}),
},
/**
*
@ -105,19 +101,6 @@ const props = {
type: Boolean,
default: true,
},
/**
*
* @description
*
*
*
* @default (info) => `${info.itemCount}`
*/
paginationPrefix: {
type: Function as PropType<PaginationPrefix>,
default: (info: Parameters<NonNullable<PaginationPrefix>>[0]) =>
`${info.itemCount}`,
},
}
export default props

View File

@ -1,5 +1,5 @@
import type { TableProps, RTableInst } from '@/components'
import type { UsePaginationOptions } from '@/hooks'
import type { UsePaginationReturn } from '@/hooks'
import type { Recordable } from '@/types'
export type FormatRangeTime = {
@ -17,27 +17,17 @@ export type FormatRangeTime = {
target: [string | number, string | number]
}
export interface BasePagination {
page: number
pageSize: number
itemCount: number
}
/**
*
* @description
* Pagination
*/
export type TablePagination = BasePagination
export type TablePagination = Pick<
UsePaginationReturn[1],
'getItemCount' | 'getPage' | 'getPageSize'
>
export type TablePaginationUpdate = (pagination: TablePagination) => void
export type PaginationPrefix = UsePaginationOptions['prefix']
export interface TableRequestConfig<
Params = Recordable,
ExcludeParams extends keyof Params = keyof Params,
> {
export interface TableRequestConfig<Params = Recordable> {
/**
*
* @description
@ -55,14 +45,6 @@ export interface TableRequestConfig<
* @default undefined
*/
formatRangeTime?: FormatRangeTime[]
/**
*
* @description
*
*
* @default undefined
*/
excludeParams?: ExcludeParams[]
}
export type TableProProps = Omit<TableProps, 'pagination'>
@ -72,112 +54,22 @@ export interface TableProInst extends Omit<RTableInst, 'getTableInstance'> {
*
* @description
* pagination
*
* @example
* const [register, { getTablePagination }] = useTablePro()
*
* // 获取当前 pagination 的值
* const pagination = getTablePagination()
*/
getTablePagination: () => TablePagination
/**
*
* @param extraConfig
* @param reset
*
* @description
*
*
* @example
* const [register, { runTableRequest }] = useTablePro()
*
* // 重置分页请求
* runTableRequest(void 0, true)
* runTableRequest()
* // 不重置分页请求
* runTableRequest(void 0, false)
*/
runTableRequest: <
T extends Recordable,
ExcludeParams extends keyof T = keyof T,
>(
extraConfig?: TableRequestConfig<T, ExcludeParams>,
reset?: boolean,
) => void
/**
*
* @param extraConfig
* @param reset
*
* @description
*
*
* @example
* const [register, { runAsyncTableRequest }] = useTablePro()
*
* // 重置分页请求
* runAsyncTableRequest(void 0, true)
* runAsyncTableRequest()
* // 不重置分页请求
* runAsyncTableRequest(void 0, false)
*/
runAsyncTableRequest: <
T extends Recordable,
ExcludeParams extends keyof T = keyof T,
>(
extraConfig?: TableRequestConfig<T, ExcludeParams>,
reset?: boolean,
) => Promise<void>
runTableRequest: (extraConfig?: TableRequestConfig) => void
/**
*
* @param extraConfig
*
* @description
*
*
* @example
* const [register, { getCurrentTableRequestParams }] = useTablePro()
*
* // 获取当前内部表格请求参数
* const params = getCurrentTableRequestParams()
*/
getCurrentTableRequestParams: <
T extends Recordable,
ExcludeParams extends keyof T = keyof T,
>(
extraConfig?: TableRequestConfig<T, ExcludeParams>,
) => TableRequestConfig<T, ExcludeParams>['params'] & Recordable
/**
*
* @description
*
*
* @example
* const [register, { resetTablePagination }] = useTablePro()
*
* // 重置表格分页为初始化状态,即第 1 页,每页 10 条数据
* resetTablePagination()
*/
resetTablePagination: () => void
}
export interface TableProFieldNames {
/**
*
* @description
*
*/
page: string
/**
*
* @description
*
*/
pageSize: string
/**
*
* @description
*
*/
itemCount: string
getCurrentTableRequestParams: <T = Recordable>(
extraConfig?: TableRequestConfig<T>,
) => TableRequestConfig<T>['params'] & Recordable
}

View File

@ -1,10 +1,6 @@
import dayjs from 'dayjs'
import { DEFAULT_DAYJS_LOCAL } from '@/app-config'
import 'dayjs/locale/zh-cn'
import { getStorage } from '@/utils'
import { APP_CATCH_KEY, DAYJS_LOCAL_MAP } from '@/app-config'
import type { SettingState } from '@/store/modules/setting/types'
/**
*
@ -14,16 +10,5 @@ import type { SettingState } from '@/store/modules/setting/types'
* dayjs
*/
export const setupDayjs = () => {
const { localeLanguage } = getStorage<SettingState>(
APP_CATCH_KEY.appPiniaSettingStore,
'localStorage',
{
defaultValue: {} as SettingState,
},
)
const local =
DAYJS_LOCAL_MAP[localeLanguage as keyof typeof DAYJS_LOCAL_MAP] ||
DEFAULT_DAYJS_LOCAL
dayjs.locale(local)
dayjs.locale(DEFAULT_DAYJS_LOCAL)
}

View File

@ -41,16 +41,15 @@ const throttleDirective: CustomDirectiveFC<
throttleFunction = throttle(func, wait, Object.assign({}, options))
cleanup = useEventListener(el, trigger, throttleFunction)
useEventListener(el, trigger, throttleFunction)
},
beforeUnmount: () => {
if (throttleFunction) {
throttleFunction.cancel()
cleanup?.()
}
throttleFunction = null
cleanup?.()
},
}
}

View File

@ -1,12 +1,12 @@
import type { Directive, App } from 'vue'
import type { Recordable } from '@/types'
import type { Directive } from 'vue'
import type { App } from 'vue'
export type { DebounceBindingOptions } from './modules/debounce/types'
export type { ThrottleBindingOptions } from './modules/throttle/types'
export type CustomDirectiveFC<T, K> = () => Directive<T, K>
export interface DirectiveModules<T = unknown, K = unknown> extends Recordable {
export interface DirectiveModules<T = unknown, K = unknown> extends Object {
default: CustomDirectiveFC<T, K>
}

View File

@ -8,10 +8,10 @@ export const combineDirective = <
) => {
const directives = Object.keys(directiveModules).reduce(
(pre, curr) => {
const fn = directiveModules[curr]?.default
const fc = directiveModules[curr]?.default
if (typeof fn === 'function') {
pre[curr as K] = fn
if (typeof fc === 'function') {
pre[curr] = fc
return pre
} else {

View File

@ -15,8 +15,6 @@
* createVariableState({ your state })
*/
import { updateObjectValue } from '@/utils'
import type { AnyFC } from '@/types'
/**
@ -59,7 +57,11 @@ export function setVariable<T extends VariableStateKey, FC extends AnyFC>(
value: VariableState[T],
cb?: FC,
) {
updateObjectValue(variableState, key, value, cb)
if (Object.hasOwn(variableState, key)) {
variableState[key] = value
cb?.()
}
}
/**

View File

@ -23,11 +23,10 @@ export interface UseContextmenuCoordinateOptions {
*
* @param target
*
* @description
* NDropdown 使
* NDropdown 使
*
* @example
* const target = useTemplateRef<HTMLElement | null>('target')
* const target = ref<HTMLElement | null>(null)
* const { x, y, show, stop } = useContextmenuCoordinate(target)
*
* stop
@ -45,13 +44,6 @@ export const useContextmenuCoordinate = (
const show = ref(false) // 是否显示右键菜单
const { clickOutside } = options ?? {}
/**
*
* @param value
*
* @description
*
*/
const updateShow = (value: boolean) => {
show.value = value
}
@ -60,9 +52,8 @@ export const useContextmenuCoordinate = (
*
* @param evt
*
* @description
*
*
*
*
*/
const bindContextMenuEvent = (evt: Event) => {
evt.preventDefault()
@ -82,8 +73,7 @@ export const useContextmenuCoordinate = (
if (clickOutside) {
/**
*
* @description
*
*
*/
onClickOutside(target as MaybeElementRef<MaybeElement>, (detectIframe) => {
clickOutside(detectIframe)
@ -92,8 +82,7 @@ export const useContextmenuCoordinate = (
/**
*
* @description
* ref dom
* ref dom
*/
const cleanupContextmenu = useEventListener(
target,
@ -101,11 +90,9 @@ export const useContextmenuCoordinate = (
bindContextMenuEvent,
options,
)
/**
*
* @description
* ref dom
* ref dom
*/
const cleanupClick = useEventListener(target, 'click', () => {
updateShow(false)
@ -113,9 +100,8 @@ export const useContextmenuCoordinate = (
/**
*
* @description
*
*
*
*
*/
const stop = () => {
cleanupContextmenu()

View File

@ -77,7 +77,6 @@ export function useBadge() {
/**
*
* @param target key AppMenuOption)
* @param extraOption
*
* @example
* const { show } = useBadge()
@ -85,12 +84,8 @@ export function useBadge() {
* show('your key')
* show({ ...AppMenuOption })
*/
const show = (
target: BadgeKey,
extraOption?: Omit<AppMenuExtraOptions, 'show'>,
) => {
const show = (target: BadgeKey) => {
normalOption(target, 'show', {
...extraOption,
show: true,
})
}

View File

@ -10,17 +10,17 @@ export type CloseMenuTag = Key | MenuTagOptions
/**
*
* @param target key
* @param fn
* @param fc
*
*
*/
const normalMenuTagOption = (target: CloseMenuTag, fn: string) => {
const normalMenuTagOption = (target: CloseMenuTag, fc: string) => {
const { getMenuTagOptions } = useMenuGetters()
if (typeof target === 'number') {
// 判断是否为 NaN
if (isNaN(target)) {
console.warn(`${fn}: The ${target} is NaN, expect number.`)
console.warn(`${fc}: The ${target} is NaN, expect number.`)
return
}
@ -28,7 +28,7 @@ const normalMenuTagOption = (target: CloseMenuTag, fn: string) => {
// 判断是否超出当前标签页列表最大长度或者是否为负数
if (target > getMenuTagOptions.value.length || target < -1) {
console.warn(
`${fn}: The incoming index ${target} did not match the corresponding item.`,
`${fc}: The incoming index ${target} did not match the corresponding item.`,
)
return
@ -50,7 +50,7 @@ const normalMenuTagOption = (target: CloseMenuTag, fn: string) => {
index,
}
: console.warn(
`${fn}: The incoming key ${target} did not match the corresponding item.`,
`${fc}: The incoming key ${target} did not match the corresponding item.`,
)
} else {
const { fullPath } = target
@ -60,7 +60,7 @@ const normalMenuTagOption = (target: CloseMenuTag, fn: string) => {
if (index === -1) {
console.warn(
`${fn}: The incoming menuTag option ${target.fullPath} did not match the corresponding item.`,
`${fc}: The incoming menuTag option ${target.fullPath} did not match the corresponding item.`,
)
return
@ -170,19 +170,11 @@ export function useSiderBar() {
spliceMenTagOptions(index)
if (option.fullPath === getMenuKey.value) {
let i = checkCloseLeft(index)
? index - 1
: checkCloseRight(index)
? index
: index - 1
const tag = getMenuTagOptions.value[index - 1]
if (i < 0) {
i = 0
if (tag) {
changeMenuModelValue(tag.fullPath, tag)
}
const tag = getMenuTagOptions.value[i]
tag && changeMenuModelValue(tag.fullPath, tag)
}
}
}

View File

@ -2,7 +2,6 @@ import { useSettingActions, useSettingGetters } from '@/store'
import { useI18n } from '@/hooks'
import { APP_THEME } from '@/app-config'
import { useColorMode } from '@vueuse/core'
import { merge } from 'lodash-es'
export type ThemeLabel = 'Dark' | 'Light'
@ -34,15 +33,17 @@ const setThemeOverrides = (theme: boolean) => {
updateSettingState(
'primaryColorOverride',
theme
? merge(
? Object.assign(
{},
getPrimaryColorOverride.value,
APP_THEME.appNaiveUIThemeOverrides.dark,
APP_THEME.appNaiveUIThemeOverridesCommon.dark,
)
: merge(
: Object.assign(
{},
getPrimaryColorOverride.value,
APP_THEME.appNaiveUIThemeOverrides.light,
APP_THEME.appNaiveUIThemeOverridesCommon.light,
),
)
}

View File

@ -16,7 +16,7 @@ export const useWatermark = () => {
*/
const setWatermarkContent = (content: string) => {
const { getWatermarkConfig } = useSettingGetters()
const assignWatermark = Object.assign({}, getWatermarkConfig.value, {
const assignWatermark = Object.assign(getWatermarkConfig.value, {
content,
})
const { updateSettingState } = useSettingActions()

View File

@ -1,8 +1,6 @@
import dayjs from 'dayjs'
import { DEFAULT_DAYJS_LOCAL, DAYJS_LOCAL_MAP } from '@/app-config'
import type { DayjsLocalMap } from '@/types'
export interface FormatOption {
format?: string
}
@ -21,7 +19,7 @@ export interface StartAndEndOfDay {
formatEndOfDay: string
}
export type LocalKey = keyof DayjsLocalMap
export type LocalKey = typeof DEFAULT_DAYJS_LOCAL
const defaultDayjsFormat = 'YYYY-MM-DD HH:mm:ss'
@ -42,8 +40,8 @@ export const useDayjs = () => {
* dayjs
*
* @example
* locale('zh-CN')
* locale('en-US')
* locale('en')
* locale('zh-cn')
*/
const locale = (key: LocalKey) => {
const locale = DAYJS_LOCAL_MAP[key]

View File

@ -10,32 +10,12 @@ import {
import type { BasicTarget, TargetType } from '@/types'
// html-to-image 方法
const domToImageMethods = {
svg: toSvg,
png: toPng,
jpeg: toJpeg,
blob: toBlob,
pixelData: toPixelData,
canvas: toCanvas,
} as const
// 获取 html-to-image Options
type HtmlToImageOptions = Partial<NonNullable<Parameters<typeof toPng>[1]>>
// 获取 html-to-image 方法 keys
type DomToImageMethodKeys = keyof typeof domToImageMethods
// 获取 html-to-image 方法返回值
type DomToImageReturnType<ImageType extends DomToImageMethodKeys> = Awaited<
ReturnType<(typeof domToImageMethods)[ImageType]>
>
// 自定义拓展 Options
type Options<
interface Options<
T extends TargetType = Element,
ImageType extends DomToImageMethodKeys = DomToImageMethodKeys,
> = {
ImageType extends DomToImageMethodKeys = 'jpeg',
> {
/**
*
* @description
@ -43,7 +23,7 @@ type Options<
*
* @default jpeg
*/
imageType?: ImageType
imageType?: DomToImageReturnType<ImageType>
/**
*
* @param element current dom
@ -86,10 +66,22 @@ type Options<
finally?: (element: T) => void
}
export type UseDomToImageOptions<
T extends TargetType = Element,
ImageType extends DomToImageMethodKeys = DomToImageMethodKeys,
> = Options<T, ImageType> & HtmlToImageOptions
export type UseDomToImageOptions<T extends TargetType = Element> = Options<T> &
HtmlToImageOptions
export type DomToImageMethodKeys = keyof typeof domToImageMethods
type DomToImageReturnType<ImageType extends DomToImageMethodKeys = 'jpeg'> =
Awaited<ReturnType<(typeof domToImageMethods)[ImageType]>>
const domToImageMethods = {
svg: toSvg,
png: toPng,
jpeg: toJpeg,
blob: toBlob,
pixelData: toPixelData,
canvas: toCanvas,
} as const
/**
*
@ -106,24 +98,18 @@ export type UseDomToImageOptions<
* imageType 使 options.imageType
* 使 jpeg
*
* created useDomToImage
* options.imageType
*
* @example
* const refDom = ref<HTMLElement>()
* const { create } = useDomToImage(refDom, {
* const { create, stop } = useDomToImage(refDom, {
* beforeCreate: (element) => { ... },
* created: (element, result) => { ... },
* createdError: (error) => { ... },
* finally: () => { ... },
* })
*/
export const useDomToImage = <
T extends HTMLElement,
ImageType extends DomToImageMethodKeys = DomToImageMethodKeys,
>(
export const useDomToImage = <T extends HTMLElement>(
target: BasicTarget<T>,
options?: UseDomToImageOptions<T, ImageType>,
options?: UseDomToImageOptions,
) => {
const {
beforeCreate,
@ -144,14 +130,10 @@ export const useDomToImage = <
if (!element) {
createdError?.()
return reject(`[useDomToImage]: target element is undefined.`)
return reject('useDomToImage: element is undefined.')
}
const imageTypeKey = (imageType ??
_imageType ??
'jpeg') as DomToImageMethodKeys
domToImageMethods[imageTypeKey]?.(element, options)
domToImageMethods[imageType ?? _imageType ?? 'jpeg']?.(element, options)
// eslint-disable-next-line @typescript-eslint/no-explicit-any
.then((res: any) => {
created?.(res, element)

View File

@ -1,6 +1,8 @@
import { unrefElement, effectDispose, isValueType, setStyle } from '@/utils'
import { useWindowSize } from '@vueuse/core'
import type { BasicTarget } from '@/types'
import type { CSSProperties } from 'vue'
export interface UseElementFullscreenOptions {
/**
@ -56,7 +58,7 @@ export interface UseElementFullscreenOptions {
* @description
* transition
*
* @default 'transform 0.3s var(--r-bezier)'
* @default 'width 0.3s var(--r-bezier), height 0.3s var(--r-bezier)'
*/
transition?: string
}
@ -64,6 +66,7 @@ export interface UseElementFullscreenOptions {
let currentZIndex = 999
let isAppend = false
const ID_TAG = 'ELEMENT-FULLSCREEN-RAY'
const { width, height } = useWindowSize() // 获取实际高度避免 100vh 会导致手机端浏览器获取不准确问题
const styleElement = document.createElement('style')
/**
@ -82,7 +85,7 @@ const styleElement = document.createElement('style')
* <div ref="refDom" />
* </template>
* <script lang="ts" setup>
* const refDom = useTemplateRef<HTMLElement>('refDom')
* const refDom = ref<HTMLElement>()
* const { enter, exit, toggleFullscreen } = useElementFullscreen(refDom, { UseElementFullscreenOptions })
*
* enter() // 进入全屏
@ -101,7 +104,7 @@ export const useElementFullscreen = (
exit: _exit,
backgroundColor,
zIndex,
transition = 'transform 0.3s var(--r-bezier)',
transition = 'all 0.3s var(--r-bezier)',
} = options ?? {}
let isSetup = false
const catchBoundingClientRect: {
@ -111,8 +114,6 @@ export const useElementFullscreen = (
x: null,
y: null,
}
// 使用 ref 来追踪状态
const isFullscreen = ref(false)
const updateStyle = () => {
const element = unrefElement(target) as HTMLElement | null
@ -139,8 +140,8 @@ export const useElementFullscreen = (
: zIndex,
'--element-fullscreen-transition': transition,
'--element-fullscreen-background-color': backgroundColor,
'--element-fullscreen-width': 'var(--html-width)',
'--element-fullscreen-height': 'var(--html-height)',
'--element-fullscreen-width': `${width.value}px`,
'--element-fullscreen-height': `${height.value}px`,
'--element-fullscreen-transform-x': `${catchBoundingClientRect.x}px`,
'--element-fullscreen-transform-y': `${catchBoundingClientRect.y}px`,
})
@ -155,7 +156,7 @@ export const useElementFullscreen = (
z-index: var(--element-fullscreen-z-index) !important;
background-color: var(--element-fullscreen-background-color);
}
`.trim()
`
styleElement.innerHTML = cssContent
@ -187,7 +188,6 @@ export const useElementFullscreen = (
}
element.style.transition = transition
isFullscreen.value = true
_enter?.()
}
@ -202,8 +202,6 @@ export const useElementFullscreen = (
element.removeAttribute(ID_TAG)
}
isFullscreen.value = false
_exit?.()
}
@ -219,6 +217,8 @@ export const useElementFullscreen = (
}
}
const stopWatch = watch(() => [width.value, height.value], updateStyle)
effectDispose(() => {
const element = unrefElement(target) as HTMLElement | null
@ -227,15 +227,19 @@ export const useElementFullscreen = (
}
// 回滚 z-index 值,避免无限增加
currentZIndex = Math.max(999, currentZIndex - 1) // 防止 zIndex 小于初始值
isFullscreen.value = false
currentZIndex--
stopWatch()
})
return {
enter,
exit,
toggleFullscreen,
isFullscreen: readonly(isFullscreen), // 暴露只读状态
currentWindowSize: {
width,
height,
},
}
}

View File

@ -14,28 +14,10 @@ type OmitKeys =
| 'onUpdatePageSize'
| 'onUpdate:page'
| 'onUpdate:page-size'
| 'onUpdate:pageSize'
export interface UsePaginationOptions extends Omit<PaginationProps, OmitKeys> {
/**
*
* @param page
*
* @description
*
*/
pageChange?: (page: number) => void
/**
*
* @param pageSize
*
* @description
*
*/
pageSizeChange?: (pageSize: number) => void
}
export interface UsePaginationOptions extends Omit<PaginationProps, OmitKeys> {}
const DEFAULT_OPTIONS: UsePaginationOptions = {
const defaultOptions: UsePaginationOptions = {
page: 1,
pageSize: 10,
showSizePicker: true,
@ -54,49 +36,33 @@ export const usePagination = <T extends AnyFC>(
callback?: T,
options?: UsePaginationOptions,
) => {
// 配置合并
const mergedOptions = computed(() => ({
...DEFAULT_OPTIONS,
...omit(options, [
'on-update:page',
'on-update:page-size',
'onUpdatePage',
'onUpdatePageSize',
'onUpdate:page',
'onUpdate:page-size',
'onUpdate:pageSize',
]),
...paginationMethods,
}))
// 回调函数
const callbackRef = shallowRef(callback)
// 分页方法
const paginationMethods = {
const callbackRef = ref(callback)
const omitOptions = omit(options, [
'on-update:page',
'on-update:page-size',
'onUpdatePage',
'onUpdatePageSize',
'onUpdate:page',
'onUpdate:page-size',
])
const methodsOptions = {
onUpdatePage: (page: number) => {
const { pageChange } = mergedOptions.value
paginationRef.value.page = page
callbackRef.value?.()
pageChange?.(page)
},
onUpdatePageSize: (pageSize: number) => {
const { pageSizeChange } = mergedOptions.value
paginationRef.value.pageSize = pageSize
paginationRef.value.page = DEFAULT_OPTIONS.page
paginationRef.value.page = 1
callbackRef.value?.()
pageSizeChange?.(pageSize)
},
}
// 分页配置
const paginationRef = ref<PaginationProps>(mergedOptions.value)
const paginationRef = ref<PaginationProps>(
Object.assign({}, defaultOptions, omitOptions, methodsOptions),
)
// 更新分页页数
const updatePage = paginationRef.value.onUpdatePage as (page: number) => void
// 更新分页每页条数
const updatePageSize = paginationRef.value.onUpdatePageSize as (
pageSize: number,
) => void
@ -171,7 +137,7 @@ export const usePagination = <T extends AnyFC>(
* @description
*
*/
const getCallback = callbackRef.value as T
const getCallback = callback
/**
*
@ -183,8 +149,9 @@ export const usePagination = <T extends AnyFC>(
* @example
* setCallback(() => {})
*/
const setCallback = (callback: AnyFC) => {
callbackRef.value = callback
const setCallback = (callback: T) => {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
callbackRef.value = callback as any
}
/**
@ -198,9 +165,8 @@ export const usePagination = <T extends AnyFC>(
const resetPagination = () => {
const { pageSizes } = paginationRef.value
paginationRef.value.page = DEFAULT_OPTIONS.page
paginationRef.value.pageSize =
(pageSizes?.[0] as number) || DEFAULT_OPTIONS.pageSize
paginationRef.value.page = 1
paginationRef.value.pageSize = (pageSizes?.[0] as number) || 10
}
effectDispose(() => {

View File

@ -16,16 +16,16 @@ export type UsePrintTarget<T = unknown> =
/**
*
* @param target useTemplateRef dom
* @param target ref dom
* @param options print-js options
*
* @see https://printjs.crabbly.com/
*
* @description
* print-js usePrint useTemplateRef Dom
* print-js usePrint ref Dom
*
* @example
* const refDom = useTemplateRef<HTMLElement>('refDom')
* const refDom = ref<HTMLElement>()
*
* const { print } = usePrint(refDom, {})
*

View File

@ -6,7 +6,7 @@ import { RIcon } from '@/components'
import { isValueType, renderNode } from '@/utils'
import { useSettingGetters } from '@/store'
export const SIDER_BAR_LOGO = shallowRef<HTMLElement>()
export const SIDER_BAR_LOGO = ref<HTMLElement>()
export default defineComponent({
name: 'SiderBarLogo',
@ -93,9 +93,9 @@ export default defineComponent({
<NTooltip placement="right">
{{
trigger: () => (
<NGradientText type="primary" size={18}>
<h1 class="n-menu-item-content">
{sideBarLogo.title?.[0] || null}
</NGradientText>
</h1>
),
default: () => sideBarLogo.title,
}}

View File

@ -7,7 +7,6 @@ import { LAYOUT_SIDER_REF } from '@/app-config'
import { useDevice } from '@/hooks'
import { getVariableToRefs, setVariable } from '@/global-variable'
import { useMenuGetters, useMenuActions, useSettingGetters } from '@/store'
import { positionSelectedMenuItem } from '@/utils'
import type { MenuInst } from 'naive-ui'
import type { NaiveMenuOptions } from '@/types'
@ -16,28 +15,27 @@ import type { AppMenuOption } from '@/types'
export default defineComponent({
name: 'AppMenu',
setup() {
// 这里使用 shallowRef 而不是 useTemplateRef 是因为在这里有一个特殊情况,会导致一个 readonly 的警告
const menuRef = shallowRef<MenuInst | null>()
const menuRef = ref<MenuInst | null>(null)
const { changeMenuModelValue, collapsedMenu, updateMenuState } =
useMenuActions()
const { getMenuConfig } = useSettingGetters()
const { getMenuOptions, getCollapsed, getMenuKey } = useMenuGetters()
const modelMenuKey = computed({
get: () => {
/**
*
* @description
* eslint computed 使
* computed get
*
*
*/
// eslint-disable-next-line vue/no-async-in-computed-properties
setTimeout(() => {
nextTick().then(() => {
/**
*
* @description
* eslint computed 使
* computed get
*
*
*/
showMenuOption()
positionSelectedMenuItem()
}, 300)
})
return getMenuKey.value
},
@ -82,8 +80,7 @@ export default defineComponent({
collapseMode={getMenuConfig.value.collapsedMode}
collapsedWidth={getMenuConfig.value.collapsedWidth}
onUpdateCollapsed={collapsedMenu.bind(this)}
width={getMenuConfig.value.menuWidth}
nativeScrollbar={getMenuConfig.value.nativeScrollbar}
nativeScrollbar={false}
ref={LAYOUT_SIDER_REF}
collapsed={getCollapsed.value}
onExpand={() => {
@ -92,7 +89,6 @@ export default defineComponent({
onCollapse={() => {
updateMenuState('collapsed', true)
}}
inverted={getMenuConfig.value.inverted}
>
{getMenuConfig.value.menuSiderBarLogo ? (
<SiderBarLogo collapsed={getCollapsed.value} />
@ -112,7 +108,6 @@ export default defineComponent({
}}
accordion={getMenuConfig.value.accordion}
iconSize={getMenuConfig.value.iconSize}
inverted={getMenuConfig.value.inverted}
/>
</NLayoutSider>
)

View File

@ -25,41 +25,43 @@ $menuTagWrapperWidth: 76px;
}
// 激活标签页关闭按钮样式
.menu-tag .menu-tag__btn {
transition:
color 0.3s var(--n-bezier),
background-color 0.3s var(--n-bezier),
opacity 0.3s var(--n-bezier),
border-color 0.3s var(--n-bezier),
width 0.3s var(--n-bezier);
.n-button__icon {
opacity: 0;
width: 0;
height: var(--n-icon-size);
transition: all 0.3s var(--r-bezier);
margin-left: 0px;
transform: translateX(4px);
}
&:hover {
.n-button__icon {
opacity: 1;
width: var(--n-icon-size);
transition: all 0.3s var(--r-bezier);
.menu-tag {
.menu-tag__btn {
.menu-tag__btn-icon--hidden {
display: none !important;
}
}
}
.ray-template--light {
.menu-tag__btn-icon:hover {
filter: brightness(1.2);
}
}
.menu-tag__btn-icon {
display: inline;
margin-left: 0;
width: 0;
height: 0;
transition: all 0.3s var(--r-bezier);
overflow: hidden;
opacity: 0;
.ray-template--dark {
.menu-tag__btn-icon:hover {
filter: brightness(0.8);
& .ray-icon {
width: 11px !important;
height: 11px !important;
}
}
&:hover {
.menu-tag__btn-icon {
width: 14px;
height: 14px;
margin-left: 5px;
font-size: 12px;
background-color: rgba(0, 0, 0, 0.12);
border-radius: 50%;
padding: 1px;
transition: all 0.3s var(--r-bezier);
opacity: 1;
display: flex;
justify-content: center;
align-items: center;
}
}
}
}

View File

@ -16,7 +16,7 @@
* Root Path MenuTag Root Tag
*
* outsideClick contextmenu
* 使 throttle v5.0.2
* 使 throttle v5.0.1
*/
import './index.scss'
@ -34,6 +34,7 @@ import { RIcon, RMoreDropdown } from '@/components'
import { useMenuGetters, useMenuActions } from '@/store'
import { hasClass, uuid, queryElements } from '@/utils'
import { useMaximize, useSpinning, useAppRoot, useSiderBar } from '@/hooks'
import { throttle } from 'lodash-es'
import { getVariableToRefs } from '@/global-variable'
import { useTemplateRef } from 'vue'
@ -64,6 +65,7 @@ export default defineComponent({
'closeRight',
'closeLeft',
'closeOther',
'closeCurrentPage',
]
// 当前右键标签页索引位置
let currentContextmenuIndex = Infinity
@ -177,8 +179,6 @@ export default defineComponent({
const naiveScrollbarContainerClass = 'n-scrollbar-container'
// 缓存上一次的菜单 key
let catchMenuKey = getMenuKey.value
// 当前鼠标是否划入 menu tag
const isMouseInMenuTag = ref(false)
// 关闭当前菜单标签,如果只有一个标签,则不允许关闭
const closeCurrentMenuTag = (idx: number) => {
@ -260,14 +260,21 @@ export default defineComponent({
actionState.actionDropdownShow = false
nextTick(() => {
actionState.actionDropdownShow = true
actionState.x = e.clientX
actionState.y = e.clientY
actionState.actionDropdownShow = true
})
}
// 动态设置某些项禁用
const setDisabledAccordionToIndex = () => {
const { closeable } =
getMenuTagOptions.value[currentContextmenuIndex] ??
({} as MenuTagOptions)
// 是否需要禁用关闭当前标签页
setMoreOptionsDisabled('closeCurrentPage', !closeable ?? false)
// 是否需要禁用关闭右侧标签页
checkCloseRight(currentContextmenuIndex)
? setMoreOptionsDisabled('closeRight', false)
@ -299,8 +306,6 @@ export default defineComponent({
) {
option.closeable = true
}
isMouseInMenuTag.value = true
}
// 移出 MenuTag 时,判断是否为当前已激活 key
@ -308,8 +313,6 @@ export default defineComponent({
if (option.fullPath !== getMenuKey.value) {
option.closeable = false
}
isMouseInMenuTag.value = false
}
// 每当新的页面打开后,将滚动条横向滚到至底部,使用 nextTick 避免元素未渲染挂载至页面
@ -356,7 +359,7 @@ export default defineComponent({
(ndata, odata) => {
// 当 menuTagOptions 长度为 1时禁用所有 canDisabledOptions 匹配的项
moreOptions.value.forEach((curr) => {
if (canDisabledOptions.includes(curr.key as string)) {
if (canDisabledOptions.includes(curr.key)) {
ndata.length > 1 ? (curr.disabled = false) : (curr.disabled = true)
}
})
@ -381,7 +384,8 @@ export default defineComponent({
)
watchEffect(() => {
if (actionState.actionDropdownShow) {
setDisabledAccordionToIndex()
// 使用节流函数,避免右键菜单闪烁问题
throttle(setDisabledAccordionToIndex, 300)?.()
}
if (catchMenuKey !== getMenuKey.value) {
@ -414,7 +418,6 @@ export default defineComponent({
reload,
globalMainLayoutLoad,
maximizeBtnClick,
isMouseInMenuTag,
}
},
render() {
@ -424,7 +427,6 @@ export default defineComponent({
getMenuTagOptions,
MENU_TAG_DATA,
globalMainLayoutLoad,
isMouseInMenuTag,
} = this
const {
maximizeBtnClick,
@ -452,11 +454,6 @@ export default defineComponent({
trigger="manual"
placement="bottom-start"
onSelect={actionDropdownSelect.bind(this)}
onClickoutside={() => {
if (!isMouseInMenuTag) {
this.actionState.actionDropdownShow = false
}
}}
/>
<NFlex
class="menu-tag-space"
@ -513,11 +510,12 @@ export default defineComponent({
onContextmenu: menuTagContextMenu.bind(this, idx),
onMouseenter: menuTagMouseenter.bind(this, curr),
onMouseleave: menuTagMouseleave.bind(this, curr),
onBlur: () => {
this.actionState.actionDropdownShow = false
},
[MENU_TAG_DATA]: curr.fullPath,
}}
size="small"
focusable={false}
iconPlacement="right"
>
{{
default: () => (
@ -534,18 +532,16 @@ export default defineComponent({
},
}}
</span>
<NIcon
class="menu-tag__btn-icon"
{...{
onMousedown: closeCurrentMenuTag.bind(this, idx),
}}
>
<RIcon name="close" size="14" />
</NIcon>
</>
),
icon: () => (
<RIcon
customClassName="menu-tag__btn-icon"
name="close"
size="15"
{...{
onMousedown: closeCurrentMenuTag.bind(this, idx),
}}
/>
),
}}
</NButton>
))}

View File

@ -80,23 +80,15 @@ export default defineComponent({
plain: true,
},
]
// 初始化索引
/** 初始化索引 */
let searchElementIndex = 0
// 缓存索引
/** 缓存索引 */
let preSearchElementIndex = searchElementIndex
const { isTabletOrSmaller } = useDevice({
observer: (val) => {
// 当处于小尺寸状态时,自动关闭搜索框
if (val) {
modelShow.value = false
}
},
})
const { isTabletOrSmaller } = useDevice()
const loading = ref(false)
// 激活样式 class name
const ACTIVE_CLASS = 'content-item--active'
const ACTIVE_CLASS = 'content-item--active' // 激活样式 class name
// 初始化一些值
/** 初始化一些值 */
const resetSearchSomeValue = () => {
state.searchOptions = []
state.searchValue = null
@ -104,7 +96,7 @@ export default defineComponent({
preSearchElementIndex = searchElementIndex
}
// 按下 ctrl + k 或者 command + k 激活搜索栏
/** 按下 ctrl + k 或者 command + k 激活搜索栏 */
const registerArouseKeyboard = (e: KeyboardEvent) => {
if (modelShow.value) {
return
@ -119,14 +111,7 @@ export default defineComponent({
}
}
/**
*
* @param value
*
* @description
*
* getRoutes()
*/
/** 根据输入值模糊检索菜单 */
const fuzzySearchMenuOptions = (value: string) => {
if (value) {
loading.value = true
@ -172,15 +157,14 @@ export default defineComponent({
})
loading.value = false
}, 300)
}, 500)
}
// 搜索结果项点击
const searchItemClick = (option: AppMenuOption) => {
if (option) {
const { meta } = option
// 如果配置站外跳转则不会关闭搜索框
/** 如果配置站外跳转则不会关闭搜索框 */
if (meta.windowOpen) {
window.open(meta.windowOpen)
} else {
@ -192,7 +176,7 @@ export default defineComponent({
}
}
// 自动聚焦检索项
/** 自动聚焦检索项 */
const autoFocusingSearchItem = () => {
const currentOption = state.searchOptions[searchElementIndex] // 获取当前搜索项
const preOption = state.searchOptions[preSearchElementIndex] // 获取上一搜索项
@ -221,20 +205,20 @@ export default defineComponent({
}
}
// 渲染搜索菜单前缀图标,如果没有则用 icon table 代替
/** 渲染搜索菜单前缀图标, 如果没有则用 icon table 代替 */
const RenderPreIcon = (meta: AppRouteMeta) => {
const { icon } = meta
if (typeof icon === 'string' && icon) {
if (typeof icon === 'string') {
return <RIcon name={icon} size="24" />
} else if (typeof icon === 'function') {
return <icon />
return () => icon
} else {
return <RIcon name="search" size="24" />
}
}
// 更新索引
/** 更新索引 */
const updateIndex = (type: 'up' | 'down') => {
if (type === 'up') {
searchElementIndex -= 1
@ -253,7 +237,7 @@ export default defineComponent({
}
}
// 注册按键: 上、下、回车
/** 注册按键: 上、下、回车 */
const registerChangeSearchElementIndex = (e: KeyboardEvent) => {
const keyCode = e.key
@ -316,7 +300,22 @@ export default defineComponent({
</NFlex>
)
useEventListener(window, 'keydown', registerArouseKeyboard)
watchEffect(() => {
// 当处于小尺寸状态时,自动关闭搜索框
if (isTabletOrSmaller.value) {
modelShow.value = false
}
})
useEventListener(
window,
'keydown',
(e: KeyboardEvent) => {
registerArouseKeyboard(e)
registerChangeSearchElementIndex(e)
},
true,
)
return {
...toRefs(state),
@ -328,16 +327,11 @@ export default defineComponent({
isTabletOrSmaller,
SearchItem,
loading,
registerChangeSearchElementIndex,
}
},
render() {
const { isTabletOrSmaller, searchOptions, loading } = this
const {
SearchItem,
fuzzySearchMenuOptions,
registerChangeSearchElementIndex,
} = this
const { SearchItem, fuzzySearchMenuOptions } = this
return isTabletOrSmaller ? (
<div style="display: none;"></div>
@ -347,11 +341,7 @@ export default defineComponent({
transformOrigin="center"
displayDirective="if"
>
<div
class="global-search global-search--dark global-search--light"
tabindex="-1"
onKeydown={registerChangeSearchElementIndex}
>
<div class="global-search global-search--dark global-search--light">
<div class="global-search__wrapper">
<NCard
class="global-search__card"

View File

@ -1,11 +1,23 @@
import type { SettingState } from '@/store/modules/setting/types'
import type { InjectionKey, Reactive } from 'vue'
import type { DebouncedFunc } from 'lodash-es'
interface SettingDrawerInjectKey extends SettingState {
throttleSetupAppMenu: DebouncedFunc<() => Promise<void>>
export const defaultSettingConfig: Partial<SettingState> = {
contentTransition: 'scale',
watermarkSwitch: false,
keepAliveConfig: {
maxKeepAliveLength: 10,
setupKeepAlive: true,
},
menuConfig: {
collapsedWidth: 64,
collapsedMode: 'width',
collapsedIconSize: 16,
collapsedIndent: 24,
accordion: false,
menuSiderBarLogo: true,
iconSize: 16,
},
menuTagSwitch: true,
breadcrumbSwitch: true,
copyrightSwitch: true,
drawerPlacement: 'right',
}
export const SETTING_DRAWER_INJECT_KEY: Reactive<
InjectionKey<Partial<SettingDrawerInjectKey>>
> = Symbol('segmentDrawer')

View File

@ -1,3 +1,8 @@
.n-form.setting-drawer__overrides-form .n-form-item .n-form-item-blank {
justify-content: flex-end;
.setting-drawer__space {
width: 100%;
& .n-descriptions-table-content {
display: flex !important;
justify-content: space-between;
}
}

View File

@ -3,31 +3,50 @@ import './index.scss'
import {
NDrawer,
NDrawerContent,
NDivider,
NFlex,
NSwitch,
NColorPicker,
NDescriptions,
NDescriptionsItem,
NSelect,
NInputNumber,
NFormItem,
NForm,
NButton,
NTabs,
NTabPane,
NText,
NTooltip,
} from 'naive-ui'
import ThemeSegment from './components/ThemeSegment'
import { RIcon } from '@/components'
import SegmentViewsAppearance from './segment-views/Appearance'
import SegmentViewsCommon from './segment-views/Common'
import SegmentViewsWatermark from './segment-views/Watermark'
import SegmentViewsCustomMenu from './segment-views/CustomMenu'
import { useSettingGetters, useSettingActions, useMenuActions } from '@/store'
import { SETTING_DRAWER_INJECT_KEY } from './constant'
import { cloneDeep, forIn, throttle } from 'lodash-es'
import { drawerProps } from 'naive-ui'
import { useModal } from '@/components'
import { getDefaultSettingConfig } from '@/store/modules/setting/constant'
import { APP_THEME, CONTENT_TRANSITION_OPTIONS } from '@/app-config'
import { useSettingGetters, useSettingActions } from '@/store'
import { defaultSettingConfig } from './constant'
import { forIn } from 'lodash-es'
import type { PropType } from 'vue'
import type { Placement } from '@/types'
import type { SettingState } from '@/store/modules/setting/types'
export default defineComponent({
name: 'SettingDrawer',
props: drawerProps,
setup() {
const { create: createModal } = useModal()
props: {
show: {
type: Boolean,
default: false,
},
placement: {
type: String as PropType<Placement>,
default: 'right',
},
width: {
type: Number,
default: 280,
},
},
emits: ['update:show'],
setup(props, { emit }) {
const { changePrimaryColor, updateSettingState } = useSettingActions()
const {
getAppTheme,
@ -40,55 +59,39 @@ export default defineComponent({
getKeepAliveConfig,
getMenuConfig,
getDrawerPlacement,
getColorWeakness,
getWatermarkConfig,
getDynamicDocumentTitle,
} = useSettingGetters()
const { setupAppMenu } = useMenuActions()
const throttleSetupAppMenu = throttle(setupAppMenu, 300)
const modelReactive = reactive({
menuTagSwitch: getMenuTagSwitch.value,
breadcrumbSwitch: getBreadcrumbSwitch.value,
copyrightSwitch: getCopyrightSwitch.value,
contentTransition: getContentTransition.value,
watermarkSwitch: getWatermarkSwitch.value,
keepAliveConfig: getKeepAliveConfig.value,
menuConfig: getMenuConfig.value,
drawerPlacement: getDrawerPlacement.value,
colorWeakness: getColorWeakness.value,
primaryColorOverride: getPrimaryColorOverride.value,
watermarkConfig: getWatermarkConfig.value,
dynamicDocumentTitle: getDynamicDocumentTitle.value,
throttleSetupAppMenu,
const modelShow = computed({
get: () => props.show,
set: (bool) => {
emit('update:show', bool)
},
})
// 为了方便管理多个 computed因为 computed 不能被逆向修改
const modelReactive = computed({
get: () => {
return {
getMenuTagSwitch: getMenuTagSwitch.value,
getBreadcrumbSwitch: getBreadcrumbSwitch.value,
getCopyrightSwitch: getCopyrightSwitch.value,
getContentTransition: getContentTransition.value,
getWatermarkSwitch: getWatermarkSwitch.value,
getKeepAliveConfig: getKeepAliveConfig.value,
getMenuConfig: getMenuConfig.value,
getDrawerPlacement: getDrawerPlacement.value,
}
},
set: (value) => {},
})
const defaultSettingBtnClick = () => {
createModal({
preset: 'dialog',
title: '恢复默认配置',
type: 'warning',
content: '点击【确认初始化】按钮会恢复默认系统配置,是否继续?',
positiveText: '确认初始化',
negativeText: '取消',
onPositiveClick: () => {
forIn(cloneDeep(getDefaultSettingConfig()), (value, key) => {
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
modelReactive[key] = value
console.log(value, key)
updateSettingState(key as keyof SettingState, value)
})
throttleSetupAppMenu()
},
forIn(defaultSettingConfig, (value, key) => {
updateSettingState(key as keyof SettingState, value)
})
}
provide(SETTING_DRAWER_INJECT_KEY, modelReactive)
return {
modelShow,
changePrimaryColor,
getAppTheme,
getPrimaryColorOverride,
@ -98,40 +101,224 @@ export default defineComponent({
}
},
render() {
const { defaultSettingBtnClick, $props } = this
const { trapFocus, autoFocus, nativeScrollbar, ...restProps } = $props
const {
$t,
changePrimaryColor,
updateSettingState,
defaultSettingBtnClick,
} = this
return (
<NDrawer {...restProps} trapFocus={false} autoFocus={false} width={320}>
<NDrawerContent title="个性化配置" closable>
{{
default: () => (
<NTabs type="bar" animated defaultValue="appearance">
<NTabPane name="appearance" tab="外观">
<SegmentViewsAppearance />
</NTabPane>
<NTabPane name="menu" tab="菜单">
<SegmentViewsCustomMenu />
</NTabPane>
<NTabPane name="watermark" tab="水印">
<SegmentViewsWatermark />
</NTabPane>
<NTabPane name="common" tab="通用">
<SegmentViewsCommon />
</NTabPane>
</NTabs>
),
footer: () => (
<NFlex justify="flex-start">
<NButton type="warning" onClick={defaultSettingBtnClick}>
{{
icon: () => <RIcon name="reload" />,
default: () => '初始化配置',
<NDrawer
v-model:show={this.modelShow}
placement={this.placement}
width={this.width}
trapFocus={false}
autoFocus={false}
>
<NDrawerContent title="系统配置">
<NFlex class="setting-drawer__space" vertical>
<NDivider titlePlacement="center">
{$t('headerSettingOptions.ThemeOptions.Title')}
</NDivider>
<ThemeSegment />
<NDivider titlePlacement="center">
{$t('headerSettingOptions.ThemeOptions.PrimaryColorConfig')}
</NDivider>
<NColorPicker
swatches={APP_THEME.appThemeColors}
v-model:value={this.getPrimaryColorOverride.common!.primaryColor}
onUpdateValue={changePrimaryColor.bind(this)}
/>
<NDivider titlePlacement="center">
{$t('headerSettingOptions.ContentTransition')}
</NDivider>
<NSelect
v-model:value={this.modelReactive.getContentTransition}
options={CONTENT_TRANSITION_OPTIONS}
onUpdateValue={(value) => {
updateSettingState('contentTransition', value)
}}
/>
<NDivider titlePlacement="center"></NDivider>
<NSelect
v-model:value={this.modelReactive.getDrawerPlacement}
options={[
{
label: '右边',
value: 'right',
},
{
label: '左边',
value: 'left',
},
]}
onUpdateValue={(value) => {
updateSettingState('drawerPlacement', value)
}}
/>
<NDivider titlePlacement="center"></NDivider>
<NDescriptions labelPlacement="left" column={1}>
<NDescriptionsItem label="菜单页头">
<NSwitch
v-model:value={
this.modelReactive.getMenuConfig.menuSiderBarLogo
}
onUpdateValue={(bool: boolean) =>
updateSettingState('menuConfig', {
menuSiderBarLogo: bool,
})
}
/>
</NDescriptionsItem>
<NDescriptionsItem label="菜单手风琴">
<NSwitch
v-model:value={this.modelReactive.getMenuConfig.accordion}
onUpdateValue={(bool: boolean) =>
updateSettingState('menuConfig', {
accordion: bool,
})
}
/>
</NDescriptionsItem>
<NDescriptionsItem label="页面缓存">
<NSwitch
v-model:value={
this.modelReactive.getKeepAliveConfig.setupKeepAlive
}
onUpdateValue={(bool: boolean) =>
updateSettingState('keepAliveConfig', {
setupKeepAlive: bool,
})
}
/>
</NDescriptionsItem>
<NDescriptionsItem label="多标签">
<NSwitch
v-model:value={this.modelReactive.getMenuTagSwitch}
onUpdateValue={(bool: boolean) =>
updateSettingState('menuTagSwitch', bool)
}
/>
</NDescriptionsItem>
<NDescriptionsItem label="面包屑">
<NSwitch
v-model:value={this.modelReactive.getBreadcrumbSwitch}
onUpdateValue={(bool: boolean) =>
updateSettingState('breadcrumbSwitch', bool)
}
/>
</NDescriptionsItem>
<NDescriptionsItem label="水印">
<NSwitch
v-model:value={this.modelReactive.getWatermarkSwitch}
onUpdateValue={(bool: boolean) =>
updateSettingState('watermarkSwitch', bool)
}
/>
</NDescriptionsItem>
<NDescriptionsItem label="页底信息">
<NSwitch
v-model:value={this.modelReactive.getCopyrightSwitch}
onUpdateValue={(bool: boolean) =>
updateSettingState('copyrightSwitch', bool)
}
/>
</NDescriptionsItem>
</NDescriptions>
<NDivider titlePlacement="center"></NDivider>
<NForm showFeedback={true} showRequireMark={false}>
<NFormItem label="每级菜单缩进">
<NInputNumber
v-model:value={
this.modelReactive.getMenuConfig.collapsedIndent
}
min={0}
precision={0}
onUpdateValue={(value) => {
if (value !== null) {
updateSettingState('menuConfig', {
collapsedIndent: value,
})
}
}}
</NButton>
/>
</NFormItem>
<NFormItem label="菜单图标尺寸">
<NInputNumber
v-model:value={this.modelReactive.getMenuConfig.iconSize}
min={0}
precision={0}
onUpdateValue={(value) => {
if (value !== null) {
updateSettingState('menuConfig', {
iconSize: value,
})
}
}}
/>
</NFormItem>
<NFormItem label="折叠菜单图标尺寸">
<NInputNumber
v-model:value={
this.modelReactive.getMenuConfig.collapsedIconSize
}
min={0}
precision={0}
onUpdateValue={(value) => {
if (value !== null) {
updateSettingState('menuConfig', {
collapsedIconSize: value,
})
}
}}
/>
</NFormItem>
<NFormItem label="折叠菜单宽度" showFeedback={false}>
<NInputNumber
v-model:value={
this.modelReactive.getMenuConfig.collapsedWidth
}
min={0}
precision={0}
onUpdateValue={(value) => {
if (value !== null) {
updateSettingState('menuConfig', {
collapsedWidth: value,
})
}
}}
/>
</NFormItem>
</NForm>
<NDivider titlePlacement="center">
<NFlex wrap={false} align="center" size={[4, 0]}>
<NTooltip placement="top" showArrow={false}>
{{
trigger: () => <RIcon name="question" size="16" />,
default: () => '当设置为【0】时缓存将会失效',
}}
</NTooltip>
<NText></NText>
</NFlex>
),
}}
</NDivider>
<NInputNumber
v-model:value={
this.modelReactive.getKeepAliveConfig.maxKeepAliveLength
}
showButton={true}
min={0}
max={100}
precision={0}
disabled={!this.modelReactive.getKeepAliveConfig.setupKeepAlive}
/>
<NDivider titlePlacement="center"></NDivider>
<NFlex>
<NButton type="primary" block onClick={defaultSettingBtnClick}>
</NButton>
</NFlex>
</NFlex>
</NDrawerContent>
</NDrawer>
)

View File

@ -1,117 +0,0 @@
import {
NFlex,
NColorPicker,
NFormItem,
NDivider,
NSelect,
NSwitch,
NForm,
} from 'naive-ui'
import ThemeSegment from '../components/ThemeSegment'
import { SETTING_DRAWER_INJECT_KEY } from '../constant'
import { APP_THEME, CONTENT_TRANSITION_OPTIONS } from '@/app-config'
import { useSettingActions } from '@/store'
export default defineComponent({
name: 'SegmentViewsAppearance',
setup() {
const model = inject(SETTING_DRAWER_INJECT_KEY, {})
const { changePrimaryColor, updateSettingState, toggleColorWeakness } =
useSettingActions()
return {
toggleColorWeakness,
model,
changePrimaryColor,
updateSettingState,
}
},
render() {
const {
toggleColorWeakness,
model,
changePrimaryColor,
updateSettingState,
} = this
return (
<NFlex vertical style="width: 100%;" size={[0, 0]}>
<NDivider></NDivider>
<ThemeSegment />
<NDivider></NDivider>
<NColorPicker
showPreview
swatches={APP_THEME.appThemeColors}
v-model:value={model.primaryColorOverride!.common!.primaryColor}
onUpdateValue={changePrimaryColor.bind(this)}
/>
<NDivider></NDivider>
<NSelect
v-model:value={model.contentTransition}
options={CONTENT_TRANSITION_OPTIONS}
onUpdateValue={(value) => {
updateSettingState('contentTransition', value)
}}
/>
<NDivider></NDivider>
<NSelect
v-model:value={model.drawerPlacement}
options={[
{
label: '右边',
value: 'right',
},
{
label: '左边',
value: 'left',
},
]}
onUpdateValue={(value) => {
updateSettingState('drawerPlacement', value)
}}
/>
<NDivider></NDivider>
<NForm
labelPlacement="left"
class="setting-drawer__overrides-form"
showFeedback={false}
>
<NFormItem label="面包屑">
<NSwitch
v-model:value={model.breadcrumbSwitch}
onUpdateValue={(bool) =>
updateSettingState('breadcrumbSwitch', bool)
}
/>
</NFormItem>
<NFormItem label="标签页">
<NSwitch
v-model:value={model.menuTagSwitch}
onUpdateValue={(bool) =>
updateSettingState('menuTagSwitch', bool)
}
/>
</NFormItem>
<NFormItem label="页底信息">
<NSwitch
v-model:value={model.copyrightSwitch}
onUpdateValue={(bool) =>
updateSettingState('copyrightSwitch', bool)
}
/>
</NFormItem>
<NFormItem label="色弱模式">
<NSwitch
v-model:value={model.colorWeakness}
onUpdateValue={(bool) => {
updateSettingState('colorWeakness', bool)
toggleColorWeakness(bool)
}}
/>
</NFormItem>
</NForm>
</NFlex>
)
},
})

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