living.vue 51 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762176317641765176617671768176917701771177217731774177517761777177817791780178117821783178417851786178717881789179017911792179317941795179617971798179918001801180218031804180518061807180818091810181118121813181418151816181718181819182018211822182318241825182618271828182918301831183218331834183518361837183818391840184118421843184418451846184718481849185018511852185318541855185618571858185918601861186218631864186518661867186818691870187118721873187418751876187718781879188018811882188318841885188618871888188918901891189218931894189518961897189818991900190119021903190419051906190719081909191019111912191319141915
  1. <template>
  2. <view class="container">
  3. <!-- <view class="popup-video " v-if="!autoplay">
  4. <view class="fs36 bold" v-if="livedata.status==1">——直播还未开始——</view>
  5. <view class="fs36 bold" v-if="livedata.status==3">——直播已经结束——</view>
  6. <view class="fs28 mtb20">了解更多,点击下方联系老师</view>
  7. <view class="more" @click="showadd=!showadd">联系老师</view>
  8. </view> -->
  9. <!-- <image :src="livedata.liveImgUrl"
  10. :class="livedata.status!=2?'background-images':'background-image'"></image> -->
  11. <view class="blackbg"></view>
  12. <view class="content">
  13. <!-- 页面内容 -->
  14. <view style=" position: fixed;top: 0;z-index: 5;" class="content-top">
  15. <view class="u-flex-y-center">
  16. <image @click="goBack" style="width: 60rpx;height: 64rpx;margin-right: 26rpx;"
  17. src="@/static/images/live/return.png"></image>
  18. <view class=" align-center"
  19. style="padding: 6rpx 4rpx;height: 64rpx;background: rgba(0,0,0,0.5);border-radius: 32rpx;">
  20. <u-avatar :src="livedata.liveImgUrl" size="32"></u-avatar>
  21. <view class="colorf" style="margin-left: 8rpx;">
  22. <view>{{livedata.liveName?truncateString(livedata.liveName,5):"未命名"}}</view>
  23. <view style="font-size: 16rpx;">
  24. {{ liveViewData.follow || liveViewData.follow === 0 ? liveViewData.follow : 0 }}位粉丝
  25. </view>
  26. </view>
  27. <view v-if="isFollow" @click="onFollow" class="follow-btn ml20">关注</view>
  28. <view v-else @click="onFollow" class="follow-btn ml20">已关注</view>
  29. </view>
  30. </view>
  31. <view class="u-flex-end align-center">
  32. <image class="w52 h52 mr4" v-for="(item,index) in filteredViewers" :key="index"
  33. style="border-radius: 26rpx;" :src="item.avatar"></image>
  34. <view class="sum">{{liveViewData.online||0}}</view>
  35. </view>
  36. </view>
  37. <!-- 页面内容 -->
  38. <view style=" position: fixed;top:100rpx;left: 0rpx; z-index: 5;" class="content-top">
  39. <view class="u-flex-y-center">
  40. <image v-if="redInfo?.status==1" @click="onRed" style="width: 70rpx;height: 70rpx;"
  41. src="@/static/images/hongbao.png"></image>
  42. </view>
  43. </view>
  44. <!-- 右边的 -->
  45. <view :class=" showType==1 ? 'siderow-group' : 'side-group'">
  46. <view class="side-item">
  47. <image @click="onLike" src="/static/images/live/like.png"></image>
  48. <view>{{liveViewData.like}}</view>
  49. <!-- <view>{{liveData likeName}}</view> -->
  50. </view>
  51. <view class="side-item">
  52. <image @click="goStore()" src="/static/images/live/shop.png"></image>
  53. <view>店铺</view>
  54. </view>
  55. <!-- <view class="side-item" open-type="share">
  56. <image src="/static/images/live/share.png"></image>
  57. <view>分享</view>
  58. </view> -->
  59. <view class="side-item">
  60. <button open-type="share">
  61. <image src="/static/images/live/share.png" mode="widthFix"></image>
  62. </button>
  63. <view>分享</view>
  64. </view>
  65. </view>
  66. <view class="shop-prompt u-flex-y-center" v-if="showPurchasePrompt">
  67. <image class="w32 h32 mr8" src="/static/images/live/shopping.png"></image>
  68. <text> {{orderUser.userName ? maskString(orderUser.userName) : ''}} 等 {{orderUser.count || 0}}
  69. 人正在去购买</text>
  70. </view>
  71. <!-- object-fit:fill; -->
  72. <view class="videolist " style="margin: auto 0">
  73. <!-- <view class="video" :style="{height:isScreen?'100vh':''}"> -->
  74. <view class="video" style="height:100vh">
  75. <!-- 修改video组件以支持HLS -->
  76. <!-- showType 1 横屏 2 竖屏 -->
  77. <video v-if="livingUrl" id="myVideo" :class="showType == 1 ? 'video_row' : 'videotop'"
  78. :src="livingUrl" :autoplay="autoplay" :controls='false' object-fit='contain'
  79. :custom-cache="false" :enable-progress-gesture="false" vslide-gesture-in-fullscreen='true'
  80. :show-center-play-btn="false" :http-cache="false" @error="videoError">
  81. </video>
  82. <video v-if="videoUrl" :class="showType == 1 ? 'video_row' : 'videotop'" :src="videoUrl"
  83. :autoplay="autoplay" :controls='false' object-fit='contain' :custom-cache="false"
  84. :enable-progress-gesture="false" vslide-gesture-in-fullscreen='true'
  85. :show-center-play-btn="false" :http-cache="false" loop @error="videoError">
  86. </video>
  87. </view>
  88. </view>
  89. <view class="pb40 mt90" style="position: fixed;bottom: 0;">
  90. <view class="w100 h300 mt20">
  91. <scroll-view enable-flex scroll-y="true" class="talk p20 scrolly flex-1 column"
  92. style="width: calc(100% - 40rpx);height: calc(100% - 40rpx);"
  93. :scroll-into-view="scrollIntoView">
  94. <view>
  95. <view class="list justify-start" v-for="(item,index) in talklist" :key="item.index"
  96. :id="`list_${index}`" v-show="item.cmd=='announcement'">
  97. <view class="talk-list ml16 justify-start">
  98. <view class="fs24">
  99. <text class='fs24 colorf'> {{item.msg}}直播间{{messageContent}}</text>
  100. </view>
  101. </view>
  102. </view>
  103. </view>
  104. <!-- 弹幕 -->
  105. <!-- <u-list class="chat-list" height="184" @scrolltolower="scrollchat">
  106. <u-list-item class="chat-item" v-for="(item,index) in chatList" :key="index">
  107. <view class="chat-lable u-flex-y-center mr8" v-if="item.lable">
  108. <image class="w24 h24 mr8" src="@/static/images/live/diamond.png"></image>
  109. <text>{{item.lable}}</text>
  110. </view>
  111. <text style="color: #FFDA73;white-space: nowrap;" class=" mr8">{{item.name}}:</text>
  112. <text style="flex: 1;min-width: 0;word-break: break-word;">{{item.txt}}</text>
  113. </u-list-item>
  114. </u-list> -->
  115. <view class="list justify-start" v-for="(item,index) in talklist" :key="item.index"
  116. :id="`list_${index}`" v-show="item.cmd=='sendMsg'">
  117. <view class="talk-list ml16 justify-start">
  118. <view class="fs24">
  119. <!-- <text style="color: #3fc69b;transform: scale(0.8);display: inline-block; "v-if="item.userId==userinfo.userId&&item.cmd=='sendMsg'">我</text> -->
  120. <!-- <text style="color: #c84e1e;transform: scale(0.8);display: inline-block;" v-if="item.userType==1">[ 管理员 ]</text> -->
  121. <text style="color: #FFDA73;">{{item.nickName}}:</text>
  122. <text class='fs24 colorf'>{{item.msg}}</text>
  123. </view>
  124. </view>
  125. </view>
  126. <view v-if="showWelcomeMessage" class="welcome-message">
  127. <view class="list justify-start" v-for="(item,index) in talklist" :key="item.index"
  128. :id="`list_${index}`" v-show="item.cmd=='entry'||item.cmd=='out'">
  129. <view class="talk-list ml16 justify-start">
  130. <view class="fs24">
  131. <text style="color: #ff89d6;">{{item.nickName}} </text>
  132. <text class='fs24 colorf'> {{item.msg}}直播间{{messageContent}}</text>
  133. </view>
  134. </view>
  135. </view>
  136. </view>
  137. </scroll-view>
  138. </view>
  139. <view class="justify-between p24">
  140. <view class="u-flex-y-center"
  141. style="background:rgba(157, 157, 157, 0.8);padding:18rpx 14rpx 18rpx 32rpx;width: 526rpx;box-sizing:border-box;border-radius:36rpx;">
  142. <u-input :placeholder="placeholderText" border="none" customStyle='font-size:24rpx;'
  143. v-model="value" shape='circle' color='#fff' placeholderStyle='color:#e7e7e7' class="ml20"
  144. @keydown.enter="sendMsg" @confirm="sendMsg">
  145. </u-input>
  146. <!-- <image @click="sendMsg" class="w44 h44" src="@/static/images/live/like.png"></image> -->
  147. </view>
  148. <!-- :disabled='talkdisabled' ></u-input> -->
  149. <!-- <view @click="sendMsg" class="colorf center ml10 fs24">发送</view> -->
  150. <view class="justify-between mr30 align-center">
  151. <!-- <view class="icon-bg ml20" @click="answerbtn=!answerbtn">
  152. <image src="/static/images/redbag.png" class="w40 h40"></image>
  153. </view> -->
  154. <view class="icon-bg ml20" @click="shopping=!shopping">
  155. <image src="/static/images/shopping.png" class="w48 h48"></image>
  156. </view>
  157. <view class="icon-bg ml20" @click="showziliao=!showziliao">
  158. <image src="/static/images/more-icon.png" class="w48 h48"></image>
  159. </view>
  160. <!-- <view class="icon-bg ml20" :class="{ 'zoom-button-active': isZoom }"
  161. @touchstart="handleTouchStart" @touchend="handleTouchEnd">
  162. <image src="/static/images/dianzan.png" class="w40 h40"></image>
  163. </view> -->
  164. </view>
  165. </view>
  166. </view>
  167. </view>
  168. <!-- <u-popup :show="showadd" @close="close" @open="open" round='20rpx' bgColor='#fffee1'>
  169. <view class="addchat p20">
  170. <view class="u-flex-row-reverse u-flex">
  171. <u-icon name="close" size="18" @click="showadd=!showadd"></u-icon>
  172. </view>
  173. <view class="column align-center">
  174. <view class="fs36" style="color: #ff5c03;">扫码添加助教老师</view>
  175. <view class="fs28 color6">扫码添加助教老师</view>
  176. <view class="p10 mt40" style="border: #ff5c03 solid 2rpx;">
  177. <image :src="codeimg" class="wh180" @touchstart="longPress" @touchend="cancelLongPress">
  178. </image>
  179. </view>
  180. <view class="color6 mt20">长按识别二维码</view>
  181. </view>
  182. </view>
  183. </u-popup> -->
  184. <u-popup :show="showziliao" @close="closes" round='20' bgColor='#fffee1'>
  185. <view class="addchat p20 bgf" style="border-radius: 20rpx;">
  186. <view class="u-flex-row-reverse u-flex">
  187. <u-icon name="close" size="18" @click="showziliao=!showziliao"></u-icon>
  188. </view>
  189. <view class="column align-center h400">
  190. <view class="fs36 " style="color: #ff5c03;">资料</view>
  191. <view v-html="livedata.liveDesc"></view>
  192. </view>
  193. </view>
  194. </u-popup>
  195. <!-- 抽奖 -->
  196. <!-- <u-popup :show="answerbtn" @close="closeanswer" round='40rpx' bgColor='#fff'>
  197. <view class=" p20 bgf" style="border-radius: 40rpx;height: fit-content;">
  198. <view class="u-flex-row-reverse u-flex">
  199. <u-icon name="close" size="18" @click="answerbtn=!answerbtn"></u-icon>
  200. </view>
  201. <view class="column align-center h400">
  202. <view class="fs36 " style="color: #000000;font-weight: bold;">答题获取红包/积分奖励</view>
  203. <view class="answerpop" @click="noredanswer">
  204. <view class="color6 w350 fs24">
  205. 边玩边学,解锁你的知识巅峰!
  206. </view>
  207. <view class="answera">答题挑战</view>
  208. </view>
  209. <view class="answerpop">
  210. <view class="color6 fs24" v-if='redanswertips&&redanswertips.length>0'>
  211. 🔥 <text style="color: #ff5c03;" class="fs30 bold">{{redanswertips[1].probability}}</text>
  212. %用户首抽得
  213. <text style="color: #ff5c03;" class="fs40 bold">{{redanswertips[1].maxAmount}}</text>元
  214. </view>
  215. <view v-else class="color6 w350 fs24">
  216. 随机奖励
  217. </view>
  218. <view class="answera" @click="redbagAnswer">
  219. <image src="/static/images/baganswer.png"></image>
  220. <view> 获得红包</view>
  221. </view>
  222. </view>
  223. </view>
  224. </view>
  225. </u-popup> -->
  226. <!-- <u-popup :show="showAnswer" @close="closest" round='40' bgColor='#fffee1' mode="center">
  227. <view class="answerbox p20 bgf">
  228. <view class="u-flex-row-reverse u-flex">
  229. <u-icon name="close" size="18" @click="closest"></u-icon>
  230. </view>
  231. <view class="column align-center ">
  232. <view v-if="answerlist.type==1" style="width: 100%;">
  233. <view class="mb40" style="text-align: center;">
  234. <text class="color9 fs24">(单选)</text>{{answerlist.title}}
  235. </view>
  236. <view v-for="(item,index) in answerlist.content" :key="index"
  237. :class=" checkboxValue[0]== item.label?'answeract itemanswer':'itemanswer' "
  238. @click="handleCheckboxSelect(item.label)">{{item.label}}. {{item.content}}</view>
  239. </view>
  240. <view v-if="answerlist.type==2" style="width: 100%;">
  241. <view class="mb40 " style="text-align: center;">
  242. <text class="color9 fs24">(多选)</text>{{answerlist.title}}
  243. </view>
  244. <view v-for="(item,index) in answerlist.content" :key="index"
  245. :class=" checkboxValue.includes(item.label)?'answeract itemanswer':'itemanswer' "
  246. @click="handleCheckboxSelect(item.label)">{{item.label}}. {{item.content}}</view>
  247. <view class=" submitbtn" @click="submitAnswers">确认</view>
  248. </view>
  249. <view class="fs24 mtb20 " style="color: #717171;">
  250. {{answerfrist+1}}/{{Answerlistall}}
  251. </view>
  252. </view>
  253. </view>
  254. </u-popup> -->
  255. <!-- <u-popup :show="showAnswerred" @close="closestred" round='40' bgColor='#fffee1' mode="center">
  256. <view class="answerbox p20 bgf">
  257. <view class="u-flex-row-reverse u-flex">
  258. <u-icon name="close" size="18" @click="closestred"></u-icon>
  259. </view>
  260. <view class="column align-center ">
  261. <view v-if="answerlist.type==1" style="width: 100%;">
  262. <view class="mb40" style="text-align: center;">
  263. <text class="color9 fs24">(单选)</text>{{answerlist.title}}
  264. </view>
  265. <view v-for="(item,index) in answerlist.content" :key="index"
  266. :class=" checkboxValue[0]== item.label?'answeract itemanswer':'itemanswer' "
  267. @click="handleCheckboxSelect(item.label)">{{item.label}}. {{item.content}}</view>
  268. </view>
  269. <view v-if="answerlist.type==2" style="width: 100%;">
  270. <view class="mb40 " style="text-align: center;">
  271. <text class="color9 fs24">(多选)</text>{{answerlist.title}}
  272. </view>
  273. <view v-for="(item,index) in answerlist.content" :key="index"
  274. :class=" checkboxValue.includes(item.label)?'answeract itemanswer':'itemanswer' "
  275. @click="handleCheckboxSelect(item.label)">{{item.label}}. {{item.content}}</view>
  276. <view class=" submitbtn" @click="submitAnswers">确认</view>
  277. </view>
  278. <view class="fs24 mtb20 " style="color: #717171;">
  279. {{answerfrist+1}}/{{Answerlistall}}
  280. </view>
  281. </view>
  282. </view>
  283. </u-popup> -->
  284. <!-- 小黄车弹窗 -->
  285. <u-popup :show="shopping" @close="closeshop" @open="open" round='20rpx' bgColor='#f3f5f9'>
  286. <view class="shoppop">
  287. <view class="shoppop-top">
  288. <u-avatar :src="store.logoUrl" size="36" class="ml16"></u-avatar>
  289. <view class="search-input u-flex-y-center">
  290. <image style="width: 24rpx;height: 24rpx;margin-right: 16rpx;" src="@/static/images/search.png">
  291. </image>
  292. <input placeholder="请搜索商品" v-model="inputInfo" @input="handleSearchInput" />
  293. </view>
  294. <view class="t-c search-top" style="margin-right: 48rpx;">
  295. <image @click="onStoreCollect" v-if="store.isFavorite" style="width: 32rpx;height: 32rpx;"
  296. src="@/static/images/collect_select.png"></image>
  297. <image v-else @click="onStoreCollect" style="width: 32rpx;height: 32rpx;"
  298. src="@/static/images/collect.png"></image>
  299. <view>收藏</view>
  300. </view>
  301. <view class="t-c search-top" @click="goOrderList">
  302. <image style="width: 32rpx;height: 32rpx;" src="@/static/images/order.png"></image>
  303. <view>订单</view>
  304. </view>
  305. </view>
  306. <scroll-view enable-flex scroll-y class="shop-list" :style="{ height: boxHeight + 'px' }">
  307. <view class="list-item " v-for="(item,index) in products" :key="index">
  308. <view class="goods-img">
  309. <image :src="item.imgUrl" mode="widthFix"></image>
  310. <view class="goods-label">{{index+1}}</view>
  311. </view>
  312. <view class="goods-right">
  313. <view class="goods-title">{{item.productName}}</view>
  314. <!-- <view class="goods-details">{{item.productIntroduce}}</view> -->
  315. <view class="goods-people">{{item.sales}} 人已购</view>
  316. <view class="goods-shop">
  317. <text class="nummber"><text style="font-size: 20rpx;font-weight: 600;">¥</text><text
  318. style="font-size: 36rpx;font-weight: bold;">{{Math.trunc(item.price)}}</text>.{{getPureDecimal(item.price)?getPureDecimal(item.price):'00'}}/日</text>
  319. <view class="btn-group u-flex-y-center">
  320. <view class="collect-btn">
  321. <image v-if="item.isFavorite" @click="onGoodsCollect(item)"
  322. style="width: 32rpx;height: 32rpx;" src="/static/images/collect_select.png">
  323. </image>
  324. <image v-else @click="onGoodsCollect(item)" style="width: 32rpx;height: 32rpx;"
  325. src="/static/images/collect.png">
  326. </image>
  327. </view>
  328. <view v-if="item.status==1" class="shop-btn"
  329. @click="goShop(item.productId,item.goodsId)">去购买
  330. </view>
  331. <view v-else-if="item.status==0" @click="goShop(item.productId)" class="shop-btn">
  332. 已下架</view>
  333. </view>
  334. </view>
  335. </view>
  336. </view>
  337. </scroll-view>
  338. </view>
  339. </u-popup>
  340. </view>
  341. </template>
  342. <script>
  343. import Hls from 'hls.js';
  344. import CryptoJS from 'crypto-js'
  345. import {
  346. liveRed,// 点击领红包
  347. liveDataLike, // 点赞
  348. collectStore, // 店铺收藏/取消收藏
  349. collectGoods, // 商品收藏/取消收藏
  350. store, // 查询店铺
  351. follow, // 关注/取消关注
  352. getRecentLiveViewers, //获取直播间用户(展示在线用户)
  353. // 小黄车
  354. liveStore, //店铺展示,
  355. // liveGoodsDetail, //商品详情,
  356. liveOrderUser, //正在购买
  357. getLiveInfo, //获取直播间信息接口
  358. getLiveViewData //直播间点赞、关注、在线人数数据
  359. } from '@/api/live'
  360. import {
  361. liveOrderList, // 订单列表
  362. } from '@/api/order'
  363. import parse from '../../uni_modules/uview-plus/libs/config/props/parse';
  364. import {
  365. LiveWS
  366. } from '@/utils/liveWS.js'
  367. import {
  368. getlive,
  369. gettextlist,
  370. getAnswerlist,
  371. submitAnswer
  372. } from '@/api/home'
  373. // var wsUrl = "ws://192.168.10.166:7114/app/webSocket";
  374. // var wsUrl = "ws://192.168.10.166:7114/app/webSocket"; //余红奇
  375. var wsUrl = "ws://192.168.10.125:7114/app/webSocket"; //涂小龙
  376. // var wsUrl = "ws://live.test.ylrztop.com/prod-api/app/webSocket"; //余红奇
  377. // var wsUrl = "ws://nd383294.natappfree.cc/app/webSocket"; //余红奇
  378. // var wsUrl = "ws://v56c9b8e.natappfree.cc/app/webSocket"; //余红奇
  379. // var wsUrl = "ws://192.168.10.170:7114/app/webSocket"; //陈果
  380. // var wsUrl = "ws://live.ylrzcloud.com/socket/app/webSocket";
  381. var pingpangTimes = null;
  382. var initTimes = null;
  383. var isSocketOpen = false;
  384. var socket = null;
  385. export default {
  386. data() {
  387. return {
  388. redInfo:null,
  389. inputInfo: '',
  390. searchTimer: null,
  391. storeId: null,
  392. reconnectCount: 0,
  393. maxReconnectAttempts: 3,
  394. reconnectTimer: null,
  395. isCollect: false,
  396. showPurchasePrompt: false,
  397. purchasePromptTimer: null,
  398. prevOrderCount: 0, // 用于记录上一次的购买人数
  399. videoUrl: null,
  400. showType: 1, //横屏1 竖屏2
  401. boxHeight: 300, //小黄车高度
  402. isFollow: true,
  403. liveViewData: {},
  404. liveViewers: [], //观众
  405. likeName: 0,
  406. hlsPlayer: null, // HLS播放器实例,
  407. livingUrl: "",
  408. products: {},
  409. store: {},
  410. orderUser: {}, //正在购买
  411. userType: 0,
  412. timestamp: '',
  413. liveId: null,
  414. // userId: uni.getStorageSync("userInfo.userId"),
  415. livedata: {},
  416. codeimg: '',
  417. placeholderText: '说点什么...',
  418. isZoom: false, //点赞按钮控制是否放大
  419. userinfo: '', //用户信息
  420. path: 'http://192.168.10.166/dev-api', //余红奇
  421. // path: 'http://v56c9b8e.natappfree.cc', //余红奇
  422. // path: 'live.test.ylrztop.com/prod-api', //余红奇
  423. // path: 'http://192.168.10.170/dev-api', //陈果
  424. value: '',
  425. talkdisabled: false, //输入框是否禁用
  426. autoplay: false, //视频自动播放
  427. showadd: false,
  428. talklist: [],
  429. scrollIntoView: '',
  430. bufferRate: 0, //视频缓冲时间
  431. playDuration: 0, //视频播放时间
  432. videoContext: '',
  433. thistime: uni.$u.timeFormat(new Date(), 'yyyy-mm-dd hh:MM:ss'),
  434. upDown: true, //是否视频显示隐藏
  435. isLongPress: false, // 是否长按
  436. timeout: null, // 计时器
  437. showWelcomeMessage: false,
  438. isSubmit: false,
  439. messageContent: "",
  440. showziliao: false,
  441. isScreen: true,
  442. showAnswer: false, //展示答题
  443. Answerlistall: {}, //所有题目
  444. answerlist: {}, //当前题目
  445. answerfrist: 0, //当前选择
  446. checkboxValue: [], //多选数据
  447. checkboxFormValue: "", //多选数据
  448. allAnswerLists: [], // 新增:存储所有题目列表
  449. showAnswerred: false, //展示红包答题
  450. answerbtn: false, //答题按钮弹窗
  451. redallAnswerLists: [], //储存所有红包答题列表
  452. redanswerAll: [], //红包当前题目
  453. redanswerList: [], //红包答题列表
  454. redanswertips: [], //红包答题提示
  455. shopping: false, //小黄车弹窗
  456. scrollTop: 0, //弹幕
  457. old: {
  458. scrollTop: 0
  459. },
  460. // liveData: {}, //直播间点赞、关注、在线人数数据
  461. };
  462. },
  463. onLoad(options) {
  464. if (options.liveId && options.liveId !== this.liveId) {
  465. this.liveId = options.liveId; // 仅当 liveId 变化时更新
  466. this.getliveViewData(); // 主动调用一次
  467. }
  468. uni.showShareMenu({
  469. withShareTicket: true
  470. });
  471. },
  472. onShareAppMessage() {
  473. return {
  474. // title: this.product.title,
  475. title: livedata.liveName,
  476. path: '/pages/home/index',
  477. imageUrl: this.product.image,
  478. success(res) {
  479. console.log("分享成功", res);
  480. },
  481. fail(err) {
  482. console.error("分享失败", err);
  483. }
  484. };
  485. },
  486. computed: {
  487. filteredViewers() {
  488. // 只返回前3项(索引0、1、2)
  489. return this.liveViewers.slice(0, 3);
  490. }
  491. },
  492. mounted() {
  493. // this.onLike()
  494. this.getliveViewData() ////直播间点赞、关注、在线人数数据
  495. // this.intervalId = setInterval(() => {
  496. // this.getliveViewData();
  497. // }, 60 * 1000);
  498. this.intervalId = setInterval(() => {
  499. this.getliveViewData();
  500. }, 60 * 1000);
  501. // this.getLiveinformation() // 获取直播间信息接口
  502. this.getliveOrder() //正在购买
  503. // this.getliveStore() // 获取小黄车 店铺展示
  504. // this.getliveGoods() // 获取小黄车 商品详情
  505. this.getliveUser() // 获取直播间用户
  506. this.initTime()
  507. // this.initWebSocket()
  508. this.initSocket()
  509. var that = this;
  510. uni.$on('initSocket', () => {
  511. that.initSocket()
  512. })
  513. uni.$on('sendMsg', (item) => {
  514. that.sendMsg(item)
  515. })
  516. uni.$on('closeWebSocket', () => {
  517. that.closeWebSocket()
  518. })
  519. this.getEWechatSdk();
  520. this.getliving() //hls
  521. this.gettalklist()
  522. // this.getAnswerlists()
  523. this.userinfo = JSON.parse(uni.getStorageSync("userInfo"))
  524. },
  525. onReady: function(res) {
  526. this.videoContext = uni.createVideoContext('myVideo')
  527. // console.log(this.videoContext)
  528. },
  529. onHide() {
  530. clearInterval(this.intervalId); // 页面隐藏时清理
  531. },
  532. onUnload() {
  533. this.closeWebSocket(); // 安全关闭
  534. if (this.hlsPlayer) {
  535. this.hlsPlayer.destroy(); // 清理 HLS 播放器
  536. }
  537. if (this.pingpangTimes) {
  538. clearInterval(this.pingpangTimes); // 清除心跳定时器
  539. }
  540. uni.$off('refreshOrder'); // 移除全局事件监听
  541. },
  542. watch: {
  543. // 监听orderUser.count的变化
  544. 'orderUser.count': {
  545. handler(newVal, oldVal) {
  546. if (newVal !== this.prevOrderCount) {
  547. this.prevOrderCount = newVal;
  548. this.showPurchaseMessage();
  549. }
  550. },
  551. immediate: true
  552. }
  553. },
  554. methods: {
  555. onRed() {
  556. if (!this.liveId) return;
  557. let data={
  558. liveId:this.liveId,
  559. userId:this.userinfo.userId,
  560. redId:this.redInfo.redId,
  561. }
  562. liveRed(data).then(res => {
  563. if (res.code == 200) {
  564. } else {
  565. uni.showToast({
  566. title: res.msg,
  567. icon: 'none'
  568. });
  569. }
  570. },
  571. rej => {}
  572. );
  573. },
  574. handleSearchInput() {
  575. // 使用防抖优化性能,避免频繁请求
  576. clearTimeout(this.searchTimer);
  577. this.searchTimer = setTimeout(() => {
  578. this.queryCollect();
  579. }, 500); // 500毫秒延迟
  580. },
  581. // 显示购买提示信息
  582. showPurchaseMessage() {
  583. // 清除之前的定时器
  584. if (this.purchasePromptTimer) {
  585. clearTimeout(this.purchasePromptTimer);
  586. }
  587. // 显示提示
  588. this.showPurchasePrompt = true;
  589. // 2秒后自动隐藏
  590. this.purchasePromptTimer = setTimeout(() => {
  591. this.showPurchasePrompt = false;
  592. }, 2000);
  593. },
  594. truncateString(str, maxLength) {
  595. return str.length > maxLength ? str.slice(0, maxLength) + '...' : str;
  596. },
  597. goStore() {
  598. console.log("带过去storeId", this.storeId)
  599. uni.navigateTo({
  600. url: '/pages_shop/store?liveId=' + this.liveId + "&storeId=" + this.storeId
  601. })
  602. },
  603. // 去订单列表
  604. goOrderList() {
  605. uni.navigateTo({
  606. url: "/pages_shop/order"
  607. })
  608. },
  609. // 初始化HLS播放器
  610. initHlsPlayer() {
  611. if (Hls.isSupported() && this.livingUrl) {
  612. const video = document.getElementById('myVideo');
  613. if (video) {
  614. this.hlsPlayer = new Hls();
  615. this.hlsPlayer.loadSource(this.livingUrl);
  616. this.hlsPlayer.attachMedia(video);
  617. this.hlsPlayer.on(Hls.Events.MANIFEST_PARSED, () => {
  618. video.play();
  619. });
  620. }
  621. }
  622. },
  623. // 视频错误处理
  624. videoError(e) {
  625. console.error('视频播放错误:', e.detail.errMsg);
  626. // 尝试重新加载或切换到备用流
  627. if (this.livingUrl) {
  628. setTimeout(() => {
  629. this.videoContext.play();
  630. }, 2000);
  631. }
  632. },
  633. getliving() {
  634. if (!this.liveId) return;
  635. console.log("获取直播信息");
  636. const param = {
  637. id: this.liveId
  638. };
  639. getlive(param).then(res => {
  640. if (res.code !== 200) {
  641. uni.showToast({
  642. title: res.msg,
  643. icon: 'none',
  644. duration: 2000
  645. });
  646. return;
  647. }
  648. this.livedata = res.data;
  649. this.showType = res.data.showType;
  650. this.storeId = res.storeId
  651. this.queryCollect() //查询店铺
  652. console.log("横屏1或竖屏2", this.showType)
  653. // 1. 根据直播类型设置播放地址
  654. if (res.data.liveType === 2) {
  655. // 回放视频
  656. this.videoUrl = res.data.videoUrl;
  657. this.autoplay = true;
  658. this.placeholderText = "观看回放中";
  659. this.talkdisabled = false;
  660. } else if (res.data.liveType === 1) {
  661. // 直播流
  662. this.livingUrl = res.data.flvHlsUrl;
  663. this.autoplay = true;
  664. this.placeholderText = "说点什么...";
  665. this.talkdisabled = false;
  666. } else {
  667. // 未开播
  668. this.livingUrl = "";
  669. this.autoplay = false;
  670. this.placeholderText = "直播未开始,暂时无法发言";
  671. this.talkdisabled = true;
  672. return;
  673. }
  674. // 2. 根据不同平台处理播放逻辑
  675. this.$nextTick(() => {
  676. if (!this.livingUrl) return;
  677. // H5 平台:使用 HLS.js 处理 .m3u8 流
  678. // #ifdef H5
  679. if (this.livingUrl.includes('.m3u8')) {
  680. this.initHlsPlayer();
  681. return;
  682. }
  683. // #endif
  684. // 小程序/App 平台:直接使用 video 组件
  685. if (!this.videoContext) {
  686. this.videoContext = uni.createVideoContext('myVideo', this);
  687. }
  688. if (res.data.liveType === 1) {
  689. // 回放:跳转到指定位置
  690. this.videoContext.seek(res.data.nowDuration || 0);
  691. } else {
  692. // 直播:直接播放
  693. this.videoContext.play();
  694. }
  695. });
  696. }).catch(err => {
  697. console.error("获取直播信息失败:", err);
  698. uni.showToast({
  699. title: "获取直播信息失败",
  700. icon: 'none'
  701. });
  702. });
  703. },
  704. maskString(str, maskChar = '*') {
  705. // 新增:如果str是undefined或null,直接返回空字符串
  706. if (!str) return '';
  707. // 确保str是字符串(如果是数字等类型,先转为字符串)
  708. const strVal = String(str);
  709. return strVal.split('').map((char, index) => (index === 0 ? char : maskChar)).join('');
  710. },
  711. getPureDecimal(num, precision = 6) {
  712. const decimalPart = Math.abs(num).toFixed(precision).split('.')[1];
  713. return decimalPart?.replace(/0+$/, '') || ''; // 移除末尾多余的0
  714. },
  715. // 返回上一个页面并关闭WebSocket
  716. goBack() {
  717. const pages = getCurrentPages();
  718. // 如果页面栈长度大于1,说明有上一页可以返回
  719. this.closeWebSocket();
  720. if (pages.length > 1) {
  721. uni.navigateBack();
  722. } else {
  723. // 如果没有上一页,跳转到默认页面(比如首页)
  724. uni.switchTab({
  725. url: '/pages/list/index' // 替换为你的首页路径
  726. });
  727. }
  728. },
  729. //直播间点赞、关注、在线人数数据
  730. getliveViewData() {
  731. console.log("直播间点赞、关注、在线人数数据>>>",this.liveId)
  732. if (!this.liveId) return;
  733. getLiveViewData(this.liveId).then(res => {
  734. if (res.code == 200) {
  735. // console.log("直播间点赞、关注、在线人数数据>>>>", res)
  736. this.liveViewData = res
  737. } else {
  738. uni.showToast({
  739. title: res.msg,
  740. icon: 'none'
  741. });
  742. }
  743. },
  744. rej => {}
  745. );
  746. },
  747. //正在购买
  748. getliveOrder() {
  749. if (!this.liveId) return;
  750. liveOrderUser(this.liveId).then(res => {
  751. if (res.code == 200) {
  752. console.log("正在购买>>>>", res)
  753. this.orderUser = res
  754. } else {
  755. uni.showToast({
  756. title: res.msg,
  757. icon: 'none'
  758. });
  759. }
  760. },
  761. rej => {}
  762. );
  763. },
  764. //小黄车 店铺展示
  765. // getliveStore() {
  766. // if (!this.liveId) return;
  767. // let data = {
  768. // pageSize: 10,
  769. // page: 1
  770. // }
  771. // liveStore(this.liveId, data).then(res => {
  772. // if (res.code == 200) {
  773. // console.log("小黄车 店铺展示>>>>", res)
  774. // this.products = res.data
  775. // this.queryCollect()
  776. // } else {
  777. // uni.showToast({
  778. // title: res.msg,
  779. // icon: 'none'
  780. // });
  781. // }
  782. // },
  783. // rej => {}
  784. // );
  785. // },
  786. // 获取直播间信息接口
  787. getLiveinformation() {
  788. if (!this.liveId) return;
  789. getLiveInfo(this.liveId).then(res => {
  790. if (res.code == 200) {
  791. console.log("获取直播间信息接口>>>>", res)
  792. this.livingUrl = res.livingUrl
  793. } else {
  794. uni.showToast({
  795. title: res.msg,
  796. icon: 'none'
  797. });
  798. }
  799. },
  800. rej => {}
  801. );
  802. },
  803. // 获取直播间用户
  804. getliveUser() {
  805. if (!this.liveId) return;
  806. getRecentLiveViewers(this.liveId).then(res => {
  807. if (res.code == 200) {
  808. console.log("获取直播间用户>>>>", res)
  809. this.liveViewers = res.recentLiveViewers
  810. } else {
  811. uni.showToast({
  812. title: res.msg,
  813. icon: 'none'
  814. });
  815. }
  816. },
  817. rej => {}
  818. );
  819. },
  820. // 点赞
  821. onLike() {
  822. if (!this.liveId) return;
  823. liveDataLike(this.liveId).then(res => {
  824. if (res.code == 200) {
  825. if (res.like) {
  826. this.liveViewData.like++
  827. } else {
  828. uni.showToast({
  829. title: res.msg,
  830. icon: 'none'
  831. });
  832. }
  833. } else {
  834. uni.showToast({
  835. title: res.msg,
  836. icon: 'none'
  837. });
  838. }
  839. },
  840. rej => {}
  841. );
  842. },
  843. // 去购买,跳商品详情
  844. goShop(productId, goodsId) {
  845. uni.navigateTo({
  846. url: '/pages_shop/goods?productId=' + productId + '&liveId=' + this.liveId + '&goodsId=' +
  847. goodsId + '&storeId=' + this.storeId
  848. })
  849. },
  850. // 查询店铺
  851. queryCollect() {
  852. if (!this.storeId) return;
  853. store(this.storeId, this.inputInfo).then(res => {
  854. if (res.code == 200) {
  855. console.log("查询店铺>>", res)
  856. this.products = res.data.goodsList
  857. this.store = res.data
  858. } else {
  859. uni.showToast({
  860. title: res.msg,
  861. icon: 'none'
  862. });
  863. }
  864. },
  865. rej => {}
  866. );
  867. },
  868. // 店铺收藏
  869. onStoreCollect() {
  870. if (!this.storeId) return;
  871. collectStore(this.storeId).then(res => {
  872. if (res.code == 200) {
  873. uni.showToast({
  874. title: res.msg,
  875. icon: 'none'
  876. });
  877. this.store.isFavorite = !this.store.isFavorite
  878. } else {
  879. uni.showToast({
  880. title: res.msg,
  881. icon: 'none'
  882. });
  883. }
  884. },
  885. rej => {}
  886. );
  887. },
  888. // 商品收藏
  889. onGoodsCollect(item) {
  890. if (!item || item.length === 0 || !item.goodsId) {
  891. return;
  892. }
  893. collectGoods(item.goodsId).then(res => {
  894. if (res.code == 200) {
  895. uni.showToast({
  896. title: res.msg,
  897. icon: 'none'
  898. });
  899. item.isFavorite = !item.isFavorite
  900. } else {
  901. uni.showToast({
  902. title: res.msg,
  903. icon: 'none'
  904. });
  905. }
  906. },
  907. rej => {}
  908. );
  909. },
  910. // 关注
  911. onFollow() {
  912. if (!this.liveId) return;
  913. follow(this.liveId).then(res => {
  914. this.isFollow = !this.isFollow
  915. uni.showToast({
  916. title: res.msg,
  917. icon: 'none'
  918. });
  919. },
  920. rej => {}
  921. );
  922. },
  923. // 时间戳
  924. initTime() {
  925. const now = new Date();
  926. this.timestamp = now.getTime(); // 例如:'2023-04-01 12:00:00'
  927. },
  928. // initWebSocket() {
  929. // const liveWS = new LiveWS('ws://your-server.com', 123, 456);
  930. // // 从 URL 中解析 timestamp
  931. // const urlParams = new URL(liveWS.url).searchParams;
  932. // this.timestamp = urlParams.get('timestamp');
  933. // // console.log('Timestamp:', timestamp);
  934. // },
  935. // 弹幕滚动
  936. lowerChat: function(e) {
  937. console.log(e)
  938. },
  939. scroll: function(e) {
  940. console.log(e)
  941. this.old.scrollTop = e.detail.scrollTop
  942. },
  943. loadmore() {
  944. // for (let i = 0; i < 30; i++) {
  945. // this.indexList.push({
  946. // url: this.shopList[uni.$u.random(0, this.shopList.length - 1)],
  947. // });
  948. // }
  949. },
  950. // 弹幕
  951. scrollchat() {
  952. // this.loadmore();
  953. },
  954. noredanswer() {
  955. if (this.Answerlistall > 0) {
  956. this.showAnswer = !this.this.showAnswer
  957. } else {
  958. uni.showToast({
  959. title: '暂无题目',
  960. icon: 'none',
  961. });
  962. }
  963. },
  964. redbagAnswer() {
  965. this.showAnswerred = !this.showAnswerred
  966. this.answerbtn = !this.answerbtn
  967. },
  968. submitAnswers() {
  969. if (this.isSubmit) return;
  970. this.isSubmit = true;
  971. const data = {
  972. questionId: this.answerlist.id,
  973. answer: this.checkboxFormValue
  974. }
  975. submitAnswer(data).then(res => {
  976. if (res.code == 200) {
  977. uni.showToast({
  978. title: res.msg,
  979. icon: 'none',
  980. });
  981. }
  982. // 本地切换下一题
  983. if (this.answerfrist < this.Answerlistall - 1) {
  984. this.answerfrist++;
  985. this.answerlist = this.allAnswerLists[this.answerfrist];
  986. this.answerlist.content = JSON.parse(this.answerlist.content);
  987. } else {
  988. uni.showToast({
  989. title: '已是最后一题',
  990. icon: 'none'
  991. });
  992. this.showAnswer = false; // 自动关闭弹窗
  993. }
  994. this.checkboxValue = []
  995. this.checkboxFormValue = "";
  996. uni.showToast({
  997. title: res.msg,
  998. icon: 'none'
  999. });
  1000. }).finally(e => {
  1001. this.isSubmit = false;
  1002. })
  1003. },
  1004. // handleCheckboxSelect(value) {
  1005. // const index = this.checkboxValue.indexOf(value)
  1006. // console.log(value)
  1007. // if (this.answerlist.type == 1) {
  1008. // this.checkboxValue = [value]
  1009. // this.checkboxFormValue = this.checkboxValue.join(',')
  1010. // setTimeout(() => {
  1011. // uni.showToast({
  1012. // title: '准备下一题',
  1013. // icon: 'none',
  1014. // duration: 2000
  1015. // });
  1016. // }, 1000);
  1017. // this.submitAnswers()
  1018. // console.log(this.checkboxValue)
  1019. // } else if (this.answerlist.type == 2) {
  1020. // if (index > -1) {
  1021. // this.checkboxValue.splice(index, 1)
  1022. // this.checkboxFormValue = this.checkboxValue.join(',')
  1023. // } else {
  1024. // this.checkboxValue.push(value)
  1025. // this.checkboxFormValue = this.checkboxValue.join(',')
  1026. // }
  1027. // console.log(this.checkboxFormValue)
  1028. // }
  1029. // },
  1030. getAnswerlists() {
  1031. if (!this.liveId) return;
  1032. const data = {
  1033. liveId: this.liveId
  1034. }
  1035. getAnswerlist(data).then(res => {
  1036. if (res.code == 200) {
  1037. if (res.data.length > 0) {
  1038. this.allAnswerLists = res.data; // 存储所有题目
  1039. this.Answerlistall = res.data.length;
  1040. if (this.allAnswerLists.length > 0) {
  1041. this.answerlist = this.allAnswerLists[0];
  1042. this.answerlist.content = JSON.parse(this.allAnswerLists[0].content);
  1043. }
  1044. this.showAnswer = true
  1045. } else {
  1046. this.showAnswer = false
  1047. }
  1048. }
  1049. })
  1050. },
  1051. gettalklist() {
  1052. if (!this.liveId) return;
  1053. const param = {
  1054. id: this.liveId
  1055. }
  1056. gettextlist(param).then(res => {
  1057. if (res.code == 200) {
  1058. this.talklist = res.data
  1059. // console.log(res.data);
  1060. this.$nextTick(() => {
  1061. this.scrollIntoView = `list_${this.talklist.length-1}`
  1062. })
  1063. }
  1064. })
  1065. },
  1066. open() {
  1067. },
  1068. close() {
  1069. this.showadd = !this.showadd
  1070. },
  1071. closes() {
  1072. this.showziliao = !this.showziliao
  1073. },
  1074. closest() {
  1075. this.showAnswer = !this.showAnswer
  1076. },
  1077. closestred() {
  1078. this.showAnswerred = !this.showAnswerred
  1079. },
  1080. closeanswer() {
  1081. this.answerbtn = !this.answerbtn
  1082. },
  1083. // 关闭小黄车
  1084. closeshop() {
  1085. this.shopping = !this.shopping
  1086. },
  1087. longPress() {
  1088. this.timeout = setTimeout(() => {
  1089. this.isLongPress = true;
  1090. // 执行保存图片的操作
  1091. uni.saveImageToPhotosAlbum({
  1092. filePath: this.livedata.qwQrCode, // 图片的本地路径或网络路径
  1093. success: () => {
  1094. uni.showToast({
  1095. title: '保存成功'
  1096. });
  1097. },
  1098. fail: () => {
  1099. uni.showToast({
  1100. title: '',
  1101. icon: 'none'
  1102. });
  1103. }
  1104. });
  1105. }, 500); // 设置长按的阈值时间,这里是500毫秒
  1106. },
  1107. cancelLongPress() {
  1108. clearTimeout(this.timeout);
  1109. this.isLongPress = false;
  1110. },
  1111. // 触摸开始
  1112. handleTouchStart() {
  1113. this.isZoom = true; // 触发放大效果
  1114. },
  1115. // 触摸结束
  1116. handleTouchEnd() {
  1117. this.isZoom = false; // 恢复原状
  1118. },
  1119. getEWechatSdk() {
  1120. // 只在H5平台执行
  1121. // #ifdef H5
  1122. let eWechatSdk = '';
  1123. if (/(Android)/i.test(navigator.userAgent)) {
  1124. eWechatSdk = 'jWeixin';
  1125. } else if (/(iPhone|iPad|iPod|iOS)/i.test(navigator.userAgent)) {
  1126. eWechatSdk = 'wx';
  1127. } else {
  1128. eWechatSdk = 'jWeixin';
  1129. }
  1130. uni.setStorageSync("wxSdk", eWechatSdk);
  1131. // #endif
  1132. },
  1133. closeWebSocket() {
  1134. clearInterval(this.pingpangTimes);
  1135. clearTimeout(this.reconnectTimer);
  1136. if (socket && isSocketOpen) {
  1137. try {
  1138. uni.closeSocket();
  1139. isSocketOpen = false;
  1140. console.log('WebSocket已主动关闭');
  1141. } catch (e) {
  1142. console.error('关闭WebSocket时出错:', e);
  1143. }
  1144. }
  1145. },
  1146. reConnect() {
  1147. var that = this;
  1148. try {
  1149. uni.closeSocket();
  1150. } catch (e) {
  1151. }
  1152. setTimeout(function() {
  1153. that.initSocket();
  1154. }, 10000);
  1155. },
  1156. startHeartbeat() {
  1157. clearInterval(this.pingpangTimes);
  1158. // 实现心跳检测逻辑
  1159. console.log('开始心跳检测');
  1160. this.pingpangTimes = setInterval(() => {
  1161. if (isSocketOpen) {
  1162. const pingData = {
  1163. cmd: 'ping',
  1164. timestamp: new Date().getTime()
  1165. };
  1166. socket.send({
  1167. data: JSON.stringify(pingData),
  1168. success: () => {
  1169. console.log('心跳发送成功');
  1170. },
  1171. fail: (err) => {
  1172. console.error('心跳发送失败:', err);
  1173. // 心跳失败也触发重连
  1174. this.scheduleReconnect();
  1175. }
  1176. });
  1177. }
  1178. }, 30000);
  1179. },
  1180. initSocket() {
  1181. if (isSocketOpen) {
  1182. console.log('WebSocket 已连接,无需重复初始化');
  1183. return;
  1184. }
  1185. if (!this.liveId) {
  1186. console.warn('liveId为空,不尝试WebSocket连接');
  1187. return;
  1188. }
  1189. if (this.reconnectCount >= this.maxReconnectAttempts) {
  1190. console.log('已达到最大重连次数,不再尝试连接');
  1191. uni.showToast({
  1192. title: '连接失败,请刷新页面重试',
  1193. icon: 'none'
  1194. });
  1195. return;
  1196. }
  1197. this.userinfo = JSON.parse(uni.getStorageSync("userInfo") || '{}');
  1198. if (!this.userinfo.userId) {
  1199. console.warn('用户信息不存在,不尝试WebSocket连接');
  1200. return;
  1201. }
  1202. // 先清除之前的连接和心跳
  1203. this.closeWebSocket();
  1204. let signature = CryptoJS.HmacSHA256(
  1205. CryptoJS.enc.Utf8.parse(this.liveId?.toString() + this.userinfo.userId + this.userType + this
  1206. .timestamp),
  1207. CryptoJS.enc.Utf8.parse(this.timestamp)).toString(CryptoJS.enc.Hex);
  1208. const that = this;
  1209. // 创建一个新的socket连接
  1210. socket = uni.connectSocket({
  1211. url: wsUrl + "?userId=" + this.userinfo.userId + "&liveId=" + this.liveId + "&userType=" + this
  1212. .userType +
  1213. "&timestamp=" + this.timestamp + "&signature=" + signature,
  1214. multiple: true,
  1215. success: res => {
  1216. this.reconnectCount++; // 增加重连计数
  1217. // 清除之前的心跳定时器
  1218. clearInterval(this.pingpangTimes);
  1219. uni.onSocketMessage((res) => {
  1220. if (res.data.code == 500) {
  1221. uni.showToast({
  1222. title: res.data.msg,
  1223. icon: 'none',
  1224. duration: 2000
  1225. });
  1226. }
  1227. const redata = JSON.parse(res.data);
  1228. console.log("WebSocket拿到的东西", redata)
  1229. this.talklist.push(redata.data)
  1230. this.$nextTick(() => {
  1231. this.scrollIntoView = `list_${this.talklist.length-1}`
  1232. })
  1233. if (redata.cmd == 'deleteId') {
  1234. uni.$emit('deleteId');
  1235. } else if (redata.cmd == 'init') {
  1236. uni.$emit('init', redata.data);
  1237. } else if (redata.cmd == 'reload') {
  1238. uni.$emit('reload');
  1239. } else if (redata.cmd == 'red') {
  1240. // 领红包
  1241. console.log(" 领红包>>",redata)
  1242. this.redInfo=redata.data
  1243. let redId=redata.data.redId
  1244. this.redInfo.redNum=redata.data.redNum
  1245. this.redInfo.redStatus=redata.data.redStatus
  1246. this.redInfo.duration=redata.data.duration
  1247. } else if (redata.data.cmd == 'sendRedPacketQuestion') {
  1248. const list = JSON.parse(redata.data.data)
  1249. this.redanswerAll = [...this.redanswerAll, ...list]
  1250. if (this.redanswerAll[1].randomAmount !== null) {
  1251. this.redanswertips = JSON.parse(this.redanswerAll[0].randomAmount)
  1252. }
  1253. console.log(this.redanswertips)
  1254. console.log(this.redanswerAll)
  1255. } else if (redata.data.cmd == 'entry') {
  1256. this.showWelcomeMessage = true
  1257. // setTimeout(() => {
  1258. // this.showWelcomeMessage = false;
  1259. // }, 1000);
  1260. uni.$emit('entry', redata.data);
  1261. }
  1262. })
  1263. },
  1264. fail: res => {
  1265. uni.$emit('websocket', 0);
  1266. console.log(res);
  1267. this.scheduleReconnect();
  1268. },
  1269. })
  1270. //监听socket打开
  1271. uni.onSocketOpen(() => {
  1272. isSocketOpen = true;
  1273. this.reconnectCount = 0; // 重置重连计数器
  1274. console.log('WebSocket 连接成功');
  1275. this.startHeartbeat();
  1276. });
  1277. //监听socket关闭
  1278. uni.onSocketClose(() => {
  1279. isSocketOpen = false;
  1280. console.log('WebSocket连接已关闭!');
  1281. this.scheduleReconnect();
  1282. });
  1283. //监听socket错误
  1284. uni.onSocketError((err) => {
  1285. isSocketOpen = false;
  1286. console.error('WebSocket 连接错误:', err);
  1287. this.scheduleReconnect();
  1288. });
  1289. },
  1290. scheduleReconnect() {
  1291. if (this.reconnectCount >= this.maxReconnectAttempts) {
  1292. console.log('已达到最大重连次数,不再尝试连接');
  1293. uni.showToast({
  1294. title: '连接失败,请刷新页面重试',
  1295. icon: 'none'
  1296. });
  1297. return;
  1298. }
  1299. // 清除之前的定时器和连接
  1300. clearTimeout(this.reconnectTimer);
  1301. this.closeWebSocket();
  1302. // 指数退避策略,重连间隔逐渐增加
  1303. const delay = Math.min(1000 * Math.pow(2, this.reconnectCount), 30000);
  1304. console.log(`将在 ${delay}ms 后尝试第 ${this.reconnectCount + 1} 次重连`);
  1305. this.reconnectTimer = setTimeout(() => {
  1306. this.initSocket();
  1307. }, delay);
  1308. },
  1309. sendMsg() {
  1310. if (isSocketOpen) {
  1311. const data = {
  1312. liveId: this.liveId,
  1313. userId: this.userinfo.userId,
  1314. userType: 0,
  1315. cmd: "sendMsg",
  1316. msg: this.value,
  1317. nickName: this.userinfo.nickName,
  1318. avatar: this.userinfo.avatar
  1319. }
  1320. if (this.value == "") {
  1321. uni.showToast({
  1322. title: "不能发送空消息",
  1323. icon: 'none',
  1324. });
  1325. } else {
  1326. socket.send({
  1327. data: JSON.stringify(data),
  1328. success: () => {
  1329. console.log("发送成功")
  1330. this.value = ''
  1331. },
  1332. fail: () => {
  1333. console.log("发送失败")
  1334. }
  1335. })
  1336. }
  1337. }
  1338. },
  1339. },
  1340. };
  1341. </script>
  1342. <style scoped lang="scss">
  1343. /* button自带样式清除 */
  1344. .student-orther-icon button::after {
  1345. border: none !important;
  1346. padding: 0 !important;
  1347. margin: 0 !important;
  1348. }
  1349. .student-orther-icon button {
  1350. background-color: transparent !important;
  1351. padding: 0 !important;
  1352. line-height: inherit !important;
  1353. margin: 0 !important;
  1354. width: auto !important;
  1355. font-weight: 500 !important;
  1356. border-radius: none !important;
  1357. }
  1358. // 抽奖
  1359. // .answerpop {
  1360. // background: linear-gradient(to right, #fff7f8, #fee1e2);
  1361. // margin-top: 30rpx;
  1362. // padding: 10rpx 20rpx;
  1363. // width: calc(100% - 40rpx);
  1364. // border-radius: 20rpx;
  1365. // display: flex;
  1366. // align-items: center;
  1367. // justify-content: space-between;
  1368. // box-shadow: 2px 2px 4px 0 rgba(255, 124, 126, 0.1);
  1369. // .answera {
  1370. // display: flex;
  1371. // justify-content: center;
  1372. // background-color: #fff;
  1373. // padding: 20rpx 0;
  1374. // width: 35%;
  1375. // border-radius: 40rpx;
  1376. // align-items: center;
  1377. // margin: 10rpx 0;
  1378. // box-shadow: 2px 2px 4px 0 rgba(72, 72, 72, 0.1);
  1379. // image {
  1380. // width: 40rpx;
  1381. // height: 40rpx;
  1382. // margin-right: 10rpx;
  1383. // }
  1384. // }
  1385. // }
  1386. // .submitbtn {
  1387. // width: 260rpx;
  1388. // margin: 0 auto;
  1389. // text-align: center;
  1390. // border-radius: 80rpx;
  1391. // padding: 20rpx 0;
  1392. // color: #fff;
  1393. // background-color: #ff5c03;
  1394. // }
  1395. // .itemanswer {
  1396. // padding: 20rpx 20rpx;
  1397. // text-align: center;
  1398. // border-radius: 80rpx;
  1399. // border: 2rpx solid #dddddd;
  1400. // margin: 12rpx 0;
  1401. // color: #555;
  1402. // width: calc(100% - 40rpx);
  1403. // }
  1404. // .answeract {
  1405. // background-color: rgba(0, 202, 166, 1);
  1406. // color: #fff;
  1407. // }
  1408. // .answerbox {
  1409. // width: 600rpx;
  1410. // border-radius: 20rpx;
  1411. // }
  1412. .welcome-message {
  1413. position: fixed;
  1414. width: 100%;
  1415. bottom: 120rpx;
  1416. left: 50%;
  1417. transform: translateX(-50%);
  1418. color: white;
  1419. padding: 10px 20px;
  1420. border-radius: 20px;
  1421. animation: fadeOut 1s ease 1s forwards;
  1422. z-index: 1000;
  1423. }
  1424. @keyframes fadeOut {
  1425. from {
  1426. opacity: 1;
  1427. }
  1428. to {
  1429. opacity: 0;
  1430. }
  1431. }
  1432. // .container {
  1433. // position: relative;
  1434. // width: 100%;
  1435. // height: 100vh;
  1436. // overflow: hidden;
  1437. // }
  1438. // .talktext {
  1439. // border-radius: 8rpx;
  1440. // background-color: rgba(255, 255, 255, 0.1);
  1441. // view {
  1442. // width: 100%;
  1443. // }
  1444. // }
  1445. .talk-list {
  1446. border-radius: 30rpx;
  1447. background-color: rgba(33, 33, 33, 0.5);
  1448. padding: 10rpx 30rpx;
  1449. }
  1450. // .zoom-button-active {
  1451. // transform: scale(1.5);
  1452. // }
  1453. // .background-image {
  1454. // position: absolute;
  1455. // top: 0;
  1456. // left: -40rpx;
  1457. // width: 110%;
  1458. // height: 110%;
  1459. // object-fit: cover;
  1460. // filter: blur(20px);
  1461. // z-index: 0;
  1462. // }
  1463. // .background-images {
  1464. // position: absolute;
  1465. // top: 0;
  1466. // left: -40rpx;
  1467. // width: 110%;
  1468. // height: 110%;
  1469. // object-fit: cover;
  1470. // filter: blur(20px);
  1471. // z-index: 6;
  1472. // }
  1473. // .blackbg {
  1474. // position: absolute;
  1475. // top: 0;
  1476. // left: 0;
  1477. // width: 100%;
  1478. // height: 100%;
  1479. // background: rgba(0, 0, 0, 0.7);
  1480. // object-fit: cover;
  1481. // filter: blur(10px);
  1482. // z-index: 1;
  1483. // }
  1484. .content {
  1485. position: relative;
  1486. z-index: 2;
  1487. height: 100%;
  1488. width: 100%;
  1489. top: 0;
  1490. left: 0;
  1491. display: flex;
  1492. flex-direction: column;
  1493. justify-content: space-between;
  1494. .content-top {
  1495. width: 100%;
  1496. margin-top: 150rpx;
  1497. display: flex;
  1498. align-items: center;
  1499. justify-content: space-between;
  1500. padding: 0 24rpx;
  1501. box-sizing: border-box;
  1502. .sum {
  1503. width: 80rpx;
  1504. height: 52rpx;
  1505. background: rgba(0, 0, 0, 0.5);
  1506. border-radius: 26rpx 26rpx 26rpx 26rpx;
  1507. font-size: 24rpx;
  1508. color: #FFFFFF;
  1509. text-align: center;
  1510. line-height: 52rpx;
  1511. }
  1512. }
  1513. .follow-btn {
  1514. padding: 8rpx 16rpx;
  1515. background: linear-gradient(270deg, #FF5C03 0%, #FFAC64 100%);
  1516. border-radius: 26rpx;
  1517. font-weight: 500;
  1518. font-size: 26rpx;
  1519. color: #FFFFFF;
  1520. }
  1521. }
  1522. .videolist {
  1523. position: relative;
  1524. }
  1525. .video {
  1526. height: 100vh;
  1527. /* 占屏幕高度的80% */
  1528. width: 100%;
  1529. background-color: rgba(0, 0, 0, 0.8);
  1530. }
  1531. .videotop {
  1532. width: 100%;
  1533. height: 100%;
  1534. }
  1535. .video_row {
  1536. width: 100%;
  1537. max-height: 500rpx;
  1538. overflow: hidden;
  1539. margin-top: 300rpx;
  1540. }
  1541. .popup-video {
  1542. position: absolute;
  1543. top: 30%;
  1544. height: 500rpx;
  1545. display: flex;
  1546. flex-direction: column;
  1547. align-items: center;
  1548. justify-content: center;
  1549. width: 100%;
  1550. color: #fff;
  1551. z-index: 9;
  1552. .more {
  1553. background-color: #3280fe;
  1554. border-radius: 80rpx;
  1555. width: 280rpx;
  1556. text-align: center;
  1557. height: 60rpx;
  1558. line-height: 60rpx;
  1559. }
  1560. }
  1561. .icon-bg {
  1562. background-color: rgba(157, 157, 157, 0.8);
  1563. border-radius: 50%;
  1564. width: 72rpx;
  1565. height: 72rpx;
  1566. display: flex;
  1567. justify-content: center;
  1568. align-items: center;
  1569. transition: transform 0.2s ease;
  1570. }
  1571. .list {
  1572. width: 80%;
  1573. margin-bottom: 20rpx;
  1574. animation: xxxawdawd .2s;
  1575. }
  1576. @keyframes xxxawdawd {
  1577. from {
  1578. margin-top: 0rpx;
  1579. opacity: 0;
  1580. }
  1581. to {
  1582. margin-top: 20rpx;
  1583. opacity: 1;
  1584. }
  1585. }
  1586. .shop-prompt {
  1587. position: absolute;
  1588. bottom: 600rpx;
  1589. left: 24rpx;
  1590. padding: 6rpx 20rpx;
  1591. background: rgba(230, 154, 34, 0.7);
  1592. border-radius: 24rpx;
  1593. z-index: 9;
  1594. font-weight: 500;
  1595. font-size: 26rpx;
  1596. color: #FFFFFF;
  1597. transition: opacity 0.3s ease;
  1598. }
  1599. .siderow-group {
  1600. position: absolute;
  1601. top: 21%;
  1602. right: 50rpx;
  1603. z-index: 9;
  1604. display: flex;
  1605. flex-direction: column;
  1606. align-items: center;
  1607. .side-item {
  1608. font-weight: 500;
  1609. font-size: 24rpx;
  1610. color: #FFFFFF;
  1611. margin-bottom: 32rpx;
  1612. text-align: center;
  1613. button {
  1614. background-color: transparent;
  1615. margin: 0;
  1616. line-height: 1;
  1617. padding: 0;
  1618. }
  1619. image {
  1620. width: 72rpx;
  1621. height: 72rpx;
  1622. }
  1623. }
  1624. }
  1625. .side-group {
  1626. position: absolute;
  1627. top: 30%;
  1628. right: 50rpx;
  1629. z-index: 9;
  1630. display: flex;
  1631. flex-direction: column;
  1632. align-items: center;
  1633. .side-item {
  1634. font-weight: 500;
  1635. font-size: 24rpx;
  1636. color: #FFFFFF;
  1637. margin-bottom: 32rpx;
  1638. text-align: center;
  1639. button {
  1640. background-color: transparent;
  1641. margin: 0;
  1642. line-height: 1;
  1643. padding: 0;
  1644. }
  1645. image {
  1646. width: 72rpx;
  1647. height: 72rpx;
  1648. }
  1649. }
  1650. }
  1651. .shoppop {
  1652. padding: 22rpx 16rpx;
  1653. .shoppop-top {
  1654. display: flex;
  1655. justify-content: space-between;
  1656. align-items: center;
  1657. padding: 0 16rpx 22rpx;
  1658. .search-input {
  1659. width: 414rpx;
  1660. height: 76rpx;
  1661. background: #FFFFFF;
  1662. border-radius: 36rpx;
  1663. margin-left: 20rpx;
  1664. padding: 0 32rpx;
  1665. box-sizing: border-box;
  1666. font-size: 24rpx;
  1667. margin-right: 24rpx;
  1668. }
  1669. .search-top {
  1670. font-size: 18rpx;
  1671. color: #222222;
  1672. }
  1673. }
  1674. .shop-list {
  1675. overflow: hidden;
  1676. .list-item {
  1677. display: flex;
  1678. align-items: center;
  1679. padding: 20rpx 16rpx;
  1680. background: #FFFFFF;
  1681. border-radius: 16rpx;
  1682. margin-bottom: 16rpx;
  1683. .goods-img {
  1684. width: 200rpx;
  1685. height: 200rpx;
  1686. border-radius: 16rpx;
  1687. overflow: hidden;
  1688. position: relative;
  1689. margin-right: 24rpx;
  1690. image {
  1691. width: 100%;
  1692. height: 100%;
  1693. }
  1694. .goods-label {
  1695. position: absolute;
  1696. top: 0;
  1697. width: 64rpx;
  1698. height: 40rpx;
  1699. background: rgba(0, 0, 0, 0.5);
  1700. border-radius: 16rpx 0rpx 16rpx 0rpx;
  1701. text-align: center;
  1702. font-weight: 500;
  1703. font-size: 28rpx;
  1704. color: #FFFFFF;
  1705. }
  1706. }
  1707. .goods-right {
  1708. flex: 1;
  1709. .goods-title {
  1710. font-weight: 500;
  1711. font-size: 28rpx;
  1712. color: #000000;
  1713. }
  1714. .goods-details {
  1715. font-size: 24rpx;
  1716. color: #999999;
  1717. margin: 10rpx 0 20rpx;
  1718. }
  1719. .goods-people {
  1720. font-size: 22rpx;
  1721. color: #E69A22;
  1722. height: 56rpx;
  1723. }
  1724. .goods-shop {
  1725. display: flex;
  1726. justify-content: space-between;
  1727. .nummber {
  1728. color: #FF5C03;
  1729. font-size: 22rpx;
  1730. font-weight: 500;
  1731. }
  1732. .btn-group {
  1733. text-align: center;
  1734. line-height: 56rpx;
  1735. .collect-btn {
  1736. width: 72rpx;
  1737. background: #F5F7FA;
  1738. border-radius: 8rpx 0rpx 0rpx 8rpx;
  1739. }
  1740. .shop-btn {
  1741. width: 152rpx;
  1742. background: linear-gradient(270deg, #FF5C03 0%, #FFAC64 100%);
  1743. border-radius: 0rpx 8rpx 8rpx 0rpx;
  1744. font-weight: 500;
  1745. font-size: 26rpx;
  1746. color: #FFFFFF;
  1747. }
  1748. }
  1749. }
  1750. }
  1751. }
  1752. }
  1753. }
  1754. :deep(.u-list-item) {
  1755. width: 100%;
  1756. display: flex;
  1757. align-items: center;
  1758. }
  1759. </style>