mirror of
https://github.com/vbenjs/vue-vben-admin.git
synced 2025-08-25 16:16:20 +08:00
Compare commits
3 Commits
v3.0.0-alp
...
thin
Author | SHA1 | Date | |
---|---|---|---|
![]() |
43a4d986b2 | ||
![]() |
c4216e24d6 | ||
![]() |
d5e2d26a0f |
1
.vscode/settings.json
vendored
1
.vscode/settings.json
vendored
@@ -109,7 +109,6 @@
|
||||
"esnext",
|
||||
"antv",
|
||||
"tinymce",
|
||||
"qrcode",
|
||||
"sider",
|
||||
"pinia",
|
||||
"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
176
README.md
176
README.md
@@ -5,164 +5,96 @@
|
||||
<h1>Vue vben admin</h1>
|
||||
</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
|
||||
git clone https://github.com/anncwb/vue-vben-admin.git
|
||||
```
|
||||
|
||||
- Installation dependencies
|
||||
- 安装依赖
|
||||
|
||||
```bash
|
||||
cd vue-vben-admin
|
||||
|
||||
git checkout thin
|
||||
pnpm install
|
||||
|
||||
```
|
||||
|
||||
- run
|
||||
- 运行
|
||||
|
||||
```bash
|
||||
pnpm serve
|
||||
```
|
||||
|
||||
- build
|
||||
- 打包
|
||||
|
||||
```bash
|
||||
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。
|
||||
|
||||
**Pull Request:**
|
||||
|
||||
1. Fork code!
|
||||
2. Create your own branch: `git checkout -b feat/xxxx`
|
||||
3. Submit your changes: `git commit -am 'feat(function): add xxxxx'`
|
||||
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)
|
||||
- [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 雪碧图
|
||||
|
||||
## 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 vue from '@vitejs/plugin-vue';
|
||||
import vueJsx from '@vitejs/plugin-vue-jsx';
|
||||
import legacy from '@vitejs/plugin-legacy';
|
||||
import purgeIcons from 'vite-plugin-purge-icons';
|
||||
import windiCSS from 'vite-plugin-windicss';
|
||||
import VitePluginCertificate from 'vite-plugin-mkcert';
|
||||
import { PluginOption } from 'vite'
|
||||
import vue from '@vitejs/plugin-vue'
|
||||
import vueJsx from '@vitejs/plugin-vue-jsx'
|
||||
import legacy from '@vitejs/plugin-legacy'
|
||||
import purgeIcons from 'vite-plugin-purge-icons'
|
||||
import windiCSS from 'vite-plugin-windicss'
|
||||
import VitePluginCertificate from 'vite-plugin-mkcert'
|
||||
//import vueSetupExtend from 'vite-plugin-vue-setup-extend';
|
||||
import { configHtmlPlugin } from './html';
|
||||
import { configPwaConfig } from './pwa';
|
||||
import { configMockPlugin } from './mock';
|
||||
import { configCompressPlugin } from './compress';
|
||||
import { configStyleImportPlugin } from './styleImport';
|
||||
import { configVisualizerConfig } from './visualizer';
|
||||
import { configThemePlugin } from './theme';
|
||||
import { configImageminPlugin } from './imagemin';
|
||||
import { configSvgIconsPlugin } from './svgSprite';
|
||||
import { configHtmlPlugin } from './html'
|
||||
import { configPwaConfig } from './pwa'
|
||||
import { configMockPlugin } from './mock'
|
||||
import { configCompressPlugin } from './compress'
|
||||
import { configStyleImportPlugin } from './styleImport'
|
||||
import { configVisualizerConfig } from './visualizer'
|
||||
import { configThemePlugin } from './theme'
|
||||
import { configImageminPlugin } from './imagemin'
|
||||
import { configSvgIconsPlugin } from './svgSprite'
|
||||
|
||||
export function createVitePlugins(viteEnv: ViteEnv, isBuild: boolean) {
|
||||
const {
|
||||
@@ -23,7 +23,7 @@ export function createVitePlugins(viteEnv: ViteEnv, isBuild: boolean) {
|
||||
VITE_LEGACY,
|
||||
VITE_BUILD_COMPRESS,
|
||||
VITE_BUILD_COMPRESS_DELETE_ORIGIN_FILE,
|
||||
} = viteEnv;
|
||||
} = viteEnv
|
||||
|
||||
const vitePlugins: (PluginOption | PluginOption[])[] = [
|
||||
// have to
|
||||
@@ -35,48 +35,48 @@ export function createVitePlugins(viteEnv: ViteEnv, isBuild: boolean) {
|
||||
VitePluginCertificate({
|
||||
source: 'coding',
|
||||
}),
|
||||
];
|
||||
]
|
||||
|
||||
// vite-plugin-windicss
|
||||
vitePlugins.push(windiCSS());
|
||||
vitePlugins.push(windiCSS())
|
||||
|
||||
// @vitejs/plugin-legacy
|
||||
VITE_LEGACY && isBuild && vitePlugins.push(legacy());
|
||||
VITE_LEGACY && isBuild && vitePlugins.push(legacy())
|
||||
|
||||
// vite-plugin-html
|
||||
vitePlugins.push(configHtmlPlugin(viteEnv, isBuild));
|
||||
vitePlugins.push(configHtmlPlugin(viteEnv, isBuild))
|
||||
|
||||
// vite-plugin-svg-icons
|
||||
vitePlugins.push(configSvgIconsPlugin(isBuild));
|
||||
vitePlugins.push(configSvgIconsPlugin(isBuild))
|
||||
|
||||
// vite-plugin-mock
|
||||
VITE_USE_MOCK && vitePlugins.push(configMockPlugin(isBuild));
|
||||
VITE_USE_MOCK && vitePlugins.push(configMockPlugin(isBuild))
|
||||
|
||||
// vite-plugin-purge-icons
|
||||
vitePlugins.push(purgeIcons());
|
||||
vitePlugins.push(purgeIcons())
|
||||
|
||||
// vite-plugin-style-import
|
||||
vitePlugins.push(configStyleImportPlugin(isBuild));
|
||||
vitePlugins.push(configStyleImportPlugin(isBuild))
|
||||
|
||||
// rollup-plugin-visualizer
|
||||
vitePlugins.push(configVisualizerConfig());
|
||||
vitePlugins.push(configVisualizerConfig())
|
||||
|
||||
// vite-plugin-theme
|
||||
vitePlugins.push(configThemePlugin(isBuild));
|
||||
vitePlugins.push(configThemePlugin(isBuild))
|
||||
|
||||
// The following plugins only work in the production environment
|
||||
if (isBuild) {
|
||||
// vite-plugin-imagemin
|
||||
VITE_USE_IMAGEMIN && vitePlugins.push(configImageminPlugin());
|
||||
VITE_USE_IMAGEMIN && vitePlugins.push(configImageminPlugin())
|
||||
|
||||
// rollup-plugin-gzip
|
||||
vitePlugins.push(
|
||||
configCompressPlugin(VITE_BUILD_COMPRESS, VITE_BUILD_COMPRESS_DELETE_ORIGIN_FILE),
|
||||
);
|
||||
)
|
||||
|
||||
// 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.
|
||||
* 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) {
|
||||
if (!_isBuild) {
|
||||
return [];
|
||||
return []
|
||||
}
|
||||
const styleImportPlugin = createStyleImportPlugin({
|
||||
libs: [
|
||||
@@ -45,7 +45,7 @@ export function configStyleImportPlugin(_isBuild: boolean) {
|
||||
'skeleton-paragraph',
|
||||
'skeleton-image',
|
||||
'skeleton-button',
|
||||
];
|
||||
]
|
||||
// 这里是需要额外引入样式的子组件列表
|
||||
// 单独引入子组件时需引入组件样式,否则会在打包后导致子组件样式丢失
|
||||
const replaceList = {
|
||||
@@ -66,16 +66,16 @@ export function configStyleImportPlugin(_isBuild: boolean) {
|
||||
'month-picker': 'date-picker',
|
||||
'range-picker': 'date-picker',
|
||||
'image-preview-group': 'image',
|
||||
};
|
||||
}
|
||||
|
||||
return ignoreList.includes(name)
|
||||
? ''
|
||||
: replaceList.hasOwnProperty(name)
|
||||
? `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 path = require('path');
|
||||
const { execSync } = require('child_process');
|
||||
const fs = require('fs')
|
||||
const path = require('path')
|
||||
const { execSync } = require('child_process')
|
||||
|
||||
const scopes = fs
|
||||
.readdirSync(path.resolve(__dirname, 'src'), { withFileTypes: true })
|
||||
.filter((dirent) => dirent.isDirectory())
|
||||
.map((dirent) => dirent.name.replace(/s$/, ''));
|
||||
.map((dirent) => dirent.name.replace(/s$/, ''))
|
||||
|
||||
// precomputed scope
|
||||
const scopeComplete = execSync('git status --porcelain || true')
|
||||
@@ -15,7 +15,7 @@ const scopeComplete = execSync('git status --porcelain || true')
|
||||
.find((r) => ~r.indexOf('M src'))
|
||||
?.replace(/(\/)/g, '%%')
|
||||
?.match(/src%%((\w|-)*)/)?.[1]
|
||||
?.replace(/s$/, '');
|
||||
?.replace(/s$/, '')
|
||||
|
||||
/** @type {import('cz-git').UserConfig} */
|
||||
module.exports = {
|
||||
@@ -104,4 +104,4 @@ module.exports = {
|
||||
// emptyScopesAlias: 'empty: 不填写',
|
||||
// customScopesAlias: 'custom: 自定义',
|
||||
},
|
||||
};
|
||||
}
|
||||
|
14
index.html
14
index.html
@@ -1,5 +1,5 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en" id="htmlRoot">
|
||||
<html lang="zh-cn" id="htmlRoot">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1" />
|
||||
@@ -13,14 +13,14 @@
|
||||
</head>
|
||||
<body>
|
||||
<script>
|
||||
(() => {
|
||||
var htmlRoot = document.getElementById('htmlRoot');
|
||||
var theme = window.localStorage.getItem('__APP__DARK__MODE__');
|
||||
;(() => {
|
||||
var htmlRoot = document.getElementById('htmlRoot')
|
||||
var theme = window.localStorage.getItem('__APP__DARK__MODE__')
|
||||
if (htmlRoot && theme) {
|
||||
htmlRoot.setAttribute('data-theme', theme);
|
||||
theme = htmlRoot = null;
|
||||
htmlRoot.setAttribute('data-theme', theme)
|
||||
theme = htmlRoot = null
|
||||
}
|
||||
})();
|
||||
})()
|
||||
</script>
|
||||
<div id="app">
|
||||
<style>
|
||||
|
@@ -1,6 +1,6 @@
|
||||
import { resultSuccess, resultError, getRequestToken, requestParams } from '../_util';
|
||||
import { MockMethod } from 'vite-plugin-mock';
|
||||
import { createFakeUserList } from './user';
|
||||
import { resultSuccess, resultError, getRequestToken, requestParams } from '../_util'
|
||||
import { MockMethod } from 'vite-plugin-mock'
|
||||
import { createFakeUserList } from './user'
|
||||
|
||||
// single
|
||||
const dashboardRoute = {
|
||||
@@ -39,7 +39,7 @@ const dashboardRoute = {
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
||||
}
|
||||
|
||||
const backRoute = {
|
||||
path: 'back',
|
||||
@@ -66,7 +66,7 @@ const backRoute = {
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
||||
}
|
||||
|
||||
const authRoute = {
|
||||
path: '/permission',
|
||||
@@ -78,7 +78,7 @@ const authRoute = {
|
||||
title: 'routes.demo.permission.permission',
|
||||
},
|
||||
children: [backRoute],
|
||||
};
|
||||
}
|
||||
|
||||
const levelRoute = {
|
||||
path: '/level',
|
||||
@@ -134,7 +134,7 @@ const levelRoute = {
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
||||
}
|
||||
|
||||
const sysRoute = {
|
||||
path: '/system',
|
||||
@@ -176,16 +176,6 @@ const sysRoute = {
|
||||
},
|
||||
component: '/demo/system/role/index',
|
||||
},
|
||||
|
||||
{
|
||||
path: 'menu',
|
||||
name: 'MenuManagement',
|
||||
meta: {
|
||||
title: 'routes.demo.system.menu',
|
||||
ignoreKeepAlive: true,
|
||||
},
|
||||
component: '/demo/system/menu/index',
|
||||
},
|
||||
{
|
||||
path: 'dept',
|
||||
name: 'DeptManagement',
|
||||
@@ -205,7 +195,7 @@ const sysRoute = {
|
||||
component: '/demo/system/password/index',
|
||||
},
|
||||
],
|
||||
};
|
||||
}
|
||||
|
||||
const linkRoute = {
|
||||
path: '/link',
|
||||
@@ -233,7 +223,7 @@ const linkRoute = {
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
||||
}
|
||||
|
||||
export default [
|
||||
{
|
||||
@@ -241,30 +231,30 @@ export default [
|
||||
timeout: 1000,
|
||||
method: 'get',
|
||||
response: (request: requestParams) => {
|
||||
const token = getRequestToken(request);
|
||||
const token = getRequestToken(request)
|
||||
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) {
|
||||
return resultError('Invalid user token!');
|
||||
return resultError('Invalid user token!')
|
||||
}
|
||||
const id = checkUser.userId;
|
||||
let menu: Object[];
|
||||
const id = checkUser.userId
|
||||
let menu: Object[]
|
||||
switch (id) {
|
||||
case '1':
|
||||
dashboardRoute.redirect = dashboardRoute.path + '/' + dashboardRoute.children[0].path;
|
||||
menu = [dashboardRoute, authRoute, levelRoute, sysRoute, linkRoute];
|
||||
break;
|
||||
dashboardRoute.redirect = dashboardRoute.path + '/' + dashboardRoute.children[0].path
|
||||
menu = [dashboardRoute, authRoute, levelRoute, sysRoute, linkRoute]
|
||||
break
|
||||
case '2':
|
||||
dashboardRoute.redirect = dashboardRoute.path + '/' + dashboardRoute.children[1].path;
|
||||
menu = [dashboardRoute, authRoute, levelRoute, linkRoute];
|
||||
break;
|
||||
dashboardRoute.redirect = dashboardRoute.path + '/' + dashboardRoute.children[1].path
|
||||
menu = [dashboardRoute, authRoute, levelRoute, linkRoute]
|
||||
break
|
||||
default:
|
||||
menu = [];
|
||||
menu = []
|
||||
}
|
||||
|
||||
return resultSuccess(menu);
|
||||
return resultSuccess(menu)
|
||||
},
|
||||
},
|
||||
] as MockMethod[];
|
||||
] as MockMethod[]
|
||||
|
@@ -56,15 +56,13 @@
|
||||
"nprogress": "^0.2.0",
|
||||
"path-to-regexp": "^6.2.0",
|
||||
"pinia": "2.0.12",
|
||||
"print-js": "^1.6.0",
|
||||
"qrcode": "^1.5.0",
|
||||
"qs": "^6.10.3",
|
||||
"resize-observer-polyfill": "^1.5.1",
|
||||
"showdown": "^2.1.0",
|
||||
"sortablejs": "^1.15.0",
|
||||
"tinymce": "^5.10.3",
|
||||
"vditor": "^3.8.13",
|
||||
"vue": "^3.2.33",
|
||||
"vue": "^3.2.34",
|
||||
"vue-i18n": "^9.1.9",
|
||||
"vue-json-pretty": "^2.0.6",
|
||||
"vue-router": "^4.0.14",
|
||||
@@ -85,7 +83,6 @@
|
||||
"@types/mockjs": "^1.0.6",
|
||||
"@types/node": "^17.0.25",
|
||||
"@types/nprogress": "^0.2.0",
|
||||
"@types/qrcode": "^1.4.2",
|
||||
"@types/qs": "^6.9.7",
|
||||
"@types/showdown": "^1.9.4",
|
||||
"@types/sortablejs": "^1.10.7",
|
||||
@@ -99,8 +96,8 @@
|
||||
"autoprefixer": "^10.4.4",
|
||||
"conventional-changelog-cli": "^2.2.2",
|
||||
"cross-env": "^7.0.3",
|
||||
"cz-git": "^1.3.9",
|
||||
"czg": "^1.3.9",
|
||||
"cz-git": "^1.3.11",
|
||||
"czg": "^1.3.11",
|
||||
"dotenv": "^16.0.0",
|
||||
"eslint": "^8.13.0",
|
||||
"eslint-config-prettier": "^8.5.0",
|
||||
|
466
pnpm-lock.yaml
generated
466
pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
@@ -1,10 +1,10 @@
|
||||
module.exports = {
|
||||
printWidth: 100,
|
||||
semi: true,
|
||||
semi: false,
|
||||
vueIndentScriptAndStyle: true,
|
||||
singleQuote: true,
|
||||
trailingComma: 'all',
|
||||
proseWrap: 'never',
|
||||
htmlWhitespaceSensitivity: 'strict',
|
||||
endOfLine: 'auto',
|
||||
};
|
||||
}
|
||||
|
14
src/App.vue
14
src/App.vue
@@ -7,15 +7,15 @@
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ConfigProvider } from 'ant-design-vue';
|
||||
import { AppProvider } from '/@/components/Application';
|
||||
import { useTitle } from '/@/hooks/web/useTitle';
|
||||
import { useLocale } from '/@/locales/useLocale';
|
||||
import { ConfigProvider } from 'ant-design-vue'
|
||||
import { AppProvider } from '/@/components/Application'
|
||||
import { useTitle } from '/@/hooks/web/useTitle'
|
||||
import { useLocale } from '/@/locales/useLocale'
|
||||
|
||||
import 'dayjs/locale/zh-cn';
|
||||
import 'dayjs/locale/zh-cn'
|
||||
// support Multi-language
|
||||
const { getAntdLocale } = useLocale();
|
||||
const { getAntdLocale } = useLocale()
|
||||
|
||||
// Listening to page changes and dynamically changing site titles
|
||||
useTitle();
|
||||
useTitle()
|
||||
</script>
|
||||
|
@@ -1,5 +1,5 @@
|
||||
import { defHttp } from '/@/utils/http/axios';
|
||||
import { GetAccountInfoModel } from './model/accountModel';
|
||||
import { defHttp } from '/@/utils/http/axios'
|
||||
import { GetAccountInfoModel } from './model/accountModel'
|
||||
|
||||
enum Api {
|
||||
ACCOUNT_INFO = '/account/getAccountInfo',
|
||||
@@ -9,8 +9,8 @@ enum Api {
|
||||
|
||||
// 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 { AreaModel, AreaParams } from '/@/api/demo/model/areaModel';
|
||||
import { defHttp } from '/@/utils/http/axios'
|
||||
import { AreaModel, AreaParams } from '/@/api/demo/model/areaModel'
|
||||
|
||||
enum Api {
|
||||
AREA_RECORD = '/cascader/getAreaRecord',
|
||||
}
|
||||
|
||||
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 {
|
||||
// The address does not exist
|
||||
@@ -9,4 +9,4 @@ enum Api {
|
||||
* @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 {
|
||||
email: string;
|
||||
name: string;
|
||||
introduction: string;
|
||||
phone: string;
|
||||
address: string;
|
||||
email: string
|
||||
name: string
|
||||
introduction: string
|
||||
phone: string
|
||||
address: string
|
||||
}
|
||||
|
@@ -1,12 +1,12 @@
|
||||
export interface AreaModel {
|
||||
id: string;
|
||||
code: string;
|
||||
parentCode: string;
|
||||
name: string;
|
||||
levelType: number;
|
||||
[key: string]: string | number;
|
||||
id: string
|
||||
code: string
|
||||
parentCode: string
|
||||
name: string
|
||||
levelType: number
|
||||
[key: string]: string | number
|
||||
}
|
||||
|
||||
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 {
|
||||
label: string;
|
||||
value: string;
|
||||
label: string
|
||||
value: string
|
||||
}
|
||||
|
||||
export interface selectParams {
|
||||
id: number | string;
|
||||
id: number | string
|
||||
}
|
||||
|
||||
/**
|
||||
* @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 & {
|
||||
account?: string;
|
||||
nickname?: string;
|
||||
};
|
||||
account?: string
|
||||
nickname?: string
|
||||
}
|
||||
|
||||
export type RoleParams = {
|
||||
roleName?: string;
|
||||
status?: string;
|
||||
};
|
||||
roleName?: string
|
||||
status?: string
|
||||
}
|
||||
|
||||
export type RolePageParams = BasicPageParams & RoleParams;
|
||||
export type RolePageParams = BasicPageParams & RoleParams
|
||||
|
||||
export type DeptParams = {
|
||||
deptName?: string;
|
||||
status?: string;
|
||||
};
|
||||
deptName?: string
|
||||
status?: string
|
||||
}
|
||||
|
||||
export type MenuParams = {
|
||||
menuName?: string;
|
||||
status?: string;
|
||||
};
|
||||
menuName?: string
|
||||
status?: string
|
||||
}
|
||||
|
||||
export interface AccountListItem {
|
||||
id: string;
|
||||
account: string;
|
||||
email: string;
|
||||
nickname: string;
|
||||
role: number;
|
||||
createTime: string;
|
||||
remark: string;
|
||||
status: number;
|
||||
id: string
|
||||
account: string
|
||||
email: string
|
||||
nickname: string
|
||||
role: number
|
||||
createTime: string
|
||||
remark: string
|
||||
status: number
|
||||
}
|
||||
|
||||
export interface DeptListItem {
|
||||
id: string;
|
||||
orderNo: string;
|
||||
createTime: string;
|
||||
remark: string;
|
||||
status: number;
|
||||
id: string
|
||||
orderNo: string
|
||||
createTime: string
|
||||
remark: string
|
||||
status: number
|
||||
}
|
||||
|
||||
export interface MenuListItem {
|
||||
id: string;
|
||||
orderNo: string;
|
||||
createTime: string;
|
||||
status: number;
|
||||
icon: string;
|
||||
component: string;
|
||||
permission: string;
|
||||
id: string
|
||||
orderNo: string
|
||||
createTime: string
|
||||
status: number
|
||||
icon: string
|
||||
component: string
|
||||
permission: string
|
||||
}
|
||||
|
||||
export interface RoleListItem {
|
||||
id: string;
|
||||
roleName: string;
|
||||
roleValue: string;
|
||||
status: number;
|
||||
orderNo: string;
|
||||
createTime: string;
|
||||
id: string
|
||||
roleName: string
|
||||
roleValue: string
|
||||
status: number
|
||||
orderNo: string
|
||||
createTime: string
|
||||
}
|
||||
|
||||
/**
|
||||
* @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
|
||||
*/
|
||||
export type DemoParams = BasicPageParams;
|
||||
export type DemoParams = BasicPageParams
|
||||
|
||||
export interface DemoListItem {
|
||||
id: string;
|
||||
beginTime: string;
|
||||
endTime: string;
|
||||
address: string;
|
||||
name: string;
|
||||
no: number;
|
||||
status: number;
|
||||
id: string
|
||||
beginTime: string
|
||||
endTime: string
|
||||
address: string
|
||||
name: string
|
||||
no: number
|
||||
status: number
|
||||
}
|
||||
|
||||
/**
|
||||
* @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 { DemoOptionsItem, selectParams } from './model/optionsModel';
|
||||
import { defHttp } from '/@/utils/http/axios'
|
||||
import { DemoOptionsItem, selectParams } from './model/optionsModel'
|
||||
enum Api {
|
||||
OPTIONS_LIST = '/select/getDemoOptions',
|
||||
}
|
||||
@@ -8,4 +8,4 @@ enum Api {
|
||||
* @description: Get sample options value
|
||||
*/
|
||||
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,
|
||||
RolePageListGetResultModel,
|
||||
RoleListGetResultModel,
|
||||
} from './model/systemModel';
|
||||
import { defHttp } from '/@/utils/http/axios';
|
||||
} from './model/systemModel'
|
||||
import { defHttp } from '/@/utils/http/axios'
|
||||
|
||||
enum Api {
|
||||
AccountList = '/system/getAccountList',
|
||||
@@ -23,22 +23,22 @@ enum Api {
|
||||
}
|
||||
|
||||
export const getAccountList = (params: AccountParams) =>
|
||||
defHttp.get<AccountListGetResultModel>({ url: Api.AccountList, params });
|
||||
defHttp.get<AccountListGetResultModel>({ url: Api.AccountList, params })
|
||||
|
||||
export const getDeptList = (params?: DeptListItem) =>
|
||||
defHttp.get<DeptListGetResultModel>({ url: Api.DeptList, params });
|
||||
defHttp.get<DeptListGetResultModel>({ url: Api.DeptList, params })
|
||||
|
||||
export const getMenuList = (params?: MenuParams) =>
|
||||
defHttp.get<MenuListGetResultModel>({ url: Api.MenuList, params });
|
||||
defHttp.get<MenuListGetResultModel>({ url: Api.MenuList, params })
|
||||
|
||||
export const getRoleListByPage = (params?: RolePageParams) =>
|
||||
defHttp.get<RolePageListGetResultModel>({ url: Api.RolePageList, params });
|
||||
defHttp.get<RolePageListGetResultModel>({ url: Api.RolePageList, params })
|
||||
|
||||
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) =>
|
||||
defHttp.post({ url: Api.setRoleStatus, params: { id, status } });
|
||||
defHttp.post({ url: Api.setRoleStatus, params: { id, status } })
|
||||
|
||||
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 { DemoParams, DemoListGetResultModel } from './model/tableModel';
|
||||
import { defHttp } from '/@/utils/http/axios'
|
||||
import { DemoParams, DemoListGetResultModel } from './model/tableModel'
|
||||
|
||||
enum Api {
|
||||
DEMO_LIST = '/table/getDemoList',
|
||||
@@ -17,4 +17,4 @@ export const demoListApi = (params: DemoParams) =>
|
||||
// @ts-ignore
|
||||
ignoreCancelToken: true,
|
||||
},
|
||||
});
|
||||
})
|
||||
|
@@ -1,4 +1,4 @@
|
||||
import { defHttp } from '/@/utils/http/axios';
|
||||
import { defHttp } from '/@/utils/http/axios'
|
||||
|
||||
enum Api {
|
||||
TREE_OPTIONS_LIST = '/tree/getDemoOptions',
|
||||
@@ -8,4 +8,4 @@ enum Api {
|
||||
* @description: Get sample options value
|
||||
*/
|
||||
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 {
|
||||
page: number;
|
||||
pageSize: number;
|
||||
page: number
|
||||
pageSize: number
|
||||
}
|
||||
|
||||
export interface BasicFetchResult<T> {
|
||||
items: T[];
|
||||
total: number;
|
||||
items: T[]
|
||||
total: number
|
||||
}
|
||||
|
@@ -1,5 +1,5 @@
|
||||
import { defHttp } from '/@/utils/http/axios';
|
||||
import { getMenuListResultModel } from './model/menuModel';
|
||||
import { defHttp } from '/@/utils/http/axios'
|
||||
import { getMenuListResultModel } from './model/menuModel'
|
||||
|
||||
enum Api {
|
||||
GetMenuList = '/getMenuList',
|
||||
@@ -10,5 +10,5 @@ enum Api {
|
||||
*/
|
||||
|
||||
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 {
|
||||
path: string;
|
||||
component: any;
|
||||
meta: RouteMeta;
|
||||
name?: string;
|
||||
alias?: string | string[];
|
||||
redirect?: string;
|
||||
caseSensitive?: boolean;
|
||||
children?: RouteItem[];
|
||||
path: string
|
||||
component: any
|
||||
meta: RouteMeta
|
||||
name?: string
|
||||
alias?: string | string[]
|
||||
redirect?: string
|
||||
caseSensitive?: boolean
|
||||
children?: RouteItem[]
|
||||
}
|
||||
|
||||
/**
|
||||
* @description: Get menu return value
|
||||
*/
|
||||
export type getMenuListResultModel = RouteItem[];
|
||||
export type getMenuListResultModel = RouteItem[]
|
||||
|
@@ -1,5 +1,5 @@
|
||||
export interface UploadApiResult {
|
||||
message: string;
|
||||
code: number;
|
||||
url: string;
|
||||
message: string
|
||||
code: number
|
||||
url: string
|
||||
}
|
||||
|
@@ -2,37 +2,37 @@
|
||||
* @description: Login interface parameters
|
||||
*/
|
||||
export interface LoginParams {
|
||||
username: string;
|
||||
password: string;
|
||||
username: string
|
||||
password: string
|
||||
}
|
||||
|
||||
export interface RoleInfo {
|
||||
roleName: string;
|
||||
value: string;
|
||||
roleName: string
|
||||
value: string
|
||||
}
|
||||
|
||||
/**
|
||||
* @description: Login interface return value
|
||||
*/
|
||||
export interface LoginResultModel {
|
||||
userId: string | number;
|
||||
token: string;
|
||||
role: RoleInfo;
|
||||
userId: string | number
|
||||
token: string
|
||||
role: RoleInfo
|
||||
}
|
||||
|
||||
/**
|
||||
* @description: Get user information return value
|
||||
*/
|
||||
export interface GetUserInfoModel {
|
||||
roles: RoleInfo[];
|
||||
roles: RoleInfo[]
|
||||
// 用户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 { LoginParams, LoginResultModel, GetUserInfoModel } from './model/userModel';
|
||||
import { defHttp } from '/@/utils/http/axios'
|
||||
import { LoginParams, LoginResultModel, GetUserInfoModel } from './model/userModel'
|
||||
|
||||
import { ErrorMessageMode } from '/#/axios';
|
||||
import { ErrorMessageMode } from '/#/axios'
|
||||
|
||||
enum Api {
|
||||
Login = '/login',
|
||||
@@ -23,22 +23,22 @@ export function loginApi(params: LoginParams, mode: ErrorMessageMode = 'modal')
|
||||
{
|
||||
errorMessageMode: mode,
|
||||
},
|
||||
);
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* @description: 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() {
|
||||
return defHttp.get<string[]>({ url: Api.GetPermCode });
|
||||
return defHttp.get<string[]>({ url: Api.GetPermCode })
|
||||
}
|
||||
|
||||
export function doLogout() {
|
||||
return defHttp.get({ url: Api.Logout });
|
||||
return defHttp.get({ url: Api.Logout })
|
||||
}
|
||||
|
||||
export function testRetry() {
|
||||
@@ -51,5 +51,5 @@ export function testRetry() {
|
||||
waitTime: 1000,
|
||||
},
|
||||
},
|
||||
);
|
||||
)
|
||||
}
|
||||
|
@@ -1,15 +1,15 @@
|
||||
import { withInstall } from '/@/utils';
|
||||
import { withInstall } from '/@/utils'
|
||||
|
||||
import appLogo from './src/AppLogo.vue';
|
||||
import appProvider from './src/AppProvider.vue';
|
||||
import appSearch from './src/search/AppSearch.vue';
|
||||
import appLocalePicker from './src/AppLocalePicker.vue';
|
||||
import appDarkModeToggle from './src/AppDarkModeToggle.vue';
|
||||
import appLogo from './src/AppLogo.vue'
|
||||
import appProvider from './src/AppProvider.vue'
|
||||
import appSearch from './src/search/AppSearch.vue'
|
||||
import appLocalePicker from './src/AppLocalePicker.vue'
|
||||
import appDarkModeToggle from './src/AppDarkModeToggle.vue'
|
||||
|
||||
export { useAppProviderContext } from './src/useAppContext';
|
||||
export { useAppProviderContext } from './src/useAppContext'
|
||||
|
||||
export const AppLogo = withInstall(appLogo);
|
||||
export const AppProvider = withInstall(appProvider);
|
||||
export const AppSearch = withInstall(appSearch);
|
||||
export const AppLocalePicker = withInstall(appLocalePicker);
|
||||
export const AppDarkModeToggle = withInstall(appDarkModeToggle);
|
||||
export const AppLogo = withInstall(appLogo)
|
||||
export const AppProvider = withInstall(appProvider)
|
||||
export const AppSearch = withInstall(appSearch)
|
||||
export const AppLocalePicker = withInstall(appLocalePicker)
|
||||
export const AppDarkModeToggle = withInstall(appDarkModeToggle)
|
||||
|
@@ -6,32 +6,32 @@
|
||||
</div>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import { computed, unref } from 'vue';
|
||||
import { SvgIcon } from '/@/components/Icon';
|
||||
import { useDesign } from '/@/hooks/web/useDesign';
|
||||
import { useRootSetting } from '/@/hooks/setting/useRootSetting';
|
||||
import { updateHeaderBgColor, updateSidebarBgColor } from '/@/logics/theme/updateBackground';
|
||||
import { updateDarkTheme } from '/@/logics/theme/dark';
|
||||
import { ThemeEnum } from '/@/enums/appEnum';
|
||||
import { computed, unref } from 'vue'
|
||||
import { SvgIcon } from '/@/components/Icon'
|
||||
import { useDesign } from '/@/hooks/web/useDesign'
|
||||
import { useRootSetting } from '/@/hooks/setting/useRootSetting'
|
||||
import { updateHeaderBgColor, updateSidebarBgColor } from '/@/logics/theme/updateBackground'
|
||||
import { updateDarkTheme } from '/@/logics/theme/dark'
|
||||
import { ThemeEnum } from '/@/enums/appEnum'
|
||||
|
||||
const { prefixCls } = useDesign('dark-switch');
|
||||
const { getDarkMode, setDarkMode, getShowDarkModeToggle } = useRootSetting();
|
||||
const { prefixCls } = useDesign('dark-switch')
|
||||
const { getDarkMode, setDarkMode, getShowDarkModeToggle } = useRootSetting()
|
||||
|
||||
const isDark = computed(() => getDarkMode.value === ThemeEnum.DARK);
|
||||
const isDark = computed(() => getDarkMode.value === ThemeEnum.DARK)
|
||||
|
||||
const getClass = computed(() => [
|
||||
prefixCls,
|
||||
{
|
||||
[`${prefixCls}--dark`]: unref(isDark),
|
||||
},
|
||||
]);
|
||||
])
|
||||
|
||||
function toggleDarkMode() {
|
||||
const darkMode = getDarkMode.value === ThemeEnum.DARK ? ThemeEnum.LIGHT : ThemeEnum.DARK;
|
||||
setDarkMode(darkMode);
|
||||
updateDarkTheme(darkMode);
|
||||
updateHeaderBgColor();
|
||||
updateSidebarBgColor();
|
||||
const darkMode = getDarkMode.value === ThemeEnum.DARK ? ThemeEnum.LIGHT : ThemeEnum.DARK
|
||||
setDarkMode(darkMode)
|
||||
updateDarkTheme(darkMode)
|
||||
updateHeaderBgColor()
|
||||
updateSidebarBgColor()
|
||||
}
|
||||
</script>
|
||||
<style lang="less" scoped>
|
||||
|
@@ -18,13 +18,13 @@
|
||||
</Dropdown>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import type { LocaleType } from '/#/config';
|
||||
import type { DropMenu } from '/@/components/Dropdown';
|
||||
import { ref, watchEffect, unref, computed } from 'vue';
|
||||
import { Dropdown } from '/@/components/Dropdown';
|
||||
import { Icon } from '/@/components/Icon';
|
||||
import { useLocale } from '/@/locales/useLocale';
|
||||
import { localeList } from '/@/settings/localeSetting';
|
||||
import type { LocaleType } from '/#/config'
|
||||
import type { DropMenu } from '/@/components/Dropdown'
|
||||
import { ref, watchEffect, unref, computed } from 'vue'
|
||||
import { Dropdown } from '/@/components/Dropdown'
|
||||
import { Icon } from '/@/components/Icon'
|
||||
import { useLocale } from '/@/locales/useLocale'
|
||||
import { localeList } from '/@/settings/localeSetting'
|
||||
|
||||
const props = defineProps({
|
||||
/**
|
||||
@@ -35,35 +35,35 @@
|
||||
* Whether to refresh the interface when changing
|
||||
*/
|
||||
reload: { type: Boolean },
|
||||
});
|
||||
})
|
||||
|
||||
const selectedKeys = ref<string[]>([]);
|
||||
const selectedKeys = ref<string[]>([])
|
||||
|
||||
const { changeLocale, getLocale } = useLocale();
|
||||
const { changeLocale, getLocale } = useLocale()
|
||||
|
||||
const getLocaleText = computed(() => {
|
||||
const key = selectedKeys.value[0];
|
||||
const key = selectedKeys.value[0]
|
||||
if (!key) {
|
||||
return '';
|
||||
return ''
|
||||
}
|
||||
return localeList.find((item) => item.event === key)?.text;
|
||||
});
|
||||
return localeList.find((item) => item.event === key)?.text
|
||||
})
|
||||
|
||||
watchEffect(() => {
|
||||
selectedKeys.value = [unref(getLocale)];
|
||||
});
|
||||
selectedKeys.value = [unref(getLocale)]
|
||||
})
|
||||
|
||||
async function toggleLocale(lang: LocaleType | string) {
|
||||
await changeLocale(lang as LocaleType);
|
||||
selectedKeys.value = [lang as string];
|
||||
props.reload && location.reload();
|
||||
await changeLocale(lang as LocaleType)
|
||||
selectedKeys.value = [lang as string]
|
||||
props.reload && location.reload()
|
||||
}
|
||||
|
||||
function handleMenuEvent(menu: DropMenu) {
|
||||
if (unref(getLocale) === menu.event) {
|
||||
return;
|
||||
return
|
||||
}
|
||||
toggleLocale(menu.event as string);
|
||||
toggleLocale(menu.event as string)
|
||||
}
|
||||
</script>
|
||||
|
||||
|
@@ -11,13 +11,13 @@
|
||||
</div>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import { computed, unref } from 'vue';
|
||||
import { useGlobSetting } from '/@/hooks/setting';
|
||||
import { useGo } from '/@/hooks/web/usePage';
|
||||
import { useMenuSetting } from '/@/hooks/setting/useMenuSetting';
|
||||
import { useDesign } from '/@/hooks/web/useDesign';
|
||||
import { PageEnum } from '/@/enums/pageEnum';
|
||||
import { useUserStore } from '/@/store/modules/user';
|
||||
import { computed, unref } from 'vue'
|
||||
import { useGlobSetting } from '/@/hooks/setting'
|
||||
import { useGo } from '/@/hooks/web/usePage'
|
||||
import { useMenuSetting } from '/@/hooks/setting/useMenuSetting'
|
||||
import { useDesign } from '/@/hooks/web/useDesign'
|
||||
import { PageEnum } from '/@/enums/pageEnum'
|
||||
import { useUserStore } from '/@/store/modules/user'
|
||||
|
||||
const props = defineProps({
|
||||
/**
|
||||
@@ -32,29 +32,29 @@
|
||||
* The title is also displayed when the menu is collapsed
|
||||
*/
|
||||
alwaysShowTitle: { type: Boolean },
|
||||
});
|
||||
})
|
||||
|
||||
const { prefixCls } = useDesign('app-logo');
|
||||
const { getCollapsedShowTitle } = useMenuSetting();
|
||||
const userStore = useUserStore();
|
||||
const { title } = useGlobSetting();
|
||||
const go = useGo();
|
||||
const { prefixCls } = useDesign('app-logo')
|
||||
const { getCollapsedShowTitle } = useMenuSetting()
|
||||
const userStore = useUserStore()
|
||||
const { title } = useGlobSetting()
|
||||
const go = useGo()
|
||||
|
||||
const getAppLogoClass = computed(() => [
|
||||
prefixCls,
|
||||
props.theme,
|
||||
{ 'collapsed-show-title': unref(getCollapsedShowTitle) },
|
||||
]);
|
||||
])
|
||||
|
||||
const getTitleClass = computed(() => [
|
||||
`${prefixCls}__title`,
|
||||
{
|
||||
'xs:opacity-0': !props.alwaysShowTitle,
|
||||
},
|
||||
]);
|
||||
])
|
||||
|
||||
function goHome() {
|
||||
go(userStore.getUserInfo.homePath || PageEnum.BASE_HOME);
|
||||
go(userStore.getUserInfo.homePath || PageEnum.BASE_HOME)
|
||||
}
|
||||
</script>
|
||||
<style lang="less" scoped>
|
||||
|
@@ -1,41 +1,41 @@
|
||||
<script lang="ts">
|
||||
import { defineComponent, toRefs, ref, unref } from 'vue';
|
||||
import { createAppProviderContext } from './useAppContext';
|
||||
import { createBreakpointListen } from '/@/hooks/event/useBreakpoint';
|
||||
import { prefixCls } from '/@/settings/designSetting';
|
||||
import { useAppStore } from '/@/store/modules/app';
|
||||
import { MenuModeEnum, MenuTypeEnum } from '/@/enums/menuEnum';
|
||||
import { defineComponent, toRefs, ref, unref } from 'vue'
|
||||
import { createAppProviderContext } from './useAppContext'
|
||||
import { createBreakpointListen } from '/@/hooks/event/useBreakpoint'
|
||||
import { prefixCls } from '/@/settings/designSetting'
|
||||
import { useAppStore } from '/@/store/modules/app'
|
||||
import { MenuModeEnum, MenuTypeEnum } from '/@/enums/menuEnum'
|
||||
|
||||
const props = {
|
||||
/**
|
||||
* class style prefix
|
||||
*/
|
||||
prefixCls: { type: String, default: prefixCls },
|
||||
};
|
||||
}
|
||||
|
||||
export default defineComponent({
|
||||
name: 'AppProvider',
|
||||
inheritAttrs: false,
|
||||
props,
|
||||
setup(props, { slots }) {
|
||||
const isMobile = ref(false);
|
||||
const isSetState = ref(false);
|
||||
const isMobile = ref(false)
|
||||
const isSetState = ref(false)
|
||||
|
||||
const appStore = useAppStore();
|
||||
const appStore = useAppStore()
|
||||
|
||||
// Monitor screen breakpoint information changes
|
||||
createBreakpointListen(({ screenMap, sizeEnum, width }) => {
|
||||
const lgWidth = screenMap.get(sizeEnum.LG);
|
||||
const lgWidth = screenMap.get(sizeEnum.LG)
|
||||
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
|
||||
createAppProviderContext({ prefixCls, isMobile });
|
||||
createAppProviderContext({ prefixCls, isMobile })
|
||||
|
||||
/**
|
||||
* Used to maintain the state before the window changes
|
||||
@@ -43,7 +43,7 @@
|
||||
function handleRestoreState() {
|
||||
if (unref(isMobile)) {
|
||||
if (!unref(isSetState)) {
|
||||
isSetState.value = true;
|
||||
isSetState.value = true
|
||||
const {
|
||||
menuSetting: {
|
||||
type: menuType,
|
||||
@@ -51,20 +51,20 @@
|
||||
collapsed: menuCollapsed,
|
||||
split: menuSplit,
|
||||
},
|
||||
} = appStore.getProjectConfig;
|
||||
} = appStore.getProjectConfig
|
||||
appStore.setProjectConfig({
|
||||
menuSetting: {
|
||||
type: MenuTypeEnum.SIDEBAR,
|
||||
mode: MenuModeEnum.INLINE,
|
||||
split: false,
|
||||
},
|
||||
});
|
||||
appStore.setBeforeMiniInfo({ menuMode, menuCollapsed, menuType, menuSplit });
|
||||
})
|
||||
appStore.setBeforeMiniInfo({ menuMode, menuCollapsed, menuType, menuSplit })
|
||||
}
|
||||
} else {
|
||||
if (unref(isSetState)) {
|
||||
isSetState.value = false;
|
||||
const { menuMode, menuCollapsed, menuType, menuSplit } = appStore.getBeforeMiniInfo;
|
||||
isSetState.value = false
|
||||
const { menuMode, menuCollapsed, menuType, menuSplit } = appStore.getBeforeMiniInfo
|
||||
appStore.setProjectConfig({
|
||||
menuSetting: {
|
||||
type: menuType,
|
||||
@@ -72,11 +72,11 @@
|
||||
collapsed: menuCollapsed,
|
||||
split: menuSplit,
|
||||
},
|
||||
});
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
return () => slots.default?.();
|
||||
return () => slots.default?.()
|
||||
},
|
||||
});
|
||||
})
|
||||
</script>
|
||||
|
@@ -1,18 +1,18 @@
|
||||
<script lang="tsx">
|
||||
import { defineComponent, ref, unref } from 'vue';
|
||||
import { Tooltip } from 'ant-design-vue';
|
||||
import { SearchOutlined } from '@ant-design/icons-vue';
|
||||
import AppSearchModal from './AppSearchModal.vue';
|
||||
import { useI18n } from '/@/hooks/web/useI18n';
|
||||
import { defineComponent, ref, unref } from 'vue'
|
||||
import { Tooltip } from 'ant-design-vue'
|
||||
import { SearchOutlined } from '@ant-design/icons-vue'
|
||||
import AppSearchModal from './AppSearchModal.vue'
|
||||
import { useI18n } from '/@/hooks/web/useI18n'
|
||||
|
||||
export default defineComponent({
|
||||
name: 'AppSearch',
|
||||
setup() {
|
||||
const showModal = ref(false);
|
||||
const { t } = useI18n();
|
||||
const showModal = ref(false)
|
||||
const { t } = useI18n()
|
||||
|
||||
function changeModal(show: boolean) {
|
||||
showModal.value = show;
|
||||
showModal.value = show
|
||||
}
|
||||
|
||||
return () => {
|
||||
@@ -26,8 +26,8 @@
|
||||
</Tooltip>
|
||||
<AppSearchModal onClose={changeModal.bind(null, false)} visible={unref(showModal)} />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
)
|
||||
}
|
||||
},
|
||||
});
|
||||
})
|
||||
</script>
|
||||
|
@@ -11,11 +11,11 @@
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import AppSearchKeyItem from './AppSearchKeyItem.vue';
|
||||
import { useDesign } from '/@/hooks/web/useDesign';
|
||||
import { useI18n } from '/@/hooks/web/useI18n';
|
||||
const { prefixCls } = useDesign('app-search-footer');
|
||||
const { t } = useI18n();
|
||||
import AppSearchKeyItem from './AppSearchKeyItem.vue'
|
||||
import { useDesign } from '/@/hooks/web/useDesign'
|
||||
import { useI18n } from '/@/hooks/web/useI18n'
|
||||
const { prefixCls } = useDesign('app-search-footer')
|
||||
const { t } = useI18n()
|
||||
</script>
|
||||
<style lang="less" scoped>
|
||||
@prefix-cls: ~'@{namespace}-app-search-footer';
|
||||
|
@@ -4,8 +4,8 @@
|
||||
</span>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import { Icon } from '/@/components/Icon';
|
||||
import { Icon } from '/@/components/Icon'
|
||||
defineProps({
|
||||
icon: String,
|
||||
});
|
||||
})
|
||||
</script>
|
||||
|
@@ -58,36 +58,36 @@
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { computed, unref, ref, watch, nextTick } from 'vue';
|
||||
import { SearchOutlined } from '@ant-design/icons-vue';
|
||||
import AppSearchFooter from './AppSearchFooter.vue';
|
||||
import Icon from '/@/components/Icon';
|
||||
import { computed, unref, ref, watch, nextTick } from 'vue'
|
||||
import { SearchOutlined } from '@ant-design/icons-vue'
|
||||
import AppSearchFooter from './AppSearchFooter.vue'
|
||||
import Icon from '/@/components/Icon'
|
||||
// @ts-ignore
|
||||
import vClickOutside from '/@/directives/clickOutside';
|
||||
import { useDesign } from '/@/hooks/web/useDesign';
|
||||
import { useRefs } from '/@/hooks/core/useRefs';
|
||||
import { useMenuSearch } from './useMenuSearch';
|
||||
import { useI18n } from '/@/hooks/web/useI18n';
|
||||
import { useAppInject } from '/@/hooks/web/useAppInject';
|
||||
import vClickOutside from '/@/directives/clickOutside'
|
||||
import { useDesign } from '/@/hooks/web/useDesign'
|
||||
import { useRefs } from '/@/hooks/core/useRefs'
|
||||
import { useMenuSearch } from './useMenuSearch'
|
||||
import { useI18n } from '/@/hooks/web/useI18n'
|
||||
import { useAppInject } from '/@/hooks/web/useAppInject'
|
||||
|
||||
const props = defineProps({
|
||||
visible: { type: Boolean },
|
||||
});
|
||||
})
|
||||
|
||||
const emit = defineEmits(['close']);
|
||||
const emit = defineEmits(['close'])
|
||||
|
||||
const scrollWrap = ref(null);
|
||||
const inputRef = ref<Nullable<HTMLElement>>(null);
|
||||
const scrollWrap = ref(null)
|
||||
const inputRef = ref<Nullable<HTMLElement>>(null)
|
||||
|
||||
const { t } = useI18n();
|
||||
const { prefixCls } = useDesign('app-search-modal');
|
||||
const [refs, setRefs] = useRefs();
|
||||
const { getIsMobile } = useAppInject();
|
||||
const { t } = useI18n()
|
||||
const { prefixCls } = useDesign('app-search-modal')
|
||||
const [refs, setRefs] = useRefs()
|
||||
const { getIsMobile } = useAppInject()
|
||||
|
||||
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(() => {
|
||||
return [
|
||||
@@ -95,22 +95,22 @@
|
||||
{
|
||||
[`${prefixCls}--mobile`]: unref(getIsMobile),
|
||||
},
|
||||
];
|
||||
});
|
||||
]
|
||||
})
|
||||
|
||||
watch(
|
||||
() => props.visible,
|
||||
(visible: boolean) => {
|
||||
visible &&
|
||||
nextTick(() => {
|
||||
unref(inputRef)?.focus();
|
||||
});
|
||||
unref(inputRef)?.focus()
|
||||
})
|
||||
},
|
||||
);
|
||||
)
|
||||
|
||||
function handleClose() {
|
||||
searchResult.value = [];
|
||||
emit('close');
|
||||
searchResult.value = []
|
||||
emit('close')
|
||||
}
|
||||
</script>
|
||||
<style lang="less" scoped>
|
||||
|
@@ -1,166 +1,166 @@
|
||||
import type { Menu } from '/@/router/types';
|
||||
import { ref, onBeforeMount, unref, Ref, nextTick } from 'vue';
|
||||
import { getMenus } from '/@/router/menus';
|
||||
import { cloneDeep } from 'lodash-es';
|
||||
import { filter, forEach } from '/@/utils/helper/treeHelper';
|
||||
import { useGo } from '/@/hooks/web/usePage';
|
||||
import { useScrollTo } from '/@/hooks/event/useScrollTo';
|
||||
import { onKeyStroke, useDebounceFn } from '@vueuse/core';
|
||||
import { useI18n } from '/@/hooks/web/useI18n';
|
||||
import type { Menu } from '/@/router/types'
|
||||
import { ref, onBeforeMount, unref, Ref, nextTick } from 'vue'
|
||||
import { getMenus } from '/@/router/menus'
|
||||
import { cloneDeep } from 'lodash-es'
|
||||
import { filter, forEach } from '/@/utils/helper/treeHelper'
|
||||
import { useGo } from '/@/hooks/web/usePage'
|
||||
import { useScrollTo } from '/@/hooks/event/useScrollTo'
|
||||
import { onKeyStroke, useDebounceFn } from '@vueuse/core'
|
||||
import { useI18n } from '/@/hooks/web/useI18n'
|
||||
|
||||
export interface SearchResult {
|
||||
name: string;
|
||||
path: string;
|
||||
icon?: string;
|
||||
name: string
|
||||
path: string
|
||||
icon?: string
|
||||
}
|
||||
|
||||
// Translate special characters
|
||||
function transform(c: string) {
|
||||
const code: string[] = ['$', '(', ')', '*', '+', '.', '[', ']', '?', '\\', '^', '{', '}', '|'];
|
||||
return code.includes(c) ? `\\${c}` : c;
|
||||
const code: string[] = ['$', '(', ')', '*', '+', '.', '[', ']', '?', '\\', '^', '{', '}', '|']
|
||||
return code.includes(c) ? `\\${c}` : c
|
||||
}
|
||||
|
||||
function createSearchReg(key: string) {
|
||||
const keys = [...key].map((item) => transform(item));
|
||||
const str = ['', ...keys, ''].join('.*');
|
||||
return new RegExp(str);
|
||||
const keys = [...key].map((item) => transform(item))
|
||||
const str = ['', ...keys, ''].join('.*')
|
||||
return new RegExp(str)
|
||||
}
|
||||
|
||||
export function useMenuSearch(refs: Ref<HTMLElement[]>, scrollWrap: Ref<ElRef>, emit: EmitType) {
|
||||
const searchResult = ref<SearchResult[]>([]);
|
||||
const keyword = ref('');
|
||||
const activeIndex = ref(-1);
|
||||
const searchResult = ref<SearchResult[]>([])
|
||||
const keyword = ref('')
|
||||
const activeIndex = ref(-1)
|
||||
|
||||
let menuList: Menu[] = [];
|
||||
let menuList: Menu[] = []
|
||||
|
||||
const { t } = useI18n();
|
||||
const go = useGo();
|
||||
const handleSearch = useDebounceFn(search, 200);
|
||||
const { t } = useI18n()
|
||||
const go = useGo()
|
||||
const handleSearch = useDebounceFn(search, 200)
|
||||
|
||||
onBeforeMount(async () => {
|
||||
const list = await getMenus();
|
||||
menuList = cloneDeep(list);
|
||||
const list = await getMenus()
|
||||
menuList = cloneDeep(list)
|
||||
forEach(menuList, (item) => {
|
||||
item.name = t(item.name);
|
||||
});
|
||||
});
|
||||
item.name = t(item.name)
|
||||
})
|
||||
})
|
||||
|
||||
function search(e: ChangeEvent) {
|
||||
e?.stopPropagation();
|
||||
const key = e.target.value;
|
||||
keyword.value = key.trim();
|
||||
e?.stopPropagation()
|
||||
const key = e.target.value
|
||||
keyword.value = key.trim()
|
||||
if (!key) {
|
||||
searchResult.value = [];
|
||||
return;
|
||||
searchResult.value = []
|
||||
return
|
||||
}
|
||||
const reg = createSearchReg(unref(keyword));
|
||||
const reg = createSearchReg(unref(keyword))
|
||||
const filterMenu = filter(menuList, (item) => {
|
||||
return reg.test(item.name) && !item.hideMenu;
|
||||
});
|
||||
searchResult.value = handlerSearchResult(filterMenu, reg);
|
||||
activeIndex.value = 0;
|
||||
return reg.test(item.name) && !item.hideMenu
|
||||
})
|
||||
searchResult.value = handlerSearchResult(filterMenu, reg)
|
||||
activeIndex.value = 0
|
||||
}
|
||||
|
||||
function handlerSearchResult(filterMenu: Menu[], reg: RegExp, parent?: Menu) {
|
||||
const ret: SearchResult[] = [];
|
||||
const ret: SearchResult[] = []
|
||||
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)) {
|
||||
ret.push({
|
||||
name: parent?.name ? `${parent.name} > ${name}` : name,
|
||||
path,
|
||||
icon,
|
||||
});
|
||||
})
|
||||
}
|
||||
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
|
||||
function handleMouseenter(e: any) {
|
||||
const index = e.target.dataset.index;
|
||||
activeIndex.value = Number(index);
|
||||
const index = e.target.dataset.index
|
||||
activeIndex.value = Number(index)
|
||||
}
|
||||
|
||||
// Arrow key up
|
||||
function handleUp() {
|
||||
if (!searchResult.value.length) return;
|
||||
activeIndex.value--;
|
||||
if (!searchResult.value.length) return
|
||||
activeIndex.value--
|
||||
if (activeIndex.value < 0) {
|
||||
activeIndex.value = searchResult.value.length - 1;
|
||||
activeIndex.value = searchResult.value.length - 1
|
||||
}
|
||||
handleScroll();
|
||||
handleScroll()
|
||||
}
|
||||
|
||||
// Arrow key down
|
||||
function handleDown() {
|
||||
if (!searchResult.value.length) return;
|
||||
activeIndex.value++;
|
||||
if (!searchResult.value.length) return
|
||||
activeIndex.value++
|
||||
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
|
||||
// the scroll bar needs to scroll automatically
|
||||
function handleScroll() {
|
||||
const refList = unref(refs);
|
||||
const refList = unref(refs)
|
||||
if (!refList || !Array.isArray(refList) || refList.length === 0 || !unref(scrollWrap)) {
|
||||
return;
|
||||
return
|
||||
}
|
||||
|
||||
const index = unref(activeIndex);
|
||||
const currentRef = refList[index];
|
||||
const index = unref(activeIndex)
|
||||
const currentRef = refList[index]
|
||||
if (!currentRef) {
|
||||
return;
|
||||
return
|
||||
}
|
||||
const wrapEl = unref(scrollWrap);
|
||||
const wrapEl = unref(scrollWrap)
|
||||
if (!wrapEl) {
|
||||
return;
|
||||
return
|
||||
}
|
||||
const scrollHeight = currentRef.offsetTop + currentRef.offsetHeight;
|
||||
const wrapHeight = wrapEl.offsetHeight;
|
||||
const scrollHeight = currentRef.offsetTop + currentRef.offsetHeight
|
||||
const wrapHeight = wrapEl.offsetHeight
|
||||
const { start } = useScrollTo({
|
||||
el: wrapEl,
|
||||
duration: 100,
|
||||
to: scrollHeight - wrapHeight,
|
||||
});
|
||||
start();
|
||||
})
|
||||
start()
|
||||
}
|
||||
|
||||
// enter keyboard event
|
||||
async function handleEnter() {
|
||||
if (!searchResult.value.length) {
|
||||
return;
|
||||
return
|
||||
}
|
||||
const result = unref(searchResult);
|
||||
const index = unref(activeIndex);
|
||||
const result = unref(searchResult)
|
||||
const index = unref(activeIndex)
|
||||
if (result.length === 0 || index < 0) {
|
||||
return;
|
||||
return
|
||||
}
|
||||
const to = result[index];
|
||||
handleClose();
|
||||
await nextTick();
|
||||
go(to.path);
|
||||
const to = result[index]
|
||||
handleClose()
|
||||
await nextTick()
|
||||
go(to.path)
|
||||
}
|
||||
|
||||
// close search modal
|
||||
function handleClose() {
|
||||
searchResult.value = [];
|
||||
emit('close');
|
||||
searchResult.value = []
|
||||
emit('close')
|
||||
}
|
||||
|
||||
// enter search
|
||||
onKeyStroke('Enter', handleEnter);
|
||||
onKeyStroke('Enter', handleEnter)
|
||||
// Monitor keyboard arrow keys
|
||||
onKeyStroke('ArrowUp', handleUp);
|
||||
onKeyStroke('ArrowDown', handleDown);
|
||||
onKeyStroke('ArrowUp', handleUp)
|
||||
onKeyStroke('ArrowDown', handleDown)
|
||||
// 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 { createContext, useContext } from '/@/hooks/core/useContext';
|
||||
import { InjectionKey, Ref } from 'vue'
|
||||
import { createContext, useContext } from '/@/hooks/core/useContext'
|
||||
|
||||
export interface AppProviderContextProps {
|
||||
prefixCls: Ref<string>;
|
||||
isMobile: Ref<boolean>;
|
||||
prefixCls: Ref<string>
|
||||
isMobile: Ref<boolean>
|
||||
}
|
||||
|
||||
const key: InjectionKey<AppProviderContextProps> = Symbol();
|
||||
const key: InjectionKey<AppProviderContextProps> = Symbol()
|
||||
|
||||
export function createAppProviderContext(context: AppProviderContextProps) {
|
||||
return createContext<AppProviderContextProps>(context, key);
|
||||
return createContext<AppProviderContextProps>(context, key)
|
||||
}
|
||||
|
||||
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 basicArrow from './src/BasicArrow.vue';
|
||||
import basicTitle from './src/BasicTitle.vue';
|
||||
import basicHelp from './src/BasicHelp.vue';
|
||||
import { withInstall } from '/@/utils'
|
||||
import basicArrow from './src/BasicArrow.vue'
|
||||
import basicTitle from './src/BasicTitle.vue'
|
||||
import basicHelp from './src/BasicHelp.vue'
|
||||
|
||||
export const BasicArrow = withInstall(basicArrow);
|
||||
export const BasicTitle = withInstall(basicTitle);
|
||||
export const BasicHelp = withInstall(basicHelp);
|
||||
export const BasicArrow = withInstall(basicArrow)
|
||||
export const BasicTitle = withInstall(basicTitle)
|
||||
export const BasicHelp = withInstall(basicHelp)
|
||||
|
@@ -8,9 +8,9 @@
|
||||
</span>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import { computed } from 'vue';
|
||||
import { Icon } from '/@/components/Icon';
|
||||
import { useDesign } from '/@/hooks/web/useDesign';
|
||||
import { computed } from 'vue'
|
||||
import { Icon } from '/@/components/Icon'
|
||||
import { useDesign } from '/@/hooks/web/useDesign'
|
||||
|
||||
const props = defineProps({
|
||||
/**
|
||||
@@ -29,13 +29,13 @@
|
||||
* Cancel padding/margin for inline
|
||||
*/
|
||||
inset: { type: Boolean },
|
||||
});
|
||||
})
|
||||
|
||||
const { prefixCls } = useDesign('basic-arrow');
|
||||
const { prefixCls } = useDesign('basic-arrow')
|
||||
|
||||
// get component class
|
||||
const getClass = computed(() => {
|
||||
const { expand, up, down, inset } = props;
|
||||
const { expand, up, down, inset } = props
|
||||
return [
|
||||
prefixCls,
|
||||
{
|
||||
@@ -44,8 +44,8 @@
|
||||
inset,
|
||||
down,
|
||||
},
|
||||
];
|
||||
});
|
||||
]
|
||||
})
|
||||
</script>
|
||||
<style lang="less" scoped>
|
||||
@prefix-cls: ~'@{namespace}-basic-arrow';
|
||||
|
@@ -1,12 +1,12 @@
|
||||
<script lang="tsx">
|
||||
import type { CSSProperties, PropType } from 'vue';
|
||||
import { defineComponent, computed, unref } from 'vue';
|
||||
import { Tooltip } from 'ant-design-vue';
|
||||
import { InfoCircleOutlined } from '@ant-design/icons-vue';
|
||||
import { getPopupContainer } from '/@/utils';
|
||||
import { isString, isArray } from '/@/utils/is';
|
||||
import { getSlot } from '/@/utils/helper/tsxHelper';
|
||||
import { useDesign } from '/@/hooks/web/useDesign';
|
||||
import type { CSSProperties, PropType } from 'vue'
|
||||
import { defineComponent, computed, unref } from 'vue'
|
||||
import { Tooltip } from 'ant-design-vue'
|
||||
import { InfoCircleOutlined } from '@ant-design/icons-vue'
|
||||
import { getPopupContainer } from '/@/utils'
|
||||
import { isString, isArray } from '/@/utils/is'
|
||||
import { getSlot } from '/@/utils/helper/tsxHelper'
|
||||
import { useDesign } from '/@/hooks/web/useDesign'
|
||||
|
||||
const props = {
|
||||
/**
|
||||
@@ -37,26 +37,26 @@
|
||||
* Help text list
|
||||
*/
|
||||
text: { type: [Array, String] as PropType<string[] | string> },
|
||||
};
|
||||
}
|
||||
|
||||
export default defineComponent({
|
||||
name: 'BasicHelp',
|
||||
components: { Tooltip },
|
||||
props,
|
||||
setup(props, { slots }) {
|
||||
const { prefixCls } = useDesign('basic-help');
|
||||
const { prefixCls } = useDesign('basic-help')
|
||||
|
||||
const getTooltipStyle = computed(
|
||||
(): CSSProperties => ({ color: props.color, fontSize: props.fontSize }),
|
||||
);
|
||||
)
|
||||
|
||||
const getOverlayStyle = computed((): CSSProperties => ({ maxWidth: props.maxWidth }));
|
||||
const getOverlayStyle = computed((): CSSProperties => ({ maxWidth: props.maxWidth }))
|
||||
|
||||
function renderTitle() {
|
||||
const textList = props.text;
|
||||
const textList = props.text
|
||||
|
||||
if (isString(textList)) {
|
||||
return <p>{textList}</p>;
|
||||
return <p>{textList}</p>
|
||||
}
|
||||
|
||||
if (isArray(textList)) {
|
||||
@@ -68,10 +68,10 @@
|
||||
{text}
|
||||
</>
|
||||
</p>
|
||||
);
|
||||
});
|
||||
)
|
||||
})
|
||||
}
|
||||
return null;
|
||||
return null
|
||||
}
|
||||
|
||||
return () => {
|
||||
@@ -86,10 +86,10 @@
|
||||
>
|
||||
<span class={prefixCls}>{getSlot(slots) || <InfoCircleOutlined />}</span>
|
||||
</Tooltip>
|
||||
);
|
||||
};
|
||||
)
|
||||
}
|
||||
},
|
||||
});
|
||||
})
|
||||
</script>
|
||||
<style lang="less">
|
||||
@prefix-cls: ~'@{namespace}-basic-help';
|
||||
|
@@ -5,10 +5,10 @@
|
||||
</span>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import type { PropType } from 'vue';
|
||||
import { useSlots, computed } from 'vue';
|
||||
import BasicHelp from './BasicHelp.vue';
|
||||
import { useDesign } from '/@/hooks/web/useDesign';
|
||||
import type { PropType } from 'vue'
|
||||
import { useSlots, computed } from 'vue'
|
||||
import BasicHelp from './BasicHelp.vue'
|
||||
import { useDesign } from '/@/hooks/web/useDesign'
|
||||
|
||||
const props = defineProps({
|
||||
/**
|
||||
@@ -29,15 +29,15 @@
|
||||
* @default: false
|
||||
*/
|
||||
normal: { type: Boolean },
|
||||
});
|
||||
})
|
||||
|
||||
const { prefixCls } = useDesign('basic-title');
|
||||
const slots = useSlots();
|
||||
const { prefixCls } = useDesign('basic-title')
|
||||
const slots = useSlots()
|
||||
const getClass = computed(() => [
|
||||
prefixCls,
|
||||
{ [`${prefixCls}-show-span`]: props.span && slots.default },
|
||||
{ [`${prefixCls}-normal`]: props.normal },
|
||||
]);
|
||||
])
|
||||
</script>
|
||||
<style lang="less" scoped>
|
||||
@prefix-cls: ~'@{namespace}-basic-title';
|
||||
|
@@ -1,9 +1,9 @@
|
||||
import { withInstall } from '/@/utils';
|
||||
import type { ExtractPropTypes } from 'vue';
|
||||
import button from './src/BasicButton.vue';
|
||||
import popConfirmButton from './src/PopConfirmButton.vue';
|
||||
import { buttonProps } from './src/props';
|
||||
import { withInstall } from '/@/utils'
|
||||
import type { ExtractPropTypes } from 'vue'
|
||||
import button from './src/BasicButton.vue'
|
||||
import popConfirmButton from './src/PopConfirmButton.vue'
|
||||
import { buttonProps } from './src/props'
|
||||
|
||||
export const Button = withInstall(button);
|
||||
export const PopConfirmButton = withInstall(popConfirmButton);
|
||||
export declare type ButtonProps = Partial<ExtractPropTypes<typeof buttonProps>>;
|
||||
export const Button = withInstall(button)
|
||||
export const PopConfirmButton = withInstall(popConfirmButton)
|
||||
export declare type ButtonProps = Partial<ExtractPropTypes<typeof buttonProps>>
|
||||
|
@@ -9,32 +9,32 @@
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent } from 'vue';
|
||||
import { defineComponent } from 'vue'
|
||||
export default defineComponent({
|
||||
name: 'AButton',
|
||||
inheritAttrs: false,
|
||||
});
|
||||
})
|
||||
</script>
|
||||
<script lang="ts" setup>
|
||||
import { computed, unref } from 'vue';
|
||||
import { Button } from 'ant-design-vue';
|
||||
import Icon from '/@/components/Icon/src/Icon.vue';
|
||||
import { buttonProps } from './props';
|
||||
import { useAttrs } from '/@/hooks/core/useAttrs';
|
||||
import { computed, unref } from 'vue'
|
||||
import { Button } from 'ant-design-vue'
|
||||
import Icon from '/@/components/Icon/src/Icon.vue'
|
||||
import { buttonProps } from './props'
|
||||
import { useAttrs } from '/@/hooks/core/useAttrs'
|
||||
|
||||
const props = defineProps(buttonProps);
|
||||
const props = defineProps(buttonProps)
|
||||
// get component class
|
||||
const attrs = useAttrs({ excludeDefaultKeys: false });
|
||||
const attrs = useAttrs({ excludeDefaultKeys: false })
|
||||
const getButtonClass = computed(() => {
|
||||
const { color, disabled } = props;
|
||||
const { color, disabled } = props
|
||||
return [
|
||||
{
|
||||
[`ant-btn-${color}`]: !!color,
|
||||
[`is-disabled`]: disabled,
|
||||
},
|
||||
];
|
||||
});
|
||||
]
|
||||
})
|
||||
|
||||
// get inherit binding value
|
||||
const getBindValue = computed(() => ({ ...unref(attrs), ...props }));
|
||||
const getBindValue = computed(() => ({ ...unref(attrs), ...props }))
|
||||
</script>
|
||||
|
@@ -1,11 +1,11 @@
|
||||
<script lang="ts">
|
||||
import { computed, defineComponent, h, unref } from 'vue';
|
||||
import BasicButton from './BasicButton.vue';
|
||||
import { Popconfirm } from 'ant-design-vue';
|
||||
import { extendSlots } from '/@/utils/helper/tsxHelper';
|
||||
import { omit } from 'lodash-es';
|
||||
import { useAttrs } from '/@/hooks/core/useAttrs';
|
||||
import { useI18n } from '/@/hooks/web/useI18n';
|
||||
import { computed, defineComponent, h, unref } from 'vue'
|
||||
import BasicButton from './BasicButton.vue'
|
||||
import { Popconfirm } from 'ant-design-vue'
|
||||
import { extendSlots } from '/@/utils/helper/tsxHelper'
|
||||
import { omit } from 'lodash-es'
|
||||
import { useAttrs } from '/@/hooks/core/useAttrs'
|
||||
import { useI18n } from '/@/hooks/web/useI18n'
|
||||
|
||||
const props = {
|
||||
/**
|
||||
@@ -16,15 +16,15 @@
|
||||
type: Boolean,
|
||||
default: true,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
export default defineComponent({
|
||||
name: 'PopButton',
|
||||
inheritAttrs: false,
|
||||
props,
|
||||
setup(props, { slots }) {
|
||||
const { t } = useI18n();
|
||||
const attrs = useAttrs();
|
||||
const { t } = useI18n()
|
||||
const attrs = useAttrs()
|
||||
|
||||
// get inherit binding value
|
||||
const getBindValues = computed(() => {
|
||||
@@ -34,21 +34,21 @@
|
||||
cancelText: t('common.cancelText'),
|
||||
},
|
||||
{ ...props, ...unref(attrs) },
|
||||
);
|
||||
});
|
||||
)
|
||||
})
|
||||
|
||||
return () => {
|
||||
const bindValues = omit(unref(getBindValues), 'icon');
|
||||
const btnBind = omit(bindValues, 'title') as Recordable;
|
||||
if (btnBind.disabled) btnBind.color = '';
|
||||
const Button = h(BasicButton, btnBind, extendSlots(slots));
|
||||
const bindValues = omit(unref(getBindValues), 'icon')
|
||||
const btnBind = omit(bindValues, 'title') as Recordable
|
||||
if (btnBind.disabled) btnBind.color = ''
|
||||
const Button = h(BasicButton, btnBind, extendSlots(slots))
|
||||
|
||||
// If it is not enabled, it is a normal button
|
||||
if (!props.enable) {
|
||||
return Button;
|
||||
return Button
|
||||
}
|
||||
return h(Popconfirm, bindValues, { default: () => Button });
|
||||
};
|
||||
return h(Popconfirm, bindValues, { default: () => Button })
|
||||
}
|
||||
},
|
||||
});
|
||||
})
|
||||
</script>
|
||||
|
@@ -16,4 +16,4 @@ export const buttonProps = {
|
||||
*/
|
||||
iconSize: { type: Number, default: 14 },
|
||||
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 collapseContainer from './src/collapse/CollapseContainer.vue';
|
||||
import scrollContainer from './src/ScrollContainer.vue';
|
||||
import lazyContainer from './src/LazyContainer.vue';
|
||||
import { withInstall } from '/@/utils'
|
||||
import collapseContainer from './src/collapse/CollapseContainer.vue'
|
||||
import scrollContainer from './src/ScrollContainer.vue'
|
||||
import lazyContainer from './src/LazyContainer.vue'
|
||||
|
||||
export const CollapseContainer = withInstall(collapseContainer);
|
||||
export const ScrollContainer = withInstall(scrollContainer);
|
||||
export const LazyContainer = withInstall(lazyContainer);
|
||||
export const CollapseContainer = withInstall(collapseContainer)
|
||||
export const ScrollContainer = withInstall(scrollContainer)
|
||||
export const LazyContainer = withInstall(lazyContainer)
|
||||
|
||||
export * from './src/typing';
|
||||
export * from './src/typing'
|
||||
|
@@ -17,16 +17,16 @@
|
||||
</transition-group>
|
||||
</template>
|
||||
<script lang="ts">
|
||||
import type { PropType } from 'vue';
|
||||
import { defineComponent, reactive, onMounted, ref, toRef, toRefs } from 'vue';
|
||||
import { Skeleton } from 'ant-design-vue';
|
||||
import { useTimeoutFn } from '/@/hooks/core/useTimeout';
|
||||
import { useIntersectionObserver } from '/@/hooks/event/useIntersectionObserver';
|
||||
import type { PropType } from 'vue'
|
||||
import { defineComponent, reactive, onMounted, ref, toRef, toRefs } from 'vue'
|
||||
import { Skeleton } from 'ant-design-vue'
|
||||
import { useTimeoutFn } from '/@/hooks/core/useTimeout'
|
||||
import { useIntersectionObserver } from '/@/hooks/event/useIntersectionObserver'
|
||||
|
||||
interface State {
|
||||
isInit: boolean;
|
||||
loading: boolean;
|
||||
intersectionObserverInstance: IntersectionObserver | null;
|
||||
isInit: boolean
|
||||
loading: boolean
|
||||
intersectionObserverInstance: IntersectionObserver | null
|
||||
}
|
||||
|
||||
const props = {
|
||||
@@ -63,7 +63,7 @@
|
||||
* transition name
|
||||
*/
|
||||
transitionName: { type: String, default: 'lazy-container' },
|
||||
};
|
||||
}
|
||||
|
||||
export default defineComponent({
|
||||
name: 'LazyContainer',
|
||||
@@ -72,49 +72,49 @@
|
||||
props,
|
||||
emits: ['init'],
|
||||
setup(props, { emit }) {
|
||||
const elRef = ref();
|
||||
const elRef = ref()
|
||||
const state = reactive<State>({
|
||||
isInit: false,
|
||||
loading: false,
|
||||
intersectionObserverInstance: null,
|
||||
});
|
||||
})
|
||||
|
||||
onMounted(() => {
|
||||
immediateInit();
|
||||
initIntersectionObserver();
|
||||
});
|
||||
immediateInit()
|
||||
initIntersectionObserver()
|
||||
})
|
||||
|
||||
// If there is a set delay time, it will be executed immediately
|
||||
function immediateInit() {
|
||||
const { timeout } = props;
|
||||
const { timeout } = props
|
||||
timeout &&
|
||||
useTimeoutFn(() => {
|
||||
init();
|
||||
}, timeout);
|
||||
init()
|
||||
}, timeout)
|
||||
}
|
||||
|
||||
function init() {
|
||||
state.loading = true;
|
||||
state.loading = true
|
||||
|
||||
useTimeoutFn(() => {
|
||||
if (state.isInit) return;
|
||||
state.isInit = true;
|
||||
emit('init');
|
||||
}, props.maxWaitingTime || 80);
|
||||
if (state.isInit) return
|
||||
state.isInit = true
|
||||
emit('init')
|
||||
}, props.maxWaitingTime || 80)
|
||||
}
|
||||
|
||||
function initIntersectionObserver() {
|
||||
const { timeout, direction, threshold } = props;
|
||||
if (timeout) return;
|
||||
const { timeout, direction, threshold } = props
|
||||
if (timeout) return
|
||||
// According to the scrolling direction to construct the viewport margin, used to load in advance
|
||||
let rootMargin = '0px';
|
||||
let rootMargin = '0px'
|
||||
switch (direction) {
|
||||
case 'vertical':
|
||||
rootMargin = `${threshold} 0px`;
|
||||
break;
|
||||
rootMargin = `${threshold} 0px`
|
||||
break
|
||||
case 'horizontal':
|
||||
rootMargin = `0px ${threshold}`;
|
||||
break;
|
||||
rootMargin = `0px ${threshold}`
|
||||
break
|
||||
}
|
||||
|
||||
try {
|
||||
@@ -122,24 +122,24 @@
|
||||
rootMargin,
|
||||
target: toRef(elRef.value, '$el'),
|
||||
onIntersect: (entries: any[]) => {
|
||||
const isIntersecting = entries[0].isIntersecting || entries[0].intersectionRatio;
|
||||
const isIntersecting = entries[0].isIntersecting || entries[0].intersectionRatio
|
||||
if (isIntersecting) {
|
||||
init();
|
||||
init()
|
||||
if (observer) {
|
||||
stop();
|
||||
stop()
|
||||
}
|
||||
}
|
||||
},
|
||||
root: toRef(props, 'viewport'),
|
||||
});
|
||||
})
|
||||
} catch (e) {
|
||||
init();
|
||||
init()
|
||||
}
|
||||
}
|
||||
return {
|
||||
elRef,
|
||||
...toRefs(state),
|
||||
};
|
||||
}
|
||||
},
|
||||
});
|
||||
})
|
||||
</script>
|
||||
|
@@ -5,66 +5,66 @@
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent, ref, unref, nextTick } from 'vue';
|
||||
import { Scrollbar, ScrollbarType } from '/@/components/Scrollbar';
|
||||
import { useScrollTo } from '/@/hooks/event/useScrollTo';
|
||||
import { defineComponent, ref, unref, nextTick } from 'vue'
|
||||
import { Scrollbar, ScrollbarType } from '/@/components/Scrollbar'
|
||||
import { useScrollTo } from '/@/hooks/event/useScrollTo'
|
||||
|
||||
export default defineComponent({
|
||||
name: 'ScrollContainer',
|
||||
components: { Scrollbar },
|
||||
setup() {
|
||||
const scrollbarRef = ref<Nullable<ScrollbarType>>(null);
|
||||
const scrollbarRef = ref<Nullable<ScrollbarType>>(null)
|
||||
|
||||
/**
|
||||
* Scroll to the specified position
|
||||
*/
|
||||
function scrollTo(to: number, duration = 500) {
|
||||
const scrollbar = unref(scrollbarRef);
|
||||
const scrollbar = unref(scrollbarRef)
|
||||
if (!scrollbar) {
|
||||
return;
|
||||
return
|
||||
}
|
||||
nextTick(() => {
|
||||
const wrap = unref(scrollbar.wrap);
|
||||
const wrap = unref(scrollbar.wrap)
|
||||
if (!wrap) {
|
||||
return;
|
||||
return
|
||||
}
|
||||
const { start } = useScrollTo({
|
||||
el: wrap,
|
||||
to,
|
||||
duration,
|
||||
});
|
||||
start();
|
||||
});
|
||||
})
|
||||
start()
|
||||
})
|
||||
}
|
||||
|
||||
function getScrollWrap() {
|
||||
const scrollbar = unref(scrollbarRef);
|
||||
const scrollbar = unref(scrollbarRef)
|
||||
if (!scrollbar) {
|
||||
return null;
|
||||
return null
|
||||
}
|
||||
return scrollbar.wrap;
|
||||
return scrollbar.wrap
|
||||
}
|
||||
|
||||
/**
|
||||
* Scroll to the bottom
|
||||
*/
|
||||
function scrollBottom() {
|
||||
const scrollbar = unref(scrollbarRef);
|
||||
const scrollbar = unref(scrollbarRef)
|
||||
if (!scrollbar) {
|
||||
return;
|
||||
return
|
||||
}
|
||||
nextTick(() => {
|
||||
const wrap = unref(scrollbar.wrap) as any;
|
||||
const wrap = unref(scrollbar.wrap) as any
|
||||
if (!wrap) {
|
||||
return;
|
||||
return
|
||||
}
|
||||
const scrollHeight = wrap.scrollHeight as number;
|
||||
const scrollHeight = wrap.scrollHeight as number
|
||||
const { start } = useScrollTo({
|
||||
el: wrap,
|
||||
to: scrollHeight,
|
||||
});
|
||||
start();
|
||||
});
|
||||
})
|
||||
start()
|
||||
})
|
||||
}
|
||||
|
||||
return {
|
||||
@@ -72,9 +72,9 @@
|
||||
scrollTo,
|
||||
scrollBottom,
|
||||
getScrollWrap,
|
||||
};
|
||||
}
|
||||
},
|
||||
});
|
||||
})
|
||||
</script>
|
||||
<style lang="less">
|
||||
.scroll-container {
|
||||
|
@@ -23,17 +23,17 @@
|
||||
</div>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import type { PropType } from 'vue';
|
||||
import { ref } from 'vue';
|
||||
import { isNil } from 'lodash-es';
|
||||
import type { PropType } from 'vue'
|
||||
import { ref } from 'vue'
|
||||
import { isNil } from 'lodash-es'
|
||||
// component
|
||||
import { Skeleton } from 'ant-design-vue';
|
||||
import { CollapseTransition } from '/@/components/Transition';
|
||||
import CollapseHeader from './CollapseHeader.vue';
|
||||
import { triggerWindowResize } from '/@/utils/event';
|
||||
import { Skeleton } from 'ant-design-vue'
|
||||
import { CollapseTransition } from '/@/components/Transition'
|
||||
import CollapseHeader from './CollapseHeader.vue'
|
||||
import { triggerWindowResize } from '/@/utils/event'
|
||||
// hook
|
||||
import { useTimeoutFn } from '/@/hooks/core/useTimeout';
|
||||
import { useDesign } from '/@/hooks/web/useDesign';
|
||||
import { useTimeoutFn } from '/@/hooks/core/useTimeout'
|
||||
import { useDesign } from '/@/hooks/web/useDesign'
|
||||
|
||||
const props = defineProps({
|
||||
title: { type: String, default: '' },
|
||||
@@ -58,26 +58,26 @@
|
||||
* Delayed loading time
|
||||
*/
|
||||
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
|
||||
*/
|
||||
function handleExpand(val: boolean) {
|
||||
show.value = isNil(val) ? !show.value : val;
|
||||
show.value = isNil(val) ? !show.value : val
|
||||
if (props.triggerWindowResize) {
|
||||
// 200 milliseconds here is because the expansion has animation,
|
||||
useTimeoutFn(triggerWindowResize, 200);
|
||||
useTimeoutFn(triggerWindowResize, 200)
|
||||
}
|
||||
}
|
||||
|
||||
defineExpose({
|
||||
handleExpand,
|
||||
});
|
||||
})
|
||||
</script>
|
||||
<style lang="less">
|
||||
@prefix-cls: ~'@{namespace}-collapse-container';
|
||||
|
@@ -15,8 +15,8 @@
|
||||
</div>
|
||||
</template>
|
||||
<script lang="ts">
|
||||
import { defineComponent } from 'vue';
|
||||
import { BasicArrow, BasicTitle } from '/@/components/Basic';
|
||||
import { defineComponent } from 'vue'
|
||||
import { BasicArrow, BasicTitle } from '/@/components/Basic'
|
||||
|
||||
const props = {
|
||||
prefixCls: { type: String },
|
||||
@@ -27,12 +27,12 @@
|
||||
title: { type: String },
|
||||
show: { type: Boolean },
|
||||
canExpan: { type: Boolean },
|
||||
};
|
||||
}
|
||||
|
||||
export default defineComponent({
|
||||
components: { BasicArrow, BasicTitle },
|
||||
inheritAttrs: false,
|
||||
props,
|
||||
emits: ['expand'],
|
||||
});
|
||||
})
|
||||
</script>
|
||||
|
@@ -1,17 +1,17 @@
|
||||
export type ScrollType = 'default' | 'main';
|
||||
export type ScrollType = 'default' | 'main'
|
||||
|
||||
export interface CollapseContainerOptions {
|
||||
canExpand?: boolean;
|
||||
title?: string;
|
||||
helpMessage?: Array<any> | string;
|
||||
canExpand?: boolean
|
||||
title?: string
|
||||
helpMessage?: Array<any> | string
|
||||
}
|
||||
export interface ScrollContainerOptions {
|
||||
enableScroll?: boolean;
|
||||
type?: ScrollType;
|
||||
enableScroll?: boolean
|
||||
type?: ScrollType
|
||||
}
|
||||
|
||||
export type ScrollActionType = RefType<{
|
||||
scrollBottom: () => void;
|
||||
getScrollWrap: () => Nullable<HTMLElement>;
|
||||
scrollTo: (top: number) => void;
|
||||
}>;
|
||||
scrollBottom: () => void
|
||||
getScrollWrap: () => Nullable<HTMLElement>
|
||||
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 countButton from './src/CountButton.vue';
|
||||
import countdownInput from './src/CountdownInput.vue';
|
||||
import { withInstall } from '/@/utils'
|
||||
import countButton from './src/CountButton.vue'
|
||||
import countdownInput from './src/CountdownInput.vue'
|
||||
|
||||
export const CountdownInput = withInstall(countdownInput);
|
||||
export const CountButton = withInstall(countButton);
|
||||
export const CountdownInput = withInstall(countdownInput)
|
||||
export const CountButton = withInstall(countButton)
|
||||
|
@@ -4,11 +4,11 @@
|
||||
</Button>
|
||||
</template>
|
||||
<script lang="ts">
|
||||
import { defineComponent, ref, watchEffect, computed, unref } from 'vue';
|
||||
import { Button } from 'ant-design-vue';
|
||||
import { useCountdown } from './useCountdown';
|
||||
import { isFunction } from '/@/utils/is';
|
||||
import { useI18n } from '/@/hooks/web/useI18n';
|
||||
import { defineComponent, ref, watchEffect, computed, unref } from 'vue'
|
||||
import { Button } from 'ant-design-vue'
|
||||
import { useCountdown } from './useCountdown'
|
||||
import { isFunction } from '/@/utils/is'
|
||||
import { useI18n } from '/@/hooks/web/useI18n'
|
||||
|
||||
const props = {
|
||||
value: { type: [Object, Number, String, Array] },
|
||||
@@ -17,46 +17,46 @@
|
||||
type: Function as PropType<() => Promise<boolean>>,
|
||||
default: null,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
export default defineComponent({
|
||||
name: 'CountButton',
|
||||
components: { Button },
|
||||
props,
|
||||
setup(props) {
|
||||
const loading = ref(false);
|
||||
const loading = ref(false)
|
||||
|
||||
const { currentCount, isStart, start, reset } = useCountdown(props.count);
|
||||
const { t } = useI18n();
|
||||
const { currentCount, isStart, start, reset } = useCountdown(props.count)
|
||||
const { t } = useI18n()
|
||||
|
||||
const getButtonText = computed(() => {
|
||||
return !unref(isStart)
|
||||
? t('component.countdown.normalText')
|
||||
: t('component.countdown.sendText', [unref(currentCount)]);
|
||||
});
|
||||
: t('component.countdown.sendText', [unref(currentCount)])
|
||||
})
|
||||
|
||||
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
|
||||
*/
|
||||
async function handleStart() {
|
||||
const { beforeStartFunc } = props;
|
||||
const { beforeStartFunc } = props
|
||||
if (beforeStartFunc && isFunction(beforeStartFunc)) {
|
||||
loading.value = true;
|
||||
loading.value = true
|
||||
try {
|
||||
const canStart = await beforeStartFunc();
|
||||
canStart && start();
|
||||
const canStart = await beforeStartFunc()
|
||||
canStart && start()
|
||||
} finally {
|
||||
loading.value = false;
|
||||
loading.value = false
|
||||
}
|
||||
} else {
|
||||
start();
|
||||
start()
|
||||
}
|
||||
}
|
||||
return { handleStart, currentCount, loading, getButtonText, isStart };
|
||||
return { handleStart, currentCount, loading, getButtonText, isStart }
|
||||
},
|
||||
});
|
||||
})
|
||||
</script>
|
||||
|
@@ -9,10 +9,10 @@
|
||||
</a-input>
|
||||
</template>
|
||||
<script lang="ts">
|
||||
import { defineComponent, PropType } from 'vue';
|
||||
import CountButton from './CountButton.vue';
|
||||
import { useDesign } from '/@/hooks/web/useDesign';
|
||||
import { useRuleFormItem } from '/@/hooks/component/useFormItem';
|
||||
import { defineComponent, PropType } from 'vue'
|
||||
import CountButton from './CountButton.vue'
|
||||
import { useDesign } from '/@/hooks/web/useDesign'
|
||||
import { useRuleFormItem } from '/@/hooks/component/useFormItem'
|
||||
|
||||
const props = {
|
||||
value: { type: String },
|
||||
@@ -22,7 +22,7 @@
|
||||
type: Function as PropType<() => Promise<boolean>>,
|
||||
default: null,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
export default defineComponent({
|
||||
name: 'CountDownInput',
|
||||
@@ -30,12 +30,12 @@
|
||||
inheritAttrs: false,
|
||||
props,
|
||||
setup(props) {
|
||||
const { prefixCls } = useDesign('countdown-input');
|
||||
const [state] = useRuleFormItem(props);
|
||||
const { prefixCls } = useDesign('countdown-input')
|
||||
const [state] = useRuleFormItem(props)
|
||||
|
||||
return { prefixCls, state };
|
||||
return { prefixCls, state }
|
||||
},
|
||||
});
|
||||
})
|
||||
</script>
|
||||
<style lang="less">
|
||||
@prefix-cls: ~'@{namespace}-countdown-input';
|
||||
|
@@ -1,51 +1,51 @@
|
||||
import { ref, unref } from 'vue';
|
||||
import { tryOnUnmounted } from '@vueuse/core';
|
||||
import { ref, unref } from 'vue'
|
||||
import { tryOnUnmounted } from '@vueuse/core'
|
||||
|
||||
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() {
|
||||
timerId && window.clearInterval(timerId);
|
||||
timerId && window.clearInterval(timerId)
|
||||
}
|
||||
|
||||
function stop() {
|
||||
isStart.value = false;
|
||||
clear();
|
||||
timerId = null;
|
||||
isStart.value = false
|
||||
clear()
|
||||
timerId = null
|
||||
}
|
||||
|
||||
function start() {
|
||||
if (unref(isStart) || !!timerId) {
|
||||
return;
|
||||
return
|
||||
}
|
||||
isStart.value = true;
|
||||
isStart.value = true
|
||||
timerId = setInterval(() => {
|
||||
if (unref(currentCount) === 1) {
|
||||
stop();
|
||||
currentCount.value = count;
|
||||
stop()
|
||||
currentCount.value = count
|
||||
} else {
|
||||
currentCount.value -= 1;
|
||||
currentCount.value -= 1
|
||||
}
|
||||
}, 1000);
|
||||
}, 1000)
|
||||
}
|
||||
|
||||
function reset() {
|
||||
currentCount.value = count;
|
||||
stop();
|
||||
currentCount.value = count
|
||||
stop()
|
||||
}
|
||||
|
||||
function restart() {
|
||||
reset();
|
||||
start();
|
||||
reset()
|
||||
start()
|
||||
}
|
||||
|
||||
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 description from './src/Description.vue';
|
||||
import { withInstall } from '/@/utils'
|
||||
import description from './src/Description.vue'
|
||||
|
||||
export * from './src/typing';
|
||||
export { useDescription } from './src/useDescription';
|
||||
export const Description = withInstall(description);
|
||||
export * from './src/typing'
|
||||
export { useDescription } from './src/useDescription'
|
||||
export const Description = withInstall(description)
|
||||
|
@@ -1,16 +1,16 @@
|
||||
<script lang="tsx">
|
||||
import type { DescriptionProps, DescInstance, DescItem } from './typing';
|
||||
import type { DescriptionsProps } from 'ant-design-vue/es/descriptions/index';
|
||||
import type { CSSProperties } from 'vue';
|
||||
import type { CollapseContainerOptions } from '/@/components/Container/index';
|
||||
import { defineComponent, computed, ref, unref, toRefs } from 'vue';
|
||||
import { get } from 'lodash-es';
|
||||
import { Descriptions } from 'ant-design-vue';
|
||||
import { CollapseContainer } from '/@/components/Container/index';
|
||||
import { useDesign } from '/@/hooks/web/useDesign';
|
||||
import { isFunction } from '/@/utils/is';
|
||||
import { getSlot } from '/@/utils/helper/tsxHelper';
|
||||
import { useAttrs } from '/@/hooks/core/useAttrs';
|
||||
import type { DescriptionProps, DescInstance, DescItem } from './typing'
|
||||
import type { DescriptionsProps } from 'ant-design-vue/es/descriptions/index'
|
||||
import type { CSSProperties } from 'vue'
|
||||
import type { CollapseContainerOptions } from '/@/components/Container/index'
|
||||
import { defineComponent, computed, ref, unref, toRefs } from 'vue'
|
||||
import { get } from 'lodash-es'
|
||||
import { Descriptions } from 'ant-design-vue'
|
||||
import { CollapseContainer } from '/@/components/Container/index'
|
||||
import { useDesign } from '/@/hooks/web/useDesign'
|
||||
import { isFunction } from '/@/utils/is'
|
||||
import { getSlot } from '/@/utils/helper/tsxHelper'
|
||||
import { useAttrs } from '/@/hooks/core/useAttrs'
|
||||
|
||||
const props = {
|
||||
useCollapse: { type: Boolean, default: true },
|
||||
@@ -24,7 +24,7 @@
|
||||
column: {
|
||||
type: [Number, Object] as PropType<number | Recordable>,
|
||||
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: {
|
||||
@@ -36,38 +36,38 @@
|
||||
default: () => [],
|
||||
},
|
||||
data: { type: Object },
|
||||
};
|
||||
}
|
||||
|
||||
export default defineComponent({
|
||||
name: 'Description',
|
||||
props,
|
||||
emits: ['register'],
|
||||
setup(props, { slots, emit }) {
|
||||
const propsRef = ref<Partial<DescriptionProps> | null>(null);
|
||||
const propsRef = ref<Partial<DescriptionProps> | null>(null)
|
||||
|
||||
const { prefixCls } = useDesign('description');
|
||||
const attrs = useAttrs();
|
||||
const { prefixCls } = useDesign('description')
|
||||
const attrs = useAttrs()
|
||||
|
||||
// Custom title component: get title
|
||||
const getMergeProps = computed(() => {
|
||||
return {
|
||||
...props,
|
||||
...(unref(propsRef) as Recordable),
|
||||
} as DescriptionProps;
|
||||
});
|
||||
} as DescriptionProps
|
||||
})
|
||||
|
||||
const getProps = computed(() => {
|
||||
const opt = {
|
||||
...unref(getMergeProps),
|
||||
title: undefined,
|
||||
};
|
||||
return opt as DescriptionProps;
|
||||
});
|
||||
}
|
||||
return opt as DescriptionProps
|
||||
})
|
||||
|
||||
/**
|
||||
* @description: Whether to setting title
|
||||
*/
|
||||
const useWrapper = computed(() => !!unref(getMergeProps).title);
|
||||
const useWrapper = computed(() => !!unref(getMergeProps).title)
|
||||
|
||||
/**
|
||||
* @description: Get configuration Collapse
|
||||
@@ -77,72 +77,72 @@
|
||||
// Cannot be expanded by default
|
||||
canExpand: false,
|
||||
...unref(getProps).collapseOptions,
|
||||
};
|
||||
});
|
||||
}
|
||||
})
|
||||
|
||||
const getDescriptionsProps = computed(() => {
|
||||
return { ...unref(attrs), ...unref(getProps) } as DescriptionsProps;
|
||||
});
|
||||
return { ...unref(attrs), ...unref(getProps) } as DescriptionsProps
|
||||
})
|
||||
|
||||
/**
|
||||
* @description:设置desc
|
||||
*/
|
||||
function setDescProps(descProps: Partial<DescriptionProps>): void {
|
||||
// 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
|
||||
function renderLabel({ label, labelMinWidth, labelStyle }: DescItem) {
|
||||
if (!labelStyle && !labelMinWidth) {
|
||||
return label;
|
||||
return label
|
||||
}
|
||||
|
||||
const labelStyles: CSSProperties = {
|
||||
...labelStyle,
|
||||
minWidth: `${labelMinWidth}px `,
|
||||
};
|
||||
return <div style={labelStyles}>{label}</div>;
|
||||
}
|
||||
return <div style={labelStyles}>{label}</div>
|
||||
}
|
||||
|
||||
function renderItem() {
|
||||
const { schema, data } = unref(getProps);
|
||||
const { schema, data } = unref(getProps)
|
||||
return unref(schema)
|
||||
.map((item) => {
|
||||
const { render, field, span, show, contentMinWidth } = item;
|
||||
const { render, field, span, show, contentMinWidth } = item
|
||||
|
||||
if (show && isFunction(show) && !show(data)) {
|
||||
return null;
|
||||
return null
|
||||
}
|
||||
|
||||
const getContent = () => {
|
||||
const _data = unref(getProps)?.data;
|
||||
const _data = unref(getProps)?.data
|
||||
if (!_data) {
|
||||
return null;
|
||||
return null
|
||||
}
|
||||
const getField = get(_data, field);
|
||||
const getField = get(_data, 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 (
|
||||
<Descriptions.Item label={renderLabel(item)} key={field} span={span}>
|
||||
{() => {
|
||||
if (!contentMinWidth) {
|
||||
return getContent();
|
||||
return getContent()
|
||||
}
|
||||
const style: CSSProperties = {
|
||||
minWidth: `${width}px`,
|
||||
};
|
||||
return <div style={style}>{getContent()}</div>;
|
||||
}
|
||||
return <div style={style}>{getContent()}</div>
|
||||
}}
|
||||
</Descriptions.Item>
|
||||
);
|
||||
)
|
||||
})
|
||||
.filter((item) => !!item);
|
||||
.filter((item) => !!item)
|
||||
}
|
||||
|
||||
const renderDesc = () => {
|
||||
@@ -150,18 +150,18 @@
|
||||
<Descriptions class={`${prefixCls}`} {...(unref(getDescriptionsProps) as any)}>
|
||||
{renderItem()}
|
||||
</Descriptions>
|
||||
);
|
||||
};
|
||||
)
|
||||
}
|
||||
|
||||
const renderContainer = () => {
|
||||
const content = props.useCollapse ? renderDesc() : <div>{renderDesc()}</div>;
|
||||
const content = props.useCollapse ? renderDesc() : <div>{renderDesc()}</div>
|
||||
// Reduce the dom level
|
||||
if (!props.useCollapse) {
|
||||
return content;
|
||||
return content
|
||||
}
|
||||
|
||||
const { canExpand, helpMessage } = unref(getCollapseOptions);
|
||||
const { title } = unref(getMergeProps);
|
||||
const { canExpand, helpMessage } = unref(getCollapseOptions)
|
||||
const { title } = unref(getMergeProps)
|
||||
|
||||
return (
|
||||
<CollapseContainer title={title} canExpan={canExpand} helpMessage={helpMessage}>
|
||||
@@ -170,15 +170,15 @@
|
||||
action: () => getSlot(slots, 'action'),
|
||||
}}
|
||||
</CollapseContainer>
|
||||
);
|
||||
};
|
||||
)
|
||||
}
|
||||
|
||||
const methods: DescInstance = {
|
||||
setDescProps,
|
||||
};
|
||||
}
|
||||
|
||||
emit('register', methods);
|
||||
return () => (unref(useWrapper) ? renderContainer() : renderDesc());
|
||||
emit('register', methods)
|
||||
return () => (unref(useWrapper) ? renderContainer() : renderDesc())
|
||||
},
|
||||
});
|
||||
})
|
||||
</script>
|
||||
|
@@ -1,50 +1,50 @@
|
||||
import type { VNode, CSSProperties } from 'vue';
|
||||
import type { CollapseContainerOptions } from '/@/components/Container/index';
|
||||
import type { DescriptionsProps } from 'ant-design-vue/es/descriptions/index';
|
||||
import type { VNode, CSSProperties } from 'vue'
|
||||
import type { CollapseContainerOptions } from '/@/components/Container/index'
|
||||
import type { DescriptionsProps } from 'ant-design-vue/es/descriptions/index'
|
||||
|
||||
export interface DescItem {
|
||||
labelMinWidth?: number;
|
||||
contentMinWidth?: number;
|
||||
labelStyle?: CSSProperties;
|
||||
field: string;
|
||||
label: string | VNode | JSX.Element;
|
||||
labelMinWidth?: number
|
||||
contentMinWidth?: number
|
||||
labelStyle?: CSSProperties
|
||||
field: string
|
||||
label: string | VNode | JSX.Element
|
||||
// Merge column
|
||||
span?: number;
|
||||
show?: (...arg: any) => boolean;
|
||||
span?: number
|
||||
show?: (...arg: any) => boolean
|
||||
// render
|
||||
render?: (
|
||||
val: any,
|
||||
data: Recordable,
|
||||
) => VNode | undefined | JSX.Element | Element | string | number;
|
||||
) => VNode | undefined | JSX.Element | Element | string | number
|
||||
}
|
||||
|
||||
export interface DescriptionProps extends DescriptionsProps {
|
||||
// Whether to include the collapse component
|
||||
useCollapse?: boolean;
|
||||
useCollapse?: boolean
|
||||
/**
|
||||
* item configuration
|
||||
* @type DescItem
|
||||
*/
|
||||
schema: DescItem[];
|
||||
schema: DescItem[]
|
||||
/**
|
||||
* 数据
|
||||
* @type object
|
||||
*/
|
||||
data: Recordable;
|
||||
data: Recordable
|
||||
/**
|
||||
* Built-in CollapseContainer component configuration
|
||||
* @type CollapseContainerOptions
|
||||
*/
|
||||
collapseOptions?: CollapseContainerOptions;
|
||||
collapseOptions?: CollapseContainerOptions
|
||||
}
|
||||
|
||||
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:
|
||||
*/
|
||||
export type UseDescReturnType = [Register, DescInstance];
|
||||
export type UseDescReturnType = [Register, DescInstance]
|
||||
|
@@ -1,28 +1,28 @@
|
||||
import type { DescriptionProps, DescInstance, UseDescReturnType } from './typing';
|
||||
import { ref, getCurrentInstance, unref } from 'vue';
|
||||
import { isProdMode } from '/@/utils/env';
|
||||
import type { DescriptionProps, DescInstance, UseDescReturnType } from './typing'
|
||||
import { ref, getCurrentInstance, unref } from 'vue'
|
||||
import { isProdMode } from '/@/utils/env'
|
||||
|
||||
export function useDescription(props?: Partial<DescriptionProps>): UseDescReturnType {
|
||||
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 loaded = ref(false);
|
||||
const desc = ref<Nullable<DescInstance>>(null)
|
||||
const loaded = ref(false)
|
||||
|
||||
function register(instance: DescInstance) {
|
||||
if (unref(loaded) && isProdMode()) {
|
||||
return;
|
||||
return
|
||||
}
|
||||
desc.value = instance;
|
||||
props && instance.setDescProps(props);
|
||||
loaded.value = true;
|
||||
desc.value = instance
|
||||
props && instance.setDescProps(props)
|
||||
loaded.value = true
|
||||
}
|
||||
|
||||
const methods: DescInstance = {
|
||||
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 basicDrawer from './src/BasicDrawer.vue';
|
||||
import { withInstall } from '/@/utils'
|
||||
import basicDrawer from './src/BasicDrawer.vue'
|
||||
|
||||
export const BasicDrawer = withInstall(basicDrawer);
|
||||
export * from './src/typing';
|
||||
export { useDrawer, useDrawerInner } from './src/useDrawer';
|
||||
export const BasicDrawer = withInstall(basicDrawer)
|
||||
export * from './src/typing'
|
||||
export { useDrawer, useDrawerInner } from './src/useDrawer'
|
||||
|
@@ -31,8 +31,8 @@
|
||||
</Drawer>
|
||||
</template>
|
||||
<script lang="ts">
|
||||
import type { DrawerInstance, DrawerProps } from './typing';
|
||||
import type { CSSProperties } from 'vue';
|
||||
import type { DrawerInstance, DrawerProps } from './typing'
|
||||
import type { CSSProperties } from 'vue'
|
||||
import {
|
||||
defineComponent,
|
||||
ref,
|
||||
@@ -42,17 +42,17 @@
|
||||
nextTick,
|
||||
toRaw,
|
||||
getCurrentInstance,
|
||||
} from 'vue';
|
||||
import { Drawer } from 'ant-design-vue';
|
||||
import { useI18n } from '/@/hooks/web/useI18n';
|
||||
import { isFunction, isNumber } from '/@/utils/is';
|
||||
import { deepMerge } from '/@/utils';
|
||||
import DrawerFooter from './components/DrawerFooter.vue';
|
||||
import DrawerHeader from './components/DrawerHeader.vue';
|
||||
import { ScrollContainer } from '/@/components/Container';
|
||||
import { basicProps } from './props';
|
||||
import { useDesign } from '/@/hooks/web/useDesign';
|
||||
import { useAttrs } from '/@/hooks/core/useAttrs';
|
||||
} from 'vue'
|
||||
import { Drawer } from 'ant-design-vue'
|
||||
import { useI18n } from '/@/hooks/web/useI18n'
|
||||
import { isFunction, isNumber } from '/@/utils/is'
|
||||
import { deepMerge } from '/@/utils'
|
||||
import DrawerFooter from './components/DrawerFooter.vue'
|
||||
import DrawerHeader from './components/DrawerHeader.vue'
|
||||
import { ScrollContainer } from '/@/components/Container'
|
||||
import { basicProps } from './props'
|
||||
import { useDesign } from '/@/hooks/web/useDesign'
|
||||
import { useAttrs } from '/@/hooks/core/useAttrs'
|
||||
|
||||
export default defineComponent({
|
||||
components: { Drawer, ScrollContainer, DrawerFooter, DrawerHeader },
|
||||
@@ -60,25 +60,25 @@
|
||||
props: basicProps,
|
||||
emits: ['visible-change', 'ok', 'close', 'register'],
|
||||
setup(props, { emit }) {
|
||||
const visibleRef = ref(false);
|
||||
const attrs = useAttrs();
|
||||
const propsRef = ref<Partial<Nullable<DrawerProps>>>(null);
|
||||
const visibleRef = ref(false)
|
||||
const attrs = useAttrs()
|
||||
const propsRef = ref<Partial<Nullable<DrawerProps>>>(null)
|
||||
|
||||
const { t } = useI18n();
|
||||
const { prefixVar, prefixCls } = useDesign('basic-drawer');
|
||||
const { t } = useI18n()
|
||||
const { prefixVar, prefixCls } = useDesign('basic-drawer')
|
||||
|
||||
const drawerInstance: DrawerInstance = {
|
||||
setDrawerProps: setDrawerProps,
|
||||
emitVisible: undefined,
|
||||
};
|
||||
}
|
||||
|
||||
const instance = getCurrentInstance();
|
||||
const instance = getCurrentInstance()
|
||||
|
||||
instance && emit('register', drawerInstance, instance.uid);
|
||||
instance && emit('register', drawerInstance, instance.uid)
|
||||
|
||||
const getMergeProps = computed((): DrawerProps => {
|
||||
return deepMerge(toRaw(props), unref(propsRef));
|
||||
});
|
||||
return deepMerge(toRaw(props), unref(propsRef))
|
||||
})
|
||||
|
||||
const getProps = computed((): DrawerProps => {
|
||||
const opt = {
|
||||
@@ -86,95 +86,95 @@
|
||||
...unref(attrs),
|
||||
...unref(getMergeProps),
|
||||
visible: unref(visibleRef),
|
||||
};
|
||||
opt.title = undefined;
|
||||
const { isDetail, width, wrapClassName, getContainer } = opt;
|
||||
}
|
||||
opt.title = undefined
|
||||
const { isDetail, width, wrapClassName, getContainer } = opt
|
||||
if (isDetail) {
|
||||
if (!width) {
|
||||
opt.width = '100%';
|
||||
opt.width = '100%'
|
||||
}
|
||||
const detailCls = `${prefixCls}__detail`;
|
||||
opt.class = wrapClassName ? `${wrapClassName} ${detailCls}` : detailCls;
|
||||
const detailCls = `${prefixCls}__detail`
|
||||
opt.class = wrapClassName ? `${wrapClassName} ${detailCls}` : detailCls
|
||||
|
||||
if (!getContainer) {
|
||||
// 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 => {
|
||||
return {
|
||||
...attrs,
|
||||
...unref(getProps),
|
||||
};
|
||||
});
|
||||
}
|
||||
})
|
||||
|
||||
// Custom implementation of the bottom button,
|
||||
const getFooterHeight = computed(() => {
|
||||
const { footerHeight, showFooter } = unref(getProps);
|
||||
const { footerHeight, showFooter } = unref(getProps)
|
||||
if (showFooter && footerHeight) {
|
||||
return isNumber(footerHeight)
|
||||
? `${footerHeight}px`
|
||||
: `${footerHeight.replace('px', '')}px`;
|
||||
: `${footerHeight.replace('px', '')}px`
|
||||
}
|
||||
return `0px`;
|
||||
});
|
||||
return `0px`
|
||||
})
|
||||
|
||||
const getScrollContentStyle = computed((): CSSProperties => {
|
||||
const footerHeight = unref(getFooterHeight);
|
||||
const footerHeight = unref(getFooterHeight)
|
||||
return {
|
||||
position: 'relative',
|
||||
height: `calc(100% - ${footerHeight})`,
|
||||
};
|
||||
});
|
||||
}
|
||||
})
|
||||
|
||||
const getLoading = computed(() => {
|
||||
return !!unref(getProps)?.loading;
|
||||
});
|
||||
return !!unref(getProps)?.loading
|
||||
})
|
||||
|
||||
watch(
|
||||
() => props.visible,
|
||||
(newVal, oldVal) => {
|
||||
if (newVal !== oldVal) visibleRef.value = newVal;
|
||||
if (newVal !== oldVal) visibleRef.value = newVal
|
||||
},
|
||||
{ deep: true },
|
||||
);
|
||||
)
|
||||
|
||||
watch(
|
||||
() => visibleRef.value,
|
||||
(visible) => {
|
||||
nextTick(() => {
|
||||
emit('visible-change', visible);
|
||||
instance && drawerInstance.emitVisible?.(visible, instance.uid);
|
||||
});
|
||||
emit('visible-change', visible)
|
||||
instance && drawerInstance.emitVisible?.(visible, instance.uid)
|
||||
})
|
||||
},
|
||||
);
|
||||
)
|
||||
|
||||
// Cancel event
|
||||
async function onClose(e: Recordable) {
|
||||
const { closeFunc } = unref(getProps);
|
||||
emit('close', e);
|
||||
const { closeFunc } = unref(getProps)
|
||||
emit('close', e)
|
||||
if (closeFunc && isFunction(closeFunc)) {
|
||||
const res = await closeFunc();
|
||||
visibleRef.value = !res;
|
||||
return;
|
||||
const res = await closeFunc()
|
||||
visibleRef.value = !res
|
||||
return
|
||||
}
|
||||
visibleRef.value = false;
|
||||
visibleRef.value = false
|
||||
}
|
||||
|
||||
function setDrawerProps(props: Partial<DrawerProps>): void {
|
||||
// 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')) {
|
||||
visibleRef.value = !!props.visible;
|
||||
visibleRef.value = !!props.visible
|
||||
}
|
||||
}
|
||||
|
||||
function handleOk() {
|
||||
emit('ok');
|
||||
emit('ok')
|
||||
}
|
||||
|
||||
return {
|
||||
@@ -188,9 +188,9 @@
|
||||
getBindValues,
|
||||
getFooterHeight,
|
||||
handleOk,
|
||||
};
|
||||
}
|
||||
},
|
||||
});
|
||||
})
|
||||
</script>
|
||||
<style lang="less">
|
||||
@header-height: 60px;
|
||||
|
@@ -25,11 +25,11 @@
|
||||
</div>
|
||||
</template>
|
||||
<script lang="ts">
|
||||
import type { CSSProperties } from 'vue';
|
||||
import { defineComponent, computed } from 'vue';
|
||||
import { useDesign } from '/@/hooks/web/useDesign';
|
||||
import type { CSSProperties } from 'vue'
|
||||
import { defineComponent, computed } from 'vue'
|
||||
import { useDesign } from '/@/hooks/web/useDesign'
|
||||
|
||||
import { footerProps } from '../props';
|
||||
import { footerProps } from '../props'
|
||||
export default defineComponent({
|
||||
name: 'BasicDrawerFooter',
|
||||
props: {
|
||||
@@ -41,26 +41,26 @@
|
||||
},
|
||||
emits: ['ok', 'close'],
|
||||
setup(props, { emit }) {
|
||||
const { prefixCls } = useDesign('basic-drawer-footer');
|
||||
const { prefixCls } = useDesign('basic-drawer-footer')
|
||||
|
||||
const getStyle = computed((): CSSProperties => {
|
||||
const heightStr = `${props.height}`;
|
||||
const heightStr = `${props.height}`
|
||||
return {
|
||||
height: heightStr,
|
||||
lineHeight: `calc(${heightStr} - 1px)`,
|
||||
};
|
||||
});
|
||||
}
|
||||
})
|
||||
|
||||
function handleOk() {
|
||||
emit('ok');
|
||||
emit('ok')
|
||||
}
|
||||
|
||||
function handleClose() {
|
||||
emit('close');
|
||||
emit('close')
|
||||
}
|
||||
return { handleOk, prefixCls, handleClose, getStyle };
|
||||
return { handleOk, prefixCls, handleClose, getStyle }
|
||||
},
|
||||
});
|
||||
})
|
||||
</script>
|
||||
|
||||
<style lang="less">
|
||||
|
@@ -18,13 +18,13 @@
|
||||
</div>
|
||||
</template>
|
||||
<script lang="ts">
|
||||
import { defineComponent } from 'vue';
|
||||
import { BasicTitle } from '/@/components/Basic';
|
||||
import { ArrowLeftOutlined } from '@ant-design/icons-vue';
|
||||
import { defineComponent } from 'vue'
|
||||
import { BasicTitle } from '/@/components/Basic'
|
||||
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({
|
||||
name: 'BasicDrawerHeader',
|
||||
components: { BasicTitle, ArrowLeftOutlined },
|
||||
@@ -35,15 +35,15 @@
|
||||
},
|
||||
emits: ['close'],
|
||||
setup(_, { emit }) {
|
||||
const { prefixCls } = useDesign('basic-drawer-header');
|
||||
const { prefixCls } = useDesign('basic-drawer-header')
|
||||
|
||||
function handleClose() {
|
||||
emit('close');
|
||||
emit('close')
|
||||
}
|
||||
|
||||
return { prefixCls, handleClose };
|
||||
return { prefixCls, handleClose }
|
||||
},
|
||||
});
|
||||
})
|
||||
</script>
|
||||
|
||||
<style lang="less">
|
||||
|
@@ -1,7 +1,7 @@
|
||||
import type { PropType } from 'vue';
|
||||
import type { PropType } from 'vue'
|
||||
|
||||
import { useI18n } from '/@/hooks/web/useI18n';
|
||||
const { t } = useI18n();
|
||||
import { useI18n } from '/@/hooks/web/useI18n'
|
||||
const { t } = useI18n()
|
||||
|
||||
export const footerProps = {
|
||||
confirmLoading: { type: Boolean },
|
||||
@@ -23,7 +23,7 @@ export const footerProps = {
|
||||
type: [String, Number] as PropType<string | number>,
|
||||
default: 60,
|
||||
},
|
||||
};
|
||||
}
|
||||
export const basicProps = {
|
||||
isDetail: { type: Boolean },
|
||||
title: { type: String, default: '' },
|
||||
@@ -41,4 +41,4 @@ export const basicProps = {
|
||||
},
|
||||
destroyOnClose: { type: Boolean },
|
||||
...footerProps,
|
||||
};
|
||||
}
|
||||
|
@@ -1,193 +1,193 @@
|
||||
import type { ButtonProps } from 'ant-design-vue/lib/button/buttonTypes';
|
||||
import type { CSSProperties, VNodeChild, ComputedRef } from 'vue';
|
||||
import type { ScrollContainerOptions } from '/@/components/Container/index';
|
||||
import type { ButtonProps } from 'ant-design-vue/lib/button/buttonTypes'
|
||||
import type { CSSProperties, VNodeChild, ComputedRef } from 'vue'
|
||||
import type { ScrollContainerOptions } from '/@/components/Container/index'
|
||||
|
||||
export interface DrawerInstance {
|
||||
setDrawerProps: (props: Partial<DrawerProps> | boolean) => void;
|
||||
emitVisible?: (visible: boolean, uid: number) => void;
|
||||
setDrawerProps: (props: Partial<DrawerProps> | boolean) => void
|
||||
emitVisible?: (visible: boolean, uid: number) => void
|
||||
}
|
||||
|
||||
export interface ReturnMethods extends DrawerInstance {
|
||||
openDrawer: <T = any>(visible?: boolean, data?: T, openOnSet?: boolean) => void;
|
||||
closeDrawer: () => void;
|
||||
getVisible?: ComputedRef<boolean>;
|
||||
openDrawer: <T = any>(visible?: boolean, data?: T, openOnSet?: boolean) => void
|
||||
closeDrawer: () => void
|
||||
getVisible?: ComputedRef<boolean>
|
||||
}
|
||||
|
||||
export type RegisterFn = (drawerInstance: DrawerInstance, uuid?: string) => void;
|
||||
export type RegisterFn = (drawerInstance: DrawerInstance, uuid?: string) => void
|
||||
|
||||
export interface ReturnInnerMethods extends DrawerInstance {
|
||||
closeDrawer: () => void;
|
||||
changeLoading: (loading: boolean) => void;
|
||||
changeOkLoading: (loading: boolean) => void;
|
||||
getVisible?: ComputedRef<boolean>;
|
||||
closeDrawer: () => void
|
||||
changeLoading: (loading: boolean) => void
|
||||
changeOkLoading: (loading: boolean) => void
|
||||
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 {
|
||||
showOkBtn: boolean;
|
||||
showCancelBtn: boolean;
|
||||
showOkBtn: boolean
|
||||
showCancelBtn: boolean
|
||||
/**
|
||||
* Text of the Cancel button
|
||||
* @default 'cancel'
|
||||
* @type string
|
||||
*/
|
||||
cancelText: string;
|
||||
cancelText: string
|
||||
/**
|
||||
* Text of the OK button
|
||||
* @default 'OK'
|
||||
* @type string
|
||||
*/
|
||||
okText: string;
|
||||
okText: string
|
||||
|
||||
/**
|
||||
* Button type of the OK button
|
||||
* @default 'primary'
|
||||
* @type string
|
||||
*/
|
||||
okType: 'primary' | 'danger' | 'dashed' | 'ghost' | 'default';
|
||||
okType: 'primary' | 'danger' | 'dashed' | 'ghost' | 'default'
|
||||
/**
|
||||
* The ok button props, follow jsx rules
|
||||
* @type object
|
||||
*/
|
||||
okButtonProps: { props: ButtonProps; on: {} };
|
||||
okButtonProps: { props: ButtonProps; on: {} }
|
||||
|
||||
/**
|
||||
* The cancel button props, follow jsx rules
|
||||
* @type object
|
||||
*/
|
||||
cancelButtonProps: { props: ButtonProps; on: {} };
|
||||
cancelButtonProps: { props: ButtonProps; on: {} }
|
||||
/**
|
||||
* Whether to apply loading visual effect for OK button or not
|
||||
* @default false
|
||||
* @type boolean
|
||||
*/
|
||||
confirmLoading: boolean;
|
||||
confirmLoading: boolean
|
||||
|
||||
showFooter: boolean;
|
||||
footerHeight: string | number;
|
||||
showFooter: boolean
|
||||
footerHeight: string | number
|
||||
}
|
||||
export interface DrawerProps extends DrawerFooterProps {
|
||||
isDetail?: boolean;
|
||||
loading?: boolean;
|
||||
showDetailBack?: boolean;
|
||||
visible?: boolean;
|
||||
isDetail?: boolean
|
||||
loading?: boolean
|
||||
showDetailBack?: boolean
|
||||
visible?: boolean
|
||||
/**
|
||||
* Built-in ScrollContainer component configuration
|
||||
* @type ScrollContainerOptions
|
||||
*/
|
||||
scrollOptions?: ScrollContainerOptions;
|
||||
closeFunc?: () => Promise<any>;
|
||||
triggerWindowResize?: boolean;
|
||||
scrollOptions?: ScrollContainerOptions
|
||||
closeFunc?: () => Promise<any>
|
||||
triggerWindowResize?: boolean
|
||||
/**
|
||||
* Whether a close (x) button is visible on top right of the Drawer dialog or not.
|
||||
* @default true
|
||||
* @type boolean
|
||||
*/
|
||||
closable?: boolean;
|
||||
closable?: boolean
|
||||
|
||||
/**
|
||||
* Whether to unmount child components on closing drawer or not.
|
||||
* @default false
|
||||
* @type boolean
|
||||
*/
|
||||
destroyOnClose?: boolean;
|
||||
destroyOnClose?: boolean
|
||||
|
||||
/**
|
||||
* Return the mounted node for Drawer.
|
||||
* @default 'body'
|
||||
* @type any ( HTMLElement| () => HTMLElement | string)
|
||||
*/
|
||||
getContainer?: () => HTMLElement | string;
|
||||
getContainer?: () => HTMLElement | string
|
||||
|
||||
/**
|
||||
* Whether to show mask or not.
|
||||
* @default true
|
||||
* @type boolean
|
||||
*/
|
||||
mask?: boolean;
|
||||
mask?: boolean
|
||||
|
||||
/**
|
||||
* Clicking on the mask (area outside the Drawer) to close the Drawer or not.
|
||||
* @default true
|
||||
* @type boolean
|
||||
*/
|
||||
maskClosable?: boolean;
|
||||
maskClosable?: boolean
|
||||
|
||||
/**
|
||||
* Style for Drawer's mask element.
|
||||
* @default {}
|
||||
* @type object
|
||||
*/
|
||||
maskStyle?: CSSProperties;
|
||||
maskStyle?: CSSProperties
|
||||
|
||||
/**
|
||||
* The title for Drawer.
|
||||
* @type any (string | slot)
|
||||
*/
|
||||
title?: VNodeChild | JSX.Element;
|
||||
title?: VNodeChild | JSX.Element
|
||||
/**
|
||||
* The class name of the container of the Drawer dialog.
|
||||
* @type string
|
||||
*/
|
||||
wrapClassName?: string;
|
||||
class?: string;
|
||||
wrapClassName?: string
|
||||
class?: string
|
||||
/**
|
||||
* Style of wrapper element which **contains mask** compare to `drawerStyle`
|
||||
* @type object
|
||||
*/
|
||||
wrapStyle?: CSSProperties;
|
||||
wrapStyle?: CSSProperties
|
||||
|
||||
/**
|
||||
* Style of the popup layer element
|
||||
* @type object
|
||||
*/
|
||||
drawerStyle?: CSSProperties;
|
||||
drawerStyle?: CSSProperties
|
||||
|
||||
/**
|
||||
* Style of floating layer, typically used for adjusting its position.
|
||||
* @type object
|
||||
*/
|
||||
bodyStyle?: CSSProperties;
|
||||
headerStyle?: CSSProperties;
|
||||
bodyStyle?: CSSProperties
|
||||
headerStyle?: CSSProperties
|
||||
|
||||
/**
|
||||
* Width of the Drawer dialog.
|
||||
* @default 256
|
||||
* @type string | number
|
||||
*/
|
||||
width?: string | number;
|
||||
width?: string | number
|
||||
|
||||
/**
|
||||
* placement is top or bottom, height of the Drawer dialog.
|
||||
* @type string | number
|
||||
*/
|
||||
height?: string | number;
|
||||
height?: string | number
|
||||
|
||||
/**
|
||||
* The z-index of the Drawer.
|
||||
* @default 1000
|
||||
* @type number
|
||||
*/
|
||||
zIndex?: number;
|
||||
zIndex?: number
|
||||
|
||||
/**
|
||||
* The placement of the Drawer.
|
||||
* @default 'right'
|
||||
* @type string
|
||||
*/
|
||||
placement?: 'top' | 'right' | 'bottom' | 'left';
|
||||
afterVisibleChange?: (visible?: boolean) => void;
|
||||
keyboard?: boolean;
|
||||
placement?: 'top' | 'right' | 'bottom' | 'left'
|
||||
afterVisibleChange?: (visible?: boolean) => void
|
||||
keyboard?: boolean
|
||||
/**
|
||||
* 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 {
|
||||
scrollBottom: () => void;
|
||||
scrollTo: (to: number) => void;
|
||||
getScrollWrap: () => Element | null;
|
||||
scrollBottom: () => void
|
||||
scrollTo: (to: number) => void
|
||||
getScrollWrap: () => Element | null
|
||||
}
|
||||
|
@@ -4,7 +4,7 @@ import type {
|
||||
ReturnMethods,
|
||||
DrawerProps,
|
||||
UseDrawerInnerReturnType,
|
||||
} from './typing';
|
||||
} from './typing'
|
||||
import {
|
||||
ref,
|
||||
getCurrentInstance,
|
||||
@@ -14,148 +14,148 @@ import {
|
||||
nextTick,
|
||||
toRaw,
|
||||
computed,
|
||||
} from 'vue';
|
||||
import { isProdMode } from '/@/utils/env';
|
||||
import { isFunction } from '/@/utils/is';
|
||||
import { tryOnUnmounted } from '@vueuse/core';
|
||||
import { isEqual } from 'lodash-es';
|
||||
import { error } from '/@/utils/log';
|
||||
} from 'vue'
|
||||
import { isProdMode } from '/@/utils/env'
|
||||
import { isFunction } from '/@/utils/is'
|
||||
import { tryOnUnmounted } from '@vueuse/core'
|
||||
import { isEqual } from 'lodash-es'
|
||||
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
|
||||
*/
|
||||
export function useDrawer(): UseDrawerReturnType {
|
||||
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 loaded = ref<Nullable<boolean>>(false);
|
||||
const uid = ref<string>('');
|
||||
const drawer = ref<DrawerInstance | null>(null)
|
||||
const loaded = ref<Nullable<boolean>>(false)
|
||||
const uid = ref<string>('')
|
||||
|
||||
function register(drawerInstance: DrawerInstance, uuid: string) {
|
||||
isProdMode() &&
|
||||
tryOnUnmounted(() => {
|
||||
drawer.value = null;
|
||||
loaded.value = null;
|
||||
dataTransferRef[unref(uid)] = null;
|
||||
});
|
||||
drawer.value = null
|
||||
loaded.value = null
|
||||
dataTransferRef[unref(uid)] = null
|
||||
})
|
||||
|
||||
if (unref(loaded) && isProdMode() && drawerInstance === unref(drawer)) {
|
||||
return;
|
||||
return
|
||||
}
|
||||
uid.value = uuid;
|
||||
drawer.value = drawerInstance;
|
||||
loaded.value = true;
|
||||
uid.value = uuid
|
||||
drawer.value = drawerInstance
|
||||
loaded.value = true
|
||||
|
||||
drawerInstance.emitVisible = (visible: boolean, uid: number) => {
|
||||
visibleData[uid] = visible;
|
||||
};
|
||||
visibleData[uid] = visible
|
||||
}
|
||||
}
|
||||
|
||||
const getInstance = () => {
|
||||
const instance = unref(drawer);
|
||||
const instance = unref(drawer)
|
||||
if (!instance) {
|
||||
error('useDrawer instance is undefined!');
|
||||
error('useDrawer instance is undefined!')
|
||||
}
|
||||
return instance;
|
||||
};
|
||||
return instance
|
||||
}
|
||||
|
||||
const methods: ReturnMethods = {
|
||||
setDrawerProps: (props: Partial<DrawerProps>): void => {
|
||||
getInstance()?.setDrawerProps(props);
|
||||
getInstance()?.setDrawerProps(props)
|
||||
},
|
||||
|
||||
getVisible: computed((): boolean => {
|
||||
return visibleData[~~unref(uid)];
|
||||
return visibleData[~~unref(uid)]
|
||||
}),
|
||||
|
||||
openDrawer: <T = any>(visible = true, data?: T, openOnSet = true): void => {
|
||||
getInstance()?.setDrawerProps({
|
||||
visible: visible,
|
||||
});
|
||||
if (!data) return;
|
||||
})
|
||||
if (!data) return
|
||||
|
||||
if (openOnSet) {
|
||||
dataTransferRef[unref(uid)] = null;
|
||||
dataTransferRef[unref(uid)] = toRaw(data);
|
||||
return;
|
||||
dataTransferRef[unref(uid)] = null
|
||||
dataTransferRef[unref(uid)] = toRaw(data)
|
||||
return
|
||||
}
|
||||
const equal = isEqual(toRaw(dataTransferRef[unref(uid)]), toRaw(data));
|
||||
const equal = isEqual(toRaw(dataTransferRef[unref(uid)]), toRaw(data))
|
||||
if (!equal) {
|
||||
dataTransferRef[unref(uid)] = toRaw(data);
|
||||
dataTransferRef[unref(uid)] = toRaw(data)
|
||||
}
|
||||
},
|
||||
closeDrawer: () => {
|
||||
getInstance()?.setDrawerProps({ visible: false });
|
||||
getInstance()?.setDrawerProps({ visible: false })
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
return [register, methods];
|
||||
return [register, methods]
|
||||
}
|
||||
|
||||
export const useDrawerInner = (callbackFn?: Fn): UseDrawerInnerReturnType => {
|
||||
const drawerInstanceRef = ref<Nullable<DrawerInstance>>(null);
|
||||
const currentInstance = getCurrentInstance();
|
||||
const uidRef = ref<string>('');
|
||||
const drawerInstanceRef = ref<Nullable<DrawerInstance>>(null)
|
||||
const currentInstance = getCurrentInstance()
|
||||
const uidRef = ref<string>('')
|
||||
|
||||
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 instance = unref(drawerInstanceRef);
|
||||
const instance = unref(drawerInstanceRef)
|
||||
if (!instance) {
|
||||
error('useDrawerInner instance is undefined!');
|
||||
return;
|
||||
error('useDrawerInner instance is undefined!')
|
||||
return
|
||||
}
|
||||
return instance;
|
||||
};
|
||||
return instance
|
||||
}
|
||||
|
||||
const register = (modalInstance: DrawerInstance, uuid: string) => {
|
||||
isProdMode() &&
|
||||
tryOnUnmounted(() => {
|
||||
drawerInstanceRef.value = null;
|
||||
});
|
||||
drawerInstanceRef.value = null
|
||||
})
|
||||
|
||||
uidRef.value = uuid;
|
||||
drawerInstanceRef.value = modalInstance;
|
||||
currentInstance?.emit('register', modalInstance, uuid);
|
||||
};
|
||||
uidRef.value = uuid
|
||||
drawerInstanceRef.value = modalInstance
|
||||
currentInstance?.emit('register', modalInstance, uuid)
|
||||
}
|
||||
|
||||
watchEffect(() => {
|
||||
const data = dataTransferRef[unref(uidRef)];
|
||||
if (!data) return;
|
||||
if (!callbackFn || !isFunction(callbackFn)) return;
|
||||
const data = dataTransferRef[unref(uidRef)]
|
||||
if (!data) return
|
||||
if (!callbackFn || !isFunction(callbackFn)) return
|
||||
nextTick(() => {
|
||||
callbackFn(data);
|
||||
});
|
||||
});
|
||||
callbackFn(data)
|
||||
})
|
||||
})
|
||||
|
||||
return [
|
||||
register,
|
||||
{
|
||||
changeLoading: (loading = true) => {
|
||||
getInstance()?.setDrawerProps({ loading });
|
||||
getInstance()?.setDrawerProps({ loading })
|
||||
},
|
||||
|
||||
changeOkLoading: (loading = true) => {
|
||||
getInstance()?.setDrawerProps({ confirmLoading: loading });
|
||||
getInstance()?.setDrawerProps({ confirmLoading: loading })
|
||||
},
|
||||
getVisible: computed((): boolean => {
|
||||
return visibleData[~~unref(uidRef)];
|
||||
return visibleData[~~unref(uidRef)]
|
||||
}),
|
||||
|
||||
closeDrawer: () => {
|
||||
getInstance()?.setDrawerProps({ visible: false });
|
||||
getInstance()?.setDrawerProps({ visible: false })
|
||||
},
|
||||
|
||||
setDrawerProps: (props: Partial<DrawerProps>) => {
|
||||
getInstance()?.setDrawerProps(props);
|
||||
getInstance()?.setDrawerProps(props)
|
||||
},
|
||||
},
|
||||
];
|
||||
};
|
||||
]
|
||||
}
|
||||
|
@@ -1,5 +1,5 @@
|
||||
import { withInstall } from '/@/utils';
|
||||
import dropdown from './src/Dropdown.vue';
|
||||
import { withInstall } from '/@/utils'
|
||||
import dropdown from './src/Dropdown.vue'
|
||||
|
||||
export * from './src/typing';
|
||||
export const Dropdown = withInstall(dropdown);
|
||||
export * from './src/typing'
|
||||
export const Dropdown = withInstall(dropdown)
|
||||
|
@@ -36,18 +36,18 @@
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { computed, PropType } from 'vue';
|
||||
import type { DropMenu } from './typing';
|
||||
import { Dropdown, Menu, Popconfirm } from 'ant-design-vue';
|
||||
import { Icon } from '/@/components/Icon';
|
||||
import { omit } from 'lodash-es';
|
||||
import { isFunction } from '/@/utils/is';
|
||||
import { computed, PropType } from 'vue'
|
||||
import type { DropMenu } from './typing'
|
||||
import { Dropdown, Menu, Popconfirm } from 'ant-design-vue'
|
||||
import { Icon } from '/@/components/Icon'
|
||||
import { omit } from 'lodash-es'
|
||||
import { isFunction } from '/@/utils/is'
|
||||
|
||||
const ADropdown = Dropdown;
|
||||
const AMenu = Menu;
|
||||
const AMenuItem = Menu.Item;
|
||||
const AMenuDivider = Menu.Divider;
|
||||
const APopconfirm = Popconfirm;
|
||||
const ADropdown = Dropdown
|
||||
const AMenu = Menu
|
||||
const AMenuItem = Menu.Item
|
||||
const AMenuDivider = Menu.Divider
|
||||
const APopconfirm = Popconfirm
|
||||
|
||||
const props = defineProps({
|
||||
popconfirm: Boolean,
|
||||
@@ -59,7 +59,7 @@
|
||||
trigger: {
|
||||
type: [Array] as PropType<('contextmenu' | 'click' | 'hover')[]>,
|
||||
default: () => {
|
||||
return ['contextmenu'];
|
||||
return ['contextmenu']
|
||||
},
|
||||
},
|
||||
dropMenuList: {
|
||||
@@ -70,27 +70,27 @@
|
||||
type: Array as PropType<string[]>,
|
||||
default: () => [],
|
||||
},
|
||||
});
|
||||
})
|
||||
|
||||
const emit = defineEmits(['menuEvent']);
|
||||
const emit = defineEmits(['menuEvent'])
|
||||
|
||||
function handleClickMenu(item: DropMenu) {
|
||||
const { event } = item;
|
||||
const menu = props.dropMenuList.find((item) => `${item.event}` === `${event}`);
|
||||
emit('menuEvent', menu);
|
||||
item.onClick?.();
|
||||
const { event } = item
|
||||
const menu = props.dropMenuList.find((item) => `${item.event}` === `${event}`)
|
||||
emit('menuEvent', menu)
|
||||
item.onClick?.()
|
||||
}
|
||||
|
||||
const getPopConfirmAttrs = computed(() => {
|
||||
return (attrs) => {
|
||||
const originAttrs = omit(attrs, ['confirm', 'cancel', 'icon']);
|
||||
const originAttrs = omit(attrs, ['confirm', 'cancel', 'icon'])
|
||||
if (!attrs.onConfirm && attrs.confirm && isFunction(attrs.confirm))
|
||||
originAttrs['onConfirm'] = attrs.confirm;
|
||||
originAttrs['onConfirm'] = attrs.confirm
|
||||
if (!attrs.onCancel && attrs.cancel && isFunction(attrs.cancel))
|
||||
originAttrs['onCancel'] = attrs.cancel;
|
||||
return originAttrs;
|
||||
};
|
||||
});
|
||||
originAttrs['onCancel'] = attrs.cancel
|
||||
return originAttrs
|
||||
}
|
||||
})
|
||||
|
||||
const getAttr = (key: string | number) => ({ key });
|
||||
const getAttr = (key: string | number) => ({ key })
|
||||
</script>
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user