From 16162c01eddcb812b7a2e130a8465e8eb6fdcbc2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=8E=8B=E6=96=87=E5=BA=AD?= <31032745+wangwenting-dev@users.noreply.github.com> Date: Wed, 8 Jan 2025 16:01:23 +0800 Subject: [PATCH 1/8] fix: download from url triggered twice sometimes (#5319) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 解决Chrome、Safari通过路径一次下载两个文件的BUG --- packages/@core/base/shared/src/utils/download.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/@core/base/shared/src/utils/download.ts b/packages/@core/base/shared/src/utils/download.ts index 3e708d3c..6f38ee5b 100644 --- a/packages/@core/base/shared/src/utils/download.ts +++ b/packages/@core/base/shared/src/utils/download.ts @@ -31,6 +31,7 @@ export async function downloadFileFromUrl({ if (isChrome || isSafari) { triggerDownload(source, resolveFileName(source, fileName)); + return; } if (!source.includes('?')) { source += '?download'; From 2828e7a7b60d17a228d41cbbf391e10bbef516a5 Mon Sep 17 00:00:00 2001 From: Netfan Date: Thu, 9 Jan 2025 12:28:33 +0800 Subject: [PATCH 2/8] fix: form `fieldMappingTime` is not working (#5333) * fix: form option `fieldMappingTime` is not working * fix: form merge support `fieldMappingTime` --- .../form-ui/src/components/form-actions.vue | 55 +------------------ packages/@core/ui-kit/form-ui/src/form-api.ts | 54 ++++++++++++++++-- .../ui-kit/form-ui/src/use-form-context.ts | 6 +- .../ui-kit/form-ui/src/vben-use-form.vue | 6 +- .../plugins/src/echarts/use-echarts.ts | 2 +- .../plugins/src/vxe-table/use-vxe-grid.vue | 10 ++-- .../src/views/examples/vxe-table/form.vue | 7 ++- 7 files changed, 72 insertions(+), 68 deletions(-) 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 b9a878e8..fb90a5e4 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 @@ -3,12 +3,7 @@ import { computed, toRaw, unref, watch } from 'vue'; import { useSimpleLocale } from '@vben-core/composables'; import { VbenExpandableArrow } from '@vben-core/shadcn-ui'; -import { - cn, - formatDate, - isFunction, - triggerWindowResize, -} from '@vben-core/shared/utils'; +import { cn, isFunction, triggerWindowResize } from '@vben-core/shared/utils'; import { COMPONENT_MAP } from '../config'; import { injectFormProps } from '../use-form-context'; @@ -58,7 +53,7 @@ async function handleSubmit(e: Event) { return; } - const values = handleRangeTimeValue(toRaw(form.values)); + const values = toRaw(await unref(rootProps).formApi?.getValues()); await unref(rootProps).handleSubmit?.(values); } @@ -67,13 +62,7 @@ async function handleReset(e: Event) { e?.stopPropagation(); const props = unref(rootProps); - const values = toRaw(form.values); - // 清理时间字段 - props.fieldMappingTime && - props.fieldMappingTime.forEach(([_, [startTimeKey, endTimeKey]]) => { - delete values[startTimeKey]; - delete values[endTimeKey]; - }); + const values = toRaw(props.formApi?.getValues()); if (isFunction(props.handleReset)) { await props.handleReset?.(values); @@ -82,44 +71,6 @@ async function handleReset(e: Event) { } } -function handleRangeTimeValue(values: Record) { - const fieldMappingTime = unref(rootProps).fieldMappingTime; - - if (!fieldMappingTime || !Array.isArray(fieldMappingTime)) { - return values; - } - - fieldMappingTime.forEach( - ([field, [startTimeKey, endTimeKey], format = 'YYYY-MM-DD']) => { - if (startTimeKey && endTimeKey && values[field] === null) { - delete values[startTimeKey]; - delete values[endTimeKey]; - } - - if (!values[field]) { - delete values[field]; - return; - } - - const [startTime, endTime] = values[field]; - const [startTimeFormat, endTimeFormat] = Array.isArray(format) - ? format - : [format, format]; - - values[startTimeKey] = startTime - ? formatDate(startTime, startTimeFormat) - : undefined; - values[endTimeKey] = endTime - ? formatDate(endTime, endTimeFormat) - : undefined; - - delete values[field]; - }, - ); - - return values; -} - watch( () => collapsed.value, () => { 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 9fc98a3c..173e1ba1 100644 --- a/packages/@core/ui-kit/form-ui/src/form-api.ts +++ b/packages/@core/ui-kit/form-ui/src/form-api.ts @@ -15,6 +15,7 @@ import { Store } from '@vben-core/shared/store'; import { bindMethods, createMerge, + formatDate, isDate, isDayjsObject, isFunction, @@ -94,7 +95,7 @@ export class FormApi { async getValues() { const form = await this.getForm(); - return form.values; + return this.handleRangeTimeValue(form.values); } async isFieldValid(fieldName: string) { @@ -117,12 +118,11 @@ export class FormApi { try { const results = await Promise.all( chain.map(async (api) => { - const form = await api.getForm(); const validateResult = await api.validate(); if (!validateResult.valid) { return; } - const rawValues = toRaw(form.values || {}); + const rawValues = toRaw((await api.getValues()) || {}); return rawValues; }), ); @@ -147,7 +147,9 @@ export class FormApi { if (!this.isMounted) { Object.assign(this.form, formActions); this.stateHandler.setConditionTrue(); - this.setLatestSubmissionValues({ ...toRaw(this.form.values) }); + this.setLatestSubmissionValues({ + ...toRaw(this.handleRangeTimeValue(this.form.values)), + }); this.isMounted = true; } } @@ -253,7 +255,7 @@ export class FormApi { e?.stopPropagation(); const form = await this.getForm(); await form.submitForm(); - const rawValues = toRaw(form.values || {}); + const rawValues = toRaw(await this.getValues()); await this.state?.handleSubmit?.(rawValues); return rawValues; @@ -342,6 +344,48 @@ export class FormApi { return this.form; } + private handleRangeTimeValue = (originValues: Record) => { + const values = { ...originValues }; + const fieldMappingTime = this.state?.fieldMappingTime; + + if (!fieldMappingTime || !Array.isArray(fieldMappingTime)) { + return values; + } + + fieldMappingTime.forEach( + ([field, [startTimeKey, endTimeKey], format = 'YYYY-MM-DD']) => { + if (startTimeKey && endTimeKey && values[field] === null) { + Reflect.deleteProperty(values, startTimeKey); + Reflect.deleteProperty(values, endTimeKey); + // delete values[startTimeKey]; + // delete values[endTimeKey]; + } + + if (!values[field]) { + Reflect.deleteProperty(values, field); + // delete values[field]; + return; + } + + const [startTime, endTime] = values[field]; + const [startTimeFormat, endTimeFormat] = Array.isArray(format) + ? format + : [format, format]; + + values[startTimeKey] = startTime + ? formatDate(startTime, startTimeFormat) + : undefined; + values[endTimeKey] = endTime + ? formatDate(endTime, endTimeFormat) + : undefined; + + // delete values[field]; + Reflect.deleteProperty(values, field); + }, + ); + return values; + }; + private updateState() { const currentSchema = this.state?.schema ?? []; const prevSchema = this.prevState?.schema ?? []; diff --git a/packages/@core/ui-kit/form-ui/src/use-form-context.ts b/packages/@core/ui-kit/form-ui/src/use-form-context.ts index 879a64c6..8e37300e 100644 --- a/packages/@core/ui-kit/form-ui/src/use-form-context.ts +++ b/packages/@core/ui-kit/form-ui/src/use-form-context.ts @@ -2,7 +2,7 @@ import type { ZodRawShape } from 'zod'; import type { ComputedRef } from 'vue'; -import type { FormActions, VbenFormProps } from './types'; +import type { ExtendedFormApi, FormActions, VbenFormProps } from './types'; import { computed, unref, useSlots } from 'vue'; @@ -13,8 +13,10 @@ import { useForm } from 'vee-validate'; import { object } from 'zod'; import { getDefaultsForSchema } from 'zod-defaults'; +type ExtendFormProps = VbenFormProps & { formApi: ExtendedFormApi }; + export const [injectFormProps, provideFormProps] = - createContext<[ComputedRef | VbenFormProps, FormActions]>( + createContext<[ComputedRef | ExtendFormProps, FormActions]>( 'VbenFormProps', ); 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 8f0efea9..a5a7f660 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 @@ -53,8 +53,10 @@ function handleKeyDownEnter(event: KeyboardEvent) { forward.value.formApi.validateAndSubmitForm(); } -const handleValuesChangeDebounced = useDebounceFn((newVal) => { - forward.value.handleValuesChange?.(cloneDeep(newVal)); +const handleValuesChangeDebounced = useDebounceFn(async () => { + forward.value.handleValuesChange?.( + cloneDeep(await forward.value.formApi.getValues()), + ); state.value.submitOnChange && forward.value.formApi?.validateAndSubmitForm(); }, 300); diff --git a/packages/effects/plugins/src/echarts/use-echarts.ts b/packages/effects/plugins/src/echarts/use-echarts.ts index 0fd7c4b6..ec5786e8 100644 --- a/packages/effects/plugins/src/echarts/use-echarts.ts +++ b/packages/effects/plugins/src/echarts/use-echarts.ts @@ -109,7 +109,7 @@ function useEcharts(chartRef: Ref) { return { renderEcharts, resize, - chartInstance + chartInstance, }; } diff --git a/packages/effects/plugins/src/vxe-table/use-vxe-grid.vue b/packages/effects/plugins/src/vxe-table/use-vxe-grid.vue index 707ea59e..cbae6f29 100644 --- a/packages/effects/plugins/src/vxe-table/use-vxe-grid.vue +++ b/packages/effects/plugins/src/vxe-table/use-vxe-grid.vue @@ -8,6 +8,8 @@ import type { VxeToolbarPropTypes, } from 'vxe-table'; +import type { SetupContext } from 'vue'; + import type { VbenFormProps } from '@vben-core/form-ui'; import type { ExtendedVxeGridApi, VxeGridProps } from './types'; @@ -68,18 +70,18 @@ const { const { isMobile } = usePreferences(); -const slots = useSlots(); +const slots: SetupContext['slots'] = useSlots(); const [Form, formApi] = useTableForm({ compact: true, handleSubmit: async () => { - const formValues = formApi.form.values; + const formValues = await formApi.getValues(); formApi.setLatestSubmissionValues(toRaw(formValues)); props.api.reload(formValues); }, handleReset: async () => { await formApi.resetForm(); - const formValues = formApi.form.values; + const formValues = await formApi.getValues(); formApi.setLatestSubmissionValues(formValues); props.api.reload(formValues); }, @@ -246,7 +248,7 @@ async function init() { const autoLoad = defaultGridOptions.proxyConfig?.autoLoad; const enableProxyConfig = options.value.proxyConfig?.enabled; if (enableProxyConfig && autoLoad) { - props.api.grid.commitProxy?.('_init', formApi.form?.values ?? {}); + props.api.grid.commitProxy?.('_init', (await formApi.getValues()) ?? {}); // props.api.reload(formApi.form?.values ?? {}); } diff --git a/playground/src/views/examples/vxe-table/form.vue b/playground/src/views/examples/vxe-table/form.vue index dd9e2a37..cda65168 100644 --- a/playground/src/views/examples/vxe-table/form.vue +++ b/playground/src/views/examples/vxe-table/form.vue @@ -5,6 +5,7 @@ import type { VxeTableGridOptions } from '#/adapter/vxe-table'; import { Page } from '@vben/common-ui'; import { message } from 'ant-design-vue'; +import dayjs from 'dayjs'; import { useVbenVxeGrid } from '#/adapter/vxe-table'; import { getExampleTableApi } from '#/api'; @@ -21,6 +22,7 @@ interface RowType { const formOptions: VbenFormProps = { // 默认展开 collapsed: false, + fieldMappingTime: [['date', ['start', 'end']]], schema: [ { component: 'Input', @@ -58,8 +60,9 @@ const formOptions: VbenFormProps = { label: 'Color', }, { - component: 'DatePicker', - fieldName: 'datePicker', + component: 'RangePicker', + defaultValue: [dayjs().subtract(-7, 'days'), dayjs()], + fieldName: 'date', label: 'Date', }, ], From 99c7fd72f80e2acb90fd36d8dcebeb85ecbf09be Mon Sep 17 00:00:00 2001 From: Netfan Date: Thu, 9 Jan 2025 13:04:14 +0800 Subject: [PATCH 3/8] fix: code lint --- docs/.vitepress/components/preview-group.vue | 4 +++- packages/@core/ui-kit/menu-ui/src/components/menu.vue | 4 ++-- playground/src/views/examples/vxe-table/form.vue | 2 +- 3 files changed, 6 insertions(+), 4 deletions(-) diff --git a/docs/.vitepress/components/preview-group.vue b/docs/.vitepress/components/preview-group.vue index c8c6e83c..e712157c 100644 --- a/docs/.vitepress/components/preview-group.vue +++ b/docs/.vitepress/components/preview-group.vue @@ -1,4 +1,6 @@ + From c979c23e6b76a944f60f78316a83d7275587b7c1 Mon Sep 17 00:00:00 2001 From: Netfan Date: Fri, 10 Jan 2025 01:15:30 +0800 Subject: [PATCH 5/8] fix: form valid-error style in naive (#5336) --- apps/web-naive/src/bootstrap.ts | 1 + packages/styles/package.json | 3 +++ packages/styles/src/naive/index.css | 20 ++++++++++++++++++++ 3 files changed, 24 insertions(+) create mode 100644 packages/styles/src/naive/index.css diff --git a/apps/web-naive/src/bootstrap.ts b/apps/web-naive/src/bootstrap.ts index fc7f961d..40416d82 100644 --- a/apps/web-naive/src/bootstrap.ts +++ b/apps/web-naive/src/bootstrap.ts @@ -4,6 +4,7 @@ import { registerAccessDirective } from '@vben/access'; import { preferences } from '@vben/preferences'; import { initStores } from '@vben/stores'; import '@vben/styles'; +import '@vben/styles/naive'; import { useTitle } from '@vueuse/core'; diff --git a/packages/styles/package.json b/packages/styles/package.json index 5bfae7e5..518aea36 100644 --- a/packages/styles/package.json +++ b/packages/styles/package.json @@ -21,6 +21,9 @@ "./ele": { "default": "./src/ele/index.css" }, + "./naive": { + "default": "./src/naive/index.css" + }, "./global": { "default": "./src/global/index.scss" } diff --git a/packages/styles/src/naive/index.css b/packages/styles/src/naive/index.css new file mode 100644 index 00000000..a7751165 --- /dev/null +++ b/packages/styles/src/naive/index.css @@ -0,0 +1,20 @@ +.form-valid-error { + .n-base-selection__state-border, + .n-input__state-border, + .n-radio-group__splitor { + border: var(--n-border-error); + } + + .n-radio-group .n-radio-button, + .n-radio-group .n-radio-group__splitor { + --n-button-border-color: rgb(255 56 96); + } + + .n-radio__dot { + --n-box-shadow: inset 0 0 0 1px rgb(255 56 96); + } + + .n-checkbox-box__border { + --n-border: 1px solid rgb(255 56 96); + } +} From d34838bdd88460234e74e1f42b28db79c75f9493 Mon Sep 17 00:00:00 2001 From: Netfan Date: Fri, 10 Jan 2025 01:51:38 +0800 Subject: [PATCH 6/8] fix: primaryColor calculation (#5337) --- apps/web-naive/src/views/demos/form/basic.vue | 18 ++++++++++++++- .../preferences/blocks/theme/builtin.vue | 23 ++++++++++++++----- 2 files changed, 34 insertions(+), 7 deletions(-) diff --git a/apps/web-naive/src/views/demos/form/basic.vue b/apps/web-naive/src/views/demos/form/basic.vue index 7d04ff4d..fe26624c 100644 --- a/apps/web-naive/src/views/demos/form/basic.vue +++ b/apps/web-naive/src/views/demos/form/basic.vue @@ -40,6 +40,7 @@ const [Form, formApi] = useVbenForm({ fieldName: 'api', // 界面显示的label label: 'ApiSelect', + rules: 'required', }, { component: 'ApiTreeSelect', @@ -56,16 +57,19 @@ const [Form, formApi] = useVbenForm({ fieldName: 'apiTree', // 界面显示的label label: 'ApiTreeSelect', + rules: 'required', }, { component: 'Input', fieldName: 'string', label: 'String', + rules: 'required', }, { component: 'InputNumber', fieldName: 'number', label: 'Number', + rules: 'required', }, { component: 'RadioGroup', @@ -80,6 +84,7 @@ const [Form, formApi] = useVbenForm({ { value: 'E', label: 'E' }, ], }, + rules: 'selectRequired', }, { component: 'RadioGroup', @@ -94,9 +99,9 @@ const [Form, formApi] = useVbenForm({ { value: 'C', label: '选项C' }, { value: 'D', label: '选项D' }, { value: 'E', label: '选项E' }, - { value: 'F', label: '选项F' }, ], }, + rules: 'selectRequired', }, { component: 'CheckboxGroup', @@ -109,11 +114,22 @@ const [Form, formApi] = useVbenForm({ { value: 'C', label: '选项C' }, ], }, + rules: 'selectRequired', }, { component: 'DatePicker', fieldName: 'date', label: 'Date', + rules: 'required', + }, + { + component: 'Input', + fieldName: 'textArea', + label: 'TextArea', + componentProps: { + type: 'textarea', + }, + rules: 'required', }, ], }); diff --git a/packages/effects/layouts/src/widgets/preferences/blocks/theme/builtin.vue b/packages/effects/layouts/src/widgets/preferences/blocks/theme/builtin.vue index d4b265ee..8a41763a 100644 --- a/packages/effects/layouts/src/widgets/preferences/blocks/theme/builtin.vue +++ b/packages/effects/layouts/src/widgets/preferences/blocks/theme/builtin.vue @@ -2,7 +2,7 @@ import type { BuiltinThemePreset } from '@vben/preferences'; import type { BuiltinThemeType } from '@vben/types'; -import { computed, ref } from 'vue'; +import { computed, ref, watch } from 'vue'; import { UserRoundPen } from '@vben/icons'; import { $t } from '@vben/locales'; @@ -80,11 +80,6 @@ function typeView(name: BuiltinThemeType) { function handleSelect(theme: BuiltinThemePreset) { modelValue.value = theme.type; - const primaryColor = props.isDark - ? theme.darkPrimaryColor || theme.primaryColor - : theme.primaryColor; - - themeColorPrimary.value = primaryColor || theme.color; } function handleInputChange(e: Event) { @@ -95,6 +90,22 @@ function handleInputChange(e: Event) { function selectColor() { colorInput.value?.[0]?.click?.(); } + +watch( + () => [modelValue.value, props.isDark] as [BuiltinThemeType, boolean], + ([themeType, isDark]) => { + const theme = builtinThemePresets.value.find( + (item) => item.type === themeType, + ); + if (theme) { + const primaryColor = isDark + ? theme.darkPrimaryColor || theme.primaryColor + : theme.primaryColor; + + themeColorPrimary.value = primaryColor || theme.color; + } + }, +);