HBuilderX

HBuilderX

极客开发工具
uni-app

uni-app

开发一次,多端覆盖
uniCloud

uniCloud

云开发平台
HTML5+

HTML5+

增强HTML5的功能体验
MUI

MUI

上万Star的前端框架

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

收起阅读 »

删除

外包

删除

删除

uni-app跟rn和flutter没什么好比的,就比比quasar

DCloud uniapp

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,专做教育培训的开源项目,他们的盈利方式是打包云服务卖,当然云服务相关的源代码是不开源的。现在也好好活着,而且依旧开源,我觉的这个比起暗暗的抽广告费要合理一些,既然都是阿里云,腾讯云的下游,自己的产品在集成的云服务上加点钱卖。

收起阅读 »

快手支付 担保支付

快手小程序

用的还是微信H5支付

用的还是微信H5支付

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+'&nbsp;'}}</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+'&nbsp;'}}</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,支持正则,比如匹配任意标签 ".*"

    labelrootEle 支持在单独页面的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,支持正则,比如匹配任意标签 ".*"

    labelrootEle 支持在单独页面的style里配置,优先级高于全局配置

收起阅读 »

uniapp开发抖音团购模板webpack插件

uniapp

uniapp-to-group

uniapp-to-groupuni-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-groupuni-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

收起阅读 »

小说app前后端源码分享

前端:uniapp
首页缓存、图片缓存、收藏缓存、阅读记录缓存;
后端:java springboot
redis缓存、knife4j(Swagger UI升级版)接口预览、webmagic小说数据爬取、支持TXT全本导入
管理系统:
vue2 全家桶框架管理系统

预览:

继续阅读 »

前端:uniapp
首页缓存、图片缓存、收藏缓存、阅读记录缓存;
后端:java springboot
redis缓存、knife4j(Swagger UI升级版)接口预览、webmagic小说数据爬取、支持TXT全本导入
管理系统:
vue2 全家桶框架管理系统

预览:

收起阅读 »

【简单原理】满足swiper组件的高度自适应,随内容<图片>高度变化而变化

swiper

项目中遇到,研究一下,特此记录,不喜勿喷!

此方法适用于swiper图片宽度铺满屏幕,可根据需求自定义调整
原理:获取图片高度,根据图片宽高比例,取得swiper在宽度100vw情况下的高度,实现swiper高度根据内容图片高度适应

template:


...  
<swiper class="swiper"  :style="`height:${swiperheight}`">  
<swiper-item class="swiperitem" v-for="(item,index) in imgList" :key="index" >  
                </swiper-item>  
             </swiper>   
...  

js

export default {  
        data() {  
            return {  
                swiperheight:"0px",  
                imgList:[...]  

            }  
        },  
        onLoad() {  
            this.getswiperImageInfo()  
        },  
        methods: {  
            getswiperImageInfo(src){  
              uni.getImageInfo({  
                src: this.imgList[0].img,  
                success: (res) => {  
                  console.log(res)  
                  let endwidth = res.width/res.height  
                  this.swiperheight = 100/endwidth+"vw"  
                },  
                fail: (err) => {  
                  console.log(err)  
                }  
              });  
             }  

        }  
    }

css

.swiper{  
        width: 100vw;  
    }
继续阅读 »

项目中遇到,研究一下,特此记录,不喜勿喷!

此方法适用于swiper图片宽度铺满屏幕,可根据需求自定义调整
原理:获取图片高度,根据图片宽高比例,取得swiper在宽度100vw情况下的高度,实现swiper高度根据内容图片高度适应

template:


...  
<swiper class="swiper"  :style="`height:${swiperheight}`">  
<swiper-item class="swiperitem" v-for="(item,index) in imgList" :key="index" >  
                </swiper-item>  
             </swiper>   
...  

js

export default {  
        data() {  
            return {  
                swiperheight:"0px",  
                imgList:[...]  

            }  
        },  
        onLoad() {  
            this.getswiperImageInfo()  
        },  
        methods: {  
            getswiperImageInfo(src){  
              uni.getImageInfo({  
                src: this.imgList[0].img,  
                success: (res) => {  
                  console.log(res)  
                  let endwidth = res.width/res.height  
                  this.swiperheight = 100/endwidth+"vw"  
                },  
                fail: (err) => {  
                  console.log(err)  
                }  
              });  
             }  

        }  
    }

css

.swiper{  
        width: 100vw;  
    }
收起阅读 »

新安装hbuilder内置浏览器运行默认demo报错 [Vue warn]: Unknown custom element: <App>

HBuilderX

最近在一个电脑上新安装了hbuilderX,下面是问题表现

  • 新建了一个demo,点击内置浏览器运行就直接报错
    [Vue warn]: Unknown custom element: <App> - did you register the component correctly? For recursive components, make sure to provide the "name" option.

  • . 然后选择运行在微信开发者工具也会报错:
    ERROR TypeError: Cannot read property 'id' of undefined
    00:01:20.814 TypeError: Cannot read property 'id' of undefined
    00:01:20.818 at generateComponent (C:\Users\Administrator\Desktop\HBuilderX\plugins\uniapp-cli\node_modules\@dcloudio\webpack-uni-mp-loader\lib\plugin\generate-component.js:71:110)

  • 选择运行在真机上就会报无法独立运行此app的错误。

然后就非常奇怪,网上搜了很多例子,看到有一些人也跟我出现了同样的问题,但是都没有解决。新建任何demo运行都是这个错误,并没有人能够解决成功。
后来我试了各种方式都不行。这个demo也能够在正确的位置找到,这就奇了怪了。

试了各种网上说的奇怪的方法之后都没有效果,但是并没有放弃,我又新建了一个官方demo,突发奇想的把新建demo时候选择project的位置改了一下,改到了桌面上,这次一运行竟然神奇的成功了。

我个人推测可能是跟我使用过360的c盘搬家有关系,因为后来我看到hbuilder里面打印的log有360的某某路径指向了360的c盘搬家的位置(这个位置里面有Administrator的那个文件夹用来放用户文件的,而hbuilder默认创建项目的文件夹名字就是这个名字)。可能就是这样子,导致hbuilder运行项目的时候被指向了一个奇怪的路径,导致运行失败了。

继续阅读 »

最近在一个电脑上新安装了hbuilderX,下面是问题表现

  • 新建了一个demo,点击内置浏览器运行就直接报错
    [Vue warn]: Unknown custom element: <App> - did you register the component correctly? For recursive components, make sure to provide the "name" option.

  • . 然后选择运行在微信开发者工具也会报错:
    ERROR TypeError: Cannot read property 'id' of undefined
    00:01:20.814 TypeError: Cannot read property 'id' of undefined
    00:01:20.818 at generateComponent (C:\Users\Administrator\Desktop\HBuilderX\plugins\uniapp-cli\node_modules\@dcloudio\webpack-uni-mp-loader\lib\plugin\generate-component.js:71:110)

  • 选择运行在真机上就会报无法独立运行此app的错误。

然后就非常奇怪,网上搜了很多例子,看到有一些人也跟我出现了同样的问题,但是都没有解决。新建任何demo运行都是这个错误,并没有人能够解决成功。
后来我试了各种方式都不行。这个demo也能够在正确的位置找到,这就奇了怪了。

试了各种网上说的奇怪的方法之后都没有效果,但是并没有放弃,我又新建了一个官方demo,突发奇想的把新建demo时候选择project的位置改了一下,改到了桌面上,这次一运行竟然神奇的成功了。

我个人推测可能是跟我使用过360的c盘搬家有关系,因为后来我看到hbuilder里面打印的log有360的某某路径指向了360的c盘搬家的位置(这个位置里面有Administrator的那个文件夹用来放用户文件的,而hbuilder默认创建项目的文件夹名字就是这个名字)。可能就是这样子,导致hbuilder运行项目的时候被指向了一个奇怪的路径,导致运行失败了。

收起阅读 »

Facebook登录开通指南

登录 uni_app项目 facebook

开通条件

1.海外网络环境
2.Facebook账号(Facebook登录页面)

创建应用

1.打开Facebook开发者中心

2.点击右上角"我的应用"

3.进入应用管理界面,点击"创建应用"

4.根据需要选择应用产品的类型(应用类型详见"详细了解应用类型"),然后点击继续

5.填写应用信息

6.创建完成后即可获取应用的应用编号(即appID)

7.为应用添加登录功能

设置登录-iOS

1.我的应用--设置--基本,选择添加平台,选择iOS

2.填写信息保存即可

设置登录-Android

我的应用--设置--基本,选择添加平台

选择android平台,应用商店选择Google Play

填写必要的包名和散列信息,类名是固定的。如图

散列的获取方法,参考文档:
https://developers.facebook.com/docs/facebook-login/android 第六小节

如果获取到的散列位数不对,需要找台linux/mac 计算机。
使用下面的命令获取
keytool -exportcert -alias hbuilder -keystore ./HBuilder.keystore | openssl dgst -sha1 -binary | openssl base64

应用权限

使用Facebook登录需开启"public_profile"以及"email"的访问权限
点击"应用审核"-"权限和功能",开启"public_profile"以及"email"的高级访问权限

继续阅读 »

开通条件

1.海外网络环境
2.Facebook账号(Facebook登录页面)

创建应用

1.打开Facebook开发者中心

2.点击右上角"我的应用"

3.进入应用管理界面,点击"创建应用"

4.根据需要选择应用产品的类型(应用类型详见"详细了解应用类型"),然后点击继续

5.填写应用信息

6.创建完成后即可获取应用的应用编号(即appID)

7.为应用添加登录功能

设置登录-iOS

1.我的应用--设置--基本,选择添加平台,选择iOS

2.填写信息保存即可

设置登录-Android

我的应用--设置--基本,选择添加平台

选择android平台,应用商店选择Google Play

填写必要的包名和散列信息,类名是固定的。如图

散列的获取方法,参考文档:
https://developers.facebook.com/docs/facebook-login/android 第六小节

如果获取到的散列位数不对,需要找台linux/mac 计算机。
使用下面的命令获取
keytool -exportcert -alias hbuilder -keystore ./HBuilder.keystore | openssl dgst -sha1 -binary | openssl base64

应用权限

使用Facebook登录需开启"public_profile"以及"email"的访问权限
点击"应用审核"-"权限和功能",开启"public_profile"以及"email"的高级访问权限

收起阅读 »