diff --git a/.eslintignore b/.eslintignore index 108c8d65..74e0c783 100644 --- a/.eslintignore +++ b/.eslintignore @@ -13,5 +13,6 @@ visualizer.html .env.* src/locales/lang .depcheckrc -src/components/RayChart/theme +src/components/RChart/theme *.md +src/icons/*.svg \ No newline at end of file diff --git a/.eslintrc.cjs b/.eslintrc.cjs index 7aeaa58d..5a8d10e1 100644 --- a/.eslintrc.cjs +++ b/.eslintrc.cjs @@ -105,8 +105,13 @@ module.exports = { 'no-labels': 2, // 禁止标签声明 'no-lone-blocks': 2, // 禁止不必要的嵌套块 'no-multi-spaces': 1, // 禁止使用多余的空格 - 'no-multiple-empty-lines': [1, { max: 2 }], // 空行最多不能超过 `2` 行 - 'no-new-func': 1, // 禁止使用 `new Function` + 'no-multiple-empty-lines': [ + 'error', + { + max: 2, + }, + ], // 空行最多不能超过 `2` 行 + 'no-new-func': 2, // 禁止使用 `new Function` 'no-new-object': 2, // 禁止使用 `new Object` 'no-new-require': 2, // 禁止使用 `new require` 'no-sparse-arrays': 2, // 禁止稀疏数组 @@ -124,7 +129,6 @@ module.exports = { 'no-useless-call': 2, // 禁止不必要的 `call` 和 `apply` 'no-var': 'error', // 禁用 `var` 'no-with': 2, // 禁用 `with` - 'no-undef': 0, 'use-isnan': 2, // 强制使用 isNaN 判断 NaN 'no-multi-assign': 2, // 禁止连续声明变量 'prefer-arrow-callback': 2, // 强制使用箭头函数作为回调 @@ -143,15 +147,6 @@ module.exports = { ], 'vue/require-v-for-key': ['error'], 'vue/require-valid-default-prop': ['error'], - 'no-use-before-define': [ - 'error', - { - functions: true, - classes: true, - variables: false, - allowNamedExports: false, - }, - ], 'vue/component-definition-name-casing': ['error', 'PascalCase'], 'vue/html-closing-bracket-newline': [ 'error', diff --git a/.vscode/settings.json b/.vscode/settings.json index 93251d2d..409e9964 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -9,5 +9,13 @@ "i18n-ally.displayLanguage": "zh-CN", "i18n-ally.enabledFrameworks": ["vue", "react"], "typescript.tsdk": "node_modules/typescript/lib", - "synthwave84.disableGlow": true + "alias-skip.mappings": { + "@": "/src", + "@use-utils": "/src/utils", + "@use-api": "/src/axios/api", + "@use-images": "/src/assets/images", + "@mock": "/mock" + }, + "alias-skip.allowedsuffix": ["ts", "tsx"], + "alias-skip.rootpath": "package.json" } diff --git a/CHANGELOG.md b/CHANGELOG.md index cfe443df..988b6a33 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,69 @@ # CHANGE LOG +## 4.2.1 + +经过综合考虑,还是给模板增加 `cdn` 的配置。基于 `vite-plugin-cdn2` 插件实现。 + +### Feats + +- 指令相关 + - `v-copy` 指令将使用 `String` 强制转换传入的值 + - 统一暴露节流、防抖指令的配置项类型 `import type { DebounceBindingOptions, ThrottleBindingOptions } from '@/directives/type'` + - 现在 `v-disabled` 指令生效时会降低一点元素的亮度 +- `changeMenuModelValue` 方法添加节流锁,避免重复刷新 url 导致的一些问题 +- 新增 `cdn`,缩减构建体积。如果不需要该配置,搜索 `viteCDNPlugin` 注释即可 + +## 4.2.0 + +针对分包,做了全局的重新设计、调整。让包的名称更加语义化;最重要的是,重新抽离了一些全局可能常用的方法,例如:useI18n、useDayjs 等,在以前这些方法存放于对应的包中,其实这样很不合理,所以现在统一存放于 `src/hooks` 包中。并且该包以后统一存放 `hooks` 方法,并不是 `utils` 方法,做了一个本质的区分,所以 `xxxCopilot.ts` 文件中的方法并不会移动,维持存放在原有的模块下。 + +引入 `useGlobalVariable` 来管理全局变量。与 `pinia` `的使用场景不同,useGlobalVariable` 是用于引入一些全局的响应式变量,这些变量不需要缓存,也不依赖任何插件。一个典型的应用是实现 `GlobalSpin`。使用该方法时,请谨慎使用,避免滥用,因为这些变量会被全局缓存且无法被回收。该方法存放的值,暂不支持缓存(如果有需要,可能后期会增加该功能)。 + +当项目插件或者需要配置项过多时候,会导致 `vite.config.ts` 文件变得异常臃肿。所以,在本次更新中,将插件的配置单独提出维护(`vite.plugin.confit.ts`)。系统的常用配置依旧在 `cfg.ts` 文件中。所以默认情况下,一般不需要修改 `vite.config.ts` 文件。 + +### Feats + +- 新增 `useGlobalVariable` 管理全局变量(该变量可以是在注册插件之前被调用) +- `v-disbaled` 指令现在会尝试给元素添加 `disabled` 属性,如果该属性生效的话 +- 注册指令操作现在不会中断程序执行,但是会抛出错误警告 +- 抽离 `vite.plugin.confit.ts` 维护项目启动所需插件 + +## 4.1.9 + +### Feats + +- 新增 RayQRCode 组件(二维码) + - 基于 awesome-qr 封装,继承其所有特性。并且拓展 状态、下载、自动更新等属性 + - 自动卸载于释放内存,仅需要关注 text 内容填充 +- 移除 qrcode.vue 依赖 +- 更新 vue-hooks-plus 版本至 v1.8.2 +- 移除 office 功能集成 +- 统一包命名方式 +- 更改 v-copy 实现细节(使用方式不变) + +### Fixes + +- 修复了一些小细节问题 + +## 4.1.8 + +### Feats + +- 更新 `vite` 版本至 `v4.4.9` +- 更新 `vue-hooks-plus` 版本至 `v1.8.1` +- 更新了 RayTable 的一些事件的命名 +- `RayChart` 组件做了一些调整 + - 支持指定 observer 监听对象,默认为 chart 组件本身 + - 默认开启 autoChangeTheme 功能 + - 支持配置 throttleWait 节流等待时间,默认 500ms + - 支持通过配置 `desginConfig.echartTheme` 属性指定 `echart theme`。并且只需按照约定方式注册的主题,只需要指定主题名称,即可完成 `light` `dark` 两种主题指定 + - RayChartInst 新增 dispose render 方法,允许手动渲染与卸载 chart 图 + - 新增 animation 属性,如果为 true 则会强制触发渲染过渡动画。该配置受 `options.animation` 属性影响,如果该配置为 false 则不会启用过渡动画 +- 移除反转色功能 +- 新增图标页面 +- 修改国际化图标 +- 剔除无用代码,性能++++ + ## 4.1.7 ### Feats diff --git a/COMMONPROBLEM.md b/COMMONPROBLEM.md deleted file mode 100644 index fa14ec84..00000000 --- a/COMMONPROBLEM.md +++ /dev/null @@ -1,21 +0,0 @@ -## 常见问题 - -### 路由 - -#### 缓存失效 - -> 如果出现缓存配置不生效的情况可以按照如下方法进行排查 - -- 查看 APP_KEEP_ALIVE setupKeepAlive 属性是否配置为 true -- 查看每个组件的 `name` 是否唯一,[`KeepAlive`](https://cn.vuejs.org/guide/built-ins/keep-alive.html) 组件重度依赖组件 `name` 作为唯一标识。详情可以查看官方文档 -- 查看该页面的路由配置是否正确,比如:`path` 是否按照模板约定方式进行配置 - -#### 自动导入失败 - -> 模板采用自动导入路由模块方式。如果发现路由导入有误、或者导入报错,请查看文件命名是否有误。 - -### 国际化 - -#### 国际化切换错误、警告 - -> 模板二次封装 [`useI18n`](https://github.com/XiaoDaiGua-Ray/ray-template/blob/main/src/locales/useI18n.ts) 方法,首选该方法作为国际化语言切换方法。 diff --git a/README-US.md b/README-US.md new file mode 100644 index 00000000..997c5a71 --- /dev/null +++ b/README-US.md @@ -0,0 +1,121 @@ +
+ Ray Template

+ LICENSE +
+ +
+ +# Ray Template + +A middle and backend template based on vite4.x & ts(x) & pinia & vue3.x + +
+ +## ✨ Feature + +- **Latest Technology Stack**:Developed using front-end cutting-edge technologies such as vue3.x/vite4.x/pinia +- **TypeScript**:The language for application-level JavaScript +- **App Theme**:Configurable themes +- **Globalization**:Built-in complete internationalization solution +- **Mock Data**:Built-in Mock data scheme +- **Permissions**:Built-in complete dynamic routing permission generation solution +- **Components**:Secondary encapsulation of multiple commonly used components +- **Axios Request**:Secondary encapsulation of the axios library, supporting functions such as cancellation, anti-shake, automatic repeat cancellation, etc. +- **Page Cache**:Arbitrarily deep page cache +- **SVG**:Built-in svg icon solution +- **Standalone Data Methods Views**:Decoupled management of data, methods, and views allows for secondary development with confidence + +## 🪄 Preview + +- [Click to preview](https://xiaodaigua-ray.github.io/ray-template/#/) +- [Click to preview(Acceleration address)](https://ray-template.yunkuangao.com/#/) + +## 🦾 Document + +- [Document](https://xiaodaigua-ray.github.io/ray-template-doc/) +- [Document(Acceleration address)](https://ray-template.yunkuangao.com/ray-template-doc/) + +## 🔋 Change Log + +- [Change Log](https://github.com/XiaoDaiGua-Ray/xiaodaigua-ray.github.io/blob/main/CHANGELOG.md) + +## 🪴 Prepare + +- [Node](http://nodejs.org/) and [git](https://git-scm.com/) - Project development environment +- [Vite](https://vitejs.dev/) - Familiar with vite features +- [Vue3](https://v3.vuejs.org/) - Familiar with Vue3.x basic syntax +- [TypeScript](https://www.typescriptlang.org/) - Familiar with TypeScript basic syntax +- [Es6+](http://es6.ruanyifeng.com/) - Familiar with es6 basic syntax +- [Vue-Router-Next](https://next.router.vuejs.org/) - Familiar with the basic use of vue-router4.x +- [Naive-UI](https://www.naiveui.com) - UI basic usage +- [Mock.js](https://github.com/nuysoft/Mock) - Mockjs basic syntax +- [Pinia](https://pinia.vuejs.org/zh/introduction.html) - State manager pinia uses +- [TSX](https://github.com/vuejs/babel-plugin-jsx/blob/main/packages/babel-plugin-jsx/README-zh_CN.md) - TSX basic syntax + +## 📦 Setup + +### Get Project + +```sh +# github +git clone https://github.com/XiaoDaiGua-Ray/ray-template.git + +# If your download speed is very slow, you can switch to the proxy address below +git clone https://gh.yka.moe/https://github.com/XiaoDaiGua-Ray/ray-template.git +``` + +### Pull dependencies + +```sh +pnpm i +``` + +### Startup project + +```sh +pnpm dev +``` + +### Project packaging + +```sh +pnpm build +``` + +### Preview project + +```sh +pnpm preview +``` + +### Volumetric analysis + +```sh +pnpm report +``` + +### Develop + +Introduction and ease of use are the core ideas of this template. So you can safely delete all files under `views/demo` and `router/moduels/demo`, and you will have a clean project. + +## 🪴 Project Activities + +![Alt](https://repobeats.axiom.co/api/embed/fab6071297ab281913a42f07a2779b488cfd62b8.svg 'Repobeats analytics image') + +### Contributor + +Thanks for all their contributions 🐝! + + + + + +## Browser Support + +| [ Edge](http://godban.github.io/browsers-support-badges/)
IE | [ Edge](http://godban.github.io/browsers-support-badges/)
Edge | [Firefox](http://godban.github.io/browsers-support-badges/)
Firefox | [Chrome](http://godban.github.io/browsers-support-badges/)
Chrome | [Safari](http://godban.github.io/browsers-support-badges/)
Safari | +| :--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | +| not support | last 2 versions | last 2 versions | last 2 versions | last 2 versions | + +## 📄 License + +[MIT License](https://github.com/XiaoDaiGua-Ray/ray-template/blob/main/LICENSE) © 2022-PRESENT [Ray](https://github.com/XiaoDaiGua-Ray) diff --git a/README.md b/README.md index 9d93aeb9..c0702f8f 100644 --- a/README.md +++ b/README.md @@ -1,57 +1,49 @@ -
Ray Template

- -

Ray Template

+
+ Ray Template

+ LICENSE
- +
-[![All Contributors](https://img.shields.io/badge/all_contributors-1-orange.svg?style=flat-square)](#contributors-) +# Ray Template - +简体中文 | [English](https://github.com/XiaoDaiGua-Ray/ray-template/blob/main/README-US.md) -## 前言 +一个基于 vite4.x & ts(x) & pinia & vue3.x 的中后台模板 -> 该项目模板采用 `vue3.x` `vite4.x` `pinia` `tsx` 进行开发。 -> 使用 `naive ui` 作为组件库。 -> 预设了最佳构建体验的配置与常用搬砖工具。意在提供一个简洁、快速上手的模板。 -> 该模板不支持移动端设备。 +
-## 感谢 +## ✨ 特性 -> 感谢 [`yun`](https://me.yka.moe/) 对于本人的支持。 - -## 预览地址 - -- [点击预览](https://xiaodaigua-ray.github.io/ray-template/#/) -- [点击预览(加速地址)](https://ray-template.yunkuangao.com/#/) - -## 文档地址 - -- [文档](https://xiaodaigua-ray.github.io/ray-template-doc/) -- [文档(加速地址)](https://ray-template.yunkuangao.com/ray-template-doc/) - -## 更新日志 - -- [日志](https://github.com/XiaoDaiGua-Ray/xiaodaigua-ray.github.io/blob/main/CHANGELOG.md) - -## 常见问题 - -- [常见问题](https://github.com/XiaoDaiGua-Ray/ray-template/blob/main/COMMONPROBLEM.md) - -## 特性 - -- **最新技术栈**:使用 Vue3.x/vite4.x 等前端前沿技术开发 +- **最新技术栈**:使用 vue3.x/vite4.x/pinia 等前端前沿技术开发 - **TypeScript**:应用程序级 JavaScript 的语言 - **主题**:可配置的主题 - **国际化**:内置完善的国际化方案 - **Mock 数据**:内置 Mock 数据方案 - **权限**:内置完善的动态路由权限生成方案 - **组件**:二次封装了多个常用的组件 -- **Axios 请求**:二次封装 axios 库 +- **Axios 请求**:二次封装 axios 库,支持:取消、防抖、自动重复取消等功能 +- **缓存**:任意深度页面缓存 +- **SVG**:内置 svg icon 解决方案 +- **独立的 Data Methods Views**:解耦管理的数据、方法、视图,放心二次开发 -## 准备 +## 🪄 预览地址 -- [node](http://nodejs.org/) 和 [git](https://git-scm.com/) -项目开发环境 +- [点击预览](https://xiaodaigua-ray.github.io/ray-template/#/) +- [点击预览(加速地址)](https://ray-template.yunkuangao.com/#/) + +## 🦾 文档地址 + +- [文档](https://xiaodaigua-ray.github.io/ray-template-doc/) +- [文档(加速地址)](https://ray-template.yunkuangao.com/ray-template-doc/) + +## 🔋 更新日志 + +- [更新日志](https://github.com/XiaoDaiGua-Ray/xiaodaigua-ray.github.io/blob/main/CHANGELOG.md) + +## 🪴 准备 + +- [Node](http://nodejs.org/) 和 [git](https://git-scm.com/) - 项目开发环境 - [Vite](https://vitejs.dev/) - 熟悉 vite 特性 - [Vue3](https://v3.vuejs.org/) - 熟悉 Vue 基础语法 - [TypeScript](https://www.typescriptlang.org/) - 熟悉 TypeScript 基本语法 @@ -62,16 +54,9 @@ - [Pinia](https://pinia.vuejs.org/zh/introduction.html) - 状态管理器 pinia 使用 - [TSX](https://github.com/vuejs/babel-plugin-jsx/blob/main/packages/babel-plugin-jsx/README-zh_CN.md) - tsx 基本语法 -## 未来 +## 📦 起步 -> 根据个人时间空余情况,会不定时对该模板进行更新和迭代。希望将该工具的功能不断补全(虽然现在已经是足够日常开发和使用),将该模板打造为一个更加健全的中后台模板。如果你有好的想法和建议,可以直接联系我或者直接提 `issues` 即可。 - -## 提示 - -> 项目默认启用严格模式 `eslint`,但是由于 `vite-plugin-eslint` 插件优先级最高,所以如果出现自动导入类型错误提示,请优先解决其他问题。 -> 建议开启 `vscode` 保存自动修复功能。 - -## 项目安装 +### 获取项目 ```sh # github @@ -81,118 +66,58 @@ git clone https://github.com/XiaoDaiGua-Ray/ray-template.git git clone https://gh.yka.moe/https://github.com/XiaoDaiGua-Ray/ray-template.git ``` -## 拉取依赖 +### 拉取依赖 ```sh -# yarn - -yarn +pnpm i ``` +### 启动项目 + ```sh -# npm - -npm install +pnpm dev ``` -## 启动项目 +### 项目打包 ```sh -# yarn - -yarn dev +pnpm build ``` +### 预览项目 + ```sh -# npm - -npm run dev +pnpm preview ``` -## 项目打包 +### 体积分析 ```sh -# yarn - -yarn build +pnpm report ``` -```sh -# npm +### 开发 -npm run build -``` +简介、易上手是该模板的核心思路。所以你可以放心的直接删除 `views/demo` `router/moduels/demo` 下的所有文件,这样就是一个干净的项目了。 -## 预览项目 +## 🪴 项目活动 -```sh -# yarn +![Alt](https://repobeats.axiom.co/api/embed/fab6071297ab281913a42f07a2779b488cfd62b8.svg 'Repobeats analytics image') -yarn preview -``` +### 贡献者 -```sh -# npm +感谢他们的所做的一切贡献 🐝 ! -npm run preview -``` - -## 体积分析 - -```sh -# yarn - -yarn report -``` - -```sh -# npm - -npm run report -``` + + + ## 浏览器支持 -> 仅支持现代浏览器,不支持 `IE` - | [ Edge](http://godban.github.io/browsers-support-badges/)
IE | [ Edge](http://godban.github.io/browsers-support-badges/)
Edge | [Firefox](http://godban.github.io/browsers-support-badges/)
Firefox | [Chrome](http://godban.github.io/browsers-support-badges/)
Chrome | [Safari](http://godban.github.io/browsers-support-badges/)
Safari | | :--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | | not support | last 2 versions | last 2 versions | last 2 versions | last 2 versions | -## 最后,希望大家搬砖愉快 +## 📄 证书 -## 贡献者 - - - - - - - - - - -
Cloud
Cloud

🔧
- - - - - - -## Contributors ✨ - -Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/docs/en/emoji-key)): - - - - - - - - - -This project follows the [all-contributors](https://github.com/all-contributors/all-contributors) specification. Contributions of any kind welcome! - -## License - -[MIT © Ray-2020](./LICENSE) +[MIT License](https://github.com/XiaoDaiGua-Ray/ray-template/blob/main/LICENSE) © 2022-PRESENT [Ray](https://github.com/XiaoDaiGua-Ray) diff --git a/cfg.ts b/cfg.ts index c3367645..83526643 100644 --- a/cfg.ts +++ b/cfg.ts @@ -38,15 +38,12 @@ import path from 'node:path' -import { - HTMLTitlePlugin, - buildOptions, - mixinCSSPlugin, -} from './vite-plugin/index' -import { APP_THEME } from './src/appConfig/designConfig' -import { PRE_LOADING_CONFIG, SIDE_BAR_LOGO } from './src/appConfig/appConfig' +import { htmlTitlePlugin, mixinCSSPlugin } from './vite-plugins/index' +import { APP_THEME } from './src/app-config/designConfig' +import { PRE_LOADING_CONFIG, SIDE_BAR_LOGO } from './src/app-config/appConfig' import type { AppConfigExport } from '@/types/modules/cfg' +import type { BuildOptions } from 'vite' const config: AppConfigExport = { /** 公共基础路径配置, 如果为空则会默认以 '/' 填充 */ @@ -82,7 +79,7 @@ const config: AppConfigExport = { * * 浏览器标题 */ - title: HTMLTitlePlugin('Ray Template'), + title: htmlTitlePlugin(PRE_LOADING_CONFIG.title || 'Ray Template'), /** * * 配置 HMR 特定选项(端口、主机、路径和协议) @@ -109,7 +106,39 @@ const config: AppConfigExport = { * * 打包相关配置 */ - buildOptions: buildOptions, + buildOptions: (mode: string): BuildOptions => { + const outDirMap = { + test: 'dist/test-dist', + development: 'dist/development-dist', + production: 'dist/production-dist', + report: 'dist/report-dist', + } + const dirPath = outDirMap[mode] || 'dist/test-dist' + + if (mode === 'production') { + return { + outDir: dirPath, + sourcemap: false, + terserOptions: { + compress: { + drop_console: true, + drop_debugger: true, + }, + }, + } + } else { + return { + outDir: dirPath, + sourcemap: true, + terserOptions: { + compress: { + drop_console: false, + drop_debugger: false, + }, + }, + } + } + }, /** * * 预设别名 @@ -135,6 +164,10 @@ const config: AppConfigExport = { find: '@use-images', replacement: path.resolve(__dirname, './src/assets/images'), }, + { + find: '@mock', + replacement: path.resolve(__dirname, './mock'), + }, ], } diff --git a/mock/demo/person.mock.ts b/mock/demo/person.mock.ts index 8cfefbbf..f4232eeb 100644 --- a/mock/demo/person.mock.ts +++ b/mock/demo/person.mock.ts @@ -1,26 +1,17 @@ import { defineMock } from 'vite-plugin-mock-dev-server' +import { pagination, stringify, response, array } from '@mock/shared/utils' +import { tableMock } from '@mock/shared/database' import Mock from 'mockjs' -import { pagination, stringify, response } from '../shared/utils' -import { array } from '../shared/database' export const getPersonList = defineMock({ url: '/api/list', method: 'GET', delay: 500, response: (req, res) => { - const person = () => ({ - id: Mock.Random.guid(), - address: Mock.Random.county(true), - email: Mock.Random.email(), - name: Mock.Random.cname(), - age: Mock.Random.integer(18, 60), - createDate: Mock.Random.date(), - }) - const { query: { page, pageSize, email }, } = req - let list = array(100).map(() => person()) + let list = array(100).map(() => tableMock()) let length = list.length if (!page || !pageSize) { diff --git a/mock/shared/database.ts b/mock/shared/database.ts index 8652e13d..91b817c5 100644 --- a/mock/shared/database.ts +++ b/mock/shared/database.ts @@ -9,6 +9,22 @@ * @remark 今天也是元气满满撸代码的一天 */ -export function array(length: number) { - return new Array(length).fill(0) +import Mock from 'mockjs' + +/** + * + * @param option 自定义配置 + * + * 基础表格数据 + */ +export function tableMock(option?: object) { + return { + ...option, + id: Mock.Random.guid(), + address: Mock.Random.county(true), + email: Mock.Random.email(), + name: Mock.Random.cname(), + age: Mock.Random.integer(18, 60), + createDate: Mock.Random.date(), + } } diff --git a/mock/shared/utils.ts b/mock/shared/utils.ts index bfcb991b..94ccd564 100644 --- a/mock/shared/utils.ts +++ b/mock/shared/utils.ts @@ -11,6 +11,10 @@ /* eslint-disable @typescript-eslint/no-explicit-any */ +export function array(length: number) { + return new Array(length).fill(0) +} + /** * * @param pageCurrent 当前页码 diff --git a/package.json b/package.json index a9c15207..643bd8be 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "ray-template", "private": false, - "version": "4.1.6", + "version": "4.2.1", "type": "module", "engines": { "node": ">=16.0.0", @@ -25,6 +25,7 @@ }, "dependencies": { "@vueuse/core": "^9.1.0", + "awesome-qr": "2.1.5-rc.0", "axios": "^1.2.0", "clipboard": "^2.0.11", "crypto-js": "^4.1.1", @@ -37,11 +38,9 @@ "pinia": "^2.1.4", "pinia-plugin-persistedstate": "^3.1.0", "print-js": "^1.6.0", - "qrcode.vue": "^3.3.4", - "sass": "^1.54.3", "screenfull": "^6.0.2", "vue": "^3.3.4", - "vue-hooks-plus": "1.7.6", + "vue-hooks-plus": "1.8.2", "vue-i18n": "^9.2.2", "vue-router": "^4.2.4", "vuedraggable": "^4.1.0", @@ -81,11 +80,13 @@ "postcss-px-to-viewport-8-plugin": "1.2.2", "prettier": "^2.7.1", "rollup-plugin-visualizer": "^5.8.3", + "sass": "1.54.3", "svg-sprite-loader": "^6.0.11", "typescript": "^5.0.2", "unplugin-auto-import": "^0.15.0", "unplugin-vue-components": "^0.25.1", - "vite": "^4.3.9", + "vite": "^4.4.9", + "vite-plugin-cdn2": "0.12.4", "vite-plugin-compression": "^0.5.1", "vite-plugin-ejs": "^1.6.4", "vite-plugin-eslint": "1.8.1", diff --git a/postcss.config.cjs b/postcss.config.cjs index b5f63577..be83e78f 100644 --- a/postcss.config.cjs +++ b/postcss.config.cjs @@ -22,6 +22,8 @@ module.exports = { unitPrecision: 3, /** 指定需要转换成的视窗单位 */ viewportUnit: 'rem', + /** 制定字体转换单位 */ + fontViewportUnit: 'rem', /** 指定不转换为视窗单位的类 */ selectorBlackList: ['.ignore'], /** 小于或等于 1px 不转换为视窗单位 */ diff --git a/src/app-components/app/AppAvatar/index.tsx b/src/app-components/app/AppAvatar/index.tsx index 777597a4..4e561648 100644 --- a/src/app-components/app/AppAvatar/index.tsx +++ b/src/app-components/app/AppAvatar/index.tsx @@ -22,7 +22,7 @@ import './index.scss' import { NAvatar, NSpace } from 'naive-ui' import { avatarProps, spaceProps } from 'naive-ui' -import { APP_CATCH_KEY } from '@/appConfig/appConfig' +import { APP_CATCH_KEY } from '@/app-config/appConfig' import { getStorage } from '@/utils/cache' import type { PropType } from 'vue' diff --git a/src/app-components/app/RayLink/index.tsx b/src/app-components/app/RayLink/index.tsx index 67459166..fe7c62eb 100644 --- a/src/app-components/app/RayLink/index.tsx +++ b/src/app-components/app/RayLink/index.tsx @@ -27,25 +27,25 @@ const RayLink = defineComponent({ key: 'ray-js-note', src: 'https://note.youdao.com/s/ObWEe2BB', tooltip: 'Ray的前端学习笔记', - icon: 'https://usc1.contabostorage.com/c2e495d7890844d392e8ec0c6e5d77eb:image/longmao.jpeg', + icon: 'https://usc1.contabostorage.com/c2e495d7890844d392e8ec0c6e5d77eb:image/longmao.navigator.png', }, { key: 'ray-js-cover', src: 'https://note.youdao.com/s/IC8xKPdB', tooltip: 'Ray的面试题总结', - icon: 'https://usc1.contabostorage.com/c2e495d7890844d392e8ec0c6e5d77eb:image/longmao.jpeg', + icon: 'https://usc1.contabostorage.com/c2e495d7890844d392e8ec0c6e5d77eb:image/longmao.navigator.png', }, { key: 'ray-template-doc', src: 'https://xiaodaigua-ray.github.io/ray-template-doc/', tooltip: 'Ray Template Doc', - icon: 'https://usc1.contabostorage.com/c2e495d7890844d392e8ec0c6e5d77eb:image/longmao.jpeg', + icon: 'https://usc1.contabostorage.com/c2e495d7890844d392e8ec0c6e5d77eb:image/longmao.navigator.png', }, { key: 'ray-template-doc-out', src: 'https://ray-template.yunkuangao.com/', tooltip: 'Ray Template Doc (国内地址)', - icon: 'https://usc1.contabostorage.com/c2e495d7890844d392e8ec0c6e5d77eb:image/longmao.jpeg', + icon: 'https://usc1.contabostorage.com/c2e495d7890844d392e8ec0c6e5d77eb:image/longmao.navigator.png', }, ] diff --git a/src/app-components/provider/AppNaiveGlobalProvider/index.tsx b/src/app-components/provider/AppNaiveGlobalProvider/index.tsx index cdbc4602..fe12f1d0 100644 --- a/src/app-components/provider/AppNaiveGlobalProvider/index.tsx +++ b/src/app-components/provider/AppNaiveGlobalProvider/index.tsx @@ -31,9 +31,9 @@ import { import { useSetting } from '@/store' import { naiveLocales } from '@/locales/helper' -const GlobalProvider = defineComponent({ +export default defineComponent({ name: 'GlobalProvider', - setup() { + setup(_, { expose }) { const settingStore = useSetting() const modelPrimaryColorOverride = computed( @@ -54,6 +54,7 @@ const GlobalProvider = defineComponent({ configProviderProps: computed(() => ({ theme: modelThemeValue.value, })), + notificationProviderProps: {}, }, ) @@ -62,6 +63,8 @@ const GlobalProvider = defineComponent({ window.$loadingBar = loadingBar // 注入 `loadingBar` window.$notification = notification // 注入 `notification` + expose() + return { modelPrimaryColorOverride, modelThemeValue, @@ -90,5 +93,3 @@ const GlobalProvider = defineComponent({ ) }, }) - -export default GlobalProvider diff --git a/src/app-components/provider/AppRequestCanceler/index.tsx b/src/app-components/provider/AppRequestCancelerProvider/index.tsx similarity index 77% rename from src/app-components/provider/AppRequestCanceler/index.tsx rename to src/app-components/provider/AppRequestCancelerProvider/index.tsx index 44f177d9..6aa15f36 100644 --- a/src/app-components/provider/AppRequestCanceler/index.tsx +++ b/src/app-components/provider/AppRequestCancelerProvider/index.tsx @@ -19,12 +19,14 @@ import { axiosCanceler } from '@/axios/helper/interceptor' -const AppRequestCanceler = defineComponent({ - name: 'AppRequestCanceler', - setup() { +const AppRequestCancelerProvider = defineComponent({ + name: 'AppRequestCancelerProvider', + setup(_, { expose }) { onBeforeRouteUpdate(() => { axiosCanceler.cancelAllRequest() }) + + expose() }, render() { return ( @@ -37,4 +39,4 @@ const AppRequestCanceler = defineComponent({ }, }) -export default AppRequestCanceler +export default AppRequestCancelerProvider diff --git a/src/app-components/provider/AppStyleProvider/index.tsx b/src/app-components/provider/AppStyleProvider/index.tsx index 7cd3c14d..d60a4edb 100644 --- a/src/app-components/provider/AppStyleProvider/index.tsx +++ b/src/app-components/provider/AppStyleProvider/index.tsx @@ -20,7 +20,7 @@ import type { SettingState } from '@/store/modules/setting/type' const AppStyleProvider = defineComponent({ name: 'AppStyleProvider', - setup() { + setup(_, { expose }) { const settingStore = useSetting() const { themeValue } = storeToRefs(settingStore) @@ -97,6 +97,8 @@ const AppStyleProvider = defineComponent({ immediate: true, }, ) + + expose() }, render() { return
diff --git a/src/appConfig/appConfig.ts b/src/app-config/appConfig.ts similarity index 96% rename from src/appConfig/appConfig.ts rename to src/app-config/appConfig.ts index a99f7ec7..8889df7e 100644 --- a/src/appConfig/appConfig.ts +++ b/src/app-config/appConfig.ts @@ -33,7 +33,11 @@ export const APP_KEEP_ALIVE: Readonly = { maxKeepAliveLength: 5, } -/** 首屏加载信息配置 */ +/** + * + * 首屏加载信息配置 + * 其中 title 属性会是默认的浏览器标题(初始化时) + */ export const PRE_LOADING_CONFIG: PreloadingConfig = { title: 'Ray Template', tagColor: '#ff6700', diff --git a/src/appConfig/designConfig.ts b/src/app-config/designConfig.ts similarity index 87% rename from src/appConfig/designConfig.ts rename to src/app-config/designConfig.ts index 013d2d67..f07b04bb 100644 --- a/src/appConfig/designConfig.ts +++ b/src/app-config/designConfig.ts @@ -60,4 +60,10 @@ export const APP_THEME: AppTheme = { * 地址: */ APP_NAIVE_UI_THEME_OVERRIDES: {}, + /** + * + * 配置 echart 主题颜色 + * 约定配置时以:主题名称为文件名,其文件夹下两个主题风格的 json 文件。并且暗色主题必须为 xxx-dark.json + */ + echartTheme: 'macarons', } diff --git a/src/appConfig/localConfig.ts b/src/app-config/localConfig.ts similarity index 100% rename from src/appConfig/localConfig.ts rename to src/app-config/localConfig.ts diff --git a/src/appConfig/regexConfig.ts b/src/app-config/regexConfig.ts similarity index 100% rename from src/appConfig/regexConfig.ts rename to src/app-config/regexConfig.ts diff --git a/src/appConfig/requestConfig.ts b/src/app-config/requestConfig.ts similarity index 100% rename from src/appConfig/requestConfig.ts rename to src/app-config/requestConfig.ts diff --git a/src/appConfig/routerConfig.ts b/src/app-config/routerConfig.ts similarity index 100% rename from src/appConfig/routerConfig.ts rename to src/app-config/routerConfig.ts diff --git a/src/axios/index.ts b/src/axios/index.ts index 2dec3231..74e17f57 100644 --- a/src/axios/index.ts +++ b/src/axios/index.ts @@ -29,13 +29,16 @@ import type { AppRawRequestConfig } from '@/axios/type' /** * - * 该方法有一定的局限性,仅可在 setup 环境中使用 - * 如果在非 vue component 文件中使用,会抛出一些警告 + * 该方法有一定的局限性,仅可在 effect 作用域中使用 + * 如果在非 vue effect scope 中使用,会抛出一些警告 * - * 非 vue component 中使用 + * 非 vue effect 中使用 * @example * + * // 请求函数 * const getUser = () => request({ url: 'http://localhost:3000/user' }) + * + * // effect 中使用 * const { data } = useHookPlusRequest(getUser) */ function useRequest< diff --git a/src/axios/inject/request/provide.ts b/src/axios/inject/request/provide.ts index f2becd29..1b06c3c9 100644 --- a/src/axios/inject/request/provide.ts +++ b/src/axios/inject/request/provide.ts @@ -20,7 +20,7 @@ import { useAxiosInterceptor, axiosCanceler } from '@/axios/helper/interceptor' import { appendRequestHeaders } from '@/axios/helper/axiosCopilot' -import { APP_CATCH_KEY } from '@/appConfig/appConfig' +import { APP_CATCH_KEY } from '@/app-config/appConfig' import { getStorage } from '@/utils/cache' import type { diff --git a/src/axios/instance.ts b/src/axios/instance.ts index 5f717cf1..e968087a 100644 --- a/src/axios/instance.ts +++ b/src/axios/instance.ts @@ -17,7 +17,7 @@ */ import axios from 'axios' -import { AXIOS_CONFIG } from '@/appConfig/requestConfig' +import { AXIOS_CONFIG } from '@/app-config/requestConfig' import { useAxiosInterceptor, axiosCanceler } from '@/axios/helper/interceptor' import { setupResponseInterceptor, diff --git a/src/components/RayChart/helper.ts b/src/components/RChart/helper.ts similarity index 88% rename from src/components/RayChart/helper.ts rename to src/components/RChart/helper.ts index 4d1e63a4..8b012084 100644 --- a/src/components/RayChart/helper.ts +++ b/src/components/RChart/helper.ts @@ -13,7 +13,7 @@ import type { ChartThemeRawArray, ChartThemeRawModules, LoadingOptions, -} from '@/components/RayChart/type' +} from '@/components/RChart/type' /** * @@ -26,12 +26,12 @@ import type { * 1. 配置、选择主题 * 2. 点击下载主题 * 3. 选择 json 类型,然后复制 - * 4. 在 @/components/RayChart/theme 包中创建对应的 json 文件,文件名为主题名称 + * 4. 在 @/components/RChart/theme 包中创建对应的 json 文件,文件名为主题名称 */ export const setupChartTheme = () => { // 获取所有主题 const themeRawModules: Record = - import.meta.glob('@/components/RayChart/theme/**/*.json', { + import.meta.glob('@/components/RChart/theme/**/*.json', { eager: true, }) const regx = /\/([^/]+)\.json$/ diff --git a/src/components/RayChart/index.scss b/src/components/RChart/index.scss similarity index 65% rename from src/components/RayChart/index.scss rename to src/components/RChart/index.scss index 5ea9021d..4e574163 100644 --- a/src/components/RayChart/index.scss +++ b/src/components/RChart/index.scss @@ -5,4 +5,10 @@ outline: none; box-sizing: border-box; transition: width 0.35s var(--r-bezier); + + & .ray-chart__container { + width: 100%; + height: 100%; + box-sizing: border-box; + } } diff --git a/src/components/RayChart/index.tsx b/src/components/RChart/index.tsx similarity index 66% rename from src/components/RayChart/index.tsx rename to src/components/RChart/index.tsx index fcce942c..50fbd7de 100644 --- a/src/components/RayChart/index.tsx +++ b/src/components/RChart/index.tsx @@ -36,30 +36,38 @@ import { PictorialBarChart, } from 'echarts/charts' // 系列类型(后缀都为 `SeriesOption`) import { LabelLayout, UniversalTransition } from 'echarts/features' // 标签自动布局, 全局过渡动画等特性 -import { CanvasRenderer } from 'echarts/renderers' // `echarts` 渲染器 +import { + CanvasRenderer, + // SVGRenderer, +} from 'echarts/renderers' // `echarts` 渲染器 import { useSetting } from '@/store' import { cloneDeep, throttle } from 'lodash-es' import { on, off, completeSize } from '@/utils/element' import { call } from '@/utils/vue/index' import { setupChartTheme, loadingOptions } from './helper' -import { LAYOUT_CONTENT_REF } from '@/appConfig/routerConfig' +import { APP_THEME } from '@/app-config/designConfig' import type { PropType } from 'vue' -import type { EChartsInstance } from '@/types/modules/component' import type { AnyFC, MaybeArray } from '@/types/modules/utils' import type { DebouncedFunc } from 'lodash-es' import type { LoadingOptions, AutoResize, ChartTheme, -} from '@/components/RayChart/type' -import type { UseResizeObserverReturn } from '@vueuse/core' +} from '@/components/RChart/type' +import type { + UseResizeObserverReturn, + MaybeComputedElementRef, + MaybeElement, +} from '@vueuse/core' +import type { ECharts, EChartsCoreOption, SetOptionOpts } from 'echarts/core' export type EChartsExtensionInstallRegisters = typeof CanvasRenderer +export type { RayChartInst } from './type' -const RayChart = defineComponent({ - name: 'RayChart', +export default defineComponent({ + name: 'RChart', props: { width: { /** @@ -116,21 +124,19 @@ const RayChart = defineComponent({ type: Object as PropType, default: () => ({}), }, - success: { + onSuccess: { /** * * 返回 chart 实例 * * 渲染成功回调函数 * - * () => EChartsInstance + * () => ECharts */ - type: [Function, Array] as PropType< - MaybeArray<(e: EChartsInstance) => void> - >, + type: [Function, Array] as PropType void>>, default: null, }, - error: { + onError: { /** * * 渲染失败回调函数 @@ -148,13 +154,12 @@ const RayChart = defineComponent({ /** * * 是否自动跟随模板主题切换 - * * 如果开启此属性, 则会覆盖 `theme` 属性 * - * 注意: 这个属性重度依赖此模板, 所以默认不开启. 并且动态切换主题有一定的性能问题 + * 注意: 这个属性重度依赖此模板 */ type: Boolean, - default: false, + default: true, }, use: { /** @@ -180,15 +185,53 @@ const RayChart = defineComponent({ type: Object as PropType, default: () => loadingOptions(), }, + observer: { + /** + * + * 需要被监听尺寸的元素 + * 需要开启 autoResize 才能生效 + * 默认以父元素作为监听对象 + */ + type: Object as PropType>, + default: null, + }, + throttleWait: { + /** 节流等待时间 */ + type: Number, + default: 500, + }, + animation: { + /** 是否强制启用渲染动画 */ + type: Boolean, + default: true, + }, + setChartOptions: { + /** + * + * 当 options 配置项更改时候,setOptions 方法配置项 + * + * 默认值 + * notMerge: false, + * lazyUpdate: true, + * silent: false, + * replaceMerge: [], + * + * 会自动进行合并配置项 + */ + type: Object as PropType, + default: () => ({}), + }, }, setup(props, { expose }) { const settingStore = useSetting() const { themeValue } = storeToRefs(settingStore) const rayChartRef = ref() // `echart` 容器实例 - const echartInstanceRef = ref() // `echart` 拷贝实例, 解决直接使用响应式实例带来的问题 - let echartInstance: EChartsInstance // `echart` 实例 - let resizeThrottle: DebouncedFunc // resize 防抖方法实例 - let resizeOvserverReturn: UseResizeObserverReturn | undefined + const rayChartWrapperRef = ref() + const echartInstanceRef = ref() // `echart` 实例 + let echartInstance: ECharts | null // `echart` 拷贝实例, 解决直接使用响应式实例带来的问题 + let resizeThrottleReturn: DebouncedFunc | null // resize 防抖方法实例 + let resizeOvserverReturn: UseResizeObserverReturn | null + const { echartTheme } = APP_THEME const cssVarsRef = computed(() => { const cssVars = { @@ -198,9 +241,6 @@ const RayChart = defineComponent({ return cssVars }) - const modelLoadingOptions = computed(() => - loadingOptions(props.loadingOptions), - ) /** * @@ -237,10 +277,10 @@ const RayChart = defineComponent({ echarts.use([CanvasRenderer]) // 注册渲染器 try { - echarts.use(props.use) + echarts.use(props.use?.filter(Boolean)) } catch (e) { console.error( - 'Error: wrong property and method passed in extend attribute', + 'register chart Core error: wrong property and method passed in extend attribute', ) } } @@ -253,10 +293,17 @@ const RayChart = defineComponent({ * * 如果有需要特殊全局配置的可以在此继续写... */ - const combineChartOptions = () => { - let options = cloneDeep(props.options) + const combineChartOptions = (ops: EChartsCoreOption) => { + let options = cloneDeep(ops) - const assign = (opts: object) => Object.assign({}, options, opts) + const assign = (opts: object) => + Object.assign( + { + animation: true, + }, + options, + opts, + ) if (props.showAria) { options = assign({ @@ -277,17 +324,16 @@ const RayChart = defineComponent({ * 渲染 `echart` * * 缓存两个实例 - * * 直接使用响应式代理实例会出现诡异的问题, 例如 `legend` 点击时报错 */ - const renderChart = (theme: ChartTheme = 'macarons') => { + const renderChart = (theme: ChartTheme = echartTheme) => { /** 获取 dom 容器 */ const element = rayChartRef.value as HTMLElement /** 获取配置项 */ - const options = combineChartOptions() + const options = combineChartOptions(props.options) /** 获取 dom 容器实际宽高 */ const { height, width } = element.getBoundingClientRect() - const { success, error } = props + const { onSuccess, onError } = props try { /** 注册主题 */ @@ -305,19 +351,25 @@ const RayChart = defineComponent({ echartInstanceRef.value = echartInstance /** 设置 options 配置项 */ - options && echartInstance.setOption(options) + options && echartInstance.setOption({}) + + if (props.animation) { + setTimeout(() => { + options && echartInstance?.setOption(options) + }) + } /** 渲染成功回调 */ - if (success) { - call(success, echartInstance) + if (onSuccess) { + call(onSuccess, echartInstance) } } catch (e) { /** 渲染失败回调 */ - if (error) { - call(error) + if (onError) { + call(onError) } - console.error('RayChart render error: ', e) + console.error('RChart render error: ', e) } } @@ -329,9 +381,9 @@ const RayChart = defineComponent({ */ const renderThemeChart = (bool?: boolean) => { if (props.autoChangeTheme) { - bool ? renderChart('macarons-dark') : renderChart() + bool ? renderChart(`${echartTheme}-dark`) : renderChart() - return void 0 + return } if (!props.theme) { @@ -357,10 +409,51 @@ const RayChart = defineComponent({ } } + const mount = () => { + // 避免重复渲染 + if (echartInstance?.getDom()) { + console.warn( + 'RChart mount: There is a chart instance already initialized on the dom. Execution was interrupted', + ) + + return + } + + if (props.autoChangeTheme) { + /** 注册 echarts */ + renderThemeChart(themeValue.value) + } else { + props.theme ? renderChart(`${echartTheme}-dark`) : renderChart() + } + + /** 注册事件 */ + if (props.autoResize) { + resizeThrottleReturn = throttle(resizeChart, props.throttleWait) + /** 监听内容区域尺寸变化更新 chart */ + resizeOvserverReturn = useResizeObserver( + props.observer || rayChartWrapperRef, + resizeThrottleReturn, + ) + + on(window, 'resize', resizeThrottleReturn) + } + } + + const unmount = () => { + /** 卸载 echarts */ + destroyChart() + /** 卸载事件柄 */ + resizeThrottleReturn && off(window, 'resize', resizeThrottleReturn) + /** 注销防抖 */ + resizeThrottleReturn?.cancel() + /** 注销 observer 监听 */ + resizeOvserverReturn?.stop?.() + } + /** 监听全局主题变化, 然后重新渲染对应主题 echarts */ watch( - () => [themeValue.value], - ([theme]) => { + () => themeValue.value, + (theme) => { /** * * Q: 为什么需要重新卸载再渲染 @@ -375,19 +468,19 @@ const RayChart = defineComponent({ }, ) + /** + * + * 贴花跟随主题渲染 + * + * 自动跟随模板主题或者指定主题皆可 + */ watch( () => props.showAria, () => { destroyChart() - /** - * - * 贴花跟随主题渲染 - * - * 自动跟随模板主题或者指定主题皆可 - */ if (props.autoChangeTheme || props.theme) { - themeValue.value ? renderChart('macarons-dark') : renderChart() + themeValue.value ? renderChart(`${echartTheme}-dark`) : renderChart() } else { renderChart() } @@ -397,26 +490,41 @@ const RayChart = defineComponent({ /** 显示/隐藏加载动画 */ watch( () => props.loading, - (newData) => { - newData - ? echartInstance?.showLoading(modelLoadingOptions.value) + (ndata) => { + ndata + ? echartInstance?.showLoading(props.loadingOptions) : echartInstance?.hideLoading() }, ) /** 监听 options 变化 */ - if (props.watchOptions) { - watch( - () => props.watchOptions, - () => { + watch( + () => props.options, + (noptions) => { + if (props.watchOptions) { /** 重新组合 options */ - const options = combineChartOptions() + const options = combineChartOptions(noptions) + const setOpt = Object.assign({}, props.setChartOptions, { + notMerge: false, + lazyUpdate: true, + silent: false, + replaceMerge: [], + }) /** 如果 options 发生变动更新 echarts */ - echartInstance?.setOption(options) - }, - ) - } + echartInstance?.setOption(options, setOpt) + } + }, + { + deep: true, + }, + ) + + expose({ + echart: echartInstanceRef, + dispose: unmount, + render: mount, + }) onBeforeMount(async () => { /** 注册 echarts 组件与渲染器 */ @@ -425,53 +533,25 @@ const RayChart = defineComponent({ onMounted(() => { nextTick(() => { - /** 注册 echarts */ - if (props.autoChangeTheme) { - renderThemeChart(themeValue.value) - } else { - props.theme ? renderChart('macarons-dark') : renderChart() - } - - /** 注册事件 */ - if (props.autoResize) { - resizeThrottle = throttle(resizeChart, 500) - - on(window, 'resize', resizeThrottle) - } - - /** 监听内容区域尺寸变化更新 chart */ - resizeOvserverReturn = useResizeObserver( - LAYOUT_CONTENT_REF.value as unknown as Ref, - resizeThrottle, - ) + mount() }) }) onBeforeUnmount(() => { - /** 卸载 echarts */ - destroyChart() - /** 卸载事件柄 */ - off(window, 'resize', resizeThrottle) - /** 注销防抖 */ - resizeThrottle.cancel() - resizeOvserverReturn?.stop?.() - }) - - expose({ - echart: echartInstanceRef, + unmount() }) return { rayChartRef, cssVarsRef, - echartInstance: echartInstanceRef, + rayChartWrapperRef, } }, render() { return ( -
+
+
+
) }, }) - -export default RayChart diff --git a/src/components/RayChart/theme/macarons/macarons-dark.json b/src/components/RChart/theme/macarons/macarons-dark.json similarity index 100% rename from src/components/RayChart/theme/macarons/macarons-dark.json rename to src/components/RChart/theme/macarons/macarons-dark.json diff --git a/src/components/RayChart/theme/macarons/macarons.json b/src/components/RChart/theme/macarons/macarons.json similarity index 100% rename from src/components/RayChart/theme/macarons/macarons.json rename to src/components/RChart/theme/macarons/macarons.json diff --git a/src/components/RayChart/type.ts b/src/components/RChart/type.ts similarity index 65% rename from src/components/RayChart/type.ts rename to src/components/RChart/type.ts index d2810740..0750be61 100644 --- a/src/components/RayChart/type.ts +++ b/src/components/RChart/type.ts @@ -9,6 +9,8 @@ * @remark 今天也是元气满满撸代码的一天 */ +import type { ECharts, EChartsCoreOption } from 'echarts/core' + export interface ChartThemeRawModules { default: Record } @@ -41,3 +43,26 @@ export type AutoResize = } export type ChartTheme = 'macarons-dark' | string | object | 'macarons' + +export interface RayChartInst { + /** + * + * echart 实例 + * 访问当前 chart 图所有方法与属性 + * + * @default undefined + */ + echart: Ref + /** + * + * 手动卸载当前 chart 图 + * 注意:不会卸载当前组件,仅仅是卸载 chart + */ + dispose: () => void + /** + * + * 手动渲染 chart 图 + * 注意:会根据当前的 options 配置项与 props 配置项重新渲染 chart + */ + render: () => void +} diff --git a/src/components/RayCollapseGrid/index.ts b/src/components/RCollapseGrid/index.ts similarity index 100% rename from src/components/RayCollapseGrid/index.ts rename to src/components/RCollapseGrid/index.ts diff --git a/src/components/RayCollapseGrid/src/index.scss b/src/components/RCollapseGrid/src/index.scss similarity index 100% rename from src/components/RayCollapseGrid/src/index.scss rename to src/components/RCollapseGrid/src/index.scss diff --git a/src/components/RayCollapseGrid/src/index.tsx b/src/components/RCollapseGrid/src/index.tsx similarity index 85% rename from src/components/RayCollapseGrid/src/index.tsx rename to src/components/RCollapseGrid/src/index.tsx index 1f49d0d5..d6eaa430 100644 --- a/src/components/RayCollapseGrid/src/index.tsx +++ b/src/components/RCollapseGrid/src/index.tsx @@ -9,12 +9,22 @@ * @remark 今天也是元气满满撸代码的一天 */ +/** + * + * + * + * 可折叠操作栏 + * 可以结合表单或者表格使用,让你快捷的实现高级搜索功能 + * + * 该组件完全基于 `NGrid` `NGridItem` 实现, 所以需要在使用该组件时使用 `NGridItem` 包裹元素 + */ + import './index.scss' import { collapseGridProps } from './props' import { NCard, NGrid, NGridItem, NSpace } from 'naive-ui' -import RayIcon from '@/components/RayIcon' +import RayIcon from '@/components/RIcon' import { call } from '@/utils/vue/index' @@ -76,14 +86,10 @@ const RayCollapseGrid = defineComponent({ > {this.$slots.default?.()} - {{ - default: ({ overflow }: { overflow: boolean }) => ( - - {this.$slots.action?.()} - {overflow ? this.CollapseIcon() : ''} - - ), - }} + + {this.$slots.action?.()} + {this.CollapseIcon()} + ), @@ -94,14 +100,3 @@ const RayCollapseGrid = defineComponent({ }) export default RayCollapseGrid - -/** - * - * - * - * 可折叠操作栏 - * - * 可以结合表单或者表格使用 - * - * 该组件完全基于 `NGrid` `NGridItem` 实现, 所以需要在使用该组件时使用 `NGridItem` 包裹元素 - */ diff --git a/src/components/RayCollapseGrid/src/props.ts b/src/components/RCollapseGrid/src/props.ts similarity index 100% rename from src/components/RayCollapseGrid/src/props.ts rename to src/components/RCollapseGrid/src/props.ts diff --git a/src/components/RayCollapseGrid/src/type.ts b/src/components/RCollapseGrid/src/type.ts similarity index 100% rename from src/components/RayCollapseGrid/src/type.ts rename to src/components/RCollapseGrid/src/type.ts diff --git a/src/components/RayIcon/index.scss b/src/components/RIcon/index.scss similarity index 100% rename from src/components/RayIcon/index.scss rename to src/components/RIcon/index.scss diff --git a/src/components/RayIcon/index.tsx b/src/components/RIcon/index.tsx similarity index 100% rename from src/components/RayIcon/index.tsx rename to src/components/RIcon/index.tsx diff --git a/src/components/RIframe/index.ts b/src/components/RIframe/index.ts new file mode 100644 index 00000000..e0123a4c --- /dev/null +++ b/src/components/RIframe/index.ts @@ -0,0 +1,6 @@ +import RayIframe from './src/index' + +import type { RayIframeInst } from './src/index' + +export default RayIframe +export type { RayIframeInst } diff --git a/src/components/RayIframe/src/index.scss b/src/components/RIframe/src/index.scss similarity index 100% rename from src/components/RayIframe/src/index.scss rename to src/components/RIframe/src/index.scss diff --git a/src/components/RayIframe/src/index.tsx b/src/components/RIframe/src/index.tsx similarity index 93% rename from src/components/RayIframe/src/index.tsx rename to src/components/RIframe/src/index.tsx index f7834848..2ec2871e 100644 --- a/src/components/RayIframe/src/index.tsx +++ b/src/components/RIframe/src/index.tsx @@ -20,6 +20,10 @@ import type { PropType } from 'vue' import type { MaybeArray } from '@/types/modules/utils' import type { SpinProps } from 'naive-ui' +export interface RayIframeInst { + iframe: Ref +} + const RayIframe = defineComponent({ name: 'RayIframe', props: { @@ -73,7 +77,7 @@ const RayIframe = defineComponent({ type: String, default: null, }, - success: { + onSuccess: { /** * * iframe 加载成功回调 @@ -84,7 +88,7 @@ const RayIframe = defineComponent({ >, default: null, }, - error: { + onError: { /** * * iframe 加载失败回调 @@ -119,20 +123,20 @@ const RayIframe = defineComponent({ const iframeLoadSuccess = (e: Event) => { spinShow.value = false - const { success } = props + const { onSuccess } = props - if (success) { - call(success, iframeRef.value as HTMLIFrameElement, e) + if (onSuccess) { + call(onSuccess, iframeRef.value as HTMLIFrameElement, e) } } const iframeLoadError = (e: Event) => { spinShow.value = false - const { error } = props + const { onError } = props - if (error) { - call(error, e) + if (onError) { + call(onError, e) } } diff --git a/src/components/RQRCode/index.ts b/src/components/RQRCode/index.ts new file mode 100644 index 00000000..6002f700 --- /dev/null +++ b/src/components/RQRCode/index.ts @@ -0,0 +1,9 @@ +import RayQRcode from './src/index' + +export default RayQRcode +export type { + QRCodeStatus, + QRCodeLevel, + QRCodeRenderResponse, + QRCodeInst, +} from './src/type' diff --git a/src/components/RQRCode/src/index.scss b/src/components/RQRCode/src/index.scss new file mode 100644 index 00000000..e447cce8 --- /dev/null +++ b/src/components/RQRCode/src/index.scss @@ -0,0 +1,26 @@ +.ray-qrcode { + position: relative; + display: inline-flex; + + & .ray-qrcode__error { + position: absolute; + width: 100%; + top: 50%; + left: 50%; + transform: translate(-50%, -50%); + z-index: 10; + background-color: rgba(0, 0, 0, 0.7); + width: 100%; + height: 100%; + @include flexCenter; + flex-direction: column; + gap: 18px 0; + + & .ray-qrcode__error-content { + text-align: center; + font-size: 18px; + font-weight: 500; + color: #ffffff; + } + } +} diff --git a/src/components/RQRCode/src/index.tsx b/src/components/RQRCode/src/index.tsx new file mode 100644 index 00000000..ce3d3245 --- /dev/null +++ b/src/components/RQRCode/src/index.tsx @@ -0,0 +1,188 @@ +/** + * + * @author Ray + * + * @date 2023-08-29 + * + * @workspace ray-template + * + * @remark 今天也是元气满满撸代码的一天 + */ + +import './index.scss' + +import { NButton, NSpin } from 'naive-ui' +import RayIcon from '@/components/RIcon/index' + +import props from './props' +import { AwesomeQR } from 'awesome-qr' +import { isValueType, downloadBase64File } from '@use-utils/hook' +import { call } from '@/utils/vue/index' + +import type { QRCodeRenderResponse, GIFBuffer } from './type' + +const readGIFAsArrayBuffer = (url: string): Promise => { + return new Promise((resolve, reject) => { + const xhr = new XMLHttpRequest() + + xhr.responseType = 'blob' + + xhr.onload = () => { + const reader = new FileReader() + + reader.onloadend = () => { + resolve(reader.result) + } + reader.onerror = (e) => { + reject(e) + } + reader.onabort = (e) => { + reject(e) + } + + reader.readAsArrayBuffer(xhr.response) + } + + xhr.open('GET', url) + xhr.send() + }) +} + +export default defineComponent({ + name: 'RayQRcode', + props, + setup(props, ctx) { + const { expose } = ctx + + const qrcodeURL = ref() + const spinOverrides = { + opacitySpinning: '0.1', + } + let gifBuffer: GIFBuffer + + const getGIFImageByURL = async () => { + const { gifBackgroundURL } = props + + if (!gifBackgroundURL) { + return + } + + try { + gifBuffer = await readGIFAsArrayBuffer(gifBackgroundURL) + } catch (e) { + console.error(e) + } + } + + const renderQRCode = () => { + const { gifBackground, ...ops } = props + + new AwesomeQR({ + ...ops, + gifBackground: (gifBuffer as ArrayBuffer) ?? undefined, + }) + .draw() + .then((res) => { + const { onSuccess } = props + + if (onSuccess) { + call(onSuccess, res) + } + + qrcodeURL.value = res + }) + .catch((err) => { + const { onError } = props + + if (onError) { + call(onError, err) + } + }) + } + + const errorActionClick = () => { + if (ctx.slots.errorAction) { + return + } + + const { onReload } = props + + if (onReload) { + call(onReload) + } + } + + const downloadQRCode = (fileName?: string) => { + if (qrcodeURL.value && isValueType(qrcodeURL.value, 'String')) { + downloadBase64File( + qrcodeURL.value, + fileName || new Date().getTime() + '.png', + ) + } + } + + watchEffect(() => { + if (props.watchText) { + nextTick().then(() => { + renderQRCode() + }) + } + }) + + expose({ + downloadQRCode, + }) + + onMounted(async () => { + await getGIFImageByURL() + renderQRCode() + }) + + return { + qrcodeURL, + spinOverrides, + errorActionClick, + } + }, + render() { + return ( +
+ + + + {this.status === 'error' ? ( +
+
+ {isValueType(this.errorDescription, 'String') + ? this.errorDescription + : () => this.errorDescription} +
+
+ {this.$slots.errorAction ? ( + this.$slots.errorAction() + ) : ( + <> + + {{ + default: () => this.errorActionDescription, + icon: () => ( + + ), + }} + + + )} +
+
+ ) : null} +
+ ) + }, +}) diff --git a/src/components/RQRCode/src/props.ts b/src/components/RQRCode/src/props.ts new file mode 100644 index 00000000..7fc97324 --- /dev/null +++ b/src/components/RQRCode/src/props.ts @@ -0,0 +1,310 @@ +/** + * + * @author Ray + * + * @date 2023-08-29 + * + * @workspace ray-template + * + * @remark 今天也是元气满满撸代码的一天 + */ + +import type { QRCodeStatus, QRCodeLevel } from './type' +import type { PropType, VNode } from 'vue' +import type { MaybeArray } from '@/types/modules/utils' +import type { Options } from 'awesome-qr' + +const props = { + loadingDescription: { + /** + * + * Loading status description label + * + * @default undefined + */ + type: String, + }, + watchText: { + /** + * + * Atuo watch QR code text + * If update text, then re-render QR code + * + * @default true + */ + type: Boolean, + default: true, + }, + status: { + /** + * + * QR code status + * + * @default undefined + */ + type: String as PropType, + }, + errorDescription: { + /** + * + * QR code error description label + * + * @default 二维码已过期 + */ + type: [String, Object] as PropType, + default: '二维码已过期', + }, + errorActionDescription: { + /** + * + * QR code error action description label + * + * @default 重新加载 + */ + type: String, + default: '重新加载', + }, + text: { + /** + * + * Text to be encoded in the QR code + */ + type: String, + required: true, + }, + size: { + /** + * + * Size of the QR code in pixel. + * + * @default 160 + */ + type: Number, + default: 160, + }, + margin: { + /** + * + * Size of margins around the QR code body in pixel. + * + * @default 12 + */ + type: Number, + default: 12, + }, + correctLevel: { + /** + * + * Error correction level of the QR code + * Accepts a value provided by _QRErrorCorrectLevel_ + * + * @default 1 + */ + type: Number as PropType, + default: 1, + validator: (value: unknown) => [0, 1, 2, 3].includes(value as number), + }, + maskPattern: { + /** + * + * Specify the mask pattern to be used in QR code encoding + * Accepts a value provided by _QRMaskPattern_ + */ + type: Number, + }, + version: { + /** + * + * Specify the version to be used in QR code encoding + * Accepts an integer in range [1, 40] + */ + type: Number, + }, + components: { + /** + * + * Options to control components in the QR code. + * + * @default {data:{scale...},...} + */ + type: Object as PropType, + default: () => ({ + data: { + scale: 1, + }, + timing: { + scale: 1, + protectors: false, + }, + alignment: { + scale: 1, + protectors: false, + }, + cornerAlignment: { + scale: 1, + protectors: true, + }, + }), + }, + colorDark: { + /** + * + * Color of the blocks on the QR code + * Accepts a CSS <color> + * + * @default #000000 + */ + type: String, + default: '#000000', + }, + colorLight: { + /** + * + * Color of the blocks on the QR code + * Accepts a CSS <color> + * + * @default #ffffff + */ + type: String, + default: '#ffffff', + }, + autoColor: { + /** + * + * Automatically calculate the _colorLight_ value from the QR code's background + * + * @default true + */ + type: Boolean, + default: true, + }, + backgroundImage: { + /** + * + * Background image to be used in the QR code + * Accepts a `data:` string in web browsers or a Buffer in Node.js + * + * @default undefined + */ + type: String, + }, + backgroundDimming: { + /** + * + * Color of the dimming mask above the background image + * Accepts a CSS <color> + * + * @default rgba(0, 0, 0, 0) + */ + type: String, + default: 'rgba(0, 0, 0, 0)', + }, + gifBackgroundURL: { + /** + * + * GIF background image to be used in the QR code + * + * @default undefined + */ + type: String, + }, + gifBackground: { + /** + * + * GIF background image to be used in the QR code + * + * @default undefined + */ + type: ArrayBuffer, + }, + whiteMargin: { + /** + * + * Use a white margin instead of a transparent one which reveals the background of the QR code on margins + * + * @default true + */ + type: Boolean, + default: true, + }, + logoImage: { + /** + * + * Logo image to be displayed at the center of the QR code + * Accepts a `data:` string in web browsers or a Buffer in Node.js + * When set to `undefined` or `null`, the logo is disabled + * + * @default undefined + */ + type: String, + }, + logoScale: { + /** + * + * Ratio of the logo size to the QR code size + * + * @default 0.4 + */ + type: Number, + default: 0.4, + }, + logoMargin: { + /** + * + * Size of margins around the logo image in pixels + * + * @default 6 + */ + type: Number, + default: 6, + }, + logoCornerRadius: { + /** + * Corner radius of the logo image in pixels. + * + * @default 8 + */ + type: Number, + default: 8, + }, + dotScale: { + /** + * + * Ratio of the real size to the full size of the blocks. + * This can be helpful when you want to make more parts of the background visible. + * + * @default 1 + */ + type: Number, + default: 1, + }, + onSuccess: { + /** + * + * When the QR code is successfully generated, this callback is called + */ + type: [Function, Array] as PropType< + MaybeArray<(dataURL: ArrayBuffer | string | undefined) => void> + >, + default: null, + }, + onError: { + /** + * + * When the QR code generation fails, this callback is called + */ + type: [Function, Array] as PropType void>>, + default: null, + }, + onReload: { + /** + * + * When reload button is clicked, this callback is called + * This method will not execute if the errorAction slot is used + */ + type: [Function, Array] as PropType void>>, + default: null, + }, +} + +export default props diff --git a/src/components/RQRCode/src/type.ts b/src/components/RQRCode/src/type.ts new file mode 100644 index 00000000..6cffb552 --- /dev/null +++ b/src/components/RQRCode/src/type.ts @@ -0,0 +1,28 @@ +/** + * + * @author Ray + * + * @date 2023-08-29 + * + * @workspace ray-template + * + * @remark 今天也是元气满满撸代码的一天 + */ + +export type QRCodeStatus = 'error' | 'success' | 'loading' + +export type QRCodeLevel = 0 | 1 | 2 | 3 + +export type QRCodeRenderResponse = string | ArrayBuffer | Buffer | undefined + +export type QRCodeInst = { + /** + * + * @param fileName file name + * + * 如果未设置名称,则默认以 时间戳.png 命名 + */ + downloadQRCode: (fileName?: string) => void +} + +export type GIFBuffer = string | ArrayBuffer | null diff --git a/src/components/RayTable/index.ts b/src/components/RTable/index.ts similarity index 100% rename from src/components/RayTable/index.ts rename to src/components/RTable/index.ts diff --git a/src/components/RayTable/src/components/TableAction/index.tsx b/src/components/RTable/src/components/TableAction/index.tsx similarity index 98% rename from src/components/RayTable/src/components/TableAction/index.tsx rename to src/components/RTable/src/components/TableAction/index.tsx index 319352fa..564f04ae 100644 --- a/src/components/RayTable/src/components/TableAction/index.tsx +++ b/src/components/RTable/src/components/TableAction/index.tsx @@ -10,7 +10,7 @@ */ import { NPopconfirm, NSpace, NButton, NPopover } from 'naive-ui' -import RayIcon from '@/components/RayIcon/index' +import RayIcon from '@/components/RIcon/index' export type EmitterType = 'positive' | 'negative' diff --git a/src/components/RayTable/src/components/TableScreenfull/index.scss b/src/components/RTable/src/components/TableScreenfull/index.scss similarity index 100% rename from src/components/RayTable/src/components/TableScreenfull/index.scss rename to src/components/RTable/src/components/TableScreenfull/index.scss diff --git a/src/components/RayTable/src/components/TableScreenfull/index.tsx b/src/components/RTable/src/components/TableScreenfull/index.tsx similarity index 92% rename from src/components/RayTable/src/components/TableScreenfull/index.tsx rename to src/components/RTable/src/components/TableScreenfull/index.tsx index e8d55abe..eb449a51 100644 --- a/src/components/RayTable/src/components/TableScreenfull/index.tsx +++ b/src/components/RTable/src/components/TableScreenfull/index.tsx @@ -12,11 +12,11 @@ import './index.scss' import { NPopover } from 'naive-ui' -import RayIcon from '@/components/RayIcon/index' +import RayIcon from '@/components/RIcon/index' import screenfull from 'screenfull' -import type { TableSettingProvider } from '@/components/RayTable/src/type' +import type { TableSettingProvider } from '@/components/RTable/src/type' const TableScreenfull = defineComponent({ name: 'TableScreenfull', diff --git a/src/components/RayTable/src/components/TableSetting/hook.ts b/src/components/RTable/src/components/TableSetting/hook.ts similarity index 84% rename from src/components/RayTable/src/components/TableSetting/hook.ts rename to src/components/RTable/src/components/TableSetting/hook.ts index 5314b758..59d19d73 100644 --- a/src/components/RayTable/src/components/TableSetting/hook.ts +++ b/src/components/RTable/src/components/TableSetting/hook.ts @@ -1,4 +1,4 @@ -import type { ActionOptions } from '@/components/RayTable/src/type' +import type { ActionOptions } from '@/components/RTable/src/type' export const setupSettingOptions = (options: ActionOptions[]) => { const arr = options.map((curr) => { diff --git a/src/components/RayTable/src/components/TableSetting/index.scss b/src/components/RTable/src/components/TableSetting/index.scss similarity index 100% rename from src/components/RayTable/src/components/TableSetting/index.scss rename to src/components/RTable/src/components/TableSetting/index.scss diff --git a/src/components/RayTable/src/components/TableSetting/index.tsx b/src/components/RTable/src/components/TableSetting/index.tsx similarity index 98% rename from src/components/RayTable/src/components/TableSetting/index.tsx rename to src/components/RTable/src/components/TableSetting/index.tsx index 891e08d0..f48e4816 100644 --- a/src/components/RayTable/src/components/TableSetting/index.tsx +++ b/src/components/RTable/src/components/TableSetting/index.tsx @@ -19,7 +19,7 @@ import './index.scss' import { NCard, NPopover, NEllipsis } from 'naive-ui' -import RayIcon from '@/components/RayIcon/index' +import RayIcon from '@/components/RIcon/index' import VueDraggable from 'vuedraggable' import { setupSettingOptions } from './hook' @@ -30,7 +30,7 @@ import type { ActionOptions, FixedType, TableSettingFixedPopoverIcon, -} from '@/components/RayTable/src/type' +} from '@/components/RTable/src/type' const TableSetting = defineComponent({ name: 'TableSetting', diff --git a/src/components/RayTable/src/components/TableSize/index.scss b/src/components/RTable/src/components/TableSize/index.scss similarity index 100% rename from src/components/RayTable/src/components/TableSize/index.scss rename to src/components/RTable/src/components/TableSize/index.scss diff --git a/src/components/RayTable/src/components/TableSize/index.tsx b/src/components/RTable/src/components/TableSize/index.tsx similarity index 96% rename from src/components/RayTable/src/components/TableSize/index.tsx rename to src/components/RTable/src/components/TableSize/index.tsx index fe992cc3..cef8e91b 100644 --- a/src/components/RayTable/src/components/TableSize/index.tsx +++ b/src/components/RTable/src/components/TableSize/index.tsx @@ -12,11 +12,11 @@ import './index.scss' import { NPopover, NCard } from 'naive-ui' -import RayIcon from '@/components/RayIcon/index' +import RayIcon from '@/components/RIcon/index' import { call } from '@/utils/vue/index' -import type { TableSettingProvider } from '@/components/RayTable/src/type' +import type { TableSettingProvider } from '@/components/RTable/src/type' import type { ComponentSize } from '@/types/modules/component' import type { MaybeArray } from '@/types/modules/utils' diff --git a/src/components/RayTable/src/index.scss b/src/components/RTable/src/index.scss similarity index 70% rename from src/components/RayTable/src/index.scss rename to src/components/RTable/src/index.scss index 71ef89d8..06a30254 100644 --- a/src/components/RayTable/src/index.scss +++ b/src/components/RTable/src/index.scss @@ -1,21 +1,10 @@ -@keyframes scaleScreenfull { - 0% { - transform: scale(1); - } - 50% { - transform: scale(1.3); - } - 100% { - transform: scale(1); - } -} - .ray-table { & .ray-table-icon { transition: transform 0.3s var(--r-bezier); &:hover { - animation: scaleScreenfull 0.3s linear; + @include scaleAnimate(); + animation: elementScale 0.3s linear; animation-direction: alternate; } } diff --git a/src/components/RayTable/src/index.tsx b/src/components/RTable/src/index.tsx similarity index 86% rename from src/components/RayTable/src/index.tsx rename to src/components/RTable/src/index.tsx index 7a2ca908..bc3de22c 100644 --- a/src/components/RayTable/src/index.tsx +++ b/src/components/RTable/src/index.tsx @@ -58,8 +58,7 @@ import type { ComponentSize } from '@/types/modules/component' const RayTable = defineComponent({ name: 'RayTable', props: props, - emits: ['update:columns', 'exportSuccess', 'exportError'], - setup(props, { emit, expose }) { + setup(props, { expose }) { const rayTableInstance = ref() const tableUUID = uuid(16) // 表格 id, 用于打印表格 @@ -68,7 +67,15 @@ const RayTable = defineComponent({ const modelColumns = computed({ get: () => props.columns, set: (arr) => { - emit('update:columns', arr) + const { onUpdateColumns, 'onUpdate:columns': _onUpdateColumns } = props + + if (onUpdateColumns) { + call(onUpdateColumns, arr) + } + + if (_onUpdateColumns) { + call(_onUpdateColumns, arr) + } }, }) as unknown as WritableComputedRef const menuConfig = reactive({ @@ -76,7 +83,6 @@ const RayTable = defineComponent({ y: 0, showMenu: false, }) - let prevRightClickIndex = -1 // 缓存上次点击索引位置 const cssVars = computed(() => { const cssVar = { '--ray-table-header-space': props.tableHeaderSpace, @@ -86,6 +92,7 @@ const RayTable = defineComponent({ }) const tableSize = ref(props.size) const tableMethods = ref>() + let prevRightClickIndex = -1 // 缓存上次点击索引位置 /** 注入相关属性 */ provide('tableSettingProvider', { @@ -111,17 +118,12 @@ const RayTable = defineComponent({ key: string | number, option: DropdownOption, ) => { - const { onRightMenuClick, 'onUpdate:rightMenuClick': _onRightMenuClick } = - props + const { onRightMenuClick } = props if (onRightMenuClick) { call(onRightMenuClick, key, prevRightClickIndex, option) } - if (_onRightMenuClick) { - call(_onRightMenuClick, key, prevRightClickIndex, option) - } - menuConfig.showMenu = false } @@ -136,22 +138,24 @@ const RayTable = defineComponent({ const handleRowProps = (arr: ActionOptions, idx: number) => { const interceptRowProps = props.rowProps?.(arr, idx) + const contextmenu = modelRightClickMenu.value.length + ? (e: MouseEvent) => { + e.preventDefault() + + prevRightClickIndex = idx + menuConfig.showMenu = false + + nextTick().then(() => { + menuConfig.showMenu = true + menuConfig.x = e.clientX + menuConfig.y = e.clientY + }) + } + : void 0 + return { ...interceptRowProps, - onContextmenu: (e: MouseEvent) => { - e.preventDefault() - - prevRightClickIndex = idx - - menuConfig.showMenu = false - - nextTick().then(() => { - menuConfig.showMenu = true - - menuConfig.x = e.clientX - menuConfig.y = e.clientY - }) - }, + onContextmenu: contextmenu, } } @@ -164,6 +168,8 @@ const RayTable = defineComponent({ * 按需导入 `xlsx` 减少体积, 不依赖传统 `file save` 插件导出方式 */ const handleExportPositive = async () => { + const { onExportSuccess, onExportError } = props + if (props.data.length && props.columns.length) { try { await exportFileToXLSX( @@ -174,9 +180,9 @@ const RayTable = defineComponent({ }, ) - emit('exportSuccess') + onExportSuccess && call(onExportSuccess) } catch (e) { - emit('exportError') + onExportError && call(onExportError) } } } @@ -255,7 +261,6 @@ const RayTable = defineComponent({ } }, render() { - console.log(this.action) return ( - {this.showMenu ? ( - // 右键菜单 - (this.showMenu = false)} - onSelect={this.handleRightMenuSelect.bind(this)} - /> - ) : ( - '' - )} + (this.showMenu = false)} + onSelect={this.handleRightMenuSelect.bind(this)} + /> ), header: () => this.title ||
, diff --git a/src/components/RayTable/src/props.ts b/src/components/RTable/src/props.ts similarity index 88% rename from src/components/RayTable/src/props.ts rename to src/components/RTable/src/props.ts index 468f4675..4e41f180 100644 --- a/src/components/RayTable/src/props.ts +++ b/src/components/RTable/src/props.ts @@ -15,7 +15,7 @@ import type { PropType, VNode, VNodeChild } from 'vue' import type { DropdownMixedOption } from './type' import type PrintConfiguration from 'print-js' import type { MaybeArray } from '@/types/modules/utils' -import type { DropdownOption } from 'naive-ui' +import type { DropdownOption, DataTableColumn } from 'naive-ui' const rayTableProps = { ...dataTableProps, // 继承 `data table props` @@ -39,14 +39,6 @@ const rayTableProps = { >, default: null, }, - 'onUpdate:rightMenuClick': { - type: [Function, Array] as PropType< - MaybeArray< - (key: string | number, index: number, option: DropdownOption) => void - > - >, - default: null, - }, title: { /** * @@ -77,16 +69,6 @@ const rayTableProps = { type: Object as PropType, default: () => ({}), }, - showMenu: { - /** - * - * 是否展示右键菜单 - * - * 默认启用 - */ - type: Boolean, - default: true, - }, exportTooltip: { /** * @@ -225,7 +207,30 @@ const rayTableProps = { type: Boolean, default: false, }, + /** 导出成功 */ + onExportSuccess: { + type: [Function, Array] as PropType void>>, + default: null, + }, + /** 导出失败 */ + onExportError: { + type: [Function, Array] as PropType void>>, + default: null, + }, + onUpdateColumns: { + type: [Function, Array] as PropType< + MaybeArray<(arr: DataTableColumn[]) => void> + >, + default: null, + }, + 'onUpdate:columns': { + type: [Function, Array] as PropType< + MaybeArray<(arr: DataTableColumn[]) => void> + >, + default: null, + }, } as const + export default rayTableProps /** diff --git a/src/components/RayTable/src/type.ts b/src/components/RTable/src/type.ts similarity index 100% rename from src/components/RayTable/src/type.ts rename to src/components/RTable/src/type.ts diff --git a/src/components/RayTransitionComponent/index.vue b/src/components/RTransitionComponent/index.vue similarity index 96% rename from src/components/RayTransitionComponent/index.vue rename to src/components/RTransitionComponent/index.vue index 7970c719..93a7edc4 100644 --- a/src/components/RayTransitionComponent/index.vue +++ b/src/components/RTransitionComponent/index.vue @@ -24,7 +24,7 @@