Interactive_Courseware_without_ToolAI_theme_blue.html 389 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694169516961697169816991700170117021703170417051706170717081709171017111712171317141715171617171718171917201721172217231724172517261727172817291730173117321733173417351736173717381739174017411742174317441745174617471748174917501751175217531754175517561757175817591760176117621763176417651766176717681769177017711772177317741775177617771778177917801781178217831784178517861787178817891790179117921793179417951796179717981799180018011802180318041805180618071808180918101811181218131814181518161817181818191820182118221823182418251826182718281829183018311832183318341835183618371838183918401841184218431844184518461847184818491850185118521853185418551856185718581859186018611862186318641865186618671868186918701871187218731874187518761877187818791880188118821883188418851886188718881889189018911892189318941895189618971898189919001901190219031904190519061907190819091910191119121913191419151916191719181919192019211922192319241925192619271928192919301931193219331934193519361937193819391940194119421943194419451946194719481949195019511952195319541955195619571958195919601961196219631964196519661967196819691970197119721973197419751976197719781979198019811982198319841985198619871988198919901991199219931994199519961997199819992000200120022003200420052006200720082009201020112012201320142015201620172018201920202021202220232024202520262027202820292030203120322033203420352036203720382039204020412042204320442045204620472048204920502051205220532054205520562057205820592060206120622063206420652066206720682069207020712072207320742075207620772078207920802081208220832084208520862087208820892090209120922093209420952096209720982099210021012102210321042105210621072108210921102111211221132114211521162117211821192120212121222123212421252126212721282129213021312132213321342135213621372138213921402141214221432144214521462147214821492150215121522153215421552156215721582159216021612162216321642165216621672168216921702171217221732174217521762177217821792180218121822183218421852186218721882189219021912192219321942195219621972198219922002201220222032204220522062207220822092210221122122213221422152216221722182219222022212222222322242225222622272228222922302231223222332234223522362237223822392240224122422243224422452246224722482249225022512252225322542255225622572258225922602261226222632264226522662267226822692270227122722273227422752276227722782279228022812282228322842285228622872288228922902291229222932294229522962297229822992300230123022303230423052306230723082309231023112312231323142315231623172318231923202321232223232324232523262327232823292330233123322333233423352336233723382339234023412342234323442345234623472348234923502351235223532354235523562357235823592360236123622363236423652366236723682369237023712372237323742375237623772378237923802381238223832384238523862387238823892390239123922393239423952396239723982399240024012402240324042405240624072408240924102411241224132414241524162417241824192420242124222423242424252426242724282429243024312432243324342435243624372438243924402441244224432444244524462447244824492450245124522453245424552456245724582459246024612462246324642465246624672468246924702471247224732474247524762477247824792480248124822483248424852486248724882489249024912492249324942495249624972498249925002501250225032504250525062507250825092510251125122513251425152516251725182519252025212522252325242525252625272528252925302531253225332534253525362537253825392540254125422543254425452546254725482549255025512552255325542555255625572558255925602561256225632564256525662567256825692570257125722573257425752576257725782579258025812582258325842585258625872588258925902591259225932594259525962597259825992600260126022603260426052606260726082609261026112612261326142615261626172618261926202621262226232624262526262627262826292630263126322633263426352636263726382639264026412642264326442645264626472648264926502651265226532654265526562657265826592660266126622663266426652666266726682669267026712672267326742675267626772678267926802681268226832684268526862687268826892690269126922693269426952696269726982699270027012702270327042705270627072708270927102711271227132714271527162717271827192720272127222723272427252726272727282729273027312732273327342735273627372738273927402741274227432744274527462747274827492750275127522753275427552756275727582759276027612762276327642765276627672768276927702771277227732774277527762777277827792780278127822783278427852786278727882789279027912792279327942795279627972798279928002801280228032804280528062807280828092810281128122813281428152816281728182819282028212822282328242825282628272828282928302831283228332834283528362837283828392840284128422843284428452846284728482849285028512852285328542855285628572858285928602861286228632864286528662867286828692870287128722873287428752876287728782879288028812882288328842885288628872888288928902891289228932894289528962897289828992900290129022903290429052906290729082909291029112912291329142915291629172918291929202921292229232924292529262927292829292930293129322933293429352936293729382939294029412942294329442945294629472948294929502951295229532954295529562957295829592960296129622963296429652966296729682969297029712972297329742975297629772978297929802981298229832984298529862987298829892990299129922993299429952996299729982999300030013002300330043005300630073008300930103011301230133014301530163017301830193020302130223023302430253026302730283029303030313032303330343035303630373038303930403041304230433044304530463047304830493050305130523053305430553056305730583059306030613062306330643065306630673068306930703071307230733074307530763077307830793080308130823083308430853086308730883089309030913092309330943095309630973098309931003101310231033104310531063107310831093110311131123113311431153116311731183119312031213122312331243125312631273128312931303131313231333134313531363137313831393140314131423143314431453146314731483149315031513152315331543155315631573158315931603161316231633164316531663167316831693170317131723173317431753176317731783179318031813182318331843185318631873188318931903191319231933194319531963197319831993200320132023203320432053206320732083209321032113212321332143215321632173218321932203221322232233224322532263227322832293230323132323233323432353236323732383239324032413242324332443245324632473248324932503251325232533254325532563257325832593260326132623263326432653266326732683269327032713272327332743275327632773278327932803281328232833284328532863287328832893290329132923293329432953296329732983299330033013302330333043305330633073308330933103311331233133314331533163317331833193320332133223323332433253326332733283329333033313332333333343335333633373338333933403341334233433344334533463347334833493350335133523353335433553356335733583359336033613362336333643365336633673368336933703371337233733374337533763377337833793380338133823383338433853386338733883389339033913392339333943395339633973398339934003401340234033404340534063407340834093410341134123413341434153416341734183419342034213422342334243425342634273428342934303431343234333434343534363437343834393440344134423443344434453446344734483449345034513452345334543455345634573458345934603461346234633464346534663467346834693470347134723473347434753476347734783479348034813482348334843485348634873488348934903491349234933494349534963497349834993500350135023503350435053506350735083509351035113512351335143515351635173518351935203521352235233524352535263527352835293530353135323533353435353536353735383539354035413542354335443545354635473548354935503551355235533554355535563557355835593560356135623563356435653566356735683569357035713572357335743575357635773578357935803581358235833584358535863587358835893590359135923593359435953596359735983599360036013602360336043605360636073608360936103611361236133614361536163617361836193620362136223623362436253626362736283629363036313632363336343635363636373638363936403641364236433644364536463647364836493650365136523653365436553656365736583659366036613662366336643665366636673668366936703671367236733674367536763677367836793680368136823683368436853686368736883689369036913692369336943695369636973698369937003701370237033704370537063707370837093710371137123713371437153716371737183719372037213722372337243725372637273728372937303731373237333734373537363737373837393740374137423743374437453746374737483749375037513752375337543755375637573758375937603761376237633764376537663767376837693770377137723773377437753776377737783779378037813782378337843785378637873788378937903791379237933794379537963797379837993800380138023803380438053806380738083809381038113812381338143815381638173818381938203821382238233824382538263827382838293830383138323833383438353836383738383839384038413842384338443845384638473848384938503851385238533854385538563857385838593860386138623863386438653866386738683869387038713872387338743875387638773878387938803881388238833884388538863887388838893890389138923893389438953896389738983899390039013902390339043905390639073908390939103911391239133914391539163917391839193920392139223923392439253926392739283929393039313932393339343935393639373938393939403941394239433944394539463947394839493950395139523953395439553956395739583959396039613962396339643965396639673968396939703971397239733974397539763977397839793980398139823983398439853986398739883989399039913992399339943995399639973998399940004001400240034004400540064007400840094010401140124013401440154016401740184019402040214022402340244025402640274028402940304031403240334034403540364037403840394040404140424043404440454046404740484049405040514052405340544055405640574058405940604061406240634064406540664067406840694070407140724073407440754076407740784079408040814082408340844085408640874088408940904091409240934094409540964097409840994100410141024103410441054106410741084109411041114112411341144115411641174118411941204121412241234124412541264127412841294130413141324133413441354136413741384139414041414142414341444145414641474148414941504151415241534154415541564157415841594160416141624163416441654166416741684169417041714172417341744175417641774178417941804181418241834184418541864187418841894190419141924193419441954196419741984199420042014202420342044205420642074208420942104211421242134214421542164217421842194220422142224223422442254226422742284229423042314232423342344235423642374238423942404241424242434244424542464247424842494250425142524253425442554256425742584259426042614262426342644265426642674268426942704271427242734274427542764277427842794280428142824283428442854286428742884289429042914292429342944295429642974298429943004301430243034304430543064307430843094310431143124313431443154316431743184319432043214322432343244325432643274328432943304331433243334334433543364337433843394340434143424343434443454346434743484349435043514352435343544355435643574358435943604361436243634364436543664367436843694370437143724373437443754376437743784379438043814382438343844385438643874388438943904391439243934394439543964397439843994400440144024403440444054406440744084409441044114412441344144415441644174418441944204421442244234424442544264427442844294430443144324433443444354436443744384439444044414442444344444445444644474448444944504451445244534454445544564457445844594460446144624463446444654466446744684469447044714472447344744475447644774478447944804481448244834484448544864487448844894490449144924493449444954496449744984499450045014502450345044505450645074508450945104511451245134514451545164517451845194520452145224523452445254526452745284529453045314532453345344535453645374538453945404541454245434544454545464547454845494550455145524553455445554556455745584559456045614562456345644565456645674568456945704571457245734574457545764577457845794580458145824583458445854586458745884589459045914592459345944595459645974598459946004601460246034604460546064607460846094610461146124613461446154616461746184619462046214622462346244625462646274628462946304631463246334634463546364637463846394640464146424643464446454646464746484649465046514652465346544655465646574658465946604661466246634664466546664667466846694670467146724673467446754676467746784679468046814682468346844685468646874688468946904691469246934694469546964697469846994700470147024703470447054706470747084709471047114712471347144715471647174718471947204721472247234724472547264727472847294730473147324733473447354736473747384739474047414742474347444745474647474748474947504751475247534754475547564757475847594760476147624763476447654766476747684769477047714772477347744775477647774778477947804781478247834784478547864787478847894790479147924793479447954796479747984799480048014802480348044805480648074808480948104811481248134814481548164817481848194820482148224823482448254826482748284829483048314832483348344835483648374838483948404841484248434844484548464847484848494850485148524853485448554856485748584859486048614862486348644865486648674868486948704871487248734874487548764877487848794880488148824883488448854886488748884889489048914892489348944895489648974898489949004901490249034904490549064907490849094910491149124913491449154916491749184919492049214922492349244925492649274928492949304931493249334934493549364937493849394940494149424943494449454946494749484949495049514952495349544955495649574958495949604961496249634964496549664967496849694970497149724973497449754976497749784979498049814982498349844985498649874988498949904991499249934994499549964997499849995000500150025003500450055006500750085009501050115012501350145015501650175018501950205021502250235024502550265027502850295030503150325033503450355036503750385039504050415042504350445045504650475048504950505051505250535054505550565057505850595060506150625063506450655066506750685069507050715072507350745075507650775078507950805081508250835084508550865087508850895090509150925093509450955096509750985099510051015102510351045105510651075108510951105111511251135114511551165117511851195120512151225123512451255126512751285129513051315132513351345135513651375138513951405141514251435144514551465147514851495150515151525153515451555156515751585159516051615162516351645165516651675168516951705171517251735174517551765177517851795180518151825183518451855186518751885189519051915192519351945195519651975198519952005201520252035204520552065207520852095210521152125213521452155216521752185219522052215222522352245225522652275228522952305231523252335234523552365237523852395240524152425243524452455246524752485249525052515252525352545255525652575258525952605261526252635264526552665267526852695270527152725273527452755276527752785279528052815282528352845285528652875288528952905291529252935294529552965297529852995300530153025303530453055306530753085309531053115312531353145315531653175318531953205321532253235324532553265327532853295330533153325333533453355336533753385339534053415342534353445345534653475348534953505351535253535354535553565357535853595360536153625363536453655366536753685369537053715372537353745375537653775378537953805381538253835384538553865387538853895390539153925393539453955396539753985399540054015402540354045405540654075408540954105411541254135414541554165417541854195420542154225423542454255426542754285429543054315432543354345435543654375438543954405441544254435444544554465447544854495450545154525453545454555456545754585459546054615462546354645465546654675468546954705471547254735474547554765477547854795480548154825483548454855486548754885489549054915492549354945495549654975498549955005501550255035504550555065507550855095510551155125513551455155516551755185519552055215522552355245525552655275528552955305531553255335534553555365537553855395540554155425543554455455546554755485549555055515552555355545555555655575558555955605561556255635564556555665567556855695570557155725573557455755576557755785579558055815582558355845585558655875588558955905591559255935594559555965597559855995600560156025603560456055606560756085609561056115612561356145615561656175618561956205621562256235624562556265627562856295630563156325633563456355636563756385639564056415642564356445645564656475648564956505651565256535654565556565657565856595660566156625663566456655666566756685669567056715672567356745675567656775678567956805681568256835684568556865687568856895690569156925693569456955696569756985699570057015702570357045705570657075708570957105711571257135714571557165717571857195720572157225723572457255726572757285729573057315732573357345735573657375738573957405741574257435744574557465747574857495750575157525753575457555756575757585759576057615762576357645765576657675768576957705771577257735774577557765777577857795780578157825783578457855786578757885789579057915792579357945795579657975798579958005801580258035804580558065807580858095810581158125813581458155816581758185819582058215822582358245825582658275828582958305831583258335834583558365837583858395840584158425843584458455846584758485849585058515852585358545855585658575858585958605861586258635864586558665867586858695870587158725873587458755876587758785879588058815882588358845885588658875888588958905891589258935894589558965897589858995900590159025903590459055906590759085909591059115912591359145915591659175918591959205921592259235924592559265927592859295930593159325933593459355936593759385939594059415942594359445945594659475948594959505951595259535954595559565957595859595960596159625963596459655966596759685969597059715972597359745975597659775978597959805981598259835984598559865987598859895990599159925993599459955996599759985999600060016002600360046005600660076008600960106011601260136014601560166017601860196020602160226023602460256026602760286029603060316032603360346035603660376038603960406041604260436044604560466047604860496050605160526053605460556056605760586059606060616062606360646065606660676068606960706071607260736074607560766077607860796080608160826083608460856086608760886089609060916092609360946095609660976098609961006101610261036104610561066107610861096110611161126113611461156116611761186119612061216122612361246125612661276128612961306131613261336134613561366137613861396140614161426143614461456146614761486149615061516152615361546155615661576158615961606161616261636164616561666167616861696170617161726173617461756176617761786179618061816182618361846185618661876188618961906191619261936194619561966197619861996200620162026203620462056206620762086209621062116212621362146215621662176218621962206221622262236224622562266227622862296230623162326233623462356236623762386239624062416242624362446245624662476248624962506251625262536254625562566257625862596260626162626263626462656266626762686269627062716272627362746275627662776278627962806281628262836284628562866287628862896290629162926293629462956296629762986299630063016302630363046305630663076308630963106311631263136314631563166317631863196320632163226323632463256326632763286329633063316332633363346335633663376338633963406341634263436344634563466347634863496350635163526353635463556356635763586359636063616362636363646365636663676368636963706371637263736374637563766377637863796380638163826383638463856386638763886389639063916392639363946395639663976398639964006401640264036404640564066407640864096410641164126413641464156416641764186419642064216422642364246425642664276428642964306431643264336434643564366437643864396440644164426443644464456446644764486449645064516452645364546455645664576458645964606461646264636464646564666467646864696470647164726473647464756476647764786479648064816482648364846485648664876488648964906491649264936494649564966497649864996500650165026503650465056506650765086509651065116512651365146515651665176518651965206521652265236524652565266527652865296530653165326533653465356536653765386539654065416542654365446545654665476548654965506551655265536554655565566557655865596560656165626563656465656566656765686569657065716572657365746575657665776578657965806581658265836584658565866587658865896590659165926593659465956596659765986599660066016602660366046605660666076608660966106611661266136614661566166617661866196620662166226623662466256626662766286629663066316632663366346635663666376638663966406641664266436644664566466647664866496650665166526653665466556656665766586659666066616662666366646665666666676668666966706671667266736674667566766677667866796680668166826683668466856686668766886689669066916692669366946695669666976698669967006701670267036704670567066707670867096710671167126713671467156716671767186719672067216722672367246725672667276728672967306731673267336734673567366737673867396740674167426743674467456746674767486749675067516752675367546755675667576758675967606761676267636764676567666767676867696770677167726773677467756776677767786779678067816782678367846785678667876788678967906791679267936794679567966797679867996800680168026803680468056806680768086809681068116812681368146815681668176818681968206821682268236824682568266827682868296830683168326833683468356836683768386839684068416842684368446845684668476848684968506851685268536854685568566857685868596860686168626863686468656866686768686869687068716872687368746875687668776878687968806881688268836884688568866887688868896890689168926893689468956896689768986899690069016902690369046905690669076908690969106911691269136914691569166917691869196920692169226923692469256926692769286929693069316932693369346935693669376938693969406941694269436944694569466947694869496950695169526953695469556956695769586959696069616962696369646965696669676968696969706971697269736974697569766977697869796980698169826983698469856986698769886989699069916992699369946995699669976998699970007001700270037004700570067007700870097010701170127013701470157016701770187019702070217022702370247025702670277028702970307031703270337034703570367037703870397040704170427043704470457046704770487049705070517052705370547055705670577058705970607061706270637064706570667067706870697070707170727073707470757076707770787079708070817082708370847085708670877088708970907091709270937094709570967097709870997100710171027103710471057106710771087109711071117112711371147115711671177118711971207121712271237124712571267127712871297130713171327133713471357136713771387139714071417142714371447145714671477148714971507151715271537154715571567157715871597160716171627163716471657166716771687169717071717172717371747175717671777178717971807181718271837184718571867187718871897190719171927193719471957196719771987199720072017202720372047205720672077208720972107211721272137214721572167217721872197220722172227223722472257226722772287229723072317232723372347235723672377238723972407241724272437244724572467247724872497250725172527253725472557256725772587259726072617262726372647265726672677268726972707271727272737274727572767277727872797280728172827283728472857286728772887289729072917292729372947295729672977298729973007301730273037304730573067307730873097310731173127313731473157316731773187319732073217322732373247325732673277328732973307331733273337334733573367337733873397340734173427343734473457346734773487349735073517352735373547355735673577358735973607361736273637364736573667367736873697370737173727373737473757376737773787379738073817382738373847385738673877388738973907391739273937394739573967397739873997400740174027403740474057406740774087409741074117412741374147415741674177418741974207421742274237424742574267427742874297430743174327433743474357436743774387439744074417442744374447445744674477448744974507451745274537454745574567457745874597460746174627463746474657466746774687469747074717472747374747475747674777478747974807481748274837484748574867487748874897490749174927493749474957496749774987499750075017502750375047505750675077508750975107511751275137514751575167517751875197520752175227523752475257526752775287529753075317532753375347535753675377538753975407541754275437544754575467547754875497550755175527553755475557556755775587559756075617562756375647565756675677568756975707571757275737574757575767577757875797580758175827583758475857586758775887589759075917592759375947595759675977598759976007601760276037604760576067607760876097610761176127613761476157616761776187619762076217622762376247625762676277628762976307631763276337634763576367637763876397640764176427643764476457646764776487649765076517652765376547655765676577658765976607661766276637664766576667667766876697670767176727673767476757676767776787679768076817682768376847685768676877688768976907691769276937694769576967697769876997700770177027703770477057706770777087709771077117712771377147715771677177718771977207721772277237724772577267727772877297730773177327733773477357736773777387739774077417742774377447745774677477748774977507751775277537754775577567757775877597760776177627763776477657766776777687769777077717772777377747775777677777778777977807781778277837784778577867787778877897790779177927793779477957796779777987799780078017802780378047805780678077808780978107811781278137814781578167817781878197820782178227823782478257826782778287829783078317832783378347835783678377838783978407841784278437844784578467847784878497850785178527853785478557856785778587859786078617862786378647865786678677868786978707871787278737874787578767877787878797880788178827883788478857886788778887889789078917892789378947895789678977898789979007901790279037904790579067907790879097910791179127913791479157916791779187919792079217922792379247925792679277928792979307931793279337934793579367937793879397940794179427943794479457946794779487949795079517952795379547955795679577958795979607961796279637964796579667967796879697970797179727973797479757976797779787979798079817982798379847985798679877988798979907991799279937994799579967997799879998000800180028003800480058006800780088009801080118012801380148015801680178018801980208021802280238024802580268027802880298030803180328033803480358036803780388039804080418042804380448045804680478048804980508051805280538054805580568057805880598060806180628063806480658066806780688069807080718072807380748075807680778078807980808081808280838084808580868087808880898090809180928093809480958096809780988099810081018102810381048105810681078108810981108111811281138114811581168117811881198120812181228123812481258126812781288129813081318132813381348135813681378138813981408141814281438144814581468147814881498150815181528153815481558156815781588159816081618162816381648165816681678168816981708171817281738174817581768177817881798180818181828183818481858186818781888189819081918192819381948195819681978198819982008201820282038204820582068207820882098210821182128213821482158216821782188219822082218222822382248225822682278228822982308231823282338234823582368237823882398240824182428243824482458246824782488249825082518252825382548255825682578258825982608261826282638264826582668267826882698270827182728273827482758276827782788279828082818282828382848285828682878288828982908291829282938294829582968297829882998300830183028303830483058306830783088309831083118312831383148315831683178318831983208321832283238324832583268327832883298330833183328333833483358336833783388339834083418342834383448345834683478348834983508351835283538354835583568357835883598360836183628363836483658366836783688369837083718372837383748375837683778378837983808381838283838384838583868387838883898390839183928393839483958396839783988399840084018402840384048405840684078408840984108411841284138414841584168417841884198420842184228423842484258426842784288429843084318432843384348435843684378438843984408441844284438444844584468447844884498450845184528453845484558456845784588459846084618462846384648465846684678468846984708471847284738474847584768477847884798480848184828483848484858486848784888489849084918492849384948495849684978498849985008501850285038504850585068507850885098510851185128513851485158516851785188519852085218522852385248525852685278528852985308531853285338534853585368537853885398540854185428543854485458546854785488549855085518552855385548555855685578558855985608561856285638564856585668567856885698570857185728573857485758576857785788579858085818582858385848585858685878588858985908591859285938594859585968597859885998600860186028603860486058606860786088609861086118612861386148615861686178618861986208621862286238624862586268627862886298630863186328633863486358636863786388639864086418642864386448645864686478648864986508651865286538654865586568657865886598660866186628663866486658666866786688669867086718672867386748675867686778678867986808681868286838684868586868687868886898690869186928693869486958696869786988699870087018702870387048705870687078708870987108711871287138714871587168717871887198720872187228723872487258726872787288729873087318732873387348735873687378738873987408741874287438744874587468747874887498750875187528753875487558756875787588759876087618762876387648765876687678768876987708771877287738774877587768777877887798780878187828783878487858786878787888789879087918792879387948795879687978798879988008801880288038804880588068807880888098810881188128813881488158816881788188819882088218822882388248825882688278828882988308831883288338834883588368837883888398840884188428843884488458846884788488849885088518852885388548855885688578858885988608861886288638864886588668867886888698870887188728873887488758876887788788879888088818882888388848885888688878888888988908891889288938894889588968897889888998900890189028903890489058906890789088909891089118912891389148915891689178918891989208921892289238924892589268927892889298930893189328933893489358936893789388939894089418942894389448945894689478948894989508951895289538954895589568957895889598960896189628963896489658966896789688969897089718972897389748975897689778978897989808981898289838984898589868987898889898990899189928993899489958996899789988999900090019002900390049005900690079008900990109011901290139014901590169017901890199020902190229023902490259026902790289029903090319032903390349035903690379038903990409041904290439044904590469047904890499050905190529053905490559056905790589059906090619062906390649065906690679068906990709071907290739074907590769077907890799080908190829083908490859086908790889089909090919092909390949095909690979098909991009101910291039104910591069107910891099110911191129113911491159116911791189119912091219122912391249125912691279128912991309131913291339134913591369137913891399140914191429143914491459146914791489149915091519152915391549155915691579158915991609161916291639164916591669167916891699170917191729173917491759176917791789179918091819182918391849185918691879188918991909191919291939194919591969197919891999200920192029203920492059206920792089209921092119212921392149215921692179218921992209221922292239224922592269227922892299230923192329233923492359236923792389239924092419242924392449245924692479248924992509251925292539254925592569257925892599260926192629263926492659266926792689269927092719272927392749275927692779278927992809281928292839284928592869287928892899290929192929293929492959296929792989299930093019302930393049305930693079308930993109311931293139314931593169317931893199320932193229323932493259326932793289329933093319332933393349335933693379338933993409341934293439344934593469347934893499350935193529353935493559356935793589359936093619362936393649365936693679368936993709371937293739374937593769377937893799380938193829383938493859386938793889389939093919392939393949395939693979398939994009401940294039404940594069407940894099410941194129413941494159416941794189419942094219422942394249425942694279428942994309431943294339434943594369437943894399440944194429443944494459446944794489449945094519452945394549455945694579458945994609461946294639464946594669467946894699470947194729473947494759476947794789479948094819482948394849485948694879488948994909491949294939494949594969497949894999500950195029503950495059506950795089509951095119512951395149515951695179518951995209521952295239524952595269527952895299530953195329533953495359536953795389539954095419542954395449545954695479548954995509551955295539554955595569557955895599560956195629563956495659566956795689569957095719572957395749575957695779578957995809581958295839584958595869587958895899590959195929593959495959596959795989599960096019602960396049605960696079608960996109611961296139614961596169617961896199620962196229623962496259626962796289629963096319632963396349635963696379638963996409641964296439644964596469647964896499650965196529653965496559656965796589659966096619662966396649665966696679668966996709671967296739674967596769677967896799680968196829683968496859686968796889689969096919692969396949695969696979698969997009701970297039704970597069707970897099710971197129713971497159716971797189719972097219722972397249725972697279728972997309731973297339734973597369737973897399740974197429743974497459746974797489749975097519752975397549755975697579758975997609761976297639764976597669767976897699770977197729773977497759776977797789779978097819782978397849785978697879788978997909791979297939794979597969797979897999800980198029803980498059806980798089809981098119812981398149815981698179818981998209821982298239824982598269827982898299830983198329833983498359836983798389839984098419842984398449845984698479848984998509851985298539854985598569857985898599860986198629863986498659866986798689869987098719872987398749875987698779878987998809881988298839884988598869887988898899890989198929893989498959896989798989899990099019902990399049905990699079908990999109911991299139914991599169917991899199920992199229923992499259926992799289929993099319932993399349935993699379938993999409941994299439944994599469947994899499950995199529953995499559956995799589959996099619962996399649965996699679968996999709971997299739974997599769977997899799980998199829983998499859986998799889989999099919992999399949995999699979998999910000100011000210003100041000510006100071000810009100101001110012100131001410015100161001710018100191002010021100221002310024100251002610027100281002910030100311003210033100341003510036100371003810039100401004110042100431004410045100461004710048100491005010051100521005310054100551005610057100581005910060100611006210063100641006510066100671006810069100701007110072100731007410075100761007710078100791008010081100821008310084100851008610087100881008910090100911009210093100941009510096100971009810099101001010110102101031010410105101061010710108101091011010111101121011310114101151011610117101181011910120101211012210123101241012510126101271012810129101301013110132101331013410135101361013710138101391014010141101421014310144101451014610147101481014910150101511015210153101541015510156101571015810159101601016110162101631016410165101661016710168101691017010171101721017310174101751017610177101781017910180101811018210183101841018510186101871018810189101901019110192101931019410195101961019710198101991020010201102021020310204102051020610207102081020910210102111021210213102141021510216102171021810219102201022110222102231022410225102261022710228102291023010231102321023310234102351023610237102381023910240102411024210243102441024510246102471024810249102501025110252102531025410255102561025710258102591026010261102621026310264102651026610267102681026910270102711027210273102741027510276102771027810279102801028110282102831028410285102861028710288102891029010291102921029310294102951029610297102981029910300103011030210303103041030510306103071030810309103101031110312103131031410315103161031710318103191032010321103221032310324103251032610327103281032910330103311033210333103341033510336103371033810339103401034110342103431034410345103461034710348103491035010351103521035310354103551035610357103581035910360103611036210363103641036510366103671036810369103701037110372103731037410375103761037710378103791038010381103821038310384103851038610387103881038910390103911039210393103941039510396103971039810399104001040110402104031040410405104061040710408104091041010411104121041310414104151041610417104181041910420104211042210423104241042510426104271042810429104301043110432104331043410435104361043710438104391044010441104421044310444104451044610447104481044910450104511045210453104541045510456104571045810459104601046110462104631046410465104661046710468104691047010471104721047310474104751047610477104781047910480104811048210483104841048510486104871048810489104901049110492104931049410495104961049710498104991050010501105021050310504105051050610507105081050910510105111051210513105141051510516105171051810519105201052110522105231052410525105261052710528105291053010531105321053310534105351053610537105381053910540105411054210543105441054510546105471054810549105501055110552105531055410555105561055710558105591056010561105621056310564105651056610567105681056910570105711057210573105741057510576105771057810579105801058110582105831058410585105861058710588105891059010591105921059310594105951059610597105981059910600106011060210603106041060510606106071060810609106101061110612106131061410615106161061710618106191062010621106221062310624106251062610627
  1. <!DOCTYPE html>
  2. <html lang="zh-CN">
  3. <head>
  4. <meta charset="UTF-8">
  5. <meta name="viewport" content="width=device-width, initial-scale=1.0">
  6. <title>互动课件编辑器</title>
  7. <style>
  8. * {
  9. margin: 0;
  10. padding: 0;
  11. box-sizing: border-box;
  12. }
  13. body {
  14. font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei", sans-serif;
  15. background: #f0f2f5;
  16. overflow: hidden;
  17. }
  18. /* ========== 顶部工具栏 ========== */
  19. .top-bar {
  20. height: 64px;
  21. background: white;
  22. display: flex;
  23. align-items: center;
  24. justify-content: space-between;
  25. padding: 0 24px;
  26. position: relative;
  27. z-index: 100;
  28. box-shadow: 0 1px 3px 0 rgba(0, 0, 0, 0.06);
  29. }
  30. .top-bar-left {
  31. display: flex;
  32. align-items: center;
  33. gap: 16px;
  34. }
  35. .logo-menu-wrapper {
  36. position: relative;
  37. }
  38. .logo-btn {
  39. height: 36px;
  40. padding: 6px 10px;
  41. border-radius: 10px;
  42. border: 1.5px solid #e5e7eb;
  43. background: #f9fafb;
  44. cursor: pointer;
  45. display: inline-flex;
  46. align-items: center;
  47. gap: 8px;
  48. transition: all 0.2s;
  49. }
  50. .logo-btn:hover {
  51. background: #fff;
  52. border-color: #285cf5;
  53. box-shadow: 0 4px 10px rgba(40, 92, 245, 0.15);
  54. }
  55. .logo-img {
  56. width: 20px;
  57. height: 20px;
  58. object-fit: contain;
  59. }
  60. .logo-caret {
  61. width: 14px;
  62. height: 14px;
  63. color: #9ca3af;
  64. }
  65. .top-dropdown {
  66. position: absolute;
  67. top: 44px;
  68. left: 0;
  69. background: #fff;
  70. border: 1px solid #e5e7eb;
  71. box-shadow: 0 12px 30px rgba(0,0,0,0.12);
  72. border-radius: 12px;
  73. min-width: 160px;
  74. padding: 6px 0;
  75. display: none;
  76. z-index: 200;
  77. }
  78. .top-dropdown.active {
  79. display: block;
  80. }
  81. .top-dropdown-item {
  82. display: flex;
  83. align-items: center;
  84. gap: 10px;
  85. padding: 10px 14px;
  86. cursor: pointer;
  87. font-size: 13px;
  88. color: #111827;
  89. transition: all 0.15s;
  90. }
  91. .top-dropdown-item:hover {
  92. background: #eef3ff;
  93. color: #1f4ad6;
  94. }
  95. .top-dropdown-item.danger {
  96. color: #dc2626;
  97. }
  98. .top-dropdown-item.danger:hover {
  99. background: #fef2f2;
  100. color: #b91c1c;
  101. }
  102. .top-dropdown-sep {
  103. height: 1px;
  104. background: #f3f4f6;
  105. margin: 4px 0;
  106. }
  107. .course-title {
  108. font-size: 16px;
  109. font-weight: 600;
  110. color: #111827;
  111. padding: 8px 12px;
  112. border: 1px solid transparent;
  113. border-radius: 8px;
  114. cursor: text;
  115. transition: all 0.2s;
  116. }
  117. .course-title:hover {
  118. border-color: #e5e7eb;
  119. background: #f9fafb;
  120. }
  121. .course-title:focus {
  122. outline: none;
  123. border-color: #285cf5;
  124. background: #f6f8ff;
  125. }
  126. .auto-save {
  127. font-size: 13px;
  128. color: #6b7280;
  129. display: flex;
  130. align-items: center;
  131. gap: 6px;
  132. }
  133. .save-dot {
  134. width: 6px;
  135. height: 6px;
  136. border-radius: 50%;
  137. background: #10b981;
  138. animation: pulse 2s ease-in-out infinite;
  139. }
  140. @keyframes pulse {
  141. 0%, 100% { opacity: 1; }
  142. 50% { opacity: 0.5; }
  143. }
  144. .top-bar-right {
  145. display: flex;
  146. align-items: center;
  147. gap: 12px;
  148. }
  149. .top-btn {
  150. height: 40px;
  151. padding: 0 20px;
  152. border-radius: 10px;
  153. border: none;
  154. font-size: 14px;
  155. font-weight: 500;
  156. cursor: pointer;
  157. transition: all 0.2s;
  158. display: flex;
  159. align-items: center;
  160. gap: 8px;
  161. }
  162. .btn-secondary {
  163. background: white;
  164. color: #374151;
  165. border: 1.5px solid #e5e7eb;
  166. }
  167. .btn-secondary:hover {
  168. background: #f9fafb;
  169. border-color: #d1d5db;
  170. transform: translateY(-1px);
  171. box-shadow: 0 2px 4px rgba(0, 0, 0, 0.05);
  172. }
  173. .btn-primary {
  174. background: #285cf5;
  175. color: white;
  176. box-shadow: 0 2px 8px rgba(40, 92, 245, 0.2);
  177. }
  178. .btn-primary:hover {
  179. background: #1f4ad6;
  180. transform: translateY(-1px);
  181. box-shadow: 0 4px 12px rgba(40, 92, 245, 0.3);
  182. }
  183. /* ========== 主容器 ========== */
  184. .main-container {
  185. height: calc(100vh - 64px);
  186. display: flex;
  187. position: relative;
  188. padding: 16px;
  189. gap: 16px;
  190. }
  191. /* ========== 左侧容器 ========== */
  192. .left-container {
  193. display: flex;
  194. background: white;
  195. border-radius: 16px;
  196. box-shadow: 0 2px 8px rgba(0, 0, 0, 0.06);
  197. position: relative;
  198. z-index: 50;
  199. overflow: hidden;
  200. }
  201. .primary-menu {
  202. width: 100px;
  203. background: #fafbfc;
  204. border-right: 1px solid #e5e7eb;
  205. padding: 16px 8px;
  206. display: flex;
  207. flex-direction: column;
  208. gap: 6px;
  209. }
  210. .menu-item {
  211. width: 84px;
  212. padding: 12px 8px;
  213. border-radius: 12px;
  214. display: flex;
  215. flex-direction: column;
  216. align-items: center;
  217. justify-content: center;
  218. gap: 6px;
  219. cursor: pointer;
  220. transition: all 0.2s;
  221. position: relative;
  222. }
  223. .menu-item:hover {
  224. background: #f3f4f6;
  225. }
  226. .menu-item.active {
  227. background: #eef3ff;
  228. box-shadow: 0 2px 8px rgba(40, 92, 245, 0.15);
  229. }
  230. .menu-item.active::after {
  231. content: '';
  232. position: absolute;
  233. left: -8px;
  234. top: 50%;
  235. transform: translateY(-50%);
  236. width: 4px;
  237. height: 32px;
  238. background: #285cf5;
  239. border-radius: 0 2px 2px 0;
  240. }
  241. .menu-icon {
  242. width: 22px;
  243. height: 22px;
  244. color: #6b7280;
  245. flex-shrink: 0;
  246. }
  247. .menu-item.active .menu-icon {
  248. color: #285cf5;
  249. }
  250. .menu-label {
  251. font-size: 11px;
  252. font-weight: 500;
  253. color: #6b7280;
  254. text-align: center;
  255. line-height: 1.2;
  256. }
  257. .menu-item.active .menu-label {
  258. color: #285cf5;
  259. font-weight: 600;
  260. }
  261. /* ========== 二级菜单 - AI对话 ========== */
  262. .secondary-panel {
  263. width: 420px;
  264. background: white;
  265. display: flex;
  266. flex-direction: column;
  267. transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
  268. }
  269. .secondary-panel.collapsed {
  270. width: 0;
  271. overflow: hidden;
  272. }
  273. .secondary-panel.hidden {
  274. display: none;
  275. }
  276. .panel-header {
  277. padding: 20px 20px 16px;
  278. border-bottom: 1px solid #f3f4f6;
  279. display: flex;
  280. align-items: center;
  281. justify-content: space-between;
  282. }
  283. .panel-title {
  284. font-size: 15px;
  285. font-weight: 600;
  286. color: #111827;
  287. display: flex;
  288. align-items: center;
  289. gap: 10px;
  290. }
  291. .ai-badge {
  292. background: linear-gradient(135deg, #285cf5 0%, #1f4ad6 100%);
  293. color: white;
  294. font-size: 11px;
  295. font-weight: 600;
  296. padding: 4px 10px;
  297. border-radius: 12px;
  298. }
  299. .collapse-btn {
  300. width: 28px;
  301. height: 28px;
  302. border-radius: 10px;
  303. border: none;
  304. background: transparent;
  305. cursor: pointer;
  306. display: flex;
  307. align-items: center;
  308. justify-content: center;
  309. transition: all 0.2s;
  310. color: #9ca3af;
  311. }
  312. .collapse-btn:hover {
  313. background: #f3f4f6;
  314. color: #6b7280;
  315. }
  316. /* 输入框容器 */
  317. .chat-input-container {
  318. padding: 16px 20px;
  319. background: white;
  320. order: 1;
  321. border-bottom: 1px solid #f0f1f3;
  322. transition: all 0.3s ease;
  323. }
  324. .chat-input-container.bottom {
  325. order: 3;
  326. border-bottom: none;
  327. border-top: 1px solid #f0f1f3;
  328. }
  329. .secondary-content {
  330. flex: 1;
  331. overflow-y: auto;
  332. padding: 20px;
  333. display: flex;
  334. flex-direction: column;
  335. order: 2;
  336. }
  337. .chat-messages {
  338. min-height: 600px;
  339. overflow-y: auto;
  340. padding-bottom: 16px;
  341. }
  342. .message {
  343. margin-bottom: 16px;
  344. }
  345. .message-user {
  346. display: flex;
  347. justify-content: flex-end;
  348. }
  349. .message-user .message-content {
  350. background: #eef3ff;
  351. border: 1.5px solid #285cf5;
  352. color: #111827;
  353. border-radius: 16px 16px 4px 16px;
  354. padding: 12px 16px;
  355. max-width: 85%;
  356. font-size: 14px;
  357. line-height: 1.6;
  358. }
  359. .message-ai {
  360. display: flex;
  361. justify-content: flex-start;
  362. }
  363. .message-ai .message-content {
  364. background: #fafbfc;
  365. border: 1.5px solid #e5e7eb;
  366. color: #374151;
  367. border-radius: 16px 16px 16px 4px;
  368. padding: 12px 16px;
  369. max-width: 85%;
  370. font-size: 14px;
  371. line-height: 1.6;
  372. white-space: pre-line;
  373. }
  374. .chat-input-wrapper {
  375. display: flex;
  376. flex-direction: column;
  377. gap: 8px;
  378. background: #fafbfc;
  379. border: 1.5px solid #e5e7eb;
  380. border-radius: 16px;
  381. padding: 12px;
  382. transition: all 0.2s;
  383. }
  384. .chat-input-wrapper:focus-within {
  385. border-color: #285cf5;
  386. background: white;
  387. }
  388. .chat-textarea-container {
  389. width: 100%;
  390. min-height: 72px;
  391. }
  392. .chat-textarea {
  393. width: 100%;
  394. border: none;
  395. background: transparent;
  396. outline: none;
  397. resize: none;
  398. font-size: 14px;
  399. color: #111827;
  400. line-height: 1.5;
  401. padding: 0;
  402. min-height: 72px;
  403. max-height: 180px;
  404. font-family: inherit;
  405. }
  406. .chat-textarea::placeholder {
  407. color: #9ca3af;
  408. }
  409. .chat-textarea:disabled {
  410. opacity: 0.6;
  411. cursor: not-allowed;
  412. }
  413. .chat-bottom-row {
  414. display: flex;
  415. align-items: center;
  416. justify-content: space-between;
  417. gap: 8px;
  418. }
  419. .upload-file-btn {
  420. width: 36px;
  421. height: 36px;
  422. border-radius: 8px;
  423. border: none;
  424. background: transparent;
  425. cursor: pointer;
  426. display: flex;
  427. align-items: center;
  428. justify-content: center;
  429. transition: all 0.2s;
  430. flex-shrink: 0;
  431. }
  432. .upload-file-btn:hover {
  433. background: #eef3ff;
  434. }
  435. .upload-file-btn svg {
  436. width: 20px;
  437. height: 20px;
  438. color: #6b7280;
  439. }
  440. .upload-file-btn:hover svg {
  441. color: #285cf5;
  442. }
  443. .status-text {
  444. font-size: 13px;
  445. color: #9ca3af;
  446. display: none;
  447. align-items: center;
  448. gap: 6px;
  449. }
  450. .status-text.active {
  451. display: flex;
  452. }
  453. .status-dot {
  454. width: 6px;
  455. height: 6px;
  456. border-radius: 50%;
  457. background: #285cf5;
  458. animation: pulse-dot 1.5s ease-in-out infinite;
  459. }
  460. @keyframes pulse-dot {
  461. 0%, 100% { opacity: 1; transform: scale(1); }
  462. 50% { opacity: 0.5; transform: scale(1.2); }
  463. }
  464. .send-btn {
  465. width: 36px;
  466. height: 36px;
  467. border-radius: 50%;
  468. border: none;
  469. background: #285cf5;
  470. cursor: pointer;
  471. display: flex;
  472. align-items: center;
  473. justify-content: center;
  474. transition: all 0.2s;
  475. flex-shrink: 0;
  476. }
  477. .send-btn.generating {
  478. background: #1f4ad6;
  479. cursor: not-allowed;
  480. box-shadow: 0 0 0 3px rgba(40, 92, 245, 0.16);
  481. }
  482. .send-btn.generating svg {
  483. animation: spin 1s linear infinite;
  484. }
  485. @keyframes spin {
  486. from { transform: rotate(0deg); }
  487. to { transform: rotate(360deg); }
  488. }
  489. .send-btn:hover {
  490. background: #1f4ad6;
  491. transform: scale(1.05);
  492. box-shadow: 0 4px 12px rgba(40, 92, 245, 0.25);
  493. }
  494. .send-btn.generating:hover {
  495. background: #1f4ad6;
  496. transform: none;
  497. box-shadow: 0 0 0 3px rgba(40, 92, 245, 0.16);
  498. }
  499. .send-btn svg {
  500. width: 18px;
  501. height: 18px;
  502. color: white;
  503. }
  504. /* ========== 中央编辑区 ========== */
  505. .center-area {
  506. flex: 1;
  507. display: flex;
  508. flex-direction: column;
  509. background: white;
  510. overflow: hidden;
  511. border-radius: 16px;
  512. box-shadow: 0 2px 8px rgba(0, 0, 0, 0.06);
  513. position: relative;
  514. }
  515. /* 元素工具栏 */
  516. .element-toolbar {
  517. min-height: 60px;
  518. background: white;
  519. border-bottom: 1px solid #f0f1f3;
  520. display: none;
  521. align-items: center;
  522. padding: 12px 24px;
  523. gap: 10px;
  524. position: sticky;
  525. top: 0;
  526. z-index: 3;
  527. }
  528. .element-toolbar.visible {
  529. display: flex;
  530. }
  531. .toolbar-section {
  532. display: flex;
  533. align-items: center;
  534. gap: 8px;
  535. padding-right: 16px;
  536. border-right: 1px solid #e5e7eb;
  537. }
  538. .toolbar-section:last-child {
  539. border-right: none;
  540. }
  541. .toolbar-btn {
  542. height: 38px;
  543. padding: 0 14px;
  544. border-radius: 10px;
  545. border: none;
  546. background: #f9fafb;
  547. font-size: 13px;
  548. font-weight: 500;
  549. color: #4b5563;
  550. cursor: pointer;
  551. display: flex;
  552. align-items: center;
  553. gap: 7px;
  554. transition: all 0.2s;
  555. }
  556. .toolbar-btn:hover {
  557. background: #eef3ff;
  558. color: #285cf5;
  559. transform: translateY(-1px);
  560. }
  561. .toolbar-btn svg {
  562. width: 16px;
  563. height: 16px;
  564. }
  565. /* Slides编辑区 */
  566. .slides-area {
  567. flex: 1;
  568. display: flex;
  569. flex-direction: column;
  570. padding: 0;
  571. overflow: hidden;
  572. background: #fafbfc;
  573. position: relative;
  574. }
  575. .slides-area > .slide-canvas {
  576. flex: 1;
  577. width: 100%;
  578. height: auto;
  579. background: white;
  580. box-shadow: none;
  581. border-radius: 0;
  582. display: flex;
  583. align-items: center;
  584. justify-content: center;
  585. padding: 40px;
  586. }
  587. .slides-area > .slide-canvas .slide-placeholder {
  588. text-align: center;
  589. color: #9ca3af;
  590. }
  591. .slide-placeholder-icon {
  592. font-size: 56px;
  593. margin-bottom: 20px;
  594. opacity: 0.6;
  595. }
  596. .slide-placeholder-text {
  597. font-size: 16px;
  598. font-weight: 500;
  599. margin-bottom: 8px;
  600. color: #6b7280;
  601. }
  602. .slide-placeholder-hint {
  603. font-size: 13px;
  604. color: #d1d5db;
  605. }
  606. /* 可编辑元素 */
  607. .slide-element {
  608. position: absolute;
  609. border: 2px solid transparent;
  610. cursor: move;
  611. transition: border-color 0.2s;
  612. }
  613. .slide-element:hover {
  614. border-color: #d1d5db;
  615. }
  616. .slide-element.selected {
  617. border-color: #285cf5;
  618. box-shadow: 0 0 0 1px #285cf5;
  619. }
  620. .slide-element.selected .resize-handle {
  621. display: block;
  622. }
  623. .resize-handle {
  624. position: absolute;
  625. width: 8px;
  626. height: 8px;
  627. background: #285cf5;
  628. border: 2px solid white;
  629. border-radius: 50%;
  630. display: none;
  631. }
  632. .resize-handle.nw { top: -4px; left: -4px; cursor: nw-resize; }
  633. .resize-handle.ne { top: -4px; right: -4px; cursor: ne-resize; }
  634. .resize-handle.sw { bottom: -4px; left: -4px; cursor: sw-resize; }
  635. .resize-handle.se { bottom: -4px; right: -4px; cursor: se-resize; }
  636. .text-element {
  637. padding: 12px;
  638. font-size: 16px;
  639. line-height: 1.5;
  640. outline: none;
  641. }
  642. .image-element {
  643. width: 100%;
  644. height: 100%;
  645. object-fit: cover;
  646. }
  647. /* 下拉菜单 */
  648. .dropdown {
  649. position: relative;
  650. }
  651. .dropdown-menu {
  652. position: absolute;
  653. top: 100%;
  654. left: 0;
  655. margin-top: 4px;
  656. background: white;
  657. border-radius: 12px;
  658. box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
  659. padding: 8px;
  660. min-width: 160px;
  661. display: none;
  662. z-index: 1000;
  663. }
  664. .dropdown.active .dropdown-menu {
  665. display: block;
  666. }
  667. .dropdown-item {
  668. padding: 10px 12px;
  669. border-radius: 8px;
  670. cursor: pointer;
  671. transition: all 0.2s;
  672. display: flex;
  673. align-items: center;
  674. gap: 10px;
  675. font-size: 13px;
  676. color: #374151;
  677. }
  678. .dropdown-item:hover {
  679. background: #f3f4f6;
  680. }
  681. .dropdown-item svg {
  682. width: 18px;
  683. height: 18px;
  684. color: #6b7280;
  685. }
  686. /* 底部大纲 */
  687. .bottom-outline {
  688. height: 150px;
  689. background: #fafbfc;
  690. border-top: 1px solid #f0f1f3;
  691. padding: 16px 24px;
  692. overflow-x: auto;
  693. overflow-y: hidden;
  694. display: none;
  695. }
  696. .bottom-outline.visible {
  697. display: block;
  698. }
  699. .outline-track {
  700. display: flex;
  701. gap: 14px;
  702. height: 100%;
  703. align-items: center;
  704. }
  705. .outline-item-wrapper {
  706. position: relative;
  707. display: flex;
  708. align-items: center;
  709. gap: 14px;
  710. z-index: 1;
  711. }
  712. .outline-item-wrapper:has(.page-menu.active) {
  713. z-index: 10000;
  714. }
  715. .outline-item {
  716. width: 190px;
  717. height: 110px;
  718. flex-shrink: 0;
  719. border-radius: 12px;
  720. border: 2px solid #e5e7eb;
  721. background: white;
  722. cursor: grab;
  723. position: relative;
  724. transition: all 0.2s;
  725. display: flex;
  726. flex-direction: column;
  727. align-items: center;
  728. justify-content: center;
  729. font-size: 12px;
  730. color: #9ca3af;
  731. }
  732. .outline-item:active {
  733. cursor: grabbing;
  734. }
  735. .outline-item.dragging {
  736. opacity: 0.5;
  737. cursor: grabbing;
  738. }
  739. .outline-item.drag-over {
  740. border-color: #285cf5;
  741. border-style: dashed;
  742. background: #f6f8ff;
  743. }
  744. .outline-item:hover {
  745. border-color: #285cf5;
  746. transform: translateY(-2px);
  747. box-shadow: 0 4px 8px rgba(40, 92, 245, 0.15);
  748. z-index: 100;
  749. }
  750. .outline-item.active {
  751. border-color: #285cf5;
  752. box-shadow: 0 4px 12px rgba(40, 92, 245, 0.2);
  753. }
  754. .outline-item:has(.page-menu.active) {
  755. z-index: 10000;
  756. }
  757. .outline-item .page-number {
  758. position: absolute;
  759. top: 8px;
  760. left: 10px;
  761. font-size: 11px;
  762. font-weight: 700;
  763. color: #6b7280;
  764. background: white;
  765. width: 24px;
  766. height: 24px;
  767. border-radius: 6px;
  768. display: flex;
  769. align-items: center;
  770. justify-content: center;
  771. }
  772. /* 页面操作菜单 */
  773. .page-menu {
  774. position: absolute;
  775. bottom: 8px;
  776. right: 8px;
  777. width: 28px;
  778. height: 28px;
  779. border-radius: 8px;
  780. background: rgba(255, 255, 255, 0.95);
  781. border: 1px solid #e5e7eb;
  782. display: none;
  783. align-items: center;
  784. justify-content: center;
  785. cursor: pointer;
  786. transition: all 0.2s;
  787. z-index: 10000;
  788. }
  789. .outline-item:hover .page-menu {
  790. display: flex;
  791. }
  792. .page-menu.active {
  793. z-index: 10001;
  794. }
  795. .page-menu:hover {
  796. background: #285cf5;
  797. border-color: #285cf5;
  798. }
  799. .page-menu:hover svg {
  800. color: white;
  801. }
  802. .page-menu svg {
  803. width: 16px;
  804. height: 16px;
  805. color: #6b7280;
  806. }
  807. .page-menu-dropdown {
  808. position: absolute;
  809. bottom: 100%;
  810. right: 0;
  811. margin-bottom: 4px;
  812. background: white;
  813. border-radius: 12px;
  814. box-shadow: 0 8px 20px rgba(0, 0, 0, 0.2);
  815. padding: 8px;
  816. min-width: 120px;
  817. display: none;
  818. z-index: 10002;
  819. }
  820. .page-menu.active .page-menu-dropdown {
  821. display: block;
  822. }
  823. .page-menu-item {
  824. padding: 10px 12px;
  825. border-radius: 8px;
  826. cursor: pointer;
  827. transition: all 0.2s;
  828. display: flex;
  829. align-items: center;
  830. gap: 10px;
  831. font-size: 13px;
  832. color: #374151;
  833. }
  834. .page-menu-item:hover {
  835. background: #eef3ff;
  836. }
  837. .page-menu-item:hover svg {
  838. color: #285cf5;
  839. }
  840. .page-menu-item.danger:hover {
  841. background: #fef2f2;
  842. color: #dc2626;
  843. }
  844. .page-menu-item.danger:hover svg {
  845. color: #dc2626;
  846. }
  847. .page-menu-item svg {
  848. width: 16px;
  849. height: 16px;
  850. color: #6b7280;
  851. }
  852. /* 页面间添加按钮 */
  853. .add-page-between {
  854. width: 40px;
  855. height: 40px;
  856. border-radius: 50%;
  857. border: 2px dashed #d1d5db;
  858. background: white;
  859. cursor: pointer;
  860. display: none;
  861. align-items: center;
  862. justify-content: center;
  863. color: #9ca3af;
  864. font-size: 20px;
  865. transition: all 0.2s;
  866. flex-shrink: 0;
  867. }
  868. .outline-item-wrapper:hover .add-page-between {
  869. display: flex;
  870. }
  871. .add-page-between:hover {
  872. border-color: #285cf5;
  873. border-style: solid;
  874. color: #285cf5;
  875. background: #f6f8ff;
  876. }
  877. /* ========== 页面模板二级菜单 ========== */
  878. .template-grid {
  879. display: grid;
  880. grid-template-columns: repeat(2, 1fr);
  881. gap: 14px;
  882. padding: 20px;
  883. }
  884. .template-card {
  885. background: #fafbfc;
  886. border: 1.5px solid #e5e7eb;
  887. border-radius: 14px;
  888. padding: 14px;
  889. cursor: pointer;
  890. transition: all 0.2s;
  891. position: relative;
  892. overflow: hidden;
  893. }
  894. .template-card:hover {
  895. border-color: #285cf5;
  896. background: #f6f8ff;
  897. transform: translateY(-2px);
  898. box-shadow: 0 4px 12px rgba(40, 92, 245, 0.15);
  899. }
  900. .template-card:hover .template-preview {
  901. opacity: 1;
  902. transform: translateY(0);
  903. }
  904. .template-preview {
  905. width: 100%;
  906. height: 100px;
  907. background: white;
  908. border-radius: 8px;
  909. margin-bottom: 12px;
  910. display: flex;
  911. align-items: center;
  912. justify-content: center;
  913. border: 1px solid #e5e7eb;
  914. position: relative;
  915. opacity: 0.9;
  916. transform: translateY(-2px);
  917. transition: all 0.3s;
  918. }
  919. .template-preview svg {
  920. width: 48px;
  921. height: 48px;
  922. color: #d1d5db;
  923. }
  924. .template-card:hover .template-preview svg {
  925. color: #285cf5;
  926. }
  927. .template-name {
  928. font-size: 14px;
  929. font-weight: 600;
  930. color: #111827;
  931. text-align: center;
  932. }
  933. .upload-ppt-card {
  934. grid-column: 1 / -1;
  935. background: #fafbfc;
  936. border: 1.5px solid #e5e7eb;
  937. display: flex;
  938. align-items: center;
  939. justify-content: center;
  940. gap: 12px;
  941. padding: 14px;
  942. border-radius: 14px;
  943. }
  944. .upload-ppt-card:hover {
  945. background: #f6f8ff;
  946. border-color: #285cf5;
  947. box-shadow: 0 4px 12px rgba(40, 92, 245, 0.15);
  948. transform: translateY(-2px);
  949. }
  950. .upload-ppt-card svg {
  951. width: 28px;
  952. height: 28px;
  953. color: #d1d5db;
  954. }
  955. .upload-ppt-card:hover svg {
  956. color: #285cf5;
  957. }
  958. .upload-ppt-text {
  959. font-size: 14px;
  960. font-weight: 600;
  961. color: #111827;
  962. }
  963. /* ========== 图片悬浮菜单 ========== */
  964. .image-hover-menu {
  965. position: absolute;
  966. top: 50%;
  967. left: 50%;
  968. transform: translate(-50%, -50%);
  969. background: white;
  970. border-radius: 12px;
  971. box-shadow: 0 8px 24px rgba(0, 0, 0, 0.15);
  972. padding: 8px;
  973. display: none;
  974. flex-direction: column;
  975. gap: 4px;
  976. z-index: 1000;
  977. min-width: 160px;
  978. }
  979. .slide-element.has-image:hover .image-hover-menu {
  980. display: flex;
  981. }
  982. .image-menu-item {
  983. padding: 12px 14px;
  984. border-radius: 8px;
  985. cursor: pointer;
  986. transition: all 0.2s;
  987. display: flex;
  988. align-items: center;
  989. gap: 10px;
  990. font-size: 13px;
  991. color: #374151;
  992. font-weight: 500;
  993. }
  994. .image-menu-item:hover {
  995. background: #eef3ff;
  996. color: #285cf5;
  997. }
  998. .image-menu-item svg {
  999. width: 18px;
  1000. height: 18px;
  1001. flex-shrink: 0;
  1002. }
  1003. /* ========== 搜索/生成浮窗 ========== */
  1004. .floating-modal {
  1005. position: fixed;
  1006. top: 0;
  1007. left: 0;
  1008. right: 0;
  1009. bottom: 0;
  1010. background: rgba(0, 0, 0, 0.4);
  1011. backdrop-filter: blur(4px);
  1012. display: none;
  1013. align-items: center;
  1014. justify-content: center;
  1015. z-index: 2000;
  1016. opacity: 0;
  1017. transition: opacity 0.3s;
  1018. }
  1019. .floating-modal.active {
  1020. display: flex;
  1021. opacity: 1;
  1022. }
  1023. .floating-content {
  1024. background: white;
  1025. border-radius: 20px;
  1026. padding: 32px;
  1027. max-width: 600px;
  1028. width: 90%;
  1029. max-height: 80vh;
  1030. overflow-y: auto;
  1031. box-shadow: 0 20px 60px rgba(0, 0, 0, 0.2);
  1032. transform: scale(0.95);
  1033. transition: transform 0.3s;
  1034. }
  1035. .floating-modal.active .floating-content {
  1036. transform: scale(1);
  1037. }
  1038. .floating-header {
  1039. display: flex;
  1040. align-items: center;
  1041. justify-content: space-between;
  1042. margin-bottom: 24px;
  1043. }
  1044. .floating-title {
  1045. font-size: 20px;
  1046. font-weight: 700;
  1047. color: #111827;
  1048. }
  1049. .floating-close {
  1050. width: 32px;
  1051. height: 32px;
  1052. border-radius: 8px;
  1053. border: none;
  1054. background: #f3f4f6;
  1055. cursor: pointer;
  1056. display: flex;
  1057. align-items: center;
  1058. justify-content: center;
  1059. transition: all 0.2s;
  1060. }
  1061. .floating-close:hover {
  1062. background: #e5e7eb;
  1063. }
  1064. .search-input-group {
  1065. margin-bottom: 20px;
  1066. }
  1067. .search-input-label {
  1068. font-size: 13px;
  1069. font-weight: 600;
  1070. color: #6b7280;
  1071. margin-bottom: 8px;
  1072. display: block;
  1073. }
  1074. .search-input {
  1075. width: 100%;
  1076. padding: 14px 16px;
  1077. border: 1.5px solid #e5e7eb;
  1078. border-radius: 12px;
  1079. font-size: 14px;
  1080. color: #111827;
  1081. background: #fafbfc;
  1082. transition: all 0.2s;
  1083. font-family: inherit;
  1084. }
  1085. .search-input:focus {
  1086. outline: none;
  1087. border-color: #285cf5;
  1088. background: white;
  1089. }
  1090. /* ========== AI应用中心浮窗 ========== */
  1091. .app-center-content {
  1092. background: white;
  1093. border-radius: 20px;
  1094. padding: 32px;
  1095. max-width: 900px;
  1096. width: 90%;
  1097. max-height: 85vh;
  1098. display: flex;
  1099. flex-direction: column;
  1100. box-shadow: 0 20px 60px rgba(0, 0, 0, 0.2);
  1101. transform: scale(0.95);
  1102. transition: transform 0.3s;
  1103. }
  1104. .floating-modal.active .app-center-content {
  1105. transform: scale(1);
  1106. }
  1107. .app-filters {
  1108. display: flex;
  1109. gap: 16px;
  1110. margin-bottom: 24px;
  1111. padding-bottom: 20px;
  1112. border-bottom: 1px solid #e5e7eb;
  1113. }
  1114. .filter-group {
  1115. display: flex;
  1116. flex-direction: column;
  1117. gap: 8px;
  1118. flex: 1;
  1119. position: relative;
  1120. }
  1121. .filter-label {
  1122. font-size: 13px;
  1123. font-weight: 600;
  1124. color: #6b7280;
  1125. }
  1126. .filter-select {
  1127. height: 40px;
  1128. padding: 0 36px 0 14px;
  1129. border: 1.5px solid #e5e7eb;
  1130. border-radius: 10px;
  1131. font-size: 13px;
  1132. color: #111827;
  1133. background: #fafbfc;
  1134. cursor: pointer;
  1135. transition: all 0.2s;
  1136. outline: none;
  1137. appearance: none;
  1138. font-weight: 500;
  1139. 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");
  1140. background-repeat: no-repeat;
  1141. background-position: right 12px center;
  1142. background-size: 16px;
  1143. }
  1144. .filter-select:hover {
  1145. border-color: #285cf5;
  1146. background-color: #f6f8ff;
  1147. 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");
  1148. }
  1149. .filter-select:focus {
  1150. border-color: #285cf5;
  1151. background-color: white;
  1152. }
  1153. .filter-select option {
  1154. padding: 10px;
  1155. font-size: 13px;
  1156. }
  1157. .app-grid {
  1158. display: grid;
  1159. grid-template-columns: repeat(3, 1fr);
  1160. gap: 16px;
  1161. flex: 1;
  1162. overflow-y: auto;
  1163. padding: 4px;
  1164. }
  1165. .app-card {
  1166. background: #fafbfc;
  1167. border: 1.5px solid #e5e7eb;
  1168. border-radius: 14px;
  1169. padding: 16px;
  1170. cursor: pointer;
  1171. transition: all 0.2s;
  1172. position: relative;
  1173. display: flex;
  1174. flex-direction: column;
  1175. }
  1176. .app-card:hover {
  1177. border-color: #285cf5;
  1178. background: #f6f8ff;
  1179. transform: translateY(-2px);
  1180. box-shadow: 0 4px 12px rgba(40, 92, 245, 0.15);
  1181. }
  1182. .app-card.selected {
  1183. border-color: #285cf5;
  1184. background: #f6f8ff;
  1185. }
  1186. .app-card.selected::after {
  1187. content: '';
  1188. position: absolute;
  1189. top: 10px;
  1190. right: 10px;
  1191. width: 20px;
  1192. height: 20px;
  1193. background: #285cf5;
  1194. border-radius: 50%;
  1195. 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");
  1196. background-size: 14px;
  1197. background-position: center;
  1198. background-repeat: no-repeat;
  1199. }
  1200. .app-cover {
  1201. width: 100%;
  1202. aspect-ratio: 16/9;
  1203. border-radius: 10px;
  1204. background: linear-gradient(135deg, #eef3ff 0%, #f6f8ff 100%);
  1205. display: flex;
  1206. align-items: center;
  1207. justify-content: center;
  1208. margin-bottom: 12px;
  1209. border: 1px solid #dbe6ff;
  1210. }
  1211. .app-cover svg {
  1212. width: 36px;
  1213. height: 36px;
  1214. color: #285cf5;
  1215. }
  1216. .app-info {
  1217. flex: 1;
  1218. }
  1219. .app-name {
  1220. font-size: 14px;
  1221. font-weight: 600;
  1222. color: #111827;
  1223. margin-bottom: 4px;
  1224. }
  1225. .app-description {
  1226. font-size: 12px;
  1227. color: #6b7280;
  1228. line-height: 1.4;
  1229. }
  1230. .app-meta {
  1231. display: flex;
  1232. gap: 6px;
  1233. margin-top: 8px;
  1234. flex-wrap: wrap;
  1235. }
  1236. .app-tag {
  1237. font-size: 11px;
  1238. padding: 3px 8px;
  1239. border-radius: 6px;
  1240. background: #f3f4f6;
  1241. color: #6b7280;
  1242. }
  1243. .app-center-footer {
  1244. display: flex;
  1245. justify-content: space-between;
  1246. align-items: center;
  1247. padding-top: 20px;
  1248. margin-top: 20px;
  1249. border-top: 1px solid #e5e7eb;
  1250. }
  1251. .selected-count {
  1252. font-size: 14px;
  1253. color: #6b7280;
  1254. }
  1255. .selected-count span {
  1256. font-weight: 600;
  1257. color: #285cf5;
  1258. }
  1259. .app-center-actions {
  1260. display: flex;
  1261. gap: 12px;
  1262. }
  1263. .app-center-btn {
  1264. padding: 10px 20px;
  1265. border-radius: 10px;
  1266. border: none;
  1267. font-size: 14px;
  1268. font-weight: 600;
  1269. cursor: pointer;
  1270. transition: all 0.2s;
  1271. }
  1272. .app-center-btn-cancel {
  1273. background: #f3f4f6;
  1274. color: #6b7280;
  1275. }
  1276. .app-center-btn-cancel:hover {
  1277. background: #e5e7eb;
  1278. }
  1279. .app-center-btn-confirm {
  1280. background: #285cf5;
  1281. color: white;
  1282. display: flex;
  1283. align-items: center;
  1284. gap: 6px;
  1285. }
  1286. .app-center-btn-confirm:hover {
  1287. background: #1f4ad6;
  1288. }
  1289. .app-center-btn-confirm:disabled {
  1290. opacity: 0.5;
  1291. cursor: not-allowed;
  1292. }
  1293. /* ========== AI创建应用浮窗 ========== */
  1294. .ai-create-app-content {
  1295. background: white;
  1296. border-radius: 20px;
  1297. padding: 32px;
  1298. max-width: 1000px;
  1299. width: 90%;
  1300. max-height: 85vh;
  1301. display: flex;
  1302. flex-direction: column;
  1303. box-shadow: 0 20px 60px rgba(0, 0, 0, 0.2);
  1304. transform: scale(0.95);
  1305. transition: transform 0.3s;
  1306. }
  1307. .floating-modal.active .ai-create-app-content {
  1308. transform: scale(1);
  1309. }
  1310. .ai-create-main {
  1311. display: grid;
  1312. grid-template-columns: 1fr 360px;
  1313. gap: 24px;
  1314. flex: 1;
  1315. min-height: 0;
  1316. }
  1317. .app-canvas-preview {
  1318. border: 2px dashed #e5e7eb;
  1319. border-radius: 16px;
  1320. background: #fafbfc;
  1321. display: flex;
  1322. align-items: center;
  1323. justify-content: center;
  1324. color: #9ca3af;
  1325. font-size: 14px;
  1326. overflow: auto;
  1327. padding: 20px;
  1328. }
  1329. .ai-chat-panel {
  1330. display: flex;
  1331. flex-direction: column;
  1332. border: 1.5px solid #e5e7eb;
  1333. border-radius: 16px;
  1334. background: #fafbfc;
  1335. overflow: hidden;
  1336. }
  1337. .ai-chat-messages {
  1338. flex: 1;
  1339. overflow-y: auto;
  1340. padding: 16px;
  1341. display: flex;
  1342. flex-direction: column;
  1343. gap: 12px;
  1344. }
  1345. .ai-chat-message {
  1346. display: flex;
  1347. gap: 10px;
  1348. max-width: 85%;
  1349. }
  1350. .ai-chat-message.user {
  1351. align-self: flex-end;
  1352. flex-direction: row-reverse;
  1353. }
  1354. .chat-avatar {
  1355. width: 32px;
  1356. height: 32px;
  1357. border-radius: 50%;
  1358. background: #285cf5;
  1359. display: flex;
  1360. align-items: center;
  1361. justify-content: center;
  1362. flex-shrink: 0;
  1363. color: white;
  1364. font-size: 14px;
  1365. font-weight: 600;
  1366. }
  1367. .ai-chat-message.user .chat-avatar {
  1368. background: #3b82f6;
  1369. }
  1370. .chat-bubble {
  1371. background: white;
  1372. padding: 10px 14px;
  1373. border-radius: 12px;
  1374. font-size: 13px;
  1375. line-height: 1.5;
  1376. color: #111827;
  1377. border: 1px solid #e5e7eb;
  1378. }
  1379. .ai-chat-message.user .chat-bubble {
  1380. background: #3b82f6;
  1381. color: white;
  1382. border-color: #3b82f6;
  1383. }
  1384. .ai-chat-input-area {
  1385. border-top: 1px solid #e5e7eb;
  1386. padding: 12px;
  1387. background: white;
  1388. }
  1389. .ai-chat-input-wrapper {
  1390. display: flex;
  1391. gap: 8px;
  1392. }
  1393. .ai-chat-input {
  1394. flex: 1;
  1395. padding: 10px 12px;
  1396. border: 1.5px solid #e5e7eb;
  1397. border-radius: 10px;
  1398. font-size: 13px;
  1399. font-family: inherit;
  1400. resize: none;
  1401. min-height: 40px;
  1402. max-height: 100px;
  1403. }
  1404. .ai-chat-input:focus {
  1405. outline: none;
  1406. border-color: #285cf5;
  1407. }
  1408. .ai-chat-send-btn {
  1409. width: 40px;
  1410. height: 40px;
  1411. border-radius: 10px;
  1412. border: none;
  1413. background: #285cf5;
  1414. color: white;
  1415. cursor: pointer;
  1416. display: flex;
  1417. align-items: center;
  1418. justify-content: center;
  1419. transition: all 0.2s;
  1420. flex-shrink: 0;
  1421. }
  1422. .ai-chat-send-btn:hover {
  1423. background: #1f4ad6;
  1424. }
  1425. .ai-chat-send-btn:disabled {
  1426. opacity: 0.5;
  1427. cursor: not-allowed;
  1428. }
  1429. .ai-chat-send-btn svg {
  1430. width: 18px;
  1431. height: 18px;
  1432. }
  1433. .ai-create-footer {
  1434. display: flex;
  1435. justify-content: flex-end;
  1436. gap: 12px;
  1437. padding-top: 20px;
  1438. margin-top: 20px;
  1439. border-top: 1px solid #e5e7eb;
  1440. }
  1441. .search-results {
  1442. display: grid;
  1443. grid-template-columns: repeat(3, 1fr);
  1444. gap: 12px;
  1445. margin-top: 20px;
  1446. }
  1447. .search-result-item {
  1448. aspect-ratio: 16/9;
  1449. background: #e5e7eb;
  1450. border-radius: 10px;
  1451. cursor: pointer;
  1452. transition: all 0.2s;
  1453. border: 2px solid transparent;
  1454. overflow: hidden;
  1455. position: relative;
  1456. }
  1457. .search-result-item:hover {
  1458. border-color: #285cf5;
  1459. transform: scale(1.05);
  1460. }
  1461. .search-result-item img {
  1462. width: 100%;
  1463. height: 100%;
  1464. object-fit: cover;
  1465. }
  1466. .floating-actions {
  1467. display: flex;
  1468. gap: 12px;
  1469. margin-top: 24px;
  1470. }
  1471. .floating-btn {
  1472. flex: 1;
  1473. padding: 14px 24px;
  1474. border-radius: 12px;
  1475. border: none;
  1476. font-size: 14px;
  1477. font-weight: 600;
  1478. cursor: pointer;
  1479. transition: all 0.2s;
  1480. }
  1481. .floating-btn-secondary {
  1482. background: #f3f4f6;
  1483. color: #374151;
  1484. }
  1485. .floating-btn-secondary:hover {
  1486. background: #e5e7eb;
  1487. }
  1488. .floating-btn-primary {
  1489. background: #285cf5;
  1490. color: white;
  1491. }
  1492. .floating-btn-primary:hover {
  1493. background: #1f4ad6;
  1494. transform: translateY(-1px);
  1495. box-shadow: 0 4px 12px rgba(40, 92, 245, 0.3);
  1496. }
  1497. .generate-preview {
  1498. width: 100%;
  1499. aspect-ratio: 16/9;
  1500. background: #fafbfc;
  1501. border: 1.5px solid #e5e7eb;
  1502. border-radius: 12px;
  1503. display: flex;
  1504. align-items: center;
  1505. justify-content: center;
  1506. margin-top: 20px;
  1507. position: relative;
  1508. overflow: hidden;
  1509. }
  1510. .generate-preview img {
  1511. width: 100%;
  1512. height: 100%;
  1513. object-fit: cover;
  1514. }
  1515. .generate-loading {
  1516. position: absolute;
  1517. inset: 0;
  1518. background: rgba(255, 255, 255, 0.9);
  1519. display: none;
  1520. align-items: center;
  1521. justify-content: center;
  1522. flex-direction: column;
  1523. gap: 12px;
  1524. }
  1525. .generate-loading.active {
  1526. display: flex;
  1527. }
  1528. .loading-spinner {
  1529. width: 40px;
  1530. height: 40px;
  1531. border: 3px solid #f3f4f6;
  1532. border-top-color: #285cf5;
  1533. border-radius: 50%;
  1534. animation: spin 1s linear infinite;
  1535. }
  1536. .loading-text {
  1537. font-size: 13px;
  1538. color: #6b7280;
  1539. }
  1540. /* 右侧面板 */
  1541. .right-panel {
  1542. width: 320px;
  1543. background: white;
  1544. border-radius: 16px;
  1545. box-shadow: 0 2px 8px rgba(0, 0, 0, 0.06);
  1546. display: flex;
  1547. flex-direction: column;
  1548. transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
  1549. position: relative;
  1550. z-index: 50;
  1551. overflow: hidden;
  1552. height: calc(100vh - 32px);
  1553. max-height: calc(100vh - 32px);
  1554. }
  1555. .right-panel.collapsed {
  1556. width: 60px;
  1557. }
  1558. .right-panel.collapsed .menu-content {
  1559. opacity: 0;
  1560. pointer-events: none;
  1561. }
  1562. .right-panel.collapsed .collapse-text,
  1563. .right-panel.collapsed .outline-panel {
  1564. display: none;
  1565. }
  1566. .outline-panel {
  1567. flex: 1;
  1568. display: flex;
  1569. flex-direction: column;
  1570. border-top: 1px solid #f0f1f3;
  1571. overflow: hidden;
  1572. min-height: 0;
  1573. }
  1574. .outline-toolbar {
  1575. display: flex;
  1576. align-items: center;
  1577. justify-content: space-between;
  1578. padding: 10px 12px;
  1579. gap: 8px;
  1580. border-bottom: 1px solid #f3f4f6;
  1581. }
  1582. .outline-toolbar-left {
  1583. display: flex;
  1584. align-items: center;
  1585. gap: 6px;
  1586. font-weight: 600;
  1587. color: #111827;
  1588. }
  1589. .outline-actions {
  1590. display: flex;
  1591. gap: 6px;
  1592. }
  1593. .outline-btn {
  1594. height: 32px;
  1595. padding: 0 10px;
  1596. border-radius: 8px;
  1597. border: 1px solid #e5e7eb;
  1598. background: #f9fafb;
  1599. color: #374151;
  1600. font-size: 12px;
  1601. display: inline-flex;
  1602. align-items: center;
  1603. gap: 6px;
  1604. cursor: pointer;
  1605. transition: all 0.2s;
  1606. }
  1607. .outline-btn:hover {
  1608. background: #eef3ff;
  1609. border-color: #285cf5;
  1610. color: #1f4ad6;
  1611. }
  1612. .outline-list {
  1613. flex: 1;
  1614. overflow-y: auto;
  1615. padding: 8px 10px 12px;
  1616. display: flex;
  1617. flex-direction: column;
  1618. gap: 6px;
  1619. min-height: 0;
  1620. }
  1621. .outline-group {
  1622. background: #f9fafb;
  1623. border: 1px solid #e5e7eb;
  1624. border-radius: 10px;
  1625. padding: 8px;
  1626. display: flex;
  1627. flex-direction: column;
  1628. gap: 6px;
  1629. }
  1630. .outline-group-header {
  1631. display: flex;
  1632. align-items: center;
  1633. justify-content: space-between;
  1634. padding: 6px 8px;
  1635. border-radius: 8px;
  1636. background: #fff;
  1637. cursor: pointer;
  1638. }
  1639. .outline-group-left {
  1640. display: flex;
  1641. align-items: center;
  1642. gap: 8px;
  1643. font-weight: 600;
  1644. color: #1f2937;
  1645. }
  1646. .outline-group-title {
  1647. max-width: 160px;
  1648. overflow: hidden;
  1649. text-overflow: ellipsis;
  1650. white-space: nowrap;
  1651. font-size: 14px;
  1652. }
  1653. .outline-items {
  1654. display: flex;
  1655. flex-direction: column;
  1656. gap: 6px;
  1657. padding-top: 4px;
  1658. }
  1659. .outline-item {
  1660. background: #fff;
  1661. border: 1px solid #e5e7eb;
  1662. border-radius: 8px;
  1663. padding: 8px 10px;
  1664. display: flex;
  1665. align-items: center;
  1666. justify-content: space-between;
  1667. gap: 8px;
  1668. cursor: grab;
  1669. transition: all 0.15s;
  1670. }
  1671. .outline-item:hover {
  1672. border-color: #285cf5;
  1673. box-shadow: 0 2px 6px rgba(0,0,0,0.05);
  1674. }
  1675. .outline-item.dragging {
  1676. opacity: 0.7;
  1677. }
  1678. .outline-item-left {
  1679. display: flex;
  1680. align-items: center;
  1681. gap: 8px;
  1682. min-width: 0;
  1683. }
  1684. .outline-index {
  1685. width: 20px;
  1686. height: 20px;
  1687. border-radius: 6px;
  1688. background: #eef3ff;
  1689. color: #1f4ad6;
  1690. display: inline-flex;
  1691. align-items: center;
  1692. justify-content: center;
  1693. font-size: 12px;
  1694. font-weight: 700;
  1695. flex-shrink: 0;
  1696. }
  1697. .outline-title {
  1698. font-size: 13px;
  1699. color: #111827;
  1700. font-weight: 600;
  1701. white-space: nowrap;
  1702. overflow: hidden;
  1703. text-overflow: ellipsis;
  1704. max-width: 140px;
  1705. }
  1706. .outline-type {
  1707. padding: 2px 8px;
  1708. border-radius: 999px;
  1709. background: #f3f4f6;
  1710. color: #4b5563;
  1711. font-size: 11px;
  1712. font-weight: 600;
  1713. flex-shrink: 0;
  1714. }
  1715. .outline-handle {
  1716. color: #9ca3af;
  1717. display: inline-flex;
  1718. align-items: center;
  1719. cursor: pointer;
  1720. opacity: 0;
  1721. pointer-events: none;
  1722. transition: opacity 0.15s ease;
  1723. }
  1724. .outline-item:hover .outline-handle {
  1725. opacity: 1;
  1726. pointer-events: auto;
  1727. }
  1728. .outline-drag-handle {
  1729. color: #9ca3af;
  1730. display: inline-flex;
  1731. align-items: center;
  1732. cursor: grab;
  1733. opacity: 0;
  1734. pointer-events: none;
  1735. transition: opacity 0.15s ease;
  1736. }
  1737. .outline-group:hover .outline-drag-handle {
  1738. opacity: 1;
  1739. pointer-events: auto;
  1740. }
  1741. .outline-delete-btn {
  1742. color: #9ca3af;
  1743. display: inline-flex;
  1744. align-items: center;
  1745. cursor: pointer;
  1746. padding: 4px;
  1747. border-radius: 8px;
  1748. }
  1749. .outline-delete-btn:hover {
  1750. color: #ef4444;
  1751. background: #fef2f2;
  1752. }
  1753. .outline-group-input {
  1754. font-size: 13px;
  1755. font-weight: 600;
  1756. color: #111827;
  1757. padding: 6px 10px;
  1758. border: 1.5px solid #285cf5;
  1759. border-radius: 8px;
  1760. outline: none;
  1761. box-shadow: 0 0 0 3px rgba(40, 92, 245, 0.1);
  1762. min-width: 120px;
  1763. max-width: 220px;
  1764. }
  1765. .outline-empty {
  1766. padding: 12px;
  1767. text-align: center;
  1768. color: #9ca3af;
  1769. font-size: 13px;
  1770. border: 1px dashed #e5e7eb;
  1771. border-radius: 10px;
  1772. }
  1773. /* ========== 交互网页样式 ========== */
  1774. /* 网页配置表单 */
  1775. .web-config-form {
  1776. padding: 20px;
  1777. }
  1778. .form-group {
  1779. margin-bottom: 24px;
  1780. }
  1781. .form-label {
  1782. display: block;
  1783. font-size: 13px;
  1784. font-weight: 600;
  1785. color: #374151;
  1786. margin-bottom: 8px;
  1787. }
  1788. .form-input {
  1789. width: 100%;
  1790. height: 44px;
  1791. padding: 0 14px;
  1792. border: 1.5px solid #e5e7eb;
  1793. border-radius: 10px;
  1794. font-size: 14px;
  1795. color: #111827;
  1796. background: white;
  1797. transition: all 0.2s;
  1798. outline: none;
  1799. }
  1800. .form-input:focus {
  1801. border-color: #285cf5;
  1802. background: #f6f8ff;
  1803. }
  1804. .form-input::placeholder {
  1805. color: #9ca3af;
  1806. }
  1807. .form-hint {
  1808. margin-top: 6px;
  1809. font-size: 12px;
  1810. color: #6b7280;
  1811. }
  1812. /* 文件上传区域 */
  1813. #uploadFilePanel .form-group {
  1814. display: flex;
  1815. flex-direction: column;
  1816. gap: 12px;
  1817. }
  1818. .file-upload-area {
  1819. border: 2px dashed #e5e7eb;
  1820. border-radius: 12px;
  1821. background: #fafbfc;
  1822. padding: 40px 20px;
  1823. text-align: center;
  1824. cursor: pointer;
  1825. transition: all 0.2s;
  1826. display: flex;
  1827. flex-direction: column;
  1828. align-items: center;
  1829. justify-content: center;
  1830. gap: 10px;
  1831. min-height: 190px; /* 对齐粘贴代码区域高度 */
  1832. }
  1833. .file-upload-area:hover {
  1834. border-color: #285cf5;
  1835. background: #f6f8ff;
  1836. }
  1837. .file-upload-area.dragover {
  1838. border-color: #285cf5;
  1839. background: #f6f8ff;
  1840. border-style: solid;
  1841. }
  1842. .file-upload-area svg {
  1843. color: #9ca3af;
  1844. margin-bottom: 12px;
  1845. }
  1846. .upload-text {
  1847. font-size: 14px;
  1848. font-weight: 500;
  1849. color: #374151;
  1850. margin-bottom: 6px;
  1851. }
  1852. .upload-hint {
  1853. font-size: 12px;
  1854. color: #6b7280;
  1855. }
  1856. /* 状态提示 */
  1857. .web-status {
  1858. margin-top: 16px;
  1859. padding: 12px 16px;
  1860. background: #f9fafb;
  1861. border-radius: 10px;
  1862. display: flex;
  1863. align-items: center;
  1864. gap: 10px;
  1865. }
  1866. .web-status .status-icon {
  1867. font-size: 18px;
  1868. }
  1869. .web-status .status-text {
  1870. font-size: 13px;
  1871. color: #6b7280;
  1872. font-weight: 500;
  1873. }
  1874. .web-status.loading .status-icon {
  1875. animation: spin 1s linear infinite;
  1876. }
  1877. .web-status.success {
  1878. background: #f0fdf4;
  1879. border: 1px solid #86efac;
  1880. }
  1881. .web-status.success .status-icon {
  1882. color: #22c55e;
  1883. }
  1884. .web-status.success .status-text {
  1885. color: #16a34a;
  1886. }
  1887. .web-status.error {
  1888. background: #fef2f2;
  1889. border: 1px solid #fecaca;
  1890. }
  1891. .web-status.error .status-icon {
  1892. color: #ef4444;
  1893. }
  1894. .web-status.error .status-text {
  1895. color: #dc2626;
  1896. }
  1897. @keyframes spin {
  1898. from { transform: rotate(0deg); }
  1899. to { transform: rotate(360deg); }
  1900. }
  1901. /* 配置操作按钮 */
  1902. .web-config-actions {
  1903. display: flex;
  1904. gap: 12px;
  1905. margin-top: 24px;
  1906. }
  1907. .config-btn {
  1908. flex: 1;
  1909. height: 44px;
  1910. border-radius: 10px;
  1911. border: none;
  1912. font-size: 14px;
  1913. font-weight: 600;
  1914. cursor: pointer;
  1915. transition: all 0.2s;
  1916. }
  1917. .config-btn-secondary {
  1918. background: #f3f4f6;
  1919. color: #6b7280;
  1920. }
  1921. .config-btn-secondary:hover {
  1922. background: #e5e7eb;
  1923. }
  1924. .config-btn-primary {
  1925. background: #285cf5;
  1926. color: white;
  1927. }
  1928. .config-btn-primary:hover {
  1929. background: #1f4ad6;
  1930. }
  1931. .config-btn-primary:disabled {
  1932. opacity: 0.5;
  1933. cursor: not-allowed;
  1934. }
  1935. .status-btn,
  1936. .crawl-btn {
  1937. display: inline-flex;
  1938. align-items: center;
  1939. justify-content: center;
  1940. gap: 8px;
  1941. }
  1942. .status-btn .btn-status-icon,
  1943. .crawl-btn .btn-status-icon {
  1944. display: inline-flex;
  1945. align-items: center;
  1946. justify-content: center;
  1947. }
  1948. .status-btn .btn-status-icon svg,
  1949. .crawl-btn .btn-status-icon svg {
  1950. width: 16px;
  1951. height: 16px;
  1952. stroke: currentColor;
  1953. fill: none;
  1954. }
  1955. .status-btn.status-loading,
  1956. .crawl-btn.status-loading {
  1957. opacity: 0.9;
  1958. }
  1959. .status-btn.status-loading .btn-status-icon svg,
  1960. .crawl-btn.status-loading .btn-status-icon svg {
  1961. animation: spin 1s linear infinite;
  1962. }
  1963. .status-btn.status-success,
  1964. .crawl-btn.status-success {
  1965. background: #f0fdf4;
  1966. color: #16a34a;
  1967. border: 1px solid #86efac;
  1968. }
  1969. .status-btn.status-error,
  1970. .crawl-btn.status-error {
  1971. background: #fef2f2;
  1972. color: #dc2626;
  1973. border: 1px solid #fecaca;
  1974. }
  1975. /* Upload Tabs */
  1976. .upload-tabs {
  1977. display: flex;
  1978. margin-bottom: 20px;
  1979. border-bottom: 1px solid #e5e7eb;
  1980. }
  1981. .upload-tab {
  1982. padding: 10px 16px;
  1983. border: none;
  1984. background-color: transparent;
  1985. cursor: pointer;
  1986. font-size: 14px;
  1987. color: #6b7280;
  1988. margin-bottom: -1px;
  1989. border-bottom: 2px solid transparent;
  1990. }
  1991. .upload-tab.active {
  1992. color: #285cf5;
  1993. border-bottom-color: #285cf5;
  1994. font-weight: 600;
  1995. }
  1996. .code-textarea {
  1997. width: 100%;
  1998. min-height: 190px; /* Increased height to match file upload area */
  1999. border: 1px solid #d1d5db;
  2000. border-radius: 8px;
  2001. padding: 12px;
  2002. font-family: monospace;
  2003. font-size: 13px;
  2004. resize: vertical;
  2005. transition: border-color 0.2s;
  2006. }
  2007. .code-textarea:focus {
  2008. outline: none;
  2009. border-color: #285cf5;
  2010. }
  2011. .file-name-display {
  2012. margin-top: 12px;
  2013. font-size: 13px;
  2014. color: #4b5563;
  2015. background-color: #f9fafb;
  2016. border-radius: 6px;
  2017. padding: 8px 12px;
  2018. border: 1px solid #e5e7eb;
  2019. display: none; /* Hidden by default */
  2020. }
  2021. /* 网页中心浮窗 */
  2022. .web-center-content {
  2023. background: white;
  2024. border-radius: 20px;
  2025. padding: 32px;
  2026. max-width: 900px;
  2027. width: 90%;
  2028. max-height: 85vh;
  2029. display: flex;
  2030. flex-direction: column;
  2031. box-shadow: 0 20px 60px rgba(0, 0, 0, 0.2);
  2032. transform: scale(0.95);
  2033. transition: transform 0.3s;
  2034. }
  2035. .floating-modal.active .web-center-content {
  2036. transform: scale(1);
  2037. }
  2038. .web-filters {
  2039. display: flex;
  2040. gap: 16px;
  2041. margin-bottom: 24px;
  2042. padding-bottom: 20px;
  2043. border-bottom: 1px solid #e5e7eb;
  2044. }
  2045. .web-grid {
  2046. display: grid;
  2047. grid-template-columns: repeat(3, 1fr);
  2048. gap: 16px;
  2049. flex: 1;
  2050. overflow-y: auto;
  2051. padding: 4px;
  2052. }
  2053. .web-card {
  2054. background: #fafbfc;
  2055. border: 1.5px solid #e5e7eb;
  2056. border-radius: 14px;
  2057. padding: 16px;
  2058. cursor: pointer;
  2059. transition: all 0.2s;
  2060. position: relative;
  2061. display: flex;
  2062. flex-direction: column;
  2063. }
  2064. .web-card:hover {
  2065. border-color: #285cf5;
  2066. background: #f6f8ff;
  2067. transform: translateY(-2px);
  2068. box-shadow: 0 4px 12px rgba(40, 92, 245, 0.15);
  2069. }
  2070. .web-card.selected {
  2071. border-color: #285cf5;
  2072. background: #f6f8ff;
  2073. }
  2074. .web-card.selected::after {
  2075. content: '';
  2076. position: absolute;
  2077. top: 10px;
  2078. right: 10px;
  2079. width: 20px;
  2080. height: 20px;
  2081. background: #285cf5;
  2082. border-radius: 50%;
  2083. 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");
  2084. background-size: 14px;
  2085. background-position: center;
  2086. background-repeat: no-repeat;
  2087. }
  2088. .web-preview {
  2089. width: 100%;
  2090. aspect-ratio: 16/9;
  2091. border-radius: 10px;
  2092. background: linear-gradient(135deg, #eef3ff 0%, #f6f8ff 100%);
  2093. display: flex;
  2094. align-items: center;
  2095. justify-content: center;
  2096. margin-bottom: 12px;
  2097. border: 1px solid #dbe6ff;
  2098. overflow: hidden;
  2099. }
  2100. .web-preview svg {
  2101. width: 36px;
  2102. height: 36px;
  2103. color: #285cf5;
  2104. }
  2105. .web-preview img {
  2106. width: 100%;
  2107. height: 100%;
  2108. object-fit: cover;
  2109. }
  2110. .web-info {
  2111. flex: 1;
  2112. }
  2113. .web-name {
  2114. font-size: 14px;
  2115. font-weight: 600;
  2116. color: #111827;
  2117. margin-bottom: 4px;
  2118. }
  2119. .web-description {
  2120. font-size: 12px;
  2121. color: #6b7280;
  2122. line-height: 1.5;
  2123. margin-bottom: 8px;
  2124. display: -webkit-box;
  2125. -webkit-line-clamp: 2;
  2126. -webkit-box-orient: vertical;
  2127. overflow: hidden;
  2128. }
  2129. .web-meta {
  2130. display: flex;
  2131. gap: 8px;
  2132. flex-wrap: wrap;
  2133. }
  2134. .web-tag {
  2135. padding: 3px 8px;
  2136. background: #f3f4f6;
  2137. border-radius: 6px;
  2138. font-size: 11px;
  2139. color: #6b7280;
  2140. font-weight: 500;
  2141. }
  2142. .web-center-footer {
  2143. display: flex;
  2144. align-items: center;
  2145. justify-content: space-between;
  2146. padding-top: 20px;
  2147. margin-top: 20px;
  2148. border-top: 1px solid #e5e7eb;
  2149. }
  2150. /* 网页详情浮窗 */
  2151. .web-detail-modal {
  2152. position: fixed;
  2153. top: 0;
  2154. left: 0;
  2155. right: 0;
  2156. bottom: 0;
  2157. background: rgba(0, 0, 0, 0.7);
  2158. display: none;
  2159. align-items: center;
  2160. justify-content: center;
  2161. z-index: 10001;
  2162. animation: fadeIn 0.3s;
  2163. }
  2164. .web-detail-modal.active {
  2165. display: flex;
  2166. }
  2167. .web-detail-content {
  2168. background: white;
  2169. border-radius: 20px;
  2170. width: 90%;
  2171. max-width: 1200px;
  2172. height: 85vh;
  2173. display: grid;
  2174. grid-template-columns: 1fr 360px;
  2175. overflow: hidden;
  2176. box-shadow: 0 20px 60px rgba(0, 0, 0, 0.3);
  2177. }
  2178. .web-preview-area {
  2179. background: #f9fafb;
  2180. display: flex;
  2181. flex-direction: column;
  2182. border-right: 1px solid #e5e7eb;
  2183. }
  2184. .web-preview-header {
  2185. padding: 20px 24px;
  2186. border-bottom: 1px solid #e5e7eb;
  2187. display: flex;
  2188. align-items: center;
  2189. justify-content: space-between;
  2190. }
  2191. .web-preview-title {
  2192. font-size: 16px;
  2193. font-weight: 600;
  2194. color: #111827;
  2195. }
  2196. .web-fullscreen-btn {
  2197. padding: 8px 12px;
  2198. background: #f3f4f6;
  2199. border: none;
  2200. border-radius: 8px;
  2201. color: #6b7280;
  2202. font-size: 13px;
  2203. cursor: pointer;
  2204. transition: all 0.2s;
  2205. display: flex;
  2206. align-items: center;
  2207. gap: 6px;
  2208. }
  2209. .web-fullscreen-btn:hover {
  2210. background: #e5e7eb;
  2211. color: #374151;
  2212. }
  2213. .web-preview-iframe {
  2214. flex: 1;
  2215. padding: 20px;
  2216. }
  2217. .web-preview-iframe iframe {
  2218. width: 100%;
  2219. height: 100%;
  2220. border: 1px solid #e5e7eb;
  2221. border-radius: 12px;
  2222. background: white;
  2223. }
  2224. .web-info-area {
  2225. background: white;
  2226. display: flex;
  2227. flex-direction: column;
  2228. overflow-y: auto;
  2229. }
  2230. .web-detail-header {
  2231. padding: 24px;
  2232. border-bottom: 1px solid #e5e7eb;
  2233. }
  2234. .web-detail-name {
  2235. font-size: 18px;
  2236. font-weight: 600;
  2237. color: #111827;
  2238. margin-bottom: 12px;
  2239. }
  2240. .web-meta-item {
  2241. display: flex;
  2242. align-items: center;
  2243. gap: 8px;
  2244. margin-bottom: 8px;
  2245. font-size: 13px;
  2246. color: #6b7280;
  2247. }
  2248. .web-meta-item svg {
  2249. width: 16px;
  2250. height: 16px;
  2251. }
  2252. .web-detail-body {
  2253. flex: 1;
  2254. padding: 24px;
  2255. }
  2256. .web-detail-section {
  2257. margin-bottom: 24px;
  2258. }
  2259. .web-detail-section-title {
  2260. font-size: 14px;
  2261. font-weight: 600;
  2262. color: #374151;
  2263. margin-bottom: 8px;
  2264. }
  2265. .web-detail-section-content {
  2266. font-size: 13px;
  2267. color: #6b7280;
  2268. line-height: 1.6;
  2269. }
  2270. .web-detail-footer {
  2271. padding: 20px 24px;
  2272. border-top: 1px solid #e5e7eb;
  2273. display: flex;
  2274. gap: 12px;
  2275. }
  2276. .web-detail-btn {
  2277. flex: 1;
  2278. height: 44px;
  2279. border-radius: 10px;
  2280. border: none;
  2281. font-size: 14px;
  2282. font-weight: 600;
  2283. cursor: pointer;
  2284. transition: all 0.2s;
  2285. }
  2286. .web-detail-btn-secondary {
  2287. background: #f3f4f6;
  2288. color: #6b7280;
  2289. }
  2290. .web-detail-btn-secondary:hover {
  2291. background: #e5e7eb;
  2292. }
  2293. .web-detail-btn-primary {
  2294. background: #285cf5;
  2295. color: white;
  2296. }
  2297. .web-detail-btn-primary:hover {
  2298. background: #1f4ad6;
  2299. }
  2300. /* ========== 创建入口弹窗 ========== */
  2301. .modal-overlay {
  2302. position: fixed;
  2303. top: 0;
  2304. left: 0;
  2305. right: 0;
  2306. bottom: 0;
  2307. background: rgba(0, 0, 0, 0.5);
  2308. backdrop-filter: blur(4px);
  2309. display: flex;
  2310. align-items: center;
  2311. justify-content: center;
  2312. z-index: 1000;
  2313. opacity: 0;
  2314. pointer-events: none;
  2315. transition: opacity 0.3s;
  2316. }
  2317. .modal-overlay.active {
  2318. opacity: 1;
  2319. pointer-events: all;
  2320. }
  2321. .settings-modal {
  2322. background: #fff;
  2323. border-radius: 16px;
  2324. padding: 20px;
  2325. width: 360px;
  2326. box-shadow: 0 16px 40px rgba(0,0,0,0.16);
  2327. display: flex;
  2328. flex-direction: column;
  2329. gap: 16px;
  2330. position: relative;
  2331. }
  2332. .settings-header {
  2333. display: flex;
  2334. align-items: center;
  2335. justify-content: space-between;
  2336. margin-bottom: 4px;
  2337. }
  2338. .settings-title {
  2339. font-size: 16px;
  2340. font-weight: 700;
  2341. color: #111827;
  2342. }
  2343. .settings-body {
  2344. display: flex;
  2345. flex-direction: column;
  2346. gap: 12px;
  2347. }
  2348. .settings-row {
  2349. display: flex;
  2350. gap: 12px;
  2351. }
  2352. .settings-actions {
  2353. display: flex;
  2354. justify-content: flex-end;
  2355. gap: 10px;
  2356. }
  2357. .settings-btn {
  2358. height: 36px;
  2359. padding: 0 14px;
  2360. border-radius: 10px;
  2361. border: 1px solid #e5e7eb;
  2362. background: #f9fafb;
  2363. color: #374151;
  2364. font-size: 13px;
  2365. font-weight: 600;
  2366. cursor: pointer;
  2367. transition: all 0.2s;
  2368. }
  2369. .settings-btn:hover {
  2370. background: #fff;
  2371. border-color: #d1d5db;
  2372. }
  2373. .settings-btn.primary {
  2374. background: #285cf5;
  2375. color: #fff;
  2376. border-color: #285cf5;
  2377. box-shadow: 0 4px 12px rgba(40, 92, 245, 0.24);
  2378. }
  2379. .settings-btn.primary:hover {
  2380. background: #1f4ad6;
  2381. }
  2382. .confirm-modal {
  2383. background: #fff;
  2384. border-radius: 16px;
  2385. padding: 22px;
  2386. width: 360px;
  2387. box-shadow: 0 16px 40px rgba(0,0,0,0.16);
  2388. display: flex;
  2389. flex-direction: column;
  2390. gap: 14px;
  2391. }
  2392. .confirm-title {
  2393. font-size: 16px;
  2394. font-weight: 700;
  2395. color: #111827;
  2396. }
  2397. .confirm-text {
  2398. font-size: 13px;
  2399. color: #4b5563;
  2400. line-height: 1.6;
  2401. }
  2402. .confirm-actions {
  2403. display: flex;
  2404. justify-content: flex-end;
  2405. gap: 10px;
  2406. }
  2407. /* PPT解析浮窗 */
  2408. .ppt-parse-overlay {
  2409. position: fixed;
  2410. inset: 0;
  2411. background: rgba(0, 0, 0, 0.4);
  2412. backdrop-filter: blur(2px);
  2413. display: flex;
  2414. align-items: center;
  2415. justify-content: center;
  2416. z-index: 1200;
  2417. opacity: 0;
  2418. pointer-events: none;
  2419. transition: opacity 0.25s ease;
  2420. }
  2421. .ppt-parse-overlay.active {
  2422. opacity: 1;
  2423. pointer-events: all;
  2424. }
  2425. .ppt-parse-card {
  2426. background: #ffffff;
  2427. border-radius: 16px;
  2428. padding: 24px;
  2429. width: 360px;
  2430. box-shadow: 0 16px 40px rgba(0,0,0,0.16);
  2431. display: flex;
  2432. flex-direction: column;
  2433. gap: 14px;
  2434. text-align: center;
  2435. }
  2436. .ppt-parse-icon {
  2437. width: 56px;
  2438. height: 56px;
  2439. border-radius: 14px;
  2440. margin: 0 auto;
  2441. display: flex;
  2442. align-items: center;
  2443. justify-content: center;
  2444. background: #eef3ff;
  2445. color: #285cf5;
  2446. }
  2447. .ppt-parse-icon svg {
  2448. width: 26px;
  2449. height: 26px;
  2450. stroke: currentColor;
  2451. fill: none;
  2452. }
  2453. .ppt-parse-icon.success {
  2454. background: #f0fdf4;
  2455. color: #16a34a;
  2456. }
  2457. .ppt-parse-icon.error {
  2458. background: #fef2f2;
  2459. color: #dc2626;
  2460. }
  2461. .ppt-parse-title {
  2462. font-size: 16px;
  2463. font-weight: 700;
  2464. color: #111827;
  2465. }
  2466. .ppt-parse-desc {
  2467. font-size: 13px;
  2468. color: #6b7280;
  2469. }
  2470. .ppt-parse-actions {
  2471. display: flex;
  2472. gap: 10px;
  2473. margin-top: 4px;
  2474. }
  2475. .ppt-parse-btn {
  2476. flex: 1;
  2477. height: 40px;
  2478. border-radius: 10px;
  2479. border: none;
  2480. font-size: 14px;
  2481. font-weight: 600;
  2482. cursor: pointer;
  2483. transition: all 0.2s;
  2484. }
  2485. .ppt-parse-btn.primary {
  2486. background: #285cf5;
  2487. color: white;
  2488. }
  2489. .ppt-parse-btn.primary:hover {
  2490. background: #1f4ad6;
  2491. }
  2492. .ppt-parse-btn.secondary {
  2493. background: #f3f4f6;
  2494. color: #4b5563;
  2495. }
  2496. .ppt-parse-btn.secondary:hover {
  2497. background: #e5e7eb;
  2498. }
  2499. .create-modal {
  2500. background: white;
  2501. border-radius: 20px;
  2502. padding: 40px;
  2503. max-width: 900px;
  2504. width: 90%;
  2505. max-height: 85vh;
  2506. overflow-y: auto;
  2507. box-shadow: 0 20px 60px rgba(0, 0, 0, 0.2);
  2508. transform: scale(0.9);
  2509. transition: transform 0.3s;
  2510. }
  2511. .modal-overlay.active .create-modal {
  2512. transform: scale(1);
  2513. }
  2514. .modal-header {
  2515. text-align: center;
  2516. margin-bottom: 32px;
  2517. }
  2518. .modal-header h2 {
  2519. font-size: 28px;
  2520. font-weight: 700;
  2521. color: #111827;
  2522. margin-bottom: 8px;
  2523. }
  2524. .modal-header p {
  2525. font-size: 15px;
  2526. color: #6b7280;
  2527. }
  2528. .create-options {
  2529. display: grid;
  2530. grid-template-columns: repeat(2, 1fr);
  2531. gap: 16px;
  2532. }
  2533. .create-card {
  2534. background: #fafbfc;
  2535. border: 1.5px solid #e5e7eb;
  2536. border-radius: 16px;
  2537. padding: 24px 20px;
  2538. cursor: pointer;
  2539. transition: all 0.2s;
  2540. display: flex;
  2541. flex-direction: column;
  2542. align-items: center;
  2543. text-align: center;
  2544. position: relative;
  2545. }
  2546. .create-card:hover {
  2547. border-color: #285cf5;
  2548. background: #f6f8ff;
  2549. transform: translateY(-2px);
  2550. box-shadow: 0 4px 12px rgba(40, 92, 245, 0.15);
  2551. }
  2552. .create-card.featured {
  2553. background: linear-gradient(135deg, #eef3ff 0%, #f6f8ff 100%);
  2554. border-color: #285cf5;
  2555. }
  2556. .create-card.featured::before {
  2557. content: '推荐';
  2558. position: absolute;
  2559. top: 12px;
  2560. right: 12px;
  2561. background: #285cf5;
  2562. color: white;
  2563. font-size: 11px;
  2564. font-weight: 600;
  2565. padding: 4px 10px;
  2566. border-radius: 12px;
  2567. }
  2568. .create-icon {
  2569. width: 56px;
  2570. height: 56px;
  2571. border-radius: 14px;
  2572. background: white;
  2573. display: flex;
  2574. align-items: center;
  2575. justify-content: center;
  2576. margin-bottom: 16px;
  2577. }
  2578. .create-icon svg {
  2579. width: 28px;
  2580. height: 28px;
  2581. color: #6b7280;
  2582. }
  2583. .create-card.featured .create-icon svg {
  2584. color: #285cf5;
  2585. }
  2586. .create-card h3 {
  2587. font-size: 16px;
  2588. font-weight: 600;
  2589. color: #111827;
  2590. margin-bottom: 6px;
  2591. }
  2592. .create-card p {
  2593. font-size: 13px;
  2594. color: #6b7280;
  2595. line-height: 1.4;
  2596. }
  2597. .modal-close {
  2598. position: absolute;
  2599. top: 20px;
  2600. right: 20px;
  2601. width: 36px;
  2602. height: 36px;
  2603. border-radius: 10px;
  2604. border: none;
  2605. background: #f3f4f6;
  2606. cursor: pointer;
  2607. display: flex;
  2608. align-items: center;
  2609. justify-content: center;
  2610. transition: all 0.2s;
  2611. }
  2612. .modal-close:hover {
  2613. background: #e5e7eb;
  2614. }
  2615. /* ========== 发布弹窗 ========== */
  2616. .publish-modal {
  2617. background: white;
  2618. border-radius: 20px;
  2619. padding: 28px;
  2620. max-width: 780px;
  2621. width: 90%;
  2622. max-height: 85vh;
  2623. overflow-y: auto;
  2624. box-shadow: 0 20px 60px rgba(0, 0, 0, 0.2);
  2625. transform: scale(0.9);
  2626. transition: transform 0.3s;
  2627. position: relative;
  2628. }
  2629. .modal-overlay.active .publish-modal {
  2630. transform: scale(1);
  2631. }
  2632. .publish-header {
  2633. margin-bottom: 18px;
  2634. padding-bottom: 12px;
  2635. border-bottom: 1px solid #f0f1f3;
  2636. text-align: center;
  2637. }
  2638. .publish-title {
  2639. font-size: 22px;
  2640. font-weight: 700;
  2641. color: #111827;
  2642. margin-bottom: 8px;
  2643. }
  2644. .publish-course-name {
  2645. font-size: 18px;
  2646. font-weight: 600;
  2647. color: #111827;
  2648. padding: 10px 16px;
  2649. border: 1.5px solid transparent;
  2650. border-radius: 10px;
  2651. cursor: text;
  2652. transition: all 0.2s;
  2653. display: inline-block;
  2654. min-width: 120px;
  2655. max-width: 500px;
  2656. }
  2657. .publish-course-name:hover {
  2658. background: #fafbfc;
  2659. border-color: #e5e7eb;
  2660. }
  2661. .publish-course-name-input {
  2662. font-size: 18px;
  2663. font-weight: 600;
  2664. color: #111827;
  2665. padding: 10px 16px;
  2666. border: 1.5px solid #285cf5;
  2667. border-radius: 10px;
  2668. outline: none;
  2669. box-shadow: 0 0 0 3px rgba(40, 92, 245, 0.1);
  2670. min-width: 120px;
  2671. max-width: 500px;
  2672. text-align: center;
  2673. }
  2674. .publish-content {
  2675. display: grid;
  2676. grid-template-columns: 1fr 260px;
  2677. gap: 16px;
  2678. }
  2679. .publish-form {
  2680. display: flex;
  2681. flex-direction: column;
  2682. gap: 14px;
  2683. }
  2684. .publish-cover-section {
  2685. display: flex;
  2686. flex-direction: column;
  2687. gap: 12px;
  2688. }
  2689. .publish-cover-label {
  2690. font-size: 14px;
  2691. font-weight: 600;
  2692. color: #374151;
  2693. }
  2694. .publish-cover-wrapper {
  2695. position: relative;
  2696. width: 100%;
  2697. aspect-ratio: 16/9;
  2698. border-radius: 12px;
  2699. overflow: hidden;
  2700. border: 2px dashed #d1d5db;
  2701. background: #fafbfc;
  2702. cursor: default;
  2703. transition: all 0.2s;
  2704. display: flex;
  2705. align-items: center;
  2706. justify-content: center;
  2707. }
  2708. .publish-cover-wrapper:hover {
  2709. border-color: #285cf5;
  2710. background: #f6f8ff;
  2711. }
  2712. .publish-cover-wrapper img {
  2713. width: 100%;
  2714. height: 100%;
  2715. object-fit: cover;
  2716. position: absolute;
  2717. top: 0;
  2718. left: 0;
  2719. }
  2720. .publish-cover-placeholder {
  2721. color: #9ca3af;
  2722. text-align: center;
  2723. z-index: 1;
  2724. }
  2725. .publish-cover-placeholder svg {
  2726. width: 48px;
  2727. height: 48px;
  2728. margin-bottom: 8px;
  2729. color: #d1d5db;
  2730. }
  2731. .publish-cover-placeholder p {
  2732. font-size: 13px;
  2733. font-weight: 500;
  2734. }
  2735. /* 课程封面悬浮菜单 */
  2736. .publish-cover-menu {
  2737. position: absolute;
  2738. top: 50%;
  2739. left: 50%;
  2740. transform: translate(-50%, -50%);
  2741. background: white;
  2742. border-radius: 12px;
  2743. box-shadow: 0 8px 24px rgba(0, 0, 0, 0.15);
  2744. padding: 8px;
  2745. display: none;
  2746. flex-direction: column;
  2747. gap: 4px;
  2748. z-index: 1000;
  2749. min-width: 160px;
  2750. }
  2751. .publish-cover-wrapper:hover .publish-cover-menu {
  2752. display: flex;
  2753. }
  2754. .publish-cover-menu-item {
  2755. padding: 12px 14px;
  2756. border-radius: 8px;
  2757. cursor: pointer;
  2758. transition: all 0.2s;
  2759. display: flex;
  2760. align-items: center;
  2761. gap: 10px;
  2762. font-size: 13px;
  2763. color: #374151;
  2764. font-weight: 500;
  2765. }
  2766. .publish-cover-menu-item:hover {
  2767. background: #eef3ff;
  2768. color: #285cf5;
  2769. }
  2770. .publish-cover-menu-item svg {
  2771. width: 18px;
  2772. height: 18px;
  2773. flex-shrink: 0;
  2774. }
  2775. .publish-cover-actions {
  2776. position: absolute;
  2777. bottom: 12px;
  2778. right: 12px;
  2779. display: none;
  2780. gap: 8px;
  2781. z-index: 999;
  2782. }
  2783. .publish-cover-wrapper:hover .publish-cover-actions {
  2784. display: flex;
  2785. }
  2786. .cover-action-btn {
  2787. width: 36px;
  2788. height: 36px;
  2789. border-radius: 8px;
  2790. border: none;
  2791. background: rgba(255, 255, 255, 0.95);
  2792. backdrop-filter: blur(4px);
  2793. cursor: pointer;
  2794. display: flex;
  2795. align-items: center;
  2796. justify-content: center;
  2797. transition: all 0.2s;
  2798. box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
  2799. }
  2800. .cover-action-btn:hover {
  2801. background: white;
  2802. transform: translateY(-2px);
  2803. box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
  2804. }
  2805. .cover-action-btn svg {
  2806. width: 18px;
  2807. height: 18px;
  2808. color: #6b7280;
  2809. }
  2810. .cover-action-btn:hover svg {
  2811. color: #285cf5;
  2812. }
  2813. .cover-action-btn.delete:hover svg {
  2814. color: #dc2626;
  2815. }
  2816. .form-group {
  2817. display: flex;
  2818. flex-direction: column;
  2819. gap: 10px;
  2820. }
  2821. .form-label {
  2822. font-size: 14px;
  2823. font-weight: 600;
  2824. color: #374151;
  2825. display: flex;
  2826. align-items: center;
  2827. gap: 6px;
  2828. }
  2829. .form-label .required {
  2830. color: #dc2626;
  2831. }
  2832. .form-select {
  2833. height: 44px;
  2834. padding: 0 16px;
  2835. border: 1.5px solid #e5e7eb;
  2836. border-radius: 12px;
  2837. font-size: 14px;
  2838. color: #111827;
  2839. background: white;
  2840. cursor: pointer;
  2841. transition: all 0.2s;
  2842. outline: none;
  2843. appearance: none;
  2844. 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");
  2845. background-repeat: no-repeat;
  2846. background-position: right 16px center;
  2847. padding-right: 40px;
  2848. }
  2849. .form-select:hover {
  2850. border-color: #d1d5db;
  2851. }
  2852. .form-select:focus {
  2853. border-color: #285cf5;
  2854. box-shadow: 0 0 0 3px rgba(40, 92, 245, 0.1);
  2855. }
  2856. .form-row {
  2857. display: grid;
  2858. grid-template-columns: 1fr 1fr;
  2859. gap: 16px;
  2860. }
  2861. .visibility-options {
  2862. display: flex;
  2863. flex-direction: column;
  2864. gap: 12px;
  2865. }
  2866. .radio-option {
  2867. display: flex;
  2868. align-items: center;
  2869. padding: 14px 16px;
  2870. border: 1.5px solid #e5e7eb;
  2871. border-radius: 12px;
  2872. cursor: pointer;
  2873. transition: all 0.2s;
  2874. background: white;
  2875. }
  2876. .radio-option:hover {
  2877. border-color: #d1d5db;
  2878. background: #fafbfc;
  2879. }
  2880. .radio-option.selected {
  2881. border-color: #285cf5;
  2882. background: #f6f8ff;
  2883. }
  2884. .radio-option input[type="radio"] {
  2885. display: none;
  2886. }
  2887. .radio-custom {
  2888. width: 20px;
  2889. height: 20px;
  2890. border: 2px solid #d1d5db;
  2891. border-radius: 50%;
  2892. margin-right: 12px;
  2893. position: relative;
  2894. flex-shrink: 0;
  2895. transition: all 0.2s;
  2896. }
  2897. .radio-option.selected .radio-custom {
  2898. border-color: #285cf5;
  2899. }
  2900. .radio-custom::after {
  2901. content: '';
  2902. position: absolute;
  2903. top: 50%;
  2904. left: 50%;
  2905. transform: translate(-50%, -50%) scale(0);
  2906. width: 10px;
  2907. height: 10px;
  2908. border-radius: 50%;
  2909. background: #285cf5;
  2910. transition: transform 0.2s;
  2911. }
  2912. .radio-option.selected .radio-custom::after {
  2913. transform: translate(-50%, -50%) scale(1);
  2914. }
  2915. .radio-content {
  2916. flex: 1;
  2917. }
  2918. .radio-label {
  2919. font-size: 14px;
  2920. font-weight: 600;
  2921. color: #111827;
  2922. margin-bottom: 4px;
  2923. }
  2924. .radio-description {
  2925. font-size: 13px;
  2926. color: #6b7280;
  2927. line-height: 1.4;
  2928. }
  2929. .publish-actions {
  2930. display: flex;
  2931. gap: 10px;
  2932. margin-top: 18px;
  2933. padding-top: 14px;
  2934. border-top: 1px solid #f0f1f3;
  2935. }
  2936. .publish-btn {
  2937. flex: 1;
  2938. height: 44px;
  2939. border-radius: 12px;
  2940. font-size: 15px;
  2941. font-weight: 600;
  2942. cursor: pointer;
  2943. transition: all 0.2s;
  2944. border: none;
  2945. display: flex;
  2946. align-items: center;
  2947. justify-content: center;
  2948. gap: 8px;
  2949. }
  2950. .publish-btn-cancel {
  2951. background: white;
  2952. color: #6b7280;
  2953. border: 1.5px solid #e5e7eb;
  2954. }
  2955. .publish-btn-cancel:hover {
  2956. background: #f9fafb;
  2957. border-color: #d1d5db;
  2958. }
  2959. .publish-btn-confirm {
  2960. background: #285cf5;
  2961. color: white;
  2962. box-shadow: 0 2px 8px rgba(40, 92, 245, 0.2);
  2963. }
  2964. .publish-btn-confirm:hover {
  2965. background: #1f4ad6;
  2966. box-shadow: 0 4px 12px rgba(40, 92, 245, 0.3);
  2967. transform: translateY(-1px);
  2968. }
  2969. .publish-btn-confirm svg {
  2970. width: 18px;
  2971. height: 18px;
  2972. }
  2973. /* ========== 互动工具样式 ========== */
  2974. .tools-intro {
  2975. padding: 20px;
  2976. text-align: center;
  2977. background: #fafbfc;
  2978. border-radius: 12px;
  2979. margin: 16px 20px;
  2980. }
  2981. .tools-intro-image {
  2982. margin-bottom: 12px;
  2983. display: flex;
  2984. justify-content: center;
  2985. }
  2986. .tools-intro-text {
  2987. font-size: 13px;
  2988. color: #6b7280;
  2989. font-weight: 500;
  2990. }
  2991. .tools-grid {
  2992. display: grid;
  2993. grid-template-columns: repeat(2, 1fr);
  2994. gap: 12px;
  2995. padding: 0 20px 20px;
  2996. }
  2997. .tool-card {
  2998. background: white;
  2999. border: 1.5px solid #e5e7eb;
  3000. border-radius: 12px;
  3001. padding: 14px 16px;
  3002. cursor: pointer;
  3003. transition: all 0.2s;
  3004. display: flex;
  3005. flex-direction: row;
  3006. align-items: center;
  3007. gap: 12px;
  3008. }
  3009. .tool-card:hover {
  3010. border-color: #285cf5;
  3011. background: #f6f8ff;
  3012. transform: translateY(-2px);
  3013. box-shadow: 0 4px 12px rgba(40, 92, 245, 0.15);
  3014. }
  3015. .tool-icon {
  3016. width: 40px;
  3017. height: 40px;
  3018. border-radius: 10px;
  3019. background: #f9fafb;
  3020. display: flex;
  3021. align-items: center;
  3022. justify-content: center;
  3023. transition: all 0.2s;
  3024. flex-shrink: 0;
  3025. }
  3026. .tool-card:hover .tool-icon {
  3027. background: #eef3ff;
  3028. }
  3029. .tool-icon svg {
  3030. width: 20px;
  3031. height: 20px;
  3032. color: #6b7280;
  3033. }
  3034. .tool-card:hover .tool-icon svg {
  3035. color: #285cf5;
  3036. }
  3037. .tool-name {
  3038. font-size: 14px;
  3039. font-weight: 600;
  3040. color: #374151;
  3041. flex: 1;
  3042. }
  3043. /* 面板内容包装器 */
  3044. .panel-content-wrapper {
  3045. flex: 1;
  3046. position: relative;
  3047. overflow: hidden;
  3048. }
  3049. /* 工具配置视图 */
  3050. .tools-config-view {
  3051. position: absolute;
  3052. top: 0;
  3053. left: 0;
  3054. right: 0;
  3055. bottom: 0;
  3056. background: white;
  3057. display: flex;
  3058. flex-direction: column;
  3059. z-index: 10;
  3060. }
  3061. .tools-config-view.hidden {
  3062. display: none;
  3063. }
  3064. /* 通用hidden类 */
  3065. .hidden {
  3066. display: none !important;
  3067. }
  3068. .config-header {
  3069. padding: 20px 20px 16px;
  3070. border-bottom: 1px solid #f3f4f6;
  3071. display: flex;
  3072. align-items: center;
  3073. gap: 12px;
  3074. }
  3075. .config-back-btn {
  3076. width: 32px;
  3077. height: 32px;
  3078. border-radius: 8px;
  3079. border: none;
  3080. background: #f9fafb;
  3081. cursor: pointer;
  3082. display: flex;
  3083. align-items: center;
  3084. justify-content: center;
  3085. transition: all 0.2s;
  3086. color: #6b7280;
  3087. }
  3088. .config-back-btn:hover {
  3089. background: #eef3ff;
  3090. color: #285cf5;
  3091. }
  3092. .config-tool-name {
  3093. font-size: 15px;
  3094. font-weight: 600;
  3095. color: #111827;
  3096. }
  3097. .config-content {
  3098. flex: 1;
  3099. overflow-y: auto;
  3100. padding: 20px;
  3101. }
  3102. .config-section {
  3103. margin-bottom: 24px;
  3104. }
  3105. .config-label {
  3106. font-size: 13px;
  3107. font-weight: 600;
  3108. color: #374151;
  3109. margin-bottom: 10px;
  3110. display: block;
  3111. }
  3112. .config-switch {
  3113. display: flex;
  3114. align-items: center;
  3115. justify-content: space-between;
  3116. padding: 14px 16px;
  3117. background: #fafbfc;
  3118. border: 1.5px solid #e5e7eb;
  3119. border-radius: 12px;
  3120. margin-bottom: 10px;
  3121. }
  3122. .config-switch-label {
  3123. font-size: 14px;
  3124. color: #374151;
  3125. font-weight: 500;
  3126. }
  3127. .switch-toggle {
  3128. position: relative;
  3129. width: 48px;
  3130. height: 28px;
  3131. background: #e5e7eb;
  3132. border-radius: 14px;
  3133. cursor: pointer;
  3134. transition: all 0.3s;
  3135. }
  3136. .switch-toggle.active {
  3137. background: #285cf5;
  3138. }
  3139. .switch-toggle::after {
  3140. content: '';
  3141. position: absolute;
  3142. top: 3px;
  3143. left: 3px;
  3144. width: 22px;
  3145. height: 22px;
  3146. background: white;
  3147. border-radius: 50%;
  3148. transition: all 0.3s;
  3149. }
  3150. .switch-toggle.active::after {
  3151. left: 23px;
  3152. }
  3153. .config-mode-list {
  3154. display: flex;
  3155. flex-direction: column;
  3156. gap: 8px;
  3157. }
  3158. .mode-item {
  3159. display: flex;
  3160. align-items: center;
  3161. gap: 12px;
  3162. padding: 12px;
  3163. background: #fafbfc;
  3164. border: 1.5px solid #e5e7eb;
  3165. border-radius: 10px;
  3166. cursor: pointer;
  3167. transition: all 0.2s;
  3168. }
  3169. .mode-item:hover {
  3170. border-color: #285cf5;
  3171. background: #f6f8ff;
  3172. }
  3173. .mode-checkbox {
  3174. width: 20px;
  3175. height: 20px;
  3176. border: 2px solid #d1d5db;
  3177. border-radius: 4px;
  3178. display: flex;
  3179. align-items: center;
  3180. justify-content: center;
  3181. transition: all 0.2s;
  3182. }
  3183. .mode-item.active .mode-checkbox {
  3184. background: #285cf5;
  3185. border-color: #285cf5;
  3186. }
  3187. .mode-checkbox svg {
  3188. width: 14px;
  3189. height: 14px;
  3190. color: white;
  3191. display: none;
  3192. }
  3193. .mode-item.active .mode-checkbox svg {
  3194. display: block;
  3195. }
  3196. .mode-radio {
  3197. width: 20px;
  3198. height: 20px;
  3199. border: 2px solid #d1d5db;
  3200. border-radius: 50%;
  3201. display: flex;
  3202. align-items: center;
  3203. justify-content: center;
  3204. transition: all 0.2s;
  3205. }
  3206. .mode-item.active .mode-radio {
  3207. border-color: #285cf5;
  3208. }
  3209. .mode-radio-dot {
  3210. width: 10px;
  3211. height: 10px;
  3212. border-radius: 50%;
  3213. background: transparent;
  3214. transition: all 0.2s;
  3215. }
  3216. .mode-item.active .mode-radio-dot {
  3217. background: #285cf5;
  3218. }
  3219. .mode-label {
  3220. font-size: 13px;
  3221. color: #374151;
  3222. font-weight: 500;
  3223. }
  3224. /* 填空符按钮 */
  3225. .question-input-wrapper {
  3226. position: relative;
  3227. }
  3228. .add-blank-btn {
  3229. position: absolute;
  3230. right: 8px;
  3231. bottom: 8px;
  3232. height: 32px;
  3233. padding: 0 12px;
  3234. border: 1.5px solid #e5e7eb;
  3235. border-radius: 8px;
  3236. background: white;
  3237. color: #6b7280;
  3238. cursor: pointer;
  3239. transition: all 0.2s;
  3240. display: flex;
  3241. align-items: center;
  3242. gap: 6px;
  3243. font-size: 12px;
  3244. font-weight: 500;
  3245. }
  3246. .add-blank-btn svg {
  3247. width: 14px;
  3248. height: 14px;
  3249. }
  3250. .add-blank-btn:hover {
  3251. border-color: #285cf5;
  3252. color: #285cf5;
  3253. background: #f6f8ff;
  3254. }
  3255. /* 图片上传按钮 */
  3256. .upload-image-btn {
  3257. position: absolute;
  3258. right: 8px;
  3259. bottom: 8px;
  3260. width: 32px;
  3261. height: 32px;
  3262. border: 1.5px solid #e5e7eb;
  3263. border-radius: 8px;
  3264. background: white;
  3265. color: #6b7280;
  3266. cursor: pointer;
  3267. transition: all 0.2s;
  3268. display: flex;
  3269. align-items: center;
  3270. justify-content: center;
  3271. z-index: 5;
  3272. }
  3273. .upload-image-btn svg {
  3274. width: 16px;
  3275. height: 16px;
  3276. }
  3277. .upload-image-btn:hover {
  3278. border-color: #285cf5;
  3279. color: #285cf5;
  3280. background: #f6f8ff;
  3281. }
  3282. /* 带图片按钮的输入框容器 */
  3283. .input-with-image {
  3284. position: relative;
  3285. }
  3286. .input-with-image textarea,
  3287. .input-with-image .option-input {
  3288. padding-right: 48px;
  3289. }
  3290. /* 图片预览 */
  3291. .image-preview {
  3292. position: relative;
  3293. margin-top: 12px;
  3294. margin-bottom: 8px;
  3295. max-width: 200px;
  3296. }
  3297. .image-preview img {
  3298. width: 100%;
  3299. height: auto;
  3300. border-radius: 8px;
  3301. border: 1.5px solid #e5e7eb;
  3302. }
  3303. .remove-image-btn {
  3304. position: absolute;
  3305. top: 8px;
  3306. right: 8px;
  3307. width: 24px;
  3308. height: 24px;
  3309. border-radius: 50%;
  3310. border: none;
  3311. background: rgba(0, 0, 0, 0.6);
  3312. color: white;
  3313. cursor: pointer;
  3314. display: flex;
  3315. align-items: center;
  3316. justify-content: center;
  3317. transition: all 0.2s;
  3318. }
  3319. .remove-image-btn:hover {
  3320. background: rgba(220, 38, 38, 0.9);
  3321. }
  3322. .remove-image-btn svg {
  3323. width: 14px;
  3324. height: 14px;
  3325. }
  3326. /* 排序项样式 */
  3327. .sort-item {
  3328. display: flex;
  3329. align-items: center;
  3330. gap: 12px;
  3331. position: relative;
  3332. padding: 6px 8px;
  3333. border-radius: 10px;
  3334. transition: all 0.2s;
  3335. border: 1px solid transparent;
  3336. }
  3337. .sort-item:hover {
  3338. background: #eef3ff;
  3339. border-color: #285cf5;
  3340. box-shadow: 0 4px 12px rgba(40, 92, 245, 0.16);
  3341. }
  3342. .sort-item:hover .option-actions {
  3343. opacity: 1;
  3344. pointer-events: auto;
  3345. }
  3346. .sort-item-static {
  3347. display: flex;
  3348. align-items: center;
  3349. gap: 12px;
  3350. position: relative;
  3351. padding: 4px;
  3352. border-radius: 8px;
  3353. transition: all 0.2s;
  3354. }
  3355. .sort-item-static:hover {
  3356. background: #f9fafb;
  3357. }
  3358. .sort-item-static:hover .option-actions {
  3359. opacity: 1;
  3360. pointer-events: auto;
  3361. }
  3362. .sort-number {
  3363. width: 32px;
  3364. height: 32px;
  3365. background: #285cf5;
  3366. color: white;
  3367. border-radius: 8px;
  3368. display: flex;
  3369. align-items: center;
  3370. justify-content: center;
  3371. font-size: 14px;
  3372. font-weight: 700;
  3373. flex-shrink: 0;
  3374. }
  3375. .sort-order-display {
  3376. display: flex;
  3377. align-items: center;
  3378. gap: 12px;
  3379. padding: 16px;
  3380. background: white;
  3381. border-radius: 10px;
  3382. flex-wrap: wrap;
  3383. }
  3384. .sort-order-item {
  3385. width: 56px;
  3386. height: 56px;
  3387. background: #eef3ff;
  3388. border: 2px solid #285cf5;
  3389. color: #285cf5;
  3390. border-radius: 10px;
  3391. display: flex;
  3392. flex-direction: column;
  3393. align-items: center;
  3394. justify-content: center;
  3395. font-size: 18px;
  3396. font-weight: 700;
  3397. position: relative;
  3398. transition: all 0.2s;
  3399. }
  3400. .sort-order-item.draggable {
  3401. cursor: move;
  3402. }
  3403. .sort-order-item.draggable:hover {
  3404. background: #dbe6ff;
  3405. transform: scale(1.05);
  3406. box-shadow: 0 6px 14px rgba(40, 92, 245, 0.22);
  3407. }
  3408. .sort-order-item.dragging {
  3409. opacity: 0.5;
  3410. }
  3411. .sort-order-item.drag-over {
  3412. border-color: #285cf5;
  3413. background: #cddcff;
  3414. box-shadow: 0 6px 14px rgba(40, 92, 245, 0.22);
  3415. }
  3416. .sort-drag-handle {
  3417. position: absolute;
  3418. top: 2px;
  3419. right: 2px;
  3420. width: 16px;
  3421. height: 16px;
  3422. color: #285cf5;
  3423. opacity: 0;
  3424. transition: opacity 0.2s;
  3425. }
  3426. .sort-order-item.draggable:hover .sort-drag-handle {
  3427. opacity: 1;
  3428. }
  3429. .sort-drag-handle svg {
  3430. width: 12px;
  3431. height: 12px;
  3432. }
  3433. .sort-arrow {
  3434. color: #285cf5;
  3435. font-size: 20px;
  3436. font-weight: 700;
  3437. }
  3438. /* 工具顶部栏 */
  3439. .tool-header {
  3440. height: 60px;
  3441. background: white;
  3442. border-bottom: 1px solid #f0f1f3;
  3443. display: flex;
  3444. align-items: center;
  3445. padding: 0 24px;
  3446. flex-shrink: 0;
  3447. }
  3448. .tool-type-selector {
  3449. position: relative;
  3450. display: inline-block;
  3451. }
  3452. .tool-type-btn {
  3453. display: flex;
  3454. align-items: center;
  3455. gap: 8px;
  3456. padding: 10px 16px;
  3457. background: #fafbfc;
  3458. border: 1.5px solid #e5e7eb;
  3459. border-radius: 10px;
  3460. font-size: 14px;
  3461. font-weight: 600;
  3462. color: #111827;
  3463. cursor: pointer;
  3464. transition: all 0.2s;
  3465. }
  3466. .tool-type-btn:hover {
  3467. border-color: #285cf5;
  3468. background: #f6f8ff;
  3469. }
  3470. .tool-type-btn svg {
  3471. width: 16px;
  3472. height: 16px;
  3473. color: #6b7280;
  3474. flex-shrink: 0;
  3475. }
  3476. .tool-type-btn svg:last-child {
  3477. width: 12px;
  3478. height: 12px;
  3479. }
  3480. .tool-type-dropdown {
  3481. position: absolute;
  3482. top: 100%;
  3483. left: 0;
  3484. margin-top: 8px;
  3485. background: white;
  3486. border-radius: 12px;
  3487. box-shadow: 0 8px 24px rgba(0, 0, 0, 0.15);
  3488. padding: 8px;
  3489. min-width: 200px;
  3490. display: none;
  3491. z-index: 1000;
  3492. }
  3493. .tool-type-selector.active .tool-type-dropdown {
  3494. display: block;
  3495. }
  3496. .tool-type-item {
  3497. padding: 10px 12px;
  3498. border-radius: 8px;
  3499. cursor: pointer;
  3500. transition: all 0.2s;
  3501. display: flex;
  3502. align-items: center;
  3503. gap: 10px;
  3504. font-size: 14px;
  3505. color: #374151;
  3506. }
  3507. .tool-type-item svg {
  3508. width: 18px;
  3509. height: 18px;
  3510. flex-shrink: 0;
  3511. }
  3512. .tool-type-item:hover {
  3513. background: #eef3ff;
  3514. color: #285cf5;
  3515. }
  3516. .tool-type-item:hover svg {
  3517. color: #285cf5;
  3518. }
  3519. .tool-type-item.active {
  3520. background: #eef3ff;
  3521. color: #285cf5;
  3522. }
  3523. .tool-type-item.active svg {
  3524. color: #285cf5;
  3525. }
  3526. /* 工具编辑区域 */
  3527. .tool-edit-container {
  3528. display: none;
  3529. flex-direction: column;
  3530. background: white;
  3531. width: 100%;
  3532. height: 100%;
  3533. flex: 1;
  3534. }
  3535. .tool-edit-container.active {
  3536. display: flex;
  3537. }
  3538. .tool-edit-area {
  3539. flex: 1;
  3540. overflow-y: auto;
  3541. padding: 40px;
  3542. background: #fafbfc;
  3543. }
  3544. .tool-canvas {
  3545. width: 960px;
  3546. max-width: 100%;
  3547. margin: 0 auto;
  3548. background: white;
  3549. border-radius: 16px;
  3550. box-shadow: 0 2px 8px rgba(0, 0, 0, 0.06);
  3551. padding: 40px;
  3552. min-height: 500px;
  3553. }
  3554. .question-item {
  3555. margin-bottom: 32px;
  3556. padding-bottom: 32px;
  3557. border-bottom: 1px solid #f0f1f3;
  3558. }
  3559. .question-item:last-child {
  3560. border-bottom: none;
  3561. }
  3562. .question-header {
  3563. display: flex;
  3564. align-items: center;
  3565. justify-content: space-between;
  3566. margin-bottom: 16px;
  3567. }
  3568. .question-number {
  3569. font-size: 16px;
  3570. font-weight: 700;
  3571. color: #285cf5;
  3572. }
  3573. .question-type-toggle {
  3574. display: flex;
  3575. align-items: center;
  3576. gap: 8px;
  3577. }
  3578. .toggle-label {
  3579. font-size: 13px;
  3580. color: #6b7280;
  3581. }
  3582. .mini-toggle {
  3583. width: 40px;
  3584. height: 22px;
  3585. background: #e5e7eb;
  3586. border-radius: 11px;
  3587. cursor: pointer;
  3588. transition: all 0.3s;
  3589. position: relative;
  3590. }
  3591. .mini-toggle.active {
  3592. background: #285cf5;
  3593. }
  3594. .mini-toggle::after {
  3595. content: '';
  3596. position: absolute;
  3597. top: 2px;
  3598. left: 2px;
  3599. width: 18px;
  3600. height: 18px;
  3601. background: white;
  3602. border-radius: 50%;
  3603. transition: all 0.3s;
  3604. }
  3605. .mini-toggle.active::after {
  3606. left: 20px;
  3607. }
  3608. .question-input {
  3609. width: 100%;
  3610. padding: 14px 16px;
  3611. border: 1.5px solid #e5e7eb;
  3612. border-radius: 10px;
  3613. font-size: 14px;
  3614. color: #111827;
  3615. background: #fafbfc;
  3616. transition: all 0.2s;
  3617. font-family: inherit;
  3618. resize: vertical;
  3619. min-height: 80px;
  3620. }
  3621. .question-input:focus {
  3622. outline: none;
  3623. border-color: #285cf5;
  3624. background: white;
  3625. }
  3626. .options-list {
  3627. margin-top: 16px;
  3628. display: flex;
  3629. flex-direction: column;
  3630. gap: 10px;
  3631. }
  3632. .option-item {
  3633. display: flex;
  3634. align-items: center;
  3635. gap: 12px;
  3636. position: relative;
  3637. padding: 4px;
  3638. border-radius: 8px;
  3639. transition: all 0.2s;
  3640. }
  3641. .option-item:hover {
  3642. background: #f9fafb;
  3643. }
  3644. .option-item:hover .option-actions {
  3645. opacity: 1;
  3646. pointer-events: auto;
  3647. }
  3648. .option-actions {
  3649. display: flex;
  3650. align-items: center;
  3651. gap: 4px;
  3652. opacity: 0;
  3653. pointer-events: none;
  3654. transition: opacity 0.2s;
  3655. }
  3656. .option-action-btn {
  3657. width: 24px;
  3658. height: 24px;
  3659. border: none;
  3660. background: transparent;
  3661. cursor: pointer;
  3662. display: flex;
  3663. align-items: center;
  3664. justify-content: center;
  3665. border-radius: 4px;
  3666. transition: all 0.2s;
  3667. color: #9ca3af;
  3668. }
  3669. .option-action-btn:hover {
  3670. background: #e5e7eb;
  3671. color: #374151;
  3672. }
  3673. .option-action-btn.delete:hover {
  3674. background: #fee2e2;
  3675. color: #dc2626;
  3676. }
  3677. .option-action-btn svg {
  3678. width: 14px;
  3679. height: 14px;
  3680. }
  3681. .option-drag-handle {
  3682. cursor: grab;
  3683. color: #d1d5db;
  3684. width: 16px;
  3685. height: 16px;
  3686. display: flex;
  3687. align-items: center;
  3688. justify-content: center;
  3689. flex-shrink: 0;
  3690. opacity: 0;
  3691. transition: opacity 0.2s;
  3692. }
  3693. .option-item:hover .option-drag-handle {
  3694. opacity: 1;
  3695. }
  3696. .option-drag-handle:active {
  3697. cursor: grabbing;
  3698. }
  3699. .option-drag-handle svg {
  3700. width: 14px;
  3701. height: 14px;
  3702. }
  3703. .option-checkbox {
  3704. width: 22px;
  3705. height: 22px;
  3706. border: 2px solid #d1d5db;
  3707. border-radius: 4px;
  3708. flex-shrink: 0;
  3709. cursor: pointer;
  3710. display: flex;
  3711. align-items: center;
  3712. justify-content: center;
  3713. transition: all 0.2s;
  3714. }
  3715. .option-checkbox.checked {
  3716. background: #285cf5;
  3717. border-color: #285cf5;
  3718. }
  3719. .option-checkbox svg {
  3720. width: 14px;
  3721. height: 14px;
  3722. color: white;
  3723. display: none;
  3724. }
  3725. .option-checkbox.checked svg {
  3726. display: block;
  3727. }
  3728. .option-input {
  3729. flex: 1;
  3730. padding: 10px 14px;
  3731. border: 1.5px solid #e5e7eb;
  3732. border-radius: 8px;
  3733. font-size: 14px;
  3734. color: #111827;
  3735. background: white;
  3736. transition: all 0.2s;
  3737. font-family: inherit;
  3738. }
  3739. .option-input:focus {
  3740. outline: none;
  3741. border-color: #285cf5;
  3742. }
  3743. .add-option-btn {
  3744. width: 32px;
  3745. height: 32px;
  3746. margin-top: 8px;
  3747. margin-left: 34px;
  3748. border: 1.5px dashed #d1d5db;
  3749. border-radius: 8px;
  3750. background: transparent;
  3751. color: #6b7280;
  3752. cursor: pointer;
  3753. transition: all 0.2s;
  3754. display: flex;
  3755. align-items: center;
  3756. justify-content: center;
  3757. flex-shrink: 0;
  3758. }
  3759. .add-option-btn svg {
  3760. width: 16px;
  3761. height: 16px;
  3762. }
  3763. .add-option-btn:hover {
  3764. border-color: #285cf5;
  3765. color: #285cf5;
  3766. background: #f6f8ff;
  3767. }
  3768. .add-option-btn-with-text {
  3769. height: 36px;
  3770. padding: 0 14px;
  3771. margin-top: 8px;
  3772. margin-left: 66px;
  3773. border: 1.5px dashed #d1d5db;
  3774. border-radius: 8px;
  3775. background: transparent;
  3776. color: #6b7280;
  3777. cursor: pointer;
  3778. transition: all 0.2s;
  3779. display: inline-flex;
  3780. align-items: center;
  3781. justify-content: center;
  3782. gap: 6px;
  3783. font-size: 13px;
  3784. font-weight: 500;
  3785. white-space: nowrap;
  3786. align-self: flex-start;
  3787. }
  3788. .add-option-btn-with-text svg {
  3789. width: 14px;
  3790. height: 14px;
  3791. flex-shrink: 0;
  3792. }
  3793. .add-option-btn-with-text:hover {
  3794. border-color: #285cf5;
  3795. color: #285cf5;
  3796. background: #f6f8ff;
  3797. }
  3798. .question-actions {
  3799. display: flex;
  3800. align-items: center;
  3801. gap: 8px;
  3802. margin-left: auto;
  3803. }
  3804. .question-action-btn {
  3805. width: 32px;
  3806. height: 32px;
  3807. border: none;
  3808. background: #f9fafb;
  3809. border-radius: 8px;
  3810. cursor: pointer;
  3811. display: flex;
  3812. align-items: center;
  3813. justify-content: center;
  3814. transition: all 0.2s;
  3815. color: #6b7280;
  3816. }
  3817. .question-action-btn:hover {
  3818. background: #f3f4f6;
  3819. color: #374151;
  3820. }
  3821. .question-action-btn.delete:hover {
  3822. background: #fee2e2;
  3823. color: #dc2626;
  3824. }
  3825. .question-action-btn svg {
  3826. width: 16px;
  3827. height: 16px;
  3828. }
  3829. .explanation-section {
  3830. margin-top: 16px;
  3831. padding: 16px;
  3832. background: #f9fafb;
  3833. border-radius: 10px;
  3834. }
  3835. .explanation-header {
  3836. display: flex;
  3837. align-items: center;
  3838. justify-content: space-between;
  3839. margin-bottom: 10px;
  3840. }
  3841. .explanation-label {
  3842. font-size: 13px;
  3843. font-weight: 600;
  3844. color: #6b7280;
  3845. display: flex;
  3846. align-items: center;
  3847. gap: 6px;
  3848. }
  3849. .ai-gen-btn {
  3850. padding: 6px 12px;
  3851. border-radius: 8px;
  3852. border: none;
  3853. background: linear-gradient(135deg, #285cf5 0%, #1f4ad6 100%);
  3854. color: white;
  3855. font-size: 12px;
  3856. font-weight: 600;
  3857. cursor: pointer;
  3858. display: flex;
  3859. align-items: center;
  3860. gap: 4px;
  3861. transition: all 0.2s;
  3862. }
  3863. .ai-gen-btn:hover {
  3864. transform: translateY(-1px);
  3865. box-shadow: 0 2px 8px rgba(40, 92, 245, 0.3);
  3866. }
  3867. .explanation-textarea {
  3868. width: 100%;
  3869. padding: 10px;
  3870. border: 1.5px solid #e5e7eb;
  3871. border-radius: 8px;
  3872. font-size: 13px;
  3873. color: #374151;
  3874. background: white;
  3875. resize: vertical;
  3876. min-height: 60px;
  3877. font-family: inherit;
  3878. }
  3879. .explanation-textarea:focus {
  3880. outline: none;
  3881. border-color: #285cf5;
  3882. }
  3883. .add-question-btn {
  3884. margin-top: 24px;
  3885. width: 40px;
  3886. height: 40px;
  3887. border: 2px dashed #d1d5db;
  3888. border-radius: 10px;
  3889. background: transparent;
  3890. color: #6b7280;
  3891. cursor: pointer;
  3892. transition: all 0.2s;
  3893. display: flex;
  3894. align-items: center;
  3895. justify-content: center;
  3896. flex-shrink: 0;
  3897. }
  3898. .add-question-btn svg {
  3899. width: 20px;
  3900. height: 20px;
  3901. }
  3902. .add-question-btn:hover {
  3903. border-color: #285cf5;
  3904. color: #285cf5;
  3905. background: #f6f8ff;
  3906. transform: scale(1.05);
  3907. }
  3908. .add-question-btn-with-text {
  3909. margin-top: 24px;
  3910. height: 44px;
  3911. padding: 0 18px;
  3912. border: 2px dashed #d1d5db;
  3913. border-radius: 10px;
  3914. background: transparent;
  3915. color: #6b7280;
  3916. cursor: pointer;
  3917. transition: all 0.2s;
  3918. display: inline-flex;
  3919. align-items: center;
  3920. justify-content: center;
  3921. gap: 8px;
  3922. font-size: 14px;
  3923. font-weight: 600;
  3924. white-space: nowrap;
  3925. align-self: flex-start;
  3926. }
  3927. .add-question-btn-with-text svg {
  3928. width: 18px;
  3929. height: 18px;
  3930. flex-shrink: 0;
  3931. }
  3932. .add-question-btn-with-text:hover {
  3933. border-color: #285cf5;
  3934. color: #285cf5;
  3935. background: #f6f8ff;
  3936. transform: scale(1.02);
  3937. }
  3938. /* 抽认卡特殊布局 */
  3939. .flashcard-layout {
  3940. display: grid;
  3941. grid-template-columns: 1fr 1fr;
  3942. gap: 24px;
  3943. margin-bottom: 32px;
  3944. }
  3945. .flashcard-side {
  3946. border: 1.5px solid #e5e7eb;
  3947. border-radius: 12px;
  3948. padding: 20px;
  3949. min-height: 200px;
  3950. }
  3951. .flashcard-side-label {
  3952. font-size: 13px;
  3953. font-weight: 600;
  3954. color: #6b7280;
  3955. margin-bottom: 12px;
  3956. }
  3957. .flashcard-content {
  3958. width: 100%;
  3959. padding: 14px;
  3960. border: 1.5px solid #e5e7eb;
  3961. border-radius: 8px;
  3962. font-size: 14px;
  3963. color: #111827;
  3964. background: white;
  3965. resize: vertical;
  3966. min-height: 120px;
  3967. font-family: inherit;
  3968. }
  3969. .flashcard-content:focus {
  3970. outline: none;
  3971. border-color: #285cf5;
  3972. }
  3973. /* 资源弹窗与列表 */
  3974. .resource-upload-list {
  3975. margin-top: 12px;
  3976. display: flex;
  3977. flex-direction: column;
  3978. gap: 10px;
  3979. }
  3980. .resource-file-item {
  3981. display: flex;
  3982. align-items: center;
  3983. justify-content: space-between;
  3984. padding: 10px 12px;
  3985. border: 1px solid #e5e7eb;
  3986. border-radius: 10px;
  3987. background: #fafbfc;
  3988. font-size: 13px;
  3989. color: #374151;
  3990. }
  3991. .resource-file-name {
  3992. display: flex;
  3993. align-items: center;
  3994. gap: 8px;
  3995. overflow: hidden;
  3996. }
  3997. .resource-file-name span {
  3998. white-space: nowrap;
  3999. text-overflow: ellipsis;
  4000. overflow: hidden;
  4001. }
  4002. .resource-remove-btn {
  4003. border: none;
  4004. background: transparent;
  4005. color: #9ca3af;
  4006. cursor: pointer;
  4007. display: flex;
  4008. align-items: center;
  4009. justify-content: center;
  4010. width: 28px;
  4011. height: 28px;
  4012. border-radius: 8px;
  4013. transition: all 0.2s;
  4014. }
  4015. .resource-remove-btn:hover {
  4016. background: #fef2f2;
  4017. color: #dc2626;
  4018. }
  4019. .resource-tip {
  4020. font-size: 12px;
  4021. color: #6b7280;
  4022. margin-top: 8px;
  4023. }
  4024. .resource-actions {
  4025. display: flex;
  4026. gap: 12px;
  4027. margin-top: 16px;
  4028. }
  4029. .resource-chip {
  4030. display: inline-flex;
  4031. align-items: center;
  4032. gap: 6px;
  4033. padding: 6px 10px;
  4034. border-radius: 999px;
  4035. background: #f3f4f6;
  4036. color: #4b5563;
  4037. font-size: 12px;
  4038. font-weight: 600;
  4039. }
  4040. .resource-tabs {
  4041. display: flex;
  4042. align-items: center;
  4043. gap: 8px;
  4044. min-height: 60px;
  4045. padding: 0 16px;
  4046. border-bottom: 1px solid #f0f1f3;
  4047. background: #fff;
  4048. border-radius: 12px 12px 0 0;
  4049. }
  4050. .resource-tab {
  4051. padding: 8px 14px;
  4052. border-radius: 10px;
  4053. border: 1px solid #e5e7eb;
  4054. background: #fafbfc;
  4055. cursor: pointer;
  4056. font-size: 13px;
  4057. font-weight: 600;
  4058. color: #4b5563;
  4059. transition: all 0.2s;
  4060. height: 36px;
  4061. display: inline-flex;
  4062. align-items: center;
  4063. justify-content: center;
  4064. }
  4065. .resource-tab.active {
  4066. border-color: #285cf5;
  4067. color: #285cf5;
  4068. background: #eef3ff;
  4069. box-shadow: 0 2px 8px rgba(40, 92, 245, 0.12);
  4070. }
  4071. /* 滚动条 */
  4072. ::-webkit-scrollbar {
  4073. width: 8px;
  4074. height: 8px;
  4075. }
  4076. ::-webkit-scrollbar-track {
  4077. background: transparent;
  4078. }
  4079. ::-webkit-scrollbar-thumb {
  4080. background: #e0e2e5;
  4081. border-radius: 4px;
  4082. }
  4083. ::-webkit-scrollbar-thumb:hover {
  4084. background: #c5c8cc;
  4085. }
  4086. </style>
  4087. </head>
  4088. <body>
  4089. <!-- 顶部工具栏 -->
  4090. <div class="top-bar">
  4091. <div class="top-bar-left">
  4092. <div class="logo-menu-wrapper">
  4093. <button class="logo-btn" id="topMenuToggle" onclick="toggleTopMenu(event)">
  4094. <img src="/var/folders/6_/1f501dn50cqbnjhxhfyqnbcm0000gn/T/cocorobo_icon_rgb_blue_png_1769396590215.png" alt="Coco" class="logo-img">
  4095. <svg class="logo-caret" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
  4096. <polyline points="6 9 12 15 18 9"/>
  4097. </svg>
  4098. </button>
  4099. <div class="top-dropdown" id="topDropdown">
  4100. <div class="top-dropdown-item" onclick="handleTopMenuAction('back')">
  4101. <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
  4102. <path d="M15 18l-6-6 6-6"/>
  4103. </svg>
  4104. 返回列表
  4105. </div>
  4106. <div class="top-dropdown-item" onclick="handleTopMenuAction('settings')">
  4107. <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
  4108. <circle cx="12" cy="12" r="3"/>
  4109. <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"/>
  4110. </svg>
  4111. 设置
  4112. </div>
  4113. <div class="top-dropdown-item" onclick="handleTopMenuAction('template')">
  4114. <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
  4115. <rect x="3" y="3" width="7" height="7"/>
  4116. <rect x="14" y="3" width="7" height="7"/>
  4117. <rect x="14" y="14" width="7" height="7"/>
  4118. <rect x="3" y="14" width="7" height="7"/>
  4119. </svg>
  4120. 导入模板
  4121. </div>
  4122. <div class="top-dropdown-item" onclick="handleTopMenuAction('duplicate')">
  4123. <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
  4124. <rect x="9" y="9" width="13" height="13" rx="2" ry="2"/>
  4125. <path d="M5 15H4a2 2 0 01-2-2V4a2 2 0 012-2h9a2 2 0 012 2v1"/>
  4126. </svg>
  4127. 另存为副本
  4128. </div>
  4129. <div class="top-dropdown-sep"></div>
  4130. <div class="top-dropdown-item danger" onclick="handleTopMenuAction('delete')">
  4131. <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
  4132. <polyline points="3 6 5 6 21 6"/>
  4133. <path d="M19 6v14a2 2 0 01-2 2H7a2 2 0 01-2-2V6m3 0V4a2 2 0 012-2h4a2 2 0 012 2v2"/>
  4134. </svg>
  4135. 删除
  4136. </div>
  4137. </div>
  4138. </div>
  4139. <div class="course-title" contenteditable="true">新建课程</div>
  4140. <div class="auto-save">
  4141. <span class="save-dot"></span>
  4142. <span id="saveStatus">未保存</span>
  4143. </div>
  4144. </div>
  4145. <div class="top-bar-right">
  4146. <button class="top-btn btn-secondary">
  4147. <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
  4148. <path d="M15 12a3 3 0 11-6 0 3 3 0 016 0z"/>
  4149. <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"/>
  4150. </svg>
  4151. 预览
  4152. </button>
  4153. <button class="top-btn btn-secondary">
  4154. <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
  4155. <path d="M19 21H5a2 2 0 01-2-2V5a2 2 0 012-2h11l5 5v11a2 2 0 01-2 2z"/>
  4156. <polyline points="17 21 17 13 7 13 7 21"/>
  4157. <polyline points="7 3 7 8 15 8"/>
  4158. </svg>
  4159. 保存
  4160. </button>
  4161. <button class="top-btn btn-primary" onclick="showPublishModal()">
  4162. <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
  4163. <circle cx="12" cy="12" r="10"/>
  4164. <polyline points="12 6 12 12 16 14"/>
  4165. </svg>
  4166. 发布
  4167. </button>
  4168. </div>
  4169. </div>
  4170. <!-- 主容器 -->
  4171. <div class="main-container">
  4172. <!-- 左侧容器 -->
  4173. <div class="left-container">
  4174. <!-- 一级菜单 -->
  4175. <div class="primary-menu">
  4176. <div class="menu-item active" data-panel="ai">
  4177. <svg class="menu-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
  4178. <path d="M12 2L2 7l10 5 10-5-10-5z"/>
  4179. <path d="M2 17l10 5 10-5"/>
  4180. <path d="M2 12l10 5 10-5"/>
  4181. </svg>
  4182. <span class="menu-label">Coco AI</span>
  4183. </div>
  4184. <div class="menu-item" data-panel="pages">
  4185. <svg class="menu-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
  4186. <path d="M14 2H6a2 2 0 00-2 2v16a2 2 0 002 2h12a2 2 0 002-2V8z"/>
  4187. <polyline points="14 2 14 8 20 8"/>
  4188. </svg>
  4189. <span class="menu-label">页面</span>
  4190. </div>
  4191. <div class="menu-item" data-panel="tools">
  4192. <svg class="menu-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
  4193. <circle cx="12" cy="12" r="3"/>
  4194. <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"/>
  4195. </svg>
  4196. <span class="menu-label">互动工具</span>
  4197. </div>
  4198. <div class="menu-item" data-panel="apps">
  4199. <svg class="menu-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
  4200. <rect x="3" y="3" width="7" height="7"/>
  4201. <rect x="14" y="3" width="7" height="7"/>
  4202. <rect x="14" y="14" width="7" height="7"/>
  4203. <rect x="3" y="14" width="7" height="7"/>
  4204. </svg>
  4205. <span class="menu-label">AI应用</span>
  4206. </div>
  4207. <div class="menu-item" data-panel="web">
  4208. <svg class="menu-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
  4209. <circle cx="12" cy="12" r="10"/>
  4210. <path d="M2 12h20"/>
  4211. <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"/>
  4212. </svg>
  4213. <span class="menu-label">交互网页</span>
  4214. </div>
  4215. <div class="menu-item" data-panel="resources">
  4216. <svg class="menu-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
  4217. <path d="M22 19a2 2 0 01-2 2H4a2 2 0 01-2-2V5a2 2 0 012-2h5l2 3h9a2 2 0 012 2z"/>
  4218. </svg>
  4219. <span class="menu-label">多媒体</span>
  4220. </div>
  4221. </div>
  4222. <!-- 二级菜单 - AI对话 -->
  4223. <div class="secondary-panel" id="aiPanel">
  4224. <div class="panel-header">
  4225. <span class="panel-title">Coco AI</span>
  4226. <button class="collapse-btn" onclick="toggleSecondaryPanel()">
  4227. <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
  4228. <polyline points="15 18 9 12 15 6"/>
  4229. </svg>
  4230. </button>
  4231. </div>
  4232. <div class="chat-input-container" id="chatInputContainer">
  4233. <div class="chat-input-wrapper">
  4234. <div class="chat-textarea-container">
  4235. <textarea
  4236. class="chat-textarea"
  4237. id="chatInput"
  4238. placeholder="例如:创建一节45分钟的四年级教学内容为水的三态变化的课程"
  4239. rows="3"
  4240. oninput="autoResize(this)"
  4241. >创建一节45分钟的四年级教学内容为水的三态变化的课程</textarea>
  4242. </div>
  4243. <div class="chat-bottom-row">
  4244. <div style="display: flex; align-items: center; gap: 8px;">
  4245. <button class="upload-file-btn" title="上传参考资料" id="uploadBtn">
  4246. <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
  4247. <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"/>
  4248. </svg>
  4249. </button>
  4250. <span class="status-text" id="statusText">
  4251. <span class="status-dot"></span>
  4252. 生成中……
  4253. </span>
  4254. </div>
  4255. <button class="send-btn" onclick="sendMessage()" id="sendBtn">
  4256. <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
  4257. <line x1="22" y1="2" x2="11" y2="13"/>
  4258. <polygon points="22 2 15 22 11 13 2 9 22 2"/>
  4259. </svg>
  4260. </button>
  4261. </div>
  4262. </div>
  4263. </div>
  4264. <div class="secondary-content">
  4265. <div class="chat-messages" id="chatMessages">
  4266. <!-- 对话消息将显示在这里 -->
  4267. </div>
  4268. </div>
  4269. </div>
  4270. <!-- 二级菜单 - 页面模板 -->
  4271. <div class="secondary-panel hidden" id="pagesPanel">
  4272. <div class="panel-header">
  4273. <span class="panel-title">添加模板页面</span>
  4274. <button class="collapse-btn" onclick="toggleSecondaryPanel()">
  4275. <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
  4276. <polyline points="15 18 9 12 15 6"/>
  4277. </svg>
  4278. </button>
  4279. </div>
  4280. <div class="secondary-content" style="overflow-y: auto;">
  4281. <div class="template-grid">
  4282. <!-- 标题页 -->
  4283. <div class="template-card" onclick="addPageFromTemplate('title')">
  4284. <div class="template-preview">
  4285. <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
  4286. <path d="M4 7V4h16v3M9 20h6M12 4v16"/>
  4287. </svg>
  4288. </div>
  4289. <div class="template-name">标题页</div>
  4290. </div>
  4291. <!-- 图片页 -->
  4292. <div class="template-card" onclick="addPageFromTemplate('image')">
  4293. <div class="template-preview">
  4294. <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
  4295. <rect x="3" y="3" width="18" height="18" rx="2" ry="2"/>
  4296. <circle cx="8.5" cy="8.5" r="1.5"/>
  4297. <polyline points="21 15 16 10 5 21"/>
  4298. </svg>
  4299. </div>
  4300. <div class="template-name">图片页</div>
  4301. </div>
  4302. <!-- 内容页 -->
  4303. <div class="template-card" onclick="addPageFromTemplate('content')">
  4304. <div class="template-preview">
  4305. <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
  4306. <path d="M14 2H6a2 2 0 00-2 2v16a2 2 0 002 2h12a2 2 0 002-2V8z"/>
  4307. <polyline points="14 2 14 8 20 8"/>
  4308. <line x1="16" y1="13" x2="8" y2="13"/>
  4309. <line x1="16" y1="17" x2="8" y2="17"/>
  4310. <polyline points="10 9 9 9 8 9"/>
  4311. </svg>
  4312. </div>
  4313. <div class="template-name">内容页</div>
  4314. </div>
  4315. <!-- 文图页(左文右图) -->
  4316. <div class="template-card" onclick="addPageFromTemplate('text-image')">
  4317. <div class="template-preview">
  4318. <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
  4319. <rect x="3" y="3" width="7" height="18" rx="1"/>
  4320. <rect x="14" y="3" width="7" height="18" rx="1"/>
  4321. </svg>
  4322. </div>
  4323. <div class="template-name">文图页</div>
  4324. </div>
  4325. <!-- 图文页(左图右文) -->
  4326. <div class="template-card" onclick="addPageFromTemplate('image-text')">
  4327. <div class="template-preview">
  4328. <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
  4329. <rect x="3" y="3" width="7" height="18" rx="1"/>
  4330. <rect x="14" y="3" width="7" height="18" rx="1"/>
  4331. </svg>
  4332. </div>
  4333. <div class="template-name">图文页</div>
  4334. </div>
  4335. <!-- 上传PPT -->
  4336. <div class="template-card upload-ppt-card" onclick="uploadPPT()">
  4337. <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
  4338. <path d="M21 15v4a2 2 0 01-2 2H5a2 2 0 01-2-2v-4"/>
  4339. <polyline points="17 8 12 3 7 8"/>
  4340. <line x1="12" y1="3" x2="12" y2="15"/>
  4341. </svg>
  4342. <span class="upload-ppt-text">上传PPT</span>
  4343. </div>
  4344. </div>
  4345. </div>
  4346. </div>
  4347. <!-- 二级菜单 - 互动工具 -->
  4348. <div class="secondary-panel hidden" id="toolsPanel">
  4349. <div class="panel-header">
  4350. <span class="panel-title">添加互动工具</span>
  4351. <button class="collapse-btn" onclick="toggleSecondaryPanel()">
  4352. <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
  4353. <polyline points="15 18 9 12 15 6"/>
  4354. </svg>
  4355. </button>
  4356. </div>
  4357. <!-- 内容区域包装器 -->
  4358. <div class="panel-content-wrapper">
  4359. <!-- 工具列表视图 -->
  4360. <div class="secondary-content tools-list-view" id="toolsListView" style="overflow-y: auto;">
  4361. <!-- 插画式说明 -->
  4362. <div class="tools-intro">
  4363. <div class="tools-intro-image">
  4364. <svg width="120" height="80" viewBox="0 0 120 80" fill="none">
  4365. <rect x="10" y="15" width="100" height="50" rx="8" fill="#eef3ff" stroke="#285cf5" stroke-width="2"/>
  4366. <circle cx="30" cy="30" r="4" fill="#285cf5"/>
  4367. <rect x="40" y="27" width="60" height="6" rx="3" fill="#ffd9a8"/>
  4368. <circle cx="30" cy="45" r="4" fill="#d1d5db"/>
  4369. <rect x="40" y="42" width="45" height="6" rx="3" fill="#e5e7eb"/>
  4370. </svg>
  4371. </div>
  4372. <div class="tools-intro-text">选择工具创建互动页面</div>
  4373. </div>
  4374. <div class="tools-grid">
  4375. <!-- 选择 -->
  4376. <div class="tool-card" onclick="selectTool('choice')">
  4377. <div class="tool-icon">
  4378. <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
  4379. <circle cx="12" cy="12" r="10"/>
  4380. <path d="M12 16v-4m0-4h.01"/>
  4381. </svg>
  4382. </div>
  4383. <div class="tool-name">选择</div>
  4384. </div>
  4385. <!-- 问答 -->
  4386. <div class="tool-card" onclick="selectTool('qa')">
  4387. <div class="tool-icon">
  4388. <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
  4389. <path d="M21 15a2 2 0 01-2 2H7l-4 4V5a2 2 0 012-2h14a2 2 0 012 2z"/>
  4390. </svg>
  4391. </div>
  4392. <div class="tool-name">问答</div>
  4393. </div>
  4394. <!-- 投票 -->
  4395. <div class="tool-card" onclick="selectTool('vote')">
  4396. <div class="tool-icon">
  4397. <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
  4398. <polyline points="9 11 12 14 22 4"/>
  4399. <path d="M21 12v7a2 2 0 01-2 2H5a2 2 0 01-2-2V5a2 2 0 012-2h11"/>
  4400. </svg>
  4401. </div>
  4402. <div class="tool-name">投票</div>
  4403. </div>
  4404. <!-- 拍照 -->
  4405. <div class="tool-card" onclick="selectTool('photo')">
  4406. <div class="tool-icon">
  4407. <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
  4408. <path d="M23 19a2 2 0 01-2 2H3a2 2 0 01-2-2V8a2 2 0 012-2h4l2-3h6l2 3h4a2 2 0 012 2z"/>
  4409. <circle cx="12" cy="13" r="4"/>
  4410. </svg>
  4411. </div>
  4412. <div class="tool-name">拍照</div>
  4413. </div>
  4414. <!-- 填空 -->
  4415. <div class="tool-card" onclick="selectTool('fillblank')">
  4416. <div class="tool-icon">
  4417. <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
  4418. <line x1="8" y1="6" x2="21" y2="6"/>
  4419. <line x1="8" y1="12" x2="21" y2="12"/>
  4420. <line x1="8" y1="18" x2="21" y2="18"/>
  4421. <line x1="3" y1="6" x2="3.01" y2="6"/>
  4422. <line x1="3" y1="12" x2="3.01" y2="12"/>
  4423. <line x1="3" y1="18" x2="3.01" y2="18"/>
  4424. </svg>
  4425. </div>
  4426. <div class="tool-name">填空</div>
  4427. </div>
  4428. <!-- 排序 -->
  4429. <div class="tool-card" onclick="selectTool('sort')">
  4430. <div class="tool-icon">
  4431. <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
  4432. <line x1="4" y1="6" x2="20" y2="6"/>
  4433. <line x1="4" y1="12" x2="20" y2="12"/>
  4434. <line x1="4" y1="18" x2="20" y2="18"/>
  4435. </svg>
  4436. </div>
  4437. <div class="tool-name">排序</div>
  4438. </div>
  4439. <!-- 白板 -->
  4440. <div class="tool-card" onclick="selectTool('whiteboard')">
  4441. <div class="tool-icon">
  4442. <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
  4443. <path d="M12 19l7-7 3 3-7 7-3-3z"/>
  4444. <path d="M18 13l-1.5-7.5L2 2l3.5 14.5L13 18l5-5z"/>
  4445. <path d="M2 2l7.586 7.586"/>
  4446. </svg>
  4447. </div>
  4448. <div class="tool-name">白板</div>
  4449. </div>
  4450. <!-- 抽认卡 -->
  4451. <div class="tool-card" onclick="selectTool('flashcard')">
  4452. <div class="tool-icon">
  4453. <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
  4454. <rect x="2" y="4" width="20" height="16" rx="2"/>
  4455. <path d="M7 15h10M12 9v6"/>
  4456. </svg>
  4457. </div>
  4458. <div class="tool-name">抽认卡</div>
  4459. </div>
  4460. <!-- CocoPi -->
  4461. <div class="tool-card" onclick="selectTool('cocopi')">
  4462. <div class="tool-icon">
  4463. <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
  4464. <polyline points="16 18 22 12 16 6"/>
  4465. <polyline points="8 6 2 12 8 18"/>
  4466. </svg>
  4467. </div>
  4468. <div class="tool-name">CocoPi</div>
  4469. </div>
  4470. <!-- 创作空间 -->
  4471. <div class="tool-card" onclick="selectTool('workspace')">
  4472. <div class="tool-icon">
  4473. <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
  4474. <path d="M12 2L2 7l10 5 10-5-10-5z"/>
  4475. <path d="M2 17l10 5 10-5"/>
  4476. <path d="M2 12l10 5 10-5"/>
  4477. </svg>
  4478. </div>
  4479. <div class="tool-name">创作空间</div>
  4480. </div>
  4481. </div>
  4482. </div>
  4483. <!-- 工具配置视图 -->
  4484. <div class="secondary-content tools-config-view hidden" id="toolsConfigView">
  4485. <div class="config-header">
  4486. <button class="config-back-btn" onclick="backToToolsList()">
  4487. <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
  4488. <path d="M19 12H5M12 19l-7-7 7-7"/>
  4489. </svg>
  4490. </button>
  4491. <span class="config-tool-name" id="currentToolName">选择</span>
  4492. </div>
  4493. <!-- 配置内容区域 -->
  4494. <div class="config-content" id="toolConfigContent">
  4495. <!-- 配置项将通过JavaScript动态生成 -->
  4496. </div>
  4497. </div>
  4498. </div><!-- 关闭 panel-content-wrapper -->
  4499. </div>
  4500. <!-- 二级菜单 - AI应用 -->
  4501. <div class="secondary-panel hidden" id="appsPanel">
  4502. <div class="panel-header">
  4503. <span class="panel-title">添加AI应用</span>
  4504. <button class="collapse-btn" onclick="toggleSecondaryPanel()">
  4505. <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
  4506. <polyline points="15 18 9 12 15 6"/>
  4507. </svg>
  4508. </button>
  4509. </div>
  4510. <div class="secondary-content" style="overflow-y: auto;">
  4511. <div class="template-grid">
  4512. <!-- 应用中心 -->
  4513. <div class="template-card" onclick="openAIAppCenter()">
  4514. <div class="template-preview">
  4515. <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
  4516. <rect x="3" y="3" width="7" height="7" rx="1"/>
  4517. <rect x="14" y="3" width="7" height="7" rx="1"/>
  4518. <rect x="14" y="14" width="7" height="7" rx="1"/>
  4519. <rect x="3" y="14" width="7" height="7" rx="1"/>
  4520. </svg>
  4521. </div>
  4522. <div class="template-name">应用中心</div>
  4523. </div>
  4524. <!-- 创建应用 -->
  4525. <div class="template-card" onclick="openCreateAppModal()">
  4526. <div class="template-preview">
  4527. <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
  4528. <circle cx="12" cy="12" r="10"/>
  4529. <line x1="12" y1="8" x2="12" y2="16"/>
  4530. <line x1="8" y1="12" x2="16" y2="12"/>
  4531. </svg>
  4532. </div>
  4533. <div class="template-name">创建应用</div>
  4534. </div>
  4535. </div>
  4536. </div>
  4537. </div>
  4538. <!-- 二级菜单 - 交互网页 -->
  4539. <div class="secondary-panel hidden" id="webPanel">
  4540. <!-- 列表视图 -->
  4541. <div id="webListView">
  4542. <div class="panel-header">
  4543. <span class="panel-title">添加交互网页</span>
  4544. <button class="collapse-btn" onclick="toggleSecondaryPanel()">
  4545. <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
  4546. <polyline points="15 18 9 12 15 6"/>
  4547. </svg>
  4548. </button>
  4549. </div>
  4550. <div class="secondary-content" style="overflow-y: auto;">
  4551. <div class="template-grid">
  4552. <!-- 网页中心 -->
  4553. <div class="template-card" onclick="openWebCenter()">
  4554. <div class="template-preview">
  4555. <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
  4556. <rect x="2" y="3" width="20" height="14" rx="2"/>
  4557. <line x1="2" y1="7" x2="22" y2="7"/>
  4558. <circle cx="5" cy="5" r="0.5" fill="currentColor"/>
  4559. <circle cx="7" cy="5" r="0.5" fill="currentColor"/>
  4560. <circle cx="9" cy="5" r="0.5" fill="currentColor"/>
  4561. <path d="M8 11h8M8 14h5"/>
  4562. </svg>
  4563. </div>
  4564. <div class="template-name">网页中心</div>
  4565. </div>
  4566. <!-- 上传网页 -->
  4567. <div class="template-card" onclick="uploadWebPage()">
  4568. <div class="template-preview">
  4569. <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
  4570. <path d="M21 15v4a2 2 0 01-2 2H5a2 2 0 01-2-2v-4"/>
  4571. <polyline points="17 8 12 3 7 8"/>
  4572. <line x1="12" y1="3" x2="12" y2="15"/>
  4573. </svg>
  4574. </div>
  4575. <div class="template-name">上传网页</div>
  4576. </div>
  4577. <!-- 爬取网页 -->
  4578. <div class="template-card" onclick="crawlWebPage()">
  4579. <div class="template-preview">
  4580. <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
  4581. <circle cx="12" cy="12" r="10"/>
  4582. <path d="M2 12h20"/>
  4583. <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"/>
  4584. <path d="M16 8l-4 4-4-4" stroke-width="1.5"/>
  4585. </svg>
  4586. </div>
  4587. <div class="template-name">爬取网页</div>
  4588. </div>
  4589. </div>
  4590. </div>
  4591. </div>
  4592. <!-- 配置视图 -->
  4593. <div id="webConfigView" class="hidden">
  4594. <!-- 上传网页配置 -->
  4595. <div class="panel-content-wrapper hidden" id="uploadWebView">
  4596. <div class="config-header">
  4597. <button class="config-back-btn" onclick="backToWebList()">
  4598. <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
  4599. <path d="M19 12H5M12 19l-7-7 7-7"/>
  4600. </svg>
  4601. </button>
  4602. <span class="config-tool-name">上传网页</span>
  4603. </div>
  4604. <div class="config-content">
  4605. <div class="web-config-form">
  4606. <div class="form-group">
  4607. <label class="form-label">网页名称</label>
  4608. <input type="text" class="form-input" id="uploadWebName" placeholder="请输入网页名称" oninput="validateUploadForm()">
  4609. </div>
  4610. <!-- Tabs -->
  4611. <div class="upload-tabs">
  4612. <button id="uploadFileTab" class="upload-tab active" onclick="switchUploadTab('file')">上传文件</button>
  4613. <button id="pasteCodeTab" class="upload-tab" onclick="switchUploadTab('code')">粘贴代码</button>
  4614. </div>
  4615. <!-- Tab Panels -->
  4616. <div id="uploadFilePanel">
  4617. <div class="form-group">
  4618. <div class="file-upload-area" id="fileUploadArea" onclick="document.getElementById('fileInput').click()">
  4619. <input type="file" id="fileInput" accept=".html,.htm,.zip" style="display: none;" onchange="handleFileSelect(event)">
  4620. <svg width="48" height="48" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5">
  4621. <path d="M21 15v4a2 2 0 01-2 2H5a2 2 0 01-2-2v-4"/>
  4622. <polyline points="17 8 12 3 7 8"/>
  4623. <line x1="12" y1="3" x2="12" y2="15"/>
  4624. </svg>
  4625. <p class="upload-text">点击或拖拽文件到此处上传</p>
  4626. <p class="upload-hint">支持 HTML、HTM、ZIP 格式</p>
  4627. </div>
  4628. <div id="fileNameDisplay" class="file-name-display"></div>
  4629. </div>
  4630. </div>
  4631. <div id="pasteCodePanel" class="hidden">
  4632. <div class="form-group">
  4633. <textarea id="pasteCodeTextarea" class="code-textarea" placeholder="请在此处粘贴完整的HTML代码" oninput="validateUploadForm()"></textarea>
  4634. </div>
  4635. </div>
  4636. <div class="web-config-actions" style="padding: 0 30%;">
  4637. <button class="config-btn config-btn-primary status-btn" id="confirmCreateWebBtn" onclick="confirmCreateWebPage()" disabled>
  4638. <span class="btn-status-icon" id="uploadStatusIcon" aria-hidden="true"></span>
  4639. <span class="btn-status-text" id="uploadStatusText">等待上传...</span>
  4640. </button>
  4641. </div>
  4642. </div>
  4643. </div>
  4644. </div>
  4645. <!-- 爬取网页配置 -->
  4646. <div class="panel-content-wrapper hidden" id="crawlWebView">
  4647. <div class="config-header">
  4648. <button class="config-back-btn" onclick="backToWebList()">
  4649. <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
  4650. <path d="M19 12H5M12 19l-7-7 7-7"/>
  4651. </svg>
  4652. </button>
  4653. <span class="config-tool-name">爬取网页</span>
  4654. </div>
  4655. <div class="config-content">
  4656. <div class="web-config-form">
  4657. <div class="form-group">
  4658. <label class="form-label">网页名称</label>
  4659. <input type="text" class="form-input" id="crawlWebName" placeholder="请输入网页名称" oninput="validateCrawlUrl()">
  4660. </div>
  4661. <div class="form-group">
  4662. <label class="form-label">网页链接</label>
  4663. <input type="url" class="form-input" id="crawlWebUrl" placeholder="请输入完整的网页URL地址" oninput="validateCrawlUrl()">
  4664. </div>
  4665. <div class="web-config-actions" style="padding: 0 30%;">
  4666. <button class="config-btn config-btn-primary crawl-btn" id="startCrawlBtn" onclick="startCrawlWeb()" disabled>
  4667. <span class="btn-status-icon" id="crawlStatusIcon" aria-hidden="true"></span>
  4668. <span class="btn-status-text" id="crawlStatusText">等待输入...</span>
  4669. </button>
  4670. </div>
  4671. </div>
  4672. </div>
  4673. </div>
  4674. </div>
  4675. </div>
  4676. <!-- 二级菜单 - 资源 -->
  4677. <div class="secondary-panel hidden" id="resourcesPanel">
  4678. <div class="panel-header">
  4679. <span class="panel-title">添加多媒体</span>
  4680. <button class="collapse-btn" onclick="toggleSecondaryPanel()">
  4681. <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
  4682. <polyline points="15 18 9 12 15 6"/>
  4683. </svg>
  4684. </button>
  4685. </div>
  4686. <div class="secondary-content" style="overflow-y: auto;">
  4687. <div class="template-grid">
  4688. <div class="template-card" onclick="openVideoSourceModal()">
  4689. <div class="template-preview">
  4690. <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
  4691. <rect x="3" y="4" width="18" height="16" rx="2" ry="2"/>
  4692. <polygon points="10 9 16 12 10 15 10 9"/>
  4693. </svg>
  4694. </div>
  4695. <div class="template-name">视频</div>
  4696. </div>
  4697. <div class="template-card" onclick="openAudioUploadModal()">
  4698. <div class="template-preview">
  4699. <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
  4700. <path d="M9 18V5l12-2v13"/>
  4701. <circle cx="6" cy="18" r="3"/>
  4702. <circle cx="18" cy="16" r="3"/>
  4703. </svg>
  4704. </div>
  4705. <div class="template-name">音频</div>
  4706. </div>
  4707. <div class="template-card" onclick="openDocumentUploadModal()">
  4708. <div class="template-preview">
  4709. <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
  4710. <path d="M14 2H6a2 2 0 00-2 2v16a2 2 0 002 2h12a2 2 0 002-2V8z"/>
  4711. <polyline points="14 2 14 8 20 8"/>
  4712. <line x1="8" y1="13" x2="16" y2="13"/>
  4713. <line x1="8" y1="17" x2="14" y2="17"/>
  4714. </svg>
  4715. </div>
  4716. <div class="template-name">文档</div>
  4717. </div>
  4718. <div class="template-card" onclick="openCollectionModal()">
  4719. <div class="template-preview">
  4720. <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
  4721. <rect x="3" y="4" width="8" height="14" rx="2"/>
  4722. <rect x="13" y="4" width="8" height="14" rx="2"/>
  4723. <line x1="7" y1="9" x2="7" y2="13"/>
  4724. <line x1="17" y1="9" x2="17" y2="13"/>
  4725. </svg>
  4726. </div>
  4727. <div class="template-name">文档集</div>
  4728. </div>
  4729. </div>
  4730. </div>
  4731. </div>
  4732. </div>
  4733. <!-- 中央编辑区 -->
  4734. <div class="center-area">
  4735. <div class="element-toolbar" id="elementToolbar">
  4736. <!-- 插入工具 -->
  4737. <div class="toolbar-section">
  4738. <button class="toolbar-btn" onclick="insertText()">
  4739. <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
  4740. <path d="M4 7V4h16v3M9 20h6M12 4v16"/>
  4741. </svg>
  4742. 文本
  4743. </button>
  4744. <button class="toolbar-btn" onclick="insertImage()">
  4745. <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
  4746. <rect x="3" y="3" width="18" height="18" rx="2" ry="2"/>
  4747. <circle cx="8.5" cy="8.5" r="1.5"/>
  4748. <polyline points="21 15 16 10 5 21"/>
  4749. </svg>
  4750. 图片
  4751. </button>
  4752. <button class="toolbar-btn" onclick="insertTable()">
  4753. <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
  4754. <rect x="3" y="3" width="18" height="18" rx="2"/>
  4755. <line x1="3" y1="9" x2="21" y2="9"/>
  4756. <line x1="3" y1="15" x2="21" y2="15"/>
  4757. <line x1="9" y1="3" x2="9" y2="21"/>
  4758. <line x1="15" y1="3" x2="15" y2="21"/>
  4759. </svg>
  4760. 表格
  4761. </button>
  4762. <div class="dropdown" id="shapeDropdown">
  4763. <button class="toolbar-btn" onclick="toggleDropdown('shapeDropdown')">
  4764. <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
  4765. <circle cx="12" cy="12" r="10"/>
  4766. </svg>
  4767. 形状
  4768. <svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
  4769. <polyline points="6 9 12 15 18 9"/>
  4770. </svg>
  4771. </button>
  4772. <div class="dropdown-menu">
  4773. <div class="dropdown-item" onclick="insertShape('circle')">
  4774. <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
  4775. <circle cx="12" cy="12" r="10"/>
  4776. </svg>
  4777. 圆形
  4778. </div>
  4779. <div class="dropdown-item" onclick="insertShape('rectangle')">
  4780. <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
  4781. <rect x="3" y="3" width="18" height="18" rx="2"/>
  4782. </svg>
  4783. 矩形
  4784. </div>
  4785. <div class="dropdown-item" onclick="insertShape('triangle')">
  4786. <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
  4787. <path d="M12 2 L22 20 L2 20 Z"/>
  4788. </svg>
  4789. 三角形
  4790. </div>
  4791. <div class="dropdown-item" onclick="insertShape('arrow')">
  4792. <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
  4793. <line x1="5" y1="12" x2="19" y2="12"/>
  4794. <polyline points="12 5 19 12 12 19"/>
  4795. </svg>
  4796. 箭头
  4797. </div>
  4798. </div>
  4799. </div>
  4800. </div>
  4801. <!-- 编辑工具(选中元素时显示) -->
  4802. <div class="toolbar-section" id="editTools" style="display: none;">
  4803. <button class="toolbar-btn">
  4804. <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
  4805. <path d="M11 4H4a2 2 0 00-2 2v14a2 2 0 002 2h14a2 2 0 002-2v-7"/>
  4806. <path d="M18.5 2.5a2.121 2.121 0 013 3L12 15l-4 1 1-4 9.5-9.5z"/>
  4807. </svg>
  4808. 编辑
  4809. </button>
  4810. <button class="toolbar-btn" onclick="deleteElement()">
  4811. <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
  4812. <polyline points="3 6 5 6 21 6"/>
  4813. <path d="M19 6v14a2 2 0 01-2 2H7a2 2 0 01-2-2V6m3 0V4a2 2 0 012-2h4a2 2 0 012 2v2"/>
  4814. </svg>
  4815. 删除
  4816. </button>
  4817. <button class="toolbar-btn">
  4818. <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
  4819. <rect x="8" y="8" width="8" height="8"/>
  4820. <path d="M4 16c-1.1 0-2-.9-2-2V6c0-1.1.9-2 2-2h8c1.1 0 2 .9 2 2"/>
  4821. </svg>
  4822. 复制
  4823. </button>
  4824. </div>
  4825. </div>
  4826. <div class="slides-area">
  4827. <div class="slide-canvas" id="slideCanvas">
  4828. <div class="slide-placeholder" id="slidePlaceholder">
  4829. <div class="slide-placeholder-icon">✨</div>
  4830. <div class="slide-placeholder-text">使用AI生成或创建课程内容</div>
  4831. <div class="slide-placeholder-hint">描述您的需求,AI将为您创建完整课程</div>
  4832. </div>
  4833. </div>
  4834. <!-- 工具编辑区域 -->
  4835. <div class="tool-edit-container" id="toolEditContainer" style="display: none;">
  4836. <!-- 工具编辑内容将在这里动态生成 -->
  4837. </div>
  4838. </div>
  4839. <div class="bottom-outline" id="bottomOutline">
  4840. <div class="outline-track" id="outlineTrack">
  4841. <!-- 页面大纲将在这里生成 -->
  4842. </div>
  4843. </div>
  4844. </div>
  4845. <!-- 右侧面板 -->
  4846. <div class="right-panel collapsed" id="rightPanel">
  4847. <div class="panel-header">
  4848. <span class="panel-title collapse-text">页面大纲</span>
  4849. <button class="collapse-btn" onclick="toggleRightPanel()">
  4850. <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
  4851. <polyline points="15 18 9 12 15 6"/>
  4852. </svg>
  4853. </button>
  4854. </div>
  4855. <div class="outline-panel">
  4856. <div class="outline-toolbar">
  4857. <div class="outline-toolbar-left">
  4858. <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
  4859. <rect x="3" y="4" width="18" height="16" rx="2"/>
  4860. <line x1="3" y1="10" x2="21" y2="10"/>
  4861. </svg>
  4862. <span>大纲</span>
  4863. </div>
  4864. <div class="outline-actions">
  4865. <button class="outline-btn" onclick="addOutlineGroup()">
  4866. <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
  4867. <line x1="12" y1="5" x2="12" y2="19"/>
  4868. <line x1="5" y1="12" x2="19" y2="12"/>
  4869. </svg>
  4870. 分组
  4871. </button>
  4872. </div>
  4873. </div>
  4874. <div class="outline-list" id="rightOutlineList">
  4875. <div class="outline-empty">暂无页面,创建后将出现在此</div>
  4876. </div>
  4877. </div>
  4878. </div>
  4879. </div>
  4880. <!-- 创建入口弹窗 -->
  4881. <div class="modal-overlay" id="createModal">
  4882. <div class="create-modal">
  4883. <button class="modal-close" onclick="hideCreateModal()">
  4884. <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
  4885. <line x1="18" y1="6" x2="6" y2="18"/>
  4886. <line x1="6" y1="6" x2="18" y2="18"/>
  4887. </svg>
  4888. </button>
  4889. <div class="modal-header">
  4890. <h2>创建新课程</h2>
  4891. <p>选择一种方式开始创建您的互动课件</p>
  4892. </div>
  4893. <div class="create-options">
  4894. <div class="create-card featured" id="aiCreateCard" onclick="startAICreate()">
  4895. <div class="create-icon">
  4896. <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
  4897. <path d="M12 2L2 7l10 5 10-5-10-5z"/>
  4898. <path d="M2 17l10 5 10-5"/>
  4899. <path d="M2 12l10 5 10-5"/>
  4900. </svg>
  4901. </div>
  4902. <h3>从AI创建</h3>
  4903. <p>AI自动生成完整教学内容</p>
  4904. </div>
  4905. <div class="create-card" id="pptCreateCard" onclick="startPptUploadFlow()" onmouseenter="unsetAiFeatured()">
  4906. <div class="create-icon">
  4907. <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
  4908. <path d="M21 15v4a2 2 0 01-2 2H5a2 2 0 01-2-2v-4"/>
  4909. <polyline points="17 8 12 3 7 8"/>
  4910. <line x1="12" y1="3" x2="12" y2="15"/>
  4911. </svg>
  4912. </div>
  4913. <h3>上传我的文件</h3>
  4914. <p>上传本地PPT文件并解析</p>
  4915. </div>
  4916. <div class="create-card" onmouseenter="unsetAiFeatured()">
  4917. <div class="create-icon">
  4918. <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
  4919. <path d="M3 9l9-7 9 7v11a2 2 0 01-2 2H5a2 2 0 01-2-2z"/>
  4920. <polyline points="9 22 9 12 15 12 15 22"/>
  4921. </svg>
  4922. </div>
  4923. <h3>从资源库导入</h3>
  4924. <p>选择已有的课程模板</p>
  4925. </div>
  4926. <div class="create-card" onmouseenter="unsetAiFeatured()" onclick="createBlankCourse()">
  4927. <div class="create-icon">
  4928. <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
  4929. <path d="M14 2H6a2 2 0 00-2 2v16a2 2 0 002 2h12a2 2 0 002-2V8z"/>
  4930. <polyline points="14 2 14 8 20 8"/>
  4931. </svg>
  4932. </div>
  4933. <h3>创建空白</h3>
  4934. <p>从零开始自定义</p>
  4935. </div>
  4936. </div>
  4937. </div>
  4938. </div>
  4939. <!-- 课程设置弹窗 -->
  4940. <div class="modal-overlay" id="courseSettingsModal">
  4941. <div class="settings-modal">
  4942. <div class="settings-header">
  4943. <div class="settings-title">课程设置</div>
  4944. <button class="modal-close" onclick="hideCourseSettings()">
  4945. <svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
  4946. <line x1="18" y1="6" x2="6" y2="18"/>
  4947. <line x1="6" y1="6" x2="18" y2="18"/>
  4948. </svg>
  4949. </button>
  4950. </div>
  4951. <div class="settings-body">
  4952. <div class="form-group">
  4953. <label class="form-label">学科</label>
  4954. <select class="form-select" id="settingsSubjectSelect">
  4955. <option value="">请选择学科</option>
  4956. <option value="chinese">语文</option>
  4957. <option value="math">数学</option>
  4958. <option value="english">英语</option>
  4959. <option value="physics">物理</option>
  4960. <option value="chemistry">化学</option>
  4961. <option value="biology">生物</option>
  4962. <option value="history">历史</option>
  4963. <option value="geography">地理</option>
  4964. <option value="politics">道德与法治</option>
  4965. <option value="science">科学</option>
  4966. </select>
  4967. </div>
  4968. <div class="form-group">
  4969. <label class="form-label">年级</label>
  4970. <select class="form-select" id="settingsGradeSelect">
  4971. <option value="">请选择年级</option>
  4972. <option value="1">一年级</option>
  4973. <option value="2">二年级</option>
  4974. <option value="3">三年级</option>
  4975. <option value="4">四年级</option>
  4976. <option value="5">五年级</option>
  4977. <option value="6">六年级</option>
  4978. <option value="7">七年级</option>
  4979. <option value="8">八年级</option>
  4980. <option value="9">九年级</option>
  4981. </select>
  4982. </div>
  4983. </div>
  4984. <div class="settings-actions">
  4985. <button class="settings-btn" onclick="hideCourseSettings()">取消</button>
  4986. <button class="settings-btn primary" onclick="applyCourseSettings()">应用</button>
  4987. </div>
  4988. </div>
  4989. </div>
  4990. <!-- PPT上传隐藏输入 -->
  4991. <input type="file" id="pptFileInput" accept=".ppt,.pptx" style="display: none;" onchange="handlePptFileSelected(event)">
  4992. <!-- PPT解析浮窗 -->
  4993. <div class="ppt-parse-overlay" id="pptParseOverlay">
  4994. <div class="ppt-parse-card">
  4995. <div class="ppt-parse-icon" id="pptParseIcon"></div>
  4996. <div class="ppt-parse-title" id="pptParseTitle">解析中...</div>
  4997. <div class="ppt-parse-desc" id="pptParseDesc">正在处理所选PPT文件</div>
  4998. <div class="ppt-parse-actions" id="pptParseActions">
  4999. <button class="ppt-parse-btn primary" onclick="closePptParseOverlay()">关闭</button>
  5000. </div>
  5001. </div>
  5002. </div>
  5003. <!-- 发布弹窗 -->
  5004. <div class="modal-overlay" id="publishModal">
  5005. <div class="publish-modal">
  5006. <button class="modal-close" onclick="hidePublishModal()">
  5007. <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
  5008. <line x1="18" y1="6" x2="6" y2="18"/>
  5009. <line x1="6" y1="6" x2="18" y2="18"/>
  5010. </svg>
  5011. </button>
  5012. <div class="publish-header">
  5013. <h2 class="publish-title">发布课程</h2>
  5014. <div class="publish-course-name" id="publishCourseNameDisplay" onclick="editCourseName()">
  5015. 水的三态变化
  5016. </div>
  5017. </div>
  5018. <div class="publish-content">
  5019. <!-- 左侧表单 -->
  5020. <form class="publish-form" onsubmit="confirmPublish(event)">
  5021. <div class="form-group">
  5022. <label class="form-label">
  5023. 学科 <span class="required">*</span>
  5024. </label>
  5025. <select class="form-select" id="subjectSelect" required>
  5026. <option value="">请选择学科</option>
  5027. <option value="chinese">语文</option>
  5028. <option value="math">数学</option>
  5029. <option value="english">英语</option>
  5030. <option value="physics">物理</option>
  5031. <option value="chemistry">化学</option>
  5032. <option value="biology">生物</option>
  5033. <option value="history">历史</option>
  5034. <option value="geography">地理</option>
  5035. <option value="politics">道德与法治</option>
  5036. <option value="science">科学</option>
  5037. </select>
  5038. </div>
  5039. <div class="form-row">
  5040. <div class="form-group">
  5041. <label class="form-label">
  5042. 年级 <span class="required">*</span>
  5043. </label>
  5044. <select class="form-select" id="classGradeSelect" required>
  5045. <option value="">请选择年级</option>
  5046. <option value="1">一年级</option>
  5047. <option value="2">二年级</option>
  5048. <option value="3">三年级</option>
  5049. <option value="4">四年级</option>
  5050. <option value="5">五年级</option>
  5051. <option value="6">六年级</option>
  5052. <option value="7">七年级</option>
  5053. <option value="8">八年级</option>
  5054. <option value="9">九年级</option>
  5055. </select>
  5056. </div>
  5057. <div class="form-group">
  5058. <label class="form-label">
  5059. 班级 <span class="required">*</span>
  5060. </label>
  5061. <select class="form-select" id="classSelect" required>
  5062. <option value="">请选择班级</option>
  5063. <option value="1">1班</option>
  5064. <option value="2">2班</option>
  5065. <option value="3">3班</option>
  5066. <option value="4">4班</option>
  5067. <option value="5">5班</option>
  5068. <option value="6">6班</option>
  5069. <option value="7">7班</option>
  5070. <option value="8">8班</option>
  5071. <option value="9">9班</option>
  5072. <option value="10">10班</option>
  5073. </select>
  5074. </div>
  5075. </div>
  5076. <div class="form-group">
  5077. <label class="form-label">
  5078. 可见范围 <span class="required">*</span>
  5079. </label>
  5080. <div class="visibility-options">
  5081. <label class="radio-option selected" onclick="selectVisibility(this, 'students')">
  5082. <input type="radio" name="visibility" value="students" checked>
  5083. <div class="radio-custom"></div>
  5084. <div class="radio-content">
  5085. <div class="radio-label">仅发布学生可见</div>
  5086. <div class="radio-description">仅对发布的班级学生可见,其他人无法访问</div>
  5087. </div>
  5088. </label>
  5089. <label class="radio-option" onclick="selectVisibility(this, 'organization')">
  5090. <input type="radio" name="visibility" value="organization">
  5091. <div class="radio-custom"></div>
  5092. <div class="radio-content">
  5093. <div class="radio-label">组织可见</div>
  5094. <div class="radio-description">学校内所有师生均可查看和使用</div>
  5095. </div>
  5096. </label>
  5097. </div>
  5098. </div>
  5099. <div class="publish-actions">
  5100. <button type="button" class="publish-btn publish-btn-cancel" onclick="hidePublishModal()">
  5101. 取消
  5102. </button>
  5103. <button type="submit" class="publish-btn publish-btn-confirm">
  5104. <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
  5105. <path d="M22 11.08V12a10 10 0 11-5.93-9.14"/>
  5106. <polyline points="22 4 12 14.01 9 11.01"/>
  5107. </svg>
  5108. 确认发布
  5109. </button>
  5110. </div>
  5111. </form>
  5112. <!-- 右侧封面 -->
  5113. <div class="publish-cover-section">
  5114. <label class="publish-cover-label">课程封面</label>
  5115. <div class="publish-cover-wrapper">
  5116. <div class="publish-cover-placeholder" id="coverPlaceholder">
  5117. <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
  5118. <rect x="3" y="3" width="18" height="18" rx="2" ry="2"/>
  5119. <circle cx="8.5" cy="8.5" r="1.5"/>
  5120. <polyline points="21 15 16 10 5 21"/>
  5121. </svg>
  5122. <p>悬浮选择上传方式</p>
  5123. </div>
  5124. <img id="courseCoverImage" style="display: none;">
  5125. <!-- 悬浮菜单 -->
  5126. <div class="publish-cover-menu">
  5127. <div class="publish-cover-menu-item" onclick="uploadCourseCoverLocal(event)">
  5128. <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
  5129. <path d="M21 15v4a2 2 0 01-2 2H5a2 2 0 01-2-2v-4"/>
  5130. <polyline points="17 8 12 3 7 8"/>
  5131. <line x1="12" y1="3" x2="12" y2="15"/>
  5132. </svg>
  5133. 自本地上传
  5134. </div>
  5135. <div class="publish-cover-menu-item" onclick="searchWebImageForCover(event)">
  5136. <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
  5137. <circle cx="11" cy="11" r="8"/>
  5138. <path d="M21 21l-4.35-4.35"/>
  5139. </svg>
  5140. 自网页搜索
  5141. </div>
  5142. <div class="publish-cover-menu-item" onclick="generateAIImageForCover(event)">
  5143. <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
  5144. <path d="M12 2L2 7l10 5 10-5-10-5z"/>
  5145. <path d="M2 17l10 5 10-5"/>
  5146. <path d="M2 12l10 5 10-5"/>
  5147. </svg>
  5148. 自AI生成
  5149. </div>
  5150. </div>
  5151. <!-- 已上传封面时显示的操作按钮 -->
  5152. <div class="publish-cover-actions">
  5153. <button class="cover-action-btn delete" type="button" onclick="event.stopPropagation(); deleteCourseCover()" title="删除封面">
  5154. <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
  5155. <polyline points="3 6 5 6 21 6"/>
  5156. <path d="M19 6v14a2 2 0 01-2 2H7a2 2 0 01-2-2V6m3 0V4a2 2 0 012-2h4a2 2 0 012 2v2"/>
  5157. </svg>
  5158. </button>
  5159. </div>
  5160. </div>
  5161. </div>
  5162. </div>
  5163. </div>
  5164. </div>
  5165. <!-- 隐藏的文件上传输入框 -->
  5166. <input type="file" id="courseCoverInput" accept="image/*" style="display: none;" onchange="handleCoverUpload(event)">
  5167. <!-- 网页搜索图片浮窗 -->
  5168. <div class="floating-modal" id="searchImageModal">
  5169. <div class="floating-content">
  5170. <div class="floating-header">
  5171. <h3 class="floating-title">网页搜索图片</h3>
  5172. <button class="floating-close" onclick="closeSearchModal()">
  5173. <svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
  5174. <line x1="18" y1="6" x2="6" y2="18"/>
  5175. <line x1="6" y1="6" x2="18" y2="18"/>
  5176. </svg>
  5177. </button>
  5178. </div>
  5179. <div class="search-input-group">
  5180. <label class="search-input-label">搜索关键词</label>
  5181. <input type="text" class="search-input" id="searchKeyword" placeholder="AI已为您生成关键词..." value="水的三态变化 实验">
  5182. </div>
  5183. <div class="search-results" id="searchResults">
  5184. <!-- 搜索结果将在这里显示 -->
  5185. <div class="search-result-item" style="background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);">
  5186. </div>
  5187. <div class="search-result-item" style="background: linear-gradient(135deg, #f093fb 0%, #f5576c 100%);">
  5188. </div>
  5189. <div class="search-result-item" style="background: linear-gradient(135deg, #4facfe 0%, #00f2fe 100%);">
  5190. </div>
  5191. <div class="search-result-item" style="background: linear-gradient(135deg, #43e97b 0%, #38f9d7 100%);">
  5192. </div>
  5193. <div class="search-result-item" style="background: linear-gradient(135deg, #fa709a 0%, #fee140 100%);">
  5194. </div>
  5195. <div class="search-result-item" style="background: linear-gradient(135deg, #30cfd0 0%, #330867 100%);">
  5196. </div>
  5197. </div>
  5198. <div class="floating-actions">
  5199. <button class="floating-btn floating-btn-secondary" onclick="closeSearchModal()">取消</button>
  5200. <button class="floating-btn floating-btn-primary" onclick="confirmSearchImage()">确定</button>
  5201. </div>
  5202. </div>
  5203. </div>
  5204. <!-- AI生成图片浮窗 -->
  5205. <div class="floating-modal" id="generateImageModal">
  5206. <div class="floating-content">
  5207. <div class="floating-header">
  5208. <h3 class="floating-title">AI生成图片</h3>
  5209. <button class="floating-close" onclick="closeGenerateModal()">
  5210. <svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
  5211. <line x1="18" y1="6" x2="6" y2="18"/>
  5212. <line x1="6" y1="6" x2="18" y2="18"/>
  5213. </svg>
  5214. </button>
  5215. </div>
  5216. <div class="search-input-group">
  5217. <label class="search-input-label">生成描述</label>
  5218. <input type="text" class="search-input" id="generatePrompt" placeholder="AI已为您生成描述..." value="水分子在不同温度下的三态变化示意图">
  5219. </div>
  5220. <div class="generate-preview" id="generatePreview">
  5221. <svg width="64" height="64" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5">
  5222. <rect x="3" y="3" width="18" height="18" rx="2" ry="2"/>
  5223. <circle cx="8.5" cy="8.5" r="1.5"/>
  5224. <polyline points="21 15 16 10 5 21"/>
  5225. </svg>
  5226. <div class="generate-loading" id="generateLoading">
  5227. <div class="loading-spinner"></div>
  5228. <div class="loading-text">AI生成中...</div>
  5229. </div>
  5230. </div>
  5231. <div class="floating-actions">
  5232. <button class="floating-btn floating-btn-secondary" onclick="closeGenerateModal()">取消</button>
  5233. <button class="floating-btn floating-btn-primary" onclick="startGenerate()">生成</button>
  5234. </div>
  5235. </div>
  5236. </div>
  5237. <!-- AI应用中心浮窗 -->
  5238. <div class="floating-modal" id="aiAppCenterModal">
  5239. <div class="app-center-content">
  5240. <div class="floating-header">
  5241. <h3 class="floating-title">应用中心</h3>
  5242. <button class="floating-close" onclick="closeAppCenter()">
  5243. <svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
  5244. <line x1="18" y1="6" x2="6" y2="18"/>
  5245. <line x1="6" y1="6" x2="18" y2="18"/>
  5246. </svg>
  5247. </button>
  5248. </div>
  5249. <!-- 筛选区 -->
  5250. <div class="app-filters">
  5251. <div class="filter-group">
  5252. <div class="filter-label">应用来源</div>
  5253. <select class="filter-select" id="sourceFilter" onchange="filterApps()">
  5254. <option value="all">所有应用</option>
  5255. <option value="public">公开应用</option>
  5256. <option value="mine">我的应用</option>
  5257. </select>
  5258. </div>
  5259. <div class="filter-group">
  5260. <div class="filter-label">应用类型</div>
  5261. <select class="filter-select" id="typeFilter" onchange="filterApps()">
  5262. <option value="all">所有类型</option>
  5263. <option value="agent">智能体</option>
  5264. <option value="workflow">工作流</option>
  5265. </select>
  5266. </div>
  5267. <div class="filter-group">
  5268. <div class="filter-label">交互模式</div>
  5269. <select class="filter-select" id="modeFilter" onchange="filterApps()">
  5270. <option value="all">所有模式</option>
  5271. <option value="card">卡片式</option>
  5272. <option value="immersive">沉浸式</option>
  5273. <option value="chat">聊天式</option>
  5274. </select>
  5275. </div>
  5276. </div>
  5277. <!-- 应用列表 -->
  5278. <div class="app-grid" id="appGrid">
  5279. <!-- 应用卡片将在这里动态生成 -->
  5280. </div>
  5281. <!-- 底部操作栏 -->
  5282. <div class="app-center-footer">
  5283. <div class="selected-count">已选择 <span id="selectedCountText">0</span> 个应用</div>
  5284. <div class="app-center-actions">
  5285. <button class="app-center-btn app-center-btn-confirm" id="confirmAddAppsBtn" onclick="confirmAddApps()" disabled>
  5286. <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
  5287. <polyline points="20 6 9 17 4 12"/>
  5288. </svg>
  5289. 添加
  5290. </button>
  5291. </div>
  5292. </div>
  5293. </div>
  5294. </div>
  5295. <!-- 模板中心浮窗 -->
  5296. <div class="floating-modal" id="templateCenterModal">
  5297. <div class="app-center-content">
  5298. <div class="floating-header">
  5299. <h3 class="floating-title">模板中心</h3>
  5300. <button class="floating-close" onclick="closeTemplateCenter()">
  5301. <svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
  5302. <line x1="18" y1="6" x2="6" y2="18"/>
  5303. <line x1="6" y1="6" x2="18" y2="18"/>
  5304. </svg>
  5305. </button>
  5306. </div>
  5307. <div class="app-filters">
  5308. <div class="filter-group">
  5309. <div class="filter-label">来源</div>
  5310. <select class="filter-select" id="tplSourceFilter" onchange="filterTemplates()">
  5311. <option value="all">所有模板</option>
  5312. <option value="official">官方</option>
  5313. <option value="mine">我的</option>
  5314. </select>
  5315. </div>
  5316. <div class="filter-group">
  5317. <div class="filter-label">学科</div>
  5318. <select class="filter-select" id="tplSubjectFilter" onchange="filterTemplates()">
  5319. <option value="all">全部学科</option>
  5320. <option value="chinese">语文</option>
  5321. <option value="math">数学</option>
  5322. <option value="english">英语</option>
  5323. <option value="science">科学</option>
  5324. </select>
  5325. </div>
  5326. <div class="filter-group">
  5327. <div class="filter-label">年级</div>
  5328. <select class="filter-select" id="tplGradeFilter" onchange="filterTemplates()">
  5329. <option value="all">全部年级</option>
  5330. <option value="1">一年级</option>
  5331. <option value="2">二年级</option>
  5332. <option value="3">三年级</option>
  5333. <option value="4">四年级</option>
  5334. <option value="5">五年级</option>
  5335. <option value="6">六年级</option>
  5336. <option value="7">七年级</option>
  5337. <option value="8">八年级</option>
  5338. <option value="9">九年级</option>
  5339. </select>
  5340. </div>
  5341. </div>
  5342. <div class="app-grid" id="templateGrid">
  5343. <!-- 模板卡片将在这里生成 -->
  5344. </div>
  5345. <div class="app-center-footer">
  5346. <div class="selected-count">已选择 <span id="selectedTemplateCount">0</span> 个模板</div>
  5347. <div class="app-center-actions">
  5348. <button class="app-center-btn app-center-btn-confirm" id="confirmApplyTemplateBtn" onclick="confirmApplyTemplates()" disabled>
  5349. <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
  5350. <polyline points="20 6 9 17 4 12"/>
  5351. </svg>
  5352. 应用
  5353. </button>
  5354. </div>
  5355. </div>
  5356. </div>
  5357. </div>
  5358. <!-- 删除课程确认弹窗 -->
  5359. <div class="modal-overlay" id="deleteCourseModal">
  5360. <div class="confirm-modal">
  5361. <div class="confirm-title">删除课程</div>
  5362. <div class="confirm-text">删除后不可恢复,课件及关联资源将移除,确认要删除当前课程吗?</div>
  5363. <div class="confirm-actions">
  5364. <button class="settings-btn" onclick="hideDeleteCourse()">取消</button>
  5365. <button class="settings-btn primary" onclick="confirmDeleteCourse()">删除</button>
  5366. </div>
  5367. </div>
  5368. </div>
  5369. <!-- 网页中心浮窗 -->
  5370. <div class="floating-modal" id="webCenterModal">
  5371. <div class="web-center-content">
  5372. <div class="floating-header">
  5373. <h3 class="floating-title">网页中心</h3>
  5374. <button class="floating-close" onclick="closeWebCenter()">
  5375. <svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
  5376. <line x1="18" y1="6" x2="6" y2="18"/>
  5377. <line x1="6" y1="6" x2="18" y2="18"/>
  5378. </svg>
  5379. </button>
  5380. </div>
  5381. <!-- 筛选区 -->
  5382. <div class="web-filters">
  5383. <div class="filter-group">
  5384. <div class="filter-label">网页来源</div>
  5385. <select class="filter-select" id="webSourceFilter" onchange="filterWebs()">
  5386. <option value="all">所有网页</option>
  5387. <option value="public">公开网页</option>
  5388. <option value="mine">我的网页</option>
  5389. </select>
  5390. </div>
  5391. <div class="filter-group">
  5392. <div class="filter-label">学科分类</div>
  5393. <select class="filter-select" id="webSubjectFilter" onchange="filterWebs()">
  5394. <option value="all">所有学科</option>
  5395. <option value="语文">语文</option>
  5396. <option value="数学">数学</option>
  5397. <option value="英语">英语</option>
  5398. <option value="物理">物理</option>
  5399. <option value="化学">化学</option>
  5400. <option value="生物">生物</option>
  5401. <option value="历史">历史</option>
  5402. <option value="地理">地理</option>
  5403. </select>
  5404. </div>
  5405. <div class="filter-group">
  5406. <div class="filter-label">年级分类</div>
  5407. <select class="filter-select" id="webGradeFilter" onchange="filterWebs()">
  5408. <option value="all">所有年级</option>
  5409. <option value="小学">小学</option>
  5410. <option value="初中">初中</option>
  5411. <option value="高中">高中</option>
  5412. </select>
  5413. </div>
  5414. </div>
  5415. <!-- 网页列表 -->
  5416. <div class="web-grid" id="webGrid">
  5417. <!-- 网页卡片将在这里动态生成 -->
  5418. </div>
  5419. <!-- 底部操作栏 -->
  5420. <div class="web-center-footer">
  5421. <div class="selected-count">已选择 <span id="webSelectedCountText">0</span> 个网页</div>
  5422. <div class="app-center-actions">
  5423. <button class="app-center-btn app-center-btn-confirm" id="confirmAddWebsBtn" onclick="confirmAddWebs()" disabled>
  5424. <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
  5425. <polyline points="20 6 9 17 4 12"/>
  5426. </svg>
  5427. 添加
  5428. </button>
  5429. </div>
  5430. </div>
  5431. </div>
  5432. </div>
  5433. <!-- 网页详情浮窗 -->
  5434. <div class="web-detail-modal" id="webDetailModal">
  5435. <div class="web-detail-content">
  5436. <!-- 左侧预览区 -->
  5437. <div class="web-preview-area">
  5438. <div class="web-preview-header">
  5439. <h4 class="web-preview-title" id="webDetailPreviewTitle">网页预览</h4>
  5440. <button class="web-fullscreen-btn" onclick="toggleWebPreviewFullscreen()">
  5441. <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
  5442. <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"/>
  5443. </svg>
  5444. 全屏
  5445. </button>
  5446. </div>
  5447. <div class="web-preview-iframe" id="webDetailPreview">
  5448. <iframe src="" frameborder="0" id="webDetailIframe"></iframe>
  5449. </div>
  5450. </div>
  5451. <!-- 右侧信息区 -->
  5452. <div class="web-info-area">
  5453. <div class="web-detail-header">
  5454. <h3 class="web-detail-name" id="webDetailName">网页名称</h3>
  5455. <div class="web-meta-item">
  5456. <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
  5457. <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"/>
  5458. </svg>
  5459. <span id="webDetailSubject">学科</span>
  5460. </div>
  5461. <div class="web-meta-item">
  5462. <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
  5463. <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"/>
  5464. </svg>
  5465. <span id="webDetailGrade">年级</span>
  5466. </div>
  5467. <div class="web-meta-item">
  5468. <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
  5469. <circle cx="12" cy="12" r="10"/>
  5470. <polyline points="12 6 12 12 16 14"/>
  5471. </svg>
  5472. <span id="webDetailDuration">时长</span>
  5473. </div>
  5474. </div>
  5475. <div class="web-detail-body">
  5476. <div class="web-detail-section">
  5477. <h4 class="web-detail-section-title">简介</h4>
  5478. <p class="web-detail-section-content" id="webDetailDescription">
  5479. 网页简介内容
  5480. </p>
  5481. </div>
  5482. <div class="web-detail-section">
  5483. <h4 class="web-detail-section-title">作者信息</h4>
  5484. <p class="web-detail-section-content" id="webDetailAuthor">
  5485. 作者名称
  5486. </p>
  5487. </div>
  5488. <div class="web-detail-section">
  5489. <h4 class="web-detail-section-title">创建时间</h4>
  5490. <p class="web-detail-section-content" id="webDetailTime">
  5491. 创建时间
  5492. </p>
  5493. </div>
  5494. </div>
  5495. <div class="web-detail-footer">
  5496. <button class="web-detail-btn web-detail-btn-secondary" onclick="closeWebDetail()">返回列表</button>
  5497. <button class="web-detail-btn web-detail-btn-primary" onclick="addWebFromDetail()">添加到课件</button>
  5498. </div>
  5499. </div>
  5500. </div>
  5501. </div>
  5502. <!-- 资源 - 视频来源弹窗 -->
  5503. <div class="floating-modal" id="videoSourceModal">
  5504. <div class="floating-content">
  5505. <div class="floating-header">
  5506. <h3 class="floating-title">选择视频来源</h3>
  5507. <button class="floating-close" onclick="closeVideoSourceModal()">
  5508. <svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
  5509. <line x1="18" y1="6" x2="6" y2="18"/>
  5510. <line x1="6" y1="6" x2="18" y2="18"/>
  5511. </svg>
  5512. </button>
  5513. </div>
  5514. <div class="template-grid" style="grid-template-columns: repeat(2, 1fr);">
  5515. <div class="template-card" onclick="chooseLocalVideo()">
  5516. <div class="template-preview">
  5517. <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
  5518. <path d="M12 5v14"/>
  5519. <polyline points="5 12 12 5 19 12"/>
  5520. </svg>
  5521. </div>
  5522. <div class="template-name">本地上传</div>
  5523. </div>
  5524. <div class="template-card" onclick="chooseBilibiliVideo()">
  5525. <div class="template-preview">
  5526. <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
  5527. <rect x="3" y="4" width="18" height="14" rx="2"/>
  5528. <polyline points="10 9 16 12 10 15 10 9"/>
  5529. </svg>
  5530. </div>
  5531. <div class="template-name">Bilibili 检索</div>
  5532. </div>
  5533. </div>
  5534. </div>
  5535. </div>
  5536. <!-- 资源 - 音频上传弹窗 -->
  5537. <div class="floating-modal" id="audioUploadModal">
  5538. <div class="floating-content">
  5539. <div class="floating-header">
  5540. <h3 class="floating-title">上传音频</h3>
  5541. <button class="floating-close" onclick="closeAudioUploadModal()">
  5542. <svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
  5543. <line x1="18" y1="6" x2="6" y2="18"/>
  5544. <line x1="6" y1="6" x2="18" y2="18"/>
  5545. </svg>
  5546. </button>
  5547. </div>
  5548. <div class="web-config-form">
  5549. <div class="file-upload-area" onclick="document.getElementById('audioFileInput').click()">
  5550. <input type="file" id="audioFileInput" accept="audio/*" style="display: none;" onchange="handleAudioFile(event)">
  5551. <svg width="48" height="48" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5">
  5552. <path d="M21 15v4a2 2 0 01-2 2H5a2 2 0 01-2-2v-4"/>
  5553. <polyline points="17 8 12 3 7 8"/>
  5554. <line x1="12" y1="3" x2="12" y2="15"/>
  5555. </svg>
  5556. <p class="upload-text">点击或拖拽音频到此处上传</p>
  5557. <p class="upload-hint">支持.mp3、.wav等常见音频格式</p>
  5558. </div>
  5559. <div id="audioFileNameDisplay" class="file-name-display"></div>
  5560. <div class="resource-actions">
  5561. <button class="floating-btn floating-btn-secondary" onclick="closeAudioUploadModal()">取消</button>
  5562. <button class="floating-btn floating-btn-primary" id="confirmAudioUploadBtn" onclick="confirmAudioUpload()" disabled>确定</button>
  5563. </div>
  5564. </div>
  5565. </div>
  5566. </div>
  5567. <!-- 资源 - 文档上传弹窗 -->
  5568. <div class="floating-modal" id="documentUploadModal">
  5569. <div class="floating-content">
  5570. <div class="floating-header">
  5571. <h3 class="floating-title">上传文档</h3>
  5572. <button class="floating-close" onclick="closeDocumentUploadModal()">
  5573. <svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
  5574. <line x1="18" y1="6" x2="6" y2="18"/>
  5575. <line x1="6" y1="6" x2="18" y2="18"/>
  5576. </svg>
  5577. </button>
  5578. </div>
  5579. <div class="web-config-form">
  5580. <div class="file-upload-area" onclick="document.getElementById('documentFileInput').click()">
  5581. <input type="file" id="documentFileInput" accept=".docx,.pdf" style="display: none;" onchange="handleDocumentFile(event)">
  5582. <svg width="48" height="48" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5">
  5583. <path d="M21 15v4a2 2 0 01-2 2H5a2 2 0 01-2-2v-4"/>
  5584. <polyline points="17 8 12 3 7 8"/>
  5585. <line x1="12" y1="3" x2="12" y2="15"/>
  5586. </svg>
  5587. <p class="upload-text">点击或拖拽文档到此处上传</p>
  5588. <p class="upload-hint">支持 .docx、.doc、.pdf格式</p>
  5589. </div>
  5590. <div id="documentFileNameDisplay" class="file-name-display"></div>
  5591. <div class="resource-actions">
  5592. <button class="floating-btn floating-btn-secondary" onclick="closeDocumentUploadModal()">取消</button>
  5593. <button class="floating-btn floating-btn-primary" id="confirmDocumentUploadBtn" onclick="confirmDocumentUpload()" disabled>确定</button>
  5594. </div>
  5595. </div>
  5596. </div>
  5597. </div>
  5598. <!-- 资源 - 资源集合弹窗 -->
  5599. <div class="floating-modal" id="collectionModal">
  5600. <div class="floating-content">
  5601. <div class="floating-header">
  5602. <h3 class="floating-title">资源集合</h3>
  5603. <button class="floating-close" onclick="closeCollectionModal()">
  5604. <svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
  5605. <line x1="18" y1="6" x2="6" y2="18"/>
  5606. <line x1="6" y1="6" x2="18" y2="18"/>
  5607. </svg>
  5608. </button>
  5609. </div>
  5610. <div class="web-config-form">
  5611. <div class="file-upload-area" onclick="document.getElementById('collectionFileInput').click()">
  5612. <input type="file" id="collectionFileInput" multiple style="display: none;" onchange="handleCollectionFiles(event)">
  5613. <svg width="48" height="48" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5">
  5614. <path d="M21 15v4a2 2 0 01-2 2H5a2 2 0 01-2-2v-4"/>
  5615. <polyline points="17 8 12 3 7 8"/>
  5616. <line x1="12" y1="3" x2="12" y2="15"/>
  5617. </svg>
  5618. <p class="upload-text">点击或拖拽文件到此处上传</p>
  5619. <p class="upload-hint">支持 .docx、.doc、.pdf格式</p>
  5620. </div>
  5621. <div class="resource-upload-list" id="collectionFileList"></div>
  5622. <div class="resource-tip">最多添加5个文件</div>
  5623. <div class="resource-actions">
  5624. <button class="floating-btn floating-btn-secondary" onclick="closeCollectionModal()">取消</button>
  5625. <button class="floating-btn floating-btn-primary" id="confirmCollectionBtn" onclick="confirmCollection()" disabled>确定</button>
  5626. </div>
  5627. </div>
  5628. </div>
  5629. </div>
  5630. <!-- 创建应用方式选择弹窗 -->
  5631. <div class="modal-overlay" id="createAppMethodModal">
  5632. <div class="create-modal">
  5633. <button class="modal-close" onclick="hideCreateAppMethodModal()">
  5634. <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
  5635. <line x1="18" y1="6" x2="6" y2="18"/>
  5636. <line x1="6" y1="6" x2="18" y2="18"/>
  5637. </svg>
  5638. </button>
  5639. <div class="modal-header">
  5640. <h2>创建应用</h2>
  5641. <p>选择一种方式开始创建您的AI应用</p>
  5642. </div>
  5643. <div class="create-options">
  5644. <div class="create-card featured" onclick="selectCreateMethod('ai')">
  5645. <div class="create-icon">
  5646. <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
  5647. <path d="M12 2L2 7l10 5 10-5-10-5z"/>
  5648. <path d="M2 17l10 5 10-5"/>
  5649. <path d="M2 12l10 5 10-5"/>
  5650. </svg>
  5651. </div>
  5652. <h3>自AI创建</h3>
  5653. <p>通过对话让AI为您生成应用</p>
  5654. </div>
  5655. <div class="create-card" onclick="selectCreateMethod('blank')">
  5656. <div class="create-icon">
  5657. <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
  5658. <rect x="3" y="3" width="18" height="18" rx="2" ry="2"/>
  5659. <line x1="12" y1="8" x2="12" y2="16"/>
  5660. <line x1="8" y1="12" x2="16" y2="12"/>
  5661. </svg>
  5662. </div>
  5663. <h3>自空白创建</h3>
  5664. <p>从空白画布开始手动创建应用</p>
  5665. </div>
  5666. </div>
  5667. </div>
  5668. </div>
  5669. <!-- AI创建应用浮窗 -->
  5670. <div class="floating-modal" id="aiCreateAppModal">
  5671. <div class="ai-create-app-content">
  5672. <div class="floating-header">
  5673. <h3 class="floating-title">AI创建应用</h3>
  5674. <button class="floating-close" onclick="closeAICreateModal()">
  5675. <svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
  5676. <line x1="18" y1="6" x2="6" y2="18"/>
  5677. <line x1="6" y1="6" x2="18" y2="18"/>
  5678. </svg>
  5679. </button>
  5680. </div>
  5681. <div class="ai-create-main">
  5682. <!-- 左侧画布预览 -->
  5683. <div class="app-canvas-preview" id="appCanvasPreview">
  5684. <span>应用画布预览区</span>
  5685. </div>
  5686. <!-- 右侧对话栏 -->
  5687. <div class="ai-chat-panel">
  5688. <div class="ai-chat-messages" id="aiChatMessages">
  5689. <!-- 对话消息将在这里显示 -->
  5690. </div>
  5691. <div class="ai-chat-input-area">
  5692. <div class="ai-chat-input-wrapper">
  5693. <textarea class="ai-chat-input" id="aiChatInput" placeholder="描述您想要的应用功能..." rows="2"></textarea>
  5694. <button class="ai-chat-send-btn" id="aiChatSendBtn" onclick="sendMessageToAI()">
  5695. <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
  5696. <line x1="22" y1="2" x2="11" y2="13"/>
  5697. <polygon points="22 2 15 22 11 13 2 9 22 2"/>
  5698. </svg>
  5699. </button>
  5700. </div>
  5701. </div>
  5702. </div>
  5703. </div>
  5704. <div class="ai-create-footer">
  5705. <button class="app-center-btn app-center-btn-cancel" onclick="closeAICreateModal()">取消</button>
  5706. <button class="app-center-btn app-center-btn-confirm" id="confirmCreateAppBtn" onclick="confirmAICreatedApp()">
  5707. 确认创建
  5708. </button>
  5709. </div>
  5710. </div>
  5711. </div>
  5712. <script>
  5713. let isEditMode = false;
  5714. let elementIdCounter = 0;
  5715. let selectedElement = null;
  5716. let currentTool = null; // 当前选中的工具
  5717. let isToolMode = false; // 是否处于工具编辑模式
  5718. // 工具配置数据
  5719. const toolConfigs = {
  5720. choice: {
  5721. name: '选择',
  5722. icon: '<circle cx="12" cy="12" r="10"/><path d="M12 16v-4m0-4h.01"/>',
  5723. hasTime: true,
  5724. hasViewWork: true,
  5725. hasVoting: false,
  5726. hasShowAnswer: true
  5727. },
  5728. qa: {
  5729. name: '问答',
  5730. icon: '<path d="M21 15a2 2 0 01-2 2H7l-4 4V5a2 2 0 012-2h14a2 2 0 012 2z"/>',
  5731. hasTime: true,
  5732. hasViewWork: true,
  5733. hasVoting: true
  5734. },
  5735. vote: {
  5736. name: '投票',
  5737. 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"/>',
  5738. hasTime: true,
  5739. hasViewWork: true,
  5740. hasVoting: false
  5741. },
  5742. photo: {
  5743. name: '拍照',
  5744. 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"/>',
  5745. hasTime: true,
  5746. hasViewWork: true,
  5747. hasVoting: true
  5748. },
  5749. fillblank: {
  5750. name: '填空',
  5751. 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"/>',
  5752. hasTime: true,
  5753. hasViewWork: true,
  5754. hasVoting: false
  5755. },
  5756. sort: {
  5757. name: '排序',
  5758. 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"/>',
  5759. hasTime: true,
  5760. hasViewWork: true,
  5761. hasVoting: false
  5762. },
  5763. whiteboard: {
  5764. name: '白板',
  5765. 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"/>',
  5766. hasTime: true,
  5767. hasViewWork: true,
  5768. hasVoting: true
  5769. },
  5770. flashcard: {
  5771. name: '抽认卡',
  5772. icon: '<rect x="2" y="4" width="20" height="16" rx="2"/><path d="M7 15h10M12 9v6"/>',
  5773. hasTime: true,
  5774. hasViewWork: false,
  5775. hasVoting: false,
  5776. hasLearningMode: true
  5777. },
  5778. cocopi: {
  5779. name: 'CocoPi',
  5780. icon: '<polyline points="16 18 22 12 16 6"/><polyline points="8 6 2 12 8 18"/>',
  5781. hasTime: true,
  5782. hasViewWork: true,
  5783. hasVoting: true
  5784. },
  5785. workspace: {
  5786. name: '创作空间',
  5787. 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"/>',
  5788. hasTime: true,
  5789. hasViewWork: true,
  5790. hasVoting: true
  5791. }
  5792. };
  5793. // 选择工具
  5794. function selectTool(toolType) {
  5795. currentTool = toolType;
  5796. // 切换到工具编辑模式
  5797. switchToToolMode(toolType);
  5798. // 显示配置面板
  5799. showToolConfig(toolType);
  5800. // 显示工具编辑区域
  5801. showToolEditArea(toolType);
  5802. }
  5803. // 切换到工具模式
  5804. function switchToToolMode(toolType) {
  5805. isToolMode = true;
  5806. // 隐藏普通元素工具栏
  5807. document.getElementById('elementToolbar').classList.remove('visible');
  5808. document.getElementById('elementToolbar').style.display = 'none';
  5809. // 显示底部大纲
  5810. document.getElementById('bottomOutline').classList.add('visible');
  5811. // 隐藏占位符
  5812. const placeholder = document.getElementById('slidePlaceholder');
  5813. if (placeholder) {
  5814. placeholder.style.display = 'none';
  5815. }
  5816. // 如果还没有进入编辑模式,则进入
  5817. if (!isEditMode) {
  5818. isEditMode = true;
  5819. if (document.getElementById('outlineTrack').children.length === 0) {
  5820. generateSamplePages();
  5821. }
  5822. }
  5823. }
  5824. // 显示工具配置面板
  5825. function showToolConfig(toolType) {
  5826. const config = toolConfigs[toolType];
  5827. // 切换视图
  5828. document.getElementById('toolsListView').classList.add('hidden');
  5829. document.getElementById('toolsConfigView').classList.remove('hidden');
  5830. // 更新配置标题
  5831. document.getElementById('currentToolName').textContent = config.name;
  5832. // 生成配置内容
  5833. const configContent = document.getElementById('toolConfigContent');
  5834. let html = '';
  5835. // 查看作业配置
  5836. if (config.hasViewWork) {
  5837. html += `
  5838. <div class="config-section">
  5839. <div class="config-switch" onclick="toggleSwitch('viewWork')">
  5840. <span class="config-switch-label">学生查看结果</span>
  5841. <div class="switch-toggle" id="viewWorkSwitch"></div>
  5842. </div>
  5843. </div>
  5844. `;
  5845. }
  5846. // 提交后显示答案配置(选择题专用)
  5847. if (config.hasShowAnswer) {
  5848. html += `
  5849. <div class="config-switch" onclick="toggleSwitch('showAnswer')">
  5850. <span class="config-switch-label">提交后显示答案</span>
  5851. <div class="switch-toggle" id="showAnswerSwitch"></div>
  5852. </div>
  5853. `;
  5854. }
  5855. // 投票配置
  5856. if (config.hasVoting) {
  5857. html += `
  5858. <div class="config-switch" onclick="toggleSwitch('voting')">
  5859. <span class="config-switch-label">学生点赞</span>
  5860. <div class="switch-toggle" id="votingSwitch"></div>
  5861. </div>
  5862. `;
  5863. }
  5864. // 学习模式配置(抽认卡专用)
  5865. if (config.hasLearningMode) {
  5866. html += `
  5867. <div class="config-section">
  5868. <label class="config-label">学习模式</label>
  5869. <div class="config-mode-list">
  5870. <div class="mode-item active" onclick="toggleModeRadio(this, 'random')">
  5871. <div class="mode-radio">
  5872. <div class="mode-radio-dot"></div>
  5873. </div>
  5874. <span class="mode-label">随机乱序</span>
  5875. </div>
  5876. <div class="mode-item" onclick="toggleModeRadio(this, 'autoread')">
  5877. <div class="mode-radio">
  5878. <div class="mode-radio-dot"></div>
  5879. </div>
  5880. <span class="mode-label">自动朗读</span>
  5881. </div>
  5882. <div class="mode-item" onclick="toggleModeRadio(this, 'flip')">
  5883. <div class="mode-radio">
  5884. <div class="mode-radio-dot"></div>
  5885. </div>
  5886. <span class="mode-label">正反对调</span>
  5887. </div>
  5888. </div>
  5889. </div>
  5890. `;
  5891. }
  5892. configContent.innerHTML = html;
  5893. // 隐藏顶部标题栏
  5894. const panelHeader = document.querySelector('#toolsPanel .panel-header');
  5895. if (panelHeader) {
  5896. panelHeader.style.display = 'none';
  5897. }
  5898. }
  5899. // 显示工具编辑区域
  5900. function showToolEditArea(toolType) {
  5901. const toolEditContainer = document.getElementById('toolEditContainer');
  5902. const slideCanvas = document.getElementById('slideCanvas');
  5903. const config = toolConfigs[toolType];
  5904. // 隐藏slide-canvas,显示工具编辑容器
  5905. if (slideCanvas) {
  5906. slideCanvas.style.display = 'none';
  5907. }
  5908. if (toolEditContainer) {
  5909. toolEditContainer.style.display = 'flex';
  5910. }
  5911. // 创建工具编辑区域HTML
  5912. let html = `
  5913. <div class="tool-header">
  5914. <div class="tool-type-selector" id="toolTypeSelector">
  5915. <button class="tool-type-btn" onclick="toggleToolTypeDropdown()">
  5916. <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
  5917. ${config.icon}
  5918. </svg>
  5919. <span>${config.name}</span>
  5920. <svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
  5921. <polyline points="6 9 12 15 18 9"/>
  5922. </svg>
  5923. </button>
  5924. <div class="tool-type-dropdown">
  5925. ${Object.keys(toolConfigs).map(key => {
  5926. const cfg = toolConfigs[key];
  5927. return `
  5928. <div class="tool-type-item ${key === toolType ? 'active' : ''}" onclick="switchToolType('${key}')">
  5929. <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
  5930. ${cfg.icon}
  5931. </svg>
  5932. <span>${cfg.name}</span>
  5933. </div>
  5934. `;
  5935. }).join('')}
  5936. </div>
  5937. </div>
  5938. </div>
  5939. <div class="tool-edit-area">
  5940. <div class="tool-canvas" id="toolCanvas">
  5941. ${generateToolContent(toolType)}
  5942. </div>
  5943. </div>
  5944. `;
  5945. toolEditContainer.innerHTML = html;
  5946. // 如果是排序工具,初始化拖动功能
  5947. if (toolType === 'sort') {
  5948. setTimeout(initSortDragAndDrop, 100);
  5949. }
  5950. }
  5951. // 生成工具内容
  5952. function generateToolContent(toolType) {
  5953. switch(toolType) {
  5954. case 'choice':
  5955. return generateChoiceContent();
  5956. case 'qa':
  5957. return generateQAContent();
  5958. case 'vote':
  5959. return generateVoteContent();
  5960. case 'photo':
  5961. return generatePhotoContent();
  5962. case 'fillblank':
  5963. return generateFillBlankContent();
  5964. case 'sort':
  5965. return generateSortContent();
  5966. case 'whiteboard':
  5967. return generateWhiteboardContent();
  5968. case 'flashcard':
  5969. return generateFlashcardContent();
  5970. case 'cocopi':
  5971. return generateCocoPiContent();
  5972. case 'workspace':
  5973. return generateWorkspaceContent();
  5974. default:
  5975. return '<div>工具内容加载中...</div>';
  5976. }
  5977. }
  5978. // 生成选择题内容
  5979. function generateChoiceContent() {
  5980. return `
  5981. <div class="question-item">
  5982. <div class="question-header">
  5983. <span class="question-number">题目 1</span>
  5984. <div class="question-actions">
  5985. <button class="question-action-btn" onclick="copyQuestion(this)" title="复制题目">
  5986. <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
  5987. <rect x="9" y="9" width="13" height="13" rx="2" ry="2"/>
  5988. <path d="M5 15H4a2 2 0 01-2-2V4a2 2 0 012-2h9a2 2 0 012 2v1"/>
  5989. </svg>
  5990. </button>
  5991. <button class="question-action-btn delete" onclick="deleteQuestion(this)" title="删除题目">
  5992. <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
  5993. <polyline points="3 6 5 6 21 6"/>
  5994. <path d="M19 6v14a2 2 0 01-2 2H7a2 2 0 01-2-2V6m3 0V4a2 2 0 012-2h4a2 2 0 012 2v2"/>
  5995. </svg>
  5996. </button>
  5997. </div>
  5998. </div>
  5999. <div class="input-with-image">
  6000. <textarea class="question-input" placeholder="输入题目内容...">水在多少摄氏度会结冰?</textarea>
  6001. <button class="upload-image-btn" onclick="uploadImage(this)" title="上传图片">
  6002. <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
  6003. <rect x="3" y="3" width="18" height="18" rx="2" ry="2"/>
  6004. <circle cx="8.5" cy="8.5" r="1.5"/>
  6005. <polyline points="21 15 16 10 5 21"/>
  6006. </svg>
  6007. </button>
  6008. </div>
  6009. <div class="options-list">
  6010. <div class="option-item">
  6011. <div class="option-drag-handle" title="拖动排序">
  6012. <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
  6013. <line x1="3" y1="12" x2="21" y2="12"/>
  6014. <line x1="3" y1="6" x2="21" y2="6"/>
  6015. <line x1="3" y1="18" x2="21" y2="18"/>
  6016. </svg>
  6017. </div>
  6018. <div class="option-checkbox checked" onclick="toggleOption(this)">
  6019. <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
  6020. <polyline points="20 6 9 17 4 12"/>
  6021. </svg>
  6022. </div>
  6023. <input type="text" class="option-input" placeholder="选项A" value="0°C">
  6024. <div class="option-actions">
  6025. <button class="option-action-btn" onclick="addOptionAfter(this)" title="在下方添加选项">
  6026. <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
  6027. <line x1="12" y1="5" x2="12" y2="19"/>
  6028. <line x1="5" y1="12" x2="19" y2="12"/>
  6029. </svg>
  6030. </button>
  6031. <button class="option-action-btn delete" onclick="deleteOption(this)" title="删除选项">
  6032. <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
  6033. <line x1="18" y1="6" x2="6" y2="18"/>
  6034. <line x1="6" y1="6" x2="18" y2="18"/>
  6035. </svg>
  6036. </button>
  6037. </div>
  6038. </div>
  6039. <div class="option-item">
  6040. <div class="option-drag-handle" title="拖动排序">
  6041. <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
  6042. <line x1="3" y1="12" x2="21" y2="12"/>
  6043. <line x1="3" y1="6" x2="21" y2="6"/>
  6044. <line x1="3" y1="18" x2="21" y2="18"/>
  6045. </svg>
  6046. </div>
  6047. <div class="option-checkbox" onclick="toggleOption(this)">
  6048. <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
  6049. <polyline points="20 6 9 17 4 12"/>
  6050. </svg>
  6051. </div>
  6052. <input type="text" class="option-input" placeholder="选项B" value="100°C">
  6053. <div class="option-actions">
  6054. <button class="option-action-btn" onclick="addOptionAfter(this)" title="在下方添加选项">
  6055. <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
  6056. <line x1="12" y1="5" x2="12" y2="19"/>
  6057. <line x1="5" y1="12" x2="19" y2="12"/>
  6058. </svg>
  6059. </button>
  6060. <button class="option-action-btn delete" onclick="deleteOption(this)" title="删除选项">
  6061. <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
  6062. <line x1="18" y1="6" x2="6" y2="18"/>
  6063. <line x1="6" y1="6" x2="18" y2="18"/>
  6064. </svg>
  6065. </button>
  6066. </div>
  6067. </div>
  6068. <div class="option-item">
  6069. <div class="option-drag-handle" title="拖动排序">
  6070. <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
  6071. <line x1="3" y1="12" x2="21" y2="12"/>
  6072. <line x1="3" y1="6" x2="21" y2="6"/>
  6073. <line x1="3" y1="18" x2="21" y2="18"/>
  6074. </svg>
  6075. </div>
  6076. <div class="option-checkbox" onclick="toggleOption(this)">
  6077. <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
  6078. <polyline points="20 6 9 17 4 12"/>
  6079. </svg>
  6080. </div>
  6081. <input type="text" class="option-input" placeholder="选项C" value="50°C">
  6082. <div class="option-actions">
  6083. <button class="option-action-btn" onclick="addOptionAfter(this)" title="在下方添加选项">
  6084. <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
  6085. <line x1="12" y1="5" x2="12" y2="19"/>
  6086. <line x1="5" y1="12" x2="19" y2="12"/>
  6087. </svg>
  6088. </button>
  6089. <button class="option-action-btn delete" onclick="deleteOption(this)" title="删除选项">
  6090. <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
  6091. <line x1="18" y1="6" x2="6" y2="18"/>
  6092. <line x1="6" y1="6" x2="18" y2="18"/>
  6093. </svg>
  6094. </button>
  6095. </div>
  6096. </div>
  6097. <button class="add-option-btn-with-text" onclick="addOption(this)" title="添加选项">
  6098. <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
  6099. <line x1="12" y1="5" x2="12" y2="19"/>
  6100. <line x1="5" y1="12" x2="19" y2="12"/>
  6101. </svg>
  6102. <span>选项</span>
  6103. </button>
  6104. </div>
  6105. <div class="explanation-section">
  6106. <div class="explanation-header">
  6107. <span class="explanation-label">解释说明</span>
  6108. <button class="ai-gen-btn" onclick="generateExplanation()">
  6109. <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
  6110. <path d="M12 2L2 7l10 5 10-5-10-5z"/>
  6111. <path d="M2 17l10 5 10-5"/>
  6112. <path d="M2 12l10 5 10-5"/>
  6113. </svg>
  6114. AI生成
  6115. </button>
  6116. </div>
  6117. <textarea class="explanation-textarea" placeholder="为这道题目添加解释说明..."></textarea>
  6118. </div>
  6119. </div>
  6120. <button class="add-question-btn-with-text" onclick="addQuestion()" title="添加题目">
  6121. <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
  6122. <line x1="12" y1="5" x2="12" y2="19"/>
  6123. <line x1="5" y1="12" x2="19" y2="12"/>
  6124. </svg>
  6125. <span>题目</span>
  6126. </button>
  6127. `;
  6128. }
  6129. // 生成问答内容
  6130. function generateQAContent() {
  6131. return `
  6132. <div class="question-item">
  6133. <div class="question-header">
  6134. <span class="question-number">题目 1</span>
  6135. </div>
  6136. <div class="input-with-image">
  6137. <textarea class="question-input" placeholder="输入题目内容...">请描述水的三态变化过程</textarea>
  6138. <button class="upload-image-btn" onclick="uploadImage(this)" title="上传图片">
  6139. <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
  6140. <rect x="3" y="3" width="18" height="18" rx="2" ry="2"/>
  6141. <circle cx="8.5" cy="8.5" r="1.5"/>
  6142. <polyline points="21 15 16 10 5 21"/>
  6143. </svg>
  6144. </button>
  6145. </div>
  6146. <div class="explanation-section">
  6147. <div class="explanation-header">
  6148. <span class="explanation-label">评价标准</span>
  6149. </div>
  6150. <textarea class="explanation-textarea" placeholder="设置评价标准,如:必须包括/避免..."></textarea>
  6151. </div>
  6152. </div>
  6153. `;
  6154. }
  6155. // 生成投票内容
  6156. function generateVoteContent() {
  6157. return `
  6158. <div class="question-item">
  6159. <div class="question-header">
  6160. <span class="question-number">投票主题</span>
  6161. </div>
  6162. <div class="input-with-image">
  6163. <textarea class="question-input" placeholder="输入投票主题...">你最喜欢哪种状态的水?</textarea>
  6164. <button class="upload-image-btn" onclick="uploadImage(this)" title="上传图片">
  6165. <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
  6166. <rect x="3" y="3" width="18" height="18" rx="2" ry="2"/>
  6167. <circle cx="8.5" cy="8.5" r="1.5"/>
  6168. <polyline points="21 15 16 10 5 21"/>
  6169. </svg>
  6170. </button>
  6171. </div>
  6172. <div class="options-list">
  6173. <div class="option-item">
  6174. <input type="text" class="option-input" placeholder="选项1" value="固态(冰)">
  6175. </div>
  6176. <div class="option-item">
  6177. <input type="text" class="option-input" placeholder="选项2" value="液态(水)">
  6178. </div>
  6179. <div class="option-item">
  6180. <input type="text" class="option-input" placeholder="选项3" value="气态(水蒸气)">
  6181. </div>
  6182. <button class="add-option-btn-with-text" onclick="addOption(this)" title="添加选项">
  6183. <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
  6184. <line x1="12" y1="5" x2="12" y2="19"/>
  6185. <line x1="5" y1="12" x2="19" y2="12"/>
  6186. </svg>
  6187. <span>选项</span>
  6188. </button>
  6189. </div>
  6190. </div>
  6191. `;
  6192. }
  6193. // 生成拍照内容
  6194. function generatePhotoContent() {
  6195. return `
  6196. <div class="question-item">
  6197. <div class="question-header">
  6198. <span class="question-number">拍照指引</span>
  6199. </div>
  6200. <textarea class="question-input" placeholder="输入拍照指引内容...">请拍摄家中水的不同状态</textarea>
  6201. </div>
  6202. `;
  6203. }
  6204. // 生成填空内容
  6205. function generateFillBlankContent() {
  6206. return `
  6207. <div class="question-item">
  6208. <div class="question-header">
  6209. <span class="question-number">题目 1</span>
  6210. </div>
  6211. <div class="question-input-wrapper">
  6212. <textarea class="question-input" placeholder="输入题目内容,使用___表示填空...">水在___°C会结冰,在___°C会沸腾。</textarea>
  6213. <button class="add-blank-btn" onclick="insertBlank(this)" title="插入填空符">
  6214. <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
  6215. <line x1="5" y1="12" x2="19" y2="12"/>
  6216. <line x1="5" y1="12" x2="19" y2="12" stroke-dasharray="2,2"/>
  6217. </svg>
  6218. <span>填空符</span>
  6219. </button>
  6220. </div>
  6221. <div class="explanation-section">
  6222. <div class="explanation-header">
  6223. <span class="explanation-label">参考答案</span>
  6224. </div>
  6225. <textarea class="explanation-textarea" placeholder="输入参考答案...">0, 100</textarea>
  6226. </div>
  6227. </div>
  6228. <button class="add-question-btn-with-text" onclick="addQuestion()" title="添加题目">
  6229. <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
  6230. <line x1="12" y1="5" x2="12" y2="19"/>
  6231. <line x1="5" y1="12" x2="19" y2="12"/>
  6232. </svg>
  6233. <span>题目</span>
  6234. </button>
  6235. `;
  6236. }
  6237. // 生成排序内容
  6238. function generateSortContent() {
  6239. return `
  6240. <div class="question-item">
  6241. <div class="question-header">
  6242. <span class="question-number">题目 1</span>
  6243. </div>
  6244. <div class="input-with-image">
  6245. <textarea class="question-input" placeholder="输入题目内容...">将水的状态变化过程按顺序排列</textarea>
  6246. <button class="upload-image-btn" onclick="uploadImage(this)" title="上传图片">
  6247. <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
  6248. <rect x="3" y="3" width="18" height="18" rx="2" ry="2"/>
  6249. <circle cx="8.5" cy="8.5" r="1.5"/>
  6250. <polyline points="21 15 16 10 5 21"/>
  6251. </svg>
  6252. </button>
  6253. </div>
  6254. <div class="options-list">
  6255. <div class="sort-item-static">
  6256. <div class="sort-number">1</div>
  6257. <input type="text" class="option-input" placeholder="片段1" value="冰融化">
  6258. <div class="option-actions">
  6259. <button class="option-action-btn delete" onclick="deleteSortOption(this)" title="删除片段">
  6260. <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
  6261. <line x1="18" y1="6" x2="6" y2="18"/>
  6262. <line x1="6" y1="6" x2="18" y2="18"/>
  6263. </svg>
  6264. </button>
  6265. </div>
  6266. </div>
  6267. <div class="sort-item-static">
  6268. <div class="sort-number">2</div>
  6269. <input type="text" class="option-input" placeholder="片段2" value="水加热">
  6270. <div class="option-actions">
  6271. <button class="option-action-btn delete" onclick="deleteSortOption(this)" title="删除片段">
  6272. <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
  6273. <line x1="18" y1="6" x2="6" y2="18"/>
  6274. <line x1="6" y1="6" x2="18" y2="18"/>
  6275. </svg>
  6276. </button>
  6277. </div>
  6278. </div>
  6279. <div class="sort-item-static">
  6280. <div class="sort-number">3</div>
  6281. <input type="text" class="option-input" placeholder="片段3" value="水沸腾">
  6282. <div class="option-actions">
  6283. <button class="option-action-btn delete" onclick="deleteSortOption(this)" title="删除片段">
  6284. <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
  6285. <line x1="18" y1="6" x2="6" y2="18"/>
  6286. <line x1="6" y1="6" x2="18" y2="18"/>
  6287. </svg>
  6288. </button>
  6289. </div>
  6290. </div>
  6291. <button class="add-option-btn-with-text" onclick="addSortOption(this)" title="添加片段">
  6292. <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
  6293. <line x1="12" y1="5" x2="12" y2="19"/>
  6294. <line x1="5" y1="12" x2="19" y2="12"/>
  6295. </svg>
  6296. <span>片段</span>
  6297. </button>
  6298. </div>
  6299. <div class="explanation-section">
  6300. <div class="explanation-header">
  6301. <span class="explanation-label">正确排序</span>
  6302. </div>
  6303. <div class="sort-order-display" id="sortOrderDisplay">
  6304. <div class="sort-order-item draggable" draggable="true" data-order="1">
  6305. <div class="sort-drag-handle">
  6306. <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
  6307. <line x1="3" y1="12" x2="21" y2="12"/>
  6308. <line x1="3" y1="6" x2="21" y2="6"/>
  6309. <line x1="3" y1="18" x2="21" y2="18"/>
  6310. </svg>
  6311. </div>
  6312. <span>1</span>
  6313. </div>
  6314. <div class="sort-arrow">→</div>
  6315. <div class="sort-order-item draggable" draggable="true" data-order="2">
  6316. <div class="sort-drag-handle">
  6317. <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
  6318. <line x1="3" y1="12" x2="21" y2="12"/>
  6319. <line x1="3" y1="6" x2="21" y2="6"/>
  6320. <line x1="3" y1="18" x2="21" y2="18"/>
  6321. </svg>
  6322. </div>
  6323. <span>2</span>
  6324. </div>
  6325. <div class="sort-arrow">→</div>
  6326. <div class="sort-order-item draggable" draggable="true" data-order="3">
  6327. <div class="sort-drag-handle">
  6328. <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
  6329. <line x1="3" y1="12" x2="21" y2="12"/>
  6330. <line x1="3" y1="6" x2="21" y2="6"/>
  6331. <line x1="3" y1="18" x2="21" y2="18"/>
  6332. </svg>
  6333. </div>
  6334. <span>3</span>
  6335. </div>
  6336. </div>
  6337. </div>
  6338. </div>
  6339. <button class="add-question-btn-with-text" onclick="addQuestion()" title="添加题目">
  6340. <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
  6341. <line x1="12" y1="5" x2="12" y2="19"/>
  6342. <line x1="5" y1="12" x2="19" y2="12"/>
  6343. </svg>
  6344. <span>题目</span>
  6345. </button>
  6346. `;
  6347. }
  6348. // 生成白板内容
  6349. function generateWhiteboardContent() {
  6350. return `
  6351. <div class="question-item">
  6352. <div class="question-header">
  6353. <span class="question-number">白板主题</span>
  6354. </div>
  6355. <div class="input-with-image">
  6356. <textarea class="question-input" placeholder="输入白板主题...">画出水的三态变化示意图</textarea>
  6357. <button class="upload-image-btn" onclick="uploadImage(this)" title="上传图片">
  6358. <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
  6359. <rect x="3" y="3" width="18" height="18" rx="2" ry="2"/>
  6360. <circle cx="8.5" cy="8.5" r="1.5"/>
  6361. <polyline points="21 15 16 10 5 21"/>
  6362. </svg>
  6363. </button>
  6364. </div>
  6365. </div>
  6366. `;
  6367. }
  6368. // 生成抽认卡内容
  6369. function generateFlashcardContent() {
  6370. return `
  6371. <div class="question-item">
  6372. <div class="flashcard-layout">
  6373. <div class="flashcard-side">
  6374. <div class="flashcard-side-label">正面</div>
  6375. <div class="input-with-image">
  6376. <textarea class="flashcard-content" placeholder="输入正面内容...">水的固态叫什么?</textarea>
  6377. <button class="upload-image-btn" onclick="uploadImage(this)" title="上传图片">
  6378. <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
  6379. <rect x="3" y="3" width="18" height="18" rx="2" ry="2"/>
  6380. <circle cx="8.5" cy="8.5" r="1.5"/>
  6381. <polyline points="21 15 16 10 5 21"/>
  6382. </svg>
  6383. </button>
  6384. </div>
  6385. </div>
  6386. <div class="flashcard-side">
  6387. <div class="flashcard-side-label">背面</div>
  6388. <div class="input-with-image">
  6389. <textarea class="flashcard-content" placeholder="输入背面内容...">冰</textarea>
  6390. <button class="upload-image-btn" onclick="uploadImage(this)" title="上传图片">
  6391. <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
  6392. <rect x="3" y="3" width="18" height="18" rx="2" ry="2"/>
  6393. <circle cx="8.5" cy="8.5" r="1.5"/>
  6394. <polyline points="21 15 16 10 5 21"/>
  6395. </svg>
  6396. </button>
  6397. </div>
  6398. </div>
  6399. </div>
  6400. </div>
  6401. <button class="add-question-btn-with-text" onclick="addFlashcard()" title="添加卡片">
  6402. <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
  6403. <line x1="12" y1="5" x2="12" y2="19"/>
  6404. <line x1="5" y1="12" x2="19" y2="12"/>
  6405. </svg>
  6406. <span>卡片</span>
  6407. </button>
  6408. `;
  6409. }
  6410. // 生成CocoPi内容
  6411. function generateCocoPiContent() {
  6412. return `
  6413. <div class="question-item">
  6414. <div class="question-header">
  6415. <span class="question-number">编程任务</span>
  6416. </div>
  6417. <textarea class="question-input" placeholder="输入编程任务描述...">使用温度传感器监测水的温度变化</textarea>
  6418. <div style="margin-top: 20px; padding: 40px; background: #f3f4f6; border-radius: 12px; text-align: center; color: #6b7280;">
  6419. <svg width="64" height="64" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" style="margin: 0 auto 16px;">
  6420. <polyline points="16 18 22 12 16 6"/>
  6421. <polyline points="8 6 2 12 8 18"/>
  6422. </svg>
  6423. <div>CocoPi编程界面占位</div>
  6424. <div style="font-size: 12px; margin-top: 8px;">点击加载CocoPi编程模块</div>
  6425. </div>
  6426. </div>
  6427. `;
  6428. }
  6429. // 生成创作空间内容
  6430. function generateWorkspaceContent() {
  6431. return `
  6432. <div class="question-item">
  6433. <div class="question-header">
  6434. <span class="question-number">创作任务</span>
  6435. </div>
  6436. <textarea class="question-input" placeholder="输入创作任务描述...">创建一个介绍水循环的智能体</textarea>
  6437. <div style="margin-top: 20px; padding: 40px; background: #f3f4f6; border-radius: 12px; text-align: center; color: #6b7280;">
  6438. <svg width="64" height="64" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" style="margin: 0 auto 16px;">
  6439. <path d="M12 2L2 7l10 5 10-5-10-5z"/>
  6440. <path d="M2 17l10 5 10-5"/>
  6441. <path d="M2 12l10 5 10-5"/>
  6442. </svg>
  6443. <div>CocoFlow创作空间占位</div>
  6444. <div style="font-size: 12px; margin-top: 8px;">点击进入CocoFlow创作界面</div>
  6445. </div>
  6446. </div>
  6447. `;
  6448. }
  6449. // 返回工具列表
  6450. function backToToolsList() {
  6451. document.getElementById('toolsListView').classList.remove('hidden');
  6452. document.getElementById('toolsConfigView').classList.add('hidden');
  6453. // 显示顶部标题栏
  6454. const panelHeader = document.querySelector('#toolsPanel .panel-header');
  6455. if (panelHeader) {
  6456. panelHeader.style.display = 'flex';
  6457. }
  6458. }
  6459. // 恢复正常的slide显示
  6460. function restoreNormalView() {
  6461. const toolEditContainer = document.getElementById('toolEditContainer');
  6462. const slideCanvas = document.getElementById('slideCanvas');
  6463. if (toolEditContainer) {
  6464. toolEditContainer.style.display = 'none';
  6465. }
  6466. if (slideCanvas) {
  6467. slideCanvas.style.display = 'flex';
  6468. }
  6469. isToolMode = false;
  6470. }
  6471. // ========== 资源功能 ==========
  6472. let audioSelectedFile = null;
  6473. let documentSelectedFile = null;
  6474. let collectionFiles = [];
  6475. function openVideoSourceModal() {
  6476. document.getElementById('videoSourceModal').classList.add('active');
  6477. }
  6478. function closeVideoSourceModal() {
  6479. document.getElementById('videoSourceModal').classList.remove('active');
  6480. }
  6481. function chooseLocalVideo() {
  6482. const input = document.createElement('input');
  6483. input.type = 'file';
  6484. input.accept = 'video/*';
  6485. input.onchange = (e) => {
  6486. const file = e.target.files[0];
  6487. if (file) {
  6488. closeVideoSourceModal();
  6489. addResourcePage('视频', file.name || '视频');
  6490. }
  6491. };
  6492. input.click();
  6493. }
  6494. function chooseBilibiliVideo() {
  6495. closeVideoSourceModal();
  6496. alert('已选择来自 Bilibili 的视频占位');
  6497. addResourcePage('视频', 'Bilibili 视频');
  6498. }
  6499. function openAudioUploadModal() {
  6500. document.getElementById('audioFileInput').value = '';
  6501. document.getElementById('audioFileNameDisplay').style.display = 'none';
  6502. document.getElementById('confirmAudioUploadBtn').disabled = true;
  6503. audioSelectedFile = null;
  6504. document.getElementById('audioUploadModal').classList.add('active');
  6505. }
  6506. function handleAudioFile(event) {
  6507. audioSelectedFile = event.target.files[0] || null;
  6508. const display = document.getElementById('audioFileNameDisplay');
  6509. if (audioSelectedFile) {
  6510. display.textContent = audioSelectedFile.name;
  6511. display.style.display = 'block';
  6512. } else {
  6513. display.textContent = '';
  6514. display.style.display = 'none';
  6515. }
  6516. document.getElementById('confirmAudioUploadBtn').disabled = !audioSelectedFile;
  6517. }
  6518. function confirmAudioUpload() {
  6519. if (!audioSelectedFile) return;
  6520. addResourcePage('音频', audioSelectedFile.name || '音频');
  6521. closeAudioUploadModal();
  6522. }
  6523. function closeAudioUploadModal() {
  6524. audioSelectedFile = null;
  6525. document.getElementById('audioUploadModal').classList.remove('active');
  6526. document.getElementById('confirmAudioUploadBtn').disabled = true;
  6527. }
  6528. function openDocumentUploadModal() {
  6529. document.getElementById('documentFileInput').value = '';
  6530. document.getElementById('documentFileNameDisplay').style.display = 'none';
  6531. document.getElementById('confirmDocumentUploadBtn').disabled = true;
  6532. documentSelectedFile = null;
  6533. document.getElementById('documentUploadModal').classList.add('active');
  6534. }
  6535. function handleDocumentFile(event) {
  6536. documentSelectedFile = event.target.files[0] || null;
  6537. const display = document.getElementById('documentFileNameDisplay');
  6538. if (documentSelectedFile) {
  6539. display.textContent = documentSelectedFile.name;
  6540. display.style.display = 'block';
  6541. } else {
  6542. display.textContent = '';
  6543. display.style.display = 'none';
  6544. }
  6545. document.getElementById('confirmDocumentUploadBtn').disabled = !documentSelectedFile;
  6546. }
  6547. function confirmDocumentUpload() {
  6548. if (!documentSelectedFile) return;
  6549. addResourcePage('文档', documentSelectedFile.name || '文档');
  6550. closeDocumentUploadModal();
  6551. }
  6552. function closeDocumentUploadModal() {
  6553. documentSelectedFile = null;
  6554. document.getElementById('documentUploadModal').classList.remove('active');
  6555. document.getElementById('confirmDocumentUploadBtn').disabled = true;
  6556. }
  6557. function openCollectionModal() {
  6558. collectionFiles = [];
  6559. renderCollectionList();
  6560. document.getElementById('collectionModal').classList.add('active');
  6561. }
  6562. function closeCollectionModal() {
  6563. collectionFiles = [];
  6564. renderCollectionList();
  6565. document.getElementById('collectionModal').classList.remove('active');
  6566. }
  6567. function handleCollectionFiles(event) {
  6568. const newFiles = Array.from(event.target.files || []);
  6569. newFiles.forEach(file => {
  6570. if (collectionFiles.length < 5) {
  6571. collectionFiles.push(file);
  6572. }
  6573. });
  6574. event.target.value = '';
  6575. renderCollectionList();
  6576. }
  6577. function renderCollectionList() {
  6578. const list = document.getElementById('collectionFileList');
  6579. if (!list) return;
  6580. list.innerHTML = collectionFiles.map((file, index) => `
  6581. <div class="resource-file-item">
  6582. <div class="resource-file-name">
  6583. <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
  6584. <path d="M14 2H6a2 2 0 00-2 2v16a2 2 0 002 2h12a2 2 0 002-2V8z"/>
  6585. <polyline points="14 2 14 8 20 8"/>
  6586. </svg>
  6587. <span title="${file.name}">${file.name}</span>
  6588. </div>
  6589. <button class="resource-remove-btn" onclick="removeCollectionFile(${index})">
  6590. <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
  6591. <line x1="18" y1="6" x2="6" y2="18"/>
  6592. <line x1="6" y1="6" x2="18" y2="18"/>
  6593. </svg>
  6594. </button>
  6595. </div>
  6596. `).join('');
  6597. document.getElementById('confirmCollectionBtn').disabled = collectionFiles.length === 0;
  6598. }
  6599. function removeCollectionFile(index) {
  6600. collectionFiles.splice(index, 1);
  6601. renderCollectionList();
  6602. }
  6603. function confirmCollection() {
  6604. if (collectionFiles.length === 0) return;
  6605. const filesSnapshot = [...collectionFiles];
  6606. addResourcePage('资源集合', `资源集合 (${collectionFiles.length})`, filesSnapshot);
  6607. closeCollectionModal();
  6608. }
  6609. function addResourcePage(resourceType, label, files = []) {
  6610. if (!isEditMode) {
  6611. switchToEditMode();
  6612. } else {
  6613. document.getElementById('elementToolbar').classList.add('visible');
  6614. document.getElementById('bottomOutline').classList.add('visible');
  6615. const placeholder = document.getElementById('slidePlaceholder');
  6616. if (placeholder) placeholder.style.display = 'none';
  6617. }
  6618. const outlineTrack = document.getElementById('outlineTrack');
  6619. const pageIndex = outlineTrack.querySelectorAll('.outline-item-wrapper').length + 1;
  6620. const wrapper = document.createElement('div');
  6621. wrapper.className = 'outline-item-wrapper';
  6622. const outlineItem = document.createElement('div');
  6623. outlineItem.className = 'outline-item active';
  6624. outlineItem.draggable = true;
  6625. outlineItem.dataset.pageIndex = pageIndex;
  6626. outlineItem.onclick = (e) => selectPage(pageIndex, e);
  6627. outlineItem.innerHTML = `
  6628. <span class="page-number">${pageIndex}</span>
  6629. <span>${label}</span>
  6630. <div class="page-menu" onclick="event.stopPropagation(); togglePageMenu(event, ${pageIndex})">
  6631. <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
  6632. <circle cx="12" cy="12" r="1"/>
  6633. <circle cx="12" cy="5" r="1"/>
  6634. <circle cx="12" cy="19" r="1"/>
  6635. </svg>
  6636. <div class="page-menu-dropdown">
  6637. <div class="page-menu-item" onclick="copyPage(${pageIndex})">
  6638. <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
  6639. <rect x="9" y="9" width="13" height="13" rx="2" ry="2"/>
  6640. <path d="M5 15H4a2 2 0 01-2-2V4a2 2 0 012-2h9a2 2 0 012 2v1"/>
  6641. </svg>
  6642. 复制
  6643. </div>
  6644. <div class="page-menu-item" onclick="addBlankPage(${pageIndex})">
  6645. <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
  6646. <line x1="12" y1="5" x2="12" y2="19"/>
  6647. <line x1="5" y1="12" x2="19" y2="12"/>
  6648. </svg>
  6649. 新增
  6650. </div>
  6651. <div class="page-menu-item danger" onclick="deletePage(${pageIndex})">
  6652. <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
  6653. <polyline points="3 6 5 6 21 6"/>
  6654. <path d="M19 6v14a2 2 0 01-2 2H7a2 2 0 01-2-2V6m3 0V4a2 2 0 012-2h4a2 2 0 012 2v2"/>
  6655. </svg>
  6656. 删除
  6657. </div>
  6658. </div>
  6659. </div>
  6660. `;
  6661. outlineItem.addEventListener('dragstart', handleDragStart);
  6662. outlineItem.addEventListener('dragend', handleDragEnd);
  6663. outlineItem.addEventListener('dragover', handleDragOver);
  6664. outlineItem.addEventListener('drop', handleDrop);
  6665. outlineItem.addEventListener('dragleave', handleDragLeave);
  6666. document.querySelectorAll('.outline-item').forEach(item => item.classList.remove('active'));
  6667. const addBtn = document.createElement('button');
  6668. addBtn.className = 'add-page-between';
  6669. addBtn.textContent = '+';
  6670. addBtn.onclick = () => addPageBetween(pageIndex);
  6671. wrapper.appendChild(outlineItem);
  6672. wrapper.appendChild(addBtn);
  6673. outlineTrack.appendChild(wrapper);
  6674. syncRightOutlineWithBottom();
  6675. renderResourceCanvas(resourceType, label, files);
  6676. alert('已添加资源页面: ' + label);
  6677. }
  6678. function renderResourceCanvas(resourceType, label, files = []) {
  6679. const canvas = document.getElementById('slideCanvas');
  6680. const placeholder = document.getElementById('slidePlaceholder');
  6681. if (placeholder) {
  6682. placeholder.style.display = 'none';
  6683. }
  6684. const elementToolbar = document.getElementById('elementToolbar');
  6685. if (elementToolbar) {
  6686. elementToolbar.classList.remove('visible');
  6687. elementToolbar.style.display = 'none';
  6688. }
  6689. document.getElementById('bottomOutline').classList.add('visible');
  6690. const icons = {
  6691. '视频': '▶️',
  6692. '音频': '🎵',
  6693. '文档': '📄',
  6694. '资源集合': '📚'
  6695. };
  6696. const descMap = {
  6697. '视频': '全屏视频播放器占位',
  6698. '音频': '音频播放条占位',
  6699. '文档': '文档阅读器占位',
  6700. '资源集合': '顶部标签可切换的资源集合占位'
  6701. };
  6702. const showTabs = resourceType === '资源集合';
  6703. const tabItems = showTabs
  6704. ? (files.length > 0 ? files.slice(0, 5).map((f, idx) => f.name || `资源${idx + 1}`) : ['资源1', '资源2', '资源3'])
  6705. : [];
  6706. const tabsHtml = showTabs ? `<div class="resource-tabs">${tabItems.map((name, idx) => `<div class="resource-tab ${idx === 0 ? 'active' : ''}">${name}</div>`).join('')}</div>` : '';
  6707. canvas.innerHTML = `
  6708. <div style="width: 100%; height: 100%; display: flex; flex-direction: column;">
  6709. ${tabsHtml}
  6710. <div style="flex: 1; display: flex; align-items: center; justify-content: center;">
  6711. <div style="width: 80%; background: #fafbfc; border: 2px dashed #e5e7eb; border-radius: 16px; padding: 32px; text-align: center;">
  6712. <div style="font-size: 42px; margin-bottom: 12px;">${icons[resourceType] || '📦'}</div>
  6713. <div style="font-size: 18px; font-weight: 700; color: #111827; margin-bottom: 6px;">${label}</div>
  6714. <div style="font-size: 13px; color: #6b7280;">${descMap[resourceType] || '资源内容占位'}</div>
  6715. </div>
  6716. </div>
  6717. </div>
  6718. `;
  6719. }
  6720. // 切换开关
  6721. function toggleSwitch(switchType) {
  6722. event.stopPropagation();
  6723. const switchEl = document.getElementById(switchType + 'Switch');
  6724. if (switchEl) {
  6725. // 如果是关闭"学生查看结果",需要同时关闭"学生点赞"
  6726. if (switchType === 'viewWork' && switchEl.classList.contains('active')) {
  6727. const votingSwitch = document.getElementById('votingSwitch');
  6728. if (votingSwitch && votingSwitch.classList.contains('active')) {
  6729. votingSwitch.classList.remove('active');
  6730. }
  6731. }
  6732. // 如果是打开"学生点赞",需要同时打开"学生查看结果"
  6733. if (switchType === 'voting' && !switchEl.classList.contains('active')) {
  6734. const viewWorkSwitch = document.getElementById('viewWorkSwitch');
  6735. if (viewWorkSwitch && !viewWorkSwitch.classList.contains('active')) {
  6736. viewWorkSwitch.classList.add('active');
  6737. }
  6738. }
  6739. switchEl.classList.toggle('active');
  6740. }
  6741. }
  6742. // 切换模式(多选)
  6743. function toggleMode(element, mode) {
  6744. element.classList.toggle('active');
  6745. }
  6746. // 切换模式(单选)
  6747. function toggleModeRadio(element, mode) {
  6748. const parent = element.parentElement;
  6749. parent.querySelectorAll('.mode-item').forEach(item => {
  6750. item.classList.remove('active');
  6751. });
  6752. element.classList.add('active');
  6753. }
  6754. // 插入填空符
  6755. function insertBlank(button) {
  6756. const wrapper = button.closest('.question-input-wrapper');
  6757. const textarea = wrapper.querySelector('.question-input');
  6758. const cursorPos = textarea.selectionStart;
  6759. const textBefore = textarea.value.substring(0, cursorPos);
  6760. const textAfter = textarea.value.substring(cursorPos);
  6761. textarea.value = textBefore + '___' + textAfter;
  6762. textarea.focus();
  6763. textarea.setSelectionRange(cursorPos + 3, cursorPos + 3);
  6764. }
  6765. // 添加排序选项
  6766. function addSortOption(button) {
  6767. const optionsList = button.parentElement;
  6768. const currentItems = optionsList.querySelectorAll('.sort-item-static');
  6769. const nextNumber = currentItems.length + 1;
  6770. const sortItem = document.createElement('div');
  6771. sortItem.className = 'sort-item-static';
  6772. sortItem.innerHTML = `
  6773. <div class="sort-number">${nextNumber}</div>
  6774. <input type="text" class="option-input" placeholder="片段${nextNumber}">
  6775. <div class="option-actions">
  6776. <button class="option-action-btn delete" onclick="deleteSortOption(this)" title="删除片段">
  6777. <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
  6778. <line x1="18" y1="6" x2="6" y2="18"/>
  6779. <line x1="6" y1="6" x2="18" y2="18"/>
  6780. </svg>
  6781. </button>
  6782. </div>
  6783. `;
  6784. optionsList.insertBefore(sortItem, button);
  6785. // 更新正确排序显示
  6786. updateSortOrderDisplay();
  6787. }
  6788. // 删除排序选项
  6789. function deleteSortOption(button) {
  6790. const sortItem = button.closest('.sort-item-static');
  6791. const optionsList = sortItem.parentElement;
  6792. const allItems = optionsList.querySelectorAll('.sort-item-static');
  6793. // 至少保留2个选项
  6794. if (allItems.length <= 2) {
  6795. alert('至少需要保留两个片段');
  6796. return;
  6797. }
  6798. sortItem.remove();
  6799. // 重新编号
  6800. const remainingItems = optionsList.querySelectorAll('.sort-item-static');
  6801. remainingItems.forEach((item, index) => {
  6802. const numberEl = item.querySelector('.sort-number');
  6803. const inputEl = item.querySelector('.option-input');
  6804. numberEl.textContent = index + 1;
  6805. if (inputEl.placeholder) {
  6806. inputEl.placeholder = `片段${index + 1}`;
  6807. }
  6808. });
  6809. // 更新正确排序显示
  6810. updateSortOrderDisplay();
  6811. }
  6812. // 更新排序顺序显示
  6813. function updateSortOrderDisplay() {
  6814. const sortItems = document.querySelectorAll('.sort-item-static');
  6815. const display = document.querySelector('.sort-order-display');
  6816. if (!display) return;
  6817. let html = '';
  6818. sortItems.forEach((item, index) => {
  6819. const number = item.querySelector('.sort-number').textContent;
  6820. html += `
  6821. <div class="sort-order-item draggable" draggable="true" data-order="${number}">
  6822. <div class="sort-drag-handle">
  6823. <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
  6824. <line x1="3" y1="12" x2="21" y2="12"/>
  6825. <line x1="3" y1="6" x2="21" y2="6"/>
  6826. <line x1="3" y1="18" x2="21" y2="18"/>
  6827. </svg>
  6828. </div>
  6829. <span>${number}</span>
  6830. </div>
  6831. `;
  6832. if (index < sortItems.length - 1) {
  6833. html += '<div class="sort-arrow">→</div>';
  6834. }
  6835. });
  6836. display.innerHTML = html;
  6837. // 重新初始化拖动事件
  6838. initSortDragAndDrop();
  6839. }
  6840. // 初始化排序拖动功能
  6841. function initSortDragAndDrop() {
  6842. const draggables = document.querySelectorAll('.sort-order-item.draggable');
  6843. const container = document.querySelector('.sort-order-display');
  6844. if (!container) return;
  6845. let draggedElement = null;
  6846. draggables.forEach(item => {
  6847. item.addEventListener('dragstart', function(e) {
  6848. draggedElement = this;
  6849. this.classList.add('dragging');
  6850. e.dataTransfer.effectAllowed = 'move';
  6851. });
  6852. item.addEventListener('dragend', function(e) {
  6853. this.classList.remove('dragging');
  6854. draggables.forEach(el => el.classList.remove('drag-over'));
  6855. });
  6856. item.addEventListener('dragover', function(e) {
  6857. e.preventDefault();
  6858. e.dataTransfer.dropEffect = 'move';
  6859. if (this !== draggedElement) {
  6860. this.classList.add('drag-over');
  6861. }
  6862. });
  6863. item.addEventListener('dragleave', function(e) {
  6864. this.classList.remove('drag-over');
  6865. });
  6866. item.addEventListener('drop', function(e) {
  6867. e.preventDefault();
  6868. this.classList.remove('drag-over');
  6869. if (this !== draggedElement) {
  6870. // 交换元素
  6871. const allItems = Array.from(container.querySelectorAll('.sort-order-item.draggable'));
  6872. const draggedIndex = allItems.indexOf(draggedElement);
  6873. const targetIndex = allItems.indexOf(this);
  6874. if (draggedIndex < targetIndex) {
  6875. this.parentNode.insertBefore(draggedElement, this.nextSibling);
  6876. } else {
  6877. this.parentNode.insertBefore(draggedElement, this);
  6878. }
  6879. // 重建箭头
  6880. rebuildSortArrows();
  6881. }
  6882. });
  6883. });
  6884. }
  6885. // 重建排序箭头
  6886. function rebuildSortArrows() {
  6887. const container = document.querySelector('.sort-order-display');
  6888. if (!container) return;
  6889. const items = Array.from(container.querySelectorAll('.sort-order-item.draggable'));
  6890. const arrows = Array.from(container.querySelectorAll('.sort-arrow'));
  6891. // 移除所有箭头
  6892. arrows.forEach(arrow => arrow.remove());
  6893. // 重新插入箭头
  6894. items.forEach((item, index) => {
  6895. if (index < items.length - 1) {
  6896. const arrow = document.createElement('div');
  6897. arrow.className = 'sort-arrow';
  6898. arrow.textContent = '→';
  6899. item.parentNode.insertBefore(arrow, item.nextSibling);
  6900. }
  6901. });
  6902. }
  6903. // 页面加载后初始化拖动
  6904. document.addEventListener('DOMContentLoaded', function() {
  6905. // 延迟初始化,等待工具内容加载
  6906. setTimeout(initSortDragAndDrop, 500);
  6907. });
  6908. // 图片上传函数
  6909. function uploadImage(button) {
  6910. const input = document.createElement('input');
  6911. input.type = 'file';
  6912. input.accept = 'image/*';
  6913. input.onchange = (e) => {
  6914. const file = e.target.files[0];
  6915. if (file) {
  6916. // 创建图片预览
  6917. const reader = new FileReader();
  6918. reader.onload = function(event) {
  6919. const wrapper = button.closest('.input-with-image');
  6920. const textarea = wrapper.querySelector('textarea, .option-input');
  6921. // 在textarea下方插入图片预览
  6922. let preview = wrapper.querySelector('.image-preview');
  6923. if (!preview) {
  6924. preview = document.createElement('div');
  6925. preview.className = 'image-preview';
  6926. textarea.parentNode.insertBefore(preview, button);
  6927. }
  6928. preview.innerHTML = `
  6929. <img src="${event.target.result}" alt="预览图片">
  6930. <button class="remove-image-btn" onclick="removeImage(this)" title="删除图片">
  6931. <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
  6932. <line x1="18" y1="6" x2="6" y2="18"/>
  6933. <line x1="6" y1="6" x2="18" y2="18"/>
  6934. </svg>
  6935. </button>
  6936. `;
  6937. };
  6938. reader.readAsDataURL(file);
  6939. }
  6940. };
  6941. input.click();
  6942. }
  6943. // 删除图片
  6944. function removeImage(button) {
  6945. const preview = button.closest('.image-preview');
  6946. if (preview) {
  6947. preview.remove();
  6948. }
  6949. }
  6950. // 切换工具类型下拉菜单
  6951. function toggleToolTypeDropdown() {
  6952. event.stopPropagation();
  6953. document.getElementById('toolTypeSelector').classList.toggle('active');
  6954. }
  6955. // 切换工具类型
  6956. function switchToolType(toolType) {
  6957. currentTool = toolType;
  6958. showToolConfig(toolType);
  6959. showToolEditArea(toolType);
  6960. document.getElementById('toolTypeSelector').classList.remove('active');
  6961. }
  6962. // 切换题目类型(单选/多选)
  6963. function toggleQuestionType(toggle) {
  6964. toggle.classList.toggle('active');
  6965. }
  6966. // 切换选项选中状态
  6967. function toggleOption(checkbox) {
  6968. checkbox.classList.toggle('checked');
  6969. }
  6970. // 添加选项
  6971. function addOption(button) {
  6972. const optionsList = button.parentElement;
  6973. const optionItem = document.createElement('div');
  6974. optionItem.className = 'option-item';
  6975. optionItem.innerHTML = `
  6976. <div class="option-drag-handle" title="拖动排序">
  6977. <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
  6978. <line x1="3" y1="12" x2="21" y2="12"/>
  6979. <line x1="3" y1="6" x2="21" y2="6"/>
  6980. <line x1="3" y1="18" x2="21" y2="18"/>
  6981. </svg>
  6982. </div>
  6983. <div class="option-checkbox" onclick="toggleOption(this)">
  6984. <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
  6985. <polyline points="20 6 9 17 4 12"/>
  6986. </svg>
  6987. </div>
  6988. <input type="text" class="option-input" placeholder="新选项">
  6989. <div class="option-actions">
  6990. <button class="option-action-btn" onclick="addOptionAfter(this)" title="在下方添加选项">
  6991. <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
  6992. <line x1="12" y1="5" x2="12" y2="19"/>
  6993. <line x1="5" y1="12" x2="19" y2="12"/>
  6994. </svg>
  6995. </button>
  6996. <button class="option-action-btn delete" onclick="deleteOption(this)" title="删除选项">
  6997. <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
  6998. <line x1="18" y1="6" x2="6" y2="18"/>
  6999. <line x1="6" y1="6" x2="18" y2="18"/>
  7000. </svg>
  7001. </button>
  7002. </div>
  7003. `;
  7004. optionsList.insertBefore(optionItem, button);
  7005. }
  7006. // 在指定选项后添加新选项
  7007. function addOptionAfter(button) {
  7008. const currentOption = button.closest('.option-item');
  7009. const optionsList = currentOption.parentElement;
  7010. const newOption = document.createElement('div');
  7011. newOption.className = 'option-item';
  7012. newOption.innerHTML = `
  7013. <div class="option-drag-handle" title="拖动排序">
  7014. <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
  7015. <line x1="3" y1="12" x2="21" y2="12"/>
  7016. <line x1="3" y1="6" x2="21" y2="6"/>
  7017. <line x1="3" y1="18" x2="21" y2="18"/>
  7018. </svg>
  7019. </div>
  7020. <div class="option-checkbox" onclick="toggleOption(this)">
  7021. <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
  7022. <polyline points="20 6 9 17 4 12"/>
  7023. </svg>
  7024. </div>
  7025. <input type="text" class="option-input" placeholder="新选项">
  7026. <div class="option-actions">
  7027. <button class="option-action-btn" onclick="addOptionAfter(this)" title="在下方添加选项">
  7028. <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
  7029. <line x1="12" y1="5" x2="12" y2="19"/>
  7030. <line x1="5" y1="12" x2="19" y2="12"/>
  7031. </svg>
  7032. </button>
  7033. <button class="option-action-btn delete" onclick="deleteOption(this)" title="删除选项">
  7034. <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
  7035. <line x1="18" y1="6" x2="6" y2="18"/>
  7036. <line x1="6" y1="6" x2="18" y2="18"/>
  7037. </svg>
  7038. </button>
  7039. </div>
  7040. `;
  7041. currentOption.after(newOption);
  7042. newOption.querySelector('.option-input').focus();
  7043. }
  7044. // 删除选项
  7045. function deleteOption(button) {
  7046. const optionItem = button.closest('.option-item');
  7047. const optionsList = optionItem.parentElement;
  7048. const allOptions = optionsList.querySelectorAll('.option-item');
  7049. // 至少保留2个选项
  7050. if (allOptions.length <= 2) {
  7051. alert('至少需要保留两个选项');
  7052. return;
  7053. }
  7054. optionItem.remove();
  7055. }
  7056. // 复制题目
  7057. function copyQuestion(button) {
  7058. const questionItem = button.closest('.question-item');
  7059. const clone = questionItem.cloneNode(true);
  7060. const questionNumber = clone.querySelector('.question-number');
  7061. const currentNum = parseInt(questionNumber.textContent.match(/\d+/)[0]);
  7062. questionNumber.textContent = `题目 ${currentNum + 1}`;
  7063. questionItem.after(clone);
  7064. alert('题目已复制');
  7065. }
  7066. // 删除题目
  7067. function deleteQuestion(button) {
  7068. const questionItem = button.closest('.question-item');
  7069. const allQuestions = document.querySelectorAll('.question-item');
  7070. // 至少保留1个题目
  7071. if (allQuestions.length <= 1) {
  7072. alert('至少需要保留一个题目');
  7073. return;
  7074. }
  7075. if (confirm('确定要删除这道题目吗?')) {
  7076. questionItem.remove();
  7077. }
  7078. }
  7079. // 添加题目
  7080. function addQuestion() {
  7081. alert('添加新题目');
  7082. }
  7083. // 添加抽认卡
  7084. function addFlashcard() {
  7085. alert('添加新抽认卡');
  7086. }
  7087. // AI生成解释
  7088. function generateExplanation() {
  7089. alert('AI正在生成解释...');
  7090. }
  7091. // 点击页面其他地方关闭下拉菜单
  7092. document.addEventListener('click', function() {
  7093. const selector = document.getElementById('toolTypeSelector');
  7094. if (selector) {
  7095. selector.classList.remove('active');
  7096. }
  7097. });
  7098. // 显示创建弹窗
  7099. function showCreateModal() {
  7100. document.getElementById('createModal').classList.add('active');
  7101. }
  7102. // 隐藏创建弹窗
  7103. function hideCreateModal() {
  7104. document.getElementById('createModal').classList.remove('active');
  7105. }
  7106. // 取消AI卡片高亮
  7107. function unsetAiFeatured() {
  7108. const aiCard = document.getElementById('aiCreateCard');
  7109. if (aiCard) aiCard.classList.remove('featured');
  7110. }
  7111. function createBlankCourse() {
  7112. hideCreateModal();
  7113. addPageFromTemplate('content');
  7114. }
  7115. // 点击遮罩关闭弹窗
  7116. document.getElementById('createModal').addEventListener('click', function(e) {
  7117. if (e.target === this) {
  7118. hideCreateModal();
  7119. }
  7120. });
  7121. // ========== PPT上传与解析(模拟) ==========
  7122. let currentPptFile = null;
  7123. let pptParseTimer = null;
  7124. // ========== 右侧大纲(分组 + 拖动) ==========
  7125. let outlineData = { groups: [], ungrouped: [] };
  7126. let draggingItem = null;
  7127. function initRightOutline() {
  7128. syncRightOutlineWithBottom();
  7129. }
  7130. function snapshotBottomOutline() {
  7131. const track = document.getElementById('outlineTrack');
  7132. if (!track) return [];
  7133. const items = Array.from(track.querySelectorAll('.outline-item-wrapper'));
  7134. return items.map((item, idx) => {
  7135. const titleSpan = item.querySelector('.outline-item span:nth-child(2)');
  7136. const title = titleSpan ? titleSpan.textContent.trim() : `页面 ${idx + 1}`;
  7137. const pageNumber = item.querySelector('.page-number');
  7138. const id = pageNumber ? pageNumber.textContent.trim() : `${idx + 1}`;
  7139. return { id, title, type: '页面' };
  7140. });
  7141. }
  7142. function syncRightOutlineWithBottom() {
  7143. const bottomPages = snapshotBottomOutline();
  7144. const orderMap = new Map(bottomPages.map((p, idx) => [p.id, idx]));
  7145. const existsInBottom = (id) => orderMap.has(id);
  7146. // 移除已被删除的页面
  7147. outlineData.groups.forEach(group => {
  7148. group.items = group.items.filter(item => existsInBottom(item.id));
  7149. });
  7150. outlineData.ungrouped = outlineData.ungrouped.filter(item => existsInBottom(item.id));
  7151. // 添加新增页面到未分组
  7152. bottomPages.forEach(page => {
  7153. const inGroup = outlineData.groups.some(g => g.items.some(it => it.id === page.id));
  7154. const inUngrouped = outlineData.ungrouped.some(it => it.id === page.id);
  7155. if (!inGroup && !inUngrouped) {
  7156. outlineData.ungrouped.push(page);
  7157. }
  7158. });
  7159. // 未分组跟随底部顺序
  7160. outlineData.ungrouped.sort((a, b) => (orderMap.get(a.id) ?? 0) - (orderMap.get(b.id) ?? 0));
  7161. renderRightOutline();
  7162. }
  7163. function refreshRightOutline() {
  7164. syncRightOutlineWithBottom();
  7165. }
  7166. function addOutlineGroup() {
  7167. const group = {
  7168. id: `group_${Date.now()}`,
  7169. name: '新建分组',
  7170. collapsed: false,
  7171. items: []
  7172. };
  7173. outlineData.groups.push(group);
  7174. renderRightOutline();
  7175. }
  7176. function deleteGroup(groupId) {
  7177. const group = outlineData.groups.find(g => g.id === groupId);
  7178. if (!group) return;
  7179. // 将子项平铺到未分组,保持顺序
  7180. outlineData.ungrouped = [...outlineData.ungrouped, ...group.items];
  7181. outlineData.groups = outlineData.groups.filter(g => g.id !== groupId);
  7182. renderRightOutline();
  7183. }
  7184. function confirmDeleteGroup(groupId) {
  7185. const group = outlineData.groups.find(g => g.id === groupId);
  7186. if (!group) return;
  7187. const ok = confirm('确定删除该分组?其中的页面将移动到未分组');
  7188. if (ok) {
  7189. deleteGroup(groupId);
  7190. }
  7191. }
  7192. function toggleGroupCollapse(groupId) {
  7193. const group = outlineData.groups.find(g => g.id === groupId);
  7194. if (group) {
  7195. group.collapsed = !group.collapsed;
  7196. renderRightOutline();
  7197. }
  7198. }
  7199. function renameGroup(groupId, titleSpan) {
  7200. const group = outlineData.groups.find(g => g.id === groupId);
  7201. if (!group || !titleSpan || titleSpan.dataset.editing === '1') return;
  7202. const input = document.createElement('input');
  7203. input.className = 'outline-group-input';
  7204. input.value = group.name;
  7205. const width = Math.min(220, Math.max(120, titleSpan.offsetWidth + 20));
  7206. input.style.width = width + 'px';
  7207. titleSpan.dataset.editing = '1';
  7208. titleSpan.replaceWith(input);
  7209. input.focus();
  7210. input.select();
  7211. let finished = false;
  7212. const finish = (commit) => {
  7213. if (finished) return;
  7214. finished = true;
  7215. if (commit) {
  7216. const name = input.value.trim();
  7217. if (name) group.name = name;
  7218. }
  7219. const newSpan = document.createElement('span');
  7220. newSpan.className = 'outline-group-title';
  7221. newSpan.textContent = group.name;
  7222. newSpan.onclick = (e) => { e.stopPropagation(); renameGroup(groupId, newSpan); };
  7223. input.replaceWith(newSpan);
  7224. };
  7225. input.addEventListener('blur', () => finish(true));
  7226. input.addEventListener('keydown', (e) => {
  7227. if (e.key === 'Enter') {
  7228. e.preventDefault();
  7229. input.blur();
  7230. } else if (e.key === 'Escape') {
  7231. e.preventDefault();
  7232. finish(false);
  7233. }
  7234. });
  7235. }
  7236. function renderRightOutline() {
  7237. const container = document.getElementById('rightOutlineList');
  7238. if (!container) return;
  7239. container.innerHTML = '';
  7240. const hasContent = outlineData.groups.length > 0 || outlineData.ungrouped.length > 0;
  7241. if (!hasContent) {
  7242. container.innerHTML = '<div class="outline-empty">暂无页面,创建后将出现在此</div>';
  7243. return;
  7244. }
  7245. // 先渲染分组
  7246. outlineData.groups.forEach(group => {
  7247. const groupEl = document.createElement('div');
  7248. groupEl.className = 'outline-group';
  7249. groupEl.dataset.id = group.id;
  7250. const header = document.createElement('div');
  7251. header.className = 'outline-group-header';
  7252. header.draggable = false;
  7253. const left = document.createElement('div');
  7254. left.className = 'outline-group-left';
  7255. const arrow = document.createElement('span');
  7256. arrow.innerHTML = `
  7257. <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
  7258. <polyline points="6 9 12 15 18 9" style="transform: ${group.collapsed ? 'rotate(-90deg)' : 'none'}; transform-origin: 12px 12px;"/>
  7259. </svg>`;
  7260. arrow.style.cursor = 'pointer';
  7261. arrow.onclick = (e) => { e.stopPropagation(); toggleGroupCollapse(group.id); };
  7262. const titleSpan = document.createElement('span');
  7263. titleSpan.className = 'outline-group-title';
  7264. titleSpan.textContent = group.name;
  7265. titleSpan.onclick = (e) => { e.stopPropagation(); renameGroup(group.id, titleSpan); };
  7266. left.appendChild(arrow);
  7267. left.appendChild(titleSpan);
  7268. header.appendChild(left);
  7269. const dragHandle = document.createElement('div');
  7270. dragHandle.className = 'outline-drag-handle';
  7271. dragHandle.innerHTML = `
  7272. <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
  7273. <circle cx="5" cy="8" r="1"/>
  7274. <circle cx="5" cy="16" r="1"/>
  7275. <circle cx="12" cy="8" r="1"/>
  7276. <circle cx="12" cy="16" r="1"/>
  7277. <circle cx="19" cy="8" r="1"/>
  7278. <circle cx="19" cy="16" r="1"/>
  7279. </svg>`;
  7280. dragHandle.draggable = true;
  7281. dragHandle.addEventListener('dragstart', (e) => onGroupDragStart(e, group.id));
  7282. dragHandle.addEventListener('dragover', onDragOver);
  7283. dragHandle.addEventListener('drop', (e) => onDrop(e, { targetGroup: group.id }));
  7284. header.appendChild(dragHandle);
  7285. const deleteBtn = document.createElement('div');
  7286. deleteBtn.className = 'outline-delete-btn';
  7287. deleteBtn.innerHTML = `
  7288. <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
  7289. <polyline points="3 6 5 6 21 6"/>
  7290. <path d="M19 6v14a2 2 0 01-2 2H7a2 2 0 01-2-2V6m3 0V4a2 2 0 012-2h4a2 2 0 012 2v2"/>
  7291. </svg>`;
  7292. deleteBtn.onclick = (e) => { e.stopPropagation(); confirmDeleteGroup(group.id); };
  7293. header.appendChild(deleteBtn);
  7294. groupEl.appendChild(header);
  7295. const dropAreaTarget = { targetGroup: group.id };
  7296. header.addEventListener('dragover', onDragOver);
  7297. header.addEventListener('drop', (e) => onDrop(e, dropAreaTarget));
  7298. if (!group.collapsed) {
  7299. const itemsBox = document.createElement('div');
  7300. itemsBox.className = 'outline-items';
  7301. itemsBox.addEventListener('dragover', onDragOver);
  7302. itemsBox.addEventListener('drop', (e) => onDrop(e, dropAreaTarget));
  7303. if (group.items.length === 0) {
  7304. const empty = document.createElement('div');
  7305. empty.className = 'outline-empty';
  7306. empty.textContent = '拖动页面到此';
  7307. itemsBox.appendChild(empty);
  7308. } else {
  7309. group.items.forEach((item, idx) => {
  7310. itemsBox.appendChild(createOutlineItem(item, idx, group.id));
  7311. });
  7312. }
  7313. groupEl.appendChild(itemsBox);
  7314. }
  7315. container.appendChild(groupEl);
  7316. });
  7317. // 渲染未分组
  7318. outlineData.ungrouped.forEach((item, idx) => {
  7319. container.appendChild(createOutlineItem(item, idx, null));
  7320. });
  7321. }
  7322. function createOutlineItem(item, idx, groupId) {
  7323. const el = document.createElement('div');
  7324. el.className = 'outline-item';
  7325. el.draggable = true;
  7326. el.dataset.id = item.id;
  7327. el.dataset.group = groupId || '';
  7328. el.addEventListener('dragstart', (e) => onItemDragStart(e, item, groupId));
  7329. el.addEventListener('dragover', onDragOver);
  7330. el.addEventListener('drop', (e) => onDrop(e, { targetItem: item, groupId }));
  7331. el.onclick = () => selectPageById(item.id);
  7332. el.innerHTML = `
  7333. <div class="outline-item-left">
  7334. <span class="outline-index">${idx + 1}</span>
  7335. <span class="outline-title" title="${item.title}">${item.title}</span>
  7336. <span class="outline-type">${item.type || '页面'}</span>
  7337. </div>
  7338. <span class="outline-handle">
  7339. <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
  7340. <circle cx="5" cy="12" r="1"/>
  7341. <circle cx="12" cy="12" r="1"/>
  7342. <circle cx="19" cy="12" r="1"/>
  7343. </svg>
  7344. </span>
  7345. `;
  7346. return el;
  7347. }
  7348. function onItemDragStart(e, item, groupId) {
  7349. draggingItem = { type: 'item', item, groupId };
  7350. e.dataTransfer.effectAllowed = 'move';
  7351. }
  7352. function onGroupDragStart(e, groupId) {
  7353. draggingItem = { type: 'group', groupId };
  7354. e.dataTransfer.effectAllowed = 'move';
  7355. }
  7356. function onDragOver(e) {
  7357. e.preventDefault();
  7358. e.dataTransfer.dropEffect = 'move';
  7359. }
  7360. function onDrop(e, target) {
  7361. e.preventDefault();
  7362. if (!draggingItem) return;
  7363. if (draggingItem.type === 'group' && target.targetGroup) {
  7364. reorderGroups(draggingItem.groupId, target.targetGroup);
  7365. } else if (draggingItem.type === 'item') {
  7366. moveItem(draggingItem.item, draggingItem.groupId, target);
  7367. }
  7368. draggingItem = null;
  7369. renderRightOutline();
  7370. syncBottomOutlineOrder();
  7371. }
  7372. function reorderGroups(sourceId, targetId) {
  7373. if (sourceId === targetId) return;
  7374. const list = outlineData.groups;
  7375. const fromIdx = list.findIndex(g => g.id === sourceId);
  7376. const toIdx = list.findIndex(g => g.id === targetId);
  7377. if (fromIdx === -1 || toIdx === -1) return;
  7378. const [g] = list.splice(fromIdx, 1);
  7379. list.splice(toIdx, 0, g);
  7380. }
  7381. function moveItem(item, fromGroupId, target) {
  7382. removeItemFromData(item.id);
  7383. if (target.targetItem) {
  7384. const targetGroup = target.groupId || null;
  7385. insertItemBefore(item, target.targetItem.id, targetGroup);
  7386. } else if (target.targetGroup) {
  7387. const group = outlineData.groups.find(g => g.id === target.targetGroup);
  7388. if (group) group.items.push(item);
  7389. } else {
  7390. outlineData.ungrouped.push(item);
  7391. }
  7392. }
  7393. function removeItemFromData(itemId) {
  7394. outlineData.groups.forEach(g => {
  7395. g.items = g.items.filter(it => it.id !== itemId);
  7396. });
  7397. outlineData.ungrouped = outlineData.ungrouped.filter(it => it.id !== itemId);
  7398. }
  7399. function insertItemBefore(item, targetId, groupId) {
  7400. if (groupId) {
  7401. const group = outlineData.groups.find(g => g.id === groupId);
  7402. if (!group) return;
  7403. const idx = group.items.findIndex(it => it.id === targetId);
  7404. if (idx === -1) group.items.push(item); else group.items.splice(idx, 0, item);
  7405. } else {
  7406. const idx = outlineData.ungrouped.findIndex(it => it.id === targetId);
  7407. if (idx === -1) outlineData.ungrouped.push(item); else outlineData.ungrouped.splice(idx, 0, item);
  7408. }
  7409. }
  7410. function selectPageById(id) {
  7411. // 通过底部大纲的序号匹配
  7412. const track = document.getElementById('outlineTrack');
  7413. if (!track) return;
  7414. const item = Array.from(track.querySelectorAll('.outline-item-wrapper')).find(w => {
  7415. const num = w.querySelector('.page-number');
  7416. return num && num.textContent.trim() === id;
  7417. });
  7418. if (item) {
  7419. const index = Array.from(track.children).indexOf(item) + 1;
  7420. selectPage(index, { stopPropagation: () => {} });
  7421. }
  7422. }
  7423. function syncBottomOutlineOrder() {
  7424. const track = document.getElementById('outlineTrack');
  7425. if (!track) return;
  7426. const order = [];
  7427. outlineData.groups.forEach(g => {
  7428. g.items.forEach(it => order.push(it.id));
  7429. });
  7430. outlineData.ungrouped.forEach(it => order.push(it.id));
  7431. const wrappers = Array.from(track.querySelectorAll('.outline-item-wrapper'));
  7432. const map = new Map();
  7433. wrappers.forEach(w => {
  7434. const id = w.querySelector('.page-number')?.textContent.trim();
  7435. if (id) map.set(id, w);
  7436. });
  7437. track.innerHTML = '';
  7438. order.forEach(id => {
  7439. const node = map.get(id);
  7440. if (node) track.appendChild(node);
  7441. });
  7442. }
  7443. document.addEventListener('DOMContentLoaded', () => {
  7444. initRightOutline();
  7445. });
  7446. function startPptUploadFlow() {
  7447. const input = document.getElementById('pptFileInput');
  7448. if (!input) return;
  7449. unsetAiFeatured();
  7450. input.value = '';
  7451. input.click();
  7452. }
  7453. function handlePptFileSelected(event) {
  7454. const file = event.target.files[0];
  7455. if (!file) return;
  7456. const ext = file.name.split('.').pop().toLowerCase();
  7457. if (!['ppt', 'pptx'].includes(ext)) {
  7458. alert('仅支持上传PPT文件');
  7459. return;
  7460. }
  7461. currentPptFile = file;
  7462. hideCreateModal();
  7463. beginPptParseSimulation(file);
  7464. }
  7465. function beginPptParseSimulation(file) {
  7466. setPptParseState('parsing', `正在解析 ${file.name}`);
  7467. const shouldFail = file.name.toLowerCase().includes('fail');
  7468. clearTimeout(pptParseTimer);
  7469. pptParseTimer = setTimeout(() => {
  7470. if (shouldFail) {
  7471. setPptParseState('error', '解析失败,请重试或更换文件');
  7472. } else {
  7473. setPptParseState('success', '解析完成,已生成课件');
  7474. setTimeout(() => {
  7475. closePptParseOverlay();
  7476. addPptContentToCourse(file.name);
  7477. }, 900);
  7478. }
  7479. }, 2200);
  7480. }
  7481. function setPptParseState(state, desc) {
  7482. const overlay = document.getElementById('pptParseOverlay');
  7483. const icon = document.getElementById('pptParseIcon');
  7484. const title = document.getElementById('pptParseTitle');
  7485. const text = document.getElementById('pptParseDesc');
  7486. const actions = document.getElementById('pptParseActions');
  7487. overlay.classList.add('active');
  7488. icon.className = 'ppt-parse-icon';
  7489. icon.innerHTML = statusIcons.loading;
  7490. title.textContent = '解析中...';
  7491. text.textContent = desc || '';
  7492. actions.style.display = 'flex';
  7493. Array.from(actions.querySelectorAll('button')).forEach(btn => btn.disabled = true);
  7494. if (state === 'parsing') {
  7495. icon.innerHTML = statusIcons.loading;
  7496. title.textContent = '解析中...';
  7497. icon.classList.remove('success', 'error');
  7498. } else if (state === 'success') {
  7499. icon.innerHTML = statusIcons.success;
  7500. icon.classList.add('success');
  7501. title.textContent = '解析完成';
  7502. Array.from(actions.querySelectorAll('button')).forEach(btn => btn.disabled = false);
  7503. } else if (state === 'error') {
  7504. icon.innerHTML = statusIcons.error;
  7505. icon.classList.add('error');
  7506. title.textContent = '解析失败';
  7507. Array.from(actions.querySelectorAll('button')).forEach(btn => btn.disabled = false);
  7508. }
  7509. }
  7510. function closePptParseOverlay() {
  7511. clearTimeout(pptParseTimer);
  7512. const overlay = document.getElementById('pptParseOverlay');
  7513. overlay.classList.remove('active');
  7514. }
  7515. function addPptContentToCourse(pptName) {
  7516. // 简单模拟:新增一页内容页并提示
  7517. addPageFromTemplate('content');
  7518. alert(`已从PPT "${pptName}" 生成课程页面`);
  7519. }
  7520. // 顶部菜单 & 课程设置
  7521. let courseMeta = { subject: '', grade: '' };
  7522. function toggleTopMenu(event) {
  7523. event.stopPropagation();
  7524. const dropdown = document.getElementById('topDropdown');
  7525. dropdown.classList.toggle('active');
  7526. }
  7527. function closeTopMenu() {
  7528. const dropdown = document.getElementById('topDropdown');
  7529. if (dropdown) dropdown.classList.remove('active');
  7530. }
  7531. document.addEventListener('click', () => {
  7532. closeTopMenu();
  7533. });
  7534. function handleTopMenuAction(action) {
  7535. closeTopMenu();
  7536. if (action === 'back') {
  7537. showCreateModal();
  7538. } else if (action === 'settings') {
  7539. showCourseSettings();
  7540. } else if (action === 'template') {
  7541. openTemplateCenter();
  7542. } else if (action === 'duplicate') {
  7543. alert('已另存为副本(示例)');
  7544. } else if (action === 'delete') {
  7545. openDeleteCourse();
  7546. }
  7547. }
  7548. function setMenuActive(panelType) {
  7549. document.querySelectorAll('.primary-menu .menu-item').forEach(item => {
  7550. const isMatch = item.getAttribute('data-panel') === panelType;
  7551. if (isMatch) {
  7552. item.classList.add('active');
  7553. } else {
  7554. item.classList.remove('active');
  7555. }
  7556. });
  7557. }
  7558. function showCourseSettings() {
  7559. const modal = document.getElementById('courseSettingsModal');
  7560. const subjectSelect = document.getElementById('settingsSubjectSelect');
  7561. const gradeSelect = document.getElementById('settingsGradeSelect');
  7562. if (!modal || !subjectSelect || !gradeSelect) return;
  7563. const publishSubject = document.getElementById('subjectSelect');
  7564. const publishGrade = document.getElementById('classGradeSelect');
  7565. subjectSelect.value = courseMeta.subject || (publishSubject ? publishSubject.value : '') || '';
  7566. gradeSelect.value = courseMeta.grade || (publishGrade ? publishGrade.value : '') || '';
  7567. modal.classList.add('active');
  7568. }
  7569. function hideCourseSettings() {
  7570. const modal = document.getElementById('courseSettingsModal');
  7571. if (modal) modal.classList.remove('active');
  7572. }
  7573. document.getElementById('courseSettingsModal').addEventListener('click', function(e) {
  7574. if (e.target === this) hideCourseSettings();
  7575. });
  7576. function applyCourseSettings() {
  7577. const subjectSelect = document.getElementById('settingsSubjectSelect');
  7578. const gradeSelect = document.getElementById('settingsGradeSelect');
  7579. courseMeta.subject = subjectSelect ? subjectSelect.value : '';
  7580. courseMeta.grade = gradeSelect ? gradeSelect.value : '';
  7581. applyCourseMetaToPublish();
  7582. hideCourseSettings();
  7583. }
  7584. function applyCourseMetaToPublish() {
  7585. const publishSubject = document.getElementById('subjectSelect');
  7586. const publishGrade = document.getElementById('classGradeSelect');
  7587. if (publishSubject) publishSubject.value = courseMeta.subject || '';
  7588. if (publishGrade) publishGrade.value = courseMeta.grade || '';
  7589. }
  7590. // 发布表单变化时同步回全局设置
  7591. const publishSubjectSelect = document.getElementById('subjectSelect');
  7592. if (publishSubjectSelect) publishSubjectSelect.addEventListener('change', () => {
  7593. courseMeta.subject = publishSubjectSelect.value;
  7594. });
  7595. const publishGradeSelect = document.getElementById('classGradeSelect');
  7596. if (publishGradeSelect) publishGradeSelect.addEventListener('change', () => {
  7597. courseMeta.grade = publishGradeSelect.value;
  7598. });
  7599. courseMeta.subject = publishSubjectSelect ? publishSubjectSelect.value : '';
  7600. courseMeta.grade = publishGradeSelect ? publishGradeSelect.value : '';
  7601. // 模板中心数据
  7602. const sampleTemplates = [
  7603. { id: 'tpl-1', name: '小学语文·故事导入', source: 'official', subject: 'chinese', grade: '3', pages: 8 },
  7604. { id: 'tpl-2', name: '小学数学·分数基础', source: 'official', subject: 'math', grade: '4', pages: 10 },
  7605. { id: 'tpl-3', name: '科学·水的三态', source: 'mine', subject: 'science', grade: '4', pages: 6 },
  7606. { id: 'tpl-4', name: '英语·词汇练习', source: 'official', subject: 'english', grade: '5', pages: 5 }
  7607. ];
  7608. let selectedTemplates = [];
  7609. function openTemplateCenter() {
  7610. document.getElementById('templateCenterModal').classList.add('active');
  7611. filterTemplates();
  7612. }
  7613. function closeTemplateCenter() {
  7614. document.getElementById('templateCenterModal').classList.remove('active');
  7615. selectedTemplates = [];
  7616. updateTemplateSelectedCount();
  7617. }
  7618. document.getElementById('templateCenterModal').addEventListener('click', function(e) {
  7619. if (e.target === this) closeTemplateCenter();
  7620. });
  7621. function filterTemplates() {
  7622. const source = document.getElementById('tplSourceFilter').value;
  7623. const subject = document.getElementById('tplSubjectFilter').value;
  7624. const grade = document.getElementById('tplGradeFilter').value;
  7625. const grid = document.getElementById('templateGrid');
  7626. let filtered = sampleTemplates.filter(t => {
  7627. if (source !== 'all' && t.source !== source) return false;
  7628. if (subject !== 'all' && t.subject !== subject) return false;
  7629. if (grade !== 'all' && t.grade !== grade) return false;
  7630. return true;
  7631. });
  7632. grid.innerHTML = filtered.map(t => `
  7633. <div class="app-card ${selectedTemplates.includes(t.id) ? 'selected' : ''}" onclick="toggleTemplateSelection('${t.id}')">
  7634. <div class="app-cover">
  7635. <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
  7636. <rect x="3" y="4" width="18" height="14" rx="2"/>
  7637. <line x1="8" y1="8" x2="16" y2="8"/>
  7638. <line x1="8" y1="12" x2="16" y2="12"/>
  7639. </svg>
  7640. </div>
  7641. <div class="app-info">
  7642. <div class="app-name">${t.name}</div>
  7643. <div class="app-description">${(t.subject || '通用')} · ${t.pages}页</div>
  7644. </div>
  7645. </div>
  7646. `).join('');
  7647. updateTemplateSelectedCount();
  7648. }
  7649. function toggleTemplateSelection(id) {
  7650. if (selectedTemplates.includes(id)) {
  7651. selectedTemplates = selectedTemplates.filter(tid => tid !== id);
  7652. } else {
  7653. selectedTemplates.push(id);
  7654. }
  7655. filterTemplates();
  7656. }
  7657. function updateTemplateSelectedCount() {
  7658. const countText = document.getElementById('selectedTemplateCount');
  7659. const btn = document.getElementById('confirmApplyTemplateBtn');
  7660. if (countText) countText.textContent = selectedTemplates.length;
  7661. if (btn) btn.disabled = selectedTemplates.length === 0;
  7662. }
  7663. function confirmApplyTemplates() {
  7664. if (selectedTemplates.length === 0) return;
  7665. selectedTemplates.forEach(() => addPageFromTemplate('content'));
  7666. alert('已应用模板,生成对应页面');
  7667. closeTemplateCenter();
  7668. }
  7669. // 删除课程确认
  7670. function openDeleteCourse() {
  7671. document.getElementById('deleteCourseModal').classList.add('active');
  7672. }
  7673. function hideDeleteCourse() {
  7674. document.getElementById('deleteCourseModal').classList.remove('active');
  7675. }
  7676. document.getElementById('deleteCourseModal').addEventListener('click', function(e) {
  7677. if (e.target === this) hideDeleteCourse();
  7678. });
  7679. function confirmDeleteCourse() {
  7680. hideDeleteCourse();
  7681. showCreateModal();
  7682. }
  7683. // 显示发布弹窗
  7684. function showPublishModal() {
  7685. // 获取当前课程名称
  7686. const courseName = document.querySelector('.course-title').textContent.trim();
  7687. document.getElementById('publishCourseNameDisplay').textContent = courseName;
  7688. document.getElementById('publishModal').classList.add('active');
  7689. }
  7690. // 隐藏发布弹窗
  7691. function hidePublishModal() {
  7692. document.getElementById('publishModal').classList.remove('active');
  7693. // 重置封面
  7694. const coverImage = document.getElementById('courseCoverImage');
  7695. const placeholder = document.getElementById('coverPlaceholder');
  7696. coverImage.style.display = 'none';
  7697. coverImage.src = '';
  7698. placeholder.style.display = 'block';
  7699. }
  7700. // 点击遮罩关闭发布弹窗
  7701. document.getElementById('publishModal').addEventListener('click', function(e) {
  7702. if (e.target === this) {
  7703. hidePublishModal();
  7704. }
  7705. });
  7706. // 编辑课程名称
  7707. function editCourseName() {
  7708. const display = document.getElementById('publishCourseNameDisplay');
  7709. const currentName = display.textContent;
  7710. // 创建输入框
  7711. const input = document.createElement('input');
  7712. input.type = 'text';
  7713. input.value = currentName;
  7714. input.className = 'publish-course-name-input';
  7715. // 设置初始宽度(基于当前文本)
  7716. const measureSpan = document.createElement('span');
  7717. measureSpan.style.visibility = 'hidden';
  7718. measureSpan.style.position = 'absolute';
  7719. measureSpan.style.fontSize = '18px';
  7720. measureSpan.style.fontWeight = '600';
  7721. measureSpan.style.padding = '10px 16px';
  7722. measureSpan.textContent = currentName;
  7723. document.body.appendChild(measureSpan);
  7724. const initialWidth = Math.max(120, Math.min(500, measureSpan.offsetWidth));
  7725. document.body.removeChild(measureSpan);
  7726. input.style.width = initialWidth + 'px';
  7727. // 替换显示元素
  7728. display.replaceWith(input);
  7729. input.focus();
  7730. input.select();
  7731. // 输入时自动调整宽度
  7732. input.addEventListener('input', () => {
  7733. const tempSpan = document.createElement('span');
  7734. tempSpan.style.visibility = 'hidden';
  7735. tempSpan.style.position = 'absolute';
  7736. tempSpan.style.fontSize = '18px';
  7737. tempSpan.style.fontWeight = '600';
  7738. tempSpan.style.padding = '10px 16px';
  7739. tempSpan.textContent = input.value || currentName;
  7740. document.body.appendChild(tempSpan);
  7741. const newWidth = Math.max(120, Math.min(500, tempSpan.offsetWidth));
  7742. document.body.removeChild(tempSpan);
  7743. input.style.width = newWidth + 'px';
  7744. });
  7745. // 失去焦点时恢复
  7746. const saveEdit = () => {
  7747. const newName = input.value.trim() || currentName;
  7748. const newDisplay = document.createElement('div');
  7749. newDisplay.id = 'publishCourseNameDisplay';
  7750. newDisplay.className = 'publish-course-name';
  7751. newDisplay.textContent = newName;
  7752. newDisplay.onclick = editCourseName;
  7753. input.replaceWith(newDisplay);
  7754. // 同步更新顶部课程标题
  7755. document.querySelector('.course-title').textContent = newName;
  7756. };
  7757. input.addEventListener('blur', saveEdit);
  7758. input.addEventListener('keydown', (e) => {
  7759. if (e.key === 'Enter') {
  7760. input.blur();
  7761. } else if (e.key === 'Escape') {
  7762. input.value = currentName;
  7763. input.blur();
  7764. }
  7765. });
  7766. }
  7767. // 从本地上传课程封面
  7768. function uploadCourseCoverLocal(event) {
  7769. event.stopPropagation();
  7770. document.getElementById('courseCoverInput').click();
  7771. }
  7772. // 网页搜索图片作为封面
  7773. function searchWebImageForCover(event) {
  7774. event.stopPropagation();
  7775. console.log('打开网页搜索图片弹窗(用于课程封面)');
  7776. alert('网页搜索图片功能(为课程封面)');
  7777. // 实际应用中应打开搜索浮窗,选择后设置封面
  7778. }
  7779. // AI生成图片作为封面
  7780. function generateAIImageForCover(event) {
  7781. event.stopPropagation();
  7782. console.log('打开AI生成图片弹窗(用于课程封面)');
  7783. alert('AI生成图片功能(为课程封面)');
  7784. // 实际应用中应打开AI生成浮窗,生成后设置封面
  7785. }
  7786. // 处理封面上传
  7787. function handleCoverUpload(event) {
  7788. const file = event.target.files[0];
  7789. if (file && file.type.startsWith('image/')) {
  7790. const reader = new FileReader();
  7791. reader.onload = function(e) {
  7792. const coverImage = document.getElementById('courseCoverImage');
  7793. const placeholder = document.getElementById('coverPlaceholder');
  7794. coverImage.src = e.target.result;
  7795. coverImage.style.display = 'block';
  7796. placeholder.style.display = 'none';
  7797. };
  7798. reader.readAsDataURL(file);
  7799. }
  7800. }
  7801. // 删除课程封面
  7802. function deleteCourseCover() {
  7803. const coverImage = document.getElementById('courseCoverImage');
  7804. const placeholder = document.getElementById('coverPlaceholder');
  7805. coverImage.src = '';
  7806. coverImage.style.display = 'none';
  7807. placeholder.style.display = 'block';
  7808. // 清空文件选择
  7809. document.getElementById('courseCoverInput').value = '';
  7810. }
  7811. // 选择可见范围
  7812. function selectVisibility(element, value) {
  7813. // 移除所有选中状态
  7814. document.querySelectorAll('.radio-option').forEach(option => {
  7815. option.classList.remove('selected');
  7816. });
  7817. // 添加选中状态
  7818. element.classList.add('selected');
  7819. // 设置radio按钮
  7820. element.querySelector('input[type="radio"]').checked = true;
  7821. }
  7822. // 确认发布
  7823. function confirmPublish(event) {
  7824. event.preventDefault();
  7825. const subject = document.getElementById('subjectSelect').value;
  7826. const classGrade = document.getElementById('classGradeSelect').value;
  7827. const classNum = document.getElementById('classSelect').value;
  7828. const visibility = document.querySelector('input[name="visibility"]:checked').value;
  7829. const courseName = document.getElementById('publishCourseNameDisplay').textContent;
  7830. const coverImage = document.getElementById('courseCoverImage');
  7831. const hasCover = coverImage.style.display !== 'none' && coverImage.src;
  7832. console.log('发布课程:', {
  7833. courseName,
  7834. subject,
  7835. classGrade,
  7836. classNum,
  7837. visibility,
  7838. hasCover
  7839. });
  7840. // 显示成功提示
  7841. alert(`课程《${courseName}》发布成功!\n学科: ${subject}\n班级: ${classGrade}年级${classNum}班\n可见范围: ${visibility === 'students' ? '仅发布学生可见' : '组织可见'}`);
  7842. hidePublishModal();
  7843. }
  7844. // 开始AI创建
  7845. function startAICreate() {
  7846. hideCreateModal();
  7847. // 切换到AI面板
  7848. switchToPanel('ai');
  7849. }
  7850. // 切换面板
  7851. function switchToPanel(panelType) {
  7852. const aiPanel = document.getElementById('aiPanel');
  7853. const pagesPanel = document.getElementById('pagesPanel');
  7854. const toolsPanel = document.getElementById('toolsPanel');
  7855. const appsPanel = document.getElementById('appsPanel');
  7856. const webPanel = document.getElementById('webPanel');
  7857. const resourcesPanel = document.getElementById('resourcesPanel');
  7858. [aiPanel, pagesPanel, toolsPanel, appsPanel, webPanel, resourcesPanel].forEach(panel => {
  7859. if (panel) {
  7860. panel.classList.remove('collapsed', 'hidden');
  7861. }
  7862. });
  7863. if (panelType === 'ai') {
  7864. pagesPanel.classList.add('hidden');
  7865. toolsPanel.classList.add('hidden');
  7866. appsPanel.classList.add('hidden');
  7867. webPanel.classList.add('hidden');
  7868. resourcesPanel.classList.add('hidden');
  7869. restoreNormalView();
  7870. } else if (panelType === 'pages') {
  7871. aiPanel.classList.add('hidden');
  7872. toolsPanel.classList.add('hidden');
  7873. appsPanel.classList.add('hidden');
  7874. webPanel.classList.add('hidden');
  7875. resourcesPanel.classList.add('hidden');
  7876. restoreNormalView();
  7877. } else if (panelType === 'tools') {
  7878. aiPanel.classList.add('hidden');
  7879. pagesPanel.classList.add('hidden');
  7880. appsPanel.classList.add('hidden');
  7881. webPanel.classList.add('hidden');
  7882. resourcesPanel.classList.add('hidden');
  7883. document.getElementById('toolsListView').classList.remove('hidden');
  7884. document.getElementById('toolsConfigView').classList.add('hidden');
  7885. restoreNormalView();
  7886. } else if (panelType === 'apps') {
  7887. aiPanel.classList.add('hidden');
  7888. pagesPanel.classList.add('hidden');
  7889. toolsPanel.classList.add('hidden');
  7890. webPanel.classList.add('hidden');
  7891. resourcesPanel.classList.add('hidden');
  7892. restoreNormalView();
  7893. } else if (panelType === 'web') {
  7894. aiPanel.classList.add('hidden');
  7895. pagesPanel.classList.add('hidden');
  7896. toolsPanel.classList.add('hidden');
  7897. appsPanel.classList.add('hidden');
  7898. resourcesPanel.classList.add('hidden');
  7899. switchToWebView('list');
  7900. restoreNormalView();
  7901. } else if (panelType === 'resources') {
  7902. aiPanel.classList.add('hidden');
  7903. pagesPanel.classList.add('hidden');
  7904. toolsPanel.classList.add('hidden');
  7905. appsPanel.classList.add('hidden');
  7906. webPanel.classList.add('hidden');
  7907. restoreNormalView();
  7908. }
  7909. }
  7910. // 二级菜单折叠
  7911. function toggleSecondaryPanel() {
  7912. const aiPanel = document.getElementById('aiPanel');
  7913. const pagesPanel = document.getElementById('pagesPanel');
  7914. const toolsPanel = document.getElementById('toolsPanel');
  7915. const appsPanel = document.getElementById('appsPanel');
  7916. const webPanel = document.getElementById('webPanel');
  7917. const resourcesPanel = document.getElementById('resourcesPanel');
  7918. let activePanel = null;
  7919. if (!aiPanel.classList.contains('hidden')) {
  7920. activePanel = aiPanel;
  7921. } else if (!pagesPanel.classList.contains('hidden')) {
  7922. activePanel = pagesPanel;
  7923. } else if (!toolsPanel.classList.contains('hidden')) {
  7924. activePanel = toolsPanel;
  7925. } else if (!appsPanel.classList.contains('hidden')) {
  7926. activePanel = appsPanel;
  7927. } else if (!webPanel.classList.contains('hidden')) {
  7928. activePanel = webPanel;
  7929. } else if (!resourcesPanel.classList.contains('hidden')) {
  7930. activePanel = resourcesPanel;
  7931. }
  7932. if (!activePanel) return;
  7933. activePanel.classList.toggle('collapsed');
  7934. const btn = activePanel.querySelector('.collapse-btn svg');
  7935. if (activePanel.classList.contains('collapsed')) {
  7936. btn.innerHTML = '<polyline points="9 18 15 12 9 6"/>';
  7937. } else {
  7938. btn.innerHTML = '<polyline points="15 18 9 12 15 6"/>';
  7939. }
  7940. }
  7941. // 右侧面板折叠
  7942. function toggleRightPanel() {
  7943. const panel = document.getElementById('rightPanel');
  7944. panel.classList.toggle('collapsed');
  7945. const btn = panel.querySelector('.collapse-btn svg');
  7946. if (panel.classList.contains('collapsed')) {
  7947. btn.innerHTML = '<polyline points="15 18 9 12 15 6"/>';
  7948. } else {
  7949. btn.innerHTML = '<polyline points="9 18 15 12 9 6"/>';
  7950. }
  7951. }
  7952. // 一级菜单点击
  7953. document.querySelectorAll('.primary-menu .menu-item').forEach(item => {
  7954. item.addEventListener('click', function() {
  7955. document.querySelectorAll('.primary-menu .menu-item').forEach(i => i.classList.remove('active'));
  7956. this.classList.add('active');
  7957. const panelType = this.getAttribute('data-panel');
  7958. if (panelType === 'ai') {
  7959. switchToPanel('ai');
  7960. } else if (panelType === 'pages') {
  7961. switchToPanel('pages');
  7962. } else if (panelType === 'tools') {
  7963. switchToPanel('tools');
  7964. } else if (panelType === 'apps') {
  7965. switchToPanel('apps');
  7966. } else if (panelType === 'web') {
  7967. switchToPanel('web');
  7968. } else if (panelType === 'resources') {
  7969. switchToPanel('resources');
  7970. }
  7971. });
  7972. });
  7973. // 自动调整textarea高度
  7974. function autoResize(textarea) {
  7975. textarea.style.height = 'auto';
  7976. const newHeight = Math.max(72, Math.min(textarea.scrollHeight, 180));
  7977. textarea.style.height = newHeight + 'px';
  7978. }
  7979. // 发送消息
  7980. function sendMessage() {
  7981. const input = document.getElementById('chatInput');
  7982. const message = input.value.trim();
  7983. const inputContainer = document.getElementById('chatInputContainer');
  7984. const statusText = document.getElementById('statusText');
  7985. const sendBtn = document.getElementById('sendBtn');
  7986. const uploadBtn = document.getElementById('uploadBtn');
  7987. if (!message) return;
  7988. // 添加用户消息
  7989. addMessage(message, 'user');
  7990. // 清空输入框并重置高度
  7991. input.value = '';
  7992. input.style.height = '72px';
  7993. // 移动对话框到底部
  7994. inputContainer.classList.add('bottom');
  7995. // 显示生成状态
  7996. statusText.classList.add('active');
  7997. sendBtn.classList.add('generating');
  7998. uploadBtn.style.display = 'none';
  7999. // 禁用输入和按钮
  8000. input.disabled = true;
  8001. sendBtn.disabled = true;
  8002. // 模拟AI回复
  8003. setTimeout(() => {
  8004. const topic = extractTopic(message);
  8005. const aiResponse = `好的,我正在为您生成"${topic}"的课程内容。\n\n课程将包括:\n• 教学目标和重点\n• 互动演示内容\n• 课堂练习题\n• 小组活动设计\n\n预计生成时间:30秒`;
  8006. addMessage(aiResponse, 'ai');
  8007. // 更新课程标题
  8008. document.querySelector('.course-title').textContent = topic;
  8009. // 模拟生成完成
  8010. setTimeout(() => {
  8011. addMessage('✅ 课程已生成完成!已为您创建了5个教学页面和3个互动工具。您可以在底部查看课程大纲,或在中央区域开始编辑。', 'ai');
  8012. // 切换到编辑模式
  8013. switchToEditMode();
  8014. // 恢复正常状态
  8015. statusText.classList.remove('active');
  8016. sendBtn.classList.remove('generating');
  8017. uploadBtn.style.display = 'flex';
  8018. input.disabled = false;
  8019. sendBtn.disabled = false;
  8020. // 更新保存状态
  8021. document.getElementById('saveStatus').textContent = '已保存';
  8022. }, 2000);
  8023. }, 800);
  8024. }
  8025. // 添加消息到聊天区域
  8026. function addMessage(text, type) {
  8027. const messagesContainer = document.getElementById('chatMessages');
  8028. const messageDiv = document.createElement('div');
  8029. messageDiv.className = `message message-${type}`;
  8030. const contentDiv = document.createElement('div');
  8031. contentDiv.className = 'message-content';
  8032. contentDiv.textContent = text;
  8033. messageDiv.appendChild(contentDiv);
  8034. messagesContainer.appendChild(messageDiv);
  8035. // 滚动到底部
  8036. messagesContainer.scrollTop = messagesContainer.scrollHeight;
  8037. }
  8038. // 提取课程主题
  8039. function extractTopic(message) {
  8040. const match = message.match(/教学内容为(.+?)的/);
  8041. if (match) {
  8042. return match[1];
  8043. }
  8044. return '新课程';
  8045. }
  8046. // 切换到编辑模式
  8047. function switchToEditMode() {
  8048. isEditMode = true;
  8049. // 隐藏占位符
  8050. document.getElementById('slidePlaceholder').style.display = 'none';
  8051. // 显示工具栏
  8052. document.getElementById('elementToolbar').classList.add('visible');
  8053. // 显示底部大纲
  8054. document.getElementById('bottomOutline').classList.add('visible');
  8055. // 生成示例页面
  8056. generateSamplePages();
  8057. }
  8058. // 生成示例页面
  8059. function generateSamplePages() {
  8060. const outlineTrack = document.getElementById('outlineTrack');
  8061. const pageCount = 5;
  8062. const pageTypes = ['标题页', '内容页', '选择题', '问答', '总结'];
  8063. for (let i = 1; i <= pageCount; i++) {
  8064. const wrapper = document.createElement('div');
  8065. wrapper.className = 'outline-item-wrapper';
  8066. const outlineItem = document.createElement('div');
  8067. outlineItem.className = 'outline-item' + (i === 1 ? ' active' : '');
  8068. outlineItem.draggable = true;
  8069. outlineItem.dataset.pageIndex = i;
  8070. outlineItem.onclick = (e) => selectPage(i, e);
  8071. outlineItem.innerHTML = `
  8072. <span class="page-number">${i}</span>
  8073. <span>${pageTypes[i - 1]}</span>
  8074. <div class="page-menu" onclick="event.stopPropagation(); togglePageMenu(event, ${i})">
  8075. <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
  8076. <circle cx="12" cy="12" r="1"/>
  8077. <circle cx="12" cy="5" r="1"/>
  8078. <circle cx="12" cy="19" r="1"/>
  8079. </svg>
  8080. <div class="page-menu-dropdown">
  8081. <div class="page-menu-item" onclick="copyPage(${i})">
  8082. <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
  8083. <rect x="9" y="9" width="13" height="13" rx="2" ry="2"/>
  8084. <path d="M5 15H4a2 2 0 01-2-2V4a2 2 0 012-2h9a2 2 0 012 2v1"/>
  8085. </svg>
  8086. 复制
  8087. </div>
  8088. <div class="page-menu-item" onclick="addBlankPage(${i})">
  8089. <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
  8090. <line x1="12" y1="5" x2="12" y2="19"/>
  8091. <line x1="5" y1="12" x2="19" y2="12"/>
  8092. </svg>
  8093. 新增
  8094. </div>
  8095. <div class="page-menu-item danger" onclick="deletePage(${i})">
  8096. <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
  8097. <polyline points="3 6 5 6 21 6"/>
  8098. <path d="M19 6v14a2 2 0 01-2 2H7a2 2 0 01-2-2V6m3 0V4a2 2 0 012-2h4a2 2 0 012 2v2"/>
  8099. </svg>
  8100. 删除
  8101. </div>
  8102. </div>
  8103. </div>
  8104. `;
  8105. // 添加拖拽事件监听器
  8106. outlineItem.addEventListener('dragstart', handleDragStart);
  8107. outlineItem.addEventListener('dragend', handleDragEnd);
  8108. outlineItem.addEventListener('dragover', handleDragOver);
  8109. outlineItem.addEventListener('drop', handleDrop);
  8110. outlineItem.addEventListener('dragleave', handleDragLeave);
  8111. const addBtn = document.createElement('button');
  8112. addBtn.className = 'add-page-between';
  8113. addBtn.textContent = '+';
  8114. addBtn.onclick = () => addPageBetween(i);
  8115. wrapper.appendChild(outlineItem);
  8116. wrapper.appendChild(addBtn);
  8117. outlineTrack.appendChild(wrapper);
  8118. }
  8119. syncRightOutlineWithBottom();
  8120. }
  8121. // 选择页面
  8122. function selectPage(pageNum, evt) {
  8123. const currentEvent = evt || event;
  8124. document.querySelectorAll('.outline-item').forEach(item => {
  8125. item.classList.remove('active');
  8126. });
  8127. if (currentEvent && currentEvent.currentTarget) {
  8128. currentEvent.currentTarget.classList.add('active');
  8129. }
  8130. }
  8131. // 页面菜单切换
  8132. function togglePageMenu(event, pageNum) {
  8133. event.stopPropagation();
  8134. const menu = event.currentTarget;
  8135. // 关闭其他菜单
  8136. document.querySelectorAll('.page-menu').forEach(m => {
  8137. if (m !== menu) m.classList.remove('active');
  8138. });
  8139. menu.classList.toggle('active');
  8140. }
  8141. // 复制页面
  8142. function copyPage(pageNum) {
  8143. console.log('复制页面', pageNum);
  8144. alert('复制页面 ' + pageNum);
  8145. }
  8146. // 新增空白页面
  8147. function addBlankPage(afterPage) {
  8148. console.log('在页面后新增空白页', afterPage);
  8149. alert('在页面 ' + afterPage + ' 后新增空白页');
  8150. }
  8151. // 删除页面
  8152. function deletePage(pageNum) {
  8153. if (confirm('确定要删除页面 ' + pageNum + ' 吗?')) {
  8154. console.log('删除页面', pageNum);
  8155. alert('删除页面 ' + pageNum);
  8156. }
  8157. }
  8158. // 在页面之间添加
  8159. function addPageBetween(afterPage) {
  8160. console.log('在页面后添加新页面', afterPage);
  8161. alert('在页面 ' + afterPage + ' 后添加新页面');
  8162. }
  8163. // 页面拖拽功能
  8164. let draggedElement = null;
  8165. let draggedIndex = null;
  8166. function handleDragStart(e) {
  8167. draggedElement = e.currentTarget;
  8168. draggedIndex = parseInt(draggedElement.dataset.pageIndex);
  8169. draggedElement.classList.add('dragging');
  8170. e.dataTransfer.effectAllowed = 'move';
  8171. e.dataTransfer.setData('text/html', draggedElement.innerHTML);
  8172. }
  8173. function handleDragEnd(e) {
  8174. e.currentTarget.classList.remove('dragging');
  8175. // 清除所有拖拽状态
  8176. document.querySelectorAll('.outline-item').forEach(item => {
  8177. item.classList.remove('drag-over');
  8178. });
  8179. }
  8180. function handleDragOver(e) {
  8181. if (e.preventDefault) {
  8182. e.preventDefault();
  8183. }
  8184. e.dataTransfer.dropEffect = 'move';
  8185. const targetElement = e.currentTarget;
  8186. if (targetElement !== draggedElement) {
  8187. targetElement.classList.add('drag-over');
  8188. }
  8189. return false;
  8190. }
  8191. function handleDragLeave(e) {
  8192. e.currentTarget.classList.remove('drag-over');
  8193. }
  8194. function handleDrop(e) {
  8195. if (e.stopPropagation) {
  8196. e.stopPropagation();
  8197. }
  8198. const targetElement = e.currentTarget;
  8199. const targetIndex = parseInt(targetElement.dataset.pageIndex);
  8200. if (draggedElement !== targetElement && draggedIndex !== targetIndex) {
  8201. // 交换页面顺序
  8202. const outlineTrack = document.getElementById('outlineTrack');
  8203. const allWrappers = Array.from(outlineTrack.querySelectorAll('.outline-item-wrapper'));
  8204. const draggedWrapper = draggedElement.parentElement;
  8205. const targetWrapper = targetElement.parentElement;
  8206. // 获取位置
  8207. const draggedWrapperIndex = allWrappers.indexOf(draggedWrapper);
  8208. const targetWrapperIndex = allWrappers.indexOf(targetWrapper);
  8209. if (draggedWrapperIndex < targetWrapperIndex) {
  8210. targetWrapper.parentNode.insertBefore(draggedWrapper, targetWrapper.nextSibling);
  8211. } else {
  8212. targetWrapper.parentNode.insertBefore(draggedWrapper, targetWrapper);
  8213. }
  8214. // 更新页面序号
  8215. updatePageNumbers();
  8216. console.log(`页面从位置 ${draggedIndex} 移动到位置 ${targetIndex}`);
  8217. }
  8218. targetElement.classList.remove('drag-over');
  8219. return false;
  8220. }
  8221. // 更新页面序号
  8222. function updatePageNumbers() {
  8223. const outlineItems = document.querySelectorAll('.outline-item');
  8224. outlineItems.forEach((item, index) => {
  8225. const pageNumber = index + 1;
  8226. item.dataset.pageIndex = pageNumber;
  8227. const numberSpan = item.querySelector('.page-number');
  8228. if (numberSpan) {
  8229. numberSpan.textContent = pageNumber;
  8230. }
  8231. });
  8232. }
  8233. // 下拉菜单切换
  8234. function toggleDropdown(id) {
  8235. event.stopPropagation();
  8236. const dropdown = document.getElementById(id);
  8237. dropdown.classList.toggle('active');
  8238. }
  8239. // 点击页面其他地方关闭下拉菜单
  8240. document.addEventListener('click', function() {
  8241. document.querySelectorAll('.dropdown').forEach(d => d.classList.remove('active'));
  8242. document.querySelectorAll('.page-menu').forEach(m => m.classList.remove('active'));
  8243. });
  8244. // 插入表格
  8245. function insertTable() {
  8246. alert('插入表格功能');
  8247. }
  8248. // 插入形状
  8249. function insertShape(type) {
  8250. const canvas = document.getElementById('slideCanvas');
  8251. const element = document.createElement('div');
  8252. element.className = 'slide-element';
  8253. element.id = 'element-' + (++elementIdCounter);
  8254. element.style.left = '200px';
  8255. element.style.top = '200px';
  8256. element.style.width = '150px';
  8257. element.style.height = '150px';
  8258. if (type === 'circle') {
  8259. element.style.borderRadius = '50%';
  8260. element.style.background = '#285cf5';
  8261. } else if (type === 'rectangle') {
  8262. element.style.background = '#3b82f6';
  8263. } else if (type === 'triangle') {
  8264. element.style.background = '#10b981';
  8265. } else if (type === 'arrow') {
  8266. element.style.background = '#f59e0b';
  8267. }
  8268. addResizeHandles(element);
  8269. canvas.appendChild(element);
  8270. makeElementDraggable(element);
  8271. element.addEventListener('click', () => selectElement(element));
  8272. }
  8273. // 添加调整大小手柄
  8274. function addResizeHandles(element) {
  8275. const positions = ['nw', 'ne', 'sw', 'se'];
  8276. positions.forEach(pos => {
  8277. const handle = document.createElement('div');
  8278. handle.className = 'resize-handle ' + pos;
  8279. element.appendChild(handle);
  8280. });
  8281. }
  8282. // 插入文本
  8283. function insertText() {
  8284. const canvas = document.getElementById('slideCanvas');
  8285. const element = document.createElement('div');
  8286. element.className = 'slide-element text-element';
  8287. element.id = 'element-' + (++elementIdCounter);
  8288. element.contentEditable = true;
  8289. element.textContent = '双击编辑文本';
  8290. element.style.left = '100px';
  8291. element.style.top = '100px';
  8292. element.style.width = '300px';
  8293. element.style.minHeight = '50px';
  8294. addResizeHandles(element);
  8295. canvas.appendChild(element);
  8296. makeElementDraggable(element);
  8297. element.addEventListener('click', () => selectElement(element));
  8298. }
  8299. // 插入图片
  8300. function insertImage() {
  8301. const canvas = document.getElementById('slideCanvas');
  8302. const element = document.createElement('div');
  8303. element.className = 'slide-element has-image';
  8304. element.id = 'element-' + (++elementIdCounter);
  8305. element.style.left = '150px';
  8306. element.style.top = '150px';
  8307. element.style.width = '200px';
  8308. element.style.height = '150px';
  8309. element.style.background = '#e5e7eb';
  8310. element.style.display = 'flex';
  8311. element.style.alignItems = 'center';
  8312. element.style.justifyContent = 'center';
  8313. element.innerHTML = `
  8314. <span style="color: #9ca3af;">图片占位</span>
  8315. <div class="image-hover-menu">
  8316. <div class="image-menu-item" onclick="uploadLocal(event)">
  8317. <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
  8318. <path d="M21 15v4a2 2 0 01-2 2H5a2 2 0 01-2-2v-4"/>
  8319. <polyline points="17 8 12 3 7 8"/>
  8320. <line x1="12" y1="3" x2="12" y2="15"/>
  8321. </svg>
  8322. 自本地上传
  8323. </div>
  8324. <div class="image-menu-item" onclick="searchWebImage(event)">
  8325. <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
  8326. <circle cx="11" cy="11" r="8"/>
  8327. <path d="M21 21l-4.35-4.35"/>
  8328. </svg>
  8329. 自网页搜索
  8330. </div>
  8331. <div class="image-menu-item" onclick="generateAIImage(event)">
  8332. <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
  8333. <path d="M12 2L2 7l10 5 10-5-10-5z"/>
  8334. <path d="M2 17l10 5 10-5"/>
  8335. <path d="M2 12l10 5 10-5"/>
  8336. </svg>
  8337. 自AI生成
  8338. </div>
  8339. </div>
  8340. `;
  8341. addResizeHandles(element);
  8342. canvas.appendChild(element);
  8343. makeElementDraggable(element);
  8344. element.addEventListener('click', () => selectElement(element));
  8345. }
  8346. // 从模板添加页面
  8347. function addPageFromTemplate(templateType) {
  8348. if (!isEditMode) {
  8349. switchToEditMode();
  8350. }
  8351. const outlineTrack = document.getElementById('outlineTrack');
  8352. const pageCount = outlineTrack.querySelectorAll('.outline-item-wrapper').length + 1;
  8353. const templateNames = {
  8354. 'title': '标题页',
  8355. 'image': '图片页',
  8356. 'content': '内容页',
  8357. 'text-image': '文图页',
  8358. 'image-text': '图文页'
  8359. };
  8360. const wrapper = document.createElement('div');
  8361. wrapper.className = 'outline-item-wrapper';
  8362. const outlineItem = document.createElement('div');
  8363. outlineItem.className = 'outline-item';
  8364. outlineItem.onclick = (e) => selectPage(pageCount, e);
  8365. outlineItem.innerHTML = `
  8366. <span class="page-number">${pageCount}</span>
  8367. <span>${templateNames[templateType]}</span>
  8368. <div class="page-menu" onclick="event.stopPropagation(); togglePageMenu(event, ${pageCount})">
  8369. <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
  8370. <circle cx="12" cy="12" r="1"/>
  8371. <circle cx="12" cy="5" r="1"/>
  8372. <circle cx="12" cy="19" r="1"/>
  8373. </svg>
  8374. <div class="page-menu-dropdown">
  8375. <div class="page-menu-item" onclick="copyPage(${pageCount})">
  8376. <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
  8377. <rect x="9" y="9" width="13" height="13" rx="2" ry="2"/>
  8378. <path d="M5 15H4a2 2 0 01-2-2V4a2 2 0 012-2h9a2 2 0 012 2v1"/>
  8379. </svg>
  8380. 复制
  8381. </div>
  8382. <div class="page-menu-item" onclick="addBlankPage(${pageCount})">
  8383. <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
  8384. <line x1="12" y1="5" x2="12" y2="19"/>
  8385. <line x1="5" y1="12" x2="19" y2="12"/>
  8386. </svg>
  8387. 新增
  8388. </div>
  8389. <div class="page-menu-item danger" onclick="deletePage(${pageCount})">
  8390. <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
  8391. <polyline points="3 6 5 6 21 6"/>
  8392. <path d="M19 6v14a2 2 0 01-2 2H7a2 2 0 01-2-2V6m3 0V4a2 2 0 012-2h4a2 2 0 012 2v2"/>
  8393. </svg>
  8394. 删除
  8395. </div>
  8396. </div>
  8397. </div>
  8398. `;
  8399. const addBtn = document.createElement('button');
  8400. addBtn.className = 'add-page-between';
  8401. addBtn.textContent = '+';
  8402. addBtn.onclick = () => addPageBetween(pageCount);
  8403. wrapper.appendChild(outlineItem);
  8404. wrapper.appendChild(addBtn);
  8405. outlineTrack.appendChild(wrapper);
  8406. // 如果是包含图片的页面,自动添加图片元素
  8407. if (['image', 'text-image', 'image-text'].includes(templateType)) {
  8408. insertImage();
  8409. }
  8410. alert('已添加' + templateNames[templateType]);
  8411. syncRightOutlineWithBottom();
  8412. }
  8413. // 上传PPT
  8414. function uploadPPT() {
  8415. const input = document.createElement('input');
  8416. input.type = 'file';
  8417. input.accept = '.ppt,.pptx';
  8418. input.onchange = (e) => {
  8419. const file = e.target.files[0];
  8420. if (file) {
  8421. alert('正在解析PPT文件: ' + file.name + '\n\n此功能将自动识别PPT中的页面并导入到课件中。');
  8422. }
  8423. };
  8424. input.click();
  8425. }
  8426. // 图片上传方法
  8427. function uploadLocal(event) {
  8428. event.stopPropagation();
  8429. const input = document.createElement('input');
  8430. input.type = 'file';
  8431. input.accept = 'image/*';
  8432. input.onchange = (e) => {
  8433. const file = e.target.files[0];
  8434. if (file) {
  8435. alert('已选择图片: ' + file.name);
  8436. }
  8437. };
  8438. input.click();
  8439. }
  8440. function searchWebImage(event) {
  8441. event.stopPropagation();
  8442. document.getElementById('searchImageModal').classList.add('active');
  8443. }
  8444. function generateAIImage(event) {
  8445. event.stopPropagation();
  8446. document.getElementById('generateImageModal').classList.add('active');
  8447. }
  8448. // 关闭搜索浮窗
  8449. function closeSearchModal() {
  8450. document.getElementById('searchImageModal').classList.remove('active');
  8451. }
  8452. // 确认选择搜索图片
  8453. function confirmSearchImage() {
  8454. alert('已选择网页图片');
  8455. closeSearchModal();
  8456. }
  8457. // 关闭生成浮窗
  8458. function closeGenerateModal() {
  8459. document.getElementById('generateImageModal').classList.remove('active');
  8460. }
  8461. // 开始生成图片
  8462. function startGenerate() {
  8463. const loading = document.getElementById('generateLoading');
  8464. loading.classList.add('active');
  8465. setTimeout(() => {
  8466. loading.classList.remove('active');
  8467. alert('图片生成完成!');
  8468. closeGenerateModal();
  8469. }, 3000);
  8470. }
  8471. // 点击浮窗遮罩关闭
  8472. document.getElementById('searchImageModal').addEventListener('click', function(e) {
  8473. if (e.target === this) {
  8474. closeSearchModal();
  8475. }
  8476. });
  8477. document.getElementById('generateImageModal').addEventListener('click', function(e) {
  8478. if (e.target === this) {
  8479. closeGenerateModal();
  8480. }
  8481. });
  8482. document.getElementById('videoSourceModal').addEventListener('click', function(e) {
  8483. if (e.target === this) {
  8484. closeVideoSourceModal();
  8485. }
  8486. });
  8487. document.getElementById('audioUploadModal').addEventListener('click', function(e) {
  8488. if (e.target === this) {
  8489. closeAudioUploadModal();
  8490. }
  8491. });
  8492. document.getElementById('documentUploadModal').addEventListener('click', function(e) {
  8493. if (e.target === this) {
  8494. closeDocumentUploadModal();
  8495. }
  8496. });
  8497. document.getElementById('collectionModal').addEventListener('click', function(e) {
  8498. if (e.target === this) {
  8499. closeCollectionModal();
  8500. }
  8501. });
  8502. // 使元素可拖动
  8503. function makeElementDraggable(element) {
  8504. let isDragging = false;
  8505. let startX, startY, startLeft, startTop;
  8506. element.addEventListener('mousedown', function(e) {
  8507. if (e.target.classList.contains('resize-handle')) return;
  8508. if (e.target.contentEditable === 'true' && e.target === element) return;
  8509. isDragging = true;
  8510. startX = e.clientX;
  8511. startY = e.clientY;
  8512. startLeft = parseInt(element.style.left) || 0;
  8513. startTop = parseInt(element.style.top) || 0;
  8514. e.preventDefault();
  8515. });
  8516. document.addEventListener('mousemove', function(e) {
  8517. if (!isDragging) return;
  8518. const dx = e.clientX - startX;
  8519. const dy = e.clientY - startY;
  8520. element.style.left = (startLeft + dx) + 'px';
  8521. element.style.top = (startTop + dy) + 'px';
  8522. });
  8523. document.addEventListener('mouseup', function() {
  8524. isDragging = false;
  8525. });
  8526. }
  8527. // 选中元素
  8528. function selectElement(element) {
  8529. if (selectedElement) {
  8530. selectedElement.classList.remove('selected');
  8531. }
  8532. selectedElement = element;
  8533. element.classList.add('selected');
  8534. // 显示编辑工具
  8535. document.getElementById('editTools').style.display = 'flex';
  8536. }
  8537. // 删除元素
  8538. function deleteElement() {
  8539. if (selectedElement) {
  8540. selectedElement.remove();
  8541. selectedElement = null;
  8542. document.getElementById('editTools').style.display = 'none';
  8543. }
  8544. }
  8545. // 点击canvas空白处取消选择
  8546. document.getElementById('slideCanvas').addEventListener('click', function(e) {
  8547. if (e.target === this && selectedElement) {
  8548. selectedElement.classList.remove('selected');
  8549. selectedElement = null;
  8550. document.getElementById('editTools').style.display = 'none';
  8551. }
  8552. });
  8553. // 监听Enter键发送消息
  8554. document.getElementById('chatInput').addEventListener('keydown', function(e) {
  8555. if (e.key === 'Enter' && !e.shiftKey) {
  8556. e.preventDefault();
  8557. sendMessage();
  8558. }
  8559. });
  8560. // 页面加载时显示创建弹窗
  8561. window.addEventListener('load', function() {
  8562. setTimeout(() => {
  8563. showCreateModal();
  8564. }, 300);
  8565. });
  8566. // ========== AI应用功能 ==========
  8567. // 应用数据(模拟)
  8568. const sampleApps = [
  8569. {
  8570. id: 'app-001',
  8571. name: '单词记忆卡',
  8572. description: '帮助学生记忆单词的互动卡片应用',
  8573. source: 'public',
  8574. type: 'agent',
  8575. mode: 'card',
  8576. category: '英语',
  8577. grade: '小学'
  8578. },
  8579. {
  8580. id: 'app-002',
  8581. name: '数学计算练习',
  8582. description: '支持四则运算的智能练习系统',
  8583. source: 'public',
  8584. type: 'workflow',
  8585. mode: 'immersive',
  8586. category: '数学',
  8587. grade: '小学'
  8588. },
  8589. {
  8590. id: 'app-003',
  8591. name: '诗词鉴赏助手',
  8592. description: 'AI辅助的诗词理解与鉴赏工具',
  8593. source: 'public',
  8594. type: 'agent',
  8595. mode: 'chat',
  8596. category: '语文',
  8597. grade: '初中'
  8598. },
  8599. {
  8600. id: 'app-004',
  8601. name: '化学实验模拟',
  8602. description: '虚拟化学实验室,安全探索化学反应',
  8603. source: 'public',
  8604. type: 'workflow',
  8605. mode: 'immersive',
  8606. category: '化学',
  8607. grade: '高中'
  8608. },
  8609. {
  8610. id: 'app-005',
  8611. name: '历史时间轴',
  8612. description: '交互式历史事件时间轴展示',
  8613. source: 'mine',
  8614. type: 'workflow',
  8615. mode: 'card',
  8616. category: '历史',
  8617. grade: '初中'
  8618. },
  8619. {
  8620. id: 'app-006',
  8621. name: '地理地图探索',
  8622. description: '3D地球仪和地理知识问答',
  8623. source: 'public',
  8624. type: 'agent',
  8625. mode: 'immersive',
  8626. category: '地理',
  8627. grade: '初中'
  8628. },
  8629. {
  8630. id: 'app-007',
  8631. name: '物理公式推导',
  8632. description: 'AI辅助的物理公式推导与解题',
  8633. source: 'mine',
  8634. type: 'agent',
  8635. mode: 'chat',
  8636. category: '物理',
  8637. grade: '高中'
  8638. },
  8639. {
  8640. id: 'app-008',
  8641. name: '生物细胞识别',
  8642. description: '显微镜下的细胞结构学习工具',
  8643. source: 'public',
  8644. type: 'workflow',
  8645. mode: 'card',
  8646. category: '生物',
  8647. grade: '初中'
  8648. },
  8649. {
  8650. id: 'app-009',
  8651. name: '作文批改助手',
  8652. description: 'AI智能作文评分与改进建议',
  8653. source: 'public',
  8654. type: 'agent',
  8655. mode: 'chat',
  8656. category: '语文',
  8657. grade: '小学'
  8658. }
  8659. ];
  8660. let selectedApps = [];
  8661. let currentFilters = { source: 'all', type: 'all', mode: 'all' };
  8662. let chatMessages = [];
  8663. // 打开应用中心
  8664. function openAIAppCenter() {
  8665. document.getElementById('aiAppCenterModal').classList.add('active');
  8666. renderAppGrid();
  8667. }
  8668. // 关闭应用中心
  8669. function closeAppCenter() {
  8670. document.getElementById('aiAppCenterModal').classList.remove('active');
  8671. selectedApps = [];
  8672. updateSelectedCount();
  8673. }
  8674. // 点击遮罩关闭
  8675. document.getElementById('aiAppCenterModal').addEventListener('click', function(e) {
  8676. if (e.target === this) {
  8677. closeAppCenter();
  8678. }
  8679. });
  8680. // 筛选应用
  8681. function filterApps() {
  8682. // 获取三个下拉菜单的值
  8683. currentFilters.source = document.getElementById('sourceFilter').value;
  8684. currentFilters.type = document.getElementById('typeFilter').value;
  8685. currentFilters.mode = document.getElementById('modeFilter').value;
  8686. // 重新渲染应用列表
  8687. renderAppGrid();
  8688. }
  8689. // 渲染应用网格
  8690. function renderAppGrid() {
  8691. const appGrid = document.getElementById('appGrid');
  8692. // 过滤应用
  8693. let filteredApps = sampleApps.filter(app => {
  8694. if (currentFilters.source !== 'all' && app.source !== currentFilters.source) return false;
  8695. if (currentFilters.type !== 'all' && app.type !== currentFilters.type) return false;
  8696. if (currentFilters.mode !== 'all' && app.mode !== currentFilters.mode) return false;
  8697. return true;
  8698. });
  8699. // 生成HTML
  8700. appGrid.innerHTML = filteredApps.map(app => `
  8701. <div class="app-card ${selectedApps.includes(app.id) ? 'selected' : ''}"
  8702. onclick="toggleAppSelection('${app.id}')">
  8703. <div class="app-cover">
  8704. <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
  8705. <rect x="3" y="3" width="7" height="7" rx="1"/>
  8706. <rect x="14" y="3" width="7" height="7" rx="1"/>
  8707. <rect x="14" y="14" width="7" height="7" rx="1"/>
  8708. <rect x="3" y="14" width="7" height="7" rx="1"/>
  8709. </svg>
  8710. </div>
  8711. <div class="app-info">
  8712. <div class="app-name">${app.name}</div>
  8713. <div class="app-description">${app.description}</div>
  8714. <div class="app-meta">
  8715. <span class="app-tag">${app.type === 'agent' ? '智能体' : '工作流'}</span>
  8716. <span class="app-tag">${app.mode === 'card' ? '卡片式' : app.mode === 'immersive' ? '沉浸式' : '聊天式'}</span>
  8717. </div>
  8718. </div>
  8719. </div>
  8720. `).join('');
  8721. }
  8722. // 切换应用选中状态
  8723. function toggleAppSelection(appId) {
  8724. const index = selectedApps.indexOf(appId);
  8725. if (index > -1) {
  8726. selectedApps.splice(index, 1);
  8727. } else {
  8728. selectedApps.push(appId);
  8729. }
  8730. renderAppGrid();
  8731. updateSelectedCount();
  8732. }
  8733. // 更新已选数量
  8734. function updateSelectedCount() {
  8735. const countText = document.getElementById('selectedCountText');
  8736. const confirmBtn = document.getElementById('confirmAddAppsBtn');
  8737. countText.textContent = selectedApps.length;
  8738. confirmBtn.disabled = selectedApps.length === 0;
  8739. }
  8740. // 确认添加应用
  8741. function confirmAddApps() {
  8742. if (selectedApps.length === 0) return;
  8743. const selectedAppNames = sampleApps
  8744. .filter(app => selectedApps.includes(app.id))
  8745. .map(app => app.name)
  8746. .join('、');
  8747. alert(`已添加 ${selectedApps.length} 个应用页面:\n${selectedAppNames}`);
  8748. // 这里可以添加实际的页面创建逻辑
  8749. // selectedApps.forEach(appId => {
  8750. // const app = sampleApps.find(a => a.id === appId);
  8751. // addPageFromAIApp(app);
  8752. // });
  8753. closeAppCenter();
  8754. }
  8755. // 打开创建应用方式选择弹窗
  8756. function openCreateAppModal() {
  8757. document.getElementById('createAppMethodModal').classList.add('active');
  8758. }
  8759. // 隐藏创建应用方式选择弹窗
  8760. function hideCreateAppMethodModal() {
  8761. document.getElementById('createAppMethodModal').classList.remove('active');
  8762. }
  8763. // 点击遮罩关闭
  8764. document.getElementById('createAppMethodModal').addEventListener('click', function(e) {
  8765. if (e.target === this) {
  8766. hideCreateAppMethodModal();
  8767. }
  8768. });
  8769. // 选择创建方式
  8770. function selectCreateMethod(method) {
  8771. hideCreateAppMethodModal();
  8772. if (method === 'blank') {
  8773. // 创建空白应用
  8774. alert('创建空白应用页面');
  8775. // 实际应用中应创建包含空白应用画布的新页面
  8776. } else if (method === 'ai') {
  8777. // 打开AI创建界面
  8778. openAICreateInterface();
  8779. }
  8780. }
  8781. // 打开AI创建界面
  8782. function openAICreateInterface() {
  8783. document.getElementById('aiCreateAppModal').classList.add('active');
  8784. initAIChat();
  8785. }
  8786. // 关闭AI创建应用浮窗
  8787. function closeAICreateModal() {
  8788. document.getElementById('aiCreateAppModal').classList.remove('active');
  8789. chatMessages = [];
  8790. }
  8791. // 点击遮罩关闭
  8792. document.getElementById('aiCreateAppModal').addEventListener('click', function(e) {
  8793. if (e.target === this) {
  8794. closeAICreateModal();
  8795. }
  8796. });
  8797. // 初始化AI对话
  8798. function initAIChat() {
  8799. chatMessages = [
  8800. {
  8801. id: 'msg-' + Date.now(),
  8802. role: 'ai',
  8803. content: '您好!我将帮您创建AI应用。请描述您想要的应用功能,例如:"创建一个数学口算练习应用"。'
  8804. }
  8805. ];
  8806. renderChatMessages();
  8807. }
  8808. // 渲染对话消息
  8809. function renderChatMessages() {
  8810. const chatMessagesContainer = document.getElementById('aiChatMessages');
  8811. chatMessagesContainer.innerHTML = chatMessages.map(msg => `
  8812. <div class="ai-chat-message ${msg.role}">
  8813. <div class="chat-avatar">${msg.role === 'ai' ? 'AI' : 'U'}</div>
  8814. <div class="chat-bubble">${msg.content}</div>
  8815. </div>
  8816. `).join('');
  8817. // 滚动到底部
  8818. chatMessagesContainer.scrollTop = chatMessagesContainer.scrollHeight;
  8819. }
  8820. // 发送消息给AI
  8821. function sendMessageToAI() {
  8822. const input = document.getElementById('aiChatInput');
  8823. const message = input.value.trim();
  8824. if (!message) return;
  8825. // 添加用户消息
  8826. chatMessages.push({
  8827. id: 'msg-' + Date.now(),
  8828. role: 'user',
  8829. content: message
  8830. });
  8831. input.value = '';
  8832. renderChatMessages();
  8833. // 模拟AI回复
  8834. setTimeout(() => {
  8835. const aiResponse = generateAIResponse(message);
  8836. chatMessages.push({
  8837. id: 'msg-' + Date.now(),
  8838. role: 'ai',
  8839. content: aiResponse
  8840. });
  8841. renderChatMessages();
  8842. // 更新画布预览(模拟)
  8843. updateAppCanvasPreview(message);
  8844. }, 1000);
  8845. }
  8846. // 生成AI回复(模拟)
  8847. function generateAIResponse(userMessage) {
  8848. if (userMessage.includes('数学') || userMessage.includes('计算')) {
  8849. return '好的!我将为您创建一个数学口算练习应用。\n\n应用将包含:\n1. 题目生成器(支持加减乘除)\n2. 答题输入框\n3. 即时反馈\n4. 分数统计\n\n您可以继续描述更多需求,或点击"确认创建"完成。';
  8850. } else if (userMessage.includes('单词') || userMessage.includes('英语')) {
  8851. return '明白了!我将创建一个英语单词学习应用。\n\n功能包括:\n1. 单词卡片展示\n2. 中英文切换\n3. 发音功能\n4. 记忆进度追踪\n\n请告诉我还需要什么功能?';
  8852. } else {
  8853. return '收到您的需求!我正在为您设计应用界面。\n\n您可以继续补充功能要求,或者查看左侧预览区的效果。满意的话,点击"确认创建"即可添加到课件中。';
  8854. }
  8855. }
  8856. // 更新应用画布预览(模拟)
  8857. function updateAppCanvasPreview(userMessage) {
  8858. const canvas = document.getElementById('appCanvasPreview');
  8859. if (userMessage.includes('数学') || userMessage.includes('计算')) {
  8860. canvas.innerHTML = `
  8861. <div style="background: white; padding: 20px; border-radius: 12px; width: 80%; text-align: center;">
  8862. <div style="font-size: 32px; font-weight: 600; color: #111827; margin-bottom: 20px;">
  8863. 25 + 17 = ?
  8864. </div>
  8865. <input type="text" style="width: 200px; padding: 12px; border: 2px solid #e5e7eb; border-radius: 10px; font-size: 18px; text-align: center;" placeholder="输入答案">
  8866. <div style="margin-top: 16px;">
  8867. <button style="padding: 10px 30px; background: #285cf5; color: white; border: none; border-radius: 10px; font-size: 14px; font-weight: 600; cursor: pointer;">提交</button>
  8868. </div>
  8869. </div>
  8870. `;
  8871. } else if (userMessage.includes('单词') || userMessage.includes('英语')) {
  8872. canvas.innerHTML = `
  8873. <div style="background: white; padding: 30px; border-radius: 12px; width: 80%; text-align: center;">
  8874. <div style="font-size: 40px; font-weight: 700; color: #111827; margin-bottom: 12px;">
  8875. Apple
  8876. </div>
  8877. <div style="font-size: 18px; color: #6b7280; margin-bottom: 20px;">
  8878. 苹果
  8879. </div>
  8880. <button style="padding: 10px 30px; background: #3b82f6; color: white; border: none; border-radius: 10px; font-size: 14px; font-weight: 600; cursor: pointer;">🔊 发音</button>
  8881. </div>
  8882. `;
  8883. } else {
  8884. canvas.innerHTML = `
  8885. <div style="background: white; padding: 40px; border-radius: 12px; width: 80%; text-align: center; color: #9ca3af;">
  8886. <svg width="64" height="64" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" style="margin-bottom: 12px;">
  8887. <rect x="3" y="3" width="18" height="18" rx="2" ry="2"/>
  8888. <circle cx="8.5" cy="8.5" r="1.5"/>
  8889. <polyline points="21 15 16 10 5 21"/>
  8890. </svg>
  8891. <div style="font-size: 14px;">应用界面预览</div>
  8892. </div>
  8893. `;
  8894. }
  8895. }
  8896. // 确认创建AI应用
  8897. function confirmAICreatedApp() {
  8898. alert('AI应用已创建!\n\n新的应用页面已添加到课件中。');
  8899. closeAICreateModal();
  8900. }
  8901. // 监听AI对话输入框的Enter键
  8902. document.getElementById('aiChatInput').addEventListener('keydown', function(e) {
  8903. if (e.key === 'Enter' && !e.shiftKey) {
  8904. e.preventDefault();
  8905. sendMessageToAI();
  8906. }
  8907. });
  8908. // ========== 交互网页功能 ==========
  8909. let selectedWebs = [];
  8910. let webFilters = { source: 'all', subject: 'all', grade: 'all' };
  8911. let currentWebDetail = null;
  8912. let selectedWebFile = null;
  8913. // 切换交互网页的内部视图 (list, upload, crawl)
  8914. function switchToWebView(viewType) {
  8915. const webListView = document.getElementById('webListView');
  8916. const webConfigView = document.getElementById('webConfigView');
  8917. const uploadWebView = document.getElementById('uploadWebView');
  8918. const crawlWebView = document.getElementById('crawlWebView');
  8919. // 先隐藏所有主视图和配置视图
  8920. webListView.classList.add('hidden');
  8921. webConfigView.classList.add('hidden');
  8922. uploadWebView.classList.add('hidden');
  8923. crawlWebView.classList.add('hidden');
  8924. if (viewType === 'list') {
  8925. webListView.classList.remove('hidden');
  8926. } else if (viewType === 'upload') {
  8927. webConfigView.classList.remove('hidden');
  8928. uploadWebView.classList.remove('hidden');
  8929. } else if (viewType === 'crawl') {
  8930. webConfigView.classList.remove('hidden');
  8931. crawlWebView.classList.remove('hidden');
  8932. }
  8933. }
  8934. // 从配置界面返回列表
  8935. function backToWebList() {
  8936. switchToWebView('list');
  8937. }
  8938. // 打开上传网页视图
  8939. function uploadWebPage() {
  8940. switchToWebView('upload');
  8941. resetUploadForm();
  8942. }
  8943. // 打开爬取网页视图
  8944. function crawlWebPage() {
  8945. switchToWebView('crawl');
  8946. // 重置表单
  8947. document.getElementById('crawlWebName').value = '';
  8948. document.getElementById('crawlWebUrl').value = '';
  8949. document.getElementById('startCrawlBtn').disabled = true;
  8950. updateCrawlStatus('waiting', '等待输入...', '');
  8951. }
  8952. let currentUploadMode = 'file'; // 'file' or 'code'
  8953. function switchUploadTab(mode) {
  8954. currentUploadMode = mode;
  8955. const fileTab = document.getElementById('uploadFileTab');
  8956. const codeTab = document.getElementById('pasteCodeTab');
  8957. const filePanel = document.getElementById('uploadFilePanel');
  8958. const codePanel = document.getElementById('pasteCodePanel');
  8959. if (mode === 'file') {
  8960. fileTab.classList.add('active');
  8961. codeTab.classList.remove('active');
  8962. filePanel.classList.remove('hidden');
  8963. codePanel.classList.add('hidden');
  8964. } else {
  8965. fileTab.classList.remove('active');
  8966. codeTab.classList.add('active');
  8967. filePanel.classList.add('hidden');
  8968. codePanel.classList.remove('hidden');
  8969. }
  8970. validateUploadForm();
  8971. }
  8972. function handleFileSelect(event) {
  8973. const file = event.target.files[0];
  8974. const fileNameDisplay = document.getElementById('fileNameDisplay');
  8975. const fileUploadArea = document.getElementById('fileUploadArea');
  8976. if (!file) {
  8977. fileNameDisplay.textContent = '';
  8978. fileNameDisplay.style.display = 'none';
  8979. fileUploadArea.style.display = 'flex';
  8980. selectedWebFile = null;
  8981. validateUploadForm();
  8982. return;
  8983. }
  8984. selectedWebFile = file;
  8985. fileNameDisplay.textContent = '已选择文件: ' + file.name;
  8986. fileNameDisplay.style.display = 'block';
  8987. fileUploadArea.style.display = 'none';
  8988. // 自动填充网页名称
  8989. const fileName = file.name.replace(/\.[^/.]+$/, "");
  8990. if (!document.getElementById('uploadWebName').value) {
  8991. document.getElementById('uploadWebName').value = fileName;
  8992. }
  8993. // 重新校验按钮状态
  8994. validateUploadForm();
  8995. }
  8996. function validateUploadForm() {
  8997. const webName = document.getElementById('uploadWebName').value.trim();
  8998. const fileInput = document.getElementById('fileInput').files[0];
  8999. const codeInput = document.getElementById('pasteCodeTextarea').value.trim();
  9000. const confirmBtn = document.getElementById('confirmCreateWebBtn');
  9001. let isModeValid = false;
  9002. if (currentUploadMode === 'file') {
  9003. isModeValid = !!fileInput;
  9004. } else { // mode === 'code'
  9005. isModeValid = codeInput.length > 0;
  9006. }
  9007. if (webName.length > 0 && isModeValid) {
  9008. confirmBtn.disabled = false;
  9009. updateUploadButtonStatus('ready', '开始上传', 'ready');
  9010. } else {
  9011. confirmBtn.disabled = true;
  9012. updateUploadButtonStatus('waiting', '等待上传...', '');
  9013. }
  9014. }
  9015. function confirmCreateWebPage() {
  9016. if (currentUploadMode === 'file') {
  9017. startUploadWeb();
  9018. } else {
  9019. startRecognizeCode();
  9020. }
  9021. }
  9022. function startRecognizeCode() {
  9023. const uploadBtn = document.getElementById('confirmCreateWebBtn');
  9024. uploadBtn.disabled = true;
  9025. updateUploadButtonStatus('loading', '解析中...', 'loading');
  9026. const code = document.getElementById('pasteCodeTextarea').value;
  9027. console.log('Recognizing code:', code);
  9028. setTimeout(() => {
  9029. updateUploadButtonStatus('success', '解析完成!', 'success');
  9030. // Mock success
  9031. addWebToPage({ name: document.getElementById('uploadWebName').value, type: 'pasted' });
  9032. setTimeout(() => {
  9033. resetUploadForm();
  9034. backToWebList();
  9035. }, 800);
  9036. }, 800);
  9037. }
  9038. // 模拟网页数据
  9039. const sampleWebs = [
  9040. {
  9041. id: 'web_001',
  9042. name: '化学实验-酸碱中和反应',
  9043. source: 'public',
  9044. subject: '化学',
  9045. grade: '初中',
  9046. description: '通过交互式动画展示酸碱中和反应的全过程,学生可以自主调整反应物浓度',
  9047. preview: '',
  9048. url: 'https://example.com/chem-001',
  9049. duration: '10分钟',
  9050. author: '张老师',
  9051. createTime: '2024-01-15'
  9052. },
  9053. {
  9054. id: 'web_002',
  9055. name: '物理模拟-牛顿摆',
  9056. source: 'public',
  9057. subject: '物理',
  9058. grade: '高中',
  9059. description: '3D交互式牛顿摆模拟,可调整球数、质量和初速度',
  9060. preview: '',
  9061. url: 'https://example.com/physics-001',
  9062. duration: '15分钟',
  9063. author: '李老师',
  9064. createTime: '2024-01-10'
  9065. },
  9066. {
  9067. id: 'web_003',
  9068. name: '数学可视化-函数图像',
  9069. source: 'mine',
  9070. subject: '数学',
  9071. grade: '高中',
  9072. description: '动态函数图像绘制工具,支持多种函数类型',
  9073. preview: '',
  9074. url: 'https://example.com/math-001',
  9075. duration: '20分钟',
  9076. author: '我',
  9077. createTime: '2024-01-20'
  9078. },
  9079. {
  9080. id: 'web_004',
  9081. name: '地理探索-地球仪3D',
  9082. source: 'public',
  9083. subject: '地理',
  9084. grade: '初中',
  9085. description: '交互式3D地球仪,可查看地形、气候、人口等多种数据层',
  9086. preview: '',
  9087. url: 'https://example.com/geo-001',
  9088. duration: '12分钟',
  9089. author: '王老师',
  9090. createTime: '2024-01-18'
  9091. },
  9092. {
  9093. id: 'web_005',
  9094. name: '生物模型-细胞结构',
  9095. source: 'public',
  9096. subject: '生物',
  9097. grade: '初中',
  9098. description: '3D细胞结构模型,可放大查看各细胞器的详细结构',
  9099. preview: '',
  9100. url: 'https://example.com/bio-001',
  9101. duration: '8分钟',
  9102. author: '赵老师',
  9103. createTime: '2024-01-12'
  9104. },
  9105. {
  9106. id: 'web_006',
  9107. name: '历史时间轴-中国近代史',
  9108. source: 'mine',
  9109. subject: '历史',
  9110. grade: '高中',
  9111. description: '交互式历史时间轴,包含重要事件、人物和影响',
  9112. preview: '',
  9113. url: 'https://example.com/history-001',
  9114. duration: '25分钟',
  9115. author: '我',
  9116. createTime: '2024-01-22'
  9117. }
  9118. ];
  9119. // ========== 网页中心功能 ==========
  9120. // 打开网页中心
  9121. function openWebCenter() {
  9122. document.getElementById('webCenterModal').classList.add('active');
  9123. renderWebGrid();
  9124. }
  9125. // 关闭网页中心
  9126. function closeWebCenter() {
  9127. document.getElementById('webCenterModal').classList.remove('active');
  9128. selectedWebs = [];
  9129. updateWebSelectedCount();
  9130. }
  9131. // 点击遮罩关闭
  9132. document.getElementById('webCenterModal').addEventListener('click', function(e) {
  9133. if (e.target === this) {
  9134. closeWebCenter();
  9135. }
  9136. });
  9137. // 筛选网页
  9138. function filterWebs() {
  9139. webFilters.source = document.getElementById('webSourceFilter').value;
  9140. webFilters.subject = document.getElementById('webSubjectFilter').value;
  9141. webFilters.grade = document.getElementById('webGradeFilter').value;
  9142. renderWebGrid();
  9143. }
  9144. // 渲染网页网格
  9145. function renderWebGrid() {
  9146. const webGrid = document.getElementById('webGrid');
  9147. // 过滤网页
  9148. let filteredWebs = sampleWebs.filter(web => {
  9149. if (webFilters.source !== 'all' && web.source !== webFilters.source) return false;
  9150. if (webFilters.subject !== 'all' && web.subject !== webFilters.subject) return false;
  9151. if (webFilters.grade !== 'all' && web.grade !== webFilters.grade) return false;
  9152. return true;
  9153. });
  9154. // 生成HTML
  9155. webGrid.innerHTML = filteredWebs.map(web => `
  9156. <div class="web-card ${selectedWebs.includes(web.id) ? 'selected' : ''}"
  9157. onclick="toggleWebSelection('${web.id}')">
  9158. <div class="web-preview">
  9159. <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
  9160. <rect x="2" y="3" width="20" height="14" rx="2"/>
  9161. <line x1="2" y1="7" x2="22" y2="7"/>
  9162. <path d="M8 11h8M8 14h5"/>
  9163. </svg>
  9164. </div>
  9165. <div class="web-info">
  9166. <div class="web-name">${web.name}</div>
  9167. <div class="web-description">${web.description}</div>
  9168. <div class="web-meta">
  9169. <span class="web-tag">${web.subject}</span>
  9170. <span class="web-tag">${web.grade}</span>
  9171. <span class="web-tag">${web.duration}</span>
  9172. </div>
  9173. </div>
  9174. </div>
  9175. `).join('');
  9176. }
  9177. // 切换网页选中状态
  9178. function toggleWebSelection(webId) {
  9179. const index = selectedWebs.indexOf(webId);
  9180. if (index > -1) {
  9181. selectedWebs.splice(index, 1);
  9182. } else {
  9183. selectedWebs.push(webId);
  9184. }
  9185. renderWebGrid();
  9186. updateWebSelectedCount();
  9187. }
  9188. // 更新选中计数
  9189. function updateWebSelectedCount() {
  9190. const countText = document.getElementById('webSelectedCountText');
  9191. const confirmBtn = document.getElementById('confirmAddWebsBtn');
  9192. countText.textContent = selectedWebs.length;
  9193. confirmBtn.disabled = selectedWebs.length === 0;
  9194. }
  9195. // 确认添加网页
  9196. function confirmAddWebs() {
  9197. if (selectedWebs.length === 0) return;
  9198. console.log('添加网页:', selectedWebs);
  9199. alert(`已添加 ${selectedWebs.length} 个网页到课件`);
  9200. // 这里应该实际创建新页面并插入网页内容
  9201. // 暂时模拟功能
  9202. closeWebCenter();
  9203. }
  9204. // 打开网页详情
  9205. function openWebDetail(webId) {
  9206. const web = sampleWebs.find(w => w.id === webId);
  9207. if (!web) return;
  9208. currentWebDetail = web;
  9209. // 填充详情信息
  9210. document.getElementById('webDetailName').textContent = web.name;
  9211. document.getElementById('webDetailSubject').textContent = web.subject;
  9212. document.getElementById('webDetailGrade').textContent = web.grade;
  9213. document.getElementById('webDetailDuration').textContent = web.duration;
  9214. document.getElementById('webDetailDescription').textContent = web.description;
  9215. document.getElementById('webDetailAuthor').textContent = web.author;
  9216. document.getElementById('webDetailTime').textContent = web.createTime;
  9217. document.getElementById('webDetailPreviewTitle').textContent = web.name;
  9218. document.getElementById('webDetailIframe').src = web.url;
  9219. // 显示详情浮窗
  9220. document.getElementById('webDetailModal').classList.add('active');
  9221. }
  9222. // 关闭网页详情
  9223. function closeWebDetail() {
  9224. document.getElementById('webDetailModal').classList.remove('active');
  9225. document.getElementById('webDetailIframe').src = '';
  9226. currentWebDetail = null;
  9227. }
  9228. // 点击遮罩关闭详情
  9229. document.getElementById('webDetailModal').addEventListener('click', function(e) {
  9230. if (e.target === this) {
  9231. closeWebDetail();
  9232. }
  9233. });
  9234. // 切换预览全屏
  9235. function toggleWebPreviewFullscreen() {
  9236. const iframe = document.getElementById('webDetailIframe');
  9237. if (iframe.requestFullscreen) {
  9238. iframe.requestFullscreen();
  9239. } else if (iframe.webkitRequestFullscreen) {
  9240. iframe.webkitRequestFullscreen();
  9241. } else if (iframe.mozRequestFullScreen) {
  9242. iframe.mozRequestFullScreen();
  9243. } else if (iframe.msRequestFullscreen) {
  9244. iframe.msRequestFullscreen();
  9245. }
  9246. }
  9247. // 从详情页添加网页
  9248. function addWebFromDetail() {
  9249. if (!currentWebDetail) return;
  9250. console.log('从详情页添加网页:', currentWebDetail);
  9251. alert(`已添加"${currentWebDetail.name}"到课件`);
  9252. // 这里应该实际创建新页面并插入网页内容
  9253. closeWebDetail();
  9254. closeWebCenter();
  9255. }
  9256. // ========== 上传网页功能 ==========
  9257. // 开始上传网页
  9258. function startUploadWeb() {
  9259. const webName = document.getElementById('uploadWebName').value.trim();
  9260. if (!webName) {
  9261. alert('请输入网页名称');
  9262. return;
  9263. }
  9264. if (!selectedWebFile) {
  9265. alert('请选择文件');
  9266. return;
  9267. }
  9268. // 显示解析中状态
  9269. const uploadBtn = document.getElementById('confirmCreateWebBtn');
  9270. uploadBtn.disabled = true;
  9271. updateUploadButtonStatus('loading', '解析中...', 'loading');
  9272. // 模拟解析过程
  9273. setTimeout(() => {
  9274. updateUploadButtonStatus('success', '解析完成!', 'success');
  9275. // 2秒后创建页面并显示内容
  9276. setTimeout(() => {
  9277. console.log('创建新页面并插入网页内容:', webName);
  9278. alert(`网页"${webName}"已成功添加到课件`);
  9279. resetUploadForm();
  9280. returnFromUpload();
  9281. }, 2000);
  9282. }, 2000);
  9283. }
  9284. function resetUploadForm() {
  9285. const uploadBtn = document.getElementById('confirmCreateWebBtn');
  9286. document.getElementById('uploadWebName').value = '';
  9287. document.getElementById('fileInput').value = '';
  9288. document.getElementById('pasteCodeTextarea').value = '';
  9289. const fileNameDisplay = document.getElementById('fileNameDisplay');
  9290. const fileUploadArea = document.getElementById('fileUploadArea');
  9291. fileNameDisplay.textContent = '';
  9292. fileNameDisplay.style.display = 'none';
  9293. fileUploadArea.style.display = 'flex';
  9294. selectedWebFile = null;
  9295. uploadBtn.disabled = true;
  9296. updateUploadButtonStatus('waiting', '等待上传...', '');
  9297. }
  9298. // 返回列表视图
  9299. function returnFromUpload() {
  9300. switchToWebView('list');
  9301. }
  9302. // 更新上传按钮状态
  9303. function updateUploadButtonStatus(statusKey, text, className) {
  9304. const uploadBtn = document.getElementById('confirmCreateWebBtn');
  9305. const statusIcon = document.getElementById('uploadStatusIcon');
  9306. const statusText = document.getElementById('uploadStatusText');
  9307. uploadBtn.classList.remove('status-loading', 'status-success', 'status-error', 'status-ready');
  9308. if (className) {
  9309. uploadBtn.classList.add(`status-${className}`);
  9310. }
  9311. statusIcon.innerHTML = statusIcons[statusKey] || statusIcons.waiting;
  9312. statusText.textContent = text;
  9313. }
  9314. // 文件拖拽上传
  9315. const fileUploadArea = document.getElementById('fileUploadArea');
  9316. fileUploadArea.addEventListener('dragover', (e) => {
  9317. e.preventDefault();
  9318. fileUploadArea.classList.add('dragover');
  9319. });
  9320. fileUploadArea.addEventListener('dragleave', () => {
  9321. fileUploadArea.classList.remove('dragover');
  9322. });
  9323. fileUploadArea.addEventListener('drop', (e) => {
  9324. e.preventDefault();
  9325. fileUploadArea.classList.remove('dragover');
  9326. const files = e.dataTransfer.files;
  9327. if (files.length > 0) {
  9328. const file = files[0];
  9329. // 检查文件类型
  9330. const validTypes = ['.html', '.htm', '.zip'];
  9331. const fileExt = '.' + file.name.split('.').pop().toLowerCase();
  9332. if (validTypes.includes(fileExt)) {
  9333. document.getElementById('fileInput').files = files;
  9334. handleFileSelect({ target: { files: files } });
  9335. } else {
  9336. alert('仅支持 HTML、HTM、ZIP 格式文件');
  9337. }
  9338. }
  9339. });
  9340. // ========== 交互网页按钮状态图标 ==========
  9341. const statusIcons = {
  9342. 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>',
  9343. 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>',
  9344. 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>',
  9345. 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>',
  9346. 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>'
  9347. };
  9348. // 验证爬取URL
  9349. function validateCrawlUrl() {
  9350. const url = document.getElementById('crawlWebUrl').value.trim();
  9351. const name = document.getElementById('crawlWebName').value.trim();
  9352. const startBtn = document.getElementById('startCrawlBtn');
  9353. if (url && name && isValidUrl(url)) {
  9354. startBtn.disabled = false;
  9355. updateCrawlStatus('ready', '开始爬取', 'ready');
  9356. } else if (url && !isValidUrl(url)) {
  9357. startBtn.disabled = true;
  9358. updateCrawlStatus('error', 'URL格式不正确,请检查', 'error');
  9359. } else {
  9360. startBtn.disabled = true;
  9361. updateCrawlStatus('waiting', '等待输入...', '');
  9362. }
  9363. }
  9364. // 验证URL格式
  9365. function isValidUrl(string) {
  9366. try {
  9367. const url = new URL(string);
  9368. return url.protocol === 'http:' || url.protocol === 'https:';
  9369. } catch (_) {
  9370. return false;
  9371. }
  9372. }
  9373. // 开始爬取网页
  9374. function startCrawlWeb() {
  9375. const webName = document.getElementById('crawlWebName').value.trim();
  9376. const webUrl = document.getElementById('crawlWebUrl').value.trim();
  9377. const startBtn = document.getElementById('startCrawlBtn');
  9378. if (!webName) {
  9379. alert('请输入网页名称');
  9380. return;
  9381. }
  9382. if (!webUrl || !isValidUrl(webUrl)) {
  9383. alert('请输入有效的网页链接');
  9384. return;
  9385. }
  9386. // 显示爬取中状态
  9387. startBtn.disabled = true;
  9388. updateCrawlStatus('loading', '爬取中...', 'loading');
  9389. // 模拟爬取过程
  9390. setTimeout(() => {
  9391. updateCrawlStatus('success', '爬取完成!', 'success');
  9392. setTimeout(() => {
  9393. resetCrawlForm();
  9394. }, 1200);
  9395. }, 2500);
  9396. }
  9397. function resetCrawlForm() {
  9398. const startBtn = document.getElementById('startCrawlBtn');
  9399. const nameInput = document.getElementById('crawlWebName');
  9400. const urlInput = document.getElementById('crawlWebUrl');
  9401. nameInput.value = '';
  9402. urlInput.value = '';
  9403. startBtn.disabled = true;
  9404. updateCrawlStatus('waiting', '等待输入...', '');
  9405. }
  9406. // 返回列表视图(保留,未使用)
  9407. function returnFromCrawl() {
  9408. switchToWebView('list');
  9409. }
  9410. // 更新爬取状态
  9411. function updateCrawlStatus(statusKey, text, className) {
  9412. const startBtn = document.getElementById('startCrawlBtn');
  9413. const statusIcon = document.getElementById('crawlStatusIcon');
  9414. const statusText = document.getElementById('crawlStatusText');
  9415. startBtn.classList.remove('status-loading', 'status-success', 'status-error', 'status-ready');
  9416. if (className) {
  9417. startBtn.classList.add(`status-${className}`);
  9418. }
  9419. statusIcon.innerHTML = statusIcons[statusKey] || statusIcons.waiting;
  9420. statusText.textContent = text;
  9421. }
  9422. </script>
  9423. </body>
  9424. </html>