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

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

预览效果

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

使用技术

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

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

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

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

项目结构目录

uniapp跨端自定义navbar+tabbar组件

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

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

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

uniapp+vue3小视频模块

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视频播放进度条等功能。

<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直播模块

直播模块整体分为顶部信息+直播流视频区+滚动消息(加入直播间+送礼物+讲解商品)+底部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

uniapp-welive仿微信/抖音直播带货|uni-app+vue3+pinia短视频直播商城的更多相关文章

  1. Vue3.0短视频+直播|vue3+vite2+vant3仿抖音界面|vue3.x小视频实例

    基于vue3.0构建移动端仿抖音/快手短视频+直播实战项目Vue3-DouYin. 5G时代已来,短视频也越来越成为新一代年轻人的娱乐方式,在这个特殊之年,又将再一次成为新年俗! 基于vue3.x+v ...

  2. uni-app仿抖音APP短视频+直播+聊天实例|uniapp全屏滑动小视频+直播

    基于uniapp+uView-ui跨端H5+小程序+APP短视频|直播项目uni-ttLive. uni-ttLive一款全新基于uni-app技术开发的仿制抖音/快手短视频直播项目.支持全屏丝滑般上 ...

  3. uniapp+nvue实现仿微信/得物相册插件:选择界面 +自定义相册+图片视频过滤

    本篇文章基于uniapp 框架+ nvue,实现了uniapp仿微信/得物相册选择功能实例项目,该插件实例实现了以下功能: 1: 相册过滤 2: 图视频过滤 3: 界面UI定制化 4: 栅格列数定制化 ...

  4. CDN百科第四讲 | 如何优雅地在云上“摆摊”——做直播带货,你不得不关注的技术

    最近,国家政策开始鼓励“地摊经济”,一时间各家企业平台纷纷推出地摊扶持政策,地摊概念股顺势大涨,地摊生态及配套商品也开始走俏,甚至在网络上也涌现出各种“新摊主速成攻略”,万亿的烟火经济俨然已经走上风口 ...

  5. 618技术特辑(三)直播带货王,“OMG买它”的背后,为什么是一连串技术挑战?

    [本期推荐]为什么一到大促,我们的钱包总是被掏空?是大家自制力不够,还是电商平台太会读懂人心,从技术维度,抽丝剥茧一探究竟. 摘要:动辄几十上百万人同时在线的直播间,让所有人能同时公平的去抢购,并且还 ...

  6. 2020无损车载音源抖音歌曲下栽经典流行MP3新歌曲视频音乐下载包

    2020无损车载音源抖音歌曲下栽经典流行MP3新歌曲视频音乐下载包 偶有几次乘出租车晚归,除了发现出租车司机都很爱听叶文有话要说之外,也发现有的出租车司机还是很有听歌品位的,车载音响系统改的也很棒.有 ...

  7. 直播带货APP源码开发为什么选择云服务器

    云服务器可以为直播带货APP源码提供弹性计算以及更高的运行效率,避免资源浪费,随着直播带货APP源码业务需求的变化,可以实时扩展或缩减计算资源.CVM支持按实际使用的资源计费,可以节约计算成本. 一. ...

  8. 从直播商城系统的KOL效应分析,直播带货井喷的必然性

    网红营销.直播带货作为近年来的热点发展迅猛,同时也捧红了一个概念:KOL.随着直播商城系统的不断完善发展,让KOL成为近年来营销最热门的香饽饽.随着原创直播平台低门槛化.模板化内容创作和大数据智能分发 ...

  9. Vite2+Electron仿抖音|vite2.x+electron12+vant3短视频|直播|聊天

    整合vite2+electron12跨平台仿抖音电脑版实战Vite2-ElectronDouYin. 基于vite2.0+electron12+vant3+swiper6+v3popup等技术跨端仿制 ...

  10. 这群程序员疯了!他们想成为IT界最会带货的男人

    随着网红主播越来越火,通过直播带货种草的形式也成了今年双12的热点. 不过,网红主播带货早已见怪不怪,但你们见过程序员直播带货吗!? 近日,趁着阿里云双12年末采购节,阿里云邀请了一波程序员GG来为大 ...

随机推荐

  1. 简化 Go 开发:使用强大的工具提高生产力

    作为 Go 开发人员,应该都知道维持简洁高效开发工作流程的重要性.为了提高工作效率和代码质量,简化开发流程并自动执行重复性任务至关重要.在本文中,我们将探讨一些强大的工具和技术,它们将简化 Go 开发 ...

  2. 利用ChatGPT提升测试工作效率——测试工程师的新利器(一)

    1.前言 随着ChatGPT的爆火,各个行业开始尝试利用ChatGPT来提升工作效率.其中,测试工程师们也开始探索如何应用ChatGPT来加强测试工作.在本文中,我们将从测试工程师的角度出发,探讨Ch ...

  3. 下载kubernetes

    前言 页面介绍了k8s的组件下载的方法 二进制文件 二进制文件的下载链接在CHANGELOG文件中,这里有一个技巧是直接下载Server Binaries,这个是包含了所有的二进制文件.下载后记得比对 ...

  4. 虹科分享 | HPC调度解决方案:HK-Adaptive在数字卫星图像领域的应用

    2011年3月11日,日本海岸附近发生了9.0级地震.这次地震引发了强大的海啸,并向内陆传播了6英里,不仅使地球的轴心偏移了大约10到25厘米,还导致福岛核电站发生核紧急情况. 为了减少这场灾害的损失 ...

  5. 关于通过StringTemplate模板生成xml转成excel后office无法打开的问题解决

    说明:本人最近在着手实现导出日志数据,由于日志数据过多,在网上查找java 导出大量数据到excel的例子. 后发现园子里某位老哥通过StringTemplate模板生成excel格式的xml,这个思 ...

  6. 【前端开发】基于vue+elemnt-ui流程图设计器解决方案

    前言 越来越多的企业都在研发低代码平台,其中流程引擎是核心之一,拥有一个可以拖拽设计审批流程的设计器是相当重要的. 介绍 审批流程设计器是一种工具,用于创建和设计审批流程.它通常是一个可视化的设计器界 ...

  7. ELK-日志收集-Kibana WEB安全认证

    1.ELK收集MYSQL日志实战: 日志收集存放目录位置: /usr/local/logstash/config/etc/   1)日志采集-存入redis缓存数据库:mysql-redis.conf ...

  8. 数字逻辑笔记 全加器全减器8421BCD转余3

    二进制全加器 全减器 十进制加法 8421BCD转余3码

  9. 极速指南:在 SpringBoot 中快速集成腾讯云短信功能

    前言 今天分享一个SpringBoot集成腾讯云短信的功能,平常除了工作,很多xdm做自己的小项目都可能用到短信,但自己去看文档挺费劲的,我这边就帮你节省时间,直接把步骤给你列出来,照做就行. 实战 ...

  10. k8s Redis安装部署

    一.文档简介 作者:lanjiaxuan 邮箱:lanheader@163.com 博客地址:https://www.cnblogs.com/lanheader/ 更新时间:2021-07-09 安装 ...