Compact main branch (#2255)

* init

* init

* fix: 修改外联路由打包bug

* fix: sime

* wip(lock): remove

* fix: LOCK

* fix: lock

* init

* feat: remove lock

* chore: remove semi

* chore: chore

* chore: chore

* chore: chore

* init

* init

* init

* init

* init

* init

* init

* init

* init

* init

* init

* init

* init

* init

* init

* init

* init

* init

* init

* init

* init

* init
This commit is contained in:
saber 2022-10-10 10:53:48 +08:00 committed by GitHub
parent 39188780ea
commit d5e2d26a0f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
716 changed files with 7340 additions and 48285 deletions

View File

@ -109,7 +109,6 @@
"esnext", "esnext",
"antv", "antv",
"tinymce", "tinymce",
"qrcode",
"sider", "sider",
"pinia", "pinia",
"sider", "sider",

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

174
README.md
View File

@ -5,67 +5,47 @@
<h1>Vue vben admin</h1> <h1>Vue vben admin</h1>
</div> </div>
**English** | [中文](./README.zh-CN.md) ## 简介
## Introduction 精简 Vue Vben Admin。
Vue Vben Admin is a free and open source middle and back-end template. Using the latest `vue3`, `vite2`, `TypeScript` and other mainstream technology development, the out-of-the-box middle and back-end front-end solutions can also be used for learning reference. ## 特性
## Feature - **最新技术栈**:使用 Vue3/vite2 等前端前沿技术开发
- **TypeScript**: 应用程序级 JavaScript 的语言
- **主题**:可配置的主题
- **国际化**:内置完善的国际化方案
- **Mock 数据** 内置 Mock 数据方案
- **权限** 内置完善的动态路由权限生成方案
- **组件** 二次封装了多个常用的组件
- **State of The Art Development**Use front-end front-end technology development such as Vue3/vite2 ## 预览
- **TypeScript**: Application-level JavaScript language
- **Theming**: Configurable themes
- **International**Built-in complete internationalization program
- **Mock Server** Built-in mock data scheme
- **Authority** Built-in complete dynamic routing permission generation scheme.
- **Component** Multiple commonly used components are encapsulated twice
## Preview - [vue-vben-admin](https://vvbin.cn/next/) - 完整版中文站点
- [vue-vben-admin-gh-pages](https://anncwb.github.io/vue-vben-admin/) - 完整版 github 站点
- [vben-admin-thin-next](https://vvbin.cn/thin/next/) - 简化版中文站点
- [vben-admin-thin-gh-pages](https://anncwb.github.io/vben-admin-thin-next/) - 简化版 github 站点
- [vue-vben-admin](https://vvbin.cn/next/) - Full version Chinese site ## 准备
- [vue-vben-admin-gh-pages](https://anncwb.github.io/vue-vben-admin/) - Full version of the github site
- [vben-admin-thin-next](https://vvbin.cn/thin/next/) - Simplified Chinese site
- [vben-admin-thin-gh-pages](https://anncwb.github.io/vben-admin-thin-next/) -Simplified github site
Test account: vben/123456 - [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`基本语法
- [Es6+](http://es6.ruanyifeng.com/) - 熟悉 es6 基本语法
- [Vue-Router-Next](https://next.router.vuejs.org/) - 熟悉 vue-router 基本使用
- [Ant-Design-Vue](https://2x.antdv.com/docs/vue/introduce-cn/) - ui 基本使用
- [Mock.js](https://github.com/nuysoft/Mock) - mockjs 基本语法
<p align="center"> ## 安装使用
<img alt="VbenAdmin Logo" width="100%" src="https://anncwb.github.io/anncwb/images/preview1.png">
<img alt="VbenAdmin Logo" width="100%" src="https://anncwb.github.io/anncwb/images/preview2.png">
<img alt="VbenAdmin Logo" width="100%" src="https://anncwb.github.io/anncwb/images/preview3.png">
</p>
### Use Gitpod - 获取项目代码
Open the project in Gitpod (free online dev environment for GitHub) and start coding immediately.
[![Open in Gitpod](https://gitpod.io/button/open-in-gitpod.svg)](https://gitpod.io/#https://github.com/anncwb/vue-vben-admin)
## Documentation
[Document](https://vvbin.cn/doc-next/)
## Preparation
- [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 Vue basic syntax
- [TypeScript](https://www.typescriptlang.org/) - Familiar with the basic syntax of `TypeScript`
- [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-router
- [Ant-Design-Vue](https://2x.antdv.com/docs/vue/introduce-cn/) - ui basic use
- [Mock.js](https://github.com/nuysoft/Mock) - mockjs basic syntax
## Install and use
- Get the project code
```bash ```bash
git clone https://github.com/anncwb/vue-vben-admin.git git clone https://github.com/anncwb/vue-vben-admin.git
``` ```
- Installation dependencies - 安装依赖
```bash ```bash
cd vue-vben-admin cd vue-vben-admin
@ -74,95 +54,47 @@ pnpm install
``` ```
- run - 运行
```bash ```bash
pnpm serve pnpm serve
``` ```
- build - 打包
```bash ```bash
pnpm build pnpm build
``` ```
## Change Log ## Git 贡献提交规范
[CHANGELOG](./CHANGELOG.zh_CN.md) - 参考 [vue](https://github.com/vuejs/vue/blob/dev/.github/COMMIT_CONVENTION.md) 规范 ([Angular](https://github.com/conventional-changelog/conventional-changelog/tree/master/packages/conventional-changelog-angular))
## Project - `feat` 增加新功能
- `fix` 修复问题/BUG
- `style` 代码风格相关无影响运行结果的
- `perf` 优化/性能提升
- `refactor` 重构
- `revert` 撤销修改
- `test` 测试相关
- `docs` 文档/注释
- `chore` 依赖更新/脚手架配置修改等
- `workflow` 工作流改进
- `ci` 持续集成
- `types` 类型定义文件更改
- `wip` 开发中
- [vue-vben-admin](https://github.com/anncwb/vue-vben-admin) - full version ## 相关仓库
- [vue-vben-admin-thin-next](https://github.com/anncwb/vben-admin-thin-next) - Simplified version
## How to contribute 如果这些插件对你有帮助,可以给一个 star 支持下
You are very welcome to join[Raise an issue](https://github.com/anncwb/vue-vben-admin/issues/new/choose) Or submit a Pull Request。 - [vite-plugin-mock](https://github.com/anncwb/vite-plugin-mock) - 用于本地及开发环境数据 mock
- [vite-plugin-html](https://github.com/anncwb/vite-plugin-html) - 用于 html 模版转换及压缩
**Pull Request:** - [vite-plugin-style-import](https://github.com/anncwb/vite-plugin-style-import) - 用于组件库样式按需引入
- [vite-plugin-theme](https://github.com/anncwb/vite-plugin-theme) - 用于在线切换主题色等颜色相关配置
1. Fork code! - [vite-plugin-imagemin](https://github.com/anncwb/vite-plugin-imagemin) - 用于打包压缩图片资源
2. Create your own branch: `git checkout -b feat/xxxx` - [vite-plugin-compression](https://github.com/anncwb/vite-plugin-compression) - 用于打包输出.gz|.brotil 文件
3. Submit your changes: `git commit -am 'feat(function): add xxxxx'` - [vite-plugin-svg-icons](https://github.com/anncwb/vite-plugin-svg-icons) - 用于快速生成 svg 雪碧图
4. Push your branch: `git push origin feat/xxxx`
5. submit`pull request`
## Git Contribution submission specification
- reference [vue](https://github.com/vuejs/vue/blob/dev/.github/COMMIT_CONVENTION.md) specification ([Angular](https://github.com/conventional-changelog/conventional-changelog/tree/master/packages/conventional-changelog-angular))
- `feat` Add new features
- `fix` Fix the problem/BUG
- `style` The code style is related and does not affect the running result
- `perf` Optimization/performance improvement
- `refactor` Refactor
- `revert` Undo edit
- `test` Test related
- `docs` Documentation/notes
- `chore` Dependency update/scaffolding configuration modification etc.
- `workflow` Workflow improvements
- `ci` Continuous integration
- `types` Type definition file changes
- `wip` In development
## Related warehouse
If these plugins are helpful to you, you can give a star support
- [vite-plugin-mock](https://github.com/anncwb/vite-plugin-mock) - Used for local and development environment data mock
- [vite-plugin-html](https://github.com/anncwb/vite-plugin-html) - Used for html template conversion and compression
- [vite-plugin-style-import](https://github.com/anncwb/vite-plugin-style-import) - Used for component library style introduction on demand
- [vite-plugin-theme](https://github.com/anncwb/vite-plugin-theme) - Used for online switching of theme colors and other color-related configurations
- [vite-plugin-imagemin](https://github.com/anncwb/vite-plugin-imagemin) - Used to pack compressed image resources
- [vite-plugin-compression](https://github.com/anncwb/vite-plugin-compression) - Used to pack input .gz|.brotil files
- [vite-plugin-svg-icons](https://github.com/anncwb/vite-plugin-svg-icons) - Used to quickly generate svg sprite
## Browser support
The `Chrome 80+` browser is recommended for local development
Support modern browsers, not IE
| [<img src="https://raw.githubusercontent.com/alrra/browser-logos/master/src/edge/edge_48x48.png" alt=" Edge" width="24px" height="24px" />](http://godban.github.io/browsers-support-badges/)</br>IE | [<img src="https://raw.githubusercontent.com/alrra/browser-logos/master/src/edge/edge_48x48.png" alt=" Edge" width="24px" height="24px" />](http://godban.github.io/browsers-support-badges/)</br>Edge | [<img src="https://raw.githubusercontent.com/alrra/browser-logos/master/src/firefox/firefox_48x48.png" alt="Firefox" width="24px" height="24px" />](http://godban.github.io/browsers-support-badges/)</br>Firefox | [<img src="https://raw.githubusercontent.com/alrra/browser-logos/master/src/chrome/chrome_48x48.png" alt="Chrome" width="24px" height="24px" />](http://godban.github.io/browsers-support-badges/)</br>Chrome | [<img src="https://raw.githubusercontent.com/alrra/browser-logos/master/src/safari/safari_48x48.png" alt="Safari" width="24px" height="24px" />](http://godban.github.io/browsers-support-badges/)</br>Safari |
| :-: | :-: | :-: | :-: | :-: |
| not support | last 2 versions | last 2 versions | last 2 versions | last 2 versions |
## Maintainer
[@Vben](https://github.com/anncwb)
## Donate
If you think this project is helpful to you, you can help the author buy a cup of coffee to show your support!
![donate](https://anncwb.github.io/anncwb/images/sponsor.png)
<a style="display: block;width: 100px;height: 50px;line-height: 50px; color: #fff;text-align: center; background: #408aed;border-radius: 4px;" href="https://www.paypal.com/paypalme/cvvben">Paypal Me</a>
## Discord
- [github discussions](https://github.com/anncwb/vue-vben-admin/discussions)
- [Discord](https://discord.gg/8GuAdwDhj6)
## License ## License

View File

@ -1,175 +0,0 @@
<div align="center"> <a href="https://github.com/anncwb/vue-vben-admin"> <img alt="VbenAdmin Logo" width="200" height="200" src="https://anncwb.github.io/anncwb/images/logo.png"> </a> <br> <br>
[![license](https://img.shields.io/github/license/anncwb/vue-vben-admin.svg)](LICENSE)
<h1>Vue vben admin</h1>
</div>
**中文** | [English](./README.md)
## 简介
Vue Vben Admin 是一个免费开源的中后台模版。使用了最新的`vue3`,`vite2`,`TypeScript`等主流技术开发,开箱即用的中后台前端解决方案,也可用于学习参考。
## 特性
- **最新技术栈**:使用 Vue3/vite2 等前端前沿技术开发
- **TypeScript**: 应用程序级 JavaScript 的语言
- **主题**:可配置的主题
- **国际化**:内置完善的国际化方案
- **Mock 数据** 内置 Mock 数据方案
- **权限** 内置完善的动态路由权限生成方案
- **组件** 二次封装了多个常用的组件
## 预览
- [vue-vben-admin](https://vvbin.cn/next/) - 完整版中文站点
- [vue-vben-admin-gh-pages](https://anncwb.github.io/vue-vben-admin/) - 完整版 github 站点
- [vben-admin-thin-next](https://vvbin.cn/thin/next/) - 简化版中文站点
- [vben-admin-thin-gh-pages](https://anncwb.github.io/vben-admin-thin-next/) - 简化版 github 站点
测试账号: vben/123456
<p align="center">
<img alt="VbenAdmin Logo" width="100%" src="https://anncwb.github.io/anncwb/images/preview1.png">
<img alt="VbenAdmin Logo" width="100%" src="https://anncwb.github.io/anncwb/images/preview2.png">
<img alt="VbenAdmin Logo" width="100%" src="https://anncwb.github.io/anncwb/images/preview3.png">
</p>
### 使用 Gitpod
在 Gitpod适用于 GitHub 的免费在线开发环境)中打开项目,并立即开始编码.
[![Open in Gitpod](https://gitpod.io/button/open-in-gitpod.svg)](https://gitpod.io/#https://github.com/anncwb/vue-vben-admin)
## 文档
[文档地址](https://vvbin.cn/doc-next/)
## 准备
- [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`基本语法
- [Es6+](http://es6.ruanyifeng.com/) - 熟悉 es6 基本语法
- [Vue-Router-Next](https://next.router.vuejs.org/) - 熟悉 vue-router 基本使用
- [Ant-Design-Vue](https://2x.antdv.com/docs/vue/introduce-cn/) - ui 基本使用
- [Mock.js](https://github.com/nuysoft/Mock) - mockjs 基本语法
## 安装使用
- 获取项目代码
```bash
git clone https://github.com/anncwb/vue-vben-admin.git
```
- 安装依赖
```bash
cd vue-vben-admin
pnpm install
```
- 运行
```bash
pnpm serve
```
- 打包
```bash
pnpm build
```
## 更新日志
[CHANGELOG](./CHANGELOG.zh_CN.md)
## 项目地址
- [vue-vben-admin](https://github.com/anncwb/vue-vben-admin) - 完整版
- [vue-vben-admin-thin-next](https://github.com/anncwb/vben-admin-thin-next) - 简化版
## 如何贡献
非常欢迎你的加入![提一个 Issue](https://github.com/anncwb/vue-vben-admin/issues/new/choose) 或者提交一个 Pull Request。
**Pull Request:**
1. Fork 代码!
2. 创建自己的分支: `git checkout -b feat/xxxx`
3. 提交你的修改: `git commit -am 'feat(function): add xxxxx'`
4. 推送您的分支: `git push origin feat/xxxx`
5. 提交`pull request`
## Git 贡献提交规范
- 参考 [vue](https://github.com/vuejs/vue/blob/dev/.github/COMMIT_CONVENTION.md) 规范 ([Angular](https://github.com/conventional-changelog/conventional-changelog/tree/master/packages/conventional-changelog-angular))
- `feat` 增加新功能
- `fix` 修复问题/BUG
- `style` 代码风格相关无影响运行结果的
- `perf` 优化/性能提升
- `refactor` 重构
- `revert` 撤销修改
- `test` 测试相关
- `docs` 文档/注释
- `chore` 依赖更新/脚手架配置修改等
- `workflow` 工作流改进
- `ci` 持续集成
- `types` 类型定义文件更改
- `wip` 开发中
## 浏览器支持
本地开发推荐使用`Chrome 80+` 浏览器
支持现代浏览器, 不支持 IE
| [<img src="https://raw.githubusercontent.com/alrra/browser-logos/master/src/edge/edge_48x48.png" alt=" Edge" width="24px" height="24px" />](http://godban.github.io/browsers-support-badges/)</br>IE | [<img src="https://raw.githubusercontent.com/alrra/browser-logos/master/src/edge/edge_48x48.png" alt=" Edge" width="24px" height="24px" />](http://godban.github.io/browsers-support-badges/)</br>Edge | [<img src="https://raw.githubusercontent.com/alrra/browser-logos/master/src/firefox/firefox_48x48.png" alt="Firefox" width="24px" height="24px" />](http://godban.github.io/browsers-support-badges/)</br>Firefox | [<img src="https://raw.githubusercontent.com/alrra/browser-logos/master/src/chrome/chrome_48x48.png" alt="Chrome" width="24px" height="24px" />](http://godban.github.io/browsers-support-badges/)</br>Chrome | [<img src="https://raw.githubusercontent.com/alrra/browser-logos/master/src/safari/safari_48x48.png" alt="Safari" width="24px" height="24px" />](http://godban.github.io/browsers-support-badges/)</br>Safari |
| :-: | :-: | :-: | :-: | :-: |
| not support | last 2 versions | last 2 versions | last 2 versions | last 2 versions |
## 相关仓库
如果这些插件对你有帮助,可以给一个 star 支持下
- [vite-plugin-mock](https://github.com/anncwb/vite-plugin-mock) - 用于本地及开发环境数据 mock
- [vite-plugin-html](https://github.com/anncwb/vite-plugin-html) - 用于 html 模版转换及压缩
- [vite-plugin-style-import](https://github.com/anncwb/vite-plugin-style-import) - 用于组件库样式按需引入
- [vite-plugin-theme](https://github.com/anncwb/vite-plugin-theme) - 用于在线切换主题色等颜色相关配置
- [vite-plugin-imagemin](https://github.com/anncwb/vite-plugin-imagemin) - 用于打包压缩图片资源
- [vite-plugin-compression](https://github.com/anncwb/vite-plugin-compression) - 用于打包输出.gz|.brotil 文件
- [vite-plugin-svg-icons](https://github.com/anncwb/vite-plugin-svg-icons) - 用于快速生成 svg 雪碧图
## 后台整合示例
- [lamp-cloud](https://github.com/zuihou/lamp-cloud) - 基于 SpringCloud Alibaba 的微服务中后台快速开发平台
- [matecloud](https://github.com/matevip/matecloud) - MateCloud 微服务脚手架,基于 Spring Cloud 2020.0.3、SpringBoot 2.5.3 的全开源平台
## 维护者
[@Vben](https://github.com/anncwb)
## 捐赠
如果你觉得这个项目对你有帮助,你可以帮作者买一杯咖啡表示支持!
![donate](https://anncwb.github.io/anncwb/images/sponsor.png)
<a style="display: block;width: 100px;height: 50px;line-height: 50px; color: #fff;text-align: center; background: #408aed;border-radius: 4px;" href="https://www.paypal.com/paypalme/cvvben">Paypal Me</a>
## 交流
`Vue-vben-Admin` 是完全开源免费的项目,在帮助开发者更方便地进行中大型管理系统开发,同时也提供 QQ 交流群使用问题欢迎在群内提问。
- QQ 群 `569291866`
## License
[MIT © Vben-2020](./LICENSE)

View File

@ -1,20 +1,20 @@
import { PluginOption } from 'vite'; import { PluginOption } from 'vite'
import vue from '@vitejs/plugin-vue'; import vue from '@vitejs/plugin-vue'
import vueJsx from '@vitejs/plugin-vue-jsx'; import vueJsx from '@vitejs/plugin-vue-jsx'
import legacy from '@vitejs/plugin-legacy'; import legacy from '@vitejs/plugin-legacy'
import purgeIcons from 'vite-plugin-purge-icons'; import purgeIcons from 'vite-plugin-purge-icons'
import windiCSS from 'vite-plugin-windicss'; import windiCSS from 'vite-plugin-windicss'
import VitePluginCertificate from 'vite-plugin-mkcert'; import VitePluginCertificate from 'vite-plugin-mkcert'
//import vueSetupExtend from 'vite-plugin-vue-setup-extend'; //import vueSetupExtend from 'vite-plugin-vue-setup-extend';
import { configHtmlPlugin } from './html'; import { configHtmlPlugin } from './html'
import { configPwaConfig } from './pwa'; import { configPwaConfig } from './pwa'
import { configMockPlugin } from './mock'; import { configMockPlugin } from './mock'
import { configCompressPlugin } from './compress'; import { configCompressPlugin } from './compress'
import { configStyleImportPlugin } from './styleImport'; import { configStyleImportPlugin } from './styleImport'
import { configVisualizerConfig } from './visualizer'; import { configVisualizerConfig } from './visualizer'
import { configThemePlugin } from './theme'; import { configThemePlugin } from './theme'
import { configImageminPlugin } from './imagemin'; import { configImageminPlugin } from './imagemin'
import { configSvgIconsPlugin } from './svgSprite'; import { configSvgIconsPlugin } from './svgSprite'
export function createVitePlugins(viteEnv: ViteEnv, isBuild: boolean) { export function createVitePlugins(viteEnv: ViteEnv, isBuild: boolean) {
const { const {
@ -23,7 +23,7 @@ export function createVitePlugins(viteEnv: ViteEnv, isBuild: boolean) {
VITE_LEGACY, VITE_LEGACY,
VITE_BUILD_COMPRESS, VITE_BUILD_COMPRESS,
VITE_BUILD_COMPRESS_DELETE_ORIGIN_FILE, VITE_BUILD_COMPRESS_DELETE_ORIGIN_FILE,
} = viteEnv; } = viteEnv
const vitePlugins: (PluginOption | PluginOption[])[] = [ const vitePlugins: (PluginOption | PluginOption[])[] = [
// have to // have to
@ -35,48 +35,48 @@ export function createVitePlugins(viteEnv: ViteEnv, isBuild: boolean) {
VitePluginCertificate({ VitePluginCertificate({
source: 'coding', source: 'coding',
}), }),
]; ]
// vite-plugin-windicss // vite-plugin-windicss
vitePlugins.push(windiCSS()); vitePlugins.push(windiCSS())
// @vitejs/plugin-legacy // @vitejs/plugin-legacy
VITE_LEGACY && isBuild && vitePlugins.push(legacy()); VITE_LEGACY && isBuild && vitePlugins.push(legacy())
// vite-plugin-html // vite-plugin-html
vitePlugins.push(configHtmlPlugin(viteEnv, isBuild)); vitePlugins.push(configHtmlPlugin(viteEnv, isBuild))
// vite-plugin-svg-icons // vite-plugin-svg-icons
vitePlugins.push(configSvgIconsPlugin(isBuild)); vitePlugins.push(configSvgIconsPlugin(isBuild))
// vite-plugin-mock // vite-plugin-mock
VITE_USE_MOCK && vitePlugins.push(configMockPlugin(isBuild)); VITE_USE_MOCK && vitePlugins.push(configMockPlugin(isBuild))
// vite-plugin-purge-icons // vite-plugin-purge-icons
vitePlugins.push(purgeIcons()); vitePlugins.push(purgeIcons())
// vite-plugin-style-import // vite-plugin-style-import
vitePlugins.push(configStyleImportPlugin(isBuild)); vitePlugins.push(configStyleImportPlugin(isBuild))
// rollup-plugin-visualizer // rollup-plugin-visualizer
vitePlugins.push(configVisualizerConfig()); vitePlugins.push(configVisualizerConfig())
// vite-plugin-theme // vite-plugin-theme
vitePlugins.push(configThemePlugin(isBuild)); vitePlugins.push(configThemePlugin(isBuild))
// The following plugins only work in the production environment // The following plugins only work in the production environment
if (isBuild) { if (isBuild) {
// vite-plugin-imagemin // vite-plugin-imagemin
VITE_USE_IMAGEMIN && vitePlugins.push(configImageminPlugin()); VITE_USE_IMAGEMIN && vitePlugins.push(configImageminPlugin())
// rollup-plugin-gzip // rollup-plugin-gzip
vitePlugins.push( vitePlugins.push(
configCompressPlugin(VITE_BUILD_COMPRESS, VITE_BUILD_COMPRESS_DELETE_ORIGIN_FILE), configCompressPlugin(VITE_BUILD_COMPRESS, VITE_BUILD_COMPRESS_DELETE_ORIGIN_FILE),
); )
// vite-plugin-pwa // vite-plugin-pwa
vitePlugins.push(configPwaConfig(viteEnv)); vitePlugins.push(configPwaConfig(viteEnv))
} }
return vitePlugins; return vitePlugins
} }

View File

@ -2,11 +2,11 @@
* Introduces component library styles on demand. * Introduces component library styles on demand.
* https://github.com/anncwb/vite-plugin-style-import * https://github.com/anncwb/vite-plugin-style-import
*/ */
import { createStyleImportPlugin } from 'vite-plugin-style-import'; import { createStyleImportPlugin } from 'vite-plugin-style-import'
export function configStyleImportPlugin(_isBuild: boolean) { export function configStyleImportPlugin(_isBuild: boolean) {
if (!_isBuild) { if (!_isBuild) {
return []; return []
} }
const styleImportPlugin = createStyleImportPlugin({ const styleImportPlugin = createStyleImportPlugin({
libs: [ libs: [
@ -45,7 +45,7 @@ export function configStyleImportPlugin(_isBuild: boolean) {
'skeleton-paragraph', 'skeleton-paragraph',
'skeleton-image', 'skeleton-image',
'skeleton-button', 'skeleton-button',
]; ]
// 这里是需要额外引入样式的子组件列表 // 这里是需要额外引入样式的子组件列表
// 单独引入子组件时需引入组件样式,否则会在打包后导致子组件样式丢失 // 单独引入子组件时需引入组件样式,否则会在打包后导致子组件样式丢失
const replaceList = { const replaceList = {
@ -66,16 +66,16 @@ export function configStyleImportPlugin(_isBuild: boolean) {
'month-picker': 'date-picker', 'month-picker': 'date-picker',
'range-picker': 'date-picker', 'range-picker': 'date-picker',
'image-preview-group': 'image', 'image-preview-group': 'image',
}; }
return ignoreList.includes(name) return ignoreList.includes(name)
? '' ? ''
: replaceList.hasOwnProperty(name) : replaceList.hasOwnProperty(name)
? `ant-design-vue/es/${replaceList[name]}/style/index` ? `ant-design-vue/es/${replaceList[name]}/style/index`
: `ant-design-vue/es/${name}/style/index`; : `ant-design-vue/es/${name}/style/index`
}, },
}, },
], ],
}); })
return styleImportPlugin; return styleImportPlugin
} }

View File

@ -1,11 +1,11 @@
const fs = require('fs'); const fs = require('fs')
const path = require('path'); const path = require('path')
const { execSync } = require('child_process'); const { execSync } = require('child_process')
const scopes = fs const scopes = fs
.readdirSync(path.resolve(__dirname, 'src'), { withFileTypes: true }) .readdirSync(path.resolve(__dirname, 'src'), { withFileTypes: true })
.filter((dirent) => dirent.isDirectory()) .filter((dirent) => dirent.isDirectory())
.map((dirent) => dirent.name.replace(/s$/, '')); .map((dirent) => dirent.name.replace(/s$/, ''))
// precomputed scope // precomputed scope
const scopeComplete = execSync('git status --porcelain || true') const scopeComplete = execSync('git status --porcelain || true')
@ -15,7 +15,7 @@ const scopeComplete = execSync('git status --porcelain || true')
.find((r) => ~r.indexOf('M src')) .find((r) => ~r.indexOf('M src'))
?.replace(/(\/)/g, '%%') ?.replace(/(\/)/g, '%%')
?.match(/src%%((\w|-)*)/)?.[1] ?.match(/src%%((\w|-)*)/)?.[1]
?.replace(/s$/, ''); ?.replace(/s$/, '')
/** @type {import('cz-git').UserConfig} */ /** @type {import('cz-git').UserConfig} */
module.exports = { module.exports = {
@ -104,4 +104,4 @@ module.exports = {
// emptyScopesAlias: 'empty: 不填写', // emptyScopesAlias: 'empty: 不填写',
// customScopesAlias: 'custom: 自定义', // customScopesAlias: 'custom: 自定义',
}, },
}; }

View File

@ -1,5 +1,5 @@
<!DOCTYPE html> <!DOCTYPE html>
<html lang="en" id="htmlRoot"> <html lang="zh-cn" id="htmlRoot">
<head> <head>
<meta charset="UTF-8" /> <meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1" /> <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1" />
@ -13,14 +13,14 @@
</head> </head>
<body> <body>
<script> <script>
(() => { ;(() => {
var htmlRoot = document.getElementById('htmlRoot'); var htmlRoot = document.getElementById('htmlRoot')
var theme = window.localStorage.getItem('__APP__DARK__MODE__'); var theme = window.localStorage.getItem('__APP__DARK__MODE__')
if (htmlRoot && theme) { if (htmlRoot && theme) {
htmlRoot.setAttribute('data-theme', theme); htmlRoot.setAttribute('data-theme', theme)
theme = htmlRoot = null; theme = htmlRoot = null
} }
})(); })()
</script> </script>
<div id="app"> <div id="app">
<style> <style>

View File

@ -1,6 +1,6 @@
import { resultSuccess, resultError, getRequestToken, requestParams } from '../_util'; import { resultSuccess, resultError, getRequestToken, requestParams } from '../_util'
import { MockMethod } from 'vite-plugin-mock'; import { MockMethod } from 'vite-plugin-mock'
import { createFakeUserList } from './user'; import { createFakeUserList } from './user'
// single // single
const dashboardRoute = { const dashboardRoute = {
@ -39,7 +39,7 @@ const dashboardRoute = {
}, },
}, },
], ],
}; }
const backRoute = { const backRoute = {
path: 'back', path: 'back',
@ -66,7 +66,7 @@ const backRoute = {
}, },
}, },
], ],
}; }
const authRoute = { const authRoute = {
path: '/permission', path: '/permission',
@ -78,7 +78,7 @@ const authRoute = {
title: 'routes.demo.permission.permission', title: 'routes.demo.permission.permission',
}, },
children: [backRoute], children: [backRoute],
}; }
const levelRoute = { const levelRoute = {
path: '/level', path: '/level',
@ -134,7 +134,7 @@ const levelRoute = {
}, },
}, },
], ],
}; }
const sysRoute = { const sysRoute = {
path: '/system', path: '/system',
@ -176,16 +176,6 @@ const sysRoute = {
}, },
component: '/demo/system/role/index', component: '/demo/system/role/index',
}, },
{
path: 'menu',
name: 'MenuManagement',
meta: {
title: 'routes.demo.system.menu',
ignoreKeepAlive: true,
},
component: '/demo/system/menu/index',
},
{ {
path: 'dept', path: 'dept',
name: 'DeptManagement', name: 'DeptManagement',
@ -205,7 +195,7 @@ const sysRoute = {
component: '/demo/system/password/index', component: '/demo/system/password/index',
}, },
], ],
}; }
const linkRoute = { const linkRoute = {
path: '/link', path: '/link',
@ -233,7 +223,7 @@ const linkRoute = {
}, },
}, },
], ],
}; }
export default [ export default [
{ {
@ -241,30 +231,30 @@ export default [
timeout: 1000, timeout: 1000,
method: 'get', method: 'get',
response: (request: requestParams) => { response: (request: requestParams) => {
const token = getRequestToken(request); const token = getRequestToken(request)
if (!token) { if (!token) {
return resultError('Invalid token!'); return resultError('Invalid token!')
} }
const checkUser = createFakeUserList().find((item) => item.token === token); const checkUser = createFakeUserList().find((item) => item.token === token)
if (!checkUser) { if (!checkUser) {
return resultError('Invalid user token!'); return resultError('Invalid user token!')
} }
const id = checkUser.userId; const id = checkUser.userId
let menu: Object[]; let menu: Object[]
switch (id) { switch (id) {
case '1': case '1':
dashboardRoute.redirect = dashboardRoute.path + '/' + dashboardRoute.children[0].path; dashboardRoute.redirect = dashboardRoute.path + '/' + dashboardRoute.children[0].path
menu = [dashboardRoute, authRoute, levelRoute, sysRoute, linkRoute]; menu = [dashboardRoute, authRoute, levelRoute, sysRoute, linkRoute]
break; break
case '2': case '2':
dashboardRoute.redirect = dashboardRoute.path + '/' + dashboardRoute.children[1].path; dashboardRoute.redirect = dashboardRoute.path + '/' + dashboardRoute.children[1].path
menu = [dashboardRoute, authRoute, levelRoute, linkRoute]; menu = [dashboardRoute, authRoute, levelRoute, linkRoute]
break; break
default: default:
menu = []; menu = []
} }
return resultSuccess(menu); return resultSuccess(menu)
}, },
}, },
] as MockMethod[]; ] as MockMethod[]

View File

@ -56,8 +56,6 @@
"nprogress": "^0.2.0", "nprogress": "^0.2.0",
"path-to-regexp": "^6.2.0", "path-to-regexp": "^6.2.0",
"pinia": "2.0.12", "pinia": "2.0.12",
"print-js": "^1.6.0",
"qrcode": "^1.5.0",
"qs": "^6.10.3", "qs": "^6.10.3",
"resize-observer-polyfill": "^1.5.1", "resize-observer-polyfill": "^1.5.1",
"showdown": "^2.1.0", "showdown": "^2.1.0",
@ -85,7 +83,6 @@
"@types/mockjs": "^1.0.6", "@types/mockjs": "^1.0.6",
"@types/node": "^17.0.25", "@types/node": "^17.0.25",
"@types/nprogress": "^0.2.0", "@types/nprogress": "^0.2.0",
"@types/qrcode": "^1.4.2",
"@types/qs": "^6.9.7", "@types/qs": "^6.9.7",
"@types/showdown": "^1.9.4", "@types/showdown": "^1.9.4",
"@types/sortablejs": "^1.10.7", "@types/sortablejs": "^1.10.7",
@ -99,8 +96,8 @@
"autoprefixer": "^10.4.4", "autoprefixer": "^10.4.4",
"conventional-changelog-cli": "^2.2.2", "conventional-changelog-cli": "^2.2.2",
"cross-env": "^7.0.3", "cross-env": "^7.0.3",
"cz-git": "^1.3.9", "cz-git": "^1.3.11",
"czg": "^1.3.9", "czg": "^1.3.11",
"dotenv": "^16.0.0", "dotenv": "^16.0.0",
"eslint": "^8.13.0", "eslint": "^8.13.0",
"eslint-config-prettier": "^8.5.0", "eslint-config-prettier": "^8.5.0",

File diff suppressed because it is too large Load Diff

View File

@ -1,10 +1,10 @@
module.exports = { module.exports = {
printWidth: 100, printWidth: 100,
semi: true, semi: false,
vueIndentScriptAndStyle: true, vueIndentScriptAndStyle: true,
singleQuote: true, singleQuote: true,
trailingComma: 'all', trailingComma: 'all',
proseWrap: 'never', proseWrap: 'never',
htmlWhitespaceSensitivity: 'strict', htmlWhitespaceSensitivity: 'strict',
endOfLine: 'auto', endOfLine: 'auto',
}; }

View File

@ -7,15 +7,15 @@
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import { ConfigProvider } from 'ant-design-vue'; import { ConfigProvider } from 'ant-design-vue'
import { AppProvider } from '/@/components/Application'; import { AppProvider } from '/@/components/Application'
import { useTitle } from '/@/hooks/web/useTitle'; import { useTitle } from '/@/hooks/web/useTitle'
import { useLocale } from '/@/locales/useLocale'; import { useLocale } from '/@/locales/useLocale'
import 'dayjs/locale/zh-cn'; import 'dayjs/locale/zh-cn'
// support Multi-language // support Multi-language
const { getAntdLocale } = useLocale(); const { getAntdLocale } = useLocale()
// Listening to page changes and dynamically changing site titles // Listening to page changes and dynamically changing site titles
useTitle(); useTitle()
</script> </script>

View File

@ -1,5 +1,5 @@
import { defHttp } from '/@/utils/http/axios'; import { defHttp } from '/@/utils/http/axios'
import { GetAccountInfoModel } from './model/accountModel'; import { GetAccountInfoModel } from './model/accountModel'
enum Api { enum Api {
ACCOUNT_INFO = '/account/getAccountInfo', ACCOUNT_INFO = '/account/getAccountInfo',
@ -9,8 +9,8 @@ enum Api {
// Get personal center-basic settings // Get personal center-basic settings
export const accountInfoApi = () => defHttp.get<GetAccountInfoModel>({ url: Api.ACCOUNT_INFO }); export const accountInfoApi = () => defHttp.get<GetAccountInfoModel>({ url: Api.ACCOUNT_INFO })
export const sessionTimeoutApi = () => defHttp.post<void>({ url: Api.SESSION_TIMEOUT }); export const sessionTimeoutApi = () => defHttp.post<void>({ url: Api.SESSION_TIMEOUT })
export const tokenExpiredApi = () => defHttp.post<void>({ url: Api.TOKEN_EXPIRED }); export const tokenExpiredApi = () => defHttp.post<void>({ url: Api.TOKEN_EXPIRED })

View File

@ -1,9 +1,9 @@
import { defHttp } from '/@/utils/http/axios'; import { defHttp } from '/@/utils/http/axios'
import { AreaModel, AreaParams } from '/@/api/demo/model/areaModel'; import { AreaModel, AreaParams } from '/@/api/demo/model/areaModel'
enum Api { enum Api {
AREA_RECORD = '/cascader/getAreaRecord', AREA_RECORD = '/cascader/getAreaRecord',
} }
export const areaRecord = (data: AreaParams) => export const areaRecord = (data: AreaParams) =>
defHttp.post<AreaModel>({ url: Api.AREA_RECORD, data }); defHttp.post<AreaModel>({ url: Api.AREA_RECORD, data })

View File

@ -1,4 +1,4 @@
import { defHttp } from '/@/utils/http/axios'; import { defHttp } from '/@/utils/http/axios'
enum Api { enum Api {
// The address does not exist // The address does not exist
@ -9,4 +9,4 @@ enum Api {
* @description: Trigger ajax error * @description: Trigger ajax error
*/ */
export const fireErrorApi = () => defHttp.get({ url: Api.Error }); export const fireErrorApi = () => defHttp.get({ url: Api.Error })

View File

@ -1,7 +1,7 @@
export interface GetAccountInfoModel { export interface GetAccountInfoModel {
email: string; email: string
name: string; name: string
introduction: string; introduction: string
phone: string; phone: string
address: string; address: string
} }

View File

@ -1,12 +1,12 @@
export interface AreaModel { export interface AreaModel {
id: string; id: string
code: string; code: string
parentCode: string; parentCode: string
name: string; name: string
levelType: number; levelType: number
[key: string]: string | number; [key: string]: string | number
} }
export interface AreaParams { export interface AreaParams {
parentCode: string; parentCode: string
} }

View File

@ -1,15 +1,15 @@
import { BasicFetchResult } from '/@/api/model/baseModel'; import { BasicFetchResult } from '/@/api/model/baseModel'
export interface DemoOptionsItem { export interface DemoOptionsItem {
label: string; label: string
value: string; value: string
} }
export interface selectParams { export interface selectParams {
id: number | string; id: number | string
} }
/** /**
* @description: Request list return value * @description: Request list return value
*/ */
export type DemoOptionsGetResultModel = BasicFetchResult<DemoOptionsItem>; export type DemoOptionsGetResultModel = BasicFetchResult<DemoOptionsItem>

View File

@ -1,74 +1,74 @@
import { BasicPageParams, BasicFetchResult } from '/@/api/model/baseModel'; import { BasicPageParams, BasicFetchResult } from '/@/api/model/baseModel'
export type AccountParams = BasicPageParams & { export type AccountParams = BasicPageParams & {
account?: string; account?: string
nickname?: string; nickname?: string
}; }
export type RoleParams = { export type RoleParams = {
roleName?: string; roleName?: string
status?: string; status?: string
}; }
export type RolePageParams = BasicPageParams & RoleParams; export type RolePageParams = BasicPageParams & RoleParams
export type DeptParams = { export type DeptParams = {
deptName?: string; deptName?: string
status?: string; status?: string
}; }
export type MenuParams = { export type MenuParams = {
menuName?: string; menuName?: string
status?: string; status?: string
}; }
export interface AccountListItem { export interface AccountListItem {
id: string; id: string
account: string; account: string
email: string; email: string
nickname: string; nickname: string
role: number; role: number
createTime: string; createTime: string
remark: string; remark: string
status: number; status: number
} }
export interface DeptListItem { export interface DeptListItem {
id: string; id: string
orderNo: string; orderNo: string
createTime: string; createTime: string
remark: string; remark: string
status: number; status: number
} }
export interface MenuListItem { export interface MenuListItem {
id: string; id: string
orderNo: string; orderNo: string
createTime: string; createTime: string
status: number; status: number
icon: string; icon: string
component: string; component: string
permission: string; permission: string
} }
export interface RoleListItem { export interface RoleListItem {
id: string; id: string
roleName: string; roleName: string
roleValue: string; roleValue: string
status: number; status: number
orderNo: string; orderNo: string
createTime: string; createTime: string
} }
/** /**
* @description: Request list return value * @description: Request list return value
*/ */
export type AccountListGetResultModel = BasicFetchResult<AccountListItem>; export type AccountListGetResultModel = BasicFetchResult<AccountListItem>
export type DeptListGetResultModel = BasicFetchResult<DeptListItem>; export type DeptListGetResultModel = BasicFetchResult<DeptListItem>
export type MenuListGetResultModel = BasicFetchResult<MenuListItem>; export type MenuListGetResultModel = BasicFetchResult<MenuListItem>
export type RolePageListGetResultModel = BasicFetchResult<RoleListItem>; export type RolePageListGetResultModel = BasicFetchResult<RoleListItem>
export type RoleListGetResultModel = RoleListItem[]; export type RoleListGetResultModel = RoleListItem[]

View File

@ -1,20 +1,20 @@
import { BasicPageParams, BasicFetchResult } from '/@/api/model/baseModel'; import { BasicPageParams, BasicFetchResult } from '/@/api/model/baseModel'
/** /**
* @description: Request list interface parameters * @description: Request list interface parameters
*/ */
export type DemoParams = BasicPageParams; export type DemoParams = BasicPageParams
export interface DemoListItem { export interface DemoListItem {
id: string; id: string
beginTime: string; beginTime: string
endTime: string; endTime: string
address: string; address: string
name: string; name: string
no: number; no: number
status: number; status: number
} }
/** /**
* @description: Request list return value * @description: Request list return value
*/ */
export type DemoListGetResultModel = BasicFetchResult<DemoListItem>; export type DemoListGetResultModel = BasicFetchResult<DemoListItem>

View File

@ -1,5 +1,5 @@
import { defHttp } from '/@/utils/http/axios'; import { defHttp } from '/@/utils/http/axios'
import { DemoOptionsItem, selectParams } from './model/optionsModel'; import { DemoOptionsItem, selectParams } from './model/optionsModel'
enum Api { enum Api {
OPTIONS_LIST = '/select/getDemoOptions', OPTIONS_LIST = '/select/getDemoOptions',
} }
@ -8,4 +8,4 @@ enum Api {
* @description: Get sample options value * @description: Get sample options value
*/ */
export const optionsListApi = (params?: selectParams) => export const optionsListApi = (params?: selectParams) =>
defHttp.get<DemoOptionsItem[]>({ url: Api.OPTIONS_LIST, params }); defHttp.get<DemoOptionsItem[]>({ url: Api.OPTIONS_LIST, params })

View File

@ -9,8 +9,8 @@ import {
AccountListGetResultModel, AccountListGetResultModel,
RolePageListGetResultModel, RolePageListGetResultModel,
RoleListGetResultModel, RoleListGetResultModel,
} from './model/systemModel'; } from './model/systemModel'
import { defHttp } from '/@/utils/http/axios'; import { defHttp } from '/@/utils/http/axios'
enum Api { enum Api {
AccountList = '/system/getAccountList', AccountList = '/system/getAccountList',
@ -23,22 +23,22 @@ enum Api {
} }
export const getAccountList = (params: AccountParams) => export const getAccountList = (params: AccountParams) =>
defHttp.get<AccountListGetResultModel>({ url: Api.AccountList, params }); defHttp.get<AccountListGetResultModel>({ url: Api.AccountList, params })
export const getDeptList = (params?: DeptListItem) => export const getDeptList = (params?: DeptListItem) =>
defHttp.get<DeptListGetResultModel>({ url: Api.DeptList, params }); defHttp.get<DeptListGetResultModel>({ url: Api.DeptList, params })
export const getMenuList = (params?: MenuParams) => export const getMenuList = (params?: MenuParams) =>
defHttp.get<MenuListGetResultModel>({ url: Api.MenuList, params }); defHttp.get<MenuListGetResultModel>({ url: Api.MenuList, params })
export const getRoleListByPage = (params?: RolePageParams) => export const getRoleListByPage = (params?: RolePageParams) =>
defHttp.get<RolePageListGetResultModel>({ url: Api.RolePageList, params }); defHttp.get<RolePageListGetResultModel>({ url: Api.RolePageList, params })
export const getAllRoleList = (params?: RoleParams) => export const getAllRoleList = (params?: RoleParams) =>
defHttp.get<RoleListGetResultModel>({ url: Api.GetAllRoleList, params }); defHttp.get<RoleListGetResultModel>({ url: Api.GetAllRoleList, params })
export const setRoleStatus = (id: number, status: string) => export const setRoleStatus = (id: number, status: string) =>
defHttp.post({ url: Api.setRoleStatus, params: { id, status } }); defHttp.post({ url: Api.setRoleStatus, params: { id, status } })
export const isAccountExist = (account: string) => export const isAccountExist = (account: string) =>
defHttp.post({ url: Api.IsAccountExist, params: { account } }, { errorMessageMode: 'none' }); defHttp.post({ url: Api.IsAccountExist, params: { account } }, { errorMessageMode: 'none' })

View File

@ -1,5 +1,5 @@
import { defHttp } from '/@/utils/http/axios'; import { defHttp } from '/@/utils/http/axios'
import { DemoParams, DemoListGetResultModel } from './model/tableModel'; import { DemoParams, DemoListGetResultModel } from './model/tableModel'
enum Api { enum Api {
DEMO_LIST = '/table/getDemoList', DEMO_LIST = '/table/getDemoList',
@ -17,4 +17,4 @@ export const demoListApi = (params: DemoParams) =>
// @ts-ignore // @ts-ignore
ignoreCancelToken: true, ignoreCancelToken: true,
}, },
}); })

View File

@ -1,4 +1,4 @@
import { defHttp } from '/@/utils/http/axios'; import { defHttp } from '/@/utils/http/axios'
enum Api { enum Api {
TREE_OPTIONS_LIST = '/tree/getDemoOptions', TREE_OPTIONS_LIST = '/tree/getDemoOptions',
@ -8,4 +8,4 @@ enum Api {
* @description: Get sample options value * @description: Get sample options value
*/ */
export const treeOptionsListApi = (params?: Recordable) => export const treeOptionsListApi = (params?: Recordable) =>
defHttp.get<Recordable[]>({ url: Api.TREE_OPTIONS_LIST, params }); defHttp.get<Recordable[]>({ url: Api.TREE_OPTIONS_LIST, params })

View File

@ -1,9 +1,9 @@
export interface BasicPageParams { export interface BasicPageParams {
page: number; page: number
pageSize: number; pageSize: number
} }
export interface BasicFetchResult<T> { export interface BasicFetchResult<T> {
items: T[]; items: T[]
total: number; total: number
} }

View File

@ -1,5 +1,5 @@
import { defHttp } from '/@/utils/http/axios'; import { defHttp } from '/@/utils/http/axios'
import { getMenuListResultModel } from './model/menuModel'; import { getMenuListResultModel } from './model/menuModel'
enum Api { enum Api {
GetMenuList = '/getMenuList', GetMenuList = '/getMenuList',
@ -10,5 +10,5 @@ enum Api {
*/ */
export const getMenuList = () => { export const getMenuList = () => {
return defHttp.get<getMenuListResultModel>({ url: Api.GetMenuList }); return defHttp.get<getMenuListResultModel>({ url: Api.GetMenuList })
}; }

View File

@ -1,16 +1,16 @@
import type { RouteMeta } from 'vue-router'; import type { RouteMeta } from 'vue-router'
export interface RouteItem { export interface RouteItem {
path: string; path: string
component: any; component: any
meta: RouteMeta; meta: RouteMeta
name?: string; name?: string
alias?: string | string[]; alias?: string | string[]
redirect?: string; redirect?: string
caseSensitive?: boolean; caseSensitive?: boolean
children?: RouteItem[]; children?: RouteItem[]
} }
/** /**
* @description: Get menu return value * @description: Get menu return value
*/ */
export type getMenuListResultModel = RouteItem[]; export type getMenuListResultModel = RouteItem[]

View File

@ -1,5 +1,5 @@
export interface UploadApiResult { export interface UploadApiResult {
message: string; message: string
code: number; code: number
url: string; url: string
} }

View File

@ -2,37 +2,37 @@
* @description: Login interface parameters * @description: Login interface parameters
*/ */
export interface LoginParams { export interface LoginParams {
username: string; username: string
password: string; password: string
} }
export interface RoleInfo { export interface RoleInfo {
roleName: string; roleName: string
value: string; value: string
} }
/** /**
* @description: Login interface return value * @description: Login interface return value
*/ */
export interface LoginResultModel { export interface LoginResultModel {
userId: string | number; userId: string | number
token: string; token: string
role: RoleInfo; role: RoleInfo
} }
/** /**
* @description: Get user information return value * @description: Get user information return value
*/ */
export interface GetUserInfoModel { export interface GetUserInfoModel {
roles: RoleInfo[]; roles: RoleInfo[]
// 用户id // 用户id
userId: string | number; userId: string | number
// 用户名 // 用户名
username: string; username: string
// 真实名字 // 真实名字
realName: string; realName: string
// 头像 // 头像
avatar: string; avatar: string
// 介绍 // 介绍
desc?: string; desc?: string
} }

View File

@ -1,22 +0,0 @@
import { UploadApiResult } from './model/uploadModel';
import { defHttp } from '/@/utils/http/axios';
import { UploadFileParams } from '/#/axios';
import { useGlobSetting } from '/@/hooks/setting';
const { uploadUrl = '' } = useGlobSetting();
/**
* @description: Upload interface
*/
export function uploadApi(
params: UploadFileParams,
onUploadProgress: (progressEvent: ProgressEvent) => void,
) {
return defHttp.uploadFile<UploadApiResult>(
{
url: uploadUrl,
onUploadProgress,
},
params,
);
}

View File

@ -1,7 +1,7 @@
import { defHttp } from '/@/utils/http/axios'; import { defHttp } from '/@/utils/http/axios'
import { LoginParams, LoginResultModel, GetUserInfoModel } from './model/userModel'; import { LoginParams, LoginResultModel, GetUserInfoModel } from './model/userModel'
import { ErrorMessageMode } from '/#/axios'; import { ErrorMessageMode } from '/#/axios'
enum Api { enum Api {
Login = '/login', Login = '/login',
@ -23,22 +23,22 @@ export function loginApi(params: LoginParams, mode: ErrorMessageMode = 'modal')
{ {
errorMessageMode: mode, errorMessageMode: mode,
}, },
); )
} }
/** /**
* @description: getUserInfo * @description: getUserInfo
*/ */
export function getUserInfo() { export function getUserInfo() {
return defHttp.get<GetUserInfoModel>({ url: Api.GetUserInfo }, { errorMessageMode: 'none' }); return defHttp.get<GetUserInfoModel>({ url: Api.GetUserInfo }, { errorMessageMode: 'none' })
} }
export function getPermCode() { export function getPermCode() {
return defHttp.get<string[]>({ url: Api.GetPermCode }); return defHttp.get<string[]>({ url: Api.GetPermCode })
} }
export function doLogout() { export function doLogout() {
return defHttp.get({ url: Api.Logout }); return defHttp.get({ url: Api.Logout })
} }
export function testRetry() { export function testRetry() {
@ -51,5 +51,5 @@ export function testRetry() {
waitTime: 1000, waitTime: 1000,
}, },
}, },
); )
} }

View File

@ -1,15 +1,15 @@
import { withInstall } from '/@/utils'; import { withInstall } from '/@/utils'
import appLogo from './src/AppLogo.vue'; import appLogo from './src/AppLogo.vue'
import appProvider from './src/AppProvider.vue'; import appProvider from './src/AppProvider.vue'
import appSearch from './src/search/AppSearch.vue'; import appSearch from './src/search/AppSearch.vue'
import appLocalePicker from './src/AppLocalePicker.vue'; import appLocalePicker from './src/AppLocalePicker.vue'
import appDarkModeToggle from './src/AppDarkModeToggle.vue'; import appDarkModeToggle from './src/AppDarkModeToggle.vue'
export { useAppProviderContext } from './src/useAppContext'; export { useAppProviderContext } from './src/useAppContext'
export const AppLogo = withInstall(appLogo); export const AppLogo = withInstall(appLogo)
export const AppProvider = withInstall(appProvider); export const AppProvider = withInstall(appProvider)
export const AppSearch = withInstall(appSearch); export const AppSearch = withInstall(appSearch)
export const AppLocalePicker = withInstall(appLocalePicker); export const AppLocalePicker = withInstall(appLocalePicker)
export const AppDarkModeToggle = withInstall(appDarkModeToggle); export const AppDarkModeToggle = withInstall(appDarkModeToggle)

View File

@ -6,32 +6,32 @@
</div> </div>
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import { computed, unref } from 'vue'; import { computed, unref } from 'vue'
import { SvgIcon } from '/@/components/Icon'; import { SvgIcon } from '/@/components/Icon'
import { useDesign } from '/@/hooks/web/useDesign'; import { useDesign } from '/@/hooks/web/useDesign'
import { useRootSetting } from '/@/hooks/setting/useRootSetting'; import { useRootSetting } from '/@/hooks/setting/useRootSetting'
import { updateHeaderBgColor, updateSidebarBgColor } from '/@/logics/theme/updateBackground'; import { updateHeaderBgColor, updateSidebarBgColor } from '/@/logics/theme/updateBackground'
import { updateDarkTheme } from '/@/logics/theme/dark'; import { updateDarkTheme } from '/@/logics/theme/dark'
import { ThemeEnum } from '/@/enums/appEnum'; import { ThemeEnum } from '/@/enums/appEnum'
const { prefixCls } = useDesign('dark-switch'); const { prefixCls } = useDesign('dark-switch')
const { getDarkMode, setDarkMode, getShowDarkModeToggle } = useRootSetting(); const { getDarkMode, setDarkMode, getShowDarkModeToggle } = useRootSetting()
const isDark = computed(() => getDarkMode.value === ThemeEnum.DARK); const isDark = computed(() => getDarkMode.value === ThemeEnum.DARK)
const getClass = computed(() => [ const getClass = computed(() => [
prefixCls, prefixCls,
{ {
[`${prefixCls}--dark`]: unref(isDark), [`${prefixCls}--dark`]: unref(isDark),
}, },
]); ])
function toggleDarkMode() { function toggleDarkMode() {
const darkMode = getDarkMode.value === ThemeEnum.DARK ? ThemeEnum.LIGHT : ThemeEnum.DARK; const darkMode = getDarkMode.value === ThemeEnum.DARK ? ThemeEnum.LIGHT : ThemeEnum.DARK
setDarkMode(darkMode); setDarkMode(darkMode)
updateDarkTheme(darkMode); updateDarkTheme(darkMode)
updateHeaderBgColor(); updateHeaderBgColor()
updateSidebarBgColor(); updateSidebarBgColor()
} }
</script> </script>
<style lang="less" scoped> <style lang="less" scoped>

View File

@ -18,13 +18,13 @@
</Dropdown> </Dropdown>
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import type { LocaleType } from '/#/config'; import type { LocaleType } from '/#/config'
import type { DropMenu } from '/@/components/Dropdown'; import type { DropMenu } from '/@/components/Dropdown'
import { ref, watchEffect, unref, computed } from 'vue'; import { ref, watchEffect, unref, computed } from 'vue'
import { Dropdown } from '/@/components/Dropdown'; import { Dropdown } from '/@/components/Dropdown'
import { Icon } from '/@/components/Icon'; import { Icon } from '/@/components/Icon'
import { useLocale } from '/@/locales/useLocale'; import { useLocale } from '/@/locales/useLocale'
import { localeList } from '/@/settings/localeSetting'; import { localeList } from '/@/settings/localeSetting'
const props = defineProps({ const props = defineProps({
/** /**
@ -35,35 +35,35 @@
* Whether to refresh the interface when changing * Whether to refresh the interface when changing
*/ */
reload: { type: Boolean }, reload: { type: Boolean },
}); })
const selectedKeys = ref<string[]>([]); const selectedKeys = ref<string[]>([])
const { changeLocale, getLocale } = useLocale(); const { changeLocale, getLocale } = useLocale()
const getLocaleText = computed(() => { const getLocaleText = computed(() => {
const key = selectedKeys.value[0]; const key = selectedKeys.value[0]
if (!key) { if (!key) {
return ''; return ''
} }
return localeList.find((item) => item.event === key)?.text; return localeList.find((item) => item.event === key)?.text
}); })
watchEffect(() => { watchEffect(() => {
selectedKeys.value = [unref(getLocale)]; selectedKeys.value = [unref(getLocale)]
}); })
async function toggleLocale(lang: LocaleType | string) { async function toggleLocale(lang: LocaleType | string) {
await changeLocale(lang as LocaleType); await changeLocale(lang as LocaleType)
selectedKeys.value = [lang as string]; selectedKeys.value = [lang as string]
props.reload && location.reload(); props.reload && location.reload()
} }
function handleMenuEvent(menu: DropMenu) { function handleMenuEvent(menu: DropMenu) {
if (unref(getLocale) === menu.event) { if (unref(getLocale) === menu.event) {
return; return
} }
toggleLocale(menu.event as string); toggleLocale(menu.event as string)
} }
</script> </script>

View File

@ -11,13 +11,13 @@
</div> </div>
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import { computed, unref } from 'vue'; import { computed, unref } from 'vue'
import { useGlobSetting } from '/@/hooks/setting'; import { useGlobSetting } from '/@/hooks/setting'
import { useGo } from '/@/hooks/web/usePage'; import { useGo } from '/@/hooks/web/usePage'
import { useMenuSetting } from '/@/hooks/setting/useMenuSetting'; import { useMenuSetting } from '/@/hooks/setting/useMenuSetting'
import { useDesign } from '/@/hooks/web/useDesign'; import { useDesign } from '/@/hooks/web/useDesign'
import { PageEnum } from '/@/enums/pageEnum'; import { PageEnum } from '/@/enums/pageEnum'
import { useUserStore } from '/@/store/modules/user'; import { useUserStore } from '/@/store/modules/user'
const props = defineProps({ const props = defineProps({
/** /**
@ -32,29 +32,29 @@
* The title is also displayed when the menu is collapsed * The title is also displayed when the menu is collapsed
*/ */
alwaysShowTitle: { type: Boolean }, alwaysShowTitle: { type: Boolean },
}); })
const { prefixCls } = useDesign('app-logo'); const { prefixCls } = useDesign('app-logo')
const { getCollapsedShowTitle } = useMenuSetting(); const { getCollapsedShowTitle } = useMenuSetting()
const userStore = useUserStore(); const userStore = useUserStore()
const { title } = useGlobSetting(); const { title } = useGlobSetting()
const go = useGo(); const go = useGo()
const getAppLogoClass = computed(() => [ const getAppLogoClass = computed(() => [
prefixCls, prefixCls,
props.theme, props.theme,
{ 'collapsed-show-title': unref(getCollapsedShowTitle) }, { 'collapsed-show-title': unref(getCollapsedShowTitle) },
]); ])
const getTitleClass = computed(() => [ const getTitleClass = computed(() => [
`${prefixCls}__title`, `${prefixCls}__title`,
{ {
'xs:opacity-0': !props.alwaysShowTitle, 'xs:opacity-0': !props.alwaysShowTitle,
}, },
]); ])
function goHome() { function goHome() {
go(userStore.getUserInfo.homePath || PageEnum.BASE_HOME); go(userStore.getUserInfo.homePath || PageEnum.BASE_HOME)
} }
</script> </script>
<style lang="less" scoped> <style lang="less" scoped>

View File

@ -1,41 +1,41 @@
<script lang="ts"> <script lang="ts">
import { defineComponent, toRefs, ref, unref } from 'vue'; import { defineComponent, toRefs, ref, unref } from 'vue'
import { createAppProviderContext } from './useAppContext'; import { createAppProviderContext } from './useAppContext'
import { createBreakpointListen } from '/@/hooks/event/useBreakpoint'; import { createBreakpointListen } from '/@/hooks/event/useBreakpoint'
import { prefixCls } from '/@/settings/designSetting'; import { prefixCls } from '/@/settings/designSetting'
import { useAppStore } from '/@/store/modules/app'; import { useAppStore } from '/@/store/modules/app'
import { MenuModeEnum, MenuTypeEnum } from '/@/enums/menuEnum'; import { MenuModeEnum, MenuTypeEnum } from '/@/enums/menuEnum'
const props = { const props = {
/** /**
* class style prefix * class style prefix
*/ */
prefixCls: { type: String, default: prefixCls }, prefixCls: { type: String, default: prefixCls },
}; }
export default defineComponent({ export default defineComponent({
name: 'AppProvider', name: 'AppProvider',
inheritAttrs: false, inheritAttrs: false,
props, props,
setup(props, { slots }) { setup(props, { slots }) {
const isMobile = ref(false); const isMobile = ref(false)
const isSetState = ref(false); const isSetState = ref(false)
const appStore = useAppStore(); const appStore = useAppStore()
// Monitor screen breakpoint information changes // Monitor screen breakpoint information changes
createBreakpointListen(({ screenMap, sizeEnum, width }) => { createBreakpointListen(({ screenMap, sizeEnum, width }) => {
const lgWidth = screenMap.get(sizeEnum.LG); const lgWidth = screenMap.get(sizeEnum.LG)
if (lgWidth) { if (lgWidth) {
isMobile.value = width.value - 1 < lgWidth; isMobile.value = width.value - 1 < lgWidth
} }
handleRestoreState(); handleRestoreState()
}); })
const { prefixCls } = toRefs(props); const { prefixCls } = toRefs(props)
// Inject variables into the global // Inject variables into the global
createAppProviderContext({ prefixCls, isMobile }); createAppProviderContext({ prefixCls, isMobile })
/** /**
* Used to maintain the state before the window changes * Used to maintain the state before the window changes
@ -43,7 +43,7 @@
function handleRestoreState() { function handleRestoreState() {
if (unref(isMobile)) { if (unref(isMobile)) {
if (!unref(isSetState)) { if (!unref(isSetState)) {
isSetState.value = true; isSetState.value = true
const { const {
menuSetting: { menuSetting: {
type: menuType, type: menuType,
@ -51,20 +51,20 @@
collapsed: menuCollapsed, collapsed: menuCollapsed,
split: menuSplit, split: menuSplit,
}, },
} = appStore.getProjectConfig; } = appStore.getProjectConfig
appStore.setProjectConfig({ appStore.setProjectConfig({
menuSetting: { menuSetting: {
type: MenuTypeEnum.SIDEBAR, type: MenuTypeEnum.SIDEBAR,
mode: MenuModeEnum.INLINE, mode: MenuModeEnum.INLINE,
split: false, split: false,
}, },
}); })
appStore.setBeforeMiniInfo({ menuMode, menuCollapsed, menuType, menuSplit }); appStore.setBeforeMiniInfo({ menuMode, menuCollapsed, menuType, menuSplit })
} }
} else { } else {
if (unref(isSetState)) { if (unref(isSetState)) {
isSetState.value = false; isSetState.value = false
const { menuMode, menuCollapsed, menuType, menuSplit } = appStore.getBeforeMiniInfo; const { menuMode, menuCollapsed, menuType, menuSplit } = appStore.getBeforeMiniInfo
appStore.setProjectConfig({ appStore.setProjectConfig({
menuSetting: { menuSetting: {
type: menuType, type: menuType,
@ -72,11 +72,11 @@
collapsed: menuCollapsed, collapsed: menuCollapsed,
split: menuSplit, split: menuSplit,
}, },
}); })
} }
} }
} }
return () => slots.default?.(); return () => slots.default?.()
}, },
}); })
</script> </script>

View File

@ -1,18 +1,18 @@
<script lang="tsx"> <script lang="tsx">
import { defineComponent, ref, unref } from 'vue'; import { defineComponent, ref, unref } from 'vue'
import { Tooltip } from 'ant-design-vue'; import { Tooltip } from 'ant-design-vue'
import { SearchOutlined } from '@ant-design/icons-vue'; import { SearchOutlined } from '@ant-design/icons-vue'
import AppSearchModal from './AppSearchModal.vue'; import AppSearchModal from './AppSearchModal.vue'
import { useI18n } from '/@/hooks/web/useI18n'; import { useI18n } from '/@/hooks/web/useI18n'
export default defineComponent({ export default defineComponent({
name: 'AppSearch', name: 'AppSearch',
setup() { setup() {
const showModal = ref(false); const showModal = ref(false)
const { t } = useI18n(); const { t } = useI18n()
function changeModal(show: boolean) { function changeModal(show: boolean) {
showModal.value = show; showModal.value = show
} }
return () => { return () => {
@ -26,8 +26,8 @@
</Tooltip> </Tooltip>
<AppSearchModal onClose={changeModal.bind(null, false)} visible={unref(showModal)} /> <AppSearchModal onClose={changeModal.bind(null, false)} visible={unref(showModal)} />
</div> </div>
); )
}; }
}, },
}); })
</script> </script>

View File

@ -11,11 +11,11 @@
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import AppSearchKeyItem from './AppSearchKeyItem.vue'; import AppSearchKeyItem from './AppSearchKeyItem.vue'
import { useDesign } from '/@/hooks/web/useDesign'; import { useDesign } from '/@/hooks/web/useDesign'
import { useI18n } from '/@/hooks/web/useI18n'; import { useI18n } from '/@/hooks/web/useI18n'
const { prefixCls } = useDesign('app-search-footer'); const { prefixCls } = useDesign('app-search-footer')
const { t } = useI18n(); const { t } = useI18n()
</script> </script>
<style lang="less" scoped> <style lang="less" scoped>
@prefix-cls: ~'@{namespace}-app-search-footer'; @prefix-cls: ~'@{namespace}-app-search-footer';

View File

@ -4,8 +4,8 @@
</span> </span>
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import { Icon } from '/@/components/Icon'; import { Icon } from '/@/components/Icon'
defineProps({ defineProps({
icon: String, icon: String,
}); })
</script> </script>

View File

@ -58,36 +58,36 @@
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import { computed, unref, ref, watch, nextTick } from 'vue'; import { computed, unref, ref, watch, nextTick } from 'vue'
import { SearchOutlined } from '@ant-design/icons-vue'; import { SearchOutlined } from '@ant-design/icons-vue'
import AppSearchFooter from './AppSearchFooter.vue'; import AppSearchFooter from './AppSearchFooter.vue'
import Icon from '/@/components/Icon'; import Icon from '/@/components/Icon'
// @ts-ignore // @ts-ignore
import vClickOutside from '/@/directives/clickOutside'; import vClickOutside from '/@/directives/clickOutside'
import { useDesign } from '/@/hooks/web/useDesign'; import { useDesign } from '/@/hooks/web/useDesign'
import { useRefs } from '/@/hooks/core/useRefs'; import { useRefs } from '/@/hooks/core/useRefs'
import { useMenuSearch } from './useMenuSearch'; import { useMenuSearch } from './useMenuSearch'
import { useI18n } from '/@/hooks/web/useI18n'; import { useI18n } from '/@/hooks/web/useI18n'
import { useAppInject } from '/@/hooks/web/useAppInject'; import { useAppInject } from '/@/hooks/web/useAppInject'
const props = defineProps({ const props = defineProps({
visible: { type: Boolean }, visible: { type: Boolean },
}); })
const emit = defineEmits(['close']); const emit = defineEmits(['close'])
const scrollWrap = ref(null); const scrollWrap = ref(null)
const inputRef = ref<Nullable<HTMLElement>>(null); const inputRef = ref<Nullable<HTMLElement>>(null)
const { t } = useI18n(); const { t } = useI18n()
const { prefixCls } = useDesign('app-search-modal'); const { prefixCls } = useDesign('app-search-modal')
const [refs, setRefs] = useRefs(); const [refs, setRefs] = useRefs()
const { getIsMobile } = useAppInject(); const { getIsMobile } = useAppInject()
const { handleSearch, searchResult, keyword, activeIndex, handleEnter, handleMouseenter } = const { handleSearch, searchResult, keyword, activeIndex, handleEnter, handleMouseenter } =
useMenuSearch(refs, scrollWrap, emit); useMenuSearch(refs, scrollWrap, emit)
const getIsNotData = computed(() => !keyword || unref(searchResult).length === 0); const getIsNotData = computed(() => !keyword || unref(searchResult).length === 0)
const getClass = computed(() => { const getClass = computed(() => {
return [ return [
@ -95,22 +95,22 @@
{ {
[`${prefixCls}--mobile`]: unref(getIsMobile), [`${prefixCls}--mobile`]: unref(getIsMobile),
}, },
]; ]
}); })
watch( watch(
() => props.visible, () => props.visible,
(visible: boolean) => { (visible: boolean) => {
visible && visible &&
nextTick(() => { nextTick(() => {
unref(inputRef)?.focus(); unref(inputRef)?.focus()
}); })
}, },
); )
function handleClose() { function handleClose() {
searchResult.value = []; searchResult.value = []
emit('close'); emit('close')
} }
</script> </script>
<style lang="less" scoped> <style lang="less" scoped>

View File

@ -1,166 +1,166 @@
import type { Menu } from '/@/router/types'; import type { Menu } from '/@/router/types'
import { ref, onBeforeMount, unref, Ref, nextTick } from 'vue'; import { ref, onBeforeMount, unref, Ref, nextTick } from 'vue'
import { getMenus } from '/@/router/menus'; import { getMenus } from '/@/router/menus'
import { cloneDeep } from 'lodash-es'; import { cloneDeep } from 'lodash-es'
import { filter, forEach } from '/@/utils/helper/treeHelper'; import { filter, forEach } from '/@/utils/helper/treeHelper'
import { useGo } from '/@/hooks/web/usePage'; import { useGo } from '/@/hooks/web/usePage'
import { useScrollTo } from '/@/hooks/event/useScrollTo'; import { useScrollTo } from '/@/hooks/event/useScrollTo'
import { onKeyStroke, useDebounceFn } from '@vueuse/core'; import { onKeyStroke, useDebounceFn } from '@vueuse/core'
import { useI18n } from '/@/hooks/web/useI18n'; import { useI18n } from '/@/hooks/web/useI18n'
export interface SearchResult { export interface SearchResult {
name: string; name: string
path: string; path: string
icon?: string; icon?: string
} }
// Translate special characters // Translate special characters
function transform(c: string) { function transform(c: string) {
const code: string[] = ['$', '(', ')', '*', '+', '.', '[', ']', '?', '\\', '^', '{', '}', '|']; const code: string[] = ['$', '(', ')', '*', '+', '.', '[', ']', '?', '\\', '^', '{', '}', '|']
return code.includes(c) ? `\\${c}` : c; return code.includes(c) ? `\\${c}` : c
} }
function createSearchReg(key: string) { function createSearchReg(key: string) {
const keys = [...key].map((item) => transform(item)); const keys = [...key].map((item) => transform(item))
const str = ['', ...keys, ''].join('.*'); const str = ['', ...keys, ''].join('.*')
return new RegExp(str); return new RegExp(str)
} }
export function useMenuSearch(refs: Ref<HTMLElement[]>, scrollWrap: Ref<ElRef>, emit: EmitType) { export function useMenuSearch(refs: Ref<HTMLElement[]>, scrollWrap: Ref<ElRef>, emit: EmitType) {
const searchResult = ref<SearchResult[]>([]); const searchResult = ref<SearchResult[]>([])
const keyword = ref(''); const keyword = ref('')
const activeIndex = ref(-1); const activeIndex = ref(-1)
let menuList: Menu[] = []; let menuList: Menu[] = []
const { t } = useI18n(); const { t } = useI18n()
const go = useGo(); const go = useGo()
const handleSearch = useDebounceFn(search, 200); const handleSearch = useDebounceFn(search, 200)
onBeforeMount(async () => { onBeforeMount(async () => {
const list = await getMenus(); const list = await getMenus()
menuList = cloneDeep(list); menuList = cloneDeep(list)
forEach(menuList, (item) => { forEach(menuList, (item) => {
item.name = t(item.name); item.name = t(item.name)
}); })
}); })
function search(e: ChangeEvent) { function search(e: ChangeEvent) {
e?.stopPropagation(); e?.stopPropagation()
const key = e.target.value; const key = e.target.value
keyword.value = key.trim(); keyword.value = key.trim()
if (!key) { if (!key) {
searchResult.value = []; searchResult.value = []
return; return
} }
const reg = createSearchReg(unref(keyword)); const reg = createSearchReg(unref(keyword))
const filterMenu = filter(menuList, (item) => { const filterMenu = filter(menuList, (item) => {
return reg.test(item.name) && !item.hideMenu; return reg.test(item.name) && !item.hideMenu
}); })
searchResult.value = handlerSearchResult(filterMenu, reg); searchResult.value = handlerSearchResult(filterMenu, reg)
activeIndex.value = 0; activeIndex.value = 0
} }
function handlerSearchResult(filterMenu: Menu[], reg: RegExp, parent?: Menu) { function handlerSearchResult(filterMenu: Menu[], reg: RegExp, parent?: Menu) {
const ret: SearchResult[] = []; const ret: SearchResult[] = []
filterMenu.forEach((item) => { filterMenu.forEach((item) => {
const { name, path, icon, children, hideMenu, meta } = item; const { name, path, icon, children, hideMenu, meta } = item
if (!hideMenu && reg.test(name) && (!children?.length || meta?.hideChildrenInMenu)) { if (!hideMenu && reg.test(name) && (!children?.length || meta?.hideChildrenInMenu)) {
ret.push({ ret.push({
name: parent?.name ? `${parent.name} > ${name}` : name, name: parent?.name ? `${parent.name} > ${name}` : name,
path, path,
icon, icon,
}); })
} }
if (!meta?.hideChildrenInMenu && Array.isArray(children) && children.length) { if (!meta?.hideChildrenInMenu && Array.isArray(children) && children.length) {
ret.push(...handlerSearchResult(children, reg, item)); ret.push(...handlerSearchResult(children, reg, item))
} }
}); })
return ret; return ret
} }
// Activate when the mouse moves to a certain line // Activate when the mouse moves to a certain line
function handleMouseenter(e: any) { function handleMouseenter(e: any) {
const index = e.target.dataset.index; const index = e.target.dataset.index
activeIndex.value = Number(index); activeIndex.value = Number(index)
} }
// Arrow key up // Arrow key up
function handleUp() { function handleUp() {
if (!searchResult.value.length) return; if (!searchResult.value.length) return
activeIndex.value--; activeIndex.value--
if (activeIndex.value < 0) { if (activeIndex.value < 0) {
activeIndex.value = searchResult.value.length - 1; activeIndex.value = searchResult.value.length - 1
} }
handleScroll(); handleScroll()
} }
// Arrow key down // Arrow key down
function handleDown() { function handleDown() {
if (!searchResult.value.length) return; if (!searchResult.value.length) return
activeIndex.value++; activeIndex.value++
if (activeIndex.value > searchResult.value.length - 1) { if (activeIndex.value > searchResult.value.length - 1) {
activeIndex.value = 0; activeIndex.value = 0
} }
handleScroll(); handleScroll()
} }
// When the keyboard up and down keys move to an invisible place // When the keyboard up and down keys move to an invisible place
// the scroll bar needs to scroll automatically // the scroll bar needs to scroll automatically
function handleScroll() { function handleScroll() {
const refList = unref(refs); const refList = unref(refs)
if (!refList || !Array.isArray(refList) || refList.length === 0 || !unref(scrollWrap)) { if (!refList || !Array.isArray(refList) || refList.length === 0 || !unref(scrollWrap)) {
return; return
} }
const index = unref(activeIndex); const index = unref(activeIndex)
const currentRef = refList[index]; const currentRef = refList[index]
if (!currentRef) { if (!currentRef) {
return; return
} }
const wrapEl = unref(scrollWrap); const wrapEl = unref(scrollWrap)
if (!wrapEl) { if (!wrapEl) {
return; return
} }
const scrollHeight = currentRef.offsetTop + currentRef.offsetHeight; const scrollHeight = currentRef.offsetTop + currentRef.offsetHeight
const wrapHeight = wrapEl.offsetHeight; const wrapHeight = wrapEl.offsetHeight
const { start } = useScrollTo({ const { start } = useScrollTo({
el: wrapEl, el: wrapEl,
duration: 100, duration: 100,
to: scrollHeight - wrapHeight, to: scrollHeight - wrapHeight,
}); })
start(); start()
} }
// enter keyboard event // enter keyboard event
async function handleEnter() { async function handleEnter() {
if (!searchResult.value.length) { if (!searchResult.value.length) {
return; return
} }
const result = unref(searchResult); const result = unref(searchResult)
const index = unref(activeIndex); const index = unref(activeIndex)
if (result.length === 0 || index < 0) { if (result.length === 0 || index < 0) {
return; return
} }
const to = result[index]; const to = result[index]
handleClose(); handleClose()
await nextTick(); await nextTick()
go(to.path); go(to.path)
} }
// close search modal // close search modal
function handleClose() { function handleClose() {
searchResult.value = []; searchResult.value = []
emit('close'); emit('close')
} }
// enter search // enter search
onKeyStroke('Enter', handleEnter); onKeyStroke('Enter', handleEnter)
// Monitor keyboard arrow keys // Monitor keyboard arrow keys
onKeyStroke('ArrowUp', handleUp); onKeyStroke('ArrowUp', handleUp)
onKeyStroke('ArrowDown', handleDown); onKeyStroke('ArrowDown', handleDown)
// esc close // esc close
onKeyStroke('Escape', handleClose); onKeyStroke('Escape', handleClose)
return { handleSearch, searchResult, keyword, activeIndex, handleMouseenter, handleEnter }; return { handleSearch, searchResult, keyword, activeIndex, handleMouseenter, handleEnter }
} }

View File

@ -1,17 +1,17 @@
import { InjectionKey, Ref } from 'vue'; import { InjectionKey, Ref } from 'vue'
import { createContext, useContext } from '/@/hooks/core/useContext'; import { createContext, useContext } from '/@/hooks/core/useContext'
export interface AppProviderContextProps { export interface AppProviderContextProps {
prefixCls: Ref<string>; prefixCls: Ref<string>
isMobile: Ref<boolean>; isMobile: Ref<boolean>
} }
const key: InjectionKey<AppProviderContextProps> = Symbol(); const key: InjectionKey<AppProviderContextProps> = Symbol()
export function createAppProviderContext(context: AppProviderContextProps) { export function createAppProviderContext(context: AppProviderContextProps) {
return createContext<AppProviderContextProps>(context, key); return createContext<AppProviderContextProps>(context, key)
} }
export function useAppProviderContext() { export function useAppProviderContext() {
return useContext<AppProviderContextProps>(key); return useContext<AppProviderContextProps>(key)
} }

View File

@ -1,4 +0,0 @@
import { withInstall } from '/@/utils';
import authority from './src/Authority.vue';
export const Authority = withInstall(authority);

View File

@ -1,45 +0,0 @@
<!--
Access control component for fine-grained access control.
-->
<script lang="ts">
import type { PropType } from 'vue';
import { defineComponent } from 'vue';
import { RoleEnum } from '/@/enums/roleEnum';
import { usePermission } from '/@/hooks/web/usePermission';
import { getSlot } from '/@/utils/helper/tsxHelper';
export default defineComponent({
name: 'Authority',
props: {
/**
* Specified role is visible
* When the permission mode is the role mode, the value value can pass the role value.
* When the permission mode is background, the value value can pass the code permission value
* @default ''
*/
value: {
type: [Number, Array, String] as PropType<RoleEnum | RoleEnum[] | string | string[]>,
default: '',
},
},
setup(props, { slots }) {
const { hasPermission } = usePermission();
/**
* Render role button
*/
function renderAuth() {
const { value } = props;
if (!value) {
return getSlot(slots);
}
return hasPermission(value) ? getSlot(slots) : null;
}
return () => {
// Role-based value control
return renderAuth();
};
},
});
</script>

View File

@ -1,8 +1,8 @@
import { withInstall } from '/@/utils'; import { withInstall } from '/@/utils'
import basicArrow from './src/BasicArrow.vue'; import basicArrow from './src/BasicArrow.vue'
import basicTitle from './src/BasicTitle.vue'; import basicTitle from './src/BasicTitle.vue'
import basicHelp from './src/BasicHelp.vue'; import basicHelp from './src/BasicHelp.vue'
export const BasicArrow = withInstall(basicArrow); export const BasicArrow = withInstall(basicArrow)
export const BasicTitle = withInstall(basicTitle); export const BasicTitle = withInstall(basicTitle)
export const BasicHelp = withInstall(basicHelp); export const BasicHelp = withInstall(basicHelp)

View File

@ -8,9 +8,9 @@
</span> </span>
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import { computed } from 'vue'; import { computed } from 'vue'
import { Icon } from '/@/components/Icon'; import { Icon } from '/@/components/Icon'
import { useDesign } from '/@/hooks/web/useDesign'; import { useDesign } from '/@/hooks/web/useDesign'
const props = defineProps({ const props = defineProps({
/** /**
@ -29,13 +29,13 @@
* Cancel padding/margin for inline * Cancel padding/margin for inline
*/ */
inset: { type: Boolean }, inset: { type: Boolean },
}); })
const { prefixCls } = useDesign('basic-arrow'); const { prefixCls } = useDesign('basic-arrow')
// get component class // get component class
const getClass = computed(() => { const getClass = computed(() => {
const { expand, up, down, inset } = props; const { expand, up, down, inset } = props
return [ return [
prefixCls, prefixCls,
{ {
@ -44,8 +44,8 @@
inset, inset,
down, down,
}, },
]; ]
}); })
</script> </script>
<style lang="less" scoped> <style lang="less" scoped>
@prefix-cls: ~'@{namespace}-basic-arrow'; @prefix-cls: ~'@{namespace}-basic-arrow';

View File

@ -1,12 +1,12 @@
<script lang="tsx"> <script lang="tsx">
import type { CSSProperties, PropType } from 'vue'; import type { CSSProperties, PropType } from 'vue'
import { defineComponent, computed, unref } from 'vue'; import { defineComponent, computed, unref } from 'vue'
import { Tooltip } from 'ant-design-vue'; import { Tooltip } from 'ant-design-vue'
import { InfoCircleOutlined } from '@ant-design/icons-vue'; import { InfoCircleOutlined } from '@ant-design/icons-vue'
import { getPopupContainer } from '/@/utils'; import { getPopupContainer } from '/@/utils'
import { isString, isArray } from '/@/utils/is'; import { isString, isArray } from '/@/utils/is'
import { getSlot } from '/@/utils/helper/tsxHelper'; import { getSlot } from '/@/utils/helper/tsxHelper'
import { useDesign } from '/@/hooks/web/useDesign'; import { useDesign } from '/@/hooks/web/useDesign'
const props = { const props = {
/** /**
@ -37,26 +37,26 @@
* Help text list * Help text list
*/ */
text: { type: [Array, String] as PropType<string[] | string> }, text: { type: [Array, String] as PropType<string[] | string> },
}; }
export default defineComponent({ export default defineComponent({
name: 'BasicHelp', name: 'BasicHelp',
components: { Tooltip }, components: { Tooltip },
props, props,
setup(props, { slots }) { setup(props, { slots }) {
const { prefixCls } = useDesign('basic-help'); const { prefixCls } = useDesign('basic-help')
const getTooltipStyle = computed( const getTooltipStyle = computed(
(): CSSProperties => ({ color: props.color, fontSize: props.fontSize }), (): CSSProperties => ({ color: props.color, fontSize: props.fontSize }),
); )
const getOverlayStyle = computed((): CSSProperties => ({ maxWidth: props.maxWidth })); const getOverlayStyle = computed((): CSSProperties => ({ maxWidth: props.maxWidth }))
function renderTitle() { function renderTitle() {
const textList = props.text; const textList = props.text
if (isString(textList)) { if (isString(textList)) {
return <p>{textList}</p>; return <p>{textList}</p>
} }
if (isArray(textList)) { if (isArray(textList)) {
@ -68,10 +68,10 @@
{text} {text}
</> </>
</p> </p>
); )
}); })
} }
return null; return null
} }
return () => { return () => {
@ -86,10 +86,10 @@
> >
<span class={prefixCls}>{getSlot(slots) || <InfoCircleOutlined />}</span> <span class={prefixCls}>{getSlot(slots) || <InfoCircleOutlined />}</span>
</Tooltip> </Tooltip>
); )
}; }
}, },
}); })
</script> </script>
<style lang="less"> <style lang="less">
@prefix-cls: ~'@{namespace}-basic-help'; @prefix-cls: ~'@{namespace}-basic-help';

View File

@ -5,10 +5,10 @@
</span> </span>
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import type { PropType } from 'vue'; import type { PropType } from 'vue'
import { useSlots, computed } from 'vue'; import { useSlots, computed } from 'vue'
import BasicHelp from './BasicHelp.vue'; import BasicHelp from './BasicHelp.vue'
import { useDesign } from '/@/hooks/web/useDesign'; import { useDesign } from '/@/hooks/web/useDesign'
const props = defineProps({ const props = defineProps({
/** /**
@ -29,15 +29,15 @@
* @default: false * @default: false
*/ */
normal: { type: Boolean }, normal: { type: Boolean },
}); })
const { prefixCls } = useDesign('basic-title'); const { prefixCls } = useDesign('basic-title')
const slots = useSlots(); const slots = useSlots()
const getClass = computed(() => [ const getClass = computed(() => [
prefixCls, prefixCls,
{ [`${prefixCls}-show-span`]: props.span && slots.default }, { [`${prefixCls}-show-span`]: props.span && slots.default },
{ [`${prefixCls}-normal`]: props.normal }, { [`${prefixCls}-normal`]: props.normal },
]); ])
</script> </script>
<style lang="less" scoped> <style lang="less" scoped>
@prefix-cls: ~'@{namespace}-basic-title'; @prefix-cls: ~'@{namespace}-basic-title';

View File

@ -1,9 +1,9 @@
import { withInstall } from '/@/utils'; import { withInstall } from '/@/utils'
import type { ExtractPropTypes } from 'vue'; import type { ExtractPropTypes } from 'vue'
import button from './src/BasicButton.vue'; import button from './src/BasicButton.vue'
import popConfirmButton from './src/PopConfirmButton.vue'; import popConfirmButton from './src/PopConfirmButton.vue'
import { buttonProps } from './src/props'; import { buttonProps } from './src/props'
export const Button = withInstall(button); export const Button = withInstall(button)
export const PopConfirmButton = withInstall(popConfirmButton); export const PopConfirmButton = withInstall(popConfirmButton)
export declare type ButtonProps = Partial<ExtractPropTypes<typeof buttonProps>>; export declare type ButtonProps = Partial<ExtractPropTypes<typeof buttonProps>>

View File

@ -9,32 +9,32 @@
</template> </template>
<script lang="ts"> <script lang="ts">
import { defineComponent } from 'vue'; import { defineComponent } from 'vue'
export default defineComponent({ export default defineComponent({
name: 'AButton', name: 'AButton',
inheritAttrs: false, inheritAttrs: false,
}); })
</script> </script>
<script lang="ts" setup> <script lang="ts" setup>
import { computed, unref } from 'vue'; import { computed, unref } from 'vue'
import { Button } from 'ant-design-vue'; import { Button } from 'ant-design-vue'
import Icon from '/@/components/Icon/src/Icon.vue'; import Icon from '/@/components/Icon/src/Icon.vue'
import { buttonProps } from './props'; import { buttonProps } from './props'
import { useAttrs } from '/@/hooks/core/useAttrs'; import { useAttrs } from '/@/hooks/core/useAttrs'
const props = defineProps(buttonProps); const props = defineProps(buttonProps)
// get component class // get component class
const attrs = useAttrs({ excludeDefaultKeys: false }); const attrs = useAttrs({ excludeDefaultKeys: false })
const getButtonClass = computed(() => { const getButtonClass = computed(() => {
const { color, disabled } = props; const { color, disabled } = props
return [ return [
{ {
[`ant-btn-${color}`]: !!color, [`ant-btn-${color}`]: !!color,
[`is-disabled`]: disabled, [`is-disabled`]: disabled,
}, },
]; ]
}); })
// get inherit binding value // get inherit binding value
const getBindValue = computed(() => ({ ...unref(attrs), ...props })); const getBindValue = computed(() => ({ ...unref(attrs), ...props }))
</script> </script>

View File

@ -1,11 +1,11 @@
<script lang="ts"> <script lang="ts">
import { computed, defineComponent, h, unref } from 'vue'; import { computed, defineComponent, h, unref } from 'vue'
import BasicButton from './BasicButton.vue'; import BasicButton from './BasicButton.vue'
import { Popconfirm } from 'ant-design-vue'; import { Popconfirm } from 'ant-design-vue'
import { extendSlots } from '/@/utils/helper/tsxHelper'; import { extendSlots } from '/@/utils/helper/tsxHelper'
import { omit } from 'lodash-es'; import { omit } from 'lodash-es'
import { useAttrs } from '/@/hooks/core/useAttrs'; import { useAttrs } from '/@/hooks/core/useAttrs'
import { useI18n } from '/@/hooks/web/useI18n'; import { useI18n } from '/@/hooks/web/useI18n'
const props = { const props = {
/** /**
@ -16,15 +16,15 @@
type: Boolean, type: Boolean,
default: true, default: true,
}, },
}; }
export default defineComponent({ export default defineComponent({
name: 'PopButton', name: 'PopButton',
inheritAttrs: false, inheritAttrs: false,
props, props,
setup(props, { slots }) { setup(props, { slots }) {
const { t } = useI18n(); const { t } = useI18n()
const attrs = useAttrs(); const attrs = useAttrs()
// get inherit binding value // get inherit binding value
const getBindValues = computed(() => { const getBindValues = computed(() => {
@ -34,21 +34,21 @@
cancelText: t('common.cancelText'), cancelText: t('common.cancelText'),
}, },
{ ...props, ...unref(attrs) }, { ...props, ...unref(attrs) },
); )
}); })
return () => { return () => {
const bindValues = omit(unref(getBindValues), 'icon'); const bindValues = omit(unref(getBindValues), 'icon')
const btnBind = omit(bindValues, 'title') as Recordable; const btnBind = omit(bindValues, 'title') as Recordable
if (btnBind.disabled) btnBind.color = ''; if (btnBind.disabled) btnBind.color = ''
const Button = h(BasicButton, btnBind, extendSlots(slots)); const Button = h(BasicButton, btnBind, extendSlots(slots))
// If it is not enabled, it is a normal button // If it is not enabled, it is a normal button
if (!props.enable) { if (!props.enable) {
return Button; return Button
}
return h(Popconfirm, bindValues, { default: () => Button })
} }
return h(Popconfirm, bindValues, { default: () => Button });
};
}, },
}); })
</script> </script>

View File

@ -16,4 +16,4 @@ export const buttonProps = {
*/ */
iconSize: { type: Number, default: 14 }, iconSize: { type: Number, default: 14 },
onClick: { type: Function as PropType<(...args) => any>, default: null }, onClick: { type: Function as PropType<(...args) => any>, default: null },
}; }

View File

@ -1,4 +0,0 @@
import { withInstall } from '/@/utils';
import cardList from './src/CardList.vue';
export const CardList = withInstall(cardList);

View File

@ -1,177 +0,0 @@
<template>
<div class="p-2">
<div class="p-4 mb-2 bg-white">
<BasicForm @register="registerForm" />
</div>
<div class="p-2 bg-white">
<List
:grid="{ gutter: 5, xs: 1, sm: 2, md: 4, lg: 4, xl: 6, xxl: grid }"
:data-source="data"
:pagination="paginationProp"
>
<template #header>
<div class="flex justify-end space-x-2"
><slot name="header"></slot>
<Tooltip>
<template #title>
<div class="w-50">每行显示数量</div
><Slider
id="slider"
v-bind="sliderProp"
v-model:value="grid"
@change="sliderChange"
/></template>
<Button><TableOutlined /></Button>
</Tooltip>
<Tooltip @click="fetch">
<template #title>刷新</template>
<Button><RedoOutlined /></Button>
</Tooltip>
</div>
</template>
<template #renderItem="{ item }">
<ListItem>
<Card>
<template #title></template>
<template #cover>
<div :class="height">
<Image :src="item.imgs[0]" />
</div>
</template>
<template #actions>
<!-- <SettingOutlined key="setting" />-->
<EditOutlined key="edit" />
<Dropdown
:trigger="['hover']"
:dropMenuList="[
{
text: '删除',
event: '1',
popConfirm: {
title: '是否确认删除',
confirm: handleDelete.bind(null, item.id),
},
},
]"
popconfirm
>
<EllipsisOutlined key="ellipsis" />
</Dropdown>
</template>
<CardMeta>
<template #title>
<TypographyText :content="item.name" :ellipsis="{ tooltip: item.address }" />
</template>
<template #avatar>
<Avatar :src="item.avatar" />
</template>
<template #description>{{ item.time }}</template>
</CardMeta>
</Card>
</ListItem>
</template>
</List>
</div>
</div>
</template>
<script lang="ts" setup>
import { computed, onMounted, ref } from 'vue';
import {
EditOutlined,
EllipsisOutlined,
RedoOutlined,
TableOutlined,
} from '@ant-design/icons-vue';
import { List, Card, Image, Typography, Tooltip, Slider, Avatar } from 'ant-design-vue';
import { Dropdown } from '/@/components/Dropdown';
import { BasicForm, useForm } from '/@/components/Form';
import { propTypes } from '/@/utils/propTypes';
import { Button } from '/@/components/Button';
import { isFunction } from '/@/utils/is';
import { useSlider, grid } from './data';
const ListItem = List.Item;
const CardMeta = Card.Meta;
const TypographyText = Typography.Text;
// slider
const sliderProp = computed(() => useSlider(4));
//
const props = defineProps({
// API
params: propTypes.object.def({}),
//api
api: propTypes.func,
});
//
const emit = defineEmits(['getMethod', 'delete']);
//
const data = ref([]);
//
// cover
//pageSize
const height = computed(() => {
return `h-${120 - grid.value * 6}`;
});
//
const [registerForm, { validate }] = useForm({
schemas: [{ field: 'type', component: 'Input', label: '类型' }],
labelWidth: 80,
baseColProps: { span: 6 },
actionColOptions: { span: 24 },
autoSubmitOnEnter: true,
submitFunc: handleSubmit,
});
//
async function handleSubmit() {
const data = await validate();
await fetch(data);
}
function sliderChange(n) {
pageSize.value = n * 4;
fetch();
}
//
onMounted(() => {
fetch();
emit('getMethod', fetch);
});
async function fetch(p = {}) {
const { api, params } = props;
if (api && isFunction(api)) {
const res = await api({ ...params, page: page.value, pageSize: pageSize.value, ...p });
data.value = res.items;
total.value = res.total;
}
}
//
const page = ref(1);
const pageSize = ref(36);
const total = ref(0);
const paginationProp = ref({
showSizeChanger: false,
showQuickJumper: true,
pageSize,
current: page,
total,
showTotal: (total) => `${total}`,
onChange: pageChange,
onShowSizeChange: pageSizeChange,
});
function pageChange(p, pz) {
page.value = p;
pageSize.value = pz;
fetch();
}
function pageSizeChange(_current, size) {
pageSize.value = size;
fetch();
}
async function handleDelete(id) {
emit('delete', id);
}
</script>

View File

@ -1,25 +0,0 @@
import { ref } from 'vue';
// 每行个数
export const grid = ref(12);
// slider属性
export const useSlider = (min = 6, max = 12) => {
// 每行显示个数滑动条
const getMarks = () => {
const l = {};
for (let i = min; i < max + 1; i++) {
l[i] = {
style: {
color: '#fff',
},
label: i,
};
}
return l;
};
return {
min,
max,
marks: getMarks(),
step: 1,
};
};

View File

@ -1,4 +0,0 @@
import { withInstall } from '/@/utils';
import clickOutSide from './src/ClickOutSide.vue';
export const ClickOutSide = withInstall(clickOutSide);

View File

@ -1,19 +0,0 @@
<template>
<div ref="wrap">
<slot></slot>
</div>
</template>
<script lang="ts" setup>
import { ref, onMounted } from 'vue';
import { onClickOutside } from '@vueuse/core';
const emit = defineEmits(['mounted', 'clickOutside']);
const wrap = ref<ElRef>(null);
onClickOutside(wrap, () => {
emit('clickOutside');
});
onMounted(() => {
emit('mounted');
});
</script>

View File

@ -1,8 +0,0 @@
import { withInstall } from '/@/utils';
import codeEditor from './src/CodeEditor.vue';
import jsonPreview from './src/json-preview/JsonPreview.vue';
export const CodeEditor = withInstall(codeEditor);
export const JsonPreview = withInstall(jsonPreview);
export * from './src/typing';

View File

@ -1,54 +0,0 @@
<template>
<div class="h-full">
<CodeMirrorEditor
:value="getValue"
@change="handleValueChange"
:mode="mode"
:readonly="readonly"
/>
</div>
</template>
<script lang="ts" setup>
import { computed } from 'vue';
import CodeMirrorEditor from './codemirror/CodeMirror.vue';
import { isString } from '/@/utils/is';
import { MODE } from './typing';
const props = defineProps({
value: { type: [Object, String] as PropType<Record<string, any> | string> },
mode: {
type: String as PropType<MODE>,
default: MODE.JSON,
validator(value: any) {
//
return Object.values(MODE).includes(value);
},
},
readonly: { type: Boolean },
autoFormat: { type: Boolean, default: true },
});
const emit = defineEmits(['change', 'update:value', 'format-error']);
const getValue = computed(() => {
const { value, mode, autoFormat } = props;
if (!autoFormat || mode !== MODE.JSON) {
return value as string;
}
let result = value;
if (isString(value)) {
try {
result = JSON.parse(value);
} catch (e) {
emit('format-error', value);
return value as string;
}
}
return JSON.stringify(result, null, 2);
});
function handleValueChange(v) {
emit('update:value', v);
emit('change', v);
}
</script>

View File

@ -1,113 +0,0 @@
<template>
<div class="relative !h-full w-full overflow-hidden" ref="el"></div>
</template>
<script lang="ts" setup>
import { ref, onMounted, onUnmounted, watchEffect, watch, unref, nextTick } from 'vue';
import { useDebounceFn } from '@vueuse/core';
import { useAppStore } from '/@/store/modules/app';
import { useWindowSizeFn } from '/@/hooks/event/useWindowSizeFn';
import CodeMirror from 'codemirror';
import { MODE } from './../typing';
// css
import './codemirror.css';
import 'codemirror/theme/idea.css';
import 'codemirror/theme/material-palenight.css';
// modes
import 'codemirror/mode/javascript/javascript';
import 'codemirror/mode/css/css';
import 'codemirror/mode/htmlmixed/htmlmixed';
const props = defineProps({
mode: {
type: String as PropType<MODE>,
default: MODE.JSON,
validator(value: any) {
//
return Object.values(MODE).includes(value);
},
},
value: { type: String, default: '' },
readonly: { type: Boolean, default: false },
});
const emit = defineEmits(['change']);
const el = ref();
let editor: Nullable<CodeMirror.Editor>;
const debounceRefresh = useDebounceFn(refresh, 100);
const appStore = useAppStore();
watch(
() => props.value,
async (value) => {
await nextTick();
const oldValue = editor?.getValue();
if (value !== oldValue) {
editor?.setValue(value ? value : '');
}
},
{ flush: 'post' },
);
watchEffect(() => {
editor?.setOption('mode', props.mode);
});
watch(
() => appStore.getDarkMode,
async () => {
setTheme();
},
{
immediate: true,
},
);
function setTheme() {
unref(editor)?.setOption(
'theme',
appStore.getDarkMode === 'light' ? 'idea' : 'material-palenight',
);
}
function refresh() {
editor?.refresh();
}
async function init() {
const addonOptions = {
autoCloseBrackets: true,
autoCloseTags: true,
foldGutter: true,
gutters: ['CodeMirror-linenumbers'],
};
editor = CodeMirror(el.value!, {
value: '',
mode: props.mode,
readOnly: props.readonly,
tabSize: 2,
theme: 'material-palenight',
lineWrapping: true,
lineNumbers: true,
...addonOptions,
});
editor?.setValue(props.value);
setTheme();
editor?.on('change', () => {
emit('change', editor?.getValue());
});
}
onMounted(async () => {
await nextTick();
init();
useWindowSizeFn(debounceRefresh);
});
onUnmounted(() => {
editor = null;
});
</script>

View File

@ -1,21 +0,0 @@
import CodeMirror from 'codemirror';
import './codemirror.css';
import 'codemirror/theme/idea.css';
import 'codemirror/theme/material-palenight.css';
// import 'codemirror/addon/lint/lint.css';
// modes
import 'codemirror/mode/javascript/javascript';
import 'codemirror/mode/css/css';
import 'codemirror/mode/htmlmixed/htmlmixed';
// addons
// import 'codemirror/addon/edit/closebrackets';
// import 'codemirror/addon/edit/closetag';
// import 'codemirror/addon/comment/comment';
// import 'codemirror/addon/fold/foldcode';
// import 'codemirror/addon/fold/foldgutter';
// import 'codemirror/addon/fold/brace-fold';
// import 'codemirror/addon/fold/indent-fold';
// import 'codemirror/addon/lint/json-lint';
// import 'codemirror/addon/fold/comment-fold';
export { CodeMirror };

View File

@ -1,525 +0,0 @@
/* BASICS */
.CodeMirror {
--base: #545281;
--comment: hsl(210deg 25% 60%);
--keyword: #af4ab1;
--variable: #0055d1;
--function: #c25205;
--string: #2ba46d;
--number: #c25205;
--tags: #d00;
--qualifier: #ff6032;
--important: var(--string);
position: relative;
height: auto;
height: 100%;
overflow: hidden;
font-family: var(--font-code);
background: white;
direction: ltr;
}
/* PADDING */
.CodeMirror-lines {
min-height: 1px; /* prevents collapsing before first draw */
padding: 4px 0; /* Vertical padding around content */
cursor: text;
}
.CodeMirror-scrollbar-filler,
.CodeMirror-gutter-filler {
background-color: white; /* The little square between H and V scrollbars */
}
/* GUTTER */
.CodeMirror-gutters {
position: absolute;
top: 0;
left: 0;
z-index: 3;
min-height: 100%;
white-space: nowrap;
background-color: transparent;
border-right: 1px solid #ddd;
}
.CodeMirror-linenumber {
min-width: 20px;
padding: 0 3px 0 5px;
color: var(--comment);
text-align: right;
white-space: nowrap;
opacity: 0.6;
}
.CodeMirror-guttermarker {
color: black;
}
.CodeMirror-guttermarker-subtle {
color: #999;
}
/* FOLD GUTTER */
.CodeMirror-foldmarker {
font-family: arial;
line-height: 0.3;
color: #414141;
text-shadow: #f96 1px 1px 2px, #f96 -1px -1px 2px, #f96 1px -1px 2px, #f96 -1px 1px 2px;
cursor: pointer;
}
.CodeMirror-foldgutter {
width: 0.7em;
}
.CodeMirror-foldgutter-open,
.CodeMirror-foldgutter-folded {
cursor: pointer;
}
.CodeMirror-foldgutter-open::after,
.CodeMirror-foldgutter-folded::after {
position: relative;
top: -0.1em;
display: inline-block;
font-size: 0.8em;
content: '>';
opacity: 0.8;
transform: rotate(90deg);
transition: transform 0.2s;
}
.CodeMirror-foldgutter-folded::after {
transform: none;
}
/* CURSOR */
.CodeMirror-cursor {
position: absolute;
width: 0;
pointer-events: none;
border-right: none;
border-left: 1px solid black;
}
/* Shown when moving in bi-directional text */
.CodeMirror div.CodeMirror-secondarycursor {
border-left: 1px solid silver;
}
.cm-fat-cursor .CodeMirror-cursor {
width: auto;
background: #7e7;
border: 0 !important;
}
.cm-fat-cursor div.CodeMirror-cursors {
z-index: 1;
}
.cm-fat-cursor-mark {
background-color: rgb(20 255 20 / 50%);
animation: blink 1.06s steps(1) infinite;
}
.cm-animate-fat-cursor {
width: auto;
background-color: #7e7;
border: 0;
animation: blink 1.06s steps(1) infinite;
}
@keyframes blink {
50% {
background-color: transparent;
}
}
@keyframes blink {
50% {
background-color: transparent;
}
}
@keyframes blink {
50% {
background-color: transparent;
}
}
.cm-tab {
display: inline-block;
text-decoration: inherit;
}
.CodeMirror-rulers {
position: absolute;
top: -50px;
right: 0;
bottom: -20px;
left: 0;
overflow: hidden;
}
.CodeMirror-ruler {
position: absolute;
top: 0;
bottom: 0;
border-left: 1px solid #ccc;
}
/* DEFAULT THEME */
.cm-s-default.CodeMirror {
background-color: transparent;
}
.cm-s-default .cm-header {
color: blue;
}
.cm-s-default .cm-quote {
color: #090;
}
.cm-negative {
color: #d44;
}
.cm-positive {
color: #292;
}
.cm-header,
.cm-strong {
font-weight: bold;
}
.cm-em {
font-style: italic;
}
.cm-link {
text-decoration: underline;
}
.cm-strikethrough {
text-decoration: line-through;
}
.cm-s-default .cm-atom,
.cm-s-default .cm-def,
.cm-s-default .cm-property,
.cm-s-default .cm-variable-2,
.cm-s-default .cm-variable-3,
.cm-s-default .cm-punctuation {
color: var(--base);
}
.cm-s-default .cm-hr,
.cm-s-default .cm-comment {
color: var(--comment);
}
.cm-s-default .cm-attribute,
.cm-s-default .cm-keyword {
color: var(--keyword);
}
.cm-s-default .cm-variable {
color: var(--variable);
}
.cm-s-default .cm-bracket,
.cm-s-default .cm-tag {
color: var(--tags);
}
.cm-s-default .cm-number {
color: var(--number);
}
.cm-s-default .cm-string,
.cm-s-default .cm-string-2 {
color: var(--string);
}
.cm-s-default .cm-type {
color: #085;
}
.cm-s-default .cm-meta {
color: #555;
}
.cm-s-default .cm-qualifier {
color: var(--qualifier);
}
.cm-s-default .cm-builtin {
color: #7539ff;
}
.cm-s-default .cm-link {
color: var(--flash);
}
.cm-s-default .cm-error {
color: #ff008c;
}
.cm-invalidchar {
color: #ff008c;
}
.CodeMirror-composing {
border-bottom: 2px solid;
}
/* Default styles for common addons */
div.CodeMirror span.CodeMirror-matchingbracket {
color: #0b0;
}
div.CodeMirror span.CodeMirror-nonmatchingbracket {
color: #a22;
}
.CodeMirror-matchingtag {
background: rgb(255 150 0 / 30%);
}
.CodeMirror-activeline-background {
background: #e8f2ff;
}
/* STOP */
/* The rest of this file contains styles related to the mechanics of
the editor. You probably shouldn't touch them. */
.CodeMirror-scroll {
position: relative;
height: 100%;
padding-bottom: 30px;
margin-right: -30px;
/* 30px is the magic margin used to hide the element's real scrollbars */
/* See overflow: hidden in .CodeMirror */
margin-bottom: -30px;
overflow: scroll !important; /* Things will break if this is overridden */
outline: none; /* Prevent dragging from highlighting the element */
}
.CodeMirror-sizer {
position: relative;
margin-bottom: 20px !important;
border-right: 30px solid transparent;
}
/* The fake, visible scrollbars. Used to force redraw during scrolling
before actual scrolling happens, thus preventing shaking and
flickering artifacts. */
.CodeMirror-vscrollbar,
.CodeMirror-hscrollbar,
.CodeMirror-scrollbar-filler,
.CodeMirror-gutter-filler {
position: absolute;
z-index: 6;
display: none;
}
.CodeMirror-vscrollbar {
top: 0;
right: 0;
overflow-x: hidden;
overflow-y: scroll;
}
.CodeMirror-hscrollbar {
bottom: 0;
left: 0;
overflow-x: scroll;
overflow-y: hidden;
}
.CodeMirror-scrollbar-filler {
right: 0;
bottom: 0;
}
.CodeMirror-gutter-filler {
bottom: 0;
left: 0;
}
.CodeMirror-gutter {
display: inline-block;
height: 100%;
margin-bottom: -30px;
white-space: normal;
vertical-align: top;
}
.CodeMirror-gutter-wrapper {
position: absolute;
z-index: 4;
background: none !important;
border: none !important;
}
.CodeMirror-gutter-background {
position: absolute;
top: 0;
bottom: 0;
z-index: 4;
}
.CodeMirror-gutter-elt {
position: absolute;
z-index: 4;
cursor: default;
}
.CodeMirror-gutter-wrapper ::selection {
background-color: transparent;
}
.CodeMirrorwrapper ::selection {
background-color: transparent;
}
.CodeMirror pre {
position: relative;
z-index: 2;
padding: 0 4px; /* Horizontal padding of content */
margin: 0;
overflow: visible;
font-family: inherit;
font-size: inherit;
line-height: inherit;
color: inherit;
word-wrap: normal;
white-space: pre;
background: transparent;
border-width: 0;
/* Reset some styles that the rest of the page might have set */
border-radius: 0;
-webkit-tap-highlight-color: transparent;
font-variant-ligatures: contextual;
}
.CodeMirror-wrap pre {
word-break: normal;
word-wrap: break-word;
white-space: pre-wrap;
}
.CodeMirror-linebackground {
position: absolute;
top: 0;
right: 0;
bottom: 0;
left: 0;
z-index: 0;
}
.CodeMirror-linewidget {
position: relative;
z-index: 2;
padding: 0.1px; /* Force widget margins to stay inside of the container */
}
.CodeMirror-rtl pre {
direction: rtl;
}
.CodeMirror-code {
outline: none;
}
/* Force content-box sizing for the elements where we expect it */
.CodeMirror-scroll,
.CodeMirror-sizer,
.CodeMirror-gutter,
.CodeMirror-gutters,
.CodeMirror-linenumber {
box-sizing: content-box;
}
.CodeMirror-measure {
position: absolute;
width: 100%;
height: 0;
overflow: hidden;
visibility: hidden;
}
.CodeMirror-measure pre {
position: static;
}
div.CodeMirror-cursors {
position: relative;
z-index: 3;
visibility: hidden;
}
div.CodeMirror-dragcursors {
visibility: visible;
}
.CodeMirror-focused div.CodeMirror-cursors {
visibility: visible;
}
.CodeMirror-selected {
background: #d9d9d9;
}
.CodeMirror-focused .CodeMirror-selected {
background: #d7d4f0;
}
.CodeMirror-crosshair {
cursor: crosshair;
}
.CodeMirror-line::selection,
.CodeMirror-line > span::selection,
.CodeMirror-line > span > span::selection {
background: #d7d4f0;
}
.cm-searching {
background-color: #ffa;
background-color: rgb(255 255 0 / 40%);
}
/* Used to force a border model for a node */
.cm-force-border {
padding-right: 0.1px;
}
@media print {
/* Hide the cursor when printing */
.CodeMirror div.CodeMirror-cursors {
visibility: hidden;
}
}
/* See issue #2901 */
.cm-tab-wrap-hack::after {
content: '';
}
/* Help users use markselection to safely style text background */
span.CodeMirror-selectedtext {
background: none;
}

View File

@ -1,12 +0,0 @@
<template>
<vue-json-pretty :path="'res'" :deep="3" :showLength="true" :data="data" />
</template>
<script lang="ts" setup>
import VueJsonPretty from 'vue-json-pretty';
import 'vue-json-pretty/lib/styles.css';
defineProps({
data: Object,
});
</script>

View File

@ -1,5 +0,0 @@
export enum MODE {
JSON = 'application/json',
HTML = 'htmlmixed',
JS = 'javascript',
}

View File

@ -1,10 +1,10 @@
import { withInstall } from '/@/utils'; import { withInstall } from '/@/utils'
import collapseContainer from './src/collapse/CollapseContainer.vue'; import collapseContainer from './src/collapse/CollapseContainer.vue'
import scrollContainer from './src/ScrollContainer.vue'; import scrollContainer from './src/ScrollContainer.vue'
import lazyContainer from './src/LazyContainer.vue'; import lazyContainer from './src/LazyContainer.vue'
export const CollapseContainer = withInstall(collapseContainer); export const CollapseContainer = withInstall(collapseContainer)
export const ScrollContainer = withInstall(scrollContainer); export const ScrollContainer = withInstall(scrollContainer)
export const LazyContainer = withInstall(lazyContainer); export const LazyContainer = withInstall(lazyContainer)
export * from './src/typing'; export * from './src/typing'

View File

@ -17,16 +17,16 @@
</transition-group> </transition-group>
</template> </template>
<script lang="ts"> <script lang="ts">
import type { PropType } from 'vue'; import type { PropType } from 'vue'
import { defineComponent, reactive, onMounted, ref, toRef, toRefs } from 'vue'; import { defineComponent, reactive, onMounted, ref, toRef, toRefs } from 'vue'
import { Skeleton } from 'ant-design-vue'; import { Skeleton } from 'ant-design-vue'
import { useTimeoutFn } from '/@/hooks/core/useTimeout'; import { useTimeoutFn } from '/@/hooks/core/useTimeout'
import { useIntersectionObserver } from '/@/hooks/event/useIntersectionObserver'; import { useIntersectionObserver } from '/@/hooks/event/useIntersectionObserver'
interface State { interface State {
isInit: boolean; isInit: boolean
loading: boolean; loading: boolean
intersectionObserverInstance: IntersectionObserver | null; intersectionObserverInstance: IntersectionObserver | null
} }
const props = { const props = {
@ -63,7 +63,7 @@
* transition name * transition name
*/ */
transitionName: { type: String, default: 'lazy-container' }, transitionName: { type: String, default: 'lazy-container' },
}; }
export default defineComponent({ export default defineComponent({
name: 'LazyContainer', name: 'LazyContainer',
@ -72,49 +72,49 @@
props, props,
emits: ['init'], emits: ['init'],
setup(props, { emit }) { setup(props, { emit }) {
const elRef = ref(); const elRef = ref()
const state = reactive<State>({ const state = reactive<State>({
isInit: false, isInit: false,
loading: false, loading: false,
intersectionObserverInstance: null, intersectionObserverInstance: null,
}); })
onMounted(() => { onMounted(() => {
immediateInit(); immediateInit()
initIntersectionObserver(); initIntersectionObserver()
}); })
// If there is a set delay time, it will be executed immediately // If there is a set delay time, it will be executed immediately
function immediateInit() { function immediateInit() {
const { timeout } = props; const { timeout } = props
timeout && timeout &&
useTimeoutFn(() => { useTimeoutFn(() => {
init(); init()
}, timeout); }, timeout)
} }
function init() { function init() {
state.loading = true; state.loading = true
useTimeoutFn(() => { useTimeoutFn(() => {
if (state.isInit) return; if (state.isInit) return
state.isInit = true; state.isInit = true
emit('init'); emit('init')
}, props.maxWaitingTime || 80); }, props.maxWaitingTime || 80)
} }
function initIntersectionObserver() { function initIntersectionObserver() {
const { timeout, direction, threshold } = props; const { timeout, direction, threshold } = props
if (timeout) return; if (timeout) return
// According to the scrolling direction to construct the viewport margin, used to load in advance // According to the scrolling direction to construct the viewport margin, used to load in advance
let rootMargin = '0px'; let rootMargin = '0px'
switch (direction) { switch (direction) {
case 'vertical': case 'vertical':
rootMargin = `${threshold} 0px`; rootMargin = `${threshold} 0px`
break; break
case 'horizontal': case 'horizontal':
rootMargin = `0px ${threshold}`; rootMargin = `0px ${threshold}`
break; break
} }
try { try {
@ -122,24 +122,24 @@
rootMargin, rootMargin,
target: toRef(elRef.value, '$el'), target: toRef(elRef.value, '$el'),
onIntersect: (entries: any[]) => { onIntersect: (entries: any[]) => {
const isIntersecting = entries[0].isIntersecting || entries[0].intersectionRatio; const isIntersecting = entries[0].isIntersecting || entries[0].intersectionRatio
if (isIntersecting) { if (isIntersecting) {
init(); init()
if (observer) { if (observer) {
stop(); stop()
} }
} }
}, },
root: toRef(props, 'viewport'), root: toRef(props, 'viewport'),
}); })
} catch (e) { } catch (e) {
init(); init()
} }
} }
return { return {
elRef, elRef,
...toRefs(state), ...toRefs(state),
}; }
}, },
}); })
</script> </script>

View File

@ -5,66 +5,66 @@
</template> </template>
<script lang="ts"> <script lang="ts">
import { defineComponent, ref, unref, nextTick } from 'vue'; import { defineComponent, ref, unref, nextTick } from 'vue'
import { Scrollbar, ScrollbarType } from '/@/components/Scrollbar'; import { Scrollbar, ScrollbarType } from '/@/components/Scrollbar'
import { useScrollTo } from '/@/hooks/event/useScrollTo'; import { useScrollTo } from '/@/hooks/event/useScrollTo'
export default defineComponent({ export default defineComponent({
name: 'ScrollContainer', name: 'ScrollContainer',
components: { Scrollbar }, components: { Scrollbar },
setup() { setup() {
const scrollbarRef = ref<Nullable<ScrollbarType>>(null); const scrollbarRef = ref<Nullable<ScrollbarType>>(null)
/** /**
* Scroll to the specified position * Scroll to the specified position
*/ */
function scrollTo(to: number, duration = 500) { function scrollTo(to: number, duration = 500) {
const scrollbar = unref(scrollbarRef); const scrollbar = unref(scrollbarRef)
if (!scrollbar) { if (!scrollbar) {
return; return
} }
nextTick(() => { nextTick(() => {
const wrap = unref(scrollbar.wrap); const wrap = unref(scrollbar.wrap)
if (!wrap) { if (!wrap) {
return; return
} }
const { start } = useScrollTo({ const { start } = useScrollTo({
el: wrap, el: wrap,
to, to,
duration, duration,
}); })
start(); start()
}); })
} }
function getScrollWrap() { function getScrollWrap() {
const scrollbar = unref(scrollbarRef); const scrollbar = unref(scrollbarRef)
if (!scrollbar) { if (!scrollbar) {
return null; return null
} }
return scrollbar.wrap; return scrollbar.wrap
} }
/** /**
* Scroll to the bottom * Scroll to the bottom
*/ */
function scrollBottom() { function scrollBottom() {
const scrollbar = unref(scrollbarRef); const scrollbar = unref(scrollbarRef)
if (!scrollbar) { if (!scrollbar) {
return; return
} }
nextTick(() => { nextTick(() => {
const wrap = unref(scrollbar.wrap) as any; const wrap = unref(scrollbar.wrap) as any
if (!wrap) { if (!wrap) {
return; return
} }
const scrollHeight = wrap.scrollHeight as number; const scrollHeight = wrap.scrollHeight as number
const { start } = useScrollTo({ const { start } = useScrollTo({
el: wrap, el: wrap,
to: scrollHeight, to: scrollHeight,
}); })
start(); start()
}); })
} }
return { return {
@ -72,9 +72,9 @@
scrollTo, scrollTo,
scrollBottom, scrollBottom,
getScrollWrap, getScrollWrap,
}; }
}, },
}); })
</script> </script>
<style lang="less"> <style lang="less">
.scroll-container { .scroll-container {

View File

@ -23,17 +23,17 @@
</div> </div>
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import type { PropType } from 'vue'; import type { PropType } from 'vue'
import { ref } from 'vue'; import { ref } from 'vue'
import { isNil } from 'lodash-es'; import { isNil } from 'lodash-es'
// component // component
import { Skeleton } from 'ant-design-vue'; import { Skeleton } from 'ant-design-vue'
import { CollapseTransition } from '/@/components/Transition'; import { CollapseTransition } from '/@/components/Transition'
import CollapseHeader from './CollapseHeader.vue'; import CollapseHeader from './CollapseHeader.vue'
import { triggerWindowResize } from '/@/utils/event'; import { triggerWindowResize } from '/@/utils/event'
// hook // hook
import { useTimeoutFn } from '/@/hooks/core/useTimeout'; import { useTimeoutFn } from '/@/hooks/core/useTimeout'
import { useDesign } from '/@/hooks/web/useDesign'; import { useDesign } from '/@/hooks/web/useDesign'
const props = defineProps({ const props = defineProps({
title: { type: String, default: '' }, title: { type: String, default: '' },
@ -58,26 +58,26 @@
* Delayed loading time * Delayed loading time
*/ */
lazyTime: { type: Number, default: 0 }, lazyTime: { type: Number, default: 0 },
}); })
const show = ref(true); const show = ref(true)
const { prefixCls } = useDesign('collapse-container'); const { prefixCls } = useDesign('collapse-container')
/** /**
* @description: Handling development events * @description: Handling development events
*/ */
function handleExpand(val: boolean) { function handleExpand(val: boolean) {
show.value = isNil(val) ? !show.value : val; show.value = isNil(val) ? !show.value : val
if (props.triggerWindowResize) { if (props.triggerWindowResize) {
// 200 milliseconds here is because the expansion has animation, // 200 milliseconds here is because the expansion has animation,
useTimeoutFn(triggerWindowResize, 200); useTimeoutFn(triggerWindowResize, 200)
} }
} }
defineExpose({ defineExpose({
handleExpand, handleExpand,
}); })
</script> </script>
<style lang="less"> <style lang="less">
@prefix-cls: ~'@{namespace}-collapse-container'; @prefix-cls: ~'@{namespace}-collapse-container';

View File

@ -15,8 +15,8 @@
</div> </div>
</template> </template>
<script lang="ts"> <script lang="ts">
import { defineComponent } from 'vue'; import { defineComponent } from 'vue'
import { BasicArrow, BasicTitle } from '/@/components/Basic'; import { BasicArrow, BasicTitle } from '/@/components/Basic'
const props = { const props = {
prefixCls: { type: String }, prefixCls: { type: String },
@ -27,12 +27,12 @@
title: { type: String }, title: { type: String },
show: { type: Boolean }, show: { type: Boolean },
canExpan: { type: Boolean }, canExpan: { type: Boolean },
}; }
export default defineComponent({ export default defineComponent({
components: { BasicArrow, BasicTitle }, components: { BasicArrow, BasicTitle },
inheritAttrs: false, inheritAttrs: false,
props, props,
emits: ['expand'], emits: ['expand'],
}); })
</script> </script>

View File

@ -1,17 +1,17 @@
export type ScrollType = 'default' | 'main'; export type ScrollType = 'default' | 'main'
export interface CollapseContainerOptions { export interface CollapseContainerOptions {
canExpand?: boolean; canExpand?: boolean
title?: string; title?: string
helpMessage?: Array<any> | string; helpMessage?: Array<any> | string
} }
export interface ScrollContainerOptions { export interface ScrollContainerOptions {
enableScroll?: boolean; enableScroll?: boolean
type?: ScrollType; type?: ScrollType
} }
export type ScrollActionType = RefType<{ export type ScrollActionType = RefType<{
scrollBottom: () => void; scrollBottom: () => void
getScrollWrap: () => Nullable<HTMLElement>; getScrollWrap: () => Nullable<HTMLElement>
scrollTo: (top: number) => void; scrollTo: (top: number) => void
}>; }>

View File

@ -1,3 +0,0 @@
export { createContextMenu, destroyContextMenu } from './src/createContextMenu';
export * from './src/typing';

View File

@ -1,209 +0,0 @@
<script lang="tsx">
import type { ContextMenuItem, ItemContentProps, Axis } from './typing';
import type { FunctionalComponent, CSSProperties, PropType } from 'vue';
import { defineComponent, nextTick, onMounted, computed, ref, unref, onUnmounted } from 'vue';
import Icon from '/@/components/Icon';
import { Menu, Divider } from 'ant-design-vue';
const prefixCls = 'context-menu';
const props = {
width: { type: Number, default: 156 },
customEvent: { type: Object as PropType<Event>, default: null },
styles: { type: Object as PropType<CSSProperties> },
showIcon: { type: Boolean, default: true },
axis: {
// The position of the right mouse button click
type: Object as PropType<Axis>,
default() {
return { x: 0, y: 0 };
},
},
items: {
// The most important list, if not, will not be displayed
type: Array as PropType<ContextMenuItem[]>,
default() {
return [];
},
},
};
const ItemContent: FunctionalComponent<ItemContentProps> = (props) => {
const { item } = props;
return (
<span
style="display: inline-block; width: 100%; "
class="px-4"
onClick={props.handler.bind(null, item)}
>
{props.showIcon && item.icon && <Icon class="mr-2" icon={item.icon} />}
<span>{item.label}</span>
</span>
);
};
export default defineComponent({
name: 'ContextMenu',
props,
setup(props) {
const wrapRef = ref(null);
const showRef = ref(false);
const getStyle = computed((): CSSProperties => {
const { axis, items, styles, width } = props;
const { x, y } = axis || { x: 0, y: 0 };
const menuHeight = (items || []).length * 40;
const menuWidth = width;
const body = document.body;
const left = body.clientWidth < x + menuWidth ? x - menuWidth : x;
const top = body.clientHeight < y + menuHeight ? y - menuHeight : y;
return {
...styles,
position: 'absolute',
width: `${width}px`,
left: `${left + 1}px`,
top: `${top + 1}px`,
zIndex: 9999,
};
});
onMounted(() => {
nextTick(() => (showRef.value = true));
});
onUnmounted(() => {
const el = unref(wrapRef);
el && document.body.removeChild(el);
});
function handleAction(item: ContextMenuItem, e: MouseEvent) {
const { handler, disabled } = item;
if (disabled) {
return;
}
showRef.value = false;
e?.stopPropagation();
e?.preventDefault();
handler?.();
}
function renderMenuItem(items: ContextMenuItem[]) {
const visibleItems = items.filter((item) => !item.hidden);
return visibleItems.map((item) => {
const { disabled, label, children, divider = false } = item;
const contentProps = {
item,
handler: handleAction,
showIcon: props.showIcon,
};
if (!children || children.length === 0) {
return (
<>
<Menu.Item disabled={disabled} class={`${prefixCls}__item`} key={label}>
<ItemContent {...contentProps} />
</Menu.Item>
{divider ? <Divider key={`d-${label}`} /> : null}
</>
);
}
if (!unref(showRef)) return null;
return (
<Menu.SubMenu key={label} disabled={disabled} popupClassName={`${prefixCls}__popup`}>
{{
title: () => <ItemContent {...contentProps} />,
default: () => renderMenuItem(children),
}}
</Menu.SubMenu>
);
});
}
return () => {
if (!unref(showRef)) {
return null;
}
const { items } = props;
return (
<div class={prefixCls}>
<Menu inlineIndent={12} mode="vertical" ref={wrapRef} style={unref(getStyle)}>
{renderMenuItem(items)}
</Menu>
</div>
);
};
},
});
</script>
<style lang="less">
@default-height: 42px !important;
@small-height: 36px !important;
@large-height: 36px !important;
.item-style() {
li {
display: inline-block;
width: 100%;
height: @default-height;
margin: 0 !important;
line-height: @default-height;
span {
line-height: @default-height;
}
> div {
margin: 0 !important;
}
&:not(.ant-menu-item-disabled):hover {
color: @text-color-base;
background-color: @item-hover-bg;
}
}
}
.context-menu {
position: fixed;
top: 0;
left: 0;
z-index: 200;
display: block;
width: 156px;
margin: 0;
list-style: none;
background-color: @component-background;
border: 1px solid rgb(0 0 0 / 8%);
border-radius: 0.25rem;
box-shadow: 0 2px 2px 0 rgb(0 0 0 / 14%), 0 3px 1px -2px rgb(0 0 0 / 10%),
0 1px 5px 0 rgb(0 0 0 / 6%);
background-clip: padding-box;
user-select: none;
&__item {
margin: 0 !important;
}
.item-style();
.ant-divider {
margin: 0;
}
&__popup {
.ant-divider {
margin: 0;
}
.item-style();
}
.ant-menu-submenu-title,
.ant-menu-item {
padding: 0 !important;
}
}
</style>

View File

@ -1,75 +0,0 @@
import contextMenuVue from './ContextMenu.vue';
import { isClient } from '/@/utils/is';
import { CreateContextOptions, ContextMenuProps } from './typing';
import { createVNode, render } from 'vue';
const menuManager: {
domList: Element[];
resolve: Fn;
} = {
domList: [],
resolve: () => {},
};
export const createContextMenu = function (options: CreateContextOptions) {
const { event } = options || {};
event && event?.preventDefault();
if (!isClient) {
return;
}
return new Promise((resolve) => {
const body = document.body;
const container = document.createElement('div');
const propsData: Partial<ContextMenuProps> = {};
if (options.styles) {
propsData.styles = options.styles;
}
if (options.items) {
propsData.items = options.items;
}
if (options.event) {
propsData.customEvent = event;
propsData.axis = { x: event.clientX, y: event.clientY };
}
const vm = createVNode(contextMenuVue, propsData);
render(vm, container);
const handleClick = function () {
menuManager.resolve('');
};
menuManager.domList.push(container);
const remove = function () {
menuManager.domList.forEach((dom: Element) => {
try {
dom && body.removeChild(dom);
} catch (error) {}
});
body.removeEventListener('click', handleClick);
body.removeEventListener('scroll', handleClick);
};
menuManager.resolve = function (arg) {
remove();
resolve(arg);
};
remove();
body.appendChild(container);
body.addEventListener('click', handleClick);
body.addEventListener('scroll', handleClick);
});
};
export const destroyContextMenu = function () {
if (menuManager) {
menuManager.resolve('');
menuManager.domList = [];
}
};

View File

@ -1,36 +0,0 @@
export interface Axis {
x: number;
y: number;
}
export interface ContextMenuItem {
label: string;
icon?: string;
hidden?: boolean;
disabled?: boolean;
handler?: Fn;
divider?: boolean;
children?: ContextMenuItem[];
}
export interface CreateContextOptions {
event: MouseEvent;
icon?: string;
styles?: any;
items?: ContextMenuItem[];
}
export interface ContextMenuProps {
event?: MouseEvent;
styles?: any;
items: ContextMenuItem[];
customEvent?: MouseEvent;
axis?: Axis;
width?: number;
showIcon?: boolean;
}
export interface ItemContentProps {
showIcon: boolean | undefined;
item: ContextMenuItem;
handler: Fn;
}

View File

@ -1,6 +1,6 @@
import { withInstall } from '/@/utils'; import { withInstall } from '/@/utils'
import countButton from './src/CountButton.vue'; import countButton from './src/CountButton.vue'
import countdownInput from './src/CountdownInput.vue'; import countdownInput from './src/CountdownInput.vue'
export const CountdownInput = withInstall(countdownInput); export const CountdownInput = withInstall(countdownInput)
export const CountButton = withInstall(countButton); export const CountButton = withInstall(countButton)

View File

@ -4,11 +4,11 @@
</Button> </Button>
</template> </template>
<script lang="ts"> <script lang="ts">
import { defineComponent, ref, watchEffect, computed, unref } from 'vue'; import { defineComponent, ref, watchEffect, computed, unref } from 'vue'
import { Button } from 'ant-design-vue'; import { Button } from 'ant-design-vue'
import { useCountdown } from './useCountdown'; import { useCountdown } from './useCountdown'
import { isFunction } from '/@/utils/is'; import { isFunction } from '/@/utils/is'
import { useI18n } from '/@/hooks/web/useI18n'; import { useI18n } from '/@/hooks/web/useI18n'
const props = { const props = {
value: { type: [Object, Number, String, Array] }, value: { type: [Object, Number, String, Array] },
@ -17,46 +17,46 @@
type: Function as PropType<() => Promise<boolean>>, type: Function as PropType<() => Promise<boolean>>,
default: null, default: null,
}, },
}; }
export default defineComponent({ export default defineComponent({
name: 'CountButton', name: 'CountButton',
components: { Button }, components: { Button },
props, props,
setup(props) { setup(props) {
const loading = ref(false); const loading = ref(false)
const { currentCount, isStart, start, reset } = useCountdown(props.count); const { currentCount, isStart, start, reset } = useCountdown(props.count)
const { t } = useI18n(); const { t } = useI18n()
const getButtonText = computed(() => { const getButtonText = computed(() => {
return !unref(isStart) return !unref(isStart)
? t('component.countdown.normalText') ? t('component.countdown.normalText')
: t('component.countdown.sendText', [unref(currentCount)]); : t('component.countdown.sendText', [unref(currentCount)])
}); })
watchEffect(() => { watchEffect(() => {
props.value === undefined && reset(); props.value === undefined && reset()
}); })
/** /**
* @description: Judge whether there is an external function before execution, and decide whether to start after execution * @description: Judge whether there is an external function before execution, and decide whether to start after execution
*/ */
async function handleStart() { async function handleStart() {
const { beforeStartFunc } = props; const { beforeStartFunc } = props
if (beforeStartFunc && isFunction(beforeStartFunc)) { if (beforeStartFunc && isFunction(beforeStartFunc)) {
loading.value = true; loading.value = true
try { try {
const canStart = await beforeStartFunc(); const canStart = await beforeStartFunc()
canStart && start(); canStart && start()
} finally { } finally {
loading.value = false; loading.value = false
} }
} else { } else {
start(); start()
} }
} }
return { handleStart, currentCount, loading, getButtonText, isStart }; return { handleStart, currentCount, loading, getButtonText, isStart }
}, },
}); })
</script> </script>

View File

@ -9,10 +9,10 @@
</a-input> </a-input>
</template> </template>
<script lang="ts"> <script lang="ts">
import { defineComponent, PropType } from 'vue'; import { defineComponent, PropType } from 'vue'
import CountButton from './CountButton.vue'; import CountButton from './CountButton.vue'
import { useDesign } from '/@/hooks/web/useDesign'; import { useDesign } from '/@/hooks/web/useDesign'
import { useRuleFormItem } from '/@/hooks/component/useFormItem'; import { useRuleFormItem } from '/@/hooks/component/useFormItem'
const props = { const props = {
value: { type: String }, value: { type: String },
@ -22,7 +22,7 @@
type: Function as PropType<() => Promise<boolean>>, type: Function as PropType<() => Promise<boolean>>,
default: null, default: null,
}, },
}; }
export default defineComponent({ export default defineComponent({
name: 'CountDownInput', name: 'CountDownInput',
@ -30,12 +30,12 @@
inheritAttrs: false, inheritAttrs: false,
props, props,
setup(props) { setup(props) {
const { prefixCls } = useDesign('countdown-input'); const { prefixCls } = useDesign('countdown-input')
const [state] = useRuleFormItem(props); const [state] = useRuleFormItem(props)
return { prefixCls, state }; return { prefixCls, state }
}, },
}); })
</script> </script>
<style lang="less"> <style lang="less">
@prefix-cls: ~'@{namespace}-countdown-input'; @prefix-cls: ~'@{namespace}-countdown-input';

View File

@ -1,51 +1,51 @@
import { ref, unref } from 'vue'; import { ref, unref } from 'vue'
import { tryOnUnmounted } from '@vueuse/core'; import { tryOnUnmounted } from '@vueuse/core'
export function useCountdown(count: number) { export function useCountdown(count: number) {
const currentCount = ref(count); const currentCount = ref(count)
const isStart = ref(false); const isStart = ref(false)
let timerId: ReturnType<typeof setInterval> | null; let timerId: ReturnType<typeof setInterval> | null
function clear() { function clear() {
timerId && window.clearInterval(timerId); timerId && window.clearInterval(timerId)
} }
function stop() { function stop() {
isStart.value = false; isStart.value = false
clear(); clear()
timerId = null; timerId = null
} }
function start() { function start() {
if (unref(isStart) || !!timerId) { if (unref(isStart) || !!timerId) {
return; return
} }
isStart.value = true; isStart.value = true
timerId = setInterval(() => { timerId = setInterval(() => {
if (unref(currentCount) === 1) { if (unref(currentCount) === 1) {
stop(); stop()
currentCount.value = count; currentCount.value = count
} else { } else {
currentCount.value -= 1; currentCount.value -= 1
} }
}, 1000); }, 1000)
} }
function reset() { function reset() {
currentCount.value = count; currentCount.value = count
stop(); stop()
} }
function restart() { function restart() {
reset(); reset()
start(); start()
} }
tryOnUnmounted(() => { tryOnUnmounted(() => {
reset(); reset()
}); })
return { start, reset, restart, clear, stop, currentCount, isStart }; return { start, reset, restart, clear, stop, currentCount, isStart }
} }

View File

@ -1,4 +0,0 @@
import { withInstall } from '/@/utils';
import countTo from './src/CountTo.vue';
export const CountTo = withInstall(countTo);

View File

@ -1,110 +0,0 @@
<template>
<span :style="{ color }">
{{ value }}
</span>
</template>
<script lang="ts">
import { defineComponent, ref, computed, watchEffect, unref, onMounted, watch } from 'vue';
import { useTransition, TransitionPresets } from '@vueuse/core';
import { isNumber } from '/@/utils/is';
const props = {
startVal: { type: Number, default: 0 },
endVal: { type: Number, default: 2021 },
duration: { type: Number, default: 1500 },
autoplay: { type: Boolean, default: true },
decimals: {
type: Number,
default: 0,
validator(value: number) {
return value >= 0;
},
},
prefix: { type: String, default: '' },
suffix: { type: String, default: '' },
separator: { type: String, default: ',' },
decimal: { type: String, default: '.' },
/**
* font color
*/
color: { type: String },
/**
* Turn on digital animation
*/
useEasing: { type: Boolean, default: true },
/**
* Digital animation
*/
transition: { type: String, default: 'linear' },
};
export default defineComponent({
name: 'CountTo',
props,
emits: ['onStarted', 'onFinished'],
setup(props, { emit }) {
const source = ref(props.startVal);
const disabled = ref(false);
let outputValue = useTransition(source);
const value = computed(() => formatNumber(unref(outputValue)));
watchEffect(() => {
source.value = props.startVal;
});
watch([() => props.startVal, () => props.endVal], () => {
if (props.autoplay) {
start();
}
});
onMounted(() => {
props.autoplay && start();
});
function start() {
run();
source.value = props.endVal;
}
function reset() {
source.value = props.startVal;
run();
}
function run() {
outputValue = useTransition(source, {
disabled,
duration: props.duration,
onFinished: () => emit('onFinished'),
onStarted: () => emit('onStarted'),
...(props.useEasing ? { transition: TransitionPresets[props.transition] } : {}),
});
}
function formatNumber(num: number | string) {
if (!num && num !== 0) {
return '';
}
const { decimals, decimal, separator, suffix, prefix } = props;
num = Number(num).toFixed(decimals);
num += '';
const x = num.split('.');
let x1 = x[0];
const x2 = x.length > 1 ? decimal + x[1] : '';
const rgx = /(\d+)(\d{3})/;
if (separator && !isNumber(separator)) {
while (rgx.test(x1)) {
x1 = x1.replace(rgx, '$1' + separator + '$2');
}
}
return prefix + x1 + x2 + suffix;
}
return { value, start, reset };
},
});
</script>

View File

@ -1,7 +0,0 @@
import { withInstall } from '/@/utils';
import cropperImage from './src/Cropper.vue';
import avatarCropper from './src/CropperAvatar.vue';
export * from './src/typing';
export const CropperImage = withInstall(cropperImage);
export const CropperAvatar = withInstall(avatarCropper);

View File

@ -1,283 +0,0 @@
<template>
<BasicModal
v-bind="$attrs"
@register="register"
:title="t('component.cropper.modalTitle')"
width="800px"
:canFullscreen="false"
@ok="handleOk"
:okText="t('component.cropper.okText')"
>
<div :class="prefixCls">
<div :class="`${prefixCls}-left`">
<div :class="`${prefixCls}-cropper`">
<CropperImage
v-if="src"
:src="src"
height="300px"
:circled="circled"
@cropend="handleCropend"
@ready="handleReady"
/>
</div>
<div :class="`${prefixCls}-toolbar`">
<Upload :fileList="[]" accept="image/*" :beforeUpload="handleBeforeUpload">
<Tooltip :title="t('component.cropper.selectImage')" placement="bottom">
<a-button size="small" preIcon="ant-design:upload-outlined" type="primary" />
</Tooltip>
</Upload>
<Space>
<Tooltip :title="t('component.cropper.btn_reset')" placement="bottom">
<a-button
type="primary"
preIcon="ant-design:reload-outlined"
size="small"
:disabled="!src"
@click="handlerToolbar('reset')"
/>
</Tooltip>
<Tooltip :title="t('component.cropper.btn_rotate_left')" placement="bottom">
<a-button
type="primary"
preIcon="ant-design:rotate-left-outlined"
size="small"
:disabled="!src"
@click="handlerToolbar('rotate', -45)"
/>
</Tooltip>
<Tooltip :title="t('component.cropper.btn_rotate_right')" placement="bottom">
<a-button
type="primary"
preIcon="ant-design:rotate-right-outlined"
size="small"
:disabled="!src"
@click="handlerToolbar('rotate', 45)"
/>
</Tooltip>
<Tooltip :title="t('component.cropper.btn_scale_x')" placement="bottom">
<a-button
type="primary"
preIcon="vaadin:arrows-long-h"
size="small"
:disabled="!src"
@click="handlerToolbar('scaleX')"
/>
</Tooltip>
<Tooltip :title="t('component.cropper.btn_scale_y')" placement="bottom">
<a-button
type="primary"
preIcon="vaadin:arrows-long-v"
size="small"
:disabled="!src"
@click="handlerToolbar('scaleY')"
/>
</Tooltip>
<Tooltip :title="t('component.cropper.btn_zoom_in')" placement="bottom">
<a-button
type="primary"
preIcon="ant-design:zoom-in-outlined"
size="small"
:disabled="!src"
@click="handlerToolbar('zoom', 0.1)"
/>
</Tooltip>
<Tooltip :title="t('component.cropper.btn_zoom_out')" placement="bottom">
<a-button
type="primary"
preIcon="ant-design:zoom-out-outlined"
size="small"
:disabled="!src"
@click="handlerToolbar('zoom', -0.1)"
/>
</Tooltip>
</Space>
</div>
</div>
<div :class="`${prefixCls}-right`">
<div :class="`${prefixCls}-preview`">
<img :src="previewSource" v-if="previewSource" :alt="t('component.cropper.preview')" />
</div>
<template v-if="previewSource">
<div :class="`${prefixCls}-group`">
<Avatar :src="previewSource" size="large" />
<Avatar :src="previewSource" :size="48" />
<Avatar :src="previewSource" :size="64" />
<Avatar :src="previewSource" :size="80" />
</div>
</template>
</div>
</div>
</BasicModal>
</template>
<script lang="ts">
import type { CropendResult, Cropper } from './typing';
import { defineComponent, ref } from 'vue';
import CropperImage from './Cropper.vue';
import { Space, Upload, Avatar, Tooltip } from 'ant-design-vue';
import { useDesign } from '/@/hooks/web/useDesign';
import { BasicModal, useModalInner } from '/@/components/Modal';
import { dataURLtoBlob } from '/@/utils/file/base64Conver';
import { isFunction } from '/@/utils/is';
import { useI18n } from '/@/hooks/web/useI18n';
type apiFunParams = { file: Blob; name: string; filename: string };
const props = {
circled: { type: Boolean, default: true },
uploadApi: {
type: Function as PropType<(params: apiFunParams) => Promise<any>>,
},
};
export default defineComponent({
name: 'CropperModal',
components: { BasicModal, Space, CropperImage, Upload, Avatar, Tooltip },
props,
emits: ['uploadSuccess', 'register'],
setup(props, { emit }) {
let filename = '';
const src = ref('');
const previewSource = ref('');
const cropper = ref<Cropper>();
let scaleX = 1;
let scaleY = 1;
const { prefixCls } = useDesign('cropper-am');
const [register, { closeModal, setModalProps }] = useModalInner();
const { t } = useI18n();
// Block upload
function handleBeforeUpload(file: File) {
const reader = new FileReader();
reader.readAsDataURL(file);
src.value = '';
previewSource.value = '';
reader.onload = function (e) {
src.value = (e.target?.result as string) ?? '';
filename = file.name;
};
return false;
}
function handleCropend({ imgBase64 }: CropendResult) {
previewSource.value = imgBase64;
}
function handleReady(cropperInstance: Cropper) {
cropper.value = cropperInstance;
}
function handlerToolbar(event: string, arg?: number) {
if (event === 'scaleX') {
scaleX = arg = scaleX === -1 ? 1 : -1;
}
if (event === 'scaleY') {
scaleY = arg = scaleY === -1 ? 1 : -1;
}
cropper?.value?.[event]?.(arg);
}
async function handleOk() {
const uploadApi = props.uploadApi;
if (uploadApi && isFunction(uploadApi)) {
const blob = dataURLtoBlob(previewSource.value);
try {
setModalProps({ confirmLoading: true });
const result = await uploadApi({ name: 'file', file: blob, filename });
emit('uploadSuccess', { source: previewSource.value, data: result.data });
closeModal();
} finally {
setModalProps({ confirmLoading: false });
}
}
}
return {
t,
prefixCls,
src,
register,
previewSource,
handleBeforeUpload,
handleCropend,
handleReady,
handlerToolbar,
handleOk,
};
},
});
</script>
<style lang="less">
@prefix-cls: ~'@{namespace}-cropper-am';
.@{prefix-cls} {
display: flex;
&-left,
&-right {
height: 340px;
}
&-left {
width: 55%;
}
&-right {
width: 45%;
}
&-cropper {
height: 300px;
background: #eee;
background-image: linear-gradient(
45deg,
rgb(0 0 0 / 25%) 25%,
transparent 0,
transparent 75%,
rgb(0 0 0 / 25%) 0
),
linear-gradient(
45deg,
rgb(0 0 0 / 25%) 25%,
transparent 0,
transparent 75%,
rgb(0 0 0 / 25%) 0
);
background-position: 0 0, 12px 12px;
background-size: 24px 24px;
}
&-toolbar {
display: flex;
justify-content: space-between;
align-items: center;
margin-top: 10px;
}
&-preview {
width: 220px;
height: 220px;
margin: 0 auto;
overflow: hidden;
border: 1px solid @border-color-base;
border-radius: 50%;
img {
width: 100%;
height: 100%;
}
}
&-group {
display: flex;
padding-top: 8px;
margin-top: 8px;
border-top: 1px solid @border-color-base;
justify-content: space-around;
align-items: center;
}
}
</style>

View File

@ -1,188 +0,0 @@
<template>
<div :class="getClass" :style="getWrapperStyle">
<img
v-show="isReady"
ref="imgElRef"
:src="src"
:alt="alt"
:crossorigin="crossorigin"
:style="getImageStyle"
/>
</div>
</template>
<script lang="ts">
import type { CSSProperties } from 'vue';
import { defineComponent, onMounted, ref, unref, computed, onUnmounted } from 'vue';
import Cropper from 'cropperjs';
import 'cropperjs/dist/cropper.css';
import { useDesign } from '/@/hooks/web/useDesign';
import { useDebounceFn } from '@vueuse/shared';
type Options = Cropper.Options;
const defaultOptions: Options = {
aspectRatio: 1,
zoomable: true,
zoomOnTouch: true,
zoomOnWheel: true,
cropBoxMovable: true,
cropBoxResizable: true,
toggleDragModeOnDblclick: true,
autoCrop: true,
background: true,
highlight: true,
center: true,
responsive: true,
restore: true,
checkCrossOrigin: true,
checkOrientation: true,
scalable: true,
modal: true,
guides: true,
movable: true,
rotatable: true,
};
const props = {
src: { type: String, required: true },
alt: { type: String },
circled: { type: Boolean, default: false },
realTimePreview: { type: Boolean, default: true },
height: { type: [String, Number], default: '360px' },
crossorigin: {
type: String as PropType<'' | 'anonymous' | 'use-credentials' | undefined>,
default: undefined,
},
imageStyle: { type: Object as PropType<CSSProperties>, default: () => ({}) },
options: { type: Object as PropType<Options>, default: () => ({}) },
};
export default defineComponent({
name: 'CropperImage',
props,
emits: ['cropend', 'ready', 'cropendError'],
setup(props, { attrs, emit }) {
const imgElRef = ref<ElRef<HTMLImageElement>>();
const cropper = ref<Nullable<Cropper>>();
const isReady = ref(false);
const { prefixCls } = useDesign('cropper-image');
const debounceRealTimeCroppered = useDebounceFn(realTimeCroppered, 80);
const getImageStyle = computed((): CSSProperties => {
return {
height: props.height,
maxWidth: '100%',
...props.imageStyle,
};
});
const getClass = computed(() => {
return [
prefixCls,
attrs.class,
{
[`${prefixCls}--circled`]: props.circled,
},
];
});
const getWrapperStyle = computed((): CSSProperties => {
return { height: `${props.height}`.replace(/px/, '') + 'px' };
});
onMounted(init);
onUnmounted(() => {
cropper.value?.destroy();
});
async function init() {
const imgEl = unref(imgElRef);
if (!imgEl) {
return;
}
cropper.value = new Cropper(imgEl, {
...defaultOptions,
ready: () => {
isReady.value = true;
realTimeCroppered();
emit('ready', cropper.value);
},
crop() {
debounceRealTimeCroppered();
},
zoom() {
debounceRealTimeCroppered();
},
cropmove() {
debounceRealTimeCroppered();
},
...props.options,
});
}
// Real-time display preview
function realTimeCroppered() {
props.realTimePreview && croppered();
}
// event: return base64 and width and height information after cropping
function croppered() {
if (!cropper.value) {
return;
}
let imgInfo = cropper.value.getData();
const canvas = props.circled ? getRoundedCanvas() : cropper.value.getCroppedCanvas();
canvas.toBlob((blob) => {
if (!blob) {
return;
}
let fileReader: FileReader = new FileReader();
fileReader.readAsDataURL(blob);
fileReader.onloadend = (e) => {
emit('cropend', {
imgBase64: e.target?.result ?? '',
imgInfo,
});
};
fileReader.onerror = () => {
emit('cropendError');
};
}, 'image/png');
}
// Get a circular picture canvas
function getRoundedCanvas() {
const sourceCanvas = cropper.value!.getCroppedCanvas();
const canvas = document.createElement('canvas');
const context = canvas.getContext('2d')!;
const width = sourceCanvas.width;
const height = sourceCanvas.height;
canvas.width = width;
canvas.height = height;
context.imageSmoothingEnabled = true;
context.drawImage(sourceCanvas, 0, 0, width, height);
context.globalCompositeOperation = 'destination-in';
context.beginPath();
context.arc(width / 2, height / 2, Math.min(width, height) / 2, 0, 2 * Math.PI, true);
context.fill();
return canvas;
}
return { getClass, imgElRef, getWrapperStyle, getImageStyle, isReady, croppered };
},
});
</script>
<style lang="less">
@prefix-cls: ~'@{namespace}-cropper-image';
.@{prefix-cls} {
&--circled {
.cropper-view-box,
.cropper-face {
border-radius: 50%;
}
}
}
</style>

View File

@ -1,161 +0,0 @@
<template>
<div :class="getClass" :style="getStyle">
<div :class="`${prefixCls}-image-wrapper`" :style="getImageWrapperStyle" @click="openModal">
<div :class="`${prefixCls}-image-mask`" :style="getImageWrapperStyle">
<Icon
icon="ant-design:cloud-upload-outlined"
:size="getIconWidth"
:style="getImageWrapperStyle"
color="#d6d6d6"
/>
</div>
<img :src="sourceValue" v-if="sourceValue" alt="avatar" />
</div>
<a-button
:class="`${prefixCls}-upload-btn`"
@click="openModal"
v-if="showBtn"
v-bind="btnProps"
>
{{ btnText ? btnText : t('component.cropper.selectImage') }}
</a-button>
<CopperModal
@register="register"
@upload-success="handleUploadSuccess"
:uploadApi="uploadApi"
:src="sourceValue"
/>
</div>
</template>
<script lang="ts">
import {
defineComponent,
computed,
CSSProperties,
unref,
ref,
watchEffect,
watch,
PropType,
} from 'vue';
import CopperModal from './CopperModal.vue';
import { useDesign } from '/@/hooks/web/useDesign';
import { useModal } from '/@/components/Modal';
import { useMessage } from '/@/hooks/web/useMessage';
import { useI18n } from '/@/hooks/web/useI18n';
import type { ButtonProps } from '/@/components/Button';
import Icon from '/@/components/Icon';
const props = {
width: { type: [String, Number], default: '200px' },
value: { type: String },
showBtn: { type: Boolean, default: true },
btnProps: { type: Object as PropType<ButtonProps> },
btnText: { type: String, default: '' },
uploadApi: { type: Function as PropType<({ file: Blob, name: string }) => Promise<void>> },
};
export default defineComponent({
name: 'CropperAvatar',
components: { CopperModal, Icon },
props,
emits: ['update:value', 'change'],
setup(props, { emit, expose }) {
const sourceValue = ref(props.value || '');
const { prefixCls } = useDesign('cropper-avatar');
const [register, { openModal, closeModal }] = useModal();
const { createMessage } = useMessage();
const { t } = useI18n();
const getClass = computed(() => [prefixCls]);
const getWidth = computed(() => `${props.width}`.replace(/px/, '') + 'px');
const getIconWidth = computed(() => parseInt(`${props.width}`.replace(/px/, '')) / 2 + 'px');
const getStyle = computed((): CSSProperties => ({ width: unref(getWidth) }));
const getImageWrapperStyle = computed(
(): CSSProperties => ({ width: unref(getWidth), height: unref(getWidth) }),
);
watchEffect(() => {
sourceValue.value = props.value || '';
});
watch(
() => sourceValue.value,
(v: string) => {
emit('update:value', v);
},
);
function handleUploadSuccess({ source, data }) {
sourceValue.value = source;
emit('change', { source, data });
createMessage.success(t('component.cropper.uploadSuccess'));
}
expose({ openModal: openModal.bind(null, true), closeModal });
return {
t,
prefixCls,
register,
openModal: openModal as any,
getIconWidth,
sourceValue,
getClass,
getImageWrapperStyle,
getStyle,
handleUploadSuccess,
};
},
});
</script>
<style lang="less" scoped>
@prefix-cls: ~'@{namespace}-cropper-avatar';
.@{prefix-cls} {
display: inline-block;
text-align: center;
&-image-wrapper {
overflow: hidden;
cursor: pointer;
background: @component-background;
border: 1px solid @border-color-base;
border-radius: 50%;
img {
width: 100%;
}
}
&-image-mask {
opacity: 0%;
position: absolute;
width: inherit;
height: inherit;
border-radius: inherit;
border: inherit;
background: rgb(0 0 0 / 40%);
cursor: pointer;
transition: opacity 0.4s;
::v-deep(svg) {
margin: auto;
}
}
&-image-mask:hover {
opacity: 4000%;
}
&-upload-btn {
margin: 10px auto;
}
}
</style>

View File

@ -1,8 +0,0 @@
import type Cropper from 'cropperjs';
export interface CropendResult {
imgBase64: string;
imgInfo: Cropper.Data;
}
export type { Cropper };

View File

@ -1,6 +1,6 @@
import { withInstall } from '/@/utils'; import { withInstall } from '/@/utils'
import description from './src/Description.vue'; import description from './src/Description.vue'
export * from './src/typing'; export * from './src/typing'
export { useDescription } from './src/useDescription'; export { useDescription } from './src/useDescription'
export const Description = withInstall(description); export const Description = withInstall(description)

View File

@ -1,16 +1,16 @@
<script lang="tsx"> <script lang="tsx">
import type { DescriptionProps, DescInstance, DescItem } from './typing'; import type { DescriptionProps, DescInstance, DescItem } from './typing'
import type { DescriptionsProps } from 'ant-design-vue/es/descriptions/index'; import type { DescriptionsProps } from 'ant-design-vue/es/descriptions/index'
import type { CSSProperties } from 'vue'; import type { CSSProperties } from 'vue'
import type { CollapseContainerOptions } from '/@/components/Container/index'; import type { CollapseContainerOptions } from '/@/components/Container/index'
import { defineComponent, computed, ref, unref, toRefs } from 'vue'; import { defineComponent, computed, ref, unref, toRefs } from 'vue'
import { get } from 'lodash-es'; import { get } from 'lodash-es'
import { Descriptions } from 'ant-design-vue'; import { Descriptions } from 'ant-design-vue'
import { CollapseContainer } from '/@/components/Container/index'; import { CollapseContainer } from '/@/components/Container/index'
import { useDesign } from '/@/hooks/web/useDesign'; import { useDesign } from '/@/hooks/web/useDesign'
import { isFunction } from '/@/utils/is'; import { isFunction } from '/@/utils/is'
import { getSlot } from '/@/utils/helper/tsxHelper'; import { getSlot } from '/@/utils/helper/tsxHelper'
import { useAttrs } from '/@/hooks/core/useAttrs'; import { useAttrs } from '/@/hooks/core/useAttrs'
const props = { const props = {
useCollapse: { type: Boolean, default: true }, useCollapse: { type: Boolean, default: true },
@ -24,7 +24,7 @@
column: { column: {
type: [Number, Object] as PropType<number | Recordable>, type: [Number, Object] as PropType<number | Recordable>,
default: () => { default: () => {
return { xxl: 4, xl: 3, lg: 3, md: 3, sm: 2, xs: 1 }; return { xxl: 4, xl: 3, lg: 3, md: 3, sm: 2, xs: 1 }
}, },
}, },
collapseOptions: { collapseOptions: {
@ -36,38 +36,38 @@
default: () => [], default: () => [],
}, },
data: { type: Object }, data: { type: Object },
}; }
export default defineComponent({ export default defineComponent({
name: 'Description', name: 'Description',
props, props,
emits: ['register'], emits: ['register'],
setup(props, { slots, emit }) { setup(props, { slots, emit }) {
const propsRef = ref<Partial<DescriptionProps> | null>(null); const propsRef = ref<Partial<DescriptionProps> | null>(null)
const { prefixCls } = useDesign('description'); const { prefixCls } = useDesign('description')
const attrs = useAttrs(); const attrs = useAttrs()
// Custom title component: get title // Custom title component: get title
const getMergeProps = computed(() => { const getMergeProps = computed(() => {
return { return {
...props, ...props,
...(unref(propsRef) as Recordable), ...(unref(propsRef) as Recordable),
} as DescriptionProps; } as DescriptionProps
}); })
const getProps = computed(() => { const getProps = computed(() => {
const opt = { const opt = {
...unref(getMergeProps), ...unref(getMergeProps),
title: undefined, title: undefined,
}; }
return opt as DescriptionProps; return opt as DescriptionProps
}); })
/** /**
* @description: Whether to setting title * @description: Whether to setting title
*/ */
const useWrapper = computed(() => !!unref(getMergeProps).title); const useWrapper = computed(() => !!unref(getMergeProps).title)
/** /**
* @description: Get configuration Collapse * @description: Get configuration Collapse
@ -77,72 +77,72 @@
// Cannot be expanded by default // Cannot be expanded by default
canExpand: false, canExpand: false,
...unref(getProps).collapseOptions, ...unref(getProps).collapseOptions,
}; }
}); })
const getDescriptionsProps = computed(() => { const getDescriptionsProps = computed(() => {
return { ...unref(attrs), ...unref(getProps) } as DescriptionsProps; return { ...unref(attrs), ...unref(getProps) } as DescriptionsProps
}); })
/** /**
* @description:设置desc * @description:设置desc
*/ */
function setDescProps(descProps: Partial<DescriptionProps>): void { function setDescProps(descProps: Partial<DescriptionProps>): void {
// Keep the last setDrawerProps // Keep the last setDrawerProps
propsRef.value = { ...(unref(propsRef) as Recordable), ...descProps } as Recordable; propsRef.value = { ...(unref(propsRef) as Recordable), ...descProps } as Recordable
} }
// Prevent line breaks // Prevent line breaks
function renderLabel({ label, labelMinWidth, labelStyle }: DescItem) { function renderLabel({ label, labelMinWidth, labelStyle }: DescItem) {
if (!labelStyle && !labelMinWidth) { if (!labelStyle && !labelMinWidth) {
return label; return label
} }
const labelStyles: CSSProperties = { const labelStyles: CSSProperties = {
...labelStyle, ...labelStyle,
minWidth: `${labelMinWidth}px `, minWidth: `${labelMinWidth}px `,
}; }
return <div style={labelStyles}>{label}</div>; return <div style={labelStyles}>{label}</div>
} }
function renderItem() { function renderItem() {
const { schema, data } = unref(getProps); const { schema, data } = unref(getProps)
return unref(schema) return unref(schema)
.map((item) => { .map((item) => {
const { render, field, span, show, contentMinWidth } = item; const { render, field, span, show, contentMinWidth } = item
if (show && isFunction(show) && !show(data)) { if (show && isFunction(show) && !show(data)) {
return null; return null
} }
const getContent = () => { const getContent = () => {
const _data = unref(getProps)?.data; const _data = unref(getProps)?.data
if (!_data) { if (!_data) {
return null; return null
} }
const getField = get(_data, field); const getField = get(_data, field)
if (getField && !toRefs(_data).hasOwnProperty(field)) { if (getField && !toRefs(_data).hasOwnProperty(field)) {
return isFunction(render) ? render('', _data) : ''; return isFunction(render) ? render('', _data) : ''
}
return isFunction(render) ? render(getField, _data) : getField ?? ''
} }
return isFunction(render) ? render(getField, _data) : getField ?? '';
};
const width = contentMinWidth; const width = contentMinWidth
return ( return (
<Descriptions.Item label={renderLabel(item)} key={field} span={span}> <Descriptions.Item label={renderLabel(item)} key={field} span={span}>
{() => { {() => {
if (!contentMinWidth) { if (!contentMinWidth) {
return getContent(); return getContent()
} }
const style: CSSProperties = { const style: CSSProperties = {
minWidth: `${width}px`, minWidth: `${width}px`,
}; }
return <div style={style}>{getContent()}</div>; return <div style={style}>{getContent()}</div>
}} }}
</Descriptions.Item> </Descriptions.Item>
); )
}) })
.filter((item) => !!item); .filter((item) => !!item)
} }
const renderDesc = () => { const renderDesc = () => {
@ -150,18 +150,18 @@
<Descriptions class={`${prefixCls}`} {...(unref(getDescriptionsProps) as any)}> <Descriptions class={`${prefixCls}`} {...(unref(getDescriptionsProps) as any)}>
{renderItem()} {renderItem()}
</Descriptions> </Descriptions>
); )
};
const renderContainer = () => {
const content = props.useCollapse ? renderDesc() : <div>{renderDesc()}</div>;
// Reduce the dom level
if (!props.useCollapse) {
return content;
} }
const { canExpand, helpMessage } = unref(getCollapseOptions); const renderContainer = () => {
const { title } = unref(getMergeProps); const content = props.useCollapse ? renderDesc() : <div>{renderDesc()}</div>
// Reduce the dom level
if (!props.useCollapse) {
return content
}
const { canExpand, helpMessage } = unref(getCollapseOptions)
const { title } = unref(getMergeProps)
return ( return (
<CollapseContainer title={title} canExpan={canExpand} helpMessage={helpMessage}> <CollapseContainer title={title} canExpan={canExpand} helpMessage={helpMessage}>
@ -170,15 +170,15 @@
action: () => getSlot(slots, 'action'), action: () => getSlot(slots, 'action'),
}} }}
</CollapseContainer> </CollapseContainer>
); )
}; }
const methods: DescInstance = { const methods: DescInstance = {
setDescProps, setDescProps,
}; }
emit('register', methods); emit('register', methods)
return () => (unref(useWrapper) ? renderContainer() : renderDesc()); return () => (unref(useWrapper) ? renderContainer() : renderDesc())
}, },
}); })
</script> </script>

View File

@ -1,50 +1,50 @@
import type { VNode, CSSProperties } from 'vue'; import type { VNode, CSSProperties } from 'vue'
import type { CollapseContainerOptions } from '/@/components/Container/index'; import type { CollapseContainerOptions } from '/@/components/Container/index'
import type { DescriptionsProps } from 'ant-design-vue/es/descriptions/index'; import type { DescriptionsProps } from 'ant-design-vue/es/descriptions/index'
export interface DescItem { export interface DescItem {
labelMinWidth?: number; labelMinWidth?: number
contentMinWidth?: number; contentMinWidth?: number
labelStyle?: CSSProperties; labelStyle?: CSSProperties
field: string; field: string
label: string | VNode | JSX.Element; label: string | VNode | JSX.Element
// Merge column // Merge column
span?: number; span?: number
show?: (...arg: any) => boolean; show?: (...arg: any) => boolean
// render // render
render?: ( render?: (
val: any, val: any,
data: Recordable, data: Recordable,
) => VNode | undefined | JSX.Element | Element | string | number; ) => VNode | undefined | JSX.Element | Element | string | number
} }
export interface DescriptionProps extends DescriptionsProps { export interface DescriptionProps extends DescriptionsProps {
// Whether to include the collapse component // Whether to include the collapse component
useCollapse?: boolean; useCollapse?: boolean
/** /**
* item configuration * item configuration
* @type DescItem * @type DescItem
*/ */
schema: DescItem[]; schema: DescItem[]
/** /**
* *
* @type object * @type object
*/ */
data: Recordable; data: Recordable
/** /**
* Built-in CollapseContainer component configuration * Built-in CollapseContainer component configuration
* @type CollapseContainerOptions * @type CollapseContainerOptions
*/ */
collapseOptions?: CollapseContainerOptions; collapseOptions?: CollapseContainerOptions
} }
export interface DescInstance { export interface DescInstance {
setDescProps(descProps: Partial<DescriptionProps>): void; setDescProps(descProps: Partial<DescriptionProps>): void
} }
export type Register = (descInstance: DescInstance) => void; export type Register = (descInstance: DescInstance) => void
/** /**
* @description: * @description:
*/ */
export type UseDescReturnType = [Register, DescInstance]; export type UseDescReturnType = [Register, DescInstance]

View File

@ -1,28 +1,28 @@
import type { DescriptionProps, DescInstance, UseDescReturnType } from './typing'; import type { DescriptionProps, DescInstance, UseDescReturnType } from './typing'
import { ref, getCurrentInstance, unref } from 'vue'; import { ref, getCurrentInstance, unref } from 'vue'
import { isProdMode } from '/@/utils/env'; import { isProdMode } from '/@/utils/env'
export function useDescription(props?: Partial<DescriptionProps>): UseDescReturnType { export function useDescription(props?: Partial<DescriptionProps>): UseDescReturnType {
if (!getCurrentInstance()) { if (!getCurrentInstance()) {
throw new Error('useDescription() can only be used inside setup() or functional components!'); throw new Error('useDescription() can only be used inside setup() or functional components!')
} }
const desc = ref<Nullable<DescInstance>>(null); const desc = ref<Nullable<DescInstance>>(null)
const loaded = ref(false); const loaded = ref(false)
function register(instance: DescInstance) { function register(instance: DescInstance) {
if (unref(loaded) && isProdMode()) { if (unref(loaded) && isProdMode()) {
return; return
} }
desc.value = instance; desc.value = instance
props && instance.setDescProps(props); props && instance.setDescProps(props)
loaded.value = true; loaded.value = true
} }
const methods: DescInstance = { const methods: DescInstance = {
setDescProps: (descProps: Partial<DescriptionProps>): void => { setDescProps: (descProps: Partial<DescriptionProps>): void => {
unref(desc)?.setDescProps(descProps); unref(desc)?.setDescProps(descProps)
}, },
}; }
return [register, methods]; return [register, methods]
} }

View File

@ -1,6 +1,6 @@
import { withInstall } from '/@/utils'; import { withInstall } from '/@/utils'
import basicDrawer from './src/BasicDrawer.vue'; import basicDrawer from './src/BasicDrawer.vue'
export const BasicDrawer = withInstall(basicDrawer); export const BasicDrawer = withInstall(basicDrawer)
export * from './src/typing'; export * from './src/typing'
export { useDrawer, useDrawerInner } from './src/useDrawer'; export { useDrawer, useDrawerInner } from './src/useDrawer'

View File

@ -31,8 +31,8 @@
</Drawer> </Drawer>
</template> </template>
<script lang="ts"> <script lang="ts">
import type { DrawerInstance, DrawerProps } from './typing'; import type { DrawerInstance, DrawerProps } from './typing'
import type { CSSProperties } from 'vue'; import type { CSSProperties } from 'vue'
import { import {
defineComponent, defineComponent,
ref, ref,
@ -42,17 +42,17 @@
nextTick, nextTick,
toRaw, toRaw,
getCurrentInstance, getCurrentInstance,
} from 'vue'; } from 'vue'
import { Drawer } from 'ant-design-vue'; import { Drawer } from 'ant-design-vue'
import { useI18n } from '/@/hooks/web/useI18n'; import { useI18n } from '/@/hooks/web/useI18n'
import { isFunction, isNumber } from '/@/utils/is'; import { isFunction, isNumber } from '/@/utils/is'
import { deepMerge } from '/@/utils'; import { deepMerge } from '/@/utils'
import DrawerFooter from './components/DrawerFooter.vue'; import DrawerFooter from './components/DrawerFooter.vue'
import DrawerHeader from './components/DrawerHeader.vue'; import DrawerHeader from './components/DrawerHeader.vue'
import { ScrollContainer } from '/@/components/Container'; import { ScrollContainer } from '/@/components/Container'
import { basicProps } from './props'; import { basicProps } from './props'
import { useDesign } from '/@/hooks/web/useDesign'; import { useDesign } from '/@/hooks/web/useDesign'
import { useAttrs } from '/@/hooks/core/useAttrs'; import { useAttrs } from '/@/hooks/core/useAttrs'
export default defineComponent({ export default defineComponent({
components: { Drawer, ScrollContainer, DrawerFooter, DrawerHeader }, components: { Drawer, ScrollContainer, DrawerFooter, DrawerHeader },
@ -60,25 +60,25 @@
props: basicProps, props: basicProps,
emits: ['visible-change', 'ok', 'close', 'register'], emits: ['visible-change', 'ok', 'close', 'register'],
setup(props, { emit }) { setup(props, { emit }) {
const visibleRef = ref(false); const visibleRef = ref(false)
const attrs = useAttrs(); const attrs = useAttrs()
const propsRef = ref<Partial<Nullable<DrawerProps>>>(null); const propsRef = ref<Partial<Nullable<DrawerProps>>>(null)
const { t } = useI18n(); const { t } = useI18n()
const { prefixVar, prefixCls } = useDesign('basic-drawer'); const { prefixVar, prefixCls } = useDesign('basic-drawer')
const drawerInstance: DrawerInstance = { const drawerInstance: DrawerInstance = {
setDrawerProps: setDrawerProps, setDrawerProps: setDrawerProps,
emitVisible: undefined, emitVisible: undefined,
}; }
const instance = getCurrentInstance(); const instance = getCurrentInstance()
instance && emit('register', drawerInstance, instance.uid); instance && emit('register', drawerInstance, instance.uid)
const getMergeProps = computed((): DrawerProps => { const getMergeProps = computed((): DrawerProps => {
return deepMerge(toRaw(props), unref(propsRef)); return deepMerge(toRaw(props), unref(propsRef))
}); })
const getProps = computed((): DrawerProps => { const getProps = computed((): DrawerProps => {
const opt = { const opt = {
@ -86,95 +86,95 @@
...unref(attrs), ...unref(attrs),
...unref(getMergeProps), ...unref(getMergeProps),
visible: unref(visibleRef), visible: unref(visibleRef),
}; }
opt.title = undefined; opt.title = undefined
const { isDetail, width, wrapClassName, getContainer } = opt; const { isDetail, width, wrapClassName, getContainer } = opt
if (isDetail) { if (isDetail) {
if (!width) { if (!width) {
opt.width = '100%'; opt.width = '100%'
} }
const detailCls = `${prefixCls}__detail`; const detailCls = `${prefixCls}__detail`
opt.class = wrapClassName ? `${wrapClassName} ${detailCls}` : detailCls; opt.class = wrapClassName ? `${wrapClassName} ${detailCls}` : detailCls
if (!getContainer) { if (!getContainer) {
// TODO type error? // TODO type error?
opt.getContainer = `.${prefixVar}-layout-content` as any; opt.getContainer = `.${prefixVar}-layout-content` as any
} }
} }
return opt as DrawerProps; return opt as DrawerProps
}); })
const getBindValues = computed((): DrawerProps => { const getBindValues = computed((): DrawerProps => {
return { return {
...attrs, ...attrs,
...unref(getProps), ...unref(getProps),
}; }
}); })
// Custom implementation of the bottom button, // Custom implementation of the bottom button,
const getFooterHeight = computed(() => { const getFooterHeight = computed(() => {
const { footerHeight, showFooter } = unref(getProps); const { footerHeight, showFooter } = unref(getProps)
if (showFooter && footerHeight) { if (showFooter && footerHeight) {
return isNumber(footerHeight) return isNumber(footerHeight)
? `${footerHeight}px` ? `${footerHeight}px`
: `${footerHeight.replace('px', '')}px`; : `${footerHeight.replace('px', '')}px`
} }
return `0px`; return `0px`
}); })
const getScrollContentStyle = computed((): CSSProperties => { const getScrollContentStyle = computed((): CSSProperties => {
const footerHeight = unref(getFooterHeight); const footerHeight = unref(getFooterHeight)
return { return {
position: 'relative', position: 'relative',
height: `calc(100% - ${footerHeight})`, height: `calc(100% - ${footerHeight})`,
}; }
}); })
const getLoading = computed(() => { const getLoading = computed(() => {
return !!unref(getProps)?.loading; return !!unref(getProps)?.loading
}); })
watch( watch(
() => props.visible, () => props.visible,
(newVal, oldVal) => { (newVal, oldVal) => {
if (newVal !== oldVal) visibleRef.value = newVal; if (newVal !== oldVal) visibleRef.value = newVal
}, },
{ deep: true }, { deep: true },
); )
watch( watch(
() => visibleRef.value, () => visibleRef.value,
(visible) => { (visible) => {
nextTick(() => { nextTick(() => {
emit('visible-change', visible); emit('visible-change', visible)
instance && drawerInstance.emitVisible?.(visible, instance.uid); instance && drawerInstance.emitVisible?.(visible, instance.uid)
}); })
}, },
); )
// Cancel event // Cancel event
async function onClose(e: Recordable) { async function onClose(e: Recordable) {
const { closeFunc } = unref(getProps); const { closeFunc } = unref(getProps)
emit('close', e); emit('close', e)
if (closeFunc && isFunction(closeFunc)) { if (closeFunc && isFunction(closeFunc)) {
const res = await closeFunc(); const res = await closeFunc()
visibleRef.value = !res; visibleRef.value = !res
return; return
} }
visibleRef.value = false; visibleRef.value = false
} }
function setDrawerProps(props: Partial<DrawerProps>): void { function setDrawerProps(props: Partial<DrawerProps>): void {
// Keep the last setDrawerProps // Keep the last setDrawerProps
propsRef.value = deepMerge(unref(propsRef) || ({} as any), props); propsRef.value = deepMerge(unref(propsRef) || ({} as any), props)
if (Reflect.has(props, 'visible')) { if (Reflect.has(props, 'visible')) {
visibleRef.value = !!props.visible; visibleRef.value = !!props.visible
} }
} }
function handleOk() { function handleOk() {
emit('ok'); emit('ok')
} }
return { return {
@ -188,9 +188,9 @@
getBindValues, getBindValues,
getFooterHeight, getFooterHeight,
handleOk, handleOk,
}; }
}, },
}); })
</script> </script>
<style lang="less"> <style lang="less">
@header-height: 60px; @header-height: 60px;

View File

@ -25,11 +25,11 @@
</div> </div>
</template> </template>
<script lang="ts"> <script lang="ts">
import type { CSSProperties } from 'vue'; import type { CSSProperties } from 'vue'
import { defineComponent, computed } from 'vue'; import { defineComponent, computed } from 'vue'
import { useDesign } from '/@/hooks/web/useDesign'; import { useDesign } from '/@/hooks/web/useDesign'
import { footerProps } from '../props'; import { footerProps } from '../props'
export default defineComponent({ export default defineComponent({
name: 'BasicDrawerFooter', name: 'BasicDrawerFooter',
props: { props: {
@ -41,26 +41,26 @@
}, },
emits: ['ok', 'close'], emits: ['ok', 'close'],
setup(props, { emit }) { setup(props, { emit }) {
const { prefixCls } = useDesign('basic-drawer-footer'); const { prefixCls } = useDesign('basic-drawer-footer')
const getStyle = computed((): CSSProperties => { const getStyle = computed((): CSSProperties => {
const heightStr = `${props.height}`; const heightStr = `${props.height}`
return { return {
height: heightStr, height: heightStr,
lineHeight: `calc(${heightStr} - 1px)`, lineHeight: `calc(${heightStr} - 1px)`,
}; }
}); })
function handleOk() { function handleOk() {
emit('ok'); emit('ok')
} }
function handleClose() { function handleClose() {
emit('close'); emit('close')
} }
return { handleOk, prefixCls, handleClose, getStyle }; return { handleOk, prefixCls, handleClose, getStyle }
}, },
}); })
</script> </script>
<style lang="less"> <style lang="less">

View File

@ -18,13 +18,13 @@
</div> </div>
</template> </template>
<script lang="ts"> <script lang="ts">
import { defineComponent } from 'vue'; import { defineComponent } from 'vue'
import { BasicTitle } from '/@/components/Basic'; import { BasicTitle } from '/@/components/Basic'
import { ArrowLeftOutlined } from '@ant-design/icons-vue'; import { ArrowLeftOutlined } from '@ant-design/icons-vue'
import { useDesign } from '/@/hooks/web/useDesign'; import { useDesign } from '/@/hooks/web/useDesign'
import { propTypes } from '/@/utils/propTypes'; import { propTypes } from '/@/utils/propTypes'
export default defineComponent({ export default defineComponent({
name: 'BasicDrawerHeader', name: 'BasicDrawerHeader',
components: { BasicTitle, ArrowLeftOutlined }, components: { BasicTitle, ArrowLeftOutlined },
@ -35,15 +35,15 @@
}, },
emits: ['close'], emits: ['close'],
setup(_, { emit }) { setup(_, { emit }) {
const { prefixCls } = useDesign('basic-drawer-header'); const { prefixCls } = useDesign('basic-drawer-header')
function handleClose() { function handleClose() {
emit('close'); emit('close')
} }
return { prefixCls, handleClose }; return { prefixCls, handleClose }
}, },
}); })
</script> </script>
<style lang="less"> <style lang="less">

View File

@ -1,7 +1,7 @@
import type { PropType } from 'vue'; import type { PropType } from 'vue'
import { useI18n } from '/@/hooks/web/useI18n'; import { useI18n } from '/@/hooks/web/useI18n'
const { t } = useI18n(); const { t } = useI18n()
export const footerProps = { export const footerProps = {
confirmLoading: { type: Boolean }, confirmLoading: { type: Boolean },
@ -23,7 +23,7 @@ export const footerProps = {
type: [String, Number] as PropType<string | number>, type: [String, Number] as PropType<string | number>,
default: 60, default: 60,
}, },
}; }
export const basicProps = { export const basicProps = {
isDetail: { type: Boolean }, isDetail: { type: Boolean },
title: { type: String, default: '' }, title: { type: String, default: '' },
@ -41,4 +41,4 @@ export const basicProps = {
}, },
destroyOnClose: { type: Boolean }, destroyOnClose: { type: Boolean },
...footerProps, ...footerProps,
}; }

View File

@ -1,193 +1,193 @@
import type { ButtonProps } from 'ant-design-vue/lib/button/buttonTypes'; import type { ButtonProps } from 'ant-design-vue/lib/button/buttonTypes'
import type { CSSProperties, VNodeChild, ComputedRef } from 'vue'; import type { CSSProperties, VNodeChild, ComputedRef } from 'vue'
import type { ScrollContainerOptions } from '/@/components/Container/index'; import type { ScrollContainerOptions } from '/@/components/Container/index'
export interface DrawerInstance { export interface DrawerInstance {
setDrawerProps: (props: Partial<DrawerProps> | boolean) => void; setDrawerProps: (props: Partial<DrawerProps> | boolean) => void
emitVisible?: (visible: boolean, uid: number) => void; emitVisible?: (visible: boolean, uid: number) => void
} }
export interface ReturnMethods extends DrawerInstance { export interface ReturnMethods extends DrawerInstance {
openDrawer: <T = any>(visible?: boolean, data?: T, openOnSet?: boolean) => void; openDrawer: <T = any>(visible?: boolean, data?: T, openOnSet?: boolean) => void
closeDrawer: () => void; closeDrawer: () => void
getVisible?: ComputedRef<boolean>; getVisible?: ComputedRef<boolean>
} }
export type RegisterFn = (drawerInstance: DrawerInstance, uuid?: string) => void; export type RegisterFn = (drawerInstance: DrawerInstance, uuid?: string) => void
export interface ReturnInnerMethods extends DrawerInstance { export interface ReturnInnerMethods extends DrawerInstance {
closeDrawer: () => void; closeDrawer: () => void
changeLoading: (loading: boolean) => void; changeLoading: (loading: boolean) => void
changeOkLoading: (loading: boolean) => void; changeOkLoading: (loading: boolean) => void
getVisible?: ComputedRef<boolean>; getVisible?: ComputedRef<boolean>
} }
export type UseDrawerReturnType = [RegisterFn, ReturnMethods]; export type UseDrawerReturnType = [RegisterFn, ReturnMethods]
export type UseDrawerInnerReturnType = [RegisterFn, ReturnInnerMethods]; export type UseDrawerInnerReturnType = [RegisterFn, ReturnInnerMethods]
export interface DrawerFooterProps { export interface DrawerFooterProps {
showOkBtn: boolean; showOkBtn: boolean
showCancelBtn: boolean; showCancelBtn: boolean
/** /**
* Text of the Cancel button * Text of the Cancel button
* @default 'cancel' * @default 'cancel'
* @type string * @type string
*/ */
cancelText: string; cancelText: string
/** /**
* Text of the OK button * Text of the OK button
* @default 'OK' * @default 'OK'
* @type string * @type string
*/ */
okText: string; okText: string
/** /**
* Button type of the OK button * Button type of the OK button
* @default 'primary' * @default 'primary'
* @type string * @type string
*/ */
okType: 'primary' | 'danger' | 'dashed' | 'ghost' | 'default'; okType: 'primary' | 'danger' | 'dashed' | 'ghost' | 'default'
/** /**
* The ok button props, follow jsx rules * The ok button props, follow jsx rules
* @type object * @type object
*/ */
okButtonProps: { props: ButtonProps; on: {} }; okButtonProps: { props: ButtonProps; on: {} }
/** /**
* The cancel button props, follow jsx rules * The cancel button props, follow jsx rules
* @type object * @type object
*/ */
cancelButtonProps: { props: ButtonProps; on: {} }; cancelButtonProps: { props: ButtonProps; on: {} }
/** /**
* Whether to apply loading visual effect for OK button or not * Whether to apply loading visual effect for OK button or not
* @default false * @default false
* @type boolean * @type boolean
*/ */
confirmLoading: boolean; confirmLoading: boolean
showFooter: boolean; showFooter: boolean
footerHeight: string | number; footerHeight: string | number
} }
export interface DrawerProps extends DrawerFooterProps { export interface DrawerProps extends DrawerFooterProps {
isDetail?: boolean; isDetail?: boolean
loading?: boolean; loading?: boolean
showDetailBack?: boolean; showDetailBack?: boolean
visible?: boolean; visible?: boolean
/** /**
* Built-in ScrollContainer component configuration * Built-in ScrollContainer component configuration
* @type ScrollContainerOptions * @type ScrollContainerOptions
*/ */
scrollOptions?: ScrollContainerOptions; scrollOptions?: ScrollContainerOptions
closeFunc?: () => Promise<any>; closeFunc?: () => Promise<any>
triggerWindowResize?: boolean; triggerWindowResize?: boolean
/** /**
* Whether a close (x) button is visible on top right of the Drawer dialog or not. * Whether a close (x) button is visible on top right of the Drawer dialog or not.
* @default true * @default true
* @type boolean * @type boolean
*/ */
closable?: boolean; closable?: boolean
/** /**
* Whether to unmount child components on closing drawer or not. * Whether to unmount child components on closing drawer or not.
* @default false * @default false
* @type boolean * @type boolean
*/ */
destroyOnClose?: boolean; destroyOnClose?: boolean
/** /**
* Return the mounted node for Drawer. * Return the mounted node for Drawer.
* @default 'body' * @default 'body'
* @type any ( HTMLElement| () => HTMLElement | string) * @type any ( HTMLElement| () => HTMLElement | string)
*/ */
getContainer?: () => HTMLElement | string; getContainer?: () => HTMLElement | string
/** /**
* Whether to show mask or not. * Whether to show mask or not.
* @default true * @default true
* @type boolean * @type boolean
*/ */
mask?: boolean; mask?: boolean
/** /**
* Clicking on the mask (area outside the Drawer) to close the Drawer or not. * Clicking on the mask (area outside the Drawer) to close the Drawer or not.
* @default true * @default true
* @type boolean * @type boolean
*/ */
maskClosable?: boolean; maskClosable?: boolean
/** /**
* Style for Drawer's mask element. * Style for Drawer's mask element.
* @default {} * @default {}
* @type object * @type object
*/ */
maskStyle?: CSSProperties; maskStyle?: CSSProperties
/** /**
* The title for Drawer. * The title for Drawer.
* @type any (string | slot) * @type any (string | slot)
*/ */
title?: VNodeChild | JSX.Element; title?: VNodeChild | JSX.Element
/** /**
* The class name of the container of the Drawer dialog. * The class name of the container of the Drawer dialog.
* @type string * @type string
*/ */
wrapClassName?: string; wrapClassName?: string
class?: string; class?: string
/** /**
* Style of wrapper element which **contains mask** compare to `drawerStyle` * Style of wrapper element which **contains mask** compare to `drawerStyle`
* @type object * @type object
*/ */
wrapStyle?: CSSProperties; wrapStyle?: CSSProperties
/** /**
* Style of the popup layer element * Style of the popup layer element
* @type object * @type object
*/ */
drawerStyle?: CSSProperties; drawerStyle?: CSSProperties
/** /**
* Style of floating layer, typically used for adjusting its position. * Style of floating layer, typically used for adjusting its position.
* @type object * @type object
*/ */
bodyStyle?: CSSProperties; bodyStyle?: CSSProperties
headerStyle?: CSSProperties; headerStyle?: CSSProperties
/** /**
* Width of the Drawer dialog. * Width of the Drawer dialog.
* @default 256 * @default 256
* @type string | number * @type string | number
*/ */
width?: string | number; width?: string | number
/** /**
* placement is top or bottom, height of the Drawer dialog. * placement is top or bottom, height of the Drawer dialog.
* @type string | number * @type string | number
*/ */
height?: string | number; height?: string | number
/** /**
* The z-index of the Drawer. * The z-index of the Drawer.
* @default 1000 * @default 1000
* @type number * @type number
*/ */
zIndex?: number; zIndex?: number
/** /**
* The placement of the Drawer. * The placement of the Drawer.
* @default 'right' * @default 'right'
* @type string * @type string
*/ */
placement?: 'top' | 'right' | 'bottom' | 'left'; placement?: 'top' | 'right' | 'bottom' | 'left'
afterVisibleChange?: (visible?: boolean) => void; afterVisibleChange?: (visible?: boolean) => void
keyboard?: boolean; keyboard?: boolean
/** /**
* Specify a callback that will be called when a user clicks mask, close button or Cancel button. * Specify a callback that will be called when a user clicks mask, close button or Cancel button.
*/ */
onClose?: (e?: Event) => void; onClose?: (e?: Event) => void
} }
export interface DrawerActionType { export interface DrawerActionType {
scrollBottom: () => void; scrollBottom: () => void
scrollTo: (to: number) => void; scrollTo: (to: number) => void
getScrollWrap: () => Element | null; getScrollWrap: () => Element | null
} }

View File

@ -4,7 +4,7 @@ import type {
ReturnMethods, ReturnMethods,
DrawerProps, DrawerProps,
UseDrawerInnerReturnType, UseDrawerInnerReturnType,
} from './typing'; } from './typing'
import { import {
ref, ref,
getCurrentInstance, getCurrentInstance,
@ -14,148 +14,148 @@ import {
nextTick, nextTick,
toRaw, toRaw,
computed, computed,
} from 'vue'; } from 'vue'
import { isProdMode } from '/@/utils/env'; import { isProdMode } from '/@/utils/env'
import { isFunction } from '/@/utils/is'; import { isFunction } from '/@/utils/is'
import { tryOnUnmounted } from '@vueuse/core'; import { tryOnUnmounted } from '@vueuse/core'
import { isEqual } from 'lodash-es'; import { isEqual } from 'lodash-es'
import { error } from '/@/utils/log'; import { error } from '/@/utils/log'
const dataTransferRef = reactive<any>({}); const dataTransferRef = reactive<any>({})
const visibleData = reactive<{ [key: number]: boolean }>({}); const visibleData = reactive<{ [key: number]: boolean }>({})
/** /**
* @description: Applicable to separate drawer and call outside * @description: Applicable to separate drawer and call outside
*/ */
export function useDrawer(): UseDrawerReturnType { export function useDrawer(): UseDrawerReturnType {
if (!getCurrentInstance()) { if (!getCurrentInstance()) {
throw new Error('useDrawer() can only be used inside setup() or functional components!'); throw new Error('useDrawer() can only be used inside setup() or functional components!')
} }
const drawer = ref<DrawerInstance | null>(null); const drawer = ref<DrawerInstance | null>(null)
const loaded = ref<Nullable<boolean>>(false); const loaded = ref<Nullable<boolean>>(false)
const uid = ref<string>(''); const uid = ref<string>('')
function register(drawerInstance: DrawerInstance, uuid: string) { function register(drawerInstance: DrawerInstance, uuid: string) {
isProdMode() && isProdMode() &&
tryOnUnmounted(() => { tryOnUnmounted(() => {
drawer.value = null; drawer.value = null
loaded.value = null; loaded.value = null
dataTransferRef[unref(uid)] = null; dataTransferRef[unref(uid)] = null
}); })
if (unref(loaded) && isProdMode() && drawerInstance === unref(drawer)) { if (unref(loaded) && isProdMode() && drawerInstance === unref(drawer)) {
return; return
} }
uid.value = uuid; uid.value = uuid
drawer.value = drawerInstance; drawer.value = drawerInstance
loaded.value = true; loaded.value = true
drawerInstance.emitVisible = (visible: boolean, uid: number) => { drawerInstance.emitVisible = (visible: boolean, uid: number) => {
visibleData[uid] = visible; visibleData[uid] = visible
}; }
} }
const getInstance = () => { const getInstance = () => {
const instance = unref(drawer); const instance = unref(drawer)
if (!instance) { if (!instance) {
error('useDrawer instance is undefined!'); error('useDrawer instance is undefined!')
}
return instance
} }
return instance;
};
const methods: ReturnMethods = { const methods: ReturnMethods = {
setDrawerProps: (props: Partial<DrawerProps>): void => { setDrawerProps: (props: Partial<DrawerProps>): void => {
getInstance()?.setDrawerProps(props); getInstance()?.setDrawerProps(props)
}, },
getVisible: computed((): boolean => { getVisible: computed((): boolean => {
return visibleData[~~unref(uid)]; return visibleData[~~unref(uid)]
}), }),
openDrawer: <T = any>(visible = true, data?: T, openOnSet = true): void => { openDrawer: <T = any>(visible = true, data?: T, openOnSet = true): void => {
getInstance()?.setDrawerProps({ getInstance()?.setDrawerProps({
visible: visible, visible: visible,
}); })
if (!data) return; if (!data) return
if (openOnSet) { if (openOnSet) {
dataTransferRef[unref(uid)] = null; dataTransferRef[unref(uid)] = null
dataTransferRef[unref(uid)] = toRaw(data); dataTransferRef[unref(uid)] = toRaw(data)
return; return
} }
const equal = isEqual(toRaw(dataTransferRef[unref(uid)]), toRaw(data)); const equal = isEqual(toRaw(dataTransferRef[unref(uid)]), toRaw(data))
if (!equal) { if (!equal) {
dataTransferRef[unref(uid)] = toRaw(data); dataTransferRef[unref(uid)] = toRaw(data)
} }
}, },
closeDrawer: () => { closeDrawer: () => {
getInstance()?.setDrawerProps({ visible: false }); getInstance()?.setDrawerProps({ visible: false })
}, },
}; }
return [register, methods]; return [register, methods]
} }
export const useDrawerInner = (callbackFn?: Fn): UseDrawerInnerReturnType => { export const useDrawerInner = (callbackFn?: Fn): UseDrawerInnerReturnType => {
const drawerInstanceRef = ref<Nullable<DrawerInstance>>(null); const drawerInstanceRef = ref<Nullable<DrawerInstance>>(null)
const currentInstance = getCurrentInstance(); const currentInstance = getCurrentInstance()
const uidRef = ref<string>(''); const uidRef = ref<string>('')
if (!getCurrentInstance()) { if (!getCurrentInstance()) {
throw new Error('useDrawerInner() can only be used inside setup() or functional components!'); throw new Error('useDrawerInner() can only be used inside setup() or functional components!')
} }
const getInstance = () => { const getInstance = () => {
const instance = unref(drawerInstanceRef); const instance = unref(drawerInstanceRef)
if (!instance) { if (!instance) {
error('useDrawerInner instance is undefined!'); error('useDrawerInner instance is undefined!')
return; return
}
return instance
} }
return instance;
};
const register = (modalInstance: DrawerInstance, uuid: string) => { const register = (modalInstance: DrawerInstance, uuid: string) => {
isProdMode() && isProdMode() &&
tryOnUnmounted(() => { tryOnUnmounted(() => {
drawerInstanceRef.value = null; drawerInstanceRef.value = null
}); })
uidRef.value = uuid; uidRef.value = uuid
drawerInstanceRef.value = modalInstance; drawerInstanceRef.value = modalInstance
currentInstance?.emit('register', modalInstance, uuid); currentInstance?.emit('register', modalInstance, uuid)
}; }
watchEffect(() => { watchEffect(() => {
const data = dataTransferRef[unref(uidRef)]; const data = dataTransferRef[unref(uidRef)]
if (!data) return; if (!data) return
if (!callbackFn || !isFunction(callbackFn)) return; if (!callbackFn || !isFunction(callbackFn)) return
nextTick(() => { nextTick(() => {
callbackFn(data); callbackFn(data)
}); })
}); })
return [ return [
register, register,
{ {
changeLoading: (loading = true) => { changeLoading: (loading = true) => {
getInstance()?.setDrawerProps({ loading }); getInstance()?.setDrawerProps({ loading })
}, },
changeOkLoading: (loading = true) => { changeOkLoading: (loading = true) => {
getInstance()?.setDrawerProps({ confirmLoading: loading }); getInstance()?.setDrawerProps({ confirmLoading: loading })
}, },
getVisible: computed((): boolean => { getVisible: computed((): boolean => {
return visibleData[~~unref(uidRef)]; return visibleData[~~unref(uidRef)]
}), }),
closeDrawer: () => { closeDrawer: () => {
getInstance()?.setDrawerProps({ visible: false }); getInstance()?.setDrawerProps({ visible: false })
}, },
setDrawerProps: (props: Partial<DrawerProps>) => { setDrawerProps: (props: Partial<DrawerProps>) => {
getInstance()?.setDrawerProps(props); getInstance()?.setDrawerProps(props)
}, },
}, },
]; ]
}; }

View File

@ -1,5 +1,5 @@
import { withInstall } from '/@/utils'; import { withInstall } from '/@/utils'
import dropdown from './src/Dropdown.vue'; import dropdown from './src/Dropdown.vue'
export * from './src/typing'; export * from './src/typing'
export const Dropdown = withInstall(dropdown); export const Dropdown = withInstall(dropdown)

View File

@ -36,18 +36,18 @@
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import { computed, PropType } from 'vue'; import { computed, PropType } from 'vue'
import type { DropMenu } from './typing'; import type { DropMenu } from './typing'
import { Dropdown, Menu, Popconfirm } from 'ant-design-vue'; import { Dropdown, Menu, Popconfirm } from 'ant-design-vue'
import { Icon } from '/@/components/Icon'; import { Icon } from '/@/components/Icon'
import { omit } from 'lodash-es'; import { omit } from 'lodash-es'
import { isFunction } from '/@/utils/is'; import { isFunction } from '/@/utils/is'
const ADropdown = Dropdown; const ADropdown = Dropdown
const AMenu = Menu; const AMenu = Menu
const AMenuItem = Menu.Item; const AMenuItem = Menu.Item
const AMenuDivider = Menu.Divider; const AMenuDivider = Menu.Divider
const APopconfirm = Popconfirm; const APopconfirm = Popconfirm
const props = defineProps({ const props = defineProps({
popconfirm: Boolean, popconfirm: Boolean,
@ -59,7 +59,7 @@
trigger: { trigger: {
type: [Array] as PropType<('contextmenu' | 'click' | 'hover')[]>, type: [Array] as PropType<('contextmenu' | 'click' | 'hover')[]>,
default: () => { default: () => {
return ['contextmenu']; return ['contextmenu']
}, },
}, },
dropMenuList: { dropMenuList: {
@ -70,27 +70,27 @@
type: Array as PropType<string[]>, type: Array as PropType<string[]>,
default: () => [], default: () => [],
}, },
}); })
const emit = defineEmits(['menuEvent']); const emit = defineEmits(['menuEvent'])
function handleClickMenu(item: DropMenu) { function handleClickMenu(item: DropMenu) {
const { event } = item; const { event } = item
const menu = props.dropMenuList.find((item) => `${item.event}` === `${event}`); const menu = props.dropMenuList.find((item) => `${item.event}` === `${event}`)
emit('menuEvent', menu); emit('menuEvent', menu)
item.onClick?.(); item.onClick?.()
} }
const getPopConfirmAttrs = computed(() => { const getPopConfirmAttrs = computed(() => {
return (attrs) => { return (attrs) => {
const originAttrs = omit(attrs, ['confirm', 'cancel', 'icon']); const originAttrs = omit(attrs, ['confirm', 'cancel', 'icon'])
if (!attrs.onConfirm && attrs.confirm && isFunction(attrs.confirm)) if (!attrs.onConfirm && attrs.confirm && isFunction(attrs.confirm))
originAttrs['onConfirm'] = attrs.confirm; originAttrs['onConfirm'] = attrs.confirm
if (!attrs.onCancel && attrs.cancel && isFunction(attrs.cancel)) if (!attrs.onCancel && attrs.cancel && isFunction(attrs.cancel))
originAttrs['onCancel'] = attrs.cancel; originAttrs['onCancel'] = attrs.cancel
return originAttrs; return originAttrs
}; }
}); })
const getAttr = (key: string | number) => ({ key }); const getAttr = (key: string | number) => ({ key })
</script> </script>

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