From 71e8d12b709362b01eccce00dd93e4d756e01157 Mon Sep 17 00:00:00 2001 From: Netfan Date: Mon, 7 Apr 2025 01:21:30 +0800 Subject: [PATCH 01/43] fix: improve prompt component (#5879) * fix: prompt component render fixed * fix: alert buttonAlign default value --- docs/src/components/common-ui/vben-alert.md | 26 ++++ docs/src/demos/vben-alert/alert/index.vue | 9 +- docs/src/demos/vben-alert/prompt/index.vue | 52 +++++++- .../ui-kit/popup-ui/src/alert/AlertBuilder.ts | 115 ++++++++++++------ .../@core/ui-kit/popup-ui/src/alert/alert.ts | 34 +++++- .../@core/ui-kit/popup-ui/src/alert/alert.vue | 5 +- 6 files changed, 193 insertions(+), 48 deletions(-) diff --git a/docs/src/components/common-ui/vben-alert.md b/docs/src/components/common-ui/vben-alert.md index 6a477e31..61caac6d 100644 --- a/docs/src/components/common-ui/vben-alert.md +++ b/docs/src/components/common-ui/vben-alert.md @@ -43,6 +43,9 @@ export type BeforeCloseScope = { isConfirm: boolean; }; +/** + * alert 属性 + */ export type AlertProps = { /** 关闭前的回调,如果返回false,则终止关闭 */ beforeClose?: ( @@ -50,6 +53,8 @@ export type AlertProps = { ) => boolean | Promise | undefined; /** 边框 */ bordered?: boolean; + /** 按钮对齐方式 */ + buttonAlign?: 'center' | 'end' | 'start'; /** 取消按钮的标题 */ cancelText?: string; /** 是否居中显示 */ @@ -62,6 +67,8 @@ export type AlertProps = { content: Component | string; /** 弹窗内容的额外样式 */ contentClass?: string; + /** 执行beforeClose回调期间,在内容区域显示一个loading遮罩*/ + contentMasking?: boolean; /** 弹窗的图标(在标题的前面) */ icon?: Component | IconType; /** 是否显示取消按钮 */ @@ -70,6 +77,25 @@ export type AlertProps = { title?: string; }; +/** prompt 属性 */ +export type PromptProps = { + /** 关闭前的回调,如果返回false,则终止关闭 */ + beforeClose?: (scope: { + isConfirm: boolean; + value: T | undefined; + }) => boolean | Promise | undefined; + /** 用于接受用户输入的组件 */ + component?: Component; + /** 输入组件的属性 */ + componentProps?: Recordable; + /** 输入组件的插槽 */ + componentSlots?: Recordable; + /** 默认值 */ + defaultValue?: T; + /** 输入组件的值属性名 */ + modelPropName?: string; +} & Omit; + /** * 函数签名 * alert和confirm的函数签名相同。 diff --git a/docs/src/demos/vben-alert/alert/index.vue b/docs/src/demos/vben-alert/alert/index.vue index 103ce64f..9ba18a4d 100644 --- a/docs/src/demos/vben-alert/alert/index.vue +++ b/docs/src/demos/vben-alert/alert/index.vue @@ -3,7 +3,7 @@ import { h } from 'vue'; import { alert, VbenButton } from '@vben/common-ui'; -import { Empty } from 'ant-design-vue'; +import { Result } from 'ant-design-vue'; function showAlert() { alert('This is an alert message'); @@ -18,7 +18,12 @@ function showIconAlert() { function showCustomAlert() { alert({ - content: h(Empty, { description: '什么都没有' }), + buttonAlign: 'center', + content: h(Result, { + status: 'success', + subTitle: '已成功创建订单。订单ID:2017182818828182881', + title: '操作成功', + }), }); } diff --git a/docs/src/demos/vben-alert/prompt/index.vue b/docs/src/demos/vben-alert/prompt/index.vue index 423124e7..c9cf5c3e 100644 --- a/docs/src/demos/vben-alert/prompt/index.vue +++ b/docs/src/demos/vben-alert/prompt/index.vue @@ -1,7 +1,10 @@ diff --git a/packages/@core/ui-kit/popup-ui/src/alert/AlertBuilder.ts b/packages/@core/ui-kit/popup-ui/src/alert/AlertBuilder.ts index d022d1cd..20e4254c 100644 --- a/packages/@core/ui-kit/popup-ui/src/alert/AlertBuilder.ts +++ b/packages/@core/ui-kit/popup-ui/src/alert/AlertBuilder.ts @@ -1,10 +1,10 @@ -import type { Component } from 'vue'; +import type { Component, VNode } from 'vue'; import type { Recordable } from '@vben-core/typings'; -import type { AlertProps, BeforeCloseScope } from './alert'; +import type { AlertProps, BeforeCloseScope, PromptProps } from './alert'; -import { h, ref, render } from 'vue'; +import { h, nextTick, ref, render } from 'vue'; import { useSimpleLocale } from '@vben-core/composables'; import { Input } from '@vben-core/shadcn-ui'; @@ -130,40 +130,58 @@ export function vbenConfirm( } export async function vbenPrompt( - options: Omit & { - beforeClose?: (scope: { - isConfirm: boolean; - value: T | undefined; - }) => boolean | Promise | undefined; - component?: Component; - componentProps?: Recordable; - defaultValue?: T; - modelPropName?: string; - }, + options: PromptProps, ): Promise { const { component: _component, componentProps: _componentProps, + componentSlots, content, defaultValue, modelPropName: _modelPropName, ...delegated } = options; - const contents: Component[] = []; + const modelValue = ref(defaultValue); + const inputComponentRef = ref(null); + const staticContents: Component[] = []; + if (isString(content)) { - contents.push(h('span', content)); - } else { - contents.push(content); + staticContents.push(h('span', content)); + } else if (content) { + staticContents.push(content as Component); } - const componentProps = _componentProps || {}; + const modelPropName = _modelPropName || 'modelValue'; - componentProps[modelPropName] = modelValue.value; - componentProps[`onUpdate:${modelPropName}`] = (val: any) => { - modelValue.value = val; + const componentProps = { ..._componentProps }; + + // 每次渲染时都会重新计算的内容函数 + const contentRenderer = () => { + const currentProps = { ...componentProps }; + + // 设置当前值 + currentProps[modelPropName] = modelValue.value; + + // 设置更新处理函数 + currentProps[`onUpdate:${modelPropName}`] = (val: T) => { + modelValue.value = val; + }; + + // 创建输入组件 + inputComponentRef.value = h( + _component || Input, + currentProps, + componentSlots, + ); + + // 返回包含静态内容和输入组件的数组 + return h( + 'div', + { class: 'flex flex-col gap-2' }, + { default: () => [...staticContents, inputComponentRef.value] }, + ); }; - const componentRef = h(_component || Input, componentProps); - contents.push(componentRef); + const props: AlertProps & Recordable = { ...delegated, async beforeClose(scope: BeforeCloseScope) { @@ -174,23 +192,46 @@ export async function vbenPrompt( }); } }, - content: h( - 'div', - { class: 'flex flex-col gap-2' }, - { default: () => contents }, - ), - onOpened() { - // 组件挂载完成后,自动聚焦到输入组件 - if ( - componentRef.component?.exposed && - isFunction(componentRef.component.exposed.focus) - ) { - componentRef.component.exposed.focus(); - } else if (componentRef.el && isFunction(componentRef.el.focus)) { - componentRef.el.focus(); + // 使用函数形式,每次渲染都会重新计算内容 + content: contentRenderer, + contentMasking: true, + async onOpened() { + await nextTick(); + const componentRef: null | VNode = inputComponentRef.value; + if (componentRef) { + if ( + componentRef.component?.exposed && + isFunction(componentRef.component.exposed.focus) + ) { + componentRef.component.exposed.focus(); + } else { + if (componentRef.el) { + if ( + isFunction(componentRef.el.focus) && + ['BUTTON', 'INPUT', 'SELECT', 'TEXTAREA'].includes( + componentRef.el.tagName, + ) + ) { + componentRef.el.focus(); + } else if (isFunction(componentRef.el.querySelector)) { + const focusableElement = componentRef.el.querySelector( + 'input, select, textarea, button', + ); + if (focusableElement && isFunction(focusableElement.focus)) { + focusableElement.focus(); + } + } else if ( + componentRef.el.nextElementSibling && + isFunction(componentRef.el.nextElementSibling.focus) + ) { + componentRef.el.nextElementSibling.focus(); + } + } + } } }, }; + await vbenConfirm(props); return modelValue.value; } diff --git a/packages/@core/ui-kit/popup-ui/src/alert/alert.ts b/packages/@core/ui-kit/popup-ui/src/alert/alert.ts index 6a574daa..49d65ed5 100644 --- a/packages/@core/ui-kit/popup-ui/src/alert/alert.ts +++ b/packages/@core/ui-kit/popup-ui/src/alert/alert.ts @@ -1,4 +1,6 @@ -import type { Component } from 'vue'; +import type { Component, VNode, VNodeArrayChildren } from 'vue'; + +import type { Recordable } from '@vben-core/typings'; export type IconType = 'error' | 'info' | 'question' | 'success' | 'warning'; @@ -13,6 +15,11 @@ export type AlertProps = { ) => boolean | Promise | undefined; /** 边框 */ bordered?: boolean; + /** + * 按钮对齐方式 + * @default 'end' + */ + buttonAlign?: 'center' | 'end' | 'start'; /** 取消按钮的标题 */ cancelText?: string; /** 是否居中显示 */ @@ -25,6 +32,8 @@ export type AlertProps = { content: Component | string; /** 弹窗内容的额外样式 */ contentClass?: string; + /** 执行beforeClose回调期间,在内容区域显示一个loading遮罩*/ + contentMasking?: boolean; /** 弹窗的图标(在标题的前面) */ icon?: Component | IconType; /** 是否显示取消按钮 */ @@ -32,3 +41,26 @@ export type AlertProps = { /** 弹窗标题 */ title?: string; }; + +/** Prompt属性 */ +export type PromptProps = { + /** 关闭前的回调,如果返回false,则终止关闭 */ + beforeClose?: (scope: { + isConfirm: boolean; + value: T | undefined; + }) => boolean | Promise | undefined; + /** 用于接受用户输入的组件 */ + component?: Component; + /** 输入组件的属性 */ + componentProps?: Recordable; + /** 输入组件的插槽 */ + componentSlots?: + | (() => any) + | Recordable + | VNode + | VNodeArrayChildren; + /** 默认值 */ + defaultValue?: T; + /** 输入组件的值属性名 */ + modelPropName?: string; +} & Omit; diff --git a/packages/@core/ui-kit/popup-ui/src/alert/alert.vue b/packages/@core/ui-kit/popup-ui/src/alert/alert.vue index 2cd334b7..8c7c4541 100644 --- a/packages/@core/ui-kit/popup-ui/src/alert/alert.vue +++ b/packages/@core/ui-kit/popup-ui/src/alert/alert.vue @@ -30,6 +30,7 @@ import { cn } from '@vben-core/shared/utils'; const props = withDefaults(defineProps(), { bordered: true, + buttonAlign: 'end', centered: true, containerClass: 'w-[520px]', }); @@ -154,9 +155,9 @@ async function handleOpenChange(val: boolean) {
- + -
+
Date: Mon, 7 Apr 2025 13:05:30 +0800 Subject: [PATCH 02/43] =?UTF-8?q?refactor:=20=E7=A7=BB=E9=99=A4deepWatch?= =?UTF-8?q?=E5=8F=82=E6=95=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/web-antd/src/components/upload/src/hook.ts | 3 +-- apps/web-antd/src/components/upload/src/props.d.ts | 7 ------- 2 files changed, 1 insertion(+), 9 deletions(-) diff --git a/apps/web-antd/src/components/upload/src/hook.ts b/apps/web-antd/src/components/upload/src/hook.ts index 313ceae4..a45cd858 100644 --- a/apps/web-antd/src/components/upload/src/hook.ts +++ b/apps/web-antd/src/components/upload/src/hook.ts @@ -358,8 +358,7 @@ export function useUpload( ); } }, - // TODO: deepWatch参数需要删除 - { immediate: true, deep: props.deepWatch }, + { immediate: true }, ); return { diff --git a/apps/web-antd/src/components/upload/src/props.d.ts b/apps/web-antd/src/components/upload/src/props.d.ts index 11e289fc..3aa324c1 100644 --- a/apps/web-antd/src/components/upload/src/props.d.ts +++ b/apps/web-antd/src/components/upload/src/props.d.ts @@ -87,13 +87,6 @@ export interface BaseUploadProps { * @default false */ enableDragUpload?: boolean; - /** - * 是否开启深度监听 - * 默认外部的数组地址重新改变才会触发watch 不会监听内部元素的变化 - * 开启后 无论内部还是外部改变都会触发查询信息接口(包括上传后, 删除等操作都会触发) - * @default false - */ - deepWatch?: boolean; /** * 当ossId查询不到文件信息时 比如被删除了 * 是否保留列表对应的ossId 默认不保留 From 92fe406ae9874123eb2ee2f2cae3a824ddd1bce1 Mon Sep 17 00:00:00 2001 From: dap <15891557205@163.com> Date: Mon, 7 Apr 2025 17:20:41 +0800 Subject: [PATCH 03/43] =?UTF-8?q?update:=20=E5=AD=97=E5=85=B8loading?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/web-antd/src/components/dict/src/index.vue | 14 ++++++++++++-- apps/web-antd/src/store/dict.ts | 1 + 2 files changed, 13 insertions(+), 2 deletions(-) diff --git a/apps/web-antd/src/components/dict/src/index.vue b/apps/web-antd/src/components/dict/src/index.vue index 6c15c7f4..11eda3a1 100644 --- a/apps/web-antd/src/components/dict/src/index.vue +++ b/apps/web-antd/src/components/dict/src/index.vue @@ -4,7 +4,7 @@ import type { DictData } from '#/api/system/dict/dict-data-model'; import { computed } from 'vue'; -import { Tag } from 'ant-design-vue'; +import { Spin, Tag } from 'ant-design-vue'; import { tagTypes } from './data'; @@ -41,12 +41,22 @@ const label = computed(() => { }); const tagComponent = computed(() => (color.value ? Tag : 'div')); + +const loading = computed(() => { + return props.dicts?.length === 0; +}); diff --git a/apps/web-antd/src/store/dict.ts b/apps/web-antd/src/store/dict.ts index f47f944b..c24f4c3c 100644 --- a/apps/web-antd/src/store/dict.ts +++ b/apps/web-antd/src/store/dict.ts @@ -59,6 +59,7 @@ export const useDictStore = defineStore('app-dict', () => { } function resetCache() { + dictRequestCache.clear(); dictOptionsMap.clear(); /** * 不需要清空dictRequestCache 每次请求成功/失败都清空key From 1286b521358e4daa2f56537de458a314c16e0bb8 Mon Sep 17 00:00:00 2001 From: dap <15891557205@163.com> Date: Mon, 7 Apr 2025 17:21:49 +0800 Subject: [PATCH 04/43] fix: getVxePopupContainer --- .../utils/src/helpers/get-popup-container.ts | 61 ++++++++++++++----- 1 file changed, 47 insertions(+), 14 deletions(-) diff --git a/packages/utils/src/helpers/get-popup-container.ts b/packages/utils/src/helpers/get-popup-container.ts index bd9b1a91..14ca6a3c 100644 --- a/packages/utils/src/helpers/get-popup-container.ts +++ b/packages/utils/src/helpers/get-popup-container.ts @@ -12,20 +12,53 @@ export function getPopupContainer(node?: HTMLElement): HTMLElement { /** * VxeTable专用弹窗层 * 解决问题: https://gitee.com/dapppp/ruoyi-plus-vben5/issues/IB1DM3 - * @param _node 触发的元素 + * @param node 触发的元素 + * @param tableId 表格ID,用于区分不同表格(可选) * @returns 挂载节点 */ -export function getVxePopupContainer(_node?: HTMLElement): HTMLElement { - /** - * 需要区分是否为固定列情况 如果为固定列返回parent会导致展开宽度不正常 - * 如果是固定列的情况直接返回body 但是这样不会跟随滚动(个人认为这属于极限场景) - * 如果有更好的办法解决 请告知 - */ - // if (_node?.closest('td.fixed--width')) { - // return document.body; - // } - /** - * 兼容以前代码 先返回body 这样会造成无法跟随滚动 - */ - return document.body; +export function getVxePopupContainer( + node?: HTMLElement, + tableId?: string, +): HTMLElement { + if (!node) return document.body; + + // 检查是否在固定列内 + const isInFixedColumn = + node.closest('.vxe-table--fixed-wrapper') || + node.closest('.vxe-table--fixed-left-wrapper') || + node.closest('.vxe-table--fixed-right-wrapper'); + + // 如果在固定列内,则挂载到固定列容器 + if (isInFixedColumn) { + // 优先查找表格容器及父级容器 + const tableContainer = + // 查找通用固定列容器 + node.closest('.vxe-table--fixed-wrapper') || + // 查找固定列容器(左侧固定列) + node.closest('.vxe-table--fixed-left-wrapper') || + // 查找固定列容器(右侧固定列) + node.closest('.vxe-table--fixed-right-wrapper'); + + // 如果指定了tableId,可以查找特定ID的表格 + if (tableId && tableContainer) { + const specificTable = tableContainer.closest( + `[data-table-id="${tableId}"]`, + ); + if (specificTable) { + return specificTable as HTMLElement; + } + } + + return tableContainer as HTMLElement; + } + + // 非固定列情况下,为了保证滚动跟随,找到最近的单元格或行 + const cell = + node.closest('.vxe-cell') || node.closest('td') || node.closest('tr'); + if (cell) { + return cell as HTMLElement; + } + + // 兜底方案:使用元素的父节点或文档体 + return (node.parentNode as HTMLElement) || document.body; } From 7463df053ad91e07674214cfbd5ab5aec3d6a065 Mon Sep 17 00:00:00 2001 From: dap <15891557205@163.com> Date: Mon, 7 Apr 2025 18:25:21 +0800 Subject: [PATCH 05/43] =?UTF-8?q?update:=20=E5=8E=BB=E9=99=A4=E5=AD=97?= =?UTF-8?q?=E5=85=B8=E5=8A=A8=E7=94=BB?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/web-antd/src/components/dict/src/index.vue | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/web-antd/src/components/dict/src/index.vue b/apps/web-antd/src/components/dict/src/index.vue index 11eda3a1..1edd1274 100644 --- a/apps/web-antd/src/components/dict/src/index.vue +++ b/apps/web-antd/src/components/dict/src/index.vue @@ -52,7 +52,7 @@ const loading = computed(() => { {{ label }} From 5e1de6fc79b28dd6146d48852eabde38bc0024d9 Mon Sep 17 00:00:00 2001 From: dap <15891557205@163.com> Date: Mon, 7 Apr 2025 18:41:23 +0800 Subject: [PATCH 06/43] =?UTF-8?q?fix:=20=E8=A1=A8=E6=A0=BC=E5=9B=BA?= =?UTF-8?q?=E5=AE=9A=E9=AB=98=E5=BA=A6=20getVxePopupContainer?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- packages/utils/src/helpers/get-popup-container.ts | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/packages/utils/src/helpers/get-popup-container.ts b/packages/utils/src/helpers/get-popup-container.ts index 14ca6a3c..27072964 100644 --- a/packages/utils/src/helpers/get-popup-container.ts +++ b/packages/utils/src/helpers/get-popup-container.ts @@ -52,11 +52,13 @@ export function getVxePopupContainer( return tableContainer as HTMLElement; } - // 非固定列情况下,为了保证滚动跟随,找到最近的单元格或行 - const cell = - node.closest('.vxe-cell') || node.closest('td') || node.closest('tr'); - if (cell) { - return cell as HTMLElement; + /** + * 设置行高度需要特殊处理 + */ + const fixedHeightElement = node.closest('td.col--cs-height'); + if (fixedHeightElement) { + // 默认为hidden 显示异常 + (fixedHeightElement as HTMLTableCellElement).style.overflow = 'visible'; } // 兜底方案:使用元素的父节点或文档体 From 53e02d46c2f4b44c913d48bc221641dc564686e5 Mon Sep 17 00:00:00 2001 From: dap <15891557205@163.com> Date: Mon, 7 Apr 2025 18:44:42 +0800 Subject: [PATCH 07/43] docs: changelog --- CHANGELOG.md | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4eb3fe1b..bac503a9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,13 @@ +# 1.3.1 + +**BUG FIX** + +- getVxePopupContainer逻辑调整 解决表格固定高度展开不全的问题 + +**FEATURES** + +- 字典渲染支持loading(length为0情况) + # 1.3.0 注意: 如果你使用老版本的`文件上传`/`图片上传` 可暂时使用 From 88316d749886325b5d2dac12399dcde1356d6154 Mon Sep 17 00:00:00 2001 From: dap <15891557205@163.com> Date: Mon, 7 Apr 2025 18:48:46 +0800 Subject: [PATCH 08/43] =?UTF-8?q?refactor:=20useBeforeCloseDiff=E9=80=BB?= =?UTF-8?q?=E8=BE=91=E6=9B=B4=E6=96=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/web-antd/src/utils/popup.ts | 6 ++++ .../src/views/system/config/config-modal.vue | 32 +++++++++---------- 2 files changed, 21 insertions(+), 17 deletions(-) diff --git a/apps/web-antd/src/utils/popup.ts b/apps/web-antd/src/utils/popup.ts index 0fa2e2e4..ee771b9e 100644 --- a/apps/web-antd/src/utils/popup.ts +++ b/apps/web-antd/src/utils/popup.ts @@ -47,6 +47,11 @@ export function useBeforeCloseDiff(props: BeforeCloseDiffProps) { isInitialized.value = true; } + function resetInitialized() { + initialized.value = ''; + isInitialized.value = false; + } + function setSubmitted() { isSubmitted.value = true; } @@ -101,6 +106,7 @@ export function useBeforeCloseDiff(props: BeforeCloseDiffProps) { onBeforeClose, updateInitialized, setSubmitted, + resetInitialized, }; } diff --git a/apps/web-antd/src/views/system/config/config-modal.vue b/apps/web-antd/src/views/system/config/config-modal.vue index db8b47a2..c07eb49b 100644 --- a/apps/web-antd/src/views/system/config/config-modal.vue +++ b/apps/web-antd/src/views/system/config/config-modal.vue @@ -26,10 +26,11 @@ const [BasicForm, formApi] = useVbenForm({ showDefaultActions: false, }); -const { onBeforeClose, updateInitialized, setSubmitted } = useBeforeCloseDiff({ - initializedGetter: defaultFormValueGetter(formApi), - currentGetter: defaultFormValueGetter(formApi), -}); +const { onBeforeClose, updateInitialized, setSubmitted, resetInitialized } = + useBeforeCloseDiff({ + initializedGetter: defaultFormValueGetter(formApi), + currentGetter: defaultFormValueGetter(formApi), + }); const [BasicModal, modalApi] = useVbenModal({ fullscreenButton: false, @@ -40,22 +41,18 @@ const [BasicModal, modalApi] = useVbenModal({ if (!isOpen) { return null; } - try { - modalApi.lock(true); + modalApi.modalLoading(true); - const { id } = modalApi.getData() as { id?: number | string }; - isUpdate.value = !!id; + const { id } = modalApi.getData() as { id?: number | string }; + isUpdate.value = !!id; - if (isUpdate.value && id) { - const record = await configInfo(id); - await formApi.setValues(record); - } - await updateInitialized(); - } catch (error) { - console.error(error); - } finally { - modalApi.lock(false); + if (isUpdate.value && id) { + const record = await configInfo(id); + await formApi.setValues(record); } + await updateInitialized(); + + modalApi.modalLoading(false); }, }); @@ -80,6 +77,7 @@ async function handleConfirm() { async function handleClosed() { await formApi.resetForm(); + resetInitialized(); } From 07587c0faf7939dc659a04883a96f48fb3de87d2 Mon Sep 17 00:00:00 2001 From: dap <15891557205@163.com> Date: Mon, 7 Apr 2025 19:02:28 +0800 Subject: [PATCH 09/43] =?UTF-8?q?update:=20=E7=94=A8=E6=88=B7=E7=AE=A1?= =?UTF-8?q?=E7=90=86=20=E8=A1=A8=E5=8D=95=E6=9B=B4=E6=96=B0(=E9=9D=9E?= =?UTF-8?q?=E6=9C=80=E7=BB=88=E6=96=B9=E6=A1=88)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/views/system/user/user-drawer.vue | 28 +++++++++++++------ .../ui-kit/popup-ui/src/drawer/drawer-api.ts | 4 +-- .../ui-kit/popup-ui/src/drawer/drawer.ts | 4 +-- 3 files changed, 24 insertions(+), 12 deletions(-) diff --git a/apps/web-antd/src/views/system/user/user-drawer.vue b/apps/web-antd/src/views/system/user/user-drawer.vue index f3ae9e58..afd0fce2 100644 --- a/apps/web-antd/src/views/system/user/user-drawer.vue +++ b/apps/web-antd/src/views/system/user/user-drawer.vue @@ -18,6 +18,7 @@ import { userAdd, userUpdate, } from '#/api/system/user'; +import { defaultFormValueGetter, useBeforeCloseDiff } from '#/utils/popup'; import { authScopeOptions } from '#/views/system/role/data'; import { drawerSchema } from './data'; @@ -134,8 +135,15 @@ async function loadDefaultPassword(update: boolean) { } } +const { onBeforeClose, updateInitialized, setSubmitted, resetInitialized } = + useBeforeCloseDiff({ + initializedGetter: defaultFormValueGetter(formApi), + currentGetter: defaultFormValueGetter(formApi), + }); + const [BasicDrawer, drawerApi] = useVbenDrawer({ - onCancel: handleCancel, + onBeforeClose, + onClosed: handleClosed, onConfirm: handleConfirm, async onOpenChange(isOpen) { if (!isOpen) { @@ -149,6 +157,7 @@ const [BasicDrawer, drawerApi] = useVbenDrawer({ return null; } drawerApi.drawerLoading(true); + const { id } = drawerApi.getData() as { id?: number | string }; isUpdate.value = !!id; /** update时 禁用用户名修改 不显示密码框 */ @@ -199,36 +208,39 @@ const [BasicDrawer, drawerApi] = useVbenDrawer({ setupPostOptions(user.deptId), ]); } + await updateInitialized(); + drawerApi.drawerLoading(false); }, }); async function handleConfirm() { try { - drawerApi.drawerLoading(true); + drawerApi.lock(true); const { valid } = await formApi.validate(); if (!valid) { return; } const data = cloneDeep(await formApi.getValues()); await (isUpdate.value ? userUpdate(data) : userAdd(data)); + setSubmitted(); emit('reload'); - await handleCancel(); + drawerApi.close(); } catch (error) { console.error(error); } finally { - drawerApi.drawerLoading(false); + drawerApi.lock(false); } } -async function handleCancel() { - drawerApi.close(); - await formApi.resetForm(); +async function handleClosed() { + formApi.resetForm(); + resetInitialized(); } diff --git a/packages/@core/ui-kit/popup-ui/src/drawer/drawer-api.ts b/packages/@core/ui-kit/popup-ui/src/drawer/drawer-api.ts index 525596e2..d2e7d690 100644 --- a/packages/@core/ui-kit/popup-ui/src/drawer/drawer-api.ts +++ b/packages/@core/ui-kit/popup-ui/src/drawer/drawer-api.ts @@ -88,10 +88,10 @@ export class DrawerApi { /** * 关闭弹窗 */ - close() { + async close() { // 通过 onBeforeClose 钩子函数来判断是否允许关闭弹窗 // 如果 onBeforeClose 返回 false,则不关闭弹窗 - const allowClose = this.api.onBeforeClose?.() ?? true; + const allowClose = (await this.api.onBeforeClose?.()) ?? true; if (allowClose) { this.store.setState((prev) => ({ ...prev, diff --git a/packages/@core/ui-kit/popup-ui/src/drawer/drawer.ts b/packages/@core/ui-kit/popup-ui/src/drawer/drawer.ts index b3ae0fb8..30009a64 100644 --- a/packages/@core/ui-kit/popup-ui/src/drawer/drawer.ts +++ b/packages/@core/ui-kit/popup-ui/src/drawer/drawer.ts @@ -1,6 +1,6 @@ import type { Component, Ref } from 'vue'; -import type { ClassType } from '@vben-core/typings'; +import type { ClassType, MaybePromise } from '@vben-core/typings'; import type { DrawerApi } from './drawer-api'; @@ -151,7 +151,7 @@ export interface DrawerApiOptions extends DrawerState { * 关闭前的回调,返回 false 可以阻止关闭 * @returns */ - onBeforeClose?: () => void; + onBeforeClose?: () => MaybePromise; /** * 点击取消按钮的回调 */ From 34e5812de913a4173e4d344997bfea58c746b046 Mon Sep 17 00:00:00 2001 From: dap <15891557205@163.com> Date: Mon, 7 Apr 2025 19:37:11 +0800 Subject: [PATCH 10/43] update: vxe active color --- packages/effects/plugins/src/vxe-table/style.css | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/effects/plugins/src/vxe-table/style.css b/packages/effects/plugins/src/vxe-table/style.css index 5a97d7f2..c586b736 100644 --- a/packages/effects/plugins/src/vxe-table/style.css +++ b/packages/effects/plugins/src/vxe-table/style.css @@ -50,6 +50,7 @@ /** 右上角toolbar按钮色/翻页主题色保持一致 */ --vxe-ui-font-primary-lighten-color: hsl(var(--primary-500)); + --vxe-ui-font-primary-darken-color: hsl(var(--primary-600)); height: auto !important; } From eb9f278e7f2a70a2dfecd4af2cceacdb88f5aa86 Mon Sep 17 00:00:00 2001 From: dap <15891557205@163.com> Date: Tue, 8 Apr 2025 10:10:15 +0800 Subject: [PATCH 11/43] refactor: useBeforeCloseDiff --- apps/web-antd/src/utils/popup.ts | 41 ++++++++++--------- .../src/views/system/config/config-modal.vue | 11 ++--- .../src/views/system/user/user-drawer.vue | 11 ++--- 3 files changed, 34 insertions(+), 29 deletions(-) diff --git a/apps/web-antd/src/utils/popup.ts b/apps/web-antd/src/utils/popup.ts index ee771b9e..0d5c9aaf 100644 --- a/apps/web-antd/src/utils/popup.ts +++ b/apps/web-antd/src/utils/popup.ts @@ -29,48 +29,52 @@ interface BeforeCloseDiffProps { } /** - * @deprecated 注意为实验性功能 可能有api变动/被移除 + * 用于Drawer/Modal使用 判断表单是否有变动来决定是否弹窗提示 * @param props props * @returns hook - * - * 待解决问题: 网速慢情况直接关闭 会导致数据不一致问题 - * 但是使用api.lock会导致在报错情况无法关闭(因为目前代码没有finally) */ export function useBeforeCloseDiff(props: BeforeCloseDiffProps) { const { initializedGetter, currentGetter, compare } = props; + /** + * 记录初始值 json + */ const initialized = ref(''); + /** + * 是否已经初始化了 通过这个值判断是否需要进行对比 为false直接关闭 不弹窗 + */ const isInitialized = ref(false); - const isSubmitted = ref(false); - async function updateInitialized(data?: string) { + /** + * 标记是否已经完成初始化 后续需要进行对比 + * @param data 自定义初始化数据 可选 + */ + async function markInitialized(data?: string) { initialized.value = data || (await initializedGetter()); isInitialized.value = true; } + /** + * 重置初始化状态 需要在closed前调用 或者打开窗口时 + */ function resetInitialized() { initialized.value = ''; isInitialized.value = false; } - function setSubmitted() { - isSubmitted.value = true; - } - + /** + * 提供给useVbenForm/useVbenDrawer使用 + * @returns 是否允许关闭 + */ async function onBeforeClose(): Promise { // 如果还未初始化,直接允许关闭 if (!isInitialized.value) { return true; } - // 如果已经提交过,直接允许关闭 - if (isSubmitted.value) { - // 重置状态 - isSubmitted.value = false; - return true; - } try { + // 获取当前表单数据 const current = await currentGetter(); - + // 自定义比较的情况 if (isFunction(compare) && compare(initialized.value, current)) { return true; } else { @@ -104,8 +108,7 @@ export function useBeforeCloseDiff(props: BeforeCloseDiffProps) { return { onBeforeClose, - updateInitialized, - setSubmitted, + markInitialized, resetInitialized, }; } diff --git a/apps/web-antd/src/views/system/config/config-modal.vue b/apps/web-antd/src/views/system/config/config-modal.vue index c07eb49b..00727a4a 100644 --- a/apps/web-antd/src/views/system/config/config-modal.vue +++ b/apps/web-antd/src/views/system/config/config-modal.vue @@ -26,11 +26,12 @@ const [BasicForm, formApi] = useVbenForm({ showDefaultActions: false, }); -const { onBeforeClose, updateInitialized, setSubmitted, resetInitialized } = - useBeforeCloseDiff({ +const { onBeforeClose, markInitialized, resetInitialized } = useBeforeCloseDiff( + { initializedGetter: defaultFormValueGetter(formApi), currentGetter: defaultFormValueGetter(formApi), - }); + }, +); const [BasicModal, modalApi] = useVbenModal({ fullscreenButton: false, @@ -50,7 +51,7 @@ const [BasicModal, modalApi] = useVbenModal({ const record = await configInfo(id); await formApi.setValues(record); } - await updateInitialized(); + await markInitialized(); modalApi.modalLoading(false); }, @@ -65,7 +66,7 @@ async function handleConfirm() { } const data = cloneDeep(await formApi.getValues()); await (isUpdate.value ? configUpdate(data) : configAdd(data)); - setSubmitted(); + resetInitialized(); emit('reload'); modalApi.close(); } catch (error) { diff --git a/apps/web-antd/src/views/system/user/user-drawer.vue b/apps/web-antd/src/views/system/user/user-drawer.vue index afd0fce2..e55ce889 100644 --- a/apps/web-antd/src/views/system/user/user-drawer.vue +++ b/apps/web-antd/src/views/system/user/user-drawer.vue @@ -135,11 +135,12 @@ async function loadDefaultPassword(update: boolean) { } } -const { onBeforeClose, updateInitialized, setSubmitted, resetInitialized } = - useBeforeCloseDiff({ +const { onBeforeClose, markInitialized, resetInitialized } = useBeforeCloseDiff( + { initializedGetter: defaultFormValueGetter(formApi), currentGetter: defaultFormValueGetter(formApi), - }); + }, +); const [BasicDrawer, drawerApi] = useVbenDrawer({ onBeforeClose, @@ -208,7 +209,7 @@ const [BasicDrawer, drawerApi] = useVbenDrawer({ setupPostOptions(user.deptId), ]); } - await updateInitialized(); + await markInitialized(); drawerApi.drawerLoading(false); }, @@ -223,7 +224,7 @@ async function handleConfirm() { } const data = cloneDeep(await formApi.getValues()); await (isUpdate.value ? userUpdate(data) : userAdd(data)); - setSubmitted(); + resetInitialized(); emit('reload'); drawerApi.close(); } catch (error) { From e6dab8300d6d0d8ee729a838e2977ab93c043026 Mon Sep 17 00:00:00 2001 From: dap <15891557205@163.com> Date: Tue, 8 Apr 2025 10:22:21 +0800 Subject: [PATCH 12/43] =?UTF-8?q?refactor:=20=E8=A7=92=E8=89=B2=E7=AE=A1?= =?UTF-8?q?=E7=90=86=20useBeforeCloseDiff?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/views/system/role/role-drawer.vue | 38 +++++++++++++++---- 1 file changed, 31 insertions(+), 7 deletions(-) diff --git a/apps/web-antd/src/views/system/role/role-drawer.vue b/apps/web-antd/src/views/system/role/role-drawer.vue index 5b583d8e..2e48e948 100644 --- a/apps/web-antd/src/views/system/role/role-drawer.vue +++ b/apps/web-antd/src/views/system/role/role-drawer.vue @@ -1,3 +1,6 @@ +