更新记录
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 为原生渲染,不支持
@keyframes与animation,因此全部动画使用 CSS transition + transform + opacity 实现(libs/css/animate.scss),一套代码两端表现一致 - 样式全部使用单层类选择器,不使用后代选择器、伪类、
100vw/100vh、z-index、v-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.vue 的 onLaunch 中调用:
// 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-icon 的 success 图标 |
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-property 与 var(),主题色无法通过 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,无法在运行时往页面树里插入组件。
所以统一采用「宿主占位 + 事件总线」方案,两端行为一致。
实现上有两个细节:
- 回调不能用函数传递 —— App 端
uni.$emit是跨页面全局事件,经由原生层转发时函数会丢失。 因此改为:调用方生成唯一 id,宿主执行完把结果 emit 到该 id 频道,调用方用$once接收,从而支持 Promise。 - 事件带来源页面路由 —— 多个页面各挂一个
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时无需再监听click,clickable默认为 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();事件:change、update: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 过渡动画
给插槽内容包裹一层显隐过渡。show 由 false 改为 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.ts,d-loading 与 d-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%
pulse 用 setInterval 轮流切换高亮下标(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();事件:select、cancel、change
示例
声明式:
<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 使用注意
- 传入插槽的内容,文字必须放在
text组件中,否则不显示 - 自定义样式建议使用对象形式,或使用
background-color而非background简写 - 不要在插槽内容中使用百分比宽度、
vh/vw、后代选择器 App.vue中的全局样式会编译进每个 nvue 文件,若有 nvue 不支持的属性可用// #ifndef APP-NVUE条件编译隔离-
flex 默认值跨端不一致(最容易踩的坑):
- nvue 的
view默认display: flex且flex-direction: column - H5 / 小程序的
view默认display: block,即使设成 flex 也是flex-direction: row
因此自定义插槽内容的样式时,
display: flex与flex-direction都要显式声明, 否则 nvue 正常但 H5 下元素会全部堆到顶部、本该纵向排列的变成横向 - nvue 的
- nvue 支持的 CSS 是白名单制,官方文档明确「本页未列出的属性均不被支持」。
常用但 nvue 不支持的有:
min-width/max-width、box-shadow(Android 需留空间)、backdrop-filter、z-index、pointer-events、百分比、vw/vh、CSS 变量。 本库已用条件编译处理,你自定义插槽内容时需自行规避
示例
项目内置了两个演示页,可直接运行验证:
| 页面 | 说明 |
|---|---|
pages/index/index.vue |
vue 页面演示,覆盖全部组件与用法 |
pages/nvue-demo/nvue-demo.nvue |
nvue 页面演示,用于验证原生渲染兼容性(仅 App 端可预览) |

收藏人数:
下载插件并导入HBuilderX
赞赏(0)
下载 33
赞赏 0
下载 12621493
赞赏 1950
赞赏
京公网安备:11010802035340号