更新记录

1.1.2(2026-09-02) 下载此版本

优化动态设置主题色

1.1.1(2026-09-02) 下载此版本

优化nvue动画

1.1.0(2026-09-01) 下载此版本

兼容 nvue

查看更多

平台兼容性

uni-app(3.8.3)

Vue2 Vue3 Vue3插件版本 Chrome Safari app-vue app-nvue Android iOS 鸿蒙
× 1.0.0 -
微信小程序 支付宝小程序 抖音小程序 百度小程序 快手小程序 京东小程序 鸿蒙元服务 QQ小程序 飞书小程序 小红书小程序 快应用-华为 快应用-联盟
- - - - - - - - - - -

uni-app x(3.8.3)

Chrome Safari Android iOS 鸿蒙 微信小程序
- - - - - -

d-view

vue3 弹层组件集合,包含 d-overlay(遮罩层)、d-popup(弹出层)、d-modal(模态框)、 d-transition(过渡动画)、d-loading(加载中)、d-actionsheet(动作面板)、 d-empty(空状态)、d-icon(图标)、d-host(函数式 API 宿主)。

特性

  • 同时兼容 vue 页面 / nvue 页面 / H5 / 微信小程序
  • 不依赖第三方动画库。nvue 为原生渲染,不支持 @keyframesanimation,因此全部动画使用 CSS transition + transform + opacity 实现(libs/css/animate.scss),一套代码两端表现一致
  • 样式全部使用单层类选择器,不使用后代选择器、伪类、100vw/100vhz-indexv-bind() 等 nvue 不支持的写法
  • 同时支持 v-model:show 声明式与 ref.open() 命令式两种调用方式
  • 内置主题系统,默认主色 #3c9cff,一处配置全局生效,支持运行时换肤(见 主题
  • 内置自绘图标 d-icon,不依赖字体文件与 SVG,nvue 下同样可用

安装

uni_modules 目录下的组件由 easycom 自动按需引入,无需手动注册,直接在模板中使用即可。

如需全局注册(例如要在 JS 中动态使用):

// main.js
import DView from '@/uni_modules/d-view'

export function createApp() {
    const app = createSSRApp(App)
    app.use(DView)
    return { app }
}

主题

默认主色为 #3c9cff,可通过 setTheme 全局覆盖。

快速开始

App.vueonLaunch 中调用:

// App.vue
import { setTheme } from '@/uni_modules/d-view/libs/ts/theme'

export default {
    onLaunch() {
        setTheme({
            primary: '#3c9cff'   // 改成你的品牌色
        })
    }
}

建议用 libs/ts/theme 这个路径,而不是 @/uni_modules/d-view—— 后者会把全部组件都打进 App.vue 的作用域,前者只引入主题配置。

增量合并:只传需要改的字段,未传的保持默认值。

配置项

字段 默认值 说明
primary #3c9cff 主题色,弹窗确认按钮、light 主题下的图标色
success #22c55e 成功色,用于 d-iconsuccess 图标
warning #f59e0b 警告色
error #ef4444 错误色
info #8b95a5 提示色
mask #000000 遮罩层基色
maskOpacity 0.5 遮罩透明度(d-overlay 未指定 opacity 时生效)
darkBg rgba(28, 30, 36, 0.92) 深色弹层背景(loading 的 theme="dark"
darkText #ffffff 深色弹层主文字
darkTextSecondary rgba(255, 255, 255, 0.6) 深色弹层次要文字
lightBg rgba(255, 255, 255, 0.97) 浅色弹层背景
lightText #1a1a1a 浅色弹层主文字
lightTextSecondary #8a8f99 浅色弹层次要文字
lightBorder rgba(0, 0, 0, 0.06) 浅色弹层描边(nvue 无阴影时的替代)
popupBg #ffffff 弹窗背景
popupTitle #1a1a1a 弹窗标题文字
popupContent #8a8f99 弹窗内容文字
modalCancelBg #f2f3f5 弹窗取消按钮背景
modalCancelText #4a4f59 弹窗取消按钮文字
emptyIcon #c9ced6 空状态图标颜色
emptyText #8a8f99 空状态文案颜色

完整示例

setTheme({
    primary: '#ff6b35',
    success: '#00b578',
    error:   '#e54545',
    maskOpacity: 0.6,
    darkBg: 'rgba(20, 20, 22, 0.94)'
})

运行时换肤

主题是响应式的,在任意页面调用都会立即生效,无需刷新:

import { setTheme, getTheme, resetTheme } from '@/uni_modules/d-view/libs/ts/theme'

setTheme({ primary: '#722ed1' })  // 切换主题色
getTheme().primary                // 读取当前值
resetTheme()                      // 恢复默认

在组件中订阅主题:

import { useTheme } from '@/uni_modules/d-view/libs/ts/theme'

const theme = useTheme()
// theme.primary 随 setTheme 自动更新

为什么不用 CSS 变量

nvue 是原生渲染,不支持 --custom-propertyvar(),主题色无法通过 CSS 变量下发。 因此主题由 JS 维护,组件通过内联样式读取——两端表现一致,且天然支持运行时换肤。

注意:组件样式里凡是与主题相关的颜色都走内联样式, 如果你想用 customStyle 覆盖,内联样式的优先级高于 class,直接覆盖即可。

函数式 API

不想在模板里挂 ref?可以直接用函数调用。

import { showLoading, hideLoading, showModal, showActionSheet } from '@/uni_modules/d-view/libs/ts/function'

showLoading('加载中...')
hideLoading()

const res = await showModal({ title: '确认删除?' })
if (res.confirm) { /* ... */ }

const sheet = await showActionSheet({ actions: [{ name: '拍照' }, { name: '从相册选择' }] })
if (sheet.select) { console.log(sheet.select.index) }

使用前必须挂载 d-host

<view class="page">
    ...页面内容...
    <d-host />
</view>

挂载一次后,该页面内任意位置(包括深层子组件、纯 JS 模块)都能直接调用函数。

未挂载时调用会在控制台报错提示,不会崩溃。

为什么需要 d-host

H5 有 DOM,可以 createVNode + render 动态挂到 body 上; 但 小程序与 App-nvue 没有 DOM,无法在运行时往页面树里插入组件。 所以统一采用「宿主占位 + 事件总线」方案,两端行为一致。

实现上有两个细节:

  1. 回调不能用函数传递 —— App 端 uni.$emit 是跨页面全局事件,经由原生层转发时函数会丢失。 因此改为:调用方生成唯一 id,宿主执行完把结果 emit 到该 id 频道,调用方用 $once 接收,从而支持 Promise。
  2. 事件带来源页面路由 —— 多个页面各挂一个 d-host 时,全局事件会被所有宿主收到。 payload 里带上调用方所在页面的 route,宿主只响应同页面的调用。

组件

d-overlay 遮罩层

全屏遮罩,可单独使用,也可作为其他弹层的基础层。

采用双层结构:背景层独立做淡入淡出,插槽内容层不受透明度影响,因此内容不会「跟着遮罩一起变淡」。

属性

属性 类型 默认值 说明
show Boolean false 是否展示,支持 v-model:show
opacity Number 0.5 遮罩透明度 0~1
color String #000000 遮罩颜色,支持 #000 / #000000 / rgb() / rgba()
duration Number 300 动画时长(ms)
clickable Boolean true 点击遮罩是否自动关闭(触发 close + update:show
position String fixed 定位方式,fixed 独立使用 / absolute 嵌在其他全屏容器内

事件

事件 说明
click 点击遮罩或内容区时触发
close clickable 为 true 时点击触发
change 显示状态变化;关闭时于动画结束后才触发
update:show 支持 v-model:show 双向绑定

插槽

名称 说明
default 遮罩之上需要展示的内容(可选,不传则不渲染内容层)

示例

纯遮罩,点击自动关闭:

<d-overlay v-model:show="visible" />

带内容 + 自定义颜色与动画时长:

<d-overlay :show="visible" color="#000000" :opacity="0.7" :duration="200" @click="visible = false">
    <view class="guide">
        <text>这里是引导内容,不会跟随遮罩变淡</text>
    </view>
</d-overlay>
const visible = ref(false)

说明:使用 v-model:show 时无需再监听 clickclickable 默认为 true,点击遮罩会自动置为 false。 若 clickable 设为 false,则需自行监听 click 处理关闭逻辑。

d-popup 弹出层

属性 类型 默认值 说明
show Boolean false 是否展示,支持 v-model:show
mode String bottom top / bottom / left / right / center
isClickOverlay Boolean true 点击遮罩是否关闭
radius String | Number 30 圆角,数字时按 rpx
customStyle String | Object {} 自定义内容区样式
safeBottom Boolean true mode=bottom 时是否开启底部安全区
duration Number 300 动画时长(ms)
overlayOpacity Number 0.5 遮罩层透明度 0~1
closeOnBack Boolean true App 端物理返回键是否优先关闭弹窗(仅 App 生效)

方法:open() / close();事件:changeupdate:show

内部遮罩层由 d-overlay 提供,可通过 overlayOpacity 调整透明度。

命令式:

<d-popup ref="popupRef" mode="bottom" :radius="30">
    <view>内容</view>
</d-popup>
popupRef.value.open()
popupRef.value.close()

声明式:

<d-popup v-model:show="visible" mode="center">
    <view>内容</view>
</d-popup>

两种方式可混用:命令式调用时会自动同步 update:show。 App 端弹窗打开时按物理返回键会先关闭弹窗而不是退出页面,无需在页面里额外处理。

d-modal 模态框

<d-modal ref="modalRef" />
modalRef.value.open({
    title: '温馨提示',
    content: '确定要执行该操作吗?',
    cancelText: '取消',
    confirmText: '确定',
    width: 580,
    radius: 30,
    success: ({ confirm, cancel }) => {
        if (confirm) {}
        if (cancel) {}
    }
})

d-transition 过渡动画

给插槽内容包裹一层显隐过渡。showfalse 改为 true 时播放进场动画,改为 false 时自动播放对应的退场动画,动画结束后节点才会被卸载。

属性

属性 类型 默认值 说明
show Boolean false 是否展示,切换即触发进/退场动画
mode String fadeIn 动画类型,见下表(只需传进场名,退场会自动配对)
duration Number 300 动画时长(ms),关闭延迟与之一致

动画类型

mode 进场效果 自动配对的退场效果
fadeIn 淡入 fadeOut 淡出
fadeInDown 从上往下滑入 + 淡入 fadeOutUp 向上滑出 + 淡出
fadeInUp 从下往上滑入 + 淡入 fadeOutDown 向下滑出 + 淡出
fadeInLeft 从左往右滑入 + 淡入 fadeOutLeft 向左滑出 + 淡出
fadeInRight 从右往左滑入 + 淡入 fadeOutRight 向右滑出 + 淡出
slideInDown 从上往下滑入 slideOutUp 向上滑出
slideInUp 从下往上滑入 slideOutDown 向下滑出
slideInLeft 从左往右滑入 slideOutLeft 向左滑出
slideInRight 从右往左滑入 slideOutRight 向右滑出
zoomIn 由小放大 + 淡入(0.6 → 1) zoomOut 缩小 + 淡出

位移类动画的偏移量是元素自身宽/高的 100%,因此 slideInUp 刚好从容器下方滑入、slideInRight 刚好从右侧滑入。

插槽

名称 说明
default 需要执行动画的内容

示例

<d-transition :show="visible" mode="slideInUp" :duration="300">
    <view class="panel">内容</view>
</d-transition>
const visible = ref(false)
// 打开:播放 slideInUp
visible.value = true
// 关闭:自动播放 slideOutDown,300ms 后节点卸载
visible.value = false

实现说明

  • 内部不使用 @keyframes/animation(nvue 原生渲染不支持),而是「CSS transition + 状态切换」: 先以起始态渲染,下一帧(30ms)再切到终止态,从而触发过渡
  • 过渡属性固定为 opacity, transform,缓动 cubic-bezier(0.25, 0.8, 0.25, 1)
  • 组件本身不设置任何对齐样式,内容尺寸由父级 flex 容器决定,方便配合 d-popup 使用
  • 传入 mode 不存在时自动降级为 fadeIn

d-loading 加载中

与轻提示的不同:默认带遮罩、不会自动关闭、转圈图标更大、文字在图标下方。

属性

属性 类型 默认值 说明
show Boolean false 是否展示,支持 v-model:show
text String 加载文案
type String dot dot 圆点环 / circle 经典圆环 / pulse 三点脉冲
size Number 72 图标尺寸(rpx)
color String 图标颜色,默认取主题色 primary(深色主题下为白色)
spinDuration Number 900 旋转一圈的时长(ms)
theme String dark dark / light
overlay Boolean true 是否显示遮罩层
overlayOpacity Number 0.3 遮罩透明度
radius Number 24 圆角(rpx)
duration Number 300 显隐动画时长(ms)
customStyle String | Object 自定义内容区样式

方法:show() / hide()

<d-loading v-model:show="loading" type="dot" text="加载中..." />
const loading = ref(false)
loading.value = true
setTimeout(() => { loading.value = false }, 2000)

三种 type 的区别:dot 是 12 个点透明度递减形成拖尾(默认,最精致); circle 是细圆环 + 高亮弧(节点最少);pulse 是三个点轮流放大,呼吸感最强。

转圈动画说明

nvue 不支持 @keyframes / animation,常见替代方案是用 setInterval 逐帧改角度, 但帧率等于定时器频率(一般只有 12~16fps),肉眼明显卡顿。

这里的实现是每圈只把角度累加 360 度,中间补间全部交给原生 transition

spin.value = 0
setTimeout(() => {
    spin.value = 360                                   // 第一圈
    setInterval(() => { spin.value += 360 }, 900)      // 之后每圈累加
}, 30)
transition-property: transform;
transition-duration: .9s;
transition-timing-function: linear;

帧率由系统保证(60fps),定时器每圈只触发一次,开销极小。 角度持续累加而不取模,是为了保证始终是正向过渡,不会在 360→0 处倒转。

该逻辑已抽成 libs/ts/useSpin.tsd-loadingd-icon 的旋转类图标共用。

d-icon 图标

内置图标组件,d-loading 基于它渲染图标,也可单独使用。

为什么是自绘而不是 SVG / 字体

方案 nvue 支持 说明
SVG 组件(如 @vicons/* 原生渲染没有 SVG 元素
SVG 图片 / base64 uni-app 明确不支持 SVG 格式图片
CSS @font-face 字体图标 只能在 JS 里用 dom.addRule 加载
view 自绘 零依赖,两端表现一致

所以图标全部由 view + border + border-radius + transform 画出来。 代价是图标数量有限,好处是零依赖、零网络请求、任意颜色尺寸。

如果需要成百上千个业务图标,建议走 iconfont 字体方案: vue 端用 @font-face,nvue 端用 weex.requireModule('dom').addRule('fontFace', ...) 加载。

属性

属性 类型 默认值 说明
name String success 图标名,见下表
size Number 40 尺寸(rpx)
color String #ffffff 颜色
spinDuration Number 900 旋转类图标转一圈的时长(ms)

图标名

name 图形 说明
success 对勾,矩形保留右、下两边后旋转 42°
error / close 两条线 ±45° 交叉
warning ! 上竖条 + 下圆点
info i 上圆点 + 下竖条
loading 12 个点环形分布,透明度递减
circle 细圆环 + 高亮弧拖尾
pulse 三个点轮流放大
empty 📦 空箱子,上盖 + 下方 U 形箱身

示例

<d-icon name="success" :size="56" color="#22c55e" />
<d-icon name="loading" :size="72" color="#3c9cff" />
<d-icon name="warning" :size="48" color="#f59e0b" />

实现说明

nvue 不支持百分比,所以图标内部的定位全部按 size 计算出具体 rpx,不使用 left: 50% 这类写法:

const w = Math.round(s * 0.78)
left: Math.round((s - w) / 2) + 'rpx'   // 代替 left: 50%

pulsesetInterval 轮流切换高亮下标(350ms), loading / circle 复用 useSpin 的 transition 补间方案。

d-actionsheet 动作面板

基于 d-popup(mode="bottom") 封装,支持声明式与命令式。 典型场景:上传图片(拍照 / 从相册选择)、分享、更多操作、删除确认。

属性

属性 类型 默认值 说明
show Boolean false 是否展示,支持 v-model:show
title String 标题
desc String 描述
actions Array [] 选项列表 [{ name, color, disabled }]color 常用于标红危险操作
cancelText String 取消 取消按钮文案,为空则不显示
closeOnClickAction Boolean true 点击选项后是否关闭
closeOnClickOverlay Boolean true 点击遮罩是否关闭
radius Number 24 圆角(rpx)
safeBottom Boolean true 是否开启底部安全区
duration Number 300 动画时长(ms)
overlayOpacity Number 0.5 遮罩透明度

方法:open(options) / close();事件:selectcancelchange

示例

声明式:

<d-actionsheet
    v-model:show="visible"
    title="选择操作"
    :actions="[{ name: '拍照' }, { name: '从相册选择' }, { name: '删除', color: '#ef4444' }]"
    @select=""
/>

命令式:

<d-actionsheet ref="sheetRef" />
sheetRef.value.open({
    title: '选择操作',
    actions: [{ name: '拍照' }, { name: '从相册选择' }],
    select: ({ index, item }) => { console.log(index, item.name) }
})

函数式:

const res = await showActionSheet({
    actions: [{ name: '拍照' }, { name: '从相册选择' }]
})
if (res.select) { console.log(res.select.index) }

disabled: true 的选项点击无效,且自动使用灰色文字。

d-empty 空状态

用于空列表、空购物车、无搜索结果、加载失败等场景。

属性

属性 类型 默认值 说明
preset String 内置预设图名称,见下方可用值
image String 自定义图片地址,优先级高于 preset
icon String empty 内置图标名,无图时使用
iconSize Number 140 图标尺寸(rpx)
imageWidth Number 240 图片宽度(rpx)
imageHeight Number 240 图片高度(rpx)
iconColor String 图标颜色,默认取主题 emptyIcon
title String 暂无数据 主文案
desc String 副文案
customStyle String | Object 自定义容器样式

图片来源优先级image 插槽 > image 属性 > preset 预设图 > 内置自绘图标

内置预设图(15 种)

图片位于 libs/image/empty/,传入名称即可,无需关心路径:

preset 含义 preset 含义
data 数据为空 order 订单为空
cart 购物车为空 coupon 没有优惠券
search 暂无搜索结果 address 没有收货地址
message 消息为空 message-list 消息列表为空
comment 暂无评论 news 无新闻列表
history 无历史记录 collect 无收藏
wifi 没有网络 permission 无权限
not-found 页面不存在

传入不存在的名称会在控制台警告,并自动回退到内置自绘图标,不会显示破图。

插槽

名称 说明
image 自定义图片区域
default 底部操作区(按钮等),有内容时才渲染

示例

<!-- 使用内置预设图 -->
<d-empty preset="cart" title="购物车是空的" desc="去挑几件好物吧" />

<!-- 网络异常 + 重试按钮 -->
<d-empty preset="wifi" title="网络似乎断开了" desc="请检查网络连接">
    <button size="mini" type="primary" @click="reload">重新加载</button>
</d-empty>

<!-- 无图,使用内置自绘图标 -->
<d-empty title="暂无数据" desc="下拉刷新试试" />

<!-- 自定义图片(优先级最高) -->
<d-empty image="/static/empty.png" title="购物车是空的" />

nvue 提示:若 nvue 下相对路径的预设图不显示,把 libs/image/empty 复制到项目 static 目录,再用 image 属性传绝对路径(/static/empty/cart.png)即可。

nvue 使用注意

  1. 传入插槽的内容,文字必须放在 text 组件中,否则不显示
  2. 自定义样式建议使用对象形式,或使用 background-color 而非 background 简写
  3. 不要在插槽内容中使用百分比宽度、vh/vw、后代选择器
  4. App.vue 中的全局样式会编译进每个 nvue 文件,若有 nvue 不支持的属性可用 // #ifndef APP-NVUE 条件编译隔离
  5. flex 默认值跨端不一致(最容易踩的坑):

    • nvue 的 view 默认 display: flexflex-direction: column
    • H5 / 小程序的 view 默认 display: block,即使设成 flex 也是 flex-direction: row

    因此自定义插槽内容的样式时,display: flexflex-direction 都要显式声明, 否则 nvue 正常但 H5 下元素会全部堆到顶部、本该纵向排列的变成横向

  6. nvue 支持的 CSS 是白名单制,官方文档明确「本页未列出的属性均不被支持」。 常用但 nvue 不支持的有:min-width / max-widthbox-shadow(Android 需留空间)、 backdrop-filterz-indexpointer-events、百分比、vw/vh、CSS 变量。 本库已用条件编译处理,你自定义插槽内容时需自行规避

示例

项目内置了两个演示页,可直接运行验证:

页面 说明
pages/index/index.vue vue 页面演示,覆盖全部组件与用法
pages/nvue-demo/nvue-demo.nvue nvue 页面演示,用于验证原生渲染兼容性(仅 App 端可预览)

更新记录

changelog.md

隐私、权限声明

1. 本插件需要申请的系统权限列表:

2. 本插件采集的数据、发送的服务器地址、以及数据用途说明:

插件不采集任何数据

3. 本插件是否包含广告,如包含需详细说明广告表达方式、展示频率:

许可协议

MIT协议