diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 3b7f81878080534452818edc8b16e2eb23593e65..9fd865479af8aa7dcc19c351029ad06aa3816aef 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -1,14 +1,14 @@ # default onwer -* anncwb@126.com vince292007@gmail.com +* anncwb@126.com vince292007@gmail.com netfan@foxmail.com # vben core onwer -/.github/ anncwb@126.com vince292007@gmail.com -/.vscode/ anncwb@126.com vince292007@gmail.com -/packages/ anncwb@126.com vince292007@gmail.com -/packages/@core/ anncwb@126.com vince292007@gmail.com -/internal/ anncwb@126.com vince292007@gmail.com -/scripts/ anncwb@126.com vince292007@gmail.com +/.github/ anncwb@126.com vince292007@gmail.com netfan@foxmail.com +/.vscode/ anncwb@126.com vince292007@gmail.com netfan@foxmail.com +/packages/ anncwb@126.com vince292007@gmail.com netfan@foxmail.com +/packages/@core/ anncwb@126.com vince292007@gmail.com netfan@foxmail.com +/internal/ anncwb@126.com vince292007@gmail.com netfan@foxmail.com +/scripts/ anncwb@126.com vince292007@gmail.com netfan@foxmail.com # vben team onwer -apps/ anncwb@126.com vince292007@gmail.com @vbenjs/team-v5 -docs/ anncwb@126.com vince292007@gmail.com @vbenjs/team-v5 +apps/ anncwb@126.com vince292007@gmail.com netfan@foxmail.com @vbenjs/team-v5 +docs/ anncwb@126.com vince292007@gmail.com netfan@foxmail.com @vbenjs/team-v5 diff --git a/.github/ISSUE_TEMPLATE/bug-report.yml b/.github/ISSUE_TEMPLATE/bug-report.yml index 352ed11e60f88433eaca9772da1cd3889664c0ec..ae92780319da11aa841e4e3b09492a9255976578 100644 --- a/.github/ISSUE_TEMPLATE/bug-report.yml +++ b/.github/ISSUE_TEMPLATE/bug-report.yml @@ -62,7 +62,7 @@ body: description: Before submitting the issue, please make sure you do the following # description: By submitting this issue, you agree to follow our [Code of Conduct](https://example.com). options: - - label: Read the [docs](https://anncwb.github.io/vue-vben-admin-doc/) + - label: Read the [docs](https://doc.vben.pro/) required: true - label: Ensure the code is up to date. (Some issues have been fixed in the latest version) required: true diff --git a/.github/ISSUE_TEMPLATE/feature-request.yml b/.github/ISSUE_TEMPLATE/feature-request.yml index e40eac66c0c5840b56eb3043ba3a4fbf53755f9c..393334e8dc122e8a96e7c63dbef327e735332193 100644 --- a/.github/ISSUE_TEMPLATE/feature-request.yml +++ b/.github/ISSUE_TEMPLATE/feature-request.yml @@ -62,7 +62,7 @@ body: label: Validations description: Before submitting the issue, please make sure you do the following options: - - label: Read the [docs](https://anncwb.github.io/vue-vben-admin-doc/) + - label: Read the [docs](https://doc.vben.pro/) required: true - label: Ensure the code is up to date. (Some issues have been fixed in the latest version) required: true diff --git a/.vscode/launch.json b/.vscode/launch.json index 7740e810407b2d70b31dad043cefd2aecf038b9b..e9673304486afd5658e003e86262b4391b071e01 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -9,7 +9,7 @@ "url": "http://localhost:5555", "env": { "NODE_ENV": "development" }, "sourceMaps": true, - "webRoot": "${workspaceFolder}" + "webRoot": "${workspaceFolder}/playground" }, { "type": "chrome", @@ -18,7 +18,7 @@ "url": "http://localhost:5666", "env": { "NODE_ENV": "development" }, "sourceMaps": true, - "webRoot": "${workspaceFolder}" + "webRoot": "${workspaceFolder}/apps/web-antd" }, { "type": "chrome", @@ -27,7 +27,7 @@ "url": "http://localhost:5777", "env": { "NODE_ENV": "development" }, "sourceMaps": true, - "webRoot": "${workspaceFolder}" + "webRoot": "${workspaceFolder}/apps/web-ele" }, { "type": "chrome", @@ -36,7 +36,7 @@ "url": "http://localhost:5888", "env": { "NODE_ENV": "development" }, "sourceMaps": true, - "webRoot": "${workspaceFolder}" + "webRoot": "${workspaceFolder}/apps/web-naive" } ] } diff --git a/apps/backend-mock/api/table/list.ts b/apps/backend-mock/api/table/list.ts index 4a0db94ec8ebaa3d532a1bfdc4f8341c129daf51..55b88eaaa0694e8fce01e569190d7996a634f2c9 100644 --- a/apps/backend-mock/api/table/list.ts +++ b/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/backend-mock/utils/mock-data.ts b/apps/backend-mock/utils/mock-data.ts index 71970a2859fd56fb073faaa66c171298c1ad7d70..057588e363ab93b9a34f7bcf2801e65101194c07 100644 --- a/apps/backend-mock/utils/mock-data.ts +++ b/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/web-antd/package.json b/apps/web-antd/package.json index 42afa3c696d2df2539f2224c3a7c3470220effa1..33b987642e5264465c4f59d07710d7a2fb6a7b83 100644 --- a/apps/web-antd/package.json +++ b/apps/web-antd/package.json @@ -1,6 +1,6 @@ { "name": "@vben/web-antd", - "version": "5.4.8", + "version": "5.5.2", "homepage": "https://vben.pro", "bugs": "https://github.com/vbenjs/vue-vben-admin/issues", "repository": { diff --git a/apps/web-antd/src/adapter/component/index.ts b/apps/web-antd/src/adapter/component/index.ts index 1afa62174637a977bf5f2d28e5a1ce0225fa45ad..a43a8280a8e2ebdac282b921ba5867a5fc90db70 100644 --- a/apps/web-antd/src/adapter/component/index.ts +++ b/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/web-antd/src/router/guard.ts b/apps/web-antd/src/router/guard.ts index fce5a892c2193eebef9ad674c44e1c64ba9c51fa..cbb5235ec77de62008d7cf6f6e13912990f5f9de 100644 --- a/apps/web-antd/src/router/guard.ts +++ b/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/web-antd/src/views/_core/authentication/code-login.vue b/apps/web-antd/src/views/_core/authentication/code-login.vue index 556b273af7aa7fc8fcdf63a7f30238a3a59b3cb8..acfd1fd78818ff33cc8718160416a1499e2809bc 100644 --- a/apps/web-antd/src/views/_core/authentication/code-login.vue +++ b/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/web-ele/package.json b/apps/web-ele/package.json index 4301569515093c9e8311fe7512e37d0d5c772af8..5876940054b87c167c267627543a8a81482d83ab 100644 --- a/apps/web-ele/package.json +++ b/apps/web-ele/package.json @@ -1,6 +1,6 @@ { "name": "@vben/web-ele", - "version": "5.4.8", + "version": "5.5.2", "homepage": "https://vben.pro", "bugs": "https://github.com/vbenjs/vue-vben-admin/issues", "repository": { diff --git a/apps/web-ele/src/adapter/component/index.ts b/apps/web-ele/src/adapter/component/index.ts index ebf9dd3e1ea081495fb5bffaeff99f464b02393f..818c8c4e194d5c10ddb68b3aec167b7f636c7fff 100644 --- a/apps/web-ele/src/adapter/component/index.ts +++ b/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/web-ele/src/adapter/form.ts b/apps/web-ele/src/adapter/form.ts index 1b6e047194cb4c2ea984a0a5faa7b4e076b255e8..13ae9c428a3eb12b9091b5b1904092ca18aada9c 100644 --- a/apps/web-ele/src/adapter/form.ts +++ b/apps/web-ele/src/adapter/form.ts @@ -12,6 +12,7 @@ setupVbenForm({ config: { modelPropNameMap: { Upload: 'fileList', + CheckboxGroup: 'model-value', }, }, defineRules: { diff --git a/apps/web-ele/src/bootstrap.ts b/apps/web-ele/src/bootstrap.ts index de18847307de55d6a89dab1c38fe52e663c7ad65..ad1dce0f9e95195a8e0f7069b011f10e4e917918 100644 --- a/apps/web-ele/src/bootstrap.ts +++ b/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/web-ele/src/locales/langs/en-US/demos.json b/apps/web-ele/src/locales/langs/en-US/demos.json index 056da0dae6a3cf89905a10046936d457de422f1a..6eddebb531dc7768ab1e403eab14b9b70e12a88f 100644 --- a/apps/web-ele/src/locales/langs/en-US/demos.json +++ b/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/web-ele/src/locales/langs/zh-CN/demos.json b/apps/web-ele/src/locales/langs/zh-CN/demos.json index 0620e16a1b44dde04546940f39953b6986b1a189..ba6d6ccd0ef976fb2f40ddabc7c77d7ceeadf076 100644 --- a/apps/web-ele/src/locales/langs/zh-CN/demos.json +++ b/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/web-ele/src/router/guard.ts b/apps/web-ele/src/router/guard.ts index fce5a892c2193eebef9ad674c44e1c64ba9c51fa..cbb5235ec77de62008d7cf6f6e13912990f5f9de 100644 --- a/apps/web-ele/src/router/guard.ts +++ b/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/web-ele/src/router/routes/modules/demos.ts b/apps/web-ele/src/router/routes/modules/demos.ts index 223efcf9d2568fd6caca75dd155a6c2b4bad4e09..90cc2f11c3eb5a1a64512bec23b400d988bd2f83 100644 --- a/apps/web-ele/src/router/routes/modules/demos.ts +++ b/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/web-ele/src/views/_core/authentication/code-login.vue b/apps/web-ele/src/views/_core/authentication/code-login.vue index 556b273af7aa7fc8fcdf63a7f30238a3a59b3cb8..acfd1fd78818ff33cc8718160416a1499e2809bc 100644 --- a/apps/web-ele/src/views/_core/authentication/code-login.vue +++ b/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/web-ele/src/views/demos/element/index.vue b/apps/web-ele/src/views/demos/element/index.vue index 2625cebdfc96feb637acd6fb398fe035bcaef877..0a7012d63ee5a54cadc3f4dd387c09480cadf843 100644 --- a/apps/web-ele/src/views/demos/element/index.vue +++ b/apps/web-ele/src/views/demos/element/index.vue @@ -1,4 +1,6 @@ diff --git a/apps/web-ele/src/views/demos/form/basic.vue b/apps/web-ele/src/views/demos/form/basic.vue new file mode 100644 index 0000000000000000000000000000000000000000..771665a660415338e2ea224f755b49b98de723d2 --- /dev/null +++ b/apps/web-ele/src/views/demos/form/basic.vue @@ -0,0 +1,181 @@ + + diff --git a/apps/web-naive/package.json b/apps/web-naive/package.json index 0710c3415bf11aad09cede1887a4d0c3912f08ee..57857c892be87705170e5e611418e2f93d60ec14 100644 --- a/apps/web-naive/package.json +++ b/apps/web-naive/package.json @@ -1,6 +1,6 @@ { "name": "@vben/web-naive", - "version": "5.4.8", + "version": "5.5.2", "homepage": "https://vben.pro", "bugs": "https://github.com/vbenjs/vue-vben-admin/issues", "repository": { diff --git a/apps/web-naive/src/adapter/component/index.ts b/apps/web-naive/src/adapter/component/index.ts index c5dffddb8dc92885227b8ea8e052fd53558f4c82..545a61994c1cfe753c762ed4534a06b91ee4a3c1 100644 --- a/apps/web-naive/src/adapter/component/index.ts +++ b/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/web-naive/src/adapter/form.ts b/apps/web-naive/src/adapter/form.ts index 2c3cee875ef69adc9e3cfa6143058f88d15e719a..2f2ed2abe392bb9a5188c8607d8c9d79a22ebd08 100644 --- a/apps/web-naive/src/adapter/form.ts +++ b/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/web-naive/src/locales/langs/en-US/demos.json b/apps/web-naive/src/locales/langs/en-US/demos.json index 9fdffc765e869d04516f0fdfd93eb806b5c3983a..839fc2e68d739b19ddb82b6afea50e3fb4a0ba11 100644 --- a/apps/web-naive/src/locales/langs/en-US/demos.json +++ b/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/web-naive/src/locales/langs/zh-CN/demos.json b/apps/web-naive/src/locales/langs/zh-CN/demos.json index 79b54fa2109505e1905480fbf4737c6fd32f0f70..e0d7e616d2b67c52598318b5097242236c7b6ba6 100644 --- a/apps/web-naive/src/locales/langs/zh-CN/demos.json +++ b/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/web-naive/src/router/guard.ts b/apps/web-naive/src/router/guard.ts index c95d994ecb7c1647f9e5fa96deeed021190dc0bc..281ea31a6d6c16ecc0079067cc025a0d7b1d1590 100644 --- a/apps/web-naive/src/router/guard.ts +++ b/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/web-naive/src/router/routes/modules/demos.ts b/apps/web-naive/src/router/routes/modules/demos.ts index cf5658800ce5ad28dc14a587109938b639efdb24..d0631cb53d00f8c362dfc8c34747e9cf51ff5902 100644 --- a/apps/web-naive/src/router/routes/modules/demos.ts +++ b/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/web-naive/src/views/_core/authentication/code-login.vue b/apps/web-naive/src/views/_core/authentication/code-login.vue index 556b273af7aa7fc8fcdf63a7f30238a3a59b3cb8..acfd1fd78818ff33cc8718160416a1499e2809bc 100644 --- a/apps/web-naive/src/views/_core/authentication/code-login.vue +++ b/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/web-naive/src/views/demos/form/basic.vue b/apps/web-naive/src/views/demos/form/basic.vue new file mode 100644 index 0000000000000000000000000000000000000000..7d04ff4d9b6c1f4420908ab6f91ff1b01805df4d --- /dev/null +++ b/apps/web-naive/src/views/demos/form/basic.vue @@ -0,0 +1,143 @@ + + diff --git a/docs/.vitepress/config/zh.mts b/docs/.vitepress/config/zh.mts index 6b31658edb92c7f9f79304e801ff003a60197da5..dff8ac280774469bdc3a2b726bdbb106afb642ac 100644 --- a/docs/.vitepress/config/zh.mts +++ b/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/docs/package.json b/docs/package.json index 15a87bb971f47cadba26418c7a6146af5f446f53..5ea22fd1c4cc03bfb583978e81490962db22b2b4 100644 --- a/docs/package.json +++ b/docs/package.json @@ -1,6 +1,6 @@ { "name": "@vben/docs", - "version": "5.4.8", + "version": "5.5.2", "private": true, "scripts": { "build": "vitepress build", diff --git a/docs/src/_env/adapter/form.ts b/docs/src/_env/adapter/form.ts index 67e2483e53ecaa48a6f4f59bfe5783b1d3d309c8..d8b51c254d17bae8f248365276b8e87f291de03f 100644 --- a/docs/src/_env/adapter/form.ts +++ b/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/docs/src/components/common-ui/vben-api-component.md b/docs/src/components/common-ui/vben-api-component.md new file mode 100644 index 0000000000000000000000000000000000000000..f275adfc2c1e7804499cbb800ebe0fe490511d39 --- /dev/null +++ b/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/docs/src/components/common-ui/vben-drawer.md b/docs/src/components/common-ui/vben-drawer.md index e149b6aa99d770de2ad3675b51373f9433580a2d..7091570f16dadf9f51534feda6b45e4ffb5c7b7f 100644 --- a/docs/src/components/common-ui/vben-drawer.md +++ b/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` | - | @@ -88,12 +89,20 @@ const [Drawer, drawerApi] = useVbenDrawer({ | closeOnPressEscape | esc 关闭弹窗 | `boolean` | `true` | | confirmText | 确认按钮文本 | `string\|slot` | `确认` | | cancelText | 取消按钮文本 | `string\|slot` | `取消` | +| placement | 抽屉弹出位置 | `'left'\|'right'\|'top'\|'bottom'` | `right` | | showCancelButton | 显示取消按钮 | `boolean` | `true` | | showConfirmButton | 显示确认按钮文本 | `boolean` | `true` | | class | modal的class,宽度通过这个配置 | `string` | - | | 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/docs/src/components/common-ui/vben-ellipsis-text.md b/docs/src/components/common-ui/vben-ellipsis-text.md new file mode 100644 index 0000000000000000000000000000000000000000..109f1161cd6f4fc6dcc0669e9dfe549a0a42388a --- /dev/null +++ b/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/docs/src/components/common-ui/vben-form.md b/docs/src/components/common-ui/vben-form.md index 0ed4db404a541903d5236928108567a799ffd897..8d27c08d533eba896c472d49b5d2d8eb2f5b59e3 100644 --- a/docs/src/components/common-ui/vben-form.md +++ b/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,13 +431,13 @@ export interface FormSchema< dependencies?: FormItemDependencies; /** 描述 */ description?: string; - /** 字段名 */ + /** 字段名,也作为自定义插槽的名称 */ fieldName: string; /** 帮助信息 */ help?: string; /** 表单项 */ label?: string; - // 自定义组件内部渲染 + /** 自定义组件内部渲染 */ renderComponentContent?: RenderComponentContentType; /** 字段规则 */ rules?: FormSchemaRuleType; @@ -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: "请输入" }) @@ -500,3 +523,20 @@ import { z } from '#/adapter/form'; }); } ``` + +## Slots + +可以使用以下插槽在表单中插入自定义的内容 + +| 插槽名 | 描述 | +| ------------- | ------------------ | +| reset-before | 重置按钮之前的位置 | +| submit-before | 提交按钮之前的位置 | +| expand-before | 展开按钮之前的位置 | +| expand-after | 展开按钮之后的位置 | + +::: tip 字段插槽 + +除了以上内置插槽之外,`schema`属性中每个字段的`fieldName`都可以作为插槽名称,这些字段插槽的优先级高于`component`定义的组件。也就是说,当提供了与`fieldName`同名的插槽时,这些插槽的内容将会作为这些字段的组件,此时`component`的值将会被忽略。 + +::: diff --git a/docs/src/components/common-ui/vben-modal.md b/docs/src/components/common-ui/vben-modal.md index c795b9dedeab26e7356d56d363d8e8989b199dd8..d6e3ef47f7903294d93d741bf439508c1d1a6a1b 100644 --- a/docs/src/components/common-ui/vben-modal.md +++ b/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` | - | @@ -93,18 +94,26 @@ const [Modal, modalApi] = useVbenModal({ | modal | 显示遮罩 | `boolean` | `true` | | header | 显示header | `boolean` | `true` | | footer | 显示footer | `boolean\|slot` | `true` | +| confirmDisabled | 禁用确认按钮 | `boolean` | `false` | | confirmLoading | 确认按钮loading状态 | `boolean` | `false` | | closeOnClickModal | 点击遮罩关闭弹窗 | `boolean` | `true` | | closeOnPressEscape | esc 关闭弹窗 | `boolean` | `true` | | confirmText | 确认按钮文本 | `string\|slot` | `确认` | | cancelText | 取消按钮文本 | `string\|slot` | `取消` | | showCancelButton | 显示取消按钮 | `boolean` | `true` | -| showConfirmButton | 显示确认按钮文本 | `boolean` | `true` | +| showConfirmButton | 显示确认按钮 | `boolean` | `true` | | class | modal的class,宽度通过这个配置 | `string` | - | | contentClass | modal内容区域的class | `string` | - | | 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/docs/src/components/common-ui/vben-vxe-table.md b/docs/src/components/common-ui/vben-vxe-table.md index 29f679f6aaf25a327d6a83c30c080e1b68024d21..24d911da794f6b71b9ac6e8533bd2c7e55bcfd37 100644 --- a/docs/src/components/common-ui/vben-vxe-table.md +++ b/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/docs/src/components/introduction.md b/docs/src/components/introduction.md index 039ec8cd832f23096fc400fb3bdaf874c33be39d..438470e9a36856ceed1cc0925a79ae003d8dae3d 100644 --- a/docs/src/components/introduction.md +++ b/docs/src/components/introduction.md @@ -6,6 +6,10 @@ ::: +## 布局组件 + +布局组件一般在页面内容区域用作顶层容器组件,提供一些统一的布局样式和基本功能。 + ## 通用组件 通用组件是一些常用的组件,比如弹窗、抽屉、表单等。大部分基于 `Tailwind CSS` 实现,可适用于不同 UI 组件库的应用。 diff --git a/docs/src/components/layout-ui/page.md b/docs/src/components/layout-ui/page.md new file mode 100644 index 0000000000000000000000000000000000000000..29fbdd40f0e63f2a5ef038758e7613e5b125888e --- /dev/null +++ b/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/docs/src/demos/vben-api-component/cascader/index.vue b/docs/src/demos/vben-api-component/cascader/index.vue new file mode 100644 index 0000000000000000000000000000000000000000..957964cd3df48c251103851b38e73a5c22cc3a8d --- /dev/null +++ b/docs/src/demos/vben-api-component/cascader/index.vue @@ -0,0 +1,100 @@ + + diff --git a/docs/src/demos/vben-ellipsis-text/expand/index.vue b/docs/src/demos/vben-ellipsis-text/expand/index.vue new file mode 100644 index 0000000000000000000000000000000000000000..842f6b32e840c180d7ae9ab192fb8f8179d1ed12 --- /dev/null +++ b/docs/src/demos/vben-ellipsis-text/expand/index.vue @@ -0,0 +1,10 @@ + + diff --git a/docs/src/demos/vben-ellipsis-text/line/index.vue b/docs/src/demos/vben-ellipsis-text/line/index.vue new file mode 100644 index 0000000000000000000000000000000000000000..dfbf20effc72a518f4979e47bc5c9bd5e8e57843 --- /dev/null +++ b/docs/src/demos/vben-ellipsis-text/line/index.vue @@ -0,0 +1,10 @@ + + diff --git a/docs/src/demos/vben-ellipsis-text/tooltip/index.vue b/docs/src/demos/vben-ellipsis-text/tooltip/index.vue new file mode 100644 index 0000000000000000000000000000000000000000..e6287a12f51330c725d602d0a6f7555068a2c36a --- /dev/null +++ b/docs/src/demos/vben-ellipsis-text/tooltip/index.vue @@ -0,0 +1,14 @@ + + diff --git a/docs/src/demos/vben-vxe-table/form/index.vue b/docs/src/demos/vben-vxe-table/form/index.vue index a5e8a547b8d95bf6987c84d051fb068db5ddbb75..bcf3f5a5d19a6aebc7c51399105bb0dfa5c58999 100644 --- a/docs/src/demos/vben-vxe-table/form/index.vue +++ b/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/docs/src/en/guide/essentials/settings.md b/docs/src/en/guide/essentials/settings.md index 68fef3e7e93bf08cb5197aa6d83f6619a945560d..a3cd579ec7a6418d46064c240a916d4f876f712f 100644 --- a/docs/src/en/guide/essentials/settings.md +++ b/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/docs/src/guide/essentials/settings.md b/docs/src/guide/essentials/settings.md index e33572066cbd74fb0fc9405c44ca0a0257224418..3669a771c21c64ce755cd42d946bec3ca782f445 100644 --- a/docs/src/guide/essentials/settings.md +++ b/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/docs/src/guide/in-depth/ui-framework.md b/docs/src/guide/in-depth/ui-framework.md index ce05608fcd5a427d668f56fb2ebe4137164d4bec..6a7508e1d4f233048355ca5281f3fae60074ff39 100644 --- a/docs/src/guide/in-depth/ui-framework.md +++ b/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/internal/lint-configs/commitlint-config/package.json b/internal/lint-configs/commitlint-config/package.json index 6679a66274e15a6296d3af4894669dec477e4a23..d7e4c51802d48821245574f95dc051f625f9926a 100644 --- a/internal/lint-configs/commitlint-config/package.json +++ b/internal/lint-configs/commitlint-config/package.json @@ -1,6 +1,6 @@ { "name": "@vben/commitlint-config", - "version": "5.4.8", + "version": "5.5.2", "private": true, "homepage": "https://github.com/vbenjs/vue-vben-admin", "bugs": "https://github.com/vbenjs/vue-vben-admin/issues", diff --git a/internal/lint-configs/eslint-config/src/configs/vue.ts b/internal/lint-configs/eslint-config/src/configs/vue.ts index 27cc3cf29c195a933ace3383afbb171616153b12..d1c6521cff4979b10e51413ff79e10e0c95b364a 100644 --- a/internal/lint-configs/eslint-config/src/configs/vue.ts +++ b/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/internal/lint-configs/stylelint-config/package.json b/internal/lint-configs/stylelint-config/package.json index c9e8cf53977c2907f54b951526a0564762c45444..02491dd0f267193a8bc30a2920cb099f1ec60979 100644 --- a/internal/lint-configs/stylelint-config/package.json +++ b/internal/lint-configs/stylelint-config/package.json @@ -1,6 +1,6 @@ { "name": "@vben/stylelint-config", - "version": "5.4.8", + "version": "5.5.2", "private": true, "homepage": "https://github.com/vbenjs/vue-vben-admin", "bugs": "https://github.com/vbenjs/vue-vben-admin/issues", diff --git a/internal/node-utils/package.json b/internal/node-utils/package.json index 0cd1eaa3d48874893f03e95d766a283f1ac9f2bf..b2a60da8c2586027e6aaa8ffca927cc4dfd0e3a1 100644 --- a/internal/node-utils/package.json +++ b/internal/node-utils/package.json @@ -1,6 +1,6 @@ { "name": "@vben/node-utils", - "version": "5.4.8", + "version": "5.5.2", "private": true, "homepage": "https://github.com/vbenjs/vue-vben-admin", "bugs": "https://github.com/vbenjs/vue-vben-admin/issues", diff --git a/internal/tailwind-config/package.json b/internal/tailwind-config/package.json index 004ae0bc110c115507e758f08b0f313dea38966d..0b64d1d53dbb150b5a497309d555279eb1ee62af 100644 --- a/internal/tailwind-config/package.json +++ b/internal/tailwind-config/package.json @@ -1,6 +1,6 @@ { "name": "@vben/tailwind-config", - "version": "5.4.8", + "version": "5.5.2", "private": true, "homepage": "https://github.com/vbenjs/vue-vben-admin", "bugs": "https://github.com/vbenjs/vue-vben-admin/issues", diff --git a/internal/tsconfig/package.json b/internal/tsconfig/package.json index d6bae6239c49871080b8d6a25236b1da56de3ff4..8ebfeebfdca56368179fa2fc75721fdfed2881a8 100644 --- a/internal/tsconfig/package.json +++ b/internal/tsconfig/package.json @@ -1,6 +1,6 @@ { "name": "@vben/tsconfig", - "version": "5.4.8", + "version": "5.5.2", "private": true, "homepage": "https://github.com/vbenjs/vue-vben-admin", "bugs": "https://github.com/vbenjs/vue-vben-admin/issues", diff --git a/internal/vite-config/package.json b/internal/vite-config/package.json index 2d68b95b752549b2fdf2672c1ee7f23f06766820..6c589a1f15dd01fbb967c45e5e43de58bde3d98a 100644 --- a/internal/vite-config/package.json +++ b/internal/vite-config/package.json @@ -1,6 +1,6 @@ { "name": "@vben/vite-config", - "version": "5.4.8", + "version": "5.5.2", "private": true, "homepage": "https://github.com/vbenjs/vue-vben-admin", "bugs": "https://github.com/vbenjs/vue-vben-admin/issues", diff --git a/internal/vite-config/src/config/application.ts b/internal/vite-config/src/config/application.ts index f227609449ca4fd1eebe0a7907d2e2357ec7929c..f9808cc7493f8c78122ebb1b63d248e66f03dd2d 100644 --- a/internal/vite-config/src/config/application.ts +++ b/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/internal/vite-config/src/utils/env.ts b/internal/vite-config/src/utils/env.ts index 1dfd180869f7bd8c41ef57cad00f2154ed881898..3a042fe8a5476cf6ca7d9f3a51456d09132f2633 100644 --- a/internal/vite-config/src/utils/env.ts +++ b/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/package.json b/package.json index 220ac0c62fd1100f2ef6523c7f5eeb12e46b78f1..8f6c4d27ed75fbd9529b11b9bb5f740b00064ad1 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "vben-admin-monorepo", - "version": "5.4.8", + "version": "5.5.2", "private": true, "keywords": [ "monorepo", @@ -99,7 +99,7 @@ "node": ">=20.10.0", "pnpm": ">=9.12.0" }, - "packageManager": "pnpm@9.14.2", + "packageManager": "pnpm@9.15.1", "pnpm": { "peerDependencyRules": { "allowedVersions": { @@ -110,6 +110,7 @@ "@ast-grep/napi": "catalog:", "@ctrl/tinycolor": "catalog:", "clsx": "catalog:", + "esbuild": "0.24.0", "pinia": "catalog:", "vue": "catalog:" }, diff --git a/packages/@core/base/design/package.json b/packages/@core/base/design/package.json index cbe5159c00c3cd152bb917349d1295e970fceea0..1d9888ca7ecf352d74f3c2eb51646d31b1339b7a 100644 --- a/packages/@core/base/design/package.json +++ b/packages/@core/base/design/package.json @@ -1,6 +1,6 @@ { "name": "@vben-core/design", - "version": "5.4.8", + "version": "5.5.2", "homepage": "https://github.com/vbenjs/vue-vben-admin", "bugs": "https://github.com/vbenjs/vue-vben-admin/issues", "repository": { @@ -28,7 +28,7 @@ ".": { "types": "./src/index.ts", "development": "./src/index.ts", - "default": "./dist/style.css" + "default": "./dist/design.css" } }, "publishConfig": { diff --git a/packages/@core/base/design/src/design-tokens/dark.css b/packages/@core/base/design/src/design-tokens/dark.css index 6d236253e0b51d7c68bbc00c458021698a908e1d..2a1d052f605bfbd3f6cc69e8e6a1d52c48da04f4 100644 --- a/packages/@core/base/design/src/design-tokens/dark.css +++ b/packages/@core/base/design/src/design-tokens/dark.css @@ -58,6 +58,8 @@ /* Used for accents such as hover effects on , ...etc */ --accent: 216 5% 19%; + --accent-dark: 240 0% 22%; + --accent-darker: 240 0% 26%; --accent-lighter: 216 5% 12%; --accent-hover: 216 5% 24%; --accent-foreground: 0 0% 98%; diff --git a/packages/@core/base/design/src/design-tokens/default.css b/packages/@core/base/design/src/design-tokens/default.css index b999e1298f8d215cd6dd7a217f2163df94140f5c..c81ace7ea0455d103f9bdd1b76b17a2c58531f91 100644 --- a/packages/@core/base/design/src/design-tokens/default.css +++ b/packages/@core/base/design/src/design-tokens/default.css @@ -58,6 +58,8 @@ /* Used for accents such as hover effects on , ...etc */ --accent: 240 5% 96%; + --accent-dark: 216 14% 93%; + --accent-darker: 216 11% 91%; --accent-lighter: 240 0% 98%; --accent-hover: 200deg 10% 90%; --accent-foreground: 240 6% 10%; diff --git a/packages/@core/base/icons/package.json b/packages/@core/base/icons/package.json index bc13a58e4b6c572581132aed00224594176e760b..144814a040fc090caae44ac7be2ace2758fda9ef 100644 --- a/packages/@core/base/icons/package.json +++ b/packages/@core/base/icons/package.json @@ -1,6 +1,6 @@ { "name": "@vben-core/icons", - "version": "5.4.8", + "version": "5.5.2", "homepage": "https://github.com/vbenjs/vue-vben-admin", "bugs": "https://github.com/vbenjs/vue-vben-admin/issues", "repository": { diff --git a/packages/@core/base/icons/src/lucide.ts b/packages/@core/base/icons/src/lucide.ts index 97603eb56477eb1ac83b9855ea2e76017a28bf8b..21a1beffd6fefe6136d1034722bf0fe09854d76f 100644 --- a/packages/@core/base/icons/src/lucide.ts +++ b/packages/@core/base/icons/src/lucide.ts @@ -28,6 +28,7 @@ export { Fullscreen, Github, Grip, + GripVertical, Info, InspectionPanel, Languages, diff --git a/packages/@core/base/shared/package.json b/packages/@core/base/shared/package.json index 15ba9010cb9e0ee1c1cf06d689f5b33dc75e76f6..c52c040cc835123e938b93693219bc2922d3924c 100644 --- a/packages/@core/base/shared/package.json +++ b/packages/@core/base/shared/package.json @@ -1,6 +1,6 @@ { "name": "@vben-core/shared", - "version": "5.4.8", + "version": "5.5.2", "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/packages/@core/base/shared/src/constants/globals.ts b/packages/@core/base/shared/src/constants/globals.ts index 17941de118355a3fb5be5494d75cd8a03132e763..3c699570f015be3a1ecfb736ac54b0a226909768 100644 --- a/packages/@core/base/shared/src/constants/globals.ts +++ b/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/packages/@core/base/shared/src/utils/date.ts b/packages/@core/base/shared/src/utils/date.ts index 522e99465f133af9e21ddff377d72093876e016a..3736b9ad591cd4e9de01f18f7777d57c77bf6fef 100644 --- a/packages/@core/base/shared/src/utils/date.ts +++ b/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/packages/@core/base/shared/src/utils/index.ts b/packages/@core/base/shared/src/utils/index.ts index 2f56c6018595eabe3378f6f75de3c17b2d7aa96c..1bf09c713bc78e5335ae4bd11a3e87fed8bd05bb 100644 --- a/packages/@core/base/shared/src/utils/index.ts +++ b/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/packages/@core/base/typings/package.json b/packages/@core/base/typings/package.json index d7b940a5ad14b3b7970a3df7d3c270783f2dc25f..c715393956d5715b4525b934f801e44338c51381 100644 --- a/packages/@core/base/typings/package.json +++ b/packages/@core/base/typings/package.json @@ -1,6 +1,6 @@ { "name": "@vben-core/typings", - "version": "5.4.8", + "version": "5.5.2", "homepage": "https://github.com/vbenjs/vue-vben-admin", "bugs": "https://github.com/vbenjs/vue-vben-admin/issues", "repository": { diff --git a/packages/@core/base/typings/src/app.d.ts b/packages/@core/base/typings/src/app.d.ts index e49a8657d9313f96b25ab356ffc1f463aa13caf6..f783c8b5690fa7b8dc58651c6b8040702e345add 100644 --- a/packages/@core/base/typings/src/app.d.ts +++ b/packages/@core/base/typings/src/app.d.ts @@ -38,6 +38,7 @@ type BuiltinThemeType = type ContentCompactType = 'compact' | 'wide'; type LayoutHeaderModeType = 'auto' | 'auto-scroll' | 'fixed' | 'static'; +type LayoutHeaderMenuAlignType = 'center' | 'end' | 'start'; /** * 登录过期模式 @@ -95,6 +96,7 @@ export type { BreadcrumbStyleType, BuiltinThemeType, ContentCompactType, + LayoutHeaderMenuAlignType, LayoutHeaderModeType, LayoutType, LoginExpiredModeType, diff --git a/packages/@core/composables/package.json b/packages/@core/composables/package.json index bac205809f205d15cf4953b5df6936fd023b5402..49f972fe06ff770e715007f97a4c7bab4c1c6e68 100644 --- a/packages/@core/composables/package.json +++ b/packages/@core/composables/package.json @@ -1,6 +1,6 @@ { "name": "@vben-core/composables", - "version": "5.4.8", + "version": "5.5.2", "homepage": "https://github.com/vbenjs/vue-vben-admin", "bugs": "https://github.com/vbenjs/vue-vben-admin/issues", "repository": { diff --git a/packages/@core/preferences/__tests__/__snapshots__/config.test.ts.snap b/packages/@core/preferences/__tests__/__snapshots__/config.test.ts.snap index def5ff60bc7cacbee24bb0ea9cb6d18655235ea9..87f03de69bb50d0a121e13a2502f2317fecf7bcb 100644 --- a/packages/@core/preferences/__tests__/__snapshots__/config.test.ts.snap +++ b/packages/@core/preferences/__tests__/__snapshots__/config.test.ts.snap @@ -46,6 +46,7 @@ exports[`defaultPreferences immutability test > should not modify the config obj "header": { "enable": true, "hidden": false, + "menuAlign": "start", "mode": "fixed", }, "logo": { @@ -65,6 +66,7 @@ exports[`defaultPreferences immutability test > should not modify the config obj "globalSearch": true, }, "sidebar": { + "autoActivateChild": false, "collapsed": false, "collapsedShowTitle": false, "enable": true, @@ -83,6 +85,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/packages/@core/preferences/package.json b/packages/@core/preferences/package.json index 1fc760482379cb892dc06a6a1116a4188f3a3f8c..26232bf561ff70afeb49d02dfe79d3b1fb44e9e5 100644 --- a/packages/@core/preferences/package.json +++ b/packages/@core/preferences/package.json @@ -1,6 +1,6 @@ { "name": "@vben-core/preferences", - "version": "5.4.8", + "version": "5.5.2", "homepage": "https://github.com/vbenjs/vue-vben-admin", "bugs": "https://github.com/vbenjs/vue-vben-admin/issues", "repository": { diff --git a/packages/@core/preferences/src/config.ts b/packages/@core/preferences/src/config.ts index 2ee0deaff58c3a2bd3e31a85085c856d0eafb2d8..a5912a0672275ad2138e22e1592d0e89ca08c6d4 100644 --- a/packages/@core/preferences/src/config.ts +++ b/packages/@core/preferences/src/config.ts @@ -46,6 +46,7 @@ const defaultPreferences: Preferences = { header: { enable: true, hidden: false, + menuAlign: 'start', mode: 'fixed', }, logo: { @@ -65,6 +66,7 @@ const defaultPreferences: Preferences = { globalSearch: true, }, sidebar: { + autoActivateChild: false, collapsed: false, collapsedShowTitle: false, enable: true, @@ -83,6 +85,7 @@ const defaultPreferences: Preferences = { showMaximize: true, showMore: true, styleType: 'chrome', + wheelable: true, }, theme: { builtinType: 'default', diff --git a/packages/@core/preferences/src/types.ts b/packages/@core/preferences/src/types.ts index 2b536b85a81887d77bf1a848c5bed8c24d727a0d..f8b35242c6ac9fe4098f871abbb68da9b99122eb 100644 --- a/packages/@core/preferences/src/types.ts +++ b/packages/@core/preferences/src/types.ts @@ -5,6 +5,7 @@ import type { BuiltinThemeType, ContentCompactType, DeepPartial, + LayoutHeaderMenuAlignType, LayoutHeaderModeType, LayoutType, LoginExpiredModeType, @@ -104,6 +105,8 @@ interface HeaderPreferences { enable: boolean; /** 顶栏是否隐藏,css-隐藏 */ hidden: boolean; + /** 顶栏菜单位置 */ + menuAlign: LayoutHeaderMenuAlignType; /** header显示模式 */ mode: LayoutHeaderModeType; } @@ -125,6 +128,8 @@ interface NavigationPreferences { } interface SidebarPreferences { + /** 点击目录时自动激活子菜单 */ + autoActivateChild: boolean; /** 侧边栏是否折叠 */ collapsed: boolean; /** 侧边栏折叠时,是否显示title */ @@ -173,6 +178,8 @@ interface TabbarPreferences { showMore: boolean; /** 标签页风格 */ styleType: TabsStyleType; + /** 是否开启鼠标滚轮响应 */ + wheelable: boolean; } interface ThemePreferences { diff --git a/packages/@core/ui-kit/form-ui/package.json b/packages/@core/ui-kit/form-ui/package.json index ed5eb535e3fb14cbd690241b055e16f33a9e9d27..a3f42ded60632aacac4feb3f943b2333d78cb769 100644 --- a/packages/@core/ui-kit/form-ui/package.json +++ b/packages/@core/ui-kit/form-ui/package.json @@ -1,6 +1,6 @@ { "name": "@vben-core/form-ui", - "version": "5.4.8", + "version": "5.5.2", "homepage": "https://github.com/vbenjs/vue-vben-admin", "bugs": "https://github.com/vbenjs/vue-vben-admin/issues", "repository": { diff --git a/packages/@core/ui-kit/form-ui/src/components/form-actions.vue b/packages/@core/ui-kit/form-ui/src/components/form-actions.vue index 26e426fe55f352fb2c697c5af116b679e7c166b7..b9a878e8a8c34f3e474be487c758fb977ee103e1 100644 --- a/packages/@core/ui-kit/form-ui/src/components/form-actions.vue +++ b/packages/@core/ui-kit/form-ui/src/components/form-actions.vue @@ -138,17 +138,37 @@ defineExpose({