video.vue 167 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694169516961697169816991700170117021703170417051706170717081709171017111712171317141715171617171718171917201721172217231724172517261727172817291730173117321733173417351736173717381739174017411742174317441745174617471748174917501751175217531754175517561757175817591760176117621763176417651766176717681769177017711772177317741775177617771778177917801781178217831784178517861787178817891790179117921793179417951796179717981799180018011802180318041805180618071808180918101811181218131814181518161817181818191820182118221823182418251826182718281829183018311832183318341835183618371838183918401841184218431844184518461847184818491850185118521853185418551856185718581859186018611862186318641865186618671868186918701871187218731874187518761877187818791880188118821883188418851886188718881889189018911892189318941895189618971898189919001901190219031904190519061907190819091910191119121913191419151916191719181919192019211922192319241925192619271928192919301931193219331934193519361937193819391940194119421943194419451946194719481949195019511952195319541955195619571958195919601961196219631964196519661967196819691970197119721973197419751976197719781979198019811982198319841985198619871988198919901991199219931994199519961997199819992000200120022003200420052006200720082009201020112012201320142015201620172018201920202021202220232024202520262027202820292030203120322033203420352036203720382039204020412042204320442045204620472048204920502051205220532054205520562057205820592060206120622063206420652066206720682069207020712072207320742075207620772078207920802081208220832084208520862087208820892090209120922093209420952096209720982099210021012102210321042105210621072108210921102111211221132114211521162117211821192120212121222123212421252126212721282129213021312132213321342135213621372138213921402141214221432144214521462147214821492150215121522153215421552156215721582159216021612162216321642165216621672168216921702171217221732174217521762177217821792180218121822183218421852186218721882189219021912192219321942195219621972198219922002201220222032204220522062207220822092210221122122213221422152216221722182219222022212222222322242225222622272228222922302231223222332234223522362237223822392240224122422243224422452246224722482249225022512252225322542255225622572258225922602261226222632264226522662267226822692270227122722273227422752276227722782279228022812282228322842285228622872288228922902291229222932294229522962297229822992300230123022303230423052306230723082309231023112312231323142315231623172318231923202321232223232324232523262327232823292330233123322333233423352336233723382339234023412342234323442345234623472348234923502351235223532354235523562357235823592360236123622363236423652366236723682369237023712372237323742375237623772378237923802381238223832384238523862387238823892390239123922393239423952396239723982399240024012402240324042405240624072408240924102411241224132414241524162417241824192420242124222423242424252426242724282429243024312432243324342435243624372438243924402441244224432444244524462447244824492450245124522453245424552456245724582459246024612462246324642465246624672468246924702471247224732474247524762477247824792480248124822483248424852486248724882489249024912492249324942495249624972498249925002501250225032504250525062507250825092510251125122513251425152516251725182519252025212522252325242525252625272528252925302531253225332534253525362537253825392540254125422543254425452546254725482549255025512552255325542555255625572558255925602561256225632564256525662567256825692570257125722573257425752576257725782579258025812582258325842585258625872588258925902591259225932594259525962597259825992600260126022603260426052606260726082609261026112612261326142615261626172618261926202621262226232624262526262627262826292630263126322633263426352636263726382639264026412642264326442645264626472648264926502651265226532654265526562657265826592660266126622663266426652666266726682669267026712672267326742675267626772678267926802681268226832684268526862687268826892690269126922693269426952696269726982699270027012702270327042705270627072708270927102711271227132714271527162717271827192720272127222723272427252726272727282729273027312732273327342735273627372738273927402741274227432744274527462747274827492750275127522753275427552756275727582759276027612762276327642765276627672768276927702771277227732774277527762777277827792780278127822783278427852786278727882789279027912792279327942795279627972798279928002801280228032804280528062807280828092810281128122813281428152816281728182819282028212822282328242825282628272828282928302831283228332834283528362837283828392840284128422843284428452846284728482849285028512852285328542855285628572858285928602861286228632864286528662867286828692870287128722873287428752876287728782879288028812882288328842885288628872888288928902891289228932894289528962897289828992900290129022903290429052906290729082909291029112912291329142915291629172918291929202921292229232924292529262927292829292930293129322933293429352936293729382939294029412942294329442945294629472948294929502951295229532954295529562957295829592960296129622963296429652966296729682969297029712972297329742975297629772978297929802981298229832984298529862987298829892990299129922993299429952996299729982999300030013002300330043005300630073008300930103011301230133014301530163017301830193020302130223023302430253026302730283029303030313032303330343035303630373038303930403041304230433044304530463047304830493050305130523053305430553056305730583059306030613062306330643065306630673068306930703071307230733074307530763077307830793080308130823083308430853086308730883089309030913092309330943095309630973098309931003101310231033104310531063107310831093110311131123113311431153116311731183119312031213122312331243125312631273128312931303131313231333134313531363137313831393140314131423143314431453146314731483149315031513152315331543155315631573158315931603161316231633164316531663167316831693170317131723173317431753176317731783179318031813182318331843185318631873188318931903191319231933194319531963197319831993200320132023203320432053206320732083209321032113212321332143215321632173218321932203221322232233224322532263227322832293230323132323233323432353236323732383239324032413242324332443245324632473248324932503251325232533254325532563257325832593260326132623263326432653266326732683269327032713272327332743275327632773278327932803281328232833284328532863287328832893290329132923293329432953296329732983299330033013302330333043305330633073308330933103311331233133314331533163317331833193320332133223323332433253326332733283329333033313332333333343335333633373338333933403341334233433344334533463347334833493350335133523353335433553356335733583359336033613362336333643365336633673368336933703371337233733374337533763377337833793380338133823383338433853386338733883389339033913392339333943395339633973398339934003401340234033404340534063407340834093410341134123413341434153416341734183419342034213422342334243425342634273428342934303431343234333434343534363437343834393440344134423443344434453446344734483449345034513452345334543455345634573458345934603461346234633464346534663467346834693470347134723473347434753476347734783479348034813482348334843485348634873488348934903491349234933494349534963497349834993500350135023503350435053506350735083509351035113512351335143515351635173518351935203521352235233524352535263527352835293530353135323533353435353536353735383539354035413542354335443545354635473548354935503551355235533554355535563557355835593560356135623563356435653566356735683569357035713572357335743575357635773578357935803581358235833584358535863587358835893590359135923593359435953596359735983599360036013602360336043605360636073608360936103611361236133614361536163617361836193620362136223623362436253626362736283629363036313632363336343635363636373638363936403641364236433644364536463647364836493650365136523653365436553656365736583659366036613662366336643665366636673668366936703671367236733674367536763677367836793680368136823683368436853686368736883689369036913692369336943695369636973698369937003701370237033704370537063707370837093710371137123713371437153716371737183719372037213722372337243725372637273728372937303731373237333734373537363737373837393740374137423743374437453746374737483749375037513752375337543755375637573758375937603761376237633764376537663767376837693770377137723773377437753776377737783779378037813782378337843785378637873788378937903791379237933794379537963797379837993800380138023803380438053806380738083809381038113812381338143815381638173818381938203821382238233824382538263827382838293830383138323833383438353836383738383839384038413842384338443845384638473848384938503851385238533854385538563857385838593860386138623863386438653866386738683869387038713872387338743875387638773878387938803881388238833884388538863887388838893890389138923893389438953896389738983899390039013902390339043905390639073908390939103911391239133914391539163917391839193920392139223923392439253926392739283929393039313932393339343935393639373938393939403941394239433944394539463947394839493950395139523953395439553956395739583959396039613962396339643965396639673968396939703971397239733974397539763977397839793980398139823983398439853986398739883989399039913992399339943995399639973998399940004001400240034004400540064007400840094010401140124013401440154016401740184019402040214022402340244025402640274028402940304031403240334034403540364037403840394040404140424043404440454046404740484049405040514052405340544055405640574058405940604061406240634064406540664067406840694070407140724073407440754076407740784079408040814082408340844085408640874088408940904091409240934094409540964097409840994100410141024103410441054106410741084109411041114112411341144115411641174118411941204121412241234124412541264127412841294130413141324133413441354136413741384139414041414142414341444145414641474148414941504151415241534154415541564157415841594160416141624163416441654166416741684169417041714172417341744175417641774178417941804181418241834184418541864187418841894190419141924193419441954196419741984199420042014202420342044205420642074208420942104211421242134214421542164217421842194220422142224223422442254226422742284229423042314232423342344235423642374238423942404241424242434244424542464247424842494250425142524253425442554256425742584259426042614262426342644265426642674268426942704271427242734274427542764277427842794280428142824283428442854286428742884289429042914292429342944295429642974298429943004301430243034304430543064307430843094310431143124313431443154316431743184319432043214322432343244325432643274328432943304331433243334334433543364337433843394340434143424343434443454346434743484349435043514352435343544355435643574358435943604361436243634364436543664367436843694370437143724373437443754376437743784379438043814382438343844385438643874388438943904391439243934394439543964397439843994400440144024403440444054406440744084409441044114412441344144415441644174418441944204421442244234424442544264427442844294430443144324433443444354436443744384439444044414442444344444445444644474448444944504451445244534454445544564457445844594460446144624463446444654466446744684469447044714472447344744475447644774478447944804481448244834484448544864487448844894490449144924493449444954496449744984499450045014502450345044505450645074508450945104511451245134514451545164517451845194520452145224523452445254526452745284529453045314532453345344535453645374538453945404541454245434544454545464547454845494550455145524553455445554556455745584559456045614562456345644565456645674568456945704571457245734574457545764577457845794580458145824583458445854586458745884589459045914592459345944595459645974598459946004601460246034604460546064607460846094610461146124613461446154616461746184619462046214622462346244625462646274628462946304631463246334634463546364637463846394640464146424643464446454646464746484649465046514652465346544655465646574658465946604661466246634664466546664667466846694670467146724673467446754676467746784679468046814682468346844685468646874688468946904691469246934694469546964697469846994700470147024703470447054706470747084709471047114712471347144715471647174718471947204721472247234724472547264727472847294730473147324733473447354736473747384739474047414742474347444745474647474748474947504751475247534754475547564757475847594760476147624763476447654766476747684769477047714772477347744775477647774778477947804781478247834784478547864787478847894790479147924793479447954796479747984799480048014802480348044805480648074808480948104811481248134814481548164817481848194820482148224823482448254826482748284829483048314832483348344835483648374838483948404841484248434844484548464847484848494850485148524853485448554856485748584859486048614862486348644865486648674868486948704871487248734874487548764877487848794880488148824883488448854886488748884889489048914892489348944895489648974898489949004901490249034904490549064907490849094910491149124913491449154916491749184919492049214922492349244925492649274928492949304931493249334934493549364937493849394940494149424943494449454946494749484949495049514952495349544955495649574958495949604961496249634964496549664967496849694970497149724973497449754976497749784979498049814982498349844985498649874988498949904991499249934994499549964997499849995000500150025003500450055006500750085009501050115012501350145015501650175018501950205021502250235024502550265027502850295030503150325033503450355036503750385039504050415042504350445045504650475048504950505051505250535054505550565057505850595060506150625063506450655066506750685069507050715072507350745075507650775078507950805081508250835084508550865087508850895090509150925093509450955096509750985099510051015102510351045105510651075108510951105111511251135114511551165117511851195120512151225123512451255126512751285129513051315132513351345135513651375138513951405141514251435144514551465147514851495150515151525153515451555156515751585159516051615162516351645165516651675168516951705171517251735174517551765177517851795180518151825183518451855186518751885189519051915192519351945195519651975198519952005201520252035204520552065207520852095210521152125213521452155216521752185219522052215222522352245225522652275228522952305231523252335234523552365237523852395240524152425243524452455246524752485249525052515252525352545255525652575258525952605261526252635264526552665267526852695270527152725273527452755276527752785279528052815282528352845285528652875288528952905291529252935294529552965297529852995300530153025303530453055306530753085309531053115312531353145315531653175318531953205321532253235324532553265327532853295330533153325333533453355336533753385339534053415342534353445345534653475348534953505351535253535354535553565357535853595360536153625363536453655366536753685369537053715372537353745375537653775378537953805381538253835384538553865387538853895390539153925393539453955396539753985399540054015402540354045405540654075408540954105411541254135414541554165417541854195420542154225423542454255426542754285429543054315432543354345435543654375438543954405441544254435444544554465447544854495450545154525453545454555456545754585459546054615462546354645465546654675468546954705471547254735474547554765477547854795480548154825483548454855486548754885489549054915492549354945495549654975498549955005501550255035504550555065507550855095510551155125513551455155516551755185519552055215522552355245525552655275528552955305531553255335534553555365537553855395540554155425543554455455546554755485549555055515552555355545555555655575558555955605561556255635564556555665567556855695570557155725573557455755576557755785579558055815582558355845585558655875588558955905591559255935594559555965597559855995600560156025603560456055606560756085609561056115612561356145615561656175618561956205621562256235624562556265627562856295630563156325633563456355636563756385639564056415642564356445645564656475648564956505651565256535654565556565657565856595660566156625663566456655666566756685669567056715672567356745675567656775678567956805681568256835684568556865687568856895690569156925693569456955696569756985699570057015702570357045705570657075708570957105711571257135714571557165717571857195720572157225723572457255726572757285729573057315732573357345735573657375738573957405741574257435744574557465747574857495750575157525753575457555756575757585759576057615762576357645765576657675768576957705771577257735774577557765777577857795780578157825783578457855786578757885789579057915792579357945795579657975798579958005801580258035804580558065807580858095810581158125813581458155816581758185819582058215822582358245825582658275828
  1. <!-- 自动发课看课页面 -->
  2. <template>
  3. <view class="content">
  4. <!-- <view class="header-nav" :style="{height: `calc(88rpx + ${statusBarHeight}px)`,paddingTop: statusBarHeight + 'px'}">
  5. <view class="arrow-left-warning" @click="navback" v-if="isOpen==1">
  6. <u-icon name="arrow-left" size='22' color="#222" bold></u-icon>
  7. </view>
  8. <view class="arrow-left-warning" v-else @click="feedback">
  9. <image :src="imgPath+'/app/image/warning.png'"></image>
  10. <text>投诉</text>
  11. </view>
  12. <view class="header-title" :style="{width:menuButtonLeft + 'px',height:menuButtonH+'px',lineHeight:menuButtonH+'px'}">{{courseInfo.title}}</view>
  13. </view> -->
  14. <view class="notice-box" v-if="isLogin&&isAddKf==1">
  15. <view class="notice-marquee-wrap">
  16. <view class="notice-marquee-track">
  17. <view class="notice-text"><image src="/static/images/notice.png"></image>{{notice || '央广网独家溯源&nbsp;&nbsp;全品类100%经过国标检测'}}</view>
  18. <view class="notice-text"><image src="/static/images/notice.png"></image>{{notice || '央广网独家溯源&nbsp;&nbsp;全品类100%经过国标检测'}}</view>
  19. <!-- <view class="notice-text"><image src="/static/images/notice.png"></image>央广网独家溯源&nbsp;&nbsp;全品类100%经过国标检测</view> -->
  20. </view>
  21. </view>
  22. </view>
  23. <view class="video-box" :style="{'height':isShu?'calc(100vh - 264rpx)':'420rpx'}">
  24. <!-- <image v-if="!isLogin || isAddKf!=1" class="video-poster" :src="courseInfo.imgUrl" mode="aspectFill">
  25. </image> -->
  26. <video
  27. @timeupdate="onTimeUpdate"
  28. @progress="progressChange"
  29. @error="videoErrorCallback"
  30. @play="getPlay"
  31. @pause="getPause"
  32. @ended="getEnded"
  33. @controlstoggle="controlstoggle"
  34. @fullscreenchange="fullscreenchange"
  35. :title="courseInfo.title"
  36. style="width: 100%"
  37. :style="{'height':isShu?'100%':'420rpx'}"
  38. :poster="poster"
  39. id="video-content-box"
  40. controls
  41. :auto-pause-if-open-native="true"
  42. :auto-pause-if-navigate="true"
  43. :enable-progress-gesture="false"
  44. :show-progress="showProgress"
  45. :picture-in-picture-mode="[]"
  46. :show-background-playback-button="false"
  47. :src="videoUrl">
  48. <!-- 完课积分倒计时 -->
  49. <cover-view v-if="remainTime > 0" class="progress-countdown">
  50. <cover-view class="progress-title">完课积分</cover-view>
  51. <!-- <cover-view class="progress-bar-bg">
  52. <cover-view class="progress-bar-fill" :style="{ width: countdownPercentage + '%' }"></cover-view>
  53. </cover-view> -->
  54. <cover-view class="progress-text">
  55. <cover-view class="progress-text-label">倒计时</cover-view>
  56. <cover-view style="font-size: 13px;">{{ formattedCountdown.hours||'00' }}:{{ formattedCountdown.minutes||'00' }}:{{ formattedCountdown.seconds||"00" }}</cover-view>
  57. </cover-view>
  58. </cover-view>
  59. <!-- 虚拟下单跑马灯:视频顶层覆盖,向上滚动淡出切换;!featuredCommentMediaPreviewShow 与 data 直连,避免 video 内 cover-view 对计算属性更新滞后 -->
  60. <cover-view class="vip-order-cover-wrap vip-order-cover-wrap--triple" v-if="showFakeMarqueeOnVideoCover && !featuredCommentMediaPreviewShow && isTripleMarqueeMode && marqueeTriplePayloads.length">
  61. <cover-view class="vip-order-cover-triple-panel vip-order-cover-triple-current" :class="[marqueeTripleSwitching ? 'vip-order-triple-leave' : '']">
  62. <cover-view
  63. class="vip-order-cover-item vip-order-cover-triple-item"
  64. v-for="(row, idx) in marqueeTriplePayloads"
  65. :key="'triple-current-' + idx + '-' + row.nickname + row.timeOffset"
  66. :class="[row.type === 'self' ? 'vip-order-cover-self' : '']"
  67. >
  68. <cover-view class="vip-order-cover-content" v-if="row.type === 'virtual' || row.type === 'self'">
  69. <cover-view class="vip-order-seg vip-order-seg-user">{{ row.nickname }}{{ row.maskedId }}</cover-view>
  70. <cover-view class="vip-order-seg vip-order-seg-time">{{ row.timeOffset }}{{ row.suffix }}</cover-view>
  71. </cover-view>
  72. <cover-view class="vip-order-cover-content" v-else>
  73. {{ row.fullText }}
  74. </cover-view>
  75. </cover-view>
  76. </cover-view>
  77. <cover-view v-if="marqueeTripleSwitching && marqueeTripleIncomingPayloads.length" class="vip-order-cover-triple-panel vip-order-cover-triple-incoming vip-order-triple-enter">
  78. <cover-view
  79. class="vip-order-cover-item vip-order-cover-triple-item"
  80. v-for="(row, idx) in marqueeTripleIncomingPayloads"
  81. :key="'triple-incoming-' + idx + '-' + row.nickname + row.timeOffset"
  82. :class="[row.type === 'self' ? 'vip-order-cover-self' : '']"
  83. >
  84. <cover-view class="vip-order-cover-content" v-if="row.type === 'virtual' || row.type === 'self'">
  85. <cover-view class="vip-order-seg vip-order-seg-user">{{ row.nickname }}{{ row.maskedId }}</cover-view>
  86. <cover-view class="vip-order-seg vip-order-seg-time">{{ row.timeOffset }}{{ row.suffix }}</cover-view>
  87. </cover-view>
  88. <cover-view class="vip-order-cover-content" v-else>
  89. {{ row.fullText }}
  90. </cover-view>
  91. </cover-view>
  92. </cover-view>
  93. </cover-view>
  94. <cover-view class="vip-order-cover-wrap" v-else-if="showFakeMarqueeOnVideoCover && !featuredCommentMediaPreviewShow && marqueeDisplayPayload">
  95. <cover-view
  96. class="vip-order-cover-item vip-order-cover-current"
  97. :class="[
  98. marqueeSwitching ? 'vip-order-leave' : '',
  99. marqueeDisplaySelf ? 'vip-order-cover-self' : ''
  100. ]"
  101. >
  102. <cover-view class="vip-order-cover-content" v-if="marqueeDisplayPayload.type === 'virtual' || marqueeDisplayPayload.type === 'self'">
  103. <cover-view class="vip-order-seg vip-order-seg-user">{{ marqueeDisplayPayload.nickname }}{{ marqueeDisplayPayload.maskedId }}</cover-view>
  104. <cover-view class="vip-order-seg vip-order-seg-time">{{ marqueeDisplayPayload.timeOffset }}{{ marqueeDisplayPayload.suffix }}</cover-view>
  105. </cover-view>
  106. <cover-view class="vip-order-cover-content" v-else>
  107. {{ marqueeDisplayPayload.fullText }}
  108. </cover-view>
  109. </cover-view>
  110. <cover-view
  111. v-if="marqueeSwitching && marqueeIncomingPayload"
  112. class="vip-order-cover-item vip-order-cover-incoming"
  113. :class="[
  114. 'vip-order-enter',
  115. marqueeIncomingSelf ? 'vip-order-cover-self' : ''
  116. ]"
  117. >
  118. <cover-view class="vip-order-cover-content" v-if="marqueeIncomingPayload.type === 'virtual' || marqueeIncomingPayload.type === 'self'">
  119. <cover-view class="vip-order-seg vip-order-seg-user">{{ marqueeIncomingPayload.nickname }}{{ marqueeIncomingPayload.maskedId }}</cover-view>
  120. <cover-view class="vip-order-seg vip-order-seg-time">{{ marqueeIncomingPayload.timeOffset }}{{ marqueeIncomingPayload.suffix }}</cover-view>
  121. </cover-view>
  122. <cover-view class="vip-order-cover-content" v-else>
  123. {{ marqueeIncomingPayload.fullText }}
  124. </cover-view>
  125. </cover-view>
  126. </cover-view>
  127. <cover-view
  128. class="goods-cover"
  129. :style="{ display:isFull&&cardPopup && currentCardItem ? 'block' : 'none'}"
  130. @click.stop="goBuy(currentCardItem)"
  131. >
  132. <cover-view class="goods-cover-hot" v-if="hasHotSaleTag(currentCardItem) && getProductHotCount(currentCardItem) > 0">
  133. <cover-image style="height: 30px;" src="https://bjzmky-1323137866.cos.ap-chongqing.myqcloud.com/class/remai.png" mode="heightFix"></cover-image>
  134. <cover-view class="goods-cover-hot-text">{{ getProductHotCount(currentCardItem) }}</cover-view>
  135. </cover-view>
  136. <cover-view class="goods-cover-inner">
  137. <!-- 关闭按钮 -->
  138. <cover-view class="close-box" @click.stop="closeCardPopup">
  139. <cover-image src="/static/images/close.png"></cover-image>
  140. </cover-view>
  141. <!-- 商品主图 -->
  142. <cover-view class="goods-cover-img">
  143. <cover-image
  144. style="width:150px;height:150px;object-fit:cover;"
  145. :src="currentCardItem.images"
  146. mode="aspectFit"
  147. ></cover-image>
  148. </cover-view>
  149. <cover-view class="goods-cover-info">
  150. <cover-view class="goods-cover-title">{{ currentCardItem.productName || '-' }}</cover-view>
  151. <cover-view class="goods-cover-bottom">
  152. <cover-view class="goods-cover-tag">
  153. <cover-image class="goods-cover-tag-bg" src="/static/images/pbg.png" mode="widthFix"></cover-image>
  154. <cover-view class="goods-cover-tag-text">惊喜价</cover-view>
  155. </cover-view>
  156. <cover-view class="goods-cover-price">
  157. <cover-view class="unit">¥</cover-view>
  158. <cover-view class="price">
  159. {{ currentCardItem.price || '0.00' }}
  160. </cover-view>
  161. </cover-view>
  162. </cover-view>
  163. </cover-view>
  164. </cover-view>
  165. </cover-view>
  166. <!-- logo -->
  167. <!-- 弹幕展示 -->
  168. <!-- <template v-if="showDanmu==1&&openCommentStatus==2">
  169. <text v-for="(item, index) in activeDanmus" :key="item.commentId" class="danmu-item danmuMove"
  170. :style="item.danmustyle" @animationend="animationend(item,index)">
  171. {{ item.content }}
  172. </text>
  173. </template> -->
  174. <!-- <cover-view class="video-danmu-btnbox" :style="{display: openCommentStatus==2&&isfull&&crtShow&&isLogin&&isAddKf==1 ? 'block':'none'}">
  175. <cover-image class="video-danmu-image" :src="imgPath+'/app/image/danmu_set.png'" @click="openDanmu(1)"></cover-image>
  176. </cover-view> -->
  177. <!-- <cover-image v-if="courseLogo" :class="isfull?'logo-full':'logo'" :src="courseLogo" mode="widthFix"></cover-image> -->
  178. </video>
  179. </view>
  180. <!-- 商品卡片弹窗 -->
  181. <view class="goods-card" :style="{'bottom':isShu?'280rpx':'220rpx'}" v-if="!isFull&&cardPopup && currentCardItem" @click.stop="goBuy(currentCardItem)">
  182. <view class="close-box" @click.stop="closeCardPopup">
  183. <image src="/static/images/close.png"></image>
  184. </view>
  185. <view class="goods-card-hot" v-if="hasHotSaleTag(currentCardItem) && getProductHotCount(currentCardItem) > 0">
  186. <image style="height: 56rpx;" src="https://bjzmky-1323137866.cos.ap-chongqing.myqcloud.com/class/remai.png" mode="heightFix"></image>
  187. <text class="goods-card-hot-count">{{ getProductHotCount(currentCardItem) }}</text>
  188. </view>
  189. <view class="goods-card-inner">
  190. <image class="goods-card-img" :src="currentCardItem.images || currentCardItem.image" mode="aspectFill"></image>
  191. <view class="goods-card-info">
  192. <view class="goods-card-title">{{ currentCardItem.productName || '-' }}</view>
  193. <view class="goods-card-bottom">
  194. <view class="goods-card-tag">
  195. <image class="goods-card-tag-bg" src="/static/images/pbg.png" mode="widthFix"></image>
  196. <view class="goods-card-tag-text">惊喜价</view>
  197. </view>
  198. <view class="goods-card-price">
  199. <view class="unit">¥</view>
  200. <text class="price">
  201. {{ currentCardItem.price || '0.00' }}
  202. </text>
  203. </view>
  204. </view>
  205. </view>
  206. </view>
  207. </view>
  208. <!-- 弹幕方法 -->
  209. <commentBox
  210. v-if="openCommentStatus==2"
  211. ref="danmuBox"
  212. :height="height"
  213. :urlOption="urlOption"
  214. :time="playTime"
  215. :showDanmu="showDanmu"
  216. :viewCommentNum="viewCommentNum"
  217. :openCommentStatus="openCommentStatus"
  218. @setInputText="setInputText"
  219. @getScrollTop="getScrollTop"
  220. @getMore="getMore"
  221. @getActiveDanmus="getActiveDanmus"></commentBox>
  222. <!-- <view id="title-contentnav">
  223. <view class="title-content" v-show="openCommentStatus!=1"> -->
  224. <!-- 答题时展示小节课程名,其他展示课程名 -->
  225. <!-- 小节课程名 -->
  226. <!-- <view class="subtitlebox" v-if="isLogin&&isAddKf==1">
  227. {{courseInfo.title}}
  228. </view> -->
  229. <!-- 课程名字 -->
  230. <!-- <view class="miantitlebox" v-else>
  231. {{courseInfo.courseName}}
  232. </view>
  233. </view>
  234. <view class="tabbox" v-if="openCommentStatus==1">
  235. <view :class="currentTab == nav.id ? 'tabbox-active':''" v-for="nav in navList" :key="nav.id" @click="handleTab(nav.id)">{{nav.name}}</view>
  236. </view>
  237. </view> -->
  238. <!-- <scroll-view
  239. class="scroll-view"
  240. :style="{height: height}"
  241. :scroll-top="scrollTop"
  242. :scroll-y="true"
  243. :refresher-enabled="currentTab==0"
  244. :refresher-triggered="triggered"
  245. @refresherrefresh="handleRefresher">
  246. <template v-if="openCommentStatus==1">
  247. <view v-show="currentTab==0">
  248. <descInfoNav ref="descInfoNav" :isLogin="isLogin" :isAddKf="isAddKf" :courseInfo="courseInfo"></descInfoNav>
  249. </view>
  250. <view v-show="currentTab==2">
  251. <commentBox
  252. ref="commentBox"
  253. :height="height"
  254. :urlOption="urlOption"
  255. :time="playTime"
  256. :flagTime="flagTime"
  257. :showDanmu="showDanmu"
  258. :viewCommentNum="viewCommentNum"
  259. :openCommentStatus="openCommentStatus"
  260. @setInputText="setInputText"
  261. @getScrollTop="getScrollTop"
  262. @getMore="getMore"></commentBox>
  263. </view>
  264. </template> -->
  265. <!-- <view v-show="currentTab==1"> -->
  266. <!-- <template v-if="openCommentStatus!=1&&(courseInfo.title || courseInfo.description)">
  267. <descInfo ref="descInfo" :isLogin="isLogin" :isAddKf="isAddKf" :courseInfo="courseInfo"></descInfo>
  268. </template> -->
  269. <!-- 问题 -->
  270. <!-- <template v-if="isLogin&&isAddKf==1">
  271. <ques ref="ques" source="videoauto" :urlOption="urlOption" :quesList="quesList" :openCommentStatus="openCommentStatus" :treatmentPackage="treatmentPackage" :showTreatment="showTreatment" @handleAnswer="handleAnswer" @showBtnType="showBtnType"></ques>
  272. </template> -->
  273. <!-- </view> -->
  274. <!-- </scroll-view> -->
  275. <!-- 评价(多 Tab:精选留言 / 栏目介绍 / 评价得积分) -->
  276. <view class="rating-box" :style="{paddingTop:visibleRatingTabCount == 1?'30rpx':'24rpx'}" v-if="isLogin&&isAddKf==1&&!isShu&&hasVisibleRatingTab">
  277. <image class="bg" src="https://bjzmky-1323137866.cos.ap-chongqing.myqcloud.com/app/vip/bg-back.png" mode="widthFix"></image>
  278. <view class="tab-Box" v-if="visibleRatingTabCount > 1">
  279. <view
  280. v-for="tab in ratingTabList"
  281. :key="tab.key"
  282. class="tab-item"
  283. :class="{ 'tab-item--active': tabIndex == tab.index }"
  284. @click="selecTab(tab.index)"
  285. >
  286. <view class="tab-item__surface">
  287. <image :src="tabIndex == tab.index ? tab.iconActive : tab.icon" mode="aspectFit"></image>
  288. <text class="tab-item__text">{{ tab.label }}</text>
  289. </view>
  290. </view>
  291. </view>
  292. <!-- 仅一个 Tab:单行左对齐标题,无选中态(顺序:精选留言 / 栏目介绍 / 评价得积分) -->
  293. <view class="tab-Box tab-Box--single" v-else-if="visibleRatingTabCount === 1">
  294. <view v-for="tab in ratingTabList" :key="tab.key" class="tab-item tab-item--single">
  295. <view class="tab-item__surface tab-item__surface--single">
  296. <image :src="tab.iconActive" mode="aspectFit"></image>
  297. <text class="tab-item__text tab-item__text--single">{{ tab.label }}</text>
  298. </view>
  299. </view>
  300. </view>
  301. <view class="rating-body" v-if="tabIndex == 2 && showTabReviewPoints">
  302. <view class="rating-ques-block" v-for="(item,index) in quesList" :key="index">
  303. <view class="title">{{item.title}}</view>
  304. <view class="sat-box">
  305. <view class="sat" v-for="(option,idx) in item.questionOption" :key="idx" :class="aindex==option.indexId?'sat--active':''" @click="handleAnswer(item,option)">
  306. <view class="sat-img-wrap" :class="rateBounceIndex==option.indexId?'sat-img-bounce':''">
  307. <image :src="aindex==option.indexId?rateList[option.indexId].selIcon:rateList[option.indexId].icon" mode="aspectFit"></image>
  308. </view>
  309. <text :class="aindex==option.indexId?'active':''">{{option.name}}</text>
  310. </view>
  311. </view>
  312. </view>
  313. </view>
  314. <view class="rating-body rating-body--plain" :style="{padding:courseInfo.courseIntroImg?'0rpx':'30rpx'}" v-if="tabIndex === 1 && showTabColumnIntro">
  315. <image
  316. class="rating-intro-cover-img"
  317. :src="courseInfo.courseIntroImg"
  318. mode="widthFix"
  319. ></image>
  320. </view>
  321. <view class="rating-body rating-body--featured" v-if="tabIndex == 0 && showTabFeaturedComments">
  322. <scroll-view
  323. scroll-y
  324. class="rating-msg-scroll"
  325. :show-scrollbar="false"
  326. scroll-with-animation
  327. lower-threshold="100"
  328. @scrolltolower="onFeaturedCommentScrollToLower"
  329. >
  330. <view v-if="featuredCommentLoading" class="rating-msg-empty">加载中...</view>
  331. <template v-else>
  332. <view
  333. class="rating-msg-item"
  334. v-for="(it, idx) in featuredCommentDisplayList"
  335. :key="it.commentId"
  336. >
  337. <view class="rating-msg-avatar-wrap">
  338. <image
  339. class="rating-msg-avatar"
  340. :src="getCommentAvatar(it) || 'https://bjzmky-1323137866.cos.ap-chongqing.myqcloud.com/video/head.png'"
  341. mode="aspectFill"
  342. />
  343. </view>
  344. <view class="rating-msg-main">
  345. <text class="rating-msg-name">{{ getCommentDisplayName(it) }}</text>
  346. <view class="rating-msg-text">{{ getCommentTextOnly(it) }}</view>
  347. <view v-if="getCommentVideoUrl(it)" class="rating-msg-media rating-msg-media--video">
  348. <view class="rating-msg-video-wrap">
  349. <video
  350. :id="getFeaturedCommentVideoId(it, idx)"
  351. class="rating-msg-video"
  352. :src="getCommentVideoUrl(it)"
  353. :poster="getCommentVideoPoster(it)"
  354. :controls="false"
  355. object-fit="fill"
  356. :show-play-btn="false"
  357. :show-center-play-btn="false"
  358. :enable-progress-gesture="false"
  359. ></video>
  360. <view
  361. class="rating-msg-video-play-cover"
  362. @tap.stop="openFeaturedCommentMediaPreview(it, idx, 'video')"
  363. >
  364. <image
  365. class="rating-msg-video-play-icon"
  366. src="https://bjzmky-1323137866.cos.ap-chongqing.myqcloud.com/video/bofang.png"
  367. mode="aspectFit"
  368. />
  369. </view>
  370. </view>
  371. </view>
  372. <view
  373. v-else-if="commentImageUrls(it).length"
  374. class="rating-msg-media rating-msg-media--imgs"
  375. >
  376. <image
  377. v-for="(img, mi) in commentImageUrls(it)"
  378. :key="mi"
  379. :src="img"
  380. mode="widthFix"
  381. class="rating-msg-img"
  382. @click="openFeaturedCommentMediaPreview(it, idx, 'image', mi)"
  383. />
  384. </view>
  385. </view>
  386. </view>
  387. <view v-if="!featuredCommentDisplayList.length" class="rating-msg-empty">暂无精选留言</view>
  388. <view v-if="featuredCommentLoadingMore" class="rating-msg-load-more">加载中...</view>
  389. <view
  390. v-else-if="showFeaturedCommentNoMoreFooter"
  391. class="rating-msg-load-more rating-msg-load-more--done"
  392. >没有更多了</view>
  393. <!-- 底部锚点:发布成功后 scroll-into-view 定位到底部 -->
  394. <view id="fc-scroll-anchor" class="rating-msg-scroll-anchor"></view>
  395. </template>
  396. </scroll-view>
  397. </view>
  398. </view>
  399. <!-- 线路 -->
  400. <!-- <view class="video-line" @click="openPop" v-if="isLogin&&isAddKf==1">
  401. <image :src="imgPath+'/app/image/changePlayer-icon.png'"></image>
  402. <text>线路{{numberToChinese(lineIndex + 1)}}</text>
  403. </view> -->
  404. <!-- 线路弹窗 -->
  405. <uni-popup ref="popup" type="bottom" class="full-width-popup">
  406. <view class="popupbox">
  407. <view class="popupbox-head">
  408. <text>线路选择</text>
  409. <image class="close-icon" :src="imgPath+'/app/image/tc_close_icon.png'" mode="aspectFill" @click="close">
  410. </image>
  411. </view>
  412. <view class="popupbox-content">
  413. <view :class="lineIndex == index ? 'line-item line-active': 'line-item'"
  414. v-for="(it,index) in lineList" :key="index" @click="handleLine(index)">
  415. 线路{{numberToChinese(index + 1)}}</view>
  416. </view>
  417. </view>
  418. </uni-popup>
  419. <!-- 温馨提示弹窗 -->
  420. <uni-popup ref="tipsPopup" type="center" :is-mask-click="false">
  421. <view class="tipsPopup-mask">
  422. <image class="red_envelope_top" :src="imgPath+'/app/image/red_envelope_img.png'" mode="aspectFill"></image>
  423. <view class="tipsPopup">
  424. <image class="tipsPopup-close" :src="imgPath+'/app/image/course_close_white_icon.png'" mode="aspectFill"
  425. @click="closeTipsPop"></image>
  426. <view class="tipsPopup-line">
  427. <view class="tipsPopup-box">
  428. <view class="tipsPopup-head">
  429. <image class="tipsPopup-head-title" :src="imgPath+'/app/image/tips_title_img.png'"
  430. mode="widthFix"></image>
  431. </view>
  432. <view class="tipsPopup-content">
  433. <view class="tipsPopup-content-title">亲爱的用户,</view>
  434. <view>您已经观看课程一半的时间了,请注意休息并保持专注。</view>
  435. </view>
  436. <view class="tipsPopup-btn-box">
  437. <view class="tipsPopup-btn" @click="closeTipsPop">继续观看领奖励</view>
  438. </view>
  439. </view>
  440. </view>
  441. </view>
  442. </view>
  443. </uni-popup>
  444. <!-- 发送弹幕 -->
  445. <view class="video-line danmu-line" @click="openDanmu(0)" v-if="isLogin&&isAddKf==1&&openCommentStatus==2">
  446. <image class="set_image" :src="imgPath+'/app/image/danmu_set_black.png'" mode="aspectFill"></image>
  447. <text>发弹幕</text>
  448. </view>
  449. <!-- 发送弹幕弹窗 -->
  450. <uni-popup ref="danmuPopup" type="bottom" style="z-index: 999;" @change="changeShowPopup">
  451. <view class="danmuPopup" :style="{marginLeft:isfull ? statusBarHeight+'px': 0,marginBottom: danmuboxHeight+'px'}">
  452. <view class="danmuPopup-head border-line">
  453. <image class="danmu-icon" :src="showDanmu==0? imgPath+'/app/image/danmu-off.png':imgPath+'/app/image/danmu-on.png'" mode="heightFix" @click="switchDanmu()"></image>
  454. <view class="u-border">
  455. <input
  456. class="danmuPopup-input"
  457. placeholder="发个弹幕吧~"
  458. border="border"
  459. :focus="focus"
  460. :adjustPosition="false"
  461. :autoBlur="false"
  462. maxlength="50"
  463. v-model.trim="inputText" />
  464. <text style="font-size: 24rpx;color: #bbb;margin-left: 10rpx;">{{inputText?inputText.trim().length:0}}/50</text>
  465. </view>
  466. <button class="danmuPopup-send" @click="handleChatInput">发送</button>
  467. </view>
  468. </view>
  469. </uni-popup>
  470. <!-- 答题弹窗 -->
  471. <uni-popup ref="answerPopup" type="center" :show="answerPopup">
  472. <view :class="errTitle == '恭喜你,回答正确' ? 'answerPopup-box bg':'answerPopup-box'">
  473. <!-- 正确 -->
  474. <image class="tipimg" v-if="errTitle == '恭喜你,回答正确'" :src="imgPath+'/app/image/course_answer_img.png'"
  475. mode="aspectFill"></image>
  476. <!-- 错误 -->
  477. <image class="tipimg" v-else :src="imgPath+'/app/image/course_answer_incorrectly_img.png'" mode="aspectFill">
  478. </image>
  479. <view class="answerPopup-title">{{errTitle}}</view>
  480. <view class="answerPopup-desc" v-if="isOpen!=1" v-html="errDesc"></view>
  481. <!-- 选择奖励 -->
  482. <view class="reward-list" v-if="errTitle == '恭喜你,回答正确'&&isOpen!=1">
  483. <radio-group class="reward-list-group" @change="rewardChange">
  484. <label class="reward-list-option" v-for="(item, index) in rewardType" :key="item.value">
  485. <radio :value="item.value+ ''" :checked="item.value == currentReward"
  486. activeBorderColor="#FF5C03" activeBackgroundColor="#FF5C03"
  487. style="transform:scale(0.7)" />
  488. <view :style="{color: item.value == currentReward ? '#FF5C03':''}">{{item.name}}</view>
  489. </label>
  490. </radio-group>
  491. </view>
  492. <!-- 错误题目 -->
  493. <view class="errQuesbox" v-if="errQues&&errQues.length>0">
  494. <view class="errQuesbox-item textOne" v-for="(it,index) in errQues" :key="index">{{it.title}}</view>
  495. </view>
  496. <view class="answerPopup-btn" v-if="errTitle == '恭喜你,回答正确'" @click="closeAnswerPopup">确认</view>
  497. <view class="tipsPopup-btn-box" v-else
  498. :style="{marginTop: errQues&&errQues.length>0 ? '40rpx':'54rpx'}">
  499. <view class="tipsPopup-btn" @click="closeAnswerPopup">{{remain > 0 ? '重新答题': '确认'}}</view>
  500. </view>
  501. </view>
  502. </uni-popup>
  503. <!-- 可以答题提示 -->
  504. <!-- <view class="answerTip" v-if="isLogin&&currentTab!=1&&openCommentStatus==1&&showAnswerTip" @click="handleTab(1)">
  505. <text>开始</text>
  506. <text>答题</text>
  507. </view> -->
  508. <!-- 客服二维码弹窗 -->
  509. <uni-popup ref="kfPopup" type="center" :mask-click="false">
  510. <view class="kfqrcode-box">
  511. <image class="kfqrcode" :src="qrcode" show-menu-by-longpress="true"></image>
  512. <view v-show="qrcodeMsg" style="margin-top: 30rpx;" v-html="qrcodeMsg"></view>
  513. <image class="kfqrcode-close" :src="imgPath+'/app/image/course_close_white_icon.png'" mode="aspectFill"
  514. @click="closeKFPop"></image>
  515. </view>
  516. </uni-popup>
  517. <!-- 购物车弹窗 -->
  518. <u-popup :show="isCart" @close="closeShop" round="20rpx" bgColor="#fff">
  519. <scroll-view class="scroll-view" style="height:500rpx;box-sizing: border-box;padding: 24rpx;" scroll-y="true" enable-flex>
  520. <goodsList ref="goodsList" :treatmentPackage="displayProductList" :hotCountMap="productHotCountMap" :urlOption="urlOption"></goodsList>
  521. </scroll-view>
  522. </u-popup>
  523. <!-- 更多操作弹窗 -->
  524. <u-popup :show="isMore" @close="closeMore" round="20rpx" bgColor="#fff">
  525. <view class="more-actions-popup">
  526. <view class="more-action-item" @click="navgetTo('/pages_user/user/storeOrder?status=')">
  527. <u-icon name="shopping-cart" color="#FF233C" size="40"></u-icon>
  528. <view class="action-label">我的订单</view>
  529. </view>
  530. <view class="more-action-item" @click="navgetTo('/pages_user/user/integralLogsList')">
  531. <u-icon name="calendar" color="#FF233C" size="40"></u-icon>
  532. <view class="action-label">积分记录</view>
  533. </view>
  534. <view class="more-action-item" @click="navgetTo('/pages_user/user/integralGoodsList')">
  535. <u-icon name="gift" color="#FF233C" size="40"></u-icon>
  536. <view class="action-label">兑换好礼</view>
  537. </view>
  538. </view>
  539. </u-popup>
  540. <u-popup
  541. :show="featuredMediaActionSheetShow"
  542. mode="bottom"
  543. round="20rpx"
  544. bgColor="#fff"
  545. :duration="300"
  546. @close="closeFeaturedMediaActionSheet"
  547. >
  548. <view class="fc-media-actions-sheet">
  549. <view class="fc-media-actions-sheet-item" @tap.stop="onFeaturedMediaSheetPick(0)">相册图片</view>
  550. <view class="fc-media-actions-sheet-item" @tap.stop="onFeaturedMediaSheetPick(1)">相册视频</view>
  551. <!-- <view class="fc-media-actions-sheet-gap"></view> -->
  552. <view
  553. class="fc-media-actions-sheet-item fc-media-actions-sheet-cancel"
  554. @tap.stop="closeFeaturedMediaActionSheet"
  555. >取消</view>
  556. </view>
  557. </u-popup>
  558. <!-- footer -->
  559. <view class="footer" v-if="videoId">
  560. <view class="btns" :style="{'justify-content':isLogin&&isAddKf==1&&remainTime==0&&hasVisibleRatingTab&&!showTabColumnIntro?'space-between':'flex-end'}">
  561. <!-- <button
  562. class="author-btn"
  563. open-type="getPhoneNumber"
  564. @getphonenumber="phoneLogin" v-if="!isquestion&&authType==1">{{isLogin&&isAddKf==1 ? '提交答案领取奖励' : '立即学习'}}</button> -->
  565. <!-- <button
  566. class="author-btn"
  567. open-type="getUserInfo" :disabled="userdisabled"
  568. @getuserinfo="userInfologin" v-if="authType==0&&!isquestion">{{isLogin&&isAddKf==1 ? '提交答案领取奖励' : '立即学习'}}</button> -->
  569. <template>
  570. <button class="author-btn" @click="openList" v-if="isLogin&&isAddKf==1&&isShu&&(showTabFeaturedComments || showTabColumnIntro || showTabReviewPoints)">{{ showTabFeaturedComments ? '去留言' : showTabColumnIntro ? '栏目介绍' : '评价得积分' }}
  571. <image v-if="showTabReviewPoints&&!showTabFeaturedComments&&!showTabColumnIntro" src="/static/images/jifen.png"></image></button>
  572. <button class="author-btn" @click="submit" v-if="isLogin&&isAddKf==1&&remainTime==0&&!isShu&&tabIndex==2">评价得积分
  573. <image src="/static/images/jifen.png"></image>
  574. </button>
  575. </template>
  576. <button class="author-btn" @click="submit" v-if="!isLogin&&isAddKf!==1">立即学习</button>
  577. <!-- <button
  578. class="author-btn"
  579. open-type="getUserInfo" :disabled="userdisabled"
  580. @getuserinfo="userInfologin" v-if="authType==0&&!isLogin&&isAddKf!==1">立即学习</button> -->
  581. <!-- 竖屏 isShu 下 rating 区隐藏;若仍停留在精选留言 tab,勿在 footer 展示留言框,只保留「评价得积分」按钮 -->
  582. <view v-if="tabIndex==0 && showTabFeaturedComments&&!isShu" class="rating-msg-input-shell">
  583. <view
  584. v-if="featuredCommentMedia"
  585. class="rating-msg-media-pill"
  586. @click.stop="clearFeaturedCommentMedia"
  587. >
  588. <image
  589. v-if="featuredCommentMedia.type === 'image'"
  590. class="rating-msg-media-thumb"
  591. :src="featuredCommentMedia.path"
  592. mode="aspectFill"
  593. />
  594. <image
  595. v-else
  596. class="rating-msg-media-thumb"
  597. :src="featuredCommentMedia.thumbTempPath || featuredCommentMedia.path"
  598. mode="aspectFill"
  599. />
  600. <view v-if="featuredCommentMedia.type === 'video'" class="rating-msg-media-badge">视频</view>
  601. <text class="rating-msg-media-x">×</text>
  602. </view>
  603. <view class="rating-msg-input rating-msg-input-trigger" @tap.stop="openFeaturedCommentComposer">
  604. <text
  605. class="rating-msg-input-trigger-text"
  606. :class="{ 'is-ph': !featuredCommentInput }"
  607. >{{ featuredCommentInput || '善言善语结善缘~' }}</text>
  608. </view>
  609. <view class="rating-msg-input-icon" @tap.stop="openFeaturedCommentComposer">
  610. <image src="https://bjzmky-1323137866.cos.ap-chongqing.myqcloud.com/video/upimg.png"></image>
  611. </view>
  612. </view>
  613. <view class="author-cart" v-show="!(tabIndex === 0 && showTabFeaturedComments && isLogin && isAddKf == 1 && !isquestion && !isShu)">
  614. <view class="more-box">
  615. <image src="/static/images/more24.png" @click="showMore"></image>
  616. <view class="tips">更多</view>
  617. </view>
  618. <view class="more-box" style="margin-left: 30rpx;position: relative;">
  619. <image src="/static/images/kefu.png" @click="showMore"></image>
  620. <view class="tips">客服</view>
  621. <button class="contact-btn" open-type="contact"></button>
  622. </view>
  623. <image class="xianshi" v-if="showTreatment==0&&displayProductList.length>0&&!cardPopup" src="/static/images/xg.png"></image>
  624. <view class="more-box" v-if="showTreatment==0">
  625. <image @click="showCart" src="/static/images/cart.png"></image>
  626. <view class="tips">去下单</view>
  627. </view>
  628. </view>
  629. </view>
  630. </view>
  631. <view v-show="currentTab==2">
  632. <view class="chatinput" :style="{bottom:danmuboxHeight>0?danmuboxHeight+'px':'calc(var(--window-bottom) + 24rpx)'}">
  633. <input class="uni-input" v-model.trim="inputText" :adjustPosition="false" :autoBlur="false" maxlength="140" placeholder="发消息···" confirm-type="send" @confirm="handleChatInput" />
  634. <button class="send" @click="handleChatInput">发送</button>
  635. </view>
  636. </view>
  637. <template v-if="tabIndex==0 && showTabFeaturedComments && !(showTabReviewPoints && isShu)">
  638. <view
  639. v-if="featuredCommentComposerActive"
  640. class="featured-comment-composer-mask"
  641. @tap="onFeaturedCommentComposerMaskTap"
  642. ></view>
  643. <view
  644. v-if="featuredCommentComposerActive"
  645. class="featured-comment-composer-wrap"
  646. :style="{ bottom: featuredCommentKeyboardBottomPx + 'px' }"
  647. >
  648. <view class="featured-comment-composer-inner">
  649. <view
  650. v-if="featuredCommentMedia"
  651. class="fc-composer-media-pill"
  652. @click.stop="clearFeaturedCommentMedia"
  653. >
  654. <image
  655. v-if="featuredCommentMedia.type === 'image'"
  656. class="fc-composer-media-thumb"
  657. :src="featuredCommentMedia.path"
  658. mode="aspectFill"
  659. />
  660. <image
  661. v-else
  662. class="fc-composer-media-thumb"
  663. :src="featuredCommentMedia.thumbTempPath || featuredCommentMedia.path"
  664. mode="aspectFill"
  665. />
  666. <view v-if="featuredCommentMedia.type === 'video'" class="fc-composer-media-badge">视频</view>
  667. <text class="fc-composer-media-x">×</text>
  668. </view>
  669. <view class="featured-comment-composer-input-row">
  670. <textarea
  671. class="fc-composer-input"
  672. v-model="featuredCommentInput"
  673. type="text"
  674. confirm-type="done"
  675. :show-confirm-bar="false"
  676. placeholder="善言善语结善缘~"
  677. placeholder-class="fc-composer-ph"
  678. :focus="featuredCommentInputFocused"
  679. :adjust-position="false"
  680. @blur="onFeaturedCommentComposerInputBlur"
  681. />
  682. <view class="fc-composer-input-icon" @click.stop="onFeaturedPickMedia">
  683. <image src="https://bjzmky-1323137866.cos.ap-chongqing.myqcloud.com/video/upimg.png"></image>
  684. </view>
  685. </view>
  686. <view class="featured-comment-composer-publish" @tap.stop="submitFeaturedComment">发布</view>
  687. </view>
  688. </view>
  689. </template>
  690. <view
  691. v-if="featuredCommentMediaPreviewShow"
  692. class="fc-media-preview-mask"
  693. @tap="closeFeaturedCommentMediaPreview"
  694. >
  695. <view class="fc-media-preview-card" @tap.stop="featuredCommentPreviewCardTap">
  696. <view
  697. v-if="featuredCommentMediaPreviewMode === 'image' && featuredCommentMediaPreviewUrls.length"
  698. class="fc-media-preview-img-box"
  699. >
  700. <view class="fc-media-preview-slide">
  701. <image
  702. class="fc-media-preview-main-img"
  703. :src="featuredCommentMediaPreviewUrls[featuredCommentMediaPreviewIndex]"
  704. mode="aspectFit"
  705. />
  706. </view>
  707. </view>
  708. <view
  709. v-else-if="featuredCommentMediaPreviewMode === 'video' && featuredCommentMediaPreviewVideoUrl"
  710. class="fc-media-preview-video-box"
  711. >
  712. <video
  713. id="fc-media-preview-video"
  714. class="fc-media-preview-video"
  715. :src="featuredCommentMediaPreviewVideoUrl"
  716. :poster="featuredCommentMediaPreviewPoster"
  717. controls
  718. :show-center-play-btn="true"
  719. :show-play-btn="true"
  720. :show-fullscreen-btn="false"
  721. :auto-pause-if-open-native="false"
  722. :auto-pause-if-navigate="true"
  723. :enable-progress-gesture="true"
  724. :show-background-playback-button="false"
  725. @play="onFeaturedMediaPreviewModalVideoPlay"
  726. />
  727. </view>
  728. <view class="fc-media-preview-footer">
  729. <scroll-view
  730. v-if="featuredCommentMediaPreviewMode === 'image' && featuredCommentMediaPreviewUrls.length > 1"
  731. scroll-x
  732. class="fc-media-preview-thumbs"
  733. :show-scrollbar="false"
  734. >
  735. <view class="fc-media-preview-thumbs-inner">
  736. <image
  737. v-for="(tu, ti) in featuredCommentMediaPreviewUrls"
  738. :key="'t-' + ti"
  739. class="fc-media-preview-thumb"
  740. :class="{ 'fc-media-preview-thumb--active': ti === featuredCommentMediaPreviewIndex }"
  741. :src="tu"
  742. mode="aspectFill"
  743. @tap.stop="setFeaturedMediaPreviewIndex(ti)"
  744. />
  745. </view>
  746. </scroll-view>
  747. <view v-else class="fc-media-preview-thumbs-spacer" />
  748. <view class="fc-media-preview-close" @tap.stop="closeFeaturedCommentMediaPreview">
  749. <u-icon name="close-circle" color="#fff" size="30"></u-icon>
  750. </view>
  751. </view>
  752. </view>
  753. </view>
  754. <ykscreenRecord></ykscreenRecord>
  755. <courseExpiration v-if="showExpiration" :code="resCode" :msg="resMsg" :qrcode="qrcode"
  756. :userId="user && user.userId ? user.userId : ''"></courseExpiration>
  757. </view>
  758. </template>
  759. <script>
  760. import ques from "./components/ques.vue"
  761. import descInfo from "./components/descInfo.vue"
  762. import descInfoNav from "./components/descInfoNav.vue"
  763. import commentBox from "./components/commentBox.vue"
  764. import goodsList from "./components/goodsList.vue"
  765. import { hasHotSaleTag, getProductHotKey, clampHotSaleCount } from './utils/productHotSale.js'
  766. import {TOKEN_KEYAuto,generateRandomString} from './utils/courseTool.js'
  767. import { buildFakeOrderPool } from './utils/videovipFakeMarquee.js'
  768. import ykscreenRecord from "@/components/yk-screenRecord/yk-screenRecord.vue"
  769. import courseExpiration from './components/courseExpiration.vue'
  770. import dayjs from 'dayjs';
  771. import { mapGetters } from 'vuex';
  772. import {
  773. getErrMsg,
  774. getH5CourseByVideoId,
  775. getH5CourseVideoDetails,
  776. courseAnswer,
  777. getFinishCourseVideo,
  778. getIsAddKf,
  779. getInternetTraffic,
  780. getIntegralByH5Video,
  781. sendReward,
  782. loginByMp,
  783. getRealLink,
  784. loginByMiniApp
  785. } from "@/api/courseAuto.js"
  786. import {
  787. getConfigByKey
  788. } from "@/api/user.js"
  789. import {
  790. handleFsUserWx,
  791. getRemainTime,
  792. getCommentList,
  793. addCommentWithMedia
  794. } from '@/api/courseLook.js'
  795. export default {
  796. components: {
  797. descInfoNav,
  798. descInfo,
  799. commentBox,
  800. ques,
  801. goodsList,
  802. ykscreenRecord,
  803. courseExpiration
  804. },
  805. data() {
  806. return {
  807. displayType:'',
  808. isShu:false,//竖屏默认
  809. notice:'',
  810. aindex:null,
  811. rateBounceIndex: null,
  812. _rateBounceTimer: null,
  813. rateList:[{
  814. id:0,
  815. icon:'https://bjzmky-1323137866.cos.ap-chongqing.myqcloud.com/app/vip/hsat.png',
  816. name:'非常满意',
  817. selIcon:'https://bjzmky-1323137866.cos.ap-chongqing.myqcloud.com/app/vip/hsat_sel.png'
  818. },
  819. {
  820. id:1,
  821. icon:'https://bjzmky-1323137866.cos.ap-chongqing.myqcloud.com/app/vip/sat.png',
  822. name:'满意',
  823. selIcon:'https://bjzmky-1323137866.cos.ap-chongqing.myqcloud.com/app/vip/sat_sel.png'
  824. },
  825. {
  826. id:2,
  827. icon:'https://bjzmky-1323137866.cos.ap-chongqing.myqcloud.com/app/vip/fari.png',
  828. name:'一般',
  829. selIcon:'https://bjzmky-1323137866.cos.ap-chongqing.myqcloud.com/app/vip/fari_sel.png'
  830. },
  831. {
  832. id:3,
  833. icon:'https://bjzmky-1323137866.cos.ap-chongqing.myqcloud.com/app/vip/unsat.png',
  834. name:'不满意',
  835. selIcon:'https://bjzmky-1323137866.cos.ap-chongqing.myqcloud.com/app/vip/unsat_sel.png'
  836. }
  837. ],
  838. resMsg:"",
  839. resCode:'',
  840. showExpiration:false,
  841. code: '',
  842. baseUrl:uni.getStorageSync('requestPath'),
  843. // 1 红包 2 积分
  844. rewardType: [{
  845. name: '红包奖励',
  846. value: 1
  847. }, {
  848. name: '积分奖励',
  849. value: 2
  850. }],
  851. currentReward: 1,
  852. player: null,
  853. loading: true,
  854. progress: 0,
  855. statusBarHeight: uni.getSystemInfoSync().statusBarHeight,
  856. scrollTop: 0,
  857. height: '0px',
  858. isLogin: false,
  859. videoUrl: "",
  860. videoId: "",
  861. //现在的时长
  862. /** 恢复前台 / seek 后短时间内跳过「快进」判定,避免 iOS、鸿蒙息屏后首帧 currentTime 抖动误报 */
  863. _antiSeekSuppressExpireAt: 0,
  864. _videoPageHadShownOnce: false,
  865. playTime: 0,
  866. //总时长
  867. duration: 0,
  868. playDuration: 0,
  869. // 用于续播
  870. playDurationSeek: 0,
  871. // 温馨提醒时间节点,
  872. tipsTime: 0,
  873. tipsOpen: false,
  874. config: {},
  875. courseInfo: {},
  876. quesList: [],
  877. lineList: [],
  878. // 错题
  879. errQues: [],
  880. // 答题机会
  881. remain: 0,
  882. errTitle: "",
  883. errDesc: "",
  884. showPlay: true,
  885. showControls: false,
  886. playStatus: "",
  887. isAddKf: 0,
  888. lineIndex: 0,
  889. // 是否展开
  890. isExpand: true,
  891. textHeight: 0, //文本高度
  892. qwUserId: "",
  893. qrcode: "",
  894. corpId: "",
  895. qrcodeMsg: "",
  896. urlOption: {},
  897. bufferRate: 0, // 缓冲时间
  898. uuId: "",
  899. isEnded: false,
  900. // 是否允许拖动进度条
  901. videolinkType: 0,
  902. ip: null,
  903. checked: true,
  904. isFinish: 0, // 是否完课
  905. interval: null,
  906. intervalIntegral: null, // 积分定时
  907. options: {
  908. sources: [{
  909. src: ""
  910. }],
  911. poster: "",
  912. live: false /* 是否直播 */ ,
  913. controls: true,
  914. autoplay: false,
  915. licenseUrl: 'https://license.vod2.myqcloud.com/license/v2/1323137866_1/v_cube.license', // license 地址,参考准备工作部分,在视立方控制台申请 license 后可获得 licenseUrl,
  916. LicenseKey: 'bcc5bd9a14b798b48c52ff005a21d926',
  917. controlBar: {
  918. volumePanel: false,
  919. playbackRateMenuButton: false,
  920. QualitySwitcherMenuButton: false,
  921. // progressControl: false
  922. },
  923. plugins: {
  924. // ProgressMarker: false,
  925. ContextMenu: {
  926. statistic: false
  927. }
  928. },
  929. },
  930. // 错误请求次数
  931. errorCount: 0,
  932. answerPopup: false,
  933. sortLink:"",
  934. // 课程是否过期
  935. isExpire: false,
  936. menuButtonLeft: 281,
  937. menuButtonH: 45,
  938. timer: null,
  939. flag: false,
  940. msg:'',
  941. poster:'',
  942. focus: false,
  943. openDanmuType: 0,
  944. danmuboxHeight: 0,
  945. user: {},
  946. crtShow: true,
  947. courseLogo: '',
  948. isfull: false,
  949. isFull: false,
  950. navList:[{
  951. id: 0,
  952. name: '介绍'
  953. },{
  954. id: 1,
  955. name: '答题'
  956. },{
  957. id: 2,
  958. name: '评论'
  959. }],
  960. currentTab: 1,
  961. triggered: false,
  962. // 没有更多
  963. isMore: false,
  964. isCart: false, // 购物车弹窗
  965. inputText:"",
  966. // 获取最多历史评论条数
  967. viewCommentNum: 200,
  968. // 1-开启评论;2-开启弹幕;3-关闭
  969. openCommentStatus: 3,
  970. showAnswerTip: false,
  971. showDanmu: 1,
  972. activeDanmus:[],
  973. flagTime:0,
  974. showProgress: true,
  975. imgPath:this.$store.state.imgpath,
  976. videoitem:null,
  977. showTreatment: 1, // 1不展示,0展示疗法
  978. treatmentPackage: [],
  979. // 商品上下架/商品卡片弹窗状态
  980. displayProductList: [], // 根据上架/下架时间过滤后的展示列表(用于购物车弹窗)
  981. cardPopup: false, // 商品卡片弹窗
  982. currentCardItem: null,
  983. productHotCountMap: {},
  984. productHotTimer: null,
  985. dismissedCardKey: '',
  986. // 跑马灯数据就绪门禁:避免首次进入时使用旧状态导致闪现
  987. marqueeDataReady: false,
  988. // 虚拟下单跑马灯
  989. fakeOrderPool: [],
  990. marqueeTriplePayloads: [],
  991. marqueeTripleIncomingPayloads: [],
  992. marqueeTripleSwitching: false,
  993. marqueeCurrentText: '',
  994. marqueeSelfHighlight: false,
  995. marqueeDisplayPayload: null,
  996. marqueeDisplaySelf: false,
  997. marqueeIncomingPayload: null,
  998. marqueeIncomingSelf: false,
  999. marqueeSwitching: false,
  1000. pendingSelfMarqueeFirst: false,
  1001. _fakeMarqueeStarted: false,
  1002. _fakeMarqueeEligiblePrev: undefined,
  1003. _marqueeStartDebounceTimer: null,
  1004. _fakeMarqueeTimer: null,
  1005. _marqueeSwitchTimer: null,
  1006. _marqueeTripleSwitchTimer: null,
  1007. _selfMarqueeClockTimer: null,
  1008. // 完课积分倒计时状态
  1009. remainTime: 0, // 倒计时(秒)
  1010. totalRemainTime: 0, // 总倒计时时长(秒,用于计算百分比)
  1011. countdownTimer: null, // 倒计时定时器
  1012. remainTimeReady: false, // 是否已从接口拉取到倒计时数据
  1013. hasReportedAfterCountdown: false, // 倒计时结束后是否已上报一次
  1014. // 是否公开课
  1015. isOpen: 0,
  1016. showBtn: 0,
  1017. isquestion: false,
  1018. fullscreenToggleLock: false,
  1019. fullscreenToggleTimer: null,
  1020. qwExternalId:'',
  1021. videoPlaying: false,
  1022. tabIndex: 0,
  1023. questionBankList: [],
  1024. defaultPageInfoList: [],
  1025. featuredCommentList: [],
  1026. featuredCommentInput: '',
  1027. featuredCommentLoading: false,
  1028. featuredCommentLoadingMore: false,
  1029. featuredCommentPageNum: 1,
  1030. featuredCommentPageSize: 10,
  1031. featuredCommentHasMore: true,
  1032. featuredCommentUserTotal: NaN,
  1033. featuredCommentDefaultTotal: NaN,
  1034. featuredCommentSending: false,
  1035. featuredCommentMedia: null,
  1036. videovipSuppressOnShowCourseFetchOnce: false,
  1037. _shouldResumeCourseVideoAfterFeaturedUpload: false,
  1038. _courseWasPlayingBeforeFeaturedCommentOpen: false,
  1039. featuredMediaActionSheetShow: false,
  1040. featuredCommentMediaPreviewShow: false,
  1041. featuredCommentMediaPreviewMode: 'image',
  1042. featuredCommentMediaPreviewUrls: [],
  1043. featuredCommentMediaPreviewIndex: 0,
  1044. featuredCommentMediaPreviewVideoUrl: '',
  1045. featuredCommentMediaPreviewPoster: '',
  1046. featuredCommentScrollIntoView: '',
  1047. featuredCommentComposerActive: false,
  1048. featuredCommentInputFocused: false,
  1049. featuredCommentKeyboardBottomPx: 0,
  1050. }
  1051. },
  1052. //发送给朋友
  1053. onShareAppMessage(res) {
  1054. return {
  1055. title: this.$store.state.logoname,
  1056. path: '/pages/about/index',
  1057. imageUrl: this.$store.state.imgpath+'/app/image/logoshare.png' //分享图标,路径可以是本地文件路径、代码包文件路径或者网络图片路径.支持PNG及JPG。显示图片长宽比是 5:4
  1058. }
  1059. },
  1060. //分享到朋友圈
  1061. onShareTimeline(res) {
  1062. return {
  1063. title: this.$store.state.logoname,
  1064. path: '/pages/about/index',
  1065. imageUrl: this.$store.state.imgpath+'/app/image/logoshare.png' //分享图标,路径可以是本地文件路径、代码包文件路径或者网络图片路径.支持PNG及JPG。显示图片长宽比是 5:4
  1066. }
  1067. },
  1068. computed:{
  1069. appid() {
  1070. return this.$store.state.appid
  1071. },
  1072. isSpare() {
  1073. return this.$store.state.isSpare
  1074. },
  1075. // 完课积分倒计时展示:格式化后的时间
  1076. formattedCountdown() {
  1077. return this.formatCountdown(this.remainTime);
  1078. },
  1079. // 完课积分倒计时展示:进度百分比
  1080. countdownPercentage() {
  1081. return this.getCountdownPercentage();
  1082. },
  1083. fakeMarqueeEligible() {
  1084. if (!this.marqueeDataReady) return false
  1085. if (this.showTreatment !== 0) return false
  1086. return Array.isArray(this.displayProductList) && this.displayProductList.length > 0
  1087. },
  1088. // 竖屏课打开小黄车 goodsList 时藏住视频内虚拟下单条;精选留言大图/视频预览由模板 !featuredCommentMediaPreviewShow 与 data 直连(同层 video 内 cover-view 更可靠)
  1089. showFakeMarqueeOnVideoCover() {
  1090. if (!this.fakeMarqueeEligible) return false
  1091. if (this.isShu && this.isCart) return false
  1092. return true
  1093. },
  1094. showTabReviewPoints() {
  1095. return Array.isArray(this.questionBankList) && this.questionBankList.length > 0
  1096. },
  1097. showTabColumnIntro() {
  1098. const img = this.courseInfo && this.courseInfo.courseIntroImg
  1099. return typeof img === 'string' && img.trim() !== ''
  1100. },
  1101. showTabFeaturedComments() {
  1102. return Array.isArray(this.defaultPageInfoList) && this.defaultPageInfoList.length > 0
  1103. },
  1104. featuredCommentDisplayList() {
  1105. const def = Array.isArray(this.defaultPageInfoList) ? this.defaultPageInfoList : []
  1106. if (!def.length) return []
  1107. const user = Array.isArray(this.featuredCommentList) ? this.featuredCommentList : []
  1108. return this._mergeFeaturedUserComments(user.slice(), def)
  1109. },
  1110. showFeaturedCommentNoMoreFooter() {
  1111. if (this.featuredCommentLoading || this.featuredCommentLoadingMore) return false
  1112. const list = this.featuredCommentDisplayList
  1113. if (!Array.isArray(list) || !list.length) return false
  1114. return !this.featuredCommentHasMore
  1115. },
  1116. visibleRatingTabCount() {
  1117. return this.ratingTabList.length
  1118. },
  1119. /** 评价区 Tab 展示顺序:精选留言 → 栏目介绍 → 评价得积分(仅渲染有数据的项) */
  1120. ratingTabList() {
  1121. const tabs = []
  1122. if (this.showTabFeaturedComments) {
  1123. tabs.push({
  1124. key: 'featured',
  1125. index: 0,
  1126. label: '精选留言',
  1127. icon: 'https://bjzmky-1323137866.cos.ap-chongqing.myqcloud.com/video/pl.png',
  1128. iconActive: 'https://bjzmky-1323137866.cos.ap-chongqing.myqcloud.com/video/pl-sel.png'
  1129. })
  1130. }
  1131. if (this.showTabColumnIntro) {
  1132. tabs.push({
  1133. key: 'column',
  1134. index: 1,
  1135. label: '栏目介绍',
  1136. icon: 'https://bjzmky-1323137866.cos.ap-chongqing.myqcloud.com/video/des.png',
  1137. iconActive: 'https://bjzmky-1323137866.cos.ap-chongqing.myqcloud.com/video/des-sel.png'
  1138. })
  1139. }
  1140. if (this.showTabReviewPoints) {
  1141. tabs.push({
  1142. key: 'review',
  1143. index: 2,
  1144. label: '评价得积分',
  1145. icon: 'https://bjzmky-1323137866.cos.ap-chongqing.myqcloud.com/video/sel.png',
  1146. iconActive: 'https://bjzmky-1323137866.cos.ap-chongqing.myqcloud.com/video/zan-sel.png'
  1147. })
  1148. }
  1149. return tabs
  1150. },
  1151. hasVisibleRatingTab() {
  1152. return this.visibleRatingTabCount > 0
  1153. },
  1154. isTripleMarqueeMode() {
  1155. return !!(this.isShu || this.isFull)
  1156. },
  1157. ...mapGetters(['coureLogin']),
  1158. },
  1159. watch: {
  1160. coureLogin: {
  1161. immediate: true, // 页面一进入就检查一次
  1162. handler(val) {
  1163. if (val == 2&&this.isLogin) {
  1164. console.log("看课AppToken失效,请重新登录")
  1165. this.isLogin = false
  1166. this.isAddKf = 0
  1167. this.goLogin()
  1168. }
  1169. }
  1170. },
  1171. isTripleMarqueeMode(now, was) {
  1172. if (!now || was) return
  1173. if (!this.fakeMarqueeEligible || !this._fakeMarqueeStarted) return
  1174. this.syncTripleMarqueeImmediate()
  1175. },
  1176. /** 精选留言 Tab 出现后默认高亮 */
  1177. showTabFeaturedComments(now, was) {
  1178. if (now && was !== true) {
  1179. this._selectDefaultRatingTab()
  1180. }
  1181. },
  1182. /**
  1183. * 评价 Tab 晚于栏目介绍返回时,_syncRatingTabIndex 可能暂落在 tab=1;
  1184. * 若有精选留言则仍默认高亮 tab=0,否则仅校正到当前第一个可见 Tab。
  1185. */
  1186. showTabReviewPoints(now, was) {
  1187. if (now && was !== true) {
  1188. this._selectDefaultRatingTab()
  1189. }
  1190. },
  1191. /** Tab 数量增加且含精选留言时,默认高亮精选留言 */
  1192. visibleRatingTabCount(n, o) {
  1193. if (n <= 0 || !this.showTabFeaturedComments) return
  1194. if (o != null && n <= o) return
  1195. this._selectDefaultRatingTab()
  1196. },
  1197. /** 离开精选留言 Tab 时收起键盘输入层与选图弹层(与 videovip 行为一致) */
  1198. tabIndex(n) {
  1199. if (n !== 0) {
  1200. this._closeFeaturedCommentComposerUi()
  1201. this.closeFeaturedMediaActionSheet()
  1202. }
  1203. },
  1204. displayProductList: {
  1205. handler() {
  1206. this.syncProductHotCounts()
  1207. },
  1208. deep: true,
  1209. },
  1210. cardPopup() {
  1211. this.syncProductHotCounts()
  1212. },
  1213. currentCardItem() {
  1214. this.syncProductHotCounts()
  1215. },
  1216. },
  1217. onLoad(option) {
  1218. this.getWebviewUrl()
  1219. uni.$on('usercode', (data) => {
  1220. console.log('huoqu ', data)
  1221. if(this.isSpare == 1&&data) {
  1222. this.code=data.code
  1223. this.goLogin(data)
  1224. }
  1225. })
  1226. this.videoContext = uni.createVideoContext('video-content-box', this)
  1227. if(!option.course){
  1228. if(option.videoitem){
  1229. this.isOpen = 1
  1230. this.urlOption=JSON.parse(option.videoitem)
  1231. this.urlOption = {
  1232. ...this.urlOption,
  1233. isOpen: 1
  1234. }
  1235. }else{
  1236. const keys = decodeURIComponent(Object.keys(option)[0]);
  1237. this.urlOption = JSON.parse(keys.split('course=')[1])
  1238. }
  1239. }else{
  1240. this.isOpen = 0
  1241. this.urlOption = option.course ? JSON.parse(decodeURIComponent(option.course)) : {}
  1242. }
  1243. this.videoId = this.urlOption.videoId
  1244. this.qwUserId = this.urlOption.qwUserId || ''
  1245. this.qwExternalId = this.urlOption.qwExternalId || ''
  1246. this.corpId = this.urlOption.corpId || ''
  1247. this.videolinkType = this.urlOption.linkType || 0
  1248. this.urlOption.appId=wx.getAccountInfoSync().miniProgram.appId
  1249. var that=this;
  1250. if (this.videoId) {
  1251. this.getH5CourseByVideo()
  1252. }
  1253. this.sortLink = this.urlOption.link || ''
  1254. this.getMenuButton()
  1255. // #ifndef H5
  1256. uni.onKeyboardHeightChange(this.keyboardHeightChange);
  1257. // #endif
  1258. },
  1259. onShow() {
  1260. this.refreshVipPurchaseMarqueeFlag()
  1261. if (this._videoPageHadShownOnce) {
  1262. this._touchAntiSeekSuppress(2000)
  1263. } else {
  1264. this._videoPageHadShownOnce = true
  1265. }
  1266. this.tipsOpen = false
  1267. this.isExpand = true
  1268. // this.isLogin = this.$isLoginCourseAuto()
  1269. this.uuId = generateRandomString(16)
  1270. // 精选留言从相册/相机返回:勿停虚拟跑马灯、勿重置门禁(与 onHide 抑制停表共用标志)
  1271. if (this.videovipSuppressOnShowCourseFetchOnce) {
  1272. this.videovipSuppressOnShowCourseFetchOnce = false
  1273. return
  1274. }
  1275. this.stopFakeMarqueeLoop()
  1276. this.marqueeDataReady = false
  1277. this._fakeMarqueeEligiblePrev = undefined
  1278. if(uni.getStorageSync('auto_userInfo') && JSON.stringify(uni.getStorageSync('auto_userInfo'))!='{}') {
  1279. this.user = JSON.parse(uni.getStorageSync('auto_userInfo'))
  1280. } else {
  1281. this.user = {}
  1282. }
  1283. if((this.sortLink||this.isOpen==1)&&!this.code){
  1284. this.getLink()
  1285. } else {
  1286. if(this.code) return;
  1287. uni.showToast({
  1288. title: '链接地址有误',
  1289. icon: 'none'
  1290. });
  1291. }
  1292. },
  1293. mounted() {
  1294. //this.getIP()
  1295. this.getHeight()
  1296. },
  1297. onHide() {
  1298. this.closeFeaturedCommentMediaPreview()
  1299. this._closeFeaturedCommentComposerUi()
  1300. // this.player = uni.createVideoContext('video-content-box');
  1301. if (this.player) {
  1302. this.player.pause()
  1303. }
  1304. // 精选留言调起相册/相机时也会触发 onHide,勿停虚拟跑马灯(与 onShow 抑制拉课共用同一标志)
  1305. if (!this.videovipSuppressOnShowCourseFetchOnce) {
  1306. this.stopFakeMarqueeLoop()
  1307. }
  1308. this.stopCountdown()
  1309. this.stopProductHotTimer()
  1310. // if (this.interval != null) {
  1311. // clearInterval(this.interval)
  1312. // this.interval = null
  1313. // }
  1314. },
  1315. onUnload() {
  1316. this.closeFeaturedCommentMediaPreview()
  1317. this._closeFeaturedCommentComposerUi()
  1318. this.closeFeaturedMediaActionSheet()
  1319. this._videoPageHadShownOnce = false
  1320. uni.$off('usercode')
  1321. if (this.interval != null) {
  1322. clearInterval(this.interval)
  1323. this.interval = null
  1324. }
  1325. if (this.fullscreenToggleTimer) {
  1326. clearTimeout(this.fullscreenToggleTimer)
  1327. this.fullscreenToggleTimer = null
  1328. }
  1329. if (this._rateBounceTimer) {
  1330. clearTimeout(this._rateBounceTimer)
  1331. this._rateBounceTimer = null
  1332. }
  1333. this.fullscreenToggleLock = false
  1334. this.stopCountdown()
  1335. this.stopProductHotTimer()
  1336. this.clearIntegral()
  1337. this.stopFakeMarqueeLoop()
  1338. this.fakeOrderPool = []
  1339. // #ifndef H5
  1340. uni.offKeyboardHeightChange(this.keyboardHeightChange);
  1341. // #endif
  1342. },
  1343. beforeDestroy() {
  1344. this.closeFeaturedCommentMediaPreview()
  1345. this._closeFeaturedCommentComposerUi()
  1346. this.closeFeaturedMediaActionSheet()
  1347. uni.$off('usercode')
  1348. this.player = uni.createVideoContext('video-content-box');
  1349. if (this.player) {
  1350. this.player.stop()
  1351. this.player = null
  1352. }
  1353. if (this.interval != null) {
  1354. clearInterval(this.interval)
  1355. this.interval = null
  1356. }
  1357. if (this.fullscreenToggleTimer) {
  1358. clearTimeout(this.fullscreenToggleTimer)
  1359. this.fullscreenToggleTimer = null
  1360. }
  1361. if (this._rateBounceTimer) {
  1362. clearTimeout(this._rateBounceTimer)
  1363. this._rateBounceTimer = null
  1364. }
  1365. this.fullscreenToggleLock = false
  1366. this.stopCountdown()
  1367. this.stopProductHotTimer()
  1368. this.clearIntegral()
  1369. this.stopFakeMarqueeLoop()
  1370. // #ifndef H5
  1371. uni.offKeyboardHeightChange(this.keyboardHeightChange);
  1372. // #endif
  1373. },
  1374. methods: {
  1375. _touchAntiSeekSuppress(ms = 2000) {
  1376. const until = Date.now() + ms
  1377. if (until > (this._antiSeekSuppressExpireAt || 0)) this._antiSeekSuppressExpireAt = until
  1378. },
  1379. _normalizePurchaseTs(ts) {
  1380. if (ts == null || ts === '') return 0
  1381. const n = typeof ts === 'number' ? ts : Number(ts)
  1382. return Number.isFinite(n) && n > 0 ? n : 0
  1383. },
  1384. _readVipSelfPurchaseTsMs() {
  1385. const row = uni.getStorageSync('videovip_myPurchase_' + this.videoId)
  1386. return this._normalizePurchaseTs(row && row.ts)
  1387. },
  1388. refreshVipPurchaseMarqueeFlag() {
  1389. const key = 'videovip_myPurchase_' + this.videoId
  1390. const consumedKey = 'videovip_myPurchaseMarqueeConsumed_' + this.videoId
  1391. const row = uni.getStorageSync(key)
  1392. const consumed = uni.getStorageSync(consumedKey)
  1393. const tsMs = this._normalizePurchaseTs(row && row.ts)
  1394. if (tsMs && Date.now() - tsMs <= 10 * 60 * 1000) {
  1395. const consumedTs = this._normalizePurchaseTs(consumed && consumed.ts)
  1396. this.pendingSelfMarqueeFirst = !(consumedTs > 0 && consumedTs === tsMs)
  1397. } else {
  1398. this.pendingSelfMarqueeFirst = false
  1399. if (row && row.ts != null && row.ts !== '') {
  1400. uni.removeStorageSync(key)
  1401. const consumedTs = this._normalizePurchaseTs(consumed && consumed.ts)
  1402. if (consumedTs > 0 && consumedTs === tsMs) uni.removeStorageSync(consumedKey)
  1403. }
  1404. }
  1405. },
  1406. _markSelfPurchaseMarqueeConsumed() {
  1407. const tsMs = this._readVipSelfPurchaseTsMs()
  1408. if (!tsMs) return
  1409. try {
  1410. uni.setStorageSync('videovip_myPurchaseMarqueeConsumed_' + this.videoId, { ts: tsMs })
  1411. } catch (e) {}
  1412. },
  1413. formatSelfPurchaseMarqueePayload() {
  1414. const tsMs = this._readVipSelfPurchaseTsMs()
  1415. if (!tsMs) {
  1416. return {
  1417. type: 'self',
  1418. nickname: '您本人',
  1419. maskedId: '',
  1420. timeOffset: '刚刚',
  1421. suffix: '已下单',
  1422. fullText: '您本人刚刚已下单'
  1423. }
  1424. }
  1425. const secTotal = Math.floor((Date.now() - tsMs) / 1000)
  1426. if (secTotal < 60) {
  1427. const text = `${Math.max(1, secTotal)}秒前`
  1428. return {
  1429. type: 'self',
  1430. nickname: '您本人',
  1431. maskedId: '',
  1432. timeOffset: text,
  1433. suffix: '已下单',
  1434. fullText: `您本人${text}已下单`
  1435. }
  1436. }
  1437. const min = Math.min(10, Math.floor(secTotal / 60))
  1438. const text = `${Math.max(1, min)}分钟前`
  1439. return {
  1440. type: 'self',
  1441. nickname: '您本人',
  1442. maskedId: '',
  1443. timeOffset: text,
  1444. suffix: '已下单',
  1445. fullText: `您本人${text}已下单`
  1446. }
  1447. },
  1448. _syncFakeMarqueeIfEligibleChanged() {
  1449. const val = this.fakeMarqueeEligible
  1450. if (val === this._fakeMarqueeEligiblePrev) return
  1451. this._fakeMarqueeEligiblePrev = val
  1452. if (val) {
  1453. if (this._marqueeStartDebounceTimer) {
  1454. clearTimeout(this._marqueeStartDebounceTimer)
  1455. this._marqueeStartDebounceTimer = null
  1456. }
  1457. this._marqueeStartDebounceTimer = setTimeout(() => {
  1458. this._marqueeStartDebounceTimer = null
  1459. if (this.fakeMarqueeEligible && !this._fakeMarqueeStarted) {
  1460. this.ensureFakeMarqueeStarted()
  1461. }
  1462. }, 50)
  1463. } else {
  1464. if (this._marqueeStartDebounceTimer) {
  1465. clearTimeout(this._marqueeStartDebounceTimer)
  1466. this._marqueeStartDebounceTimer = null
  1467. }
  1468. this.stopFakeMarqueeLoop()
  1469. }
  1470. },
  1471. ensureFakeMarqueeStarted() {
  1472. if (!this.fakeMarqueeEligible || this._fakeMarqueeStarted) return
  1473. this._fakeMarqueeStarted = true
  1474. if (!this.fakeOrderPool.length) {
  1475. this.fakeOrderPool = buildFakeOrderPool()
  1476. }
  1477. this.applyFakeMarqueeTick()
  1478. this.scheduleFakeMarqueeNext()
  1479. this._startSelfMarqueeClockIfNeeded()
  1480. },
  1481. applyFakeMarqueeTick() {
  1482. if (this.isTripleMarqueeMode) {
  1483. const rows = this.buildTripleMarqueePayloads()
  1484. this.setTripleMarqueeDisplayWithAnim(rows)
  1485. if (rows.some(item => item && item.type === 'self')) {
  1486. this.pendingSelfMarqueeFirst = false
  1487. this._markSelfPurchaseMarqueeConsumed()
  1488. }
  1489. if (rows.length) {
  1490. this.marqueeDisplayPayload = rows[0]
  1491. this.marqueeDisplaySelf = this.marqueeDisplayPayload.type === 'self'
  1492. }
  1493. return
  1494. }
  1495. this.marqueeTriplePayloads = []
  1496. this.marqueeTripleIncomingPayloads = []
  1497. this.marqueeTripleSwitching = false
  1498. const tsMs = this._readVipSelfPurchaseTsMs()
  1499. const purchaseOk = !!(tsMs && Date.now() - tsMs <= 10 * 60 * 1000)
  1500. if (purchaseOk && this.pendingSelfMarqueeFirst) {
  1501. const selfPayload = this.formatSelfPurchaseMarqueePayload()
  1502. this.marqueeCurrentText = selfPayload.fullText
  1503. this.marqueeSelfHighlight = true
  1504. this.pendingSelfMarqueeFirst = false
  1505. this._markSelfPurchaseMarqueeConsumed()
  1506. this.setMarqueeDisplayWithAnim(selfPayload, true)
  1507. return
  1508. }
  1509. this.marqueeSelfHighlight = false
  1510. if (!this.fakeOrderPool.length) {
  1511. this.fakeOrderPool = buildFakeOrderPool()
  1512. }
  1513. const line = this.fakeOrderPool.shift()
  1514. if (line) {
  1515. this.fakeOrderPool.push(line)
  1516. this.marqueeCurrentText = `${line.nickname}${line.maskedId}${line.timeOffset}${line.suffix}`
  1517. this.setMarqueeDisplayWithAnim({
  1518. type: 'virtual',
  1519. nickname: line.nickname,
  1520. maskedId: line.maskedId,
  1521. timeOffset: line.timeOffset,
  1522. suffix: line.suffix
  1523. }, false)
  1524. }
  1525. },
  1526. buildTripleMarqueePayloads() {
  1527. const rows = []
  1528. const hasSelf = this.hasRecentSelfPurchase() && this.pendingSelfMarqueeFirst
  1529. if (!this.fakeOrderPool.length) {
  1530. this.fakeOrderPool = buildFakeOrderPool()
  1531. }
  1532. let guard = 0
  1533. if (hasSelf) {
  1534. rows.push(this.formatSelfPurchaseMarqueePayload())
  1535. }
  1536. const targetVirtualCount = hasSelf ? 2 : 3
  1537. let virtualCount = 0
  1538. while (virtualCount < targetVirtualCount && this.fakeOrderPool.length && guard < 10) {
  1539. const line = this.fakeOrderPool.shift()
  1540. guard += 1
  1541. if (!line) break
  1542. this.fakeOrderPool.push(line)
  1543. rows.push({
  1544. type: 'virtual',
  1545. nickname: line.nickname,
  1546. maskedId: line.maskedId,
  1547. timeOffset: line.timeOffset,
  1548. suffix: line.suffix
  1549. })
  1550. virtualCount += 1
  1551. }
  1552. return rows.slice(0, 3)
  1553. },
  1554. hasRecentSelfPurchase() {
  1555. const tsMs = this._readVipSelfPurchaseTsMs()
  1556. return !!(tsMs && Date.now() - tsMs <= 10 * 60 * 1000)
  1557. },
  1558. refreshSelfPurchaseMarqueeDisplayedTiming() {
  1559. const fresh = this.formatSelfPurchaseMarqueePayload()
  1560. if (this.isTripleMarqueeMode && this.marqueeTriplePayloads.length) {
  1561. const upd = (arr) =>
  1562. arr.map((r) => (r && r.type === 'self' ? Object.assign({}, fresh) : r))
  1563. this.marqueeTriplePayloads = upd(this.marqueeTriplePayloads)
  1564. if (this.marqueeTripleIncomingPayloads.length) {
  1565. this.marqueeTripleIncomingPayloads = upd(this.marqueeTripleIncomingPayloads)
  1566. }
  1567. }
  1568. if (this.marqueeDisplayPayload && this.marqueeDisplayPayload.type === 'self') {
  1569. this.marqueeDisplayPayload = Object.assign({}, fresh)
  1570. }
  1571. if (this.marqueeIncomingPayload && this.marqueeIncomingPayload.type === 'self') {
  1572. this.marqueeIncomingPayload = Object.assign({}, fresh)
  1573. }
  1574. },
  1575. _startSelfMarqueeClockIfNeeded() {
  1576. if (this._selfMarqueeClockTimer != null) return
  1577. if (!this.hasRecentSelfPurchase()) return
  1578. this._selfMarqueeClockTimer = setInterval(() => {
  1579. if (!this._fakeMarqueeStarted || !this.hasRecentSelfPurchase()) {
  1580. this._stopSelfMarqueeClock()
  1581. return
  1582. }
  1583. this.refreshSelfPurchaseMarqueeDisplayedTiming()
  1584. }, 10000)
  1585. },
  1586. _stopSelfMarqueeClock() {
  1587. if (this._selfMarqueeClockTimer != null) {
  1588. clearInterval(this._selfMarqueeClockTimer)
  1589. this._selfMarqueeClockTimer = null
  1590. }
  1591. },
  1592. syncTripleMarqueeImmediate() {
  1593. if (!this.fakeMarqueeEligible || !this._fakeMarqueeStarted || !this.isTripleMarqueeMode) return
  1594. const rows = this.buildTripleMarqueePayloads()
  1595. if (!rows.length) return
  1596. this.setTripleMarqueeDisplayWithAnim(rows)
  1597. if (rows.some(item => item && item.type === 'self')) {
  1598. this.pendingSelfMarqueeFirst = false
  1599. this._markSelfPurchaseMarqueeConsumed()
  1600. }
  1601. if (rows[0]) {
  1602. this.marqueeDisplayPayload = rows[0]
  1603. this.marqueeDisplaySelf = rows[0].type === 'self'
  1604. }
  1605. },
  1606. setTripleMarqueeDisplayWithAnim(rows) {
  1607. if (!rows || !rows.length) return
  1608. if (!this.marqueeTriplePayloads.length) {
  1609. this.marqueeTriplePayloads = rows
  1610. this.marqueeTripleIncomingPayloads = []
  1611. this.marqueeTripleSwitching = false
  1612. return
  1613. }
  1614. if (this._marqueeTripleSwitchTimer) {
  1615. clearTimeout(this._marqueeTripleSwitchTimer)
  1616. this._marqueeTripleSwitchTimer = null
  1617. }
  1618. this.marqueeTripleIncomingPayloads = rows
  1619. this.marqueeTripleSwitching = true
  1620. this._marqueeTripleSwitchTimer = setTimeout(() => {
  1621. this.marqueeTriplePayloads = this.marqueeTripleIncomingPayloads
  1622. this.marqueeTripleIncomingPayloads = []
  1623. this.marqueeTripleSwitching = false
  1624. this._marqueeTripleSwitchTimer = null
  1625. }, 460)
  1626. },
  1627. setMarqueeDisplayWithAnim(payload, isSelf) {
  1628. if (!payload) return
  1629. if (!this.marqueeDisplayPayload) {
  1630. this.marqueeDisplayPayload = payload
  1631. this.marqueeDisplaySelf = !!isSelf
  1632. this.marqueeSwitching = false
  1633. this.marqueeIncomingPayload = null
  1634. this.marqueeIncomingSelf = false
  1635. return
  1636. }
  1637. if (this._marqueeSwitchTimer) {
  1638. clearTimeout(this._marqueeSwitchTimer)
  1639. this._marqueeSwitchTimer = null
  1640. }
  1641. this.marqueeIncomingPayload = payload
  1642. this.marqueeIncomingSelf = !!isSelf
  1643. this.marqueeSwitching = true
  1644. this._marqueeSwitchTimer = setTimeout(() => {
  1645. this.marqueeDisplayPayload = this.marqueeIncomingPayload
  1646. this.marqueeDisplaySelf = this.marqueeIncomingSelf
  1647. this.marqueeIncomingPayload = null
  1648. this.marqueeIncomingSelf = false
  1649. this.marqueeSwitching = false
  1650. this._marqueeSwitchTimer = null
  1651. }, 460)
  1652. },
  1653. scheduleFakeMarqueeNext() {
  1654. if (this._fakeMarqueeTimer) {
  1655. clearTimeout(this._fakeMarqueeTimer)
  1656. this._fakeMarqueeTimer = null
  1657. }
  1658. const delay = 4000 + Math.floor(Math.random() * 3001)
  1659. this._fakeMarqueeTimer = setTimeout(() => {
  1660. this._fakeMarqueeTimer = null
  1661. if (!this.fakeMarqueeEligible || !this._fakeMarqueeStarted) return
  1662. this.applyFakeMarqueeTick()
  1663. this.scheduleFakeMarqueeNext()
  1664. }, delay)
  1665. },
  1666. stopFakeMarqueeLoop() {
  1667. if (this._marqueeStartDebounceTimer) {
  1668. clearTimeout(this._marqueeStartDebounceTimer)
  1669. this._marqueeStartDebounceTimer = null
  1670. }
  1671. if (this._fakeMarqueeTimer) {
  1672. clearTimeout(this._fakeMarqueeTimer)
  1673. this._fakeMarqueeTimer = null
  1674. }
  1675. if (this._marqueeSwitchTimer) {
  1676. clearTimeout(this._marqueeSwitchTimer)
  1677. this._marqueeSwitchTimer = null
  1678. }
  1679. if (this._marqueeTripleSwitchTimer) {
  1680. clearTimeout(this._marqueeTripleSwitchTimer)
  1681. this._marqueeTripleSwitchTimer = null
  1682. }
  1683. this._fakeMarqueeStarted = false
  1684. this.marqueeSwitching = false
  1685. this.marqueeDisplayPayload = null
  1686. this.marqueeDisplaySelf = false
  1687. this.marqueeCurrentText = ''
  1688. this.marqueeSelfHighlight = false
  1689. this.marqueeIncomingPayload = null
  1690. this.marqueeIncomingSelf = false
  1691. this.marqueeTriplePayloads = []
  1692. this.marqueeTripleIncomingPayloads = []
  1693. this.marqueeTripleSwitching = false
  1694. this._stopSelfMarqueeClock()
  1695. },
  1696. closeCardPopup() {
  1697. if (this.currentCardItem) {
  1698. this.dismissedCardKey =
  1699. `${this.currentCardItem.productId || ''}_${this.currentCardItem.cardPopupTime || ''}_${this.currentCardItem.cardCloseTime || ''}`
  1700. }
  1701. this.cardPopup = false
  1702. },
  1703. hasHotSaleTag(item) {
  1704. return hasHotSaleTag(item)
  1705. },
  1706. getProductHotCount(item) {
  1707. if (!hasHotSaleTag(item)) return 0
  1708. const key = getProductHotKey(item)
  1709. return key ? clampHotSaleCount(this.productHotCountMap[key]) : 0
  1710. },
  1711. _collectHotSaleProducts() {
  1712. const seen = new Set()
  1713. const list = []
  1714. const add = (item) => {
  1715. if (!hasHotSaleTag(item)) return
  1716. const key = getProductHotKey(item)
  1717. if (!key || seen.has(key)) return
  1718. seen.add(key)
  1719. list.push(item)
  1720. }
  1721. ;(this.displayProductList || []).forEach(add)
  1722. if (this.cardPopup && this.currentCardItem) add(this.currentCardItem)
  1723. return list
  1724. },
  1725. _randomInt(min, max) {
  1726. return Math.floor(Math.random() * (max - min + 1)) + min
  1727. },
  1728. syncProductHotCounts() {
  1729. const products = this._collectHotSaleProducts()
  1730. const next = { ...this.productHotCountMap }
  1731. const activeKeys = new Set()
  1732. products.forEach((item) => {
  1733. const key = getProductHotKey(item)
  1734. if (!key) return
  1735. activeKeys.add(key)
  1736. if (!next[key]) {
  1737. next[key] = clampHotSaleCount(this._randomInt(100, 800))
  1738. }
  1739. })
  1740. Object.keys(next).forEach((key) => {
  1741. if (!activeKeys.has(key)) delete next[key]
  1742. })
  1743. this.productHotCountMap = next
  1744. if (activeKeys.size) {
  1745. this.startProductHotTimer()
  1746. } else {
  1747. this.stopProductHotTimer()
  1748. }
  1749. },
  1750. startProductHotTimer() {
  1751. if (this.productHotTimer) return
  1752. this.productHotTimer = setInterval(() => {
  1753. const products = this._collectHotSaleProducts()
  1754. if (!products.length) return
  1755. const next = { ...this.productHotCountMap }
  1756. products.forEach((item) => {
  1757. const key = getProductHotKey(item)
  1758. if (key && next[key]) {
  1759. next[key] = clampHotSaleCount(next[key] + this._randomInt(10, 20))
  1760. }
  1761. })
  1762. this.productHotCountMap = next
  1763. }, 30000)
  1764. },
  1765. stopProductHotTimer() {
  1766. if (this.productHotTimer) {
  1767. clearInterval(this.productHotTimer)
  1768. this.productHotTimer = null
  1769. }
  1770. },
  1771. openList(){
  1772. const wasShu = this.isShu
  1773. this.isShu=!this.isShu
  1774. if (wasShu && this.showTabFeaturedComments) this.tabIndex = 0
  1775. },
  1776. /** 有精选留言时默认 tabIndex=0,否则落到第一个可见 Tab */
  1777. _selectDefaultRatingTab() {
  1778. if (this.showTabFeaturedComments) {
  1779. this.tabIndex = 0
  1780. this.loadFeaturedComments()
  1781. return
  1782. }
  1783. this._syncRatingTabIndex()
  1784. },
  1785. _closeFeaturedCommentComposerUi() {
  1786. this.featuredCommentComposerActive = false
  1787. this.featuredCommentInputFocused = false
  1788. this.featuredCommentKeyboardBottomPx = 0
  1789. },
  1790. openFeaturedCommentComposer() {
  1791. this._expandLayoutIfPortraitFeaturedCommentsOnly()
  1792. this._courseWasPlayingBeforeFeaturedCommentOpen = this.videoPlaying && !this.isEnded
  1793. this.featuredCommentComposerActive = true
  1794. this.$nextTick(() => {
  1795. this.featuredCommentInputFocused = true
  1796. })
  1797. },
  1798. onFeaturedCommentComposerMaskTap() {
  1799. this._closeFeaturedCommentComposerUi()
  1800. },
  1801. onFeaturedCommentComposerInputFocus() {
  1802. this._expandLayoutIfPortraitFeaturedCommentsOnly()
  1803. },
  1804. onFeaturedCommentComposerInputBlur() {
  1805. this.featuredCommentInputFocused = false
  1806. },
  1807. _syncRatingTabIndex() {
  1808. const tabs = [
  1809. this.showTabFeaturedComments,
  1810. this.showTabColumnIntro,
  1811. this.showTabReviewPoints
  1812. ]
  1813. if (!tabs.some(Boolean)) return
  1814. if (tabs[this.tabIndex]) return
  1815. const idx = tabs.findIndex(Boolean)
  1816. if (idx < 0) return
  1817. this.tabIndex = idx
  1818. if (idx === 0) {
  1819. this.loadFeaturedComments()
  1820. }
  1821. },
  1822. selecTab(idx){
  1823. if (idx === 0 && !this.showTabFeaturedComments) return
  1824. if (idx === 1 && !this.showTabColumnIntro) return
  1825. if (idx === 2 && !this.showTabReviewPoints) return
  1826. this.tabIndex = idx
  1827. if (idx === 0) {
  1828. this.loadFeaturedComments()
  1829. }
  1830. },
  1831. _resolveUserInfoObj() {
  1832. let u = this.user
  1833. if (!u || !Object.keys(u).length) {
  1834. try {
  1835. const raw = uni.getStorageSync('auto_userInfo')
  1836. if (typeof raw === 'string') u = JSON.parse(raw) || {}
  1837. else if (raw && typeof raw === 'object') u = raw
  1838. else u = {}
  1839. } catch (e) {
  1840. u = {}
  1841. }
  1842. }
  1843. return u && typeof u === 'object' ? u : {}
  1844. },
  1845. _pickUserCommentListFromCommentListRes(res) {
  1846. if (!res || res.code != 200) return []
  1847. const d = res.data
  1848. if (d && Array.isArray(d.list)) return d.list.slice()
  1849. if (Array.isArray(d)) return d.slice()
  1850. if (Array.isArray(res.list)) return res.list.slice()
  1851. if (d && Array.isArray(d.records)) return d.records.slice()
  1852. return []
  1853. },
  1854. _pickDefaultPageInfoListFromCommentListRes(res) {
  1855. if (!res || res.code != 200) return []
  1856. const dpi = res.defaultPageInfo || (res.data && res.data.defaultPageInfo)
  1857. if (dpi && Array.isArray(dpi.list)) return dpi.list.slice()
  1858. return []
  1859. },
  1860. _pickFeaturedUserCommentTotalFromRes(res) {
  1861. if (!res || res.code != 200) return NaN
  1862. const d = res.data
  1863. const tryNum = (v) => {
  1864. const t = Number(v)
  1865. return Number.isFinite(t) && t >= 0 ? t : NaN
  1866. }
  1867. if (d && typeof d === 'object' && !Array.isArray(d)) {
  1868. if (typeof d.userTotal !== 'undefined') {
  1869. const t = tryNum(d.userTotal)
  1870. if (Number.isFinite(t)) return t
  1871. }
  1872. if (typeof d.commentTotal !== 'undefined') {
  1873. const t = tryNum(d.commentTotal)
  1874. if (Number.isFinite(t)) return t
  1875. }
  1876. if (typeof d.listTotal !== 'undefined') {
  1877. const t = tryNum(d.listTotal)
  1878. if (Number.isFinite(t)) return t
  1879. }
  1880. if (typeof d.total !== 'undefined') {
  1881. const t = tryNum(d.total)
  1882. if (Number.isFinite(t)) return t
  1883. }
  1884. }
  1885. if (typeof res.userTotal !== 'undefined') {
  1886. const t = tryNum(res.userTotal)
  1887. if (Number.isFinite(t)) return t
  1888. }
  1889. if (typeof res.total !== 'undefined') {
  1890. const t = tryNum(res.total)
  1891. if (Number.isFinite(t)) return t
  1892. }
  1893. return NaN
  1894. },
  1895. _pickFeaturedDefaultTotalFromRes(res) {
  1896. if (!res || res.code != 200) return NaN
  1897. const dpi = res.defaultPageInfo || (res.data && res.data.defaultPageInfo)
  1898. if (dpi && typeof dpi.total !== 'undefined') {
  1899. const t = Number(dpi.total)
  1900. return Number.isFinite(t) && t >= 0 ? t : NaN
  1901. }
  1902. return NaN
  1903. },
  1904. _syncFeaturedCommentTotalsFromRes(res) {
  1905. const u = this._pickFeaturedUserCommentTotalFromRes(res)
  1906. const dt = this._pickFeaturedDefaultTotalFromRes(res)
  1907. this.featuredCommentUserTotal = Number.isFinite(u) ? u : NaN
  1908. this.featuredCommentDefaultTotal = Number.isFinite(dt) ? dt : NaN
  1909. },
  1910. _mergeFeaturedUserComments(prev, incoming) {
  1911. const a = Array.isArray(prev) ? prev : []
  1912. const b = Array.isArray(incoming) ? incoming : []
  1913. const keyOf = (it) => {
  1914. if (!it) return ''
  1915. if (it.commentId != null && it.commentId !== '') return 'c:' + it.commentId
  1916. if (it.id != null && it.id !== '') return 'i:' + it.id
  1917. return ''
  1918. }
  1919. const seen = new Set()
  1920. const out = []
  1921. for (let i = 0; i < a.length; i++) {
  1922. const k = keyOf(a[i])
  1923. if (k) seen.add(k)
  1924. out.push(a[i])
  1925. }
  1926. for (let j = 0; j < b.length; j++) {
  1927. const it = b[j]
  1928. const k = keyOf(it)
  1929. if (k) {
  1930. if (seen.has(k)) continue
  1931. seen.add(k)
  1932. }
  1933. out.push(it)
  1934. }
  1935. return out
  1936. },
  1937. _applyFeaturedCommentHasMore(res, pageUserList, mergedUserList) {
  1938. const pageLen = Array.isArray(pageUserList) ? pageUserList.length : 0
  1939. const mergedLen = Array.isArray(mergedUserList) ? mergedUserList.length : 0
  1940. const userTotal = this._pickFeaturedUserCommentTotalFromRes(res)
  1941. if (Number.isFinite(userTotal)) {
  1942. this.featuredCommentHasMore = mergedLen < userTotal
  1943. } else {
  1944. this.featuredCommentHasMore = pageLen >= this.featuredCommentPageSize
  1945. }
  1946. if (pageLen === 0) this.featuredCommentHasMore = false
  1947. },
  1948. onFeaturedCommentScrollToLower() {
  1949. if (this.tabIndex !== 0 || !this.showTabFeaturedComments) return
  1950. this.loadFeaturedComments({ append: true })
  1951. },
  1952. loadFeaturedComments(opts) {
  1953. if (!this.videoId || !this.urlOption.courseId) {
  1954. return Promise.resolve()
  1955. }
  1956. const o = opts || {}
  1957. const scrollToBottom = !!o.scrollToBottom
  1958. const append = !!o.append
  1959. if (append) {
  1960. if (!this.featuredCommentHasMore) return Promise.resolve()
  1961. if (this.featuredCommentLoading || this.featuredCommentLoadingMore) return Promise.resolve()
  1962. this.featuredCommentLoadingMore = true
  1963. } else {
  1964. if (this.featuredCommentLoading) return Promise.resolve()
  1965. this.featuredCommentPageNum = 1
  1966. this.featuredCommentHasMore = true
  1967. this.featuredCommentLoading = true
  1968. }
  1969. const pageNum = this.featuredCommentPageNum
  1970. return getCommentList({
  1971. pageNum,
  1972. pageSize: this.featuredCommentPageSize,
  1973. courseId: this.urlOption.courseId,
  1974. videoId: this.videoId,
  1975. }).then(res => {
  1976. if (append) {
  1977. this.featuredCommentLoadingMore = false
  1978. } else {
  1979. this.featuredCommentLoading = false
  1980. }
  1981. if (res.code == 200) {
  1982. const pageUser = this._pickUserCommentListFromCommentListRes(res)
  1983. this._syncFeaturedCommentTotalsFromRes(res)
  1984. if (!append) {
  1985. this.featuredCommentList = pageUser
  1986. this.defaultPageInfoList = this._pickDefaultPageInfoListFromCommentListRes(res)
  1987. this._applyFeaturedCommentHasMore(res, pageUser, this.featuredCommentList)
  1988. if (pageUser.length > 0) this.featuredCommentPageNum = pageNum + 1
  1989. } else {
  1990. this.featuredCommentList = this._mergeFeaturedUserComments(
  1991. this.featuredCommentList,
  1992. pageUser
  1993. )
  1994. this._applyFeaturedCommentHasMore(res, pageUser, this.featuredCommentList)
  1995. if (pageUser.length > 0) this.featuredCommentPageNum = pageNum + 1
  1996. }
  1997. } else {
  1998. if (!append) {
  1999. this.defaultPageInfoList = []
  2000. this.featuredCommentList = []
  2001. this.featuredCommentHasMore = false
  2002. this.featuredCommentUserTotal = NaN
  2003. this.featuredCommentDefaultTotal = NaN
  2004. }
  2005. }
  2006. this._syncRatingTabIndex()
  2007. if (scrollToBottom) {
  2008. this.$nextTick(() => {
  2009. this._scrollFeaturedCommentsToBottom()
  2010. setTimeout(() => this._scrollFeaturedCommentsToBottom(), 280)
  2011. })
  2012. }
  2013. }).catch(() => {
  2014. if (append) {
  2015. this.featuredCommentLoadingMore = false
  2016. } else {
  2017. this.featuredCommentLoading = false
  2018. this.featuredCommentList = []
  2019. this.defaultPageInfoList = []
  2020. this.featuredCommentHasMore = false
  2021. this.featuredCommentUserTotal = NaN
  2022. this.featuredCommentDefaultTotal = NaN
  2023. }
  2024. this._syncRatingTabIndex()
  2025. })
  2026. },
  2027. _scrollFeaturedCommentsToBottom() {
  2028. this.featuredCommentScrollIntoView = ''
  2029. this.$nextTick(() => {
  2030. this.featuredCommentScrollIntoView = 'fc-scroll-anchor'
  2031. setTimeout(() => {
  2032. this.featuredCommentScrollIntoView = ''
  2033. }, 400)
  2034. })
  2035. },
  2036. getCommentAvatar(it) {
  2037. if (!it) return ''
  2038. return it.avatar || it.headImg || it.headimg || it.userAvatar || ''
  2039. },
  2040. getCommentDisplayName(it) {
  2041. if (!it) return '用户'
  2042. if (it.nickName) return it.nickName
  2043. if (it.userId != null && it.userId !== '') {
  2044. return it.toNickName || '用户'
  2045. }
  2046. return '用户'
  2047. },
  2048. _urlsEmbeddedInCommentContent(it) {
  2049. const c = it && it.content
  2050. if (c == null || typeof c !== 'string') return []
  2051. const text = String(c).replace(/\u21b5/g, '\n')
  2052. const m = text.match(/https?:\/\/[^\s]+/gi) || []
  2053. const cleaned = m.map(u => u.replace(/[),.;!?]+$/g, ''))
  2054. return [...new Set(cleaned)]
  2055. },
  2056. _isEmbeddedCommentVideoUrl(u) {
  2057. if (!u) return false
  2058. return /\/comment\/video\//i.test(u) || /\.(mp4|m3u8|mov|webm|avi|mkv)(\?|#|$)/i.test(u)
  2059. },
  2060. _isEmbeddedCommentImageUrl(u) {
  2061. if (!u) return false
  2062. return /\/comment\/image\//i.test(u) || /\.(jpg|jpeg|png|gif|webp|bmp)(\?|#|$)/i.test(u)
  2063. },
  2064. _videoUrlFromCommentContent(it) {
  2065. const urls = this._urlsEmbeddedInCommentContent(it)
  2066. for (let i = 0; i < urls.length; i++) {
  2067. if (this._isEmbeddedCommentVideoUrl(urls[i])) return urls[i]
  2068. }
  2069. return ''
  2070. },
  2071. getCommentTextOnly(it) {
  2072. if (!it || it.content == null) return ''
  2073. let text = String(it.content).replace(/\u21b5/g, '\n')
  2074. const strip = new Set()
  2075. const v = this.getCommentVideoUrl(it)
  2076. if (v) strip.add(v)
  2077. const urls = this._urlsEmbeddedInCommentContent(it)
  2078. for (let i = 0; i < urls.length; i++) {
  2079. const u = urls[i]
  2080. if (this._isEmbeddedCommentVideoUrl(u)) strip.add(u)
  2081. else if (this._isEmbeddedCommentImageUrl(u) && u !== v) strip.add(u)
  2082. }
  2083. strip.forEach(u => {
  2084. text = text.split(u).join('')
  2085. })
  2086. return text.replace(/(\r?\n\s*){2,}/g, '\n').replace(/^\s+|\s+$/g, '').trim()
  2087. },
  2088. getCommentVideoUrl(it) {
  2089. if (!it) return ''
  2090. if (it.videoUrl) return it.videoUrl
  2091. if (it.video) return it.video
  2092. if (it.mediaType === 'video' && it.mediaUrl) return it.mediaUrl
  2093. return this._videoUrlFromCommentContent(it)
  2094. },
  2095. getCommentVideoPoster(it) {
  2096. if (!it) return ''
  2097. const raw =
  2098. it.videoCover || it.coverUrl || it.poster || it.thumbnail || ''
  2099. return typeof raw === 'string' ? raw.trim() : String(raw || '').trim()
  2100. },
  2101. commentImageUrls(it) {
  2102. if (!it) return []
  2103. const raw = it.images || it.imageUrl || it.imgUrl || it.picUrl || it.pic
  2104. let fromFields = []
  2105. if (raw) {
  2106. if (Array.isArray(raw)) fromFields = raw.filter(Boolean)
  2107. else fromFields = String(raw).split(',').map(s => s.trim()).filter(Boolean)
  2108. }
  2109. const videoUrl = this.getCommentVideoUrl(it)
  2110. const fromContent = this._urlsEmbeddedInCommentContent(it).filter(u =>
  2111. this._isEmbeddedCommentImageUrl(u) && !this._isEmbeddedCommentVideoUrl(u) && u !== videoUrl
  2112. )
  2113. return [...new Set([...fromFields, ...fromContent])]
  2114. },
  2115. _pauseCourseVideo() {
  2116. try {
  2117. const ctx =
  2118. this.videoContext ||
  2119. uni.createVideoContext('video-content-box', this)
  2120. this.videoContext = ctx
  2121. if (ctx && ctx.pause) ctx.pause()
  2122. } catch (e) {}
  2123. try {
  2124. if (this.player && this.player.pause) this.player.pause()
  2125. } catch (e2) {}
  2126. },
  2127. _resumeCourseVideoAfterFeaturedJob() {
  2128. if (!this._shouldResumeCourseVideoAfterFeaturedUpload) return
  2129. this._shouldResumeCourseVideoAfterFeaturedUpload = false
  2130. this._courseWasPlayingBeforeFeaturedCommentOpen = false
  2131. if (this.isEnded || !this.videoUrl || !this.videoId) return
  2132. const ctx =
  2133. this.videoContext ||
  2134. uni.createVideoContext('video-content-box', this)
  2135. this.videoContext = ctx
  2136. const tryPlay = () => {
  2137. try {
  2138. if (ctx && ctx.play) ctx.play()
  2139. } catch (e) {}
  2140. }
  2141. this.$nextTick(() => {
  2142. tryPlay()
  2143. setTimeout(tryPlay, 80)
  2144. setTimeout(tryPlay, 260)
  2145. })
  2146. },
  2147. getFeaturedCommentRowKey(it, idx) {
  2148. const id =
  2149. it && it.commentId != null && it.commentId !== ''
  2150. ? String(it.commentId)
  2151. : it && it.id != null
  2152. ? String(it.id)
  2153. : ''
  2154. return 'fc-' + idx + '-' + (id || 'n')
  2155. },
  2156. getFeaturedCommentVideoId(it, idx) {
  2157. return 'feat-comment-video-' + this.getFeaturedCommentRowKey(it, idx)
  2158. },
  2159. openFeaturedCommentMediaPreview(it, listIdx, mode, imageIndex) {
  2160. const m = mode === 'video' ? 'video' : 'image'
  2161. if (m === 'image') {
  2162. const urls = this.commentImageUrls(it).filter(u => u && !/^blob:/i.test(u))
  2163. if (!urls.length) return
  2164. const i = Math.min(
  2165. Math.max(0, Number(imageIndex) || 0),
  2166. urls.length - 1
  2167. )
  2168. this._pauseCourseVideo()
  2169. try {
  2170. if (this.getCommentVideoUrl(it)) {
  2171. const id = this.getFeaturedCommentVideoId(it, listIdx)
  2172. const c = uni.createVideoContext(id, this)
  2173. if (c && c.pause) c.pause()
  2174. }
  2175. } catch (e) {}
  2176. this.featuredCommentMediaPreviewMode = 'image'
  2177. this.featuredCommentMediaPreviewUrls = urls
  2178. this.featuredCommentMediaPreviewIndex = i
  2179. this.featuredCommentMediaPreviewVideoUrl = ''
  2180. this.featuredCommentMediaPreviewPoster = ''
  2181. this.featuredCommentMediaPreviewShow = true
  2182. return
  2183. }
  2184. const vurl = this.getCommentVideoUrl(it)
  2185. if (!vurl) return
  2186. this._pauseCourseVideo()
  2187. try {
  2188. const id = this.getFeaturedCommentVideoId(it, listIdx)
  2189. const c = uni.createVideoContext(id, this)
  2190. if (c && c.pause) c.pause()
  2191. } catch (e) {}
  2192. this.featuredCommentMediaPreviewMode = 'video'
  2193. this.featuredCommentMediaPreviewUrls = []
  2194. this.featuredCommentMediaPreviewIndex = 0
  2195. this.featuredCommentMediaPreviewVideoUrl = vurl
  2196. this.featuredCommentMediaPreviewPoster = this.getCommentVideoPoster(it) || ''
  2197. this.featuredCommentMediaPreviewShow = true
  2198. this._tryPlayFeaturedModalVideo()
  2199. },
  2200. _tryPlayFeaturedModalVideo() {
  2201. if (this.featuredCommentMediaPreviewMode !== 'video' || !this.featuredCommentMediaPreviewVideoUrl) {
  2202. return
  2203. }
  2204. const run = () => {
  2205. try {
  2206. const c = uni.createVideoContext('fc-media-preview-video', this)
  2207. if (c && c.play) c.play()
  2208. } catch (e) {}
  2209. }
  2210. this.$nextTick(() => {
  2211. run()
  2212. setTimeout(run, 100)
  2213. setTimeout(run, 280)
  2214. })
  2215. },
  2216. featuredCommentPreviewCardTap() {},
  2217. closeFeaturedCommentMediaPreview() {
  2218. if (!this.featuredCommentMediaPreviewShow) return
  2219. try {
  2220. const c = uni.createVideoContext('fc-media-preview-video', this)
  2221. if (c && c.pause) c.pause()
  2222. } catch (e) {}
  2223. this.featuredCommentMediaPreviewShow = false
  2224. this.featuredCommentMediaPreviewUrls = []
  2225. this.featuredCommentMediaPreviewVideoUrl = ''
  2226. this.featuredCommentMediaPreviewPoster = ''
  2227. },
  2228. setFeaturedMediaPreviewIndex(ti) {
  2229. const n = Number(ti)
  2230. if (
  2231. !Number.isFinite(n) ||
  2232. n < 0 ||
  2233. n >= (this.featuredCommentMediaPreviewUrls || []).length
  2234. ) {
  2235. return
  2236. }
  2237. this.featuredCommentMediaPreviewIndex = n
  2238. },
  2239. onFeaturedMediaPreviewModalVideoPlay() {
  2240. this._pauseCourseVideo()
  2241. const list = this.featuredCommentDisplayList || []
  2242. for (let i = 0; i < list.length; i++) {
  2243. const row = list[i]
  2244. if (!this.getCommentVideoUrl(row)) continue
  2245. const vid = this.getFeaturedCommentVideoId(row, i)
  2246. try {
  2247. const c = uni.createVideoContext(vid, this)
  2248. if (c && c.pause) c.pause()
  2249. } catch (e) {}
  2250. }
  2251. },
  2252. _featuredMediaMaxBytes() {
  2253. return 100 * 1024 * 1024
  2254. },
  2255. _readFeaturedLocalFileSizeBytes(filePath) {
  2256. return new Promise((resolve) => {
  2257. const tryFsStat = () => {
  2258. let n = 0
  2259. // #ifdef MP-WEIXIN
  2260. try {
  2261. const fs = wx.getFileSystemManager && wx.getFileSystemManager()
  2262. if (fs && fs.statSync) {
  2263. const st = fs.statSync(filePath)
  2264. if (st && typeof st.size === 'number' && st.size > 0) n = st.size
  2265. }
  2266. } catch (e) {}
  2267. // #endif
  2268. return n
  2269. }
  2270. if (!filePath || typeof filePath !== 'string') {
  2271. resolve(0)
  2272. return
  2273. }
  2274. uni.getFileInfo({
  2275. filePath,
  2276. success: (fi) => {
  2277. let sz =
  2278. fi && fi.size != null ? Math.floor(Number(fi.size)) : 0
  2279. if (!Number.isFinite(sz) || sz <= 0) sz = tryFsStat()
  2280. resolve(sz)
  2281. },
  2282. fail: () => resolve(tryFsStat())
  2283. })
  2284. })
  2285. },
  2286. _alertFeaturedMediaLimit(content) {
  2287. uni.showModal({
  2288. title: '提示',
  2289. content:
  2290. content ||
  2291. '图片或视频大小不能超过100MB,请压缩或更换后再试。',
  2292. showCancel: false,
  2293. confirmText: '知道了'
  2294. })
  2295. },
  2296. _assertFeaturedLocalMediaUnderMaxBytes(filePath) {
  2297. const max = this._featuredMediaMaxBytes()
  2298. return new Promise((resolve) => {
  2299. if (!filePath || typeof filePath !== 'string') {
  2300. this._alertFeaturedMediaLimit('文件路径无效,请重新选择。')
  2301. resolve(false)
  2302. return
  2303. }
  2304. this._readFeaturedLocalFileSizeBytes(filePath).then((sz) => {
  2305. if (sz <= 0) {
  2306. uni.showToast({
  2307. title: '本地暂无法读取大小,请勿超过100MB',
  2308. icon: 'none',
  2309. duration: 2600
  2310. })
  2311. resolve(true)
  2312. return
  2313. }
  2314. if (sz > max) {
  2315. this._alertFeaturedMediaLimit()
  2316. resolve(false)
  2317. return
  2318. }
  2319. resolve(true)
  2320. })
  2321. })
  2322. },
  2323. _applyFeaturedMediaIfSizeOk(type, path, size, thumbTempPath) {
  2324. const max = this._featuredMediaMaxBytes()
  2325. const applyAccepted = () => {
  2326. this.featuredCommentMedia = {
  2327. type,
  2328. path,
  2329. thumbTempPath: thumbTempPath || ''
  2330. }
  2331. }
  2332. let bytes =
  2333. size != null && Number(size) > 0
  2334. ? Math.floor(Number(size))
  2335. : 0
  2336. if (!Number.isFinite(bytes)) bytes = 0
  2337. const finalize = (n) => {
  2338. if (n > max) {
  2339. this._alertFeaturedMediaLimit()
  2340. return
  2341. }
  2342. applyAccepted()
  2343. }
  2344. if (bytes > 0) {
  2345. finalize(bytes)
  2346. return
  2347. }
  2348. this._readFeaturedLocalFileSizeBytes(path).then(finalize)
  2349. },
  2350. clearFeaturedCommentMedia() {
  2351. this.featuredCommentMedia = null
  2352. this._resumeCourseVideoAfterFeaturedJob()
  2353. },
  2354. _expandLayoutIfPortraitFeaturedCommentsOnly() {
  2355. if (this.displayType !== 'portrait') return
  2356. if (!this.isShu) return
  2357. if (!(this.visibleRatingTabCount === 1 && this.showTabFeaturedComments)) return
  2358. this.isShu = false
  2359. this.tabIndex = 0
  2360. },
  2361. _armFeaturedMediaPickerOnShowSuppress() {
  2362. this.videovipSuppressOnShowCourseFetchOnce = true
  2363. const clearIfStillArmed = () => {
  2364. setTimeout(() => {
  2365. if (this.videovipSuppressOnShowCourseFetchOnce) {
  2366. this.videovipSuppressOnShowCourseFetchOnce = false
  2367. }
  2368. }, 600)
  2369. }
  2370. return clearIfStillArmed
  2371. },
  2372. onFeaturedPickMedia() {
  2373. this._expandLayoutIfPortraitFeaturedCommentsOnly()
  2374. this.featuredMediaActionSheetShow = true
  2375. },
  2376. closeFeaturedMediaActionSheet() {
  2377. this.featuredMediaActionSheetShow = false
  2378. },
  2379. onFeaturedMediaSheetPick(tapIndex) {
  2380. this.closeFeaturedMediaActionSheet()
  2381. const afterSheetCloseMs = 350
  2382. setTimeout(() => {
  2383. if (tapIndex === 0) {
  2384. this._shouldResumeCourseVideoAfterFeaturedUpload =
  2385. !this.isEnded &&
  2386. (this.videoPlaying || this._courseWasPlayingBeforeFeaturedCommentOpen)
  2387. this._pauseCourseVideo()
  2388. const clearIfStillArmed = this._armFeaturedMediaPickerOnShowSuppress()
  2389. uni.chooseImage({
  2390. count: 1,
  2391. sizeType: ['compressed', 'original'],
  2392. sourceType: ['album', 'camera'],
  2393. success: (res) => {
  2394. const f0 = res.tempFiles && res.tempFiles[0]
  2395. const path =
  2396. (res.tempFilePaths && res.tempFilePaths[0]) ||
  2397. (f0 && f0.path) ||
  2398. ''
  2399. if (!path) return
  2400. const size = f0 && f0.size != null ? f0.size : 0
  2401. this._applyFeaturedMediaIfSizeOk('image', path, size, '')
  2402. },
  2403. fail: () => {
  2404. // 勿在此处清 suppress:须由 onShow 消费,避免先于 onShow 清标志导致停跑马灯
  2405. this._resumeCourseVideoAfterFeaturedJob()
  2406. },
  2407. complete: clearIfStillArmed
  2408. })
  2409. } else if (tapIndex === 1) {
  2410. this._shouldResumeCourseVideoAfterFeaturedUpload =
  2411. !this.isEnded &&
  2412. (this.videoPlaying || this._courseWasPlayingBeforeFeaturedCommentOpen)
  2413. this._pauseCourseVideo()
  2414. const clearIfStillArmed = this._armFeaturedMediaPickerOnShowSuppress()
  2415. wx.chooseMedia({
  2416. count: 1,
  2417. mediaType: ['video'],
  2418. sourceType: ['album'],
  2419. maxDuration: 30,
  2420. success: (res) => {
  2421. const path = res.tempFiles[0].tempFilePath
  2422. if (!path) return
  2423. const size = res.tempFiles[0].size != null ? res.tempFiles[0].size : 0
  2424. this._applyFeaturedMediaIfSizeOk('video', path, size, res.tempFiles[0].thumbTempFilePath || '')
  2425. },
  2426. fail: () => {
  2427. // 勿在此处清 suppress:须由 onShow 消费,避免先于 onShow 清标志导致停跑马灯
  2428. this._resumeCourseVideoAfterFeaturedJob()
  2429. },
  2430. complete: clearIfStillArmed
  2431. })
  2432. }
  2433. }, afterSheetCloseMs)
  2434. },
  2435. async submitFeaturedComment() {
  2436. const raw = this.featuredCommentInput || ''
  2437. const text = raw.trim()
  2438. if (raw.length > 50) {
  2439. uni.showToast({
  2440. title: '留言不能超过50个字',
  2441. icon: 'none'
  2442. })
  2443. this._resumeCourseVideoAfterFeaturedJob()
  2444. return
  2445. }
  2446. const media = this.featuredCommentMedia
  2447. const hasMedia = !!(media && media.path)
  2448. if (!text && !hasMedia) {
  2449. uni.showToast({
  2450. title: '请输入留言或选择图片/视频',
  2451. icon: 'none'
  2452. })
  2453. return
  2454. }
  2455. const u = this._resolveUserInfoObj()
  2456. const userId = u.userId || ''
  2457. if (!userId) {
  2458. uni.showToast({
  2459. title: '请先登录',
  2460. icon: 'none'
  2461. })
  2462. this._resumeCourseVideoAfterFeaturedJob()
  2463. return
  2464. }
  2465. if (this.featuredCommentSending) return
  2466. if (hasMedia && media.path) {
  2467. const ok = await this._assertFeaturedLocalMediaUnderMaxBytes(media.path)
  2468. if (!ok) {
  2469. this._resumeCourseVideoAfterFeaturedJob()
  2470. return
  2471. }
  2472. }
  2473. this.featuredCommentSending = true
  2474. const done = (res) => {
  2475. this.featuredCommentSending = false
  2476. if (res.code == 200) {
  2477. this.featuredCommentInput = ''
  2478. this.featuredCommentMedia = null
  2479. this._closeFeaturedCommentComposerUi()
  2480. uni.showToast({
  2481. title: '发送成功',
  2482. icon: 'none'
  2483. })
  2484. this.loadFeaturedComments({
  2485. scrollToBottom: true
  2486. })
  2487. } else {
  2488. uni.showToast({
  2489. title: res.msg || '发送失败',
  2490. icon: 'none'
  2491. })
  2492. }
  2493. }
  2494. const fail = (err) => {
  2495. this.featuredCommentSending = false
  2496. const msg = err && err.message ? err.message : '网络异常,请重试'
  2497. uni.showToast({
  2498. title: msg,
  2499. icon: 'none'
  2500. })
  2501. }
  2502. const payload = {
  2503. toUserId: userId,
  2504. courseId: this.urlOption.courseId,
  2505. videoId: this.videoId,
  2506. type: 1,
  2507. content: text
  2508. }
  2509. if (hasMedia) {
  2510. if (media.type === 'video') {
  2511. payload.videoFiles = media.path
  2512. } else {
  2513. payload.imageFiles = media.path
  2514. }
  2515. }
  2516. uni.showLoading({
  2517. title: '发布中',
  2518. mask: true
  2519. })
  2520. addCommentWithMedia(payload)
  2521. .then((res) => {
  2522. uni.hideLoading()
  2523. done(res)
  2524. })
  2525. .catch((err) => {
  2526. uni.hideLoading()
  2527. fail(err)
  2528. })
  2529. .then(() => {
  2530. this.$nextTick(() => {
  2531. setTimeout(() => {
  2532. this._resumeCourseVideoAfterFeaturedJob()
  2533. }, 60)
  2534. })
  2535. })
  2536. },
  2537. getWebviewUrl() {
  2538. var data = {
  2539. key: 'course.config'
  2540. }
  2541. getConfigByKey(data).then(res => {
  2542. if (res.code == 200) {
  2543. console.log("getConfigByKey====", JSON.parse(res.data))
  2544. let data = JSON.parse(res.data)
  2545. this.notice = data.camelCase
  2546. uni.setStorageSync('setWebviewUrl',data.userCourseAuthDomain)
  2547. }else{
  2548. uni.showToast({
  2549. icon:'none',
  2550. title: res.msg,
  2551. });
  2552. }
  2553. })
  2554. },
  2555. numberToChinese(number) {
  2556. if (number) {
  2557. const chineseNumber = ['一', '二', '三', '四', '五', '六', '七', '八', '九'];
  2558. return chineseNumber[number - 1];
  2559. } else {
  2560. return ''
  2561. }
  2562. },
  2563. keyboardHeightChange(res) {
  2564. // #ifndef H5
  2565. console.log("this.danmuboxHeight",this.danmuboxHeight)
  2566. this.danmuboxHeight = res.height
  2567. const h = res && res.height != null ? res.height : 0
  2568. const prev = this.featuredCommentKeyboardBottomPx
  2569. this.featuredCommentKeyboardBottomPx = h
  2570. if (
  2571. this.featuredCommentComposerActive &&
  2572. prev > 0 &&
  2573. h === 0
  2574. ) {
  2575. this.featuredCommentComposerActive = false
  2576. this.featuredCommentInputFocused = false
  2577. }
  2578. // #endif
  2579. },
  2580. getMenuButton(){
  2581. const menuButtonInfo = uni.getMenuButtonBoundingClientRect()
  2582. this.menuButtonLeft = menuButtonInfo.left
  2583. this.menuButtonH = menuButtonInfo.height
  2584. },
  2585. // 购物车
  2586. showCart() {
  2587. this.isCart = true
  2588. },
  2589. closeShop() {
  2590. this.isCart = false
  2591. },
  2592. // 更多
  2593. showMore() {
  2594. this.isMore = true
  2595. },
  2596. closeMore() {
  2597. this.isMore = false
  2598. },
  2599. navgetTo(url) {
  2600. uni.navigateTo({ url })
  2601. },
  2602. // 商品卡片跳转
  2603. goBuy(item) {
  2604. if (!item || !item.productId) return
  2605. uni.navigateTo({
  2606. url: '/pages/shopping/productDetails?productId=' + item.productId +
  2607. '&companyId=' + (this.urlOption.companyId || '') +
  2608. '&companyUserId=' + (this.urlOption.companyUserId || '') +
  2609. '&courseId=' + (this.urlOption.courseId || '') +
  2610. '&videoId=' + (this.urlOption.videoId || '') +
  2611. '&projectId=' + (this.urlOption.projectId || '') +
  2612. '&periodId=' + (this.urlOption.periodId || '')
  2613. })
  2614. },
  2615. // 倒计时格式化函数
  2616. formatCountdown(seconds) {
  2617. const totalSeconds = Math.max(0, Math.floor(Number(seconds) || 0))
  2618. const hours = Math.floor(totalSeconds / 3600)
  2619. const remainingAfterHours = totalSeconds % 3600
  2620. const minutes = Math.floor(remainingAfterHours / 60)
  2621. const secs = remainingAfterHours % 60
  2622. return {
  2623. hours: this.padZero(hours),
  2624. minutes: this.padZero(minutes),
  2625. seconds: this.padZero(secs),
  2626. total: totalSeconds
  2627. }
  2628. },
  2629. getCountdownPercentage() {
  2630. const total = Number(this.totalRemainTime) || 0
  2631. if (!total) return 0
  2632. const remain = Math.max(0, Number(this.remainTime) || 0)
  2633. const watched = Math.max(0, total - remain)
  2634. const percentage = (watched / total) * 100
  2635. return Math.min(100, Math.max(0, Number(percentage.toFixed(2))))
  2636. },
  2637. padZero(num) {
  2638. return num < 10 ? `0${num}` : num.toString()
  2639. },
  2640. // 启动完课积分倒计时(仅在播放时递减)
  2641. startCountdown() {
  2642. if (this.remainTime <= 0 || this.countdownTimer) return;
  2643. this.countdownTimer = setInterval(() => {
  2644. if (this.remainTime > 0) {
  2645. this.remainTime--;
  2646. }
  2647. if (this.remainTime <= 0 && this.countdownTimer) {
  2648. clearInterval(this.countdownTimer);
  2649. this.countdownTimer = null;
  2650. if (!this.hasReportedAfterCountdown) {
  2651. this.hasReportedAfterCountdown = true;
  2652. this.getFinishCourseVideo();
  2653. this.getInternetTraffic();
  2654. }
  2655. }
  2656. }, 1000);
  2657. },
  2658. // 停止完课积分倒计时(暂停/离开页面时调用)
  2659. stopCountdown() {
  2660. if (this.countdownTimer) {
  2661. clearInterval(this.countdownTimer)
  2662. this.countdownTimer = null
  2663. }
  2664. },
  2665. // 剩余倒计时
  2666. getRemainTime(userId) {
  2667. const data = {
  2668. videoId: this.videoId,
  2669. fsUserId: userId,
  2670. courseId: this.urlOption.courseId,
  2671. qwExternalId:this.urlOption.qwExternalId,
  2672. qwUserId:this.urlOption.qwUserId,
  2673. // projectId: this.urlOption.projectId,
  2674. // periodId: this.urlOption.periodId,
  2675. companyUserId: this.urlOption.companyUserId
  2676. }
  2677. getRemainTime(data).then(res => {
  2678. if (res.code == 200) {
  2679. const remain = Number(res.remainTime) || 0
  2680. this.remainTime = remain;
  2681. this.remainTimeReady = true
  2682. this.hasReportedAfterCountdown = remain <= 0
  2683. if (!this.totalRemainTime || remain > this.totalRemainTime) {
  2684. this.totalRemainTime = remain;
  2685. }
  2686. } else {
  2687. this.remainTimeReady = false
  2688. uni.showToast({
  2689. icon: 'none',
  2690. title: res.msg,
  2691. })
  2692. }
  2693. })
  2694. },
  2695. // 根据当前播放时间:1)扫描商品栏展示(上架/下架时间)2)控制商品卡片弹窗(弹出/关闭时间)
  2696. updateProductAndCardDisplay(currentSec) {
  2697. const toSeconds = (timeStr) => {
  2698. if (!timeStr || typeof timeStr !== 'string') return 0
  2699. const parts = timeStr.split(':')
  2700. if (parts.length !== 3) return 0
  2701. const h = parseInt(parts[0], 10) || 0
  2702. const m = parseInt(parts[1], 10) || 0
  2703. const s = parseInt(parts[2], 10) || 0
  2704. return h * 3600 + m * 60 + s
  2705. }
  2706. const hasValidShelfTime = (timeStr) => {
  2707. if (!timeStr || typeof timeStr !== 'string') return false
  2708. if (timeStr === '00:00:00') return false
  2709. return !!toSeconds(timeStr)
  2710. }
  2711. if (this.treatmentPackage.length === 0) {
  2712. this.displayProductList = []
  2713. this.cardPopup = false
  2714. this.currentCardItem = null
  2715. this._syncFakeMarqueeIfEligibleChanged()
  2716. return
  2717. }
  2718. // 按“上架时间”倒序排序,保证后上架商品展示在列表最前
  2719. const sortedTreatmentPackage = this.treatmentPackage.slice().sort((a, b) => {
  2720. const aOn = hasValidShelfTime(a.onShelfTime) ? toSeconds(a.onShelfTime) : 0
  2721. const bOn = hasValidShelfTime(b.onShelfTime) ? toSeconds(b.onShelfTime) : 0
  2722. if (bOn !== aOn) return bOn - aOn
  2723. // 上架时间相同则按下架时间倒序,进一步保证新近优先
  2724. const aOff = hasValidShelfTime(a.offShelfTime) ? toSeconds(a.offShelfTime) : 0
  2725. const bOff = hasValidShelfTime(b.offShelfTime) ? toSeconds(b.offShelfTime) : 0
  2726. return bOff - aOff
  2727. })
  2728. const videoDuration = this.duration || 0
  2729. // 1. 商品栏展示:按上架/下架时间过滤
  2730. this.displayProductList = sortedTreatmentPackage.filter(item => {
  2731. // 上架时间必填;下架时间可为空(为空表示一直展示到视频结束)
  2732. if (!hasValidShelfTime(item.onShelfTime)) return false
  2733. const onShelfSec = toSeconds(item.onShelfTime)
  2734. if (currentSec < onShelfSec) return false
  2735. const hasOffShelfTime = hasValidShelfTime(item.offShelfTime)
  2736. if (!hasOffShelfTime) return true
  2737. const offShelfSec = toSeconds(item.offShelfTime)
  2738. if (videoDuration > 0 && offShelfSec > videoDuration) return true
  2739. return currentSec < offShelfSec
  2740. })
  2741. //console.log('this.displayProductList',this.displayProductList)
  2742. // 2. 商品卡片弹窗:按弹出/关闭时间查找当前应展示的卡片
  2743. let activeCard = null
  2744. let activePopupSec = -1
  2745. for (let i = 0; i < sortedTreatmentPackage.length; i++) {
  2746. const item = sortedTreatmentPackage[i]
  2747. const popupStr = item.cardPopupTime
  2748. const closeStr = item.cardCloseTime
  2749. if (!popupStr || popupStr === '00:00:00') continue
  2750. const popupSec = toSeconds(popupStr)
  2751. if (!popupSec) continue
  2752. const closeSec = (closeStr && closeStr !== '00:00:00') ? toSeconds(closeStr) : null
  2753. if (currentSec >= popupSec && (closeSec == null || currentSec < closeSec)) {
  2754. // 同时命中多个弹窗时:优先展示“启动时间更晚”的后续弹窗
  2755. if (popupSec > activePopupSec) {
  2756. activePopupSec = popupSec
  2757. activeCard = item
  2758. } else if (popupSec === activePopupSec && activeCard) {
  2759. // 上架时间更晚优先(兜底)
  2760. const activeOn = hasValidShelfTime(activeCard.onShelfTime) ? toSeconds(activeCard.onShelfTime) : 0
  2761. const itemOn = hasValidShelfTime(item.onShelfTime) ? toSeconds(item.onShelfTime) : 0
  2762. if (itemOn > activeOn) activeCard = item
  2763. }
  2764. }
  2765. }
  2766. const activeCardKey = activeCard ?
  2767. `${activeCard.productId || ''}_${activeCard.cardPopupTime || ''}_${activeCard.cardCloseTime || ''}` :
  2768. ''
  2769. if (!activeCardKey) {
  2770. this.dismissedCardKey = ''
  2771. }
  2772. this.cardPopup = !!activeCard && this.dismissedCardKey !== activeCardKey
  2773. //this.getVideoContainerHeight()
  2774. this.currentCardItem = activeCard
  2775. this.syncProductHotCounts()
  2776. this._syncFakeMarqueeIfEligibleChanged()
  2777. },
  2778. //播放时间更新事件方法
  2779. onTimeUpdate(e){
  2780. let currentTime = Math.round(e.detail.currentTime)
  2781. if (this.playDurationSeek > 0) {
  2782. this.playTime = this.playDurationSeek
  2783. this.throttle(() => this.changeTime(this), 1000, false)
  2784. } else {
  2785. const suppressAntiSeek = Date.now() < (this._antiSeekSuppressExpireAt || 0)
  2786. if (!suppressAntiSeek && this.videolinkType != 1 && (currentTime - this.playTime > 3 || currentTime - this.playTime < -3)&&this.isFinish!=1) {
  2787. uni.showToast({
  2788. title: '不能快进哦',
  2789. icon: 'none',
  2790. });
  2791. currentTime = this.playTime
  2792. this.player.seek(this.playTime);
  2793. }
  2794. this.playTime = currentTime
  2795. }
  2796. if (Math.floor(e.detail.currentTime) != this.flagTime) {
  2797. this.flagTime = Math.floor(e.detail.currentTime)
  2798. let answerType = this.$store.state.answerType;
  2799. this.showAnswerTip = answerType == 1 ? this.shouldShowByRate(this): this.shouldShowByLastMinute(this);
  2800. if(this.openCommentStatus == 2) {
  2801. this.$refs.danmuBox&&this.$refs.danmuBox.checkDanmu(this.flagTime)
  2802. }
  2803. }
  2804. // 基于播放时间动态更新商品上/下架列表、商品卡片弹窗,以及完课积分倒计时
  2805. this.updateProductAndCardDisplay(this.playTime)
  2806. this.startCountdown()
  2807. },
  2808. // 工具函数:保留两位小数的百分比(0~100)
  2809. calcPercent(play, total) {
  2810. return Number(((play || 0) / (total || 1) * 100).toFixed(2));
  2811. },
  2812. // 策略1:按完课领红包
  2813. shouldShowByRate({ playTime, duration, config }) {
  2814. const finished = this.calcPercent(playTime, duration) >= Number(config?.answerRate || 100);
  2815. return finished;
  2816. },
  2817. // 策略2:完课且最后一分钟可领(第二次进来只看是否已完课)
  2818. shouldShowByLastMinute({ isEnded, isFinish, playTime, duration }) {
  2819. const alreadyDone = isEnded || isFinish == 1; // 已完课
  2820. const inLastMinute = playTime >= (duration || 0) - 60; // 最后一分钟
  2821. return alreadyDone || inLastMinute;
  2822. },
  2823. changeTime(that,e) {
  2824. that.playDurationSeek = 0
  2825. },
  2826. videoErrorCallback(e) {
  2827. this.clearIntegral()
  2828. this.errorCount++
  2829. if (this.errorCount > 3) return
  2830. if (this.interval != null) {
  2831. clearInterval(this.interval)
  2832. }
  2833. this.getErrMsg(e.target.errMsg)
  2834. this.getH5CourseVideoDetails('error')
  2835. },
  2836. // 当开始/继续播放时触发play事件
  2837. getPlay() {
  2838. this.errorCount = 0
  2839. this.videoPlaying = true
  2840. // this.judgeDuration()
  2841. this.startCountdown()
  2842. },
  2843. getPause() {
  2844. this.videoPlaying = false
  2845. this.clearIntegral()
  2846. this.stopCountdown()
  2847. },
  2848. getEnded() {
  2849. this.videoPlaying = false
  2850. this.clearIntegral()
  2851. this.stopCountdown()
  2852. this.isEnded = true
  2853. this.showAnswerTip = true
  2854. // this.playDurationSeek = 0
  2855. this.isFinish = 1
  2856. this.showProgress = true
  2857. this.getFinishCourseVideo()
  2858. },
  2859. fullscreenchange(event) {
  2860. //this.isfull = event.detail.fullScreen
  2861. // if(this.isfull) {
  2862. // this.$refs.danmuBox&&this.$refs.danmuBox.initTracks()
  2863. // }
  2864. const isFullScreen = !!(event && event.detail && event.detail.fullScreen);
  2865. this.isFull = event.detail.fullScreen;
  2866. // 竖屏课程下:把全屏按钮当作布局切换按钮,禁止原生全屏停留
  2867. if (this.displayType === 'portrait') {
  2868. if (event && event.stopPropagation) event.stopPropagation();
  2869. if (event && event.preventDefault) event.preventDefault();
  2870. // 只在“进入全屏”时切换,避免 exitFullScreen 触发的 false 事件二次反转
  2871. if (!isFullScreen || this.fullscreenToggleLock) return;
  2872. this.fullscreenToggleLock = true;
  2873. // const wasShu = this.isShu
  2874. this.isShu = !this.isShu;
  2875. // if (!this.isShu && wasShu && this.showTabFeaturedComments) this.tabIndex = 0
  2876. // 先让布局切换完成,再退出原生全屏,减少抖动闪频
  2877. setTimeout(() => {
  2878. if (this.videoContext && this.videoContext.exitFullScreen) {
  2879. this.videoContext.exitFullScreen();
  2880. }
  2881. this.isFull = false;
  2882. this.fullscreenToggleTimer = setTimeout(() => {
  2883. this.fullscreenToggleLock = false;
  2884. this.fullscreenToggleTimer = null;
  2885. }, 220);
  2886. }, 40);
  2887. //console.log(this.isFull,'this.isFull111 ')
  2888. }
  2889. },
  2890. controlstoggle(event) {
  2891. this.crtShow = event.detail.show
  2892. },
  2893. getIP() {
  2894. uni.request({
  2895. url: 'https://ipinfo.io/json', //仅为示例,并非真实接口地址。
  2896. method: 'GET',
  2897. success: (res) => {
  2898. this.ip = res.data.ip
  2899. }
  2900. });
  2901. },
  2902. getHeight() {
  2903. this.height =
  2904. `calc(100vh - 420rpx - 160rpx)`
  2905. // setTimeout(()=>{
  2906. // const query = uni.createSelectorQuery().in(this);
  2907. // query
  2908. // .select("#title-contentnav")
  2909. // .boundingClientRect((data) => {
  2910. // if(data) {
  2911. // this.height =
  2912. // `calc(100vh - ${data.height}px - 420rpx - ${this.statusBarHeight}px - 75px - 88rpx)`
  2913. // }
  2914. // })
  2915. // .exec();
  2916. // },200)
  2917. },
  2918. updateTime() {
  2919. var that = this;
  2920. if (this.interval != null) {
  2921. clearInterval(this.interval)
  2922. }
  2923. this.interval = setInterval(function() {
  2924. that.getFinishCourseVideo()
  2925. that.getInternetTraffic()
  2926. }, 60000);
  2927. },
  2928. judgeDuration() {
  2929. var that = this;
  2930. if (this.intervalIntegral != null) {
  2931. clearInterval(this.intervalIntegral)
  2932. this.intervalIntegral = null
  2933. }
  2934. // 观看10分钟获得积分
  2935. this.intervalIntegral = setInterval(function() {
  2936. that.getIntegralByH5Video()
  2937. }, 600000);
  2938. },
  2939. clearIntegral() {
  2940. if (this.intervalIntegral != null) {
  2941. clearInterval(this.intervalIntegral)
  2942. this.intervalIntegral = null
  2943. }
  2944. },
  2945. getH5CourseByVideo() {
  2946. this.loading = true
  2947. getH5CourseByVideoId({
  2948. videoId: this.videoId
  2949. }).then(res => {
  2950. this.loading = false
  2951. if (res.code == 200) {
  2952. this.courseInfo = res.data
  2953. uni.setNavigationBarTitle({
  2954. title: this.courseInfo && this.courseInfo.title ? this.courseInfo.title : ''
  2955. });
  2956. this.$nextTick(() => {
  2957. this._syncRatingTabIndex()
  2958. })
  2959. }
  2960. this.getHeight()
  2961. this.$nextTick(()=>{
  2962. this.$refs.descInfo&&this.$refs.descInfo.getDescHeight()
  2963. this.$refs.descInfoNav&&this.$refs.descInfoNav.getDescHeight()
  2964. })
  2965. },
  2966. rej => {
  2967. this.loading = false
  2968. }
  2969. ).catch(() => {
  2970. this.loading = false
  2971. })
  2972. },
  2973. getH5CourseVideoDetails(type) {
  2974. getH5CourseVideoDetails(this.urlOption).then(res => {
  2975. if (res.code == 200) {
  2976. this.config = res.config || {}
  2977. this.courseLogo = res.config&&res.config.courseLogo
  2978. this.isFinish = res.isFinish || 0
  2979. this.duration = res.course && res.course.duration ? res.course.duration : 0
  2980. this.playDuration = res.playDuration || 0
  2981. this.tipsTime = res.tipsTime || 0
  2982. this.displayType = res.course && res.course
  2983. .displayType != '' ? res.course.displayType :'landscape'
  2984. this.isShu = this.displayType=='landscape'?false:true
  2985. const showTreatmentFlag = res.course&&res.course.showProduct!=null ? res.course.showProduct : 1
  2986. this.treatmentPackage = res.course&&res.course.fsStoreProductScrms ? res.course.fsStoreProductScrms : []
  2987. this.showTreatment = showTreatmentFlag==0&&this.treatmentPackage.length>0 ? 0 : 1;
  2988. let lineList = []
  2989. if (res.course && res.course.lineOne) {
  2990. lineList.push(res.course.lineOne)
  2991. }
  2992. if (res.course && res.course.lineTwo) {
  2993. lineList.push(res.course.lineTwo)
  2994. }
  2995. if (res.course && res.course.lineThree) {
  2996. lineList.push(res.course.lineThree)
  2997. }
  2998. this.lineList = lineList
  2999. this.viewCommentNum = res.config&&res.config.viewCommentNum || 200
  3000. const status = res.config&&res.config.openCommentStatus || 3
  3001. if(status != this.openCommentStatus) {
  3002. if(this.openCommentStatus != 3 && status==3) {
  3003. this.$refs.commentBox&&this.$refs.commentBox.closeWSocket()
  3004. this.$refs.danmuBox&&this.$refs.danmuBox.closeWSocket()
  3005. }
  3006. this.openCommentStatus = this.isOpen == 1 ? 3: status
  3007. }
  3008. this.currentTab = 1
  3009. if(this.openCommentStatus!=2 || this.showDanmu!=1) {
  3010. this.activeDanmus = []
  3011. }
  3012. if (!this.player || type == 'error') {
  3013. this.lineIndex = this.config.defaultLine
  3014. this.videoUrl = lineList[this.lineIndex]
  3015. this.poster= res.course && res.course.imgUrl ? res.course.imgUrl : ''
  3016. // this.options.sources = [{
  3017. // src: this.videoUrl
  3018. // }]
  3019. // this.options.poster = res.course && res.course.imgUrl ? res.course.imgUrl : ''
  3020. // this.initVideo()
  3021. this.playTime = this.playDuration >= this.duration ? 0 : this.playDuration
  3022. this.playDurationSeek = this.playTime
  3023. this.updateProductAndCardDisplay(this.playTime || 0)
  3024. setTimeout(()=>{
  3025. this.player = uni.createVideoContext('video-content-box');
  3026. this.player.seek(this.playTime)
  3027. this.player.play();
  3028. this._touchAntiSeekSuppress(2000)
  3029. },500);
  3030. } else {
  3031. // let div = document.querySelector(".vjs-progress-control");
  3032. // if(div) {
  3033. // if (this.isFinish == 1 || this.isEnded || this.videolinkType == 1) {
  3034. // div.style.pointerEvents = "auto";
  3035. // } else {
  3036. // div.style.pointerEvents = "none"; //禁止所有事件
  3037. // }
  3038. // }
  3039. this.playTime = this.playTime > this.playDuration ? this.playTime : this.playDuration >= this.duration ? 0 : this.playDuration
  3040. this.playDurationSeek = this.playTime
  3041. this.updateProductAndCardDisplay(this.playTime || 0)
  3042. this.player.seek(this.playTime)
  3043. this.player.play();
  3044. this._touchAntiSeekSuppress(2000)
  3045. }
  3046. this.updateTime();
  3047. this.$nextTick(() => {
  3048. this.marqueeDataReady = true
  3049. this._syncFakeMarqueeIfEligibleChanged()
  3050. })
  3051. const rawQ =
  3052. (res.course && res.course.questionBankList) ||
  3053. res.questionBankList ||
  3054. res.questions ||
  3055. []
  3056. const qbl = Array.isArray(rawQ) ? rawQ : []
  3057. this.questionBankList = qbl
  3058. if (qbl.length === 0) {
  3059. this.isquestion = true
  3060. this.quesList = []
  3061. } else {
  3062. this.isquestion = false
  3063. this.quesList = qbl.map(item => ({
  3064. ...item,
  3065. questionOption: JSON.parse(item.question),
  3066. answer: ''
  3067. }))
  3068. }
  3069. this.$nextTick(() => {
  3070. this._syncRatingTabIndex()
  3071. })
  3072. }
  3073. this.getHeight()
  3074. this.$nextTick(()=>{
  3075. this.$refs.descInfo&&this.$refs.descInfo.getDescHeight()
  3076. this.$refs.descInfoNav&&this.$refs.descInfoNav.getDescHeight()
  3077. })
  3078. },
  3079. rej => {}
  3080. )
  3081. },
  3082. goRate(index){
  3083. this.aindex = index
  3084. if (this._rateBounceTimer) {
  3085. clearTimeout(this._rateBounceTimer)
  3086. this._rateBounceTimer = null
  3087. }
  3088. this.rateBounceIndex = null
  3089. this.$nextTick(() => {
  3090. this.rateBounceIndex = index
  3091. this._rateBounceTimer = setTimeout(() => {
  3092. this.rateBounceIndex = null
  3093. this._rateBounceTimer = null
  3094. }, 480)
  3095. })
  3096. },
  3097. handleAnswer(item, option,index) {
  3098. // 完课积分倒计时:倒计时未结束前不允许答题
  3099. this.goRate(option.indexId)
  3100. if (Number(this.remainTime || 0) > 0) {
  3101. uni.showToast({
  3102. title: "完课倒计时结束再评分",
  3103. icon: "none"
  3104. })
  3105. return
  3106. }
  3107. if (item.type == 1) {
  3108. // 单选option
  3109. item.answer = option.name
  3110. } else if (item.type == 2) {
  3111. // 多选
  3112. let answer = item.answer ? item.answer.split(',') : []
  3113. if (answer.indexOf(option.name) === -1) {
  3114. answer.push(option.name)
  3115. item.answer = answer.join(',')
  3116. } else {
  3117. answer.splice(answer.indexOf(option.name), 1)
  3118. item.answer = answer.join(',')
  3119. }
  3120. }
  3121. },
  3122. submit() {
  3123. if(this.isExpire){
  3124. uni.showToast({
  3125. title: '课程已过期或链接无效',
  3126. icon: 'none'
  3127. });
  3128. return
  3129. }
  3130. // 登录
  3131. this.$isLoginCourseAuto().then(
  3132. res => {
  3133. if(res){
  3134. if (this.isAddKf == 1) {
  3135. // 答题
  3136. // 您已提交过答案,请领取红包
  3137. this.courseAnswer()
  3138. } else {
  3139. // 添加客服
  3140. if (this.videoId && this.qwUserId) {
  3141. this.getIsAddKf()
  3142. } else {
  3143. uni.showToast({
  3144. title: '请添加客服',
  3145. icon: 'none'
  3146. })
  3147. }
  3148. }
  3149. } else{
  3150. this.goLogin()
  3151. }
  3152. },
  3153. rej => {}
  3154. );
  3155. },
  3156. // 答题
  3157. courseAnswer() {
  3158. // 完课积分倒计时:倒计时未结束前不允许提交
  3159. if (Number(this.remainTime || 0) > 0) {
  3160. uni.showToast({
  3161. title: "完课倒计时结束再评分",
  3162. icon: "none"
  3163. })
  3164. return
  3165. }
  3166. if (this.quesList.some(item => !item.answer)) {
  3167. uni.showToast({
  3168. title: "请选择评分",
  3169. icon: "none"
  3170. })
  3171. return
  3172. }
  3173. const questions = this.quesList.map(obj => {
  3174. const {
  3175. questionOption,
  3176. ...rest
  3177. } = obj;
  3178. return rest;
  3179. });
  3180. const param = {
  3181. ...this.urlOption,
  3182. questions: questions,
  3183. videoId: this.videoId,
  3184. duration: this.playTime,
  3185. }
  3186. this.errTitle = ""
  3187. this.errDesc = ""
  3188. this.errQues = []
  3189. courseAnswer(param).then(res => {
  3190. if (res.code == 200) {
  3191. if (res.incorrectQuestions) {
  3192. // 答题失败
  3193. if (res.incorrectQuestions.length > 0) {
  3194. this.errQues = res.incorrectQuestions
  3195. }
  3196. this.remain = res.remain || 0
  3197. if (res.remain > 0) {
  3198. this.errTitle = "很遗憾答错了"
  3199. this.errDesc = `<span style="color:#FF5C03">还有${res.remain}次机会,继续加油</span>`
  3200. this.$refs.answerPopup.open("center")
  3201. }
  3202. } else {
  3203. // 答题成功
  3204. this.errTitle = "完课积分已到账"
  3205. if (res.msg == '答题成功') {
  3206. this.closeAnswerPopup(1)
  3207. }
  3208. // this.errTitle = "恭喜你,回答正确"
  3209. // this.errDesc = `请选择奖励`
  3210. // this.$refs.answerPopup.open("center")
  3211. // if(this.isOpen==1) {
  3212. // this.$refs.answerPopup.open("center")
  3213. // return
  3214. // }
  3215. // this.closeAnswerPopup(1)
  3216. }
  3217. } else {
  3218. if (res.msg == "该课题到达答错次数限制") {
  3219. this.errTitle = "答题次数超过限制"
  3220. this.errDesc = "以后的课程要认真学习哦"
  3221. this.$refs.answerPopup.open("center")
  3222. } else {
  3223. uni.showToast({
  3224. title: res.msg,
  3225. icon: "none"
  3226. })
  3227. }
  3228. }
  3229. },
  3230. rej => {}
  3231. )
  3232. },
  3233. // 选择
  3234. rewardChange(e) {
  3235. this.currentReward = e.detail.value
  3236. },
  3237. closeAnswerPopup(type) {
  3238. //this.$refs.answerPopup.close()
  3239. // if(this.isOpen==1) {
  3240. // return
  3241. // }
  3242. uni.showLoading({
  3243. title: "加载中..."
  3244. })
  3245. if (this.errTitle == '完课积分已到账') {
  3246. let param = {
  3247. ...this.urlOption,
  3248. rewardType: Number(this.currentReward),
  3249. source: 2,
  3250. appId: this.appid
  3251. }
  3252. if(type==1) {
  3253. param = {
  3254. ...this.urlOption,
  3255. source: 2,
  3256. appId: this.appid
  3257. }
  3258. }
  3259. sendReward(param).then(res => {
  3260. if(res.isNew&&res.isNew==1) {
  3261. const packageInfo = res.data.packageInfo || ''
  3262. if(packageInfo) {
  3263. uni.setStorageSync('receive_package',packageInfo);
  3264. if(res.mchId) uni.setStorageSync('mchId',res.mchId);
  3265. uni.navigateTo({
  3266. url: '/pages_course/reward'
  3267. })
  3268. }
  3269. } else {
  3270. uni.showToast({
  3271. title: res.msg,
  3272. icon: 'none'
  3273. })
  3274. }
  3275. })
  3276. }
  3277. },
  3278. // 线路
  3279. openPop() {
  3280. this.$refs.popup.open('bottom')
  3281. },
  3282. close() {
  3283. this.$refs.popup.close()
  3284. },
  3285. handleLine(index) {
  3286. var that=this;
  3287. if (this.lineIndex == index && this.videoUrl == this.lineList[index]) {
  3288. this.close()
  3289. return
  3290. } else {
  3291. // let div = document.querySelector(".vjs-progress-control");
  3292. // if(div) {
  3293. // if (this.isFinish == 1 || this.isEnded || this.videolinkType == 1) {
  3294. // div.style.pointerEvents = "auto";
  3295. // } else {
  3296. // div.style.pointerEvents = "none"; //禁止所有事件
  3297. // }
  3298. // }
  3299. this.lineIndex = index
  3300. this.videoUrl = this.lineList[index]
  3301. this.tipsOpen = false
  3302. this.playDurationSeek = this.playTime || 0
  3303. this.player = uni.createVideoContext('video-content-box');
  3304. setTimeout(function(){
  3305. that.player.seek(that.playDurationSeek)
  3306. that.player.play();
  3307. },500);
  3308. // this.player.src(this.lineList[index])
  3309. // this.player.one('loadedmetadata', () => {
  3310. // this.player.currentTime(this.playDurationSeek);
  3311. // this.player.play();
  3312. // });
  3313. this.close()
  3314. }
  3315. },
  3316. // 温馨提示
  3317. openTipsPop() {
  3318. this.$refs.tipsPopup.open()
  3319. this.tipsOpen = true
  3320. this.pause()
  3321. },
  3322. closeTipsPop() {
  3323. this.$refs.tipsPopup.close()
  3324. },
  3325. // 客服
  3326. getIsAddKf() {
  3327. this.qrcode = ''
  3328. this.qrcodeMsg = ''
  3329. this.isAddKf = 0
  3330. // {videoId: this.videoId,qwUserId: this.qwUserId,corpId: this.corpId}
  3331. getIsAddKf(this.urlOption).then(res => {
  3332. if (res.code == 200) {
  3333. this.isAddKf = 1
  3334. this.isLogin = true
  3335. this.getH5CourseVideoDetails()
  3336. if (this.user && this.user.userId) {
  3337. this.getRemainTime(this.user.userId)
  3338. this.loadFeaturedComments()
  3339. }
  3340. this.getMp()
  3341. } else if (res.code == 400) {
  3342. this.isAddKf = 0
  3343. this.qrcode = res.qrcode
  3344. this.qrcodeMsg = res.msg
  3345. this.$refs.kfPopup.open()
  3346. this.initExpiration(res.msg,res.code)
  3347. } else if (res.code == 504) {
  3348. // 登录
  3349. this.goLogin()
  3350. this.initExpiration(res.msg,res.code)
  3351. } else if (res.code == 566) {
  3352. // 官方群发通用链接
  3353. const url = res.courseLink.realLink.split('?course=')[1]
  3354. this.urlOption = JSON.parse(url)
  3355. this.isAddKf = 1
  3356. this.isLogin = true
  3357. this.getH5CourseVideoDetails()
  3358. if (this.user && this.user.userId) {
  3359. this.getRemainTime(this.user.userId)
  3360. this.loadFeaturedComments()
  3361. }
  3362. // this.initExpiration(res.msg,res.code)
  3363. } else if (res.code == 567) {
  3364. // 群聊通用链接
  3365. this.urlOption = {
  3366. ...this.urlOption,
  3367. qwExternalId: res.qwExternalId
  3368. }
  3369. this.isAddKf = 1
  3370. this.isLogin = true
  3371. this.getH5CourseVideoDetails()
  3372. if (this.user && this.user.userId) {
  3373. this.getRemainTime(this.user.userId)
  3374. }
  3375. // this.initExpiration(res.msg,res.code)
  3376. } else {
  3377. this.isAddKf = 0
  3378. uni.showToast({
  3379. title: res.msg,
  3380. icon: 'none'
  3381. });
  3382. this.initExpiration(res.msg,res.code)
  3383. }
  3384. },
  3385. err => {}
  3386. );
  3387. },
  3388. getMp(){
  3389. let provider = 'weixin'
  3390. uni.login({
  3391. provider: provider,
  3392. success: async loginRes => {
  3393. console.log(loginRes)
  3394. uni.getUserInfo({
  3395. provider: provider,
  3396. success: (infoRes)=> {
  3397. // uni.showToast({
  3398. // title: '处理中...',
  3399. // icon: 'loading'
  3400. // });
  3401. //console.log(infoRes,'infoRes')
  3402. loginByMiniApp({
  3403. code: loginRes.code,
  3404. encryptedData:infoRes.encryptedData,
  3405. iv:infoRes.iv,
  3406. appId:wx.getAccountInfoSync().miniProgram.appId,
  3407. }).then(res=>{
  3408. //uni.hideLoading();
  3409. if (res.code == 200) {
  3410. uni.setStorageSync('userInfo',JSON.stringify(res.user));
  3411. } else {
  3412. uni.showToast({
  3413. title: res.msg,
  3414. icon: 'none'
  3415. });
  3416. }
  3417. }).catch(err=>{
  3418. //uni.hideLoading();
  3419. uni.showToast({
  3420. icon:'none',
  3421. title: "登录失败,请重新登录",
  3422. });
  3423. });
  3424. }
  3425. });
  3426. }
  3427. })
  3428. },
  3429. initExpiration(resMsg,resCode) {
  3430. if(resCode==401) return;
  3431. this.resMsg = resMsg
  3432. this.resCode = resCode
  3433. this.showExpiration = true
  3434. },
  3435. closeKFPop() {
  3436. this.$refs.kfPopup.close()
  3437. },
  3438. getFinishCourseVideo() {
  3439. if (!this.playTime || this.isAddKf!=1 ||!this.isLogin) return
  3440. // {videoId: this.videoId,duration:this.playTime}
  3441. const param = {
  3442. duration: this.playTime,
  3443. ...this.urlOption
  3444. }
  3445. getFinishCourseVideo(param)
  3446. },
  3447. // 每十分钟获得积分
  3448. getIntegralByH5Video() {
  3449. if(this.isAddKf!=1||!this.isLogin) return
  3450. const param = {
  3451. duration: this.playTime,
  3452. ...this.urlOption
  3453. }
  3454. getIntegralByH5Video(param).then(res => {
  3455. if (res.code == 200) {
  3456. uni.showToast({
  3457. title: "积分+10",
  3458. icon: "none"
  3459. })
  3460. }
  3461. })
  3462. },
  3463. progressChange(e) {
  3464. this.bufferRate = Math.ceil(e.detail.buffered)
  3465. },
  3466. // 缓冲
  3467. getInternetTraffic() {
  3468. if(!this.isLogin||this.isAddKf!=1) return
  3469. const playVideoTime = Math.ceil(this.playTime / this.duration * 100) // 播放百分比
  3470. if(this.bufferRate == 0 || this.bufferRate < playVideoTime) {
  3471. this.bufferRate = playVideoTime
  3472. }
  3473. if(this.bufferRate == 0 || Number(this.bufferRate.toFixed(2)) == 0) return
  3474. const param = {
  3475. ...this.urlOption,
  3476. uuId: dayjs().format('YYYYMMDD') + this.uuId,
  3477. duration: this.playTime,
  3478. bufferRate: Number(this.bufferRate.toFixed(2)),
  3479. }
  3480. if(!param.bufferRate) return
  3481. getInternetTraffic(param)
  3482. },
  3483. getErrMsg(err) {
  3484. let msgerr = {
  3485. videoUrl: this.videoUrl,
  3486. lineIndex: this.lineIndex,
  3487. errTime: new Date(),
  3488. ip: this.ip,
  3489. errMsg: err
  3490. }
  3491. getErrMsg({
  3492. msg: JSON.stringify(msgerr)
  3493. })
  3494. },
  3495. goLogin(data) {
  3496. console.log(this.isSpare,'this.isSpare')
  3497. if(data || this.isSpare==1) {
  3498. this.loginFsUserWx(data)
  3499. return
  3500. }
  3501. let provider = 'weixin'
  3502. uni.login({
  3503. provider: provider,
  3504. success: async loginRes => {
  3505. uni.getUserInfo({
  3506. provider: provider,
  3507. success: (infoRes)=> {
  3508. uni.showToast({
  3509. title: '处理中...',
  3510. icon: 'loading'
  3511. });
  3512. loginByMp({
  3513. code: loginRes.code,
  3514. encryptedData:infoRes.encryptedData,
  3515. iv:infoRes.iv,
  3516. appId: this.appid
  3517. }).then(res=>{
  3518. uni.hideLoading();
  3519. if (res.code == 200) {
  3520. this.$store.commit('setCoureLogin', 1);
  3521. uni.setStorageSync(TOKEN_KEYAuto, res.token);
  3522. uni.setStorageSync('auto_userInfo', JSON.stringify(res.user));
  3523. this.user = res.user
  3524. this.isLogin = true
  3525. console.log("TOKEN_KEYAuto",TOKEN_KEYAuto)
  3526. this.getIsAddKf()
  3527. } else {
  3528. uni.showToast({
  3529. title: res.msg,
  3530. icon: 'none'
  3531. });
  3532. }
  3533. }).catch(err=>{
  3534. uni.hideLoading();
  3535. uni.showToast({
  3536. icon:'none',
  3537. title: "登录失败,请重新登录",
  3538. });
  3539. });
  3540. }
  3541. });
  3542. }
  3543. })
  3544. },
  3545. getLink() {
  3546. let that = this;
  3547. this.msg = ''
  3548. if(this.isOpen==1) {
  3549. if (this.isLogin && this.isAddKf == 1) {
  3550. this.getH5CourseVideoDetails()
  3551. }
  3552. if (this.videoId &&this.isAddKf != 1) {
  3553. this.$isLoginCourseAuto().then(
  3554. isLogin => {
  3555. this.isLogin = isLogin
  3556. if(isLogin){
  3557. this.getIsAddKf()
  3558. } else {
  3559. this.goLogin()
  3560. }
  3561. },
  3562. rej => {}
  3563. );
  3564. }
  3565. return
  3566. }
  3567. getRealLink({sortLink:this.sortLink}).then(res=>{
  3568. if(res.code == 200) {
  3569. this.isExpire = false
  3570. // 如果响应中包含真实链接,则跳转到真实链接
  3571. // window.location.href = res.realLink +"&sortLink="+this.sortLink+"&code="+this.code+"&time="+new Date().getTime()
  3572. if (this.isLogin && this.isAddKf == 1) {
  3573. this.getH5CourseVideoDetails()
  3574. }
  3575. if (this.videoId &&this.isAddKf != 1) {
  3576. this.$isLoginCourseAuto().then(
  3577. isLogin => {
  3578. this.isLogin = isLogin
  3579. if(isLogin){
  3580. this.getIsAddKf()
  3581. } else {
  3582. this.goLogin()
  3583. }
  3584. },
  3585. rej => {}
  3586. );
  3587. }
  3588. } else {
  3589. this.isExpire = true
  3590. this.msg = '课程已过期或链接无效'
  3591. uni.showToast({
  3592. title: '课程已过期或链接无效',
  3593. icon: 'none'
  3594. });
  3595. }
  3596. }).catch(err=>{
  3597. this.isExpire = true
  3598. this.msg = '发生错误,请稍后再试'
  3599. uni.showToast({
  3600. title: '发生错误,请稍后再试',
  3601. icon: 'none'
  3602. });
  3603. })
  3604. },
  3605. /**
  3606. * 节流原理:在一定时间内,只能触发一次
  3607. *
  3608. * @param {Function} func 要执行的回调函数
  3609. * @param {Number} wait 延时的时间
  3610. * @param {Boolean} immediate 是否立即执行
  3611. * @return null
  3612. */
  3613. throttle(func, wait = 1000, immediate = true) {
  3614. if (immediate) {
  3615. if (!this.flag) {
  3616. this.flag = true
  3617. // 如果是立即执行,则在wait毫秒内开始时执行
  3618. typeof func === 'function' && func()
  3619. this.timer = setTimeout(() => {
  3620. this.flag = false
  3621. }, wait)
  3622. }
  3623. } else if (!this.flag) {
  3624. this.flag = true
  3625. // 如果是非立即执行,则在wait毫秒内的结束处执行
  3626. this.timer = setTimeout(() => {
  3627. this.flag = false
  3628. typeof func === 'function' && func()
  3629. }, wait)
  3630. }
  3631. },
  3632. // 弹幕
  3633. openDanmu(type) {
  3634. this.openDanmuType = type
  3635. this.inputText = ''
  3636. if(type == 1) {
  3637. this.player.exitFullScreen()
  3638. }
  3639. this.$refs.danmuPopup.open()
  3640. },
  3641. changeShowPopup(val) {
  3642. this.focus = val.show
  3643. },
  3644. switchDanmu() {
  3645. this.showDanmu = this.showDanmu == 1 ? 0:1
  3646. if(this.showDanmu == 0&&this.$refs.danmuBox) {
  3647. this.$refs.danmuPopup.close()
  3648. this.activeDanmus = []
  3649. this.$refs.danmuBox.activeDanmus = []
  3650. this.$refs.danmuBox.initTracks()
  3651. }
  3652. },
  3653. getScrollTop(res) {
  3654. if(this.currentTab == 2) {
  3655. this.scrollTop = res
  3656. } else {
  3657. this.scrollTop = 0
  3658. }
  3659. },
  3660. handleTab(index) {
  3661. this.currentTab = index
  3662. if(this.currentTab==2) {
  3663. if(this.$refs.commentBox) {
  3664. this.$refs.commentBox.msgs = []
  3665. this.$refs.commentBox.pageNum = 1
  3666. this.$refs.commentBox.getCommentsFun()
  3667. }
  3668. } else {
  3669. setTimeout(()=>{
  3670. this.scrollTop = 0
  3671. },100)
  3672. }
  3673. },
  3674. handleRefresher() {
  3675. this.triggered = true;
  3676. if (!this.isMore&&this.currentTab==2&&this.openCommentStatus==1) {
  3677. this.$nextTick(()=>{
  3678. this.$refs.commentBox&&this.$refs.commentBox.getCommentsFun()
  3679. })
  3680. }
  3681. setTimeout(() => {
  3682. this.triggered = false;
  3683. }, 500);
  3684. },
  3685. getMore(val) {
  3686. this.triggered = false;
  3687. this.isMore = val == 1
  3688. },
  3689. handleChatInput() {
  3690. this.inputText = this.inputText.trim()
  3691. if (this.inputText == "" || this.inputText.trim() == "") {
  3692. uni.showToast({
  3693. title: '请输入评论',
  3694. icon: "none"
  3695. })
  3696. return;
  3697. }
  3698. if(this.openCommentStatus==1) {
  3699. this.$refs.commentBox&&this.$refs.commentBox.handleInput(this.inputText)
  3700. } else if(this.openCommentStatus==2) {
  3701. this.$refs.danmuBox&&this.$refs.danmuBox.handleInput(this.inputText)
  3702. }
  3703. },
  3704. setInputText() {
  3705. this.inputText = ""
  3706. if(this.openCommentStatus==2) {
  3707. this.$refs.danmuPopup.close()
  3708. }
  3709. },
  3710. getActiveDanmus(val) {
  3711. this.activeDanmus = val.map(item=>({
  3712. ...item,
  3713. danmustyle: {
  3714. top: item.top + 'px',
  3715. ...item.style,
  3716. 'animation-duration': '8s'
  3717. }
  3718. }))
  3719. },
  3720. animationend(moveItem, i) {
  3721. // 移除动画结束的弹幕(性能优化)
  3722. if(this.openCommentStatus==2) {
  3723. this.$refs.danmuBox&&this.$refs.danmuBox.animationend(moveItem, i)
  3724. }
  3725. },
  3726. navback() {
  3727. const pages = getCurrentPages(); // 获取当前页面栈
  3728. if (pages.length > 1) {
  3729. uni.navigateBack(); // 有上一页才返回
  3730. } else {
  3731. // 如果是首页,跳转到某个默认页面(如首页)
  3732. uni.reLaunch({ url: '/pages/index/index' }); // 或者用 switchTab 如果是 tabBar 页面
  3733. }
  3734. },
  3735. feedback() {
  3736. const userId = this.user.userId || ''
  3737. const courseId = this.urlOption.courseId || ''
  3738. const videoId = this.urlOption.videoId || ''
  3739. const companyId = this.urlOption.companyId || ''
  3740. const companyUserId = this.urlOption.companyUserId || ''
  3741. uni.navigateTo({
  3742. url: './feedback?userId='+userId+'&courseId='+courseId+'&videoId='+videoId+'&companyId='+companyId+'&companyUserId='+companyUserId
  3743. })
  3744. },
  3745. // 公开课登录\备用登录
  3746. async loginFsUserWx(data){
  3747. if(data){
  3748. console.log('huoqu1222',data)
  3749. uni.showLoading({
  3750. title: '登录中'
  3751. })
  3752. uni.login({
  3753. provider: "weixin",
  3754. success: async loginRes => {
  3755. console.log(loginRes)
  3756. let code = loginRes.code // 获取开发code
  3757. handleFsUserWx({
  3758. code: code,
  3759. appId:this.appid,
  3760. userId:data.userId
  3761. })
  3762. .then( res => {
  3763. uni.hideLoading();
  3764. if(res.code==200){
  3765. console.log("loginFsUserWx:",res)
  3766. // this.userinfos=uni.getStorageSync('userinfos')
  3767. let token = uni.getStorageSync('TOKEN_WEXIN');
  3768. let user = uni.getStorageSync('userInfo')
  3769. // this.userInfo=uni.getStorageSync('userInfo');
  3770. // this.isLogin = true
  3771. uni.setStorageSync(TOKEN_KEYAuto, token);
  3772. uni.setStorageSync('auto_userInfo', JSON.stringify(user));
  3773. this.user = user
  3774. this.isLogin = true
  3775. this.getIsAddKf()
  3776. }else if(res.code==406){
  3777. uni.showToast({
  3778. icon:'none',
  3779. title: '该用户已成为其他销售会员',
  3780. });
  3781. }else{
  3782. uni.showToast({
  3783. icon:'none',
  3784. title: res.msg,
  3785. });
  3786. }
  3787. })
  3788. },
  3789. })
  3790. }else{
  3791. uni.setStorageSync('H5course',{
  3792. companyId: this.urlOption.companyId,
  3793. companyUserId:this.urlOption.companyUserId,
  3794. type: 1, //1自动,其他手动
  3795. })
  3796. await this.$store.dispatch('getWebviewUrl');
  3797. uni.navigateTo({
  3798. url:'/pages_course/webview?H5course='+uni.getStorageSync('H5course')
  3799. })
  3800. }
  3801. },
  3802. showBtnType(value) {
  3803. this.showBtn = value
  3804. this.getHeight()
  3805. },
  3806. // getHeight() {
  3807. // setTimeout(()=>{
  3808. // const query = uni.createSelectorQuery().in(this);
  3809. // query
  3810. // .select("#title-contentnav")
  3811. // .boundingClientRect((data) => {
  3812. // if(data) {
  3813. // const footerH = this.showBtn==0? 75: 0
  3814. // this.height =
  3815. // `calc(100vh - ${data.height}px - 420rpx - ${this.statusBarHeight}px - ${footerH}px - 88rpx)`
  3816. // }
  3817. // })
  3818. // .exec();
  3819. // },200)
  3820. // },
  3821. }
  3822. }
  3823. </script>
  3824. <style scoped>
  3825. .full-width-popup {
  3826. width: 100%;
  3827. }
  3828. </style>
  3829. <style lang="scss" scoped>
  3830. @mixin u-flex($flexD, $alignI, $justifyC) {
  3831. display: flex;
  3832. flex-direction: $flexD;
  3833. align-items: $alignI;
  3834. justify-content: $justifyC;
  3835. }
  3836. .contact-btn {
  3837. display: inline-block;
  3838. position: absolute;
  3839. top: 0;
  3840. left: 0;
  3841. width: 100%;
  3842. height: 100%;
  3843. opacity: 0;
  3844. }
  3845. .notice-box{
  3846. padding: 12rpx 18rpx;
  3847. background: #FFF8F8;
  3848. display: flex;
  3849. align-items: center;
  3850. image{
  3851. width:40rpx;
  3852. height:40rpx;
  3853. margin-right: 10rpx;
  3854. flex-shrink: 0;
  3855. }
  3856. .notice-marquee-wrap{
  3857. flex: 1;
  3858. min-width: 0;
  3859. overflow: hidden;
  3860. display: flex;
  3861. align-items: center;
  3862. }
  3863. .notice-marquee-track{
  3864. display: inline-flex;
  3865. white-space: nowrap;
  3866. animation: notice-marquee-scroll 20s linear infinite;
  3867. }
  3868. .notice-text{
  3869. flex-shrink: 0;
  3870. padding-right: 60rpx;
  3871. font-family: PingFangSC, PingFang SC;
  3872. font-weight: 600;
  3873. font-size: 32rpx;
  3874. line-height: 44rpx;
  3875. color: #FF0C4D;
  3876. display: flex;
  3877. }
  3878. }
  3879. @keyframes notice-marquee-scroll{
  3880. 0%{ transform: translateX(0); }
  3881. 100%{ transform: translateX(-50%); }
  3882. }
  3883. .vip-order-cover-wrap {
  3884. position: fixed;
  3885. left: 10px;
  3886. bottom:40px;
  3887. height: 26px;
  3888. z-index: 999;
  3889. display: inline-block;
  3890. overflow: visible;
  3891. pointer-events: none;
  3892. width: 200px;
  3893. }
  3894. .vip-order-cover-wrap--triple {
  3895. height: 110px;
  3896. }
  3897. .vip-order-cover-triple-panel {
  3898. display: flex;
  3899. align-items: flex-start;
  3900. flex-direction: column;
  3901. position: absolute;
  3902. left: 0;
  3903. right: auto;
  3904. top: 0;
  3905. }
  3906. .vip-order-cover-triple-current {
  3907. z-index: 1;
  3908. }
  3909. .vip-order-cover-triple-incoming {
  3910. z-index: 2;
  3911. }
  3912. .vip-order-cover-item {
  3913. position: absolute;
  3914. left: 0;
  3915. right: auto;
  3916. top: 0;
  3917. display: inline-flex;
  3918. align-items: center;
  3919. background: rgba(0,0,0,0.35);
  3920. border-radius: 26rpx;
  3921. text-align: center;
  3922. font-family: PingFangSC, PingFang SC;
  3923. font-size: 16px;
  3924. height: 26px;
  3925. line-height: 26px;
  3926. color: #fff;
  3927. font-weight: 400;
  3928. box-sizing: border-box;
  3929. }
  3930. .vip-order-cover-triple-item {
  3931. position: relative;
  3932. top: auto;
  3933. margin-bottom: 8px;
  3934. }
  3935. .vip-order-cover-triple-item:last-child {
  3936. margin-bottom: 0;
  3937. }
  3938. .vip-order-triple-enter {
  3939. animation: vip-order-triple-enter-up 0.46s ease-out forwards;
  3940. }
  3941. .vip-order-triple-leave {
  3942. animation: vip-order-triple-leave-up 0.46s ease-in forwards;
  3943. }
  3944. @keyframes vip-order-triple-enter-up {
  3945. 0% {
  3946. opacity: 0;
  3947. transform: translateY(38px);
  3948. }
  3949. 100% {
  3950. opacity: 1;
  3951. transform: translateY(0);
  3952. }
  3953. }
  3954. @keyframes vip-order-triple-leave-up {
  3955. 0% {
  3956. opacity: 1;
  3957. transform: translateY(0);
  3958. }
  3959. 78% {
  3960. opacity: 1;
  3961. transform: translateY(-34px);
  3962. }
  3963. 100% {
  3964. opacity: 0;
  3965. transform: translateY(-38px);
  3966. }
  3967. }
  3968. .vip-order-cover-content {
  3969. display: flex;
  3970. align-items: center;
  3971. flex-wrap: nowrap;
  3972. white-space: nowrap;
  3973. overflow: visible;
  3974. justify-content: flex-start;
  3975. height: 26px;
  3976. line-height: 26px;
  3977. padding:0 8px;
  3978. }
  3979. .vip-order-seg {
  3980. display: inline-block;
  3981. }
  3982. .vip-order-seg-user {
  3983. color: #fff;
  3984. }
  3985. .vip-order-seg-time {
  3986. margin-left: 6px;
  3987. }
  3988. .vip-order-seg-suffix {
  3989. color: #fff;
  3990. }
  3991. .vip-order-cover-self {
  3992. background: rgba(230,0,26,0.5);
  3993. color: #fff;
  3994. }
  3995. .vip-order-cover-self .vip-order-seg-user {
  3996. color: #fff;
  3997. }
  3998. .vip-order-cover-self .vip-order-seg-time {
  3999. color: #fff;
  4000. margin-left: 6px;
  4001. }
  4002. .vip-order-cover-self .vip-order-seg-suffix {
  4003. color: #fff;
  4004. }
  4005. .vip-order-enter {
  4006. animation: vip-order-enter-up 0.46s ease-out forwards;
  4007. }
  4008. .vip-order-leave {
  4009. animation: vip-order-leave-up 0.46s ease-in forwards;
  4010. }
  4011. @keyframes vip-order-enter-up {
  4012. 0% {
  4013. opacity: 0;
  4014. transform: translateY(24px);
  4015. }
  4016. 100% {
  4017. opacity: 1;
  4018. transform: translateY(0);
  4019. }
  4020. }
  4021. @keyframes vip-order-leave-up {
  4022. 0% {
  4023. opacity: 1;
  4024. transform: translateY(0);
  4025. }
  4026. 100% {
  4027. opacity: 0;
  4028. transform: translateY(-24px);
  4029. }
  4030. }
  4031. .footer-tips {
  4032. position: fixed;
  4033. width: 100%;
  4034. bottom: 144rpx;
  4035. text-align: center;
  4036. font-family: PingFang SC, PingFang SC;
  4037. font-weight: 500;
  4038. font-size: 12px;
  4039. color: #bbb;
  4040. transform: scale(0.8);
  4041. }
  4042. .btns {
  4043. //position: relative;
  4044. width: 100%;
  4045. height: 96rpx;
  4046. display: flex;
  4047. align-items: center;
  4048. justify-content: space-between;
  4049. .rating-msg-input-shell {
  4050. flex: 1;
  4051. min-width: 0;
  4052. display: flex;
  4053. align-items: center;
  4054. background: #f5f5f5;
  4055. border-radius: 48rpx;
  4056. padding: 24rpx 30rpx;
  4057. box-sizing: border-box;
  4058. }
  4059. .rating-msg-media-pill {
  4060. position: relative;
  4061. flex-shrink: 0;
  4062. width: 64rpx;
  4063. height: 64rpx;
  4064. margin-right: 16rpx;
  4065. border-radius: 12rpx;
  4066. overflow: hidden;
  4067. }
  4068. .rating-msg-media-thumb {
  4069. width: 100%;
  4070. height: 100%;
  4071. display: block;
  4072. }
  4073. .rating-msg-media-badge {
  4074. position: absolute;
  4075. left: 0;
  4076. right: 0;
  4077. bottom: 0;
  4078. font-size: 18rpx;
  4079. color: #fff;
  4080. text-align: center;
  4081. line-height: 1.4;
  4082. background: rgba(0, 0, 0, 0.45);
  4083. }
  4084. .rating-msg-media-x {
  4085. position: absolute;
  4086. top: -4rpx;
  4087. right: -4rpx;
  4088. width: 32rpx;
  4089. height: 32rpx;
  4090. line-height: 32rpx;
  4091. text-align: center;
  4092. font-size: 28rpx;
  4093. color: #fff;
  4094. background: rgba(0, 0, 0, 0.5);
  4095. border-radius: 50%;
  4096. }
  4097. .rating-msg-input {
  4098. flex: 1;
  4099. min-width: 0;
  4100. font-size: 32rpx;
  4101. color: rgba(0, 0, 0, 0.85);
  4102. height: 48rpx;
  4103. line-height: 48rpx;
  4104. }
  4105. .rating-msg-input-trigger {
  4106. overflow: hidden;
  4107. }
  4108. .rating-msg-input-trigger-text {
  4109. display: block;
  4110. font-size: 32rpx;
  4111. overflow: hidden;
  4112. text-overflow: ellipsis;
  4113. white-space: nowrap;
  4114. color: rgba(0, 0, 0, 0.85);
  4115. &.is-ph {
  4116. color: rgba(0, 0, 0, 0.28);
  4117. }
  4118. }
  4119. .rating-msg-input-icon {
  4120. flex-shrink: 0;
  4121. padding-left:24rpx;
  4122. display:flex;
  4123. image{
  4124. width:40rpx;
  4125. height:40rpx
  4126. }
  4127. }
  4128. .author-btn {
  4129. // z-index: 100;
  4130. // position: absolute;
  4131. // width: 100%;
  4132. flex:1;
  4133. height: 96rpx;
  4134. background: linear-gradient( 135deg, #FF5267 0%, #FF233C 100%);
  4135. border-radius: 50rpx;
  4136. font-family: PingFangSC, PingFang SC;
  4137. font-weight: 600;
  4138. font-style: normal;
  4139. font-size: 40rpx;
  4140. color: #FFFFFF;
  4141. text-align: center;
  4142. line-height: 96rpx;
  4143. display: flex;
  4144. align-items: center;
  4145. justify-content: center;
  4146. image{
  4147. margin-left: 4rpx;
  4148. width: 40rpx;
  4149. height: 40rpx;
  4150. }
  4151. }
  4152. .author-cart{
  4153. padding-left: 40rpx;
  4154. display: flex;
  4155. align-items: center;
  4156. .more-box{
  4157. display: flex;
  4158. align-items: center;
  4159. flex-direction: column;
  4160. .tips{
  4161. font-family: PingFangSC, PingFang SC;
  4162. font-weight: 600;
  4163. font-size: 32rpx;
  4164. color: #222222;
  4165. line-height: 44rpx;
  4166. text-align: center;
  4167. margin-top: 12rpx;
  4168. }
  4169. image{
  4170. width: 80rpx;
  4171. height: 80rpx;
  4172. }
  4173. &:last-child{
  4174. margin-left: 40rpx;
  4175. }
  4176. }
  4177. .xianshi{
  4178. position: absolute;
  4179. z-index: 10;
  4180. right:20rpx;
  4181. bottom: 180rpx;
  4182. width: 214rpx;
  4183. height:90rpx;
  4184. }
  4185. }
  4186. }
  4187. .textOne {
  4188. overflow: hidden;
  4189. white-space: nowrap;
  4190. text-overflow: ellipsis;
  4191. }
  4192. .textTwo {
  4193. overflow: hidden;
  4194. text-overflow: ellipsis;
  4195. display: -webkit-box;
  4196. -webkit-line-clamp: 2;
  4197. -webkit-box-orient: vertical;
  4198. }
  4199. .header-nav {
  4200. height: 88rpx;
  4201. @include u-flex(row, center, flex-start);
  4202. overflow: hidden;
  4203. background-color: #fff;
  4204. box-sizing: border-box;
  4205. .header-title {
  4206. text-align: center;
  4207. overflow: hidden;
  4208. white-space: nowrap;
  4209. text-overflow: ellipsis;
  4210. padding: 0 10rpx 0 100rpx;
  4211. font-family: PingFang SC,PingFang SC;
  4212. font-weight: 500;
  4213. font-size: 15px;
  4214. color: #000;
  4215. box-sizing: border-box;
  4216. }
  4217. }
  4218. .reward-list {
  4219. width: 100%;
  4220. margin-top: 20rpx;
  4221. margin-bottom: -40rpx;
  4222. &-group {
  4223. font-family: PingFang SC, PingFang SC;
  4224. font-weight: 400;
  4225. font-size: 14px;
  4226. color: #222222;
  4227. @include u-flex(row, center, center);
  4228. }
  4229. &-option {
  4230. @include u-flex(row, center, flex-start);
  4231. &:first-child {
  4232. margin-right: 40rpx;
  4233. }
  4234. }
  4235. }
  4236. .err {
  4237. color: #f56c6c !important;
  4238. }
  4239. .kfqrcode-box {
  4240. background-color: #fff;
  4241. border-radius: 16rpx;
  4242. max-width: 560rpx;
  4243. padding: 60rpx 40rpx;
  4244. margin-top: -100rpx;
  4245. box-sizing: border-box;
  4246. @include u-flex(column, center, flex-start);
  4247. font-family: PingFang SC, PingFang SC;
  4248. font-weight: 400;
  4249. font-size: 34rpx;
  4250. color: #222;
  4251. position: relative;
  4252. text-align: center;
  4253. .kfqrcode {
  4254. height: 460rpx;
  4255. width: 460rpx;
  4256. }
  4257. }
  4258. .kfqrcode-close {
  4259. width: 64rpx;
  4260. height: 64rpx;
  4261. position: absolute;
  4262. bottom: -100rpx;
  4263. left: 50%;
  4264. transform: translateX(-50%);
  4265. }
  4266. .tipsPopup-mask {
  4267. position: relative;
  4268. width: 560rpx;
  4269. background-color: transparent;
  4270. .red_envelope_top {
  4271. width: 480rpx;
  4272. height: 360rpx;
  4273. margin: 0 auto;
  4274. display: inherit;
  4275. }
  4276. }
  4277. .tipsPopup-btn-box {
  4278. width: 456rpx;
  4279. height: 104rpx;
  4280. padding: 4rpx;
  4281. box-sizing: border-box;
  4282. background: linear-gradient(180deg, rgba(252, 209, 94, 1), rgba(254, 253, 251, 1));
  4283. border-radius: 52rpx;
  4284. }
  4285. .tipsPopup-btn {
  4286. width: 100%;
  4287. height: 100%;
  4288. background: linear-gradient(180deg, #FF9F22 0%, #FA1E05 100%);
  4289. border-radius: 52rpx 52rpx 52rpx 52rpx;
  4290. font-family: PingFang SC, PingFang SC;
  4291. font-weight: 500;
  4292. font-size: 36rpx;
  4293. color: #FFFFFF;
  4294. line-height: 96rpx;
  4295. text-align: center;
  4296. }
  4297. .tipsPopup {
  4298. width: 560rpx;
  4299. padding: 12rpx;
  4300. margin-top: -72rpx;
  4301. box-sizing: border-box;
  4302. background: linear-gradient(180deg, #FFFBEF 0%, #FFFFF5 43%, #F5EAC2 100%);
  4303. border-radius: 32rpx 32rpx 32rpx 32rpx;
  4304. position: relative;
  4305. &-close {
  4306. width: 64rpx;
  4307. height: 64rpx;
  4308. position: absolute;
  4309. right: 0;
  4310. top: -188rpx;
  4311. }
  4312. &-line {
  4313. padding: 3rpx;
  4314. box-sizing: border-box;
  4315. background: linear-gradient(180deg, rgba(247, 245, 220, 1), rgba(250, 220, 157, 1));
  4316. border-radius: 24rpx;
  4317. }
  4318. &-box {
  4319. padding: 0 40rpx 40rpx 40rpx;
  4320. box-sizing: border-box;
  4321. background: linear-gradient(180deg, #FFFBEF 0%, #FFFFF5 43%, #F5EAC2 100%);
  4322. border-radius: 24rpx;
  4323. @include u-flex(column, center, flex-start);
  4324. }
  4325. &-head {
  4326. @include u-flex(row, center, center);
  4327. &-title {
  4328. width: 364rpx;
  4329. height: auto;
  4330. margin-top: -22rpx;
  4331. }
  4332. }
  4333. &-content {
  4334. margin: 48rpx 0;
  4335. font-family: PingFang SC, PingFang SC;
  4336. font-weight: 500;
  4337. font-size: 32rpx;
  4338. color: #222222;
  4339. text-align: center;
  4340. &-title {
  4341. margin-bottom: 26rpx;
  4342. font-weight: 600;
  4343. font-size: 40rpx;
  4344. color: #FF5C03;
  4345. }
  4346. }
  4347. }
  4348. .video-controls-box {
  4349. width: 100%;
  4350. height: 420rpx;
  4351. overflow: hidden;
  4352. position: absolute;
  4353. bottom: 0;
  4354. left: 0;
  4355. z-index: 2;
  4356. background: rgba(0, 0, 0, 0.2);
  4357. .video-play {
  4358. height: 72rpx;
  4359. width: 72rpx;
  4360. position: absolute;
  4361. top: 50%;
  4362. left: 50%;
  4363. transform: translate(-50%, -50%);
  4364. }
  4365. }
  4366. .video-controls {
  4367. width: 100%;
  4368. height: 80rpx;
  4369. padding: 0 28rpx;
  4370. box-sizing: border-box;
  4371. position: absolute;
  4372. bottom: 0;
  4373. left: 0;
  4374. display: flex;
  4375. align-items: center;
  4376. justify-content: space-between;
  4377. background: linear-gradient(to top, #222 0%, transparent 80%);
  4378. .video-icon {
  4379. height: 44rpx;
  4380. width: 44rpx;
  4381. }
  4382. }
  4383. .rating-box{
  4384. position: relative;
  4385. padding:24rpx 0rpx 24rpx;
  4386. z-index:0;
  4387. height: calc(100vh - 488rpx);
  4388. .bg {
  4389. width: 100%;
  4390. position: absolute;
  4391. top: 0;
  4392. left: 0;
  4393. z-index: -1;
  4394. }
  4395. .tab-Box {
  4396. display: flex;
  4397. align-items: flex-end;
  4398. justify-content: space-around;
  4399. padding: 0 8rpx;
  4400. margin-bottom: -2rpx;
  4401. position: relative;
  4402. z-index: 3;
  4403. }
  4404. .tab-Box--single {
  4405. align-items: center;
  4406. justify-content: flex-start;
  4407. padding: 0 24rpx 0 16rpx;
  4408. margin-bottom: 30rpx;
  4409. }
  4410. .tab-item--single {
  4411. flex: 0 1 auto;
  4412. justify-content: flex-start;
  4413. padding: 0;
  4414. }
  4415. .tab-item__surface--single {
  4416. flex-direction: row !important;
  4417. align-items: center;
  4418. justify-content: flex-start;
  4419. width: auto;
  4420. max-width: 100%;
  4421. border-radius: 0;
  4422. image {
  4423. margin-top: 0!important;
  4424. margin-right: 16rpx;
  4425. flex-shrink: 0;
  4426. }
  4427. }
  4428. .tab-item__text--single {
  4429. padding-top: 0!important;
  4430. padding-bottom: 0!important;
  4431. text-align: left;
  4432. font-weight: 600 !important;
  4433. font-size: 40rpx !important;
  4434. line-height: 56rpx !important;
  4435. }
  4436. .tab-item {
  4437. display: flex;
  4438. justify-content: center;
  4439. min-width: 0;
  4440. padding: 0 6rpx;
  4441. }
  4442. .tab-item__surface {
  4443. width: 100%;
  4444. display: flex;
  4445. flex-direction: column;
  4446. align-items: center;
  4447. justify-content: flex-end;
  4448. box-sizing: border-box;
  4449. border-radius: 28rpx 28rpx 0 0;
  4450. transition: background-color 0.28s ease, box-shadow 0.28s ease, transform 0.22s ease;
  4451. image{
  4452. margin-top: 12rpx;
  4453. width: 40rpx;
  4454. height: 40rpx;
  4455. }
  4456. }
  4457. .tab-item--active .tab-item__surface {
  4458. background:url('https://bjzmky-1323137866.cos.ap-chongqing.myqcloud.com/video/active-bg.png') no-repeat center;
  4459. background-size: 100% 100%;
  4460. padding: 0 70rpx;
  4461. transform: translateY(-2rpx);
  4462. }
  4463. .tab-item__text {
  4464. padding-top: 10rpx;
  4465. padding-bottom: 12rpx;
  4466. font-family: PingFangSC, PingFang SC;
  4467. font-weight: 400;
  4468. font-size: 36rpx;
  4469. line-height: 50rpx;
  4470. color: #ff233c;
  4471. text-align: center;
  4472. }
  4473. .tab-item--active .tab-item__text{
  4474. font-weight: 600;
  4475. font-size: 40rpx;
  4476. }
  4477. .rating-ques-block + .rating-ques-block {
  4478. margin-top: 40rpx;
  4479. }
  4480. .rating-body--plain image {
  4481. width: 100%;
  4482. }
  4483. .rating-intro-cover-img {
  4484. width: 100%;
  4485. height: auto;
  4486. display: block;
  4487. }
  4488. .rating-msg-empty {
  4489. padding: 80rpx 24rpx;
  4490. text-align: center;
  4491. font-size: 28rpx;
  4492. color: rgba(0, 0, 0, 0.35);
  4493. }
  4494. .rating-body.rating-body--featured {
  4495. display: flex;
  4496. flex-direction: column;
  4497. overflow: hidden;
  4498. padding: 30rpx 24rpx 20rpx;
  4499. }
  4500. .rating-msg-scroll {
  4501. height:100%;
  4502. width: 100%;
  4503. box-sizing: border-box;
  4504. }
  4505. .rating-msg-scroll-anchor {
  4506. width: 100%;
  4507. height: 1px;
  4508. }
  4509. .rating-msg-load-more {
  4510. padding: 24rpx 0 32rpx;
  4511. text-align: center;
  4512. font-size: 26rpx;
  4513. color: rgba(0, 0, 0, 0.4);
  4514. }
  4515. .rating-msg-load-more--done {
  4516. color: rgba(0, 0, 0, 0.28);
  4517. }
  4518. .rating-msg-item {
  4519. display: flex;
  4520. flex-direction: row;
  4521. align-items: flex-start;
  4522. gap: 20rpx;
  4523. padding-bottom: 40rpx;
  4524. &:not(:first-child) {
  4525. padding-top: 8rpx;
  4526. }
  4527. }
  4528. .rating-msg-avatar-wrap {
  4529. flex-shrink: 0;
  4530. width: 64rpx;
  4531. }
  4532. .rating-msg-main {
  4533. flex: 1;
  4534. min-width: 0;
  4535. display: flex;
  4536. flex-direction: column;
  4537. align-items: stretch;
  4538. }
  4539. .rating-msg-avatar {
  4540. width: 64rpx;
  4541. height: 64rpx;
  4542. border-radius: 50%;
  4543. flex-shrink: 0;
  4544. background: #ffe4ec;
  4545. display: block;
  4546. }
  4547. .rating-msg-name {
  4548. font-size: 32rpx;
  4549. color: rgba(0,0,0,0.65);
  4550. font-family: PingFangSC, PingFang SC;
  4551. font-weight: 400;
  4552. line-height: 44rpx;
  4553. }
  4554. .rating-msg-text {
  4555. margin-top: 12rpx;
  4556. font-size: 28rpx;
  4557. line-height: 44rpx;
  4558. color: rgba(0, 0, 0, 0.85);
  4559. word-break: break-word;
  4560. }
  4561. .rating-msg-media {
  4562. margin-top: 12rpx;
  4563. overflow: hidden;
  4564. }
  4565. .rating-msg-media--video {
  4566. overflow: visible;
  4567. }
  4568. .rating-msg-video-wrap {
  4569. position: relative;
  4570. max-width: 320rpx;
  4571. }
  4572. .rating-msg-video {
  4573. width: 100%;
  4574. display: block;
  4575. max-height: 240rpx;
  4576. }
  4577. .rating-msg-video-play-cover {
  4578. position: absolute;
  4579. left: 0;
  4580. top: 0;
  4581. right: 0;
  4582. bottom: 0;
  4583. z-index: 2;
  4584. display: flex;
  4585. align-items: center;
  4586. justify-content: center;
  4587. background: rgba(0, 0, 0, 0.18);
  4588. }
  4589. .rating-msg-video-play-icon {
  4590. width: 88rpx;
  4591. height: 88rpx;
  4592. flex-shrink: 0;
  4593. }
  4594. .rating-msg-media--imgs {
  4595. display: flex;
  4596. flex-wrap: wrap;
  4597. justify-content: flex-start;
  4598. gap: 12rpx;
  4599. }
  4600. .rating-msg-img {
  4601. flex-shrink: 0;
  4602. max-width: 320rpx;
  4603. height: auto;
  4604. }
  4605. .rating-body{
  4606. position: relative;
  4607. z-index: 1;
  4608. padding:30rpx;
  4609. box-sizing: border-box;
  4610. background: #FFFFFF;
  4611. border-radius: 30rpx 30rpx;
  4612. height: calc(100% - 300rpx);
  4613. overflow-y: auto;
  4614. -webkit-overflow-scrolling: touch;
  4615. margin-top: 0;
  4616. .title{
  4617. font-family: PingFangSC, PingFang SC;
  4618. font-weight: 600;
  4619. font-size: 40rpx;
  4620. color: rgba(0,0,0,0.85);
  4621. line-height: 56rpx;
  4622. }
  4623. }
  4624. .sat-box{
  4625. display: grid;
  4626. grid-template-columns: repeat(2, 1fr);
  4627. gap: 28rpx 24rpx;
  4628. margin-top: 32rpx;
  4629. }
  4630. .sat{
  4631. display: flex;
  4632. flex-direction: column;
  4633. align-items: center;
  4634. justify-content: center;
  4635. text-align: center;
  4636. background: #F5F7FA;
  4637. padding: 20rpx;
  4638. border-radius: 20rpx;
  4639. transition: background 0.3s ease, box-shadow 0.3s ease;
  4640. .sat-img-wrap{
  4641. width: 100rpx;
  4642. height: 100rpx;
  4643. display: flex;
  4644. align-items: center;
  4645. justify-content: center;
  4646. will-change: transform;
  4647. image{
  4648. width: 100rpx;
  4649. height: 100rpx;
  4650. transition: transform 0.35s cubic-bezier(0.34, 1.3, 0.64, 1);
  4651. }
  4652. }
  4653. .sat-img-bounce{
  4654. animation: sat-rate-pop 0.48s cubic-bezier(0.34, 1.45, 0.64, 1);
  4655. }
  4656. text{
  4657. margin-top: 10rpx;
  4658. font-family: PingFangSC, PingFang SC;
  4659. font-weight: 400;
  4660. font-size: 36rpx;
  4661. line-height: 50rpx;
  4662. transition: color 0.32s ease, font-weight 0.28s ease, transform 0.28s ease;
  4663. color: rgba(0,0,0,0.42);
  4664. }
  4665. .active{
  4666. font-weight: 600;
  4667. color: #FF233C;
  4668. }
  4669. }
  4670. .sat.sat--active{
  4671. background: #FFEEF0;
  4672. //box-shadow: 0 4rpx 16rpx rgba(255, 92, 3, 0.12);
  4673. .sat-img-wrap image{
  4674. transform: scale(1.06);
  4675. }
  4676. }
  4677. }
  4678. .fc-media-actions-sheet {
  4679. display: flex;
  4680. flex-direction: column;
  4681. width: 100%;
  4682. box-sizing: border-box;
  4683. padding-bottom:68rpx;
  4684. }
  4685. .fc-media-actions-sheet-item {
  4686. padding: 30rpx 32rpx;
  4687. font-size: 32rpx;
  4688. color: #1e1e1e;
  4689. text-align: center;
  4690. border-bottom: 1rpx solid #f0f0f0;
  4691. }
  4692. .fc-media-actions-sheet-gap {
  4693. height: 16rpx;
  4694. background: #f3f5f9;
  4695. border-bottom: none;
  4696. }
  4697. .fc-media-actions-sheet-cancel {
  4698. border-bottom: none;
  4699. color: #666;
  4700. }
  4701. @keyframes sat-rate-pop{
  4702. 0%{ transform: scale(1); }
  4703. 28%{ transform: scale(1.2); }
  4704. 52%{ transform: scale(0.94); }
  4705. 76%{ transform: scale(1.06); }
  4706. 100%{ transform: scale(1); }
  4707. }
  4708. .errQuesbox {
  4709. width: 100%;
  4710. max-height: 260rpx;
  4711. overflow-y: auto;
  4712. margin-top: 24rpx;
  4713. font-family: PingFang SC, PingFang SC;
  4714. font-weight: 500;
  4715. font-size: 30rpx;
  4716. color: #222222;
  4717. &-item {
  4718. width: 100%;
  4719. height: 128rpx;
  4720. line-height: 128rpx;
  4721. margin-bottom: 24rpx;
  4722. padding: 0 30rpx;
  4723. box-sizing: border-box;
  4724. overflow: hidden;
  4725. background: #fff;
  4726. border-radius: 16rpx 16rpx 16rpx 16rpx;
  4727. position: relative;
  4728. &::after {
  4729. content: "题目";
  4730. min-width: 64rpx;
  4731. height: 36rpx;
  4732. padding: 0 12rpx;
  4733. line-height: 36rpx;
  4734. background: #FF5C03;
  4735. box-sizing: border-box;
  4736. border-radius: 0rpx 0rpx 16rpx 0rpx;
  4737. text-align: center;
  4738. font-family: PingFang SC, PingFang SC;
  4739. font-weight: 500;
  4740. font-size: 20rpx;
  4741. color: #fff;
  4742. position: absolute;
  4743. left: 0;
  4744. top: 0;
  4745. }
  4746. }
  4747. }
  4748. .bg {
  4749. background: #fff !important;
  4750. }
  4751. .answerPopup {
  4752. &-box {
  4753. width: 560rpx;
  4754. background: linear-gradient(180deg, #FFFAF6 0%, #FEECD8 100%);
  4755. border-radius: 32rpx 32rpx 32rpx 32rpx;
  4756. background-color: #fff;
  4757. font-weight: 400;
  4758. padding: 32rpx;
  4759. box-sizing: border-box;
  4760. position: relative;
  4761. @include u-flex(column, center, flex-start);
  4762. font-family: PingFang SC, PingFang SC;
  4763. font-weight: 400;
  4764. .tipimg {
  4765. width: 206rpx;
  4766. height: 206rpx;
  4767. margin-bottom: 16rpx;
  4768. }
  4769. }
  4770. &-title {
  4771. font-weight: 600;
  4772. font-size: 36rpx;
  4773. color: #222222;
  4774. }
  4775. &-desc {
  4776. margin-top: 10rpx;
  4777. font-size: 28rpx;
  4778. color: #757575;
  4779. }
  4780. &-btn {
  4781. width: 464rpx;
  4782. height: 84rpx;
  4783. margin-top: 54rpx;
  4784. margin-bottom: 16rpx;
  4785. background: #FF5C03;
  4786. border-radius: 42rpx;
  4787. font-weight: 500;
  4788. font-size: 32rpx;
  4789. color: #FFFFFF;
  4790. text-align: center;
  4791. line-height: 84rpx;
  4792. }
  4793. }
  4794. .popupbox {
  4795. width: 100%;
  4796. background-color: #fff;
  4797. border-radius: 16rpx 16rpx 0 0;
  4798. padding: 24rpx 32rpx;
  4799. position: relative;
  4800. &-head {
  4801. height: 60rpx;
  4802. margin-bottom: 30rpx;
  4803. text-align: center;
  4804. overflow-y: auto;
  4805. color: #414858;
  4806. font-size: 32rpx;
  4807. font-weight: bold;
  4808. position: relative;
  4809. .close-icon {
  4810. position: absolute;
  4811. right: 0;
  4812. top: 0;
  4813. height: 40rpx;
  4814. width: 40rpx;
  4815. }
  4816. }
  4817. &-content {
  4818. height: 20vh;
  4819. overflow-y: auto;
  4820. display: flex;
  4821. align-items: flex-start;
  4822. flex-wrap: wrap;
  4823. gap: 32rpx;
  4824. .line-item {
  4825. display: inline-block;
  4826. min-width: 200rpx;
  4827. min-height: 60rpx;
  4828. padding: 0 20rpx;
  4829. box-sizing: border-box;
  4830. border-radius: 50rpx;
  4831. overflow: hidden;
  4832. background-color: #f7f7f7;
  4833. text-align: center;
  4834. color: #414858;
  4835. font-size: 28rpx;
  4836. line-height: 60rpx;
  4837. }
  4838. .line-active {
  4839. color: #f56c6c !important;
  4840. background-color: #fef0f0 !important;
  4841. }
  4842. }
  4843. }
  4844. .content {
  4845. //padding-bottom: calc(var(--window-bottom));
  4846. .video-box {
  4847. width: 100%;
  4848. height: 420rpx;
  4849. overflow: hidden;
  4850. position: relative;
  4851. #myVideo {
  4852. width: 100%;
  4853. height: 100%;
  4854. }
  4855. }
  4856. .video-poster {
  4857. width: 100%;
  4858. height: 420rpx;
  4859. }
  4860. .miantitlebox {
  4861. padding: 30rpx 0;
  4862. border-bottom: 2rpx solid #F5F7FA;
  4863. font-family: PingFang SC, PingFang SC;
  4864. font-weight: 500;
  4865. font-size: 36rpx;
  4866. color: #222222;
  4867. }
  4868. .subtitlebox {
  4869. padding: 30rpx 0;
  4870. border-bottom: 2rpx solid #F5F7FA;
  4871. font-family: PingFang SC, PingFang SC;
  4872. font-weight: 500;
  4873. font-size: 36rpx;
  4874. color: #222222;
  4875. }
  4876. .title-content {
  4877. padding: 0 32rpx;
  4878. background-color: #fff;
  4879. font-size: 28rpx;
  4880. line-height: 1.6;
  4881. .title {
  4882. font-size: 36rpx;
  4883. font-weight: 500;
  4884. color: #414858;
  4885. }
  4886. .time-or-subtitle {
  4887. margin-top: 12rpx;
  4888. color: #666666;
  4889. }
  4890. }
  4891. .video-line {
  4892. min-width: 140rpx;
  4893. max-width: 200rpx;
  4894. height: 60rpx;
  4895. padding: 0 20rpx;
  4896. box-sizing: border-box;
  4897. border-radius: 50rpx 0 0 50rpx;
  4898. overflow: hidden;
  4899. background-color: #fff;
  4900. text-align: center;
  4901. color: #888;
  4902. font-size: 28rpx;
  4903. line-height: 60rpx;
  4904. display: inline-flex;
  4905. align-items: center;
  4906. justify-content: center;
  4907. position: fixed;
  4908. right: 0;
  4909. z-index: 9;
  4910. bottom: calc(var(--window-bottom) + 280rpx);
  4911. box-shadow: 0 4rpx 10rpx rgba(0, 0, 0, .12);
  4912. image {
  4913. flex-shrink: 0;
  4914. height: 34rpx;
  4915. width: 34rpx;
  4916. margin-right: 6rpx;
  4917. }
  4918. }
  4919. .danmu-line {
  4920. bottom: calc(var(--window-bottom) + 370rpx);
  4921. word-break: keep-all;
  4922. .set_image {
  4923. height: 40rpx;
  4924. width: 40rpx;
  4925. }
  4926. }
  4927. .fc-media-preview-mask {
  4928. position: fixed;
  4929. left: 0;
  4930. right: 0;
  4931. top: 0;
  4932. bottom: 0;
  4933. z-index: 10050;
  4934. background: rgba(0, 0, 0, 0.72);
  4935. display: flex;
  4936. align-items: center;
  4937. justify-content: center;
  4938. padding: 32rpx 24rpx calc(32rpx + env(safe-area-inset-bottom));
  4939. box-sizing: border-box;
  4940. }
  4941. .fc-media-preview-card {
  4942. width: 100%;
  4943. max-width: 630rpx;
  4944. overflow: hidden;
  4945. display: flex;
  4946. flex-direction: column;
  4947. align-items: center;
  4948. }
  4949. .fc-media-preview-img-box {
  4950. width: 100%;
  4951. height: 62vh;
  4952. max-height: 820rpx;
  4953. }
  4954. .fc-media-preview-slide {
  4955. width: 100%;
  4956. height: 100%;
  4957. display: flex;
  4958. align-items: center;
  4959. justify-content: center;
  4960. padding: 16rpx;
  4961. box-sizing: border-box;
  4962. }
  4963. .fc-media-preview-main-img {
  4964. width: 100%;
  4965. height: 100%;
  4966. max-height: 100%;
  4967. }
  4968. .fc-media-preview-video-box {
  4969. width: 100%;
  4970. height: 62vh;
  4971. max-height: 820rpx;
  4972. background: #000;
  4973. display: flex;
  4974. align-items: center;
  4975. justify-content: center;
  4976. }
  4977. .fc-media-preview-video {
  4978. width: 100%;
  4979. height: 100%;
  4980. }
  4981. .fc-media-preview-footer {
  4982. display: flex;
  4983. flex-direction: row;
  4984. align-items: center;
  4985. padding:30rpx;
  4986. }
  4987. .fc-media-preview-thumbs {
  4988. flex: 1;
  4989. min-width: 0;
  4990. height: 112rpx;
  4991. white-space: nowrap;
  4992. }
  4993. .fc-media-preview-thumbs-inner {
  4994. display: inline-flex;
  4995. flex-direction: row;
  4996. align-items: center;
  4997. gap: 16rpx;
  4998. padding-right: 8rpx;
  4999. }
  5000. .fc-media-preview-thumb {
  5001. width: 96rpx;
  5002. height: 96rpx;
  5003. flex-shrink: 0;
  5004. border-radius: 12rpx;
  5005. border: 3rpx solid transparent;
  5006. box-sizing: border-box;
  5007. background: #333;
  5008. }
  5009. .fc-media-preview-thumb--active {
  5010. border-color: #ff5c03;
  5011. }
  5012. .fc-media-preview-thumbs-spacer {
  5013. flex: 1;
  5014. min-height: 1rpx;
  5015. }
  5016. .fc-media-preview-close {
  5017. flex-shrink: 0;
  5018. display: flex;
  5019. align-items: center;
  5020. justify-content: center;
  5021. }
  5022. .featured-comment-composer-mask {
  5023. position: fixed;
  5024. left: 0;
  5025. right: 0;
  5026. top: 0;
  5027. bottom: 0;
  5028. background: rgba(0, 0, 0, 0.35);
  5029. z-index: 9998;
  5030. }
  5031. .featured-comment-composer-wrap {
  5032. position: fixed;
  5033. left: 0;
  5034. right: 0;
  5035. z-index: 9999;
  5036. background: #fff;
  5037. border-radius: 24rpx 24rpx 0 0;
  5038. padding: 20rpx 24rpx;
  5039. padding-bottom: calc(20rpx + env(safe-area-inset-bottom));
  5040. box-sizing: border-box;
  5041. box-shadow: 0 -4rpx 24rpx rgba(0, 0, 0, 0.06);
  5042. }
  5043. .featured-comment-composer-inner {
  5044. display: flex;
  5045. align-items: center;
  5046. gap: 24rpx;
  5047. }
  5048. .featured-comment-composer-input-row {
  5049. flex: 1;
  5050. min-width: 0;
  5051. display: flex;
  5052. align-items: flex-start;
  5053. background: #f5f5f5;
  5054. border-radius: 24rpx;
  5055. padding:20rpx;
  5056. box-sizing: border-box;
  5057. }
  5058. .fc-composer-input {
  5059. flex: 1;
  5060. min-width: 0;
  5061. font-size: 32rpx;
  5062. color: rgba(0, 0, 0, 0.85);
  5063. height:88rpx;
  5064. line-height: 44rpx;
  5065. }
  5066. .fc-composer-ph {
  5067. color: rgba(0, 0, 0, 0.28);
  5068. font-size: 32rpx;
  5069. }
  5070. .fc-composer-input-icon {
  5071. flex-shrink: 0;
  5072. margin-left: 12rpx;
  5073. display: flex;
  5074. align-items: center;
  5075. justify-content: center;
  5076. image {
  5077. width: 40rpx;
  5078. height: 40rpx;
  5079. }
  5080. }
  5081. .fc-composer-media-pill {
  5082. position: relative;
  5083. flex-shrink: 0;
  5084. width: 64rpx;
  5085. height: 64rpx;
  5086. border-radius: 12rpx;
  5087. overflow: hidden;
  5088. }
  5089. .fc-composer-media-thumb {
  5090. width: 100%;
  5091. height: 100%;
  5092. display: block;
  5093. }
  5094. .fc-composer-media-badge {
  5095. position: absolute;
  5096. left: 0;
  5097. right: 0;
  5098. bottom: 0;
  5099. font-size: 18rpx;
  5100. color: #fff;
  5101. text-align: center;
  5102. line-height: 1.4;
  5103. background: rgba(0, 0, 0, 0.45);
  5104. }
  5105. .fc-composer-media-x {
  5106. position: absolute;
  5107. top: -4rpx;
  5108. right: -4rpx;
  5109. width: 32rpx;
  5110. height: 32rpx;
  5111. line-height: 32rpx;
  5112. text-align: center;
  5113. font-size: 28rpx;
  5114. color: #fff;
  5115. background: rgba(0, 0, 0, 0.5);
  5116. border-radius: 50%;
  5117. }
  5118. .featured-comment-composer-publish {
  5119. flex-shrink: 0;
  5120. padding: 20rpx 40rpx;
  5121. background: linear-gradient( 135deg, #FF5267 0%, #FF233C 100%);
  5122. font-family: PingFangSC, PingFang SC;
  5123. font-weight: 600;
  5124. font-size: 40rpx;
  5125. color: #FFFFFF;
  5126. line-height: 56rpx;
  5127. text-align: center;
  5128. border-radius: 48rpx;
  5129. }
  5130. .footer {
  5131. //border-top: 1rpx solid #ededef;
  5132. background: #fff;
  5133. width: 100%;
  5134. position: fixed;
  5135. bottom: 0;
  5136. padding: 32rpx;
  5137. padding-bottom:68rpx;
  5138. box-sizing: border-box;
  5139. z-index: 0;
  5140. &-btn {
  5141. width: 100%;
  5142. height: 98rpx;
  5143. background: #FF5C03;
  5144. border-radius: 49rpx 49rpx 49rpx 49rpx;
  5145. line-height: 98rpx;
  5146. text-align: center;
  5147. font-family: PingFang SC, PingFang SC;
  5148. font-weight: 600;
  5149. font-size: 32rpx;
  5150. color: #FFFFFF;
  5151. @include u-flex(row, center, center);
  5152. &-img {
  5153. flex-shrink: 0;
  5154. width: 144rpx;
  5155. height: 144rpx;
  5156. margin-right: 8rpx;
  5157. margin-top: -24rpx;
  5158. }
  5159. }
  5160. &-btn-border {
  5161. position: relative;
  5162. &::after {
  5163. content: "";
  5164. background: linear-gradient(180deg, rgba(255, 255, 255, 0), rgba(255, 255, 255, 1));
  5165. position: absolute;
  5166. top: -2rpx;
  5167. left: 0;
  5168. height: 103rpx;
  5169. width: 100%;
  5170. z-index: -1;
  5171. border-radius: 49rpx 49rpx 49rpx 49rpx;
  5172. box-shadow: 0rpx 8rpx 11rpx 0rpx rgba(255, 92, 3, 0.3);
  5173. overflow: hidden;
  5174. }
  5175. }
  5176. }
  5177. }
  5178. .agreement {
  5179. display: inline-flex;
  5180. margin-top: 16rpx;
  5181. font-size: 24rpx;
  5182. color: #525252;
  5183. align-items: center;
  5184. justify-content: center;
  5185. }
  5186. .agreement {
  5187. display: inline-flex;
  5188. margin-top: 16rpx;
  5189. font-size: 24rpx;
  5190. color: #525252;
  5191. align-items: center;
  5192. justify-content: center;
  5193. }
  5194. .video-danmu-btnbox {
  5195. width: 50px;
  5196. height: 50px;
  5197. border-radius: 50%;
  5198. overflow: hidden;
  5199. position: absolute;
  5200. right: 10px;
  5201. bottom: calc(50% - 50px);
  5202. transform: translateY(-50%);
  5203. padding: 8px;
  5204. box-sizing: border-box;
  5205. }
  5206. .video-danmu-image {
  5207. width: 100%;
  5208. height: 100%;
  5209. }
  5210. .danmuPopup {
  5211. background-color: #fff;
  5212. padding-bottom: calc(var(--window-bottom) + 10px);
  5213. .u-border {
  5214. flex: 1;
  5215. @include u-flex(row,center,flex-start);
  5216. padding: 0 10rpx;
  5217. border-radius: 6px;
  5218. border: 1rpx solid #eee;
  5219. }
  5220. &-head {
  5221. width: 100%;
  5222. padding: 10px;
  5223. box-sizing: border-box;
  5224. overflow: hidden;
  5225. @include u-flex(row,center,flex-start);
  5226. .danmu-icon {
  5227. height: 24px;
  5228. width: 24px;
  5229. margin-right: 12px;
  5230. }
  5231. }
  5232. &-input {
  5233. flex: 1;
  5234. height: 35px;
  5235. }
  5236. &-send {
  5237. flex-shrink: 0;
  5238. height: 35px;
  5239. display: flex;
  5240. align-items: center;
  5241. justify-content: center;
  5242. padding: 5px 15px;
  5243. box-sizing: border-box;
  5244. background: #FF5C03 !important;
  5245. border-radius: 22px;
  5246. font-family: PingFang SC, PingFang SC;
  5247. font-weight: 500;
  5248. font-size: 15px;
  5249. color: #fff !important;
  5250. margin-left: 12px;
  5251. &::after {
  5252. border: none;
  5253. }
  5254. }
  5255. &-con {
  5256. background-color: #F5F7FA;
  5257. padding: 24px 12px 48px 12px;
  5258. font-family: PingFang SC, PingFang SC;
  5259. font-weight: 400;
  5260. font-size: 14px;
  5261. color: #757575;
  5262. }
  5263. }
  5264. .danmu-icon{
  5265. height: 24px;
  5266. width: 24px;
  5267. margin-right: 12px;
  5268. }
  5269. .logo {
  5270. display: inline-block;
  5271. width: 30px;
  5272. height: auto;
  5273. margin: 20px 0 0 10px;
  5274. pointer-events: none;
  5275. object-fit: cover;
  5276. }
  5277. .logo-full {
  5278. display: inline-block;
  5279. width: 40px;
  5280. height: auto;
  5281. margin: 50px 0 0 30px;
  5282. pointer-events: none;
  5283. object-fit: cover;
  5284. }
  5285. .tabbox {
  5286. @include u-flex(row, center, center);
  5287. border-bottom: 2rpx solid #F5F7FA;
  5288. height: 44px;
  5289. background-color: #fff;
  5290. view {
  5291. flex: 1;
  5292. padding: 20rpx 0;
  5293. margin-right: 40rpx;
  5294. text-align: center;
  5295. }
  5296. &-active {
  5297. position: relative;
  5298. &::after {
  5299. position: absolute;
  5300. bottom: 0;
  5301. left: 50%;
  5302. transform: translateX(-50%);
  5303. content: "";
  5304. width: 3rem;
  5305. border-bottom: 4px solid #FF5C03;
  5306. }
  5307. }
  5308. }
  5309. .chatinput {
  5310. position: fixed;
  5311. left: 32rpx;
  5312. right: 32rpx;
  5313. z-index: 999;
  5314. height: 96rpx;
  5315. background-color: green;
  5316. background: #FFFFFF;
  5317. box-shadow: 0rpx 8rpx 21rpx 0rpx rgba(0, 0, 0, 0.1);
  5318. border-radius: 24rpx 24rpx 24rpx 24rpx;
  5319. @include u-flex(row, center, center);
  5320. padding: 0 24rpx;
  5321. box-sizing: border-box;
  5322. .uni-input {
  5323. flex: 1;
  5324. margin-right: 32rpx;
  5325. font-size: 30rpx;
  5326. }
  5327. .send {
  5328. font-family: PingFang SC, PingFang SC;
  5329. font-weight: 400;
  5330. font-size: 28rpx;
  5331. color: #FFFFFF !important;
  5332. flex-shrink: 0;
  5333. padding: 0 20rpx;
  5334. height: 72rpx;
  5335. background: #FF5C03 !important;
  5336. border-radius: 8rpx 8rpx 8rpx 8rpx;
  5337. &::after {
  5338. border: none;
  5339. }
  5340. }
  5341. }
  5342. .answerTip {
  5343. position: fixed;
  5344. right: 0;
  5345. z-index: 9;
  5346. bottom: calc(var(--window-bottom) + 380rpx);
  5347. box-shadow: 0rpx 8rpx 21rpx 0rpx rgba(0, 0, 0, 0.1);
  5348. border-radius: 24rpx 24rpx 24rpx 24rpx;
  5349. background-color: #ff5c03;
  5350. color: #fff;
  5351. border-radius: 50%;
  5352. height: 90rpx;
  5353. width: 90rpx;
  5354. font-size: 25rpx;
  5355. text-align: center;
  5356. padding: 10rpx;
  5357. @include u-flex(column, center, center);
  5358. }
  5359. .danmu-item {
  5360. position: absolute;
  5361. top: 0;
  5362. white-space: nowrap;
  5363. font-size: 16px;
  5364. height: 20px;
  5365. display: inline-flex;
  5366. box-sizing: border-box;
  5367. align-items: center;
  5368. }
  5369. .danmuMove {
  5370. // animation: mymove 8s linear forwards;
  5371. // animation-duration: 8s;
  5372. animation-timing-function: linear;
  5373. animation-delay: 0s;
  5374. animation-iteration-count: 1;
  5375. animation-direction: normal;
  5376. animation-fill-mode: forwards;
  5377. animation-play-state: running;
  5378. animation-name: mymove;
  5379. will-change: transform;
  5380. }
  5381. @keyframes mymove {
  5382. from {
  5383. transform: translateX(100vw);
  5384. }
  5385. to {
  5386. transform: translateX(-100%);
  5387. }
  5388. }
  5389. .arrow-left-warning {
  5390. position: absolute;
  5391. left: 24rpx;
  5392. height: 88rpx;
  5393. overflow: hidden;
  5394. color: #888;
  5395. font-size: 24rpx;
  5396. @include u-flex(column, center, center);
  5397. image {
  5398. flex-shrink: 0;
  5399. height: 36rpx;
  5400. width: 36rpx;
  5401. }
  5402. }
  5403. // 完课积分倒计时遮罩
  5404. .progress-countdown {
  5405. position: fixed;
  5406. right: 6px;
  5407. top: 6px;
  5408. z-index: 99999999;
  5409. // width: 112px;
  5410. height: 50px;
  5411. background: rgba(0, 0, 0, 0.45);
  5412. display: flex;
  5413. align-items: center;
  5414. justify-content: space-around;
  5415. padding: 0 8px;
  5416. flex-direction: column;
  5417. border-radius: 6px;
  5418. box-sizing: border-box;
  5419. }
  5420. .progress-title {
  5421. font-size: 16px;
  5422. color: #FF233C;
  5423. font-weight: 600;
  5424. }
  5425. .progress-bar-bg {
  5426. width: 86px;
  5427. height: 6px;
  5428. background: rgba(255, 255, 255, 0.8);
  5429. border-radius: 3px;
  5430. overflow: hidden;
  5431. }
  5432. .progress-bar-fill {
  5433. height: 100%;
  5434. background: #FF233C;
  5435. border-radius: 3px;
  5436. transition: width 1s linear;
  5437. }
  5438. .progress-text {
  5439. white-space: nowrap;
  5440. color: #fff;
  5441. font-size: 13px;
  5442. display: flex;
  5443. align-items: flex-start;
  5444. justify-content: center;
  5445. }
  5446. .progress-text-label {
  5447. font-size: 12px;
  5448. margin-right: 2px;
  5449. }
  5450. // 购物车/更多弹窗
  5451. .more-actions-popup {
  5452. border-radius: 20rpx 0 0 20rpx;
  5453. padding: 30rpx;
  5454. display: flex;
  5455. justify-content: space-between;
  5456. .more-action-item {
  5457. display: flex;
  5458. flex-direction: column;
  5459. align-items: center;
  5460. .action-icon {
  5461. width: 48rpx;
  5462. height: 48rpx;
  5463. }
  5464. .action-label {
  5465. color: #1e1e1e;
  5466. text-align: center;
  5467. margin-top: 10rpx;
  5468. }
  5469. }
  5470. }
  5471. /* 商品卡片样式 */
  5472. .goods-cover {
  5473. /* cover-view 在 video 内使用 absolute 比 fixed 更稳定 */
  5474. position: absolute;
  5475. right: 12px;
  5476. width: 150px;
  5477. z-index: 99999;
  5478. border-radius: 10px;
  5479. overflow: visible;
  5480. bottom: 40px;
  5481. &-hot {
  5482. // margin-left: 15px;
  5483. margin-bottom: 15px;
  5484. display: flex;
  5485. flex-direction: row;
  5486. align-items: center;
  5487. position: relative;
  5488. }
  5489. &-hot-text {
  5490. position: absolute;
  5491. left:85px;
  5492. font-family: PingFangSC, PingFang SC;
  5493. font-weight: 600;
  5494. font-size: 20px;
  5495. color: #FFFFFF;
  5496. line-height: 28px;
  5497. text-align: justify;
  5498. }
  5499. &-inner {
  5500. display: flex;
  5501. flex-direction:column;
  5502. border-radius: 10px;
  5503. overflow: hidden;
  5504. background: #FFFFFF;
  5505. position: relative;
  5506. }
  5507. &-img {
  5508. width:150px;
  5509. height:150px;
  5510. flex-shrink: 0;
  5511. }
  5512. &-info {
  5513. flex: 1;
  5514. padding: 6px;
  5515. display: flex;
  5516. flex-direction: column;
  5517. justify-content: space-between;
  5518. }
  5519. &-title {
  5520. font-family: PingFangSC, PingFang SC;
  5521. font-weight: 600;
  5522. font-size: 16px;
  5523. color: #000000D9;
  5524. line-height: 22px;
  5525. overflow: hidden;
  5526. width: 150px; /* 必须给宽度 */
  5527. white-space: nowrap; /* 关键 */
  5528. text-overflow: ellipsis;
  5529. }
  5530. &-bottom {
  5531. margin-top: 6px;
  5532. display: flex;
  5533. flex-direction: row;
  5534. align-items: center;
  5535. }
  5536. &-tag {
  5537. position: relative;
  5538. width: 60px;
  5539. height: 22px;
  5540. }
  5541. &-tag-bg {
  5542. width: 100%;
  5543. height: 100%;
  5544. }
  5545. &-tag-text {
  5546. position: absolute;
  5547. left: 0;
  5548. top: 0;
  5549. width: 100%;
  5550. height: 100%;
  5551. text-align: center;
  5552. line-height: 22px;
  5553. font-size: 12px;
  5554. color: #FFFFFF;
  5555. font-weight: 500;
  5556. }
  5557. &-price{
  5558. margin-left: 7px;
  5559. display: flex;
  5560. align-items:baseline;
  5561. .unit{
  5562. font-family: PingFangSC, PingFang SC;
  5563. font-weight: 600;
  5564. font-size: 16px;
  5565. color: #FF233C;
  5566. }
  5567. .price{
  5568. font-family: PingFangSC, PingFang SC;
  5569. font-weight: 600;
  5570. font-size: 24px;
  5571. color: #FF233C;
  5572. }
  5573. }
  5574. .close-box{
  5575. position: absolute;
  5576. top:10px;
  5577. right:10px;
  5578. z-index:9;
  5579. cover-image{
  5580. width: 15px;
  5581. height: 15px;
  5582. }
  5583. }
  5584. }
  5585. // 商品卡片弹窗
  5586. .goods-card {
  5587. position: absolute;
  5588. right: 24rpx;
  5589. bottom: 160rpx;
  5590. z-index: 9999;
  5591. width: 300rpx;
  5592. background: #FFFFFF;
  5593. border-radius: 20rpx 20rpx 0rpx 0rpx;
  5594. overflow: visible;
  5595. &-hot {
  5596. position: absolute;
  5597. top: -66rpx;
  5598. left: 30rpx;
  5599. z-index: 11;
  5600. display: flex;
  5601. flex-direction: row;
  5602. align-items: center;
  5603. }
  5604. &-hot-flame {
  5605. font-size: 24rpx;
  5606. line-height: 1;
  5607. margin-right: 4rpx;
  5608. }
  5609. &-hot-label {
  5610. font-size: 24rpx;
  5611. font-weight: 600;
  5612. color: #FFFFFF;
  5613. line-height: 44rpx;
  5614. }
  5615. &-hot-count {
  5616. position: absolute;
  5617. left:130rpx;
  5618. font-family: PingFangSC, PingFang SC;
  5619. font-weight: 600;
  5620. font-size: 40rpx;
  5621. color: #FFFFFF;
  5622. line-height: 56rpx;
  5623. text-align: justify;
  5624. }
  5625. &-inner {
  5626. display: flex;
  5627. flex-direction: column;
  5628. border-radius: 20rpx 20rpx 0 0;
  5629. overflow: hidden;
  5630. }
  5631. &-img {
  5632. width: 300rpx;
  5633. height: 300rpx;
  5634. flex-shrink: 0;
  5635. }
  5636. &-info {
  5637. flex: 1;
  5638. padding: 12rpx;
  5639. display: flex;
  5640. flex-direction: column;
  5641. justify-content: space-between;
  5642. }
  5643. &-title {
  5644. font-family: PingFangSC, PingFangSC;
  5645. font-weight: 600;
  5646. font-size: 32rpx;
  5647. color: rgba(0, 0, 0, 0.85);
  5648. line-height: 44rpx;
  5649. overflow: hidden;
  5650. text-overflow: ellipsis;
  5651. display: -webkit-box;
  5652. -webkit-line-clamp: 2;
  5653. -webkit-box-orient: vertical;
  5654. }
  5655. &-bottom {
  5656. margin-top: 12rpx;
  5657. display: flex;
  5658. flex-direction: row;
  5659. align-items: center;
  5660. }
  5661. &-tag {
  5662. position: relative;
  5663. width: 120rpx;
  5664. height: 44rpx;
  5665. }
  5666. &-tag-bg {
  5667. width: 100%;
  5668. height: 100%;
  5669. }
  5670. &-tag-text {
  5671. position: absolute;
  5672. left: 0;
  5673. top: 0;
  5674. width: 100%;
  5675. height: 100%;
  5676. text-align: center;
  5677. line-height: 44rpx;
  5678. font-size: 24rpx;
  5679. color: #FFFFFF;
  5680. font-weight: 500;
  5681. }
  5682. &-price {
  5683. margin-left: 14rpx;
  5684. display: flex;
  5685. align-items: baseline;
  5686. .unit {
  5687. font-family: PingFangSC, PingFangSC;
  5688. font-weight: 600;
  5689. font-size: 32rpx;
  5690. color: #FF233C;
  5691. }
  5692. .price {
  5693. font-family: PingFangSC, PingFangSC;
  5694. font-weight: 600;
  5695. font-size: 48rpx;
  5696. color: #FF233C;
  5697. }
  5698. }
  5699. .close-box{
  5700. position: absolute;
  5701. top:20rpx;
  5702. right:20rpx;
  5703. image{
  5704. width: 30rpx;
  5705. height: 30rpx;
  5706. }
  5707. }
  5708. }
  5709. </style>