前面我们已经讲过 Dialog 与 Toast 这对基础反馈(见本站《Vant Dialog 弹窗与 Toast 轻提示:交互反馈利器》),但移动端还有一整层「轻量反馈」没被覆盖:底部操作菜单、分享面板、顶部通知条、全屏图片预览。Vant 反馈层组件的这四个成员各有明确的调用范式,混用起来最容易出的问题不是样式,而是样式没引入、函数式调用找不到实例、关闭时机被拦截。本文按分层模型逐一拆解,最后拼成一个商品详情页的完整交互。

先建立分层模型

移动端反馈层(由轻到重)
Notify:顶部/底部通知条,非阻断,自动消失
↓
ActionSheet / ShareSheet:底部面板,需用户决策
↓
ImagePreview:全屏遮罩,独占交互
↓
Dialog:强确认,阻断流程(已另文讲解)

Notify 与 ImagePreview 提供函数式调用(showNotify / showImagePreview),不需要在模板里写标签;ActionSheet 与 ShareSheet 是普通组件,用 v-model:show 控制。这个区别直接决定了按需引入时样式要不要手动加。

⚠️
按需引入时最容易漏的一步
用 VantResolver / unplugin-vue-components 自动导入时,函数式组件的样式不会自动带上。如果 showNotify 弹出来了却完全没有样式,在入口补一行 import 'vant/es/notify/style';ImagePreview 同理补 import 'vant/es/image-preview/style'。

ActionSheet:actions 数据结构与关闭时机

每一项是 ActionSheetAction:name 主文案,subname 副文案,color 文字色,icon 图标(Vant 图标名或图片 URL,v4.8.6+),disabled / loading 状态,className 自定义类,以及可挂在项上的 callback。

vue MoreActions.vue
<script setup>
import { ref } from 'vue';
import { showNotify, showToast } from 'vant';

const show = ref(false);
const actions = [
  { name: '收藏商品', icon: 'star-o' },
  { name: '分享给好友', icon: 'share-o', subname: '可获得积分' },
  { name: '举报', color: '#ee0a24' },
  { name: '正在下架中', disabled: true },
];

const onSelect = (action, index) => {
  // close-on-click-action 为 true 时组件会自行关闭
  showToast(`选择了:${action.name}(第 ${index + 1} 项)`);
  showNotify({ type: 'success', message: `${action.name} 成功` });
};
</script>

<template>
  <van-cell is-link title="更多操作" @click="show = true" />

  <van-action-sheet
    v-model:show="show"
    :actions="actions"
    cancel-text="取消"
    description="请选择要执行的操作"
    close-on-click-action
    @select="onSelect"
    @cancel="() => console.log('已取消')"
  />
</template>

close-on-click-action 默认是 false——不显式开启,点完选项面板不会自己收起,这是最常见的「点了没反应」错觉。若要在关闭前做异步确认,用 before-close:接收 action 字符串,返回 false 或 Promise<false> 即可拦住。ActionSheet 也支持默认插槽,可塞任意自定义内容当底部面板用。

ShareSheet:一维数组与二维数组

options 是一维数组时渲染单行;传二维数组则自动分成多行(第一行常用作分享渠道,第二行放复制链接、生成海报等操作)。内置 icon 名包括 wechat、wechat-moments、weibo、qq、link、poster、qrcode、weapp-qrcode,也可以直接传图片 URL。

vue SharePanel.vue
<script setup>
import { ref } from 'vue';
import { showNotify } from 'vant';

const showShare = ref(false);
const options = [
  [
    { name: '微信', icon: 'wechat' },
    { name: '朋友圈', icon: 'wechat-moments' },
    { name: '微博', icon: 'weibo' },
    { name: 'QQ', icon: 'qq' },
  ],
  [
    { name: '复制链接', icon: 'link' },
    { name: '生成海报', icon: 'poster' },
    { name: '二维码', icon: 'qrcode' },
  ],
];

const onSelect = (option, index) => {
  showShare.value = false;
  showNotify({ type: 'primary', message: `已选择:${option.name}(${index})` });
};
</script>

<template>
  <van-share-sheet
    v-model:show="showShare"
    title="分享给好友"
    description="分享后可获得 10 积分"
    :options="options"
    cancel-text="取消"
    @select="onSelect"
  />
</template>
💡 小技巧:ShareSheet 不包含任何分享逻辑,它只负责把渠道渲染出来。真实项目里在 select 里按 option.icon 分发到 JS-SDK、剪贴板或海报生成函数即可;自定义渠道用图片 URL 当 icon,别去改内置样式变量。

Notify:函数式调用与全局默认配置

showNotify 接受字符串或 NotifyOptions 对象,返回 Notify 实例;还有 closeNotify()、setNotifyDefaultOptions()、resetNotifyDefaultOptions() 三个配套方法。常用配置:type(primary/success/danger/warning)、message、duration(默认 3000ms,设为 0 不自动消失)、position(top/bottom)、color、background、onClick、onClose。

js main.ts 中统一默认配置
import { setNotifyDefaultOptions } from 'vant';
import 'vant/es/notify/style';

setNotifyDefaultOptions({
  type: 'primary',
  duration: 2000,
  position: 'top',
  onClose() {
    console.log('notify closed');
  },
});
js 业务里直接调用
import { showNotify, closeNotify } from 'vant';

showNotify('已加入购物车');
showNotify({ type: 'danger', message: '库存不足', duration: 0 });

// 需要常驻时手动关闭
closeNotify();

如果要在通知条里放图标、按钮等自定义内容,改用组件式:在模板里写 <van-notify v-model:show="show" type="success">,用默认插槽填内容,自己控制显隐时机。

ImagePreview:从函数式调用到 beforeClose

最直接的用法是传字符串数组;需要控制起始位置、关闭按钮或监听关闭时,传配置对象。showImagePreview 返回实例,可调用 close()。

js 预览商品图
import { showImagePreview } from 'vant';
import 'vant/es/image-preview/style';

const images = [
  'https://example.com/goods-1.jpg',
  'https://example.com/goods-2.jpg',
];

const instance = showImagePreview({
  images,
  startPosition: 1,      // 从第 2 张开始
  closeable: true,       // 显示右上角关闭图标
  showIndex: true,
  maxZoom: 3,
  onChange: (index) => console.log('current', index),
  onClose: () => console.log('preview closed'),
});

// 需要时主动关闭:instance.close();

其它常用项:loop(默认 true)、showIndicators、swipeDuration、closeIconPosition(top-left/bottom-left/bottom-right)、closeOnClickImage、closeOnClickOverlay、beforeClose(返回 false 阻止关闭)、onScale。组件式 <van-image-preview> 则提供 index、cover、image 三个插槽,后者甚至能把图片替换成视频。

ℹ️
函数式 vs 组件式
只是看图、不需要在预览层里放业务 UI,用 showImagePreview 最省代码;要自定义页码、加「保存到相册」按钮或渲染视频,才用组件式 + 插槽。两者的 images 都是纯 URL 数组,不要塞 File 对象。

组合实战:一个商品详情页的交互层

vue GoodsDetail.vue(节选)
<script setup>
import { ref } from 'vue';
import { showNotify, showImagePreview, showToast } from 'vant';

const images = ['/a.jpg', '/b.jpg', '/c.jpg'];
const showActions = ref(false);
const showShare = ref(false);

const actions = [
  { name: '分享', icon: 'share-o' },
  { name: '收藏', icon: 'star-o' },
  { name: '举报', color: '#ee0a24' },
];
const shareOptions = [
  [
    { name: '微信', icon: 'wechat' },
    { name: '朋友圈', icon: 'wechat-moments' },
  ],
  [{ name: '复制链接', icon: 'link' }],
];

const onSelectAction = (action) => {
  if (action.name === '分享') {
    showShare.value = true;
    return;
  }
  showNotify({ type: 'success', message: `${action.name}成功` });
};

const onSelectShare = async (option) => {
  showShare.value = false;
  if (option.icon === 'link') {
    await navigator.clipboard.writeText(location.href);
    showNotify({ type: 'success', message: '链接已复制' });
    return;
  }
  showToast(`调起 ${option.name} 分享`);
};

const preview = (index: number) =>
  showImagePreview({ images, startPosition: index, closeable: true });
</script>

<template>
  <van-swipe :autoplay="0" @change="() => {}">
    <van-swipe-item v-for="(src, i) in images" :key="src">
      <img :src="src" @click="preview(i)" />
    </van-swipe-item>
  </van-swipe>

  <van-action-sheet
    v-model:show="showActions"
    :actions="actions"
    cancel-text="取消"
    close-on-click-action
    @select="onSelectAction"
  />
  <van-share-sheet
    v-model:show="showShare"
    title="分享商品"
    :options="shareOptions"
    @select="onSelectShare"
  />
</template>

常见坑速查

现象原因处理
showNotify 有内容无样式按需引入未带函数式组件样式import 'vant/es/notify/style'
ActionSheet 点选项后不关闭close-on-click-action 默认 false显式开启,或在 select 里手动置 false
ShareSheet 只显示一行options 传成了一维数组改成二维数组分行
预览层里加不了按钮函数式调用不支持插槽换组件式 van-image-preview + cover 插槽
弹层被业务浮层盖住z-index 默认 2000+,与自定义层冲突传 z-index / zIndex 统一规划层级
底部按钮被 Home 条挡住未开启安全区保持 safe-area-inset-bottom 默认 true,并在 meta 里加 viewport-fit=cover

总结

这四个组件其实只回答两个问题:要不要用户做决策(要 → ActionSheet / ShareSheet),要不要独占屏幕(要 → ImagePreview,不要 → Notify)。把函数式与组件式的边界划清楚,再补上按需引入时那两行样式,反馈层基本就不会再出诡异问题。

反馈组件的价值不在动画,而在于「不打断」与「必须打断」之间的取舍。
—— 选错层级,用户就会觉得 App 黏手