92 lines
2.7 KiB
Vue
92 lines
2.7 KiB
Vue
<script setup lang="ts">
|
|
import { NButton, NModal } from 'naive-ui'
|
|
import { computed } from 'vue'
|
|
|
|
import { useIsMobile } from '@/composables/useIsMobile'
|
|
|
|
const props = defineProps<{
|
|
show: boolean
|
|
title: string
|
|
submitting?: boolean
|
|
submitText?: string
|
|
submitDisabled?: boolean
|
|
danger?: boolean
|
|
width?: number
|
|
}>()
|
|
|
|
const emit = defineEmits<{ 'update:show': [boolean]; submit: [] }>()
|
|
|
|
const isMobile = useIsMobile()
|
|
|
|
/** 移动端全屏(100dvh 覆盖底栏与手势区),桌面维持定宽居中 */
|
|
const modalStyle = computed(() =>
|
|
isMobile.value
|
|
? { width: '100vw', height: '100dvh', maxWidth: '100vw', margin: '0', borderRadius: '0' }
|
|
: { width: `${props.width ?? 480}px`, maxWidth: 'calc(100vw - 24px)' },
|
|
)
|
|
</script>
|
|
|
|
<template>
|
|
<NModal
|
|
:show="show"
|
|
preset="card"
|
|
:title="title"
|
|
:style="modalStyle"
|
|
:class="isMobile ? 'form-modal-mobile' : ''"
|
|
:mask-closable="!submitting"
|
|
:close-on-esc="!submitting"
|
|
@update:show="emit('update:show', $event)"
|
|
>
|
|
<!-- 标题插槽:需要在标题旁放徽章等富内容时覆盖 -->
|
|
<template v-if="$slots.header" #header>
|
|
<slot name="header" />
|
|
</template>
|
|
<div class="form-modal-body">
|
|
<slot />
|
|
</div>
|
|
<template #footer>
|
|
<div class="flex justify-end gap-2.5">
|
|
<NButton
|
|
:size="isMobile ? 'medium' : 'small'"
|
|
:class="isMobile ? 'flex-1' : ''"
|
|
:disabled="submitting"
|
|
@click="emit('update:show', false)"
|
|
>
|
|
取消
|
|
</NButton>
|
|
<NButton
|
|
:size="isMobile ? 'medium' : 'small'"
|
|
:class="isMobile ? 'flex-1' : ''"
|
|
:type="danger ? 'error' : 'primary'"
|
|
:loading="submitting"
|
|
:disabled="submitDisabled"
|
|
@click="emit('submit')"
|
|
>
|
|
{{ submitText ?? '确定' }}
|
|
</NButton>
|
|
</div>
|
|
</template>
|
|
</NModal>
|
|
</template>
|
|
|
|
<style scoped>
|
|
/* 长表单不撑破视口:内容区限高滚动,页脚按钮常驻;
|
|
负 margin + 等量 padding 让滚动条贴弹窗边缘(变量继承自 .n-card) */
|
|
.form-modal-body {
|
|
overflow-y: auto;
|
|
margin-inline: calc(-1 * var(--n-padding-left, 24px));
|
|
padding-inline: var(--n-padding-left, 24px);
|
|
}
|
|
/* 限高只在桌面定宽形态生效:移动全屏由全局 .form-modal-mobile 的 flex:1
|
|
接管高度。不能靠全局规则 max-height:none 覆盖——main.css 先于组件
|
|
scoped 样式加载,同特异性下这里会反超,故直接用断点隔离 */
|
|
@media (min-width: 768px) {
|
|
.form-modal-body {
|
|
max-height: min(62vh, calc(100vh - 220px));
|
|
}
|
|
}
|
|
</style>
|
|
|
|
<!-- 移动端全屏形态 .form-modal-mobile 的规则在 main.css(全局):
|
|
NModal teleport 到 body,scoped 够不到;且该类被各弹窗跨 chunk 复用 -->
|