125 Commits

Author SHA1 Message Date
dap
a10a981fab Merge branch 'main' of https://github.com/vbenjs/vue-vben-admin into dev 2025-04-30 11:13:25 +08:00
Jin Mao
20c15f352f perf: page componet supports custom height offset for flexible content height … (#6081)
* perf: Page supports custom height offset for flexible content height control.

允许通过 height 属性调整页面内容高度计算。修改了 Page 组件以支持自定义高度偏移量,用于更灵活的内容高度控制。

* chore: typo

* perf(page): replace height with heightOffset for flexible content sizing

The `height` prop was replaced with `heightOffset` to better describe its purpose when used with `autoContentHeight`. The new prop allows custom offset values (in pixels) to adjust content area sizing, with clearer documentation.
2025-04-29 18:15:12 +08:00
Netfan
8aa7dabeff fix: calculation for collapsing search form is incorrect while initially hidden (#6068)
* 修复当默认隐藏搜索表单时,折叠位置的计算不正确的问题
2025-04-28 23:20:33 +08:00
vben
78c7c1589a chore: update readme.md 2025-04-28 23:11:34 +08:00
Vben
dd833ca56b chore: update dependencies and documentation, optimize build toolchain (#6060)
* chore: update packageManager version to pnpm@10.9.0 for compatibility improvements

* chore: Update dependent versions and configurations to improve compatibility and stability

- Update Node version to 22.1.0
- Updated pnpm version to 10.10.0
- Fixed syntax error in prettier command in lintstagedrc
- Update dependent versions in pnpm-lock.yaml to ensure consistency
- Update format and content in README documents to improve readability

* fix: lint error
2025-04-28 23:08:05 +08:00
vem
681c1dc267 fix: Update existing route index to prevent 404 on user switch (#6003)
Co-authored-by: tars-macmini <vem@qq.com>
2025-04-28 18:19:47 +08:00
Netfan
4545422ee0 fix: lock state will not change overflow style in drawer and modal (#6067)
* Modal和Drawer的锁定状态不再修改overflow样式
2025-04-28 17:02:54 +08:00
dap
5f26f5662e Merge branch 'main' of https://github.com/vbenjs/vue-vben-admin into dev 2025-04-28 13:19:57 +08:00
Gahotx
ca94ca906f fix: add rounded corners to project and quick nav items (#5296) 2025-04-27 22:50:42 +08:00
Vben
76de450c71 chore: update dependency version for improved stability and compatibility (#6023)
* chore: update dependency version for improved stability and compatibility

* fix: optimize clearPoints function in useCaptchaPoints hook to improve performance

* fix: make several props optional in various components for better flexibility
2025-04-27 22:06:49 +08:00
Trivikram Kamat
dd2b1ed580 fix: install corepack from npm (#5905)
* fix: install corepack from npm

* docs: install corepack from npm
2025-04-27 22:03:35 +08:00
ming4762
baec89f896 perf: resolve duplicate component names (#6039) 2025-04-27 22:02:38 +08:00
vben
7c7051a11e chore: release v5.5.5 2025-04-27 21:45:10 +08:00
Netfan
aa27a2f7a1 feat: encrypt the privacy data when it is persisted (#6056)
* 对私密数据持久化时执行加密
* 将锁屏密码合并到accessStore中进行加密
2025-04-27 20:59:10 +08:00
Jin Mao
9ee6d06d50 docs: add deepWiki doc link (#6057) 2025-04-27 20:54:07 +08:00
ming4762
0cc1cb5a7b perf: improve destroyOnClose for VbenDrawer&VbenModal (#6051)
* fix: fix that the default value of modal destroyOnClose does not take effect

* perf: improve destroyOnClose for VbenDrawer
2025-04-27 11:26:50 +08:00
dap
e662681ce2 fix: 拖拽上传在单文件时的样式 2025-04-27 09:57:16 +08:00
Netfan
0a9fc4e02d fix: title of search button in vxeTable toolbar (#6046)
* 修改vxeTable工具栏里的搜索按钮的提示文案
2025-04-26 01:08:41 +08:00
Netfan
be840460d8 feat: vbenSelect support prop allowClear (#6043) 2025-04-25 23:37:03 +08:00
Netfan
cb45987fe2 docs: update example (#6036)
* 跟进后端菜单逻辑的修改,现已无需传递basicLayout布局
2025-04-25 11:44:47 +08:00
panda7
5ffd7db8e0 fix: the initial value echo for the check-button-group (#6029)
Co-authored-by: sqchen <9110848@qq.com>
2025-04-25 08:35:03 +08:00
Netfan
14377705e7 fix: alert confirm state in beforeClose callback (#6019) 2025-04-23 12:20:52 +08:00
dap
23503778d4 chore: 去除vxe的锁定 2025-04-22 17:56:22 +08:00
dap
f54fab0bae Merge branch 'main' of https://github.com/vbenjs/vue-vben-admin into dev 2025-04-22 17:55:44 +08:00
pangyajun123
b985ff0584 fix: vxe-table theme token follow primary color (#6007) 2025-04-21 19:15:05 +08:00
dap
eff2f2a0b1 Merge branch 'main' of https://github.com/vbenjs/vue-vben-admin into dev 2025-04-20 09:14:37 +08:00
dap
664fa800cd chore: 搜定vxe版本 2025-04-20 09:13:08 +08:00
dap
5dc4448c01 docs: changelog 2025-04-20 09:06:32 +08:00
dap
ccfe992779 chore: 暂时锁定vxe版本(样式问题) 2025-04-20 09:05:57 +08:00
dap
583504495d feat: 对模板的说明... 2025-04-20 08:53:50 +08:00
dap
7fb4bf3431 fix: 工作流list展示在开启缩放会有误差导致触底逻辑不会触发 2025-04-20 08:42:19 +08:00
wyc001122
b148b8ec92 fix: fix geader menu activation path (#5997)
Co-authored-by: 王泳超 <wangyongchao@testor.com.cn>
2025-04-19 14:35:33 +08:00
Netfan
79de6bcbf7 fix: alert send wrong confirm state to beforeClose (#5991)
* 修复alert在按下Esc或者点击遮罩关闭时,可能发送错误的isConfirm状态
2025-04-17 22:23:05 +08:00
Netfan
14bd6dd25d fix: destroyOnClose works within connectedComponent (#5989)
* 修复destroyOnClose没能销毁connectedComponent自身的问题
2025-04-17 20:25:49 +08:00
dap
9b577261e2 chore: vite6.3.1已经修复开发/打包问题 解除版本锁定 2025-04-17 14:21:47 +08:00
PIPEDREA_WZJ
7f269e0d69 Update tailwindcss.md (#5602)
tailwindcss最新的版本已经是v4.x,vben中使用的是3.x的tailwindcss。在未进行兼容前,会出现运行失败的问题
2025-04-17 14:01:39 +08:00
yuh
4baec83db5 feat: add examples: form-upload (#5955)
* feat: add examples: form-upload

* fix: upload: accept and label

* fix: upload: 设置表单值、图片预览
2025-04-17 14:00:46 +08:00
dap
7d8416890b docs: changelog 2025-04-16 21:53:29 +08:00
dap
2e2ffcd59e Merge branch 'main' of https://github.com/vbenjs/vue-vben-admin into dev 2025-04-16 21:33:11 +08:00
dap
2046bfa846 chore: 暂时锁定vite版本 会导致i18n插件打包失败 2025-04-16 19:57:07 +08:00
dap
0446adf778 refactor: 菜单图标更新 2025-04-16 17:38:11 +08:00
Netfan
f7a4d13a4c fix: fixed arguments of callbacks in formApi (#5970)
* 修复 `handleValuesChange` 传递的参数不是处理后的表单值的问题

* 修复 `handleReset` 未能传递正确参数的问题
2025-04-16 14:11:04 +08:00
dap
e587256425 update: placeholder update 2025-04-16 13:53:58 +08:00
Netfan
0936861da1 feat: pass fieldsChanged into the handleValuesChange callback function (#5968)
* fieldsChanged(已被改变值的字段名)将传入handleValuesChange回调函数
2025-04-16 11:29:01 +08:00
ming4762
3318d76bab perf: improve destroyOnClose for VbenModal (#5964) 2025-04-16 11:28:36 +08:00
LinaBell
8f3881eabf perf: beforeClose of drawer support promise (#5932)
* perf: the beforeClose function of drawer is consistent with that of modal

* refactor: drawer test update
2025-04-16 11:27:13 +08:00
zhouda1fu
5252480b09 fix: missing await in department form(#5967) 2025-04-16 11:22:59 +08:00
dap
f096dfc6e6 Merge branch 'main' of https://github.com/vbenjs/vue-vben-admin into dev 2025-04-16 10:09:48 +08:00
Netfan
d18f56177c docs: update alert and apiComponent docs (#5961) 2025-04-15 20:52:23 +08:00
wyc001122
333998b518 fix: determine if scrollbar has been totally scrolled (#5934)
* 修复在系统屏幕缩放比例不为100%的情况下,滚动组件对是否已滚动到边界的判断可能不正确的问题
2025-04-15 20:51:38 +08:00
ming4762
3fb4fba1cb fix: modal closing animation (#5960) 2025-04-15 18:49:57 +08:00
ming4762
c7e6210c8d feat: modal&drawer support center-footer slot (#5956) 2025-04-15 16:04:44 +08:00
lztb
d864085c13 feat: vben-form添加arrayToStringFields属性 (#5957)
* feat: vben-form添加arrayToStringFields属性

* feat: 修改handleArrayToStringFields和handleStringToArrayFields中嵌套数组格式的处理不一致

---------

Co-authored-by: 米山 <17726957223@189.cn>
2025-04-15 16:03:20 +08:00
Netfan
fcdc1a1602 feat: add more expose methods for apiComponent (#5958)
* 为ApiComponent组件添加getOptions和getValue导出方法。
2025-04-15 15:32:30 +08:00
Netfan
bf7496f0d5 feat: add useAlertContext for Alert component (#5947)
* 新增Alert的子组件中获取弹窗上下文的能力
2025-04-15 00:00:05 +08:00
Netfan
9700150653 fix: table actions in fixed column (#5945) 2025-04-14 19:56:52 +08:00
Netfan
f0e9e55af2 feat: alert support customize footer (#5940)
* Alert组件支持自定义footer
2025-04-14 11:48:21 +08:00
Netfan
ff88274554 fix: long navigation menu can be scrolled (#5939)
* 修复超长的导航菜单无法纵向滚动的问题
2025-04-14 11:18:33 +08:00
ming4762
afce9dc5c0 perf: improve destroyOnClose for VbenModal (#5935)
* perf: 优化Vben Modal destroyOnClose,解决destroyOnClose=false,Modal依旧会被销毁的问题

影响范围(重要):destroyOnClose默认为true,这会导致所有的modal都会默认渲染到body
radix-vue Dialog组件默认会销毁挂载的组件,所以即使destroyOnClose=false,Modal依旧会被销毁的问题
对于一些大表单重复渲染导致卡顿,ApiComponent也会频繁的加载数据

* fix: modal closing animation

---------

Co-authored-by: Netfan <netfan@foxmail.com>
2025-04-13 23:02:07 +08:00
ming4762
b5700bd0b1 perf: improve autoSelect of ApiComponent (#5936)
* fix: 修复autoSelect不生效的问题,props.valueField已经被omit了

* feat: ApiComponent autoSelect支持使用函数,可以满足灵活性要求更高的场景
2025-04-13 20:03:18 +08:00
dap
e085083e42 Merge branch 'main' of https://github.com/vbenjs/vue-vben-admin into dev 2025-04-12 22:28:23 +08:00
dap
a47910f650 refactor: 所有表格操作列宽度调整为'auto', 这样会根据子元素宽度适配(比如没有分配权限的情况) 2025-04-12 15:01:28 +08:00
Netfan
a8c4786311 feat: api-component support autoSelect prop (#5931)
* feat: api-component support autoSelect prop

* docs: add version requirement
2025-04-12 14:02:35 +08:00
Netfan
2971ccc0b7 docs: docs modal z-index fixed, update alert docs (#5930) 2025-04-12 13:41:40 +08:00
dap
4ead56eaf1 fix: onClosed 2025-04-12 10:44:53 +08:00
dap
4fad8d77de refactor: 角色管理 auto 2025-04-12 10:42:16 +08:00
dap
9db1087d32 update: 岗位 useBeforeCloseDiff 2025-04-12 10:38:14 +08:00
Netfan
4a2c7b313f fix: alert animation (#5927) 2025-04-12 10:37:47 +08:00
dap
0f5fc5f54c fix: onClosed 2025-04-12 10:34:44 +08:00
dap
76108e7b8f refactor: 宽度设置为auto(根据子元素宽度动态变化) 2025-04-12 10:31:43 +08:00
dap
6018817906 chore: version动态获取 2025-04-12 10:20:12 +08:00
dap
7e4bdf7bd6 Merge branch 'main' of https://github.com/vbenjs/vue-vben-admin into dev 2025-04-12 09:56:51 +08:00
dap
32117574f6 chore: version update 2025-04-12 09:54:50 +08:00
dap
a48dfa1de2 fix: 新增dictType不显示 2025-04-12 09:44:27 +08:00
Netfan
36bf6fc149 fix: builtin color change throttled in preference drawer (#5924)
修复偏好设置中的自定义主题色拖动选择颜色时页面会明显卡顿的问题
2025-04-12 01:44:08 +08:00
Netfan
f46ec30995 fix: theme mode follow the system only auto (#5923)
* 修复主题在未设置为auto时,仍然会跟随系统主题变化的问题。
2025-04-12 01:16:57 +08:00
Netfan
9bd5a190c2 fix: alert action button focus, fixed #5921 (#5922)
* 修复Alert组件的按钮焦点切换问题
2025-04-12 00:59:56 +08:00
dap
4dc7543bb6 docs: changelog 2025-04-11 13:29:27 +08:00
dap
d8e7945f9f docs: changelog 2025-04-11 13:24:34 +08:00
dap
2fd1fdcb32 refactor: 更改header参数ClientID命名 2025-04-11 11:33:23 +08:00
zhang
86da3cedc2 chore: 导出框架自带的组件,方便独立页面使用 (#5876) 2025-04-09 16:16:56 +08:00
dap
44ba945a12 fix: 无法点击遮罩关闭 2025-04-09 15:07:14 +08:00
dap
2680101872 fix: 无法点击遮罩关闭 2025-04-09 15:06:00 +08:00
dap
1c2e27613c refactor: 富文本/上传同步改为异步组件导入 2025-04-09 10:03:47 +08:00
dap
3e7a2336b0 Merge branch 'main' of https://github.com/vbenjs/vue-vben-admin into dev 2025-04-09 09:34:17 +08:00
dap
022d5182d7 fix: onCancel -> onClosed 2025-04-09 09:22:13 +08:00
Netfan
329a176a5c perf: optimize bootstrap modules to speed up first-screen loading (#5899)
优化首屏加载速度
2025-04-09 01:05:20 +08:00
dap
41962ef380 docs: changelog 2025-04-08 21:07:09 +08:00
dap
9003df713c fix: vxe新版需要单独设置headerCellConfig 2025-04-08 21:04:01 +08:00
dap
ebb4738be7 refactor: 流程定义 useBeforeCloseDiff 2025-04-08 20:58:09 +08:00
dap
ad7c33a7d6 refactor: 流程分类 useBeforeCloseDiff 2025-04-08 20:55:34 +08:00
dap
a114335a56 refactor: oss配置 useBeforeCloseDiff 2025-04-08 20:53:44 +08:00
Netfan
9379093a4f feat: customizable table separator (#5898)
* 表格的分隔条支持定制背景色或完全移除
2025-04-08 20:28:50 +08:00
ming4762
c9014d0338 perf: 优化关闭页面切换动画的tab切换性能 (#5883) 2025-04-08 20:27:03 +08:00
dap
b8ec8edb38 update: 字典 colorpicker 2025-04-08 19:33:35 +08:00
Netfan
ed26dca64e chore: update pnpm-lock.yaml 2025-04-08 16:31:41 +08:00
Netfan
08c6496e24 chore: update deps 2025-04-08 14:56:40 +08:00
Netfan
a8c5df38e9 fix: possible circular reference issue during build (#5894)
* 修复构建期间出现的循环引用警告
2025-04-08 14:50:05 +08:00
dap
5b9f647cfd update: [vxe table v4.12.5] 参数 "row-config.height" 已废弃,请使用 "cell-config.height" 2025-04-08 13:29:06 +08:00
dap
ae6bf6ee53 refactor: 用户drawer Promise逻辑重构 2025-04-08 12:03:15 +08:00
dap
77894d5df4 update: i18n更新 2025-04-08 11:09:07 +08:00
dap
ba8f36a2c0 update: 移除老版本的不需要组件/代码 2025-04-08 11:04:12 +08:00
dap
133abe9ded refactor: 角色权限 useBeforeCloseDiff 2025-04-08 11:02:36 +08:00
dap
ef390ae636 refactor: 租户套餐useBeforeCloseDiff 2025-04-08 10:57:08 +08:00
dap
6d2f4e8486 refactor: 租户管理 useBeforeCloseDiff 2025-04-08 10:54:28 +08:00
dap
c4962aaf85 refactor: 客户端管理useBeforeCloseDiff 2025-04-08 10:51:16 +08:00
dap
f7128b099e refactor: 通知公告 useBeforeCloseDiff 2025-04-08 10:47:18 +08:00
dap
5510b6dea4 refactor: 字典useBeforeCloseDiff 2025-04-08 10:40:32 +08:00
dap
98f658d46f refactor: 部门管理useBeforeCloseDiff 2025-04-08 10:34:26 +08:00
dap
e307db2f3d refactor: useBeforeCloseDiff 2025-04-08 10:30:56 +08:00
dap
e6dab8300d refactor: 角色管理 useBeforeCloseDiff 2025-04-08 10:22:21 +08:00
dap
eb9f278e7f refactor: useBeforeCloseDiff 2025-04-08 10:10:15 +08:00
dap
34e5812de9 update: vxe active color 2025-04-07 19:37:11 +08:00
dap
07587c0faf update: 用户管理 表单更新(非最终方案) 2025-04-07 19:02:28 +08:00
dap
88316d7498 refactor: useBeforeCloseDiff逻辑更新 2025-04-07 18:48:46 +08:00
dap
53e02d46c2 docs: changelog 2025-04-07 18:44:42 +08:00
dap
5e1de6fc79 fix: 表格固定高度 getVxePopupContainer 2025-04-07 18:41:23 +08:00
dap
7463df053a update: 去除字典动画 2025-04-07 18:25:21 +08:00
dap
1286b52135 fix: getVxePopupContainer 2025-04-07 17:21:49 +08:00
dap
92fe406ae9 update: 字典loading 2025-04-07 17:20:41 +08:00
dap
5b72d9b79d refactor: 移除deepWatch参数 2025-04-07 13:05:30 +08:00
dap
b97fe47afd fix: 直接使用.value无法触发useForm的更新(原生是正常的) 需要修改地址 2025-04-07 12:53:20 +08:00
dap
4f2354b53a update: 兼容以前代码 先返回body 这样会造成无法跟随滚动 2025-04-07 11:10:10 +08:00
dap
8f9006c96d fix: vxe 右上角toolbar按钮色/翻页主题色保持一致 2025-04-07 10:58:51 +08:00
Netfan
71e8d12b70 fix: improve prompt component (#5879)
* fix: prompt component render fixed

* fix: alert buttonAlign default value
2025-04-07 01:21:30 +08:00
235 changed files with 3353 additions and 1300 deletions

View File

@@ -2,5 +2,5 @@ ports:
- port: 5555
onOpen: open-preview
tasks:
- init: corepack enable && pnpm install
- init: npm i -g corepack && pnpm install
command: pnpm run dev:play

View File

@@ -15,6 +15,6 @@ export default {
],
'package.json': ['prettier --cache --write'],
'{!(package)*.json,*.code-snippets,.!(browserslist)*rc}': [
'prettier --cache --write--parser json',
'prettier --cache --write --parser json',
],
};

View File

@@ -1 +1 @@
20.14.0
22.1.0

19
.vscode/settings.json vendored
View File

@@ -14,7 +14,7 @@
"editor.tabSize": 2,
"editor.detectIndentation": false,
"editor.cursorBlinking": "expand",
"editor.largeFileOptimizations": false,
"editor.largeFileOptimizations": true,
"editor.accessibilitySupport": "off",
"editor.cursorSmoothCaretAnimation": "on",
"editor.guides.bracketPairs": "active",
@@ -91,6 +91,7 @@
"**/bower_components": true,
"**/.turbo": true,
"**/.idea": true,
"**/.vitepress": true,
"**/tmp": true,
"**/.git": true,
"**/.svn": true,
@@ -113,6 +114,8 @@
"**/yarn.lock": true
},
"typescript.tsserver.exclude": ["**/node_modules", "**/dist", "**/.turbo"],
// search
"search.searchEditor.singleClickBehaviour": "peekDefinition",
"search.followSymlinks": false,
@@ -224,10 +227,20 @@
"commentTranslate.multiLineMerge": true,
"vue.server.hybridMode": true,
"vitest.disableWorkspaceWarning": true,
"cSpell.words": ["tinymce", "vditor"],
"typescript.tsdk": "node_modules/typescript/lib",
"editor.linkedEditing": true, // 自动同步更改html标签,
"vscodeCustomCodeColor.highlightValue": "v-access", // v-access显示的颜色
"vscodeCustomCodeColor.highlightValueColor": "#CCFFFF",
"oxc.enable": false
"oxc.enable": false,
"cSpell.words": [
"archiver",
"axios",
"dotenv",
"isequal",
"jspm",
"napi",
"nolebase",
"rollup",
"vitest"
]
}

View File

@@ -1,3 +1,44 @@
# 1.3.3
**BUG FIX**
- 工作流list展示在开启缩放会有误差导致触底逻辑不会触发
**OTHER**
- 代码生成预览对模板的提示...(下载都懒得点一下吗)
# 1.3.2
**REFACTOR**
- 所有表格操作列宽度调整为'auto', 这样会根据子元素宽度适配(比如没有分配权限的情况)
- 菜单图标更新了一部分 sql同步更新
**OTHER**
- 暂时锁死vite依赖 i18n会报错
# 1.3.1
**REFACTOR**
- 所有Modal/Drawer表单关闭前会进行表单数据对比来弹出提示框
- 字典项颜色选择从`原生input type=color`改为`vue3-colorpicker`组件
- 全局Header: ClientID 更改大小写 [spring的问题导致](https://gitee.com/dapppp/ruoyi-plus-vben5/issues/IC0BDS)
**BUG FIX**
- getVxePopupContainer逻辑调整 解决表格固定高度展开不全的问题
**FEATURES**
- 字典渲染支持loading(length为0情况)
**OTHERS**
- useForm的组件改为异步导入(官方更新) bootstrap.js体积从2M降到600K 首屏加载速度提升
# 1.3.0
注意: 如果你使用老版本的`文件上传`/`图片上传` 可暂时使用

View File

@@ -1,8 +1,13 @@
<div align="center"> <a href="https://github.com/anncwb/vue-vben-admin"> <img alt="VbenAdmin Logo" width="215" src="https://unpkg.com/@vbenjs/static-source@0.1.7/source/logo-v1.webp"> </a> <br> <br>
<div align="center">
<a href="https://github.com/anncwb/vue-vben-admin">
<img alt="VbenAdmin Logo" width="215" src="https://unpkg.com/@vbenjs/static-source@0.1.7/source/logo-v1.webp">
</a>
<br>
<br>
[![license](https://img.shields.io/github/license/anncwb/vue-vben-admin.svg)](LICENSE)
<h1>Vue Vben Admin</h1>
<h1>Vue Vben Admin</h1>
</div>
[![Quality Gate Status](https://sonarcloud.io/api/project_badges/measure?project=vbenjs_vue-vben-admin&metric=alert_status)](https://sonarcloud.io/summary/new_code?id=vbenjs_vue-vben-admin) ![codeql](https://github.com/vbenjs/vue-vben-admin/actions/workflows/codeql.yml/badge.svg) ![build](https://github.com/vbenjs/vue-vben-admin/actions/workflows/build.yml/badge.svg) ![ci](https://github.com/vbenjs/vue-vben-admin/actions/workflows/ci.yml/badge.svg) ![deploy](https://github.com/vbenjs/vue-vben-admin/actions/workflows/deploy.yml/badge.svg)
@@ -15,27 +20,27 @@ Vue Vben Adminは、最新の`vue3`、`vite`、`TypeScript`などの主流技術
## アップグレード通知
これは最新バージョン5.0であり、以前のバージョンとは互換性がありません。新しいプロジェクトを開始する場合は、最新バージョンを使用することをお勧めします。古いバージョンを表示したい場合は、[v2ブランチ](https://github.com/vbenjs/vue-vben-admin/tree/v2)を使用してください。
これは最新バージョン `5.0` であり、以前のバージョンとは互換性がありません。新しいプロジェクトを開始する場合は、最新バージョンを使用することをお勧めします。古いバージョンを表示したい場合は、[v2ブランチ](https://github.com/vbenjs/vue-vben-admin/tree/v2)を使用してください。
## 特徴
- **最新技術スタック**: Vue 3やViteなどの最先端フロントエンド技術で開発
- **TypeScript**: アプリケーション規模のJavaScriptのための言語
- **テーマ**: 複数のテーマカラーが利用可能で、カスタマイズオプションも豊富
- **国際化**: 完全な内蔵国際化サポート
- **権限管理**: 動的ルートベースの権限生成ソリューションを内蔵
- **最新技術スタック**Vue 3やViteなどの最先端フロントエンド技術で開発
- **TypeScript**アプリケーション規模のJavaScriptのための言語
- **テーマ**複数のテーマカラーが利用可能で、カスタマイズオプションも豊富
- **国際化**完全な内蔵国際化サポート
- **権限管理**動的ルートベースの権限生成ソリューションを内蔵
## プレビュー
- [Vben Admin](https://vben.pro/) - フルバージョンの中国語サイト
テストアカウント: vben/123456
テストアカウントvben/123456
<p align="center">
<img alt="VbenAdmin Logo" width="100%" src="https://anncwb.github.io/anncwb/images/preview1.png">
<img alt="VbenAdmin Logo" width="100%" src="https://anncwb.github.io/anncwb/images/preview2.png">
<img alt="VbenAdmin Logo" width="100%" src="https://anncwb.github.io/anncwb/images/preview3.png">
</p>
<div align="center">
<img alt="VbenAdmin Logo" width="100%" src="https://anncwb.github.io/anncwb/images/preview1.png">
<img alt="VbenAdmin Logo" width="100%" src="https://anncwb.github.io/anncwb/images/preview2.png">
<img alt="VbenAdmin Logo" width="100%" src="https://anncwb.github.io/anncwb/images/preview3.png">
</div>
### Gitpodを使用
@@ -49,30 +54,27 @@ GitpodGitHub用の無料オンライン開発環境でプロジェクト
## インストールと使用
- プロジェクトコードを取得
1. プロジェクトコードを取得
```bash
git clone https://github.com/vbenjs/vue-vben-admin.git
```
- 依存関係のインストール
2. 依存関係のインストール
```bash
cd vue-vben-admin
corepack enable
npm i -g corepack
pnpm install
```
- 実行
3. 実行
```bash
pnpm dev
```
- ビルド
4. ビルド
```bash
pnpm build
@@ -86,40 +88,39 @@ pnpm build
ご参加をお待ちしております![Issueを提出](https://github.com/anncwb/vue-vben-admin/issues/new/choose)するか、Pull Requestを送信してください。
**Pull Request:**
**Pull Request プロセス:**
1. コードをフォーク
2. 自分のブランチを作成: `git checkout -b feat/xxxx`
3. 変更をコミット: `git commit -am 'feat(function): add xxxxx'`
4. ブランチをプッシュ: `git push origin feat/xxxx`
1. コードをフォーク
2. 自分のブランチを作成`git checkout -b feat/xxxx`
3. 変更をコミット`git commit -am 'feat(function): add xxxxx'`
4. ブランチをプッシュ`git push origin feat/xxxx`
5. `pull request`を送信
## Git貢献提出規則
- 参考 [vue](https://github.com/vuejs/vue/blob/dev/.github/COMMIT_CONVENTION.md) 規則 ([Angular](https://github.com/conventional-changelog/conventional-changelog/tree/master/packages/conventional-changelog-angular))
参考 [vue](https://github.com/vuejs/vue/blob/dev/.github/COMMIT_CONVENTION.md) 規則 ([Angular](https://github.com/conventional-changelog/conventional-changelog/tree/master/packages/conventional-changelog-angular))
- `feat` 新機能の追加
- `fix` 問題/バグの修正
- `style` コードスタイルに関連し、実行結果に影響しない
- `perf` 最適化/パフォーマンス向上
- `refactor` リファクタリング
- `revert` 変更の取り消し
- `test` テスト関連
- `docs` ドキュメント/注釈
- `chore` 依存関係の更新/スキャフォールディング設定の変更など
- `ci` 継続的インテグレーション
- `types` 型定義ファイルの変更
- `wip` 開発中
- `feat` 新機能の追加
- `fix` 問題/バグの修正
- `style` コードスタイルに関連し、実行結果に影響しない
- `perf` 最適化/パフォーマンス向上
- `refactor` リファクタリング
- `revert` 変更の取り消し
- `test` テスト関連
- `docs` ドキュメント/注釈
- `chore` 依存関係の更新/スキャフォールディング設定の変更など
- `ci` 継続的インテグレーション
- `types` 型定義ファイルの変更
## ブラウザサポート
ローカル開発には`Chrome 80+`ブラウザを推奨します
ローカル開発には `Chrome 80+` ブラウザを推奨します
モダンブラウザをサポートし、IEはサポートしません
| [<img src="https://raw.githubusercontent.com/alrra/browser-logos/master/src/edge/edge_48x48.png" alt=" Edge" width="24px" height="24px" />](http://godban.github.io/browsers-support-badges/)</br>IE | [<img src="https://raw.githubusercontent.com/alrra/browser-logos/master/src/edge/edge_48x48.png" alt=" Edge" width="24px" height="24px" />](http://godban.github.io/browsers-support-badges/)</br>Edge | [<img src="https://raw.githubusercontent.com/alrra/browser-logos/master/src/firefox/firefox_48x48.png" alt="Firefox" width="24px" height="24px" />](http://godban.github.io/browsers-support-badges/)</br>Firefox | [<img src="https://raw.githubusercontent.com/alrra/browser-logos/master/src/chrome/chrome_48x48.png" alt="Chrome" width="24px" height="24px" />](http://godban.github.io/browsers-support-badges/)</br>Chrome | [<img src="https://raw.githubusercontent.com/alrra/browser-logos/master/src/safari/safari_48x48.png" alt="Safari" width="24px" height="24px" />](http://godban.github.io/browsers-support-badges/)</br>Safari |
| :-: | :-: | :-: | :-: | :-: |
| サポートしない | 最新2バージョン | 最新2バージョン | 最新2バージョン | 最新2バージョン |
| [<img src="https://raw.githubusercontent.com/alrra/browser-logos/master/src/edge/edge_48x48.png" alt="Edge" width="24px" height="24px" />](http://godban.github.io/browsers-support-badges/)</br>Edge | [<img src="https://raw.githubusercontent.com/alrra/browser-logos/master/src/firefox/firefox_48x48.png" alt="Firefox" width="24px" height="24px" />](http://godban.github.io/browsers-support-badges/)</br>Firefox | [<img src="https://raw.githubusercontent.com/alrra/browser-logos/master/src/chrome/chrome_48x48.png" alt="Chrome" width="24px" height="24px" />](http://godban.github.io/browsers-support-badges/)</br>Chrome | [<img src="https://raw.githubusercontent.com/alrra/browser-logos/master/src/safari/safari_48x48.png" alt="Safari" width="24px" height="24px" />](http://godban.github.io/browsers-support-badges/)</br>Safari |
| :-: | :-: | :-: | :-: |
| 最新2バージョン | 最新2バージョン | 最新2バージョン | 最新2バージョン |
## メンテナー
@@ -140,8 +141,7 @@ pnpm build
## 貢献者
<a href="https://github.com/vbenjs/vue-vben-admin/graphs/contributors">
<img alt="Contributors"
src="https://opencollective.com/vbenjs/contributors.svg?button=false" />
<img alt="Contributors" src="https://opencollective.com/vbenjs/contributors.svg?button=false" />
</a>
## Discord

View File

@@ -1,8 +1,13 @@
<div align="center"> <a href="https://github.com/anncwb/vue-vben-admin"> <img alt="VbenAdmin Logo" width="215" src="https://unpkg.com/@vbenjs/static-source@0.1.7/source/logo-v1.webp"> </a> <br> <br>
<div align="center">
<a href="https://github.com/anncwb/vue-vben-admin">
<img alt="VbenAdmin Logo" width="215" src="https://unpkg.com/@vbenjs/static-source@0.1.7/source/logo-v1.webp">
</a>
<br>
<br>
[![license](https://img.shields.io/github/license/anncwb/vue-vben-admin.svg)](LICENSE)
<h1>Vue Vben Admin</h1>
<h1>Vue Vben Admin</h1>
</div>
[![Quality Gate Status](https://sonarcloud.io/api/project_badges/measure?project=vbenjs_vue-vben-admin&metric=alert_status)](https://sonarcloud.io/summary/new_code?id=vbenjs_vue-vben-admin) ![codeql](https://github.com/vbenjs/vue-vben-admin/actions/workflows/codeql.yml/badge.svg) ![build](https://github.com/vbenjs/vue-vben-admin/actions/workflows/build.yml/badge.svg) ![ci](https://github.com/vbenjs/vue-vben-admin/actions/workflows/ci.yml/badge.svg) ![deploy](https://github.com/vbenjs/vue-vben-admin/actions/workflows/deploy.yml/badge.svg)
@@ -17,7 +22,7 @@ Vue Vben Admin is a free and open source middle and back-end template. Using the
This is the latest version, 5.0, and it is not compatible with previous versions. If you are starting a new project, it is recommended to use the latest version. If you wish to view the old version, please use the [v2 branch](https://github.com/vbenjs/vue-vben-admin/tree/v2).
## Feature
## Features
- **Latest Technology Stack**: Developed with cutting-edge front-end technologies like Vue 3 and Vite
- **TypeScript**: A language for application-scale JavaScript
@@ -31,11 +36,11 @@ This is the latest version, 5.0, and it is not compatible with previous versions
Test Account: vben/123456
<p align="center">
<img alt="VbenAdmin Logo" width="100%" src="https://anncwb.github.io/anncwb/images/preview1.png">
<img alt="VbenAdmin Logo" width="100%" src="https://anncwb.github.io/anncwb/images/preview2.png">
<img alt="VbenAdmin Logo" width="100%" src="https://anncwb.github.io/anncwb/images/preview3.png">
</p>
<div align="center">
<img alt="VbenAdmin Logo" width="100%" src="https://anncwb.github.io/anncwb/images/preview1.png">
<img alt="VbenAdmin Logo" width="100%" src="https://anncwb.github.io/anncwb/images/preview2.png">
<img alt="VbenAdmin Logo" width="100%" src="https://anncwb.github.io/anncwb/images/preview3.png">
</div>
### Use Gitpod
@@ -47,31 +52,29 @@ Open the project in Gitpod (free online dev environment for GitHub) and start co
[Document](https://doc.vben.pro/)
## Install and use
## Install and Use
- Get the project code
1. Get the project code
```bash
git clone https://github.com/vbenjs/vue-vben-admin.git
```
- Installation dependencies
2. Install dependencies
```bash
cd vue-vben-admin
corepack enable
npm i -g corepack
pnpm install
```
- run
3. Run
```bash
pnpm dev
```
- build
4. Build
```bash
pnpm build
@@ -81,44 +84,43 @@ pnpm build
[CHANGELOG](https://github.com/vbenjs/vue-vben-admin/releases)
## How to contribute
## How to Contribute
You are very welcome to join[Raise an issue](https://github.com/anncwb/vue-vben-admin/issues/new/choose) Or submit a Pull Request
You are very welcome to join! [Raise an issue](https://github.com/anncwb/vue-vben-admin/issues/new/choose) or submit a Pull Request.
**Pull Request:**
**Pull Request Process:**
1. Fork code!
2. Create your own branch: `git checkout -b feat/xxxx`
1. Fork the code
2. Create your branch: `git checkout -b feat/xxxx`
3. Submit your changes: `git commit -am 'feat(function): add xxxxx'`
4. Push your branch: `git push origin feat/xxxx`
5. submit`pull request`
5. Submit `pull request`
## Git Contribution submission specification
## Git Contribution Submission Specification
- reference [vue](https://github.com/vuejs/vue/blob/dev/.github/COMMIT_CONVENTION.md) specification ([Angular](https://github.com/conventional-changelog/conventional-changelog/tree/master/packages/conventional-changelog-angular))
Reference [vue](https://github.com/vuejs/vue/blob/dev/.github/COMMIT_CONVENTION.md) specification ([Angular](https://github.com/conventional-changelog/conventional-changelog/tree/master/packages/conventional-changelog-angular))
- `feat` Add new features
- `fix` Fix the problem/BUG
- `style` The code style is related and does not affect the running result
- `perf` Optimization/performance improvement
- `refactor` Refactor
- `revert` Undo edit
- `test` Test related
- `docs` Documentation/notes
- `chore` Dependency update/scaffolding configuration modification etc.
- `ci` Continuous integration
- `types` Type definition file changes
- `wip` In development
- `feat` Add new features
- `fix` Fix the problem/BUG
- `style` The code style is related and does not affect the running result
- `perf` Optimization/performance improvement
- `refactor` Refactor
- `revert` Undo edit
- `test` Test related
- `docs` Documentation/notes
- `chore` Dependency update/scaffolding configuration modification etc.
- `ci` Continuous integration
- `types` Type definition file changes
## Browser support
## Browser Support
The `Chrome 80+` browser is recommended for local development
Support modern browsers, not IE
| [<img src="https://raw.githubusercontent.com/alrra/browser-logos/master/src/edge/edge_48x48.png" alt=" Edge" width="24px" height="24px" />](http://godban.github.io/browsers-support-badges/)</br>IE | [<img src="https://raw.githubusercontent.com/alrra/browser-logos/master/src/edge/edge_48x48.png" alt=" Edge" width="24px" height="24px" />](http://godban.github.io/browsers-support-badges/)</br>Edge | [<img src="https://raw.githubusercontent.com/alrra/browser-logos/master/src/firefox/firefox_48x48.png" alt="Firefox" width="24px" height="24px" />](http://godban.github.io/browsers-support-badges/)</br>Firefox | [<img src="https://raw.githubusercontent.com/alrra/browser-logos/master/src/chrome/chrome_48x48.png" alt="Chrome" width="24px" height="24px" />](http://godban.github.io/browsers-support-badges/)</br>Chrome | [<img src="https://raw.githubusercontent.com/alrra/browser-logos/master/src/safari/safari_48x48.png" alt="Safari" width="24px" height="24px" />](http://godban.github.io/browsers-support-badges/)</br>Safari |
| :-: | :-: | :-: | :-: | :-: |
| not support | last 2 versions | last 2 versions | last 2 versions | last 2 versions |
| [<img src="https://raw.githubusercontent.com/alrra/browser-logos/master/src/edge/edge_48x48.png" alt="Edge" width="24px" height="24px" />](http://godban.github.io/browsers-support-badges/)</br>Edge | [<img src="https://raw.githubusercontent.com/alrra/browser-logos/master/src/firefox/firefox_48x48.png" alt="Firefox" width="24px" height="24px" />](http://godban.github.io/browsers-support-badges/)</br>Firefox | [<img src="https://raw.githubusercontent.com/alrra/browser-logos/master/src/chrome/chrome_48x48.png" alt="Chrome" width="24px" height="24px" />](http://godban.github.io/browsers-support-badges/)</br>Chrome | [<img src="https://raw.githubusercontent.com/alrra/browser-logos/master/src/safari/safari_48x48.png" alt="Safari" width="24px" height="24px" />](http://godban.github.io/browsers-support-badges/)</br>Safari |
| :-: | :-: | :-: | :-: |
| last 2 versions | last 2 versions | last 2 versions | last 2 versions |
## Maintainer
@@ -136,11 +138,10 @@ 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: #408aee;border-radius: 4px;" href="https://www.paypal.com/paypalme/cvvben">Paypal Me</a>
## Contributor
## Contributors
<a href="https://github.com/vbenjs/vue-vben-admin/graphs/contributors">
<img alt="Contributors"
src="https://opencollective.com/vbenjs/contributors.svg?button=false" />
<img alt="Contributors" src="https://opencollective.com/vbenjs/contributors.svg?button=false" />
</a>
## Discord

View File

@@ -76,7 +76,7 @@ admin 账号: admin admin123
git clone https://gitee.com/dapppp/ruoyi-plus-vben5.git
```
- 安装依赖
2. 安装依赖
```bash
cd ruoyi-plus-vben5
@@ -150,7 +150,7 @@ VITE_GLOB_WEBSOCKET_ENABLE=false
pnpm dev:antd
```
- 打包
4. 打包
```bash
pnpm build:antd
@@ -164,21 +164,21 @@ pnpm build:antd
## Git 贡献提交规范
- 参考 [vue](https://github.com/vuejs/vue/blob/dev/.github/COMMIT_CONVENTION.md) 规范 ([Angular](https://github.com/conventional-changelog/conventional-changelog/tree/master/packages/conventional-changelog-angular))
参考 [vue](https://github.com/vuejs/vue/blob/dev/.github/COMMIT_CONVENTION.md) 规范 ([Angular](https://github.com/conventional-changelog/conventional-changelog/tree/master/packages/conventional-changelog-angular))
- `feat` 增加新功能
- `fix` 修复问题/BUG
- `style` 代码风格相关无影响运行结果的
- `perf` 优化/性能提升
- `refactor` 重构
- `revert` 撤销修改
- `test` 测试相关
- `docs` 文档/注释
- `chore` 依赖更新/脚手架配置修改等
- `workflow` 工作流改进
- `ci` 持续集成
- `types` 类型定义文件更改
- `wip` 开发中
- `feat` 增加新功能
- `fix` 修复问题/BUG
- `style` 代码风格相关无影响运行结果的
- `perf` 优化/性能提升
- `refactor` 重构
- `revert` 撤销修改
- `test` 测试相关
- `docs` 文档/注释
- `chore` 依赖更新/脚手架配置修改等
- `workflow` 工作流改进
- `ci` 持续集成
- `types` 类型定义文件更改
- `wip` 开发中
## 浏览器支持
@@ -186,7 +186,7 @@ pnpm build:antd
本地开发推荐使用`Chrome` 最新版本浏览器
支持现代浏览器, 不支持 IE
支持现代浏览器不支持 IE
| [<img src="https://raw.githubusercontent.com/alrra/browser-logos/master/src/edge/edge_48x48.png" alt=" Edge" width="24px" height="24px" />](http://godban.github.io/browsers-support-badges/)</br>IE | [<img src="https://raw.githubusercontent.com/alrra/browser-logos/master/src/edge/edge_48x48.png" alt=" Edge" width="24px" height="24px" />](http://godban.github.io/browsers-support-badges/)</br>Edge | [<img src="https://raw.githubusercontent.com/alrra/browser-logos/master/src/firefox/firefox_48x48.png" alt="Firefox" width="24px" height="24px" />](http://godban.github.io/browsers-support-badges/)</br>Firefox | [<img src="https://raw.githubusercontent.com/alrra/browser-logos/master/src/chrome/chrome_48x48.png" alt="Chrome" width="24px" height="24px" />](http://godban.github.io/browsers-support-badges/)</br>Chrome | [<img src="https://raw.githubusercontent.com/alrra/browser-logos/master/src/safari/safari_48x48.png" alt="Safari" width="24px" height="24px" />](http://godban.github.io/browsers-support-badges/)</br>Safari |
| :-: | :-: | :-: | :-: | :-: |

View File

@@ -0,0 +1,13 @@
import { verifyAccessToken } from '~/utils/jwt-utils';
import { unAuthorizedResponse } from '~/utils/response';
export default eventHandler((event) => {
const userinfo = verifyAccessToken(event);
if (!userinfo) {
return unAuthorizedResponse(event);
}
return useResponseSuccess({
url: 'https://unpkg.com/@vbenjs/static-source@0.1.7/source/logo-v1.webp',
});
// return useResponseError("test")
});

View File

@@ -7,6 +7,7 @@ export default defineEventHandler(() => {
<li><a href="/api/menu">/api/menu/all</a></li>
<li><a href="/api/auth/codes">/api/auth/codes</a></li>
<li><a href="/api/auth/login">/api/auth/login</a></li>
<li><a href="/api/upload">/api/upload</a></li>
</ul>
`;
});

View File

@@ -3,3 +3,6 @@ VITE_APP_TITLE=Plus Admin
# 应用命名空间用于缓存、store等功能的前缀确保隔离
VITE_APP_NAMESPACE=vben-web-antd
# 对store进行加密的密钥在将store持久化到localStorage时会使用该密钥进行加密
VITE_APP_STORE_SECURE_KEY=please-replace-me-with-your-own-key

View File

@@ -1,6 +1,6 @@
{
"name": "@vben/web-antd",
"version": "1.3.0",
"version": "1.3.2",
"homepage": "https://vben.pro",
"bugs": "https://github.com/vbenjs/vue-vben-admin/issues",
"repository": {
@@ -54,7 +54,8 @@
"tinymce": "^7.3.0",
"unplugin-vue-components": "^0.27.3",
"vue": "catalog:",
"vue-router": "catalog:"
"vue-router": "catalog:",
"vue3-colorpicker": "^2.3.0"
},
"devDependencies": {
"@types/crypto-js": "^4.2.2",

View File

@@ -8,58 +8,92 @@ import type { Component } from 'vue';
import type { BaseFormComponentType } from '@vben/common-ui';
import type { Recordable } from '@vben/types';
import { computed, defineComponent, getCurrentInstance, h, ref } from 'vue';
import {
defineAsyncComponent,
defineComponent,
getCurrentInstance,
h,
ref,
} from 'vue';
import { ApiComponent, globalShareState, IconPicker } 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 { notification } from 'ant-design-vue';
import { Tinymce as RichTextarea } from '#/components/tinymce';
import { FileUpload, ImageUpload } from '#/components/upload';
import { FileUploadOld, ImageUploadOld } from '#/components/upload-old';
const RichTextarea = defineAsyncComponent(() =>
import('#/components/tinymce/index').then((res) => res.Tinymce),
);
const FileUpload = defineAsyncComponent(() =>
import('#/components/upload').then((res) => res.FileUpload),
);
const ImageUpload = defineAsyncComponent(() =>
import('#/components/upload').then((res) => res.ImageUpload),
);
const AutoComplete = defineAsyncComponent(
() => import('ant-design-vue/es/auto-complete'),
);
const Button = defineAsyncComponent(() => import('ant-design-vue/es/button'));
const Checkbox = defineAsyncComponent(
() => import('ant-design-vue/es/checkbox'),
);
const CheckboxGroup = defineAsyncComponent(() =>
import('ant-design-vue/es/checkbox').then((res) => res.CheckboxGroup),
);
const DatePicker = defineAsyncComponent(
() => import('ant-design-vue/es/date-picker'),
);
const Divider = defineAsyncComponent(() => import('ant-design-vue/es/divider'));
const Input = defineAsyncComponent(() => import('ant-design-vue/es/input'));
const InputNumber = defineAsyncComponent(
() => import('ant-design-vue/es/input-number'),
);
const InputPassword = defineAsyncComponent(() =>
import('ant-design-vue/es/input').then((res) => res.InputPassword),
);
const Mentions = defineAsyncComponent(
() => import('ant-design-vue/es/mentions'),
);
const Radio = defineAsyncComponent(() => import('ant-design-vue/es/radio'));
const RadioGroup = defineAsyncComponent(() =>
import('ant-design-vue/es/radio').then((res) => res.RadioGroup),
);
const RangePicker = defineAsyncComponent(() =>
import('ant-design-vue/es/date-picker').then((res) => res.RangePicker),
);
const Rate = defineAsyncComponent(() => import('ant-design-vue/es/rate'));
const Select = defineAsyncComponent(() => import('ant-design-vue/es/select'));
const Space = defineAsyncComponent(() => import('ant-design-vue/es/space'));
const Switch = defineAsyncComponent(() => import('ant-design-vue/es/switch'));
const Textarea = defineAsyncComponent(() =>
import('ant-design-vue/es/input').then((res) => res.Textarea),
);
const TimePicker = defineAsyncComponent(
() => import('ant-design-vue/es/time-picker'),
);
const TreeSelect = defineAsyncComponent(
() => import('ant-design-vue/es/tree-select'),
);
const Upload = defineAsyncComponent(() => import('ant-design-vue/es/upload'));
const withDefaultPlaceholder = <T extends Component>(
component: T,
type: 'input' | 'select',
componentProps: Recordable<any> = {},
) => {
return defineComponent({
inheritAttrs: false,
name: component.name,
inheritAttrs: false,
setup: (props: any, { attrs, expose, slots }) => {
/**
* 需要使用computed 否则后续updateSchema更新的placeholder无法显示(响应式问题)
*/
const placeholder = computed(
() =>
props?.placeholder ||
attrs?.placeholder ||
$t(`ui.placeholder.${type}`),
);
const placeholder =
props?.placeholder ||
attrs?.placeholder ||
$t(`ui.placeholder.${type}`);
// 透传组件暴露的方法
const innerRef = ref();
@@ -78,7 +112,7 @@ const withDefaultPlaceholder = <T extends Component>(
component,
{
...componentProps,
placeholder: placeholder.value,
placeholder,
...props,
...attrs,
ref: innerRef,
@@ -128,20 +162,34 @@ async function initComponentAdapter() {
// 如果你的组件体积比较大,可以使用异步加载
// Button: () =>
// import('xxx').then((res) => res.Button),
ApiSelect: withDefaultPlaceholder(ApiComponent, 'select', {
component: Select,
loadingSlot: 'suffixIcon',
visibleEvent: 'onDropdownVisibleChange',
modelPropName: 'value',
}),
ApiTreeSelect: withDefaultPlaceholder(ApiComponent, 'select', {
component: TreeSelect,
fieldNames: { label: 'label', value: 'value', children: 'children' },
loadingSlot: 'suffixIcon',
modelPropName: 'value',
optionsPropName: 'treeData',
visibleEvent: 'onVisibleChange',
}),
ApiSelect: withDefaultPlaceholder(
{
...ApiComponent,
name: 'ApiSelect',
},
'select',
{
component: Select,
loadingSlot: 'suffixIcon',
visibleEvent: 'onDropdownVisibleChange',
modelPropName: 'value',
},
),
ApiTreeSelect: withDefaultPlaceholder(
{
...ApiComponent,
name: 'ApiTreeSelect',
},
'select',
{
component: TreeSelect,
fieldNames: { label: 'label', value: 'value', children: 'children' },
loadingSlot: 'suffixIcon',
modelPropName: 'value',
optionsPropName: 'treeData',
visibleEvent: 'onVisibleChange',
},
),
AutoComplete,
Checkbox,
CheckboxGroup,

View File

@@ -93,9 +93,12 @@ function createRequestClient(baseURL: string) {
const language = preferences.app.locale.replace('-', '_');
config.headers['Accept-Language'] = language;
config.headers['Content-Language'] = language;
// 添加全局clientId
config.headers.clientId = clientId;
/**
* 添加全局clientId
* 关于header的clientId被错误绑定到实体类
* https://gitee.com/dapppp/ruoyi-plus-vben5/issues/IC0BDS
*/
config.headers.ClientID = clientId;
/**
* 格式化get/delete参数
* 如果包含自定义的paramsSerializer则不走此逻辑

View File

@@ -1,8 +1,7 @@
import { createApp, watchEffect } from 'vue';
import { registerAccessDirective } from '@vben/access';
import { initTippy, registerLoadingDirective } from '@vben/common-ui';
import { MotionPlugin } from '@vben/plugins/motion';
import { registerLoadingDirective } from '@vben/common-ui/es/loading';
import { preferences } from '@vben/preferences';
import { initStores } from '@vben/stores';
import '@vben/styles';
@@ -50,12 +49,14 @@ async function bootstrap(namespace: string) {
registerAccessDirective(app);
// 初始化 tippy
const { initTippy } = await import('@vben/common-ui/es/tippy');
initTippy(app);
// 配置路由及路由守卫
app.use(router);
// 配置Motion插件
const { MotionPlugin } = await import('@vben/plugins/motion');
app.use(MotionPlugin);
// 动态更新标题

View File

@@ -4,7 +4,7 @@ import type { DictData } from '#/api/system/dict/dict-data-model';
import { computed } from 'vue';
import { Tag } from 'ant-design-vue';
import { Spin, Tag } from 'ant-design-vue';
import { tagTypes } from './data';
@@ -41,12 +41,22 @@ const label = computed<number | string>(() => {
});
const tagComponent = computed(() => (color.value ? Tag : 'div'));
const loading = computed(() => {
return props.dicts?.length === 0;
});
</script>
<template>
<div>
<component :is="tagComponent" :class="cssClass" :color="color">
<component
v-if="!loading"
:is="tagComponent"
:class="cssClass"
:color="color"
>
{{ label }}
</component>
<Spin v-else :spinning="true" size="small" />
</div>
</template>

View File

@@ -98,7 +98,7 @@ Upload.Dragger只会影响样式
{{ $t('component.upload.upload') }}
</a-button>
</div>
<div v-if="enableDragUpload && innerFileList?.length < maxCount">
<div v-if="enableDragUpload">
<p class="ant-upload-drag-icon">
<InboxOutlined />
</p>

View File

@@ -204,7 +204,12 @@ export function useUpload(
if (props.maxCount === 1) {
bindValue.value = ossId;
} else {
(bindValue.value as string[]).push(ossId);
// 给默认值
if (!Array.isArray(bindValue.value)) {
bindValue.value = [];
}
// 直接使用.value无法触发useForm的更新(原生是正常的) 需要修改地址
bindValue.value = [...bindValue.value, ossId];
}
break;
}
@@ -344,12 +349,16 @@ export function useUpload(
!props.keepMissingId &&
props.maxCount !== 1
) {
bindValue.value = (bindValue.value as string[]).filter((ossId) =>
// 给默认值
if (!Array.isArray(bindValue.value)) {
bindValue.value = [];
}
bindValue.value = bindValue.value.filter((ossId) =>
resp.map((res) => res.ossId).includes(ossId),
);
}
},
{ immediate: true, deep: props.deepWatch },
{ immediate: true },
);
return {

View File

@@ -87,13 +87,6 @@ export interface BaseUploadProps {
* @default false
*/
enableDragUpload?: boolean;
/**
* 是否开启深度监听
* 默认外部的数组地址重新改变才会触发watch 不会监听内部元素的变化
* 开启后 无论内部还是外部改变都会触发查询信息接口(包括上传后, 删除等操作都会触发)
* @default false
*/
deepWatch?: boolean;
/**
* 当ossId查询不到文件信息时 比如被删除了
* 是否保留列表对应的ossId 默认不保留

View File

@@ -21,6 +21,7 @@
"preview": "Preview",
"tip": "Tip",
"enable": "On",
"disable": "Off"
"disable": "Off",
"beforeCloseTip": "You have unsaved changes. Are you sure you want to exit?"
}
}

View File

@@ -21,6 +21,7 @@
"preview": "预览",
"tip": "提示",
"enable": "启用",
"disable": "禁用"
"disable": "禁用",
"beforeCloseTip": "您有未保存的更改,确认要退出吗?"
}
}

View File

@@ -2,10 +2,10 @@ import type { RouteRecordRaw } from 'vue-router';
import { DEFAULT_HOME_PATH, LOGIN_PATH } from '@vben/constants';
import { AuthPageLayout, BasicLayout } from '#/layouts';
import { $t } from '#/locales';
import Login from '#/views/_core/authentication/login.vue';
const BasicLayout = () => import('#/layouts/basic.vue');
const AuthPageLayout = () => import('#/layouts/auth.vue');
/** 全局404页面 */
const fallbackNotFoundRoute: RouteRecordRaw = {
component: () => import('#/views/_core/fallback/not-found.vue'),
@@ -58,7 +58,7 @@ const coreRoutes: RouteRecordRaw[] = [
{
name: 'Login',
path: 'login',
component: Login,
component: () => import('#/views/_core/authentication/login.vue'),
meta: {
title: $t('page.auth.login'),
},

View File

@@ -2,6 +2,11 @@ import type { RouteRecordStringComponent } from '@vben/types';
import { $t } from '@vben/locales';
const {
version,
// vite inject-metadata 插件注入的全局变量
} = __VBEN_ADMIN_METADATA__ || {};
/**
* 该文件放非后台返回的路由 比如个人中心 等需要跳转显示的页面
*/
@@ -134,8 +139,8 @@ export const localMenuList: RouteRecordStringComponent[] = [
icon: 'lucide:book-open-text',
keepAlive: true,
title: '更新记录',
badge: '1.3.0',
badgeVariants: '#CC0033',
badge: `当前: ${version}`,
badgeVariants: 'bg-primary',
},
},
],

View File

@@ -59,6 +59,7 @@ export const useDictStore = defineStore('app-dict', () => {
}
function resetCache() {
dictRequestCache.clear();
dictOptionsMap.clear();
/**
* 不需要清空dictRequestCache 每次请求成功/失败都清空key

View File

@@ -29,43 +29,52 @@ interface BeforeCloseDiffProps {
}
/**
* @deprecated 注意为实验性功能 可能有api变动/被移除
* 用于Drawer/Modal使用 判断表单是否有变动来决定是否弹窗提示
* @param props props
* @returns hook
*
* 待解决问题: 网速慢情况直接关闭 会导致数据不一致问题
* 但是使用api.lock会导致在报错情况无法关闭(因为目前代码没有finally)
*/
export function useBeforeCloseDiff(props: BeforeCloseDiffProps) {
const { initializedGetter, currentGetter, compare } = props;
/**
* 记录初始值 json
*/
const initialized = ref<string>('');
/**
* 是否已经初始化了 通过这个值判断是否需要进行对比 为false直接关闭 不弹窗
*/
const isInitialized = ref(false);
const isSubmitted = ref(false);
async function updateInitialized(data?: string) {
/**
* 标记是否已经完成初始化 后续需要进行对比
* @param data 自定义初始化数据 可选
*/
async function markInitialized(data?: string) {
initialized.value = data || (await initializedGetter());
isInitialized.value = true;
}
function setSubmitted() {
isSubmitted.value = true;
/**
* 重置初始化状态 需要在closed前调用 或者打开窗口时
*/
function resetInitialized() {
initialized.value = '';
isInitialized.value = false;
}
/**
* 提供给useVbenForm/useVbenDrawer使用
* @returns 是否允许关闭
*/
async function onBeforeClose(): Promise<boolean> {
// 如果还未初始化,直接允许关闭
if (!isInitialized.value) {
return true;
}
// 如果已经提交过,直接允许关闭
if (isSubmitted.value) {
// 重置状态
isSubmitted.value = false;
return true;
}
try {
// 获取当前表单数据
const current = await currentGetter();
// 自定义比较的情况
if (isFunction(compare) && compare(initialized.value, current)) {
return true;
} else {
@@ -79,7 +88,7 @@ export function useBeforeCloseDiff(props: BeforeCloseDiffProps) {
return new Promise<boolean>((resolve) => {
Modal.confirm({
title: $t('pages.common.tip'),
content: $t('您有未保存的更改,确认要退出吗?'),
content: $t('pages.common.beforeCloseTip'),
centered: true,
okButtonProps: { danger: true },
cancelText: $t('common.cancel'),
@@ -99,8 +108,8 @@ export function useBeforeCloseDiff(props: BeforeCloseDiffProps) {
return {
onBeforeClose,
updateInitialized,
setSubmitted,
markInitialized,
resetInitialized,
};
}

View File

@@ -51,7 +51,8 @@ export const columns: VxeGridProps['columns'] = [
fixed: 'right',
slots: { default: 'action' },
title: '操作',
width: 180,
resizable: false,
width: 'auto',
},
];

View File

@@ -60,7 +60,8 @@ export const columns: VxeGridProps['columns'] = [
fixed: 'right',
slots: { default: 'action' },
title: '操作',
width: 180,
resizable: false,
width: 'auto',
},
];

View File

@@ -79,6 +79,7 @@ export const columns: VxeGridProps['columns'] = [
fixed: 'right',
slots: { default: 'action' },
title: '操作',
width: 120,
resizable: false,
width: 'auto',
},
];

View File

@@ -86,6 +86,7 @@ export const columns: VxeGridProps['columns'] = [
fixed: 'right',
slots: { default: 'action' },
title: '操作',
width: 120,
resizable: false,
width: 'auto',
},
];

View File

@@ -7,6 +7,7 @@ import { cloneDeep } from '@vben/utils';
import { useVbenForm } from '#/adapter/form';
import { clientAdd, clientInfo, clientUpdate } from '#/api/system/client';
import { defaultFormValueGetter, useBeforeCloseDiff } from '#/utils/popup';
import { drawerSchema } from './data';
import SecretInput from './secret-input.vue';
@@ -55,6 +56,13 @@ function setupForm(update: boolean) {
]);
}
const { onBeforeClose, markInitialized, resetInitialized } = useBeforeCloseDiff(
{
initializedGetter: defaultFormValueGetter(formApi),
currentGetter: defaultFormValueGetter(formApi),
},
);
// 提取生成状态字段Schema的函数
const getStatusSchema = (disabled: boolean) => [
{
@@ -64,13 +72,15 @@ const getStatusSchema = (disabled: boolean) => [
];
const [BasicDrawer, drawerApi] = useVbenDrawer({
onCancel: handleCancel,
onBeforeClose,
onClosed: handleClosed,
onConfirm: handleConfirm,
async onOpenChange(isOpen) {
if (!isOpen) {
return null;
}
drawerApi.drawerLoading(true);
const { id } = drawerApi.getData() as { id?: number | string };
isUpdate.value = !!id;
// 初始化
@@ -84,36 +94,39 @@ const [BasicDrawer, drawerApi] = useVbenDrawer({
// 新增模式: 确保状态字段可用
formApi.updateSchema(getStatusSchema(false));
}
await markInitialized();
drawerApi.drawerLoading(false);
},
});
async function handleConfirm() {
try {
drawerApi.drawerLoading(true);
drawerApi.lock(true);
const { valid } = await formApi.validate();
if (!valid) {
return;
}
const data = cloneDeep(await formApi.getValues());
await (isUpdate.value ? clientUpdate(data) : clientAdd(data));
resetInitialized();
emit('reload');
await handleCancel();
drawerApi.close();
} catch (error) {
console.error(error);
} finally {
drawerApi.drawerLoading(false);
drawerApi.lock(false);
}
}
async function handleCancel() {
drawerApi.close();
async function handleClosed() {
await formApi.resetForm();
resetInitialized();
}
</script>
<template>
<BasicDrawer :close-on-click-modal="false" :title="title" class="w-[600px]">
<BasicDrawer :title="title" class="w-[600px]">
<BasicForm>
<template #clientSecret="slotProps">
<SecretInput v-bind="slotProps" :disabled="isUpdate" />

View File

@@ -95,7 +95,8 @@ export const columns: VxeGridProps['columns'] = [
fixed: 'right',
slots: { default: 'action' },
title: '操作',
width: 180,
resizable: false,
width: 'auto',
},
];

View File

@@ -26,10 +26,12 @@ const [BasicForm, formApi] = useVbenForm({
showDefaultActions: false,
});
const { onBeforeClose, updateInitialized, setSubmitted } = useBeforeCloseDiff({
initializedGetter: defaultFormValueGetter(formApi),
currentGetter: defaultFormValueGetter(formApi),
});
const { onBeforeClose, markInitialized, resetInitialized } = useBeforeCloseDiff(
{
initializedGetter: defaultFormValueGetter(formApi),
currentGetter: defaultFormValueGetter(formApi),
},
);
const [BasicModal, modalApi] = useVbenModal({
fullscreenButton: false,
@@ -40,22 +42,18 @@ const [BasicModal, modalApi] = useVbenModal({
if (!isOpen) {
return null;
}
try {
modalApi.lock(true);
modalApi.modalLoading(true);
const { id } = modalApi.getData() as { id?: number | string };
isUpdate.value = !!id;
const { id } = modalApi.getData() as { id?: number | string };
isUpdate.value = !!id;
if (isUpdate.value && id) {
const record = await configInfo(id);
await formApi.setValues(record);
}
await updateInitialized();
} catch (error) {
console.error(error);
} finally {
modalApi.lock(false);
if (isUpdate.value && id) {
const record = await configInfo(id);
await formApi.setValues(record);
}
await markInitialized();
modalApi.modalLoading(false);
},
});
@@ -68,7 +66,7 @@ async function handleConfirm() {
}
const data = cloneDeep(await formApi.getValues());
await (isUpdate.value ? configUpdate(data) : configAdd(data));
setSubmitted();
resetInitialized();
emit('reload');
modalApi.close();
} catch (error) {
@@ -80,6 +78,7 @@ async function handleConfirm() {
async function handleClosed() {
await formApi.resetForm();
resetInitialized();
}
</script>

View File

@@ -71,7 +71,8 @@ export const columns: VxeGridProps['columns'] = [
fixed: 'right',
slots: { default: 'action' },
title: '操作',
width: 180,
resizable: false,
width: 'auto',
},
];

View File

@@ -39,11 +39,13 @@ export const columns: VxeGridProps['columns'] = [
{
field: 'orderNum',
title: '排序',
width: 180,
resizable: false,
width: 'auto',
},
{
field: 'status',
width: 180,
resizable: false,
width: 'auto',
title: '状态',
slots: {
default: ({ row }) => {

View File

@@ -16,6 +16,7 @@ import {
deptUpdate,
} from '#/api/system/dept';
import { listUserByDeptId } from '#/api/system/user';
import { defaultFormValueGetter, useBeforeCloseDiff } from '#/utils/popup';
import { drawerSchema } from './data';
@@ -107,8 +108,16 @@ async function setLeaderOptions() {
]);
}
const { onBeforeClose, markInitialized, resetInitialized } = useBeforeCloseDiff(
{
initializedGetter: defaultFormValueGetter(formApi),
currentGetter: defaultFormValueGetter(formApi),
},
);
const [BasicDrawer, drawerApi] = useVbenDrawer({
onCancel: handleCancel,
onBeforeClose,
onClosed: handleClosed,
onConfirm: handleConfirm,
async onOpenChange(isOpen) {
if (!isOpen) {
@@ -130,6 +139,7 @@ const [BasicDrawer, drawerApi] = useVbenDrawer({
await (update && id ? initDeptUsers(id) : setLeaderOptions());
/** 部门选择 下拉框 */
await initDeptSelect(id);
await markInitialized();
drawerApi.drawerLoading(false);
},
@@ -137,30 +147,31 @@ const [BasicDrawer, drawerApi] = useVbenDrawer({
async function handleConfirm() {
try {
drawerApi.drawerLoading(true);
drawerApi.lock(true);
const { valid } = await formApi.validate();
if (!valid) {
return;
}
const data = cloneDeep(await formApi.getValues());
await (isUpdate.value ? deptUpdate(data) : deptAdd(data));
resetInitialized();
emit('reload');
await handleCancel();
drawerApi.close();
} catch (error) {
console.error(error);
} finally {
drawerApi.drawerLoading(false);
drawerApi.lock(false);
}
}
async function handleCancel() {
drawerApi.close();
async function handleClosed() {
await formApi.resetForm();
resetInitialized();
}
</script>
<template>
<BasicDrawer :close-on-click-modal="false" :title="title" class="w-[600px]">
<BasicDrawer :title="title" class="w-[600px]">
<BasicForm />
</BasicDrawer>
</template>

View File

@@ -45,7 +45,8 @@ export const columns: VxeGridProps['columns'] = [
fixed: 'right',
slots: { default: 'action' },
title: '操作',
width: 180,
resizable: false,
width: 'auto',
},
];

View File

@@ -12,6 +12,7 @@ import {
dictDetailInfo,
} from '#/api/system/dict/dict-data';
import { tagTypes } from '#/components/dict';
import { defaultFormValueGetter, useBeforeCloseDiff } from '#/utils/popup';
import { drawerSchema } from './data';
import TagStylePicker from './tag-style-picker.vue';
@@ -57,8 +58,16 @@ function setupSelectType(listClass: string) {
selectType.value = isDefault ? 'default' : 'custom';
}
const { onBeforeClose, markInitialized, resetInitialized } = useBeforeCloseDiff(
{
initializedGetter: defaultFormValueGetter(formApi),
currentGetter: defaultFormValueGetter(formApi),
},
);
const [BasicDrawer, drawerApi] = useVbenDrawer({
onCancel: handleCancel,
onBeforeClose,
onClosed: handleClosed,
onConfirm: handleConfirm,
async onOpenChange(isOpen) {
if (!isOpen) {
@@ -68,13 +77,14 @@ const [BasicDrawer, drawerApi] = useVbenDrawer({
const { dictCode, dictType } = drawerApi.getData() as DrawerProps;
isUpdate.value = !!dictCode;
formApi.setFieldValue('dictType', dictType);
await formApi.setFieldValue('dictType', dictType);
if (dictCode && isUpdate.value) {
const record = await dictDetailInfo(dictCode);
setupSelectType(record.listClass);
await formApi.setValues(record);
}
await markInitialized();
drawerApi.drawerLoading(false);
},
@@ -82,7 +92,7 @@ const [BasicDrawer, drawerApi] = useVbenDrawer({
async function handleConfirm() {
try {
drawerApi.drawerLoading(true);
drawerApi.lock(true);
const { valid } = await formApi.validate();
if (!valid) {
return;
@@ -93,19 +103,20 @@ async function handleConfirm() {
data.listClass = '';
}
await (isUpdate.value ? dictDataUpdate(data) : dictDataAdd(data));
resetInitialized();
emit('reload');
await handleCancel();
drawerApi.close();
} catch (error) {
console.error(error);
} finally {
drawerApi.drawerLoading(false);
drawerApi.lock(false);
}
}
async function handleCancel() {
drawerApi.close();
async function handleClosed() {
await formApi.resetForm();
selectType.value = 'default';
resetInitialized();
}
/**
@@ -117,7 +128,7 @@ async function handleDeSelect() {
</script>
<template>
<BasicDrawer :close-on-click-modal="false" :title="title" class="w-[600px]">
<BasicDrawer :title="title" class="w-[600px]">
<BasicForm>
<template #listClass="slotProps">
<TagStylePicker

View File

@@ -1,14 +1,17 @@
<script setup lang="ts">
import type { RadioChangeEvent } from 'ant-design-vue';
import type { PropType } from 'vue';
import { computed } from 'vue';
import { Input, RadioGroup, Select } from 'ant-design-vue';
import { usePreferences } from '@vben/preferences';
import { RadioGroup, Select } from 'ant-design-vue';
import { ColorPicker } from 'vue3-colorpicker';
import { tagSelectOptions } from '#/components/dict';
import 'vue3-colorpicker/style.css';
/**
* 需要禁止透传
* 不禁止会有奇怪的bug 会绑定到selectType上
@@ -32,23 +35,26 @@ const computedOptions = computed(
type SelectType = (typeof options)[number]['value'];
const selectType = defineModel('selectType', {
const selectType = defineModel<SelectType>('selectType', {
default: 'default',
type: String as PropType<SelectType>,
});
/**
* color必须为hex颜色或者undefined
*/
const color = defineModel('value', {
const color = defineModel<string | undefined>('value', {
default: undefined,
type: String as PropType<string | undefined>,
});
function handleSelectTypeChange(e: RadioChangeEvent) {
// 必须给默认hex颜色 不能为空字符串
color.value = e.target.value === 'custom' ? '#000000' : undefined;
color.value = e.target.value === 'custom' ? '#1677ff' : undefined;
}
const { isDark } = usePreferences();
const theme = computed(() => {
return isDark.value ? 'black' : 'white';
});
</script>
<template>
@@ -69,15 +75,12 @@ function handleSelectTypeChange(e: RadioChangeEvent) {
placeholder="请选择标签样式"
@deselect="$emit('deselect')"
/>
<Input
<ColorPicker
v-if="selectType === 'custom'"
v-model:value="color"
class="flex-1"
disabled
>
<template #addonAfter>
<input v-model="color" class="rounded-lg" type="color" />
</template>
</Input>
disable-alpha
format="hex"
v-model:pure-color="color"
:theme="theme"
/>
</div>
</template>

View File

@@ -39,7 +39,8 @@ export const columns: VxeGridProps['columns'] = [
fixed: 'right',
slots: { default: 'action' },
title: '操作',
width: 180,
resizable: false,
width: 'auto',
},
];
@@ -71,7 +72,6 @@ export const modalSchema: FormSchemaGetter = () => [
{
component: 'Textarea',
fieldName: 'remark',
formItemClass: 'items-start',
label: '备注',
},
];

View File

@@ -11,6 +11,7 @@ import {
dictTypeInfo,
dictTypeUpdate,
} from '#/api/system/dict/dict-type';
import { defaultFormValueGetter, useBeforeCloseDiff } from '#/utils/popup';
import { modalSchema } from './data';
@@ -22,6 +23,7 @@ const title = computed(() => {
});
const [BasicForm, formApi] = useVbenForm({
layout: 'vertical',
commonConfig: {
labelWidth: 100,
},
@@ -29,51 +31,63 @@ const [BasicForm, formApi] = useVbenForm({
showDefaultActions: false,
});
const { onBeforeClose, markInitialized, resetInitialized } = useBeforeCloseDiff(
{
initializedGetter: defaultFormValueGetter(formApi),
currentGetter: defaultFormValueGetter(formApi),
},
);
const [BasicModal, modalApi] = useVbenModal({
fullscreenButton: false,
onCancel: handleCancel,
onBeforeClose,
onClosed: handleClosed,
onConfirm: handleConfirm,
onOpenChange: async (isOpen) => {
if (!isOpen) {
return null;
}
modalApi.modalLoading(true);
const { id } = modalApi.getData() as { id?: number | string };
isUpdate.value = !!id;
if (isUpdate.value && id) {
const record = await dictTypeInfo(id);
await formApi.setValues(record);
}
await markInitialized();
modalApi.modalLoading(false);
},
});
async function handleConfirm() {
try {
modalApi.modalLoading(true);
modalApi.lock(true);
const { valid } = await formApi.validate();
if (!valid) {
return;
}
const data = cloneDeep(await formApi.getValues());
await (isUpdate.value ? dictTypeUpdate(data) : dictTypeAdd(data));
resetInitialized();
emit('reload');
await handleCancel();
modalApi.close();
} catch (error) {
console.error(error);
} finally {
modalApi.modalLoading(false);
modalApi.lock(false);
}
}
async function handleCancel() {
modalApi.close();
async function handleClosed() {
await formApi.resetForm();
resetInitialized();
}
</script>
<template>
<BasicModal :close-on-click-modal="false" :title="title">
<BasicModal :title="title">
<BasicForm />
</BasicModal>
</template>

View File

@@ -145,7 +145,8 @@ export const columns: VxeGridProps['columns'] = [
fixed: 'right',
slots: { default: 'action' },
title: '操作',
width: 200,
resizable: false,
width: 'auto',
},
];

View File

@@ -12,6 +12,7 @@ import {
import { useVbenForm } from '#/adapter/form';
import { menuAdd, menuInfo, menuList, menuUpdate } from '#/api/system/menu';
import { defaultFormValueGetter, useBeforeCloseDiff } from '#/utils/popup';
import { drawerSchema } from './data';
@@ -88,14 +89,23 @@ async function setupMenuSelect() {
]);
}
const { onBeforeClose, markInitialized, resetInitialized } = useBeforeCloseDiff(
{
initializedGetter: defaultFormValueGetter(formApi),
currentGetter: defaultFormValueGetter(formApi),
},
);
const [BasicDrawer, drawerApi] = useVbenDrawer({
onCancel: handleCancel,
onBeforeClose,
onClosed: handleClosed,
onConfirm: handleConfirm,
async onOpenChange(isOpen) {
if (!isOpen) {
return null;
}
drawerApi.drawerLoading(true);
const { id, update } = drawerApi.getData() as ModalProps;
isUpdate.value = update;
@@ -108,36 +118,39 @@ const [BasicDrawer, drawerApi] = useVbenDrawer({
await formApi.setValues(record);
}
}
await markInitialized();
drawerApi.drawerLoading(false);
},
});
async function handleConfirm() {
try {
drawerApi.drawerLoading(true);
drawerApi.lock(true);
const { valid } = await formApi.validate();
if (!valid) {
return;
}
const data = cloneDeep(await formApi.getValues());
await (isUpdate.value ? menuUpdate(data) : menuAdd(data));
resetInitialized();
emit('reload');
await handleCancel();
drawerApi.close();
} catch (error) {
console.error(error);
} finally {
drawerApi.drawerLoading(false);
drawerApi.lock(false);
}
}
async function handleCancel() {
drawerApi.close();
async function handleClosed() {
await formApi.resetForm();
resetInitialized();
}
</script>
<template>
<BasicDrawer :close-on-click-modal="false" :title="title" class="w-[600px]">
<BasicDrawer :title="title" class="w-[600px]">
<BasicForm />
</BasicDrawer>
</template>

View File

@@ -69,7 +69,8 @@ export const columns: VxeGridProps['columns'] = [
fixed: 'right',
slots: { default: 'action' },
title: '操作',
width: 180,
resizable: false,
width: 'auto',
},
];

View File

@@ -18,6 +18,7 @@ import { pick } from 'lodash-es';
import { noticeAdd, noticeInfo, noticeUpdate } from '#/api/system/notice';
import { Tinymce } from '#/components/tinymce';
import { getDictOptions } from '#/utils/dict';
import { useBeforeCloseDiff } from '#/utils/popup';
const emit = defineEmits<{ reload: [] }>();
@@ -74,17 +75,29 @@ const { validate, validateInfos, resetFields } = Form.useForm(
formRules,
);
function customFormValueGetter() {
return JSON.stringify(formData.value);
}
const { onBeforeClose, markInitialized, resetInitialized } = useBeforeCloseDiff(
{
initializedGetter: customFormValueGetter,
currentGetter: customFormValueGetter,
},
);
const [BasicModal, modalApi] = useVbenModal({
class: 'w-[800px]',
fullscreenButton: true,
closeOnClickModal: false,
onClosed: handleCancel,
onBeforeClose,
onClosed: handleClosed,
onConfirm: handleConfirm,
onOpenChange: async (isOpen) => {
if (!isOpen) {
return null;
}
modalApi.modalLoading(true);
const { id } = modalApi.getData() as { id?: number | string };
isUpdate.value = !!id;
if (isUpdate.value && id) {
@@ -93,30 +106,33 @@ const [BasicModal, modalApi] = useVbenModal({
const filterRecord = pick(record, Object.keys(defaultValues));
formData.value = filterRecord;
}
await markInitialized();
modalApi.modalLoading(false);
},
});
async function handleConfirm() {
try {
modalApi.modalLoading(true);
modalApi.lock(true);
await validate();
// 可能会做数据处理 使用cloneDeep深拷贝
const data = cloneDeep(formData.value);
await (isUpdate.value ? noticeUpdate(data) : noticeAdd(data));
resetInitialized();
emit('reload');
await handleCancel();
modalApi.close();
} catch (error) {
console.error(error);
} finally {
modalApi.modalLoading(false);
modalApi.lock(false);
}
}
async function handleCancel() {
modalApi.close();
async function handleClosed() {
formData.value = defaultValues;
resetFields();
resetInitialized();
}
</script>

View File

@@ -81,7 +81,8 @@ export const columns: VxeGridProps['columns'] = [
fixed: 'right',
slots: { default: 'action' },
title: '操作',
width: 180,
resizable: false,
width: 'auto',
},
];

View File

@@ -13,6 +13,7 @@ import {
ossConfigInfo,
ossConfigUpdate,
} from '#/api/system/oss-config';
import { defaultFormValueGetter, useBeforeCloseDiff } from '#/utils/popup';
import { drawerSchema } from './data';
@@ -33,27 +34,38 @@ const [BasicForm, formApi] = useVbenForm({
wrapperClass: 'grid-cols-3',
});
const { onBeforeClose, markInitialized, resetInitialized } = useBeforeCloseDiff(
{
initializedGetter: defaultFormValueGetter(formApi),
currentGetter: defaultFormValueGetter(formApi),
},
);
const [BasicDrawer, drawerApi] = useVbenDrawer({
onCancel: handleCancel,
onBeforeClose,
onClosed: handleClosed,
onConfirm: handleConfirm,
async onOpenChange(isOpen) {
if (!isOpen) {
return null;
}
drawerApi.drawerLoading(true);
const { id } = drawerApi.getData() as { id?: number | string };
isUpdate.value = !!id;
if (isUpdate.value && id) {
const record = await ossConfigInfo(id);
await formApi.setValues(record);
}
await markInitialized();
drawerApi.drawerLoading(false);
},
});
async function handleConfirm() {
try {
drawerApi.drawerLoading(true);
drawerApi.lock(true);
/**
* 这里解构出来的values只能获取到自定义校验参数的值
* 需要自行调用formApi.getValues()获取表单值
@@ -64,23 +76,24 @@ async function handleConfirm() {
}
const data = cloneDeep(await formApi.getValues());
await (isUpdate.value ? ossConfigUpdate(data) : ossConfigAdd(data));
resetInitialized();
emit('reload');
await handleCancel();
drawerApi.close();
} catch (error) {
console.error(error);
} finally {
drawerApi.drawerLoading(false);
drawerApi.lock(false);
}
}
async function handleCancel() {
drawerApi.close();
async function handleClosed() {
await formApi.resetForm();
resetInitialized();
}
</script>
<template>
<BasicDrawer :close-on-click-modal="false" :title="title" class="w-[650px]">
<BasicDrawer :title="title" class="w-[650px]">
<BasicForm>
<template #tip>
<div class="ml-7 w-full">

View File

@@ -69,7 +69,8 @@ export const columns: VxeGridProps['columns'] = [
fixed: 'right',
slots: { default: 'action' },
title: '操作',
width: 180,
resizable: false,
width: 'auto',
},
];

View File

@@ -83,6 +83,9 @@ const gridOptions: VxeGridProps = {
},
},
},
headerCellConfig: {
height: 44,
},
cellConfig: {
height: 65,
},

View File

@@ -65,7 +65,8 @@ export const columns: VxeGridProps['columns'] = [
fixed: 'right',
slots: { default: 'action' },
title: '操作',
width: 180,
resizable: false,
width: 'auto',
},
];

View File

@@ -8,6 +8,7 @@ import { addFullName, cloneDeep } from '@vben/utils';
import { useVbenForm } from '#/adapter/form';
import { postAdd, postInfo, postUpdate } from '#/api/system/post';
import { getDeptTree } from '#/api/system/user';
import { defaultFormValueGetter, useBeforeCloseDiff } from '#/utils/popup';
import { drawerSchema } from './data';
@@ -50,8 +51,16 @@ async function setupDeptSelect() {
]);
}
const { onBeforeClose, markInitialized, resetInitialized } = useBeforeCloseDiff(
{
initializedGetter: defaultFormValueGetter(formApi),
currentGetter: defaultFormValueGetter(formApi),
},
);
const [BasicDrawer, drawerApi] = useVbenDrawer({
onCancel: handleCancel,
onBeforeClose,
onClosed: handleClosed,
onConfirm: handleConfirm,
async onOpenChange(isOpen) {
if (!isOpen) {
@@ -67,36 +76,38 @@ const [BasicDrawer, drawerApi] = useVbenDrawer({
const record = await postInfo(id);
await formApi.setValues(record);
}
await markInitialized();
drawerApi.drawerLoading(false);
},
});
async function handleConfirm() {
try {
drawerApi.drawerLoading(true);
drawerApi.lock(true);
const { valid } = await formApi.validate();
if (!valid) {
return;
}
const data = cloneDeep(await formApi.getValues());
await (isUpdate.value ? postUpdate(data) : postAdd(data));
resetInitialized();
emit('reload');
await handleCancel();
drawerApi.close();
} catch (error) {
console.error(error);
} finally {
drawerApi.drawerLoading(false);
drawerApi.lock(false);
}
}
async function handleCancel() {
drawerApi.close();
async function handleClosed() {
await formApi.resetForm();
resetInitialized();
}
</script>
<template>
<BasicDrawer :close-on-click-modal="false" :title="title" class="w-[600px]">
<BasicDrawer :title="title" class="w-[600px]">
<BasicForm />
</BasicDrawer>
</template>

View File

@@ -37,6 +37,7 @@ export const columns: VxeGridProps['columns'] = [
fixed: 'right',
slots: { default: 'action' },
title: '操作',
width: 180,
resizable: false,
width: 'auto',
},
];

View File

@@ -94,7 +94,8 @@ export const columns: VxeGridProps['columns'] = [
fixed: 'right',
slots: { default: 'action' },
title: '操作',
width: 180,
resizable: false,
width: 'auto',
},
];

View File

@@ -226,7 +226,11 @@ function handleAssignRole(record: Role) {
</MenuItem>
</Menu>
</template>
<a-button size="small" type="link">
<a-button
size="small"
type="link"
v-access:code="'system:role:edit'"
>
{{ $t('pages.common.more') }}
</a-button>
</Dropdown>

View File

@@ -9,6 +9,7 @@ import { cloneDeep } from '@vben/utils';
import { useVbenForm } from '#/adapter/form';
import { roleDataScope, roleDeptTree, roleInfo } from '#/api/system/role';
import { TreeSelectPanel } from '#/components/tree';
import { defaultFormValueGetter, useBeforeCloseDiff } from '#/utils/popup';
import { authModalSchemas } from './data';
@@ -33,9 +34,25 @@ async function setupDeptTree(id: number | string) {
deptTree.value = resp.depts;
}
async function customFormValueGetter() {
const v = await defaultFormValueGetter(formApi)();
// 获取勾选信息
const menuIds = deptSelectRef.value?.[0]?.getCheckedKeys() ?? [];
const mixStr = v + menuIds.join(',');
return mixStr;
}
const { onBeforeClose, markInitialized, resetInitialized } = useBeforeCloseDiff(
{
initializedGetter: customFormValueGetter,
currentGetter: customFormValueGetter,
},
);
const [BasicModal, modalApi] = useVbenModal({
fullscreenButton: false,
onCancel: handleCancel,
onBeforeClose,
onClosed: handleClosed,
onConfirm: handleConfirm,
onOpenChange: async (isOpen) => {
if (!isOpen) {
@@ -48,6 +65,7 @@ const [BasicModal, modalApi] = useVbenModal({
setupDeptTree(id);
const record = await roleInfo(id);
await formApi.setValues(record);
markInitialized();
modalApi.modalLoading(false);
},
@@ -60,7 +78,7 @@ const deptSelectRef = ref();
async function handleConfirm() {
try {
modalApi.modalLoading(true);
modalApi.lock(true);
const { valid } = await formApi.validate();
if (!valid) {
return;
@@ -75,18 +93,19 @@ async function handleConfirm() {
data.deptIds = [];
}
await roleDataScope(data);
resetInitialized();
emit('reload');
await handleCancel();
modalApi.close();
} catch (error) {
console.error(error);
} finally {
modalApi.modalLoading(false);
modalApi.lock(false);
}
}
async function handleCancel() {
modalApi.close();
async function handleClosed() {
await formApi.resetForm();
resetInitialized();
}
/**
@@ -99,11 +118,7 @@ function handleCheckStrictlyChange(value: boolean) {
</script>
<template>
<BasicModal
:close-on-click-modal="false"
class="min-h-[600px] w-[550px]"
title="分配权限"
>
<BasicModal class="min-h-[600px] w-[550px]" title="分配权限">
<BasicForm>
<template #deptIds="slotProps">
<TreeSelectPanel

View File

@@ -1,3 +1,6 @@
<!--
TODO: 这个页面要优化逻辑
-->
<script setup lang="ts">
import type { MenuOption } from '#/api/system/menu/model';
@@ -11,6 +14,7 @@ import { useVbenForm } from '#/adapter/form';
import { menuTreeSelect, roleMenuTreeSelect } from '#/api/system/menu';
import { roleAdd, roleInfo, roleUpdate } from '#/api/system/role';
import { MenuSelectTable } from '#/components/tree';
import { defaultFormValueGetter, useBeforeCloseDiff } from '#/utils/popup';
import { drawerSchema } from './data';
@@ -62,14 +66,31 @@ async function setupMenuTree(id?: number | string) {
}
}
async function customFormValueGetter() {
const v = await defaultFormValueGetter(formApi)();
// 获取勾选信息
const menuIds = menuSelectRef.value?.getCheckedKeys?.() ?? [];
const mixStr = v + menuIds.join(',');
return mixStr;
}
const { onBeforeClose, markInitialized, resetInitialized } = useBeforeCloseDiff(
{
initializedGetter: customFormValueGetter,
currentGetter: customFormValueGetter,
},
);
const [BasicDrawer, drawerApi] = useVbenDrawer({
onCancel: handleCancel,
onBeforeClose,
onClosed: handleClosed,
onConfirm: handleConfirm,
async onOpenChange(isOpen) {
if (!isOpen) {
return null;
}
drawerApi.drawerLoading(true);
const { id } = drawerApi.getData() as { id?: number | string };
isUpdate.value = !!id;
@@ -79,6 +100,7 @@ const [BasicDrawer, drawerApi] = useVbenDrawer({
}
// init菜单 注意顺序要放在赋值record之后 内部watch会依赖record
await setupMenuTree(id);
await markInitialized();
drawerApi.drawerLoading(false);
},
@@ -87,7 +109,8 @@ const [BasicDrawer, drawerApi] = useVbenDrawer({
const menuSelectRef = ref<InstanceType<typeof MenuSelectTable>>();
async function handleConfirm() {
try {
drawerApi.drawerLoading(true);
drawerApi.lock(true);
const { valid } = await formApi.validate();
if (!valid) {
return;
@@ -99,17 +122,18 @@ async function handleConfirm() {
data.menuIds = menuIds;
await (isUpdate.value ? roleUpdate(data) : roleAdd(data));
emit('reload');
await handleCancel();
resetInitialized();
drawerApi.close();
} catch (error) {
console.error(error);
} finally {
drawerApi.drawerLoading(false);
drawerApi.lock(false);
}
}
async function handleCancel() {
drawerApi.close();
async function handleClosed() {
await formApi.resetForm();
resetInitialized();
}
/**
@@ -122,7 +146,7 @@ function handleMenuCheckStrictlyChange(value: boolean) {
</script>
<template>
<BasicDrawer :close-on-click-modal="false" :title="title" class="w-[800px]">
<BasicDrawer :title="title" class="w-[800px]">
<BasicForm>
<template #menuIds="slotProps">
<div class="h-[600px] w-full">

View File

@@ -68,7 +68,8 @@ export const columns: VxeGridProps['columns'] = [
fixed: 'right',
slots: { default: 'action' },
title: '操作',
width: 200,
resizable: false,
width: 'auto',
},
];

View File

@@ -9,6 +9,7 @@ import { useVbenForm } from '#/adapter/form';
import { tenantAdd, tenantInfo, tenantUpdate } from '#/api/system/tenant';
import { packageSelectList } from '#/api/system/tenant-package';
import { useTenantStore } from '#/store/tenant';
import { defaultFormValueGetter, useBeforeCloseDiff } from '#/utils/popup';
import { drawerSchema } from './data';
@@ -51,22 +52,33 @@ async function setupPackageSelect() {
]);
}
const { onBeforeClose, markInitialized, resetInitialized } = useBeforeCloseDiff(
{
initializedGetter: defaultFormValueGetter(formApi),
currentGetter: defaultFormValueGetter(formApi),
},
);
const [BasicDrawer, drawerApi] = useVbenDrawer({
onCancel: handleCancel,
onBeforeClose,
onClosed: handleClosed,
onConfirm: handleConfirm,
async onOpenChange(isOpen) {
if (!isOpen) {
return null;
}
drawerApi.drawerLoading(true);
const { id } = drawerApi.getData() as { id?: number | string };
isUpdate.value = !!id;
// 初始化
await setupPackageSelect();
if (isUpdate.value && id) {
const record = await tenantInfo(id);
await formApi.setValues(record);
}
formApi.updateSchema([
{
fieldName: 'packageId',
@@ -75,6 +87,8 @@ const [BasicDrawer, drawerApi] = useVbenDrawer({
},
},
]);
await markInitialized();
drawerApi.drawerLoading(false);
},
});
@@ -82,32 +96,33 @@ const [BasicDrawer, drawerApi] = useVbenDrawer({
const tenantStore = useTenantStore();
async function handleConfirm() {
try {
drawerApi.drawerLoading(true);
drawerApi.lock(true);
const { valid } = await formApi.validate();
if (!valid) {
return;
}
const data = cloneDeep(await formApi.getValues());
await (isUpdate.value ? tenantUpdate(data) : tenantAdd(data));
resetInitialized();
emit('reload');
await handleCancel();
drawerApi.close();
// 重新加载租户信息
tenantStore.initTenant();
} catch (error) {
console.error(error);
} finally {
drawerApi.drawerLoading(false);
drawerApi.lock(false);
}
}
async function handleCancel() {
drawerApi.close();
async function handleClosed() {
await formApi.resetForm();
resetInitialized();
}
</script>
<template>
<BasicDrawer :close-on-click-modal="false" :title="title" class="w-[600px]">
<BasicDrawer :title="title" class="w-[600px]">
<BasicForm />
</BasicDrawer>
</template>

View File

@@ -29,7 +29,8 @@ export const columns: VxeGridProps['columns'] = [
fixed: 'right',
slots: { default: 'action' },
title: '操作',
width: 180,
resizable: false,
width: 'auto',
},
];
@@ -65,12 +66,6 @@ export const drawerSchema: FormSchemaGetter = () => [
{
component: 'Textarea',
fieldName: 'remark',
formItemClass: 'items-start',
label: '备注',
},
];
// 租户管理 不可分配 只有superadmin有权限操作 分配了也没用
export const excludeIds = [
6, 121, 122, 1606, 1607, 1608, 1609, 1610, 1611, 1612, 1613, 1614, 1615,
];

View File

@@ -17,6 +17,7 @@ import {
packageUpdate,
} from '#/api/system/tenant-package';
import { MenuSelectTable } from '#/components/tree';
import { defaultFormValueGetter, useBeforeCloseDiff } from '#/utils/popup';
import { drawerSchema } from './data';
@@ -65,8 +66,24 @@ async function setupMenuTree(id?: number | string) {
}
}
async function customFormValueGetter() {
const v = await defaultFormValueGetter(formApi)();
// 获取勾选信息
const menuIds = menuSelectRef.value?.getCheckedKeys?.() ?? [];
const mixStr = v + menuIds.join(',');
return mixStr;
}
const { onBeforeClose, markInitialized, resetInitialized } = useBeforeCloseDiff(
{
initializedGetter: customFormValueGetter,
currentGetter: customFormValueGetter,
},
);
const [BasicDrawer, drawerApi] = useVbenDrawer({
onCancel: handleCancel,
onBeforeClose,
onClosed: handleClosed,
onConfirm: handleConfirm,
async onOpenChange(isOpen) {
if (!isOpen) {
@@ -84,6 +101,7 @@ const [BasicDrawer, drawerApi] = useVbenDrawer({
}
// init菜单 注意顺序要放在赋值record之后 内部watch会依赖record
await setupMenuTree(id);
await markInitialized();
drawerApi.drawerLoading(false);
},
@@ -103,8 +121,9 @@ async function handleConfirm() {
const data = cloneDeep(await formApi.getValues());
data.menuIds = menuIds;
await (isUpdate.value ? packageUpdate(data) : packageAdd(data));
resetInitialized();
emit('reload');
await handleCancel();
drawerApi.close();
} catch (error) {
console.error(error);
} finally {
@@ -112,9 +131,9 @@ async function handleConfirm() {
}
}
async function handleCancel() {
drawerApi.close();
async function handleClosed() {
await formApi.resetForm();
resetInitialized();
}
/**
@@ -127,7 +146,7 @@ function handleMenuCheckStrictlyChange(value: boolean) {
</script>
<template>
<BasicDrawer :close-on-click-modal="false" :title="title" class="w-[800px]">
<BasicDrawer :title="title" class="w-[800px]">
<BasicForm>
<template #menuIds="slotProps">
<div class="h-[600px] w-full">

View File

@@ -1,44 +0,0 @@
import type { PropType } from 'vue';
import type { Menu } from '#/api/system/menu/model';
import { computed, defineComponent } from 'vue';
import { Tag } from 'ant-design-vue';
export default defineComponent({
name: 'TreeItem',
props: {
data: {
required: true,
type: Object as PropType<Menu>,
},
},
setup(props, { expose }) {
expose();
interface TagProp {
color: string;
text: string;
}
const menuTagProp = computed<TagProp>(() => {
// 正则判断是否为链接
if (/^https?:\/\/[^\s/$.?#].\S*$/i.test(props.data.path)) {
return { color: 'pink', text: '外链' };
}
const type = props.data.menuType;
if (type === 'M') return { color: 'green', text: '目录' };
if (type === 'C') return { color: 'blue', text: '菜单' };
if (type === 'F') return { color: '', text: '按钮' };
return { color: 'error', text: '未知' };
});
return () => (
<div class="flex gap-[6px]">
<span>{props.data.menuName}</span>
<Tag color={menuTagProp.value.color}>{menuTagProp.value.text}</Tag>
</div>
);
},
});

View File

@@ -87,7 +87,7 @@ export const columns: VxeGridProps['columns'] = [
slots: { default: 'action' },
title: '操作',
resizable: false,
width: 180,
width: 'auto',
},
];

View File

@@ -113,6 +113,9 @@ const gridOptions: VxeGridProps = {
},
},
},
headerCellConfig: {
height: 44,
},
cellConfig: {
height: 48,
},

View File

@@ -18,6 +18,7 @@ import {
userAdd,
userUpdate,
} from '#/api/system/user';
import { defaultFormValueGetter, useBeforeCloseDiff } from '#/utils/popup';
import { authScopeOptions } from '#/views/system/role/data';
import { drawerSchema } from './data';
@@ -134,8 +135,16 @@ async function loadDefaultPassword(update: boolean) {
}
}
const { onBeforeClose, markInitialized, resetInitialized } = useBeforeCloseDiff(
{
initializedGetter: defaultFormValueGetter(formApi),
currentGetter: defaultFormValueGetter(formApi),
},
);
const [BasicDrawer, drawerApi] = useVbenDrawer({
onCancel: handleCancel,
onBeforeClose,
onClosed: handleClosed,
onConfirm: handleConfirm,
async onOpenChange(isOpen) {
if (!isOpen) {
@@ -149,6 +158,7 @@ const [BasicDrawer, drawerApi] = useVbenDrawer({
return null;
}
drawerApi.drawerLoading(true);
const { id } = drawerApi.getData() as { id?: number | string };
isUpdate.value = !!id;
/** update时 禁用用户名修改 不显示密码框 */
@@ -186,10 +196,11 @@ const [BasicDrawer, drawerApi] = useVbenDrawer({
fieldName: 'postIds',
},
]);
// 部门选择 && 初始密码
await Promise.all([setupDeptSelect(), loadDefaultPassword(isUpdate.value)]);
// 部门选择、初始密码及用户相关操作并行处理
const promises = [setupDeptSelect(), loadDefaultPassword(isUpdate.value)];
if (user) {
await Promise.all([
promises.push(
// 添加基础信息
formApi.setValues(user),
// 添加角色和岗位
@@ -197,38 +208,43 @@ const [BasicDrawer, drawerApi] = useVbenDrawer({
formApi.setFieldValue('roleIds', roleIds),
// 更新时不会触发onSelect 需要手动调用
setupPostOptions(user.deptId),
]);
);
}
// 并行处理 重构后会带来10-50ms的优化
await Promise.all(promises);
await markInitialized();
drawerApi.drawerLoading(false);
},
});
async function handleConfirm() {
try {
drawerApi.drawerLoading(true);
drawerApi.lock(true);
const { valid } = await formApi.validate();
if (!valid) {
return;
}
const data = cloneDeep(await formApi.getValues());
await (isUpdate.value ? userUpdate(data) : userAdd(data));
resetInitialized();
emit('reload');
await handleCancel();
drawerApi.close();
} catch (error) {
console.error(error);
} finally {
drawerApi.drawerLoading(false);
drawerApi.lock(false);
}
}
async function handleCancel() {
drawerApi.close();
await formApi.resetForm();
async function handleClosed() {
formApi.resetForm();
resetInitialized();
}
</script>
<template>
<BasicDrawer :close-on-click-modal="false" :title="title" class="w-[600px]">
<BasicDrawer :title="title" class="w-[600px]">
<BasicForm />
</BasicDrawer>
</template>

View File

@@ -20,7 +20,7 @@ import {
} from '@vben/icons';
import { useClipboard } from '@vueuse/core';
import { Skeleton, Tree } from 'ant-design-vue';
import { Alert, Skeleton, Tree } from 'ant-design-vue';
import { previewCode } from '#/api/tool/gen';
@@ -185,6 +185,11 @@ const { copy } = useClipboard({ legacy: true });
</div>
</template>
</Tree>
<Alert
class="mt-2"
show-icon
message="👆显示的名称为模板的文件名,非最终下载文件名..."
/>
</div>
<CodeMirror
v-model="codeContent"

View File

@@ -17,6 +17,7 @@ import {
categoryList,
categoryUpdate,
} from '#/api/workflow/category';
import { defaultFormValueGetter, useBeforeCloseDiff } from '#/utils/popup';
import { modalSchema } from './data';
@@ -65,9 +66,17 @@ async function setupCategorySelect() {
]);
}
const { onBeforeClose, markInitialized, resetInitialized } = useBeforeCloseDiff(
{
initializedGetter: defaultFormValueGetter(formApi),
currentGetter: defaultFormValueGetter(formApi),
},
);
const [BasicModal, modalApi] = useVbenModal({
fullscreenButton: false,
onCancel: handleCancel,
onBeforeClose,
onClosed: handleClosed,
onConfirm: handleConfirm,
onOpenChange: async (isOpen) => {
if (!isOpen) {
@@ -89,6 +98,7 @@ const [BasicModal, modalApi] = useVbenModal({
await formApi.setValues({ parentId });
}
await setupCategorySelect();
await markInitialized();
modalApi.modalLoading(false);
},
@@ -96,7 +106,7 @@ const [BasicModal, modalApi] = useVbenModal({
async function handleConfirm() {
try {
modalApi.modalLoading(true);
modalApi.lock(true);
const { valid } = await formApi.validate();
if (!valid) {
return;
@@ -104,27 +114,24 @@ async function handleConfirm() {
// getValues获取为一个readonly的对象 需要修改必须先深拷贝一次
const data = cloneDeep(await formApi.getValues());
await (isUpdate.value ? categoryUpdate(data) : categoryAdd(data));
resetInitialized();
emit('reload');
await handleCancel();
modalApi.close();
} catch (error) {
console.error(error);
} finally {
modalApi.modalLoading(false);
modalApi.lock(false);
}
}
async function handleCancel() {
modalApi.close();
async function handleClosed() {
await formApi.resetForm();
resetInitialized();
}
</script>
<template>
<BasicModal
:close-on-click-modal="false"
:title="title"
class="min-h-[500px]"
>
<BasicModal :title="title" class="min-h-[500px]">
<BasicForm />
</BasicModal>
</template>

View File

@@ -33,7 +33,8 @@ export const columns: VxeGridProps['columns'] = [
fixed: 'right',
slots: { default: 'action' },
title: '操作',
width: 200,
resizable: false,
width: 'auto',
},
];

View File

@@ -91,7 +91,8 @@ export const columns: VxeGridProps['columns'] = [
fixed: 'right',
slots: { default: 'action' },
title: '操作',
width: 210,
resizable: false,
width: 'auto',
},
];

View File

@@ -1,7 +1,9 @@
<script setup lang="ts">
import type { PropType } from 'vue';
import type { CategoryTree } from '#/api/workflow/category/model';
import { onMounted, type PropType, ref } from 'vue';
import { onMounted, ref } from 'vue';
import { SyncOutlined } from '@ant-design/icons-vue';
import { InputSearch, Skeleton, Tree } from 'ant-design-vue';

View File

@@ -63,7 +63,7 @@ export const columns: VxeGridProps['columns'] = [
slots: { default: 'action' },
title: '操作',
resizable: false,
width: 200,
width: 'auto',
},
];

View File

@@ -93,9 +93,14 @@ const gridOptions: VxeGridProps = {
},
},
},
headerCellConfig: {
height: 44,
},
cellConfig: {
height: 100,
},
rowConfig: {
keyField: 'id',
height: 100,
},
id: 'workflow-definition-index',
};

View File

@@ -12,6 +12,7 @@ import {
workflowDefinitionInfo,
workflowDefinitionUpdate,
} from '#/api/workflow/definition';
import { defaultFormValueGetter, useBeforeCloseDiff } from '#/utils/popup';
import { modalSchema } from './data';
@@ -65,8 +66,16 @@ async function setupCategorySelect() {
]);
}
const { onBeforeClose, markInitialized, resetInitialized } = useBeforeCloseDiff(
{
initializedGetter: defaultFormValueGetter(formApi),
currentGetter: defaultFormValueGetter(formApi),
},
);
const [BasicDrawer, modalApi] = useVbenModal({
onCancel: handleCancel,
onBeforeClose,
onClosed: handleClosed,
onConfirm: handleConfirm,
async onOpenChange(isOpen) {
if (!isOpen) {
@@ -83,6 +92,7 @@ const [BasicDrawer, modalApi] = useVbenModal({
const record = await workflowDefinitionInfo(id);
await formApi.setValues(record);
}
await markInitialized();
modalApi.modalLoading(false);
},
@@ -90,7 +100,7 @@ const [BasicDrawer, modalApi] = useVbenModal({
async function handleConfirm() {
try {
modalApi.modalLoading(true);
modalApi.lock(true);
const { valid } = await formApi.validate();
if (!valid) {
return;
@@ -103,27 +113,23 @@ async function handleConfirm() {
await workflowDefinitionAdd(data);
emit('reload', 'add');
}
await handleCancel();
resetInitialized();
modalApi.close();
} catch (error) {
console.error(error);
} finally {
modalApi.modalLoading(false);
modalApi.lock(false);
}
}
async function handleCancel() {
modalApi.close();
async function handleClosed() {
await formApi.resetForm();
resetInitialized();
}
</script>
<template>
<BasicDrawer
:close-on-click-modal="false"
:fullscreen-button="false"
:title="title"
class="w-[550px]"
>
<BasicDrawer :fullscreen-button="false" :title="title" class="w-[550px]">
<div class="min-h-[400px]">
<BasicForm />
</div>

View File

@@ -41,7 +41,7 @@ const formOptions: VbenFormProps = {
wrapperClass: 'grid-cols-1 md:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4',
handleReset: async () => {
selectedCode.value = [];
// eslint-disable-next-line no-use-before-define
const { formApi, reload } = tableApi;
await formApi.resetForm();
const formValues = formApi.form.values;
@@ -68,7 +68,7 @@ async function handleTypeChange(e: RadioChangeEvent) {
break;
}
}
// eslint-disable-next-line no-use-before-define
await tableApi.reload();
}
@@ -103,9 +103,14 @@ const gridOptions: VxeGridProps = {
},
},
},
headerCellConfig: {
height: 44,
},
cellConfig: {
height: 66,
},
rowConfig: {
keyField: 'id',
height: 66,
},
id: 'workflow-definition-index',
};

View File

@@ -28,6 +28,7 @@ import { categoryTree } from '#/api/workflow/category';
import { pageByAllTaskFinish, pageByAllTaskWait } from '#/api/workflow/task';
import { ApprovalCard, ApprovalPanel, CopyComponent } from '../components';
import { bottomOffset } from './constant';
const emptyImage = Empty.PRESENTED_IMAGE_SIMPLE;
@@ -140,7 +141,9 @@ const handleScroll = debounce(async (e: Event) => {
// e.target.scrollHeight 是元素的总高度。
const { scrollTop, clientHeight, scrollHeight } = e.target as HTMLElement;
// 判断是否滚动到底部
const isBottom = scrollTop + clientHeight >= scrollHeight;
const isBottom = scrollTop + clientHeight >= scrollHeight - bottomOffset;
console.log('scrollTop + clientHeight', scrollTop + clientHeight);
console.log('scrollHeight', scrollHeight);
// 滚动到底部且没有加载完成
if (isBottom && !isLoadComplete.value) {

View File

@@ -0,0 +1,7 @@
/**
* 底部偏移量
* 在缩放时会差大概0.5px 导致触底逻辑不会触发
* 在这里设置手动补偿
* @see https://gitee.com/dapppp/ruoyi-plus-vben5/issues/IC28RE#note_40175381
*/
export const bottomOffset = 2;

View File

@@ -24,6 +24,7 @@ import { cloneDeep, debounce } from 'lodash-es';
import { pageByCurrent } from '#/api/workflow/instance';
import { ApprovalCard, ApprovalPanel } from '../components';
import { bottomOffset } from './constant';
const emptyImage = Empty.PRESENTED_IMAGE_SIMPLE;
@@ -95,7 +96,7 @@ const handleScroll = debounce(async (e: Event) => {
// e.target.scrollHeight 是元素的总高度。
const { scrollTop, clientHeight, scrollHeight } = e.target as HTMLElement;
// 判断是否滚动到底部
const isBottom = scrollTop + clientHeight >= scrollHeight;
const isBottom = scrollTop + clientHeight >= scrollHeight - bottomOffset;
// 滚动到底部且没有加载完成
if (isBottom && !isLoadComplete.value) {

View File

@@ -26,6 +26,7 @@ import { categoryTree } from '#/api/workflow/category';
import { pageByTaskCopy } from '#/api/workflow/task';
import { ApprovalCard, ApprovalPanel, CopyComponent } from '../components';
import { bottomOffset } from './constant';
const emptyImage = Empty.PRESENTED_IMAGE_SIMPLE;
@@ -99,7 +100,7 @@ const handleScroll = debounce(async (e: Event) => {
// e.target.scrollHeight 是元素的总高度。
const { scrollTop, clientHeight, scrollHeight } = e.target as HTMLElement;
// 判断是否滚动到底部
const isBottom = scrollTop + clientHeight >= scrollHeight;
const isBottom = scrollTop + clientHeight >= scrollHeight - bottomOffset;
// 滚动到底部且没有加载完成
if (isBottom && !isLoadComplete.value) {

View File

@@ -26,6 +26,7 @@ import { categoryTree } from '#/api/workflow/category';
import { pageByTaskFinish } from '#/api/workflow/task';
import { ApprovalCard, ApprovalPanel, CopyComponent } from '../components';
import { bottomOffset } from './constant';
const emptyImage = Empty.PRESENTED_IMAGE_SIMPLE;
@@ -99,7 +100,7 @@ const handleScroll = debounce(async (e: Event) => {
// e.target.scrollHeight 是元素的总高度。
const { scrollTop, clientHeight, scrollHeight } = e.target as HTMLElement;
// 判断是否滚动到底部
const isBottom = scrollTop + clientHeight >= scrollHeight;
const isBottom = scrollTop + clientHeight >= scrollHeight - bottomOffset;
// 滚动到底部且没有加载完成
if (isBottom && !isLoadComplete.value) {

View File

@@ -27,6 +27,7 @@ import { categoryTree } from '#/api/workflow/category';
import { pageByTaskWait } from '#/api/workflow/task';
import { ApprovalCard, ApprovalPanel, CopyComponent } from '../components';
import { bottomOffset } from './constant';
const emptyImage = Empty.PRESENTED_IMAGE_SIMPLE;
@@ -100,7 +101,7 @@ const handleScroll = debounce(async (e: Event) => {
// e.target.scrollHeight 是元素的总高度。
const { scrollTop, clientHeight, scrollHeight } = e.target as HTMLElement;
// 判断是否滚动到底部
const isBottom = scrollTop + clientHeight >= scrollHeight;
const isBottom = scrollTop + clientHeight >= scrollHeight - bottomOffset;
// 滚动到底部且没有加载完成
if (isBottom && !isLoadComplete.value) {

View File

@@ -1,9 +1,11 @@
<script setup lang="ts">
import type { VxeGridProps } from '#/adapter/vxe-table';
import { Page, useVbenModal } from '@vben/common-ui';
import { Space } from 'ant-design-vue';
import { useVbenVxeGrid, type VxeGridProps } from '#/adapter/vxe-table';
import { useVbenVxeGrid } from '#/adapter/vxe-table';
import { sseList } from './api';
import sendMsgModal from './send-msg-modal.vue';
@@ -31,7 +33,8 @@ const gridOptions: VxeGridProps = {
fixed: 'right',
slots: { default: 'action' },
title: '操作',
width: 180,
resizable: false,
width: 'auto',
},
],
height: 'auto',

View File

@@ -5,7 +5,7 @@ import type { CustomGetter } from '#/components/upload/src/props';
import { h, ref } from 'vue';
import { CodeMirror, Page } from '@vben/common-ui';
import { CodeMirror, Page, useVbenModal } from '@vben/common-ui';
import { useClipboard } from '@vueuse/core';
import { Alert, Card, Modal, RadioGroup, Switch } from 'ant-design-vue';
@@ -14,6 +14,7 @@ import { FileUpload, ImageUpload } from '#/components/upload';
import { useFileType, useImageType } from './hook';
import sql from './insert.sql?raw';
import uploadModal from './upload-modal.vue';
const singleImageId = ref('1905537674682916865');
const singleFileId = ref('1905191167882518529');
@@ -53,6 +54,10 @@ const customThumbnailUrl: CustomGetter<undefined> = () => {
const { copy } = useClipboard({ legacy: true });
const animationEnable = ref(false);
const [UploadModal, uploadModalApi] = useVbenModal({
connectedComponent: uploadModal,
});
</script>
<template>
@@ -63,6 +68,10 @@ const animationEnable = ref(false);
<CodeMirror class="mt-2" v-model="sql" language="sql" readonly />
</Card>
<div class="grid grid-cols-2 gap-4">
<Card title="表单上传">
<a-button @click="uploadModalApi.open()">打开</a-button>
<UploadModal />
</Card>
<Card title="单上传, 会绑定为string" size="small">
<ImageUpload v-model:value="singleImageId" />
当前绑定值: {{ singleImageId }}

View File

@@ -0,0 +1,70 @@
<script setup lang="ts">
import { h } from 'vue';
import { JsonPreview, useVbenModal } from '@vben/common-ui';
import { Modal, Space } from 'ant-design-vue';
import { useVbenForm } from '#/adapter/form';
const [BasicForm, formApi] = useVbenForm({
layout: 'vertical',
schema: [
{
label: '图片上传多图',
component: 'ImageUpload',
fieldName: 'ossIds',
componentProps: {
maxCount: 3,
},
},
{
label: '图片上传单图',
component: 'ImageUpload',
fieldName: 'ossId',
componentProps: {
maxCount: 1,
},
},
],
showDefaultActions: false,
});
async function getValues() {
try {
const v = await formApi.getValues();
console.log(v);
Modal.info({
content: () => h(JsonPreview, { data: v }),
});
} catch (error) {
console.error(error);
}
}
async function handleAssign() {
const ids = ['1908761290673315841', '1907738568539332610'];
await formApi.setValues({
ossIds: ids,
ossId: ids[0],
});
}
const [BasicModal] = useVbenModal({
title: '上传',
footer: false,
});
</script>
<template>
<BasicModal>
<div class="flex flex-col">
<Space>
<a-button @click="handleAssign">赋值</a-button>
<a-button @click="getValues">获取值</a-button>
</Space>
<BasicForm />
</div>
</BasicModal>
</template>

View File

@@ -104,6 +104,11 @@
--vp-custom-block-tip-text: var(--vp-c-text-1);
--vp-custom-block-tip-bg: var(--vp-c-brand-soft);
--vp-custom-block-tip-code-bg: var(--vp-c-brand-soft);
/**
* modal zIndex
*/
--popup-z-index: 1000;
}
@media (min-width: 640px) {

View File

@@ -1,6 +1,6 @@
{
"name": "@vben/docs",
"version": "5.5.4",
"version": "5.5.5",
"private": true,
"scripts": {
"build": "vitepress build",

View File

@@ -12,6 +12,12 @@ Alert提供的功能与Modal类似但只适用于简单应用场景。例如
:::
::: tip 注意
Alert提供的快捷方法alert、confirm、prompt动态创建的弹窗在已打开的情况下不支持HMR热更新代码变更后需要关闭这些弹窗后重新打开。
:::
::: tip README
下方示例代码中的,存在一些主题色未适配、样式缺失的问题,这些问题只在文档内会出现,实际使用并不会有这些问题,可忽略,不必纠结。
@@ -32,6 +38,23 @@ Alert提供的功能与Modal类似但只适用于简单应用场景。例如
<DemoPreview dir="demos/vben-alert/prompt" />
## useAlertContext
当弹窗的content、footer、icon使用自定义组件时在这些组件中可以使用 `useAlertContext` 获取当前弹窗的上下文对象,用来主动控制弹窗。
::: tip 注意
`useAlertContext`只能用在setup或者函数式组件中。
:::
### Methods
| 方法 | 描述 | 类型 | 版本要求 |
| --------- | ------------------ | -------- | -------- |
| doConfirm | 调用弹窗的确认操作 | ()=>void | >5.5.4 |
| doCancel | 调用弹窗的取消操作 | ()=>void | >5.5.4 |
## 类型说明
```ts
@@ -43,6 +66,9 @@ export type BeforeCloseScope = {
isConfirm: boolean;
};
/**
* alert 属性
*/
export type AlertProps = {
/** 关闭前的回调如果返回false则终止关闭 */
beforeClose?: (
@@ -50,6 +76,8 @@ export type AlertProps = {
) => boolean | Promise<boolean | undefined> | undefined;
/** 边框 */
bordered?: boolean;
/** 按钮对齐方式 */
buttonAlign?: 'center' | 'end' | 'start';
/** 取消按钮的标题 */
cancelText?: string;
/** 是否居中显示 */
@@ -62,14 +90,41 @@ export type AlertProps = {
content: Component | string;
/** 弹窗内容的额外样式 */
contentClass?: string;
/** 执行beforeClose回调期间在内容区域显示一个loading遮罩*/
contentMasking?: boolean;
/** 弹窗底部内容(与按钮在同一个容器中) */
footer?: Component | string;
/** 弹窗的图标(在标题的前面) */
icon?: Component | IconType;
/**
* 弹窗遮罩模糊效果
*/
overlayBlur?: number;
/** 是否显示取消按钮 */
showCancel?: boolean;
/** 弹窗标题 */
title?: string;
};
/** prompt 属性 */
export type PromptProps<T = any> = {
/** 关闭前的回调如果返回false则终止关闭 */
beforeClose?: (scope: {
isConfirm: boolean;
value: T | undefined;
}) => boolean | Promise<boolean | undefined> | undefined;
/** 用于接受用户输入的组件 */
component?: Component;
/** 输入组件的属性 */
componentProps?: Recordable<any>;
/** 输入组件的插槽 */
componentSlots?: Recordable<Component>;
/** 默认值 */
defaultValue?: T;
/** 输入组件的值属性名 */
modelPropName?: string;
} & Omit<AlertProps, 'beforeClose'>;
/**
* 函数签名
* alert和confirm的函数签名相同。

View File

@@ -131,26 +131,37 @@ function fetchApi(): Promise<Record<string, any>> {
### Props
| 属性名 | 描述 | 类型 | 默认值 |
| --- | --- | --- | --- |
| modelValue(v-model) | 当前值 | `any` | - |
| component | 欲包装的组件(以下称为目标组件) | `Component` | - |
| numberToString | 是否将value从数字转为string | `boolean` | `false` |
| api | 获取数据的函数 | `(arg?: any) => Promise<OptionsItem[] \| Record<string, any>>` | - |
| params | 传递给api的参数 | `Record<string, any>` | - |
| resultField | 从api返回的结果中提取options数组的字段名 | `string` | - |
| labelField | label字段名 | `string` | `label` |
| childrenField | 子级数据字段名,需要层级数据的组件可用 | `string` | `` |
| valueField | value字段名 | `string` | `value` |
| optionsPropName | 目标组件接收options数据的属性名称 | `string` | `options` |
| modelPropName | 目标组件的双向绑定属性名默认为modelValue。部分组件可能为value | `string` | `modelValue` |
| immediate | 是否立即调用api | `boolean` | `true` |
| alwaysLoad | 每次`visibleEvent`事件发生时都重新请求数据 | `boolean` | `false` |
| beforeFetch | 在api请求之前的回调函数 | `AnyPromiseFunction<any, any>` | - |
| afterFetch | 在api请求之后的回调函数 | `AnyPromiseFunction<any, any>` | - |
| options | 直接传入选项数据也作为api返回空数据时的后备数据 | `OptionsItem[]` | - |
| visibleEvent | 触发重新请求数据的事件名 | `string` | - |
| loadingSlot | 目标组件的插槽名称,用来显示一个"加载中"的图标 | `string` | - |
| 属性名 | 描述 | 类型 | 默认值 | 版本要求 |
| --- | --- | --- | --- | --- |
| modelValue(v-model) | 当前值 | `any` | - | - |
| component | 欲包装的组件(以下称为目标组件) | `Component` | - | - |
| numberToString | 是否将value从数字转为string | `boolean` | `false` | - |
| api | 获取数据的函数 | `(arg?: any) => Promise<OptionsItem[] \| Record<string, any>>` | - | - |
| params | 传递给api的参数 | `Record<string, any>` | - | - |
| resultField | 从api返回的结果中提取options数组的字段名 | `string` | - | - |
| labelField | label字段名 | `string` | `label` | - |
| childrenField | 子级数据字段名,需要层级数据的组件可用 | `string` | `` | - |
| valueField | value字段名 | `string` | `value` | - |
| optionsPropName | 目标组件接收options数据的属性名称 | `string` | `options` | - |
| modelPropName | 目标组件的双向绑定属性名默认为modelValue。部分组件可能为value | `string` | `modelValue` | - |
| immediate | 是否立即调用api | `boolean` | `true` | - |
| alwaysLoad | 每次`visibleEvent`事件发生时都重新请求数据 | `boolean` | `false` | - |
| beforeFetch | 在api请求之前的回调函数 | `AnyPromiseFunction<any, any>` | - | - |
| afterFetch | 在api请求之后的回调函数 | `AnyPromiseFunction<any, any>` | - | - |
| options | 直接传入选项数据也作为api返回空数据时的后备数据 | `OptionsItem[]` | - | - |
| visibleEvent | 触发重新请求数据的事件名 | `string` | - | - |
| loadingSlot | 目标组件的插槽名称,用来显示一个"加载中"的图标 | `string` | - | - |
| autoSelect | 自动设置选项 | `'first' \| 'last' \| 'one'\| ((item: OptionsItem[]) => OptionsItem) \| false` | `false` | >5.5.4 |
#### autoSelect 自动设置选项
如果当前值为undefined在选项数据成功加载之后自动从备选项中选择一个作为当前值。默认值为`false`,即不自动选择选项。注意:该属性不应用于多选组件。可选值有:
- `"first"`:自动选择第一个选项
- `"last"`:自动选择最后一个选项
- `"one"`:有且仅有一个选项时,自动选择它
- `自定义函数`自定义选择逻辑函数的参数为options返回值为选择的选项
- `false`:不自动选择选项
### Methods
@@ -158,3 +169,5 @@ function fetchApi(): Promise<Record<string, any>> {
| --- | --- | --- | --- |
| getComponentRef | 获取被包装的组件的实例 | ()=>T | >5.5.4 |
| updateParam | 设置接口请求参数将与params属性合并 | (newParams: Record<string, any>)=>void | >5.5.4 |
| getOptions | 获取已加载的选项数据 | ()=>OptionsItem[] | >5.5.4 |
| getValue | 获取当前值 | ()=>any | >5.5.4 |

View File

@@ -127,13 +127,14 @@ const [Drawer, drawerApi] = useVbenDrawer({
除了上面的属性类型包含`slot`,还可以通过插槽来自定义弹窗的内容。
| 插槽名 | 描述 |
| -------------- | ------------------- |
| default | 默认插槽 - 弹窗内容 |
| prepend-footer | 取消按钮左侧 |
| append-footer | 取消按钮右侧 |
| close-icon | 关闭按钮图标 |
| extra | 额外内容(标题右侧) |
| 插槽名 | 描述 |
| -------------- | -------------------------------------------------- |
| default | 默认插槽 - 弹窗内容 |
| prepend-footer | 取消按钮左侧 |
| center-footer | 取消按钮和确认按钮中间(不使用 footer 插槽时有效) |
| append-footer | 确认按钮右侧 |
| close-icon | 关闭按钮图标 |
| extra | 额外内容(标题右侧) |
### drawerApi

View File

@@ -310,7 +310,7 @@ useVbenForm 返回的第二个参数,是一个对象,包含了一些表单
| actionWrapperClass | 表单操作区域class | `any` | - |
| handleReset | 表单重置回调 | `(values: Record<string, any>,) => Promise<void> \| void` | - |
| handleSubmit | 表单提交回调 | `(values: Record<string, any>,) => Promise<void> \| void` | - |
| handleValuesChange | 表单值变化回调 | `(values: Record<string, any>,) => void` | - |
| handleValuesChange | 表单值变化回调 | `(values: Record<string, any>, fieldsChanged: string[]) => void` | - |
| actionButtonsReverse | 调换操作按钮位置 | `boolean` | `false` |
| resetButtonOptions | 重置按钮组件参数 | `ActionButtonOptions` | - |
| submitButtonOptions | 提交按钮组件参数 | `ActionButtonOptions` | - |
@@ -325,6 +325,12 @@ useVbenForm 返回的第二个参数,是一个对象,包含了一些表单
| submitOnChange | 字段值改变时提交表单(内部防抖,这个属性一般用于表格的搜索表单) | `boolean` | false |
| compact | 是否紧凑模式(忽略为校验信息所预留的空间) | `boolean` | false |
::: tip handleValuesChange
`handleValuesChange` 回调函数的第一个参数`values`装载了表单改变后的当前值对象,第二个参数`fieldsChanged`是一个数组包含了所有被改变的字段名。注意第二个参数仅在v5.5.4(不含)以上版本可用并且传递的是已在schema中定义的字段名。如果你使用了字段映射并且需要检查是哪些字段发生了变化的话请注意该参数并不会包含映射后的字段名。
:::
::: tip fieldMappingTime
此属性用于将表单内的数组值映射成 2 个字段它应当传入一个数组数组的每一项是一个映射规则规则的第一个成员是一个字符串表示需要映射的字段名第二个成员是一个数组表示映射后的字段名第三个成员是一个可选的格式掩码用于格式化日期时间字段也可以提供一个格式化函数参数分别为当前值和当前字段名返回格式化后的值。如果明确地将格式掩码设为null则原值映射而不进行格式化适用于非日期时间字段。例如`[['timeRange', ['startTime', 'endTime'], 'YYYY-MM-DD']]``timeRange`应当是一个至少具有2个成员的数组类型的值。Form会将`timeRange`的值前两个值分别按照格式掩码`YYYY-MM-DD`格式化后映射到`startTime``endTime`字段上。每一项的第三个参数是一个可选的格式掩码,

View File

@@ -59,8 +59,7 @@ Modal 内的内容一般业务中,会比较复杂,所以我们可以将 moda
::: info 注意
- `VbenModal` 组件对与参数的处理优先级是 `slot` > `props` > `state`(通过api更新的状态以及useVbenModal参数)。如果你已经传入了 `slot` 或者 `props`,那么 `setState` 将不会生效,这种情况下你可以通过 `slot` 或者 `props` 来更新状态。
- 如果你使用到了 `connectedComponent` 参数,那么会存在 2 个`useVbenModal`, 此时,如果同时设置了相同的参数,那么以内部为准(也就是没有设置 connectedComponent 的代码)。比如 同时设置了 `onConfirm`,那么以内部的 `onConfirm` 为准。`onOpenChange`事件除外,内外都会触发。
- 使用了`connectedComponent`参数时,可以配置`destroyOnClose`属性来决定当关闭弹窗时,是否要销毁`connectedComponent`组件(重新创建`connectedComponent`组件,这将会把其内部所有的变量、状态、数据等恢复到初始状态。)。
- 如果你使用到了 `connectedComponent` 参数,那么会存在 2 个`useVbenModal`, 此时,如果同时设置了相同的参数,那么以内部为准(也就是没有设置 connectedComponent 的代码)。比如 同时设置了 `onConfirm`,那么以内部的 `onConfirm` 为准。`onOpenChange`事件除外,内外都会触发。另外,如果设置了`destroyOnClose`内部Modal及其子组件会在被关闭后<b>完全销毁</b>
- 如果弹窗的默认行为不符合你的预期,可以在`src\bootstrap.ts`中修改`setDefaultModalProps`的参数来设置默认的属性如默认隐藏全屏按钮修改默认ZIndex等。
:::
@@ -84,7 +83,7 @@ const [Modal, modalApi] = useVbenModal({
| --- | --- | --- | --- |
| appendToMain | 是否挂载到内容区域默认挂载到body | `boolean` | `false` |
| connectedComponent | 连接另一个Modal组件 | `Component` | - |
| destroyOnClose | 关闭时销毁`connectedComponent` | `boolean` | `false` |
| destroyOnClose | 关闭时销毁 | `boolean` | `false` |
| title | 标题 | `string\|slot` | - |
| titleTooltip | 标题提示信息 | `string\|slot` | - |
| description | 描述信息 | `string\|slot` | - |
@@ -138,11 +137,12 @@ const [Modal, modalApi] = useVbenModal({
除了上面的属性类型包含`slot`,还可以通过插槽来自定义弹窗的内容。
| 插槽名 | 描述 |
| -------------- | ------------------- |
| default | 默认插槽 - 弹窗内容 |
| prepend-footer | 取消按钮左侧 |
| append-footer | 取消按钮右侧 |
| 插槽名 | 描述 |
| -------------- | -------------------------------------------------- |
| default | 默认插槽 - 弹窗内容 |
| prepend-footer | 取消按钮左侧 |
| center-footer | 取消按钮和确认按钮中间(不使用 footer 插槽时有效) |
| append-footer | 确认按钮右侧 |
### modalApi

View File

@@ -167,6 +167,23 @@ vxeUI.renderer.add('CellLink', {
当启用了表单搜索时可以在toolbarConfig中配置`search``true`来让表格在工具栏区域显示一个搜索表单控制按钮。表格的所有以`form-`开头的命名插槽都会被传递给搜索表单。
### 定制分隔条
当你启用表单搜索时在表单和表格之间会显示一个分隔条。这个分隔条使用了默认的组件背景色并且横向贯穿整个Vben Vxe Table在视觉上融入了页面的默认背景中。如果你在Vben Vxe Table的外层包裹了一个不同背景色的容器如将其放在一个Card内默认的表单和表格之间的分隔条可能就显得格格不入了下面的代码演示了如何定制这个分隔条。
```ts
const [Grid] = useVbenVxeGrid({
formOptions: {},
gridOptions: {},
// 完全移除分隔条
separator: false,
// 你也可以使用下面的代码来移除分隔条
// separator: { show: false },
// 或者使用下面的代码来改变分隔条的颜色
// separator: { backgroundColor: 'rgba(100,100,0,0.5)' },
});
```
<DemoPreview dir="demos/vben-vxe-table/form" />
## 单元格编辑
@@ -231,15 +248,16 @@ useVbenVxeGrid 返回的第二个参数,是一个对象,包含了一些表
所有属性都可以传入 `useVbenVxeGrid` 的第一个参数中。
| 属性名 | 描述 | 类型 |
| -------------- | -------------------- | ------------------- |
| tableTitle | 表格标题 | `string` |
| tableTitleHelp | 表格标题帮助信息 | `string` |
| gridClass | grid组件的class | `string` |
| gridOptions | grid组件的参数 | `VxeTableGridProps` |
| gridEvents | grid组件的触发的事件 | `VxeGridListeners` |
| formOptions | 表单参数 | `VbenFormProps` |
| showSearchForm | 是否显示搜索表单 | `boolean` |
| 属性名 | 描述 | 类型 | 版本要求 |
| --- | --- | --- | --- |
| tableTitle | 表格标题 | `string` | - |
| tableTitleHelp | 表格标题帮助信息 | `string` | - |
| gridClass | grid组件的class | `string` | - |
| gridOptions | grid组件的参数 | `VxeTableGridProps` | - |
| gridEvents | grid组件的触发的事件 | `VxeGridListeners` | - |
| formOptions | 表单参数 | `VbenFormProps` | - |
| showSearchForm | 是否显示搜索表单 | `boolean` | - |
| separator | 搜索表单与表格主体之间的分隔条 | `boolean\|SeparatorOptions` | >5.5.4 |
## Slots

View File

@@ -3,7 +3,7 @@ import { h } from 'vue';
import { alert, VbenButton } from '@vben/common-ui';
import { Empty } from 'ant-design-vue';
import { Result } from 'ant-design-vue';
function showAlert() {
alert('This is an alert message');
@@ -18,7 +18,12 @@ function showIconAlert() {
function showCustomAlert() {
alert({
content: h(Empty, { description: '什么都没有' }),
buttonAlign: 'center',
content: h(Result, {
status: 'success',
subTitle: '已成功创建订单。订单ID2017182818828182881',
title: '操作成功',
}),
});
}
</script>

View File

@@ -1,6 +1,10 @@
<script lang="ts" setup>
import { h, ref } from 'vue';
import { alert, confirm, VbenButton } from '@vben/common-ui';
import { Checkbox, message } from 'ant-design-vue';
function showConfirm() {
confirm('This is an alert message')
.then(() => {
@@ -18,6 +22,34 @@ function showIconConfirm() {
});
}
function showfooterConfirm() {
const checked = ref(false);
confirm({
cancelText: '不要虾扯蛋',
confirmText: '是的我们都是NPC',
content:
'刚才发生的事情,为什么我似乎早就经历过一般?\n我甚至能在事情发生过程中潜意识里预知到接下来会发生什么。\n\n听起来挺玄乎的你有过这种感觉吗',
footer: () =>
h(
Checkbox,
{
checked: checked.value,
class: 'flex-1',
'onUpdate:checked': (v) => (checked.value = v),
},
'不再提示',
),
icon: 'question',
title: '未解之谜',
}).then(() => {
if (checked.value) {
message.success('我不会再拿这个问题烦你了');
} else {
message.info('下次还要继续问你哟');
}
});
}
function showAsyncConfirm() {
confirm({
beforeClose({ isConfirm }) {
@@ -37,6 +69,7 @@ function showAsyncConfirm() {
<div class="flex gap-4">
<VbenButton @click="showConfirm">Confirm</VbenButton>
<VbenButton @click="showIconConfirm">Confirm With Icon</VbenButton>
<VbenButton @click="showfooterConfirm">Confirm With Footer</VbenButton>
<VbenButton @click="showAsyncConfirm">Async Confirm</VbenButton>
</div>
</template>

View File

@@ -1,7 +1,10 @@
<script lang="ts" setup>
import { alert, prompt, VbenButton } from '@vben/common-ui';
import { h } from 'vue';
import { VbenSelect } from '@vben-core/shadcn-ui';
import { alert, prompt, useAlertContext, VbenButton } from '@vben/common-ui';
import { Input, RadioGroup, Select } from 'ant-design-vue';
import { BadgeJapaneseYen } from 'lucide-vue-next';
function showPrompt() {
prompt({
@@ -15,27 +18,101 @@ function showPrompt() {
});
}
function showSlotsPrompt() {
prompt({
component: () => {
// 获取弹窗上下文。注意只能在setup或者函数式组件中调用
const { doConfirm } = useAlertContext();
return h(
Input,
{
onKeydown(e: KeyboardEvent) {
if (e.key === 'Enter') {
e.preventDefault();
// 调用弹窗提供的确认方法
doConfirm();
}
},
placeholder: '请输入',
prefix: '充值金额:',
type: 'number',
},
{
addonAfter: () => h(BadgeJapaneseYen),
},
);
},
content:
'此弹窗演示了如何使用自定义插槽并且可以使用useAlertContext获取到弹窗的上下文。\n在输入框中按下回车键会触发确认操作。',
icon: 'question',
modelPropName: 'value',
}).then((val) => {
if (val) alert(`你输入的是${val}`);
});
}
function showSelectPrompt() {
prompt({
component: VbenSelect,
component: Select,
componentProps: {
options: [
{ label: 'Option A', value: 'Option A' },
{ label: 'Option B', value: 'Option B' },
{ label: 'Option C', value: 'Option C' },
],
placeholder: '请选择',
// 弹窗会设置body的pointer-events为none这回影响下拉框的点击事件
popupClassName: 'pointer-events-auto',
},
content: '此弹窗演示了如何使用component传递自定义组件',
icon: 'question',
modelPropName: 'value',
}).then((val) => {
if (val) {
alert(`你选择了${val}`);
}
});
}
function sleep(ms: number) {
return new Promise((resolve) => setTimeout(resolve, ms));
}
function showAsyncPrompt() {
prompt({
async beforeClose(scope) {
if (scope.isConfirm) {
if (scope.value) {
// 模拟异步操作如果不成功可以返回false
await sleep(2000);
} else {
alert('请选择一个选项');
return false;
}
}
},
component: RadioGroup,
componentProps: {
class: 'flex flex-col',
options: [
{ label: 'Option 1', value: 'option1' },
{ label: 'Option 2', value: 'option2' },
{ label: 'Option 3', value: 'option3' },
],
placeholder: '请选择',
},
content: 'This is an alert message with icon',
content: '选择一个选项后再点击[确认]',
icon: 'question',
modelPropName: 'value',
}).then((val) => {
alert(`你选择的是${val}`);
alert(`${val} 已设置。`);
});
}
</script>
<template>
<div class="flex gap-4">
<VbenButton @click="showPrompt">Prompt</VbenButton>
<VbenButton @click="showSelectPrompt">Confirm With Select</VbenButton>
<VbenButton @click="showSlotsPrompt"> Prompt With slots </VbenButton>
<VbenButton @click="showSelectPrompt">Prompt With Select</VbenButton>
<VbenButton @click="showAsyncPrompt">Prompt With Async</VbenButton>
</div>
</template>

View File

@@ -58,7 +58,7 @@ Open a terminal in your code directory and execute the following commands:
cd vue-vben-admin
# Enable the project-specified version of pnpm
corepack enable
npm i -g corepack
# Install dependencies
pnpm install

View File

@@ -114,8 +114,6 @@ async function generateAccess(options: GenerateMenuAndRoutesOptions) {
```ts
const dashboardMenus = [
{
// 这里固定写死 BasicLayout不可更改
component: 'BasicLayout',
meta: {
order: -1,
title: 'page.dashboard.title',
@@ -144,6 +142,16 @@ const dashboardMenus = [
},
],
},
{
name: 'Test',
path: '/test',
component: '/test/index',
meta: {
title: 'page.test',
// 部分特殊页面如果不需要基础布局页面顶部和侧边栏可将noBasicLayout设置为true
noBasicLayout: true,
},
},
];
```

View File

@@ -58,7 +58,7 @@ git clone https://gitee.com/annsion/vue-vben-admin.git
cd vue-vben-admin
# 使用项目指定的pnpm版本进行依赖安装
corepack enable
npm i -g corepack
# 安装依赖
pnpm install

View File

@@ -11,3 +11,7 @@
当前只有对应的包下面存在 `tailwind.config.mjs` 文件才会启用 tailwindcss 的编译,否则不会启用 tailwindcss。如果你是纯粹的 SDK 包,不需要使用 tailwindcss可以不用创建 `tailwind.config.mjs` 文件。
:::
## 提示
`tailwindcss`已至v4.x版本使用方法与`tailwindcss: ^3.4.17`有差异v4.0无法与v3.x版本兼容在开发前请确认`package.json`中的`tailwindcss`版本。

View File

@@ -20,6 +20,9 @@ hero:
- theme: alt
text: 在 GitHub 查看
link: https://github.com/vbenjs/vue-vben-admin
- theme: alt
text: DeepWiki 文档
link: https://deepwiki.com/vbenjs/vue-vben-admin
features:
- icon: 🚀

View File

@@ -1,6 +1,6 @@
{
"name": "@vben/commitlint-config",
"version": "5.5.4",
"version": "5.5.5",
"private": true,
"homepage": "https://github.com/vbenjs/vue-vben-admin",
"bugs": "https://github.com/vbenjs/vue-vben-admin/issues",

View File

@@ -10,7 +10,15 @@ export async function vue(): Promise<Linter.Config[]> {
interopDefault(import('@typescript-eslint/parser')),
] as const);
const flatEssential = pluginVue.configs?.['flat/essential'] || [];
const flatStronglyRecommended =
pluginVue.configs?.['flat/strongly-recommended'] || [];
const flatRecommended = pluginVue.configs?.['flat/recommended'] || [];
return [
...flatEssential,
...flatStronglyRecommended,
...flatRecommended,
{
files: ['**/*.vue'],
languageOptions: {
@@ -43,12 +51,9 @@ export async function vue(): Promise<Linter.Config[]> {
plugins: {
vue: pluginVue,
},
processor: pluginVue.processors['.vue'],
processor: pluginVue.processors?.['.vue'],
rules: {
...pluginVue.configs.base.rules,
...pluginVue.configs['vue3-essential'].rules,
...pluginVue.configs['vue3-strongly-recommended'].rules,
...pluginVue.configs['vue3-recommended'].rules,
...pluginVue.configs?.base?.rules,
'vue/attribute-hyphenation': [
'error',
@@ -131,7 +136,6 @@ export async function vue(): Promise<Linter.Config[]> {
'vue/require-default-prop': 'error',
'vue/require-explicit-emits': 'error',
'vue/require-prop-types': 'off',
'vue/script-setup-uses-vars': 'error',
'vue/singleline-html-element-content-newline': 'off',
'vue/space-infix-ops': 'error',
'vue/space-unary-ops': ['error', { nonwords: false, words: true }],

View File

@@ -43,6 +43,7 @@ export default {
'stylelint-scss',
],
rules: {
'at-rule-no-deprecated': null,
'at-rule-no-unknown': [
true,
{

Some files were not shown because too many files have changed in this diff Show More