Merge branch 'main' of https://gitee.com/dapppp/ruoyi-plus-vben5
215
README.zh-CN.md
@ -1,126 +1,153 @@
|
|||||||
开发中:
|
|
||||||
|
|
||||||
工作流模块等待后端重构后开发 暂时不开发
|
|
||||||
|
|
||||||
**目前表格仅为测试使用 不代表最终版本**
|
|
||||||
|
|
||||||
其他:
|
|
||||||
|
|
||||||
1. 等待官方Table组件
|
|
||||||
2. 已完成所有表单开发
|
|
||||||
|
|
||||||
演示站: [点击前往](https://vben5.dapdap.top)
|
|
||||||
|
|
||||||
贡献规范:
|
|
||||||
|
|
||||||
文件夹/组件采用短横线命名法: 比如 `a-table.vue test-form.vue`
|
|
||||||
|
|
||||||
组件导入采用手动导入&大写(button/input等常用除外 可正常使用a-button/a-input) 与html标签区分开
|
|
||||||
|
|
||||||
```vue
|
|
||||||
<template>
|
|
||||||
<Table />
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script setup lang="ts">
|
|
||||||
import { Table } from 'ant-design-vue';
|
|
||||||
</script>
|
|
||||||
```
|
|
||||||
|
|
||||||
<div align="center"> <a href="https://github.com/anncwb/vue-vben-admin"> <img alt="VbenAdmin Logo" width="215" src="https://unpkg.com/@vbenjs/static-source@0.1.6/source/logo-v1.webp"> </a> <br> <br>
|
|
||||||
|
|
||||||
[](LICENSE)
|
[](LICENSE)
|
||||||
|
|
||||||
<h1>Vue Vben Admin</h1>
|
## 提示
|
||||||
</div>
|
|
||||||
|
|
||||||
[](https://sonarcloud.io/summary/new_code?id=vbenjs_vue-vben-admin)    
|
该仓库使用vben最新版本v5开发, 老版本v2地址 [前往](https://gitee.com/dapppp/ruoyi-plus-vben)
|
||||||
|
|
||||||
**中文** | [English](./README.md) | [日本語](./README.ja-JP.md)
|
v5版本采用分仓(包)目录结构, 具体开发路径为: `根目录/apps/web-antd`
|
||||||
|
|
||||||
|
## 进度
|
||||||
|
|
||||||
|
目前为beta版本 等待最后收尾
|
||||||
|
|
||||||
|
**工作流相关模块等待后端重构后开发**
|
||||||
|
|
||||||
|
基础功能大致完成,有一部分需要等待官方更新
|
||||||
|
|
||||||
|
除文件上传(不包含图片上传) 基本功能都已完成
|
||||||
|
|
||||||
## 简介
|
## 简介
|
||||||
|
|
||||||
Vue Vben Admin 是 Vue Vben Admin 的升级版本。作为一个免费开源的中后台模板,它采用了最新的 Vue 3、Vite、TypeScript 等主流技术开发,开箱即用,可用于中后台前端开发,也适合学习参考。
|
基于 [vben5 & ant-design-vue](https://github.com/vbenjs/vue-vben-admin) 的 RuoYi-Vue-Plus 前端项目
|
||||||
|
|
||||||
## 升级提示
|
| 组件/框架 | 版本 |
|
||||||
|
| :------------- | :----- |
|
||||||
|
| vben | 5.3.2 |
|
||||||
|
| ant-design-vue | 4.2.5 |
|
||||||
|
| vue | 3.5.11 |
|
||||||
|
|
||||||
该版本为最新版本`5.0`, 与其他版本不兼容,如果你是新项目,建议使用最新版本。如果你想查看旧版本,请使用 [v2 分支](https://github.com/vbenjs/vue-vben-admin/tree/v2)
|
对应后端项目: **(分布式 5.X 分支 微服务 2.分支)**
|
||||||
|
|
||||||
## 特性
|
分布式 [RuoYi-Vue-Plus](https://gitee.com/dromara/RuoYi-Vue-Plus/tree/5.X/)
|
||||||
|
|
||||||
- **最新技术栈**:使用 Vue3/vite 等前端前沿技术开发
|
微服务 [RuoYi-Cloud-Plus](https://gitee.com/dromara/RuoYi-Cloud-Plus/tree/2.X/)
|
||||||
- **TypeScript**: 应用程序级 JavaScript 的语言
|
|
||||||
- **主题**:提供多套主题色彩,可配置自定义主题
|
|
||||||
- **国际化**:内置完善的国际化方案
|
|
||||||
- **权限** 内置完善的动态路由权限生成方案
|
|
||||||
|
|
||||||
## 预览
|
## 预览
|
||||||
|
|
||||||
- [Vben Admin](https://vben.pro/) - 完整版中文站点
|
admin 账号: admin admin123
|
||||||
|
|
||||||
测试账号: vben/123456
|
[预览地址点这里](http://vben5.dapdap.top)
|
||||||
|
|
||||||
<p align="center">
|
## WX Group
|
||||||
<img alt="VbenAdmin Logo" width="100%" src="https://anncwb.github.io/anncwb/images/preview1.png">
|
|
||||||
<img alt="VbenAdmin Logo" width="100%" src="https://anncwb.github.io/anncwb/images/preview2.png">
|
|
||||||
<img alt="VbenAdmin Logo" width="100%" src="https://anncwb.github.io/anncwb/images/preview3.png">
|
|
||||||
</p>
|
|
||||||
|
|
||||||
### 使用 Gitpod
|
暂不开放
|
||||||
|
|
||||||
在 Gitpod(适用于 GitHub 的免费在线开发环境)中打开项目,并立即开始编码.
|
|
||||||
|
|
||||||
[](https://gitpod.io/#https://github.com/vbenjs/vue-vben-admin)
|
|
||||||
|
|
||||||
## 文档
|
## 文档
|
||||||
|
|
||||||
[文档地址](https://doc.vben.pro/)
|
[vben 文档地址](https://doc.vvbin.cn/)
|
||||||
|
|
||||||
|
[RuoYi-Plus 文档地址](https://plus-doc.dromara.org/#/)
|
||||||
|
|
||||||
|
## 预览图
|
||||||
|
|
||||||
|
         
|
||||||
|
|
||||||
## 安装使用
|
## 安装使用
|
||||||
|
|
||||||
|
前置准备环境(只能用pnpm)
|
||||||
|
|
||||||
|
```json
|
||||||
|
"packageManager": "pnpm",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=20.10.0",
|
||||||
|
"pnpm": ">=9.5.0"
|
||||||
|
},
|
||||||
|
```
|
||||||
|
|
||||||
- 获取项目代码
|
- 获取项目代码
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
git clone https://github.com/vbenjs/vue-vben-admin.git
|
git clone https://gitee.com/dapppp/ruoyi-plus-vben5.git
|
||||||
```
|
```
|
||||||
|
|
||||||
- 安装依赖
|
- 安装依赖
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
cd vue-vben-admin
|
cd ruoyi-plus-vben5
|
||||||
|
|
||||||
corepack enable
|
|
||||||
|
|
||||||
pnpm install
|
pnpm install
|
||||||
```
|
```
|
||||||
|
|
||||||
|
- 关于代码生成
|
||||||
|
|
||||||
|
v5版本代码生成模板为付费功能(暂未开放)
|
||||||
|
|
||||||
|
- 关于一些监控的地址配置(微服务版本可以跳过这一小节)
|
||||||
|
|
||||||
|
使用[RuoYi-Vue-Plus](https://gitee.com/dromara/RuoYi-Vue-Plus/tree/5.X/)注意 `已经去除 admin/snailjob 的.env 配置` 可自行修改 有两种方式
|
||||||
|
|
||||||
|
1. 修改源码`/views/monitor/admin` `views/monitor/snailjob`
|
||||||
|
|
||||||
|
```ts
|
||||||
|
// 修改地址
|
||||||
|
const url = ref<string>('http://127.0.0.1:7700/#/oms/home');
|
||||||
|
```
|
||||||
|
|
||||||
|
2. **推荐** 使用菜单自行配置 (跟 cloud 版本打开方式一致)
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
使用内嵌 iframe 方式需要解决跨域问题 可参考[nginx.conf](https://gitee.com/dromara/RuoYi-Vue-Plus/blob/5.X/script/docker/nginx/conf/nginx.conf#LC87)配置
|
||||||
|
|
||||||
|
- 修改.env.development 配置文件
|
||||||
|
- **注意 RSA 公私钥一定要修改和后端匹配**
|
||||||
|
- RSA 公私钥为两对 `前端请求加密-后端解密是一对` `后端响应加密 前端解密是一对`
|
||||||
|
|
||||||
|
```properties
|
||||||
|
# 端口号
|
||||||
|
VITE_PORT=5666
|
||||||
|
|
||||||
|
VITE_BASE=/
|
||||||
|
# 是否开启 Nitro Mock服务,true 为开启,false 为关闭
|
||||||
|
VITE_NITRO_MOCK=false
|
||||||
|
# 是否打开 devtools,true 为打开,false 为关闭
|
||||||
|
VITE_DEVTOOLS=false
|
||||||
|
# 是否注入全局loading
|
||||||
|
VITE_INJECT_APP_LOADING=true
|
||||||
|
|
||||||
|
# 后台请求路径 具体在vite.config.mts配置代理
|
||||||
|
VITE_GLOB_API_URL=/api
|
||||||
|
|
||||||
|
# 全局加密开关(即开启了加解密功能才会生效 不是全部接口加密 需要和后端对应)
|
||||||
|
VITE_GLOB_ENABLE_ENCRYPT=true
|
||||||
|
# RSA公钥 请求加密使用 注意这两个是两对RSA公私钥 请求加密-后端解密是一对 响应解密-后端加密是一对
|
||||||
|
VITE_GLOB_RSA_PUBLIC_KEY=
|
||||||
|
# RSA私钥 响应解密使用 注意这两个是两对RSA公私钥 请求加密-后端解密是一对 响应解密-后端加密是一对
|
||||||
|
VITE_GLOB_RSA_PRIVATE_KEY=
|
||||||
|
# 客户端id
|
||||||
|
VITE_GLOB_APP_CLIENT_ID=e5cd7e4891bf95d1d19206ce24a7b32e
|
||||||
|
|
||||||
|
# 开启WEBSOCKET
|
||||||
|
VITE_GLOB_WEBSOCKET_ENABLE=false
|
||||||
|
```
|
||||||
|
|
||||||
- 运行
|
- 运行
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
pnpm dev
|
pnpm dev:antd
|
||||||
```
|
```
|
||||||
|
|
||||||
- 打包
|
- 打包
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
pnpm build
|
pnpm build:antd
|
||||||
```
|
```
|
||||||
|
|
||||||
## 更新日志
|
## 这是一个特性 而不是一个bug!
|
||||||
|
|
||||||
[CHANGELOG](https://github.com/vbenjs/vue-vben-admin/releases)
|
1. 菜单管理可分配 但只有`admin`/`superadmin`角色能访问 其他角色访问会到403页面
|
||||||
|
2. 租户相关菜单可分配 但只有`superadmin`角色能访问 其他角色访问会到403页面
|
||||||
## 如何贡献
|
3. 分配的租户管理员无法修改自己的角色的菜单(即管理员角色的菜单) 防止自己把自己权限弄没了
|
||||||
|
|
||||||
非常欢迎你的加入 或者提交一个 Pull Request。
|
|
||||||
|
|
||||||
**Pull Request:**
|
|
||||||
|
|
||||||
1. Fork 代码!
|
|
||||||
2. 创建自己的分支: `git checkout -b feature/xxxx`
|
|
||||||
3. 提交你的修改: `git commit -am 'feat(function): add xxxxx'`
|
|
||||||
4. 推送您的分支: `git push origin feature/xxxx`
|
|
||||||
5. 提交`pull request`
|
|
||||||
|
|
||||||
## Git 贡献提交规范
|
## Git 贡献提交规范
|
||||||
|
|
||||||
@ -135,13 +162,16 @@ pnpm build
|
|||||||
- `test` 测试相关
|
- `test` 测试相关
|
||||||
- `docs` 文档/注释
|
- `docs` 文档/注释
|
||||||
- `chore` 依赖更新/脚手架配置修改等
|
- `chore` 依赖更新/脚手架配置修改等
|
||||||
|
- `workflow` 工作流改进
|
||||||
- `ci` 持续集成
|
- `ci` 持续集成
|
||||||
- `types` 类型定义文件更改
|
- `types` 类型定义文件更改
|
||||||
- `wip` 开发中
|
- `wip` 开发中
|
||||||
|
|
||||||
## 浏览器支持
|
## 浏览器支持
|
||||||
|
|
||||||
本地开发推荐使用`Chrome 80+` 浏览器
|
最低适配应该为`Chrome 88+`以上浏览器 详见 [css - where](https://developer.mozilla.org/en-US/docs/Web/CSS/:where#browser_compatibility)
|
||||||
|
|
||||||
|
本地开发推荐使用`Chrome` 最新版本浏览器
|
||||||
|
|
||||||
支持现代浏览器, 不支持 IE
|
支持现代浏览器, 不支持 IE
|
||||||
|
|
||||||
@ -149,33 +179,8 @@ pnpm build
|
|||||||
| :-: | :-: | :-: | :-: | :-: |
|
| :-: | :-: | :-: | :-: | :-: |
|
||||||
| not support | last 2 versions | last 2 versions | last 2 versions | last 2 versions |
|
| not support | last 2 versions | last 2 versions | last 2 versions | last 2 versions |
|
||||||
|
|
||||||
## 维护者
|
|
||||||
|
|
||||||
[@Vben](https://github.com/anncwb)
|
|
||||||
|
|
||||||
## Star History
|
|
||||||
|
|
||||||
[](https://star-history.com/#vbenjs/vue-vben-admin&Date)
|
|
||||||
|
|
||||||
## 捐赠
|
## 捐赠
|
||||||
|
|
||||||
如果你觉得这个项目对你有帮助,你可以帮作者买一杯咖啡表示支持!
|
如果项目帮助到您 可以考虑请作者喝杯咖啡 万分感谢您对开源的支持!
|
||||||
|
|
||||||

|
<img src=https://plus.dapdap.top/minio-server/plus/2024/03/16/98a9d704eb0c4c04b721bf7799217571.jpg height=360px />
|
||||||
|
|
||||||
<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>
|
|
||||||
|
|
||||||
## Contributor
|
|
||||||
|
|
||||||
<a href="https://github.com/vbenjs/vue-vben-admin/graphs/contributors">
|
|
||||||
<img alt="Contributors"
|
|
||||||
src="https://opencollective.com/vbenjs/contributors.svg?button=false" />
|
|
||||||
</a>
|
|
||||||
|
|
||||||
## Discord
|
|
||||||
|
|
||||||
- [Github Discussions](https://github.com/anncwb/vue-vben-admin/discussions)
|
|
||||||
|
|
||||||
## License
|
|
||||||
|
|
||||||
[MIT © Vben-2020](./LICENSE)
|
|
||||||
|
@ -26,6 +26,7 @@
|
|||||||
"#/*": "./src/*"
|
"#/*": "./src/*"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@ant-design/icons-vue": "^7.0.1",
|
||||||
"@tinymce/tinymce-vue": "^6.0.1",
|
"@tinymce/tinymce-vue": "^6.0.1",
|
||||||
"@vben/access": "workspace:*",
|
"@vben/access": "workspace:*",
|
||||||
"@vben/common-ui": "workspace:*",
|
"@vben/common-ui": "workspace:*",
|
||||||
|
@ -36,6 +36,7 @@ import {
|
|||||||
import { isArray } from 'lodash-es';
|
import { isArray } from 'lodash-es';
|
||||||
|
|
||||||
import { Tinymce as RichTextarea } from '#/components/tinymce';
|
import { Tinymce as RichTextarea } from '#/components/tinymce';
|
||||||
|
import { ImageUpload } from '#/components/upload';
|
||||||
|
|
||||||
// 这里需要自行根据业务组件库进行适配,需要用到的组件都需要在这里类型说明
|
// 这里需要自行根据业务组件库进行适配,需要用到的组件都需要在这里类型说明
|
||||||
export type FormComponentType =
|
export type FormComponentType =
|
||||||
@ -44,6 +45,7 @@ export type FormComponentType =
|
|||||||
| 'CheckboxGroup'
|
| 'CheckboxGroup'
|
||||||
| 'DatePicker'
|
| 'DatePicker'
|
||||||
| 'Divider'
|
| 'Divider'
|
||||||
|
| 'ImageUpload'
|
||||||
| 'Input'
|
| 'Input'
|
||||||
| 'InputNumber'
|
| 'InputNumber'
|
||||||
| 'InputPassword'
|
| 'InputPassword'
|
||||||
@ -104,6 +106,7 @@ setupVbenForm<FormComponentType>({
|
|||||||
TimePicker,
|
TimePicker,
|
||||||
TreeSelect: withDefaultPlaceholder(TreeSelect, 'select'),
|
TreeSelect: withDefaultPlaceholder(TreeSelect, 'select'),
|
||||||
Upload,
|
Upload,
|
||||||
|
ImageUpload,
|
||||||
},
|
},
|
||||||
config: {
|
config: {
|
||||||
// ant design vue组件库默认都是 v-model:value
|
// ant design vue组件库默认都是 v-model:value
|
||||||
|
@ -1,7 +1,13 @@
|
|||||||
import { requestClient } from '#/api/request';
|
import { requestClient } from '#/api/request';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 通过单文件上传接口
|
||||||
|
* @param file 上传的文件
|
||||||
|
* @returns 上传结果
|
||||||
|
*/
|
||||||
export function uploadApi(file: Blob | File) {
|
export function uploadApi(file: Blob | File) {
|
||||||
return requestClient.upload('/resource/oss/upload', file);
|
console.log('uploadApi', file);
|
||||||
|
return requestClient.upload('/resource/oss/upload', { file });
|
||||||
}
|
}
|
||||||
/**
|
/**
|
||||||
* 默认上传结果
|
* 默认上传结果
|
||||||
|
1
apps/web-antd/src/components/upload/index.ts
Normal file
@ -0,0 +1 @@
|
|||||||
|
export { default as ImageUpload } from './src/image-upload.vue';
|
32
apps/web-antd/src/components/upload/src/helper.ts
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
export function checkFileType(file: File, accepts: string[]) {
|
||||||
|
let reg;
|
||||||
|
if (!accepts || accepts.length === 0) {
|
||||||
|
reg = /.(?:jpg|jpeg|png|gif|webp)$/i;
|
||||||
|
} else {
|
||||||
|
const newTypes = accepts.join('|');
|
||||||
|
reg = new RegExp(`${String.raw`\.(` + newTypes})$`, 'i');
|
||||||
|
}
|
||||||
|
return reg.test(file.name);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function checkImgType(file: File) {
|
||||||
|
return isImgTypeByName(file.name);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function isImgTypeByName(name: string) {
|
||||||
|
return /\.(?:jpg|jpeg|png|gif|webp)$/i.test(name);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getBase64WithFile(file: File) {
|
||||||
|
return new Promise<{
|
||||||
|
file: File;
|
||||||
|
result: string;
|
||||||
|
}>((resolve, reject) => {
|
||||||
|
const reader = new FileReader();
|
||||||
|
reader.readAsDataURL(file);
|
||||||
|
reader.addEventListener('load', () =>
|
||||||
|
resolve({ result: reader.result as string, file }),
|
||||||
|
);
|
||||||
|
reader.addEventListener('error', (error) => reject(error));
|
||||||
|
});
|
||||||
|
}
|
261
apps/web-antd/src/components/upload/src/image-upload.vue
Normal file
@ -0,0 +1,261 @@
|
|||||||
|
<script lang="ts" setup>
|
||||||
|
import type { Recordable } from '@vben/types';
|
||||||
|
import type { UploadFile, UploadProps } from 'ant-design-vue';
|
||||||
|
import type { UploadRequestOption } from 'ant-design-vue/lib/vc-upload/interface';
|
||||||
|
|
||||||
|
import { ref, toRefs, watch } from 'vue';
|
||||||
|
|
||||||
|
import { $t } from '@vben/locales';
|
||||||
|
|
||||||
|
import { PlusOutlined } from '@ant-design/icons-vue';
|
||||||
|
import { message, Modal, Upload } from 'ant-design-vue';
|
||||||
|
import { isArray, isFunction, isObject, isString } from 'lodash-es';
|
||||||
|
|
||||||
|
import { uploadApi } from '#/api';
|
||||||
|
|
||||||
|
import { checkFileType } from './helper';
|
||||||
|
import { UploadResultStatus } from './typing';
|
||||||
|
import { useUploadType } from './use-upload';
|
||||||
|
|
||||||
|
defineOptions({ name: 'ImageUpload' });
|
||||||
|
|
||||||
|
const props = withDefaults(
|
||||||
|
defineProps<{
|
||||||
|
/**
|
||||||
|
* 建议使用拓展名(不带.)
|
||||||
|
* 或者文件头 image/png等(测试判断不准确) 不支持image/*类似的写法
|
||||||
|
* 需自行改造 ./helper/checkFileType方法
|
||||||
|
*/
|
||||||
|
accept?: string[];
|
||||||
|
api?: (...args: any[]) => Promise<any>;
|
||||||
|
disabled?: boolean;
|
||||||
|
filename?: null | string;
|
||||||
|
helpText?: string;
|
||||||
|
// eslint-disable-next-line no-use-before-define
|
||||||
|
listType?: ListType;
|
||||||
|
// 最大数量的文件,Infinity不限制
|
||||||
|
maxNumber?: number;
|
||||||
|
// 文件最大多少MB
|
||||||
|
maxSize?: number;
|
||||||
|
multiple?: boolean;
|
||||||
|
name?: string;
|
||||||
|
// support xxx.xxx.xx
|
||||||
|
resultField?: string;
|
||||||
|
uploadParams?: Recordable<any>;
|
||||||
|
value?: string[];
|
||||||
|
}>(),
|
||||||
|
{
|
||||||
|
value: () => [],
|
||||||
|
disabled: false,
|
||||||
|
listType: 'picture-card',
|
||||||
|
helpText: '',
|
||||||
|
maxSize: 2,
|
||||||
|
maxNumber: 1,
|
||||||
|
accept: () => [],
|
||||||
|
multiple: false,
|
||||||
|
uploadParams: () => ({}),
|
||||||
|
api: uploadApi,
|
||||||
|
name: 'file',
|
||||||
|
filename: null,
|
||||||
|
resultField: '',
|
||||||
|
},
|
||||||
|
);
|
||||||
|
const emit = defineEmits(['change', 'update:value', 'delete']);
|
||||||
|
type ListType = 'picture' | 'picture-card' | 'text';
|
||||||
|
const { accept, helpText, maxNumber, maxSize } = toRefs(props);
|
||||||
|
const isInnerOperate = ref<boolean>(false);
|
||||||
|
const { getStringAccept } = useUploadType({
|
||||||
|
acceptRef: accept,
|
||||||
|
helpTextRef: helpText,
|
||||||
|
maxNumberRef: maxNumber,
|
||||||
|
maxSizeRef: maxSize,
|
||||||
|
});
|
||||||
|
const previewOpen = ref<boolean>(false);
|
||||||
|
const previewImage = ref<string>('');
|
||||||
|
const previewTitle = ref<string>('');
|
||||||
|
|
||||||
|
const fileList = ref<UploadProps['fileList']>([]);
|
||||||
|
const isLtMsg = ref<boolean>(true);
|
||||||
|
const isActMsg = ref<boolean>(true);
|
||||||
|
const isFirstRender = ref<boolean>(true);
|
||||||
|
|
||||||
|
watch(
|
||||||
|
() => props.value,
|
||||||
|
(v) => {
|
||||||
|
if (isInnerOperate.value) {
|
||||||
|
isInnerOperate.value = false;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
let value: string[] = [];
|
||||||
|
if (v) {
|
||||||
|
if (isArray(v)) {
|
||||||
|
value = v;
|
||||||
|
} else {
|
||||||
|
value.push(v);
|
||||||
|
}
|
||||||
|
fileList.value = value.map((item, i) => {
|
||||||
|
if (item && isString(item)) {
|
||||||
|
return {
|
||||||
|
uid: `${-i}`,
|
||||||
|
name: item.slice(Math.max(0, item.lastIndexOf('/') + 1)),
|
||||||
|
status: 'done',
|
||||||
|
url: item,
|
||||||
|
};
|
||||||
|
} else if (item && isObject(item)) {
|
||||||
|
return item;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}) as UploadProps['fileList'];
|
||||||
|
}
|
||||||
|
emit('update:value', value);
|
||||||
|
if (!isFirstRender.value) {
|
||||||
|
emit('change', value);
|
||||||
|
isFirstRender.value = false;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
immediate: true,
|
||||||
|
deep: true,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
function getBase64<T extends ArrayBuffer | null | string>(file: File) {
|
||||||
|
return new Promise<T>((resolve, reject) => {
|
||||||
|
const reader = new FileReader();
|
||||||
|
reader.readAsDataURL(file);
|
||||||
|
reader.addEventListener('load', () => {
|
||||||
|
resolve(reader.result as T);
|
||||||
|
});
|
||||||
|
reader.addEventListener('error', (error) => reject(error));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const handlePreview = async (file: UploadFile) => {
|
||||||
|
if (!file.url && !file.preview) {
|
||||||
|
file.preview = await getBase64<string>(file.originFileObj!);
|
||||||
|
}
|
||||||
|
previewImage.value = file.url || file.preview || '';
|
||||||
|
previewOpen.value = true;
|
||||||
|
previewTitle.value =
|
||||||
|
file.name ||
|
||||||
|
previewImage.value.slice(
|
||||||
|
Math.max(0, previewImage.value.lastIndexOf('/') + 1),
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleRemove = async (file: UploadFile) => {
|
||||||
|
if (fileList.value) {
|
||||||
|
const index = fileList.value.findIndex((item) => item.uid === file.uid);
|
||||||
|
index !== -1 && fileList.value.splice(index, 1);
|
||||||
|
const value = getValue();
|
||||||
|
isInnerOperate.value = true;
|
||||||
|
emit('update:value', value);
|
||||||
|
emit('change', value);
|
||||||
|
emit('delete', file);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleCancel = () => {
|
||||||
|
previewOpen.value = false;
|
||||||
|
previewTitle.value = '';
|
||||||
|
};
|
||||||
|
|
||||||
|
const beforeUpload = (file: File) => {
|
||||||
|
const { maxSize, accept } = props;
|
||||||
|
const isAct = checkFileType(file, accept);
|
||||||
|
if (!isAct) {
|
||||||
|
message.error($t('component.upload.acceptUpload', [accept]));
|
||||||
|
isActMsg.value = false;
|
||||||
|
// 防止弹出多个错误提示
|
||||||
|
setTimeout(() => (isActMsg.value = true), 1000);
|
||||||
|
}
|
||||||
|
const isLt = file.size / 1024 / 1024 > maxSize;
|
||||||
|
if (isLt) {
|
||||||
|
message.error($t('component.upload.maxSizeMultiple', [maxSize]));
|
||||||
|
isLtMsg.value = false;
|
||||||
|
// 防止弹出多个错误提示
|
||||||
|
setTimeout(() => (isLtMsg.value = true), 1000);
|
||||||
|
}
|
||||||
|
return (isAct && !isLt) || Upload.LIST_IGNORE;
|
||||||
|
};
|
||||||
|
|
||||||
|
async function customRequest(info: UploadRequestOption<any>) {
|
||||||
|
const { api } = props;
|
||||||
|
if (!api || !isFunction(api)) {
|
||||||
|
console.warn('upload api must exist and be a function');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
const res = await api?.(info.file);
|
||||||
|
/**
|
||||||
|
* 由getValue处理 传对象过去
|
||||||
|
* 直接传string(id)会被转为Number
|
||||||
|
*/
|
||||||
|
info.onSuccess!(res);
|
||||||
|
// 获取
|
||||||
|
const value = getValue();
|
||||||
|
isInnerOperate.value = true;
|
||||||
|
emit('update:value', value);
|
||||||
|
emit('change', value);
|
||||||
|
} catch (error: any) {
|
||||||
|
console.error(error);
|
||||||
|
info.onError!(error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function getValue() {
|
||||||
|
const list = (fileList.value || [])
|
||||||
|
.filter((item) => item?.status === UploadResultStatus.DONE)
|
||||||
|
.map((item: any) => {
|
||||||
|
if (item?.response && props?.resultField) {
|
||||||
|
return item?.response?.[props.resultField];
|
||||||
|
}
|
||||||
|
// 注意这里取的key为 url
|
||||||
|
return item?.response?.url;
|
||||||
|
});
|
||||||
|
return list;
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<Upload
|
||||||
|
v-bind="$attrs"
|
||||||
|
v-model:file-list="fileList"
|
||||||
|
:accept="getStringAccept"
|
||||||
|
:before-upload="beforeUpload"
|
||||||
|
:custom-request="customRequest"
|
||||||
|
:disabled="disabled"
|
||||||
|
:list-type="listType"
|
||||||
|
:max-count="maxNumber"
|
||||||
|
:multiple="multiple"
|
||||||
|
@preview="handlePreview"
|
||||||
|
@remove="handleRemove"
|
||||||
|
>
|
||||||
|
<div v-if="fileList && fileList.length < maxNumber">
|
||||||
|
<PlusOutlined />
|
||||||
|
<div style="margin-top: 8px">{{ $t('component.upload.upload') }}</div>
|
||||||
|
</div>
|
||||||
|
</Upload>
|
||||||
|
<Modal
|
||||||
|
:footer="null"
|
||||||
|
:open="previewOpen"
|
||||||
|
:title="previewTitle"
|
||||||
|
@cancel="handleCancel"
|
||||||
|
>
|
||||||
|
<img :src="previewImage" alt="" style="width: 100%" />
|
||||||
|
</Modal>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style lang="less">
|
||||||
|
.ant-upload-select-picture-card i {
|
||||||
|
color: #999;
|
||||||
|
font-size: 32px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ant-upload-select-picture-card .ant-upload-text {
|
||||||
|
margin-top: 8px;
|
||||||
|
color: #666;
|
||||||
|
}
|
||||||
|
</style>
|
37
apps/web-antd/src/components/upload/src/typing.ts
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
import type { Recordable } from '@vben/types';
|
||||||
|
|
||||||
|
export enum UploadResultStatus {
|
||||||
|
DONE = 'done',
|
||||||
|
ERROR = 'error',
|
||||||
|
SUCCESS = 'success',
|
||||||
|
UPLOADING = 'uploading',
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface FileItem {
|
||||||
|
thumbUrl?: string;
|
||||||
|
name: string;
|
||||||
|
size: number | string;
|
||||||
|
type?: string;
|
||||||
|
percent: number;
|
||||||
|
file: File;
|
||||||
|
status?: UploadResultStatus;
|
||||||
|
response?: { fileName: string; ossId: string; url: string } | Recordable<any>;
|
||||||
|
uuid: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface Wrapper {
|
||||||
|
record: FileItem;
|
||||||
|
uidKey: string;
|
||||||
|
valueKey: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface BaseFileItem {
|
||||||
|
uid: number | string;
|
||||||
|
url: string;
|
||||||
|
name?: string;
|
||||||
|
}
|
||||||
|
export interface PreviewFileItem {
|
||||||
|
url: string;
|
||||||
|
name: string;
|
||||||
|
type: string;
|
||||||
|
}
|
60
apps/web-antd/src/components/upload/src/use-upload.ts
Normal file
@ -0,0 +1,60 @@
|
|||||||
|
import { computed, unref } from 'vue';
|
||||||
|
import type { Ref } from 'vue';
|
||||||
|
|
||||||
|
import { $t } from '@vben/locales';
|
||||||
|
|
||||||
|
export function useUploadType({
|
||||||
|
acceptRef,
|
||||||
|
helpTextRef,
|
||||||
|
maxNumberRef,
|
||||||
|
maxSizeRef,
|
||||||
|
}: {
|
||||||
|
acceptRef: Ref<string[]>;
|
||||||
|
helpTextRef: Ref<string>;
|
||||||
|
maxNumberRef: Ref<number>;
|
||||||
|
maxSizeRef: Ref<number>;
|
||||||
|
}) {
|
||||||
|
// 文件类型限制
|
||||||
|
const getAccept = computed(() => {
|
||||||
|
const accept = unref(acceptRef);
|
||||||
|
if (accept && accept.length > 0) {
|
||||||
|
return accept;
|
||||||
|
}
|
||||||
|
return [];
|
||||||
|
});
|
||||||
|
const getStringAccept = computed(() => {
|
||||||
|
return unref(getAccept)
|
||||||
|
.map((item) => {
|
||||||
|
return item.indexOf('/') > 0 || item.startsWith('.')
|
||||||
|
? item
|
||||||
|
: `.${item}`;
|
||||||
|
})
|
||||||
|
.join(',');
|
||||||
|
});
|
||||||
|
|
||||||
|
// 支持jpg、jpeg、png格式,不超过2M,最多可选择10张图片,。
|
||||||
|
const getHelpText = computed(() => {
|
||||||
|
const helpText = unref(helpTextRef);
|
||||||
|
if (helpText) {
|
||||||
|
return helpText;
|
||||||
|
}
|
||||||
|
const helpTexts: string[] = [];
|
||||||
|
|
||||||
|
const accept = unref(acceptRef);
|
||||||
|
if (accept.length > 0) {
|
||||||
|
helpTexts.push($t('component.upload.accept', [accept.join(',')]));
|
||||||
|
}
|
||||||
|
|
||||||
|
const maxSize = unref(maxSizeRef);
|
||||||
|
if (maxSize) {
|
||||||
|
helpTexts.push($t('component.upload.maxSize', [maxSize]));
|
||||||
|
}
|
||||||
|
|
||||||
|
const maxNumber = unref(maxNumberRef);
|
||||||
|
if (maxNumber && maxNumber !== Infinity) {
|
||||||
|
helpTexts.push($t('component.upload.maxNumber', [maxNumber]));
|
||||||
|
}
|
||||||
|
return helpTexts.join(',');
|
||||||
|
});
|
||||||
|
return { getAccept, getStringAccept, getHelpText };
|
||||||
|
}
|
@ -29,6 +29,35 @@
|
|||||||
"notice": {
|
"notice": {
|
||||||
"title": "Notice",
|
"title": "Notice",
|
||||||
"received": "You have received a new message"
|
"received": "You have received a new message"
|
||||||
|
},
|
||||||
|
"upload": {
|
||||||
|
"save": "Save",
|
||||||
|
"upload": "Upload",
|
||||||
|
"imgUpload": "ImageUpload",
|
||||||
|
"uploaded": "Uploaded",
|
||||||
|
"operating": "Operating",
|
||||||
|
"del": "Delete",
|
||||||
|
"download": "download",
|
||||||
|
"saveWarn": "Please wait for the file to upload and save!",
|
||||||
|
"saveError": "There is no file successfully uploaded and cannot be saved!",
|
||||||
|
"preview": "Preview",
|
||||||
|
"choose": "Select the file",
|
||||||
|
"accept": "Support {0} format",
|
||||||
|
"acceptUpload": "Only upload files in {0} format",
|
||||||
|
"maxSize": "A single file does not exceed {0}MB ",
|
||||||
|
"maxSizeMultiple": "Only upload files up to {0}MB!",
|
||||||
|
"maxNumber": "Only upload up to {0} files",
|
||||||
|
"legend": "Legend",
|
||||||
|
"fileName": "File name",
|
||||||
|
"fileSize": "File size",
|
||||||
|
"fileStatue": "File status",
|
||||||
|
"pending": "Pending",
|
||||||
|
"startUpload": "Start upload",
|
||||||
|
"uploadSuccess": "Upload successfully",
|
||||||
|
"uploadError": "Upload failed",
|
||||||
|
"uploading": "Uploading",
|
||||||
|
"uploadWait": "Please wait for the file upload to finish",
|
||||||
|
"reUploadFailed": "Re-upload failed files"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"pages": {
|
"pages": {
|
||||||
|
@ -29,6 +29,35 @@
|
|||||||
"notice": {
|
"notice": {
|
||||||
"title": "消息",
|
"title": "消息",
|
||||||
"received": "收到新消息"
|
"received": "收到新消息"
|
||||||
|
},
|
||||||
|
"upload": {
|
||||||
|
"save": "保存",
|
||||||
|
"upload": "上传",
|
||||||
|
"imgUpload": "图片上传",
|
||||||
|
"uploaded": "已上传",
|
||||||
|
"operating": "操作",
|
||||||
|
"del": "删除",
|
||||||
|
"download": "下载",
|
||||||
|
"saveWarn": "请等待文件上传后,保存!",
|
||||||
|
"saveError": "没有上传成功的文件,无法保存!",
|
||||||
|
"preview": "预览",
|
||||||
|
"choose": "选择文件",
|
||||||
|
"accept": "支持{0}格式",
|
||||||
|
"acceptUpload": "只能上传{0}格式文件",
|
||||||
|
"maxSize": "单个文件不超过{0}MB",
|
||||||
|
"maxSizeMultiple": "只能上传不超过{0}MB的文件!",
|
||||||
|
"maxNumber": "最多只能上传{0}个文件",
|
||||||
|
"legend": "略缩图",
|
||||||
|
"fileName": "文件名",
|
||||||
|
"fileSize": "文件大小",
|
||||||
|
"fileStatue": "状态",
|
||||||
|
"pending": "待上传",
|
||||||
|
"startUpload": "开始上传",
|
||||||
|
"uploadSuccess": "上传成功",
|
||||||
|
"uploadError": "上传失败",
|
||||||
|
"uploading": "上传中",
|
||||||
|
"uploadWait": "请等待文件上传结束后操作",
|
||||||
|
"reUploadFailed": "重新上传失败文件"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"pages": {
|
"pages": {
|
||||||
|
@ -3,7 +3,7 @@ import type { UserProfile } from '#/api/system/profile/model';
|
|||||||
|
|
||||||
import { computed } from 'vue';
|
import { computed } from 'vue';
|
||||||
|
|
||||||
import { usePreferences } from '@vben/preferences';
|
import { preferences, usePreferences } from '@vben/preferences';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
Card,
|
Card,
|
||||||
@ -24,9 +24,7 @@ defineEmits<{
|
|||||||
}>();
|
}>();
|
||||||
|
|
||||||
const avatar = computed(
|
const avatar = computed(
|
||||||
() =>
|
() => props.profile?.user.avatar ?? preferences.app.defaultAvatar,
|
||||||
props.profile?.user.avatar ??
|
|
||||||
'https://cube.elemecdn.com/3/7c/3ea6beec64369c2642b92c6726f1epng.png',
|
|
||||||
);
|
);
|
||||||
|
|
||||||
const { isDark } = usePreferences();
|
const { isDark } = usePreferences();
|
||||||
|
60
apps/web-antd/src/views/demo/demo/api/index.ts
Executable file
@ -0,0 +1,60 @@
|
|||||||
|
import type { DemoForm, DemoQuery, DemoVO } from './model';
|
||||||
|
|
||||||
|
import type { ID, IDS, PageResult } from '#/api/common';
|
||||||
|
|
||||||
|
import { commonExport } from '#/api/helper';
|
||||||
|
import { requestClient } from '#/api/request';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 查询测试单表列表
|
||||||
|
* @param params
|
||||||
|
* @returns 测试单表列表
|
||||||
|
*/
|
||||||
|
export function demoList(params?: DemoQuery) {
|
||||||
|
return requestClient.get<PageResult<DemoVO>>('/demo/demo/list', { params });
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 导出测试单表列表
|
||||||
|
* @param params
|
||||||
|
* @returns 测试单表列表
|
||||||
|
*/
|
||||||
|
export function demoExport(params?: DemoQuery) {
|
||||||
|
return commonExport('/demo/demo/export', params ?? {});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 查询测试单表详情
|
||||||
|
* @param id id
|
||||||
|
* @returns 测试单表详情
|
||||||
|
*/
|
||||||
|
export function demoInfo(id: ID) {
|
||||||
|
return requestClient.get<DemoVO>(`/demo/demo/${id}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 新增测试单表
|
||||||
|
* @param data
|
||||||
|
* @returns void
|
||||||
|
*/
|
||||||
|
export function demoAdd(data: DemoForm) {
|
||||||
|
return requestClient.postWithMsg<void>('/demo/demo', data);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 更新测试单表
|
||||||
|
* @param data
|
||||||
|
* @returns void
|
||||||
|
*/
|
||||||
|
export function demoUpdate(data: DemoForm) {
|
||||||
|
return requestClient.putWithMsg<void>('/demo/demo', data);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 删除测试单表
|
||||||
|
* @param id id
|
||||||
|
* @returns void
|
||||||
|
*/
|
||||||
|
export function demoRemove(id: ID | IDS) {
|
||||||
|
return requestClient.deleteWithMsg<void>(`/demo/demo/${id}`);
|
||||||
|
}
|
82
apps/web-antd/src/views/demo/demo/api/model.d.ts
vendored
Executable file
@ -0,0 +1,82 @@
|
|||||||
|
import type { BaseEntity, PageQuery } from '#/api/common';
|
||||||
|
|
||||||
|
export interface DemoVO {
|
||||||
|
/**
|
||||||
|
* 主键
|
||||||
|
*/
|
||||||
|
id: number | string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 排序号
|
||||||
|
*/
|
||||||
|
orderNum: number;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* key键
|
||||||
|
*/
|
||||||
|
testKey: string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 值
|
||||||
|
*/
|
||||||
|
value: string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 版本
|
||||||
|
*/
|
||||||
|
version: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface DemoForm extends BaseEntity {
|
||||||
|
/**
|
||||||
|
* 主键
|
||||||
|
*/
|
||||||
|
id?: number | string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 排序号
|
||||||
|
*/
|
||||||
|
orderNum?: number;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* key键
|
||||||
|
*/
|
||||||
|
testKey?: string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 值
|
||||||
|
*/
|
||||||
|
value?: string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 版本
|
||||||
|
*/
|
||||||
|
version?: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface DemoQuery extends PageQuery {
|
||||||
|
/**
|
||||||
|
* 排序号
|
||||||
|
*/
|
||||||
|
orderNum?: number;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* key键
|
||||||
|
*/
|
||||||
|
testKey?: string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 值
|
||||||
|
*/
|
||||||
|
value?: string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 版本
|
||||||
|
*/
|
||||||
|
version?: number;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 日期范围参数
|
||||||
|
*/
|
||||||
|
params?: any;
|
||||||
|
}
|
91
apps/web-antd/src/views/demo/demo/data.ts
Executable file
@ -0,0 +1,91 @@
|
|||||||
|
import type { FormSchemaGetter, VxeGridProps } from '#/adapter';
|
||||||
|
|
||||||
|
export const querySchema: FormSchemaGetter = () => [
|
||||||
|
{
|
||||||
|
component: 'Input',
|
||||||
|
fieldName: 'orderNum',
|
||||||
|
label: '排序号',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
component: 'Input',
|
||||||
|
fieldName: 'testKey',
|
||||||
|
label: 'key键',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
component: 'Input',
|
||||||
|
fieldName: 'value',
|
||||||
|
label: '值',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
component: 'Input',
|
||||||
|
fieldName: 'version',
|
||||||
|
label: '版本',
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
export const columns: VxeGridProps['columns'] = [
|
||||||
|
{ type: 'checkbox', width: 60 },
|
||||||
|
{
|
||||||
|
title: '主键',
|
||||||
|
field: 'id',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '排序号',
|
||||||
|
field: 'orderNum',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'key键',
|
||||||
|
field: 'testKey',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '值',
|
||||||
|
field: 'value',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '版本',
|
||||||
|
field: 'version',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
field: 'action',
|
||||||
|
fixed: 'right',
|
||||||
|
slots: { default: 'action' },
|
||||||
|
title: '操作',
|
||||||
|
width: 180,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
export const modalSchema: FormSchemaGetter = () => [
|
||||||
|
{
|
||||||
|
label: '主键',
|
||||||
|
fieldName: 'id',
|
||||||
|
component: 'Input',
|
||||||
|
dependencies: {
|
||||||
|
show: () => false,
|
||||||
|
triggerFields: [''],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: '排序号',
|
||||||
|
fieldName: 'orderNum',
|
||||||
|
component: 'Input',
|
||||||
|
rules: 'required',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'key键',
|
||||||
|
fieldName: 'testKey',
|
||||||
|
component: 'Input',
|
||||||
|
rules: 'required',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: '值',
|
||||||
|
fieldName: 'value',
|
||||||
|
component: 'Input',
|
||||||
|
rules: 'required',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: '版本',
|
||||||
|
fieldName: 'version',
|
||||||
|
component: 'Input',
|
||||||
|
rules: 'required',
|
||||||
|
},
|
||||||
|
];
|
87
apps/web-antd/src/views/demo/demo/demo-modal.vue
Executable file
@ -0,0 +1,87 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import { computed, ref } from 'vue';
|
||||||
|
|
||||||
|
import { useVbenModal } from '@vben/common-ui';
|
||||||
|
import { $t } from '@vben/locales';
|
||||||
|
import { cloneDeep } from '@vben/utils';
|
||||||
|
|
||||||
|
import { useVbenForm } from '#/adapter';
|
||||||
|
|
||||||
|
import { demoAdd, demoInfo, demoUpdate } from './api';
|
||||||
|
import { modalSchema } from './data';
|
||||||
|
|
||||||
|
const emit = defineEmits<{ reload: [] }>();
|
||||||
|
|
||||||
|
const isUpdate = ref(false);
|
||||||
|
const title = computed(() => {
|
||||||
|
return isUpdate.value ? $t('pages.common.edit') : $t('pages.common.add');
|
||||||
|
});
|
||||||
|
|
||||||
|
const [BasicForm, formApi] = useVbenForm({
|
||||||
|
commonConfig: {
|
||||||
|
// 默认占满两列
|
||||||
|
formItemClass: 'col-span-2',
|
||||||
|
// 默认label宽度 px
|
||||||
|
labelWidth: 80,
|
||||||
|
// 通用配置项 会影响到所有表单项
|
||||||
|
componentProps: {
|
||||||
|
class: 'w-full',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
schema: modalSchema(),
|
||||||
|
showDefaultActions: false,
|
||||||
|
wrapperClass: 'grid-cols-2',
|
||||||
|
});
|
||||||
|
|
||||||
|
const [BasicModal, modalApi] = useVbenModal({
|
||||||
|
fullscreenButton: false,
|
||||||
|
onCancel: handleCancel,
|
||||||
|
onConfirm: handleConfirm,
|
||||||
|
onOpenChange: async (isOpen) => {
|
||||||
|
if (!isOpen) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
modalApi.modalLoading(true);
|
||||||
|
|
||||||
|
const { id } = modalApi.getData() as { id?: number | string };
|
||||||
|
isUpdate.value = !!id;
|
||||||
|
|
||||||
|
if (isUpdate.value && id) {
|
||||||
|
const record = await demoInfo(id);
|
||||||
|
await formApi.setValues(record);
|
||||||
|
}
|
||||||
|
|
||||||
|
modalApi.modalLoading(false);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
async function handleConfirm() {
|
||||||
|
try {
|
||||||
|
modalApi.modalLoading(true);
|
||||||
|
const { valid } = await formApi.validate();
|
||||||
|
if (!valid) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// getValues获取为一个readonly的对象 需要修改必须先深拷贝一次
|
||||||
|
const data = cloneDeep(await formApi.getValues());
|
||||||
|
await (isUpdate.value ? demoUpdate(data) : demoAdd(data));
|
||||||
|
emit('reload');
|
||||||
|
await handleCancel();
|
||||||
|
} catch (error) {
|
||||||
|
console.error(error);
|
||||||
|
} finally {
|
||||||
|
modalApi.modalLoading(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function handleCancel() {
|
||||||
|
modalApi.close();
|
||||||
|
await formApi.resetForm();
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<BasicModal :close-on-click-modal="false" :title="title" class="w-[550px]">
|
||||||
|
<BasicForm />
|
||||||
|
</BasicModal>
|
||||||
|
</template>
|
@ -1,9 +1,183 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import CommonSkeleton from '#/views/common';
|
import type { Recordable } from '@vben/types';
|
||||||
|
|
||||||
|
import { ref } from 'vue';
|
||||||
|
|
||||||
|
import { Page, useVbenModal, type VbenFormProps } from '@vben/common-ui';
|
||||||
|
|
||||||
|
import { Modal, Popconfirm, Space } from 'ant-design-vue';
|
||||||
|
import dayjs from 'dayjs';
|
||||||
|
|
||||||
|
import { useVbenVxeGrid, type VxeGridProps } from '#/adapter';
|
||||||
|
import { downloadExcel } from '#/utils/file/download';
|
||||||
|
|
||||||
|
import { demoExport, demoList, demoRemove } from './api';
|
||||||
|
import { columns, querySchema } from './data';
|
||||||
|
import demoModal from './demo-modal.vue';
|
||||||
|
|
||||||
|
const formOptions: VbenFormProps = {
|
||||||
|
commonConfig: {
|
||||||
|
labelWidth: 80,
|
||||||
|
},
|
||||||
|
schema: querySchema(),
|
||||||
|
wrapperClass: 'grid-cols-1 md:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4',
|
||||||
|
};
|
||||||
|
|
||||||
|
const gridOptions: VxeGridProps = {
|
||||||
|
checkboxConfig: {
|
||||||
|
// 高亮
|
||||||
|
highlight: true,
|
||||||
|
// 翻页时保留选中状态
|
||||||
|
reserve: true,
|
||||||
|
// 点击行选中
|
||||||
|
// trigger: 'row',
|
||||||
|
},
|
||||||
|
columns,
|
||||||
|
height: 'auto',
|
||||||
|
keepSource: true,
|
||||||
|
pagerConfig: {},
|
||||||
|
proxyConfig: {
|
||||||
|
ajax: {
|
||||||
|
query: async ({ page }, formValues = {}) => {
|
||||||
|
// 区间选择器处理
|
||||||
|
if (formValues?.createTime) {
|
||||||
|
formValues.params = {
|
||||||
|
beginTime: dayjs(formValues.createTime[0]).format(
|
||||||
|
'YYYY-MM-DD 00:00:00',
|
||||||
|
),
|
||||||
|
endTime: dayjs(formValues.createTime[1]).format(
|
||||||
|
'YYYY-MM-DD 23:59:59',
|
||||||
|
),
|
||||||
|
};
|
||||||
|
Reflect.deleteProperty(formValues, 'createTime');
|
||||||
|
} else {
|
||||||
|
Reflect.deleteProperty(formValues, 'params');
|
||||||
|
}
|
||||||
|
|
||||||
|
return await demoList({
|
||||||
|
pageNum: page.currentPage,
|
||||||
|
pageSize: page.pageSize,
|
||||||
|
...formValues,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
rowConfig: {
|
||||||
|
isHover: true,
|
||||||
|
keyField: 'id',
|
||||||
|
},
|
||||||
|
round: true,
|
||||||
|
align: 'center',
|
||||||
|
showOverflow: true,
|
||||||
|
};
|
||||||
|
|
||||||
|
const checked = ref(false);
|
||||||
|
const [BasicTable, tableApi] = useVbenVxeGrid({
|
||||||
|
formOptions,
|
||||||
|
gridOptions,
|
||||||
|
gridEvents: {
|
||||||
|
checkboxChange: (e: any) => {
|
||||||
|
checked.value = e.records.length > 0;
|
||||||
|
},
|
||||||
|
checkboxAll: (e: any) => {
|
||||||
|
checked.value = e.records.length > 0;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const [DemoModal, modalApi] = useVbenModal({
|
||||||
|
connectedComponent: demoModal,
|
||||||
|
});
|
||||||
|
|
||||||
|
function handleAdd() {
|
||||||
|
modalApi.setData({});
|
||||||
|
modalApi.open();
|
||||||
|
}
|
||||||
|
|
||||||
|
async function handleEdit(record: Recordable<any>) {
|
||||||
|
modalApi.setData({ id: record.id });
|
||||||
|
modalApi.open();
|
||||||
|
}
|
||||||
|
|
||||||
|
async function handleDelete(row: Recordable<any>) {
|
||||||
|
await demoRemove(row.id);
|
||||||
|
await tableApi.query();
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleMultiDelete() {
|
||||||
|
const rows = tableApi.grid.getCheckboxRecords();
|
||||||
|
const ids = rows.map((row: any) => row.id);
|
||||||
|
Modal.confirm({
|
||||||
|
title: '提示',
|
||||||
|
okType: 'danger',
|
||||||
|
content: `确认删除选中的${ids.length}条记录吗?`,
|
||||||
|
onOk: async () => {
|
||||||
|
await demoRemove(ids);
|
||||||
|
await tableApi.query();
|
||||||
|
checked.value = false;
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div>
|
<Page :auto-content-height="true">
|
||||||
<CommonSkeleton />
|
<BasicTable>
|
||||||
</div>
|
<template #toolbar-actions>
|
||||||
|
<span class="pl-[7px] text-[16px]">测试单表列表</span>
|
||||||
|
</template>
|
||||||
|
<template #toolbar-tools>
|
||||||
|
<Space>
|
||||||
|
<a-button
|
||||||
|
v-access:code="['demo:demo:export']"
|
||||||
|
@click="downloadExcel(demoExport, '测试单表数据', {})"
|
||||||
|
>
|
||||||
|
{{ $t('pages.common.export') }}
|
||||||
|
</a-button>
|
||||||
|
<a-button
|
||||||
|
:disabled="!checked"
|
||||||
|
danger
|
||||||
|
type="primary"
|
||||||
|
v-access:code="['demo:demo:remove']"
|
||||||
|
@click="handleMultiDelete"
|
||||||
|
>
|
||||||
|
{{ $t('pages.common.delete') }}
|
||||||
|
</a-button>
|
||||||
|
<a-button
|
||||||
|
type="primary"
|
||||||
|
v-access:code="['demo:demo:add']"
|
||||||
|
@click="handleAdd"
|
||||||
|
>
|
||||||
|
{{ $t('pages.common.add') }}
|
||||||
|
</a-button>
|
||||||
|
</Space>
|
||||||
|
</template>
|
||||||
|
<template #action="{ row }">
|
||||||
|
<a-button
|
||||||
|
size="small"
|
||||||
|
type="link"
|
||||||
|
v-access:code="['demo:demo:edit']"
|
||||||
|
@click="handleEdit(row)"
|
||||||
|
>
|
||||||
|
{{ $t('pages.common.edit') }}
|
||||||
|
</a-button>
|
||||||
|
<Popconfirm
|
||||||
|
placement="left"
|
||||||
|
title="确认删除?"
|
||||||
|
@confirm="handleDelete(row)"
|
||||||
|
>
|
||||||
|
<a-button
|
||||||
|
danger
|
||||||
|
size="small"
|
||||||
|
type="link"
|
||||||
|
v-access:code="['demo:demo:remove']"
|
||||||
|
@click.stop=""
|
||||||
|
>
|
||||||
|
{{ $t('pages.common.delete') }}
|
||||||
|
</a-button>
|
||||||
|
</Popconfirm>
|
||||||
|
</template>
|
||||||
|
</BasicTable>
|
||||||
|
<DemoModal @reload="tableApi.query()" />
|
||||||
|
</Page>
|
||||||
</template>
|
</template>
|
||||||
|
50
apps/web-antd/src/views/demo/tree/api/index.ts
Executable file
@ -0,0 +1,50 @@
|
|||||||
|
import type { TreeForm, TreeQuery, TreeVO } from './model';
|
||||||
|
|
||||||
|
import type { ID, IDS } from '#/api/common';
|
||||||
|
|
||||||
|
import { requestClient } from '#/api/request';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 查询测试树列表
|
||||||
|
* @param params
|
||||||
|
* @returns 测试树列表
|
||||||
|
*/
|
||||||
|
export function treeList(params?: TreeQuery) {
|
||||||
|
return requestClient.get<TreeVO[]>('/demo/tree/list', { params });
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 查询测试树详情
|
||||||
|
* @param id id
|
||||||
|
* @returns 测试树详情
|
||||||
|
*/
|
||||||
|
export function treeInfo(id: ID) {
|
||||||
|
return requestClient.get<TreeVO>(`/demo/tree/${id}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 新增测试树
|
||||||
|
* @param data
|
||||||
|
* @returns void
|
||||||
|
*/
|
||||||
|
export function treeAdd(data: TreeForm) {
|
||||||
|
return requestClient.postWithMsg<void>('/demo/tree', data);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 更新测试树
|
||||||
|
* @param data
|
||||||
|
* @returns void
|
||||||
|
*/
|
||||||
|
export function treeUpdate(data: TreeForm) {
|
||||||
|
return requestClient.putWithMsg<void>('/demo/tree', data);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 删除测试树
|
||||||
|
* @param id id
|
||||||
|
* @returns void
|
||||||
|
*/
|
||||||
|
export function treeRemove(id: ID | IDS) {
|
||||||
|
return requestClient.deleteWithMsg<void>(`/demo/tree/${id}`);
|
||||||
|
}
|
102
apps/web-antd/src/views/demo/tree/api/model.d.ts
vendored
Executable file
@ -0,0 +1,102 @@
|
|||||||
|
import type { BaseEntity } from '#/api/common';
|
||||||
|
|
||||||
|
export interface TreeVO {
|
||||||
|
/**
|
||||||
|
* 主键
|
||||||
|
*/
|
||||||
|
id: number | string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 父id
|
||||||
|
*/
|
||||||
|
parentId: number | string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 部门id
|
||||||
|
*/
|
||||||
|
deptId: number | string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 用户id
|
||||||
|
*/
|
||||||
|
userId: number | string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 值
|
||||||
|
*/
|
||||||
|
treeName: string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 版本
|
||||||
|
*/
|
||||||
|
version: number;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 子对象
|
||||||
|
*/
|
||||||
|
children: TreeVO[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface TreeForm extends BaseEntity {
|
||||||
|
/**
|
||||||
|
* 主键
|
||||||
|
*/
|
||||||
|
id?: number | string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 父id
|
||||||
|
*/
|
||||||
|
parentId?: number | string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 部门id
|
||||||
|
*/
|
||||||
|
deptId?: number | string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 用户id
|
||||||
|
*/
|
||||||
|
userId?: number | string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 值
|
||||||
|
*/
|
||||||
|
treeName?: string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 版本
|
||||||
|
*/
|
||||||
|
version?: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface TreeQuery {
|
||||||
|
/**
|
||||||
|
* 父id
|
||||||
|
*/
|
||||||
|
parentId?: number | string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 部门id
|
||||||
|
*/
|
||||||
|
deptId?: number | string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 用户id
|
||||||
|
*/
|
||||||
|
userId?: number | string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 值
|
||||||
|
*/
|
||||||
|
treeName?: string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 版本
|
||||||
|
*/
|
||||||
|
version?: number;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 日期范围参数
|
||||||
|
*/
|
||||||
|
params?: any;
|
||||||
|
}
|
106
apps/web-antd/src/views/demo/tree/data.ts
Executable file
@ -0,0 +1,106 @@
|
|||||||
|
import type { FormSchemaGetter, VxeGridProps } from '#/adapter';
|
||||||
|
|
||||||
|
export const querySchema: FormSchemaGetter = () => [
|
||||||
|
{
|
||||||
|
component: 'Input',
|
||||||
|
fieldName: 'parentId',
|
||||||
|
label: '父id',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
component: 'Input',
|
||||||
|
fieldName: 'deptId',
|
||||||
|
label: '部门id',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
component: 'Input',
|
||||||
|
fieldName: 'userId',
|
||||||
|
label: '用户id',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
component: 'Input',
|
||||||
|
fieldName: 'treeName',
|
||||||
|
label: '值',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
component: 'Input',
|
||||||
|
fieldName: 'version',
|
||||||
|
label: '版本',
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
export const columns: VxeGridProps['columns'] = [
|
||||||
|
{
|
||||||
|
title: '主键',
|
||||||
|
field: 'id',
|
||||||
|
treeNode: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '父id',
|
||||||
|
field: 'parentId',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '部门id',
|
||||||
|
field: 'deptId',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '用户id',
|
||||||
|
field: 'userId',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '值',
|
||||||
|
field: 'treeName',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '版本',
|
||||||
|
field: 'version',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
field: 'action',
|
||||||
|
fixed: 'right',
|
||||||
|
slots: { default: 'action' },
|
||||||
|
title: '操作',
|
||||||
|
width: 180,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
export const modalSchema: FormSchemaGetter = () => [
|
||||||
|
{
|
||||||
|
label: '主键',
|
||||||
|
fieldName: 'id',
|
||||||
|
component: 'Input',
|
||||||
|
dependencies: {
|
||||||
|
show: () => false,
|
||||||
|
triggerFields: [''],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: '父id',
|
||||||
|
fieldName: 'parentId',
|
||||||
|
component: 'TreeSelect',
|
||||||
|
rules: 'required',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: '部门id',
|
||||||
|
fieldName: 'deptId',
|
||||||
|
component: 'Input',
|
||||||
|
rules: 'required',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: '用户id',
|
||||||
|
fieldName: 'userId',
|
||||||
|
component: 'Input',
|
||||||
|
rules: 'required',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: '值',
|
||||||
|
fieldName: 'treeName',
|
||||||
|
component: 'Input',
|
||||||
|
rules: 'required',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: '版本',
|
||||||
|
fieldName: 'version',
|
||||||
|
component: 'Input',
|
||||||
|
rules: 'required',
|
||||||
|
},
|
||||||
|
];
|
@ -1,9 +1,147 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import CommonSkeleton from '#/views/common';
|
import type { Recordable } from '@vben/types';
|
||||||
|
|
||||||
|
import { nextTick } from 'vue';
|
||||||
|
|
||||||
|
import { Page, useVbenModal, type VbenFormProps } from '@vben/common-ui';
|
||||||
|
import { listToTree } from '@vben/utils';
|
||||||
|
|
||||||
|
import { Popconfirm, Space } from 'ant-design-vue';
|
||||||
|
|
||||||
|
import { useVbenVxeGrid, type VxeGridProps } from '#/adapter';
|
||||||
|
|
||||||
|
import { treeList, treeRemove } from './api';
|
||||||
|
import { columns, querySchema } from './data';
|
||||||
|
import treeModal from './tree-modal.vue';
|
||||||
|
|
||||||
|
const formOptions: VbenFormProps = {
|
||||||
|
commonConfig: {
|
||||||
|
labelWidth: 80,
|
||||||
|
},
|
||||||
|
schema: querySchema(),
|
||||||
|
wrapperClass: 'grid-cols-1 md:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4',
|
||||||
|
};
|
||||||
|
|
||||||
|
const gridOptions: VxeGridProps = {
|
||||||
|
columns,
|
||||||
|
height: 'auto',
|
||||||
|
keepSource: true,
|
||||||
|
pagerConfig: {
|
||||||
|
enabled: false,
|
||||||
|
},
|
||||||
|
proxyConfig: {
|
||||||
|
ajax: {
|
||||||
|
query: async (_, formValues = {}) => {
|
||||||
|
const resp = await treeList({
|
||||||
|
...formValues,
|
||||||
|
});
|
||||||
|
const treeData = listToTree(resp, {
|
||||||
|
id: 'id',
|
||||||
|
pid: 'parentId',
|
||||||
|
children: 'children',
|
||||||
|
});
|
||||||
|
return { rows: treeData };
|
||||||
|
},
|
||||||
|
// 默认请求接口后展开全部 不需要可以删除这段
|
||||||
|
querySuccess: () => {
|
||||||
|
nextTick(() => {
|
||||||
|
expandAll();
|
||||||
|
});
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
rowConfig: {
|
||||||
|
isHover: true,
|
||||||
|
keyField: 'id',
|
||||||
|
},
|
||||||
|
round: true,
|
||||||
|
align: 'center',
|
||||||
|
showOverflow: true,
|
||||||
|
treeConfig: {
|
||||||
|
parentField: 'parentId',
|
||||||
|
rowField: 'id',
|
||||||
|
transform: false,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const [BasicTable, tableApi] = useVbenVxeGrid({ formOptions, gridOptions });
|
||||||
|
const [TreeModal, modalApi] = useVbenModal({
|
||||||
|
connectedComponent: treeModal,
|
||||||
|
});
|
||||||
|
|
||||||
|
function handleAdd() {
|
||||||
|
modalApi.setData({ update: false });
|
||||||
|
modalApi.open();
|
||||||
|
}
|
||||||
|
|
||||||
|
async function handleEdit(row: Recordable<any>) {
|
||||||
|
modalApi.setData({ id: row.id, update: true });
|
||||||
|
modalApi.open();
|
||||||
|
}
|
||||||
|
|
||||||
|
async function handleDelete(row: Recordable<any>) {
|
||||||
|
await treeRemove(row.id);
|
||||||
|
await tableApi.query();
|
||||||
|
}
|
||||||
|
|
||||||
|
function expandAll() {
|
||||||
|
tableApi.grid?.setAllTreeExpand(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
function collapseAll() {
|
||||||
|
tableApi.grid?.setAllTreeExpand(false);
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div>
|
<Page :auto-content-height="true">
|
||||||
<CommonSkeleton />
|
<BasicTable>
|
||||||
</div>
|
<template #toolbar-actions>
|
||||||
|
<span class="pl-[7px] text-[16px]">测试树列表</span>
|
||||||
|
</template>
|
||||||
|
<template #toolbar-tools>
|
||||||
|
<Space>
|
||||||
|
<a-button @click="collapseAll">
|
||||||
|
{{ $t('pages.common.collapse') }}
|
||||||
|
</a-button>
|
||||||
|
<a-button @click="expandAll">
|
||||||
|
{{ $t('pages.common.expand') }}
|
||||||
|
</a-button>
|
||||||
|
<a-button
|
||||||
|
type="primary"
|
||||||
|
v-access:code="['demo:tree:add']"
|
||||||
|
@click="handleAdd"
|
||||||
|
>
|
||||||
|
{{ $t('pages.common.add') }}
|
||||||
|
</a-button>
|
||||||
|
</Space>
|
||||||
|
</template>
|
||||||
|
<template #action="{ row }">
|
||||||
|
<a-button
|
||||||
|
size="small"
|
||||||
|
type="link"
|
||||||
|
v-access:code="['demo:tree:edit']"
|
||||||
|
@click="handleEdit(row)"
|
||||||
|
>
|
||||||
|
{{ $t('pages.common.edit') }}
|
||||||
|
</a-button>
|
||||||
|
<Popconfirm
|
||||||
|
placement="left"
|
||||||
|
title="确认删除?"
|
||||||
|
@confirm="handleDelete(row)"
|
||||||
|
>
|
||||||
|
<a-button
|
||||||
|
danger
|
||||||
|
size="small"
|
||||||
|
type="link"
|
||||||
|
v-access:code="['demo:tree:remove']"
|
||||||
|
@click.stop=""
|
||||||
|
>
|
||||||
|
{{ $t('pages.common.delete') }}
|
||||||
|
</a-button>
|
||||||
|
</Popconfirm>
|
||||||
|
</template>
|
||||||
|
</BasicTable>
|
||||||
|
<TreeModal @reload="tableApi.query()" />
|
||||||
|
</Page>
|
||||||
</template>
|
</template>
|
||||||
|
104
apps/web-antd/src/views/demo/tree/tree-modal.vue
Executable file
@ -0,0 +1,104 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import { computed, ref } from 'vue';
|
||||||
|
|
||||||
|
import { useVbenModal } from '@vben/common-ui';
|
||||||
|
import { $t } from '@vben/locales';
|
||||||
|
import { cloneDeep, listToTree } from '@vben/utils';
|
||||||
|
|
||||||
|
import { useVbenForm } from '#/adapter';
|
||||||
|
|
||||||
|
import { treeAdd, treeInfo, treeList, treeUpdate } from './api';
|
||||||
|
import { modalSchema } from './data';
|
||||||
|
|
||||||
|
const emit = defineEmits<{ reload: [] }>();
|
||||||
|
|
||||||
|
const isUpdate = ref(false);
|
||||||
|
const title = computed(() => {
|
||||||
|
return isUpdate.value ? $t('pages.common.edit') : $t('pages.common.add');
|
||||||
|
});
|
||||||
|
|
||||||
|
const [BasicForm, formApi] = useVbenForm({
|
||||||
|
commonConfig: {
|
||||||
|
// 默认占满两列
|
||||||
|
formItemClass: 'col-span-2',
|
||||||
|
// 默认label宽度 px
|
||||||
|
labelWidth: 80,
|
||||||
|
// 通用配置项 会影响到所有表单项
|
||||||
|
componentProps: {
|
||||||
|
class: 'w-full',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
schema: modalSchema(),
|
||||||
|
showDefaultActions: false,
|
||||||
|
wrapperClass: 'grid-cols-2',
|
||||||
|
});
|
||||||
|
|
||||||
|
async function setupTreeSelect() {
|
||||||
|
const listData = await treeList();
|
||||||
|
const treeData = listToTree(listData, { id: 'id', pid: 'parentId' });
|
||||||
|
formApi.updateSchema([
|
||||||
|
{
|
||||||
|
fieldName: 'parentId',
|
||||||
|
componentProps: {
|
||||||
|
treeData,
|
||||||
|
treeLine: { showLeafIcon: false },
|
||||||
|
fieldNames: { label: 'treeName', value: 'id' },
|
||||||
|
treeDefaultExpandAll: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
const [BasicModal, modalApi] = useVbenModal({
|
||||||
|
fullscreenButton: false,
|
||||||
|
onCancel: handleCancel,
|
||||||
|
onConfirm: handleConfirm,
|
||||||
|
onOpenChange: async (isOpen) => {
|
||||||
|
if (!isOpen) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
modalApi.modalLoading(true);
|
||||||
|
|
||||||
|
const { id } = modalApi.getData() as { id?: number | string };
|
||||||
|
isUpdate.value = !!id;
|
||||||
|
|
||||||
|
if (isUpdate.value && id) {
|
||||||
|
const record = await treeInfo(id);
|
||||||
|
await formApi.setValues(record);
|
||||||
|
}
|
||||||
|
await setupTreeSelect();
|
||||||
|
|
||||||
|
modalApi.modalLoading(false);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
async function handleConfirm() {
|
||||||
|
try {
|
||||||
|
modalApi.modalLoading(true);
|
||||||
|
const { valid } = await formApi.validate();
|
||||||
|
if (!valid) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// getValues获取为一个readonly的对象 需要修改必须先深拷贝一次
|
||||||
|
const data = cloneDeep(await formApi.getValues());
|
||||||
|
await (isUpdate.value ? treeUpdate(data) : treeAdd(data));
|
||||||
|
emit('reload');
|
||||||
|
await handleCancel();
|
||||||
|
} catch (error) {
|
||||||
|
console.error(error);
|
||||||
|
} finally {
|
||||||
|
modalApi.modalLoading(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function handleCancel() {
|
||||||
|
modalApi.close();
|
||||||
|
await formApi.resetForm();
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<BasicModal :close-on-click-modal="false" :title="title" class="w-[550px]">
|
||||||
|
<BasicForm />
|
||||||
|
</BasicModal>
|
||||||
|
</template>
|
@ -137,7 +137,10 @@ async function handleUnlock() {
|
|||||||
</template>
|
</template>
|
||||||
<template #toolbar-tools>
|
<template #toolbar-tools>
|
||||||
<Space>
|
<Space>
|
||||||
<a-button @click="handleClear">
|
<a-button
|
||||||
|
v-access:code="['monitor:logininfor:remove']"
|
||||||
|
@click="handleClear"
|
||||||
|
>
|
||||||
{{ $t('pages.common.clear') }}
|
{{ $t('pages.common.clear') }}
|
||||||
</a-button>
|
</a-button>
|
||||||
<a-button
|
<a-button
|
||||||
@ -155,7 +158,12 @@ async function handleUnlock() {
|
|||||||
>
|
>
|
||||||
{{ $t('pages.common.delete') }}
|
{{ $t('pages.common.delete') }}
|
||||||
</a-button>
|
</a-button>
|
||||||
<a-button :disabled="!canUnlock" type="primary" @click="handleUnlock">
|
<a-button
|
||||||
|
:disabled="!canUnlock"
|
||||||
|
type="primary"
|
||||||
|
v-access:code="['monitor:logininfor:unlock']"
|
||||||
|
@click="handleUnlock"
|
||||||
|
>
|
||||||
解锁
|
解锁
|
||||||
</a-button>
|
</a-button>
|
||||||
</Space>
|
</Space>
|
||||||
|
@ -1,7 +1,11 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import type { Recordable } from '@vben/types';
|
import type { Recordable } from '@vben/types';
|
||||||
|
|
||||||
|
import { computed } from 'vue';
|
||||||
|
|
||||||
|
import { useAccess } from '@vben/access';
|
||||||
import { Page, useVbenDrawer, type VbenFormProps } from '@vben/common-ui';
|
import { Page, useVbenDrawer, type VbenFormProps } from '@vben/common-ui';
|
||||||
|
import { Fallback } from '@vben/common-ui';
|
||||||
import { listToTree } from '@vben/utils';
|
import { listToTree } from '@vben/utils';
|
||||||
|
|
||||||
import { Popconfirm, Space } from 'ant-design-vue';
|
import { Popconfirm, Space } from 'ant-design-vue';
|
||||||
@ -83,10 +87,23 @@ function expandAll() {
|
|||||||
function collapseAll() {
|
function collapseAll() {
|
||||||
tableApi.grid?.setAllTreeExpand(false);
|
tableApi.grid?.setAllTreeExpand(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 与后台逻辑相同
|
||||||
|
* 只有租户管理和超级管理能访问菜单管理
|
||||||
|
*/
|
||||||
|
const { hasAccessByRoles } = useAccess();
|
||||||
|
const isAdmin = computed(() => {
|
||||||
|
return hasAccessByRoles(['admin', 'superadmin']);
|
||||||
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 不要问为什么有两个根节点 v-if会控制只会渲染一个
|
||||||
|
*/
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<Page :auto-content-height="true">
|
<Page v-if="isAdmin" :auto-content-height="true">
|
||||||
<BasicTable>
|
<BasicTable>
|
||||||
<template #toolbar-actions>
|
<template #toolbar-actions>
|
||||||
<span class="pl-[7px] text-[16px]">菜单权限列表</span>
|
<span class="pl-[7px] text-[16px]">菜单权限列表</span>
|
||||||
@ -136,4 +153,5 @@ function collapseAll() {
|
|||||||
</BasicTable>
|
</BasicTable>
|
||||||
<MenuDrawer @reload="tableApi.query()" />
|
<MenuDrawer @reload="tableApi.query()" />
|
||||||
</Page>
|
</Page>
|
||||||
|
<Fallback v-else description="您没有菜单管理的访问权限" status="403" />
|
||||||
</template>
|
</template>
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import type { Recordable } from '@vben/types';
|
import type { Recordable } from '@vben/types';
|
||||||
|
|
||||||
import { ref } from 'vue';
|
import { computed, ref } from 'vue';
|
||||||
import { useRouter } from 'vue-router';
|
import { useRouter } from 'vue-router';
|
||||||
|
|
||||||
import { useAccess } from '@vben/access';
|
import { useAccess } from '@vben/access';
|
||||||
@ -141,7 +141,9 @@ function handleMultiDelete() {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
const { hasAccessByCodes } = useAccess();
|
const { hasAccessByCodes, hasAccessByRoles } = useAccess();
|
||||||
|
|
||||||
|
const isSuperAdmin = computed(() => hasAccessByRoles(['superadmin']));
|
||||||
|
|
||||||
const [RoleAuthModal, authModalApi] = useVbenModal({
|
const [RoleAuthModal, authModalApi] = useVbenModal({
|
||||||
connectedComponent: roleAuthModal,
|
connectedComponent: roleAuthModal,
|
||||||
@ -203,7 +205,11 @@ function handleAssignRole(record: Recordable<any>) {
|
|||||||
/>
|
/>
|
||||||
</template>
|
</template>
|
||||||
<template #action="{ row }">
|
<template #action="{ row }">
|
||||||
<template v-if="row.roleId !== 1">
|
<!-- 租户管理员不可修改admin角色 防止误操作 -->
|
||||||
|
<!-- 超级管理员可通过租户切换来操作租户管理员角色 -->
|
||||||
|
<template
|
||||||
|
v-if="!row.superAdmin && (row.roleKey !== 'admin' || isSuperAdmin)"
|
||||||
|
>
|
||||||
<a-button
|
<a-button
|
||||||
size="small"
|
size="small"
|
||||||
type="link"
|
type="link"
|
||||||
|
@ -1,10 +1,11 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import type { Recordable } from '@vben/types';
|
import type { Recordable } from '@vben/types';
|
||||||
|
|
||||||
import { ref } from 'vue';
|
import { computed, ref } from 'vue';
|
||||||
|
|
||||||
import { useAccess } from '@vben/access';
|
import { useAccess } from '@vben/access';
|
||||||
import { Page, useVbenDrawer, type VbenFormProps } from '@vben/common-ui';
|
import { Page, useVbenDrawer, type VbenFormProps } from '@vben/common-ui';
|
||||||
|
import { Fallback } from '@vben/common-ui';
|
||||||
|
|
||||||
import { Modal, Popconfirm, Space } from 'ant-design-vue';
|
import { Modal, Popconfirm, Space } from 'ant-design-vue';
|
||||||
import dayjs from 'dayjs';
|
import dayjs from 'dayjs';
|
||||||
@ -15,6 +16,7 @@ import {
|
|||||||
tenantList,
|
tenantList,
|
||||||
tenantRemove,
|
tenantRemove,
|
||||||
tenantStatusChange,
|
tenantStatusChange,
|
||||||
|
tenantSyncPackage,
|
||||||
} from '#/api/system/tenant';
|
} from '#/api/system/tenant';
|
||||||
import { TableSwitch } from '#/components/table';
|
import { TableSwitch } from '#/components/table';
|
||||||
import { useTenantStore } from '#/store/tenant';
|
import { useTenantStore } from '#/store/tenant';
|
||||||
@ -107,6 +109,12 @@ async function handleEdit(record: Recordable<any>) {
|
|||||||
drawerApi.open();
|
drawerApi.open();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function handleSync(record: Recordable<any>) {
|
||||||
|
const { tenantId, packageId } = record;
|
||||||
|
await tenantSyncPackage(tenantId, packageId);
|
||||||
|
await tableApi.query();
|
||||||
|
}
|
||||||
|
|
||||||
const tenantStore = useTenantStore();
|
const tenantStore = useTenantStore();
|
||||||
async function handleDelete(row: Recordable<any>) {
|
async function handleDelete(row: Recordable<any>) {
|
||||||
await tenantRemove(row.id);
|
await tenantRemove(row.id);
|
||||||
@ -131,11 +139,19 @@ function handleMultiDelete() {
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
const { hasAccessByCodes } = useAccess();
|
/**
|
||||||
|
* 与后台逻辑相同
|
||||||
|
* 只有超级管理员能访问租户相关
|
||||||
|
*/
|
||||||
|
const { hasAccessByCodes, hasAccessByRoles } = useAccess();
|
||||||
|
|
||||||
|
const isSuperAdmin = computed(() => {
|
||||||
|
return hasAccessByRoles(['superadmin']);
|
||||||
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<Page :auto-content-height="true">
|
<Page v-if="isSuperAdmin" :auto-content-height="true">
|
||||||
<BasicTable>
|
<BasicTable>
|
||||||
<template #toolbar-actions>
|
<template #toolbar-actions>
|
||||||
<span class="pl-[7px] text-[16px]">租户列表 </span>
|
<span class="pl-[7px] text-[16px]">租户列表 </span>
|
||||||
@ -183,6 +199,19 @@ const { hasAccessByCodes } = useAccess();
|
|||||||
>
|
>
|
||||||
{{ $t('pages.common.edit') }}
|
{{ $t('pages.common.edit') }}
|
||||||
</a-button>
|
</a-button>
|
||||||
|
<Popconfirm
|
||||||
|
:title="`确认同步[${row.companyName}]的套餐吗?`"
|
||||||
|
placement="left"
|
||||||
|
@confirm="handleSync(row)"
|
||||||
|
>
|
||||||
|
<a-button
|
||||||
|
size="small"
|
||||||
|
type="link"
|
||||||
|
v-access:code="['system:tenant:edit']"
|
||||||
|
>
|
||||||
|
{{ $t('pages.common.sync') }}
|
||||||
|
</a-button>
|
||||||
|
</Popconfirm>
|
||||||
<Popconfirm
|
<Popconfirm
|
||||||
placement="left"
|
placement="left"
|
||||||
title="确认删除?"
|
title="确认删除?"
|
||||||
@ -202,4 +231,5 @@ const { hasAccessByCodes } = useAccess();
|
|||||||
</BasicTable>
|
</BasicTable>
|
||||||
<TenantDrawer @reload="tableApi.query()" />
|
<TenantDrawer @reload="tableApi.query()" />
|
||||||
</Page>
|
</Page>
|
||||||
|
<Fallback v-else description="您没有租户的访问权限" status="403" />
|
||||||
</template>
|
</template>
|
||||||
|
@ -1,10 +1,11 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import type { Recordable } from '@vben/types';
|
import type { Recordable } from '@vben/types';
|
||||||
|
|
||||||
import { ref } from 'vue';
|
import { computed, ref } from 'vue';
|
||||||
|
|
||||||
import { useAccess } from '@vben/access';
|
import { useAccess } from '@vben/access';
|
||||||
import { Page, useVbenDrawer, type VbenFormProps } from '@vben/common-ui';
|
import { Page, useVbenDrawer, type VbenFormProps } from '@vben/common-ui';
|
||||||
|
import { Fallback } from '@vben/common-ui';
|
||||||
|
|
||||||
import { Modal, Popconfirm, Space } from 'ant-design-vue';
|
import { Modal, Popconfirm, Space } from 'ant-design-vue';
|
||||||
import dayjs from 'dayjs';
|
import dayjs from 'dayjs';
|
||||||
@ -126,11 +127,19 @@ function handleMultiDelete() {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
const { hasAccessByCodes } = useAccess();
|
/**
|
||||||
|
* 与后台逻辑相同
|
||||||
|
* 只有超级管理员能访问租户相关
|
||||||
|
*/
|
||||||
|
const { hasAccessByCodes, hasAccessByRoles } = useAccess();
|
||||||
|
|
||||||
|
const isSuperAdmin = computed(() => {
|
||||||
|
return hasAccessByRoles(['superadmin']);
|
||||||
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<Page :auto-content-height="true">
|
<Page v-if="isSuperAdmin" :auto-content-height="true">
|
||||||
<BasicTable>
|
<BasicTable>
|
||||||
<template #toolbar-actions>
|
<template #toolbar-actions>
|
||||||
<span class="pl-[7px] text-[16px]">租户套餐列表</span>
|
<span class="pl-[7px] text-[16px]">租户套餐列表</span>
|
||||||
@ -197,4 +206,5 @@ const { hasAccessByCodes } = useAccess();
|
|||||||
</BasicTable>
|
</BasicTable>
|
||||||
<TenantPackageDrawer @reload="tableApi.query()" />
|
<TenantPackageDrawer @reload="tableApi.query()" />
|
||||||
</Page>
|
</Page>
|
||||||
|
<Fallback v-else description="您没有租户的访问权限" status="403" />
|
||||||
</template>
|
</template>
|
||||||
|
@ -10,6 +10,7 @@ import {
|
|||||||
type VbenFormProps,
|
type VbenFormProps,
|
||||||
} from '@vben/common-ui';
|
} from '@vben/common-ui';
|
||||||
import { $t } from '@vben/locales';
|
import { $t } from '@vben/locales';
|
||||||
|
import { preferences } from '@vben/preferences';
|
||||||
import { getPopupContainer } from '@vben/utils';
|
import { getPopupContainer } from '@vben/utils';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
@ -122,7 +123,6 @@ const gridOptions: VxeGridProps = {
|
|||||||
align: 'center',
|
align: 'center',
|
||||||
showOverflow: true,
|
showOverflow: true,
|
||||||
};
|
};
|
||||||
|
|
||||||
const checked = ref(false);
|
const checked = ref(false);
|
||||||
const [BasicTable, tableApi] = useVbenVxeGrid({
|
const [BasicTable, tableApi] = useVbenVxeGrid({
|
||||||
formOptions,
|
formOptions,
|
||||||
@ -236,10 +236,7 @@ function handleResetPwd(record: Recordable<any>) {
|
|||||||
</template>
|
</template>
|
||||||
<template #avatar="{ row }">
|
<template #avatar="{ row }">
|
||||||
<Avatar v-if="row.avatar" :src="row.avatar" />
|
<Avatar v-if="row.avatar" :src="row.avatar" />
|
||||||
<Avatar
|
<Avatar v-else :src="preferences.app.defaultAvatar" />
|
||||||
v-else
|
|
||||||
src="https://cube.elemecdn.com/3/7c/3ea6beec64369c2642b92c6726f1epng.png"
|
|
||||||
/>
|
|
||||||
</template>
|
</template>
|
||||||
<template #status="{ row }">
|
<template #status="{ row }">
|
||||||
<TableSwitch
|
<TableSwitch
|
||||||
|
30
apps/web-antd/src/views/演示使用自行删除/upload/index.vue
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import { ref } from 'vue';
|
||||||
|
|
||||||
|
import { JsonPreview, Page } from '@vben/common-ui';
|
||||||
|
|
||||||
|
import { RadioGroup } from 'ant-design-vue';
|
||||||
|
|
||||||
|
import { ImageUpload } from '#/components/upload';
|
||||||
|
|
||||||
|
const resultField = ref<'ossId' | 'url'>('ossId');
|
||||||
|
|
||||||
|
const fileList = ref([]);
|
||||||
|
const fieldOptions = [
|
||||||
|
{ label: 'ossId', value: 'ossId' },
|
||||||
|
{ label: '链接地址', value: 'url' },
|
||||||
|
];
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<Page class="flex flex-col gap-[8px]">
|
||||||
|
<div class="bg-background flex flex-col gap-[12px] rounded-lg p-6">
|
||||||
|
<div class="flex gap-[8px]">
|
||||||
|
<span>返回字段: </span>
|
||||||
|
<RadioGroup v-model:value="resultField" :options="fieldOptions" />
|
||||||
|
</div>
|
||||||
|
<ImageUpload v-model:value="fileList" :result-field="resultField" />
|
||||||
|
<JsonPreview :data="fileList" />
|
||||||
|
</div>
|
||||||
|
</Page>
|
||||||
|
</template>
|
@ -621,6 +621,9 @@ importers:
|
|||||||
|
|
||||||
apps/web-antd:
|
apps/web-antd:
|
||||||
dependencies:
|
dependencies:
|
||||||
|
'@ant-design/icons-vue':
|
||||||
|
specifier: ^7.0.1
|
||||||
|
version: 7.0.1(vue@3.5.11(typescript@5.6.2))
|
||||||
'@tinymce/tinymce-vue':
|
'@tinymce/tinymce-vue':
|
||||||
specifier: ^6.0.1
|
specifier: ^6.0.1
|
||||||
version: 6.0.1(vue@3.5.11(typescript@5.6.2))
|
version: 6.0.1(vue@3.5.11(typescript@5.6.2))
|
||||||
@ -5926,9 +5929,6 @@ packages:
|
|||||||
resolution: {integrity: sha512-yCEafptTtb4bk7GLEQoM8KVJpxAfdBJYaXyzQEgQQQgYrZiDp8SJmGKlYza6CYjEDNstAdNdKA3UuoULlEbS6w==}
|
resolution: {integrity: sha512-yCEafptTtb4bk7GLEQoM8KVJpxAfdBJYaXyzQEgQQQgYrZiDp8SJmGKlYza6CYjEDNstAdNdKA3UuoULlEbS6w==}
|
||||||
engines: {node: '>=12.13'}
|
engines: {node: '>=12.13'}
|
||||||
|
|
||||||
core-js-compat@3.38.0:
|
|
||||||
resolution: {integrity: sha512-75LAicdLa4OJVwFxFbQR3NdnZjNgX6ILpVcVzcC4T2smerB5lELMrJQQQoWV6TiuC/vlaFqgU2tKQx9w5s0e0A==}
|
|
||||||
|
|
||||||
core-js-compat@3.38.1:
|
core-js-compat@3.38.1:
|
||||||
resolution: {integrity: sha512-JRH6gfXxGmrzF3tZ57lFx97YARxCXPaMzPo6jELZhv88pBH5VXpQ+y0znKGlFnzuaihqhLbefxSJxWJMPtfDzw==}
|
resolution: {integrity: sha512-JRH6gfXxGmrzF3tZ57lFx97YARxCXPaMzPo6jELZhv88pBH5VXpQ+y0znKGlFnzuaihqhLbefxSJxWJMPtfDzw==}
|
||||||
|
|
||||||
@ -12029,7 +12029,7 @@ snapshots:
|
|||||||
babel-plugin-polyfill-corejs2: 0.4.11(@babel/core@7.25.2)
|
babel-plugin-polyfill-corejs2: 0.4.11(@babel/core@7.25.2)
|
||||||
babel-plugin-polyfill-corejs3: 0.10.6(@babel/core@7.25.2)
|
babel-plugin-polyfill-corejs3: 0.10.6(@babel/core@7.25.2)
|
||||||
babel-plugin-polyfill-regenerator: 0.6.2(@babel/core@7.25.2)
|
babel-plugin-polyfill-regenerator: 0.6.2(@babel/core@7.25.2)
|
||||||
core-js-compat: 3.38.0
|
core-js-compat: 3.38.1
|
||||||
semver: 6.3.1
|
semver: 6.3.1
|
||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
- supports-color
|
- supports-color
|
||||||
@ -15176,7 +15176,7 @@ snapshots:
|
|||||||
dependencies:
|
dependencies:
|
||||||
'@babel/core': 7.25.2
|
'@babel/core': 7.25.2
|
||||||
'@babel/helper-define-polyfill-provider': 0.6.2(@babel/core@7.25.2)
|
'@babel/helper-define-polyfill-provider': 0.6.2(@babel/core@7.25.2)
|
||||||
core-js-compat: 3.38.0
|
core-js-compat: 3.38.1
|
||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
- supports-color
|
- supports-color
|
||||||
|
|
||||||
@ -15645,10 +15645,6 @@ snapshots:
|
|||||||
dependencies:
|
dependencies:
|
||||||
is-what: 4.1.16
|
is-what: 4.1.16
|
||||||
|
|
||||||
core-js-compat@3.38.0:
|
|
||||||
dependencies:
|
|
||||||
browserslist: 4.23.3
|
|
||||||
|
|
||||||
core-js-compat@3.38.1:
|
core-js-compat@3.38.1:
|
||||||
dependencies:
|
dependencies:
|
||||||
browserslist: 4.23.3
|
browserslist: 4.23.3
|
||||||
|
BIN
scripts/preview/1.png
Normal file
After Width: | Height: | Size: 1.2 MiB |
BIN
scripts/preview/10.png
Normal file
After Width: | Height: | Size: 526 KiB |
BIN
scripts/preview/2.png
Normal file
After Width: | Height: | Size: 821 KiB |
BIN
scripts/preview/3.png
Normal file
After Width: | Height: | Size: 742 KiB |
BIN
scripts/preview/4.png
Normal file
After Width: | Height: | Size: 749 KiB |
BIN
scripts/preview/5.png
Normal file
After Width: | Height: | Size: 835 KiB |
BIN
scripts/preview/6.png
Normal file
After Width: | Height: | Size: 818 KiB |
BIN
scripts/preview/7.png
Normal file
After Width: | Height: | Size: 512 KiB |
BIN
scripts/preview/8.png
Normal file
After Width: | Height: | Size: 610 KiB |
BIN
scripts/preview/9.png
Normal file
After Width: | Height: | Size: 657 KiB |