@@ -0,0 +1,144 @@
|
||||
<script setup lang="ts">
|
||||
import { NButton, NPopover, type PopoverPlacement } from 'naive-ui'
|
||||
import { ref } from 'vue'
|
||||
|
||||
export type ConfirmKind = 'danger' | 'warn' | 'plain'
|
||||
|
||||
/**
|
||||
* 统一确认气泡:替代 NPopconfirm 默认样式(批次8 设计稿)。
|
||||
* onConfirm 返回 Promise 时确认按钮进入 loading,期间弹层不可关闭、不可重复提交。
|
||||
*/
|
||||
const props = withDefaults(
|
||||
defineProps<{
|
||||
title: string
|
||||
content?: string
|
||||
note?: string
|
||||
kind?: ConfirmKind
|
||||
confirmText?: string
|
||||
cancelText?: string
|
||||
placement?: PopoverPlacement
|
||||
onConfirm?: () => unknown
|
||||
}>(),
|
||||
{
|
||||
kind: 'danger',
|
||||
confirmText: '确认',
|
||||
cancelText: '取消',
|
||||
placement: 'top',
|
||||
content: undefined,
|
||||
note: undefined,
|
||||
onConfirm: undefined,
|
||||
},
|
||||
)
|
||||
|
||||
const show = ref(false)
|
||||
const loading = ref(false)
|
||||
|
||||
function onUpdateShow(v: boolean) {
|
||||
if (loading.value) return
|
||||
show.value = v
|
||||
}
|
||||
|
||||
async function confirm() {
|
||||
if (loading.value) return
|
||||
const r = props.onConfirm?.()
|
||||
if (r instanceof Promise) {
|
||||
loading.value = true
|
||||
try {
|
||||
await r
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
show.value = false
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<NPopover
|
||||
:show="show"
|
||||
trigger="click"
|
||||
:placement="placement"
|
||||
raw
|
||||
:show-arrow="false"
|
||||
@update:show="onUpdateShow"
|
||||
>
|
||||
<template #trigger><slot name="trigger" /></template>
|
||||
<div
|
||||
class="w-[268px] rounded-lg border border-line bg-white p-4 pb-3.5 shadow-overlay"
|
||||
>
|
||||
<div class="flex items-center gap-2.5">
|
||||
<span
|
||||
class="flex h-[26px] w-[26px] flex-none items-center justify-center rounded-[7px]"
|
||||
:class="
|
||||
kind === 'danger'
|
||||
? 'bg-err/12 text-err'
|
||||
: kind === 'warn'
|
||||
? 'bg-warn/14 text-warn'
|
||||
: 'bg-wash text-ink-2'
|
||||
"
|
||||
>
|
||||
<svg
|
||||
v-if="kind === 'danger'"
|
||||
width="14"
|
||||
height="14"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
stroke-width="2"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
>
|
||||
<path d="M10.29 3.86L1.82 18a2 2 0 0 0 1.71 3h16.94a2 2 0 0 0 1.71-3L13.71 3.86a2 2 0 0 0-3.42 0z" />
|
||||
<line x1="12" y1="9" x2="12" y2="13" />
|
||||
<line x1="12" y1="17" x2="12.01" y2="17" />
|
||||
</svg>
|
||||
<svg
|
||||
v-else-if="kind === 'warn'"
|
||||
width="14"
|
||||
height="14"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
stroke-width="2"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
>
|
||||
<path d="M12 22s8-4 8-10V5l-8-3-8 3v7c0 6 8 10 8 10z" />
|
||||
</svg>
|
||||
<svg
|
||||
v-else
|
||||
width="14"
|
||||
height="14"
|
||||
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="16" x2="12" y2="12" />
|
||||
<line x1="12" y1="8" x2="12.01" y2="8" />
|
||||
</svg>
|
||||
</span>
|
||||
<!-- 超长资源名(如桶名)单行截断,悬停 tooltip 看全文 -->
|
||||
<span class="min-w-0 truncate text-[13.5px] font-semibold" :title="title">{{ title }}</span>
|
||||
</div>
|
||||
<div v-if="content" class="mt-2 pl-9 text-[12.5px] leading-relaxed text-ink-2">
|
||||
{{ content }}
|
||||
</div>
|
||||
<div v-if="note" class="mt-1 pl-9 text-xs leading-normal text-ink-3">{{ note }}</div>
|
||||
<div class="mt-3 flex justify-end gap-2">
|
||||
<NButton size="small" :disabled="loading" @click="show = false">{{ cancelText }}</NButton>
|
||||
<NButton
|
||||
size="small"
|
||||
:type="kind === 'danger' ? 'error' : 'primary'"
|
||||
:loading="loading"
|
||||
@click="confirm"
|
||||
>
|
||||
{{ confirmText }}
|
||||
</NButton>
|
||||
</div>
|
||||
</div>
|
||||
</NPopover>
|
||||
</template>
|
||||
Reference in New Issue
Block a user