From 35bc29f35692e4615dbf3e1ed6c2bc4998abd581 Mon Sep 17 00:00:00 2001 From: ZhangAo Date: Thu, 18 Sep 2025 17:28:22 +0800 Subject: [PATCH] u --- .../ao-design/src/_styles/_variables.scss | 2 + .../ao-design/src/_styles/theme/_dark.scss | 2 + .../ao-design/src/_styles/theme/_light.scss | 2 + .../packages/ao-design/src/button/index.tsx | 3 +- .../packages/ao-design/src/drawer/index.tsx | 38 ++- .../packages/ao-design/src/modal/index.scss | 195 +++++++++---- .../packages/ao-design/src/modal/index.tsx | 263 ++++++++++++++---- .../ao-design/src/modal/modalManager.ts | 12 +- .../packages/ao-design/src/modal/old.tsx | 224 +++++++++++++++ .../ao-design/src/popconfirm/index.tsx | 24 +- .../packages/ao-design/src/popup/index.tsx | 48 ++-- .../packages/ao-design/src/switch/index.tsx | 21 +- code/base-project/base-vue/src/App.vue | 12 +- 13 files changed, 675 insertions(+), 171 deletions(-) create mode 100644 code/base-project/base-vue/packages/ao-design/src/modal/old.tsx diff --git a/code/base-project/base-vue/packages/ao-design/src/_styles/_variables.scss b/code/base-project/base-vue/packages/ao-design/src/_styles/_variables.scss index a0e1bfb3..eb6dd992 100644 --- a/code/base-project/base-vue/packages/ao-design/src/_styles/_variables.scss +++ b/code/base-project/base-vue/packages/ao-design/src/_styles/_variables.scss @@ -102,6 +102,8 @@ $border-color-component: var(--ao-border-color-component); // 组件边框(Inp $bg-color-secondarycomponent: var(--ao-bg-color-secondarycomponent); $bg-color-secondarycomponent-hover: var(--ao-bg-color-secondarycomponent-hover); +$bg-color-mask: var(--ao-bg-color-mask); + // shadow // 基础/下层 投影 hover 使用的组件包括:表格 / $shadow-surface: var(--ao-shadow-surface); diff --git a/code/base-project/base-vue/packages/ao-design/src/_styles/theme/_dark.scss b/code/base-project/base-vue/packages/ao-design/src/_styles/theme/_dark.scss index 741eb6f7..b43bebaf 100644 --- a/code/base-project/base-vue/packages/ao-design/src/_styles/theme/_dark.scss +++ b/code/base-project/base-vue/packages/ao-design/src/_styles/theme/_dark.scss @@ -79,6 +79,8 @@ --ao-bg-color-secondarycomponent: #4b4b4b; --ao-bg-color-secondarycomponent-hover: #5e5e5e; + --ao-bg-color-mask: rgba(23, 23, 26, 0.6); + // shadow阴影 // 基础/下层 投影 hover 使用的组件包括:表格 / --ao-shadow-surface: 0 4px 6px rgba(0, 0, 0, 6%), 0 1px 10px rgba(0, 0, 0, 8%), 0 2px 4px rgba(0, 0, 0, 12%); diff --git a/code/base-project/base-vue/packages/ao-design/src/_styles/theme/_light.scss b/code/base-project/base-vue/packages/ao-design/src/_styles/theme/_light.scss index f428449a..273b9fc2 100644 --- a/code/base-project/base-vue/packages/ao-design/src/_styles/theme/_light.scss +++ b/code/base-project/base-vue/packages/ao-design/src/_styles/theme/_light.scss @@ -80,6 +80,8 @@ --ao-bg-color-secondarycomponent: #ddd; --ao-bg-color-secondarycomponent-hover: #c6c6c6; + --ao-bg-color-mask: rgba(29, 33, 41, 0.6); + // shadow阴影 // 基础/下层 投影 hover 使用的组件包括:表格 / --ao-shadow-surface: 0 1px 10px rgba(0, 0, 0, 5%), 0 4px 5px rgba(0, 0, 0, 8%), 0 2px 4px -1px rgba(0, 0, 0, 12%); diff --git a/code/base-project/base-vue/packages/ao-design/src/button/index.tsx b/code/base-project/base-vue/packages/ao-design/src/button/index.tsx index 65217a80..cb80f86b 100644 --- a/code/base-project/base-vue/packages/ao-design/src/button/index.tsx +++ b/code/base-project/base-vue/packages/ao-design/src/button/index.tsx @@ -37,9 +37,10 @@ export default defineComponent({ const map = { small: 14, medium: 16, large: 16 } return map[props.size] }) - const hasContent = !!slots.default?.().length return () => { + const hasContent = !!slots.default?.().length + return ( + const typeIconMap: Record = { + success: 'ix:success-filled', + danger: 'ix:error-filled', + warning: 'ix:warning-filled', + info: 'ix:info-filled', + question: 'ix:question-filled', + } + + return () => { + return ( + + {isMounted.value && ( +
+ {props.mask && ( + +
+ + )} + +
+
+
+ {props.type !== 'default' && } + {slots.title?.()} + {props.showClose && } +
+
{slots.default?.()}
+
+ {slots.footer?.() ?? ( + <> + + + + )} +
+
+
+
-
{slots.default?.()}
- {slots.footer &&
{slots.footer()}
} -
- -
- ) + )} + + ) + } }, }) diff --git a/code/base-project/base-vue/packages/ao-design/src/modal/modalManager.ts b/code/base-project/base-vue/packages/ao-design/src/modal/modalManager.ts index 00cefc27..4c9e0b7c 100644 --- a/code/base-project/base-vue/packages/ao-design/src/modal/modalManager.ts +++ b/code/base-project/base-vue/packages/ao-design/src/modal/modalManager.ts @@ -1,15 +1,8 @@ let zIndexBase = 1000 const modalStack: number[] = [] // 当前打开的 Modal zIndex -const freeZIndex: number[] = [] // 已关闭 Modal 可复用 zIndex export function addModal() { - let zIndex: number - if (freeZIndex.length > 0) { - zIndex = freeZIndex.pop()! - } - else { - zIndex = ++zIndexBase - } + const zIndex = ++zIndexBase // 每次直接递增 modalStack.push(zIndex) return zIndex } @@ -17,8 +10,7 @@ export function addModal() { export function removeModal(zIndex: number) { const idx = modalStack.indexOf(zIndex) if (idx !== -1) { - modalStack.splice(idx, 1) - freeZIndex.push(zIndex) // 回收 zIndex + modalStack.splice(idx, 1) // 只移出 stack,不回收 zIndex } } diff --git a/code/base-project/base-vue/packages/ao-design/src/modal/old.tsx b/code/base-project/base-vue/packages/ao-design/src/modal/old.tsx new file mode 100644 index 00000000..6719e138 --- /dev/null +++ b/code/base-project/base-vue/packages/ao-design/src/modal/old.tsx @@ -0,0 +1,224 @@ +import type { PropType } from 'vue' +import { computed, defineComponent, onMounted, ref, Teleport, Transition, watch } from 'vue' +import { getPrefixCls } from '../_utils/getPrefixCls' +import Button from '../button/index' +import Icon from '../icon/index' + +export default defineComponent({ + name: 'Modal', + props: { + visible: { + type: Boolean, + default: undefined, + }, + defaultVisible: { + type: Boolean, + default: false, + }, + mask: { + type: Boolean, + default: true, + }, + maskClosable: { + type: Boolean, + default: true, + }, + position: { + type: String as PropType<'top' | 'center' | 'bottom'>, + default: 'center', + }, + type: { + type: String as PropType<'default' | 'info' | 'success' | 'warning' | 'danger' | 'question'>, + default: 'default', + }, + showClose: { + type: Boolean, + default: true, + }, + confirmBtn: { + type: null, + default: () => ({ label: '确认', type: 'solid', status: 'primary', size: 'small' }), + }, + cancelBtn: { + type: null, + default: () => ({ label: '取消', type: 'solid', status: 'default', size: 'small' }), + }, + onBeforeConfirm: { + type: Function as PropType<(done: (ok: boolean) => void) => void | boolean | Promise>, + }, + onBeforeCancel: { + type: Function as PropType<(done: (ok: boolean) => void) => void | boolean | Promise>, + }, + destroyOnClose: { + type: Boolean, + default: false, + }, + appendTo: { + type: [String, Object] as PropType, + default: 'body', + }, + class: { + type: [String, Array, Object], + default: '', + }, + style: { + type: Object, + default: () => ({}), + }, + }, + emits: ['update:visible', 'confirm', 'cancel', 'open', 'close', 'beforeOpen', 'beforeClose'], + setup(props, { slots, emit }) { + const prefixCls = getPrefixCls('modal') + const targetEl = ref(document.body) + const confirmLoading = ref(false) + + const _visible = ref(props.defaultVisible) + const _rendered = ref(props.defaultVisible) + const _mounted = ref(props.defaultVisible) + + watch(() => props.visible, (val) => { + if (val !== undefined) { + _visible.value = val + if (val) { + _mounted.value = true + _rendered.value = true + } + } + }, { immediate: true }) + + onMounted(() => { + if (props.appendTo instanceof HTMLElement) { + targetEl.value = props.appendTo + } + else if (typeof props.appendTo === 'string') { + const el = document.querySelector(props.appendTo) + if (el) + targetEl.value = el as HTMLElement + } + }) + + const handleMaskClick = () => { + if (props.maskClosable) { + emit('update:visible', false) + } + } + + const handleCancel = async () => { + let allow = true + if (props.onBeforeCancel) { + try { + allow = await new Promise((resolve) => { + const result = props.onBeforeCancel!(ok => resolve(ok)) + if (typeof result === 'boolean') + resolve(result) + else if (result instanceof Promise) + result.then(resolve).catch(() => resolve(false)) + }) + } + catch { + allow = false + } + } + + if (allow) { + emit('update:visible', false) + emit('cancel') + confirmLoading.value = false + } + } + + const handleConfirm = async () => { + let allow = true + if (props.onBeforeConfirm) { + confirmLoading.value = true + try { + allow = await new Promise((resolve) => { + const result = props.onBeforeConfirm!(ok => resolve(ok)) + if (typeof result === 'boolean') + resolve(result) + else if (result instanceof Promise) + result.then(resolve).catch(() => resolve(false)) + }) + } + catch { allow = false } + } + if (allow) { + emit('update:visible', false) + emit('confirm') + } + confirmLoading.value = false + } + + const handleBeforeEnter = () => { + emit('beforeOpen') + } + + const handleAfterEnter = () => { + emit('open') + } + + const handleBeforeLeave = () => { + emit('beforeClose') + } + + const handleAfterLeave = () => { + _mounted.value = false + emit('close') + } + + const typeIconMap: Record = { + success: 'ix:success-filled', + danger: 'ix:error-filled', + warning: 'ix:warning-filled', + info: 'ix:info-filled', + question: 'ix:question-filled', + } + + return () => { + const shouldRenderPopup = props.destroyOnClose + ? _visible.value + : _visible.value || _rendered.value + + return ( + + {shouldRenderPopup && ( +
+ {props.mask && ( + +
+ + )} + +
+
+
+ {props.type !== 'default' && } + {slots.title?.()} + {props.showClose && } +
+
{slots.default?.()}
+
+ {slots.footer?.() ?? ( + <> + + + + )} +
+
+
+
+
+ )} + + ) + } + }, +}) diff --git a/code/base-project/base-vue/packages/ao-design/src/popconfirm/index.tsx b/code/base-project/base-vue/packages/ao-design/src/popconfirm/index.tsx index 00bba24e..ed9c21df 100644 --- a/code/base-project/base-vue/packages/ao-design/src/popconfirm/index.tsx +++ b/code/base-project/base-vue/packages/ao-design/src/popconfirm/index.tsx @@ -21,7 +21,9 @@ export default defineComponent({ required: false, }, beforeConfirm: { - type: Function as PropType<() => boolean | Promise>, + type: Function as PropType< + (done: (ok: boolean) => void) => void | boolean | Promise + >, }, confirmBtn: { type: null, @@ -68,11 +70,27 @@ export default defineComponent({ } const handleConfirm = async () => { - confirmLoading.value = true let allow = true if (props.beforeConfirm) { - allow = await Promise.resolve(props.beforeConfirm()) + confirmLoading.value = true + + try { + allow = await new Promise((resolve) => { + const result = props.beforeConfirm!(ok => resolve(ok)) + + if (typeof result === 'boolean') { + resolve(result) + } + else if (result instanceof Promise) { + result.then(ok => resolve(ok)).catch(() => resolve(false)) + } + // 如果是用 done 回调,就等调用方触发 resolve + }) + } + catch { + allow = false + } } if (allow) { diff --git a/code/base-project/base-vue/packages/ao-design/src/popup/index.tsx b/code/base-project/base-vue/packages/ao-design/src/popup/index.tsx index a97dbfba..e442f7fd 100644 --- a/code/base-project/base-vue/packages/ao-design/src/popup/index.tsx +++ b/code/base-project/base-vue/packages/ao-design/src/popup/index.tsx @@ -17,8 +17,10 @@ import { getPrefixCls } from '../_utils/getPrefixCls' export default defineComponent({ name: 'APopup', props: { - visible: { type: Boolean, default: false }, - + visible: { + type: Boolean, + default: false, + }, trigger: { type: [String, Array] as PropType< 'hover' | 'click' | 'focus' | 'contextMenu' | Array<'hover' | 'click' | 'focus' | 'contextMenu'> @@ -65,8 +67,14 @@ export default defineComponent({ type: [String, Object, Function] as unknown as () => string | VNode | (() => VNode), default: '', }, - showDelay: { type: Number, default: 0 }, - hideDelay: { type: Number, default: 0 }, + showDelay: { + type: Number, + default: 0, + }, + hideDelay: { + type: Number, + default: 0, + }, }, emits: ['update:visible'], setup(props, { slots, emit }) { @@ -75,8 +83,8 @@ export default defineComponent({ const triggerRef = ref(null) // 触发 引用 const floatingRef = ref(null) // float 引用 const arrowRef = ref(null) // 箭头引用 - const localVisible = ref(props.visible) - const hasRendered = ref(props.visible) + const _visible = ref(props.visible) + const _rendered = ref(props.visible) // 定时器 let showTimer: number | null = null let hideTimer: number | null = null @@ -155,12 +163,12 @@ export default defineComponent({ const showPopup = async () => { clearTimers() showTimer = window.setTimeout(async () => { - if (!hasRendered.value) - hasRendered.value = true + if (!_rendered.value) + _rendered.value = true await nextTick() fitFloatingWidth() - localVisible.value = true + _visible.value = true setupAutoUpdate() emit('update:visible', true) }, props.showDelay) @@ -169,7 +177,7 @@ export default defineComponent({ const hidePopup = async () => { clearTimers() hideTimer = window.setTimeout(() => { - localVisible.value = false + _visible.value = false clearAutoUpdate() emit('update:visible', false) }, props.hideDelay) @@ -178,15 +186,15 @@ export default defineComponent({ const togglePopup = async () => { clearTimers() - if (!hasRendered.value) - hasRendered.value = true + if (!_rendered.value) + _rendered.value = true await nextTick() fitFloatingWidth() - if (!localVisible.value) { + if (!_visible.value) { // 需要显示 showTimer = window.setTimeout(async () => { - localVisible.value = true + _visible.value = true await nextTick() setupAutoUpdate() emit('update:visible', true) @@ -195,7 +203,7 @@ export default defineComponent({ else { // 需要隐藏 hideTimer = window.setTimeout(() => { - localVisible.value = false + _visible.value = false clearAutoUpdate() emit('update:visible', false) }, props.hideDelay) @@ -230,7 +238,7 @@ export default defineComponent({ const triggerEl = getDOM(triggerRef.value) const floatingEl = getDOM(floatingRef.value) if (!triggerEl?.contains(target) && !floatingEl?.contains(target)) { - localVisible.value = false + _visible.value = false } } @@ -296,7 +304,7 @@ export default defineComponent({ }) watch(() => props.visible, (val) => { - localVisible.value = val + _visible.value = val }, { immediate: true }) const renderContent = (content: string | VNode | (() => VNode)) => { @@ -310,8 +318,8 @@ export default defineComponent({ return () => { // 懒加载 const shouldRenderPopup = props.destroyOnClose - ? localVisible.value // 需要销毁时,只在显示时渲染 - : localVisible.value || hasRendered.value // 不销毁时保持已渲染 + ? _visible.value // 需要销毁时,只在显示时渲染 + : _visible.value || _rendered.value // 不销毁时保持已渲染 return ( <> @@ -332,7 +340,7 @@ export default defineComponent({ appear >
boolean | Promise>, + type: Function as PropType< + (done: (ok: boolean) => void) => void | boolean | Promise + >, }, class: { type: [String, Array, Object], @@ -70,12 +71,24 @@ export default defineComponent({ if (props.disabled || isLoading.value) return - _loading.value = true let allow = true if (props.beforeConfirm) { + _loading.value = true + try { - allow = await Promise.resolve(props.beforeConfirm()) + // 用一个统一的函数来兼容三种写法 + allow = await new Promise((resolve) => { + const result = props.beforeConfirm!(ok => resolve(ok)) + + if (typeof result === 'boolean') { + resolve(result) + } + else if (result instanceof Promise) { + result.then(ok => resolve(ok)).catch(() => resolve(false)) + } + // 如果 beforeConfirm 里调用了 done,就等 done 触发 + }) } catch { allow = false diff --git a/code/base-project/base-vue/src/App.vue b/code/base-project/base-vue/src/App.vue index be15aacd..b7477b2d 100644 --- a/code/base-project/base-vue/src/App.vue +++ b/code/base-project/base-vue/src/App.vue @@ -12,19 +12,11 @@ const AModal3Visible = ref(false) - +

这是第 1 个弹窗内容

-
@@ -44,7 +36,7 @@ const AModal3Visible = ref(false)
- +