Commit 3b790f14 by lijiabin

【需求 20331】 feat: 加入messagebox组件以及相关hook

parent 5351d2ad
<template>
<Teleport to="body">
<Transition name="confirm">
<div v-if="visible" class="fixed inset-0 z-3000 flex items-center justify-center p-4">
<!-- Backdrop -->
<div class="confirm-backdrop absolute inset-0 bg-black/20" />
<!-- Card -->
<div
class="confirm-card relative bg-white rounded-2xl max-w-full"
:style="{ width: normalizedWidth }"
>
<!-- 右上角关闭按钮 -->
<div class="absolute top-2 right-2 cursor-pointer" @click="close">
<el-icon><Close /></el-icon>
</div>
<div class="px-6 py-5">
<!-- Icon -->
<div
class="mx-auto mb-3 flex h-10 w-10 items-center justify-center rounded-xl"
:class="
type === 'danger'
? 'bg-red-50'
: type === 'warning'
? 'bg-yellow-50'
: 'bg-indigo-50/80'
"
>
<svg
class="w-5 h-5"
:class="
type === 'danger'
? 'text-red-500'
: type === 'warning'
? 'text-yellow-500'
: 'text-[#7083FF]'
"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
>
<circle cx="12" cy="12" r="10" />
<line x1="12" y1="8" x2="12" y2="12" />
<line x1="12" y1="16" x2="12.01" y2="16" />
</svg>
</div>
<!-- Title -->
<h3 class="text-center text-base font-600 text-gray-800 leading-6">
{{ title }}
</h3>
<!-- Message -->
<p v-if="message" class="mt-1.5 text-center text-13px text-gray-400 leading-5">
{{ message }}
</p>
<!-- Actions -->
<div class="mt-5 grid grid-cols-2 gap-2.5">
<button
v-if="showCancelButton"
type="button"
class="confirm-btn cancel-btn"
@click="onCancel"
>
{{ cancelText }}
</button>
<button
v-if="showConfirmButton"
type="button"
class="confirm-btn primary-btn"
:class="{ 'danger-btn': type === 'danger', 'warning-btn': type === 'warning' }"
:disabled="loading"
@click="onConfirm"
>
<svg
v-if="loading"
class="animate-spin h-3.5 w-3.5 mr-1.5 text-white/70"
viewBox="0 0 24 24"
fill="none"
>
<circle
cx="12"
cy="12"
r="10"
stroke="currentColor"
stroke-width="3"
class="opacity-25"
/>
<path
fill="currentColor"
class="opacity-75"
d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4z"
/>
</svg>
{{ confirmText }}
</button>
</div>
</div>
</div>
</div>
</Transition>
</Teleport>
</template>
<script setup lang="ts">
import type { MessageBoxProps } from './types'
import { Close } from '@element-plus/icons-vue'
const {
title = '确认操作',
message = '',
confirmText = '确认',
cancelText = '取消',
width = 380,
loading = false,
type = 'primary',
showCancelButton = true,
showConfirmButton = true,
} = defineProps<MessageBoxProps>()
const normalizedWidth = computed(() => (typeof width === 'number' ? `${width}px` : width))
const visible = defineModel<boolean>({ default: false })
const emit = defineEmits<{
confirm: []
cancel: []
close: []
}>()
const close = () => {
visible.value = false
emit('close')
}
const onCancel = () => {
emit('cancel')
close()
}
const onConfirm = () => {
emit('confirm')
if (!loading) close()
}
const open = () => {
visible.value = true
}
defineExpose({ open, close })
</script>
<style scoped>
.confirm-enter-active {
transition: opacity 0.22s ease;
}
.confirm-leave-active {
transition: opacity 0.16s ease;
}
.confirm-enter-from,
.confirm-leave-to {
opacity: 0;
}
.confirm-enter-to,
.confirm-leave-from {
opacity: 1;
}
.confirm-enter-active .confirm-card {
transition: transform 0.3s cubic-bezier(0.22, 1.2, 0.36, 1);
}
.confirm-leave-active .confirm-card {
transition: transform 0.16s ease;
}
.confirm-enter-from .confirm-card {
transform: scale(0.82);
}
.confirm-leave-to .confirm-card {
transform: scale(0.96);
}
.confirm-card {
box-shadow:
0 8px 30px rgba(100, 116, 180, 0.16),
0 2px 6px rgba(0, 0, 0, 0.04);
}
.confirm-btn {
display: inline-flex;
align-items: center;
justify-content: center;
height: 36px;
border-radius: 10px;
font-size: 13px;
font-weight: 600;
cursor: pointer;
transition: all 0.2s ease;
border: none;
outline: none;
}
.cancel-btn {
color: rgb(107 114 128);
background: #fff;
border: 1px solid rgb(229 231 235);
}
.cancel-btn:hover {
color: rgb(75 85 99);
background: rgb(249 250 251);
border-color: rgb(209 213 219);
}
.cancel-btn:active {
transform: scale(0.97);
}
.primary-btn {
color: #fff;
background: linear-gradient(135deg, #b3b8fd 0%, #7083ff 100%);
box-shadow: 0 2px 8px rgba(112, 131, 255, 0.3);
}
.primary-btn:hover {
box-shadow: 0 4px 14px rgba(112, 131, 255, 0.4);
filter: brightness(1.05);
}
.primary-btn:active {
transform: scale(0.97);
}
.primary-btn:disabled {
opacity: 0.7;
cursor: not-allowed;
}
.danger-btn {
background: linear-gradient(135deg, #fca5a5 0%, #ef4444 100%);
box-shadow: 0 2px 8px rgba(239, 68, 68, 0.3);
}
.danger-btn:hover {
box-shadow: 0 4px 14px rgba(239, 68, 68, 0.4);
}
.warning-btn {
background: linear-gradient(135deg, #ffa500 0%, #ff8c00 100%);
box-shadow: 0 2px 8px rgba(255, 165, 0, 0.3);
}
.warning-btn:hover {
box-shadow: 0 4px 14px rgba(255, 165, 0, 0.4);
}
</style>
export interface MessageBoxProps {
title?: string
message?: string
confirmText?: string
cancelText?: string
width?: string | number
loading?: boolean
type?: 'primary' | 'warning' | 'danger'
showCancelButton?: boolean
showConfirmButton?: boolean
}
......@@ -4,3 +4,4 @@ export * from './useScrollTop'
export * from './useHintAnimation'
export * from './useUploadImg'
export * from './useNavigation'
export * from './useMessageBox.tsx'
import MessageBox from '@/components/common/MessageBox/index.vue'
import type { MessageBoxProps } from '@/components/common/MessageBox/types'
import { render, effect } from 'vue'
interface ConfirmProps extends MessageBoxProps {
useLoading?: boolean // 是否用到loading
}
export const useMessageBox = () => {
const confirm = (props: ConfirmProps) => {
return new Promise((resolve, reject) => {
const visible = ref(false)
const loading = ref(false)
const onConfirm = () => {
if (props.useLoading) {
// 设置loading
loading.value = true
// 关闭全给用户
resolve({
close: () => {
visible.value = false
},
setLoading: (value: boolean) => {
loading.value = value
},
})
} else {
resolve(true)
}
}
const onCancel = () => {
reject(false)
}
const onClose = () => {
reject(false)
}
effect(() => {
render(
<MessageBox
v-model={visible.value}
{...props}
loading={loading.value}
onConfirm={onConfirm}
onCancel={onCancel}
onClose={onClose}
/>,
document.body,
)
})
visible.value = true
})
}
return { confirm }
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment