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/apps/web-antd/package.json b/apps/web-antd/package.json index 42afa3c696d2df2539f2224c3a7c3470220effa1..bbc5593f0ab65d63e24367409df8492268bb2ff2 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.1", "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-ele/package.json b/apps/web-ele/package.json index 4301569515093c9e8311fe7512e37d0d5c772af8..a02376ee8700a96db3618397904c3e3863ec5737 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.1", "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/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/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..90aaccef305908ee636ad6aedeafd1de2b40e76d --- /dev/null +++ b/apps/web-ele/src/views/demos/form/basic.vue @@ -0,0 +1,180 @@ + + diff --git a/apps/web-naive/package.json b/apps/web-naive/package.json index 0710c3415bf11aad09cede1887a4d0c3912f08ee..5bd3b1a74ba17d50ecd34884d5df11c9bd90f104 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.1", "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/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/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/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..25e93ced867d93bdae3148179c09233bc470ab15 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 模态框', diff --git a/docs/package.json b/docs/package.json index 15a87bb971f47cadba26418c7a6146af5f446f53..2d56a8ba57c033d67884668602a3f82f49b512a8 100644 --- a/docs/package.json +++ b/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/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..f9db74e4fdf2d51ecd384bd815d4d3de6ef2798d --- /dev/null +++ b/docs/src/components/common-ui/vben-api-component.md @@ -0,0 +1,150 @@ +--- +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 + + +``` + +::: + +### 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-form.md b/docs/src/components/common-ui/vben-form.md index 0ed4db404a541903d5236928108567a799ffd897..618e3d3a50d160227645798d440f40565f699347 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'), @@ -304,6 +306,8 @@ 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` | @@ -314,6 +318,7 @@ useVbenForm 返回的第二个参数,是一个对象,包含了一些表单 | commonConfig | 表单项的通用配置,每个配置都会传递到每个表单项,表单项可覆盖 | `FormCommonConfig` | - | | schema | 表单项的每一项配置 | `FormSchema` | - | | submitOnEnter | 按下回车健时提交表单 | `boolean` | false | +| submitOnChange | 字段值改变时提交表单 | `boolean` | false | ### TS 类型说明 @@ -419,7 +424,7 @@ export interface FormSchema< help?: string; /** 表单项 */ label?: string; - // 自定义组件内部渲染 + /** 自定义组件内部渲染 */ renderComponentContent?: RenderComponentContentType; /** 字段规则 */ rules?: FormSchemaRuleType; @@ -500,3 +505,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-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/internal/lint-configs/commitlint-config/package.json b/internal/lint-configs/commitlint-config/package.json index 6679a66274e15a6296d3af4894669dec477e4a23..01b43088c96fe28b3bb3576fe76315df2c1512ad 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.1", "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/stylelint-config/package.json b/internal/lint-configs/stylelint-config/package.json index c9e8cf53977c2907f54b951526a0564762c45444..64815741ffb1d3c9c9d91016dc95cf40d69aec3a 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.1", "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..9412656a941f59f715925bc8444271515140c306 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.1", "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..26da63b485256978a72fc66f60b86b845d018cb8 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.1", "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..d935b671c19c4d166572b7ce0c71399fa21078e1 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.1", "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..60955a1d44903551b96acbfda80585855858c131 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.1", "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/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..5e449593df905ad9d3b13942e926e6c425d9c9c8 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "vben-admin-monorepo", - "version": "5.4.8", + "version": "5.5.1", "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.0", "pnpm": { "peerDependencyRules": { "allowedVersions": { diff --git a/packages/@core/base/design/package.json b/packages/@core/base/design/package.json index cbe5159c00c3cd152bb917349d1295e970fceea0..7b46ddd7437952a9013c47542cacdefdc4a9c856 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.1", "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..754b745097c7f62df3176eb86a5e42594122b89c 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.1", "homepage": "https://github.com/vbenjs/vue-vben-admin", "bugs": "https://github.com/vbenjs/vue-vben-admin/issues", "repository": { diff --git a/packages/@core/base/shared/package.json b/packages/@core/base/shared/package.json index 15ba9010cb9e0ee1c1cf06d689f5b33dc75e76f6..20eeea450c67e88501bf56037f878665d670aa6a 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.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/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..504e53ed84d1c277db9e4624b6eeeb0027a1fb90 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.1", "homepage": "https://github.com/vbenjs/vue-vben-admin", "bugs": "https://github.com/vbenjs/vue-vben-admin/issues", "repository": { diff --git a/packages/@core/composables/package.json b/packages/@core/composables/package.json index bac205809f205d15cf4953b5df6936fd023b5402..736feefa28f66363b7cc2958a4b35a9459a0d515 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.1", "homepage": "https://github.com/vbenjs/vue-vben-admin", "bugs": "https://github.com/vbenjs/vue-vben-admin/issues", "repository": { diff --git a/packages/@core/preferences/package.json b/packages/@core/preferences/package.json index 1fc760482379cb892dc06a6a1116a4188f3a3f8c..24b522e21bff28447ab709ee2e7122a785e2bcd9 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.1", "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/package.json b/packages/@core/ui-kit/form-ui/package.json index ed5eb535e3fb14cbd690241b055e16f33a9e9d27..944fd49ef55357dd8845f491a176c2fa0ebcc3e8 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.1", "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..ac5505d5e368573c819901a6b7652770abda00d3 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 @@ -142,13 +142,29 @@ defineExpose({ " :style="queryFormStyle" > + + - - - - - {{ submitButtonOptions.content }} - + diff --git a/packages/@core/ui-kit/form-ui/src/form-api.ts b/packages/@core/ui-kit/form-ui/src/form-api.ts index 07de5d75ce31407a5c487f5ece0e0325112ca679..585afaff2fcbdb4d690461f99a0d4be35abf526e 100644 --- a/packages/@core/ui-kit/form-ui/src/form-api.ts +++ b/packages/@core/ui-kit/form-ui/src/form-api.ts @@ -14,6 +14,8 @@ import { Store } from '@vben-core/shared/store'; import { bindMethods, createMerge, + isDate, + isDayjsObject, isFunction, isObject, mergeWithArrayOverride, @@ -36,6 +38,7 @@ function getDefaultState(): VbenFormProps { showCollapseButton: false, showDefaultActions: true, submitButtonOptions: {}, + submitOnChange: false, submitOnEnter: false, wrapperClass: 'grid-cols-1', }; @@ -185,7 +188,7 @@ export class FormApi { const fieldSet = new Set(fields); const schema = this.state?.schema ?? []; - const filterSchema = schema.filter((item) => fieldSet.has(item.fieldName)); + const filterSchema = schema.filter((item) => !fieldSet.has(item.fieldName)); this.setState({ schema: filterSchema, @@ -251,10 +254,19 @@ export class FormApi { return; } + /** + * 合并算法有待改进,目前的算法不支持object类型的值。 + * antd的日期时间相关组件的值类型为dayjs对象 + * element-plus的日期时间相关组件的值类型可能为Date对象 + * 以上两种类型需要排除深度合并 + */ const fieldMergeFn = createMerge((obj, key, value) => { if (key in obj) { obj[key] = - !Array.isArray(obj[key]) && isObject(obj[key]) + !Array.isArray(obj[key]) && + isObject(obj[key]) && + !isDayjsObject(obj[key]) && + !isDate(obj[key]) ? fieldMergeFn(obj[key], value) : value; } diff --git a/packages/@core/ui-kit/form-ui/src/types.ts b/packages/@core/ui-kit/form-ui/src/types.ts index 5855ccc60df8f1baac8efd09f3e238607df184ad..2f8a7be7f7af8c5f81b9efecfdf890dc1e36dd3b 100644 --- a/packages/@core/ui-kit/form-ui/src/types.ts +++ b/packages/@core/ui-kit/form-ui/src/types.ts @@ -307,6 +307,10 @@ export interface VbenFormProps< FormRenderProps, 'componentBindEventMap' | 'componentMap' | 'form' > { + /** + * 操作按钮是否反转(提交按钮前置) + */ + actionButtonsReverse?: boolean; /** * 表单操作区域class */ @@ -342,6 +346,12 @@ export interface VbenFormProps< */ submitButtonOptions?: ActionButtonOptions; + /** + * 是否在字段值改变时提交表单 + * @default false + */ + submitOnChange?: boolean; + /** * 是否在回车时提交表单 * @default false diff --git a/packages/@core/ui-kit/form-ui/src/vben-use-form.vue b/packages/@core/ui-kit/form-ui/src/vben-use-form.vue index 0401d48eea675a5dbbfda5d077686d7e7e26b424..855472d960b56b04fdbbdfd20c2eaf78a304db41 100644 --- a/packages/@core/ui-kit/form-ui/src/vben-use-form.vue +++ b/packages/@core/ui-kit/form-ui/src/vben-use-form.vue @@ -6,7 +6,9 @@ import type { ExtendedFormApi, VbenFormProps } from './types'; import { useForwardPriorityValues } from '@vben-core/composables'; // import { isFunction } from '@vben-core/shared/utils'; -import { useTemplateRef } from 'vue'; +import { toRaw, useTemplateRef, watch } from 'vue'; + +import { useDebounceFn } from '@vueuse/core'; import FormActions from './components/form-actions.vue'; import { @@ -56,6 +58,15 @@ function handleKeyDownEnter(event: KeyboardEvent) { formActionsRef.value?.handleSubmit?.(); } + +watch( + () => form.values, + useDebounceFn(() => { + forward.value.handleValuesChange?.(toRaw(form.values)); + state.value.submitOnChange && props.formApi?.submitForm(); + }, 300), + { deep: true }, +);