HBuilderX

HBuilderX

极客开发工具
uni-app

uni-app

开发一次,多端覆盖
uniCloud

uniCloud

云开发平台
HTML5+

HTML5+

增强HTML5的功能体验
MUI

MUI

上万Star的前端框架

process.env.TZ = 'Asia/Shanghai'在云函数里无法成功设置

所以涉及到时间的转换,最好在前端,云端0时区太蛋疼了

所以涉及到时间的转换,最好在前端,云端0时区太蛋疼了

项目简要 uniapp 框架会员移动网页客户端

外包

项目简要
uniapp框架会员移动网页客户端

工作要求

  1. 增加功能, 优化和长期维护.
  2. 迁移 uniapp vue2 到 vue3.

开发者要求:
全职独立开发者, 谢绝团队.
至少2年 uniapp 项目开发经验.
至少1年 typescript 项目开发经验.
熟悉或使用过 thorui 优先.
有 迁移 uniapp vue2 到 vue3 的项目经验优先
使用过Gitflow
具备英文读写能力.

三,待遇:

  1. 基本底薪,月结 (考虑到接任何项目如果想提升代码质量,都需要熟悉,需要能优化,模块化代码)
  2. 底薪之外,按页面和功能付钱,多劳多得.
继续阅读 »

项目简要
uniapp框架会员移动网页客户端

工作要求

  1. 增加功能, 优化和长期维护.
  2. 迁移 uniapp vue2 到 vue3.

开发者要求:
全职独立开发者, 谢绝团队.
至少2年 uniapp 项目开发经验.
至少1年 typescript 项目开发经验.
熟悉或使用过 thorui 优先.
有 迁移 uniapp vue2 到 vue3 的项目经验优先
使用过Gitflow
具备英文读写能力.

三,待遇:

  1. 基本底薪,月结 (考虑到接任何项目如果想提升代码质量,都需要熟悉,需要能优化,模块化代码)
  2. 底薪之外,按页面和功能付钱,多劳多得.
收起阅读 »

uniapp框架 外包 | 长期开发和维护项目1

项目简要
项目是跨小程序, H5, APP, CRM APP 包括推广营销和独立外卖.

开发者要求:

  1. 全职独立开发者, 谢绝团队.
  2. 有 uniapp 项目开发经验
  3. 熟悉 Git 和 Gitflow
  4. 拥有较强的学习能力,良好的沟通与理解能力及逻辑思维能力
    5.l联系方式微信18872319732 邮箱xuqu911@126.com
继续阅读 »

项目简要
项目是跨小程序, H5, APP, CRM APP 包括推广营销和独立外卖.

开发者要求:

  1. 全职独立开发者, 谢绝团队.
  2. 有 uniapp 项目开发经验
  3. 熟悉 Git 和 Gitflow
  4. 拥有较强的学习能力,良好的沟通与理解能力及逻辑思维能力
    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
再重新运行就可以了

收起阅读 »

从评论页面返回后实时刷新评论总数的实现

返回刷新 评论 uniapp

场景:从列表页点击评论按钮,进入评论页面,评论后返回列表页,希望被点击的列表项评论总数实时更新。

实现方法:

列表页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开发的小说阅读器 前后端源码

java 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 服务端渲染 SEO

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做了深度集成,你可以按照如下步骤快速上手:

步骤一:调整代码适配服务端运行环境

  1. 生命周期:uni-app的生命周期钩子函数中,页面onLoad、组件beforeCreatecreated 会在服务器端渲染 (SSR) 过程中被调用,你需要检查原项目代码中获取数据的时机;
  2. 特定平台API:若直接使用了如 windowdocument,这类仅浏览器支持的全局变量,则会在云端 Node.js 中执行时抛出错误;
  3. 数据预取:<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在背后做了大量工作,包括:

  1. 编译uni-app项目,分别生成Server BundleClient Bundle
  2. Client Bundle上传到uniCloud前端网页托管中
  3. 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做了深度集成,你可以按照如下步骤快速上手:

步骤一:调整代码适配服务端运行环境

  1. 生命周期:uni-app的生命周期钩子函数中,页面onLoad、组件beforeCreatecreated 会在服务器端渲染 (SSR) 过程中被调用,你需要检查原项目代码中获取数据的时机;
  2. 特定平台API:若直接使用了如 windowdocument,这类仅浏览器支持的全局变量,则会在云端 Node.js 中执行时抛出错误;
  3. 数据预取:<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在背后做了大量工作,包括:

  1. 编译uni-app项目,分别生成Server BundleClient Bundle
  2. Client Bundle上传到uniCloud前端网页托管中
  3. 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

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直播/短视频

uni_app项目 uniapp插件 uniapp

之前有给大家分享一个electron-webosx仿MAC桌面管理系统。今天分享的是全端uniapp小视频/直播实例项目。

uniapp-ttLive短视频 一款基于uni-app+nvue+uview-ui等技术开发的多功能跨端直播+短视频项目。流畅的上下滑动体验、支持全景式悬浮沉浸效果。可暂停/播放小视频、直播送礼物、聊天互动等功能。

vue3.x+electron13+vite2模仿macui桌面管理框架

img

使用技术

  • 编码器/技术:HbuilderX3.1.21+Uniapp+Nvue+Vuex+Uapopup
  • UI组件库:uView-ui / uni-ui
  • 矢量图标库:iconfont字体图标
  • 弹窗组件:UApopup 基于uni-app封装跨端弹窗组件
  • 自定义导航条+底部菜单栏
  • 编译支持:H5+小程序+APP端

img

基于 HbuilderX3.1.21 编码器开发,搭配nvue原生页面解决video层级过高问题,自定义 ua-navbar ua-tabbar ua-popup 组件全面支持Nvue页面。搭配uview-ui和uni-ui组件库。可编译至h5+小程序+APP端。

img

项目结构目录

img

img

img

img

img

img

img

img

img

img

img

img

img

img

img

img

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全端自定义组件

img

img

项目中用到的顶部导航栏、底部菜单栏及弹窗均是自定义组件来实现功能。

<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自定义组件之导航栏+菜单栏

uniapp全端弹窗插件|uni-app模态弹框

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桌面管理框架

img

使用技术

  • 编码器/技术:HbuilderX3.1.21+Uniapp+Nvue+Vuex+Uapopup
  • UI组件库:uView-ui / uni-ui
  • 矢量图标库:iconfont字体图标
  • 弹窗组件:UApopup 基于uni-app封装跨端弹窗组件
  • 自定义导航条+底部菜单栏
  • 编译支持:H5+小程序+APP端

img

基于 HbuilderX3.1.21 编码器开发,搭配nvue原生页面解决video层级过高问题,自定义 ua-navbar ua-tabbar ua-popup 组件全面支持Nvue页面。搭配uview-ui和uni-ui组件库。可编译至h5+小程序+APP端。

img

项目结构目录

img

img

img

img

img

img

img

img

img

img

img

img

img

img

img

img

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全端自定义组件

img

img

项目中用到的顶部导航栏、底部菜单栏及弹窗均是自定义组件来实现功能。

<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自定义组件之导航栏+菜单栏

uniapp全端弹窗插件|uni-app模态弹框

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

收起阅读 »