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:
parent
39188780ea
commit
d5e2d26a0f
|
|
@ -109,7 +109,6 @@
|
||||||
"esnext",
|
"esnext",
|
||||||
"antv",
|
"antv",
|
||||||
"tinymce",
|
"tinymce",
|
||||||
"qrcode",
|
|
||||||
"sider",
|
"sider",
|
||||||
"pinia",
|
"pinia",
|
||||||
"sider",
|
"sider",
|
||||||
|
|
|
||||||
1262
CHANGELOG.en_US.md
1262
CHANGELOG.en_US.md
File diff suppressed because it is too large
Load Diff
1317
CHANGELOG.zh_CN.md
1317
CHANGELOG.zh_CN.md
File diff suppressed because it is too large
Load Diff
174
README.md
174
README.md
|
|
@ -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.
|
|
||||||
|
|
||||||
[](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 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!
|
|
||||||
|
|
||||||

|
|
||||||
|
|
||||||
<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
|
||||||
|
|
||||||
|
|
|
||||||
175
README.zh-CN.md
175
README.zh-CN.md
|
|
@ -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)
|
|
||||||
|
|
||||||
<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 的免费在线开发环境)中打开项目,并立即开始编码.
|
|
||||||
|
|
||||||
[](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) - 简化版
|
|
||||||
|
|
||||||
## 如何贡献
|
|
||||||
|
|
||||||
非常欢迎你的加入 或者提交一个 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)
|
|
||||||
|
|
||||||
## 捐赠
|
|
||||||
|
|
||||||
如果你觉得这个项目对你有帮助,你可以帮作者买一杯咖啡表示支持!
|
|
||||||
|
|
||||||

|
|
||||||
|
|
||||||
<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)
|
|
||||||
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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: 自定义',
|
||||||
},
|
},
|
||||||
};
|
}
|
||||||
|
|
|
||||||
14
index.html
14
index.html
|
|
@ -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>
|
||||||
|
|
|
||||||
|
|
@ -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[]
|
||||||
|
|
|
||||||
|
|
@ -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",
|
||||||
|
|
|
||||||
466
pnpm-lock.yaml
466
pnpm-lock.yaml
File diff suppressed because it is too large
Load Diff
|
|
@ -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',
|
||||||
};
|
}
|
||||||
|
|
|
||||||
14
src/App.vue
14
src/App.vue
|
|
@ -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>
|
||||||
|
|
|
||||||
|
|
@ -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 })
|
||||||
|
|
|
||||||
|
|
@ -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 })
|
||||||
|
|
|
||||||
|
|
@ -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 })
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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>
|
||||||
|
|
|
||||||
|
|
@ -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[]
|
||||||
|
|
|
||||||
|
|
@ -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>
|
||||||
|
|
|
||||||
|
|
@ -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 })
|
||||||
|
|
|
||||||
|
|
@ -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' })
|
||||||
|
|
|
||||||
|
|
@ -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,
|
||||||
},
|
},
|
||||||
});
|
})
|
||||||
|
|
|
||||||
|
|
@ -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 })
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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 })
|
||||||
};
|
}
|
||||||
|
|
|
||||||
|
|
@ -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[]
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
export interface UploadApiResult {
|
export interface UploadApiResult {
|
||||||
message: string;
|
message: string
|
||||||
code: number;
|
code: number
|
||||||
url: string;
|
url: string
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
@ -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,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
);
|
)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
|
|
|
||||||
|
|
@ -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>
|
||||||
|
|
|
||||||
|
|
@ -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>
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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>
|
||||||
|
|
|
||||||
|
|
@ -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>
|
||||||
|
|
|
||||||
|
|
@ -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>
|
||||||
|
|
|
||||||
|
|
@ -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';
|
||||||
|
|
|
||||||
|
|
@ -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>
|
||||||
|
|
|
||||||
|
|
@ -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>
|
||||||
|
|
|
||||||
|
|
@ -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 }
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,4 +0,0 @@
|
||||||
import { withInstall } from '/@/utils';
|
|
||||||
import authority from './src/Authority.vue';
|
|
||||||
|
|
||||||
export const Authority = withInstall(authority);
|
|
||||||
|
|
@ -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>
|
|
||||||
|
|
@ -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)
|
||||||
|
|
|
||||||
|
|
@ -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';
|
||||||
|
|
|
||||||
|
|
@ -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';
|
||||||
|
|
|
||||||
|
|
@ -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';
|
||||||
|
|
|
||||||
|
|
@ -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>>
|
||||||
|
|
|
||||||
|
|
@ -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>
|
||||||
|
|
|
||||||
|
|
@ -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>
|
||||||
|
|
|
||||||
|
|
@ -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 },
|
||||||
};
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,4 +0,0 @@
|
||||||
import { withInstall } from '/@/utils';
|
|
||||||
import cardList from './src/CardList.vue';
|
|
||||||
|
|
||||||
export const CardList = withInstall(cardList);
|
|
||||||
|
|
@ -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>
|
|
||||||
|
|
@ -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,
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
@ -1,4 +0,0 @@
|
||||||
import { withInstall } from '/@/utils';
|
|
||||||
import clickOutSide from './src/ClickOutSide.vue';
|
|
||||||
|
|
||||||
export const ClickOutSide = withInstall(clickOutSide);
|
|
||||||
|
|
@ -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>
|
|
||||||
|
|
@ -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';
|
|
||||||
|
|
@ -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>
|
|
||||||
|
|
@ -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>
|
|
||||||
|
|
@ -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 };
|
|
||||||
|
|
@ -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;
|
|
||||||
}
|
|
||||||
|
|
@ -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>
|
|
||||||
|
|
@ -1,5 +0,0 @@
|
||||||
export enum MODE {
|
|
||||||
JSON = 'application/json',
|
|
||||||
HTML = 'htmlmixed',
|
|
||||||
JS = 'javascript',
|
|
||||||
}
|
|
||||||
|
|
@ -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'
|
||||||
|
|
|
||||||
|
|
@ -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>
|
||||||
|
|
|
||||||
|
|
@ -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 {
|
||||||
|
|
|
||||||
|
|
@ -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';
|
||||||
|
|
|
||||||
|
|
@ -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>
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
}>;
|
}>
|
||||||
|
|
|
||||||
|
|
@ -1,3 +0,0 @@
|
||||||
export { createContextMenu, destroyContextMenu } from './src/createContextMenu';
|
|
||||||
|
|
||||||
export * from './src/typing';
|
|
||||||
|
|
@ -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>
|
|
||||||
|
|
@ -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 = [];
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
@ -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;
|
|
||||||
}
|
|
||||||
|
|
@ -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)
|
||||||
|
|
|
||||||
|
|
@ -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>
|
||||||
|
|
|
||||||
|
|
@ -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';
|
||||||
|
|
|
||||||
|
|
@ -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 }
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,4 +0,0 @@
|
||||||
import { withInstall } from '/@/utils';
|
|
||||||
import countTo from './src/CountTo.vue';
|
|
||||||
|
|
||||||
export const CountTo = withInstall(countTo);
|
|
||||||
|
|
@ -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>
|
|
||||||
|
|
@ -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);
|
|
||||||
|
|
@ -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>
|
|
||||||
|
|
@ -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>
|
|
||||||
|
|
@ -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>
|
|
||||||
|
|
@ -1,8 +0,0 @@
|
||||||
import type Cropper from 'cropperjs';
|
|
||||||
|
|
||||||
export interface CropendResult {
|
|
||||||
imgBase64: string;
|
|
||||||
imgInfo: Cropper.Data;
|
|
||||||
}
|
|
||||||
|
|
||||||
export type { Cropper };
|
|
||||||
|
|
@ -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)
|
||||||
|
|
|
||||||
|
|
@ -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>
|
||||||
|
|
|
||||||
|
|
@ -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]
|
||||||
|
|
|
||||||
|
|
@ -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]
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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'
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
|
|
|
||||||
|
|
@ -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">
|
||||||
|
|
|
||||||
|
|
@ -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">
|
||||||
|
|
|
||||||
|
|
@ -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,
|
||||||
};
|
}
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
];
|
]
|
||||||
};
|
}
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
|
|
|
||||||
|
|
@ -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
Loading…
Reference in New Issue