Compare commits
168 Commits
1.0.0-beta
...
1.0.0
Author | SHA1 | Date | |
---|---|---|---|
![]() |
9e365a336d | ||
![]() |
fb79029765 | ||
![]() |
938ccd85f8 | ||
![]() |
9311dfe25b | ||
![]() |
6688a6b3c2 | ||
![]() |
39e41d05be | ||
![]() |
390ec31162 | ||
![]() |
478fe5a069 | ||
![]() |
862bbd8344 | ||
![]() |
73acf6a3c6 | ||
![]() |
a38fc307d0 | ||
![]() |
68f3ac8c79 | ||
![]() |
e5625efcf7 | ||
![]() |
23768ea620 | ||
![]() |
e224971975 | ||
![]() |
f60796f961 | ||
![]() |
7f4c733fa3 | ||
![]() |
330dcf480e | ||
![]() |
0d63b6d9ca | ||
![]() |
f9c6284c35 | ||
![]() |
7b5e2e71d8 | ||
![]() |
1b172b0329 | ||
![]() |
ade90a5bdd | ||
![]() |
88d2b3e569 | ||
![]() |
625862e082 | ||
![]() |
ad2f327d4a | ||
![]() |
ea59f7d9e6 | ||
![]() |
bfaa2780ab | ||
![]() |
d262b7b6c0 | ||
![]() |
93b48ef244 | ||
![]() |
860fc15ce6 | ||
![]() |
646598afba | ||
![]() |
234544c40d | ||
![]() |
1b8d6c193e | ||
![]() |
8395a7f167 | ||
![]() |
4ea9b0d342 | ||
![]() |
88a722a298 | ||
![]() |
a7e4799dea | ||
![]() |
e8640a59ae | ||
![]() |
b0a862a448 | ||
![]() |
29ec264dcd | ||
![]() |
86950953ff | ||
![]() |
71d005bd48 | ||
![]() |
23c548ac3d | ||
![]() |
0334db44ac | ||
![]() |
02957060bf | ||
![]() |
8bc62cf2df | ||
![]() |
972e9439b7 | ||
![]() |
307a71fb35 | ||
![]() |
39d62a91bc | ||
![]() |
477a05c26c | ||
![]() |
12de789f2e | ||
![]() |
0df8c5c02c | ||
![]() |
e3d176e245 | ||
![]() |
6cf618dbf7 | ||
![]() |
b2268b03e7 | ||
![]() |
d1ca09c7bb | ||
![]() |
ff3c5f8581 | ||
![]() |
240f0b5f8d | ||
![]() |
6f3d50984f | ||
![]() |
280af21ffe | ||
![]() |
9cdc2780b3 | ||
![]() |
6e7fade539 | ||
![]() |
c6984c164c | ||
![]() |
162b3207fd | ||
![]() |
8788d2eeab | ||
![]() |
c491b9e021 | ||
![]() |
0796f4327c | ||
![]() |
7e7e23fc09 | ||
![]() |
6cd9937c03 | ||
![]() |
f89f4f32c7 | ||
![]() |
ef956a5507 | ||
![]() |
bec201b95b | ||
![]() |
4a393a1920 | ||
![]() |
c432e0ac33 | ||
![]() |
719c9a8f2d | ||
![]() |
70e7d6a131 | ||
![]() |
b6256d4736 | ||
![]() |
a0fbe0b21a | ||
![]() |
f7fa69d33b | ||
![]() |
7c45aeb868 | ||
![]() |
f9feeccc44 | ||
![]() |
850a6af1e0 | ||
![]() |
2f16e64a3d | ||
![]() |
30b16fd5a8 | ||
![]() |
106476b755 | ||
![]() |
c27acef777 | ||
![]() |
d5a210f53f | ||
![]() |
6c4a742627 | ||
![]() |
45987fc1e3 | ||
![]() |
72ae9edd2c | ||
![]() |
86a2539d27 | ||
![]() |
47a817f73c | ||
![]() |
8f6a2a6c23 | ||
![]() |
ea962e75d0 | ||
![]() |
24d14c2841 | ||
![]() |
8b961a9d7f | ||
![]() |
6dedefb6b9 | ||
![]() |
c60f826d31 | ||
![]() |
6b667374af | ||
![]() |
330a8a1d70 | ||
![]() |
9856bc88d2 | ||
![]() |
68465b5fbf | ||
![]() |
0ea0f204cb | ||
![]() |
55611a1eb4 | ||
![]() |
415176b2ee | ||
![]() |
6677d5cfa8 | ||
![]() |
1b65254383 | ||
![]() |
0a99f27127 | ||
![]() |
89047a7dde | ||
![]() |
329b41bc44 | ||
![]() |
21e05a1bc8 | ||
![]() |
0a27114ea3 | ||
![]() |
113c2d60b5 | ||
![]() |
fbbb023971 | ||
![]() |
1a6e5d22fd | ||
![]() |
c5fcf50c76 | ||
![]() |
be06f4cb50 | ||
![]() |
6f098cd5c0 | ||
![]() |
9288341c85 | ||
![]() |
2099e672ce | ||
![]() |
2eff47b59a | ||
![]() |
27f62c2bab | ||
![]() |
f707fcb3da | ||
![]() |
26ac896f5c | ||
![]() |
304b1b2efc | ||
![]() |
f923f59070 | ||
![]() |
437cb02e11 | ||
![]() |
b20174d110 | ||
![]() |
21015c6dd7 | ||
![]() |
132e20f83e | ||
![]() |
663bad5e71 | ||
![]() |
26ee8e28ea | ||
![]() |
d10f950b6b | ||
![]() |
60d513ce40 | ||
![]() |
ba539f6793 | ||
![]() |
fffe2d0db9 | ||
![]() |
2e8563d808 | ||
![]() |
f8e480d724 | ||
![]() |
e6374a6a06 | ||
![]() |
521ba6af9c | ||
![]() |
df79908056 | ||
![]() |
493a0effe8 | ||
![]() |
078f255e1a | ||
![]() |
961f65215d | ||
![]() |
ba4662522e | ||
![]() |
8fe87b10dc | ||
![]() |
99a0c63f92 | ||
![]() |
3b8b8812a5 | ||
![]() |
836e845051 | ||
![]() |
2af01881ab | ||
![]() |
cf53a4f3bd | ||
![]() |
11a0b2f2f3 | ||
![]() |
2dbd323b2a | ||
![]() |
403b73978b | ||
![]() |
f873b8ac3e | ||
![]() |
6cdcc8cbca | ||
![]() |
846bab7b7e | ||
![]() |
a7b8b6efb0 | ||
![]() |
599623042c | ||
![]() |
deeae45823 | ||
![]() |
a93e3e976f | ||
![]() |
e8d0fd0e27 | ||
![]() |
a0c2cffcab | ||
![]() |
335dc0cc11 | ||
![]() |
bda0711bbd | ||
![]() |
8e7cf4b10e | ||
![]() |
8ad2b8665d |
2
.github/release-drafter.yml
vendored
2
.github/release-drafter.yml
vendored
@@ -16,7 +16,7 @@ categories:
|
||||
- title: '🐞 Bug Fixes'
|
||||
labels:
|
||||
- 'bug'
|
||||
- title: '📈 Performance'
|
||||
- title: '📈 Performance & Enhancement'
|
||||
labels:
|
||||
- 'perf'
|
||||
- 'enhancement'
|
||||
|
1
.gitignore
vendored
1
.gitignore
vendored
@@ -20,6 +20,7 @@ dev-dist
|
||||
.stylelintcache
|
||||
yarn.lock
|
||||
package-lock.json
|
||||
pnpm-lock.yaml
|
||||
.VSCodeCounter
|
||||
**/backend-mock/data
|
||||
|
||||
|
14
.vscode/settings.json
vendored
14
.vscode/settings.json
vendored
@@ -197,11 +197,14 @@
|
||||
"playground/src/locales/langs",
|
||||
"apps/*/src/locales/langs"
|
||||
],
|
||||
"i18n-ally.pathMatcher": "{locale}.json",
|
||||
"i18n-ally.enabledParsers": ["json", "ts", "js", "yaml"],
|
||||
"i18n-ally.pathMatcher": "{locale}/{namespace}.{ext}",
|
||||
"i18n-ally.enabledParsers": ["json"],
|
||||
"i18n-ally.sourceLanguage": "en",
|
||||
"i18n-ally.displayLanguage": "zh-CN",
|
||||
"i18n-ally.enabledFrameworks": ["vue", "react"],
|
||||
"i18n-ally.keystyle": "nested",
|
||||
"i18n-ally.sortKeys": true,
|
||||
"i18n-ally.namespace": true,
|
||||
|
||||
// 控制相关文件嵌套展示
|
||||
"explorer.fileNesting.enabled": true,
|
||||
@@ -216,11 +219,12 @@
|
||||
"tailwind.config.mjs": "postcss.*"
|
||||
},
|
||||
"commentTranslate.hover.enabled": false,
|
||||
"i18n-ally.keystyle": "nested",
|
||||
"commentTranslate.multiLineMerge": true,
|
||||
"vue.server.hybridMode": true,
|
||||
"vitest.disableWorkspaceWarning": true,
|
||||
"cSpell.words": ["tinymce"],
|
||||
"cSpell.words": ["tinymce", "vditor"],
|
||||
"typescript.tsdk": "node_modules/typescript/lib",
|
||||
"editor.linkedEditing": true // 自动同步更改html标签
|
||||
"editor.linkedEditing": true, // 自动同步更改html标签,
|
||||
"vscodeCustomCodeColor.highlightValue": "v-access", // v-access显示的颜色
|
||||
"vscodeCustomCodeColor.highlightValueColor": "#CCFFFF"
|
||||
}
|
||||
|
35
CHANGELOG.md
35
CHANGELOG.md
@@ -1,6 +1,37 @@
|
||||
1.0.0-beta (2024-10-8)
|
||||
# 1.0.0
|
||||
|
||||
# FEATURES
|
||||
**FEATURES**
|
||||
|
||||
- 用户管理 新增从参数取默认密码
|
||||
- 全局表格加上id 方便进行缓存列排序的操作
|
||||
- 支持菜单名称i18n
|
||||
- 登录页 验证码登录
|
||||
- Markdown编辑/预览组件(基于vditor)
|
||||
- VxeTable搜索表单 enter提交
|
||||
|
||||
**BUG FIXES**
|
||||
|
||||
- 登录页面 关闭租户后下拉框没有正常隐藏
|
||||
- 字典管理 关闭租户不应显示`同步租户字典`按钮
|
||||
- 登录日志 漏掉了登录日志日期查询
|
||||
- 登出相关逻辑在并发(非await)情况下重复执行的问题
|
||||
- VxeTable在开启/关闭查询表单时 需要使用不同的padding
|
||||
- VxeTable表格刷新 默认为reload 修改为在当前页刷新(query)
|
||||
- 岗位管理 部门参数错误
|
||||
- 角色管理 菜单分配 节点独立下的回显及提交问题
|
||||
- 租户管理 套餐管理 回显时候`已选中节点`数量为0
|
||||
- 用户管理 更新用户时打开drawer需要加载该部门下的岗位信息
|
||||
|
||||
**OTHERS**
|
||||
|
||||
- 登录页 租户选择框浮层固定高度[256px] 超过高度自动滚动
|
||||
- 表单的Label默认方向改为`top` 支持\n换行
|
||||
- 所有表格的搜索加上allowClear属性 支持清除
|
||||
- vxe表格loading 只加载表格 不加载上面的表单
|
||||
|
||||
# 1.0.0-beta (2024-10-8)
|
||||
|
||||
**FEATURES**
|
||||
|
||||
- 基础功能已经开发完毕
|
||||
- 工作流相关模块等待后端重构后开发
|
||||
|
@@ -134,7 +134,7 @@ If you think this project is helpful to you, you can help the author buy a cup o
|
||||
|
||||

|
||||
|
||||
<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>
|
||||
<a style="display: block;width: 100px;height: 50px;line-height: 50px; color: #fff;text-align: center; background: #408aee;border-radius: 4px;" href="https://www.paypal.com/paypalme/cvvben">Paypal Me</a>
|
||||
|
||||
## Contributor
|
||||
|
||||
|
@@ -2,10 +2,12 @@
|
||||
|
||||
## 提示
|
||||
|
||||
该仓库使用vben最新版本v5开发, 老版本v2地址 [前往](https://gitee.com/dapppp/ruoyi-plus-vben)
|
||||
该仓库使用vben最新版本v5开发, ~~老版本v2地址(不维护)~~ [前往](https://gitee.com/dapppp/ruoyi-plus-vben)
|
||||
|
||||
v5版本采用分仓(包)目录结构, 具体开发路径为: `根目录/apps/web-antd`
|
||||
|
||||
目前对应后端版本: **5.2.3/2.2.3**
|
||||
|
||||
## 进度
|
||||
|
||||
**工作流相关模块等待后端重构后开发**
|
||||
@@ -20,7 +22,7 @@ v5版本采用分仓(包)目录结构, 具体开发路径为: `根目录/apps/we
|
||||
|
||||
| 组件/框架 | 版本 |
|
||||
| :------------- | :----- |
|
||||
| vben | 5.3.2 |
|
||||
| vben | 5.4.1 |
|
||||
| ant-design-vue | 4.2.5 |
|
||||
| vue | 3.5.11 |
|
||||
|
||||
@@ -38,11 +40,13 @@ admin 账号: admin admin123
|
||||
|
||||
## WX Group
|
||||
|
||||
暂不开放
|
||||
演示站 - 微信群菜单
|
||||
|
||||
## 文档
|
||||
|
||||
[vben 文档地址](https://doc.vvbin.cn/)
|
||||
[本框架文档 强烈建议阅读](https://dapdap.top/)
|
||||
|
||||
[Vben V5 文档地址](https://doc.vben.pro/)
|
||||
|
||||
[RuoYi-Plus 文档地址](https://plus-doc.dromara.org/#/)
|
||||
|
||||
@@ -57,8 +61,8 @@ admin 账号: admin admin123
|
||||
```json
|
||||
"packageManager": "pnpm",
|
||||
"engines": {
|
||||
"node": ">=20.10.0",
|
||||
"pnpm": ">=9.5.0"
|
||||
"node": ">=20.15.0",
|
||||
"pnpm": "latest"
|
||||
},
|
||||
```
|
||||
|
||||
@@ -76,9 +80,13 @@ cd ruoyi-plus-vben5
|
||||
pnpm install
|
||||
```
|
||||
|
||||
- 菜单图标替换
|
||||
|
||||
[根目录/scripts/菜单图标替换sql/update_icon.sql](https://gitee.com/dapppp/ruoyi-plus-vben5/blob/main/scripts/%E8%8F%9C%E5%8D%95%E5%9B%BE%E6%A0%87%E6%9B%BF%E6%8D%A2sql/update_icon.sql)
|
||||
|
||||
- 关于代码生成
|
||||
|
||||
V5版本代码生成模板为付费功能(暂未开放)
|
||||
V5版本代码生成模板为付费功能 [详见](https://dapdap.top/other/template.html)
|
||||
|
||||
- 关于一些监控的地址配置(微服务版本可以跳过这一小节)
|
||||
|
||||
|
@@ -21,7 +21,7 @@ export default defineEventHandler(async (event) => {
|
||||
|
||||
if (!findUser) {
|
||||
clearRefreshTokenCookie(event);
|
||||
return forbiddenResponse(event);
|
||||
return forbiddenResponse(event, 'Username or password is incorrect.');
|
||||
}
|
||||
|
||||
const accessToken = generateAccessToken(findUser);
|
||||
|
@@ -86,7 +86,7 @@ const createDemosMenus = (role: 'admin' | 'super' | 'user') => {
|
||||
component: '/demos/access/admin-visible',
|
||||
meta: {
|
||||
icon: 'mdi:button-cursor',
|
||||
title: 'page.demos.access.adminVisible',
|
||||
title: 'demos.access.adminVisible',
|
||||
},
|
||||
name: 'AccessAdminVisibleDemo',
|
||||
path: '/demos/access/admin-visible',
|
||||
@@ -95,7 +95,7 @@ const createDemosMenus = (role: 'admin' | 'super' | 'user') => {
|
||||
component: '/demos/access/super-visible',
|
||||
meta: {
|
||||
icon: 'mdi:button-cursor',
|
||||
title: 'page.demos.access.superVisible',
|
||||
title: 'demos.access.superVisible',
|
||||
},
|
||||
name: 'AccessSuperVisibleDemo',
|
||||
path: '/demos/access/super-visible',
|
||||
@@ -104,7 +104,7 @@ const createDemosMenus = (role: 'admin' | 'super' | 'user') => {
|
||||
component: '/demos/access/user-visible',
|
||||
meta: {
|
||||
icon: 'mdi:button-cursor',
|
||||
title: 'page.demos.access.userVisible',
|
||||
title: 'demos.access.userVisible',
|
||||
},
|
||||
name: 'AccessUserVisibleDemo',
|
||||
path: '/demos/access/user-visible',
|
||||
@@ -118,7 +118,7 @@ const createDemosMenus = (role: 'admin' | 'super' | 'user') => {
|
||||
icon: 'ic:baseline-view-in-ar',
|
||||
keepAlive: true,
|
||||
order: 1000,
|
||||
title: 'page.demos.title',
|
||||
title: 'demos.title',
|
||||
},
|
||||
name: 'Demos',
|
||||
path: '/demos',
|
||||
@@ -129,7 +129,7 @@ const createDemosMenus = (role: 'admin' | 'super' | 'user') => {
|
||||
path: '/demosaccess',
|
||||
meta: {
|
||||
icon: 'mdi:cloud-key-outline',
|
||||
title: 'page.demos.access.backendPermissions',
|
||||
title: 'demos.access.backendPermissions',
|
||||
},
|
||||
redirect: '/demos/access/page-control',
|
||||
children: [
|
||||
@@ -139,7 +139,7 @@ const createDemosMenus = (role: 'admin' | 'super' | 'user') => {
|
||||
component: '/demos/access/index',
|
||||
meta: {
|
||||
icon: 'mdi:page-previous-outline',
|
||||
title: 'page.demos.access.pageAccess',
|
||||
title: 'demos.access.pageAccess',
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -148,7 +148,7 @@ const createDemosMenus = (role: 'admin' | 'super' | 'user') => {
|
||||
component: '/demos/access/button-control',
|
||||
meta: {
|
||||
icon: 'mdi:button-cursor',
|
||||
title: 'page.demos.access.buttonControl',
|
||||
title: 'demos.access.buttonControl',
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -159,7 +159,7 @@ const createDemosMenus = (role: 'admin' | 'super' | 'user') => {
|
||||
authority: ['no-body'],
|
||||
icon: 'mdi:button-cursor',
|
||||
menuVisibleWithForbidden: true,
|
||||
title: 'page.demos.access.menuVisible403',
|
||||
title: 'demos.access.menuVisible403',
|
||||
},
|
||||
},
|
||||
roleWithMenus[role],
|
||||
|
@@ -39,14 +39,17 @@ export function useResponseError(message: string, error: any = null) {
|
||||
};
|
||||
}
|
||||
|
||||
export function forbiddenResponse(event: H3Event<EventHandlerRequest>) {
|
||||
export function forbiddenResponse(
|
||||
event: H3Event<EventHandlerRequest>,
|
||||
message = 'Forbidden Exception',
|
||||
) {
|
||||
setResponseStatus(event, 403);
|
||||
return useResponseError('ForbiddenException', 'Forbidden Exception');
|
||||
return useResponseError(message, message);
|
||||
}
|
||||
|
||||
export function unAuthorizedResponse(event: H3Event<EventHandlerRequest>) {
|
||||
setResponseStatus(event, 401);
|
||||
return useResponseError('UnauthorizedException', 'Unauthorized Exception');
|
||||
return useResponseError('Unauthorized Exception', 'Unauthorized Exception');
|
||||
}
|
||||
|
||||
export function sleep(ms: number) {
|
||||
|
@@ -25,7 +25,7 @@ VITE_GLOB_RSA_PUBLIC_KEY=MFwwDQYJKoZIhvcNAQEBBQADSwAwSAJBAKoR8mX0rGKLqzcWmOzbfj6
|
||||
# RSA私钥 响应解密使用 注意这两个是两对RSA公私钥 请求加密-后端解密是一对 响应解密-后端加密是一对
|
||||
VITE_GLOB_RSA_PRIVATE_KEY=MIIBVAIBADANBgkqhkiG9w0BAQEFAASCAT4wggE6AgEAAkEAmc3CuPiGL/LcIIm7zryCEIbl1SPzBkr75E2VMtxegyZ1lYRD+7TZGAPkvIsBcaMs6Nsy0L78n2qh+lIZMpLH8wIDAQABAkEAk82Mhz0tlv6IVCyIcw/s3f0E+WLmtPFyR9/WtV3Y5aaejUkU60JpX4m5xNR2VaqOLTZAYjW8Wy0aXr3zYIhhQQIhAMfqR9oFdYw1J9SsNc+CrhugAvKTi0+BF6VoL6psWhvbAiEAxPPNTmrkmrXwdm/pQQu3UOQmc2vCZ5tiKpW10CgJi8kCIFGkL6utxw93Ncj4exE/gPLvKcT+1Emnoox+O9kRXss5AiAMtYLJDaLEzPrAWcZeeSgSIzbL+ecokmFKSDDcRske6QIgSMkHedwND1olF8vlKsJUGK3BcdtM8w4Xq7BpSBwsloE=
|
||||
# 客户端id
|
||||
VITE_GLOB_APP_CLIENT_ID=6afcaa29272b14c1c87264950c726ef4
|
||||
VITE_GLOB_APP_CLIENT_ID=e5cd7e4891bf95d1d19206ce24a7b32e
|
||||
|
||||
# 开启WEBSOCKET
|
||||
VITE_GLOB_WEBSOCKET_ENABLE=false
|
||||
|
136
apps/web-antd/src/adapter/component/index.ts
Normal file
136
apps/web-antd/src/adapter/component/index.ts
Normal file
@@ -0,0 +1,136 @@
|
||||
/**
|
||||
* 通用组件共同的使用的基础组件,原先放在 adapter/form 内部,限制了使用范围,这里提取出来,方便其他地方使用
|
||||
* 可用于 vben-form、vben-modal、vben-drawer 等组件使用,
|
||||
*/
|
||||
|
||||
import type { BaseFormComponentType } from '@vben/common-ui';
|
||||
|
||||
import type { Component, SetupContext } from 'vue';
|
||||
import { h } from 'vue';
|
||||
|
||||
import { globalShareState } from '@vben/common-ui';
|
||||
import { $t } from '@vben/locales';
|
||||
|
||||
import {
|
||||
AutoComplete,
|
||||
Button,
|
||||
Checkbox,
|
||||
CheckboxGroup,
|
||||
DatePicker,
|
||||
Divider,
|
||||
Input,
|
||||
InputNumber,
|
||||
InputPassword,
|
||||
Mentions,
|
||||
notification,
|
||||
Radio,
|
||||
RadioGroup,
|
||||
RangePicker,
|
||||
Rate,
|
||||
Select,
|
||||
Space,
|
||||
Switch,
|
||||
Textarea,
|
||||
TimePicker,
|
||||
TreeSelect,
|
||||
Upload,
|
||||
} from 'ant-design-vue';
|
||||
|
||||
import { Tinymce as RichTextarea } from '#/components/tinymce';
|
||||
import { FileUpload, ImageUpload } from '#/components/upload';
|
||||
|
||||
const withDefaultPlaceholder = <T extends Component>(
|
||||
component: T,
|
||||
type: 'input' | 'select',
|
||||
) => {
|
||||
return (props: any, { attrs, slots }: Omit<SetupContext, 'expose'>) => {
|
||||
const placeholder = props?.placeholder || $t(`ui.placeholder.${type}`);
|
||||
return h(component, { ...props, ...attrs, placeholder }, slots);
|
||||
};
|
||||
};
|
||||
|
||||
// 这里需要自行根据业务组件库进行适配,需要用到的组件都需要在这里类型说明
|
||||
export type ComponentType =
|
||||
| 'AutoComplete'
|
||||
| 'Checkbox'
|
||||
| 'CheckboxGroup'
|
||||
| 'DatePicker'
|
||||
| 'DefaultButton'
|
||||
| 'Divider'
|
||||
| 'FileUpload'
|
||||
| 'ImageUpload'
|
||||
| 'Input'
|
||||
| 'InputNumber'
|
||||
| 'InputPassword'
|
||||
| 'Mentions'
|
||||
| 'PrimaryButton'
|
||||
| 'Radio'
|
||||
| 'RadioGroup'
|
||||
| 'RangePicker'
|
||||
| 'Rate'
|
||||
| 'RichTextarea'
|
||||
| 'Select'
|
||||
| 'Space'
|
||||
| 'Switch'
|
||||
| 'Textarea'
|
||||
| 'TimePicker'
|
||||
| 'TreeSelect'
|
||||
| 'Upload'
|
||||
| BaseFormComponentType;
|
||||
|
||||
async function initComponentAdapter() {
|
||||
const components: Partial<Record<ComponentType, Component>> = {
|
||||
// 如果你的组件体积比较大,可以使用异步加载
|
||||
// Button: () =>
|
||||
// import('xxx').then((res) => res.Button),
|
||||
|
||||
AutoComplete,
|
||||
Checkbox,
|
||||
CheckboxGroup,
|
||||
DatePicker,
|
||||
// 自定义默认按钮
|
||||
DefaultButton: (props, { attrs, slots }) => {
|
||||
return h(Button, { ...props, attrs, type: 'default' }, slots);
|
||||
},
|
||||
Divider,
|
||||
Input: withDefaultPlaceholder(Input, 'input'),
|
||||
InputNumber: withDefaultPlaceholder(InputNumber, 'input'),
|
||||
InputPassword: withDefaultPlaceholder(InputPassword, 'input'),
|
||||
Mentions: withDefaultPlaceholder(Mentions, 'input'),
|
||||
// 自定义主要按钮
|
||||
PrimaryButton: (props, { attrs, slots }) => {
|
||||
return h(Button, { ...props, attrs, type: 'primary' }, slots);
|
||||
},
|
||||
Radio,
|
||||
RadioGroup,
|
||||
RangePicker,
|
||||
Rate,
|
||||
Select: withDefaultPlaceholder(Select, 'select'),
|
||||
Space,
|
||||
Switch,
|
||||
Textarea: withDefaultPlaceholder(Textarea, 'input'),
|
||||
TimePicker,
|
||||
TreeSelect: withDefaultPlaceholder(TreeSelect, 'select'),
|
||||
Upload,
|
||||
ImageUpload,
|
||||
FileUpload,
|
||||
RichTextarea,
|
||||
};
|
||||
|
||||
// 将组件注册到全局共享状态中
|
||||
globalShareState.setComponents(components);
|
||||
|
||||
// 定义全局共享状态中的消息提示
|
||||
globalShareState.defineMessage({
|
||||
// 复制成功消息提示
|
||||
copyPreferencesSuccess: (title, content) => {
|
||||
notification.success({
|
||||
description: content,
|
||||
message: title,
|
||||
placement: 'bottomRight',
|
||||
});
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
export { initComponentAdapter };
|
@@ -1,115 +1,16 @@
|
||||
import type {
|
||||
BaseFormComponentType,
|
||||
VbenFormSchema as FormSchema,
|
||||
VbenFormProps,
|
||||
} from '@vben/common-ui';
|
||||
|
||||
import type { Component, SetupContext } from 'vue';
|
||||
import { h } from 'vue';
|
||||
import type { ComponentType } from './component';
|
||||
|
||||
import { setupVbenForm, useVbenForm as useForm, z } from '@vben/common-ui';
|
||||
import { $t } from '@vben/locales';
|
||||
|
||||
import {
|
||||
AutoComplete,
|
||||
Button,
|
||||
Checkbox,
|
||||
CheckboxGroup,
|
||||
DatePicker,
|
||||
Divider,
|
||||
Input,
|
||||
InputNumber,
|
||||
InputPassword,
|
||||
Mentions,
|
||||
Radio,
|
||||
RadioGroup,
|
||||
RangePicker,
|
||||
Rate,
|
||||
Select,
|
||||
Space,
|
||||
Switch,
|
||||
Textarea,
|
||||
TimePicker,
|
||||
TreeSelect,
|
||||
Upload,
|
||||
} from 'ant-design-vue';
|
||||
import { isArray } from 'lodash-es';
|
||||
|
||||
import { Tinymce as RichTextarea } from '#/components/tinymce';
|
||||
import { FileUpload, ImageUpload } from '#/components/upload';
|
||||
|
||||
// 这里需要自行根据业务组件库进行适配,需要用到的组件都需要在这里类型说明
|
||||
export type FormComponentType =
|
||||
| 'AutoComplete'
|
||||
| 'Checkbox'
|
||||
| 'CheckboxGroup'
|
||||
| 'DatePicker'
|
||||
| 'Divider'
|
||||
| 'FileUpload'
|
||||
| 'ImageUpload'
|
||||
| 'Input'
|
||||
| 'InputNumber'
|
||||
| 'InputPassword'
|
||||
| 'Mentions'
|
||||
| 'Radio'
|
||||
| 'RadioGroup'
|
||||
| 'RangePicker'
|
||||
| 'Rate'
|
||||
| 'RichTextarea'
|
||||
| 'Select'
|
||||
| 'Space'
|
||||
| 'Switch'
|
||||
| 'Textarea'
|
||||
| 'TimePicker'
|
||||
| 'TreeSelect'
|
||||
| 'Upload'
|
||||
| BaseFormComponentType;
|
||||
|
||||
const withDefaultPlaceholder = <T extends Component>(
|
||||
component: T,
|
||||
type: 'input' | 'select',
|
||||
) => {
|
||||
return (props: any, { attrs, slots }: Omit<SetupContext, 'expose'>) => {
|
||||
const placeholder = props?.placeholder || $t(`placeholder.${type}`);
|
||||
return h(component, { ...props, ...attrs, placeholder }, slots);
|
||||
};
|
||||
};
|
||||
|
||||
// 初始化表单组件,并注册到form组件内部
|
||||
setupVbenForm<FormComponentType>({
|
||||
components: {
|
||||
AutoComplete,
|
||||
Checkbox,
|
||||
CheckboxGroup,
|
||||
DatePicker,
|
||||
// 自定义默认的重置按钮
|
||||
DefaultResetActionButton: (props, { attrs, slots }) => {
|
||||
return h(Button, { ...props, attrs, type: 'default' }, slots);
|
||||
},
|
||||
// 自定义默认的提交按钮
|
||||
DefaultSubmitActionButton: (props, { attrs, slots }) => {
|
||||
return h(Button, { ...props, attrs, type: 'primary' }, slots);
|
||||
},
|
||||
Divider,
|
||||
Input: withDefaultPlaceholder(Input, 'input'),
|
||||
InputNumber: withDefaultPlaceholder(InputNumber, 'input'),
|
||||
InputPassword: withDefaultPlaceholder(InputPassword, 'input'),
|
||||
Mentions: withDefaultPlaceholder(Mentions, 'input'),
|
||||
Radio,
|
||||
RadioGroup,
|
||||
RangePicker,
|
||||
Rate,
|
||||
RichTextarea,
|
||||
Select: withDefaultPlaceholder(Select, 'select'),
|
||||
Space,
|
||||
Switch,
|
||||
Textarea: withDefaultPlaceholder(Textarea, 'input'),
|
||||
TimePicker,
|
||||
TreeSelect: withDefaultPlaceholder(TreeSelect, 'select'),
|
||||
Upload,
|
||||
ImageUpload,
|
||||
FileUpload,
|
||||
},
|
||||
setupVbenForm<ComponentType>({
|
||||
config: {
|
||||
// ant design vue组件库默认都是 v-model:value
|
||||
baseModelPropName: 'value',
|
||||
@@ -127,7 +28,7 @@ setupVbenForm<FormComponentType>({
|
||||
// 输入项目必填国际化适配
|
||||
required: (value, _params, ctx) => {
|
||||
if (value === undefined || value === null || value.length === 0) {
|
||||
return $t('formRules.required', [ctx.label]);
|
||||
return $t('ui.formRules.required', [ctx.label]);
|
||||
}
|
||||
return true;
|
||||
},
|
||||
@@ -137,17 +38,17 @@ setupVbenForm<FormComponentType>({
|
||||
[false, null, undefined].includes(value) ||
|
||||
(isArray(value) && value.length === 0)
|
||||
) {
|
||||
return $t('formRules.selectRequired', [ctx.label]);
|
||||
return $t('ui.formRules.selectRequired', [ctx.label]);
|
||||
}
|
||||
return true;
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
const useVbenForm = useForm<FormComponentType>;
|
||||
const useVbenForm = useForm<ComponentType>;
|
||||
|
||||
export { useVbenForm, z };
|
||||
|
||||
export type VbenFormSchema = FormSchema<FormComponentType>;
|
||||
export type VbenFormSchema = FormSchema<ComponentType>;
|
||||
export type { VbenFormProps };
|
||||
export type FormSchemaGetter = () => VbenFormSchema[];
|
||||
|
@@ -1,2 +0,0 @@
|
||||
export * from './form';
|
||||
export * from './vxe-table';
|
@@ -1,6 +1,10 @@
|
||||
import { h } from 'vue';
|
||||
import { h, type Ref } from 'vue';
|
||||
|
||||
import { setupVbenVxeTable, useVbenVxeGrid } from '@vben/plugins/vxe-table';
|
||||
import {
|
||||
setupVbenVxeTable,
|
||||
useVbenVxeGrid,
|
||||
type VxeGridDefines,
|
||||
} from '@vben/plugins/vxe-table';
|
||||
|
||||
import { Button, Image } from 'ant-design-vue';
|
||||
|
||||
@@ -23,9 +27,13 @@ setupVbenVxeTable({
|
||||
showActiveMsg: true,
|
||||
showResponseMsg: false,
|
||||
},
|
||||
// 溢出展示形式
|
||||
showOverflow: true,
|
||||
pagerConfig: {
|
||||
// pageSize: 10,
|
||||
// pageSizes: [10, 20, 30, 40, 50],
|
||||
// 默认条数
|
||||
pageSize: 10,
|
||||
// 分页可选条数
|
||||
pageSizes: [10, 20, 30, 40, 50],
|
||||
},
|
||||
rowConfig: {
|
||||
// 鼠标移入行显示 hover 样式
|
||||
@@ -44,12 +52,20 @@ setupVbenVxeTable({
|
||||
// 最大化
|
||||
zoom: true,
|
||||
// 刷新
|
||||
refresh: true,
|
||||
refresh: {
|
||||
// 默认为reload 修改为在当前页刷新
|
||||
code: 'query',
|
||||
},
|
||||
},
|
||||
// 圆角按钮
|
||||
round: true,
|
||||
// 表格尺寸
|
||||
size: 'medium',
|
||||
customConfig: {
|
||||
// 表格右上角自定义列配置 是否保存到localStorage
|
||||
// 必须存在id参数才能使用
|
||||
storage: false,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
@@ -82,3 +98,17 @@ setupVbenVxeTable({
|
||||
export { useVbenVxeGrid };
|
||||
|
||||
export type * from '@vben/plugins/vxe-table';
|
||||
|
||||
/**
|
||||
* 通用的表格复选框是否选中事件
|
||||
* @param checked 是否选中
|
||||
* @returns function
|
||||
*/
|
||||
export function tableCheckboxEvent(checked: Ref<boolean>) {
|
||||
const event: (params: VxeGridDefines.CheckboxChangeEventParams) => void = (
|
||||
params,
|
||||
) => {
|
||||
checked.value = params.$table.getCheckboxRecords().length > 0;
|
||||
};
|
||||
return event;
|
||||
}
|
||||
|
@@ -6,7 +6,6 @@ import { requestClient } from '#/api/request';
|
||||
* @returns 上传结果
|
||||
*/
|
||||
export function uploadApi(file: Blob | File) {
|
||||
console.log('uploadApi', file);
|
||||
return requestClient.upload('/resource/oss/upload', { file });
|
||||
}
|
||||
/**
|
||||
|
@@ -39,7 +39,8 @@ export interface UserInfoResp {
|
||||
|
||||
/**
|
||||
* 获取用户信息
|
||||
* 存在返回null的情况(401) 不会抛出异常 需要手动抛异常
|
||||
*/
|
||||
export async function getUserInfoApi() {
|
||||
return requestClient.get<UserInfoResp>('/system/user/getInfo');
|
||||
return requestClient.get<null | UserInfoResp>('/system/user/getInfo');
|
||||
}
|
||||
|
@@ -34,8 +34,11 @@ const { apiURL, clientId, enableEncrypt } = useAppConfig(
|
||||
import.meta.env.PROD,
|
||||
);
|
||||
|
||||
/** 控制是否弹窗 防止登录超时请求多个api会弹窗多次 */
|
||||
let showTimeoutToast = true;
|
||||
/**
|
||||
* 是否已经处在登出过程中了 一个标志位
|
||||
* 主要是防止一个页面会请求多个api 都401 会导致登出执行多次
|
||||
*/
|
||||
let isLogoutProcessing = false;
|
||||
|
||||
function createRequestClient(baseURL: string) {
|
||||
const client = new RequestClient({
|
||||
@@ -45,7 +48,7 @@ function createRequestClient(baseURL: string) {
|
||||
errorMessageMode: 'message',
|
||||
// 格式化提交参数时间
|
||||
formatDate: true,
|
||||
// 是否返回原生响应头 比如:需要获取响应头时使用该属性
|
||||
// 是否返回原生响应 比如:需要获取响应头时使用该属性
|
||||
isReturnNativeResponse: false,
|
||||
// 需要对返回数据进行处理
|
||||
isTransformResponse: true,
|
||||
@@ -96,11 +99,13 @@ function createRequestClient(baseURL: string) {
|
||||
*/
|
||||
const language = preferences.app.locale.replace('-', '_');
|
||||
config.headers['Accept-Language'] = language;
|
||||
// 添加全局clientId
|
||||
config.headers.clientId = clientId;
|
||||
|
||||
const { encrypt, formatDate, joinParamsToUrl, joinTime = true } = config;
|
||||
const params = config.params || {};
|
||||
const data = config.data || false;
|
||||
// TODO: 这块要重构 复杂度太高了
|
||||
formatDate && data && !isString(data) && formatRequestDate(data);
|
||||
if (config.method?.toUpperCase() === 'GET') {
|
||||
if (isString(params)) {
|
||||
@@ -142,7 +147,7 @@ function createRequestClient(baseURL: string) {
|
||||
}
|
||||
}
|
||||
}
|
||||
// 全局开启 && 该请求开启 && 是post/put请求
|
||||
// 全局开启请求加密功能 && 该请求开启 && 是post/put请求
|
||||
if (
|
||||
enableEncrypt &&
|
||||
encrypt &&
|
||||
@@ -186,7 +191,7 @@ function createRequestClient(baseURL: string) {
|
||||
}
|
||||
|
||||
const { isReturnNativeResponse, isTransformResponse } = response.config;
|
||||
// 是否返回原生响应头 比如:需要获取响应头时使用该属性
|
||||
// 是否返回原生响应 比如:需要获取响应时使用该属性
|
||||
if (isReturnNativeResponse) {
|
||||
return response;
|
||||
}
|
||||
@@ -221,7 +226,6 @@ function createRequestClient(baseURL: string) {
|
||||
} else if (response.config.successMessageMode === 'message') {
|
||||
message.success(successMsg);
|
||||
}
|
||||
// ruoyi-plus没有采用严格的{code, msg, data}模式
|
||||
// 如果有data 直接返回data 没有data将剩余参数(...other)封装为data返回
|
||||
// 需要考虑data为null的情况(比如查询为空)
|
||||
if (data !== undefined) {
|
||||
@@ -234,18 +238,16 @@ function createRequestClient(baseURL: string) {
|
||||
let timeoutMsg = '';
|
||||
switch (code) {
|
||||
case 401: {
|
||||
// 已经在登出过程中 不再执行
|
||||
if (isLogoutProcessing) {
|
||||
return;
|
||||
}
|
||||
isLogoutProcessing = true;
|
||||
const _msg = '登录超时, 请重新登录';
|
||||
const userStore = useAuthStore();
|
||||
userStore.logout().then(() => {
|
||||
/** 只弹窗一次 */
|
||||
if (showTimeoutToast) {
|
||||
showTimeoutToast = false;
|
||||
message.error(_msg);
|
||||
/** 定时器 3s后再开启弹窗 */
|
||||
setTimeout(() => {
|
||||
showTimeoutToast = true;
|
||||
}, 3000);
|
||||
}
|
||||
userStore.logout().finally(() => {
|
||||
message.error(_msg);
|
||||
isLogoutProcessing = false;
|
||||
});
|
||||
// 不再执行下面逻辑
|
||||
return;
|
||||
|
@@ -37,5 +37,5 @@ export function clientChangeStatus(data: any) {
|
||||
}
|
||||
|
||||
export function clientRemove(ids: IDS) {
|
||||
return requestClient.deleteWithMsg(`${Api.root}/${ids.join(',')}`);
|
||||
return requestClient.deleteWithMsg(`${Api.root}/${ids}`);
|
||||
}
|
||||
|
@@ -44,7 +44,7 @@ export function dictDataExport(data: any) {
|
||||
* @returns void
|
||||
*/
|
||||
export function dictDataRemove(dictIds: IDS) {
|
||||
return requestClient.deleteWithMsg<void>(`${Api.root}/${dictIds.join(',')}`);
|
||||
return requestClient.deleteWithMsg<void>(`${Api.root}/${dictIds}`);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@@ -37,7 +37,7 @@ export function dictTypeExport(data: any) {
|
||||
* @returns void
|
||||
*/
|
||||
export function dictTypeRemove(dictIds: IDS) {
|
||||
return requestClient.deleteWithMsg<void>(`${Api.root}/${dictIds.join(',')}`);
|
||||
return requestClient.deleteWithMsg<void>(`${Api.root}/${dictIds}`);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@@ -8,10 +8,14 @@ import '@vben/styles/antd';
|
||||
import { setupGlobalComponent } from '#/components/global';
|
||||
import { setupI18n } from '#/locales';
|
||||
|
||||
import { initComponentAdapter } from './adapter/component';
|
||||
import App from './app.vue';
|
||||
import { router } from './router';
|
||||
|
||||
async function bootstrap(namespace: string) {
|
||||
// 初始化组件适配器
|
||||
await initComponentAdapter();
|
||||
|
||||
const app = createApp(App);
|
||||
|
||||
// 全局组件
|
||||
|
@@ -41,11 +41,14 @@ const label = computed<number | string>(() => {
|
||||
const current = props.dicts.find((item) => item.dictValue == props.value);
|
||||
return current?.dictLabel ?? 'unknown';
|
||||
});
|
||||
|
||||
const tagComponent = computed(() => (color.value ? Tag : 'div'));
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<Tag v-if="color" :class="cssClass" :color="color">{{ label }}</Tag>
|
||||
<div v-if="!color" :class="cssClass">{{ label }}</div>
|
||||
<component :is="tagComponent" :class="cssClass" :color="color">
|
||||
{{ label }}
|
||||
</component>
|
||||
</div>
|
||||
</template>
|
||||
|
@@ -75,7 +75,6 @@ const onSelected: SelectHandler = async (tenantId: string, option: any) => {
|
||||
|
||||
async function onDeselect() {
|
||||
await tenantDynamicClear();
|
||||
dictStore.resetCache();
|
||||
message.success($t('component.tenantToggle.reset'));
|
||||
lastSelected.value = '';
|
||||
close(false);
|
||||
|
@@ -74,24 +74,27 @@ const checkedRealKeys = ref<(number | string)[]>([]);
|
||||
* 取第一次的menuTree id 设置到checkedMenuKeys
|
||||
* 主要为了解决没有任何修改 直接点击保存的情况
|
||||
*/
|
||||
const stop = watch(
|
||||
() => props.treeData,
|
||||
() => {
|
||||
/** 节点关联情况下是不带父节点的 */
|
||||
if (props.checkStrictly) {
|
||||
/** 找到父节点 添加上 */
|
||||
const parentIds = findGroupParentIds(
|
||||
props.treeData,
|
||||
checkedKeys.value as any,
|
||||
);
|
||||
checkedRealKeys.value = [...parentIds, ...checkedKeys.value];
|
||||
} else {
|
||||
/** 节点独立 这里是全部的节点 */
|
||||
checkedRealKeys.value = checkedKeys.value;
|
||||
}
|
||||
const stop = watch([checkedKeys, () => props.treeData], () => {
|
||||
if (
|
||||
props.checkStrictly &&
|
||||
checkedKeys.value.length > 0 &&
|
||||
props.treeData.length > 0
|
||||
) {
|
||||
/** 找到父节点 添加上 */
|
||||
const parentIds = findGroupParentIds(
|
||||
props.treeData,
|
||||
checkedKeys.value as any,
|
||||
{ id: props.fieldNames.key },
|
||||
);
|
||||
checkedRealKeys.value = [...parentIds, ...checkedKeys.value];
|
||||
stop();
|
||||
},
|
||||
);
|
||||
}
|
||||
if (!props.checkStrictly && checkedKeys.value.length > 0) {
|
||||
/** 节点独立 这里是全部的节点 */
|
||||
checkedRealKeys.value = checkedKeys.value;
|
||||
stop();
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
*
|
||||
|
@@ -16,7 +16,7 @@ import { checkFileType } from './helper';
|
||||
import { UploadResultStatus } from './typing';
|
||||
import { useUploadType } from './use-upload';
|
||||
|
||||
defineOptions({ name: 'FileUpload' });
|
||||
defineOptions({ name: 'FileUpload', inheritAttrs: false });
|
||||
|
||||
const props = withDefaults(
|
||||
defineProps<{
|
||||
@@ -95,7 +95,6 @@ watch(
|
||||
return null;
|
||||
}) as UploadProps['fileList'];
|
||||
}
|
||||
emit('update:value', value);
|
||||
if (!isFirstRender.value) {
|
||||
emit('change', value);
|
||||
isFirstRender.value = false;
|
||||
@@ -119,9 +118,9 @@ const handleRemove = async (file: UploadFile) => {
|
||||
}
|
||||
};
|
||||
|
||||
const beforeUpload = (file: File) => {
|
||||
const beforeUpload = async (file: File) => {
|
||||
const { maxSize, accept } = props;
|
||||
const isAct = checkFileType(file, accept);
|
||||
const isAct = await checkFileType(file, accept);
|
||||
if (!isAct) {
|
||||
message.error($t('component.upload.acceptUpload', [accept]));
|
||||
isActMsg.value = false;
|
||||
@@ -169,6 +168,10 @@ function getValue() {
|
||||
if (item?.response && props?.resultField) {
|
||||
return item?.response?.[props.resultField];
|
||||
}
|
||||
// 适用于已经有图片 回显的情况 会默认在init处理为{url: 'xx'}
|
||||
if (item?.url) {
|
||||
return item.url;
|
||||
}
|
||||
// 注意这里取的key为 url
|
||||
return item?.response?.url;
|
||||
});
|
||||
|
@@ -1,33 +1,51 @@
|
||||
export function checkFileType(file: File, accepts: string[]) {
|
||||
console.log(file.name, accepts);
|
||||
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');
|
||||
import { fileTypeFromBlob } from '@vben/utils';
|
||||
|
||||
/**
|
||||
* 不支持txt文件 @see https://github.com/sindresorhus/file-type/issues/55
|
||||
* 需要自行修改
|
||||
* @param file file对象
|
||||
* @param accepts 文件类型数组 包括拓展名(不带点) 文件头(image/png等 不包括泛写法即image/*)
|
||||
* @returns 是否通过文件类型校验
|
||||
*/
|
||||
export async function checkFileType(file: File, accepts: string[]) {
|
||||
if (!accepts || accepts?.length === 0) {
|
||||
return true;
|
||||
}
|
||||
return reg.test(file.name);
|
||||
console.log(file);
|
||||
const fileType = await fileTypeFromBlob(file);
|
||||
if (!fileType) {
|
||||
console.error('无法获取文件类型');
|
||||
return false;
|
||||
}
|
||||
console.log('文件类型', fileType);
|
||||
// 是否文件拓展名/文件头任意有一个匹配
|
||||
return accepts.includes(fileType.ext) || accepts.includes(fileType.mime);
|
||||
}
|
||||
|
||||
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));
|
||||
});
|
||||
/**
|
||||
* 默认图片类型
|
||||
*/
|
||||
export const defaultImageAccept = ['jpg', 'jpeg', 'png', 'gif', 'webp'];
|
||||
/**
|
||||
* 判断文件类型是否符合要求
|
||||
* @param file file对象
|
||||
* @param accepts 文件类型数组 包括拓展名(不带点) 文件头(image/png等 不包括泛写法即image/*)
|
||||
* @returns 是否通过文件类型校验
|
||||
*/
|
||||
export async function checkImageFileType(file: File, accepts: string[]) {
|
||||
// 空的accepts 使用默认规则
|
||||
if (!accepts || accepts.length === 0) {
|
||||
accepts = defaultImageAccept;
|
||||
}
|
||||
const fileType = await fileTypeFromBlob(file);
|
||||
if (!fileType) {
|
||||
console.error('无法获取文件类型');
|
||||
return false;
|
||||
}
|
||||
console.log('文件类型', fileType);
|
||||
// 是否文件拓展名/文件头任意有一个匹配
|
||||
if (accepts.includes(fileType.ext) || accepts.includes(fileType.mime)) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
@@ -12,18 +12,16 @@ import { isArray, isFunction, isObject, isString } from 'lodash-es';
|
||||
|
||||
import { uploadApi } from '#/api';
|
||||
|
||||
import { checkFileType } from './helper';
|
||||
import { checkImageFileType, defaultImageAccept } from './helper';
|
||||
import { UploadResultStatus } from './typing';
|
||||
import { useUploadType } from './use-upload';
|
||||
|
||||
defineOptions({ name: 'ImageUpload' });
|
||||
defineOptions({ name: 'ImageUpload', inheritAttrs: false });
|
||||
|
||||
const props = withDefaults(
|
||||
defineProps<{
|
||||
/**
|
||||
* 建议使用拓展名(不带.)
|
||||
* 或者文件头 image/png等(测试判断不准确) 不支持image/*类似的写法
|
||||
* 需自行改造 ./helper/checkFileType方法
|
||||
* 包括拓展名(不带点) 文件头(image/png等 不包括泛写法即image/*)
|
||||
*/
|
||||
accept?: string[];
|
||||
api?: (...args: any[]) => Promise<any>;
|
||||
@@ -40,7 +38,7 @@ const props = withDefaults(
|
||||
// support xxx.xxx.xx
|
||||
// 返回的字段 默认url
|
||||
resultField?: 'fileName' | 'ossId' | 'url' | string;
|
||||
value?: string[];
|
||||
value?: string | string[];
|
||||
}>(),
|
||||
{
|
||||
value: () => [],
|
||||
@@ -49,7 +47,7 @@ const props = withDefaults(
|
||||
helpText: '',
|
||||
maxSize: 2,
|
||||
maxNumber: 1,
|
||||
accept: () => [],
|
||||
accept: () => defaultImageAccept,
|
||||
multiple: false,
|
||||
api: uploadApi,
|
||||
resultField: '',
|
||||
@@ -81,14 +79,18 @@ watch(
|
||||
isInnerOperate.value = false;
|
||||
return;
|
||||
}
|
||||
let value: string[] = [];
|
||||
let value: string | string[] = [];
|
||||
if (v) {
|
||||
if (isArray(v)) {
|
||||
value = v;
|
||||
} else {
|
||||
value.push(v);
|
||||
const _fileList: string[] = [];
|
||||
if (isString(v)) {
|
||||
_fileList.push(v);
|
||||
}
|
||||
fileList.value = value.map((item, i) => {
|
||||
if (isArray(v)) {
|
||||
_fileList.push(...v);
|
||||
}
|
||||
// 直接赋值 可能为string | string[]
|
||||
value = v;
|
||||
fileList.value = _fileList.map((item, i) => {
|
||||
if (item && isString(item)) {
|
||||
return {
|
||||
uid: `${-i}`,
|
||||
@@ -102,7 +104,6 @@ watch(
|
||||
return null;
|
||||
}) as UploadProps['fileList'];
|
||||
}
|
||||
emit('update:value', value);
|
||||
if (!isFirstRender.value) {
|
||||
emit('change', value);
|
||||
isFirstRender.value = false;
|
||||
@@ -155,9 +156,9 @@ const handleCancel = () => {
|
||||
previewTitle.value = '';
|
||||
};
|
||||
|
||||
const beforeUpload = (file: File) => {
|
||||
const beforeUpload = async (file: File) => {
|
||||
const { maxSize, accept } = props;
|
||||
const isAct = checkFileType(file, accept);
|
||||
const isAct = await checkImageFileType(file, accept);
|
||||
if (!isAct) {
|
||||
message.error($t('component.upload.acceptUpload', [accept]));
|
||||
isActMsg.value = false;
|
||||
@@ -205,9 +206,21 @@ function getValue() {
|
||||
if (item?.response && props?.resultField) {
|
||||
return item?.response?.[props.resultField];
|
||||
}
|
||||
// 适用于已经有图片 回显的情况 会默认在init处理为{url: 'xx'}
|
||||
if (item?.url) {
|
||||
return item.url;
|
||||
}
|
||||
// 注意这里取的key为 url
|
||||
return item?.response?.url;
|
||||
});
|
||||
// 只有一张图片 默认绑定string而非string[]
|
||||
if (props.maxNumber === 1 && list.length === 1) {
|
||||
return list[0];
|
||||
}
|
||||
// 只有一张图片 && 删除图片时 可自行修改
|
||||
if (props.maxNumber === 1 && list.length === 0) {
|
||||
return '';
|
||||
}
|
||||
return list;
|
||||
}
|
||||
</script>
|
||||
|
@@ -41,14 +41,14 @@ const menus = computed(() => {
|
||||
});
|
||||
},
|
||||
icon: BookOpenText,
|
||||
text: $t('widgets.document'),
|
||||
text: $t('ui.widgets.document'),
|
||||
},
|
||||
{
|
||||
handler: () => {
|
||||
router.push('/profile');
|
||||
},
|
||||
icon: ProfileIcon,
|
||||
text: $t('widgets.profile'),
|
||||
text: $t('ui.widgets.profile'),
|
||||
},
|
||||
{
|
||||
handler: () => {
|
||||
@@ -66,7 +66,7 @@ const menus = computed(() => {
|
||||
});
|
||||
},
|
||||
icon: CircleHelp,
|
||||
text: $t('widgets.qa'),
|
||||
text: $t('ui.widgets.qa'),
|
||||
},
|
||||
];
|
||||
/**
|
||||
|
@@ -4,7 +4,11 @@ import type { Locale } from 'ant-design-vue/es/locale';
|
||||
import type { App } from 'vue';
|
||||
import { ref } from 'vue';
|
||||
|
||||
import { $t, setupI18n as coreSetup, loadLocalesMap } from '@vben/locales';
|
||||
import {
|
||||
$t,
|
||||
setupI18n as coreSetup,
|
||||
loadLocalesMapFromDir,
|
||||
} from '@vben/locales';
|
||||
import { preferences } from '@vben/preferences';
|
||||
|
||||
import antdEnLocale from 'ant-design-vue/es/locale/en_US';
|
||||
@@ -13,10 +17,12 @@ import dayjs from 'dayjs';
|
||||
|
||||
const antdLocale = ref<Locale>(antdDefaultLocale);
|
||||
|
||||
const modules = import.meta.glob('./langs/*.json');
|
||||
|
||||
const localesMap = loadLocalesMap(modules);
|
||||
const modules = import.meta.glob('./langs/**/*.json');
|
||||
|
||||
const localesMap = loadLocalesMapFromDir(
|
||||
/\.\/langs\/([^/]+)\/(.*)\.json$/,
|
||||
modules,
|
||||
);
|
||||
/**
|
||||
* 加载应用特有的语言包
|
||||
* 这里也可以改造为从服务端获取翻译数据
|
||||
@@ -45,14 +51,14 @@ async function loadThirdPartyMessage(lang: SupportedLanguagesType) {
|
||||
async function loadDayjsLocale(lang: SupportedLanguagesType) {
|
||||
let locale;
|
||||
switch (lang) {
|
||||
case 'zh-CN': {
|
||||
locale = await import('dayjs/locale/zh-cn');
|
||||
break;
|
||||
}
|
||||
case 'en-US': {
|
||||
locale = await import('dayjs/locale/en');
|
||||
break;
|
||||
}
|
||||
case 'zh-CN': {
|
||||
locale = await import('dayjs/locale/zh-cn');
|
||||
break;
|
||||
}
|
||||
// 默认使用英语
|
||||
default: {
|
||||
locale = await import('dayjs/locale/en');
|
||||
@@ -71,14 +77,14 @@ async function loadDayjsLocale(lang: SupportedLanguagesType) {
|
||||
*/
|
||||
async function loadAntdLocale(lang: SupportedLanguagesType) {
|
||||
switch (lang) {
|
||||
case 'zh-CN': {
|
||||
antdLocale.value = antdDefaultLocale;
|
||||
break;
|
||||
}
|
||||
case 'en-US': {
|
||||
antdLocale.value = antdEnLocale;
|
||||
break;
|
||||
}
|
||||
case 'zh-CN': {
|
||||
antdLocale.value = antdDefaultLocale;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -1,86 +0,0 @@
|
||||
{
|
||||
"page": {
|
||||
"demos": {
|
||||
"title": "Demos",
|
||||
"antd": "Ant Design Vue"
|
||||
}
|
||||
},
|
||||
"component": {
|
||||
"cropper": {
|
||||
"selectImage": "Select Image",
|
||||
"uploadSuccess": "Uploaded success!",
|
||||
"imageTooBig": "Image too big",
|
||||
"modalTitle": "Avatar upload",
|
||||
"okText": "Confirm and upload",
|
||||
"btn_reset": "Reset",
|
||||
"btn_rotate_left": "Counterclockwise rotation",
|
||||
"btn_rotate_right": "Clockwise rotation",
|
||||
"btn_scale_x": "Flip horizontal",
|
||||
"btn_scale_y": "Flip vertical",
|
||||
"btn_zoom_in": "Zoom in",
|
||||
"btn_zoom_out": "Zoom out",
|
||||
"preview": "Preview"
|
||||
},
|
||||
"tenantToggle": {
|
||||
"placeholder": "Please select a tenant",
|
||||
"switch": "Switch to tenant: ",
|
||||
"reset": "Reset to default tenant"
|
||||
},
|
||||
"notice": {
|
||||
"title": "Notice",
|
||||
"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": {
|
||||
"common": {
|
||||
"add": "Add",
|
||||
"edit": "Edit",
|
||||
"delete": "Delete",
|
||||
"more": "More",
|
||||
"search": "Search",
|
||||
"reset": "Reset",
|
||||
"import": "Import",
|
||||
"export": "Export",
|
||||
"expand": "Expand",
|
||||
"collapse": "Collapse",
|
||||
"info": "Info",
|
||||
"clear": "Clear",
|
||||
"unlock": "Unlock",
|
||||
"download": "Download",
|
||||
"sync": "Sync",
|
||||
"refresh": "Refresh",
|
||||
"generate": "Generate",
|
||||
"downloadLoading": "Downloading... Please wait.",
|
||||
"preview": "Preview"
|
||||
}
|
||||
}
|
||||
}
|
55
apps/web-antd/src/locales/langs/en-US/component.json
Normal file
55
apps/web-antd/src/locales/langs/en-US/component.json
Normal file
@@ -0,0 +1,55 @@
|
||||
{
|
||||
"cropper": {
|
||||
"selectImage": "Select Image",
|
||||
"uploadSuccess": "Uploaded success!",
|
||||
"imageTooBig": "Image too big",
|
||||
"modalTitle": "Avatar upload",
|
||||
"okText": "Confirm and upload",
|
||||
"btn_reset": "Reset",
|
||||
"btn_rotate_left": "Counterclockwise rotation",
|
||||
"btn_rotate_right": "Clockwise rotation",
|
||||
"btn_scale_x": "Flip horizontal",
|
||||
"btn_scale_y": "Flip vertical",
|
||||
"btn_zoom_in": "Zoom in",
|
||||
"btn_zoom_out": "Zoom out",
|
||||
"preview": "Preview"
|
||||
},
|
||||
"tenantToggle": {
|
||||
"placeholder": "Please select a tenant",
|
||||
"switch": "Switch to tenant: ",
|
||||
"reset": "Reset to default tenant"
|
||||
},
|
||||
"notice": {
|
||||
"title": "Notice",
|
||||
"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"
|
||||
}
|
||||
}
|
12
apps/web-antd/src/locales/langs/en-US/demos.json
Normal file
12
apps/web-antd/src/locales/langs/en-US/demos.json
Normal file
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"title": "Demos",
|
||||
"antd": "Ant Design Vue",
|
||||
"vben": {
|
||||
"title": "Project",
|
||||
"about": "About",
|
||||
"document": "Document",
|
||||
"antdv": "Ant Design Vue Version",
|
||||
"naive-ui": "Naive UI Version",
|
||||
"element-plus": "Element Plus Version"
|
||||
}
|
||||
}
|
55
apps/web-antd/src/locales/langs/en-US/menu.json
Normal file
55
apps/web-antd/src/locales/langs/en-US/menu.json
Normal file
@@ -0,0 +1,55 @@
|
||||
{
|
||||
"root": "Root",
|
||||
"system": {
|
||||
"root": "System",
|
||||
"user": "User",
|
||||
"role": "Role",
|
||||
"menu": "Menu",
|
||||
"dept": "Department",
|
||||
"post": "Post",
|
||||
"dict": "Dictionary",
|
||||
"config": "Parameter Settings",
|
||||
"notice": "Notifications",
|
||||
"log": {
|
||||
"root": "Log",
|
||||
"operation": "Operation Log",
|
||||
"login": "Login Log"
|
||||
},
|
||||
"oss": "File",
|
||||
"client": "Client"
|
||||
},
|
||||
"tenant": {
|
||||
"root": "Tenant",
|
||||
"package": "Package"
|
||||
},
|
||||
"monitor": {
|
||||
"root": "System Monitoring",
|
||||
"online": "Online Users",
|
||||
"cache": "Cache Monitoring",
|
||||
"admin": "Admin Monitoring",
|
||||
"job": "Task Scheduling Center"
|
||||
},
|
||||
"tool": {
|
||||
"root": "System Tools",
|
||||
"gen": "Code Generation"
|
||||
},
|
||||
"workflow": {
|
||||
"root": "Workflow",
|
||||
"category": "Process Category",
|
||||
"model": "Model",
|
||||
"define": "Process Definition",
|
||||
"monitor": {
|
||||
"root": "Process Monitoring",
|
||||
"instance": "Process Instance",
|
||||
"todo": "Pending Tasks"
|
||||
},
|
||||
"form": "Form"
|
||||
},
|
||||
"task": {
|
||||
"root": "My Tasks",
|
||||
"apply": "My Initiated Tasks",
|
||||
"todo": "My Pending Tasks",
|
||||
"done": "My Completed Tasks",
|
||||
"cc": "My CC"
|
||||
}
|
||||
}
|
15
apps/web-antd/src/locales/langs/en-US/page.json
Normal file
15
apps/web-antd/src/locales/langs/en-US/page.json
Normal file
@@ -0,0 +1,15 @@
|
||||
{
|
||||
"auth": {
|
||||
"login": "Login",
|
||||
"register": "Register",
|
||||
"codeLogin": "Code Login",
|
||||
"qrcodeLogin": "Qr Code Login",
|
||||
"forgetPassword": "Forget Password",
|
||||
"oauthLogin": "Oauth Login"
|
||||
},
|
||||
"dashboard": {
|
||||
"title": "Dashboard",
|
||||
"analytics": "Analytics",
|
||||
"workspace": "Workspace"
|
||||
}
|
||||
}
|
23
apps/web-antd/src/locales/langs/en-US/pages.json
Normal file
23
apps/web-antd/src/locales/langs/en-US/pages.json
Normal file
@@ -0,0 +1,23 @@
|
||||
{
|
||||
"common": {
|
||||
"add": "Add",
|
||||
"edit": "Edit",
|
||||
"delete": "Delete",
|
||||
"more": "More",
|
||||
"search": "Search",
|
||||
"reset": "Reset",
|
||||
"import": "Import",
|
||||
"export": "Export",
|
||||
"expand": "Expand",
|
||||
"collapse": "Collapse",
|
||||
"info": "Info",
|
||||
"clear": "Clear",
|
||||
"unlock": "Unlock",
|
||||
"download": "Download",
|
||||
"sync": "Sync",
|
||||
"refresh": "Refresh",
|
||||
"generate": "Generate",
|
||||
"downloadLoading": "Downloading... Please wait.",
|
||||
"preview": "Preview"
|
||||
}
|
||||
}
|
@@ -1,86 +0,0 @@
|
||||
{
|
||||
"page": {
|
||||
"demos": {
|
||||
"title": "演示",
|
||||
"antd": "Ant Design Vue"
|
||||
}
|
||||
},
|
||||
"component": {
|
||||
"cropper": {
|
||||
"selectImage": "选择图片",
|
||||
"uploadSuccess": "上传成功",
|
||||
"imageTooBig": "图片超限",
|
||||
"modalTitle": "头像上传",
|
||||
"okText": "确认并上传",
|
||||
"btn_reset": "重置",
|
||||
"btn_rotate_left": "逆时针旋转",
|
||||
"btn_rotate_right": "顺时针旋转",
|
||||
"btn_scale_x": "水平翻转",
|
||||
"btn_scale_y": "垂直翻转",
|
||||
"btn_zoom_in": "放大",
|
||||
"btn_zoom_out": "缩小",
|
||||
"preview": "预览"
|
||||
},
|
||||
"tenantToggle": {
|
||||
"placeholder": "选择租户",
|
||||
"switch": "切换当前租户为: ",
|
||||
"reset": "还原为默认租户"
|
||||
},
|
||||
"notice": {
|
||||
"title": "消息",
|
||||
"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": {
|
||||
"common": {
|
||||
"add": "新增",
|
||||
"edit": "编辑",
|
||||
"delete": "删除",
|
||||
"more": "更多",
|
||||
"search": "搜索",
|
||||
"reset": "重置",
|
||||
"import": "导入",
|
||||
"export": "导出",
|
||||
"expand": "展开",
|
||||
"collapse": "收起",
|
||||
"info": "详情",
|
||||
"clear": "清空",
|
||||
"unlock": "解锁",
|
||||
"download": "下载",
|
||||
"sync": "同步",
|
||||
"refresh": "刷新",
|
||||
"generate": "生成",
|
||||
"downloadLoading": "下载中, 请稍后...",
|
||||
"preview": "预览"
|
||||
}
|
||||
}
|
||||
}
|
55
apps/web-antd/src/locales/langs/zh-CN/component.json
Normal file
55
apps/web-antd/src/locales/langs/zh-CN/component.json
Normal file
@@ -0,0 +1,55 @@
|
||||
{
|
||||
"cropper": {
|
||||
"selectImage": "选择图片",
|
||||
"uploadSuccess": "上传成功",
|
||||
"imageTooBig": "图片超限",
|
||||
"modalTitle": "头像上传",
|
||||
"okText": "确认并上传",
|
||||
"btn_reset": "重置",
|
||||
"btn_rotate_left": "逆时针旋转",
|
||||
"btn_rotate_right": "顺时针旋转",
|
||||
"btn_scale_x": "水平翻转",
|
||||
"btn_scale_y": "垂直翻转",
|
||||
"btn_zoom_in": "放大",
|
||||
"btn_zoom_out": "缩小",
|
||||
"preview": "预览"
|
||||
},
|
||||
"tenantToggle": {
|
||||
"placeholder": "选择租户",
|
||||
"switch": "切换当前租户为: ",
|
||||
"reset": "还原为默认租户"
|
||||
},
|
||||
"notice": {
|
||||
"title": "消息",
|
||||
"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": "重新上传失败文件"
|
||||
}
|
||||
}
|
12
apps/web-antd/src/locales/langs/zh-CN/demos.json
Normal file
12
apps/web-antd/src/locales/langs/zh-CN/demos.json
Normal file
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"title": "演示",
|
||||
"antd": "Ant Design Vue",
|
||||
"vben": {
|
||||
"title": "项目",
|
||||
"about": "关于",
|
||||
"document": "文档",
|
||||
"antdv": "Ant Design Vue 版本",
|
||||
"naive-ui": "Naive UI 版本",
|
||||
"element-plus": "Element Plus 版本"
|
||||
}
|
||||
}
|
55
apps/web-antd/src/locales/langs/zh-CN/menu.json
Normal file
55
apps/web-antd/src/locales/langs/zh-CN/menu.json
Normal file
@@ -0,0 +1,55 @@
|
||||
{
|
||||
"root": "根目录",
|
||||
"system": {
|
||||
"root": "系统管理",
|
||||
"user": "用户管理",
|
||||
"role": "角色管理",
|
||||
"menu": "菜单管理",
|
||||
"dept": "部门管理",
|
||||
"post": "岗位管理",
|
||||
"dict": "字典管理",
|
||||
"config": "参数设置",
|
||||
"notice": "通知公告",
|
||||
"log": {
|
||||
"root": "日志管理",
|
||||
"operation": "操作日志",
|
||||
"login": "登录日志"
|
||||
},
|
||||
"oss": "文件管理",
|
||||
"client": "客户端管理"
|
||||
},
|
||||
"tenant": {
|
||||
"root": "租户管理",
|
||||
"package": "套餐管理"
|
||||
},
|
||||
"monitor": {
|
||||
"root": "系统监控",
|
||||
"online": "在线用户",
|
||||
"cache": "缓存监控",
|
||||
"admin": "Admin监控",
|
||||
"job": "任务调度中心"
|
||||
},
|
||||
"tool": {
|
||||
"root": "系统工具",
|
||||
"gen": "代码生成"
|
||||
},
|
||||
"workflow": {
|
||||
"root": "工作流",
|
||||
"category": "流程分类",
|
||||
"model": "模型管理",
|
||||
"define": "流程定义",
|
||||
"monitor": {
|
||||
"root": "流程监控",
|
||||
"instance": "流程实例",
|
||||
"todo": "待办任务"
|
||||
},
|
||||
"form": "表单管理"
|
||||
},
|
||||
"task": {
|
||||
"root": "我的任务",
|
||||
"apply": "我发起的",
|
||||
"todo": "我的待办",
|
||||
"done": "我的已办",
|
||||
"cc": "我的抄送"
|
||||
}
|
||||
}
|
15
apps/web-antd/src/locales/langs/zh-CN/page.json
Normal file
15
apps/web-antd/src/locales/langs/zh-CN/page.json
Normal file
@@ -0,0 +1,15 @@
|
||||
{
|
||||
"auth": {
|
||||
"login": "登录",
|
||||
"register": "注册",
|
||||
"codeLogin": "验证码登录",
|
||||
"qrcodeLogin": "二维码登录",
|
||||
"forgetPassword": "忘记密码",
|
||||
"oauthLogin": "第三方登录"
|
||||
},
|
||||
"dashboard": {
|
||||
"title": "概览",
|
||||
"analytics": "分析页",
|
||||
"workspace": "工作台"
|
||||
}
|
||||
}
|
23
apps/web-antd/src/locales/langs/zh-CN/pages.json
Normal file
23
apps/web-antd/src/locales/langs/zh-CN/pages.json
Normal file
@@ -0,0 +1,23 @@
|
||||
{
|
||||
"common": {
|
||||
"add": "新增",
|
||||
"edit": "编辑",
|
||||
"delete": "删除",
|
||||
"more": "更多",
|
||||
"search": "搜索",
|
||||
"reset": "重置",
|
||||
"import": "导入",
|
||||
"export": "导出",
|
||||
"expand": "展开",
|
||||
"collapse": "收起",
|
||||
"info": "详情",
|
||||
"clear": "清空",
|
||||
"unlock": "解锁",
|
||||
"download": "下载",
|
||||
"sync": "同步",
|
||||
"refresh": "刷新",
|
||||
"generate": "生成",
|
||||
"downloadLoading": "下载中, 请稍后...",
|
||||
"preview": "预览"
|
||||
}
|
||||
}
|
@@ -20,6 +20,12 @@ export const overridesPreferences = defineOverridesPreferences({
|
||||
*/
|
||||
// defaultAvatar: '',
|
||||
name: import.meta.env.VITE_APP_TITLE,
|
||||
/**
|
||||
* 不支持modal模式 需要改动的地方太多
|
||||
* 1. 正常重新登录后不会再触发接口请求 即触发登录超时的页面为空数据
|
||||
* 2. 切换租户登录后不会重新加载菜单
|
||||
*/
|
||||
// loginExpiredMode: 'modal',
|
||||
},
|
||||
footer: {
|
||||
/**
|
||||
|
@@ -19,6 +19,170 @@ import { localMenuList } from './routes/local';
|
||||
const forbiddenComponent = () => import('#/views/_core/fallback/forbidden.vue');
|
||||
const NotFoundComponent = () => import('#/views/_core/fallback/not-found.vue');
|
||||
|
||||
/**
|
||||
* 后台路由转vben路由
|
||||
* @param menuList 后台菜单
|
||||
* @param parentPath 上级目录
|
||||
* @returns vben路由
|
||||
*/
|
||||
function backMenuToVbenMenu(
|
||||
menuList: Menu[],
|
||||
parentPath = '',
|
||||
): RouteRecordStringComponent[] {
|
||||
const resultList: RouteRecordStringComponent[] = [];
|
||||
menuList.forEach((menu) => {
|
||||
// 根目录为菜单形式
|
||||
// 固定有一个children children为当前菜单
|
||||
if (menu.path === '/' && menu.children && menu.children.length === 1) {
|
||||
if (!menu.children || !menu.children[0]) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 需要处理根目录为内嵌的情况 不会带InnerLink
|
||||
if (/^https?:\/\//.test(menu.children[0].path)) {
|
||||
menu.children[0].component = 'InnerLink';
|
||||
menu.children[0].path = menu.children[0].path
|
||||
.replaceAll(/^https?:\/\//g, '')
|
||||
.replaceAll('/#/', '')
|
||||
.replaceAll('#', '')
|
||||
.replaceAll(/[?&]/g, '');
|
||||
}
|
||||
|
||||
// 取子路径作为父级路径
|
||||
const path = menu.children[0].path;
|
||||
// 取子菜单的meta作为当前菜单的meta
|
||||
menu.meta = menu.children[0].meta;
|
||||
// 由于在一级路由 父级路径需要加上/
|
||||
menu.path = `/${path}`;
|
||||
menu.component = 'RootMenu';
|
||||
// 将子路径设置为''
|
||||
menu.children[0].path = '';
|
||||
}
|
||||
|
||||
// 外链: http开头 & 组件为Layout || ParentView
|
||||
// 正则判断是否为http://或者https://开头
|
||||
if (
|
||||
/^https?:\/\//.test(menu.path) &&
|
||||
(menu.component === 'Layout' || menu.component === 'ParentView')
|
||||
) {
|
||||
menu.component = 'Link';
|
||||
}
|
||||
|
||||
// 内嵌iframe 组件为InnerLink
|
||||
if (menu.meta?.link && menu.component === 'InnerLink') {
|
||||
menu.component = 'IFrameView';
|
||||
}
|
||||
|
||||
/**
|
||||
* 拼接path
|
||||
* menu.path为''(根目录路由) 则不拼接
|
||||
*/
|
||||
if (parentPath && menu.path) {
|
||||
menu.path = `${parentPath}/${menu.path}`;
|
||||
}
|
||||
|
||||
// 创建vben路由对象
|
||||
const vbenRoute: RouteRecordStringComponent = {
|
||||
component: menu.component,
|
||||
meta: {
|
||||
// 当前路由不在菜单显示 但是可以通过链接访问
|
||||
// 不可访问的路由由后端控制隐藏(不返回对应路由)
|
||||
hideInMenu: menu.hidden,
|
||||
icon: menu.meta?.icon,
|
||||
keepAlive: !menu.meta?.noCache,
|
||||
title: menu.meta?.title,
|
||||
},
|
||||
name: menu.name,
|
||||
path: menu.path,
|
||||
};
|
||||
|
||||
// 添加路由参数信息
|
||||
if (menu.query) {
|
||||
try {
|
||||
const query = JSON.parse(menu.query);
|
||||
vbenRoute.meta && (vbenRoute.meta.query = query);
|
||||
} catch {
|
||||
console.error('错误的路由参数类型, 必须为[json]格式');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理不同组件
|
||||
*/
|
||||
switch (menu.component) {
|
||||
/**
|
||||
* iframe内嵌
|
||||
*/
|
||||
case 'IFrameView': {
|
||||
vbenRoute.component = 'IFrameView';
|
||||
if (vbenRoute.meta) {
|
||||
vbenRoute.meta.iframeSrc = menu.meta.link;
|
||||
}
|
||||
/**
|
||||
* 需要判断特殊情况 比如vue的hash是带#的
|
||||
* 比如链接 aaa.com/#/bbb path会转换为 aaa/com/#/bbb
|
||||
* 比如链接 aaa.com/?bbb=xxx
|
||||
* 需要去除# 否则无法被添加到路由
|
||||
*/
|
||||
vbenRoute.path = vbenRoute.path
|
||||
// 替换https:// 或者 http://
|
||||
.replaceAll(/^https?:\/\//g, '')
|
||||
.replaceAll('/#/', '')
|
||||
.replaceAll('#', '')
|
||||
.replaceAll(/[?&]/g, '');
|
||||
break;
|
||||
}
|
||||
case 'Layout': {
|
||||
vbenRoute.component = 'BasicLayout';
|
||||
break;
|
||||
}
|
||||
/**
|
||||
* 外链 新窗口打开
|
||||
*/
|
||||
case 'Link': {
|
||||
if (vbenRoute.meta) {
|
||||
vbenRoute.meta.link = menu.meta.link;
|
||||
}
|
||||
vbenRoute.component = 'BasicLayout';
|
||||
break;
|
||||
}
|
||||
/**
|
||||
* 三级以上菜单 父级component为ParentView
|
||||
* 不能为layout 会套两层BasicLayout
|
||||
*/
|
||||
case 'ParentView': {
|
||||
vbenRoute.component = '';
|
||||
break;
|
||||
}
|
||||
/**
|
||||
* 根目录菜单
|
||||
*/
|
||||
case 'RootMenu': {
|
||||
if (vbenRoute.meta) {
|
||||
vbenRoute.meta.hideChildrenInMenu = true;
|
||||
}
|
||||
vbenRoute.component = 'BasicLayout';
|
||||
break;
|
||||
}
|
||||
/**
|
||||
* 其他自定义组件 如system/user/index 拼接/
|
||||
*/
|
||||
default: {
|
||||
vbenRoute.component = `/${menu.component}`;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// children处理
|
||||
if (menu.children && menu.children.length > 0) {
|
||||
vbenRoute.children = backMenuToVbenMenu(menu.children, menu.path);
|
||||
}
|
||||
// 添加
|
||||
resultList.push(vbenRoute);
|
||||
});
|
||||
return resultList;
|
||||
}
|
||||
|
||||
async function generateAccess(options: GenerateMenuAndRoutesOptions) {
|
||||
const pageMap: ComponentRecordType = import.meta.glob('../views/**/*.vue');
|
||||
|
||||
@@ -28,146 +192,6 @@ async function generateAccess(options: GenerateMenuAndRoutesOptions) {
|
||||
NotFoundComponent,
|
||||
};
|
||||
|
||||
/**
|
||||
* 后台路由转vben路由
|
||||
*
|
||||
* todo 需要重构
|
||||
* @param menuList 后台菜单
|
||||
* @param parentPath 上级目录
|
||||
* @returns vben路由
|
||||
*/
|
||||
function backMenuToVbenMenu(
|
||||
menuList: Menu[],
|
||||
parentPath = '',
|
||||
): RouteRecordStringComponent[] {
|
||||
const resultList: RouteRecordStringComponent[] = [];
|
||||
menuList.forEach((menu) => {
|
||||
// 根目录为菜单形式
|
||||
// 固定有一个children children为当前菜单
|
||||
if (menu.path === '/' && menu.children && menu.children.length === 1) {
|
||||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
||||
menu.meta = menu.children[0]!.meta;
|
||||
/**
|
||||
* todo 先写死 后续再优化
|
||||
*/
|
||||
menu.path = '/root_menu';
|
||||
menu.component = 'RootMenu';
|
||||
}
|
||||
// 外链: http开头 & 组件为Layout || ParentView
|
||||
// 正则判断是否为http://或者https://开头
|
||||
if (
|
||||
/^https?:\/\//.test(menu.path) &&
|
||||
(menu.component === 'Layout' || menu.component === 'ParentView')
|
||||
) {
|
||||
menu.component = 'Link';
|
||||
}
|
||||
// 内嵌iframe 组件为InnerLink
|
||||
if (menu.meta?.link && menu.component === 'InnerLink') {
|
||||
menu.component = 'IFrameView';
|
||||
}
|
||||
|
||||
// path
|
||||
if (parentPath) {
|
||||
menu.path = `${parentPath}/${menu.path}`;
|
||||
}
|
||||
|
||||
const vbenRoute: RouteRecordStringComponent = {
|
||||
component: menu.component,
|
||||
meta: {
|
||||
// 当前路由不在菜单显示 但是可以通过链接访问
|
||||
// 不可访问的路由由后端控制隐藏(不返回对应路由)
|
||||
hideInMenu: menu.hidden,
|
||||
icon: menu.meta?.icon,
|
||||
keepAlive: !menu.meta?.noCache,
|
||||
title: menu.meta?.title,
|
||||
},
|
||||
name: menu.name,
|
||||
path: menu.path,
|
||||
};
|
||||
|
||||
/**
|
||||
* 处理不同组件
|
||||
*/
|
||||
switch (menu.component) {
|
||||
case 'Layout': {
|
||||
vbenRoute.component = 'BasicLayout';
|
||||
break;
|
||||
}
|
||||
/**
|
||||
* iframe内嵌
|
||||
*/
|
||||
case 'IFrameView': {
|
||||
vbenRoute.component = 'IFrameView';
|
||||
if (vbenRoute.meta) {
|
||||
vbenRoute.meta.iframeSrc = menu.meta.link;
|
||||
}
|
||||
/**
|
||||
* 需要判断特殊情况 比如vue的hash是带#的
|
||||
* 比如链接 aaa.com/#/bbb path会转换为 aaa/com/#/bbb
|
||||
* 比如链接 aaa.com/?bbb=xxx
|
||||
* 需要去除# 否则无法被添加到路由
|
||||
*/
|
||||
/**
|
||||
* todo 不优雅 考虑别的方案
|
||||
*/
|
||||
if (vbenRoute.path.includes('/#/')) {
|
||||
vbenRoute.path = vbenRoute.path.replace('/#/', '');
|
||||
}
|
||||
if (vbenRoute.path.includes('#')) {
|
||||
vbenRoute.path = vbenRoute.path.replace('#', '');
|
||||
}
|
||||
if (vbenRoute.path.includes('?') || vbenRoute.path.includes('&')) {
|
||||
vbenRoute.path = vbenRoute.path.replace('?', '');
|
||||
vbenRoute.path = vbenRoute.path.replace('&', '');
|
||||
}
|
||||
break;
|
||||
}
|
||||
/**
|
||||
* 外链 新窗口打开
|
||||
*/
|
||||
case 'Link': {
|
||||
if (vbenRoute.meta) {
|
||||
vbenRoute.meta.link = menu.meta.link;
|
||||
}
|
||||
vbenRoute.component = 'BasicLayout';
|
||||
break;
|
||||
}
|
||||
/**
|
||||
* 根目录菜单
|
||||
*/
|
||||
case 'RootMenu': {
|
||||
if (vbenRoute.meta) {
|
||||
vbenRoute.meta.hideChildrenInMenu = true;
|
||||
}
|
||||
vbenRoute.component = 'BasicLayout';
|
||||
break;
|
||||
}
|
||||
/**
|
||||
* 不能为layout 会套两层BasicLayout
|
||||
*/
|
||||
case 'ParentView': {
|
||||
vbenRoute.component = '';
|
||||
break;
|
||||
}
|
||||
/**
|
||||
* 其他自定义组件 如system/user/index 拼接/
|
||||
*/
|
||||
default: {
|
||||
vbenRoute.component = `/${menu.component}`;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// children处理
|
||||
if (menu.children && menu.children.length > 0) {
|
||||
vbenRoute.children = backMenuToVbenMenu(menu.children, menu.path);
|
||||
}
|
||||
|
||||
resultList.push(vbenRoute);
|
||||
});
|
||||
return resultList;
|
||||
}
|
||||
|
||||
return await generateAccessible(preferences.app.accessMode, {
|
||||
...options,
|
||||
fetchMenuListAsync: async () => {
|
||||
|
@@ -1,6 +1,6 @@
|
||||
import type { RouteRecordRaw } from 'vue-router';
|
||||
|
||||
import { DEFAULT_HOME_PATH } from '@vben/constants';
|
||||
import { DEFAULT_HOME_PATH, LOGIN_PATH } from '@vben/constants';
|
||||
|
||||
import { AuthPageLayout } from '#/layouts';
|
||||
import { $t } from '#/locales';
|
||||
@@ -32,7 +32,7 @@ const coreRoutes: RouteRecordRaw[] = [
|
||||
{
|
||||
component: () => import('#/views/_core/social-callback/index.vue'),
|
||||
meta: {
|
||||
title: $t('page.core.oauthLogin'),
|
||||
title: $t('page.auth.oauthLogin'),
|
||||
},
|
||||
name: 'OAuthRedirect',
|
||||
path: '/social-callback',
|
||||
@@ -40,17 +40,19 @@ const coreRoutes: RouteRecordRaw[] = [
|
||||
{
|
||||
component: AuthPageLayout,
|
||||
meta: {
|
||||
hideInTab: true,
|
||||
title: 'Authentication',
|
||||
},
|
||||
name: 'Authentication',
|
||||
path: '/auth',
|
||||
redirect: LOGIN_PATH,
|
||||
children: [
|
||||
{
|
||||
name: 'Login',
|
||||
path: 'login',
|
||||
component: Login,
|
||||
meta: {
|
||||
title: $t('page.core.login'),
|
||||
title: $t('page.auth.login'),
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -58,7 +60,7 @@ const coreRoutes: RouteRecordRaw[] = [
|
||||
path: 'code-login',
|
||||
component: () => import('#/views/_core/authentication/code-login.vue'),
|
||||
meta: {
|
||||
title: $t('page.core.codeLogin'),
|
||||
title: $t('page.auth.codeLogin'),
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -67,7 +69,7 @@ const coreRoutes: RouteRecordRaw[] = [
|
||||
component: () =>
|
||||
import('#/views/_core/authentication/qrcode-login.vue'),
|
||||
meta: {
|
||||
title: $t('page.core.qrcodeLogin'),
|
||||
title: $t('page.auth.qrcodeLogin'),
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -76,7 +78,7 @@ const coreRoutes: RouteRecordRaw[] = [
|
||||
component: () =>
|
||||
import('#/views/_core/authentication/forget-password.vue'),
|
||||
meta: {
|
||||
title: $t('page.core.forgetPassword'),
|
||||
title: $t('page.auth.forgetPassword'),
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -84,7 +86,7 @@ const coreRoutes: RouteRecordRaw[] = [
|
||||
path: 'register',
|
||||
component: () => import('#/views/_core/authentication/register.vue'),
|
||||
meta: {
|
||||
title: $t('page.core.register'),
|
||||
title: $t('page.auth.register'),
|
||||
},
|
||||
},
|
||||
],
|
||||
|
@@ -15,7 +15,7 @@ const profileRoute: RouteRecordStringComponent[] = [
|
||||
meta: {
|
||||
hideChildrenInMenu: true,
|
||||
hideInMenu: true,
|
||||
title: $t('widgets.profile'),
|
||||
title: $t('ui.widgets.profile'),
|
||||
},
|
||||
name: 'Profile',
|
||||
path: '/',
|
||||
@@ -26,7 +26,7 @@ const profileRoute: RouteRecordStringComponent[] = [
|
||||
meta: {
|
||||
icon: 'mingcute:profile-line',
|
||||
keepAlive: true,
|
||||
title: $t('widgets.profile'),
|
||||
title: $t('ui.widgets.profile'),
|
||||
},
|
||||
name: 'ProfileIndex',
|
||||
path: '/profile',
|
||||
@@ -147,7 +147,7 @@ export const localMenuList: RouteRecordStringComponent[] = [
|
||||
icon: 'lucide:book-open-text',
|
||||
iframeSrc: 'https://dapdap.top',
|
||||
keepAlive: true,
|
||||
title: $t('page.vben.document'),
|
||||
title: $t('demos.vben.document'),
|
||||
},
|
||||
},
|
||||
],
|
||||
@@ -158,7 +158,7 @@ export const localMenuList: RouteRecordStringComponent[] = [
|
||||
hideChildrenInMenu: true,
|
||||
icon: 'lucide:copyright',
|
||||
order: 9999,
|
||||
title: $t('page.vben.about'),
|
||||
title: $t('demos.vben.about'),
|
||||
},
|
||||
name: 'About',
|
||||
path: '/about',
|
||||
@@ -166,7 +166,7 @@ export const localMenuList: RouteRecordStringComponent[] = [
|
||||
{
|
||||
component: '/_core/about/index',
|
||||
meta: {
|
||||
title: $t('page.vben.about'),
|
||||
title: $t('demos.vben.about'),
|
||||
},
|
||||
name: 'VbenAbout',
|
||||
path: '/vben-admin/about',
|
||||
|
@@ -10,14 +10,14 @@ const routes: RouteRecordRaw[] = [
|
||||
icon: 'ic:baseline-view-in-ar',
|
||||
keepAlive: true,
|
||||
order: 1000,
|
||||
title: $t('page.demos.title'),
|
||||
title: $t('demos.title'),
|
||||
},
|
||||
name: 'Demos',
|
||||
path: '/demos',
|
||||
children: [
|
||||
{
|
||||
meta: {
|
||||
title: $t('page.demos.antd'),
|
||||
title: $t('demos.antd'),
|
||||
},
|
||||
name: 'AntDesignDemos',
|
||||
path: '/demos/ant-design',
|
||||
|
@@ -18,7 +18,7 @@ const routes: RouteRecordRaw[] = [
|
||||
badgeType: 'dot',
|
||||
icon: VBEN_LOGO_URL,
|
||||
order: 9999,
|
||||
title: $t('page.vben.title'),
|
||||
title: $t('demos.vben.title'),
|
||||
},
|
||||
name: 'VbenProject',
|
||||
path: '/vben-admin',
|
||||
@@ -29,7 +29,7 @@ const routes: RouteRecordRaw[] = [
|
||||
component: () => import('#/views/_core/about/index.vue'),
|
||||
meta: {
|
||||
icon: 'lucide:copyright',
|
||||
title: $t('page.vben.about'),
|
||||
title: $t('demos.vben.about'),
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -39,7 +39,7 @@ const routes: RouteRecordRaw[] = [
|
||||
meta: {
|
||||
icon: 'lucide:book-open-text',
|
||||
link: VBEN_DOC_URL,
|
||||
title: $t('page.vben.document'),
|
||||
title: $t('demos.vben.document'),
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -60,7 +60,7 @@ const routes: RouteRecordRaw[] = [
|
||||
badgeType: 'dot',
|
||||
icon: 'logos:naiveui',
|
||||
link: VBEN_NAIVE_PREVIEW_URL,
|
||||
title: $t('page.vben.naive-ui'),
|
||||
title: $t('demos.vben.naive-ui'),
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -71,7 +71,7 @@ const routes: RouteRecordRaw[] = [
|
||||
badgeType: 'dot',
|
||||
icon: 'logos:element',
|
||||
link: VBEN_ELE_PREVIEW_URL,
|
||||
title: $t('page.vben.element-plus'),
|
||||
title: $t('demos.vben.element-plus'),
|
||||
},
|
||||
},
|
||||
],
|
||||
|
@@ -81,9 +81,6 @@ export const useAuthStore = defineStore('auth', () => {
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
} finally {
|
||||
// 需要清除字典缓存
|
||||
const dictStore = useDictStore();
|
||||
dictStore.resetCache();
|
||||
resetAllStores();
|
||||
accessStore.setLoginExpired(false);
|
||||
|
||||
@@ -100,8 +97,14 @@ export const useAuthStore = defineStore('auth', () => {
|
||||
}
|
||||
|
||||
async function fetchUserInfo() {
|
||||
const { permissions = [], roles = [], user } = await getUserInfoApi();
|
||||
|
||||
const backUserInfo = await getUserInfoApi();
|
||||
/**
|
||||
* 登录超时的情况
|
||||
*/
|
||||
if (!backUserInfo) {
|
||||
throw new Error('获取用户信息失败.');
|
||||
}
|
||||
const { permissions = [], roles = [], user } = backUserInfo;
|
||||
/**
|
||||
* 从后台user -> vben user转换
|
||||
*/
|
||||
@@ -114,6 +117,12 @@ export const useAuthStore = defineStore('auth', () => {
|
||||
username: user.userName,
|
||||
};
|
||||
userStore.setUserInfo(userInfo);
|
||||
/**
|
||||
* 需要重新加载字典
|
||||
* 比如退出登录切换到其他租户
|
||||
*/
|
||||
const dictStore = useDictStore();
|
||||
dictStore.resetCache();
|
||||
return userInfo;
|
||||
}
|
||||
|
||||
|
@@ -75,6 +75,7 @@ export const useNotifyStore = defineStore(
|
||||
userId: userId.value,
|
||||
});
|
||||
|
||||
// 需要手动置空 vue3在值相同时不会触发watch
|
||||
data.value = null;
|
||||
});
|
||||
}
|
||||
|
@@ -7,11 +7,17 @@ import {
|
||||
type TenantOption,
|
||||
} from '#/api/core/auth';
|
||||
|
||||
/**
|
||||
* 用于超级管理员切换租户
|
||||
*/
|
||||
export const useTenantStore = defineStore('app-tenant', () => {
|
||||
// 是否已经选中租户
|
||||
const checked = ref(false);
|
||||
// 是否开启租户功能
|
||||
const tenantEnable = ref(true);
|
||||
const tenantList = ref<TenantOption[]>([]);
|
||||
|
||||
// 初始化 获取租户信息
|
||||
async function initTenant() {
|
||||
const { tenantEnabled, voList } = await tenantListApi();
|
||||
tenantEnable.value = tenantEnabled;
|
||||
|
@@ -12,13 +12,16 @@ export function getDict(dictName: string): DictData[] {
|
||||
if (dictList.length === 0 && !dictRequestCache.has(dictName)) {
|
||||
dictRequestCache.set(
|
||||
dictName,
|
||||
dictDataInfo(dictName).then((resp) => {
|
||||
// 缓存到store 这样就不用重复获取了
|
||||
// 内部处理了push的逻辑 这里不用push
|
||||
setDictInfo(dictName, resp);
|
||||
// 移除请求状态缓存
|
||||
dictRequestCache.delete(dictName);
|
||||
}),
|
||||
dictDataInfo(dictName)
|
||||
.then((resp) => {
|
||||
// 缓存到store 这样就不用重复获取了
|
||||
// 内部处理了push的逻辑 这里不用push
|
||||
setDictInfo(dictName, resp);
|
||||
})
|
||||
.finally(() => {
|
||||
// 移除请求状态缓存
|
||||
dictRequestCache.delete(dictName);
|
||||
}),
|
||||
);
|
||||
}
|
||||
return dictList;
|
||||
@@ -31,13 +34,16 @@ export function getDictOptions(dictName: string): Option[] {
|
||||
if (dictOptionList.length === 0 && !dictRequestCache.has(dictName)) {
|
||||
dictRequestCache.set(
|
||||
dictName,
|
||||
dictDataInfo(dictName).then((resp) => {
|
||||
// 缓存到store 这样就不用重复获取了
|
||||
// 内部处理了push的逻辑 这里不用push
|
||||
setDictInfo(dictName, resp);
|
||||
// 移除请求状态缓存
|
||||
dictRequestCache.delete(dictName);
|
||||
}),
|
||||
dictDataInfo(dictName)
|
||||
.then((resp) => {
|
||||
// 缓存到store 这样就不用重复获取了
|
||||
// 内部处理了push的逻辑 这里不用push
|
||||
setDictInfo(dictName, resp);
|
||||
})
|
||||
.finally(() => {
|
||||
// 移除请求状态缓存
|
||||
dictRequestCache.delete(dictName);
|
||||
}),
|
||||
);
|
||||
}
|
||||
return dictOptionList;
|
||||
|
@@ -1,7 +1,7 @@
|
||||
import type { DictData } from '#/api/system/dict/dict-data-model';
|
||||
|
||||
import { JsonPreview } from '@vben/common-ui';
|
||||
import { Icon } from '@vben/icons';
|
||||
import { IconifyIcon } from '@vben/icons';
|
||||
|
||||
import { Tag } from 'ant-design-vue';
|
||||
|
||||
@@ -66,32 +66,26 @@ export function renderJsonPreview(json: any) {
|
||||
* @returns render
|
||||
*/
|
||||
export function renderIcon(icon: string) {
|
||||
return <Icon icon={icon}></Icon>;
|
||||
return <IconifyIcon icon={icon}></IconifyIcon>;
|
||||
}
|
||||
|
||||
// httpMethod
|
||||
/**
|
||||
* httpMethod标签
|
||||
* @param type method类型
|
||||
* @returns render
|
||||
*/
|
||||
export function renderHttpMethodTag(type: string) {
|
||||
const method = type.toUpperCase();
|
||||
let color = 'default';
|
||||
const colors: { [key: string]: string } = {
|
||||
DELETE: 'red',
|
||||
GET: 'green',
|
||||
POST: 'blue',
|
||||
PUT: 'orange',
|
||||
};
|
||||
|
||||
const color = colors[method] ?? 'default';
|
||||
const title = `${method}请求`;
|
||||
switch (method) {
|
||||
case 'DELETE': {
|
||||
color = 'red';
|
||||
break;
|
||||
}
|
||||
case 'GET': {
|
||||
color = 'green';
|
||||
break;
|
||||
}
|
||||
case 'POST': {
|
||||
color = 'blue';
|
||||
break;
|
||||
}
|
||||
case 'PUT': {
|
||||
color = 'orange';
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return <Tag color={color}>{title}</Tag>;
|
||||
}
|
||||
|
||||
|
@@ -1,17 +1,61 @@
|
||||
<script lang="ts" setup>
|
||||
import type { LoginCodeParams, VbenFormSchema } from '@vben/common-ui';
|
||||
|
||||
import { computed, ref } from 'vue';
|
||||
import { computed, onMounted, ref, useTemplateRef } from 'vue';
|
||||
|
||||
import { AuthenticationCodeLogin, z } from '@vben/common-ui';
|
||||
import { $t } from '@vben/locales';
|
||||
|
||||
import { Alert, message } from 'ant-design-vue';
|
||||
|
||||
import { tenantList, type TenantResp } from '#/api';
|
||||
import { sendSmsCode } from '#/api/core/captcha';
|
||||
import { useAuthStore } from '#/store';
|
||||
|
||||
defineOptions({ name: 'CodeLogin' });
|
||||
|
||||
const loading = ref(false);
|
||||
|
||||
const tenantInfo = ref<TenantResp>({
|
||||
tenantEnabled: false,
|
||||
voList: [],
|
||||
});
|
||||
|
||||
const codeLoginRef = useTemplateRef('codeLoginRef');
|
||||
async function loadTenant() {
|
||||
const resp = await tenantList();
|
||||
tenantInfo.value = resp;
|
||||
// 选中第一个租户
|
||||
if (resp.tenantEnabled && resp.voList.length > 0) {
|
||||
const firstTenantId = resp.voList[0]!.tenantId;
|
||||
codeLoginRef.value?.setFieldValue('tenantId', firstTenantId);
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(loadTenant);
|
||||
|
||||
const formSchema = computed((): VbenFormSchema[] => {
|
||||
return [
|
||||
{
|
||||
component: 'VbenSelect',
|
||||
componentProps: {
|
||||
class: 'bg-background h-[40px] focus:border-primary',
|
||||
contentClass: 'max-h-[256px] overflow-y-auto',
|
||||
options: tenantInfo.value.voList?.map((item) => ({
|
||||
label: item.companyName,
|
||||
value: item.tenantId,
|
||||
})),
|
||||
placeholder: $t('authentication.selectAccount'),
|
||||
},
|
||||
defaultValue: '000000',
|
||||
dependencies: {
|
||||
if: () => tenantInfo.value.tenantEnabled,
|
||||
triggerFields: [''],
|
||||
},
|
||||
fieldName: 'tenantId',
|
||||
label: $t('authentication.selectAccount'),
|
||||
rules: z.string().min(1, { message: $t('authentication.selectAccount') }),
|
||||
},
|
||||
{
|
||||
component: 'VbenInput',
|
||||
componentProps: {
|
||||
@@ -28,15 +72,29 @@ const formSchema = computed((): VbenFormSchema[] => {
|
||||
},
|
||||
{
|
||||
component: 'VbenPinInput',
|
||||
componentProps: {
|
||||
createText: (countdown: number) => {
|
||||
const text =
|
||||
countdown > 0
|
||||
? $t('authentication.sendText', [countdown])
|
||||
: $t('authentication.sendCode');
|
||||
return text;
|
||||
},
|
||||
placeholder: $t('authentication.code'),
|
||||
componentProps(_, form) {
|
||||
return {
|
||||
createText: (countdown: number) => {
|
||||
const text =
|
||||
countdown > 0
|
||||
? $t('authentication.sendText', [countdown])
|
||||
: $t('authentication.sendCode');
|
||||
return text;
|
||||
},
|
||||
// 验证码长度 在这设置
|
||||
codeLength: 4,
|
||||
placeholder: $t('authentication.code'),
|
||||
handleSendCode: async () => {
|
||||
const { valid, value } = await form.validateField('phoneNumber');
|
||||
if (!valid) {
|
||||
// 必须抛异常 不能直接return
|
||||
throw new Error('未填写手机号');
|
||||
}
|
||||
// 调用接口发送
|
||||
await sendSmsCode(value);
|
||||
message.success('验证码发送成功');
|
||||
},
|
||||
};
|
||||
},
|
||||
fieldName: 'code',
|
||||
label: $t('authentication.code'),
|
||||
@@ -44,20 +102,37 @@ const formSchema = computed((): VbenFormSchema[] => {
|
||||
},
|
||||
];
|
||||
});
|
||||
/**
|
||||
* 异步处理登录操作
|
||||
* Asynchronously handle the login process
|
||||
* @param values 登录表单数据
|
||||
*/
|
||||
|
||||
const authStore = useAuthStore();
|
||||
async function handleLogin(values: LoginCodeParams) {
|
||||
console.log(values);
|
||||
try {
|
||||
const requestParams: any = {
|
||||
tenantId: values.tenantId,
|
||||
phonenumber: values.phoneNumber,
|
||||
smsCode: values.code,
|
||||
grantType: 'sms',
|
||||
};
|
||||
console.log('login params', requestParams);
|
||||
await authStore.authLogin(requestParams);
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<AuthenticationCodeLogin
|
||||
:form-schema="formSchema"
|
||||
:loading="loading"
|
||||
@submit="handleLogin"
|
||||
/>
|
||||
<div>
|
||||
<Alert
|
||||
class="mb-4"
|
||||
how-icon
|
||||
message="测试手机号: 15888888888 正确验证码: 1234 演示使用 不会真的发送"
|
||||
type="info"
|
||||
/>
|
||||
<AuthenticationCodeLogin
|
||||
ref="codeLoginRef"
|
||||
:form-schema="formSchema"
|
||||
:loading="loading"
|
||||
@submit="handleLogin"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
@@ -1,5 +1,6 @@
|
||||
<script lang="ts" setup>
|
||||
import type { VbenFormSchema } from '@vben/common-ui';
|
||||
import type { Recordable } from '@vben/types';
|
||||
|
||||
import { computed, ref } from 'vue';
|
||||
|
||||
@@ -27,7 +28,7 @@ const formSchema = computed((): VbenFormSchema[] => {
|
||||
];
|
||||
});
|
||||
|
||||
function handleSubmit(value: string) {
|
||||
function handleSubmit(value: Recordable<any>) {
|
||||
console.log('reset email:', value);
|
||||
}
|
||||
</script>
|
||||
|
@@ -1,5 +1,5 @@
|
||||
<script lang="ts" setup>
|
||||
import type { VbenFormSchema } from '@vben/common-ui';
|
||||
import type { LoginAndRegisterParams, VbenFormSchema } from '@vben/common-ui';
|
||||
|
||||
import { computed, onMounted, ref, useTemplateRef } from 'vue';
|
||||
|
||||
@@ -60,7 +60,8 @@ const formSchema = computed((): VbenFormSchema[] => {
|
||||
component: 'VbenSelect',
|
||||
componentProps: {
|
||||
class: 'bg-background h-[40px] focus:border-primary',
|
||||
options: tenantInfo.value.voList.map((item) => ({
|
||||
contentClass: 'max-h-[256px] overflow-y-auto',
|
||||
options: tenantInfo.value.voList?.map((item) => ({
|
||||
label: item.companyName,
|
||||
value: item.tenantId,
|
||||
})),
|
||||
@@ -69,7 +70,15 @@ const formSchema = computed((): VbenFormSchema[] => {
|
||||
defaultValue: '000000',
|
||||
dependencies: {
|
||||
if: () => tenantInfo.value.tenantEnabled,
|
||||
triggerFields: [],
|
||||
// 这里大致上是watch的一个效果
|
||||
componentProps: (model) => {
|
||||
localStorage.setItem(
|
||||
'__oauth_tenant_id',
|
||||
model?.tenantId ?? '000000',
|
||||
);
|
||||
return {};
|
||||
},
|
||||
triggerFields: ['', 'tenantId'],
|
||||
},
|
||||
fieldName: 'tenantId',
|
||||
label: $t('authentication.selectAccount'),
|
||||
@@ -116,15 +125,7 @@ const formSchema = computed((): VbenFormSchema[] => {
|
||||
];
|
||||
});
|
||||
|
||||
interface LoginForm {
|
||||
code?: string;
|
||||
grantType: string;
|
||||
password: string;
|
||||
tenantId: string;
|
||||
username: string;
|
||||
}
|
||||
|
||||
async function handleAccountLogin(values: LoginForm) {
|
||||
async function handleAccountLogin(values: LoginAndRegisterParams) {
|
||||
try {
|
||||
const requestParam: any = omit(values, ['code']);
|
||||
// 验证码
|
||||
|
@@ -1,5 +1,6 @@
|
||||
<script lang="ts" setup>
|
||||
import type { LoginAndRegisterParams, VbenFormSchema } from '@vben/common-ui';
|
||||
import type { VbenFormSchema } from '@vben/common-ui';
|
||||
import type { Recordable } from '@vben/types';
|
||||
|
||||
import { computed, h, ref } from 'vue';
|
||||
|
||||
@@ -45,7 +46,7 @@ const formSchema = computed((): VbenFormSchema[] => {
|
||||
rules(values) {
|
||||
const { password } = values;
|
||||
return z
|
||||
.string()
|
||||
.string({ required_error: $t('authentication.passwordTip') })
|
||||
.min(1, { message: $t('authentication.passwordTip') })
|
||||
.refine((value) => value === password, {
|
||||
message: $t('authentication.confirmPasswordTip'),
|
||||
@@ -55,7 +56,6 @@ const formSchema = computed((): VbenFormSchema[] => {
|
||||
},
|
||||
fieldName: 'confirmPassword',
|
||||
label: $t('authentication.confirmPassword'),
|
||||
rules: z.string().min(1, { message: $t('authentication.passwordTip') }),
|
||||
},
|
||||
{
|
||||
component: 'VbenCheckbox',
|
||||
@@ -67,15 +67,10 @@ const formSchema = computed((): VbenFormSchema[] => {
|
||||
h(
|
||||
'a',
|
||||
{
|
||||
class:
|
||||
'cursor-pointer text-primary ml-1 hover:text-primary-hover',
|
||||
class: 'vben-link ml-1 ',
|
||||
href: '',
|
||||
},
|
||||
[
|
||||
$t('authentication.privacyPolicy'),
|
||||
'&',
|
||||
$t('authentication.terms'),
|
||||
],
|
||||
`${$t('authentication.privacyPolicy')} & ${$t('authentication.terms')}`,
|
||||
),
|
||||
]),
|
||||
}),
|
||||
@@ -86,7 +81,7 @@ const formSchema = computed((): VbenFormSchema[] => {
|
||||
];
|
||||
});
|
||||
|
||||
function handleSubmit(value: LoginAndRegisterParams) {
|
||||
function handleSubmit(value: Recordable<any>) {
|
||||
console.log('register submit:', value);
|
||||
}
|
||||
</script>
|
||||
|
@@ -10,14 +10,14 @@ import { useUserStore } from '@vben/stores';
|
||||
|
||||
import { pick } from 'lodash-es';
|
||||
|
||||
import { useVbenForm, z } from '#/adapter';
|
||||
import { useVbenForm, z } from '#/adapter/form';
|
||||
import { userProfileUpdate } from '#/api/system/profile';
|
||||
import { useAuthStore } from '#/store';
|
||||
import { getDictOptions } from '#/utils/dict';
|
||||
|
||||
const props = defineProps<{ profile: UserProfile }>();
|
||||
import { emitter } from '../mitt';
|
||||
|
||||
const emit = defineEmits<{ reload: [] }>();
|
||||
const props = defineProps<{ profile: UserProfile }>();
|
||||
|
||||
const userStore = useUserStore();
|
||||
const authStore = useAuthStore();
|
||||
@@ -74,7 +74,7 @@ const [BasicForm, formApi] = useVbenForm({
|
||||
},
|
||||
],
|
||||
submitButtonOptions: {
|
||||
text: '更新信息',
|
||||
content: '更新信息',
|
||||
},
|
||||
});
|
||||
|
||||
@@ -93,7 +93,7 @@ async function handleSubmit(values: Recordable<any>) {
|
||||
const userInfo = await authStore.fetchUserInfo();
|
||||
userStore.setUserInfo(userInfo);
|
||||
// 左边reload
|
||||
emit('reload');
|
||||
emitter.emit('updateProfile');
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
} finally {
|
||||
@@ -109,9 +109,7 @@ onMounted(() => {
|
||||
'phonenumber',
|
||||
'sex',
|
||||
]);
|
||||
for (const key in data) {
|
||||
formApi.setFieldValue(key, data[key as keyof typeof data]);
|
||||
}
|
||||
formApi.setValues(data);
|
||||
});
|
||||
</script>
|
||||
|
||||
|
@@ -3,7 +3,7 @@ import type { Recordable } from '@vben/types';
|
||||
|
||||
import { Popconfirm } from 'ant-design-vue';
|
||||
|
||||
import { useVbenVxeGrid, type VxeGridProps } from '#/adapter';
|
||||
import { useVbenVxeGrid, type VxeGridProps } from '#/adapter/vxe-table';
|
||||
import { forceLogout2, onlineDeviceList } from '#/api/monitor/online';
|
||||
import { columns } from '#/views/monitor/online/data';
|
||||
|
||||
@@ -22,9 +22,6 @@ const gridOptions: VxeGridProps = {
|
||||
isHover: true,
|
||||
keyField: 'tokenId',
|
||||
},
|
||||
round: true,
|
||||
align: 'center',
|
||||
showOverflow: true,
|
||||
};
|
||||
|
||||
const [BasicTable, tableApi] = useVbenVxeGrid({ gridOptions });
|
||||
@@ -37,10 +34,7 @@ async function handleForceOffline(row: Recordable<any>) {
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<BasicTable>
|
||||
<template #toolbar-actions>
|
||||
<span class="pl-[7px] text-[16px]">我的在线设备</span>
|
||||
</template>
|
||||
<BasicTable table-title="我的在线设备">
|
||||
<template #action="{ row }">
|
||||
<Popconfirm
|
||||
:title="`确认强制下线[${row.userName}]?`"
|
||||
|
@@ -4,7 +4,7 @@ import type { UpdatePasswordParam } from '#/api/system/profile/model';
|
||||
import { Modal } from 'ant-design-vue';
|
||||
import { omit } from 'lodash-es';
|
||||
|
||||
import { useVbenForm, z } from '#/adapter';
|
||||
import { useVbenForm, z } from '#/adapter/form';
|
||||
import { userUpdatePassword } from '#/api/system/profile';
|
||||
import { useAuthStore } from '#/store';
|
||||
|
||||
@@ -67,7 +67,7 @@ const [BasicForm, formApi] = useVbenForm({
|
||||
},
|
||||
],
|
||||
submitButtonOptions: {
|
||||
text: '修改密码',
|
||||
content: '修改密码',
|
||||
},
|
||||
});
|
||||
|
||||
|
@@ -9,6 +9,7 @@ import { useUserStore } from '@vben/stores';
|
||||
import { userProfile } from '#/api/system/profile';
|
||||
import { useAuthStore } from '#/store';
|
||||
|
||||
import { emitter } from './mitt';
|
||||
import ProfilePanel from './profile-panel.vue';
|
||||
import SettingPanel from './setting-panel.vue';
|
||||
|
||||
@@ -32,6 +33,8 @@ async function handleUploadFinish() {
|
||||
const userInfo = await authStore.fetchUserInfo();
|
||||
userStore.setUserInfo(userInfo);
|
||||
}
|
||||
|
||||
emitter.on('updateProfile', loadProfile);
|
||||
</script>
|
||||
|
||||
<template>
|
||||
@@ -44,7 +47,6 @@ async function handleUploadFinish() {
|
||||
v-if="profile"
|
||||
:profile="profile"
|
||||
class="flex-1 overflow-hidden"
|
||||
@reload="loadProfile"
|
||||
/>
|
||||
</div>
|
||||
</Page>
|
||||
|
7
apps/web-antd/src/views/_core/profile/mitt.ts
Normal file
7
apps/web-antd/src/views/_core/profile/mitt.ts
Normal file
@@ -0,0 +1,7 @@
|
||||
import { mitt } from '@vben/utils';
|
||||
|
||||
type Events = {
|
||||
updateProfile: void;
|
||||
};
|
||||
|
||||
export const emitter = mitt<Events>();
|
@@ -1,4 +1,5 @@
|
||||
import type { FormSchemaGetter, VxeGridProps } from '#/adapter';
|
||||
import type { FormSchemaGetter } from '#/adapter/form';
|
||||
import type { VxeGridProps } from '#/adapter/vxe-table';
|
||||
|
||||
export const querySchema: FormSchemaGetter = () => [
|
||||
{
|
||||
|
@@ -5,7 +5,7 @@ import { useVbenModal } from '@vben/common-ui';
|
||||
import { $t } from '@vben/locales';
|
||||
import { cloneDeep } from '@vben/utils';
|
||||
|
||||
import { useVbenForm } from '#/adapter';
|
||||
import { useVbenForm } from '#/adapter/form';
|
||||
|
||||
import { demoAdd, demoInfo, demoUpdate } from './api';
|
||||
import { modalSchema } from './data';
|
||||
|
@@ -9,7 +9,11 @@ import { getPopupContainer } from '@vben/utils';
|
||||
import { Modal, Popconfirm, Space } from 'ant-design-vue';
|
||||
import dayjs from 'dayjs';
|
||||
|
||||
import { useVbenVxeGrid, type VxeGridProps } from '#/adapter';
|
||||
import {
|
||||
tableCheckboxEvent,
|
||||
useVbenVxeGrid,
|
||||
type VxeGridProps,
|
||||
} from '#/adapter/vxe-table';
|
||||
import { downloadExcel } from '#/utils/file/download';
|
||||
|
||||
import { demoExport, demoList, demoRemove } from './api';
|
||||
@@ -67,9 +71,6 @@ const gridOptions: VxeGridProps = {
|
||||
isHover: true,
|
||||
keyField: 'id',
|
||||
},
|
||||
round: true,
|
||||
align: 'center',
|
||||
showOverflow: true,
|
||||
};
|
||||
|
||||
const checked = ref(false);
|
||||
@@ -77,12 +78,8 @@ 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;
|
||||
},
|
||||
checkboxChange: tableCheckboxEvent(checked),
|
||||
checkboxAll: tableCheckboxEvent(checked),
|
||||
},
|
||||
});
|
||||
|
||||
|
@@ -1,4 +1,5 @@
|
||||
import type { FormSchemaGetter, VxeGridProps } from '#/adapter';
|
||||
import type { FormSchemaGetter } from '#/adapter/form';
|
||||
import type { VxeGridProps } from '#/adapter/vxe-table';
|
||||
|
||||
export const querySchema: FormSchemaGetter = () => [
|
||||
{
|
||||
|
@@ -8,7 +8,7 @@ import { getPopupContainer, listToTree } from '@vben/utils';
|
||||
|
||||
import { Popconfirm, Space } from 'ant-design-vue';
|
||||
|
||||
import { useVbenVxeGrid, type VxeGridProps } from '#/adapter';
|
||||
import { useVbenVxeGrid, type VxeGridProps } from '#/adapter/vxe-table';
|
||||
|
||||
import { treeList, treeRemove } from './api';
|
||||
import { columns, querySchema } from './data';
|
||||
@@ -54,9 +54,7 @@ const gridOptions: VxeGridProps = {
|
||||
isHover: true,
|
||||
keyField: 'id',
|
||||
},
|
||||
round: true,
|
||||
align: 'center',
|
||||
showOverflow: true,
|
||||
|
||||
treeConfig: {
|
||||
parentField: 'parentId',
|
||||
rowField: 'id',
|
||||
|
@@ -5,7 +5,7 @@ import { useVbenModal } from '@vben/common-ui';
|
||||
import { $t } from '@vben/locales';
|
||||
import { cloneDeep, listToTree } from '@vben/utils';
|
||||
|
||||
import { useVbenForm } from '#/adapter';
|
||||
import { useVbenForm } from '#/adapter/form';
|
||||
|
||||
import { treeAdd, treeInfo, treeList, treeUpdate } from './api';
|
||||
import { modalSchema } from './data';
|
||||
|
@@ -1,10 +1,11 @@
|
||||
import type { FormSchemaGetter, VxeGridProps } from '#/adapter';
|
||||
import type { VxeGridProps } from '#/adapter/vxe-table';
|
||||
import type { DescItem } from '#/components/description';
|
||||
|
||||
import type { VNode } from 'vue';
|
||||
|
||||
import { DictEnum } from '@vben/constants';
|
||||
|
||||
import { type FormSchemaGetter } from '#/adapter/form';
|
||||
import { getDictOptions } from '#/utils/dict';
|
||||
import { renderBrowserIcon, renderDict, renderOsIcon } from '#/utils/render';
|
||||
|
||||
|
@@ -7,8 +7,14 @@ import { Page, useVbenModal, type VbenFormProps } from '@vben/common-ui';
|
||||
import { getPopupContainer } from '@vben/utils';
|
||||
|
||||
import { Modal, Popconfirm, Space } from 'ant-design-vue';
|
||||
import dayjs from 'dayjs';
|
||||
|
||||
import { useVbenVxeGrid, type VxeGridProps } from '#/adapter';
|
||||
import {
|
||||
tableCheckboxEvent,
|
||||
useVbenVxeGrid,
|
||||
type VxeGridDefines,
|
||||
type VxeGridProps,
|
||||
} from '#/adapter/vxe-table';
|
||||
import {
|
||||
loginInfoClean,
|
||||
loginInfoExport,
|
||||
@@ -25,6 +31,9 @@ import loginInfoModal from './login-info-modal.vue';
|
||||
const formOptions: VbenFormProps = {
|
||||
commonConfig: {
|
||||
labelWidth: 80,
|
||||
componentProps: {
|
||||
allowClear: true,
|
||||
},
|
||||
},
|
||||
schema: querySchema(),
|
||||
wrapperClass: 'grid-cols-1 md:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4',
|
||||
@@ -46,6 +55,20 @@ const gridOptions: VxeGridProps = {
|
||||
proxyConfig: {
|
||||
ajax: {
|
||||
query: async ({ page }, formValues = {}) => {
|
||||
// 区间选择器处理
|
||||
if (formValues?.dateTime) {
|
||||
formValues.params = {
|
||||
beginTime: dayjs(formValues.dateTime[0]).format(
|
||||
'YYYY-MM-DD 00:00:00',
|
||||
),
|
||||
endTime: dayjs(formValues.dateTime[1]).format(
|
||||
'YYYY-MM-DD 23:59:59',
|
||||
),
|
||||
};
|
||||
Reflect.deleteProperty(formValues, 'dateTime');
|
||||
} else {
|
||||
Reflect.deleteProperty(formValues, 'params');
|
||||
}
|
||||
return await loginInfoList({
|
||||
pageNum: page.currentPage,
|
||||
pageSize: page.pageSize,
|
||||
@@ -58,9 +81,7 @@ const gridOptions: VxeGridProps = {
|
||||
isHover: true,
|
||||
keyField: 'infoId',
|
||||
},
|
||||
round: true,
|
||||
align: 'center',
|
||||
showOverflow: true,
|
||||
id: 'monitor-logininfo-index',
|
||||
};
|
||||
|
||||
const checked = ref(false);
|
||||
@@ -69,13 +90,12 @@ const [BasicTable, tableApi] = useVbenVxeGrid({
|
||||
formOptions,
|
||||
gridOptions,
|
||||
gridEvents: {
|
||||
checkboxChange: (e: any) => {
|
||||
checked.value = e.records.length > 0;
|
||||
canUnlock.value = e.records.length === 1 && e.records[0]?.status === '1';
|
||||
},
|
||||
checkboxAll: (e: any) => {
|
||||
checked.value = e.records.length > 0;
|
||||
checkboxChange: (e: VxeGridDefines.CheckboxChangeEventParams) => {
|
||||
const records = e.$table.getCheckboxRecords();
|
||||
checked.value = records.length > 0;
|
||||
canUnlock.value = records.length === 1 && records[0]?.status === '1';
|
||||
},
|
||||
checkboxAll: tableCheckboxEvent(checked),
|
||||
},
|
||||
});
|
||||
|
||||
@@ -92,6 +112,7 @@ function handleClear() {
|
||||
confirmDeleteModal({
|
||||
onValidated: async () => {
|
||||
await loginInfoClean();
|
||||
await tableApi.reload();
|
||||
},
|
||||
});
|
||||
}
|
||||
@@ -132,10 +153,7 @@ async function handleUnlock() {
|
||||
|
||||
<template>
|
||||
<Page auto-content-height>
|
||||
<BasicTable>
|
||||
<template #toolbar-actions>
|
||||
<span class="pl-[7px] text-[16px]">登录日志列表</span>
|
||||
</template>
|
||||
<BasicTable table-title="登录日志列表">
|
||||
<template #toolbar-tools>
|
||||
<Space>
|
||||
<a-button
|
||||
|
@@ -1,9 +1,10 @@
|
||||
import type { FormSchemaGetter, VxeGridProps } from '#/adapter';
|
||||
import type { VxeGridProps } from '#/adapter/vxe-table';
|
||||
|
||||
import type { VNode } from 'vue';
|
||||
|
||||
import dayjs from 'dayjs';
|
||||
|
||||
import { type FormSchemaGetter } from '#/adapter/form';
|
||||
import { renderBrowserIcon, renderOsIcon } from '#/utils/render';
|
||||
|
||||
export const querySchema: FormSchemaGetter = () => [
|
||||
|
@@ -6,7 +6,7 @@ import { getPopupContainer } from '@vben/utils';
|
||||
|
||||
import { Popconfirm } from 'ant-design-vue';
|
||||
|
||||
import { useVbenVxeGrid, type VxeGridProps } from '#/adapter';
|
||||
import { useVbenVxeGrid, type VxeGridProps } from '#/adapter/vxe-table';
|
||||
import { forceLogout, onlineList } from '#/api/monitor/online';
|
||||
|
||||
import { columns, querySchema } from './data';
|
||||
@@ -14,6 +14,9 @@ import { columns, querySchema } from './data';
|
||||
const formOptions: VbenFormProps = {
|
||||
commonConfig: {
|
||||
labelWidth: 80,
|
||||
componentProps: {
|
||||
allowClear: true,
|
||||
},
|
||||
},
|
||||
schema: querySchema(),
|
||||
wrapperClass: 'grid-cols-1 md:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4',
|
||||
@@ -39,9 +42,7 @@ const gridOptions: VxeGridProps = {
|
||||
isHover: true,
|
||||
keyField: 'tokenId',
|
||||
},
|
||||
round: true,
|
||||
align: 'center',
|
||||
showOverflow: true,
|
||||
id: 'monitor-online-index',
|
||||
};
|
||||
|
||||
const [BasicTable, tableApi] = useVbenVxeGrid({ formOptions, gridOptions });
|
||||
@@ -54,10 +55,7 @@ async function handleForceOffline(row: Recordable<any>) {
|
||||
|
||||
<template>
|
||||
<Page :auto-content-height="true">
|
||||
<BasicTable>
|
||||
<template #toolbar-actions>
|
||||
<span class="pl-[7px] text-[16px]">在线用户列表</span>
|
||||
</template>
|
||||
<BasicTable table-title="在线用户列表">
|
||||
<template #action="{ row }">
|
||||
<Popconfirm
|
||||
:get-popup-container="getPopupContainer"
|
||||
|
@@ -1,10 +1,11 @@
|
||||
import type { FormSchemaGetter, VxeGridProps } from '#/adapter';
|
||||
import type { VxeGridProps } from '#/adapter/vxe-table';
|
||||
import type { DescItem } from '#/components/description';
|
||||
|
||||
import { DictEnum } from '@vben/constants';
|
||||
|
||||
import { Tag } from 'ant-design-vue';
|
||||
|
||||
import { type FormSchemaGetter } from '#/adapter/form';
|
||||
import { getDictOptions } from '#/utils/dict';
|
||||
import {
|
||||
renderDict,
|
||||
@@ -78,10 +79,11 @@ export const columns: VxeGridProps['columns'] = [
|
||||
},
|
||||
},
|
||||
},
|
||||
{ field: 'operTime', title: '操作日期' },
|
||||
{ field: 'operTime', title: '操作日期', sortable: true },
|
||||
{
|
||||
field: 'costTime',
|
||||
title: '操作耗时',
|
||||
sortable: true,
|
||||
formatter({ cellValue }) {
|
||||
return `${cellValue} ms`;
|
||||
},
|
||||
|
@@ -10,8 +10,13 @@ import { $t } from '@vben/locales';
|
||||
|
||||
import { Modal, Space } from 'ant-design-vue';
|
||||
import dayjs from 'dayjs';
|
||||
import { isEmpty } from 'lodash-es';
|
||||
|
||||
import { useVbenVxeGrid, type VxeGridProps } from '#/adapter';
|
||||
import {
|
||||
tableCheckboxEvent,
|
||||
useVbenVxeGrid,
|
||||
type VxeGridProps,
|
||||
} from '#/adapter/vxe-table';
|
||||
import {
|
||||
operLogClean,
|
||||
operLogDelete,
|
||||
@@ -22,11 +27,14 @@ import { downloadExcel } from '#/utils/file/download';
|
||||
import { confirmDeleteModal } from '#/utils/modal';
|
||||
|
||||
import { columns, querySchema } from './data';
|
||||
import operationPreviewDrawer from './OperationPreviewDrawer.vue';
|
||||
import operationPreviewDrawer from './operation-preview-drawer.vue';
|
||||
|
||||
const formOptions: VbenFormProps = {
|
||||
commonConfig: {
|
||||
labelWidth: 80,
|
||||
componentProps: {
|
||||
allowClear: true,
|
||||
},
|
||||
},
|
||||
schema: querySchema(),
|
||||
wrapperClass: 'grid-cols-1 md:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4',
|
||||
@@ -47,7 +55,7 @@ const gridOptions: VxeGridProps<OperationLog> = {
|
||||
pagerConfig: {},
|
||||
proxyConfig: {
|
||||
ajax: {
|
||||
query: async ({ page }, formValues = {}) => {
|
||||
query: async ({ page, sort }, formValues = {}) => {
|
||||
// 区间选择器处理
|
||||
if (formValues?.createTime) {
|
||||
formValues.params = {
|
||||
@@ -62,11 +70,18 @@ const gridOptions: VxeGridProps<OperationLog> = {
|
||||
} else {
|
||||
Reflect.deleteProperty(formValues, 'params');
|
||||
}
|
||||
return await operLogList({
|
||||
|
||||
const params: any = {
|
||||
pageNum: page.currentPage,
|
||||
pageSize: page.pageSize,
|
||||
...formValues,
|
||||
});
|
||||
};
|
||||
|
||||
if (!isEmpty(sort)) {
|
||||
params.orderByColumn = sort.field;
|
||||
params.isAsc = sort.order;
|
||||
}
|
||||
return await operLogList(params);
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -74,9 +89,10 @@ const gridOptions: VxeGridProps<OperationLog> = {
|
||||
isHover: true,
|
||||
keyField: 'operId',
|
||||
},
|
||||
round: true,
|
||||
align: 'center',
|
||||
showOverflow: true,
|
||||
sortConfig: {
|
||||
remote: true,
|
||||
},
|
||||
id: 'monitor-operlog-index',
|
||||
};
|
||||
|
||||
const checked = ref(false);
|
||||
@@ -84,12 +100,11 @@ 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;
|
||||
sortChange: () => {
|
||||
tableApi.query();
|
||||
},
|
||||
checkboxChange: tableCheckboxEvent(checked),
|
||||
checkboxAll: tableCheckboxEvent(checked),
|
||||
},
|
||||
});
|
||||
|
||||
@@ -113,6 +128,7 @@ function handleClear() {
|
||||
confirmDeleteModal({
|
||||
onValidated: async () => {
|
||||
await operLogClean();
|
||||
await tableApi.reload();
|
||||
},
|
||||
});
|
||||
}
|
||||
@@ -137,10 +153,7 @@ async function handleDelete() {
|
||||
|
||||
<template>
|
||||
<Page :auto-content-height="true">
|
||||
<BasicTable>
|
||||
<template #toolbar-actions>
|
||||
<span class="pl-[7px] text-[16px]">操作日志列表</span>
|
||||
</template>
|
||||
<BasicTable table-title="操作日志列表">
|
||||
<template #toolbar-tools>
|
||||
<Space>
|
||||
<a-button
|
||||
|
@@ -5,7 +5,7 @@ import { useVbenDrawer } from '@vben/common-ui';
|
||||
import { $t } from '@vben/locales';
|
||||
import { cloneDeep } from '@vben/utils';
|
||||
|
||||
import { useVbenForm } from '#/adapter';
|
||||
import { useVbenForm } from '#/adapter/form';
|
||||
import { clientAdd, clientInfo, clientUpdate } from '#/api/system/client';
|
||||
|
||||
import { drawerSchema } from './data';
|
||||
|
@@ -1,8 +1,9 @@
|
||||
import type { FormSchemaGetter, VxeGridProps } from '#/adapter';
|
||||
import type { VxeGridProps } from '#/adapter/vxe-table';
|
||||
|
||||
import { DictEnum } from '@vben/constants';
|
||||
import { getPopupContainer } from '@vben/utils';
|
||||
|
||||
import { type FormSchemaGetter } from '#/adapter/form';
|
||||
import { getDict, getDictOptions } from '#/utils/dict';
|
||||
import { renderDict, renderDictTags } from '#/utils/render';
|
||||
|
||||
|
@@ -9,7 +9,11 @@ import { getPopupContainer } from '@vben/utils';
|
||||
|
||||
import { Modal, Popconfirm, Space } from 'ant-design-vue';
|
||||
|
||||
import { useVbenVxeGrid, type VxeGridProps } from '#/adapter';
|
||||
import {
|
||||
tableCheckboxEvent,
|
||||
useVbenVxeGrid,
|
||||
type VxeGridProps,
|
||||
} from '#/adapter/vxe-table';
|
||||
import {
|
||||
clientChangeStatus,
|
||||
clientExport,
|
||||
@@ -25,6 +29,9 @@ import { columns, querySchema } from './data';
|
||||
const formOptions: VbenFormProps = {
|
||||
commonConfig: {
|
||||
labelWidth: 80,
|
||||
componentProps: {
|
||||
allowClear: true,
|
||||
},
|
||||
},
|
||||
schema: querySchema(),
|
||||
wrapperClass: 'grid-cols-1 md:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4',
|
||||
@@ -60,9 +67,7 @@ const gridOptions: VxeGridProps = {
|
||||
keyField: 'id',
|
||||
height: 90,
|
||||
},
|
||||
round: true,
|
||||
align: 'center',
|
||||
showOverflow: true,
|
||||
id: 'system-client-index',
|
||||
};
|
||||
|
||||
const checked = ref(false);
|
||||
@@ -70,12 +75,8 @@ 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;
|
||||
},
|
||||
checkboxChange: tableCheckboxEvent(checked),
|
||||
checkboxAll: tableCheckboxEvent(checked),
|
||||
},
|
||||
});
|
||||
|
||||
@@ -118,10 +119,7 @@ const { hasAccessByCodes } = useAccess();
|
||||
|
||||
<template>
|
||||
<Page :auto-content-height="true">
|
||||
<BasicTable>
|
||||
<template #toolbar-actions>
|
||||
<span class="pl-[7px] text-[16px]">系统授权列表</span>
|
||||
</template>
|
||||
<BasicTable table-title="系统授权列表">
|
||||
<template #toolbar-tools>
|
||||
<Space>
|
||||
<a-button
|
||||
|
@@ -1,5 +1,5 @@
|
||||
<script setup lang="ts">
|
||||
import { Icon } from '@vben/icons';
|
||||
import { IconifyIcon } from '@vben/icons';
|
||||
import { buildUUID } from '@vben/utils';
|
||||
|
||||
import { Input } from 'ant-design-vue';
|
||||
@@ -38,7 +38,7 @@ defineExpose({ refreshSecret });
|
||||
<template v-if="!disabled" #addonAfter>
|
||||
<a-button type="primary" @click="refreshSecret">
|
||||
<div class="flex items-center gap-[4px]">
|
||||
<Icon icon="charm:refresh" />
|
||||
<IconifyIcon icon="charm:refresh" />
|
||||
<span>随机生成</span>
|
||||
</div>
|
||||
</a-button>
|
||||
|
@@ -5,7 +5,7 @@ import { useVbenModal } from '@vben/common-ui';
|
||||
import { $t } from '@vben/locales';
|
||||
import { cloneDeep } from '@vben/utils';
|
||||
|
||||
import { useVbenForm } from '#/adapter';
|
||||
import { useVbenForm } from '#/adapter/form';
|
||||
import { configAdd, configInfo, configUpdate } from '#/api/system/config';
|
||||
|
||||
import { modalSchema } from './data';
|
||||
|
@@ -1,8 +1,9 @@
|
||||
import type { FormSchemaGetter, VxeGridProps } from '#/adapter';
|
||||
import type { VxeGridProps } from '#/adapter/vxe-table';
|
||||
|
||||
import { DictEnum } from '@vben/constants';
|
||||
import { getPopupContainer } from '@vben/utils';
|
||||
|
||||
import { type FormSchemaGetter } from '#/adapter/form';
|
||||
import { getDictOptions } from '#/utils/dict';
|
||||
import { renderDict } from '#/utils/render';
|
||||
|
||||
|
@@ -9,7 +9,11 @@ import { getPopupContainer } from '@vben/utils';
|
||||
import { Modal, Popconfirm, Space } from 'ant-design-vue';
|
||||
import dayjs from 'dayjs';
|
||||
|
||||
import { useVbenVxeGrid, type VxeGridProps } from '#/adapter';
|
||||
import {
|
||||
tableCheckboxEvent,
|
||||
useVbenVxeGrid,
|
||||
type VxeGridProps,
|
||||
} from '#/adapter/vxe-table';
|
||||
import {
|
||||
configExport,
|
||||
configList,
|
||||
@@ -24,6 +28,9 @@ import { columns, querySchema } from './data';
|
||||
const formOptions: VbenFormProps = {
|
||||
commonConfig: {
|
||||
labelWidth: 80,
|
||||
componentProps: {
|
||||
allowClear: true,
|
||||
},
|
||||
},
|
||||
schema: querySchema(),
|
||||
wrapperClass: 'grid-cols-1 md:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4',
|
||||
@@ -70,9 +77,7 @@ const gridOptions: VxeGridProps = {
|
||||
isHover: true,
|
||||
keyField: 'configId',
|
||||
},
|
||||
round: true,
|
||||
align: 'center',
|
||||
showOverflow: true,
|
||||
id: 'system-config-index',
|
||||
};
|
||||
|
||||
const checked = ref(false);
|
||||
@@ -80,12 +85,8 @@ 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;
|
||||
},
|
||||
checkboxChange: tableCheckboxEvent(checked),
|
||||
checkboxAll: tableCheckboxEvent(checked),
|
||||
},
|
||||
});
|
||||
const [ConfigModal, modalApi] = useVbenModal({
|
||||
@@ -130,10 +131,7 @@ async function handleRefreshCache() {
|
||||
|
||||
<template>
|
||||
<Page :auto-content-height="true">
|
||||
<BasicTable>
|
||||
<template #toolbar-actions>
|
||||
<span class="pl-[7px] text-[16px]">参数列表</span>
|
||||
</template>
|
||||
<BasicTable table-title="参数列表">
|
||||
<template #toolbar-tools>
|
||||
<Space>
|
||||
<a-button @click="handleRefreshCache"> 刷新缓存 </a-button>
|
||||
|
@@ -1,7 +1,9 @@
|
||||
import type { VxeGridProps } from '#/adapter/vxe-table';
|
||||
|
||||
import { DictEnum } from '@vben/constants';
|
||||
import { getPopupContainer } from '@vben/utils';
|
||||
|
||||
import { type FormSchemaGetter, type VxeGridProps, z } from '#/adapter';
|
||||
import { type FormSchemaGetter, z } from '#/adapter/form';
|
||||
import { getDictOptions } from '#/utils/dict';
|
||||
import { renderDict } from '#/utils/render';
|
||||
|
||||
|
@@ -5,7 +5,7 @@ import { useVbenDrawer } from '@vben/common-ui';
|
||||
import { $t } from '@vben/locales';
|
||||
import { addFullName, cloneDeep, listToTree } from '@vben/utils';
|
||||
|
||||
import { useVbenForm } from '#/adapter';
|
||||
import { useVbenForm } from '#/adapter/form';
|
||||
import {
|
||||
deptAdd,
|
||||
deptInfo,
|
||||
|
@@ -11,10 +11,9 @@ import {
|
||||
removeEmptyChildren,
|
||||
} from '@vben/utils';
|
||||
|
||||
import { QuestionCircleOutlined } from '@ant-design/icons-vue';
|
||||
import { Popconfirm, Space, Tooltip } from 'ant-design-vue';
|
||||
import { Popconfirm, Space } from 'ant-design-vue';
|
||||
|
||||
import { useVbenVxeGrid, type VxeGridProps } from '#/adapter';
|
||||
import { useVbenVxeGrid, type VxeGridProps } from '#/adapter/vxe-table';
|
||||
import { deptList, deptRemove } from '#/api/system/dept';
|
||||
|
||||
import { columns, querySchema } from './data';
|
||||
@@ -23,6 +22,9 @@ import deptDrawer from './dept-drawer.vue';
|
||||
const formOptions: VbenFormProps = {
|
||||
commonConfig: {
|
||||
labelWidth: 80,
|
||||
componentProps: {
|
||||
allowClear: true,
|
||||
},
|
||||
},
|
||||
schema: querySchema(),
|
||||
wrapperClass: 'grid-cols-1 md:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4',
|
||||
@@ -64,14 +66,13 @@ const gridOptions: VxeGridProps = {
|
||||
isHover: true,
|
||||
keyField: 'deptId',
|
||||
},
|
||||
round: true,
|
||||
align: 'center',
|
||||
showOverflow: true,
|
||||
|
||||
treeConfig: {
|
||||
parentField: 'parentId',
|
||||
rowField: 'deptId',
|
||||
transform: false,
|
||||
},
|
||||
id: 'system-dept-index',
|
||||
};
|
||||
|
||||
const [BasicTable, tableApi] = useVbenVxeGrid({
|
||||
@@ -131,15 +132,7 @@ function setExpandOrCollapse(expand: boolean) {
|
||||
|
||||
<template>
|
||||
<Page :auto-content-height="true">
|
||||
<BasicTable>
|
||||
<template #toolbar-actions>
|
||||
<div class="flex items-center gap-[6px]">
|
||||
<span class="pl-[7px] text-[16px]">部门列表</span>
|
||||
<Tooltip title="提示:双击展开/收起子菜单">
|
||||
<QuestionCircleOutlined class="text-center" />
|
||||
</Tooltip>
|
||||
</div>
|
||||
</template>
|
||||
<BasicTable table-title="部门列表" table-title-help="双击展开/收起子菜单">
|
||||
<template #toolbar-tools>
|
||||
<Space>
|
||||
<a-button @click="setExpandOrCollapse(false)">
|
||||
|
@@ -1,40 +1,9 @@
|
||||
import type { FormSchemaGetter, VxeGridProps } from '#/adapter';
|
||||
import type { VxeGridProps } from '#/adapter/vxe-table';
|
||||
|
||||
import { reactive } from 'vue';
|
||||
|
||||
import { getPopupContainer } from '@vben/utils';
|
||||
|
||||
import { dictOptionSelectList } from '#/api/system/dict/dict-type';
|
||||
import { type FormSchemaGetter } from '#/adapter/form';
|
||||
import { renderDictTag } from '#/utils/render';
|
||||
|
||||
/**
|
||||
* updateSchema无法赋值
|
||||
* TODO: 使用updateSchema重构
|
||||
*/
|
||||
const dictTypeOptions = reactive<{ label: string; value: string }[]>([]);
|
||||
(async () => {
|
||||
const resp = await dictOptionSelectList();
|
||||
const options = resp.map((item) => ({
|
||||
label: item.dictName,
|
||||
value: item.dictType,
|
||||
}));
|
||||
dictTypeOptions.push(...options);
|
||||
})();
|
||||
|
||||
export const querySchema: FormSchemaGetter = () => [
|
||||
{
|
||||
component: 'Select',
|
||||
dependencies: {
|
||||
show: () => false,
|
||||
triggerFields: [''],
|
||||
},
|
||||
componentProps: {
|
||||
getPopupContainer,
|
||||
options: dictTypeOptions,
|
||||
},
|
||||
fieldName: 'dictType',
|
||||
label: '字典类型',
|
||||
},
|
||||
{
|
||||
component: 'Input',
|
||||
fieldName: 'dictLabel',
|
||||
|
@@ -5,7 +5,7 @@ import { useVbenDrawer } from '@vben/common-ui';
|
||||
import { $t } from '@vben/locales';
|
||||
import { cloneDeep } from '@vben/utils';
|
||||
|
||||
import { useVbenForm } from '#/adapter';
|
||||
import { useVbenForm } from '#/adapter/form';
|
||||
import {
|
||||
dictDataAdd,
|
||||
dictDataUpdate,
|
||||
|
@@ -3,13 +3,17 @@ import type { Recordable } from '@vben/types';
|
||||
|
||||
import { ref } from 'vue';
|
||||
|
||||
import { Page, useVbenDrawer, type VbenFormProps } from '@vben/common-ui';
|
||||
import { useVbenDrawer, type VbenFormProps } from '@vben/common-ui';
|
||||
import { getPopupContainer } from '@vben/utils';
|
||||
|
||||
import { Modal, Popconfirm, Space } from 'ant-design-vue';
|
||||
import dayjs from 'dayjs';
|
||||
|
||||
import { useVbenVxeGrid, type VxeGridProps } from '#/adapter';
|
||||
import {
|
||||
tableCheckboxEvent,
|
||||
useVbenVxeGrid,
|
||||
type VxeGridProps,
|
||||
} from '#/adapter/vxe-table';
|
||||
import {
|
||||
dictDataExport,
|
||||
dictDataList,
|
||||
@@ -26,6 +30,9 @@ const dictType = ref('');
|
||||
const formOptions: VbenFormProps = {
|
||||
commonConfig: {
|
||||
labelWidth: 80,
|
||||
componentProps: {
|
||||
allowClear: true,
|
||||
},
|
||||
},
|
||||
schema: querySchema(),
|
||||
wrapperClass: 'grid-cols-1 md:grid-cols-2 lg:grid-cols-3',
|
||||
@@ -79,9 +86,7 @@ const gridOptions: VxeGridProps = {
|
||||
isHover: true,
|
||||
keyField: 'dictCode',
|
||||
},
|
||||
round: true,
|
||||
align: 'center',
|
||||
showOverflow: true,
|
||||
id: 'system-dict-data-index',
|
||||
};
|
||||
|
||||
const checked = ref(false);
|
||||
@@ -89,12 +94,8 @@ 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;
|
||||
},
|
||||
checkboxChange: tableCheckboxEvent(checked),
|
||||
checkboxAll: tableCheckboxEvent(checked),
|
||||
},
|
||||
});
|
||||
|
||||
@@ -142,11 +143,8 @@ emitter.on('rowClick', async (value) => {
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Page :auto-content-height="true">
|
||||
<BasicTable>
|
||||
<template #toolbar-actions>
|
||||
<span class="pl-[7px] text-[16px]">字典数据列表</span>
|
||||
</template>
|
||||
<div>
|
||||
<BasicTable table-title="字典数据列表">
|
||||
<template #toolbar-tools>
|
||||
<Space>
|
||||
<a-button
|
||||
@@ -206,5 +204,5 @@ emitter.on('rowClick', async (value) => {
|
||||
</template>
|
||||
</BasicTable>
|
||||
<DictDataDrawer @reload="tableApi.query()" />
|
||||
</Page>
|
||||
</div>
|
||||
</template>
|
||||
|
@@ -6,17 +6,11 @@ import DictTypePanel from './type/index.vue';
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Page :auto-content-height="true" content-class="flex flex-col lg:flex-row">
|
||||
<Page
|
||||
:auto-content-height="true"
|
||||
content-class="flex flex-col lg:flex-row gap-4"
|
||||
>
|
||||
<DictTypePanel class="flex-1 overflow-hidden" />
|
||||
<DictDataPanel class="flex-1 overflow-hidden" />
|
||||
</Page>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
/**
|
||||
TODO: ugly code
|
||||
*/
|
||||
:deep(.p-4) {
|
||||
padding: 6px;
|
||||
}
|
||||
</style>
|
||||
|
@@ -1,4 +1,6 @@
|
||||
import { type FormSchemaGetter, type VxeGridProps, z } from '#/adapter';
|
||||
import type { VxeGridProps } from '#/adapter/vxe-table';
|
||||
|
||||
import { type FormSchemaGetter, z } from '#/adapter/form';
|
||||
|
||||
export const querySchema: FormSchemaGetter = () => [
|
||||
{
|
||||
|
@@ -5,7 +5,7 @@ import { useVbenModal } from '@vben/common-ui';
|
||||
import { $t } from '@vben/locales';
|
||||
import { cloneDeep } from '@vben/utils';
|
||||
|
||||
import { useVbenForm } from '#/adapter';
|
||||
import { useVbenForm } from '#/adapter/form';
|
||||
import {
|
||||
dictTypeAdd,
|
||||
dictTypeInfo,
|
||||
|
@@ -1,9 +1,10 @@
|
||||
<script setup lang="ts">
|
||||
import type { Recordable } from '@vben/types';
|
||||
|
||||
import { ref } from 'vue';
|
||||
import { computed, ref } from 'vue';
|
||||
|
||||
import { Page, useVbenModal, type VbenFormProps } from '@vben/common-ui';
|
||||
import { useAccess } from '@vben/access';
|
||||
import { useVbenModal, type VbenFormProps } from '@vben/common-ui';
|
||||
import { getPopupContainer } from '@vben/utils';
|
||||
|
||||
import {
|
||||
@@ -17,7 +18,11 @@ import {
|
||||
} from 'ant-design-vue';
|
||||
import dayjs from 'dayjs';
|
||||
|
||||
import { useVbenVxeGrid, type VxeGridProps } from '#/adapter';
|
||||
import {
|
||||
tableCheckboxEvent,
|
||||
useVbenVxeGrid,
|
||||
type VxeGridProps,
|
||||
} from '#/adapter/vxe-table';
|
||||
import {
|
||||
dictTypeExport,
|
||||
dictTypeList,
|
||||
@@ -25,6 +30,7 @@ import {
|
||||
refreshDictTypeCache,
|
||||
} from '#/api/system/dict/dict-type';
|
||||
import { dictSyncTenant } from '#/api/system/tenant';
|
||||
import { useTenantStore } from '#/store/tenant';
|
||||
import { downloadExcel } from '#/utils/file/download';
|
||||
|
||||
import { emitter } from '../mitt';
|
||||
@@ -34,6 +40,9 @@ import dictTypeModal from './dict-type-modal.vue';
|
||||
const formOptions: VbenFormProps = {
|
||||
commonConfig: {
|
||||
labelWidth: 70,
|
||||
componentProps: {
|
||||
allowClear: true,
|
||||
},
|
||||
},
|
||||
schema: querySchema(),
|
||||
wrapperClass: 'grid-cols-1 md:grid-cols-2 lg:grid-cols-3',
|
||||
@@ -82,9 +91,7 @@ const gridOptions: VxeGridProps = {
|
||||
isHover: true,
|
||||
keyField: 'dictId',
|
||||
},
|
||||
round: true,
|
||||
align: 'center',
|
||||
showOverflow: true,
|
||||
id: 'system-dict-type-index',
|
||||
};
|
||||
|
||||
const checked = ref(false);
|
||||
@@ -102,12 +109,8 @@ const [BasicTable, tableApi] = useVbenVxeGrid({
|
||||
emitter.emit('rowClick', row.dictType);
|
||||
lastDictType.value = row.dictType;
|
||||
},
|
||||
checkboxChange: (e: any) => {
|
||||
checked.value = e.records.length > 0;
|
||||
},
|
||||
checkboxAll: (e: any) => {
|
||||
checked.value = e.records.length > 0;
|
||||
},
|
||||
checkboxChange: tableCheckboxEvent(checked),
|
||||
checkboxAll: tableCheckboxEvent(checked),
|
||||
},
|
||||
});
|
||||
const [DictTypeModal, modalApi] = useVbenModal({
|
||||
@@ -172,14 +175,20 @@ function handleSyncTenantDict() {
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
const { hasAccessByRoles } = useAccess();
|
||||
const tenantStore = useTenantStore();
|
||||
/**
|
||||
* 开启租户 & 超级管理员才可以同步租户字典
|
||||
*/
|
||||
const couldSyncTenantDict = computed(() => {
|
||||
return tenantStore.tenantEnable && hasAccessByRoles(['superadmin']);
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Page :auto-content-height="true">
|
||||
<BasicTable>
|
||||
<template #toolbar-actions>
|
||||
<span class="pl-[7px] text-[16px]">字典类型列表</span>
|
||||
</template>
|
||||
<div>
|
||||
<BasicTable table-title="字典类型列表">
|
||||
<template #toolbar-tools>
|
||||
<Space>
|
||||
<Dropdown>
|
||||
@@ -188,7 +197,7 @@ function handleSyncTenantDict() {
|
||||
<span v-access:code="['system:dict:edit']">
|
||||
<MenuItem key="1">刷新字典缓存</MenuItem>
|
||||
</span>
|
||||
<span v-access:role="['superadmin']">
|
||||
<span v-if="couldSyncTenantDict">
|
||||
<MenuItem key="2"> 同步租户字典 </MenuItem>
|
||||
</span>
|
||||
</Menu>
|
||||
@@ -251,5 +260,5 @@ function handleSyncTenantDict() {
|
||||
</template>
|
||||
</BasicTable>
|
||||
<DictTypeModal @reload="tableApi.query()" />
|
||||
</Page>
|
||||
</div>
|
||||
</template>
|
||||
|
@@ -1,7 +1,10 @@
|
||||
import type { VxeGridProps } from '#/adapter/vxe-table';
|
||||
|
||||
import { DictEnum } from '@vben/constants';
|
||||
import { $t } from '@vben/locales';
|
||||
import { getPopupContainer } from '@vben/utils';
|
||||
|
||||
import { type FormSchemaGetter, type VxeGridProps, z } from '#/adapter';
|
||||
import { type FormSchemaGetter, z } from '#/adapter/form';
|
||||
import { getDictOptions } from '#/utils/dict';
|
||||
import { renderDict, renderIcon } from '#/utils/render';
|
||||
|
||||
@@ -56,6 +59,10 @@ export const columns: VxeGridProps['columns'] = [
|
||||
field: 'menuName',
|
||||
treeNode: true,
|
||||
width: 200,
|
||||
slots: {
|
||||
// 需要i18n支持 否则返回原始值
|
||||
default: ({ row }) => $t(row.menuName),
|
||||
},
|
||||
},
|
||||
{
|
||||
title: '图标',
|
||||
@@ -66,7 +73,9 @@ export const columns: VxeGridProps['columns'] = [
|
||||
if (row?.icon === '#') {
|
||||
return '';
|
||||
}
|
||||
return renderIcon(row.icon);
|
||||
return (
|
||||
<span class={'flex justify-center'}>{renderIcon(row.icon)}</span>
|
||||
);
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -180,14 +189,22 @@ export const drawerSchema: FormSchemaGetter = () => [
|
||||
show: (values) => values.menuType !== 'F',
|
||||
triggerFields: ['menuType'],
|
||||
},
|
||||
renderComponentContent: () => ({
|
||||
addonAfter: () => (
|
||||
<a href="https://icon-sets.iconify.design/" target="_blank">
|
||||
搜索图标
|
||||
</a>
|
||||
),
|
||||
}),
|
||||
fieldName: 'icon',
|
||||
help: '选择或者从 https://icon-sets.iconify.design/ 查找名称粘贴',
|
||||
help: '点击搜索图标跳转到iconify & 粘贴',
|
||||
label: '菜单图标',
|
||||
},
|
||||
{
|
||||
component: 'Input',
|
||||
fieldName: 'menuName',
|
||||
label: '菜单名称',
|
||||
help: '支持i18n写法, 如: menu.system.user',
|
||||
rules: 'required',
|
||||
},
|
||||
{
|
||||
@@ -227,7 +244,7 @@ export const drawerSchema: FormSchemaGetter = () => [
|
||||
triggerFields: ['isFrame', 'menuType'],
|
||||
},
|
||||
fieldName: 'path',
|
||||
help: `路由地址不带/, 如: menu, user 链接为http(s)://开头 链接默认使用内部iframe打开, 可通过{是否外链}控制打开方式`,
|
||||
help: `路由地址不带/, 如: menu, user\n 链接为http(s)://开头\n 链接默认使用内部iframe打开, 可通过{是否外链}控制打开方式`,
|
||||
label: '路由地址',
|
||||
},
|
||||
{
|
||||
@@ -275,7 +292,7 @@ export const drawerSchema: FormSchemaGetter = () => [
|
||||
triggerFields: ['menuType'],
|
||||
},
|
||||
fieldName: 'isFrame',
|
||||
help: '外链为http(s)://开头 选择否时, 使用iframe从内部打开页面, 否则新窗口打开',
|
||||
help: '外链为http(s)://开头\n 选择否时, 使用iframe从内部打开页面, 否则新窗口打开',
|
||||
label: '是否外链',
|
||||
},
|
||||
{
|
||||
@@ -320,16 +337,15 @@ export const drawerSchema: FormSchemaGetter = () => [
|
||||
triggerFields: ['menuType'],
|
||||
},
|
||||
fieldName: 'perms',
|
||||
help: `控制器中定义的权限字符, 如: @SaCheckPermission("system:user:import")`,
|
||||
help: `控制器中定义的权限字符\n 如: @SaCheckPermission("system:user:import")`,
|
||||
label: '权限标识',
|
||||
},
|
||||
{
|
||||
component: 'Input',
|
||||
componentProps: () => ({
|
||||
componentProps: (model) => ({
|
||||
// 为链接时组件disabled
|
||||
// disabled: model.isFrame === '0',
|
||||
placeholder: '暂未实现功能',
|
||||
disabled: true,
|
||||
disabled: model.isFrame === '0',
|
||||
placeholder: '必须为json字符串格式',
|
||||
}),
|
||||
dependencies: {
|
||||
// 类型为菜单时显示
|
||||
@@ -337,7 +353,7 @@ export const drawerSchema: FormSchemaGetter = () => [
|
||||
triggerFields: ['menuType'],
|
||||
},
|
||||
fieldName: 'queryParam',
|
||||
help: 'vue-router中的query属性, 如{"name": "xxx", "age": 16}',
|
||||
help: 'vue-router中的query属性\n 如{"name": "xxx", "age": 16}',
|
||||
label: '路由参数',
|
||||
},
|
||||
{
|
||||
|
@@ -13,9 +13,9 @@ import {
|
||||
removeEmptyChildren,
|
||||
} from '@vben/utils';
|
||||
|
||||
import { Popconfirm, Space, Tooltip } from 'ant-design-vue';
|
||||
import { Popconfirm, Space } from 'ant-design-vue';
|
||||
|
||||
import { useVbenVxeGrid, type VxeGridProps } from '#/adapter';
|
||||
import { useVbenVxeGrid, type VxeGridProps } from '#/adapter/vxe-table';
|
||||
import { menuList, menuRemove } from '#/api/system/menu';
|
||||
|
||||
import { columns, querySchema } from './data';
|
||||
@@ -24,11 +24,13 @@ import menuDrawer from './menu-drawer.vue';
|
||||
/**
|
||||
* 不要问为什么有两个根节点 v-if会控制只会渲染一个
|
||||
*/
|
||||
import { QuestionCircleOutlined } from '@ant-design/icons-vue';
|
||||
|
||||
const formOptions: VbenFormProps = {
|
||||
commonConfig: {
|
||||
labelWidth: 80,
|
||||
componentProps: {
|
||||
allowClear: true,
|
||||
},
|
||||
},
|
||||
schema: querySchema(),
|
||||
wrapperClass: 'grid-cols-1 md:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4',
|
||||
@@ -61,14 +63,13 @@ const gridOptions: VxeGridProps = {
|
||||
isHover: true,
|
||||
keyField: 'menuId',
|
||||
},
|
||||
round: true,
|
||||
align: 'center',
|
||||
showOverflow: true,
|
||||
|
||||
treeConfig: {
|
||||
parentField: 'parentId',
|
||||
rowField: 'menuId',
|
||||
transform: false,
|
||||
},
|
||||
id: 'system-menu-index',
|
||||
};
|
||||
|
||||
const [BasicTable, tableApi] = useVbenVxeGrid({
|
||||
@@ -137,15 +138,7 @@ const isAdmin = computed(() => {
|
||||
|
||||
<template>
|
||||
<Page v-if="isAdmin" :auto-content-height="true">
|
||||
<BasicTable>
|
||||
<template #toolbar-actions>
|
||||
<div class="flex items-center gap-[6px]">
|
||||
<span class="pl-[7px] text-[16px]">菜单列表</span>
|
||||
<Tooltip title="提示:双击展开/收起子菜单">
|
||||
<QuestionCircleOutlined class="text-center" />
|
||||
</Tooltip>
|
||||
</div>
|
||||
</template>
|
||||
<BasicTable table-title="菜单列表" table-title-help="双击展开/收起子菜单">
|
||||
<template #toolbar-tools>
|
||||
<Space>
|
||||
<a-button @click="setExpandOrCollapse(false)">
|
||||
|
@@ -10,7 +10,7 @@ import {
|
||||
listToTree,
|
||||
} from '@vben/utils';
|
||||
|
||||
import { useVbenForm } from '#/adapter';
|
||||
import { useVbenForm } from '#/adapter/form';
|
||||
import { menuAdd, menuInfo, menuList, menuUpdate } from '#/api/system/menu';
|
||||
|
||||
import { drawerSchema } from './data';
|
||||
@@ -43,12 +43,16 @@ const [BasicForm, formApi] = useVbenForm({
|
||||
async function setupMenuSelect() {
|
||||
// menu
|
||||
const menuArray = await menuList();
|
||||
// support i18n
|
||||
menuArray.forEach((item) => {
|
||||
item.menuName = $t(item.menuName);
|
||||
});
|
||||
// const folderArray = menuArray.filter((item) => item.menuType === 'M');
|
||||
const menuTree = listToTree(menuArray, { id: 'menuId', pid: 'parentId' });
|
||||
const fullMenuTree = [
|
||||
{
|
||||
menuId: 0,
|
||||
menuName: '根目录',
|
||||
menuName: $t('menu.root'),
|
||||
children: menuTree,
|
||||
},
|
||||
];
|
||||
|
@@ -1,8 +1,9 @@
|
||||
import type { FormSchemaGetter, VxeGridProps } from '#/adapter';
|
||||
import type { VxeGridProps } from '#/adapter/vxe-table';
|
||||
|
||||
import { DictEnum } from '@vben/constants';
|
||||
import { getPopupContainer } from '@vben/utils';
|
||||
|
||||
import { type FormSchemaGetter } from '#/adapter/form';
|
||||
import { getDictOptions } from '#/utils/dict';
|
||||
import { renderDict } from '#/utils/render';
|
||||
|
||||
|
@@ -9,7 +9,11 @@ import { getPopupContainer } from '@vben/utils';
|
||||
import { Modal, Popconfirm, Space } from 'ant-design-vue';
|
||||
import dayjs from 'dayjs';
|
||||
|
||||
import { useVbenVxeGrid, type VxeGridProps } from '#/adapter';
|
||||
import {
|
||||
tableCheckboxEvent,
|
||||
useVbenVxeGrid,
|
||||
type VxeGridProps,
|
||||
} from '#/adapter/vxe-table';
|
||||
import { noticeList, noticeRemove } from '#/api/system/notice';
|
||||
|
||||
import { columns, querySchema } from './data';
|
||||
@@ -18,6 +22,9 @@ import noticeModal from './notice-modal.vue';
|
||||
const formOptions: VbenFormProps = {
|
||||
commonConfig: {
|
||||
labelWidth: 80,
|
||||
componentProps: {
|
||||
allowClear: true,
|
||||
},
|
||||
},
|
||||
schema: querySchema(),
|
||||
wrapperClass: 'grid-cols-1 md:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4',
|
||||
@@ -66,9 +73,7 @@ const gridOptions: VxeGridProps = {
|
||||
isHover: true,
|
||||
keyField: 'noticeId',
|
||||
},
|
||||
round: true,
|
||||
align: 'center',
|
||||
showOverflow: true,
|
||||
id: 'system-notice-index',
|
||||
};
|
||||
|
||||
const checked = ref(false);
|
||||
@@ -76,12 +81,8 @@ 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;
|
||||
},
|
||||
checkboxChange: tableCheckboxEvent(checked),
|
||||
checkboxAll: tableCheckboxEvent(checked),
|
||||
},
|
||||
});
|
||||
|
||||
@@ -122,10 +123,7 @@ function handleMultiDelete() {
|
||||
|
||||
<template>
|
||||
<Page :auto-content-height="true">
|
||||
<BasicTable>
|
||||
<template #toolbar-actions>
|
||||
<span class="pl-[7px] text-[16px]">通知公告列表</span>
|
||||
</template>
|
||||
<BasicTable table-title="通知公告列表">
|
||||
<template #toolbar-tools>
|
||||
<Space>
|
||||
<a-button
|
||||
|
@@ -5,7 +5,7 @@ import { useVbenModal } from '@vben/common-ui';
|
||||
import { $t } from '@vben/locales';
|
||||
import { cloneDeep } from '@vben/utils';
|
||||
|
||||
import { useVbenForm } from '#/adapter';
|
||||
import { useVbenForm } from '#/adapter/form';
|
||||
import { noticeAdd, noticeInfo, noticeUpdate } from '#/api/system/notice';
|
||||
|
||||
import { modalSchema } from './data';
|
||||
|
@@ -1,8 +1,10 @@
|
||||
import type { VxeGridProps } from '#/adapter/vxe-table';
|
||||
|
||||
import { DictEnum } from '@vben/constants';
|
||||
|
||||
import { Tag } from 'ant-design-vue';
|
||||
|
||||
import { type FormSchemaGetter, type VxeGridProps, z } from '#/adapter';
|
||||
import { type FormSchemaGetter, z } from '#/adapter/form';
|
||||
import { getDictOptions } from '#/utils/dict';
|
||||
|
||||
const accessPolicyOptions = [
|
||||
|
@@ -10,7 +10,11 @@ import { getPopupContainer } from '@vben/utils';
|
||||
import { Modal, Popconfirm, Space } from 'ant-design-vue';
|
||||
import dayjs from 'dayjs';
|
||||
|
||||
import { useVbenVxeGrid, type VxeGridProps } from '#/adapter';
|
||||
import {
|
||||
tableCheckboxEvent,
|
||||
useVbenVxeGrid,
|
||||
type VxeGridProps,
|
||||
} from '#/adapter/vxe-table';
|
||||
import {
|
||||
ossConfigChangeStatus,
|
||||
ossConfigList,
|
||||
@@ -23,6 +27,12 @@ import ossConfigDrawer from './oss-config-drawer.vue';
|
||||
|
||||
const formOptions: VbenFormProps = {
|
||||
schema: querySchema(),
|
||||
commonConfig: {
|
||||
labelWidth: 80,
|
||||
componentProps: {
|
||||
allowClear: true,
|
||||
},
|
||||
},
|
||||
wrapperClass: 'grid-cols-1 md:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4',
|
||||
};
|
||||
|
||||
@@ -69,9 +79,7 @@ const gridOptions: VxeGridProps = {
|
||||
isHover: true,
|
||||
keyField: 'ossConfigId',
|
||||
},
|
||||
round: true,
|
||||
align: 'center',
|
||||
showOverflow: true,
|
||||
id: 'system-oss-config-index',
|
||||
};
|
||||
|
||||
const checked = ref(false);
|
||||
@@ -79,12 +87,8 @@ 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;
|
||||
},
|
||||
checkboxChange: tableCheckboxEvent(checked),
|
||||
checkboxAll: tableCheckboxEvent(checked),
|
||||
},
|
||||
});
|
||||
|
||||
@@ -127,10 +131,7 @@ const { hasAccessByCodes } = useAccess();
|
||||
|
||||
<template>
|
||||
<Page :auto-content-height="true">
|
||||
<BasicTable>
|
||||
<template #toolbar-actions>
|
||||
<span class="pl-[7px] text-[16px]">oss配置列表</span>
|
||||
</template>
|
||||
<BasicTable table-title="oss配置列表">
|
||||
<template #toolbar-tools>
|
||||
<Space>
|
||||
<a-button
|
||||
|
@@ -5,7 +5,7 @@ import { useVbenDrawer } from '@vben/common-ui';
|
||||
import { $t } from '@vben/locales';
|
||||
import { cloneDeep } from '@vben/utils';
|
||||
|
||||
import { useVbenForm } from '#/adapter';
|
||||
import { useVbenForm } from '#/adapter/form';
|
||||
import {
|
||||
ossConfigAdd,
|
||||
ossConfigInfo,
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user