在线示例
本页所有示例都运行在真实的库上 —— 点一点,亲眼看看。
确认对话框
经典场景:await 用户给出的布尔值。点击遮罩会关闭对话框并以 SummonDismissedError 拒绝。
结果: —
查看源码
vue
<script setup lang="ts">
import { useData } from 'vitepress'
import { computed, ref } from 'vue'
import { summon, SummonDismissedError } from 'vue-summon'
import ConfirmDialog from './ConfirmDialog.vue'
const { lang } = useData()
const zh = computed(() => lang.value.startsWith('zh'))
const result = ref('')
async function open() {
result.value = zh.value ? '等待用户操作…' : 'waiting for user…'
try {
const ok = await summon(ConfirmDialog, {
title: zh.value ? '删除这份文件?' : 'Delete this file?',
message: zh.value
? '此操作无法撤销。对话框通过 Promise 把用户的选择返回给调用方。'
: "This action cannot be undone. The dialog returns the user's choice to the caller via a Promise.",
})
result.value = ok
? zh.value
? 'resolve(true) → 已确认'
: 'resolve(true) → confirmed'
: zh.value
? 'resolve(false) → 已取消'
: 'resolve(false) → canceled'
} catch (error) {
if (error instanceof SummonDismissedError) {
result.value = zh.value
? 'reject(SummonDismissedError) → 点击遮罩关闭'
: 'reject(SummonDismissedError) → dismissed via overlay'
}
}
}
</script>
<template>
<div class="vs-demo">
<button class="vs-btn vs-btn-brand" @click="open">
{{ zh ? '打开确认对话框' : 'Open confirm dialog' }}
</button>
<div class="vs-demo-result">
{{ zh ? '结果' : 'result' }}: <strong>{{ result || '—' }}</strong>
</div>
</div>
</template>vue
<script setup lang="ts">
import { useData } from 'vitepress'
import { computed } from 'vue'
import { useSummoned } from 'vue-summon'
defineProps<{ title: string; message: string }>()
const { resolve, dismiss } = useSummoned<boolean>()
const { lang } = useData()
const zh = computed(() => lang.value.startsWith('zh'))
</script>
<template>
<div class="vs-overlay" @click.self="dismiss()">
<div class="vs-dialog">
<h3>{{ title }}</h3>
<p>{{ message }}</p>
<div class="vs-dialog-actions">
<button class="vs-btn vs-btn-ghost" @click="resolve(false)">
{{ zh ? '取消' : 'Cancel' }}
</button>
<button class="vs-btn vs-btn-brand" @click="resolve(true)">
{{ zh ? '确认' : 'Confirm' }}
</button>
</div>
</div>
</div>
</template>Toast
发后即忘;也可以等待它的结局 —— 自动关闭,或被点击关闭。
结果: —
查看源码
vue
<script setup lang="ts">
import { useData } from 'vitepress'
import { computed, ref } from 'vue'
import { summon } from 'vue-summon'
import ToastCard from './ToastCard.vue'
const { lang } = useData()
const zh = computed(() => lang.value.startsWith('zh'))
const count = ref(0)
const result = ref('')
async function push() {
count.value += 1
const message = zh.value ? `这是第 ${count.value} 条通知` : `Notification #${count.value}`
const reason = await summon(ToastCard, { message })
result.value = zh.value ? `toast resolve("${reason}")` : `toast resolved with "${reason}"`
}
</script>
<template>
<div class="vs-demo">
<button class="vs-btn vs-btn-brand" @click="push">
{{ zh ? '弹出一个 Toast' : 'Push a toast' }}
</button>
<div class="vs-demo-result">
{{ zh ? '结果' : 'result' }}: <strong>{{ result || '—' }}</strong>
</div>
</div>
</template>vue
<script setup lang="ts">
import { useData } from 'vitepress'
import { computed, onBeforeUnmount, onMounted } from 'vue'
import { useSummoned } from 'vue-summon'
defineProps<{ message: string }>()
const { resolve } = useSummoned<string>()
const { lang } = useData()
const zh = computed(() => lang.value.startsWith('zh'))
let timer: ReturnType<typeof setTimeout> | undefined
onMounted(() => {
timer = setTimeout(() => resolve('auto-closed'), 2600)
})
onBeforeUnmount(() => clearTimeout(timer))
</script>
<template>
<div class="vs-toast" @click="resolve('clicked')">
<span class="vs-toast-dot" />
<span>{{ message }}</span>
<span style="opacity: 0.55; font-size: 12px">{{ zh ? '点击关闭' : 'click to close' }}</span>
</div>
</template>用 update() 推送进度
Promise 同时也是控制器:任务运行中随时修补 props,完成后 resolve。
结果: —
查看源码
vue
<script setup lang="ts">
import { useData } from 'vitepress'
import { computed, ref } from 'vue'
import { summon, SummonDismissedError } from 'vue-summon'
import ProgressDialog from './ProgressDialog.vue'
const { lang } = useData()
const zh = computed(() => lang.value.startsWith('zh'))
const running = ref(false)
const result = ref('')
async function start() {
if (running.value) return
running.value = true
result.value = zh.value ? '任务进行中…' : 'task running…'
const task = summon(ProgressDialog, {
title: zh.value ? '正在上传文件' : 'Uploading files',
progress: 0,
})
let progress = 0
const timer = setInterval(() => {
progress = Math.min(100, progress + 4 + Math.random() * 10)
task.update({ progress: Math.round(progress) })
if (progress >= 100) {
clearInterval(timer)
setTimeout(() => task.resolve('done'), 350)
}
}, 320)
try {
await task
result.value = zh.value ? 'resolve("done") → 上传完成' : 'resolve("done") → upload finished'
} catch (error) {
clearInterval(timer)
if (error instanceof SummonDismissedError) {
result.value = zh.value ? '任务被用户中止' : 'task aborted by user'
}
} finally {
running.value = false
}
}
</script>
<template>
<div class="vs-demo">
<button class="vs-btn vs-btn-brand" :disabled="running" @click="start">
{{ running ? (zh ? '进行中…' : 'Running…') : zh ? '开始模拟任务' : 'Start mock task' }}
</button>
<div class="vs-demo-result">
{{ zh ? '结果' : 'result' }}: <strong>{{ result || '—' }}</strong>
</div>
</div>
</template>vue
<script setup lang="ts">
import { useData } from 'vitepress'
import { computed } from 'vue'
import { useSummoned } from 'vue-summon'
const props = defineProps<{ title: string; progress: number }>()
const { dismiss } = useSummoned<string>()
const { lang } = useData()
const zh = computed(() => lang.value.startsWith('zh'))
const percent = computed(() => Math.min(100, Math.max(0, props.progress)))
</script>
<template>
<div class="vs-overlay">
<div class="vs-dialog">
<h3>{{ title }}</h3>
<div class="vs-progress-track">
<div class="vs-progress-bar" :style="{ width: `${percent}%` }" />
</div>
<p style="margin-bottom: 16px">{{ percent }}%</p>
<div class="vs-dialog-actions">
<button class="vs-btn vs-btn-ghost" @click="dismiss()">
{{ zh ? '取消任务' : 'Abort task' }}
</button>
</div>
</div>
</div>
</template>Key 去重
以相同的 key 召唤两次会返回同一个控制器 —— 永远只渲染一个实例。
log: —
查看源码
vue
<script setup lang="ts">
import { useData } from 'vitepress'
import { computed, ref } from 'vue'
import { summon } from 'vue-summon'
import KeyDialog from './KeyDialog.vue'
const { lang } = useData()
const zh = computed(() => lang.value.startsWith('zh'))
const log = ref('')
function summonTwice() {
const first = summon(KeyDialog, {}, { key: 'singleton' })
const second = summon(KeyDialog, {}, { key: 'singleton' })
log.value = zh.value
? `first === second → ${first === second}(同一个控制器,只渲染一个实例)`
: `first === second → ${first === second} (same controller, one instance rendered)`
void first.then(() => {
log.value = zh.value ? '对话框已 resolve("ok")' : 'dialog resolved with "ok"'
})
}
</script>
<template>
<div class="vs-demo">
<button class="vs-btn vs-btn-brand" @click="summonTwice">
{{ zh ? '以相同 key 召唤两次' : 'Summon twice with the same key' }}
</button>
<div class="vs-demo-result">
log: <strong>{{ log || '—' }}</strong>
</div>
</div>
</template>vue
<script setup lang="ts">
import { useData } from 'vitepress'
import { computed } from 'vue'
import { useSummoned } from 'vue-summon'
const { resolve } = useSummoned<string>()
const { lang } = useData()
const zh = computed(() => lang.value.startsWith('zh'))
</script>
<template>
<div class="vs-overlay">
<div class="vs-dialog">
<h3>{{ zh ? '唯一实例' : 'A unique instance' }}</h3>
<p>
{{
zh
? '这个对话框使用 key: "singleton" 召唤。在它打开期间,再次以相同 key 召唤不会创建新实例,而是返回同一个控制器。'
: 'This dialog was summoned with key: "singleton". While it is open, summoning again with the same key returns the same controller instead of creating a new instance.'
}}
</p>
<div class="vs-dialog-actions">
<button class="vs-btn vs-btn-brand" @click="resolve('ok')">
{{ zh ? '知道了' : 'Got it' }}
</button>
</div>
</div>
</div>
</template>过渡动画
在宿主上为所有实例设置默认过渡动画,或透传完整的 TransitionProps 对象:
vue
<template>
<SummonHost transition="fade" />
<!-- 或 -->
<SummonHost :transition="{ name: 'zoom', mode: 'out-in' }" />
</template>