diff --git a/apps/vben5/apps/app-antd/package.json b/apps/vben5/apps/app-antd/package.json index 2a5979c5f..8b95e821b 100644 --- a/apps/vben5/apps/app-antd/package.json +++ b/apps/vben5/apps/app-antd/package.json @@ -1,6 +1,6 @@ { "name": "@vben/app-antd", - "version": "5.4.8", + "version": "5.5.1", "homepage": "https://vben.pro", "bugs": "https://github.com/vbenjs/vue-vben-admin/issues", "repository": { diff --git a/apps/vben5/apps/app-antd/src/adapter/component/index.ts b/apps/vben5/apps/app-antd/src/adapter/component/index.ts index e237b52fc..a43a8280a 100644 --- a/apps/vben5/apps/app-antd/src/adapter/component/index.ts +++ b/apps/vben5/apps/app-antd/src/adapter/component/index.ts @@ -8,7 +8,7 @@ import type { BaseFormComponentType } from '@vben/common-ui'; import type { Component, SetupContext } from 'vue'; import { h } from 'vue'; -import { globalShareState } from '@vben/common-ui'; +import { ApiComponent, globalShareState, IconPicker } from '@vben/common-ui'; import { $t } from '@vben/locales'; import { @@ -21,7 +21,6 @@ import { Input, InputNumber, InputPassword, - InputSearch, Mentions, notification, Radio, @@ -49,16 +48,18 @@ const withDefaultPlaceholder = ( // 这里需要自行根据业务组件库进行适配,需要用到的组件都需要在这里类型说明 export type ComponentType = + | 'ApiSelect' + | 'ApiTreeSelect' | 'AutoComplete' | 'Checkbox' | 'CheckboxGroup' | 'DatePicker' | 'DefaultButton' | 'Divider' + | 'IconPicker' | 'Input' | 'InputNumber' | 'InputPassword' - | 'InputSearch' | 'Mentions' | 'PrimaryButton' | 'Radio' @@ -79,7 +80,38 @@ async function initComponentAdapter() { // 如果你的组件体积比较大,可以使用异步加载 // Button: () => // import('xxx').then((res) => res.Button), - + ApiSelect: (props, { attrs, slots }) => { + return h( + ApiComponent, + { + placeholder: $t('ui.placeholder.select'), + ...props, + ...attrs, + component: Select, + loadingSlot: 'suffixIcon', + visibleEvent: 'onDropdownVisibleChange', + modelPropName: 'value', + }, + slots, + ); + }, + ApiTreeSelect: (props, { attrs, slots }) => { + return h( + ApiComponent, + { + placeholder: $t('ui.placeholder.select'), + ...props, + ...attrs, + component: TreeSelect, + fieldNames: { label: 'label', value: 'value', children: 'children' }, + loadingSlot: 'suffixIcon', + modelPropName: 'value', + optionsPropName: 'treeData', + visibleEvent: 'onVisibleChange', + }, + slots, + ); + }, AutoComplete, Checkbox, CheckboxGroup, @@ -89,10 +121,16 @@ async function initComponentAdapter() { return h(Button, { ...props, attrs, type: 'default' }, slots); }, Divider, + IconPicker: (props, { attrs, slots }) => { + return h( + IconPicker, + { iconSlot: 'addonAfter', inputComponent: Input, ...props, ...attrs }, + slots, + ); + }, Input: withDefaultPlaceholder(Input, 'input'), InputNumber: withDefaultPlaceholder(InputNumber, 'input'), InputPassword: withDefaultPlaceholder(InputPassword, 'input'), - InputSearch, Mentions: withDefaultPlaceholder(Mentions, 'input'), // 自定义主要按钮 PrimaryButton: (props, { attrs, slots }) => { diff --git a/apps/vben5/apps/app-antd/src/adapter/vxe-table.ts b/apps/vben5/apps/app-antd/src/adapter/vxe-table.ts index d296b2050..d27910397 100644 --- a/apps/vben5/apps/app-antd/src/adapter/vxe-table.ts +++ b/apps/vben5/apps/app-antd/src/adapter/vxe-table.ts @@ -20,6 +20,10 @@ setupVbenVxeTable({ // 全局禁用vxe-table的表单配置,使用formOptions enabled: false, }, + pagerConfig: { + pageSize: 10, + pageSizes: [10, 15, 25, 50, 100], + }, proxyConfig: { autoLoad: true, response: { diff --git a/apps/vben5/apps/app-antd/src/router/guard.ts b/apps/vben5/apps/app-antd/src/router/guard.ts index fce5a892c..cbb5235ec 100644 --- a/apps/vben5/apps/app-antd/src/router/guard.ts +++ b/apps/vben5/apps/app-antd/src/router/guard.ts @@ -54,7 +54,9 @@ function setupAccessGuard(router: Router) { if (coreRouteNames.includes(to.name as string)) { if (to.path === LOGIN_PATH && accessStore.accessToken) { return decodeURIComponent( - (to.query?.redirect as string) || DEFAULT_HOME_PATH, + (to.query?.redirect as string) || + userStore.userInfo?.homePath || + DEFAULT_HOME_PATH, ); } return true; @@ -72,7 +74,10 @@ function setupAccessGuard(router: Router) { return { path: LOGIN_PATH, // 如不需要,直接删除 query - query: { redirect: encodeURIComponent(to.fullPath) }, + query: + to.fullPath === DEFAULT_HOME_PATH + ? {} + : { redirect: encodeURIComponent(to.fullPath) }, // 携带当前跳转的页面,登录后重新跳转该页面 replace: true, }; @@ -102,7 +107,10 @@ function setupAccessGuard(router: Router) { accessStore.setAccessMenus(accessibleMenus); accessStore.setAccessRoutes(accessibleRoutes); accessStore.setIsAccessChecked(true); - const redirectPath = (from.query.redirect ?? to.fullPath) as string; + const redirectPath = (from.query.redirect ?? + (to.path === DEFAULT_HOME_PATH + ? userInfo.homePath || DEFAULT_HOME_PATH + : to.fullPath)) as string; return { ...router.resolve(decodeURIComponent(redirectPath)), diff --git a/apps/vben5/apps/backend-mock/api/table/list.ts b/apps/vben5/apps/backend-mock/api/table/list.ts index 4a0db94ec..55b88eaaa 100644 --- a/apps/vben5/apps/backend-mock/api/table/list.ts +++ b/apps/vben5/apps/backend-mock/api/table/list.ts @@ -43,6 +43,31 @@ export default eventHandler(async (event) => { await sleep(600); - const { page, pageSize } = getQuery(event); - return usePageResponseSuccess(page as string, pageSize as string, mockData); + const { page, pageSize, sortBy, sortOrder } = getQuery(event); + const listData = structuredClone(mockData); + if (sortBy && Reflect.has(listData[0], sortBy as string)) { + listData.sort((a, b) => { + if (sortOrder === 'asc') { + if (sortBy === 'price') { + return ( + Number.parseFloat(a[sortBy as string]) - + Number.parseFloat(b[sortBy as string]) + ); + } else { + return a[sortBy as string] > b[sortBy as string] ? 1 : -1; + } + } else { + if (sortBy === 'price') { + return ( + Number.parseFloat(b[sortBy as string]) - + Number.parseFloat(a[sortBy as string]) + ); + } else { + return a[sortBy as string] < b[sortBy as string] ? 1 : -1; + } + } + }); + } + + return usePageResponseSuccess(page as string, pageSize as string, listData); }); diff --git a/apps/vben5/apps/backend-mock/utils/mock-data.ts b/apps/vben5/apps/backend-mock/utils/mock-data.ts index 71970a285..057588e36 100644 --- a/apps/vben5/apps/backend-mock/utils/mock-data.ts +++ b/apps/vben5/apps/backend-mock/utils/mock-data.ts @@ -4,6 +4,7 @@ export interface UserInfo { realName: string; roles: string[]; username: string; + homePath?: string; } export const MOCK_USERS: UserInfo[] = [ @@ -20,6 +21,7 @@ export const MOCK_USERS: UserInfo[] = [ realName: 'Admin', roles: ['admin'], username: 'admin', + homePath: '/workspace', }, { id: 2, @@ -27,6 +29,7 @@ export const MOCK_USERS: UserInfo[] = [ realName: 'Jack', roles: ['user'], username: 'jack', + homePath: '/analytics', }, ]; diff --git a/apps/vben5/apps/web-antd/package.json b/apps/vben5/apps/web-antd/package.json index 42afa3c69..bbc5593f0 100644 --- a/apps/vben5/apps/web-antd/package.json +++ b/apps/vben5/apps/web-antd/package.json @@ -1,6 +1,6 @@ { "name": "@vben/web-antd", - "version": "5.4.8", + "version": "5.5.1", "homepage": "https://vben.pro", "bugs": "https://github.com/vbenjs/vue-vben-admin/issues", "repository": { diff --git a/apps/vben5/apps/web-antd/src/adapter/component/index.ts b/apps/vben5/apps/web-antd/src/adapter/component/index.ts index 1afa62174..a43a8280a 100644 --- a/apps/vben5/apps/web-antd/src/adapter/component/index.ts +++ b/apps/vben5/apps/web-antd/src/adapter/component/index.ts @@ -8,7 +8,7 @@ import type { BaseFormComponentType } from '@vben/common-ui'; import type { Component, SetupContext } from 'vue'; import { h } from 'vue'; -import { globalShareState } from '@vben/common-ui'; +import { ApiComponent, globalShareState, IconPicker } from '@vben/common-ui'; import { $t } from '@vben/locales'; import { @@ -48,12 +48,15 @@ const withDefaultPlaceholder = ( // 这里需要自行根据业务组件库进行适配,需要用到的组件都需要在这里类型说明 export type ComponentType = + | 'ApiSelect' + | 'ApiTreeSelect' | 'AutoComplete' | 'Checkbox' | 'CheckboxGroup' | 'DatePicker' | 'DefaultButton' | 'Divider' + | 'IconPicker' | 'Input' | 'InputNumber' | 'InputPassword' @@ -77,7 +80,38 @@ async function initComponentAdapter() { // 如果你的组件体积比较大,可以使用异步加载 // Button: () => // import('xxx').then((res) => res.Button), - + ApiSelect: (props, { attrs, slots }) => { + return h( + ApiComponent, + { + placeholder: $t('ui.placeholder.select'), + ...props, + ...attrs, + component: Select, + loadingSlot: 'suffixIcon', + visibleEvent: 'onDropdownVisibleChange', + modelPropName: 'value', + }, + slots, + ); + }, + ApiTreeSelect: (props, { attrs, slots }) => { + return h( + ApiComponent, + { + placeholder: $t('ui.placeholder.select'), + ...props, + ...attrs, + component: TreeSelect, + fieldNames: { label: 'label', value: 'value', children: 'children' }, + loadingSlot: 'suffixIcon', + modelPropName: 'value', + optionsPropName: 'treeData', + visibleEvent: 'onVisibleChange', + }, + slots, + ); + }, AutoComplete, Checkbox, CheckboxGroup, @@ -87,6 +121,13 @@ async function initComponentAdapter() { return h(Button, { ...props, attrs, type: 'default' }, slots); }, Divider, + IconPicker: (props, { attrs, slots }) => { + return h( + IconPicker, + { iconSlot: 'addonAfter', inputComponent: Input, ...props, ...attrs }, + slots, + ); + }, Input: withDefaultPlaceholder(Input, 'input'), InputNumber: withDefaultPlaceholder(InputNumber, 'input'), InputPassword: withDefaultPlaceholder(InputPassword, 'input'), diff --git a/apps/vben5/apps/web-antd/src/router/guard.ts b/apps/vben5/apps/web-antd/src/router/guard.ts index fce5a892c..cbb5235ec 100644 --- a/apps/vben5/apps/web-antd/src/router/guard.ts +++ b/apps/vben5/apps/web-antd/src/router/guard.ts @@ -54,7 +54,9 @@ function setupAccessGuard(router: Router) { if (coreRouteNames.includes(to.name as string)) { if (to.path === LOGIN_PATH && accessStore.accessToken) { return decodeURIComponent( - (to.query?.redirect as string) || DEFAULT_HOME_PATH, + (to.query?.redirect as string) || + userStore.userInfo?.homePath || + DEFAULT_HOME_PATH, ); } return true; @@ -72,7 +74,10 @@ function setupAccessGuard(router: Router) { return { path: LOGIN_PATH, // 如不需要,直接删除 query - query: { redirect: encodeURIComponent(to.fullPath) }, + query: + to.fullPath === DEFAULT_HOME_PATH + ? {} + : { redirect: encodeURIComponent(to.fullPath) }, // 携带当前跳转的页面,登录后重新跳转该页面 replace: true, }; @@ -102,7 +107,10 @@ function setupAccessGuard(router: Router) { accessStore.setAccessMenus(accessibleMenus); accessStore.setAccessRoutes(accessibleRoutes); accessStore.setIsAccessChecked(true); - const redirectPath = (from.query.redirect ?? to.fullPath) as string; + const redirectPath = (from.query.redirect ?? + (to.path === DEFAULT_HOME_PATH + ? userInfo.homePath || DEFAULT_HOME_PATH + : to.fullPath)) as string; return { ...router.resolve(decodeURIComponent(redirectPath)), diff --git a/apps/vben5/apps/web-antd/src/views/_core/authentication/code-login.vue b/apps/vben5/apps/web-antd/src/views/_core/authentication/code-login.vue index 556b273af..acfd1fd78 100644 --- a/apps/vben5/apps/web-antd/src/views/_core/authentication/code-login.vue +++ b/apps/vben5/apps/web-antd/src/views/_core/authentication/code-login.vue @@ -10,6 +10,7 @@ import { $t } from '@vben/locales'; defineOptions({ name: 'CodeLogin' }); const loading = ref(false); +const CODE_LENGTH = 6; const formSchema = computed((): VbenFormSchema[] => { return [ @@ -30,6 +31,7 @@ const formSchema = computed((): VbenFormSchema[] => { { component: 'VbenPinInput', componentProps: { + codeLength: CODE_LENGTH, createText: (countdown: number) => { const text = countdown > 0 @@ -41,7 +43,9 @@ const formSchema = computed((): VbenFormSchema[] => { }, fieldName: 'code', label: $t('authentication.code'), - rules: z.string().min(1, { message: $t('authentication.codeTip') }), + rules: z.string().length(CODE_LENGTH, { + message: $t('authentication.codeTip', [CODE_LENGTH]), + }), }, ]; }); diff --git a/apps/vben5/apps/web-ele/package.json b/apps/vben5/apps/web-ele/package.json index 430156951..a02376ee8 100644 --- a/apps/vben5/apps/web-ele/package.json +++ b/apps/vben5/apps/web-ele/package.json @@ -1,6 +1,6 @@ { "name": "@vben/web-ele", - "version": "5.4.8", + "version": "5.5.1", "homepage": "https://vben.pro", "bugs": "https://github.com/vbenjs/vue-vben-admin/issues", "repository": { diff --git a/apps/vben5/apps/web-ele/src/adapter/component/index.ts b/apps/vben5/apps/web-ele/src/adapter/component/index.ts index ebf9dd3e1..818c8c4e1 100644 --- a/apps/vben5/apps/web-ele/src/adapter/component/index.ts +++ b/apps/vben5/apps/web-ele/src/adapter/component/index.ts @@ -4,24 +4,28 @@ */ import type { BaseFormComponentType } from '@vben/common-ui'; +import type { Recordable } from '@vben/types'; import type { Component, SetupContext } from 'vue'; import { h } from 'vue'; -import { globalShareState } from '@vben/common-ui'; +import { ApiComponent, globalShareState, IconPicker } from '@vben/common-ui'; import { $t } from '@vben/locales'; import { ElButton, ElCheckbox, + ElCheckboxButton, ElCheckboxGroup, ElDatePicker, ElDivider, ElInput, ElInputNumber, ElNotification, + ElRadio, + ElRadioButton, ElRadioGroup, - ElSelect, + ElSelectV2, ElSpace, ElSwitch, ElTimePicker, @@ -41,10 +45,13 @@ const withDefaultPlaceholder = ( // 这里需要自行根据业务组件库进行适配,需要用到的组件都需要在这里类型说明 export type ComponentType = + | 'ApiSelect' + | 'ApiTreeSelect' | 'Checkbox' | 'CheckboxGroup' | 'DatePicker' | 'Divider' + | 'IconPicker' | 'Input' | 'InputNumber' | 'RadioGroup' @@ -61,9 +68,57 @@ async function initComponentAdapter() { // 如果你的组件体积比较大,可以使用异步加载 // Button: () => // import('xxx').then((res) => res.Button), - + ApiSelect: (props, { attrs, slots }) => { + return h( + ApiComponent, + { + placeholder: $t('ui.placeholder.select'), + ...props, + ...attrs, + component: ElSelectV2, + loadingSlot: 'loading', + visibleEvent: 'onVisibleChange', + }, + slots, + ); + }, + ApiTreeSelect: (props, { attrs, slots }) => { + return h( + ApiComponent, + { + placeholder: $t('ui.placeholder.select'), + ...props, + ...attrs, + component: ElTreeSelect, + props: { label: 'label', children: 'children' }, + nodeKey: 'value', + loadingSlot: 'loading', + optionsPropName: 'data', + visibleEvent: 'onVisibleChange', + }, + slots, + ); + }, Checkbox: ElCheckbox, - CheckboxGroup: ElCheckboxGroup, + CheckboxGroup: (props, { attrs, slots }) => { + let defaultSlot; + if (Reflect.has(slots, 'default')) { + defaultSlot = slots.default; + } else { + const { options, isButton } = attrs; + if (Array.isArray(options)) { + defaultSlot = () => + options.map((option) => + h(isButton ? ElCheckboxButton : ElCheckbox, option), + ); + } + } + return h( + ElCheckboxGroup, + { ...props, ...attrs }, + { ...slots, default: defaultSlot }, + ); + }, // 自定义默认按钮 DefaultButton: (props, { attrs, slots }) => { return h(ElButton, { ...props, attrs, type: 'info' }, slots); @@ -73,14 +128,87 @@ async function initComponentAdapter() { return h(ElButton, { ...props, attrs, type: 'primary' }, slots); }, Divider: ElDivider, + IconPicker: (props, { attrs, slots }) => { + return h( + IconPicker, + { + iconSlot: 'append', + modelValueProp: 'model-value', + inputComponent: ElInput, + ...props, + ...attrs, + }, + slots, + ); + }, Input: withDefaultPlaceholder(ElInput, 'input'), InputNumber: withDefaultPlaceholder(ElInputNumber, 'input'), - RadioGroup: ElRadioGroup, - Select: withDefaultPlaceholder(ElSelect, 'select'), + RadioGroup: (props, { attrs, slots }) => { + let defaultSlot; + if (Reflect.has(slots, 'default')) { + defaultSlot = slots.default; + } else { + const { options } = attrs; + if (Array.isArray(options)) { + defaultSlot = () => + options.map((option) => + h(attrs.isButton ? ElRadioButton : ElRadio, option), + ); + } + } + return h( + ElRadioGroup, + { ...props, ...attrs }, + { ...slots, default: defaultSlot }, + ); + }, + Select: (props, { attrs, slots }) => { + return h(ElSelectV2, { ...props, attrs }, slots); + }, Space: ElSpace, Switch: ElSwitch, - TimePicker: ElTimePicker, - DatePicker: ElDatePicker, + TimePicker: (props, { attrs, slots }) => { + const { name, id, isRange } = props; + const extraProps: Recordable = {}; + if (isRange) { + if (name && !Array.isArray(name)) { + extraProps.name = [name, `${name}_end`]; + } + if (id && !Array.isArray(id)) { + extraProps.id = [id, `${id}_end`]; + } + } + return h( + ElTimePicker, + { + ...props, + ...attrs, + ...extraProps, + }, + slots, + ); + }, + DatePicker: (props, { attrs, slots }) => { + const { name, id, type } = props; + const extraProps: Recordable = {}; + if (type && type.includes('range')) { + if (name && !Array.isArray(name)) { + extraProps.name = [name, `${name}_end`]; + } + if (id && !Array.isArray(id)) { + extraProps.id = [id, `${id}_end`]; + } + } + return h( + ElDatePicker, + { + ...props, + ...attrs, + ...extraProps, + }, + slots, + ); + }, TreeSelect: withDefaultPlaceholder(ElTreeSelect, 'select'), Upload: ElUpload, }; diff --git a/apps/vben5/apps/web-ele/src/adapter/form.ts b/apps/vben5/apps/web-ele/src/adapter/form.ts index 1b6e04719..13ae9c428 100644 --- a/apps/vben5/apps/web-ele/src/adapter/form.ts +++ b/apps/vben5/apps/web-ele/src/adapter/form.ts @@ -12,6 +12,7 @@ setupVbenForm({ config: { modelPropNameMap: { Upload: 'fileList', + CheckboxGroup: 'model-value', }, }, defineRules: { diff --git a/apps/vben5/apps/web-ele/src/bootstrap.ts b/apps/vben5/apps/web-ele/src/bootstrap.ts index de1884730..ad1dce0f9 100644 --- a/apps/vben5/apps/web-ele/src/bootstrap.ts +++ b/apps/vben5/apps/web-ele/src/bootstrap.ts @@ -7,6 +7,7 @@ import '@vben/styles'; import '@vben/styles/ele'; import { useTitle } from '@vueuse/core'; +import { ElLoading } from 'element-plus'; import { $t, setupI18n } from '#/locales'; @@ -19,6 +20,9 @@ async function bootstrap(namespace: string) { await initComponentAdapter(); const app = createApp(App); + // 注册Element Plus提供的v-loading指令 + app.directive('loading', ElLoading.directive); + // 国际化 i18n 配置 await setupI18n(app); diff --git a/apps/vben5/apps/web-ele/src/locales/langs/en-US/demos.json b/apps/vben5/apps/web-ele/src/locales/langs/en-US/demos.json index 056da0dae..6eddebb53 100644 --- a/apps/vben5/apps/web-ele/src/locales/langs/en-US/demos.json +++ b/apps/vben5/apps/web-ele/src/locales/langs/en-US/demos.json @@ -1,6 +1,7 @@ { "title": "Demos", "elementPlus": "Element Plus", + "form": "Form", "vben": { "title": "Project", "about": "About", diff --git a/apps/vben5/apps/web-ele/src/locales/langs/zh-CN/demos.json b/apps/vben5/apps/web-ele/src/locales/langs/zh-CN/demos.json index 0620e16a1..ba6d6ccd0 100644 --- a/apps/vben5/apps/web-ele/src/locales/langs/zh-CN/demos.json +++ b/apps/vben5/apps/web-ele/src/locales/langs/zh-CN/demos.json @@ -1,6 +1,7 @@ { "title": "演示", "elementPlus": "Element Plus", + "form": "表单演示", "vben": { "title": "项目", "about": "关于", diff --git a/apps/vben5/apps/web-ele/src/router/guard.ts b/apps/vben5/apps/web-ele/src/router/guard.ts index fce5a892c..cbb5235ec 100644 --- a/apps/vben5/apps/web-ele/src/router/guard.ts +++ b/apps/vben5/apps/web-ele/src/router/guard.ts @@ -54,7 +54,9 @@ function setupAccessGuard(router: Router) { if (coreRouteNames.includes(to.name as string)) { if (to.path === LOGIN_PATH && accessStore.accessToken) { return decodeURIComponent( - (to.query?.redirect as string) || DEFAULT_HOME_PATH, + (to.query?.redirect as string) || + userStore.userInfo?.homePath || + DEFAULT_HOME_PATH, ); } return true; @@ -72,7 +74,10 @@ function setupAccessGuard(router: Router) { return { path: LOGIN_PATH, // 如不需要,直接删除 query - query: { redirect: encodeURIComponent(to.fullPath) }, + query: + to.fullPath === DEFAULT_HOME_PATH + ? {} + : { redirect: encodeURIComponent(to.fullPath) }, // 携带当前跳转的页面,登录后重新跳转该页面 replace: true, }; @@ -102,7 +107,10 @@ function setupAccessGuard(router: Router) { accessStore.setAccessMenus(accessibleMenus); accessStore.setAccessRoutes(accessibleRoutes); accessStore.setIsAccessChecked(true); - const redirectPath = (from.query.redirect ?? to.fullPath) as string; + const redirectPath = (from.query.redirect ?? + (to.path === DEFAULT_HOME_PATH + ? userInfo.homePath || DEFAULT_HOME_PATH + : to.fullPath)) as string; return { ...router.resolve(decodeURIComponent(redirectPath)), diff --git a/apps/vben5/apps/web-ele/src/router/routes/modules/demos.ts b/apps/vben5/apps/web-ele/src/router/routes/modules/demos.ts index 223efcf9d..90cc2f11c 100644 --- a/apps/vben5/apps/web-ele/src/router/routes/modules/demos.ts +++ b/apps/vben5/apps/web-ele/src/router/routes/modules/demos.ts @@ -23,6 +23,14 @@ const routes: RouteRecordRaw[] = [ path: '/demos/element', component: () => import('#/views/demos/element/index.vue'), }, + { + meta: { + title: $t('demos.form'), + }, + name: 'BasicForm', + path: '/demos/form', + component: () => import('#/views/demos/form/basic.vue'), + }, ], }, ]; diff --git a/apps/vben5/apps/web-ele/src/views/_core/authentication/code-login.vue b/apps/vben5/apps/web-ele/src/views/_core/authentication/code-login.vue index 556b273af..acfd1fd78 100644 --- a/apps/vben5/apps/web-ele/src/views/_core/authentication/code-login.vue +++ b/apps/vben5/apps/web-ele/src/views/_core/authentication/code-login.vue @@ -10,6 +10,7 @@ import { $t } from '@vben/locales'; defineOptions({ name: 'CodeLogin' }); const loading = ref(false); +const CODE_LENGTH = 6; const formSchema = computed((): VbenFormSchema[] => { return [ @@ -30,6 +31,7 @@ const formSchema = computed((): VbenFormSchema[] => { { component: 'VbenPinInput', componentProps: { + codeLength: CODE_LENGTH, createText: (countdown: number) => { const text = countdown > 0 @@ -41,7 +43,9 @@ const formSchema = computed((): VbenFormSchema[] => { }, fieldName: 'code', label: $t('authentication.code'), - rules: z.string().min(1, { message: $t('authentication.codeTip') }), + rules: z.string().length(CODE_LENGTH, { + message: $t('authentication.codeTip', [CODE_LENGTH]), + }), }, ]; }); diff --git a/apps/vben5/apps/web-ele/src/views/demos/element/index.vue b/apps/vben5/apps/web-ele/src/views/demos/element/index.vue index 55bc25713..0a7012d63 100644 --- a/apps/vben5/apps/web-ele/src/views/demos/element/index.vue +++ b/apps/vben5/apps/web-ele/src/views/demos/element/index.vue @@ -61,49 +61,57 @@ const segmentedOptions = ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun']; description="支持多语言,主题功能集成切换等" title="Element Plus组件使用演示" > - - - - Text - Default - Primary - Info - Success - Warning - Error - - - - - - 信息 - 错误 - 警告 - 成功 - - - - - - 信息 - 错误 - 警告 - 成功 - - - - - - - - - - - - +
+ + + + Text + Default + Primary + Info + Success + Warning + Error + + + + + + 信息 + 错误 + 警告 + 成功 + + + + + + 信息 + 错误 + 警告 + 成功 + + + + + + + + +
+ 一些演示的内容 +
+
+ + + + + + +
diff --git a/apps/vben5/apps/web-ele/src/views/demos/form/basic.vue b/apps/vben5/apps/web-ele/src/views/demos/form/basic.vue new file mode 100644 index 000000000..771665a66 --- /dev/null +++ b/apps/vben5/apps/web-ele/src/views/demos/form/basic.vue @@ -0,0 +1,181 @@ + + diff --git a/apps/vben5/apps/web-naive/package.json b/apps/vben5/apps/web-naive/package.json index 0710c3415..5bd3b1a74 100644 --- a/apps/vben5/apps/web-naive/package.json +++ b/apps/vben5/apps/web-naive/package.json @@ -1,6 +1,6 @@ { "name": "@vben/web-naive", - "version": "5.4.8", + "version": "5.5.1", "homepage": "https://vben.pro", "bugs": "https://github.com/vbenjs/vue-vben-admin/issues", "repository": { diff --git a/apps/vben5/apps/web-naive/src/adapter/component/index.ts b/apps/vben5/apps/web-naive/src/adapter/component/index.ts index c5dffddb8..545a61994 100644 --- a/apps/vben5/apps/web-naive/src/adapter/component/index.ts +++ b/apps/vben5/apps/web-naive/src/adapter/component/index.ts @@ -8,7 +8,7 @@ import type { BaseFormComponentType } from '@vben/common-ui'; import type { Component, SetupContext } from 'vue'; import { h } from 'vue'; -import { globalShareState } from '@vben/common-ui'; +import { ApiComponent, globalShareState, IconPicker } from '@vben/common-ui'; import { $t } from '@vben/locales'; import { @@ -19,6 +19,8 @@ import { NDivider, NInput, NInputNumber, + NRadio, + NRadioButton, NRadioGroup, NSelect, NSpace, @@ -42,10 +44,13 @@ const withDefaultPlaceholder = ( // 这里需要自行根据业务组件库进行适配,需要用到的组件都需要在这里类型说明 export type ComponentType = + | 'ApiSelect' + | 'ApiTreeSelect' | 'Checkbox' | 'CheckboxGroup' | 'DatePicker' | 'Divider' + | 'IconPicker' | 'Input' | 'InputNumber' | 'RadioGroup' @@ -63,8 +68,54 @@ async function initComponentAdapter() { // Button: () => // import('xxx').then((res) => res.Button), + ApiSelect: (props, { attrs, slots }) => { + return h( + ApiComponent, + { + placeholder: $t('ui.placeholder.select'), + ...props, + ...attrs, + component: NSelect, + modelPropName: 'value', + }, + slots, + ); + }, + ApiTreeSelect: (props, { attrs, slots }) => { + return h( + ApiComponent, + { + placeholder: $t('ui.placeholder.select'), + ...props, + ...attrs, + component: NTreeSelect, + nodeKey: 'value', + loadingSlot: 'arrow', + keyField: 'value', + modelPropName: 'value', + optionsPropName: 'options', + visibleEvent: 'onVisibleChange', + }, + slots, + ); + }, Checkbox: NCheckbox, - CheckboxGroup: NCheckboxGroup, + CheckboxGroup: (props, { attrs, slots }) => { + let defaultSlot; + if (Reflect.has(slots, 'default')) { + defaultSlot = slots.default; + } else { + const { options } = attrs; + if (Array.isArray(options)) { + defaultSlot = () => options.map((option) => h(NCheckbox, option)); + } + } + return h( + NCheckboxGroup, + { ...props, ...attrs }, + { default: defaultSlot }, + ); + }, DatePicker: NDatePicker, // 自定义默认按钮 DefaultButton: (props, { attrs, slots }) => { @@ -75,9 +126,37 @@ async function initComponentAdapter() { return h(NButton, { ...props, attrs, type: 'primary' }, slots); }, Divider: NDivider, + IconPicker: (props, { attrs, slots }) => { + return h( + IconPicker, + { iconSlot: 'suffix', inputComponent: NInput, ...props, ...attrs }, + slots, + ); + }, Input: withDefaultPlaceholder(NInput, 'input'), InputNumber: withDefaultPlaceholder(NInputNumber, 'input'), - RadioGroup: NRadioGroup, + RadioGroup: (props, { attrs, slots }) => { + let defaultSlot; + if (Reflect.has(slots, 'default')) { + defaultSlot = slots.default; + } else { + const { options } = attrs; + if (Array.isArray(options)) { + defaultSlot = () => + options.map((option) => + h(attrs.isButton ? NRadioButton : NRadio, option), + ); + } + } + const groupRender = h( + NRadioGroup, + { ...props, ...attrs }, + { default: defaultSlot }, + ); + return attrs.isButton + ? h(NSpace, { vertical: true }, () => groupRender) + : groupRender; + }, Select: withDefaultPlaceholder(NSelect, 'select'), Space: NSpace, Switch: NSwitch, diff --git a/apps/vben5/apps/web-naive/src/adapter/form.ts b/apps/vben5/apps/web-naive/src/adapter/form.ts index 2c3cee875..2f2ed2abe 100644 --- a/apps/vben5/apps/web-naive/src/adapter/form.ts +++ b/apps/vben5/apps/web-naive/src/adapter/form.ts @@ -10,8 +10,6 @@ import { $t } from '@vben/locales'; setupVbenForm({ config: { - // naive-ui组件不接受onChang事件,所以需要禁用 - disabledOnChangeListener: true, // naive-ui组件的空值为null,不能是undefined,否则重置表单时不生效 emptyStateValue: null, baseModelPropName: 'value', diff --git a/apps/vben5/apps/web-naive/src/locales/langs/en-US/demos.json b/apps/vben5/apps/web-naive/src/locales/langs/en-US/demos.json index 9fdffc765..839fc2e68 100644 --- a/apps/vben5/apps/web-naive/src/locales/langs/en-US/demos.json +++ b/apps/vben5/apps/web-naive/src/locales/langs/en-US/demos.json @@ -2,6 +2,7 @@ "title": "Demos", "naive": "Naive UI", "table": "Table", + "form": "Form", "vben": { "title": "Project", "about": "About", diff --git a/apps/vben5/apps/web-naive/src/locales/langs/zh-CN/demos.json b/apps/vben5/apps/web-naive/src/locales/langs/zh-CN/demos.json index 79b54fa21..e0d7e616d 100644 --- a/apps/vben5/apps/web-naive/src/locales/langs/zh-CN/demos.json +++ b/apps/vben5/apps/web-naive/src/locales/langs/zh-CN/demos.json @@ -2,6 +2,7 @@ "title": "演示", "naive": "Naive UI", "table": "Table", + "form": "表单", "vben": { "title": "项目", "about": "关于", diff --git a/apps/vben5/apps/web-naive/src/router/guard.ts b/apps/vben5/apps/web-naive/src/router/guard.ts index c95d994ec..281ea31a6 100644 --- a/apps/vben5/apps/web-naive/src/router/guard.ts +++ b/apps/vben5/apps/web-naive/src/router/guard.ts @@ -54,7 +54,9 @@ function setupAccessGuard(router: Router) { if (coreRouteNames.includes(to.name as string)) { if (to.path === LOGIN_PATH && accessStore.accessToken) { return decodeURIComponent( - (to.query?.redirect as string) || DEFAULT_HOME_PATH, + (to.query?.redirect as string) || + userStore.userInfo?.homePath || + DEFAULT_HOME_PATH, ); } return true; @@ -72,7 +74,10 @@ function setupAccessGuard(router: Router) { return { path: LOGIN_PATH, // 如不需要,直接删除 query - query: { redirect: encodeURIComponent(to.fullPath) }, + query: + to.fullPath === DEFAULT_HOME_PATH + ? {} + : { redirect: encodeURIComponent(to.fullPath) }, // 携带当前跳转的页面,登录后重新跳转该页面 replace: true, }; @@ -101,7 +106,10 @@ function setupAccessGuard(router: Router) { accessStore.setAccessMenus(accessibleMenus); accessStore.setAccessRoutes(accessibleRoutes); accessStore.setIsAccessChecked(true); - const redirectPath = (from.query.redirect ?? to.fullPath) as string; + const redirectPath = (from.query.redirect ?? + (to.path === DEFAULT_HOME_PATH + ? userInfo.homePath || DEFAULT_HOME_PATH + : to.fullPath)) as string; return { ...router.resolve(decodeURIComponent(redirectPath)), diff --git a/apps/vben5/apps/web-naive/src/router/routes/modules/demos.ts b/apps/vben5/apps/web-naive/src/router/routes/modules/demos.ts index cf5658800..d0631cb53 100644 --- a/apps/vben5/apps/web-naive/src/router/routes/modules/demos.ts +++ b/apps/vben5/apps/web-naive/src/router/routes/modules/demos.ts @@ -31,6 +31,14 @@ const routes: RouteRecordRaw[] = [ path: '/demos/table', component: () => import('#/views/demos/table/index.vue'), }, + { + meta: { + title: $t('demos.form'), + }, + name: 'Form', + path: '/demos/form', + component: () => import('#/views/demos/form/basic.vue'), + }, ], }, ]; diff --git a/apps/vben5/apps/web-naive/src/views/_core/authentication/code-login.vue b/apps/vben5/apps/web-naive/src/views/_core/authentication/code-login.vue index 556b273af..acfd1fd78 100644 --- a/apps/vben5/apps/web-naive/src/views/_core/authentication/code-login.vue +++ b/apps/vben5/apps/web-naive/src/views/_core/authentication/code-login.vue @@ -10,6 +10,7 @@ import { $t } from '@vben/locales'; defineOptions({ name: 'CodeLogin' }); const loading = ref(false); +const CODE_LENGTH = 6; const formSchema = computed((): VbenFormSchema[] => { return [ @@ -30,6 +31,7 @@ const formSchema = computed((): VbenFormSchema[] => { { component: 'VbenPinInput', componentProps: { + codeLength: CODE_LENGTH, createText: (countdown: number) => { const text = countdown > 0 @@ -41,7 +43,9 @@ const formSchema = computed((): VbenFormSchema[] => { }, fieldName: 'code', label: $t('authentication.code'), - rules: z.string().min(1, { message: $t('authentication.codeTip') }), + rules: z.string().length(CODE_LENGTH, { + message: $t('authentication.codeTip', [CODE_LENGTH]), + }), }, ]; }); diff --git a/apps/vben5/apps/web-naive/src/views/demos/form/basic.vue b/apps/vben5/apps/web-naive/src/views/demos/form/basic.vue new file mode 100644 index 000000000..7d04ff4d9 --- /dev/null +++ b/apps/vben5/apps/web-naive/src/views/demos/form/basic.vue @@ -0,0 +1,143 @@ + + diff --git a/apps/vben5/docs/.vitepress/config/zh.mts b/apps/vben5/docs/.vitepress/config/zh.mts index 6b31658ed..dff8ac280 100644 --- a/apps/vben5/docs/.vitepress/config/zh.mts +++ b/apps/vben5/docs/.vitepress/config/zh.mts @@ -148,10 +148,24 @@ function sidebarComponents(): DefaultTheme.SidebarItem[] { }, ], }, + { + collapsed: false, + text: '布局组件', + items: [ + { + link: 'layout-ui/page', + text: 'Page 页面', + }, + ], + }, { collapsed: false, text: '通用组件', items: [ + { + link: 'common-ui/vben-api-component', + text: 'ApiComponent Api组件包装器', + }, { link: 'common-ui/vben-modal', text: 'Modal 模态框', @@ -172,6 +186,10 @@ function sidebarComponents(): DefaultTheme.SidebarItem[] { link: 'common-ui/vben-count-to-animator', text: 'CountToAnimator 数字动画', }, + { + link: 'common-ui/vben-ellipsis-text', + text: 'EllipsisText 省略文本', + }, ], }, ]; diff --git a/apps/vben5/docs/package.json b/apps/vben5/docs/package.json index 15a87bb97..2d56a8ba5 100644 --- a/apps/vben5/docs/package.json +++ b/apps/vben5/docs/package.json @@ -1,6 +1,6 @@ { "name": "@vben/docs", - "version": "5.4.8", + "version": "5.5.1", "private": true, "scripts": { "build": "vitepress build", diff --git a/apps/vben5/docs/src/_env/adapter/form.ts b/apps/vben5/docs/src/_env/adapter/form.ts index 67e2483e5..d8b51c254 100644 --- a/apps/vben5/docs/src/_env/adapter/form.ts +++ b/apps/vben5/docs/src/_env/adapter/form.ts @@ -14,8 +14,6 @@ initComponentAdapter(); setupVbenForm({ config: { baseModelPropName: 'value', - // naive-ui组件不接受onChang事件,所以需要禁用 - disabledOnChangeListener: true, // naive-ui组件的空值为null,不能是undefined,否则重置表单时不生效 emptyStateValue: null, modelPropNameMap: { diff --git a/apps/vben5/docs/src/components/common-ui/vben-api-component.md b/apps/vben5/docs/src/components/common-ui/vben-api-component.md new file mode 100644 index 000000000..f275adfc2 --- /dev/null +++ b/apps/vben5/docs/src/components/common-ui/vben-api-component.md @@ -0,0 +1,152 @@ +--- +outline: deep +--- + +# Vben ApiComponent Api组件包装器 + +框架提供的API“包装器”,它一般不独立使用,主要用于包装其它组件,为目标组件提供自动获取远程数据的能力,但仍然保持了目标组件的原始用法。 + +::: info 写在前面 + +我们在各个应用的组件适配器中,使用ApiComponent包装了Select、TreeSelect组件,使得这些组件可以自动获取远程数据并生成选项。其它类似的组件(比如Cascader)如有需要也可以参考示例代码自行进行包装。 + +::: + +## 基础用法 + +通过 `component` 传入其它组件的定义,并配置相关的其它属性(主要是一些名称映射)。包装组件将通过`api`获取数据(`beforerFetch`、`afterFetch`将分别在`api`运行前、运行后被调用),使用`resultField`从中提取数组,使用`valueField`、`labelField`等来从数据中提取value和label(如果提供了`childrenField`,会将其作为树形结构递归处理每一级数据),之后将处理好的数据通过`optionsPropName`指定的属性传递给目标组件。 + +::: details 包装级联选择器,点击下拉时开始加载远程数据 + +```vue + + +``` + +::: + +## API + +### Props + +| 属性名 | 描述 | 类型 | 默认值 | +| --- | --- | --- | --- | +| component | 欲包装的组件 | `Component` | - | +| numberToString | 是否将value从数字转为string | `boolean` | `false` | +| api | 获取数据的函数 | `(arg?: any) => Promise>` | - | +| params | 传递给api的参数 | `Record` | - | +| resultField | 从api返回的结果中提取options数组的字段名 | `string` | - | +| labelField | label字段名 | `string` | `label` | +| childrenField | 子级数据字段名,需要层级数据的组件可用 | `string` | `` | +| valueField | value字段名 | `string` | `value` | +| optionsPropName | 组件接收options数据的属性名称 | `string` | `options` | +| modelPropName | 组件的双向绑定属性名,默认为modelValue。部分组件可能为value | `string` | `modelValue` | +| immediate | 是否立即调用api | `boolean` | `true` | +| alwaysLoad | 每次`visibleEvent`事件发生时都重新请求数据 | `boolean` | `false` | +| beforeFetch | 在api请求之前的回调函数 | `AnyPromiseFunction` | - | +| afterFetch | 在api请求之后的回调函数 | `AnyPromiseFunction` | - | +| options | 直接传入选项数据,也作为api返回空数据时的后备数据 | `OptionsItem[]` | - | +| visibleEvent | 触发重新请求数据的事件名 | `string` | - | +| loadingSlot | 组件的插槽名称,用来显示一个"加载中"的图标 | `string` | - | + +``` + +``` diff --git a/apps/vben5/docs/src/components/common-ui/vben-drawer.md b/apps/vben5/docs/src/components/common-ui/vben-drawer.md index 939593fa9..7091570f1 100644 --- a/apps/vben5/docs/src/components/common-ui/vben-drawer.md +++ b/apps/vben5/docs/src/components/common-ui/vben-drawer.md @@ -74,6 +74,7 @@ const [Drawer, drawerApi] = useVbenDrawer({ | 属性名 | 描述 | 类型 | 默认值 | | --- | --- | --- | --- | +| appendToMain | 是否挂载到内容区域(默认挂载到body) | `boolean` | `false` | | title | 标题 | `string\|slot` | - | | titleTooltip | 标题提示信息 | `string\|slot` | - | | description | 描述信息 | `string\|slot` | - | @@ -95,6 +96,13 @@ const [Drawer, drawerApi] = useVbenDrawer({ | contentClass | modal内容区域的class | `string` | - | | footerClass | modal底部区域的class | `string` | - | | headerClass | modal顶部区域的class | `string` | - | +| zIndex | 抽屉的ZIndex层级 | `number` | `1000` | + +::: info appendToMain + +`appendToMain`可以指定将抽屉挂载到内容区域,打开抽屉时,内容区域以外的部分(标签栏、导航菜单等等)不会被遮挡。默认情况下,抽屉会挂载到body上。但是:挂载到内容区域时,作为页面根容器的`Page`组件,需要设置`auto-content-height`属性,以便抽屉能够正确计算高度。 + +::: ### Event diff --git a/apps/vben5/docs/src/components/common-ui/vben-ellipsis-text.md b/apps/vben5/docs/src/components/common-ui/vben-ellipsis-text.md new file mode 100644 index 000000000..109f1161c --- /dev/null +++ b/apps/vben5/docs/src/components/common-ui/vben-ellipsis-text.md @@ -0,0 +1,56 @@ +--- +outline: deep +--- + +# Vben EllipsisText 省略文本 + +框架提供的文本展示组件,可配置超长省略、tooltip提示、展开收起等功能。 + +> 如果文档内没有参数说明,可以尝试在在线示例内寻找 + +## 基础用法 + +通过默认插槽设置文本内容,`maxWidth`属性设置最大宽度。 + + + +## 可折叠的文本块 + +通过`line`设置折叠后的行数,`expand`属性设置是否支持展开收起。 + + + +## 自定义提示浮层 + +通过名为`tooltip`的插槽定制提示信息。 + + + +## API + +### Props + +| 属性名 | 描述 | 类型 | 默认值 | +| --- | --- | --- | --- | +| expand | 支持点击展开或收起 | `boolean` | `false` | +| line | 文本最大行数 | `number` | `1` | +| maxWidth | 文本区域最大宽度 | `number \| string` | `'100%'` | +| placement | 提示浮层的位置 | `'bottom'\|'left'\|'right'\|'top'` | `'top'` | +| tooltip | 启用文本提示 | `boolean` | `true` | +| tooltipBackgroundColor | 提示文本的背景颜色 | `string` | - | +| tooltipColor | 提示文本的颜色 | `string` | - | +| tooltipFontSize | 提示文本的大小 | `string` | - | +| tooltipMaxWidth | 提示浮层的最大宽度。如不设置则保持与文本宽度一致 | `number` | - | +| tooltipOverlayStyle | 提示框内容区域样式 | `CSSProperties` | `{ textAlign: 'justify' }` | + +### Events + +| 事件名 | 描述 | 类型 | +| ------------ | ------------ | -------------------------- | +| expandChange | 展开状态改变 | `(isExpand:boolean)=>void` | + +### Slots + +| 插槽名 | 描述 | +| ------- | -------------------------------- | +| tooltip | 启用文本提示时,用来定制提示内容 | diff --git a/apps/vben5/docs/src/components/common-ui/vben-form.md b/apps/vben5/docs/src/components/common-ui/vben-form.md index 34e96adfd..8d27c08d5 100644 --- a/apps/vben5/docs/src/components/common-ui/vben-form.md +++ b/apps/vben5/docs/src/components/common-ui/vben-form.md @@ -87,7 +87,7 @@ import type { BaseFormComponentType } from '@vben/common-ui'; import type { Component, SetupContext } from 'vue'; import { h } from 'vue'; -import { globalShareState } from '@vben/common-ui'; +import { globalShareState, IconPicker } from '@vben/common-ui'; import { $t } from '@vben/locales'; import { @@ -149,6 +149,7 @@ export type ComponentType = | 'TimePicker' | 'TreeSelect' | 'Upload' + | 'IconPicker'; | BaseFormComponentType; async function initComponentAdapter() { @@ -166,6 +167,7 @@ async function initComponentAdapter() { return h(Button, { ...props, attrs, type: 'default' }, slots); }, Divider, + IconPicker, Input: withDefaultPlaceholder(Input, 'input'), InputNumber: withDefaultPlaceholder(InputNumber, 'input'), InputPassword: withDefaultPlaceholder(InputPassword, 'input'), @@ -285,6 +287,8 @@ useVbenForm 返回的第二个参数,是一个对象,包含了一些表单 | setValues | 设置表单值, 默认会过滤不在schema中定义的field, 可通过filterFields形参关闭过滤 | `(fields: Record, filterFields?: boolean, shouldValidate?: boolean) => Promise` | | getValues | 获取表单值 | `(fields:Record,shouldValidate: boolean = false)=>Promise` | | validate | 表单校验 | `()=>Promise` | +| validateField | 校验指定字段 | `(fieldName: string)=>Promise>` | +| isFieldValid | 检查某个字段是否已通过校验 | `(fieldName: string)=>Promise` | | resetValidate | 重置表单校验 | `()=>Promise` | | updateSchema | 更新formSchema | `(schema:FormSchema[])=>void` | | setFieldValue | 设置字段值 | `(field: string, value: any, shouldValidate?: boolean)=>Promise` | @@ -304,16 +308,19 @@ useVbenForm 返回的第二个参数,是一个对象,包含了一些表单 | actionWrapperClass | 表单操作区域class | `any` | - | | handleReset | 表单重置回调 | `(values: Record,) => Promise \| void` | - | | handleSubmit | 表单提交回调 | `(values: Record,) => Promise \| void` | - | +| handleValuesChange | 表单值变化回调 | `(values: Record,) => void` | - | +| actionButtonsReverse | 调换操作按钮位置 | `boolean` | `false` | | resetButtonOptions | 重置按钮组件参数 | `ActionButtonOptions` | - | | submitButtonOptions | 提交按钮组件参数 | `ActionButtonOptions` | - | | showDefaultActions | 是否显示默认操作按钮 | `boolean` | `true` | -| collapsed | 是否折叠,在`是否展开,在showCollapseButton=true`时生效 | `boolean` | `false` | +| collapsed | 是否折叠,在`showCollapseButton`为`true`时生效 | `boolean` | `false` | | collapseTriggerResize | 折叠时,触发`resize`事件 | `boolean` | `false` | | collapsedRows | 折叠时保持的行数 | `number` | `1` | -| fieldMappingTime | 用于将表单内时间区域的应设成 2 个字段 | `[string, [string, string], string?][]` | - | +| fieldMappingTime | 用于将表单内时间区域组件的数组值映射成 2 个字段 | `[string, [string, string], string?][]` | - | | commonConfig | 表单项的通用配置,每个配置都会传递到每个表单项,表单项可覆盖 | `FormCommonConfig` | - | -| schema | 表单项的每一项配置 | `FormSchema` | - | +| schema | 表单项的每一项配置 | `FormSchema[]` | - | | submitOnEnter | 按下回车健时提交表单 | `boolean` | false | +| submitOnChange | 字段值改变时提交表单(内部防抖,这个属性一般用于表格的搜索表单) | `boolean` | false | ### TS 类型说明 @@ -350,10 +357,21 @@ export interface FormCommonConfig { * 所有表单项的props */ componentProps?: ComponentProps; + /** + * 是否紧凑模式(移除表单底部为显示校验错误信息所预留的空间)。 + * 在有设置校验规则的场景下,建议不要将其设置为true + * 默认为false。但用作表格的搜索表单时,默认为true + * @default false + */ + compact?: boolean; /** * 所有表单项的控件样式 */ controlClass?: string; + /** + * 在表单项的Label后显示一个冒号 + */ + colon?: boolean; /** * 所有表单项的禁用状态 * @default false @@ -413,7 +431,7 @@ export interface FormSchema< dependencies?: FormItemDependencies; /** 描述 */ description?: string; - /** 字段名 */ + /** 字段名,也作为自定义插槽的名称 */ fieldName: string; /** 帮助信息 */ help?: string; @@ -436,7 +454,7 @@ export interface FormSchema< ```ts dependencies: { - // 只有当 name 字段的值变化时,才会触发联动 + // 触发字段。只有这些字段值变动时,联动才会触发 triggerFields: ['name'], // 动态判断当前字段是否需要显示,不显示则直接销毁 if(values,formApi){}, @@ -457,11 +475,11 @@ dependencies: { ### 表单校验 -表单联动需要通过 schema 内的 `rules` 属性进行配置。 +表单校验需要通过 schema 内的 `rules` 属性进行配置。 -rules的值可以是一个字符串,也可以是一个zod的schema。 +rules的值可以是字符串(预定义的校验规则名称),也可以是一个zod的schema。 -#### 字符串 +#### 预定义的校验规则 ```ts // 表示字段必填,默认会根据适配器的required进行国际化 @@ -487,11 +505,16 @@ import { z } from '#/adapter/form'; rules: z.string().min(1, { message: '请输入字符串' }); } -// 可选,并且携带默认值 +// 可选(可以是undefined),并且携带默认值。注意zod的optional不包括空字符串'' { rules: z.string().default('默认值').optional(), } +// 可以是空字符串、undefined或者一个邮箱地址 +{ + rules: z.union(z.string().email().optional(), z.literal("")) +} + // 复杂校验 { z.string().min(1, { message: "请输入" }) diff --git a/apps/vben5/docs/src/components/common-ui/vben-modal.md b/apps/vben5/docs/src/components/common-ui/vben-modal.md index 75f620ae5..d6e3ef47f 100644 --- a/apps/vben5/docs/src/components/common-ui/vben-modal.md +++ b/apps/vben5/docs/src/components/common-ui/vben-modal.md @@ -80,6 +80,7 @@ const [Modal, modalApi] = useVbenModal({ | 属性名 | 描述 | 类型 | 默认值 | | --- | --- | --- | --- | +| appendToMain | 是否挂载到内容区域(默认挂载到body) | `boolean` | `false` | | title | 标题 | `string\|slot` | - | | titleTooltip | 标题提示信息 | `string\|slot` | - | | description | 描述信息 | `string\|slot` | - | @@ -106,6 +107,13 @@ const [Modal, modalApi] = useVbenModal({ | footerClass | modal底部区域的class | `string` | - | | headerClass | modal顶部区域的class | `string` | - | | bordered | 是否显示border | `boolean` | `false` | +| zIndex | 弹窗的ZIndex层级 | `number` | `1000` | + +::: info appendToMain + +`appendToMain`可以指定将弹窗挂载到内容区域,打开这种弹窗时,内容区域以外的部分(标签栏、导航菜单等等)不会被遮挡。默认情况下,弹窗会挂载到body上。但是:挂载到内容区域时,作为页面根容器的`Page`组件,需要设置`auto-content-height`属性,以便弹窗能够正确计算高度。 + +::: ### Event diff --git a/apps/vben5/docs/src/components/common-ui/vben-vxe-table.md b/apps/vben5/docs/src/components/common-ui/vben-vxe-table.md index 29f679f6a..24d911da7 100644 --- a/apps/vben5/docs/src/components/common-ui/vben-vxe-table.md +++ b/apps/vben5/docs/src/components/common-ui/vben-vxe-table.md @@ -165,6 +165,8 @@ vxeUI.renderer.add('CellLink', { **表单搜索** 部分采用了`Vben Form 表单`,参考 [Vben Form 表单文档](/components/common-ui/vben-form)。 +当启用了表单搜索时,可以在toolbarConfig中配置`search`为`true`来让表格在工具栏区域显示一个搜索表单控制按钮。 + ## 单元格编辑 @@ -215,14 +217,15 @@ const [Grid, gridApi] = useVbenVxeGrid({ useVbenVxeGrid 返回的第二个参数,是一个对象,包含了一些表单的方法。 -| 方法名 | 描述 | 类型 | -| --- | --- | --- | -| setLoading | 设置loading状态 | `(loading)=>void` | -| setGridOptions | 设置vxe-table grid组件参数 | `(options: Partialvoid` | -| reload | 重载表格,会进行初始化 | `(params:any)=>void` | -| query | 重载表格,会保留当前分页 | `(params:any)=>void` | -| grid | vxe-table grid实例 | `VxeGridInstance` | -| formApi | vbenForm api实例 | `FormApi` | +| 方法名 | 描述 | 类型 | 说明 | +| --- | --- | --- | --- | +| setLoading | 设置loading状态 | `(loading)=>void` | - | +| setGridOptions | 设置vxe-table grid组件参数 | `(options: Partialvoid` | - | +| reload | 重载表格,会进行初始化 | `(params:any)=>void` | - | +| query | 重载表格,会保留当前分页 | `(params:any)=>void` | - | +| grid | vxe-table grid实例 | `VxeGridInstance` | - | +| formApi | vbenForm api实例 | `FormApi` | - | +| toggleSearchForm | 设置搜索表单显示状态 | `(show?: boolean)=>boolean` | 当省略参数时,则将表单在显示和隐藏两种状态之间切换 | ## Props @@ -236,3 +239,4 @@ useVbenVxeGrid 返回的第二个参数,是一个对象,包含了一些表 | gridOptions | grid组件的参数 | `VxeTableGridProps` | | gridEvents | grid组件的触发的⌚️ | `VxeGridListeners` | | formOptions | 表单参数 | `VbenFormProps` | +| showSearchForm | 是否显示搜索表单 | `boolean` | diff --git a/apps/vben5/docs/src/components/introduction.md b/apps/vben5/docs/src/components/introduction.md index 039ec8cd8..438470e9a 100644 --- a/apps/vben5/docs/src/components/introduction.md +++ b/apps/vben5/docs/src/components/introduction.md @@ -6,6 +6,10 @@ ::: +## 布局组件 + +布局组件一般在页面内容区域用作顶层容器组件,提供一些统一的布局样式和基本功能。 + ## 通用组件 通用组件是一些常用的组件,比如弹窗、抽屉、表单等。大部分基于 `Tailwind CSS` 实现,可适用于不同 UI 组件库的应用。 diff --git a/apps/vben5/docs/src/components/layout-ui/page.md b/apps/vben5/docs/src/components/layout-ui/page.md new file mode 100644 index 000000000..29fbdd40f --- /dev/null +++ b/apps/vben5/docs/src/components/layout-ui/page.md @@ -0,0 +1,44 @@ +--- +outline: deep +--- + +# Page 常规页面组件 + +提供一个常规页面布局的组件,包括头部、内容区域、底部三个部分。 + +::: info 写在前面 + +本组件是一个基本布局组件。如果有更多的通用页面布局需求(比如双列布局等),可以根据实际需求自行封装。 + +::: + +## 基础用法 + +将`Page`作为你的业务页面的根组件即可。 + +### Props + +| 属性名 | 描述 | 类型 | 默认值 | 说明 | +| --- | --- | --- | --- | --- | +| title | 页面标题 | `string\|slot` | - | - | +| description | 页面描述(标题下的内容) | `string\|slot` | - | - | +| contentClass | 内容区域的class | `string` | - | - | +| headerClass | 头部区域的class | `string` | - | - | +| footerClass | 底部区域的class | `string` | - | - | +| autoContentHeight | 自动调整内容区域的高度 | `boolean` | `false` | - | + +::: tip 注意 + +如果`title`、`description`、`extra`三者均未提供有效内容(通过`props`或者`slots`均可),则页面头部区域不会渲染。 + +::: + +### Slots + +| 插槽名称 | 描述 | +| ----------- | ------------ | +| default | 页面内容 | +| title | 页面标题 | +| description | 页面描述 | +| extra | 页面头部右侧 | +| footer | 页面底部 | diff --git a/apps/vben5/docs/src/demos/vben-api-component/cascader/index.vue b/apps/vben5/docs/src/demos/vben-api-component/cascader/index.vue new file mode 100644 index 000000000..957964cd3 --- /dev/null +++ b/apps/vben5/docs/src/demos/vben-api-component/cascader/index.vue @@ -0,0 +1,100 @@ + + diff --git a/apps/vben5/docs/src/demos/vben-ellipsis-text/expand/index.vue b/apps/vben5/docs/src/demos/vben-ellipsis-text/expand/index.vue new file mode 100644 index 000000000..842f6b32e --- /dev/null +++ b/apps/vben5/docs/src/demos/vben-ellipsis-text/expand/index.vue @@ -0,0 +1,10 @@ + + diff --git a/apps/vben5/docs/src/demos/vben-ellipsis-text/line/index.vue b/apps/vben5/docs/src/demos/vben-ellipsis-text/line/index.vue new file mode 100644 index 000000000..dfbf20eff --- /dev/null +++ b/apps/vben5/docs/src/demos/vben-ellipsis-text/line/index.vue @@ -0,0 +1,10 @@ + + diff --git a/apps/vben5/docs/src/demos/vben-ellipsis-text/tooltip/index.vue b/apps/vben5/docs/src/demos/vben-ellipsis-text/tooltip/index.vue new file mode 100644 index 000000000..e6287a12f --- /dev/null +++ b/apps/vben5/docs/src/demos/vben-ellipsis-text/tooltip/index.vue @@ -0,0 +1,14 @@ + + diff --git a/apps/vben5/docs/src/demos/vben-vxe-table/form/index.vue b/apps/vben5/docs/src/demos/vben-vxe-table/form/index.vue index a5e8a547b..bcf3f5a5d 100644 --- a/apps/vben5/docs/src/demos/vben-vxe-table/form/index.vue +++ b/apps/vben5/docs/src/demos/vben-vxe-table/form/index.vue @@ -76,6 +76,8 @@ const formOptions: VbenFormProps = { submitButtonOptions: { content: '查询', }, + // 是否在字段值改变时提交表单 + submitOnChange: false, // 按下回车时是否提交表单 submitOnEnter: false, }; @@ -108,6 +110,11 @@ const gridOptions: VxeGridProps = { }, }, }, + toolbarConfig: { + // 是否显示搜索表单控制按钮 + // @ts-ignore 正式环境时有完整的类型声明 + search: true, + }, }; const [Grid] = useVbenVxeGrid({ formOptions, gridOptions }); diff --git a/apps/vben5/docs/src/en/guide/essentials/settings.md b/apps/vben5/docs/src/en/guide/essentials/settings.md index 68fef3e7e..a3cd579ec 100644 --- a/apps/vben5/docs/src/en/guide/essentials/settings.md +++ b/apps/vben5/docs/src/en/guide/essentials/settings.md @@ -217,6 +217,7 @@ const defaultPreferences: Preferences = { globalSearch: true, }, sidebar: { + autoActivateChild: false, collapsed: false, collapsedShowTitle: false, enable: true, diff --git a/apps/vben5/docs/src/guide/essentials/settings.md b/apps/vben5/docs/src/guide/essentials/settings.md index e33572066..3669a771c 100644 --- a/apps/vben5/docs/src/guide/essentials/settings.md +++ b/apps/vben5/docs/src/guide/essentials/settings.md @@ -240,6 +240,7 @@ const defaultPreferences: Preferences = { globalSearch: true, }, sidebar: { + autoActivateChild: false, collapsed: false, collapsedShowTitle: false, enable: true, diff --git a/apps/vben5/docs/src/guide/in-depth/ui-framework.md b/apps/vben5/docs/src/guide/in-depth/ui-framework.md index ce05608fc..6a7508e1d 100644 --- a/apps/vben5/docs/src/guide/in-depth/ui-framework.md +++ b/apps/vben5/docs/src/guide/in-depth/ui-framework.md @@ -4,7 +4,7 @@ ## 新增组件库应用 -如果你想用其他别的组件库,你只需要按一下步骤进行操作: +如果你想用其他别的组件库,你只需要按以下步骤进行操作: 1. 在`apps`内创建一个新的文件夹,例如`apps/web-xxx`。 2. 更改`apps/web-xxx/package.json`的`name`字段为`web-xxx`。 diff --git a/apps/vben5/internal/lint-configs/commitlint-config/package.json b/apps/vben5/internal/lint-configs/commitlint-config/package.json index 6679a6627..01b43088c 100644 --- a/apps/vben5/internal/lint-configs/commitlint-config/package.json +++ b/apps/vben5/internal/lint-configs/commitlint-config/package.json @@ -1,6 +1,6 @@ { "name": "@vben/commitlint-config", - "version": "5.4.8", + "version": "5.5.1", "private": true, "homepage": "https://github.com/vbenjs/vue-vben-admin", "bugs": "https://github.com/vbenjs/vue-vben-admin/issues", diff --git a/apps/vben5/internal/lint-configs/eslint-config/src/configs/vue.ts b/apps/vben5/internal/lint-configs/eslint-config/src/configs/vue.ts index 27cc3cf29..d1c6521cf 100644 --- a/apps/vben5/internal/lint-configs/eslint-config/src/configs/vue.ts +++ b/apps/vben5/internal/lint-configs/eslint-config/src/configs/vue.ts @@ -4,7 +4,6 @@ import { interopDefault } from '../util'; export async function vue(): Promise { const [pluginVue, parserVue, parserTs] = await Promise.all([ - // @ts-expect-error missing types interopDefault(import('eslint-plugin-vue')), interopDefault(import('vue-eslint-parser')), // @ts-expect-error missing types diff --git a/apps/vben5/internal/lint-configs/stylelint-config/package.json b/apps/vben5/internal/lint-configs/stylelint-config/package.json index c9e8cf539..64815741f 100644 --- a/apps/vben5/internal/lint-configs/stylelint-config/package.json +++ b/apps/vben5/internal/lint-configs/stylelint-config/package.json @@ -1,6 +1,6 @@ { "name": "@vben/stylelint-config", - "version": "5.4.8", + "version": "5.5.1", "private": true, "homepage": "https://github.com/vbenjs/vue-vben-admin", "bugs": "https://github.com/vbenjs/vue-vben-admin/issues", diff --git a/apps/vben5/internal/node-utils/package.json b/apps/vben5/internal/node-utils/package.json index 0cd1eaa3d..9412656a9 100644 --- a/apps/vben5/internal/node-utils/package.json +++ b/apps/vben5/internal/node-utils/package.json @@ -1,6 +1,6 @@ { "name": "@vben/node-utils", - "version": "5.4.8", + "version": "5.5.1", "private": true, "homepage": "https://github.com/vbenjs/vue-vben-admin", "bugs": "https://github.com/vbenjs/vue-vben-admin/issues", diff --git a/apps/vben5/internal/tailwind-config/package.json b/apps/vben5/internal/tailwind-config/package.json index 004ae0bc1..26da63b48 100644 --- a/apps/vben5/internal/tailwind-config/package.json +++ b/apps/vben5/internal/tailwind-config/package.json @@ -1,6 +1,6 @@ { "name": "@vben/tailwind-config", - "version": "5.4.8", + "version": "5.5.1", "private": true, "homepage": "https://github.com/vbenjs/vue-vben-admin", "bugs": "https://github.com/vbenjs/vue-vben-admin/issues", diff --git a/apps/vben5/internal/tsconfig/package.json b/apps/vben5/internal/tsconfig/package.json index d6bae6239..d935b671c 100644 --- a/apps/vben5/internal/tsconfig/package.json +++ b/apps/vben5/internal/tsconfig/package.json @@ -1,6 +1,6 @@ { "name": "@vben/tsconfig", - "version": "5.4.8", + "version": "5.5.1", "private": true, "homepage": "https://github.com/vbenjs/vue-vben-admin", "bugs": "https://github.com/vbenjs/vue-vben-admin/issues", diff --git a/apps/vben5/internal/vite-config/package.json b/apps/vben5/internal/vite-config/package.json index 2d68b95b7..60955a1d4 100644 --- a/apps/vben5/internal/vite-config/package.json +++ b/apps/vben5/internal/vite-config/package.json @@ -1,6 +1,6 @@ { "name": "@vben/vite-config", - "version": "5.4.8", + "version": "5.5.1", "private": true, "homepage": "https://github.com/vbenjs/vue-vben-admin", "bugs": "https://github.com/vbenjs/vue-vben-admin/issues", diff --git a/apps/vben5/internal/vite-config/src/config/application.ts b/apps/vben5/internal/vite-config/src/config/application.ts index f22760944..f9808cc74 100644 --- a/apps/vben5/internal/vite-config/src/config/application.ts +++ b/apps/vben5/internal/vite-config/src/config/application.ts @@ -1,4 +1,4 @@ -import type { UserConfig } from 'vite'; +import type { CSSOptions, UserConfig } from 'vite'; import type { DefineApplicationOptions } from '../typing'; @@ -100,7 +100,7 @@ function defineApplicationConfig(userConfigPromise?: DefineApplicationOptions) { }); } -function createCssOptions(injectGlobalScss = true) { +function createCssOptions(injectGlobalScss = true): CSSOptions { const root = findMonorepoRoot(); return { preprocessorOptions: injectGlobalScss diff --git a/apps/vben5/internal/vite-config/src/utils/env.ts b/apps/vben5/internal/vite-config/src/utils/env.ts index 1dfd18086..3a042fe8a 100644 --- a/apps/vben5/internal/vite-config/src/utils/env.ts +++ b/apps/vben5/internal/vite-config/src/utils/env.ts @@ -1,5 +1,6 @@ import type { ApplicationPluginOptions } from '../typing'; +import { existsSync } from 'node:fs'; import { join } from 'node:path'; import { fs } from '@vben/node-utils'; @@ -21,12 +22,11 @@ function getConfFiles() { const script = process.env.npm_lifecycle_script as string; const reg = /--mode ([\d_a-z]+)/; const result = reg.exec(script); - + let mode = 'production'; if (result) { - const mode = result[1]; - return ['.env', `.env.${mode}`]; + mode = result[1] as string; } - return ['.env', '.env.production']; + return ['.env', '.env.local', `.env.${mode}`, `.env.${mode}.local`]; } /** @@ -42,11 +42,14 @@ async function loadEnv>( for (const confFile of confFiles) { try { - const envPath = await fs.readFile(join(process.cwd(), confFile), { - encoding: 'utf8', - }); - const env = dotenv.parse(envPath); - envConfig = { ...envConfig, ...env }; + const confFilePath = join(process.cwd(), confFile); + if (existsSync(confFilePath)) { + const envPath = await fs.readFile(confFilePath, { + encoding: 'utf8', + }); + const env = dotenv.parse(envPath); + envConfig = { ...envConfig, ...env }; + } } catch (error) { console.error(`Error while parsing ${confFile}`, error); } diff --git a/apps/vben5/package.json b/apps/vben5/package.json index 1bf68b17d..f5a830eb7 100644 --- a/apps/vben5/package.json +++ b/apps/vben5/package.json @@ -1,6 +1,6 @@ { "name": "vben-admin-monorepo", - "version": "5.4.8", + "version": "5.5.1", "private": true, "keywords": [ "monorepo", @@ -101,7 +101,7 @@ "node": ">=20.10.0", "pnpm": ">=9.12.0" }, - "packageManager": "pnpm@9.14.4", + "packageManager": "pnpm@9.15.0", "pnpm": { "peerDependencyRules": { "allowedVersions": { @@ -112,6 +112,7 @@ "@ast-grep/napi": "catalog:", "@ctrl/tinycolor": "catalog:", "clsx": "catalog:", + "esbuild": "0.24.0", "pinia": "catalog:", "vue": "catalog:" }, diff --git a/apps/vben5/packages/@abp/account/package.json b/apps/vben5/packages/@abp/account/package.json index 2a5a46074..64bb473bd 100644 --- a/apps/vben5/packages/@abp/account/package.json +++ b/apps/vben5/packages/@abp/account/package.json @@ -1,6 +1,6 @@ { "name": "@abp/account", - "version": "8.2.3", + "version": "8.3.2", "homepage": "https://github.com/colinin/abp-next-admin", "bugs": "https://github.com/colinin/abp-next-admin/issues", "repository": { diff --git a/apps/vben5/packages/@abp/core/package.json b/apps/vben5/packages/@abp/core/package.json index b46de0539..ce80edc1a 100644 --- a/apps/vben5/packages/@abp/core/package.json +++ b/apps/vben5/packages/@abp/core/package.json @@ -37,12 +37,12 @@ "dependencies": { "@vueuse/core": "catalog:", "dayjs": "catalog:", - "lodash": "catalog:", + "lodash.merge": "catalog:", "pinia": "catalog:", "pinia-plugin-persistedstate": "catalog:", "vue": "catalog:" }, "devDependencies": { - "@types/lodash": "catalog:" + "@types/lodash.merge": "catalog:" } } diff --git a/apps/vben5/packages/@abp/core/src/hooks/useLocalization.ts b/apps/vben5/packages/@abp/core/src/hooks/useLocalization.ts index 4593a11e5..0067107c5 100644 --- a/apps/vben5/packages/@abp/core/src/hooks/useLocalization.ts +++ b/apps/vben5/packages/@abp/core/src/hooks/useLocalization.ts @@ -2,7 +2,7 @@ import type { Dictionary, StringLocalizer } from '../types'; import { computed } from 'vue'; -import { merge } from 'lodash'; +import merge from 'lodash.merge'; import { useAbpStore } from '../store/abp'; import { format } from '../utils/string'; diff --git a/apps/vben5/packages/@abp/identity/package.json b/apps/vben5/packages/@abp/identity/package.json index b19e896c6..5ad844354 100644 --- a/apps/vben5/packages/@abp/identity/package.json +++ b/apps/vben5/packages/@abp/identity/package.json @@ -1,6 +1,6 @@ { "name": "@abp/identity", - "version": "8.2.3", + "version": "8.3.2", "homepage": "https://github.com/colinin/abp-next-admin", "bugs": "https://github.com/colinin/abp-next-admin/issues", "repository": { diff --git a/apps/vben5/packages/@abp/request/package.json b/apps/vben5/packages/@abp/request/package.json index 24acb7a42..edaf1f8ab 100644 --- a/apps/vben5/packages/@abp/request/package.json +++ b/apps/vben5/packages/@abp/request/package.json @@ -1,6 +1,6 @@ { "name": "@abp/request", - "version": "8.2.3", + "version": "8.3.2", "homepage": "https://github.com/colinin/abp-next-admin", "bugs": "https://github.com/colinin/abp-next-admin/issues", "repository": { diff --git a/apps/vben5/packages/@abp/ui/src/adapter/component/index.ts b/apps/vben5/packages/@abp/ui/src/adapter/component/index.ts index e237b52fc..939141ce7 100644 --- a/apps/vben5/packages/@abp/ui/src/adapter/component/index.ts +++ b/apps/vben5/packages/@abp/ui/src/adapter/component/index.ts @@ -8,7 +8,7 @@ import type { BaseFormComponentType } from '@vben/common-ui'; import type { Component, SetupContext } from 'vue'; import { h } from 'vue'; -import { globalShareState } from '@vben/common-ui'; +import { ApiComponent, globalShareState, IconPicker } from '@vben/common-ui'; import { $t } from '@vben/locales'; import { @@ -21,7 +21,6 @@ import { Input, InputNumber, InputPassword, - InputSearch, Mentions, notification, Radio, @@ -49,16 +48,18 @@ const withDefaultPlaceholder = ( // 这里需要自行根据业务组件库进行适配,需要用到的组件都需要在这里类型说明 export type ComponentType = + | 'ApiSelect' + | 'ApiTreeSelect' | 'AutoComplete' | 'Checkbox' | 'CheckboxGroup' | 'DatePicker' | 'DefaultButton' | 'Divider' + | 'IconPicker' | 'Input' | 'InputNumber' | 'InputPassword' - | 'InputSearch' | 'Mentions' | 'PrimaryButton' | 'Radio' @@ -79,7 +80,38 @@ async function initComponentAdapter() { // 如果你的组件体积比较大,可以使用异步加载 // Button: () => // import('xxx').then((res) => res.Button), - + ApiSelect: (props, { attrs, slots }) => { + return h( + ApiComponent, + { + placeholder: $t('ui.placeholder.select'), + ...props, + ...attrs, + component: Select, + loadingSlot: 'suffixIcon', + modelPropName: 'value', + visibleEvent: 'onDropdownVisibleChange', + }, + slots, + ); + }, + ApiTreeSelect: (props, { attrs, slots }) => { + return h( + ApiComponent, + { + placeholder: $t('ui.placeholder.select'), + ...props, + ...attrs, + component: TreeSelect, + fieldNames: { label: 'label', value: 'value', children: 'children' }, + loadingSlot: 'suffixIcon', + modelPropName: 'value', + optionsPropName: 'treeData', + visibleEvent: 'onVisibleChange', + }, + slots, + ); + }, AutoComplete, Checkbox, CheckboxGroup, @@ -89,10 +121,16 @@ async function initComponentAdapter() { return h(Button, { ...props, attrs, type: 'default' }, slots); }, Divider, + IconPicker: (props, { attrs, slots }) => { + return h( + IconPicker, + { iconSlot: 'addonAfter', inputComponent: Input, ...props, ...attrs }, + slots, + ); + }, Input: withDefaultPlaceholder(Input, 'input'), InputNumber: withDefaultPlaceholder(InputNumber, 'input'), InputPassword: withDefaultPlaceholder(InputPassword, 'input'), - InputSearch, Mentions: withDefaultPlaceholder(Mentions, 'input'), // 自定义主要按钮 PrimaryButton: (props, { attrs, slots }) => { diff --git a/apps/vben5/packages/@core/base/design/package.json b/apps/vben5/packages/@core/base/design/package.json index f40b335d0..7b46ddd74 100644 --- a/apps/vben5/packages/@core/base/design/package.json +++ b/apps/vben5/packages/@core/base/design/package.json @@ -1,6 +1,6 @@ { "name": "@vben-core/design", - "version": "5.4.8", + "version": "5.5.1", "homepage": "https://github.com/vbenjs/vue-vben-admin", "bugs": "https://github.com/vbenjs/vue-vben-admin/issues", "repository": { diff --git a/apps/vben5/packages/@core/base/icons/package.json b/apps/vben5/packages/@core/base/icons/package.json index bc13a58e4..754b74509 100644 --- a/apps/vben5/packages/@core/base/icons/package.json +++ b/apps/vben5/packages/@core/base/icons/package.json @@ -1,6 +1,6 @@ { "name": "@vben-core/icons", - "version": "5.4.8", + "version": "5.5.1", "homepage": "https://github.com/vbenjs/vue-vben-admin", "bugs": "https://github.com/vbenjs/vue-vben-admin/issues", "repository": { diff --git a/apps/vben5/packages/@core/base/icons/src/lucide.ts b/apps/vben5/packages/@core/base/icons/src/lucide.ts index 97603eb56..21a1beffd 100644 --- a/apps/vben5/packages/@core/base/icons/src/lucide.ts +++ b/apps/vben5/packages/@core/base/icons/src/lucide.ts @@ -28,6 +28,7 @@ export { Fullscreen, Github, Grip, + GripVertical, Info, InspectionPanel, Languages, diff --git a/apps/vben5/packages/@core/base/shared/package.json b/apps/vben5/packages/@core/base/shared/package.json index 15ba9010c..20eeea450 100644 --- a/apps/vben5/packages/@core/base/shared/package.json +++ b/apps/vben5/packages/@core/base/shared/package.json @@ -1,6 +1,6 @@ { "name": "@vben-core/shared", - "version": "5.4.8", + "version": "5.5.1", "homepage": "https://github.com/vbenjs/vue-vben-admin", "bugs": "https://github.com/vbenjs/vue-vben-admin/issues", "repository": { @@ -86,12 +86,16 @@ "dayjs": "catalog:", "defu": "catalog:", "lodash.clonedeep": "catalog:", + "lodash.get": "catalog:", + "lodash.isequal": "catalog:", "nprogress": "catalog:", "tailwind-merge": "catalog:", "theme-colors": "catalog:" }, "devDependencies": { "@types/lodash.clonedeep": "catalog:", + "@types/lodash.get": "catalog:", + "@types/lodash.isequal": "catalog:", "@types/nprogress": "catalog:" } } diff --git a/apps/vben5/packages/@core/base/shared/src/constants/globals.ts b/apps/vben5/packages/@core/base/shared/src/constants/globals.ts index 17941de11..3c699570f 100644 --- a/apps/vben5/packages/@core/base/shared/src/constants/globals.ts +++ b/apps/vben5/packages/@core/base/shared/src/constants/globals.ts @@ -7,6 +7,9 @@ export const CSS_VARIABLE_LAYOUT_HEADER_HEIGHT = `--vben-header-height`; /** layout footer 组件的高度 */ export const CSS_VARIABLE_LAYOUT_FOOTER_HEIGHT = `--vben-footer-height`; +/** 内容区域的组件ID */ +export const ELEMENT_ID_MAIN_CONTENT = `__vben_main_content`; + /** * @zh_CN 默认命名空间 */ diff --git a/apps/vben5/packages/@core/base/shared/src/utils/date.ts b/apps/vben5/packages/@core/base/shared/src/utils/date.ts index 522e99465..3736b9ad5 100644 --- a/apps/vben5/packages/@core/base/shared/src/utils/date.ts +++ b/apps/vben5/packages/@core/base/shared/src/utils/date.ts @@ -16,3 +16,11 @@ export function formatDate(time: number | string, format = 'YYYY-MM-DD') { export function formatDateTime(time: number | string) { return formatDate(time, 'YYYY-MM-DD HH:mm:ss'); } + +export function isDate(value: any): value is Date { + return value instanceof Date; +} + +export function isDayjsObject(value: any): value is dayjs.Dayjs { + return dayjs.isDayjs(value); +} diff --git a/apps/vben5/packages/@core/base/shared/src/utils/index.ts b/apps/vben5/packages/@core/base/shared/src/utils/index.ts index 2f56c6018..1bf09c713 100644 --- a/apps/vben5/packages/@core/base/shared/src/utils/index.ts +++ b/apps/vben5/packages/@core/base/shared/src/utils/index.ts @@ -15,3 +15,5 @@ export * from './update-css-variables'; export * from './util'; export * from './window'; export { default as cloneDeep } from 'lodash.clonedeep'; +export { default as get } from 'lodash.get'; +export { default as isEqual } from 'lodash.isequal'; diff --git a/apps/vben5/packages/@core/base/typings/package.json b/apps/vben5/packages/@core/base/typings/package.json index d7b940a5a..504e53ed8 100644 --- a/apps/vben5/packages/@core/base/typings/package.json +++ b/apps/vben5/packages/@core/base/typings/package.json @@ -1,6 +1,6 @@ { "name": "@vben-core/typings", - "version": "5.4.8", + "version": "5.5.1", "homepage": "https://github.com/vbenjs/vue-vben-admin", "bugs": "https://github.com/vbenjs/vue-vben-admin/issues", "repository": { diff --git a/apps/vben5/packages/@core/composables/package.json b/apps/vben5/packages/@core/composables/package.json index bac205809..736feefa2 100644 --- a/apps/vben5/packages/@core/composables/package.json +++ b/apps/vben5/packages/@core/composables/package.json @@ -1,6 +1,6 @@ { "name": "@vben-core/composables", - "version": "5.4.8", + "version": "5.5.1", "homepage": "https://github.com/vbenjs/vue-vben-admin", "bugs": "https://github.com/vbenjs/vue-vben-admin/issues", "repository": { diff --git a/apps/vben5/packages/@core/preferences/__tests__/__snapshots__/config.test.ts.snap b/apps/vben5/packages/@core/preferences/__tests__/__snapshots__/config.test.ts.snap index def5ff60b..f05a96dc3 100644 --- a/apps/vben5/packages/@core/preferences/__tests__/__snapshots__/config.test.ts.snap +++ b/apps/vben5/packages/@core/preferences/__tests__/__snapshots__/config.test.ts.snap @@ -65,6 +65,7 @@ exports[`defaultPreferences immutability test > should not modify the config obj "globalSearch": true, }, "sidebar": { + "autoActivateChild": false, "collapsed": false, "collapsedShowTitle": false, "enable": true, @@ -83,6 +84,7 @@ exports[`defaultPreferences immutability test > should not modify the config obj "showMaximize": true, "showMore": true, "styleType": "chrome", + "wheelable": true, }, "theme": { "builtinType": "default", diff --git a/apps/vben5/packages/@core/preferences/package.json b/apps/vben5/packages/@core/preferences/package.json index 1fc760482..24b522e21 100644 --- a/apps/vben5/packages/@core/preferences/package.json +++ b/apps/vben5/packages/@core/preferences/package.json @@ -1,6 +1,6 @@ { "name": "@vben-core/preferences", - "version": "5.4.8", + "version": "5.5.1", "homepage": "https://github.com/vbenjs/vue-vben-admin", "bugs": "https://github.com/vbenjs/vue-vben-admin/issues", "repository": { diff --git a/apps/vben5/packages/@core/preferences/src/config.ts b/apps/vben5/packages/@core/preferences/src/config.ts index 2ee0deaff..c4dce039f 100644 --- a/apps/vben5/packages/@core/preferences/src/config.ts +++ b/apps/vben5/packages/@core/preferences/src/config.ts @@ -65,6 +65,7 @@ const defaultPreferences: Preferences = { globalSearch: true, }, sidebar: { + autoActivateChild: false, collapsed: false, collapsedShowTitle: false, enable: true, @@ -83,6 +84,7 @@ const defaultPreferences: Preferences = { showMaximize: true, showMore: true, styleType: 'chrome', + wheelable: true, }, theme: { builtinType: 'default', diff --git a/apps/vben5/packages/@core/preferences/src/types.ts b/apps/vben5/packages/@core/preferences/src/types.ts index 2b536b85a..59dce2e21 100644 --- a/apps/vben5/packages/@core/preferences/src/types.ts +++ b/apps/vben5/packages/@core/preferences/src/types.ts @@ -125,6 +125,8 @@ interface NavigationPreferences { } interface SidebarPreferences { + /** 点击目录时自动激活子菜单 */ + autoActivateChild: boolean; /** 侧边栏是否折叠 */ collapsed: boolean; /** 侧边栏折叠时,是否显示title */ @@ -173,6 +175,8 @@ interface TabbarPreferences { showMore: boolean; /** 标签页风格 */ styleType: TabsStyleType; + /** 是否开启鼠标滚轮响应 */ + wheelable: boolean; } interface ThemePreferences { diff --git a/apps/vben5/packages/@core/ui-kit/form-ui/package.json b/apps/vben5/packages/@core/ui-kit/form-ui/package.json index ed5eb535e..944fd49ef 100644 --- a/apps/vben5/packages/@core/ui-kit/form-ui/package.json +++ b/apps/vben5/packages/@core/ui-kit/form-ui/package.json @@ -1,6 +1,6 @@ { "name": "@vben-core/form-ui", - "version": "5.4.8", + "version": "5.5.1", "homepage": "https://github.com/vbenjs/vue-vben-admin", "bugs": "https://github.com/vbenjs/vue-vben-admin/issues", "repository": { diff --git a/apps/vben5/packages/@core/ui-kit/form-ui/src/components/form-actions.vue b/apps/vben5/packages/@core/ui-kit/form-ui/src/components/form-actions.vue index 26e426fe5..b9a878e8a 100644 --- a/apps/vben5/packages/@core/ui-kit/form-ui/src/components/form-actions.vue +++ b/apps/vben5/packages/@core/ui-kit/form-ui/src/components/form-actions.vue @@ -138,17 +138,37 @@ defineExpose({