diff --git a/code/base-project/base-vue/packages/ao-design/src/components.ts b/code/base-project/base-vue/packages/ao-design/src/components.ts index ae7f8470..4aa64b43 100644 --- a/code/base-project/base-vue/packages/ao-design/src/components.ts +++ b/code/base-project/base-vue/packages/ao-design/src/components.ts @@ -6,6 +6,7 @@ export { default as Drawer } from './drawer' export { default as Group } from './group' export { default as Icon } from './icon' export { default as Input } from './input' +export { default as Modal } from './modal' export { default as Popconfirm } from './popconfirm' export { default as Popup } from './popup' export { default as Radio } from './radio/radio' diff --git a/code/base-project/base-vue/packages/ao-design/src/index.scss b/code/base-project/base-vue/packages/ao-design/src/index.scss index 55978e97..6e9b9d97 100644 --- a/code/base-project/base-vue/packages/ao-design/src/index.scss +++ b/code/base-project/base-vue/packages/ao-design/src/index.scss @@ -19,6 +19,7 @@ @use './checkbox/index.scss' as *; @use './switch/index.scss' as *; @use './spin/index.scss' as *; +@use './modal/index.scss' as *; [class*='#{variables.$prefix}-'] { -webkit-box-sizing: border-box; diff --git a/code/base-project/base-vue/packages/ao-design/src/modal/index.scss b/code/base-project/base-vue/packages/ao-design/src/modal/index.scss new file mode 100644 index 00000000..d5b3428b --- /dev/null +++ b/code/base-project/base-vue/packages/ao-design/src/modal/index.scss @@ -0,0 +1,67 @@ +@use '../_styles/_variables.scss' as *; + +$modal-prefix: '#{$prefix}-modal'; + +.#{$modal-prefix}-wrapper { + position: fixed; + inset: 0; + display: flex; + align-items: center; + justify-content: center; + z-index: 1000; +} + +.#{$modal-prefix}-mask { + position: absolute; + inset: 0; + background: rgba(0, 0, 0, 0.45); +} + +.#{$modal-prefix} { + position: relative; + z-index: 1001; + background: #fff; + border-radius: 8px; + min-width: 300px; + max-width: 90%; + box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15); + display: flex; + flex-direction: column; + overflow: hidden; +} + +.#{$modal-prefix}-header { + padding: 12px 16px; + font-weight: 600; + border-bottom: 1px solid #eee; + display: flex; + justify-content: space-between; + align-items: center; +} + +.#{$modal-prefix}-close { + border: none; + background: transparent; + font-size: 18px; + cursor: pointer; +} + +.#{$modal-prefix}-body { + padding: 16px; +} + +.#{$modal-prefix}-footer { + padding: 12px 16px; + border-top: 1px solid #eee; + text-align: right; +} + +.#{$modal-prefix}-fade-enter-active, +.#{$modal-prefix}-fade-leave-active { + transition: opacity 0.2s ease; +} + +.#{$modal-prefix}-fade-enter-from, +.#{$modal-prefix}-fade-leave-to { + opacity: 0; +} 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 new file mode 100644 index 00000000..ff95c6fa --- /dev/null +++ b/code/base-project/base-vue/packages/ao-design/src/modal/index.tsx @@ -0,0 +1,85 @@ +import { defineComponent, ref, Teleport, Transition, watch } from 'vue' +import { getPrefixCls } from '../_utils/getPrefixCls' +import { addModal, getMaskZIndex, removeModal } from './modalManager' + +export default defineComponent({ + name: 'Modal', + props: { + visible: { type: Boolean, default: false }, + mask: { type: Boolean, default: true }, + maskClosable: { type: Boolean, default: true }, + class: { type: [String, Array, Object], default: '' }, + style: { type: Object, default: () => ({}) }, + lazy: { type: Boolean, default: true }, // 是否懒加载 + }, + emits: ['update:visible', 'close'], + setup(props, { slots, emit }) { + const prefixCls = getPrefixCls('modal') + const zIndex = ref(0) + const isMounted = ref(false) // DOM 是否挂载 + + // 打开 Modal + const openModal = () => { + zIndex.value = addModal() + isMounted.value = true + } + + // 关闭 Modal + const closeModal = () => { + removeModal(zIndex.value) + zIndex.value = 0 + // 不卸载 DOM,仍保持 isMounted = true + } + + watch( + () => props.visible, + (val) => { + if (val) + openModal() + else closeModal() + }, + { immediate: true }, + ) + + const handleMaskClick = () => { + if (props.maskClosable) { + emit('update:visible', false) + emit('close') + } + } + + const handleClose = () => { + emit('update:visible', false) + emit('close') + } + + return () => ( + + {/* DOM 永远挂载,只用 v-show 控制显示 */} +
+ {props.mask && ( +
+ )} +
+
+ {slots.title?.()} + +
+
{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 new file mode 100644 index 00000000..00cefc27 --- /dev/null +++ b/code/base-project/base-vue/packages/ao-design/src/modal/modalManager.ts @@ -0,0 +1,31 @@ +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 + } + modalStack.push(zIndex) + return zIndex +} + +export function removeModal(zIndex: number) { + const idx = modalStack.indexOf(zIndex) + if (idx !== -1) { + modalStack.splice(idx, 1) + freeZIndex.push(zIndex) // 回收 zIndex + } +} + +export function getTopZIndex() { + return modalStack.length > 0 ? modalStack[modalStack.length - 1] : zIndexBase +} + +export function getMaskZIndex() { + return modalStack.length > 0 ? modalStack[modalStack.length - 1] - 1 : zIndexBase +} diff --git a/code/base-project/base-vue/packages/ao-design/src/switch/index.tsx b/code/base-project/base-vue/packages/ao-design/src/switch/index.tsx index 3a8d31ea..7ddcbb38 100644 --- a/code/base-project/base-vue/packages/ao-design/src/switch/index.tsx +++ b/code/base-project/base-vue/packages/ao-design/src/switch/index.tsx @@ -31,10 +31,14 @@ export default defineComponent({ type: Boolean, default: false, }, + loading: { type: Boolean, default: false, }, + beforeConfirm: { + type: Function as PropType<() => boolean | Promise>, + }, class: { type: [String, Array, Object], default: '', @@ -48,22 +52,44 @@ export default defineComponent({ setup(props, { emit, slots }) { const prefixCls = getPrefixCls('switch') + const _loading = ref(false) const _checked = ref(props.modelValue) + const isLoading = computed(() => props.loading || _loading.value) + watch( () => props.modelValue, (val) => { _checked.value = val }, ) + const isChecked = computed(() => _checked.value === props.checkedValue) - const toggle = (e: Event) => { - const newValue = isChecked.value ? props.uncheckedValue : props.checkedValue - _checked.value = newValue + const toggle = async (e: Event) => { + if (props.disabled || isLoading.value) + return - emit('update:modelValue', newValue) - emit('change', newValue, e) + _loading.value = true + let allow = true + + if (props.beforeConfirm) { + try { + allow = await Promise.resolve(props.beforeConfirm()) + } + catch { + allow = false + } + } + + if (allow) { + const newValue = isChecked.value ? props.uncheckedValue : props.checkedValue + _checked.value = newValue + emit('update:modelValue', newValue) + emit('change', newValue, e) + } + + _loading.value = false } const displayContent = computed(() => { @@ -96,13 +122,13 @@ export default defineComponent({ { [`${prefixCls}-checked`]: isChecked.value, [`${prefixCls}-disabled`]: props.disabled, - [`${prefixCls}-loading`]: props.loading, + [`${prefixCls}-loading`]: isLoading.value, }, ]} onClick={toggle} > - {props.loading && } + {isLoading.value && } {displayContent.value} {displayContent.value} diff --git a/code/base-project/base-vue/src/App.vue b/code/base-project/base-vue/src/App.vue index 8063e42d..be15aacd 100644 --- a/code/base-project/base-vue/src/App.vue +++ b/code/base-project/base-vue/src/App.vue @@ -1,24 +1,58 @@ + - + + + +

这是第 1 个弹窗内容

+ +
+ + + + +

这是第 2 个弹窗内容

+ +
+ + + + +

这是第 3 个弹窗内容

+ +
+ diff --git a/code/base-project/base-vue/src/components.d.ts b/code/base-project/base-vue/src/components.d.ts index 012e7f17..a14b26fc 100644 --- a/code/base-project/base-vue/src/components.d.ts +++ b/code/base-project/base-vue/src/components.d.ts @@ -18,6 +18,7 @@ declare module 'vue' { AInput: typeof import('@ao/ao-design')['Input'] ALoading: typeof import('@ao/ao-design')['Loading'] AMessage: typeof import('@ao/ao-design')['Message'] + AModal: typeof import('@ao/ao-design')['Modal'] APopconfirm: typeof import('@ao/ao-design')['Popconfirm'] APopup: typeof import('@ao/ao-design')['Popup'] ARadio: typeof import('@ao/ao-design')['Radio'] diff --git a/code/base-project/base-vue/test.html b/code/base-project/base-vue/test.html index 784b7862..dcf6d501 100644 --- a/code/base-project/base-vue/test.html +++ b/code/base-project/base-vue/test.html @@ -1,64 +1,73 @@ -# 驰诺国际货运有限公司 + + + + + + Dialog Multi-Level Demo + + + + -
订舱委托书
+ -公司编号:CNNYYY230108 + -
- - - - - - - - - - - - - - - - - - - - - -
- 发货人 JIANGXIARTSENCHEMICALCO.,LTD SULPHUR&PHOSPHORUS CHEMICAL BASE GUIXI,JIANGXI,P.R.CHINA -
- 收货人 ARTSEN?CHEMICAL?AMERICA?LLC 19?LOVELAND?STREET MADISON,?NJ?07940 T:?908-419-0704??F:973-520-8290 - ATTN:?WALTER?CHENG -
- 通知人 ARTSEN?CHEMICAL?AMERICA?LLC 19?LOVELAND?STREET MADISON,?NJ?07940 T:?908-419-0704??F:973-520-8290 - ATTN:?WALTER?CHENG -
船公司船期 2023/01/20航名/航次 XIN DA YANG ZHOU 086E
起运港 NINGBO, CHINA目的港 HOUSTON
-
+ + +