Compare commits

..

102 Commits

Author SHA1 Message Date
vben
c8dd9bbf0b chore: release 5.4.8 2024-11-24 22:00:41 +08:00
Vben
3587ec54eb fix: supplement datepicker component (#4943)
* fix: supplement datepicker component

* chore: typo
2024-11-24 21:56:41 +08:00
Vben
dbcb7138f2 fix: resolve issue with Upload component not working correctly inside Form (#4916) 2024-11-17 21:37:37 +08:00
ryomahan
fe58af2e78 fix: form-api.setValues can't resolve nested fields (#4915)
fix #4912
2024-11-17 21:04:35 +08:00
huangxiaomin
94c68c966e fix: fieldMappingTime data error when clear inputvalue (#4906) 2024-11-17 21:04:04 +08:00
Arthur Darkstone
77083abcc5 feat: add 3 resize examples (#4907) 2024-11-17 21:01:32 +08:00
Netfan
1302092798 fix: dialog opened/closed event triggered incorrectly,fixed #4902 (#4908) 2024-11-17 20:55:19 +08:00
Mintnoii
ec53bf8084 docs: optimize the introduction in both Chinese and English (#4913)
* 优化简介中文文档

1. 优化文案及病句
2. 统一格式

* Optimize the introduction English document

1. Optimize copywriting and sentences
2. Uniform format
2024-11-17 20:54:28 +08:00
Netfan
b87d41bada fix: adjust useWatermark logic (#4896) 2024-11-15 14:06:13 +08:00
vben
788a29a8cb chore: release v5.4.7 2024-11-14 22:15:46 +08:00
zyy
3bd5ef4523 fix(@vben/common-ui): pagination current page error (#4893) 2024-11-14 22:11:32 +08:00
Vben
86e52ce58a fix: resolve onChange issue in form component (#4890) 2024-11-13 22:53:09 +08:00
Vben
9ddaba5333 fix: correct grid styling issue (#4889) 2024-11-13 22:28:30 +08:00
Vben
5b079471b9 fix: resolve issue with grid reload parameter not working (#4888) 2024-11-13 22:27:50 +08:00
Arthur Darkstone
8cc73cf59c feat: add reize components & demo (#4862)
* feat: resize component

* chore: change positon of resize components

* feat: add resize demo

* chore: resize demo completed

* chore: fix display number

* chore: add infer comment

* fix: move reszie demo to examples

* fix: fix icon & removed scss
2024-11-13 15:43:17 +08:00
vince
a89711610d chore: update eslint configuration (#4872) 2024-11-12 13:42:32 +08:00
vince
67c2b13713 fix: drawer console warning (#4871) 2024-11-12 13:34:47 +08:00
Netfan
1ff1e4a8d7 fix: form enter event handling, fixed #4865 (#4867) 2024-11-12 13:20:48 +08:00
Arthur Darkstone
ea8af98324 docs: add route config desc (#4857) 2024-11-11 19:22:39 +08:00
Arthur Darkstone
dc15accd04 fix: clipboard demo not working with a-input (#4856) 2024-11-11 14:50:30 +08:00
vben
94efcec7da chore: release v5.4.6 2024-11-10 11:50:46 +08:00
Vben
a3d0d2ed34 feat: added file download examples (#4853) 2024-11-10 11:50:06 +08:00
Vben
90dc00b168 fix: unable to preventDefault inside passive event listener invocation (#4852) 2024-11-10 10:26:35 +08:00
Vben
ba36ce8836 feat: pinInput supports disabled props (#4851)
* feat: pinInput supports disabled props
2024-11-10 10:09:06 +08:00
vben
57d5a919d2 chore: release v5.4.5 2024-11-09 21:06:10 +08:00
Vben
546c0928fb fix: form data that is not submitted by the form should not be carried when switching paging (#4847) 2024-11-09 21:04:58 +08:00
Vben
5e44aa9283 fix: drawer header is missing (#4846) 2024-11-09 20:00:09 +08:00
Vben
26bec4222f fix: pages cannot be quickly moved back by hand gestures (#4845) 2024-11-09 16:32:55 +08:00
Vben
4005023fd4 fix: drawer component header does not take effect (#4844) 2024-11-09 15:53:17 +08:00
Vben
8617d3dd1e perf: formApi added validateAndSubmitForm & filedMapToTime renamed fieldMappingTime (#4842)
* perf: formApi added validateAndSubmitForm & filedMapToTime renamed fieldMappingTime
2024-11-09 15:00:59 +08:00
huangxiaomin
632081e828 feat: add icon-picker component (#4832)
* feat: add icon-picker component

* fix: resolve conversations

* refactor: resort @vben/hooks
2024-11-09 14:10:17 +08:00
huangxiaomin
6b9acf09dc feat: add fieldMapToTime prop to FormRenderProps (#4838) 2024-11-09 14:08:46 +08:00
Vben
2c6edafeb2 fix: when opening the tool separately, there is no need to pass the toolbar-tools slot (#4841) 2024-11-09 14:07:41 +08:00
Vben
9cf0573921 perf: optimize local startup speed and add header Class configuration to drawer (#4840) 2024-11-09 11:12:30 +08:00
Xiaoyu
da7d61b160 feat: add click-to-click event support to the WorkenchProject and WorkenchQuickNav components (#4831)
* feat(@vben/common-ui): add click event emission to WorkbenchProject and WorkbenchQuickNav components

* feat: add navigation and project link functionality to dashboard workspace

* feat: add URL property to WorkbenchProjectItem and WorkbenchQuickNavItem for enhanced navigation

---------

Co-authored-by: XiaoyuDing <xiaoyuding@keymedbio.com>
2024-11-09 10:26:58 +08:00
Vben
8f1e397077 fix: when the form is destroyed, the form parameters still exist (#4834)
* fix: when the form is destroyed, the form parameters still exist

* chore: update deps
2024-11-07 22:36:26 +08:00
Arthur Darkstone
dcdebaf7ca docs: remove unnecessary commas (#4833) 2024-11-07 21:55:20 +08:00
Vben
4e88ef0840 perf: improve the usage documentation of vben-vxe-table (#4829)
* perf: improve the usage documentation of vben-vxe-table
2024-11-06 23:03:33 +08:00
Arthur Darkstone
33ce4d3cf3 docs: add vxe-table doc (#4807)
* docs: init vxe-table demos

* style: fix vxe-table index.scss import error

* docs: fix vxe-table style & theme toggle problem

* docs: add rest demos

* docs: add vxe-table demo desc

* fix: add maximumFileSizeToCacheInBytes to fix build error

* fix: fix vxe-table set-theme build error

* docs: fix vitepress ssr render problem

* docs: add some tips for vitepress compatibility
2024-11-06 21:44:02 +08:00
zhaoweijie
6b54cb7563 chore: update vscode settings to configure stylelint for .vue files (#4821)
Co-authored-by: zhaoweijie <zhaoweijie1108@qq.com>
2024-11-06 21:34:06 +08:00
Netfan
488ccb5976 fix: page title is not updated after language switch. fixed: #4799 (#4813) 2024-11-05 13:58:56 +08:00
zyy
bbc426caa0 chore: vxe-table's renderDefault is deprecated, use renderTableDefault (#4814) 2024-11-05 11:25:57 +08:00
vben
44440d0951 chore: release v5.4.4 2024-11-04 22:47:52 +08:00
Vben
5999a862b6 perf: expose the formApi for a login form (#4806) 2024-11-04 22:46:16 +08:00
BobbyCheng
d31535cd98 docs(@vben/docs): add public static resources path documentation (#4788) (#4801)
Co-authored-by: chengjm <3497346788@qq.com>
2024-11-04 21:56:52 +08:00
Vben
35eef33779 refactor: upgrade unbuild to 3.0.0 rc version (#4797) 2024-11-02 21:21:51 +08:00
Vben
422936a981 fix: docker image build failed (#4796) 2024-11-02 20:31:05 +08:00
1302岁的龙猫
a64a06bf59 chore: disable the default form configuration of vke-table (#4794)
* fix: 处理某个页面加载多个Table时,第2个之后的Table初始化报出警告

* fix: 使用vxe-table时全局禁用formConfig
2024-11-02 15:46:19 +08:00
Vben
43d95cc587 fix: remove vite-plugin-lib-inject-css (#4793) 2024-11-02 15:30:49 +08:00
Vben
faf18d0143 fix: try to fix win32 startup error (#4792) 2024-11-02 14:54:11 +08:00
1302岁的龙猫
4b94d62145 fix: When multiple Tables are loaded on a page, a warning will be reported in the initialization of Tables after the second one (#4791) 2024-11-02 14:31:46 +08:00
eamd-wq
cf130b293d chore: update loading.md (#4787) 2024-11-02 14:30:51 +08:00
Arthur Darkstone
566f357dfa chore: add a different way for pnpm to manage nodejs version, because users may not want to download other packages. (#4786)
* docs: add a way for pnpm to manage nodejs version

* docs: add a way for pnpm to manage nodejs version

---------

Co-authored-by: liliang18 <liliang18@tal.com>
2024-11-02 14:18:56 +08:00
jackhoo(胡彪)
f78cc319ab fix(@vben-core/menu-ui): fix horizontal layout top menu language switching issue(#4724) (#4777)
Co-authored-by: jackhoo_98 <jackhoo_98@foxmail.com>
2024-10-31 22:05:51 +08:00
Netfan
06ba7cb224 feat: add opened and closed events for dialog (#4775) 2024-10-31 22:05:18 +08:00
Netfan
353e8be289 fix: long text style in sidemenu. fixed #4749 (#4770) 2024-10-30 21:43:41 +08:00
Svend
c2d59dea69 chore: add vxe-table export example (#4769) 2024-10-30 21:38:19 +08:00
Vben
e600d78cb9 chore: add vxe-table toolbar example (#4765) 2024-10-29 22:47:15 +08:00
Joy
d23db32f04 docs: typo (#4748) 2024-10-29 21:49:15 +08:00
Joy
1180291c1a docs: typo (#4755) 2024-10-29 14:06:25 +08:00
fourteendp
0cf4509c70 fix: czg configuration not effective (#4759)
closed #4758
2024-10-29 14:00:11 +08:00
vben
ad3963ff70 chore: release v5.4.3 2024-10-27 21:59:54 +08:00
Vben
fe7df5ad3b feat: allow configuration of staticRroues (#4746)
* feat: allow configuration of staticRroues

* chore: update deps

* chore: typo
2024-10-27 21:58:39 +08:00
zhou
d7d7466524 fix: fix renderEcharts refresh issue (#4741) 2024-10-27 21:18:36 +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
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
Vben
23768ea620 feat: menu routing support opens in a new window (#4715) 2024-10-22 22:24:56 +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
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
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
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
Vben
307a71fb35 fix: downgrade the sass version to suppress the warnings (#4689) 2024-10-19 22:04:31 +08:00
Vben
477a05c26c feat: menu supports carrying default query (#4687) 2024-10-19 19:50:23 +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
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
vince
c491b9e021 fix: maximum call stack size (#4674)
* fix: maximum call stack size
2024-10-18 14:24:39 +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
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
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
Svend
850a6af1e0 fix: fix the issues with the local build docker script (#4647) 2024-10-15 21:45:05 +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
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
vben
9856bc88d2 chore: release v5.4.0 2024-10-13 14:21:54 +08:00
403 changed files with 13044 additions and 8499 deletions

View File

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

12
.vscode/settings.json vendored
View File

@@ -160,6 +160,7 @@
"stylelint.enable": true,
"stylelint.packageManager": "pnpm",
"stylelint.validate": ["css", "less", "postcss", "scss", "vue"],
"stylelint.customSyntax": "postcss-html",
"stylelint.snippet": ["css", "less", "postcss", "scss", "vue"],
"typescript.inlayHints.enumMemberValues.enabled": true,
@@ -197,11 +198,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,8 +220,8 @@
"tailwind.config.mjs": "postcss.*"
},
"commentTranslate.hover.enabled": false,
"i18n-ally.keystyle": "nested",
"commentTranslate.multiLineMerge": true,
"vue.server.hybridMode": true,
"typescript.tsdk": "node_modules/typescript/lib"
"typescript.tsdk": "node_modules/typescript/lib",
"oxc.enable": false
}

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

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

@@ -1,5 +1,6 @@
import errorHandler from './error';
process.env.COMPATIBILITY_DATE = new Date().toISOString();
export default defineNitroConfig({
devErrorHandler: errorHandler,
errorHandler: '~/error',

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,9 +39,12 @@ 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('Forbidden Exception', 'Forbidden Exception');
return useResponseError(message, message);
}
export function unAuthorizedResponse(event: H3Event<EventHandlerRequest>) {

View File

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

View File

@@ -0,0 +1,127 @@
/**
* 通用组件共同的使用的基础组件,原先放在 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';
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'
| 'Input'
| 'InputNumber'
| 'InputPassword'
| 'Mentions'
| 'PrimaryButton'
| 'Radio'
| 'RadioGroup'
| 'RangePicker'
| 'Rate'
| '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,
};
// 将组件注册到全局共享状态中
globalShareState.setComponents(components);
// 定义全局共享状态中的消息提示
globalShareState.defineMessage({
// 复制成功消息提示
copyPreferencesSuccess: (title, content) => {
notification.success({
description: content,
message: title,
placement: 'bottomRight',
});
},
});
}
export { initComponentAdapter };

View File

@@ -1,105 +1,14 @@
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';
// 这里需要自行根据业务组件库进行适配,需要用到的组件都需要在这里类型说明
export type FormComponentType =
| 'AutoComplete'
| 'Checkbox'
| 'CheckboxGroup'
| 'DatePicker'
| 'Divider'
| 'Input'
| 'InputNumber'
| 'InputPassword'
| 'Mentions'
| 'Radio'
| 'RadioGroup'
| 'RangePicker'
| 'Rate'
| '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,
Select: withDefaultPlaceholder(Select, 'select'),
Space,
Switch,
Textarea: withDefaultPlaceholder(Textarea, 'input'),
TimePicker,
TreeSelect: withDefaultPlaceholder(TreeSelect, 'select'),
Upload,
},
setupVbenForm<ComponentType>({
config: {
// ant design vue组件库默认都是 v-model:value
baseModelPropName: 'value',
@@ -116,23 +25,23 @@ 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;
},
// 选择项目必填国际化适配
selectRequired: (value, _params, ctx) => {
if (value === undefined || value === null) {
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 };

View File

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

View File

@@ -11,8 +11,15 @@ setupVbenVxeTable({
vxeUI.setConfig({
grid: {
align: 'center',
border: true,
border: false,
columnConfig: {
resizable: true,
},
minHeight: 180,
formConfig: {
// 全局禁用vxe-table的表单配置使用formOptions
enabled: false,
},
proxyConfig: {
autoLoad: true,
response: {
@@ -24,13 +31,14 @@ setupVbenVxeTable({
showResponseMsg: false,
},
round: true,
showOverflow: true,
size: 'small',
},
});
// 表格配置项可以用 cellRender: { name: 'CellImage' },
vxeUI.renderer.add('CellImage', {
renderDefault(_renderOpts, params) {
renderTableDefault(_renderOpts, params) {
const { column, row } = params;
return h(Image, { src: row[column.field] });
},
@@ -38,7 +46,7 @@ setupVbenVxeTable({
// 表格配置项可以用 cellRender: { name: 'CellLink' },
vxeUI.renderer.add('CellLink', {
renderDefault(renderOpts) {
renderTableDefault(renderOpts) {
const { props } = renderOpts;
return h(
Button,

View File

@@ -74,11 +74,12 @@ function createRequestClient(baseURL: string) {
fulfilled: (response) => {
const { data: responseData, status } = response;
const { code, data, message: msg } = responseData;
const { code, data } = responseData;
if (status >= 200 && status < 400 && code === 0) {
return data;
}
throw new Error(`Error ${status}: ${msg}`);
throw Object.assign({}, response, { response });
},
});

View File

@@ -1,16 +1,23 @@
import { createApp } from 'vue';
import { createApp, watchEffect } from 'vue';
import { registerAccessDirective } from '@vben/access';
import { preferences } from '@vben/preferences';
import { initStores } from '@vben/stores';
import '@vben/styles';
import '@vben/styles/antd';
import { setupI18n } from '#/locales';
import { useTitle } from '@vueuse/core';
import { $t, 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);
// 国际化 i18n 配置
@@ -25,6 +32,16 @@ async function bootstrap(namespace: string) {
// 配置路由及路由守卫
app.use(router);
// 动态更新标题
watchEffect(() => {
if (preferences.app.dynamicTitle) {
const routeTitle = router.currentRoute.value.meta?.title;
const pageTitle =
(routeTitle ? `${$t(routeTitle)} - ` : '') + preferences.app.name;
useTitle(pageTitle);
}
});
app.mount('#app');
}

View File

@@ -68,7 +68,7 @@ const menus = computed(() => [
});
},
icon: BookOpenText,
text: $t('widgets.document'),
text: $t('ui.widgets.document'),
},
{
handler: () => {
@@ -86,7 +86,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,8 +0,0 @@
{
"page": {
"demos": {
"title": "Demos",
"antd": "Ant Design Vue"
}
}
}

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,14 @@
{
"auth": {
"login": "Login",
"register": "Register",
"codeLogin": "Code Login",
"qrcodeLogin": "Qr Code Login",
"forgetPassword": "Forget Password"
},
"dashboard": {
"title": "Dashboard",
"analytics": "Analytics",
"workspace": "Workspace"
}
}

View File

@@ -1,8 +0,0 @@
{
"page": {
"demos": {
"title": "演示",
"antd": "Ant Design Vue"
}
}
}

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,14 @@
{
"auth": {
"login": "登录",
"register": "注册",
"codeLogin": "验证码登录",
"qrcodeLogin": "二维码登录",
"forgetPassword": "忘记密码"
},
"dashboard": {
"title": "概览",
"analytics": "分析页",
"workspace": "工作台"
}
}

View File

@@ -3,6 +3,7 @@ import { defineOverridesPreferences } from '@vben/preferences';
/**
* @description 项目配置文件
* 只需要覆盖项目中的一部分配置,不需要的配置不用覆盖,会自动使用默认配置
* !!! 更改配置后请清空缓存,否则可能不生效
*/
export const overridesPreferences = defineOverridesPreferences({
// overrides

View File

@@ -5,10 +5,7 @@ import { preferences } from '@vben/preferences';
import { useAccessStore, useUserStore } from '@vben/stores';
import { startProgress, stopProgress } from '@vben/utils';
import { useTitle } from '@vueuse/core';
import { $t } from '#/locales';
import { coreRouteNames, dynamicRoutes } from '#/router/routes';
import { accessRoutes, coreRouteNames } from '#/router/routes';
import { useAuthStore } from '#/store';
import { generateAccess } from './access';
@@ -40,13 +37,6 @@ function setupCommonGuard(router: Router) {
if (preferences.transition.progress) {
stopProgress();
}
// 动态修改标题
if (preferences.app.dynamicTitle) {
const { title } = to.meta;
// useTitle(`${$t(title)} - ${preferences.app.name}`);
useTitle(`${$t(title)} - ${preferences.app.name}`);
}
});
}
@@ -105,7 +95,7 @@ function setupAccessGuard(router: Router) {
roles: userRoles,
router,
// 则会在菜单中显示但是访问会被重定向到403
routes: dynamicRoutes,
routes: accessRoutes,
});
// 保存菜单信息和路由信息

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,17 +32,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'),
},
},
{
@@ -50,7 +52,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'),
},
},
{
@@ -59,7 +61,7 @@ const coreRoutes: RouteRecordRaw[] = [
component: () =>
import('#/views/_core/authentication/qrcode-login.vue'),
meta: {
title: $t('page.core.qrcodeLogin'),
title: $t('page.auth.qrcodeLogin'),
},
},
{
@@ -68,7 +70,7 @@ const coreRoutes: RouteRecordRaw[] = [
component: () =>
import('#/views/_core/authentication/forget-password.vue'),
meta: {
title: $t('page.core.forgetPassword'),
title: $t('page.auth.forgetPassword'),
},
},
{
@@ -76,7 +78,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

@@ -10,15 +10,19 @@ const dynamicRouteFiles = import.meta.glob('./modules/**/*.ts', {
// 有需要可以自行打开注释,并创建文件夹
// const externalRouteFiles = import.meta.glob('./external/**/*.ts', { eager: true });
// const staticRouteFiles = import.meta.glob('./static/**/*.ts', { eager: true });
/** 动态路由 */
const dynamicRoutes: RouteRecordRaw[] = mergeRouteModules(dynamicRouteFiles);
/** 外部路由列表访问这些页面可以不需要Layout可能用于内嵌在别的系统 */
/** 外部路由列表访问这些页面可以不需要Layout可能用于内嵌在别的系统(不会显示在菜单中) */
// const externalRoutes: RouteRecordRaw[] = mergeRouteModules(externalRouteFiles);
// const staticRoutes: RouteRecordRaw[] = mergeRouteModules(staticRouteFiles);
const staticRoutes: RouteRecordRaw[] = [];
const externalRoutes: RouteRecordRaw[] = [];
/** 路由列表,由基本路由+静态路由组成 */
/** 路由列表,由基本路由、外部路由和404兜底路由组成
* 无需走权限验证(会一直显示在菜单中) */
const routes: RouteRecordRaw[] = [
...coreRoutes,
...externalRoutes,
@@ -28,4 +32,6 @@ const routes: RouteRecordRaw[] = [
/** 基本路由列表,这些路由不需要进入权限拦截 */
const coreRouteNames = traverseTreeValues(coreRoutes, (route) => route.name);
export { coreRouteNames, dynamicRoutes, routes };
/** 有权限校验的路由列表,包含动态路由和静态路由 */
const accessRoutes = [...dynamicRoutes, ...staticRoutes];
export { accessRoutes, coreRouteNames, routes };

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

@@ -1,5 +1,4 @@
import type { LoginAndRegisterParams } from '@vben/common-ui';
import type { UserInfo } from '@vben/types';
import type { Recordable, UserInfo } from '@vben/types';
import { ref } from 'vue';
import { useRouter } from 'vue-router';
@@ -26,7 +25,7 @@ export const useAuthStore = defineStore('auth', () => {
* @param params 登录表单数据
*/
async function authLogin(
params: LoginAndRegisterParams,
params: Recordable<any>,
onSuccess?: () => Promise<void> | void,
) {
// 异步处理用户登录操作并获取 accessToken
@@ -84,7 +83,7 @@ export const useAuthStore = defineStore('auth', () => {
resetAllStores();
accessStore.setLoginExpired(false);
// 回登页带上当前路由地址
// 回登页带上当前路由地址
await router.replace({
path: LOGIN_PATH,
query: redirect

View File

@@ -1,5 +1,6 @@
<script lang="ts" setup>
import type { LoginCodeParams, VbenFormSchema } from '@vben/common-ui';
import type { VbenFormSchema } from '@vben/common-ui';
import type { Recordable } from '@vben/types';
import { computed, ref } from 'vue';
@@ -49,7 +50,7 @@ const formSchema = computed((): VbenFormSchema[] => {
* Asynchronously handle the login process
* @param values 登录表单数据
*/
async function handleLogin(values: LoginCodeParams) {
async function handleLogin(values: Recordable<any>) {
// eslint-disable-next-line no-console
console.log(values);
}

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>) {
// eslint-disable-next-line no-console
console.log('reset email:', value);
}

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>) {
// eslint-disable-next-line no-console
console.log('register submit:', value);
}

View File

@@ -7,6 +7,7 @@ import type {
} from '@vben/common-ui';
import { ref } from 'vue';
import { useRouter } from 'vue-router';
import {
AnalysisChartCard,
@@ -18,11 +19,15 @@ import {
} from '@vben/common-ui';
import { preferences } from '@vben/preferences';
import { useUserStore } from '@vben/stores';
import { openWindow } from '@vben/utils';
import AnalyticsVisitsSource from '../analytics/analytics-visits-source.vue';
const userStore = useUserStore();
// 这是一个示例数据,实际项目中需要根据实际情况进行调整
// url 也可以是内部路由,在 navTo 方法中识别处理,进行内部跳转
// 例如url: /dashboard/workspace
const projectItems: WorkbenchProjectItem[] = [
{
color: '',
@@ -31,6 +36,7 @@ const projectItems: WorkbenchProjectItem[] = [
group: '开源组',
icon: 'carbon:logo-github',
title: 'Github',
url: 'https://github.com',
},
{
color: '#3fb27f',
@@ -39,6 +45,7 @@ const projectItems: WorkbenchProjectItem[] = [
group: '算法组',
icon: 'ion:logo-vue',
title: 'Vue',
url: 'https://vuejs.org',
},
{
color: '#e18525',
@@ -47,6 +54,7 @@ const projectItems: WorkbenchProjectItem[] = [
group: '上班摸鱼',
icon: 'ion:logo-html5',
title: 'Html5',
url: 'https://developer.mozilla.org/zh-CN/docs/Web/HTML',
},
{
color: '#bf0c2c',
@@ -55,6 +63,7 @@ const projectItems: WorkbenchProjectItem[] = [
group: 'UI',
icon: 'ion:logo-angular',
title: 'Angular',
url: 'https://angular.io',
},
{
color: '#00d8ff',
@@ -63,6 +72,7 @@ const projectItems: WorkbenchProjectItem[] = [
group: '技术牛',
icon: 'bx:bxl-react',
title: 'React',
url: 'https://reactjs.org',
},
{
color: '#EBD94E',
@@ -71,39 +81,47 @@ const projectItems: WorkbenchProjectItem[] = [
group: '架构组',
icon: 'ion:logo-javascript',
title: 'Js',
url: 'https://developer.mozilla.org/zh-CN/docs/Web/JavaScript',
},
];
// 同样,这里的 url 也可以使用以 http 开头的外部链接
const quickNavItems: WorkbenchQuickNavItem[] = [
{
color: '#1fdaca',
icon: 'ion:home-outline',
title: '首页',
url: '/',
},
{
color: '#bf0c2c',
icon: 'ion:grid-outline',
title: '仪表盘',
url: '/dashboard',
},
{
color: '#e18525',
icon: 'ion:layers-outline',
title: '组件',
url: '/demos/features/icons',
},
{
color: '#3fb27f',
icon: 'ion:settings-outline',
title: '系统管理',
url: '/demos/features/login-expired', // 这里的 URL 是示例,实际项目中需要根据实际情况进行调整
},
{
color: '#4daf1bc9',
icon: 'ion:key-outline',
title: '权限管理',
url: '/demos/access/page-control',
},
{
color: '#00d8ff',
icon: 'ion:bar-chart-outline',
title: '图表',
url: '/analytics',
},
];
@@ -195,6 +213,24 @@ const trendItems: WorkbenchTrendItem[] = [
title: 'Vben',
},
];
const router = useRouter();
// 这是一个示例方法,实际项目中需要根据实际情况进行调整
// This is a sample method, adjust according to the actual project requirements
function navTo(nav: WorkbenchProjectItem | WorkbenchQuickNavItem) {
if (nav.url?.startsWith('http')) {
openWindow(nav.url);
return;
}
if (nav.url?.startsWith('/')) {
router.push(nav.url).catch((error) => {
console.error('Navigation failed:', error);
});
} else {
console.warn(`Unknown URL for navigation item: ${nav.title} -> ${nav.url}`);
}
}
</script>
<template>
@@ -210,7 +246,7 @@ const trendItems: WorkbenchTrendItem[] = [
<div class="mt-5 flex flex-col lg:flex-row">
<div class="mr-4 w-full lg:w-3/5">
<WorkbenchProject :items="projectItems" title="项目" />
<WorkbenchProject :items="projectItems" title="项目" @click="navTo" />
<WorkbenchTrends :items="trendItems" class="mt-5" title="最新动态" />
</div>
<div class="w-full lg:w-2/5">
@@ -218,6 +254,7 @@ const trendItems: WorkbenchTrendItem[] = [
:items="quickNavItems"
class="mt-5 lg:mt-0"
title="快捷导航"
@click="navTo"
/>
<WorkbenchTodo :items="todoItems" class="mt-5" title="待办事项" />
<AnalysisChartCard class="mt-5" title="访问来源">

View File

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

View File

@@ -0,0 +1,106 @@
/**
* 通用组件共同的使用的基础组件,原先放在 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 {
ElButton,
ElCheckbox,
ElCheckboxGroup,
ElDatePicker,
ElDivider,
ElInput,
ElInputNumber,
ElNotification,
ElRadioGroup,
ElSelect,
ElSpace,
ElSwitch,
ElTimePicker,
ElTreeSelect,
ElUpload,
} from 'element-plus';
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 =
| 'Checkbox'
| 'CheckboxGroup'
| 'DatePicker'
| 'Divider'
| 'Input'
| 'InputNumber'
| 'RadioGroup'
| 'Select'
| 'Space'
| 'Switch'
| 'TimePicker'
| 'TreeSelect'
| 'Upload'
| BaseFormComponentType;
async function initComponentAdapter() {
const components: Partial<Record<ComponentType, Component>> = {
// 如果你的组件体积比较大,可以使用异步加载
// Button: () =>
// import('xxx').then((res) => res.Button),
Checkbox: ElCheckbox,
CheckboxGroup: ElCheckboxGroup,
// 自定义默认按钮
DefaulButton: (props, { attrs, slots }) => {
return h(ElButton, { ...props, attrs, type: 'info' }, slots);
},
// 自定义主要按钮
PrimaryButton: (props, { attrs, slots }) => {
return h(ElButton, { ...props, attrs, type: 'primary' }, slots);
},
Divider: ElDivider,
Input: withDefaultPlaceholder(ElInput, 'input'),
InputNumber: withDefaultPlaceholder(ElInputNumber, 'input'),
RadioGroup: ElRadioGroup,
Select: withDefaultPlaceholder(ElSelect, 'select'),
Space: ElSpace,
Switch: ElSwitch,
TimePicker: ElTimePicker,
DatePicker: ElDatePicker,
TreeSelect: withDefaultPlaceholder(ElTreeSelect, 'select'),
Upload: ElUpload,
};
// 将组件注册到全局共享状态中
globalShareState.setComponents(components);
// 定义全局共享状态中的消息提示
globalShareState.defineMessage({
// 复制成功消息提示
copyPreferencesSuccess: (title, content) => {
ElNotification({
title,
message: content,
position: 'bottom-right',
duration: 0,
type: 'success',
});
},
});
}
export { initComponentAdapter };

View File

@@ -1,82 +1,14 @@
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 {
ElButton,
ElCheckbox,
ElCheckboxGroup,
ElDivider,
ElInput,
ElInputNumber,
ElRadioGroup,
ElSelect,
ElSpace,
ElSwitch,
ElTimePicker,
ElTreeSelect,
ElUpload,
} from 'element-plus';
// 业务表单组件适配
export type FormComponentType =
| 'Checkbox'
| 'CheckboxGroup'
| 'DatePicker'
| 'Divider'
| 'Input'
| 'InputNumber'
| 'RadioGroup'
| 'Select'
| 'Space'
| 'Switch'
| '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: {
Checkbox: ElCheckbox,
CheckboxGroup: ElCheckboxGroup,
// 自定义默认的重置按钮
DefaultResetActionButton: (props, { attrs, slots }) => {
return h(ElButton, { ...props, attrs, type: 'info' }, slots);
},
// 自定义默认的提交按钮
DefaultSubmitActionButton: (props, { attrs, slots }) => {
return h(ElButton, { ...props, attrs, type: 'primary' }, slots);
},
Divider: ElDivider,
Input: withDefaultPlaceholder(ElInput, 'input'),
InputNumber: withDefaultPlaceholder(ElInputNumber, 'input'),
RadioGroup: ElRadioGroup,
Select: withDefaultPlaceholder(ElSelect, 'select'),
Space: ElSpace,
Switch: ElSwitch,
TimePicker: ElTimePicker,
TreeSelect: withDefaultPlaceholder(ElTreeSelect, 'select'),
Upload: ElUpload,
},
setupVbenForm<ComponentType>({
config: {
modelPropNameMap: {
Upload: 'fileList',
@@ -85,22 +17,22 @@ setupVbenForm<FormComponentType>({
defineRules: {
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;
},
selectRequired: (value, _params, ctx) => {
if (value === undefined || value === null) {
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 };

View File

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

View File

@@ -11,8 +11,15 @@ setupVbenVxeTable({
vxeUI.setConfig({
grid: {
align: 'center',
border: true,
border: false,
columnConfig: {
resizable: true,
},
minHeight: 180,
formConfig: {
// 全局禁用vxe-table的表单配置使用formOptions
enabled: false,
},
proxyConfig: {
autoLoad: true,
response: {
@@ -24,13 +31,14 @@ setupVbenVxeTable({
showResponseMsg: false,
},
round: true,
showOverflow: true,
size: 'small',
},
});
// 表格配置项可以用 cellRender: { name: 'CellImage' },
vxeUI.renderer.add('CellImage', {
renderDefault(_renderOpts, params) {
renderTableDefault(_renderOpts, params) {
const { column, row } = params;
const src = row[column.field];
return h(ElImage, { src, previewSrcList: [src] });
@@ -39,7 +47,7 @@ setupVbenVxeTable({
// 表格配置项可以用 cellRender: { name: 'CellLink' },
vxeUI.renderer.add('CellLink', {
renderDefault(renderOpts) {
renderTableDefault(renderOpts) {
const { props } = renderOpts;
return h(
ElButton,

View File

@@ -74,11 +74,11 @@ function createRequestClient(baseURL: string) {
fulfilled: (response) => {
const { data: responseData, status } = response;
const { code, data, message: msg } = responseData;
const { code, data } = responseData;
if (status >= 200 && status < 400 && code === 0) {
return data;
}
throw new Error(`Error ${status}: ${msg}`);
throw Object.assign({}, response, { response });
},
});

View File

@@ -1,16 +1,22 @@
import { createApp } from 'vue';
import { createApp, watchEffect } from 'vue';
import { registerAccessDirective } from '@vben/access';
import { preferences } from '@vben/preferences';
import { initStores } from '@vben/stores';
import '@vben/styles';
import '@vben/styles/ele';
import { setupI18n } from '#/locales';
import { useTitle } from '@vueuse/core';
import { $t, 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);
// 国际化 i18n 配置
@@ -25,6 +31,16 @@ async function bootstrap(namespace: string) {
// 配置路由及路由守卫
app.use(router);
// 动态更新标题
watchEffect(() => {
if (preferences.app.dynamicTitle) {
const routeTitle = router.currentRoute.value.meta?.title;
const pageTitle =
(routeTitle ? `${$t(routeTitle)} - ` : '') + preferences.app.name;
useTitle(pageTitle);
}
});
app.mount('#app');
}

View File

@@ -68,7 +68,7 @@ const menus = computed(() => [
});
},
icon: BookOpenText,
text: $t('widgets.document'),
text: $t('ui.widgets.document'),
},
{
handler: () => {
@@ -86,7 +86,7 @@ const menus = computed(() => [
});
},
icon: CircleHelp,
text: $t('widgets.qa'),
text: $t('ui.widgets.qa'),
},
]);

View File

@@ -4,7 +4,11 @@ import type { Language } from 'element-plus/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 dayjs from 'dayjs';
@@ -13,10 +17,12 @@ import defaultLocale from 'element-plus/es/locale/lang/zh-cn';
const elementLocale = ref<Language>(defaultLocale);
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 loadElementLocale(lang: SupportedLanguagesType) {
switch (lang) {
case 'zh-CN': {
elementLocale.value = defaultLocale;
break;
}
case 'en-US': {
elementLocale.value = enLocale;
break;
}
case 'zh-CN': {
elementLocale.value = defaultLocale;
break;
}
}
}

View File

@@ -1,8 +0,0 @@
{
"page": {
"demos": {
"title": "Demos",
"element-plus": "Element Plus"
}
}
}

View File

@@ -0,0 +1,12 @@
{
"title": "Demos",
"elementPlus": "Element Plus",
"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,14 @@
{
"auth": {
"login": "Login",
"register": "Register",
"codeLogin": "Code Login",
"qrcodeLogin": "Qr Code Login",
"forgetPassword": "Forget Password"
},
"dashboard": {
"title": "Dashboard",
"analytics": "Analytics",
"workspace": "Workspace"
}
}

View File

@@ -1,8 +0,0 @@
{
"page": {
"demos": {
"title": "演示",
"element-plus": "Element Plus"
}
}
}

View File

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

View File

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

View File

@@ -3,6 +3,7 @@ import { defineOverridesPreferences } from '@vben/preferences';
/**
* @description 项目配置文件
* 只需要覆盖项目中的一部分配置,不需要的配置不用覆盖,会自动使用默认配置
* !!! 更改配置后请清空缓存,否则可能不生效
*/
export const overridesPreferences = defineOverridesPreferences({
// overrides

View File

@@ -5,10 +5,7 @@ import { preferences } from '@vben/preferences';
import { useAccessStore, useUserStore } from '@vben/stores';
import { startProgress, stopProgress } from '@vben/utils';
import { useTitle } from '@vueuse/core';
import { $t } from '#/locales';
import { coreRouteNames, dynamicRoutes } from '#/router/routes';
import { accessRoutes, coreRouteNames } from '#/router/routes';
import { useAuthStore } from '#/store';
import { generateAccess } from './access';
@@ -40,13 +37,6 @@ function setupCommonGuard(router: Router) {
if (preferences.transition.progress) {
stopProgress();
}
// 动态修改标题
if (preferences.app.dynamicTitle) {
const { title } = to.meta;
// useTitle(`${$t(title)} - ${preferences.app.name}`);
useTitle(`${$t(title)} - ${preferences.app.name}`);
}
});
}
@@ -105,7 +95,7 @@ function setupAccessGuard(router: Router) {
roles: userRoles,
router,
// 则会在菜单中显示但是访问会被重定向到403
routes: dynamicRoutes,
routes: accessRoutes,
});
// 保存菜单信息和路由信息

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,17 +32,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'),
},
},
{
@@ -50,7 +52,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'),
},
},
{
@@ -59,7 +61,7 @@ const coreRoutes: RouteRecordRaw[] = [
component: () =>
import('#/views/_core/authentication/qrcode-login.vue'),
meta: {
title: $t('page.core.qrcodeLogin'),
title: $t('page.auth.qrcodeLogin'),
},
},
{
@@ -68,7 +70,7 @@ const coreRoutes: RouteRecordRaw[] = [
component: () =>
import('#/views/_core/authentication/forget-password.vue'),
meta: {
title: $t('page.core.forgetPassword'),
title: $t('page.auth.forgetPassword'),
},
},
{
@@ -76,7 +78,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

@@ -10,15 +10,19 @@ const dynamicRouteFiles = import.meta.glob('./modules/**/*.ts', {
// 有需要可以自行打开注释,并创建文件夹
// const externalRouteFiles = import.meta.glob('./external/**/*.ts', { eager: true });
// const staticRouteFiles = import.meta.glob('./static/**/*.ts', { eager: true });
/** 动态路由 */
const dynamicRoutes: RouteRecordRaw[] = mergeRouteModules(dynamicRouteFiles);
/** 外部路由列表访问这些页面可以不需要Layout可能用于内嵌在别的系统 */
/** 外部路由列表访问这些页面可以不需要Layout可能用于内嵌在别的系统(不会显示在菜单中) */
// const externalRoutes: RouteRecordRaw[] = mergeRouteModules(externalRouteFiles);
// const staticRoutes: RouteRecordRaw[] = mergeRouteModules(staticRouteFiles);
const staticRoutes: RouteRecordRaw[] = [];
const externalRoutes: RouteRecordRaw[] = [];
/** 路由列表,由基本路由+静态路由组成 */
/** 路由列表,由基本路由、外部路由和404兜底路由组成
* 无需走权限验证(会一直显示在菜单中) */
const routes: RouteRecordRaw[] = [
...coreRoutes,
...externalRoutes,
@@ -28,4 +32,6 @@ const routes: RouteRecordRaw[] = [
/** 基本路由列表,这些路由不需要进入权限拦截 */
const coreRouteNames = traverseTreeValues(coreRoutes, (route) => route.name);
export { coreRouteNames, dynamicRoutes, routes };
/** 有权限校验的路由列表,包含动态路由和静态路由 */
const accessRoutes = [...dynamicRoutes, ...staticRoutes];
export { accessRoutes, coreRouteNames, routes };

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.element-plus'),
title: $t('demos.elementPlus'),
},
name: 'NaiveDemos',
path: '/demos/element',

View File

@@ -19,7 +19,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',
@@ -30,7 +30,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'),
},
},
{
@@ -40,7 +40,7 @@ const routes: RouteRecordRaw[] = [
meta: {
icon: 'lucide:book-open-text',
link: VBEN_DOC_URL,
title: $t('page.vben.document'),
title: $t('demos.vben.document'),
},
},
{
@@ -61,7 +61,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'),
},
},
{
@@ -72,7 +72,7 @@ const routes: RouteRecordRaw[] = [
badgeType: 'dot',
icon: SvgAntdvLogoIcon,
link: VBEN_ANT_PREVIEW_URL,
title: $t('page.vben.antdv'),
title: $t('demos.vben.antdv'),
},
},
],

View File

@@ -1,5 +1,4 @@
import type { LoginAndRegisterParams } from '@vben/common-ui';
import type { UserInfo } from '@vben/types';
import type { Recordable, UserInfo } from '@vben/types';
import { ref } from 'vue';
import { useRouter } from 'vue-router';
@@ -26,7 +25,7 @@ export const useAuthStore = defineStore('auth', () => {
* @param params 登录表单数据
*/
async function authLogin(
params: LoginAndRegisterParams,
params: Recordable<any>,
onSuccess?: () => Promise<void> | void,
) {
// 异步处理用户登录操作并获取 accessToken
@@ -85,7 +84,7 @@ export const useAuthStore = defineStore('auth', () => {
resetAllStores();
accessStore.setLoginExpired(false);
// 回登页带上当前路由地址
// 回登页带上当前路由地址
await router.replace({
path: LOGIN_PATH,
query: redirect

View File

@@ -1,5 +1,6 @@
<script lang="ts" setup>
import type { LoginCodeParams, VbenFormSchema } from '@vben/common-ui';
import type { VbenFormSchema } from '@vben/common-ui';
import type { Recordable } from '@vben/types';
import { computed, ref } from 'vue';
@@ -49,7 +50,7 @@ const formSchema = computed((): VbenFormSchema[] => {
* Asynchronously handle the login process
* @param values 登录表单数据
*/
async function handleLogin(values: LoginCodeParams) {
async function handleLogin(values: Recordable<any>) {
// eslint-disable-next-line no-console
console.log(values);
}

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>) {
// eslint-disable-next-line no-console
console.log('reset email:', value);
}

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>) {
// eslint-disable-next-line no-console
console.log('register submit:', value);
}

View File

@@ -7,6 +7,7 @@ import type {
} from '@vben/common-ui';
import { ref } from 'vue';
import { useRouter } from 'vue-router';
import {
AnalysisChartCard,
@@ -18,11 +19,15 @@ import {
} from '@vben/common-ui';
import { preferences } from '@vben/preferences';
import { useUserStore } from '@vben/stores';
import { openWindow } from '@vben/utils';
import AnalyticsVisitsSource from '../analytics/analytics-visits-source.vue';
const userStore = useUserStore();
// 这是一个示例数据,实际项目中需要根据实际情况进行调整
// url 也可以是内部路由,在 navTo 方法中识别处理,进行内部跳转
// 例如url: /dashboard/workspace
const projectItems: WorkbenchProjectItem[] = [
{
color: '',
@@ -31,6 +36,7 @@ const projectItems: WorkbenchProjectItem[] = [
group: '开源组',
icon: 'carbon:logo-github',
title: 'Github',
url: 'https://github.com',
},
{
color: '#3fb27f',
@@ -39,6 +45,7 @@ const projectItems: WorkbenchProjectItem[] = [
group: '算法组',
icon: 'ion:logo-vue',
title: 'Vue',
url: 'https://vuejs.org',
},
{
color: '#e18525',
@@ -47,6 +54,7 @@ const projectItems: WorkbenchProjectItem[] = [
group: '上班摸鱼',
icon: 'ion:logo-html5',
title: 'Html5',
url: 'https://developer.mozilla.org/zh-CN/docs/Web/HTML',
},
{
color: '#bf0c2c',
@@ -55,6 +63,7 @@ const projectItems: WorkbenchProjectItem[] = [
group: 'UI',
icon: 'ion:logo-angular',
title: 'Angular',
url: 'https://angular.io',
},
{
color: '#00d8ff',
@@ -63,6 +72,7 @@ const projectItems: WorkbenchProjectItem[] = [
group: '技术牛',
icon: 'bx:bxl-react',
title: 'React',
url: 'https://reactjs.org',
},
{
color: '#EBD94E',
@@ -71,39 +81,47 @@ const projectItems: WorkbenchProjectItem[] = [
group: '架构组',
icon: 'ion:logo-javascript',
title: 'Js',
url: 'https://developer.mozilla.org/zh-CN/docs/Web/JavaScript',
},
];
// 同样,这里的 url 也可以使用以 http 开头的外部链接
const quickNavItems: WorkbenchQuickNavItem[] = [
{
color: '#1fdaca',
icon: 'ion:home-outline',
title: '首页',
url: '/',
},
{
color: '#bf0c2c',
icon: 'ion:grid-outline',
title: '仪表盘',
url: '/dashboard',
},
{
color: '#e18525',
icon: 'ion:layers-outline',
title: '组件',
url: '/demos/features/icons',
},
{
color: '#3fb27f',
icon: 'ion:settings-outline',
title: '系统管理',
url: '/demos/features/login-expired', // 这里的 URL 是示例,实际项目中需要根据实际情况进行调整
},
{
color: '#4daf1bc9',
icon: 'ion:key-outline',
title: '权限管理',
url: '/demos/access/page-control',
},
{
color: '#00d8ff',
icon: 'ion:bar-chart-outline',
title: '图表',
url: '/analytics',
},
];
@@ -195,6 +213,24 @@ const trendItems: WorkbenchTrendItem[] = [
title: 'Vben',
},
];
const router = useRouter();
// 这是一个示例方法,实际项目中需要根据实际情况进行调整
// This is a sample method, adjust according to the actual project requirements
function navTo(nav: WorkbenchProjectItem | WorkbenchQuickNavItem) {
if (nav.url?.startsWith('http')) {
openWindow(nav.url);
return;
}
if (nav.url?.startsWith('/')) {
router.push(nav.url).catch((error) => {
console.error('Navigation failed:', error);
});
} else {
console.warn(`Unknown URL for navigation item: ${nav.title} -> ${nav.url}`);
}
}
</script>
<template>
@@ -210,7 +246,7 @@ const trendItems: WorkbenchTrendItem[] = [
<div class="mt-5 flex flex-col lg:flex-row">
<div class="mr-4 w-full lg:w-3/5">
<WorkbenchProject :items="projectItems" title="项目" />
<WorkbenchProject :items="projectItems" title="项目" @click="navTo" />
<WorkbenchTrends :items="trendItems" class="mt-5" title="最新动态" />
</div>
<div class="w-full lg:w-2/5">
@@ -218,6 +254,7 @@ const trendItems: WorkbenchTrendItem[] = [
:items="quickNavItems"
class="mt-5 lg:mt-0"
title="快捷导航"
@click="navTo"
/>
<WorkbenchTodo :items="todoItems" class="mt-5" title="待办事项" />
<AnalysisChartCard class="mt-5" title="访问来源">

View File

@@ -57,6 +57,7 @@ const tableData = [
<ElCard class="mb-5">
<template #header> 按钮 </template>
<ElSpace>
<ElButton text>Text</ElButton>
<ElButton>Default</ElButton>
<ElButton type="primary"> Primary </ElButton>
<ElButton type="info"> Info </ElButton>

View File

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

View File

@@ -0,0 +1,103 @@
/**
* 通用组件共同的使用的基础组件,原先放在 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 {
NButton,
NCheckbox,
NCheckboxGroup,
NDatePicker,
NDivider,
NInput,
NInputNumber,
NRadioGroup,
NSelect,
NSpace,
NSwitch,
NTimePicker,
NTreeSelect,
NUpload,
} from 'naive-ui';
import { message } from '#/adapter/naive';
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 =
| 'Checkbox'
| 'CheckboxGroup'
| 'DatePicker'
| 'Divider'
| 'Input'
| 'InputNumber'
| 'RadioGroup'
| 'Select'
| 'Space'
| 'Switch'
| 'TimePicker'
| 'TreeSelect'
| 'Upload'
| BaseFormComponentType;
async function initComponentAdapter() {
const components: Partial<Record<ComponentType, Component>> = {
// 如果你的组件体积比较大,可以使用异步加载
// Button: () =>
// import('xxx').then((res) => res.Button),
Checkbox: NCheckbox,
CheckboxGroup: NCheckboxGroup,
DatePicker: NDatePicker,
// 自定义默认按钮
DefaultButton: (props, { attrs, slots }) => {
return h(NButton, { ...props, attrs, type: 'default' }, slots);
},
// 自定义主要按钮
PrimaryButton: (props, { attrs, slots }) => {
return h(NButton, { ...props, attrs, type: 'primary' }, slots);
},
Divider: NDivider,
Input: withDefaultPlaceholder(NInput, 'input'),
InputNumber: withDefaultPlaceholder(NInputNumber, 'input'),
RadioGroup: NRadioGroup,
Select: withDefaultPlaceholder(NSelect, 'select'),
Space: NSpace,
Switch: NSwitch,
TimePicker: NTimePicker,
TreeSelect: withDefaultPlaceholder(NTreeSelect, 'select'),
Upload: NUpload,
};
// 将组件注册到全局共享状态中
globalShareState.setComponents(components);
// 定义全局共享状态中的消息提示
globalShareState.defineMessage({
// 复制成功消息提示
copyPreferencesSuccess: (title, content) => {
message.success(content || title, {
duration: 0,
});
},
});
}
export { initComponentAdapter };

View File

@@ -1,84 +1,14 @@
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 {
NButton,
NCheckbox,
NCheckboxGroup,
NDatePicker,
NDivider,
NInput,
NInputNumber,
NRadioGroup,
NSelect,
NSpace,
NSwitch,
NTimePicker,
NTreeSelect,
NUpload,
} from 'naive-ui';
// 业务表单组件适配
export type FormComponentType =
| 'Checkbox'
| 'CheckboxGroup'
| 'DatePicker'
| 'Divider'
| 'Input'
| 'InputNumber'
| 'RadioGroup'
| 'Select'
| 'Space'
| 'Switch'
| '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: {
Checkbox: NCheckbox,
CheckboxGroup: NCheckboxGroup,
DatePicker: NDatePicker,
// 自定义默认的重置按钮
DefaultResetActionButton: (props, { attrs, slots }) => {
return h(NButton, { ...props, attrs, type: 'info' }, slots);
},
// 自定义默认的提交按钮
DefaultSubmitActionButton: (props, { attrs, slots }) => {
return h(NButton, { ...props, attrs, type: 'primary' }, slots);
},
Divider: NDivider,
Input: withDefaultPlaceholder(NInput, 'input'),
InputNumber: withDefaultPlaceholder(NInputNumber, 'input'),
RadioGroup: NRadioGroup,
Select: withDefaultPlaceholder(NSelect, 'select'),
Space: NSpace,
Switch: NSwitch,
TimePicker: NTimePicker,
TreeSelect: withDefaultPlaceholder(NTreeSelect, 'select'),
Upload: NUpload,
},
setupVbenForm<ComponentType>({
config: {
// naive-ui组件不接受onChang事件所以需要禁用
disabledOnChangeListener: true,
@@ -94,22 +24,22 @@ setupVbenForm<FormComponentType>({
defineRules: {
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;
},
selectRequired: (value, _params, ctx) => {
if (value === undefined || value === null) {
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 };

View File

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

View File

@@ -11,8 +11,15 @@ setupVbenVxeTable({
vxeUI.setConfig({
grid: {
align: 'center',
border: true,
border: false,
columnConfig: {
resizable: true,
},
minHeight: 180,
formConfig: {
// 全局禁用vxe-table的表单配置使用formOptions
enabled: false,
},
proxyConfig: {
autoLoad: true,
response: {
@@ -24,13 +31,14 @@ setupVbenVxeTable({
showResponseMsg: false,
},
round: true,
showOverflow: true,
size: 'small',
},
});
// 表格配置项可以用 cellRender: { name: 'CellImage' },
vxeUI.renderer.add('CellImage', {
renderDefault(_renderOpts, params) {
renderTableDefault(_renderOpts, params) {
const { column, row } = params;
return h(NImage, { src: row[column.field] });
},
@@ -38,7 +46,7 @@ setupVbenVxeTable({
// 表格配置项可以用 cellRender: { name: 'CellLink' },
vxeUI.renderer.add('CellLink', {
renderDefault(renderOpts) {
renderTableDefault(renderOpts) {
const { props } = renderOpts;
return h(
NButton,

View File

@@ -12,7 +12,7 @@ import {
} from '@vben/request';
import { useAccessStore } from '@vben/stores';
import { message } from '#/adapter';
import { message } from '#/adapter/naive';
import { useAuthStore } from '#/store';
import { refreshTokenApi } from './core';
@@ -73,11 +73,11 @@ function createRequestClient(baseURL: string) {
fulfilled: (response) => {
const { data: responseData, status } = response;
const { code, data, message: msg } = responseData;
const { code, data } = responseData;
if (status >= 200 && status < 400 && code === 0) {
return data;
}
throw new Error(`Error ${status}: ${msg}`);
throw Object.assign({}, response, { response });
},
});

View File

@@ -1,15 +1,21 @@
import { createApp } from 'vue';
import { createApp, watchEffect } from 'vue';
import { registerAccessDirective } from '@vben/access';
import { preferences } from '@vben/preferences';
import { initStores } from '@vben/stores';
import '@vben/styles';
import { setupI18n } from '#/locales';
import { useTitle } from '@vueuse/core';
import { $t, setupI18n } from '#/locales';
import { initComponentAdapter } from './adapter/component';
import App from './app.vue';
import { router } from './router';
async function bootstrap(namespace: string) {
// 初始化组件适配器
initComponentAdapter();
const app = createApp(App);
// 国际化 i18n 配置
@@ -24,6 +30,16 @@ async function bootstrap(namespace: string) {
// 配置路由及路由守卫
app.use(router);
// 动态更新标题
watchEffect(() => {
if (preferences.app.dynamicTitle) {
const routeTitle = router.currentRoute.value.meta?.title;
const pageTitle =
(routeTitle ? `${$t(routeTitle)} - ` : '') + preferences.app.name;
useTitle(pageTitle);
}
});
app.mount('#app');
}

View File

@@ -68,7 +68,7 @@ const menus = computed(() => [
});
},
icon: BookOpenText,
text: $t('widgets.document'),
text: $t('ui.widgets.document'),
},
{
handler: () => {
@@ -86,7 +86,7 @@ const menus = computed(() => [
});
},
icon: CircleHelp,
text: $t('widgets.qa'),
text: $t('ui.widgets.qa'),
},
]);

View File

@@ -2,12 +2,19 @@ import type { LocaleSetupOptions, SupportedLanguagesType } from '@vben/locales';
import type { App } 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';
const modules = import.meta.glob('./langs/*.json');
const modules = import.meta.glob('./langs/**/*.json');
const localesMap = loadLocalesMap(modules);
const localesMap = loadLocalesMapFromDir(
/\.\/langs\/([^/]+)\/(.*)\.json$/,
modules,
);
/**
* 加载应用特有的语言包

View File

@@ -1,9 +0,0 @@
{
"page": {
"demos": {
"title": "Demos",
"naive": "Naive UI",
"table": "Table"
}
}
}

View File

@@ -0,0 +1,13 @@
{
"title": "Demos",
"naive": "Naive UI",
"table": "Table",
"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,14 @@
{
"auth": {
"login": "Login",
"register": "Register",
"codeLogin": "Code Login",
"qrcodeLogin": "Qr Code Login",
"forgetPassword": "Forget Password"
},
"dashboard": {
"title": "Dashboard",
"analytics": "Analytics",
"workspace": "Workspace"
}
}

View File

@@ -1,9 +0,0 @@
{
"page": {
"demos": {
"title": "演示",
"naive": "Naive UI",
"table": "Table"
}
}
}

View File

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

View File

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

View File

@@ -3,6 +3,7 @@ import { defineOverridesPreferences } from '@vben/preferences';
/**
* @description 项目配置文件
* 只需要覆盖项目中的一部分配置,不需要的配置不用覆盖,会自动使用默认配置
* !!! 更改配置后请清空缓存,否则可能不生效
*/
export const overridesPreferences = defineOverridesPreferences({
// overrides

View File

@@ -6,7 +6,7 @@ import type {
import { generateAccessible } from '@vben/access';
import { preferences } from '@vben/preferences';
import { message } from '#/adapter';
import { message } from '#/adapter/naive';
import { getAllMenusApi } from '#/api';
import { BasicLayout, IFrameView } from '#/layouts';
import { $t } from '#/locales';

View File

@@ -5,10 +5,7 @@ import { preferences } from '@vben/preferences';
import { useAccessStore, useUserStore } from '@vben/stores';
import { startProgress, stopProgress } from '@vben/utils';
import { useTitle } from '@vueuse/core';
import { $t } from '#/locales';
import { coreRouteNames, dynamicRoutes } from '#/router/routes';
import { accessRoutes, coreRouteNames } from '#/router/routes';
import { useAuthStore } from '#/store';
import { generateAccess } from './access';
@@ -40,13 +37,6 @@ function setupCommonGuard(router: Router) {
if (preferences.transition.progress) {
stopProgress();
}
// 动态修改标题
if (preferences.app.dynamicTitle) {
const { title } = to.meta;
// useTitle(`${$t(title)} - ${preferences.app.name}`);
useTitle(`${$t(title)} - ${preferences.app.name}`);
}
});
}
@@ -104,7 +94,7 @@ function setupAccessGuard(router: Router) {
roles: userRoles,
router,
// 则会在菜单中显示但是访问会被重定向到403
routes: dynamicRoutes,
routes: accessRoutes,
});
// 保存菜单信息和路由信息

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,17 +32,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'),
},
},
{
@@ -50,7 +52,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'),
},
},
{
@@ -59,7 +61,7 @@ const coreRoutes: RouteRecordRaw[] = [
component: () =>
import('#/views/_core/authentication/qrcode-login.vue'),
meta: {
title: $t('page.core.qrcodeLogin'),
title: $t('page.auth.qrcodeLogin'),
},
},
{
@@ -68,7 +70,7 @@ const coreRoutes: RouteRecordRaw[] = [
component: () =>
import('#/views/_core/authentication/forget-password.vue'),
meta: {
title: $t('page.core.forgetPassword'),
title: $t('page.auth.forgetPassword'),
},
},
{
@@ -76,7 +78,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

@@ -10,15 +10,19 @@ const dynamicRouteFiles = import.meta.glob('./modules/**/*.ts', {
// 有需要可以自行打开注释,并创建文件夹
// const externalRouteFiles = import.meta.glob('./external/**/*.ts', { eager: true });
// const staticRouteFiles = import.meta.glob('./static/**/*.ts', { eager: true });
/** 动态路由 */
const dynamicRoutes: RouteRecordRaw[] = mergeRouteModules(dynamicRouteFiles);
/** 外部路由列表访问这些页面可以不需要Layout可能用于内嵌在别的系统 */
/** 外部路由列表访问这些页面可以不需要Layout可能用于内嵌在别的系统(不会显示在菜单中) */
// const externalRoutes: RouteRecordRaw[] = mergeRouteModules(externalRouteFiles);
// const staticRoutes: RouteRecordRaw[] = mergeRouteModules(staticRouteFiles);
const staticRoutes: RouteRecordRaw[] = [];
const externalRoutes: RouteRecordRaw[] = [];
/** 路由列表,由基本路由+静态路由组成 */
/** 路由列表,由基本路由、外部路由和404兜底路由组成
* 无需走权限验证(会一直显示在菜单中) */
const routes: RouteRecordRaw[] = [
...coreRoutes,
...externalRoutes,
@@ -28,4 +32,6 @@ const routes: RouteRecordRaw[] = [
/** 基本路由列表,这些路由不需要进入权限拦截 */
const coreRouteNames = traverseTreeValues(coreRoutes, (route) => route.name);
export { coreRouteNames, dynamicRoutes, routes };
/** 有权限校验的路由列表,包含动态路由和静态路由 */
const accessRoutes = [...dynamicRoutes, ...staticRoutes];
export { accessRoutes, coreRouteNames, routes };

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.naive'),
title: $t('demos.naive'),
},
name: 'NaiveDemos',
path: '/demos/naive',
@@ -25,7 +25,7 @@ const routes: RouteRecordRaw[] = [
},
{
meta: {
title: $t('page.demos.table'),
title: $t('demos.table'),
},
name: 'Table',
path: '/demos/table',

View File

@@ -19,7 +19,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',
@@ -30,7 +30,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'),
},
},
{
@@ -40,7 +40,7 @@ const routes: RouteRecordRaw[] = [
meta: {
icon: 'lucide:book-open-text',
link: VBEN_DOC_URL,
title: $t('page.vben.document'),
title: $t('demos.vben.document'),
},
},
{
@@ -61,7 +61,7 @@ const routes: RouteRecordRaw[] = [
badgeType: 'dot',
icon: SvgAntdvLogoIcon,
link: VBEN_ANT_PREVIEW_URL,
title: $t('page.vben.antdv'),
title: $t('demos.vben.antdv'),
},
},
{
@@ -72,7 +72,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

@@ -1,5 +1,4 @@
import type { LoginAndRegisterParams } from '@vben/common-ui';
import type { UserInfo } from '@vben/types';
import type { Recordable, UserInfo } from '@vben/types';
import { ref } from 'vue';
import { useRouter } from 'vue-router';
@@ -9,7 +8,7 @@ import { resetAllStores, useAccessStore, useUserStore } from '@vben/stores';
import { defineStore } from 'pinia';
import { notification } from '#/adapter';
import { notification } from '#/adapter/naive';
import { getAccessCodesApi, getUserInfoApi, loginApi, logoutApi } from '#/api';
import { $t } from '#/locales';
@@ -26,7 +25,7 @@ export const useAuthStore = defineStore('auth', () => {
* @param params 登录表单数据
*/
async function authLogin(
params: LoginAndRegisterParams,
params: Recordable<any>,
onSuccess?: () => Promise<void> | void,
) {
// 异步处理用户登录操作并获取 accessToken
@@ -85,7 +84,7 @@ export const useAuthStore = defineStore('auth', () => {
resetAllStores();
accessStore.setLoginExpired(false);
// 回登页带上当前路由地址
// 回登页带上当前路由地址
await router.replace({
path: LOGIN_PATH,
query: redirect

View File

@@ -1,5 +1,6 @@
<script lang="ts" setup>
import type { LoginCodeParams, VbenFormSchema } from '@vben/common-ui';
import type { VbenFormSchema } from '@vben/common-ui';
import type { Recordable } from '@vben/types';
import { computed, ref } from 'vue';
@@ -49,7 +50,7 @@ const formSchema = computed((): VbenFormSchema[] => {
* Asynchronously handle the login process
* @param values 登录表单数据
*/
async function handleLogin(values: LoginCodeParams) {
async function handleLogin(values: Recordable<any>) {
// eslint-disable-next-line no-console
console.log(values);
}

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>) {
// eslint-disable-next-line no-console
console.log('reset email:', value);
}

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>) {
// eslint-disable-next-line no-console
console.log('register submit:', value);
}

View File

@@ -7,6 +7,7 @@ import type {
} from '@vben/common-ui';
import { ref } from 'vue';
import { useRouter } from 'vue-router';
import {
AnalysisChartCard,
@@ -18,11 +19,15 @@ import {
} from '@vben/common-ui';
import { preferences } from '@vben/preferences';
import { useUserStore } from '@vben/stores';
import { openWindow } from '@vben/utils';
import AnalyticsVisitsSource from '../analytics/analytics-visits-source.vue';
const userStore = useUserStore();
// 这是一个示例数据,实际项目中需要根据实际情况进行调整
// url 也可以是内部路由,在 navTo 方法中识别处理,进行内部跳转
// 例如url: /dashboard/workspace
const projectItems: WorkbenchProjectItem[] = [
{
color: '',
@@ -31,6 +36,7 @@ const projectItems: WorkbenchProjectItem[] = [
group: '开源组',
icon: 'carbon:logo-github',
title: 'Github',
url: 'https://github.com',
},
{
color: '#3fb27f',
@@ -39,6 +45,7 @@ const projectItems: WorkbenchProjectItem[] = [
group: '算法组',
icon: 'ion:logo-vue',
title: 'Vue',
url: 'https://vuejs.org',
},
{
color: '#e18525',
@@ -47,6 +54,7 @@ const projectItems: WorkbenchProjectItem[] = [
group: '上班摸鱼',
icon: 'ion:logo-html5',
title: 'Html5',
url: 'https://developer.mozilla.org/zh-CN/docs/Web/HTML',
},
{
color: '#bf0c2c',
@@ -55,6 +63,7 @@ const projectItems: WorkbenchProjectItem[] = [
group: 'UI',
icon: 'ion:logo-angular',
title: 'Angular',
url: 'https://angular.io',
},
{
color: '#00d8ff',
@@ -63,6 +72,7 @@ const projectItems: WorkbenchProjectItem[] = [
group: '技术牛',
icon: 'bx:bxl-react',
title: 'React',
url: 'https://reactjs.org',
},
{
color: '#EBD94E',
@@ -71,39 +81,47 @@ const projectItems: WorkbenchProjectItem[] = [
group: '架构组',
icon: 'ion:logo-javascript',
title: 'Js',
url: 'https://developer.mozilla.org/zh-CN/docs/Web/JavaScript',
},
];
// 同样,这里的 url 也可以使用以 http 开头的外部链接
const quickNavItems: WorkbenchQuickNavItem[] = [
{
color: '#1fdaca',
icon: 'ion:home-outline',
title: '首页',
url: '/',
},
{
color: '#bf0c2c',
icon: 'ion:grid-outline',
title: '仪表盘',
url: '/dashboard',
},
{
color: '#e18525',
icon: 'ion:layers-outline',
title: '组件',
url: '/demos/features/icons',
},
{
color: '#3fb27f',
icon: 'ion:settings-outline',
title: '系统管理',
url: '/demos/features/login-expired', // 这里的 URL 是示例,实际项目中需要根据实际情况进行调整
},
{
color: '#4daf1bc9',
icon: 'ion:key-outline',
title: '权限管理',
url: '/demos/access/page-control',
},
{
color: '#00d8ff',
icon: 'ion:bar-chart-outline',
title: '图表',
url: '/analytics',
},
];
@@ -195,6 +213,24 @@ const trendItems: WorkbenchTrendItem[] = [
title: 'Vben',
},
];
const router = useRouter();
// 这是一个示例方法,实际项目中需要根据实际情况进行调整
// This is a sample method, adjust according to the actual project requirements
function navTo(nav: WorkbenchProjectItem | WorkbenchQuickNavItem) {
if (nav.url?.startsWith('http')) {
openWindow(nav.url);
return;
}
if (nav.url?.startsWith('/')) {
router.push(nav.url).catch((error) => {
console.error('Navigation failed:', error);
});
} else {
console.warn(`Unknown URL for navigation item: ${nav.title} -> ${nav.url}`);
}
}
</script>
<template>
@@ -210,7 +246,7 @@ const trendItems: WorkbenchTrendItem[] = [
<div class="mt-5 flex flex-col lg:flex-row">
<div class="mr-4 w-full lg:w-3/5">
<WorkbenchProject :items="projectItems" title="项目" />
<WorkbenchProject :items="projectItems" title="项目" @click="navTo" />
<WorkbenchTrends :items="trendItems" class="mt-5" title="最新动态" />
</div>
<div class="w-full lg:w-2/5">
@@ -218,6 +254,7 @@ const trendItems: WorkbenchTrendItem[] = [
:items="quickNavItems"
class="mt-5 lg:mt-0"
title="快捷导航"
@click="navTo"
/>
<WorkbenchTodo :items="todoItems" class="mt-5" title="待办事项" />
<AnalysisChartCard class="mt-5" title="访问来源">

View File

@@ -4,53 +4,55 @@
"language": "en,en-US",
"allowCompoundWords": true,
"words": [
"clsx",
"esno",
"demi",
"unref",
"taze",
"acmr",
"antd",
"lucide",
"antdv",
"astro",
"brotli",
"clsx",
"defu",
"demi",
"echarts",
"ependencies",
"esno",
"etag",
"execa",
"iconify",
"iconoir",
"intlify",
"lockb",
"lucide",
"minh",
"minw",
"mkdist",
"mockjs",
"vitejs",
"naiveui",
"nocheck",
"noopener",
"noreferrer",
"nprogress",
"nuxt",
"pinia",
"prefixs",
"publint",
"qrcode",
"shadcn",
"sonner",
"sortablejs",
"styl",
"taze",
"ui-kit",
"uicons",
"unplugin",
"unref",
"vben",
"vbenjs",
"vueuse",
"yxxx",
"nuxt",
"lockb",
"astro",
"ui-kit",
"styl",
"vnode",
"nocheck",
"prefixs",
"vitepress",
"antdv",
"ependencies",
"vite",
"echarts",
"sortablejs",
"etag",
"naiveui",
"uicons",
"iconoir"
"vitejs",
"vitepress",
"vnode",
"vueuse",
"yxxx"
],
"ignorePaths": [
"**/node_modules/**",

View File

@@ -221,9 +221,9 @@ function nav(): DefaultTheme.NavItem[] {
link: '/commercial/community',
text: '👨‍👦‍👦 Community',
},
{
link: '/friend-links/',
text: '🤝 Friend Links',
},
// {
// link: '/friend-links/',
// text: '🤝 Friend Links',
// },
];
}

View File

@@ -3,7 +3,10 @@ import type { HeadConfig } from 'vitepress';
import { resolve } from 'node:path';
import { viteArchiverPlugin } from '@vben/vite-config';
import {
viteArchiverPlugin,
viteVxeTableImportsPlugin,
} from '@vben/vite-config';
import {
GitChangelog,
@@ -59,6 +62,11 @@ export const shared = defineConfig({
postcssIsolateStyles({ includeFiles: [/vp-doc\.css/] }),
],
},
preprocessorOptions: {
scss: {
api: 'modern',
},
},
},
json: {
stringify: true,
@@ -85,6 +93,7 @@ export const shared = defineConfig({
GitChangelogMarkdownSection(),
viteArchiverPlugin({ outputDir: '.vitepress' }),
groupIconVitePlugin(),
await viteVxeTableImportsPlugin(),
],
server: {
fs: {
@@ -93,6 +102,7 @@ export const shared = defineConfig({
host: true,
port: 6173,
},
ssr: {
external: ['@vue/repl'],
},
@@ -156,6 +166,7 @@ function pwa(): PwaOptions {
registerType: 'autoUpdate',
workbox: {
globPatterns: ['**/*.{css,js,html,svg,png,ico,txt,woff2}'],
maximumFileSizeToCacheInBytes: 5 * 1024 * 1024,
},
};
}

View File

@@ -124,7 +124,7 @@ function sidebarCommercial(): DefaultTheme.SidebarItem[] {
return [
{
link: 'community',
text: '社区',
text: '交流群',
},
{
link: 'technical-support',
@@ -164,6 +164,10 @@ function sidebarComponents(): DefaultTheme.SidebarItem[] {
link: 'common-ui/vben-form',
text: 'Form 表单',
},
{
link: 'common-ui/vben-vxe-table',
text: 'Vxe Table 表格',
},
{
link: 'common-ui/vben-count-to-animator',
text: 'CountToAnimator 数字动画',
@@ -262,7 +266,7 @@ function nav(): DefaultTheme.NavItem[] {
},
{
link: '/commercial/community',
text: '👨‍👦‍👦 社区',
text: '👨‍👦‍👦 交流群',
// items: [
// {
// link: 'https://qun.qq.com/qqweb/qunpro/share?_wv=3&_wwv=128&appChannel=share&inviteCode=22ySzj7pKiw&businessType=9&from=246610&biz=ka&mainSourceId=share&subSourceId=others&jumpsource=shorturl#/pc',
@@ -278,10 +282,10 @@ function nav(): DefaultTheme.NavItem[] {
// },
// ],
},
{
link: '/friend-links/',
text: '🤝 友情链接',
},
// {
// link: '/friend-links/',
// text: '🤝 友情链接',
// },
];
}

View File

@@ -15,11 +15,12 @@ import 'virtual:group-icons.css';
import '@nolebase/vitepress-plugin-git-changelog/client/style.css';
export default {
enhanceApp(ctx: EnhanceAppContext) {
async enhanceApp(ctx: EnhanceAppContext) {
const { app } = ctx;
app.component('VbenContributors', VbenContributors);
app.component('DemoPreview', DemoPreview);
app.use(NolebaseGitChangelogPlugin);
// 百度统计
initHmPlugin();
},

View File

@@ -1,3 +1,4 @@
import '@vben/styles';
import './variables.css';
import './base.css';
import '@vben/styles';

View File

@@ -1,6 +1,6 @@
{
"name": "@vben/docs",
"version": "5.4.0-beta.1",
"version": "5.4.8",
"private": true,
"scripts": {
"build": "vitepress build",
@@ -8,12 +8,16 @@
"docs:preview": "vitepress preview"
},
"imports": {
"#/*": "./src/_env/*"
"#/*": {
"node": "./src/_env/node/*",
"default": "./src/_env/*"
}
},
"dependencies": {
"@vben-core/shadcn-ui": "workspace:*",
"@vben/common-ui": "workspace:*",
"@vben/locales": "workspace:*",
"@vben/plugins": "workspace:*",
"@vben/styles": "workspace:*",
"ant-design-vue": "catalog:",
"lucide-vue-next": "catalog:",

View File

@@ -0,0 +1,127 @@
/**
* 通用组件共同的使用的基础组件,原先放在 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';
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'
| 'Input'
| 'InputNumber'
| 'InputPassword'
| 'Mentions'
| 'PrimaryButton'
| 'Radio'
| 'RadioGroup'
| 'RangePicker'
| 'Rate'
| '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,
};
// 将组件注册到全局共享状态中
globalShareState.setComponents(components);
// 定义全局共享状态中的消息提示
globalShareState.defineMessage({
// 复制成功消息提示
copyPreferencesSuccess: (title, content) => {
notification.success({
description: content,
message: title,
placement: 'bottomRight',
});
},
});
}
export { initComponentAdapter };

View File

@@ -1,99 +1,23 @@
import type {
BaseFormComponentType,
VbenFormSchema as FormSchema,
VbenFormProps,
} from '@vben/common-ui';
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 { initComponentAdapter } from './component';
// 这里需要自行根据业务组件库进行适配,需要用到的组件都需要在这里类型说明
export type FormComponentType =
| 'AutoComplete'
| 'Checkbox'
| 'CheckboxGroup'
| 'DatePicker'
| 'Divider'
| 'Input'
| 'InputNumber'
| 'InputPassword'
| 'Mentions'
| 'Radio'
| 'RadioGroup'
| 'RangePicker'
| 'Rate'
| 'Select'
| 'Space'
| 'Switch'
| 'Textarea'
| 'TimePicker'
| 'TreeSelect'
| 'Upload'
| BaseFormComponentType;
// 初始化表单组件并注册到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,
InputNumber,
InputPassword,
Mentions,
Radio,
RadioGroup,
RangePicker,
Rate,
Select,
Space,
Switch,
Textarea,
TimePicker,
TreeSelect,
Upload,
},
initComponentAdapter();
setupVbenForm<ComponentType>({
config: {
// ant design vue组件库默认都是 v-model:value
baseModelPropName: 'value',
// 一些组件是 v-model:checked 或者 v-model:fileList
// naive-ui组件不接受onChang事件所以需要禁用
disabledOnChangeListener: true,
// naive-ui组件的空值为null,不能是undefined否则重置表单时不生效
emptyStateValue: null,
modelPropNameMap: {
Checkbox: 'checked',
Radio: 'checked',
@@ -102,26 +26,24 @@ setupVbenForm<FormComponentType>({
},
},
defineRules: {
// 输入项目必填国际化适配
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;
},
// 选择项目必填国际化适配
selectRequired: (value, _params, ctx) => {
if (value === undefined || value === null) {
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 };

View File

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

View File

@@ -0,0 +1,70 @@
import { h } from 'vue';
import { setupVbenVxeTable, useVbenVxeGrid } from '@vben/plugins/vxe-table';
import { Button, Image } from 'ant-design-vue';
import { useVbenForm } from './form';
if (!import.meta.env.SSR) {
setupVbenVxeTable({
configVxeTable: (vxeUI) => {
vxeUI.setConfig({
grid: {
align: 'center',
border: false,
columnConfig: {
resizable: true,
},
formConfig: {
// 全局禁用vxe-table的表单配置使用formOptions
enabled: false,
},
minHeight: 180,
proxyConfig: {
autoLoad: true,
response: {
result: 'items',
total: 'total',
list: 'items',
},
showActiveMsg: true,
showResponseMsg: false,
},
round: true,
showOverflow: true,
size: 'small',
},
});
// 表格配置项可以用 cellRender: { name: 'CellImage' },
vxeUI.renderer.add('CellImage', {
renderTableDefault(_renderOpts, params) {
const { column, row } = params;
return h(Image, { src: row[column.field] });
},
});
// 表格配置项可以用 cellRender: { name: 'CellLink' },
vxeUI.renderer.add('CellLink', {
renderTableDefault(renderOpts) {
const { props } = renderOpts;
return h(
Button,
{ size: 'small', type: 'link' },
{ default: () => props?.text },
);
},
});
// 这里可以自行扩展 vxe-table 的全局配置,比如自定义格式化
// vxeUI.formats.add
},
useVbenForm,
});
}
export { useVbenVxeGrid };
export type * from '@vben/plugins/vxe-table';

View File

@@ -0,0 +1,4 @@
export const useVbenForm = () => {};
export const z = {};
export type VbenFormSchema = any;
export type VbenFormProps = any;

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