加载中...

uniapp-welive仿微信/抖音直播带货|uni-app+vue3+pinia短视频直播商城

uniapp-welive仿微信/抖音直播带货|uni-app+vue3+pinia短视频直播商城

基于uniapp+vue3+uv-ui跨端H5+小程序+App短视频+直播带货商城Uniapp-WeLive

uni-welive一款全新基于uniapp+vue3+pinia+vk-uview等技术跨端仿制抖音/微信直播带货商城项目。支持沉浸式全屏上下滑动短视频直播,Nvue多视频层级覆盖,支持编译到兼容H5+小程序+App端

预览效果

编译H5+小程序+App端效果如下

New Image

使用技术

  • 编辑器:HbuilderX 3.98
  • 框架技术:Uniapp+Vue3+Vite4+Nvue+Pinia
  • UI组件库:uv-ui+vk-uview
  • 弹框组件:uaPopup(uniapp封装多端弹框组件)
  • 自定义组件:uaNavbar+uaTabbar组件
  • 本地缓存:pinia-plugin-unistorage
  • 编译支持:H5+小程序+APP端

New Image

大家如果对uniapp+vue3搭建跨端项目感兴趣,可以去看看之前的分享文章。

https://www.cnblogs.com/xiaoyan2017/p/17487018.html

New Image

uniapp-welive拥有丝滑般滑动体验,全屏沉浸式tabbar切换,微型迷你播放时间进度条等功能。

New Image

New Image

项目结构目录

New Image

New Image

New Image

New Image

New Image

New Image

New Image

New Image

New Image

New Image

New Image

New Image

New Image

New Image

New Image

New Image

New Image

New Image

New Image

New Image

目前uniapp-dylive项目,已经同步到我的原创作品集,有需要的可以去看看。

https://gf.bilibili.com/item/detail/1105131011

uniapp跨端自定义navbar+tabbar组件

New Image

New Image

New Image

New Image

在components目录下自定义navbar、tabbar组件。

New Image

之前有过一篇分享文章,感兴趣的话也可以去看看,这里就不详细的介绍实现过程了。

https://www.cnblogs.com/xiaoyan2017/p/14978408.html

uniapp+vue3小视频模块

New Image

uni-welive项目短视频模块采用全屏沉浸式上下无缝衔接滑动效果。整体分为顶部固定tabs+视频区+底部视频信息浮层三大模块。

复制代码
<ua-layout>
    <view class="ua__swipervideo flex1">
        <swiper
            class="ua__swipervideo-wrap flex1"
            :current="currentVideo"
            vertical
            :circular="true"
            :duration="200"
            @change="handleChange"
            @transition="handleTransition"
        >
            <swiper-item v-for="(item, index) in videoList" :key="index">
                <video
                    class="ua__swipervideo-player flex1"
                    :id="''uplayer'' + index"
                    :src="item.src"
                    :danmu-list="item.danmu"
                    :enable-danmu="true"
                    :controls="false"
                    :loop="true"
                    :autoplay="index == currentVideo"
                    :show-center-play-btn="false"
                    object-fit="contain"
                    @click="handleClickVideo"
                    @play="isPlaying=true"
                    @timeupdate="handleTimeUpdate"
                    :style="{''width'': `${winWidth}px`, ''height'': `${winHeight}px`}"
                >
                </video>
                
                <!-- 浮层模块 -->
                <view class="ulive__video-float__info flexbox flex-col">
                    <view class="flexbox flex-row flex-alignb">
                        <!-- 左侧 -->
                        <view class="vdinfo__left flex1 flexbox flex-col">
                            <view class="ltrow danmu flexbox" @click="handleOpenDanmu"><text class="danmu-txt"></text><uv-icon class="ico" name="edit-pen" color="#fff" size="14" /></view>
                            <view class="ltrow"><text class="ait">@{{item.author}}</text></view>
                            <view class="ltrow"><text class="desc">{{item.desc}}</text></view>
                        </view>
                        <!-- 右侧操作栏 -->
                        <view class="vdinfo__right flexbox flex-col">
                            <view class="rtbtn avatar flexbox flex-col">
                                <view class="ubox"><image class="uimg" :src="item.avatar" mode="aspectFill" /></view>
                                <view class="btn flexbox" :class="{''active'': item.isFollow}" @click="handleFollow(index)"><uv-icon :name="item.isFollow ? ''checkmark'' : ''plus''" :color="item.isFollow ? ''#ff007f'' : ''#fff''" size="11" /></view>
                            </view>
                            <view class="rtbtn flexbox flex-col" @click="handleLiked(index)"><uv-icon name="heart-fill" :color="item.isLike ? ''#ff007f'' : ''#fff''" size="40" /><text class="num">{{item.likeNum+(item.isLike ? 1 : 0)}}</text></view>
                            <view class="rtbtn flexbox flex-col" @click="handleOpenComment(index)"><uv-icon name="chat-fill" color="#fff" size="40" /><text class="num">{{item.replyNum}}</text></view>
                            <view class="rtbtn flexbox flex-col"><uv-icon name="star-fill" color="#fff" size="40" /><text class="num">{{item.starNum}}</text></view>
                            <view class="rtbtn flexbox flex-col" @click="handleOpenShare(index)"><uv-icon name="share-fill" color="#fff" size="40" /><text class="num">{{item.shareNum}}</text></view>
                        </view>
                    </view>
                </view>
            </swiper-item>
        </swiper>
        
        <!-- 固定tabs(脱离滑动区) -->
        <view class="ulive__video-header__tabs" :style="{''margin-top'': `${menuBarT}px`}">
            <uv-tabs :current="tabsCurrent" :list="tabsList" />
        </view>
        
        <!-- 播放暂停按钮 -->
        <view v-if="!isPlaying" class="ua__swipervideo-playbtn" :style="{''left'': `${winWidth/2}px`, ''top'': `${winHeight/2}px`}" @click="handleClickVideo">
            <text class="ua__swipervideo-playico welive-icon welive-icon-play nvueicon"></text>
        </view>
        <!-- 播放mini进度条 -->
        <view class="ua__swipervideo-progress" :style="{''width'': `${winWidth}px`}"><view class="ua__swipervideo-progressbar" :style="{''width'': `${progressBar}px`}"></view></view>
    </view>
    
    <template #footer>
        <ua-tabbar bgcolor="transparent" color="rgba(255,255,255,.7)" :border="false" :dock="false" transparent z-index="1000" />
    </template>
</ua-layout>
复制代码

uni-welive支持自动播放、发送视频弹幕、底部Mini视频播放进度条等功能。

New Image

复制代码
<script setup>
    import { ref, computed, getCurrentInstance } from ''vue''
    import { onShow, onHide } from ''@dcloudio/uni-app''
    import { getRandomColor } from ''@/utils''
    
    // ...
    
    const { globalData } = getApp()
    const menuBarT = ref(globalData.menu?.top || globalData.statusBarH)
    const winWidth = ref(globalData.screenWidth)
    const winHeight = ref(globalData.screenHeight)
    
    const tabsList = ref([
        { name: ''推荐'', count: 5 },
        { name: ''关注'' },
        { name: ''同城'' }
    ])
    const tabsCurrent = ref(0)
    
    // 视频参数
    const currentVideo = ref(0)
    const isPlaying = ref(false)
    const clickNum = ref(0)
    const clickTimer = ref(null)
    const progressBar = ref(0)
    
    // 视频源
    const videoList = ref(videoJson)
    const danmuEditor = ref('''')
    const isVisibleDanmu = ref(false)
    const commentRef = ref(null)
    const shareRef = ref(null)
    
    // ...
    
    
    /**
     * ====================== 视频播放模块 ======================
    */
    // 创建并返回 video 上下文 videoContext 对象
    const getVideoContext = () => {
        // return uni.createVideoContext(`uplayer${currentVideo.value}`, this)
        return uni.createVideoContext(`uplayer${currentVideo.value}`, getCurrentInstance())
    }
    
    // 垂直滑动视频,滑动改变时会触发 change 事件
    const handleChange = (e) => {
        const index = e.detail.current
        progressBar.value = 0
        handleReset()
        
        currentVideo.value = index
        // 播放
        handlePlay()
    }
    
    // 播放
    const handlePlay = () => {
        console.log(''video play'')
        let video = getVideoContext()
        if(!video) return
        video.play()
        isPlaying.value = true
    }
    
    // 暂停
    const handlePause = () => {
        console.log(''video pause'')
        let video = getVideoContext()
        if(!video) return
        video.pause()
        isPlaying.value = false
    }
    
    // 重置播放
    const handleReset = () => {
        console.log(''video reset'')
        let video = getVideoContext()
        if(!video) return
        video.pause()
        video.seek(0)
        video.stop()
        isPlaying.value = false
    }
    
    // 监听播放进度条
    const handleTimeUpdate = (e) => {
        let { currentTime, duration } = e.detail
        progressBar.value = parseInt((currentTime / duration).toFixed(2) * parseInt(winWidth.value))
    }
    
    // 点击视频(监听单双击)
    const handleClickVideo = () => {
        console.log(''video click'')
        clearTimeout(clickTimer.value)
        clickNum.value++
        clickTimer.value = setTimeout(() => {
            if(clickNum.value >= 2) {
                console.log(''double click'')
            }else {
                if(isPlaying.value) {
                    handlePause()
                }else {
                    handlePlay()
                }
            }
            clickNum.value = 0
        }, 200)
    }
    
    
    /**
     * ====================== 其它功能模块 ======================
    */
    // 打开弹幕弹框
    const handleOpenDanmu = () => {
        isVisibleDanmu.value = true
    }
    // 关闭弹幕弹框
    const handleCloseDanmu = () => {
        uni.hideKeyboard()
        isVisibleDanmu.value = false
        danmuEditor.value = ''''
    }
    // 发送弹幕
    const handleSendDanmu = () => {
        let video = getVideoContext()
        if(!video) return
        video.sendDanmu({
            text: danmuEditor.value,
            color: getRandomColor()
        })
        handleCloseDanmu()
    }
    
    // 打开评论框
    const handleOpenComment = (index) => {
        commentRef.value.open()
    }
    
    // ...

</script>
复制代码

注意:uniapp+vue3获取创建视频上下文实例,由于vue3没有this,只能通过getCurrentInstance获取上下文实例。

uni.createVideoContext(`uplayer${currentVideo.value}`, getCurrentInstance())

uniapp+vue3直播模块

New Image

直播模块整体分为顶部信息+直播流视频区+滚动消息(加入直播间+送礼物+讲解商品)+底部toolbar栏

复制代码
<ua-layout>
    <view class="ua__swipervideo flex1">
        <swiper
            class="ua__swipervideo-wrap flex1"
            :current="currentLive"
            vertical
            @change="handleChange"
        >
            <swiper-item v-for="(item, index) in liveList" :key="index">
                <video
                    class="ua__swipervideo-player flex1"
                    :id="''uplayer'' + index"
                    :src="item.src"
                    :controls="false"
                    :loop="true"
                    :autoplay="index == currentLive"
                    :show-center-play-btn="false"
                    object-fit="contain"
                    :style="{''width'': `${winWidth}px`, ''height'': `${winHeight}px`}"
                >
                </video>
                
                <!-- 浮层模块 -->
                <swiper class="ulive__swiperscreen flex1" :current="1">
                    <!-- 清屏 -->
                    <swiper-item>
                        第一屏
                    </swiper-item>
                    <swiper-item>
                        <!-- 顶部区域 -->
                        <view class="ulive__headlayer" :style="{''top'': menuBarT+''px''}">
                            <!-- logo+关注 -->
                            <view class="ulive__hd-liveinfo flexbox flex-row flex-alignc">
                                <view class="ulive__hd-avatar ulive__mask flex-alignc">
                                    <image class="logo" :src="item.logo" mode="widthFix" />
                                    <view class="flex1 flexbox flex-col ml-10">
                                        <text class="name">{{item.name}}</text>
                                        <text class="zan">{{item.likeNum}}本场点赞</text>
                                    </view>
                                    <view class="btn flexbox flex-row flex-alignc" :class="{''active'': item.isFollow}" @click="handleFollow(index)"><text class="btntext" :class="{''active'': item.isFollow}">{{item.isFollow ? ''已关注'' : ''关注''}}</text></view>
                                </view>
                                <view class="ulive__hd-onlineuser flex1">
                                    <uv-icon name="close" color="#fff" @click="handleLiveQuit" />
                                </view>
                            </view>
                            <view class="ulive__hd-livewrap flexbox flex-row">
                                <view class="ulive__hd-livewrap__left flex1 flexbox flex-col">
                                    <view class="ulive__hd-livewrap__tags flexbox flex-row">
                                        <view class="ulive__roundwrap ulive__mask">
                                            <uv-icon name="shopping-cart" color="#ffdd1a" /><text class="ulive__roundtext">服饰鞋包榜第1名</text>
                                        </view>
                                        <view class="ulive__roundwrap ulive__mask ml-10">
                                            <uv-icon name="level" color="#ffdd1a" /><text class="ulive__roundtext">小时榜</text>
                                        </view>
                                    </view>
                                    <!-- 红包+福袋倒计时 -->
                                    <view class="ulive__hd-livewrap__redpacket flexbox flex-row">
                                        <view class="ulive__redpacket-item ulive__mask" @click="handleOpenRedpacket(1)">
                                            <image class="ulive__redpacket-image" src="/static/icon-fudai.png" mode="widthFix" /><text class="ulive__redpacket-time">04:49</text>
                                        </view>
                                        <view class="ulive__redpacket-item ulive__mask" @click="handleOpenRedpacket(2)">
                                            <image class="ulive__redpacket-image" src="/static/icon-hb.png" mode="widthFix" /><text class="ulive__redpacket-time">04:49</text>
                                        </view>
                                        <view class="ulive__redpacket-item ulive__mask center">
                                            <image class="ulive__redpacket-image" src="/static/icon-rotate.png" mode="widthFix" /><text class="ulive__redpacket-time">04:49</text>
                                        </view>
                                    </view>
                                </view>
                                <view class="ulive__hd-livewrap__right flexbox flex-col">
                                    <view class="ulive__roundwrap ulive__mask mr-20">
                                        <uv-icon name="kefu-ermai" color="#fff" /><text class="ulive__roundtext ml-5">后台</text>
                                    </view>
                                </view>
                            </view>
                        </view>
                        
                        <!-- 底部区域 -->
                        <view class="ulive__footlayer">
                            <!-- 商品提示层 -->
                            <view class="ulive__ft-livewrap-placeholder animated fadeIn">
                                <view class="ulive__ft-livewrap-hotbuy flexbox flex-row">
                                    <image class="gimg" :src="item.poster" mode="aspectFill" />
                                    <view class="ginfo flex1">
                                        <view class="flexbox flex-row"><text class="user c-ffdd1a">Andy</text><text class="c-fff">等{{item.saleNum}}人在购买</text></view>
                                        <text class="gdesc clamp1">{{item.desc}}</text>
                                    </view>
                                    <view class="btn"><text class="btntext">去购买</text></view>
                                </view>
                            </view>
                            <!-- 加入直播间/送礼物提示 -->
                            <view class="ulive__ft-livewrap-animateview flexbox flex-col">
                                <view class="ulive__ft-livewrap-animatejoin ulive__ft-livewrap-placeholder">
                                    <view v-if="joinRoomData" class="ulive__ft-livewrap-joinroom"><text class="ulive__ft-livewrap-joinroom__text">欢迎{{joinRoomData}}加入了直播间</text></view>
                                </view>
                                
                                <!-- 送礼物 -->
                                <view class="ulive__ft-livewrap-animategift ulive__ft-livewrap-placeholder">
                                    <view v-if="!isEmpty(sendGiftData)" class="ulive__ft-livewrap-activegift flexbox flex-row flex-alignc">
                                        <image class="avatar" :src="sendGiftData.avatar" />
                                        <view class="info flex1"><text class="name">{{sendGiftData.user}}</text><text class="desc">送出</text></view>
                                        <image class="gift" :src="sendGiftData.pic" />
                                    </view>
                                </view>
                            </view>
                            <!-- 聊天浮层+商品讲解 -->
                            <view class="ulive__ft-livewrap-mixinview flexbox flex-row">
                                <!-- 聊天消息 -->
                                <view class="ulive__ft-livewrap-chats flex1">
                                    <scroll-view class="ulive__ft-livewrap-chats__scrollview flex1" scroll-y show-scrollbar="false" :scroll-into-view="scrollToView" :lower-threshold="5" @scroll="handleMsgScroll" @scrolltolower="handleMsgScrollLower">
                                        <block v-for="(msgitem, msgidx) in item.message" :key="msgidx">
                                            <view v-if="msgitem.type == ''notice''" class="notice" :id="`msg-${msgitem.id}`"><view class="item"><text class="noticetext">{{msgitem.content}}</text></view></view>
                                            <view v-else-if="msgitem.type == ''gift''" class="gift" :id="`msg-${msgitem.id}`">
                                                <view class="item">
                                                    <text class="giftuser">{{msgitem.user}}</text>
                                                    <text class="gifttext">送出了{{msgitem.content}}</text>
                                                    <image class="giftimg" :src="msgitem.img" mode="widthFix" />
                                                    <text class="giftnum">x{{msgitem.num}}</text>
                                                </view>
                                            </view>
                                            <view v-else class="msg" :id="`msg-${msgitem.id}`">
                                                <view class="item">
                                                    <text v-if="msgitem.tag" class="tag">{{msgitem.tag}}</text>
                                                    <text class="user">{{msgitem.user}}</text>
                                                    <text class="text" :style="[fixTextStyle]">{{msgitem.isbuy ? ''正在购买'' : msgitem.content}}</text>
                                                    <text v-if="msgitem.isbuy" class="tag tag-buy">去购买</text>
                                                </view>
                                            </view>
                                        </block>
                                    </scroll-view>
                                    <view v-if="!isEmpty(msgUnread)" class="ulive__ft-livewrap-chats__unread" @click="handleMsgIsRead"><text class="c-eb4868 fs-24">{{msgUnread.length}}条新消息</text></view>
                                </view>
                                <!-- 商品讲解 -->
                                <view v-if="isVisibleGoodsTalk" class="ulive__ft-livewrap-activegoods animated fadeInRight" id="goodsTalkID">
                                    <view class="ulive__ft-livewrap-activegoods__hotsale flexbox flex-row">
                                        <image class="fimg" src="/static/icon-hot.png" mode="widthFix" /><text class="c-fff fs-32">热卖 x{{item.saleNum}}</text>
                                    </view>
                                    <swiper class="ulive__ft-livewrap-activegoods__swiper">
                                        <swiper-item>
                                            <view class="ulive__ft-livewrap-activegoods__card">
                                                <view class="gwrap" @click="toGoodsDetail">
                                                    <image class="gimg" :src="item.poster" mode="aspectFill" />
                                                    <view class="waves"><text class="c-fff fs-24">讲解中</text></view>
                                                    <view class="close" @click.stop="isVisibleGoodsTalk=false"><uv-icon name="close-circle-fill" color="rgba(0, 0, 0, .3)" size="14" /></view>
                                                </view>
                                                <view class="ginfo flexbox flex-col">
                                                    <text class="clamp1 fs-24">{{item.desc}}</text>
                                                    <text class="clamp1 fs-24 c-eb4868">7天无理由退货</text>
                                                </view>
                                                <view class="btn flexbox flex-row"><text class="flex1 c-fff fs-28">¥79.00</text><text class="qiang"></text></view>
                                            </view>
                                        </swiper-item>
                                    </swiper>
                                </view>
                            </view>
                            <!-- 工具栏 -->
                            <view class="ulive__ft-livewrap-toolbar flexbox flex-row">
                                <view class="editorwrap flex1 flexbox flex-row flex-alignc">
                                    <view class="flex1" @click="handleOpenChatbox"><text class="editorwrap-text">说点什么...</text></view>
                                </view>
                                <view class="btnwrap flexbox flex-row">
                                    <view class="btn flexbox" @click="handleOpenMenus"><uv-icon name="grid" color="#3c9cff" size="22" /></view>
                                    <view class="btn flexbox" @click="handleOpenGoods(item)"><uv-icon name="shopping-cart-fill" color="#ffaa00" size="24" /></view>
                                    <view class="btn flexbox" @click="handleOpenGifts"><uv-icon name="gift" color="#ff0ad3" size="22" /></view>
                                    <view class="btn flexbox"><uv-icon name="more-dot-fill" color="#efe9ff" size="18" /></view>
                                </view>
                            </view>
                        </view>
                    </swiper-item>
                </swiper>
            </swiper-item>
        </swiper>
    </view>
</ua-layout>
复制代码

Okay,以上就是uniapp+vue3开发仿微信/抖音短视频+直播商城的一些知识分享,希望对大家有所帮助哈~~💪

最后附上两个最近实例项目

Electron27+React18跨平台macos桌面系统管理:https://www.cnblogs.com/xiaoyan2017/p/17850653.html

Uniapp+Vue3仿制chatgpt会话实例:https://www.cnblogs.com/xiaoyan2017/p/17507581.html

New Image