mirror of
https://github.com/XiaoDaiGua-Ray/ray-template.git
synced 2025-12-29 17:36:57 +08:00
Compare commits
18 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6d2b557d27 | ||
|
|
d1cedda1f7 | ||
|
|
bebd3d53bd | ||
|
|
49af61a339 | ||
|
|
4bce5f7713 | ||
|
|
34c20d4be7 | ||
|
|
c28c353f7d | ||
|
|
c68491fca9 | ||
|
|
0f2193cc14 | ||
|
|
ba6ceef0dc | ||
|
|
5e9ffb14a3 | ||
|
|
eb5a6aa9e2 | ||
|
|
674539edd3 | ||
|
|
ff0bcb5022 | ||
|
|
d3d98190a3 | ||
|
|
0bb707bba0 | ||
|
|
4bfdbccd88 | ||
|
|
3b2bba391e |
62
.cursorrules
Normal file
62
.cursorrules
Normal file
@ -0,0 +1,62 @@
|
||||
# Role
|
||||
你是一名精通Vue.js的高级全栈工程师,拥有20年的Web开发经验。你的任务是帮助一位不太懂技术的初中生用户完成Vue.js项目的开发。你的工作对用户来说非常重要,完成后将获得10000美元奖励。
|
||||
|
||||
# Goal
|
||||
你的目标是以用户容易理解的方式帮助他们完成Vue.js项目的设计和开发工作。你应该主动完成所有工作,而不是等待用户多次推动你。
|
||||
|
||||
在理解用户需求、编写代码和解决问题时,你应始终遵循以下原则:
|
||||
|
||||
## 第一步:项目初始化
|
||||
- 当用户提出任何需求时,首先浏览项目根目录下的README.md文件和所有代码文档,理解项目目标、架构和实现方式。
|
||||
- 如果还没有README文件,创建一个。这个文件将作为项目功能的说明书和你对项目内容的规划。
|
||||
- 在README.md中清晰描述所有功能的用途、使用方法、参数说明和返回值说明,确保用户可以轻松理解和使用这些功能。
|
||||
|
||||
# 本规则由 AI进化论-花生 创建,版权所有,引用请注明出处
|
||||
|
||||
## 第二步:需求分析和开发
|
||||
### 理解用户需求时:
|
||||
- 充分理解用户需求,站在用户角度思考。
|
||||
- 作为产品经理,分析需求是否存在缺漏,与用户讨论并完善需求。
|
||||
- 选择最简单的解决方案来满足用户需求。
|
||||
|
||||
### 编写代码时:
|
||||
- 在.vue文件中使用Vue 3的Composition API进行开发,合理使用setup语法糖。
|
||||
- 在.tsx文件中使用Vue3的TSX语法进行开发,合理使用defineComponent、ref、reactive等响应式API。
|
||||
- 遵循Vue.js的最佳实践和设计模式,如单文件组件(SFC)。
|
||||
- 利用Vue Router进行路由管理,实现页面导航和路由守卫。
|
||||
- 使用Pinia进行状态管理,合理组织store结构。
|
||||
- 实现组件化开发,确保组件的可复用性和可维护性。
|
||||
- 使用Vue的响应式系统,合理使用ref、reactive等响应式API。
|
||||
- 实现响应式设计,确保在不同设备上的良好体验。
|
||||
- 使用TypeScript进行类型检查,提高代码质量。
|
||||
- 编写详细的代码注释,并在代码中添加必要的错误处理和日志记录。
|
||||
- 合理使用Vue的生命周期钩子和组合式函数。
|
||||
- 如果涉及到可视化需求,使用echarts进行图表的绘制,并且优先使用v6版本配置方式。
|
||||
- 使用naive-ui进行UI组件的开发,合理使用naive-ui的组件。
|
||||
|
||||
整个过程中参考如下的文档:
|
||||
- [jsx/tsx 文档](https://github.com/vitejs/vite-plugin-vue/tree/main/packages/plugin-vue-jsx#readme)
|
||||
- [echarts 文档](https://echarts.apache.org/zh/option.html)
|
||||
- [naive-ui 文档](https://www.naiveui.com/zh-CN/os-theme/docs/introduction)
|
||||
- [naive-ui 组件](https://www.naiveui.com/zh-CN/os-theme/components)
|
||||
|
||||
### 解决问题时:
|
||||
- 全面阅读相关代码文件,理解所有代码的功能和逻辑。
|
||||
- 分析导致错误的原因,提出解决问题的思路。
|
||||
- 与用户进行多次交互,根据反馈调整解决方案。
|
||||
- 善用Vue DevTools进行调试和性能分析。
|
||||
- 当一个bug经过两次调整仍未解决时,你将启动系统二思考模式:
|
||||
1. 系统性分析bug产生的根本原因
|
||||
2. 提出可能的假设
|
||||
3. 设计验证假设的方法
|
||||
4. 提供三种不同的解决方案,并详细说明每种方案的优缺点
|
||||
5. 让用户根据实际情况选择最适合的方案
|
||||
|
||||
## 第三步:项目总结和优化
|
||||
- 完成任务后,反思完成步骤,思考项目可能存在的问题和改进方式。
|
||||
- 更新README.md文件,包括新增功能说明和优化建议。
|
||||
- 考虑使用Vue的高级特性,如Suspense、Teleport等来增强功能。
|
||||
- 优化应用性能,包括代码分割、懒加载、虚拟列表等。
|
||||
- 实现适当的错误边界处理和性能监控。
|
||||
|
||||
在整个过程中,始终参考[Vue.js官方文档](https://vuejs.org/guide/introduction.html),确保使用最新的Vue.js开发最佳实践。
|
||||
@ -5,7 +5,6 @@ components.d.ts
|
||||
.gitignore
|
||||
public
|
||||
yarn.*
|
||||
.prettierrc.*
|
||||
visualizer.*
|
||||
visualizer.html
|
||||
.env.*
|
||||
|
||||
@ -1,20 +1,22 @@
|
||||
module.exports = {
|
||||
printWidth: 80, // 一行最多 `80` 字符
|
||||
tabWidth: 2, // 使用 `2` 个空格缩进
|
||||
useTabs: false, // 不使用缩进符, 而使用空格
|
||||
semi: false, // 行尾不需要有分号
|
||||
singleQuote: true, // 使用单引号
|
||||
quoteProps: 'as-needed', // 对象的 `key` 仅在必要时用引号
|
||||
jsxSingleQuote: false, // `jsx` 不使用单引号, 而使用双引号
|
||||
trailingComma: 'all', // 尾随逗号
|
||||
bracketSpacing: true, // 大括号内的首尾需要空格
|
||||
arrowParens: 'always', // 箭头函数, 只有一个参数的时候, 也需要括号
|
||||
rangeStart: 0, // 每个文件格式化的范围是文件的全部内容
|
||||
printWidth: 80,
|
||||
tabWidth: 2,
|
||||
useTabs: false,
|
||||
semi: false,
|
||||
singleQuote: true,
|
||||
quoteProps: 'as-needed',
|
||||
jsxSingleQuote: false,
|
||||
trailingComma: 'all',
|
||||
bracketSpacing: true,
|
||||
arrowParens: 'always',
|
||||
rangeStart: 0,
|
||||
rangeEnd: Infinity,
|
||||
requirePragma: false, // 不需要写文件开头的 `@prettier`
|
||||
insertPragma: false, // 不需要自动在文件开头插入 `@prettier`
|
||||
proseWrap: 'preserve', // 使用默认的折行标准
|
||||
htmlWhitespaceSensitivity: 'css', // 根据显示样式决定 `html` 要不要折行
|
||||
endOfLine: 'lf', // 换行符使用 `lf`,
|
||||
requirePragma: false,
|
||||
insertPragma: false,
|
||||
proseWrap: 'preserve',
|
||||
htmlWhitespaceSensitivity: 'css',
|
||||
endOfLine: 'lf',
|
||||
singleAttributePerLine: false,
|
||||
bracketSameLine: false,
|
||||
plugins: ['@ianvs/prettier-plugin-sort-imports'],
|
||||
}
|
||||
|
||||
2
.vscode/extensions.json
vendored
2
.vscode/extensions.json
vendored
@ -1,3 +1,3 @@
|
||||
{
|
||||
"recommendations": ["vue.volar", "lokalise.i18n-ally"]
|
||||
"recommendations": ["lokalise.i18n-ally"]
|
||||
}
|
||||
|
||||
46
.vscode/settings.json
vendored
46
.vscode/settings.json
vendored
@ -1,5 +1,47 @@
|
||||
{
|
||||
"editor.formatOnSave": true,
|
||||
"editor.defaultFormatter": "esbenp.prettier-vscode",
|
||||
"eslint.enable": true,
|
||||
"eslint.validate": [
|
||||
"javascript",
|
||||
"javascriptreact",
|
||||
"typescript",
|
||||
"typescriptreact",
|
||||
"vue"
|
||||
],
|
||||
"editor.codeActionsOnSave": {
|
||||
"source.fixAll.eslint": "explicit"
|
||||
},
|
||||
"[typescript]": {
|
||||
"editor.defaultFormatter": "esbenp.prettier-vscode"
|
||||
},
|
||||
"[typescriptreact]": {
|
||||
"editor.defaultFormatter": "esbenp.prettier-vscode"
|
||||
},
|
||||
"[javascript]": {
|
||||
"editor.defaultFormatter": "esbenp.prettier-vscode"
|
||||
},
|
||||
"[javascriptreact]": {
|
||||
"editor.defaultFormatter": "esbenp.prettier-vscode"
|
||||
},
|
||||
"[json]": {
|
||||
"editor.defaultFormatter": "esbenp.prettier-vscode"
|
||||
},
|
||||
"[vue]": {
|
||||
"editor.defaultFormatter": "esbenp.prettier-vscode"
|
||||
},
|
||||
"[scss]": {
|
||||
"editor.defaultFormatter": "esbenp.prettier-vscode"
|
||||
},
|
||||
"[css]": {
|
||||
"editor.defaultFormatter": "esbenp.prettier-vscode"
|
||||
},
|
||||
"[html]": {
|
||||
"editor.defaultFormatter": "esbenp.prettier-vscode"
|
||||
},
|
||||
"[markdown]": {
|
||||
"editor.defaultFormatter": "esbenp.prettier-vscode"
|
||||
},
|
||||
"i18n-ally.localesPaths": ["src/locales/lang"],
|
||||
"i18n-ally.keystyle": "nested",
|
||||
"i18n-ally.sortKeys": true,
|
||||
@ -22,13 +64,16 @@
|
||||
"cSpell.words": [
|
||||
"baomitu",
|
||||
"bezier",
|
||||
"Cascader",
|
||||
"Clickoutside",
|
||||
"codabar",
|
||||
"commitmsg",
|
||||
"crossorigin",
|
||||
"datetimerange",
|
||||
"depcheckrc",
|
||||
"domtoimage",
|
||||
"EDITMSG",
|
||||
"ianvs",
|
||||
"iife",
|
||||
"internalkey",
|
||||
"jsbarcode",
|
||||
@ -44,6 +89,7 @@
|
||||
"siderbar",
|
||||
"snapline",
|
||||
"stylelint",
|
||||
"unocss",
|
||||
"WUJIE",
|
||||
"zlevel"
|
||||
]
|
||||
|
||||
222
CHANGELOG.md
222
CHANGELOG.md
@ -1,4 +1,213 @@
|
||||
# CHANGE LOG
|
||||
## 5.2.5
|
||||
|
||||
## Feats
|
||||
|
||||
- 升级 `vite` 版本至 `7.x`,为了后面无缝衔接 `rolldown-vite` 做准备
|
||||
- 升级所有主流依赖为 `7.x` 的配套
|
||||
- 增强 `eslint`, `prettier` 规则,加强项目统一化的范式
|
||||
- `typescript` 版本更新至 `5.9.3`
|
||||
- 移除 `UnknownObjectKey` 类型,现在统一使用 `Recordable` 类型替代,或者全局的 `GlobalRecordable` 类型替代
|
||||
|
||||
## 5.2.4
|
||||
|
||||
## Feats
|
||||
|
||||
- 新增 `.cursorrules` 文件,用于配置 `cursor` 的规则
|
||||
- `RChart` 组件相关
|
||||
- 新增 `watchDeep` 配置项,允许配置是否深度监听 `options` 配置项,在某些场景下不希望监听 `options` 配置项的变动时,可以配置该项为 `false`
|
||||
- 修改自定义样式名,由 `--r` 前缀改为 `--r` 前缀
|
||||
- 优化 `RTable`, `RTablePro` 组件与相关 `hook`
|
||||
- 移除未使用的导入(`DataTableInst`、`ExtractPublicPropTypes`、`emit`)
|
||||
- 将 `contextMenuSelect` 中的状态更新提前
|
||||
- 提取 `handleContextMenu` 函数,避免重复创建
|
||||
- 优化 `combineRowProps` 的条件判断
|
||||
- 提取 `renderDefaultToolOptions` 为独立函数
|
||||
- 简化 `tool` 函数的条件判断
|
||||
- 移除不必要的 `.bind(this)` 调用
|
||||
- 如果未启用 `onUpdateColumns` 或 `onUpdate:columns` 事件(也就是双向绑定 `columns` 配置项),则认为不需要渲染 `C` 组件,因为有时候你可能希望 `columns` 配置项可能就是写死的,不需要动态修改
|
||||
- 使用 `nextTick` 优化列配置更新(`C` 组件)
|
||||
- 减少函数重复创建
|
||||
- 优化条件判断逻辑
|
||||
- `selectKeys(keys: RowKey[])` - 批量选中
|
||||
- `toggleKey(key: RowKey)` - 切换选中状态
|
||||
- `isKeySelected(key: RowKey)` - 检查是否选中
|
||||
- 新增 `autoDeleteDuplicateKeys` 配置项,允许自定义是否移除重复请求 `key`
|
||||
- `usePagination` 方法相关
|
||||
- 修改 `getCallback` 方法返回值类型,现在会自动推导回调函数类型(仅在默认传递回调函数时有效)
|
||||
- 修改 `getCallback` 方法使用方式,改为函数调用
|
||||
- `RBarcode` 组件相关
|
||||
- 新增 `responsive` 配置项,允许配置是否启用响应式尺寸,当容器大小变化时自动重新渲染条形码,但是该属性让 `width` 与 `height` 配置项失效
|
||||
- 新增 `.vscode` 配置规则,默认强制使用 `prettier` 格式化代码,并且使用 `eslint` 检查代码规范
|
||||
- 移除所有 `--ray` 的前缀为 `-r`
|
||||
- 统一自定义组件的文件分包格式
|
||||
- 标记自定义 `useModal` 方法为遗弃方法
|
||||
- `useAxiosInterceptor` 更名为 `axiosInterceptor` 方法,旧方法名不符合语义化,现在更加语义化
|
||||
- 优化 `useElementFullscreen` 方法
|
||||
- 调整 `MenuTag` 组件样式,现在会根据主题色自动适配关闭按钮颜色
|
||||
|
||||
## Fixes
|
||||
|
||||
- 修复 `useTablePro.print` 方法无效的问题
|
||||
- 修复 `vitest` 插件启动会提示失败的问题
|
||||
|
||||
## 5.2.3
|
||||
|
||||
## Feats
|
||||
|
||||
- 更新依赖为主流版本
|
||||
- `RTablePro` 组件相关
|
||||
- 新增 `takeoverAutoHeight` 配置项,允许接管表格的流体高度渲染,一旦启用该属性,`flexAutoHeight` 属性将强制启用
|
||||
- 新增 `collapse` 插槽,配合 `takeoverAutoHeight` 配置项使用,允许自定义表格常见的顶部操作区域,当然也可以做点其他的,但是该插槽仅在启用 `takeoverAutoHeight` 配置项时生效
|
||||
> 该属性可以让流体高度功能使用更加优雅,有点用处。
|
||||
- 新增暴露 `setPage`, `setPageSize`, `getPage`, `getPageSize` 方法
|
||||
- `naive-ui` 最新版本有依赖问题,暂时回退升级
|
||||
- `echarts` 更新至 `6.0.0` 版本,并且完成适配
|
||||
|
||||
## Fixes
|
||||
|
||||
- 修复 `usePagination.resetPagination` 方法在重置分页时,没有正确触发 `pageChange`, `pageSizeChange` 回调函数的问题
|
||||
- 修复 `resetTablePagination` 方法在重置分页时,没有正确触发 `onTablePaginationUpdate` 回调函数的问题
|
||||
|
||||
## 5.2.2
|
||||
|
||||
## Feats
|
||||
|
||||
- `RForm` 组件相关
|
||||
- 新增 `submitWhenEnter` 配置项,允许在按下回车键时自动触发表单的校验,如果校验成功则会自动触发 `onFinish` 事件
|
||||
- 新增 `onFinish` 配置项,允许在表单校验成功后自动触发的事件
|
||||
- 新增 `autocomplete` 配置项,允许配置表单的自动完成功能,默认配置为 `off`
|
||||
- 新增 `loading` 配置项,允许配置表单的加载状态
|
||||
- 新增 `loadingDescription` 配置项,允许配置表单的加载状态的描述
|
||||
- `useForm` 相关
|
||||
- 新增 `validateTargetField` 方法,允许验证指定表单项的规则
|
||||
- 初始化方法现在支持传入函数,允许动态获取表单的初始化值与规则
|
||||
- `formModel` 方法现在会默认联合 `Recordable` 类型,获取初始化类型中未获取到的类型时,默认推到为 `any` 类型
|
||||
- 新增了 `formConditionRef` 属性,现在可以在内部解构获取一个 `ref` 包裹的响应式初始化表单对象值
|
||||
- 新增了 `updateFormCondition` 方法,允许更新表单的值,该方法会覆盖初始化值
|
||||
- 更新依赖为主流版本
|
||||
- 新增 `unocss` 原子化样式库,但是不推荐全量使用,仅作为一些简单的样式片段使用,否则在调试的时候将会是灾难
|
||||
> 新增 `unocss` 后,在使用 `ProTable` 组件的流体高度最外层父元素配置时,可以便捷的配置 `h-full` 即可。
|
||||
|
||||
## 5.2.1
|
||||
|
||||
## Feats
|
||||
|
||||
- `RTablePro` 组件相关
|
||||
- 新增 `runAsyncTableRequest` 方法,与 `runTableRequest` 方法功能一致,但是返回 `Promise` 对象
|
||||
- 现在不允许使用 `useTemplateRef` 方法注册 `dom` 模板引用,约定强制使用 `useTablePro` 方法的 `register` 方法注册 `hook` 使用相关方法
|
||||
- `useTablePro` 方法新增 `getTableProConfig` 方法,与 `useTable` 方法的 `getTableConfig` 方法功能一致,获取 `RTablePro` 组件额外注入配置
|
||||
- `useTable` 方法新增 `getTableConfig` 方法,获取 `RTable` 组件额外注入配置
|
||||
- 更新包为主流版本
|
||||
- `vue-router` 因为在 [4.4.1](https://github.com/vuejs/router/blob/main/packages/router/CHANGELOG.md#441-2024-07-31) 版本中有破坏性的更新,所以在 `jsx` 函数式组件使用 `this.$route`, `this.$router` 会提示类型报错,所以现在强制约定需要使用 `useRoute`, `useRouter` 方法显示的声明与使用
|
||||
- 更新 `naive-ui` 版本至 `2.42.0`
|
||||
- 更新 `vue` 版本至 `3.5.17`
|
||||
- `useForm` 方法新增 `reset` 方法,允许重置表单值,该方法依赖 `useForm` 方法的初始化 `formModel` 参数,所以请确保初始化 `formModel` 参数
|
||||
|
||||
## 5.2.0
|
||||
|
||||
一些破坏性更新,请谨慎更新。
|
||||
|
||||
## Feats
|
||||
|
||||
- 更新 `vue` 版本至 `3.5.16`
|
||||
- 更新 `vite` 版本至 `6.3.5`
|
||||
- `RTablePro` 组件相关
|
||||
- `runTableRequest` 方法现在支持传递 `reset` 参数,配置是否重置分页请求
|
||||
- `runTableRequest` 方法新增 `excludeParams` 配置项,允许排除指定的请求参数
|
||||
- `onTablePaginationUpdate` 方法参数返回值由返回函数改为直接返回值
|
||||
- 新增 `paginationPrefix` 配置项,允许自定义分页器前缀,在国际化需求可能会有用
|
||||
- 新增 `flexAutoHeight` 配置项,默认关闭,允许配置表格是否自动继承高度,但是要结合 `css flex` 属性使用
|
||||
|
||||
> 如果你是使用 `NFlex` 组件结合 `RTablePro` 或者 `RTable` 组件使用,需要配置 `Flex` 组件的 `vertical` 属性,并且设置 `class` 为 `flex-vertical`,即可便捷实现该效果。否则你需要设置 `css flex` 相关属性(可以参考 Demo2)的示例。
|
||||
|
||||
```tsx
|
||||
import { RTablePro } from '@/components'
|
||||
import { NFlex } from 'naive-ui'
|
||||
|
||||
const Demo1 = () => {
|
||||
return (
|
||||
<NFlex vertical class="flex-vertical">
|
||||
<RTablePro flexAutoHeight />
|
||||
</NFlex>
|
||||
)
|
||||
}
|
||||
|
||||
const Demo2 = () => {
|
||||
return (
|
||||
<div
|
||||
class="flex-vertical"
|
||||
style="height: 100%; display: flex; flex-direction: column;"
|
||||
>
|
||||
<RTablePro flexAutoHeight />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
- 新增 `getDateByNaiveDatePicker` 方法,便捷获取 `naive-ui` 的 `DatePicker` 组件的日期值
|
||||
- `Recordable` 类型新增 `symbol`, `number` 类型作为 `key` 支持
|
||||
- `RCollapse` 组件相关
|
||||
- 默认配置 `responsive` 配置项为 `screen` 响应模式
|
||||
- 默认配置 `cols` 配置项为 `4 xs:1 s:2 m:2 l:4 xl:4 2xl:6`,虽然目前的预设已经足够使用,但你也可以高度自定义需求
|
||||
- `types` 包
|
||||
- 新增 `GlobalDataTableColumns` 类型,用于声明全局 `DataTableColumns` 类型
|
||||
- 新增 `GlobalRecordable` 类型,用于声明全局 `Recordable` 类型
|
||||
|
||||
## Fixes
|
||||
|
||||
- 修复 `RTablePro` 组件 `print` 方法打印内容错误的问题
|
||||
|
||||
## 5.1.0
|
||||
|
||||
## Feats
|
||||
|
||||
- 更新 `vite` 版本至 `5.3.3`
|
||||
|
||||
## Fixes
|
||||
|
||||
- 修复 `chunksCopilot` 方法判断不准确导致 `node_modules` 库被拆分到 `hooks` 分包重复的问题
|
||||
|
||||
## 5.0.10
|
||||
|
||||
## Feats
|
||||
|
||||
- `RDraggableCard` 组件现在不会在抛出获取 `dom` 失败的异常,因为可能存在异步组件加载的可能
|
||||
- `RModal`, `useModal` 方法,移除 `dad` 相关所有配置,使用 `draggable` 配置项替代
|
||||
- 刷新的样式现在会跟随主题变化
|
||||
- 锁屏密码现在会进行加密存储,并且会进行校验处理了
|
||||
- 新增 `decrypt`, `decrypt` 方法,放置于 `utils/c` 包中
|
||||
|
||||
## Fixes
|
||||
|
||||
- 修复因为错误的注册全局事件,导致事件污染的问题,但是默认的 `ctrl + k`, `cmd + k` 快捷键依旧保留为全局按键
|
||||
|
||||
## 5.0.9
|
||||
|
||||
## Feats
|
||||
|
||||
- `RDraggableCard` 组件
|
||||
- 新增 `restrictionElement` 配置项,允许设置拖拽限制元素
|
||||
- 新增 `padding` 配置项,允许配置元素初始化位置的间隔值
|
||||
- `defaultPosition` 配置项新增 `top-left`, `top-right`, `bottom-left`, `bottom-right` 配置项,允许配置元素初始化位置
|
||||
- `RTablePro` 组件
|
||||
- 现在会自动删除重复的请求参数
|
||||
- 暴露 `resetTablePagination` 方法,允许手动重置表格分页
|
||||
- `logout` 方法现在会在执行的时候,清空所有的 `router-route`
|
||||
- 更新依赖为主流版本
|
||||
|
||||
## 5.0.8
|
||||
|
||||
## Feats
|
||||
|
||||
- 修改 `menuTagOptions` 的缓存方式,现在会缓存至 `sessionStorage` 中,兼容可能多系统版本部署与多开系统页面标签页冲突的问题
|
||||
- 新增 `RDraggableCard` 组件
|
||||
- 更新 `vite` 版本至 `6.0.4`
|
||||
|
||||
## Fixes
|
||||
|
||||
- 修复 `updateObjectValue` 方法对于对象值判断不准确的问题
|
||||
- 修复 `SettingDrawer` 组件初始化时,没有正确初始化 `settingStore` 的问题
|
||||
- 修复 `RTable` 组件在未设置 `title` 与 `tool` 为 `false` 时,导致 `headerStyle` 样式会高一些的问题
|
||||
|
||||
## 5.0.7
|
||||
|
||||
@ -580,7 +789,7 @@ const [
|
||||
补充拓展了 `useModal` 方法,支持 `dad`, `fullscreen` 等拓展配置项。
|
||||
|
||||
```ts
|
||||
import { useTable, useForm } from '@/components'
|
||||
import { useForm, useTable } from '@/components'
|
||||
|
||||
const [registerTable, { getTableInstance }] = useTable()
|
||||
const [registerForm, { getFormInstance }] = useForm()
|
||||
@ -600,8 +809,7 @@ const [registerForm, { getFormInstance }] = useForm()
|
||||
> 该方法比起常见的 `ref` 注册,然后 `tableRef.value.xxx` 的方法获取表格方法更为简洁一点。但是也值得注意的是,需要手动调用一次 `register` 方法,否则会报错;还有值得注意的是,需要注意表格方法的调用时机,需要等待表格注册完成后才能正常调用。如果需要在 `Parent Create` 阶段调用,可以尝试 `nextTick` 包裹一层。
|
||||
|
||||
```tsx
|
||||
import { RTable } from '@/components'
|
||||
import { useTable } from '@/components'
|
||||
import { RTable, useTable } from '@/components'
|
||||
|
||||
defineComponent({
|
||||
setup() {
|
||||
@ -1773,7 +1981,7 @@ const demo2 = null
|
||||
- 新增切换路由自动取消上一路由所有请求。但是可以通过配置 `useRequest` 与 `request` 方法的 `cancelConfig.cancel` 属性控制是否需要自动取消该请求。该配置默认为 `true`,当配置为 `false` 时,则不会被取消器取消
|
||||
|
||||
```ts
|
||||
import { useRequest, useHookPlusRequest } from '@/axios/index'
|
||||
import { useHookPlusRequest, useRequest } from '@/axios/index'
|
||||
|
||||
// useRequest
|
||||
const { data, loading, run } = useRequest<{
|
||||
@ -1839,8 +2047,8 @@ request({
|
||||
- useHookPlusRequest 支持接收一个 Promise 返回值的方法,可以用来包裹 axios 方法然后进行请求配置
|
||||
|
||||
```ts
|
||||
import { useHookPlusRequest, useRequest } from '@/axios/index'
|
||||
import axiosInstance from '@/axios/instance'
|
||||
import { useRequest, useHookPlusRequest } from '@/axios/index'
|
||||
|
||||
// 使用 useRequest
|
||||
const { data, loading, run } = useRequest<{
|
||||
@ -2101,7 +2309,7 @@ useAppTheme key 类型: 'dark' | 'light'
|
||||
### Feats
|
||||
|
||||
- 修改 Menu 菜单过滤逻辑,现在如果权限不匹配或者设置了 hidden 属性,则会被过滤掉
|
||||
- 移除 $activedColor 全局 sass 变量,使用 --ray-theme-primary-color 替代
|
||||
- 移除 $activedColor 全局 sass 变量,使用 --r-theme-primary-color 替代
|
||||
- 新增路由菜单检索功能
|
||||
- 移除 App.tsx 中同步主题方法,改为使用 cfg 配置并且使用 ejs 注入
|
||||
- 移除 MenuTag 默认主题色,现在会以当前主题色为主色
|
||||
|
||||
4
__test__/cache/index.spec.ts
vendored
4
__test__/cache/index.spec.ts
vendored
@ -1,8 +1,8 @@
|
||||
import {
|
||||
hasStorage,
|
||||
setStorage,
|
||||
getStorage,
|
||||
hasStorage,
|
||||
removeStorage,
|
||||
setStorage,
|
||||
} from '../../src/utils/cache'
|
||||
|
||||
describe('cache utils', () => {
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
import { RModal } from '../../src/components/base/RModal/index'
|
||||
import { mount } from '@vue/test-utils'
|
||||
import { RModal } from '../../src/components/base/RModal/index'
|
||||
|
||||
describe('RModal', () => {
|
||||
it('should execute the onAfterEnter callback', () => {
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
import { printDom } from '../../src/utils/dom'
|
||||
import { mount } from '@vue/test-utils'
|
||||
import { printDom } from '../../src/utils/dom'
|
||||
import renderHook from '../utils/renderHook'
|
||||
|
||||
// happy-dom 官方有一个 bug,无法使用 canvas.toDataURL 方法。所以该模块单测暂时无法通过
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import { setClass, hasClass, removeClass } from '../../src/utils/element'
|
||||
import { hasClass, removeClass, setClass } from '../../src/utils/element'
|
||||
import createRefElement from '../utils/createRefElement'
|
||||
|
||||
describe('setClass', () => {
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import { setStyle, removeStyle } from '../../src/utils/element'
|
||||
import { removeStyle, setStyle } from '../../src/utils/element'
|
||||
import createRefElement from '../utils/createRefElement'
|
||||
|
||||
describe('setStyle', () => {
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
import setupMiniApp from '../utils/setupMiniApp'
|
||||
import { useAppRoot } from '../../src/hooks/template/useAppRoot'
|
||||
import setupMiniApp from '../utils/setupMiniApp'
|
||||
|
||||
describe('useAppRoot', async () => {
|
||||
await setupMiniApp()
|
||||
|
||||
@ -1,8 +1,7 @@
|
||||
import setupMiniApp from '../utils/setupMiniApp'
|
||||
import { useBadge } from '../../src/hooks/template/useBadge'
|
||||
import { useMenuGetters } from '../../src/store'
|
||||
|
||||
import type { AppMenuExtraOptions } from '../../src/router/types'
|
||||
import { useMenuGetters } from '../../src/store'
|
||||
import setupMiniApp from '../utils/setupMiniApp'
|
||||
|
||||
describe('useBadge', async () => {
|
||||
await setupMiniApp()
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
import { useContextmenuCoordinate } from '../../src/hooks/components/useContextmenuCoordinate'
|
||||
import renderHook from '../utils/renderHook'
|
||||
import createRefElement from '../utils/createRefElement'
|
||||
import renderHook from '../utils/renderHook'
|
||||
|
||||
describe('useContextmenuCoordinate', () => {
|
||||
const wrapperRef = createRefElement()
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
import { useDayjs } from '../../src/hooks/web/useDayjs'
|
||||
import dayjs from 'dayjs'
|
||||
import { useDayjs } from '../../src/hooks/web/useDayjs'
|
||||
|
||||
describe('useDayjs', () => {
|
||||
const {
|
||||
|
||||
@ -1,9 +1,8 @@
|
||||
import setupMiniApp from '../utils/setupMiniApp'
|
||||
import { useSiderBar } from '../../src/hooks/template/useSiderBar'
|
||||
import { useMenuGetters, useMenuActions } from '../../src/store'
|
||||
import { useVueRouter } from '../../src/hooks/web/useVueRouter'
|
||||
|
||||
import { useMenuActions, useMenuGetters } from '../../src/store'
|
||||
import type { AppMenuOption, MenuTagOptions } from '../../src/types/modules/app'
|
||||
import setupMiniApp from '../utils/setupMiniApp'
|
||||
|
||||
describe('useSiderBar', async () => {
|
||||
await setupMiniApp()
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
import setupMiniApp from '../utils/setupMiniApp'
|
||||
import { getVariableToRefs, setVariable } from '../../src/global-variable'
|
||||
import { useSpinning } from '../../src/hooks/template/useSpinning'
|
||||
import { setVariable, getVariableToRefs } from '../../src/global-variable'
|
||||
import setupMiniApp from '../utils/setupMiniApp'
|
||||
|
||||
describe('useSpinning', async () => {
|
||||
await setupMiniApp()
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
import setupMiniApp from '../utils/setupMiniApp'
|
||||
import { useTheme } from '../../src/hooks/template/useTheme'
|
||||
import setupMiniApp from '../utils/setupMiniApp'
|
||||
|
||||
describe('useTheme', async () => {
|
||||
await setupMiniApp()
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
import setupMiniApp from '../utils/setupMiniApp'
|
||||
import { useVueRouter } from '../../src/hooks/web/useVueRouter'
|
||||
import setupMiniApp from '../utils/setupMiniApp'
|
||||
|
||||
describe('useVueRouter', async () => {
|
||||
await setupMiniApp()
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
import setupMiniApp from '../utils/setupMiniApp'
|
||||
import { useWatermark } from '../../src/hooks/template/useWatermark'
|
||||
import { useSettingGetters } from '../../src/store'
|
||||
import setupMiniApp from '../utils/setupMiniApp'
|
||||
|
||||
describe('useWatermark', async () => {
|
||||
await setupMiniApp()
|
||||
|
||||
@ -1,11 +1,11 @@
|
||||
import {
|
||||
isCurrency,
|
||||
format,
|
||||
add,
|
||||
subtract,
|
||||
multiply,
|
||||
divide,
|
||||
distribute,
|
||||
divide,
|
||||
format,
|
||||
isCurrency,
|
||||
multiply,
|
||||
subtract,
|
||||
} from '../../src/utils/precision'
|
||||
|
||||
describe('precision', () => {
|
||||
|
||||
@ -1,6 +1,5 @@
|
||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||
import { createApp, defineComponent } from 'vue'
|
||||
|
||||
import type { App } from 'vue'
|
||||
|
||||
export default function renderHook<R = any>(
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
import { setupStore } from '../../src/store'
|
||||
import { setupRouter } from '../../src/router'
|
||||
import { setupI18n } from '../../src/locales'
|
||||
import { setupRouter } from '../../src/router'
|
||||
import { setupStore } from '../../src/store'
|
||||
import renderHook from '../utils/renderHook'
|
||||
|
||||
/**
|
||||
|
||||
@ -1,12 +1,12 @@
|
||||
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'
|
||||
import js from '@eslint/js'
|
||||
import typescriptEslint from '@typescript-eslint/eslint-plugin'
|
||||
import prettier from 'eslint-plugin-prettier'
|
||||
import vue from 'eslint-plugin-vue'
|
||||
import globals from 'globals'
|
||||
import parser from 'vue-eslint-parser'
|
||||
|
||||
const __filename = fileURLToPath(import.meta.url)
|
||||
const __dirname = path.dirname(__filename)
|
||||
@ -44,14 +44,11 @@ export default [
|
||||
{
|
||||
files: ['**/*.js', '**/*.ts', '**/*.jsx', '**/*.tsx', '**/*.vue'],
|
||||
},
|
||||
js.configs.recommended,
|
||||
...vue.configs['flat/recommended'],
|
||||
...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',
|
||||
),
|
||||
{
|
||||
@ -91,7 +88,7 @@ export default [
|
||||
ignoreRestArgs: true,
|
||||
},
|
||||
],
|
||||
'prettier/prettier': 'error',
|
||||
'prettier/prettier': ['error', {}],
|
||||
'no-unused-vars': 'off',
|
||||
'@typescript-eslint/no-unused-vars': 'off',
|
||||
'@typescript-eslint/ban-types': 'off',
|
||||
@ -213,7 +210,7 @@ export default [
|
||||
},
|
||||
],
|
||||
'vue/v-on-event-hyphenation': ['error', 'never'],
|
||||
'vue/component-tags-order': [
|
||||
'vue/block-order': [
|
||||
'error',
|
||||
{
|
||||
order: ['template', 'script', 'style'],
|
||||
@ -258,92 +255,30 @@ export default [
|
||||
],
|
||||
'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: 'always', prev: 'import', next: '*' },
|
||||
{ blankLine: 'any', prev: 'import', next: 'import' },
|
||||
{ blankLine: 'always', prev: '*', next: 'export' },
|
||||
{ blankLine: 'always', prev: 'export', next: '*' },
|
||||
{ blankLine: 'any', prev: 'export', next: 'export' },
|
||||
{ blankLine: 'always', prev: ['const', 'let', 'var'], next: '*' },
|
||||
{
|
||||
blankLine: 'any',
|
||||
prev: ['const', 'let', 'var'],
|
||||
next: ['const', 'let', 'var'],
|
||||
},
|
||||
{
|
||||
blankLine: 'always',
|
||||
prev: '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: '*',
|
||||
},
|
||||
{ blankLine: 'always', prev: '*', next: 'function' },
|
||||
{ blankLine: 'always', prev: 'function', next: '*' },
|
||||
{ blankLine: 'always', prev: '*', next: 'return' },
|
||||
{ blankLine: 'always', prev: '*', next: 'if' },
|
||||
{ blankLine: 'always', prev: 'if', next: '*' },
|
||||
{ blankLine: 'always', prev: '*', next: 'for' },
|
||||
{ blankLine: 'always', prev: 'for', next: '*' },
|
||||
{ blankLine: 'always', prev: '*', next: 'while' },
|
||||
{ blankLine: 'always', prev: 'while', next: '*' },
|
||||
{ blankLine: 'always', prev: '*', next: 'class' },
|
||||
{ blankLine: 'always', prev: 'class', next: '*' },
|
||||
{ blankLine: 'always', prev: '*', next: 'try' },
|
||||
{ blankLine: 'always', prev: 'try', next: '*' },
|
||||
],
|
||||
'@typescript-eslint/no-unused-expressions': [
|
||||
'error',
|
||||
|
||||
43
index.html
43
index.html
@ -13,8 +13,29 @@
|
||||
:root {
|
||||
--preloading-tag-color: <%= preloadingConfig.tagColor %>;
|
||||
--preloading-title-color: <%= preloadingConfig.titleColor %>;
|
||||
--ray-theme-primary-fade-color: <%= appPrimaryColor.primaryFadeColor %>;
|
||||
--ray-theme-primary-color: <%= appPrimaryColor.primaryColor %>;
|
||||
--r-theme-primary-fade-color: <%= appPrimaryColor.primaryFadeColor %>;
|
||||
--r-theme-primary-color: <%= appPrimaryColor.primaryColor %>;
|
||||
--global-loading-bg-color: #ffffff;
|
||||
}
|
||||
|
||||
@media (prefers-color-scheme: dark) {
|
||||
#pre-loading-animation {
|
||||
background-color: var(--global-loading-bg-color);
|
||||
}
|
||||
}
|
||||
|
||||
@media (prefers-color-scheme: light) {
|
||||
#pre-loading-animation {
|
||||
background-color: var(--global-loading-bg-color);
|
||||
}
|
||||
}
|
||||
|
||||
html.dark #pre-loading-animation {
|
||||
background-color: var(--global-loading-bg-color);
|
||||
}
|
||||
|
||||
html.light #pre-loading-animation {
|
||||
background-color: var(--global-loading-bg-color);
|
||||
}
|
||||
|
||||
#pre-loading-animation {
|
||||
@ -23,13 +44,9 @@
|
||||
right: 0;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
background-color: #ffffff;
|
||||
color: var(--preloading-title-color);
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.ray-template--dark #pre-loading-animation {
|
||||
background-color: #2a3146;
|
||||
background-color: var(--global-loading-bg-color);
|
||||
}
|
||||
|
||||
#pre-loading-animation .pre-loading-animation__wrapper {
|
||||
@ -95,6 +112,18 @@
|
||||
}
|
||||
}
|
||||
</style>
|
||||
<script>
|
||||
;(function () {
|
||||
const html = document.documentElement
|
||||
const store = window.localStorage.getItem('piniaSettingStore')
|
||||
const { _appTheme = false } = store ? JSON.parse(store) : {}
|
||||
const loadingBgColor = _appTheme ? '#1c1e23' : '#ffffff'
|
||||
|
||||
html.classList.add(_appTheme ? 'dark' : 'light')
|
||||
html.style.setProperty('--global-loading-bg-color', loadingBgColor)
|
||||
html.style.setProperty('background-color', loadingBgColor)
|
||||
})()
|
||||
</script>
|
||||
<body>
|
||||
<div id="app"></div>
|
||||
<div id="pre-loading-animation">
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
import { defineMock } from 'vite-plugin-mock-dev-server'
|
||||
import { pagination, stringify, response, array } from '@mock/shared/utils'
|
||||
import { tableMock } from '@mock/shared/database'
|
||||
import { array, pagination, response, stringify } from '@mock/shared/utils'
|
||||
import Mock from 'mockjs'
|
||||
import { defineMock } from 'vite-plugin-mock-dev-server'
|
||||
|
||||
export const getPersonList = defineMock({
|
||||
url: '/api/list',
|
||||
|
||||
114
package.json
Executable file → Normal file
114
package.json
Executable file → Normal file
@ -1,10 +1,10 @@
|
||||
{
|
||||
"name": "ray-template",
|
||||
"private": false,
|
||||
"version": "5.0.7",
|
||||
"version": "5.2.5",
|
||||
"type": "module",
|
||||
"engines": {
|
||||
"node": "^18.0.0 || ^20.0.0 || >=22.0.0",
|
||||
"node": "^20.0.0 || >=22.0.0",
|
||||
"pnpm": ">=9.0.0"
|
||||
},
|
||||
"scripts": {
|
||||
@ -33,79 +33,83 @@
|
||||
]
|
||||
},
|
||||
"dependencies": {
|
||||
"@logicflow/core": "2.0.6",
|
||||
"@logicflow/extension": "2.0.10",
|
||||
"@vueuse/core": "^12.0.0",
|
||||
"axios": "^1.7.9",
|
||||
"@logicflow/core": "2.0.10",
|
||||
"@logicflow/extension": "2.0.14",
|
||||
"@vueuse/core": "^13.1.0",
|
||||
"axios": "^1.10.0",
|
||||
"clipboard": "^2.0.11",
|
||||
"crypto-js": "4.2.0",
|
||||
"currency.js": "^2.0.4",
|
||||
"dayjs": "^1.11.13",
|
||||
"echarts": "^5.5.1",
|
||||
"html-to-image": "1.11.11",
|
||||
"echarts": "^6.0.0",
|
||||
"html-to-image": "1.11.13",
|
||||
"interactjs": "1.10.27",
|
||||
"jsbarcode": "3.11.6",
|
||||
"lodash-es": "^4.17.21",
|
||||
"mockjs": "1.1.0",
|
||||
"naive-ui": "^2.40.3",
|
||||
"pinia": "^2.3.0",
|
||||
"pinia-plugin-persistedstate": "^4.1.3",
|
||||
"naive-ui": "^2.43.2",
|
||||
"pinia": "^3.0.3",
|
||||
"pinia-plugin-persistedstate": "^4.4.1",
|
||||
"print-js": "^1.6.0",
|
||||
"vue": "^3.5.13",
|
||||
"vue": "^3.5.25",
|
||||
"vue-demi": "0.14.10",
|
||||
"vue-hooks-plus": "2.2.1",
|
||||
"vue-i18n": "^9.13.1",
|
||||
"vue-router": "^4.4.0",
|
||||
"vue3-next-qrcode": "2.0.10"
|
||||
"vue-hooks-plus": "2.4.1",
|
||||
"vue-i18n": "^11.1.3",
|
||||
"vue-router": "^4.6.3",
|
||||
"vue3-next-qrcode": "3.0.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@commitlint/cli": "^19.3.0",
|
||||
"@commitlint/config-conventional": "^19.2.2",
|
||||
"@eslint/eslintrc": "3.1.0",
|
||||
"@eslint/js": "9.11.0",
|
||||
"@commitlint/cli": "19.7.1",
|
||||
"@commitlint/config-conventional": "19.7.1",
|
||||
"@eslint/eslintrc": "3.3.1",
|
||||
"@eslint/js": "9.39.1",
|
||||
"@ianvs/prettier-plugin-sort-imports": "4.7.0",
|
||||
"@interactjs/types": "1.10.27",
|
||||
"@intlify/unplugin-vue-i18n": "^4.0.0",
|
||||
"@types/crypto-js": "^4.2.2",
|
||||
"@intlify/unplugin-vue-i18n": "11.0.1",
|
||||
"@types/crypto-js": "4.2.2",
|
||||
"@types/jsbarcode": "3.11.4",
|
||||
"@types/lodash-es": "^4.17.12",
|
||||
"@types/lodash-es": "4.17.12",
|
||||
"@types/mockjs": "1.0.10",
|
||||
"@typescript-eslint/eslint-plugin": "^8.16.0",
|
||||
"@typescript-eslint/parser": "^8.16.0",
|
||||
"@vitejs/plugin-vue": "^5.1.0",
|
||||
"@vitejs/plugin-vue-jsx": "^4.1.1",
|
||||
"@vitest/ui": "1.5.2",
|
||||
"@vue/eslint-config-prettier": "^9.0.0",
|
||||
"@vue/eslint-config-typescript": "^14.1.4",
|
||||
"@typescript-eslint/eslint-plugin": "8.47.0",
|
||||
"@typescript-eslint/parser": "8.47.0",
|
||||
"@vitejs/plugin-vue": "6.0.2",
|
||||
"@vitejs/plugin-vue-jsx": "5.1.2",
|
||||
"@vitest/ui": "4.0.12",
|
||||
"@vue/eslint-config-prettier": "10.1.0",
|
||||
"@vue/eslint-config-typescript": "14.2.0",
|
||||
"@vue/test-utils": "2.4.6",
|
||||
"autoprefixer": "^10.4.20",
|
||||
"depcheck": "^1.4.7",
|
||||
"eslint": "^9.11.0",
|
||||
"eslint-config-prettier": "^9.1.0",
|
||||
"eslint-plugin-prettier": "^5.1.3",
|
||||
"eslint-plugin-vue": "^9.26.0",
|
||||
"globals": "15.12.0",
|
||||
"happy-dom": "14.12.3",
|
||||
"autoprefixer": "10.4.21",
|
||||
"depcheck": "1.4.7",
|
||||
"eslint": "9.39.1",
|
||||
"eslint-config-prettier": "10.1.8",
|
||||
"eslint-plugin-prettier": "5.5.4",
|
||||
"eslint-plugin-vue": "10.6.0",
|
||||
"globals": "16.5.0",
|
||||
"happy-dom": "17.1.0",
|
||||
"husky": "8.0.3",
|
||||
"lint-staged": "^15.2.2",
|
||||
"postcss": "^8.4.49",
|
||||
"lint-staged": "15.4.3",
|
||||
"postcss": "8.5.6",
|
||||
"postcss-px-to-viewport-8-with-include": "1.2.2",
|
||||
"prettier": "^3.3.2",
|
||||
"prettier": "3.6.2",
|
||||
"rollup-plugin-gzip": "4.0.1",
|
||||
"sass": "1.77.1",
|
||||
"svg-sprite-loader": "^6.0.11",
|
||||
"typescript": "^5.6.3",
|
||||
"unplugin-auto-import": "^0.18.2",
|
||||
"unplugin-vue-components": "^0.27.4",
|
||||
"vite": "^6.0.3",
|
||||
"vite-bundle-analyzer": "0.9.4",
|
||||
"sass": "1.94.2",
|
||||
"svg-sprite-loader": "6.0.11",
|
||||
"typescript": "5.9.3",
|
||||
"unocss": "66.5.9",
|
||||
"unplugin-auto-import": "20.2.0",
|
||||
"unplugin-vue-components": "0.28.0",
|
||||
"vite": "7.2.6",
|
||||
"vite-bundle-analyzer": "1.2.3",
|
||||
"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-inspect": "^0.8.4",
|
||||
"vite-plugin-mock-dev-server": "1.8.0",
|
||||
"vite-plugin-svg-icons": "^2.0.1",
|
||||
"vite-svg-loader": "^5.1.0",
|
||||
"vitest": "2.0.5",
|
||||
"vue-tsc": "^2.1.10"
|
||||
"vite-plugin-inspect": "11.3.3",
|
||||
"vite-plugin-mock-dev-server": "2.0.4",
|
||||
"vite-plugin-svg-icons": "2.0.1",
|
||||
"vite-svg-loader": "5.1.0",
|
||||
"vitest": "4.0.12",
|
||||
"vue-eslint-parser": "10.2.0",
|
||||
"vue-tsc": "2.2.8"
|
||||
},
|
||||
"description": "<!-- ALL-CONTRIBUTORS-BADGE:START - Do not remove or modify this section -->",
|
||||
"main": "index.ts",
|
||||
|
||||
6161
pnpm-lock.yaml
generated
6161
pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
@ -1,12 +1,11 @@
|
||||
import { RouterView } from 'vue-router'
|
||||
import AppGlobalSpin from '@/app-components/app/AppGlobalSpin'
|
||||
import AppLockScreen from '@/app-components/app/AppLockScreen'
|
||||
import AppNaiveGlobalProvider from '@/app-components/provider/AppNaiveGlobalProvider'
|
||||
import AppStyleProvider from '@/app-components/provider/AppStyleProvider'
|
||||
import AppLockScreen from '@/app-components/app/AppLockScreen'
|
||||
import AppWatermarkProvider from '@/app-components/provider/AppWatermarkProvider'
|
||||
import AppGlobalSpin from '@/app-components/app/AppGlobalSpin'
|
||||
import AppVersionProvider from '@/app-components/provider/AppVersionProvider'
|
||||
|
||||
import AppWatermarkProvider from '@/app-components/provider/AppWatermarkProvider'
|
||||
import { APP_GLOBAL_LOADING } from '@/app-config'
|
||||
import { RouterView } from 'vue-router'
|
||||
|
||||
export default defineComponent({
|
||||
name: 'App',
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
import { useSettingGetters } from '@/store'
|
||||
import { useVueRouter } from '@/hooks'
|
||||
import { useSettingGetters } from '@/store'
|
||||
|
||||
/**
|
||||
*
|
||||
|
||||
@ -1,9 +1,9 @@
|
||||
import {
|
||||
DAYJS_LOCAL_MAP,
|
||||
DEFAULT_DAYJS_LOCAL,
|
||||
LOCAL_OPTIONS,
|
||||
SYSTEM_DEFAULT_LOCAL,
|
||||
SYSTEM_FALLBACK_LOCALE,
|
||||
DAYJS_LOCAL_MAP,
|
||||
DEFAULT_DAYJS_LOCAL,
|
||||
} from '@/app-config'
|
||||
|
||||
/**
|
||||
|
||||
@ -1,5 +1,4 @@
|
||||
import { request } from '@/axios'
|
||||
|
||||
import type { PaginationResponse } from '@/types'
|
||||
|
||||
export interface MockListParams {
|
||||
|
||||
@ -9,11 +9,10 @@
|
||||
*/
|
||||
|
||||
import { request } from '@/axios'
|
||||
|
||||
import type { BasicResponse } from '@/types'
|
||||
|
||||
interface AxiosTestResponse extends UnknownObjectKey {
|
||||
data: UnknownObjectKey[]
|
||||
interface AxiosTestResponse extends GlobalRecordable {
|
||||
data: GlobalRecordable[]
|
||||
city?: string
|
||||
}
|
||||
|
||||
@ -28,7 +27,7 @@ interface JSONPlaceholder {
|
||||
*
|
||||
* @returns 测试
|
||||
*
|
||||
* @medthod get
|
||||
* @method get
|
||||
*/
|
||||
export const getWeather = (city: string) => {
|
||||
return request<AxiosTestResponse>({
|
||||
|
||||
@ -6,13 +6,10 @@
|
||||
* 默认读取本地 session catch 缓存
|
||||
*/
|
||||
|
||||
import { NAvatar, NButton, NFlex } from 'naive-ui'
|
||||
|
||||
import { avatarProps } from 'naive-ui'
|
||||
import { useSigningGetters } from '@/store'
|
||||
|
||||
import type { PropType } from 'vue'
|
||||
import { avatarProps, NAvatar, NButton, NFlex } from 'naive-ui'
|
||||
import type { AvatarProps, FlexProps } from 'naive-ui'
|
||||
import type { PropType } from 'vue'
|
||||
|
||||
const AppAvatar = defineComponent({
|
||||
name: 'AppAvatar',
|
||||
|
||||
@ -15,11 +15,8 @@
|
||||
* 2. 如果需要使用该组件请注意控制取消时机
|
||||
*/
|
||||
|
||||
import { NSpin } from 'naive-ui'
|
||||
|
||||
import { spinProps } from 'naive-ui'
|
||||
import { getVariableToRefs } from '@/global-variable'
|
||||
|
||||
import { NSpin, spinProps } from 'naive-ui'
|
||||
import type { SpinProps } from 'naive-ui'
|
||||
|
||||
const GlobalSpin = defineComponent({
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
import { useStorage } from '@vueuse/core'
|
||||
import { APP_CATCH_KEY } from '@/app-config'
|
||||
import { useStorage } from '@vueuse/core'
|
||||
|
||||
const appLockScreen = useStorage(
|
||||
APP_CATCH_KEY.isAppLockScreen,
|
||||
|
||||
@ -1,14 +1,13 @@
|
||||
import { NInput, NFormItem, NButton } from 'naive-ui'
|
||||
import AppAvatar from '@/app-components/app/AppAvatar'
|
||||
import { RForm } from '@/components'
|
||||
|
||||
import useAppLockScreen from '@/app-components/app/AppLockScreen/appLockVar'
|
||||
import { rules, useCondition } from '@/app-components/app/AppLockScreen/shared'
|
||||
import { APP_CATCH_KEY } from '@/app-config'
|
||||
import { RForm, useForm } from '@/components'
|
||||
import { useSettingActions } from '@/store'
|
||||
import { useTemplateRef } from 'vue'
|
||||
import { useForm } from '@/components'
|
||||
|
||||
import { encrypt, setStorage } from '@/utils'
|
||||
import { NButton, NFormItem, NInput } from 'naive-ui'
|
||||
import type { InputInst } from 'naive-ui'
|
||||
import { useTemplateRef } from 'vue'
|
||||
|
||||
const LockScreen = defineComponent({
|
||||
name: 'LockScreen',
|
||||
@ -27,6 +26,11 @@ const LockScreen = defineComponent({
|
||||
validate().then(() => {
|
||||
setLockAppScreen(true)
|
||||
updateSettingState('lockScreenSwitch', false)
|
||||
setStorage(
|
||||
APP_CATCH_KEY.appLockScreenPasswordKey,
|
||||
encrypt(state.lockCondition.lockPassword),
|
||||
'localStorage',
|
||||
)
|
||||
|
||||
state.lockCondition = useCondition()
|
||||
})
|
||||
|
||||
@ -1,15 +1,14 @@
|
||||
import '../../index.scss'
|
||||
|
||||
import { NInput, NFormItem, NButton, NFlex } from 'naive-ui'
|
||||
import AppAvatar from '@/app-components/app/AppAvatar'
|
||||
import { RForm } from '@/components'
|
||||
|
||||
import dayjs from 'dayjs'
|
||||
import { useSigningActions, useSettingActions } from '@/store'
|
||||
import { rules, useCondition } from '@/app-components/app/AppLockScreen/shared'
|
||||
import useAppLockScreen from '@/app-components/app/AppLockScreen/appLockVar'
|
||||
import { rules, useCondition } from '@/app-components/app/AppLockScreen/shared'
|
||||
import { APP_CATCH_KEY } from '@/app-config'
|
||||
import { RForm, useForm } from '@/components'
|
||||
import { useDevice } from '@/hooks'
|
||||
import { useForm } from '@/components'
|
||||
import { useSettingActions, useSigningActions } from '@/store'
|
||||
import { decrypt, getStorage, removeStorage } from '@/utils'
|
||||
import dayjs from 'dayjs'
|
||||
import { NButton, NFlex, NFormItem, NInput } from 'naive-ui'
|
||||
|
||||
export default defineComponent({
|
||||
name: 'UnlockScreen',
|
||||
@ -42,27 +41,56 @@ export default defineComponent({
|
||||
state.DDD = dayjs().format(DDD_FORMAT)
|
||||
}, 86_400_000)
|
||||
|
||||
const backToSigning = () => {
|
||||
window.$dialog.warning({
|
||||
title: '警告',
|
||||
content: '是否返回到登陆页?',
|
||||
positiveText: '确定',
|
||||
negativeText: '取消',
|
||||
onPositiveClick: () => {
|
||||
const toSigningFn = () => {
|
||||
removeStorage(APP_CATCH_KEY.appLockScreenPasswordKey, 'localStorage')
|
||||
updateSettingState('lockScreenSwitch', false)
|
||||
setTimeout(() => {
|
||||
logout()
|
||||
}, 100)
|
||||
},
|
||||
}
|
||||
|
||||
const backToSigning = () => {
|
||||
window.$dialog.warning({
|
||||
title: '警告',
|
||||
content: '是否返回到登陆页并且重新登录',
|
||||
positiveText: '确定',
|
||||
negativeText: '重新登录',
|
||||
onPositiveClick: toSigningFn,
|
||||
})
|
||||
}
|
||||
|
||||
const unlockScreen = () => {
|
||||
const catchPassword = getStorage<string>(
|
||||
APP_CATCH_KEY.appLockScreenPasswordKey,
|
||||
'localStorage',
|
||||
)
|
||||
|
||||
if (!catchPassword) {
|
||||
window.$dialog.warning({
|
||||
title: '警告',
|
||||
content: () => '检测到锁屏密码被修改,请重新登录',
|
||||
closable: false,
|
||||
maskClosable: false,
|
||||
closeOnEsc: false,
|
||||
positiveText: '重新登录',
|
||||
onPositiveClick: toSigningFn,
|
||||
})
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
const dCatchPassword = decrypt(catchPassword)
|
||||
|
||||
validate().then(() => {
|
||||
if (dCatchPassword === state.lockCondition.lockPassword) {
|
||||
setLockAppScreen(false)
|
||||
updateSettingState('lockScreenSwitch', false)
|
||||
removeStorage(APP_CATCH_KEY.appLockScreenPasswordKey, 'localStorage')
|
||||
|
||||
state.lockCondition = useCondition()
|
||||
} else {
|
||||
window.$message.warning('密码错误,请重新输入')
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@ -1,8 +1,7 @@
|
||||
import { RModal } from '@/components'
|
||||
import { useSettingActions, useSettingGetters } from '@/store'
|
||||
import LockScreen from './components/LockScreen'
|
||||
|
||||
import { useSettingGetters, useSettingActions } from '@/store'
|
||||
|
||||
const AppLockScreen = defineComponent({
|
||||
name: 'AppLockScreen',
|
||||
setup() {
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import { NAvatar, NTooltip, NFlex } from 'naive-ui'
|
||||
import { NAvatar, NFlex, NTooltip } from 'naive-ui'
|
||||
|
||||
interface AvatarOptions {
|
||||
key: string
|
||||
|
||||
@ -6,21 +6,20 @@
|
||||
* 如果需要更改弹出位置, 需要在需要地方重新定义组件注册
|
||||
*/
|
||||
|
||||
import {
|
||||
NDialogProvider,
|
||||
NLoadingBarProvider,
|
||||
NMessageProvider,
|
||||
NNotificationProvider,
|
||||
NConfigProvider,
|
||||
createDiscreteApi,
|
||||
darkTheme,
|
||||
NGlobalStyle,
|
||||
NModalProvider,
|
||||
} from 'naive-ui'
|
||||
|
||||
import { MESSAGE_PROVIDER } from '@/app-config'
|
||||
import { getNaiveLocales } from '@/locales/utils'
|
||||
import { useSettingGetters } from '@/store'
|
||||
import { MESSAGE_PROVIDER } from '@/app-config'
|
||||
import {
|
||||
createDiscreteApi,
|
||||
darkTheme,
|
||||
NConfigProvider,
|
||||
NDialogProvider,
|
||||
NGlobalStyle,
|
||||
NLoadingBarProvider,
|
||||
NMessageProvider,
|
||||
NModalProvider,
|
||||
NNotificationProvider,
|
||||
} from 'naive-ui'
|
||||
|
||||
export default defineComponent({
|
||||
name: 'GlobalProvider',
|
||||
|
||||
@ -1,16 +1,15 @@
|
||||
import { get } from 'lodash-es'
|
||||
import { APP_CATCH_KEY, APP_THEME, GLOBAL_CLASS_NAMES } from '@/app-config'
|
||||
import { useSettingGetters } from '@/store'
|
||||
import type { SettingState } from '@/store/modules/setting/types'
|
||||
import {
|
||||
setClass,
|
||||
removeClass,
|
||||
setStyle,
|
||||
colorToRgba,
|
||||
getStorage,
|
||||
removeClass,
|
||||
setClass,
|
||||
setStyle,
|
||||
} from '@/utils'
|
||||
import { useSettingGetters } from '@/store'
|
||||
import { APP_CATCH_KEY, GLOBAL_CLASS_NAMES, APP_THEME } from '@/app-config'
|
||||
import { useWindowSize } from '@vueuse/core'
|
||||
|
||||
import type { SettingState } from '@/store/modules/setting/types'
|
||||
import { get } from 'lodash-es'
|
||||
|
||||
export default defineComponent({
|
||||
name: 'AppStyleProvider',
|
||||
@ -44,7 +43,7 @@ export default defineComponent({
|
||||
primaryColor,
|
||||
)
|
||||
// 将主色调任意颜色转换为 rgba 格式
|
||||
const fp = colorToRgba(p, 0.8)
|
||||
const fp = colorToRgba(p, 0.85)
|
||||
|
||||
// 设置全局主题色 css 变量
|
||||
html.style.setProperty(rayTemplateThemePrimaryColor, p) // 主色调
|
||||
|
||||
@ -4,11 +4,10 @@
|
||||
* 如果不是最新版本则弹出提示框,提示用户更新,点击确认后退出登录并且刷新资源
|
||||
*/
|
||||
|
||||
import { RModal } from '@/components'
|
||||
|
||||
import { getStorage, setStorage } from '@/utils'
|
||||
import { useSigningActions } from '@/store'
|
||||
import { APP_CATCH_KEY } from '@/app-config'
|
||||
import { RModal } from '@/components'
|
||||
import { useSigningActions } from '@/store'
|
||||
import { getStorage, setStorage } from '@/utils'
|
||||
|
||||
export default defineComponent({
|
||||
name: 'AppVersionProvider',
|
||||
@ -53,7 +52,7 @@ export default defineComponent({
|
||||
title="发现新版本"
|
||||
content="当前版本已更新,点击确认加载新版本~"
|
||||
zIndex={999999999}
|
||||
dad
|
||||
draggable
|
||||
positiveText="确认"
|
||||
negativeText="取消"
|
||||
onPositiveClick={logout}
|
||||
|
||||
@ -8,9 +8,8 @@
|
||||
* 当然你也可以通过 useWatermark hook 自定义控制水印的显示以及内容
|
||||
*/
|
||||
|
||||
import { NWatermark } from 'naive-ui'
|
||||
|
||||
import { useSettingGetters } from '@/store'
|
||||
import { NWatermark } from 'naive-ui'
|
||||
|
||||
export default defineComponent({
|
||||
name: 'AppWatermarkProvider',
|
||||
|
||||
@ -12,8 +12,8 @@ import type { MessageProviderProps } from 'naive-ui'
|
||||
export const GLOBAL_CLASS_NAMES = {
|
||||
darkClassName: 'ray-template--dark',
|
||||
lightClassName: 'ray-template--light',
|
||||
rayTemplateThemePrimaryColor: '--ray-theme-primary-color',
|
||||
rayTemplateThemePrimaryFadeColor: '--ray-theme-primary-fade-color',
|
||||
rayTemplateThemePrimaryColor: '--r-theme-primary-color',
|
||||
rayTemplateThemePrimaryFadeColor: '--r-theme-primary-fade-color',
|
||||
preLoadingAnimation: 'pre-loading-animation',
|
||||
htmlHeight: '--html-height',
|
||||
htmlWidth: '--html-width',
|
||||
@ -93,6 +93,8 @@ export const APP_CATCH_KEY_PREFIX = ''
|
||||
* - appPiniaMenuStore: pinia menu store key
|
||||
* - appPiniaSigningStore: pinia signing store key
|
||||
* - appVersionProvider: 版本信息缓存 key
|
||||
* - appMenuTagOptions: 标签页菜单列表
|
||||
* - appLockScreenPasswordKey: 锁屏密码缓存 key
|
||||
*/
|
||||
export const APP_CATCH_KEY = {
|
||||
signing: 'signing',
|
||||
@ -106,6 +108,8 @@ export const APP_CATCH_KEY = {
|
||||
appVersionProvider: 'appVersionProvider',
|
||||
isAppLockScreen: 'isAppLockScreen',
|
||||
appGlobalSearchOptions: 'appGlobalSearchOptions',
|
||||
appMenuTagOptions: 'appMenuTagOptions',
|
||||
appLockScreenPasswordKey: 'appLockScreenPasswordKey',
|
||||
} as const
|
||||
|
||||
/**
|
||||
|
||||
@ -20,7 +20,7 @@ export const APP_THEME: AppTheme = {
|
||||
// 主题色
|
||||
primaryColor: '#2d8cf0',
|
||||
// 主题辅助色(用于整体 hover、active 等之类颜色)
|
||||
primaryFadeColor: 'rgba(45, 140, 240, 0.8)',
|
||||
primaryFadeColor: 'rgba(45, 140, 240, 0.85)',
|
||||
},
|
||||
/**
|
||||
*
|
||||
@ -51,15 +51,13 @@ export const APP_THEME: AppTheme = {
|
||||
dark: {
|
||||
common: {
|
||||
borderRadius: '4px',
|
||||
baseColor: 'rgb(18, 18, 18)',
|
||||
textColorBase: 'rgb(255, 255, 255)',
|
||||
baseColor: 'rgba(18, 18, 18, 1)',
|
||||
},
|
||||
},
|
||||
light: {
|
||||
common: {
|
||||
borderRadius: '4px',
|
||||
baseColor: 'rgb(255, 255, 255)',
|
||||
textColorBase: 'rgb(31, 31, 31)',
|
||||
baseColor: 'rgba(255, 255, 255, 1)',
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
@ -1,5 +1,9 @@
|
||||
import type { TemplateLocale, LocalOptions, DayjsLocalMap } from '@/types'
|
||||
import type { ValueOf } from '@/types'
|
||||
import type {
|
||||
DayjsLocalMap,
|
||||
LocalOptions,
|
||||
TemplateLocale,
|
||||
ValueOf,
|
||||
} from '@/types'
|
||||
|
||||
/**
|
||||
*
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
import { useAxiosInterceptor } from '@/axios/utils/interceptor'
|
||||
import { axiosInterceptor } from '@/axios/utils/interceptor'
|
||||
import implement from './provider'
|
||||
|
||||
const { setImplement } = useAxiosInterceptor()
|
||||
const { setImplement } = axiosInterceptor()
|
||||
|
||||
export const setupRequestInterceptor = () => {
|
||||
const { implementRequestInterceptorArray } = implement
|
||||
|
||||
@ -1,6 +1,5 @@
|
||||
import { axiosCanceler } from '@/axios/utils/interceptor'
|
||||
|
||||
import type { AxiosRequestInterceptor, FetchErrorFunction } from '@/axios/types'
|
||||
import { axiosCanceler } from '@/axios/utils/interceptor'
|
||||
|
||||
/**
|
||||
*
|
||||
|
||||
@ -1,11 +1,10 @@
|
||||
import { appendRequestHeaders } from '@/axios/utils/append-request-headers'
|
||||
import { APP_CATCH_KEY } from '@/app-config'
|
||||
import { getStorage } from '@/utils'
|
||||
|
||||
import type {
|
||||
RequestInterceptorConfig,
|
||||
AxiosRequestInterceptor,
|
||||
RequestInterceptorConfig,
|
||||
} from '@/axios/types'
|
||||
import { appendRequestHeaders } from '@/axios/utils/append-request-headers'
|
||||
import { getStorage } from '@/utils'
|
||||
|
||||
/**
|
||||
*
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
import { useAxiosInterceptor } from '@/axios/utils/interceptor'
|
||||
import { axiosInterceptor } from '@/axios/utils/interceptor'
|
||||
import implement from './provider'
|
||||
|
||||
const { setImplement } = useAxiosInterceptor()
|
||||
const { setImplement } = axiosInterceptor()
|
||||
|
||||
export const setupResponseInterceptor = () => {
|
||||
const { implementResponseInterceptorArray } = implement
|
||||
|
||||
@ -1,9 +1,8 @@
|
||||
import { axiosCanceler } from '@/axios/utils/interceptor'
|
||||
|
||||
import type {
|
||||
AxiosResponseInterceptor,
|
||||
FetchErrorFunction,
|
||||
} from '@/axios/types'
|
||||
import { axiosCanceler } from '@/axios/utils/interceptor'
|
||||
|
||||
/**
|
||||
*
|
||||
|
||||
@ -10,11 +10,10 @@
|
||||
* 由于中间件注册了自动取消重复请求的方法,所以会导致方法在初始化时,会抛出一个重复请求被取消的错误(该问题不影响使用)
|
||||
*/
|
||||
|
||||
import useHookPlusRequest from 'vue-hooks-plus/es/useRequest'
|
||||
import request from '@/axios/instance'
|
||||
|
||||
import type { UseRequestOptions } from 'vue-hooks-plus/es/useRequest/types'
|
||||
import type { AppRawRequestConfig } from '@/axios/types'
|
||||
import useHookPlusRequest from 'vue-hooks-plus/es/useRequest'
|
||||
import type { UseRequestOptions } from 'vue-hooks-plus/es/useRequest/types'
|
||||
|
||||
/**
|
||||
*
|
||||
|
||||
@ -1,39 +1,41 @@
|
||||
/**
|
||||
*
|
||||
* 请求拦截器与响应拦截器
|
||||
* 如果有需要拓展拦截器, 请在 inject 目录下参照示例方法继续拓展
|
||||
* 该页面不应该做过多的改动与配置
|
||||
*/
|
||||
|
||||
import axios from 'axios'
|
||||
import { AXIOS_CONFIG } from '@/app-config'
|
||||
import { useAxiosInterceptor } from '@/axios/utils/interceptor'
|
||||
import {
|
||||
setupResponseInterceptor,
|
||||
setupResponseErrorInterceptor,
|
||||
} from '@/axios/axios-interceptor/response'
|
||||
import {
|
||||
setupRequestInterceptor,
|
||||
setupRequestErrorInterceptor,
|
||||
setupRequestInterceptor,
|
||||
} from '@/axios/axios-interceptor/request'
|
||||
import {
|
||||
setupResponseErrorInterceptor,
|
||||
setupResponseInterceptor,
|
||||
} from '@/axios/axios-interceptor/response'
|
||||
import { axiosInterceptor } from '@/axios/utils/interceptor'
|
||||
import axios from 'axios'
|
||||
import type { AxiosInstanceExpand, RequestInterceptorConfig } from './types'
|
||||
|
||||
import type { AxiosInstanceExpand } from './types'
|
||||
|
||||
// 创建 axios 实例
|
||||
const server: AxiosInstanceExpand = axios.create(AXIOS_CONFIG)
|
||||
const { createAxiosInstance, beforeFetch, fetchError } = useAxiosInterceptor()
|
||||
// 获取拦截器实例
|
||||
const { createAxiosInstance, beforeFetch, fetchError } = axiosInterceptor()
|
||||
|
||||
// 请求拦截器
|
||||
server.interceptors.request.use(
|
||||
(request) => {
|
||||
createAxiosInstance(request, 'requestInstance') // 生成 request instance
|
||||
setupRequestInterceptor() // 初始化拦截器所有已注入方法
|
||||
beforeFetch('requestInstance', 'implementRequestInterceptorArray', 'ok') // 执行拦截器所有已注入方法
|
||||
// 生成 request instance
|
||||
createAxiosInstance(
|
||||
request as RequestInterceptorConfig<unknown>,
|
||||
'requestInstance',
|
||||
)
|
||||
// 初始化拦截器所有已注入方法
|
||||
setupRequestInterceptor()
|
||||
// 执行拦截器所有已注入方法
|
||||
beforeFetch('requestInstance', 'implementRequestInterceptorArray', 'ok')
|
||||
|
||||
return request
|
||||
},
|
||||
(error) => {
|
||||
setupRequestErrorInterceptor() // 初始化拦截器所有已注入方法(错误状态)
|
||||
fetchError('requestError', error, 'implementRequestInterceptorErrorArray') // 执行所有已注入方法
|
||||
// 初始化拦截器所有已注入方法(错误状态)
|
||||
setupRequestErrorInterceptor()
|
||||
// 执行所有已注入方法
|
||||
fetchError('requestError', error, 'implementRequestInterceptorErrorArray')
|
||||
|
||||
return Promise.reject(error)
|
||||
},
|
||||
@ -42,17 +44,22 @@ server.interceptors.request.use(
|
||||
// 响应拦截器
|
||||
server.interceptors.response.use(
|
||||
(response) => {
|
||||
createAxiosInstance(response, 'responseInstance') // 创建响应实例
|
||||
setupResponseInterceptor() // 注入响应成功待执行队列
|
||||
beforeFetch('responseInstance', 'implementResponseInterceptorArray', 'ok') // 执行响应成功拦截器
|
||||
// 创建响应实例
|
||||
createAxiosInstance(response, 'responseInstance')
|
||||
// 注入响应成功待执行队列
|
||||
setupResponseInterceptor()
|
||||
// 执行响应成功拦截器
|
||||
beforeFetch('responseInstance', 'implementResponseInterceptorArray', 'ok')
|
||||
|
||||
const { data } = response
|
||||
|
||||
return Promise.resolve(data)
|
||||
},
|
||||
(error) => {
|
||||
setupResponseErrorInterceptor() // 注入响应失败待执行队列
|
||||
fetchError('responseError', error, 'implementResponseInterceptorErrorArray') // 执行响应失败后拦截器
|
||||
// 注入响应失败待执行队列
|
||||
setupResponseErrorInterceptor()
|
||||
// 执行响应失败后拦截器
|
||||
fetchError('responseError', error, 'implementResponseInterceptorErrorArray')
|
||||
|
||||
return Promise.reject(error)
|
||||
},
|
||||
|
||||
@ -1,16 +1,16 @@
|
||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||
import type { AnyFn } from '@/types'
|
||||
import type {
|
||||
Axios,
|
||||
AxiosDefaults,
|
||||
AxiosError,
|
||||
AxiosHeaders,
|
||||
AxiosRequestConfig,
|
||||
HeadersDefaults,
|
||||
AxiosDefaults,
|
||||
Axios,
|
||||
AxiosResponse,
|
||||
AxiosError,
|
||||
InternalAxiosRequestConfig,
|
||||
AxiosRequestHeaders,
|
||||
AxiosResponse,
|
||||
HeadersDefaults,
|
||||
InternalAxiosRequestConfig,
|
||||
} from 'axios'
|
||||
import type { AnyFC } from '@/types'
|
||||
|
||||
export type AxiosHeaderValue =
|
||||
| AxiosHeaders
|
||||
@ -136,13 +136,13 @@ export type RequestInterceptorConfig<T = any> = AppRawRequestConfig<T>
|
||||
export type ResponseInterceptorConfig<T = any, K = any> = AxiosResponse<T, K>
|
||||
|
||||
export interface ImplementQueue {
|
||||
implementRequestInterceptorArray: AnyFC[]
|
||||
implementResponseInterceptorArray: AnyFC[]
|
||||
implementRequestInterceptorArray: AnyFn[]
|
||||
implementResponseInterceptorArray: AnyFn[]
|
||||
}
|
||||
|
||||
export interface ErrorImplementQueue {
|
||||
implementRequestInterceptorErrorArray: AnyFC[]
|
||||
implementResponseInterceptorErrorArray: AnyFC[]
|
||||
implementRequestInterceptorErrorArray: AnyFn[]
|
||||
implementResponseInterceptorErrorArray: AnyFn[]
|
||||
}
|
||||
|
||||
export type FetchType = 'ok' | 'error'
|
||||
|
||||
@ -1,116 +1,129 @@
|
||||
/**
|
||||
*
|
||||
* 自动取消重复请求
|
||||
*
|
||||
* 可以根据自己项目进行定制化配置
|
||||
*/
|
||||
|
||||
import type { AppRawRequestConfig, CancelerParams } from '@/axios/types'
|
||||
|
||||
/**
|
||||
*
|
||||
* @class RequestCanceler
|
||||
*
|
||||
* @description
|
||||
* 用于取消重复请求,会在请求前添加 signal 属性,用于取消请求。
|
||||
* 通过 generateRequestKey 方法生成请求 key,用于标识请求。
|
||||
* 请求取消器。
|
||||
*
|
||||
* 如果需要取消请求,则需要在请求前添加 cancelConfig.cancel 为 true;
|
||||
* 并且会在请求前添加 __CANCELER_TAG_RAY_TEMPLATE__ 属性,用于标识是否需要取消。
|
||||
* 用于管理和取消重复的 HTTP 请求:
|
||||
* - 自动为请求添加 AbortController signal
|
||||
* - 通过请求特征生成唯一 key 来识别重复请求
|
||||
* - 支持取消单个或所有待处理的请求
|
||||
*
|
||||
* @example
|
||||
* ```ts
|
||||
* const canceler = new RequestCanceler()
|
||||
*
|
||||
* // 添加请求到待处理队列
|
||||
* canceler.addPendingRequest(config)
|
||||
*
|
||||
* // 移除并取消特定请求
|
||||
* canceler.removePendingRequest(config)
|
||||
*
|
||||
* // 取消所有待处理请求
|
||||
* canceler.cancelAllRequest()
|
||||
* ```
|
||||
*/
|
||||
export default class RequestCanceler {
|
||||
private pendingRequest: Map<string, AbortController>
|
||||
|
||||
constructor() {
|
||||
this.pendingRequest = new Map<string, AbortController>()
|
||||
}
|
||||
/** 待处理请求的 Map,key 为请求标识,value 为 AbortController */
|
||||
private readonly pendingRequest = new Map<string, AbortController>()
|
||||
|
||||
/**
|
||||
*
|
||||
* @param config 请求体 config
|
||||
* @param config - 请求配置
|
||||
*
|
||||
* @returns 是否需要添加取消功能,默认为 true
|
||||
*
|
||||
* @description
|
||||
* 判断是否需要添加 signal 属性。
|
||||
*
|
||||
* 如果 cancelConfig 为 false,则不添加 signal 属性;
|
||||
* 如果 cancelConfig 为 true,则添加 signal 属性。
|
||||
*
|
||||
* @example
|
||||
* const bool = isAppending(config) // true or false
|
||||
* 判断请求是否需要添加取消功能。
|
||||
*/
|
||||
private isAppending(config: AppRawRequestConfig | CancelerParams) {
|
||||
private shouldAddCanceler(
|
||||
config: AppRawRequestConfig | CancelerParams,
|
||||
): boolean {
|
||||
return config.cancelConfig?.cancel ?? true
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param config 请求体 config
|
||||
* @param config - 请求配置
|
||||
*
|
||||
* @returns 请求的唯一标识字符串
|
||||
*
|
||||
* @description
|
||||
* 根据当前请求生成 key。
|
||||
*
|
||||
* @example
|
||||
* const key = generateRequestKey(config) // string
|
||||
* 基于 URL、方法、参数和数据生成请求的唯一标识 key。
|
||||
*/
|
||||
private generateRequestKey(config: AppRawRequestConfig | CancelerParams) {
|
||||
const { method, url } = config
|
||||
private generateRequestKey(
|
||||
config: AppRawRequestConfig | CancelerParams,
|
||||
): string {
|
||||
const { method = '', url = '', params, data } = config
|
||||
|
||||
return [
|
||||
url || '',
|
||||
method || '',
|
||||
JSON.stringify(config.params),
|
||||
JSON.stringify(config.data),
|
||||
].join('&')
|
||||
return [url, method, JSON.stringify(params), JSON.stringify(data)].join('&')
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param config axios request config
|
||||
*
|
||||
|
||||
* @param config - Axios 请求配置
|
||||
|
||||
* @description
|
||||
* 添加请求到 pendingRequest map 中,用于取消请求。
|
||||
* 并且如果已经存在该请求,则会取消上次请求,并且重新挂载 signal。
|
||||
*
|
||||
* 如果不需要该请求被挂载,则需要在请求前添加 cancelConfig.cancel 为 false。
|
||||
* 如果该请求需要被取消,则会添加 __CANCELER_TAG_RAY_TEMPLATE__ 属性,标记是否需要取消。
|
||||
*
|
||||
|
||||
* @example
|
||||
* addPendingRequest(config)
|
||||
* ```ts
|
||||
* // 默认启用取消功能
|
||||
* canceler.addPendingRequest(config)
|
||||
*
|
||||
* // 禁用取消功能
|
||||
* canceler.addPendingRequest({
|
||||
* ...config,
|
||||
* cancelConfig: { cancel: false }
|
||||
* })
|
||||
* ```
|
||||
*/
|
||||
addPendingRequest(config: AppRawRequestConfig | CancelerParams) {
|
||||
if (this.isAppending(config)) {
|
||||
addPendingRequest(config: AppRawRequestConfig | CancelerParams): void {
|
||||
if (!this.shouldAddCanceler(config)) {
|
||||
return
|
||||
}
|
||||
|
||||
config.__CANCELER_TAG_RAY_TEMPLATE__ = '__CANCELER_TAG_RAY_TEMPLATE__'
|
||||
|
||||
const requestKey = this.generateRequestKey(config)
|
||||
const existingController = this.pendingRequest.get(requestKey)
|
||||
|
||||
if (!this.pendingRequest.has(requestKey)) {
|
||||
if (existingController) {
|
||||
// 复用现有的 signal
|
||||
config.signal = existingController.signal
|
||||
} else {
|
||||
// 创建新的 AbortController
|
||||
const controller = new AbortController()
|
||||
|
||||
config.signal = controller.signal
|
||||
|
||||
this.pendingRequest.set(requestKey, controller)
|
||||
} else {
|
||||
// 如果已经有该 key 则重新挂载 signal
|
||||
config.signal = this.pendingRequest.get(requestKey)?.signal
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param config axios request config
|
||||
* @param config - Axios 请求配置
|
||||
*
|
||||
* @description
|
||||
* 移除 pendingRequest map 中的请求,如果存在的话。
|
||||
* 移除并取消特定请求,
|
||||
* 从待处理队列中移除请求,并调用 abort() 取消该请求。
|
||||
*
|
||||
* @example
|
||||
* removePendingRequest(config)
|
||||
* ```ts
|
||||
* canceler.removePendingRequest(config)
|
||||
* ```
|
||||
*/
|
||||
removePendingRequest(config: AppRawRequestConfig | CancelerParams) {
|
||||
removePendingRequest(config: AppRawRequestConfig | CancelerParams): void {
|
||||
const requestKey = this.generateRequestKey(config)
|
||||
const controller = this.pendingRequest.get(requestKey)
|
||||
|
||||
if (this.pendingRequest.has(requestKey)) {
|
||||
this.pendingRequest.get(requestKey)!.abort()
|
||||
if (controller) {
|
||||
controller.abort()
|
||||
this.pendingRequest.delete(requestKey)
|
||||
}
|
||||
}
|
||||
@ -118,17 +131,50 @@ export default class RequestCanceler {
|
||||
/**
|
||||
*
|
||||
* @description
|
||||
* 移除所有 pendingRequest map 中的请求。
|
||||
* 取消所有待处理的请求。
|
||||
*
|
||||
* 值得注意的是,该方法会一次性移除所有的请求,所以需要注意是否有需要在后台挂载的请求;
|
||||
* 如果有需要在后台挂载的请求,则需要在请求前添加 cancelConfig.cancel 为 false。
|
||||
* 遍历并取消队列中的所有请求,然后清空队列。
|
||||
*
|
||||
* ⚠️ 注意:此方法会取消所有请求,包括后台运行的请求。
|
||||
*
|
||||
* 如果某些请求不应被取消,请在请求配置中设置 `cancelConfig.cancel = false`
|
||||
*
|
||||
* @example
|
||||
* cancelAllRequest()
|
||||
* ```ts
|
||||
* // 在路由切换或组件卸载时取消所有请求
|
||||
* canceler.cancelAllRequest()
|
||||
* ```
|
||||
*/
|
||||
cancelAllRequest() {
|
||||
this.pendingRequest.forEach((curr) => {
|
||||
curr.abort()
|
||||
cancelAllRequest(): void {
|
||||
this.pendingRequest.forEach((controller) => {
|
||||
controller.abort()
|
||||
})
|
||||
this.pendingRequest.clear()
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @returns 待处理请求数量
|
||||
*
|
||||
* @description
|
||||
* 获取当前待处理请求的数量。
|
||||
*/
|
||||
getPendingCount(): number {
|
||||
return this.pendingRequest.size
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param config - 请求配置
|
||||
*
|
||||
* @returns 是否存在于待处理队列
|
||||
*
|
||||
* @description
|
||||
* 检查特定请求是否在待处理队列中。
|
||||
*/
|
||||
hasPendingRequest(config: AppRawRequestConfig | CancelerParams): boolean {
|
||||
const requestKey = this.generateRequestKey(config)
|
||||
|
||||
return this.pendingRequest.has(requestKey)
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import type { RawAxiosRequestHeaders, AxiosRequestConfig } from 'axios'
|
||||
import type { AxiosRequestConfig, RawAxiosRequestHeaders } from 'axios'
|
||||
import type { RequestHeaderOptions } from '../types'
|
||||
|
||||
/**
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import type { RawAxiosRequestHeaders, AxiosRequestConfig } from 'axios'
|
||||
import type { AxiosRequestConfig, RawAxiosRequestHeaders } from 'axios'
|
||||
import type { RequestHeaderOptions } from '../types'
|
||||
|
||||
/**
|
||||
|
||||
@ -1,132 +1,183 @@
|
||||
/**
|
||||
*
|
||||
* axios 拦截器注入
|
||||
*
|
||||
* 请求拦截器、响应拦截器
|
||||
* 暴露启动方法调用所有已注册方法
|
||||
*
|
||||
* 该拦截器仅适合放置公共的 axios 拦截器操作, 并且采用队列形式管理请求拦截器的注入
|
||||
* 所以在使用的时候, 需要按照约定格式进行参数传递
|
||||
*/
|
||||
|
||||
import RequestCanceler from '@/axios/utils/RequestCanceler'
|
||||
import { getAppEnvironment } from '@/utils'
|
||||
|
||||
import type {
|
||||
RequestInterceptorConfig,
|
||||
ResponseInterceptorConfig,
|
||||
ImplementQueue,
|
||||
AxiosFetchError,
|
||||
AxiosFetchInstance,
|
||||
ErrorImplementQueue,
|
||||
FetchType,
|
||||
AxiosFetchInstance,
|
||||
AxiosFetchError,
|
||||
ImplementQueue,
|
||||
RequestInterceptorConfig,
|
||||
ResponseInterceptorConfig,
|
||||
} from '@/axios/types'
|
||||
import type { AnyFC } from '@/types'
|
||||
import RequestCanceler from '@/axios/utils/RequestCanceler'
|
||||
import type { AnyFn } from '@/types'
|
||||
import { getAppEnvironment } from '@/utils'
|
||||
import type { AxiosError } from 'axios'
|
||||
|
||||
/** 当前请求的实例 */
|
||||
type ImplementKeys = keyof ImplementQueue
|
||||
type ErrorImplementKeys = keyof ErrorImplementQueue
|
||||
|
||||
// 当前请求的实例
|
||||
const axiosFetchInstance: AxiosFetchInstance = {
|
||||
requestInstance: null,
|
||||
responseInstance: null,
|
||||
}
|
||||
/** 请求失败返回值 */
|
||||
|
||||
// 请求失败返回值
|
||||
const axiosFetchError: AxiosFetchError<AxiosError<unknown, unknown>> = {
|
||||
requestError: null,
|
||||
responseError: null,
|
||||
}
|
||||
/** 请求队列(区分 resolve 与 reject 状态) */
|
||||
|
||||
// 请求队列(区分 resolve 与 reject 状态)
|
||||
const implement: ImplementQueue = {
|
||||
implementRequestInterceptorArray: [],
|
||||
implementResponseInterceptorArray: [],
|
||||
}
|
||||
|
||||
// 请求失败队列
|
||||
const errorImplement: ErrorImplementQueue = {
|
||||
implementRequestInterceptorErrorArray: [],
|
||||
implementResponseInterceptorErrorArray: [],
|
||||
}
|
||||
|
||||
/** 取消器实例 */
|
||||
// 取消器实例
|
||||
export const axiosCanceler = new RequestCanceler()
|
||||
|
||||
export const useAxiosInterceptor = () => {
|
||||
/** 创建拦截器实例 */
|
||||
export const axiosInterceptor = () => {
|
||||
/**
|
||||
*
|
||||
* @param instance - 请求或响应实例
|
||||
* @param instanceKey - 实例类型标识
|
||||
*
|
||||
* @description
|
||||
* 创建拦截器实例。
|
||||
*/
|
||||
const createAxiosInstance = (
|
||||
instance: RequestInterceptorConfig | ResponseInterceptorConfig,
|
||||
instanceKey: keyof AxiosFetchInstance,
|
||||
) => {
|
||||
instanceKey === 'requestInstance'
|
||||
? (axiosFetchInstance['requestInstance'] =
|
||||
instance as RequestInterceptorConfig)
|
||||
: (axiosFetchInstance['responseInstance'] =
|
||||
instance as ResponseInterceptorConfig)
|
||||
): void => {
|
||||
if (instanceKey === 'requestInstance') {
|
||||
axiosFetchInstance.requestInstance = instance as RequestInterceptorConfig
|
||||
} else {
|
||||
axiosFetchInstance.responseInstance =
|
||||
instance as ResponseInterceptorConfig
|
||||
}
|
||||
}
|
||||
|
||||
/** 获取当前实例 */
|
||||
/**
|
||||
*
|
||||
* @param instanceKey - 实例类型标识
|
||||
*
|
||||
* @returns 对应的实例
|
||||
*
|
||||
* @description
|
||||
* 获取当前实例。
|
||||
*/
|
||||
const getAxiosInstance = (instanceKey: keyof AxiosFetchInstance) => {
|
||||
return axiosFetchInstance[instanceKey]
|
||||
}
|
||||
|
||||
/** 设置注入方法队列 */
|
||||
/**
|
||||
*
|
||||
* @param key - 队列键名
|
||||
* @param func - 拦截器函数数组
|
||||
* @param fetchType - 请求类型(成功/失败)
|
||||
*
|
||||
* @description
|
||||
* 设置注入方法队列。
|
||||
*/
|
||||
const setImplement = (
|
||||
key: keyof ImplementQueue | keyof ErrorImplementQueue,
|
||||
func: AnyFC[],
|
||||
key: ImplementKeys | ErrorImplementKeys,
|
||||
func: AnyFn[],
|
||||
fetchType: FetchType,
|
||||
) => {
|
||||
fetchType === 'ok'
|
||||
? (implement[key as keyof ImplementQueue] = func)
|
||||
: (errorImplement[key as keyof ErrorImplementQueue] = func)
|
||||
): void => {
|
||||
if (fetchType === 'ok') {
|
||||
implement[key as ImplementKeys] = func
|
||||
} else {
|
||||
errorImplement[key as ErrorImplementKeys] = func
|
||||
}
|
||||
}
|
||||
|
||||
/** 获取队列中所有的所有拦截器方法 */
|
||||
/**
|
||||
*
|
||||
* @param key - 队列键名
|
||||
* @param fetchType - 请求类型(成功/失败)
|
||||
*
|
||||
* @returns 拦截器函数数组
|
||||
*
|
||||
* @description
|
||||
* 获取队列中所有的拦截器方法。
|
||||
*/
|
||||
const getImplement = (
|
||||
key: keyof ImplementQueue | keyof ErrorImplementQueue,
|
||||
key: ImplementKeys | ErrorImplementKeys,
|
||||
fetchType: FetchType,
|
||||
): AnyFC[] => {
|
||||
): AnyFn[] => {
|
||||
return fetchType === 'ok'
|
||||
? implement[key as keyof ImplementQueue]
|
||||
: errorImplement[key as keyof ErrorImplementQueue]
|
||||
? implement[key as ImplementKeys]
|
||||
: errorImplement[key as ErrorImplementKeys]
|
||||
}
|
||||
|
||||
/** 队列执行器 */
|
||||
const implementer = (funcs: AnyFC[], ...args: any[]) => {
|
||||
if (Array.isArray(funcs)) {
|
||||
funcs.forEach((curr) => {
|
||||
if (typeof curr === 'function') {
|
||||
curr(...args)
|
||||
/**
|
||||
*
|
||||
* @param funcs - 函数数组
|
||||
* @param args - 传递给函数的参数
|
||||
*
|
||||
* @description
|
||||
* 队列执行器 - 执行所有拦截器函数。
|
||||
*/
|
||||
const executeQueue = (funcs: AnyFn[], ...args: unknown[]): void => {
|
||||
funcs.forEach((func) => {
|
||||
if (typeof func === 'function') {
|
||||
func(...args)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/** 请求、响应前执行拦截器队列中的所有方法 */
|
||||
/**
|
||||
*
|
||||
* @param key - 实例类型标识
|
||||
* @param implementKey - 队列键名
|
||||
* @param fetchType - 请求类型(成功/失败)
|
||||
*
|
||||
* @description
|
||||
* 请求、响应前执行拦截器队列中的所有方法。
|
||||
*/
|
||||
const beforeFetch = (
|
||||
key: keyof AxiosFetchInstance,
|
||||
implementKey: keyof ImplementQueue | keyof ErrorImplementQueue,
|
||||
implementKey: ImplementKeys | ErrorImplementKeys,
|
||||
fetchType: FetchType,
|
||||
) => {
|
||||
const funcArr =
|
||||
fetchType === 'ok'
|
||||
? implement[implementKey as keyof ImplementQueue]
|
||||
: errorImplement[implementKey as keyof ErrorImplementQueue]
|
||||
): void => {
|
||||
const instance = getAxiosInstance(key)
|
||||
|
||||
if (!instance) {
|
||||
return
|
||||
}
|
||||
|
||||
const funcArr = getImplement(implementKey, fetchType)
|
||||
const { MODE } = getAppEnvironment()
|
||||
|
||||
if (instance) {
|
||||
implementer(funcArr, instance, MODE)
|
||||
}
|
||||
executeQueue(funcArr, instance, MODE)
|
||||
}
|
||||
|
||||
/** 请求、响应错误时执行队列中所有方法 */
|
||||
/**
|
||||
*
|
||||
* @param key - 错误类型标识
|
||||
* @param error - 错误对象
|
||||
* @param errorImplementKey - 错误队列键名
|
||||
*
|
||||
* @description
|
||||
* 请求、响应错误时执行队列中所有方法。
|
||||
*/
|
||||
const fetchError = (
|
||||
key: keyof AxiosFetchError,
|
||||
error: AxiosError<unknown, unknown>,
|
||||
errorImplementKey: keyof ErrorImplementQueue,
|
||||
) => {
|
||||
errorImplementKey: ErrorImplementKeys,
|
||||
): void => {
|
||||
axiosFetchError[key] = error
|
||||
|
||||
const funcArr = errorImplement[errorImplementKey]
|
||||
const { MODE } = getAppEnvironment()
|
||||
|
||||
implementer(funcArr, error, MODE)
|
||||
executeQueue(funcArr, error, MODE)
|
||||
}
|
||||
|
||||
return {
|
||||
@ -139,4 +190,4 @@ export const useAxiosInterceptor = () => {
|
||||
}
|
||||
}
|
||||
|
||||
export type UseAxiosInterceptor = ReturnType<typeof useAxiosInterceptor>
|
||||
export type AxiosInterceptor = ReturnType<typeof axiosInterceptor>
|
||||
|
||||
@ -1,8 +1,22 @@
|
||||
import type { ExtractPublicPropTypes } from 'vue'
|
||||
import RBarcode from './src/Barcode'
|
||||
import barcodeProps from './src/props'
|
||||
import type { RBarcodeSize } from './src/types'
|
||||
|
||||
import type { ExtractPublicPropTypes } from 'vue'
|
||||
|
||||
export type BarcodeProps = ExtractPublicPropTypes<typeof barcodeProps>
|
||||
// 扩展 BarcodeProps 以提供更好的类型提示
|
||||
export type BarcodeProps = Omit<
|
||||
ExtractPublicPropTypes<typeof barcodeProps>,
|
||||
'width' | 'height'
|
||||
> & {
|
||||
width?: RBarcodeSize
|
||||
height?: RBarcodeSize
|
||||
}
|
||||
|
||||
export { RBarcode, barcodeProps }
|
||||
|
||||
export type {
|
||||
RBarcodeSize,
|
||||
RBarcodeRender,
|
||||
RBarcodeFormat,
|
||||
RBarcodeOptions,
|
||||
} from './src/types'
|
||||
|
||||
@ -1,13 +1,12 @@
|
||||
import './index.scss'
|
||||
|
||||
import { NSpin } from 'naive-ui'
|
||||
|
||||
import { call, completeSize } from '@/utils'
|
||||
import { useResizeObserver } from '@vueuse/core'
|
||||
import type { UseResizeObserverReturn } from '@vueuse/core'
|
||||
import barcode from 'jsbarcode'
|
||||
import props from './props'
|
||||
import { completeSize, call } from '@/utils'
|
||||
import { NSpin } from 'naive-ui'
|
||||
import { useTemplateRef } from 'vue'
|
||||
|
||||
import type { WatchStopHandle } from 'vue'
|
||||
import props from './props'
|
||||
|
||||
export default defineComponent({
|
||||
name: 'RBarcode',
|
||||
@ -16,10 +15,25 @@ export default defineComponent({
|
||||
const barcodeRef = useTemplateRef<HTMLCanvasElement | HTMLOrSVGElement>(
|
||||
'barcodeRef',
|
||||
)
|
||||
const containerRef = useTemplateRef<HTMLDivElement>('containerRef')
|
||||
const containerSize = ref({ width: 0, height: 0 })
|
||||
let resizeObserverReturn: UseResizeObserverReturn | null
|
||||
|
||||
const cssVars = computed(() => {
|
||||
let width = completeSize(props.width)
|
||||
let height = completeSize(props.height)
|
||||
|
||||
if (props.width === 'responsive' && containerSize.value.width > 0) {
|
||||
width = `${containerSize.value.width}px`
|
||||
}
|
||||
|
||||
if (props.height === 'responsive' && containerSize.value.height > 0) {
|
||||
height = `${containerSize.value.height}px`
|
||||
}
|
||||
|
||||
const cssVar = {
|
||||
'--r-barcode-width': completeSize(props.width),
|
||||
'--r-barcode-height': completeSize(props.height),
|
||||
'--r-barcode-width': width,
|
||||
'--r-barcode-height': height,
|
||||
}
|
||||
|
||||
return cssVar
|
||||
@ -27,6 +41,10 @@ export default defineComponent({
|
||||
let watchStop: WatchStopHandle
|
||||
|
||||
const barcodeRender = () => {
|
||||
if (!barcodeRef.value) {
|
||||
return
|
||||
}
|
||||
|
||||
try {
|
||||
const { format, text, options, onSuccess } = props
|
||||
|
||||
@ -34,6 +52,20 @@ export default defineComponent({
|
||||
format,
|
||||
})
|
||||
|
||||
// 如果是响应式模式,根据容器尺寸调整条形码选项
|
||||
if (containerSize.value.width > 0) {
|
||||
if (props.width === 'responsive') {
|
||||
assignOptions.width = Math.max(1, containerSize.value.width / 100)
|
||||
}
|
||||
|
||||
if (props.height === 'responsive') {
|
||||
assignOptions.height = Math.max(
|
||||
20,
|
||||
containerSize.value.height * 0.8,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
barcode(
|
||||
barcodeRef.value,
|
||||
text !== void 0 && text !== null ? text.toString() : '',
|
||||
@ -60,26 +92,64 @@ export default defineComponent({
|
||||
|
||||
watchEffect(() => {
|
||||
if (props.watchText) {
|
||||
watchStop?.()
|
||||
|
||||
watchStop = watch(() => props.text, barcodeRender)
|
||||
} else {
|
||||
watchStop?.()
|
||||
}
|
||||
|
||||
// 监听容器尺寸变化
|
||||
if (props.responsive) {
|
||||
resizeObserverReturn?.stop()
|
||||
|
||||
resizeObserverReturn = useResizeObserver(
|
||||
containerRef,
|
||||
(entries: readonly ResizeObserverEntry[]) => {
|
||||
const entry = entries[0]
|
||||
|
||||
if (entry) {
|
||||
const { width, height } = entry.contentRect
|
||||
|
||||
containerSize.value = { width, height }
|
||||
|
||||
nextTick(() => {
|
||||
barcodeRender()
|
||||
})
|
||||
}
|
||||
},
|
||||
)
|
||||
} else {
|
||||
resizeObserverReturn?.stop()
|
||||
|
||||
resizeObserverReturn = null
|
||||
}
|
||||
})
|
||||
|
||||
onMounted(() => {
|
||||
// 初始化容器尺寸
|
||||
if (containerRef.value) {
|
||||
const rect = containerRef.value.getBoundingClientRect()
|
||||
|
||||
containerSize.value = { width: rect.width, height: rect.height }
|
||||
}
|
||||
|
||||
nextTick(() => {
|
||||
barcodeRender()
|
||||
})
|
||||
})
|
||||
onBeforeUnmount(() => {
|
||||
watchStop?.()
|
||||
})
|
||||
|
||||
return {
|
||||
barcodeRef,
|
||||
containerRef,
|
||||
cssVars,
|
||||
}
|
||||
},
|
||||
render() {
|
||||
const { barcodeRender, loading, cssVars } = this
|
||||
const { barcodeRender, loading, cssVars, responsive } = this
|
||||
const c = [
|
||||
'r-barcode',
|
||||
{
|
||||
@ -87,12 +157,21 @@ export default defineComponent({
|
||||
},
|
||||
]
|
||||
|
||||
return (
|
||||
<NSpin class="r-barcode-spin" show={loading}>
|
||||
{barcodeRender === 'canvas' ? (
|
||||
const barcodeElement =
|
||||
barcodeRender === 'canvas' ? (
|
||||
<canvas class={c} style={cssVars} ref="barcodeRef" />
|
||||
) : (
|
||||
<svg class={c} style={cssVars} ref="barcodeRef" />
|
||||
)
|
||||
|
||||
return (
|
||||
<NSpin class="r-barcode-spin" show={loading}>
|
||||
{responsive ? (
|
||||
<div class="r-barcode-container" ref="containerRef">
|
||||
{barcodeElement}
|
||||
</div>
|
||||
) : (
|
||||
barcodeElement
|
||||
)}
|
||||
</NSpin>
|
||||
)
|
||||
|
||||
@ -9,8 +9,27 @@
|
||||
}
|
||||
}
|
||||
|
||||
.r-barcode-container {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
|
||||
.r-barcode {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
.r-barcode-spin,
|
||||
.r-barcode-spin .n-spin-content {
|
||||
width: max-content !important;
|
||||
height: max-content !important;
|
||||
}
|
||||
|
||||
.r-barcode-spin:has(.r-barcode-container),
|
||||
.r-barcode-spin:has(.r-barcode-container) .n-spin-content {
|
||||
width: 100% !important;
|
||||
height: 100% !important;
|
||||
}
|
||||
|
||||
@ -1,29 +1,50 @@
|
||||
import type { RBarcodeRender, RBarcodeOptions, RBarcodeFormat } from './types'
|
||||
import type { PropType } from 'vue'
|
||||
import type { MaybeArray } from '@/types'
|
||||
import type { PropType } from 'vue'
|
||||
import type {
|
||||
RBarcodeFormat,
|
||||
RBarcodeOptions,
|
||||
RBarcodeRender,
|
||||
RBarcodeSize,
|
||||
} from './types'
|
||||
|
||||
const props = {
|
||||
/**
|
||||
*
|
||||
* @description
|
||||
* 条形码宽度。
|
||||
* - 数字:固定宽度(单位:px)
|
||||
* - 'auto':自动宽度
|
||||
* - 其他字符串:CSS 宽度值(如 '100%', '200px')
|
||||
*
|
||||
* @default 'auto'
|
||||
* @example
|
||||
* width={200}
|
||||
* width="auto"
|
||||
* width="responsive"
|
||||
* width="100%"
|
||||
*/
|
||||
width: {
|
||||
type: [String, Number] as PropType<string | number>,
|
||||
default: 'auto',
|
||||
type: [String, Number] as PropType<RBarcodeSize>,
|
||||
default: 'auto' as const,
|
||||
},
|
||||
/**
|
||||
*
|
||||
* @description
|
||||
* 条形码高度。
|
||||
* - 数字:固定高度(单位:px)
|
||||
* - 'auto':自动高度
|
||||
* - 其他字符串:CSS 高度值(如 '100%', '200px')
|
||||
*
|
||||
* @default 'auto'
|
||||
* @example
|
||||
* height={100}
|
||||
* height="auto"
|
||||
* height="responsive"
|
||||
* height="50%"
|
||||
*/
|
||||
height: {
|
||||
type: [String, Number] as PropType<string | number>,
|
||||
default: 'auto',
|
||||
type: [String, Number] as PropType<RBarcodeSize>,
|
||||
default: 'auto' as const,
|
||||
},
|
||||
/**
|
||||
*
|
||||
@ -132,6 +153,19 @@ const props = {
|
||||
onFinally: {
|
||||
type: [Function, Array] as PropType<MaybeArray<() => void>>,
|
||||
},
|
||||
/**
|
||||
*
|
||||
* @description
|
||||
* 是否启用响应式尺寸,当容器大小变化时自动重新渲染条形码。
|
||||
*
|
||||
* 如果启用了该属性,width 和 height 配置项将失效。
|
||||
*
|
||||
* @default false
|
||||
*/
|
||||
responsive: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
} as const
|
||||
|
||||
export default props
|
||||
|
||||
@ -24,3 +24,6 @@ export type RBarcodeFormat =
|
||||
| 'MSI1110'
|
||||
| 'pharmacode'
|
||||
| 'codabar'
|
||||
|
||||
// 使用模板字面量类型来保留字面量提示
|
||||
export type RBarcodeSize = number | 'auto' | (string & {})
|
||||
|
||||
@ -1,10 +1,9 @@
|
||||
import RChart from './src'
|
||||
import chartProps from './src/props'
|
||||
import useChart from './src/hooks/useChart'
|
||||
|
||||
import type { ExtractPublicPropTypes } from 'vue'
|
||||
import type * as RChartType from './src/types'
|
||||
import RChart from './src/Chart'
|
||||
import useChart from './src/hooks/useChart'
|
||||
import type { UseChartReturn } from './src/hooks/useChart'
|
||||
import chartProps from './src/props'
|
||||
import type * as RChartType from './src/types'
|
||||
|
||||
export type ChartProps = ExtractPublicPropTypes<typeof chartProps>
|
||||
export type { RChartType, UseChartReturn }
|
||||
|
||||
@ -1,52 +1,53 @@
|
||||
import './index.scss'
|
||||
|
||||
import { use, registerTheme, init } from 'echarts/core' // echarts 核心模块
|
||||
import {
|
||||
TitleComponent,
|
||||
TooltipComponent,
|
||||
GridComponent,
|
||||
DatasetComponent,
|
||||
TransformComponent,
|
||||
LegendComponent,
|
||||
ToolboxComponent,
|
||||
AriaComponent,
|
||||
} from 'echarts/components' // 提示框, 标题, 直角坐标系, 数据集, 内置数据转换器等组件(组件后缀都为 Component)
|
||||
import {
|
||||
BarChart,
|
||||
LineChart,
|
||||
PieChart,
|
||||
CandlestickChart,
|
||||
ScatterChart,
|
||||
PictorialBarChart,
|
||||
} from 'echarts/charts' // 系列类型(后缀都为 SeriesOption)
|
||||
import { LabelLayout, UniversalTransition } from 'echarts/features' // 标签自动布局, 全局过渡动画等特性
|
||||
import { CanvasRenderer } from 'echarts/renderers' // echarts 渲染器
|
||||
import { NCard } from 'naive-ui'
|
||||
|
||||
import props from './props'
|
||||
import { throttle } from 'lodash-es'
|
||||
import { completeSize, downloadBase64File, call, renderNode } from '@/utils'
|
||||
import { getCustomEchartTheme, loadingOptions, setEchartOptions } from './utils'
|
||||
import { APP_THEME } from '@/app-config'
|
||||
import {
|
||||
useResizeObserver,
|
||||
useIntersectionObserver,
|
||||
watchThrottled,
|
||||
} from '@vueuse/core'
|
||||
import { RMoreDropdown } from '@/components'
|
||||
import { useSettingGetters } from '@/store'
|
||||
import { useTemplateRef } from 'vue'
|
||||
import { USE_CHART_PROVIDER_KEY } from './config'
|
||||
|
||||
import type { WatchStopHandle } from 'vue'
|
||||
import type { AnyFC } from '@/types'
|
||||
import type { DebouncedFunc } from 'lodash-es'
|
||||
import type {
|
||||
UseResizeObserverReturn,
|
||||
UseIntersectionObserverReturn,
|
||||
import type { AnyFn } from '@/types'
|
||||
import { call, completeSize, downloadBase64File, renderNode } from '@/utils'
|
||||
import {
|
||||
useIntersectionObserver,
|
||||
useResizeObserver,
|
||||
watchThrottled,
|
||||
} from '@vueuse/core'
|
||||
import type {
|
||||
UseIntersectionObserverReturn,
|
||||
UseResizeObserverReturn,
|
||||
} from '@vueuse/core'
|
||||
// 提示框, 标题, 直角坐标系, 数据集, 内置数据转换器等组件(组件后缀都为 Component)
|
||||
import {
|
||||
BarChart,
|
||||
CandlestickChart,
|
||||
LineChart,
|
||||
PictorialBarChart,
|
||||
PieChart,
|
||||
ScatterChart,
|
||||
} from 'echarts/charts'
|
||||
import {
|
||||
AriaComponent,
|
||||
DatasetComponent,
|
||||
GridComponent,
|
||||
LegendComponent,
|
||||
TitleComponent,
|
||||
ToolboxComponent,
|
||||
TooltipComponent,
|
||||
TransformComponent,
|
||||
} from 'echarts/components'
|
||||
import { init, registerTheme, use } from 'echarts/core' // echarts 核心模块
|
||||
|
||||
import type { ECharts, EChartsCoreOption } from 'echarts/core'
|
||||
import type { DropdownProps, DropdownOption } from 'naive-ui'
|
||||
// 系列类型(后缀都为 SeriesOption)
|
||||
import { LegacyGridContainLabel, UniversalTransition } from 'echarts/features' // 标签自动布局, 全局过渡动画等特性
|
||||
import { CanvasRenderer } from 'echarts/renderers' // echarts 渲染器
|
||||
|
||||
import { throttle } from 'lodash-es'
|
||||
import type { DebouncedFunc } from 'lodash-es'
|
||||
import { NCard } from 'naive-ui'
|
||||
import type { DropdownOption, DropdownProps } from 'naive-ui'
|
||||
import { useTemplateRef } from 'vue'
|
||||
import type { WatchStopHandle } from 'vue'
|
||||
import { USE_CHART_PROVIDER_KEY } from './config'
|
||||
import props from './props'
|
||||
import { getCustomEchartTheme, loadingOptions, setEchartOptions } from './utils'
|
||||
|
||||
// 获取 chart 主题
|
||||
const echartThemes = getCustomEchartTheme()
|
||||
@ -88,13 +89,13 @@ export default defineComponent({
|
||||
setup(props, { expose }) {
|
||||
const { getAppTheme } = useSettingGetters()
|
||||
// echart 容器实例
|
||||
const rayChartRef = useTemplateRef<HTMLElement>('rayChartRef')
|
||||
const chartRef = useTemplateRef<HTMLElement>('chartRef')
|
||||
// echart 父容器实例
|
||||
const rayChartWrapperRef = useTemplateRef<HTMLElement>('rayChartWrapperRef')
|
||||
// echart 实例
|
||||
const echartInstanceRef = shallowRef<ECharts>()
|
||||
// resize 防抖方法实例
|
||||
let resizeThrottleReturn: DebouncedFunc<AnyFC> | null
|
||||
let resizeThrottleReturn: DebouncedFunc<AnyFn> | null
|
||||
// resize observer 实例
|
||||
let resizeObserverReturn: UseResizeObserverReturn | null
|
||||
// 当前配置主题
|
||||
@ -113,8 +114,8 @@ export default defineComponent({
|
||||
])
|
||||
const cssVarsRef = computed(() => {
|
||||
return {
|
||||
'--ray-chart-width': completeSize(props.width),
|
||||
'--ray-chart-height': completeSize(props.height),
|
||||
'--r-chart-width': completeSize(props.width),
|
||||
'--r-chart-height': completeSize(props.height),
|
||||
}
|
||||
})
|
||||
// 目标是否可见
|
||||
@ -154,7 +155,7 @@ export default defineComponent({
|
||||
ScatterChart,
|
||||
PictorialBarChart,
|
||||
]) // 注册 chart series type
|
||||
use([LabelLayout, UniversalTransition]) // 注册布局, 过度效果
|
||||
use([LegacyGridContainLabel, UniversalTransition]) // 注册布局, 过度效果
|
||||
use([CanvasRenderer]) // 注册渲染器
|
||||
|
||||
try {
|
||||
@ -241,7 +242,7 @@ export default defineComponent({
|
||||
*/
|
||||
const renderChart = (theme: string = echartTheme) => {
|
||||
// 获取 dom 容器
|
||||
const element = rayChartRef.value as HTMLElement
|
||||
const element = chartRef.value as HTMLElement
|
||||
// 获取配置项
|
||||
const options = combineChartOptions(props.options)
|
||||
// 获取 dom 容器实际宽高
|
||||
@ -264,7 +265,7 @@ export default defineComponent({
|
||||
|
||||
// 是否强制下一队列渲染图表
|
||||
if (props.nextTick) {
|
||||
echartInstanceRef.value.setOption({})
|
||||
// echartInstanceRef.value.setOption({})
|
||||
|
||||
nextTick(() => {
|
||||
options && echartInstanceRef.value?.setOption(options)
|
||||
@ -366,7 +367,7 @@ export default defineComponent({
|
||||
if (!resizeObserverReturn) {
|
||||
resizeObserverReturn = useResizeObserver(
|
||||
props.autoResizeObserverTarget || rayChartWrapperRef,
|
||||
resizeThrottleReturn as AnyFC,
|
||||
resizeThrottleReturn as AnyFn,
|
||||
)
|
||||
}
|
||||
}
|
||||
@ -435,6 +436,8 @@ export default defineComponent({
|
||||
watchEffect(() => {
|
||||
// 是否启用了可视区域监听
|
||||
if (props.intersectionObserver) {
|
||||
intersectionObserverReturn?.stop()
|
||||
|
||||
intersectionObserverReturn = useIntersectionObserver(
|
||||
props.intersectionObserverTarget || rayChartWrapperRef,
|
||||
([entry]) => {
|
||||
@ -442,10 +445,14 @@ export default defineComponent({
|
||||
},
|
||||
props.intersectionOptions,
|
||||
)
|
||||
} else {
|
||||
intersectionObserverReturn?.stop()
|
||||
}
|
||||
|
||||
// 监听 options 变化
|
||||
if (props.watchOptions) {
|
||||
watchThrottledCallback?.()
|
||||
|
||||
watchThrottledCallback = watchThrottled(
|
||||
() => props.options,
|
||||
(ndata) => {
|
||||
@ -462,7 +469,7 @@ export default defineComponent({
|
||||
},
|
||||
{
|
||||
// 深度监听 options
|
||||
deep: true,
|
||||
deep: props.watchDeep,
|
||||
throttle: props.watchOptionsThrottleWait,
|
||||
},
|
||||
)
|
||||
@ -506,7 +513,7 @@ export default defineComponent({
|
||||
})
|
||||
|
||||
return {
|
||||
rayChartRef,
|
||||
chartRef,
|
||||
cssVarsRef,
|
||||
rayChartWrapperRef,
|
||||
moreDropDownOptions,
|
||||
@ -536,7 +543,7 @@ export default defineComponent({
|
||||
>
|
||||
{{
|
||||
default: renderNode(
|
||||
<div class="ray-chart__container" ref="rayChartRef"></div>,
|
||||
<div class="ray-chart__container" ref="chartRef"></div>,
|
||||
),
|
||||
header: renderNode(title, {
|
||||
defaultElement: <div style="display: none;"></div>,
|
||||
@ -557,7 +564,7 @@ export default defineComponent({
|
||||
</NCard>
|
||||
) : (
|
||||
<div class="ray-chart" style={[this.cssVarsRef]} ref="rayChartWrapperRef">
|
||||
<div class="ray-chart__container" ref="rayChartRef"></div>
|
||||
<div class="ray-chart__container" ref="chartRef"></div>
|
||||
</div>
|
||||
)
|
||||
},
|
||||
@ -1,5 +1,5 @@
|
||||
import type { ECharts } from 'echarts/core'
|
||||
import type { VoidFC } from '@/types'
|
||||
import type { ECharts } from 'echarts/core'
|
||||
|
||||
/**
|
||||
*
|
||||
|
||||
@ -1,5 +1,4 @@
|
||||
import { USE_CHART_PROVIDER_KEY } from '../config'
|
||||
|
||||
import type { ChartTheme } from '../types'
|
||||
|
||||
export interface ChartProviderOptions {
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
.ray-chart {
|
||||
width: var(--ray-chart-width);
|
||||
height: var(--ray-chart-height);
|
||||
width: var(--r-chart-width);
|
||||
height: var(--r-chart-height);
|
||||
border: none;
|
||||
outline: none;
|
||||
box-sizing: border-box;
|
||||
|
||||
@ -1,23 +1,22 @@
|
||||
import { loadingOptions, setEchartOptions } from './utils'
|
||||
|
||||
import type * as echarts from 'echarts/core' // echarts 核心模块
|
||||
import type { PropType, VNode } from 'vue'
|
||||
import type { MaybeArray } from '@/types'
|
||||
import type { ECharts, SetOptionOpts } from 'echarts/core'
|
||||
import type { MaybeArray, VoidFC } from '@/types'
|
||||
import type {
|
||||
MaybeComputedElementRef,
|
||||
MaybeElement,
|
||||
UseIntersectionObserverOptions,
|
||||
} from '@vueuse/core'
|
||||
import type * as echarts from 'echarts/core' // echarts 核心模块
|
||||
|
||||
import type { ECharts, SetOptionOpts } from 'echarts/core'
|
||||
import type { CardProps, DropdownOption, DropdownProps } from 'naive-ui'
|
||||
import type { PropType, VNode } from 'vue'
|
||||
import type {
|
||||
LoadingOptions,
|
||||
ChartTheme,
|
||||
EChartsExtensionInstallRegisters,
|
||||
RChartPresetType,
|
||||
LoadingOptions,
|
||||
RChartDownloadOptions,
|
||||
RChartPresetType,
|
||||
} from './types'
|
||||
import type { CardProps, DropdownProps, DropdownOption } from 'naive-ui'
|
||||
import type { VoidFC } from '@/types'
|
||||
import { loadingOptions, setEchartOptions } from './utils'
|
||||
|
||||
const props = {
|
||||
/**
|
||||
@ -341,6 +340,17 @@ const props = {
|
||||
type: Number,
|
||||
default: 500,
|
||||
},
|
||||
/**
|
||||
*
|
||||
* @description
|
||||
* 是否深度监听 options 配置项。
|
||||
*
|
||||
* @default true
|
||||
*/
|
||||
watchDeep: {
|
||||
type: Boolean,
|
||||
default: true,
|
||||
},
|
||||
/**
|
||||
*
|
||||
* @description
|
||||
|
||||
@ -13,12 +13,12 @@ import type { ECharts } from 'echarts/core'
|
||||
import type { CanvasRenderer } from 'echarts/renderers' // `echarts` 渲染器
|
||||
|
||||
export interface ChartThemeRawModules {
|
||||
default: Record<string, UnknownObjectKey>
|
||||
default: Record<string, GlobalRecordable>
|
||||
}
|
||||
|
||||
export interface ChartThemeRawArray {
|
||||
name: string
|
||||
theme: UnknownObjectKey
|
||||
theme: GlobalRecordable
|
||||
}
|
||||
|
||||
export interface LoadingOptions {
|
||||
|
||||
@ -1,6 +1,5 @@
|
||||
import { useTheme } from '@/hooks'
|
||||
|
||||
import type { LoadingOptions } from '@/components/base/RChart/src/types'
|
||||
import { useTheme } from '@/hooks'
|
||||
|
||||
/**
|
||||
*
|
||||
|
||||
@ -1,8 +1,7 @@
|
||||
import type { ExtractPublicPropTypes } from 'vue'
|
||||
import RCollapseGrid from './src'
|
||||
import collapseGridProps from './src/props'
|
||||
|
||||
import type * as RCollapseGridType from './src/types'
|
||||
import type { ExtractPublicPropTypes } from 'vue'
|
||||
|
||||
export type CollapseGridProps = ExtractPublicPropTypes<typeof collapseGridProps>
|
||||
export type { RCollapseGridType }
|
||||
|
||||
@ -9,14 +9,11 @@
|
||||
*/
|
||||
|
||||
import './index.scss'
|
||||
|
||||
import { NCard, NGrid, NGridItem, NFlex } from 'naive-ui'
|
||||
import { RIcon } from '@/components'
|
||||
|
||||
import { call } from '@/utils'
|
||||
import props from './props'
|
||||
|
||||
import { NCard, NFlex, NGrid, NGridItem } from 'naive-ui'
|
||||
import type { GridProps } from 'naive-ui'
|
||||
import props from './props'
|
||||
|
||||
export default defineComponent({
|
||||
name: 'RCollapseGrid',
|
||||
|
||||
@ -1,8 +1,7 @@
|
||||
import type { AnyFn, MaybeArray } from '@/types'
|
||||
import { gridProps } from 'naive-ui'
|
||||
|
||||
import type { PropType } from 'vue'
|
||||
import type { CollapseToggleText, ActionAlignType } from './types'
|
||||
import type { AnyFC, MaybeArray } from '@/types'
|
||||
import type { ActionAlignType, CollapseToggleText } from './types'
|
||||
|
||||
const props = {
|
||||
/**
|
||||
|
||||
384
src/components/base/RDraggableCard/DraggableCard.tsx
Normal file
384
src/components/base/RDraggableCard/DraggableCard.tsx
Normal file
@ -0,0 +1,384 @@
|
||||
import './index.scss'
|
||||
import type { AnyFn } from '@/types'
|
||||
import { completeSize, queryElements, unrefElement } from '@/utils'
|
||||
import type { MaybeElement, MaybeRefOrGetter } from '@vueuse/core'
|
||||
import interact from 'interactjs'
|
||||
import { cardProps, NCard } from 'naive-ui'
|
||||
import { Teleport, Transition } from 'vue'
|
||||
import type { VNode } from 'vue'
|
||||
|
||||
type RestrictRectOptions = Parameters<typeof interact.modifiers.restrictRect>[0]
|
||||
|
||||
type Padding = {
|
||||
x: number
|
||||
y: number
|
||||
}
|
||||
|
||||
export type DefaultPosition =
|
||||
| Padding
|
||||
| 'top-left'
|
||||
| 'top-right'
|
||||
| 'bottom-left'
|
||||
| 'bottom-right'
|
||||
| 'center'
|
||||
| 'top-center'
|
||||
| 'bottom-center'
|
||||
|
||||
const props = {
|
||||
...cardProps,
|
||||
/**
|
||||
*
|
||||
* @description
|
||||
* 需要限制的区域位置。
|
||||
*
|
||||
* @default body
|
||||
*/
|
||||
restrictionElement: {
|
||||
type: [String, HTMLElement, Function, Object] as PropType<
|
||||
string | HTMLElement | (() => VNode) | MaybeRefOrGetter<MaybeElement>
|
||||
>,
|
||||
default: 'body',
|
||||
},
|
||||
/**
|
||||
*
|
||||
* @description
|
||||
* 是否启用拖拽。
|
||||
*
|
||||
* @default true
|
||||
*/
|
||||
dad: {
|
||||
type: Boolean,
|
||||
default: true,
|
||||
},
|
||||
/**
|
||||
*
|
||||
* @description
|
||||
* 自定义限制拖拽范围。
|
||||
*
|
||||
* @default undefined
|
||||
*/
|
||||
restrictRectOptions: {
|
||||
type: Object as PropType<RestrictRectOptions>,
|
||||
default: void 0,
|
||||
},
|
||||
/**
|
||||
*
|
||||
* @description
|
||||
* 默认位置。
|
||||
*
|
||||
* @default { x: 0, y: 0 }
|
||||
*/
|
||||
defaultPosition: {
|
||||
type: [Object, String] as PropType<DefaultPosition>,
|
||||
default: () => ({
|
||||
x: 0,
|
||||
y: 0,
|
||||
}),
|
||||
},
|
||||
/**
|
||||
*
|
||||
* @description
|
||||
* 拖拽卡片宽度。
|
||||
*
|
||||
* @default 600
|
||||
*/
|
||||
width: {
|
||||
type: [String, Number] as PropType<string | number>,
|
||||
default: 600,
|
||||
},
|
||||
/**
|
||||
*
|
||||
* @description
|
||||
* 拖拽卡片 z-index。
|
||||
*
|
||||
* @default undefined
|
||||
*/
|
||||
zIndex: {
|
||||
type: Number,
|
||||
default: void 0,
|
||||
},
|
||||
/**
|
||||
*
|
||||
* @description
|
||||
* 是否启用动画。
|
||||
*
|
||||
* @default false
|
||||
*/
|
||||
animation: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
/**
|
||||
*
|
||||
* @description
|
||||
* 默认的边距。
|
||||
* 设置该属性后,卡片首次出现的位置会根据该属性进行偏移。
|
||||
*
|
||||
* @default undefined
|
||||
*/
|
||||
padding: {
|
||||
type: Object as PropType<Padding>,
|
||||
default: void 0,
|
||||
},
|
||||
}
|
||||
|
||||
export default defineComponent({
|
||||
name: 'RDraggableCard',
|
||||
props,
|
||||
setup(props, { expose }) {
|
||||
const cardRef = useTemplateRef<HTMLElement>('cardRef')
|
||||
let interactInst: ReturnType<typeof interact> | null = null
|
||||
const position = {
|
||||
x: 0,
|
||||
y: 0,
|
||||
}
|
||||
const CONTAINER_ID = 'r-draggable-card-container'
|
||||
const cssVars = computed(() => {
|
||||
return {
|
||||
'--r-draggable-card-width': completeSize(props.width),
|
||||
'--r-draggable-card-z-index': props.zIndex,
|
||||
}
|
||||
})
|
||||
let isSetup = false
|
||||
const cacheProps = {
|
||||
defaultPosition: props.defaultPosition,
|
||||
dad: props.dad,
|
||||
}
|
||||
|
||||
// 创建 DraggableCard 容器
|
||||
const createDraggableCardContainer = () => {
|
||||
if (!document.getElementById(CONTAINER_ID)) {
|
||||
const container = document.createElement('div')
|
||||
|
||||
container.id = CONTAINER_ID
|
||||
document.documentElement.appendChild(container)
|
||||
}
|
||||
}
|
||||
|
||||
createDraggableCardContainer()
|
||||
|
||||
// 获取 card, restrictionElement 的 dom 信息
|
||||
const getDom = () => {
|
||||
const card = unrefElement(cardRef)
|
||||
const re =
|
||||
typeof props.restrictionElement === 'string'
|
||||
? queryElements<HTMLElement>(props.restrictionElement)
|
||||
: props.restrictionElement
|
||||
let restrictionElement: HTMLElement | null = null
|
||||
|
||||
if (Array.isArray(re)) {
|
||||
restrictionElement = re[0]
|
||||
} else {
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
restrictionElement = unrefElement<HTMLElement>(re as any) as HTMLElement
|
||||
}
|
||||
|
||||
return {
|
||||
card,
|
||||
restrictionElement,
|
||||
}
|
||||
}
|
||||
|
||||
// 获取 container, card 的位置
|
||||
const getPosition = (containerRect: DOMRect, cardRect: DOMRect) => {
|
||||
const { defaultPosition, padding } = props
|
||||
const { x: paddingX = 0, y: paddingY = 0 } = padding ?? {}
|
||||
// 默认的 body restrictionElement 的偏移量是 0
|
||||
const {
|
||||
x: containerX,
|
||||
y: containerY,
|
||||
width: containerWidth,
|
||||
height: containerHeight,
|
||||
} = containerRect
|
||||
const { width: cardWidth, height: cardHeight } = cardRect
|
||||
|
||||
if (typeof defaultPosition === 'string') {
|
||||
switch (defaultPosition) {
|
||||
case 'top-center': {
|
||||
const cx1 = (containerWidth - cardWidth) / 2 + containerX
|
||||
const cy1 = paddingY + containerY
|
||||
const cx2 = paddingX + cx1
|
||||
const cy2 = cy1
|
||||
|
||||
return { x: cx2, y: cy2 }
|
||||
}
|
||||
|
||||
case 'bottom-center': {
|
||||
const cx1 = (containerWidth - cardWidth) / 2 + containerX
|
||||
const cy1 = containerHeight - cardHeight - paddingY + containerY
|
||||
const cx2 = paddingX + cx1
|
||||
const cy2 = cy1
|
||||
|
||||
return { x: cx2, y: cy2 }
|
||||
}
|
||||
|
||||
case 'center': {
|
||||
const cx1 = (containerWidth - cardWidth) / 2 + containerX
|
||||
const cy1 = (containerHeight - cardHeight) / 2 + containerY
|
||||
const cx2 = paddingX + cx1
|
||||
const cy2 = paddingY + cy1
|
||||
|
||||
return { x: cx2, y: cy2 }
|
||||
}
|
||||
|
||||
case 'top-left':
|
||||
return { x: paddingX + containerX, y: paddingY + containerY }
|
||||
|
||||
case 'top-right':
|
||||
return {
|
||||
x: containerWidth - cardWidth - paddingX + containerX,
|
||||
y: paddingY + containerY,
|
||||
}
|
||||
|
||||
case 'bottom-left':
|
||||
return {
|
||||
x: paddingX + containerX,
|
||||
y: containerHeight - cardHeight - paddingY + containerY,
|
||||
}
|
||||
|
||||
case 'bottom-right':
|
||||
return {
|
||||
x: containerWidth - cardWidth - paddingX + containerX,
|
||||
y: containerHeight - cardHeight - paddingY + containerY,
|
||||
}
|
||||
|
||||
// 默认为左上角
|
||||
default:
|
||||
return { x: paddingX + containerX, y: paddingY + containerY }
|
||||
}
|
||||
} else {
|
||||
const { x: defaultX, y: defaultY } = defaultPosition
|
||||
|
||||
return {
|
||||
x: defaultX + containerX + paddingX,
|
||||
y: defaultY + containerY + paddingY,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 初始化设置 card 的位置,并且根据配置启用拖拽
|
||||
const setupDraggable = () => {
|
||||
const { card, restrictionElement } = getDom()
|
||||
|
||||
if (!card) {
|
||||
return
|
||||
}
|
||||
|
||||
const restrictionRect = restrictionElement?.getBoundingClientRect()
|
||||
const cardHeader = card.querySelector('.n-card-header')
|
||||
const restrictRectOptions = Object.assign(
|
||||
{},
|
||||
{
|
||||
restriction: restrictionElement,
|
||||
endOnly: true,
|
||||
},
|
||||
props.restrictRectOptions,
|
||||
)
|
||||
|
||||
if (restrictionRect && !isSetup) {
|
||||
// 计算偏移位置
|
||||
const p = getPosition(restrictionRect, card.getBoundingClientRect())
|
||||
|
||||
// 设置初始位置
|
||||
card.style.transform = `translate(${p.x}px, ${p.y}px)`
|
||||
position.x = p.x
|
||||
position.y = p.y
|
||||
}
|
||||
|
||||
if (!props.dad) {
|
||||
return
|
||||
}
|
||||
|
||||
interactInst = interact(card)
|
||||
.draggable({
|
||||
inertia: true,
|
||||
autoScroll: true,
|
||||
allowFrom: cardHeader ? '.n-card-header' : '.n-card__content',
|
||||
modifiers: [interact.modifiers.restrictRect(restrictRectOptions)],
|
||||
listeners: {
|
||||
move: (event) => {
|
||||
card.setAttribute('can-drag', 'true')
|
||||
|
||||
position.x += event.dx
|
||||
position.y += event.dy
|
||||
|
||||
card.style.transform = `translate(${position.x}px, ${position.y}px)`
|
||||
},
|
||||
},
|
||||
})
|
||||
.resizable(false)
|
||||
|
||||
isSetup = true
|
||||
}
|
||||
|
||||
// 取消拖拽
|
||||
const resetDraggable = () => {
|
||||
interactInst?.unset()
|
||||
|
||||
interactInst = null
|
||||
}
|
||||
|
||||
// 更新拖拽
|
||||
const refreshDraggableWhenPropsChange = (fn: AnyFn) => {
|
||||
isSetup = false
|
||||
|
||||
fn()
|
||||
setupDraggable()
|
||||
}
|
||||
|
||||
expose()
|
||||
|
||||
watchEffect(() => {
|
||||
props.dad ? setupDraggable() : resetDraggable()
|
||||
|
||||
if (props.defaultPosition !== cacheProps.defaultPosition) {
|
||||
refreshDraggableWhenPropsChange(() => {
|
||||
cacheProps.defaultPosition = props.defaultPosition
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
onMounted(() => {
|
||||
nextTick(() => {
|
||||
setupDraggable()
|
||||
})
|
||||
})
|
||||
|
||||
return {
|
||||
cardRef,
|
||||
CONTAINER_ID,
|
||||
cssVars,
|
||||
}
|
||||
},
|
||||
render() {
|
||||
const { $attrs, $slots, $props, CONTAINER_ID, cssVars, animation } = this
|
||||
|
||||
return (
|
||||
<Teleport to={`#${CONTAINER_ID}`}>
|
||||
{animation ? (
|
||||
<Transition name="draggable-card" appear mode="out-in">
|
||||
<NCard
|
||||
{...$attrs}
|
||||
{...$props}
|
||||
class="r-draggable-card"
|
||||
style={[cssVars]}
|
||||
ref="cardRef"
|
||||
>
|
||||
{{ ...$slots }}
|
||||
</NCard>
|
||||
</Transition>
|
||||
) : (
|
||||
<NCard
|
||||
{...$attrs}
|
||||
{...$props}
|
||||
class="r-draggable-card"
|
||||
style={[cssVars]}
|
||||
ref="cardRef"
|
||||
>
|
||||
{{ ...$slots }}
|
||||
</NCard>
|
||||
)}
|
||||
</Teleport>
|
||||
)
|
||||
},
|
||||
})
|
||||
25
src/components/base/RDraggableCard/index.scss
Normal file
25
src/components/base/RDraggableCard/index.scss
Normal file
@ -0,0 +1,25 @@
|
||||
.n-card.r-draggable-card {
|
||||
transform-origin: 0 0;
|
||||
position: absolute;
|
||||
width: var(--r-draggable-card-width);
|
||||
z-index: var(--r-draggable-card-z-index);
|
||||
}
|
||||
|
||||
#r-draggable-card-container {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 0;
|
||||
height: 0;
|
||||
}
|
||||
|
||||
// draggable-card 的 Transition 样式
|
||||
.draggable-card-enter-active,
|
||||
.draggable-card-leave-active {
|
||||
transition: opacity 0.3s var(--r-bezier);
|
||||
}
|
||||
|
||||
.draggable-card-enter-from,
|
||||
.draggable-card-leave-to {
|
||||
opacity: 0;
|
||||
}
|
||||
@ -1,8 +1,7 @@
|
||||
import RFlow from './src/Flow'
|
||||
import flowProps from './src/props'
|
||||
import { useFlow } from './src/hooks'
|
||||
|
||||
import type { ExtractPublicPropTypes } from 'vue'
|
||||
import RFlow from './src/Flow'
|
||||
import { useFlow } from './src/hooks'
|
||||
import flowProps from './src/props'
|
||||
|
||||
export type FlowProps = ExtractPublicPropTypes<typeof flowProps>
|
||||
|
||||
|
||||
@ -1,14 +1,12 @@
|
||||
import './index.scss'
|
||||
import '@logicflow/core/lib/style/index.css'
|
||||
|
||||
import { useTemplateRef } from 'vue'
|
||||
import props from './props'
|
||||
import { completeSize, call } from '@/utils'
|
||||
import { call, completeSize } from '@/utils'
|
||||
import LogicFlow from '@logicflow/core'
|
||||
import { omit } from 'lodash-es'
|
||||
|
||||
import type { FlowGraphData, G } from './types'
|
||||
import { useTemplateRef } from 'vue'
|
||||
import type { WatchStopHandle } from 'vue'
|
||||
import props from './props'
|
||||
import type { FlowGraphData, G } from './types'
|
||||
|
||||
// 是否首次注册插件
|
||||
let isSetup = false
|
||||
|
||||
@ -1,8 +1,7 @@
|
||||
import { getDefaultFlowOptions } from './constant'
|
||||
|
||||
import type { FlowGraphData, FlowOptions, ExtensionType } from './types'
|
||||
import type LogicFlow from '@logicflow/core'
|
||||
import type { MaybeArray } from '@/types'
|
||||
import type LogicFlow from '@logicflow/core'
|
||||
import { getDefaultFlowOptions } from './constant'
|
||||
import type { ExtensionType, FlowGraphData, FlowOptions } from './types'
|
||||
|
||||
const props = {
|
||||
/**
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
import type LogicFlow from '@logicflow/core'
|
||||
import type { Recordable, SetRequired } from '@/types'
|
||||
import type LogicFlow from '@logicflow/core'
|
||||
|
||||
/**
|
||||
*
|
||||
|
||||
@ -1,10 +1,9 @@
|
||||
import RForm from './src/Form'
|
||||
import formProps from './src/props'
|
||||
import useForm from './src/hooks/useForm'
|
||||
|
||||
import type * as RFormType from './src/types'
|
||||
import type { ExtractPublicPropTypes } from 'vue'
|
||||
import RForm from './src/Form'
|
||||
import useForm from './src/hooks/useForm'
|
||||
import type { UseFormReturn } from './src/hooks/useForm'
|
||||
import formProps from './src/props'
|
||||
import type * as RFormType from './src/types'
|
||||
|
||||
export type FormProps = ExtractPublicPropTypes<typeof formProps>
|
||||
export type { RFormType, UseFormReturn }
|
||||
|
||||
@ -1,17 +1,39 @@
|
||||
import { NForm } from 'naive-ui'
|
||||
|
||||
import props from './props'
|
||||
import { call } from '@/utils'
|
||||
import { call, unrefElement } from '@/utils'
|
||||
import { useEventListener } from '@vueuse/core'
|
||||
import { NForm, NSpin } from 'naive-ui'
|
||||
import { useTemplateRef } from 'vue'
|
||||
|
||||
import type { ShallowRef } from 'vue'
|
||||
import props from './props'
|
||||
import type { RFormInst } from './types'
|
||||
import type { FormProps } from 'naive-ui'
|
||||
|
||||
export default defineComponent({
|
||||
name: 'RForm',
|
||||
props,
|
||||
setup(props, { expose }) {
|
||||
const formRef = useTemplateRef<RFormInst>('formRef')
|
||||
const currentSubmitFn = computed(() => props.onFinish ?? Promise.resolve)
|
||||
|
||||
const bindKeydownListener = (e: KeyboardEvent) => {
|
||||
const keyCode = e.code
|
||||
|
||||
if (keyCode === 'Enter') {
|
||||
e.stopPropagation()
|
||||
e.preventDefault()
|
||||
|
||||
formRef.value?.validate().then(currentSubmitFn.value)
|
||||
}
|
||||
}
|
||||
|
||||
if (props.submitWhenEnter) {
|
||||
useEventListener(
|
||||
formRef as unknown as ShallowRef<HTMLElement>,
|
||||
'keydown',
|
||||
bindKeydownListener,
|
||||
{
|
||||
capture: true,
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
// 主动调用 register 方法,满足 useForm 方法正常调用
|
||||
@ -20,6 +42,16 @@ export default defineComponent({
|
||||
if (onRegister && formRef.value) {
|
||||
call(onRegister, formRef.value)
|
||||
}
|
||||
|
||||
if (formRef.value) {
|
||||
const formElement = unrefElement(
|
||||
formRef.value as unknown as HTMLFormElement,
|
||||
)
|
||||
|
||||
if (formElement) {
|
||||
formElement.autocomplete = props.autocomplete
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
expose()
|
||||
@ -30,13 +62,22 @@ export default defineComponent({
|
||||
},
|
||||
render() {
|
||||
const { $attrs, $props, $slots } = this
|
||||
const { loading, loadingDescription, ...restProps } = $props
|
||||
|
||||
return (
|
||||
<NForm {...$attrs} {...($props as FormProps)} ref="formRef">
|
||||
<NSpin
|
||||
show={loading}
|
||||
description={loadingDescription}
|
||||
style={{
|
||||
height: 'auto',
|
||||
}}
|
||||
>
|
||||
<NForm {...$attrs} {...restProps} ref="formRef">
|
||||
{{
|
||||
...$slots,
|
||||
}}
|
||||
</NForm>
|
||||
</NSpin>
|
||||
)
|
||||
},
|
||||
})
|
||||
|
||||
@ -1,12 +1,11 @@
|
||||
import { cloneDeep } from 'lodash-es'
|
||||
|
||||
import type {
|
||||
RFormInst,
|
||||
FormValidateCallback,
|
||||
ShouldRuleBeApplied,
|
||||
RFormRules,
|
||||
} from '../types'
|
||||
import type { Recordable } from '@/types'
|
||||
import { cloneDeep } from 'lodash-es'
|
||||
import type {
|
||||
FormValidateCallback,
|
||||
RFormInst,
|
||||
RFormRules,
|
||||
ShouldRuleBeApplied,
|
||||
} from '../types'
|
||||
|
||||
/**
|
||||
*
|
||||
@ -36,11 +35,15 @@ import type { Recordable } from '@/types'
|
||||
* },
|
||||
* })
|
||||
*/
|
||||
const useForm = <T extends Recordable, R extends RFormRules>(
|
||||
model?: T,
|
||||
rules?: R,
|
||||
const useForm = <
|
||||
T extends Recordable = Recordable,
|
||||
R extends RFormRules = RFormRules,
|
||||
>(
|
||||
model?: T | (() => T),
|
||||
rules?: R | (() => R),
|
||||
) => {
|
||||
const formRef = shallowRef<RFormInst>()
|
||||
const formModelRef = ref<T>()
|
||||
|
||||
const register = (inst: RFormInst) => {
|
||||
if (inst) {
|
||||
@ -58,6 +61,15 @@ const useForm = <T extends Recordable, R extends RFormRules>(
|
||||
return formRef.value
|
||||
}
|
||||
|
||||
// 初始化 formModelRef 的值,根据 model 的类型进行初始化
|
||||
const initialFormModel = () => {
|
||||
if (typeof model === 'function') {
|
||||
formModelRef.value = model() ?? ({} as T)
|
||||
} else {
|
||||
formModelRef.value = cloneDeep(model) ?? ({} as T)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @description
|
||||
@ -83,10 +95,39 @@ const useForm = <T extends Recordable, R extends RFormRules>(
|
||||
*
|
||||
* @description
|
||||
* 获取表项中收集到的值的对象。
|
||||
*
|
||||
* 调用该方法时,需要确保初始化 useForm 方法的时候传入了 model,否则可能有意想不到的问题发生。
|
||||
*
|
||||
* 该方法可以实现重置表单的需求,因为在 vue 的设计理念中,表单的值绑定是直接绑定在每个组件上,
|
||||
* 而不是利用 Form 表单机制,所以需要这么去做表单的初始化操作维护机制。
|
||||
*
|
||||
* 在 5.2.2 版本中,新增了 formConditionRef 属性,现在可以解构获取一个 ref 包裹的响应式初始化表单对象值;
|
||||
* 这样就可以直接在 hook 中调用一个响应式的表单对象值。
|
||||
*
|
||||
* @example
|
||||
*
|
||||
* interface FormModel {
|
||||
* name: string | null
|
||||
* age: number | null
|
||||
* }
|
||||
*
|
||||
* const [register, { formModel }] = useForm<FormModel>({
|
||||
* name: null,
|
||||
* age: null,
|
||||
* })
|
||||
*
|
||||
* const formModelRef = ref(formModel())
|
||||
*
|
||||
* const reset = () => {
|
||||
* formModelRef.value = formModel()
|
||||
* }
|
||||
*/
|
||||
const formModel = () => cloneDeep(model) || ({} as T)
|
||||
const formModel = (): T & Recordable => {
|
||||
if (typeof model === 'function') {
|
||||
return model()
|
||||
}
|
||||
|
||||
return cloneDeep(model) || ({} as T)
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
@ -95,7 +136,103 @@ const useForm = <T extends Recordable, R extends RFormRules>(
|
||||
*
|
||||
* 调用该方法时,需要确保初始化 useForm 方法的时候传入了 rules,否则可能有意想不到的问题发生。
|
||||
*/
|
||||
const formRules = () => cloneDeep(rules) || ({} as R)
|
||||
const formRules = () => {
|
||||
if (typeof rules === 'function') {
|
||||
return rules()
|
||||
}
|
||||
|
||||
return cloneDeep(rules) || ({} as R)
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param values 需要重置的表单值,该参数会覆盖初始化值
|
||||
*
|
||||
* @warning
|
||||
* 请注意初始化值的问题,如果设置初始化值为 undefined,
|
||||
* 则会导致 reset 初始化操作失败,必须使用 null 作为初始化的空值。
|
||||
*
|
||||
* @description
|
||||
* 重置表单的值,依赖 useForm 传入的初始化值。
|
||||
* 该方法会将初始化传入的值视为整个表单的初始化绑定值。
|
||||
*
|
||||
* 但是,对于复杂的动态值,也就是在实现业务中额外的对象值,需要手动进行初始化操作。
|
||||
* 所以,最佳的实践应该是初始化 useForm 方法的时候,就应该确定好初始化值。
|
||||
* 然后,在需要重置表单的时候,直接调用该方法即可。
|
||||
*/
|
||||
const reset = <Values extends T = T>(values?: Values & Recordable) => {
|
||||
formModelRef.value = Object.assign(
|
||||
formModelRef.value as T,
|
||||
formModel(),
|
||||
values,
|
||||
)
|
||||
restoreValidation()
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param key 需要验证的表单项的 key
|
||||
*
|
||||
* @see https://www.naiveui.com/zh-CN/dark/components/form#partially-apply-rules.vue
|
||||
*
|
||||
* @description
|
||||
* 验证表单项的规则。
|
||||
*
|
||||
* 注意,该方法想要正常运转,需要在定义 rules 时定义唯一 key;
|
||||
* 否则,该逻辑执行会失败。
|
||||
*
|
||||
* @example
|
||||
* const [register, { validateTargetField }] = useForm(
|
||||
* {
|
||||
* name: null,
|
||||
* },
|
||||
* {
|
||||
* name: {
|
||||
* required: true,
|
||||
* message: 'name is required',
|
||||
* trigger: ['blur', 'change'],
|
||||
* type: 'string',
|
||||
* key: 'name',
|
||||
* },
|
||||
* },
|
||||
* )
|
||||
*
|
||||
* validateTargetField('name')
|
||||
*/
|
||||
const validateTargetField = (key: string) => {
|
||||
if (!key || typeof key !== 'string') {
|
||||
throw new Error(
|
||||
`[useForm-validateTargetField]: except key is string, but got ${typeof key}.`,
|
||||
)
|
||||
}
|
||||
|
||||
return validate(void 0, (rules) => {
|
||||
return rules?.key === key
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @description
|
||||
* 更新表单的值,该方法会覆盖初始化值。
|
||||
* 推荐在表单组件中使用该方法,而不是直接修改表单的值,符合函数式的理念。
|
||||
*
|
||||
* @example
|
||||
* const [register, { updateFormCondition }] = useForm(
|
||||
* {
|
||||
* name: null,
|
||||
* },
|
||||
* )
|
||||
*
|
||||
* updateFormCondition({
|
||||
* name: 'John',
|
||||
* })
|
||||
*/
|
||||
const updateFormCondition = (values: T & Recordable) => {
|
||||
formModelRef.value = Object.assign(formModelRef.value as T, values)
|
||||
}
|
||||
|
||||
initialFormModel()
|
||||
|
||||
return [
|
||||
register,
|
||||
@ -105,6 +242,10 @@ const useForm = <T extends Recordable, R extends RFormRules>(
|
||||
restoreValidation,
|
||||
formModel,
|
||||
formRules,
|
||||
reset,
|
||||
validateTargetField,
|
||||
formConditionRef: formModelRef as Ref<T>,
|
||||
updateFormCondition,
|
||||
},
|
||||
] as const
|
||||
}
|
||||
|
||||
@ -1,10 +1,74 @@
|
||||
import type { AnyFn, MaybeArray } from '@/types'
|
||||
import { omit } from 'lodash-es'
|
||||
import { formProps } from 'naive-ui'
|
||||
|
||||
import type { MaybeArray } from '@/types'
|
||||
import type { RFormInst } from './types'
|
||||
|
||||
const props = {
|
||||
...formProps,
|
||||
...omit(formProps, ['onSubmit']),
|
||||
/**
|
||||
*
|
||||
* @description
|
||||
* 表单的加载状态。
|
||||
*
|
||||
* @default false
|
||||
*/
|
||||
loading: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
/**
|
||||
*
|
||||
* @description
|
||||
* 表单的加载状态的描述。
|
||||
*
|
||||
* @default undefined
|
||||
*/
|
||||
loadingDescription: {
|
||||
type: String,
|
||||
default: void 0,
|
||||
},
|
||||
/**
|
||||
*
|
||||
* @description
|
||||
* 表单的自动完成功能。
|
||||
*
|
||||
* @default 'off'
|
||||
*/
|
||||
autocomplete: {
|
||||
type: String as PropType<AutoFillBase>,
|
||||
default: 'off',
|
||||
},
|
||||
/**
|
||||
*
|
||||
* @description
|
||||
* 是否在按下回车键时自动触发表单的校验,如果校验成功则会自动触发 onFinish 事件。
|
||||
* 该功能需要结合 onFinish 属性使用。
|
||||
*
|
||||
* 注意,该属性不支持响应式更新,只依赖于初始化传入的值;
|
||||
* 因为该方法特性的原因,做响应式的更新意义不大。
|
||||
*
|
||||
* 并且该属性启用后,会自动的拦截 Enter 键的默认行为;
|
||||
* 例如 NSelect, NInput 等官方自带组件的默认快捷键功能,都会被阻止;
|
||||
* 但是不得不这么做,以避免一些奇奇怪怪的问题。
|
||||
*
|
||||
* @default false
|
||||
*/
|
||||
submitWhenEnter: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
/**
|
||||
*
|
||||
* @description
|
||||
* 表单校验成功后自动触发的事件,该事件的触发时机为 submitWhenEnter 属性为 true 时,按下回车键触发。
|
||||
* 该功能需要结合 submitWhenEnter 属性使用。
|
||||
*
|
||||
* @default null
|
||||
*/
|
||||
onFinish: {
|
||||
type: Function as PropType<AnyFn>,
|
||||
default: null,
|
||||
},
|
||||
/**
|
||||
*
|
||||
* @description
|
||||
|
||||
@ -1,7 +1,6 @@
|
||||
import RIcon from './src'
|
||||
import iconProps from './src/props'
|
||||
|
||||
import type { ExtractPublicPropTypes } from 'vue'
|
||||
import RIcon from './src/Icon'
|
||||
import iconProps from './src/props'
|
||||
|
||||
export type IconProps = ExtractPublicPropTypes<typeof iconProps>
|
||||
|
||||
|
||||
@ -1,6 +1,5 @@
|
||||
import './index.scss'
|
||||
|
||||
import { completeSize, call } from '@/utils'
|
||||
import { call, completeSize } from '@/utils'
|
||||
import props from './props'
|
||||
|
||||
export default defineComponent({
|
||||
@ -10,15 +9,15 @@ export default defineComponent({
|
||||
const symbolId = computed(() => `#${props.prefix}-${props.name}`)
|
||||
const cssVars = computed(() => {
|
||||
const cssVar = {
|
||||
'--ray-icon-width': props.width
|
||||
'--r-icon-width': props.width
|
||||
? completeSize(props.width)
|
||||
: completeSize(props.size),
|
||||
'--ray-icon-height': props.height
|
||||
'--r-icon-height': props.height
|
||||
? completeSize(props.height)
|
||||
: completeSize(props.size),
|
||||
'--ray-icon-depth': props.depth,
|
||||
'--ray-icon-cursor': props.cursor,
|
||||
'--ray-icon-color': props.color,
|
||||
'--r-icon-depth': props.depth,
|
||||
'--r-icon-cursor': props.cursor,
|
||||
'--r-icon-color': props.color,
|
||||
}
|
||||
|
||||
return cssVar
|
||||
@ -41,13 +40,13 @@ export default defineComponent({
|
||||
render() {
|
||||
return (
|
||||
<span
|
||||
class={['ray-icon', this.customClassName]}
|
||||
class={['r-icon', this.customClassName]}
|
||||
style={[this.cssVars]}
|
||||
onClick={this.iconClick.bind(this)}
|
||||
onClick={this.iconClick}
|
||||
>
|
||||
<svg
|
||||
{...({
|
||||
RayIconAttribute: 'ray-icon',
|
||||
RIconAttribute: 'r-icon',
|
||||
ariaHidden: true,
|
||||
} as object)}
|
||||
>
|
||||
@ -1,28 +1,28 @@
|
||||
.ray-icon {
|
||||
.r-icon {
|
||||
position: relative;
|
||||
width: var(--ray-icon-width);
|
||||
height: var(--ray-icon-height);
|
||||
width: var(--r-icon-width);
|
||||
height: var(--r-icon-height);
|
||||
border: none;
|
||||
outline: none;
|
||||
text-align: center;
|
||||
display: inline-flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
color: var(--ray-icon-color);
|
||||
color: var(--r-icon-color);
|
||||
transform: translateZ(0);
|
||||
opacity: var(--ray-icon-depth);
|
||||
cursor: var(--ray-icon-cursor);
|
||||
opacity: var(--r-icon-depth);
|
||||
cursor: var(--r-icon-cursor);
|
||||
|
||||
& svg[RayIconAttribute='ray-icon'] {
|
||||
width: var(--ray-icon-width);
|
||||
height: var(--ray-icon-height);
|
||||
& svg[RIconAttribute='r-icon'] {
|
||||
width: var(--r-icon-width);
|
||||
height: var(--r-icon-height);
|
||||
fill: currentColor;
|
||||
}
|
||||
}
|
||||
|
||||
.ray-icon-path__animate {
|
||||
stroke-dasharray: var(--ray-icon-path-length);
|
||||
stroke-dashoffset: var(--ray-icon-path-length);
|
||||
.r-icon-path__animate {
|
||||
stroke-dasharray: var(--r-icon-path-length);
|
||||
stroke-dashoffset: var(--r-icon-path-length);
|
||||
animation: rayIconPathAnimate 2s forwards;
|
||||
}
|
||||
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
import type { PropType } from 'vue'
|
||||
import type { MaybeArray } from '@/types'
|
||||
import type { PropType } from 'vue'
|
||||
|
||||
const props = {
|
||||
color: {
|
||||
|
||||
@ -1,8 +1,7 @@
|
||||
import RIframe from './src'
|
||||
import iframeProps from './src/props'
|
||||
|
||||
import type * as RIframeType from './src/types'
|
||||
import type { ExtractPublicPropTypes } from 'vue'
|
||||
import RIframe from './src/Iframe'
|
||||
import iframeProps from './src/props'
|
||||
import type * as RIframeType from './src/types'
|
||||
|
||||
export type IframeProps = ExtractPublicPropTypes<typeof iframeProps>
|
||||
export type { RIframeType }
|
||||
|
||||
@ -1,11 +1,9 @@
|
||||
import './index.scss'
|
||||
|
||||
import { NSpin } from 'naive-ui'
|
||||
|
||||
import { call, completeSize } from '@/utils'
|
||||
import props from './props'
|
||||
import { useEventListener } from '@vueuse/core'
|
||||
import { NSpin } from 'naive-ui'
|
||||
import { useTemplateRef } from 'vue'
|
||||
import props from './props'
|
||||
|
||||
export default defineComponent({
|
||||
name: 'RIframe',
|
||||
@ -13,9 +11,9 @@ export default defineComponent({
|
||||
setup(props, { expose }) {
|
||||
const cssVars = computed(() => {
|
||||
const cssVar = {
|
||||
'--ray-iframe-frameborder': completeSize(props.frameborder),
|
||||
'--ray-iframe-width': completeSize(props.width),
|
||||
'--ray-iframe-height': completeSize(props.height),
|
||||
'--r-iframe-frameborder': completeSize(props.frameborder),
|
||||
'--r-iframe-width': completeSize(props.width),
|
||||
'--r-iframe-height': completeSize(props.height),
|
||||
}
|
||||
|
||||
return cssVar
|
||||
@ -1,8 +1,8 @@
|
||||
.ray-iframe {
|
||||
width: var(--ray-iframe-width);
|
||||
height: var(--ray-iframe-height);
|
||||
width: var(--r-iframe-width);
|
||||
height: var(--r-iframe-height);
|
||||
box-sizing: border-box;
|
||||
border: var(--ray-iframe-frameborder);
|
||||
border: var(--r-iframe-frameborder);
|
||||
|
||||
& .ray-iframe__container {
|
||||
width: 100%;
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
import type { PropType } from 'vue'
|
||||
import type { MaybeArray } from '@/types'
|
||||
import type { SpinProps } from 'naive-ui'
|
||||
import type { PropType } from 'vue'
|
||||
import type { Lazy } from './types'
|
||||
|
||||
const props = {
|
||||
|
||||
@ -1,10 +1,9 @@
|
||||
import type { ExtractPublicPropTypes } from 'vue'
|
||||
import useModal from './src/hooks/useModal'
|
||||
import type { UseModalReturn } from './src/hooks/useModal'
|
||||
import RModal from './src/Modal'
|
||||
import modalProps from './src/props'
|
||||
import useModal from './src/hooks/useModal'
|
||||
|
||||
import type * as RModalType from './src/types'
|
||||
import type { ExtractPublicPropTypes } from 'vue'
|
||||
import type { UseModalReturn } from './src/hooks/useModal'
|
||||
|
||||
export type ModalProps = ExtractPublicPropTypes<typeof modalProps>
|
||||
export type { RModalType, UseModalReturn }
|
||||
|
||||
@ -1,18 +1,13 @@
|
||||
import './index.scss'
|
||||
|
||||
import { NModal } from 'naive-ui'
|
||||
|
||||
import props from './props'
|
||||
import { completeSize, uuid } from '@/utils'
|
||||
import { setupInteract } from './utils'
|
||||
import { NModal } from 'naive-ui'
|
||||
import type { ModalProps } from 'naive-ui'
|
||||
import {
|
||||
CSS_VARS_KEYS,
|
||||
FULLSCREEN_CARD_TYPE_CLASS,
|
||||
R_MODAL_CLASS,
|
||||
CSS_VARS_KEYS,
|
||||
} from './constant'
|
||||
|
||||
import type interact from 'interactjs'
|
||||
import type { ModalProps } from 'naive-ui'
|
||||
import props from './props'
|
||||
|
||||
export default defineComponent({
|
||||
name: 'RModal',
|
||||
@ -24,57 +19,11 @@ export default defineComponent({
|
||||
[CSS_VARS_KEYS['dialogWidth']]: completeSize(props.dialogWidth ?? 446),
|
||||
}))
|
||||
const uuidEl = uuid()
|
||||
let intractable: null | ReturnType<typeof interact>
|
||||
// 记录拖拽的位置
|
||||
const position = {
|
||||
x: 0,
|
||||
y: 0,
|
||||
}
|
||||
// 当前是否为预设 card 类型并且设置了 fullscreen
|
||||
const isFullscreenCardType = computed(
|
||||
() => props.preset === 'card' && props.fullscreen,
|
||||
)
|
||||
|
||||
watch(
|
||||
() => props.show,
|
||||
(ndata) => {
|
||||
if (
|
||||
ndata &&
|
||||
props.dad &&
|
||||
(props.preset === 'card' || props.preset === 'dialog')
|
||||
) {
|
||||
nextTick(() => {
|
||||
const target = document.getElementById(uuidEl)
|
||||
|
||||
if (target) {
|
||||
setupInteract(target, {
|
||||
preset: props.preset,
|
||||
x: position.x,
|
||||
y: position.y,
|
||||
dargCallback: (x, y) => {
|
||||
position.x = x
|
||||
position.y = y
|
||||
},
|
||||
}).then((res) => {
|
||||
intractable = res
|
||||
})
|
||||
}
|
||||
|
||||
if (props.memo && target) {
|
||||
target.style.transform = `translate(${position.x}px, ${position.y}px)`
|
||||
}
|
||||
})
|
||||
} else {
|
||||
intractable?.unset()
|
||||
|
||||
intractable = null
|
||||
}
|
||||
},
|
||||
{
|
||||
immediate: true,
|
||||
},
|
||||
)
|
||||
|
||||
return {
|
||||
cssVars,
|
||||
isFullscreenCardType,
|
||||
|
||||
@ -1,10 +1,17 @@
|
||||
import { useModal as useNaiveModal, NScrollbar } from 'naive-ui'
|
||||
import { setupInteract } from '../utils'
|
||||
import { queryElements, setStyle, completeSize, setClass } from '@/utils'
|
||||
import { R_MODAL_CLASS, CSS_VARS_KEYS } from '../constant'
|
||||
|
||||
import { completeSize, queryElements, setClass, setStyle } from '@/utils'
|
||||
import { NScrollbar, useModal as useNaiveModal } from 'naive-ui'
|
||||
import { CSS_VARS_KEYS, R_MODAL_CLASS } from '../constant'
|
||||
import type { RModalProps } from '../types'
|
||||
|
||||
/**
|
||||
*
|
||||
* @deprecated
|
||||
*
|
||||
* @see https://www.naiveui.com/zh-CN/dark/components/modal#useModal-API
|
||||
*
|
||||
* @description
|
||||
* 请使用官方的 `useModal` 方法。
|
||||
*/
|
||||
const useModal = () => {
|
||||
const { create: naiveCreate, destroyAll: naiveDestroyAll } = useNaiveModal()
|
||||
|
||||
@ -21,10 +28,10 @@ const useModal = () => {
|
||||
color: 'rgba(0, 0, 0, 0)',
|
||||
colorHover: 'rgba(0, 0, 0, 0)',
|
||||
},
|
||||
trigger: 'none',
|
||||
trigger: 'hover',
|
||||
style: {
|
||||
width: 'auto',
|
||||
height:
|
||||
maxHeight:
|
||||
'calc(var(--html-height) - 29px - var(--n-padding-bottom) - var(--n-padding-bottom) - var(--n-padding-top))',
|
||||
},
|
||||
},
|
||||
@ -35,7 +42,7 @@ const useModal = () => {
|
||||
)
|
||||
}
|
||||
|
||||
const { preset, dad, fullscreen, width, cardWidth, dialogWidth } = options
|
||||
const { preset, fullscreen, width, cardWidth, dialogWidth } = options
|
||||
const modalReactive = naiveCreate({
|
||||
...rest,
|
||||
content: contentNode,
|
||||
@ -55,15 +62,6 @@ const useModal = () => {
|
||||
return
|
||||
}
|
||||
|
||||
// 是否启用拖拽
|
||||
if (dad) {
|
||||
setupInteract(modalElement, {
|
||||
preset,
|
||||
x: 0,
|
||||
y: 0,
|
||||
})
|
||||
}
|
||||
|
||||
// preset 为 card,fullscreen 为 true 时,最大化 modal
|
||||
if (fullscreen && preset === 'card') {
|
||||
setStyle(modalElement, {
|
||||
|
||||
@ -4,6 +4,11 @@
|
||||
// 当设置全屏时,启用滚动
|
||||
& .n-card__content {
|
||||
overflow: scroll;
|
||||
max-height: calc(
|
||||
var(--html-height) - var(--n-padding-bottom) - var(--n-padding-bottom) - var(
|
||||
--n-padding-top
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -3,17 +3,6 @@ import type { PropType } from 'vue'
|
||||
|
||||
const props = {
|
||||
...modalProps,
|
||||
/**
|
||||
*
|
||||
* @description
|
||||
* 是否记住上一次的位置。
|
||||
*
|
||||
* @default true
|
||||
*/
|
||||
memo: {
|
||||
type: Boolean,
|
||||
default: true,
|
||||
},
|
||||
/**
|
||||
*
|
||||
* @description
|
||||
@ -58,18 +47,6 @@ const props = {
|
||||
type: [String, Number] as PropType<string | number>,
|
||||
default: 446,
|
||||
},
|
||||
/**
|
||||
*
|
||||
* @description
|
||||
* 是否启用拖拽。
|
||||
* 当启用拖拽时,可以通过拖拽 header 部分控制模态框。
|
||||
*
|
||||
* @default false
|
||||
*/
|
||||
dad: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
}
|
||||
|
||||
export default props
|
||||
|
||||
@ -1,14 +1,6 @@
|
||||
import type { ModalOptions as NaiveModalOptions } from 'naive-ui'
|
||||
|
||||
export interface RModalProps extends NaiveModalOptions {
|
||||
/**
|
||||
*
|
||||
* @description
|
||||
* 是否记住上一次的位置。
|
||||
*
|
||||
* @default true
|
||||
*/
|
||||
memo?: boolean
|
||||
/**
|
||||
*
|
||||
* @description
|
||||
@ -41,13 +33,4 @@ export interface RModalProps extends NaiveModalOptions {
|
||||
* @default 446
|
||||
*/
|
||||
dialogWidth?: number | string
|
||||
/**
|
||||
*
|
||||
* @description
|
||||
* 是否启用拖拽。
|
||||
* 当启用拖拽时,可以通过拖拽 header 部分控制模态框。
|
||||
*
|
||||
* @default false
|
||||
*/
|
||||
dad?: boolean
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user