Compare commits

...

283 Commits

Author SHA1 Message Date
dependabot[bot]
8dabaa37a5 chore(deps): bump stylelint-config-recommended-scss
Bumps [stylelint-config-recommended-scss](https://github.com/stylelint-scss/stylelint-config-recommended-scss) from 14.1.0 to 16.0.0.
- [Release notes](https://github.com/stylelint-scss/stylelint-config-recommended-scss/releases)
- [Changelog](https://github.com/stylelint-scss/stylelint-config-recommended-scss/blob/master/CHANGELOG.md)
- [Commits](https://github.com/stylelint-scss/stylelint-config-recommended-scss/compare/v14.1.0...v16.0.0)

---
updated-dependencies:
- dependency-name: stylelint-config-recommended-scss
  dependency-version: 16.0.0
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-08-21 20:39:22 +00:00
LinaBell
cf6c4c9aae fix: cannot read properties of null (reading 'nextSibling') (#6667) 2025-08-21 22:26:10 +08:00
Ken Hai
ffaf85c8f3 fix: 修复角色修改时VbenTree组件没有回显选中 (#6662)
* fix: 修复角色修改时VbenTree组件没有回显选中

* chore: use nextTick

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

* fix: merge

* chore: 更新

---------

Co-authored-by: haiyinlong <haiyinlong@uhigame.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-08-21 15:30:58 +08:00
panda7
2cc78f925f fix: the bug in the lock method of the vbenModal component (#6648) 2025-08-21 15:17:55 +08:00
ming4762
93f0eea4e7 fix: fix the issue of excessive line spacing in vbenForm (#6653)
* gap-2和 pb-4/2 重叠导致间距过宽,gap-x只保留列间距
2025-08-21 12:41:04 +08:00
谦元吉
58e3941810 chore(docs): update the component import of the form adapter description in the document (#6656) 2025-08-19 16:48:10 +08:00
Svend
3ad433a50b fix: 修复在 hash 路由模式下无法在新窗口打开路由的问题 (#6652)
此问题是由于 PR #6583 中新增的 `resolveHref` 函数导致的。其在 hash 路由模式下,得到的 URL 会包含 #/ 前缀。在经过 openRouteInNewWindow 的逻辑后就会出现两次 /# 前缀
2025-08-19 16:47:45 +08:00
ming4762
8ac2db5b7c fix: fix the issue of VbenForm compact reactive failure (#6654) 2025-08-19 16:46:14 +08:00
Elm1992
a441dcebae fix: meta.link invalid issue 2025-08-19 16:40:16 +08:00
Vben
ff4704d5ea chore: Upgrade vite to version 7.x (#6645) 2025-08-16 22:50:31 +08:00
菠萝吹雪
6ddfbd84b0 chore: modify the contributor showcase in the README (#6636) 2025-08-16 22:47:08 +08:00
ming4762
1e6417f95b feat: vBenForm add layout: inline (#6644) 2025-08-16 22:41:08 +08:00
vben
e147a9d2fd chore: release 5.5.9 2025-08-16 22:16:02 +08:00
谦元吉
4efebb8c0b fix:update (#6635) 2025-08-14 22:01:12 +08:00
gxc685
9ce0df88ae fix: 修复mock里面eventHandler重复导致无法启动 (#6631) 2025-08-14 22:00:54 +08:00
谦元吉
3cf0c0eb04 fix(@vben/backend-mock): go back to the last modification (#6634)
* fix(@vben/backend-mock): the version went back to the last submission, and the latest submission was completely useless.

* fix: resolve conflicts
2025-08-14 12:06:41 +08:00
谦元吉
ab7e363279 fix(@vben/backend-mock): fix all ts type errors in this module (#6613)
* fix(@vben/backend-mock): 修复所有 ts 类型报错

* fix(@vben/backend-mock): 修复该模块所有 ts 类型报错

* fix(@vben/backend-mock): 解决 coderabbitai

* fix(@vben/backend-mock): 解决 coderabbitai

* fix(@vben/backend-mock): 解决 coderabbitai
2025-08-12 17:23:39 +08:00
xueyang
9fc594434f perf: 优化useVbenForm样式 (#6611)
* perf(style): 优化useVbenForm垂直布局 actions 样式

* perf(style): 优化useVbenForm actions 布局样式

- 操作按钮组显示位置
```
actionPosition?: 'center' | 'left' | 'right';
```
- 操作按钮组的样式
```
actionType?: 'block' | 'inline'
inline: 行类显示,block: 新一行单独显示
```

* perf: 优化useVbenForm actions 布局样式

删除 actionType
增加 actionLayout
- actionLayout?: 'inline' | 'newLine' | 'rowEnd';
- newLine: 在新行显示。rowEnd: 在行内显示,靠右对齐(默认)。inline: 使用grid默认样式
- 删除无用代码 queryFormStyle

* perf: 优化useVbenForm使用案例

* perf: 优化form组件样式

去掉padding,改为gap

* docs: update vben-form.md

* fix: 修复FormMessage位置

* perf: Avoid direct mutation of props object.

-  props.actionLayout = props.actionLayout || 'rowEnd';
-  props.actionPosition = props.actionPosition || 'right';
+  const actionLayout = props.actionLayout || 'rowEnd';
+  const actionPosition = props.actionPosition || 'right';

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

* fix: 修复 wrapperClass 权重

* fix: 全局搜索结果不匹配 #6603

* fix: 避免FormMessage溢出

---------

Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
2025-08-07 23:48:34 +08:00
leo
b93e22c45a fix(@vben/layouts): respect base URL when opening route in new window (#6583)
Previously, the generated URL for opening routes in a new window did not include the router base,
which led to incorrect paths when the app was deployed under a subdirectory (e.g., /admin/).
This change ensures that the resolved path includes the configured base by using
router.resolve(path).href.
2025-07-29 13:46:05 +08:00
Jin Mao
193f5b6512 Merge branch 'main' into 2025072604 2025-07-28 15:53:04 +08:00
Jin Mao
cb3f96683f fix: 修复双列布局模式下,路由为hideInMenu时,空白右列 2025-07-28 15:50:21 +08:00
zhongming4762
06ffdf164a feat: add dingding login 2025-07-25 22:02:55 +08:00
ming4762
5b75e5e917 perf: perf the control logic of VbenModal full screen and header (#6566)
* resolve the issue of header=false and full screen button display but not operable
2025-07-25 21:45:45 +08:00
aonoa
fad0b49841 fix: adding roles does not automatically refresh (#6548)
* fix: adding roles does not automatically refresh

* style: fix code style err
2025-07-25 21:35:57 +08:00
Jin Mao
260e45cd7b Merge branch 'main' into feat/add-vben-modal-animation 2025-07-25 21:33:11 +08:00
vben
1575619d53 chore: release v5.5.8 2025-07-19 22:19:50 +08:00
Jin Mao
d5a36a167d fix: fix vxeTable commit proxy (#6536)
* fix: 修正use-vxe-grid中的代理配置提交类型

* chore: change config
2025-07-19 16:07:15 +08:00
panda7
fc9ea347ca Merge branch 'main' into feat/add-vben-modal-animation 2025-07-18 00:38:54 +08:00
 panda7
1a9b0509d5 feat: add animation effects to VbenModal component 2025-07-18 00:15:40 +08:00
panda7
07b64ad384 feat: add function support for formItemClass prop (#6511)
* feat: add function support for formItemClass prop

* feat: add try-catch to formItemClass function

* fix: formItemClass function ts error

---------

Co-authored-by: sqchen <chenshiqi@sshlx.com>
2025-07-17 09:37:39 +08:00
Jin Mao
1bc5d2986b chore: update-vxe-table (#6516)
* chore: update vxe-pc-ui,vxe-table

* fix(ui): 修复代理配置初始化方法名错误

* fix(ui): 修改远程表格刷新配置

* chroe: update vxeTable

更新到最新
2025-07-16 19:15:39 +08:00
HamWong
bb36cca315 fix: 锁定屏幕页面样式自适应 (#6480) 2025-07-15 09:08:08 +08:00
Jin Mao
b8bf482c6a Merge branch 'main' into form 2025-07-07 09:16:54 +08:00
Jin Mao
3b673ca915 Merge branch 'main' into feature/scroll_to_the_error_field 2025-07-07 08:21:50 +08:00
Jin Mao
bbf0287511 chore: fix lint warning (#6487) 2025-07-07 08:21:25 +08:00
panda7
d4786f3f75 Merge branch 'main' into feature/scroll_to_the_error_field 2025-07-06 21:19:52 +08:00
xue-jn
b333fd676d docs: update vben-drawer.md (#6478)
* docs: update vben-drawer.md

* docs: update vben-drawer.md

---------

Co-authored-by: Jin Mao <50581550+jinmao88@users.noreply.github.com>
2025-07-06 20:26:06 +08:00
sqchen
f1051c8773 feat: add scrollToFirstError to the form component 2025-07-05 00:50:53 +08:00
sqchen
243f3a201d feat: add scrollToFirstError to the form component 2025-07-05 00:19:12 +08:00
陈蔚然
2f7de243f6 Merge branch 'vbenjs:main' into slider-translate-captcha 2025-07-04 10:28:40 +08:00
chenweiran
1aafb43103 feat: 增加基于图片拼图切片平移的验证码 2025-07-04 10:26:02 +08:00
chenweiran
8ccd01ade5 feat: 增加基于图片拼图切片平移的验证码 2025-07-04 10:23:47 +08:00
Netfan
e6bfbce6cb revert: page height fixed,revert #6422 2025-07-03 22:29:48 +08:00
someone-cool
1f63aed64c fix(@vben/common-ui): add page footer's height to calc contentstyle (#6422) 2025-07-03 18:36:42 +08:00
xue-jn
253b0da7d2 fix: 接口返回子节点为空数组时,还会显示折叠箭头 (#6463) 2025-07-03 18:35:03 +08:00
RanMaoting
33a4d524db feat(@vben/plugins): 新增VxeGrid组件插槽类型定义 (#6452)
* feat(@vben/plugins): 新增VxeGrid组件插槽类型定义

Closes: #6451

* fix(@vben/plugins): 优化vxe-table组件的插槽类型定义

修复Omit导致的类型丢失
2025-07-03 18:34:37 +08:00
chenweiran
bbd8a53d9d feat: 增加基于图片拼图切片平移的验证码 2025-07-03 18:20:20 +08:00
chenweiran
8554924cb9 feat: 增加基于图片拼图切片平移的验证码 2025-07-03 17:43:19 +08:00
RanMaoting
fee811d950 fix: 优化组件方法透传并新增表单弹窗示例 (#6443) 2025-07-02 19:58:48 +08:00
Netfan
78076e70b4 chore: update deps 2025-07-02 16:55:55 +08:00
chewenye
b78bc65ce7 feat: 组件json-viewer支持bigint数据显示 cwy (#6377)
Co-authored-by: 车文烨 <chewy@china-lehua.com>
2025-06-29 04:32:30 +08:00
Utopia
b1fb623113 feat: 为 auth layout 添加 slot: logo, 提升组件的灵活性和可复用性 (#6442) 2025-06-27 19:23:24 +08:00
Stephen Chang
de14908fd3 fix(icon-picker): 解决icon-picker组件切换分页后,关键词检索失效问题 (#6437)
当icon-picker组件切换分页后,在输入关键词检索,但是分页没有重置,导致检索结果异常
2025-06-27 19:21:23 +08:00
Li Kui
5c3972196a fix: Add $t import to login expired modal (#6429)
closes #6230
2025-06-27 19:20:25 +08:00
CG.gatspy
3230781538 feat: [vben-tree]增加数据disabled (#6343)
* feat: [vben-tree]增加数据disabled

* Update packages/@core/ui-kit/shadcn-ui/src/ui/tree/tree.vue

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-06-27 19:09:30 +08:00
broBinChen
e7fd0e3b6a feat(hooks): useHoverToggle的入参refElement支持传入响应式数组 (#6333)
* feat(hooks): useHoverToggle的入参refElement支持传入响应式数组

* feat(hooks): 1、增加 useHoverToggle 中 refElement 参数关于传入响应式数组的注释说明。 2、修改 watch 监听深度,仅需浅层监听 refs 变化。 3、使用 effectScope 管理 useElementHover 实例,避免 refs 变化时事件监听器累积导致的内存泄漏问题

* feat(hooks): 在useHoverToggle中增强 updateHovers  的边界处理,优化watch方案,只监听元素数量变化而不是整个数组变化,避免过度依赖收集

---------

Co-authored-by: xiaobin <xiaobin_chen@fzzixun.com>
2025-06-27 19:08:41 +08:00
yuhengshen
2f7d1f009d fix: 全屏状态下弹窗圆角优化 (#6413) 2025-06-24 23:41:54 +08:00
yuhengshen
946f91f387 feat: optimize modal dragging range(#6414)
* 当弹窗指定了容器时,拖拽将被限制在容器范围内
2025-06-24 17:05:59 +08:00
lghuahua
986eacae9a fix: improve request logic in api-component
* 修复某些情况下updateParam设置的参数可能不会提交给api的问题
* 修复在上一个请求尚未完成前如果params发生了变更,将不会再触发新的api请求
---------

Co-authored-by: Netfan <netfan@foxmail.com>
2025-06-20 08:42:45 +08:00
Netfan
97b8e28a2b docs: fix delete request usage (#6389) 2025-06-17 08:52:59 +08:00
Netfan
c0962fec18 fix: auto close popup on deactivated (#6368)
* 修复挂载到内容区域的弹窗和抽屉被意外关闭的问题
2025-06-11 12:20:52 +08:00
XiaoHetitu
8ba7bdf2bd fix(button): 为按钮添加type属性防止表单提交意外触发表单验证机制 (#6340)
在按钮组件中,按钮元素缺少type="button"属性可能导致在表单中意外提交。添加此属性以确保按钮行为符合预期。

Co-authored-by: yuanwj <ywj6792341@qq.com>
2025-06-08 17:56:24 +08:00
zyy
b015fbc9fc fix: [adapter] 表格配置类型报错 (#6327)
配置toolbarConfig中的search时会有类型报错
2025-06-08 17:53:55 +08:00
broBinChen
b69320c070 feat(hooks): support separate enter/leave delays in useHoverToggle (#6325)
Co-authored-by: xiaobin <xiaobin_chen@fzzixun.com>
2025-06-08 17:53:29 +08:00
zhang
dcccc213ce fix: requestClient.upload会将vbenform中value为undefined的值转为字符串undefined’提… (#6300)
* fix: requestClient.upload会将vbenform中value为undefined的值转为字符串undefined’提交给后台保存

* fix: requestClient.upload会将vbenform中value为undefined的值转为字符串'undefined’提交给后台保存
2025-06-08 17:51:16 +08:00
ali-pay
c0e601c020 fix: menu type is not 'button' (#6277)
Co-authored-by: Jin Mao <50581550+jinmao88@users.noreply.github.com>
2025-06-08 17:50:44 +08:00
RanMaoting
017ed1a9e1 types: 为useVbenVxeGrid添加泛型声明,使grid实例上能正确获取到行数据的类型 (#5653)
Co-authored-by: Jin Mao <50581550+jinmao88@users.noreply.github.com>
2025-06-08 17:43:02 +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
Netfan
081d08a7f8 fix: alert width fixed in small screen (#6312) 2025-05-30 19:54:26 +08:00
Netfan
96a10ca83f style: fix lint error (#6298) 2025-05-28 19:23:21 +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
liqiang0330
53304514b6 fix: Update index.ts (#6268)
* Update index.ts

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

* Update index.ts

增加空格!
2025-05-26 13:29:27 +08:00
Netfan
6fbf1387f5 fix: reset slider-captcha after login failed (#6275) 2025-05-25 16:04:56 +08:00
Netfan
e5c937396d fix: json-bigint parse used in vxeTable (#6271)
* 修复vxeTable不能加载json-bigint解析的数据的问题
2025-05-24 13:01:58 +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
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
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
zwtvip
f7bae8ac0f chore: export framework components for use in independent pages 2025-05-21 00:37:26 +08:00
wyc001122
8ac97688da fix(preferences): 更新内容内边距默认值 (#6233)
Co-authored-by: wyc001122 <wangyongchao@testor.com.cn>
2025-05-20 09:50:23 +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
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
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
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
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
afe1
90625782c0 fix: delete useless code (#6143) 2025-05-08 16:51:12 +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
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
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
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
Jin Mao
20c15f352f perf: page componet supports custom height offset for flexible content height … (#6081)
* perf: Page supports custom height offset for flexible content height control.

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

* chore: typo

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

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

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

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

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

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

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

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

* perf: improve destroyOnClose for VbenDrawer
2025-04-27 11:26:50 +08:00
Netfan
0a9fc4e02d fix: title of search button in vxeTable toolbar (#6046)
* 修改vxeTable工具栏里的搜索按钮的提示文案
2025-04-26 01:08:41 +08:00
Netfan
be840460d8 feat: vbenSelect support prop allowClear (#6043) 2025-04-25 23:37:03 +08:00
Netfan
cb45987fe2 docs: update example (#6036)
* 跟进后端菜单逻辑的修改,现已无需传递basicLayout布局
2025-04-25 11:44:47 +08:00
panda7
5ffd7db8e0 fix: the initial value echo for the check-button-group (#6029)
Co-authored-by: sqchen <9110848@qq.com>
2025-04-25 08:35:03 +08:00
Netfan
14377705e7 fix: alert confirm state in beforeClose callback (#6019) 2025-04-23 12:20:52 +08:00
pangyajun123
b985ff0584 fix: vxe-table theme token follow primary color (#6007) 2025-04-21 19:15:05 +08:00
wyc001122
b148b8ec92 fix: fix geader menu activation path (#5997)
Co-authored-by: 王泳超 <wangyongchao@testor.com.cn>
2025-04-19 14:35:33 +08:00
Netfan
79de6bcbf7 fix: alert send wrong confirm state to beforeClose (#5991)
* 修复alert在按下Esc或者点击遮罩关闭时,可能发送错误的isConfirm状态
2025-04-17 22:23:05 +08:00
Netfan
14bd6dd25d fix: destroyOnClose works within connectedComponent (#5989)
* 修复destroyOnClose没能销毁connectedComponent自身的问题
2025-04-17 20:25:49 +08:00
PIPEDREA_WZJ
7f269e0d69 Update tailwindcss.md (#5602)
tailwindcss最新的版本已经是v4.x,vben中使用的是3.x的tailwindcss。在未进行兼容前,会出现运行失败的问题
2025-04-17 14:01:39 +08:00
yuh
4baec83db5 feat: add examples: form-upload (#5955)
* feat: add examples: form-upload

* fix: upload: accept and label

* fix: upload: 设置表单值、图片预览
2025-04-17 14:00:46 +08:00
Netfan
f7a4d13a4c fix: fixed arguments of callbacks in formApi (#5970)
* 修复 `handleValuesChange` 传递的参数不是处理后的表单值的问题

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

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

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

---------

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

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

* fix: modal closing animation

---------

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

* feat: ApiComponent autoSelect支持使用函数,可以满足灵活性要求更高的场景
2025-04-13 20:03:18 +08:00
Netfan
a8c4786311 feat: api-component support autoSelect prop (#5931)
* feat: api-component support autoSelect prop

* docs: add version requirement
2025-04-12 14:02:35 +08:00
Netfan
2971ccc0b7 docs: docs modal z-index fixed, update alert docs (#5930) 2025-04-12 13:41:40 +08:00
Netfan
4a2c7b313f fix: alert animation (#5927) 2025-04-12 10:37:47 +08:00
Netfan
36bf6fc149 fix: builtin color change throttled in preference drawer (#5924)
修复偏好设置中的自定义主题色拖动选择颜色时页面会明显卡顿的问题
2025-04-12 01:44:08 +08:00
Netfan
f46ec30995 fix: theme mode follow the system only auto (#5923)
* 修复主题在未设置为auto时,仍然会跟随系统主题变化的问题。
2025-04-12 01:16:57 +08:00
Netfan
9bd5a190c2 fix: alert action button focus, fixed #5921 (#5922)
* 修复Alert组件的按钮焦点切换问题
2025-04-12 00:59:56 +08:00
zhang
86da3cedc2 chore: 导出框架自带的组件,方便独立页面使用 (#5876) 2025-04-09 16:16:56 +08:00
Netfan
329a176a5c perf: optimize bootstrap modules to speed up first-screen loading (#5899)
优化首屏加载速度
2025-04-09 01:05:20 +08:00
Netfan
9379093a4f feat: customizable table separator (#5898)
* 表格的分隔条支持定制背景色或完全移除
2025-04-08 20:28:50 +08:00
ming4762
c9014d0338 perf: 优化关闭页面切换动画的tab切换性能 (#5883) 2025-04-08 20:27:03 +08:00
Netfan
ed26dca64e chore: update pnpm-lock.yaml 2025-04-08 16:31:41 +08:00
Netfan
08c6496e24 chore: update deps 2025-04-08 14:56:40 +08:00
Netfan
a8c5df38e9 fix: possible circular reference issue during build (#5894)
* 修复构建期间出现的循环引用警告
2025-04-08 14:50:05 +08:00
Netfan
71e8d12b70 fix: improve prompt component (#5879)
* fix: prompt component render fixed

* fix: alert buttonAlign default value
2025-04-07 01:21:30 +08:00
Netfan
d216fdca44 feat: support logo text slot (#5872)
* 基础布局中的LOGO的文字区域允许通过插槽logo-text定制
2025-04-05 13:07:52 +08:00
wyc001122
384c5d7dbb fix: 布局为双列菜单或者水平模式下, 一级菜单高亮问题 (#5870)
Co-authored-by: 王泳超 <wangyongchao@testor.com.cn>
2025-04-05 11:04:59 +08:00
Netfan
b0ad08dbbc feat: use the not-found component instead of the invalid route component in the backend mode (#5871)
* 后端菜单模式下,使用not-found组件代替无效的路由组件
2025-04-04 15:21:09 +08:00
Rascal-Coder
3600603016 fix: vxeGrid height fixed #5861 (#5862) 2025-04-04 13:16:31 +08:00
superdl1996
cde1a85394 docs: typo (#5855) 2025-04-03 19:11:40 +08:00
Netfan
c623604ea9 docs: fix alert demo in docs 2025-04-02 20:37:41 +08:00
Netfan
7933da8f66 chore: update deps (#5854) 2025-04-02 18:07:06 +08:00
Netfan
ecf518bb02 fix: alert beforeClose callback arguments fixed (#5845) 2025-04-01 22:55:29 +08:00
ming4762
1d9f1be004 fix: 解决AccessModeType:backend登录过期,重新登录不会重新生成路由的问题,重现步骤分析: (#5830)
1、长时间未登录登录过期,再次打开页面构开始生成动态路由
2、fetchMenuListAsync后台返回401登录过期:doReAuthenticate函数跳转到登录页面
3、异常被拦截,return []
4、gurad.ts accessStore.setIsAccessChecked(true); 被错误的标识为已生成路由
5、重新登录后,accessStore.isAccessChecked=true未能正确的重新生成路由
2025-04-01 15:50:45 +08:00
Netfan
44138f578f feat: add preset alert, confirm, prompt components that can be simple called (#5843)
* feat: add preset alert, confirm, prompt components that can be simple called

* fix: type define
2025-04-01 15:10:18 +08:00
Joeshu
0e3abc2b53 docs: add third-party libraries to check update methods (#5819) 2025-03-31 19:28:28 +08:00
Arthur Darkstone
a96be3db98 docs: add explanation and related script configuration to distinguish build environment (#5826)
* docs: add explanation and related script configuration to distinguish build environment

* docs: fix spell error
2025-03-31 19:28:02 +08:00
Netfan
d6f239c564 docs: fix api-component demo link (#5828) 2025-03-31 12:08:45 +08:00
Netfan
166e9a0e82 chore: add demo for apiComponent with caching and concurrency (#5827)
* chore: add demo for apiComponent with caching and concurrency

* docs: update api component docs
2025-03-31 11:51:57 +08:00
Netfan
06ccad9db0 fix: vbenTree modelValue synchronization (#5825) 2025-03-31 10:18:35 +08:00
Jin Mao
18722ce434 feat: sidebar button config (#5818)
* feat: 新增 PreferenceCheckboxItem 组件

* feat(preferences): 添加侧边栏按钮配置功能

* feat: 新增按钮点击事件触发功能

* feat(SidebarPreferences): 新增侧边栏折叠按钮与固定按钮配置

* feat(ui): 新增侧边栏固定按钮及配置选项

* fix(test): 修正侧边栏配置项缺失问题
2025-03-31 10:17:42 +08:00
Netfan
a0feeb1966 fix: watermark settings in the preferences modified accidentally (#5823) 2025-03-31 09:06:02 +08:00
Jin Mao
df6341f0b8 feat(tabbar): 添加右键菜单过滤功能 (#5820) 2025-03-30 16:23:24 +08:00
anyup
dbc0b7e4a9 fix: route root.children duplicate problem (#5755)
Co-authored-by: anyup <anyupxing@163.com>
2025-03-29 22:38:30 +08:00
zhang
aa2907323f style: 更正引用格式 (#5784) 2025-03-29 22:29:16 +08:00
Netfan
96d2bc52e9 feat: pre-set serialization methods for request parameters (#5814)
添加快捷设置请求参数序列化方法的配置
2025-03-29 19:21:21 +08:00
Netfan
e91e4e0eea docs: fix form compact docs (#5811)
* docs: fix form `compact` docs

* docs: remove `compact` from FormCommonConfig
2025-03-28 16:15:35 +08:00
Netfan
c2b5f6497d fix: vben tree component warning (#5809) 2025-03-28 16:01:30 +08:00
Netfan
3c2d325d8c perf: improve api-component for using in form (#5796) 2025-03-27 15:51:11 +08:00
Netfan
a77bb8e68d perf: improve component packaging to enable instance method retrieval (#5795)
改进组件适配器里的包装函数,使得组件暴露的方法可以透传
2025-03-27 15:13:13 +08:00
Netfan
870cd86393 fix: auto check parent after node selected (#5794) 2025-03-27 14:22:05 +08:00
Netfan
0b650367f3 fix: popover background color in dark mode (#5783)
* 修复dark主题下的弹出层背景色在某些浏览器上表现为完全透明的问题
2025-03-25 21:17:55 +08:00
zhang
1616a06bfd perf: 优化多文件上传入参是数组的情况 (#5757)
Co-authored-by: Jin Mao <50581550+jinmao88@users.noreply.github.com>
2025-03-25 10:26:19 +08:00
Netfan
3f0d30897f fix: menu drawer can not be closed after a successful submitting (#5770) 2025-03-23 11:34:21 +08:00
Jin Mao
66c1d390b9 feat(ui): logo icon support click events (#5725)
* feat(ui): 扩展auth页面添加点击 Logo 的事件处理

在 `authentication.vue` 中新增 `clickLogo` 属性,允许在点击 Logo 时执行自定义操作。在 `auth.vue` 中实现了一个示例的点击事件处理函数,用于测试该功能。

* feat(layout): 添加点击 logo 的事件处理函数

在 BasicLayout 组件中添加了 clickLogo 事件处理函数,并通过 emit 方法触发 clickLogo 事件。同时,在 basic.vue 中实现了 handleClickLogo 函数,用于处理 logo 点击事件。

* fix(ui): 移除logo点击事件的控制台日志
2025-03-23 10:02:22 +08:00
Netfan
03ceb2aac5 fix: default value for nested fields (#5763) 2025-03-21 16:06:14 +08:00
Netfan
39888cebaa fix: base component focus color in form (#5760) 2025-03-21 09:41:42 +08:00
Netfan
efb69fc75f feat: add form-is-required class for required items. fixed: #5739 (#5759) 2025-03-21 09:25:38 +08:00
Jin Mao
711a179c69 chore: update codeowners (#5750) 2025-03-21 08:37:21 +08:00
Netfan
3133f8f8b9 fix: table form reset will trigger reload twice while submitOnChange set (#5756)
* 修复表格的搜索表单在重置时,可能会触发2次表格刷新的问题
2025-03-20 19:42:22 +08:00
anyup
f0a43912d1 fix: sort the menu so that it doesn't get replaced with 999 when order=0 (#5753)
Co-authored-by: anyup <anyupxing@163.com>
2025-03-20 19:41:46 +08:00
vben
b92ac5c36d chore: release 5.5.4 2025-03-18 21:43:27 +08:00
chen-d-yu
504070f3eb fix: vsh stylelint script fixed (#5729) 2025-03-17 09:23:27 +08:00
Netfan
feab6b3b30 fix: form item style adjustment (#5694) 2025-03-11 02:47:06 +08:00
Netfan
2d4ac33046 fix: miss default value in vbenLayout 2025-03-10 18:59:56 +08:00
Netfan
17e2a02281 feat: auto set component name for keep-alive (#5690)
* fix: auto set component name for keep-alive

* fix: type define
2025-03-10 16:25:30 +08:00
Netfan
096545c5a1 docs: update table slots docs 2025-03-10 10:53:17 +08:00
Netfan
04dff33ac5 feat: improved formApi for component instance support
* 改进表单API以支持组件实例的获取,以及焦点字段的获取
2025-03-10 02:56:44 +08:00
Netfan
cfa18c2b8e fix: improve component repackaging 2025-03-10 02:56:44 +08:00
Netfan
13354955db chore: update depts 2025-03-10 02:56:44 +08:00
ijackwu
bb683804f4 fix: live-server set port use [--port=PORT] (#5687) 2025-03-09 08:49:06 +08:00
Netfan
e2a577de24 feat: add size prop to avatar component and update logo component for size handling (#5684) 2025-03-08 11:37:02 +08:00
Netfan
89d963c81a fix: vxeTable search button not working with slot (#5678) 2025-03-07 22:35:09 +08:00
Netfan
b37ed48b9d feat: role management page with component tree (#5675)
* feat: add shadcn tree

* fix: update vbenTree component

* feat: role management demo page

* feat: add cellSwitch renderer for vxeTable

* chore: remove tree examples
2025-03-07 16:03:08 +08:00
Netfan
4b9cfcb867 fix: demo nested menu path (#5667)
* 修复演示的嵌套菜单path配置导致的面包屑跳转问题
2025-03-06 22:48:54 +08:00
Netfan
f86c9f90ad fix: keepAlive not working for popup appendToMain (#5666)
* 修复弹窗和抽屉 `appendToMain` 时且启用`keepAlive` 时未能正确缓存的问题
2025-03-06 22:22:45 +08:00
Netfan
31a6ab59fb feat: vben checkbox support indeterminate state and transition animation (#5662) 2025-03-06 16:11:02 +08:00
Netfan
34789645f7 fix: nitro server cookie maxAge fixed 2025-03-04 22:29:27 +08:00
Netfan
f380452ef0 feat: modal and drawer locking improve (#5648)
* feat: add `unlock` for modalApi

* fix: modal's close button style in locking

* fix: fix modal's close button disabled on locking

* feat: add `lock` and `unlock` for drawerApi
2025-03-04 22:00:32 +08:00
jasonz18
decd9c55e5 docs: typo 2025-03-04 21:40:34 +08:00
Netfan
e815f0ff89 docs: vbenVxeTable slots docs update 2025-03-01 22:16:36 +08:00
Netfan
5ea6b4a8d8 fix: logo style in login page is affected by the globally-imported antd styles
* 修复登录页左上角LOGO部分的文字在全局导入antd样式的时候位置不正确的问题
2025-02-28 22:39:42 +08:00
Netfan
a53ca3faf1 chore: update depts 2025-02-28 20:30:59 +08:00
Netfan
86fdd6c93b fix: drawer close icon placement default value 2025-02-28 14:54:11 +08:00
Netfan
0e0661fe02 fix: breadcrumb style is affected by the globally-imported antd styles (#5627)
* 修复全局引入Antd时,面包屑的样式会受到影响的问题
2025-02-27 22:28:59 +08:00
Netfan
86ce65e0ea fix: hideChildrenInMenu demo code (#5626) 2025-02-27 20:21:48 +08:00
Netfan
c3eb4fab13 docs: fix zod rules docs 2025-02-27 17:27:00 +08:00
jinmao88
7a476372e1 fix: useDrawer中closeIconPlacement设置无效 (#5624) 2025-02-27 14:34:42 +08:00
Netfan
5e421ce607 chore: demo page menu management (#5619)
* 添加菜单管理演示页面
2025-02-27 01:22:25 +08:00
Netfan
1d8676f456 chore: remove sleep in department list api 2025-02-25 22:15:27 +08:00
Netfan
0c3dd92592 fix: getPopupContainer will return closet form first (#5612) 2025-02-25 22:07:56 +08:00
Netfan
d33261d0c2 chore: demo page for system/department (#5611)
* feat: department management demo

* perf: department page improve

* feat: demo api middleware

* fix: add losing import
2025-02-25 19:47:45 +08:00
Netfan
7041c6a106 chore: output console error for invalid route component (#5593) 2025-02-24 16:03:52 +08:00
littlesparklet
12ffb310bf fix: Fix inconsistent spacing around search form (issue #5429) (#5495) 2025-02-24 15:57:50 +08:00
Netfan
d9799fec70 fix: search take no effect in icon-picker with antd (#5592) 2025-02-24 14:13:53 +08:00
Netfan
4570d5b54b feat: add VbenButtonGroup and VbenCheckButtonGroup with demo (#5591)
* 添加按钮组、选择按钮组以及相应的Demo
2025-02-24 13:50:50 +08:00
Netfan
d49e3e81a4 fix: loading and spinner style fixed and improved (#5588) 2025-02-23 15:30:17 +08:00
Netfan
579b1b486c feat: loading and spinner component with directive (#5587)
* 添加loading和spinner组件,以及对应的vue指令
2025-02-23 12:41:54 +08:00
Netfan
eba372062e feat: improve form demo (#5582) 2025-02-21 12:07:32 +08:00
Netfan
c9ccd2bbab fix: form label and control style (#5580)
* fix: form label and control style

* fix: empty label mark with required rules
2025-02-21 11:14:59 +08:00
handsomeFu
5aff8bac10 fix: CountTo component resolve separator prop not taking effect (#5578) 2025-02-21 11:06:18 +08:00
Netfan
1a12687027 fix: vben count to animator event name fixed (#5573) 2025-02-20 23:53:47 +08:00
Netfan
a221d2b491 fix: form item overflow fixed and layout improved (#5572)
* fix: form item overflow fixed and layout improved

* fix: basic form demo update

* feat: form label support render

* fix: form docs update
2025-02-20 23:05:08 +08:00
anyup
ccd99eb24d fix: solve the problem of inconsistent returns of formSchema custom field names when code login (#5563) 2025-02-20 09:09:32 +08:00
Netfan
c5c6760b5d chore: eslint rules update 2025-02-18 15:41:16 +08:00
Netfan
c07281bf41 fix: form item slot context fixed (#5552)
* 修复表单插槽
2025-02-17 21:37:05 +08:00
Netfan
24bad09c74 refactor: new CountTo component with demo (#5551) 2025-02-17 21:16:10 +08:00
Netfan
cddf71e600 fix: playground route missing 2025-02-17 17:57:15 +08:00
Netfan
9f82052c71 feat: demo of motion plugin (#5550)
添加Motion的用法例子
2025-02-17 15:25:45 +08:00
Netfan
e0eb57d38d fix: nitro server cors support with cookie (#5549)
* 修复nitro server在使用cookie时的跨域配置
2025-02-17 15:17:31 +08:00
Netfan
b6b97accb1 feat: add more event for jsonViewer (#5546)
* 为JsonViewer添加事件支持
2025-02-17 10:41:09 +08:00
Netfan
799934171a style: code style fixed 2025-02-16 23:32:06 +08:00
Netfan
10ebf03698 fix: auth api definition 2025-02-16 23:06:20 +08:00
Netfan
cd258fbb52 chore: update deps 2025-02-16 23:03:41 +08:00
Netfan
6cba181fad feat: new component jsonViewer (#5544)
* 添加新组件JsonViewer用于展示JSON结构数据
2025-02-16 22:57:00 +08:00
jinmao88
f9504cece3 chore: add qq group 5 (#5530) 2025-02-13 14:45:51 +08:00
Netfan
182f1c9da8 fix: userDropdown triggered unnecessary while overlay shown (#5520)
* 修复顶部的用户资料下拉在弹窗被打开时,仍然可以被触发的问题
2025-02-12 17:59:59 +08:00
Netfan
e7b009786b fix: width for ellipsisText tooltip in popover content (#5517)
* 修复省略文本用在气泡中时,提示弹出层的宽度计算有误的问题
2025-02-12 14:25:12 +08:00
Netfan
5262233312 feat: tabbar support max count limit (#5490)
* 标签栏支持限制打开的最大数量
2025-02-06 19:33:10 +08:00
Netfan
a9f9031f49 docs: update form docs (#5485) 2025-02-06 09:45:28 +08:00
Netfan
061fcf926d chore: update deps 2025-02-04 17:14:14 +08:00
Netfan
7e7a5f3fd4 chore: remove testing code 2025-02-04 15:07:10 +08:00
Netfan
f8bb396dc4 fix: ant tag icon default style (#5473) 2025-02-04 10:19:39 +08:00
jsxz
a832edce0d docs: update request and access docs (#5468)
* fix: Update server.md

* docs: update request and access docs
2025-02-03 16:29:34 +08:00
Netfan
67d1f299b3 fix: renderComponentContent lose slot props data (#5466)
* 修复FormItem传递插槽时丢失插槽props的问题
2025-01-26 22:33:16 +08:00
Netfan
cb7c0ecaa2 fix: menu data for backend mode fixed (#5465) 2025-01-26 20:37:37 +08:00
Netfan
e225159cce fix: request download and upload not support responseReturn (#5456)
* fix: request download and upload not support `responseReturn`

* docs: update

* fix: type of request client upload result
2025-01-22 00:59:10 +08:00
433 changed files with 22344 additions and 7593 deletions

18
.github/CODEOWNERS vendored
View File

@@ -1,14 +1,14 @@
# default onwer
* anncwb@126.com vince292007@gmail.com netfan@foxmail.com
* anncwb@126.com vince292007@gmail.com netfan@foxmail.com jinmao88@qq.com
# vben core onwer
/.github/ anncwb@126.com vince292007@gmail.com netfan@foxmail.com
/.vscode/ anncwb@126.com vince292007@gmail.com netfan@foxmail.com
/packages/ anncwb@126.com vince292007@gmail.com netfan@foxmail.com
/packages/@core/ anncwb@126.com vince292007@gmail.com netfan@foxmail.com
/internal/ anncwb@126.com vince292007@gmail.com netfan@foxmail.com
/scripts/ anncwb@126.com vince292007@gmail.com netfan@foxmail.com
/.github/ anncwb@126.com vince292007@gmail.com netfan@foxmail.com jinmao88@qq.com
/.vscode/ anncwb@126.com vince292007@gmail.com netfan@foxmail.com jinmao88@qq.com
/packages/ anncwb@126.com vince292007@gmail.com netfan@foxmail.com jinmao88@qq.com
/packages/@core/ anncwb@126.com vince292007@gmail.com netfan@foxmail.com jinmao88@qq.com
/internal/ anncwb@126.com vince292007@gmail.com netfan@foxmail.com jinmao88@qq.com
/scripts/ anncwb@126.com vince292007@gmail.com netfan@foxmail.com jinmao88@qq.com
# vben team onwer
apps/ anncwb@126.com vince292007@gmail.com netfan@foxmail.com @vbenjs/team-v5
docs/ anncwb@126.com vince292007@gmail.com netfan@foxmail.com @vbenjs/team-v5
apps/ anncwb@126.com vince292007@gmail.com netfan@foxmail.com @vbenjs/team-v5 jinmao88@qq.com
docs/ anncwb@126.com vince292007@gmail.com netfan@foxmail.com @vbenjs/team-v5 jinmao88@qq.com

View File

@@ -19,11 +19,9 @@ Project maintainers have the right and responsibility to remove, edit, or reject
- Checkout a topic branch from the relevant branch, e.g. main, and merge back against that branch.
- If adding a new feature:
- Provide a convincing reason to add this feature. Ideally, you should open a suggestion issue first and have it approved before working on it.
- If fixing bug:
- Provide a detailed description of the bug in the PR. Live demo preferred.
- It's OK to have multiple small commits as you work on the PR - GitHub can automatically squash them before merging.

1
.gitignore vendored
View File

@@ -49,3 +49,4 @@ vite.config.ts.*
*.sln
*.sw?
.history
.cursor

View File

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

View File

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

View File

@@ -1 +1 @@
20.14.0
22.1.0

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

20
.vscode/settings.json vendored
View File

@@ -14,7 +14,7 @@
"editor.tabSize": 2,
"editor.detectIndentation": false,
"editor.cursorBlinking": "expand",
"editor.largeFileOptimizations": false,
"editor.largeFileOptimizations": true,
"editor.accessibilitySupport": "off",
"editor.cursorSmoothCaretAnimation": "on",
"editor.guides.bracketPairs": "active",
@@ -91,6 +91,7 @@
"**/bower_components": true,
"**/.turbo": true,
"**/.idea": true,
"**/.vitepress": true,
"**/tmp": true,
"**/.git": true,
"**/.svn": true,
@@ -112,6 +113,8 @@
"**/yarn.lock": true
},
"typescript.tsserver.exclude": ["**/node_modules", "**/dist", "**/.turbo"],
// search
"search.searchEditor.singleClickBehaviour": "peekDefinition",
"search.followSymlinks": false,
@@ -216,12 +219,23 @@
"*.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,
"commentTranslate.multiLineMerge": true,
"vue.server.hybridMode": true,
"typescript.tsdk": "node_modules/typescript/lib",
"oxc.enable": false
"oxc.enable": false,
"cSpell.words": [
"archiver",
"axios",
"dotenv",
"isequal",
"jspm",
"napi",
"nolebase",
"rollup",
"vitest"
]
}

View File

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

View File

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

View File

@@ -1,8 +1,13 @@
<div align="center"> <a href="https://github.com/anncwb/vue-vben-admin"> <img alt="VbenAdmin Logo" width="215" src="https://unpkg.com/@vbenjs/static-source@0.1.7/source/logo-v1.webp"> </a> <br> <br>
<div align="center">
<a href="https://github.com/anncwb/vue-vben-admin">
<img alt="VbenAdmin Logo" width="215" src="https://unpkg.com/@vbenjs/static-source@0.1.7/source/logo-v1.webp">
</a>
<br>
<br>
[![license](https://img.shields.io/github/license/anncwb/vue-vben-admin.svg)](LICENSE)
<h1>Vue Vben Admin</h1>
<h1>Vue Vben Admin</h1>
</div>
[![Quality Gate Status](https://sonarcloud.io/api/project_badges/measure?project=vbenjs_vue-vben-admin&metric=alert_status)](https://sonarcloud.io/summary/new_code?id=vbenjs_vue-vben-admin) ![codeql](https://github.com/vbenjs/vue-vben-admin/actions/workflows/codeql.yml/badge.svg) ![build](https://github.com/vbenjs/vue-vben-admin/actions/workflows/build.yml/badge.svg) ![ci](https://github.com/vbenjs/vue-vben-admin/actions/workflows/ci.yml/badge.svg) ![deploy](https://github.com/vbenjs/vue-vben-admin/actions/workflows/deploy.yml/badge.svg)
@@ -15,31 +20,31 @@ Vue Vben Admin 是 Vue Vben Admin 的升级版本。作为一个免费开源的
## 升级提示
该版本为最新版本`5.0`, 与其他版本不兼容,如果你是新项目,建议使用最新版本。如果你想查看旧版本,请使用 [v2 分支](https://github.com/vbenjs/vue-vben-admin/tree/v2)
该版本为最新版本 `5.0`与其他版本不兼容,如果你是新项目,建议使用最新版本。如果你想查看旧版本,请使用 [v2 分支](https://github.com/vbenjs/vue-vben-admin/tree/v2)
## 特性
- **最新技术栈**:使用 Vue3/vite 等前端前沿技术开发
- **TypeScript**: 应用程序级 JavaScript 的语言
- **TypeScript**应用程序级 JavaScript 的语言
- **主题**:提供多套主题色彩,可配置自定义主题
- **国际化**:内置完善的国际化方案
- **权限** 内置完善的动态路由权限生成方案
- **权限**内置完善的动态路由权限生成方案
## 预览
- [Vben Admin](https://vben.pro/) - 完整版中文站点
测试账号: vben/123456
测试账号vben/123456
<p align="center">
<img alt="VbenAdmin Logo" width="100%" src="https://anncwb.github.io/anncwb/images/preview1.png">
<img alt="VbenAdmin Logo" width="100%" src="https://anncwb.github.io/anncwb/images/preview2.png">
<img alt="VbenAdmin Logo" width="100%" src="https://anncwb.github.io/anncwb/images/preview3.png">
</p>
<div align="center">
<img alt="VbenAdmin Logo" width="100%" src="https://anncwb.github.io/anncwb/images/preview1.png">
<img alt="VbenAdmin Logo" width="100%" src="https://anncwb.github.io/anncwb/images/preview2.png">
<img alt="VbenAdmin Logo" width="100%" src="https://anncwb.github.io/anncwb/images/preview3.png">
</div>
### 使用 Gitpod
在 Gitpod适用于 GitHub 的免费在线开发环境)中打开项目,并立即开始编码.
在 Gitpod适用于 GitHub 的免费在线开发环境)中打开项目,并立即开始编码
[![Open in Gitpod](https://gitpod.io/button/open-in-gitpod.svg)](https://gitpod.io/#https://github.com/vbenjs/vue-vben-admin)
@@ -49,29 +54,27 @@ Vue Vben Admin 是 Vue Vben Admin 的升级版本。作为一个免费开源的
## 安装使用
- 获取项目代码
1. 获取项目代码
```bash
git clone https://github.com/vbenjs/vue-vben-admin.git
```
- 安装依赖
2. 安装依赖
```bash
cd vue-vben-admin
corepack enable
npm i -g corepack
pnpm install
```
- 运行
3. 运行
```bash
pnpm dev
```
- 打包
4. 打包
```bash
pnpm build
@@ -85,68 +88,70 @@ pnpm build
非常欢迎你的加入![提一个 Issue](https://github.com/anncwb/vue-vben-admin/issues/new/choose) 或者提交一个 Pull Request。
**Pull Request:**
**Pull Request 流程:**
1. Fork 代码!
2. 创建自己的分支: `git checkout -b feature/xxxx`
3. 提交你的修改: `git commit -am 'feat(function): add xxxxx'`
4. 推送您的分支: `git push origin feature/xxxx`
5. 提交`pull request`
1. Fork 代码
2. 创建自己的分支`git checkout -b feature/xxxx`
3. 提交你的修改`git commit -am 'feat(function): add xxxxx'`
4. 推送您的分支`git push origin feature/xxxx`
5. 提交 `pull request`
## Git 贡献提交规范
- 参考 [vue](https://github.com/vuejs/vue/blob/dev/.github/COMMIT_CONVENTION.md) 规范 ([Angular](https://github.com/conventional-changelog/conventional-changelog/tree/master/packages/conventional-changelog-angular))
参考 [vue](https://github.com/vuejs/vue/blob/dev/.github/COMMIT_CONVENTION.md) 规范 ([Angular](https://github.com/conventional-changelog/conventional-changelog/tree/master/packages/conventional-changelog-angular))
- `feat` 增加新功能
- `fix` 修复问题/BUG
- `style` 代码风格相关无影响运行结果的
- `perf` 优化/性能提升
- `refactor` 重构
- `revert` 撤销修改
- `test` 测试相关
- `docs` 文档/注释
- `chore` 依赖更新/脚手架配置修改等
- `ci` 持续集成
- `types` 类型定义文件更改
- `wip` 开发中
- `feat` 增加新功能
- `fix` 修复问题/BUG
- `style` 代码风格相关无影响运行结果的
- `perf` 优化/性能提升
- `refactor` 重构
- `revert` 撤销修改
- `test` 测试相关
- `docs` 文档/注释
- `chore` 依赖更新/脚手架配置修改等
- `ci` 持续集成
- `types` 类型定义文件更改
## 浏览器支持
本地开发推荐使用`Chrome 80+` 浏览器
本地开发推荐使用 `Chrome 80+` 浏览器
支持现代浏览器, 不支持 IE
支持现代浏览器不支持 IE
| [<img src="https://raw.githubusercontent.com/alrra/browser-logos/master/src/edge/edge_48x48.png" alt=" Edge" width="24px" height="24px" />](http://godban.github.io/browsers-support-badges/)</br>IE | [<img src="https://raw.githubusercontent.com/alrra/browser-logos/master/src/edge/edge_48x48.png" alt=" Edge" width="24px" height="24px" />](http://godban.github.io/browsers-support-badges/)</br>Edge | [<img src="https://raw.githubusercontent.com/alrra/browser-logos/master/src/firefox/firefox_48x48.png" alt="Firefox" width="24px" height="24px" />](http://godban.github.io/browsers-support-badges/)</br>Firefox | [<img src="https://raw.githubusercontent.com/alrra/browser-logos/master/src/chrome/chrome_48x48.png" alt="Chrome" width="24px" height="24px" />](http://godban.github.io/browsers-support-badges/)</br>Chrome | [<img src="https://raw.githubusercontent.com/alrra/browser-logos/master/src/safari/safari_48x48.png" alt="Safari" width="24px" height="24px" />](http://godban.github.io/browsers-support-badges/)</br>Safari |
| :-: | :-: | :-: | :-: | :-: |
| not support | last 2 versions | last 2 versions | last 2 versions | last 2 versions |
| [<img src="https://raw.githubusercontent.com/alrra/browser-logos/master/src/edge/edge_48x48.png" alt="Edge" width="24px" height="24px" />](http://godban.github.io/browsers-support-badges/)</br>Edge | [<img src="https://raw.githubusercontent.com/alrra/browser-logos/master/src/firefox/firefox_48x48.png" alt="Firefox" width="24px" height="24px" />](http://godban.github.io/browsers-support-badges/)</br>Firefox | [<img src="https://raw.githubusercontent.com/alrra/browser-logos/master/src/chrome/chrome_48x48.png" alt="Chrome" width="24px" height="24px" />](http://godban.github.io/browsers-support-badges/)</br>Chrome | [<img src="https://raw.githubusercontent.com/alrra/browser-logos/master/src/safari/safari_48x48.png" alt="Safari" width="24px" height="24px" />](http://godban.github.io/browsers-support-badges/)</br>Safari |
| :-: | :-: | :-: | :-: |
| last 2 versions | last 2 versions | last 2 versions | last 2 versions |
## 维护者
[@Vben](https://github.com/anncwb)
## Star History
## Star 历史
[![Star History Chart](https://api.star-history.com/svg?repos=vbenjs/vue-vben-admin&type=Date)](https://star-history.com/#vbenjs/vue-vben-admin&Date)
## 捐赠
如果你觉得这个项目对你有帮助,你可以帮作者买一杯咖啡表示支持!
如果你觉得这个项目对你有帮助,你可以帮作者买一杯咖啡表示支持
![donate](https://unpkg.com/@vbenjs/static-source@0.1.7/source/sponsor.png)
<a style="display: block;width: 100px;height: 50px;line-height: 50px; color: #fff;text-align: center; background: #408aed;border-radius: 4px;" href="https://www.paypal.com/paypalme/cvvben">Paypal Me</a>
## Contributor
## 贡献者
<a href="https://openomy.app/github/vbenjs/vue-vben-admin" target="_blank" style="display: block; width: 100%;" align="center">
<img src="https://openomy.app/svg?repo=vbenjs/vue-vben-admin&chart=bubble&latestMonth=3" target="_blank" alt="Contribution Leaderboard" style="display: block; width: 100%;" />
</a>
<a href="https://github.com/vbenjs/vue-vben-admin/graphs/contributors">
<img alt="Contributors"
src="https://opencollective.com/vbenjs/contributors.svg?button=false" />
<img alt="Contributors" src="https://contrib.rocks/image?repo=vbenjs/vue-vben-admin" />
</a>
## Discord
- [Github Discussions](https://github.com/anncwb/vue-vben-admin/discussions)
## License
## 许可证
[MIT © Vben-2020](./LICENSE)

View File

@@ -1,5 +1,7 @@
import { eventHandler } from 'h3';
import { verifyAccessToken } from '~/utils/jwt-utils';
import { unAuthorizedResponse } from '~/utils/response';
import { MOCK_CODES } from '~/utils/mock-data';
import { unAuthorizedResponse, useResponseSuccess } from '~/utils/response';
export default eventHandler((event) => {
const userinfo = verifyAccessToken(event);

View File

@@ -1,9 +1,15 @@
import { defineEventHandler, readBody, setResponseStatus } from 'h3';
import {
clearRefreshTokenCookie,
setRefreshTokenCookie,
} from '~/utils/cookie-utils';
import { generateAccessToken, generateRefreshToken } from '~/utils/jwt-utils';
import { forbiddenResponse } from '~/utils/response';
import { MOCK_USERS } from '~/utils/mock-data';
import {
forbiddenResponse,
useResponseError,
useResponseSuccess,
} from '~/utils/response';
export default defineEventHandler(async (event) => {
const { password, username } = await readBody(event);

View File

@@ -1,7 +1,9 @@
import { defineEventHandler } from 'h3';
import {
clearRefreshTokenCookie,
getRefreshTokenFromCookie,
} from '~/utils/cookie-utils';
import { useResponseSuccess } from '~/utils/response';
export default defineEventHandler(async (event) => {
const refreshToken = getRefreshTokenFromCookie(event);

View File

@@ -1,9 +1,11 @@
import { defineEventHandler } from 'h3';
import {
clearRefreshTokenCookie,
getRefreshTokenFromCookie,
setRefreshTokenCookie,
} from '~/utils/cookie-utils';
import { verifyRefreshToken } from '~/utils/jwt-utils';
import { generateAccessToken, verifyRefreshToken } from '~/utils/jwt-utils';
import { MOCK_USERS } from '~/utils/mock-data';
import { forbiddenResponse } from '~/utils/response';
export default defineEventHandler(async (event) => {

View File

@@ -0,0 +1,32 @@
import { eventHandler, setHeader } from 'h3';
import { verifyAccessToken } from '~/utils/jwt-utils';
import { unAuthorizedResponse } from '~/utils/response';
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,5 +1,7 @@
import { eventHandler } from 'h3';
import { verifyAccessToken } from '~/utils/jwt-utils';
import { unAuthorizedResponse } from '~/utils/response';
import { MOCK_MENUS } from '~/utils/mock-data';
import { unAuthorizedResponse, useResponseSuccess } from '~/utils/response';
export default eventHandler(async (event) => {
const userinfo = verifyAccessToken(event);

View File

@@ -1,3 +1,6 @@
import { eventHandler, getQuery, setResponseStatus } from 'h3';
import { useResponseError } from '~/utils/response';
export default eventHandler((event) => {
const { status } = getQuery(event);
setResponseStatus(event, Number(status));

View File

@@ -0,0 +1,16 @@
import { eventHandler } from 'h3';
import { verifyAccessToken } from '~/utils/jwt-utils';
import {
sleep,
unAuthorizedResponse,
useResponseSuccess,
} from '~/utils/response';
export default eventHandler(async (event) => {
const userinfo = verifyAccessToken(event);
if (!userinfo) {
return unAuthorizedResponse(event);
}
await sleep(600);
return useResponseSuccess(null);
});

View File

@@ -0,0 +1,16 @@
import { eventHandler } from 'h3';
import { verifyAccessToken } from '~/utils/jwt-utils';
import {
sleep,
unAuthorizedResponse,
useResponseSuccess,
} from '~/utils/response';
export default eventHandler(async (event) => {
const userinfo = verifyAccessToken(event);
if (!userinfo) {
return unAuthorizedResponse(event);
}
await sleep(1000);
return useResponseSuccess(null);
});

View File

@@ -0,0 +1,16 @@
import { eventHandler } from 'h3';
import { verifyAccessToken } from '~/utils/jwt-utils';
import {
sleep,
unAuthorizedResponse,
useResponseSuccess,
} from '~/utils/response';
export default eventHandler(async (event) => {
const userinfo = verifyAccessToken(event);
if (!userinfo) {
return unAuthorizedResponse(event);
}
await sleep(2000);
return useResponseSuccess(null);
});

View File

@@ -0,0 +1,62 @@
import { faker } from '@faker-js/faker';
import { eventHandler } from 'h3';
import { verifyAccessToken } from '~/utils/jwt-utils';
import { unAuthorizedResponse, useResponseSuccess } from '~/utils/response';
const formatterCN = new Intl.DateTimeFormat('zh-CN', {
timeZone: 'Asia/Shanghai',
year: 'numeric',
month: '2-digit',
day: '2-digit',
hour: '2-digit',
minute: '2-digit',
second: '2-digit',
});
function generateMockDataList(count: number) {
const dataList = [];
for (let i = 0; i < count; i++) {
const dataItem: Record<string, any> = {
id: faker.string.uuid(),
pid: 0,
name: faker.commerce.department(),
status: faker.helpers.arrayElement([0, 1]),
createTime: formatterCN.format(
faker.date.between({ from: '2021-01-01', to: '2022-12-31' }),
),
remark: faker.lorem.sentence(),
};
if (faker.datatype.boolean()) {
dataItem.children = Array.from(
{ length: faker.number.int({ min: 1, max: 5 }) },
() => ({
id: faker.string.uuid(),
pid: dataItem.id,
name: faker.commerce.department(),
status: faker.helpers.arrayElement([0, 1]),
createTime: formatterCN.format(
faker.date.between({ from: '2023-01-01', to: '2023-12-31' }),
),
remark: faker.lorem.sentence(),
}),
);
}
dataList.push(dataItem);
}
return dataList;
}
const mockData = generateMockDataList(10);
export default eventHandler(async (event) => {
const userinfo = verifyAccessToken(event);
if (!userinfo) {
return unAuthorizedResponse(event);
}
const listData = structuredClone(mockData);
return useResponseSuccess(listData);
});

View File

@@ -0,0 +1,13 @@
import { eventHandler } from 'h3';
import { verifyAccessToken } from '~/utils/jwt-utils';
import { MOCK_MENU_LIST } from '~/utils/mock-data';
import { unAuthorizedResponse, useResponseSuccess } from '~/utils/response';
export default eventHandler(async (event) => {
const userinfo = verifyAccessToken(event);
if (!userinfo) {
return unAuthorizedResponse(event);
}
return useResponseSuccess(MOCK_MENU_LIST);
});

View File

@@ -0,0 +1,29 @@
import { eventHandler, getQuery } from 'h3';
import { verifyAccessToken } from '~/utils/jwt-utils';
import { MOCK_MENU_LIST } from '~/utils/mock-data';
import { unAuthorizedResponse, useResponseSuccess } from '~/utils/response';
const namesMap: Record<string, any> = {};
function getNames(menus: any[]) {
menus.forEach((menu) => {
namesMap[menu.name] = String(menu.id);
if (menu.children) {
getNames(menu.children);
}
});
}
getNames(MOCK_MENU_LIST);
export default eventHandler(async (event) => {
const userinfo = verifyAccessToken(event);
if (!userinfo) {
return unAuthorizedResponse(event);
}
const { id, name } = getQuery(event);
return (name as string) in namesMap &&
(!id || namesMap[name as string] !== String(id))
? useResponseSuccess(true)
: useResponseSuccess(false);
});

View File

@@ -0,0 +1,29 @@
import { eventHandler, getQuery } from 'h3';
import { verifyAccessToken } from '~/utils/jwt-utils';
import { MOCK_MENU_LIST } from '~/utils/mock-data';
import { unAuthorizedResponse, useResponseSuccess } from '~/utils/response';
const pathMap: Record<string, any> = { '/': 0 };
function getPaths(menus: any[]) {
menus.forEach((menu) => {
pathMap[menu.path] = String(menu.id);
if (menu.children) {
getPaths(menu.children);
}
});
}
getPaths(MOCK_MENU_LIST);
export default eventHandler(async (event) => {
const userinfo = verifyAccessToken(event);
if (!userinfo) {
return unAuthorizedResponse(event);
}
const { id, path } = getQuery(event);
return (path as string) in pathMap &&
(!id || pathMap[path as string] !== String(id))
? useResponseSuccess(true)
: useResponseSuccess(false);
});

View File

@@ -0,0 +1,84 @@
import { faker } from '@faker-js/faker';
import { eventHandler, getQuery } from 'h3';
import { verifyAccessToken } from '~/utils/jwt-utils';
import { getMenuIds, MOCK_MENU_LIST } from '~/utils/mock-data';
import { unAuthorizedResponse, usePageResponseSuccess } from '~/utils/response';
const formatterCN = new Intl.DateTimeFormat('zh-CN', {
timeZone: 'Asia/Shanghai',
year: 'numeric',
month: '2-digit',
day: '2-digit',
hour: '2-digit',
minute: '2-digit',
second: '2-digit',
});
const menuIds = getMenuIds(MOCK_MENU_LIST);
function generateMockDataList(count: number) {
const dataList = [];
for (let i = 0; i < count; i++) {
const dataItem: Record<string, any> = {
id: faker.string.uuid(),
name: faker.commerce.product(),
status: faker.helpers.arrayElement([0, 1]),
createTime: formatterCN.format(
faker.date.between({ from: '2022-01-01', to: '2025-01-01' }),
),
permissions: faker.helpers.arrayElements(menuIds),
remark: faker.lorem.sentence(),
};
dataList.push(dataItem);
}
return dataList;
}
const mockData = generateMockDataList(100);
export default eventHandler(async (event) => {
const userinfo = verifyAccessToken(event);
if (!userinfo) {
return unAuthorizedResponse(event);
}
const {
page = 1,
pageSize = 20,
name,
id,
remark,
startTime,
endTime,
status,
} = getQuery(event);
let listData = structuredClone(mockData);
if (name) {
listData = listData.filter((item) =>
item.name.toLowerCase().includes(String(name).toLowerCase()),
);
}
if (id) {
listData = listData.filter((item) =>
item.id.toLowerCase().includes(String(id).toLowerCase()),
);
}
if (remark) {
listData = listData.filter((item) =>
item.remark?.toLowerCase()?.includes(String(remark).toLowerCase()),
);
}
if (startTime) {
listData = listData.filter((item) => item.createTime >= startTime);
}
if (endTime) {
listData = listData.filter((item) => item.createTime <= endTime);
}
if (['0', '1'].includes(status as string)) {
listData = listData.filter((item) => item.status === Number(status));
}
return usePageResponseSuccess(page as string, pageSize as string, listData);
});

View File

@@ -1,6 +1,11 @@
import { faker } from '@faker-js/faker';
import { eventHandler, getQuery } from 'h3';
import { verifyAccessToken } from '~/utils/jwt-utils';
import { unAuthorizedResponse } from '~/utils/response';
import {
sleep,
unAuthorizedResponse,
usePageResponseSuccess,
} from '~/utils/response';
function generateMockDataList(count: number) {
const dataList = [];
@@ -44,30 +49,69 @@ export default eventHandler(async (event) => {
await sleep(600);
const { page, pageSize, sortBy, sortOrder } = getQuery(event);
// 规范化分页参数,处理 string[]
const pageRaw = Array.isArray(page) ? page[0] : page;
const pageSizeRaw = Array.isArray(pageSize) ? pageSize[0] : pageSize;
const pageNumber = Math.max(
1,
Number.parseInt(String(pageRaw ?? '1'), 10) || 1,
);
const pageSizeNumber = Math.min(
100,
Math.max(1, Number.parseInt(String(pageSizeRaw ?? '10'), 10) || 10),
);
const listData = structuredClone(mockData);
if (sortBy && Reflect.has(listData[0], sortBy as string)) {
// 规范化 query 入参,兼容 string[]
const sortKeyRaw = Array.isArray(sortBy) ? sortBy[0] : sortBy;
const sortOrderRaw = Array.isArray(sortOrder) ? sortOrder[0] : sortOrder;
// 检查 sortBy 是否是 listData 元素的合法属性键
if (
typeof sortKeyRaw === 'string' &&
listData[0] &&
Object.prototype.hasOwnProperty.call(listData[0], sortKeyRaw)
) {
// 定义数组元素的类型
type ItemType = (typeof listData)[0];
const sortKey = sortKeyRaw as keyof ItemType; // 将 sortBy 断言为合法键
const isDesc = sortOrderRaw === 'desc';
listData.sort((a, b) => {
if (sortOrder === 'asc') {
if (sortBy === 'price') {
return (
Number.parseFloat(a[sortBy as string]) -
Number.parseFloat(b[sortBy as string])
);
const aValue = a[sortKey] as unknown;
const bValue = b[sortKey] as unknown;
let result = 0;
if (typeof aValue === 'number' && typeof bValue === 'number') {
result = aValue - bValue;
} else if (aValue instanceof Date && bValue instanceof Date) {
result = aValue.getTime() - bValue.getTime();
} else if (typeof aValue === 'boolean' && typeof bValue === 'boolean') {
if (aValue === bValue) {
result = 0;
} else {
return a[sortBy as string] > b[sortBy as string] ? 1 : -1;
result = aValue ? 1 : -1;
}
} else {
if (sortBy === 'price') {
return (
Number.parseFloat(b[sortBy as string]) -
Number.parseFloat(a[sortBy as string])
);
} else {
return a[sortBy as string] < b[sortBy as string] ? 1 : -1;
}
const aStr = String(aValue);
const bStr = String(bValue);
const aNum = Number(aStr);
const bNum = Number(bStr);
result =
Number.isFinite(aNum) && Number.isFinite(bNum)
? aNum - bNum
: aStr.localeCompare(bStr, undefined, {
numeric: true,
sensitivity: 'base',
});
}
return isDesc ? -result : result;
});
}
return usePageResponseSuccess(page as string, pageSize as string, listData);
return usePageResponseSuccess(
String(pageNumber),
String(pageSizeNumber),
listData,
);
});

View File

@@ -1 +1,3 @@
import { defineEventHandler } from 'h3';
export default defineEventHandler(() => 'Test get handler');

View File

@@ -1 +1,3 @@
import { defineEventHandler } from 'h3';
export default defineEventHandler(() => 'Test post handler');

View File

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

View File

@@ -1,5 +1,6 @@
import { eventHandler } from 'h3';
import { verifyAccessToken } from '~/utils/jwt-utils';
import { unAuthorizedResponse } from '~/utils/response';
import { unAuthorizedResponse, useResponseSuccess } from '~/utils/response';
export default eventHandler((event) => {
const userinfo = verifyAccessToken(event);

View File

@@ -1,7 +1,20 @@
export default defineEventHandler((event) => {
import { defineEventHandler } from 'h3';
import { forbiddenResponse, sleep } from '~/utils/response';
export default defineEventHandler(async (event) => {
event.node.res.setHeader(
'Access-Control-Allow-Origin',
event.headers.get('Origin') ?? '*',
);
if (event.method === 'OPTIONS') {
event.node.res.statusCode = 204;
event.node.res.statusMessage = 'No Content.';
return 'OK';
} else if (
['DELETE', 'PATCH', 'POST', 'PUT'].includes(event.method) &&
event.path.startsWith('/api/system/')
) {
await sleep(Math.floor(Math.random() * 2000));
return forbiddenResponse(event, '演示环境,禁止修改');
}
});

View File

@@ -9,7 +9,8 @@ export default defineNitroConfig({
cors: true,
headers: {
'Access-Control-Allow-Credentials': 'true',
'Access-Control-Allow-Headers': '*',
'Access-Control-Allow-Headers':
'Accept, Authorization, Content-Length, Content-Type, If-Match, If-Modified-Since, If-None-Match, If-Unmodified-Since, X-CSRF-TOKEN, X-Requested-With',
'Access-Control-Allow-Methods': 'GET,HEAD,PUT,PATCH,POST,DELETE',
'Access-Control-Allow-Origin': '*',
'Access-Control-Expose-Headers': '*',

View File

@@ -1,3 +1,5 @@
import { defineEventHandler } from 'h3';
export default defineEventHandler(() => {
return `
<h1>Hello Vben Admin</h1>
@@ -7,6 +9,7 @@ export default defineEventHandler(() => {
<li><a href="/api/menu">/api/menu/all</a></li>
<li><a href="/api/auth/codes">/api/auth/codes</a></li>
<li><a href="/api/auth/login">/api/auth/login</a></li>
<li><a href="/api/upload">/api/upload</a></li>
</ul>
`;
});

View File

@@ -1,5 +1,7 @@
import type { EventHandlerRequest, H3Event } from 'h3';
import { deleteCookie, getCookie, setCookie } from 'h3';
export function clearRefreshTokenCookie(event: H3Event<EventHandlerRequest>) {
deleteCookie(event, 'jwt', {
httpOnly: true,
@@ -14,7 +16,7 @@ export function setRefreshTokenCookie(
) {
setCookie(event, 'jwt', refreshToken, {
httpOnly: true,
maxAge: 24 * 60 * 60 * 1000,
maxAge: 24 * 60 * 60, // unit: seconds
sameSite: 'none',
secure: true,
});

View File

@@ -1,8 +1,11 @@
import type { EventHandlerRequest, H3Event } from 'h3';
import type { UserInfo } from './mock-data';
import { getHeader } from 'h3';
import jwt from 'jsonwebtoken';
import { UserInfo } from './mock-data';
import { MOCK_USERS } from './mock-data';
// TODO: Replace with your own secret key
const ACCESS_TOKEN_SECRET = 'access_token_secret';
@@ -31,12 +34,22 @@ export function verifyAccessToken(
return null;
}
const token = authHeader.split(' ')[1];
const tokenParts = authHeader.split(' ');
if (tokenParts.length !== 2) {
return null;
}
const token = tokenParts[1] as string;
try {
const decoded = jwt.verify(token, ACCESS_TOKEN_SECRET) as UserPayload;
const decoded = jwt.verify(
token,
ACCESS_TOKEN_SECRET,
) as unknown as UserPayload;
const username = decoded.username;
const user = MOCK_USERS.find((item) => item.username === username);
if (!user) {
return null;
}
const { password: _pwd, ...userinfo } = user;
return userinfo;
} catch {
@@ -50,7 +63,12 @@ export function verifyRefreshToken(
try {
const decoded = jwt.verify(token, REFRESH_TOKEN_SECRET) as UserPayload;
const username = decoded.username;
const user = MOCK_USERS.find((item) => item.username === username);
const user = MOCK_USERS.find(
(item) => item.username === username,
) as UserInfo;
if (!user) {
return null;
}
const { password: _pwd, ...userinfo } = user;
return userinfo;
} catch {

View File

@@ -58,7 +58,7 @@ const dashboardMenus = [
title: 'page.dashboard.title',
},
name: 'Dashboard',
path: '/',
path: '/dashboard',
redirect: '/analytics',
children: [
{
@@ -185,3 +185,206 @@ export const MOCK_MENUS = [
username: 'jack',
},
];
export const MOCK_MENU_LIST = [
{
id: 1,
name: 'Workspace',
status: 1,
type: 'menu',
icon: 'mdi:dashboard',
path: '/workspace',
component: '/dashboard/workspace/index',
meta: {
icon: 'carbon:workspace',
title: 'page.dashboard.workspace',
affixTab: true,
order: 0,
},
},
{
id: 2,
meta: {
icon: 'carbon:settings',
order: 9997,
title: 'system.title',
badge: 'new',
badgeType: 'normal',
badgeVariants: 'primary',
},
status: 1,
type: 'catalog',
name: 'System',
path: '/system',
children: [
{
id: 201,
pid: 2,
path: '/system/menu',
name: 'SystemMenu',
authCode: 'System:Menu:List',
status: 1,
type: 'menu',
meta: {
icon: 'carbon:menu',
title: 'system.menu.title',
},
component: '/system/menu/list',
children: [
{
id: 20_101,
pid: 201,
name: 'SystemMenuCreate',
status: 1,
type: 'button',
authCode: 'System:Menu:Create',
meta: { title: 'common.create' },
},
{
id: 20_102,
pid: 201,
name: 'SystemMenuEdit',
status: 1,
type: 'button',
authCode: 'System:Menu:Edit',
meta: { title: 'common.edit' },
},
{
id: 20_103,
pid: 201,
name: 'SystemMenuDelete',
status: 1,
type: 'button',
authCode: 'System:Menu:Delete',
meta: { title: 'common.delete' },
},
],
},
{
id: 202,
pid: 2,
path: '/system/dept',
name: 'SystemDept',
status: 1,
type: 'menu',
authCode: 'System:Dept:List',
meta: {
icon: 'carbon:container-services',
title: 'system.dept.title',
},
component: '/system/dept/list',
children: [
{
id: 20_401,
pid: 201,
name: 'SystemDeptCreate',
status: 1,
type: 'button',
authCode: 'System:Dept:Create',
meta: { title: 'common.create' },
},
{
id: 20_402,
pid: 201,
name: 'SystemDeptEdit',
status: 1,
type: 'button',
authCode: 'System:Dept:Edit',
meta: { title: 'common.edit' },
},
{
id: 20_403,
pid: 201,
name: 'SystemDeptDelete',
status: 1,
type: 'button',
authCode: 'System:Dept:Delete',
meta: { title: 'common.delete' },
},
],
},
],
},
{
id: 9,
meta: {
badgeType: 'dot',
order: 9998,
title: 'demos.vben.title',
icon: 'carbon:data-center',
},
name: 'Project',
path: '/vben-admin',
type: 'catalog',
status: 1,
children: [
{
id: 901,
pid: 9,
name: 'VbenDocument',
path: '/vben-admin/document',
component: 'IFrameView',
type: 'embedded',
status: 1,
meta: {
icon: 'carbon:book',
iframeSrc: 'https://doc.vben.pro',
title: 'demos.vben.document',
},
},
{
id: 902,
pid: 9,
name: 'VbenGithub',
path: '/vben-admin/github',
component: 'IFrameView',
type: 'link',
status: 1,
meta: {
icon: 'carbon:logo-github',
link: 'https://github.com/vbenjs/vue-vben-admin',
title: 'Github',
},
},
{
id: 903,
pid: 9,
name: 'VbenAntdv',
path: '/vben-admin/antdv',
component: 'IFrameView',
type: 'link',
status: 0,
meta: {
icon: 'carbon:hexagon-vertical-solid',
badgeType: 'dot',
link: 'https://ant.vben.pro',
title: 'demos.vben.antdv',
},
},
],
},
{
id: 10,
component: '_core/about/index',
type: 'menu',
status: 1,
meta: {
icon: 'lucide:copyright',
order: 9999,
title: 'demos.vben.about',
},
name: 'About',
path: '/about',
},
];
export function getMenuIds(menus: any[]) {
const ids: number[] = [];
menus.forEach((item) => {
ids.push(item.id);
if (item.children && item.children.length > 0) {
ids.push(...getMenuIds(item.children));
}
});
return ids;
}

View File

@@ -1,5 +1,7 @@
import type { EventHandlerRequest, H3Event } from 'h3';
import { setResponseStatus } from 'h3';
export function useResponseSuccess<T = any>(data: T) {
return {
code: 0,

View File

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

View File

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

View File

@@ -3,48 +3,96 @@
* 可用于 vben-form、vben-modal、vben-drawer 等组件使用,
*/
import type { Component, SetupContext } from 'vue';
import type { Component } from 'vue';
import type { BaseFormComponentType } from '@vben/common-ui';
import type { Recordable } from '@vben/types';
import { h } from 'vue';
import { defineAsyncComponent, defineComponent, h, ref } from 'vue';
import { ApiComponent, globalShareState, IconPicker } from '@vben/common-ui';
import { $t } from '@vben/locales';
import {
AutoComplete,
Button,
Checkbox,
CheckboxGroup,
DatePicker,
Divider,
Input,
InputNumber,
InputPassword,
Mentions,
notification,
Radio,
RadioGroup,
RangePicker,
Rate,
Select,
Space,
Switch,
Textarea,
TimePicker,
TreeSelect,
Upload,
} from 'ant-design-vue';
import { notification } from 'ant-design-vue';
const AutoComplete = defineAsyncComponent(
() => import('ant-design-vue/es/auto-complete'),
);
const Button = defineAsyncComponent(() => import('ant-design-vue/es/button'));
const Checkbox = defineAsyncComponent(
() => import('ant-design-vue/es/checkbox'),
);
const CheckboxGroup = defineAsyncComponent(() =>
import('ant-design-vue/es/checkbox').then((res) => res.CheckboxGroup),
);
const DatePicker = defineAsyncComponent(
() => import('ant-design-vue/es/date-picker'),
);
const Divider = defineAsyncComponent(() => import('ant-design-vue/es/divider'));
const Input = defineAsyncComponent(() => import('ant-design-vue/es/input'));
const InputNumber = defineAsyncComponent(
() => import('ant-design-vue/es/input-number'),
);
const InputPassword = defineAsyncComponent(() =>
import('ant-design-vue/es/input').then((res) => res.InputPassword),
);
const Mentions = defineAsyncComponent(
() => import('ant-design-vue/es/mentions'),
);
const Radio = defineAsyncComponent(() => import('ant-design-vue/es/radio'));
const RadioGroup = defineAsyncComponent(() =>
import('ant-design-vue/es/radio').then((res) => res.RadioGroup),
);
const RangePicker = defineAsyncComponent(() =>
import('ant-design-vue/es/date-picker').then((res) => res.RangePicker),
);
const Rate = defineAsyncComponent(() => import('ant-design-vue/es/rate'));
const Select = defineAsyncComponent(() => import('ant-design-vue/es/select'));
const Space = defineAsyncComponent(() => import('ant-design-vue/es/space'));
const Switch = defineAsyncComponent(() => import('ant-design-vue/es/switch'));
const Textarea = defineAsyncComponent(() =>
import('ant-design-vue/es/input').then((res) => res.Textarea),
);
const TimePicker = defineAsyncComponent(
() => import('ant-design-vue/es/time-picker'),
);
const TreeSelect = defineAsyncComponent(
() => import('ant-design-vue/es/tree-select'),
);
const Upload = defineAsyncComponent(() => import('ant-design-vue/es/upload'));
const withDefaultPlaceholder = <T extends Component>(
component: T,
type: 'input' | 'select',
componentProps: Recordable<any> = {},
) => {
return (props: any, { attrs, slots }: Omit<SetupContext, 'expose'>) => {
const placeholder = props?.placeholder || $t(`ui.placeholder.${type}`);
return h(component, { ...props, ...attrs, placeholder }, slots);
};
return defineComponent({
name: component.name,
inheritAttrs: false,
setup: (props: any, { attrs, expose, slots }) => {
const placeholder =
props?.placeholder ||
attrs?.placeholder ||
$t(`ui.placeholder.${type}`);
// 透传组件暴露的方法
const innerRef = ref();
expose(
new Proxy(
{},
{
get: (_target, key) => innerRef.value?.[key],
has: (_target, key) => key in (innerRef.value || {}),
},
),
);
return () =>
h(
component,
{ ...componentProps, placeholder, ...props, ...attrs, ref: innerRef },
slots,
);
},
});
};
// 这里需要自行根据业务组件库进行适配,需要用到的组件都需要在这里类型说明
@@ -81,38 +129,34 @@ async function initComponentAdapter() {
// 如果你的组件体积比较大,可以使用异步加载
// Button: () =>
// import('xxx').then((res) => res.Button),
ApiSelect: (props, { attrs, slots }) => {
return h(
ApiComponent,
{
placeholder: $t('ui.placeholder.select'),
...props,
...attrs,
component: Select,
loadingSlot: 'suffixIcon',
visibleEvent: 'onDropdownVisibleChange',
modelPropName: 'value',
},
slots,
);
},
ApiTreeSelect: (props, { attrs, slots }) => {
return h(
ApiComponent,
{
placeholder: $t('ui.placeholder.select'),
...props,
...attrs,
component: TreeSelect,
fieldNames: { label: 'label', value: 'value', children: 'children' },
loadingSlot: 'suffixIcon',
modelPropName: 'value',
optionsPropName: 'treeData',
visibleEvent: 'onVisibleChange',
},
slots,
);
},
ApiSelect: withDefaultPlaceholder(
{
...ApiComponent,
name: 'ApiSelect',
},
'select',
{
component: Select,
loadingSlot: 'suffixIcon',
visibleEvent: 'onDropdownVisibleChange',
modelPropName: 'value',
},
),
ApiTreeSelect: withDefaultPlaceholder(
{
...ApiComponent,
name: 'ApiTreeSelect',
},
'select',
{
component: TreeSelect,
fieldNames: { label: 'label', value: 'value', children: 'children' },
loadingSlot: 'suffixIcon',
modelPropName: 'value',
optionsPropName: 'treeData',
visibleEvent: 'onVisibleChange',
},
),
AutoComplete,
Checkbox,
CheckboxGroup,
@@ -122,13 +166,11 @@ async function initComponentAdapter() {
return h(Button, { ...props, attrs, type: 'default' }, slots);
},
Divider,
IconPicker: (props, { attrs, slots }) => {
return h(
IconPicker,
{ iconSlot: 'addonAfter', inputComponent: Input, ...props, ...attrs },
slots,
);
},
IconPicker: withDefaultPlaceholder(IconPicker, 'select', {
iconSlot: 'addonAfter',
inputComponent: Input,
modelValueProp: 'value',
}),
Input: withDefaultPlaceholder(Input, 'input'),
InputNumber: withDefaultPlaceholder(InputNumber, 'input'),
InputPassword: withDefaultPlaceholder(InputPassword, 'input'),

View File

@@ -8,40 +8,42 @@ import type { ComponentType } from './component';
import { setupVbenForm, useVbenForm as useForm, z } from '@vben/common-ui';
import { $t } from '@vben/locales';
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',
Switch: 'checked',
Upload: 'fileList',
// 一些组件是 v-model:checked 或者 v-model:fileList
modelPropNameMap: {
Checkbox: 'checked',
Radio: 'checked',
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 (value === undefined || value === null) {
return $t('ui.formRules.selectRequired', [ctx.label]);
}
return true;
},
},
// 选择项目必填国际化适配
selectRequired: (value, _params, ctx) => {
if (value === undefined || value === null) {
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,3 +1,5 @@
import type { VxeTableGridOptions } from '@vben/plugins/vxe-table';
import { h } from 'vue';
import { setupVbenVxeTable, useVbenVxeGrid } from '@vben/plugins/vxe-table';
@@ -33,7 +35,7 @@ setupVbenVxeTable({
round: true,
showOverflow: true,
size: 'small',
},
} as VxeTableGridOptions,
});
// 表格配置项可以用 cellRender: { name: 'CellImage' },

View File

@@ -1,7 +1,7 @@
import { createApp, watchEffect } from 'vue';
import { registerAccessDirective } from '@vben/access';
import { initTippy } from '@vben/common-ui';
import { registerLoadingDirective } from '@vben/common-ui/es/loading';
import { preferences } from '@vben/preferences';
import { initStores } from '@vben/stores';
import '@vben/styles';
@@ -12,6 +12,7 @@ import { useTitle } from '@vueuse/core';
import { $t, setupI18n } from '#/locales';
import { initComponentAdapter } from './adapter/component';
import { initSetupVbenForm } from './adapter/form';
import App from './app.vue';
import { router } from './router';
@@ -19,6 +20,9 @@ async function bootstrap(namespace: string) {
// 初始化组件适配器
await initComponentAdapter();
// 初始化表单组件
await initSetupVbenForm();
// // 设置弹窗的默认配置
// setDefaultModalProps({
// fullscreenButton: false,
@@ -30,6 +34,12 @@ async function bootstrap(namespace: string) {
const app = createApp(App);
// 注册v-loading指令
registerLoadingDirective(app, {
loading: 'loading', // 在这里可以自定义指令名称也可以明确提供false表示不注册这个指令
spinning: 'spinning',
});
// 国际化 i18n 配置
await setupI18n(app);
@@ -40,11 +50,16 @@ async function bootstrap(namespace: string) {
registerAccessDirective(app);
// 初始化 tippy
const { initTippy } = await import('@vben/common-ui/es/tippy');
initTippy(app);
// 配置路由及路由守卫
app.use(router);
// 配置Motion插件
const { MotionPlugin } = await import('@vben/plugins/motion');
app.use(MotionPlugin);
// 动态更新标题
watchEffect(() => {
if (preferences.app.dynamicTitle) {

View File

@@ -110,7 +110,7 @@ watch(
async (enable) => {
if (enable) {
await updateWatermark({
content: `${userStore.userInfo?.username}`,
content: `${userStore.userInfo?.username} - ${userStore.userInfo?.realName}`,
});
} else {
destroyWatermark();

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,11 +1,12 @@
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 { AuthPageLayout, BasicLayout } from '#/layouts';
import { $t } from '#/locales';
import Login from '#/views/_core/authentication/login.vue';
const BasicLayout = () => import('#/layouts/basic.vue');
const AuthPageLayout = () => import('#/layouts/auth.vue');
/** 全局404页面 */
const fallbackNotFoundRoute: RouteRecordRaw = {
component: () => import('#/views/_core/fallback/not-found.vue'),
@@ -34,7 +35,7 @@ const coreRoutes: RouteRecordRaw[] = [
},
name: 'Root',
path: '/',
redirect: DEFAULT_HOME_PATH,
redirect: preferences.app.defaultHomePath,
children: [],
},
{
@@ -50,7 +51,7 @@ const coreRoutes: RouteRecordRaw[] = [
{
name: 'Login',
path: 'login',
component: Login,
component: () => import('#/views/_core/authentication/login.vue'),
meta: {
title: $t('page.auth.login'),
},

View File

@@ -3,7 +3,8 @@ import type { Recordable, 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';
@@ -54,7 +55,9 @@ export const useAuthStore = defineStore('auth', () => {
} else {
onSuccess
? await onSuccess?.()
: await router.push(userInfo.homePath || DEFAULT_HOME_PATH);
: await router.push(
userInfo.homePath || preferences.app.defaultHomePath,
);
}
if (userInfo?.realName) {

View File

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

View File

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

View File

@@ -3,45 +3,153 @@
* 可用于 vben-form、vben-modal、vben-drawer 等组件使用,
*/
import type { Component, SetupContext } from 'vue';
import type { Component } from 'vue';
import type { BaseFormComponentType } from '@vben/common-ui';
import type { Recordable } from '@vben/types';
import { h } from 'vue';
import { defineAsyncComponent, defineComponent, h, ref } from 'vue';
import { ApiComponent, globalShareState, IconPicker } from '@vben/common-ui';
import { $t } from '@vben/locales';
import {
ElButton,
ElCheckbox,
ElCheckboxButton,
ElCheckboxGroup,
ElDatePicker,
ElDivider,
ElInput,
ElInputNumber,
ElNotification,
ElRadio,
ElRadioButton,
ElRadioGroup,
ElSelectV2,
ElSpace,
ElSwitch,
ElTimePicker,
ElTreeSelect,
ElUpload,
} from 'element-plus';
import { ElNotification } from 'element-plus';
const ElButton = defineAsyncComponent(() =>
Promise.all([
import('element-plus/es/components/button/index'),
import('element-plus/es/components/button/style/css'),
]).then(([res]) => res.ElButton),
);
const ElCheckbox = defineAsyncComponent(() =>
Promise.all([
import('element-plus/es/components/checkbox/index'),
import('element-plus/es/components/checkbox/style/css'),
]).then(([res]) => res.ElCheckbox),
);
const ElCheckboxButton = defineAsyncComponent(() =>
Promise.all([
import('element-plus/es/components/checkbox/index'),
import('element-plus/es/components/checkbox-button/style/css'),
]).then(([res]) => res.ElCheckboxButton),
);
const ElCheckboxGroup = defineAsyncComponent(() =>
Promise.all([
import('element-plus/es/components/checkbox/index'),
import('element-plus/es/components/checkbox-group/style/css'),
]).then(([res]) => res.ElCheckboxGroup),
);
const ElDatePicker = defineAsyncComponent(() =>
Promise.all([
import('element-plus/es/components/date-picker/index'),
import('element-plus/es/components/date-picker/style/css'),
]).then(([res]) => res.ElDatePicker),
);
const ElDivider = defineAsyncComponent(() =>
Promise.all([
import('element-plus/es/components/divider/index'),
import('element-plus/es/components/divider/style/css'),
]).then(([res]) => res.ElDivider),
);
const ElInput = defineAsyncComponent(() =>
Promise.all([
import('element-plus/es/components/input/index'),
import('element-plus/es/components/input/style/css'),
]).then(([res]) => res.ElInput),
);
const ElInputNumber = defineAsyncComponent(() =>
Promise.all([
import('element-plus/es/components/input-number/index'),
import('element-plus/es/components/input-number/style/css'),
]).then(([res]) => res.ElInputNumber),
);
const ElRadio = defineAsyncComponent(() =>
Promise.all([
import('element-plus/es/components/radio/index'),
import('element-plus/es/components/radio/style/css'),
]).then(([res]) => res.ElRadio),
);
const ElRadioButton = defineAsyncComponent(() =>
Promise.all([
import('element-plus/es/components/radio/index'),
import('element-plus/es/components/radio-button/style/css'),
]).then(([res]) => res.ElRadioButton),
);
const ElRadioGroup = defineAsyncComponent(() =>
Promise.all([
import('element-plus/es/components/radio/index'),
import('element-plus/es/components/radio-group/style/css'),
]).then(([res]) => res.ElRadioGroup),
);
const ElSelectV2 = defineAsyncComponent(() =>
Promise.all([
import('element-plus/es/components/select-v2/index'),
import('element-plus/es/components/select-v2/style/css'),
]).then(([res]) => res.ElSelectV2),
);
const ElSpace = defineAsyncComponent(() =>
Promise.all([
import('element-plus/es/components/space/index'),
import('element-plus/es/components/space/style/css'),
]).then(([res]) => res.ElSpace),
);
const ElSwitch = defineAsyncComponent(() =>
Promise.all([
import('element-plus/es/components/switch/index'),
import('element-plus/es/components/switch/style/css'),
]).then(([res]) => res.ElSwitch),
);
const ElTimePicker = defineAsyncComponent(() =>
Promise.all([
import('element-plus/es/components/time-picker/index'),
import('element-plus/es/components/time-picker/style/css'),
]).then(([res]) => res.ElTimePicker),
);
const ElTreeSelect = defineAsyncComponent(() =>
Promise.all([
import('element-plus/es/components/tree-select/index'),
import('element-plus/es/components/tree-select/style/css'),
]).then(([res]) => res.ElTreeSelect),
);
const ElUpload = defineAsyncComponent(() =>
Promise.all([
import('element-plus/es/components/upload/index'),
import('element-plus/es/components/upload/style/css'),
]).then(([res]) => res.ElUpload),
);
const withDefaultPlaceholder = <T extends Component>(
component: T,
type: 'input' | 'select',
componentProps: Recordable<any> = {},
) => {
return (props: any, { attrs, slots }: Omit<SetupContext, 'expose'>) => {
const placeholder = props?.placeholder || $t(`ui.placeholder.${type}`);
return h(component, { ...props, ...attrs, placeholder }, slots);
};
return defineComponent({
name: component.name,
inheritAttrs: false,
setup: (props: any, { attrs, expose, slots }) => {
const placeholder =
props?.placeholder ||
attrs?.placeholder ||
$t(`ui.placeholder.${type}`);
// 透传组件暴露的方法
const innerRef = ref();
expose(
new Proxy(
{},
{
get: (_target, key) => innerRef.value?.[key],
has: (_target, key) => key in (innerRef.value || {}),
},
),
);
return () =>
h(
component,
{ ...componentProps, placeholder, ...props, ...attrs, ref: innerRef },
slots,
);
},
});
};
// 这里需要自行根据业务组件库进行适配,需要用到的组件都需要在这里类型说明
@@ -69,37 +177,33 @@ async function initComponentAdapter() {
// 如果你的组件体积比较大,可以使用异步加载
// Button: () =>
// import('xxx').then((res) => res.Button),
ApiSelect: (props, { attrs, slots }) => {
return h(
ApiComponent,
{
placeholder: $t('ui.placeholder.select'),
...props,
...attrs,
component: ElSelectV2,
loadingSlot: 'loading',
visibleEvent: 'onVisibleChange',
},
slots,
);
},
ApiTreeSelect: (props, { attrs, slots }) => {
return h(
ApiComponent,
{
placeholder: $t('ui.placeholder.select'),
...props,
...attrs,
component: ElTreeSelect,
props: { label: 'label', children: 'children' },
nodeKey: 'value',
loadingSlot: 'loading',
optionsPropName: 'data',
visibleEvent: 'onVisibleChange',
},
slots,
);
},
ApiSelect: withDefaultPlaceholder(
{
...ApiComponent,
name: 'ApiSelect',
},
'select',
{
component: ElSelectV2,
loadingSlot: 'loading',
visibleEvent: 'onVisibleChange',
},
),
ApiTreeSelect: withDefaultPlaceholder(
{
...ApiComponent,
name: 'ApiTreeSelect',
},
'select',
{
component: ElTreeSelect,
props: { label: 'label', children: 'children' },
nodeKey: 'value',
loadingSlot: 'loading',
optionsPropName: 'data',
visibleEvent: 'onVisibleChange',
},
),
Checkbox: ElCheckbox,
CheckboxGroup: (props, { attrs, slots }) => {
let defaultSlot;
@@ -129,19 +233,11 @@ async function initComponentAdapter() {
return h(ElButton, { ...props, attrs, type: 'primary' }, slots);
},
Divider: ElDivider,
IconPicker: (props, { attrs, slots }) => {
return h(
IconPicker,
{
iconSlot: 'append',
modelValueProp: 'model-value',
inputComponent: ElInput,
...props,
...attrs,
},
slots,
);
},
IconPicker: withDefaultPlaceholder(IconPicker, 'select', {
iconSlot: 'append',
modelValueProp: 'model-value',
inputComponent: ElInput,
}),
Input: withDefaultPlaceholder(ElInput, 'input'),
InputNumber: withDefaultPlaceholder(ElInputNumber, 'input'),
RadioGroup: (props, { attrs, slots }) => {

View File

@@ -8,32 +8,34 @@ import type { ComponentType } from './component';
import { setupVbenForm, useVbenForm as useForm, z } from '@vben/common-ui';
import { $t } from '@vben/locales';
setupVbenForm<ComponentType>({
config: {
modelPropNameMap: {
Upload: 'fileList',
CheckboxGroup: 'model-value',
async function initSetupVbenForm() {
setupVbenForm<ComponentType>({
config: {
modelPropNameMap: {
Upload: 'fileList',
CheckboxGroup: 'model-value',
},
},
},
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 (value === undefined || value === null) {
return $t('ui.formRules.selectRequired', [ctx.label]);
}
return true;
},
},
selectRequired: (value, _params, ctx) => {
if (value === undefined || value === null) {
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,3 +1,5 @@
import type { VxeTableGridOptions } from '@vben/plugins/vxe-table';
import { h } from 'vue';
import { setupVbenVxeTable, useVbenVxeGrid } from '@vben/plugins/vxe-table';
@@ -33,7 +35,7 @@ setupVbenVxeTable({
round: true,
showOverflow: true,
size: 'small',
},
} as VxeTableGridOptions,
});
// 表格配置项可以用 cellRender: { name: 'CellImage' },

View File

@@ -1,7 +1,7 @@
import { createApp, watchEffect } from 'vue';
import { registerAccessDirective } from '@vben/access';
import { initTippy } from '@vben/common-ui';
import { registerLoadingDirective } from '@vben/common-ui';
import { preferences } from '@vben/preferences';
import { initStores } from '@vben/stores';
import '@vben/styles';
@@ -13,12 +13,17 @@ import { ElLoading } from 'element-plus';
import { $t, setupI18n } from '#/locales';
import { initComponentAdapter } from './adapter/component';
import { initSetupVbenForm } from './adapter/form';
import App from './app.vue';
import { router } from './router';
async function bootstrap(namespace: string) {
// 初始化组件适配器
await initComponentAdapter();
// 初始化表单组件
await initSetupVbenForm();
// // 设置弹窗的默认配置
// setDefaultModalProps({
// fullscreenButton: false,
@@ -32,6 +37,12 @@ async function bootstrap(namespace: string) {
// 注册Element Plus提供的v-loading指令
app.directive('loading', ElLoading.directive);
// 注册Vben提供的v-loading和v-spinning指令
registerLoadingDirective(app, {
loading: false, // Vben提供的v-loading指令和Element Plus提供的v-loading指令二选一即可此处false表示不注册Vben提供的v-loading指令
spinning: 'spinning',
});
// 国际化 i18n 配置
await setupI18n(app);
@@ -42,11 +53,16 @@ async function bootstrap(namespace: string) {
registerAccessDirective(app);
// 初始化 tippy
const { initTippy } = await import('@vben/common-ui/es/tippy');
initTippy(app);
// 配置路由及路由守卫
app.use(router);
// 配置Motion插件
const { MotionPlugin } = await import('@vben/plugins/motion');
app.use(MotionPlugin);
// 动态更新标题
watchEffect(() => {
if (preferences.app.dynamicTitle) {

View File

@@ -110,7 +110,7 @@ watch(
async (enable) => {
if (enable) {
await updateWatermark({
content: `${userStore.userInfo?.username}`,
content: `${userStore.userInfo?.username} - ${userStore.userInfo?.realName}`,
});
} else {
destroyWatermark();

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,11 +1,12 @@
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 { AuthPageLayout, BasicLayout } from '#/layouts';
import { $t } from '#/locales';
import Login from '#/views/_core/authentication/login.vue';
const BasicLayout = () => import('#/layouts/basic.vue');
const AuthPageLayout = () => import('#/layouts/auth.vue');
/** 全局404页面 */
const fallbackNotFoundRoute: RouteRecordRaw = {
component: () => import('#/views/_core/fallback/not-found.vue'),
@@ -34,7 +35,7 @@ const coreRoutes: RouteRecordRaw[] = [
},
name: 'Root',
path: '/',
redirect: DEFAULT_HOME_PATH,
redirect: preferences.app.defaultHomePath,
children: [],
},
{
@@ -50,7 +51,7 @@ const coreRoutes: RouteRecordRaw[] = [
{
name: 'Login',
path: 'login',
component: Login,
component: () => import('#/views/_core/authentication/login.vue'),
meta: {
title: $t('page.auth.login'),
},

View File

@@ -3,7 +3,8 @@ import type { Recordable, 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 { ElNotification } from 'element-plus';
@@ -55,7 +56,9 @@ export const useAuthStore = defineStore('auth', () => {
} else {
onSuccess
? await onSuccess?.()
: await router.push(userInfo.homePath || DEFAULT_HOME_PATH);
: await router.push(
userInfo.homePath || preferences.app.defaultHomePath,
);
}
if (userInfo?.realName) {

View File

@@ -1,7 +1,7 @@
<script lang="ts" setup>
import { h } from 'vue';
import { Page } from '@vben/common-ui';
import { Page, useVbenDrawer } from '@vben/common-ui';
import { ElButton, ElCard, ElCheckbox, ElMessage } from 'element-plus';
@@ -17,11 +17,16 @@ const [Form, formApi] = useVbenForm({
},
layout: 'horizontal',
// 大屏一行显示3个中屏一行显示2个小屏一行显示1个
wrapperClass: 'grid-cols-1 md:grid-cols-2 lg:grid-cols-3',
// wrapperClass: 'grid-cols-1 md:grid-cols-2 lg:grid-cols-3',
handleSubmit: (values) => {
ElMessage.success(`表单数据:${JSON.stringify(values)}`);
},
schema: [
{
component: 'IconPicker',
fieldName: 'icon',
label: 'IconPicker',
},
{
// 组件需要在 #/adapter.ts内注册并加上类型
component: 'ApiSelect',
@@ -149,6 +154,8 @@ const [Form, formApi] = useVbenForm({
},
],
});
const [Drawer, drawerApi] = useVbenDrawer();
function setFormValues() {
formApi.setValues({
string: 'string',
@@ -168,6 +175,9 @@ function setFormValues() {
description="我们重新包装了CheckboxGroup、RadioGroup、Select可以通过options属性传入选项属性数组以自动生成选项"
title="表单演示"
>
<Drawer class="w-[600px]" title="基础表单示例">
<Form />
</Drawer>
<ElCard>
<template #header>
<div class="flex items-center">
@@ -175,7 +185,7 @@ function setFormValues() {
<ElButton type="primary" @click="setFormValues">设置表单值</ElButton>
</div>
</template>
<Form />
<ElButton type="primary" @click="drawerApi.open"> 打开抽屉 </ElButton>
</ElCard>
</Page>
</template>

View File

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

View File

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

View File

@@ -3,44 +3,99 @@
* 可用于 vben-form、vben-modal、vben-drawer 等组件使用,
*/
import type { Component, SetupContext } from 'vue';
import type { Component } from 'vue';
import type { BaseFormComponentType } from '@vben/common-ui';
import type { Recordable } from '@vben/types';
import { h } from 'vue';
import { defineAsyncComponent, defineComponent, h, ref } from 'vue';
import { ApiComponent, globalShareState, IconPicker } from '@vben/common-ui';
import { $t } from '@vben/locales';
import {
NButton,
NCheckbox,
NCheckboxGroup,
NDatePicker,
NDivider,
NInput,
NInputNumber,
NRadio,
NRadioButton,
NRadioGroup,
NSelect,
NSpace,
NSwitch,
NTimePicker,
NTreeSelect,
NUpload,
} from 'naive-ui';
import { message } from '#/adapter/naive';
const NButton = defineAsyncComponent(() =>
import('naive-ui/es/button').then((res) => res.NButton),
);
const NCheckbox = defineAsyncComponent(() =>
import('naive-ui/es/checkbox').then((res) => res.NCheckbox),
);
const NCheckboxGroup = defineAsyncComponent(() =>
import('naive-ui/es/checkbox').then((res) => res.NCheckboxGroup),
);
const NDatePicker = defineAsyncComponent(() =>
import('naive-ui/es/date-picker').then((res) => res.NDatePicker),
);
const NDivider = defineAsyncComponent(() =>
import('naive-ui/es/divider').then((res) => res.NDivider),
);
const NInput = defineAsyncComponent(() =>
import('naive-ui/es/input').then((res) => res.NInput),
);
const NInputNumber = defineAsyncComponent(() =>
import('naive-ui/es/input-number').then((res) => res.NInputNumber),
);
const NRadio = defineAsyncComponent(() =>
import('naive-ui/es/radio').then((res) => res.NRadio),
);
const NRadioButton = defineAsyncComponent(() =>
import('naive-ui/es/radio').then((res) => res.NRadioButton),
);
const NRadioGroup = defineAsyncComponent(() =>
import('naive-ui/es/radio').then((res) => res.NRadioGroup),
);
const NSelect = defineAsyncComponent(() =>
import('naive-ui/es/select').then((res) => res.NSelect),
);
const NSpace = defineAsyncComponent(() =>
import('naive-ui/es/space').then((res) => res.NSpace),
);
const NSwitch = defineAsyncComponent(() =>
import('naive-ui/es/switch').then((res) => res.NSwitch),
);
const NTimePicker = defineAsyncComponent(() =>
import('naive-ui/es/time-picker').then((res) => res.NTimePicker),
);
const NTreeSelect = defineAsyncComponent(() =>
import('naive-ui/es/tree-select').then((res) => res.NTreeSelect),
);
const NUpload = defineAsyncComponent(() =>
import('naive-ui/es/upload').then((res) => res.NUpload),
);
const withDefaultPlaceholder = <T extends Component>(
component: T,
type: 'input' | 'select',
componentProps: Recordable<any> = {},
) => {
return (props: any, { attrs, slots }: Omit<SetupContext, 'expose'>) => {
const placeholder = props?.placeholder || $t(`ui.placeholder.${type}`);
return h(component, { ...props, ...attrs, placeholder }, slots);
};
return defineComponent({
name: component.name,
inheritAttrs: false,
setup: (props: any, { attrs, expose, slots }) => {
const placeholder =
props?.placeholder ||
attrs?.placeholder ||
$t(`ui.placeholder.${type}`);
// 透传组件暴露的方法
const innerRef = ref();
expose(
new Proxy(
{},
{
get: (_target, key) => innerRef.value?.[key],
has: (_target, key) => key in (innerRef.value || {}),
},
),
);
return () =>
h(
component,
{ ...componentProps, placeholder, ...props, ...attrs, ref: innerRef },
slots,
);
},
});
};
// 这里需要自行根据业务组件库进行适配,需要用到的组件都需要在这里类型说明
@@ -69,37 +124,33 @@ async function initComponentAdapter() {
// Button: () =>
// import('xxx').then((res) => res.Button),
ApiSelect: (props, { attrs, slots }) => {
return h(
ApiComponent,
{
placeholder: $t('ui.placeholder.select'),
...props,
...attrs,
component: NSelect,
modelPropName: 'value',
},
slots,
);
},
ApiTreeSelect: (props, { attrs, slots }) => {
return h(
ApiComponent,
{
placeholder: $t('ui.placeholder.select'),
...props,
...attrs,
component: NTreeSelect,
nodeKey: 'value',
loadingSlot: 'arrow',
keyField: 'value',
modelPropName: 'value',
optionsPropName: 'options',
visibleEvent: 'onVisibleChange',
},
slots,
);
},
ApiSelect: withDefaultPlaceholder(
{
...ApiComponent,
name: 'ApiSelect',
},
'select',
{
component: NSelect,
modelPropName: 'value',
},
),
ApiTreeSelect: withDefaultPlaceholder(
{
...ApiComponent,
name: 'ApiTreeSelect',
},
'select',
{
component: NTreeSelect,
nodeKey: 'value',
loadingSlot: 'arrow',
keyField: 'value',
modelPropName: 'value',
optionsPropName: 'options',
visibleEvent: 'onVisibleChange',
},
),
Checkbox: NCheckbox,
CheckboxGroup: (props, { attrs, slots }) => {
let defaultSlot;
@@ -127,13 +178,10 @@ async function initComponentAdapter() {
return h(NButton, { ...props, attrs, type: 'primary' }, slots);
},
Divider: NDivider,
IconPicker: (props, { attrs, slots }) => {
return h(
IconPicker,
{ iconSlot: 'suffix', inputComponent: NInput, ...props, ...attrs },
slots,
);
},
IconPicker: withDefaultPlaceholder(IconPicker, 'select', {
iconSlot: 'suffix',
inputComponent: NInput,
}),
Input: withDefaultPlaceholder(NInput, 'input'),
InputNumber: withDefaultPlaceholder(NInputNumber, 'input'),
RadioGroup: (props, { attrs, slots }) => {

View File

@@ -8,36 +8,38 @@ import type { ComponentType } from './component';
import { setupVbenForm, useVbenForm as useForm, z } from '@vben/common-ui';
import { $t } from '@vben/locales';
setupVbenForm<ComponentType>({
config: {
// naive-ui组件的空值为null,不能是undefined否则重置表单时不生效
emptyStateValue: null,
baseModelPropName: 'value',
modelPropNameMap: {
Checkbox: 'checked',
Radio: 'checked',
Upload: 'fileList',
async function initSetupVbenForm() {
setupVbenForm<ComponentType>({
config: {
// naive-ui组件的空值为null,不能是undefined否则重置表单时不生效
emptyStateValue: null,
baseModelPropName: 'value',
modelPropNameMap: {
Checkbox: 'checked',
Radio: '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 (value === undefined || value === null) {
return $t('ui.formRules.selectRequired', [ctx.label]);
}
return true;
},
},
selectRequired: (value, _params, ctx) => {
if (value === undefined || value === null) {
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,3 +1,5 @@
import type { VxeTableGridOptions } from '@vben/plugins/vxe-table';
import { h } from 'vue';
import { setupVbenVxeTable, useVbenVxeGrid } from '@vben/plugins/vxe-table';
@@ -33,7 +35,7 @@ setupVbenVxeTable({
round: true,
showOverflow: true,
size: 'small',
},
} as VxeTableGridOptions,
});
// 表格配置项可以用 cellRender: { name: 'CellImage' },

View File

@@ -1,7 +1,7 @@
import { createApp, watchEffect } from 'vue';
import { registerAccessDirective } from '@vben/access';
import { initTippy } from '@vben/common-ui';
import { registerLoadingDirective } from '@vben/common-ui';
import { preferences } from '@vben/preferences';
import { initStores } from '@vben/stores';
import '@vben/styles';
@@ -12,12 +12,16 @@ import { useTitle } from '@vueuse/core';
import { $t, setupI18n } from '#/locales';
import { initComponentAdapter } from './adapter/component';
import { initSetupVbenForm } from './adapter/form';
import App from './app.vue';
import { router } from './router';
async function bootstrap(namespace: string) {
// 初始化组件适配器
initComponentAdapter();
await initComponentAdapter();
// 初始化表单组件
await initSetupVbenForm();
// // 设置弹窗的默认配置
// setDefaultModalProps({
@@ -30,6 +34,12 @@ async function bootstrap(namespace: string) {
const app = createApp(App);
// 注册v-loading指令
registerLoadingDirective(app, {
loading: 'loading', // 在这里可以自定义指令名称,也可以明确提供false表示不注册这个指令
spinning: 'spinning',
});
// 国际化 i18n 配置
await setupI18n(app);
@@ -40,11 +50,16 @@ async function bootstrap(namespace: string) {
registerAccessDirective(app);
// 初始化 tippy
const { initTippy } = await import('@vben/common-ui/es/tippy');
initTippy(app);
// 配置路由及路由守卫
app.use(router);
// 配置Motion插件
const { MotionPlugin } = await import('@vben/plugins/motion');
app.use(MotionPlugin);
// 动态更新标题
watchEffect(() => {
if (preferences.app.dynamicTitle) {

View File

@@ -111,7 +111,7 @@ watch(
async (enable) => {
if (enable) {
await updateWatermark({
content: `${userStore.userInfo?.username}`,
content: `${userStore.userInfo?.username} - ${userStore.userInfo?.realName}`,
});
} else {
destroyWatermark();

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) },
// 携带当前跳转的页面,登录后重新跳转该页面
@@ -107,8 +107,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,11 +1,12 @@
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 { AuthPageLayout, BasicLayout } from '#/layouts';
import { $t } from '#/locales';
import Login from '#/views/_core/authentication/login.vue';
const BasicLayout = () => import('#/layouts/basic.vue');
const AuthPageLayout = () => import('#/layouts/auth.vue');
/** 全局404页面 */
const fallbackNotFoundRoute: RouteRecordRaw = {
component: () => import('#/views/_core/fallback/not-found.vue'),
@@ -34,7 +35,7 @@ const coreRoutes: RouteRecordRaw[] = [
},
name: 'Root',
path: '/',
redirect: DEFAULT_HOME_PATH,
redirect: preferences.app.defaultHomePath,
children: [],
},
{
@@ -50,7 +51,7 @@ const coreRoutes: RouteRecordRaw[] = [
{
name: 'Login',
path: 'login',
component: Login,
component: () => import('#/views/_core/authentication/login.vue'),
meta: {
title: $t('page.auth.login'),
},

View File

@@ -3,7 +3,8 @@ import type { Recordable, 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 { defineStore } from 'pinia';
@@ -55,7 +56,9 @@ export const useAuthStore = defineStore('auth', () => {
} else {
onSuccess
? await onSuccess?.()
: await router.push(userInfo.homePath || DEFAULT_HOME_PATH);
: await router.push(
userInfo.homePath || preferences.app.defaultHomePath,
);
}
if (userInfo?.realName) {

View File

@@ -1,11 +1,13 @@
<script lang="ts" setup>
import { Page } from '@vben/common-ui';
import { Page, useVbenModal } from '@vben/common-ui';
import { NButton, NCard, useMessage } from 'naive-ui';
import { useVbenForm } from '#/adapter/form';
import { getAllMenusApi } from '#/api';
import modalDemo from './modal.vue';
const message = useMessage();
const [Form, formApi] = useVbenForm({
commonConfig: {
@@ -143,6 +145,10 @@ function setFormValues() {
date: Date.now(),
});
}
const [Modal, modalApi] = useVbenModal({
connectedComponent: modalDemo,
});
</script>
<template>
<Page
@@ -152,8 +158,12 @@ function setFormValues() {
<NCard title="基础表单">
<template #header-extra>
<NButton type="primary" @click="setFormValues">设置表单值</NButton>
<NButton type="primary" @click="modalApi.open()" class="ml-2">
打开弹窗
</NButton>
</template>
<Form />
</NCard>
<Modal />
</Page>
</template>

View File

@@ -0,0 +1,71 @@
<script lang="ts" setup>
import { useVbenModal } from '@vben/common-ui';
import { useVbenForm } from '#/adapter/form';
defineOptions({
name: 'FormModelDemo',
});
const [Form, formApi] = useVbenForm({
schema: [
{
component: 'Input',
componentProps: {
placeholder: '请输入',
},
fieldName: 'field1',
label: '字段1',
rules: 'required',
},
{
component: 'Input',
componentProps: {
placeholder: '请输入',
},
fieldName: 'field2',
label: '字段2',
rules: 'required',
},
{
component: 'Select',
componentProps: {
options: [
{ label: '选项1', value: '1' },
{ label: '选项2', value: '2' },
],
placeholder: '请输入',
},
fieldName: 'field3',
label: '字段3',
rules: 'required',
},
],
showDefaultActions: false,
});
const [Modal, modalApi] = useVbenModal({
fullscreenButton: false,
onCancel() {
modalApi.close();
},
onConfirm: async () => {
await formApi.validateAndSubmitForm();
// modalApi.close();
},
onOpenChange(isOpen: boolean) {
if (isOpen) {
const { values } = modalApi.getData<Record<string, any>>();
if (values) {
formApi.setValues(values);
}
}
},
title: '内嵌表单示例',
});
</script>
<template>
<Modal>
<Form />
</Modal>
</template>

View File

@@ -168,6 +168,10 @@ function sidebarComponents(): DefaultTheme.SidebarItem[] {
link: 'common-ui/vben-api-component',
text: 'ApiComponent Api组件包装器',
},
{
link: 'common-ui/vben-alert',
text: 'Alert 轻量提示框',
},
{
link: 'common-ui/vben-modal',
text: 'Modal 模态框',

View File

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

View File

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

View File

@@ -3,7 +3,7 @@
社区交流群主要是为了方便大家交流,提问,解答问题,分享经验等。偏自助方式,如果你有问题,可以通过以下方式加入社区交流群:
- [QQ频道](https://pd.qq.com/s/16p8lvvob):推荐!!!主要提供问题解答,分享经验等。
- QQ群[大群](https://qm.qq.com/q/MEmHoCLbG0)[1群](https://qm.qq.com/q/YacMHPYAMu)、[2群](https://qm.qq.com/q/ajVKZvFICk)、[3群](https://qm.qq.com/q/36zdwThP2E)[4群](https://qm.qq.com/q/sCzSlm3504),主要使用者交流群。
- QQ群[大群](https://qm.qq.com/q/MEmHoCLbG0)[1群](https://qm.qq.com/q/YacMHPYAMu)、[2群](https://qm.qq.com/q/ajVKZvFICk)、[3群](https://qm.qq.com/q/36zdwThP2E)[4群](https://qm.qq.com/q/sCzSlm3504)[5群](https://qm.qq.com/q/ya9XrtbS6s)主要使用者交流群。
- [Discord](https://discord.com/invite/VU62jTecad): 主要提供问题解答,分享经验等。
::: tip

View File

@@ -0,0 +1,166 @@
---
outline: deep
---
# Vben Alert 轻量提示框
框架提供的一些用于轻量提示的弹窗仅使用js代码即可快速动态创建提示而不需要在template写任何代码。
::: info 应用场景
Alert提供的功能与Modal类似但只适用于简单应用场景。例如临时性、动态地弹出模态确认框、输入框等。如果对弹窗有更复杂的需求请使用VbenModal
:::
::: tip 注意
Alert提供的快捷方法alert、confirm、prompt动态创建的弹窗在已打开的情况下不支持HMR热更新代码变更后需要关闭这些弹窗后重新打开。
:::
::: tip README
下方示例代码中的,存在一些主题色未适配、样式缺失的问题,这些问题只在文档内会出现,实际使用并不会有这些问题,可忽略,不必纠结。
:::
## 基础用法
使用 `alert` 创建只有一个确认按钮的提示框。
<DemoPreview dir="demos/vben-alert/alert" />
使用 `confirm` 创建有确认和取消按钮的提示框。
<DemoPreview dir="demos/vben-alert/confirm" />
使用 `prompt` 创建有确认和取消按钮、接受用户输入的提示框。
<DemoPreview dir="demos/vben-alert/prompt" />
## useAlertContext
当弹窗的content、footer、icon使用自定义组件时在这些组件中可以使用 `useAlertContext` 获取当前弹窗的上下文对象,用来主动控制弹窗。
::: tip 注意
`useAlertContext`只能用在setup或者函数式组件中。
:::
### Methods
| 方法 | 描述 | 类型 | 版本要求 |
| --------- | ------------------ | -------- | -------- |
| doConfirm | 调用弹窗的确认操作 | ()=>void | >5.5.4 |
| doCancel | 调用弹窗的取消操作 | ()=>void | >5.5.4 |
## 类型说明
```ts
/** 预置的图标类型 */
export type IconType = 'error' | 'info' | 'question' | 'success' | 'warning';
export type BeforeCloseScope = {
/** 是否为点击确认按钮触发的关闭 */
isConfirm: boolean;
};
/**
* alert 属性
*/
export type AlertProps = {
/** 关闭前的回调如果返回false则终止关闭 */
beforeClose?: (
scope: BeforeCloseScope,
) => boolean | Promise<boolean | undefined> | undefined;
/** 边框 */
bordered?: boolean;
/** 按钮对齐方式 */
buttonAlign?: 'center' | 'end' | 'start';
/** 取消按钮的标题 */
cancelText?: string;
/** 是否居中显示 */
centered?: boolean;
/** 确认按钮的标题 */
confirmText?: string;
/** 弹窗容器的额外样式 */
containerClass?: string;
/** 弹窗提示内容 */
content: Component | string;
/** 弹窗内容的额外样式 */
contentClass?: string;
/** 执行beforeClose回调期间在内容区域显示一个loading遮罩*/
contentMasking?: boolean;
/** 弹窗底部内容(与按钮在同一个容器中) */
footer?: Component | string;
/** 弹窗的图标(在标题的前面) */
icon?: Component | IconType;
/**
* 弹窗遮罩模糊效果
*/
overlayBlur?: number;
/** 是否显示取消按钮 */
showCancel?: boolean;
/** 弹窗标题 */
title?: string;
};
/** prompt 属性 */
export type PromptProps<T = any> = {
/** 关闭前的回调如果返回false则终止关闭 */
beforeClose?: (scope: {
isConfirm: boolean;
value: T | undefined;
}) => boolean | Promise<boolean | undefined> | undefined;
/** 用于接受用户输入的组件 */
component?: Component;
/** 输入组件的属性 */
componentProps?: Recordable<any>;
/** 输入组件的插槽 */
componentSlots?: Recordable<Component>;
/** 默认值 */
defaultValue?: T;
/** 输入组件的值属性名 */
modelPropName?: string;
} & Omit<AlertProps, 'beforeClose'>;
/**
* 函数签名
* alert和confirm的函数签名相同。
* confirm默认会显示取消按钮而alert默认只有一个按钮
* */
export function alert(options: AlertProps): Promise<void>;
export function alert(
message: string,
options?: Partial<AlertProps>,
): Promise<void>;
export function alert(
message: string,
title?: string,
options?: Partial<AlertProps>,
): Promise<void>;
/**
* 弹出输入框的函数签名。
* beforeClose的参数会传入用户当前输入的值
* component指定接受用户输入的组件默认为Input
* componentProps 为输入组件设置的属性数据
* defaultValue 默认的值
* modelPropName 输入组件的值属性名称。默认为modelValue
*/
export async function prompt<T = any>(
options: Omit<AlertProps, 'beforeClose'> & {
beforeClose?: (
scope: BeforeCloseScope & {
/** 输入组件的当前值 */
value: T;
},
) => boolean | Promise<boolean | undefined> | undefined;
component?: Component;
componentProps?: Recordable<any>;
defaultValue?: T;
modelPropName?: string;
},
): Promise<T | undefined>;
```

View File

@@ -123,27 +123,51 @@ function fetchApi(): Promise<Record<string, any>> {
:::
## 并发和缓存
有些场景下可能需要使用多个ApiComponent它们使用了相同的远程数据源例如用在可编辑的表格中。如果直接将请求后端接口的函数传递给api属性则每一个实例都会访问一次接口这会造成资源浪费是完全没有必要的。Tanstack Query提供了并发控制、缓存、重试等诸多特性我们可以将接口请求函数用useQuery包装一下再传递给ApiComponent这样的话无论页面有多少个使用相同数据源的ApiComponent实例都只会发起一次远程请求。演示效果请参考 [Playground vue-query](https://www.vben.pro/#/demos/features/vue-query),具体代码请查看项目文件[concurrency-caching](https://github.com/vbenjs/vue-vben-admin/blob/main/playground/src/views/demos/features/vue-query/concurrency-caching.vue)
## API
### Props
| 属性名 | 描述 | 类型 | 默认值 |
| 属性名 | 描述 | 类型 | 默认值 | 版本要求 |
| --- | --- | --- | --- | --- |
| modelValue(v-model) | 当前值 | `any` | - | - |
| component | 欲包装的组件(以下称为目标组件) | `Component` | - | - |
| numberToString | 是否将value从数字转为string | `boolean` | `false` | - |
| api | 获取数据的函数 | `(arg?: any) => Promise<OptionsItem[] \| Record<string, any>>` | - | - |
| params | 传递给api的参数 | `Record<string, any>` | - | - |
| resultField | 从api返回的结果中提取options数组的字段名 | `string` | - | - |
| labelField | label字段名 | `string` | `label` | - |
| childrenField | 子级数据字段名,需要层级数据的组件可用 | `string` | `` | - |
| valueField | value字段名 | `string` | `value` | - |
| optionsPropName | 目标组件接收options数据的属性名称 | `string` | `options` | - |
| modelPropName | 目标组件的双向绑定属性名默认为modelValue。部分组件可能为value | `string` | `modelValue` | - |
| immediate | 是否立即调用api | `boolean` | `true` | - |
| alwaysLoad | 每次`visibleEvent`事件发生时都重新请求数据 | `boolean` | `false` | - |
| beforeFetch | 在api请求之前的回调函数 | `AnyPromiseFunction<any, any>` | - | - |
| afterFetch | 在api请求之后的回调函数 | `AnyPromiseFunction<any, any>` | - | - |
| options | 直接传入选项数据也作为api返回空数据时的后备数据 | `OptionsItem[]` | - | - |
| visibleEvent | 触发重新请求数据的事件名 | `string` | - | - |
| loadingSlot | 目标组件的插槽名称,用来显示一个"加载中"的图标 | `string` | - | - |
| autoSelect | 自动设置选项 | `'first' \| 'last' \| 'one'\| ((item: OptionsItem[]) => OptionsItem) \| false` | `false` | >5.5.4 |
#### autoSelect 自动设置选项
如果当前值为undefined在选项数据成功加载之后自动从备选项中选择一个作为当前值。默认值为`false`,即不自动选择选项。注意:该属性不应用于多选组件。可选值有:
- `"first"`:自动选择第一个选项
- `"last"`:自动选择最后一个选项
- `"one"`:有且仅有一个选项时,自动选择它
- `自定义函数`自定义选择逻辑函数的参数为options返回值为选择的选项
- `false`:不自动选择选项
### Methods
| 方法 | 描述 | 类型 | 版本要求 |
| --- | --- | --- | --- |
| modelValue(v-model) | 当前值 | `any` | - |
| component | 欲包装的组件(以下称为目标组件) | `Component` | - |
| numberToString | 是否将value从数字转为string | `boolean` | `false` |
| api | 获取数据的函数 | `(arg?: any) => Promise<OptionsItem[] \| Record<string, any>>` | - |
| params | 传递给api的参数 | `Record<string, any>` | - |
| resultField | 从api返回的结果中提取options数组的字段名 | `string` | - |
| labelField | label字段名 | `string` | `label` |
| childrenField | 子级数据字段名,需要层级数据的组件可用 | `string` | `` |
| valueField | value字段名 | `string` | `value` |
| optionsPropName | 目标组件接收options数据的属性名称 | `string` | `options` |
| modelPropName | 目标组件的双向绑定属性名默认为modelValue。部分组件可能为value | `string` | `modelValue` |
| immediate | 是否立即调用api | `boolean` | `true` |
| alwaysLoad | 每次`visibleEvent`事件发生时都重新请求数据 | `boolean` | `false` |
| beforeFetch | 在api请求之前的回调函数 | `AnyPromiseFunction<any, any>` | - |
| afterFetch | 在api请求之后的回调函数 | `AnyPromiseFunction<any, any>` | - |
| options | 直接传入选项数据也作为api返回空数据时的后备数据 | `OptionsItem[]` | - |
| visibleEvent | 触发重新请求数据的事件名 | `string` | - |
| loadingSlot | 目标组件的插槽名称,用来显示一个"加载中"的图标 | `string` | - |
| getComponentRef | 获取被包装的组件的实例 | ()=>T | >5.5.4 |
| updateParam | 设置接口请求参数将与params属性合并 | (newParams: Record<string, any>)=>void | >5.5.4 |
| getOptions | 获取已加载的选项数据 | ()=>OptionsItem[] | >5.5.4 |
| getValue | 获取当前值 | ()=>any | >5.5.4 |

View File

@@ -42,11 +42,18 @@ outline: deep
| transition | 动画效果 | `string` | `linear` |
| decimals | 保留小数点位数 | `number` | `0` |
### Events
| 事件名 | 描述 | 类型 |
| -------------- | -------------- | -------------- |
| started | 动画已开始 | `()=>void` |
| finished | 动画已结束 | `()=>void` |
| ~~onStarted~~ | ~~动画已开始~~ | ~~`()=>void`~~ |
| ~~onFinished~~ | ~~动画已结束~~ | ~~`()=>void`~~ |
### Methods
以下事件,只有在 `useVbenModal({onCancel:()=>{}})` 中传入才会生效。
| 事件名 | 描述 | 类型 |
| 方法名 | 描述 | 类型 |
| ------ | ------------ | ---------- |
| start | 开始执行动画 | `()=>void` |
| reset | 重置 | `()=>void` |

View File

@@ -22,7 +22,7 @@ outline: deep
## 基础用法
使用 `useVbenDrawer` 创建最基础的模态框
使用 `useVbenDrawer` 创建最基础的抽屉
<DemoPreview dir="demos/vben-drawer/basic" />
@@ -52,7 +52,7 @@ Drawer 内的内容一般业务中,会比较复杂,所以我们可以将 dra
::: info 注意
- `VbenDrawer` 组件对参数的处理优先级是 `slot` > `props` > `state`(通过api更新的状态以及useVbenDrawer参数)。如果你已经传入了 `slot` 或者 `props`,那么 `setState` 将不会生效,这种情况下你可以通过 `slot` 或者 `props` 来更新状态。
- `VbenDrawer` 组件对参数的处理优先级是 `slot` > `props` > `state`(通过api更新的状态以及useVbenDrawer参数)。如果你已经传入了 `slot` 或者 `props`,那么 `setState` 将不会生效,这种情况下你可以通过 `slot` 或者 `props` 来更新状态。
- 如果你使用到了 `connectedComponent` 参数,那么会存在 2 个`useVbenDrawer`, 此时,如果同时设置了相同的参数,那么以内部为准(也就是没有设置 connectedComponent 的代码)。比如 同时设置了 `onConfirm`,那么以内部的 `onConfirm` 为准。`onOpenChange`事件除外,内外都会触发。
- 使用了`connectedComponent`参数时,可以配置`destroyOnClose`属性来决定当关闭弹窗时,是否要销毁`connectedComponent`组件(重新创建`connectedComponent`组件,这将会把其内部所有的变量、状态、数据等恢复到初始状态。)。
- 如果抽屉的默认行为不符合你的预期,可以在`src\bootstrap.ts`中修改`setDefaultDrawerProps`的参数来设置默认的属性如默认隐藏全屏按钮修改默认ZIndex等。
@@ -77,8 +77,8 @@ const [Drawer, drawerApi] = useVbenDrawer({
| 属性名 | 描述 | 类型 | 默认值 |
| --- | --- | --- | --- |
| appendToMain | 是否挂载到内容区域默认挂载到body | `boolean` | `false` |
| connectedComponent | 连接另一个Modal组件 | `Component` | - |
| destroyOnClose | 关闭时销毁`connectedComponent` | `boolean` | `false` |
| connectedComponent | 连接另一个Drawer组件 | `Component` | - |
| destroyOnClose | 关闭时销毁 | `boolean` | `false` |
| title | 标题 | `string\|slot` | - |
| titleTooltip | 标题提示信息 | `string\|slot` | - |
| description | 描述信息 | `string\|slot` | - |
@@ -96,7 +96,7 @@ const [Drawer, drawerApi] = useVbenDrawer({
| cancelText | 取消按钮文本 | `string\|slot` | `取消` |
| placement | 抽屉弹出位置 | `'left'\|'right'\|'top'\|'bottom'` | `right` |
| showCancelButton | 显示取消按钮 | `boolean` | `true` |
| showConfirmButton | 显示确认按钮文本 | `boolean` | `true` |
| showConfirmButton | 显示确认按钮 | `boolean` | `true` |
| class | modal的class宽度通过这个配置 | `string` | - |
| contentClass | modal内容区域的class | `string` | - |
| footerClass | modal底部区域的class | `string` | - |
@@ -127,21 +127,30 @@ const [Drawer, drawerApi] = useVbenDrawer({
除了上面的属性类型包含`slot`,还可以通过插槽来自定义弹窗的内容。
| 插槽名 | 描述 |
| -------------- | ------------------- |
| default | 默认插槽 - 弹窗内容 |
| prepend-footer | 取消按钮左侧 |
| append-footer | 取消按钮右侧 |
| close-icon | 关闭按钮图标 |
| extra | 额外内容(标题右侧) |
| 插槽名 | 描述 |
| -------------- | -------------------------------------------------- |
| default | 默认插槽 - 弹窗内容 |
| prepend-footer | 取消按钮左侧 |
| center-footer | 取消按钮和确认按钮中间(不使用 footer 插槽时有效) |
| append-footer | 确认按钮右侧 |
| close-icon | 关闭按钮图标 |
| extra | 额外内容(标题右侧) |
### drawerApi
| 方法 | 描述 | 类型 |
| --- | --- | --- |
| 方法 | 描述 | 类型 | 版本限制 |
| --- | --- | --- | --- |
| setState | 动态设置弹窗状态属性 | `(((prev: ModalState) => Partial<ModalState>)\| Partial<ModalState>)=>drawerApi` |
| open | 打开弹窗 | `()=>void` |
| close | 关闭弹窗 | `()=>void` |
| setData | 设置共享数据 | `<T>(data:T)=>drawerApi` |
| getData | 获取共享数据 | `<T>()=>T` |
| useStore | 获取可响应式状态 | - |
| open | 打开弹窗 | `()=>void` | --- |
| close | 关闭弹窗 | `()=>void` | --- |
| setData | 设置共享数据 | `<T>(data:T)=>drawerApi` | --- |
| getData | 获取共享数据 | `<T>()=>T` | --- |
| useStore | 获取可响应式状态 | - | --- |
| lock | 将抽屉标记为提交中,锁定当前状态 | `(isLock:boolean)=>drawerApi` | >5.5.3 |
| unlock | lock方法的反操作解除抽屉的锁定状态也是lock(false)的别名 | `()=>drawerApi` | >5.5.3 |
::: info lock
`lock`方法用于锁定抽屉的状态一般用于提交数据的过程中防止用户重复提交或者抽屉被意外关闭、表单数据被改变等等。当处于锁定状态时抽屉的确认按钮会变为loading状态同时禁用取消按钮和关闭按钮、禁止ESC或者点击遮罩等方式关闭抽屉、开启抽屉的spinner动画以遮挡弹窗内容。调用`close`方法关闭处于锁定状态的抽屉时,会自动解锁。要主动解除这种状态,可以调用`unlock`方法或者再次调用lock方法并传入false参数。
:::

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

@@ -90,30 +90,52 @@ import { h } from 'vue';
import { globalShareState, IconPicker } from '@vben/common-ui';
import { $t } from '@vben/locales';
import {
AutoComplete,
Button,
Checkbox,
CheckboxGroup,
DatePicker,
Divider,
Input,
InputNumber,
InputPassword,
Mentions,
notification,
Radio,
RadioGroup,
RangePicker,
Rate,
Select,
Space,
Switch,
Textarea,
TimePicker,
TreeSelect,
Upload,
} from 'ant-design-vue';
const AutoComplete = defineAsyncComponent(
() => import('ant-design-vue/es/auto-complete'),
);
const Button = defineAsyncComponent(() => import('ant-design-vue/es/button'));
const Checkbox = defineAsyncComponent(
() => import('ant-design-vue/es/checkbox'),
);
const CheckboxGroup = defineAsyncComponent(() =>
import('ant-design-vue/es/checkbox').then((res) => res.CheckboxGroup),
);
const DatePicker = defineAsyncComponent(
() => import('ant-design-vue/es/date-picker'),
);
const Divider = defineAsyncComponent(() => import('ant-design-vue/es/divider'));
const Input = defineAsyncComponent(() => import('ant-design-vue/es/input'));
const InputNumber = defineAsyncComponent(
() => import('ant-design-vue/es/input-number'),
);
const InputPassword = defineAsyncComponent(() =>
import('ant-design-vue/es/input').then((res) => res.InputPassword),
);
const Mentions = defineAsyncComponent(
() => import('ant-design-vue/es/mentions'),
);
const Radio = defineAsyncComponent(() => import('ant-design-vue/es/radio'));
const RadioGroup = defineAsyncComponent(() =>
import('ant-design-vue/es/radio').then((res) => res.RadioGroup),
);
const RangePicker = defineAsyncComponent(() =>
import('ant-design-vue/es/date-picker').then((res) => res.RangePicker),
);
const Rate = defineAsyncComponent(() => import('ant-design-vue/es/rate'));
const Select = defineAsyncComponent(() => import('ant-design-vue/es/select'));
const Space = defineAsyncComponent(() => import('ant-design-vue/es/space'));
const Switch = defineAsyncComponent(() => import('ant-design-vue/es/switch'));
const Textarea = defineAsyncComponent(() =>
import('ant-design-vue/es/input').then((res) => res.Textarea),
);
const TimePicker = defineAsyncComponent(
() => import('ant-design-vue/es/time-picker'),
);
const TreeSelect = defineAsyncComponent(
() => import('ant-design-vue/es/tree-select'),
);
const Upload = defineAsyncComponent(() => import('ant-design-vue/es/upload'));
const withDefaultPlaceholder = <T extends Component>(
component: T,
@@ -279,22 +301,24 @@ const [Form, formApi] = useVbenForm({
useVbenForm 返回的第二个参数,是一个对象,包含了一些表单的方法。
| 方法名 | 描述 | 类型 |
| --- | --- | --- |
| submitForm | 提交表单 | `(e:Event)=>Promise<Record<string,any>>` |
| validateAndSubmitForm | 提交并校验表单 | `(e:Event)=>Promise<Record<string,any>>` |
| resetForm | 重置表单 | `()=>Promise<void>` |
| setValues | 设置表单值, 默认会过滤不在schema中定义的field, 可通过filterFields形参关闭过滤 | `(fields: Record<string, any>, filterFields?: boolean, shouldValidate?: boolean) => Promise<void>` |
| getValues | 获取表单值 | `(fields:Record<string, any>,shouldValidate: boolean = false)=>Promise<void>` |
| validate | 表单校验 | `()=>Promise<void>` |
| validateField | 校验指定字段 | `(fieldName: string)=>Promise<ValidationResult<unknown>>` |
| isFieldValid | 检查某个字段是否已通过校验 | `(fieldName: string)=>Promise<boolean>` |
| resetValidate | 重置表单校验 | `()=>Promise<void>` |
| updateSchema | 更新formSchema | `(schema:FormSchema[])=>void` |
| setFieldValue | 设置字段值 | `(field: string, value: any, shouldValidate?: boolean)=>Promise<void>` |
| setState | 设置组件状态props | `(stateOrFn:\| ((prev: VbenFormProps) => Partial<VbenFormProps>)\| Partial<VbenFormProps>)=>Promise<void>` |
| getState | 获取组件状态props | `()=>Promise<VbenFormProps>` |
| form | 表单对象实例,可以操作表单,见 [useForm](https://vee-validate.logaretm.com/v4/api/use-form/) | - |
| 方法名 | 描述 | 类型 | 版本号 |
| --- | --- | --- | --- |
| submitForm | 提交表单 | `(e:Event)=>Promise<Record<string,any>>` | - |
| validateAndSubmitForm | 提交并校验表单 | `(e:Event)=>Promise<Record<string,any>>` | - |
| resetForm | 重置表单 | `()=>Promise<void>` | - |
| setValues | 设置表单值, 默认会过滤不在schema中定义的field, 可通过filterFields形参关闭过滤 | `(fields: Record<string, any>, filterFields?: boolean, shouldValidate?: boolean) => Promise<void>` | - |
| getValues | 获取表单值 | `(fields:Record<string, any>,shouldValidate: boolean = false)=>Promise<void>` | - |
| validate | 表单校验 | `()=>Promise<void>` | - |
| validateField | 校验指定字段 | `(fieldName: string)=>Promise<ValidationResult<unknown>>` | - |
| isFieldValid | 检查某个字段是否已通过校验 | `(fieldName: string)=>Promise<boolean>` | - |
| resetValidate | 重置表单校验 | `()=>Promise<void>` | - |
| updateSchema | 更新formSchema | `(schema:FormSchema[])=>void` | - |
| setFieldValue | 设置字段值 | `(field: string, value: any, shouldValidate?: boolean)=>Promise<void>` | - |
| setState | 设置组件状态props | `(stateOrFn:\| ((prev: VbenFormProps) => Partial<VbenFormProps>)\| Partial<VbenFormProps>)=>Promise<void>` | - |
| getState | 获取组件状态props | `()=>Promise<VbenFormProps>` | - |
| form | 表单对象实例,可以操作表单,见 [useForm](https://vee-validate.logaretm.com/v4/api/use-form/) | - | - |
| getFieldComponentRef | 获取指定字段的组件实例 | `<T=unknown>(fieldName: string)=>T` | >5.5.3 |
| getFocusedField | 获取当前已获得焦点的字段 | `()=>string\|undefined` | >5.5.3 |
## Props
@@ -302,13 +326,15 @@ useVbenForm 返回的第二个参数,是一个对象,包含了一些表单
| 属性名 | 描述 | 类型 | 默认值 |
| --- | --- | --- | --- |
| layout | 表单项布局 | `'horizontal' \| 'vertical'` | `horizontal` |
| layout | 表单项布局 | `'horizontal' \| 'vertical'\| 'inline'` | `horizontal` |
| showCollapseButton | 是否显示折叠按钮 | `boolean` | `false` |
| wrapperClass | 表单的布局基于tailwindcss | `any` | - |
| actionWrapperClass | 表单操作区域class | `any` | - |
| actionLayout | 表单操作按钮位置 | `'newLine' \| 'rowEnd' \| 'inline'` | `rowEnd` |
| actionPosition | 表单操作按钮对齐方式 | `'left' \| 'center' \| 'right'` | `right` |
| handleReset | 表单重置回调 | `(values: Record<string, any>,) => Promise<void> \| void` | - |
| handleSubmit | 表单提交回调 | `(values: Record<string, any>,) => Promise<void> \| void` | - |
| handleValuesChange | 表单值变化回调 | `(values: Record<string, any>,) => void` | - |
| handleValuesChange | 表单值变化回调 | `(values: Record<string, any>, fieldsChanged: string[]) => void` | - |
| actionButtonsReverse | 调换操作按钮位置 | `boolean` | `false` |
| resetButtonOptions | 重置按钮组件参数 | `ActionButtonOptions` | - |
| submitButtonOptions | 提交按钮组件参数 | `ActionButtonOptions` | - |
@@ -316,11 +342,19 @@ useVbenForm 返回的第二个参数,是一个对象,包含了一些表单
| collapsed | 是否折叠,在`showCollapseButton``true`时生效 | `boolean` | `false` |
| collapseTriggerResize | 折叠时,触发`resize`事件 | `boolean` | `false` |
| collapsedRows | 折叠时保持的行数 | `number` | `1` |
| fieldMappingTime | 用于将表单内的数组值映射成 2 个字段 | `[string, [string, string],Nullable<string>\|[string,string]\|((any,string)=>any)?][]` | - |
| fieldMappingTime | 用于将表单内的数组值映射成 2 个字段 | `[string, [string, string],Nullable<string>\|[string,string]\|((any,string)=>any)?][]` | - |
| commonConfig | 表单项的通用配置,每个配置都会传递到每个表单项,表单项可覆盖 | `FormCommonConfig` | - |
| schema | 表单项的每一项配置 | `FormSchema[]` | - |
| submitOnEnter | 按下回车健时提交表单 | `boolean` | false |
| submitOnChange | 字段值改变时提交表单(内部防抖,这个属性一般用于表格的搜索表单) | `boolean` | false |
| compact | 是否紧凑模式(忽略为校验信息所预留的空间) | `boolean` | false |
| scrollToFirstError | 表单验证失败时是否自动滚动到第一个错误字段 | `boolean` | false |
::: tip handleValuesChange
`handleValuesChange` 回调函数的第一个参数`values`装载了表单改变后的当前值对象,第二个参数`fieldsChanged`是一个数组包含了所有被改变的字段名。注意第二个参数仅在v5.5.4(不含)以上版本可用并且传递的是已在schema中定义的字段名。如果你使用了字段映射并且需要检查是哪些字段发生了变化的话请注意该参数并不会包含映射后的字段名。
:::
::: tip fieldMappingTime
@@ -347,7 +381,7 @@ export interface ActionButtonOptions {
/** 是否显示 */
show?: boolean;
/** 按钮文本 */
text?: string;
content?: string;
/** 任意属性 */
[key: string]: any;
}
@@ -363,13 +397,6 @@ export interface FormCommonConfig {
* 所有表单项的props
*/
componentProps?: ComponentProps;
/**
* 是否紧凑模式(移除表单底部为显示校验错误信息所预留的空间)。
* 在有设置校验规则的场景下建议不要将其设置为true
* 默认为false。但用作表格的搜索表单时默认为true
* @default false
*/
compact?: boolean;
/**
* 所有表单项的控件样式
*/
@@ -392,7 +419,7 @@ export interface FormCommonConfig {
* 所有表单项的栅格布局
* @default ""
*/
formItemClass?: string;
formItemClass?: (() => string) | string;
/**
* 隐藏所有表单项label
* @default false
@@ -445,9 +472,9 @@ export interface FormSchema<
/** 字段名,也作为自定义插槽的名称 */
fieldName: string;
/** 帮助信息 */
help?: string;
/** 表单 */
label?: string;
help?: CustomRenderType;
/** 表单的标签如果是一个string会用于默认必选规则的消息提示 */
label?: CustomRenderType;
/** 自定义组件内部渲染 */
renderComponentContent?: RenderComponentContentType;
/** 字段规则 */
@@ -518,20 +545,25 @@ import { z } from '#/adapter/form';
// 可选(可以是undefined)并且携带默认值。注意zod的optional不包括空字符串''
{
rules: z.string().default('默认值').optional(),
rules: z.string().default('默认值').optional();
}
// 可以是空字符串、undefined或者一个邮箱地址
// 可以是空字符串、undefined或者一个邮箱地址(两种不同的用法)
{
rules: z.union(z.string().email().optional(), z.literal(""))
rules: z.union([z.string().email().optional(), z.literal('')]);
}
{
rules: z.string().email().or(z.literal('')).optional();
}
// 复杂校验
{
z.string().min(1, { message: "请输入" })
.refine((value) => value === "123", {
message: "值必须为123",
});
z.string()
.min(1, { message: '请输入' })
.refine((value) => value === '123', {
message: '值必须为123',
});
}
```

View File

@@ -56,11 +56,19 @@ Modal 内的内容一般业务中,会比较复杂,所以我们可以将 moda
<DemoPreview dir="demos/vben-modal/shared-data" />
## 动画类型
通过 `animationType` 属性可以控制弹窗的动画效果:
- `slide`(默认):从顶部向下滑动进入/退出
- `scale`:缩放淡入/淡出效果
<DemoPreview dir="demos/vben-modal/animation-type" />
::: info 注意
- `VbenModal` 组件对与参数的处理优先级是 `slot` > `props` > `state`(通过api更新的状态以及useVbenModal参数)。如果你已经传入了 `slot` 或者 `props`,那么 `setState` 将不会生效,这种情况下你可以通过 `slot` 或者 `props` 来更新状态。
- 如果你使用到了 `connectedComponent` 参数,那么会存在 2 个`useVbenModal`, 此时,如果同时设置了相同的参数,那么以内部为准(也就是没有设置 connectedComponent 的代码)。比如 同时设置了 `onConfirm`,那么以内部的 `onConfirm` 为准。`onOpenChange`事件除外,内外都会触发。
- 使用了`connectedComponent`参数时,可以配置`destroyOnClose`属性来决定当关闭弹窗时,是否要销毁`connectedComponent`组件(重新创建`connectedComponent`组件,这将会把其内部所有的变量、状态、数据等恢复到初始状态。)。
- 如果你使用到了 `connectedComponent` 参数,那么会存在 2 个`useVbenModal`, 此时,如果同时设置了相同的参数,那么以内部为准(也就是没有设置 connectedComponent 的代码)。比如 同时设置了 `onConfirm`,那么以内部的 `onConfirm` 为准。`onOpenChange`事件除外,内外都会触发。另外,如果设置了`destroyOnClose`内部Modal及其子组件会在被关闭后<b>完全销毁</b>
- 如果弹窗的默认行为不符合你的预期,可以在`src\bootstrap.ts`中修改`setDefaultModalProps`的参数来设置默认的属性如默认隐藏全屏按钮修改默认ZIndex等。
:::
@@ -84,7 +92,7 @@ const [Modal, modalApi] = useVbenModal({
| --- | --- | --- | --- |
| appendToMain | 是否挂载到内容区域默认挂载到body | `boolean` | `false` |
| connectedComponent | 连接另一个Modal组件 | `Component` | - |
| destroyOnClose | 关闭时销毁`connectedComponent` | `boolean` | `false` |
| destroyOnClose | 关闭时销毁 | `boolean` | `false` |
| title | 标题 | `string\|slot` | - |
| titleTooltip | 标题提示信息 | `string\|slot` | - |
| description | 描述信息 | `string\|slot` | - |
@@ -113,6 +121,7 @@ const [Modal, modalApi] = useVbenModal({
| bordered | 是否显示border | `boolean` | `false` |
| zIndex | 弹窗的ZIndex层级 | `number` | `1000` |
| overlayBlur | 遮罩模糊度 | `number` | - |
| animationType | 动画类型 | `'slide' \| 'scale'` | `'slide'` |
| submitting | 标记为提交中,锁定弹窗当前状态 | `boolean` | `false` |
::: info appendToMain
@@ -138,11 +147,12 @@ const [Modal, modalApi] = useVbenModal({
除了上面的属性类型包含`slot`,还可以通过插槽来自定义弹窗的内容。
| 插槽名 | 描述 |
| -------------- | ------------------- |
| default | 默认插槽 - 弹窗内容 |
| prepend-footer | 取消按钮左侧 |
| append-footer | 取消按钮右侧 |
| 插槽名 | 描述 |
| -------------- | -------------------------------------------------- |
| default | 默认插槽 - 弹窗内容 |
| prepend-footer | 取消按钮左侧 |
| center-footer | 取消按钮和确认按钮中间(不使用 footer 插槽时有效) |
| append-footer | 确认按钮右侧 |
### modalApi
@@ -155,9 +165,10 @@ const [Modal, modalApi] = useVbenModal({
| getData | 获取共享数据 | `<T>()=>T` | - |
| useStore | 获取可响应式状态 | - | - |
| lock | 将弹窗标记为提交中,锁定当前状态 | `(isLock:boolean)=>modalApi` | >5.5.2 |
| unlock | lock方法的反操作解除弹窗的锁定状态也是lock(false)的别名 | `()=>modalApi` | >5.5.3 |
::: info lock
`lock`方法用于锁定当前弹窗的状态一般用于提交数据的过程中防止用户重复提交或者弹窗被意外关闭、表单数据被改变等等。当处于锁定状态时弹窗的确认按钮会变为loading状态同时禁用确认按钮、隐藏关闭按钮、禁止ESC或者点击遮罩等方式关闭弹窗、开启弹窗的spinner动画以遮挡弹窗内容。调用`close`方法关闭处于锁定状态的弹窗时,会自动解锁。
`lock`方法用于锁定当前弹窗的状态一般用于提交数据的过程中防止用户重复提交或者弹窗被意外关闭、表单数据被改变等等。当处于锁定状态时弹窗的确认按钮会变为loading状态同时禁用取消按钮和关闭按钮、禁止ESC或者点击遮罩等方式关闭弹窗、开启弹窗的spinner动画以遮挡弹窗内容。调用`close`方法关闭处于锁定状态的弹窗时,会自动解锁。要主动解除这种状态,可以调用`unlock`方法或者再次调用lock方法并传入false参数。
:::

View File

@@ -165,7 +165,24 @@ vxeUI.renderer.add('CellLink', {
**表单搜索** 部分采用了`Vben Form 表单`,参考 [Vben Form 表单文档](/components/common-ui/vben-form)。
当启用了表单搜索时可以在toolbarConfig中配置`search``true`来让表格在工具栏区域显示一个搜索表单控制按钮。
当启用了表单搜索时可以在toolbarConfig中配置`search``true`来让表格在工具栏区域显示一个搜索表单控制按钮。表格的所有以`form-`开头的命名插槽都会被传递给搜索表单。
### 定制分隔条
当你启用表单搜索时在表单和表格之间会显示一个分隔条。这个分隔条使用了默认的组件背景色并且横向贯穿整个Vben Vxe Table在视觉上融入了页面的默认背景中。如果你在Vben Vxe Table的外层包裹了一个不同背景色的容器如将其放在一个Card内默认的表单和表格之间的分隔条可能就显得格格不入了下面的代码演示了如何定制这个分隔条。
```ts
const [Grid] = useVbenVxeGrid({
formOptions: {},
gridOptions: {},
// 完全移除分隔条
separator: false,
// 你也可以使用下面的代码来移除分隔条
// separator: { show: false },
// 或者使用下面的代码来改变分隔条的颜色
// separator: { backgroundColor: 'rgba(100,100,0,0.5)' },
});
```
<DemoPreview dir="demos/vben-vxe-table/form" />
@@ -231,12 +248,29 @@ useVbenVxeGrid 返回的第二个参数,是一个对象,包含了一些表
所有属性都可以传入 `useVbenVxeGrid` 的第一个参数中。
| 属性名 | 描述 | 类型 |
| -------------- | ------------------ | ------------------- |
| tableTitle | 表格标题 | `string` |
| tableTitleHelp | 表格标题帮助信息 | `string` |
| gridClass | grid组件的class | `string` |
| gridOptions | grid组件的参数 | `VxeTableGridProps` |
| gridEvents | grid组件的触发的⌚️ | `VxeGridListeners` |
| formOptions | 表单参数 | `VbenFormProps` |
| showSearchForm | 是否显示搜索表单 | `boolean` |
| 属性名 | 描述 | 类型 | 版本要求 |
| --- | --- | --- | --- |
| tableTitle | 表格标题 | `string` | - |
| tableTitleHelp | 表格标题帮助信息 | `string` | - |
| gridClass | grid组件的class | `string` | - |
| gridOptions | grid组件的参数 | `VxeTableGridProps` | - |
| gridEvents | grid组件的触发的事件 | `VxeGridListeners` | - |
| formOptions | 表单参数 | `VbenFormProps` | - |
| showSearchForm | 是否显示搜索表单 | `boolean` | - |
| separator | 搜索表单与表格主体之间的分隔条 | `boolean\|SeparatorOptions` | >5.5.4 |
## Slots
大部分插槽的说明请参考 [vxe-table 官方文档](https://vxetable.cn/v4/#/grid/api),但工具栏部分由于做了一些定制封装,需使用以下插槽定制表格的工具栏:
| 插槽名 | 描述 |
| --------------- | -------------------------------------------- |
| toolbar-actions | 工具栏左侧部分(表格标题附近) |
| toolbar-tools | 工具栏右侧部分vxeTable原生工具按钮的左侧 |
| table-title | 表格标题插槽 |
::: info 搜索表单的插槽
对于使用了搜索表单的表格来说,所有以`form-`开头的命名插槽都会传递给表单。
:::

View File

@@ -0,0 +1,36 @@
<script lang="ts" setup>
import { h } from 'vue';
import { alert, VbenButton } from '@vben/common-ui';
import { Result } from 'ant-design-vue';
function showAlert() {
alert('This is an alert message');
}
function showIconAlert() {
alert({
content: 'This is an alert message with icon',
icon: 'success',
});
}
function showCustomAlert() {
alert({
buttonAlign: 'center',
content: h(Result, {
status: 'success',
subTitle: '已成功创建订单。订单ID2017182818828182881',
title: '操作成功',
}),
});
}
</script>
<template>
<div class="flex gap-4">
<VbenButton @click="showAlert">Alert</VbenButton>
<VbenButton @click="showIconAlert">Alert With Icon</VbenButton>
<VbenButton @click="showCustomAlert">Alert With Custom Content</VbenButton>
</div>
</template>

View File

@@ -0,0 +1,75 @@
<script lang="ts" setup>
import { h, ref } from 'vue';
import { alert, confirm, VbenButton } from '@vben/common-ui';
import { Checkbox, message } from 'ant-design-vue';
function showConfirm() {
confirm('This is an alert message')
.then(() => {
alert('Confirmed');
})
.catch(() => {
alert('Canceled');
});
}
function showIconConfirm() {
confirm({
content: 'This is an alert message with icon',
icon: 'success',
});
}
function showfooterConfirm() {
const checked = ref(false);
confirm({
cancelText: '不要虾扯蛋',
confirmText: '是的我们都是NPC',
content:
'刚才发生的事情,为什么我似乎早就经历过一般?\n我甚至能在事情发生过程中潜意识里预知到接下来会发生什么。\n\n听起来挺玄乎的你有过这种感觉吗',
footer: () =>
h(
Checkbox,
{
checked: checked.value,
class: 'flex-1',
'onUpdate:checked': (v) => (checked.value = v),
},
'不再提示',
),
icon: 'question',
title: '未解之谜',
}).then(() => {
if (checked.value) {
message.success('我不会再拿这个问题烦你了');
} else {
message.info('下次还要继续问你哟');
}
});
}
function showAsyncConfirm() {
confirm({
beforeClose({ isConfirm }) {
if (isConfirm) {
// 这里可以执行一些异步操作。如果最终返回了false将阻止关闭弹窗
return new Promise((resolve) => setTimeout(resolve, 2000));
}
},
content: 'This is an alert message with async confirm',
icon: 'success',
}).then(() => {
alert('Confirmed');
});
}
</script>
<template>
<div class="flex gap-4">
<VbenButton @click="showConfirm">Confirm</VbenButton>
<VbenButton @click="showIconConfirm">Confirm With Icon</VbenButton>
<VbenButton @click="showfooterConfirm">Confirm With Footer</VbenButton>
<VbenButton @click="showAsyncConfirm">Async Confirm</VbenButton>
</div>
</template>

View File

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

View File

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

@@ -15,6 +15,7 @@ const [Form] = useVbenForm({
handleSubmit: onSubmit,
// 垂直布局label和input在不同行值为vertical
// 水平布局label和input在同一行
scrollToFirstError: true,
layout: 'horizontal',
schema: [
{

View File

@@ -0,0 +1,36 @@
<script lang="ts" setup>
import { useVbenModal, VbenButton } from '@vben/common-ui';
const [SlideModal, slideModalApi] = useVbenModal({
animationType: 'slide',
});
const [ScaleModal, scaleModalApi] = useVbenModal({
animationType: 'scale',
});
function openSlideModal() {
slideModalApi.open();
}
function openScaleModal() {
scaleModalApi.open();
}
</script>
<template>
<div class="space-y-4">
<div class="flex gap-4">
<VbenButton @click="openSlideModal">滑动动画</VbenButton>
<VbenButton @click="openScaleModal">缩放动画</VbenButton>
</div>
<SlideModal title="滑动动画示例" class="w-[500px]">
<p>这是使用滑动动画的弹窗从顶部向下滑动进入</p>
</SlideModal>
<ScaleModal title="缩放动画示例" class="w-[500px]">
<p>这是使用缩放动画的弹窗以缩放淡入淡出的方式显示</p>
</ScaleModal>
</div>
</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
@@ -150,6 +150,73 @@ To run the `docs` application:
pnpm dev:docs
```
### Distinguishing Build Environments
In actual business development, multiple environments are usually distinguished during the build process, such as the test environment `test` and the production environment `build`.
At this point, you can modify three files and add corresponding script configurations to distinguish between production environments.
Take the addition of the test environment `test` to `@vben/web-antd` as an example:
- `apps\web-antd\package.json`
```json
"scripts": {
"build:prod": "pnpm vite build --mode production",
"build:test": "pnpm vite build --mode test",
"build:analyze": "pnpm vite build --mode analyze",
"dev": "pnpm vite --mode development",
"preview": "vite preview",
"typecheck": "vue-tsc --noEmit --skipLibCheck"
}
```
Add the command `"build:test"` and change the original `"build"` to `"build:prod"` to avoid building packages for two environments simultaneously.
- `package.json`
```json
"scripts": {
"build": "cross-env NODE_OPTIONS=--max-old-space-size=8192 turbo build",
"build:analyze": "turbo build:analyze",
"build:antd": "pnpm run build --filter=@vben/web-antd",
"build-test:antd": "pnpm run build --filter=@vben/web-antd build:test",
······
}
```
Add the command to build the test environment in the root directory `package.json`.
- `turbo.json`
```json
"tasks": {
"build": {
"dependsOn": ["^build"],
"outputs": [
"dist/**",
"dist.zip",
".vitepress/dist.zip",
".vitepress/dist/**"
]
},
"build-test:antd": {
"dependsOn": ["@vben/web-antd#build:test"],
"outputs": ["dist/**"]
},
"@vben/web-antd#build:test": {
"dependsOn": ["^build"],
"outputs": ["dist/**"]
},
······
```
Add the relevant dependent commands in `turbo.json`.
## Public Static Resources
If you need to use public static resources in the project, such as images, static HTML, etc., and you want to directly import them in the development process through `src="/xxx.png"`.

View File

@@ -150,8 +150,8 @@ export async function saveUserApi(user: UserInfo) {
```ts
import { requestClient } from '#/api/request';
export async function deleteUserApi(user: UserInfo) {
return requestClient.delete<boolean>(`/user/${user.id}`, user);
export async function deleteUserApi(userId: number) {
return requestClient.delete<boolean>(`/user/${userId}`);
}
```

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

@@ -28,9 +28,10 @@ You can check the list below to understand all the available variables.
```css
:root {
--font-family: -apple-system, blinkmacsystemfont, 'Segoe UI', roboto,
'Helvetica Neue', arial, 'Noto Sans', sans-serif, 'Apple Color Emoji',
'Segoe UI Emoji', 'Segoe UI Symbol', 'Noto Color Emoji';
--font-family:
-apple-system, blinkmacsystemfont, 'Segoe UI', roboto, 'Helvetica Neue',
arial, 'Noto Sans', sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji',
'Segoe UI Symbol', 'Noto Color Emoji';
/* Default background color of <body />...etc */
--background: 0 0% 100%;
@@ -322,9 +323,10 @@ type BuiltinThemeType =
```css
:root {
--font-family: -apple-system, blinkmacsystemfont, 'Segoe UI', roboto,
'Helvetica Neue', arial, 'Noto Sans', sans-serif, 'Apple Color Emoji',
'Segoe UI Emoji', 'Segoe UI Symbol', 'Noto Color Emoji';
--font-family:
-apple-system, blinkmacsystemfont, 'Segoe UI', roboto, 'Helvetica Neue',
arial, 'Noto Sans', sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji',
'Segoe UI Symbol', 'Noto Color Emoji';
/* Default background color of <body />...etc */
--background: 0 0% 100%;

View File

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

View File

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

@@ -4,7 +4,6 @@
- If you want to contribute code to the project, please ensure your code complies with the project's coding standards.
- If you are using `vscode`, you need to install the following plugins:
- [ESLint](https://marketplace.visualstudio.com/items?itemName=dbaeumer.vscode-eslint) - Script code checking
- [Prettier](https://marketplace.visualstudio.com/items?itemName=esbenp.prettier-vscode) - Code formatting
- [Code Spell Checker](https://marketplace.visualstudio.com/items?itemName=streetsidesoftware.code-spell-checker) - Word syntax checking
@@ -33,8 +32,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 +147,64 @@ 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
- `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
If you want to disable Husky, simply delete the .husky directory.
- `post-merge`: Runs after merge, used for automatic dependency installation
- `install`: Runs `pnpm install` to install new dependencies
### lint-staged
- `commit-msg`: Runs during commit, used for checking commit message format
- `commitlint`: Uses commitlint to check commit messages
Used for automatically fixing style issues of committed files. Its configuration file is `.lintstagedrc.mjs`, which can be modified according to project needs.
#### 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

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