使用场景 ​
围绕页面目标组织多步骤引导。
目标步骤与无目标步骤 ​
目标来自当前 SFC 的元素 ref,避免依赖页面全局 ID。
目标步骤与无目标步骤
<template>
<AsamButton type="default" native-type="button" @click="open = true">开始导览</AsamButton>
<AsamButton type="default" ref="target" native-type="button">导览目标</AsamButton>
<AsamTour v-model="open" v-model:current="current" @finish="finished++">
<AsamTourStep :target="target?.ref" title="第一步" description="这里展示目标说明。" />
<AsamTourStep title="完成导览">
<template #header>下一步操作</template>
<p class="example-paragraph">没有目标的步骤使用居中展示。</p>
</AsamTourStep>
<template #indicators="{ current: index, total }">{{ index + 1 }} / {{ total }}</template>
</AsamTour>
<p class="example-paragraph" role="status">当前步骤 {{ current + 1 }};完成 {{ finished }} 次</p>
</template>
<script lang="ts" setup>
defineOptions({ name: 'AsamTourBasicDemo' });
import { ref } from 'vue';
import { AsamButton, type AsamButtonExpose, AsamTour, AsamTourStep } from '@asam-ui/ui';
const open = ref(false);
const current = ref(0);
const finished = ref(0);
const target = ref<AsamButtonExpose>();
</script>
<style scoped>
p:where(.example-paragraph) {
margin-block: 16px;
line-height: 1.8;
overflow-wrap: anywhere;
}
</style>
<template>
<AsamButton type="default" native-type="button" @click="open = true">开始导览</AsamButton>
<AsamButton type="default" ref="target" native-type="button">导览目标</AsamButton>
<AsamTour v-model="open" v-model:current="current" @finish="finished++">
<AsamTourStep :target="target?.ref" title="第一步" description="这里展示目标说明。" />
<AsamTourStep title="完成导览">
<template #header>下一步操作</template>
<p class="example-paragraph">没有目标的步骤使用居中展示。</p>
</AsamTourStep>
<template #indicators="{ current: index, total }">{{ index + 1 }} / {{ total }}</template>
</AsamTour>
<p class="example-paragraph" role="status">当前步骤 {{ current + 1 }};完成 {{ finished }} 次</p>
</template>
<script setup>
defineOptions({ name: 'AsamTourBasicDemo' });
import { ref } from 'vue';
import { AsamButton, AsamTour, AsamTourStep } from '@asam-ui/ui';
const open = ref(false);
const current = ref(0);
const finished = ref(0);
const target = ref();
</script>
<style scoped>
p:where(.example-paragraph) {
margin-block: 16px;
line-height: 1.8;
overflow-wrap: anywhere;
}
</style>
使用约定 ​
modelValue 与 current 分别控制显隐和当前步骤,change/finish/close 通知进度。 完整说明。
完整用法 ​
以下用法使用本库公开入口。每个示例提供完整 TS/JS SFC,参数、事件、插槽和实例类型见本页 API。服务实例由创建方负责关闭;HTML 内容仅用于调用方已知的可信字符串。
基本用法 ​
基本用法
<!-- 此演示还使用 @asam-ui/icons-vue,请按官网对应指南配置这些公开依赖。 -->
<template>
<AsamButton type="default" @click="open = true">开始导览</AsamButton>
<AsamDivider />
<AsamSpace>
<AsamButton type="default" ref="ref1">上传</AsamButton>
<AsamButton ref="ref2" type="default">保存</AsamButton>
<AsamButton type="default" ref="ref3" :icon="MoreFilled" />
</AsamSpace>
<AsamTour v-model="open">
<AsamTourStep :target="ref1?.$el" title="上传文件">
<AsamImage
style="width: 240px"
src="data:image/svg+xml,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20width%3D%22120%22%20height%3D%2248%22%20viewBox%3D%220%200%20120%2048%22%3E%3Crect%20width%3D%22120%22%20height%3D%2248%22%20fill%3D%22%23e8edf3%22%2F%3E%3Ctext%20x%3D%2212%22%20y%3D%2231%22%20fill%3D%22%23345%22%20font-size%3D%2224%22%3EASAM%3C%2Ftext%3E%3C%2Fsvg%3E"
alt="tour.png"
/>
<div>将文件放在这里。</div>
</AsamTourStep>
<AsamTourStep :target="ref2?.$el" title="保存" description="保存修改" />
<AsamTourStep :target="ref3?.$el" title="其他操作" description="点击查看其他内容" />
</AsamTour>
</template>
<script lang="ts" setup>
import { AsamImage, AsamButton, AsamDivider, AsamSpace, AsamTour, AsamTourStep } from '@asam-ui/ui';
type ButtonInstance = InstanceType<typeof AsamButton>;
import { ref } from 'vue';
import { MoreFilled } from '@asam-ui/icons-vue';
const ref1 = ref<ButtonInstance>();
const ref2 = ref<ButtonInstance>();
const ref3 = ref<ButtonInstance>();
const open = ref(false);
</script>
<!-- 此演示还使用 @asam-ui/icons-vue,请按官网对应指南配置这些公开依赖。 -->
<template>
<AsamButton type="default" @click="open = true">开始导览</AsamButton>
<AsamDivider />
<AsamSpace>
<AsamButton type="default" ref="ref1">上传</AsamButton>
<AsamButton ref="ref2" type="default">保存</AsamButton>
<AsamButton type="default" ref="ref3" :icon="MoreFilled" />
</AsamSpace>
<AsamTour v-model="open">
<AsamTourStep :target="ref1?.$el" title="上传文件">
<AsamImage
style="width: 240px"
src="data:image/svg+xml,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20width%3D%22120%22%20height%3D%2248%22%20viewBox%3D%220%200%20120%2048%22%3E%3Crect%20width%3D%22120%22%20height%3D%2248%22%20fill%3D%22%23e8edf3%22%2F%3E%3Ctext%20x%3D%2212%22%20y%3D%2231%22%20fill%3D%22%23345%22%20font-size%3D%2224%22%3EASAM%3C%2Ftext%3E%3C%2Fsvg%3E"
alt="tour.png"
/>
<div>将文件放在这里。</div>
</AsamTourStep>
<AsamTourStep :target="ref2?.$el" title="保存" description="保存修改" />
<AsamTourStep :target="ref3?.$el" title="其他操作" description="点击查看其他内容" />
</AsamTour>
</template>
<script setup>
import { AsamImage, AsamButton, AsamDivider, AsamSpace, AsamTour, AsamTourStep } from '@asam-ui/ui';
import { ref } from 'vue';
import { MoreFilled } from '@asam-ui/icons-vue';
const ref1 = ref();
const ref2 = ref();
const ref3 = ref();
const open = ref(false);
</script>
无遮罩引导 ​
无遮罩引导
<!-- 此演示还使用 @asam-ui/icons-vue,请按官网对应指南配置这些公开依赖。 -->
<template>
<AsamButton type="default" @click="open = true">开始导览</AsamButton>
<AsamDivider />
<AsamSpace>
<AsamButton type="default" ref="ref1">上传</AsamButton>
<AsamButton ref="ref2" type="default">保存</AsamButton>
<AsamButton type="default" ref="ref3" :icon="MoreFilled" />
</AsamSpace>
<AsamTour v-model="open" type="primary" :mask="false">
<AsamTourStep :target="ref1?.$el" title="上传文件" description="将文件放在这里。" />
<AsamTourStep :target="ref2?.$el" title="保存" description="保存修改" />
<AsamTourStep :target="ref3?.$el" title="其他操作" description="点击查看其他内容" />
</AsamTour>
</template>
<script lang="ts" setup>
import { AsamButton, AsamDivider, AsamSpace, AsamTour, AsamTourStep } from '@asam-ui/ui';
type ButtonInstance = InstanceType<typeof AsamButton>;
import { ref } from 'vue';
import { MoreFilled } from '@asam-ui/icons-vue';
const ref1 = ref<ButtonInstance>();
const ref2 = ref<ButtonInstance>();
const ref3 = ref<ButtonInstance>();
const open = ref(false);
</script>
<!-- 此演示还使用 @asam-ui/icons-vue,请按官网对应指南配置这些公开依赖。 -->
<template>
<AsamButton type="default" @click="open = true">开始导览</AsamButton>
<AsamDivider />
<AsamSpace>
<AsamButton type="default" ref="ref1">上传</AsamButton>
<AsamButton ref="ref2" type="default">保存</AsamButton>
<AsamButton type="default" ref="ref3" :icon="MoreFilled" />
</AsamSpace>
<AsamTour v-model="open" type="primary" :mask="false">
<AsamTourStep :target="ref1?.$el" title="上传文件" description="将文件放在这里。" />
<AsamTourStep :target="ref2?.$el" title="保存" description="保存修改" />
<AsamTourStep :target="ref3?.$el" title="其他操作" description="点击查看其他内容" />
</AsamTour>
</template>
<script setup>
import { AsamButton, AsamDivider, AsamSpace, AsamTour, AsamTourStep } from '@asam-ui/ui';
import { ref } from 'vue';
import { MoreFilled } from '@asam-ui/icons-vue';
const ref1 = ref();
const ref2 = ref();
const ref3 = ref();
const open = ref(false);
</script>
定位方向 ​
定位方向
<template>
<AsamButton ref="btnRef" type="default" @click="open = true"> 开始导览 </AsamButton>
<AsamTour v-model="open">
<AsamTourStep title="居中" description="显示在屏幕中央。" />
<AsamTourStep
title="右侧"
description="位于目标右侧。"
placement="right"
:target="btnRef?.$el"
/>
<AsamTourStep
title="顶部"
description="位于目标上方。"
placement="top"
:target="btnRef?.$el"
/>
</AsamTour>
</template>
<script lang="ts" setup>
import { AsamButton, AsamTour, AsamTourStep } from '@asam-ui/ui';
type ButtonInstance = InstanceType<typeof AsamButton>;
import { ref } from 'vue';
const btnRef = ref<ButtonInstance>();
const open = ref(false);
</script>
<template>
<AsamButton ref="btnRef" type="default" @click="open = true"> 开始导览 </AsamButton>
<AsamTour v-model="open">
<AsamTourStep title="居中" description="显示在屏幕中央。" />
<AsamTourStep
title="右侧"
description="位于目标右侧。"
placement="right"
:target="btnRef?.$el"
/>
<AsamTourStep
title="顶部"
description="位于目标上方。"
placement="top"
:target="btnRef?.$el"
/>
</AsamTour>
</template>
<script setup>
import { AsamButton, AsamTour, AsamTourStep } from '@asam-ui/ui';
import { ref } from 'vue';
const btnRef = ref();
const open = ref(false);
</script>
遮罩与点击区域 ​
遮罩与点击区域
<!-- 此演示还使用 @asam-ui/icons-vue,请按官网对应指南配置这些公开依赖。 -->
<template>
<AsamButton type="default" @click="open = true">开始导览</AsamButton>
<AsamDivider />
<AsamSpace>
<AsamButton type="default" ref="ref1">上传</AsamButton>
<AsamButton ref="ref2" type="default">保存</AsamButton>
<AsamButton type="default" ref="ref3" :icon="MoreFilled" />
</AsamSpace>
<AsamTour
v-model="open"
:mask="{
style: {
boxShadow: 'inset 0 0 15px #333'
},
color: 'rgba(80, 255, 255, .4)'
}"
>
<AsamTourStep :target="ref1?.$el" title="上传文件">
<AsamImage
src="data:image/svg+xml,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20width%3D%22120%22%20height%3D%2248%22%20viewBox%3D%220%200%20120%2048%22%3E%3Crect%20width%3D%22120%22%20height%3D%2248%22%20fill%3D%22%23e8edf3%22%2F%3E%3Ctext%20x%3D%2212%22%20y%3D%2231%22%20fill%3D%22%23345%22%20font-size%3D%2224%22%3EASAM%3C%2Ftext%3E%3C%2Fsvg%3E"
alt="tour.png"
/>
<div>将文件放在这里。</div>
</AsamTourStep>
<AsamTourStep
:target="ref2?.$el"
title="保存"
description="保存修改"
:mask="{
style: {
boxShadow: 'inset 0 0 15px #fff'
},
color: 'rgba(40, 0, 255, .4)'
}"
/>
<AsamTourStep
:target="ref3?.$el"
title="其他操作"
description="点击查看其他内容"
:mask="false"
/>
</AsamTour>
</template>
<script lang="ts" setup>
import { AsamImage, AsamButton, AsamDivider, AsamSpace, AsamTour, AsamTourStep } from '@asam-ui/ui';
type ButtonInstance = InstanceType<typeof AsamButton>;
import { ref } from 'vue';
import { MoreFilled } from '@asam-ui/icons-vue';
const ref1 = ref<ButtonInstance>();
const ref2 = ref<ButtonInstance>();
const ref3 = ref<ButtonInstance>();
const open = ref(false);
</script>
<!-- 此演示还使用 @asam-ui/icons-vue,请按官网对应指南配置这些公开依赖。 -->
<template>
<AsamButton type="default" @click="open = true">开始导览</AsamButton>
<AsamDivider />
<AsamSpace>
<AsamButton type="default" ref="ref1">上传</AsamButton>
<AsamButton ref="ref2" type="default">保存</AsamButton>
<AsamButton type="default" ref="ref3" :icon="MoreFilled" />
</AsamSpace>
<AsamTour
v-model="open"
:mask="{
style: {
boxShadow: 'inset 0 0 15px #333'
},
color: 'rgba(80, 255, 255, .4)'
}"
>
<AsamTourStep :target="ref1?.$el" title="上传文件">
<AsamImage
src="data:image/svg+xml,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20width%3D%22120%22%20height%3D%2248%22%20viewBox%3D%220%200%20120%2048%22%3E%3Crect%20width%3D%22120%22%20height%3D%2248%22%20fill%3D%22%23e8edf3%22%2F%3E%3Ctext%20x%3D%2212%22%20y%3D%2231%22%20fill%3D%22%23345%22%20font-size%3D%2224%22%3EASAM%3C%2Ftext%3E%3C%2Fsvg%3E"
alt="tour.png"
/>
<div>将文件放在这里。</div>
</AsamTourStep>
<AsamTourStep
:target="ref2?.$el"
title="保存"
description="保存修改"
:mask="{
style: {
boxShadow: 'inset 0 0 15px #fff'
},
color: 'rgba(40, 0, 255, .4)'
}"
/>
<AsamTourStep
:target="ref3?.$el"
title="其他操作"
description="点击查看其他内容"
:mask="false"
/>
</AsamTour>
</template>
<script setup>
import { AsamImage, AsamButton, AsamDivider, AsamSpace, AsamTour, AsamTourStep } from '@asam-ui/ui';
import { ref } from 'vue';
import { MoreFilled } from '@asam-ui/icons-vue';
const ref1 = ref();
const ref2 = ref();
const ref3 = ref();
const open = ref(false);
</script>
自定义进度指示 ​
自定义进度指示
<!-- 此演示还使用 @asam-ui/icons-vue,请按官网对应指南配置这些公开依赖。 -->
<template>
<AsamButton type="default" @click="open = true">开始导览</AsamButton>
<AsamDivider />
<AsamSpace>
<AsamButton type="default" ref="ref1">上传</AsamButton>
<AsamButton ref="ref2" type="default">保存</AsamButton>
<AsamButton type="default" ref="ref3" :icon="MoreFilled" />
</AsamSpace>
<AsamTour v-model="open">
<AsamTourStep :target="ref1?.$el" title="上传文件" description="将文件放在这里。" />
<AsamTourStep :target="ref2?.$el" title="保存" description="保存修改" />
<AsamTourStep :target="ref3?.$el" title="其他操作" description="点击查看其他内容" />
<template #indicators="{ current, total }">
<span>{{ current + 1 }} / {{ total }}</span>
</template>
</AsamTour>
</template>
<script lang="ts" setup>
import { AsamButton, AsamDivider, AsamSpace, AsamTour, AsamTourStep } from '@asam-ui/ui';
type ButtonInstance = InstanceType<typeof AsamButton>;
import { ref } from 'vue';
import { MoreFilled } from '@asam-ui/icons-vue';
const ref1 = ref<ButtonInstance>();
const ref2 = ref<ButtonInstance>();
const ref3 = ref<ButtonInstance>();
const open = ref(false);
</script>
<!-- 此演示还使用 @asam-ui/icons-vue,请按官网对应指南配置这些公开依赖。 -->
<template>
<AsamButton type="default" @click="open = true">开始导览</AsamButton>
<AsamDivider />
<AsamSpace>
<AsamButton type="default" ref="ref1">上传</AsamButton>
<AsamButton ref="ref2" type="default">保存</AsamButton>
<AsamButton type="default" ref="ref3" :icon="MoreFilled" />
</AsamSpace>
<AsamTour v-model="open">
<AsamTourStep :target="ref1?.$el" title="上传文件" description="将文件放在这里。" />
<AsamTourStep :target="ref2?.$el" title="保存" description="保存修改" />
<AsamTourStep :target="ref3?.$el" title="其他操作" description="点击查看其他内容" />
<template #indicators="{ current, total }">
<span>{{ current + 1 }} / {{ total }}</span>
</template>
</AsamTour>
</template>
<script setup>
import { AsamButton, AsamDivider, AsamSpace, AsamTour, AsamTourStep } from '@asam-ui/ui';
import { ref } from 'vue';
import { MoreFilled } from '@asam-ui/icons-vue';
const ref1 = ref();
const ref2 = ref();
const ref3 = ref();
const open = ref(false);
</script>
动态目标 ​
动态目标
<!-- 此演示还使用 @asam-ui/icons-vue,请按官网对应指南配置这些公开依赖。 -->
<template>
<AsamButton type="default" @click="open = true">开始导览</AsamButton>
<AsamDivider />
<AsamSpace>
<span id="btn1"><AsamButton type="default">上传</AsamButton></span>
<span id="btn2"><AsamButton type="default">保存</AsamButton></span>
<AsamButton type="default" ref="btnRef" :icon="MoreFilled" />
</AsamSpace>
<AsamTour v-model="open">
<AsamTourStep target="#btn1" title="上传文件" description="将文件放在这里。" />
<AsamTourStep :target="el" title="保存" description="保存修改" />
<AsamTourStep :target="btnRef?.$el" title="其他操作" description="点击查看其他内容" />
</AsamTour>
</template>
<script lang="ts" setup>
import { AsamButton, AsamDivider, AsamSpace, AsamTour, AsamTourStep } from '@asam-ui/ui';
type ButtonInstance = InstanceType<typeof AsamButton>;
import { ref } from 'vue';
import { MoreFilled } from '@asam-ui/icons-vue';
const el = () => document.querySelector<HTMLElement>('#btn2');
const btnRef = ref<ButtonInstance>();
const open = ref(false);
</script>
<!-- 此演示还使用 @asam-ui/icons-vue,请按官网对应指南配置这些公开依赖。 -->
<template>
<AsamButton type="default" @click="open = true">开始导览</AsamButton>
<AsamDivider />
<AsamSpace>
<span id="btn1"><AsamButton type="default">上传</AsamButton></span>
<span id="btn2"><AsamButton type="default">保存</AsamButton></span>
<AsamButton type="default" ref="btnRef" :icon="MoreFilled" />
</AsamSpace>
<AsamTour v-model="open">
<AsamTourStep target="#btn1" title="上传文件" description="将文件放在这里。" />
<AsamTourStep :target="el" title="保存" description="保存修改" />
<AsamTourStep :target="btnRef?.$el" title="其他操作" description="点击查看其他内容" />
</AsamTour>
</template>
<script setup>
import { AsamButton, AsamDivider, AsamSpace, AsamTour, AsamTourStep } from '@asam-ui/ui';
import { ref } from 'vue';
import { MoreFilled } from '@asam-ui/icons-vue';
const el = () => document.querySelector('#btn2');
const btnRef = ref();
const open = ref(false);
</script>
组合职责与生命周期 ​
default 中可直接放置 AsamTourStep,或使用原生可识别的 Fragment 组织步骤。 完整说明。
current 为零起始索引,change 通知切换,finish 只在最后一步完成时发出,close 包含当前索引。 完整说明。
步骤组合与原生限制 ​
下面的 Fragment 和无 props 纯函数示例各有两步。有状态包装示例中,包装后的步骤不计入 total,只剩直接声明的一步;第一次完成便结束导览。这是原生组合限制。需要有状态内容时,将 AsamTourStep 直接放在导览插槽中,把内容组件放进步骤内部。
步骤组合与原生限制
<template>
<div>
<AsamButton type="default" native-type="button" @click="begin('fragment')"
>查看 Fragment 两步</AsamButton
>
<AsamButton type="default" native-type="button" @click="begin('functional')"
>查看纯函数两步</AsamButton
>
<AsamButton type="default" native-type="button" @click="begin('stateful')"
>查看有状态包装限制</AsamButton
>
</div>
<p class="example-paragraph">Fragment 和无 props 纯函数各有两步;有状态包装模式只枚举直接声明的一步。</p>
<AsamTour :key="mode" v-model="open" @finish="finished++">
<template v-if="mode === 'fragment'">
<AsamTourStep
title="Fragment 第一步"
description="直接声明的步骤可以放在 Fragment 中。"
/>
<AsamTourStep title="Fragment 第二步" description="两步都会计入 total。" />
</template>
<FunctionalSteps v-else-if="mode === 'functional'" />
<template v-else>
<StatefulStep />
<AsamTourStep
title="仅枚举直接步骤"
description="包装后的步骤没有计入 total;当前只有一步。"
/>
</template>
<template #indicators="{ current: index, total }">
<span data-tour-composition-indicator>{{ index + 1 }} / {{ total }}</span>
</template>
</AsamTour>
<p class="example-paragraph" role="status">完成 {{ finished }} 次</p>
</template>
<script lang="ts" setup>
import { defineComponent, h, ref } from 'vue';
import { AsamButton, AsamTour, AsamTourStep } from '@asam-ui/ui';
defineOptions({ name: 'AsamTourStepCompositionDemo' });
type Mode = 'fragment' | 'functional' | 'stateful';
const mode = ref<Mode>('fragment');
const open = ref(false);
const finished = ref(0);
// 纯函数没有 props、slots 或组件实例依赖,只返回公开步骤。
const FunctionalSteps = () => [
h(AsamTourStep, { title: '纯函数第一步', description: '无 props 的纯函数返回步骤。' }),
h(AsamTourStep, { title: '纯函数第二步', description: '两步都会计入 total。' })
];
// 反例:有状态包装返回步骤,但不会被原生枚举为一个步骤。
const StatefulStep = defineComponent({
name: 'TourStatefulStep',
setup: () => () => h(AsamTourStep, { title: '不会被枚举的包装步骤' })
});
function begin(nextMode: Mode) {
mode.value = nextMode;
finished.value = 0;
open.value = true;
}
</script>
<style scoped>
p:where(.example-paragraph) {
margin-block: 16px;
line-height: 1.8;
overflow-wrap: anywhere;
}
</style>
<template>
<div>
<AsamButton type="default" native-type="button" @click="begin('fragment')"
>查看 Fragment 两步</AsamButton
>
<AsamButton type="default" native-type="button" @click="begin('functional')"
>查看纯函数两步</AsamButton
>
<AsamButton type="default" native-type="button" @click="begin('stateful')"
>查看有状态包装限制</AsamButton
>
</div>
<p class="example-paragraph">Fragment 和无 props 纯函数各有两步;有状态包装模式只枚举直接声明的一步。</p>
<AsamTour :key="mode" v-model="open" @finish="finished++">
<template v-if="mode === 'fragment'">
<AsamTourStep
title="Fragment 第一步"
description="直接声明的步骤可以放在 Fragment 中。"
/>
<AsamTourStep title="Fragment 第二步" description="两步都会计入 total。" />
</template>
<FunctionalSteps v-else-if="mode === 'functional'" />
<template v-else>
<StatefulStep />
<AsamTourStep
title="仅枚举直接步骤"
description="包装后的步骤没有计入 total;当前只有一步。"
/>
</template>
<template #indicators="{ current: index, total }">
<span data-tour-composition-indicator>{{ index + 1 }} / {{ total }}</span>
</template>
</AsamTour>
<p class="example-paragraph" role="status">完成 {{ finished }} 次</p>
</template>
<script setup>
import { defineComponent, h, ref } from 'vue';
import { AsamButton, AsamTour, AsamTourStep } from '@asam-ui/ui';
defineOptions({ name: 'AsamTourStepCompositionDemo' });
const mode = ref('fragment');
const open = ref(false);
const finished = ref(0);
// 纯函数没有 props、slots 或组件实例依赖,只返回公开步骤。
const FunctionalSteps = () => [
h(AsamTourStep, { title: '纯函数第一步', description: '无 props 的纯函数返回步骤。' }),
h(AsamTourStep, { title: '纯函数第二步', description: '两步都会计入 total。' })
];
// 反例:有状态包装返回步骤,但不会被原生枚举为一个步骤。
const StatefulStep = defineComponent({
name: 'TourStatefulStep',
setup: () => () => h(AsamTourStep, { title: '不会被枚举的包装步骤' })
});
function begin(nextMode) {
mode.value = nextMode;
finished.value = 0;
open.value = true;
}
</script>
<style scoped>
p:where(.example-paragraph) {
margin-block: 16px;
line-height: 1.8;
overflow-wrap: anywhere;
}
</style>
原生步骤与载荷 ​
默认插槽原样交给原生 Tour;使用同对象别名 AsamTourStep。modelValue/current 的更新、change、close(current) 和 finish 仅转发原生事件;indicators 保留 current/total scope。组件不克隆或递归替换步骤类型。
局部与全局挂载 ​
切换的是公开 appendTo 挂载位置;导览的目标定位和遮罩仍遵循组件原有视口语义。 开关显示当前模式,两个模式均可操作;关闭后可以再次切换。
局部与全局挂载
<template>
<div ref="mountTarget" class="popup-stage" :class="{ local: !globalPopup }">
<div class="popup-mode">
<ContainerSwitch
v-model="globalPopup"
active-text="全局挂载到 body"
inactive-text="局部挂载到当前区域"
/>
</div>
<AsamButton type="default" native-type="button" @click="open = true">开始导览</AsamButton>
<AsamButton type="default" ref="target" native-type="button">导览目标</AsamButton>
<AsamTour
v-model="open"
v-model:current="current"
@finish="finished++"
:append-to="globalPopup ? 'body' : mountTarget || 'body'"
>
<AsamTourStep :target="target?.ref" title="第一步" description="这里展示目标说明。" />
<AsamTourStep title="完成导览">
<template #header>下一步操作</template>
<p class="example-paragraph">没有目标的步骤使用居中展示。</p>
</AsamTourStep>
<template #indicators="{ current: index, total }"
>{{ index + 1 }} / {{ total }}</template
>
</AsamTour>
<p class="example-paragraph" role="status">当前步骤 {{ current + 1 }};完成 {{ finished }} 次</p>
</div>
</template>
<script lang="ts" setup>
defineOptions({ name: 'AsamTourBasicDemo' });
import { ref } from 'vue';
import { AsamButton, type AsamButtonExpose, AsamTour, AsamTourStep } from '@asam-ui/ui';
const open = ref(false);
const current = ref(0);
const finished = ref(0);
const target = ref<AsamButtonExpose>();
import { ref as containerRef } from 'vue';
import { AsamSwitch as ContainerSwitch } from '@asam-ui/ui';
const globalPopup = containerRef(true);
const mountTarget = containerRef<HTMLElement>();
</script>
<style scoped>
.popup-stage {
position: relative;
isolation: isolate;
min-height: 340px;
padding: var(--asam-space-md);
border: 1px dashed var(--asam-color-border);
}
.popup-mode {
margin-bottom: var(--asam-space-lg);
}
</style>
<style scoped>
p:where(.example-paragraph) {
margin-block: 16px;
line-height: 1.8;
overflow-wrap: anywhere;
}
</style>
<template>
<div ref="mountTarget" class="popup-stage" :class="{ local: !globalPopup }">
<div class="popup-mode">
<ContainerSwitch
v-model="globalPopup"
active-text="全局挂载到 body"
inactive-text="局部挂载到当前区域"
/>
</div>
<AsamButton type="default" native-type="button" @click="open = true">开始导览</AsamButton>
<AsamButton type="default" ref="target" native-type="button">导览目标</AsamButton>
<AsamTour
v-model="open"
v-model:current="current"
@finish="finished++"
:append-to="globalPopup ? 'body' : mountTarget || 'body'"
>
<AsamTourStep :target="target?.ref" title="第一步" description="这里展示目标说明。" />
<AsamTourStep title="完成导览">
<template #header>下一步操作</template>
<p class="example-paragraph">没有目标的步骤使用居中展示。</p>
</AsamTourStep>
<template #indicators="{ current: index, total }"
>{{ index + 1 }} / {{ total }}</template
>
</AsamTour>
<p class="example-paragraph" role="status">当前步骤 {{ current + 1 }};完成 {{ finished }} 次</p>
</div>
</template>
<script setup>
defineOptions({ name: 'AsamTourBasicDemo' });
import { ref } from 'vue';
import { AsamButton, AsamTour, AsamTourStep } from '@asam-ui/ui';
const open = ref(false);
const current = ref(0);
const finished = ref(0);
const target = ref();
import { ref as containerRef } from 'vue';
import { AsamSwitch as ContainerSwitch } from '@asam-ui/ui';
const globalPopup = containerRef(true);
const mountTarget = containerRef();
</script>
<style scoped>
.popup-stage {
position: relative;
isolation: isolate;
min-height: 340px;
padding: var(--asam-space-md);
border: 1px dashed var(--asam-color-border);
}
.popup-mode {
margin-bottom: var(--asam-space-lg);
}
</style>
<style scoped>
p:where(.example-paragraph) {
margin-block: 16px;
line-height: 1.8;
overflow-wrap: anywhere;
}
</style>
API
属性 AsamTourProps
| 属性名 | 说明 | 类型 | 默认值 |
|---|---|---|---|
modelValue | — | boolean | undefined | false |
current | — | number | undefined | — |
showArrow | — | boolean | undefined | true |
showClose | — | boolean | undefined | true |
closeIcon | — | (AsamTourProps)["closeIcon"] | — |
placement | — | enum"top" | "bottom" | "right" | "left" | "top-start" | "top-end" | "bottom-start" | "bottom-end" | "right-start" | "right-end" | "left-start" | "left-end"完整类型:AsamTourPlacement | undefined | "bottom" |
contentStyle | — | import("vue").CSSProperties | undefined | — |
mask | — | (AsamTourProps)["mask"] | true |
gap | — | (AsamTourProps)["gap"] | 工厂函数(每个实例创建) |
zIndex | — | number | undefined | — |
scrollIntoViewOptions | — | boolean | ScrollIntoViewOptions | undefined | 工厂函数(每个实例创建) |
type | — | enum"default" | "primary"完整类型:"default" | "primary" | undefined | — |
appendTo | — | string | HTMLElement | undefined | "body" |
closeOnPressEscape | — | boolean | undefined | true |
targetAreaClickable | — | boolean | undefined | true |
事件 AsamTourEmits
| 属性名 | 说明 | 类型 | 默认值 |
|---|---|---|---|
update:modelValue | — | [value: boolean] | — |
update:current | — | [current: number] | — |
close | — | [current: number] | — |
finish | — | (AsamTourEmits)["finish"] | — |
change | — | [current: number] | — |
插槽 AsamTourSlots
| 属性名 | 说明 | 类型 | 默认值 |
|---|---|---|---|
default | — | (() => unknown) | undefined | — |
indicators | — | ((props: { current: number; total: number; }) => unknown) | undefined | — |
类型 AsamTourPlacement
用法与契约补充说明 ​
使用约定 ​
modelValue 与 current 分别控制显隐和当前步骤,change/finish/close 通知进度。mask、gap、placement、showArrow、scrollIntoViewOptions 和 targetAreaClickable 配置目标遮罩及定位。每个步骤使用 AsamTourStep;步骤枚举沿用原生能力,不能把任意包装组件当作步骤。indicators 插槽可自定义进度;无四档密度 size。
组合职责与生命周期 ​
default 中可直接放置 AsamTourStep,或使用原生可识别的 Fragment 组织步骤。无 props、无 slots 的纯函数组件可返回这些步骤;函数不应依赖组件实例或 setup 状态。用 defineComponent 或 SFC 创建的有状态包装组件即使渲染 AsamTourStep,也不会因此参与步骤枚举。不要依靠包装组件的 render 结果增加步骤,组件不为此递归渲染或改写 VNode。
组合职责与生命周期 ​
current 为零起始索引,change 通知切换,finish 只在最后一步完成时发出,close 包含当前索引。target 可为选择器、HTMLElement 或返回元素的函数,动态目标需保证当前 DOM 已挂载。scrollIntoViewOptions 接受布尔值或原生滚动选项对象,步骤可覆盖父配置。mask/gap/targetAreaClickable 控制遮罩与可点击区域;无目标步骤居中。indicators 接收 current/total;导航按钮配置由各步骤承担,关闭和卸载释放焦点、遮罩及监听。
关联组件
参数与事件补充说明
参数与事件说明
modelValue
必填:否;声明来源:ASAM 声明。
modelValue 参数与事件数据
下列为声明可读视图;可导入类型以公开类型视图为准。分支条件与父属性一致。
枚举:false / true
默认值与来源
false(隐式 Boolean 默认;已安装组件)current
必填:否;声明来源:ASAM 声明。
默认值与来源
未知 / 未声明固定上游源码未声明 default(2.14.7;源码事实,未作为 ASAM 运行实测)
showArrow
必填:否;声明来源:ASAM 声明。
showArrow 参数与事件数据
下列为声明可读视图;可导入类型以公开类型视图为准。分支条件与父属性一致。
枚举:false / true
默认值与来源
true(已安装运行时;已安装组件)固定上游源码默认表达式:true(2.14.7;源码事实,未作为 ASAM 运行实测)
showClose
必填:否;声明来源:ASAM 声明。
showClose 参数与事件数据
下列为声明可读视图;可导入类型以公开类型视图为准。分支条件与父属性一致。
枚举:false / true
默认值与来源
true(已安装运行时;已安装组件)固定上游源码默认表达式:true(2.14.7;源码事实,未作为 ASAM 运行实测)
closeIcon
必填:否;声明来源:ASAM 声明。
closeIcon 参数与事件数据
下列为声明可读视图;可导入类型以公开类型视图为准。分支条件与父属性一致。
事件参数:(props: any, ctx: Parameters<Extract<NonNullable<(AsamTourProps)["closeIcon"]>, (...args: never[]) => unknown>>[1]): any
closeIcon.__isFragment:undefined— 依赖声明closeIcon.__isTeleport:undefined— 依赖声明closeIcon.__isSuspense:undefined— 依赖声明
默认值与来源
未知 / 未声明固定上游源码未声明 default(2.14.7;源码事实,未作为 ASAM 运行实测)
placement
必填:否;声明来源:ASAM 声明。
placement 参数与事件数据
下列为声明可读视图;可导入类型以公开类型视图为准。分支条件与父属性一致。
枚举:"top" / "bottom" / "right" / "left" / "top-start" / "top-end" / "bottom-start" / "bottom-end" / "right-start" / "right-end" / "left-start" / "left-end"
默认值与来源
"bottom"(已安装运行时;已安装组件)contentStyle
必填:否;声明来源:ASAM 声明。
contentStyle 参数与事件数据
下列为声明可读视图;可导入类型以公开类型视图为准。分支条件与父属性一致。
嵌套展开边界:大型实例类型;完整类型见公开类型视图。
默认值与来源
未知 / 未声明固定上游源码未声明 default(2.14.7;源码事实,未作为 ASAM 运行实测)
mask
必填:否;声明来源:ASAM 声明。
mask 参数与事件数据
下列为声明可读视图;可导入类型以公开类型视图为准。分支条件与父属性一致。
枚举:false / true
mask.style:CSSProperties | undefined— 上游声明嵌套展开边界:大型实例类型;完整类型见公开类型视图。
mask.color:string | undefined— 上游声明
默认值与来源
true(已安装运行时;已安装组件)固定上游源码默认表达式:true(2.14.7;源码事实,未作为 ASAM 运行实测)
gap
必填:否;声明来源:ASAM 声明。
gap 参数与事件数据
下列为声明可读视图;可导入类型以公开类型视图为准。分支条件与父属性一致。
gap.offset:number | [number, number] | undefined— 上游声明gap.radius:number | undefined— 上游声明
默认值与来源
工厂函数(每个实例创建)(实例工厂;已安装组件)固定上游源码工厂(未执行):() => ({
offset: 6,
radius: 2,
})(2.14.7;源码事实,未作为 ASAM 运行实测)
zIndex
必填:否;声明来源:ASAM 声明。
默认值与来源
未知 / 未声明固定上游源码未声明 default(2.14.7;源码事实,未作为 ASAM 运行实测)
scrollIntoViewOptions
必填:否;声明来源:ASAM 声明。
scrollIntoViewOptions 参数与事件数据
下列为声明可读视图;可导入类型以公开类型视图为准。分支条件与父属性一致。
枚举:false / true
scrollIntoViewOptions.block:ScrollLogicalPosition | undefined— 依赖声明枚举:
"start"/"center"/"end"/"nearest"scrollIntoViewOptions.inline:ScrollLogicalPosition | undefined— 依赖声明枚举:
"start"/"center"/"end"/"nearest"scrollIntoViewOptions.behavior:ScrollBehavior | undefined— 依赖声明枚举:
"auto"/"smooth"/"instant"
默认值与来源
工厂函数(每个实例创建)(实例工厂;已安装组件)固定上游源码工厂(未执行):() => ({
block: 'center',
})(2.14.7;源码事实,未作为 ASAM 运行实测)
type
必填:否;声明来源:ASAM 声明。
type 参数与事件数据
下列为声明可读视图;可导入类型以公开类型视图为准。分支条件与父属性一致。
枚举:"default" / "primary"
默认值与来源
未知 / 未声明固定上游源码未声明 default(2.14.7;源码事实,未作为 ASAM 运行实测)
appendTo
必填:否;声明来源:ASAM 声明。
appendTo 参数与事件数据
下列为声明可读视图;可导入类型以公开类型视图为准。分支条件与父属性一致。
嵌套展开边界:大型实例类型;完整类型见公开类型视图。
默认值与来源
"body"(已安装运行时;已安装组件)固定上游源码默认表达式:'body'(2.14.7;源码事实,未作为 ASAM 运行实测)
closeOnPressEscape
必填:否;声明来源:ASAM 声明。
closeOnPressEscape 参数与事件数据
下列为声明可读视图;可导入类型以公开类型视图为准。分支条件与父属性一致。
枚举:false / true
默认值与来源
true(已安装运行时;已安装组件)固定上游源码默认表达式:true(2.14.7;源码事实,未作为 ASAM 运行实测)
targetAreaClickable
必填:否;声明来源:ASAM 声明。
targetAreaClickable 参数与事件数据
下列为声明可读视图;可导入类型以公开类型视图为准。分支条件与父属性一致。
枚举:false / true
默认值与来源
true(已安装运行时;已安装组件)固定上游源码默认表达式:true(2.14.7;源码事实,未作为 ASAM 运行实测)
AsamTourProps 公开类型视图
import type { Attrs, CSSProperties } from 'vue';
import type { AsamTourProps, AsamTourPlacement, AsamTourStepProps } from '@asam-ui/ui';
type PublicMember<T, K extends PropertyKey> = T extends unknown ? K extends keyof T ? T[K] : never : never;
// AsamTourProps
type PublicContract = {
"modelValue"?: boolean | undefined;
"current"?: number | undefined;
"showArrow"?: boolean | undefined;
"showClose"?: boolean | undefined;
"closeIcon"?: (AsamTourProps)["closeIcon"];
"placement"?: AsamTourPlacement | undefined;
"contentStyle"?: import("vue").CSSProperties | undefined;
"mask"?: (AsamTourProps)["mask"];
"gap"?: (AsamTourProps)["gap"];
"zIndex"?: number | undefined;
"scrollIntoViewOptions"?: boolean | ScrollIntoViewOptions | undefined;
"type"?: "default" | "primary" | undefined;
"appendTo"?: string | HTMLElement | undefined;
"closeOnPressEscape"?: boolean | undefined;
"targetAreaClickable"?: boolean | undefined;
};
参数与事件说明
update:modelValue
必填:是;声明来源:ASAM 声明。
update:current
必填:是;声明来源:ASAM 声明。
close
必填:是;声明来源:ASAM 声明。
finish
必填:是;声明来源:ASAM 声明。
change
必填:是;声明来源:ASAM 声明。
AsamTourEmits 公开类型视图
import type { AsamTourEmits } from '@asam-ui/ui';
// AsamTourEmits
type PublicContract = {
"update:modelValue": [value: boolean];
"update:current": [current: number];
"close": [current: number];
"finish": (AsamTourEmits)["finish"];
"change": [current: number];
};
参数与事件说明
default
必填:否;声明来源:ASAM 声明。
default 参数与事件数据
下列为声明可读视图;可导入类型以公开类型视图为准。分支条件与父属性一致。
事件参数:(): unknown
indicators
必填:否;声明来源:ASAM 声明。
indicators 参数与事件数据
下列为声明可读视图;可导入类型以公开类型视图为准。分支条件与父属性一致。
事件参数:(props: { current: number; total: number; }): unknown
AsamTourSlots 公开类型视图
import type { AsamTourSlots } from '@asam-ui/ui';
// AsamTourSlots
type PublicContract = {
"default"?: (() => unknown) | undefined;
"indicators"?: ((props: { current: number; total: number; }) => unknown) | undefined;
};
精确保留 floating-ui 的 Placement 取值范围,使用本地类型名便于 SFC 声明移植。
组合契约按分支生效;请同时查看公开类型。
AsamTourPlacement 公开类型视图
import type { AsamTourPlacement } from '@asam-ui/ui';
// AsamTourPlacement
type PublicContract = AsamTourPlacement;