118 Commits

Author SHA1 Message Date
dap
006370798b refactor: tinymce+表单 校验失败样式 2025-06-05 21:18:36 +08:00
dap
831700660c refactor: 角色去掉moredropdown 改为平铺 2025-06-04 21:53:11 +08:00
dap
a53b9382f5 refactor: 排序 默认值 2025-06-04 19:49:03 +08:00
dap
703586123a refactor: 字典接口抛出异常(为什么会抛出异常?)无限调用接口 兼容处理 2025-06-04 19:06:18 +08:00
dap
0295418f79 fix: 菜单管理 路由地址的必填项不生效 2025-06-04 17:53:09 +08:00
dap
14b0d9b50f Merge branch 'main' of https://github.com/vbenjs/vue-vben-admin into dev 2025-06-04 09:18:54 +08:00
vben
b9aef618fe chore: release 5.5.7 2025-06-04 05:33:06 +08:00
Netfan
4102cc2211 feat: improve vbenCheckButtonGroup (#6329)
* 按钮组支持单选清除和多选限制最大选项数

* 按钮组支持icon插槽来定制图标
2025-06-03 23:11:56 +08:00
chewenye
ea776aa710 types: 扩展user-dropdown组件的menus类型,支持iconify (#6283)
Co-authored-by: 车文烨 <chewy@china-lehua.com>
2025-06-03 06:07:06 +08:00
huanghezhen
feb96dc8ea fix: resolve onClosed method failure in connectedComponent of useVbenModal (#6309) 2025-06-02 08:16:48 +08:00
wyc001122
470fd43b49 fix: 修复使用useVbenVxeGrid配置hasEmptyText、hasEmptyRender不生效的问题 (#6310) 2025-06-02 08:16:26 +08:00
zhang
76d106e474 fix: When defaultHomePage is inconsistent with user.homePath, the pa… (#6299)
* fix:  When defaultHomePage is inconsistent with user.homePath, the page refresh route jump will be abnormal

* fix:  When defaultHomePage is inconsistent with user.homePath, the page refresh route jump will be abnormal
2025-06-02 08:07:06 +08:00
wyc001122
78c3c9da6f docs(settings): 完善'生产环境动态配置'步骤 (#6297) 2025-06-02 08:05:23 +08:00
dap
8b7d717b21 Merge branch 'main' of https://github.com/vbenjs/vue-vben-admin into dev 2025-05-31 18:53:41 +08:00
Netfan
081d08a7f8 fix: alert width fixed in small screen (#6312) 2025-05-30 19:54:26 +08:00
dap
0da75418d0 refactor: 菜单管理 刷新接口后 记录展开行的情况 (改为vxe配置) 2025-05-30 17:51:06 +08:00
dap
55f0da3085 refactor: tree-search 2025-05-30 14:24:40 +08:00
dap
3849800388 Merge branch 'main' of https://github.com/vbenjs/vue-vben-admin into dev 2025-05-30 09:20:33 +08:00
dap
f913955259 docs: changelog 2025-05-29 19:45:38 +08:00
dap
8d6ef40d3e refactor: 适配新工作流预览 由logicflow改为iframe 真服了 2025-05-29 19:30:57 +08:00
dap
2141c93399 docs: readme 2025-05-29 11:17:29 +08:00
dap
906502f49b refactor: 字典项列表样式重构(下个版本再介入) 2025-05-29 11:10:46 +08:00
Netfan
96a10ca83f style: fix lint error (#6298) 2025-05-28 19:23:21 +08:00
dap
1f68fd31b7 Merge branch 'main' of https://github.com/vbenjs/vue-vben-admin into dev 2025-05-28 17:13:50 +08:00
wyc001122
f31360ba4e feat: support for hybrid permission access control mode (#6294)
* feat: 添加混合权限访问控制模式

* feat: 文档补充
2025-05-28 17:01:58 +08:00
wyc001122
4eb16d6d3a fix: fix table-title slot not work (#6295) 2025-05-28 17:01:11 +08:00
dap
9db6ade1ed fix: oss配置是否默认取值 2025-05-27 19:35:22 +08:00
dap
2569e1da0d refactor: 字典项布局重构(未完成) 2025-05-27 14:27:01 +08:00
dap
2de9cd2334 docs: changelog 2025-05-27 11:52:41 +08:00
dap
739e04816a refactor: 只有超管才能对菜单进行增删改操作 2025-05-27 11:47:23 +08:00
dap
d9c57dfb61 feat: 流程预览dot画布+夜间背景色 2025-05-26 17:26:13 +08:00
dap
2217c96cd9 chore: ele版本会使用这个文件 只是为了不报错未找到对应组件才新建的这个文件 无实际意义 2025-05-26 17:08:15 +08:00
dap
752c1ac3ed refactor: 字典项布局重构(未完成) 2025-05-26 17:05:52 +08:00
liqiang0330
53304514b6 fix: Update index.ts (#6268)
* Update index.ts

VxeGridPropTypes.原文件缺少这个,现在补全!

* Update index.ts

增加空格!
2025-05-26 13:29:27 +08:00
dap
cf913f8b8d docs: changelog 2025-05-26 13:27:42 +08:00
dap
ad9c465622 update: remove unused file 2025-05-26 13:25:44 +08:00
dap
bea7c1a094 refactor: preserveTreeTableState 2025-05-26 09:28:54 +08:00
dap
95859e36a2 update: menuName i18n 2025-05-26 09:26:34 +08:00
dap
8bfc482a7f feat: logicflow显示流程图 2025-05-25 16:38:13 +08:00
Netfan
6fbf1387f5 fix: reset slider-captcha after login failed (#6275) 2025-05-25 16:04:56 +08:00
dap
c3edbec3f0 Merge branch 'main' of https://github.com/vbenjs/vue-vben-admin into dev 2025-05-25 11:48:58 +08:00
Netfan
e5c937396d fix: json-bigint parse used in vxeTable (#6271)
* 修复vxeTable不能加载json-bigint解析的数据的问题
2025-05-24 13:01:58 +08:00
dap
45de9b7547 docs: changelog 2025-05-24 12:46:50 +08:00
dap
6daedd1de5 feat: 菜单级联删除 2025-05-24 12:45:18 +08:00
dap
c45eed90d9 fix: dept list 列宽调整 2025-05-24 11:59:39 +08:00
dap
845719d951 chore: version update 2025-05-23 16:29:49 +08:00
dap
4ef974ca4e feat: 跳转菜单图标更新 2025-05-23 16:07:17 +08:00
littlesparklet
af186f878d fix: repair the unexpected form default value (#5567)
* fix: Fix inconsistent spacing around search form (issue #5429)

* fix: repair the unexpected default value in validated form.(issue #5451)

* Update packages/@core/ui-kit/form-ui/src/use-form-context.ts

Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>

---------

Co-authored-by: Jin Mao <50581550+jinmao88@users.noreply.github.com>
Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
2025-05-23 16:05:11 +08:00
dap
2dc7e564b2 feat: 升级需要执行sql的提示 2025-05-23 15:55:25 +08:00
wyc001122
97894a940e feat: optimize logo display (#6267)
* feat(VbenAvatar): add fit property to VbenAvatar component

* feat(VbenLogo): add fit property to VbenLogo component

* feat(VbenLogo): add logo fit preference configuration

- Add preferences.logo.fit setting for logo display control
- Include corresponding documentation for the new preference

* feat(preferences): add default value for logo.fit preference

- Set default configuration for logo fit behavior
- Ensures consistent logo display across applications

* test(preferences): update configuration snapshots

---------

Co-authored-by: wyc001122 <wangyongchao@testor.com.cn>
2025-05-23 15:24:01 +08:00
yingzi2019
48d70182b4 feat: improve check updates (#6257)
Co-authored-by: monkey <maotao@tutamail.com>
2025-05-23 15:23:06 +08:00
Netfan
a1091bad46 feat: enhances compatibility with APIs returning large numeric values (#6250) 2025-05-23 15:22:18 +08:00
zhang
9f9be21e2a fix: component Input is not registered when initialize page (#6246)
* fix: Component Input is not registered when initialize page

* fix: Component Input is not registered when initialize page
2025-05-23 15:21:09 +08:00
panda7
a2bdcd6e49 feat: ellipsis text automatically displays tooltip based on ellipsis (#6244)
* feat: ellipsis text automatically displays tooltip based on ellipsis

* feat: ellipsis text automatically displays tooltip based on ellipsis

---------

Co-authored-by: sqchen <9110848@qq.com>
Co-authored-by: sqchen <chenshiqi@sshlx.com>
2025-05-23 15:20:38 +08:00
dap
a38f2de982 refactor: 所有本地路由(除个人中心/workflow-iframe)改为从后端返回 适配 2025-05-23 14:30:12 +08:00
dap
d039c53053 Merge branch 'main' of https://github.com/vbenjs/vue-vben-admin into dev 2025-05-22 09:20:32 +08:00
ali-pay
11b2b5bcc2 fix: 修复菜单管理中按钮类型值错误的问题 (#6255) 2025-05-22 09:09:31 +08:00
LinaBell
ebef2c91e2 fix: tab cannot be displayed correctly after browser refresh (#6256) 2025-05-22 09:04:40 +08:00
Netfan
0c3edb10b0 fix: getFieldComponentRef will return actual ref within AsyncComponentWrapper (#6252)
修复异步加载组件时,表单的getFieldComponentRef方法没能获取到正确的组件实例
2025-05-21 14:48:51 +08:00
dap
801514dbe3 feat: 支持pdf预览(原生 浏览器接管) 2025-05-21 11:54:55 +08:00
dap
2dce7718d6 refactor: fallbackImageBase64 2025-05-20 21:16:43 +08:00
dap
8a8e090792 Merge branch 'dev' of https://gitee.com/dapppp/ruoyi-plus-vben5 into dev 2025-05-20 12:56:14 +08:00
dap
6fc2c4e3cc Merge branch 'main' of https://github.com/vbenjs/vue-vben-admin into dev 2025-05-20 12:56:05 +08:00
wyc001122
8ac97688da fix(preferences): 更新内容内边距默认值 (#6233)
Co-authored-by: wyc001122 <wangyongchao@testor.com.cn>
2025-05-20 09:50:23 +08:00
陈绍华
c89ec0088b style: 更改OSS配置的状态方法添加 TS 类型
Signed-off-by: 陈绍华 <marlboro027@foxmail.com>
2025-05-20 01:40:04 +00:00
dap
0a076f5e6e fix: 更新后的padding设置为0 2025-05-19 21:52:16 +08:00
dap
4fd68bc083 Merge branch 'main' of https://github.com/vbenjs/vue-vben-admin into dev 2025-05-19 21:33:49 +08:00
李轻舟
2efacb3e5b docs: Update build.md (#6228) 2025-05-19 16:30:39 +08:00
wyc001122
dae46abb71 feat: additional-settings (#6225)
* feat(preferences): 补充VbenAdminLayout传入属性(来自偏好设置)

* docs(@vben/docs):update settings doc

---------

Co-authored-by: wyc001122 <wangyongchao@testor.com.cn>
2025-05-19 16:29:15 +08:00
wyc001122
5ee2a74e2d fix(use-design-tokens): 完善element-plus暗色主题颜色 (#6224)
Co-authored-by: wyc001122 <wangyongchao@testor.com.cn>
2025-05-19 16:27:34 +08:00
dap
79d89005b6 对错误用法的提示(完全无奈...) 2025-05-18 14:28:57 +08:00
afe1
d0b8349a2d perf: stub unbuild params (#6210) 2025-05-18 10:35:20 +08:00
wyc001122
34c4ecb047 fix: in mixed layout mode, the sidebar does not display when the first child node is an external link (#6219)
Co-authored-by: wyc001122 <wangyongchao@testor.com.cn>
2025-05-18 10:34:41 +08:00
ming4762
3d9dba965f perf: perf the control logic of Tab (#6220)
* perf: perf the control logic of Tab

* 每个标签页Tab使用唯一的key来控制关闭打开等逻辑
* 统一函数获取tab的key
* 通过3种方式设置tab key:1、使用router query参数pageKey 2、使用路由meta参数fullPathKey设置使用fullPath或path作为key
* 单个路由可以打开多个标签页
* 如果设置fullPathKey为false,则query变更不会打开新的标签(这很实用)

* perf: perf the control logic of Tab

* perf: perf the control logic of Tab

* 测试用例适配

* perf: perf the control logic of Tab

* 解决AI提示的警告
2025-05-18 10:33:02 +08:00
dap
96b8ae94fd feat: 保存表格滚动/展开状态并执行回调 用于树表在执行 新增/编辑/删除等操作后 依然在当前位置(体验优化) 2025-05-16 16:48:22 +08:00
wyc001122
024c01d350 fix(@vben-core/shadcn-ui): fix disabled functionality not working in VbenTree component (#6205)
* fix(@vben-core/shadcn-ui): fix disabled functionality not working in VbenTree component

* fix(@vben-core/shadcn-ui): add cursor-not-allowed className when disabled and disable onfocus

---------

Co-authored-by: wyc001122 <wangyongchao@testor.com.cn>
Co-authored-by: Jin Mao <50581550+jinmao88@users.noreply.github.com>
2025-05-16 14:13:43 +08:00
dap
10b8b81954 docs: version update 2025-05-16 10:06:51 +08:00
afe1
2adb8acd80 fix: css style (#6176) 2025-05-16 09:40:40 +08:00
panda7
a23bc4cb5c fix: the mobile terminal can wrap lines and expand slot attributes (#6165)
Co-authored-by: sqchen <chenshiqi@sshlx.com>
2025-05-16 09:40:05 +08:00
XiaoHetitu
cf17a45d8d feat(tabs): 支持计算属性作为标签标题,解决 #6170 的问题 (#6163)
* feat(tabs): 支持动态函数作为标签标题

修改 `setTabTitle` 和 `tabsView` 逻辑,允许传入函数作为标签标题,以便动态生成标题内容

* feat(tabbar): 添加动态设置标签页标题功能

允许设置静态字符串或动态函数作为标签标题,支持根据状态或语言变化动态更新标题

* refactor(tabs): 移除冗余的newTabTitle2变量并优化标题设置逻辑

移除tabs组件中冗余的newTabTitle2变量,直接使用newTabTitle作为标题来源。同时,优化use-tabs和tabbar模块的标题设置逻辑,支持ComputedRef作为动态标题,提升代码简洁性和可维护性。

---------

Co-authored-by: yuanwj <ywj6792341@qq.com>
2025-05-16 09:37:50 +08:00
chewenye
b46ebe756e types: 导出authentication组件的type,自定义toolbarList时类型使用ToolbarType (#6158)
Co-authored-by: 车文烨 <chewy@china-lehua.com>
2025-05-16 09:36:49 +08:00
哦是吗
1f50c95c66 update apps/web-antd/src/components/upload/src/hook.ts.
fix: 上传组件清空绑定值时,同时清空innerFileList,避免外部使用时还能读取到

Signed-off-by: 哦是吗 <1733179386@qq.com>
2025-05-15 07:38:02 +00:00
zhangl1438
cd4706b717 fix:修复切换默认oss config后,上传文件报错:“文件存储服务类型无法找到” 2025-05-14 11:45:43 +08:00
dap
769aceb55f Merge branch 'main' of https://github.com/vbenjs/vue-vben-admin into dev 2025-05-13 11:32:35 +08:00
Netfan
e89cf400c0 fix: refresh command of tabbar issue, fixed: #6162 (#6169) 2025-05-12 23:34:08 +08:00
anyup
9e67929ee7 feat: support to refresh the tab page by route name (#6153)
Co-authored-by: anyup <anyupxing@163.com>
2025-05-10 22:33:31 +08:00
dap
7926865bf9 docs: version update 2025-05-09 11:12:54 +08:00
dap
51fbfcedd2 fix: 某些带Vxe表格弹窗 关闭后没有正常清理表格数据的问题 2025-05-09 11:12:24 +08:00
dap
8f71d6a5d9 docs: changelog and version update 2025-05-09 10:08:48 +08:00
afe1
90625782c0 fix: delete useless code (#6143) 2025-05-08 16:51:12 +08:00
dap
12d0ba24e5 Merge branch 'main' of https://github.com/vbenjs/vue-vben-admin into dev 2025-05-08 09:28:52 +08:00
dap
540f24ed43 Revert "fix: 更新表格增加minWidth属性"
This reverts commit b52f3ba0c5.
2025-05-08 09:26:03 +08:00
Yann
c57d3f32b5 Merge remote-tracking branch 'origin/dev' 2025-05-07 22:53:51 +08:00
Yann
b52f3ba0c5 fix: 更新表格增加minWidth属性 2025-05-07 22:52:13 +08:00
wyc001122
84ef207d9c docs(@vben/docs): update settings doc (#6128)
Co-authored-by: wyc001122 <wangyongchao@testor.com.cn>
2025-05-07 12:04:48 +08:00
zyf0624
e68fff58e8 fix: tsconfig moduleResolution (#6122)
Co-authored-by: pzzyf <2279948211@qq.com>
2025-05-07 12:04:15 +08:00
dap
63c06e02b2 fix: 修改手机号验证码长度 2025-05-07 10:31:45 +08:00
Netfan
bf70539221 fix: missing argument for getPopupContainer 2025-05-06 22:48:03 +08:00
Leeson
5949c73a30 fix: delete Popconfirm being obscured by fixed columns (#6118)
* fix: delete Popconfirm being obscured by fixed columns

* fix: opened popConfirm will prevent the table from scrolling

---------

Co-authored-by: Netfan <netfan@foxmail.com>
2025-05-06 22:33:17 +08:00
vben
cc6c9bf7a0 chore: release v5.5.6 2025-05-06 22:32:58 +08:00
Jin Mao
6b1aab9c67 fix: handle undefined children in generate-menus (#6117)
When children is undefined, use empty array as fallback to prevent potential runtime errors. This matches the behavior when hideChildrenInMenu is true.
2025-05-06 14:29:50 +08:00
LinaBell
8f4d3d418d fix: when keepAlive is enabled, returning directly through browser buttons/gestures will not close pop ups (#6113) 2025-05-06 14:02:23 +08:00
dap
aa086a2800 refactor: replace defaultHomePath 2025-05-06 09:41:11 +08:00
ming4762
3b3f8e4e44 fix: fix IconPicker props warning (#6108)
Invalid prop: type check failed for prop "onUpdate:value". Expected Function, got Array
2025-05-06 09:30:37 +08:00
dap
b0763d6429 Merge branch 'main' of https://github.com/vbenjs/vue-vben-admin into dev 2025-05-04 17:23:32 +08:00
vben
f94ca10adf chore: remove prepare script from package.json 2025-05-04 07:33:36 +08:00
vben
4471bc7a5d chore: update prepare script in package.json to remove lefthook installation 2025-05-04 00:05:29 +08:00
Vben
5689ac60ff feat(project): migrate from husky and lint-staged to lefthook (#6104) 2025-05-03 19:43:12 +08:00
Vben
045bc4e5ee feat: support smooth auto-scroll to active menu item (#6102) 2025-05-03 18:05:26 +08:00
Vben
17a18fc9ba chore: close eslint object sorting (#6101) 2025-05-03 16:06:36 +08:00
aonoa
41152d1722 refactor: modify the default homepage path loaded from the preference… (#6099)
* refactor: modify the default homepage path loaded from the preferences.ts

Signed-off-by: aonoa <1991849113@qq.com>

* refactor: modify the default homepage path loaded from the preferences.ts

Signed-off-by: aonoa <1991849113@qq.com>

---------

Signed-off-by: aonoa <1991849113@qq.com>
2025-05-03 16:03:08 +08:00
Netfan
f1af9f8f6e fix: add triggerClass binding to PopoverTrigger and update icon-picker styles (#6095)
* Popover支持设置trigger的样式
* 修正icon-picker的input值更新
2025-05-01 21:40:45 +08:00
Netfan
0517a7014f fix: add missing translation for preferences drawer (#6094) 2025-05-01 20:08:44 +08:00
Netfan
3e6d608a2f fix: destroyOnClose incorrect default value, fixed #6092 (#6093) 2025-05-01 14:09:37 +08:00
ming4762
5de954baa4 fix: fix LoginExpiredModal in some cases, message may be obscured (#6086) 2025-05-01 10:40:42 +08:00
Netfan
add1e61b6f fix: show validation message as tooltip in compact form (#6087)
* 紧凑模式表单的校验消息将显示为一个tooltip
2025-04-30 23:41:44 +08:00
dap
9f978cc9b0 fix: 用来标识是否为上传 这样在watch内部不需要请求api 2025-04-30 14:13:35 +08:00
dap
89dd4b8131 chore: version update 2025-04-30 11:26:57 +08:00
194 changed files with 2560 additions and 682 deletions

View File

@@ -1,6 +0,0 @@
echo Start running commit-msg hook...
# Check whether the git commit information is standardized
pnpm exec commitlint --edit "$1"
echo Run commit-msg hook done.

View File

@@ -1,3 +0,0 @@
# 每次 git pull 之后, 安装依赖
pnpm install

View File

@@ -1,7 +0,0 @@
# update `.vscode/vben-admin.code-workspace` file
pnpm vsh code-workspace --auto-commit
# Format and submit code according to lintstagedrc.js configuration
pnpm exec lint-staged
echo Run pre-commit hook done.

View File

@@ -1,20 +0,0 @@
export default {
'*.md': ['prettier --cache --ignore-unknown --write'],
'*.vue': [
'prettier --write',
'eslint --cache --fix',
'stylelint --fix --allow-empty-input',
],
'*.{js,jsx,ts,tsx}': [
'prettier --cache --ignore-unknown --write',
'eslint --cache --fix',
],
'*.{scss,less,styl,html,vue,css}': [
'prettier --cache --ignore-unknown --write',
'stylelint --fix --allow-empty-input',
],
'package.json': ['prettier --cache --write'],
'{!(package)*.json,*.code-snippets,.!(browserslist)*rc}': [
'prettier --cache --write --parser json',
],
};

2
.npmrc
View File

@@ -1,5 +1,5 @@
registry = "https://registry.npmmirror.com"
public-hoist-pattern[]=husky
public-hoist-pattern[]=lefthook
public-hoist-pattern[]=eslint
public-hoist-pattern[]=prettier
public-hoist-pattern[]=prettier-plugin-tailwindcss

View File

@@ -220,7 +220,7 @@
"*.env": "$(capture).env.*",
"README.md": "README*,CHANGELOG*,LICENSE,CNAME",
"package.json": "pnpm-lock.yaml,pnpm-workspace.yaml,.gitattributes,.gitignore,.gitpod.yml,.npmrc,.browserslistrc,.node-version,.git*,.tazerc.json",
"eslint.config.mjs": ".eslintignore,.prettierignore,.stylelintignore,.commitlintrc.*,.prettierrc.*,stylelint.config.*,.lintstagedrc.mjs,cspell.json",
"eslint.config.mjs": ".eslintignore,.prettierignore,.stylelintignore,.commitlintrc.*,.prettierrc.*,stylelint.config.*,.lintstagedrc.mjs,cspell.json,lefthook.yml",
"tailwind.config.mjs": "postcss.*"
},
"commentTranslate.hover.enabled": false,
@@ -241,6 +241,7 @@
"napi",
"nolebase",
"rollup",
"tinymce",
"vitest"
]
}

View File

@@ -1,3 +1,50 @@
# 1.4.1
**BUG FIX**
- 菜单管理 路由地址的必填项不生效
**REFACTOR**
- 字典接口抛出异常(为什么会抛出异常?)无限调用接口 兼容处理
# 1.4.0
**FEATURES**
- 菜单管理(通用方法) 保存表格滚动/展开状态并执行回调 用于树表在执行 新增/编辑/删除等操作后 依然在当前位置(体验优化)
- 菜单管理 级联删除 删除菜单和children
**REFACTOR**
- 除个人中心外所有本地路由改为从后端返回(需要执行更新sql)
- 流程图预览改为logicflow预览而非图片 ...然后后端又更新了 又改成iframe了
- 菜单管理 新增角色校验(与后端权限保持一致) 只有superadmin可进行增删改
# 1.3.6
**BUG FIX**
- oss配置switch切换 导致报错`存储类型找不到`
- 文件上传无法正确清除(innerList)
# 1.3.5
**BUG FIX**
- 某些带Vxe表格弹窗 关闭后没有正常清理表格数据的问题
# 1.3.4
**BUG FIX**
- 文件上传多次触发导致数据不一致 https://gitee.com/dapppp/ruoyi-plus-vben5/issues/IC3BK6
**PREFORMANCE**
- 浏览器返回按钮/手势操作时 弹窗不会被关闭(keepAlive导致)
# 1.3.3
**BUG FIX**

View File

@@ -6,7 +6,7 @@
v5版本采用分仓(包)目录结构, 具体开发路径为: `根目录/apps/web-antd`
目前对应后端版本: **分布式5.3.1/微服务2.3.0**
目前对应后端版本: **分布式5.4.0/微服务2.4.0**
V1.1.0版本已支持离线图标
@@ -18,7 +18,7 @@ V1.2.0版本对接warmflow工作流
| 组件/框架 | 版本 |
| :------------- | :----- |
| vben | 5.5.4 |
| vben | 5.5.6 |
| ant-design-vue | 4.2.6 |
| vue | 3.5.13 |
@@ -46,14 +46,6 @@ admin 账号: admin admin123
[RuoYi-Plus 文档地址](https://plus-doc.dromara.org/#/)
## 关于表单
如果你觉得`useVbenForm`难度很大, 完全可以**使用原生antd表单**进行开发, 不一定非得用`useVbenForm`进行开发
`apps/web-antd/src/views/system/notice/notice-modal.vue``通知公告modal`使用**原生antd form**进行(反向🤔)重构, 不想用`useVbenForm`可参考该页面进行表单开发
复杂表单(如各种联动, 需要自定义样式布局, 需要自定义组件)**优先使用原生表单**(反正说了也没人听听😅)
## 预览图
![图片](https://gitee.com/dapppp/ruoyi-plus-vben5/raw/main/scripts/preview/1.png) ![图片](https://gitee.com/dapppp/ruoyi-plus-vben5/raw/main/scripts/preview/2.png) ![图片](https://gitee.com/dapppp/ruoyi-plus-vben5/raw/main/scripts/preview/3.png) ![图片](https://gitee.com/dapppp/ruoyi-plus-vben5/raw/main/scripts/preview/4.png) ![图片](https://gitee.com/dapppp/ruoyi-plus-vben5/raw/main/scripts/preview/5.png) ![图片](https://gitee.com/dapppp/ruoyi-plus-vben5/raw/main/scripts/preview/6.png) ![图片](https://gitee.com/dapppp/ruoyi-plus-vben5/raw/main/scripts/preview/7.png) ![图片](https://gitee.com/dapppp/ruoyi-plus-vben5/raw/main/scripts/preview/8.png) ![图片](https://gitee.com/dapppp/ruoyi-plus-vben5/raw/main/scripts/preview/9.png)

View File

@@ -0,0 +1,28 @@
export default eventHandler(async (event) => {
const userinfo = verifyAccessToken(event);
if (!userinfo) {
return unAuthorizedResponse(event);
}
const data = `
{
"code": 0,
"message": "success",
"data": [
{
"id": 123456789012345678901234567890123456789012345678901234567890,
"name": "John Doe",
"age": 30,
"email": "john-doe@demo.com"
},
{
"id": 987654321098765432109876543210987654321098765432109876543210,
"name": "Jane Smith",
"age": 25,
"email": "jane@demo.com"
}
]
}
`;
setHeader(event, 'Content-Type', 'application/json');
return data;
});

View File

@@ -1,6 +1,6 @@
{
"name": "@vben/web-antd",
"version": "1.3.2",
"version": "1.4.1",
"homepage": "https://vben.pro",
"bugs": "https://github.com/vbenjs/vue-vben-admin/issues",
"repository": {

View File

@@ -10,44 +10,46 @@ import { $t } from '@vben/locales';
import { isArray } from 'lodash-es';
setupVbenForm<ComponentType>({
config: {
// ant design vue组件库默认都是 v-model:value
baseModelPropName: 'value',
async function initSetupVbenForm() {
setupVbenForm<ComponentType>({
config: {
// ant design vue组件库默认都是 v-model:value
baseModelPropName: 'value',
// 一些组件是 v-model:checked 或者 v-model:fileList
modelPropNameMap: {
Checkbox: 'checked',
Radio: 'checked',
RichTextarea: 'modelValue',
Switch: 'checked',
Upload: 'fileList',
// 一些组件是 v-model:checked 或者 v-model:fileList
modelPropNameMap: {
Checkbox: 'checked',
Radio: 'checked',
RichTextarea: 'modelValue',
Switch: 'checked',
Upload: 'fileList',
},
},
},
defineRules: {
// 输入项目必填国际化适配
required: (value, _params, ctx) => {
if (value === undefined || value === null || value.length === 0) {
return $t('ui.formRules.required', [ctx.label]);
}
return true;
defineRules: {
// 输入项目必填国际化适配
required: (value, _params, ctx) => {
if (value === undefined || value === null || value.length === 0) {
return $t('ui.formRules.required', [ctx.label]);
}
return true;
},
// 选择项目必填国际化适配
selectRequired: (value, _params, ctx) => {
if (
[false, null, undefined].includes(value) ||
(isArray(value) && value.length === 0)
) {
return $t('ui.formRules.selectRequired', [ctx.label]);
}
return true;
},
},
// 选择项目必填国际化适配
selectRequired: (value, _params, ctx) => {
if (
[false, null, undefined].includes(value) ||
(isArray(value) && value.length === 0)
) {
return $t('ui.formRules.selectRequired', [ctx.label]);
}
return true;
},
},
});
});
}
const useVbenForm = useForm<ComponentType>;
export { useVbenForm, z };
export { initSetupVbenForm, useVbenForm, z };
export type VbenFormSchema = FormSchema<ComponentType>;
export type { VbenFormProps };

View File

@@ -1,7 +1,12 @@
import type { GrantType } from '@vben/common-ui';
import type { HttpResponse } from '@vben/request';
import { h } from 'vue';
import { useAppConfig } from '@vben/hooks';
import { Modal } from 'ant-design-vue';
import { requestClient } from '#/api/request';
const { clientId, sseEnable } = useAppConfig(
@@ -90,8 +95,32 @@ export async function loginApi(data: AuthApi.LoginParams) {
* 用户登出
* @returns void
*/
export function doLogout() {
return requestClient.post<void>('/auth/logout');
export async function doLogout() {
const resp = await requestClient.post<HttpResponse<void>>(
'/auth/logout',
null,
{
isTransformResponse: false,
},
);
// 无奈之举 对错误用法的提示
if (resp.code === 401 && import.meta.env.DEV) {
Modal.destroyAll();
Modal.warn({
title: '后端配置出现错误',
centered: true,
content: h('div', { class: 'flex flex-col gap-2' }, [
`检测到你的logout接口返回了401, 导致前端一直进入循环逻辑???`,
...Array.from({ length: 3 }, () =>
h(
'span',
{ class: 'font-bold text-red-500 text-[18px]' },
'去检查你的后端配置!别盯着前端找问题了!这不是前端问题!',
),
),
]),
});
}
}
/**

View File

@@ -39,6 +39,11 @@ const { apiURL, clientId, enableEncrypt } = useAppConfig(
*/
let isLogoutProcessing = false;
/**
* 定义一个401专用异常 用于可能会用到的区分场景?
*/
export class UnauthorizedException extends Error {}
function createRequestClient(baseURL: string) {
const client = new RequestClient({
// 后端地址
@@ -228,7 +233,7 @@ function createRequestClient(baseURL: string) {
case 401: {
// 已经在登出过程中 不再执行
if (isLogoutProcessing) {
throw new Error(timeoutMsg);
throw new UnauthorizedException(timeoutMsg);
}
isLogoutProcessing = true;
const _msg = $t('http.loginTimeout');
@@ -238,7 +243,7 @@ function createRequestClient(baseURL: string) {
isLogoutProcessing = false;
});
// 不再执行下面逻辑
throw new Error(_msg);
throw new UnauthorizedException(_msg);
}
default: {
if (msg) {

View File

@@ -81,3 +81,12 @@ export function tenantPackageMenuTreeSelect(packageId: ID) {
`${Api.tenantPackageMenuTreeselect}/${packageId}`,
);
}
/**
* 批量删除菜单
* @param menuIds 菜单ids
* @returns void
*/
export function menuCascadeRemove(menuIds: IDS) {
return requestClient.deleteWithMsg<void>(`${Api.root}/cascade/${menuIds}`);
}

View File

@@ -37,9 +37,10 @@ export function ossConfigRemove(ossConfigIds: IDS) {
// 更改OSS配置的状态
export function ossConfigChangeStatus(data: any) {
const requestData = {
const requestData: Partial<OssConfig> = {
ossConfigId: data.ossConfigId,
status: data.status,
configKey: data.configKey,
};
return requestClient.putWithMsg(Api.ossConfigChangeStatus, requestData);
}

View File

@@ -94,7 +94,7 @@ export function pageByCurrent(params?: PageQuery) {
*/
export function flowInfo(businessId: string) {
return requestClient.get<FlowInfoResponse>(
`/workflow/instance/flowImage/${businessId}`,
`/workflow/instance/flowHisTaskList/${businessId}`,
);
}

View File

@@ -36,6 +36,6 @@ export interface Flow {
}
export interface FlowInfoResponse {
image: string;
instanceId: string;
list: Flow[];
}

View File

@@ -8,6 +8,8 @@ import { App, ConfigProvider, theme } from 'ant-design-vue';
import { antdLocale } from '#/locales';
import { useUploadTip } from './upload-tip';
defineOptions({ name: 'App' });
const { isDark } = usePreferences();
@@ -28,6 +30,8 @@ const tokenTheme = computed(() => {
token: tokens,
};
});
useUploadTip();
</script>
<template>

View File

@@ -13,6 +13,7 @@ import { setupGlobalComponent } from '#/components/global';
import { $t, setupI18n } from '#/locales';
import { initComponentAdapter } from './adapter/component';
import { initSetupVbenForm } from './adapter/form';
import App from './app.vue';
import { router } from './router';
@@ -20,6 +21,9 @@ async function bootstrap(namespace: string) {
// 初始化组件适配器
await initComponentAdapter();
// 初始化表单组件
await initSetupVbenForm();
// // 设置弹窗的默认配置
// setDefaultModalProps({
// fullscreenButton: false,

View File

@@ -202,10 +202,21 @@ const events = computed(() => {
</template>
<style lang="scss">
/***
由于modal/drawer的zIndex升级后为2000
这里会造成遮挡 修改为更高的zIndex
*/
@mixin tinymce-valid-fail($color) {
.app-tinymce {
// 最外层的tinymce容器
.tox-tinymce {
border-color: $color;
}
// focus样式
.tox .tox-edit-area::before {
border-color: $color;
border-right: none;
border-left: none;
}
}
}
.tox.tox-silver-sink.tox-tinymce-aux {
/** 该样式默认为1300的zIndex */
z-index: 2025;
@@ -218,5 +229,24 @@ const events = computed(() => {
.tox-promotion {
display: none;
}
/** 保持focus时与primary色一致 */
.tox .tox-edit-area::before {
border-color: hsl(var(--primary));
}
}
// antd原生表单 校验失败样式
.ant-form-item:has(.ant-form-item-explain-error) {
$error-color: #ff3860;
@include tinymce-valid-fail($error-color);
}
// useVbenForm 校验失败样式
.form-valid-error {
$error-color: hsl(var(--destructive));
@include tinymce-valid-fail($error-color);
}
</style>

View File

@@ -160,6 +160,8 @@ export function useUpload(
return undefined;
}
// 用来标识是否为上传 这样在watch内部不需要请求api
let isUpload = false;
function handleChange(info: UploadChangeParam) {
/**
* 移除当前文件
@@ -199,7 +201,8 @@ export function useUpload(
currentFile.fileName = transformFilename(cb);
currentFile.name = transformFilename(cb);
currentFile.thumbUrl = transformThumbUrl(cb);
// 标记为上传 watch根据值做处理
isUpload = true;
// ossID添加 单个文件会被当做string
if (props.maxCount === 1) {
bindValue.value = ossId;
@@ -319,8 +322,18 @@ export function useUpload(
() => bindValue.value,
async (value) => {
if (value.length === 0) {
// 清空绑定值时同时清空innerFileList避免外部使用时还能读取到
innerFileList.value = [];
return;
}
// 上传完毕 不需要调用获取信息接口
if (isUpload) {
// 清理 使下一次状态可用
isUpload = false;
return;
}
const resp = await ossInfo(value);
function transformFile(info: OssFile) {
const cb = { type: 'info', response: info } as const;

View File

@@ -1,6 +1,7 @@
import type {
ComponentRecordType,
GenerateMenuAndRoutesOptions,
RouteMeta,
RouteRecordStringComponent,
} from '@vben/types';
@@ -21,6 +22,37 @@ import { localMenuList } from './routes/local';
const forbiddenComponent = () => import('#/views/_core/fallback/forbidden.vue');
const NotFoundComponent = () => import('#/views/_core/fallback/not-found.vue');
/**
* 后端返回的meta有时候不包括需要的信息 比如activePath等
* 在这里定义映射
*/
const routeMetaMapping: Record<string, Omit<RouteMeta, 'title'>> = {
'/system/role-auth/user/:roleId(\\d+)': {
activePath: '/system/role',
requireHomeRedirect: true,
},
'/system/oss-config/index': {
activePath: '/system/oss',
requireHomeRedirect: true,
},
'/tool/gen-edit/index/:tableId(\\d+)': {
activePath: '/tool/gen',
requireHomeRedirect: true,
},
'/workflow/design/index': {
activePath: '/workflow/processDefinition',
requireHomeRedirect: true,
},
'/workflow/leaveEdit/index': {
activePath: '/demo/leave',
requireHomeRedirect: true,
},
};
/**
* 后台路由转vben路由
* @param menuList 后台菜单
@@ -98,6 +130,17 @@ function backMenuToVbenMenu(
path: menu.path,
};
// 处理meta映射
if (Object.keys(routeMetaMapping).includes(vbenRoute.path)) {
const routeMeta = routeMetaMapping[vbenRoute.path];
if (routeMeta) {
vbenRoute.meta = {
...vbenRoute.meta,
...(routeMeta as RouteMeta),
};
}
}
// 添加路由参数信息
if (menu.query) {
try {

View File

@@ -1,6 +1,6 @@
import type { Router } from 'vue-router';
import { DEFAULT_HOME_PATH, LOGIN_PATH } from '@vben/constants';
import { LOGIN_PATH } from '@vben/constants';
import { preferences } from '@vben/preferences';
import { useAccessStore, useUserStore } from '@vben/stores';
import { startProgress, stopProgress } from '@vben/utils';
@@ -18,7 +18,7 @@ function setupCommonGuard(router: Router) {
// 记录已经加载的页面
const loadedPaths = new Set<string>();
router.beforeEach(async (to) => {
router.beforeEach((to) => {
to.meta.loaded = loadedPaths.has(to.path);
// 页面加载进度条
@@ -56,7 +56,7 @@ function setupAccessGuard(router: Router) {
return decodeURIComponent(
(to.query?.redirect as string) ||
userStore.userInfo?.homePath ||
DEFAULT_HOME_PATH,
preferences.app.defaultHomePath,
);
}
return true;
@@ -75,7 +75,7 @@ function setupAccessGuard(router: Router) {
path: LOGIN_PATH,
// 如不需要,直接删除 query
query:
to.fullPath === DEFAULT_HOME_PATH
to.fullPath === preferences.app.defaultHomePath
? {}
: { redirect: encodeURIComponent(to.fullPath) },
// 携带当前跳转的页面,登录后重新跳转该页面
@@ -108,8 +108,8 @@ function setupAccessGuard(router: Router) {
accessStore.setAccessRoutes(accessibleRoutes);
accessStore.setIsAccessChecked(true);
const redirectPath = (from.query.redirect ??
(to.path === DEFAULT_HOME_PATH
? userInfo.homePath || DEFAULT_HOME_PATH
(to.path === preferences.app.defaultHomePath
? userInfo.homePath || preferences.app.defaultHomePath
: to.fullPath)) as string;
return {

View File

@@ -1,6 +1,7 @@
import type { RouteRecordRaw } from 'vue-router';
import { DEFAULT_HOME_PATH, LOGIN_PATH } from '@vben/constants';
import { LOGIN_PATH } from '@vben/constants';
import { preferences } from '@vben/preferences';
import { $t } from '#/locales';
@@ -34,7 +35,7 @@ const coreRoutes: RouteRecordRaw[] = [
},
name: 'Root',
path: '/',
redirect: DEFAULT_HOME_PATH,
redirect: preferences.app.defaultHomePath,
children: [],
},
{

View File

@@ -9,6 +9,7 @@ const {
/**
* 该文件放非后台返回的路由 比如个人中心 等需要跳转显示的页面
* 也可以直接在菜单管理配置
*/
const localRoutes: RouteRecordStringComponent[] = [
{
@@ -22,69 +23,6 @@ const localRoutes: RouteRecordStringComponent[] = [
name: 'Profile',
path: '/profile',
},
{
component: '/system/oss-config/index',
meta: {
activePath: '/system/oss',
icon: 'ant-design:setting-outlined',
title: 'oss配置',
hideInMenu: true,
requireHomeRedirect: true,
},
name: 'OssConfig',
path: '/system/oss-config',
},
{
component: '/tool/gen/edit-gen',
meta: {
activePath: '/tool/gen',
icon: 'tabler:code',
title: '生成配置',
hideInMenu: true,
requireHomeRedirect: true,
},
name: 'GenConfig',
path: '/code-gen/edit/:tableId',
},
{
component: '/system/role-assign/index',
meta: {
activePath: '/system/role',
icon: 'eos-icons:role-binding-outlined',
title: '分配角色',
hideInMenu: true,
requireHomeRedirect: true,
},
name: 'RoleAssign',
path: '/system/role-assign/:roleId',
},
{
component: '/workflow/components/flow-designer',
meta: {
activePath: '/workflow/processDefinition',
icon: 'fluent-mdl2:flow',
title: '流程设计',
hideInMenu: true,
requireHomeRedirect: true,
},
name: 'WorkflowDesigner',
path: '/workflow/designer',
},
/**
* 需要添加iframe路由 同目录的./workflow-iframe.ts
*/
{
component: 'workflow/leave/leave-form',
meta: {
icon: 'flat-color-icons:leave',
title: '请假申请',
activePath: '/demo/leave',
hideInMenu: true,
requireHomeRedirect: true,
},
name: 'WorkflowLeaveIndex',
path: '/workflow/leaveEdit/index',
},
];
/**

View File

@@ -4,7 +4,8 @@ import type { UserInfo } from '@vben/types';
import { ref } from 'vue';
import { useRouter } from 'vue-router';
import { DEFAULT_HOME_PATH, LOGIN_PATH } from '@vben/constants';
import { LOGIN_PATH } from '@vben/constants';
import { preferences } from '@vben/preferences';
import { resetAllStores, useAccessStore, useUserStore } from '@vben/stores';
import { notification } from 'ant-design-vue';
@@ -55,7 +56,9 @@ export const useAuthStore = defineStore('auth', () => {
if (accessStore.loginExpired) {
accessStore.setLoginExpired(false);
} else {
onSuccess ? await onSuccess?.() : await router.push(DEFAULT_HOME_PATH);
onSuccess
? await onSuccess?.()
: await router.push(preferences.app.defaultHomePath);
}
if (userInfo?.realName) {

View File

@@ -0,0 +1,36 @@
import { onMounted } from 'vue';
import { useLocalStorage } from '@vueuse/core';
import { Modal } from 'ant-design-vue';
export function useUploadTip() {
const readTip = useLocalStorage<boolean>('__upload_tip_read_5.4.0', false);
onMounted(() => {
if (readTip.value || !import.meta.env.DEV) {
return;
}
const modalInstance = Modal.info({
title: '提示',
centered: true,
content:
'如果你的版本是从低版本升级到后端>5.4.0, 记得执行升级sql, 否则跳转页面(如oss 代码生成配置)等会404',
okButtonProps: { disabled: true },
onOk() {
modalInstance.destroy();
readTip.value = true;
},
});
let time = 3;
const interval = setInterval(() => {
modalInstance.update({
okText: time === 0 ? '我知道了, 不再弹出' : `${time}秒后关闭`,
okButtonProps: { disabled: time > 0 },
});
if (time <= 0) {
clearInterval(interval);
}
time--;
}, 1000);
});
}

View File

@@ -1,3 +1,4 @@
import { UnauthorizedException } from '#/api/request';
import { dictDataInfo } from '#/api/system/dict/dict-data';
import { useDictStore } from '#/store/dict';
@@ -27,9 +28,16 @@ function fetchAndCacheDictData<T>(
// 内部处理了push的逻辑 这里不用push
setDictInfo(dictName, resp, formatNumber);
})
.catch(() => {
// 401时 移除字典缓存 下次登录重新获取
dictRequestCache.delete(dictName);
.catch((error) => {
/**
* 需要判断是否为401抛出的特定异常 401清除缓存
* 其他error清除缓存会导致无限循环调用字典接口 则不做处理
*/
if (error instanceof UnauthorizedException) {
// 401时 移除字典缓存 下次登录重新获取
dictRequestCache.delete(dictName);
}
// 其他不做处理
})
.finally(() => {
// 移除请求状态缓存

View File

@@ -18,7 +18,7 @@ import { useAuthStore } from '#/store';
defineOptions({ name: 'CodeLogin' });
const loading = ref(false);
const CODE_LENGTH = 6;
const CODE_LENGTH = 4;
const tenantInfo = ref<TenantResp>({
tenantEnabled: false,
@@ -85,8 +85,8 @@ const formSchema = computed((): VbenFormSchema[] => {
: $t('authentication.sendCode');
return text;
},
// 验证码长度 在这设置
codeLength: 4,
// 验证码长度
codeLength: CODE_LENGTH,
placeholder: $t('authentication.code'),
handleSendCode: async () => {
const { valid, value } = await form.validateField('phoneNumber');

View File

@@ -4,7 +4,8 @@ import type { AuthApi } from '#/api';
import { onMounted } from 'vue';
import { useRoute, useRouter } from 'vue-router';
import { DEFAULT_HOME_PATH, DEFAULT_TENANT_ID } from '@vben/constants';
import { DEFAULT_TENANT_ID } from '@vben/constants';
import { preferences } from '@vben/preferences';
import { useAccessStore } from '@vben/stores';
import { message } from 'ant-design-vue';
@@ -70,7 +71,7 @@ onMounted(async () => {
// 500 你还没有绑定第三方账号,绑定后才可以登录!
} finally {
setTimeout(() => {
router.push(DEFAULT_HOME_PATH);
router.push(preferences.app.defaultHomePath);
}, 1500);
}
});

View File

@@ -30,7 +30,6 @@ export const columns: VxeGridProps['columns'] = [
field: 'deptName',
title: '部门名称',
treeNode: true,
width: 200,
},
{
field: 'deptCategory',
@@ -39,13 +38,9 @@ export const columns: VxeGridProps['columns'] = [
{
field: 'orderNum',
title: '排序',
resizable: false,
width: 'auto',
},
{
field: 'status',
resizable: false,
width: 'auto',
title: '状态',
slots: {
default: ({ row }) => {
@@ -62,7 +57,8 @@ export const columns: VxeGridProps['columns'] = [
fixed: 'right',
slots: { default: 'action' },
title: '操作',
width: 200,
resizable: false,
width: 'auto',
},
];
@@ -99,6 +95,7 @@ export const drawerSchema: FormSchemaGetter = () => [
fieldName: 'orderNum',
label: '显示排序',
rules: 'required',
defaultValue: 0,
},
{
component: 'Input',

View File

@@ -0,0 +1,6 @@
<template>
<div>
ele版本会使用这个文件 只是为了不报错`未找到对应组件`才新建的这个文件
无实际意义
</div>
</template>

View File

@@ -99,6 +99,7 @@ export const drawerSchema: FormSchemaGetter = () => [
fieldName: 'dictSort',
label: '显示排序',
rules: 'required',
defaultValue: 0,
},
{
component: 'Textarea',

View File

@@ -0,0 +1,281 @@
<!-- 使用vxe实现成本最小 且自带虚拟滚动 -->
<script setup lang="ts">
import type { VxeGridProps } from '#/adapter/vxe-table';
import type { DictType } from '#/api/system/dict/dict-type-model';
import { h, ref, shallowRef, watch } from 'vue';
import { useVbenModal } from '@vben/common-ui';
import { cn } from '@vben/utils';
import {
DeleteOutlined,
EditOutlined,
ExportOutlined,
PlusOutlined,
SyncOutlined,
} from '@ant-design/icons-vue';
import {
Alert,
Input,
Modal,
Popconfirm,
Space,
Tooltip,
} from 'ant-design-vue';
import { useVbenVxeGrid } from '#/adapter/vxe-table';
import {
dictTypeExport,
dictTypeList,
dictTypeRemove,
refreshDictTypeCache,
} from '#/api/system/dict/dict-type';
import { commonDownloadExcel } from '#/utils/file/download';
import { emitter } from '../mitt';
import dictTypeModal from './dict-type-modal.vue';
const tableAllData = shallowRef<DictType[]>([]);
const gridOptions: VxeGridProps = {
columns: [
{
title: 'name',
field: 'render',
slots: { default: 'render' },
},
],
height: 'auto',
keepSource: true,
pagerConfig: {
enabled: false,
},
proxyConfig: {
ajax: {
query: async () => {
const resp = await dictTypeList();
total.value = resp.total;
tableAllData.value = resp.rows;
return resp;
},
},
},
rowConfig: {
keyField: 'dictId',
// 高亮当前行
isCurrent: true,
},
cellConfig: {
height: 60,
},
showHeader: false,
toolbarConfig: {
enabled: false,
},
// 开启虚拟滚动
scrollY: {
enabled: false,
gt: 0,
},
rowClassName: 'cursor-pointer',
id: 'system-dict-data-index',
};
const [BasicTable, tableApi] = useVbenVxeGrid({
gridOptions,
gridEvents: {
cellClick: ({ row }) => {
handleRowClick(row);
},
},
});
const [DictTypeModal, modalApi] = useVbenModal({
connectedComponent: dictTypeModal,
});
function handleAdd() {
modalApi.setData({});
modalApi.open();
}
async function handleEdit(record: DictType) {
modalApi.setData({ id: record.dictId });
modalApi.open();
}
async function handleDelete(row: DictType) {
await dictTypeRemove([row.dictId]);
await tableApi.query();
}
async function handleReset() {
currentRowId.value = '';
searchValue.value = '';
await tableApi.query();
}
function handleDownloadExcel() {
commonDownloadExcel(dictTypeExport, '字典类型数据');
}
function handleRefreshCache() {
Modal.confirm({
title: '提示',
content: '确认刷新字典类型缓存吗?',
okButtonProps: {
danger: true,
},
onOk: async () => {
await refreshDictTypeCache();
await tableApi.query();
},
});
}
const lastDictType = ref<string>('');
const currentRowId = ref<null | number | string>(null);
function handleRowClick(row: DictType) {
if (lastDictType.value === row.dictType) {
return;
}
currentRowId.value = row.dictId;
emitter.emit('rowClick', row.dictType);
}
const searchValue = ref('');
const total = ref(0);
watch(searchValue, (value) => {
if (!tableApi) {
return;
}
if (value) {
const names = tableAllData.value.filter((item) =>
item.dictName.includes(searchValue.value),
);
const types = tableAllData.value.filter((item) =>
item.dictType.includes(searchValue.value),
);
const filtered = [...new Set([...names, ...types])];
total.value = filtered.length;
tableApi.grid.loadData(filtered);
} else {
total.value = tableAllData.value.length;
tableApi.grid.loadData(tableAllData.value);
}
});
</script>
<template>
<div
:class="
cn(
'bg-background flex max-h-[100vh] w-[360px] flex-col overflow-y-hidden',
'rounded-lg',
'dict-type-card',
)
"
>
<div :class="cn('flex items-center justify-between', 'border-b px-4 py-2')">
<span class="font-semibold">字典项列表</span>
<Space>
<Tooltip title="刷新缓存">
<a-button
v-access:code="['system:dict:edit']"
:icon="h(SyncOutlined)"
@click="handleRefreshCache"
/>
</Tooltip>
<Tooltip :title="$t('pages.common.export')">
<a-button
v-access:code="['system:dict:export']"
:icon="h(ExportOutlined)"
@click="handleDownloadExcel"
/>
</Tooltip>
<Tooltip :title="$t('pages.common.add')">
<a-button
v-access:code="['system:dict:add']"
:icon="h(PlusOutlined)"
@click="handleAdd"
/>
</Tooltip>
</Space>
</div>
<div class="flex flex-1 flex-col overflow-y-hidden p-4">
<Alert
class="mb-4"
show-icon
message="如果你的数据量大 自行开启虚拟滚动"
/>
<Input
placeholder="搜索字典项名称/类型"
v-model:value="searchValue"
allow-clear
>
<template #addonAfter>
<Tooltip title="重置/刷新">
<SyncOutlined
v-access:code="['system:dict:edit']"
@click="handleReset"
/>
</Tooltip>
</template>
</Input>
<BasicTable class="flex-1 overflow-hidden">
<template #render="{ row: item }">
<div :class="cn('flex items-center justify-between px-2 py-2')">
<div class="flex flex-col items-baseline overflow-hidden">
<span class="font-medium">{{ item.dictName }}</span>
<div
class="max-w-full overflow-hidden text-ellipsis whitespace-nowrap"
>
{{ item.dictType }}
</div>
</div>
<div class="flex items-center gap-3 text-[17px]">
<EditOutlined
class="text-primary"
v-access:code="['system:dict:edit']"
@click.stop="handleEdit(item)"
/>
<Popconfirm
placement="left"
:title="`确认删除 [${item.dictName}]?`"
@confirm="handleDelete(item)"
>
<DeleteOutlined
v-access:code="['system:dict:remove']"
class="text-destructive"
@click.stop=""
/>
</Popconfirm>
</div>
</div>
</template>
</BasicTable>
</div>
<div class="border-t px-4 py-3"> {{ total }} 条数据</div>
<DictTypeModal @reload="tableApi.query()" />
</div>
</template>
<style lang="scss">
.dict-type-card {
.vxe-grid {
padding: 12px 0 0;
.vxe-body--row {
&.row--current {
// 选中行背景色
background-color: hsl(var(--accent-hover)) !important;
}
}
}
.ant-alert {
padding: 6px 12px;
}
}
</style>

View File

@@ -219,6 +219,7 @@ export const drawerSchema: FormSchemaGetter = () => [
fieldName: 'orderNum',
help: '排序, 数字越小越靠前',
label: '显示排序',
defaultValue: 0,
rules: 'required',
},
{
@@ -237,6 +238,7 @@ export const drawerSchema: FormSchemaGetter = () => [
if (model.isFrame !== '0') {
return z
.string({ message: '请输入路由地址' })
.min(1, '请输入路由地址')
.refine((val) => !val.startsWith('/'), {
message: '路由地址不需要带/',
});

View File

@@ -4,16 +4,17 @@ import type { VbenFormProps } from '@vben/common-ui';
import type { VxeGridProps } from '#/adapter/vxe-table';
import type { Menu } from '#/api/system/menu/model';
import { computed } from 'vue';
import { computed, ref } from 'vue';
import { useAccess } from '@vben/access';
import { Fallback, Page, useVbenDrawer } from '@vben/common-ui';
import { eachTree, getVxePopupContainer } from '@vben/utils';
import { $t } from '@vben/locales';
import { eachTree, getVxePopupContainer, treeToList } from '@vben/utils';
import { Popconfirm, Space } from 'ant-design-vue';
import { Popconfirm, Space, Switch, Tooltip } from 'ant-design-vue';
import { useVbenVxeGrid } from '#/adapter/vxe-table';
import { menuList, menuRemove } from '#/api/system/menu';
import { menuCascadeRemove, menuList, menuRemove } from '#/api/system/menu';
import { columns, querySchema } from './data';
import menuDrawer from './menu-drawer.vue';
@@ -67,6 +68,8 @@ const gridOptions: VxeGridProps = {
rowField: 'menuId',
// 自动转换为tree 由vxe处理 无需手动转换
transform: true,
// 刷新接口后 记录展开行的情况
reserve: true,
},
id: 'system-menu-index',
};
@@ -111,11 +114,41 @@ async function handleEdit(record: Menu) {
drawerApi.open();
}
/**
* 是否级联删除
*/
const cascadingDeletion = ref(false);
async function handleDelete(row: Menu) {
await menuRemove([row.menuId]);
if (cascadingDeletion.value) {
// 级联删除
const menuAndChildren: Menu[] = treeToList([row], { id: 'menuId' });
await menuCascadeRemove(menuAndChildren.map((item) => item.menuId));
} else {
// 单删除
await menuRemove([row.menuId]);
}
await tableApi.query();
}
function removeConfirmTitle(row: Menu) {
const menuName = $t(row.menuName);
if (!cascadingDeletion.value) {
return `是否确认删除 [${menuName}] ?`;
}
const menuAndChildren = treeToList([row], { id: 'menuId' });
if (menuAndChildren.length === 1) {
return `是否确认删除 [${menuName}] ?`;
}
return `是否确认删除 [${menuName}] 及 [${menuAndChildren.length - 1}]个子项目 ?`;
}
/**
* 编辑/添加成功后刷新表格
*/
async function afterEditOrAdd() {
tableApi.query();
}
/**
* 全部展开/折叠
* @param expand 是否展开
@@ -128,6 +161,9 @@ function setExpandOrCollapse(expand: boolean) {
/**
* 与后台逻辑相同
* 只有租户管理和超级管理能访问菜单管理
* 注意: 只有超管才能对菜单进行`增删改`操作
* 注意: 只有超管才能对菜单进行`增删改`操作
* 注意: 只有超管才能对菜单进行`增删改`操作
*/
const { hasAccessByRoles } = useAccess();
const isAdmin = computed(() => {
@@ -140,6 +176,16 @@ const isAdmin = computed(() => {
<BasicTable table-title="菜单列表" table-title-help="双击展开/收起子菜单">
<template #toolbar-tools>
<Space>
<Tooltip title="删除菜单以及子菜单">
<div
v-access:role="['superadmin']"
v-access:code="['system:menu:remove']"
class="flex items-center"
>
<span class="mr-2 text-sm text-[#666666]">级联删除</span>
<Switch v-model:checked="cascadingDeletion" />
</div>
</Tooltip>
<a-button @click="setExpandOrCollapse(false)">
{{ $t('pages.common.collapse') }}
</a-button>
@@ -149,6 +195,7 @@ const isAdmin = computed(() => {
<a-button
type="primary"
v-access:code="['system:menu:add']"
v-access:role="['superadmin']"
@click="handleAdd"
>
{{ $t('pages.common.add') }}
@@ -159,6 +206,7 @@ const isAdmin = computed(() => {
<Space>
<ghost-button
v-access:code="['system:menu:edit']"
v-access:role="['superadmin']"
@click="handleEdit(row)"
>
{{ $t('pages.common.edit') }}
@@ -168,6 +216,7 @@ const isAdmin = computed(() => {
v-if="row.menuType !== 'F'"
class="btn-success"
v-access:code="['system:menu:add']"
v-access:role="['superadmin']"
@click="handleSubAdd(row)"
>
{{ $t('pages.common.add') }}
@@ -175,12 +224,13 @@ const isAdmin = computed(() => {
<Popconfirm
:get-popup-container="getVxePopupContainer"
placement="left"
title="确认删除?"
:title="removeConfirmTitle(row)"
@confirm="handleDelete(row)"
>
<ghost-button
danger
v-access:code="['system:menu:remove']"
v-access:role="['superadmin']"
@click.stop=""
>
{{ $t('pages.common.delete') }}
@@ -189,7 +239,7 @@ const isAdmin = computed(() => {
</Space>
</template>
</BasicTable>
<MenuDrawer @reload="tableApi.query()" />
<MenuDrawer @reload="afterEditOrAdd" />
</Page>
<Fallback v-else description="您没有菜单管理的访问权限" status="403" />
</template>

View File

@@ -28,7 +28,10 @@ export const querySchema: FormSchemaGetter = () => [
{
component: 'Select',
componentProps: {
options: getDictOptions(DictEnum.SYS_YES_NO),
options: [
{ label: '是', value: '0' },
{ label: '否', value: '1' },
],
},
fieldName: 'status',
label: '是否默认',

View File

@@ -131,6 +131,8 @@ const { hasAccessByCodes } = useAccess();
v-model:value="row.status"
:api="() => ossConfigChangeStatus(row)"
:disabled="!hasAccessByCodes(['system:ossConfig:edit'])"
checked-text=""
un-checked-text=""
@reload="tableApi.query()"
/>
</template>

View File

@@ -0,0 +1,11 @@
<!--
后端版本>=5.4.0 这个从本地路由变为从后台返回
未修改文件名 而是新加了这个文件
-->
<script setup lang="ts">
import OssConfigPage from '#/views/system/oss-config/index.vue';
</script>
<template>
<OssConfigPage />
</template>

View File

@@ -0,0 +1,2 @@
/** 支持的图片列表 */
export const supportImageList = ['jpg', 'jpeg', 'png', 'gif', 'webp'];

View File

@@ -73,9 +73,3 @@ export const columns: VxeGridProps['columns'] = [
width: 'auto',
},
];
/**
* 图片加载失败的fallback
*/
export const fallbackImageBase64 =
'data:image/png;base64,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';

View File

@@ -0,0 +1 @@
data:image/png;base64,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

View File

@@ -31,7 +31,11 @@ const [BasicModal, modalApi] = useVbenModal({
title="文件上传"
>
<div class="flex flex-col gap-4">
<FileUpload v-model:value="fileList" :enable-drag-upload="true" />
<FileUpload
v-model:value="fileList"
:enable-drag-upload="true"
:max-count="3"
/>
</div>
</BasicModal>
</template>

View File

@@ -31,7 +31,7 @@ const [BasicModal, modalApi] = useVbenModal({
title="图片上传"
>
<div class="flex flex-col gap-4">
<ImageUpload v-model:value="fileList" />
<ImageUpload v-model:value="fileList" :max-count="3" />
</div>
</BasicModal>
</template>

View File

@@ -33,7 +33,9 @@ import { ossDownload, ossList, ossRemove } from '#/api/system/oss';
import { calculateFileSize } from '#/utils/file';
import { downloadByData } from '#/utils/file/download';
import { columns, fallbackImageBase64, querySchema } from './data';
import { supportImageList } from './constant';
import { columns, querySchema } from './data';
import fallbackImageBase64 from './fallback-image.txt?raw';
import fileUploadModal from './file-upload-modal.vue';
import imageUploadModal from './image-upload-modal.vue';
@@ -154,7 +156,7 @@ function handleMultiDelete() {
const router = useRouter();
function handleToSettings() {
router.push('/system/oss-config');
router.push('/system/oss-config/index');
}
const preview = ref(false);
@@ -163,10 +165,32 @@ onMounted(async () => {
preview.value = previewStr === 'true';
});
/**
* 根据拓展名判断是否是图片
* @param ext 拓展名
*/
function isImageFile(ext: string) {
const supportList = ['jpg', 'jpeg', 'png', 'gif', 'webp'];
return supportList.some((item) => ext.toLocaleLowerCase().includes(item));
return supportImageList.some((item) =>
ext.toLocaleLowerCase().includes(item),
);
}
/**
* 判断是否是pdf文件
* @param ext 扩展名
*/
function isPdfFile(ext: string) {
return ext.toLocaleLowerCase().includes('pdf');
}
/**
* pdf预览 使用浏览器接管
* @param url 文件地址
*/
function pdfPreview(url: string) {
window.open(url);
}
const [ImageUploadModal, imageUploadApi] = useVbenModal({
connectedComponent: imageUploadModal,
});
@@ -230,6 +254,12 @@ const [FileUploadModal, fileUploadApi] = useVbenModal({
</div>
</template>
</Image>
<!-- pdf预览 使用浏览器开新窗口 -->
<span
v-else-if="preview && isPdfFile(row.url)"
class="icon-[vscode-icons--file-type-pdf2] size-10 cursor-pointer"
@click.stop="pdfPreview(row.url)"
></span>
<span v-else>{{ row.url }}</span>
</template>
<template #action="{ row }">

View File

@@ -111,6 +111,7 @@ export const drawerSchema: FormSchemaGetter = () => [
fieldName: 'postSort',
label: '岗位排序',
rules: 'required',
defaultValue: 0,
},
{
component: 'RadioGroup',

View File

@@ -17,6 +17,7 @@ const emit = defineEmits<{ reload: [] }>();
const [BasicDrawer, drawerApi] = useVbenDrawer({
onConfirm: handleSubmit,
onCancel: handleReset,
destroyOnClose: true,
});
const route = useRoute();

View File

@@ -0,0 +1,11 @@
<!--
后端版本>=5.4.0 这个从本地路由变为从后台返回
未修改文件名 而是新加了这个文件
-->
<script setup lang="ts">
import RoleAssignPage from '#/views/system/role-assign/index.vue';
</script>
<template>
<RoleAssignPage />
</template>

View File

@@ -127,6 +127,7 @@ export const drawerSchema: FormSchemaGetter = () => [
fieldName: 'roleSort',
label: '角色排序',
rules: 'required',
defaultValue: 0,
},
{
component: 'Select',

View File

@@ -11,14 +11,7 @@ import { useAccess } from '@vben/access';
import { Page, useVbenDrawer, useVbenModal } from '@vben/common-ui';
import { getVxePopupContainer } from '@vben/utils';
import {
Dropdown,
Menu,
MenuItem,
Modal,
Popconfirm,
Space,
} from 'ant-design-vue';
import { Modal, Popconfirm, Space } from 'ant-design-vue';
import { useVbenVxeGrid, vxeCheckboxChecked } from '#/adapter/vxe-table';
import {
@@ -142,7 +135,7 @@ function handleAuthEdit(record: Role) {
const router = useRouter();
function handleAssignRole(record: Role) {
router.push(`/system/role-assign/${record.roleId}`);
router.push(`/system/role-auth/user/${record.roleId}`);
}
</script>
@@ -200,6 +193,18 @@ function handleAssignRole(record: Role) {
>
{{ $t('pages.common.edit') }}
</ghost-button>
<ghost-button
v-access:code="['system:role:edit']"
@click.stop="handleAuthEdit(row)"
>
权限
</ghost-button>
<ghost-button
v-access:code="['system:role:edit']"
@click.stop="handleAssignRole(row)"
>
分配
</ghost-button>
<Popconfirm
:get-popup-container="getVxePopupContainer"
placement="left"
@@ -215,25 +220,6 @@ function handleAssignRole(record: Role) {
</ghost-button>
</Popconfirm>
</Space>
<Dropdown placement="bottomRight">
<template #overlay>
<Menu>
<MenuItem key="1" @click="handleAuthEdit(row)">
数据权限
</MenuItem>
<MenuItem key="2" @click="handleAssignRole(row)">
分配用户
</MenuItem>
</Menu>
</template>
<a-button
size="small"
type="link"
v-access:code="'system:role:edit'"
>
{{ $t('pages.common.more') }}
</a-button>
</Dropdown>
</template>
</template>
</BasicTable>

View File

@@ -85,6 +85,7 @@ const [BasicDrawer, drawerApi] = useVbenDrawer({
onBeforeClose,
onClosed: handleClosed,
onConfirm: handleConfirm,
destroyOnClose: true,
async onOpenChange(isOpen) {
if (!isOpen) {
return null;

View File

@@ -85,6 +85,7 @@ const [BasicDrawer, drawerApi] = useVbenDrawer({
onBeforeClose,
onClosed: handleClosed,
onConfirm: handleConfirm,
destroyOnClose: true,
async onOpenChange(isOpen) {
if (!isOpen) {
return null;

View File

@@ -0,0 +1,6 @@
<template>
<div>
ele版本会使用这个文件 只是为了不报错`未找到对应组件`才新建的这个文件
无实际意义
</div>
</template>

View File

@@ -80,6 +80,7 @@ onMounted(loadTree);
v-model:value="searchValue"
:placeholder="$t('pages.common.search')"
size="small"
allow-clear
>
<template #enterButton>
<a-button @click="handleReload">
@@ -102,9 +103,9 @@ onMounted(loadTree);
@select="$emit('select')"
>
<template #title="{ label }">
<span v-if="label.indexOf(searchValue) > -1">
<span v-if="label.includes(searchValue)">
{{ label.substring(0, label.indexOf(searchValue)) }}
<span style="color: #f50">{{ searchValue }}</span>
<span class="text-primary">{{ searchValue }}</span>
{{
label.substring(
label.indexOf(searchValue) + searchValue.length,

View File

@@ -0,0 +1,10 @@
<!--
后端版本>=5.4.0 这个从本地路由变为从后台返回
-->
<script setup lang="ts">
import EditGenPage from './edit-gen.vue';
</script>
<template>
<EditGenPage />
</template>

View File

@@ -110,7 +110,7 @@ function handlePreview(record: Recordable<any>) {
const router = useRouter();
function handleEdit(record: Recordable<any>) {
router.push(`/code-gen/edit/${record.tableId}`);
router.push(`/tool/gen-edit/index/${record.tableId}`);
}
async function handleSync(record: Recordable<any>) {

View File

@@ -42,6 +42,7 @@ import { renderDict } from '#/utils/render';
import { approvalModal, approvalRejectionModal, flowInterfereModal } from '.';
import ApprovalDetails from './approval-details.vue';
import FlowPreview from './flow-preview.vue';
import { approveWithReasonModal } from './helper';
import userSelectModal from './user-select-modal.vue';
@@ -442,10 +443,7 @@ async function handleCopy(text: string) {
/>
</TabPane>
<TabPane key="2" tab="审批流程图">
<img
:src="`data:image/png;base64,${currentFlowInfo.image}`"
class="rounded-lg border"
/>
<FlowPreview :instance-id="currentFlowInfo.instanceId" />
</TabPane>
</Tabs>
</div>

View File

@@ -0,0 +1,28 @@
<script setup lang="ts">
import { useAppConfig } from '@vben/hooks';
import { stringify } from '@vben/request';
import { useAccessStore } from '@vben/stores';
defineOptions({ name: 'FlowPreview' });
const props = defineProps<{ instanceId: string }>();
const { clientId } = useAppConfig(import.meta.env, import.meta.env.PROD);
const accessStore = useAccessStore();
const params = {
Authorization: `Bearer ${accessStore.accessToken}`,
id: props.instanceId,
clientid: clientId,
type: 'FlowChart',
};
/**
* iframe地址
*/
const url = `${import.meta.env.VITE_GLOB_API_URL}/warm-flow-ui/index.html?${stringify(params)}`;
</script>
<template>
<iframe :src="url" class="h-[500px] w-full border"></iframe>
</template>

View File

@@ -0,0 +1,11 @@
<!--
后端版本>=5.4.0 这个从本地路由变为从后台返回
未修改文件名 而是新加了这个文件
-->
<script setup lang="ts">
import LeaveFormPage from './leave-form.vue';
</script>
<template>
<LeaveFormPage />
</template>

View File

@@ -73,6 +73,7 @@ onMounted(loadTree);
v-model:value="searchValue"
:placeholder="$t('pages.common.search')"
size="small"
allow-clear
>
<template #enterButton>
<a-button @click="handleReload">
@@ -95,9 +96,9 @@ onMounted(loadTree);
@select="$emit('select')"
>
<template #title="{ label }">
<span v-if="label.indexOf(searchValue) > -1">
<span v-if="label.includes(searchValue)">
{{ label.substring(0, label.indexOf(searchValue)) }}
<span style="color: #f50">{{ searchValue }}</span>
<span class="text-primary">{{ searchValue }}</span>
{{
label.substring(
label.indexOf(searchValue) + searchValue.length,

View File

@@ -0,0 +1,11 @@
<!--
后端版本>=5.4.0 这个从本地路由变为从后台返回
未修改文件名 而是新加了这个文件
-->
<script setup lang="ts">
import FlowDesignerPage from '../components/flow-designer.vue';
</script>
<template>
<FlowDesignerPage />
</template>

View File

@@ -154,7 +154,7 @@ const router = useRouter();
*/
function handleDesign(row: any, disabled: boolean) {
router.push({
path: '/workflow/designer',
path: '/workflow/design/index',
query: { definitionId: row.id, disabled: String(disabled) },
});
}

View File

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

View File

@@ -78,7 +78,7 @@ const [Drawer, drawerApi] = useVbenDrawer({
| --- | --- | --- | --- |
| appendToMain | 是否挂载到内容区域默认挂载到body | `boolean` | `false` |
| connectedComponent | 连接另一个Modal组件 | `Component` | - |
| destroyOnClose | 关闭时销毁`connectedComponent` | `boolean` | `false` |
| destroyOnClose | 关闭时销毁 | `boolean` | `false` |
| title | 标题 | `string\|slot` | - |
| titleTooltip | 标题提示信息 | `string\|slot` | - |
| description | 描述信息 | `string\|slot` | - |

View File

@@ -26,6 +26,12 @@ outline: deep
<DemoPreview dir="demos/vben-ellipsis-text/tooltip" />
## 自动显示 tooltip
通过`tooltip-when-ellipsis`设置,仅在文本长度超出导致省略号出现时才触发 tooltip。
<DemoPreview dir="demos/vben-ellipsis-text/auto-display" />
## API
### Props
@@ -37,6 +43,8 @@ outline: deep
| maxWidth | 文本区域最大宽度 | `number \| string` | `'100%'` |
| placement | 提示浮层的位置 | `'bottom'\|'left'\|'right'\|'top'` | `'top'` |
| tooltip | 启用文本提示 | `boolean` | `true` |
| tooltipWhenEllipsis | 内容超出,自动启用文本提示 | `boolean` | `false` |
| ellipsisThreshold | 设置 tooltipWhenEllipsis 后才生效,文本截断检测的像素差异阈值,越大则判断越严格,如果碰见异常情况可以自己设置阈值 | `number` | `3` |
| tooltipBackgroundColor | 提示文本的背景颜色 | `string` | - |
| tooltipColor | 提示文本的颜色 | `string` | - |
| tooltipFontSize | 提示文本的大小 | `string` | - |

View File

@@ -0,0 +1,16 @@
<script lang="ts" setup>
import { EllipsisText } from '@vben/common-ui';
const text = `
Vben Admin 是一个基于 Vue3.0、Vite、 TypeScript 的后台解决方案,目标是为开发中大型项目提供开箱即用的解决方案。
`;
</script>
<template>
<EllipsisText :line="2" :tooltip-when-ellipsis="true">
{{ text }}
</EllipsisText>
<EllipsisText :line="3" :tooltip-when-ellipsis="true">
{{ text }}
</EllipsisText>
</template>

View File

@@ -98,8 +98,8 @@ The execution command is: `pnpm run [script]` or `npm run [script]`.
"postinstall": "pnpm -r run stub --if-present",
// Only allow using pnpm
"preinstall": "npx only-allow pnpm",
// Install husky
"prepare": "is-ci || husky",
// Install lefthook
"prepare": "is-ci || lefthook install",
// Preview the application
"preview": "turbo-run preview",
// Package specification check

View File

@@ -21,7 +21,7 @@ The rules are consistent with [Vite Env Variables and Modes](https://vitejs.dev/
console.log(import.meta.env.VITE_PROT);
```
- Variables starting with `VITE_GLOB_*` will be added to the `_app.config.js` configuration file during packaging. :::
- Variables starting with `VITE_GLOB_*` will be added to the `_app.config.js` configuration file during packaging.
:::
@@ -60,6 +60,29 @@ VITE_INJECT_APP_LOADING=true
VITE_ARCHIVER=true
```
```bash [.env.production]
# Public Path for Resources, must start and end with /
VITE_BASE=/
# API URL
VITE_GLOB_API_URL=https://mock-napi.vben.pro/api
# Whether to enable compression, can be set to none, brotli, gzip
VITE_COMPRESS=gzip
# Whether to enable PWA
VITE_PWA=false
# vue-router mode
VITE_ROUTER_HISTORY=hash
# Whether to inject global loading
VITE_INJECT_APP_LOADING=true
# Whether to generate dist.zip after packaging
VITE_ARCHIVER=true
```
:::
## Dynamic Configuration in Production Environment
@@ -115,6 +138,27 @@ To add a new dynamically modifiable configuration item, simply follow the steps
}
```
- In `packages/effects/hooks/src/use-app-config.ts`, add the corresponding configuration item, such as:
```ts
export function useAppConfig(
env: Record<string, any>,
isProduction: boolean,
): ApplicationConfig {
// In production environment, directly use the window._VBEN_ADMIN_PRO_APP_CONF_ global variable
const config = isProduction
? window._VBEN_ADMIN_PRO_APP_CONF_
: (env as VbenAdminProAppConfigRaw);
const { VITE_GLOB_API_URL, VITE_GLOB_OTHER_API_URL } = config; // [!code ++]
return {
apiURL: VITE_GLOB_API_URL,
otherApiURL: VITE_GLOB_OTHER_API_URL, // [!code ++]
};
}
```
At this point, you can use the `useAppConfig` method within the project to access the newly added configuration item.
```ts
@@ -142,6 +186,7 @@ import { defineOverridesPreferences } from '@vben/preferences';
/**
* @description Project configuration file
* Only a part of the configuration in the project needs to be covered, and unnecessary configurations do not need to be covered. The default configuration will be automatically used
* !!! Please clear the cache after changing the configuration, otherwise it may not take effect
*/
export const overridesPreferences = defineOverridesPreferences({
// overrides
@@ -162,8 +207,15 @@ const defaultPreferences: Preferences = {
colorWeakMode: false,
compact: false,
contentCompact: 'wide',
contentCompactWidth: 1200,
contentPadding: 0,
contentPaddingBottom: 0,
contentPaddingLeft: 0,
contentPaddingRight: 0,
contentPaddingTop: 0,
defaultAvatar:
'https://unpkg.com/@vbenjs/static-source@0.1.7/source/avatar-v1.webp',
defaultHomePath: '/analytics',
dynamicTitle: true,
enableCheckUpdates: true,
enablePreferences: true,
@@ -171,10 +223,11 @@ const defaultPreferences: Preferences = {
isMobile: false,
layout: 'sidebar-nav',
locale: 'zh-CN',
loginExpiredMode: 'modal',
loginExpiredMode: 'page',
name: 'Vben Admin',
preferencesButtonPosition: 'auto',
watermark: false,
zIndex: 200,
},
breadcrumb: {
enable: true,
@@ -190,18 +243,23 @@ const defaultPreferences: Preferences = {
enable: true,
icp: '',
icpLink: '',
settingShow: true,
},
footer: {
enable: true,
enable: false,
fixed: false,
height: 32,
},
header: {
enable: true,
height: 50,
hidden: false,
menuAlign: 'start',
mode: 'fixed',
},
logo: {
enable: true,
fit: 'contain',
source: 'https://unpkg.com/@vbenjs/static-source@0.1.7/source/logo-v1.webp',
},
navigation: {
@@ -219,23 +277,31 @@ const defaultPreferences: Preferences = {
sidebar: {
autoActivateChild: false,
collapsed: false,
collapsedButton: true,
collapsedShowTitle: false,
collapseWidth: 60,
enable: true,
expandOnHover: true,
extraCollapse: true,
extraCollapse: false,
extraCollapsedWidth: 60,
fixedButton: true,
hidden: false,
width: 230,
mixedWidth: 80,
width: 224,
},
tabbar: {
draggable: true,
enable: true,
height: 36,
height: 38,
keepAlive: true,
maxCount: 0,
middleClickToClose: false,
persist: true,
showIcon: true,
showMaximize: true,
showMore: true,
styleType: 'chrome',
wheelable: true,
},
theme: {
builtinType: 'default',
@@ -246,7 +312,7 @@ const defaultPreferences: Preferences = {
mode: 'dark',
radius: '0.5',
semiDarkHeader: false,
semiDarkSidebar: true,
semiDarkSidebar: false,
},
transition: {
enable: true,
@@ -260,9 +326,9 @@ const defaultPreferences: Preferences = {
languageToggle: true,
lockScreen: true,
notification: true,
refresh: true,
sidebarToggle: true,
themeToggle: true,
refresh: true,
},
};
```
@@ -287,8 +353,22 @@ interface AppPreferences {
compact: boolean;
/** Whether to enable content compact mode */
contentCompact: ContentCompactType;
/** Content compact width */
contentCompactWidth: number;
/** Content padding */
contentPadding: number;
/** Content bottom padding */
contentPaddingBottom: number;
/** Content left padding */
contentPaddingLeft: number;
/** Content right padding */
contentPaddingRight: number;
/** Content top padding */
contentPaddingTop: number;
// /** Default application avatar */
defaultAvatar: string;
/** Default homepage path */
defaultHomePath: string;
// /** Enable dynamic title */
dynamicTitle: boolean;
/** Whether to enable update checks */
@@ -315,6 +395,8 @@ interface AppPreferences {
* @zh_CN Whether to enable watermark
*/
watermark: boolean;
/** z-index */
zIndex: number;
}
interface BreadcrumbPreferences {
/** Whether breadcrumbs are enabled */
@@ -342,6 +424,8 @@ interface CopyrightPreferences {
icp: string;
/** Link to the ICP */
icpLink: string;
/** Whether to show in settings panel */
settingShow?: boolean;
}
interface FooterPreferences {
@@ -349,13 +433,19 @@ interface FooterPreferences {
enable: boolean;
/** Whether the footer is fixed */
fixed: boolean;
/** Footer height */
height: number;
}
interface HeaderPreferences {
/** Whether the header is enabled */
enable: boolean;
/** Header height */
height: number;
/** Whether the header is hidden, css-hidden */
hidden: boolean;
/** Header menu alignment */
menuAlign: LayoutHeaderMenuAlignType;
/** Header display mode */
mode: LayoutHeaderModeType;
}
@@ -363,6 +453,8 @@ interface HeaderPreferences {
interface LogoPreferences {
/** Whether the logo is visible */
enable: boolean;
/** Logo image fitting method */
fit: 'contain' | 'cover' | 'fill' | 'none' | 'scale-down';
/** Logo URL */
source: string;
}
@@ -376,18 +468,30 @@ interface NavigationPreferences {
styleType: NavigationStyleType;
}
interface SidebarPreferences {
/** Automatically activate child menu when clicking on directory */
autoActivateChild: boolean;
/** Whether the sidebar is collapsed */
collapsed: boolean;
/** Whether the sidebar collapse button is visible */
collapsedButton: boolean;
/** Whether to show title when sidebar is collapsed */
collapsedShowTitle: boolean;
/** Sidebar collapse width */
collapseWidth: number;
/** Whether the sidebar is visible */
enable: boolean;
/** Menu auto-expand state */
expandOnHover: boolean;
/** Whether the sidebar extension area is collapsed */
extraCollapse: boolean;
/** Sidebar extension area collapse width */
extraCollapsedWidth: number;
/** Whether the sidebar fixed button is visible */
fixedButton: boolean;
/** Whether the sidebar is hidden - css */
hidden: boolean;
/** Mixed sidebar width */
mixedWidth: number;
/** Sidebar width */
width: number;
}
@@ -414,6 +518,10 @@ interface TabbarPreferences {
height: number;
/** Whether tab caching is enabled */
keepAlive: boolean;
/** Maximum number of tabs */
maxCount: number;
/** Whether to close tab when middle-clicked */
middleClickToClose: boolean;
/** Whether tabs are persistent */
persist: boolean;
/** Whether icons in multiple tabs are enabled */
@@ -424,6 +532,8 @@ interface TabbarPreferences {
showMore: boolean;
/** Tab style */
styleType: TabsStyleType;
/** Whether mouse wheel response is enabled */
wheelable: boolean;
}
interface ThemePreferences {
/** Built-in theme name */
@@ -511,5 +621,6 @@ interface Preferences {
- The `overridesPreferences` method only needs to override a part of the configurations in the project. There's no need to override configurations that are not needed; they will automatically use the default settings.
- Any configuration item can be overridden. You just need to override it within the `overridesPreferences` method. Do not modify the default configuration file.
- Please clear the cache after changing the configuration, otherwise it may not take effect.
:::

View File

@@ -4,10 +4,11 @@ outline: deep
# Access Control
The framework has built-in two types of access control methods:
The framework has built-in three types of access control methods:
- Determining whether a menu or button can be accessed based on user roles
- Determining whether a menu or button can be accessed through an API
- Mixed mode: Using both frontend and backend access control simultaneously
## Frontend Access Control
@@ -151,6 +152,43 @@ const dashboardMenus = [
At this point, the configuration is complete. You need to ensure that after logging in, the format of the menu returned by the interface is correct; otherwise, access will not be possible.
## Mixed Access Control
**Implementation Principle**: Mixed mode combines both frontend access control and backend access control methods. The system processes frontend fixed route permissions and backend dynamic menu data in parallel, ultimately merging both parts of routes to provide a more flexible access control solution.
**Advantages**: Combines the performance advantages of frontend control with the flexibility of backend control, suitable for complex business scenarios requiring permission management.
### Steps
- Ensure the current mode is set to mixed access control
Adjust `preferences.ts` in the corresponding application directory to ensure `accessMode='mixed'`.
```ts
import { defineOverridesPreferences } from '@vben/preferences';
export const overridesPreferences = defineOverridesPreferences({
// overrides
app: {
accessMode: 'mixed',
},
});
```
- Configure frontend route permissions
Same as the route permission configuration method in [Frontend Access Control](#frontend-access-control) mode.
- Configure backend menu interface
Same as the interface configuration method in [Backend Access Control](#backend-access-control) mode.
- Ensure roles and permissions match
Must satisfy both frontend route permission configuration and backend menu data return requirements, ensuring user roles match the permission configurations of both modes.
At this point, the configuration is complete. Mixed mode will automatically merge frontend and backend routes, providing complete access control functionality.
## Fine-grained Control of Buttons
In some cases, we need to control the display of buttons with fine granularity. We can control the display of buttons through interfaces or roles.

View File

@@ -18,7 +18,7 @@ If you encounter a problem, you can start looking from the following aspects:
## Dependency Issues
In a `Monorepo` project, it is necessary to develop the habit of executing `pnpm install` every time you `git pull` the code, as new dependency packages are often added. The project has already configured automatic execution of `pnpm install` in `.husky/git-merge`, but sometimes there might be issues. If it does not execute automatically, it is recommended to execute it manually once.
In a `Monorepo` project, it's important to get into the habit of running `pnpm install` after every `git pull` because new dependencies are often added. The project has configured automatic execution of `pnpm install` in `lefthook.yml`, but sometimes there might be issues. If it does not execute automatically, it is recommended to execute it manually once.
## About Cache Update Issues

View File

@@ -33,8 +33,8 @@ The project integrates the following code verification tools:
- [Prettier](https://prettier.io/) for code formatting
- [Commitlint](https://commitlint.js.org/) for checking the standard of git commit messages
- [Publint](https://publint.dev/) for checking the standard of npm packages
- [Lint Staged](https://github.com/lint-staged/lint-staged) for running code verification before git commits
- [Cspell](https://cspell.org/) for checking spelling errors
- [lefthook](https://github.com/evilmartians/lefthook) for managing Git hooks, automatically running code checks and formatting before commits
## ESLint
@@ -148,18 +148,66 @@ The cspell configuration file is `cspell.json`, which can be modified according
Git hooks are generally combined with various lints to check code style during git commits. If the check fails, the commit will not proceed. Developers need to modify and resubmit.
### husky
### lefthook
One issue is that the check will verify all code, but we only want to check the code we are committing. This is where husky comes in.
One issue is that the check will verify all code, but we only want to check the code we are committing. This is where lefthook comes in.
The most effective solution is to perform Lint checks locally before committing. A common practice is to use husky or pre-commit to perform a Lint check before local submission.
The most effective solution is to perform Lint checks locally before committing. A common practice is to use lefthook to perform a Lint check before local submission.
The project defines corresponding hooks inside `.husky`.
The project defines corresponding hooks inside `lefthook.yml`:
#### How to Disable Husky
- `pre-commit`: Runs before commit, used for code formatting and checking
If you want to disable Husky, simply delete the .husky directory.
- `code-workspace`: Updates VSCode workspace configuration
- `lint-md`: Formats Markdown files
- `lint-vue`: Formats and checks Vue files
- `lint-js`: Formats and checks JavaScript/TypeScript files
- `lint-style`: Formats and checks style files
- `lint-package`: Formats package.json
- `lint-json`: Formats other JSON files
### lint-staged
- `post-merge`: Runs after merge, used for automatic dependency installation
Used for automatically fixing style issues of committed files. Its configuration file is `.lintstagedrc.mjs`, which can be modified according to project needs.
- `install`: Runs `pnpm install` to install new dependencies
- `commit-msg`: Runs during commit, used for checking commit message format
- `commitlint`: Uses commitlint to check commit messages
#### How to Disable lefthook
If you want to disable lefthook, there are two ways:
::: code-group
```bash [Temporary disable]
git commit -m 'feat: add home page' --no-verify
```
```bash [Permanent disable]
# Simply delete the lefthook.yml file
rm lefthook.yml
```
:::
#### How to Modify lefthook Configuration
If you want to modify lefthook's configuration, you can edit the `lefthook.yml` file. For example:
```yaml
pre-commit:
parallel: true # Execute tasks in parallel
jobs:
- name: lint-js
run: pnpm prettier --cache --ignore-unknown --write {staged_files}
glob: '*.{js,jsx,ts,tsx}'
```
Where:
- `parallel`: Whether to execute tasks in parallel
- `jobs`: Defines the list of tasks to execute
- `name`: Task name
- `run`: Command to execute
- `glob`: File pattern to match
- `{staged_files}`: Represents the list of staged files

View File

@@ -214,7 +214,7 @@ server {
使用 nginx 处理项目部署后的跨域问题
1. 配置前端项目接口地址,在项目目录下的``.env.production`文件中配置:
1. 配置前端项目接口地址,在项目目录下的`.env.production`文件中配置:
```bash
VITE_GLOB_API_URL=/api

View File

@@ -98,8 +98,8 @@ npm 脚本是项目常见的配置,用于执行一些常见的任务,比如
"postinstall": "pnpm -r run stub --if-present",
// 只允许使用pnpm
"preinstall": "npx only-allow pnpm",
// husky的安装
"prepare": "is-ci || husky",
// lefthook的安装
"prepare": "is-ci || lefthook install",
// 预览应用
"preview": "turbo-run preview",
// 包规范检查

View File

@@ -339,6 +339,10 @@ interface RouteMeta {
| 'success'
| 'warning'
| string;
/**
* 路由的完整路径作为key默认true
*/
fullPathKey?: boolean;
/**
* 当前路由的子级在菜单中不展现
* @default false
@@ -502,6 +506,13 @@ interface RouteMeta {
用于配置页面的徽标颜色。
### fullPathKey
- 类型:`boolean`
- 默认值:`true`
是否将路由的完整路径作为tab key默认true
### activePath
- 类型:`string`
@@ -602,3 +613,32 @@ const { refresh } = useRefresh();
refresh();
</script>
```
## 标签页与路由控制
在某些场景下需要单个路由打开多个标签页或者修改路由的query不打开新的标签页
每个标签页Tab使用唯一的key标识设置Tab key有三种方式优先级由高到低
- 使用路由query参数pageKey
```vue
<script setup lang="ts">
import { useRouter } from 'vue-router';
// 跳转路由
const router = useRouter();
router.push({
path: 'path',
query: {
pageKey: 'key',
},
});
```
- 路由的完整路径作为key
`meta` 属性中的 `fullPathKey`不为false则使用路由`fullPath`作为key
- 路由的path作为key
`meta` 属性中的 `fullPathKey`为false则使用路由`path`作为key

View File

@@ -21,7 +21,7 @@
console.log(import.meta.env.VITE_PROT);
```
- 以 `VITE_GLOB_*` 开头的的变量,在打包的时候,会被加入 `_app.config.js`配置文件当中. :::
- 以 `VITE_GLOB_*` 开头的的变量,在打包的时候,会被加入 `_app.config.js`配置文件当中.
:::
@@ -137,6 +137,27 @@ const { apiURL } = useAppConfig(import.meta.env, import.meta.env.PROD);
}
```
- 在 `packages/effects/hooks/src/use-app-config.ts` 中,新增对应的配置项,如:
```ts
export function useAppConfig(
env: Record<string, any>,
isProduction: boolean,
): ApplicationConfig {
// 生产环境下,直接使用 window._VBEN_ADMIN_PRO_APP_CONF_ 全局变量
const config = isProduction
? window._VBEN_ADMIN_PRO_APP_CONF_
: (env as VbenAdminProAppConfigRaw);
const { VITE_GLOB_API_URL, VITE_GLOB_OTHER_API_URL } = config; // [!code ++]
return {
apiURL: VITE_GLOB_API_URL,
otherApiURL: VITE_GLOB_OTHER_API_URL, // [!code ++]
};
}
```
到这里,就可以在项目内使用 `useAppConfig`方法获取到新增的配置项了。
```ts
@@ -185,8 +206,15 @@ const defaultPreferences: Preferences = {
colorWeakMode: false,
compact: false,
contentCompact: 'wide',
contentCompactWidth: 1200,
contentPadding: 0,
contentPaddingBottom: 0,
contentPaddingLeft: 0,
contentPaddingRight: 0,
contentPaddingTop: 0,
defaultAvatar:
'https://unpkg.com/@vbenjs/static-source@0.1.7/source/avatar-v1.webp',
defaultHomePath: '/analytics',
dynamicTitle: true,
enableCheckUpdates: true,
enablePreferences: true,
@@ -194,10 +222,11 @@ const defaultPreferences: Preferences = {
isMobile: false,
layout: 'sidebar-nav',
locale: 'zh-CN',
loginExpiredMode: 'modal',
loginExpiredMode: 'page',
name: 'Vben Admin',
preferencesButtonPosition: 'auto',
watermark: false,
zIndex: 200,
},
breadcrumb: {
enable: true,
@@ -213,18 +242,23 @@ const defaultPreferences: Preferences = {
enable: true,
icp: '',
icpLink: '',
settingShow: true,
},
footer: {
enable: true,
enable: false,
fixed: false,
height: 32,
},
header: {
enable: true,
height: 50,
hidden: false,
menuAlign: 'start',
mode: 'fixed',
},
logo: {
enable: true,
fit: 'contain',
source: 'https://unpkg.com/@vbenjs/static-source@0.1.7/source/logo-v1.webp',
},
navigation: {
@@ -242,23 +276,31 @@ const defaultPreferences: Preferences = {
sidebar: {
autoActivateChild: false,
collapsed: false,
collapsedButton: true,
collapsedShowTitle: false,
collapseWidth: 60,
enable: true,
expandOnHover: true,
extraCollapse: true,
extraCollapse: false,
extraCollapsedWidth: 60,
fixedButton: true,
hidden: false,
width: 230,
mixedWidth: 80,
width: 224,
},
tabbar: {
draggable: true,
enable: true,
height: 36,
height: 38,
keepAlive: true,
maxCount: 0,
middleClickToClose: false,
persist: true,
showIcon: true,
showMaximize: true,
showMore: true,
styleType: 'chrome',
wheelable: true,
},
theme: {
builtinType: 'default',
@@ -269,7 +311,7 @@ const defaultPreferences: Preferences = {
mode: 'dark',
radius: '0.5',
semiDarkHeader: false,
semiDarkSidebar: true,
semiDarkSidebar: false,
},
transition: {
enable: true,
@@ -310,8 +352,22 @@ interface AppPreferences {
compact: boolean;
/** 是否开启内容紧凑模式 */
contentCompact: ContentCompactType;
/** 内容紧凑宽度 */
contentCompactWidth: number;
/** 内容内边距 */
contentPadding: number;
/** 内容底部内边距 */
contentPaddingBottom: number;
/** 内容左侧内边距 */
contentPaddingLeft: number;
/** 内容右侧内边距 */
contentPaddingRight: number;
/** 内容顶部内边距 */
contentPaddingTop: number;
// /** 应用默认头像 */
defaultAvatar: string;
/** 默认首页地址 */
defaultHomePath: string;
// /** 开启动态标题 */
dynamicTitle: boolean;
/** 是否开启检查更新 */
@@ -338,6 +394,8 @@ interface AppPreferences {
* @zh_CN 是否开启水印
*/
watermark: boolean;
/** z-index */
zIndex: number;
}
interface BreadcrumbPreferences {
@@ -366,6 +424,8 @@ interface CopyrightPreferences {
icp: string;
/** 备案号链接 */
icpLink: string;
/** 设置面板是否显示*/
settingShow?: boolean;
}
interface FooterPreferences {
@@ -373,13 +433,19 @@ interface FooterPreferences {
enable: boolean;
/** 底栏是否固定 */
fixed: boolean;
/** 底栏高度 */
height: number;
}
interface HeaderPreferences {
/** 顶栏是否启用 */
enable: boolean;
/** 顶栏高度 */
height: number;
/** 顶栏是否隐藏,css-隐藏 */
hidden: boolean;
/** 顶栏菜单位置 */
menuAlign: LayoutHeaderMenuAlignType;
/** header显示模式 */
mode: LayoutHeaderModeType;
}
@@ -387,6 +453,8 @@ interface HeaderPreferences {
interface LogoPreferences {
/** logo是否可见 */
enable: boolean;
/** logo图片适应方式 */
fit: 'contain' | 'cover' | 'fill' | 'none' | 'scale-down';
/** logo地址 */
source: string;
}
@@ -401,18 +469,30 @@ interface NavigationPreferences {
}
interface SidebarPreferences {
/** 点击目录时自动激活子菜单 */
autoActivateChild: boolean;
/** 侧边栏是否折叠 */
collapsed: boolean;
/** 侧边栏折叠按钮是否可见 */
collapsedButton: boolean;
/** 侧边栏折叠时是否显示title */
collapsedShowTitle: boolean;
/** 侧边栏折叠宽度 */
collapseWidth: number;
/** 侧边栏是否可见 */
enable: boolean;
/** 菜单自动展开状态 */
expandOnHover: boolean;
/** 侧边栏扩展区域是否折叠 */
extraCollapse: boolean;
/** 侧边栏扩展区域折叠宽度 */
extraCollapsedWidth: number;
/** 侧边栏固定按钮是否可见 */
fixedButton: boolean;
/** 侧边栏是否隐藏 - css */
hidden: boolean;
/** 混合侧边栏宽度 */
mixedWidth: number;
/** 侧边栏宽度 */
width: number;
}
@@ -439,6 +519,10 @@ interface TabbarPreferences {
height: number;
/** 开启标签页缓存功能 */
keepAlive: boolean;
/** 限制最大数量 */
maxCount: number;
/** 是否点击中键时关闭标签 */
middleClickToClose: boolean;
/** 是否持久化标签 */
persist: boolean;
/** 是否开启多标签页图标 */
@@ -449,6 +533,8 @@ interface TabbarPreferences {
showMore: boolean;
/** 标签页风格 */
styleType: TabsStyleType;
/** 是否开启鼠标滚轮响应 */
wheelable: boolean;
}
interface ThemePreferences {

View File

@@ -4,10 +4,11 @@ outline: deep
# 权限
框架内置了种权限控制方式:
框架内置了种权限控制方式:
- 通过用户角色来判断菜单或者按钮是否可以访问
- 通过接口来判断菜单或者按钮是否可以访问
- 混合模式:同时使用前端和后端权限控制
## 前端访问控制
@@ -159,6 +160,43 @@ const dashboardMenus = [
到这里,就已经配置完成,你需要确保登录后,接口返回的菜单格式正确,否则无法访问。
## 混合访问控制
**实现原理**: 混合模式同时结合了前端访问控制和后端访问控制两种方式。系统会并行处理前端固定路由权限和后端动态菜单数据,最终将两部分路由合并,提供更灵活的权限控制方案。
**优点**: 兼具前端控制的性能优势和后端控制的灵活性,适合复杂业务场景下的权限管理。
### 步骤
- 确保当前模式为混合访问控制模式
调整对应应用目录下的`preferences.ts`,确保`accessMode='mixed'`。
```ts
import { defineOverridesPreferences } from '@vben/preferences';
export const overridesPreferences = defineOverridesPreferences({
// overrides
app: {
accessMode: 'mixed',
},
});
```
- 配置前端路由权限
同[前端访问控制](#前端访问控制)模式的路由权限配置方式。
- 配置后端菜单接口
同[后端访问控制](#后端访问控制)模式的接口配置方式。
- 确保角色和权限匹配
需要同时满足前端路由权限配置和后端菜单数据返回的要求,确保用户角色与两种模式的权限配置都匹配。
到这里,就已经配置完成,混合模式会自动合并前端和后端的路由,提供完整的权限控制功能。
## 按钮细粒度控制
在某些情况下,我们需要对按钮进行细粒度的控制,我们可以借助接口或者角色来控制按钮的显示。

View File

@@ -18,7 +18,7 @@
## 依赖问题
`Monorepo` 项目下,需要养成每次 `git pull`代码都要执行`pnpm install`的习惯,因为经常会有新的依赖包加入,项目在`.husky/git-merge`已经配置了自动执行`pnpm install`,但是有时候会出现问题,如果没有自动执行,建议手动执行一次。
`Monorepo` 项目下,需要养成每次 `git pull`代码都要执行`pnpm install`的习惯,因为经常会有新的依赖包加入,项目在`lefthook.yml`已经配置了自动执行`pnpm install`,但是有时候会出现问题,如果没有自动执行,建议手动执行一次。
## 关于缓存更新问题

View File

@@ -33,8 +33,8 @@
- [Prettier](https://prettier.io/) 用于代码格式化
- [Commitlint](https://commitlint.js.org/) 用于检查 git 提交信息的规范
- [Publint](https://publint.dev/) 用于检查 npm 包的规范
- [Lint Staged](https://github.com/lint-staged/lint-staged) 用于在 git 提交前运行代码校验
- [Cspell](https://cspell.org/) 用于检查拼写错误
- [lefthook](https://github.com/evilmartians/lefthook) 用于管理 Git hooks在提交前自动运行代码校验和格式化
## ESLint
@@ -148,18 +148,66 @@ cspell 配置文件为 `cspell.json`,可以根据项目需求进行修改。
git hook 一般结合各种 lint在 git 提交代码的时候进行代码风格校验,如果校验没通过,则不会进行提交。需要开发者自行修改后再次进行提交
### husky
### lefthook
有一个问题就是校验会校验全部代码,但是我们只想校验我们自己提交的代码,这个时候就可以使用 husky
有一个问题就是校验会校验全部代码,但是我们只想校验我们自己提交的代码,这个时候就可以使用 lefthook
最有效的解决方案就是将 Lint 校验放到本地,常见做法是使用 husky 或者 pre-commit 在本地提交之前先做一次 Lint 校验。
最有效的解决方案就是将 Lint 校验放到本地,常见做法是使用 lefthook 在本地提交之前先做一次 Lint 校验。
项目在 `.husky` 内部定义了相应的 hooks
项目在 `lefthook.yml` 内部定义了相应的 hooks
#### 如何关闭 Husky
- `pre-commit`: 在提交前运行,用于代码格式化和检查
如果你想关闭 Husky直接删除 `.husky` 目录即可。
- `code-workspace`: 更新 VSCode 工作区配置
- `lint-md`: 格式化 Markdown 文件
- `lint-vue`: 格式化并检查 Vue 文件
- `lint-js`: 格式化并检查 JavaScript/TypeScript 文件
- `lint-style`: 格式化并检查样式文件
- `lint-package`: 格式化 package.json
- `lint-json`: 格式化其他 JSON 文件
### lint-staged
- `post-merge`: 在合并后运行,用于自动安装依赖
用于自动修复提交文件风格问题,其配置文件为 `.lintstagedrc.mjs`,可以根据项目需求进行修改。
- `install`: 运行 `pnpm install` 安装新依赖
- `commit-msg`: 在提交时运行,用于检查提交信息格式
- `commitlint`: 使用 commitlint 检查提交信息
#### 如何关闭 lefthook
如果你想关闭 lefthook有两种方式
::: code-group
```bash [临时关闭]
git commit -m 'feat: add home page' --no-verify
```
```bash [永久关闭]
# 删除 lefthook.yml 文件即可
rm lefthook.yml
```
:::
#### 如何修改 lefthook 配置
如果你想修改 lefthook 的配置,可以编辑 `lefthook.yml` 文件。例如:
```yaml
pre-commit:
parallel: true # 并行执行任务
jobs:
- name: lint-js
run: pnpm prettier --cache --ignore-unknown --write {staged_files}
glob: '*.{js,jsx,ts,tsx}'
```
其中:
- `parallel`: 是否并行执行任务
- `jobs`: 定义要执行的任务列表
- `name`: 任务名称
- `run`: 要执行的命令
- `glob`: 匹配的文件模式
- `{staged_files}`: 表示暂存的文件列表

View File

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

View File

@@ -70,7 +70,7 @@ export async function perfectionist(): Promise<Linter.Config[]> {
},
],
'perfectionist/sort-objects': [
'error',
'off',
{
customGroups: {
items: 'items',

View File

@@ -28,6 +28,13 @@ const customConfig: Linter.Config[] = [
'perfectionist/sort-objects': 'off',
},
},
{
files: ['**/**.vue'],
ignores: restrictedImportIgnores,
rules: {
'perfectionist/sort-objects': 'off',
},
},
{
// apps内部的一些基础规则
files: ['apps/**/**'],

View File

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

View File

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

View File

@@ -1,6 +1,6 @@
{
"name": "@vben/tailwind-config",
"version": "5.5.5",
"version": "5.5.7",
"private": true,
"homepage": "https://github.com/vbenjs/vue-vben-admin",
"bugs": "https://github.com/vbenjs/vue-vben-admin/issues",
@@ -12,7 +12,7 @@
"license": "MIT",
"type": "module",
"scripts": {
"stub": "pnpm unbuild"
"stub": "pnpm unbuild --stub"
},
"files": [
"dist"

View File

@@ -1,6 +1,9 @@
{
"$schema": "https://json.schemastore.org/tsconfig",
"extends": "@vben/tsconfig/node.json",
"compilerOptions": {
"moduleResolution": "bundler"
},
"include": ["src"],
"exclude": ["node_modules"]
}

View File

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

View File

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

76
lefthook.yml Normal file
View File

@@ -0,0 +1,76 @@
# EXAMPLE USAGE:
#
# Refer for explanation to following link:
# https://lefthook.dev/configuration/
#
# pre-push:
# jobs:
# - name: packages audit
# tags:
# - frontend
# - security
# run: yarn audit
#
# - name: gems audit
# tags:
# - backend
# - security
# run: bundle audit
#
# pre-commit:
# parallel: true
# jobs:
# - run: yarn eslint {staged_files}
# glob: "*.{js,ts,jsx,tsx}"
#
# - name: rubocop
# glob: "*.rb"
# exclude:
# - config/application.rb
# - config/routes.rb
# run: bundle exec rubocop --force-exclusion {all_files}
#
# - name: govet
# files: git ls-files -m
# glob: "*.go"
# run: go vet {files}
#
# - script: "hello.js"
# runner: node
#
# - script: "hello.go"
# runner: go run
pre-commit:
parallel: true
commands:
code-workspace:
run: pnpm vsh code-workspace --auto-commit
lint-md:
run: pnpm prettier --cache --ignore-unknown --write {staged_files}
glob: '*.md'
lint-vue:
run: pnpm prettier --write {staged_files} && pnpm eslint --cache --fix {staged_files} && pnpm stylelint --fix --allow-empty-input {staged_files}
glob: '*.vue'
lint-js:
run: pnpm prettier --cache --ignore-unknown --write {staged_files} && pnpm eslint --cache --fix {staged_files}
glob: '*.{js,jsx,ts,tsx}'
lint-style:
run: pnpm prettier --cache --ignore-unknown --write {staged_files} && pnpm stylelint --fix --allow-empty-input {staged_files}
glob: '*.{scss,less,styl,html,vue,css}'
lint-package:
run: pnpm prettier --cache --write {staged_files}
glob: 'package.json'
lint-json:
run: pnpm prettier --cache --write --parser json {staged_files}
glob: '{!(package)*.json,*.code-snippets,.!(browserslist)*rc}'
post-merge:
commands:
install:
run: pnpm install
commit-msg:
commands:
commitlint:
run: pnpm exec commitlint --edit $1

View File

@@ -1,6 +1,6 @@
{
"name": "vben-admin-monorepo",
"version": "5.5.5",
"version": "5.5.7",
"private": true,
"keywords": [
"monorepo",
@@ -48,14 +48,14 @@
"lint": "vsh lint",
"postinstall": "pnpm -r run stub --if-present",
"preinstall": "npx only-allow pnpm",
"prepare": "is-ci || husky",
"preview": "turbo-run preview",
"publint": "vsh publint",
"reinstall": "pnpm clean --del-lock && pnpm install",
"test:unit": "vitest run --dom",
"test:e2e": "turbo run test:e2e",
"update:deps": "npx taze -r -w",
"version": "pnpm exec changeset version && pnpm install --no-frozen-lockfile"
"version": "pnpm exec changeset version && pnpm install --no-frozen-lockfile",
"catalog": "pnpx codemod pnpm/catalog"
},
"devDependencies": {
"@changesets/changelog-github": "catalog:",
@@ -78,9 +78,8 @@
"cross-env": "catalog:",
"cspell": "catalog:",
"happy-dom": "catalog:",
"husky": "catalog:",
"is-ci": "catalog:",
"lint-staged": "catalog:",
"lefthook": "catalog:",
"playwright": "catalog:",
"rimraf": "catalog:",
"tailwindcss": "catalog:",

View File

@@ -1,6 +1,6 @@
{
"name": "@vben-core/design",
"version": "5.5.5",
"version": "5.5.7",
"homepage": "https://github.com/vbenjs/vue-vben-admin",
"bugs": "https://github.com/vbenjs/vue-vben-admin/issues",
"repository": {

View File

@@ -1,6 +1,6 @@
{
"name": "@vben-core/icons",
"version": "5.5.5",
"version": "5.5.7",
"homepage": "https://github.com/vbenjs/vue-vben-admin",
"bugs": "https://github.com/vbenjs/vue-vben-admin/issues",
"repository": {

View File

@@ -1,6 +1,6 @@
{
"name": "@vben-core/shared",
"version": "5.5.5",
"version": "5.5.7",
"homepage": "https://github.com/vbenjs/vue-vben-admin",
"bugs": "https://github.com/vbenjs/vue-vben-admin/issues",
"repository": {

View File

@@ -1,6 +1,6 @@
{
"name": "@vben-core/typings",
"version": "5.5.5",
"version": "5.5.7",
"homepage": "https://github.com/vbenjs/vue-vben-admin",
"bugs": "https://github.com/vbenjs/vue-vben-admin/issues",
"repository": {

View File

@@ -60,8 +60,9 @@ type BreadcrumbStyleType = 'background' | 'normal';
* 权限模式
* backend 后端权限模式
* frontend 前端权限模式
* mixed 混合权限模式
*/
type AccessModeType = 'backend' | 'frontend';
type AccessModeType = 'backend' | 'frontend' | 'mixed';
/**
* 导航风格

View File

@@ -1,3 +1,8 @@
import type { RouteLocationNormalized } from 'vue-router';
export type TabDefinition = RouteLocationNormalized;
export interface TabDefinition extends RouteLocationNormalized {
/**
* 标签页的key
*/
key?: string;
}

View File

@@ -43,6 +43,10 @@ interface RouteMeta {
| 'success'
| 'warning'
| string;
/**
* 路由的完整路径作为key默认true
*/
fullPathKey?: boolean;
/**
* 当前路由的子级在菜单中不展现
* @default false

View File

@@ -1,6 +1,6 @@
{
"name": "@vben-core/composables",
"version": "5.5.5",
"version": "5.5.7",
"homepage": "https://github.com/vbenjs/vue-vben-admin",
"bugs": "https://github.com/vbenjs/vue-vben-admin/issues",
"repository": {

View File

@@ -10,7 +10,14 @@ exports[`defaultPreferences immutability test > should not modify the config obj
"colorWeakMode": false,
"compact": false,
"contentCompact": "wide",
"contentCompactWidth": 1200,
"contentPadding": 0,
"contentPaddingBottom": 0,
"contentPaddingLeft": 0,
"contentPaddingRight": 0,
"contentPaddingTop": 0,
"defaultAvatar": "https://unpkg.com/@vbenjs/static-source@0.1.7/source/avatar-v1.webp",
"defaultHomePath": "/analytics",
"dynamicTitle": true,
"enableCheckUpdates": true,
"enablePreferences": true,
@@ -22,6 +29,7 @@ exports[`defaultPreferences immutability test > should not modify the config obj
"name": "Vben Admin",
"preferencesButtonPosition": "auto",
"watermark": false,
"zIndex": 200,
},
"breadcrumb": {
"enable": true,
@@ -42,15 +50,18 @@ exports[`defaultPreferences immutability test > should not modify the config obj
"footer": {
"enable": false,
"fixed": false,
"height": 32,
},
"header": {
"enable": true,
"height": 50,
"hidden": false,
"menuAlign": "start",
"mode": "fixed",
},
"logo": {
"enable": true,
"fit": "contain",
"source": "https://unpkg.com/@vbenjs/static-source@0.1.7/source/logo-v1.webp",
},
"navigation": {
@@ -67,14 +78,17 @@ exports[`defaultPreferences immutability test > should not modify the config obj
},
"sidebar": {
"autoActivateChild": false,
"collapseWidth": 60,
"collapsed": false,
"collapsedButton": true,
"collapsedShowTitle": false,
"enable": true,
"expandOnHover": true,
"extraCollapse": false,
"extraCollapsedWidth": 60,
"fixedButton": true,
"hidden": false,
"mixedWidth": 80,
"width": 224,
},
"tabbar": {

View File

@@ -1,6 +1,6 @@
{
"name": "@vben-core/preferences",
"version": "5.5.5",
"version": "5.5.7",
"homepage": "https://github.com/vbenjs/vue-vben-admin",
"bugs": "https://github.com/vbenjs/vue-vben-admin/issues",
"repository": {

View File

@@ -9,8 +9,15 @@ const defaultPreferences: Preferences = {
colorWeakMode: false,
compact: false,
contentCompact: 'wide',
contentCompactWidth: 1200,
contentPadding: 0,
contentPaddingBottom: 0,
contentPaddingLeft: 0,
contentPaddingRight: 0,
contentPaddingTop: 0,
defaultAvatar:
'https://unpkg.com/@vbenjs/static-source@0.1.7/source/avatar-v1.webp',
defaultHomePath: '/analytics',
dynamicTitle: true,
enableCheckUpdates: true,
enablePreferences: true,
@@ -22,6 +29,7 @@ const defaultPreferences: Preferences = {
name: 'Vben Admin',
preferencesButtonPosition: 'auto',
watermark: false,
zIndex: 200,
},
breadcrumb: {
enable: true,
@@ -42,15 +50,19 @@ const defaultPreferences: Preferences = {
footer: {
enable: false,
fixed: false,
height: 32,
},
header: {
enable: true,
height: 50,
hidden: false,
menuAlign: 'start',
mode: 'fixed',
},
logo: {
enable: true,
fit: 'contain',
source: 'https://unpkg.com/@vbenjs/static-source@0.1.7/source/logo-v1.webp',
},
navigation: {
@@ -70,11 +82,14 @@ const defaultPreferences: Preferences = {
collapsed: false,
collapsedButton: true,
collapsedShowTitle: false,
collapseWidth: 60,
enable: true,
expandOnHover: true,
extraCollapse: false,
extraCollapsedWidth: 60,
fixedButton: true,
hidden: false,
mixedWidth: 80,
width: 224,
},
tabbar: {

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