diff --git a/CHANGELOG.md b/CHANGELOG.md index 94198281..df3796a1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -26,11 +26,15 @@ - TableSwitch组件重构 - 管理员租户切换不再返回首页 直接刷新当前页(除特殊页面外会回到首页) - 租户切换Select增加loading -- modalLoading/drawerLoading改为调用内部的lock/unlock方法 +- ~~modalLoading/drawerLoading改为调用内部的lock/unlock方法~~ 有待商榷暂时按老版本逻辑不变 - 登录验证码 增加loading - DictEnum使用const代替enum - TinyMCE组件重构 移除冗余代码/功能 增加loading +**ALPHA功能** + +- 弹窗表单数据更改关闭时的提示框(可能最终不会加入) 测试页面: 参数管理 + **BUG FIX** - 重新登录 字典会unknown的情况[详细分析](https://gitee.com/dapppp/ruoyi-plus-vben5/issues/IBY27D) diff --git a/apps/web-antd/src/utils/popup.ts b/apps/web-antd/src/utils/popup.ts new file mode 100644 index 00000000..0fa2e2e4 --- /dev/null +++ b/apps/web-antd/src/utils/popup.ts @@ -0,0 +1,117 @@ +import type { ExtendedFormApi } from '@vben/common-ui'; +import type { MaybePromise } from '@vben/types'; + +import { ref } from 'vue'; + +import { $t } from '@vben/locales'; + +import { Modal } from 'ant-design-vue'; +import { isFunction } from 'lodash-es'; + +interface BeforeCloseDiffProps { + /** + * 初始化值如何获取 + * @returns Promise + */ + initializedGetter: () => MaybePromise; + /** + * 当前值如何获取 + * @returns Promise + */ + currentGetter: () => MaybePromise; + /** + * 自定义比较函数 + * @param init 初始值 + * @param current 当前值 + * @returns boolean + */ + compare?: (init: string, current: string) => boolean; +} + +/** + * @deprecated 注意为实验性功能 可能有api变动/被移除 + * @param props props + * @returns hook + * + * 待解决问题: 网速慢情况直接关闭 会导致数据不一致问题 + * 但是使用api.lock会导致在报错情况无法关闭(因为目前代码没有finally) + */ +export function useBeforeCloseDiff(props: BeforeCloseDiffProps) { + const { initializedGetter, currentGetter, compare } = props; + const initialized = ref(''); + const isInitialized = ref(false); + const isSubmitted = ref(false); + + async function updateInitialized(data?: string) { + initialized.value = data || (await initializedGetter()); + isInitialized.value = true; + } + + function setSubmitted() { + isSubmitted.value = true; + } + + 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 { + // 如果数据没有变化,直接允许关闭 + if (current === initialized.value) { + return true; + } + } + + // 数据有变化,显示确认对话框 + return new Promise((resolve) => { + Modal.confirm({ + title: $t('pages.common.tip'), + content: $t('您有未保存的更改,确认要退出吗?'), + centered: true, + okButtonProps: { danger: true }, + cancelText: $t('common.cancel'), + okText: $t('common.confirm'), + onOk: () => { + resolve(true); + isInitialized.value = false; + }, + onCancel: () => resolve(false), + }); + }); + } catch (error) { + console.error('Failed to compare data:', error); + return true; + } + } + + return { + onBeforeClose, + updateInitialized, + setSubmitted, + }; +} + +/** + * 给useVbenForm使用的 封装函数 + * @param formApi 表单实例 + * @returns getter + */ +export function defaultFormValueGetter(formApi: ExtendedFormApi) { + return async () => { + const v = await formApi.getValues(); + return JSON.stringify(v); + }; +} 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 9569f0c5..1094c231 100644 --- a/apps/web-antd/src/views/system/config/config-modal.vue +++ b/apps/web-antd/src/views/system/config/config-modal.vue @@ -7,6 +7,7 @@ import { cloneDeep } from '@vben/utils'; import { useVbenForm } from '#/adapter/form'; import { configAdd, configInfo, configUpdate } from '#/api/system/config'; +import { defaultFormValueGetter, useBeforeCloseDiff } from '#/utils/popup'; import { modalSchema } from './data'; @@ -25,9 +26,15 @@ const [BasicForm, formApi] = useVbenForm({ showDefaultActions: false, }); +const { onBeforeClose, updateInitialized, setSubmitted } = useBeforeCloseDiff({ + initializedGetter: defaultFormValueGetter(formApi), + currentGetter: defaultFormValueGetter(formApi), +}); + const [BasicModal, modalApi] = useVbenModal({ fullscreenButton: false, - onCancel: handleCancel, + onBeforeClose, + onClosed: handleClosed, onConfirm: handleConfirm, onOpenChange: async (isOpen) => { if (!isOpen) { @@ -42,6 +49,7 @@ const [BasicModal, modalApi] = useVbenModal({ const record = await configInfo(id); await formApi.setValues(record); } + await updateInitialized(); modalApi.modalLoading(false); }, @@ -56,8 +64,9 @@ async function handleConfirm() { } const data = cloneDeep(await formApi.getValues()); await (isUpdate.value ? configUpdate(data) : configAdd(data)); + setSubmitted(); emit('reload'); - await handleCancel(); + modalApi.close(); } catch (error) { console.error(error); } finally { @@ -65,14 +74,13 @@ async function handleConfirm() { } } -async function handleCancel() { - modalApi.close(); +async function handleClosed() { await formApi.resetForm(); }