
uni-app提供开箱即用的SSR支持
SSR(服务端渲染)可以给SPA站点带来两大核心优势:
- 更好的SEO
- 更快的首屏渲染
很多uni-app
开发者都在积极尝试SSR,但大多没入门就放弃了,原因在于SSR颇高的技术门槛。
vue.js
官网在介绍 SSR 章节时的描述如下:
本指南将会非常深入,并且假设你已经熟悉 Vue.js 本身,并且具有 Node.js 和 webpack 的相当不错的应用经验。
故很多uni-app
开发者在社区中呼吁DCloud官方提供更为简洁、开箱即用的 uni-app SSR
方案。
uni-app
团队近期在完成 vue 3.0
的全平台升级后,基于vue 3.0 + uniCloud
,发布了开箱即用的 SSR 支持,我们称其为uniCloud版SSR
。
这是一个uniCloud版的SSR
示例:news.dcloud.io是基于uni-app & uniCloud
开发的新闻系统。通过审查元素会发现,新闻列表数据包含在服务端下发的源码中,而不是客户端Ajax请求所得。
uniCloud版的SSR
实现的较为简单,且和HBuilderX
做了深度集成,你可以按照如下步骤快速上手:
步骤一:调整代码适配服务端运行环境
- 生命周期:
uni-app
的生命周期钩子函数中,页面onLoad
、组件beforeCreate
和created
会在服务器端渲染 (SSR) 过程中被调用,你需要检查原项目代码中获取数据的时机; - 特定平台API:若直接使用了如
window
或document
,这类仅浏览器支持的全局变量,则会在云端Node.js
中执行时抛出错误; - 数据预取:
<uniCloud-db>
组件天然支持SSR,无需调整代码,推荐使用<uniCloud-db>
查询数据库。如果你未使用<uniCloud-db>
组件,则可使用serverPrefetch
来实现服务器端数据获取,使用@dcloudio/uni-app
提供的ssrRef
或Vue.js官方的Vuex
来实现状态同步;
更多详细信息及示例代码,参考:https://uniapp.dcloud.net.cn/collocation/ssr
步骤二:编译发行
通过HBuilderX的发行菜单->网站 PC-Web或手机H5、勾选ssr、勾选将编译后的资源部署在uniCloud前端网页托管
这个过程,对开发者来说很简单,只需要点击按钮即可,实际上HBuilderX
在背后做了大量工作,包括:
- 编译
uni-app
项目,分别生成Server Bundle
和Client Bundle
- 将
Client Bundle
上传到uniCloud前端网页托管中 - 将
Server Bundle
作为uni-ssr
云函数资源,编译并上传到uniCloud服务空间
步骤三:配置 uni-ssr 云函数的URL化路径
给uni-ssr
云函数绑定自定义域名,然后在浏览器中访问该域名,你就可以获得服务端渲染的页面了。
至此,uniCloud版SSR
开发部署工作结束,是不是比原来简单多了?
总结
Vue.js
官网及社区很多文档,介绍SSR时都会提到,SSR是把双刃剑,优点缺点都很明显(2优3缺,缺大于优)。但经过HBuilderX & uniCloud
加持的SSR,则完美解决了SSR的部分缺点,将其由缺转优,变成4优1缺,实现优大于缺。具体对比如下:
欢迎大家使用uniCloud版SSR
!
关联阅读:uni-app官网ssr介绍
SSR(服务端渲染)可以给SPA站点带来两大核心优势:
- 更好的SEO
- 更快的首屏渲染
很多uni-app
开发者都在积极尝试SSR,但大多没入门就放弃了,原因在于SSR颇高的技术门槛。
vue.js
官网在介绍 SSR 章节时的描述如下:
本指南将会非常深入,并且假设你已经熟悉 Vue.js 本身,并且具有 Node.js 和 webpack 的相当不错的应用经验。
故很多uni-app
开发者在社区中呼吁DCloud官方提供更为简洁、开箱即用的 uni-app SSR
方案。
uni-app
团队近期在完成 vue 3.0
的全平台升级后,基于vue 3.0 + uniCloud
,发布了开箱即用的 SSR 支持,我们称其为uniCloud版SSR
。
这是一个uniCloud版的SSR
示例:news.dcloud.io是基于uni-app & uniCloud
开发的新闻系统。通过审查元素会发现,新闻列表数据包含在服务端下发的源码中,而不是客户端Ajax请求所得。
uniCloud版的SSR
实现的较为简单,且和HBuilderX
做了深度集成,你可以按照如下步骤快速上手:
步骤一:调整代码适配服务端运行环境
- 生命周期:
uni-app
的生命周期钩子函数中,页面onLoad
、组件beforeCreate
和created
会在服务器端渲染 (SSR) 过程中被调用,你需要检查原项目代码中获取数据的时机; - 特定平台API:若直接使用了如
window
或document
,这类仅浏览器支持的全局变量,则会在云端Node.js
中执行时抛出错误; - 数据预取:
<uniCloud-db>
组件天然支持SSR,无需调整代码,推荐使用<uniCloud-db>
查询数据库。如果你未使用<uniCloud-db>
组件,则可使用serverPrefetch
来实现服务器端数据获取,使用@dcloudio/uni-app
提供的ssrRef
或Vue.js官方的Vuex
来实现状态同步;
更多详细信息及示例代码,参考:https://uniapp.dcloud.net.cn/collocation/ssr
步骤二:编译发行
通过HBuilderX的发行菜单->网站 PC-Web或手机H5、勾选ssr、勾选将编译后的资源部署在uniCloud前端网页托管
这个过程,对开发者来说很简单,只需要点击按钮即可,实际上HBuilderX
在背后做了大量工作,包括:
- 编译
uni-app
项目,分别生成Server Bundle
和Client Bundle
- 将
Client Bundle
上传到uniCloud前端网页托管中 - 将
Server Bundle
作为uni-ssr
云函数资源,编译并上传到uniCloud服务空间
步骤三:配置 uni-ssr 云函数的URL化路径
给uni-ssr
云函数绑定自定义域名,然后在浏览器中访问该域名,你就可以获得服务端渲染的页面了。
至此,uniCloud版SSR
开发部署工作结束,是不是比原来简单多了?
总结
Vue.js
官网及社区很多文档,介绍SSR时都会提到,SSR是把双刃剑,优点缺点都很明显(2优3缺,缺大于优)。但经过HBuilderX & uniCloud
加持的SSR,则完美解决了SSR的部分缺点,将其由缺转优,变成4优1缺,实现优大于缺。具体对比如下:
欢迎大家使用uniCloud版SSR
!
关联阅读:uni-app官网ssr介绍
收起阅读 »
云打包是真慢啊 官方能不能好好优化下体验啊
[HBuilder] 12:28:27.014 时间: 2021-09-29 11:59:09 类型: Android 公共测试证书 队列中
[HBuilder] 12:28:27.014 时间: 2021-09-29 11:59:09 类型: Android 公共测试证书 队列中

普通项目使用uniCloud
uinCloud admin项目实在是太丑,而且很多功能不全,而一般的项目不能直接用,毕竟有些后台还不是用vue,用云函数URL化太麻烦,而且不能用<unicloud-db> 组件,发挥不了uniCloud的优势。
这里我找到一个后门,可以让普通项目也用上uniCloud的功能,如直接在项目中直接用 uniCloud.callFunction()、uniCloud.database()、uniCloud.uploadFile()、直接引用<unicloud-db>,这样相当于是用到了uniCloud的优势,而对本身的项目又不需要做过多的修改就能直接兼容。
具体的方法就是建一个uni-app项目,在main.js文件中,只加上一句,window.uniCloud=uniCloud,然后打包,一般会生成三个JS文件,可以上传到CDN也可以直接放到项目里直接引用。
这里有几点要说一下,用VUE3打包,生成的是模块形式,代码较小,用VUE2打包生成文件会较大。还有就是一定要在项目开始前就引入文件。生面的文件可以适当的修改。
到此为止, uniCloud.callFunction()、uniCloud.database()、uniCloud.uploadFile()就可以直接在项目中使用。
<unicloud-db>则需要手动加一下。到uni-app的源码里面直接搜unicloud-db 找到对应的VUE文件,搜uni.强窗什么的全部删除或自己根据项目重写,可直接添加到全局也可以局部引用。对于VUE3项目注意一下销毁的生命周期。
uinCloud admin项目实在是太丑,而且很多功能不全,而一般的项目不能直接用,毕竟有些后台还不是用vue,用云函数URL化太麻烦,而且不能用<unicloud-db> 组件,发挥不了uniCloud的优势。
这里我找到一个后门,可以让普通项目也用上uniCloud的功能,如直接在项目中直接用 uniCloud.callFunction()、uniCloud.database()、uniCloud.uploadFile()、直接引用<unicloud-db>,这样相当于是用到了uniCloud的优势,而对本身的项目又不需要做过多的修改就能直接兼容。
具体的方法就是建一个uni-app项目,在main.js文件中,只加上一句,window.uniCloud=uniCloud,然后打包,一般会生成三个JS文件,可以上传到CDN也可以直接放到项目里直接引用。
这里有几点要说一下,用VUE3打包,生成的是模块形式,代码较小,用VUE2打包生成文件会较大。还有就是一定要在项目开始前就引入文件。生面的文件可以适当的修改。
到此为止, uniCloud.callFunction()、uniCloud.database()、uniCloud.uploadFile()就可以直接在项目中使用。
<unicloud-db>则需要手动加一下。到uni-app的源码里面直接搜unicloud-db 找到对应的VUE文件,搜uni.强窗什么的全部删除或自己根据项目重写,可直接添加到全局也可以局部引用。对于VUE3项目注意一下销毁的生命周期。

针对升级到HbuilderX-3.2.9中 Android 推送权限判断是否开启失效的解决
该文章是针对 升级到3.2.9后该推送权限功能失效的问题
前提代码:可以查看是否有相应匹配的代码
var isOn = undefined;
var main = plus.android.runtimeMainActivity();
var pkName = main.getPackageName();
var uid = main.getApplicationInfo().plusGetAttribute("uid");
var NotificationManagerCompat = plus.android.importClass("androidx.core.app.NotificationManagerCompat");
// var NotificationManagerCompat = plus.android.importClass("android.support.v4.app.NotificationManagerCompat");
isOn = NotificationManagerCompat.from(main).areNotificationsEnabled();
3.2.9更新中有一条:【重要】Android平台 新增 Android Support Library 升级迁移到 AndroidX
所以将 "android.support.v4.app.NotificationManagerCompat" 替换为 "androidx.core.app.NotificationManagerCompat",就可以正常运行
该文章是针对 升级到3.2.9后该推送权限功能失效的问题
前提代码:可以查看是否有相应匹配的代码
var isOn = undefined;
var main = plus.android.runtimeMainActivity();
var pkName = main.getPackageName();
var uid = main.getApplicationInfo().plusGetAttribute("uid");
var NotificationManagerCompat = plus.android.importClass("androidx.core.app.NotificationManagerCompat");
// var NotificationManagerCompat = plus.android.importClass("android.support.v4.app.NotificationManagerCompat");
isOn = NotificationManagerCompat.from(main).areNotificationsEnabled();
3.2.9更新中有一条:【重要】Android平台 新增 Android Support Library 升级迁移到 AndroidX
所以将 "android.support.v4.app.NotificationManagerCompat" 替换为 "androidx.core.app.NotificationManagerCompat",就可以正常运行
收起阅读 »
nvue+uniapp模仿抖音APP实例|uni-app直播/短视频
之前有给大家分享一个electron-webosx仿MAC桌面管理系统。今天分享的是全端uniapp小视频/直播实例项目。
uniapp-ttLive短视频 一款基于uni-app+nvue+uview-ui等技术开发的多功能跨端直播+短视频项目。流畅的上下滑动体验、支持全景式悬浮沉浸效果。可暂停/播放小视频、直播送礼物、聊天互动等功能。
vue3.x+electron13+vite2模仿macui桌面管理框架
使用技术
- 编码器/技术:HbuilderX3.1.21+Uniapp+Nvue+Vuex+Uapopup
- UI组件库:uView-ui / uni-ui
- 矢量图标库:iconfont字体图标
- 弹窗组件:UApopup 基于uni-app封装跨端弹窗组件
- 自定义导航条+底部菜单栏
- 编译支持:H5+小程序+APP端
基于 HbuilderX3.1.21 编码器开发,搭配nvue原生页面解决video层级过高问题,自定义 ua-navbar ua-tabbar ua-popup 组件全面支持Nvue页面。搭配uview-ui和uni-ui组件库。可编译至h5+小程序+APP端。
项目结构目录
main.js配置
import Vue from 'vue'
import App from './App'
import uView from 'uview-ui'
Vue.use(uView)
// 引入状态管理
import Store from './store'
Vue.prototype.$store = Store
const app = new Vue({
...App
})
app.$mount()
全局获取状态栏高度
<script>
export default {
globalData: {
// 全局设置状态栏和导航栏高度
statusBarH: 0,
customBarH: 0,
},
onLaunch: function() {
console.log('App Launch')
let token = uni.getStorageSync('token')
if(!token) {
uni.navigateTo({
url: '/pages/auth/login'
})
}
uni.getSystemInfo({
success: (e) => {
// 获取手机状态栏高度
let statusBar = e.statusBarHeight
let customBar
// #ifndef MP
customBar = statusBar + (e.platform == 'android' ? 50 : 45)
// #endif
// #ifdef MP-WEIXIN
// 获取胶囊按钮的布局位置信息
let menu = wx.getMenuButtonBoundingClientRect()
// 导航栏高度 = 胶囊下距离 + 胶囊上距离 - 状态栏高度
customBar = menu.bottom + menu.top - statusBar
// #endif
// #ifdef MP-ALIPAY
customBar = statusBar + e.titleBarHeight
// #endif
// 兼容nvue写法(H5/小程序/APP/APP-Nvue)
this.globalData.statusBarH = statusBar
this.globalData.customBarH = customBar
}
})
},
onShow: function() {
console.log('App Show')
},
onHide: function() {
console.log('App Hide')
}
}
</script>
uni-app全端自定义组件
项目中用到的顶部导航栏、底部菜单栏及弹窗均是自定义组件来实现功能。
<ua-popup v-model="isVisibleConfirm" shadeClose="false" title="标题" xclose z-index="1001"
content="<div style='color:#ff557f;padding:20px 40px;'>预测未来的最好办法是自己亲手创造未来!</div>"
:btns="[
{text: '取消', click: handleCancel},
{text: '确定', style: 'color:#00aa00;', click: handleOk},
]"
/>
<script>
export default {
methods: {
handleOk() {
let $ua = this.$refs.uapopup
$ua.open({
content: '人生漫漫,且行且珍惜',
customStyle: {'background-color': 'rgba(170, 0, 127, 0.6)', 'color': '#fff'},
time: 3,
onClose() {
$ua.open({
type: 'android',
content: '<div style="color:#aa007f">一切都将一去杳然,任何人都无法将其捕获。</div>',
customStyle: {'width': '210px'},
btns: [
{
text: '关闭',
click() {
$ua.close()
}
},
{
text: '确定',
style: 'color:#00aa00;',
click() {
// ...
}
}
]
})
}
})
}
}
}
</script>
uniapp直播/短视频
项目中短视频页面,整体分为顶部导航栏、底部菜单栏、中间视频区域。
<view v-if="currentTab == 2" class="ua__tabcnt-recommend">
<swiper class="ua__vdplayer-swiper flex1" :current="currentVideo" vertical @change="handleSwipeVertical">
<swiper-item v-for="(item, index) in videoList" :key="index">
<!-- 视频模块 -->
<view class="ua__vdplayer-video flex1">
<video class="vdplayer" :id="'vdplayer' + index" :ref="'vdplayer' + index"
:src="item.src"
:controls="false" :loop="true" :show-center-play-btn="false" object-fit="fill"
:autoplay="index == currentVideo"
@play="isPlaying=true" @timeupdate="handleTimeUpdate"
:style="{'width': winWidth, 'height': winHeight}"
>
</video>
<view class="ua__vdplayer-playwrap" @click="handleVideoClicked"><view v-if="!isPlaying" class="ua__vdplayer-playbtn"><text class="iconfont">{{`\ue607`}}</text></view></view>
</view>
<!-- 信息模块 -->
<view class="ua__vdplayer-info flexbox flex-col">
<view class="flexbox flex-row flex-alignb">
<!-- //左侧信息 -->
<view class="vdinfo__left flex1">
<view class="ltitem uavatar flexbox flex-row">
<navigator url="#" class="flexbox flex-alignc flex-row"><image class="uimg" :src="item.avatar" /><text class="uname">{{item.author}}</text></navigator>
<view class="flexbox btn" :class="{'actived': item.isFollow}" @click="handleFollow(index)"><text class="btn-text">{{item.isFollow ? '已关注' : '关注'}}</text></view>
</view>
<view v-if="item.topic" class="ltitem flexbox flex-row">
<view class="kw" v-for="(kw, index2) in item.topic" :key="index2"><text class="lbl">#{{kw}}</text></view>
</view>
<view class="ltitem"><text class="desc">{{item.desc}}</text></view>
</view>
<!-- //右侧按钮 -->
<view class="vdinfo__right flexbox flex-col">
<view class="rtitem ball" v-if="item.goods&&item.goods.length > 0" @click="handleShowGoodsPopup(item.goods)"><text class="icon iconfont">{{`\ue734`}}</text></view>
<view class="rtitem" :class="{'isliked': item.isLike}" @click="handleLiked(index)"><text class="icon iconfont">{{`\ue635`}}</text><text class="num">{{item.likeNum+(item.isLike ? 1 : 0)}}</text></view>
<view class="rtitem" @click="showReplyPopup = true"><text class="icon iconfont">{{`\ue632`}}</text><text class="num">{{item.replyNum}}</text></view>
<view class="rtitem" @click="showSharePopup = true"><text class="icon iconfont">{{`\ue63b`}}</text><text class="num">{{item.shareNum}}</text></view>
</view>
</view>
</view>
</swiper-item>
</swiper>
<!-- 底部播放进度条 -->
<view class="ua__vdplayer-progress"><view class="bar" :style="{'width': progressBar+'px'}"></view></view>
</view>
<script>
const app = getApp()
import videoJSON from '@/mock/videolist.js'
export default {
data() {
return {
// 导航栏高度
customBarHeight: app.globalData.customBarH,
navbarBgcolor: '#21252b',
tabbarBgcolor: '#21252b',
tabNavLs: [
{label: '附近动态', badge: 5, lists: []},
{label: '关注', lists: []},
{label: '推荐', dot: true, lists: []},
],
// 当前选项卡
currentTab: 0,
// 当前视频索引
currentVideo: 0,
// 视频数据
videoList: videoJSON,
// 视频是否播放中
isPlaying: false,
// 点击次数
clickNum: 0,
// 视频播放进度条
progressBar: 0,
clickTimer: null,
// 屏幕宽高
winWidth: '',
winHeight: '',
popupGoodsList: [],
showGoodsPopup: false,
showReplyPopup: false,
showSharePopup: false,
}
},
watch: {
currentTab(val) {
this.changeTabPanel(val)
}
},
computed:{
customBarMargin() {
return `margin-top: ${this.customBarHeight}px`
}
},
created() {
// 引入iconfont字体
// #ifdef APP-NVUE
const domModule = weex.requireModule('dom')
domModule.addRule('fontFace', {
fontFamily: "nvueIcon",
'src': "url('/static/fonts/iconfont.ttf')"
});
// #endif
let wW = uni.getSystemInfoSync().windowWidth
let wH = uni.getSystemInfoSync().windowHeight
this.winWidth = `${wW}px`
this.winHeight = `${wH}px`
},
methods: {
// 长按动态
handleDynamicMenu(e) {
let points
// #ifndef APP-NVUE
points = [e.touches[0].clientX, e.touches[0].clientY]
// #endif
// #ifdef APP-NVUE
points = [e.touches[0].screenX, e.touches[0].screenY]
// #endif
this.$refs.uapopup.open({
type: 'contextmenu',
follow: points,
btns: [
{text: '不感兴趣'},
{text: '复制'},
{
text: '举报',
style: 'color:#f00;',
click: () => {
this.$refs.uapopup.close()
}
},
],
})
},
/* ++++++++++ { 视频播放模块 } ++++++++++ */
getVideoCtx() {
// return this.$refs['vdplayer' + this.currentVideo][0]
return uni.createVideoContext('vdplayer'+ this.currentVideo, this)
},
// 垂直滑动视频
handleSwipeVertical(e) {
let index = e.detail.current
this.progressBar = 0
this.isPlaying = false
let video = this.getVideoCtx()
if(!video) return
video.pause()
// 重新开始
video.seek(0)
this.currentVideo = index
// 自动播放
this.handlePlay()
},
handlePlay() {
let video = this.getVideoCtx()
if(!video) return
video.play()
this.isPlaying = true
},
handlePause() {
let video = this.getVideoCtx()
if(!video) return
video.pause()
this.isPlaying = false
},
// 点击视频(单击/双击)
handleVideoClicked() {
this.clickTimer && clearTimeout(this.clickTimer)
this.clickNum++
this.clickTimer = setTimeout(() => {
if(this.clickNum >= 2) {
console.log('你双击了')
}else {
console.log('你单击了')
if(this.isPlaying) {
this.handlePause()
}else {
this.handlePlay()
}
}
this.clickNum = 0
}, 250)
},
...
}
}
</script>
okey,基于uni-app开发原生短视频/直播项目就分享到这里。
链接:https://juejin.cn/post/7010195797174124557/
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
之前有给大家分享一个electron-webosx仿MAC桌面管理系统。今天分享的是全端uniapp小视频/直播实例项目。
uniapp-ttLive短视频 一款基于uni-app+nvue+uview-ui等技术开发的多功能跨端直播+短视频项目。流畅的上下滑动体验、支持全景式悬浮沉浸效果。可暂停/播放小视频、直播送礼物、聊天互动等功能。
vue3.x+electron13+vite2模仿macui桌面管理框架
使用技术
- 编码器/技术:HbuilderX3.1.21+Uniapp+Nvue+Vuex+Uapopup
- UI组件库:uView-ui / uni-ui
- 矢量图标库:iconfont字体图标
- 弹窗组件:UApopup 基于uni-app封装跨端弹窗组件
- 自定义导航条+底部菜单栏
- 编译支持:H5+小程序+APP端
基于 HbuilderX3.1.21 编码器开发,搭配nvue原生页面解决video层级过高问题,自定义 ua-navbar ua-tabbar ua-popup 组件全面支持Nvue页面。搭配uview-ui和uni-ui组件库。可编译至h5+小程序+APP端。
项目结构目录
main.js配置
import Vue from 'vue'
import App from './App'
import uView from 'uview-ui'
Vue.use(uView)
// 引入状态管理
import Store from './store'
Vue.prototype.$store = Store
const app = new Vue({
...App
})
app.$mount()
全局获取状态栏高度
<script>
export default {
globalData: {
// 全局设置状态栏和导航栏高度
statusBarH: 0,
customBarH: 0,
},
onLaunch: function() {
console.log('App Launch')
let token = uni.getStorageSync('token')
if(!token) {
uni.navigateTo({
url: '/pages/auth/login'
})
}
uni.getSystemInfo({
success: (e) => {
// 获取手机状态栏高度
let statusBar = e.statusBarHeight
let customBar
// #ifndef MP
customBar = statusBar + (e.platform == 'android' ? 50 : 45)
// #endif
// #ifdef MP-WEIXIN
// 获取胶囊按钮的布局位置信息
let menu = wx.getMenuButtonBoundingClientRect()
// 导航栏高度 = 胶囊下距离 + 胶囊上距离 - 状态栏高度
customBar = menu.bottom + menu.top - statusBar
// #endif
// #ifdef MP-ALIPAY
customBar = statusBar + e.titleBarHeight
// #endif
// 兼容nvue写法(H5/小程序/APP/APP-Nvue)
this.globalData.statusBarH = statusBar
this.globalData.customBarH = customBar
}
})
},
onShow: function() {
console.log('App Show')
},
onHide: function() {
console.log('App Hide')
}
}
</script>
uni-app全端自定义组件
项目中用到的顶部导航栏、底部菜单栏及弹窗均是自定义组件来实现功能。
<ua-popup v-model="isVisibleConfirm" shadeClose="false" title="标题" xclose z-index="1001"
content="<div style='color:#ff557f;padding:20px 40px;'>预测未来的最好办法是自己亲手创造未来!</div>"
:btns="[
{text: '取消', click: handleCancel},
{text: '确定', style: 'color:#00aa00;', click: handleOk},
]"
/>
<script>
export default {
methods: {
handleOk() {
let $ua = this.$refs.uapopup
$ua.open({
content: '人生漫漫,且行且珍惜',
customStyle: {'background-color': 'rgba(170, 0, 127, 0.6)', 'color': '#fff'},
time: 3,
onClose() {
$ua.open({
type: 'android',
content: '<div style="color:#aa007f">一切都将一去杳然,任何人都无法将其捕获。</div>',
customStyle: {'width': '210px'},
btns: [
{
text: '关闭',
click() {
$ua.close()
}
},
{
text: '确定',
style: 'color:#00aa00;',
click() {
// ...
}
}
]
})
}
})
}
}
}
</script>
uniapp直播/短视频
项目中短视频页面,整体分为顶部导航栏、底部菜单栏、中间视频区域。
<view v-if="currentTab == 2" class="ua__tabcnt-recommend">
<swiper class="ua__vdplayer-swiper flex1" :current="currentVideo" vertical @change="handleSwipeVertical">
<swiper-item v-for="(item, index) in videoList" :key="index">
<!-- 视频模块 -->
<view class="ua__vdplayer-video flex1">
<video class="vdplayer" :id="'vdplayer' + index" :ref="'vdplayer' + index"
:src="item.src"
:controls="false" :loop="true" :show-center-play-btn="false" object-fit="fill"
:autoplay="index == currentVideo"
@play="isPlaying=true" @timeupdate="handleTimeUpdate"
:style="{'width': winWidth, 'height': winHeight}"
>
</video>
<view class="ua__vdplayer-playwrap" @click="handleVideoClicked"><view v-if="!isPlaying" class="ua__vdplayer-playbtn"><text class="iconfont">{{`\ue607`}}</text></view></view>
</view>
<!-- 信息模块 -->
<view class="ua__vdplayer-info flexbox flex-col">
<view class="flexbox flex-row flex-alignb">
<!-- //左侧信息 -->
<view class="vdinfo__left flex1">
<view class="ltitem uavatar flexbox flex-row">
<navigator url="#" class="flexbox flex-alignc flex-row"><image class="uimg" :src="item.avatar" /><text class="uname">{{item.author}}</text></navigator>
<view class="flexbox btn" :class="{'actived': item.isFollow}" @click="handleFollow(index)"><text class="btn-text">{{item.isFollow ? '已关注' : '关注'}}</text></view>
</view>
<view v-if="item.topic" class="ltitem flexbox flex-row">
<view class="kw" v-for="(kw, index2) in item.topic" :key="index2"><text class="lbl">#{{kw}}</text></view>
</view>
<view class="ltitem"><text class="desc">{{item.desc}}</text></view>
</view>
<!-- //右侧按钮 -->
<view class="vdinfo__right flexbox flex-col">
<view class="rtitem ball" v-if="item.goods&&item.goods.length > 0" @click="handleShowGoodsPopup(item.goods)"><text class="icon iconfont">{{`\ue734`}}</text></view>
<view class="rtitem" :class="{'isliked': item.isLike}" @click="handleLiked(index)"><text class="icon iconfont">{{`\ue635`}}</text><text class="num">{{item.likeNum+(item.isLike ? 1 : 0)}}</text></view>
<view class="rtitem" @click="showReplyPopup = true"><text class="icon iconfont">{{`\ue632`}}</text><text class="num">{{item.replyNum}}</text></view>
<view class="rtitem" @click="showSharePopup = true"><text class="icon iconfont">{{`\ue63b`}}</text><text class="num">{{item.shareNum}}</text></view>
</view>
</view>
</view>
</swiper-item>
</swiper>
<!-- 底部播放进度条 -->
<view class="ua__vdplayer-progress"><view class="bar" :style="{'width': progressBar+'px'}"></view></view>
</view>
<script>
const app = getApp()
import videoJSON from '@/mock/videolist.js'
export default {
data() {
return {
// 导航栏高度
customBarHeight: app.globalData.customBarH,
navbarBgcolor: '#21252b',
tabbarBgcolor: '#21252b',
tabNavLs: [
{label: '附近动态', badge: 5, lists: []},
{label: '关注', lists: []},
{label: '推荐', dot: true, lists: []},
],
// 当前选项卡
currentTab: 0,
// 当前视频索引
currentVideo: 0,
// 视频数据
videoList: videoJSON,
// 视频是否播放中
isPlaying: false,
// 点击次数
clickNum: 0,
// 视频播放进度条
progressBar: 0,
clickTimer: null,
// 屏幕宽高
winWidth: '',
winHeight: '',
popupGoodsList: [],
showGoodsPopup: false,
showReplyPopup: false,
showSharePopup: false,
}
},
watch: {
currentTab(val) {
this.changeTabPanel(val)
}
},
computed:{
customBarMargin() {
return `margin-top: ${this.customBarHeight}px`
}
},
created() {
// 引入iconfont字体
// #ifdef APP-NVUE
const domModule = weex.requireModule('dom')
domModule.addRule('fontFace', {
fontFamily: "nvueIcon",
'src': "url('/static/fonts/iconfont.ttf')"
});
// #endif
let wW = uni.getSystemInfoSync().windowWidth
let wH = uni.getSystemInfoSync().windowHeight
this.winWidth = `${wW}px`
this.winHeight = `${wH}px`
},
methods: {
// 长按动态
handleDynamicMenu(e) {
let points
// #ifndef APP-NVUE
points = [e.touches[0].clientX, e.touches[0].clientY]
// #endif
// #ifdef APP-NVUE
points = [e.touches[0].screenX, e.touches[0].screenY]
// #endif
this.$refs.uapopup.open({
type: 'contextmenu',
follow: points,
btns: [
{text: '不感兴趣'},
{text: '复制'},
{
text: '举报',
style: 'color:#f00;',
click: () => {
this.$refs.uapopup.close()
}
},
],
})
},
/* ++++++++++ { 视频播放模块 } ++++++++++ */
getVideoCtx() {
// return this.$refs['vdplayer' + this.currentVideo][0]
return uni.createVideoContext('vdplayer'+ this.currentVideo, this)
},
// 垂直滑动视频
handleSwipeVertical(e) {
let index = e.detail.current
this.progressBar = 0
this.isPlaying = false
let video = this.getVideoCtx()
if(!video) return
video.pause()
// 重新开始
video.seek(0)
this.currentVideo = index
// 自动播放
this.handlePlay()
},
handlePlay() {
let video = this.getVideoCtx()
if(!video) return
video.play()
this.isPlaying = true
},
handlePause() {
let video = this.getVideoCtx()
if(!video) return
video.pause()
this.isPlaying = false
},
// 点击视频(单击/双击)
handleVideoClicked() {
this.clickTimer && clearTimeout(this.clickTimer)
this.clickNum++
this.clickTimer = setTimeout(() => {
if(this.clickNum >= 2) {
console.log('你双击了')
}else {
console.log('你单击了')
if(this.isPlaying) {
this.handlePause()
}else {
this.handlePlay()
}
}
this.clickNum = 0
}, 250)
},
...
}
}
</script>
okey,基于uni-app开发原生短视频/直播项目就分享到这里。
链接:https://juejin.cn/post/7010195797174124557/
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

uni-app跟rn和flutter没什么好比的,就比比quasar
uni-app的产品经理喜欢拿uni-app和rn以及flutter比较,社区里也有一篇:
https://ask.dcloud.net.cn/article/36083
其实对于开发者来讲根本是不同的东西。
对标rn的是weex,weex以前被阿里扔到apache没人管,现在好像又回到阿里那边维护了,但还是不上心,没有rn社区那种活跃。
对标flutter的是...目前还没有。
weex的不活跃也直接导致了uni-app的nvue案例或者nvue ui项目很少。
国外更接近uni-app的产品架构的是quasar framework。同样的基于vue,同样的自带ui框架。uni-app的优点就是国内各平台小程序支持,多了个weex编译,还有插件市场可以用,其他都落后。
quasar 在国内用的很少,如果要后端,后端用的是firebase插件,firebase同样没有对手,不过因为集成了一大堆谷歌服务,国内并不能用。
而且firebase这个理念和云计算的serverless又不太一样,等于是把所有的后端服务都集中优化了,和分开的云函数,云数据库,云存储比更省心。而且现在阿里云这边这三个云产品相互访问还都是通过外网,速度慢,动不动就报错。腾讯云这边仗着小程序的流量还急着收费。
uni-app良心的地方挺良心,腾讯云,阿里云的资源没怎么加价卖,也是为数不多的国内开源项目的小公司。不过uni-ad广告那一层网上有传闻是抽成而且不公开的,自己调用广告联盟会被报非法应用。这个问题吧,我觉得dcloud愿意公开讲清楚我别的都给你免费用,就广告里面抽多少%,我觉得也没什么大不了的,就像epic的虚幻引擎那样。不然刚开始我都怀疑dcloud到底该怎么样变现养活自己的。
国内有另外一家开源的小公司叫edusoho,专做教育培训的开源项目,他们的盈利方式是打包云服务卖,当然云服务相关的源代码是不开源的。现在也好好活着,而且依旧开源,我觉的这个比起暗暗的抽广告费要合理一些,既然都是阿里云,腾讯云的下游,自己的产品在集成的云服务上加点钱卖。
uni-app的产品经理喜欢拿uni-app和rn以及flutter比较,社区里也有一篇:
https://ask.dcloud.net.cn/article/36083
其实对于开发者来讲根本是不同的东西。
对标rn的是weex,weex以前被阿里扔到apache没人管,现在好像又回到阿里那边维护了,但还是不上心,没有rn社区那种活跃。
对标flutter的是...目前还没有。
weex的不活跃也直接导致了uni-app的nvue案例或者nvue ui项目很少。
国外更接近uni-app的产品架构的是quasar framework。同样的基于vue,同样的自带ui框架。uni-app的优点就是国内各平台小程序支持,多了个weex编译,还有插件市场可以用,其他都落后。
quasar 在国内用的很少,如果要后端,后端用的是firebase插件,firebase同样没有对手,不过因为集成了一大堆谷歌服务,国内并不能用。
而且firebase这个理念和云计算的serverless又不太一样,等于是把所有的后端服务都集中优化了,和分开的云函数,云数据库,云存储比更省心。而且现在阿里云这边这三个云产品相互访问还都是通过外网,速度慢,动不动就报错。腾讯云这边仗着小程序的流量还急着收费。
uni-app良心的地方挺良心,腾讯云,阿里云的资源没怎么加价卖,也是为数不多的国内开源项目的小公司。不过uni-ad广告那一层网上有传闻是抽成而且不公开的,自己调用广告联盟会被报非法应用。这个问题吧,我觉得dcloud愿意公开讲清楚我别的都给你免费用,就广告里面抽多少%,我觉得也没什么大不了的,就像epic的虚幻引擎那样。不然刚开始我都怀疑dcloud到底该怎么样变现养活自己的。
国内有另外一家开源的小公司叫edusoho,专做教育培训的开源项目,他们的盈利方式是打包云服务卖,当然云服务相关的源代码是不开源的。现在也好好活着,而且依旧开源,我觉的这个比起暗暗的抽广告费要合理一些,既然都是阿里云,腾讯云的下游,自己的产品在集成的云服务上加点钱卖。
收起阅读 »
nvue遇到text是块元素,解决文字嵌套问题
<!-- nvue不支持span 解决方案 -->
<view class='reply'>
<view class='reply-detail'>
<text class="reply-detail-username" v-for="i in '刘芸'">{{i}}</text>
<text class="reply-detail-content" v-for='i in ":"'>{{i+' '}}</text>
<text class="reply-detail-content" v-for='it in "都说JAVA开发很赚钱, 我苦心专研了两年,其中参加了不少的培训。"'>{{it}}</text>
</view>
</view>
<style lang='scss'>
.reply {
width: 595upx;
min-height: 75upx;
margin: 15upx 30upx 0upx 124upx;
padding: 22upx 24upx;
background-color: #F4F4F4;
border-radius: 4upx;
}
.reply-detail {
font-size: 29upx;
line-height: 48upx;
display: flex;
flex-direction: row;
color: #343434;
flex-wrap: wrap;
>.reply-detail-username{
color: #2199D1;
}
>.reply-detail-content{}
}
</style>
完美解决nvue中的文字无法嵌套

<!-- nvue不支持span 解决方案 -->
<view class='reply'>
<view class='reply-detail'>
<text class="reply-detail-username" v-for="i in '刘芸'">{{i}}</text>
<text class="reply-detail-content" v-for='i in ":"'>{{i+' '}}</text>
<text class="reply-detail-content" v-for='it in "都说JAVA开发很赚钱, 我苦心专研了两年,其中参加了不少的培训。"'>{{it}}</text>
</view>
</view>
<style lang='scss'>
.reply {
width: 595upx;
min-height: 75upx;
margin: 15upx 30upx 0upx 124upx;
padding: 22upx 24upx;
background-color: #F4F4F4;
border-radius: 4upx;
}
.reply-detail {
font-size: 29upx;
line-height: 48upx;
display: flex;
flex-direction: row;
color: #343434;
flex-wrap: wrap;
>.reply-detail-username{
color: #2199D1;
}
>.reply-detail-content{}
}
</style>
完美解决nvue中的文字无法嵌套

引用超级全局组件方案
vue-inset-loader
编译阶段在sfc模板指定位置插入自定义内容,适用于webpack构建的vue应用,常用于小程序需要全局引入组件的场景。(由于小程序没有开放根标签,没有办法在根标签下追加全局标签,所以要使用组件必须在当前页面引入组件标签)
github:https://github.com/1977474741/vue-inset-loader
用得上顺便点个star
第一步 安装
npm install vue-inset-loader --save-dev
第二步 vue.config.js注入loader
module: {
rules: [
{
test: /\.vue$/,
use:{
loader: "vue-inset-loader"
// // 针对Hbuilder工具创建的uni-app项目
// loader: path.resolve(__dirname,"./node_modules/vue-inset-loader")
}
}
]
},
// 支持自定义pages.json文件路径
// options: {
// pagesPath: path.resolve(__dirname,'./src/pages.json')
// }
第三步 pages.json配置文件中添加insetLoader
"insetLoader": {
"config":{
"confirm": "<BaseConfirm ref='confirm'></BaseConfirm>",
"abc": "<BaseAbc ref='BaseAbc'></BaseAbc>"
},
// 全局配置
"label":["confirm"],
"rootEle":"div"
},
"pages": [
{
"path": "pages/tabbar/index/index",
"style": {
"navigationBarTitleText": "测试页面",
// 单独配置,用法跟全局配置一致,优先级高于全局
"label": ["confirm","abc"],
"rootEle":"div"
}
},
]
配置说明
config
(default:{}
)
定义标签名称和内容的键值对label
(default:[]
)
需要全局引入的标签,打包后会在所有页面引入此标签-
rootEle
(default:"div"
)
根元素的标签类型,缺省值为div,支持正则,比如匹配任意标签 ".*"✔
label
和rootEle
支持在单独页面的style里配置,优先级高于全局配置
vue-inset-loader
编译阶段在sfc模板指定位置插入自定义内容,适用于webpack构建的vue应用,常用于小程序需要全局引入组件的场景。(由于小程序没有开放根标签,没有办法在根标签下追加全局标签,所以要使用组件必须在当前页面引入组件标签)
github:https://github.com/1977474741/vue-inset-loader
用得上顺便点个star
第一步 安装
npm install vue-inset-loader --save-dev
第二步 vue.config.js注入loader
module: {
rules: [
{
test: /\.vue$/,
use:{
loader: "vue-inset-loader"
// // 针对Hbuilder工具创建的uni-app项目
// loader: path.resolve(__dirname,"./node_modules/vue-inset-loader")
}
}
]
},
// 支持自定义pages.json文件路径
// options: {
// pagesPath: path.resolve(__dirname,'./src/pages.json')
// }
第三步 pages.json配置文件中添加insetLoader
"insetLoader": {
"config":{
"confirm": "<BaseConfirm ref='confirm'></BaseConfirm>",
"abc": "<BaseAbc ref='BaseAbc'></BaseAbc>"
},
// 全局配置
"label":["confirm"],
"rootEle":"div"
},
"pages": [
{
"path": "pages/tabbar/index/index",
"style": {
"navigationBarTitleText": "测试页面",
// 单独配置,用法跟全局配置一致,优先级高于全局
"label": ["confirm","abc"],
"rootEle":"div"
}
},
]
配置说明
config
(default:{}
)
定义标签名称和内容的键值对label
(default:[]
)
需要全局引入的标签,打包后会在所有页面引入此标签-
rootEle
(default:"div"
)
根元素的标签类型,缺省值为div,支持正则,比如匹配任意标签 ".*"✔
label
和rootEle
支持在单独页面的style里配置,优先级高于全局配置

uniapp开发抖音团购模板webpack插件
uniapp-to-group
uniapp-to-group
是 uni-app
开发字节小程序的时候,使用抖音团购模板的 webpack
转换插件
HBuilder X
同样适用,编辑器需要在 3.2.3.20210825 及以上
示例
HBuilder X
在项目跟目录添加一个 vue.config.js
文件
npm i uniapp-to-group -D
or
yarn add uniapp-to-group -D
// vue.config.js
const UniappToGroup = require('uniapp-to-group')
module.exports = {
configureWebpack: {
plugins: [
new UniappToGroup({
// 对应 package.json 中引入插件步骤
package: {
'ttPlugins': {
'dependencies': {
'poi-group-buy-plugin': {
'version': '1.0.0',
'isDynamic': true
}
}
}
},
app: {
'pages': [
// 手动配置商品插件中的商品详情页面
'ext://poi-group-buy-plugin/detail'
],
'fallbackPluginPages': {
// 这里的 key 是小程序原来的商品详情页(下面只是示意),value 可以就用这个值
'product/detail/index': 'ext://poi-group-buy-plugin/detail'
}
}
})
]
}
}
uniapp-to-group
uniapp-to-group
是 uni-app
开发字节小程序的时候,使用抖音团购模板的 webpack
转换插件
HBuilder X
同样适用,编辑器需要在 3.2.3.20210825 及以上
示例
HBuilder X
在项目跟目录添加一个 vue.config.js
文件
npm i uniapp-to-group -D
or
yarn add uniapp-to-group -D
// vue.config.js
const UniappToGroup = require('uniapp-to-group')
module.exports = {
configureWebpack: {
plugins: [
new UniappToGroup({
// 对应 package.json 中引入插件步骤
package: {
'ttPlugins': {
'dependencies': {
'poi-group-buy-plugin': {
'version': '1.0.0',
'isDynamic': true
}
}
}
},
app: {
'pages': [
// 手动配置商品插件中的商品详情页面
'ext://poi-group-buy-plugin/detail'
],
'fallbackPluginPages': {
// 这里的 key 是小程序原来的商品详情页(下面只是示意),value 可以就用这个值
'product/detail/index': 'ext://poi-group-buy-plugin/detail'
}
}
})
]
}
}
收起阅读 »

uni-app 应用换肤功能
应用换肤这个 “毫无卵用” 又浪费时间的的功能却是许多产品经理们喜欢玩的(某信国民应用都不做的功能)。
首先我坦白,我TM就从没用应用换皮这种功能,其次我 氪金 跟有没有换皮这个功能毫无关系,真是受够了这种沙雕产品,虽然我在产品经理提出这个需求之前已经解决了产品经理本人。但是在这里先解救一下正在受难中的卑微的程序员鄙视链底端的 web前端。
实现方式:CSS变量 + Vuex
具体的源码下载请前往我的 uni-app 插件主页下载
源码地址:https://ext.dcloud.net.cn/plugin?id=6215
在线体验:https://mydarling.gitee.io/uniapp-extend
效果预览
扫码预览 H5 版
我的博客原文:https://blog.csdn.net/u013350495/article/details/120343941
应用换肤这个 “毫无卵用” 又浪费时间的的功能却是许多产品经理们喜欢玩的(某信国民应用都不做的功能)。
首先我坦白,我TM就从没用应用换皮这种功能,其次我 氪金 跟有没有换皮这个功能毫无关系,真是受够了这种沙雕产品,虽然我在产品经理提出这个需求之前已经解决了产品经理本人。但是在这里先解救一下正在受难中的卑微的程序员鄙视链底端的 web前端。
实现方式:CSS变量 + Vuex
具体的源码下载请前往我的 uni-app 插件主页下载
源码地址:https://ext.dcloud.net.cn/plugin?id=6215
在线体验:https://mydarling.gitee.io/uniapp-extend
效果预览
扫码预览 H5 版
我的博客原文:https://blog.csdn.net/u013350495/article/details/120343941
收起阅读 »