Compare commits

...

3 Commits

Author SHA1 Message Date
unitwk
43a4d986b2 fix: Upgrade Vue version (#3023) 2023-09-13 20:26:07 +08:00
Shell2
c4216e24d6 explicitly checkout the thin branch (#2464)
explicitly checkout the thin branch
2022-12-18 19:54:20 +08:00
saber
d5e2d26a0f Compact main branch (#2255)
* init

* init

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

* fix: sime

* wip(lock): remove

* fix: LOCK

* fix: lock

* init

* feat: remove lock

* chore: remove semi

* chore: chore

* chore: chore

* chore: chore

* init

* init

* init

* init

* init

* init

* init

* init

* init

* init

* init

* init

* init

* init

* init

* init

* init

* init

* init

* init

* init

* init
2022-10-10 10:53:48 +08:00
716 changed files with 7342 additions and 48287 deletions

View File

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

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

176
README.md
View File

@@ -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.
[![Open in Gitpod](https://gitpod.io/button/open-in-gitpod.svg)](https://gitpod.io/#https://github.com/anncwb/vue-vben-admin)
## Documentation
[Document](https://vvbin.cn/doc-next/)
## Preparation
- [node](http://nodejs.org/) and [git](https://git-scm.com/) - Project development environment
- [Vite](https://vitejs.dev/) - Familiar with vite features
- [Vue3](https://v3.vuejs.org/) - Familiar with Vue basic syntax
- [TypeScript](https://www.typescriptlang.org/) - Familiar with the basic syntax of `TypeScript`
- [Es6+](http://es6.ruanyifeng.com/) - Familiar with es6 basic syntax
- [Vue-Router-Next](https://next.router.vuejs.org/) - Familiar with the basic use of vue-router
- [Ant-Design-Vue](https://2x.antdv.com/docs/vue/introduce-cn/) - ui basic use
- [Mock.js](https://github.com/nuysoft/Mock) - mockjs basic syntax
## Install and use
- Get the project code
- 获取项目代码
```bash
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[Raise an issue](https://github.com/anncwb/vue-vben-admin/issues/new/choose) 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!
![donate](https://anncwb.github.io/anncwb/images/sponsor.png)
<a style="display: block;width: 100px;height: 50px;line-height: 50px; color: #fff;text-align: center; background: #408aed;border-radius: 4px;" href="https://www.paypal.com/paypalme/cvvben">Paypal Me</a>
## Discord
- [github discussions](https://github.com/anncwb/vue-vben-admin/discussions)
- [Discord](https://discord.gg/8GuAdwDhj6)
- [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

View File

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

View File

@@ -1,20 +1,20 @@
import { PluginOption } from 'vite';
import 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
}

View File

@@ -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
}

View File

@@ -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: 自定义',
},
};
}

View File

@@ -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>

View File

@@ -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[]

View File

@@ -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

File diff suppressed because it is too large Load Diff

View File

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

View File

@@ -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>

View File

@@ -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 })

View File

@@ -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 })

View File

@@ -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 })

View File

@@ -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
}

View File

@@ -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
}

View File

@@ -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>

View File

@@ -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[]

View File

@@ -1,20 +1,20 @@
import { BasicPageParams, BasicFetchResult } from '/@/api/model/baseModel';
import { BasicPageParams, BasicFetchResult } from '/@/api/model/baseModel'
/**
* @description: Request list interface parameters
*/
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>

View File

@@ -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 })

View File

@@ -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' })

View File

@@ -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,
},
});
})

View File

@@ -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 })

View File

@@ -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
}

View File

@@ -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 })
}

View File

@@ -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[]

View File

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

View File

@@ -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
}

View File

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

View File

@@ -1,7 +1,7 @@
import { defHttp } from '/@/utils/http/axios';
import { 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,
},
},
);
)
}

View File

@@ -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)

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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';

View File

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

View File

@@ -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>

View File

@@ -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 }
}

View File

@@ -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)
}

View File

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

View File

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

View File

@@ -1,8 +1,8 @@
import { withInstall } from '/@/utils';
import 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)

View File

@@ -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';

View File

@@ -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';

View File

@@ -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';

View File

@@ -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>>

View File

@@ -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>

View File

@@ -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>

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,10 +1,10 @@
import { withInstall } from '/@/utils';
import 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'

View File

@@ -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>

View File

@@ -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 {

View File

@@ -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';

View File

@@ -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>

View File

@@ -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
}>

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,6 +1,6 @@
import { withInstall } from '/@/utils';
import 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)

View File

@@ -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>

View File

@@ -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';

View File

@@ -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 }
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,6 +1,6 @@
import { withInstall } from '/@/utils';
import 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)

View File

@@ -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>

View File

@@ -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]

View File

@@ -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]
}

View File

@@ -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'

View File

@@ -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;

View File

@@ -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">

View File

@@ -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">

View File

@@ -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,
};
}

View File

@@ -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
}

View File

@@ -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)
},
},
];
};
]
}

View File

@@ -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)

View File

@@ -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