video.vue 68 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800180118021803180418051806180718081809181018111812181318141815181618171818181918201821182218231824182518261827182818291830183118321833183418351836183718381839184018411842184318441845184618471848184918501851185218531854185518561857185818591860186118621863186418651866186718681869187018711872187318741875187618771878187918801881188218831884188518861887188818891890189118921893189418951896189718981899190019011902190319041905190619071908190919101911191219131914191519161917191819191920192119221923192419251926192719281929193019311932193319341935193619371938193919401941194219431944194519461947194819491950195119521953195419551956195719581959196019611962196319641965196619671968196919701971197219731974197519761977197819791980198119821983198419851986198719881989199019911992199319941995199619971998199920002001200220032004200520062007200820092010201120122013201420152016201720182019202020212022202320242025202620272028202920302031203220332034203520362037203820392040204120422043204420452046204720482049205020512052205320542055205620572058205920602061206220632064206520662067206820692070207120722073207420752076207720782079208020812082208320842085208620872088208920902091209220932094209520962097209820992100210121022103210421052106210721082109211021112112211321142115211621172118211921202121212221232124212521262127212821292130213121322133213421352136213721382139214021412142214321442145214621472148214921502151215221532154215521562157215821592160216121622163216421652166216721682169217021712172217321742175217621772178217921802181218221832184218521862187218821892190219121922193219421952196219721982199220022012202220322042205220622072208220922102211221222132214221522162217221822192220222122222223222422252226222722282229223022312232223322342235223622372238223922402241224222432244224522462247224822492250225122522253225422552256225722582259226022612262226322642265226622672268226922702271227222732274227522762277227822792280228122822283228422852286228722882289229022912292229322942295229622972298229923002301230223032304230523062307230823092310231123122313231423152316231723182319232023212322232323242325232623272328232923302331233223332334233523362337233823392340234123422343234423452346234723482349235023512352235323542355235623572358235923602361236223632364236523662367236823692370237123722373237423752376237723782379238023812382238323842385238623872388238923902391239223932394239523962397239823992400240124022403240424052406240724082409241024112412241324142415241624172418241924202421242224232424242524262427242824292430243124322433243424352436243724382439244024412442244324442445244624472448244924502451245224532454245524562457245824592460246124622463246424652466246724682469247024712472247324742475247624772478247924802481248224832484248524862487248824892490249124922493249424952496249724982499250025012502250325042505250625072508250925102511251225132514251525162517251825192520252125222523252425252526252725282529253025312532253325342535253625372538253925402541254225432544254525462547254825492550255125522553255425552556255725582559256025612562256325642565256625672568
  1. <template>
  2. <view class="content">
  3. <view class="header-nav" :style="{height: `calc(88rpx + ${statusBarHeight}px)`,paddingTop: statusBarHeight + 'px'}">
  4. <view class="arrow-left-warning" @click="feedback">
  5. <image src="/static/images/warning.png"></image>
  6. <text>投诉</text>
  7. </view>
  8. <view class="header-title" :style="{width:menuButtonLeft + 'px',height:menuButtonH+'px',lineHeight:menuButtonH+'px'}">{{courseInfo.title}}</view>
  9. </view>
  10. <view class="video-box">
  11. <image v-if="!isLogin || isAddKf!=1" class="video-poster" :src="courseInfo.imgUrl" mode="aspectFill">
  12. </image>
  13. <video
  14. @timeupdate="onTimeUpdate"
  15. @progress="progressChange"
  16. @error="videoErrorCallback"
  17. @play="getPlay"
  18. @pause="getPause"
  19. @ended="getEnded"
  20. @fullscreenchange="fullscreenchange"
  21. @controlstoggle="controlstoggle"
  22. @waiting="getWaiting"
  23. :title="courseInfo.title"
  24. style="width: 100%;height: 420rpx;"
  25. :poster="poster"
  26. id="video-content-box"
  27. controls
  28. :show-fullscreen-btn="true"
  29. :auto-pause-if-open-native="true"
  30. :auto-pause-if-navigate="true"
  31. :enable-progress-gesture="false"
  32. :show-progress="true"
  33. :picture-in-picture-mode="[]"
  34. :show-background-playback-button="false"
  35. :src="videoUrl"
  36. >
  37. <!-- :danmu-list="danmuList"
  38. enable-danmu
  39. danmu-btn -->
  40. <!-- 弹幕展示 -->
  41. <template v-if="showDanmu==1&&openCommentStatus==2">
  42. <text v-for="(item, index) in activeDanmus" :key="item.commentId" class="danmu-item danmuMove"
  43. :style="item.danmustyle" @animationend="animationend(item,index)">
  44. {{ item.content }}
  45. </text>
  46. </template>
  47. <cover-view class="video-danmu-btnbox" :style="{display: openCommentStatus==2&&isfull&&crtShow&&isLogin&&isAddKf==1 ? 'block':'none'}">
  48. <cover-image class="video-danmu-image"
  49. :src="baseUrl+'/images/danmu_set.png'"
  50. @click="openDanmu(1)"></cover-image>
  51. </cover-view>
  52. <treasureChest ref="treasureChest" :isfull="isfull" :onHide="onHide" :boxList="fsUserCourseRewardRoundStatusVO" @claimFun="claimFun"></treasureChest>
  53. </video>
  54. </view>
  55. <!-- 弹幕方法 -->
  56. <commentBox
  57. v-if="openCommentStatus==2"
  58. ref="danmuBox"
  59. :height="height"
  60. :urlOption="urlOption"
  61. :time="playTime"
  62. :showDanmu="showDanmu"
  63. :viewCommentNum="viewCommentNum"
  64. :openCommentStatus="openCommentStatus"
  65. :isSocketOpen="isSocketOpen"
  66. @socketSend="socketSend"
  67. @initSocket="initSocket"
  68. @setInputText="setInputText"
  69. @getScrollTop="getScrollTop"
  70. @getMore="getMore"
  71. @getActiveDanmus="getActiveDanmus"></commentBox>
  72. <view id="title-contentnav">
  73. <template v-show="isLogin&&notice">
  74. <u-notice-bar :text="notice" color="red" speed='60' fontSize="18"></u-notice-bar>
  75. </template>
  76. </view>
  77. <scroll-view
  78. class="scroll-view"
  79. :style="{height: height}"
  80. :scroll-top="scrollTop"
  81. :scroll-y="true"
  82. :refresher-enabled="currentTab == 2"
  83. :refresher-triggered="triggered"
  84. @refresherrefresh="handleRefresher">
  85. <view style="font-size: 30rpx;color: #999;padding: 10rpx 24rpx;">本页内容受知识产权保护,请勿截屏录屏及转发,感谢理解。</view>
  86. <view class="title-content" v-if="openCommentStatus!=1">
  87. <!-- 答题时展示小节科普名,其他展示科普名 -->
  88. <!-- 小节科普名 -->
  89. <view class="subtitlebox" v-if="isLogin&&isAddKf==1">
  90. {{courseInfo.title|| ''}}
  91. </view>
  92. <!-- 科普名字 -->
  93. <view class="miantitlebox" v-else>
  94. {{courseInfo.courseName|| ''}}
  95. </view>
  96. </view>
  97. <view class="tabbox-bar" v-if="openCommentStatus==1">
  98. <view class="tabbox">
  99. <view :class="currentTab == nav.id ? 'tabbox-active':''" v-for="nav in navList" :key="nav.id" @click="handleTab(nav.id)">{{nav.name}}</view>
  100. </view>
  101. </view>
  102. <template v-if="openCommentStatus==1">
  103. <view v-show="currentTab==0">
  104. <descInfoNav ref="descInfoNav" :isLogin="isLogin" :isAddKf="isAddKf" :courseInfo="courseInfo" :configJson="configJson" :defaultInfo="defaultInfo"></descInfoNav>
  105. </view>
  106. <view v-show="currentTab==2">
  107. <commentBox
  108. ref="commentBox"
  109. :height="height"
  110. :urlOption="urlOption"
  111. :time="playTime"
  112. :flagTime="flagTime"
  113. :showDanmu="showDanmu"
  114. :viewCommentNum="viewCommentNum"
  115. :openCommentStatus="openCommentStatus"
  116. :isSocketOpen="isSocketOpen"
  117. @socketSend="socketSend"
  118. @initSocket="initSocket"
  119. @setInputText="setInputText"
  120. @getScrollTop="getScrollTop"
  121. @getMore="getMore"></commentBox>
  122. </view>
  123. </template>
  124. <view v-show="currentTab==1">
  125. <template v-if="openCommentStatus!=1">
  126. <!-- 介绍 -->
  127. <descInfo ref="descInfo" :isLogin="isLogin" :isAddKf="isAddKf" :courseInfo="courseInfo" :configJson="configJson" :defaultInfo="defaultInfo"></descInfo>
  128. </template>
  129. <!-- 问题 -->
  130. <!-- <template v-if="isLogin&&isAddKf==1&&linkType!=1">
  131. <ques ref="ques" :urlOption="urlOption" :courseNote="courseNote" :showNote="showNote" :treatmentPackage="treatmentPackage" :showTreatment="showTreatment" :quesList="quesList" :openCommentStatus="openCommentStatus" @handleAnswer="handleAnswer" @showBtnType="showBtnType"></ques>
  132. </template> -->
  133. </view>
  134. </scroll-view>
  135. <!-- 线路 -->
  136. <view class="video-line" v-if="isLogin&&isAddKf==1" @click="openPop">
  137. <image src="https://cdn.his.cdwjyyh.com/fs/20250915/04889779f7f44e22a7a378d26823b2e8.png"></image>
  138. <text>线路{{numberToChinese(lineIndex + 1)}}</text>
  139. </view>
  140. <!-- 线路弹窗 -->
  141. <uni-popup ref="popup" type="bottom" class="full-width-popup">
  142. <view class="popupbox">
  143. <view class="popupbox-head">
  144. <text>线路选择</text>
  145. <image class="close-icon" src="https://cdn.his.cdwjyyh.com/fs/20250915/b950ff20d115453f9d7f908590c9c0e7.png" mode="aspectFill" @click="close">
  146. </image>
  147. </view>
  148. <view class="popupbox-content">
  149. <view :class="lineIndex == index ? 'line-item line-active': 'line-item'"
  150. v-for="(it,index) in lineList" :key="index" @click="handleLine(index)">
  151. 线路{{numberToChinese(index + 1)}}</view>
  152. </view>
  153. </view>
  154. </uni-popup>
  155. <!-- 发送弹幕 -->
  156. <view class="video-line danmu-line" v-if="isLogin&&isAddKf==1&&openCommentStatus==2" @click="openDanmu(0)" >
  157. <image class="set_image" src="https://cdn.his.cdwjyyh.com/fs/20250418/5e508642737a44169061382566043ac9.png" mode="aspectFill"></image>
  158. <text>发弹幕</text>
  159. </view>
  160. <!-- 发送弹幕弹窗 -->
  161. <uni-popup ref="danmuPopup" type="bottom" style="z-index: 999;" @change="changeShowPopup">
  162. <view class="danmuPopup" :style="{marginLeft:isfull ? statusBarHeight+'px': 0,marginBottom: danmuboxHeight+'px'}">
  163. <view class="danmuPopup-head border-line">
  164. <image class="danmu-icon" :src="showDanmu==0?baseUrl+'/images/danmu-off.png':baseUrl+'/images/danmu-on.png'" mode="heightFix" @click="switchDanmu()"></image>
  165. <view class="u-border">
  166. <u-input
  167. class="danmuPopup-input"
  168. placeholder="发个弹幕吧~"
  169. border="border"
  170. :focus="focus"
  171. :adjustPosition="false"
  172. :autoBlur="true"
  173. maxlength="50"
  174. v-model.trim="inputText">
  175. </u-input>
  176. <text style="font-size: 24rpx;color: #bbb;margin-left: 10rpx;">{{inputText?inputText.trim().length:0}}/50</text>
  177. </view>
  178. <button class="danmuPopup-send" @click="handleChatInput">发送</button>
  179. </view>
  180. </view>
  181. </uni-popup>
  182. <!-- 答题弹窗 -->
  183. <uni-popup ref="answerPopup" type="center">
  184. <view :class="errTitle == '恭喜你,回答正确' ? 'answerPopup-box bg':'answerPopup-box'">
  185. <!-- 正确 -->
  186. <image class="tipimg" v-if="errTitle == '恭喜你,回答正确'" src="https://cdn.his.cdwjyyh.com/fs/20250915/f7b1075c21b442069ec9721a1a4790ec.png"
  187. mode="aspectFill"></image>
  188. <!-- 错误 -->
  189. <image class="tipimg" v-else src="https://cdn.his.cdwjyyh.com/fs/20250915/a4b145c94b684c778c03ad9b52d717f3.png" mode="aspectFill">
  190. </image>
  191. <view class="answerPopup-title">{{errTitle}}</view>
  192. <view class="answerPopup-desc" v-html="errDesc"></view>
  193. <view style="color: #FF5C03;">{{currentRewardText}}</view>
  194. <!-- 选择奖励 -->
  195. <view class="reward-list" v-if="errTitle == '恭喜你,回答正确'">
  196. <view :class="item.value == currentReward?'reward-item reward-active':'reward-item'"
  197. v-for="(item, index) in rewardType" :key="item.value" @click="rewardChange(item)">
  198. {{item.name}}
  199. </view>
  200. <!-- <radio-group class="reward-list-group" @change="rewardChange">
  201. <label class="reward-list-option" v-for="(item, index) in rewardType" :key="item.value">
  202. <radio :value="item.value+ ''" :checked="item.value == currentReward"
  203. activeBorderColor="#FF5C03" activeBackgroundColor="#FF5C03"
  204. style="transform:scale(0.7)" color="#FF5C03" />
  205. <view :style="{color: item.value == currentReward ? '#FF5C03':''}">{{item.name}}</view>
  206. </label>
  207. </radio-group> -->
  208. </view>
  209. <!-- 错误题目 -->
  210. <view class="errQuesbox" v-if="errQues&&errQues.length>0">
  211. <view class="errQuesbox-item textOne" v-for="(it,index) in errQues" :key="index">{{it.title}}</view>
  212. </view>
  213. <view class="answerPopup-btn" v-if="errTitle == '恭喜你,回答正确'" @click="closeAnswerPopup">确认</view>
  214. <view class="tipsPopup-btn-box" v-else
  215. :style="{marginTop: errQues&&errQues.length>0 ? '40rpx':'54rpx'}">
  216. <view class="tipsPopup-btn" @click="closeAnswerPopup">{{remain > 0 ? '重新答题': '确认'}}</view>
  217. </view>
  218. </view>
  219. </uni-popup>
  220. <!-- 展示兑换奖品弹窗 -->
  221. <appPopup ref="appPopup"></appPopup>
  222. <!-- 客服二维码弹窗 -->
  223. <uni-popup ref="kfPopup" type="center" :mask-click="false">
  224. <view class="kfqrcode-box">
  225. <image class="kfqrcode" v-if="qrcode" :src="qrcode" show-menu-by-longpress="true"></image>
  226. <view v-show="qrcodeMsg" style="margin-top: 30rpx;" v-html="qrcodeMsg"></view>
  227. <image class="kfqrcode-close" src="https://cdn.his.cdwjyyh.com/fs/20250915/97978486cc6c47c6949c5bcc9bc0c4a3.png" mode="aspectFill"
  228. @click="closeKFPop"></image>
  229. </view>
  230. </uni-popup>
  231. <!-- 可以答题提示 -->
  232. <view class="answerTip" v-if="currentTab!=1&&openCommentStatus==1&&showAnswerTip" @click="handleTab(1)">
  233. 可以答题啦
  234. </view>
  235. <!-- footer -->
  236. <view class="footer" v-show="currentTab!=2&&videoId">
  237. <view class="footer-btn" v-if="!isLogin || isAddKf !=1" @click="submit">
  238. <text>立即学习</text>
  239. </view>
  240. <view v-if="isLogin&&isAddKf==1&&linkType!=1&&currentTab==1&&quesList&&quesList.length>0&&showBtn==0" class="footer-btn footer-btn-border" @click="submit">
  241. <text>提交答案领取奖励</text>
  242. </view>
  243. </view>
  244. <view v-show="currentTab==2">
  245. <view class="chatinput" :style="{bottom:danmuboxHeight>0?danmuboxHeight+'px':'calc(var(--window-bottom) + 24rpx)'}">
  246. <input class="uni-input" v-model.trim="inputText" :adjustPosition="false" :autoBlur="false" maxlength="140" placeholder="发消息···" confirm-type="send" @confirm="handleChatInput" />
  247. <button class="send" @click="handleChatInput">发送</button>
  248. </view>
  249. </view>
  250. <turntableOne ref="turntableOne" @sendRewardFun="sendRewardFun" @openAppPop="openAppPop"></turntableOne>
  251. <turntableTwo ref="turntableTwo" @sendRewardFun="sendRewardFun" @openAppPop="openAppPop"></turntableTwo>
  252. <courseExpiration v-if="showExpiration" :code="resCode" :msg="resMsg" :qrcode="qrcode" :userId="user && user.userId ? user.userId : ''"></courseExpiration>
  253. <yk-screenRecord></yk-screenRecord>
  254. <userlogo ref="userlogo" @editUserSuccess="editUserSuccess"></userlogo>
  255. </view>
  256. </template>
  257. <script>
  258. import userlogo from "./components/userlogo"
  259. import courseExpiration from "./components/courseExpiration"
  260. import ykScreenRecord from './components/yk-screenRecord/yk-screenRecord';
  261. import { mapGetters } from 'vuex';
  262. import turntableOne from "./components/turntableOne.vue"
  263. import turntableTwo from "./components/turntableTwo.vue"
  264. import appPopup from "./components/appPopup.vue"
  265. import ques from "./components/ques.vue"
  266. import descInfo from "./components/descInfo.vue"
  267. import descInfoNav from "./components/descInfoNav.vue"
  268. import commentBox from "./components/commentBox.vue"
  269. import treasureChest from "./components/treasureChest.vue"
  270. import dayjs from 'dayjs';
  271. import {
  272. getErrMsg,
  273. getH5CourseByVideoId,
  274. getH5CourseVideoDetails,
  275. courseAnswer,
  276. getFinishCourseVideo,
  277. getIsAddKf,
  278. internetTraffic,
  279. getIntegralByH5Video,
  280. sendReward,
  281. loginByMp,
  282. getRealLink,
  283. getConfigByKey,
  284. claim,
  285. getVideoRewardTypes,
  286. getUserPhoneDeviceType,
  287. handleFsUserWx,
  288. getByAppId
  289. } from "@/api/course.js"
  290. export default {
  291. components: {
  292. descInfoNav,
  293. descInfo,
  294. commentBox,
  295. ques,
  296. treasureChest,
  297. appPopup,
  298. turntableOne,
  299. turntableTwo,
  300. ykScreenRecord,
  301. courseExpiration,
  302. userlogo
  303. },
  304. data() {
  305. return {
  306. socket: null,
  307. isSocketOpen: false,
  308. pingpangTimes: null,
  309. baseUrl:uni.getStorageSync('requestImagesPath'),
  310. // 1 红包 2 芳华币
  311. rewardType:[],
  312. currentReward: -1,
  313. player: null,
  314. loading: true,
  315. progress: 0,
  316. code: null,
  317. statusBarHeight: uni.getSystemInfoSync().statusBarHeight,
  318. scrollTop: 0,
  319. height: '0px',
  320. isLogin: false,
  321. videoUrl: "",
  322. videoId: "",
  323. //现在的时长
  324. playTime: 0,
  325. //总时长
  326. duration: 0,
  327. playDuration: 0,
  328. // 用于续播
  329. playDurationSeek: 0,
  330. // 温馨提醒时间节点,
  331. tipsTime: 0,
  332. tipsOpen: false,
  333. config: {},
  334. courseInfo: {},
  335. quesList: [],
  336. lineList: [],
  337. // 错题
  338. errQues: [],
  339. // 答题机会
  340. remain: 0,
  341. errTitle: "",
  342. errDesc: "",
  343. showPlay: true,
  344. showControls: false,
  345. playStatus: "",
  346. isfull: false,
  347. isAddKf: 0,
  348. lineIndex: 0,
  349. textHeight: 0, //文本高度
  350. qwUserId: "",
  351. qrcode: "",
  352. corpId: "",
  353. qrcodeMsg: "",
  354. urlOption: {},
  355. bufferRate: 0, // 缓冲时间
  356. uuId: "",
  357. isEnded: false,
  358. // 是否允许拖动进度条
  359. linkType: 0,
  360. ip: null,
  361. checked: true,
  362. isFinish: 0, // 是否完课
  363. interval: null,
  364. intervalIntegral: null, // 芳华币定时
  365. options: {
  366. sources: [{
  367. src: ""
  368. }],
  369. poster: "",
  370. live: false /* 是否直播 */ ,
  371. controls: true,
  372. autoplay: false,
  373. licenseUrl: 'https://license.vod2.myqcloud.com/license/v2/1323137866_1/v_cube.license', // license 地址,参考准备工作部分,在视立方控制台申请 license 后可获得 licenseUrl,
  374. LicenseKey: 'bcc5bd9a14b798b48c52ff005a21d926',
  375. controlBar: {
  376. volumePanel: false,
  377. playbackRateMenuButton: false,
  378. QualitySwitcherMenuButton: false,
  379. // progressControl: false
  380. },
  381. plugins: {
  382. // ProgressMarker: false,
  383. ContextMenu: {
  384. statistic: false
  385. }
  386. },
  387. },
  388. poster: "",
  389. // 错误请求次数
  390. errorCount: 0,
  391. answerPopup: false,
  392. sortLink:"",
  393. // 科普是否过期
  394. isExpire: false,
  395. menuButtonLeft: 281,
  396. menuButtonH: 45,
  397. timer: null,
  398. flag: false,
  399. focus: false,
  400. openDanmuType: 0,
  401. danmuboxHeight: 0,
  402. user: {},
  403. crtShow: true,
  404. // 是否获取到对应观看者的真实链接
  405. isCheckRealUrl: false,
  406. courseLogo: '',
  407. isfull: false,
  408. navList:[{
  409. id: 0,
  410. name: '介绍'
  411. },{
  412. id: 1,
  413. name: '答题'
  414. },{
  415. id: 2,
  416. name: '评论'
  417. }],
  418. currentTab: 1,
  419. triggered: false,
  420. // 没有更多
  421. isMore: false,
  422. inputText:"",
  423. // 获取最多历史评论条数
  424. viewCommentNum: 200,
  425. // 1-开启评论;2-开启弹幕;3-关闭
  426. openCommentStatus: 3,
  427. showAnswerTip: false,
  428. showDanmu: 1,
  429. activeDanmus:[],
  430. flagTime: 0,
  431. notice: '',
  432. fsUserCourseRewardRoundStatusVO: [],
  433. onHide: false,
  434. courseNote:'',
  435. showBtn: 0,
  436. showNote: 0,
  437. showTreatment: 1, // 1不展示,0展示
  438. treatmentPackage: [],
  439. resMsg:'',
  440. resCode: '',
  441. showExpiration: false,
  442. authType: 1, // 授权方式
  443. configJson: {
  444. broadcasting: '绿依享大药房互联网医院',
  445. coverImg: "",
  446. network: '芳华未来',
  447. support: '华夏文化促进会大健康养生文化专委会',
  448. team: '杜丁-副主任医师,于晓津-副主任医师,史士昊-主治医师,岳晓燕-主任医师,陈涛-执业医师,李俭-副主任医师',
  449. tv: '贵州广播电视台公共频道,大石桥综合频道',
  450. unit: '大石桥市融媒之声广播电视报刊,网络新传媒有限公司'
  451. },
  452. defaultInfo: {
  453. broadcasting: '绿依享大药房互联网医院',
  454. coverImg: "",
  455. network: '芳华未来',
  456. support: '华夏文化促进会大健康养生文化专委会',
  457. team: '杜丁-副主任医师,于晓津-副主任医师,史士昊-主治医师,岳晓燕-主任医师,陈涛-执业医师,李俭-副主任医师',
  458. tv: '贵州广播电视台公共频道,大石桥综合频道',
  459. unit: '大石桥市融媒之声广播电视报刊,网络新传媒有限公司'
  460. }
  461. }
  462. },
  463. computed:{
  464. ...mapGetters(['coureLogin']),
  465. currentRewardText(){
  466. const index = this.rewardType.findIndex(it=>it.value==this.currentReward)
  467. return index > -1 ? this.rewardType[index].text || '':''
  468. }
  469. },
  470. watch: {
  471. coureLogin: {
  472. immediate: true, // 页面一进入就检查一次
  473. handler(val) {
  474. if (val == 2&&this.isLogin) {
  475. console.log("AppToken失效,请重新登录")
  476. this.isLogin = false
  477. this.isAddKf = 0
  478. this.goLogin()
  479. }
  480. }
  481. }
  482. },
  483. onLoad(option) {
  484. if (option.course) {
  485. this.urlOption = option.course ? JSON.parse(decodeURIComponent(option.course)) : {}
  486. } else if(option.s){
  487. this.urlOption = option.s ? JSON.parse(decodeURIComponent(option.s)) : {}
  488. } else {
  489. const keys = decodeURIComponent(Object.keys(option)[0]);
  490. this.urlOption = JSON.parse(keys.split('course=')[1])
  491. }
  492. this.videoId = this.urlOption.videoId || ''
  493. this.qwUserId = this.urlOption.qwUserId || ''
  494. this.corpId = this.urlOption.corpId || ''
  495. this.linkType = this.urlOption.linkType || 0
  496. var that=this;
  497. if (this.videoId) {
  498. this.getH5CourseByVideo()
  499. this.getConfigByKey()
  500. }
  501. this.sortLink = this.urlOption.link || ''
  502. this.getMenuButton()
  503. // #ifndef H5
  504. uni.onKeyboardHeightChange(this.keyboardHeightChange);
  505. // #endif
  506. uni.$on('usercode',(data)=>{
  507. if(data) {
  508. this.code=data.code
  509. this.loginFsUserWx(data)
  510. }
  511. })
  512. },
  513. onShow() {
  514. this.onHide = false
  515. this.tipsOpen = false
  516. // this.isLogin = this.utils.isLoginCourse()
  517. this.uuId = this.utils.generateRandomString(16)
  518. if(uni.getStorageSync('auto_userInfo') && JSON.stringify(uni.getStorageSync('auto_userInfo'))!='{}') {
  519. this.user = JSON.parse(uni.getStorageSync('auto_userInfo'))
  520. } else {
  521. this.user = {}
  522. }
  523. if(this.sortLink&&!this.code){
  524. this.getLink()
  525. } else {
  526. if(this.code) return
  527. uni.showToast({
  528. title: '链接地址有误',
  529. icon: 'none'
  530. });
  531. if(this.isLogin) {
  532. this.getUserPhoneDeviceType()
  533. }
  534. }
  535. },
  536. mounted() {
  537. this.getIP()
  538. this.getHeight()
  539. },
  540. onHide() {
  541. this.onHide = true
  542. // this.player = uni.createVideoContext('video-content-box');
  543. if (this.player) {
  544. this.player.pause()
  545. }
  546. // if (this.interval != null) {
  547. // clearInterval(this.interval)
  548. // this.interval = null
  549. // }
  550. this.closeSocket()
  551. },
  552. onUnload() {
  553. uni.$off('usercode')
  554. if (this.interval != null) {
  555. clearInterval(this.interval)
  556. this.interval = null
  557. }
  558. this.clearIntegral()
  559. // #ifndef H5
  560. uni.offKeyboardHeightChange(this.keyboardHeightChange);
  561. // #endif
  562. this.closeSocket()
  563. },
  564. beforeDestroy() {
  565. uni.$off('usercode')
  566. this.player = uni.createVideoContext('video-content-box');
  567. if (this.player) {
  568. this.player.stop()
  569. this.player = null
  570. }
  571. if (this.interval != null) {
  572. clearInterval(this.interval)
  573. this.interval = null
  574. }
  575. this.clearIntegral()
  576. // #ifndef H5
  577. uni.offKeyboardHeightChange(this.keyboardHeightChange);
  578. // #endif
  579. this.closeSocket()
  580. },
  581. methods: {
  582. numberToChinese(number) {
  583. if (number) {
  584. const chineseNumber = ['一', '二', '三', '四', '五', '六', '七', '八', '九'];
  585. return chineseNumber[number - 1];
  586. } else {
  587. return ''
  588. }
  589. },
  590. keyboardHeightChange(res) {
  591. // #ifndef H5
  592. console.log("this.danmuboxHeight",this.danmuboxHeight)
  593. this.danmuboxHeight = res.height
  594. // #endif
  595. },
  596. getMenuButton(){
  597. const menuButtonInfo = uni.getMenuButtonBoundingClientRect()
  598. this.menuButtonLeft = menuButtonInfo.left
  599. this.menuButtonH = menuButtonInfo.height
  600. },
  601. //播放时间更新事件方法
  602. onTimeUpdate(e){
  603. let currentTime = Math.round(e.detail.currentTime)
  604. if (this.playDurationSeek > 0) {
  605. this.playTime = this.playDurationSeek
  606. this.throttle(() => this.changeTime(this), 1000, false)
  607. } else {
  608. if (this.linkType != 1 && (currentTime - this.playTime > 3 || currentTime - this.playTime < -3)&&this.isFinish!=1) {
  609. uni.showToast({
  610. title: '不能快进哦',
  611. icon: 'none',
  612. });
  613. currentTime = this.playTime
  614. this.player.seek(this.playTime);
  615. }
  616. this.playTime = currentTime
  617. }
  618. if (Math.floor(e.detail.currentTime) != this.flagTime) {
  619. this.flagTime = Math.floor(e.detail.currentTime)
  620. const isNearEnd = this.playTime >= this.duration - 60;
  621. this.showAnswerTip = this.isEnded || isNearEnd;
  622. const progress = this.duration ? (this.playTime || 0) / this.duration : 0;
  623. const answerRate = Number(this.config.answerRate ?? 1); // 默认 100%
  624. const hitRate = progress >= answerRate;
  625. this.showNote = (this.isEnded || this.isFinish === 1 || hitRate) ? 1 : 0;
  626. if(this.fsUserCourseRewardRoundStatusVO&&this.fsUserCourseRewardRoundStatusVO.length>0) {
  627. this.$refs.treasureChest&&this.$refs.treasureChest.showTreasure(this.flagTime)
  628. }
  629. if(this.openCommentStatus == 2) {
  630. this.$refs.danmuBox&&this.$refs.danmuBox.checkDanmu(this.flagTime)
  631. }
  632. }
  633. },
  634. changeTime(that,e) {
  635. that.playDurationSeek = 0
  636. },
  637. videoErrorCallback(e) {
  638. this.errorCount++
  639. if (this.errorCount > 3) return
  640. if (this.interval != null) {
  641. clearInterval(this.interval)
  642. }
  643. this.clearIntegral()
  644. console.log(e)
  645. if(this.isLogin&&this.isAddKf==1) {
  646. this.getErrMsg(e.target.errMsg)
  647. this.getH5CourseVideoDetails('error')
  648. }
  649. },
  650. // 当开始/继续播放时触发play事件
  651. getPlay() {
  652. this.errorCount = 0
  653. // this.judgeDuration()
  654. },
  655. getPause() {
  656. this.clearIntegral()
  657. },
  658. getEnded() {
  659. this.clearIntegral()
  660. this.isEnded = true
  661. this.showAnswerTip = true
  662. this.isFinish = 1
  663. this.showNote = 1
  664. this.getFinishCourseVideo()
  665. this.sendRewardFun(2)
  666. },
  667. getWaiting() {
  668. this.getErrMsg('','waiting')
  669. },
  670. fullscreenchange(event) {
  671. this.isfull = event.detail.fullScreen
  672. if(this.isfull) {
  673. this.$refs.danmuBox&&this.$refs.danmuBox.initTracks()
  674. }
  675. },
  676. controlstoggle(event) {
  677. this.crtShow = event.detail.show
  678. },
  679. getIP() {
  680. uni.request({
  681. url: 'https://ipinfo.io/json', //仅为示例,并非真实接口地址。
  682. method: 'GET',
  683. success: (res) => {
  684. this.ip = res.data.ip
  685. }
  686. });
  687. },
  688. getHeight() {
  689. setTimeout(()=>{
  690. const query = uni.createSelectorQuery().in(this);
  691. query
  692. .select("#title-contentnav")
  693. .boundingClientRect((data) => {
  694. if(data) {
  695. const footerH = this.showBtn==0? 80: 0
  696. this.height =
  697. `calc(100vh - ${data.height}px - 420rpx - ${this.statusBarHeight}px - ${footerH}px - 88rpx)`
  698. }
  699. })
  700. .exec();
  701. },200)
  702. },
  703. numberToLetter(num) {
  704. // 将数字转换为字母的 ASCII 码
  705. let letterCode = num + 65;
  706. // 将 ASCII 码转换为大写字母
  707. let letter = String.fromCharCode(letterCode);
  708. return letter;
  709. },
  710. updateTime() {
  711. var that = this;
  712. if (this.interval != null) {
  713. clearInterval(this.interval)
  714. }
  715. this.interval = setInterval(function() {
  716. that.getFinishCourseVideo()
  717. that.getInternetTraffic()
  718. }, 60000);
  719. },
  720. judgeDuration() {
  721. var that = this;
  722. if (this.intervalIntegral != null) {
  723. clearInterval(this.intervalIntegral)
  724. this.intervalIntegral = null
  725. }
  726. // 观看10分钟获得芳华币
  727. this.intervalIntegral = setInterval(function() {
  728. that.getIntegralByH5Video()
  729. }, 600000);
  730. },
  731. clearIntegral() {
  732. if (this.intervalIntegral != null) {
  733. clearInterval(this.intervalIntegral)
  734. this.intervalIntegral = null
  735. }
  736. },
  737. getH5CourseByVideo() {
  738. this.loading = true
  739. getH5CourseByVideoId({
  740. videoId: this.videoId
  741. }).then(res => {
  742. this.loading = false
  743. if (res.code == 200) {
  744. this.courseInfo = res.data
  745. uni.setNavigationBarTitle({
  746. title: this.courseInfo && this.courseInfo.title ? this.courseInfo.title : ''
  747. });
  748. this.courseInfo = res.data || {}
  749. this.configJson = res.data&&res.data.configJson ? JSON.parse(res.data.configJson) : this.defaultInfo
  750. }
  751. this.getHeight()
  752. this.$nextTick(()=>{
  753. this.$refs.descInfo&&this.$refs.descInfo.getDescHeight()
  754. this.$refs.descInfoNav&&this.$refs.descInfoNav.getDescHeight()
  755. })
  756. },
  757. rej => {
  758. this.loading = false
  759. }
  760. ).catch(() => {
  761. this.loading = false
  762. })
  763. },
  764. getH5CourseVideoDetails(type) {
  765. getH5CourseVideoDetails(this.urlOption).then(res => {
  766. if (res.code == 200) {
  767. this.config = res.config || {}
  768. this.courseNote = res.course&&res.course.note
  769. this.isFinish = res.isFinish || 0
  770. this.showNote = this.isFinish==1 ? 1:0
  771. this.duration = res.course && res.course.duration ? res.course.duration : 0
  772. this.playDuration = res.playDuration || 0
  773. this.tipsTime = res.tipsTime || 0
  774. const showTreatmentFlag = res.course&&res.course.showTreatment!=null ? res.course.showTreatment : 1
  775. this.treatmentPackage = res.course&&res.course.treatmentPackage ? res.course.treatmentPackage : []
  776. this.showTreatment = showTreatmentFlag==0&&this.treatmentPackage.length>0 ? 0 : 1
  777. let lineList = []
  778. if (res.course && res.course.lineOne) {
  779. lineList.push(res.course.lineOne)
  780. }
  781. if (res.course && res.course.lineTwo) {
  782. lineList.push(res.course.lineTwo)
  783. }
  784. if (res.course && res.course.lineThree) {
  785. lineList.push(res.course.lineThree)
  786. }
  787. this.lineList = lineList
  788. // this.quesList = res.questions && res.questions.length > 0 ? res.questions : []
  789. // this.quesList = this.quesList.map(item => ({
  790. // ...item,
  791. // questionOption: JSON.parse(item.question),
  792. // answer: ''
  793. // }))
  794. this.showBtn = 1
  795. this.courseLogo = res.config&&res.config.courseLogo
  796. this.viewCommentNum = res.config&&res.config.viewCommentNum || 200
  797. this.openCommentStatus = res.config&&res.config.openCommentStatus || 3;
  798. // this.openCommentStatus = source
  799. // console.log('socket===',socket,this.openCommentStatus)
  800. if(this.openCommentStatus == 3) {
  801. this.closeSocket()
  802. }else {
  803. this.initSocket()
  804. }
  805. this.currentTab = 1
  806. if(this.openCommentStatus!=2 || this.showDanmu!=1) {
  807. this.activeDanmus = []
  808. }
  809. // // 宝箱
  810. // this.fsUserCourseRewardRoundStatusVO = this.getBox(res)
  811. if (!this.player || type == 'error') {
  812. this.lineIndex = this.config.defaultLine
  813. this.videoUrl = lineList[this.lineIndex]
  814. this.poster= res.course && res.course.imgUrl ? res.course.imgUrl : ''
  815. // this.options.sources = [{
  816. // src: this.videoUrl
  817. // }]
  818. // this.options.poster = res.course && res.course.imgUrl ? res.course.imgUrl : ''
  819. // this.initVideo()
  820. this.playTime = this.playDuration >= this.duration ? 0 : this.playDuration
  821. this.playDurationSeek = this.playTime
  822. setTimeout(()=>{
  823. this.player = uni.createVideoContext('video-content-box');
  824. this.player.seek(this.playTime)
  825. this.player.play();
  826. },500);
  827. } else {
  828. // let div = document.querySelector(".vjs-progress-control");
  829. // if(div) {
  830. // if (this.isFinish == 1 || this.isEnded || this.linkType == 1) {
  831. // div.style.pointerEvents = "auto";
  832. // } else {
  833. // div.style.pointerEvents = "none"; //禁止所有事件
  834. // }
  835. // }
  836. this.playTime = this.playTime > this.playDuration ? this.playTime : this.playDuration >= this.duration ? 0 : this.playDuration
  837. this.playDurationSeek = this.playTime
  838. this.player.seek(this.playTime)
  839. this.player.play();
  840. }
  841. this.updateTime();
  842. } else {
  843. uni.showToast({
  844. title: res.msg,
  845. icon: 'none'
  846. })
  847. }
  848. this.getHeight()
  849. this.$nextTick(()=>{
  850. this.$refs.descInfo&&this.$refs.descInfo.getDescHeight()
  851. this.$refs.descInfoNav&&this.$refs.descInfoNav.getDescHeight()
  852. })
  853. },
  854. rej => {}
  855. )
  856. },
  857. handleAnswer(val) {
  858. let {item, option,index} = val
  859. let time = this.playTime
  860. if(this.isEnded||this.isFinish==1) {
  861. time = this.duration
  862. } else {
  863. if(time < this.playDuration&&this.isFinish!=1) {
  864. // 没完课且小于续播的时间
  865. time = this.playDuration
  866. }
  867. }
  868. if(Number(this.duration || 0) == 0 || time < this.duration - 60) {
  869. uni.showToast({
  870. title: "请先观看完整科普再答题哦~",
  871. icon: "none"
  872. })
  873. return
  874. }
  875. if (this.quesList[index].type == 1) {
  876. // 单选option
  877. this.quesList[index].answer = option.name
  878. } else if (this.quesList[index].type == 2) {
  879. // 多选
  880. let answer = this.quesList[index].answer ? this.quesList[index].answer.split(',') : []
  881. if (answer.indexOf(option.name) === -1) {
  882. answer.push(option.name)
  883. this.quesList[index].answer = answer.join(',')
  884. } else {
  885. answer.splice(answer.indexOf(option.name), 1)
  886. this.quesList[index].answer = answer.join(',')
  887. }
  888. }
  889. },
  890. submit() {
  891. if(this.isExpire){
  892. uni.showToast({
  893. title: '科普已过期或链接无效',
  894. icon: 'none'
  895. });
  896. return
  897. }
  898. // 登录
  899. this.utils.isLoginCourse().then(
  900. res => {
  901. if(res){
  902. if (this.isAddKf == 1&&this.isCheckRealUrl) {
  903. // 答题
  904. // 您已提交过答案,请领取红包
  905. this.courseAnswer()
  906. } else {
  907. // 添加客服
  908. if (this.videoId && this.qwUserId) {
  909. this.getIsAddKf()
  910. } else {
  911. uni.showToast({
  912. title: '请添加客服',
  913. icon: 'none'
  914. })
  915. }
  916. }
  917. } else{
  918. this.goLogin()
  919. }
  920. },
  921. rej => {}
  922. );
  923. },
  924. // 答题
  925. courseAnswer() {
  926. let time = this.playTime
  927. if (this.isEnded || this.isFinish==1) {
  928. time = this.duration
  929. } else {
  930. if (time < this.playDuration && this.isFinish != 1) {
  931. // 没完课且小于续播的时间
  932. time = this.playDuration
  933. }
  934. }
  935. if (Number(this.duration || 0) == 0 || time < this.duration - 60) {
  936. uni.showToast({
  937. title: "请先观看完整科普再答题哦~",
  938. icon: "none"
  939. })
  940. return
  941. }
  942. if (this.quesList.some(item => !item.answer)) {
  943. uni.showToast({
  944. title: "请确认是否答完所有题目",
  945. icon: "none"
  946. })
  947. return
  948. }
  949. const questions = this.quesList.map(obj => {
  950. const {
  951. questionOption,
  952. ...rest
  953. } = obj;
  954. return rest;
  955. });
  956. if(!this.isCheckRealUrl) return;
  957. const param = {
  958. ...this.urlOption,
  959. questions: questions,
  960. videoId: this.videoId,
  961. duration: this.playTime,
  962. appId: getApp().globalData.appId
  963. }
  964. this.errTitle = ""
  965. this.errDesc = ""
  966. this.errQues = []
  967. courseAnswer(param).then(res => {
  968. if (res.code == 200) {
  969. if (res.incorrectQuestions) {
  970. // 答题失败
  971. if (res.incorrectQuestions.length > 0) {
  972. this.errQues = res.incorrectQuestions
  973. }
  974. this.remain = res.remain || 0
  975. if (res.remain > 0) {
  976. this.errTitle = "很遗憾答错了"
  977. this.errDesc = `<span style="color:#FF5C03">还有${res.remain}次机会,继续加油</span>`
  978. this.$refs.answerPopup.open("center")
  979. }
  980. } else {
  981. // 答题成功
  982. this.errTitle = "恭喜你,回答正确"
  983. this.errDesc = `请选择奖励`
  984. this.currentReward = this.rewardType&&this.rewardType.length>0 ? this.rewardType[0].value : -1
  985. this.$refs.answerPopup.open("center")
  986. }
  987. } else {
  988. if (res.msg == "该课题到达答错次数限制") {
  989. this.errTitle = "答题次数超过限制"
  990. this.errDesc = "以后的科普要认真学习哦"
  991. this.$refs.answerPopup.open("center")
  992. } else {
  993. uni.showToast({
  994. title: res.msg,
  995. icon: "none"
  996. })
  997. }
  998. }
  999. },
  1000. rej => {}
  1001. )
  1002. },
  1003. // 选择
  1004. rewardChange(e) {
  1005. this.currentReward = e.value
  1006. },
  1007. closeAnswerPopup() {
  1008. this.$refs.answerPopup.close()
  1009. if(!this.isCheckRealUrl) return;
  1010. if (this.errTitle == '恭喜你,回答正确') {
  1011. if(this.currentReward==4) {
  1012. this.$refs.turntableTwo.open(this.urlOption)
  1013. } else if(this.currentReward==3) {
  1014. this.$refs.turntableOne.open(this.urlOption)
  1015. } else {
  1016. this.sendRewardFun(this.currentReward)
  1017. }
  1018. }
  1019. },
  1020. sendRewardFun(currentReward) {
  1021. this.currentReward = currentReward
  1022. const param = {
  1023. ...this.urlOption,
  1024. rewardType: Number(this.currentReward),
  1025. source: 2,
  1026. appId: getApp().globalData.appId
  1027. }
  1028. sendReward(param).then(res => {
  1029. if(res.code == 200) {
  1030. if(currentReward == 4) {
  1031. this.$refs.turntableTwo.endSuccess(res.data)
  1032. }else if(currentReward == 3) {
  1033. this.$refs.turntableOne.endSuccess(res.data)
  1034. }else {
  1035. uni.showToast({
  1036. title: res.msg,
  1037. icon: 'none'
  1038. })
  1039. this.openAppPop()
  1040. }
  1041. } else {
  1042. this.currentReward == 4 ? this.$refs.turntableTwo.close('close') : this.currentReward == 3? this.$refs.turntableOne.close('close'):''
  1043. uni.showToast({
  1044. title: res.msg,
  1045. icon: 'none'
  1046. })
  1047. }
  1048. })
  1049. },
  1050. openAppPop() {
  1051. this.$refs.appPopup.openPop()
  1052. },
  1053. // 线路
  1054. openPop() {
  1055. this.$refs.popup.open('bottom')
  1056. },
  1057. close() {
  1058. this.$refs.popup.close()
  1059. },
  1060. handleLine(index) {
  1061. var that=this;
  1062. if (this.lineIndex == index && this.videoUrl == this.lineList[index]) {
  1063. this.close()
  1064. return
  1065. } else {
  1066. // let div = document.querySelector(".vjs-progress-control");
  1067. // if(div) {
  1068. // if (this.isFinish == 1 || this.isEnded || this.linkType == 1) {
  1069. // div.style.pointerEvents = "auto";
  1070. // } else {
  1071. // div.style.pointerEvents = "none"; //禁止所有事件
  1072. // }
  1073. // }
  1074. this.lineIndex = index
  1075. this.videoUrl = this.lineList[index]
  1076. this.tipsOpen = false
  1077. this.playDurationSeek = this.playTime || 0
  1078. this.player = uni.createVideoContext('video-content-box');
  1079. setTimeout(function(){
  1080. that.player.seek(that.playDurationSeek)
  1081. that.player.play();
  1082. },500);
  1083. // this.player.src(this.lineList[index])
  1084. // this.player.one('loadedmetadata', () => {
  1085. // this.player.currentTime(this.playDurationSeek);
  1086. // this.player.play();
  1087. // });
  1088. this.close()
  1089. }
  1090. },
  1091. // 客服
  1092. getIsAddKf() {
  1093. this.qrcode = ''
  1094. this.qrcodeMsg = ''
  1095. this.isAddKf = 0
  1096. // {videoId: this.videoId,qwUserId: this.qwUserId,corpId: this.corpId}
  1097. getIsAddKf(this.urlOption).then(res => {
  1098. if (res.code == 200) {
  1099. this.isAddKf = 1
  1100. this.isCheckRealUrl = true
  1101. this.getH5CourseVideoDetails()
  1102. } else if (res.code == 400) {
  1103. this.isAddKf = 0
  1104. this.isCheckRealUrl = false
  1105. this.qrcode = res.qrcode
  1106. this.qrcodeMsg = res.msg
  1107. // this.$refs.kfPopup.open()
  1108. this.initExpiration(res.msg,res.code)
  1109. } else if (res.code == 566) {
  1110. // 官方群发通用链接
  1111. const url = res.courseLink.realLink.split('?course=')[1]
  1112. this.urlOption = JSON.parse(url)
  1113. this.isAddKf = 1
  1114. this.isCheckRealUrl = true
  1115. this.getH5CourseVideoDetails()
  1116. } else if (res.code == 567) {
  1117. // 群聊通用链接
  1118. this.urlOption = {
  1119. ...this.urlOption,
  1120. qwExternalId: res.qwExternalId
  1121. }
  1122. this.isAddKf = 1
  1123. this.isCheckRealUrl = true
  1124. this.getH5CourseVideoDetails()
  1125. }else {
  1126. this.isCheckRealUrl = false
  1127. this.isAddKf = 0
  1128. uni.showToast({
  1129. title: res.msg,
  1130. icon: 'none'
  1131. });
  1132. if(res.code==401) return;
  1133. this.initExpiration(res.msg,res.code)
  1134. }
  1135. },
  1136. err => {}
  1137. ).catch((err) => {
  1138. uni.showToast({
  1139. title: err,
  1140. icon: 'none'
  1141. });
  1142. })
  1143. },
  1144. initExpiration(resMsg,resCode) {
  1145. if(resCode==401) return;
  1146. this.resMsg = resMsg
  1147. this.resCode = resCode
  1148. this.showExpiration = true
  1149. },
  1150. closeKFPop() {
  1151. this.$refs.kfPopup.close()
  1152. },
  1153. getFinishCourseVideo() {
  1154. if (!this.isLogin||!this.playTime || !this.isCheckRealUrl) return
  1155. // {videoId: this.videoId,duration:this.playTime}
  1156. const param = {
  1157. duration: this.playTime,
  1158. ...this.urlOption
  1159. }
  1160. getFinishCourseVideo(param)
  1161. },
  1162. // 每十分钟获得芳华币
  1163. getIntegralByH5Video() {
  1164. if(!this.isLogin||!this.isCheckRealUrl) return
  1165. const param = {
  1166. duration: this.playTime,
  1167. ...this.urlOption
  1168. }
  1169. getIntegralByH5Video(param).then(res => {
  1170. if (res.code == 200) {
  1171. uni.showToast({
  1172. title: "芳华币+10",
  1173. icon: "none"
  1174. })
  1175. }
  1176. })
  1177. },
  1178. progressChange(e) {
  1179. this.bufferRate = Math.ceil(e.detail.buffered)
  1180. },
  1181. // 缓冲
  1182. getInternetTraffic() {
  1183. if(!this.isLogin||!this.isCheckRealUrl) return
  1184. let playVideoTime = 0
  1185. if (this.duration && this.duration > 0) {
  1186. playVideoTime = Math.ceil(this.playTime / this.duration * 100) // 播放百分比
  1187. }
  1188. if(this.bufferRate == 0 || this.bufferRate < playVideoTime) {
  1189. this.bufferRate = playVideoTime
  1190. }
  1191. if(this.bufferRate == 0 || Number(this.bufferRate.toFixed(2)) == 0) return
  1192. const param = {
  1193. ...this.urlOption,
  1194. uuId: dayjs().format('YYYYMMDD') + this.uuId,
  1195. duration: this.playTime,
  1196. bufferRate: Number(this.bufferRate.toFixed(2)),
  1197. }
  1198. if(!param.bufferRate) return
  1199. internetTraffic(param)
  1200. },
  1201. getErrMsg(err,type) {
  1202. let msgerr = {
  1203. errType: type || 'error',
  1204. videoUrl: this.videoUrl,
  1205. lineIndex: this.lineIndex,
  1206. errTime: new Date(),
  1207. ip: this.ip,
  1208. errMsg: err || ''
  1209. }
  1210. getErrMsg({
  1211. msg: JSON.stringify(msgerr)
  1212. })
  1213. },
  1214. editUserSuccess() {
  1215. this.isLogin = true
  1216. this.getIsAddKf()
  1217. this.getVideoRewardTypes()
  1218. this.getUserPhoneDeviceType()
  1219. },
  1220. goLogin() {
  1221. this.getByAppId()
  1222. },
  1223. goWXLogin() {
  1224. this.utils.getProvider().then(provider=>{
  1225. console.log('当前的环境商',provider)
  1226. if (!provider) {
  1227. reject()
  1228. }
  1229. uni.login({
  1230. provider: provider,
  1231. success: async loginRes => {
  1232. console.log(loginRes)
  1233. uni.getUserInfo({
  1234. provider: provider,
  1235. success: (infoRes)=> {
  1236. uni.showToast({
  1237. title: '处理中...',
  1238. icon: 'loading'
  1239. });
  1240. loginByMp({code: loginRes.code,encryptedData:infoRes.encryptedData,iv:infoRes.iv,appId:getApp().globalData.appId}).then(res=>{
  1241. uni.hideLoading();
  1242. if (res.code == 200) {
  1243. this.$store.commit('setCoureLogin', 1);
  1244. uni.setStorageSync('AppTokenmini_RTCourse', res.token);
  1245. uni.setStorageSync('auto_userInfo', JSON.stringify(res.user));
  1246. this.user = res.user
  1247. if(this.urlOption.chatId) {
  1248. this.isLogin = false
  1249. this.$refs.userlogo.userlogo = true
  1250. } else {
  1251. this.isLogin = true
  1252. this.getIsAddKf()
  1253. this.getVideoRewardTypes()
  1254. this.getUserPhoneDeviceType()
  1255. }
  1256. } else {
  1257. uni.showToast({
  1258. title: res.msg,
  1259. icon: 'none'
  1260. });
  1261. }
  1262. }).catch(err=>{
  1263. uni.hideLoading();
  1264. uni.showToast({
  1265. icon:'none',
  1266. title: "登录失败,请重新登录",
  1267. });
  1268. });
  1269. }
  1270. });
  1271. }
  1272. })
  1273. }).catch(err => {})
  1274. },
  1275. getLink() {
  1276. let that = this;
  1277. getRealLink({sortLink:this.sortLink}).then(res=>{
  1278. if(res.code == 200) {
  1279. this.isExpire = false
  1280. if(this.isLogin) {
  1281. this.getUserPhoneDeviceType()
  1282. }
  1283. // 如果响应中包含真实链接,则跳转到真实链接
  1284. // window.location.href = res.realLink +"&sortLink="+this.sortLink+"&code="+this.code+"&time="+new Date().getTime()
  1285. if (this.isLogin && this.isAddKf == 1&&this.isCheckRealUrl) {
  1286. this.getH5CourseVideoDetails()
  1287. }
  1288. if (this.videoId &&(this.isAddKf != 1 || !this.isCheckRealUrl)) {
  1289. this.utils.isLoginCourse().then(
  1290. isLogin => {
  1291. this.isLogin = isLogin
  1292. if(isLogin){
  1293. this.getIsAddKf()
  1294. this.getVideoRewardTypes()
  1295. } else {
  1296. this.goLogin()
  1297. }
  1298. },
  1299. rej => {}
  1300. );
  1301. }
  1302. } else {
  1303. this.isExpire = true
  1304. uni.showToast({
  1305. title: '科普已过期或链接无效',
  1306. icon: 'none'
  1307. });
  1308. }
  1309. }).catch(err=>{
  1310. this.isExpire = true
  1311. uni.showToast({
  1312. title: '发生错误,请稍后再试',
  1313. icon: 'none'
  1314. });
  1315. })
  1316. },
  1317. /**
  1318. * 节流原理:在一定时间内,只能触发一次
  1319. *
  1320. * @param {Function} func 要执行的回调函数
  1321. * @param {Number} wait 延时的时间
  1322. * @param {Boolean} immediate 是否立即执行
  1323. * @return null
  1324. */
  1325. throttle(func, wait = 500, immediate = true) {
  1326. if (immediate) {
  1327. if (!this.flag) {
  1328. this.flag = true
  1329. // 如果是立即执行,则在wait毫秒内开始时执行
  1330. typeof func === 'function' && func()
  1331. this.timer = setTimeout(() => {
  1332. this.flag = false
  1333. }, wait)
  1334. }
  1335. } else if (!this.flag) {
  1336. this.flag = true
  1337. // 如果是非立即执行,则在wait毫秒内的结束处执行
  1338. this.timer = setTimeout(() => {
  1339. this.flag = false
  1340. typeof func === 'function' && func()
  1341. }, wait)
  1342. }
  1343. },
  1344. // 弹幕
  1345. openDanmu(type) {
  1346. this.openDanmuType = type
  1347. this.inputText = ''
  1348. if(type == 1) {
  1349. this.player.exitFullScreen()
  1350. }
  1351. this.$refs.danmuPopup.open()
  1352. },
  1353. changeShowPopup(val) {
  1354. this.focus = val.show
  1355. },
  1356. switchDanmu() {
  1357. this.showDanmu = this.showDanmu == 1 ? 0:1
  1358. if(this.showDanmu == 0&&this.$refs.danmuBox) {
  1359. this.$refs.danmuPopup.close()
  1360. this.activeDanmus = []
  1361. this.$refs.danmuBox.activeDanmus = []
  1362. this.$refs.danmuBox.initTracks()
  1363. }
  1364. },
  1365. getScrollTop(res) {
  1366. if(this.currentTab == 2) {
  1367. this.scrollTop = res
  1368. } else {
  1369. this.scrollTop = 0
  1370. }
  1371. },
  1372. handleTab(index) {
  1373. this.currentTab = index
  1374. if(this.currentTab==2) {
  1375. if(this.$refs.commentBox) {
  1376. this.$refs.commentBox.msgs = []
  1377. this.$refs.commentBox.pageNum = 1
  1378. this.$refs.commentBox.getComments()
  1379. }
  1380. } else {
  1381. setTimeout(()=>{
  1382. this.scrollTop = 0
  1383. },100)
  1384. }
  1385. },
  1386. handleRefresher() {
  1387. this.triggered = true;
  1388. if (!this.isMore&&this.currentTab==2&&this.openCommentStatus==1) {
  1389. this.$nextTick(()=>{
  1390. this.$refs.commentBox&&this.$refs.commentBox.getComments()
  1391. })
  1392. }
  1393. setTimeout(() => {
  1394. this.triggered = false;
  1395. }, 500);
  1396. },
  1397. getMore(val) {
  1398. this.triggered = false;
  1399. this.isMore = val == 1
  1400. },
  1401. handleChatInput() {
  1402. this.inputText = this.inputText.trim()
  1403. if (this.inputText == "" || this.inputText.trim() == "") {
  1404. uni.showToast({
  1405. title: '请输入评论',
  1406. icon: "none"
  1407. })
  1408. return;
  1409. }
  1410. if(this.openCommentStatus==1) {
  1411. this.$refs.commentBox&&this.$refs.commentBox.handleInput(this.inputText)
  1412. } else if(this.openCommentStatus==2) {
  1413. this.$refs.danmuBox&&this.$refs.danmuBox.handleInput(this.inputText)
  1414. }
  1415. },
  1416. setInputText() {
  1417. this.inputText = ""
  1418. if(this.openCommentStatus==2) {
  1419. this.$refs.danmuPopup.close()
  1420. }
  1421. },
  1422. getActiveDanmus(val) {
  1423. this.activeDanmus = val.map(item=>({
  1424. ...item,
  1425. danmustyle: {
  1426. top: item.top + 'px',
  1427. ...item.style,
  1428. 'animation-duration': '8s'
  1429. }
  1430. }))
  1431. },
  1432. animationend(moveItem, i) {
  1433. // 移除动画结束的弹幕(性能优化)
  1434. if(this.openCommentStatus==2) {
  1435. this.$refs.danmuBox&&this.$refs.danmuBox.animationend(moveItem, i)
  1436. }
  1437. },
  1438. async feedback() {
  1439. const userId = this.user.userId || ''
  1440. const courseId = this.urlOption.courseId || ''
  1441. const videoId = this.urlOption.videoId || ''
  1442. uni.setStorageSync('H5course',{
  1443. companyId: this.urlOption.companyId,
  1444. companyUserId:this.urlOption.companyUserId,
  1445. })
  1446. uni.navigateTo({
  1447. url: './feedback?userId='+userId+'&courseId='+courseId+'&videoId='+videoId
  1448. })
  1449. },
  1450. getBox(data) {
  1451. if(data.fsUserCourseRewardRoundStatusVO&&data.fsUserCourseRewardRoundStatusVO.length>0) {
  1452. const list = data.fsUserCourseRewardRoundStatusVO.map(item=>({
  1453. ...item,
  1454. time: Math.round(this.duration * Number(item.question || 0) * 0.01)
  1455. }))
  1456. return list
  1457. } else {
  1458. return []
  1459. }
  1460. },
  1461. claimFun(val) {
  1462. claim({
  1463. ...this.urlOption,
  1464. second: val.time,
  1465. rewardId: val.rewardId,
  1466. status: val.status
  1467. }).then(res=>{
  1468. if(res.code == 200) {
  1469. this.$refs.treasureChest.claimSuccess(val.status)
  1470. } else {
  1471. this.$refs.treasureChest.close()
  1472. }
  1473. if(res.msg!="success") {
  1474. uni.showToast({
  1475. title: res.msg,
  1476. icon: 'none',
  1477. duration: 3000
  1478. })
  1479. }
  1480. })
  1481. },
  1482. showBtnType(value) {
  1483. this.showBtn = value
  1484. this.getHeight()
  1485. },
  1486. getUserPhoneDeviceType(){
  1487. uni.getSystemInfo({
  1488. success: (result) => {
  1489. const param = {
  1490. os: result.osName || '',
  1491. deviceId: result.deviceId || '',
  1492. appId: getApp().globalData.appId
  1493. }
  1494. getUserPhoneDeviceType({param:JSON.stringify(param)})
  1495. },
  1496. fail: (error) => {
  1497. console.log('获取型号失败',JSON.stringify(error))
  1498. }
  1499. })
  1500. },
  1501. initSocket(type) {
  1502. const userId = this.user.userId; // 假设你已经获取了用户ID
  1503. const that = this;
  1504. // 如果已经存在连接,先关闭
  1505. if (this.socket) {
  1506. this.socket.onClose(() => {
  1507. console.log('旧的 WebSocket 连接已完全关闭');
  1508. that.socket = null;
  1509. if(that.pingpangTimes) {
  1510. clearInterval(that.pingpangTimes)
  1511. that.pingpangTimes= null
  1512. }
  1513. that.createNewSocket(type,userId);
  1514. });
  1515. this.socket.close();
  1516. return;
  1517. }
  1518. // 创建一个新的 WebSocket 连接
  1519. this.createNewSocket(type,userId);
  1520. },
  1521. createNewSocket(type,userId) {
  1522. if(this.openCommentStatus == 3) return
  1523. //创建一个socket连接
  1524. const that = this;
  1525. // if (this.socket) {
  1526. // this.socket.close()
  1527. // }
  1528. this.socket = uni.connectSocket({
  1529. url: getApp().globalData.wsUrl + "/app/webSocket/" + userId,
  1530. multiple: true,
  1531. success: res => {
  1532. that.isSocketOpen = true
  1533. console.log('WebSocket连接已打开1!');
  1534. // 保持心跳
  1535. if(that.pingpangTimes) {
  1536. clearInterval(that.pingpangTimes)
  1537. that.pingpangTimes= null
  1538. }
  1539. that.pingpangTimes=setInterval(()=>{
  1540. let data={
  1541. userId: userId || '',
  1542. userType: 2, // 1-管理员,2-用户
  1543. courseId: that.urlOption.courseId,
  1544. videoId: that.urlOption.videoId,
  1545. type:1, // 评论类型 1:评论,2:回复,目前没有回复,默认传1就行了
  1546. // msg: that.inputText,
  1547. cmd:'heartbeat'
  1548. };
  1549. that.socket.send({
  1550. data: JSON.stringify(data),
  1551. success: () => {
  1552. // console.log('WebSocket发送心条数据!');
  1553. },
  1554. fail: () => {
  1555. that.isSocketOpen=false
  1556. }
  1557. });
  1558. },15000)
  1559. },
  1560. error: res => {
  1561. console.log(res)
  1562. },
  1563. })
  1564. this.socket.onMessage((res) => {
  1565. // console.log("收到消息parse",JSON.parse(res.data))
  1566. const redata = JSON.parse(res.data);
  1567. if(redata.cmd=="heartbeat"){
  1568. //心跳
  1569. // console.log("heartbeat")
  1570. }else if(redata.cmd=="sendMsg"){
  1571. if(that.openCommentStatus==1) {
  1572. that.$refs.commentBox.isSend=true;
  1573. that.$refs.commentBox.addMsg(redata);
  1574. } else if(that.openCommentStatus==2) {
  1575. that.$refs.danmuBox.isSend=true;
  1576. that.$refs.danmuBox.addMsg(redata);
  1577. }
  1578. // that.isSend=true;
  1579. // that.addMsg(redata);
  1580. }
  1581. })
  1582. //监听socket打开
  1583. this.socket.onOpen(() => {
  1584. that.isSocketOpen = true
  1585. console.log('WebSocket连接已打开2!');
  1586. // that.isSend = true;
  1587. if(that.openCommentStatus==1) {
  1588. that.$refs.commentBox.isSend=true;
  1589. } else if(that.openCommentStatus==2) {
  1590. that.$refs.danmuBox.isSend=true;
  1591. }
  1592. if(type=='reStart') {
  1593. // 重连的时候重新发消息
  1594. if(that.openCommentStatus==1) {
  1595. that.$refs.commentBox.saveMsg()
  1596. } else if(that.openCommentStatus==2) {
  1597. that.$refs.danmuBox.saveMsg()
  1598. }
  1599. // this.saveMsg()
  1600. }
  1601. })
  1602. //监听socket关闭
  1603. this.socket.onClose(() => {
  1604. that.isSocketOpen = false
  1605. that.socket = null
  1606. console.log('WebSocket连接已关闭!');
  1607. if(that.pingpangTimes) {
  1608. clearInterval(that.pingpangTimes)
  1609. that.pingpangTimes= null
  1610. }
  1611. })
  1612. //监听socket错误
  1613. this.socket.onError((err) => {
  1614. console.log("socket err:",err)
  1615. that.isSocketOpen = false
  1616. that.socket = null
  1617. if(that.pingpangTimes) {
  1618. clearInterval(that.pingpangTimes)
  1619. that.pingpangTimes= null
  1620. }
  1621. })
  1622. },
  1623. socketSend(data) {
  1624. let that = this
  1625. this.socket.send({
  1626. data: JSON.stringify(data),
  1627. success: () => {
  1628. console.log("发送成功")
  1629. // this.isSend = false;
  1630. if(that.openCommentStatus==1) {
  1631. that.$refs.commentBox.isSend = false;
  1632. } else if(that.openCommentStatus==2) {
  1633. that.$refs.danmuBox.isSend = false;
  1634. }
  1635. },
  1636. fail: () => {
  1637. console.log("发送失败")
  1638. }
  1639. });
  1640. },
  1641. closeSocket() {
  1642. if (this.socket) {
  1643. this.socket.close();
  1644. }
  1645. if (this.pingpangTimes) {
  1646. clearInterval(this.pingpangTimes);
  1647. this.pingpangTimes = null;
  1648. }
  1649. },
  1650. getVideoRewardTypes() {
  1651. getVideoRewardTypes({...this.urlOption}).then(res=>{
  1652. if(res.code == 200) {
  1653. this.rewardType = res.data
  1654. this.currentReward = this.rewardType&&this.rewardType.length>0 ? this.rewardType[0].value : -1
  1655. }
  1656. })
  1657. },
  1658. getConfigByKey(){
  1659. let param = {key:"course.config"};
  1660. getConfigByKey(param).then(
  1661. res => {
  1662. if(res.code==200){
  1663. let data=res.data ? JSON.parse(res.data) : {};
  1664. this.notice = data.notify || ''
  1665. }
  1666. },
  1667. rej => {}
  1668. );
  1669. },
  1670. getByAppId(){
  1671. getByAppId(getApp().globalData.appId).then(res=>{
  1672. if(res.code == 200) {
  1673. this.authType = res.config.authType || 1
  1674. if(this.authType == 1) {
  1675. this.code = ''
  1676. this.goWXLogin()
  1677. } else if(this.authType==2) {
  1678. this.loginFsUserWx()
  1679. }
  1680. } else {
  1681. uni.showToast({
  1682. title: res.msg,
  1683. icon: 'error'
  1684. })
  1685. }
  1686. }).catch((err)=>{
  1687. uni.showToast({
  1688. title: JSON.stringify(err),
  1689. icon: 'error'
  1690. })
  1691. })
  1692. },
  1693. async loginFsUserWx(data) {
  1694. if(data){
  1695. console.log('huoqu1222',data)
  1696. uni.showLoading({
  1697. title: '登录中'
  1698. })
  1699. uni.login({
  1700. provider: "weixin",
  1701. success: async loginRes => {
  1702. console.log(loginRes)
  1703. let code = loginRes.code // 获取开发code
  1704. handleFsUserWx({
  1705. code: code,
  1706. appId:getApp().globalData.appId,
  1707. userId:data.userId
  1708. })
  1709. .then( res => {
  1710. uni.hideLoading();
  1711. if(res.code==200){
  1712. this.code = ''
  1713. console.log("loginFsUserWx:",res)
  1714. // this.userinfos=uni.getStorageSync('userinfos')
  1715. let token = uni.getStorageSync('TOKEN_WEXIN');
  1716. let user = uni.getStorageSync('web_userInfo')
  1717. // this.userInfo=uni.getStorageSync('userInfo');
  1718. // this.isLogin = true
  1719. this.$store.commit('setCoureLogin', 1);
  1720. uni.setStorageSync('AppTokenmini_RTCourse', token);
  1721. uni.setStorageSync('auto_userInfo', JSON.stringify(user));
  1722. this.user = user
  1723. this.isLogin = true
  1724. this.getIsAddKf()
  1725. this.getVideoRewardTypes()
  1726. this.getUserPhoneDeviceType()
  1727. }else{
  1728. uni.showToast({
  1729. icon:'none',
  1730. title: res.msg,
  1731. });
  1732. }
  1733. })
  1734. },
  1735. })
  1736. }else{
  1737. uni.setStorageSync('H5course',{
  1738. companyId: this.urlOption.companyId,
  1739. companyUserId:this.urlOption.companyUserId,
  1740. })
  1741. await this.$store.dispatch('getWebviewUrl');
  1742. uni.navigateTo({
  1743. url:'/pages_course/webview?H5course='+uni.getStorageSync('H5course')
  1744. })
  1745. }
  1746. }
  1747. }
  1748. }
  1749. </script>
  1750. <style scoped>
  1751. .full-width-popup {
  1752. width: 100%;
  1753. }
  1754. </style>
  1755. <style lang="scss" scoped>
  1756. @mixin u-flex($flexD, $alignI, $justifyC) {
  1757. display: flex;
  1758. flex-direction: $flexD;
  1759. align-items: $alignI;
  1760. justify-content: $justifyC;
  1761. }
  1762. .reward-list {
  1763. display: flex;
  1764. align-items: center;
  1765. flex-wrap: wrap;
  1766. margin-top: 40rpx;
  1767. margin-bottom: -24rpx;
  1768. }
  1769. .reward-item{
  1770. padding: 16rpx 10rpx;
  1771. box-sizing: border-box;
  1772. border-radius: 10rpx;
  1773. width: calc(50% - 14rpx);
  1774. overflow: hidden;
  1775. text-align: center;
  1776. border-radius: 8rpx;
  1777. border: 1rpx solid #ededef;
  1778. // margin: 10rpx 5px;
  1779. margin-bottom: 24rpx;
  1780. color: #222;
  1781. background-color: #fff;
  1782. &:nth-child(odd) {
  1783. margin-right: 20rpx;
  1784. }
  1785. }
  1786. .reward-active {
  1787. border: 1rpx solid #ff5c03 !important;
  1788. color: #ff5c03 !important;
  1789. background: #FCF0E7 !important;
  1790. }
  1791. .footer-tips {
  1792. margin-top: 14rpx;
  1793. text-align: center;
  1794. font-family: PingFang SC,PingFang SC;
  1795. font-weight: 500;
  1796. font-size: 12px;
  1797. color: #bbb;
  1798. }
  1799. .textOne {
  1800. overflow: hidden;
  1801. white-space: nowrap;
  1802. text-overflow: ellipsis;
  1803. }
  1804. .textTwo {
  1805. overflow: hidden;
  1806. text-overflow: ellipsis;
  1807. display: -webkit-box;
  1808. -webkit-line-clamp: 2;
  1809. -webkit-box-orient: vertical;
  1810. }
  1811. .header-nav {
  1812. height: 88rpx;
  1813. @include u-flex(row, center, flex-start);
  1814. overflow: hidden;
  1815. background-color: #fff;
  1816. box-sizing: border-box;
  1817. .header-title {
  1818. text-align: center;
  1819. overflow: hidden;
  1820. white-space: nowrap;
  1821. text-overflow: ellipsis;
  1822. padding: 0 10rpx 0 100rpx;
  1823. font-family: PingFang SC,PingFang SC;
  1824. font-weight: 500;
  1825. font-size: 15px;
  1826. color: #000;
  1827. box-sizing: border-box;
  1828. }
  1829. }
  1830. .reward-list {
  1831. width: 100%;
  1832. // margin-top: 20rpx;
  1833. // margin-bottom: -40rpx;
  1834. &-group {
  1835. font-family: PingFang SC, PingFang SC;
  1836. font-weight: 400;
  1837. font-size: 14px;
  1838. color: #222222;
  1839. @include u-flex(row, center, center);
  1840. }
  1841. &-option {
  1842. @include u-flex(row, center, flex-start);
  1843. &:first-child {
  1844. margin-right: 40rpx;
  1845. }
  1846. }
  1847. }
  1848. .err {
  1849. color: #f56c6c !important;
  1850. }
  1851. .kfqrcode-box {
  1852. background-color: #fff;
  1853. border-radius: 16rpx;
  1854. max-width: 560rpx;
  1855. padding: 60rpx 40rpx;
  1856. margin-top: -100rpx;
  1857. box-sizing: border-box;
  1858. @include u-flex(column, center, flex-start);
  1859. font-family: PingFang SC, PingFang SC;
  1860. font-weight: 400;
  1861. font-size: 34rpx;
  1862. color: #222;
  1863. position: relative;
  1864. text-align: center;
  1865. .kfqrcode {
  1866. height: 460rpx;
  1867. width: 460rpx;
  1868. }
  1869. }
  1870. .kfqrcode-close {
  1871. width: 64rpx;
  1872. height: 64rpx;
  1873. position: absolute;
  1874. bottom: -100rpx;
  1875. left: 50%;
  1876. transform: translateX(-50%);
  1877. }
  1878. .tipsPopup-mask {
  1879. position: relative;
  1880. width: 560rpx;
  1881. background-color: transparent;
  1882. .red_envelope_top {
  1883. width: 480rpx;
  1884. height: 360rpx;
  1885. margin: 0 auto;
  1886. display: inherit;
  1887. }
  1888. }
  1889. .tipsPopup-btn-box {
  1890. width: 456rpx;
  1891. height: 104rpx;
  1892. padding: 4rpx;
  1893. box-sizing: border-box;
  1894. background: linear-gradient(180deg, rgba(252, 209, 94, 1), rgba(254, 253, 251, 1));
  1895. border-radius: 52rpx;
  1896. }
  1897. .tipsPopup-btn {
  1898. width: 100%;
  1899. height: 100%;
  1900. background: linear-gradient(180deg, #FF9F22 0%, #FA1E05 100%);
  1901. border-radius: 52rpx 52rpx 52rpx 52rpx;
  1902. font-family: PingFang SC, PingFang SC;
  1903. font-weight: 500;
  1904. font-size: 36rpx;
  1905. color: #FFFFFF;
  1906. line-height: 96rpx;
  1907. text-align: center;
  1908. }
  1909. .tipsPopup {
  1910. width: 560rpx;
  1911. padding: 12rpx;
  1912. margin-top: -72rpx;
  1913. box-sizing: border-box;
  1914. background: linear-gradient(180deg, #FFFBEF 0%, #FFFFF5 43%, #F5EAC2 100%);
  1915. border-radius: 32rpx 32rpx 32rpx 32rpx;
  1916. position: relative;
  1917. &-close {
  1918. width: 64rpx;
  1919. height: 64rpx;
  1920. position: absolute;
  1921. right: 0;
  1922. top: -188rpx;
  1923. }
  1924. &-line {
  1925. padding: 3rpx;
  1926. box-sizing: border-box;
  1927. background: linear-gradient(180deg, rgba(247, 245, 220, 1), rgba(250, 220, 157, 1));
  1928. border-radius: 24rpx;
  1929. }
  1930. &-box {
  1931. padding: 0 40rpx 40rpx 40rpx;
  1932. box-sizing: border-box;
  1933. background: linear-gradient(180deg, #FFFBEF 0%, #FFFFF5 43%, #F5EAC2 100%);
  1934. border-radius: 24rpx;
  1935. @include u-flex(column, center, flex-start);
  1936. }
  1937. &-head {
  1938. @include u-flex(row, center, center);
  1939. &-title {
  1940. width: 364rpx;
  1941. height: auto;
  1942. margin-top: -22rpx;
  1943. }
  1944. }
  1945. &-content {
  1946. margin: 48rpx 0;
  1947. font-family: PingFang SC, PingFang SC;
  1948. font-weight: 500;
  1949. font-size: 32rpx;
  1950. color: #222222;
  1951. text-align: center;
  1952. &-title {
  1953. margin-bottom: 26rpx;
  1954. font-weight: 600;
  1955. font-size: 40rpx;
  1956. color: #FF5C03;
  1957. }
  1958. }
  1959. }
  1960. .video-controls-box {
  1961. width: 100%;
  1962. height: 420rpx;
  1963. overflow: hidden;
  1964. position: absolute;
  1965. bottom: 0;
  1966. left: 0;
  1967. z-index: 2;
  1968. background: rgba(0, 0, 0, 0.2);
  1969. .video-play {
  1970. height: 72rpx;
  1971. width: 72rpx;
  1972. position: absolute;
  1973. top: 50%;
  1974. left: 50%;
  1975. transform: translate(-50%, -50%);
  1976. }
  1977. }
  1978. .video-controls {
  1979. width: 100%;
  1980. height: 80rpx;
  1981. padding: 0 28rpx;
  1982. box-sizing: border-box;
  1983. position: absolute;
  1984. bottom: 0;
  1985. left: 0;
  1986. display: flex;
  1987. align-items: center;
  1988. justify-content: space-between;
  1989. background: linear-gradient(to top, #222 0%, transparent 80%);
  1990. .video-icon {
  1991. height: 44rpx;
  1992. width: 44rpx;
  1993. }
  1994. }
  1995. .errQuesbox {
  1996. width: 100%;
  1997. max-height: 260rpx;
  1998. overflow-y: auto;
  1999. margin-top: 24rpx;
  2000. font-family: PingFang SC, PingFang SC;
  2001. font-weight: 500;
  2002. font-size: 30rpx;
  2003. color: #222222;
  2004. &-item {
  2005. width: 100%;
  2006. height: 128rpx;
  2007. line-height: 128rpx;
  2008. margin-bottom: 24rpx;
  2009. padding: 0 30rpx;
  2010. box-sizing: border-box;
  2011. overflow: hidden;
  2012. background: #fff;
  2013. border-radius: 16rpx 16rpx 16rpx 16rpx;
  2014. position: relative;
  2015. &::after {
  2016. content: "题目";
  2017. min-width: 64rpx;
  2018. height: 36rpx;
  2019. padding: 0 12rpx;
  2020. line-height: 36rpx;
  2021. background: #FF5C03;
  2022. box-sizing: border-box;
  2023. border-radius: 0rpx 0rpx 16rpx 0rpx;
  2024. text-align: center;
  2025. font-family: PingFang SC, PingFang SC;
  2026. font-weight: 500;
  2027. font-size: 20rpx;
  2028. color: #fff;
  2029. position: absolute;
  2030. left: 0;
  2031. top: 0;
  2032. }
  2033. }
  2034. }
  2035. .bg {
  2036. background: #fff !important;
  2037. }
  2038. .answerPopup {
  2039. &-box {
  2040. width: 560rpx;
  2041. background: linear-gradient(180deg, #FFFAF6 0%, #FEECD8 100%);
  2042. border-radius: 32rpx 32rpx 32rpx 32rpx;
  2043. background-color: #fff;
  2044. font-weight: 400;
  2045. padding: 32rpx;
  2046. box-sizing: border-box;
  2047. position: relative;
  2048. @include u-flex(column, center, flex-start);
  2049. font-family: PingFang SC, PingFang SC;
  2050. font-weight: 400;
  2051. .tipimg {
  2052. width: 206rpx;
  2053. height: 206rpx;
  2054. margin-bottom: 16rpx;
  2055. }
  2056. }
  2057. &-title {
  2058. font-weight: 600;
  2059. font-size: 36rpx;
  2060. color: #222222;
  2061. }
  2062. &-desc {
  2063. margin-top: 10rpx;
  2064. font-size: 28rpx;
  2065. color: #757575;
  2066. }
  2067. &-btn {
  2068. width: 464rpx;
  2069. height: 84rpx;
  2070. margin-top: 54rpx;
  2071. margin-bottom: 16rpx;
  2072. background: #FF5C03;
  2073. border-radius: 42rpx;
  2074. font-weight: 500;
  2075. font-size: 32rpx;
  2076. color: #FFFFFF;
  2077. text-align: center;
  2078. line-height: 84rpx;
  2079. }
  2080. }
  2081. .popupbox {
  2082. width: 100%;
  2083. background-color: #fff;
  2084. border-radius: 16rpx 16rpx 0 0;
  2085. padding: 24rpx 32rpx;
  2086. position: relative;
  2087. &-head {
  2088. height: 60rpx;
  2089. margin-bottom: 30rpx;
  2090. text-align: center;
  2091. overflow-y: auto;
  2092. color: #414858;
  2093. font-size: 32rpx;
  2094. font-weight: bold;
  2095. position: relative;
  2096. .close-icon {
  2097. position: absolute;
  2098. right: 0;
  2099. top: 0;
  2100. height: 40rpx;
  2101. width: 40rpx;
  2102. }
  2103. }
  2104. &-content {
  2105. height: 20vh;
  2106. overflow-y: auto;
  2107. display: flex;
  2108. align-items: flex-start;
  2109. flex-wrap: wrap;
  2110. gap: 32rpx;
  2111. .line-item {
  2112. display: inline-block;
  2113. min-width: 200rpx;
  2114. min-height: 60rpx;
  2115. padding: 0 20rpx;
  2116. box-sizing: border-box;
  2117. border-radius: 50rpx;
  2118. overflow: hidden;
  2119. background-color: #f7f7f7;
  2120. text-align: center;
  2121. color: #414858;
  2122. font-size: 28rpx;
  2123. line-height: 60rpx;
  2124. }
  2125. .line-active {
  2126. color: #f56c6c !important;
  2127. background-color: #fef0f0 !important;
  2128. }
  2129. }
  2130. }
  2131. .content {
  2132. padding-bottom: calc(var(--window-bottom));
  2133. .video-box {
  2134. width: 100%;
  2135. height: 420rpx;
  2136. overflow: hidden;
  2137. position: relative;
  2138. #myVideo {
  2139. width: 100%;
  2140. height: 100%;
  2141. }
  2142. }
  2143. .video-poster {
  2144. width: 100%;
  2145. height: 420rpx;
  2146. }
  2147. .miantitlebox {
  2148. padding: 30rpx 0;
  2149. border-bottom: 2rpx solid #F5F7FA;
  2150. font-family: PingFang SC, PingFang SC;
  2151. font-weight: 500;
  2152. font-size: 36rpx;
  2153. color: #222222;
  2154. }
  2155. .subtitlebox {
  2156. padding: 30rpx 0;
  2157. border-bottom: 2rpx solid #F5F7FA;
  2158. font-family: PingFang SC, PingFang SC;
  2159. font-weight: 500;
  2160. font-size: 36rpx;
  2161. color: #222222;
  2162. }
  2163. .title-content {
  2164. padding: 0 32rpx;
  2165. background-color: #fff;
  2166. font-size: 28rpx;
  2167. line-height: 1.6;
  2168. box-sizing: border-box;
  2169. @include u-flex(row, center, space-between);
  2170. .title {
  2171. font-size: 36rpx;
  2172. font-weight: 500;
  2173. color: #414858;
  2174. }
  2175. .time-or-subtitle {
  2176. margin-top: 12rpx;
  2177. color: #666666;
  2178. }
  2179. }
  2180. .warning {
  2181. flex-shrink: 0;
  2182. color: #888;
  2183. font-size: 24rpx;
  2184. @include u-flex(column, center, center);
  2185. image {
  2186. flex-shrink: 0;
  2187. height: 36rpx;
  2188. width: 36rpx;
  2189. }
  2190. }
  2191. .video-line {
  2192. min-width: 140rpx;
  2193. max-width: 200rpx;
  2194. height: 60rpx;
  2195. padding: 0 20rpx;
  2196. box-sizing: border-box;
  2197. border-radius: 50rpx 0 0 50rpx;
  2198. overflow: hidden;
  2199. background-color: #fff;
  2200. text-align: center;
  2201. color: #888;
  2202. font-size: 28rpx;
  2203. line-height: 60rpx;
  2204. display: inline-flex;
  2205. align-items: center;
  2206. justify-content: center;
  2207. position: fixed;
  2208. right: 0;
  2209. z-index: 9;
  2210. bottom: calc(var(--window-bottom) + 280rpx);
  2211. box-shadow: 0 4rpx 10rpx rgba(0, 0, 0, .12);
  2212. image {
  2213. flex-shrink: 0;
  2214. height: 34rpx;
  2215. width: 34rpx;
  2216. margin-right: 6rpx;
  2217. }
  2218. }
  2219. .danmu-line {
  2220. bottom: calc(var(--window-bottom) + 370rpx);
  2221. word-break: keep-all;
  2222. .set_image {
  2223. height: 40rpx;
  2224. width: 40rpx;
  2225. }
  2226. }
  2227. .footer {
  2228. border-top: 1rpx solid #ededef;
  2229. background: #fff;
  2230. width: 100%;
  2231. position: fixed;
  2232. bottom: 0;
  2233. // padding: 32rpx;
  2234. padding-bottom: calc(var(--window-bottom));
  2235. box-sizing: border-box;
  2236. z-index: 9;
  2237. &-btn {
  2238. width: calc(100% - 64rpx);
  2239. height: 98rpx;
  2240. background: #FF5C03;
  2241. border-radius: 49rpx 49rpx 49rpx 49rpx;
  2242. line-height: 98rpx;
  2243. text-align: center;
  2244. font-family: PingFang SC, PingFang SC;
  2245. font-weight: 600;
  2246. font-size: 32rpx;
  2247. color: #FFFFFF;
  2248. @include u-flex(row, center, center);
  2249. margin: 24rpx 32rpx;
  2250. box-sizing: border-box;
  2251. &-img {
  2252. flex-shrink: 0;
  2253. width: 144rpx;
  2254. height: 144rpx;
  2255. margin-right: 8rpx;
  2256. margin-top: -24rpx;
  2257. }
  2258. }
  2259. &-btn-border {
  2260. position: relative;
  2261. &::after {
  2262. content: "";
  2263. background: linear-gradient(180deg, rgba(255, 255, 255, 0), rgba(255, 255, 255, 1));
  2264. position: absolute;
  2265. top: -2rpx;
  2266. left: 0;
  2267. height: 103rpx;
  2268. width: 100%;
  2269. z-index: -1;
  2270. border-radius: 49rpx 49rpx 49rpx 49rpx;
  2271. box-shadow: 0rpx 8rpx 11rpx 0rpx rgba(255, 92, 3, 0.3);
  2272. overflow: hidden;
  2273. }
  2274. }
  2275. }
  2276. }
  2277. .agreement {
  2278. display: inline-flex;
  2279. margin-top: 16rpx;
  2280. font-size: 24rpx;
  2281. color: #525252;
  2282. align-items: center;
  2283. justify-content: center;
  2284. }
  2285. .video-danmu-btnbox {
  2286. width: 50px;
  2287. height: 50px;
  2288. border-radius: 50%;
  2289. overflow: hidden;
  2290. position: absolute;
  2291. right: 10px;
  2292. bottom: calc(50% - 50px);
  2293. transform: translateY(-50%);
  2294. padding: 8px;
  2295. box-sizing: border-box;
  2296. }
  2297. .video-danmu-image {
  2298. width: 100%;
  2299. height: 100%;
  2300. }
  2301. .danmuPopup {
  2302. background-color: #fff;
  2303. padding-bottom: calc(var(--window-bottom) + 10px);
  2304. .u-border {
  2305. flex: 1;
  2306. @include u-flex(row,center,flex-start);
  2307. padding: 0 6rpx;
  2308. border-radius: 6px;
  2309. }
  2310. &-head {
  2311. width: 100%;
  2312. padding: 10px;
  2313. box-sizing: border-box;
  2314. overflow: hidden;
  2315. @include u-flex(row,center,flex-start);
  2316. .danmu-icon {
  2317. height: 24px;
  2318. width: 24px;
  2319. margin-right: 10px;
  2320. }
  2321. }
  2322. &-input {
  2323. flex: 1;
  2324. height: 35px;
  2325. }
  2326. &-send {
  2327. flex-shrink: 0;
  2328. height: 35px;
  2329. display: flex;
  2330. align-items: center;
  2331. justify-content: center;
  2332. padding: 5px 15px;
  2333. box-sizing: border-box;
  2334. background: #FF5C03 !important;
  2335. border-radius: 6px;
  2336. font-family: PingFang SC, PingFang SC;
  2337. font-weight: 500;
  2338. font-size: 15px;
  2339. color: #fff !important;
  2340. margin-left: 12px;
  2341. &::after {
  2342. border: none;
  2343. }
  2344. }
  2345. &-con {
  2346. background-color: #F5F7FA;
  2347. padding: 24px 12px 48px 12px;
  2348. font-family: PingFang SC, PingFang SC;
  2349. font-weight: 400;
  2350. font-size: 14px;
  2351. color: #757575;
  2352. }
  2353. }
  2354. .danmu-icon{
  2355. height: 24px;
  2356. width: 24px;
  2357. margin-right: 12px;
  2358. }
  2359. .logo {
  2360. display: inline-block;
  2361. width: 30px;
  2362. height: auto;
  2363. margin: 20px 0 0 10px;
  2364. pointer-events: none;
  2365. object-fit: cover;
  2366. }
  2367. .logo-full {
  2368. display: inline-block;
  2369. width: 40px;
  2370. height: auto;
  2371. margin: 50px 0 0 30px;
  2372. pointer-events: none;
  2373. object-fit: cover;
  2374. }
  2375. .tabbox-bar {
  2376. @include u-flex(row, center, flex-start);
  2377. background-color: #fff;
  2378. .warning {
  2379. flex-shrink: 0;
  2380. padding-right: 20rpx;
  2381. }
  2382. }
  2383. .tabbox {
  2384. flex: 1;
  2385. @include u-flex(row, center, center);
  2386. border-bottom: 2rpx solid #F5F7FA;
  2387. height: 44px;
  2388. background-color: #fff;
  2389. view {
  2390. flex: 1;
  2391. padding: 20rpx 0;
  2392. margin-right: 40rpx;
  2393. text-align: center;
  2394. }
  2395. &-active {
  2396. position: relative;
  2397. &::after {
  2398. position: absolute;
  2399. bottom: 0;
  2400. left: 50%;
  2401. transform: translateX(-50%);
  2402. content: "";
  2403. width: 3rem;
  2404. border-bottom: 4px solid #FF5C03;
  2405. }
  2406. }
  2407. }
  2408. .chatinput {
  2409. position: fixed;
  2410. left: 32rpx;
  2411. right: 32rpx;
  2412. z-index: 999;
  2413. height: 96rpx;
  2414. background-color: green;
  2415. background: #FFFFFF;
  2416. box-shadow: 0rpx 8rpx 21rpx 0rpx rgba(0, 0, 0, 0.1);
  2417. border-radius: 24rpx 24rpx 24rpx 24rpx;
  2418. @include u-flex(row, center, center);
  2419. padding: 0 24rpx;
  2420. box-sizing: border-box;
  2421. .uni-input {
  2422. flex: 1;
  2423. margin-right: 32rpx;
  2424. font-size: 30rpx;
  2425. }
  2426. .send {
  2427. font-family: PingFang SC, PingFang SC;
  2428. font-weight: 400;
  2429. font-size: 28rpx;
  2430. color: #FFFFFF !important;
  2431. flex-shrink: 0;
  2432. padding: 0 20rpx;
  2433. height: 72rpx;
  2434. background: #FF5C03 !important;
  2435. border-radius: 8rpx 8rpx 8rpx 8rpx;
  2436. &::after {
  2437. border: none;
  2438. }
  2439. }
  2440. }
  2441. .answerTip {
  2442. position: fixed;
  2443. right: 0;
  2444. z-index: 9;
  2445. bottom: calc(var(--window-bottom) + 380rpx);
  2446. box-shadow: 0rpx 8rpx 21rpx 0rpx rgba(0, 0, 0, 0.1);
  2447. border-radius: 24rpx 24rpx 24rpx 24rpx;
  2448. background-color: #ff5c03;
  2449. color: #fff;
  2450. border-radius: 50%;
  2451. height: 100rpx;
  2452. width: 100rpx;
  2453. font-size: 25rpx;
  2454. text-align: center;
  2455. padding: 10rpx;
  2456. @include u-flex(row, center, center);
  2457. }
  2458. .danmu-item {
  2459. position: absolute;
  2460. top: 0;
  2461. white-space: nowrap;
  2462. font-size: 16px;
  2463. height: 20px;
  2464. display: inline-flex;
  2465. box-sizing: border-box;
  2466. align-items: center;
  2467. }
  2468. .danmuMove {
  2469. // animation: mymove 8s linear forwards;
  2470. // animation-duration: 8s;
  2471. animation-timing-function: linear;
  2472. animation-delay: 0s;
  2473. animation-iteration-count: 1;
  2474. animation-direction: normal;
  2475. animation-fill-mode: forwards;
  2476. animation-play-state: running;
  2477. animation-name: mymove;
  2478. will-change: transform;
  2479. }
  2480. @keyframes mymove {
  2481. from {
  2482. transform: translateX(100vw);
  2483. }
  2484. to {
  2485. transform: translateX(-100%);
  2486. }
  2487. }
  2488. .arrow-left-warning {
  2489. position: absolute;
  2490. left: 24rpx;
  2491. height: 88rpx;
  2492. overflow: hidden;
  2493. color: #888;
  2494. font-size: 24rpx;
  2495. @include u-flex(column, center, center);
  2496. image {
  2497. flex-shrink: 0;
  2498. height: 36rpx;
  2499. width: 36rpx;
  2500. }
  2501. }
  2502. </style>