Compare commits

..

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

129 changed files with 5263 additions and 5216 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

316
.eslintrc.cjs Normal file
View File

@ -0,0 +1,316 @@
/* eslint-env node */
module.exports = {
root: true,
env: {
browser: true,
node: true,
},
ignorePatterns: ['node_modules/', 'dist/'],
extends: [
'eslint-config-prettier',
'eslint:recommended',
'plugin:@typescript-eslint/recommended',
'plugin:vue/vue3-recommended',
'plugin:vue/vue3-essential',
'plugin:prettier/recommended',
'prettier',
'./unplugin/.eslintrc-auto-import.json',
],
parser: 'vue-eslint-parser',
parserOptions: {
ecmaVersion: 2020,
sourceType: 'module',
parser: '@typescript-eslint/parser',
ecmaFeatures: {
jsx: true,
tsx: true,
},
},
plugins: ['vue', '@typescript-eslint', 'prettier'],
globals: {
defineProps: 'readonly',
defineEmits: 'readonly',
defineExpose: 'readonly',
withDefaults: 'readonly',
defineOptions: 'readonly',
defineModel: 'readonly',
},
rules: {
'no-undefined': ['error'],
'linebreak-style': ['error', 'unix'],
'@typescript-eslint/no-explicit-any': [
'error',
{
ignoreRestArgs: true,
},
],
'prettier/prettier': 'error',
'no-unused-vars': 'off',
'@typescript-eslint/no-unused-vars': 'off',
'@typescript-eslint/ban-types': 'off',
'@typescript-eslint/explicit-module-boundary-types': 'off',
'@typescript-eslint/no-var-requires': 'off',
'@typescript-eslint/no-non-null-assertion': 'off',
'@typescript-eslint/no-non-null-asserted-optional-chain': 'off',
'@typescript-eslint/consistent-type-imports': [
'error',
{
disallowTypeAnnotations: false,
},
], // 强制导入类型显示标注 `import type xxx from 'xxx'`
'@typescript-eslint/no-empty-interface': [
'error',
{
allowSingleExtends: true,
},
],
'accessor-pairs': 2, // 强制同时存在 `get` 与 `set`
'constructor-super': 0, // 强制子类构造函数中使用 `super` 调用父类的构造函数
'default-case': 2, // `switch` 中强制含有 `default`
eqeqeq: [2, 'allow-null'], // 强制使用严格判断 `===`
'no-alert': 0, // 禁止使用 `alert`、`confirm`
'no-array-constructor': 2, // 禁止使用数组构造器
'no-bitwise': 0, // 禁止使用按位运算符
'no-caller': 1, // 禁止使用 `arguments.caller`、`arguments.callee`
'no-catch-shadow': 2, // 禁止 `catch` 子句参数与外部作用域变量同名
'no-class-assign': 2, // 禁止给类赋值
'no-cond-assign': 2, // 禁止在条件表达式中使用赋值语句
'no-const-assign': 2, // 禁止修改 `const` 声明的变量
'no-constant-condition': 2, // 禁止在条件中使用常量表达式 `if(true)`、`if(1)`
'no-dupe-keys': 2, // 在创建对象字面量时不允许 `key` 重复
'no-dupe-args': 2, // 函数参数不能重复
'no-duplicate-case': 2, // `switch` 中的 `case` 标签不能重复
'no-eval': 1, // 禁止使用 `eval`
'no-ex-assign': 2, // 禁止给 `catch` 语句中的异常参数赋值
'no-extend-native': 2, // 禁止扩展 `native` 对象
'no-extra-bind': 2, // 禁止不必要的函数绑定
'no-extra-boolean-cast': [
'error',
{
enforceForLogicalOperands: true,
},
], // 禁止不必要的 `bool` 转换
'no-extra-parens': 0, // 禁止非必要的括号
semi: [
'error',
'never',
{
beforeStatementContinuationChars: 'always',
},
],
'no-fallthrough': 1, // 禁止 `switch` 穿透
'no-func-assign': 2, // 禁止重复的函数声明
'no-implicit-coercion': [
'error',
{
allow: ['!!', '~'],
},
], // 禁止隐式转换
'no-implied-eval': 2, // 禁止使用隐式 `eval`
'no-invalid-regexp': 2, // 禁止无效的正则表达式
'no-invalid-this': 2, // 禁止无效的 `this`
'no-irregular-whitespace': 2, // 禁止含有不合法空格
'no-iterator': 2, // 禁止使用 `__iterator__ ` 属性
'no-label-var': 2, // `label` 名不能与 `var` 声明的变量名相同
'no-labels': 2, // 禁止标签声明
'no-lone-blocks': 2, // 禁止不必要的嵌套块
'no-multi-spaces': 1, // 禁止使用多余的空格
'no-multiple-empty-lines': [
'error',
{
max: 2,
},
], // 空行最多不能超过 `2` 行
'no-new-func': 2, // 禁止使用 `new Function`
'no-new-object': 2, // 禁止使用 `new Object`
'no-new-require': 2, // 禁止使用 `new require`
'no-sparse-arrays': 2, // 禁止稀疏数组
'no-trailing-spaces': 1, // 一行结束后面不要有空格
'no-unreachable': 2, // 禁止有无法执行的代码
'no-unused-expressions': [
'error',
{
allowShortCircuit: true,
allowTernary: true,
allowTaggedTemplates: true,
enforceForJSX: true,
},
], // 禁止无用的表达式
'no-useless-call': 2, // 禁止不必要的 `call` 和 `apply`
'no-var': 'error', // 禁用 `var`
'no-with': 2, // 禁用 `with`
'use-isnan': 2, // 强制使用 isNaN 判断 NaN
'no-multi-assign': 2, // 禁止连续声明变量
'prefer-arrow-callback': 2, // 强制使用箭头函数作为回调
curly: ['error', 'all'],
'vue/multi-word-component-names': [
'error',
{
ignores: [],
},
],
'vue/no-use-v-if-with-v-for': [
'error',
{
allowUsingIterationVar: false,
},
],
'vue/require-v-for-key': ['error'],
'vue/require-valid-default-prop': ['error'],
'vue/component-definition-name-casing': ['error', 'PascalCase'],
'vue/html-closing-bracket-newline': [
'error',
{
singleline: 'never',
multiline: 'always',
},
],
'vue/v-on-event-hyphenation': ['error', 'never'],
'vue/component-tags-order': [
'error',
{
order: ['template', 'script', 'style'],
},
],
'vue/no-v-html': ['error'],
'vue/no-v-text': ['error'],
'vue/component-api-style': [
'error',
['script-setup', 'composition', 'composition-vue2'],
],
'vue/component-name-in-template-casing': [
'error',
'PascalCase',
{
registeredComponentsOnly: false,
},
],
'vue/no-unused-refs': ['error'],
'vue/prop-name-casing': ['error', 'camelCase'],
'vue/component-options-name-casing': ['error', 'PascalCase'],
'vue/attribute-hyphenation': [
'error',
'never',
{
ignore: [],
},
],
'vue/no-restricted-static-attribute': [
'error',
{
key: 'key',
message: 'Disallow using key as a custom attribute',
},
],
'no-restricted-syntax': [
'error',
{
selector: "CallExpression[callee.property.name='deprecated']",
message: 'Using deprecated API is not allowed.',
},
],
'padding-line-between-statements': [
'error',
{
blankLine: 'always',
prev: '*',
next: 'return',
},
{
blankLine: 'always',
prev: '*',
next: 'function',
},
{
blankLine: 'always',
prev: ['const', 'let', 'var'],
next: '*',
},
{
blankLine: 'any',
prev: ['const', 'let', 'var'],
next: ['const', 'let', 'var'],
},
{
blankLine: 'always',
prev: 'directive',
next: '*',
},
{
blankLine: 'any',
prev: 'directive',
next: 'directive',
},
{
blankLine: 'always',
prev: ['case', 'default'],
next: '*',
},
{
blankLine: 'always',
prev: ['break'],
next: '*',
},
{
blankLine: 'always',
prev: ['import'],
next: '*',
},
{
blankLine: 'any',
prev: 'import',
next: 'import',
},
{
blankLine: 'always',
prev: '*',
next: 'export',
},
{
blankLine: 'any',
prev: 'export',
next: 'export',
},
{
blankLine: 'always',
prev: ['function'],
next: '*',
},
{
blankLine: 'always',
prev: ['class'],
next: '*',
},
{
blankLine: 'always',
prev: '*',
next: 'for',
},
{
blankLine: 'any',
prev: 'for',
next: 'for',
},
{
blankLine: 'always',
prev: '*',
next: ['while', 'do', 'switch'],
},
],
'@typescript-eslint/no-unused-expressions': [
'error',
{
allowShortCircuit: true,
allowTernary: true,
allowTaggedTemplates: true,
},
],
'@typescript-eslint/no-empty-object-type': [
'error',
{
allowInterfaces: 'with-single-extends',
allowObjectTypes: 'always',
},
],
},
}

View File

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

View File

@ -8,7 +8,7 @@ jobs:
strategy: strategy:
fail-fast: false fail-fast: false
matrix: matrix:
node-version: [22.x] node-version: [20.x]
os: [ubuntu-latest, windows-latest, macos-latest] os: [ubuntu-latest, windows-latest, macos-latest]
experimental: [true] experimental: [true]

2
.nvmrc
View File

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

View File

@ -1,105 +1,4 @@
## 5.1.0 # CHANGE LOG
## Feats
- 主流依赖更新
- `RDraggableCard` 组件 `defaultPosition` 配置项新增 `center`, `top-center`, `bottom-center` 配置项,并且该配置项支持动态更新了
- `RDraggableCard` 组件容器 `id``draggable-card-container` 变更为 `r-draggable-card-container`
- `views/demo` 包命名调整
## Fixes
- 修复 `RDraggableCard` 组件设置 `dad``false` 时,初始化位置错误的问题
## 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 ## 5.0.5
@ -219,7 +118,7 @@
- 新增 `clearSigningCallback` 方法 - 新增 `clearSigningCallback` 方法
- `vite.custom.config` 新增 `cdn` 配置项,是否启用 `cdn` 构建项目 - `vite.custom.config` 新增 `cdn` 配置项,是否启用 `cdn` 构建项目
- 配置 `cdn``false`,因为国内厂商更新资源速度有点慢,导致预览失败 - 配置 `cdn``false`,因为国内厂商更新资源速度有点慢,导致预览失败
- `Layout` 层注入 `--window-width`, `--window-height`, `css var` 属性 - `Layout` 层注入 `--window-width`, `--window-height` `css var` 属性
- 稳定 `Layout` 层的 `css var` 属性 - 稳定 `Layout` 层的 `css var` 属性
## Fixes ## Fixes

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

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 %>; --preloading-title-color: <%= preloadingConfig.titleColor %>;
--ray-theme-primary-fade-color: <%= appPrimaryColor.primaryFadeColor %>; --ray-theme-primary-fade-color: <%= appPrimaryColor.primaryFadeColor %>;
--ray-theme-primary-color: <%= appPrimaryColor.primaryColor %>; --ray-theme-primary-color: <%= appPrimaryColor.primaryColor %>;
--global-loading-bg-color: #ffffff;
}
@media (prefers-color-scheme: dark) {
#pre-loading-animation {
background-color: var(--global-loading-bg-color);
}
}
@media (prefers-color-scheme: light) {
#pre-loading-animation {
background-color: var(--global-loading-bg-color);
}
}
html.dark #pre-loading-animation {
background-color: var(--global-loading-bg-color);
}
html.light #pre-loading-animation {
background-color: var(--global-loading-bg-color);
} }
#pre-loading-animation { #pre-loading-animation {
@ -44,9 +23,13 @@
right: 0; right: 0;
top: 0; top: 0;
bottom: 0; bottom: 0;
background-color: #ffffff;
color: var(--preloading-title-color); color: var(--preloading-title-color);
text-align: center; text-align: center;
background-color: var(--global-loading-bg-color); }
.ray-template--dark #pre-loading-animation {
background-color: #2a3146;
} }
#pre-loading-animation .pre-loading-animation__wrapper { #pre-loading-animation .pre-loading-animation__wrapper {
@ -112,18 +95,6 @@
} }
} }
</style> </style>
<script>
;(function () {
const html = document.documentElement
const store = window.localStorage.getItem('piniaSettingStore')
const { _appTheme = false } = store ? JSON.parse(store) : {}
const loadingBgColor = _appTheme ? '#1c1e23' : '#ffffff'
html.classList.add(_appTheme ? 'dark' : 'light')
html.style.setProperty('--global-loading-bg-color', loadingBgColor)
html.style.setProperty('background-color', loadingBgColor)
})()
</script>
<body> <body>
<div id="app"></div> <div id="app"></div>
<div id="pre-loading-animation"> <div id="pre-loading-animation">

View File

@ -1,10 +1,10 @@
{ {
"name": "ray-template", "name": "ray-template",
"private": false, "private": false,
"version": "5.1.0", "version": "5.0.5",
"type": "module", "type": "module",
"engines": { "engines": {
"node": "^18.0.0 || ^20.0.0 || >=22.0.0", "node": "^18.0.0 || >=20.0.0",
"pnpm": ">=9.0.0" "pnpm": ">=9.0.0"
}, },
"scripts": { "scripts": {
@ -16,7 +16,7 @@
"prepare": "husky install", "prepare": "husky install",
"test": "vitest", "test": "vitest",
"test:ui": "vitest --ui", "test:ui": "vitest --ui",
"lint": "vue-tsc --noEmit && eslint --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": { "husky": {
"hooks": { "hooks": {
@ -29,85 +29,83 @@
"prettier --write" "prettier --write"
], ],
"*.{ts,tsx,vue}": [ "*.{ts,tsx,vue}": [
"eslint --fix" "eslint src"
] ]
}, },
"dependencies": { "dependencies": {
"@logicflow/core": "2.0.10", "@logicflow/core": "2.0.6",
"@logicflow/extension": "2.0.14", "@logicflow/extension": "2.0.10",
"@vueuse/core": "^12.4.0", "@vueuse/core": "^11.1.0",
"axios": "^1.7.9", "axios": "^1.7.5",
"clipboard": "^2.0.11", "clipboard": "^2.0.11",
"crypto-js": "4.2.0",
"currency.js": "^2.0.4", "currency.js": "^2.0.4",
"dayjs": "^1.11.13", "dayjs": "^1.11.10",
"echarts": "^5.6.0", "echarts": "^5.5.0",
"html-to-image": "1.11.11", "html-to-image": "1.11.11",
"interactjs": "1.10.27", "interactjs": "1.10.26",
"jsbarcode": "3.11.6", "jsbarcode": "3.11.6",
"lodash-es": "^4.17.21", "lodash-es": "^4.17.21",
"mockjs": "1.1.0", "mockjs": "1.1.0",
"naive-ui": "^2.41.0", "naive-ui": "^2.40.1",
"pinia": "^2.3.0", "pinia": "^2.2.4",
"pinia-plugin-persistedstate": "^4.2.0", "pinia-plugin-persistedstate": "^4.1.1",
"print-js": "^1.6.0", "print-js": "^1.6.0",
"vue": "^3.5.13", "vue": "^3.5.12",
"vue-demi": "0.14.10", "vue-demi": "0.14.6",
"vue-hooks-plus": "2.2.3", "vue-hooks-plus": "2.2.1",
"vue-i18n": "^9.13.1", "vue-i18n": "^9.13.1",
"vue-router": "^4.4.0", "vue-router": "^4.3.2",
"vue3-next-qrcode": "2.0.10" "vue3-next-qrcode": "2.0.10"
}, },
"devDependencies": { "devDependencies": {
"@amap/amap-jsapi-types": "0.0.15", "@commitlint/cli": "^17.8.1",
"@ant-design/icons-vue": "7.0.1", "@commitlint/config-conventional": "^17.8.1",
"@commitlint/cli": "19.3.0", "@interactjs/types": "1.10.21",
"@commitlint/config-conventional": "19.2.2", "@intlify/unplugin-vue-i18n": "^4.0.0",
"@interactjs/types": "1.10.27", "@types/crypto-js": "^4.2.2",
"@intlify/unplugin-vue-i18n": "4.0.0",
"@types/crypto-js": "4.2.2",
"@types/jsbarcode": "3.11.4", "@types/jsbarcode": "3.11.4",
"@types/lodash-es": "4.17.12", "@types/lodash-es": "^4.17.12",
"@types/mockjs": "1.0.10", "@types/mockjs": "1.0.7",
"@types/three": "0.171.0", "@typescript-eslint/eslint-plugin": "^8.13.0",
"@typescript-eslint/eslint-plugin": "8.20.0", "@typescript-eslint/parser": "^8.13.0",
"@typescript-eslint/parser": "8.20.0", "@vitejs/plugin-vue": "^5.1.0",
"@vitejs/plugin-vue": "5.2.1", "@vitejs/plugin-vue-jsx": "^4.0.1",
"@vitejs/plugin-vue-jsx": "4.1.1", "@vitest/ui": "1.4.0",
"@vitest/ui": "2.1.8", "@vue/eslint-config-prettier": "^9.0.0",
"@vue/eslint-config-prettier": "10.1.0", "@vue/eslint-config-typescript": "^12.0.0",
"@vue/eslint-config-typescript": "14.2.0", "@vue/test-utils": "2.4.3",
"@vue/test-utils": "2.4.6", "autoprefixer": "^10.4.16",
"autoprefixer": "10.4.20", "depcheck": "^1.4.7",
"depcheck": "1.4.7", "eslint": "^8.57.0",
"eslint": "9.18.0", "eslint-config-prettier": "^9.1.0",
"eslint-config-prettier": "10.0.1", "eslint-config-standard-with-typescript": "^43.0.0",
"eslint-plugin-prettier": "5.2.2", "eslint-plugin-prettier": "^5.1.3",
"eslint-plugin-vue": "9.32.0", "eslint-plugin-promise": "^6.1.1",
"globals": "15.14.0", "eslint-plugin-vue": "^9.25.0",
"happy-dom": "16.6.0", "happy-dom": "14.3.1",
"husky": "8.0.3", "husky": "8.0.3",
"lint-staged": "15.3.0", "lint-staged": "^15.2.0",
"postcss": "8.5.1", "postcss": "^8.4.38",
"postcss-px-to-viewport-8-with-include": "1.2.2", "postcss-px-to-viewport-8-with-include": "1.2.2",
"prettier": "3.4.2", "prettier": "^3.2.5",
"rollup-plugin-gzip": "4.0.1", "rollup-plugin-gzip": "4.0.1",
"sass": "1.83.4", "sass": "1.71.1",
"svg-sprite-loader": "6.0.11", "svg-sprite-loader": "^6.0.11",
"typescript": "5.6.3", "typescript": "^5.6.3",
"unplugin-auto-import": "19.0.0", "unplugin-auto-import": "^0.18.2",
"unplugin-vue-components": "0.28.0", "unplugin-vue-components": "^0.27.4",
"vite": "6.1.0", "vite": "^5.4.3",
"vite-bundle-analyzer": "0.16.0", "vite-bundle-analyzer": "0.9.4",
"vite-plugin-cdn2": "1.1.0", "vite-plugin-cdn2": "1.1.0",
"vite-plugin-ejs": "1.7.0", "vite-plugin-ejs": "^1.7.0",
"vite-plugin-eslint": "1.8.1", "vite-plugin-eslint": "1.8.1",
"vite-plugin-inspect": "0.8.4", "vite-plugin-inspect": "^0.8.3",
"vite-plugin-mock-dev-server": "1.8.3", "vite-plugin-mock-dev-server": "1.4.7",
"vite-plugin-svg-icons": "2.0.1", "vite-plugin-svg-icons": "^2.0.1",
"vite-svg-loader": "5.1.0", "vite-svg-loader": "^4.0.0",
"vitest": "2.1.8", "vite-tsconfig-paths": "4.3.2",
"vue-tsc": "2.2.0" "vitest": "1.5.2",
"vue-tsc": "^2.1.10"
}, },
"description": "<!-- ALL-CONTRIBUTORS-BADGE:START - Do not remove or modify this section -->", "description": "<!-- ALL-CONTRIBUTORS-BADGE:START - Do not remove or modify this section -->",
"main": "index.ts", "main": "index.ts",

6569
pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff

View File

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

View File

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

View File

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

View File

@ -1,22 +1,21 @@
import '../../index.scss' 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 AppAvatar from '@/app-components/app/AppAvatar'
import { RForm } from '@/components'
import dayjs from 'dayjs' import dayjs from 'dayjs'
import { useSigningActions, useSettingActions } from '@/store' import { useSigningActions, useSettingActions } from '@/store'
import { rules, useCondition } from '@/app-components/app/AppLockScreen/shared' import { rules, useCondition } from '@/app-components/app/AppLockScreen/shared'
import useAppLockScreen from '@/app-components/app/AppLockScreen/appLockVar' import useAppLockScreen from '@/app-components/app/AppLockScreen/appLockVar'
import { useDevice } from '@/hooks' import { useDevice } from '@/hooks'
import { useForm } from '@/components'
import { APP_CATCH_KEY } from '@/app-config' import type { FormInst, InputInst } from 'naive-ui'
import { removeStorage, decrypt, getStorage } from '@/utils'
export default defineComponent({ export default defineComponent({
name: 'UnlockScreen', name: 'UnlockScreen',
setup() { setup() {
const [register, { validate }] = useForm() const formRef = ref<FormInst | null>(null)
const inputInstRef = ref<InputInst | null>(null)
const { logout } = useSigningActions() const { logout } = useSigningActions()
const { updateSettingState } = useSettingActions() const { updateSettingState } = useSettingActions()
@ -25,13 +24,13 @@ export default defineComponent({
const HH_MM_FORMAT = 'HH:mm' const HH_MM_FORMAT = 'HH:mm'
const AM_PM_FORMAT = 'A' const AM_PM_FORMAT = 'A'
const YY_MM_DD_FORMAT = 'YYYY-MM-DD' const YY_MM_DD_FORMAT = 'YY年MM月DD日'
const DDD_FORMAT = 'ddd' const DDD_FORMAT = 'ddd'
const state = reactive({ const state = reactive({
lockCondition: useCondition(), lockCondition: useCondition(),
HH_MM: dayjs().format(HH_MM_FORMAT), HH_MM: dayjs().format(HH_MM_FORMAT),
AM_PM: dayjs().format(AM_PM_FORMAT), AM_PM: dayjs().locale('en').format(AM_PM_FORMAT),
YY_MM_DD: dayjs().format(YY_MM_DD_FORMAT), YY_MM_DD: dayjs().format(YY_MM_DD_FORMAT),
DDD: dayjs().format(DDD_FORMAT), DDD: dayjs().format(DDD_FORMAT),
}) })
@ -44,55 +43,30 @@ export default defineComponent({
state.DDD = dayjs().format(DDD_FORMAT) state.DDD = dayjs().format(DDD_FORMAT)
}, 86_400_000) }, 86_400_000)
const toSigningFn = () => { /** 退出登陆并且回到登陆页 */
removeStorage(APP_CATCH_KEY.appLockScreenPasswordKey, 'localStorage')
updateSettingState('lockScreenSwitch', false)
setTimeout(() => {
logout()
}, 100)
}
const backToSigning = () => { const backToSigning = () => {
window.$dialog.warning({ window.$dialog.warning({
title: '警告', title: '警告',
content: '是否返回到登陆页并且重新登录', content: '是否返回到登陆页?',
positiveText: '确定', positiveText: '确定',
negativeText: '重新登录', negativeText: '取消',
onPositiveClick: toSigningFn, onPositiveClick: () => {
logout()
setTimeout(() => {
updateSettingState('lockScreenSwitch', false)
})
},
}) })
} }
/** 解锁 */
const unlockScreen = () => { const unlockScreen = () => {
const catchPassword = getStorage<string>( formRef.value?.validate((error) => {
APP_CATCH_KEY.appLockScreenPasswordKey, if (!error) {
'localStorage',
)
if (!catchPassword) {
window.$dialog.warning({
title: '警告',
content: () => '检测到锁屏密码被修改,请重新登录',
closable: false,
maskClosable: false,
closeOnEsc: false,
positiveText: '重新登录',
onPositiveClick: toSigningFn,
})
return
}
const dCatchPassword = decrypt(catchPassword)
validate().then(() => {
if (dCatchPassword === state.lockCondition.lockPassword) {
setLockAppScreen(false) setLockAppScreen(false)
updateSettingState('lockScreenSwitch', false) updateSettingState('lockScreenSwitch', false)
removeStorage(APP_CATCH_KEY.appLockScreenPasswordKey, 'localStorage')
state.lockCondition = useCondition() state.lockCondition = useCondition()
} else {
window.$message.warning('密码错误,请重新输入')
} }
}) })
} }
@ -106,15 +80,16 @@ export default defineComponent({
...toRefs(state), ...toRefs(state),
backToSigning, backToSigning,
unlockScreen, unlockScreen,
formRef,
inputInstRef,
isTabletOrSmaller, isTabletOrSmaller,
register,
} }
}, },
render() { render() {
const { isTabletOrSmaller } = this const { isTabletOrSmaller } = this
const { HH_MM, AM_PM, YY_MM_DD, DDD } = this const { HH_MM, AM_PM, YY_MM_DD, DDD } = this
const hmSplit = HH_MM.split(':') const hmSplit = HH_MM.split(':')
const { unlockScreen, backToSigning, register } = this const { unlockScreen, backToSigning } = this
return ( return (
<div class="app-lock-screen__content app-lock-screen__content--full"> <div class="app-lock-screen__content app-lock-screen__content--full">
@ -142,19 +117,16 @@ export default defineComponent({
/> />
</div> </div>
<div class="app-lock-screen__unlock__content-input"> <div class="app-lock-screen__unlock__content-input">
<RForm <NForm ref="formRef" model={this.lockCondition} rules={rules}>
onRegister={register}
model={this.lockCondition}
rules={rules}
>
<NFormItem path="lockPassword"> <NFormItem path="lockPassword">
<NInput <NInput
autofocus ref="inputInstRef"
v-model:value={this.lockCondition.lockPassword} v-model:value={this.lockCondition.lockPassword}
type="password" type="password"
placeholder="请输入解锁密码" placeholder="请输入解锁密码"
clearable clearable
minlength={6} minlength={6}
maxlength={12}
onKeydown={(e: KeyboardEvent) => { onKeydown={(e: KeyboardEvent) => {
if (e.code === 'Enter') { if (e.code === 'Enter') {
unlockScreen() unlockScreen()
@ -178,11 +150,14 @@ export default defineComponent({
</NButton> </NButton>
</NFlex> </NFlex>
</RForm> </NForm>
</div> </div>
<div class="app-lock-screen__unlock__content-date"> <div class="app-lock-screen__unlock__content-date">
<div class="current-date">
{HH_MM}&nbsp;<span>{AM_PM}</span>
</div>
<div class="current-year"> <div class="current-year">
{YY_MM_DD}&nbsp;<span>{DDD}</span>&nbsp;<span>{AM_PM}</span> {YY_MM_DD}&nbsp;<span>{DDD}</span>
</div> </div>
</div> </div>
</div> </div>

View File

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

View File

@ -44,7 +44,7 @@ export default defineComponent({
primaryColor, primaryColor,
) )
// 将主色调任意颜色转换为 rgba 格式 // 将主色调任意颜色转换为 rgba 格式
const fp = colorToRgba(p, 0.85) const fp = colorToRgba(p, 0.38)
// 设置全局主题色 css 变量 // 设置全局主题色 css 变量
html.style.setProperty(rayTemplateThemePrimaryColor, p) // 主色调 html.style.setProperty(rayTemplateThemePrimaryColor, p) // 主色调

View File

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

View File

@ -93,8 +93,6 @@ export const APP_CATCH_KEY_PREFIX = ''
* - appPiniaMenuStore: pinia menu store key * - appPiniaMenuStore: pinia menu store key
* - appPiniaSigningStore: pinia signing store key * - appPiniaSigningStore: pinia signing store key
* - appVersionProvider: 版本信息缓存 key * - appVersionProvider: 版本信息缓存 key
* - appMenuTagOptions: 标签页菜单列表
* - appLockScreenPasswordKey: 锁屏密码缓存 key
*/ */
export const APP_CATCH_KEY = { export const APP_CATCH_KEY = {
signing: 'signing', signing: 'signing',
@ -108,8 +106,6 @@ export const APP_CATCH_KEY = {
appVersionProvider: 'appVersionProvider', appVersionProvider: 'appVersionProvider',
isAppLockScreen: 'isAppLockScreen', isAppLockScreen: 'isAppLockScreen',
appGlobalSearchOptions: 'appGlobalSearchOptions', appGlobalSearchOptions: 'appGlobalSearchOptions',
appMenuTagOptions: 'appMenuTagOptions',
appLockScreenPasswordKey: 'appLockScreenPasswordKey',
} as const } as const
/** /**

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,3 +1,14 @@
/**
*
* axios
*
*
*
*
* axios ,
* 使,
*/
import RequestCanceler from '@/axios/utils/RequestCanceler' import RequestCanceler from '@/axios/utils/RequestCanceler'
import { getAppEnvironment } from '@/utils' import { getAppEnvironment } from '@/utils'
@ -13,36 +24,31 @@ import type {
import type { AnyFC } from '@/types' import type { AnyFC } from '@/types'
import type { AxiosError } from 'axios' import type { AxiosError } from 'axios'
// 当前请求的实例 /** 当前请求的实例 */
const axiosFetchInstance: AxiosFetchInstance = { const axiosFetchInstance: AxiosFetchInstance = {
requestInstance: null, requestInstance: null,
responseInstance: null, responseInstance: null,
} }
// 请求失败返回值 /** 请求失败返回值 */
const axiosFetchError: AxiosFetchError<AxiosError<unknown, unknown>> = { const axiosFetchError: AxiosFetchError<AxiosError<unknown, unknown>> = {
requestError: null, requestError: null,
responseError: null, responseError: null,
} }
// 请求队列(区分 resolve 与 reject 状态) /** 请求队列(区分 resolve 与 reject 状态) */
const implement: ImplementQueue = { const implement: ImplementQueue = {
implementRequestInterceptorArray: [], implementRequestInterceptorArray: [],
implementResponseInterceptorArray: [], implementResponseInterceptorArray: [],
} }
// 请求失败队列
const errorImplement: ErrorImplementQueue = { const errorImplement: ErrorImplementQueue = {
implementRequestInterceptorErrorArray: [], implementRequestInterceptorErrorArray: [],
implementResponseInterceptorErrorArray: [], implementResponseInterceptorErrorArray: [],
} }
type ImplementKeys = keyof ImplementQueue /** 取消器实例 */
type ErrorImplementKeys = keyof ErrorImplementQueue
// 取消器实例
export const axiosCanceler = new RequestCanceler() export const axiosCanceler = new RequestCanceler()
export const useAxiosInterceptor = () => { export const useAxiosInterceptor = () => {
// 创建拦截器实例 /** 创建拦截器实例 */
const createAxiosInstance = ( const createAxiosInstance = (
instance: RequestInterceptorConfig | ResponseInterceptorConfig, instance: RequestInterceptorConfig | ResponseInterceptorConfig,
instanceKey: keyof AxiosFetchInstance, instanceKey: keyof AxiosFetchInstance,
@ -54,33 +60,33 @@ export const useAxiosInterceptor = () => {
instance as ResponseInterceptorConfig) instance as ResponseInterceptorConfig)
} }
// 获取当前实例 /** 获取当前实例 */
const getAxiosInstance = (instanceKey: keyof AxiosFetchInstance) => { const getAxiosInstance = (instanceKey: keyof AxiosFetchInstance) => {
return axiosFetchInstance[instanceKey] return axiosFetchInstance[instanceKey]
} }
// 设置注入方法队列 /** 设置注入方法队列 */
const setImplement = ( const setImplement = (
key: ImplementKeys | ErrorImplementKeys, key: keyof ImplementQueue | keyof ErrorImplementQueue,
func: AnyFC[], func: AnyFC[],
fetchType: FetchType, fetchType: FetchType,
) => { ) => {
fetchType === 'ok' fetchType === 'ok'
? (implement[key as ImplementKeys] = func) ? (implement[key as keyof ImplementQueue] = func)
: (errorImplement[key as ErrorImplementKeys] = func) : (errorImplement[key as keyof ErrorImplementQueue] = func)
} }
// 获取队列中所有的所有拦截器方法 /** 获取队列中所有的所有拦截器方法 */
const getImplement = ( const getImplement = (
key: ImplementKeys | ErrorImplementKeys, key: keyof ImplementQueue | keyof ErrorImplementQueue,
fetchType: FetchType, fetchType: FetchType,
): AnyFC[] => { ): AnyFC[] => {
return fetchType === 'ok' return fetchType === 'ok'
? implement[key as ImplementKeys] ? implement[key as keyof ImplementQueue]
: errorImplement[key as ErrorImplementKeys] : errorImplement[key as keyof ErrorImplementQueue]
} }
// 队列执行器 /** 队列执行器 */
const implementer = (funcs: AnyFC[], ...args: any[]) => { const implementer = (funcs: AnyFC[], ...args: any[]) => {
if (Array.isArray(funcs)) { if (Array.isArray(funcs)) {
funcs.forEach((curr) => { funcs.forEach((curr) => {
@ -91,16 +97,16 @@ export const useAxiosInterceptor = () => {
} }
} }
// 请求、响应前执行拦截器队列中的所有方法 /** 请求、响应前执行拦截器队列中的所有方法 */
const beforeFetch = ( const beforeFetch = (
key: keyof AxiosFetchInstance, key: keyof AxiosFetchInstance,
implementKey: ImplementKeys | ErrorImplementKeys, implementKey: keyof ImplementQueue | keyof ErrorImplementQueue,
fetchType: FetchType, fetchType: FetchType,
) => { ) => {
const funcArr = const funcArr =
fetchType === 'ok' fetchType === 'ok'
? implement[implementKey as ImplementKeys] ? implement[implementKey as keyof ImplementQueue]
: errorImplement[implementKey as ErrorImplementKeys] : errorImplement[implementKey as keyof ErrorImplementQueue]
const instance = getAxiosInstance(key) const instance = getAxiosInstance(key)
const { MODE } = getAppEnvironment() const { MODE } = getAppEnvironment()
@ -109,11 +115,11 @@ export const useAxiosInterceptor = () => {
} }
} }
// 请求、响应错误时执行队列中所有方法 /** 请求、响应错误时执行队列中所有方法 */
const fetchError = ( const fetchError = (
key: keyof AxiosFetchError, key: keyof AxiosFetchError,
error: AxiosError<unknown, unknown>, error: AxiosError<unknown, unknown>,
errorImplementKey: ErrorImplementKeys, errorImplementKey: keyof ErrorImplementQueue,
) => { ) => {
axiosFetchError[key] = error 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 { RMoreDropdown } from '@/components'
import { useSettingGetters } from '@/store' import { useSettingGetters } from '@/store'
import { useTemplateRef } from 'vue' import { useTemplateRef } from 'vue'
import { USE_CHART_PROVIDER_KEY } from './config'
import type { WatchStopHandle } from 'vue' import type { WatchStopHandle } from 'vue'
import type { AnyFC } from '@/types' import type { AnyFC } from '@/types'
@ -125,7 +124,6 @@ export default defineComponent({
const __catch = { const __catch = {
aria: props.showAria, aria: props.showAria,
} }
const chartProvideOptions = inject(USE_CHART_PROVIDER_KEY, {})
/** /**
* *
@ -176,19 +174,10 @@ export default defineComponent({
* echartTheme 使 * echartTheme 使
*/ */
const updateChartTheme = () => { const updateChartTheme = () => {
const { theme: providerTheme } = chartProvideOptions || {}
if (echartInstanceRef.value) { if (echartInstanceRef.value) {
destroyChart() destroyChart()
} }
// 如果配置了全局配置主题,则忽略后面所有逻辑
if (providerTheme) {
renderChart(providerTheme)
return
}
if (props.theme === 'default') { if (props.theme === 'default') {
props.autoChangeTheme ? renderChart('dark') : renderChart('') props.autoChangeTheme ? renderChart('dark') : renderChart('')

View File

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

View File

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

@ -36,14 +36,11 @@ import type { Recordable } from '@/types'
* }, * },
* }) * })
*/ */
const useForm = < const useForm = <T extends Recordable, R extends RFormRules>(
T extends Recordable = Recordable,
R extends RFormRules = RFormRules,
>(
model?: T, model?: T,
rules?: R, rules?: R,
) => { ) => {
const formRef = shallowRef<RFormInst>() const formRef = ref<RFormInst>()
const register = (inst: RFormInst) => { const register = (inst: RFormInst) => {
if (inst) { if (inst) {

View File

@ -4,12 +4,14 @@ import { NModal } from 'naive-ui'
import props from './props' import props from './props'
import { completeSize, uuid } from '@/utils' import { completeSize, uuid } from '@/utils'
import { setupInteract } from './utils'
import { import {
FULLSCREEN_CARD_TYPE_CLASS, FULLSCREEN_CARD_TYPE_CLASS,
R_MODAL_CLASS, R_MODAL_CLASS,
CSS_VARS_KEYS, CSS_VARS_KEYS,
} from './constant' } from './constant'
import type interact from 'interactjs'
import type { ModalProps } from 'naive-ui' import type { ModalProps } from 'naive-ui'
export default defineComponent({ export default defineComponent({
@ -22,11 +24,57 @@ export default defineComponent({
[CSS_VARS_KEYS['dialogWidth']]: completeSize(props.dialogWidth ?? 446), [CSS_VARS_KEYS['dialogWidth']]: completeSize(props.dialogWidth ?? 446),
})) }))
const uuidEl = uuid() const uuidEl = uuid()
let intractable: null | ReturnType<typeof interact>
// 记录拖拽的位置
const position = {
x: 0,
y: 0,
}
// 当前是否为预设 card 类型并且设置了 fullscreen // 当前是否为预设 card 类型并且设置了 fullscreen
const isFullscreenCardType = computed( const isFullscreenCardType = computed(
() => props.preset === 'card' && props.fullscreen, () => 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 { return {
cssVars, cssVars,
isFullscreenCardType, isFullscreenCardType,

View File

@ -1,4 +1,5 @@
import { useModal as useNaiveModal, NScrollbar } from 'naive-ui' import { useModal as useNaiveModal, NScrollbar } from 'naive-ui'
import { setupInteract } from '../utils'
import { queryElements, setStyle, completeSize, setClass } from '@/utils' import { queryElements, setStyle, completeSize, setClass } from '@/utils'
import { R_MODAL_CLASS, CSS_VARS_KEYS } from '../constant' import { R_MODAL_CLASS, CSS_VARS_KEYS } from '../constant'
@ -20,10 +21,10 @@ const useModal = () => {
color: 'rgba(0, 0, 0, 0)', color: 'rgba(0, 0, 0, 0)',
colorHover: 'rgba(0, 0, 0, 0)', colorHover: 'rgba(0, 0, 0, 0)',
}, },
trigger: 'hover', trigger: 'none',
style: { style: {
width: 'auto', width: 'auto',
maxHeight: height:
'calc(var(--html-height) - 29px - var(--n-padding-bottom) - var(--n-padding-bottom) - var(--n-padding-top))', 'calc(var(--html-height) - 29px - var(--n-padding-bottom) - var(--n-padding-bottom) - var(--n-padding-top))',
}, },
}, },
@ -34,7 +35,7 @@ const useModal = () => {
) )
} }
const { preset, fullscreen, width, cardWidth, dialogWidth } = options const { preset, dad, fullscreen, width, cardWidth, dialogWidth } = options
const modalReactive = naiveCreate({ const modalReactive = naiveCreate({
...rest, ...rest,
content: contentNode, content: contentNode,
@ -54,6 +55,15 @@ const useModal = () => {
return return
} }
// 是否启用拖拽
if (dad) {
setupInteract(modalElement, {
preset,
x: 0,
y: 0,
})
}
// preset 为 cardfullscreen 为 true 时,最大化 modal // preset 为 cardfullscreen 为 true 时,最大化 modal
if (fullscreen && preset === 'card') { if (fullscreen && preset === 'card') {
setStyle(modalElement, { setStyle(modalElement, {

View File

@ -4,11 +4,6 @@
// 当设置全屏时启用滚动 // 当设置全屏时启用滚动
& .n-card__content { & .n-card__content {
overflow: scroll; 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 = { const props = {
...modalProps, ...modalProps,
/**
*
* @description
*
*
* @default true
*/
memo: {
type: Boolean,
default: true,
},
/** /**
* *
* @description * @description
@ -47,6 +58,18 @@ const props = {
type: [String, Number] as PropType<string | number>, type: [String, Number] as PropType<string | number>,
default: 446, default: 446,
}, },
/**
*
* @description
*
* header
*
* @default false
*/
dad: {
type: Boolean,
default: false,
},
} }
export default props export default props

View File

@ -1,6 +1,14 @@
import type { ModalOptions as NaiveModalOptions } from 'naive-ui' import type { ModalOptions as NaiveModalOptions } from 'naive-ui'
export interface RModalProps extends NaiveModalOptions { export interface RModalProps extends NaiveModalOptions {
/**
*
* @description
*
*
* @default true
*/
memo?: boolean
/** /**
* *
* @description * @description
@ -33,4 +41,13 @@ export interface RModalProps extends NaiveModalOptions {
* @default 446 * @default 446
*/ */
dialogWidth?: number | string 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 ( return (
<NTabs <NTabs
{...($props as TabsProps)} {...($props as TabsProps)}
ref="segmentRef"
style={[cssVars]} style={[cssVars]}
class="r-segment" class="r-segment"
type="segment" type="segment"

View File

@ -61,23 +61,6 @@ export default defineComponent({
pick(props, 'striped', 'bordered'), 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
})
/** /**
* *
@ -145,7 +128,6 @@ export default defineComponent({
if (onUpdateColumns) { if (onUpdateColumns) {
call(onUpdateColumns, options) call(onUpdateColumns, options)
} }
if ($onUpdateColumns) { if ($onUpdateColumns) {
call($onUpdateColumns, options) call($onUpdateColumns, options)
} }
@ -248,7 +230,6 @@ export default defineComponent({
tool, tool,
wrapperRef, wrapperRef,
propsPopselectValue, propsPopselectValue,
cardHeaderStyle,
} }
}, },
render() { render() {
@ -261,7 +242,6 @@ export default defineComponent({
uuidWrapper, uuidWrapper,
privateReactive, privateReactive,
propsPopselectValue, propsPopselectValue,
cardHeaderStyle,
} = this } = this
const { class: className, ...restAttrs } = $attrs const { class: className, ...restAttrs } = $attrs
const { tool, combineRowProps, contextMenuSelect } = this const { tool, combineRowProps, contextMenuSelect } = this
@ -274,12 +254,11 @@ export default defineComponent({
tableFlexHeight, tableFlexHeight,
cardProps, cardProps,
...restProps ...restProps
} = $props } = $props as ExtractPublicPropTypes<typeof props>
const { headerStyle, ...restCardProps } = cardProps ?? {}
return ( return (
<NCard <NCard
{...restCardProps} {...cardProps}
{...{ {...{
id: uuidWrapper, id: uuidWrapper,
}} }}
@ -287,7 +266,6 @@ export default defineComponent({
ref="wrapperRef" ref="wrapperRef"
bordered={wrapperBordered} bordered={wrapperBordered}
class={className} class={className}
style={cardHeaderStyle}
> >
{{ {{
default: () => ( default: () => (

View File

@ -113,6 +113,7 @@ export default defineComponent({
return true return true
}) })
// eslint-disable-next-line @typescript-eslint/no-explicit-any
return cloneColumns.map((curr, idx) => { return cloneColumns.map((curr, idx) => {
const { key, title, children, fixed, isResizable, ...args } = const { key, title, children, fixed, isResizable, ...args } =
curr as C curr as C
@ -169,7 +170,7 @@ export default defineComponent({
} }
}) as C[] }) as C[]
}, },
// eslint-disable-next-line @typescript-eslint/no-empty-function
set: () => {}, set: () => {},
}) })

View File

@ -41,7 +41,7 @@ import type { PrintDomOptions } from '@/utils'
* }) * })
*/ */
const useTable = () => { const useTable = () => {
const tableRef = shallowRef<RTableInst>() const tableRef = ref<RTableInst>()
let extra = {} as TableProvider let extra = {} as TableProvider
const register: UseTableRegister = (inst, extraInfo) => { const register: UseTableRegister = (inst, extraInfo) => {

View File

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

View File

@ -2,7 +2,7 @@ import { RTable } from '@/components'
import props from './props' import props from './props'
import { useTable } from '@/components' import { useTable } from '@/components'
import { call, removeDuplicateKeys } from '@/utils' import { call } from '@/utils'
import { usePagination } from '@/hooks' import { usePagination } from '@/hooks'
import type { TablePagination, TableRequestConfig, TableProInst } from './types' import type { TablePagination, TableRequestConfig, TableProInst } from './types'
@ -62,8 +62,7 @@ export default defineComponent({
const combineRequestParams = (extraConfig?: TableRequestConfig) => { const combineRequestParams = (extraConfig?: TableRequestConfig) => {
const config = Object.assign({}, props.requestConfig, extraConfig) const config = Object.assign({}, props.requestConfig, extraConfig)
const { formatRangeTime } = config const { params, formatRangeTime } = config
let params = config.params || {}
// 转换时间范围,该功能仅支持 NDatePicker range 模式参数 // 转换时间范围,该功能仅支持 NDatePicker range 模式参数
if (formatRangeTime?.length && params) { if (formatRangeTime?.length && params) {
@ -85,8 +84,6 @@ export default defineComponent({
}) })
} }
params = removeDuplicateKeys(params)
const requestParams = Object.assign({}, params, { const requestParams = Object.assign({}, params, {
page: getPage(), page: getPage(),
pageSize: getPageSize(), pageSize: getPageSize(),
@ -142,7 +139,6 @@ export default defineComponent({
filter, filter,
getCurrentTableRequestParams: getCurrentTableRequestParams:
combineRequestParams as TableProInst['getCurrentTableRequestParams'], combineRequestParams as TableProInst['getCurrentTableRequestParams'],
resetTablePagination: resetPagination,
}) })
} }
}) })

View File

@ -1,6 +1,7 @@
import type { Recordable } from '@/types' import type { Recordable } from '@/types'
import type { TableProInst, TableRequestConfig } from '../types' import type { TableProInst, TableRequestConfig } from '../types'
import type { import type {
RTableInst,
CsvOptionsType, CsvOptionsType,
FilterState, FilterState,
ScrollToOptions, ScrollToOptions,
@ -151,14 +152,6 @@ export const useTablePro = () => {
// @ts-ignore // @ts-ignore
getTableProInstance().getCurrentTableRequestParams.call(null, extraConfig) getTableProInstance().getCurrentTableRequestParams.call(null, extraConfig)
/**
*
* @description
*
*/
const resetTablePagination = () =>
getTableProInstance().resetTablePagination.call(null)
return [ return [
register, register,
{ {
@ -174,7 +167,6 @@ export const useTablePro = () => {
runTableRequest, runTableRequest,
print, print,
getCurrentTableRequestParams, getCurrentTableRequestParams,
resetTablePagination,
}, },
] as const ] as const
} }

View File

@ -72,10 +72,4 @@ export interface TableProInst extends Omit<RTableInst, 'getTableInstance'> {
getCurrentTableRequestParams: <T = Recordable>( getCurrentTableRequestParams: <T = Recordable>(
extraConfig?: TableRequestConfig<T>, extraConfig?: TableRequestConfig<T>,
) => TableRequestConfig<T>['params'] & Recordable ) => TableRequestConfig<T>['params'] & Recordable
/**
*
* @description
*
*/
resetTablePagination: () => void
} }

View File

@ -1,10 +1,6 @@
import dayjs from 'dayjs' import dayjs from 'dayjs'
import { DEFAULT_DAYJS_LOCAL } from '@/app-config' import { DEFAULT_DAYJS_LOCAL } from '@/app-config'
import 'dayjs/locale/zh-cn' 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 * dayjs
*/ */
export const setupDayjs = () => { export const setupDayjs = () => {
const { localeLanguage } = getStorage<SettingState>( dayjs.locale(DEFAULT_DAYJS_LOCAL)
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)
} }

View File

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

View File

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

View File

@ -10,17 +10,17 @@ export type CloseMenuTag = Key | MenuTagOptions
/** /**
* *
* @param target key * @param target key
* @param fn * @param fc
* *
* *
*/ */
const normalMenuTagOption = (target: CloseMenuTag, fn: string) => { const normalMenuTagOption = (target: CloseMenuTag, fc: string) => {
const { getMenuTagOptions } = useMenuGetters() const { getMenuTagOptions } = useMenuGetters()
if (typeof target === 'number') { if (typeof target === 'number') {
// 判断是否为 NaN // 判断是否为 NaN
if (isNaN(target)) { if (isNaN(target)) {
console.warn(`${fn}: The ${target} is NaN, expect number.`) console.warn(`${fc}: The ${target} is NaN, expect number.`)
return return
} }
@ -28,7 +28,7 @@ const normalMenuTagOption = (target: CloseMenuTag, fn: string) => {
// 判断是否超出当前标签页列表最大长度或者是否为负数 // 判断是否超出当前标签页列表最大长度或者是否为负数
if (target > getMenuTagOptions.value.length || target < -1) { if (target > getMenuTagOptions.value.length || target < -1) {
console.warn( 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 return
@ -50,7 +50,7 @@ const normalMenuTagOption = (target: CloseMenuTag, fn: string) => {
index, index,
} }
: console.warn( : 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 { } else {
const { fullPath } = target const { fullPath } = target
@ -60,7 +60,7 @@ const normalMenuTagOption = (target: CloseMenuTag, fn: string) => {
if (index === -1) { if (index === -1) {
console.warn( 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 return

View File

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

View File

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

View File

@ -82,7 +82,7 @@ const styleElement = document.createElement('style')
* <div ref="refDom" /> * <div ref="refDom" />
* </template> * </template>
* <script lang="ts" setup> * <script lang="ts" setup>
* const refDom = useTemplateRef<HTMLElement>('refDom') * const refDom = ref<HTMLElement>()
* const { enter, exit, toggleFullscreen } = useElementFullscreen(refDom, { UseElementFullscreenOptions }) * const { enter, exit, toggleFullscreen } = useElementFullscreen(refDom, { UseElementFullscreenOptions })
* *
* enter() // 进入全屏 * enter() // 进入全屏

View File

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

View File

@ -6,7 +6,7 @@ import { RIcon } from '@/components'
import { isValueType, renderNode } from '@/utils' import { isValueType, renderNode } from '@/utils'
import { useSettingGetters } from '@/store' import { useSettingGetters } from '@/store'
export const SIDER_BAR_LOGO = shallowRef<HTMLElement>() export const SIDER_BAR_LOGO = ref<HTMLElement>()
export default defineComponent({ export default defineComponent({
name: 'SiderBarLogo', name: 'SiderBarLogo',

View File

@ -7,7 +7,6 @@ import { LAYOUT_SIDER_REF } from '@/app-config'
import { useDevice } from '@/hooks' import { useDevice } from '@/hooks'
import { getVariableToRefs, setVariable } from '@/global-variable' import { getVariableToRefs, setVariable } from '@/global-variable'
import { useMenuGetters, useMenuActions, useSettingGetters } from '@/store' import { useMenuGetters, useMenuActions, useSettingGetters } from '@/store'
import { positionSelectedMenuItem } from '@/utils'
import type { MenuInst } from 'naive-ui' import type { MenuInst } from 'naive-ui'
import type { NaiveMenuOptions } from '@/types' import type { NaiveMenuOptions } from '@/types'
@ -16,8 +15,7 @@ import type { AppMenuOption } from '@/types'
export default defineComponent({ export default defineComponent({
name: 'AppMenu', name: 'AppMenu',
setup() { setup() {
// 这里使用 shallowRef 而不是 useTemplateRef 是因为在这里有一个特殊情况,会导致一个 readonly 的警告 const menuRef = ref<MenuInst | null>(null)
const menuRef = shallowRef<MenuInst | null>()
const { changeMenuModelValue, collapsedMenu, updateMenuState } = const { changeMenuModelValue, collapsedMenu, updateMenuState } =
useMenuActions() useMenuActions()
@ -25,18 +23,17 @@ export default defineComponent({
const { getMenuOptions, getCollapsed, getMenuKey } = useMenuGetters() const { getMenuOptions, getCollapsed, getMenuKey } = useMenuGetters()
const modelMenuKey = computed({ const modelMenuKey = computed({
get: () => { get: () => {
/**
*
* @description
* eslint computed 使
* computed get
*
*
*/
// eslint-disable-next-line vue/no-async-in-computed-properties // eslint-disable-next-line vue/no-async-in-computed-properties
setTimeout(() => { setTimeout(() => {
/**
*
* @description
* eslint computed 使
* computed get
*
*
*/
showMenuOption() showMenuOption()
positionSelectedMenuItem()
}, 300) }, 300)
return getMenuKey.value return getMenuKey.value

View File

@ -25,41 +25,43 @@ $menuTagWrapperWidth: 76px;
} }
// 激活标签页关闭按钮样式 // 激活标签页关闭按钮样式
.menu-tag .menu-tag__btn { .menu-tag {
transition: .menu-tag__btn {
color 0.3s var(--n-bezier), .menu-tag__btn-icon--hidden {
background-color 0.3s var(--n-bezier), display: none !important;
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);
} }
}
}
.ray-template--light { .menu-tag__btn-icon {
.menu-tag__btn-icon:hover { display: inline;
filter: brightness(1.2); margin-left: 0;
} width: 0;
} height: 0;
transition: all 0.3s var(--r-bezier);
overflow: hidden;
opacity: 0;
.ray-template--dark { & .ray-icon {
.menu-tag__btn-icon:hover { width: 11px !important;
filter: brightness(0.8); 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

@ -517,7 +517,6 @@ export default defineComponent({
}} }}
size="small" size="small"
focusable={false} focusable={false}
iconPlacement="right"
> >
{{ {{
default: () => ( default: () => (
@ -534,18 +533,16 @@ export default defineComponent({
}, },
}} }}
</span> </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> </NButton>
))} ))}

View File

@ -84,14 +84,7 @@ export default defineComponent({
let searchElementIndex = 0 let searchElementIndex = 0
// 缓存索引 // 缓存索引
let preSearchElementIndex = searchElementIndex let preSearchElementIndex = searchElementIndex
const { isTabletOrSmaller } = useDevice({ const { isTabletOrSmaller } = useDevice()
observer: (val) => {
// 当处于小尺寸状态时,自动关闭搜索框
if (val) {
modelShow.value = false
}
},
})
const loading = ref(false) const loading = ref(false)
// 激活样式 class name // 激活样式 class name
const ACTIVE_CLASS = 'content-item--active' const ACTIVE_CLASS = 'content-item--active'
@ -316,7 +309,22 @@ export default defineComponent({
</NFlex> </NFlex>
) )
useEventListener(window, 'keydown', registerArouseKeyboard) watchEffect(() => {
// 当处于小尺寸状态时,自动关闭搜索框
if (isTabletOrSmaller.value) {
modelShow.value = false
}
})
useEventListener(
window,
'keydown',
(e: KeyboardEvent) => {
registerArouseKeyboard(e)
registerChangeSearchElementIndex(e)
},
true,
)
return { return {
...toRefs(state), ...toRefs(state),
@ -328,16 +336,11 @@ export default defineComponent({
isTabletOrSmaller, isTabletOrSmaller,
SearchItem, SearchItem,
loading, loading,
registerChangeSearchElementIndex,
} }
}, },
render() { render() {
const { isTabletOrSmaller, searchOptions, loading } = this const { isTabletOrSmaller, searchOptions, loading } = this
const { const { SearchItem, fuzzySearchMenuOptions } = this
SearchItem,
fuzzySearchMenuOptions,
registerChangeSearchElementIndex,
} = this
return isTabletOrSmaller ? ( return isTabletOrSmaller ? (
<div style="display: none;"></div> <div style="display: none;"></div>
@ -347,11 +350,7 @@ export default defineComponent({
transformOrigin="center" transformOrigin="center"
displayDirective="if" displayDirective="if"
> >
<div <div class="global-search global-search--dark global-search--light">
class="global-search global-search--dark global-search--light"
tabindex="-1"
onKeydown={registerChangeSearchElementIndex}
>
<div class="global-search__wrapper"> <div class="global-search__wrapper">
<NCard <NCard
class="global-search__card" class="global-search__card"

View File

@ -2,6 +2,83 @@ import type { SettingState } from '@/store/modules/setting/types'
import type { InjectionKey, Reactive } from 'vue' import type { InjectionKey, Reactive } from 'vue'
import type { DebouncedFunc } from 'lodash-es' import type { DebouncedFunc } from 'lodash-es'
type OmitKeys =
| 'contentTransition'
| 'watermarkSwitch'
| 'keepAliveConfig'
| 'menuConfig'
| 'menuTagSwitch'
| 'breadcrumbSwitch'
| 'copyrightSwitch'
| 'drawerPlacement'
| 'colorWeakness'
| 'watermarkConfig'
| 'dynamicDocumentTitle'
interface Config extends Pick<SettingState, OmitKeys> {}
// SettingDrawer 默认配置系统配置项
const defaultSettingConfig: Readonly<Partial<Config>> = {
contentTransition: 'scale',
watermarkSwitch: false,
keepAliveConfig: {
maxKeepAliveLength: 10,
setupKeepAlive: true,
keepAliveExclude: [],
},
menuConfig: {
collapsedWidth: 64,
collapsedMode: 'width',
collapsedIconSize: 16,
collapsedIndent: 24,
accordion: false,
menuSiderBarLogo: true,
iconSize: 16,
menuWidth: 272,
inverted: false,
nativeScrollbar: false,
},
menuTagSwitch: true,
breadcrumbSwitch: true,
copyrightSwitch: true,
drawerPlacement: 'right',
colorWeakness: false,
watermarkConfig: {
content: 'Trying be better~',
fontSize: 16,
lineHeight: 16,
width: 384,
height: 384,
xOffset: 12,
yOffset: 60,
rotate: -15,
xGap: 0,
yGap: 0,
cross: true,
},
dynamicDocumentTitle: true,
}
/**
*
* @description
*
*/
export const getDefaultSettingConfig = (): Partial<Config> => {
return defaultSettingConfig
}
/**
*
* @param config
*
* @description
*
*/
export const updateDefaultSettingConfig = (config: Partial<SettingState>) => {
Object.assign(defaultSettingConfig, config)
}
interface SettingDrawerInjectKey extends SettingState { interface SettingDrawerInjectKey extends SettingState {
throttleSetupAppMenu: DebouncedFunc<() => Promise<void>> throttleSetupAppMenu: DebouncedFunc<() => Promise<void>>
} }

View File

@ -15,11 +15,10 @@ import SegmentViewsWatermark from './segment-views/Watermark'
import SegmentViewsCustomMenu from './segment-views/CustomMenu' import SegmentViewsCustomMenu from './segment-views/CustomMenu'
import { useSettingGetters, useSettingActions, useMenuActions } from '@/store' import { useSettingGetters, useSettingActions, useMenuActions } from '@/store'
import { SETTING_DRAWER_INJECT_KEY } from './constant' import { getDefaultSettingConfig, SETTING_DRAWER_INJECT_KEY } from './constant'
import { cloneDeep, forIn, throttle } from 'lodash-es' import { forIn, throttle } from 'lodash-es'
import { drawerProps } from 'naive-ui' import { drawerProps } from 'naive-ui'
import { useModal } from '@/components' import { useModal } from '@/components'
import { getDefaultSettingConfig } from '@/store/modules/setting/constant'
import type { SettingState } from '@/store/modules/setting/types' import type { SettingState } from '@/store/modules/setting/types'
@ -71,13 +70,11 @@ export default defineComponent({
positiveText: '确认初始化', positiveText: '确认初始化',
negativeText: '取消', negativeText: '取消',
onPositiveClick: () => { onPositiveClick: () => {
forIn(cloneDeep(getDefaultSettingConfig()), (value, key) => { forIn(getDefaultSettingConfig(), (value, key) => {
// eslint-disable-next-line @typescript-eslint/ban-ts-comment // eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore // @ts-ignore
modelReactive[key] = value modelReactive[key] = value
console.log(value, key)
updateSettingState(key as keyof SettingState, value) updateSettingState(key as keyof SettingState, value)
}) })

View File

@ -37,28 +37,21 @@ import type { VNode } from 'vue'
export default defineComponent({ export default defineComponent({
name: 'AppSiderBar', name: 'AppSiderBar',
setup() { setup() {
// 获取 setting 相关值
const { updateLocale, updateSettingState } = useSettingActions() const { updateLocale, updateSettingState } = useSettingActions()
const { t } = useI18n() const { t } = useI18n()
// 获取全屏相关方法
const [isFullscreen, { toggleFullscreen, isEnabled }] = useFullscreen( const [isFullscreen, { toggleFullscreen, isEnabled }] = useFullscreen(
document.getElementsByTagName('html')[0], document.getElementsByTagName('html')[0],
) )
// 获取设置相关方法
const { getDrawerPlacement, getBreadcrumbSwitch } = useSettingGetters() const { getDrawerPlacement, getBreadcrumbSwitch } = useSettingGetters()
// 是否显示设置抽屉 const showSettings = ref(false) // 是否显示设置抽屉
const showSettings = ref(false) const globalSearchShown = ref(false) // 是否展示全局搜索
// 是否展示全局搜索
const globalSearchShown = ref(false)
// 当前是否为平板或者更小的设备
const { isTabletOrSmaller } = useDevice() const { isTabletOrSmaller } = useDevice()
// 获取全局 drawer 的值
const globalDrawerValue = getVariableToRefs('globalDrawerValue') const globalDrawerValue = getVariableToRefs('globalDrawerValue')
/** /**
* *
* @description *
*
*/ */
const leftIconOptions = computed(() => const leftIconOptions = computed(() =>
createLeftIconOptions({ createLeftIconOptions({
@ -68,8 +61,7 @@ export default defineComponent({
) )
/** /**
* *
* @description *
*
*/ */
const rightTooltipIconOptions = computed(() => const rightTooltipIconOptions = computed(() =>
createRightIconOptions({ createRightIconOptions({

View File

@ -29,9 +29,9 @@ import { useSettingGetters } from '@/store'
export default defineComponent({ export default defineComponent({
name: 'RLayout', name: 'RLayout',
setup() { setup() {
const layoutSiderBarRef = shallowRef<HTMLElement>() // 顶部操作栏 shallowRef const layoutSiderBarRef = ref<HTMLElement>() // 顶部操作栏 ref
const layoutMenuTagRef = shallowRef<HTMLElement>() // 标签页 shallowRef const layoutMenuTagRef = ref<HTMLElement>() // 标签页 ref
const layoutFooterRef = shallowRef<HTMLElement>() // 底部版权 shallowRef const layoutFooterRef = ref<HTMLElement>() // 底部版权 ref
const { getMenuTagSwitch, getCopyrightSwitch } = useSettingGetters() const { getMenuTagSwitch, getCopyrightSwitch } = useSettingGetters()
const { getLockAppScreen } = useAppLockScreen() const { getLockAppScreen } = useAppLockScreen()

View File

@ -29,6 +29,5 @@
"TemplateHooks": "Template Api", "TemplateHooks": "Template Api",
"scrollReveal": "Scroll Reveal", "scrollReveal": "Scroll Reveal",
"TablePro": "Table Pro", "TablePro": "Table Pro",
"Flow": "Flow", "Flow": "Flow"
"DraggableCard": "Draggable Card"
} }

View File

@ -29,6 +29,5 @@
"TemplateHooks": "模板内置 Api", "TemplateHooks": "模板内置 Api",
"scrollReveal": "滚动动画", "scrollReveal": "滚动动画",
"TablePro": "高级表格", "TablePro": "高级表格",
"Flow": "流程图", "Flow": "流程图"
"DraggableCard": "拖拽卡片"
} }

View File

@ -5,7 +5,7 @@ import type { AppRouteRecordRaw } from '@/router/types'
const r: AppRouteRecordRaw = { const r: AppRouteRecordRaw = {
path: '/flow', path: '/flow',
component: () => import('@/views/demo/flow-demo'), component: () => import('@/views/demo/Flow'),
meta: { meta: {
i18nKey: t('menu.Flow'), i18nKey: t('menu.Flow'),
icon: 'other', icon: 'other',

View File

@ -5,7 +5,7 @@ import type { AppRouteRecordRaw } from '@/router/types'
const barcode: AppRouteRecordRaw = { const barcode: AppRouteRecordRaw = {
path: 'barcode', path: 'barcode',
component: () => import('@/views/demo/barcode-demo'), component: () => import('@/views/demo/BarcodeDemo'),
meta: { meta: {
i18nKey: t('menu.Barcode'), i18nKey: t('menu.Barcode'),
icon: 'other', icon: 'other',

View File

@ -1,19 +0,0 @@
import { t } from '@/hooks/web/useI18n'
import { LAYOUT } from '@/router/constant'
import type { AppRouteRecordRaw } from '@/router/types'
const r: AppRouteRecordRaw = {
path: '/draggable-card',
component: () => import('@/views/demo/draggable-card'),
meta: {
i18nKey: t('menu.DraggableCard'),
icon: 'other',
order: 2,
extra: {
label: 'drag',
},
},
}
export default r

View File

@ -5,7 +5,7 @@ import type { AppRouteRecordRaw } from '@/router/types'
const r: AppRouteRecordRaw = { const r: AppRouteRecordRaw = {
path: '/table-pro', path: '/table-pro',
component: () => import('@/views/demo/table-pro-demo'), component: () => import('@/views/demo/TablePro'),
meta: { meta: {
i18nKey: t('menu.TablePro'), i18nKey: t('menu.TablePro'),
icon: 'other', icon: 'other',

View File

@ -16,6 +16,10 @@ export const orderRoutes = (routes: AppRouteRecordRaw[]) => {
const currOrder = curr.meta?.order ?? 1 const currOrder = curr.meta?.order ?? 1
const nextOrder = next.meta?.order ?? 0 const nextOrder = next.meta?.order ?? 0
if (typeof currOrder !== 'number' || typeof nextOrder !== 'number') {
throw new TypeError('orderRoutes error: order must be a number!')
}
if (currOrder === nextOrder) { if (currOrder === nextOrder) {
// 如果两个路由的 order 值相同,则按照路由名进行排序 // 如果两个路由的 order 值相同,则按照路由名进行排序
return curr.name return curr.name

View File

@ -5,8 +5,7 @@ export const useKeepAliveGetters = () => {
/** /**
* *
* @@description * @remark name
* name
*/ */
const getKeepAliveInclude = computed(() => variable.keepAliveInclude) const getKeepAliveInclude = computed(() => variable.keepAliveInclude)

View File

@ -1,49 +1,57 @@
import { piniaMenuStore } from '../modules/menu' import { piniaMenuStore } from '../modules/menu'
import { useAppRoot } from '@/hooks'
export const useMenuGetters = () => { export const useMenuGetters = () => {
const variable = piniaMenuStore() const variable = piniaMenuStore()
/** /**
* *
* @description * @remark
*
*/ */
const getMenuOptions = computed(() => variable.options) const getMenuOptions = computed(() => variable.options)
/** /**
* *
* @description * @remark
*
*/ */
const getBreadcrumbOptions = computed(() => variable.breadcrumbOptions) const getBreadcrumbOptions = computed(() => variable.breadcrumbOptions)
/** /**
* *
* @description * @remark key
* key
*/ */
const getMenuKey = computed(() => variable.menuKey) const getMenuKey = computed(() => variable.menuKey)
/** /**
* *
* @description * @remark
*
*/ */
const getMenuTagOptions = computed(() => { const getMenuTagOptions = computed(() => {
return variable.menuTagOptions const { getRootPath } = useAppRoot()
})
return variable.menuTagOptions.map((curr, _idx, currentArray) => {
if (curr.key === getMenuKey.value && curr.key !== getRootPath.value) {
curr.closeable = true
} else {
curr.closeable = false
}
if (curr.key === getRootPath.value) {
curr.closeable = false
}
if (currentArray.length <= 1) {
curr.closeable = false
}
return curr
})
})
/** /**
* *
* @description * @remark
*
*/ */
const getCurrentMenuOption = computed(() => variable.currentMenuOption) const getCurrentMenuOption = computed(() => variable.currentMenuOption)
/** /**
* *
* @description * @remark
*
*/ */
const getCollapsed = computed(() => variable.collapsed) const getCollapsed = computed(() => variable.collapsed)

View File

@ -1,3 +1,20 @@
/**
*
* menu pinia store
*
* :
* - BreadcrumbMenuTagMenuMenu
* - BreadcrumbMenuTagMenuMenu vue-router routers,
*
* (sessionStorage):
* - breadcrumbOptions
* - menuKey
* - menuTagOptions
*
* :
* - parseAndFindMatchingNodes: 如果需要反向查找完整的父子节点使
*/
import { NEllipsis } from 'naive-ui' import { NEllipsis } from 'naive-ui'
import { setStorage, equalRouterPath, updateObjectValue } from '@/utils' import { setStorage, equalRouterPath, updateObjectValue } from '@/utils'
@ -8,7 +25,6 @@ import {
createMenuIcon, createMenuIcon,
getCatchMenuKey, getCatchMenuKey,
createMenuExtra, createMenuExtra,
getCatchMenuTagOptions,
} from './utils' } from './utils'
import { useI18n } from '@/hooks' import { useI18n } from '@/hooks'
import { getAppRawRoutes } from '@/router/app-route-modules' import { getAppRawRoutes } from '@/router/app-route-modules'
@ -17,9 +33,10 @@ import { APP_CATCH_KEY } from '@/app-config'
import { pick } from 'lodash-es' import { pick } from 'lodash-es'
import { pickRouteRecordNormalizedConstant } from './constant' import { pickRouteRecordNormalizedConstant } from './constant'
import type { AppMenuOption, MenuTagOptions, AnyFC } from '@/types' import type { AppMenuOption, MenuTagOptions } from '@/types'
import type { MenuState } from '@/store/modules/menu/types' import type { MenuState } from '@/store/modules/menu/types'
import type { LocationQuery } from 'vue-router' import type { LocationQuery } from 'vue-router'
import type { UpdateMenuState } from './types'
let cachePreNormal: AppMenuOption | undefined = void 0 let cachePreNormal: AppMenuOption | undefined = void 0
@ -74,7 +91,7 @@ export const piniaMenuStore = defineStore(
menuKey: getCatchMenuKey(), // 当前菜单 `key` menuKey: getCatchMenuKey(), // 当前菜单 `key`
options: [], // 菜单列表 options: [], // 菜单列表
collapsed: false, // 是否折叠菜单 collapsed: false, // 是否折叠菜单
menuTagOptions: getCatchMenuTagOptions(), // tag 标签菜单 menuTagOptions: [], // tag 标签菜单
breadcrumbOptions: [], // 面包屑菜单 breadcrumbOptions: [], // 面包屑菜单
currentMenuOption: null, // 当前激活菜单项 currentMenuOption: null, // 当前激活菜单项
}) })
@ -84,17 +101,12 @@ export const piniaMenuStore = defineStore(
* *
* @param key menu state key * @param key menu state key
* @param value updated value * @param value updated value
* @param cb callback function
* *
* @description * @description
* menu state key * menu state key
*/ */
const updateMenuState = <T extends keyof MenuState>( const updateMenuState: UpdateMenuState = (key, value, cb) => {
key: T, updateObjectValue(menuState, key, value, cb)
value: Partial<MenuState[T]>,
cb?: AnyFC,
) => {
updateObjectValue(menuState, key, value as MenuState[T], cb)
} }
/** /**
@ -118,7 +130,11 @@ export const piniaMenuStore = defineStore(
// 设置 label, i18nKey 优先级最高 // 设置 label, i18nKey 优先级最高
const label = computed(() => (i18nKey ? t(`${i18nKey}`) : noLocalTitle)) const label = computed(() => (i18nKey ? t(`${i18nKey}`) : noLocalTitle))
// 拼装菜单项,容错处理,兼容以前版本 key 选取为 path 的情况 /**
*
*
* key path
*/
const route = { const route = {
...option, ...option,
key: option.fullPath, key: option.fullPath,
@ -134,12 +150,10 @@ export const piniaMenuStore = defineStore(
extra: createMenuExtra(option), extra: createMenuExtra(option),
}) })
// 如果当前菜单项与缓存菜单项相同,则更新当前菜单项
if (option.fullPath === getCatchMenuKey()) { if (option.fullPath === getCatchMenuKey()) {
menuState.currentMenuOption = attr menuState.currentMenuOption = attr
} }
// 验证菜单项是否显示
attr.show = validMenuItemShow(attr) attr.show = validMenuItemShow(attr)
return attr return attr
@ -155,20 +169,21 @@ export const piniaMenuStore = defineStore(
* *
*/ */
const setBreadcrumbOptions = (key: string | number) => { const setBreadcrumbOptions = (key: string | number) => {
menuState.breadcrumbOptions = unref( menuState.breadcrumbOptions = parseAndFindMatchingNodes(
parseAndFindMatchingNodes(menuState.options, 'fullPath', key), menuState.options,
'fullPath',
key,
) )
} }
/** /**
* *
* @param options menu tag option(s) * @param options menu tag options
* @param isAppend true: (push), false: * @param isAppend is append
* *
* @description * @description
* *
* options * true: pushfalse:
* options
*/ */
const setMenuTagOptions = ( const setMenuTagOptions = (
options: MenuTagOptions | MenuTagOptions[], options: MenuTagOptions | MenuTagOptions[],
@ -180,8 +195,6 @@ export const piniaMenuStore = defineStore(
isAppend isAppend
? menuState.menuTagOptions.push(...arr) ? menuState.menuTagOptions.push(...arr)
: (menuState.menuTagOptions = arr) : (menuState.menuTagOptions = arr)
setStorage(APP_CATCH_KEY.appMenuTagOptions, menuState.menuTagOptions)
} }
/** /**
@ -201,13 +214,6 @@ export const piniaMenuStore = defineStore(
if (!tag) { if (!tag) {
menuState.menuTagOptions.push(option as MenuTagOptions) menuState.menuTagOptions.push(option as MenuTagOptions)
} }
// 清理所有非根路径的标签页
menuState.menuTagOptions = menuState.menuTagOptions.filter((curr) =>
curr.fullPath?.startsWith('/'),
)
setStorage(APP_CATCH_KEY.appMenuTagOptions, menuState.menuTagOptions)
} }
/** /**
@ -248,22 +254,22 @@ export const piniaMenuStore = defineStore(
const { sameLevel } = meta const { sameLevel } = meta
// 更新缓存队列 /** 更新缓存队列 */
setKeepAliveInclude(option) setKeepAliveInclude(option)
// 更新浏览器标题 /** 更新浏览器标题 */
updateDocumentTitle(option) updateDocumentTitle(option)
// 如果不为 sameLevel则会执行更新覆盖更新面包屑、添加标签菜单、更新缓存 // 如果不为 sameLevel则会执行更新覆盖更新面包屑、添加标签菜单、更新缓存
if (!sameLevel) { if (!sameLevel) {
// 更新标签菜单 /** 更新标签菜单 */
setMenuTagOptionsWhenMenuValueChange(key, option) setMenuTagOptionsWhenMenuValueChange(key, option)
// 更新面包屑 /** 更新面包屑 */
setBreadcrumbOptions(key) setBreadcrumbOptions(key)
menuState.menuKey = key menuState.menuKey = key
menuState.currentMenuOption = option menuState.currentMenuOption = option
// 缓存菜单 key(sessionStorage) /** 缓存菜单 key(sessionStorage) */
setStorage(APP_CATCH_KEY.appMenuKey, key) setStorage(APP_CATCH_KEY.appMenuKey, key)
} else { } else {
// 使用 pick 提取仅需要的字段,避免 vue 抛错空引用,导致性能损耗 // 使用 pick 提取仅需要的字段,避免 vue 抛错空引用,导致性能损耗
@ -411,13 +417,8 @@ export const piniaMenuStore = defineStore(
* @description * @description
* menu tag * menu tag
*/ */
const spliceMenTagOptions = (idx: number, length = 1) => { const spliceMenTagOptions = (idx: number, length = 1) =>
const r = menuState.menuTagOptions.splice(idx, length) menuState.menuTagOptions.splice(idx, length)
setStorage(APP_CATCH_KEY.appMenuTagOptions, menuState.menuTagOptions)
return r
}
/** /**
* *
@ -465,7 +466,7 @@ export const piniaMenuStore = defineStore(
persist: { persist: {
key: APP_CATCH_KEY.appPiniaMenuStore, key: APP_CATCH_KEY.appPiniaMenuStore,
storage: window.localStorage, storage: window.localStorage,
pick: ['menuKey', 'collapsed'], pick: ['breadcrumbOptions', 'menuKey', 'menuTagOptions', 'collapsed'],
}, },
}, },
) )

View File

@ -8,3 +8,11 @@ export interface MenuState {
breadcrumbOptions: AppMenuOption[] breadcrumbOptions: AppMenuOption[]
currentMenuOption: AppMenuOption | null currentMenuOption: AppMenuOption | null
} }
type PickUpdateKeys = 'collapsed' | 'currentMenuOption'
export type UpdateMenuState = <T extends keyof Pick<MenuState, PickUpdateKeys>>(
key: T,
value: Partial<MenuState[T]>,
cb?: AnyFC,
) => void

View File

@ -264,17 +264,3 @@ export const getCatchMenuKey = () => {
return cacheMenuKey return cacheMenuKey
} }
/**
*
* @returns
*/
export const getCatchMenuTagOptions = () => {
return getStorage<AppMenuOption[]>(
APP_CATCH_KEY.appMenuTagOptions,
'sessionStorage',
{
defaultValue: [],
},
)
}

View File

@ -1,89 +0,0 @@
import type { SettingState } from './types'
type OmitKeys =
| 'contentTransition'
| 'watermarkSwitch'
| 'keepAliveConfig'
| 'menuConfig'
| 'menuTagSwitch'
| 'breadcrumbSwitch'
| 'copyrightSwitch'
| 'drawerPlacement'
| 'colorWeakness'
| 'watermarkConfig'
| 'dynamicDocumentTitle'
interface Config extends Pick<SettingState, OmitKeys> {}
// SettingDrawer 默认配置系统配置项
const defaultSettingConfig: Readonly<Config> = {
// 切换过渡效果
contentTransition: 'scale',
// 水印开关
watermarkSwitch: false,
// 缓存动态设置
keepAliveConfig: {
maxKeepAliveLength: 10,
setupKeepAlive: true,
keepAliveExclude: [],
},
// 菜单配置
menuConfig: {
collapsedWidth: 64,
collapsedMode: 'width',
collapsedIconSize: 16,
collapsedIndent: 24,
accordion: false,
menuSiderBarLogo: true,
iconSize: 16,
menuWidth: 272,
inverted: false,
nativeScrollbar: false,
},
// 多标签页开关
menuTagSwitch: true,
// 面包屑开关
breadcrumbSwitch: true,
// 底部区域开关
copyrightSwitch: true,
// 默认设置出现位置
drawerPlacement: 'right',
// 色弱模式
colorWeakness: false,
// 水印
watermarkConfig: {
content: 'Trying be better~',
fontSize: 16,
lineHeight: 16,
width: 384,
height: 384,
xOffset: 12,
yOffset: 60,
rotate: -15,
xGap: 0,
yGap: 0,
cross: true,
},
// 动态标题
dynamicDocumentTitle: true,
}
/**
*
* @description
*
*/
export const getDefaultSettingConfig = (): Readonly<Config> => {
return defaultSettingConfig
}
/**
*
* @param config
*
* @description
*
*/
export const updateDefaultSettingConfig = (config: Partial<SettingState>) => {
Object.assign(defaultSettingConfig, config)
}

View File

@ -2,8 +2,6 @@ import { getAppDefaultLanguage } from '@/locales/utils'
import { colorToRgba, setStorage, updateObjectValue, setStyle } from '@/utils' import { colorToRgba, setStorage, updateObjectValue, setStyle } from '@/utils'
import { useI18n, useDayjs } from '@/hooks' import { useI18n, useDayjs } from '@/hooks'
import { APP_CATCH_KEY, APP_THEME, GLOBAL_CLASS_NAMES } from '@/app-config' import { APP_CATCH_KEY, APP_THEME, GLOBAL_CLASS_NAMES } from '@/app-config'
import { getDefaultSettingConfig } from './constant'
import { cloneDeep, merge } from 'lodash-es'
import type { SettingState } from '@/store/modules/setting/types' import type { SettingState } from '@/store/modules/setting/types'
import type { LocalKey } from '@/hooks' import type { LocalKey } from '@/hooks'
@ -13,29 +11,53 @@ export const piniaSettingStore = defineStore(
'setting', 'setting',
() => { () => {
const { const {
appPrimaryColor: { primaryColor, primaryFadeColor }, appPrimaryColor: { primaryColor },
} = APP_THEME } = __APP_CFG__ // 默认主题色
const { locale } = useI18n() const { locale } = useI18n()
const { locale: dayjsLocal } = useDayjs() const { locale: dayjsLocal } = useDayjs()
const settingState = reactive<SettingState>({ const settingState = reactive<SettingState>({
// 默认设置出现位置
drawerPlacement: 'right',
// 默认主题色 // 默认主题色
primaryColorOverride: { primaryColorOverride: {
common: { common: {
primaryColor: primaryColor, primaryColor: primaryColor,
primaryColorHover: primaryFadeColor, primaryColorHover: primaryColor,
primaryColorPressed: primaryColor, primaryColorPressed: primaryColor,
primaryColorSuppl: primaryFadeColor,
}, },
}, },
// 内部使用用于判断是否为黑夜主题为了兼容历史遗留版本true 为黑夜主题,false 为明亮主题 // true 为黑夜主题, false 为明亮主题
_appTheme: false, _appTheme: false,
// 当前主题样式
appTheme: 'light', appTheme: 'light',
// 多标签页开关
menuTagSwitch: true,
// 面包屑开关
breadcrumbSwitch: true,
// 默认国际化语言 // 默认国际化语言
localeLanguage: getAppDefaultLanguage(), localeLanguage: getAppDefaultLanguage(),
// 锁屏开关 // 锁屏开关
lockScreenSwitch: false, lockScreenSwitch: false,
// 底部区域开关
copyrightSwitch: true,
// 切换过渡效果
contentTransition: 'scale',
// 水印开关
watermarkSwitch: false,
// 水印
watermarkConfig: {
content: 'Trying be better~',
fontSize: 16,
lineHeight: 16,
width: 384,
height: 384,
xOffset: 12,
xGap: 0,
yGap: 0,
yOffset: 60,
rotate: -15,
cross: true,
},
// 根路由信息 // 根路由信息
appRootRoute: { appRootRoute: {
name: 'Dashboard', name: 'Dashboard',
@ -48,7 +70,29 @@ export const piniaSettingStore = defineStore(
url: '/dashboard', url: '/dashboard',
jumpType: 'station', jumpType: 'station',
}, },
...cloneDeep(getDefaultSettingConfig()), // 缓存动态设置
keepAliveConfig: {
setupKeepAlive: true,
keepAliveExclude: [],
maxKeepAliveLength: 10,
},
// 菜单配置
menuConfig: {
collapsedWidth: 64,
collapsedMode: 'width',
collapsedIconSize: 16,
collapsedIndent: 24,
accordion: false,
menuSiderBarLogo: true,
iconSize: 16,
menuWidth: 272,
inverted: false,
nativeScrollbar: false,
},
// 色弱模式
colorWeakness: false,
// 动态标题
dynamicDocumentTitle: true,
}) })
// 修改当前语言 // 修改当前语言
@ -65,13 +109,12 @@ export const piniaSettingStore = defineStore(
* @description * @description
* naive-ui * naive-ui
*/ */
const changePrimaryColor = (value: string, alpha = 0.85) => { const changePrimaryColor = (value: string, alpha = 0.3) => {
const alphaColor1 = colorToRgba(value, alpha) const alphaColor = colorToRgba(value, alpha)
const themeOverrides = { const themeOverrides = {
primaryColor: value, primaryColor: value,
primaryColorHover: alphaColor1, primaryColorHover: value,
primaryColorPressed: value, primaryColorPressed: value,
primaryColorSuppl: alphaColor1,
} }
const { rayTemplateThemePrimaryColor, rayTemplateThemePrimaryFadeColor } = const { rayTemplateThemePrimaryColor, rayTemplateThemePrimaryFadeColor } =
GLOBAL_CLASS_NAMES GLOBAL_CLASS_NAMES
@ -82,7 +125,7 @@ export const piniaSettingStore = defineStore(
// 设置主题色变量 // 设置主题色变量
html.style.setProperty(rayTemplateThemePrimaryColor, value) html.style.setProperty(rayTemplateThemePrimaryColor, value)
// 设置主题色辅助色变量 // 设置主题色辅助色变量
html.style.setProperty(rayTemplateThemePrimaryFadeColor, alphaColor1) html.style.setProperty(rayTemplateThemePrimaryFadeColor, alphaColor)
} }
/** /**
@ -107,7 +150,7 @@ export const piniaSettingStore = defineStore(
value: Partial<V[T]>, value: Partial<V[T]>,
cb?: C, cb?: C,
) => { ) => {
updateObjectValue(settingState, key, value as V[T], cb) updateObjectValue(settingState, key, value, cb)
} }
const toggleColorWeakness = (bool: boolean) => { const toggleColorWeakness = (bool: boolean) => {
@ -126,16 +169,18 @@ export const piniaSettingStore = defineStore(
* *
*/ */
watchEffect(() => { watchEffect(() => {
settingState._appTheme settingState.appTheme
? (settingState.primaryColorOverride = merge( ? (settingState.primaryColorOverride = Object.assign(
{}, {},
settingState.primaryColorOverride, settingState.primaryColorOverride,
APP_THEME.appNaiveUIThemeOverrides.dark, APP_THEME.appNaiveUIThemeOverrides.dark,
APP_THEME.appNaiveUIThemeOverridesCommon.dark,
)) ))
: (settingState.primaryColorOverride = merge( : (settingState.primaryColorOverride = Object.assign(
{}, {},
settingState.primaryColorOverride, settingState.primaryColorOverride,
APP_THEME.appNaiveUIThemeOverrides.light, APP_THEME.appNaiveUIThemeOverrides.light,
APP_THEME.appNaiveUIThemeOverridesCommon.light,
)) ))
toggleColorWeakness(settingState.colorWeakness) toggleColorWeakness(settingState.colorWeakness)

View File

@ -22,7 +22,6 @@ import type {
export const piniaSigningStore = defineStore( export const piniaSigningStore = defineStore(
'signing', 'signing',
() => { () => {
const { clearRoutes, replace } = useRouter()
const state = reactive({ const state = reactive({
/** /**
* *
@ -91,13 +90,11 @@ export const piniaSigningStore = defineStore(
removeStorage(token, 'localStorage') removeStorage(token, 'localStorage')
removeStorage(signing, 'localStorage') removeStorage(signing, 'localStorage')
removeStorage(appMenuKey, 'localStorage') removeStorage(appMenuKey, 'localStorage')
removeStorage(APP_CATCH_KEY.isAppLockScreen, 'localStorage')
// 关闭所有侧边栏标签 // 关闭所有侧边栏标签
closeAll() closeAll()
if (toSigning) { if (toSigning) {
clearRoutes() window.location.replace('#/')
replace('/')
setTimeout(() => { setTimeout(() => {
window.location.reload() window.location.reload()

View File

@ -22,27 +22,21 @@
} }
/* scale-transform */ /* scale-transform */
.scale-transform-enter-active { .scale-transform-enter-active,
transition:
transform 0.35s cubic-bezier(0.33, 1, 0.68, 1),
opacity 0.25s cubic-bezier(0.33, 1, 0.68, 1);
transition-delay: 0.25s;
}
.scale-transform-leave-active { .scale-transform-leave-active {
transition: transition:
transform 0.35s cubic-bezier(0.32, 0, 0.67, 0), transform 0.4s var(--r-bezier),
opacity 0.25s ease-out; opacity 0.45s var(--r-bezier);
} }
.scale-transform-enter-from { .scale-transform-enter-from {
opacity: 0; opacity: 0;
transform: scale(0.95); transform: scale(0.92);
} }
.scale-transform-leave-to { .scale-transform-leave-to {
opacity: 0; opacity: 0;
transform: scale(1.03); transform: scale(1.06);
} }
/* opacity-transform */ /* opacity-transform */
@ -60,49 +54,35 @@
} }
/* fade-bottom-transform */ /* fade-bottom-transform */
.fade-bottom-transform-enter-active { .fade-bottom-transform-enter-active,
transition:
transform 0.35s cubic-bezier(0.33, 1, 0.68, 1),
opacity 0.25s cubic-bezier(0.33, 1, 0.68, 1);
transition-delay: 0.25s;
}
.fade-bottom-transform-leave-active { .fade-bottom-transform-leave-active {
transition: transition:
transform 0.35s cubic-bezier(0.32, 0, 0.67, 0), opacity 0.55s var(--r-bezier),
opacity 0.25s ease-out; transform 0.45s var(--r-bezier);
} }
.fade-bottom-transform-enter-from { .fade-bottom-transform-enter-from {
opacity: 0; opacity: 0;
transform: translateY(-15px); transform: translateY(-10%);
} }
.fade-bottom-transform-leave-to { .fade-bottom-transform-leave-to {
opacity: 0; opacity: 0;
transform: translateY(15px); transform: translateY(10%);
} }
/* fade-scale-transform */ /* fade-scale-transform */
.fade-scale-transform-leave-active,
.fade-scale-transform-enter-active { .fade-scale-transform-enter-active {
transition: transition: all 0.48s var(--r-bezier);
transform 0.35s cubic-bezier(0.33, 1, 0.68, 1),
opacity 0.25s cubic-bezier(0.33, 1, 0.68, 1);
transition-delay: 0.25s;
}
.fade-scale-transform-leave-active {
transition:
transform 0.25s cubic-bezier(0.32, 0, 0.67, 0),
opacity 0.25s ease-out;
} }
.fade-scale-transform-enter-from { .fade-scale-transform-enter-from {
opacity: 0; opacity: 0;
transform: scale(1.1); transform: scale(1.2);
} }
.fade-scale-transform-leave-to { .fade-scale-transform-leave-to {
opacity: 0; opacity: 0;
transform: scale(0.85); transform: scale(0.8);
} }

View File

@ -1,7 +1,7 @@
@use '@/styles/animate.scss' as *; @import '@/styles/animate.scss';
@use '@/styles/root.scss' as *; @import '@/styles/root.scss';
@use '@/styles/naive.scss' as *; @import '@/styles/naive.scss';
@use '@/styles/print-css.scss' as *; @import '@/styles/print-css.scss';
body, body,
h1, h1,

18
src/types/app.d.ts vendored
View File

@ -2,9 +2,27 @@
export {} export {}
import 'vue-router'
import type { AppRouteMeta } from '@/router/types'
declare module 'vue-router' {
interface RouteMeta extends AppRouteMeta {}
}
declare module '*.vue' { declare module '*.vue' {
import type { DefineComponent } from 'vue' import type { DefineComponent } from 'vue'
const Component: DefineComponent<{}, {}, any> const Component: DefineComponent<{}, {}, any>
export default Component export default Component
} }
declare module 'virtual:*' {
const result: any
export default result
}
declare module '*.json' {
const jsonContent: Record<string, any>
export default jsonContent
}

8
src/types/json.d.ts vendored
View File

@ -1,8 +0,0 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
export {}
declare module '*.json' {
const jsonContent: Record<string, any>
export default jsonContent
}

View File

@ -1,3 +1,4 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
export interface BasicResponse<T> { export interface BasicResponse<T> {
data: T data: T
msg: string msg: string

View File

@ -83,4 +83,8 @@ export interface AppTheme {
light: GlobalThemeOverrides light: GlobalThemeOverrides
} }
echartTheme: string echartTheme: string
appNaiveUIThemeOverridesCommon: {
dark: GlobalThemeOverrides['common']
light: GlobalThemeOverrides['common']
}
} }

View File

@ -1,13 +0,0 @@
export {}
import type {
RouteLocationNormalizedGeneric,
NavigationGuardNext,
} from 'vue-router'
// 为 defineComponent 添加自定义选项
// https://cn.vuejs.org/guide/typescript/options-api.html#augmenting-custom-options
declare module 'vue' {
// eslint-disable-next-line @typescript-eslint/no-empty-interface, @typescript-eslint/no-empty-object-type
interface ComponentCustomOptions {}
}

View File

@ -1,12 +0,0 @@
export {}
import type { useRoute, useRouter } from 'vue-router'
// 为 vue 添加一些自定义属性
// https://cn.vuejs.org/guide/typescript/options-api#augmenting-global-properties
declare module 'vue' {
interface ComponentCustomProperties {
$router: ReturnType<typeof useRouter>
$route: ReturnType<typeof useRoute>
}
}

View File

@ -1,9 +0,0 @@
export {}
import 'vue-router'
import type { AppRouteMeta } from '@/router/types'
declare module 'vue-router' {
interface RouteMeta extends AppRouteMeta {}
}

View File

@ -1,8 +0,0 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
export {}
declare module 'virtual:*' {
const result: any
export default result
}

View File

@ -209,32 +209,6 @@ export const downloadAnyFile = (
}) })
} }
/**
*
* @param file file object
*
* @description
* File base64
*
* @example
* const base64 = await fileToBase64(file) // 'data:image/png;base64,...'
*/
export const fileToBase64 = (file: File) => {
return new Promise<string>((resolve, reject) => {
const reader = new FileReader()
reader.onload = () => {
resolve(reader.result as string)
}
reader.onerror = (error) => {
reject(error)
}
reader.readAsDataURL(file)
})
}
/** /**
* *
* @param func * @param func
@ -280,7 +254,7 @@ export const isPromise = <T>(value: unknown): value is Promise<T> => {
/** /**
* *
* @param fn * @param fc
* @param errorCallback * @param errorCallback
* @param args * @param args
* *
@ -292,14 +266,14 @@ export const isPromise = <T>(value: unknown): value is Promise<T> => {
* callWithErrorHandling((x: number) => { throw new Error('error') }, (error) => { console.log(error) }, [123]) => undefined * callWithErrorHandling((x: number) => { throw new Error('error') }, (error) => { console.log(error) }, [123]) => undefined
*/ */
export const callWithErrorHandling = <T extends AnyFC, E extends Error>( export const callWithErrorHandling = <T extends AnyFC, E extends Error>(
fn: T, fc: T,
errorCallback: AnyFC<E, void>, errorCallback: AnyFC<E, void>,
args?: Parameters<T>, args?: Parameters<T>,
) => { ) => {
let result: ReturnType<T> | undefined let result: ReturnType<T> | undefined
try { try {
result = args ? fn(...args) : fn() result = args ? fc(...args) : fc()
} catch (error) { } catch (error) {
errorCallback(error as E) errorCallback(error as E)
} }
@ -325,16 +299,16 @@ export const callWithAsyncErrorHandling = async <
T extends AnyFC, T extends AnyFC,
E extends Error, E extends Error,
>( >(
fn: T, fc: T,
errorCallback: (error: E) => void, errorCallback: (error: E) => void,
args?: Parameters<T>, args?: Parameters<T>,
): Promise<ReturnType<T> | undefined> => { ): Promise<ReturnType<T> | undefined> => {
try { try {
if (!isPromise(fn)) { if (!isPromise(fc)) {
return Promise.resolve(callWithErrorHandling(fn, errorCallback, args)) return Promise.resolve(callWithErrorHandling(fc, errorCallback, args))
} }
return await fn(...(args as Parameters<T>)) return await fc(...(args as Parameters<T>))
} catch (error) { } catch (error) {
errorCallback(error as E) errorCallback(error as E)

View File

@ -1,6 +0,0 @@
/**
*
* @description
* key
*/
export const CRYPTO_KEY = '4cP+dX5FI2EVYzln'

View File

@ -1,32 +0,0 @@
import { AES, enc } from 'crypto-js'
import { CRYPTO_KEY } from './constant'
import type { CipherParams, WordArray } from './types'
/**
*
* @param data AES
* @param key key
*
* @description
* 使 AES
*
* @example
* const data = 'U2FsdGVkX1+3Q
* const key = CRYPTO_KEY
*
* const decrypted = decrypt(data, key) // { name: 'John Doe' }
*/
export const decrypt = (
data: string | CipherParams,
key?: string | WordArray,
) => {
try {
const decrypted = AES.decrypt(data, key || CRYPTO_KEY)
const decryptedData = decrypted.toString(enc.Utf8)
return JSON.parse(decryptedData)
} catch (e) {
console.error(`Unknown error: ${e}`)
}
}

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