---
title: "Popconfirm"
description: "基于气泡弹出层的操作确认组件。"
seo_title: "Popconfirm"
seo_description: "A popover-based confirmation component for guarding actions, with async confirm and custom content."
canonical_url: "https://nuxt.mhaibaraai.cn/docs/components/popconfirm"
---
# Popconfirm

> 基于气泡弹出层的操作确认组件。

## 简介

`MPopconfirm` 是一个气泡式确认组件，在用户执行危险或不可逆操作前弹出确认气泡。支持同步与异步确认回调，并完整透传 `UPopover` 的所有定位参数。

> \[\!NOTE\]
> See: https://ui.nuxt.com/docs/components/popover
> 
> 基于 Nuxt UI 的 Popover 组件封装

## 用法

`default` 插槽放置触发元素，点击后弹出确认气泡：

```vue
<template>
  <MPopconfirm title="确认删除？" description="删除后数据将无法恢复。">
    <UButton label="删除记录" color="neutral" variant="outline" icon="i-lucide-trash" />
  </MPopconfirm>
</template>
```

### `type` 类型

`type` 影响默认图标与确认按钮颜色，包含六种语义化值：

```vue
<template>
  <MPopconfirm type="warning">
    <UButton label="打开" color="neutral" variant="soft" />
  </MPopconfirm>
</template>
```

### `icon` 图标

`icon` 覆盖 `type` 默认图标，传入任意 Iconify 图标名称：

```vue
<template>
  <MPopconfirm type="error" icon="i-lucide-trash-2">
    <UButton label="危险操作" color="error" variant="soft" icon="i-lucide-alert-triangle" />
  </MPopconfirm>
</template>
```

### `confirmButton` 确认按钮

`confirmButton` 接收完整 `ButtonProps`，可定制 label、color、icon、variant：

```vue
<template>
  <MPopconfirm :confirm-button='{"color":"error","label":"确认删除","icon":"i-lucide-trash-2"}'>
    <UButton label="删除记录" color="error" variant="soft" />
  </MPopconfirm>
</template>
```

### `cancelButton` 取消按钮

`cancelButton` 接收 `ButtonProps` 或 `false`，传 `false` 时隐藏取消按钮以强制确认：

```vue
<template>
  <MPopconfirm :cancel-button="false">
    <UButton label="强制执行" color="warning" variant="soft" />
  </MPopconfirm>
</template>
```

### `dismissible` 关闭策略

`dismissible` 默认为 `false` 严格模式；开启后允许点击遮罩或按 Esc 关闭气泡：

```vue
<template>
  <MPopconfirm :dismissible="false">
    <UButton label="关闭策略" color="neutral" variant="outline" />
  </MPopconfirm>
</template>
```

### `arrow` 箭头

`arrow` 控制气泡指向触发器的箭头，默认开启：

```vue
<template>
  <MPopconfirm arrow>
    <UButton label="箭头开关" color="neutral" variant="subtle" />
  </MPopconfirm>
</template>
```

## 示例

### 异步确认

`:on-confirm` 支持返回 `Promise`，期间确认按钮自动进入 loading 状态，成功后自动关闭弹层：

```vue [ComponentsPopconfirmAsyncExample.vue]
<script setup lang="ts">
const result = ref('')

async function handleSubmit() {
  await new Promise(resolve => setTimeout(resolve, 1500))
  result.value = '提交成功'
}
</script>

<template>
  <div class="flex flex-wrap items-center gap-3">
    <MPopconfirm
      type="primary"
      title="确认提交？"
      description="提交后将立即生效，请确认操作。"
      :on-confirm="handleSubmit"
    >
      <UButton color="primary" label="提交申请" icon="i-lucide-send" />
    </MPopconfirm>

    <span v-if="result" class="text-sm text-success">{{ result }}</span>
  </div>
</template>
```

### 正文插槽

使用 `body` 插槽插入任意内容，`description` 传空字符串可隐藏默认描述区：

```vue [ComponentsPopconfirmSlotExample.vue]
<script setup lang="ts">
const result = ref('')
</script>

<template>
  <div class="flex flex-wrap items-center gap-3">
    <MPopconfirm
      title="删除用户"
      :description="''"
      :on-confirm="() => { result = '用户已删除' }"
    >
      <template #body>
        <div class="flex flex-col gap-2 py-1">
          <p class="text-sm text-muted">
            即将删除以下用户，操作不可撤销：
          </p>
          <div class="flex items-center gap-2 rounded-md bg-elevated px-3 py-2">
            <UAvatar size="xs" icon="i-lucide-user" />
            <div class="flex flex-col">
              <span class="text-xs font-medium text-highlighted">张三</span>
              <span class="text-xs text-muted">zhangsan@example.com</span>
            </div>
          </div>
        </div>
      </template>

      <UButton color="error" variant="soft" label="删除用户" icon="i-lucide-user-x" />
    </MPopconfirm>

    <span v-if="result" class="text-sm text-error">{{ result }}</span>
  </div>
</template>
```

### 样式定制

通过 `ui` 属性覆盖内部各区块的 class，支持 `title`、`description`、`footer`、`content` 等：

```vue [ComponentsPopconfirmUiExample.vue]
<script setup lang="ts">
const result = ref('')
</script>

<template>
  <div class="flex flex-wrap items-center gap-3">
    <MPopconfirm
      type="info"
      title="样式定制示例"
      description="通过 ui 属性覆盖内部各区块的 class，实现精细样式控制。"
      :on-confirm="() => { result = '已确认' }"
      :ui="{
        content: 'w-72',
        title: 'text-info text-base',
        description: 'text-xs text-info/70',
        footer: 'justify-between pt-2 border-t border-default'
      }"
      :confirm-button="{ label: '好的，执行', color: 'info' }"
      :cancel-button="{ label: '不了，取消', variant: 'ghost' }"
    >
      <UButton color="info" variant="soft" label="打开定制气泡" icon="i-lucide-palette" />
    </MPopconfirm>

    <span v-if="result" class="text-sm text-muted">{{ result }}</span>
  </div>
</template>
```

### 弹出方向

透传 `content` 属性给底层 `UPopover`，支持 `top`、`bottom`、`left`、`right` 四个方向：

```vue [ComponentsPopconfirmSideExample.vue]
<script setup lang="ts">
type Side = 'top' | 'bottom' | 'left' | 'right'

const sides: Side[] = ['top', 'bottom', 'left', 'right']
const result = ref('')
</script>

<template>
  <div class="flex flex-wrap items-center gap-2">
    <MPopconfirm
      v-for="side in sides"
      :key="side"
      :content="{ side }"
      :title="`从 ${side} 方弹出`"
      description="透传 Popover 定位参数，支持 top / bottom / left / right。"
      :on-confirm="() => { result = `已从 ${side} 方确认` }"
    >
      <UButton color="neutral" variant="outline" size="sm" :label="side" />
    </MPopconfirm>

    <span v-if="result" class="ml-2 text-sm text-muted">{{ result }}</span>
  </div>
</template>
```

### 错误处理

当 `onConfirm` 回调抛出异常时，弹层保持打开并触发 `@error` 事件，可在此处展示错误反馈：

```vue [ComponentsPopconfirmErrorExample.vue]
<script setup lang="ts">
const errorMsg = ref('')

const toast = useToast()

async function failingAction() {
  await new Promise(resolve => setTimeout(resolve, 800))
  throw new Error('服务器返回错误：操作被拒绝')
}

function handleError(err: unknown) {
  errorMsg.value = err instanceof Error ? err.message : '未知错误'
  toast.add({
    color: 'error',
    title: '操作失败',
    description: errorMsg.value
  })
}
</script>

<template>
  <div class="flex flex-col gap-3">
    <div class="flex flex-wrap items-center gap-3">
      <MPopconfirm
        type="error"
        title="模拟操作失败"
        description="确认后将触发一个失败的异步操作，弹层保持打开并上报错误。"
        :on-confirm="failingAction"
        @error="handleError"
      >
        <UButton color="error" variant="soft" label="触发失败操作" icon="i-lucide-zap-off" />
      </MPopconfirm>
    </div>
  </div>
</template>
```

### 事件回调

同步确认、取消、异步确认与异常依次触发 `confirm`、`cancel` 与 `error`：

```vue [ComponentsPopconfirmEventsExample.vue]
<script setup lang="ts">
const log = ref<string[]>([])

function record(msg: string) {
  log.value = [`[${new Date().toLocaleTimeString()}] ${msg}`, ...log.value].slice(0, 8)
}

async function asyncConfirm() {
  await new Promise(resolve => setTimeout(resolve, 1200))
  record('async confirmed')
}

function rejectedConfirm() {
  throw new Error('被拒绝')
}
</script>

<template>
  <div class="grid grid-cols-1 lg:grid-cols-[1fr_220px] gap-3">
    <div class="flex flex-wrap gap-2">
      <MPopconfirm
        type="warning"
        title="点击确认或取消"
        description="确认会触发 confirm 事件，取消会触发 cancel 事件。"
        :on-confirm="() => record('confirmed')"
        @cancel="record('cancelled')"
      >
        <UButton color="primary" variant="soft" label="同步事件" />
      </MPopconfirm>

      <MPopconfirm
        type="info"
        title="异步事件"
        description="确认按钮等待 onConfirm 异步完成后才触发 confirm 并关闭。"
        :on-confirm="asyncConfirm"
      >
        <UButton color="info" variant="soft" label="异步确认" />
      </MPopconfirm>

      <MPopconfirm
        type="error"
        title="抛错保留"
        description="onConfirm 抛出错误时弹层保持打开，并通过 error 事件暴露异常。"
        :on-confirm="rejectedConfirm"
        @error="(e: unknown) => record(`error: ${(e as Error).message}`)"
      >
        <UButton color="error" variant="outline" label="抛错保留" />
      </MPopconfirm>
    </div>

    <div class="rounded border border-default p-2 text-xs text-muted space-y-1 max-h-40 overflow-auto">
      <p v-if="!log.length">
        暂无事件记录
      </p>
      <p v-for="(item, idx) in log" :key="idx">
        {{ item }}
      </p>
    </div>
  </div>
</template>
```

## API

### Props

```ts
/**
 * Props for the MPopconfirm component
 */
interface MPopconfirmProps {
  /**
   * 确认气泡的标题文本。
   * @default "\"确认操作\""
   */
  title?: string | undefined;
  /**
   * 标题下方的补充说明。
   * 传入空字符串时可隐藏描述区。
   * @default "\"请确认是否执行此操作?\""
   */
  description?: string | undefined;
  /**
   * 预设的语义化颜色主题，会影响图标。
   * @default "\"neutral\""
   */
  type?: "error" | "primary" | "info" | "success" | "warning" | "neutral" | undefined;
  /**
   * 标题前展示的图标名称。
   * @default "\"i-lucide-circle-question-mark\""
   */
  icon?: any;
  /**
   * 气泡内容与触发器之间的箭头指示。
   * @default "true"
   */
  arrow?: boolean | undefined;
  /**
   * 透传给确认按钮的属性。
   * `loading` 状态由组件内部托管。
   */
  confirmButton?: OmitByKey<ButtonProps, "loading" | LinkPropsKeys> | undefined;
  /**
   * 透传给取消按钮的属性。
   * 传入 `false` 可完全隐藏取消按钮。
   * @default "true"
   */
  cancelButton?: boolean | ButtonProps | undefined;
  /**
   * 当 `false` 时，点击遮罩层或按下 `Esc` 键将不会关闭弹层。
   */
  dismissible?: boolean | undefined;
  ui?: (Record<string, C> & { content?: SlotClass; arrow?: SlotClass; header?: SlotClass; title?: SlotClass; description?: SlotClass; body?: SlotClass; footer?: SlotClass; icon?: SlotClass; }) | undefined;
  /**
   * The display mode of the popover.
   */
  mode?: PopoverMode | undefined;
  /**
   * The content of the popover.
   */
  content?: (Omit<PopoverContentProps, "as" | "asChild" | "forceMount"> & Partial<EmitsToProps<PopoverContentImplEmits>>) | undefined;
  /**
   * Render the popover in a portal.
   */
  portal?: string | boolean | HTMLElement | undefined;
  /**
   * The reference (or anchor) element that is being referred to for positioning.
   * 
   * Accepts an element or a virtual element (anything with `getBoundingClientRect`),
   * and can be changed reactively to re-anchor the popover (e.g. for a guided tour).
   * If not provided will use the current component as anchor.
   */
  reference?: ReferenceElement | undefined;
  /**
   * The modality of the popover. When set to true, interaction with outside elements will be disabled and only popover content will be visible to screen readers.
   */
  modal?: boolean | undefined;
  /**
   * The duration from when the mouse enters the trigger until the hover card opens.
   */
  openDelay?: number | undefined;
  /**
   * The duration from when the mouse leaves the trigger or content until the hover card closes.
   */
  closeDelay?: number | undefined;
  /**
   * When `true`, tapping the trigger on touch devices toggles the hover card open/closed. By default touch interactions are ignored to match pointer hover semantics.
   */
  enableTouch?: boolean | undefined;
}
```

### Emits

```ts
/**
 * Emitted events for the MPopconfirm component
 */
interface MPopconfirmEmits {
  confirm: (payload: []) => void;
  cancel: (payload: []) => void;
  error: (payload: [error: unknown]) => void;
}
```

### Slots

```ts
/**
 * Slots for the MPopconfirm component
 */
interface MPopconfirmSlots {
  default(): any;
  header(): any;
  title(): any;
  description(): any;
  actions(): any;
  body(): any;
  footer(): any;
}
```

## Theme

<component-theme></component-theme>## Changelog

See commit history for [src/runtime/components/Popconfirm.vue](https://github.com/mhaibaraai/movk-nuxt/commits/main/src/runtime/components/Popconfirm.vue).


## Sitemap

See the full [sitemap](/sitemap.md) for all pages.
