| 12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694169516961697169816991700170117021703170417051706170717081709171017111712171317141715171617171718171917201721172217231724172517261727172817291730173117321733173417351736173717381739174017411742174317441745174617471748174917501751175217531754175517561757175817591760176117621763176417651766176717681769177017711772177317741775177617771778177917801781178217831784178517861787178817891790179117921793179417951796179717981799180018011802180318041805180618071808180918101811181218131814181518161817181818191820182118221823182418251826182718281829183018311832183318341835183618371838183918401841184218431844184518461847184818491850185118521853185418551856185718581859186018611862186318641865186618671868186918701871187218731874187518761877187818791880188118821883188418851886188718881889189018911892189318941895189618971898189919001901190219031904190519061907190819091910191119121913191419151916191719181919192019211922192319241925192619271928192919301931193219331934193519361937193819391940194119421943194419451946194719481949195019511952195319541955195619571958195919601961196219631964196519661967196819691970197119721973197419751976197719781979198019811982198319841985198619871988198919901991199219931994199519961997199819992000200120022003200420052006200720082009201020112012201320142015201620172018201920202021202220232024202520262027202820292030203120322033203420352036203720382039204020412042204320442045204620472048204920502051205220532054205520562057205820592060206120622063206420652066206720682069207020712072207320742075207620772078207920802081208220832084208520862087208820892090209120922093209420952096209720982099210021012102210321042105210621072108210921102111211221132114211521162117211821192120212121222123212421252126212721282129213021312132213321342135213621372138213921402141214221432144214521462147214821492150215121522153215421552156215721582159216021612162216321642165216621672168216921702171217221732174217521762177217821792180218121822183218421852186218721882189219021912192219321942195219621972198219922002201220222032204220522062207220822092210221122122213221422152216221722182219222022212222222322242225222622272228222922302231223222332234223522362237223822392240224122422243224422452246224722482249225022512252225322542255225622572258225922602261226222632264226522662267226822692270227122722273227422752276227722782279228022812282228322842285228622872288228922902291229222932294229522962297229822992300230123022303230423052306230723082309231023112312231323142315231623172318231923202321232223232324232523262327232823292330233123322333233423352336233723382339234023412342234323442345234623472348234923502351235223532354235523562357235823592360236123622363236423652366236723682369237023712372237323742375237623772378237923802381238223832384238523862387238823892390239123922393239423952396239723982399240024012402240324042405240624072408240924102411241224132414241524162417241824192420242124222423242424252426242724282429243024312432243324342435243624372438243924402441244224432444244524462447244824492450245124522453245424552456245724582459246024612462246324642465246624672468246924702471247224732474247524762477247824792480248124822483248424852486248724882489249024912492249324942495249624972498249925002501250225032504250525062507250825092510251125122513251425152516251725182519252025212522252325242525252625272528252925302531253225332534253525362537253825392540254125422543254425452546254725482549255025512552255325542555255625572558255925602561256225632564256525662567256825692570257125722573257425752576257725782579258025812582258325842585258625872588258925902591259225932594259525962597259825992600260126022603260426052606260726082609261026112612261326142615261626172618261926202621262226232624262526262627262826292630263126322633263426352636263726382639264026412642264326442645264626472648264926502651265226532654265526562657265826592660266126622663266426652666266726682669267026712672267326742675267626772678267926802681268226832684268526862687268826892690269126922693269426952696269726982699270027012702270327042705270627072708270927102711271227132714271527162717271827192720272127222723272427252726272727282729273027312732273327342735273627372738273927402741274227432744274527462747274827492750275127522753275427552756275727582759276027612762276327642765276627672768276927702771277227732774277527762777277827792780278127822783278427852786278727882789279027912792279327942795279627972798279928002801280228032804280528062807280828092810281128122813281428152816281728182819282028212822282328242825282628272828282928302831283228332834283528362837283828392840284128422843284428452846284728482849285028512852285328542855285628572858285928602861286228632864286528662867286828692870287128722873287428752876287728782879288028812882288328842885288628872888288928902891289228932894289528962897289828992900290129022903290429052906290729082909291029112912291329142915291629172918291929202921292229232924292529262927292829292930293129322933293429352936293729382939294029412942294329442945294629472948294929502951295229532954295529562957295829592960296129622963296429652966296729682969297029712972297329742975297629772978297929802981298229832984298529862987298829892990299129922993299429952996299729982999300030013002300330043005300630073008300930103011301230133014301530163017301830193020302130223023302430253026302730283029303030313032303330343035303630373038303930403041304230433044304530463047304830493050305130523053305430553056305730583059306030613062306330643065306630673068306930703071307230733074307530763077307830793080308130823083308430853086308730883089309030913092309330943095309630973098309931003101310231033104310531063107310831093110311131123113311431153116311731183119312031213122312331243125312631273128312931303131313231333134313531363137313831393140314131423143314431453146314731483149315031513152315331543155315631573158315931603161316231633164316531663167316831693170317131723173317431753176317731783179318031813182318331843185318631873188318931903191319231933194319531963197319831993200320132023203320432053206320732083209321032113212321332143215321632173218321932203221322232233224322532263227322832293230323132323233323432353236323732383239324032413242324332443245324632473248324932503251325232533254325532563257325832593260326132623263326432653266326732683269327032713272327332743275327632773278327932803281328232833284328532863287328832893290329132923293329432953296329732983299330033013302330333043305330633073308330933103311331233133314331533163317331833193320332133223323332433253326332733283329333033313332333333343335333633373338333933403341334233433344334533463347334833493350335133523353335433553356335733583359336033613362336333643365336633673368336933703371337233733374337533763377337833793380338133823383338433853386338733883389339033913392339333943395339633973398339934003401340234033404340534063407340834093410341134123413341434153416341734183419342034213422342334243425342634273428342934303431343234333434343534363437343834393440344134423443344434453446344734483449345034513452345334543455345634573458345934603461346234633464346534663467346834693470347134723473347434753476347734783479348034813482348334843485348634873488348934903491349234933494349534963497349834993500350135023503350435053506350735083509351035113512351335143515351635173518351935203521352235233524352535263527352835293530353135323533353435353536353735383539354035413542354335443545354635473548354935503551355235533554355535563557355835593560356135623563356435653566356735683569357035713572357335743575357635773578357935803581358235833584358535863587358835893590359135923593359435953596359735983599360036013602360336043605360636073608360936103611361236133614361536163617361836193620362136223623362436253626362736283629363036313632363336343635363636373638363936403641364236433644364536463647364836493650365136523653365436553656365736583659366036613662366336643665366636673668366936703671367236733674367536763677367836793680368136823683368436853686368736883689369036913692369336943695369636973698369937003701370237033704370537063707370837093710371137123713371437153716371737183719372037213722372337243725372637273728372937303731373237333734373537363737373837393740374137423743374437453746374737483749375037513752375337543755375637573758375937603761376237633764376537663767376837693770377137723773377437753776377737783779378037813782378337843785378637873788378937903791379237933794379537963797379837993800380138023803380438053806380738083809381038113812381338143815381638173818381938203821382238233824382538263827382838293830383138323833383438353836383738383839384038413842384338443845384638473848384938503851385238533854385538563857385838593860386138623863386438653866386738683869387038713872387338743875387638773878387938803881388238833884388538863887388838893890389138923893389438953896389738983899390039013902390339043905390639073908390939103911391239133914391539163917391839193920392139223923392439253926392739283929393039313932393339343935393639373938393939403941394239433944394539463947394839493950395139523953395439553956395739583959396039613962396339643965396639673968396939703971397239733974397539763977397839793980398139823983398439853986398739883989399039913992399339943995399639973998399940004001400240034004400540064007400840094010401140124013401440154016401740184019402040214022402340244025402640274028402940304031403240334034403540364037403840394040404140424043404440454046404740484049405040514052405340544055405640574058405940604061406240634064406540664067406840694070407140724073407440754076407740784079408040814082408340844085408640874088408940904091409240934094409540964097409840994100410141024103410441054106410741084109411041114112411341144115411641174118411941204121412241234124412541264127412841294130413141324133413441354136413741384139414041414142414341444145414641474148414941504151415241534154415541564157415841594160416141624163416441654166416741684169417041714172417341744175417641774178417941804181418241834184418541864187418841894190419141924193419441954196419741984199420042014202420342044205420642074208420942104211421242134214421542164217421842194220422142224223422442254226422742284229423042314232423342344235423642374238423942404241424242434244424542464247424842494250425142524253425442554256425742584259426042614262426342644265426642674268426942704271427242734274427542764277427842794280428142824283428442854286428742884289429042914292429342944295429642974298429943004301430243034304430543064307430843094310431143124313431443154316431743184319432043214322432343244325432643274328432943304331433243334334433543364337433843394340434143424343434443454346434743484349435043514352435343544355435643574358435943604361436243634364436543664367436843694370437143724373437443754376437743784379438043814382438343844385438643874388438943904391439243934394439543964397439843994400440144024403440444054406440744084409441044114412441344144415441644174418441944204421442244234424442544264427442844294430443144324433443444354436443744384439444044414442444344444445444644474448444944504451445244534454445544564457445844594460446144624463446444654466446744684469447044714472447344744475447644774478447944804481448244834484448544864487448844894490449144924493449444954496449744984499450045014502450345044505450645074508450945104511451245134514451545164517451845194520452145224523452445254526452745284529453045314532453345344535453645374538453945404541454245434544454545464547454845494550455145524553455445554556455745584559456045614562456345644565456645674568456945704571457245734574457545764577457845794580458145824583458445854586458745884589459045914592459345944595459645974598459946004601460246034604460546064607460846094610461146124613461446154616461746184619462046214622462346244625462646274628462946304631463246334634463546364637463846394640464146424643464446454646464746484649465046514652465346544655465646574658465946604661466246634664466546664667466846694670467146724673467446754676467746784679468046814682468346844685468646874688468946904691469246934694469546964697469846994700470147024703470447054706470747084709471047114712471347144715471647174718471947204721472247234724472547264727472847294730473147324733473447354736473747384739474047414742474347444745474647474748474947504751475247534754475547564757475847594760476147624763476447654766476747684769477047714772477347744775477647774778477947804781478247834784478547864787478847894790479147924793479447954796479747984799480048014802480348044805480648074808480948104811481248134814481548164817481848194820482148224823482448254826482748284829483048314832483348344835483648374838483948404841484248434844484548464847484848494850485148524853485448554856485748584859486048614862486348644865486648674868486948704871487248734874487548764877487848794880488148824883488448854886488748884889489048914892489348944895489648974898489949004901490249034904490549064907490849094910491149124913491449154916491749184919492049214922492349244925492649274928492949304931493249334934493549364937493849394940494149424943494449454946494749484949495049514952495349544955495649574958495949604961496249634964496549664967496849694970497149724973497449754976497749784979498049814982498349844985498649874988498949904991499249934994499549964997499849995000500150025003500450055006500750085009501050115012501350145015501650175018501950205021502250235024502550265027502850295030503150325033503450355036503750385039504050415042504350445045504650475048504950505051505250535054505550565057505850595060506150625063506450655066506750685069507050715072507350745075507650775078507950805081508250835084508550865087508850895090509150925093509450955096509750985099510051015102510351045105510651075108510951105111511251135114511551165117511851195120512151225123512451255126512751285129513051315132513351345135513651375138513951405141514251435144514551465147514851495150515151525153515451555156515751585159516051615162516351645165516651675168516951705171517251735174517551765177517851795180518151825183518451855186518751885189519051915192519351945195519651975198519952005201520252035204520552065207520852095210521152125213521452155216521752185219522052215222522352245225522652275228522952305231523252335234523552365237523852395240524152425243524452455246524752485249525052515252525352545255525652575258525952605261526252635264526552665267526852695270527152725273527452755276527752785279528052815282528352845285528652875288528952905291529252935294529552965297529852995300530153025303530453055306530753085309531053115312531353145315531653175318531953205321532253235324532553265327532853295330533153325333533453355336533753385339534053415342534353445345534653475348534953505351535253535354535553565357535853595360536153625363536453655366536753685369537053715372537353745375537653775378537953805381538253835384538553865387538853895390539153925393539453955396539753985399540054015402540354045405540654075408540954105411541254135414541554165417541854195420542154225423542454255426542754285429543054315432543354345435543654375438543954405441544254435444544554465447544854495450545154525453545454555456545754585459546054615462546354645465546654675468546954705471547254735474547554765477547854795480548154825483548454855486548754885489549054915492549354945495549654975498549955005501550255035504550555065507550855095510551155125513551455155516551755185519552055215522552355245525552655275528552955305531553255335534553555365537553855395540554155425543554455455546554755485549555055515552555355545555555655575558555955605561556255635564556555665567556855695570557155725573557455755576557755785579558055815582558355845585558655875588558955905591559255935594559555965597559855995600560156025603560456055606560756085609561056115612561356145615561656175618561956205621562256235624562556265627562856295630563156325633563456355636563756385639564056415642564356445645564656475648564956505651565256535654565556565657565856595660566156625663566456655666566756685669567056715672567356745675567656775678567956805681568256835684568556865687568856895690569156925693569456955696569756985699570057015702570357045705570657075708570957105711571257135714571557165717571857195720572157225723572457255726572757285729573057315732573357345735573657375738573957405741574257435744574557465747574857495750575157525753575457555756575757585759576057615762576357645765576657675768576957705771577257735774577557765777577857795780578157825783578457855786578757885789579057915792579357945795579657975798579958005801580258035804580558065807580858095810581158125813581458155816581758185819582058215822582358245825582658275828582958305831583258335834583558365837583858395840584158425843584458455846584758485849585058515852585358545855585658575858585958605861586258635864586558665867586858695870587158725873587458755876587758785879588058815882588358845885588658875888588958905891589258935894589558965897589858995900590159025903590459055906590759085909591059115912591359145915591659175918591959205921592259235924592559265927592859295930593159325933593459355936593759385939594059415942594359445945594659475948594959505951595259535954595559565957595859595960596159625963596459655966596759685969597059715972597359745975597659775978597959805981598259835984598559865987598859895990599159925993599459955996599759985999600060016002600360046005600660076008600960106011601260136014601560166017601860196020602160226023602460256026602760286029603060316032603360346035603660376038603960406041604260436044604560466047604860496050605160526053605460556056605760586059606060616062606360646065606660676068606960706071607260736074607560766077607860796080608160826083608460856086608760886089609060916092609360946095609660976098609961006101610261036104610561066107610861096110611161126113611461156116611761186119612061216122612361246125612661276128612961306131613261336134613561366137613861396140614161426143614461456146614761486149615061516152615361546155615661576158615961606161616261636164616561666167616861696170617161726173617461756176617761786179618061816182618361846185618661876188618961906191619261936194619561966197619861996200620162026203620462056206620762086209621062116212621362146215621662176218621962206221622262236224622562266227622862296230623162326233623462356236623762386239624062416242624362446245624662476248624962506251625262536254625562566257625862596260626162626263626462656266626762686269627062716272627362746275627662776278627962806281628262836284628562866287628862896290629162926293629462956296629762986299630063016302630363046305630663076308630963106311631263136314631563166317631863196320632163226323632463256326632763286329633063316332633363346335633663376338633963406341634263436344634563466347634863496350635163526353635463556356635763586359636063616362636363646365636663676368636963706371637263736374637563766377637863796380638163826383638463856386638763886389639063916392639363946395639663976398639964006401640264036404640564066407640864096410641164126413641464156416641764186419642064216422642364246425642664276428642964306431643264336434643564366437643864396440644164426443644464456446644764486449645064516452645364546455645664576458645964606461646264636464646564666467646864696470647164726473647464756476647764786479648064816482648364846485648664876488648964906491649264936494649564966497649864996500650165026503650465056506650765086509651065116512651365146515651665176518651965206521652265236524652565266527652865296530653165326533653465356536653765386539654065416542654365446545654665476548654965506551655265536554655565566557655865596560656165626563656465656566656765686569657065716572657365746575657665776578657965806581658265836584658565866587658865896590659165926593659465956596659765986599660066016602660366046605660666076608660966106611661266136614661566166617661866196620662166226623662466256626662766286629663066316632663366346635663666376638663966406641664266436644664566466647664866496650665166526653665466556656665766586659666066616662666366646665666666676668666966706671667266736674667566766677667866796680668166826683668466856686668766886689669066916692669366946695669666976698669967006701670267036704670567066707670867096710671167126713671467156716671767186719672067216722672367246725672667276728672967306731673267336734673567366737673867396740674167426743674467456746674767486749675067516752675367546755675667576758675967606761676267636764676567666767676867696770677167726773677467756776677767786779678067816782678367846785678667876788678967906791679267936794679567966797679867996800680168026803680468056806680768086809681068116812681368146815681668176818681968206821682268236824682568266827682868296830683168326833683468356836683768386839684068416842684368446845684668476848684968506851685268536854685568566857685868596860686168626863686468656866686768686869687068716872687368746875687668776878687968806881688268836884688568866887688868896890689168926893689468956896689768986899690069016902690369046905690669076908690969106911691269136914691569166917691869196920692169226923692469256926692769286929693069316932693369346935693669376938693969406941694269436944694569466947694869496950695169526953695469556956695769586959696069616962696369646965696669676968696969706971697269736974697569766977697869796980698169826983698469856986698769886989699069916992699369946995699669976998699970007001700270037004700570067007700870097010701170127013701470157016701770187019702070217022702370247025702670277028702970307031703270337034703570367037703870397040704170427043704470457046704770487049705070517052705370547055705670577058705970607061706270637064706570667067706870697070707170727073707470757076707770787079708070817082708370847085708670877088708970907091709270937094709570967097709870997100710171027103710471057106710771087109711071117112711371147115711671177118711971207121712271237124712571267127712871297130713171327133713471357136713771387139714071417142714371447145714671477148714971507151715271537154715571567157715871597160716171627163716471657166716771687169717071717172717371747175717671777178717971807181718271837184718571867187718871897190719171927193719471957196719771987199720072017202720372047205720672077208720972107211721272137214721572167217721872197220722172227223722472257226722772287229723072317232723372347235723672377238723972407241724272437244724572467247724872497250725172527253725472557256725772587259726072617262726372647265726672677268726972707271727272737274727572767277727872797280728172827283728472857286728772887289729072917292729372947295729672977298729973007301730273037304730573067307730873097310731173127313731473157316731773187319732073217322732373247325732673277328732973307331733273337334733573367337733873397340734173427343734473457346734773487349735073517352735373547355735673577358735973607361736273637364736573667367736873697370737173727373737473757376737773787379738073817382738373847385738673877388738973907391739273937394739573967397739873997400740174027403740474057406740774087409741074117412741374147415741674177418741974207421742274237424742574267427742874297430743174327433743474357436743774387439744074417442744374447445744674477448744974507451745274537454745574567457745874597460746174627463746474657466746774687469747074717472747374747475747674777478747974807481748274837484748574867487748874897490749174927493749474957496749774987499750075017502750375047505750675077508750975107511751275137514751575167517751875197520752175227523752475257526752775287529753075317532753375347535753675377538753975407541754275437544754575467547754875497550755175527553755475557556755775587559756075617562756375647565756675677568756975707571757275737574757575767577757875797580758175827583758475857586758775887589759075917592759375947595759675977598759976007601760276037604760576067607760876097610761176127613761476157616761776187619762076217622762376247625762676277628762976307631763276337634763576367637763876397640764176427643764476457646764776487649765076517652765376547655765676577658765976607661766276637664766576667667766876697670767176727673767476757676767776787679768076817682768376847685768676877688768976907691769276937694769576967697769876997700770177027703770477057706770777087709771077117712771377147715771677177718771977207721772277237724772577267727772877297730773177327733773477357736773777387739774077417742774377447745774677477748774977507751775277537754775577567757775877597760776177627763776477657766776777687769777077717772777377747775777677777778777977807781778277837784778577867787778877897790779177927793779477957796779777987799780078017802780378047805780678077808780978107811781278137814781578167817781878197820782178227823782478257826782778287829783078317832783378347835783678377838783978407841784278437844784578467847784878497850785178527853785478557856785778587859786078617862786378647865786678677868786978707871787278737874787578767877787878797880788178827883788478857886788778887889789078917892789378947895789678977898789979007901790279037904790579067907790879097910791179127913791479157916791779187919792079217922792379247925792679277928792979307931793279337934793579367937793879397940794179427943794479457946794779487949795079517952795379547955795679577958795979607961796279637964796579667967796879697970797179727973797479757976797779787979798079817982798379847985798679877988798979907991799279937994799579967997799879998000800180028003800480058006800780088009801080118012801380148015801680178018801980208021802280238024802580268027802880298030803180328033803480358036803780388039804080418042804380448045804680478048804980508051805280538054805580568057805880598060806180628063806480658066806780688069807080718072807380748075807680778078807980808081808280838084808580868087808880898090809180928093809480958096809780988099810081018102810381048105810681078108810981108111811281138114811581168117811881198120812181228123812481258126812781288129813081318132813381348135813681378138813981408141814281438144814581468147814881498150815181528153815481558156815781588159816081618162816381648165816681678168816981708171817281738174817581768177817881798180818181828183818481858186818781888189819081918192819381948195819681978198819982008201820282038204820582068207820882098210821182128213821482158216821782188219822082218222822382248225822682278228822982308231823282338234823582368237823882398240824182428243824482458246824782488249825082518252825382548255825682578258825982608261826282638264826582668267826882698270827182728273827482758276827782788279828082818282828382848285828682878288828982908291829282938294829582968297829882998300830183028303830483058306830783088309831083118312831383148315831683178318831983208321832283238324832583268327832883298330833183328333833483358336833783388339834083418342834383448345834683478348834983508351835283538354835583568357835883598360836183628363836483658366836783688369837083718372837383748375837683778378837983808381838283838384838583868387838883898390839183928393839483958396839783988399840084018402840384048405840684078408840984108411841284138414841584168417841884198420842184228423842484258426842784288429843084318432843384348435843684378438843984408441844284438444844584468447844884498450845184528453845484558456845784588459846084618462846384648465846684678468846984708471847284738474847584768477847884798480848184828483848484858486848784888489849084918492849384948495849684978498849985008501850285038504850585068507850885098510851185128513851485158516851785188519852085218522852385248525852685278528852985308531853285338534853585368537853885398540854185428543854485458546854785488549855085518552855385548555855685578558855985608561856285638564856585668567856885698570857185728573857485758576857785788579858085818582858385848585858685878588858985908591859285938594859585968597859885998600860186028603860486058606860786088609861086118612861386148615861686178618861986208621862286238624862586268627862886298630863186328633863486358636863786388639864086418642864386448645864686478648864986508651865286538654865586568657865886598660866186628663866486658666866786688669867086718672867386748675867686778678867986808681868286838684868586868687868886898690869186928693869486958696869786988699870087018702870387048705870687078708870987108711871287138714871587168717871887198720872187228723872487258726872787288729873087318732873387348735873687378738873987408741874287438744874587468747874887498750875187528753875487558756875787588759876087618762876387648765876687678768876987708771877287738774877587768777877887798780878187828783878487858786878787888789879087918792879387948795879687978798879988008801880288038804880588068807880888098810881188128813881488158816881788188819882088218822882388248825882688278828882988308831883288338834883588368837883888398840884188428843884488458846884788488849885088518852885388548855885688578858885988608861886288638864886588668867886888698870887188728873887488758876887788788879888088818882888388848885888688878888888988908891889288938894889588968897889888998900890189028903890489058906890789088909891089118912891389148915891689178918891989208921892289238924892589268927892889298930893189328933893489358936893789388939894089418942894389448945894689478948894989508951895289538954895589568957895889598960896189628963896489658966896789688969897089718972897389748975897689778978897989808981898289838984898589868987898889898990899189928993899489958996899789988999900090019002900390049005900690079008900990109011901290139014901590169017901890199020902190229023902490259026902790289029903090319032903390349035903690379038903990409041904290439044904590469047904890499050905190529053905490559056905790589059906090619062906390649065906690679068906990709071907290739074907590769077907890799080908190829083908490859086908790889089909090919092909390949095909690979098909991009101910291039104910591069107910891099110911191129113911491159116911791189119912091219122912391249125912691279128912991309131913291339134913591369137913891399140914191429143914491459146914791489149915091519152915391549155915691579158915991609161916291639164916591669167916891699170917191729173917491759176917791789179918091819182918391849185918691879188918991909191919291939194919591969197919891999200920192029203920492059206920792089209921092119212921392149215921692179218921992209221922292239224922592269227922892299230923192329233923492359236923792389239924092419242924392449245924692479248924992509251925292539254925592569257925892599260926192629263926492659266926792689269927092719272927392749275927692779278927992809281928292839284928592869287928892899290929192929293929492959296929792989299930093019302930393049305930693079308930993109311931293139314931593169317931893199320932193229323932493259326932793289329933093319332933393349335933693379338933993409341934293439344934593469347934893499350935193529353935493559356935793589359936093619362936393649365936693679368936993709371937293739374937593769377937893799380938193829383938493859386938793889389939093919392939393949395939693979398939994009401940294039404940594069407940894099410941194129413941494159416941794189419942094219422942394249425942694279428942994309431943294339434943594369437943894399440944194429443944494459446944794489449945094519452945394549455945694579458945994609461946294639464946594669467946894699470947194729473947494759476947794789479948094819482948394849485948694879488948994909491949294939494949594969497949894999500950195029503950495059506950795089509951095119512951395149515951695179518951995209521952295239524952595269527952895299530953195329533953495359536953795389539954095419542954395449545954695479548954995509551955295539554955595569557955895599560956195629563956495659566956795689569957095719572957395749575957695779578957995809581958295839584958595869587958895899590959195929593959495959596959795989599960096019602960396049605960696079608960996109611961296139614961596169617961896199620962196229623962496259626962796289629963096319632963396349635963696379638963996409641964296439644964596469647964896499650965196529653965496559656965796589659966096619662966396649665966696679668966996709671967296739674967596769677967896799680968196829683968496859686968796889689969096919692969396949695969696979698969997009701970297039704970597069707970897099710971197129713971497159716971797189719972097219722972397249725972697279728972997309731973297339734973597369737973897399740974197429743974497459746974797489749975097519752975397549755975697579758975997609761976297639764976597669767976897699770977197729773977497759776977797789779978097819782978397849785978697879788978997909791979297939794979597969797979897999800980198029803980498059806980798089809981098119812981398149815981698179818981998209821982298239824982598269827982898299830983198329833983498359836983798389839984098419842984398449845984698479848984998509851985298539854985598569857985898599860986198629863986498659866986798689869987098719872987398749875987698779878987998809881988298839884988598869887988898899890989198929893989498959896989798989899990099019902990399049905990699079908990999109911991299139914991599169917991899199920992199229923992499259926992799289929993099319932993399349935993699379938993999409941994299439944994599469947994899499950995199529953995499559956995799589959996099619962996399649965996699679968996999709971997299739974997599769977997899799980998199829983998499859986998799889989999099919992999399949995999699979998999910000100011000210003100041000510006100071000810009100101001110012100131001410015100161001710018100191002010021100221002310024100251002610027100281002910030100311003210033100341003510036100371003810039100401004110042100431004410045100461004710048100491005010051100521005310054100551005610057100581005910060100611006210063100641006510066100671006810069100701007110072100731007410075100761007710078100791008010081100821008310084100851008610087100881008910090100911009210093100941009510096100971009810099101001010110102101031010410105101061010710108101091011010111101121011310114101151011610117101181011910120101211012210123101241012510126101271012810129101301013110132101331013410135101361013710138101391014010141101421014310144101451014610147101481014910150101511015210153101541015510156101571015810159101601016110162101631016410165101661016710168101691017010171101721017310174101751017610177101781017910180101811018210183101841018510186101871018810189101901019110192101931019410195101961019710198101991020010201102021020310204102051020610207102081020910210102111021210213102141021510216102171021810219102201022110222102231022410225102261022710228102291023010231102321023310234102351023610237102381023910240102411024210243102441024510246102471024810249102501025110252102531025410255102561025710258102591026010261102621026310264102651026610267102681026910270102711027210273102741027510276102771027810279102801028110282102831028410285102861028710288102891029010291102921029310294102951029610297102981029910300103011030210303103041030510306103071030810309103101031110312103131031410315103161031710318103191032010321103221032310324103251032610327103281032910330103311033210333103341033510336103371033810339103401034110342103431034410345103461034710348103491035010351103521035310354103551035610357103581035910360103611036210363103641036510366103671036810369103701037110372103731037410375103761037710378103791038010381103821038310384103851038610387103881038910390103911039210393103941039510396103971039810399104001040110402104031040410405104061040710408104091041010411104121041310414104151041610417104181041910420104211042210423104241042510426104271042810429104301043110432104331043410435104361043710438104391044010441104421044310444104451044610447104481044910450104511045210453104541045510456104571045810459104601046110462104631046410465104661046710468104691047010471104721047310474104751047610477104781047910480104811048210483104841048510486104871048810489104901049110492104931049410495104961049710498104991050010501105021050310504105051050610507105081050910510105111051210513105141051510516105171051810519105201052110522105231052410525105261052710528105291053010531105321053310534105351053610537105381053910540105411054210543105441054510546105471054810549105501055110552105531055410555105561055710558105591056010561105621056310564105651056610567105681056910570105711057210573105741057510576105771057810579105801058110582105831058410585105861058710588105891059010591105921059310594105951059610597105981059910600106011060210603106041060510606106071060810609106101061110612106131061410615106161061710618106191062010621106221062310624106251062610627 |
- <!DOCTYPE html>
- <html lang="zh-CN">
- <head>
- <meta charset="UTF-8">
- <meta name="viewport" content="width=device-width, initial-scale=1.0">
- <title>互动课件编辑器</title>
- <style>
- * {
- margin: 0;
- padding: 0;
- box-sizing: border-box;
- }
- body {
- font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei", sans-serif;
- background: #f0f2f5;
- overflow: hidden;
- }
- /* ========== 顶部工具栏 ========== */
- .top-bar {
- height: 64px;
- background: white;
- display: flex;
- align-items: center;
- justify-content: space-between;
- padding: 0 24px;
- position: relative;
- z-index: 100;
- box-shadow: 0 1px 3px 0 rgba(0, 0, 0, 0.06);
- }
- .top-bar-left {
- display: flex;
- align-items: center;
- gap: 16px;
- }
- .logo-menu-wrapper {
- position: relative;
- }
- .logo-btn {
- height: 36px;
- padding: 6px 10px;
- border-radius: 10px;
- border: 1.5px solid #e5e7eb;
- background: #f9fafb;
- cursor: pointer;
- display: inline-flex;
- align-items: center;
- gap: 8px;
- transition: all 0.2s;
- }
- .logo-btn:hover {
- background: #fff;
- border-color: #285cf5;
- box-shadow: 0 4px 10px rgba(40, 92, 245, 0.15);
- }
- .logo-img {
- width: 20px;
- height: 20px;
- object-fit: contain;
- }
- .logo-caret {
- width: 14px;
- height: 14px;
- color: #9ca3af;
- }
- .top-dropdown {
- position: absolute;
- top: 44px;
- left: 0;
- background: #fff;
- border: 1px solid #e5e7eb;
- box-shadow: 0 12px 30px rgba(0,0,0,0.12);
- border-radius: 12px;
- min-width: 160px;
- padding: 6px 0;
- display: none;
- z-index: 200;
- }
- .top-dropdown.active {
- display: block;
- }
- .top-dropdown-item {
- display: flex;
- align-items: center;
- gap: 10px;
- padding: 10px 14px;
- cursor: pointer;
- font-size: 13px;
- color: #111827;
- transition: all 0.15s;
- }
- .top-dropdown-item:hover {
- background: #eef3ff;
- color: #1f4ad6;
- }
- .top-dropdown-item.danger {
- color: #dc2626;
- }
- .top-dropdown-item.danger:hover {
- background: #fef2f2;
- color: #b91c1c;
- }
- .top-dropdown-sep {
- height: 1px;
- background: #f3f4f6;
- margin: 4px 0;
- }
- .course-title {
- font-size: 16px;
- font-weight: 600;
- color: #111827;
- padding: 8px 12px;
- border: 1px solid transparent;
- border-radius: 8px;
- cursor: text;
- transition: all 0.2s;
- }
- .course-title:hover {
- border-color: #e5e7eb;
- background: #f9fafb;
- }
- .course-title:focus {
- outline: none;
- border-color: #285cf5;
- background: #f6f8ff;
- }
- .auto-save {
- font-size: 13px;
- color: #6b7280;
- display: flex;
- align-items: center;
- gap: 6px;
- }
- .save-dot {
- width: 6px;
- height: 6px;
- border-radius: 50%;
- background: #10b981;
- animation: pulse 2s ease-in-out infinite;
- }
- @keyframes pulse {
- 0%, 100% { opacity: 1; }
- 50% { opacity: 0.5; }
- }
- .top-bar-right {
- display: flex;
- align-items: center;
- gap: 12px;
- }
- .top-btn {
- height: 40px;
- padding: 0 20px;
- border-radius: 10px;
- border: none;
- font-size: 14px;
- font-weight: 500;
- cursor: pointer;
- transition: all 0.2s;
- display: flex;
- align-items: center;
- gap: 8px;
- }
- .btn-secondary {
- background: white;
- color: #374151;
- border: 1.5px solid #e5e7eb;
- }
- .btn-secondary:hover {
- background: #f9fafb;
- border-color: #d1d5db;
- transform: translateY(-1px);
- box-shadow: 0 2px 4px rgba(0, 0, 0, 0.05);
- }
- .btn-primary {
- background: #285cf5;
- color: white;
- box-shadow: 0 2px 8px rgba(40, 92, 245, 0.2);
- }
- .btn-primary:hover {
- background: #1f4ad6;
- transform: translateY(-1px);
- box-shadow: 0 4px 12px rgba(40, 92, 245, 0.3);
- }
- /* ========== 主容器 ========== */
- .main-container {
- height: calc(100vh - 64px);
- display: flex;
- position: relative;
- padding: 16px;
- gap: 16px;
- }
- /* ========== 左侧容器 ========== */
- .left-container {
- display: flex;
- background: white;
- border-radius: 16px;
- box-shadow: 0 2px 8px rgba(0, 0, 0, 0.06);
- position: relative;
- z-index: 50;
- overflow: hidden;
- }
- .primary-menu {
- width: 100px;
- background: #fafbfc;
- border-right: 1px solid #e5e7eb;
- padding: 16px 8px;
- display: flex;
- flex-direction: column;
- gap: 6px;
- }
- .menu-item {
- width: 84px;
- padding: 12px 8px;
- border-radius: 12px;
- display: flex;
- flex-direction: column;
- align-items: center;
- justify-content: center;
- gap: 6px;
- cursor: pointer;
- transition: all 0.2s;
- position: relative;
- }
- .menu-item:hover {
- background: #f3f4f6;
- }
- .menu-item.active {
- background: #eef3ff;
- box-shadow: 0 2px 8px rgba(40, 92, 245, 0.15);
- }
- .menu-item.active::after {
- content: '';
- position: absolute;
- left: -8px;
- top: 50%;
- transform: translateY(-50%);
- width: 4px;
- height: 32px;
- background: #285cf5;
- border-radius: 0 2px 2px 0;
- }
- .menu-icon {
- width: 22px;
- height: 22px;
- color: #6b7280;
- flex-shrink: 0;
- }
- .menu-item.active .menu-icon {
- color: #285cf5;
- }
- .menu-label {
- font-size: 11px;
- font-weight: 500;
- color: #6b7280;
- text-align: center;
- line-height: 1.2;
- }
- .menu-item.active .menu-label {
- color: #285cf5;
- font-weight: 600;
- }
- /* ========== 二级菜单 - AI对话 ========== */
- .secondary-panel {
- width: 420px;
- background: white;
- display: flex;
- flex-direction: column;
- transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
- }
- .secondary-panel.collapsed {
- width: 0;
- overflow: hidden;
- }
- .secondary-panel.hidden {
- display: none;
- }
- .panel-header {
- padding: 20px 20px 16px;
- border-bottom: 1px solid #f3f4f6;
- display: flex;
- align-items: center;
- justify-content: space-between;
- }
- .panel-title {
- font-size: 15px;
- font-weight: 600;
- color: #111827;
- display: flex;
- align-items: center;
- gap: 10px;
- }
- .ai-badge {
- background: linear-gradient(135deg, #285cf5 0%, #1f4ad6 100%);
- color: white;
- font-size: 11px;
- font-weight: 600;
- padding: 4px 10px;
- border-radius: 12px;
- }
- .collapse-btn {
- width: 28px;
- height: 28px;
- border-radius: 10px;
- border: none;
- background: transparent;
- cursor: pointer;
- display: flex;
- align-items: center;
- justify-content: center;
- transition: all 0.2s;
- color: #9ca3af;
- }
- .collapse-btn:hover {
- background: #f3f4f6;
- color: #6b7280;
- }
- /* 输入框容器 */
- .chat-input-container {
- padding: 16px 20px;
- background: white;
- order: 1;
- border-bottom: 1px solid #f0f1f3;
- transition: all 0.3s ease;
- }
- .chat-input-container.bottom {
- order: 3;
- border-bottom: none;
- border-top: 1px solid #f0f1f3;
- }
- .secondary-content {
- flex: 1;
- overflow-y: auto;
- padding: 20px;
- display: flex;
- flex-direction: column;
- order: 2;
- }
- .chat-messages {
- min-height: 600px;
- overflow-y: auto;
- padding-bottom: 16px;
- }
- .message {
- margin-bottom: 16px;
- }
- .message-user {
- display: flex;
- justify-content: flex-end;
- }
- .message-user .message-content {
- background: #eef3ff;
- border: 1.5px solid #285cf5;
- color: #111827;
- border-radius: 16px 16px 4px 16px;
- padding: 12px 16px;
- max-width: 85%;
- font-size: 14px;
- line-height: 1.6;
- }
- .message-ai {
- display: flex;
- justify-content: flex-start;
- }
- .message-ai .message-content {
- background: #fafbfc;
- border: 1.5px solid #e5e7eb;
- color: #374151;
- border-radius: 16px 16px 16px 4px;
- padding: 12px 16px;
- max-width: 85%;
- font-size: 14px;
- line-height: 1.6;
- white-space: pre-line;
- }
- .chat-input-wrapper {
- display: flex;
- flex-direction: column;
- gap: 8px;
- background: #fafbfc;
- border: 1.5px solid #e5e7eb;
- border-radius: 16px;
- padding: 12px;
- transition: all 0.2s;
- }
- .chat-input-wrapper:focus-within {
- border-color: #285cf5;
- background: white;
- }
- .chat-textarea-container {
- width: 100%;
- min-height: 72px;
- }
- .chat-textarea {
- width: 100%;
- border: none;
- background: transparent;
- outline: none;
- resize: none;
- font-size: 14px;
- color: #111827;
- line-height: 1.5;
- padding: 0;
- min-height: 72px;
- max-height: 180px;
- font-family: inherit;
- }
- .chat-textarea::placeholder {
- color: #9ca3af;
- }
- .chat-textarea:disabled {
- opacity: 0.6;
- cursor: not-allowed;
- }
- .chat-bottom-row {
- display: flex;
- align-items: center;
- justify-content: space-between;
- gap: 8px;
- }
- .upload-file-btn {
- width: 36px;
- height: 36px;
- border-radius: 8px;
- border: none;
- background: transparent;
- cursor: pointer;
- display: flex;
- align-items: center;
- justify-content: center;
- transition: all 0.2s;
- flex-shrink: 0;
- }
- .upload-file-btn:hover {
- background: #eef3ff;
- }
- .upload-file-btn svg {
- width: 20px;
- height: 20px;
- color: #6b7280;
- }
- .upload-file-btn:hover svg {
- color: #285cf5;
- }
- .status-text {
- font-size: 13px;
- color: #9ca3af;
- display: none;
- align-items: center;
- gap: 6px;
- }
- .status-text.active {
- display: flex;
- }
- .status-dot {
- width: 6px;
- height: 6px;
- border-radius: 50%;
- background: #285cf5;
- animation: pulse-dot 1.5s ease-in-out infinite;
- }
- @keyframes pulse-dot {
- 0%, 100% { opacity: 1; transform: scale(1); }
- 50% { opacity: 0.5; transform: scale(1.2); }
- }
- .send-btn {
- width: 36px;
- height: 36px;
- border-radius: 50%;
- border: none;
- background: #285cf5;
- cursor: pointer;
- display: flex;
- align-items: center;
- justify-content: center;
- transition: all 0.2s;
- flex-shrink: 0;
- }
- .send-btn.generating {
- background: #1f4ad6;
- cursor: not-allowed;
- box-shadow: 0 0 0 3px rgba(40, 92, 245, 0.16);
- }
- .send-btn.generating svg {
- animation: spin 1s linear infinite;
- }
- @keyframes spin {
- from { transform: rotate(0deg); }
- to { transform: rotate(360deg); }
- }
- .send-btn:hover {
- background: #1f4ad6;
- transform: scale(1.05);
- box-shadow: 0 4px 12px rgba(40, 92, 245, 0.25);
- }
- .send-btn.generating:hover {
- background: #1f4ad6;
- transform: none;
- box-shadow: 0 0 0 3px rgba(40, 92, 245, 0.16);
- }
- .send-btn svg {
- width: 18px;
- height: 18px;
- color: white;
- }
- /* ========== 中央编辑区 ========== */
- .center-area {
- flex: 1;
- display: flex;
- flex-direction: column;
- background: white;
- overflow: hidden;
- border-radius: 16px;
- box-shadow: 0 2px 8px rgba(0, 0, 0, 0.06);
- position: relative;
- }
- /* 元素工具栏 */
- .element-toolbar {
- min-height: 60px;
- background: white;
- border-bottom: 1px solid #f0f1f3;
- display: none;
- align-items: center;
- padding: 12px 24px;
- gap: 10px;
- position: sticky;
- top: 0;
- z-index: 3;
- }
- .element-toolbar.visible {
- display: flex;
- }
- .toolbar-section {
- display: flex;
- align-items: center;
- gap: 8px;
- padding-right: 16px;
- border-right: 1px solid #e5e7eb;
- }
- .toolbar-section:last-child {
- border-right: none;
- }
- .toolbar-btn {
- height: 38px;
- padding: 0 14px;
- border-radius: 10px;
- border: none;
- background: #f9fafb;
- font-size: 13px;
- font-weight: 500;
- color: #4b5563;
- cursor: pointer;
- display: flex;
- align-items: center;
- gap: 7px;
- transition: all 0.2s;
- }
- .toolbar-btn:hover {
- background: #eef3ff;
- color: #285cf5;
- transform: translateY(-1px);
- }
- .toolbar-btn svg {
- width: 16px;
- height: 16px;
- }
- /* Slides编辑区 */
- .slides-area {
- flex: 1;
- display: flex;
- flex-direction: column;
- padding: 0;
- overflow: hidden;
- background: #fafbfc;
- position: relative;
- }
- .slides-area > .slide-canvas {
- flex: 1;
- width: 100%;
- height: auto;
- background: white;
- box-shadow: none;
- border-radius: 0;
- display: flex;
- align-items: center;
- justify-content: center;
- padding: 40px;
- }
- .slides-area > .slide-canvas .slide-placeholder {
- text-align: center;
- color: #9ca3af;
- }
- .slide-placeholder-icon {
- font-size: 56px;
- margin-bottom: 20px;
- opacity: 0.6;
- }
- .slide-placeholder-text {
- font-size: 16px;
- font-weight: 500;
- margin-bottom: 8px;
- color: #6b7280;
- }
- .slide-placeholder-hint {
- font-size: 13px;
- color: #d1d5db;
- }
- /* 可编辑元素 */
- .slide-element {
- position: absolute;
- border: 2px solid transparent;
- cursor: move;
- transition: border-color 0.2s;
- }
- .slide-element:hover {
- border-color: #d1d5db;
- }
- .slide-element.selected {
- border-color: #285cf5;
- box-shadow: 0 0 0 1px #285cf5;
- }
- .slide-element.selected .resize-handle {
- display: block;
- }
- .resize-handle {
- position: absolute;
- width: 8px;
- height: 8px;
- background: #285cf5;
- border: 2px solid white;
- border-radius: 50%;
- display: none;
- }
- .resize-handle.nw { top: -4px; left: -4px; cursor: nw-resize; }
- .resize-handle.ne { top: -4px; right: -4px; cursor: ne-resize; }
- .resize-handle.sw { bottom: -4px; left: -4px; cursor: sw-resize; }
- .resize-handle.se { bottom: -4px; right: -4px; cursor: se-resize; }
- .text-element {
- padding: 12px;
- font-size: 16px;
- line-height: 1.5;
- outline: none;
- }
- .image-element {
- width: 100%;
- height: 100%;
- object-fit: cover;
- }
- /* 下拉菜单 */
- .dropdown {
- position: relative;
- }
- .dropdown-menu {
- position: absolute;
- top: 100%;
- left: 0;
- margin-top: 4px;
- background: white;
- border-radius: 12px;
- box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
- padding: 8px;
- min-width: 160px;
- display: none;
- z-index: 1000;
- }
- .dropdown.active .dropdown-menu {
- display: block;
- }
- .dropdown-item {
- padding: 10px 12px;
- border-radius: 8px;
- cursor: pointer;
- transition: all 0.2s;
- display: flex;
- align-items: center;
- gap: 10px;
- font-size: 13px;
- color: #374151;
- }
- .dropdown-item:hover {
- background: #f3f4f6;
- }
- .dropdown-item svg {
- width: 18px;
- height: 18px;
- color: #6b7280;
- }
- /* 底部大纲 */
- .bottom-outline {
- height: 150px;
- background: #fafbfc;
- border-top: 1px solid #f0f1f3;
- padding: 16px 24px;
- overflow-x: auto;
- overflow-y: hidden;
- display: none;
- }
- .bottom-outline.visible {
- display: block;
- }
- .outline-track {
- display: flex;
- gap: 14px;
- height: 100%;
- align-items: center;
- }
- .outline-item-wrapper {
- position: relative;
- display: flex;
- align-items: center;
- gap: 14px;
- z-index: 1;
- }
- .outline-item-wrapper:has(.page-menu.active) {
- z-index: 10000;
- }
- .outline-item {
- width: 190px;
- height: 110px;
- flex-shrink: 0;
- border-radius: 12px;
- border: 2px solid #e5e7eb;
- background: white;
- cursor: grab;
- position: relative;
- transition: all 0.2s;
- display: flex;
- flex-direction: column;
- align-items: center;
- justify-content: center;
- font-size: 12px;
- color: #9ca3af;
- }
- .outline-item:active {
- cursor: grabbing;
- }
- .outline-item.dragging {
- opacity: 0.5;
- cursor: grabbing;
- }
- .outline-item.drag-over {
- border-color: #285cf5;
- border-style: dashed;
- background: #f6f8ff;
- }
- .outline-item:hover {
- border-color: #285cf5;
- transform: translateY(-2px);
- box-shadow: 0 4px 8px rgba(40, 92, 245, 0.15);
- z-index: 100;
- }
- .outline-item.active {
- border-color: #285cf5;
- box-shadow: 0 4px 12px rgba(40, 92, 245, 0.2);
- }
- .outline-item:has(.page-menu.active) {
- z-index: 10000;
- }
- .outline-item .page-number {
- position: absolute;
- top: 8px;
- left: 10px;
- font-size: 11px;
- font-weight: 700;
- color: #6b7280;
- background: white;
- width: 24px;
- height: 24px;
- border-radius: 6px;
- display: flex;
- align-items: center;
- justify-content: center;
- }
- /* 页面操作菜单 */
- .page-menu {
- position: absolute;
- bottom: 8px;
- right: 8px;
- width: 28px;
- height: 28px;
- border-radius: 8px;
- background: rgba(255, 255, 255, 0.95);
- border: 1px solid #e5e7eb;
- display: none;
- align-items: center;
- justify-content: center;
- cursor: pointer;
- transition: all 0.2s;
- z-index: 10000;
- }
- .outline-item:hover .page-menu {
- display: flex;
- }
- .page-menu.active {
- z-index: 10001;
- }
- .page-menu:hover {
- background: #285cf5;
- border-color: #285cf5;
- }
- .page-menu:hover svg {
- color: white;
- }
- .page-menu svg {
- width: 16px;
- height: 16px;
- color: #6b7280;
- }
- .page-menu-dropdown {
- position: absolute;
- bottom: 100%;
- right: 0;
- margin-bottom: 4px;
- background: white;
- border-radius: 12px;
- box-shadow: 0 8px 20px rgba(0, 0, 0, 0.2);
- padding: 8px;
- min-width: 120px;
- display: none;
- z-index: 10002;
- }
- .page-menu.active .page-menu-dropdown {
- display: block;
- }
- .page-menu-item {
- padding: 10px 12px;
- border-radius: 8px;
- cursor: pointer;
- transition: all 0.2s;
- display: flex;
- align-items: center;
- gap: 10px;
- font-size: 13px;
- color: #374151;
- }
- .page-menu-item:hover {
- background: #eef3ff;
- }
- .page-menu-item:hover svg {
- color: #285cf5;
- }
- .page-menu-item.danger:hover {
- background: #fef2f2;
- color: #dc2626;
- }
- .page-menu-item.danger:hover svg {
- color: #dc2626;
- }
- .page-menu-item svg {
- width: 16px;
- height: 16px;
- color: #6b7280;
- }
- /* 页面间添加按钮 */
- .add-page-between {
- width: 40px;
- height: 40px;
- border-radius: 50%;
- border: 2px dashed #d1d5db;
- background: white;
- cursor: pointer;
- display: none;
- align-items: center;
- justify-content: center;
- color: #9ca3af;
- font-size: 20px;
- transition: all 0.2s;
- flex-shrink: 0;
- }
- .outline-item-wrapper:hover .add-page-between {
- display: flex;
- }
- .add-page-between:hover {
- border-color: #285cf5;
- border-style: solid;
- color: #285cf5;
- background: #f6f8ff;
- }
- /* ========== 页面模板二级菜单 ========== */
- .template-grid {
- display: grid;
- grid-template-columns: repeat(2, 1fr);
- gap: 14px;
- padding: 20px;
- }
- .template-card {
- background: #fafbfc;
- border: 1.5px solid #e5e7eb;
- border-radius: 14px;
- padding: 14px;
- cursor: pointer;
- transition: all 0.2s;
- position: relative;
- overflow: hidden;
- }
- .template-card:hover {
- border-color: #285cf5;
- background: #f6f8ff;
- transform: translateY(-2px);
- box-shadow: 0 4px 12px rgba(40, 92, 245, 0.15);
- }
- .template-card:hover .template-preview {
- opacity: 1;
- transform: translateY(0);
- }
- .template-preview {
- width: 100%;
- height: 100px;
- background: white;
- border-radius: 8px;
- margin-bottom: 12px;
- display: flex;
- align-items: center;
- justify-content: center;
- border: 1px solid #e5e7eb;
- position: relative;
- opacity: 0.9;
- transform: translateY(-2px);
- transition: all 0.3s;
- }
- .template-preview svg {
- width: 48px;
- height: 48px;
- color: #d1d5db;
- }
- .template-card:hover .template-preview svg {
- color: #285cf5;
- }
- .template-name {
- font-size: 14px;
- font-weight: 600;
- color: #111827;
- text-align: center;
- }
- .upload-ppt-card {
- grid-column: 1 / -1;
- background: #fafbfc;
- border: 1.5px solid #e5e7eb;
- display: flex;
- align-items: center;
- justify-content: center;
- gap: 12px;
- padding: 14px;
- border-radius: 14px;
- }
- .upload-ppt-card:hover {
- background: #f6f8ff;
- border-color: #285cf5;
- box-shadow: 0 4px 12px rgba(40, 92, 245, 0.15);
- transform: translateY(-2px);
- }
- .upload-ppt-card svg {
- width: 28px;
- height: 28px;
- color: #d1d5db;
- }
- .upload-ppt-card:hover svg {
- color: #285cf5;
- }
- .upload-ppt-text {
- font-size: 14px;
- font-weight: 600;
- color: #111827;
- }
- /* ========== 图片悬浮菜单 ========== */
- .image-hover-menu {
- position: absolute;
- top: 50%;
- left: 50%;
- transform: translate(-50%, -50%);
- background: white;
- border-radius: 12px;
- box-shadow: 0 8px 24px rgba(0, 0, 0, 0.15);
- padding: 8px;
- display: none;
- flex-direction: column;
- gap: 4px;
- z-index: 1000;
- min-width: 160px;
- }
- .slide-element.has-image:hover .image-hover-menu {
- display: flex;
- }
- .image-menu-item {
- padding: 12px 14px;
- border-radius: 8px;
- cursor: pointer;
- transition: all 0.2s;
- display: flex;
- align-items: center;
- gap: 10px;
- font-size: 13px;
- color: #374151;
- font-weight: 500;
- }
- .image-menu-item:hover {
- background: #eef3ff;
- color: #285cf5;
- }
- .image-menu-item svg {
- width: 18px;
- height: 18px;
- flex-shrink: 0;
- }
- /* ========== 搜索/生成浮窗 ========== */
- .floating-modal {
- position: fixed;
- top: 0;
- left: 0;
- right: 0;
- bottom: 0;
- background: rgba(0, 0, 0, 0.4);
- backdrop-filter: blur(4px);
- display: none;
- align-items: center;
- justify-content: center;
- z-index: 2000;
- opacity: 0;
- transition: opacity 0.3s;
- }
- .floating-modal.active {
- display: flex;
- opacity: 1;
- }
- .floating-content {
- background: white;
- border-radius: 20px;
- padding: 32px;
- max-width: 600px;
- width: 90%;
- max-height: 80vh;
- overflow-y: auto;
- box-shadow: 0 20px 60px rgba(0, 0, 0, 0.2);
- transform: scale(0.95);
- transition: transform 0.3s;
- }
- .floating-modal.active .floating-content {
- transform: scale(1);
- }
- .floating-header {
- display: flex;
- align-items: center;
- justify-content: space-between;
- margin-bottom: 24px;
- }
- .floating-title {
- font-size: 20px;
- font-weight: 700;
- color: #111827;
- }
- .floating-close {
- width: 32px;
- height: 32px;
- border-radius: 8px;
- border: none;
- background: #f3f4f6;
- cursor: pointer;
- display: flex;
- align-items: center;
- justify-content: center;
- transition: all 0.2s;
- }
- .floating-close:hover {
- background: #e5e7eb;
- }
- .search-input-group {
- margin-bottom: 20px;
- }
- .search-input-label {
- font-size: 13px;
- font-weight: 600;
- color: #6b7280;
- margin-bottom: 8px;
- display: block;
- }
- .search-input {
- width: 100%;
- padding: 14px 16px;
- border: 1.5px solid #e5e7eb;
- border-radius: 12px;
- font-size: 14px;
- color: #111827;
- background: #fafbfc;
- transition: all 0.2s;
- font-family: inherit;
- }
- .search-input:focus {
- outline: none;
- border-color: #285cf5;
- background: white;
- }
- /* ========== AI应用中心浮窗 ========== */
- .app-center-content {
- background: white;
- border-radius: 20px;
- padding: 32px;
- max-width: 900px;
- width: 90%;
- max-height: 85vh;
- display: flex;
- flex-direction: column;
- box-shadow: 0 20px 60px rgba(0, 0, 0, 0.2);
- transform: scale(0.95);
- transition: transform 0.3s;
- }
- .floating-modal.active .app-center-content {
- transform: scale(1);
- }
- .app-filters {
- display: flex;
- gap: 16px;
- margin-bottom: 24px;
- padding-bottom: 20px;
- border-bottom: 1px solid #e5e7eb;
- }
- .filter-group {
- display: flex;
- flex-direction: column;
- gap: 8px;
- flex: 1;
- position: relative;
- }
- .filter-label {
- font-size: 13px;
- font-weight: 600;
- color: #6b7280;
- }
- .filter-select {
- height: 40px;
- padding: 0 36px 0 14px;
- border: 1.5px solid #e5e7eb;
- border-radius: 10px;
- font-size: 13px;
- color: #111827;
- background: #fafbfc;
- cursor: pointer;
- transition: all 0.2s;
- outline: none;
- appearance: none;
- font-weight: 500;
- background-image: url("data:image/svg+xml,%3Csvg width='16' height='16' viewBox='0 0 24 24' fill='none' stroke='%236b7280' stroke-width='2' xmlns='http://www.w3.org/2000/svg'%3E%3Cpolyline points='6 9 12 15 18 9'/%3E%3C/svg%3E");
- background-repeat: no-repeat;
- background-position: right 12px center;
- background-size: 16px;
- }
- .filter-select:hover {
- border-color: #285cf5;
- background-color: #f6f8ff;
- background-image: url("data:image/svg+xml,%3Csvg width='16' height='16' viewBox='0 0 24 24' fill='none' stroke='%23285cf5' stroke-width='2' xmlns='http://www.w3.org/2000/svg'%3E%3Cpolyline points='6 9 12 15 18 9'/%3E%3C/svg%3E");
- }
- .filter-select:focus {
- border-color: #285cf5;
- background-color: white;
- }
- .filter-select option {
- padding: 10px;
- font-size: 13px;
- }
- .app-grid {
- display: grid;
- grid-template-columns: repeat(3, 1fr);
- gap: 16px;
- flex: 1;
- overflow-y: auto;
- padding: 4px;
- }
- .app-card {
- background: #fafbfc;
- border: 1.5px solid #e5e7eb;
- border-radius: 14px;
- padding: 16px;
- cursor: pointer;
- transition: all 0.2s;
- position: relative;
- display: flex;
- flex-direction: column;
- }
- .app-card:hover {
- border-color: #285cf5;
- background: #f6f8ff;
- transform: translateY(-2px);
- box-shadow: 0 4px 12px rgba(40, 92, 245, 0.15);
- }
- .app-card.selected {
- border-color: #285cf5;
- background: #f6f8ff;
- }
- .app-card.selected::after {
- content: '';
- position: absolute;
- top: 10px;
- right: 10px;
- width: 20px;
- height: 20px;
- background: #285cf5;
- border-radius: 50%;
- background-image: url("data:image/svg+xml,%3Csvg viewBox='0 0 24 24' fill='none' stroke='white' stroke-width='3' xmlns='http://www.w3.org/2000/svg'%3E%3Cpolyline points='20 6 9 17 4 12'/%3E%3C/svg%3E");
- background-size: 14px;
- background-position: center;
- background-repeat: no-repeat;
- }
- .app-cover {
- width: 100%;
- aspect-ratio: 16/9;
- border-radius: 10px;
- background: linear-gradient(135deg, #eef3ff 0%, #f6f8ff 100%);
- display: flex;
- align-items: center;
- justify-content: center;
- margin-bottom: 12px;
- border: 1px solid #dbe6ff;
- }
- .app-cover svg {
- width: 36px;
- height: 36px;
- color: #285cf5;
- }
- .app-info {
- flex: 1;
- }
- .app-name {
- font-size: 14px;
- font-weight: 600;
- color: #111827;
- margin-bottom: 4px;
- }
- .app-description {
- font-size: 12px;
- color: #6b7280;
- line-height: 1.4;
- }
- .app-meta {
- display: flex;
- gap: 6px;
- margin-top: 8px;
- flex-wrap: wrap;
- }
- .app-tag {
- font-size: 11px;
- padding: 3px 8px;
- border-radius: 6px;
- background: #f3f4f6;
- color: #6b7280;
- }
- .app-center-footer {
- display: flex;
- justify-content: space-between;
- align-items: center;
- padding-top: 20px;
- margin-top: 20px;
- border-top: 1px solid #e5e7eb;
- }
- .selected-count {
- font-size: 14px;
- color: #6b7280;
- }
- .selected-count span {
- font-weight: 600;
- color: #285cf5;
- }
- .app-center-actions {
- display: flex;
- gap: 12px;
- }
- .app-center-btn {
- padding: 10px 20px;
- border-radius: 10px;
- border: none;
- font-size: 14px;
- font-weight: 600;
- cursor: pointer;
- transition: all 0.2s;
- }
- .app-center-btn-cancel {
- background: #f3f4f6;
- color: #6b7280;
- }
- .app-center-btn-cancel:hover {
- background: #e5e7eb;
- }
- .app-center-btn-confirm {
- background: #285cf5;
- color: white;
- display: flex;
- align-items: center;
- gap: 6px;
- }
- .app-center-btn-confirm:hover {
- background: #1f4ad6;
- }
- .app-center-btn-confirm:disabled {
- opacity: 0.5;
- cursor: not-allowed;
- }
- /* ========== AI创建应用浮窗 ========== */
- .ai-create-app-content {
- background: white;
- border-radius: 20px;
- padding: 32px;
- max-width: 1000px;
- width: 90%;
- max-height: 85vh;
- display: flex;
- flex-direction: column;
- box-shadow: 0 20px 60px rgba(0, 0, 0, 0.2);
- transform: scale(0.95);
- transition: transform 0.3s;
- }
- .floating-modal.active .ai-create-app-content {
- transform: scale(1);
- }
- .ai-create-main {
- display: grid;
- grid-template-columns: 1fr 360px;
- gap: 24px;
- flex: 1;
- min-height: 0;
- }
- .app-canvas-preview {
- border: 2px dashed #e5e7eb;
- border-radius: 16px;
- background: #fafbfc;
- display: flex;
- align-items: center;
- justify-content: center;
- color: #9ca3af;
- font-size: 14px;
- overflow: auto;
- padding: 20px;
- }
- .ai-chat-panel {
- display: flex;
- flex-direction: column;
- border: 1.5px solid #e5e7eb;
- border-radius: 16px;
- background: #fafbfc;
- overflow: hidden;
- }
- .ai-chat-messages {
- flex: 1;
- overflow-y: auto;
- padding: 16px;
- display: flex;
- flex-direction: column;
- gap: 12px;
- }
- .ai-chat-message {
- display: flex;
- gap: 10px;
- max-width: 85%;
- }
- .ai-chat-message.user {
- align-self: flex-end;
- flex-direction: row-reverse;
- }
- .chat-avatar {
- width: 32px;
- height: 32px;
- border-radius: 50%;
- background: #285cf5;
- display: flex;
- align-items: center;
- justify-content: center;
- flex-shrink: 0;
- color: white;
- font-size: 14px;
- font-weight: 600;
- }
- .ai-chat-message.user .chat-avatar {
- background: #3b82f6;
- }
- .chat-bubble {
- background: white;
- padding: 10px 14px;
- border-radius: 12px;
- font-size: 13px;
- line-height: 1.5;
- color: #111827;
- border: 1px solid #e5e7eb;
- }
- .ai-chat-message.user .chat-bubble {
- background: #3b82f6;
- color: white;
- border-color: #3b82f6;
- }
- .ai-chat-input-area {
- border-top: 1px solid #e5e7eb;
- padding: 12px;
- background: white;
- }
- .ai-chat-input-wrapper {
- display: flex;
- gap: 8px;
- }
- .ai-chat-input {
- flex: 1;
- padding: 10px 12px;
- border: 1.5px solid #e5e7eb;
- border-radius: 10px;
- font-size: 13px;
- font-family: inherit;
- resize: none;
- min-height: 40px;
- max-height: 100px;
- }
- .ai-chat-input:focus {
- outline: none;
- border-color: #285cf5;
- }
- .ai-chat-send-btn {
- width: 40px;
- height: 40px;
- border-radius: 10px;
- border: none;
- background: #285cf5;
- color: white;
- cursor: pointer;
- display: flex;
- align-items: center;
- justify-content: center;
- transition: all 0.2s;
- flex-shrink: 0;
- }
- .ai-chat-send-btn:hover {
- background: #1f4ad6;
- }
- .ai-chat-send-btn:disabled {
- opacity: 0.5;
- cursor: not-allowed;
- }
- .ai-chat-send-btn svg {
- width: 18px;
- height: 18px;
- }
- .ai-create-footer {
- display: flex;
- justify-content: flex-end;
- gap: 12px;
- padding-top: 20px;
- margin-top: 20px;
- border-top: 1px solid #e5e7eb;
- }
- .search-results {
- display: grid;
- grid-template-columns: repeat(3, 1fr);
- gap: 12px;
- margin-top: 20px;
- }
- .search-result-item {
- aspect-ratio: 16/9;
- background: #e5e7eb;
- border-radius: 10px;
- cursor: pointer;
- transition: all 0.2s;
- border: 2px solid transparent;
- overflow: hidden;
- position: relative;
- }
- .search-result-item:hover {
- border-color: #285cf5;
- transform: scale(1.05);
- }
- .search-result-item img {
- width: 100%;
- height: 100%;
- object-fit: cover;
- }
- .floating-actions {
- display: flex;
- gap: 12px;
- margin-top: 24px;
- }
- .floating-btn {
- flex: 1;
- padding: 14px 24px;
- border-radius: 12px;
- border: none;
- font-size: 14px;
- font-weight: 600;
- cursor: pointer;
- transition: all 0.2s;
- }
- .floating-btn-secondary {
- background: #f3f4f6;
- color: #374151;
- }
- .floating-btn-secondary:hover {
- background: #e5e7eb;
- }
- .floating-btn-primary {
- background: #285cf5;
- color: white;
- }
- .floating-btn-primary:hover {
- background: #1f4ad6;
- transform: translateY(-1px);
- box-shadow: 0 4px 12px rgba(40, 92, 245, 0.3);
- }
- .generate-preview {
- width: 100%;
- aspect-ratio: 16/9;
- background: #fafbfc;
- border: 1.5px solid #e5e7eb;
- border-radius: 12px;
- display: flex;
- align-items: center;
- justify-content: center;
- margin-top: 20px;
- position: relative;
- overflow: hidden;
- }
- .generate-preview img {
- width: 100%;
- height: 100%;
- object-fit: cover;
- }
- .generate-loading {
- position: absolute;
- inset: 0;
- background: rgba(255, 255, 255, 0.9);
- display: none;
- align-items: center;
- justify-content: center;
- flex-direction: column;
- gap: 12px;
- }
- .generate-loading.active {
- display: flex;
- }
- .loading-spinner {
- width: 40px;
- height: 40px;
- border: 3px solid #f3f4f6;
- border-top-color: #285cf5;
- border-radius: 50%;
- animation: spin 1s linear infinite;
- }
- .loading-text {
- font-size: 13px;
- color: #6b7280;
- }
- /* 右侧面板 */
- .right-panel {
- width: 320px;
- background: white;
- border-radius: 16px;
- box-shadow: 0 2px 8px rgba(0, 0, 0, 0.06);
- display: flex;
- flex-direction: column;
- transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
- position: relative;
- z-index: 50;
- overflow: hidden;
- height: calc(100vh - 32px);
- max-height: calc(100vh - 32px);
- }
- .right-panel.collapsed {
- width: 60px;
- }
- .right-panel.collapsed .menu-content {
- opacity: 0;
- pointer-events: none;
- }
- .right-panel.collapsed .collapse-text,
- .right-panel.collapsed .outline-panel {
- display: none;
- }
- .outline-panel {
- flex: 1;
- display: flex;
- flex-direction: column;
- border-top: 1px solid #f0f1f3;
- overflow: hidden;
- min-height: 0;
- }
- .outline-toolbar {
- display: flex;
- align-items: center;
- justify-content: space-between;
- padding: 10px 12px;
- gap: 8px;
- border-bottom: 1px solid #f3f4f6;
- }
- .outline-toolbar-left {
- display: flex;
- align-items: center;
- gap: 6px;
- font-weight: 600;
- color: #111827;
- }
- .outline-actions {
- display: flex;
- gap: 6px;
- }
- .outline-btn {
- height: 32px;
- padding: 0 10px;
- border-radius: 8px;
- border: 1px solid #e5e7eb;
- background: #f9fafb;
- color: #374151;
- font-size: 12px;
- display: inline-flex;
- align-items: center;
- gap: 6px;
- cursor: pointer;
- transition: all 0.2s;
- }
- .outline-btn:hover {
- background: #eef3ff;
- border-color: #285cf5;
- color: #1f4ad6;
- }
- .outline-list {
- flex: 1;
- overflow-y: auto;
- padding: 8px 10px 12px;
- display: flex;
- flex-direction: column;
- gap: 6px;
- min-height: 0;
- }
- .outline-group {
- background: #f9fafb;
- border: 1px solid #e5e7eb;
- border-radius: 10px;
- padding: 8px;
- display: flex;
- flex-direction: column;
- gap: 6px;
- }
- .outline-group-header {
- display: flex;
- align-items: center;
- justify-content: space-between;
- padding: 6px 8px;
- border-radius: 8px;
- background: #fff;
- cursor: pointer;
- }
- .outline-group-left {
- display: flex;
- align-items: center;
- gap: 8px;
- font-weight: 600;
- color: #1f2937;
- }
- .outline-group-title {
- max-width: 160px;
- overflow: hidden;
- text-overflow: ellipsis;
- white-space: nowrap;
- font-size: 14px;
- }
- .outline-items {
- display: flex;
- flex-direction: column;
- gap: 6px;
- padding-top: 4px;
- }
- .outline-item {
- background: #fff;
- border: 1px solid #e5e7eb;
- border-radius: 8px;
- padding: 8px 10px;
- display: flex;
- align-items: center;
- justify-content: space-between;
- gap: 8px;
- cursor: grab;
- transition: all 0.15s;
- }
- .outline-item:hover {
- border-color: #285cf5;
- box-shadow: 0 2px 6px rgba(0,0,0,0.05);
- }
- .outline-item.dragging {
- opacity: 0.7;
- }
- .outline-item-left {
- display: flex;
- align-items: center;
- gap: 8px;
- min-width: 0;
- }
- .outline-index {
- width: 20px;
- height: 20px;
- border-radius: 6px;
- background: #eef3ff;
- color: #1f4ad6;
- display: inline-flex;
- align-items: center;
- justify-content: center;
- font-size: 12px;
- font-weight: 700;
- flex-shrink: 0;
- }
- .outline-title {
- font-size: 13px;
- color: #111827;
- font-weight: 600;
- white-space: nowrap;
- overflow: hidden;
- text-overflow: ellipsis;
- max-width: 140px;
- }
- .outline-type {
- padding: 2px 8px;
- border-radius: 999px;
- background: #f3f4f6;
- color: #4b5563;
- font-size: 11px;
- font-weight: 600;
- flex-shrink: 0;
- }
- .outline-handle {
- color: #9ca3af;
- display: inline-flex;
- align-items: center;
- cursor: pointer;
- opacity: 0;
- pointer-events: none;
- transition: opacity 0.15s ease;
- }
- .outline-item:hover .outline-handle {
- opacity: 1;
- pointer-events: auto;
- }
- .outline-drag-handle {
- color: #9ca3af;
- display: inline-flex;
- align-items: center;
- cursor: grab;
- opacity: 0;
- pointer-events: none;
- transition: opacity 0.15s ease;
- }
- .outline-group:hover .outline-drag-handle {
- opacity: 1;
- pointer-events: auto;
- }
- .outline-delete-btn {
- color: #9ca3af;
- display: inline-flex;
- align-items: center;
- cursor: pointer;
- padding: 4px;
- border-radius: 8px;
- }
- .outline-delete-btn:hover {
- color: #ef4444;
- background: #fef2f2;
- }
- .outline-group-input {
- font-size: 13px;
- font-weight: 600;
- color: #111827;
- padding: 6px 10px;
- border: 1.5px solid #285cf5;
- border-radius: 8px;
- outline: none;
- box-shadow: 0 0 0 3px rgba(40, 92, 245, 0.1);
- min-width: 120px;
- max-width: 220px;
- }
- .outline-empty {
- padding: 12px;
- text-align: center;
- color: #9ca3af;
- font-size: 13px;
- border: 1px dashed #e5e7eb;
- border-radius: 10px;
- }
- /* ========== 交互网页样式 ========== */
- /* 网页配置表单 */
- .web-config-form {
- padding: 20px;
- }
- .form-group {
- margin-bottom: 24px;
- }
- .form-label {
- display: block;
- font-size: 13px;
- font-weight: 600;
- color: #374151;
- margin-bottom: 8px;
- }
- .form-input {
- width: 100%;
- height: 44px;
- padding: 0 14px;
- border: 1.5px solid #e5e7eb;
- border-radius: 10px;
- font-size: 14px;
- color: #111827;
- background: white;
- transition: all 0.2s;
- outline: none;
- }
- .form-input:focus {
- border-color: #285cf5;
- background: #f6f8ff;
- }
- .form-input::placeholder {
- color: #9ca3af;
- }
- .form-hint {
- margin-top: 6px;
- font-size: 12px;
- color: #6b7280;
- }
- /* 文件上传区域 */
- #uploadFilePanel .form-group {
- display: flex;
- flex-direction: column;
- gap: 12px;
- }
- .file-upload-area {
- border: 2px dashed #e5e7eb;
- border-radius: 12px;
- background: #fafbfc;
- padding: 40px 20px;
- text-align: center;
- cursor: pointer;
- transition: all 0.2s;
- display: flex;
- flex-direction: column;
- align-items: center;
- justify-content: center;
- gap: 10px;
- min-height: 190px; /* 对齐粘贴代码区域高度 */
- }
- .file-upload-area:hover {
- border-color: #285cf5;
- background: #f6f8ff;
- }
- .file-upload-area.dragover {
- border-color: #285cf5;
- background: #f6f8ff;
- border-style: solid;
- }
- .file-upload-area svg {
- color: #9ca3af;
- margin-bottom: 12px;
- }
- .upload-text {
- font-size: 14px;
- font-weight: 500;
- color: #374151;
- margin-bottom: 6px;
- }
- .upload-hint {
- font-size: 12px;
- color: #6b7280;
- }
- /* 状态提示 */
- .web-status {
- margin-top: 16px;
- padding: 12px 16px;
- background: #f9fafb;
- border-radius: 10px;
- display: flex;
- align-items: center;
- gap: 10px;
- }
- .web-status .status-icon {
- font-size: 18px;
- }
- .web-status .status-text {
- font-size: 13px;
- color: #6b7280;
- font-weight: 500;
- }
- .web-status.loading .status-icon {
- animation: spin 1s linear infinite;
- }
- .web-status.success {
- background: #f0fdf4;
- border: 1px solid #86efac;
- }
- .web-status.success .status-icon {
- color: #22c55e;
- }
- .web-status.success .status-text {
- color: #16a34a;
- }
- .web-status.error {
- background: #fef2f2;
- border: 1px solid #fecaca;
- }
- .web-status.error .status-icon {
- color: #ef4444;
- }
- .web-status.error .status-text {
- color: #dc2626;
- }
- @keyframes spin {
- from { transform: rotate(0deg); }
- to { transform: rotate(360deg); }
- }
- /* 配置操作按钮 */
- .web-config-actions {
- display: flex;
- gap: 12px;
- margin-top: 24px;
- }
- .config-btn {
- flex: 1;
- height: 44px;
- border-radius: 10px;
- border: none;
- font-size: 14px;
- font-weight: 600;
- cursor: pointer;
- transition: all 0.2s;
- }
- .config-btn-secondary {
- background: #f3f4f6;
- color: #6b7280;
- }
- .config-btn-secondary:hover {
- background: #e5e7eb;
- }
- .config-btn-primary {
- background: #285cf5;
- color: white;
- }
- .config-btn-primary:hover {
- background: #1f4ad6;
- }
- .config-btn-primary:disabled {
- opacity: 0.5;
- cursor: not-allowed;
- }
- .status-btn,
- .crawl-btn {
- display: inline-flex;
- align-items: center;
- justify-content: center;
- gap: 8px;
- }
- .status-btn .btn-status-icon,
- .crawl-btn .btn-status-icon {
- display: inline-flex;
- align-items: center;
- justify-content: center;
- }
- .status-btn .btn-status-icon svg,
- .crawl-btn .btn-status-icon svg {
- width: 16px;
- height: 16px;
- stroke: currentColor;
- fill: none;
- }
- .status-btn.status-loading,
- .crawl-btn.status-loading {
- opacity: 0.9;
- }
- .status-btn.status-loading .btn-status-icon svg,
- .crawl-btn.status-loading .btn-status-icon svg {
- animation: spin 1s linear infinite;
- }
- .status-btn.status-success,
- .crawl-btn.status-success {
- background: #f0fdf4;
- color: #16a34a;
- border: 1px solid #86efac;
- }
- .status-btn.status-error,
- .crawl-btn.status-error {
- background: #fef2f2;
- color: #dc2626;
- border: 1px solid #fecaca;
- }
- /* Upload Tabs */
- .upload-tabs {
- display: flex;
- margin-bottom: 20px;
- border-bottom: 1px solid #e5e7eb;
- }
- .upload-tab {
- padding: 10px 16px;
- border: none;
- background-color: transparent;
- cursor: pointer;
- font-size: 14px;
- color: #6b7280;
- margin-bottom: -1px;
- border-bottom: 2px solid transparent;
- }
- .upload-tab.active {
- color: #285cf5;
- border-bottom-color: #285cf5;
- font-weight: 600;
- }
- .code-textarea {
- width: 100%;
- min-height: 190px; /* Increased height to match file upload area */
- border: 1px solid #d1d5db;
- border-radius: 8px;
- padding: 12px;
- font-family: monospace;
- font-size: 13px;
- resize: vertical;
- transition: border-color 0.2s;
- }
- .code-textarea:focus {
- outline: none;
- border-color: #285cf5;
- }
- .file-name-display {
- margin-top: 12px;
- font-size: 13px;
- color: #4b5563;
- background-color: #f9fafb;
- border-radius: 6px;
- padding: 8px 12px;
- border: 1px solid #e5e7eb;
- display: none; /* Hidden by default */
- }
- /* 网页中心浮窗 */
- .web-center-content {
- background: white;
- border-radius: 20px;
- padding: 32px;
- max-width: 900px;
- width: 90%;
- max-height: 85vh;
- display: flex;
- flex-direction: column;
- box-shadow: 0 20px 60px rgba(0, 0, 0, 0.2);
- transform: scale(0.95);
- transition: transform 0.3s;
- }
- .floating-modal.active .web-center-content {
- transform: scale(1);
- }
- .web-filters {
- display: flex;
- gap: 16px;
- margin-bottom: 24px;
- padding-bottom: 20px;
- border-bottom: 1px solid #e5e7eb;
- }
- .web-grid {
- display: grid;
- grid-template-columns: repeat(3, 1fr);
- gap: 16px;
- flex: 1;
- overflow-y: auto;
- padding: 4px;
- }
- .web-card {
- background: #fafbfc;
- border: 1.5px solid #e5e7eb;
- border-radius: 14px;
- padding: 16px;
- cursor: pointer;
- transition: all 0.2s;
- position: relative;
- display: flex;
- flex-direction: column;
- }
- .web-card:hover {
- border-color: #285cf5;
- background: #f6f8ff;
- transform: translateY(-2px);
- box-shadow: 0 4px 12px rgba(40, 92, 245, 0.15);
- }
- .web-card.selected {
- border-color: #285cf5;
- background: #f6f8ff;
- }
- .web-card.selected::after {
- content: '';
- position: absolute;
- top: 10px;
- right: 10px;
- width: 20px;
- height: 20px;
- background: #285cf5;
- border-radius: 50%;
- background-image: url("data:image/svg+xml,%3Csvg viewBox='0 0 24 24' fill='none' stroke='white' stroke-width='3' xmlns='http://www.w3.org/2000/svg'%3E%3Cpolyline points='20 6 9 17 4 12'/%3E%3C/svg%3E");
- background-size: 14px;
- background-position: center;
- background-repeat: no-repeat;
- }
- .web-preview {
- width: 100%;
- aspect-ratio: 16/9;
- border-radius: 10px;
- background: linear-gradient(135deg, #eef3ff 0%, #f6f8ff 100%);
- display: flex;
- align-items: center;
- justify-content: center;
- margin-bottom: 12px;
- border: 1px solid #dbe6ff;
- overflow: hidden;
- }
- .web-preview svg {
- width: 36px;
- height: 36px;
- color: #285cf5;
- }
- .web-preview img {
- width: 100%;
- height: 100%;
- object-fit: cover;
- }
- .web-info {
- flex: 1;
- }
- .web-name {
- font-size: 14px;
- font-weight: 600;
- color: #111827;
- margin-bottom: 4px;
- }
- .web-description {
- font-size: 12px;
- color: #6b7280;
- line-height: 1.5;
- margin-bottom: 8px;
- display: -webkit-box;
- -webkit-line-clamp: 2;
- -webkit-box-orient: vertical;
- overflow: hidden;
- }
- .web-meta {
- display: flex;
- gap: 8px;
- flex-wrap: wrap;
- }
- .web-tag {
- padding: 3px 8px;
- background: #f3f4f6;
- border-radius: 6px;
- font-size: 11px;
- color: #6b7280;
- font-weight: 500;
- }
- .web-center-footer {
- display: flex;
- align-items: center;
- justify-content: space-between;
- padding-top: 20px;
- margin-top: 20px;
- border-top: 1px solid #e5e7eb;
- }
- /* 网页详情浮窗 */
- .web-detail-modal {
- position: fixed;
- top: 0;
- left: 0;
- right: 0;
- bottom: 0;
- background: rgba(0, 0, 0, 0.7);
- display: none;
- align-items: center;
- justify-content: center;
- z-index: 10001;
- animation: fadeIn 0.3s;
- }
- .web-detail-modal.active {
- display: flex;
- }
- .web-detail-content {
- background: white;
- border-radius: 20px;
- width: 90%;
- max-width: 1200px;
- height: 85vh;
- display: grid;
- grid-template-columns: 1fr 360px;
- overflow: hidden;
- box-shadow: 0 20px 60px rgba(0, 0, 0, 0.3);
- }
- .web-preview-area {
- background: #f9fafb;
- display: flex;
- flex-direction: column;
- border-right: 1px solid #e5e7eb;
- }
- .web-preview-header {
- padding: 20px 24px;
- border-bottom: 1px solid #e5e7eb;
- display: flex;
- align-items: center;
- justify-content: space-between;
- }
- .web-preview-title {
- font-size: 16px;
- font-weight: 600;
- color: #111827;
- }
- .web-fullscreen-btn {
- padding: 8px 12px;
- background: #f3f4f6;
- border: none;
- border-radius: 8px;
- color: #6b7280;
- font-size: 13px;
- cursor: pointer;
- transition: all 0.2s;
- display: flex;
- align-items: center;
- gap: 6px;
- }
- .web-fullscreen-btn:hover {
- background: #e5e7eb;
- color: #374151;
- }
- .web-preview-iframe {
- flex: 1;
- padding: 20px;
- }
- .web-preview-iframe iframe {
- width: 100%;
- height: 100%;
- border: 1px solid #e5e7eb;
- border-radius: 12px;
- background: white;
- }
- .web-info-area {
- background: white;
- display: flex;
- flex-direction: column;
- overflow-y: auto;
- }
- .web-detail-header {
- padding: 24px;
- border-bottom: 1px solid #e5e7eb;
- }
- .web-detail-name {
- font-size: 18px;
- font-weight: 600;
- color: #111827;
- margin-bottom: 12px;
- }
- .web-meta-item {
- display: flex;
- align-items: center;
- gap: 8px;
- margin-bottom: 8px;
- font-size: 13px;
- color: #6b7280;
- }
- .web-meta-item svg {
- width: 16px;
- height: 16px;
- }
- .web-detail-body {
- flex: 1;
- padding: 24px;
- }
- .web-detail-section {
- margin-bottom: 24px;
- }
- .web-detail-section-title {
- font-size: 14px;
- font-weight: 600;
- color: #374151;
- margin-bottom: 8px;
- }
- .web-detail-section-content {
- font-size: 13px;
- color: #6b7280;
- line-height: 1.6;
- }
- .web-detail-footer {
- padding: 20px 24px;
- border-top: 1px solid #e5e7eb;
- display: flex;
- gap: 12px;
- }
- .web-detail-btn {
- flex: 1;
- height: 44px;
- border-radius: 10px;
- border: none;
- font-size: 14px;
- font-weight: 600;
- cursor: pointer;
- transition: all 0.2s;
- }
- .web-detail-btn-secondary {
- background: #f3f4f6;
- color: #6b7280;
- }
- .web-detail-btn-secondary:hover {
- background: #e5e7eb;
- }
- .web-detail-btn-primary {
- background: #285cf5;
- color: white;
- }
- .web-detail-btn-primary:hover {
- background: #1f4ad6;
- }
- /* ========== 创建入口弹窗 ========== */
- .modal-overlay {
- position: fixed;
- top: 0;
- left: 0;
- right: 0;
- bottom: 0;
- background: rgba(0, 0, 0, 0.5);
- backdrop-filter: blur(4px);
- display: flex;
- align-items: center;
- justify-content: center;
- z-index: 1000;
- opacity: 0;
- pointer-events: none;
- transition: opacity 0.3s;
- }
- .modal-overlay.active {
- opacity: 1;
- pointer-events: all;
- }
- .settings-modal {
- background: #fff;
- border-radius: 16px;
- padding: 20px;
- width: 360px;
- box-shadow: 0 16px 40px rgba(0,0,0,0.16);
- display: flex;
- flex-direction: column;
- gap: 16px;
- position: relative;
- }
- .settings-header {
- display: flex;
- align-items: center;
- justify-content: space-between;
- margin-bottom: 4px;
- }
- .settings-title {
- font-size: 16px;
- font-weight: 700;
- color: #111827;
- }
- .settings-body {
- display: flex;
- flex-direction: column;
- gap: 12px;
- }
- .settings-row {
- display: flex;
- gap: 12px;
- }
- .settings-actions {
- display: flex;
- justify-content: flex-end;
- gap: 10px;
- }
- .settings-btn {
- height: 36px;
- padding: 0 14px;
- border-radius: 10px;
- border: 1px solid #e5e7eb;
- background: #f9fafb;
- color: #374151;
- font-size: 13px;
- font-weight: 600;
- cursor: pointer;
- transition: all 0.2s;
- }
- .settings-btn:hover {
- background: #fff;
- border-color: #d1d5db;
- }
- .settings-btn.primary {
- background: #285cf5;
- color: #fff;
- border-color: #285cf5;
- box-shadow: 0 4px 12px rgba(40, 92, 245, 0.24);
- }
- .settings-btn.primary:hover {
- background: #1f4ad6;
- }
- .confirm-modal {
- background: #fff;
- border-radius: 16px;
- padding: 22px;
- width: 360px;
- box-shadow: 0 16px 40px rgba(0,0,0,0.16);
- display: flex;
- flex-direction: column;
- gap: 14px;
- }
- .confirm-title {
- font-size: 16px;
- font-weight: 700;
- color: #111827;
- }
- .confirm-text {
- font-size: 13px;
- color: #4b5563;
- line-height: 1.6;
- }
- .confirm-actions {
- display: flex;
- justify-content: flex-end;
- gap: 10px;
- }
- /* PPT解析浮窗 */
- .ppt-parse-overlay {
- position: fixed;
- inset: 0;
- background: rgba(0, 0, 0, 0.4);
- backdrop-filter: blur(2px);
- display: flex;
- align-items: center;
- justify-content: center;
- z-index: 1200;
- opacity: 0;
- pointer-events: none;
- transition: opacity 0.25s ease;
- }
- .ppt-parse-overlay.active {
- opacity: 1;
- pointer-events: all;
- }
- .ppt-parse-card {
- background: #ffffff;
- border-radius: 16px;
- padding: 24px;
- width: 360px;
- box-shadow: 0 16px 40px rgba(0,0,0,0.16);
- display: flex;
- flex-direction: column;
- gap: 14px;
- text-align: center;
- }
- .ppt-parse-icon {
- width: 56px;
- height: 56px;
- border-radius: 14px;
- margin: 0 auto;
- display: flex;
- align-items: center;
- justify-content: center;
- background: #eef3ff;
- color: #285cf5;
- }
- .ppt-parse-icon svg {
- width: 26px;
- height: 26px;
- stroke: currentColor;
- fill: none;
- }
- .ppt-parse-icon.success {
- background: #f0fdf4;
- color: #16a34a;
- }
- .ppt-parse-icon.error {
- background: #fef2f2;
- color: #dc2626;
- }
- .ppt-parse-title {
- font-size: 16px;
- font-weight: 700;
- color: #111827;
- }
- .ppt-parse-desc {
- font-size: 13px;
- color: #6b7280;
- }
- .ppt-parse-actions {
- display: flex;
- gap: 10px;
- margin-top: 4px;
- }
- .ppt-parse-btn {
- flex: 1;
- height: 40px;
- border-radius: 10px;
- border: none;
- font-size: 14px;
- font-weight: 600;
- cursor: pointer;
- transition: all 0.2s;
- }
- .ppt-parse-btn.primary {
- background: #285cf5;
- color: white;
- }
- .ppt-parse-btn.primary:hover {
- background: #1f4ad6;
- }
- .ppt-parse-btn.secondary {
- background: #f3f4f6;
- color: #4b5563;
- }
- .ppt-parse-btn.secondary:hover {
- background: #e5e7eb;
- }
- .create-modal {
- background: white;
- border-radius: 20px;
- padding: 40px;
- max-width: 900px;
- width: 90%;
- max-height: 85vh;
- overflow-y: auto;
- box-shadow: 0 20px 60px rgba(0, 0, 0, 0.2);
- transform: scale(0.9);
- transition: transform 0.3s;
- }
- .modal-overlay.active .create-modal {
- transform: scale(1);
- }
- .modal-header {
- text-align: center;
- margin-bottom: 32px;
- }
- .modal-header h2 {
- font-size: 28px;
- font-weight: 700;
- color: #111827;
- margin-bottom: 8px;
- }
- .modal-header p {
- font-size: 15px;
- color: #6b7280;
- }
- .create-options {
- display: grid;
- grid-template-columns: repeat(2, 1fr);
- gap: 16px;
- }
- .create-card {
- background: #fafbfc;
- border: 1.5px solid #e5e7eb;
- border-radius: 16px;
- padding: 24px 20px;
- cursor: pointer;
- transition: all 0.2s;
- display: flex;
- flex-direction: column;
- align-items: center;
- text-align: center;
- position: relative;
- }
- .create-card:hover {
- border-color: #285cf5;
- background: #f6f8ff;
- transform: translateY(-2px);
- box-shadow: 0 4px 12px rgba(40, 92, 245, 0.15);
- }
- .create-card.featured {
- background: linear-gradient(135deg, #eef3ff 0%, #f6f8ff 100%);
- border-color: #285cf5;
- }
- .create-card.featured::before {
- content: '推荐';
- position: absolute;
- top: 12px;
- right: 12px;
- background: #285cf5;
- color: white;
- font-size: 11px;
- font-weight: 600;
- padding: 4px 10px;
- border-radius: 12px;
- }
- .create-icon {
- width: 56px;
- height: 56px;
- border-radius: 14px;
- background: white;
- display: flex;
- align-items: center;
- justify-content: center;
- margin-bottom: 16px;
- }
- .create-icon svg {
- width: 28px;
- height: 28px;
- color: #6b7280;
- }
- .create-card.featured .create-icon svg {
- color: #285cf5;
- }
- .create-card h3 {
- font-size: 16px;
- font-weight: 600;
- color: #111827;
- margin-bottom: 6px;
- }
- .create-card p {
- font-size: 13px;
- color: #6b7280;
- line-height: 1.4;
- }
- .modal-close {
- position: absolute;
- top: 20px;
- right: 20px;
- width: 36px;
- height: 36px;
- border-radius: 10px;
- border: none;
- background: #f3f4f6;
- cursor: pointer;
- display: flex;
- align-items: center;
- justify-content: center;
- transition: all 0.2s;
- }
- .modal-close:hover {
- background: #e5e7eb;
- }
- /* ========== 发布弹窗 ========== */
- .publish-modal {
- background: white;
- border-radius: 20px;
- padding: 28px;
- max-width: 780px;
- width: 90%;
- max-height: 85vh;
- overflow-y: auto;
- box-shadow: 0 20px 60px rgba(0, 0, 0, 0.2);
- transform: scale(0.9);
- transition: transform 0.3s;
- position: relative;
- }
- .modal-overlay.active .publish-modal {
- transform: scale(1);
- }
- .publish-header {
- margin-bottom: 18px;
- padding-bottom: 12px;
- border-bottom: 1px solid #f0f1f3;
- text-align: center;
- }
- .publish-title {
- font-size: 22px;
- font-weight: 700;
- color: #111827;
- margin-bottom: 8px;
- }
- .publish-course-name {
- font-size: 18px;
- font-weight: 600;
- color: #111827;
- padding: 10px 16px;
- border: 1.5px solid transparent;
- border-radius: 10px;
- cursor: text;
- transition: all 0.2s;
- display: inline-block;
- min-width: 120px;
- max-width: 500px;
- }
- .publish-course-name:hover {
- background: #fafbfc;
- border-color: #e5e7eb;
- }
- .publish-course-name-input {
- font-size: 18px;
- font-weight: 600;
- color: #111827;
- padding: 10px 16px;
- border: 1.5px solid #285cf5;
- border-radius: 10px;
- outline: none;
- box-shadow: 0 0 0 3px rgba(40, 92, 245, 0.1);
- min-width: 120px;
- max-width: 500px;
- text-align: center;
- }
- .publish-content {
- display: grid;
- grid-template-columns: 1fr 260px;
- gap: 16px;
- }
- .publish-form {
- display: flex;
- flex-direction: column;
- gap: 14px;
- }
- .publish-cover-section {
- display: flex;
- flex-direction: column;
- gap: 12px;
- }
- .publish-cover-label {
- font-size: 14px;
- font-weight: 600;
- color: #374151;
- }
- .publish-cover-wrapper {
- position: relative;
- width: 100%;
- aspect-ratio: 16/9;
- border-radius: 12px;
- overflow: hidden;
- border: 2px dashed #d1d5db;
- background: #fafbfc;
- cursor: default;
- transition: all 0.2s;
- display: flex;
- align-items: center;
- justify-content: center;
- }
- .publish-cover-wrapper:hover {
- border-color: #285cf5;
- background: #f6f8ff;
- }
- .publish-cover-wrapper img {
- width: 100%;
- height: 100%;
- object-fit: cover;
- position: absolute;
- top: 0;
- left: 0;
- }
- .publish-cover-placeholder {
- color: #9ca3af;
- text-align: center;
- z-index: 1;
- }
- .publish-cover-placeholder svg {
- width: 48px;
- height: 48px;
- margin-bottom: 8px;
- color: #d1d5db;
- }
- .publish-cover-placeholder p {
- font-size: 13px;
- font-weight: 500;
- }
- /* 课程封面悬浮菜单 */
- .publish-cover-menu {
- position: absolute;
- top: 50%;
- left: 50%;
- transform: translate(-50%, -50%);
- background: white;
- border-radius: 12px;
- box-shadow: 0 8px 24px rgba(0, 0, 0, 0.15);
- padding: 8px;
- display: none;
- flex-direction: column;
- gap: 4px;
- z-index: 1000;
- min-width: 160px;
- }
- .publish-cover-wrapper:hover .publish-cover-menu {
- display: flex;
- }
- .publish-cover-menu-item {
- padding: 12px 14px;
- border-radius: 8px;
- cursor: pointer;
- transition: all 0.2s;
- display: flex;
- align-items: center;
- gap: 10px;
- font-size: 13px;
- color: #374151;
- font-weight: 500;
- }
- .publish-cover-menu-item:hover {
- background: #eef3ff;
- color: #285cf5;
- }
- .publish-cover-menu-item svg {
- width: 18px;
- height: 18px;
- flex-shrink: 0;
- }
- .publish-cover-actions {
- position: absolute;
- bottom: 12px;
- right: 12px;
- display: none;
- gap: 8px;
- z-index: 999;
- }
- .publish-cover-wrapper:hover .publish-cover-actions {
- display: flex;
- }
- .cover-action-btn {
- width: 36px;
- height: 36px;
- border-radius: 8px;
- border: none;
- background: rgba(255, 255, 255, 0.95);
- backdrop-filter: blur(4px);
- cursor: pointer;
- display: flex;
- align-items: center;
- justify-content: center;
- transition: all 0.2s;
- box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
- }
- .cover-action-btn:hover {
- background: white;
- transform: translateY(-2px);
- box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
- }
- .cover-action-btn svg {
- width: 18px;
- height: 18px;
- color: #6b7280;
- }
- .cover-action-btn:hover svg {
- color: #285cf5;
- }
- .cover-action-btn.delete:hover svg {
- color: #dc2626;
- }
- .form-group {
- display: flex;
- flex-direction: column;
- gap: 10px;
- }
- .form-label {
- font-size: 14px;
- font-weight: 600;
- color: #374151;
- display: flex;
- align-items: center;
- gap: 6px;
- }
- .form-label .required {
- color: #dc2626;
- }
- .form-select {
- height: 44px;
- padding: 0 16px;
- border: 1.5px solid #e5e7eb;
- border-radius: 12px;
- font-size: 14px;
- color: #111827;
- background: white;
- cursor: pointer;
- transition: all 0.2s;
- outline: none;
- appearance: none;
- background-image: url("data:image/svg+xml,%3Csvg width='12' height='8' viewBox='0 0 12 8' fill='none' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M1 1.5L6 6.5L11 1.5' stroke='%236b7280' stroke-width='1.5' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E");
- background-repeat: no-repeat;
- background-position: right 16px center;
- padding-right: 40px;
- }
- .form-select:hover {
- border-color: #d1d5db;
- }
- .form-select:focus {
- border-color: #285cf5;
- box-shadow: 0 0 0 3px rgba(40, 92, 245, 0.1);
- }
- .form-row {
- display: grid;
- grid-template-columns: 1fr 1fr;
- gap: 16px;
- }
- .visibility-options {
- display: flex;
- flex-direction: column;
- gap: 12px;
- }
- .radio-option {
- display: flex;
- align-items: center;
- padding: 14px 16px;
- border: 1.5px solid #e5e7eb;
- border-radius: 12px;
- cursor: pointer;
- transition: all 0.2s;
- background: white;
- }
- .radio-option:hover {
- border-color: #d1d5db;
- background: #fafbfc;
- }
- .radio-option.selected {
- border-color: #285cf5;
- background: #f6f8ff;
- }
- .radio-option input[type="radio"] {
- display: none;
- }
- .radio-custom {
- width: 20px;
- height: 20px;
- border: 2px solid #d1d5db;
- border-radius: 50%;
- margin-right: 12px;
- position: relative;
- flex-shrink: 0;
- transition: all 0.2s;
- }
- .radio-option.selected .radio-custom {
- border-color: #285cf5;
- }
- .radio-custom::after {
- content: '';
- position: absolute;
- top: 50%;
- left: 50%;
- transform: translate(-50%, -50%) scale(0);
- width: 10px;
- height: 10px;
- border-radius: 50%;
- background: #285cf5;
- transition: transform 0.2s;
- }
- .radio-option.selected .radio-custom::after {
- transform: translate(-50%, -50%) scale(1);
- }
- .radio-content {
- flex: 1;
- }
- .radio-label {
- font-size: 14px;
- font-weight: 600;
- color: #111827;
- margin-bottom: 4px;
- }
- .radio-description {
- font-size: 13px;
- color: #6b7280;
- line-height: 1.4;
- }
- .publish-actions {
- display: flex;
- gap: 10px;
- margin-top: 18px;
- padding-top: 14px;
- border-top: 1px solid #f0f1f3;
- }
- .publish-btn {
- flex: 1;
- height: 44px;
- border-radius: 12px;
- font-size: 15px;
- font-weight: 600;
- cursor: pointer;
- transition: all 0.2s;
- border: none;
- display: flex;
- align-items: center;
- justify-content: center;
- gap: 8px;
- }
- .publish-btn-cancel {
- background: white;
- color: #6b7280;
- border: 1.5px solid #e5e7eb;
- }
- .publish-btn-cancel:hover {
- background: #f9fafb;
- border-color: #d1d5db;
- }
- .publish-btn-confirm {
- background: #285cf5;
- color: white;
- box-shadow: 0 2px 8px rgba(40, 92, 245, 0.2);
- }
- .publish-btn-confirm:hover {
- background: #1f4ad6;
- box-shadow: 0 4px 12px rgba(40, 92, 245, 0.3);
- transform: translateY(-1px);
- }
- .publish-btn-confirm svg {
- width: 18px;
- height: 18px;
- }
- /* ========== 互动工具样式 ========== */
- .tools-intro {
- padding: 20px;
- text-align: center;
- background: #fafbfc;
- border-radius: 12px;
- margin: 16px 20px;
- }
- .tools-intro-image {
- margin-bottom: 12px;
- display: flex;
- justify-content: center;
- }
- .tools-intro-text {
- font-size: 13px;
- color: #6b7280;
- font-weight: 500;
- }
- .tools-grid {
- display: grid;
- grid-template-columns: repeat(2, 1fr);
- gap: 12px;
- padding: 0 20px 20px;
- }
- .tool-card {
- background: white;
- border: 1.5px solid #e5e7eb;
- border-radius: 12px;
- padding: 14px 16px;
- cursor: pointer;
- transition: all 0.2s;
- display: flex;
- flex-direction: row;
- align-items: center;
- gap: 12px;
- }
- .tool-card:hover {
- border-color: #285cf5;
- background: #f6f8ff;
- transform: translateY(-2px);
- box-shadow: 0 4px 12px rgba(40, 92, 245, 0.15);
- }
- .tool-icon {
- width: 40px;
- height: 40px;
- border-radius: 10px;
- background: #f9fafb;
- display: flex;
- align-items: center;
- justify-content: center;
- transition: all 0.2s;
- flex-shrink: 0;
- }
- .tool-card:hover .tool-icon {
- background: #eef3ff;
- }
- .tool-icon svg {
- width: 20px;
- height: 20px;
- color: #6b7280;
- }
- .tool-card:hover .tool-icon svg {
- color: #285cf5;
- }
- .tool-name {
- font-size: 14px;
- font-weight: 600;
- color: #374151;
- flex: 1;
- }
- /* 面板内容包装器 */
- .panel-content-wrapper {
- flex: 1;
- position: relative;
- overflow: hidden;
- }
- /* 工具配置视图 */
- .tools-config-view {
- position: absolute;
- top: 0;
- left: 0;
- right: 0;
- bottom: 0;
- background: white;
- display: flex;
- flex-direction: column;
- z-index: 10;
- }
- .tools-config-view.hidden {
- display: none;
- }
- /* 通用hidden类 */
- .hidden {
- display: none !important;
- }
- .config-header {
- padding: 20px 20px 16px;
- border-bottom: 1px solid #f3f4f6;
- display: flex;
- align-items: center;
- gap: 12px;
- }
- .config-back-btn {
- width: 32px;
- height: 32px;
- border-radius: 8px;
- border: none;
- background: #f9fafb;
- cursor: pointer;
- display: flex;
- align-items: center;
- justify-content: center;
- transition: all 0.2s;
- color: #6b7280;
- }
- .config-back-btn:hover {
- background: #eef3ff;
- color: #285cf5;
- }
- .config-tool-name {
- font-size: 15px;
- font-weight: 600;
- color: #111827;
- }
- .config-content {
- flex: 1;
- overflow-y: auto;
- padding: 20px;
- }
- .config-section {
- margin-bottom: 24px;
- }
- .config-label {
- font-size: 13px;
- font-weight: 600;
- color: #374151;
- margin-bottom: 10px;
- display: block;
- }
- .config-switch {
- display: flex;
- align-items: center;
- justify-content: space-between;
- padding: 14px 16px;
- background: #fafbfc;
- border: 1.5px solid #e5e7eb;
- border-radius: 12px;
- margin-bottom: 10px;
- }
- .config-switch-label {
- font-size: 14px;
- color: #374151;
- font-weight: 500;
- }
- .switch-toggle {
- position: relative;
- width: 48px;
- height: 28px;
- background: #e5e7eb;
- border-radius: 14px;
- cursor: pointer;
- transition: all 0.3s;
- }
- .switch-toggle.active {
- background: #285cf5;
- }
- .switch-toggle::after {
- content: '';
- position: absolute;
- top: 3px;
- left: 3px;
- width: 22px;
- height: 22px;
- background: white;
- border-radius: 50%;
- transition: all 0.3s;
- }
- .switch-toggle.active::after {
- left: 23px;
- }
- .config-mode-list {
- display: flex;
- flex-direction: column;
- gap: 8px;
- }
- .mode-item {
- display: flex;
- align-items: center;
- gap: 12px;
- padding: 12px;
- background: #fafbfc;
- border: 1.5px solid #e5e7eb;
- border-radius: 10px;
- cursor: pointer;
- transition: all 0.2s;
- }
- .mode-item:hover {
- border-color: #285cf5;
- background: #f6f8ff;
- }
- .mode-checkbox {
- width: 20px;
- height: 20px;
- border: 2px solid #d1d5db;
- border-radius: 4px;
- display: flex;
- align-items: center;
- justify-content: center;
- transition: all 0.2s;
- }
- .mode-item.active .mode-checkbox {
- background: #285cf5;
- border-color: #285cf5;
- }
- .mode-checkbox svg {
- width: 14px;
- height: 14px;
- color: white;
- display: none;
- }
- .mode-item.active .mode-checkbox svg {
- display: block;
- }
- .mode-radio {
- width: 20px;
- height: 20px;
- border: 2px solid #d1d5db;
- border-radius: 50%;
- display: flex;
- align-items: center;
- justify-content: center;
- transition: all 0.2s;
- }
- .mode-item.active .mode-radio {
- border-color: #285cf5;
- }
- .mode-radio-dot {
- width: 10px;
- height: 10px;
- border-radius: 50%;
- background: transparent;
- transition: all 0.2s;
- }
- .mode-item.active .mode-radio-dot {
- background: #285cf5;
- }
- .mode-label {
- font-size: 13px;
- color: #374151;
- font-weight: 500;
- }
- /* 填空符按钮 */
- .question-input-wrapper {
- position: relative;
- }
- .add-blank-btn {
- position: absolute;
- right: 8px;
- bottom: 8px;
- height: 32px;
- padding: 0 12px;
- border: 1.5px solid #e5e7eb;
- border-radius: 8px;
- background: white;
- color: #6b7280;
- cursor: pointer;
- transition: all 0.2s;
- display: flex;
- align-items: center;
- gap: 6px;
- font-size: 12px;
- font-weight: 500;
- }
- .add-blank-btn svg {
- width: 14px;
- height: 14px;
- }
- .add-blank-btn:hover {
- border-color: #285cf5;
- color: #285cf5;
- background: #f6f8ff;
- }
- /* 图片上传按钮 */
- .upload-image-btn {
- position: absolute;
- right: 8px;
- bottom: 8px;
- width: 32px;
- height: 32px;
- border: 1.5px solid #e5e7eb;
- border-radius: 8px;
- background: white;
- color: #6b7280;
- cursor: pointer;
- transition: all 0.2s;
- display: flex;
- align-items: center;
- justify-content: center;
- z-index: 5;
- }
- .upload-image-btn svg {
- width: 16px;
- height: 16px;
- }
- .upload-image-btn:hover {
- border-color: #285cf5;
- color: #285cf5;
- background: #f6f8ff;
- }
- /* 带图片按钮的输入框容器 */
- .input-with-image {
- position: relative;
- }
- .input-with-image textarea,
- .input-with-image .option-input {
- padding-right: 48px;
- }
- /* 图片预览 */
- .image-preview {
- position: relative;
- margin-top: 12px;
- margin-bottom: 8px;
- max-width: 200px;
- }
- .image-preview img {
- width: 100%;
- height: auto;
- border-radius: 8px;
- border: 1.5px solid #e5e7eb;
- }
- .remove-image-btn {
- position: absolute;
- top: 8px;
- right: 8px;
- width: 24px;
- height: 24px;
- border-radius: 50%;
- border: none;
- background: rgba(0, 0, 0, 0.6);
- color: white;
- cursor: pointer;
- display: flex;
- align-items: center;
- justify-content: center;
- transition: all 0.2s;
- }
- .remove-image-btn:hover {
- background: rgba(220, 38, 38, 0.9);
- }
- .remove-image-btn svg {
- width: 14px;
- height: 14px;
- }
- /* 排序项样式 */
- .sort-item {
- display: flex;
- align-items: center;
- gap: 12px;
- position: relative;
- padding: 6px 8px;
- border-radius: 10px;
- transition: all 0.2s;
- border: 1px solid transparent;
- }
- .sort-item:hover {
- background: #eef3ff;
- border-color: #285cf5;
- box-shadow: 0 4px 12px rgba(40, 92, 245, 0.16);
- }
- .sort-item:hover .option-actions {
- opacity: 1;
- pointer-events: auto;
- }
- .sort-item-static {
- display: flex;
- align-items: center;
- gap: 12px;
- position: relative;
- padding: 4px;
- border-radius: 8px;
- transition: all 0.2s;
- }
- .sort-item-static:hover {
- background: #f9fafb;
- }
- .sort-item-static:hover .option-actions {
- opacity: 1;
- pointer-events: auto;
- }
- .sort-number {
- width: 32px;
- height: 32px;
- background: #285cf5;
- color: white;
- border-radius: 8px;
- display: flex;
- align-items: center;
- justify-content: center;
- font-size: 14px;
- font-weight: 700;
- flex-shrink: 0;
- }
- .sort-order-display {
- display: flex;
- align-items: center;
- gap: 12px;
- padding: 16px;
- background: white;
- border-radius: 10px;
- flex-wrap: wrap;
- }
- .sort-order-item {
- width: 56px;
- height: 56px;
- background: #eef3ff;
- border: 2px solid #285cf5;
- color: #285cf5;
- border-radius: 10px;
- display: flex;
- flex-direction: column;
- align-items: center;
- justify-content: center;
- font-size: 18px;
- font-weight: 700;
- position: relative;
- transition: all 0.2s;
- }
- .sort-order-item.draggable {
- cursor: move;
- }
- .sort-order-item.draggable:hover {
- background: #dbe6ff;
- transform: scale(1.05);
- box-shadow: 0 6px 14px rgba(40, 92, 245, 0.22);
- }
- .sort-order-item.dragging {
- opacity: 0.5;
- }
- .sort-order-item.drag-over {
- border-color: #285cf5;
- background: #cddcff;
- box-shadow: 0 6px 14px rgba(40, 92, 245, 0.22);
- }
- .sort-drag-handle {
- position: absolute;
- top: 2px;
- right: 2px;
- width: 16px;
- height: 16px;
- color: #285cf5;
- opacity: 0;
- transition: opacity 0.2s;
- }
- .sort-order-item.draggable:hover .sort-drag-handle {
- opacity: 1;
- }
- .sort-drag-handle svg {
- width: 12px;
- height: 12px;
- }
- .sort-arrow {
- color: #285cf5;
- font-size: 20px;
- font-weight: 700;
- }
- /* 工具顶部栏 */
- .tool-header {
- height: 60px;
- background: white;
- border-bottom: 1px solid #f0f1f3;
- display: flex;
- align-items: center;
- padding: 0 24px;
- flex-shrink: 0;
- }
- .tool-type-selector {
- position: relative;
- display: inline-block;
- }
- .tool-type-btn {
- display: flex;
- align-items: center;
- gap: 8px;
- padding: 10px 16px;
- background: #fafbfc;
- border: 1.5px solid #e5e7eb;
- border-radius: 10px;
- font-size: 14px;
- font-weight: 600;
- color: #111827;
- cursor: pointer;
- transition: all 0.2s;
- }
- .tool-type-btn:hover {
- border-color: #285cf5;
- background: #f6f8ff;
- }
- .tool-type-btn svg {
- width: 16px;
- height: 16px;
- color: #6b7280;
- flex-shrink: 0;
- }
- .tool-type-btn svg:last-child {
- width: 12px;
- height: 12px;
- }
- .tool-type-dropdown {
- position: absolute;
- top: 100%;
- left: 0;
- margin-top: 8px;
- background: white;
- border-radius: 12px;
- box-shadow: 0 8px 24px rgba(0, 0, 0, 0.15);
- padding: 8px;
- min-width: 200px;
- display: none;
- z-index: 1000;
- }
- .tool-type-selector.active .tool-type-dropdown {
- display: block;
- }
- .tool-type-item {
- padding: 10px 12px;
- border-radius: 8px;
- cursor: pointer;
- transition: all 0.2s;
- display: flex;
- align-items: center;
- gap: 10px;
- font-size: 14px;
- color: #374151;
- }
- .tool-type-item svg {
- width: 18px;
- height: 18px;
- flex-shrink: 0;
- }
- .tool-type-item:hover {
- background: #eef3ff;
- color: #285cf5;
- }
- .tool-type-item:hover svg {
- color: #285cf5;
- }
- .tool-type-item.active {
- background: #eef3ff;
- color: #285cf5;
- }
- .tool-type-item.active svg {
- color: #285cf5;
- }
- /* 工具编辑区域 */
- .tool-edit-container {
- display: none;
- flex-direction: column;
- background: white;
- width: 100%;
- height: 100%;
- flex: 1;
- }
- .tool-edit-container.active {
- display: flex;
- }
- .tool-edit-area {
- flex: 1;
- overflow-y: auto;
- padding: 40px;
- background: #fafbfc;
- }
- .tool-canvas {
- width: 960px;
- max-width: 100%;
- margin: 0 auto;
- background: white;
- border-radius: 16px;
- box-shadow: 0 2px 8px rgba(0, 0, 0, 0.06);
- padding: 40px;
- min-height: 500px;
- }
- .question-item {
- margin-bottom: 32px;
- padding-bottom: 32px;
- border-bottom: 1px solid #f0f1f3;
- }
- .question-item:last-child {
- border-bottom: none;
- }
- .question-header {
- display: flex;
- align-items: center;
- justify-content: space-between;
- margin-bottom: 16px;
- }
- .question-number {
- font-size: 16px;
- font-weight: 700;
- color: #285cf5;
- }
- .question-type-toggle {
- display: flex;
- align-items: center;
- gap: 8px;
- }
- .toggle-label {
- font-size: 13px;
- color: #6b7280;
- }
- .mini-toggle {
- width: 40px;
- height: 22px;
- background: #e5e7eb;
- border-radius: 11px;
- cursor: pointer;
- transition: all 0.3s;
- position: relative;
- }
- .mini-toggle.active {
- background: #285cf5;
- }
- .mini-toggle::after {
- content: '';
- position: absolute;
- top: 2px;
- left: 2px;
- width: 18px;
- height: 18px;
- background: white;
- border-radius: 50%;
- transition: all 0.3s;
- }
- .mini-toggle.active::after {
- left: 20px;
- }
- .question-input {
- width: 100%;
- padding: 14px 16px;
- border: 1.5px solid #e5e7eb;
- border-radius: 10px;
- font-size: 14px;
- color: #111827;
- background: #fafbfc;
- transition: all 0.2s;
- font-family: inherit;
- resize: vertical;
- min-height: 80px;
- }
- .question-input:focus {
- outline: none;
- border-color: #285cf5;
- background: white;
- }
- .options-list {
- margin-top: 16px;
- display: flex;
- flex-direction: column;
- gap: 10px;
- }
- .option-item {
- display: flex;
- align-items: center;
- gap: 12px;
- position: relative;
- padding: 4px;
- border-radius: 8px;
- transition: all 0.2s;
- }
- .option-item:hover {
- background: #f9fafb;
- }
- .option-item:hover .option-actions {
- opacity: 1;
- pointer-events: auto;
- }
- .option-actions {
- display: flex;
- align-items: center;
- gap: 4px;
- opacity: 0;
- pointer-events: none;
- transition: opacity 0.2s;
- }
- .option-action-btn {
- width: 24px;
- height: 24px;
- border: none;
- background: transparent;
- cursor: pointer;
- display: flex;
- align-items: center;
- justify-content: center;
- border-radius: 4px;
- transition: all 0.2s;
- color: #9ca3af;
- }
- .option-action-btn:hover {
- background: #e5e7eb;
- color: #374151;
- }
- .option-action-btn.delete:hover {
- background: #fee2e2;
- color: #dc2626;
- }
- .option-action-btn svg {
- width: 14px;
- height: 14px;
- }
- .option-drag-handle {
- cursor: grab;
- color: #d1d5db;
- width: 16px;
- height: 16px;
- display: flex;
- align-items: center;
- justify-content: center;
- flex-shrink: 0;
- opacity: 0;
- transition: opacity 0.2s;
- }
- .option-item:hover .option-drag-handle {
- opacity: 1;
- }
- .option-drag-handle:active {
- cursor: grabbing;
- }
- .option-drag-handle svg {
- width: 14px;
- height: 14px;
- }
- .option-checkbox {
- width: 22px;
- height: 22px;
- border: 2px solid #d1d5db;
- border-radius: 4px;
- flex-shrink: 0;
- cursor: pointer;
- display: flex;
- align-items: center;
- justify-content: center;
- transition: all 0.2s;
- }
- .option-checkbox.checked {
- background: #285cf5;
- border-color: #285cf5;
- }
- .option-checkbox svg {
- width: 14px;
- height: 14px;
- color: white;
- display: none;
- }
- .option-checkbox.checked svg {
- display: block;
- }
- .option-input {
- flex: 1;
- padding: 10px 14px;
- border: 1.5px solid #e5e7eb;
- border-radius: 8px;
- font-size: 14px;
- color: #111827;
- background: white;
- transition: all 0.2s;
- font-family: inherit;
- }
- .option-input:focus {
- outline: none;
- border-color: #285cf5;
- }
- .add-option-btn {
- width: 32px;
- height: 32px;
- margin-top: 8px;
- margin-left: 34px;
- border: 1.5px dashed #d1d5db;
- border-radius: 8px;
- background: transparent;
- color: #6b7280;
- cursor: pointer;
- transition: all 0.2s;
- display: flex;
- align-items: center;
- justify-content: center;
- flex-shrink: 0;
- }
- .add-option-btn svg {
- width: 16px;
- height: 16px;
- }
- .add-option-btn:hover {
- border-color: #285cf5;
- color: #285cf5;
- background: #f6f8ff;
- }
- .add-option-btn-with-text {
- height: 36px;
- padding: 0 14px;
- margin-top: 8px;
- margin-left: 66px;
- border: 1.5px dashed #d1d5db;
- border-radius: 8px;
- background: transparent;
- color: #6b7280;
- cursor: pointer;
- transition: all 0.2s;
- display: inline-flex;
- align-items: center;
- justify-content: center;
- gap: 6px;
- font-size: 13px;
- font-weight: 500;
- white-space: nowrap;
- align-self: flex-start;
- }
- .add-option-btn-with-text svg {
- width: 14px;
- height: 14px;
- flex-shrink: 0;
- }
- .add-option-btn-with-text:hover {
- border-color: #285cf5;
- color: #285cf5;
- background: #f6f8ff;
- }
- .question-actions {
- display: flex;
- align-items: center;
- gap: 8px;
- margin-left: auto;
- }
- .question-action-btn {
- width: 32px;
- height: 32px;
- border: none;
- background: #f9fafb;
- border-radius: 8px;
- cursor: pointer;
- display: flex;
- align-items: center;
- justify-content: center;
- transition: all 0.2s;
- color: #6b7280;
- }
- .question-action-btn:hover {
- background: #f3f4f6;
- color: #374151;
- }
- .question-action-btn.delete:hover {
- background: #fee2e2;
- color: #dc2626;
- }
- .question-action-btn svg {
- width: 16px;
- height: 16px;
- }
- .explanation-section {
- margin-top: 16px;
- padding: 16px;
- background: #f9fafb;
- border-radius: 10px;
- }
- .explanation-header {
- display: flex;
- align-items: center;
- justify-content: space-between;
- margin-bottom: 10px;
- }
- .explanation-label {
- font-size: 13px;
- font-weight: 600;
- color: #6b7280;
- display: flex;
- align-items: center;
- gap: 6px;
- }
- .ai-gen-btn {
- padding: 6px 12px;
- border-radius: 8px;
- border: none;
- background: linear-gradient(135deg, #285cf5 0%, #1f4ad6 100%);
- color: white;
- font-size: 12px;
- font-weight: 600;
- cursor: pointer;
- display: flex;
- align-items: center;
- gap: 4px;
- transition: all 0.2s;
- }
- .ai-gen-btn:hover {
- transform: translateY(-1px);
- box-shadow: 0 2px 8px rgba(40, 92, 245, 0.3);
- }
- .explanation-textarea {
- width: 100%;
- padding: 10px;
- border: 1.5px solid #e5e7eb;
- border-radius: 8px;
- font-size: 13px;
- color: #374151;
- background: white;
- resize: vertical;
- min-height: 60px;
- font-family: inherit;
- }
- .explanation-textarea:focus {
- outline: none;
- border-color: #285cf5;
- }
- .add-question-btn {
- margin-top: 24px;
- width: 40px;
- height: 40px;
- border: 2px dashed #d1d5db;
- border-radius: 10px;
- background: transparent;
- color: #6b7280;
- cursor: pointer;
- transition: all 0.2s;
- display: flex;
- align-items: center;
- justify-content: center;
- flex-shrink: 0;
- }
- .add-question-btn svg {
- width: 20px;
- height: 20px;
- }
- .add-question-btn:hover {
- border-color: #285cf5;
- color: #285cf5;
- background: #f6f8ff;
- transform: scale(1.05);
- }
- .add-question-btn-with-text {
- margin-top: 24px;
- height: 44px;
- padding: 0 18px;
- border: 2px dashed #d1d5db;
- border-radius: 10px;
- background: transparent;
- color: #6b7280;
- cursor: pointer;
- transition: all 0.2s;
- display: inline-flex;
- align-items: center;
- justify-content: center;
- gap: 8px;
- font-size: 14px;
- font-weight: 600;
- white-space: nowrap;
- align-self: flex-start;
- }
- .add-question-btn-with-text svg {
- width: 18px;
- height: 18px;
- flex-shrink: 0;
- }
- .add-question-btn-with-text:hover {
- border-color: #285cf5;
- color: #285cf5;
- background: #f6f8ff;
- transform: scale(1.02);
- }
- /* 抽认卡特殊布局 */
- .flashcard-layout {
- display: grid;
- grid-template-columns: 1fr 1fr;
- gap: 24px;
- margin-bottom: 32px;
- }
- .flashcard-side {
- border: 1.5px solid #e5e7eb;
- border-radius: 12px;
- padding: 20px;
- min-height: 200px;
- }
- .flashcard-side-label {
- font-size: 13px;
- font-weight: 600;
- color: #6b7280;
- margin-bottom: 12px;
- }
- .flashcard-content {
- width: 100%;
- padding: 14px;
- border: 1.5px solid #e5e7eb;
- border-radius: 8px;
- font-size: 14px;
- color: #111827;
- background: white;
- resize: vertical;
- min-height: 120px;
- font-family: inherit;
- }
- .flashcard-content:focus {
- outline: none;
- border-color: #285cf5;
- }
- /* 资源弹窗与列表 */
- .resource-upload-list {
- margin-top: 12px;
- display: flex;
- flex-direction: column;
- gap: 10px;
- }
- .resource-file-item {
- display: flex;
- align-items: center;
- justify-content: space-between;
- padding: 10px 12px;
- border: 1px solid #e5e7eb;
- border-radius: 10px;
- background: #fafbfc;
- font-size: 13px;
- color: #374151;
- }
- .resource-file-name {
- display: flex;
- align-items: center;
- gap: 8px;
- overflow: hidden;
- }
- .resource-file-name span {
- white-space: nowrap;
- text-overflow: ellipsis;
- overflow: hidden;
- }
- .resource-remove-btn {
- border: none;
- background: transparent;
- color: #9ca3af;
- cursor: pointer;
- display: flex;
- align-items: center;
- justify-content: center;
- width: 28px;
- height: 28px;
- border-radius: 8px;
- transition: all 0.2s;
- }
- .resource-remove-btn:hover {
- background: #fef2f2;
- color: #dc2626;
- }
- .resource-tip {
- font-size: 12px;
- color: #6b7280;
- margin-top: 8px;
- }
- .resource-actions {
- display: flex;
- gap: 12px;
- margin-top: 16px;
- }
- .resource-chip {
- display: inline-flex;
- align-items: center;
- gap: 6px;
- padding: 6px 10px;
- border-radius: 999px;
- background: #f3f4f6;
- color: #4b5563;
- font-size: 12px;
- font-weight: 600;
- }
- .resource-tabs {
- display: flex;
- align-items: center;
- gap: 8px;
- min-height: 60px;
- padding: 0 16px;
- border-bottom: 1px solid #f0f1f3;
- background: #fff;
- border-radius: 12px 12px 0 0;
- }
- .resource-tab {
- padding: 8px 14px;
- border-radius: 10px;
- border: 1px solid #e5e7eb;
- background: #fafbfc;
- cursor: pointer;
- font-size: 13px;
- font-weight: 600;
- color: #4b5563;
- transition: all 0.2s;
- height: 36px;
- display: inline-flex;
- align-items: center;
- justify-content: center;
- }
- .resource-tab.active {
- border-color: #285cf5;
- color: #285cf5;
- background: #eef3ff;
- box-shadow: 0 2px 8px rgba(40, 92, 245, 0.12);
- }
- /* 滚动条 */
- ::-webkit-scrollbar {
- width: 8px;
- height: 8px;
- }
- ::-webkit-scrollbar-track {
- background: transparent;
- }
- ::-webkit-scrollbar-thumb {
- background: #e0e2e5;
- border-radius: 4px;
- }
- ::-webkit-scrollbar-thumb:hover {
- background: #c5c8cc;
- }
- </style>
- </head>
- <body>
- <!-- 顶部工具栏 -->
- <div class="top-bar">
- <div class="top-bar-left">
- <div class="logo-menu-wrapper">
- <button class="logo-btn" id="topMenuToggle" onclick="toggleTopMenu(event)">
- <img src="/var/folders/6_/1f501dn50cqbnjhxhfyqnbcm0000gn/T/cocorobo_icon_rgb_blue_png_1769396590215.png" alt="Coco" class="logo-img">
- <svg class="logo-caret" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
- <polyline points="6 9 12 15 18 9"/>
- </svg>
- </button>
- <div class="top-dropdown" id="topDropdown">
- <div class="top-dropdown-item" onclick="handleTopMenuAction('back')">
- <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
- <path d="M15 18l-6-6 6-6"/>
- </svg>
- 返回列表
- </div>
- <div class="top-dropdown-item" onclick="handleTopMenuAction('settings')">
- <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
- <circle cx="12" cy="12" r="3"/>
- <path d="M19.4 15a1.65 1.65 0 00.33 1.82l.06.06a2 2 0 01-2.83 2.83l-.06-.06a1.65 1.65 0 00-1.82-.33 1.65 1.65 0 00-1 1.51V21a2 2 0 01-4 0v-.09a1.65 1.65 0 00-1-1.51 1.65 1.65 0 00-1.82.33l-.06.06a2 2 0 01-2.83-2.83l.06-.06a1.65 1.65 0 00.33-1.82 1.65 1.65 0 00-1.51-1H3a2 2 0 010-4h.09a1.65 1.65 0 001.51-1 1.65 1.65 0 00-.33-1.82l-.06-.06a2 2 0 012.83-2.83l.06.06a1.65 1.65 0 001.82.33h.09A1.65 1.65 0 0010.91 3V3a2 2 0 014 0v.09a1.65 1.65 0 001 1.51h.09a1.65 1.65 0 001.82-.33l.06-.06a2 2 0 012.83 2.83l-.06.06a1.65 1.65 0 00-.33 1.82v.09a1.65 1.65 0 001.51 1H21a2 2 0 010 4h-.09a1.65 1.65 0 00-1.51 1z"/>
- </svg>
- 设置
- </div>
- <div class="top-dropdown-item" onclick="handleTopMenuAction('template')">
- <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
- <rect x="3" y="3" width="7" height="7"/>
- <rect x="14" y="3" width="7" height="7"/>
- <rect x="14" y="14" width="7" height="7"/>
- <rect x="3" y="14" width="7" height="7"/>
- </svg>
- 导入模板
- </div>
- <div class="top-dropdown-item" onclick="handleTopMenuAction('duplicate')">
- <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
- <rect x="9" y="9" width="13" height="13" rx="2" ry="2"/>
- <path d="M5 15H4a2 2 0 01-2-2V4a2 2 0 012-2h9a2 2 0 012 2v1"/>
- </svg>
- 另存为副本
- </div>
- <div class="top-dropdown-sep"></div>
- <div class="top-dropdown-item danger" onclick="handleTopMenuAction('delete')">
- <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
- <polyline points="3 6 5 6 21 6"/>
- <path d="M19 6v14a2 2 0 01-2 2H7a2 2 0 01-2-2V6m3 0V4a2 2 0 012-2h4a2 2 0 012 2v2"/>
- </svg>
- 删除
- </div>
- </div>
- </div>
- <div class="course-title" contenteditable="true">新建课程</div>
- <div class="auto-save">
- <span class="save-dot"></span>
- <span id="saveStatus">未保存</span>
- </div>
- </div>
- <div class="top-bar-right">
- <button class="top-btn btn-secondary">
- <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
- <path d="M15 12a3 3 0 11-6 0 3 3 0 016 0z"/>
- <path d="M2.458 12C3.732 7.943 7.523 5 12 5c4.478 0 8.268 2.943 9.542 7-1.274 4.057-5.064 7-9.542 7-4.477 0-8.268-2.943-9.542-7z"/>
- </svg>
- 预览
- </button>
- <button class="top-btn btn-secondary">
- <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
- <path d="M19 21H5a2 2 0 01-2-2V5a2 2 0 012-2h11l5 5v11a2 2 0 01-2 2z"/>
- <polyline points="17 21 17 13 7 13 7 21"/>
- <polyline points="7 3 7 8 15 8"/>
- </svg>
- 保存
- </button>
- <button class="top-btn btn-primary" onclick="showPublishModal()">
- <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
- <circle cx="12" cy="12" r="10"/>
- <polyline points="12 6 12 12 16 14"/>
- </svg>
- 发布
- </button>
- </div>
- </div>
- <!-- 主容器 -->
- <div class="main-container">
- <!-- 左侧容器 -->
- <div class="left-container">
- <!-- 一级菜单 -->
- <div class="primary-menu">
- <div class="menu-item active" data-panel="ai">
- <svg class="menu-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
- <path d="M12 2L2 7l10 5 10-5-10-5z"/>
- <path d="M2 17l10 5 10-5"/>
- <path d="M2 12l10 5 10-5"/>
- </svg>
- <span class="menu-label">Coco AI</span>
- </div>
- <div class="menu-item" data-panel="pages">
- <svg class="menu-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
- <path d="M14 2H6a2 2 0 00-2 2v16a2 2 0 002 2h12a2 2 0 002-2V8z"/>
- <polyline points="14 2 14 8 20 8"/>
- </svg>
- <span class="menu-label">页面</span>
- </div>
- <div class="menu-item" data-panel="tools">
- <svg class="menu-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
- <circle cx="12" cy="12" r="3"/>
- <path d="M12 1v6m0 6v6M5.64 5.64l4.24 4.24m4.24 4.24l4.24 4.24M1 12h6m6 0h6M5.64 18.36l4.24-4.24m4.24-4.24l4.24-4.24"/>
- </svg>
- <span class="menu-label">互动工具</span>
- </div>
- <div class="menu-item" data-panel="apps">
- <svg class="menu-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
- <rect x="3" y="3" width="7" height="7"/>
- <rect x="14" y="3" width="7" height="7"/>
- <rect x="14" y="14" width="7" height="7"/>
- <rect x="3" y="14" width="7" height="7"/>
- </svg>
- <span class="menu-label">AI应用</span>
- </div>
- <div class="menu-item" data-panel="web">
- <svg class="menu-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
- <circle cx="12" cy="12" r="10"/>
- <path d="M2 12h20"/>
- <path d="M12 2a15.3 15.3 0 014 10 15.3 15.3 0 01-4 10 15.3 15.3 0 01-4-10 15.3 15.3 0 014-10z"/>
- </svg>
- <span class="menu-label">交互网页</span>
- </div>
- <div class="menu-item" data-panel="resources">
- <svg class="menu-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
- <path d="M22 19a2 2 0 01-2 2H4a2 2 0 01-2-2V5a2 2 0 012-2h5l2 3h9a2 2 0 012 2z"/>
- </svg>
- <span class="menu-label">多媒体</span>
- </div>
- </div>
- <!-- 二级菜单 - AI对话 -->
- <div class="secondary-panel" id="aiPanel">
- <div class="panel-header">
- <span class="panel-title">Coco AI</span>
- <button class="collapse-btn" onclick="toggleSecondaryPanel()">
- <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
- <polyline points="15 18 9 12 15 6"/>
- </svg>
- </button>
- </div>
- <div class="chat-input-container" id="chatInputContainer">
- <div class="chat-input-wrapper">
- <div class="chat-textarea-container">
- <textarea
- class="chat-textarea"
- id="chatInput"
- placeholder="例如:创建一节45分钟的四年级教学内容为水的三态变化的课程"
- rows="3"
- oninput="autoResize(this)"
- >创建一节45分钟的四年级教学内容为水的三态变化的课程</textarea>
- </div>
- <div class="chat-bottom-row">
- <div style="display: flex; align-items: center; gap: 8px;">
- <button class="upload-file-btn" title="上传参考资料" id="uploadBtn">
- <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
- <path d="M21.44 11.05l-9.19 9.19a6 6 0 01-8.49-8.49l9.19-9.19a4 4 0 015.66 5.66l-9.2 9.19a2 2 0 01-2.83-2.83l8.49-8.48"/>
- </svg>
- </button>
- <span class="status-text" id="statusText">
- <span class="status-dot"></span>
- 生成中……
- </span>
- </div>
- <button class="send-btn" onclick="sendMessage()" id="sendBtn">
- <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
- <line x1="22" y1="2" x2="11" y2="13"/>
- <polygon points="22 2 15 22 11 13 2 9 22 2"/>
- </svg>
- </button>
- </div>
- </div>
- </div>
- <div class="secondary-content">
- <div class="chat-messages" id="chatMessages">
- <!-- 对话消息将显示在这里 -->
- </div>
- </div>
- </div>
- <!-- 二级菜单 - 页面模板 -->
- <div class="secondary-panel hidden" id="pagesPanel">
- <div class="panel-header">
- <span class="panel-title">添加模板页面</span>
- <button class="collapse-btn" onclick="toggleSecondaryPanel()">
- <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
- <polyline points="15 18 9 12 15 6"/>
- </svg>
- </button>
- </div>
- <div class="secondary-content" style="overflow-y: auto;">
- <div class="template-grid">
- <!-- 标题页 -->
- <div class="template-card" onclick="addPageFromTemplate('title')">
- <div class="template-preview">
- <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
- <path d="M4 7V4h16v3M9 20h6M12 4v16"/>
- </svg>
- </div>
- <div class="template-name">标题页</div>
- </div>
- <!-- 图片页 -->
- <div class="template-card" onclick="addPageFromTemplate('image')">
- <div class="template-preview">
- <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
- <rect x="3" y="3" width="18" height="18" rx="2" ry="2"/>
- <circle cx="8.5" cy="8.5" r="1.5"/>
- <polyline points="21 15 16 10 5 21"/>
- </svg>
- </div>
- <div class="template-name">图片页</div>
- </div>
- <!-- 内容页 -->
- <div class="template-card" onclick="addPageFromTemplate('content')">
- <div class="template-preview">
- <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
- <path d="M14 2H6a2 2 0 00-2 2v16a2 2 0 002 2h12a2 2 0 002-2V8z"/>
- <polyline points="14 2 14 8 20 8"/>
- <line x1="16" y1="13" x2="8" y2="13"/>
- <line x1="16" y1="17" x2="8" y2="17"/>
- <polyline points="10 9 9 9 8 9"/>
- </svg>
- </div>
- <div class="template-name">内容页</div>
- </div>
- <!-- 文图页(左文右图) -->
- <div class="template-card" onclick="addPageFromTemplate('text-image')">
- <div class="template-preview">
- <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
- <rect x="3" y="3" width="7" height="18" rx="1"/>
- <rect x="14" y="3" width="7" height="18" rx="1"/>
- </svg>
- </div>
- <div class="template-name">文图页</div>
- </div>
- <!-- 图文页(左图右文) -->
- <div class="template-card" onclick="addPageFromTemplate('image-text')">
- <div class="template-preview">
- <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
- <rect x="3" y="3" width="7" height="18" rx="1"/>
- <rect x="14" y="3" width="7" height="18" rx="1"/>
- </svg>
- </div>
- <div class="template-name">图文页</div>
- </div>
- <!-- 上传PPT -->
- <div class="template-card upload-ppt-card" onclick="uploadPPT()">
- <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
- <path d="M21 15v4a2 2 0 01-2 2H5a2 2 0 01-2-2v-4"/>
- <polyline points="17 8 12 3 7 8"/>
- <line x1="12" y1="3" x2="12" y2="15"/>
- </svg>
- <span class="upload-ppt-text">上传PPT</span>
- </div>
- </div>
- </div>
- </div>
- <!-- 二级菜单 - 互动工具 -->
- <div class="secondary-panel hidden" id="toolsPanel">
- <div class="panel-header">
- <span class="panel-title">添加互动工具</span>
- <button class="collapse-btn" onclick="toggleSecondaryPanel()">
- <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
- <polyline points="15 18 9 12 15 6"/>
- </svg>
- </button>
- </div>
- <!-- 内容区域包装器 -->
- <div class="panel-content-wrapper">
- <!-- 工具列表视图 -->
- <div class="secondary-content tools-list-view" id="toolsListView" style="overflow-y: auto;">
- <!-- 插画式说明 -->
- <div class="tools-intro">
- <div class="tools-intro-image">
- <svg width="120" height="80" viewBox="0 0 120 80" fill="none">
- <rect x="10" y="15" width="100" height="50" rx="8" fill="#eef3ff" stroke="#285cf5" stroke-width="2"/>
- <circle cx="30" cy="30" r="4" fill="#285cf5"/>
- <rect x="40" y="27" width="60" height="6" rx="3" fill="#ffd9a8"/>
- <circle cx="30" cy="45" r="4" fill="#d1d5db"/>
- <rect x="40" y="42" width="45" height="6" rx="3" fill="#e5e7eb"/>
- </svg>
- </div>
- <div class="tools-intro-text">选择工具创建互动页面</div>
- </div>
- <div class="tools-grid">
- <!-- 选择 -->
- <div class="tool-card" onclick="selectTool('choice')">
- <div class="tool-icon">
- <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
- <circle cx="12" cy="12" r="10"/>
- <path d="M12 16v-4m0-4h.01"/>
- </svg>
- </div>
- <div class="tool-name">选择</div>
- </div>
- <!-- 问答 -->
- <div class="tool-card" onclick="selectTool('qa')">
- <div class="tool-icon">
- <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
- <path d="M21 15a2 2 0 01-2 2H7l-4 4V5a2 2 0 012-2h14a2 2 0 012 2z"/>
- </svg>
- </div>
- <div class="tool-name">问答</div>
- </div>
- <!-- 投票 -->
- <div class="tool-card" onclick="selectTool('vote')">
- <div class="tool-icon">
- <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
- <polyline points="9 11 12 14 22 4"/>
- <path d="M21 12v7a2 2 0 01-2 2H5a2 2 0 01-2-2V5a2 2 0 012-2h11"/>
- </svg>
- </div>
- <div class="tool-name">投票</div>
- </div>
- <!-- 拍照 -->
- <div class="tool-card" onclick="selectTool('photo')">
- <div class="tool-icon">
- <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
- <path d="M23 19a2 2 0 01-2 2H3a2 2 0 01-2-2V8a2 2 0 012-2h4l2-3h6l2 3h4a2 2 0 012 2z"/>
- <circle cx="12" cy="13" r="4"/>
- </svg>
- </div>
- <div class="tool-name">拍照</div>
- </div>
- <!-- 填空 -->
- <div class="tool-card" onclick="selectTool('fillblank')">
- <div class="tool-icon">
- <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
- <line x1="8" y1="6" x2="21" y2="6"/>
- <line x1="8" y1="12" x2="21" y2="12"/>
- <line x1="8" y1="18" x2="21" y2="18"/>
- <line x1="3" y1="6" x2="3.01" y2="6"/>
- <line x1="3" y1="12" x2="3.01" y2="12"/>
- <line x1="3" y1="18" x2="3.01" y2="18"/>
- </svg>
- </div>
- <div class="tool-name">填空</div>
- </div>
- <!-- 排序 -->
- <div class="tool-card" onclick="selectTool('sort')">
- <div class="tool-icon">
- <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
- <line x1="4" y1="6" x2="20" y2="6"/>
- <line x1="4" y1="12" x2="20" y2="12"/>
- <line x1="4" y1="18" x2="20" y2="18"/>
- </svg>
- </div>
- <div class="tool-name">排序</div>
- </div>
- <!-- 白板 -->
- <div class="tool-card" onclick="selectTool('whiteboard')">
- <div class="tool-icon">
- <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
- <path d="M12 19l7-7 3 3-7 7-3-3z"/>
- <path d="M18 13l-1.5-7.5L2 2l3.5 14.5L13 18l5-5z"/>
- <path d="M2 2l7.586 7.586"/>
- </svg>
- </div>
- <div class="tool-name">白板</div>
- </div>
- <!-- 抽认卡 -->
- <div class="tool-card" onclick="selectTool('flashcard')">
- <div class="tool-icon">
- <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
- <rect x="2" y="4" width="20" height="16" rx="2"/>
- <path d="M7 15h10M12 9v6"/>
- </svg>
- </div>
- <div class="tool-name">抽认卡</div>
- </div>
- <!-- CocoPi -->
- <div class="tool-card" onclick="selectTool('cocopi')">
- <div class="tool-icon">
- <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
- <polyline points="16 18 22 12 16 6"/>
- <polyline points="8 6 2 12 8 18"/>
- </svg>
- </div>
- <div class="tool-name">CocoPi</div>
- </div>
- <!-- 创作空间 -->
- <div class="tool-card" onclick="selectTool('workspace')">
- <div class="tool-icon">
- <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
- <path d="M12 2L2 7l10 5 10-5-10-5z"/>
- <path d="M2 17l10 5 10-5"/>
- <path d="M2 12l10 5 10-5"/>
- </svg>
- </div>
- <div class="tool-name">创作空间</div>
- </div>
- </div>
- </div>
- <!-- 工具配置视图 -->
- <div class="secondary-content tools-config-view hidden" id="toolsConfigView">
- <div class="config-header">
- <button class="config-back-btn" onclick="backToToolsList()">
- <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
- <path d="M19 12H5M12 19l-7-7 7-7"/>
- </svg>
- </button>
- <span class="config-tool-name" id="currentToolName">选择</span>
- </div>
- <!-- 配置内容区域 -->
- <div class="config-content" id="toolConfigContent">
- <!-- 配置项将通过JavaScript动态生成 -->
- </div>
- </div>
- </div><!-- 关闭 panel-content-wrapper -->
- </div>
- <!-- 二级菜单 - AI应用 -->
- <div class="secondary-panel hidden" id="appsPanel">
- <div class="panel-header">
- <span class="panel-title">添加AI应用</span>
- <button class="collapse-btn" onclick="toggleSecondaryPanel()">
- <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
- <polyline points="15 18 9 12 15 6"/>
- </svg>
- </button>
- </div>
- <div class="secondary-content" style="overflow-y: auto;">
- <div class="template-grid">
- <!-- 应用中心 -->
- <div class="template-card" onclick="openAIAppCenter()">
- <div class="template-preview">
- <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
- <rect x="3" y="3" width="7" height="7" rx="1"/>
- <rect x="14" y="3" width="7" height="7" rx="1"/>
- <rect x="14" y="14" width="7" height="7" rx="1"/>
- <rect x="3" y="14" width="7" height="7" rx="1"/>
- </svg>
- </div>
- <div class="template-name">应用中心</div>
- </div>
- <!-- 创建应用 -->
- <div class="template-card" onclick="openCreateAppModal()">
- <div class="template-preview">
- <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
- <circle cx="12" cy="12" r="10"/>
- <line x1="12" y1="8" x2="12" y2="16"/>
- <line x1="8" y1="12" x2="16" y2="12"/>
- </svg>
- </div>
- <div class="template-name">创建应用</div>
- </div>
- </div>
- </div>
- </div>
- <!-- 二级菜单 - 交互网页 -->
- <div class="secondary-panel hidden" id="webPanel">
- <!-- 列表视图 -->
- <div id="webListView">
- <div class="panel-header">
- <span class="panel-title">添加交互网页</span>
- <button class="collapse-btn" onclick="toggleSecondaryPanel()">
- <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
- <polyline points="15 18 9 12 15 6"/>
- </svg>
- </button>
- </div>
- <div class="secondary-content" style="overflow-y: auto;">
- <div class="template-grid">
- <!-- 网页中心 -->
- <div class="template-card" onclick="openWebCenter()">
- <div class="template-preview">
- <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
- <rect x="2" y="3" width="20" height="14" rx="2"/>
- <line x1="2" y1="7" x2="22" y2="7"/>
- <circle cx="5" cy="5" r="0.5" fill="currentColor"/>
- <circle cx="7" cy="5" r="0.5" fill="currentColor"/>
- <circle cx="9" cy="5" r="0.5" fill="currentColor"/>
- <path d="M8 11h8M8 14h5"/>
- </svg>
- </div>
- <div class="template-name">网页中心</div>
- </div>
- <!-- 上传网页 -->
- <div class="template-card" onclick="uploadWebPage()">
- <div class="template-preview">
- <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
- <path d="M21 15v4a2 2 0 01-2 2H5a2 2 0 01-2-2v-4"/>
- <polyline points="17 8 12 3 7 8"/>
- <line x1="12" y1="3" x2="12" y2="15"/>
- </svg>
- </div>
- <div class="template-name">上传网页</div>
- </div>
- <!-- 爬取网页 -->
- <div class="template-card" onclick="crawlWebPage()">
- <div class="template-preview">
- <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
- <circle cx="12" cy="12" r="10"/>
- <path d="M2 12h20"/>
- <path d="M12 2a15.3 15.3 0 014 10 15.3 15.3 0 01-4 10 15.3 15.3 0 01-4-10 15.3 15.3 0 014-10z"/>
- <path d="M16 8l-4 4-4-4" stroke-width="1.5"/>
- </svg>
- </div>
- <div class="template-name">爬取网页</div>
- </div>
- </div>
- </div>
- </div>
- <!-- 配置视图 -->
- <div id="webConfigView" class="hidden">
- <!-- 上传网页配置 -->
- <div class="panel-content-wrapper hidden" id="uploadWebView">
- <div class="config-header">
- <button class="config-back-btn" onclick="backToWebList()">
- <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
- <path d="M19 12H5M12 19l-7-7 7-7"/>
- </svg>
- </button>
- <span class="config-tool-name">上传网页</span>
- </div>
- <div class="config-content">
- <div class="web-config-form">
- <div class="form-group">
- <label class="form-label">网页名称</label>
- <input type="text" class="form-input" id="uploadWebName" placeholder="请输入网页名称" oninput="validateUploadForm()">
- </div>
- <!-- Tabs -->
- <div class="upload-tabs">
- <button id="uploadFileTab" class="upload-tab active" onclick="switchUploadTab('file')">上传文件</button>
- <button id="pasteCodeTab" class="upload-tab" onclick="switchUploadTab('code')">粘贴代码</button>
- </div>
- <!-- Tab Panels -->
- <div id="uploadFilePanel">
- <div class="form-group">
- <div class="file-upload-area" id="fileUploadArea" onclick="document.getElementById('fileInput').click()">
- <input type="file" id="fileInput" accept=".html,.htm,.zip" style="display: none;" onchange="handleFileSelect(event)">
- <svg width="48" height="48" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5">
- <path d="M21 15v4a2 2 0 01-2 2H5a2 2 0 01-2-2v-4"/>
- <polyline points="17 8 12 3 7 8"/>
- <line x1="12" y1="3" x2="12" y2="15"/>
- </svg>
- <p class="upload-text">点击或拖拽文件到此处上传</p>
- <p class="upload-hint">支持 HTML、HTM、ZIP 格式</p>
- </div>
- <div id="fileNameDisplay" class="file-name-display"></div>
- </div>
- </div>
- <div id="pasteCodePanel" class="hidden">
- <div class="form-group">
- <textarea id="pasteCodeTextarea" class="code-textarea" placeholder="请在此处粘贴完整的HTML代码" oninput="validateUploadForm()"></textarea>
- </div>
- </div>
- <div class="web-config-actions" style="padding: 0 30%;">
- <button class="config-btn config-btn-primary status-btn" id="confirmCreateWebBtn" onclick="confirmCreateWebPage()" disabled>
- <span class="btn-status-icon" id="uploadStatusIcon" aria-hidden="true"></span>
- <span class="btn-status-text" id="uploadStatusText">等待上传...</span>
- </button>
- </div>
- </div>
- </div>
- </div>
- <!-- 爬取网页配置 -->
- <div class="panel-content-wrapper hidden" id="crawlWebView">
- <div class="config-header">
- <button class="config-back-btn" onclick="backToWebList()">
- <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
- <path d="M19 12H5M12 19l-7-7 7-7"/>
- </svg>
- </button>
- <span class="config-tool-name">爬取网页</span>
- </div>
- <div class="config-content">
- <div class="web-config-form">
- <div class="form-group">
- <label class="form-label">网页名称</label>
- <input type="text" class="form-input" id="crawlWebName" placeholder="请输入网页名称" oninput="validateCrawlUrl()">
- </div>
- <div class="form-group">
- <label class="form-label">网页链接</label>
- <input type="url" class="form-input" id="crawlWebUrl" placeholder="请输入完整的网页URL地址" oninput="validateCrawlUrl()">
- </div>
- <div class="web-config-actions" style="padding: 0 30%;">
- <button class="config-btn config-btn-primary crawl-btn" id="startCrawlBtn" onclick="startCrawlWeb()" disabled>
- <span class="btn-status-icon" id="crawlStatusIcon" aria-hidden="true"></span>
- <span class="btn-status-text" id="crawlStatusText">等待输入...</span>
- </button>
- </div>
- </div>
- </div>
- </div>
- </div>
- </div>
- <!-- 二级菜单 - 资源 -->
- <div class="secondary-panel hidden" id="resourcesPanel">
- <div class="panel-header">
- <span class="panel-title">添加多媒体</span>
- <button class="collapse-btn" onclick="toggleSecondaryPanel()">
- <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
- <polyline points="15 18 9 12 15 6"/>
- </svg>
- </button>
- </div>
- <div class="secondary-content" style="overflow-y: auto;">
- <div class="template-grid">
- <div class="template-card" onclick="openVideoSourceModal()">
- <div class="template-preview">
- <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
- <rect x="3" y="4" width="18" height="16" rx="2" ry="2"/>
- <polygon points="10 9 16 12 10 15 10 9"/>
- </svg>
- </div>
- <div class="template-name">视频</div>
- </div>
- <div class="template-card" onclick="openAudioUploadModal()">
- <div class="template-preview">
- <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
- <path d="M9 18V5l12-2v13"/>
- <circle cx="6" cy="18" r="3"/>
- <circle cx="18" cy="16" r="3"/>
- </svg>
- </div>
- <div class="template-name">音频</div>
- </div>
- <div class="template-card" onclick="openDocumentUploadModal()">
- <div class="template-preview">
- <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
- <path d="M14 2H6a2 2 0 00-2 2v16a2 2 0 002 2h12a2 2 0 002-2V8z"/>
- <polyline points="14 2 14 8 20 8"/>
- <line x1="8" y1="13" x2="16" y2="13"/>
- <line x1="8" y1="17" x2="14" y2="17"/>
- </svg>
- </div>
- <div class="template-name">文档</div>
- </div>
- <div class="template-card" onclick="openCollectionModal()">
- <div class="template-preview">
- <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
- <rect x="3" y="4" width="8" height="14" rx="2"/>
- <rect x="13" y="4" width="8" height="14" rx="2"/>
- <line x1="7" y1="9" x2="7" y2="13"/>
- <line x1="17" y1="9" x2="17" y2="13"/>
- </svg>
- </div>
- <div class="template-name">文档集</div>
- </div>
- </div>
- </div>
- </div>
- </div>
- <!-- 中央编辑区 -->
- <div class="center-area">
- <div class="element-toolbar" id="elementToolbar">
- <!-- 插入工具 -->
- <div class="toolbar-section">
- <button class="toolbar-btn" onclick="insertText()">
- <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
- <path d="M4 7V4h16v3M9 20h6M12 4v16"/>
- </svg>
- 文本
- </button>
- <button class="toolbar-btn" onclick="insertImage()">
- <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
- <rect x="3" y="3" width="18" height="18" rx="2" ry="2"/>
- <circle cx="8.5" cy="8.5" r="1.5"/>
- <polyline points="21 15 16 10 5 21"/>
- </svg>
- 图片
- </button>
- <button class="toolbar-btn" onclick="insertTable()">
- <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
- <rect x="3" y="3" width="18" height="18" rx="2"/>
- <line x1="3" y1="9" x2="21" y2="9"/>
- <line x1="3" y1="15" x2="21" y2="15"/>
- <line x1="9" y1="3" x2="9" y2="21"/>
- <line x1="15" y1="3" x2="15" y2="21"/>
- </svg>
- 表格
- </button>
- <div class="dropdown" id="shapeDropdown">
- <button class="toolbar-btn" onclick="toggleDropdown('shapeDropdown')">
- <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
- <circle cx="12" cy="12" r="10"/>
- </svg>
- 形状
- <svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
- <polyline points="6 9 12 15 18 9"/>
- </svg>
- </button>
- <div class="dropdown-menu">
- <div class="dropdown-item" onclick="insertShape('circle')">
- <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
- <circle cx="12" cy="12" r="10"/>
- </svg>
- 圆形
- </div>
- <div class="dropdown-item" onclick="insertShape('rectangle')">
- <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
- <rect x="3" y="3" width="18" height="18" rx="2"/>
- </svg>
- 矩形
- </div>
- <div class="dropdown-item" onclick="insertShape('triangle')">
- <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
- <path d="M12 2 L22 20 L2 20 Z"/>
- </svg>
- 三角形
- </div>
- <div class="dropdown-item" onclick="insertShape('arrow')">
- <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
- <line x1="5" y1="12" x2="19" y2="12"/>
- <polyline points="12 5 19 12 12 19"/>
- </svg>
- 箭头
- </div>
- </div>
- </div>
- </div>
- <!-- 编辑工具(选中元素时显示) -->
- <div class="toolbar-section" id="editTools" style="display: none;">
- <button class="toolbar-btn">
- <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
- <path d="M11 4H4a2 2 0 00-2 2v14a2 2 0 002 2h14a2 2 0 002-2v-7"/>
- <path d="M18.5 2.5a2.121 2.121 0 013 3L12 15l-4 1 1-4 9.5-9.5z"/>
- </svg>
- 编辑
- </button>
- <button class="toolbar-btn" onclick="deleteElement()">
- <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
- <polyline points="3 6 5 6 21 6"/>
- <path d="M19 6v14a2 2 0 01-2 2H7a2 2 0 01-2-2V6m3 0V4a2 2 0 012-2h4a2 2 0 012 2v2"/>
- </svg>
- 删除
- </button>
- <button class="toolbar-btn">
- <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
- <rect x="8" y="8" width="8" height="8"/>
- <path d="M4 16c-1.1 0-2-.9-2-2V6c0-1.1.9-2 2-2h8c1.1 0 2 .9 2 2"/>
- </svg>
- 复制
- </button>
- </div>
- </div>
- <div class="slides-area">
- <div class="slide-canvas" id="slideCanvas">
- <div class="slide-placeholder" id="slidePlaceholder">
- <div class="slide-placeholder-icon">✨</div>
- <div class="slide-placeholder-text">使用AI生成或创建课程内容</div>
- <div class="slide-placeholder-hint">描述您的需求,AI将为您创建完整课程</div>
- </div>
- </div>
- <!-- 工具编辑区域 -->
- <div class="tool-edit-container" id="toolEditContainer" style="display: none;">
- <!-- 工具编辑内容将在这里动态生成 -->
- </div>
- </div>
- <div class="bottom-outline" id="bottomOutline">
- <div class="outline-track" id="outlineTrack">
- <!-- 页面大纲将在这里生成 -->
- </div>
- </div>
- </div>
- <!-- 右侧面板 -->
- <div class="right-panel collapsed" id="rightPanel">
- <div class="panel-header">
- <span class="panel-title collapse-text">页面大纲</span>
- <button class="collapse-btn" onclick="toggleRightPanel()">
- <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
- <polyline points="15 18 9 12 15 6"/>
- </svg>
- </button>
- </div>
- <div class="outline-panel">
- <div class="outline-toolbar">
- <div class="outline-toolbar-left">
- <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
- <rect x="3" y="4" width="18" height="16" rx="2"/>
- <line x1="3" y1="10" x2="21" y2="10"/>
- </svg>
- <span>大纲</span>
- </div>
- <div class="outline-actions">
- <button class="outline-btn" onclick="addOutlineGroup()">
- <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
- <line x1="12" y1="5" x2="12" y2="19"/>
- <line x1="5" y1="12" x2="19" y2="12"/>
- </svg>
- 分组
- </button>
- </div>
- </div>
- <div class="outline-list" id="rightOutlineList">
- <div class="outline-empty">暂无页面,创建后将出现在此</div>
- </div>
- </div>
- </div>
- </div>
- <!-- 创建入口弹窗 -->
- <div class="modal-overlay" id="createModal">
- <div class="create-modal">
- <button class="modal-close" onclick="hideCreateModal()">
- <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
- <line x1="18" y1="6" x2="6" y2="18"/>
- <line x1="6" y1="6" x2="18" y2="18"/>
- </svg>
- </button>
- <div class="modal-header">
- <h2>创建新课程</h2>
- <p>选择一种方式开始创建您的互动课件</p>
- </div>
- <div class="create-options">
- <div class="create-card featured" id="aiCreateCard" onclick="startAICreate()">
- <div class="create-icon">
- <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
- <path d="M12 2L2 7l10 5 10-5-10-5z"/>
- <path d="M2 17l10 5 10-5"/>
- <path d="M2 12l10 5 10-5"/>
- </svg>
- </div>
- <h3>从AI创建</h3>
- <p>AI自动生成完整教学内容</p>
- </div>
- <div class="create-card" id="pptCreateCard" onclick="startPptUploadFlow()" onmouseenter="unsetAiFeatured()">
- <div class="create-icon">
- <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
- <path d="M21 15v4a2 2 0 01-2 2H5a2 2 0 01-2-2v-4"/>
- <polyline points="17 8 12 3 7 8"/>
- <line x1="12" y1="3" x2="12" y2="15"/>
- </svg>
- </div>
- <h3>上传我的文件</h3>
- <p>上传本地PPT文件并解析</p>
- </div>
- <div class="create-card" onmouseenter="unsetAiFeatured()">
- <div class="create-icon">
- <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
- <path d="M3 9l9-7 9 7v11a2 2 0 01-2 2H5a2 2 0 01-2-2z"/>
- <polyline points="9 22 9 12 15 12 15 22"/>
- </svg>
- </div>
- <h3>从资源库导入</h3>
- <p>选择已有的课程模板</p>
- </div>
- <div class="create-card" onmouseenter="unsetAiFeatured()" onclick="createBlankCourse()">
- <div class="create-icon">
- <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
- <path d="M14 2H6a2 2 0 00-2 2v16a2 2 0 002 2h12a2 2 0 002-2V8z"/>
- <polyline points="14 2 14 8 20 8"/>
- </svg>
- </div>
- <h3>创建空白</h3>
- <p>从零开始自定义</p>
- </div>
- </div>
- </div>
- </div>
- <!-- 课程设置弹窗 -->
- <div class="modal-overlay" id="courseSettingsModal">
- <div class="settings-modal">
- <div class="settings-header">
- <div class="settings-title">课程设置</div>
- <button class="modal-close" onclick="hideCourseSettings()">
- <svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
- <line x1="18" y1="6" x2="6" y2="18"/>
- <line x1="6" y1="6" x2="18" y2="18"/>
- </svg>
- </button>
- </div>
- <div class="settings-body">
- <div class="form-group">
- <label class="form-label">学科</label>
- <select class="form-select" id="settingsSubjectSelect">
- <option value="">请选择学科</option>
- <option value="chinese">语文</option>
- <option value="math">数学</option>
- <option value="english">英语</option>
- <option value="physics">物理</option>
- <option value="chemistry">化学</option>
- <option value="biology">生物</option>
- <option value="history">历史</option>
- <option value="geography">地理</option>
- <option value="politics">道德与法治</option>
- <option value="science">科学</option>
- </select>
- </div>
- <div class="form-group">
- <label class="form-label">年级</label>
- <select class="form-select" id="settingsGradeSelect">
- <option value="">请选择年级</option>
- <option value="1">一年级</option>
- <option value="2">二年级</option>
- <option value="3">三年级</option>
- <option value="4">四年级</option>
- <option value="5">五年级</option>
- <option value="6">六年级</option>
- <option value="7">七年级</option>
- <option value="8">八年级</option>
- <option value="9">九年级</option>
- </select>
- </div>
- </div>
- <div class="settings-actions">
- <button class="settings-btn" onclick="hideCourseSettings()">取消</button>
- <button class="settings-btn primary" onclick="applyCourseSettings()">应用</button>
- </div>
- </div>
- </div>
- <!-- PPT上传隐藏输入 -->
- <input type="file" id="pptFileInput" accept=".ppt,.pptx" style="display: none;" onchange="handlePptFileSelected(event)">
- <!-- PPT解析浮窗 -->
- <div class="ppt-parse-overlay" id="pptParseOverlay">
- <div class="ppt-parse-card">
- <div class="ppt-parse-icon" id="pptParseIcon"></div>
- <div class="ppt-parse-title" id="pptParseTitle">解析中...</div>
- <div class="ppt-parse-desc" id="pptParseDesc">正在处理所选PPT文件</div>
- <div class="ppt-parse-actions" id="pptParseActions">
- <button class="ppt-parse-btn primary" onclick="closePptParseOverlay()">关闭</button>
- </div>
- </div>
- </div>
- <!-- 发布弹窗 -->
- <div class="modal-overlay" id="publishModal">
- <div class="publish-modal">
- <button class="modal-close" onclick="hidePublishModal()">
- <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
- <line x1="18" y1="6" x2="6" y2="18"/>
- <line x1="6" y1="6" x2="18" y2="18"/>
- </svg>
- </button>
- <div class="publish-header">
- <h2 class="publish-title">发布课程</h2>
- <div class="publish-course-name" id="publishCourseNameDisplay" onclick="editCourseName()">
- 水的三态变化
- </div>
- </div>
- <div class="publish-content">
- <!-- 左侧表单 -->
- <form class="publish-form" onsubmit="confirmPublish(event)">
- <div class="form-group">
- <label class="form-label">
- 学科 <span class="required">*</span>
- </label>
- <select class="form-select" id="subjectSelect" required>
- <option value="">请选择学科</option>
- <option value="chinese">语文</option>
- <option value="math">数学</option>
- <option value="english">英语</option>
- <option value="physics">物理</option>
- <option value="chemistry">化学</option>
- <option value="biology">生物</option>
- <option value="history">历史</option>
- <option value="geography">地理</option>
- <option value="politics">道德与法治</option>
- <option value="science">科学</option>
- </select>
- </div>
- <div class="form-row">
- <div class="form-group">
- <label class="form-label">
- 年级 <span class="required">*</span>
- </label>
- <select class="form-select" id="classGradeSelect" required>
- <option value="">请选择年级</option>
- <option value="1">一年级</option>
- <option value="2">二年级</option>
- <option value="3">三年级</option>
- <option value="4">四年级</option>
- <option value="5">五年级</option>
- <option value="6">六年级</option>
- <option value="7">七年级</option>
- <option value="8">八年级</option>
- <option value="9">九年级</option>
- </select>
- </div>
- <div class="form-group">
- <label class="form-label">
- 班级 <span class="required">*</span>
- </label>
- <select class="form-select" id="classSelect" required>
- <option value="">请选择班级</option>
- <option value="1">1班</option>
- <option value="2">2班</option>
- <option value="3">3班</option>
- <option value="4">4班</option>
- <option value="5">5班</option>
- <option value="6">6班</option>
- <option value="7">7班</option>
- <option value="8">8班</option>
- <option value="9">9班</option>
- <option value="10">10班</option>
- </select>
- </div>
- </div>
- <div class="form-group">
- <label class="form-label">
- 可见范围 <span class="required">*</span>
- </label>
- <div class="visibility-options">
- <label class="radio-option selected" onclick="selectVisibility(this, 'students')">
- <input type="radio" name="visibility" value="students" checked>
- <div class="radio-custom"></div>
- <div class="radio-content">
- <div class="radio-label">仅发布学生可见</div>
- <div class="radio-description">仅对发布的班级学生可见,其他人无法访问</div>
- </div>
- </label>
- <label class="radio-option" onclick="selectVisibility(this, 'organization')">
- <input type="radio" name="visibility" value="organization">
- <div class="radio-custom"></div>
- <div class="radio-content">
- <div class="radio-label">组织可见</div>
- <div class="radio-description">学校内所有师生均可查看和使用</div>
- </div>
- </label>
- </div>
- </div>
- <div class="publish-actions">
- <button type="button" class="publish-btn publish-btn-cancel" onclick="hidePublishModal()">
- 取消
- </button>
- <button type="submit" class="publish-btn publish-btn-confirm">
- <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
- <path d="M22 11.08V12a10 10 0 11-5.93-9.14"/>
- <polyline points="22 4 12 14.01 9 11.01"/>
- </svg>
- 确认发布
- </button>
- </div>
- </form>
- <!-- 右侧封面 -->
- <div class="publish-cover-section">
- <label class="publish-cover-label">课程封面</label>
- <div class="publish-cover-wrapper">
- <div class="publish-cover-placeholder" id="coverPlaceholder">
- <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
- <rect x="3" y="3" width="18" height="18" rx="2" ry="2"/>
- <circle cx="8.5" cy="8.5" r="1.5"/>
- <polyline points="21 15 16 10 5 21"/>
- </svg>
- <p>悬浮选择上传方式</p>
- </div>
- <img id="courseCoverImage" style="display: none;">
-
- <!-- 悬浮菜单 -->
- <div class="publish-cover-menu">
- <div class="publish-cover-menu-item" onclick="uploadCourseCoverLocal(event)">
- <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
- <path d="M21 15v4a2 2 0 01-2 2H5a2 2 0 01-2-2v-4"/>
- <polyline points="17 8 12 3 7 8"/>
- <line x1="12" y1="3" x2="12" y2="15"/>
- </svg>
- 自本地上传
- </div>
- <div class="publish-cover-menu-item" onclick="searchWebImageForCover(event)">
- <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
- <circle cx="11" cy="11" r="8"/>
- <path d="M21 21l-4.35-4.35"/>
- </svg>
- 自网页搜索
- </div>
- <div class="publish-cover-menu-item" onclick="generateAIImageForCover(event)">
- <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
- <path d="M12 2L2 7l10 5 10-5-10-5z"/>
- <path d="M2 17l10 5 10-5"/>
- <path d="M2 12l10 5 10-5"/>
- </svg>
- 自AI生成
- </div>
- </div>
-
- <!-- 已上传封面时显示的操作按钮 -->
- <div class="publish-cover-actions">
- <button class="cover-action-btn delete" type="button" onclick="event.stopPropagation(); deleteCourseCover()" title="删除封面">
- <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
- <polyline points="3 6 5 6 21 6"/>
- <path d="M19 6v14a2 2 0 01-2 2H7a2 2 0 01-2-2V6m3 0V4a2 2 0 012-2h4a2 2 0 012 2v2"/>
- </svg>
- </button>
- </div>
- </div>
- </div>
- </div>
- </div>
- </div>
- <!-- 隐藏的文件上传输入框 -->
- <input type="file" id="courseCoverInput" accept="image/*" style="display: none;" onchange="handleCoverUpload(event)">
- <!-- 网页搜索图片浮窗 -->
- <div class="floating-modal" id="searchImageModal">
- <div class="floating-content">
- <div class="floating-header">
- <h3 class="floating-title">网页搜索图片</h3>
- <button class="floating-close" onclick="closeSearchModal()">
- <svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
- <line x1="18" y1="6" x2="6" y2="18"/>
- <line x1="6" y1="6" x2="18" y2="18"/>
- </svg>
- </button>
- </div>
- <div class="search-input-group">
- <label class="search-input-label">搜索关键词</label>
- <input type="text" class="search-input" id="searchKeyword" placeholder="AI已为您生成关键词..." value="水的三态变化 实验">
- </div>
- <div class="search-results" id="searchResults">
- <!-- 搜索结果将在这里显示 -->
- <div class="search-result-item" style="background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);">
- </div>
- <div class="search-result-item" style="background: linear-gradient(135deg, #f093fb 0%, #f5576c 100%);">
- </div>
- <div class="search-result-item" style="background: linear-gradient(135deg, #4facfe 0%, #00f2fe 100%);">
- </div>
- <div class="search-result-item" style="background: linear-gradient(135deg, #43e97b 0%, #38f9d7 100%);">
- </div>
- <div class="search-result-item" style="background: linear-gradient(135deg, #fa709a 0%, #fee140 100%);">
- </div>
- <div class="search-result-item" style="background: linear-gradient(135deg, #30cfd0 0%, #330867 100%);">
- </div>
- </div>
- <div class="floating-actions">
- <button class="floating-btn floating-btn-secondary" onclick="closeSearchModal()">取消</button>
- <button class="floating-btn floating-btn-primary" onclick="confirmSearchImage()">确定</button>
- </div>
- </div>
- </div>
- <!-- AI生成图片浮窗 -->
- <div class="floating-modal" id="generateImageModal">
- <div class="floating-content">
- <div class="floating-header">
- <h3 class="floating-title">AI生成图片</h3>
- <button class="floating-close" onclick="closeGenerateModal()">
- <svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
- <line x1="18" y1="6" x2="6" y2="18"/>
- <line x1="6" y1="6" x2="18" y2="18"/>
- </svg>
- </button>
- </div>
- <div class="search-input-group">
- <label class="search-input-label">生成描述</label>
- <input type="text" class="search-input" id="generatePrompt" placeholder="AI已为您生成描述..." value="水分子在不同温度下的三态变化示意图">
- </div>
- <div class="generate-preview" id="generatePreview">
- <svg width="64" height="64" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5">
- <rect x="3" y="3" width="18" height="18" rx="2" ry="2"/>
- <circle cx="8.5" cy="8.5" r="1.5"/>
- <polyline points="21 15 16 10 5 21"/>
- </svg>
- <div class="generate-loading" id="generateLoading">
- <div class="loading-spinner"></div>
- <div class="loading-text">AI生成中...</div>
- </div>
- </div>
- <div class="floating-actions">
- <button class="floating-btn floating-btn-secondary" onclick="closeGenerateModal()">取消</button>
- <button class="floating-btn floating-btn-primary" onclick="startGenerate()">生成</button>
- </div>
- </div>
- </div>
- <!-- AI应用中心浮窗 -->
- <div class="floating-modal" id="aiAppCenterModal">
- <div class="app-center-content">
- <div class="floating-header">
- <h3 class="floating-title">应用中心</h3>
- <button class="floating-close" onclick="closeAppCenter()">
- <svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
- <line x1="18" y1="6" x2="6" y2="18"/>
- <line x1="6" y1="6" x2="18" y2="18"/>
- </svg>
- </button>
- </div>
- <!-- 筛选区 -->
- <div class="app-filters">
- <div class="filter-group">
- <div class="filter-label">应用来源</div>
- <select class="filter-select" id="sourceFilter" onchange="filterApps()">
- <option value="all">所有应用</option>
- <option value="public">公开应用</option>
- <option value="mine">我的应用</option>
- </select>
- </div>
- <div class="filter-group">
- <div class="filter-label">应用类型</div>
- <select class="filter-select" id="typeFilter" onchange="filterApps()">
- <option value="all">所有类型</option>
- <option value="agent">智能体</option>
- <option value="workflow">工作流</option>
- </select>
- </div>
- <div class="filter-group">
- <div class="filter-label">交互模式</div>
- <select class="filter-select" id="modeFilter" onchange="filterApps()">
- <option value="all">所有模式</option>
- <option value="card">卡片式</option>
- <option value="immersive">沉浸式</option>
- <option value="chat">聊天式</option>
- </select>
- </div>
- </div>
- <!-- 应用列表 -->
- <div class="app-grid" id="appGrid">
- <!-- 应用卡片将在这里动态生成 -->
- </div>
- <!-- 底部操作栏 -->
- <div class="app-center-footer">
- <div class="selected-count">已选择 <span id="selectedCountText">0</span> 个应用</div>
- <div class="app-center-actions">
- <button class="app-center-btn app-center-btn-confirm" id="confirmAddAppsBtn" onclick="confirmAddApps()" disabled>
- <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
- <polyline points="20 6 9 17 4 12"/>
- </svg>
- 添加
- </button>
- </div>
- </div>
- </div>
- </div>
- <!-- 模板中心浮窗 -->
- <div class="floating-modal" id="templateCenterModal">
- <div class="app-center-content">
- <div class="floating-header">
- <h3 class="floating-title">模板中心</h3>
- <button class="floating-close" onclick="closeTemplateCenter()">
- <svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
- <line x1="18" y1="6" x2="6" y2="18"/>
- <line x1="6" y1="6" x2="18" y2="18"/>
- </svg>
- </button>
- </div>
- <div class="app-filters">
- <div class="filter-group">
- <div class="filter-label">来源</div>
- <select class="filter-select" id="tplSourceFilter" onchange="filterTemplates()">
- <option value="all">所有模板</option>
- <option value="official">官方</option>
- <option value="mine">我的</option>
- </select>
- </div>
- <div class="filter-group">
- <div class="filter-label">学科</div>
- <select class="filter-select" id="tplSubjectFilter" onchange="filterTemplates()">
- <option value="all">全部学科</option>
- <option value="chinese">语文</option>
- <option value="math">数学</option>
- <option value="english">英语</option>
- <option value="science">科学</option>
- </select>
- </div>
- <div class="filter-group">
- <div class="filter-label">年级</div>
- <select class="filter-select" id="tplGradeFilter" onchange="filterTemplates()">
- <option value="all">全部年级</option>
- <option value="1">一年级</option>
- <option value="2">二年级</option>
- <option value="3">三年级</option>
- <option value="4">四年级</option>
- <option value="5">五年级</option>
- <option value="6">六年级</option>
- <option value="7">七年级</option>
- <option value="8">八年级</option>
- <option value="9">九年级</option>
- </select>
- </div>
- </div>
- <div class="app-grid" id="templateGrid">
- <!-- 模板卡片将在这里生成 -->
- </div>
- <div class="app-center-footer">
- <div class="selected-count">已选择 <span id="selectedTemplateCount">0</span> 个模板</div>
- <div class="app-center-actions">
- <button class="app-center-btn app-center-btn-confirm" id="confirmApplyTemplateBtn" onclick="confirmApplyTemplates()" disabled>
- <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
- <polyline points="20 6 9 17 4 12"/>
- </svg>
- 应用
- </button>
- </div>
- </div>
- </div>
- </div>
- <!-- 删除课程确认弹窗 -->
- <div class="modal-overlay" id="deleteCourseModal">
- <div class="confirm-modal">
- <div class="confirm-title">删除课程</div>
- <div class="confirm-text">删除后不可恢复,课件及关联资源将移除,确认要删除当前课程吗?</div>
- <div class="confirm-actions">
- <button class="settings-btn" onclick="hideDeleteCourse()">取消</button>
- <button class="settings-btn primary" onclick="confirmDeleteCourse()">删除</button>
- </div>
- </div>
- </div>
- <!-- 网页中心浮窗 -->
- <div class="floating-modal" id="webCenterModal">
- <div class="web-center-content">
- <div class="floating-header">
- <h3 class="floating-title">网页中心</h3>
- <button class="floating-close" onclick="closeWebCenter()">
- <svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
- <line x1="18" y1="6" x2="6" y2="18"/>
- <line x1="6" y1="6" x2="18" y2="18"/>
- </svg>
- </button>
- </div>
- <!-- 筛选区 -->
- <div class="web-filters">
- <div class="filter-group">
- <div class="filter-label">网页来源</div>
- <select class="filter-select" id="webSourceFilter" onchange="filterWebs()">
- <option value="all">所有网页</option>
- <option value="public">公开网页</option>
- <option value="mine">我的网页</option>
- </select>
- </div>
- <div class="filter-group">
- <div class="filter-label">学科分类</div>
- <select class="filter-select" id="webSubjectFilter" onchange="filterWebs()">
- <option value="all">所有学科</option>
- <option value="语文">语文</option>
- <option value="数学">数学</option>
- <option value="英语">英语</option>
- <option value="物理">物理</option>
- <option value="化学">化学</option>
- <option value="生物">生物</option>
- <option value="历史">历史</option>
- <option value="地理">地理</option>
- </select>
- </div>
- <div class="filter-group">
- <div class="filter-label">年级分类</div>
- <select class="filter-select" id="webGradeFilter" onchange="filterWebs()">
- <option value="all">所有年级</option>
- <option value="小学">小学</option>
- <option value="初中">初中</option>
- <option value="高中">高中</option>
- </select>
- </div>
- </div>
- <!-- 网页列表 -->
- <div class="web-grid" id="webGrid">
- <!-- 网页卡片将在这里动态生成 -->
- </div>
- <!-- 底部操作栏 -->
- <div class="web-center-footer">
- <div class="selected-count">已选择 <span id="webSelectedCountText">0</span> 个网页</div>
- <div class="app-center-actions">
- <button class="app-center-btn app-center-btn-confirm" id="confirmAddWebsBtn" onclick="confirmAddWebs()" disabled>
- <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
- <polyline points="20 6 9 17 4 12"/>
- </svg>
- 添加
- </button>
- </div>
- </div>
- </div>
- </div>
- <!-- 网页详情浮窗 -->
- <div class="web-detail-modal" id="webDetailModal">
- <div class="web-detail-content">
- <!-- 左侧预览区 -->
- <div class="web-preview-area">
- <div class="web-preview-header">
- <h4 class="web-preview-title" id="webDetailPreviewTitle">网页预览</h4>
- <button class="web-fullscreen-btn" onclick="toggleWebPreviewFullscreen()">
- <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
- <path d="M8 3H5a2 2 0 00-2 2v3m18 0V5a2 2 0 00-2-2h-3m0 18h3a2 2 0 002-2v-3M3 16v3a2 2 0 002 2h3"/>
- </svg>
- 全屏
- </button>
- </div>
- <div class="web-preview-iframe" id="webDetailPreview">
- <iframe src="" frameborder="0" id="webDetailIframe"></iframe>
- </div>
- </div>
- <!-- 右侧信息区 -->
- <div class="web-info-area">
- <div class="web-detail-header">
- <h3 class="web-detail-name" id="webDetailName">网页名称</h3>
- <div class="web-meta-item">
- <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
- <path d="M12 6.253v13m0-13C10.832 5.477 9.246 5 7.5 5S4.168 5.477 3 6.253v13C4.168 18.477 5.754 18 7.5 18s3.332.477 4.5 1.253m0-13C13.168 5.477 14.754 5 16.5 5c1.747 0 3.332.477 4.5 1.253v13C19.832 18.477 18.247 18 16.5 18c-1.746 0-3.332.477-4.5 1.253"/>
- </svg>
- <span id="webDetailSubject">学科</span>
- </div>
- <div class="web-meta-item">
- <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
- <path d="M19 21V5a2 2 0 00-2-2H7a2 2 0 00-2 2v16m14 0h2m-2 0h-5m-9 0H3m2 0h5M9 7h1m-1 4h1m4-4h1m-1 4h1m-5 10v-5a1 1 0 011-1h2a1 1 0 011 1v5m-4 0h4"/>
- </svg>
- <span id="webDetailGrade">年级</span>
- </div>
- <div class="web-meta-item">
- <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
- <circle cx="12" cy="12" r="10"/>
- <polyline points="12 6 12 12 16 14"/>
- </svg>
- <span id="webDetailDuration">时长</span>
- </div>
- </div>
- <div class="web-detail-body">
- <div class="web-detail-section">
- <h4 class="web-detail-section-title">简介</h4>
- <p class="web-detail-section-content" id="webDetailDescription">
- 网页简介内容
- </p>
- </div>
- <div class="web-detail-section">
- <h4 class="web-detail-section-title">作者信息</h4>
- <p class="web-detail-section-content" id="webDetailAuthor">
- 作者名称
- </p>
- </div>
- <div class="web-detail-section">
- <h4 class="web-detail-section-title">创建时间</h4>
- <p class="web-detail-section-content" id="webDetailTime">
- 创建时间
- </p>
- </div>
- </div>
- <div class="web-detail-footer">
- <button class="web-detail-btn web-detail-btn-secondary" onclick="closeWebDetail()">返回列表</button>
- <button class="web-detail-btn web-detail-btn-primary" onclick="addWebFromDetail()">添加到课件</button>
- </div>
- </div>
- </div>
- </div>
- <!-- 资源 - 视频来源弹窗 -->
- <div class="floating-modal" id="videoSourceModal">
- <div class="floating-content">
- <div class="floating-header">
- <h3 class="floating-title">选择视频来源</h3>
- <button class="floating-close" onclick="closeVideoSourceModal()">
- <svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
- <line x1="18" y1="6" x2="6" y2="18"/>
- <line x1="6" y1="6" x2="18" y2="18"/>
- </svg>
- </button>
- </div>
- <div class="template-grid" style="grid-template-columns: repeat(2, 1fr);">
- <div class="template-card" onclick="chooseLocalVideo()">
- <div class="template-preview">
- <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
- <path d="M12 5v14"/>
- <polyline points="5 12 12 5 19 12"/>
- </svg>
- </div>
- <div class="template-name">本地上传</div>
- </div>
- <div class="template-card" onclick="chooseBilibiliVideo()">
- <div class="template-preview">
- <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
- <rect x="3" y="4" width="18" height="14" rx="2"/>
- <polyline points="10 9 16 12 10 15 10 9"/>
- </svg>
- </div>
- <div class="template-name">Bilibili 检索</div>
- </div>
- </div>
- </div>
- </div>
- <!-- 资源 - 音频上传弹窗 -->
- <div class="floating-modal" id="audioUploadModal">
- <div class="floating-content">
- <div class="floating-header">
- <h3 class="floating-title">上传音频</h3>
- <button class="floating-close" onclick="closeAudioUploadModal()">
- <svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
- <line x1="18" y1="6" x2="6" y2="18"/>
- <line x1="6" y1="6" x2="18" y2="18"/>
- </svg>
- </button>
- </div>
- <div class="web-config-form">
- <div class="file-upload-area" onclick="document.getElementById('audioFileInput').click()">
- <input type="file" id="audioFileInput" accept="audio/*" style="display: none;" onchange="handleAudioFile(event)">
- <svg width="48" height="48" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5">
- <path d="M21 15v4a2 2 0 01-2 2H5a2 2 0 01-2-2v-4"/>
- <polyline points="17 8 12 3 7 8"/>
- <line x1="12" y1="3" x2="12" y2="15"/>
- </svg>
- <p class="upload-text">点击或拖拽音频到此处上传</p>
- <p class="upload-hint">支持.mp3、.wav等常见音频格式</p>
- </div>
- <div id="audioFileNameDisplay" class="file-name-display"></div>
- <div class="resource-actions">
- <button class="floating-btn floating-btn-secondary" onclick="closeAudioUploadModal()">取消</button>
- <button class="floating-btn floating-btn-primary" id="confirmAudioUploadBtn" onclick="confirmAudioUpload()" disabled>确定</button>
- </div>
- </div>
- </div>
- </div>
- <!-- 资源 - 文档上传弹窗 -->
- <div class="floating-modal" id="documentUploadModal">
- <div class="floating-content">
- <div class="floating-header">
- <h3 class="floating-title">上传文档</h3>
- <button class="floating-close" onclick="closeDocumentUploadModal()">
- <svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
- <line x1="18" y1="6" x2="6" y2="18"/>
- <line x1="6" y1="6" x2="18" y2="18"/>
- </svg>
- </button>
- </div>
- <div class="web-config-form">
- <div class="file-upload-area" onclick="document.getElementById('documentFileInput').click()">
- <input type="file" id="documentFileInput" accept=".docx,.pdf" style="display: none;" onchange="handleDocumentFile(event)">
- <svg width="48" height="48" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5">
- <path d="M21 15v4a2 2 0 01-2 2H5a2 2 0 01-2-2v-4"/>
- <polyline points="17 8 12 3 7 8"/>
- <line x1="12" y1="3" x2="12" y2="15"/>
- </svg>
- <p class="upload-text">点击或拖拽文档到此处上传</p>
- <p class="upload-hint">支持 .docx、.doc、.pdf格式</p>
- </div>
- <div id="documentFileNameDisplay" class="file-name-display"></div>
- <div class="resource-actions">
- <button class="floating-btn floating-btn-secondary" onclick="closeDocumentUploadModal()">取消</button>
- <button class="floating-btn floating-btn-primary" id="confirmDocumentUploadBtn" onclick="confirmDocumentUpload()" disabled>确定</button>
- </div>
- </div>
- </div>
- </div>
- <!-- 资源 - 资源集合弹窗 -->
- <div class="floating-modal" id="collectionModal">
- <div class="floating-content">
- <div class="floating-header">
- <h3 class="floating-title">资源集合</h3>
- <button class="floating-close" onclick="closeCollectionModal()">
- <svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
- <line x1="18" y1="6" x2="6" y2="18"/>
- <line x1="6" y1="6" x2="18" y2="18"/>
- </svg>
- </button>
- </div>
- <div class="web-config-form">
- <div class="file-upload-area" onclick="document.getElementById('collectionFileInput').click()">
- <input type="file" id="collectionFileInput" multiple style="display: none;" onchange="handleCollectionFiles(event)">
- <svg width="48" height="48" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5">
- <path d="M21 15v4a2 2 0 01-2 2H5a2 2 0 01-2-2v-4"/>
- <polyline points="17 8 12 3 7 8"/>
- <line x1="12" y1="3" x2="12" y2="15"/>
- </svg>
- <p class="upload-text">点击或拖拽文件到此处上传</p>
- <p class="upload-hint">支持 .docx、.doc、.pdf格式</p>
- </div>
- <div class="resource-upload-list" id="collectionFileList"></div>
- <div class="resource-tip">最多添加5个文件</div>
- <div class="resource-actions">
- <button class="floating-btn floating-btn-secondary" onclick="closeCollectionModal()">取消</button>
- <button class="floating-btn floating-btn-primary" id="confirmCollectionBtn" onclick="confirmCollection()" disabled>确定</button>
- </div>
- </div>
- </div>
- </div>
- <!-- 创建应用方式选择弹窗 -->
- <div class="modal-overlay" id="createAppMethodModal">
- <div class="create-modal">
- <button class="modal-close" onclick="hideCreateAppMethodModal()">
- <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
- <line x1="18" y1="6" x2="6" y2="18"/>
- <line x1="6" y1="6" x2="18" y2="18"/>
- </svg>
- </button>
- <div class="modal-header">
- <h2>创建应用</h2>
- <p>选择一种方式开始创建您的AI应用</p>
- </div>
- <div class="create-options">
- <div class="create-card featured" onclick="selectCreateMethod('ai')">
- <div class="create-icon">
- <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
- <path d="M12 2L2 7l10 5 10-5-10-5z"/>
- <path d="M2 17l10 5 10-5"/>
- <path d="M2 12l10 5 10-5"/>
- </svg>
- </div>
- <h3>自AI创建</h3>
- <p>通过对话让AI为您生成应用</p>
- </div>
- <div class="create-card" onclick="selectCreateMethod('blank')">
- <div class="create-icon">
- <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
- <rect x="3" y="3" width="18" height="18" rx="2" ry="2"/>
- <line x1="12" y1="8" x2="12" y2="16"/>
- <line x1="8" y1="12" x2="16" y2="12"/>
- </svg>
- </div>
- <h3>自空白创建</h3>
- <p>从空白画布开始手动创建应用</p>
- </div>
- </div>
- </div>
- </div>
- <!-- AI创建应用浮窗 -->
- <div class="floating-modal" id="aiCreateAppModal">
- <div class="ai-create-app-content">
- <div class="floating-header">
- <h3 class="floating-title">AI创建应用</h3>
- <button class="floating-close" onclick="closeAICreateModal()">
- <svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
- <line x1="18" y1="6" x2="6" y2="18"/>
- <line x1="6" y1="6" x2="18" y2="18"/>
- </svg>
- </button>
- </div>
- <div class="ai-create-main">
- <!-- 左侧画布预览 -->
- <div class="app-canvas-preview" id="appCanvasPreview">
- <span>应用画布预览区</span>
- </div>
- <!-- 右侧对话栏 -->
- <div class="ai-chat-panel">
- <div class="ai-chat-messages" id="aiChatMessages">
- <!-- 对话消息将在这里显示 -->
- </div>
- <div class="ai-chat-input-area">
- <div class="ai-chat-input-wrapper">
- <textarea class="ai-chat-input" id="aiChatInput" placeholder="描述您想要的应用功能..." rows="2"></textarea>
- <button class="ai-chat-send-btn" id="aiChatSendBtn" onclick="sendMessageToAI()">
- <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
- <line x1="22" y1="2" x2="11" y2="13"/>
- <polygon points="22 2 15 22 11 13 2 9 22 2"/>
- </svg>
- </button>
- </div>
- </div>
- </div>
- </div>
- <div class="ai-create-footer">
- <button class="app-center-btn app-center-btn-cancel" onclick="closeAICreateModal()">取消</button>
- <button class="app-center-btn app-center-btn-confirm" id="confirmCreateAppBtn" onclick="confirmAICreatedApp()">
- 确认创建
- </button>
- </div>
- </div>
- </div>
- <script>
- let isEditMode = false;
- let elementIdCounter = 0;
- let selectedElement = null;
- let currentTool = null; // 当前选中的工具
- let isToolMode = false; // 是否处于工具编辑模式
- // 工具配置数据
- const toolConfigs = {
- choice: {
- name: '选择',
- icon: '<circle cx="12" cy="12" r="10"/><path d="M12 16v-4m0-4h.01"/>',
- hasTime: true,
- hasViewWork: true,
- hasVoting: false,
- hasShowAnswer: true
- },
- qa: {
- name: '问答',
- icon: '<path d="M21 15a2 2 0 01-2 2H7l-4 4V5a2 2 0 012-2h14a2 2 0 012 2z"/>',
- hasTime: true,
- hasViewWork: true,
- hasVoting: true
- },
- vote: {
- name: '投票',
- icon: '<polyline points="9 11 12 14 22 4"/><path d="M21 12v7a2 2 0 01-2 2H5a2 2 0 01-2-2V5a2 2 0 012-2h11"/>',
- hasTime: true,
- hasViewWork: true,
- hasVoting: false
- },
- photo: {
- name: '拍照',
- icon: '<path d="M23 19a2 2 0 01-2 2H3a2 2 0 01-2-2V8a2 2 0 012-2h4l2-3h6l2 3h4a2 2 0 012 2z"/><circle cx="12" cy="13" r="4"/>',
- hasTime: true,
- hasViewWork: true,
- hasVoting: true
- },
- fillblank: {
- name: '填空',
- icon: '<line x1="8" y1="6" x2="21" y2="6"/><line x1="8" y1="12" x2="21" y2="12"/><line x1="8" y1="18" x2="21" y2="18"/><line x1="3" y1="6" x2="3.01" y2="6"/><line x1="3" y1="12" x2="3.01" y2="12"/><line x1="3" y1="18" x2="3.01" y2="18"/>',
- hasTime: true,
- hasViewWork: true,
- hasVoting: false
- },
- sort: {
- name: '排序',
- icon: '<line x1="4" y1="6" x2="20" y2="6"/><line x1="4" y1="12" x2="20" y2="12"/><line x1="4" y1="18" x2="20" y2="18"/>',
- hasTime: true,
- hasViewWork: true,
- hasVoting: false
- },
- whiteboard: {
- name: '白板',
- icon: '<path d="M12 19l7-7 3 3-7 7-3-3z"/><path d="M18 13l-1.5-7.5L2 2l3.5 14.5L13 18l5-5z"/><path d="M2 2l7.586 7.586"/>',
- hasTime: true,
- hasViewWork: true,
- hasVoting: true
- },
- flashcard: {
- name: '抽认卡',
- icon: '<rect x="2" y="4" width="20" height="16" rx="2"/><path d="M7 15h10M12 9v6"/>',
- hasTime: true,
- hasViewWork: false,
- hasVoting: false,
- hasLearningMode: true
- },
- cocopi: {
- name: 'CocoPi',
- icon: '<polyline points="16 18 22 12 16 6"/><polyline points="8 6 2 12 8 18"/>',
- hasTime: true,
- hasViewWork: true,
- hasVoting: true
- },
- workspace: {
- name: '创作空间',
- icon: '<path d="M12 2L2 7l10 5 10-5-10-5z"/><path d="M2 17l10 5 10-5"/><path d="M2 12l10 5 10-5"/>',
- hasTime: true,
- hasViewWork: true,
- hasVoting: true
- }
- };
- // 选择工具
- function selectTool(toolType) {
- currentTool = toolType;
- // 切换到工具编辑模式
- switchToToolMode(toolType);
- // 显示配置面板
- showToolConfig(toolType);
- // 显示工具编辑区域
- showToolEditArea(toolType);
- }
- // 切换到工具模式
- function switchToToolMode(toolType) {
- isToolMode = true;
- // 隐藏普通元素工具栏
- document.getElementById('elementToolbar').classList.remove('visible');
- document.getElementById('elementToolbar').style.display = 'none';
- // 显示底部大纲
- document.getElementById('bottomOutline').classList.add('visible');
- // 隐藏占位符
- const placeholder = document.getElementById('slidePlaceholder');
- if (placeholder) {
- placeholder.style.display = 'none';
- }
- // 如果还没有进入编辑模式,则进入
- if (!isEditMode) {
- isEditMode = true;
- if (document.getElementById('outlineTrack').children.length === 0) {
- generateSamplePages();
- }
- }
- }
- // 显示工具配置面板
- function showToolConfig(toolType) {
- const config = toolConfigs[toolType];
- // 切换视图
- document.getElementById('toolsListView').classList.add('hidden');
- document.getElementById('toolsConfigView').classList.remove('hidden');
- // 更新配置标题
- document.getElementById('currentToolName').textContent = config.name;
- // 生成配置内容
- const configContent = document.getElementById('toolConfigContent');
- let html = '';
- // 查看作业配置
- if (config.hasViewWork) {
- html += `
- <div class="config-section">
- <div class="config-switch" onclick="toggleSwitch('viewWork')">
- <span class="config-switch-label">学生查看结果</span>
- <div class="switch-toggle" id="viewWorkSwitch"></div>
- </div>
- </div>
- `;
- }
- // 提交后显示答案配置(选择题专用)
- if (config.hasShowAnswer) {
- html += `
- <div class="config-switch" onclick="toggleSwitch('showAnswer')">
- <span class="config-switch-label">提交后显示答案</span>
- <div class="switch-toggle" id="showAnswerSwitch"></div>
- </div>
- `;
- }
- // 投票配置
- if (config.hasVoting) {
- html += `
- <div class="config-switch" onclick="toggleSwitch('voting')">
- <span class="config-switch-label">学生点赞</span>
- <div class="switch-toggle" id="votingSwitch"></div>
- </div>
- `;
- }
- // 学习模式配置(抽认卡专用)
- if (config.hasLearningMode) {
- html += `
- <div class="config-section">
- <label class="config-label">学习模式</label>
- <div class="config-mode-list">
- <div class="mode-item active" onclick="toggleModeRadio(this, 'random')">
- <div class="mode-radio">
- <div class="mode-radio-dot"></div>
- </div>
- <span class="mode-label">随机乱序</span>
- </div>
- <div class="mode-item" onclick="toggleModeRadio(this, 'autoread')">
- <div class="mode-radio">
- <div class="mode-radio-dot"></div>
- </div>
- <span class="mode-label">自动朗读</span>
- </div>
- <div class="mode-item" onclick="toggleModeRadio(this, 'flip')">
- <div class="mode-radio">
- <div class="mode-radio-dot"></div>
- </div>
- <span class="mode-label">正反对调</span>
- </div>
- </div>
- </div>
- `;
- }
- configContent.innerHTML = html;
- // 隐藏顶部标题栏
- const panelHeader = document.querySelector('#toolsPanel .panel-header');
- if (panelHeader) {
- panelHeader.style.display = 'none';
- }
- }
- // 显示工具编辑区域
- function showToolEditArea(toolType) {
- const toolEditContainer = document.getElementById('toolEditContainer');
- const slideCanvas = document.getElementById('slideCanvas');
- const config = toolConfigs[toolType];
- // 隐藏slide-canvas,显示工具编辑容器
- if (slideCanvas) {
- slideCanvas.style.display = 'none';
- }
- if (toolEditContainer) {
- toolEditContainer.style.display = 'flex';
- }
- // 创建工具编辑区域HTML
- let html = `
- <div class="tool-header">
- <div class="tool-type-selector" id="toolTypeSelector">
- <button class="tool-type-btn" onclick="toggleToolTypeDropdown()">
- <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
- ${config.icon}
- </svg>
- <span>${config.name}</span>
- <svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
- <polyline points="6 9 12 15 18 9"/>
- </svg>
- </button>
- <div class="tool-type-dropdown">
- ${Object.keys(toolConfigs).map(key => {
- const cfg = toolConfigs[key];
- return `
- <div class="tool-type-item ${key === toolType ? 'active' : ''}" onclick="switchToolType('${key}')">
- <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
- ${cfg.icon}
- </svg>
- <span>${cfg.name}</span>
- </div>
- `;
- }).join('')}
- </div>
- </div>
- </div>
- <div class="tool-edit-area">
- <div class="tool-canvas" id="toolCanvas">
- ${generateToolContent(toolType)}
- </div>
- </div>
- `;
- toolEditContainer.innerHTML = html;
-
- // 如果是排序工具,初始化拖动功能
- if (toolType === 'sort') {
- setTimeout(initSortDragAndDrop, 100);
- }
- }
- // 生成工具内容
- function generateToolContent(toolType) {
- switch(toolType) {
- case 'choice':
- return generateChoiceContent();
- case 'qa':
- return generateQAContent();
- case 'vote':
- return generateVoteContent();
- case 'photo':
- return generatePhotoContent();
- case 'fillblank':
- return generateFillBlankContent();
- case 'sort':
- return generateSortContent();
- case 'whiteboard':
- return generateWhiteboardContent();
- case 'flashcard':
- return generateFlashcardContent();
- case 'cocopi':
- return generateCocoPiContent();
- case 'workspace':
- return generateWorkspaceContent();
- default:
- return '<div>工具内容加载中...</div>';
- }
- }
- // 生成选择题内容
- function generateChoiceContent() {
- return `
- <div class="question-item">
- <div class="question-header">
- <span class="question-number">题目 1</span>
- <div class="question-actions">
- <button class="question-action-btn" onclick="copyQuestion(this)" title="复制题目">
- <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
- <rect x="9" y="9" width="13" height="13" rx="2" ry="2"/>
- <path d="M5 15H4a2 2 0 01-2-2V4a2 2 0 012-2h9a2 2 0 012 2v1"/>
- </svg>
- </button>
- <button class="question-action-btn delete" onclick="deleteQuestion(this)" title="删除题目">
- <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
- <polyline points="3 6 5 6 21 6"/>
- <path d="M19 6v14a2 2 0 01-2 2H7a2 2 0 01-2-2V6m3 0V4a2 2 0 012-2h4a2 2 0 012 2v2"/>
- </svg>
- </button>
- </div>
- </div>
- <div class="input-with-image">
- <textarea class="question-input" placeholder="输入题目内容...">水在多少摄氏度会结冰?</textarea>
- <button class="upload-image-btn" onclick="uploadImage(this)" title="上传图片">
- <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
- <rect x="3" y="3" width="18" height="18" rx="2" ry="2"/>
- <circle cx="8.5" cy="8.5" r="1.5"/>
- <polyline points="21 15 16 10 5 21"/>
- </svg>
- </button>
- </div>
- <div class="options-list">
- <div class="option-item">
- <div class="option-drag-handle" title="拖动排序">
- <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
- <line x1="3" y1="12" x2="21" y2="12"/>
- <line x1="3" y1="6" x2="21" y2="6"/>
- <line x1="3" y1="18" x2="21" y2="18"/>
- </svg>
- </div>
- <div class="option-checkbox checked" onclick="toggleOption(this)">
- <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
- <polyline points="20 6 9 17 4 12"/>
- </svg>
- </div>
- <input type="text" class="option-input" placeholder="选项A" value="0°C">
- <div class="option-actions">
- <button class="option-action-btn" onclick="addOptionAfter(this)" title="在下方添加选项">
- <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
- <line x1="12" y1="5" x2="12" y2="19"/>
- <line x1="5" y1="12" x2="19" y2="12"/>
- </svg>
- </button>
- <button class="option-action-btn delete" onclick="deleteOption(this)" title="删除选项">
- <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
- <line x1="18" y1="6" x2="6" y2="18"/>
- <line x1="6" y1="6" x2="18" y2="18"/>
- </svg>
- </button>
- </div>
- </div>
- <div class="option-item">
- <div class="option-drag-handle" title="拖动排序">
- <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
- <line x1="3" y1="12" x2="21" y2="12"/>
- <line x1="3" y1="6" x2="21" y2="6"/>
- <line x1="3" y1="18" x2="21" y2="18"/>
- </svg>
- </div>
- <div class="option-checkbox" onclick="toggleOption(this)">
- <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
- <polyline points="20 6 9 17 4 12"/>
- </svg>
- </div>
- <input type="text" class="option-input" placeholder="选项B" value="100°C">
- <div class="option-actions">
- <button class="option-action-btn" onclick="addOptionAfter(this)" title="在下方添加选项">
- <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
- <line x1="12" y1="5" x2="12" y2="19"/>
- <line x1="5" y1="12" x2="19" y2="12"/>
- </svg>
- </button>
- <button class="option-action-btn delete" onclick="deleteOption(this)" title="删除选项">
- <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
- <line x1="18" y1="6" x2="6" y2="18"/>
- <line x1="6" y1="6" x2="18" y2="18"/>
- </svg>
- </button>
- </div>
- </div>
- <div class="option-item">
- <div class="option-drag-handle" title="拖动排序">
- <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
- <line x1="3" y1="12" x2="21" y2="12"/>
- <line x1="3" y1="6" x2="21" y2="6"/>
- <line x1="3" y1="18" x2="21" y2="18"/>
- </svg>
- </div>
- <div class="option-checkbox" onclick="toggleOption(this)">
- <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
- <polyline points="20 6 9 17 4 12"/>
- </svg>
- </div>
- <input type="text" class="option-input" placeholder="选项C" value="50°C">
- <div class="option-actions">
- <button class="option-action-btn" onclick="addOptionAfter(this)" title="在下方添加选项">
- <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
- <line x1="12" y1="5" x2="12" y2="19"/>
- <line x1="5" y1="12" x2="19" y2="12"/>
- </svg>
- </button>
- <button class="option-action-btn delete" onclick="deleteOption(this)" title="删除选项">
- <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
- <line x1="18" y1="6" x2="6" y2="18"/>
- <line x1="6" y1="6" x2="18" y2="18"/>
- </svg>
- </button>
- </div>
- </div>
- <button class="add-option-btn-with-text" onclick="addOption(this)" title="添加选项">
- <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
- <line x1="12" y1="5" x2="12" y2="19"/>
- <line x1="5" y1="12" x2="19" y2="12"/>
- </svg>
- <span>选项</span>
- </button>
- </div>
- <div class="explanation-section">
- <div class="explanation-header">
- <span class="explanation-label">解释说明</span>
- <button class="ai-gen-btn" onclick="generateExplanation()">
- <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
- <path d="M12 2L2 7l10 5 10-5-10-5z"/>
- <path d="M2 17l10 5 10-5"/>
- <path d="M2 12l10 5 10-5"/>
- </svg>
- AI生成
- </button>
- </div>
- <textarea class="explanation-textarea" placeholder="为这道题目添加解释说明..."></textarea>
- </div>
- </div>
- <button class="add-question-btn-with-text" onclick="addQuestion()" title="添加题目">
- <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
- <line x1="12" y1="5" x2="12" y2="19"/>
- <line x1="5" y1="12" x2="19" y2="12"/>
- </svg>
- <span>题目</span>
- </button>
- `;
- }
- // 生成问答内容
- function generateQAContent() {
- return `
- <div class="question-item">
- <div class="question-header">
- <span class="question-number">题目 1</span>
- </div>
- <div class="input-with-image">
- <textarea class="question-input" placeholder="输入题目内容...">请描述水的三态变化过程</textarea>
- <button class="upload-image-btn" onclick="uploadImage(this)" title="上传图片">
- <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
- <rect x="3" y="3" width="18" height="18" rx="2" ry="2"/>
- <circle cx="8.5" cy="8.5" r="1.5"/>
- <polyline points="21 15 16 10 5 21"/>
- </svg>
- </button>
- </div>
- <div class="explanation-section">
- <div class="explanation-header">
- <span class="explanation-label">评价标准</span>
- </div>
- <textarea class="explanation-textarea" placeholder="设置评价标准,如:必须包括/避免..."></textarea>
- </div>
- </div>
- `;
- }
- // 生成投票内容
- function generateVoteContent() {
- return `
- <div class="question-item">
- <div class="question-header">
- <span class="question-number">投票主题</span>
- </div>
- <div class="input-with-image">
- <textarea class="question-input" placeholder="输入投票主题...">你最喜欢哪种状态的水?</textarea>
- <button class="upload-image-btn" onclick="uploadImage(this)" title="上传图片">
- <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
- <rect x="3" y="3" width="18" height="18" rx="2" ry="2"/>
- <circle cx="8.5" cy="8.5" r="1.5"/>
- <polyline points="21 15 16 10 5 21"/>
- </svg>
- </button>
- </div>
- <div class="options-list">
- <div class="option-item">
- <input type="text" class="option-input" placeholder="选项1" value="固态(冰)">
- </div>
- <div class="option-item">
- <input type="text" class="option-input" placeholder="选项2" value="液态(水)">
- </div>
- <div class="option-item">
- <input type="text" class="option-input" placeholder="选项3" value="气态(水蒸气)">
- </div>
- <button class="add-option-btn-with-text" onclick="addOption(this)" title="添加选项">
- <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
- <line x1="12" y1="5" x2="12" y2="19"/>
- <line x1="5" y1="12" x2="19" y2="12"/>
- </svg>
- <span>选项</span>
- </button>
- </div>
- </div>
- `;
- }
- // 生成拍照内容
- function generatePhotoContent() {
- return `
- <div class="question-item">
- <div class="question-header">
- <span class="question-number">拍照指引</span>
- </div>
- <textarea class="question-input" placeholder="输入拍照指引内容...">请拍摄家中水的不同状态</textarea>
- </div>
- `;
- }
- // 生成填空内容
- function generateFillBlankContent() {
- return `
- <div class="question-item">
- <div class="question-header">
- <span class="question-number">题目 1</span>
- </div>
- <div class="question-input-wrapper">
- <textarea class="question-input" placeholder="输入题目内容,使用___表示填空...">水在___°C会结冰,在___°C会沸腾。</textarea>
- <button class="add-blank-btn" onclick="insertBlank(this)" title="插入填空符">
- <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
- <line x1="5" y1="12" x2="19" y2="12"/>
- <line x1="5" y1="12" x2="19" y2="12" stroke-dasharray="2,2"/>
- </svg>
- <span>填空符</span>
- </button>
- </div>
- <div class="explanation-section">
- <div class="explanation-header">
- <span class="explanation-label">参考答案</span>
- </div>
- <textarea class="explanation-textarea" placeholder="输入参考答案...">0, 100</textarea>
- </div>
- </div>
- <button class="add-question-btn-with-text" onclick="addQuestion()" title="添加题目">
- <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
- <line x1="12" y1="5" x2="12" y2="19"/>
- <line x1="5" y1="12" x2="19" y2="12"/>
- </svg>
- <span>题目</span>
- </button>
- `;
- }
- // 生成排序内容
- function generateSortContent() {
- return `
- <div class="question-item">
- <div class="question-header">
- <span class="question-number">题目 1</span>
- </div>
- <div class="input-with-image">
- <textarea class="question-input" placeholder="输入题目内容...">将水的状态变化过程按顺序排列</textarea>
- <button class="upload-image-btn" onclick="uploadImage(this)" title="上传图片">
- <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
- <rect x="3" y="3" width="18" height="18" rx="2" ry="2"/>
- <circle cx="8.5" cy="8.5" r="1.5"/>
- <polyline points="21 15 16 10 5 21"/>
- </svg>
- </button>
- </div>
- <div class="options-list">
- <div class="sort-item-static">
- <div class="sort-number">1</div>
- <input type="text" class="option-input" placeholder="片段1" value="冰融化">
- <div class="option-actions">
- <button class="option-action-btn delete" onclick="deleteSortOption(this)" title="删除片段">
- <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
- <line x1="18" y1="6" x2="6" y2="18"/>
- <line x1="6" y1="6" x2="18" y2="18"/>
- </svg>
- </button>
- </div>
- </div>
- <div class="sort-item-static">
- <div class="sort-number">2</div>
- <input type="text" class="option-input" placeholder="片段2" value="水加热">
- <div class="option-actions">
- <button class="option-action-btn delete" onclick="deleteSortOption(this)" title="删除片段">
- <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
- <line x1="18" y1="6" x2="6" y2="18"/>
- <line x1="6" y1="6" x2="18" y2="18"/>
- </svg>
- </button>
- </div>
- </div>
- <div class="sort-item-static">
- <div class="sort-number">3</div>
- <input type="text" class="option-input" placeholder="片段3" value="水沸腾">
- <div class="option-actions">
- <button class="option-action-btn delete" onclick="deleteSortOption(this)" title="删除片段">
- <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
- <line x1="18" y1="6" x2="6" y2="18"/>
- <line x1="6" y1="6" x2="18" y2="18"/>
- </svg>
- </button>
- </div>
- </div>
- <button class="add-option-btn-with-text" onclick="addSortOption(this)" title="添加片段">
- <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
- <line x1="12" y1="5" x2="12" y2="19"/>
- <line x1="5" y1="12" x2="19" y2="12"/>
- </svg>
- <span>片段</span>
- </button>
- </div>
- <div class="explanation-section">
- <div class="explanation-header">
- <span class="explanation-label">正确排序</span>
- </div>
- <div class="sort-order-display" id="sortOrderDisplay">
- <div class="sort-order-item draggable" draggable="true" data-order="1">
- <div class="sort-drag-handle">
- <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
- <line x1="3" y1="12" x2="21" y2="12"/>
- <line x1="3" y1="6" x2="21" y2="6"/>
- <line x1="3" y1="18" x2="21" y2="18"/>
- </svg>
- </div>
- <span>1</span>
- </div>
- <div class="sort-arrow">→</div>
- <div class="sort-order-item draggable" draggable="true" data-order="2">
- <div class="sort-drag-handle">
- <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
- <line x1="3" y1="12" x2="21" y2="12"/>
- <line x1="3" y1="6" x2="21" y2="6"/>
- <line x1="3" y1="18" x2="21" y2="18"/>
- </svg>
- </div>
- <span>2</span>
- </div>
- <div class="sort-arrow">→</div>
- <div class="sort-order-item draggable" draggable="true" data-order="3">
- <div class="sort-drag-handle">
- <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
- <line x1="3" y1="12" x2="21" y2="12"/>
- <line x1="3" y1="6" x2="21" y2="6"/>
- <line x1="3" y1="18" x2="21" y2="18"/>
- </svg>
- </div>
- <span>3</span>
- </div>
- </div>
- </div>
- </div>
- <button class="add-question-btn-with-text" onclick="addQuestion()" title="添加题目">
- <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
- <line x1="12" y1="5" x2="12" y2="19"/>
- <line x1="5" y1="12" x2="19" y2="12"/>
- </svg>
- <span>题目</span>
- </button>
- `;
- }
- // 生成白板内容
- function generateWhiteboardContent() {
- return `
- <div class="question-item">
- <div class="question-header">
- <span class="question-number">白板主题</span>
- </div>
- <div class="input-with-image">
- <textarea class="question-input" placeholder="输入白板主题...">画出水的三态变化示意图</textarea>
- <button class="upload-image-btn" onclick="uploadImage(this)" title="上传图片">
- <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
- <rect x="3" y="3" width="18" height="18" rx="2" ry="2"/>
- <circle cx="8.5" cy="8.5" r="1.5"/>
- <polyline points="21 15 16 10 5 21"/>
- </svg>
- </button>
- </div>
- </div>
- `;
- }
- // 生成抽认卡内容
- function generateFlashcardContent() {
- return `
- <div class="question-item">
- <div class="flashcard-layout">
- <div class="flashcard-side">
- <div class="flashcard-side-label">正面</div>
- <div class="input-with-image">
- <textarea class="flashcard-content" placeholder="输入正面内容...">水的固态叫什么?</textarea>
- <button class="upload-image-btn" onclick="uploadImage(this)" title="上传图片">
- <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
- <rect x="3" y="3" width="18" height="18" rx="2" ry="2"/>
- <circle cx="8.5" cy="8.5" r="1.5"/>
- <polyline points="21 15 16 10 5 21"/>
- </svg>
- </button>
- </div>
- </div>
- <div class="flashcard-side">
- <div class="flashcard-side-label">背面</div>
- <div class="input-with-image">
- <textarea class="flashcard-content" placeholder="输入背面内容...">冰</textarea>
- <button class="upload-image-btn" onclick="uploadImage(this)" title="上传图片">
- <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
- <rect x="3" y="3" width="18" height="18" rx="2" ry="2"/>
- <circle cx="8.5" cy="8.5" r="1.5"/>
- <polyline points="21 15 16 10 5 21"/>
- </svg>
- </button>
- </div>
- </div>
- </div>
- </div>
- <button class="add-question-btn-with-text" onclick="addFlashcard()" title="添加卡片">
- <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
- <line x1="12" y1="5" x2="12" y2="19"/>
- <line x1="5" y1="12" x2="19" y2="12"/>
- </svg>
- <span>卡片</span>
- </button>
- `;
- }
- // 生成CocoPi内容
- function generateCocoPiContent() {
- return `
- <div class="question-item">
- <div class="question-header">
- <span class="question-number">编程任务</span>
- </div>
- <textarea class="question-input" placeholder="输入编程任务描述...">使用温度传感器监测水的温度变化</textarea>
- <div style="margin-top: 20px; padding: 40px; background: #f3f4f6; border-radius: 12px; text-align: center; color: #6b7280;">
- <svg width="64" height="64" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" style="margin: 0 auto 16px;">
- <polyline points="16 18 22 12 16 6"/>
- <polyline points="8 6 2 12 8 18"/>
- </svg>
- <div>CocoPi编程界面占位</div>
- <div style="font-size: 12px; margin-top: 8px;">点击加载CocoPi编程模块</div>
- </div>
- </div>
- `;
- }
- // 生成创作空间内容
- function generateWorkspaceContent() {
- return `
- <div class="question-item">
- <div class="question-header">
- <span class="question-number">创作任务</span>
- </div>
- <textarea class="question-input" placeholder="输入创作任务描述...">创建一个介绍水循环的智能体</textarea>
- <div style="margin-top: 20px; padding: 40px; background: #f3f4f6; border-radius: 12px; text-align: center; color: #6b7280;">
- <svg width="64" height="64" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" style="margin: 0 auto 16px;">
- <path d="M12 2L2 7l10 5 10-5-10-5z"/>
- <path d="M2 17l10 5 10-5"/>
- <path d="M2 12l10 5 10-5"/>
- </svg>
- <div>CocoFlow创作空间占位</div>
- <div style="font-size: 12px; margin-top: 8px;">点击进入CocoFlow创作界面</div>
- </div>
- </div>
- `;
- }
- // 返回工具列表
- function backToToolsList() {
- document.getElementById('toolsListView').classList.remove('hidden');
- document.getElementById('toolsConfigView').classList.add('hidden');
- // 显示顶部标题栏
- const panelHeader = document.querySelector('#toolsPanel .panel-header');
- if (panelHeader) {
- panelHeader.style.display = 'flex';
- }
- }
- // 恢复正常的slide显示
- function restoreNormalView() {
- const toolEditContainer = document.getElementById('toolEditContainer');
- const slideCanvas = document.getElementById('slideCanvas');
- if (toolEditContainer) {
- toolEditContainer.style.display = 'none';
- }
- if (slideCanvas) {
- slideCanvas.style.display = 'flex';
- }
- isToolMode = false;
- }
- // ========== 资源功能 ==========
- let audioSelectedFile = null;
- let documentSelectedFile = null;
- let collectionFiles = [];
- function openVideoSourceModal() {
- document.getElementById('videoSourceModal').classList.add('active');
- }
- function closeVideoSourceModal() {
- document.getElementById('videoSourceModal').classList.remove('active');
- }
- function chooseLocalVideo() {
- const input = document.createElement('input');
- input.type = 'file';
- input.accept = 'video/*';
- input.onchange = (e) => {
- const file = e.target.files[0];
- if (file) {
- closeVideoSourceModal();
- addResourcePage('视频', file.name || '视频');
- }
- };
- input.click();
- }
- function chooseBilibiliVideo() {
- closeVideoSourceModal();
- alert('已选择来自 Bilibili 的视频占位');
- addResourcePage('视频', 'Bilibili 视频');
- }
- function openAudioUploadModal() {
- document.getElementById('audioFileInput').value = '';
- document.getElementById('audioFileNameDisplay').style.display = 'none';
- document.getElementById('confirmAudioUploadBtn').disabled = true;
- audioSelectedFile = null;
- document.getElementById('audioUploadModal').classList.add('active');
- }
- function handleAudioFile(event) {
- audioSelectedFile = event.target.files[0] || null;
- const display = document.getElementById('audioFileNameDisplay');
- if (audioSelectedFile) {
- display.textContent = audioSelectedFile.name;
- display.style.display = 'block';
- } else {
- display.textContent = '';
- display.style.display = 'none';
- }
- document.getElementById('confirmAudioUploadBtn').disabled = !audioSelectedFile;
- }
- function confirmAudioUpload() {
- if (!audioSelectedFile) return;
- addResourcePage('音频', audioSelectedFile.name || '音频');
- closeAudioUploadModal();
- }
- function closeAudioUploadModal() {
- audioSelectedFile = null;
- document.getElementById('audioUploadModal').classList.remove('active');
- document.getElementById('confirmAudioUploadBtn').disabled = true;
- }
- function openDocumentUploadModal() {
- document.getElementById('documentFileInput').value = '';
- document.getElementById('documentFileNameDisplay').style.display = 'none';
- document.getElementById('confirmDocumentUploadBtn').disabled = true;
- documentSelectedFile = null;
- document.getElementById('documentUploadModal').classList.add('active');
- }
- function handleDocumentFile(event) {
- documentSelectedFile = event.target.files[0] || null;
- const display = document.getElementById('documentFileNameDisplay');
- if (documentSelectedFile) {
- display.textContent = documentSelectedFile.name;
- display.style.display = 'block';
- } else {
- display.textContent = '';
- display.style.display = 'none';
- }
- document.getElementById('confirmDocumentUploadBtn').disabled = !documentSelectedFile;
- }
- function confirmDocumentUpload() {
- if (!documentSelectedFile) return;
- addResourcePage('文档', documentSelectedFile.name || '文档');
- closeDocumentUploadModal();
- }
- function closeDocumentUploadModal() {
- documentSelectedFile = null;
- document.getElementById('documentUploadModal').classList.remove('active');
- document.getElementById('confirmDocumentUploadBtn').disabled = true;
- }
- function openCollectionModal() {
- collectionFiles = [];
- renderCollectionList();
- document.getElementById('collectionModal').classList.add('active');
- }
- function closeCollectionModal() {
- collectionFiles = [];
- renderCollectionList();
- document.getElementById('collectionModal').classList.remove('active');
- }
- function handleCollectionFiles(event) {
- const newFiles = Array.from(event.target.files || []);
- newFiles.forEach(file => {
- if (collectionFiles.length < 5) {
- collectionFiles.push(file);
- }
- });
- event.target.value = '';
- renderCollectionList();
- }
- function renderCollectionList() {
- const list = document.getElementById('collectionFileList');
- if (!list) return;
- list.innerHTML = collectionFiles.map((file, index) => `
- <div class="resource-file-item">
- <div class="resource-file-name">
- <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
- <path d="M14 2H6a2 2 0 00-2 2v16a2 2 0 002 2h12a2 2 0 002-2V8z"/>
- <polyline points="14 2 14 8 20 8"/>
- </svg>
- <span title="${file.name}">${file.name}</span>
- </div>
- <button class="resource-remove-btn" onclick="removeCollectionFile(${index})">
- <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
- <line x1="18" y1="6" x2="6" y2="18"/>
- <line x1="6" y1="6" x2="18" y2="18"/>
- </svg>
- </button>
- </div>
- `).join('');
- document.getElementById('confirmCollectionBtn').disabled = collectionFiles.length === 0;
- }
- function removeCollectionFile(index) {
- collectionFiles.splice(index, 1);
- renderCollectionList();
- }
- function confirmCollection() {
- if (collectionFiles.length === 0) return;
- const filesSnapshot = [...collectionFiles];
- addResourcePage('资源集合', `资源集合 (${collectionFiles.length})`, filesSnapshot);
- closeCollectionModal();
- }
- function addResourcePage(resourceType, label, files = []) {
- if (!isEditMode) {
- switchToEditMode();
- } else {
- document.getElementById('elementToolbar').classList.add('visible');
- document.getElementById('bottomOutline').classList.add('visible');
- const placeholder = document.getElementById('slidePlaceholder');
- if (placeholder) placeholder.style.display = 'none';
- }
- const outlineTrack = document.getElementById('outlineTrack');
- const pageIndex = outlineTrack.querySelectorAll('.outline-item-wrapper').length + 1;
- const wrapper = document.createElement('div');
- wrapper.className = 'outline-item-wrapper';
- const outlineItem = document.createElement('div');
- outlineItem.className = 'outline-item active';
- outlineItem.draggable = true;
- outlineItem.dataset.pageIndex = pageIndex;
- outlineItem.onclick = (e) => selectPage(pageIndex, e);
- outlineItem.innerHTML = `
- <span class="page-number">${pageIndex}</span>
- <span>${label}</span>
- <div class="page-menu" onclick="event.stopPropagation(); togglePageMenu(event, ${pageIndex})">
- <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
- <circle cx="12" cy="12" r="1"/>
- <circle cx="12" cy="5" r="1"/>
- <circle cx="12" cy="19" r="1"/>
- </svg>
- <div class="page-menu-dropdown">
- <div class="page-menu-item" onclick="copyPage(${pageIndex})">
- <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
- <rect x="9" y="9" width="13" height="13" rx="2" ry="2"/>
- <path d="M5 15H4a2 2 0 01-2-2V4a2 2 0 012-2h9a2 2 0 012 2v1"/>
- </svg>
- 复制
- </div>
- <div class="page-menu-item" onclick="addBlankPage(${pageIndex})">
- <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
- <line x1="12" y1="5" x2="12" y2="19"/>
- <line x1="5" y1="12" x2="19" y2="12"/>
- </svg>
- 新增
- </div>
- <div class="page-menu-item danger" onclick="deletePage(${pageIndex})">
- <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
- <polyline points="3 6 5 6 21 6"/>
- <path d="M19 6v14a2 2 0 01-2 2H7a2 2 0 01-2-2V6m3 0V4a2 2 0 012-2h4a2 2 0 012 2v2"/>
- </svg>
- 删除
- </div>
- </div>
- </div>
- `;
- outlineItem.addEventListener('dragstart', handleDragStart);
- outlineItem.addEventListener('dragend', handleDragEnd);
- outlineItem.addEventListener('dragover', handleDragOver);
- outlineItem.addEventListener('drop', handleDrop);
- outlineItem.addEventListener('dragleave', handleDragLeave);
- document.querySelectorAll('.outline-item').forEach(item => item.classList.remove('active'));
- const addBtn = document.createElement('button');
- addBtn.className = 'add-page-between';
- addBtn.textContent = '+';
- addBtn.onclick = () => addPageBetween(pageIndex);
- wrapper.appendChild(outlineItem);
- wrapper.appendChild(addBtn);
- outlineTrack.appendChild(wrapper);
- syncRightOutlineWithBottom();
- renderResourceCanvas(resourceType, label, files);
- alert('已添加资源页面: ' + label);
- }
- function renderResourceCanvas(resourceType, label, files = []) {
- const canvas = document.getElementById('slideCanvas');
- const placeholder = document.getElementById('slidePlaceholder');
- if (placeholder) {
- placeholder.style.display = 'none';
- }
- const elementToolbar = document.getElementById('elementToolbar');
- if (elementToolbar) {
- elementToolbar.classList.remove('visible');
- elementToolbar.style.display = 'none';
- }
- document.getElementById('bottomOutline').classList.add('visible');
- const icons = {
- '视频': '▶️',
- '音频': '🎵',
- '文档': '📄',
- '资源集合': '📚'
- };
- const descMap = {
- '视频': '全屏视频播放器占位',
- '音频': '音频播放条占位',
- '文档': '文档阅读器占位',
- '资源集合': '顶部标签可切换的资源集合占位'
- };
- const showTabs = resourceType === '资源集合';
- const tabItems = showTabs
- ? (files.length > 0 ? files.slice(0, 5).map((f, idx) => f.name || `资源${idx + 1}`) : ['资源1', '资源2', '资源3'])
- : [];
- const tabsHtml = showTabs ? `<div class="resource-tabs">${tabItems.map((name, idx) => `<div class="resource-tab ${idx === 0 ? 'active' : ''}">${name}</div>`).join('')}</div>` : '';
- canvas.innerHTML = `
- <div style="width: 100%; height: 100%; display: flex; flex-direction: column;">
- ${tabsHtml}
- <div style="flex: 1; display: flex; align-items: center; justify-content: center;">
- <div style="width: 80%; background: #fafbfc; border: 2px dashed #e5e7eb; border-radius: 16px; padding: 32px; text-align: center;">
- <div style="font-size: 42px; margin-bottom: 12px;">${icons[resourceType] || '📦'}</div>
- <div style="font-size: 18px; font-weight: 700; color: #111827; margin-bottom: 6px;">${label}</div>
- <div style="font-size: 13px; color: #6b7280;">${descMap[resourceType] || '资源内容占位'}</div>
- </div>
- </div>
- </div>
- `;
- }
- // 切换开关
- function toggleSwitch(switchType) {
- event.stopPropagation();
- const switchEl = document.getElementById(switchType + 'Switch');
- if (switchEl) {
- // 如果是关闭"学生查看结果",需要同时关闭"学生点赞"
- if (switchType === 'viewWork' && switchEl.classList.contains('active')) {
- const votingSwitch = document.getElementById('votingSwitch');
- if (votingSwitch && votingSwitch.classList.contains('active')) {
- votingSwitch.classList.remove('active');
- }
- }
-
- // 如果是打开"学生点赞",需要同时打开"学生查看结果"
- if (switchType === 'voting' && !switchEl.classList.contains('active')) {
- const viewWorkSwitch = document.getElementById('viewWorkSwitch');
- if (viewWorkSwitch && !viewWorkSwitch.classList.contains('active')) {
- viewWorkSwitch.classList.add('active');
- }
- }
-
- switchEl.classList.toggle('active');
- }
- }
- // 切换模式(多选)
- function toggleMode(element, mode) {
- element.classList.toggle('active');
- }
- // 切换模式(单选)
- function toggleModeRadio(element, mode) {
- const parent = element.parentElement;
- parent.querySelectorAll('.mode-item').forEach(item => {
- item.classList.remove('active');
- });
- element.classList.add('active');
- }
- // 插入填空符
- function insertBlank(button) {
- const wrapper = button.closest('.question-input-wrapper');
- const textarea = wrapper.querySelector('.question-input');
- const cursorPos = textarea.selectionStart;
- const textBefore = textarea.value.substring(0, cursorPos);
- const textAfter = textarea.value.substring(cursorPos);
- textarea.value = textBefore + '___' + textAfter;
- textarea.focus();
- textarea.setSelectionRange(cursorPos + 3, cursorPos + 3);
- }
- // 添加排序选项
- function addSortOption(button) {
- const optionsList = button.parentElement;
- const currentItems = optionsList.querySelectorAll('.sort-item-static');
- const nextNumber = currentItems.length + 1;
-
- const sortItem = document.createElement('div');
- sortItem.className = 'sort-item-static';
- sortItem.innerHTML = `
- <div class="sort-number">${nextNumber}</div>
- <input type="text" class="option-input" placeholder="片段${nextNumber}">
- <div class="option-actions">
- <button class="option-action-btn delete" onclick="deleteSortOption(this)" title="删除片段">
- <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
- <line x1="18" y1="6" x2="6" y2="18"/>
- <line x1="6" y1="6" x2="18" y2="18"/>
- </svg>
- </button>
- </div>
- `;
- optionsList.insertBefore(sortItem, button);
-
- // 更新正确排序显示
- updateSortOrderDisplay();
- }
- // 删除排序选项
- function deleteSortOption(button) {
- const sortItem = button.closest('.sort-item-static');
- const optionsList = sortItem.parentElement;
- const allItems = optionsList.querySelectorAll('.sort-item-static');
-
- // 至少保留2个选项
- if (allItems.length <= 2) {
- alert('至少需要保留两个片段');
- return;
- }
-
- sortItem.remove();
-
- // 重新编号
- const remainingItems = optionsList.querySelectorAll('.sort-item-static');
- remainingItems.forEach((item, index) => {
- const numberEl = item.querySelector('.sort-number');
- const inputEl = item.querySelector('.option-input');
- numberEl.textContent = index + 1;
- if (inputEl.placeholder) {
- inputEl.placeholder = `片段${index + 1}`;
- }
- });
-
- // 更新正确排序显示
- updateSortOrderDisplay();
- }
- // 更新排序顺序显示
- function updateSortOrderDisplay() {
- const sortItems = document.querySelectorAll('.sort-item-static');
- const display = document.querySelector('.sort-order-display');
- if (!display) return;
-
- let html = '';
- sortItems.forEach((item, index) => {
- const number = item.querySelector('.sort-number').textContent;
- html += `
- <div class="sort-order-item draggable" draggable="true" data-order="${number}">
- <div class="sort-drag-handle">
- <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
- <line x1="3" y1="12" x2="21" y2="12"/>
- <line x1="3" y1="6" x2="21" y2="6"/>
- <line x1="3" y1="18" x2="21" y2="18"/>
- </svg>
- </div>
- <span>${number}</span>
- </div>
- `;
- if (index < sortItems.length - 1) {
- html += '<div class="sort-arrow">→</div>';
- }
- });
- display.innerHTML = html;
-
- // 重新初始化拖动事件
- initSortDragAndDrop();
- }
- // 初始化排序拖动功能
- function initSortDragAndDrop() {
- const draggables = document.querySelectorAll('.sort-order-item.draggable');
- const container = document.querySelector('.sort-order-display');
-
- if (!container) return;
-
- let draggedElement = null;
-
- draggables.forEach(item => {
- item.addEventListener('dragstart', function(e) {
- draggedElement = this;
- this.classList.add('dragging');
- e.dataTransfer.effectAllowed = 'move';
- });
-
- item.addEventListener('dragend', function(e) {
- this.classList.remove('dragging');
- draggables.forEach(el => el.classList.remove('drag-over'));
- });
-
- item.addEventListener('dragover', function(e) {
- e.preventDefault();
- e.dataTransfer.dropEffect = 'move';
-
- if (this !== draggedElement) {
- this.classList.add('drag-over');
- }
- });
-
- item.addEventListener('dragleave', function(e) {
- this.classList.remove('drag-over');
- });
-
- item.addEventListener('drop', function(e) {
- e.preventDefault();
- this.classList.remove('drag-over');
-
- if (this !== draggedElement) {
- // 交换元素
- const allItems = Array.from(container.querySelectorAll('.sort-order-item.draggable'));
- const draggedIndex = allItems.indexOf(draggedElement);
- const targetIndex = allItems.indexOf(this);
-
- if (draggedIndex < targetIndex) {
- this.parentNode.insertBefore(draggedElement, this.nextSibling);
- } else {
- this.parentNode.insertBefore(draggedElement, this);
- }
-
- // 重建箭头
- rebuildSortArrows();
- }
- });
- });
- }
- // 重建排序箭头
- function rebuildSortArrows() {
- const container = document.querySelector('.sort-order-display');
- if (!container) return;
-
- const items = Array.from(container.querySelectorAll('.sort-order-item.draggable'));
- const arrows = Array.from(container.querySelectorAll('.sort-arrow'));
-
- // 移除所有箭头
- arrows.forEach(arrow => arrow.remove());
-
- // 重新插入箭头
- items.forEach((item, index) => {
- if (index < items.length - 1) {
- const arrow = document.createElement('div');
- arrow.className = 'sort-arrow';
- arrow.textContent = '→';
- item.parentNode.insertBefore(arrow, item.nextSibling);
- }
- });
- }
- // 页面加载后初始化拖动
- document.addEventListener('DOMContentLoaded', function() {
- // 延迟初始化,等待工具内容加载
- setTimeout(initSortDragAndDrop, 500);
- });
- // 图片上传函数
- function uploadImage(button) {
- const input = document.createElement('input');
- input.type = 'file';
- input.accept = 'image/*';
- input.onchange = (e) => {
- const file = e.target.files[0];
- if (file) {
- // 创建图片预览
- const reader = new FileReader();
- reader.onload = function(event) {
- const wrapper = button.closest('.input-with-image');
- const textarea = wrapper.querySelector('textarea, .option-input');
-
- // 在textarea下方插入图片预览
- let preview = wrapper.querySelector('.image-preview');
- if (!preview) {
- preview = document.createElement('div');
- preview.className = 'image-preview';
- textarea.parentNode.insertBefore(preview, button);
- }
-
- preview.innerHTML = `
- <img src="${event.target.result}" alt="预览图片">
- <button class="remove-image-btn" onclick="removeImage(this)" title="删除图片">
- <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
- <line x1="18" y1="6" x2="6" y2="18"/>
- <line x1="6" y1="6" x2="18" y2="18"/>
- </svg>
- </button>
- `;
- };
- reader.readAsDataURL(file);
- }
- };
- input.click();
- }
- // 删除图片
- function removeImage(button) {
- const preview = button.closest('.image-preview');
- if (preview) {
- preview.remove();
- }
- }
- // 切换工具类型下拉菜单
- function toggleToolTypeDropdown() {
- event.stopPropagation();
- document.getElementById('toolTypeSelector').classList.toggle('active');
- }
- // 切换工具类型
- function switchToolType(toolType) {
- currentTool = toolType;
- showToolConfig(toolType);
- showToolEditArea(toolType);
- document.getElementById('toolTypeSelector').classList.remove('active');
- }
- // 切换题目类型(单选/多选)
- function toggleQuestionType(toggle) {
- toggle.classList.toggle('active');
- }
- // 切换选项选中状态
- function toggleOption(checkbox) {
- checkbox.classList.toggle('checked');
- }
- // 添加选项
- function addOption(button) {
- const optionsList = button.parentElement;
- const optionItem = document.createElement('div');
- optionItem.className = 'option-item';
- optionItem.innerHTML = `
- <div class="option-drag-handle" title="拖动排序">
- <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
- <line x1="3" y1="12" x2="21" y2="12"/>
- <line x1="3" y1="6" x2="21" y2="6"/>
- <line x1="3" y1="18" x2="21" y2="18"/>
- </svg>
- </div>
- <div class="option-checkbox" onclick="toggleOption(this)">
- <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
- <polyline points="20 6 9 17 4 12"/>
- </svg>
- </div>
- <input type="text" class="option-input" placeholder="新选项">
- <div class="option-actions">
- <button class="option-action-btn" onclick="addOptionAfter(this)" title="在下方添加选项">
- <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
- <line x1="12" y1="5" x2="12" y2="19"/>
- <line x1="5" y1="12" x2="19" y2="12"/>
- </svg>
- </button>
- <button class="option-action-btn delete" onclick="deleteOption(this)" title="删除选项">
- <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
- <line x1="18" y1="6" x2="6" y2="18"/>
- <line x1="6" y1="6" x2="18" y2="18"/>
- </svg>
- </button>
- </div>
- `;
- optionsList.insertBefore(optionItem, button);
- }
- // 在指定选项后添加新选项
- function addOptionAfter(button) {
- const currentOption = button.closest('.option-item');
- const optionsList = currentOption.parentElement;
- const newOption = document.createElement('div');
- newOption.className = 'option-item';
- newOption.innerHTML = `
- <div class="option-drag-handle" title="拖动排序">
- <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
- <line x1="3" y1="12" x2="21" y2="12"/>
- <line x1="3" y1="6" x2="21" y2="6"/>
- <line x1="3" y1="18" x2="21" y2="18"/>
- </svg>
- </div>
- <div class="option-checkbox" onclick="toggleOption(this)">
- <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
- <polyline points="20 6 9 17 4 12"/>
- </svg>
- </div>
- <input type="text" class="option-input" placeholder="新选项">
- <div class="option-actions">
- <button class="option-action-btn" onclick="addOptionAfter(this)" title="在下方添加选项">
- <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
- <line x1="12" y1="5" x2="12" y2="19"/>
- <line x1="5" y1="12" x2="19" y2="12"/>
- </svg>
- </button>
- <button class="option-action-btn delete" onclick="deleteOption(this)" title="删除选项">
- <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
- <line x1="18" y1="6" x2="6" y2="18"/>
- <line x1="6" y1="6" x2="18" y2="18"/>
- </svg>
- </button>
- </div>
- `;
- currentOption.after(newOption);
- newOption.querySelector('.option-input').focus();
- }
- // 删除选项
- function deleteOption(button) {
- const optionItem = button.closest('.option-item');
- const optionsList = optionItem.parentElement;
- const allOptions = optionsList.querySelectorAll('.option-item');
-
- // 至少保留2个选项
- if (allOptions.length <= 2) {
- alert('至少需要保留两个选项');
- return;
- }
-
- optionItem.remove();
- }
- // 复制题目
- function copyQuestion(button) {
- const questionItem = button.closest('.question-item');
- const clone = questionItem.cloneNode(true);
- const questionNumber = clone.querySelector('.question-number');
- const currentNum = parseInt(questionNumber.textContent.match(/\d+/)[0]);
- questionNumber.textContent = `题目 ${currentNum + 1}`;
- questionItem.after(clone);
- alert('题目已复制');
- }
- // 删除题目
- function deleteQuestion(button) {
- const questionItem = button.closest('.question-item');
- const allQuestions = document.querySelectorAll('.question-item');
-
- // 至少保留1个题目
- if (allQuestions.length <= 1) {
- alert('至少需要保留一个题目');
- return;
- }
-
- if (confirm('确定要删除这道题目吗?')) {
- questionItem.remove();
- }
- }
- // 添加题目
- function addQuestion() {
- alert('添加新题目');
- }
- // 添加抽认卡
- function addFlashcard() {
- alert('添加新抽认卡');
- }
- // AI生成解释
- function generateExplanation() {
- alert('AI正在生成解释...');
- }
- // 点击页面其他地方关闭下拉菜单
- document.addEventListener('click', function() {
- const selector = document.getElementById('toolTypeSelector');
- if (selector) {
- selector.classList.remove('active');
- }
- });
- // 显示创建弹窗
- function showCreateModal() {
- document.getElementById('createModal').classList.add('active');
- }
- // 隐藏创建弹窗
- function hideCreateModal() {
- document.getElementById('createModal').classList.remove('active');
- }
- // 取消AI卡片高亮
- function unsetAiFeatured() {
- const aiCard = document.getElementById('aiCreateCard');
- if (aiCard) aiCard.classList.remove('featured');
- }
- function createBlankCourse() {
- hideCreateModal();
- addPageFromTemplate('content');
- }
- // 点击遮罩关闭弹窗
- document.getElementById('createModal').addEventListener('click', function(e) {
- if (e.target === this) {
- hideCreateModal();
- }
- });
- // ========== PPT上传与解析(模拟) ==========
- let currentPptFile = null;
- let pptParseTimer = null;
- // ========== 右侧大纲(分组 + 拖动) ==========
- let outlineData = { groups: [], ungrouped: [] };
- let draggingItem = null;
- function initRightOutline() {
- syncRightOutlineWithBottom();
- }
- function snapshotBottomOutline() {
- const track = document.getElementById('outlineTrack');
- if (!track) return [];
- const items = Array.from(track.querySelectorAll('.outline-item-wrapper'));
- return items.map((item, idx) => {
- const titleSpan = item.querySelector('.outline-item span:nth-child(2)');
- const title = titleSpan ? titleSpan.textContent.trim() : `页面 ${idx + 1}`;
- const pageNumber = item.querySelector('.page-number');
- const id = pageNumber ? pageNumber.textContent.trim() : `${idx + 1}`;
- return { id, title, type: '页面' };
- });
- }
- function syncRightOutlineWithBottom() {
- const bottomPages = snapshotBottomOutline();
- const orderMap = new Map(bottomPages.map((p, idx) => [p.id, idx]));
- const existsInBottom = (id) => orderMap.has(id);
- // 移除已被删除的页面
- outlineData.groups.forEach(group => {
- group.items = group.items.filter(item => existsInBottom(item.id));
- });
- outlineData.ungrouped = outlineData.ungrouped.filter(item => existsInBottom(item.id));
- // 添加新增页面到未分组
- bottomPages.forEach(page => {
- const inGroup = outlineData.groups.some(g => g.items.some(it => it.id === page.id));
- const inUngrouped = outlineData.ungrouped.some(it => it.id === page.id);
- if (!inGroup && !inUngrouped) {
- outlineData.ungrouped.push(page);
- }
- });
- // 未分组跟随底部顺序
- outlineData.ungrouped.sort((a, b) => (orderMap.get(a.id) ?? 0) - (orderMap.get(b.id) ?? 0));
- renderRightOutline();
- }
- function refreshRightOutline() {
- syncRightOutlineWithBottom();
- }
- function addOutlineGroup() {
- const group = {
- id: `group_${Date.now()}`,
- name: '新建分组',
- collapsed: false,
- items: []
- };
- outlineData.groups.push(group);
- renderRightOutline();
- }
- function deleteGroup(groupId) {
- const group = outlineData.groups.find(g => g.id === groupId);
- if (!group) return;
- // 将子项平铺到未分组,保持顺序
- outlineData.ungrouped = [...outlineData.ungrouped, ...group.items];
- outlineData.groups = outlineData.groups.filter(g => g.id !== groupId);
- renderRightOutline();
- }
- function confirmDeleteGroup(groupId) {
- const group = outlineData.groups.find(g => g.id === groupId);
- if (!group) return;
- const ok = confirm('确定删除该分组?其中的页面将移动到未分组');
- if (ok) {
- deleteGroup(groupId);
- }
- }
- function toggleGroupCollapse(groupId) {
- const group = outlineData.groups.find(g => g.id === groupId);
- if (group) {
- group.collapsed = !group.collapsed;
- renderRightOutline();
- }
- }
- function renameGroup(groupId, titleSpan) {
- const group = outlineData.groups.find(g => g.id === groupId);
- if (!group || !titleSpan || titleSpan.dataset.editing === '1') return;
- const input = document.createElement('input');
- input.className = 'outline-group-input';
- input.value = group.name;
- const width = Math.min(220, Math.max(120, titleSpan.offsetWidth + 20));
- input.style.width = width + 'px';
- titleSpan.dataset.editing = '1';
- titleSpan.replaceWith(input);
- input.focus();
- input.select();
- let finished = false;
- const finish = (commit) => {
- if (finished) return;
- finished = true;
- if (commit) {
- const name = input.value.trim();
- if (name) group.name = name;
- }
- const newSpan = document.createElement('span');
- newSpan.className = 'outline-group-title';
- newSpan.textContent = group.name;
- newSpan.onclick = (e) => { e.stopPropagation(); renameGroup(groupId, newSpan); };
- input.replaceWith(newSpan);
- };
- input.addEventListener('blur', () => finish(true));
- input.addEventListener('keydown', (e) => {
- if (e.key === 'Enter') {
- e.preventDefault();
- input.blur();
- } else if (e.key === 'Escape') {
- e.preventDefault();
- finish(false);
- }
- });
- }
- function renderRightOutline() {
- const container = document.getElementById('rightOutlineList');
- if (!container) return;
- container.innerHTML = '';
- const hasContent = outlineData.groups.length > 0 || outlineData.ungrouped.length > 0;
- if (!hasContent) {
- container.innerHTML = '<div class="outline-empty">暂无页面,创建后将出现在此</div>';
- return;
- }
- // 先渲染分组
- outlineData.groups.forEach(group => {
- const groupEl = document.createElement('div');
- groupEl.className = 'outline-group';
- groupEl.dataset.id = group.id;
- const header = document.createElement('div');
- header.className = 'outline-group-header';
- header.draggable = false;
- const left = document.createElement('div');
- left.className = 'outline-group-left';
- const arrow = document.createElement('span');
- arrow.innerHTML = `
- <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
- <polyline points="6 9 12 15 18 9" style="transform: ${group.collapsed ? 'rotate(-90deg)' : 'none'}; transform-origin: 12px 12px;"/>
- </svg>`;
- arrow.style.cursor = 'pointer';
- arrow.onclick = (e) => { e.stopPropagation(); toggleGroupCollapse(group.id); };
- const titleSpan = document.createElement('span');
- titleSpan.className = 'outline-group-title';
- titleSpan.textContent = group.name;
- titleSpan.onclick = (e) => { e.stopPropagation(); renameGroup(group.id, titleSpan); };
- left.appendChild(arrow);
- left.appendChild(titleSpan);
- header.appendChild(left);
- const dragHandle = document.createElement('div');
- dragHandle.className = 'outline-drag-handle';
- dragHandle.innerHTML = `
- <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
- <circle cx="5" cy="8" r="1"/>
- <circle cx="5" cy="16" r="1"/>
- <circle cx="12" cy="8" r="1"/>
- <circle cx="12" cy="16" r="1"/>
- <circle cx="19" cy="8" r="1"/>
- <circle cx="19" cy="16" r="1"/>
- </svg>`;
- dragHandle.draggable = true;
- dragHandle.addEventListener('dragstart', (e) => onGroupDragStart(e, group.id));
- dragHandle.addEventListener('dragover', onDragOver);
- dragHandle.addEventListener('drop', (e) => onDrop(e, { targetGroup: group.id }));
- header.appendChild(dragHandle);
- const deleteBtn = document.createElement('div');
- deleteBtn.className = 'outline-delete-btn';
- deleteBtn.innerHTML = `
- <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
- <polyline points="3 6 5 6 21 6"/>
- <path d="M19 6v14a2 2 0 01-2 2H7a2 2 0 01-2-2V6m3 0V4a2 2 0 012-2h4a2 2 0 012 2v2"/>
- </svg>`;
- deleteBtn.onclick = (e) => { e.stopPropagation(); confirmDeleteGroup(group.id); };
- header.appendChild(deleteBtn);
- groupEl.appendChild(header);
- const dropAreaTarget = { targetGroup: group.id };
- header.addEventListener('dragover', onDragOver);
- header.addEventListener('drop', (e) => onDrop(e, dropAreaTarget));
- if (!group.collapsed) {
- const itemsBox = document.createElement('div');
- itemsBox.className = 'outline-items';
- itemsBox.addEventListener('dragover', onDragOver);
- itemsBox.addEventListener('drop', (e) => onDrop(e, dropAreaTarget));
- if (group.items.length === 0) {
- const empty = document.createElement('div');
- empty.className = 'outline-empty';
- empty.textContent = '拖动页面到此';
- itemsBox.appendChild(empty);
- } else {
- group.items.forEach((item, idx) => {
- itemsBox.appendChild(createOutlineItem(item, idx, group.id));
- });
- }
- groupEl.appendChild(itemsBox);
- }
- container.appendChild(groupEl);
- });
- // 渲染未分组
- outlineData.ungrouped.forEach((item, idx) => {
- container.appendChild(createOutlineItem(item, idx, null));
- });
- }
- function createOutlineItem(item, idx, groupId) {
- const el = document.createElement('div');
- el.className = 'outline-item';
- el.draggable = true;
- el.dataset.id = item.id;
- el.dataset.group = groupId || '';
- el.addEventListener('dragstart', (e) => onItemDragStart(e, item, groupId));
- el.addEventListener('dragover', onDragOver);
- el.addEventListener('drop', (e) => onDrop(e, { targetItem: item, groupId }));
- el.onclick = () => selectPageById(item.id);
- el.innerHTML = `
- <div class="outline-item-left">
- <span class="outline-index">${idx + 1}</span>
- <span class="outline-title" title="${item.title}">${item.title}</span>
- <span class="outline-type">${item.type || '页面'}</span>
- </div>
- <span class="outline-handle">
- <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
- <circle cx="5" cy="12" r="1"/>
- <circle cx="12" cy="12" r="1"/>
- <circle cx="19" cy="12" r="1"/>
- </svg>
- </span>
- `;
- return el;
- }
- function onItemDragStart(e, item, groupId) {
- draggingItem = { type: 'item', item, groupId };
- e.dataTransfer.effectAllowed = 'move';
- }
- function onGroupDragStart(e, groupId) {
- draggingItem = { type: 'group', groupId };
- e.dataTransfer.effectAllowed = 'move';
- }
- function onDragOver(e) {
- e.preventDefault();
- e.dataTransfer.dropEffect = 'move';
- }
- function onDrop(e, target) {
- e.preventDefault();
- if (!draggingItem) return;
- if (draggingItem.type === 'group' && target.targetGroup) {
- reorderGroups(draggingItem.groupId, target.targetGroup);
- } else if (draggingItem.type === 'item') {
- moveItem(draggingItem.item, draggingItem.groupId, target);
- }
- draggingItem = null;
- renderRightOutline();
- syncBottomOutlineOrder();
- }
- function reorderGroups(sourceId, targetId) {
- if (sourceId === targetId) return;
- const list = outlineData.groups;
- const fromIdx = list.findIndex(g => g.id === sourceId);
- const toIdx = list.findIndex(g => g.id === targetId);
- if (fromIdx === -1 || toIdx === -1) return;
- const [g] = list.splice(fromIdx, 1);
- list.splice(toIdx, 0, g);
- }
- function moveItem(item, fromGroupId, target) {
- removeItemFromData(item.id);
- if (target.targetItem) {
- const targetGroup = target.groupId || null;
- insertItemBefore(item, target.targetItem.id, targetGroup);
- } else if (target.targetGroup) {
- const group = outlineData.groups.find(g => g.id === target.targetGroup);
- if (group) group.items.push(item);
- } else {
- outlineData.ungrouped.push(item);
- }
- }
- function removeItemFromData(itemId) {
- outlineData.groups.forEach(g => {
- g.items = g.items.filter(it => it.id !== itemId);
- });
- outlineData.ungrouped = outlineData.ungrouped.filter(it => it.id !== itemId);
- }
- function insertItemBefore(item, targetId, groupId) {
- if (groupId) {
- const group = outlineData.groups.find(g => g.id === groupId);
- if (!group) return;
- const idx = group.items.findIndex(it => it.id === targetId);
- if (idx === -1) group.items.push(item); else group.items.splice(idx, 0, item);
- } else {
- const idx = outlineData.ungrouped.findIndex(it => it.id === targetId);
- if (idx === -1) outlineData.ungrouped.push(item); else outlineData.ungrouped.splice(idx, 0, item);
- }
- }
- function selectPageById(id) {
- // 通过底部大纲的序号匹配
- const track = document.getElementById('outlineTrack');
- if (!track) return;
- const item = Array.from(track.querySelectorAll('.outline-item-wrapper')).find(w => {
- const num = w.querySelector('.page-number');
- return num && num.textContent.trim() === id;
- });
- if (item) {
- const index = Array.from(track.children).indexOf(item) + 1;
- selectPage(index, { stopPropagation: () => {} });
- }
- }
- function syncBottomOutlineOrder() {
- const track = document.getElementById('outlineTrack');
- if (!track) return;
- const order = [];
- outlineData.groups.forEach(g => {
- g.items.forEach(it => order.push(it.id));
- });
- outlineData.ungrouped.forEach(it => order.push(it.id));
- const wrappers = Array.from(track.querySelectorAll('.outline-item-wrapper'));
- const map = new Map();
- wrappers.forEach(w => {
- const id = w.querySelector('.page-number')?.textContent.trim();
- if (id) map.set(id, w);
- });
- track.innerHTML = '';
- order.forEach(id => {
- const node = map.get(id);
- if (node) track.appendChild(node);
- });
- }
- document.addEventListener('DOMContentLoaded', () => {
- initRightOutline();
- });
- function startPptUploadFlow() {
- const input = document.getElementById('pptFileInput');
- if (!input) return;
- unsetAiFeatured();
- input.value = '';
- input.click();
- }
- function handlePptFileSelected(event) {
- const file = event.target.files[0];
- if (!file) return;
- const ext = file.name.split('.').pop().toLowerCase();
- if (!['ppt', 'pptx'].includes(ext)) {
- alert('仅支持上传PPT文件');
- return;
- }
- currentPptFile = file;
- hideCreateModal();
- beginPptParseSimulation(file);
- }
- function beginPptParseSimulation(file) {
- setPptParseState('parsing', `正在解析 ${file.name}`);
- const shouldFail = file.name.toLowerCase().includes('fail');
- clearTimeout(pptParseTimer);
- pptParseTimer = setTimeout(() => {
- if (shouldFail) {
- setPptParseState('error', '解析失败,请重试或更换文件');
- } else {
- setPptParseState('success', '解析完成,已生成课件');
- setTimeout(() => {
- closePptParseOverlay();
- addPptContentToCourse(file.name);
- }, 900);
- }
- }, 2200);
- }
- function setPptParseState(state, desc) {
- const overlay = document.getElementById('pptParseOverlay');
- const icon = document.getElementById('pptParseIcon');
- const title = document.getElementById('pptParseTitle');
- const text = document.getElementById('pptParseDesc');
- const actions = document.getElementById('pptParseActions');
- overlay.classList.add('active');
- icon.className = 'ppt-parse-icon';
- icon.innerHTML = statusIcons.loading;
- title.textContent = '解析中...';
- text.textContent = desc || '';
- actions.style.display = 'flex';
- Array.from(actions.querySelectorAll('button')).forEach(btn => btn.disabled = true);
- if (state === 'parsing') {
- icon.innerHTML = statusIcons.loading;
- title.textContent = '解析中...';
- icon.classList.remove('success', 'error');
- } else if (state === 'success') {
- icon.innerHTML = statusIcons.success;
- icon.classList.add('success');
- title.textContent = '解析完成';
- Array.from(actions.querySelectorAll('button')).forEach(btn => btn.disabled = false);
- } else if (state === 'error') {
- icon.innerHTML = statusIcons.error;
- icon.classList.add('error');
- title.textContent = '解析失败';
- Array.from(actions.querySelectorAll('button')).forEach(btn => btn.disabled = false);
- }
- }
- function closePptParseOverlay() {
- clearTimeout(pptParseTimer);
- const overlay = document.getElementById('pptParseOverlay');
- overlay.classList.remove('active');
- }
- function addPptContentToCourse(pptName) {
- // 简单模拟:新增一页内容页并提示
- addPageFromTemplate('content');
- alert(`已从PPT "${pptName}" 生成课程页面`);
- }
- // 顶部菜单 & 课程设置
- let courseMeta = { subject: '', grade: '' };
- function toggleTopMenu(event) {
- event.stopPropagation();
- const dropdown = document.getElementById('topDropdown');
- dropdown.classList.toggle('active');
- }
- function closeTopMenu() {
- const dropdown = document.getElementById('topDropdown');
- if (dropdown) dropdown.classList.remove('active');
- }
- document.addEventListener('click', () => {
- closeTopMenu();
- });
- function handleTopMenuAction(action) {
- closeTopMenu();
- if (action === 'back') {
- showCreateModal();
- } else if (action === 'settings') {
- showCourseSettings();
- } else if (action === 'template') {
- openTemplateCenter();
- } else if (action === 'duplicate') {
- alert('已另存为副本(示例)');
- } else if (action === 'delete') {
- openDeleteCourse();
- }
- }
- function setMenuActive(panelType) {
- document.querySelectorAll('.primary-menu .menu-item').forEach(item => {
- const isMatch = item.getAttribute('data-panel') === panelType;
- if (isMatch) {
- item.classList.add('active');
- } else {
- item.classList.remove('active');
- }
- });
- }
- function showCourseSettings() {
- const modal = document.getElementById('courseSettingsModal');
- const subjectSelect = document.getElementById('settingsSubjectSelect');
- const gradeSelect = document.getElementById('settingsGradeSelect');
- if (!modal || !subjectSelect || !gradeSelect) return;
- const publishSubject = document.getElementById('subjectSelect');
- const publishGrade = document.getElementById('classGradeSelect');
- subjectSelect.value = courseMeta.subject || (publishSubject ? publishSubject.value : '') || '';
- gradeSelect.value = courseMeta.grade || (publishGrade ? publishGrade.value : '') || '';
- modal.classList.add('active');
- }
- function hideCourseSettings() {
- const modal = document.getElementById('courseSettingsModal');
- if (modal) modal.classList.remove('active');
- }
- document.getElementById('courseSettingsModal').addEventListener('click', function(e) {
- if (e.target === this) hideCourseSettings();
- });
- function applyCourseSettings() {
- const subjectSelect = document.getElementById('settingsSubjectSelect');
- const gradeSelect = document.getElementById('settingsGradeSelect');
- courseMeta.subject = subjectSelect ? subjectSelect.value : '';
- courseMeta.grade = gradeSelect ? gradeSelect.value : '';
- applyCourseMetaToPublish();
- hideCourseSettings();
- }
- function applyCourseMetaToPublish() {
- const publishSubject = document.getElementById('subjectSelect');
- const publishGrade = document.getElementById('classGradeSelect');
- if (publishSubject) publishSubject.value = courseMeta.subject || '';
- if (publishGrade) publishGrade.value = courseMeta.grade || '';
- }
- // 发布表单变化时同步回全局设置
- const publishSubjectSelect = document.getElementById('subjectSelect');
- if (publishSubjectSelect) publishSubjectSelect.addEventListener('change', () => {
- courseMeta.subject = publishSubjectSelect.value;
- });
- const publishGradeSelect = document.getElementById('classGradeSelect');
- if (publishGradeSelect) publishGradeSelect.addEventListener('change', () => {
- courseMeta.grade = publishGradeSelect.value;
- });
- courseMeta.subject = publishSubjectSelect ? publishSubjectSelect.value : '';
- courseMeta.grade = publishGradeSelect ? publishGradeSelect.value : '';
- // 模板中心数据
- const sampleTemplates = [
- { id: 'tpl-1', name: '小学语文·故事导入', source: 'official', subject: 'chinese', grade: '3', pages: 8 },
- { id: 'tpl-2', name: '小学数学·分数基础', source: 'official', subject: 'math', grade: '4', pages: 10 },
- { id: 'tpl-3', name: '科学·水的三态', source: 'mine', subject: 'science', grade: '4', pages: 6 },
- { id: 'tpl-4', name: '英语·词汇练习', source: 'official', subject: 'english', grade: '5', pages: 5 }
- ];
- let selectedTemplates = [];
- function openTemplateCenter() {
- document.getElementById('templateCenterModal').classList.add('active');
- filterTemplates();
- }
- function closeTemplateCenter() {
- document.getElementById('templateCenterModal').classList.remove('active');
- selectedTemplates = [];
- updateTemplateSelectedCount();
- }
- document.getElementById('templateCenterModal').addEventListener('click', function(e) {
- if (e.target === this) closeTemplateCenter();
- });
- function filterTemplates() {
- const source = document.getElementById('tplSourceFilter').value;
- const subject = document.getElementById('tplSubjectFilter').value;
- const grade = document.getElementById('tplGradeFilter').value;
- const grid = document.getElementById('templateGrid');
- let filtered = sampleTemplates.filter(t => {
- if (source !== 'all' && t.source !== source) return false;
- if (subject !== 'all' && t.subject !== subject) return false;
- if (grade !== 'all' && t.grade !== grade) return false;
- return true;
- });
- grid.innerHTML = filtered.map(t => `
- <div class="app-card ${selectedTemplates.includes(t.id) ? 'selected' : ''}" onclick="toggleTemplateSelection('${t.id}')">
- <div class="app-cover">
- <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
- <rect x="3" y="4" width="18" height="14" rx="2"/>
- <line x1="8" y1="8" x2="16" y2="8"/>
- <line x1="8" y1="12" x2="16" y2="12"/>
- </svg>
- </div>
- <div class="app-info">
- <div class="app-name">${t.name}</div>
- <div class="app-description">${(t.subject || '通用')} · ${t.pages}页</div>
- </div>
- </div>
- `).join('');
- updateTemplateSelectedCount();
- }
- function toggleTemplateSelection(id) {
- if (selectedTemplates.includes(id)) {
- selectedTemplates = selectedTemplates.filter(tid => tid !== id);
- } else {
- selectedTemplates.push(id);
- }
- filterTemplates();
- }
- function updateTemplateSelectedCount() {
- const countText = document.getElementById('selectedTemplateCount');
- const btn = document.getElementById('confirmApplyTemplateBtn');
- if (countText) countText.textContent = selectedTemplates.length;
- if (btn) btn.disabled = selectedTemplates.length === 0;
- }
- function confirmApplyTemplates() {
- if (selectedTemplates.length === 0) return;
- selectedTemplates.forEach(() => addPageFromTemplate('content'));
- alert('已应用模板,生成对应页面');
- closeTemplateCenter();
- }
- // 删除课程确认
- function openDeleteCourse() {
- document.getElementById('deleteCourseModal').classList.add('active');
- }
- function hideDeleteCourse() {
- document.getElementById('deleteCourseModal').classList.remove('active');
- }
- document.getElementById('deleteCourseModal').addEventListener('click', function(e) {
- if (e.target === this) hideDeleteCourse();
- });
- function confirmDeleteCourse() {
- hideDeleteCourse();
- showCreateModal();
- }
- // 显示发布弹窗
- function showPublishModal() {
- // 获取当前课程名称
- const courseName = document.querySelector('.course-title').textContent.trim();
- document.getElementById('publishCourseNameDisplay').textContent = courseName;
- document.getElementById('publishModal').classList.add('active');
- }
- // 隐藏发布弹窗
- function hidePublishModal() {
- document.getElementById('publishModal').classList.remove('active');
- // 重置封面
- const coverImage = document.getElementById('courseCoverImage');
- const placeholder = document.getElementById('coverPlaceholder');
- coverImage.style.display = 'none';
- coverImage.src = '';
- placeholder.style.display = 'block';
- }
- // 点击遮罩关闭发布弹窗
- document.getElementById('publishModal').addEventListener('click', function(e) {
- if (e.target === this) {
- hidePublishModal();
- }
- });
- // 编辑课程名称
- function editCourseName() {
- const display = document.getElementById('publishCourseNameDisplay');
- const currentName = display.textContent;
-
- // 创建输入框
- const input = document.createElement('input');
- input.type = 'text';
- input.value = currentName;
- input.className = 'publish-course-name-input';
-
- // 设置初始宽度(基于当前文本)
- const measureSpan = document.createElement('span');
- measureSpan.style.visibility = 'hidden';
- measureSpan.style.position = 'absolute';
- measureSpan.style.fontSize = '18px';
- measureSpan.style.fontWeight = '600';
- measureSpan.style.padding = '10px 16px';
- measureSpan.textContent = currentName;
- document.body.appendChild(measureSpan);
- const initialWidth = Math.max(120, Math.min(500, measureSpan.offsetWidth));
- document.body.removeChild(measureSpan);
- input.style.width = initialWidth + 'px';
-
- // 替换显示元素
- display.replaceWith(input);
- input.focus();
- input.select();
-
- // 输入时自动调整宽度
- input.addEventListener('input', () => {
- const tempSpan = document.createElement('span');
- tempSpan.style.visibility = 'hidden';
- tempSpan.style.position = 'absolute';
- tempSpan.style.fontSize = '18px';
- tempSpan.style.fontWeight = '600';
- tempSpan.style.padding = '10px 16px';
- tempSpan.textContent = input.value || currentName;
- document.body.appendChild(tempSpan);
- const newWidth = Math.max(120, Math.min(500, tempSpan.offsetWidth));
- document.body.removeChild(tempSpan);
- input.style.width = newWidth + 'px';
- });
-
- // 失去焦点时恢复
- const saveEdit = () => {
- const newName = input.value.trim() || currentName;
- const newDisplay = document.createElement('div');
- newDisplay.id = 'publishCourseNameDisplay';
- newDisplay.className = 'publish-course-name';
- newDisplay.textContent = newName;
- newDisplay.onclick = editCourseName;
- input.replaceWith(newDisplay);
-
- // 同步更新顶部课程标题
- document.querySelector('.course-title').textContent = newName;
- };
-
- input.addEventListener('blur', saveEdit);
- input.addEventListener('keydown', (e) => {
- if (e.key === 'Enter') {
- input.blur();
- } else if (e.key === 'Escape') {
- input.value = currentName;
- input.blur();
- }
- });
- }
- // 从本地上传课程封面
- function uploadCourseCoverLocal(event) {
- event.stopPropagation();
- document.getElementById('courseCoverInput').click();
- }
- // 网页搜索图片作为封面
- function searchWebImageForCover(event) {
- event.stopPropagation();
- console.log('打开网页搜索图片弹窗(用于课程封面)');
- alert('网页搜索图片功能(为课程封面)');
- // 实际应用中应打开搜索浮窗,选择后设置封面
- }
- // AI生成图片作为封面
- function generateAIImageForCover(event) {
- event.stopPropagation();
- console.log('打开AI生成图片弹窗(用于课程封面)');
- alert('AI生成图片功能(为课程封面)');
- // 实际应用中应打开AI生成浮窗,生成后设置封面
- }
- // 处理封面上传
- function handleCoverUpload(event) {
- const file = event.target.files[0];
- if (file && file.type.startsWith('image/')) {
- const reader = new FileReader();
- reader.onload = function(e) {
- const coverImage = document.getElementById('courseCoverImage');
- const placeholder = document.getElementById('coverPlaceholder');
-
- coverImage.src = e.target.result;
- coverImage.style.display = 'block';
- placeholder.style.display = 'none';
- };
- reader.readAsDataURL(file);
- }
- }
- // 删除课程封面
- function deleteCourseCover() {
- const coverImage = document.getElementById('courseCoverImage');
- const placeholder = document.getElementById('coverPlaceholder');
-
- coverImage.src = '';
- coverImage.style.display = 'none';
- placeholder.style.display = 'block';
-
- // 清空文件选择
- document.getElementById('courseCoverInput').value = '';
- }
- // 选择可见范围
- function selectVisibility(element, value) {
- // 移除所有选中状态
- document.querySelectorAll('.radio-option').forEach(option => {
- option.classList.remove('selected');
- });
- // 添加选中状态
- element.classList.add('selected');
- // 设置radio按钮
- element.querySelector('input[type="radio"]').checked = true;
- }
- // 确认发布
- function confirmPublish(event) {
- event.preventDefault();
-
- const subject = document.getElementById('subjectSelect').value;
- const classGrade = document.getElementById('classGradeSelect').value;
- const classNum = document.getElementById('classSelect').value;
- const visibility = document.querySelector('input[name="visibility"]:checked').value;
-
- const courseName = document.getElementById('publishCourseNameDisplay').textContent;
- const coverImage = document.getElementById('courseCoverImage');
- const hasCover = coverImage.style.display !== 'none' && coverImage.src;
-
- console.log('发布课程:', {
- courseName,
- subject,
- classGrade,
- classNum,
- visibility,
- hasCover
- });
-
- // 显示成功提示
- alert(`课程《${courseName}》发布成功!\n学科: ${subject}\n班级: ${classGrade}年级${classNum}班\n可见范围: ${visibility === 'students' ? '仅发布学生可见' : '组织可见'}`);
-
- hidePublishModal();
- }
- // 开始AI创建
- function startAICreate() {
- hideCreateModal();
- // 切换到AI面板
- switchToPanel('ai');
- }
- // 切换面板
- function switchToPanel(panelType) {
- const aiPanel = document.getElementById('aiPanel');
- const pagesPanel = document.getElementById('pagesPanel');
- const toolsPanel = document.getElementById('toolsPanel');
- const appsPanel = document.getElementById('appsPanel');
- const webPanel = document.getElementById('webPanel');
- const resourcesPanel = document.getElementById('resourcesPanel');
- [aiPanel, pagesPanel, toolsPanel, appsPanel, webPanel, resourcesPanel].forEach(panel => {
- if (panel) {
- panel.classList.remove('collapsed', 'hidden');
- }
- });
- if (panelType === 'ai') {
- pagesPanel.classList.add('hidden');
- toolsPanel.classList.add('hidden');
- appsPanel.classList.add('hidden');
- webPanel.classList.add('hidden');
- resourcesPanel.classList.add('hidden');
- restoreNormalView();
- } else if (panelType === 'pages') {
- aiPanel.classList.add('hidden');
- toolsPanel.classList.add('hidden');
- appsPanel.classList.add('hidden');
- webPanel.classList.add('hidden');
- resourcesPanel.classList.add('hidden');
- restoreNormalView();
- } else if (panelType === 'tools') {
- aiPanel.classList.add('hidden');
- pagesPanel.classList.add('hidden');
- appsPanel.classList.add('hidden');
- webPanel.classList.add('hidden');
- resourcesPanel.classList.add('hidden');
- document.getElementById('toolsListView').classList.remove('hidden');
- document.getElementById('toolsConfigView').classList.add('hidden');
- restoreNormalView();
- } else if (panelType === 'apps') {
- aiPanel.classList.add('hidden');
- pagesPanel.classList.add('hidden');
- toolsPanel.classList.add('hidden');
- webPanel.classList.add('hidden');
- resourcesPanel.classList.add('hidden');
- restoreNormalView();
- } else if (panelType === 'web') {
- aiPanel.classList.add('hidden');
- pagesPanel.classList.add('hidden');
- toolsPanel.classList.add('hidden');
- appsPanel.classList.add('hidden');
- resourcesPanel.classList.add('hidden');
- switchToWebView('list');
- restoreNormalView();
- } else if (panelType === 'resources') {
- aiPanel.classList.add('hidden');
- pagesPanel.classList.add('hidden');
- toolsPanel.classList.add('hidden');
- appsPanel.classList.add('hidden');
- webPanel.classList.add('hidden');
- restoreNormalView();
- }
- }
- // 二级菜单折叠
- function toggleSecondaryPanel() {
- const aiPanel = document.getElementById('aiPanel');
- const pagesPanel = document.getElementById('pagesPanel');
- const toolsPanel = document.getElementById('toolsPanel');
- const appsPanel = document.getElementById('appsPanel');
- const webPanel = document.getElementById('webPanel');
- const resourcesPanel = document.getElementById('resourcesPanel');
-
- let activePanel = null;
- if (!aiPanel.classList.contains('hidden')) {
- activePanel = aiPanel;
- } else if (!pagesPanel.classList.contains('hidden')) {
- activePanel = pagesPanel;
- } else if (!toolsPanel.classList.contains('hidden')) {
- activePanel = toolsPanel;
- } else if (!appsPanel.classList.contains('hidden')) {
- activePanel = appsPanel;
- } else if (!webPanel.classList.contains('hidden')) {
- activePanel = webPanel;
- } else if (!resourcesPanel.classList.contains('hidden')) {
- activePanel = resourcesPanel;
- }
- if (!activePanel) return;
- activePanel.classList.toggle('collapsed');
- const btn = activePanel.querySelector('.collapse-btn svg');
- if (activePanel.classList.contains('collapsed')) {
- btn.innerHTML = '<polyline points="9 18 15 12 9 6"/>';
- } else {
- btn.innerHTML = '<polyline points="15 18 9 12 15 6"/>';
- }
- }
- // 右侧面板折叠
- function toggleRightPanel() {
- const panel = document.getElementById('rightPanel');
- panel.classList.toggle('collapsed');
- const btn = panel.querySelector('.collapse-btn svg');
- if (panel.classList.contains('collapsed')) {
- btn.innerHTML = '<polyline points="15 18 9 12 15 6"/>';
- } else {
- btn.innerHTML = '<polyline points="9 18 15 12 9 6"/>';
- }
- }
- // 一级菜单点击
- document.querySelectorAll('.primary-menu .menu-item').forEach(item => {
- item.addEventListener('click', function() {
- document.querySelectorAll('.primary-menu .menu-item').forEach(i => i.classList.remove('active'));
- this.classList.add('active');
- const panelType = this.getAttribute('data-panel');
- if (panelType === 'ai') {
- switchToPanel('ai');
- } else if (panelType === 'pages') {
- switchToPanel('pages');
- } else if (panelType === 'tools') {
- switchToPanel('tools');
- } else if (panelType === 'apps') {
- switchToPanel('apps');
- } else if (panelType === 'web') {
- switchToPanel('web');
- } else if (panelType === 'resources') {
- switchToPanel('resources');
- }
- });
- });
- // 自动调整textarea高度
- function autoResize(textarea) {
- textarea.style.height = 'auto';
- const newHeight = Math.max(72, Math.min(textarea.scrollHeight, 180));
- textarea.style.height = newHeight + 'px';
- }
- // 发送消息
- function sendMessage() {
- const input = document.getElementById('chatInput');
- const message = input.value.trim();
- const inputContainer = document.getElementById('chatInputContainer');
- const statusText = document.getElementById('statusText');
- const sendBtn = document.getElementById('sendBtn');
- const uploadBtn = document.getElementById('uploadBtn');
- if (!message) return;
- // 添加用户消息
- addMessage(message, 'user');
- // 清空输入框并重置高度
- input.value = '';
- input.style.height = '72px';
- // 移动对话框到底部
- inputContainer.classList.add('bottom');
- // 显示生成状态
- statusText.classList.add('active');
- sendBtn.classList.add('generating');
- uploadBtn.style.display = 'none';
- // 禁用输入和按钮
- input.disabled = true;
- sendBtn.disabled = true;
- // 模拟AI回复
- setTimeout(() => {
- const topic = extractTopic(message);
- const aiResponse = `好的,我正在为您生成"${topic}"的课程内容。\n\n课程将包括:\n• 教学目标和重点\n• 互动演示内容\n• 课堂练习题\n• 小组活动设计\n\n预计生成时间:30秒`;
- addMessage(aiResponse, 'ai');
- // 更新课程标题
- document.querySelector('.course-title').textContent = topic;
- // 模拟生成完成
- setTimeout(() => {
- addMessage('✅ 课程已生成完成!已为您创建了5个教学页面和3个互动工具。您可以在底部查看课程大纲,或在中央区域开始编辑。', 'ai');
- // 切换到编辑模式
- switchToEditMode();
- // 恢复正常状态
- statusText.classList.remove('active');
- sendBtn.classList.remove('generating');
- uploadBtn.style.display = 'flex';
- input.disabled = false;
- sendBtn.disabled = false;
- // 更新保存状态
- document.getElementById('saveStatus').textContent = '已保存';
- }, 2000);
- }, 800);
- }
- // 添加消息到聊天区域
- function addMessage(text, type) {
- const messagesContainer = document.getElementById('chatMessages');
- const messageDiv = document.createElement('div');
- messageDiv.className = `message message-${type}`;
- const contentDiv = document.createElement('div');
- contentDiv.className = 'message-content';
- contentDiv.textContent = text;
- messageDiv.appendChild(contentDiv);
- messagesContainer.appendChild(messageDiv);
- // 滚动到底部
- messagesContainer.scrollTop = messagesContainer.scrollHeight;
- }
- // 提取课程主题
- function extractTopic(message) {
- const match = message.match(/教学内容为(.+?)的/);
- if (match) {
- return match[1];
- }
- return '新课程';
- }
- // 切换到编辑模式
- function switchToEditMode() {
- isEditMode = true;
- // 隐藏占位符
- document.getElementById('slidePlaceholder').style.display = 'none';
- // 显示工具栏
- document.getElementById('elementToolbar').classList.add('visible');
- // 显示底部大纲
- document.getElementById('bottomOutline').classList.add('visible');
- // 生成示例页面
- generateSamplePages();
- }
- // 生成示例页面
- function generateSamplePages() {
- const outlineTrack = document.getElementById('outlineTrack');
- const pageCount = 5;
- const pageTypes = ['标题页', '内容页', '选择题', '问答', '总结'];
- for (let i = 1; i <= pageCount; i++) {
- const wrapper = document.createElement('div');
- wrapper.className = 'outline-item-wrapper';
- const outlineItem = document.createElement('div');
- outlineItem.className = 'outline-item' + (i === 1 ? ' active' : '');
- outlineItem.draggable = true;
- outlineItem.dataset.pageIndex = i;
- outlineItem.onclick = (e) => selectPage(i, e);
- outlineItem.innerHTML = `
- <span class="page-number">${i}</span>
- <span>${pageTypes[i - 1]}</span>
- <div class="page-menu" onclick="event.stopPropagation(); togglePageMenu(event, ${i})">
- <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
- <circle cx="12" cy="12" r="1"/>
- <circle cx="12" cy="5" r="1"/>
- <circle cx="12" cy="19" r="1"/>
- </svg>
- <div class="page-menu-dropdown">
- <div class="page-menu-item" onclick="copyPage(${i})">
- <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
- <rect x="9" y="9" width="13" height="13" rx="2" ry="2"/>
- <path d="M5 15H4a2 2 0 01-2-2V4a2 2 0 012-2h9a2 2 0 012 2v1"/>
- </svg>
- 复制
- </div>
- <div class="page-menu-item" onclick="addBlankPage(${i})">
- <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
- <line x1="12" y1="5" x2="12" y2="19"/>
- <line x1="5" y1="12" x2="19" y2="12"/>
- </svg>
- 新增
- </div>
- <div class="page-menu-item danger" onclick="deletePage(${i})">
- <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
- <polyline points="3 6 5 6 21 6"/>
- <path d="M19 6v14a2 2 0 01-2 2H7a2 2 0 01-2-2V6m3 0V4a2 2 0 012-2h4a2 2 0 012 2v2"/>
- </svg>
- 删除
- </div>
- </div>
- </div>
- `;
- // 添加拖拽事件监听器
- outlineItem.addEventListener('dragstart', handleDragStart);
- outlineItem.addEventListener('dragend', handleDragEnd);
- outlineItem.addEventListener('dragover', handleDragOver);
- outlineItem.addEventListener('drop', handleDrop);
- outlineItem.addEventListener('dragleave', handleDragLeave);
- const addBtn = document.createElement('button');
- addBtn.className = 'add-page-between';
- addBtn.textContent = '+';
- addBtn.onclick = () => addPageBetween(i);
- wrapper.appendChild(outlineItem);
- wrapper.appendChild(addBtn);
- outlineTrack.appendChild(wrapper);
- }
- syncRightOutlineWithBottom();
- }
- // 选择页面
- function selectPage(pageNum, evt) {
- const currentEvent = evt || event;
- document.querySelectorAll('.outline-item').forEach(item => {
- item.classList.remove('active');
- });
- if (currentEvent && currentEvent.currentTarget) {
- currentEvent.currentTarget.classList.add('active');
- }
- }
- // 页面菜单切换
- function togglePageMenu(event, pageNum) {
- event.stopPropagation();
- const menu = event.currentTarget;
- // 关闭其他菜单
- document.querySelectorAll('.page-menu').forEach(m => {
- if (m !== menu) m.classList.remove('active');
- });
- menu.classList.toggle('active');
- }
- // 复制页面
- function copyPage(pageNum) {
- console.log('复制页面', pageNum);
- alert('复制页面 ' + pageNum);
- }
- // 新增空白页面
- function addBlankPage(afterPage) {
- console.log('在页面后新增空白页', afterPage);
- alert('在页面 ' + afterPage + ' 后新增空白页');
- }
- // 删除页面
- function deletePage(pageNum) {
- if (confirm('确定要删除页面 ' + pageNum + ' 吗?')) {
- console.log('删除页面', pageNum);
- alert('删除页面 ' + pageNum);
- }
- }
- // 在页面之间添加
- function addPageBetween(afterPage) {
- console.log('在页面后添加新页面', afterPage);
- alert('在页面 ' + afterPage + ' 后添加新页面');
- }
- // 页面拖拽功能
- let draggedElement = null;
- let draggedIndex = null;
- function handleDragStart(e) {
- draggedElement = e.currentTarget;
- draggedIndex = parseInt(draggedElement.dataset.pageIndex);
- draggedElement.classList.add('dragging');
- e.dataTransfer.effectAllowed = 'move';
- e.dataTransfer.setData('text/html', draggedElement.innerHTML);
- }
- function handleDragEnd(e) {
- e.currentTarget.classList.remove('dragging');
- // 清除所有拖拽状态
- document.querySelectorAll('.outline-item').forEach(item => {
- item.classList.remove('drag-over');
- });
- }
- function handleDragOver(e) {
- if (e.preventDefault) {
- e.preventDefault();
- }
- e.dataTransfer.dropEffect = 'move';
-
- const targetElement = e.currentTarget;
- if (targetElement !== draggedElement) {
- targetElement.classList.add('drag-over');
- }
-
- return false;
- }
- function handleDragLeave(e) {
- e.currentTarget.classList.remove('drag-over');
- }
- function handleDrop(e) {
- if (e.stopPropagation) {
- e.stopPropagation();
- }
-
- const targetElement = e.currentTarget;
- const targetIndex = parseInt(targetElement.dataset.pageIndex);
-
- if (draggedElement !== targetElement && draggedIndex !== targetIndex) {
- // 交换页面顺序
- const outlineTrack = document.getElementById('outlineTrack');
- const allWrappers = Array.from(outlineTrack.querySelectorAll('.outline-item-wrapper'));
-
- const draggedWrapper = draggedElement.parentElement;
- const targetWrapper = targetElement.parentElement;
-
- // 获取位置
- const draggedWrapperIndex = allWrappers.indexOf(draggedWrapper);
- const targetWrapperIndex = allWrappers.indexOf(targetWrapper);
-
- if (draggedWrapperIndex < targetWrapperIndex) {
- targetWrapper.parentNode.insertBefore(draggedWrapper, targetWrapper.nextSibling);
- } else {
- targetWrapper.parentNode.insertBefore(draggedWrapper, targetWrapper);
- }
-
- // 更新页面序号
- updatePageNumbers();
-
- console.log(`页面从位置 ${draggedIndex} 移动到位置 ${targetIndex}`);
- }
-
- targetElement.classList.remove('drag-over');
-
- return false;
- }
- // 更新页面序号
- function updatePageNumbers() {
- const outlineItems = document.querySelectorAll('.outline-item');
- outlineItems.forEach((item, index) => {
- const pageNumber = index + 1;
- item.dataset.pageIndex = pageNumber;
- const numberSpan = item.querySelector('.page-number');
- if (numberSpan) {
- numberSpan.textContent = pageNumber;
- }
- });
- }
- // 下拉菜单切换
- function toggleDropdown(id) {
- event.stopPropagation();
- const dropdown = document.getElementById(id);
- dropdown.classList.toggle('active');
- }
- // 点击页面其他地方关闭下拉菜单
- document.addEventListener('click', function() {
- document.querySelectorAll('.dropdown').forEach(d => d.classList.remove('active'));
- document.querySelectorAll('.page-menu').forEach(m => m.classList.remove('active'));
- });
- // 插入表格
- function insertTable() {
- alert('插入表格功能');
- }
- // 插入形状
- function insertShape(type) {
- const canvas = document.getElementById('slideCanvas');
- const element = document.createElement('div');
- element.className = 'slide-element';
- element.id = 'element-' + (++elementIdCounter);
- element.style.left = '200px';
- element.style.top = '200px';
- element.style.width = '150px';
- element.style.height = '150px';
- if (type === 'circle') {
- element.style.borderRadius = '50%';
- element.style.background = '#285cf5';
- } else if (type === 'rectangle') {
- element.style.background = '#3b82f6';
- } else if (type === 'triangle') {
- element.style.background = '#10b981';
- } else if (type === 'arrow') {
- element.style.background = '#f59e0b';
- }
- addResizeHandles(element);
- canvas.appendChild(element);
- makeElementDraggable(element);
- element.addEventListener('click', () => selectElement(element));
- }
- // 添加调整大小手柄
- function addResizeHandles(element) {
- const positions = ['nw', 'ne', 'sw', 'se'];
- positions.forEach(pos => {
- const handle = document.createElement('div');
- handle.className = 'resize-handle ' + pos;
- element.appendChild(handle);
- });
- }
- // 插入文本
- function insertText() {
- const canvas = document.getElementById('slideCanvas');
- const element = document.createElement('div');
- element.className = 'slide-element text-element';
- element.id = 'element-' + (++elementIdCounter);
- element.contentEditable = true;
- element.textContent = '双击编辑文本';
- element.style.left = '100px';
- element.style.top = '100px';
- element.style.width = '300px';
- element.style.minHeight = '50px';
- addResizeHandles(element);
- canvas.appendChild(element);
- makeElementDraggable(element);
- element.addEventListener('click', () => selectElement(element));
- }
- // 插入图片
- function insertImage() {
- const canvas = document.getElementById('slideCanvas');
- const element = document.createElement('div');
- element.className = 'slide-element has-image';
- element.id = 'element-' + (++elementIdCounter);
- element.style.left = '150px';
- element.style.top = '150px';
- element.style.width = '200px';
- element.style.height = '150px';
- element.style.background = '#e5e7eb';
- element.style.display = 'flex';
- element.style.alignItems = 'center';
- element.style.justifyContent = 'center';
- element.innerHTML = `
- <span style="color: #9ca3af;">图片占位</span>
- <div class="image-hover-menu">
- <div class="image-menu-item" onclick="uploadLocal(event)">
- <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
- <path d="M21 15v4a2 2 0 01-2 2H5a2 2 0 01-2-2v-4"/>
- <polyline points="17 8 12 3 7 8"/>
- <line x1="12" y1="3" x2="12" y2="15"/>
- </svg>
- 自本地上传
- </div>
- <div class="image-menu-item" onclick="searchWebImage(event)">
- <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
- <circle cx="11" cy="11" r="8"/>
- <path d="M21 21l-4.35-4.35"/>
- </svg>
- 自网页搜索
- </div>
- <div class="image-menu-item" onclick="generateAIImage(event)">
- <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
- <path d="M12 2L2 7l10 5 10-5-10-5z"/>
- <path d="M2 17l10 5 10-5"/>
- <path d="M2 12l10 5 10-5"/>
- </svg>
- 自AI生成
- </div>
- </div>
- `;
- addResizeHandles(element);
- canvas.appendChild(element);
- makeElementDraggable(element);
- element.addEventListener('click', () => selectElement(element));
- }
- // 从模板添加页面
- function addPageFromTemplate(templateType) {
- if (!isEditMode) {
- switchToEditMode();
- }
- const outlineTrack = document.getElementById('outlineTrack');
- const pageCount = outlineTrack.querySelectorAll('.outline-item-wrapper').length + 1;
- const templateNames = {
- 'title': '标题页',
- 'image': '图片页',
- 'content': '内容页',
- 'text-image': '文图页',
- 'image-text': '图文页'
- };
- const wrapper = document.createElement('div');
- wrapper.className = 'outline-item-wrapper';
- const outlineItem = document.createElement('div');
- outlineItem.className = 'outline-item';
- outlineItem.onclick = (e) => selectPage(pageCount, e);
- outlineItem.innerHTML = `
- <span class="page-number">${pageCount}</span>
- <span>${templateNames[templateType]}</span>
- <div class="page-menu" onclick="event.stopPropagation(); togglePageMenu(event, ${pageCount})">
- <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
- <circle cx="12" cy="12" r="1"/>
- <circle cx="12" cy="5" r="1"/>
- <circle cx="12" cy="19" r="1"/>
- </svg>
- <div class="page-menu-dropdown">
- <div class="page-menu-item" onclick="copyPage(${pageCount})">
- <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
- <rect x="9" y="9" width="13" height="13" rx="2" ry="2"/>
- <path d="M5 15H4a2 2 0 01-2-2V4a2 2 0 012-2h9a2 2 0 012 2v1"/>
- </svg>
- 复制
- </div>
- <div class="page-menu-item" onclick="addBlankPage(${pageCount})">
- <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
- <line x1="12" y1="5" x2="12" y2="19"/>
- <line x1="5" y1="12" x2="19" y2="12"/>
- </svg>
- 新增
- </div>
- <div class="page-menu-item danger" onclick="deletePage(${pageCount})">
- <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
- <polyline points="3 6 5 6 21 6"/>
- <path d="M19 6v14a2 2 0 01-2 2H7a2 2 0 01-2-2V6m3 0V4a2 2 0 012-2h4a2 2 0 012 2v2"/>
- </svg>
- 删除
- </div>
- </div>
- </div>
- `;
- const addBtn = document.createElement('button');
- addBtn.className = 'add-page-between';
- addBtn.textContent = '+';
- addBtn.onclick = () => addPageBetween(pageCount);
- wrapper.appendChild(outlineItem);
- wrapper.appendChild(addBtn);
- outlineTrack.appendChild(wrapper);
- // 如果是包含图片的页面,自动添加图片元素
- if (['image', 'text-image', 'image-text'].includes(templateType)) {
- insertImage();
- }
- alert('已添加' + templateNames[templateType]);
- syncRightOutlineWithBottom();
- }
- // 上传PPT
- function uploadPPT() {
- const input = document.createElement('input');
- input.type = 'file';
- input.accept = '.ppt,.pptx';
- input.onchange = (e) => {
- const file = e.target.files[0];
- if (file) {
- alert('正在解析PPT文件: ' + file.name + '\n\n此功能将自动识别PPT中的页面并导入到课件中。');
- }
- };
- input.click();
- }
- // 图片上传方法
- function uploadLocal(event) {
- event.stopPropagation();
- const input = document.createElement('input');
- input.type = 'file';
- input.accept = 'image/*';
- input.onchange = (e) => {
- const file = e.target.files[0];
- if (file) {
- alert('已选择图片: ' + file.name);
- }
- };
- input.click();
- }
- function searchWebImage(event) {
- event.stopPropagation();
- document.getElementById('searchImageModal').classList.add('active');
- }
- function generateAIImage(event) {
- event.stopPropagation();
- document.getElementById('generateImageModal').classList.add('active');
- }
- // 关闭搜索浮窗
- function closeSearchModal() {
- document.getElementById('searchImageModal').classList.remove('active');
- }
- // 确认选择搜索图片
- function confirmSearchImage() {
- alert('已选择网页图片');
- closeSearchModal();
- }
- // 关闭生成浮窗
- function closeGenerateModal() {
- document.getElementById('generateImageModal').classList.remove('active');
- }
- // 开始生成图片
- function startGenerate() {
- const loading = document.getElementById('generateLoading');
- loading.classList.add('active');
- setTimeout(() => {
- loading.classList.remove('active');
- alert('图片生成完成!');
- closeGenerateModal();
- }, 3000);
- }
- // 点击浮窗遮罩关闭
- document.getElementById('searchImageModal').addEventListener('click', function(e) {
- if (e.target === this) {
- closeSearchModal();
- }
- });
- document.getElementById('generateImageModal').addEventListener('click', function(e) {
- if (e.target === this) {
- closeGenerateModal();
- }
- });
- document.getElementById('videoSourceModal').addEventListener('click', function(e) {
- if (e.target === this) {
- closeVideoSourceModal();
- }
- });
- document.getElementById('audioUploadModal').addEventListener('click', function(e) {
- if (e.target === this) {
- closeAudioUploadModal();
- }
- });
- document.getElementById('documentUploadModal').addEventListener('click', function(e) {
- if (e.target === this) {
- closeDocumentUploadModal();
- }
- });
- document.getElementById('collectionModal').addEventListener('click', function(e) {
- if (e.target === this) {
- closeCollectionModal();
- }
- });
- // 使元素可拖动
- function makeElementDraggable(element) {
- let isDragging = false;
- let startX, startY, startLeft, startTop;
- element.addEventListener('mousedown', function(e) {
- if (e.target.classList.contains('resize-handle')) return;
- if (e.target.contentEditable === 'true' && e.target === element) return;
- isDragging = true;
- startX = e.clientX;
- startY = e.clientY;
- startLeft = parseInt(element.style.left) || 0;
- startTop = parseInt(element.style.top) || 0;
- e.preventDefault();
- });
- document.addEventListener('mousemove', function(e) {
- if (!isDragging) return;
- const dx = e.clientX - startX;
- const dy = e.clientY - startY;
- element.style.left = (startLeft + dx) + 'px';
- element.style.top = (startTop + dy) + 'px';
- });
- document.addEventListener('mouseup', function() {
- isDragging = false;
- });
- }
- // 选中元素
- function selectElement(element) {
- if (selectedElement) {
- selectedElement.classList.remove('selected');
- }
- selectedElement = element;
- element.classList.add('selected');
- // 显示编辑工具
- document.getElementById('editTools').style.display = 'flex';
- }
- // 删除元素
- function deleteElement() {
- if (selectedElement) {
- selectedElement.remove();
- selectedElement = null;
- document.getElementById('editTools').style.display = 'none';
- }
- }
- // 点击canvas空白处取消选择
- document.getElementById('slideCanvas').addEventListener('click', function(e) {
- if (e.target === this && selectedElement) {
- selectedElement.classList.remove('selected');
- selectedElement = null;
- document.getElementById('editTools').style.display = 'none';
- }
- });
- // 监听Enter键发送消息
- document.getElementById('chatInput').addEventListener('keydown', function(e) {
- if (e.key === 'Enter' && !e.shiftKey) {
- e.preventDefault();
- sendMessage();
- }
- });
- // 页面加载时显示创建弹窗
- window.addEventListener('load', function() {
- setTimeout(() => {
- showCreateModal();
- }, 300);
- });
- // ========== AI应用功能 ==========
-
- // 应用数据(模拟)
- const sampleApps = [
- {
- id: 'app-001',
- name: '单词记忆卡',
- description: '帮助学生记忆单词的互动卡片应用',
- source: 'public',
- type: 'agent',
- mode: 'card',
- category: '英语',
- grade: '小学'
- },
- {
- id: 'app-002',
- name: '数学计算练习',
- description: '支持四则运算的智能练习系统',
- source: 'public',
- type: 'workflow',
- mode: 'immersive',
- category: '数学',
- grade: '小学'
- },
- {
- id: 'app-003',
- name: '诗词鉴赏助手',
- description: 'AI辅助的诗词理解与鉴赏工具',
- source: 'public',
- type: 'agent',
- mode: 'chat',
- category: '语文',
- grade: '初中'
- },
- {
- id: 'app-004',
- name: '化学实验模拟',
- description: '虚拟化学实验室,安全探索化学反应',
- source: 'public',
- type: 'workflow',
- mode: 'immersive',
- category: '化学',
- grade: '高中'
- },
- {
- id: 'app-005',
- name: '历史时间轴',
- description: '交互式历史事件时间轴展示',
- source: 'mine',
- type: 'workflow',
- mode: 'card',
- category: '历史',
- grade: '初中'
- },
- {
- id: 'app-006',
- name: '地理地图探索',
- description: '3D地球仪和地理知识问答',
- source: 'public',
- type: 'agent',
- mode: 'immersive',
- category: '地理',
- grade: '初中'
- },
- {
- id: 'app-007',
- name: '物理公式推导',
- description: 'AI辅助的物理公式推导与解题',
- source: 'mine',
- type: 'agent',
- mode: 'chat',
- category: '物理',
- grade: '高中'
- },
- {
- id: 'app-008',
- name: '生物细胞识别',
- description: '显微镜下的细胞结构学习工具',
- source: 'public',
- type: 'workflow',
- mode: 'card',
- category: '生物',
- grade: '初中'
- },
- {
- id: 'app-009',
- name: '作文批改助手',
- description: 'AI智能作文评分与改进建议',
- source: 'public',
- type: 'agent',
- mode: 'chat',
- category: '语文',
- grade: '小学'
- }
- ];
- let selectedApps = [];
- let currentFilters = { source: 'all', type: 'all', mode: 'all' };
- let chatMessages = [];
- // 打开应用中心
- function openAIAppCenter() {
- document.getElementById('aiAppCenterModal').classList.add('active');
- renderAppGrid();
- }
- // 关闭应用中心
- function closeAppCenter() {
- document.getElementById('aiAppCenterModal').classList.remove('active');
- selectedApps = [];
- updateSelectedCount();
- }
- // 点击遮罩关闭
- document.getElementById('aiAppCenterModal').addEventListener('click', function(e) {
- if (e.target === this) {
- closeAppCenter();
- }
- });
- // 筛选应用
- function filterApps() {
- // 获取三个下拉菜单的值
- currentFilters.source = document.getElementById('sourceFilter').value;
- currentFilters.type = document.getElementById('typeFilter').value;
- currentFilters.mode = document.getElementById('modeFilter').value;
- // 重新渲染应用列表
- renderAppGrid();
- }
- // 渲染应用网格
- function renderAppGrid() {
- const appGrid = document.getElementById('appGrid');
-
- // 过滤应用
- let filteredApps = sampleApps.filter(app => {
- if (currentFilters.source !== 'all' && app.source !== currentFilters.source) return false;
- if (currentFilters.type !== 'all' && app.type !== currentFilters.type) return false;
- if (currentFilters.mode !== 'all' && app.mode !== currentFilters.mode) return false;
- return true;
- });
- // 生成HTML
- appGrid.innerHTML = filteredApps.map(app => `
- <div class="app-card ${selectedApps.includes(app.id) ? 'selected' : ''}"
- onclick="toggleAppSelection('${app.id}')">
- <div class="app-cover">
- <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
- <rect x="3" y="3" width="7" height="7" rx="1"/>
- <rect x="14" y="3" width="7" height="7" rx="1"/>
- <rect x="14" y="14" width="7" height="7" rx="1"/>
- <rect x="3" y="14" width="7" height="7" rx="1"/>
- </svg>
- </div>
- <div class="app-info">
- <div class="app-name">${app.name}</div>
- <div class="app-description">${app.description}</div>
- <div class="app-meta">
- <span class="app-tag">${app.type === 'agent' ? '智能体' : '工作流'}</span>
- <span class="app-tag">${app.mode === 'card' ? '卡片式' : app.mode === 'immersive' ? '沉浸式' : '聊天式'}</span>
- </div>
- </div>
- </div>
- `).join('');
- }
- // 切换应用选中状态
- function toggleAppSelection(appId) {
- const index = selectedApps.indexOf(appId);
- if (index > -1) {
- selectedApps.splice(index, 1);
- } else {
- selectedApps.push(appId);
- }
- renderAppGrid();
- updateSelectedCount();
- }
- // 更新已选数量
- function updateSelectedCount() {
- const countText = document.getElementById('selectedCountText');
- const confirmBtn = document.getElementById('confirmAddAppsBtn');
-
- countText.textContent = selectedApps.length;
- confirmBtn.disabled = selectedApps.length === 0;
- }
- // 确认添加应用
- function confirmAddApps() {
- if (selectedApps.length === 0) return;
- const selectedAppNames = sampleApps
- .filter(app => selectedApps.includes(app.id))
- .map(app => app.name)
- .join('、');
- alert(`已添加 ${selectedApps.length} 个应用页面:\n${selectedAppNames}`);
-
- // 这里可以添加实际的页面创建逻辑
- // selectedApps.forEach(appId => {
- // const app = sampleApps.find(a => a.id === appId);
- // addPageFromAIApp(app);
- // });
- closeAppCenter();
- }
- // 打开创建应用方式选择弹窗
- function openCreateAppModal() {
- document.getElementById('createAppMethodModal').classList.add('active');
- }
- // 隐藏创建应用方式选择弹窗
- function hideCreateAppMethodModal() {
- document.getElementById('createAppMethodModal').classList.remove('active');
- }
- // 点击遮罩关闭
- document.getElementById('createAppMethodModal').addEventListener('click', function(e) {
- if (e.target === this) {
- hideCreateAppMethodModal();
- }
- });
- // 选择创建方式
- function selectCreateMethod(method) {
- hideCreateAppMethodModal();
- if (method === 'blank') {
- // 创建空白应用
- alert('创建空白应用页面');
- // 实际应用中应创建包含空白应用画布的新页面
- } else if (method === 'ai') {
- // 打开AI创建界面
- openAICreateInterface();
- }
- }
- // 打开AI创建界面
- function openAICreateInterface() {
- document.getElementById('aiCreateAppModal').classList.add('active');
- initAIChat();
- }
- // 关闭AI创建应用浮窗
- function closeAICreateModal() {
- document.getElementById('aiCreateAppModal').classList.remove('active');
- chatMessages = [];
- }
- // 点击遮罩关闭
- document.getElementById('aiCreateAppModal').addEventListener('click', function(e) {
- if (e.target === this) {
- closeAICreateModal();
- }
- });
- // 初始化AI对话
- function initAIChat() {
- chatMessages = [
- {
- id: 'msg-' + Date.now(),
- role: 'ai',
- content: '您好!我将帮您创建AI应用。请描述您想要的应用功能,例如:"创建一个数学口算练习应用"。'
- }
- ];
- renderChatMessages();
- }
- // 渲染对话消息
- function renderChatMessages() {
- const chatMessagesContainer = document.getElementById('aiChatMessages');
- chatMessagesContainer.innerHTML = chatMessages.map(msg => `
- <div class="ai-chat-message ${msg.role}">
- <div class="chat-avatar">${msg.role === 'ai' ? 'AI' : 'U'}</div>
- <div class="chat-bubble">${msg.content}</div>
- </div>
- `).join('');
-
- // 滚动到底部
- chatMessagesContainer.scrollTop = chatMessagesContainer.scrollHeight;
- }
- // 发送消息给AI
- function sendMessageToAI() {
- const input = document.getElementById('aiChatInput');
- const message = input.value.trim();
-
- if (!message) return;
- // 添加用户消息
- chatMessages.push({
- id: 'msg-' + Date.now(),
- role: 'user',
- content: message
- });
-
- input.value = '';
- renderChatMessages();
- // 模拟AI回复
- setTimeout(() => {
- const aiResponse = generateAIResponse(message);
- chatMessages.push({
- id: 'msg-' + Date.now(),
- role: 'ai',
- content: aiResponse
- });
- renderChatMessages();
-
- // 更新画布预览(模拟)
- updateAppCanvasPreview(message);
- }, 1000);
- }
- // 生成AI回复(模拟)
- function generateAIResponse(userMessage) {
- if (userMessage.includes('数学') || userMessage.includes('计算')) {
- return '好的!我将为您创建一个数学口算练习应用。\n\n应用将包含:\n1. 题目生成器(支持加减乘除)\n2. 答题输入框\n3. 即时反馈\n4. 分数统计\n\n您可以继续描述更多需求,或点击"确认创建"完成。';
- } else if (userMessage.includes('单词') || userMessage.includes('英语')) {
- return '明白了!我将创建一个英语单词学习应用。\n\n功能包括:\n1. 单词卡片展示\n2. 中英文切换\n3. 发音功能\n4. 记忆进度追踪\n\n请告诉我还需要什么功能?';
- } else {
- return '收到您的需求!我正在为您设计应用界面。\n\n您可以继续补充功能要求,或者查看左侧预览区的效果。满意的话,点击"确认创建"即可添加到课件中。';
- }
- }
- // 更新应用画布预览(模拟)
- function updateAppCanvasPreview(userMessage) {
- const canvas = document.getElementById('appCanvasPreview');
-
- if (userMessage.includes('数学') || userMessage.includes('计算')) {
- canvas.innerHTML = `
- <div style="background: white; padding: 20px; border-radius: 12px; width: 80%; text-align: center;">
- <div style="font-size: 32px; font-weight: 600; color: #111827; margin-bottom: 20px;">
- 25 + 17 = ?
- </div>
- <input type="text" style="width: 200px; padding: 12px; border: 2px solid #e5e7eb; border-radius: 10px; font-size: 18px; text-align: center;" placeholder="输入答案">
- <div style="margin-top: 16px;">
- <button style="padding: 10px 30px; background: #285cf5; color: white; border: none; border-radius: 10px; font-size: 14px; font-weight: 600; cursor: pointer;">提交</button>
- </div>
- </div>
- `;
- } else if (userMessage.includes('单词') || userMessage.includes('英语')) {
- canvas.innerHTML = `
- <div style="background: white; padding: 30px; border-radius: 12px; width: 80%; text-align: center;">
- <div style="font-size: 40px; font-weight: 700; color: #111827; margin-bottom: 12px;">
- Apple
- </div>
- <div style="font-size: 18px; color: #6b7280; margin-bottom: 20px;">
- 苹果
- </div>
- <button style="padding: 10px 30px; background: #3b82f6; color: white; border: none; border-radius: 10px; font-size: 14px; font-weight: 600; cursor: pointer;">🔊 发音</button>
- </div>
- `;
- } else {
- canvas.innerHTML = `
- <div style="background: white; padding: 40px; border-radius: 12px; width: 80%; text-align: center; color: #9ca3af;">
- <svg width="64" height="64" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" style="margin-bottom: 12px;">
- <rect x="3" y="3" width="18" height="18" rx="2" ry="2"/>
- <circle cx="8.5" cy="8.5" r="1.5"/>
- <polyline points="21 15 16 10 5 21"/>
- </svg>
- <div style="font-size: 14px;">应用界面预览</div>
- </div>
- `;
- }
- }
- // 确认创建AI应用
- function confirmAICreatedApp() {
- alert('AI应用已创建!\n\n新的应用页面已添加到课件中。');
- closeAICreateModal();
- }
- // 监听AI对话输入框的Enter键
- document.getElementById('aiChatInput').addEventListener('keydown', function(e) {
- if (e.key === 'Enter' && !e.shiftKey) {
- e.preventDefault();
- sendMessageToAI();
- }
- });
- // ========== 交互网页功能 ==========
- let selectedWebs = [];
- let webFilters = { source: 'all', subject: 'all', grade: 'all' };
- let currentWebDetail = null;
- let selectedWebFile = null;
- // 切换交互网页的内部视图 (list, upload, crawl)
- function switchToWebView(viewType) {
- const webListView = document.getElementById('webListView');
- const webConfigView = document.getElementById('webConfigView');
- const uploadWebView = document.getElementById('uploadWebView');
- const crawlWebView = document.getElementById('crawlWebView');
- // 先隐藏所有主视图和配置视图
- webListView.classList.add('hidden');
- webConfigView.classList.add('hidden');
- uploadWebView.classList.add('hidden');
- crawlWebView.classList.add('hidden');
- if (viewType === 'list') {
- webListView.classList.remove('hidden');
- } else if (viewType === 'upload') {
- webConfigView.classList.remove('hidden');
- uploadWebView.classList.remove('hidden');
- } else if (viewType === 'crawl') {
- webConfigView.classList.remove('hidden');
- crawlWebView.classList.remove('hidden');
- }
- }
- // 从配置界面返回列表
- function backToWebList() {
- switchToWebView('list');
- }
-
- // 打开上传网页视图
- function uploadWebPage() {
- switchToWebView('upload');
- resetUploadForm();
- }
- // 打开爬取网页视图
- function crawlWebPage() {
- switchToWebView('crawl');
-
- // 重置表单
- document.getElementById('crawlWebName').value = '';
- document.getElementById('crawlWebUrl').value = '';
- document.getElementById('startCrawlBtn').disabled = true;
-
- updateCrawlStatus('waiting', '等待输入...', '');
- }
- let currentUploadMode = 'file'; // 'file' or 'code'
- function switchUploadTab(mode) {
- currentUploadMode = mode;
- const fileTab = document.getElementById('uploadFileTab');
- const codeTab = document.getElementById('pasteCodeTab');
- const filePanel = document.getElementById('uploadFilePanel');
- const codePanel = document.getElementById('pasteCodePanel');
- if (mode === 'file') {
- fileTab.classList.add('active');
- codeTab.classList.remove('active');
- filePanel.classList.remove('hidden');
- codePanel.classList.add('hidden');
- } else {
- fileTab.classList.remove('active');
- codeTab.classList.add('active');
- filePanel.classList.add('hidden');
- codePanel.classList.remove('hidden');
- }
- validateUploadForm();
- }
- function handleFileSelect(event) {
- const file = event.target.files[0];
- const fileNameDisplay = document.getElementById('fileNameDisplay');
- const fileUploadArea = document.getElementById('fileUploadArea');
- if (!file) {
- fileNameDisplay.textContent = '';
- fileNameDisplay.style.display = 'none';
- fileUploadArea.style.display = 'flex';
- selectedWebFile = null;
- validateUploadForm();
- return;
- }
-
- selectedWebFile = file;
- fileNameDisplay.textContent = '已选择文件: ' + file.name;
- fileNameDisplay.style.display = 'block';
- fileUploadArea.style.display = 'none';
-
- // 自动填充网页名称
- const fileName = file.name.replace(/\.[^/.]+$/, "");
- if (!document.getElementById('uploadWebName').value) {
- document.getElementById('uploadWebName').value = fileName;
- }
-
- // 重新校验按钮状态
- validateUploadForm();
- }
- function validateUploadForm() {
- const webName = document.getElementById('uploadWebName').value.trim();
- const fileInput = document.getElementById('fileInput').files[0];
- const codeInput = document.getElementById('pasteCodeTextarea').value.trim();
- const confirmBtn = document.getElementById('confirmCreateWebBtn');
- let isModeValid = false;
- if (currentUploadMode === 'file') {
- isModeValid = !!fileInput;
- } else { // mode === 'code'
- isModeValid = codeInput.length > 0;
- }
- if (webName.length > 0 && isModeValid) {
- confirmBtn.disabled = false;
- updateUploadButtonStatus('ready', '开始上传', 'ready');
- } else {
- confirmBtn.disabled = true;
- updateUploadButtonStatus('waiting', '等待上传...', '');
- }
- }
- function confirmCreateWebPage() {
- if (currentUploadMode === 'file') {
- startUploadWeb();
- } else {
- startRecognizeCode();
- }
- }
- function startRecognizeCode() {
- const uploadBtn = document.getElementById('confirmCreateWebBtn');
- uploadBtn.disabled = true;
- updateUploadButtonStatus('loading', '解析中...', 'loading');
- const code = document.getElementById('pasteCodeTextarea').value;
- console.log('Recognizing code:', code);
-
- setTimeout(() => {
- updateUploadButtonStatus('success', '解析完成!', 'success');
- // Mock success
- addWebToPage({ name: document.getElementById('uploadWebName').value, type: 'pasted' });
- setTimeout(() => {
- resetUploadForm();
- backToWebList();
- }, 800);
- }, 800);
- }
- // 模拟网页数据
- const sampleWebs = [
- {
- id: 'web_001',
- name: '化学实验-酸碱中和反应',
- source: 'public',
- subject: '化学',
- grade: '初中',
- description: '通过交互式动画展示酸碱中和反应的全过程,学生可以自主调整反应物浓度',
- preview: '',
- url: 'https://example.com/chem-001',
- duration: '10分钟',
- author: '张老师',
- createTime: '2024-01-15'
- },
- {
- id: 'web_002',
- name: '物理模拟-牛顿摆',
- source: 'public',
- subject: '物理',
- grade: '高中',
- description: '3D交互式牛顿摆模拟,可调整球数、质量和初速度',
- preview: '',
- url: 'https://example.com/physics-001',
- duration: '15分钟',
- author: '李老师',
- createTime: '2024-01-10'
- },
- {
- id: 'web_003',
- name: '数学可视化-函数图像',
- source: 'mine',
- subject: '数学',
- grade: '高中',
- description: '动态函数图像绘制工具,支持多种函数类型',
- preview: '',
- url: 'https://example.com/math-001',
- duration: '20分钟',
- author: '我',
- createTime: '2024-01-20'
- },
- {
- id: 'web_004',
- name: '地理探索-地球仪3D',
- source: 'public',
- subject: '地理',
- grade: '初中',
- description: '交互式3D地球仪,可查看地形、气候、人口等多种数据层',
- preview: '',
- url: 'https://example.com/geo-001',
- duration: '12分钟',
- author: '王老师',
- createTime: '2024-01-18'
- },
- {
- id: 'web_005',
- name: '生物模型-细胞结构',
- source: 'public',
- subject: '生物',
- grade: '初中',
- description: '3D细胞结构模型,可放大查看各细胞器的详细结构',
- preview: '',
- url: 'https://example.com/bio-001',
- duration: '8分钟',
- author: '赵老师',
- createTime: '2024-01-12'
- },
- {
- id: 'web_006',
- name: '历史时间轴-中国近代史',
- source: 'mine',
- subject: '历史',
- grade: '高中',
- description: '交互式历史时间轴,包含重要事件、人物和影响',
- preview: '',
- url: 'https://example.com/history-001',
- duration: '25分钟',
- author: '我',
- createTime: '2024-01-22'
- }
- ];
- // ========== 网页中心功能 ==========
- // 打开网页中心
- function openWebCenter() {
- document.getElementById('webCenterModal').classList.add('active');
- renderWebGrid();
- }
- // 关闭网页中心
- function closeWebCenter() {
- document.getElementById('webCenterModal').classList.remove('active');
- selectedWebs = [];
- updateWebSelectedCount();
- }
- // 点击遮罩关闭
- document.getElementById('webCenterModal').addEventListener('click', function(e) {
- if (e.target === this) {
- closeWebCenter();
- }
- });
- // 筛选网页
- function filterWebs() {
- webFilters.source = document.getElementById('webSourceFilter').value;
- webFilters.subject = document.getElementById('webSubjectFilter').value;
- webFilters.grade = document.getElementById('webGradeFilter').value;
- renderWebGrid();
- }
- // 渲染网页网格
- function renderWebGrid() {
- const webGrid = document.getElementById('webGrid');
-
- // 过滤网页
- let filteredWebs = sampleWebs.filter(web => {
- if (webFilters.source !== 'all' && web.source !== webFilters.source) return false;
- if (webFilters.subject !== 'all' && web.subject !== webFilters.subject) return false;
- if (webFilters.grade !== 'all' && web.grade !== webFilters.grade) return false;
- return true;
- });
- // 生成HTML
- webGrid.innerHTML = filteredWebs.map(web => `
- <div class="web-card ${selectedWebs.includes(web.id) ? 'selected' : ''}"
- onclick="toggleWebSelection('${web.id}')">
- <div class="web-preview">
- <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
- <rect x="2" y="3" width="20" height="14" rx="2"/>
- <line x1="2" y1="7" x2="22" y2="7"/>
- <path d="M8 11h8M8 14h5"/>
- </svg>
- </div>
- <div class="web-info">
- <div class="web-name">${web.name}</div>
- <div class="web-description">${web.description}</div>
- <div class="web-meta">
- <span class="web-tag">${web.subject}</span>
- <span class="web-tag">${web.grade}</span>
- <span class="web-tag">${web.duration}</span>
- </div>
- </div>
- </div>
- `).join('');
- }
- // 切换网页选中状态
- function toggleWebSelection(webId) {
- const index = selectedWebs.indexOf(webId);
- if (index > -1) {
- selectedWebs.splice(index, 1);
- } else {
- selectedWebs.push(webId);
- }
- renderWebGrid();
- updateWebSelectedCount();
- }
- // 更新选中计数
- function updateWebSelectedCount() {
- const countText = document.getElementById('webSelectedCountText');
- const confirmBtn = document.getElementById('confirmAddWebsBtn');
-
- countText.textContent = selectedWebs.length;
- confirmBtn.disabled = selectedWebs.length === 0;
- }
- // 确认添加网页
- function confirmAddWebs() {
- if (selectedWebs.length === 0) return;
-
- console.log('添加网页:', selectedWebs);
- alert(`已添加 ${selectedWebs.length} 个网页到课件`);
-
- // 这里应该实际创建新页面并插入网页内容
- // 暂时模拟功能
- closeWebCenter();
- }
- // 打开网页详情
- function openWebDetail(webId) {
- const web = sampleWebs.find(w => w.id === webId);
- if (!web) return;
-
- currentWebDetail = web;
-
- // 填充详情信息
- document.getElementById('webDetailName').textContent = web.name;
- document.getElementById('webDetailSubject').textContent = web.subject;
- document.getElementById('webDetailGrade').textContent = web.grade;
- document.getElementById('webDetailDuration').textContent = web.duration;
- document.getElementById('webDetailDescription').textContent = web.description;
- document.getElementById('webDetailAuthor').textContent = web.author;
- document.getElementById('webDetailTime').textContent = web.createTime;
- document.getElementById('webDetailPreviewTitle').textContent = web.name;
- document.getElementById('webDetailIframe').src = web.url;
-
- // 显示详情浮窗
- document.getElementById('webDetailModal').classList.add('active');
- }
- // 关闭网页详情
- function closeWebDetail() {
- document.getElementById('webDetailModal').classList.remove('active');
- document.getElementById('webDetailIframe').src = '';
- currentWebDetail = null;
- }
- // 点击遮罩关闭详情
- document.getElementById('webDetailModal').addEventListener('click', function(e) {
- if (e.target === this) {
- closeWebDetail();
- }
- });
- // 切换预览全屏
- function toggleWebPreviewFullscreen() {
- const iframe = document.getElementById('webDetailIframe');
- if (iframe.requestFullscreen) {
- iframe.requestFullscreen();
- } else if (iframe.webkitRequestFullscreen) {
- iframe.webkitRequestFullscreen();
- } else if (iframe.mozRequestFullScreen) {
- iframe.mozRequestFullScreen();
- } else if (iframe.msRequestFullscreen) {
- iframe.msRequestFullscreen();
- }
- }
- // 从详情页添加网页
- function addWebFromDetail() {
- if (!currentWebDetail) return;
-
- console.log('从详情页添加网页:', currentWebDetail);
- alert(`已添加"${currentWebDetail.name}"到课件`);
-
- // 这里应该实际创建新页面并插入网页内容
- closeWebDetail();
- closeWebCenter();
- }
- // ========== 上传网页功能 ==========
- // 开始上传网页
- function startUploadWeb() {
- const webName = document.getElementById('uploadWebName').value.trim();
-
- if (!webName) {
- alert('请输入网页名称');
- return;
- }
-
- if (!selectedWebFile) {
- alert('请选择文件');
- return;
- }
-
- // 显示解析中状态
- const uploadBtn = document.getElementById('confirmCreateWebBtn');
- uploadBtn.disabled = true;
- updateUploadButtonStatus('loading', '解析中...', 'loading');
-
- // 模拟解析过程
- setTimeout(() => {
- updateUploadButtonStatus('success', '解析完成!', 'success');
-
- // 2秒后创建页面并显示内容
- setTimeout(() => {
- console.log('创建新页面并插入网页内容:', webName);
- alert(`网页"${webName}"已成功添加到课件`);
- resetUploadForm();
- returnFromUpload();
- }, 2000);
- }, 2000);
- }
- function resetUploadForm() {
- const uploadBtn = document.getElementById('confirmCreateWebBtn');
- document.getElementById('uploadWebName').value = '';
- document.getElementById('fileInput').value = '';
- document.getElementById('pasteCodeTextarea').value = '';
- const fileNameDisplay = document.getElementById('fileNameDisplay');
- const fileUploadArea = document.getElementById('fileUploadArea');
- fileNameDisplay.textContent = '';
- fileNameDisplay.style.display = 'none';
- fileUploadArea.style.display = 'flex';
- selectedWebFile = null;
- uploadBtn.disabled = true;
- updateUploadButtonStatus('waiting', '等待上传...', '');
- }
- // 返回列表视图
- function returnFromUpload() {
- switchToWebView('list');
- }
- // 更新上传按钮状态
- function updateUploadButtonStatus(statusKey, text, className) {
- const uploadBtn = document.getElementById('confirmCreateWebBtn');
- const statusIcon = document.getElementById('uploadStatusIcon');
- const statusText = document.getElementById('uploadStatusText');
- uploadBtn.classList.remove('status-loading', 'status-success', 'status-error', 'status-ready');
- if (className) {
- uploadBtn.classList.add(`status-${className}`);
- }
- statusIcon.innerHTML = statusIcons[statusKey] || statusIcons.waiting;
- statusText.textContent = text;
- }
- // 文件拖拽上传
- const fileUploadArea = document.getElementById('fileUploadArea');
-
- fileUploadArea.addEventListener('dragover', (e) => {
- e.preventDefault();
- fileUploadArea.classList.add('dragover');
- });
-
- fileUploadArea.addEventListener('dragleave', () => {
- fileUploadArea.classList.remove('dragover');
- });
-
- fileUploadArea.addEventListener('drop', (e) => {
- e.preventDefault();
- fileUploadArea.classList.remove('dragover');
-
- const files = e.dataTransfer.files;
- if (files.length > 0) {
- const file = files[0];
- // 检查文件类型
- const validTypes = ['.html', '.htm', '.zip'];
- const fileExt = '.' + file.name.split('.').pop().toLowerCase();
-
- if (validTypes.includes(fileExt)) {
- document.getElementById('fileInput').files = files;
- handleFileSelect({ target: { files: files } });
- } else {
- alert('仅支持 HTML、HTM、ZIP 格式文件');
- }
- }
- });
- // ========== 交互网页按钮状态图标 ==========
- const statusIcons = {
- waiting: '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="12" cy="12" r="9"></circle><path d="M12 7v5l3 2"></path></svg>',
- ready: '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><polygon points="10 7 17 12 10 17 10 7"></polygon><line x1="7" y1="7" x2="7" y2="17"></line></svg>',
- loading: '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="12" cy="12" r="9" stroke-dasharray="56" stroke-dashoffset="20"></circle></svg>',
- success: '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M5 13l4 4L19 7"></path></svg>',
- error: '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="12" cy="12" r="9"></circle><line x1="9" y1="9" x2="15" y2="15"></line><line x1="15" y1="9" x2="9" y2="15"></line></svg>'
- };
- // 验证爬取URL
- function validateCrawlUrl() {
- const url = document.getElementById('crawlWebUrl').value.trim();
- const name = document.getElementById('crawlWebName').value.trim();
- const startBtn = document.getElementById('startCrawlBtn');
-
- if (url && name && isValidUrl(url)) {
- startBtn.disabled = false;
- updateCrawlStatus('ready', '开始爬取', 'ready');
- } else if (url && !isValidUrl(url)) {
- startBtn.disabled = true;
- updateCrawlStatus('error', 'URL格式不正确,请检查', 'error');
- } else {
- startBtn.disabled = true;
- updateCrawlStatus('waiting', '等待输入...', '');
- }
- }
- // 验证URL格式
- function isValidUrl(string) {
- try {
- const url = new URL(string);
- return url.protocol === 'http:' || url.protocol === 'https:';
- } catch (_) {
- return false;
- }
- }
- // 开始爬取网页
- function startCrawlWeb() {
- const webName = document.getElementById('crawlWebName').value.trim();
- const webUrl = document.getElementById('crawlWebUrl').value.trim();
- const startBtn = document.getElementById('startCrawlBtn');
-
- if (!webName) {
- alert('请输入网页名称');
- return;
- }
-
- if (!webUrl || !isValidUrl(webUrl)) {
- alert('请输入有效的网页链接');
- return;
- }
-
- // 显示爬取中状态
- startBtn.disabled = true;
- updateCrawlStatus('loading', '爬取中...', 'loading');
-
- // 模拟爬取过程
- setTimeout(() => {
- updateCrawlStatus('success', '爬取完成!', 'success');
-
- setTimeout(() => {
- resetCrawlForm();
- }, 1200);
- }, 2500);
- }
- function resetCrawlForm() {
- const startBtn = document.getElementById('startCrawlBtn');
- const nameInput = document.getElementById('crawlWebName');
- const urlInput = document.getElementById('crawlWebUrl');
- nameInput.value = '';
- urlInput.value = '';
- startBtn.disabled = true;
- updateCrawlStatus('waiting', '等待输入...', '');
- }
- // 返回列表视图(保留,未使用)
- function returnFromCrawl() {
- switchToWebView('list');
- }
- // 更新爬取状态
- function updateCrawlStatus(statusKey, text, className) {
- const startBtn = document.getElementById('startCrawlBtn');
- const statusIcon = document.getElementById('crawlStatusIcon');
- const statusText = document.getElementById('crawlStatusText');
-
- startBtn.classList.remove('status-loading', 'status-success', 'status-error', 'status-ready');
- if (className) {
- startBtn.classList.add(`status-${className}`);
- }
-
- statusIcon.innerHTML = statusIcons[statusKey] || statusIcons.waiting;
- statusText.textContent = text;
- }
- </script>
- </body>
- </html>
|