168 Commits

Author SHA1 Message Date
dap
9e365a336d docs: readme 2024-10-25 11:23:04 +08:00
dap
fb79029765 fix: 用户管理 更新用户时打开drawer需要加载该部门下的岗位信息 2024-10-25 08:21:26 +08:00
dap
938ccd85f8 chore: table title改为官方方案 2024-10-25 08:09:53 +08:00
dap
9311dfe25b Merge branch 'main' of https://github.com/vbenjs/vue-vben-admin 2024-10-25 07:53:50 +08:00
Vben
6688a6b3c2 feat: table grid supports setting title and helpMessage (#4732) 2024-10-24 22:51:04 +08:00
afe1
39e41d05be fix: path '/auth' is blank page (#4731) 2024-10-24 22:43:49 +08:00
dap
390ec31162 chore: getPopupContainer 2024-10-23 17:46:19 +08:00
dap
478fe5a069 fix: getPopupContainer 2024-10-23 15:36:31 +08:00
pingsanddoss
862bbd8344 fix: fix the error of closing the default analysis page on the tab page (#4720) 2024-10-23 14:12:33 +08:00
dap
73acf6a3c6 fix: 租户管理 套餐管理 回显时候已选中节点数量为0 2024-10-23 13:53:04 +08:00
dap
a38fc307d0 fix: 角色管理 菜单分配 节点独立下的回显及提交问题 2024-10-23 13:46:23 +08:00
dap
68f3ac8c79 fix: 需要排除menuIds menuIds为string 2024-10-23 11:38:56 +08:00
dap
e5625efcf7 Merge branch 'main' of https://github.com/vbenjs/vue-vben-admin 2024-10-23 07:49:10 +08:00
Vben
23768ea620 feat: menu routing support opens in a new window (#4715) 2024-10-22 22:24:56 +08:00
dap
e224971975 chore: vxe表格loading 只加载表格 不加载上面的表单 2024-10-22 17:46:10 +08:00
pingsanddoss
f60796f961 fix(@vben/tabs-ui): modified fixed and unfixed logic, fixed #4640 (#4709)
* fix: modified fixed and unfixed logic, fixed #4640

* fix: modified fixed and unfixed logic, fixed #4640
2024-10-22 14:59:46 +08:00
Rwing
7f4c733fa3 fix: fix typo in Update faq.md (#4711) 2024-10-22 14:02:25 +08:00
dap
330dcf480e refactor: renderHttpMethodTag 2024-10-22 09:36:39 +08:00
dap
0d63b6d9ca chore: rename 2024-10-22 09:31:01 +08:00
dap
f9c6284c35 chore: 需要默认值 2024-10-22 07:45:59 +08:00
dap
7b5e2e71d8 Merge branch 'main' of https://github.com/vbenjs/vue-vben-admin 2024-10-21 20:29:43 +08:00
Vben
1b172b0329 fix: rename the Icon component to IconifyIcon to prevent name conflicts and fix type issues (#4704) 2024-10-21 20:14:25 +08:00
dap
ade90a5bdd Merge branch 'main' of https://github.com/vbenjs/vue-vben-admin 2024-10-21 19:31:09 +08:00
Svend
88d2b3e569 feat(@vben/request): export basic HttpResponse generic (#4697) 2024-10-21 17:20:58 +08:00
invalid w
625862e082 chore(@vben/docs): update nginx deployment error related documentation (#4702) 2024-10-21 17:01:52 +08:00
dap
ad2f327d4a chore: i18n 2024-10-21 08:40:52 +08:00
dap
ea59f7d9e6 Merge branch 'main' of https://github.com/vbenjs/vue-vben-admin 2024-10-21 07:58:31 +08:00
vben
bfaa2780ab chore: release v5.4.2 2024-10-20 22:35:58 +08:00
Vben
d262b7b6c0 fix: fix known issues with the form (#4696)
* fix: fix known issues with the form

* chore: typo

* chore: typo
2024-10-20 22:34:11 +08:00
afe1
93b48ef244 fix: use pnpm manage package (#4695) 2024-10-20 22:30:20 +08:00
Vben
860fc15ce6 perf: adjustment of form spelling errors, type adjustment, closer to actual development (#4694) 2024-10-20 21:44:25 +08:00
Svend
646598afba fix(@vben-core/form-ui): fix the issue of Textarea not being able to wrap lines in the form (#4691) 2024-10-20 21:07:34 +08:00
afe1
234544c40d fix: vite-config warmup config (#4693) 2024-10-20 21:06:37 +08:00
dap
1b8d6c193e chore: 所有表格的搜索加上allowClear属性 支持清除 2024-10-20 18:16:47 +08:00
dap
8395a7f167 chore: 删除后需要重新加载 2024-10-20 17:59:10 +08:00
dap
4ea9b0d342 chore: 菜单图标列没有居中对齐 2024-10-20 17:51:50 +08:00
dap
88a722a298 chore: i18n key 2024-10-20 17:36:04 +08:00
dap
a7e4799dea docs: readme 2024-10-20 13:27:58 +08:00
dap
e8640a59ae chore: missing i18n key 2024-10-20 12:46:15 +08:00
dap
b0a862a448 chore: 错误拼写 2024-10-20 12:44:14 +08:00
dap
29ec264dcd refactor: 重构backMenuToVbenMenu 2024-10-20 12:39:00 +08:00
dap
86950953ff chore: 固定高度 2024-10-20 11:17:14 +08:00
dap
71d005bd48 chore: 个人中心 2024-10-20 11:01:20 +08:00
dap
23c548ac3d docs: changelog 2024-10-20 10:45:57 +08:00
dap
0334db44ac fix: 错误的参数 2024-10-20 10:39:03 +08:00
dap
02957060bf chore: 修改VbenTooltip默认方向 新增\n换行功能 2024-10-20 10:34:25 +08:00
dap
8bc62cf2df Merge branch 'main' of https://github.com/vbenjs/vue-vben-admin 2024-10-20 09:48:22 +08:00
dap
972e9439b7 chore: 更改i18n 2024-10-20 09:47:07 +08:00
Vben
307a71fb35 fix: downgrade the sass version to suppress the warnings (#4689) 2024-10-19 22:04:31 +08:00
dap
39d62a91bc Merge branch 'main' of https://github.com/vbenjs/vue-vben-admin 2024-10-19 21:29:20 +08:00
Vben
477a05c26c feat: menu supports carrying default query (#4687) 2024-10-19 19:50:23 +08:00
dap
12de789f2e chore: 更改文件名 2024-10-19 15:25:33 +08:00
Vben
0df8c5c02c refactor: reconstruct language files into multi-file structures (#4683)
* refactor: reconstruct language files into multi-file structures

* chore: typo
2024-10-19 14:28:21 +08:00
dap
e3d176e245 chore: 修改markdown预览模式 2024-10-19 10:25:04 +08:00
dap
6cf618dbf7 docs: readme 2024-10-19 10:21:06 +08:00
dap
b2268b03e7 Merge branch 'main' of https://github.com/vbenjs/vue-vben-admin 2024-10-19 09:46:56 +08:00
vben
d1ca09c7bb chore: release v5.4.1 2024-10-18 22:12:00 +08:00
Vben
ff3c5f8581 fix: form does not take effect in vertical layout (#4680) 2024-10-18 22:09:41 +08:00
Vben
240f0b5f8d perf: improved exception handling when request status code is 200 (#4679) 2024-10-18 22:00:41 +08:00
dependabot[bot]
6f3d50984f chore(deps-dev): bump the non-breaking-changes group with 3 updates (#4671)
* chore(deps-dev): bump the non-breaking-changes group with 3 updates

Bumps the non-breaking-changes group with 3 updates: [@types/node](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/node), [cspell](https://github.com/streetsidesoftware/cspell/tree/HEAD/packages/cspell) and [sass](https://github.com/sass/dart-sass).


Updates `@types/node` from 22.7.5 to 22.7.6
- [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases)
- [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/node)

Updates `cspell` from 8.15.2 to 8.15.3
- [Release notes](https://github.com/streetsidesoftware/cspell/releases)
- [Changelog](https://github.com/streetsidesoftware/cspell/blob/main/packages/cspell/CHANGELOG.md)
- [Commits](https://github.com/streetsidesoftware/cspell/commits/v8.15.3/packages/cspell)

Updates `sass` from 1.79.5 to 1.80.1
- [Release notes](https://github.com/sass/dart-sass/releases)
- [Changelog](https://github.com/sass/dart-sass/blob/main/CHANGELOG.md)
- [Commits](https://github.com/sass/dart-sass/compare/1.79.5...1.80.1)

---
updated-dependencies:
- dependency-name: "@types/node"
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: non-breaking-changes
- dependency-name: cspell
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: non-breaking-changes
- dependency-name: sass
  dependency-type: direct:development
  update-type: version-update:semver-minor
  dependency-group: non-breaking-changes
...

Signed-off-by: dependabot[bot] <support@github.com>

* chore: update deps

* chore: update deps

---------

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-10-18 21:38:39 +08:00
dap
280af21ffe chore: 去除 2024-10-18 15:55:53 +08:00
dap
9cdc2780b3 chore: 优化布局 2024-10-18 15:43:57 +08:00
dap
6e7fade539 refactor: 通用的表格复选框是否选中事件 2024-10-18 15:39:39 +08:00
dap
c6984c164c feat: 通用的表格复选框是否选中事件 2024-10-18 15:20:20 +08:00
dap
162b3207fd chore: 更新注释信息 2024-10-18 15:02:39 +08:00
dap
8788d2eeab Merge branch 'main' of https://github.com/vbenjs/vue-vben-admin 2024-10-18 14:46:27 +08:00
vince
c491b9e021 fix: maximum call stack size (#4674)
* fix: maximum call stack size
2024-10-18 14:24:39 +08:00
dap
0796f4327c chore: 修改为官方的enter提交 2024-10-18 07:47:40 +08:00
dap
7e7e23fc09 Merge branch 'main' of https://github.com/vbenjs/vue-vben-admin 2024-10-18 07:45:53 +08:00
Vben
6cd9937c03 feat: add submitOnEnter configuration to form (#4670) 2024-10-17 22:53:05 +08:00
Vben
f89f4f32c7 fix: form required style adjustment (#4668) 2024-10-17 22:40:20 +08:00
dap
ef956a5507 chore: 漏掉的导入 2024-10-17 19:08:09 +08:00
dap
bec201b95b chore: 应该为可选 2024-10-17 19:05:45 +08:00
dap
4a393a1920 chore: 更改导入位置 2024-10-17 15:16:22 +08:00
Netfan
c432e0ac33 feat: limit the drag range of tabs, fixed #4640 (#4659) 2024-10-17 14:11:42 +08:00
afe1
719c9a8f2d chore: variables typo (#4658)
* fix: variables

---------

Co-authored-by: afe1 <yunfei.zhu@nwowtec.com>
2024-10-17 13:57:13 +08:00
dap
70e7d6a131 Merge branch 'main' of https://github.com/vbenjs/vue-vben-admin 2024-10-17 13:36:03 +08:00
dap
b6256d4736 chore: 修改为默认clientId 2024-10-17 08:10:35 +08:00
dependabot[bot]
a0fbe0b21a chore(deps): bump tailwindcss from 3.4.13 to 3.4.14 in the non-breaking-changes group (#4650)
* chore(deps): bump tailwindcss in the non-breaking-changes group

Bumps the non-breaking-changes group with 1 update: [tailwindcss](https://github.com/tailwindlabs/tailwindcss).


Updates `tailwindcss` from 3.4.13 to 3.4.14
- [Release notes](https://github.com/tailwindlabs/tailwindcss/releases)
- [Changelog](https://github.com/tailwindlabs/tailwindcss/blob/v3.4.14/CHANGELOG.md)
- [Commits](https://github.com/tailwindlabs/tailwindcss/compare/v3.4.13...v3.4.14)

---
updated-dependencies:
- dependency-name: tailwindcss
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: non-breaking-changes
...

Signed-off-by: dependabot[bot] <support@github.com>

* chore: update deps

* chore: lint fix

---------

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-10-16 21:23:11 +08:00
Vben
f7fa69d33b fix: login page brand color does not take effect (#4655) 2024-10-16 21:12:57 +08:00
菠萝吹雪
7c45aeb868 fix: tabView title internationalization switchover problem (#4652)
当tabView被固定或取消固定后,切换国际化,该tabView的title国际化切换失败
2024-10-16 21:06:37 +08:00
dap
f9feeccc44 fix: vxe默认为reload 修改为在当前页刷新 2024-10-16 11:03:24 +08:00
Svend
850a6af1e0 fix: fix the issues with the local build docker script (#4647) 2024-10-15 21:45:05 +08:00
dap
2f16e64a3d chore: file-upload 2024-10-15 20:40:41 +08:00
dap
30b16fd5a8 feat: VxeTable搜索表单 enter提交 2024-10-15 19:56:57 +08:00
dap
106476b755 feat: sse demo 2024-10-15 10:26:18 +08:00
dap
c27acef777 feat: Markdown编辑/预览组件(基于vditor) 2024-10-15 08:31:39 +08:00
Vben
d5a210f53f fix: default theme colors cannot be overridden (#4636)
* fix: default theme colors cannot be overridden

* chore: update default config
2024-10-14 23:24:21 +08:00
Vben
6c4a742627 refactor: remove the adapter bucket introduction pattern and improve potential introduction timing (#4635)
* refactor: remove the adapter bucket introduction pattern and improve potential introduction timing

* chore: update deps
2024-10-14 22:53:23 +08:00
CHUZHI
45987fc1e3 feat: add form slot for action area (#4621)
* feat: add form slot for action area

* fix: fixed rename and lint
2024-10-14 22:35:01 +08:00
dap
72ae9edd2c feat: markdown组件(开发中) 2024-10-14 21:15:47 +08:00
dap
86a2539d27 chore: 更新 2024-10-14 17:19:49 +08:00
dap
47a817f73c Merge branch 'main' of https://github.com/vbenjs/vue-vben-admin 2024-10-14 17:19:16 +08:00
dap
8f6a2a6c23 chore: 说明 2024-10-14 17:11:18 +08:00
vben
ea962e75d0 fix: table search form slot not working as expected 2024-10-13 23:44:45 +08:00
Vben
24d14c2841 refactor(adapter): separate form and component adapters so that components adapt to components other than the form (#4628)
* refactor: global components can be customized

* refactor: remove use Toast and reconstruct the form adapter
2024-10-13 18:33:43 +08:00
苗大
8b961a9d7f chore: use taze to update deps (#4627) 2024-10-13 16:28:21 +08:00
dap
6dedefb6b9 chore: 去除join 2024-10-13 16:01:50 +08:00
dap
c60f826d31 chore: 租户切换保存到localStorage 2024-10-13 15:28:39 +08:00
dap
6b667374af fix: 新版本更改了位置 2024-10-13 14:52:10 +08:00
dap
330a8a1d70 Merge branch 'main' of https://github.com/vbenjs/vue-vben-admin 2024-10-13 14:38:56 +08:00
vben
9856bc88d2 chore: release v5.4.0 2024-10-13 14:21:54 +08:00
vben
68465b5fbf chore: release v5.4.0-beta.1 2024-10-13 14:17:28 +08:00
Vben
0ea0f204cb refactor: change the shadcn-ui directory and remove rarely used components (#4626) 2024-10-13 10:58:09 +08:00
dap
55611a1eb4 Merge branch 'main' of https://github.com/vbenjs/vue-vben-admin 2024-10-13 10:46:51 +08:00
dap
415176b2ee fix: 应为最大高度而非固定高度 2024-10-13 10:46:27 +08:00
dap
6677d5cfa8 feat: 验证码登录 2024-10-13 10:45:17 +08:00
dependabot[bot]
1b65254383 chore(deps): bump the non-breaking-changes group across 1 directory with 5 updates (#4618)
* chore(deps): bump the non-breaking-changes group across 1 directory with 5 updates

Bumps the non-breaking-changes group with 5 updates in the / directory:

| Package | From | To |
| --- | --- | --- |
| [cspell](https://github.com/streetsidesoftware/cspell/tree/HEAD/packages/cspell) | `8.14.4` | `8.15.0` |
| [vue](https://github.com/vuejs/core) | `3.5.11` | `3.5.12` |
| [sass](https://github.com/sass/dart-sass) | `1.79.4` | `1.79.5` |
| [vite-plugin-dts](https://github.com/qmhc/vite-plugin-dts) | `4.2.1` | `4.2.3` |
| [@vue/shared](https://github.com/vuejs/core/tree/HEAD/packages/shared) | `3.5.11` | `3.5.12` |



Updates `cspell` from 8.14.4 to 8.15.0
- [Release notes](https://github.com/streetsidesoftware/cspell/releases)
- [Changelog](https://github.com/streetsidesoftware/cspell/blob/main/packages/cspell/CHANGELOG.md)
- [Commits](https://github.com/streetsidesoftware/cspell/commits/v8.15.0/packages/cspell)

Updates `vue` from 3.5.11 to 3.5.12
- [Release notes](https://github.com/vuejs/core/releases)
- [Changelog](https://github.com/vuejs/core/blob/main/CHANGELOG.md)
- [Commits](https://github.com/vuejs/core/compare/v3.5.11...v3.5.12)

Updates `sass` from 1.79.4 to 1.79.5
- [Release notes](https://github.com/sass/dart-sass/releases)
- [Changelog](https://github.com/sass/dart-sass/blob/main/CHANGELOG.md)
- [Commits](https://github.com/sass/dart-sass/compare/1.79.4...1.79.5)

Updates `vite-plugin-dts` from 4.2.1 to 4.2.3
- [Release notes](https://github.com/qmhc/vite-plugin-dts/releases)
- [Changelog](https://github.com/qmhc/vite-plugin-dts/blob/main/CHANGELOG.md)
- [Commits](https://github.com/qmhc/vite-plugin-dts/compare/v4.2.1...v4.2.3)

Updates `@vue/shared` from 3.5.11 to 3.5.12
- [Release notes](https://github.com/vuejs/core/releases)
- [Changelog](https://github.com/vuejs/core/blob/main/CHANGELOG.md)
- [Commits](https://github.com/vuejs/core/commits/v3.5.12/packages/shared)

---
updated-dependencies:
- dependency-name: cspell
  dependency-type: direct:development
  update-type: version-update:semver-minor
  dependency-group: non-breaking-changes
- dependency-name: vue
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: non-breaking-changes
- dependency-name: sass
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: non-breaking-changes
- dependency-name: vite-plugin-dts
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: non-breaking-changes
- dependency-name: "@vue/shared"
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: non-breaking-changes
...

Signed-off-by: dependabot[bot] <support@github.com>

* chore: update deps

---------

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-10-13 10:26:51 +08:00
afe1
0a99f27127 chore: modify the type of the incoming function in the vite configuration (#4622)
* fix: viteconfig funciont type error

---------

Co-authored-by: afe1 <yunfei.zhu@nwowtec.com>
2024-10-13 10:01:18 +08:00
dap
89047a7dde chore: 字典相关逻辑 2024-10-12 14:10:19 +08:00
dap
329b41bc44 docs: changelog 2024-10-12 11:30:29 +08:00
dap
21e05a1bc8 Merge branch 'main' of https://gitee.com/dapppp/ruoyi-plus-vben5 2024-10-12 11:29:19 +08:00
dap
0a27114ea3 fix: 登出相关逻辑在并发(非await)情况下重复执行的问题 2024-10-12 11:29:06 +08:00
dap
113c2d60b5 fix: menu support i18n 2024-10-11 21:20:23 +08:00
dap
fbbb023971 chore: 注释 2024-10-11 21:05:25 +08:00
dap
1a6e5d22fd chore: v-access类型标注 2024-10-11 20:58:59 +08:00
dap
c5fcf50c76 chore: 必须深拷贝才能进行修改 2024-10-11 17:34:56 +08:00
dap
be06f4cb50 chore: 溢出展示形式 不加会有warning 2024-10-11 17:34:13 +08:00
dap
6f098cd5c0 fix: page table default params 2024-10-11 17:28:56 +08:00
dap
9288341c85 fix: 菜单选择的i18n处理 2024-10-11 13:44:08 +08:00
dap
2099e672ce chore: help message 2024-10-11 10:15:44 +08:00
dap
2eff47b59a fix: title 2024-10-11 10:08:29 +08:00
dap
27f62c2bab feat: 支持i18n菜单 2024-10-11 09:29:06 +08:00
dap
f707fcb3da Merge branch 'main' of https://github.com/vbenjs/vue-vben-admin 2024-10-11 08:43:19 +08:00
dap
26ac896f5c style: 优化代码 2024-10-11 08:08:19 +08:00
Vben
304b1b2efc fix: when a table switches paging, no form parameters will be carried (#4607)
* fix: when a table switches paging, no form parameters will be carried

* chore: typo
2024-10-10 22:48:25 +08:00
Vben
f923f59070 fix: metadata version number injection error (#4606)
* fix: metadata version number injection error

* chore: update deps
2024-10-10 22:30:50 +08:00
GavinLucky
437cb02e11 feat: preferences settings panel to add display switches with copyright (#4603)
* feat: preferences settings panel to add display switches with copyright

* feat: 更新 snapshots 测试用例

---------

Co-authored-by: ZhangYantao <Gavin@163.com>
2024-10-10 21:59:43 +08:00
dap
b20174d110 docs: 最低版本要求 2024-10-10 16:59:10 +08:00
dap
21015c6dd7 chore: 兼容写法(原方式在node22会报错) 2024-10-10 16:25:05 +08:00
dap
132e20f83e chore: remove pnpmlock 2024-10-10 16:11:28 +08:00
dap
663bad5e71 refactor: 文件类型判断 2024-10-10 15:06:32 +08:00
dap
26ee8e28ea refactor: upload component (wip) 2024-10-10 14:21:16 +08:00
dap
d10f950b6b chore: 菜单图标的提示信息 2024-10-10 13:31:36 +08:00
dap
60d513ce40 feat: 路由参数 2024-10-10 11:48:26 +08:00
Netfan
ba539f6793 chore: correct spelling for 'dragable' (#4600) 2024-10-10 10:55:52 +08:00
dap
fffe2d0db9 fix: 漏掉了登录日志日期查询 2024-10-10 09:46:42 +08:00
dap
2e8563d808 docs: changelog 2024-10-10 09:24:12 +08:00
dap
f8e480d724 chore: 需要重新加载字典 2024-10-10 09:23:18 +08:00
dap
e6374a6a06 feat: 自定义列 保存在localStorage 2024-10-10 09:13:50 +08:00
dap
521ba6af9c chore: vxe默认显示条数 2024-10-10 08:31:40 +08:00
dap
df79908056 feat: 操作日志 排序查询 2024-10-10 08:27:56 +08:00
dap
493a0effe8 perf: fetchUserInfo 2024-10-10 08:13:09 +08:00
dependabot[bot]
078f255e1a chore(deps-dev): bump vite-plugin-dts from 4.2.1 to 4.2.3 in the non-breaking-changes group (#4591)
* chore(deps-dev): bump vite-plugin-dts in the non-breaking-changes group

Bumps the non-breaking-changes group with 1 update: [vite-plugin-dts](https://github.com/qmhc/vite-plugin-dts).


Updates `vite-plugin-dts` from 4.2.1 to 4.2.3
- [Release notes](https://github.com/qmhc/vite-plugin-dts/releases)
- [Changelog](https://github.com/qmhc/vite-plugin-dts/blob/main/CHANGELOG.md)
- [Commits](https://github.com/qmhc/vite-plugin-dts/compare/v4.2.1...v4.2.3)

---
updated-dependencies:
- dependency-name: vite-plugin-dts
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: non-breaking-changes
...

Signed-off-by: dependabot[bot] <support@github.com>

* chore: update deps

---------

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-10-09 22:25:37 +08:00
dap
961f65215d feat: 图片上传/回显支持绑定string类型 2024-10-09 22:20:09 +08:00
Vben
ba4662522e fix: fix request not displaying interface error messages correctly (#4596) 2024-10-09 22:08:55 +08:00
yrming
8fe87b10dc fix(docs): typo (#4595) 2024-10-09 21:52:26 +08:00
dap
99a0c63f92 chore: remove log 2024-10-09 21:30:00 +08:00
dap
3b8b8812a5 docs: code gen docs 2024-10-09 17:55:31 +08:00
dap
836e845051 chore: docs 2024-10-09 17:51:39 +08:00
dap
2af01881ab chore: dataName 2024-10-09 16:46:06 +08:00
dap
cf53a4f3bd chore: 租户选择框浮层固定高度[256px] 超过高度自动滚动 2024-10-09 13:58:01 +08:00
dap
11a0b2f2f3 Merge branch 'main' of https://github.com/vbenjs/vue-vben-admin 2024-10-09 07:36:02 +08:00
Vben
2dbd323b2a fix: fix the form-api reactive failure inside the form (#4590)
* fix: fix the form-api reactive failure inside the form
2024-10-08 22:43:02 +08:00
dap
403b73978b Merge branch 'main' of https://github.com/vbenjs/vue-vben-admin 2024-10-08 16:54:11 +08:00
dap
f873b8ac3e chore: fixed tree search 2024-10-08 16:52:30 +08:00
dap
6cdcc8cbca chore: tree full content 2024-10-08 16:41:34 +08:00
dap
846bab7b7e feat: 新增从参数取默认密码 2024-10-08 15:17:18 +08:00
dap
a7b8b6efb0 docs: readme 2024-10-08 14:59:24 +08:00
dap
599623042c fix: wrong text 2024-10-08 14:46:52 +08:00
dap
deeae45823 fix: wrong api 2024-10-08 14:44:10 +08:00
dap
a93e3e976f docs: readme 2024-10-08 14:35:00 +08:00
dap
e8d0fd0e27 fix: 关闭租户时 获取的voList为null 需要添加可选链 2024-10-08 14:22:25 +08:00
dap
a0c2cffcab fix: 登出时清空字典会造成bug 2024-10-08 14:19:48 +08:00
dap
335dc0cc11 fix: 关闭租户不应显示同步租户字典按钮 2024-10-08 14:16:33 +08:00
dap
bda0711bbd fix: tenant disabled should hide select https://gitee.com/dapppp/ruoyi-plus-vben5/issues/IAVCBW 2024-10-08 14:09:44 +08:00
dap
8e7cf4b10e chore: vxe表格头部背景色 与antd保持一致 2024-10-08 14:01:18 +08:00
dependabot[bot]
8ad2b8665d chore(deps): bump @iconify/json from 2.2.256 to 2.2.257 in the non-breaking-changes group (#4589)
* chore(deps): bump @iconify/json in the non-breaking-changes group

Bumps the non-breaking-changes group with 1 update: [@iconify/json](https://github.com/iconify/icon-sets).


Updates `@iconify/json` from 2.2.256 to 2.2.257
- [Commits](https://github.com/iconify/icon-sets/compare/2.2.256...2.2.257)

---
updated-dependencies:
- dependency-name: "@iconify/json"
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: non-breaking-changes
...

Signed-off-by: dependabot[bot] <support@github.com>

* chore: update deps

---------

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-10-08 13:51:06 +08:00
634 changed files with 5697 additions and 25648 deletions

View File

@@ -16,7 +16,7 @@ categories:
- title: '🐞 Bug Fixes'
labels:
- 'bug'
- title: '📈 Performance'
- title: '📈 Performance & Enhancement'
labels:
- 'perf'
- 'enhancement'

1
.gitignore vendored
View File

@@ -20,6 +20,7 @@ dev-dist
.stylelintcache
yarn.lock
package-lock.json
pnpm-lock.yaml
.VSCodeCounter
**/backend-mock/data

14
.vscode/settings.json vendored
View File

@@ -197,11 +197,14 @@
"playground/src/locales/langs",
"apps/*/src/locales/langs"
],
"i18n-ally.pathMatcher": "{locale}.json",
"i18n-ally.enabledParsers": ["json", "ts", "js", "yaml"],
"i18n-ally.pathMatcher": "{locale}/{namespace}.{ext}",
"i18n-ally.enabledParsers": ["json"],
"i18n-ally.sourceLanguage": "en",
"i18n-ally.displayLanguage": "zh-CN",
"i18n-ally.enabledFrameworks": ["vue", "react"],
"i18n-ally.keystyle": "nested",
"i18n-ally.sortKeys": true,
"i18n-ally.namespace": true,
// 控制相关文件嵌套展示
"explorer.fileNesting.enabled": true,
@@ -216,11 +219,12 @@
"tailwind.config.mjs": "postcss.*"
},
"commentTranslate.hover.enabled": false,
"i18n-ally.keystyle": "nested",
"commentTranslate.multiLineMerge": true,
"vue.server.hybridMode": true,
"vitest.disableWorkspaceWarning": true,
"cSpell.words": ["tinymce"],
"cSpell.words": ["tinymce", "vditor"],
"typescript.tsdk": "node_modules/typescript/lib",
"editor.linkedEditing": true // 自动同步更改html标签
"editor.linkedEditing": true, // 自动同步更改html标签,
"vscodeCustomCodeColor.highlightValue": "v-access", // v-access显示的颜色
"vscodeCustomCodeColor.highlightValueColor": "#CCFFFF"
}

View File

@@ -1,6 +1,37 @@
1.0.0-beta (2024-10-8)
# 1.0.0
# FEATURES
**FEATURES**
- 用户管理 新增从参数取默认密码
- 全局表格加上id 方便进行缓存列排序的操作
- 支持菜单名称i18n
- 登录页 验证码登录
- Markdown编辑/预览组件(基于vditor)
- VxeTable搜索表单 enter提交
**BUG FIXES**
- 登录页面 关闭租户后下拉框没有正常隐藏
- 字典管理 关闭租户不应显示`同步租户字典`按钮
- 登录日志 漏掉了登录日志日期查询
- 登出相关逻辑在并发(非await)情况下重复执行的问题
- VxeTable在开启/关闭查询表单时 需要使用不同的padding
- VxeTable表格刷新 默认为reload 修改为在当前页刷新(query)
- 岗位管理 部门参数错误
- 角色管理 菜单分配 节点独立下的回显及提交问题
- 租户管理 套餐管理 回显时候`已选中节点`数量为0
- 用户管理 更新用户时打开drawer需要加载该部门下的岗位信息
**OTHERS**
- 登录页 租户选择框浮层固定高度[256px] 超过高度自动滚动
- 表单的Label默认方向改为`top` 支持\n换行
- 所有表格的搜索加上allowClear属性 支持清除
- vxe表格loading 只加载表格 不加载上面的表单
# 1.0.0-beta (2024-10-8)
**FEATURES**
- 基础功能已经开发完毕
- 工作流相关模块等待后端重构后开发

View File

@@ -134,7 +134,7 @@ If you think this project is helpful to you, you can help the author buy a cup o
![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>
<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

View File

@@ -2,10 +2,12 @@
## 提示
该仓库使用vben最新版本v5开发, 老版本v2地址 [前往](https://gitee.com/dapppp/ruoyi-plus-vben)
该仓库使用vben最新版本v5开发, ~~老版本v2地址(不维护)~~ [前往](https://gitee.com/dapppp/ruoyi-plus-vben)
v5版本采用分仓(包)目录结构, 具体开发路径为: `根目录/apps/web-antd`
目前对应后端版本: **5.2.3/2.2.3**
## 进度
**工作流相关模块等待后端重构后开发**
@@ -20,7 +22,7 @@ v5版本采用分仓(包)目录结构, 具体开发路径为: `根目录/apps/we
| 组件/框架 | 版本 |
| :------------- | :----- |
| vben | 5.3.2 |
| vben | 5.4.1 |
| ant-design-vue | 4.2.5 |
| vue | 3.5.11 |
@@ -38,11 +40,13 @@ admin 账号: admin admin123
## WX Group
暂不开放
演示站 - 微信群菜单
## 文档
[vben 文档地址](https://doc.vvbin.cn/)
[本框架文档 强烈建议阅读](https://dapdap.top/)
[Vben V5 文档地址](https://doc.vben.pro/)
[RuoYi-Plus 文档地址](https://plus-doc.dromara.org/#/)
@@ -57,8 +61,8 @@ admin 账号: admin admin123
```json
"packageManager": "pnpm",
"engines": {
"node": ">=20.10.0",
"pnpm": ">=9.5.0"
"node": ">=20.15.0",
"pnpm": "latest"
},
```
@@ -76,9 +80,13 @@ cd ruoyi-plus-vben5
pnpm install
```
- 菜单图标替换
[根目录/scripts/菜单图标替换sql/update_icon.sql](https://gitee.com/dapppp/ruoyi-plus-vben5/blob/main/scripts/%E8%8F%9C%E5%8D%95%E5%9B%BE%E6%A0%87%E6%9B%BF%E6%8D%A2sql/update_icon.sql)
- 关于代码生成
V5版本代码生成模板为付费功能(暂未开放)
V5版本代码生成模板为付费功能 [详见](https://dapdap.top/other/template.html)
- 关于一些监控的地址配置(微服务版本可以跳过这一小节)

View File

@@ -21,7 +21,7 @@ export default defineEventHandler(async (event) => {
if (!findUser) {
clearRefreshTokenCookie(event);
return forbiddenResponse(event);
return forbiddenResponse(event, 'Username or password is incorrect.');
}
const accessToken = generateAccessToken(findUser);

View File

@@ -86,7 +86,7 @@ const createDemosMenus = (role: 'admin' | 'super' | 'user') => {
component: '/demos/access/admin-visible',
meta: {
icon: 'mdi:button-cursor',
title: 'page.demos.access.adminVisible',
title: 'demos.access.adminVisible',
},
name: 'AccessAdminVisibleDemo',
path: '/demos/access/admin-visible',
@@ -95,7 +95,7 @@ const createDemosMenus = (role: 'admin' | 'super' | 'user') => {
component: '/demos/access/super-visible',
meta: {
icon: 'mdi:button-cursor',
title: 'page.demos.access.superVisible',
title: 'demos.access.superVisible',
},
name: 'AccessSuperVisibleDemo',
path: '/demos/access/super-visible',
@@ -104,7 +104,7 @@ const createDemosMenus = (role: 'admin' | 'super' | 'user') => {
component: '/demos/access/user-visible',
meta: {
icon: 'mdi:button-cursor',
title: 'page.demos.access.userVisible',
title: 'demos.access.userVisible',
},
name: 'AccessUserVisibleDemo',
path: '/demos/access/user-visible',
@@ -118,7 +118,7 @@ const createDemosMenus = (role: 'admin' | 'super' | 'user') => {
icon: 'ic:baseline-view-in-ar',
keepAlive: true,
order: 1000,
title: 'page.demos.title',
title: 'demos.title',
},
name: 'Demos',
path: '/demos',
@@ -129,7 +129,7 @@ const createDemosMenus = (role: 'admin' | 'super' | 'user') => {
path: '/demosaccess',
meta: {
icon: 'mdi:cloud-key-outline',
title: 'page.demos.access.backendPermissions',
title: 'demos.access.backendPermissions',
},
redirect: '/demos/access/page-control',
children: [
@@ -139,7 +139,7 @@ const createDemosMenus = (role: 'admin' | 'super' | 'user') => {
component: '/demos/access/index',
meta: {
icon: 'mdi:page-previous-outline',
title: 'page.demos.access.pageAccess',
title: 'demos.access.pageAccess',
},
},
{
@@ -148,7 +148,7 @@ const createDemosMenus = (role: 'admin' | 'super' | 'user') => {
component: '/demos/access/button-control',
meta: {
icon: 'mdi:button-cursor',
title: 'page.demos.access.buttonControl',
title: 'demos.access.buttonControl',
},
},
{
@@ -159,7 +159,7 @@ const createDemosMenus = (role: 'admin' | 'super' | 'user') => {
authority: ['no-body'],
icon: 'mdi:button-cursor',
menuVisibleWithForbidden: true,
title: 'page.demos.access.menuVisible403',
title: 'demos.access.menuVisible403',
},
},
roleWithMenus[role],

View File

@@ -39,14 +39,17 @@ export function useResponseError(message: string, error: any = null) {
};
}
export function forbiddenResponse(event: H3Event<EventHandlerRequest>) {
export function forbiddenResponse(
event: H3Event<EventHandlerRequest>,
message = 'Forbidden Exception',
) {
setResponseStatus(event, 403);
return useResponseError('ForbiddenException', 'Forbidden Exception');
return useResponseError(message, message);
}
export function unAuthorizedResponse(event: H3Event<EventHandlerRequest>) {
setResponseStatus(event, 401);
return useResponseError('UnauthorizedException', 'Unauthorized Exception');
return useResponseError('Unauthorized Exception', 'Unauthorized Exception');
}
export function sleep(ms: number) {

View File

@@ -25,7 +25,7 @@ VITE_GLOB_RSA_PUBLIC_KEY=MFwwDQYJKoZIhvcNAQEBBQADSwAwSAJBAKoR8mX0rGKLqzcWmOzbfj6
# RSA私钥 响应解密使用 注意这两个是两对RSA公私钥 请求加密-后端解密是一对 响应解密-后端加密是一对
VITE_GLOB_RSA_PRIVATE_KEY=MIIBVAIBADANBgkqhkiG9w0BAQEFAASCAT4wggE6AgEAAkEAmc3CuPiGL/LcIIm7zryCEIbl1SPzBkr75E2VMtxegyZ1lYRD+7TZGAPkvIsBcaMs6Nsy0L78n2qh+lIZMpLH8wIDAQABAkEAk82Mhz0tlv6IVCyIcw/s3f0E+WLmtPFyR9/WtV3Y5aaejUkU60JpX4m5xNR2VaqOLTZAYjW8Wy0aXr3zYIhhQQIhAMfqR9oFdYw1J9SsNc+CrhugAvKTi0+BF6VoL6psWhvbAiEAxPPNTmrkmrXwdm/pQQu3UOQmc2vCZ5tiKpW10CgJi8kCIFGkL6utxw93Ncj4exE/gPLvKcT+1Emnoox+O9kRXss5AiAMtYLJDaLEzPrAWcZeeSgSIzbL+ecokmFKSDDcRske6QIgSMkHedwND1olF8vlKsJUGK3BcdtM8w4Xq7BpSBwsloE=
# 客户端id
VITE_GLOB_APP_CLIENT_ID=6afcaa29272b14c1c87264950c726ef4
VITE_GLOB_APP_CLIENT_ID=e5cd7e4891bf95d1d19206ce24a7b32e
# 开启WEBSOCKET
VITE_GLOB_WEBSOCKET_ENABLE=false

View File

@@ -0,0 +1,136 @@
/**
* 通用组件共同的使用的基础组件,原先放在 adapter/form 内部,限制了使用范围,这里提取出来,方便其他地方使用
* 可用于 vben-form、vben-modal、vben-drawer 等组件使用,
*/
import type { BaseFormComponentType } from '@vben/common-ui';
import type { Component, SetupContext } from 'vue';
import { h } from 'vue';
import { globalShareState } from '@vben/common-ui';
import { $t } from '@vben/locales';
import {
AutoComplete,
Button,
Checkbox,
CheckboxGroup,
DatePicker,
Divider,
Input,
InputNumber,
InputPassword,
Mentions,
notification,
Radio,
RadioGroup,
RangePicker,
Rate,
Select,
Space,
Switch,
Textarea,
TimePicker,
TreeSelect,
Upload,
} from 'ant-design-vue';
import { Tinymce as RichTextarea } from '#/components/tinymce';
import { FileUpload, ImageUpload } from '#/components/upload';
const withDefaultPlaceholder = <T extends Component>(
component: T,
type: 'input' | 'select',
) => {
return (props: any, { attrs, slots }: Omit<SetupContext, 'expose'>) => {
const placeholder = props?.placeholder || $t(`ui.placeholder.${type}`);
return h(component, { ...props, ...attrs, placeholder }, slots);
};
};
// 这里需要自行根据业务组件库进行适配,需要用到的组件都需要在这里类型说明
export type ComponentType =
| 'AutoComplete'
| 'Checkbox'
| 'CheckboxGroup'
| 'DatePicker'
| 'DefaultButton'
| 'Divider'
| 'FileUpload'
| 'ImageUpload'
| 'Input'
| 'InputNumber'
| 'InputPassword'
| 'Mentions'
| 'PrimaryButton'
| 'Radio'
| 'RadioGroup'
| 'RangePicker'
| 'Rate'
| 'RichTextarea'
| 'Select'
| 'Space'
| 'Switch'
| 'Textarea'
| 'TimePicker'
| 'TreeSelect'
| 'Upload'
| BaseFormComponentType;
async function initComponentAdapter() {
const components: Partial<Record<ComponentType, Component>> = {
// 如果你的组件体积比较大,可以使用异步加载
// Button: () =>
// import('xxx').then((res) => res.Button),
AutoComplete,
Checkbox,
CheckboxGroup,
DatePicker,
// 自定义默认按钮
DefaultButton: (props, { attrs, slots }) => {
return h(Button, { ...props, attrs, type: 'default' }, slots);
},
Divider,
Input: withDefaultPlaceholder(Input, 'input'),
InputNumber: withDefaultPlaceholder(InputNumber, 'input'),
InputPassword: withDefaultPlaceholder(InputPassword, 'input'),
Mentions: withDefaultPlaceholder(Mentions, 'input'),
// 自定义主要按钮
PrimaryButton: (props, { attrs, slots }) => {
return h(Button, { ...props, attrs, type: 'primary' }, slots);
},
Radio,
RadioGroup,
RangePicker,
Rate,
Select: withDefaultPlaceholder(Select, 'select'),
Space,
Switch,
Textarea: withDefaultPlaceholder(Textarea, 'input'),
TimePicker,
TreeSelect: withDefaultPlaceholder(TreeSelect, 'select'),
Upload,
ImageUpload,
FileUpload,
RichTextarea,
};
// 将组件注册到全局共享状态中
globalShareState.setComponents(components);
// 定义全局共享状态中的消息提示
globalShareState.defineMessage({
// 复制成功消息提示
copyPreferencesSuccess: (title, content) => {
notification.success({
description: content,
message: title,
placement: 'bottomRight',
});
},
});
}
export { initComponentAdapter };

View File

@@ -1,115 +1,16 @@
import type {
BaseFormComponentType,
VbenFormSchema as FormSchema,
VbenFormProps,
} from '@vben/common-ui';
import type { Component, SetupContext } from 'vue';
import { h } from 'vue';
import type { ComponentType } from './component';
import { setupVbenForm, useVbenForm as useForm, z } from '@vben/common-ui';
import { $t } from '@vben/locales';
import {
AutoComplete,
Button,
Checkbox,
CheckboxGroup,
DatePicker,
Divider,
Input,
InputNumber,
InputPassword,
Mentions,
Radio,
RadioGroup,
RangePicker,
Rate,
Select,
Space,
Switch,
Textarea,
TimePicker,
TreeSelect,
Upload,
} from 'ant-design-vue';
import { isArray } from 'lodash-es';
import { Tinymce as RichTextarea } from '#/components/tinymce';
import { FileUpload, ImageUpload } from '#/components/upload';
// 这里需要自行根据业务组件库进行适配,需要用到的组件都需要在这里类型说明
export type FormComponentType =
| 'AutoComplete'
| 'Checkbox'
| 'CheckboxGroup'
| 'DatePicker'
| 'Divider'
| 'FileUpload'
| 'ImageUpload'
| 'Input'
| 'InputNumber'
| 'InputPassword'
| 'Mentions'
| 'Radio'
| 'RadioGroup'
| 'RangePicker'
| 'Rate'
| 'RichTextarea'
| 'Select'
| 'Space'
| 'Switch'
| 'Textarea'
| 'TimePicker'
| 'TreeSelect'
| 'Upload'
| BaseFormComponentType;
const withDefaultPlaceholder = <T extends Component>(
component: T,
type: 'input' | 'select',
) => {
return (props: any, { attrs, slots }: Omit<SetupContext, 'expose'>) => {
const placeholder = props?.placeholder || $t(`placeholder.${type}`);
return h(component, { ...props, ...attrs, placeholder }, slots);
};
};
// 初始化表单组件并注册到form组件内部
setupVbenForm<FormComponentType>({
components: {
AutoComplete,
Checkbox,
CheckboxGroup,
DatePicker,
// 自定义默认的重置按钮
DefaultResetActionButton: (props, { attrs, slots }) => {
return h(Button, { ...props, attrs, type: 'default' }, slots);
},
// 自定义默认的提交按钮
DefaultSubmitActionButton: (props, { attrs, slots }) => {
return h(Button, { ...props, attrs, type: 'primary' }, slots);
},
Divider,
Input: withDefaultPlaceholder(Input, 'input'),
InputNumber: withDefaultPlaceholder(InputNumber, 'input'),
InputPassword: withDefaultPlaceholder(InputPassword, 'input'),
Mentions: withDefaultPlaceholder(Mentions, 'input'),
Radio,
RadioGroup,
RangePicker,
Rate,
RichTextarea,
Select: withDefaultPlaceholder(Select, 'select'),
Space,
Switch,
Textarea: withDefaultPlaceholder(Textarea, 'input'),
TimePicker,
TreeSelect: withDefaultPlaceholder(TreeSelect, 'select'),
Upload,
ImageUpload,
FileUpload,
},
setupVbenForm<ComponentType>({
config: {
// ant design vue组件库默认都是 v-model:value
baseModelPropName: 'value',
@@ -127,7 +28,7 @@ setupVbenForm<FormComponentType>({
// 输入项目必填国际化适配
required: (value, _params, ctx) => {
if (value === undefined || value === null || value.length === 0) {
return $t('formRules.required', [ctx.label]);
return $t('ui.formRules.required', [ctx.label]);
}
return true;
},
@@ -137,17 +38,17 @@ setupVbenForm<FormComponentType>({
[false, null, undefined].includes(value) ||
(isArray(value) && value.length === 0)
) {
return $t('formRules.selectRequired', [ctx.label]);
return $t('ui.formRules.selectRequired', [ctx.label]);
}
return true;
},
},
});
const useVbenForm = useForm<FormComponentType>;
const useVbenForm = useForm<ComponentType>;
export { useVbenForm, z };
export type VbenFormSchema = FormSchema<FormComponentType>;
export type VbenFormSchema = FormSchema<ComponentType>;
export type { VbenFormProps };
export type FormSchemaGetter = () => VbenFormSchema[];

View File

@@ -1,2 +0,0 @@
export * from './form';
export * from './vxe-table';

View File

@@ -1,6 +1,10 @@
import { h } from 'vue';
import { h, type Ref } from 'vue';
import { setupVbenVxeTable, useVbenVxeGrid } from '@vben/plugins/vxe-table';
import {
setupVbenVxeTable,
useVbenVxeGrid,
type VxeGridDefines,
} from '@vben/plugins/vxe-table';
import { Button, Image } from 'ant-design-vue';
@@ -23,9 +27,13 @@ setupVbenVxeTable({
showActiveMsg: true,
showResponseMsg: false,
},
// 溢出展示形式
showOverflow: true,
pagerConfig: {
// pageSize: 10,
// pageSizes: [10, 20, 30, 40, 50],
// 默认条数
pageSize: 10,
// 分页可选条数
pageSizes: [10, 20, 30, 40, 50],
},
rowConfig: {
// 鼠标移入行显示 hover 样式
@@ -44,12 +52,20 @@ setupVbenVxeTable({
// 最大化
zoom: true,
// 刷新
refresh: true,
refresh: {
// 默认为reload 修改为在当前页刷新
code: 'query',
},
},
// 圆角按钮
round: true,
// 表格尺寸
size: 'medium',
customConfig: {
// 表格右上角自定义列配置 是否保存到localStorage
// 必须存在id参数才能使用
storage: false,
},
},
});
@@ -82,3 +98,17 @@ setupVbenVxeTable({
export { useVbenVxeGrid };
export type * from '@vben/plugins/vxe-table';
/**
* 通用的表格复选框是否选中事件
* @param checked 是否选中
* @returns function
*/
export function tableCheckboxEvent(checked: Ref<boolean>) {
const event: (params: VxeGridDefines.CheckboxChangeEventParams) => void = (
params,
) => {
checked.value = params.$table.getCheckboxRecords().length > 0;
};
return event;
}

View File

@@ -6,7 +6,6 @@ import { requestClient } from '#/api/request';
* @returns 上传结果
*/
export function uploadApi(file: Blob | File) {
console.log('uploadApi', file);
return requestClient.upload('/resource/oss/upload', { file });
}
/**

View File

@@ -39,7 +39,8 @@ export interface UserInfoResp {
/**
* 获取用户信息
* 存在返回null的情况(401) 不会抛出异常 需要手动抛异常
*/
export async function getUserInfoApi() {
return requestClient.get<UserInfoResp>('/system/user/getInfo');
return requestClient.get<null | UserInfoResp>('/system/user/getInfo');
}

View File

@@ -34,8 +34,11 @@ const { apiURL, clientId, enableEncrypt } = useAppConfig(
import.meta.env.PROD,
);
/** 控制是否弹窗 防止登录超时请求多个api会弹窗多次 */
let showTimeoutToast = true;
/**
* 是否已经处在登出过程中了 一个标志位
* 主要是防止一个页面会请求多个api 都401 会导致登出执行多次
*/
let isLogoutProcessing = false;
function createRequestClient(baseURL: string) {
const client = new RequestClient({
@@ -45,7 +48,7 @@ function createRequestClient(baseURL: string) {
errorMessageMode: 'message',
// 格式化提交参数时间
formatDate: true,
// 是否返回原生响应 比如:需要获取响应头时使用该属性
// 是否返回原生响应 比如:需要获取响应头时使用该属性
isReturnNativeResponse: false,
// 需要对返回数据进行处理
isTransformResponse: true,
@@ -96,11 +99,13 @@ function createRequestClient(baseURL: string) {
*/
const language = preferences.app.locale.replace('-', '_');
config.headers['Accept-Language'] = language;
// 添加全局clientId
config.headers.clientId = clientId;
const { encrypt, formatDate, joinParamsToUrl, joinTime = true } = config;
const params = config.params || {};
const data = config.data || false;
// TODO: 这块要重构 复杂度太高了
formatDate && data && !isString(data) && formatRequestDate(data);
if (config.method?.toUpperCase() === 'GET') {
if (isString(params)) {
@@ -142,7 +147,7 @@ function createRequestClient(baseURL: string) {
}
}
}
// 全局开启 && 该请求开启 && 是post/put请求
// 全局开启请求加密功能 && 该请求开启 && 是post/put请求
if (
enableEncrypt &&
encrypt &&
@@ -186,7 +191,7 @@ function createRequestClient(baseURL: string) {
}
const { isReturnNativeResponse, isTransformResponse } = response.config;
// 是否返回原生响应 比如:需要获取响应时使用该属性
// 是否返回原生响应 比如:需要获取响应时使用该属性
if (isReturnNativeResponse) {
return response;
}
@@ -221,7 +226,6 @@ function createRequestClient(baseURL: string) {
} else if (response.config.successMessageMode === 'message') {
message.success(successMsg);
}
// ruoyi-plus没有采用严格的{code, msg, data}模式
// 如果有data 直接返回data 没有data将剩余参数(...other)封装为data返回
// 需要考虑data为null的情况(比如查询为空)
if (data !== undefined) {
@@ -234,18 +238,16 @@ function createRequestClient(baseURL: string) {
let timeoutMsg = '';
switch (code) {
case 401: {
// 已经在登出过程中 不再执行
if (isLogoutProcessing) {
return;
}
isLogoutProcessing = true;
const _msg = '登录超时, 请重新登录';
const userStore = useAuthStore();
userStore.logout().then(() => {
/** 只弹窗一次 */
if (showTimeoutToast) {
showTimeoutToast = false;
message.error(_msg);
/** 定时器 3s后再开启弹窗 */
setTimeout(() => {
showTimeoutToast = true;
}, 3000);
}
userStore.logout().finally(() => {
message.error(_msg);
isLogoutProcessing = false;
});
// 不再执行下面逻辑
return;

View File

@@ -37,5 +37,5 @@ export function clientChangeStatus(data: any) {
}
export function clientRemove(ids: IDS) {
return requestClient.deleteWithMsg(`${Api.root}/${ids.join(',')}`);
return requestClient.deleteWithMsg(`${Api.root}/${ids}`);
}

View File

@@ -44,7 +44,7 @@ export function dictDataExport(data: any) {
* @returns void
*/
export function dictDataRemove(dictIds: IDS) {
return requestClient.deleteWithMsg<void>(`${Api.root}/${dictIds.join(',')}`);
return requestClient.deleteWithMsg<void>(`${Api.root}/${dictIds}`);
}
/**

View File

@@ -37,7 +37,7 @@ export function dictTypeExport(data: any) {
* @returns void
*/
export function dictTypeRemove(dictIds: IDS) {
return requestClient.deleteWithMsg<void>(`${Api.root}/${dictIds.join(',')}`);
return requestClient.deleteWithMsg<void>(`${Api.root}/${dictIds}`);
}
/**

View File

@@ -8,10 +8,14 @@ import '@vben/styles/antd';
import { setupGlobalComponent } from '#/components/global';
import { setupI18n } from '#/locales';
import { initComponentAdapter } from './adapter/component';
import App from './app.vue';
import { router } from './router';
async function bootstrap(namespace: string) {
// 初始化组件适配器
await initComponentAdapter();
const app = createApp(App);
// 全局组件

View File

@@ -41,11 +41,14 @@ const label = computed<number | string>(() => {
const current = props.dicts.find((item) => item.dictValue == props.value);
return current?.dictLabel ?? 'unknown';
});
const tagComponent = computed(() => (color.value ? Tag : 'div'));
</script>
<template>
<div>
<Tag v-if="color" :class="cssClass" :color="color">{{ label }}</Tag>
<div v-if="!color" :class="cssClass">{{ label }}</div>
<component :is="tagComponent" :class="cssClass" :color="color">
{{ label }}
</component>
</div>
</template>

View File

@@ -75,7 +75,6 @@ const onSelected: SelectHandler = async (tenantId: string, option: any) => {
async function onDeselect() {
await tenantDynamicClear();
dictStore.resetCache();
message.success($t('component.tenantToggle.reset'));
lastSelected.value = '';
close(false);

View File

@@ -74,24 +74,27 @@ const checkedRealKeys = ref<(number | string)[]>([]);
* 取第一次的menuTree id 设置到checkedMenuKeys
* 主要为了解决没有任何修改 直接点击保存的情况
*/
const stop = watch(
() => props.treeData,
() => {
/** 节点关联情况下是不带父节点的 */
if (props.checkStrictly) {
/** 找到父节点 添加上 */
const parentIds = findGroupParentIds(
props.treeData,
checkedKeys.value as any,
);
checkedRealKeys.value = [...parentIds, ...checkedKeys.value];
} else {
/** 节点独立 这里是全部的节点 */
checkedRealKeys.value = checkedKeys.value;
}
const stop = watch([checkedKeys, () => props.treeData], () => {
if (
props.checkStrictly &&
checkedKeys.value.length > 0 &&
props.treeData.length > 0
) {
/** 找到父节点 添加上 */
const parentIds = findGroupParentIds(
props.treeData,
checkedKeys.value as any,
{ id: props.fieldNames.key },
);
checkedRealKeys.value = [...parentIds, ...checkedKeys.value];
stop();
},
);
}
if (!props.checkStrictly && checkedKeys.value.length > 0) {
/** 节点独立 这里是全部的节点 */
checkedRealKeys.value = checkedKeys.value;
stop();
}
});
/**
*

View File

@@ -16,7 +16,7 @@ import { checkFileType } from './helper';
import { UploadResultStatus } from './typing';
import { useUploadType } from './use-upload';
defineOptions({ name: 'FileUpload' });
defineOptions({ name: 'FileUpload', inheritAttrs: false });
const props = withDefaults(
defineProps<{
@@ -95,7 +95,6 @@ watch(
return null;
}) as UploadProps['fileList'];
}
emit('update:value', value);
if (!isFirstRender.value) {
emit('change', value);
isFirstRender.value = false;
@@ -119,9 +118,9 @@ const handleRemove = async (file: UploadFile) => {
}
};
const beforeUpload = (file: File) => {
const beforeUpload = async (file: File) => {
const { maxSize, accept } = props;
const isAct = checkFileType(file, accept);
const isAct = await checkFileType(file, accept);
if (!isAct) {
message.error($t('component.upload.acceptUpload', [accept]));
isActMsg.value = false;
@@ -169,6 +168,10 @@ function getValue() {
if (item?.response && props?.resultField) {
return item?.response?.[props.resultField];
}
// 适用于已经有图片 回显的情况 会默认在init处理为{url: 'xx'}
if (item?.url) {
return item.url;
}
// 注意这里取的key为 url
return item?.response?.url;
});

View File

@@ -1,33 +1,51 @@
export function checkFileType(file: File, accepts: string[]) {
console.log(file.name, accepts);
let reg;
if (!accepts || accepts.length === 0) {
reg = /.(?:jpg|jpeg|png|gif|webp)$/i;
} else {
const newTypes = accepts.join('|');
reg = new RegExp(`${String.raw`\.(` + newTypes})$`, 'i');
import { fileTypeFromBlob } from '@vben/utils';
/**
* 不支持txt文件 @see https://github.com/sindresorhus/file-type/issues/55
* 需要自行修改
* @param file file对象
* @param accepts 文件类型数组 包括拓展名(不带点) 文件头(image/png等 不包括泛写法即image/*)
* @returns 是否通过文件类型校验
*/
export async function checkFileType(file: File, accepts: string[]) {
if (!accepts || accepts?.length === 0) {
return true;
}
return reg.test(file.name);
console.log(file);
const fileType = await fileTypeFromBlob(file);
if (!fileType) {
console.error('无法获取文件类型');
return false;
}
console.log('文件类型', fileType);
// 是否文件拓展名/文件头任意有一个匹配
return accepts.includes(fileType.ext) || accepts.includes(fileType.mime);
}
export function checkImgType(file: File) {
return isImgTypeByName(file.name);
}
export function isImgTypeByName(name: string) {
return /\.(?:jpg|jpeg|png|gif|webp)$/i.test(name);
}
export function getBase64WithFile(file: File) {
return new Promise<{
file: File;
result: string;
}>((resolve, reject) => {
const reader = new FileReader();
reader.readAsDataURL(file);
reader.addEventListener('load', () =>
resolve({ result: reader.result as string, file }),
);
reader.addEventListener('error', (error) => reject(error));
});
/**
* 默认图片类型
*/
export const defaultImageAccept = ['jpg', 'jpeg', 'png', 'gif', 'webp'];
/**
* 判断文件类型是否符合要求
* @param file file对象
* @param accepts 文件类型数组 包括拓展名(不带点) 文件头(image/png等 不包括泛写法即image/*)
* @returns 是否通过文件类型校验
*/
export async function checkImageFileType(file: File, accepts: string[]) {
// 空的accepts 使用默认规则
if (!accepts || accepts.length === 0) {
accepts = defaultImageAccept;
}
const fileType = await fileTypeFromBlob(file);
if (!fileType) {
console.error('无法获取文件类型');
return false;
}
console.log('文件类型', fileType);
// 是否文件拓展名/文件头任意有一个匹配
if (accepts.includes(fileType.ext) || accepts.includes(fileType.mime)) {
return true;
}
return false;
}

View File

@@ -12,18 +12,16 @@ import { isArray, isFunction, isObject, isString } from 'lodash-es';
import { uploadApi } from '#/api';
import { checkFileType } from './helper';
import { checkImageFileType, defaultImageAccept } from './helper';
import { UploadResultStatus } from './typing';
import { useUploadType } from './use-upload';
defineOptions({ name: 'ImageUpload' });
defineOptions({ name: 'ImageUpload', inheritAttrs: false });
const props = withDefaults(
defineProps<{
/**
* 建议使用拓展名(不带.)
* 或者文件头 image/png等(测试判断不准确) 不支持image/*类似的写法
* 需自行改造 ./helper/checkFileType方法
* 包括拓展名(不带点) 文件头(image/png等 不包括泛写法即image/*)
*/
accept?: string[];
api?: (...args: any[]) => Promise<any>;
@@ -40,7 +38,7 @@ const props = withDefaults(
// support xxx.xxx.xx
// 返回的字段 默认url
resultField?: 'fileName' | 'ossId' | 'url' | string;
value?: string[];
value?: string | string[];
}>(),
{
value: () => [],
@@ -49,7 +47,7 @@ const props = withDefaults(
helpText: '',
maxSize: 2,
maxNumber: 1,
accept: () => [],
accept: () => defaultImageAccept,
multiple: false,
api: uploadApi,
resultField: '',
@@ -81,14 +79,18 @@ watch(
isInnerOperate.value = false;
return;
}
let value: string[] = [];
let value: string | string[] = [];
if (v) {
if (isArray(v)) {
value = v;
} else {
value.push(v);
const _fileList: string[] = [];
if (isString(v)) {
_fileList.push(v);
}
fileList.value = value.map((item, i) => {
if (isArray(v)) {
_fileList.push(...v);
}
// 直接赋值 可能为string | string[]
value = v;
fileList.value = _fileList.map((item, i) => {
if (item && isString(item)) {
return {
uid: `${-i}`,
@@ -102,7 +104,6 @@ watch(
return null;
}) as UploadProps['fileList'];
}
emit('update:value', value);
if (!isFirstRender.value) {
emit('change', value);
isFirstRender.value = false;
@@ -155,9 +156,9 @@ const handleCancel = () => {
previewTitle.value = '';
};
const beforeUpload = (file: File) => {
const beforeUpload = async (file: File) => {
const { maxSize, accept } = props;
const isAct = checkFileType(file, accept);
const isAct = await checkImageFileType(file, accept);
if (!isAct) {
message.error($t('component.upload.acceptUpload', [accept]));
isActMsg.value = false;
@@ -205,9 +206,21 @@ function getValue() {
if (item?.response && props?.resultField) {
return item?.response?.[props.resultField];
}
// 适用于已经有图片 回显的情况 会默认在init处理为{url: 'xx'}
if (item?.url) {
return item.url;
}
// 注意这里取的key为 url
return item?.response?.url;
});
// 只有一张图片 默认绑定string而非string[]
if (props.maxNumber === 1 && list.length === 1) {
return list[0];
}
// 只有一张图片 && 删除图片时 可自行修改
if (props.maxNumber === 1 && list.length === 0) {
return '';
}
return list;
}
</script>

View File

@@ -41,14 +41,14 @@ const menus = computed(() => {
});
},
icon: BookOpenText,
text: $t('widgets.document'),
text: $t('ui.widgets.document'),
},
{
handler: () => {
router.push('/profile');
},
icon: ProfileIcon,
text: $t('widgets.profile'),
text: $t('ui.widgets.profile'),
},
{
handler: () => {
@@ -66,7 +66,7 @@ const menus = computed(() => {
});
},
icon: CircleHelp,
text: $t('widgets.qa'),
text: $t('ui.widgets.qa'),
},
];
/**

View File

@@ -4,7 +4,11 @@ import type { Locale } from 'ant-design-vue/es/locale';
import type { App } from 'vue';
import { ref } from 'vue';
import { $t, setupI18n as coreSetup, loadLocalesMap } from '@vben/locales';
import {
$t,
setupI18n as coreSetup,
loadLocalesMapFromDir,
} from '@vben/locales';
import { preferences } from '@vben/preferences';
import antdEnLocale from 'ant-design-vue/es/locale/en_US';
@@ -13,10 +17,12 @@ import dayjs from 'dayjs';
const antdLocale = ref<Locale>(antdDefaultLocale);
const modules = import.meta.glob('./langs/*.json');
const localesMap = loadLocalesMap(modules);
const modules = import.meta.glob('./langs/**/*.json');
const localesMap = loadLocalesMapFromDir(
/\.\/langs\/([^/]+)\/(.*)\.json$/,
modules,
);
/**
* 加载应用特有的语言包
* 这里也可以改造为从服务端获取翻译数据
@@ -45,14 +51,14 @@ async function loadThirdPartyMessage(lang: SupportedLanguagesType) {
async function loadDayjsLocale(lang: SupportedLanguagesType) {
let locale;
switch (lang) {
case 'zh-CN': {
locale = await import('dayjs/locale/zh-cn');
break;
}
case 'en-US': {
locale = await import('dayjs/locale/en');
break;
}
case 'zh-CN': {
locale = await import('dayjs/locale/zh-cn');
break;
}
// 默认使用英语
default: {
locale = await import('dayjs/locale/en');
@@ -71,14 +77,14 @@ async function loadDayjsLocale(lang: SupportedLanguagesType) {
*/
async function loadAntdLocale(lang: SupportedLanguagesType) {
switch (lang) {
case 'zh-CN': {
antdLocale.value = antdDefaultLocale;
break;
}
case 'en-US': {
antdLocale.value = antdEnLocale;
break;
}
case 'zh-CN': {
antdLocale.value = antdDefaultLocale;
break;
}
}
}

View File

@@ -1,86 +0,0 @@
{
"page": {
"demos": {
"title": "Demos",
"antd": "Ant Design Vue"
}
},
"component": {
"cropper": {
"selectImage": "Select Image",
"uploadSuccess": "Uploaded success!",
"imageTooBig": "Image too big",
"modalTitle": "Avatar upload",
"okText": "Confirm and upload",
"btn_reset": "Reset",
"btn_rotate_left": "Counterclockwise rotation",
"btn_rotate_right": "Clockwise rotation",
"btn_scale_x": "Flip horizontal",
"btn_scale_y": "Flip vertical",
"btn_zoom_in": "Zoom in",
"btn_zoom_out": "Zoom out",
"preview": "Preview"
},
"tenantToggle": {
"placeholder": "Please select a tenant",
"switch": "Switch to tenant: ",
"reset": "Reset to default tenant"
},
"notice": {
"title": "Notice",
"received": "You have received a new message"
},
"upload": {
"save": "Save",
"upload": "Upload",
"imgUpload": "ImageUpload",
"uploaded": "Uploaded",
"operating": "Operating",
"del": "Delete",
"download": "download",
"saveWarn": "Please wait for the file to upload and save!",
"saveError": "There is no file successfully uploaded and cannot be saved!",
"preview": "Preview",
"choose": "Select the file",
"accept": "Support {0} format",
"acceptUpload": "Only upload files in {0} format",
"maxSize": "A single file does not exceed {0}MB ",
"maxSizeMultiple": "Only upload files up to {0}MB!",
"maxNumber": "Only upload up to {0} files",
"legend": "Legend",
"fileName": "File name",
"fileSize": "File size",
"fileStatue": "File status",
"pending": "Pending",
"startUpload": "Start upload",
"uploadSuccess": "Upload successfully",
"uploadError": "Upload failed",
"uploading": "Uploading",
"uploadWait": "Please wait for the file upload to finish",
"reUploadFailed": "Re-upload failed files"
}
},
"pages": {
"common": {
"add": "Add",
"edit": "Edit",
"delete": "Delete",
"more": "More",
"search": "Search",
"reset": "Reset",
"import": "Import",
"export": "Export",
"expand": "Expand",
"collapse": "Collapse",
"info": "Info",
"clear": "Clear",
"unlock": "Unlock",
"download": "Download",
"sync": "Sync",
"refresh": "Refresh",
"generate": "Generate",
"downloadLoading": "Downloading... Please wait.",
"preview": "Preview"
}
}
}

View File

@@ -0,0 +1,55 @@
{
"cropper": {
"selectImage": "Select Image",
"uploadSuccess": "Uploaded success!",
"imageTooBig": "Image too big",
"modalTitle": "Avatar upload",
"okText": "Confirm and upload",
"btn_reset": "Reset",
"btn_rotate_left": "Counterclockwise rotation",
"btn_rotate_right": "Clockwise rotation",
"btn_scale_x": "Flip horizontal",
"btn_scale_y": "Flip vertical",
"btn_zoom_in": "Zoom in",
"btn_zoom_out": "Zoom out",
"preview": "Preview"
},
"tenantToggle": {
"placeholder": "Please select a tenant",
"switch": "Switch to tenant: ",
"reset": "Reset to default tenant"
},
"notice": {
"title": "Notice",
"received": "You have received a new message"
},
"upload": {
"save": "Save",
"upload": "Upload",
"imgUpload": "ImageUpload",
"uploaded": "Uploaded",
"operating": "Operating",
"del": "Delete",
"download": "download",
"saveWarn": "Please wait for the file to upload and save!",
"saveError": "There is no file successfully uploaded and cannot be saved!",
"preview": "Preview",
"choose": "Select the file",
"accept": "Support {0} format",
"acceptUpload": "Only upload files in {0} format",
"maxSize": "A single file does not exceed {0}MB ",
"maxSizeMultiple": "Only upload files up to {0}MB!",
"maxNumber": "Only upload up to {0} files",
"legend": "Legend",
"fileName": "File name",
"fileSize": "File size",
"fileStatue": "File status",
"pending": "Pending",
"startUpload": "Start upload",
"uploadSuccess": "Upload successfully",
"uploadError": "Upload failed",
"uploading": "Uploading",
"uploadWait": "Please wait for the file upload to finish",
"reUploadFailed": "Re-upload failed files"
}
}

View File

@@ -0,0 +1,12 @@
{
"title": "Demos",
"antd": "Ant Design Vue",
"vben": {
"title": "Project",
"about": "About",
"document": "Document",
"antdv": "Ant Design Vue Version",
"naive-ui": "Naive UI Version",
"element-plus": "Element Plus Version"
}
}

View File

@@ -0,0 +1,55 @@
{
"root": "Root",
"system": {
"root": "System",
"user": "User",
"role": "Role",
"menu": "Menu",
"dept": "Department",
"post": "Post",
"dict": "Dictionary",
"config": "Parameter Settings",
"notice": "Notifications",
"log": {
"root": "Log",
"operation": "Operation Log",
"login": "Login Log"
},
"oss": "File",
"client": "Client"
},
"tenant": {
"root": "Tenant",
"package": "Package"
},
"monitor": {
"root": "System Monitoring",
"online": "Online Users",
"cache": "Cache Monitoring",
"admin": "Admin Monitoring",
"job": "Task Scheduling Center"
},
"tool": {
"root": "System Tools",
"gen": "Code Generation"
},
"workflow": {
"root": "Workflow",
"category": "Process Category",
"model": "Model",
"define": "Process Definition",
"monitor": {
"root": "Process Monitoring",
"instance": "Process Instance",
"todo": "Pending Tasks"
},
"form": "Form"
},
"task": {
"root": "My Tasks",
"apply": "My Initiated Tasks",
"todo": "My Pending Tasks",
"done": "My Completed Tasks",
"cc": "My CC"
}
}

View File

@@ -0,0 +1,15 @@
{
"auth": {
"login": "Login",
"register": "Register",
"codeLogin": "Code Login",
"qrcodeLogin": "Qr Code Login",
"forgetPassword": "Forget Password",
"oauthLogin": "Oauth Login"
},
"dashboard": {
"title": "Dashboard",
"analytics": "Analytics",
"workspace": "Workspace"
}
}

View File

@@ -0,0 +1,23 @@
{
"common": {
"add": "Add",
"edit": "Edit",
"delete": "Delete",
"more": "More",
"search": "Search",
"reset": "Reset",
"import": "Import",
"export": "Export",
"expand": "Expand",
"collapse": "Collapse",
"info": "Info",
"clear": "Clear",
"unlock": "Unlock",
"download": "Download",
"sync": "Sync",
"refresh": "Refresh",
"generate": "Generate",
"downloadLoading": "Downloading... Please wait.",
"preview": "Preview"
}
}

View File

@@ -1,86 +0,0 @@
{
"page": {
"demos": {
"title": "演示",
"antd": "Ant Design Vue"
}
},
"component": {
"cropper": {
"selectImage": "选择图片",
"uploadSuccess": "上传成功",
"imageTooBig": "图片超限",
"modalTitle": "头像上传",
"okText": "确认并上传",
"btn_reset": "重置",
"btn_rotate_left": "逆时针旋转",
"btn_rotate_right": "顺时针旋转",
"btn_scale_x": "水平翻转",
"btn_scale_y": "垂直翻转",
"btn_zoom_in": "放大",
"btn_zoom_out": "缩小",
"preview": "预览"
},
"tenantToggle": {
"placeholder": "选择租户",
"switch": "切换当前租户为: ",
"reset": "还原为默认租户"
},
"notice": {
"title": "消息",
"received": "收到新消息"
},
"upload": {
"save": "保存",
"upload": "上传",
"imgUpload": "图片上传",
"uploaded": "已上传",
"operating": "操作",
"del": "删除",
"download": "下载",
"saveWarn": "请等待文件上传后,保存!",
"saveError": "没有上传成功的文件,无法保存!",
"preview": "预览",
"choose": "选择文件",
"accept": "支持{0}格式",
"acceptUpload": "只能上传{0}格式文件",
"maxSize": "单个文件不超过{0}MB",
"maxSizeMultiple": "只能上传不超过{0}MB的文件!",
"maxNumber": "最多只能上传{0}个文件",
"legend": "略缩图",
"fileName": "文件名",
"fileSize": "文件大小",
"fileStatue": "状态",
"pending": "待上传",
"startUpload": "开始上传",
"uploadSuccess": "上传成功",
"uploadError": "上传失败",
"uploading": "上传中",
"uploadWait": "请等待文件上传结束后操作",
"reUploadFailed": "重新上传失败文件"
}
},
"pages": {
"common": {
"add": "新增",
"edit": "编辑",
"delete": "删除",
"more": "更多",
"search": "搜索",
"reset": "重置",
"import": "导入",
"export": "导出",
"expand": "展开",
"collapse": "收起",
"info": "详情",
"clear": "清空",
"unlock": "解锁",
"download": "下载",
"sync": "同步",
"refresh": "刷新",
"generate": "生成",
"downloadLoading": "下载中, 请稍后...",
"preview": "预览"
}
}
}

View File

@@ -0,0 +1,55 @@
{
"cropper": {
"selectImage": "选择图片",
"uploadSuccess": "上传成功",
"imageTooBig": "图片超限",
"modalTitle": "头像上传",
"okText": "确认并上传",
"btn_reset": "重置",
"btn_rotate_left": "逆时针旋转",
"btn_rotate_right": "顺时针旋转",
"btn_scale_x": "水平翻转",
"btn_scale_y": "垂直翻转",
"btn_zoom_in": "放大",
"btn_zoom_out": "缩小",
"preview": "预览"
},
"tenantToggle": {
"placeholder": "选择租户",
"switch": "切换当前租户为: ",
"reset": "还原为默认租户"
},
"notice": {
"title": "消息",
"received": "收到新消息"
},
"upload": {
"save": "保存",
"upload": "上传",
"imgUpload": "图片上传",
"uploaded": "已上传",
"operating": "操作",
"del": "删除",
"download": "下载",
"saveWarn": "请等待文件上传后,保存!",
"saveError": "没有上传成功的文件,无法保存!",
"preview": "预览",
"choose": "选择文件",
"accept": "支持{0}格式",
"acceptUpload": "只能上传{0}格式文件",
"maxSize": "单个文件不超过{0}MB",
"maxSizeMultiple": "只能上传不超过{0}MB的文件!",
"maxNumber": "最多只能上传{0}个文件",
"legend": "略缩图",
"fileName": "文件名",
"fileSize": "文件大小",
"fileStatue": "状态",
"pending": "待上传",
"startUpload": "开始上传",
"uploadSuccess": "上传成功",
"uploadError": "上传失败",
"uploading": "上传中",
"uploadWait": "请等待文件上传结束后操作",
"reUploadFailed": "重新上传失败文件"
}
}

View File

@@ -0,0 +1,12 @@
{
"title": "演示",
"antd": "Ant Design Vue",
"vben": {
"title": "项目",
"about": "关于",
"document": "文档",
"antdv": "Ant Design Vue 版本",
"naive-ui": "Naive UI 版本",
"element-plus": "Element Plus 版本"
}
}

View File

@@ -0,0 +1,55 @@
{
"root": "根目录",
"system": {
"root": "系统管理",
"user": "用户管理",
"role": "角色管理",
"menu": "菜单管理",
"dept": "部门管理",
"post": "岗位管理",
"dict": "字典管理",
"config": "参数设置",
"notice": "通知公告",
"log": {
"root": "日志管理",
"operation": "操作日志",
"login": "登录日志"
},
"oss": "文件管理",
"client": "客户端管理"
},
"tenant": {
"root": "租户管理",
"package": "套餐管理"
},
"monitor": {
"root": "系统监控",
"online": "在线用户",
"cache": "缓存监控",
"admin": "Admin监控",
"job": "任务调度中心"
},
"tool": {
"root": "系统工具",
"gen": "代码生成"
},
"workflow": {
"root": "工作流",
"category": "流程分类",
"model": "模型管理",
"define": "流程定义",
"monitor": {
"root": "流程监控",
"instance": "流程实例",
"todo": "待办任务"
},
"form": "表单管理"
},
"task": {
"root": "我的任务",
"apply": "我发起的",
"todo": "我的待办",
"done": "我的已办",
"cc": "我的抄送"
}
}

View File

@@ -0,0 +1,15 @@
{
"auth": {
"login": "登录",
"register": "注册",
"codeLogin": "验证码登录",
"qrcodeLogin": "二维码登录",
"forgetPassword": "忘记密码",
"oauthLogin": "第三方登录"
},
"dashboard": {
"title": "概览",
"analytics": "分析页",
"workspace": "工作台"
}
}

View File

@@ -0,0 +1,23 @@
{
"common": {
"add": "新增",
"edit": "编辑",
"delete": "删除",
"more": "更多",
"search": "搜索",
"reset": "重置",
"import": "导入",
"export": "导出",
"expand": "展开",
"collapse": "收起",
"info": "详情",
"clear": "清空",
"unlock": "解锁",
"download": "下载",
"sync": "同步",
"refresh": "刷新",
"generate": "生成",
"downloadLoading": "下载中, 请稍后...",
"preview": "预览"
}
}

View File

@@ -20,6 +20,12 @@ export const overridesPreferences = defineOverridesPreferences({
*/
// defaultAvatar: '',
name: import.meta.env.VITE_APP_TITLE,
/**
* 不支持modal模式 需要改动的地方太多
* 1. 正常重新登录后不会再触发接口请求 即触发登录超时的页面为空数据
* 2. 切换租户登录后不会重新加载菜单
*/
// loginExpiredMode: 'modal',
},
footer: {
/**

View File

@@ -19,6 +19,170 @@ import { localMenuList } from './routes/local';
const forbiddenComponent = () => import('#/views/_core/fallback/forbidden.vue');
const NotFoundComponent = () => import('#/views/_core/fallback/not-found.vue');
/**
* 后台路由转vben路由
* @param menuList 后台菜单
* @param parentPath 上级目录
* @returns vben路由
*/
function backMenuToVbenMenu(
menuList: Menu[],
parentPath = '',
): RouteRecordStringComponent[] {
const resultList: RouteRecordStringComponent[] = [];
menuList.forEach((menu) => {
// 根目录为菜单形式
// 固定有一个children children为当前菜单
if (menu.path === '/' && menu.children && menu.children.length === 1) {
if (!menu.children || !menu.children[0]) {
return;
}
// 需要处理根目录为内嵌的情况 不会带InnerLink
if (/^https?:\/\//.test(menu.children[0].path)) {
menu.children[0].component = 'InnerLink';
menu.children[0].path = menu.children[0].path
.replaceAll(/^https?:\/\//g, '')
.replaceAll('/#/', '')
.replaceAll('#', '')
.replaceAll(/[?&]/g, '');
}
// 取子路径作为父级路径
const path = menu.children[0].path;
// 取子菜单的meta作为当前菜单的meta
menu.meta = menu.children[0].meta;
// 由于在一级路由 父级路径需要加上/
menu.path = `/${path}`;
menu.component = 'RootMenu';
// 将子路径设置为''
menu.children[0].path = '';
}
// 外链: http开头 & 组件为Layout || ParentView
// 正则判断是否为http://或者https://开头
if (
/^https?:\/\//.test(menu.path) &&
(menu.component === 'Layout' || menu.component === 'ParentView')
) {
menu.component = 'Link';
}
// 内嵌iframe 组件为InnerLink
if (menu.meta?.link && menu.component === 'InnerLink') {
menu.component = 'IFrameView';
}
/**
* 拼接path
* menu.path为''(根目录路由) 则不拼接
*/
if (parentPath && menu.path) {
menu.path = `${parentPath}/${menu.path}`;
}
// 创建vben路由对象
const vbenRoute: RouteRecordStringComponent = {
component: menu.component,
meta: {
// 当前路由不在菜单显示 但是可以通过链接访问
// 不可访问的路由由后端控制隐藏(不返回对应路由)
hideInMenu: menu.hidden,
icon: menu.meta?.icon,
keepAlive: !menu.meta?.noCache,
title: menu.meta?.title,
},
name: menu.name,
path: menu.path,
};
// 添加路由参数信息
if (menu.query) {
try {
const query = JSON.parse(menu.query);
vbenRoute.meta && (vbenRoute.meta.query = query);
} catch {
console.error('错误的路由参数类型, 必须为[json]格式');
}
}
/**
* 处理不同组件
*/
switch (menu.component) {
/**
* iframe内嵌
*/
case 'IFrameView': {
vbenRoute.component = 'IFrameView';
if (vbenRoute.meta) {
vbenRoute.meta.iframeSrc = menu.meta.link;
}
/**
* 需要判断特殊情况 比如vue的hash是带#的
* 比如链接 aaa.com/#/bbb path会转换为 aaa/com/#/bbb
* 比如链接 aaa.com/?bbb=xxx
* 需要去除# 否则无法被添加到路由
*/
vbenRoute.path = vbenRoute.path
// 替换https:// 或者 http://
.replaceAll(/^https?:\/\//g, '')
.replaceAll('/#/', '')
.replaceAll('#', '')
.replaceAll(/[?&]/g, '');
break;
}
case 'Layout': {
vbenRoute.component = 'BasicLayout';
break;
}
/**
* 外链 新窗口打开
*/
case 'Link': {
if (vbenRoute.meta) {
vbenRoute.meta.link = menu.meta.link;
}
vbenRoute.component = 'BasicLayout';
break;
}
/**
* 三级以上菜单 父级component为ParentView
* 不能为layout 会套两层BasicLayout
*/
case 'ParentView': {
vbenRoute.component = '';
break;
}
/**
* 根目录菜单
*/
case 'RootMenu': {
if (vbenRoute.meta) {
vbenRoute.meta.hideChildrenInMenu = true;
}
vbenRoute.component = 'BasicLayout';
break;
}
/**
* 其他自定义组件 如system/user/index 拼接/
*/
default: {
vbenRoute.component = `/${menu.component}`;
break;
}
}
// children处理
if (menu.children && menu.children.length > 0) {
vbenRoute.children = backMenuToVbenMenu(menu.children, menu.path);
}
// 添加
resultList.push(vbenRoute);
});
return resultList;
}
async function generateAccess(options: GenerateMenuAndRoutesOptions) {
const pageMap: ComponentRecordType = import.meta.glob('../views/**/*.vue');
@@ -28,146 +192,6 @@ async function generateAccess(options: GenerateMenuAndRoutesOptions) {
NotFoundComponent,
};
/**
* 后台路由转vben路由
*
* todo 需要重构
* @param menuList 后台菜单
* @param parentPath 上级目录
* @returns vben路由
*/
function backMenuToVbenMenu(
menuList: Menu[],
parentPath = '',
): RouteRecordStringComponent[] {
const resultList: RouteRecordStringComponent[] = [];
menuList.forEach((menu) => {
// 根目录为菜单形式
// 固定有一个children children为当前菜单
if (menu.path === '/' && menu.children && menu.children.length === 1) {
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
menu.meta = menu.children[0]!.meta;
/**
* todo 先写死 后续再优化
*/
menu.path = '/root_menu';
menu.component = 'RootMenu';
}
// 外链: http开头 & 组件为Layout || ParentView
// 正则判断是否为http://或者https://开头
if (
/^https?:\/\//.test(menu.path) &&
(menu.component === 'Layout' || menu.component === 'ParentView')
) {
menu.component = 'Link';
}
// 内嵌iframe 组件为InnerLink
if (menu.meta?.link && menu.component === 'InnerLink') {
menu.component = 'IFrameView';
}
// path
if (parentPath) {
menu.path = `${parentPath}/${menu.path}`;
}
const vbenRoute: RouteRecordStringComponent = {
component: menu.component,
meta: {
// 当前路由不在菜单显示 但是可以通过链接访问
// 不可访问的路由由后端控制隐藏(不返回对应路由)
hideInMenu: menu.hidden,
icon: menu.meta?.icon,
keepAlive: !menu.meta?.noCache,
title: menu.meta?.title,
},
name: menu.name,
path: menu.path,
};
/**
* 处理不同组件
*/
switch (menu.component) {
case 'Layout': {
vbenRoute.component = 'BasicLayout';
break;
}
/**
* iframe内嵌
*/
case 'IFrameView': {
vbenRoute.component = 'IFrameView';
if (vbenRoute.meta) {
vbenRoute.meta.iframeSrc = menu.meta.link;
}
/**
* 需要判断特殊情况 比如vue的hash是带#的
* 比如链接 aaa.com/#/bbb path会转换为 aaa/com/#/bbb
* 比如链接 aaa.com/?bbb=xxx
* 需要去除# 否则无法被添加到路由
*/
/**
* todo 不优雅 考虑别的方案
*/
if (vbenRoute.path.includes('/#/')) {
vbenRoute.path = vbenRoute.path.replace('/#/', '');
}
if (vbenRoute.path.includes('#')) {
vbenRoute.path = vbenRoute.path.replace('#', '');
}
if (vbenRoute.path.includes('?') || vbenRoute.path.includes('&')) {
vbenRoute.path = vbenRoute.path.replace('?', '');
vbenRoute.path = vbenRoute.path.replace('&', '');
}
break;
}
/**
* 外链 新窗口打开
*/
case 'Link': {
if (vbenRoute.meta) {
vbenRoute.meta.link = menu.meta.link;
}
vbenRoute.component = 'BasicLayout';
break;
}
/**
* 根目录菜单
*/
case 'RootMenu': {
if (vbenRoute.meta) {
vbenRoute.meta.hideChildrenInMenu = true;
}
vbenRoute.component = 'BasicLayout';
break;
}
/**
* 不能为layout 会套两层BasicLayout
*/
case 'ParentView': {
vbenRoute.component = '';
break;
}
/**
* 其他自定义组件 如system/user/index 拼接/
*/
default: {
vbenRoute.component = `/${menu.component}`;
break;
}
}
// children处理
if (menu.children && menu.children.length > 0) {
vbenRoute.children = backMenuToVbenMenu(menu.children, menu.path);
}
resultList.push(vbenRoute);
});
return resultList;
}
return await generateAccessible(preferences.app.accessMode, {
...options,
fetchMenuListAsync: async () => {

View File

@@ -1,6 +1,6 @@
import type { RouteRecordRaw } from 'vue-router';
import { DEFAULT_HOME_PATH } from '@vben/constants';
import { DEFAULT_HOME_PATH, LOGIN_PATH } from '@vben/constants';
import { AuthPageLayout } from '#/layouts';
import { $t } from '#/locales';
@@ -32,7 +32,7 @@ const coreRoutes: RouteRecordRaw[] = [
{
component: () => import('#/views/_core/social-callback/index.vue'),
meta: {
title: $t('page.core.oauthLogin'),
title: $t('page.auth.oauthLogin'),
},
name: 'OAuthRedirect',
path: '/social-callback',
@@ -40,17 +40,19 @@ const coreRoutes: RouteRecordRaw[] = [
{
component: AuthPageLayout,
meta: {
hideInTab: true,
title: 'Authentication',
},
name: 'Authentication',
path: '/auth',
redirect: LOGIN_PATH,
children: [
{
name: 'Login',
path: 'login',
component: Login,
meta: {
title: $t('page.core.login'),
title: $t('page.auth.login'),
},
},
{
@@ -58,7 +60,7 @@ const coreRoutes: RouteRecordRaw[] = [
path: 'code-login',
component: () => import('#/views/_core/authentication/code-login.vue'),
meta: {
title: $t('page.core.codeLogin'),
title: $t('page.auth.codeLogin'),
},
},
{
@@ -67,7 +69,7 @@ const coreRoutes: RouteRecordRaw[] = [
component: () =>
import('#/views/_core/authentication/qrcode-login.vue'),
meta: {
title: $t('page.core.qrcodeLogin'),
title: $t('page.auth.qrcodeLogin'),
},
},
{
@@ -76,7 +78,7 @@ const coreRoutes: RouteRecordRaw[] = [
component: () =>
import('#/views/_core/authentication/forget-password.vue'),
meta: {
title: $t('page.core.forgetPassword'),
title: $t('page.auth.forgetPassword'),
},
},
{
@@ -84,7 +86,7 @@ const coreRoutes: RouteRecordRaw[] = [
path: 'register',
component: () => import('#/views/_core/authentication/register.vue'),
meta: {
title: $t('page.core.register'),
title: $t('page.auth.register'),
},
},
],

View File

@@ -15,7 +15,7 @@ const profileRoute: RouteRecordStringComponent[] = [
meta: {
hideChildrenInMenu: true,
hideInMenu: true,
title: $t('widgets.profile'),
title: $t('ui.widgets.profile'),
},
name: 'Profile',
path: '/',
@@ -26,7 +26,7 @@ const profileRoute: RouteRecordStringComponent[] = [
meta: {
icon: 'mingcute:profile-line',
keepAlive: true,
title: $t('widgets.profile'),
title: $t('ui.widgets.profile'),
},
name: 'ProfileIndex',
path: '/profile',
@@ -147,7 +147,7 @@ export const localMenuList: RouteRecordStringComponent[] = [
icon: 'lucide:book-open-text',
iframeSrc: 'https://dapdap.top',
keepAlive: true,
title: $t('page.vben.document'),
title: $t('demos.vben.document'),
},
},
],
@@ -158,7 +158,7 @@ export const localMenuList: RouteRecordStringComponent[] = [
hideChildrenInMenu: true,
icon: 'lucide:copyright',
order: 9999,
title: $t('page.vben.about'),
title: $t('demos.vben.about'),
},
name: 'About',
path: '/about',
@@ -166,7 +166,7 @@ export const localMenuList: RouteRecordStringComponent[] = [
{
component: '/_core/about/index',
meta: {
title: $t('page.vben.about'),
title: $t('demos.vben.about'),
},
name: 'VbenAbout',
path: '/vben-admin/about',

View File

@@ -10,14 +10,14 @@ const routes: RouteRecordRaw[] = [
icon: 'ic:baseline-view-in-ar',
keepAlive: true,
order: 1000,
title: $t('page.demos.title'),
title: $t('demos.title'),
},
name: 'Demos',
path: '/demos',
children: [
{
meta: {
title: $t('page.demos.antd'),
title: $t('demos.antd'),
},
name: 'AntDesignDemos',
path: '/demos/ant-design',

View File

@@ -18,7 +18,7 @@ const routes: RouteRecordRaw[] = [
badgeType: 'dot',
icon: VBEN_LOGO_URL,
order: 9999,
title: $t('page.vben.title'),
title: $t('demos.vben.title'),
},
name: 'VbenProject',
path: '/vben-admin',
@@ -29,7 +29,7 @@ const routes: RouteRecordRaw[] = [
component: () => import('#/views/_core/about/index.vue'),
meta: {
icon: 'lucide:copyright',
title: $t('page.vben.about'),
title: $t('demos.vben.about'),
},
},
{
@@ -39,7 +39,7 @@ const routes: RouteRecordRaw[] = [
meta: {
icon: 'lucide:book-open-text',
link: VBEN_DOC_URL,
title: $t('page.vben.document'),
title: $t('demos.vben.document'),
},
},
{
@@ -60,7 +60,7 @@ const routes: RouteRecordRaw[] = [
badgeType: 'dot',
icon: 'logos:naiveui',
link: VBEN_NAIVE_PREVIEW_URL,
title: $t('page.vben.naive-ui'),
title: $t('demos.vben.naive-ui'),
},
},
{
@@ -71,7 +71,7 @@ const routes: RouteRecordRaw[] = [
badgeType: 'dot',
icon: 'logos:element',
link: VBEN_ELE_PREVIEW_URL,
title: $t('page.vben.element-plus'),
title: $t('demos.vben.element-plus'),
},
},
],

View File

@@ -81,9 +81,6 @@ export const useAuthStore = defineStore('auth', () => {
} catch (error) {
console.error(error);
} finally {
// 需要清除字典缓存
const dictStore = useDictStore();
dictStore.resetCache();
resetAllStores();
accessStore.setLoginExpired(false);
@@ -100,8 +97,14 @@ export const useAuthStore = defineStore('auth', () => {
}
async function fetchUserInfo() {
const { permissions = [], roles = [], user } = await getUserInfoApi();
const backUserInfo = await getUserInfoApi();
/**
* 登录超时的情况
*/
if (!backUserInfo) {
throw new Error('获取用户信息失败.');
}
const { permissions = [], roles = [], user } = backUserInfo;
/**
* 从后台user -> vben user转换
*/
@@ -114,6 +117,12 @@ export const useAuthStore = defineStore('auth', () => {
username: user.userName,
};
userStore.setUserInfo(userInfo);
/**
* 需要重新加载字典
* 比如退出登录切换到其他租户
*/
const dictStore = useDictStore();
dictStore.resetCache();
return userInfo;
}

View File

@@ -75,6 +75,7 @@ export const useNotifyStore = defineStore(
userId: userId.value,
});
// 需要手动置空 vue3在值相同时不会触发watch
data.value = null;
});
}

View File

@@ -7,11 +7,17 @@ import {
type TenantOption,
} from '#/api/core/auth';
/**
* 用于超级管理员切换租户
*/
export const useTenantStore = defineStore('app-tenant', () => {
// 是否已经选中租户
const checked = ref(false);
// 是否开启租户功能
const tenantEnable = ref(true);
const tenantList = ref<TenantOption[]>([]);
// 初始化 获取租户信息
async function initTenant() {
const { tenantEnabled, voList } = await tenantListApi();
tenantEnable.value = tenantEnabled;

View File

@@ -12,13 +12,16 @@ export function getDict(dictName: string): DictData[] {
if (dictList.length === 0 && !dictRequestCache.has(dictName)) {
dictRequestCache.set(
dictName,
dictDataInfo(dictName).then((resp) => {
// 缓存到store 这样就不用重复获取了
// 内部处理了push的逻辑 这里不用push
setDictInfo(dictName, resp);
// 移除请求状态缓存
dictRequestCache.delete(dictName);
}),
dictDataInfo(dictName)
.then((resp) => {
// 缓存到store 这样就不用重复获取了
// 内部处理了push的逻辑 这里不用push
setDictInfo(dictName, resp);
})
.finally(() => {
// 移除请求状态缓存
dictRequestCache.delete(dictName);
}),
);
}
return dictList;
@@ -31,13 +34,16 @@ export function getDictOptions(dictName: string): Option[] {
if (dictOptionList.length === 0 && !dictRequestCache.has(dictName)) {
dictRequestCache.set(
dictName,
dictDataInfo(dictName).then((resp) => {
// 缓存到store 这样就不用重复获取了
// 内部处理了push的逻辑 这里不用push
setDictInfo(dictName, resp);
// 移除请求状态缓存
dictRequestCache.delete(dictName);
}),
dictDataInfo(dictName)
.then((resp) => {
// 缓存到store 这样就不用重复获取了
// 内部处理了push的逻辑 这里不用push
setDictInfo(dictName, resp);
})
.finally(() => {
// 移除请求状态缓存
dictRequestCache.delete(dictName);
}),
);
}
return dictOptionList;

View File

@@ -1,7 +1,7 @@
import type { DictData } from '#/api/system/dict/dict-data-model';
import { JsonPreview } from '@vben/common-ui';
import { Icon } from '@vben/icons';
import { IconifyIcon } from '@vben/icons';
import { Tag } from 'ant-design-vue';
@@ -66,32 +66,26 @@ export function renderJsonPreview(json: any) {
* @returns render
*/
export function renderIcon(icon: string) {
return <Icon icon={icon}></Icon>;
return <IconifyIcon icon={icon}></IconifyIcon>;
}
// httpMethod
/**
* httpMethod标签
* @param type method类型
* @returns render
*/
export function renderHttpMethodTag(type: string) {
const method = type.toUpperCase();
let color = 'default';
const colors: { [key: string]: string } = {
DELETE: 'red',
GET: 'green',
POST: 'blue',
PUT: 'orange',
};
const color = colors[method] ?? 'default';
const title = `${method}请求`;
switch (method) {
case 'DELETE': {
color = 'red';
break;
}
case 'GET': {
color = 'green';
break;
}
case 'POST': {
color = 'blue';
break;
}
case 'PUT': {
color = 'orange';
break;
}
}
return <Tag color={color}>{title}</Tag>;
}

View File

@@ -1,17 +1,61 @@
<script lang="ts" setup>
import type { LoginCodeParams, VbenFormSchema } from '@vben/common-ui';
import { computed, ref } from 'vue';
import { computed, onMounted, ref, useTemplateRef } from 'vue';
import { AuthenticationCodeLogin, z } from '@vben/common-ui';
import { $t } from '@vben/locales';
import { Alert, message } from 'ant-design-vue';
import { tenantList, type TenantResp } from '#/api';
import { sendSmsCode } from '#/api/core/captcha';
import { useAuthStore } from '#/store';
defineOptions({ name: 'CodeLogin' });
const loading = ref(false);
const tenantInfo = ref<TenantResp>({
tenantEnabled: false,
voList: [],
});
const codeLoginRef = useTemplateRef('codeLoginRef');
async function loadTenant() {
const resp = await tenantList();
tenantInfo.value = resp;
// 选中第一个租户
if (resp.tenantEnabled && resp.voList.length > 0) {
const firstTenantId = resp.voList[0]!.tenantId;
codeLoginRef.value?.setFieldValue('tenantId', firstTenantId);
}
}
onMounted(loadTenant);
const formSchema = computed((): VbenFormSchema[] => {
return [
{
component: 'VbenSelect',
componentProps: {
class: 'bg-background h-[40px] focus:border-primary',
contentClass: 'max-h-[256px] overflow-y-auto',
options: tenantInfo.value.voList?.map((item) => ({
label: item.companyName,
value: item.tenantId,
})),
placeholder: $t('authentication.selectAccount'),
},
defaultValue: '000000',
dependencies: {
if: () => tenantInfo.value.tenantEnabled,
triggerFields: [''],
},
fieldName: 'tenantId',
label: $t('authentication.selectAccount'),
rules: z.string().min(1, { message: $t('authentication.selectAccount') }),
},
{
component: 'VbenInput',
componentProps: {
@@ -28,15 +72,29 @@ const formSchema = computed((): VbenFormSchema[] => {
},
{
component: 'VbenPinInput',
componentProps: {
createText: (countdown: number) => {
const text =
countdown > 0
? $t('authentication.sendText', [countdown])
: $t('authentication.sendCode');
return text;
},
placeholder: $t('authentication.code'),
componentProps(_, form) {
return {
createText: (countdown: number) => {
const text =
countdown > 0
? $t('authentication.sendText', [countdown])
: $t('authentication.sendCode');
return text;
},
// 验证码长度 在这设置
codeLength: 4,
placeholder: $t('authentication.code'),
handleSendCode: async () => {
const { valid, value } = await form.validateField('phoneNumber');
if (!valid) {
// 必须抛异常 不能直接return
throw new Error('未填写手机号');
}
// 调用接口发送
await sendSmsCode(value);
message.success('验证码发送成功');
},
};
},
fieldName: 'code',
label: $t('authentication.code'),
@@ -44,20 +102,37 @@ const formSchema = computed((): VbenFormSchema[] => {
},
];
});
/**
* 异步处理登录操作
* Asynchronously handle the login process
* @param values 登录表单数据
*/
const authStore = useAuthStore();
async function handleLogin(values: LoginCodeParams) {
console.log(values);
try {
const requestParams: any = {
tenantId: values.tenantId,
phonenumber: values.phoneNumber,
smsCode: values.code,
grantType: 'sms',
};
console.log('login params', requestParams);
await authStore.authLogin(requestParams);
} catch (error) {
console.error(error);
}
}
</script>
<template>
<AuthenticationCodeLogin
:form-schema="formSchema"
:loading="loading"
@submit="handleLogin"
/>
<div>
<Alert
class="mb-4"
how-icon
message="测试手机号: 15888888888 正确验证码: 1234 演示使用 不会真的发送"
type="info"
/>
<AuthenticationCodeLogin
ref="codeLoginRef"
:form-schema="formSchema"
:loading="loading"
@submit="handleLogin"
/>
</div>
</template>

View File

@@ -1,5 +1,6 @@
<script lang="ts" setup>
import type { VbenFormSchema } from '@vben/common-ui';
import type { Recordable } from '@vben/types';
import { computed, ref } from 'vue';
@@ -27,7 +28,7 @@ const formSchema = computed((): VbenFormSchema[] => {
];
});
function handleSubmit(value: string) {
function handleSubmit(value: Recordable<any>) {
console.log('reset email:', value);
}
</script>

View File

@@ -1,5 +1,5 @@
<script lang="ts" setup>
import type { VbenFormSchema } from '@vben/common-ui';
import type { LoginAndRegisterParams, VbenFormSchema } from '@vben/common-ui';
import { computed, onMounted, ref, useTemplateRef } from 'vue';
@@ -60,7 +60,8 @@ const formSchema = computed((): VbenFormSchema[] => {
component: 'VbenSelect',
componentProps: {
class: 'bg-background h-[40px] focus:border-primary',
options: tenantInfo.value.voList.map((item) => ({
contentClass: 'max-h-[256px] overflow-y-auto',
options: tenantInfo.value.voList?.map((item) => ({
label: item.companyName,
value: item.tenantId,
})),
@@ -69,7 +70,15 @@ const formSchema = computed((): VbenFormSchema[] => {
defaultValue: '000000',
dependencies: {
if: () => tenantInfo.value.tenantEnabled,
triggerFields: [],
// 这里大致上是watch的一个效果
componentProps: (model) => {
localStorage.setItem(
'__oauth_tenant_id',
model?.tenantId ?? '000000',
);
return {};
},
triggerFields: ['', 'tenantId'],
},
fieldName: 'tenantId',
label: $t('authentication.selectAccount'),
@@ -116,15 +125,7 @@ const formSchema = computed((): VbenFormSchema[] => {
];
});
interface LoginForm {
code?: string;
grantType: string;
password: string;
tenantId: string;
username: string;
}
async function handleAccountLogin(values: LoginForm) {
async function handleAccountLogin(values: LoginAndRegisterParams) {
try {
const requestParam: any = omit(values, ['code']);
// 验证码

View File

@@ -1,5 +1,6 @@
<script lang="ts" setup>
import type { LoginAndRegisterParams, VbenFormSchema } from '@vben/common-ui';
import type { VbenFormSchema } from '@vben/common-ui';
import type { Recordable } from '@vben/types';
import { computed, h, ref } from 'vue';
@@ -45,7 +46,7 @@ const formSchema = computed((): VbenFormSchema[] => {
rules(values) {
const { password } = values;
return z
.string()
.string({ required_error: $t('authentication.passwordTip') })
.min(1, { message: $t('authentication.passwordTip') })
.refine((value) => value === password, {
message: $t('authentication.confirmPasswordTip'),
@@ -55,7 +56,6 @@ const formSchema = computed((): VbenFormSchema[] => {
},
fieldName: 'confirmPassword',
label: $t('authentication.confirmPassword'),
rules: z.string().min(1, { message: $t('authentication.passwordTip') }),
},
{
component: 'VbenCheckbox',
@@ -67,15 +67,10 @@ const formSchema = computed((): VbenFormSchema[] => {
h(
'a',
{
class:
'cursor-pointer text-primary ml-1 hover:text-primary-hover',
class: 'vben-link ml-1 ',
href: '',
},
[
$t('authentication.privacyPolicy'),
'&',
$t('authentication.terms'),
],
`${$t('authentication.privacyPolicy')} & ${$t('authentication.terms')}`,
),
]),
}),
@@ -86,7 +81,7 @@ const formSchema = computed((): VbenFormSchema[] => {
];
});
function handleSubmit(value: LoginAndRegisterParams) {
function handleSubmit(value: Recordable<any>) {
console.log('register submit:', value);
}
</script>

View File

@@ -10,14 +10,14 @@ import { useUserStore } from '@vben/stores';
import { pick } from 'lodash-es';
import { useVbenForm, z } from '#/adapter';
import { useVbenForm, z } from '#/adapter/form';
import { userProfileUpdate } from '#/api/system/profile';
import { useAuthStore } from '#/store';
import { getDictOptions } from '#/utils/dict';
const props = defineProps<{ profile: UserProfile }>();
import { emitter } from '../mitt';
const emit = defineEmits<{ reload: [] }>();
const props = defineProps<{ profile: UserProfile }>();
const userStore = useUserStore();
const authStore = useAuthStore();
@@ -74,7 +74,7 @@ const [BasicForm, formApi] = useVbenForm({
},
],
submitButtonOptions: {
text: '更新信息',
content: '更新信息',
},
});
@@ -93,7 +93,7 @@ async function handleSubmit(values: Recordable<any>) {
const userInfo = await authStore.fetchUserInfo();
userStore.setUserInfo(userInfo);
// 左边reload
emit('reload');
emitter.emit('updateProfile');
} catch (error) {
console.error(error);
} finally {
@@ -109,9 +109,7 @@ onMounted(() => {
'phonenumber',
'sex',
]);
for (const key in data) {
formApi.setFieldValue(key, data[key as keyof typeof data]);
}
formApi.setValues(data);
});
</script>

View File

@@ -3,7 +3,7 @@ import type { Recordable } from '@vben/types';
import { Popconfirm } from 'ant-design-vue';
import { useVbenVxeGrid, type VxeGridProps } from '#/adapter';
import { useVbenVxeGrid, type VxeGridProps } from '#/adapter/vxe-table';
import { forceLogout2, onlineDeviceList } from '#/api/monitor/online';
import { columns } from '#/views/monitor/online/data';
@@ -22,9 +22,6 @@ const gridOptions: VxeGridProps = {
isHover: true,
keyField: 'tokenId',
},
round: true,
align: 'center',
showOverflow: true,
};
const [BasicTable, tableApi] = useVbenVxeGrid({ gridOptions });
@@ -37,10 +34,7 @@ async function handleForceOffline(row: Recordable<any>) {
<template>
<div>
<BasicTable>
<template #toolbar-actions>
<span class="pl-[7px] text-[16px]">我的在线设备</span>
</template>
<BasicTable table-title="我的在线设备">
<template #action="{ row }">
<Popconfirm
:title="`确认强制下线[${row.userName}]?`"

View File

@@ -4,7 +4,7 @@ import type { UpdatePasswordParam } from '#/api/system/profile/model';
import { Modal } from 'ant-design-vue';
import { omit } from 'lodash-es';
import { useVbenForm, z } from '#/adapter';
import { useVbenForm, z } from '#/adapter/form';
import { userUpdatePassword } from '#/api/system/profile';
import { useAuthStore } from '#/store';
@@ -67,7 +67,7 @@ const [BasicForm, formApi] = useVbenForm({
},
],
submitButtonOptions: {
text: '修改密码',
content: '修改密码',
},
});

View File

@@ -9,6 +9,7 @@ import { useUserStore } from '@vben/stores';
import { userProfile } from '#/api/system/profile';
import { useAuthStore } from '#/store';
import { emitter } from './mitt';
import ProfilePanel from './profile-panel.vue';
import SettingPanel from './setting-panel.vue';
@@ -32,6 +33,8 @@ async function handleUploadFinish() {
const userInfo = await authStore.fetchUserInfo();
userStore.setUserInfo(userInfo);
}
emitter.on('updateProfile', loadProfile);
</script>
<template>
@@ -44,7 +47,6 @@ async function handleUploadFinish() {
v-if="profile"
:profile="profile"
class="flex-1 overflow-hidden"
@reload="loadProfile"
/>
</div>
</Page>

View File

@@ -0,0 +1,7 @@
import { mitt } from '@vben/utils';
type Events = {
updateProfile: void;
};
export const emitter = mitt<Events>();

View File

@@ -1,4 +1,5 @@
import type { FormSchemaGetter, VxeGridProps } from '#/adapter';
import type { FormSchemaGetter } from '#/adapter/form';
import type { VxeGridProps } from '#/adapter/vxe-table';
export const querySchema: FormSchemaGetter = () => [
{

View File

@@ -5,7 +5,7 @@ import { useVbenModal } from '@vben/common-ui';
import { $t } from '@vben/locales';
import { cloneDeep } from '@vben/utils';
import { useVbenForm } from '#/adapter';
import { useVbenForm } from '#/adapter/form';
import { demoAdd, demoInfo, demoUpdate } from './api';
import { modalSchema } from './data';

View File

@@ -9,7 +9,11 @@ import { getPopupContainer } from '@vben/utils';
import { Modal, Popconfirm, Space } from 'ant-design-vue';
import dayjs from 'dayjs';
import { useVbenVxeGrid, type VxeGridProps } from '#/adapter';
import {
tableCheckboxEvent,
useVbenVxeGrid,
type VxeGridProps,
} from '#/adapter/vxe-table';
import { downloadExcel } from '#/utils/file/download';
import { demoExport, demoList, demoRemove } from './api';
@@ -67,9 +71,6 @@ const gridOptions: VxeGridProps = {
isHover: true,
keyField: 'id',
},
round: true,
align: 'center',
showOverflow: true,
};
const checked = ref(false);
@@ -77,12 +78,8 @@ const [BasicTable, tableApi] = useVbenVxeGrid({
formOptions,
gridOptions,
gridEvents: {
checkboxChange: (e: any) => {
checked.value = e.records.length > 0;
},
checkboxAll: (e: any) => {
checked.value = e.records.length > 0;
},
checkboxChange: tableCheckboxEvent(checked),
checkboxAll: tableCheckboxEvent(checked),
},
});

View File

@@ -1,4 +1,5 @@
import type { FormSchemaGetter, VxeGridProps } from '#/adapter';
import type { FormSchemaGetter } from '#/adapter/form';
import type { VxeGridProps } from '#/adapter/vxe-table';
export const querySchema: FormSchemaGetter = () => [
{

View File

@@ -8,7 +8,7 @@ import { getPopupContainer, listToTree } from '@vben/utils';
import { Popconfirm, Space } from 'ant-design-vue';
import { useVbenVxeGrid, type VxeGridProps } from '#/adapter';
import { useVbenVxeGrid, type VxeGridProps } from '#/adapter/vxe-table';
import { treeList, treeRemove } from './api';
import { columns, querySchema } from './data';
@@ -54,9 +54,7 @@ const gridOptions: VxeGridProps = {
isHover: true,
keyField: 'id',
},
round: true,
align: 'center',
showOverflow: true,
treeConfig: {
parentField: 'parentId',
rowField: 'id',

View File

@@ -5,7 +5,7 @@ import { useVbenModal } from '@vben/common-ui';
import { $t } from '@vben/locales';
import { cloneDeep, listToTree } from '@vben/utils';
import { useVbenForm } from '#/adapter';
import { useVbenForm } from '#/adapter/form';
import { treeAdd, treeInfo, treeList, treeUpdate } from './api';
import { modalSchema } from './data';

View File

@@ -1,10 +1,11 @@
import type { FormSchemaGetter, VxeGridProps } from '#/adapter';
import type { VxeGridProps } from '#/adapter/vxe-table';
import type { DescItem } from '#/components/description';
import type { VNode } from 'vue';
import { DictEnum } from '@vben/constants';
import { type FormSchemaGetter } from '#/adapter/form';
import { getDictOptions } from '#/utils/dict';
import { renderBrowserIcon, renderDict, renderOsIcon } from '#/utils/render';

View File

@@ -7,8 +7,14 @@ import { Page, useVbenModal, type VbenFormProps } from '@vben/common-ui';
import { getPopupContainer } from '@vben/utils';
import { Modal, Popconfirm, Space } from 'ant-design-vue';
import dayjs from 'dayjs';
import { useVbenVxeGrid, type VxeGridProps } from '#/adapter';
import {
tableCheckboxEvent,
useVbenVxeGrid,
type VxeGridDefines,
type VxeGridProps,
} from '#/adapter/vxe-table';
import {
loginInfoClean,
loginInfoExport,
@@ -25,6 +31,9 @@ import loginInfoModal from './login-info-modal.vue';
const formOptions: VbenFormProps = {
commonConfig: {
labelWidth: 80,
componentProps: {
allowClear: true,
},
},
schema: querySchema(),
wrapperClass: 'grid-cols-1 md:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4',
@@ -46,6 +55,20 @@ const gridOptions: VxeGridProps = {
proxyConfig: {
ajax: {
query: async ({ page }, formValues = {}) => {
// 区间选择器处理
if (formValues?.dateTime) {
formValues.params = {
beginTime: dayjs(formValues.dateTime[0]).format(
'YYYY-MM-DD 00:00:00',
),
endTime: dayjs(formValues.dateTime[1]).format(
'YYYY-MM-DD 23:59:59',
),
};
Reflect.deleteProperty(formValues, 'dateTime');
} else {
Reflect.deleteProperty(formValues, 'params');
}
return await loginInfoList({
pageNum: page.currentPage,
pageSize: page.pageSize,
@@ -58,9 +81,7 @@ const gridOptions: VxeGridProps = {
isHover: true,
keyField: 'infoId',
},
round: true,
align: 'center',
showOverflow: true,
id: 'monitor-logininfo-index',
};
const checked = ref(false);
@@ -69,13 +90,12 @@ const [BasicTable, tableApi] = useVbenVxeGrid({
formOptions,
gridOptions,
gridEvents: {
checkboxChange: (e: any) => {
checked.value = e.records.length > 0;
canUnlock.value = e.records.length === 1 && e.records[0]?.status === '1';
},
checkboxAll: (e: any) => {
checked.value = e.records.length > 0;
checkboxChange: (e: VxeGridDefines.CheckboxChangeEventParams) => {
const records = e.$table.getCheckboxRecords();
checked.value = records.length > 0;
canUnlock.value = records.length === 1 && records[0]?.status === '1';
},
checkboxAll: tableCheckboxEvent(checked),
},
});
@@ -92,6 +112,7 @@ function handleClear() {
confirmDeleteModal({
onValidated: async () => {
await loginInfoClean();
await tableApi.reload();
},
});
}
@@ -132,10 +153,7 @@ async function handleUnlock() {
<template>
<Page auto-content-height>
<BasicTable>
<template #toolbar-actions>
<span class="pl-[7px] text-[16px]">登录日志列表</span>
</template>
<BasicTable table-title="登录日志列表">
<template #toolbar-tools>
<Space>
<a-button

View File

@@ -1,9 +1,10 @@
import type { FormSchemaGetter, VxeGridProps } from '#/adapter';
import type { VxeGridProps } from '#/adapter/vxe-table';
import type { VNode } from 'vue';
import dayjs from 'dayjs';
import { type FormSchemaGetter } from '#/adapter/form';
import { renderBrowserIcon, renderOsIcon } from '#/utils/render';
export const querySchema: FormSchemaGetter = () => [

View File

@@ -6,7 +6,7 @@ import { getPopupContainer } from '@vben/utils';
import { Popconfirm } from 'ant-design-vue';
import { useVbenVxeGrid, type VxeGridProps } from '#/adapter';
import { useVbenVxeGrid, type VxeGridProps } from '#/adapter/vxe-table';
import { forceLogout, onlineList } from '#/api/monitor/online';
import { columns, querySchema } from './data';
@@ -14,6 +14,9 @@ import { columns, querySchema } from './data';
const formOptions: VbenFormProps = {
commonConfig: {
labelWidth: 80,
componentProps: {
allowClear: true,
},
},
schema: querySchema(),
wrapperClass: 'grid-cols-1 md:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4',
@@ -39,9 +42,7 @@ const gridOptions: VxeGridProps = {
isHover: true,
keyField: 'tokenId',
},
round: true,
align: 'center',
showOverflow: true,
id: 'monitor-online-index',
};
const [BasicTable, tableApi] = useVbenVxeGrid({ formOptions, gridOptions });
@@ -54,10 +55,7 @@ async function handleForceOffline(row: Recordable<any>) {
<template>
<Page :auto-content-height="true">
<BasicTable>
<template #toolbar-actions>
<span class="pl-[7px] text-[16px]">在线用户列表</span>
</template>
<BasicTable table-title="在线用户列表">
<template #action="{ row }">
<Popconfirm
:get-popup-container="getPopupContainer"

View File

@@ -1,10 +1,11 @@
import type { FormSchemaGetter, VxeGridProps } from '#/adapter';
import type { VxeGridProps } from '#/adapter/vxe-table';
import type { DescItem } from '#/components/description';
import { DictEnum } from '@vben/constants';
import { Tag } from 'ant-design-vue';
import { type FormSchemaGetter } from '#/adapter/form';
import { getDictOptions } from '#/utils/dict';
import {
renderDict,
@@ -78,10 +79,11 @@ export const columns: VxeGridProps['columns'] = [
},
},
},
{ field: 'operTime', title: '操作日期' },
{ field: 'operTime', title: '操作日期', sortable: true },
{
field: 'costTime',
title: '操作耗时',
sortable: true,
formatter({ cellValue }) {
return `${cellValue} ms`;
},

View File

@@ -10,8 +10,13 @@ import { $t } from '@vben/locales';
import { Modal, Space } from 'ant-design-vue';
import dayjs from 'dayjs';
import { isEmpty } from 'lodash-es';
import { useVbenVxeGrid, type VxeGridProps } from '#/adapter';
import {
tableCheckboxEvent,
useVbenVxeGrid,
type VxeGridProps,
} from '#/adapter/vxe-table';
import {
operLogClean,
operLogDelete,
@@ -22,11 +27,14 @@ import { downloadExcel } from '#/utils/file/download';
import { confirmDeleteModal } from '#/utils/modal';
import { columns, querySchema } from './data';
import operationPreviewDrawer from './OperationPreviewDrawer.vue';
import operationPreviewDrawer from './operation-preview-drawer.vue';
const formOptions: VbenFormProps = {
commonConfig: {
labelWidth: 80,
componentProps: {
allowClear: true,
},
},
schema: querySchema(),
wrapperClass: 'grid-cols-1 md:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4',
@@ -47,7 +55,7 @@ const gridOptions: VxeGridProps<OperationLog> = {
pagerConfig: {},
proxyConfig: {
ajax: {
query: async ({ page }, formValues = {}) => {
query: async ({ page, sort }, formValues = {}) => {
// 区间选择器处理
if (formValues?.createTime) {
formValues.params = {
@@ -62,11 +70,18 @@ const gridOptions: VxeGridProps<OperationLog> = {
} else {
Reflect.deleteProperty(formValues, 'params');
}
return await operLogList({
const params: any = {
pageNum: page.currentPage,
pageSize: page.pageSize,
...formValues,
});
};
if (!isEmpty(sort)) {
params.orderByColumn = sort.field;
params.isAsc = sort.order;
}
return await operLogList(params);
},
},
},
@@ -74,9 +89,10 @@ const gridOptions: VxeGridProps<OperationLog> = {
isHover: true,
keyField: 'operId',
},
round: true,
align: 'center',
showOverflow: true,
sortConfig: {
remote: true,
},
id: 'monitor-operlog-index',
};
const checked = ref(false);
@@ -84,12 +100,11 @@ const [BasicTable, tableApi] = useVbenVxeGrid({
formOptions,
gridOptions,
gridEvents: {
checkboxChange: (e: any) => {
checked.value = e.records.length > 0;
},
checkboxAll: (e: any) => {
checked.value = e.records.length > 0;
sortChange: () => {
tableApi.query();
},
checkboxChange: tableCheckboxEvent(checked),
checkboxAll: tableCheckboxEvent(checked),
},
});
@@ -113,6 +128,7 @@ function handleClear() {
confirmDeleteModal({
onValidated: async () => {
await operLogClean();
await tableApi.reload();
},
});
}
@@ -137,10 +153,7 @@ async function handleDelete() {
<template>
<Page :auto-content-height="true">
<BasicTable>
<template #toolbar-actions>
<span class="pl-[7px] text-[16px]">操作日志列表</span>
</template>
<BasicTable table-title="操作日志列表">
<template #toolbar-tools>
<Space>
<a-button

View File

@@ -5,7 +5,7 @@ import { useVbenDrawer } from '@vben/common-ui';
import { $t } from '@vben/locales';
import { cloneDeep } from '@vben/utils';
import { useVbenForm } from '#/adapter';
import { useVbenForm } from '#/adapter/form';
import { clientAdd, clientInfo, clientUpdate } from '#/api/system/client';
import { drawerSchema } from './data';

View File

@@ -1,8 +1,9 @@
import type { FormSchemaGetter, VxeGridProps } from '#/adapter';
import type { VxeGridProps } from '#/adapter/vxe-table';
import { DictEnum } from '@vben/constants';
import { getPopupContainer } from '@vben/utils';
import { type FormSchemaGetter } from '#/adapter/form';
import { getDict, getDictOptions } from '#/utils/dict';
import { renderDict, renderDictTags } from '#/utils/render';

View File

@@ -9,7 +9,11 @@ import { getPopupContainer } from '@vben/utils';
import { Modal, Popconfirm, Space } from 'ant-design-vue';
import { useVbenVxeGrid, type VxeGridProps } from '#/adapter';
import {
tableCheckboxEvent,
useVbenVxeGrid,
type VxeGridProps,
} from '#/adapter/vxe-table';
import {
clientChangeStatus,
clientExport,
@@ -25,6 +29,9 @@ import { columns, querySchema } from './data';
const formOptions: VbenFormProps = {
commonConfig: {
labelWidth: 80,
componentProps: {
allowClear: true,
},
},
schema: querySchema(),
wrapperClass: 'grid-cols-1 md:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4',
@@ -60,9 +67,7 @@ const gridOptions: VxeGridProps = {
keyField: 'id',
height: 90,
},
round: true,
align: 'center',
showOverflow: true,
id: 'system-client-index',
};
const checked = ref(false);
@@ -70,12 +75,8 @@ const [BasicTable, tableApi] = useVbenVxeGrid({
formOptions,
gridOptions,
gridEvents: {
checkboxChange: (e: any) => {
checked.value = e.records.length > 0;
},
checkboxAll: (e: any) => {
checked.value = e.records.length > 0;
},
checkboxChange: tableCheckboxEvent(checked),
checkboxAll: tableCheckboxEvent(checked),
},
});
@@ -118,10 +119,7 @@ const { hasAccessByCodes } = useAccess();
<template>
<Page :auto-content-height="true">
<BasicTable>
<template #toolbar-actions>
<span class="pl-[7px] text-[16px]">系统授权列表</span>
</template>
<BasicTable table-title="系统授权列表">
<template #toolbar-tools>
<Space>
<a-button

View File

@@ -1,5 +1,5 @@
<script setup lang="ts">
import { Icon } from '@vben/icons';
import { IconifyIcon } from '@vben/icons';
import { buildUUID } from '@vben/utils';
import { Input } from 'ant-design-vue';
@@ -38,7 +38,7 @@ defineExpose({ refreshSecret });
<template v-if="!disabled" #addonAfter>
<a-button type="primary" @click="refreshSecret">
<div class="flex items-center gap-[4px]">
<Icon icon="charm:refresh" />
<IconifyIcon icon="charm:refresh" />
<span>随机生成</span>
</div>
</a-button>

View File

@@ -5,7 +5,7 @@ import { useVbenModal } from '@vben/common-ui';
import { $t } from '@vben/locales';
import { cloneDeep } from '@vben/utils';
import { useVbenForm } from '#/adapter';
import { useVbenForm } from '#/adapter/form';
import { configAdd, configInfo, configUpdate } from '#/api/system/config';
import { modalSchema } from './data';

View File

@@ -1,8 +1,9 @@
import type { FormSchemaGetter, VxeGridProps } from '#/adapter';
import type { VxeGridProps } from '#/adapter/vxe-table';
import { DictEnum } from '@vben/constants';
import { getPopupContainer } from '@vben/utils';
import { type FormSchemaGetter } from '#/adapter/form';
import { getDictOptions } from '#/utils/dict';
import { renderDict } from '#/utils/render';

View File

@@ -9,7 +9,11 @@ import { getPopupContainer } from '@vben/utils';
import { Modal, Popconfirm, Space } from 'ant-design-vue';
import dayjs from 'dayjs';
import { useVbenVxeGrid, type VxeGridProps } from '#/adapter';
import {
tableCheckboxEvent,
useVbenVxeGrid,
type VxeGridProps,
} from '#/adapter/vxe-table';
import {
configExport,
configList,
@@ -24,6 +28,9 @@ import { columns, querySchema } from './data';
const formOptions: VbenFormProps = {
commonConfig: {
labelWidth: 80,
componentProps: {
allowClear: true,
},
},
schema: querySchema(),
wrapperClass: 'grid-cols-1 md:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4',
@@ -70,9 +77,7 @@ const gridOptions: VxeGridProps = {
isHover: true,
keyField: 'configId',
},
round: true,
align: 'center',
showOverflow: true,
id: 'system-config-index',
};
const checked = ref(false);
@@ -80,12 +85,8 @@ const [BasicTable, tableApi] = useVbenVxeGrid({
formOptions,
gridOptions,
gridEvents: {
checkboxChange: (e: any) => {
checked.value = e.records.length > 0;
},
checkboxAll: (e: any) => {
checked.value = e.records.length > 0;
},
checkboxChange: tableCheckboxEvent(checked),
checkboxAll: tableCheckboxEvent(checked),
},
});
const [ConfigModal, modalApi] = useVbenModal({
@@ -130,10 +131,7 @@ async function handleRefreshCache() {
<template>
<Page :auto-content-height="true">
<BasicTable>
<template #toolbar-actions>
<span class="pl-[7px] text-[16px]">参数列表</span>
</template>
<BasicTable table-title="参数列表">
<template #toolbar-tools>
<Space>
<a-button @click="handleRefreshCache"> 刷新缓存 </a-button>

View File

@@ -1,7 +1,9 @@
import type { VxeGridProps } from '#/adapter/vxe-table';
import { DictEnum } from '@vben/constants';
import { getPopupContainer } from '@vben/utils';
import { type FormSchemaGetter, type VxeGridProps, z } from '#/adapter';
import { type FormSchemaGetter, z } from '#/adapter/form';
import { getDictOptions } from '#/utils/dict';
import { renderDict } from '#/utils/render';

View File

@@ -5,7 +5,7 @@ import { useVbenDrawer } from '@vben/common-ui';
import { $t } from '@vben/locales';
import { addFullName, cloneDeep, listToTree } from '@vben/utils';
import { useVbenForm } from '#/adapter';
import { useVbenForm } from '#/adapter/form';
import {
deptAdd,
deptInfo,

View File

@@ -11,10 +11,9 @@ import {
removeEmptyChildren,
} from '@vben/utils';
import { QuestionCircleOutlined } from '@ant-design/icons-vue';
import { Popconfirm, Space, Tooltip } from 'ant-design-vue';
import { Popconfirm, Space } from 'ant-design-vue';
import { useVbenVxeGrid, type VxeGridProps } from '#/adapter';
import { useVbenVxeGrid, type VxeGridProps } from '#/adapter/vxe-table';
import { deptList, deptRemove } from '#/api/system/dept';
import { columns, querySchema } from './data';
@@ -23,6 +22,9 @@ import deptDrawer from './dept-drawer.vue';
const formOptions: VbenFormProps = {
commonConfig: {
labelWidth: 80,
componentProps: {
allowClear: true,
},
},
schema: querySchema(),
wrapperClass: 'grid-cols-1 md:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4',
@@ -64,14 +66,13 @@ const gridOptions: VxeGridProps = {
isHover: true,
keyField: 'deptId',
},
round: true,
align: 'center',
showOverflow: true,
treeConfig: {
parentField: 'parentId',
rowField: 'deptId',
transform: false,
},
id: 'system-dept-index',
};
const [BasicTable, tableApi] = useVbenVxeGrid({
@@ -131,15 +132,7 @@ function setExpandOrCollapse(expand: boolean) {
<template>
<Page :auto-content-height="true">
<BasicTable>
<template #toolbar-actions>
<div class="flex items-center gap-[6px]">
<span class="pl-[7px] text-[16px]">部门列表</span>
<Tooltip title="提示:双击展开/收起子菜单">
<QuestionCircleOutlined class="text-center" />
</Tooltip>
</div>
</template>
<BasicTable table-title="部门列表" table-title-help="双击展开/收起子菜单">
<template #toolbar-tools>
<Space>
<a-button @click="setExpandOrCollapse(false)">

View File

@@ -1,40 +1,9 @@
import type { FormSchemaGetter, VxeGridProps } from '#/adapter';
import type { VxeGridProps } from '#/adapter/vxe-table';
import { reactive } from 'vue';
import { getPopupContainer } from '@vben/utils';
import { dictOptionSelectList } from '#/api/system/dict/dict-type';
import { type FormSchemaGetter } from '#/adapter/form';
import { renderDictTag } from '#/utils/render';
/**
* updateSchema无法赋值
* TODO: 使用updateSchema重构
*/
const dictTypeOptions = reactive<{ label: string; value: string }[]>([]);
(async () => {
const resp = await dictOptionSelectList();
const options = resp.map((item) => ({
label: item.dictName,
value: item.dictType,
}));
dictTypeOptions.push(...options);
})();
export const querySchema: FormSchemaGetter = () => [
{
component: 'Select',
dependencies: {
show: () => false,
triggerFields: [''],
},
componentProps: {
getPopupContainer,
options: dictTypeOptions,
},
fieldName: 'dictType',
label: '字典类型',
},
{
component: 'Input',
fieldName: 'dictLabel',

View File

@@ -5,7 +5,7 @@ import { useVbenDrawer } from '@vben/common-ui';
import { $t } from '@vben/locales';
import { cloneDeep } from '@vben/utils';
import { useVbenForm } from '#/adapter';
import { useVbenForm } from '#/adapter/form';
import {
dictDataAdd,
dictDataUpdate,

View File

@@ -3,13 +3,17 @@ import type { Recordable } from '@vben/types';
import { ref } from 'vue';
import { Page, useVbenDrawer, type VbenFormProps } from '@vben/common-ui';
import { useVbenDrawer, type VbenFormProps } from '@vben/common-ui';
import { getPopupContainer } from '@vben/utils';
import { Modal, Popconfirm, Space } from 'ant-design-vue';
import dayjs from 'dayjs';
import { useVbenVxeGrid, type VxeGridProps } from '#/adapter';
import {
tableCheckboxEvent,
useVbenVxeGrid,
type VxeGridProps,
} from '#/adapter/vxe-table';
import {
dictDataExport,
dictDataList,
@@ -26,6 +30,9 @@ const dictType = ref('');
const formOptions: VbenFormProps = {
commonConfig: {
labelWidth: 80,
componentProps: {
allowClear: true,
},
},
schema: querySchema(),
wrapperClass: 'grid-cols-1 md:grid-cols-2 lg:grid-cols-3',
@@ -79,9 +86,7 @@ const gridOptions: VxeGridProps = {
isHover: true,
keyField: 'dictCode',
},
round: true,
align: 'center',
showOverflow: true,
id: 'system-dict-data-index',
};
const checked = ref(false);
@@ -89,12 +94,8 @@ const [BasicTable, tableApi] = useVbenVxeGrid({
formOptions,
gridOptions,
gridEvents: {
checkboxChange: (e: any) => {
checked.value = e.records.length > 0;
},
checkboxAll: (e: any) => {
checked.value = e.records.length > 0;
},
checkboxChange: tableCheckboxEvent(checked),
checkboxAll: tableCheckboxEvent(checked),
},
});
@@ -142,11 +143,8 @@ emitter.on('rowClick', async (value) => {
</script>
<template>
<Page :auto-content-height="true">
<BasicTable>
<template #toolbar-actions>
<span class="pl-[7px] text-[16px]">字典数据列表</span>
</template>
<div>
<BasicTable table-title="字典数据列表">
<template #toolbar-tools>
<Space>
<a-button
@@ -206,5 +204,5 @@ emitter.on('rowClick', async (value) => {
</template>
</BasicTable>
<DictDataDrawer @reload="tableApi.query()" />
</Page>
</div>
</template>

View File

@@ -6,17 +6,11 @@ import DictTypePanel from './type/index.vue';
</script>
<template>
<Page :auto-content-height="true" content-class="flex flex-col lg:flex-row">
<Page
:auto-content-height="true"
content-class="flex flex-col lg:flex-row gap-4"
>
<DictTypePanel class="flex-1 overflow-hidden" />
<DictDataPanel class="flex-1 overflow-hidden" />
</Page>
</template>
<style scoped>
/**
TODO: ugly code
*/
:deep(.p-4) {
padding: 6px;
}
</style>

View File

@@ -1,4 +1,6 @@
import { type FormSchemaGetter, type VxeGridProps, z } from '#/adapter';
import type { VxeGridProps } from '#/adapter/vxe-table';
import { type FormSchemaGetter, z } from '#/adapter/form';
export const querySchema: FormSchemaGetter = () => [
{

View File

@@ -5,7 +5,7 @@ import { useVbenModal } from '@vben/common-ui';
import { $t } from '@vben/locales';
import { cloneDeep } from '@vben/utils';
import { useVbenForm } from '#/adapter';
import { useVbenForm } from '#/adapter/form';
import {
dictTypeAdd,
dictTypeInfo,

View File

@@ -1,9 +1,10 @@
<script setup lang="ts">
import type { Recordable } from '@vben/types';
import { ref } from 'vue';
import { computed, ref } from 'vue';
import { Page, useVbenModal, type VbenFormProps } from '@vben/common-ui';
import { useAccess } from '@vben/access';
import { useVbenModal, type VbenFormProps } from '@vben/common-ui';
import { getPopupContainer } from '@vben/utils';
import {
@@ -17,7 +18,11 @@ import {
} from 'ant-design-vue';
import dayjs from 'dayjs';
import { useVbenVxeGrid, type VxeGridProps } from '#/adapter';
import {
tableCheckboxEvent,
useVbenVxeGrid,
type VxeGridProps,
} from '#/adapter/vxe-table';
import {
dictTypeExport,
dictTypeList,
@@ -25,6 +30,7 @@ import {
refreshDictTypeCache,
} from '#/api/system/dict/dict-type';
import { dictSyncTenant } from '#/api/system/tenant';
import { useTenantStore } from '#/store/tenant';
import { downloadExcel } from '#/utils/file/download';
import { emitter } from '../mitt';
@@ -34,6 +40,9 @@ import dictTypeModal from './dict-type-modal.vue';
const formOptions: VbenFormProps = {
commonConfig: {
labelWidth: 70,
componentProps: {
allowClear: true,
},
},
schema: querySchema(),
wrapperClass: 'grid-cols-1 md:grid-cols-2 lg:grid-cols-3',
@@ -82,9 +91,7 @@ const gridOptions: VxeGridProps = {
isHover: true,
keyField: 'dictId',
},
round: true,
align: 'center',
showOverflow: true,
id: 'system-dict-type-index',
};
const checked = ref(false);
@@ -102,12 +109,8 @@ const [BasicTable, tableApi] = useVbenVxeGrid({
emitter.emit('rowClick', row.dictType);
lastDictType.value = row.dictType;
},
checkboxChange: (e: any) => {
checked.value = e.records.length > 0;
},
checkboxAll: (e: any) => {
checked.value = e.records.length > 0;
},
checkboxChange: tableCheckboxEvent(checked),
checkboxAll: tableCheckboxEvent(checked),
},
});
const [DictTypeModal, modalApi] = useVbenModal({
@@ -172,14 +175,20 @@ function handleSyncTenantDict() {
},
});
}
const { hasAccessByRoles } = useAccess();
const tenantStore = useTenantStore();
/**
* 开启租户 & 超级管理员才可以同步租户字典
*/
const couldSyncTenantDict = computed(() => {
return tenantStore.tenantEnable && hasAccessByRoles(['superadmin']);
});
</script>
<template>
<Page :auto-content-height="true">
<BasicTable>
<template #toolbar-actions>
<span class="pl-[7px] text-[16px]">字典类型列表</span>
</template>
<div>
<BasicTable table-title="字典类型列表">
<template #toolbar-tools>
<Space>
<Dropdown>
@@ -188,7 +197,7 @@ function handleSyncTenantDict() {
<span v-access:code="['system:dict:edit']">
<MenuItem key="1">刷新字典缓存</MenuItem>
</span>
<span v-access:role="['superadmin']">
<span v-if="couldSyncTenantDict">
<MenuItem key="2"> 同步租户字典 </MenuItem>
</span>
</Menu>
@@ -251,5 +260,5 @@ function handleSyncTenantDict() {
</template>
</BasicTable>
<DictTypeModal @reload="tableApi.query()" />
</Page>
</div>
</template>

View File

@@ -1,7 +1,10 @@
import type { VxeGridProps } from '#/adapter/vxe-table';
import { DictEnum } from '@vben/constants';
import { $t } from '@vben/locales';
import { getPopupContainer } from '@vben/utils';
import { type FormSchemaGetter, type VxeGridProps, z } from '#/adapter';
import { type FormSchemaGetter, z } from '#/adapter/form';
import { getDictOptions } from '#/utils/dict';
import { renderDict, renderIcon } from '#/utils/render';
@@ -56,6 +59,10 @@ export const columns: VxeGridProps['columns'] = [
field: 'menuName',
treeNode: true,
width: 200,
slots: {
// 需要i18n支持 否则返回原始值
default: ({ row }) => $t(row.menuName),
},
},
{
title: '图标',
@@ -66,7 +73,9 @@ export const columns: VxeGridProps['columns'] = [
if (row?.icon === '#') {
return '';
}
return renderIcon(row.icon);
return (
<span class={'flex justify-center'}>{renderIcon(row.icon)}</span>
);
},
},
},
@@ -180,14 +189,22 @@ export const drawerSchema: FormSchemaGetter = () => [
show: (values) => values.menuType !== 'F',
triggerFields: ['menuType'],
},
renderComponentContent: () => ({
addonAfter: () => (
<a href="https://icon-sets.iconify.design/" target="_blank">
</a>
),
}),
fieldName: 'icon',
help: '选择或者从 https://icon-sets.iconify.design/ 查找名称粘贴',
help: '点击搜索图标跳转到iconify & 粘贴',
label: '菜单图标',
},
{
component: 'Input',
fieldName: 'menuName',
label: '菜单名称',
help: '支持i18n写法, 如: menu.system.user',
rules: 'required',
},
{
@@ -227,7 +244,7 @@ export const drawerSchema: FormSchemaGetter = () => [
triggerFields: ['isFrame', 'menuType'],
},
fieldName: 'path',
help: `路由地址不带/, 如: menu, user 链接为http(s)://开头 链接默认使用内部iframe打开, 可通过{是否外链}控制打开方式`,
help: `路由地址不带/, 如: menu, user\n 链接为http(s)://开头\n 链接默认使用内部iframe打开, 可通过{是否外链}控制打开方式`,
label: '路由地址',
},
{
@@ -275,7 +292,7 @@ export const drawerSchema: FormSchemaGetter = () => [
triggerFields: ['menuType'],
},
fieldName: 'isFrame',
help: '外链为http(s)://开头 选择否时, 使用iframe从内部打开页面, 否则新窗口打开',
help: '外链为http(s)://开头\n 选择否时, 使用iframe从内部打开页面, 否则新窗口打开',
label: '是否外链',
},
{
@@ -320,16 +337,15 @@ export const drawerSchema: FormSchemaGetter = () => [
triggerFields: ['menuType'],
},
fieldName: 'perms',
help: `控制器中定义的权限字符, 如: @SaCheckPermission("system:user:import")`,
help: `控制器中定义的权限字符\n 如: @SaCheckPermission("system:user:import")`,
label: '权限标识',
},
{
component: 'Input',
componentProps: () => ({
componentProps: (model) => ({
// 为链接时组件disabled
// disabled: model.isFrame === '0',
placeholder: '暂未实现功能',
disabled: true,
disabled: model.isFrame === '0',
placeholder: '必须为json字符串格式',
}),
dependencies: {
// 类型为菜单时显示
@@ -337,7 +353,7 @@ export const drawerSchema: FormSchemaGetter = () => [
triggerFields: ['menuType'],
},
fieldName: 'queryParam',
help: 'vue-router中的query属性, 如{"name": "xxx", "age": 16}',
help: 'vue-router中的query属性\n 如{"name": "xxx", "age": 16}',
label: '路由参数',
},
{

View File

@@ -13,9 +13,9 @@ import {
removeEmptyChildren,
} from '@vben/utils';
import { Popconfirm, Space, Tooltip } from 'ant-design-vue';
import { Popconfirm, Space } from 'ant-design-vue';
import { useVbenVxeGrid, type VxeGridProps } from '#/adapter';
import { useVbenVxeGrid, type VxeGridProps } from '#/adapter/vxe-table';
import { menuList, menuRemove } from '#/api/system/menu';
import { columns, querySchema } from './data';
@@ -24,11 +24,13 @@ import menuDrawer from './menu-drawer.vue';
/**
* 不要问为什么有两个根节点 v-if会控制只会渲染一个
*/
import { QuestionCircleOutlined } from '@ant-design/icons-vue';
const formOptions: VbenFormProps = {
commonConfig: {
labelWidth: 80,
componentProps: {
allowClear: true,
},
},
schema: querySchema(),
wrapperClass: 'grid-cols-1 md:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4',
@@ -61,14 +63,13 @@ const gridOptions: VxeGridProps = {
isHover: true,
keyField: 'menuId',
},
round: true,
align: 'center',
showOverflow: true,
treeConfig: {
parentField: 'parentId',
rowField: 'menuId',
transform: false,
},
id: 'system-menu-index',
};
const [BasicTable, tableApi] = useVbenVxeGrid({
@@ -137,15 +138,7 @@ const isAdmin = computed(() => {
<template>
<Page v-if="isAdmin" :auto-content-height="true">
<BasicTable>
<template #toolbar-actions>
<div class="flex items-center gap-[6px]">
<span class="pl-[7px] text-[16px]">菜单列表</span>
<Tooltip title="提示:双击展开/收起子菜单">
<QuestionCircleOutlined class="text-center" />
</Tooltip>
</div>
</template>
<BasicTable table-title="菜单列表" table-title-help="双击展开/收起子菜单">
<template #toolbar-tools>
<Space>
<a-button @click="setExpandOrCollapse(false)">

View File

@@ -10,7 +10,7 @@ import {
listToTree,
} from '@vben/utils';
import { useVbenForm } from '#/adapter';
import { useVbenForm } from '#/adapter/form';
import { menuAdd, menuInfo, menuList, menuUpdate } from '#/api/system/menu';
import { drawerSchema } from './data';
@@ -43,12 +43,16 @@ const [BasicForm, formApi] = useVbenForm({
async function setupMenuSelect() {
// menu
const menuArray = await menuList();
// support i18n
menuArray.forEach((item) => {
item.menuName = $t(item.menuName);
});
// const folderArray = menuArray.filter((item) => item.menuType === 'M');
const menuTree = listToTree(menuArray, { id: 'menuId', pid: 'parentId' });
const fullMenuTree = [
{
menuId: 0,
menuName: '根目录',
menuName: $t('menu.root'),
children: menuTree,
},
];

View File

@@ -1,8 +1,9 @@
import type { FormSchemaGetter, VxeGridProps } from '#/adapter';
import type { VxeGridProps } from '#/adapter/vxe-table';
import { DictEnum } from '@vben/constants';
import { getPopupContainer } from '@vben/utils';
import { type FormSchemaGetter } from '#/adapter/form';
import { getDictOptions } from '#/utils/dict';
import { renderDict } from '#/utils/render';

View File

@@ -9,7 +9,11 @@ import { getPopupContainer } from '@vben/utils';
import { Modal, Popconfirm, Space } from 'ant-design-vue';
import dayjs from 'dayjs';
import { useVbenVxeGrid, type VxeGridProps } from '#/adapter';
import {
tableCheckboxEvent,
useVbenVxeGrid,
type VxeGridProps,
} from '#/adapter/vxe-table';
import { noticeList, noticeRemove } from '#/api/system/notice';
import { columns, querySchema } from './data';
@@ -18,6 +22,9 @@ import noticeModal from './notice-modal.vue';
const formOptions: VbenFormProps = {
commonConfig: {
labelWidth: 80,
componentProps: {
allowClear: true,
},
},
schema: querySchema(),
wrapperClass: 'grid-cols-1 md:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4',
@@ -66,9 +73,7 @@ const gridOptions: VxeGridProps = {
isHover: true,
keyField: 'noticeId',
},
round: true,
align: 'center',
showOverflow: true,
id: 'system-notice-index',
};
const checked = ref(false);
@@ -76,12 +81,8 @@ const [BasicTable, tableApi] = useVbenVxeGrid({
formOptions,
gridOptions,
gridEvents: {
checkboxChange: (e: any) => {
checked.value = e.records.length > 0;
},
checkboxAll: (e: any) => {
checked.value = e.records.length > 0;
},
checkboxChange: tableCheckboxEvent(checked),
checkboxAll: tableCheckboxEvent(checked),
},
});
@@ -122,10 +123,7 @@ function handleMultiDelete() {
<template>
<Page :auto-content-height="true">
<BasicTable>
<template #toolbar-actions>
<span class="pl-[7px] text-[16px]">通知公告列表</span>
</template>
<BasicTable table-title="通知公告列表">
<template #toolbar-tools>
<Space>
<a-button

View File

@@ -5,7 +5,7 @@ import { useVbenModal } from '@vben/common-ui';
import { $t } from '@vben/locales';
import { cloneDeep } from '@vben/utils';
import { useVbenForm } from '#/adapter';
import { useVbenForm } from '#/adapter/form';
import { noticeAdd, noticeInfo, noticeUpdate } from '#/api/system/notice';
import { modalSchema } from './data';

View File

@@ -1,8 +1,10 @@
import type { VxeGridProps } from '#/adapter/vxe-table';
import { DictEnum } from '@vben/constants';
import { Tag } from 'ant-design-vue';
import { type FormSchemaGetter, type VxeGridProps, z } from '#/adapter';
import { type FormSchemaGetter, z } from '#/adapter/form';
import { getDictOptions } from '#/utils/dict';
const accessPolicyOptions = [

View File

@@ -10,7 +10,11 @@ import { getPopupContainer } from '@vben/utils';
import { Modal, Popconfirm, Space } from 'ant-design-vue';
import dayjs from 'dayjs';
import { useVbenVxeGrid, type VxeGridProps } from '#/adapter';
import {
tableCheckboxEvent,
useVbenVxeGrid,
type VxeGridProps,
} from '#/adapter/vxe-table';
import {
ossConfigChangeStatus,
ossConfigList,
@@ -23,6 +27,12 @@ import ossConfigDrawer from './oss-config-drawer.vue';
const formOptions: VbenFormProps = {
schema: querySchema(),
commonConfig: {
labelWidth: 80,
componentProps: {
allowClear: true,
},
},
wrapperClass: 'grid-cols-1 md:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4',
};
@@ -69,9 +79,7 @@ const gridOptions: VxeGridProps = {
isHover: true,
keyField: 'ossConfigId',
},
round: true,
align: 'center',
showOverflow: true,
id: 'system-oss-config-index',
};
const checked = ref(false);
@@ -79,12 +87,8 @@ const [BasicTable, tableApi] = useVbenVxeGrid({
formOptions,
gridOptions,
gridEvents: {
checkboxChange: (e: any) => {
checked.value = e.records.length > 0;
},
checkboxAll: (e: any) => {
checked.value = e.records.length > 0;
},
checkboxChange: tableCheckboxEvent(checked),
checkboxAll: tableCheckboxEvent(checked),
},
});
@@ -127,10 +131,7 @@ const { hasAccessByCodes } = useAccess();
<template>
<Page :auto-content-height="true">
<BasicTable>
<template #toolbar-actions>
<span class="pl-[7px] text-[16px]">oss配置列表</span>
</template>
<BasicTable table-title="oss配置列表">
<template #toolbar-tools>
<Space>
<a-button

View File

@@ -5,7 +5,7 @@ import { useVbenDrawer } from '@vben/common-ui';
import { $t } from '@vben/locales';
import { cloneDeep } from '@vben/utils';
import { useVbenForm } from '#/adapter';
import { useVbenForm } from '#/adapter/form';
import {
ossConfigAdd,
ossConfigInfo,

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