video.vue 177 KB

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