Files
oci-portal-dash/src/components/ConfirmPop.vue
T

145 lines
4.0 KiB
Vue

<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>