代码演示
如何引入
<script setup lang="ts">
import { UserGuide } from '@fibrous/semi-ui-vue';
</script>
基本用法
代码
<script setup lang="ts">
import { ref } from 'vue';
import { Button, Space, Switch, Tag, UserGuide, type UserGuideStepItem } from '@fibrous/semi-ui-vue';
const visible = ref(false);
const steps: UserGuideStepItem[] = [
{
target: () => document.querySelector('#basic-demo-1'),
title: '新手引导',
description: 'Hello ByteDancer!',
position: 'bottom',
},
{
target: () => document.querySelector('#basic-demo-2'),
title: 'Switch',
description: 'This is a Semi Switch',
position: 'bottom',
},
{
target: () => document.querySelector('#basic-demo-3'),
title: 'Button',
description: 'This is a Semi Button',
position: 'bottom',
},
];
</script>
<template>
<div>
<Button @click="visible = true">开始引导</Button>
<br />
<br />
<Space>
<Switch id="basic-demo-1" aria-label="示例开关" defaultChecked />
<Tag id="basic-demo-2"> Default Tag </Tag>
<Button id="basic-demo-3">确定</Button>
</Space>
<UserGuide v-model:visible="visible" mask mode="popup" :steps="steps" @change="(current) => console.log('当前引导步骤', current)" @finish="console.log('引导完成')" @next="console.log('下一步引导')" @prev="console.log('上一步引导')" @skip="console.log('跳过引导')" />
</div>
</template>主题
popup 气泡卡片模式下提供两种主题 default 和 primary,通过 theme 属性设置。
代码
<script setup lang="ts">
import { ref } from 'vue';
import { Button, Space, Switch, Tag, UserGuide, type UserGuideStepItem } from '@fibrous/semi-ui-vue';
const visible = ref(false);
const steps: UserGuideStepItem[] = [
{
target: () => document.querySelector('#theme-demo-1'),
title: '新手引导',
description: 'Hello ByteDancer!',
position: 'bottom',
},
{
target: () => document.querySelector('#theme-demo-2'),
title: 'Switch',
description: 'This is a Semi Switch',
position: 'bottom',
},
{
target: () => document.querySelector('#theme-demo-3'),
title: 'Button',
description: 'This is a Semi Button',
position: 'bottom',
},
];
</script>
<template>
<div>
<Button @click="visible = true">开始引导</Button>
<br />
<br />
<Space>
<Switch id="theme-demo-1" aria-label="示例开关" defaultChecked />
<Tag id="theme-demo-2"> Default Tag </Tag>
<Button id="theme-demo-3">确定</Button>
</Space>
<UserGuide v-model:visible="visible" mask mode="popup" :steps="steps" theme="primary" @finish="console.log('引导完成')" @skip="console.log('跳过引导')" />
</div>
</template>气泡卡片弹出位置
popup 气泡卡片模式下提供 16 种弹出位置,可选值有 top, topLeft, topRight, left, leftTop, leftBottom, right, rightTop, rightBottom, bottom, bottomLeft, bottomRight, leftTopOver, rightTopOver, leftBottomOver, rightBottomOver,还可以通过 showArrow 属性设置是否显示箭头。
代码
<script setup lang="ts">
import { ref } from 'vue';
import { Button, UserGuide, type UserGuideStepItem } from '@fibrous/semi-ui-vue';
const visible = ref(false);
const target = () => document.querySelector('#position-demo');
const steps: UserGuideStepItem[] = [
{ target, title: '新手引导', description: 'Hello ByteDancer!', position: 'top' },
{ target, title: 'New Position', description: 'This is Right Position', position: 'right' },
{ target, title: 'Hide Arrow', description: 'We hide the arrow', position: 'bottom', showArrow: false },
];
</script>
<template>
<div>
<Button id="position-demo" @click="visible = true">开始引导</Button>
<UserGuide v-model:visible="visible" mask mode="popup" :steps="steps" @finish="console.log('引导完成')" @skip="console.log('跳过引导')" />
</div>
</template>设置高亮区域大小
通过 spotlightPadding 属性设置。
代码
<script setup lang="ts">
import { ref } from 'vue';
import { Button, Space, Switch, Tag, UserGuide, type UserGuideStepItem } from '@fibrous/semi-ui-vue';
const visible = ref(false);
const steps: UserGuideStepItem[] = [
{
target: () => document.querySelector('#padding-demo-1'),
title: '新手引导',
description: 'Hello ByteDancer!',
},
{
target: () => document.querySelector('#padding-demo-2'),
title: 'New Padding',
description: 'This is 10px padding',
},
{
target: () => document.querySelector('#padding-demo-3'),
title: 'Change Padding',
spotlightPadding: 15,
description: 'We change the Padding to 15px',
},
];
</script>
<template>
<div>
<Button @click="visible = true">开始引导</Button>
<br />
<br />
<Space>
<Switch id="padding-demo-1" aria-label="示例开关" defaultChecked />
<Tag id="padding-demo-2"> Default Tag </Tag>
<Button id="padding-demo-3">确定</Button>
</Space>
<UserGuide v-model:visible="visible" mask mode="popup" :spotlightPadding="10" :steps="steps" @finish="console.log('引导完成')" @skip="console.log('跳过引导')" />
</div>
</template>定制按钮
通过 nextButtonProps 和 prevButtonProps 属性设置按钮的样式。
按钮文本使用 nextText、prevText 插槽;最后一步的按钮文本由 finishText 决定。
代码
<script setup lang="ts">
import { ref } from 'vue';
import { Button, Space, Switch, Tag, UserGuide, type UserGuideStepItem } from '@fibrous/semi-ui-vue';
const visible = ref(false);
const steps: UserGuideStepItem[] = [
{
target: () => document.querySelector('#button-demo-1'),
title: '新手引导',
description: 'Hello ByteDancer!',
},
{
target: () => document.querySelector('#button-demo-2'),
title: 'New Button Style',
description: 'Button text is Next',
},
{
target: () => document.querySelector('#button-demo-3'),
title: 'New finish button text',
description: 'Button text is I know',
},
];
</script>
<template>
<div>
<Button @click="visible = true">开始引导</Button>
<br />
<br />
<Space>
<Switch id="button-demo-1" aria-label="示例开关" defaultChecked />
<Tag id="button-demo-2"> Default Tag </Tag>
<Button id="button-demo-3">确定</Button>
</Space>
<UserGuide v-model:visible="visible" finishText="我知道啦!" mask mode="popup" :prevButtonProps="{ theme: 'borderless' }" :steps="steps" @finish="console.log('引导完成')" @skip="console.log('跳过引导')">
<template #nextText>Next</template>
<template #prevText>Prev</template>
</UserGuide>
</div>
</template>受控
通过 current 属性设置当前引导步骤。
用 v-model:current 绑定当前步骤,v-model:visible 绑定显示状态。
代码
<script setup lang="ts">
import { ref } from 'vue';
import { Button, Space, Switch, Tag, UserGuide, type UserGuideStepItem } from '@fibrous/semi-ui-vue';
const visible = ref(false);
const current = ref(0);
const steps: UserGuideStepItem[] = [
{
target: () => document.querySelector('#controlled-demo-1'),
title: '新手引导',
description: 'Hello ByteDancer!',
position: 'bottom',
},
{
target: () => document.querySelector('#controlled-demo-2'),
title: 'Switch',
description: 'This is a Semi Switch',
position: 'bottom',
},
{
target: () => document.querySelector('#controlled-demo-3'),
title: 'Button',
description: 'This is a Semi Button',
position: 'bottom',
},
];
const finish = (message: string) => {
current.value = 0;
console.log(message);
};
</script>
<template>
<div>
<Button @click="visible = true">开始引导</Button>
<br />
<br />
<Space>
<Switch id="controlled-demo-1" aria-label="示例开关" defaultChecked />
<Tag id="controlled-demo-2"> Default Tag </Tag>
<Button id="controlled-demo-3">确定</Button>
</Space>
<UserGuide v-model:current="current" v-model:visible="visible" mask mode="popup" :steps="steps" @finish="finish('引导完成')" @skip="finish('跳过引导')" />
</div>
</template>弹窗式引导
通过 mode 属性设置为 modal 开启弹窗式引导。
封面使用 cover 插槽,富文本描述使用 description 插槽。
代码
<script setup lang="ts">
import { ref } from 'vue';
import { Button, Image, TypographyText, UserGuide, type UserGuideStepItem } from '@fibrous/semi-ui-vue';
const visible = ref(false);
const steps: UserGuideStepItem[] = [
{ title: '欢迎使用 Semi DSM!', position: 'bottom' },
{
title: '高可用的色盘',
description: '选取主色后,我们的颜色算法会为你生成一套高可用的色盘',
position: 'bottom',
},
{ title: '自由定制', description: '开始定制属于你的设计系统吧!', position: 'bottom' },
];
const covers = ['/assets/root-web-sites/sky.jpg', '/assets/root-web-sites/greenleaf.jpg', '/assets/root-web-sites/colorful.jpg'];
</script>
<template>
<div>
<Button @click="visible = true">开始引导</Button>
<UserGuide v-model:visible="visible" mask mode="modal" :steps="steps" @change="(current) => console.log('当前引导步骤', current)" @finish="console.log('引导完成')" @next="console.log('下一步引导')" @prev="console.log('上一步引导')" @skip="console.log('跳过引导')">
<template #description="{ index }">
<div v-if="index === 0">你可以从已发布的主题出发,或者选择<TypographyText strong>立即创造</TypographyText>来创造一个新的主题</div>
</template>
<template #cover="{ index }">
<Image height="100%" :src="covers[index]" width="600px" />
</template>
</UserGuide>
</div>
</template>无遮罩
通过 mask 属性设置为 false 开启无遮罩引导。
代码
<script setup lang="ts">
import { ref } from 'vue';
import { Button, UserGuide, type UserGuideStepItem } from '@fibrous/semi-ui-vue';
const visible = ref(false);
const steps: UserGuideStepItem[] = [
{
target: () => document.querySelector('#mask-demo'),
title: 'No Mask',
description: 'Hello ByteDancer!',
},
];
</script>
<template>
<div>
<Button id="mask-demo" @click="visible = true">开始引导</Button>
<UserGuide v-model:visible="visible" :mask="false" mode="popup" :steps="steps" @finish="console.log('引导完成')" @skip="console.log('跳过引导')" />
</div>
</template>API 参考
Props
| 属性 | 说明 | 类型 | 默认值 |
|---|---|---|---|
| current | 当前步骤的索引,对应 v-model:current;不传时由组件自己保存,从 0 开始。 | number | semiGlobalDefault("UserGuide", "current") |
| finishText | 最后一步完成按钮的文本;为空时使用语言包中的文案。 | string | semiGlobalDefault("UserGuide", "finishText") |
| getPopupContainer | modal 模式下弹窗渲染至该 DOM 中;popup 模式下指向 body 以外的元素时不锁定 body 滚动。气泡卡片位于 top layer,不受此属性影响。 | Type(() => HTMLElement) | undefined |
| mask | 是否显示蒙层,可被步骤的 mask 覆盖。 | boolean | semiDefault("UserGuide", "mask", true) |
| mode | 引导模式:popup 为气泡卡片,modal 为弹窗。 | "popup" | "modal" | semiDefault("UserGuide", "mode", "popup") |
| nextButtonProps | 下一步按钮的属性,可含 class、style 与 onClick;onClick 先于内置处理调用,按钮文本使用 nextText 插槽。 | UserGuideButtonProps | semiGlobalDefault("UserGuide", "nextButtonProps") |
| position | 气泡卡片相对于目标元素的位置,可被步骤的 position 覆盖。 | Type"top" | "bottom" | "left" | "right" | "topLeft" | "topRight" | "leftTop" | "leftBottom" | "rightTop" | "rightBottom" | "bottomLeft" | "bottomRight" | "leftTopOver" | "rightTopOver" | "leftBottomOver" | "rightBottomOver" | semiDefault("UserGuide", "position", "bottom") |
| prevButtonProps | 上一步按钮的属性,可含 class、style 与 onClick;onClick 先于内置处理调用,按钮文本使用 prevText 插槽。 | UserGuideButtonProps | semiGlobalDefault("UserGuide", "prevButtonProps") |
| showPrevButton | 是否显示上一步按钮,第一步不显示。 | boolean | semiDefault("UserGuide", "showPrevButton", true) |
| showSkipButton | 是否显示跳过按钮,最后一步不显示。 | boolean | semiDefault("UserGuide", "showSkipButton", true) |
| spotlightPadding | 高亮区域的外扩距离,单位为像素,可被步骤的 spotlightPadding 覆盖;都未设置时为 5。 | number | semiGlobalDefault("UserGuide", "spotlightPadding") |
| steps | 引导步骤的数据;标题、描述和封面的自定义内容使用 title、description、cover 插槽。 | UserGuideStepItem[] | semiDefault("UserGuide", "steps", []) |
| theme | popup 模式的主题,可被步骤的 theme 覆盖。 | "default" | "primary" | semiDefault("UserGuide", "theme", "default") |
| visible | 是否显示引导,对应 v-model:visible。 | boolean | semiDefault("UserGuide", "visible", false) |
| zIndex | popup 模式下遮罩的层级,modal 模式下弹窗的层级。气泡卡片位于 top layer,始终在遮罩之上。 | number | semiDefault("UserGuide", "zIndex", numbers.DEFAULT_Z_INDEX) |
Events
| 事件 | 说明 | 回调参数 |
|---|---|---|
| update:current | 步骤改变时触发,值为新的索引,供 v-model:current 回写;先于 change。 | [current: number] |
| update:visible | 点击跳过、按 Esc 或完成引导时触发,值为 false,供 v-model:visible 回写;先于 skip 与 finish。 | [visible: false] |
| change | 步骤改变时触发,参数为新的索引。 | [current: number] |
| next | 点击下一步按钮时触发,参数为新的索引;最后一步触发 finish。 | [current: number] |
| prev | 点击上一步按钮时触发,参数为新的索引。 | [current: number] |
| skip | 点击跳过按钮或按 Esc 时触发。 | [] |
| finish | 点击最后一步的完成按钮时触发。 | [] |
Slots
| 插槽 | 说明 | 作用域参数 |
|---|---|---|
| title | 步骤标题,作用域参数为 { item, index };渲染为空时使用 item.title。 | UserGuideStepSlotProps |
| description | 步骤描述,作用域参数为 { item, index };渲染为空时使用 item.description。 | UserGuideStepSlotProps |
| cover | 步骤封面,作用域参数为 { item, index };不提供时该步骤没有封面区。 | UserGuideStepSlotProps |
| nextText | 下一步按钮的文本,最后一步显示 finishText;不提供时使用语言包中的文案。 | any |
| prevText | 上一步按钮的文本;不提供时使用语言包中的文案。 | any |
Exposes
| 名称 | 说明 | 类型 |
|---|---|---|
| 无 |
UserGuide 没有根元素:popup 模式下 class、style 落在气泡卡片上;modal 模式下按 Modal 的落点,class 落在 portal 内最外层元素上,style 落在 .semi-modal 上。aria-* 等其余未声明的属性会被丢弃。对话框的可访问名称只来自当前步骤的标题(title 插槽或 item.title)。
StepItem
steps 的每一项(类型 UserGuideStepItem)只放数据,自定义内容通过 title、description、cover 插槽按 { item, index } 渲染。
| 属性 | 说明 | 类型 | 默认值 |
|---|---|---|---|
| class | 步骤的自定义类名 | string | object | array | - |
| target | 目标元素,高亮区域会聚焦到这个元素上;函数形式在测量步骤时调用,目标尚未出现时可返回 null 或 undefined | Element | (() => Element | null | undefined) | - |
| title | 步骤标题的文本;title 插槽渲染出内容时以插槽为准 | string | - |
| description | 步骤描述的文本;description 插槽渲染出内容时以插槽为准 | string | - |
| mask | 是否显示此步骤的蒙层,会覆盖全局配置 | boolean | - |
| showArrow | 是否显示箭头(仅在 mode=popup 时有效) | boolean | true |
| spotlightPadding | 此步骤高亮区域的内边距,会覆盖全局配置 | number | - |
| theme | 此步骤的主题,会覆盖全局配置 | default | primary | - |
| position | 此步骤弹出层的位置,会覆盖全局配置 | Position | - |
Accessibility
- 每一步是以步骤
title命名的对话框,按 Esc 等同点击「跳过」。 - 两种模式下每一步出现时,焦点都移到主按钮(「下一步」或「完成」)上。popup 模式下 Tab 在卡片内循环,引导关闭时焦点回到打开前的元素;modal 模式的焦点循环与归还由 Modal 管理。
设计变量
| Sass 变量 | 值 | 用法 |
|---|---|---|
| $transition_duration-userGuide_spotLight | 200ms | spotLight 动画持续时间 |
| $transition_function-userGuide_spotLight | cubic-bezier(0.4, 0, 0.2, 1) | spotLight 过渡曲线 |
| $color-userGuide_popup-text-default | var(--semi-color-text-0) | 气泡卡片文字颜色 - 默认 |
| $color-userGuide_popup-text-primary | var(--semi-color-tertiary-light-default) | 气泡卡片文字颜色 - primary |
| $color-userGuide_modal_content_indicator-bg | var(--semi-color-primary-light-active) | 弹窗指示器背景颜色 |
| $color-userGuide_modal_content_indicator-bg-active | var(--semi-color-primary) | 弹窗指示器背景颜色 - 选中态 |
| $width-userGuide_popover-default | 400px | 默认气泡卡片宽度 |
| $height-userGuide_popup_content_cover-default | 200px | 默认气泡卡片封面高度 |
| $width-userGuide_modal_content_cover-default | 600px | 默认弹窗式卡片封面宽度 |
| $height-userGuide_modal_content_cover-default | 300px | 默认弹窗式卡片封面高度 |
| $height-userGuide_modal_content_indicator-default | 30px | 默认弹窗式卡片指示器整体高度 |
| $width-userGuide_modal_content_indicator_item-default | 6px | 默认弹窗式卡片单个指示器宽度 |
| $height-userGuide_modal_content_indicator_item-default | 6px | 默认弹窗式卡片单个指示器高度 |
| $font-userGuide_popup_content_title-lineHeight | 24px | 气泡卡片标题行高 |
| $font-userGuide_popup_content_description-lineHeight | 20px | 气泡卡片详情行高 |
| $font-userGuide_popup_content_indicator-lineHeight | 20px | 气泡卡片指示器行高 |
| $font-userGuide_modal_content_title-lineHeight | 24px | 弹窗式卡片标题行高 |
| $font-userGuide_modal_content_description-lineHeight | 20px | 弹窗式卡片详情行高 |
| $spacing-userGuide_popup_content_body-padding | 24px | 气泡卡片内边距 |
| $spacing-userGuide_popup_content_title-marginBottom | 8px | 气泡卡片标题底部外边距 |
| $spacing-userGuide_popup_content_description-marginBottom | 48px | 气泡卡片详情底部外边距 |
| $spacing-userGuide_popup_content_button-gap | 12px | 气泡卡片底部按钮间距 |
| $spacing-userGuide_popup_content_button-marginLeft | 120px | 气泡卡片按钮组左边距 |
| $spacing-userGuide_modal_content_title-marginBottom | 8px | 弹窗式卡片标题底部外边距 |
| $spacing-userGuide_modal_content_body-padding | 24px 48px | 弹窗式卡片body内边距 |
| $spacing-userGuide_modal_content_footer-padding | 24px | 弹窗式卡片footer内边距 |
| $spacing-userGuide_popup_content_indicator-gap | 8px | 弹窗式片指示器间距 |
| $spacing-userGuide_modal_content_button-gap | 12px | 弹窗式卡片底部按钮间距 |
| $radius-userGuide_popup_content_cover | var(--semi-border-radius-medium) var(--semi-border-radius-medium) 0 0 | 气泡卡片封面圆角 |
| $radius-userGuide_modal_content_indicator | var(--semi-border-radius-large) | 气泡卡片指示器圆角 |
| $font-userGuide_popup_content_title-fontSize | $font-size-header-5 | 气泡卡片标题字体大小 |
| $font-userGuide_popup_content_description-fontSize | $font-size-regular | 气泡卡片详情字体大小 |
| $font-userGuide_popup_content_title-fontWeight | $font-weight-bold | 气泡卡片标题字重 |
| $font-userGuide_modal_content_title-fontSize | $font-size-header-5 | 弹窗式卡片标题字体大小 |
| $font-userGuide_modal_content_description-fontSize | $font-size-regular | 弹窗式卡详情字体大小 |
| $font-userGuide_modal_content_title-fontWeight | $font-weight-bold | 弹窗式卡片标题字重 |