Compare commits

...

29 Commits

Author SHA1 Message Date
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
272 changed files with 4607 additions and 4658 deletions

View File

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

View File

@@ -197,11 +197,14 @@
"playground/src/locales/langs", "playground/src/locales/langs",
"apps/*/src/locales/langs" "apps/*/src/locales/langs"
], ],
"i18n-ally.pathMatcher": "{locale}.json", "i18n-ally.pathMatcher": "{locale}/{namespace}.{ext}",
"i18n-ally.enabledParsers": ["json", "ts", "js", "yaml"], "i18n-ally.enabledParsers": ["json"],
"i18n-ally.sourceLanguage": "en", "i18n-ally.sourceLanguage": "en",
"i18n-ally.displayLanguage": "zh-CN", "i18n-ally.displayLanguage": "zh-CN",
"i18n-ally.enabledFrameworks": ["vue", "react"], "i18n-ally.enabledFrameworks": ["vue", "react"],
"i18n-ally.keystyle": "nested",
"i18n-ally.sortKeys": true,
"i18n-ally.namespace": true,
// 控制相关文件嵌套展示 // 控制相关文件嵌套展示
"explorer.fileNesting.enabled": true, "explorer.fileNesting.enabled": true,
@@ -216,7 +219,6 @@
"tailwind.config.mjs": "postcss.*" "tailwind.config.mjs": "postcss.*"
}, },
"commentTranslate.hover.enabled": false, "commentTranslate.hover.enabled": false,
"i18n-ally.keystyle": "nested",
"commentTranslate.multiLineMerge": true, "commentTranslate.multiLineMerge": true,
"vue.server.hybridMode": true, "vue.server.hybridMode": true,
"typescript.tsdk": "node_modules/typescript/lib" "typescript.tsdk": "node_modules/typescript/lib"

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) ![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 ## Contributor

View File

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

View File

@@ -86,7 +86,7 @@ const createDemosMenus = (role: 'admin' | 'super' | 'user') => {
component: '/demos/access/admin-visible', component: '/demos/access/admin-visible',
meta: { meta: {
icon: 'mdi:button-cursor', icon: 'mdi:button-cursor',
title: 'page.demos.access.adminVisible', title: 'demos.access.adminVisible',
}, },
name: 'AccessAdminVisibleDemo', name: 'AccessAdminVisibleDemo',
path: '/demos/access/admin-visible', path: '/demos/access/admin-visible',
@@ -95,7 +95,7 @@ const createDemosMenus = (role: 'admin' | 'super' | 'user') => {
component: '/demos/access/super-visible', component: '/demos/access/super-visible',
meta: { meta: {
icon: 'mdi:button-cursor', icon: 'mdi:button-cursor',
title: 'page.demos.access.superVisible', title: 'demos.access.superVisible',
}, },
name: 'AccessSuperVisibleDemo', name: 'AccessSuperVisibleDemo',
path: '/demos/access/super-visible', path: '/demos/access/super-visible',
@@ -104,7 +104,7 @@ const createDemosMenus = (role: 'admin' | 'super' | 'user') => {
component: '/demos/access/user-visible', component: '/demos/access/user-visible',
meta: { meta: {
icon: 'mdi:button-cursor', icon: 'mdi:button-cursor',
title: 'page.demos.access.userVisible', title: 'demos.access.userVisible',
}, },
name: 'AccessUserVisibleDemo', name: 'AccessUserVisibleDemo',
path: '/demos/access/user-visible', path: '/demos/access/user-visible',
@@ -118,7 +118,7 @@ const createDemosMenus = (role: 'admin' | 'super' | 'user') => {
icon: 'ic:baseline-view-in-ar', icon: 'ic:baseline-view-in-ar',
keepAlive: true, keepAlive: true,
order: 1000, order: 1000,
title: 'page.demos.title', title: 'demos.title',
}, },
name: 'Demos', name: 'Demos',
path: '/demos', path: '/demos',
@@ -129,7 +129,7 @@ const createDemosMenus = (role: 'admin' | 'super' | 'user') => {
path: '/demosaccess', path: '/demosaccess',
meta: { meta: {
icon: 'mdi:cloud-key-outline', icon: 'mdi:cloud-key-outline',
title: 'page.demos.access.backendPermissions', title: 'demos.access.backendPermissions',
}, },
redirect: '/demos/access/page-control', redirect: '/demos/access/page-control',
children: [ children: [
@@ -139,7 +139,7 @@ const createDemosMenus = (role: 'admin' | 'super' | 'user') => {
component: '/demos/access/index', component: '/demos/access/index',
meta: { meta: {
icon: 'mdi:page-previous-outline', 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', component: '/demos/access/button-control',
meta: { meta: {
icon: 'mdi:button-cursor', 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'], authority: ['no-body'],
icon: 'mdi:button-cursor', icon: 'mdi:button-cursor',
menuVisibleWithForbidden: true, menuVisibleWithForbidden: true,
title: 'page.demos.access.menuVisible403', title: 'demos.access.menuVisible403',
}, },
}, },
roleWithMenus[role], 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); setResponseStatus(event, 403);
return useResponseError('Forbidden Exception', 'Forbidden Exception'); return useResponseError(message, message);
} }
export function unAuthorizedResponse(event: H3Event<EventHandlerRequest>) { export function unAuthorizedResponse(event: H3Event<EventHandlerRequest>) {

View File

@@ -1,6 +1,6 @@
{ {
"name": "@vben/web-antd", "name": "@vben/web-antd",
"version": "5.4.0-beta.1", "version": "5.4.2",
"homepage": "https://vben.pro", "homepage": "https://vben.pro",
"bugs": "https://github.com/vbenjs/vue-vben-admin/issues", "bugs": "https://github.com/vbenjs/vue-vben-admin/issues",
"repository": { "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 { import type {
BaseFormComponentType,
VbenFormSchema as FormSchema, VbenFormSchema as FormSchema,
VbenFormProps, VbenFormProps,
} from '@vben/common-ui'; } from '@vben/common-ui';
import type { Component, SetupContext } from 'vue'; import type { ComponentType } from './component';
import { h } from 'vue';
import { setupVbenForm, useVbenForm as useForm, z } from '@vben/common-ui'; import { setupVbenForm, useVbenForm as useForm, z } from '@vben/common-ui';
import { $t } from '@vben/locales'; import { $t } from '@vben/locales';
import { setupVbenForm<ComponentType>({
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,
},
config: { config: {
// ant design vue组件库默认都是 v-model:value // ant design vue组件库默认都是 v-model:value
baseModelPropName: 'value', baseModelPropName: 'value',
@@ -116,23 +25,23 @@ setupVbenForm<FormComponentType>({
// 输入项目必填国际化适配 // 输入项目必填国际化适配
required: (value, _params, ctx) => { required: (value, _params, ctx) => {
if (value === undefined || value === null || value.length === 0) { if (value === undefined || value === null || value.length === 0) {
return $t('formRules.required', [ctx.label]); return $t('ui.formRules.required', [ctx.label]);
} }
return true; return true;
}, },
// 选择项目必填国际化适配 // 选择项目必填国际化适配
selectRequired: (value, _params, ctx) => { selectRequired: (value, _params, ctx) => {
if (value === undefined || value === null) { if (value === undefined || value === null) {
return $t('formRules.selectRequired', [ctx.label]); return $t('ui.formRules.selectRequired', [ctx.label]);
} }
return true; return true;
}, },
}, },
}); });
const useVbenForm = useForm<FormComponentType>; const useVbenForm = useForm<ComponentType>;
export { useVbenForm, z }; export { useVbenForm, z };
export type VbenFormSchema = FormSchema<FormComponentType>; export type VbenFormSchema = FormSchema<ComponentType>;
export type { VbenFormProps }; export type { VbenFormProps };

View File

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

View File

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

View File

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

View File

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

@@ -32,6 +32,7 @@ const coreRoutes: RouteRecordRaw[] = [
{ {
component: AuthPageLayout, component: AuthPageLayout,
meta: { meta: {
hideInTab: true,
title: 'Authentication', title: 'Authentication',
}, },
name: 'Authentication', name: 'Authentication',
@@ -42,7 +43,7 @@ const coreRoutes: RouteRecordRaw[] = [
path: 'login', path: 'login',
component: Login, component: Login,
meta: { meta: {
title: $t('page.core.login'), title: $t('page.auth.login'),
}, },
}, },
{ {
@@ -50,7 +51,7 @@ const coreRoutes: RouteRecordRaw[] = [
path: 'code-login', path: 'code-login',
component: () => import('#/views/_core/authentication/code-login.vue'), component: () => import('#/views/_core/authentication/code-login.vue'),
meta: { meta: {
title: $t('page.core.codeLogin'), title: $t('page.auth.codeLogin'),
}, },
}, },
{ {
@@ -59,7 +60,7 @@ const coreRoutes: RouteRecordRaw[] = [
component: () => component: () =>
import('#/views/_core/authentication/qrcode-login.vue'), import('#/views/_core/authentication/qrcode-login.vue'),
meta: { meta: {
title: $t('page.core.qrcodeLogin'), title: $t('page.auth.qrcodeLogin'),
}, },
}, },
{ {
@@ -68,7 +69,7 @@ const coreRoutes: RouteRecordRaw[] = [
component: () => component: () =>
import('#/views/_core/authentication/forget-password.vue'), import('#/views/_core/authentication/forget-password.vue'),
meta: { meta: {
title: $t('page.core.forgetPassword'), title: $t('page.auth.forgetPassword'),
}, },
}, },
{ {
@@ -76,7 +77,7 @@ const coreRoutes: RouteRecordRaw[] = [
path: 'register', path: 'register',
component: () => import('#/views/_core/authentication/register.vue'), component: () => import('#/views/_core/authentication/register.vue'),
meta: { meta: {
title: $t('page.core.register'), title: $t('page.auth.register'),
}, },
}, },
], ],

View File

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

View File

@@ -18,7 +18,7 @@ const routes: RouteRecordRaw[] = [
badgeType: 'dot', badgeType: 'dot',
icon: VBEN_LOGO_URL, icon: VBEN_LOGO_URL,
order: 9999, order: 9999,
title: $t('page.vben.title'), title: $t('demos.vben.title'),
}, },
name: 'VbenProject', name: 'VbenProject',
path: '/vben-admin', path: '/vben-admin',
@@ -29,7 +29,7 @@ const routes: RouteRecordRaw[] = [
component: () => import('#/views/_core/about/index.vue'), component: () => import('#/views/_core/about/index.vue'),
meta: { meta: {
icon: 'lucide:copyright', icon: 'lucide:copyright',
title: $t('page.vben.about'), title: $t('demos.vben.about'),
}, },
}, },
{ {
@@ -39,7 +39,7 @@ const routes: RouteRecordRaw[] = [
meta: { meta: {
icon: 'lucide:book-open-text', icon: 'lucide:book-open-text',
link: VBEN_DOC_URL, link: VBEN_DOC_URL,
title: $t('page.vben.document'), title: $t('demos.vben.document'),
}, },
}, },
{ {
@@ -60,7 +60,7 @@ const routes: RouteRecordRaw[] = [
badgeType: 'dot', badgeType: 'dot',
icon: 'logos:naiveui', icon: 'logos:naiveui',
link: VBEN_NAIVE_PREVIEW_URL, 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', badgeType: 'dot',
icon: 'logos:element', icon: 'logos:element',
link: VBEN_ELE_PREVIEW_URL, 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 { Recordable, UserInfo } from '@vben/types';
import type { UserInfo } from '@vben/types';
import { ref } from 'vue'; import { ref } from 'vue';
import { useRouter } from 'vue-router'; import { useRouter } from 'vue-router';
@@ -26,7 +25,7 @@ export const useAuthStore = defineStore('auth', () => {
* @param params 登录表单数据 * @param params 登录表单数据
*/ */
async function authLogin( async function authLogin(
params: LoginAndRegisterParams, params: Recordable<any>,
onSuccess?: () => Promise<void> | void, onSuccess?: () => Promise<void> | void,
) { ) {
// 异步处理用户登录操作并获取 accessToken // 异步处理用户登录操作并获取 accessToken
@@ -84,7 +83,7 @@ export const useAuthStore = defineStore('auth', () => {
resetAllStores(); resetAllStores();
accessStore.setLoginExpired(false); accessStore.setLoginExpired(false);
// 回登页带上当前路由地址 // 回登页带上当前路由地址
await router.replace({ await router.replace({
path: LOGIN_PATH, path: LOGIN_PATH,
query: redirect query: redirect

View File

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

View File

@@ -1,5 +1,6 @@
<script lang="ts" setup> <script lang="ts" setup>
import type { VbenFormSchema } from '@vben/common-ui'; import type { VbenFormSchema } from '@vben/common-ui';
import type { Recordable } from '@vben/types';
import { computed, ref } from 'vue'; 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 // eslint-disable-next-line no-console
console.log('reset email:', value); console.log('reset email:', value);
} }

View File

@@ -1,5 +1,6 @@
<script lang="ts" setup> <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'; import { computed, h, ref } from 'vue';
@@ -45,7 +46,7 @@ const formSchema = computed((): VbenFormSchema[] => {
rules(values) { rules(values) {
const { password } = values; const { password } = values;
return z return z
.string() .string({ required_error: $t('authentication.passwordTip') })
.min(1, { message: $t('authentication.passwordTip') }) .min(1, { message: $t('authentication.passwordTip') })
.refine((value) => value === password, { .refine((value) => value === password, {
message: $t('authentication.confirmPasswordTip'), message: $t('authentication.confirmPasswordTip'),
@@ -55,7 +56,6 @@ const formSchema = computed((): VbenFormSchema[] => {
}, },
fieldName: 'confirmPassword', fieldName: 'confirmPassword',
label: $t('authentication.confirmPassword'), label: $t('authentication.confirmPassword'),
rules: z.string().min(1, { message: $t('authentication.passwordTip') }),
}, },
{ {
component: 'VbenCheckbox', component: 'VbenCheckbox',
@@ -67,15 +67,10 @@ const formSchema = computed((): VbenFormSchema[] => {
h( h(
'a', 'a',
{ {
class: class: 'vben-link ml-1 ',
'cursor-pointer text-primary ml-1 hover:text-primary-hover',
href: '', 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 // eslint-disable-next-line no-console
console.log('register submit:', value); console.log('register submit:', value);
} }

View File

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

View File

@@ -0,0 +1,104 @@
/**
* 通用组件共同的使用的基础组件,原先放在 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,
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,
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 { import type {
BaseFormComponentType,
VbenFormSchema as FormSchema, VbenFormSchema as FormSchema,
VbenFormProps, VbenFormProps,
} from '@vben/common-ui'; } from '@vben/common-ui';
import type { Component, SetupContext } from 'vue'; import type { ComponentType } from './component';
import { h } from 'vue';
import { setupVbenForm, useVbenForm as useForm, z } from '@vben/common-ui'; import { setupVbenForm, useVbenForm as useForm, z } from '@vben/common-ui';
import { $t } from '@vben/locales'; import { $t } from '@vben/locales';
import { setupVbenForm<ComponentType>({
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,
},
config: { config: {
modelPropNameMap: { modelPropNameMap: {
Upload: 'fileList', Upload: 'fileList',
@@ -85,22 +17,22 @@ setupVbenForm<FormComponentType>({
defineRules: { defineRules: {
required: (value, _params, ctx) => { required: (value, _params, ctx) => {
if (value === undefined || value === null || value.length === 0) { if (value === undefined || value === null || value.length === 0) {
return $t('formRules.required', [ctx.label]); return $t('ui.formRules.required', [ctx.label]);
} }
return true; return true;
}, },
selectRequired: (value, _params, ctx) => { selectRequired: (value, _params, ctx) => {
if (value === undefined || value === null) { if (value === undefined || value === null) {
return $t('formRules.selectRequired', [ctx.label]); return $t('ui.formRules.selectRequired', [ctx.label]);
} }
return true; return true;
}, },
}, },
}); });
const useVbenForm = useForm<FormComponentType>; const useVbenForm = useForm<ComponentType>;
export { useVbenForm, z }; export { useVbenForm, z };
export type VbenFormSchema = FormSchema<FormComponentType>; export type VbenFormSchema = FormSchema<ComponentType>;
export type { VbenFormProps }; export type { VbenFormProps };

View File

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

View File

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

View File

@@ -7,10 +7,13 @@ import '@vben/styles/ele';
import { setupI18n } from '#/locales'; import { setupI18n } from '#/locales';
import { initComponentAdapter } from './adapter/component';
import App from './app.vue'; import App from './app.vue';
import { router } from './router'; import { router } from './router';
async function bootstrap(namespace: string) { async function bootstrap(namespace: string) {
// 初始化组件适配器
await initComponentAdapter();
const app = createApp(App); const app = createApp(App);
// 国际化 i18n 配置 // 国际化 i18n 配置

View File

@@ -68,7 +68,7 @@ const menus = computed(() => [
}); });
}, },
icon: BookOpenText, icon: BookOpenText,
text: $t('widgets.document'), text: $t('ui.widgets.document'),
}, },
{ {
handler: () => { handler: () => {
@@ -86,7 +86,7 @@ const menus = computed(() => [
}); });
}, },
icon: CircleHelp, 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 type { App } from 'vue';
import { ref } 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 { preferences } from '@vben/preferences';
import dayjs from 'dayjs'; import dayjs from 'dayjs';
@@ -13,10 +17,12 @@ import defaultLocale from 'element-plus/es/locale/lang/zh-cn';
const elementLocale = ref<Language>(defaultLocale); const elementLocale = ref<Language>(defaultLocale);
const modules = import.meta.glob('./langs/*.json'); const modules = import.meta.glob('./langs/**/*.json');
const localesMap = loadLocalesMap(modules);
const localesMap = loadLocalesMapFromDir(
/\.\/langs\/([^/]+)\/(.*)\.json$/,
modules,
);
/** /**
* 加载应用特有的语言包 * 加载应用特有的语言包
* 这里也可以改造为从服务端获取翻译数据 * 这里也可以改造为从服务端获取翻译数据
@@ -45,14 +51,14 @@ async function loadThirdPartyMessage(lang: SupportedLanguagesType) {
async function loadDayjsLocale(lang: SupportedLanguagesType) { async function loadDayjsLocale(lang: SupportedLanguagesType) {
let locale; let locale;
switch (lang) { switch (lang) {
case 'zh-CN': {
locale = await import('dayjs/locale/zh-cn');
break;
}
case 'en-US': { case 'en-US': {
locale = await import('dayjs/locale/en'); locale = await import('dayjs/locale/en');
break; break;
} }
case 'zh-CN': {
locale = await import('dayjs/locale/zh-cn');
break;
}
// 默认使用英语 // 默认使用英语
default: { default: {
locale = await import('dayjs/locale/en'); locale = await import('dayjs/locale/en');
@@ -71,14 +77,14 @@ async function loadDayjsLocale(lang: SupportedLanguagesType) {
*/ */
async function loadElementLocale(lang: SupportedLanguagesType) { async function loadElementLocale(lang: SupportedLanguagesType) {
switch (lang) { switch (lang) {
case 'zh-CN': {
elementLocale.value = defaultLocale;
break;
}
case 'en-US': { case 'en-US': {
elementLocale.value = enLocale; elementLocale.value = enLocale;
break; 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

@@ -32,6 +32,7 @@ const coreRoutes: RouteRecordRaw[] = [
{ {
component: AuthPageLayout, component: AuthPageLayout,
meta: { meta: {
hideInTab: true,
title: 'Authentication', title: 'Authentication',
}, },
name: 'Authentication', name: 'Authentication',
@@ -42,7 +43,7 @@ const coreRoutes: RouteRecordRaw[] = [
path: 'login', path: 'login',
component: Login, component: Login,
meta: { meta: {
title: $t('page.core.login'), title: $t('page.auth.login'),
}, },
}, },
{ {
@@ -50,7 +51,7 @@ const coreRoutes: RouteRecordRaw[] = [
path: 'code-login', path: 'code-login',
component: () => import('#/views/_core/authentication/code-login.vue'), component: () => import('#/views/_core/authentication/code-login.vue'),
meta: { meta: {
title: $t('page.core.codeLogin'), title: $t('page.auth.codeLogin'),
}, },
}, },
{ {
@@ -59,7 +60,7 @@ const coreRoutes: RouteRecordRaw[] = [
component: () => component: () =>
import('#/views/_core/authentication/qrcode-login.vue'), import('#/views/_core/authentication/qrcode-login.vue'),
meta: { meta: {
title: $t('page.core.qrcodeLogin'), title: $t('page.auth.qrcodeLogin'),
}, },
}, },
{ {
@@ -68,7 +69,7 @@ const coreRoutes: RouteRecordRaw[] = [
component: () => component: () =>
import('#/views/_core/authentication/forget-password.vue'), import('#/views/_core/authentication/forget-password.vue'),
meta: { meta: {
title: $t('page.core.forgetPassword'), title: $t('page.auth.forgetPassword'),
}, },
}, },
{ {
@@ -76,7 +77,7 @@ const coreRoutes: RouteRecordRaw[] = [
path: 'register', path: 'register',
component: () => import('#/views/_core/authentication/register.vue'), component: () => import('#/views/_core/authentication/register.vue'),
meta: { meta: {
title: $t('page.core.register'), title: $t('page.auth.register'),
}, },
}, },
], ],

View File

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

View File

@@ -19,7 +19,7 @@ const routes: RouteRecordRaw[] = [
badgeType: 'dot', badgeType: 'dot',
icon: VBEN_LOGO_URL, icon: VBEN_LOGO_URL,
order: 9999, order: 9999,
title: $t('page.vben.title'), title: $t('demos.vben.title'),
}, },
name: 'VbenProject', name: 'VbenProject',
path: '/vben-admin', path: '/vben-admin',
@@ -30,7 +30,7 @@ const routes: RouteRecordRaw[] = [
component: () => import('#/views/_core/about/index.vue'), component: () => import('#/views/_core/about/index.vue'),
meta: { meta: {
icon: 'lucide:copyright', icon: 'lucide:copyright',
title: $t('page.vben.about'), title: $t('demos.vben.about'),
}, },
}, },
{ {
@@ -40,7 +40,7 @@ const routes: RouteRecordRaw[] = [
meta: { meta: {
icon: 'lucide:book-open-text', icon: 'lucide:book-open-text',
link: VBEN_DOC_URL, link: VBEN_DOC_URL,
title: $t('page.vben.document'), title: $t('demos.vben.document'),
}, },
}, },
{ {
@@ -61,7 +61,7 @@ const routes: RouteRecordRaw[] = [
badgeType: 'dot', badgeType: 'dot',
icon: 'logos:naiveui', icon: 'logos:naiveui',
link: VBEN_NAIVE_PREVIEW_URL, 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', badgeType: 'dot',
icon: SvgAntdvLogoIcon, icon: SvgAntdvLogoIcon,
link: VBEN_ANT_PREVIEW_URL, 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 { Recordable, UserInfo } from '@vben/types';
import type { UserInfo } from '@vben/types';
import { ref } from 'vue'; import { ref } from 'vue';
import { useRouter } from 'vue-router'; import { useRouter } from 'vue-router';
@@ -26,7 +25,7 @@ export const useAuthStore = defineStore('auth', () => {
* @param params 登录表单数据 * @param params 登录表单数据
*/ */
async function authLogin( async function authLogin(
params: LoginAndRegisterParams, params: Recordable<any>,
onSuccess?: () => Promise<void> | void, onSuccess?: () => Promise<void> | void,
) { ) {
// 异步处理用户登录操作并获取 accessToken // 异步处理用户登录操作并获取 accessToken
@@ -85,7 +84,7 @@ export const useAuthStore = defineStore('auth', () => {
resetAllStores(); resetAllStores();
accessStore.setLoginExpired(false); accessStore.setLoginExpired(false);
// 回登页带上当前路由地址 // 回登页带上当前路由地址
await router.replace({ await router.replace({
path: LOGIN_PATH, path: LOGIN_PATH,
query: redirect query: redirect

View File

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

View File

@@ -1,5 +1,6 @@
<script lang="ts" setup> <script lang="ts" setup>
import type { VbenFormSchema } from '@vben/common-ui'; import type { VbenFormSchema } from '@vben/common-ui';
import type { Recordable } from '@vben/types';
import { computed, ref } from 'vue'; 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 // eslint-disable-next-line no-console
console.log('reset email:', value); console.log('reset email:', value);
} }

View File

@@ -1,5 +1,6 @@
<script lang="ts" setup> <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'; import { computed, h, ref } from 'vue';
@@ -45,7 +46,7 @@ const formSchema = computed((): VbenFormSchema[] => {
rules(values) { rules(values) {
const { password } = values; const { password } = values;
return z return z
.string() .string({ required_error: $t('authentication.passwordTip') })
.min(1, { message: $t('authentication.passwordTip') }) .min(1, { message: $t('authentication.passwordTip') })
.refine((value) => value === password, { .refine((value) => value === password, {
message: $t('authentication.confirmPasswordTip'), message: $t('authentication.confirmPasswordTip'),
@@ -55,7 +56,6 @@ const formSchema = computed((): VbenFormSchema[] => {
}, },
fieldName: 'confirmPassword', fieldName: 'confirmPassword',
label: $t('authentication.confirmPassword'), label: $t('authentication.confirmPassword'),
rules: z.string().min(1, { message: $t('authentication.passwordTip') }),
}, },
{ {
component: 'VbenCheckbox', component: 'VbenCheckbox',
@@ -67,15 +67,10 @@ const formSchema = computed((): VbenFormSchema[] => {
h( h(
'a', 'a',
{ {
class: class: 'vben-link ml-1 ',
'cursor-pointer text-primary ml-1 hover:text-primary-hover',
href: '', 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 // eslint-disable-next-line no-console
console.log('register submit:', value); console.log('register submit:', value);
} }

View File

@@ -1,6 +1,6 @@
{ {
"name": "@vben/web-naive", "name": "@vben/web-naive",
"version": "5.4.0-beta.1", "version": "5.4.2",
"homepage": "https://vben.pro", "homepage": "https://vben.pro",
"bugs": "https://github.com/vbenjs/vue-vben-admin/issues", "bugs": "https://github.com/vbenjs/vue-vben-admin/issues",
"repository": { "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: 'info' }, 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 { import type {
BaseFormComponentType,
VbenFormSchema as FormSchema, VbenFormSchema as FormSchema,
VbenFormProps, VbenFormProps,
} from '@vben/common-ui'; } from '@vben/common-ui';
import type { Component, SetupContext } from 'vue'; import type { ComponentType } from './component';
import { h } from 'vue';
import { setupVbenForm, useVbenForm as useForm, z } from '@vben/common-ui'; import { setupVbenForm, useVbenForm as useForm, z } from '@vben/common-ui';
import { $t } from '@vben/locales'; import { $t } from '@vben/locales';
import { setupVbenForm<ComponentType>({
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,
},
config: { config: {
// naive-ui组件不接受onChang事件所以需要禁用 // naive-ui组件不接受onChang事件所以需要禁用
disabledOnChangeListener: true, disabledOnChangeListener: true,
@@ -94,22 +24,22 @@ setupVbenForm<FormComponentType>({
defineRules: { defineRules: {
required: (value, _params, ctx) => { required: (value, _params, ctx) => {
if (value === undefined || value === null || value.length === 0) { if (value === undefined || value === null || value.length === 0) {
return $t('formRules.required', [ctx.label]); return $t('ui.formRules.required', [ctx.label]);
} }
return true; return true;
}, },
selectRequired: (value, _params, ctx) => { selectRequired: (value, _params, ctx) => {
if (value === undefined || value === null) { if (value === undefined || value === null) {
return $t('formRules.selectRequired', [ctx.label]); return $t('ui.formRules.selectRequired', [ctx.label]);
} }
return true; return true;
}, },
}, },
}); });
const useVbenForm = useForm<FormComponentType>; const useVbenForm = useForm<ComponentType>;
export { useVbenForm, z }; export { useVbenForm, z };
export type VbenFormSchema = FormSchema<FormComponentType>; export type VbenFormSchema = FormSchema<ComponentType>;
export type { VbenFormProps }; export type { VbenFormProps };

View File

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

View File

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

View File

@@ -6,10 +6,13 @@ import '@vben/styles';
import { setupI18n } from '#/locales'; import { setupI18n } from '#/locales';
import { initComponentAdapter } from './adapter/component';
import App from './app.vue'; import App from './app.vue';
import { router } from './router'; import { router } from './router';
async function bootstrap(namespace: string) { async function bootstrap(namespace: string) {
// 初始化组件适配器
initComponentAdapter();
const app = createApp(App); const app = createApp(App);
// 国际化 i18n 配置 // 国际化 i18n 配置

View File

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

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

View File

@@ -32,6 +32,7 @@ const coreRoutes: RouteRecordRaw[] = [
{ {
component: AuthPageLayout, component: AuthPageLayout,
meta: { meta: {
hideInTab: true,
title: 'Authentication', title: 'Authentication',
}, },
name: 'Authentication', name: 'Authentication',
@@ -42,7 +43,7 @@ const coreRoutes: RouteRecordRaw[] = [
path: 'login', path: 'login',
component: Login, component: Login,
meta: { meta: {
title: $t('page.core.login'), title: $t('page.auth.login'),
}, },
}, },
{ {
@@ -50,7 +51,7 @@ const coreRoutes: RouteRecordRaw[] = [
path: 'code-login', path: 'code-login',
component: () => import('#/views/_core/authentication/code-login.vue'), component: () => import('#/views/_core/authentication/code-login.vue'),
meta: { meta: {
title: $t('page.core.codeLogin'), title: $t('page.auth.codeLogin'),
}, },
}, },
{ {
@@ -59,7 +60,7 @@ const coreRoutes: RouteRecordRaw[] = [
component: () => component: () =>
import('#/views/_core/authentication/qrcode-login.vue'), import('#/views/_core/authentication/qrcode-login.vue'),
meta: { meta: {
title: $t('page.core.qrcodeLogin'), title: $t('page.auth.qrcodeLogin'),
}, },
}, },
{ {
@@ -68,7 +69,7 @@ const coreRoutes: RouteRecordRaw[] = [
component: () => component: () =>
import('#/views/_core/authentication/forget-password.vue'), import('#/views/_core/authentication/forget-password.vue'),
meta: { meta: {
title: $t('page.core.forgetPassword'), title: $t('page.auth.forgetPassword'),
}, },
}, },
{ {
@@ -76,7 +77,7 @@ const coreRoutes: RouteRecordRaw[] = [
path: 'register', path: 'register',
component: () => import('#/views/_core/authentication/register.vue'), component: () => import('#/views/_core/authentication/register.vue'),
meta: { meta: {
title: $t('page.core.register'), title: $t('page.auth.register'),
}, },
}, },
], ],

View File

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

View File

@@ -19,7 +19,7 @@ const routes: RouteRecordRaw[] = [
badgeType: 'dot', badgeType: 'dot',
icon: VBEN_LOGO_URL, icon: VBEN_LOGO_URL,
order: 9999, order: 9999,
title: $t('page.vben.title'), title: $t('demos.vben.title'),
}, },
name: 'VbenProject', name: 'VbenProject',
path: '/vben-admin', path: '/vben-admin',
@@ -30,7 +30,7 @@ const routes: RouteRecordRaw[] = [
component: () => import('#/views/_core/about/index.vue'), component: () => import('#/views/_core/about/index.vue'),
meta: { meta: {
icon: 'lucide:copyright', icon: 'lucide:copyright',
title: $t('page.vben.about'), title: $t('demos.vben.about'),
}, },
}, },
{ {
@@ -40,7 +40,7 @@ const routes: RouteRecordRaw[] = [
meta: { meta: {
icon: 'lucide:book-open-text', icon: 'lucide:book-open-text',
link: VBEN_DOC_URL, link: VBEN_DOC_URL,
title: $t('page.vben.document'), title: $t('demos.vben.document'),
}, },
}, },
{ {
@@ -61,7 +61,7 @@ const routes: RouteRecordRaw[] = [
badgeType: 'dot', badgeType: 'dot',
icon: SvgAntdvLogoIcon, icon: SvgAntdvLogoIcon,
link: VBEN_ANT_PREVIEW_URL, link: VBEN_ANT_PREVIEW_URL,
title: $t('page.vben.antdv'), title: $t('demos.vben.antdv'),
}, },
}, },
{ {
@@ -72,7 +72,7 @@ const routes: RouteRecordRaw[] = [
badgeType: 'dot', badgeType: 'dot',
icon: 'logos:element', icon: 'logos:element',
link: VBEN_ELE_PREVIEW_URL, 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 { Recordable, UserInfo } from '@vben/types';
import type { UserInfo } from '@vben/types';
import { ref } from 'vue'; import { ref } from 'vue';
import { useRouter } from 'vue-router'; import { useRouter } from 'vue-router';
@@ -9,7 +8,7 @@ import { resetAllStores, useAccessStore, useUserStore } from '@vben/stores';
import { defineStore } from 'pinia'; import { defineStore } from 'pinia';
import { notification } from '#/adapter'; import { notification } from '#/adapter/naive';
import { getAccessCodesApi, getUserInfoApi, loginApi, logoutApi } from '#/api'; import { getAccessCodesApi, getUserInfoApi, loginApi, logoutApi } from '#/api';
import { $t } from '#/locales'; import { $t } from '#/locales';
@@ -26,7 +25,7 @@ export const useAuthStore = defineStore('auth', () => {
* @param params 登录表单数据 * @param params 登录表单数据
*/ */
async function authLogin( async function authLogin(
params: LoginAndRegisterParams, params: Recordable<any>,
onSuccess?: () => Promise<void> | void, onSuccess?: () => Promise<void> | void,
) { ) {
// 异步处理用户登录操作并获取 accessToken // 异步处理用户登录操作并获取 accessToken
@@ -85,7 +84,7 @@ export const useAuthStore = defineStore('auth', () => {
resetAllStores(); resetAllStores();
accessStore.setLoginExpired(false); accessStore.setLoginExpired(false);
// 回登页带上当前路由地址 // 回登页带上当前路由地址
await router.replace({ await router.replace({
path: LOGIN_PATH, path: LOGIN_PATH,
query: redirect query: redirect

View File

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

View File

@@ -1,5 +1,6 @@
<script lang="ts" setup> <script lang="ts" setup>
import type { VbenFormSchema } from '@vben/common-ui'; import type { VbenFormSchema } from '@vben/common-ui';
import type { Recordable } from '@vben/types';
import { computed, ref } from 'vue'; 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 // eslint-disable-next-line no-console
console.log('reset email:', value); console.log('reset email:', value);
} }

View File

@@ -1,5 +1,6 @@
<script lang="ts" setup> <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'; import { computed, h, ref } from 'vue';
@@ -45,7 +46,7 @@ const formSchema = computed((): VbenFormSchema[] => {
rules(values) { rules(values) {
const { password } = values; const { password } = values;
return z return z
.string() .string({ required_error: $t('authentication.passwordTip') })
.min(1, { message: $t('authentication.passwordTip') }) .min(1, { message: $t('authentication.passwordTip') })
.refine((value) => value === password, { .refine((value) => value === password, {
message: $t('authentication.confirmPasswordTip'), message: $t('authentication.confirmPasswordTip'),
@@ -55,7 +56,6 @@ const formSchema = computed((): VbenFormSchema[] => {
}, },
fieldName: 'confirmPassword', fieldName: 'confirmPassword',
label: $t('authentication.confirmPassword'), label: $t('authentication.confirmPassword'),
rules: z.string().min(1, { message: $t('authentication.passwordTip') }),
}, },
{ {
component: 'VbenCheckbox', component: 'VbenCheckbox',
@@ -67,15 +67,10 @@ const formSchema = computed((): VbenFormSchema[] => {
h( h(
'a', 'a',
{ {
class: class: 'vben-link ml-1',
'cursor-pointer text-primary ml-1 hover:text-primary-hover',
href: '', 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 // eslint-disable-next-line no-console
console.log('register submit:', value); console.log('register submit:', value);
} }

View File

@@ -164,6 +164,10 @@ function sidebarComponents(): DefaultTheme.SidebarItem[] {
link: 'common-ui/vben-form', link: 'common-ui/vben-form',
text: 'Form 表单', text: 'Form 表单',
}, },
{
link: 'common-ui/vben-vxe-table',
text: 'Vxe Table 表格',
},
{ {
link: 'common-ui/vben-count-to-animator', link: 'common-ui/vben-count-to-animator',
text: 'CountToAnimator 数字动画', text: 'CountToAnimator 数字动画',

View File

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

View File

@@ -1,6 +1,6 @@
{ {
"name": "@vben/docs", "name": "@vben/docs",
"version": "5.4.0-beta.1", "version": "5.4.2",
"private": true, "private": true,
"scripts": { "scripts": {
"build": "vitepress build", "build": "vitepress build",

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 { import type {
BaseFormComponentType,
VbenFormSchema as FormSchema, VbenFormSchema as FormSchema,
VbenFormProps, VbenFormProps,
} from '@vben/common-ui'; } from '@vben/common-ui';
import { h } from 'vue'; import type { ComponentType } from './component';
import { setupVbenForm, useVbenForm as useForm, z } from '@vben/common-ui'; import { setupVbenForm, useVbenForm as useForm, z } from '@vben/common-ui';
import { $t } from '@vben/locales'; import { $t } from '@vben/locales';
import { import { initComponentAdapter } from './component';
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';
// 这里需要自行根据业务组件库进行适配,需要用到的组件都需要在这里类型说明 initComponentAdapter();
export type FormComponentType = setupVbenForm<ComponentType>({
| '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,
},
config: { config: {
// ant design vue组件库默认都是 v-model:value
baseModelPropName: 'value', baseModelPropName: 'value',
// naive-ui组件不接受onChang事件所以需要禁用
// 一些组件是 v-model:checked 或者 v-model:fileList disabledOnChangeListener: true,
// naive-ui组件的空值为null,不能是undefined否则重置表单时不生效
emptyStateValue: null,
modelPropNameMap: { modelPropNameMap: {
Checkbox: 'checked', Checkbox: 'checked',
Radio: 'checked', Radio: 'checked',
@@ -102,26 +26,24 @@ setupVbenForm<FormComponentType>({
}, },
}, },
defineRules: { defineRules: {
// 输入项目必填国际化适配
required: (value, _params, ctx) => { required: (value, _params, ctx) => {
if (value === undefined || value === null || value.length === 0) { if (value === undefined || value === null || value.length === 0) {
return $t('formRules.required', [ctx.label]); return $t('ui.formRules.required', [ctx.label]);
} }
return true; return true;
}, },
// 选择项目必填国际化适配
selectRequired: (value, _params, ctx) => { selectRequired: (value, _params, ctx) => {
if (value === undefined || value === null) { if (value === undefined || value === null) {
return $t('formRules.selectRequired', [ctx.label]); return $t('ui.formRules.selectRequired', [ctx.label]);
} }
return true; return true;
}, },
}, },
}); });
const useVbenForm = useForm<FormComponentType>; const useVbenForm = useForm<ComponentType>;
export { useVbenForm, z }; export { useVbenForm, z };
export type VbenFormSchema = FormSchema<FormComponentType>; export type VbenFormSchema = FormSchema<ComponentType>;
export type { VbenFormProps }; export type { VbenFormProps };

View File

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

View File

@@ -14,6 +14,12 @@ outline: deep
::: :::
::: tip README
下方示例代码中的,存在一些国际化、主题色未适配问题,这些问题只在文档内会出现,实际使用并不会有这些问题,可忽略,不必纠结。
:::
## 基础用法 ## 基础用法
使用 `useVbenDrawer` 创建最基础的模态框。 使用 `useVbenDrawer` 创建最基础的模态框。
@@ -47,7 +53,7 @@ Drawer 内的内容一般业务中,会比较复杂,所以我们可以将 dra
::: info 注意 ::: info 注意
- `VbenDrawer` 组件对与参数的处理优先级是 `slot` > `props` > `state`(通过api更新的状态以及useVbenDrawer参数)。如果你已经传入了 `slot` 或者 `props`,那么 `setState` 将不会生效,这种情况下你可以通过 `slot` 或者 `props` 来更新状态。 - `VbenDrawer` 组件对与参数的处理优先级是 `slot` > `props` > `state`(通过api更新的状态以及useVbenDrawer参数)。如果你已经传入了 `slot` 或者 `props`,那么 `setState` 将不会生效,这种情况下你可以通过 `slot` 或者 `props` 来更新状态。
- 如果你使用到了 `connectedComponent` 参数,那么会存在 2 个`useVbenDrawer`, 此时,如果同时设置了相同的参数,那么以内部为准(也就是没有设置 connectedComponent 的代码)。比如 同时设置了 `onComfirm`,那么以内部的 `onComfirm` 为准。`onOpenChange`事件除外,内外都会触发。 - 如果你使用到了 `connectedComponent` 参数,那么会存在 2 个`useVbenDrawer`, 此时,如果同时设置了相同的参数,那么以内部为准(也就是没有设置 connectedComponent 的代码)。比如 同时设置了 `onConfirm`,那么以内部的 `onConfirm` 为准。`onOpenChange`事件除外,内外都会触发。
::: :::

View File

@@ -20,116 +20,23 @@ outline: deep
### 适配器说明 ### 适配器说明
每个应用都有不同的 UI 框架,所以在应用的 `src/adapter/form` 内部,你可以根据自己的需求,进行组件适配。下面是 `Ant Design Vue` 的适配器示例代码,可根据注释查看说明: 每个应用都有不同的 UI 框架,所以在应用的 `src/adapter/form``src/adapter/component` 内部,你可以根据自己的需求,进行组件适配。下面是 `Ant Design Vue` 的适配器示例代码,可根据注释查看说明:
::: details ant design 适配器 ::: details ant design vue 表单适配器
```ts ```ts
import type { import type {
BaseFormComponentType,
VbenFormSchema as FormSchema, VbenFormSchema as FormSchema,
VbenFormProps, VbenFormProps,
} from '@vben/common-ui'; } from '@vben/common-ui';
import type { Component, SetupContext } from 'vue'; import type { ComponentType } from './component';
import { h } from 'vue';
import { setupVbenForm, useVbenForm as useForm, z } from '@vben/common-ui'; import { setupVbenForm, useVbenForm as useForm, z } from '@vben/common-ui';
import { $t } from '@vben/locales'; import { $t } from '@vben/locales';
import { setupVbenForm<ComponentType>({
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,
},
config: { config: {
// 是否禁用onChange事件监听naive ui组件库默认不需要监听onChange事件否则会在控制台报错
disabledOnChangeListener: true,
// ant design vue组件库默认都是 v-model:value // ant design vue组件库默认都是 v-model:value
baseModelPropName: 'value', baseModelPropName: 'value',
// 一些组件是 v-model:checked 或者 v-model:fileList // 一些组件是 v-model:checked 或者 v-model:fileList
@@ -144,30 +51,163 @@ setupVbenForm<FormComponentType>({
// 输入项目必填国际化适配 // 输入项目必填国际化适配
required: (value, _params, ctx) => { required: (value, _params, ctx) => {
if (value === undefined || value === null || value.length === 0) { if (value === undefined || value === null || value.length === 0) {
return $t('formRules.required', [ctx.label]); return $t('ui.formRules.required', [ctx.label]);
} }
return true; return true;
}, },
// 选择项目必填国际化适配 // 选择项目必填国际化适配
selectRequired: (value, _params, ctx) => { selectRequired: (value, _params, ctx) => {
if (value === undefined || value === null) { if (value === undefined || value === null) {
return $t('formRules.selectRequired', [ctx.label]); return $t('ui.formRules.selectRequired', [ctx.label]);
} }
return true; return true;
}, },
}, },
}); });
const useVbenForm = useForm<FormComponentType>; const useVbenForm = useForm<ComponentType>;
export { useVbenForm, z }; export { useVbenForm, z };
export type VbenFormSchema = FormSchema<ComponentType>;
export type VbenFormSchema = FormSchema<FormComponentType>;
export type { VbenFormProps }; export type { VbenFormProps };
``` ```
::: :::
::: details ant design vue 组件适配器
```ts
/**
* 通用组件共同的使用的基础组件,原先放在 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 };
```
:::
## 基础用法 ## 基础用法
::: tip README ::: tip README
@@ -218,7 +258,7 @@ _注意_ 需要指定 `dependencies` 的 `triggerFields` 属性,设置由谁
```vue ```vue
<script setup lang="ts"> <script setup lang="ts">
import { useVbenForm } from '#/adapter'; import { useVbenForm } from '#/adapter/form';
// Form 为弹窗组件 // Form 为弹窗组件
// formApi 为弹窗的方法 // formApi 为弹窗的方法
@@ -271,6 +311,7 @@ useVbenForm 返回的第二个参数,是一个对象,包含了一些表单
| collapsedRows | 折叠时保持的行数 | `number` | `1` | | collapsedRows | 折叠时保持的行数 | `number` | `1` |
| commonConfig | 表单项的通用配置,每个配置都会传递到每个表单项,表单项可覆盖 | `FormCommonConfig` | - | | commonConfig | 表单项的通用配置,每个配置都会传递到每个表单项,表单项可覆盖 | `FormCommonConfig` | - |
| schema | 表单项的每一项配置 | `FormSchema` | - | | schema | 表单项的每一项配置 | `FormSchema` | - |
| submitOnEnter | 按下回车健时提交表单 | `boolean` | false |
### TS 类型说明 ### TS 类型说明
@@ -435,7 +476,7 @@ rules的值可以是一个字符串也可以是一个zod的schema。
rules也支持 zod 的 schema可以进行更复杂的校验zod 的使用请查看 [zod文档](https://zod.dev/)。 rules也支持 zod 的 schema可以进行更复杂的校验zod 的使用请查看 [zod文档](https://zod.dev/)。
```ts ```ts
import { z } from '#/adapter'; import { z } from '#/adapter/form';
// 基础类型 // 基础类型
{ {

View File

@@ -14,6 +14,12 @@ outline: deep
::: :::
::: tip README
下方示例代码中的,存在一些国际化、主题色未适配问题,这些问题只在文档内会出现,实际使用并不会有这些问题,可忽略,不必纠结。
:::
## 基础用法 ## 基础用法
使用 `useVbenModal` 创建最基础的模态框。 使用 `useVbenModal` 创建最基础的模态框。
@@ -53,7 +59,7 @@ Modal 内的内容一般业务中,会比较复杂,所以我们可以将 moda
::: info 注意 ::: info 注意
- `VbenModal` 组件对与参数的处理优先级是 `slot` > `props` > `state`(通过api更新的状态以及useVbenModal参数)。如果你已经传入了 `slot` 或者 `props`,那么 `setState` 将不会生效,这种情况下你可以通过 `slot` 或者 `props` 来更新状态。 - `VbenModal` 组件对与参数的处理优先级是 `slot` > `props` > `state`(通过api更新的状态以及useVbenModal参数)。如果你已经传入了 `slot` 或者 `props`,那么 `setState` 将不会生效,这种情况下你可以通过 `slot` 或者 `props` 来更新状态。
- 如果你使用到了 `connectedComponent` 参数,那么会存在 2 个`useVbenModal`, 此时,如果同时设置了相同的参数,那么以内部为准(也就是没有设置 connectedComponent 的代码)。比如 同时设置了 `onComfirm`,那么以内部的 `onComfirm` 为准。`onOpenChange`事件除外,内外都会触发。 - 如果你使用到了 `connectedComponent` 参数,那么会存在 2 个`useVbenModal`, 此时,如果同时设置了相同的参数,那么以内部为准(也就是没有设置 connectedComponent 的代码)。比如 同时设置了 `onConfirm`,那么以内部的 `onConfirm` 为准。`onOpenChange`事件除外,内外都会触发。
::: :::
@@ -98,6 +104,7 @@ const [Modal, modalApi] = useVbenModal({
| contentClass | modal内容区域的class | `string` | - | | contentClass | modal内容区域的class | `string` | - |
| footerClass | modal底部区域的class | `string` | - | | footerClass | modal底部区域的class | `string` | - |
| headerClass | modal顶部区域的class | `string` | - | | headerClass | modal顶部区域的class | `string` | - |
| bordered | 是否显示border | `boolean` | `false` |
### Event ### Event

View File

@@ -0,0 +1,7 @@
---
outline: deep
---
# Vben Vxe Table 表格
文档待补充,如果需要使用,可先行查看 vxe-table 文档和 示例代码,内部有部分注释。

View File

@@ -1,7 +1,7 @@
<script lang="ts" setup> <script lang="ts" setup>
import { Button, message, Space } from 'ant-design-vue'; import { Button, message, Space } from 'ant-design-vue';
import { useVbenForm } from '#/adapter'; import { useVbenForm } from '#/adapter/form';
const [BaseForm, formApi] = useVbenForm({ const [BaseForm, formApi] = useVbenForm({
// 所有表单项共用,可单独在表单内覆盖 // 所有表单项共用,可单独在表单内覆盖
@@ -81,6 +81,97 @@ function handleClick(
| 'updateSubmitButton', | 'updateSubmitButton',
) { ) {
switch (action) { switch (action) {
case 'batchAddSchema': {
formApi.setState((prev) => {
const currentSchema = prev?.schema ?? [];
const newSchema = [];
for (let i = 0; i < 2; i++) {
newSchema.push({
component: 'Input',
componentProps: {
placeholder: '请输入',
},
fieldName: `field${i}${Date.now()}`,
label: `field+`,
});
}
return {
schema: [...currentSchema, ...newSchema],
};
});
break;
}
case 'batchDeleteSchema': {
formApi.setState((prev) => {
const currentSchema = prev?.schema ?? [];
return {
schema: currentSchema.slice(0, -2),
};
});
break;
}
case 'disabled': {
formApi.setState({ commonConfig: { disabled: true } });
break;
}
case 'hiddenAction': {
formApi.setState({ showDefaultActions: false });
break;
}
case 'hiddenResetButton': {
formApi.setState({ resetButtonOptions: { show: false } });
break;
}
case 'hiddenSubmitButton': {
formApi.setState({ submitButtonOptions: { show: false } });
break;
}
case 'labelWidth': {
formApi.setState({
commonConfig: {
labelWidth: 150,
},
});
break;
}
case 'resetDisabled': {
formApi.setState({ commonConfig: { disabled: false } });
break;
}
case 'resetLabelWidth': {
formApi.setState({
commonConfig: {
labelWidth: 100,
},
});
break;
}
case 'showAction': {
formApi.setState({ showDefaultActions: true });
break;
}
case 'showResetButton': {
formApi.setState({ resetButtonOptions: { show: true } });
break;
}
case 'showSubmitButton': {
formApi.setState({ submitButtonOptions: { show: true } });
break;
}
case 'updateActionAlign': {
formApi.setState({
// 可以自行调整class
actionWrapperClass: 'text-center',
});
break;
}
case 'updateResetButton': {
formApi.setState({
resetButtonOptions: { disabled: true },
});
break;
}
case 'updateSchema': { case 'updateSchema': {
formApi.updateSchema([ formApi.updateSchema([
{ {
@@ -106,103 +197,12 @@ function handleClick(
message.success('字段 `fieldOptions` 下拉选项更新成功。'); message.success('字段 `fieldOptions` 下拉选项更新成功。');
break; break;
} }
case 'labelWidth': {
formApi.setState({
commonConfig: {
labelWidth: 150,
},
});
break;
}
case 'resetLabelWidth': {
formApi.setState({
commonConfig: {
labelWidth: 100,
},
});
break;
}
case 'disabled': {
formApi.setState({ commonConfig: { disabled: true } });
break;
}
case 'resetDisabled': {
formApi.setState({ commonConfig: { disabled: false } });
break;
}
case 'hiddenAction': {
formApi.setState({ showDefaultActions: false });
break;
}
case 'showAction': {
formApi.setState({ showDefaultActions: true });
break;
}
case 'hiddenResetButton': {
formApi.setState({ resetButtonOptions: { show: false } });
break;
}
case 'showResetButton': {
formApi.setState({ resetButtonOptions: { show: true } });
break;
}
case 'hiddenSubmitButton': {
formApi.setState({ submitButtonOptions: { show: false } });
break;
}
case 'showSubmitButton': {
formApi.setState({ submitButtonOptions: { show: true } });
break;
}
case 'updateResetButton': {
formApi.setState({
resetButtonOptions: { disabled: true },
});
break;
}
case 'updateSubmitButton': { case 'updateSubmitButton': {
formApi.setState({ formApi.setState({
submitButtonOptions: { loading: true }, submitButtonOptions: { loading: true },
}); });
break; break;
} }
case 'updateActionAlign': {
formApi.setState({
// 可以自行调整class
actionWrapperClass: 'text-center',
});
break;
}
case 'batchAddSchema': {
formApi.setState((prev) => {
const currentSchema = prev?.schema ?? [];
const newSchema = [];
for (let i = 0; i < 2; i++) {
newSchema.push({
component: 'Input',
componentProps: {
placeholder: '请输入',
},
fieldName: `field${i}${Date.now()}`,
label: `field+`,
});
}
return {
schema: [...currentSchema, ...newSchema],
};
});
break;
}
case 'batchDeleteSchema': {
formApi.setState((prev) => {
const currentSchema = prev?.schema ?? [];
return {
schema: currentSchema.slice(0, -2),
};
});
break;
}
} }
} }
</script> </script>

View File

@@ -1,7 +1,7 @@
<script lang="ts" setup> <script lang="ts" setup>
import { message } from 'ant-design-vue'; import { message } from 'ant-design-vue';
import { useVbenForm } from '#/adapter'; import { useVbenForm } from '#/adapter/form';
const [BaseForm] = useVbenForm({ const [BaseForm] = useVbenForm({
// 所有表单项共用,可单独在表单内覆盖 // 所有表单项共用,可单独在表单内覆盖

View File

@@ -3,7 +3,7 @@ import { h } from 'vue';
import { Input, message } from 'ant-design-vue'; import { Input, message } from 'ant-design-vue';
import { useVbenForm } from '#/adapter'; import { useVbenForm } from '#/adapter/form';
const [Form] = useVbenForm({ const [Form] = useVbenForm({
// 所有表单项共用,可单独在表单内覆盖 // 所有表单项共用,可单独在表单内覆盖

View File

@@ -1,7 +1,7 @@
<script lang="ts" setup> <script lang="ts" setup>
import { message } from 'ant-design-vue'; import { message } from 'ant-design-vue';
import { useVbenForm } from '#/adapter'; import { useVbenForm } from '#/adapter/form';
const [Form] = useVbenForm({ const [Form] = useVbenForm({
// 提交函数 // 提交函数

View File

@@ -1,7 +1,7 @@
<script lang="ts" setup> <script lang="ts" setup>
import { message } from 'ant-design-vue'; import { message } from 'ant-design-vue';
import { useVbenForm } from '#/adapter'; import { useVbenForm } from '#/adapter/form';
const [QueryForm] = useVbenForm({ const [QueryForm] = useVbenForm({
// 默认展开 // 默认展开

View File

@@ -1,7 +1,7 @@
<script lang="ts" setup> <script lang="ts" setup>
import { message } from 'ant-design-vue'; import { message } from 'ant-design-vue';
import { useVbenForm, z } from '#/adapter'; import { useVbenForm, z } from '#/adapter/form';
const [Form] = useVbenForm({ const [Form] = useVbenForm({
// 所有表单项共用,可单独在表单内覆盖 // 所有表单项共用,可单独在表单内覆盖

View File

@@ -106,7 +106,7 @@ const routes: RouteRecordRaw[] = [
icon: 'ic:baseline-view-in-ar', icon: 'ic:baseline-view-in-ar',
keepAlive: true, keepAlive: true,
order: 1000, order: 1000,
title: $t('page.demos.title'), title: $t('demos.title'),
}, },
name: 'Demos', name: 'Demos',
path: '/demos', path: '/demos',
@@ -116,7 +116,7 @@ const routes: RouteRecordRaw[] = [
{ {
meta: { meta: {
icon: 'ic:round-menu', icon: 'ic:round-menu',
title: $t('page.demos.nested.title'), title: $t('demos.nested.title'),
}, },
name: 'NestedDemos', name: 'NestedDemos',
path: '/demos/nested', path: '/demos/nested',
@@ -129,7 +129,7 @@ const routes: RouteRecordRaw[] = [
meta: { meta: {
icon: 'ic:round-menu', icon: 'ic:round-menu',
keepAlive: true, keepAlive: true,
title: $t('page.demos.nested.menu1'), title: $t('demos.nested.menu1'),
}, },
}, },
{ {
@@ -138,7 +138,7 @@ const routes: RouteRecordRaw[] = [
meta: { meta: {
icon: 'ic:round-menu', icon: 'ic:round-menu',
keepAlive: true, keepAlive: true,
title: $t('page.demos.nested.menu2'), title: $t('demos.nested.menu2'),
}, },
redirect: '/demos/nested/menu2/menu2-1', redirect: '/demos/nested/menu2/menu2-1',
children: [ children: [
@@ -149,7 +149,7 @@ const routes: RouteRecordRaw[] = [
meta: { meta: {
icon: 'ic:round-menu', icon: 'ic:round-menu',
keepAlive: true, keepAlive: true,
title: $t('page.demos.nested.menu2_1'), title: $t('demos.nested.menu2_1'),
}, },
}, },
], ],
@@ -159,7 +159,7 @@ const routes: RouteRecordRaw[] = [
path: '/demos/nested/menu3', path: '/demos/nested/menu3',
meta: { meta: {
icon: 'ic:round-menu', icon: 'ic:round-menu',
title: $t('page.demos.nested.menu3'), title: $t('demos.nested.menu3'),
}, },
redirect: '/demos/nested/menu3/menu3-1', redirect: '/demos/nested/menu3/menu3-1',
children: [ children: [
@@ -170,7 +170,7 @@ const routes: RouteRecordRaw[] = [
meta: { meta: {
icon: 'ic:round-menu', icon: 'ic:round-menu',
keepAlive: true, keepAlive: true,
title: $t('page.demos.nested.menu3_1'), title: $t('demos.nested.menu3_1'),
}, },
}, },
{ {
@@ -178,7 +178,7 @@ const routes: RouteRecordRaw[] = [
path: 'menu3-2', path: 'menu3-2',
meta: { meta: {
icon: 'ic:round-menu', icon: 'ic:round-menu',
title: $t('page.demos.nested.menu3_2'), title: $t('demos.nested.menu3_2'),
}, },
redirect: '/demos/nested/menu3/menu3-2/menu3-2-1', redirect: '/demos/nested/menu3/menu3-2/menu3-2-1',
children: [ children: [
@@ -190,7 +190,7 @@ const routes: RouteRecordRaw[] = [
meta: { meta: {
icon: 'ic:round-menu', icon: 'ic:round-menu',
keepAlive: true, keepAlive: true,
title: $t('page.demos.nested.menu3_2_1'), title: $t('demos.nested.menu3_2_1'),
}, },
}, },
], ],

View File

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

View File

@@ -58,7 +58,7 @@ updateLocale('en-US');
To add new translation texts, simply find `src/locales/langs/` in the corresponding application and add the texts accordingly, for example: To add new translation texts, simply find `src/locales/langs/` in the corresponding application and add the texts accordingly, for example:
**src/locales/langs/zh-CN.ts** **src/locales/langs/zh-CN/\*.json**
````ts ````ts
```json ```json

View File

@@ -1,6 +1,6 @@
# 友情链接 # 友情链接
如果您的网站是与 Vben Admin 相关的,或者也属于开源项目,欢迎联系我们,我们会将与您的网站加入交换友情链接。 如果您的网站是与 Vben Admin 相关的,也属于开源项目,欢迎联系我们,我们会将与您的网站加入交换友情链接。
## 交换方式 ## 交换方式

View File

@@ -105,7 +105,7 @@ const routes: RouteRecordRaw[] = [
icon: 'ic:baseline-view-in-ar', icon: 'ic:baseline-view-in-ar',
keepAlive: true, keepAlive: true,
order: 1000, order: 1000,
title: $t('page.demos.title'), title: $t('demos.title'),
}, },
name: 'Demos', name: 'Demos',
path: '/demos', path: '/demos',
@@ -115,7 +115,7 @@ const routes: RouteRecordRaw[] = [
{ {
meta: { meta: {
icon: 'ic:round-menu', icon: 'ic:round-menu',
title: $t('page.demos.nested.title'), title: $t('demos.nested.title'),
}, },
name: 'NestedDemos', name: 'NestedDemos',
path: '/demos/nested', path: '/demos/nested',
@@ -128,7 +128,7 @@ const routes: RouteRecordRaw[] = [
meta: { meta: {
icon: 'ic:round-menu', icon: 'ic:round-menu',
keepAlive: true, keepAlive: true,
title: $t('page.demos.nested.menu1'), title: $t('demos.nested.menu1'),
}, },
}, },
{ {
@@ -137,7 +137,7 @@ const routes: RouteRecordRaw[] = [
meta: { meta: {
icon: 'ic:round-menu', icon: 'ic:round-menu',
keepAlive: true, keepAlive: true,
title: $t('page.demos.nested.menu2'), title: $t('demos.nested.menu2'),
}, },
redirect: '/demos/nested/menu2/menu2-1', redirect: '/demos/nested/menu2/menu2-1',
children: [ children: [
@@ -148,7 +148,7 @@ const routes: RouteRecordRaw[] = [
meta: { meta: {
icon: 'ic:round-menu', icon: 'ic:round-menu',
keepAlive: true, keepAlive: true,
title: $t('page.demos.nested.menu2_1'), title: $t('demos.nested.menu2_1'),
}, },
}, },
], ],
@@ -158,7 +158,7 @@ const routes: RouteRecordRaw[] = [
path: '/demos/nested/menu3', path: '/demos/nested/menu3',
meta: { meta: {
icon: 'ic:round-menu', icon: 'ic:round-menu',
title: $t('page.demos.nested.menu3'), title: $t('demos.nested.menu3'),
}, },
redirect: '/demos/nested/menu3/menu3-1', redirect: '/demos/nested/menu3/menu3-1',
children: [ children: [
@@ -169,7 +169,7 @@ const routes: RouteRecordRaw[] = [
meta: { meta: {
icon: 'ic:round-menu', icon: 'ic:round-menu',
keepAlive: true, keepAlive: true,
title: $t('page.demos.nested.menu3_1'), title: $t('demos.nested.menu3_1'),
}, },
}, },
{ {
@@ -177,7 +177,7 @@ const routes: RouteRecordRaw[] = [
path: 'menu3-2', path: 'menu3-2',
meta: { meta: {
icon: 'ic:round-menu', icon: 'ic:round-menu',
title: $t('page.demos.nested.menu3_2'), title: $t('demos.nested.menu3_2'),
}, },
redirect: '/demos/nested/menu3/menu3-2/menu3-2-1', redirect: '/demos/nested/menu3/menu3-2/menu3-2-1',
children: [ children: [
@@ -189,7 +189,7 @@ const routes: RouteRecordRaw[] = [
meta: { meta: {
icon: 'ic:round-menu', icon: 'ic:round-menu',
keepAlive: true, keepAlive: true,
title: $t('page.demos.nested.menu3_2_1'), title: $t('demos.nested.menu3_2_1'),
}, },
}, },
], ],
@@ -386,6 +386,10 @@ interface RouteMeta {
* 用于路由->菜单排序 * 用于路由->菜单排序
*/ */
order?: number; order?: number;
/**
* 菜单所携带的参数
*/
query?: Recordable;
/** /**
* 标题名称 * 标题名称
*/ */
@@ -542,6 +546,15 @@ interface RouteMeta {
用于配置页面的排序,用于路由到菜单排序。 用于配置页面的排序,用于路由到菜单排序。
_注意:_ 排序仅针对一级菜单有效,二级菜单的排序需要在对应的一级菜单中按代码顺序设置。
### query
- 类型:`Recordable`
- 默认值:`{}`
用于配置页面的菜单参数,会在菜单中传递给页面。
## 路由刷新 ## 路由刷新
路由刷新方式如下: 路由刷新方式如下:

View File

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

View File

@@ -11,7 +11,7 @@ outline: deep
## 前端访问控制 ## 前端访问控制
**实现原理**: 在前端固定写死路由的权限,指定路由有哪些权限可以查看。只初始化通用的路由,需要权限才能访问的路由没有被加入路由表内。在登后或者其他方式获取用户角色后,通过角色去遍历路由表,获取该角色可以访问的路由表,生成路由表,再通过 `router.addRoute` 添加到路由实例,实现权限的过滤。 **实现原理**: 在前端固定写死路由的权限,指定路由有哪些权限可以查看。只初始化通用的路由,需要权限才能访问的路由没有被加入路由表内。在登后或者其他方式获取用户角色后,通过角色去遍历路由表,获取该角色可以访问的路由表,生成路由表,再通过 `router.addRoute` 添加到路由实例,实现权限的过滤。
**缺点**: 权限相对不自由,如果后台改动角色,前台也需要跟着改动。适合角色较固定的系统 **缺点**: 权限相对不自由,如果后台改动角色,前台也需要跟着改动。适合角色较固定的系统

View File

@@ -58,7 +58,7 @@ updateLocale('en-US');
新增翻译文本,只需要在对应的应用内,找到 `src/locales/langs/`,新增对应的文本即可,例: 新增翻译文本,只需要在对应的应用内,找到 `src/locales/langs/`,新增对应的文本即可,例:
**src/locales/langs/zh-CN.ts** **src/locales/langs/zh-CN/\*.json**
````ts ````ts
```json ```json

View File

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

View File

@@ -101,6 +101,7 @@ const customConfig: Linter.Config[] = [
], ],
}, },
}, },
{ {
// 不能引入@vben/*里面的包 // 不能引入@vben/*里面的包
files: [ files: [

View File

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

View File

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

View File

@@ -11,13 +11,10 @@ import { findUpSync } from 'find-up';
* @param cwd * @param cwd
*/ */
function findMonorepoRoot(cwd: string = process.cwd()) { function findMonorepoRoot(cwd: string = process.cwd()) {
const lockFile = findUpSync( const lockFile = findUpSync('pnpm-lock.yaml', {
['pnpm-lock.yaml', 'yarn.lock', 'package-lock.json'], cwd,
{ type: 'file',
cwd, });
type: 'file',
},
);
return dirname(lockFile || ''); return dirname(lockFile || '');
} }

View File

@@ -1,6 +1,6 @@
{ {
"name": "@vben/tailwind-config", "name": "@vben/tailwind-config",
"version": "5.4.0-beta.1", "version": "5.4.2",
"private": true, "private": true,
"homepage": "https://github.com/vbenjs/vue-vben-admin", "homepage": "https://github.com/vbenjs/vue-vben-admin",
"bugs": "https://github.com/vbenjs/vue-vben-admin/issues", "bugs": "https://github.com/vbenjs/vue-vben-admin/issues",

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