videovip.vue 181 KB

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