
项目简要 uniapp 框架会员移动网页客户端
项目简要
uniapp框架会员移动网页客户端
工作要求
- 增加功能, 优化和长期维护.
- 迁移 uniapp vue2 到 vue3.
开发者要求:
全职独立开发者, 谢绝团队.
至少2年 uniapp 项目开发经验.
至少1年 typescript 项目开发经验.
熟悉或使用过 thorui 优先.
有 迁移 uniapp vue2 到 vue3 的项目经验优先
使用过Gitflow
具备英文读写能力.
三,待遇:
- 基本底薪,月结 (考虑到接任何项目如果想提升代码质量,都需要熟悉,需要能优化,模块化代码)
- 底薪之外,按页面和功能付钱,多劳多得.
项目简要
uniapp框架会员移动网页客户端
工作要求
- 增加功能, 优化和长期维护.
- 迁移 uniapp vue2 到 vue3.
开发者要求:
全职独立开发者, 谢绝团队.
至少2年 uniapp 项目开发经验.
至少1年 typescript 项目开发经验.
熟悉或使用过 thorui 优先.
有 迁移 uniapp vue2 到 vue3 的项目经验优先
使用过Gitflow
具备英文读写能力.
三,待遇:
- 基本底薪,月结 (考虑到接任何项目如果想提升代码质量,都需要熟悉,需要能优化,模块化代码)
- 底薪之外,按页面和功能付钱,多劳多得.

uniapp框架 外包 | 长期开发和维护项目1
项目简要
项目是跨小程序, H5, APP, CRM APP 包括推广营销和独立外卖.
开发者要求:
- 全职独立开发者, 谢绝团队.
- 有 uniapp 项目开发经验
- 熟悉 Git 和 Gitflow
- 拥有较强的学习能力,良好的沟通与理解能力及逻辑思维能力
5.l联系方式微信18872319732 邮箱xuqu911@126.com
项目简要
项目是跨小程序, H5, APP, CRM APP 包括推广营销和独立外卖.
开发者要求:
- 全职独立开发者, 谢绝团队.
- 有 uniapp 项目开发经验
- 熟悉 Git 和 Gitflow
- 拥有较强的学习能力,良好的沟通与理解能力及逻辑思维能力
5.l联系方式微信18872319732 邮箱xuqu911@126.com

uni-app npm update 更新项目后报错
uni-app npm update 更新项目后报错 internal/modules/cjs/loader.js:596
并:throw err:
Cannot find module '@dcloudio/uni-cli-i18n';
巴拉巴拉等
直接npm下载对应缺失的包就行了
npm i @dcloudio/uni-cli-i18n
再重新运行就可以了
uni-app npm update 更新项目后报错 internal/modules/cjs/loader.js:596
并:throw err:
Cannot find module '@dcloudio/uni-cli-i18n';
巴拉巴拉等
直接npm下载对应缺失的包就行了
npm i @dcloudio/uni-cli-i18n
再重新运行就可以了

从评论页面返回后实时刷新评论总数的实现
场景:从列表页点击评论按钮,进入评论页面,评论后返回列表页,希望被点击的列表项评论总数实时更新。
实现方法:
列表页template 中伪代码如下:
<view v-for(省略)>
<view @click="to_comment(item.post_id, item.id, index)">发表评论</view>
</view>
onShow 中:
let index = uni.getStorageSync('index');
let comment_count = uni.getStorageSync('comment_count');
if (this.record_list.length != 0) {
this.record_list[index].comment_count = comment_count;
}
methods中:
to_comment(post_id, o_user_id, index){
uni.setStorageSync('index', index);
uni.navigateTo({
url: '../comment/comment?post_id=' + post_id + '&o_user_id=' + o_user_id
})
},
评论页面:
onLoad (){
this.get_comment_list()
},
methods: {
get_comment_list() {
uni.setStorageSync('comment_count', this.comment_list.comment_count) //当不评论时,将评论总数缓存
},
comment(){
uni.setStorageSync('comment_count', this.comment_list.comment_count) //如果有评论,更新缓存
}
}
效果:可实现评论总数的实时更新,并且不用全页面刷新
场景:从列表页点击评论按钮,进入评论页面,评论后返回列表页,希望被点击的列表项评论总数实时更新。
实现方法:
列表页template 中伪代码如下:
<view v-for(省略)>
<view @click="to_comment(item.post_id, item.id, index)">发表评论</view>
</view>
onShow 中:
let index = uni.getStorageSync('index');
let comment_count = uni.getStorageSync('comment_count');
if (this.record_list.length != 0) {
this.record_list[index].comment_count = comment_count;
}
methods中:
to_comment(post_id, o_user_id, index){
uni.setStorageSync('index', index);
uni.navigateTo({
url: '../comment/comment?post_id=' + post_id + '&o_user_id=' + o_user_id
})
},
评论页面:
onLoad (){
this.get_comment_list()
},
methods: {
get_comment_list() {
uni.setStorageSync('comment_count', this.comment_list.comment_count) //当不评论时,将评论总数缓存
},
comment(){
uni.setStorageSync('comment_count', this.comment_list.comment_count) //如果有评论,更新缓存
}
}
效果:可实现评论总数的实时更新,并且不用全页面刷新
收起阅读 »
不能更新最新版本3.2.9,更新后小程序模拟器的手机语言变成英文,导致组件国际化也变成英文,回退版本才好了
不能更新最新版本3.2.9,更新后小程序模拟器的手机语言变成英文,导致组件国际化也变成英文,回退版本才好了
不能更新最新版本3.2.9,更新后小程序模拟器的手机语言变成英文,导致组件国际化也变成英文,回退版本才好了

基于uniapp开发的小说阅读器 前后端源码
前端:uniapp
首页缓存、图片缓存、收藏缓存、阅读记录、阅读历史缓存;
后端:java springboot
redis缓存、knife4j(Swagger UI升级版)接口预览、webmagic小说数据爬取、支持TXT全本导入
管理系统:
vue2 全家桶框架管理系统
所有源码和数据库: ¥800
加我V: zwb584223358 发邮箱号
前端:uniapp
首页缓存、图片缓存、收藏缓存、阅读记录、阅读历史缓存;
后端:java springboot
redis缓存、knife4j(Swagger UI升级版)接口预览、webmagic小说数据爬取、支持TXT全本导入
管理系统:
vue2 全家桶框架管理系统
所有源码和数据库: ¥800
加我V: zwb584223358 发邮箱号
收起阅读 »

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/
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。