From a221d2b49136b58a9f3f5a9b3cab3e8ae13f82a9 Mon Sep 17 00:00:00 2001 From: Netfan Date: Thu, 20 Feb 2025 23:05:08 +0800 Subject: [PATCH 01/11] fix: form item overflow fixed and layout improved (#5572) * fix: form item overflow fixed and layout improved * fix: basic form demo update * feat: form label support render * fix: form docs update --- docs/src/components/common-ui/vben-form.md | 6 +- docs/src/guide/essentials/settings.md | 4 +- .../form-ui/src/form-render/form-field.vue | 90 ++++++++++--------- .../form-ui/src/form-render/form-label.vue | 12 ++- packages/@core/ui-kit/form-ui/src/types.ts | 6 +- playground/src/views/examples/form/basic.vue | 18 +++- 6 files changed, 80 insertions(+), 56 deletions(-) diff --git a/docs/src/components/common-ui/vben-form.md b/docs/src/components/common-ui/vben-form.md index ecaae6f8..7ad64136 100644 --- a/docs/src/components/common-ui/vben-form.md +++ b/docs/src/components/common-ui/vben-form.md @@ -445,9 +445,9 @@ export interface FormSchema< /** 字段名,也作为自定义插槽的名称 */ fieldName: string; /** 帮助信息 */ - help?: string; - /** 表单项 */ - label?: string; + help?: CustomRenderType; + /** 表单的标签(如果是一个string,会用于默认必选规则的消息提示) */ + label?: CustomRenderType; /** 自定义组件内部渲染 */ renderComponentContent?: RenderComponentContentType; /** 字段规则 */ diff --git a/docs/src/guide/essentials/settings.md b/docs/src/guide/essentials/settings.md index 3669a771..a75838f6 100644 --- a/docs/src/guide/essentials/settings.md +++ b/docs/src/guide/essentials/settings.md @@ -538,4 +538,6 @@ interface Preferences { - `overridesPreferences`方法只需要覆盖项目中的一部分配置,不需要的配置不用覆盖,会自动使用默认配置。 - 任何配置项都可以覆盖,只需要在`overridesPreferences`方法内覆盖即可,不要修改默认配置文件。 -- 更改配置后请清空缓存,否则可能不生效。::: +- 更改配置后请清空缓存,否则可能不生效。 + +::: diff --git a/packages/@core/ui-kit/form-ui/src/form-render/form-field.vue b/packages/@core/ui-kit/form-ui/src/form-render/form-field.vue index 90019361..f4e006cd 100644 --- a/packages/@core/ui-kit/form-ui/src/form-render/form-field.vue +++ b/packages/@core/ui-kit/form-ui/src/form-render/form-field.vue @@ -193,7 +193,7 @@ const fieldProps = computed(() => { const rules = fieldRules.value; return { keepValue: true, - label, + label: isString(label) ? label : '', ...(rules ? { rules } : {}), ...(formFieldProps as Record), }; @@ -285,7 +285,7 @@ function autofocus() { 'pb-6': !compact, 'pb-2': compact, }" - class="flex" + class="relative flex" v-bind="$attrs" > -
- - - +
+
+ + - - - - - - -
- + + + + + + +
+ + +
+ +
+ + +
- - - - - +
diff --git a/packages/@core/ui-kit/form-ui/src/form-render/form-label.vue b/packages/@core/ui-kit/form-ui/src/form-render/form-label.vue index 459d5ee5..4ad10be2 100644 --- a/packages/@core/ui-kit/form-ui/src/form-render/form-label.vue +++ b/packages/@core/ui-kit/form-ui/src/form-render/form-label.vue @@ -1,10 +1,16 @@ + + diff --git a/playground/src/views/examples/form/rules.vue b/playground/src/views/examples/form/rules.vue index dd95894c..7ec95493 100644 --- a/playground/src/views/examples/form/rules.vue +++ b/playground/src/views/examples/form/rules.vue @@ -150,7 +150,9 @@ const [Form, formApi] = useVbenForm({ default: () => ['我已阅读并同意'], }; }, - rules: 'selectRequired', + rules: z.boolean().refine((value) => value, { + message: '请勾选', + }), }, { component: 'DatePicker', From 579b1b486cdb881d174d09d27ef17daa265c0246 Mon Sep 17 00:00:00 2001 From: Netfan Date: Sun, 23 Feb 2025 12:41:54 +0800 Subject: [PATCH 06/11] feat: loading and spinner component with directive (#5587) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * 添加loading和spinner组件,以及对应的vue指令 --- apps/web-antd/src/bootstrap.ts | 8 +- apps/web-ele/src/bootstrap.ts | 8 +- apps/web-naive/src/bootstrap.ts | 8 +- .../src/components/spinner/loading.vue | 21 +-- .../src/components/spinner/spinner.vue | 3 +- .../effects/common-ui/src/components/index.ts | 1 + .../src/components/loading/directive.ts | 132 ++++++++++++++++++ .../common-ui/src/components/loading/index.ts | 3 + .../src/components/loading/loading.vue | 19 +++ .../src/components/loading/spinner.vue | 14 ++ playground/src/bootstrap.ts | 8 +- .../src/router/routes/modules/examples.ts | 9 ++ .../src/views/examples/loading/index.vue | 101 ++++++++++++++ 13 files changed, 321 insertions(+), 14 deletions(-) create mode 100644 packages/effects/common-ui/src/components/loading/directive.ts create mode 100644 packages/effects/common-ui/src/components/loading/index.ts create mode 100644 packages/effects/common-ui/src/components/loading/loading.vue create mode 100644 packages/effects/common-ui/src/components/loading/spinner.vue create mode 100644 playground/src/views/examples/loading/index.vue diff --git a/apps/web-antd/src/bootstrap.ts b/apps/web-antd/src/bootstrap.ts index d2dad44e..fe7d9650 100644 --- a/apps/web-antd/src/bootstrap.ts +++ b/apps/web-antd/src/bootstrap.ts @@ -1,7 +1,7 @@ import { createApp, watchEffect } from 'vue'; import { registerAccessDirective } from '@vben/access'; -import { initTippy } from '@vben/common-ui'; +import { initTippy, registerLoadingDirective } from '@vben/common-ui'; import { MotionPlugin } from '@vben/plugins/motion'; import { preferences } from '@vben/preferences'; import { initStores } from '@vben/stores'; @@ -31,6 +31,12 @@ async function bootstrap(namespace: string) { const app = createApp(App); + // 注册v-loading指令 + registerLoadingDirective(app, { + loading: 'loading', // 在这里可以自定义指令名称,也可以明确提供false表示不注册这个指令 + spinning: 'spinning', + }); + // 国际化 i18n 配置 await setupI18n(app); diff --git a/apps/web-ele/src/bootstrap.ts b/apps/web-ele/src/bootstrap.ts index 1e4cc3ef..7e9e1cd4 100644 --- a/apps/web-ele/src/bootstrap.ts +++ b/apps/web-ele/src/bootstrap.ts @@ -1,7 +1,7 @@ import { createApp, watchEffect } from 'vue'; import { registerAccessDirective } from '@vben/access'; -import { initTippy } from '@vben/common-ui'; +import { initTippy, registerLoadingDirective } from '@vben/common-ui'; import { MotionPlugin } from '@vben/plugins/motion'; import { preferences } from '@vben/preferences'; import { initStores } from '@vben/stores'; @@ -33,6 +33,12 @@ async function bootstrap(namespace: string) { // 注册Element Plus提供的v-loading指令 app.directive('loading', ElLoading.directive); + // 注册Vben提供的v-loading和v-spinning指令 + registerLoadingDirective(app, { + loading: false, // Vben提供的v-loading指令和Element Plus提供的v-loading指令二选一即可,此处false表示不注册Vben提供的v-loading指令 + spinning: 'spinning', + }); + // 国际化 i18n 配置 await setupI18n(app); diff --git a/apps/web-naive/src/bootstrap.ts b/apps/web-naive/src/bootstrap.ts index cc3e775a..b8c21ad2 100644 --- a/apps/web-naive/src/bootstrap.ts +++ b/apps/web-naive/src/bootstrap.ts @@ -1,7 +1,7 @@ import { createApp, watchEffect } from 'vue'; import { registerAccessDirective } from '@vben/access'; -import { initTippy } from '@vben/common-ui'; +import { initTippy, registerLoadingDirective } from '@vben/common-ui'; import { MotionPlugin } from '@vben/plugins/motion'; import { preferences } from '@vben/preferences'; import { initStores } from '@vben/stores'; @@ -31,6 +31,12 @@ async function bootstrap(namespace: string) { const app = createApp(App); + // 注册v-loading指令 + registerLoadingDirective(app, { + loading: 'loading', // 在这里可以自定义指令名称,也可以明确提供false表示不注册这个指令 + spinning: 'spinning', + }); + // 国际化 i18n 配置 await setupI18n(app); diff --git a/packages/@core/ui-kit/shadcn-ui/src/components/spinner/loading.vue b/packages/@core/ui-kit/shadcn-ui/src/components/spinner/loading.vue index 756fa0fd..79dbda1a 100644 --- a/packages/@core/ui-kit/shadcn-ui/src/components/spinner/loading.vue +++ b/packages/@core/ui-kit/shadcn-ui/src/components/spinner/loading.vue @@ -31,7 +31,7 @@ const props = withDefaults(defineProps(), { }); // const startTime = ref(0); const showSpinner = ref(false); -const renderSpinner = ref(true); +const renderSpinner = ref(false); const timer = ref>(); watch( @@ -69,7 +69,7 @@ function onTransitionEnd() {
- - - + + + + +
{{ text }}
+
diff --git a/packages/@core/ui-kit/shadcn-ui/src/components/spinner/spinner.vue b/packages/@core/ui-kit/shadcn-ui/src/components/spinner/spinner.vue index 58eab2eb..cde39014 100644 --- a/packages/@core/ui-kit/shadcn-ui/src/components/spinner/spinner.vue +++ b/packages/@core/ui-kit/shadcn-ui/src/components/spinner/spinner.vue @@ -25,7 +25,7 @@ const props = withDefaults(defineProps(), { }); // const startTime = ref(0); const showSpinner = ref(false); -const renderSpinner = ref(true); +const renderSpinner = ref(false); const timer = ref>(); watch( @@ -74,6 +74,7 @@ function onTransitionEnd() { >
diff --git a/packages/effects/common-ui/src/components/index.ts b/packages/effects/common-ui/src/components/index.ts index 3fed8ee8..9c883077 100644 --- a/packages/effects/common-ui/src/components/index.ts +++ b/packages/effects/common-ui/src/components/index.ts @@ -5,6 +5,7 @@ export * from './count-to'; export * from './ellipsis-text'; export * from './icon-picker'; export * from './json-viewer'; +export * from './loading'; export * from './page'; export * from './resize'; export * from './tippy'; diff --git a/packages/effects/common-ui/src/components/loading/directive.ts b/packages/effects/common-ui/src/components/loading/directive.ts new file mode 100644 index 00000000..973a605d --- /dev/null +++ b/packages/effects/common-ui/src/components/loading/directive.ts @@ -0,0 +1,132 @@ +import type { App, Directive, DirectiveBinding } from 'vue'; + +import { h, render } from 'vue'; + +import { VbenLoading, VbenSpinner } from '@vben-core/shadcn-ui'; +import { isString } from '@vben-core/shared/utils'; + +const LOADING_INSTANCE_KEY = Symbol('loading'); +const SPINNER_INSTANCE_KEY = Symbol('spinner'); + +const CLASS_NAME_RELATIVE = 'spinner-parent--relative'; + +const loadingDirective: Directive = { + mounted(el, binding) { + const instance = h(VbenLoading, getOptions(binding)); + render(instance, el); + + el.classList.add(CLASS_NAME_RELATIVE); + el[LOADING_INSTANCE_KEY] = instance; + }, + unmounted(el) { + const instance = el[LOADING_INSTANCE_KEY]; + el.classList.remove(CLASS_NAME_RELATIVE); + render(null, el); + instance.el.remove(); + + el[LOADING_INSTANCE_KEY] = null; + }, + + updated(el, binding) { + const instance = el[LOADING_INSTANCE_KEY]; + const options = getOptions(binding); + if (options && instance?.component) { + try { + Object.keys(options).forEach((key) => { + instance.component.props[key] = options[key]; + }); + instance.component.update(); + } catch (error) { + console.error( + 'Failed to update loading component in directive:', + error, + ); + } + } + }, +}; + +function getOptions(binding: DirectiveBinding) { + if (binding.value === undefined) { + return { spinning: true }; + } else if (typeof binding.value === 'boolean') { + return { spinning: binding.value }; + } else { + return { ...binding.value }; + } +} + +const spinningDirective: Directive = { + mounted(el, binding) { + const instance = h(VbenSpinner, getOptions(binding)); + render(instance, el); + + el.classList.add(CLASS_NAME_RELATIVE); + el[SPINNER_INSTANCE_KEY] = instance; + }, + unmounted(el) { + const instance = el[SPINNER_INSTANCE_KEY]; + el.classList.remove(CLASS_NAME_RELATIVE); + render(null, el); + instance.el.remove(); + + el[SPINNER_INSTANCE_KEY] = null; + }, + + updated(el, binding) { + const instance = el[SPINNER_INSTANCE_KEY]; + const options = getOptions(binding); + if (options && instance?.component) { + try { + Object.keys(options).forEach((key) => { + instance.component.props[key] = options[key]; + }); + instance.component.update(); + } catch (error) { + console.error( + 'Failed to update spinner component in directive:', + error, + ); + } + } + }, +}; + +type loadingDirectiveParams = { + /** 是否注册loading指令。如果提供一个string,则将指令注册为指定的名称 */ + loading?: boolean | string; + /** 是否注册spinning指令。如果提供一个string,则将指令注册为指定的名称 */ + spinning?: boolean | string; +}; + +/** + * 注册loading指令 + * @param app + * @param params + */ +export function registerLoadingDirective( + app: App, + params?: loadingDirectiveParams, +) { + // 注入一个样式供指令使用,确保容器是相对定位 + const style = document.createElement('style'); + style.id = CLASS_NAME_RELATIVE; + style.innerHTML = ` + .${CLASS_NAME_RELATIVE} { + position: relative !important; + } + `; + document.head.append(style); + if (params?.loading !== false) { + app.directive( + isString(params?.loading) ? params.loading : 'loading', + loadingDirective, + ); + } + if (params?.spinning !== false) { + app.directive( + isString(params?.spinning) ? params.spinning : 'spinning', + spinningDirective, + ); + } +} diff --git a/packages/effects/common-ui/src/components/loading/index.ts b/packages/effects/common-ui/src/components/loading/index.ts new file mode 100644 index 00000000..2fbfb047 --- /dev/null +++ b/packages/effects/common-ui/src/components/loading/index.ts @@ -0,0 +1,3 @@ +export * from './directive'; +export { default as Loading } from './loading.vue'; +export { default as Spinner } from './spinner.vue'; diff --git a/packages/effects/common-ui/src/components/loading/loading.vue b/packages/effects/common-ui/src/components/loading/loading.vue new file mode 100644 index 00000000..6c19a12f --- /dev/null +++ b/packages/effects/common-ui/src/components/loading/loading.vue @@ -0,0 +1,19 @@ + + diff --git a/packages/effects/common-ui/src/components/loading/spinner.vue b/packages/effects/common-ui/src/components/loading/spinner.vue new file mode 100644 index 00000000..e4a22cd7 --- /dev/null +++ b/packages/effects/common-ui/src/components/loading/spinner.vue @@ -0,0 +1,14 @@ + + diff --git a/playground/src/bootstrap.ts b/playground/src/bootstrap.ts index bfa6e4cb..cecb3cf6 100644 --- a/playground/src/bootstrap.ts +++ b/playground/src/bootstrap.ts @@ -1,7 +1,7 @@ import { createApp, watchEffect } from 'vue'; import { registerAccessDirective } from '@vben/access'; -import { initTippy } from '@vben/common-ui'; +import { initTippy, registerLoadingDirective } from '@vben/common-ui'; import { MotionPlugin } from '@vben/plugins/motion'; import { preferences } from '@vben/preferences'; import { initStores } from '@vben/stores'; @@ -32,6 +32,12 @@ async function bootstrap(namespace: string) { const app = createApp(App); + // 注册v-loading指令 + registerLoadingDirective(app, { + loading: 'loading', // 在这里可以自定义指令名称,也可以明确提供false表示不注册这个指令 + spinning: 'spinning', + }); + // 国际化 i18n 配置 await setupI18n(app); diff --git a/playground/src/router/routes/modules/examples.ts b/playground/src/router/routes/modules/examples.ts index e131bf8b..e91c31ad 100644 --- a/playground/src/router/routes/modules/examples.ts +++ b/playground/src/router/routes/modules/examples.ts @@ -290,6 +290,15 @@ const routes: RouteRecordRaw[] = [ title: 'CountTo', }, }, + { + name: 'Loading', + path: '/examples/loading', + component: () => import('#/views/examples/loading/index.vue'), + meta: { + icon: 'mdi:circle-double', + title: 'Loading', + }, + }, ], }, ]; diff --git a/playground/src/views/examples/loading/index.vue b/playground/src/views/examples/loading/index.vue new file mode 100644 index 00000000..c31f7844 --- /dev/null +++ b/playground/src/views/examples/loading/index.vue @@ -0,0 +1,101 @@ + + From d49e3e81a49e87d05102d99e66a848a833c66917 Mon Sep 17 00:00:00 2001 From: Netfan Date: Sun, 23 Feb 2025 15:30:17 +0800 Subject: [PATCH 07/11] fix: loading and spinner style fixed and improved (#5588) --- .../src/components/spinner/loading.vue | 2 +- .../src/components/loading/loading.vue | 32 +++++++++++++++---- .../src/components/loading/spinner.vue | 24 +++++++++++--- 3 files changed, 46 insertions(+), 12 deletions(-) diff --git a/packages/@core/ui-kit/shadcn-ui/src/components/spinner/loading.vue b/packages/@core/ui-kit/shadcn-ui/src/components/spinner/loading.vue index 79dbda1a..0e1aa810 100644 --- a/packages/@core/ui-kit/shadcn-ui/src/components/spinner/loading.vue +++ b/packages/@core/ui-kit/shadcn-ui/src/components/spinner/loading.vue @@ -88,7 +88,7 @@ function onTransitionEnd() { -
{{ text }}
+
{{ text }}
diff --git a/packages/effects/common-ui/src/components/loading/loading.vue b/packages/effects/common-ui/src/components/loading/loading.vue index 6c19a12f..faf49e60 100644 --- a/packages/effects/common-ui/src/components/loading/loading.vue +++ b/packages/effects/common-ui/src/components/loading/loading.vue @@ -1,16 +1,36 @@