From d917b0f6e3d076aa060eccfa327b18510718302b Mon Sep 17 00:00:00 2001 From: oneao Date: Thu, 18 Sep 2025 22:11:18 +0800 Subject: [PATCH] u --- .../packages/ao-design/src/modal/index.tsx | 251 ++++++++++-------- code/base-project/base-vue/src/App.vue | 58 ++-- 2 files changed, 154 insertions(+), 155 deletions(-) diff --git a/code/base-project/base-vue/packages/ao-design/src/modal/index.tsx b/code/base-project/base-vue/packages/ao-design/src/modal/index.tsx index 6f6b2e24..ea8f0f52 100644 --- a/code/base-project/base-vue/packages/ao-design/src/modal/index.tsx +++ b/code/base-project/base-vue/packages/ao-design/src/modal/index.tsx @@ -1,4 +1,4 @@ -import type { PropType } from 'vue' +import type { PropType, Ref } from 'vue' import { computed, defineComponent, onMounted, ref, Teleport, Transition, watch } from 'vue' import { getPrefixCls } from '../_utils/getPrefixCls' import Button from '../button/index' @@ -7,90 +7,43 @@ 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', - }, - fullscreen: { - type: Boolean, - default: false, - }, - class: { - type: [String, Array, Object], - default: '', - }, - style: { - type: Object, - default: () => ({}), - }, + 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' }, + fullscreen: { type: Boolean, default: false }, + draggable: { type: Boolean, default: false }, + draggableBoundary: { type: Boolean, default: false }, + draggableRef: { type: Object as PropType | null>, default: null }, + 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 modalRef = ref() const _visible = ref(props.defaultVisible) - const isVisible = computed(() => props.visible ?? _visible.value) - const isMounted = ref(isVisible.value) const isRootVisible = ref(isVisible.value) watch(isVisible, (value) => { - if (_visible.value !== value) { + if (_visible.value !== value) _visible.value = value - } - if (value) { emit('beforeOpen') - isMounted.value = true isRootVisible.value = true } @@ -111,9 +64,8 @@ export default defineComponent({ }) const handleMaskClick = () => { - if (props.maskClosable) { + if (props.maskClosable) emit('update:visible', false) - } } const handleCancel = async () => { @@ -128,11 +80,8 @@ export default defineComponent({ result.then(resolve).catch(() => resolve(false)) }) } - catch { - allow = false - } + catch { allow = false } } - if (allow) { emit('update:visible', false) emit('cancel') @@ -162,18 +111,77 @@ export default defineComponent({ confirmLoading.value = false } - const handleAfterEnter = () => { - emit('open') - } - + const handleAfterEnter = () => emit('open') const handleAfterLeave = () => { isRootVisible.value = false - if (props.destroyOnClose) { + if (props.destroyOnClose) isMounted.value = false - } emit('close') } + // 拖拽逻辑 + const dragData = { dragging: false, offsetX: 0, offsetY: 0 } + + const handleMouseMove = (e: MouseEvent) => { + if (!dragData.dragging || !modalRef.value) + return + const modal = modalRef.value + const rect = modal.getBoundingClientRect() + let newLeft = e.clientX - dragData.offsetX + let newTop = e.clientY - dragData.offsetY + + if (props.draggableBoundary) { + const winWidth = window.innerWidth + const winHeight = window.innerHeight + if (newLeft < 0) + newLeft = 0 + if (newTop < 0) + newTop = 0 + if (newLeft + rect.width > winWidth) + newLeft = winWidth - rect.width + if (newTop + rect.height > winHeight) + newTop = winHeight - rect.height + } + + modal.style.left = `${newLeft}px` + modal.style.top = `${newTop}px` + modal.style.position = 'absolute' + modal.style.transform = '' + } + + const handleMouseUp = () => { + dragData.dragging = false + document.removeEventListener('mousemove', handleMouseMove) + document.removeEventListener('mouseup', handleMouseUp) + } + + const handleMouseDown = (e: MouseEvent) => { + if (!modalRef.value) + return + + let dragTarget: HTMLElement | null = null + + // 如果传了 ref + if (props.draggableRef?.value) { + dragTarget = props.draggableRef.value + } + // 否则默认 header + else { + dragTarget = modalRef.value.querySelector(`.${prefixCls}-header`) + } + + if (!dragTarget || !dragTarget.contains(e.target as Node)) + return + + dragData.dragging = true + const rect = modalRef.value.getBoundingClientRect() + dragData.offsetX = e.clientX - rect.left + dragData.offsetY = e.clientY - rect.top + + document.addEventListener('mousemove', handleMouseMove) + document.addEventListener('mouseup', handleMouseUp) + } + const typeIconMap: Record = { success: 'ix:success-filled', danger: 'ix:error-filled', @@ -182,45 +190,52 @@ export default defineComponent({ question: 'ix:question-filled', } - return () => { - return ( - - {isMounted.value && ( -
- {props.mask && ( - -
- - )} - ( + + {isMounted.value && ( +
+ {props.mask && ( + +
+ + )} + +
-
-
-
- {props.type !== 'default' && } - {slots.title?.()} - {props.showClose && } -
-
{slots.default?.()}
-
- {slots.footer?.() ?? ( - <> - - - - )} -
+
+
+ {props.type !== 'default' && } + {slots.title?.()} + {props.showClose && } +
+ +
{slots.default?.()}
+
+ {slots.footer?.() ?? ( + <> + + + + )}
- -
- )} - - ) - } +
+ +
+ )} + + ) }, }) diff --git a/code/base-project/base-vue/src/App.vue b/code/base-project/base-vue/src/App.vue index b7477b2d..1a173a2f 100644 --- a/code/base-project/base-vue/src/App.vue +++ b/code/base-project/base-vue/src/App.vue @@ -1,50 +1,34 @@ -