| 12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694169516961697169816991700170117021703170417051706170717081709171017111712171317141715171617171718171917201721172217231724172517261727172817291730173117321733173417351736173717381739174017411742174317441745174617471748174917501751175217531754175517561757175817591760176117621763176417651766176717681769177017711772177317741775177617771778177917801781178217831784178517861787178817891790179117921793179417951796179717981799180018011802180318041805180618071808180918101811181218131814181518161817181818191820182118221823182418251826182718281829183018311832183318341835183618371838183918401841184218431844184518461847184818491850185118521853185418551856185718581859186018611862186318641865186618671868186918701871187218731874187518761877187818791880188118821883188418851886188718881889189018911892189318941895189618971898189919001901190219031904190519061907190819091910191119121913191419151916191719181919192019211922192319241925192619271928192919301931193219331934193519361937193819391940194119421943194419451946194719481949195019511952195319541955195619571958195919601961196219631964196519661967196819691970197119721973197419751976197719781979198019811982198319841985198619871988198919901991199219931994199519961997199819992000200120022003200420052006200720082009201020112012201320142015201620172018201920202021202220232024202520262027202820292030203120322033203420352036203720382039204020412042204320442045204620472048204920502051205220532054205520562057205820592060206120622063206420652066206720682069207020712072207320742075207620772078207920802081208220832084208520862087208820892090209120922093209420952096209720982099210021012102210321042105210621072108210921102111211221132114211521162117211821192120212121222123212421252126212721282129213021312132213321342135213621372138213921402141214221432144214521462147214821492150215121522153215421552156215721582159216021612162216321642165216621672168216921702171217221732174217521762177217821792180218121822183218421852186218721882189219021912192219321942195219621972198219922002201220222032204220522062207220822092210221122122213221422152216221722182219222022212222222322242225222622272228222922302231223222332234223522362237223822392240224122422243224422452246224722482249225022512252225322542255225622572258225922602261226222632264226522662267226822692270227122722273227422752276227722782279228022812282228322842285228622872288228922902291229222932294229522962297229822992300230123022303230423052306230723082309231023112312231323142315231623172318231923202321232223232324232523262327232823292330233123322333233423352336233723382339234023412342234323442345234623472348234923502351235223532354235523562357235823592360236123622363236423652366236723682369237023712372237323742375237623772378237923802381238223832384238523862387238823892390239123922393239423952396239723982399240024012402240324042405240624072408240924102411241224132414241524162417241824192420242124222423242424252426242724282429243024312432243324342435243624372438243924402441244224432444244524462447244824492450245124522453245424552456245724582459246024612462246324642465246624672468246924702471247224732474247524762477247824792480248124822483248424852486248724882489249024912492249324942495249624972498249925002501250225032504250525062507250825092510251125122513251425152516251725182519252025212522252325242525252625272528252925302531253225332534253525362537253825392540254125422543254425452546254725482549255025512552255325542555255625572558255925602561256225632564256525662567256825692570257125722573257425752576257725782579258025812582258325842585258625872588258925902591259225932594259525962597259825992600260126022603260426052606260726082609261026112612261326142615261626172618261926202621262226232624262526262627262826292630263126322633263426352636263726382639264026412642264326442645264626472648264926502651265226532654265526562657265826592660266126622663266426652666266726682669267026712672267326742675267626772678267926802681268226832684268526862687268826892690269126922693269426952696269726982699270027012702270327042705270627072708270927102711271227132714271527162717271827192720272127222723272427252726272727282729273027312732273327342735273627372738273927402741274227432744274527462747274827492750275127522753275427552756275727582759276027612762276327642765276627672768276927702771277227732774277527762777277827792780278127822783278427852786278727882789279027912792279327942795279627972798279928002801280228032804280528062807280828092810281128122813281428152816281728182819282028212822282328242825282628272828282928302831283228332834283528362837283828392840284128422843284428452846284728482849285028512852285328542855285628572858285928602861286228632864286528662867286828692870287128722873287428752876287728782879288028812882288328842885288628872888288928902891289228932894289528962897289828992900290129022903290429052906290729082909291029112912291329142915291629172918291929202921292229232924292529262927292829292930293129322933293429352936293729382939294029412942294329442945294629472948294929502951295229532954295529562957295829592960296129622963296429652966296729682969297029712972297329742975297629772978297929802981298229832984298529862987298829892990299129922993299429952996299729982999300030013002300330043005300630073008300930103011301230133014301530163017301830193020302130223023302430253026302730283029303030313032303330343035303630373038303930403041304230433044304530463047304830493050305130523053305430553056305730583059306030613062306330643065306630673068306930703071307230733074307530763077307830793080308130823083308430853086308730883089309030913092309330943095309630973098309931003101310231033104310531063107310831093110311131123113311431153116311731183119312031213122312331243125312631273128312931303131313231333134313531363137313831393140314131423143314431453146314731483149315031513152315331543155315631573158315931603161316231633164316531663167316831693170317131723173317431753176317731783179318031813182318331843185318631873188318931903191319231933194319531963197319831993200320132023203320432053206320732083209321032113212321332143215321632173218321932203221322232233224322532263227322832293230323132323233323432353236323732383239324032413242324332443245324632473248324932503251325232533254325532563257325832593260326132623263326432653266326732683269327032713272327332743275327632773278327932803281328232833284328532863287328832893290329132923293329432953296329732983299330033013302330333043305330633073308330933103311331233133314331533163317331833193320332133223323332433253326332733283329333033313332333333343335333633373338333933403341334233433344334533463347334833493350335133523353335433553356335733583359336033613362336333643365336633673368336933703371337233733374337533763377337833793380338133823383338433853386338733883389339033913392339333943395339633973398339934003401340234033404340534063407340834093410341134123413341434153416341734183419342034213422342334243425342634273428342934303431343234333434343534363437343834393440344134423443344434453446344734483449345034513452345334543455345634573458345934603461346234633464346534663467346834693470347134723473347434753476347734783479348034813482348334843485348634873488348934903491349234933494349534963497349834993500350135023503350435053506350735083509351035113512351335143515351635173518351935203521352235233524352535263527352835293530353135323533353435353536353735383539354035413542354335443545354635473548354935503551355235533554355535563557355835593560356135623563356435653566356735683569357035713572357335743575357635773578357935803581358235833584358535863587358835893590359135923593359435953596359735983599360036013602360336043605360636073608360936103611361236133614361536163617361836193620362136223623362436253626362736283629363036313632363336343635363636373638363936403641364236433644364536463647364836493650365136523653365436553656365736583659366036613662366336643665366636673668366936703671367236733674367536763677367836793680368136823683368436853686368736883689369036913692369336943695369636973698369937003701370237033704370537063707370837093710371137123713371437153716371737183719372037213722372337243725372637273728372937303731373237333734373537363737373837393740374137423743374437453746374737483749375037513752375337543755375637573758375937603761376237633764376537663767376837693770377137723773377437753776377737783779378037813782378337843785378637873788378937903791379237933794379537963797379837993800380138023803380438053806380738083809381038113812381338143815381638173818381938203821382238233824382538263827382838293830383138323833383438353836383738383839384038413842384338443845384638473848384938503851385238533854385538563857385838593860386138623863386438653866386738683869387038713872387338743875387638773878387938803881388238833884388538863887388838893890389138923893389438953896389738983899390039013902390339043905390639073908390939103911391239133914391539163917391839193920392139223923392439253926392739283929393039313932393339343935393639373938393939403941394239433944394539463947394839493950395139523953395439553956395739583959396039613962396339643965396639673968396939703971397239733974397539763977397839793980398139823983398439853986398739883989399039913992399339943995399639973998399940004001400240034004400540064007400840094010401140124013401440154016401740184019402040214022402340244025402640274028402940304031403240334034403540364037403840394040404140424043404440454046404740484049405040514052405340544055405640574058405940604061406240634064406540664067406840694070407140724073407440754076407740784079408040814082408340844085408640874088408940904091409240934094409540964097409840994100410141024103410441054106410741084109411041114112411341144115411641174118411941204121412241234124412541264127412841294130413141324133413441354136413741384139 |
- <!DOCTYPE html>
- <html lang="zh-CN">
- <head>
- <meta charset="UTF-8">
- <meta name="viewport" content="width=device-width, initial-scale=1.0">
- <title>灵越智报 - 智能报告生成平台</title>
- <link href="https://fonts.googleapis.com/css2?family=Noto+Sans+SC:wght@400;500;600;700&display=swap" rel="stylesheet">
- <!-- 引入 iconfont 项目(已替换) -->
- <link rel="stylesheet" href="http://at.alicdn.com/t/c/font_2630279_pgouo6fulk.css">
- <style>
- :root {
- --primary: #1890ff;
- --primary-dark: #096dd9;
- --primary-light: #e6f7ff;
- --primary-gradient: linear-gradient(135deg, #1890ff 0%, #096dd9 100%);
- --white: #ffffff;
- --bg: #f5f7fa;
- --border: #e8e8e8;
- --text1: #262626;
- --text2: #595959;
- --text3: #8c8c8c;
- --success: #52c41a;
- --warning: #faad14;
- --danger: #ff4d4f;
- --ai-gradient: linear-gradient(135deg, #1890ff 0%, #722ed1 100%);
- --data-gradient: linear-gradient(135deg, #52c41a 0%, #13c2c2 100%);
- }
- /* JavaScript for syncing editor title was moved out of the style block to avoid invalid CSS. */
- * { margin: 0; padding: 0; box-sizing: border-box; }
- body { font-family: 'Noto Sans SC', sans-serif; background: var(--bg); color: var(--text1); font-size: 14px; line-height: 1.6; height: 100vh; overflow: hidden; }
- /* 全局头部 */
- .header { position: fixed; top: 0; left: 0; right: 0; height: 56px; background: var(--white); border-bottom: 1px solid var(--border); display: flex; align-items: center; justify-content: space-between; padding: 0 20px; z-index: 1000; }
- .header-left { display: flex; align-items: center; gap: 20px; }
- .logo { display: flex; align-items: center; gap: 8px; font-size: 17px; font-weight: 600; color: var(--primary); cursor: pointer; }
- .logo-icon { width: 32px; height: 32px; background: var(--primary-gradient); border-radius: 8px; display: flex; align-items: center; justify-content: center; color: white; font-size: 18px; }
- .search-box { position: relative; width: 320px; }
- .search-input { width: 100%; height: 36px; padding: 0 14px 0 36px; border: 1px solid var(--border); border-radius: 18px; font-size: 13px; background: var(--bg); outline: none; transition: all 0.2s; }
- .search-input:focus { background: var(--white); border-color: var(--primary); }
- .search-icon { position: absolute; left: 12px; top: 50%; transform: translateY(-50%); color: #8c8c8c; font-size: 14px; }
- .header-right { display: flex; align-items: center; gap: 10px; }
- .hd-btn { position: relative; width: 36px; height: 36px; border: none; background: transparent; border-radius: 50%; cursor: pointer; display: flex; align-items: center; justify-content: center; font-size: 18px; color: var(--text2); transition: all 0.2s; }
- .hd-btn:hover { background: var(--bg); color: var(--primary); }
- .badge { position: absolute; top: 2px; right: 2px; min-width: 16px; height: 16px; background: var(--danger); color: white; border-radius: 8px; font-size: 10px; display: flex; align-items: center; justify-content: center; }
- .user-menu { display: flex; align-items: center; gap: 6px; padding: 4px 8px 4px 4px; border-radius: 20px; cursor: pointer; }
- .user-menu:hover { background: var(--bg); }
- .avatar { width: 30px; height: 30px; border-radius: 50%; background: var(--primary-gradient); color: white; display: flex; align-items: center; justify-content: center; font-weight: 600; font-size: 12px; }
- .user-name { font-size: 13px; font-weight: 500; }
- /* 侧边栏 */
- .sidebar { position: fixed; top: 56px; left: 0; width: 240px; height: calc(100vh - 56px); background: var(--white); border-right: none; display: flex; flex-direction: column; transition: width 0.3s; z-index: 900; overflow-y: auto; }
- .sidebar.hidden { display: none; }
- .sidebar-menu { flex: 1; padding: 10px; overflow-y: auto; }
- .menu-item { display: flex; align-items: center; gap: 10px; padding: 10px 12px; border-radius: 8px; cursor: pointer; transition: all 0.2s; margin-bottom: 2px; position: relative; }
- .menu-item:hover { background: var(--primary-light); color: var(--primary); }
- .menu-item.active { background: var(--primary-light); color: var(--primary); }
- .menu-item.active::before { content: ''; position: absolute; left: 0; top: 50%; transform: translateY(-50%); width: 3px; height: 18px; background: var(--primary); border-radius: 0 2px 2px 0; }
- .menu-icon { font-size: 16px; }
- .menu-text { font-size: 13px; font-weight: 500; }
- .menu-badge { min-width: 18px; height: 18px; padding: 0 5px; background: var(--primary); color: white; border-radius: 9px; font-size: 10px; display: flex; align-items: center; justify-content: center; }
- /* 主内容 */
- .main { margin-left: 300px; margin-top: 56px; height: calc(100vh - 56px); overflow-y: auto; display:flex; flex-direction:column; }
- .page { display: none; padding: 20px; }
- .page.active { display: block; }
- /* 通用组件 */
- .card { background: var(--white); border-radius: 10px; box-shadow: 0 2px 8px rgba(0,0,0,0.06); }
- .btn { display: inline-flex; align-items: center; gap: 4px; padding: 7px 14px; border: 1px solid var(--border); background: var(--white); border-radius: 6px; cursor: pointer; font-size: 12px; transition: all 0.2s; }
- .btn:hover { border-color: var(--primary); color: var(--primary); }
- .btn-primary { background: var(--primary-gradient); color: white; border: none; }
- .btn-primary:hover { box-shadow: 0 4px 12px rgba(24,144,255,0.4); }
- /* 首页样式 */
- .welcome h1 { font-size: 24px; font-weight: 600; margin-bottom: 4px; }
- .welcome h1 span { background: var(--ai-gradient); background-clip: text; -webkit-background-clip: text; -webkit-text-fill-color: transparent; }
- .welcome p { color: var(--text2); }
- .stats-grid { display: grid; grid-template-columns: repeat(4, 1fr); gap: 16px; margin: 20px 0; }
- .stat-card { padding: 18px; cursor: pointer; transition: all 0.3s; }
- .stat-card:hover { transform: translateY(-4px); box-shadow: 0 8px 20px rgba(0,0,0,0.1); }
- .stat-icon { width: 40px; height: 40px; border-radius: 8px; display: flex; align-items: center; justify-content: center; font-size: 20px; margin-bottom: 10px; }
- .stat-icon.blue { background: linear-gradient(135deg, #e6f7ff, #bae7ff); }
- .stat-icon.purple { background: linear-gradient(135deg, #f9f0ff, #efdbff); }
- .stat-icon.green { background: linear-gradient(135deg, #f6ffed, #d9f7be); }
- .stat-icon.orange { background: linear-gradient(135deg, #fff7e6, #ffe7ba); }
- .stat-value { font-size: 26px; font-weight: 700; }
- .stat-label { font-size: 13px; color: var(--text2); margin-bottom: 6px; }
- .stat-trend { font-size: 12px; }
- .stat-trend.up { color: var(--success); }
- /* AI对话入口 */
- .ai-card { padding: 20px; margin-bottom: 20px; }
- .ai-welcome { background: linear-gradient(135deg, #f0f7ff, #f5f0ff); border-radius: 10px; padding: 16px; margin-bottom: 16px; }
- .ai-avatar { width: 44px; height: 44px; background: var(--ai-gradient); border-radius: 50%; display: flex; align-items: center; justify-content: center; font-size: 22px; margin-bottom: 10px; }
- .ai-title { font-size: 14px; font-weight: 600; margin-bottom: 8px; }
- .ai-list { list-style: none; margin-bottom: 10px; }
- .ai-list li { color: var(--text2); padding: 3px 0; font-size: 13px; }
- .ai-list li::before { content: '•'; color: var(--primary); margin-right: 8px; }
- .ai-tip { font-size: 12px; color: var(--text3); padding: 8px 12px; background: rgba(255,255,255,0.7); border-radius: 6px; border-left: 3px solid var(--primary); }
- .ai-input-wrap { position: relative; margin-bottom: 14px; }
- .ai-input { width: 100%; height: 46px; padding: 0 100px 0 18px; border: 2px solid var(--border); border-radius: 23px; font-size: 14px; outline: none; transition: all 0.3s; }
- .ai-input:focus { border-color: var(--primary); }
- .ai-input-btns { position: absolute; right: 6px; top: 50%; transform: translateY(-50%); display: flex; gap: 4px; }
- .ai-input-btn { width: 32px; height: 32px; border: none; background: transparent; border-radius: 50%; cursor: pointer; font-size: 16px; color: var(--text3); }
- .ai-input-btn:hover { background: var(--bg); color: var(--primary); }
- .ai-input-btn.send { background: var(--primary-gradient); color: white; display: none; }
- .ai-input-btn.send.show { display: flex; align-items: center; justify-content: center; }
- .thinking-modes { display: flex; gap: 8px; flex-wrap: wrap; }
- .mode-btn { padding: 6px 12px; background: var(--bg); border: 1px solid var(--border); border-radius: 18px; font-size: 12px; cursor: pointer; }
- .mode-btn:hover { border-color: var(--primary); background: var(--primary-light); }
- .mode-btn.active { background: var(--primary-light); border-color: var(--primary); color: var(--primary); }
- /* 模板区 */
- .section-header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 14px; }
- .section-title { font-size: 15px; font-weight: 600; }
- .section-link { font-size: 13px; color: var(--primary); cursor: pointer; }
- .tpl-grid { display: grid; grid-template-columns: repeat(3, 1fr); gap: 14px; margin-bottom: 14px; }
- .tpl-card { overflow: hidden; cursor: pointer; transition: all 0.3s; }
- .tpl-card:hover { transform: translateY(-4px); box-shadow: 0 8px 20px rgba(0,0,0,0.1); }
- .tpl-preview { height: 100px; background: linear-gradient(135deg, #f5f7fa, #e8ecf0); display: flex; align-items: center; justify-content: center; font-size: 36px; }
- .tpl-info { padding: 14px; }
- .tpl-name { font-weight: 600; margin-bottom: 6px; }
- .tpl-meta { display: flex; gap: 12px; font-size: 11px; color: var(--text3); margin-bottom: 10px; }
- .tpl-tags { display: flex; gap: 4px; margin-bottom: 10px; }
- .tpl-tag { padding: 2px 6px; background: var(--primary-light); color: var(--primary); border-radius: 3px; font-size: 10px; }
- .tpl-tag.hot { background: #fff1f0; color: var(--danger); }
- .tpl-btn { width: 100%; padding: 8px; background: var(--primary-gradient); color: white; border: none; border-radius: 6px; font-size: 12px; cursor: pointer; }
- .quick-actions { display: grid; grid-template-columns: repeat(2, 1fr); gap: 14px; }
- .quick-action { display: flex; align-items: center; gap: 12px; padding: 16px; border: 2px dashed var(--border); border-radius: 10px; cursor: pointer; }
- .quick-action:hover { border-color: var(--primary); background: var(--primary-light); }
- .quick-action-icon { width: 40px; height: 40px; background: var(--bg); border-radius: 8px; display: flex; align-items: center; justify-content: center; font-size: 20px; }
- /* 编辑器专用样式 */
- .editor-page { display: none; position: fixed; top: 56px; left: 0; right: 0; bottom: 0; background: var(--bg); z-index: 800; overflow: hidden; }
- .editor-page.active { display: block; }
- /* 编辑器工具栏 */
- .editor-toolbar { height: 56px; background: var(--white); border-bottom: 1px solid var(--border); display: flex; align-items: center; padding: 0 16px; gap: 16px; flex-shrink: 0; }
- .back-btn { display: flex; align-items: center; gap: 4px; padding: 8px 12px; border: none; background: transparent; border-radius: 6px; cursor: pointer; font-size: 13px; color: var(--text2); }
- .back-btn:hover { background: var(--bg); color: var(--primary); }
- .report-title-input { border: none; background: transparent; font-size: 15px; font-weight: 600; padding: 8px 12px; border-radius: 6px; min-width: 300px; outline: none; }
- .report-title-input:hover { background: var(--bg); }
- .report-title-input:focus { background: var(--white); box-shadow: 0 0 0 2px var(--primary-light); }
- .save-status { display: flex; align-items: center; gap: 4px; font-size: 13px; color: var(--success); }
- .toolbar-right { margin-left: auto; display: flex; gap: 10px; align-items: center; }
- .toolbar-btn { display: flex; align-items: center; gap: 6px; padding: 8px 16px; border: 1px solid var(--border); background: var(--white); border-radius: 6px; cursor: pointer; font-size: 13px; color: var(--text1); transition: all 0.2s; }
- .toolbar-btn:hover { border-color: var(--primary); color: var(--primary); background: var(--primary-light); }
- .toolbar-btn.primary { background: var(--primary); color: white; border: none; }
- .toolbar-btn.primary:hover { background: var(--primary-dark); }
- .toolbar-btn .icon { font-size: 14px; }
- .toolbar-divider { width: 1px; height: 24px; background: var(--border); }
- /* 编辑器主体 */
- .editor-body { flex: 1; display: flex; overflow: hidden; min-height: 0; }
- /* 当编辑器激活时,使用三列布局:左侧资源、中心编辑、右侧AI助手(与示例一致) */
- .editor-page.active .editor-body {
- display: grid;
- grid-template-columns: 300px 1fr 300px;
- gap: 16px;
- padding: 16px;
- /* ensure each column stretches to the full grid height */
- align-items: stretch;
- align-content: stretch;
- box-sizing: border-box;
- height: calc(100vh - 56px);
- /* ensure grid row(s) fill available space so children can stretch */
- grid-auto-rows: 1fr;
- }
- /* 左侧项目文件面板 */
- .left-panel { width: 300px; background: var(--white); border-right: 1px solid var(--border); display: flex; flex-direction: column; min-height: 0; overflow: hidden; height: 100%; }
- .panel-header { padding: 14px 16px; border-bottom: 1px solid var(--border); font-size: 13px; font-weight: 600; color: var(--text1); display: flex; align-items: center; justify-content: space-between; }
- .panel-header-tabs .tabs-left { display:flex; gap:8px; align-items:center; }
- .panel-tab { padding:8px 14px; border-radius:12px; border:1px solid transparent; background:transparent; cursor:pointer; font-weight:700; font-size:14px; }
- .panel-tab.active { background: var(--primary); color: #fff; border-color: rgba(0,0,0,0.04); box-shadow: 0 6px 18px rgba(17,24,39,0.06); }
- .panel-header .file-count { margin-left:8px; font-weight:500; color:var(--text3); }
- .file-count { font-size: 12px; color: var(--text3); font-weight: normal; }
- .panel-body { flex: 1; padding: 12px; display: flex; flex-direction: column; min-height: 0; }
- /* docs / recent split: top 40% docs (scrollable), bottom 60% recent (scrollable) */
- .docs-area { flex: 4; overflow-y: auto; min-height: 0; }
- .recent-area { flex: 6; overflow-y: auto; min-height: 0; margin-top: 8px; }
- /* 上传区 */
- .upload-zone { border: 2px dashed var(--border); border-radius: 12px; height:40px; padding: 0 12px; text-align: center; cursor: pointer; margin-bottom: 16px; transition: all 0.18s; display:flex; align-items:center; justify-content:center; background: var(--white); }
- .upload-zone:hover { border-color: var(--primary); background: var(--primary-light); }
- /* 文本式添加资源(居中、大按钮感) */
- .upload-zone .add-resource-text { font-size:16px; font-weight:600; color:var(--text1); padding:0 12px; line-height:40px; }
- .upload-hint { font-size: 11px; color: var(--text3); display:block; margin-top:8px; text-align:center; }
- /* 左侧面板折叠按钮 */
- .panel-toggle-btn { background: transparent; border: 1px solid var(--border); border-radius: 8px; padding:6px 8px; cursor:pointer; font-size:14px; color:var(--text2); }
- .panel-toggle-btn:hover { border-color: var(--primary); color:var(--primary); }
- /* 小浮动展开按钮(当面板折叠时显示) */
- .resource-expand-btn { position: fixed; left: 8px; top: 100px; width:40px; height:40px; border-radius:8px; background:var(--white); border:1px solid var(--border); display:flex; align-items:center; justify-content:center; z-index:1400; box-shadow:0 6px 18px rgba(0,0,0,0.08); cursor:pointer; }
- /* 在正文中隐藏来源徽章(仅编辑区不显示来源文件标识) */
- .editor-content .source-badge { display: none !important; }
- /* 新:左侧文档列表样式(图片示例风格) - 内部旧 tab 样式已清理 */
- .doc-section-header { display:flex; align-items:center; justify-content:space-between; padding:6px 4px 10px; font-size:13px; font-weight:600; color:var(--text1); }
- .badge-count { background: var(--bg); padding:2px 8px; border-radius:12px; font-size:12px; color:var(--primary); }
- .doc-list { display:flex; flex-direction:column; gap:10px; padding:4px 0 12px; }
- .doc-card { display:flex; gap:10px; align-items:center; padding:10px; background:var(--white); border-radius:10px; border:1px solid var(--border); cursor:pointer; }
- .doc-card .doc-thumb { width:40px; height:40px; border-radius:6px; background:var(--bg); display:flex; align-items:center; justify-content:center; font-size:18px; color:var(--text3); }
- /* file badge styles for attachment icons (PDF/DOC/XLS/MD) */
- .file-badge { display:inline-flex; width:40px; height:40px; border-radius:8px; align-items:center; justify-content:center; color:#fff; font-weight:700; font-size:13px; flex:0 0 40px; }
- .file-badge.pdf { background: #ff6b6b; }
- .file-badge.doc { background: #4dabf7; }
- .file-badge.xls { background: #73d13d; }
- .file-badge.md { background: #9254de; }
- .doc-card .doc-meta { display:flex; flex-direction:column; }
- .doc-card .doc-title { font-weight:600; font-size:13px; color:var(--text1); }
- .doc-card .doc-time { font-size:11px; color:var(--text3); margin-top:4px; }
- .recent-section { margin-top:12px; padding-top:8px; border-top:1px dashed var(--border); }
- .recent-list { display:flex; flex-direction:column; padding-top:8px; }
- .recent-item { padding:8px 10px; background:transparent; border:none; font-size:12px; color:var(--text2); border-radius:4px; transition: all 0.2s ease; }
- .recent-item:hover { background:#f5f5f5; }
- /* 文件预览模态(大弹窗) */
- .file-preview-modal-overlay { position: fixed; inset: 0; background: rgba(0,0,0,0.45); display: flex; align-items: center; justify-content: center; z-index: 1600; }
- .file-preview-modal { width: 90%; max-width: 1000px; max-height: 90vh; overflow: auto; background: var(--white); border-radius: 14px; box-shadow: 0 30px 80px rgba(10,30,60,0.35); padding: 20px; position: relative; }
- .file-preview-modal .modal-header { display:flex; align-items:center; justify-content:space-between; gap:12px; margin-bottom:12px; }
- .file-preview-modal .modal-title { font-weight:700; font-size:16px; color:var(--text1); }
- .file-preview-modal .modal-body { color:var(--text1); line-height:1.7; white-space:pre-wrap; font-size:14px; }
- .file-preview-close { position:absolute; right:12px; top:12px; background:transparent;border:none;font-size:18px;cursor:pointer;color:var(--text3); }
- /* 文件项操作按钮(悬浮显示) */
- .file-item { position: relative; }
- .file-actions { position: absolute; right: 8px; top: 50%; transform: translateY(-50%); display: none; gap:6px; align-items:center; }
- .file-item:hover .file-actions { display: flex; }
- .file-actions .action-btn { background: var(--white); border: 1px solid var(--border); padding:6px; border-radius:6px; font-size:13px; cursor:pointer; display:flex; align-items:center; justify-content:center; width:34px; height:34px; box-shadow: 0 2px 6px rgba(0,0,0,0.06); }
- .file-actions .action-btn:hover { border-color: var(--primary); color: var(--primary); }
- /* 解析过程弹出框(更接近示例:大圆角、渐变背景、分段进度) */
- .parsing-popover { position: fixed; width: 360px; background: linear-gradient(135deg, #f7fbff 0%, #e6f7ff 100%); border: 1px solid rgba(24,144,255,0.12); border-radius: 14px; box-shadow: 0 18px 48px rgba(15,35,70,0.18); z-index: 1500; padding: 14px; }
- .parsing-popover .title { font-weight:700; margin-bottom:10px; color:var(--text1); display:flex; align-items:center; justify-content:space-between; }
- .parsing-popover .parsing-progress { display:flex; gap:6px; margin-bottom:12px; }
- .parsing-popover .parsing-progress .seg { flex:1; height:8px; background: rgba(255,255,255,0.6); border-radius:6px; transition: all 0.4s; box-shadow: inset 0 -2px 4px rgba(0,0,0,0.03); }
- .parsing-popover .parsing-progress .seg.active { background: linear-gradient(90deg, rgba(24,144,255,0.95), rgba(58,150,255,0.8)); box-shadow: 0 6px 14px rgba(24,144,255,0.12); transform: translateY(-2px); }
- .parsing-popover .steps-list { display:flex; flex-direction:column; gap:8px; margin-bottom:8px; }
- .parsing-popover .step { display:flex; align-items:center; gap:12px; padding:10px 12px; border-radius:10px; background: rgba(255,255,255,0.6); }
- .parsing-popover .step .dot { width:14px;height:14px;border-radius:50%;background:var(--bg); border:2px solid rgba(0,0,0,0.06); flex-shrink:0; }
- .parsing-popover .step.completed { background: rgba(246,255,238,0.9); }
- .parsing-popover .step.completed .dot { background: var(--success); border-color: transparent; box-shadow: 0 6px 20px rgba(82,200,26,0.12); }
- .parsing-popover .step.active { background: rgba(240,248,255,0.95); }
- .parsing-popover .step.active .dot { background: var(--primary); border-color: transparent; box-shadow: 0 6px 20px rgba(24,144,255,0.12); }
- .parsing-popover .step.pending { background: rgba(250,250,250,0.9); }
- .parsing-popover .step.pending .dot { background: #f0f0f0; border-color: rgba(0,0,0,0.06); }
- .parsing-popover .step .label { font-size:13px; color:var(--text1); }
- .parsing-popover .close-btn { position:absolute; right:10px; top:8px; cursor:pointer; font-size:14px; color:var(--text3); }
- /* 文件分组 */
- .file-group { margin-bottom: 16px; }
- .file-group-header { display: flex; align-items: center; gap: 6px; font-size: 12px; color: var(--text3); margin-bottom: 8px; padding: 0 4px; }
- .file-group-header .count { background: var(--bg); padding: 1px 6px; border-radius: 8px; font-size: 10px; }
- /* 文件项 */
- .file-item { display: flex; align-items: center; gap: 10px; padding: 10px 12px; background: var(--white); border: 1px solid var(--border); border-radius: 8px; margin-bottom: 8px; cursor: pointer; transition: all 0.2s; }
- .file-item:hover { border-color: var(--primary); background: var(--primary-light); }
- .file-item.active { border-color: var(--primary); background: var(--primary-light); }
- .file-icon { font-size: 28px; flex-shrink: 0; }
- .file-icon.pdf { color: #ff4d4f; }
- .file-icon.docx { color: #1890ff; }
- .file-icon.xlsx { color: #52c41a; }
- .file-icon.md { color: #8c8c8c; }
- .file-info { flex: 1; min-width: 0; }
- .file-name { font-size: 12px; font-weight: 500; margin-bottom: 2px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
- .file-meta { display: flex; align-items: center; gap: 8px; font-size: 11px; color: var(--text3); }
- .file-status { font-size: 11px; white-space: nowrap; }
- .file-status.parsing { color: var(--primary); }
- .file-status.done { color: var(--success); }
- /* 中间编辑区 */
- .center-panel { flex: 1; display: flex; flex-direction: column; background: var(--white); overflow: hidden; min-height: 0; height: 100%; }
- /* 编辑器顶部标题栏 */
- .editor-title-bar { padding: 16px 24px; border-bottom: 1px solid var(--border); display: flex; align-items: center; gap: 12px; }
- .editor-main-title { font-size: 18px; font-weight: 600; flex: 1; cursor: pointer; }
- /* 调整:圆角 4px,固定高度 24px,水平内边距保持 8px,垂直居中 */
- .report-status { display:inline-block; margin-left:0; padding:0 8px; height:24px; line-height:24px; background:var(--primary-light); color:var(--primary); border-radius:4px; font-size:12px; font-weight:600; }
- /* Icon hover tooltip */
- .icon-tooltip {
- position: fixed;
- z-index: 4000;
- background: rgba(0,0,0,0.78);
- color: #fff;
- padding: 6px 8px;
- border-radius: 6px;
- font-size: 12px;
- pointer-events: none;
- white-space: nowrap;
- opacity: 0;
- transition: opacity 120ms ease;
- }
- .view-toggle { display: flex; align-items: center; border: 1px solid var(--border); border-radius: 6px; overflow: hidden; }
- .view-btn { padding: 8px 16px; border: none; background: var(--white); font-size: 13px; cursor: pointer; color: var(--text2); display: flex; align-items: center; gap: 6px; transition: all 0.2s; }
- .view-btn:first-child { border-right: 1px solid var(--border); }
- .view-btn:hover { background: var(--bg); }
- .view-btn.active { background: var(--primary-light); color: var(--primary); font-weight: 500; }
- .graph-btn { width: 36px; height: 36px; border: 1px solid var(--border); background: var(--white); border-radius: 6px; cursor: pointer; display: flex; align-items: center; justify-content: center; font-size: 18px; margin-left: 8px; transition: all 0.2s; }
- .graph-btn:hover { border-color: var(--primary); background: var(--primary-light); }
- .graph-btn.active { border-color: var(--primary); background: var(--primary-light); color: var(--primary); }
- /* 编辑器内容区 */
- .editor-scroll { flex: 1; overflow-y: auto; padding: 40px 48px; }
- .editor-content { max-width: 1000px; margin: 0 auto; }
- .editor-content h1 { font-size: 24px; font-weight: 700; margin-bottom: 24px; color: var(--text1); }
- .editor-content h2 { font-size: 18px; font-weight: 600; margin: 28px 0 16px; color: var(--text1); }
- .editor-content h3 { font-size: 15px; font-weight: 600; margin: 20px 0 12px; color: var(--text1); }
- .editor-content p { margin-bottom: 16px; line-height: 1.8; color: var(--text1); }
- /* 实体高亮标记 */
- .entity-highlight { display: inline; padding: 2px 6px; border: 1px solid var(--primary); border-radius: 4px; color: var(--primary); background: var(--primary-light); cursor: pointer; transition: all 0.2s; position: relative; }
- .entity-highlight:hover { background: var(--primary); color: white; }
- /* 不同类型实体的颜色样式,与右侧栏element-tag保持一致 */
- .entity-highlight.entity { border-color: var(--primary); background: var(--primary-light); color: var(--primary); }
- .entity-highlight.entity:hover { background: var(--primary); color: white; }
- .entity-highlight.concept { border-color: #722ed1; background: #f9f0ff; color: #722ed1; }
- .entity-highlight.concept:hover { background: #722ed1; color: white; }
- .entity-highlight.data { border-color: var(--success); background: #f6ffed; color: var(--success); }
- .entity-highlight.data:hover { background: var(--success); color: white; }
- .entity-highlight.location { border-color: var(--warning); background: #fff7e6; color: var(--warning); }
- .entity-highlight.location:hover { background: var(--warning); color: white; }
- .entity-highlight.asset { border-color: #eb2f96; background: #fff0f6; color: #eb2f96; }
- .entity-highlight.asset:hover { background: #eb2f96; color: white; }
- /* 来源标识小徽章(AI / 附件 / 人工) */
- .source-badge { display:inline-flex; align-items:center; justify-content:center; font-size:10px; padding:2px 6px; margin-left:6px; border-radius:10px; border:1px solid rgba(0,0,0,0.06); background:#fff; color:var(--text2); cursor:pointer; box-shadow:0 1px 2px rgba(0,0,0,0.04); }
- .source-badge.ai { background: #fff7e6; color: var(--warning); border-color: rgba(255, 216, 128, 0.4); }
- .source-badge.file { background: #f6ffed; color: var(--success); border-color: rgba(166, 230, 149, 0.45); }
- .source-badge.manual { background: #f0f5ff; color: var(--primary); border-color: rgba(173, 199, 255, 0.45); }
- .source-badge:hover { transform: translateY(-1px); }
- /* AI优化建议卡片 */
- .ai-suggestion-card { background: #fffbf0; border: 1px solid #ffe7ba; border-radius: 10px; padding: 16px; margin: 16px 0; }
- .ai-suggestion-header { display: flex; align-items: center; gap: 6px; margin-bottom: 10px; }
- .ai-suggestion-title { font-size: 13px; font-weight: 600; color: var(--warning); }
- .ai-suggestion-content { font-size: 12px; line-height: 1.7; color: var(--text1); margin-bottom: 12px; }
- .ai-suggestion-list { list-style: none; margin-bottom: 12px; }
- .ai-suggestion-list li { padding: 4px 0; font-size: 12px; color: var(--text2); }
- .ai-suggestion-list li::before { content: '•'; color: var(--primary); margin-right: 8px; }
- .ai-suggestion-actions { display: flex; gap: 8px; }
- /* 数据表格 */
- .data-table-card { background: var(--white); border: 1px solid var(--border); border-radius: 10px; margin: 16px 0; overflow: hidden; }
- .data-table-header { display: flex; align-items: center; justify-content: space-between; padding: 12px 16px; border-bottom: 1px solid var(--border); }
- .data-table-title { display: flex; align-items: center; gap: 8px; font-size: 13px; font-weight: 600; }
- .data-table-source { font-size: 11px; color: var(--text3); }
- .data-table { width: 100%; border-collapse: collapse; }
- .data-table th { background: var(--bg); padding: 10px 16px; text-align: left; font-size: 12px; font-weight: 600; color: var(--text2); border-bottom: 1px solid var(--border); }
- .data-table td { padding: 10px 16px; font-size: 13px; border-bottom: 1px solid var(--border); }
- .data-table tr:last-child td { border-bottom: none; }
- .data-table tr:hover td { background: var(--primary-light); }
- /* 数据引用卡片 */
- .data-reference-card { background: linear-gradient(135deg, #f0f9ff, #e6f7ff); border: 1px solid #bae7ff; border-radius: 10px; padding: 16px; margin: 16px 0; }
- .reference-header { display: flex; align-items: center; gap: 8px; margin-bottom: 12px; }
- .reference-icon { font-size: 16px; }
- .reference-title { font-size: 13px; font-weight: 600; color: var(--primary); }
- .reference-content { display: flex; flex-direction: column; gap: 6px; }
- .reference-item { display: flex; align-items: center; gap: 8px; font-size: 12px; }
- .reference-label { color: var(--text3); font-weight: 500; min-width: 70px; }
- .reference-value { color: var(--text1); }
- .reference-value.success { color: var(--success); font-weight: 600; }
- /* 竞争分析卡片 */
- .competition-card { background: linear-gradient(135deg, #fffbe6, #fff7e6); border: 1px solid #ffe7ba; border-radius: 10px; padding: 16px; margin: 16px 0; }
- .competition-header { display: flex; align-items: center; gap: 8px; margin-bottom: 12px; }
- .competition-icon { font-size: 16px; }
- .competition-title { font-size: 13px; font-weight: 600; color: var(--warning); }
- .competition-content { display: flex; flex-direction: column; gap: 10px; }
- .competition-item { padding: 12px; background: rgba(255,255,255,0.7); border-radius: 8px; border-left: 3px solid var(--warning); }
- .competitor-name { font-weight: 600; margin-bottom: 4px; }
- .competitor-share { font-size: 12px; color: var(--text2); margin-bottom: 2px; }
- .competitor-strength { font-size: 12px; color: var(--text3); }
- /* AI生成内容卡片 */
- .ai-generated-card { background: linear-gradient(135deg, #f9f0ff, #f5f0ff); border: 1px solid #efdbff; border-radius: 10px; padding: 16px; margin: 16px 0; }
- .ai-generated-header { display: flex; align-items: center; gap: 8px; margin-bottom: 12px; }
- .ai-generated-icon { font-size: 16px; }
- .ai-generated-title { font-size: 13px; font-weight: 600; color: var(--primary); }
- .ai-generated-content { font-size: 13px; line-height: 1.6; }
- .ai-generated-content ul { margin: 8px 0; padding-left: 20px; }
- .ai-generated-content li { margin: 4px 0; }
- .ai-generated-actions { display: flex; gap: 8px; margin-top: 12px; }
- /* 右侧AI助手面板 */
- .right-panel { width: 300px; background: var(--white); border-left: 1px solid var(--border); display: flex; flex-direction: column; min-height: 0; overflow: hidden; height: 100%; }
- /* split right panel: top = report elements (~40%), bottom = AI assistant (~60%) */
- .right-panel .element-section { flex: 4; overflow-y: auto; min-height: 0; }
- .right-panel .ai-assistant { flex: 6; overflow-y: auto; min-height: 0; display: flex; flex-direction: column; }
- /* 要素管理区 */
- .element-section { padding: 16px; border-bottom: 1px dashed var(--border); padding-bottom: 16px !important; }
- .element-header { display: flex; align-items: center; justify-content: space-between; margin-bottom: 12px; }
- .element-title { font-size: 13px; font-weight: 600; display: flex; align-items: center; gap: 6px; }
- .element-count { font-size: 11px; color: var(--text3); font-weight: normal; }
- /* 要素标签容器 */
- .element-tags-wrap { display: flex; flex-wrap: wrap; gap: 8px; max-height: 200px; overflow-y: auto; padding-right: 4px; padding-bottom: 16px; }
- .element-tags-wrap::-webkit-scrollbar { width: 4px; }
- .element-tags-wrap::-webkit-scrollbar-track { background: var(--bg); border-radius: 2px; }
- .element-tags-wrap::-webkit-scrollbar-thumb { background: var(--border); border-radius: 2px; }
- .element-tags-wrap::-webkit-scrollbar-thumb:hover { background: var(--text3); }
- /* 要素标签样式 */
- .element-tag { display: inline-flex; align-items: center; gap: 6px; padding: 6px 12px; background: var(--bg); border: 1px solid var(--border); border-radius: 16px; font-size: 12px; cursor: grab; transition: all 0.2s; user-select: none; }
- .element-tag:hover { border-color: var(--primary); background: var(--primary-light); transform: translateY(-1px); }
- .element-tag:active { cursor: grabbing; }
- .element-tag.dragging { opacity: 0.5; }
- .element-tag .tag-icon { font-size: 12px; }
- .element-tag .tag-name { font-weight: 500; }
- .element-tag.entity { border-left: 3px solid var(--primary); }
- .element-tag.concept { border-left: 3px solid #722ed1; }
- /* Tabs for elements */
- .element-tabs { display:flex; gap:8px; }
- .element-tab { padding:6px 12px; border-radius:12px; background:transparent; border:1px solid transparent; font-size:13px; cursor:pointer; color:var(--text2); }
- .element-tab.active { background: var(--primary); color:#fff; border-color: rgba(0,0,0,0.04); box-shadow: 0 6px 18px rgba(17,24,39,0.06); }
- /* Tag shape / size rules */
- .element-tag { height:28px; padding:0 12px; border-radius:2px; display:inline-flex; align-items:center; }
- .element-tag .tag-name { line-height:28px; }
- .element-tag.dynamic { border-radius:14px; } /* rounded for dynamic */
- .element-tag.static { border-radius:2px; } /* slightly rounded for static */
- /* module title style (icon + text), placed above header */
- .module-title { display:flex; align-items:center; gap:10px; font-size:15px; font-weight:700; color:var(--text1); padding:0; margin-bottom:10px; }
- .module-title .module-icon { width:36px; height:36px; border-radius:8px; background: var(--primary-gradient); display:flex; align-items:center; justify-content:center; font-size:18px; color:white; box-shadow: 0 6px 18px rgba(0,0,0,0.06); }
- .element-tag.data { border-left: 3px solid var(--success); }
- .element-tag.location { border-left: 3px solid var(--warning); }
- .element-tag.asset { border-left: 3px solid #eb2f96; }
- .element-hint { font-size: 11px; color: var(--text3); margin-top: 10px; text-align: center; }
- /* 要素详情弹出框 */
- .element-popover { position: fixed; width: 280px; background: var(--white); border-radius: 10px; box-shadow: 0 8px 24px rgba(0,0,0,0.15); z-index: 2000; display: none; }
- .element-popover.show { display: block; }
- .popover-header { padding: 12px 14px; border-bottom: 1px solid var(--border); display: flex; align-items: center; gap: 10px; }
- .popover-icon { width: 32px; height: 32px; border-radius: 8px; display: flex; align-items: center; justify-content: center; font-size: 16px; }
- .popover-icon.entity { background: linear-gradient(135deg, #e6f7ff, #bae7ff); }
- .popover-icon.data { background: linear-gradient(135deg, #f6ffed, #d9f7be); }
- .popover-icon.location { background: linear-gradient(135deg, #fff7e6, #ffe7ba); }
- .popover-icon.asset { background: linear-gradient(135deg, #fff0f6, #ffd6e7); }
- .popover-title { font-size: 14px; font-weight: 600; flex: 1; }
- .popover-close { width: 24px; height: 24px; border: none; background: var(--bg); border-radius: 50%; cursor: pointer; font-size: 12px; }
- .popover-close:hover { background: var(--danger); color: white; }
- .popover-body { padding: 14px; }
- .popover-section { margin-bottom: 10px; }
- .popover-section:last-child { margin-bottom: 0; }
- .popover-label { font-size: 10px; color: var(--text3); margin-bottom: 4px; text-transform: uppercase; }
- .popover-value { font-size: 12px; color: var(--text1); }
- .popover-relations { display: flex; flex-wrap: wrap; gap: 6px; }
- .popover-relation { padding: 4px 8px; background: var(--bg); border-radius: 4px; font-size: 11px; cursor: pointer; }
- .popover-relation:hover { background: var(--primary-light); color: var(--primary); }
- .popover-actions { display: flex; gap: 8px; margin-top: 12px; }
- .popover-actions .btn { flex: 1; justify-content: center; font-size: 12px; }
- /* AI助手区 */
- .ai-assistant { position: relative; flex: 1; display: flex; flex-direction: column; overflow: hidden; min-height: 0; }
- .ai-header { padding: 12px 16px; border-bottom: none; display: flex; align-items: center; gap: 10px; flex-shrink: 0; }
- .ai-avatar-sm { width: 36px; height: 36px; background: var(--ai-gradient); border-radius: 50%; display: flex; align-items: center; justify-content: center; font-size: 18px; flex-shrink: 0; }
- .ai-info { flex: 1; }
- .ai-name { font-size: 15px; font-weight: 600; }
- .ai-status { font-size: 11px; color: var(--success); }
- /* Hide AI subtitle and tabs per user request */
- .ai-info .ai-status { display: none; }
- .ai-tabs { display: none !important; }
- /* AI Tab切换 */
- .ai-tabs { display: flex; border-bottom: 1px solid var(--border); flex-shrink: 0; }
- .ai-tab { flex: 1; padding: 10px; text-align: center; font-size: 12px; cursor: pointer; color: var(--text2); border-bottom: 2px solid transparent; transition: all 0.2s; display: flex; align-items: center; justify-content: center; gap: 4px; }
- .ai-tab:hover { color: var(--primary); }
- .ai-tab.active { color: var(--primary); border-bottom-color: var(--primary); }
- /* AI消息区 */
- /* 消息区:为固定在底部的输入区预留底部空间,避免遮挡最后一条消息 */
- .ai-messages { flex: 1; overflow-y: auto; padding: 16px; padding-bottom: 96px; min-height: 0; }
- .msg { display: flex; gap: 10px; margin-bottom: 16px; }
- .msg.user { flex-direction: row-reverse; }
- .msg-avatar { width: 28px; height: 28px; border-radius: 50%; display: flex; align-items: center; justify-content: center; font-size: 12px; flex-shrink: 0; }
- .msg.ai .msg-avatar { background: var(--ai-gradient); color: white; }
- .msg.user .msg-avatar { background: var(--primary); color: white; }
- .msg-bubble { max-width: 85%; padding: 10px 14px; border-radius: 12px; font-size: 13px; line-height: 1.6; }
- .msg.ai .msg-bubble { background: var(--bg); border-radius: 4px 12px 12px 12px; }
- .msg.user .msg-bubble { background: var(--primary); color: white; border-radius: 12px 4px 12px 12px; }
- /* AI输入区 */
- /* 输入区固定在 AI 面板底部,不随消息滚动 */
- .ai-input-area { padding: 12px 16px; border-top: none; background: var(--white); flex-shrink: 0; position: absolute; left: 0; right: 0; bottom: 0; z-index: 4; }
- .ai-input-box { background: var(--bg); border: 1px solid var(--border); border-radius: 20px; transition: all 0.2s; }
- .ai-input-box:focus-within { border-color: var(--primary); background: var(--white); box-shadow: 0 0 0 3px rgba(24,144,255,0.1); }
- .ai-input-box textarea { width: 100%; border: none; background: transparent; resize: none; outline: none; font-size: 13px; line-height: 1.5; padding: 10px 16px; min-height: 40px; max-height: 80px; font-family: inherit; border-radius: 20px; display: block; }
- .ai-input-box textarea::placeholder { color: var(--text3); font-size: 14px; }
- /* 输入区内布局:左侧图标、中心输入、右侧操作(语音/发送) */
- /* Cursor-like AI input: larger rounded, subtle shadow, inline icons */
- .ai-input-inner { display:flex; align-items:center; gap:8px; }
- .ai-input-left, .ai-input-right { display:flex; gap:6px; align-items:center; flex-shrink:0; }
- .ai-input-box { flex:1; background:var(--white); border:1px solid var(--border); border-radius:8px; padding:8px 10px; box-shadow: 0 6px 18px rgba(16,24,40,0.06); display:flex; flex-direction:column; gap:6px; }
- .ai-input-label { font-size:12px; color:var(--text3); }
- .ai-input-row { display:flex; flex-direction:column; gap:6px; }
- .ai-input-row .ai-input-top { display:flex; align-items:center; gap:8px; }
- .ai-input-row textarea { flex:1; width:100%; border:none; outline:none; resize:none; font-size:14px; color:var(--text1); background:transparent; line-height:1.4; padding:6px 12px; min-height:20px; max-height:160px; }
- .ai-input-toolbar { display:flex; align-items:center; justify-content:space-between; gap:8px; height:28px; }
- .ai-input-toolbar .left, .ai-input-toolbar .right { display:flex; gap:12px; align-items:center; }
- .ai-input-box textarea::placeholder { color:var(--text3); }
- .ai-input-left .ai-icon-btn, .ai-input-right .ai-icon-btn { width:24px; height:24px; border-radius:4px; border:none; background:transparent; display:flex; align-items:center; justify-content:center; cursor:pointer; font-size:12px; color:#8c8c8c; padding:0; transition: all 0.2s ease; }
- .ai-input-left .ai-icon-btn:hover, .ai-input-right .ai-icon-btn:hover { background:#f0f0f0; border-color:transparent; color:#555555; }
- .ai-send-btn { width:24px; height:24px; border-radius:4px; border:none; background:#f0f0f0; display:flex; align-items:center; justify-content:center; cursor:pointer; font-size:12px; color:#555555; transition: all 0.18s; box-shadow:none; padding:0; }
- .ai-send-btn:hover { background:#1890ff; color:#ffffff; }
- .ai-send-btn.active { background: var(--primary); color: white; box-shadow: 0 6px 18px rgba(24,144,255,0.16); transform: translateY(-1px); }
- .ai-input-caption { font-size:13px; color:var(--text3); margin-bottom:6px; padding-left:6px; }
- .ai-input-caption .ai-highlight { color: var(--primary); font-weight:600; margin-left:6px; }
- .ai-input-hint { display: flex; align-items: center; justify-content: space-between; padding: 8px 4px 0; font-size: 10px; color: var(--text3); }
- /* 动画 */
- @keyframes pulse { 0%, 100% { opacity: 1; } 50% { opacity: 0.5; } }
- .parsing-anim { animation: pulse 1.5s infinite; }
- /* 可编辑内容区 */
- .editor-content[contenteditable="true"] { outline: none; }
- .editor-content[contenteditable="true"]:focus { background: #fafbfc; box-shadow: inset 0 0 0 2px var(--primary-light); }
- .editor-content[contenteditable="true"] ::selection { background: rgba(24,144,255,0.3); border-radius: 2px; }
- /* 文本编辑选中效果 */
- .editor-content[contenteditable="true"]:focus p { position: relative; }
- .editor-content[contenteditable="true"]:focus p::before {
- content: '';
- position: absolute;
- left: -20px;
- top: 0;
- bottom: 0;
- width: 3px;
- background: var(--primary);
- border-radius: 2px;
- opacity: 0;
- transition: opacity 0.2s;
- }
- .editor-content[contenteditable="true"]:focus p:hover::before {
- opacity: 0.5;
- }
- .editor-content[contenteditable="true"]:focus p.selected::before {
- opacity: 1;
- }
- /* 右键菜单 */
- .context-menu { position: fixed; min-width: 180px; background: var(--white); border-radius: 10px; box-shadow: 0 8px 24px rgba(0,0,0,0.15); z-index: 3000; display: none; overflow: hidden; }
- .context-menu.show { display: block; }
- .context-menu-item { display: flex; align-items: center; gap: 10px; padding: 10px 14px; font-size: 13px; cursor: pointer; transition: all 0.15s; }
- .context-menu-item:hover { background: var(--primary-light); color: var(--primary); }
- .context-menu-item .icon { font-size: 14px; width: 20px; text-align: center; }
- .context-menu-item .shortcut { margin-left: auto; font-size: 11px; color: var(--text3); }
- .context-menu-divider { height: 1px; background: var(--border); margin: 4px 0; }
- /* 数据关系表弹窗 */
- .data-relation-modal { position: fixed; inset: 0; background: rgba(0,0,0,0.5); display: none; align-items: center; justify-content: center; z-index: 1300; }
- .data-relation-modal.show { display: flex; }
- .data-relation-card { width: 600px; max-width: 90vw; background: var(--white); border-radius: 16px; overflow: hidden; box-shadow: 0 20px 60px rgba(0,0,0,0.3); }
- .data-relation-header { padding: 20px; background: linear-gradient(135deg, #f0f7ff, #f5f0ff); border-bottom: 1px solid var(--border); display: flex; align-items: center; gap: 15px; }
- .data-relation-icon { width: 48px; height: 48px; background: var(--white); border-radius: 12px; display: flex; align-items: center; justify-content: center; font-size: 24px; box-shadow: 0 4px 12px rgba(0,0,0,0.1); }
- .data-relation-title { flex: 1; }
- .data-relation-title h3 { font-size: 16px; font-weight: 600; margin-bottom: 4px; }
- .data-relation-title span { font-size: 12px; color: var(--text3); }
- .data-relation-close { width: 32px; height: 32px; border: none; background: rgba(0,0,0,0.05); border-radius: 50%; cursor: pointer; font-size: 16px; display: flex; align-items: center; justify-content: center; }
- .data-relation-close:hover { background: var(--danger); color: white; }
- .data-relation-body { padding: 20px; max-height: 60vh; overflow-y: auto; }
- .relation-section { margin-bottom: 24px; }
- .relation-section:last-child { margin-bottom: 0; }
- .relation-label { font-size: 14px; font-weight: 600; margin-bottom: 12px; color: var(--text1); display: flex; align-items: center; gap: 6px; }
- .relation-table { width: 100%; border-collapse: collapse; border: 1px solid var(--border); border-radius: 8px; overflow: hidden; }
- .relation-table th { background: var(--bg); padding: 12px; text-align: left; font-size: 12px; font-weight: 600; color: var(--text2); border-bottom: 1px solid var(--border); }
- .relation-table td { padding: 12px; border-bottom: 1px solid var(--border); font-size: 13px; }
- .relation-table tr:last-child td { border-bottom: none; }
- .relation-table tr:hover td { background: var(--primary-light); }
- .relation-input { width: 100%; padding: 6px 8px; border: 1px solid var(--border); border-radius: 4px; font-size: 12px; outline: none; }
- .relation-input:focus { border-color: var(--primary); }
- .relation-tags { display: flex; flex-wrap: wrap; gap: 8px; }
- .relation-tag { display: inline-flex; align-items: center; gap: 6px; padding: 6px 10px; background: var(--bg); border: 1px solid var(--border); border-radius: 12px; font-size: 11px; cursor: pointer; transition: all 0.2s; }
- .relation-tag:hover { background: var(--primary-light); border-color: var(--primary); }
- .data-relation-footer { padding: 16px 20px; background: var(--bg); border-top: 1px solid var(--border); display: flex; justify-content: flex-end; gap: 10px; }
- /* 拖拽提示 */
- .editor-content.drag-over { background: linear-gradient(135deg, rgba(24,144,255,0.05), rgba(24,144,255,0.1)); }
- .editor-content.drag-over::after { content: '释放鼠标插入要素'; position: fixed; top: 50%; left: 50%; transform: translate(-50%, -50%); padding: 12px 24px; background: var(--primary); color: white; border-radius: 8px; font-size: 14px; z-index: 100; }
- /* 报告要素弹窗样式 */
- .report-elements-modal { position: fixed; inset: 0; display: none; align-items: center; justify-content: center; z-index: 2200; }
- .report-elements-modal.show { display: flex; }
- .report-elements-card { width: 960px; max-width: 96vw; max-height: 80vh; background: var(--white); border-radius: 12px; box-shadow: 0 20px 60px rgba(0,0,0,0.3); overflow: hidden; display:flex; flex-direction:column; }
- .report-elements-header { padding: 12px 16px; border-bottom: 1px solid var(--border); display:flex; align-items:center; gap:12px; }
- .report-elements-title { font-size: 16px; font-weight:700; flex:1; }
- .report-elements-body { padding: 12px 16px; overflow:auto; flex:1; }
- .report-elements-footer { padding: 12px 16px; border-top:1px solid var(--border); display:flex; gap:8px; justify-content:flex-end; background:var(--bg); }
- .elements-table { width:100%; border-collapse: collapse; }
- .elements-table th, .elements-table td { padding:8px 10px; text-align:left; border-bottom:1px solid var(--border); font-size:13px; vertical-align:middle; }
- .elements-table th { background: var(--bg); font-weight:600; font-size:12px; color:var(--text2); }
- .elements-search { display:flex; gap:8px; align-items:center; }
- .elements-search input { padding:8px 10px; border:1px solid var(--border); border-radius:6px; width:260px; }
- .elements-actions .icon-btn { padding: 0; border-radius: 4px; width: 24px; height: 24px; display: flex; align-items: center; justify-content: center; }
- /* pager styles */
- .pager { display:flex; gap:6px; align-items:center; }
- .pager-btn { padding:6px 10px; border:1px solid var(--border); background:var(--white); border-radius:6px; cursor:pointer; font-size:13px; }
- .pager-btn.active { background:var(--primary); color:white; border-color:var(--primary); }
- .page-size-select { padding:6px 8px; border:1px solid var(--border); border-radius:6px; background:var(--white); }
- /* Toast容器 */
- .toast-container { position: fixed; top: 70px; right: 24px; z-index: 3000; display: flex; flex-direction: column; gap: 8px; }
- .toast { display: flex; align-items: center; gap: 10px; padding: 12px 18px; background: var(--white); border-radius: 10px; box-shadow: 0 8px 24px rgba(0,0,0,0.15); transform: translateX(120%); transition: transform 0.3s; min-width: 240px; }
- .toast.show { transform: translateX(0); }
- .toast.success { border-left: 4px solid var(--success); }
- .toast.error { border-left: 4px solid var(--danger); }
- .toast.warning { border-left: 4px solid var(--warning); }
- .toast.info { border-left: 4px solid var(--primary); }
- /* 通知面板 */
- .notif-panel { position: fixed; top: 56px; right: 0; width: 360px; height: calc(100vh - 56px); background: var(--white); box-shadow: -4px 0 16px rgba(0,0,0,0.1); transform: translateX(100%); transition: transform 0.3s; z-index: 999; display: flex; flex-direction: column; }
- /* 遮罩 */
- .overlay { position: fixed; inset: 0; background: rgba(0,0,0,0.3); z-index: 998; display: none; }
- .kg-overlay { position: fixed; inset: 0; background: rgba(0,0,0,0.5); z-index: 1199; display: none; }
- /* 导出菜单 */
- .export-menu { position: fixed; background: var(--white); border-radius: 10px; box-shadow: 0 8px 24px rgba(0,0,0,0.15); min-width: 180px; display: none; z-index: 2001; overflow: hidden; }
- /* 要素关系图谱弹窗 */
- .knowledge-graph-modal { position: fixed; top: 50%; left: 50%; transform: translate(-50%, -50%); width: 90%; max-width: 1200px; height: 80%; max-height: 700px; background: var(--white); border-radius: 16px; box-shadow: 0 20px 60px rgba(0,0,0,0.3); display: none; flex-direction: column; z-index: 1200; overflow: hidden; }
- .knowledge-graph-modal.show { display: flex; }
- .kg-header { padding: 16px 20px; border-bottom: 1px solid var(--border); display: flex; justify-content: space-between; align-items: center; background: linear-gradient(135deg, #f0f7ff, #f5f0ff); }
- .kg-title { display: flex; align-items: center; gap: 8px; font-size: 16px; font-weight: 600; color: var(--primary); }
- .kg-icon { font-size: 20px; }
- .kg-controls { display: flex; align-items: center; gap: 12px; }
- .kg-view-toggle { display: flex; border: 1px solid var(--border); border-radius: 6px; overflow: hidden; }
- .kg-view-btn { padding: 6px 12px; border: none; background: var(--white); font-size: 12px; cursor: pointer; color: var(--text2); display: flex; align-items: center; gap: 4px; transition: all 0.2s; }
- .kg-view-btn:first-child { border-right: 1px solid var(--border); }
- .kg-view-btn:hover { background: var(--bg); }
- .kg-view-btn.active { background: var(--primary-light); color: var(--primary); }
- .kg-close { width: 28px; height: 28px; border: none; background: rgba(0,0,0,0.05); border-radius: 50%; cursor: pointer; font-size: 16px; display: flex; align-items: center; justify-content: center; }
- .kg-close:hover { background: var(--danger); color: white; }
- .kg-content { flex: 1; overflow: hidden; }
- /* 图谱视图 */
- .kg-graph-view { height: 100%; display: flex; flex-direction: column; }
- .graph-canvas { flex: 1; position: relative; background: linear-gradient(45deg, #f8f9fa 25%, transparent 25%), linear-gradient(-45deg, #f8f9fa 25%, transparent 25%), linear-gradient(45deg, transparent 75%, #f8f9fa 75%), linear-gradient(-45deg, transparent 75%, #f8f8f8 75%); background-size: 20px 20px; background-position: 0 0, 0 10px, 10px -10px, -10px 0px; }
- .graph-node { position: absolute; width: 120px; padding: 12px; background: var(--white); border-radius: 12px; box-shadow: 0 4px 12px rgba(0,0,0,0.15); cursor: pointer; transition: all 0.3s; border: 2px solid transparent; text-align: center; }
- .graph-node:hover { transform: scale(1.05); box-shadow: 0 8px 24px rgba(0,0,0,0.2); }
- .graph-node.core { border-color: var(--primary); background: linear-gradient(135deg, #f0f7ff, #e6f7ff); }
- .graph-node.concept { border-color: #722ed1; background: linear-gradient(135deg, #f9f0ff, #efdbff); }
- .graph-node.data { border-color: var(--success); background: linear-gradient(135deg, #f6ffed, #d9f7be); }
- .graph-node.location { border-color: var(--warning); background: linear-gradient(135deg, #fff7e6, #ffe7ba); }
- .graph-node.highlighted { border-color: var(--danger); box-shadow: 0 0 20px rgba(255,77,79,0.4); transform: scale(1.1); }
- .node-icon { font-size: 24px; margin-bottom: 6px; }
- .node-label { font-size: 12px; font-weight: 600; margin-bottom: 4px; }
- .node-type { font-size: 10px; color: var(--text3); }
- .graph-lines { position: absolute; top: 0; left: 0; width: 100%; height: 100%; pointer-events: none; }
- .graph-legend { padding: 12px 16px; background: var(--bg); border-top: 1px solid var(--border); display: flex; gap: 16px; justify-content: center; }
- .legend-item { display: flex; align-items: center; gap: 6px; font-size: 12px; color: var(--text2); }
- .legend-dot { width: 12px; height: 12px; border-radius: 50%; }
- .legend-dot.core { background: var(--primary); }
- .legend-dot.concept { background: #722ed1; }
- .legend-dot.data { background: var(--success); }
- .legend-dot.location { background: var(--warning); }
- /* 列表视图 */
- .kg-list-view { height: 100%; display: flex; flex-direction: column; }
- .list-search { padding: 16px; border-bottom: 1px solid var(--border); }
- .list-search-input { width: 100%; padding: 8px 12px; border: 1px solid var(--border); border-radius: 6px; font-size: 13px; outline: none; }
- .list-search-input:focus { border-color: var(--primary); }
- .entity-categories { flex: 1; overflow-y: auto; }
- .category-section { margin-bottom: 16px; }
- .category-header { padding: 12px 16px; background: var(--bg); border-bottom: 1px solid var(--border); display: flex; align-items: center; gap: 8px; font-size: 13px; font-weight: 600; color: var(--text1); }
- .category-icon { font-size: 16px; }
- .entity-item { display: flex; align-items: center; gap: 12px; padding: 12px 16px; border-bottom: 1px solid var(--border); cursor: pointer; transition: all 0.2s; }
- .entity-item:hover { background: var(--primary-light); }
- .entity-item:last-child { border-bottom: none; }
- .entity-icon { width: 36px; height: 36px; border-radius: 8px; display: flex; align-items: center; justify-content: center; font-size: 18px; flex-shrink: 0; }
- .entity-info { flex: 1; }
- .entity-name { font-size: 13px; font-weight: 600; margin-bottom: 4px; }
- .entity-meta { font-size: 11px; color: var(--text3); }
- .entity-actions { display: flex; gap: 4px; }
- .entity-action-btn { width: 24px; height: 24px; border: none; background: transparent; border-radius: 4px; cursor: pointer; font-size: 12px; display: flex; align-items: center; justify-content: center; transition: all 0.2s; }
- .entity-action-btn:hover { background: var(--bg); }
- /* 确认对话框 */
- .confirm-dialog { position: fixed; inset: 0; background: rgba(0,0,0,0.6); display: flex; align-items: center; justify-content: center; z-index: 1400; opacity: 0; transition: opacity 0.3s; }
- .confirm-dialog.show { opacity: 1; }
- .confirm-content { background: var(--white); border-radius: 16px; box-shadow: 0 20px 60px rgba(0,0,0,0.3); min-width: 400px; max-width: 500px; overflow: hidden; transform: scale(0.9); transition: transform 0.3s; }
- .confirm-dialog.show .confirm-content { transform: scale(1); }
- .confirm-header { padding: 20px; background: linear-gradient(135deg, #f0f7ff, #f5f0ff); border-bottom: 1px solid var(--border); display: flex; justify-content: space-between; align-items: center; }
- .confirm-header h3 { font-size: 16px; font-weight: 600; margin: 0; }
- .confirm-close { width: 32px; height: 32px; border: none; background: rgba(0,0,0,0.05); border-radius: 50%; cursor: pointer; font-size: 16px; display: flex; align-items: center; justify-content: center; }
- .confirm-close:hover { background: var(--danger); color: white; }
- .confirm-body { padding: 20px; font-size: 14px; line-height: 1.6; color: var(--text1); }
- .confirm-footer { padding: 16px 20px; background: var(--bg); border-top: 1px solid var(--border); display: flex; justify-content: flex-end; gap: 10px; }
- /* 响应式 */
- @media (max-width: 1024px) {
- .sidebar { width: 200px; }
- .main { margin-left: 200px; }
- .editor-body { flex-direction: column; }
- .left-panel, .right-panel { width: 100%; height: auto; max-height: 300px; }
- .knowledge-graph-modal { width: 100%; }
- .data-relation-modal { width: 95%; }
- .confirm-content { min-width: 320px; }
- }
- </style>
- <script>
- // === 标题同步:将正文 H1 的文字同步到编辑器顶部小标题与输入中 ===
- function syncEditorTitleFromContent() {
- try {
- const h1 = document.querySelector('.editor-content h1');
- const toolbarTitle = document.querySelector('.editor-main-title');
- const titleInput = document.querySelector('.report-title-input');
- if (h1 && toolbarTitle) {
- const txt = h1.textContent.trim();
- toolbarTitle.textContent = txt;
- if (titleInput) titleInput.value = txt;
- }
- } catch (e) {
- console.warn('syncEditorTitleFromContent error', e);
- }
- }
- // 同步一次并监听内容变化(以 H1 变动为触发器)
- document.addEventListener('DOMContentLoaded', function() {
- syncEditorTitleFromContent();
- const h1 = document.querySelector('.editor-content h1');
- if (h1) {
- const ro = new MutationObserver(() => syncEditorTitleFromContent());
- ro.observe(h1, { characterData: true, childList: true, subtree: true });
- }
- });
- </script>
- </head>
- <body>
- <!-- 全局头部 -->
- <header class="topbar" id="topbar">
- <div class="topbar-left">
- <div class="logo">灵</div>
- <div class="brand">灵越智报</div>
- </div>
- <div class="topbar-right">
- <button class="icon-btn" title="搜索"><i class="iconfont icon-SEARCH"></i></button>
- <button class="icon-btn notif-btn" title="通知"><i class="iconfont icon-MESSAGE_NOTIFICATION_L"></i><span class="badge">3</span></button>
- <div class="avatar">张</div>
- <div class="username">张三</div>
- </div>
- </header>
- <style>
- .topbar {
- height: 56px;
- display: flex;
- align-items: center;
- justify-content: space-between;
- padding: 0 18px;
- background: var(--white);
- border-bottom: 1px solid var(--border);
- gap: 12px;
- position: sticky;
- top: 0;
- z-index: 50;
- }
- .topbar-left { display:flex;align-items:center;gap:10px;min-width:180px; }
- .logo {
- width:36px;height:36px;border-radius:8px;background:linear-gradient(135deg,var(--primary) 0%, #69c0ff 100%);display:flex;align-items:center;justify-content:center;color:white;font-weight:700;font-size:18px;
- }
- .brand { font-weight:600;color:var(--text); font-size:16px; }
- .topbar-center { display:none; }
- .topbar-right { display:flex; align-items:center; gap:8px; min-width:auto; justify-content:flex-end; }
- .topbar-right .notif-btn { margin-right: 8px; }
- .avatar { width:28px;height:28px;border-radius:50%;background:var(--primary);color:#fff;display:flex;align-items:center;justify-content:center;font-weight:700;font-size:12px; margin-right: 0; }
- .username { color:var(--text); font-size:13px; margin-left: -4px; }
- .icon-btn {
- background: transparent;
- border: none;
- cursor: pointer;
- font-size: 16px;
- position: relative;
- color: #8c8c8c;
- width: 24px;
- height: 24px;
- display: flex;
- align-items: center;
- justify-content: center;
- border-radius: 4px;
- transition: all 0.2s ease;
- }
- .icon-btn:hover {
- background: #f0f0f0;
- color: #555555;
- }
- .icon-btn .badge { position:absolute; top:-6px; right:-6px; background:var(--danger); color:white; border-radius:10px; font-size:11px; padding:2px 6px; }
- @media (max-width: 900px) {
- .brand { display:none; }
- }
- </style>
- <!-- 侧边栏 -->
- <aside class="sidebar" id="sidebar">
- <nav class="sidebar-menu">
- <!-- 报告编辑入口已移除,页面打开时直接进入编辑器 -->
- </nav>
- </aside>
- <!-- 主内容区 -->
- <main class="main" id="mainContent" style="display:none;">
- <!-- 首页 -->
- <div class="page" id="page-home">
- <div class="welcome">
- <h1>早上好,张三!<span>智能报告,洞察未来。</span></h1>
- <p>今天是个创作的好日子,开始您的智能报告之旅吧</p>
- </div>
- <div class="stats-grid">
- <div class="stat-card card" onclick="void(0)">
- <div class="stat-icon blue">📄</div>
- <div class="stat-value">12</div>
- <div class="stat-label">我的报告</div>
- <div class="stat-trend up">↑ 3 本周新增</div>
- </div>
- <div class="stat-card card" onclick="void(0)">
- <div class="stat-icon purple">🎨</div>
- <div class="stat-value">15</div>
- <div class="stat-label">可用模板</div>
- <div class="stat-trend up">↑ 2 新增</div>
- </div>
- <div class="stat-card card">
- <div class="stat-icon green">📚</div>
- <div class="stat-value">48</div>
- <div class="stat-label">知识文档</div>
- <div class="stat-trend">📁 1.2GB</div>
- </div>
- <div class="stat-card card" onclick="toggleFab()">
- <div class="stat-icon orange">💰</div>
- <div class="stat-value">¥127.50</div>
- <div class="stat-label">本月消耗</div>
- <div class="stat-trend">↓ 12%</div>
- </div>
- </div>
- <!-- AI对话入口 -->
- <div class="ai-card card">
- <div class="ai-welcome">
- <div class="ai-avatar">🤖</div>
- <div class="ai-title">你好!我是灵越AI助手,可以帮你:</div>
- <ul class="ai-list">
- <li>快速生成各类报告</li>
- <li>分析和解读数据</li>
- <li>回答业务相关问题</li>
- </ul>
- <div class="ai-tip">试试输入:"帮我生成一份智慧园区建设项目可行性研究报告"</div>
- </div>
- <div class="ai-input-wrap">
- <input type="text" class="ai-input" placeholder="输入您的需求,或 @知识库 引用资料..." id="homeAiInput" oninput="toggleSendBtn()">
- <div class="ai-input-btns">
- <button class="ai-input-btn">🎤</button>
- <button class="ai-input-btn">📎</button>
- <button class="ai-input-btn send" id="homeSendBtn" onclick="handleHomeAi()">➤</button>
- </div>
- </div>
- <div class="thinking-modes">
- <div class="mode-btn active" onclick="setMode(this)">🧠 深度思考</div>
- <div class="mode-btn" onclick="setMode(this)">⚡ 快速回答</div>
- <div class="mode-btn" onclick="setMode(this)">🌐 联网搜索</div>
- <div class="mode-btn" onclick="setMode(this)">📊 数据分析</div>
- </div>
- </div>
- <!-- 模板推荐区 -->
- <div class="section-header">
- <h2 class="section-title">📋 推荐模板</h2>
- <span class="section-link" onclick="void(0)">查看全部 →</span>
- </div>
- <div class="tpl-grid">
- <div class="tpl-card card" onclick="void(0)">
- <div class="tpl-preview">📊</div>
- <div class="tpl-info">
- <div class="tpl-name">市场分析报告</div>
- <div class="tpl-meta"><span>📊 128次</span><span>⭐ 4.8</span></div>
- <div class="tpl-tags"><span class="tpl-tag">官方</span><span class="tpl-tag hot">热门</span></div>
- <button class="tpl-btn">使用此模板</button>
- </div>
- </div>
- <div class="tpl-card card" onclick="void(0)">
- <div class="tpl-preview">🏢</div>
- <div class="tpl-info">
- <div class="tpl-name">可行性研究报告</div>
- <div class="tpl-meta"><span>📊 96次</span><span>⭐ 4.9</span></div>
- <div class="tpl-tags"><span class="tpl-tag">官方</span><span class="tpl-tag hot">热门</span></div>
- <button class="tpl-btn">使用此模板</button>
- </div>
- </div>
- <div class="tpl-card card" onclick="void(0)">
- <div class="tpl-preview">📅</div>
- <div class="tpl-info">
- <div class="tpl-name">项目周报</div>
- <div class="tpl-meta"><span>📊 256次</span><span>⭐ 4.9</span></div>
- <div class="tpl-tags"><span class="tpl-tag">官方</span></div>
- <button class="tpl-btn">使用此模板</button>
- </div>
- </div>
- </div>
- <div class="quick-actions">
- <div class="quick-action" onclick="showToast('上传模板', 'info')">
- <div class="quick-action-icon">📤</div>
- <span>上传新模板</span>
- </div>
- <div class="quick-action" onclick="showToast('创建模板', 'info')">
- <div class="quick-action-icon">🛠️</div>
- <span>创建新模板</span>
- </div>
- </div>
- </div>
- <!-- 报告记录页 -->
- <div class="page" id="page-reports">
- <h2>📋 报告记录</h2>
- <div style="display:flex;gap:12px;margin:16px 0;">
- <select style="padding:8px 12px;border:1px solid var(--border);border-radius:6px;font-size:13px;background:var(--white);"><option>全部状态</option><option>初稿</option><option>审核中</option><option>已定稿</option></select>
- <input type="text" placeholder="🔍 搜索报告..." style="flex:1;max-width:280px;padding:8px 12px;border:1px solid var(--border);border-radius:6px;font-size:13px;">
- <div style="margin-left:auto;display:flex;gap:6px;">
- <button class="btn" style="background:var(--primary-light);border-color:var(--primary);color:var(--primary)">全部</button>
- <button class="btn">本周</button>
- <button class="btn">本月</button>
- </div>
- </div>
- <div style="display:flex;flex-direction:column;gap:12px;">
- <div class="card" style="padding:18px;">
- <div style="display:flex;align-items:center;gap:12px;margin-bottom:10px;">
- <span style="font-size:22px;">📄</span>
- <span style="flex:1;font-size:15px;font-weight:600;">智慧园区建设项目可行性研究报告</span>
- <span style="padding:4px 10px;background:#f6ffed;color:var(--success);border-radius:12px;font-size:11px;">已定稿</span>
- </div>
- <div style="display:flex;gap:20px;font-size:12px;color:var(--text2);margin-bottom:12px;">
- <span>📅 2025-12-30</span><span>👤 张三</span><span>🏢 华南事业部</span>
- </div>
- <div style="display:flex;gap:8px;">
- <button class="btn btn-primary" onclick="void(0)">查看</button>
- <button class="btn" onclick="void(0)">编辑</button>
- <button class="btn" onclick="showToast('导出PDF成功', 'success')">导出</button>
- </div>
- </div>
- <div class="card" style="padding:18px;">
- <div style="display:flex;align-items:center;gap:12px;margin-bottom:10px;">
- <span style="font-size:22px;">📄</span>
- <span style="flex:1;font-size:15px;font-weight:600;">Q4市场分析报告</span>
- <span style="padding:4px 10px;background:#fffbe6;color:var(--warning);border-radius:12px;font-size:11px;">审核中</span>
- </div>
- <div style="display:flex;gap:20px;font-size:12px;color:var(--text2);margin-bottom:12px;">
- <span>📅 2025-12-28</span><span>👤 张三</span>
- </div>
- <div style="display:flex;gap:8px;">
- <button class="btn btn-primary" onclick="void(0)">查看</button>
- <button class="btn" onclick="showToast('已发送催办', 'success')">催办</button>
- </div>
- </div>
- </div>
- </div>
- <!-- 模板管理页 -->
- <div class="page" id="page-templates">
- <h2>🎨 模板管理</h2>
- <div style="display:flex;gap:12px;margin:16px 0;">
- <input type="text" placeholder="🔍 搜索模板..." style="width:280px;padding:8px 12px;border:1px solid var(--border);border-radius:6px;font-size:13px;">
- <div style="display:flex;gap:6px;">
- <span style="padding:8px 16px;background:var(--primary);color:white;border-radius:18px;font-size:12px;cursor:pointer;">全部</span>
- <span style="padding:8px 16px;background:var(--bg);border-radius:18px;font-size:12px;cursor:pointer;" onclick="showToast('官方模板', 'info')">官方模板</span>
- <span style="padding:8px 16px;background:var(--bg);border-radius:18px;font-size:12px;cursor:pointer;" onclick="showToast('我的模板', 'info')">我的模板</span>
- </div>
- <button class="btn btn-primary" style="margin-left:auto;">➕ 创建模板</button>
- </div>
- <div style="display:grid;grid-template-columns:repeat(4,1fr);gap:14px;">
- <div class="tpl-card card" onclick="void(0)"><div class="tpl-preview">📊</div><div class="tpl-info"><div class="tpl-name">市场分析报告</div><div class="tpl-meta"><span>📊 128次</span></div><div class="tpl-tags"><span class="tpl-tag">官方</span><span class="tpl-tag hot">热门</span></div><button class="tpl-btn">使用</button></div></div>
- <div class="tpl-card card" onclick="void(0)"><div class="tpl-preview">🏢</div><div class="tpl-info"><div class="tpl-name">可行性研究报告</div><div class="tpl-meta"><span>📊 96次</span></div><div class="tpl-tags"><span class="tpl-tag">官方</span></div><button class="tpl-btn">使用</button></div></div>
- <div class="tpl-card card" onclick="void(0)"><div class="tpl-preview">📅</div><div class="tpl-info"><div class="tpl-name">项目周报</div><div class="tpl-meta"><span>📊 256次</span></div><div class="tpl-tags"><span class="tpl-tag">官方</span></div><button class="tpl-btn">使用</button></div></div>
- <div class="tpl-card card" onclick="void(0)"><div class="tpl-preview">💼</div><div class="tpl-info"><div class="tpl-name">尽职调查报告</div><div class="tpl-meta"><span>📊 45次</span></div><div class="tpl-tags"><span class="tpl-tag">行业</span></div><button class="tpl-btn">使用</button></div></div>
- </div>
- </div>
- <!-- 数据源管理页 -->
- <div class="page" id="page-datasources">
- <h2>🔗 数据源管理</h2>
- <div style="display:flex;gap:12px;margin:16px 0;">
- <input type="text" placeholder="🔍 搜索数据源..." style="width:280px;padding:8px 12px;border:1px solid var(--border);border-radius:6px;font-size:13px;">
- <button class="btn btn-primary" style="margin-left:auto;">➕ 添加数据源</button>
- </div>
- <div style="display:flex;flex-direction:column;gap:12px;">
- <div class="card" style="padding:18px;">
- <div style="display:flex;align-items:center;gap:12px;margin-bottom:12px;">
- <div style="width:44px;height:44px;background:var(--primary-light);border-radius:10px;display:flex;align-items:center;justify-content:center;font-size:22px;">🗄️</div>
- <div style="flex:1;"><div style="font-size:15px;font-weight:600;">销售数据库</div><div style="font-size:12px;color:var(--text3);">MySQL · db.company.com:3306</div></div>
- <div style="font-size:12px;color:var(--success);">● 已连接</div>
- </div>
- <div style="display:flex;gap:8px;"><button class="btn">测试连接</button><button class="btn">同步数据</button><button class="btn">查看数据表</button></div>
- </div>
- <div class="card" style="padding:18px;">
- <div style="display:flex;align-items:center;gap:12px;margin-bottom:12px;">
- <div style="width:44px;height:44px;background:#f6ffed;border-radius:10px;display:flex;align-items:center;justify-content:center;font-size:22px;">🌐</div>
- <div style="flex:1;"><div style="font-size:15px;font-weight:600;">市场数据API</div><div style="font-size:12px;color:var(--text3);">REST API · api.marketdata.com</div></div>
- <div style="font-size:12px;color:var(--success);">● 已连接</div>
- </div>
- <div style="display:flex;gap:8px;"><button class="btn">测试接口</button><button class="btn">查看文档</button></div>
- </div>
- </div>
- </div>
- </main>
- <!-- 编辑器页面 -->
- <div class="editor-page active" id="page-editor">
- <!-- 编辑器主体 -->
- <div class="editor-body">
- <!-- 左侧项目文件面板 -->
- <div class="left-panel">
- <div class="panel-header panel-header-tabs">
- <div class="tabs-left">
- <button class="panel-tab active" id="tabDocsTop" onclick="switchLeftTab('docs')">我的文档</button>
- <button class="panel-tab" id="tabFilesTop" onclick="switchLeftTab('files')">我的附件</button>
- </div>
- <div style="display:flex;align-items:center;gap:8px;">
- <button class="panel-toggle-btn" title="折叠资源面板" onclick="toggleResourcePanel(this)" style="margin-left:8px;">❮</button>
- </div>
- </div>
- <div class="panel-body">
- <!-- 内部旧 tabs 已移除 -->
- <div class="doc-section" id="leftDocsSection">
- <div class="doc-section-header" id="reportsHeader">
- <div id="reportsTitle">报告记录 <span id="reportsCount" style="font-weight:600">· 3</span></div>
- <div style="display:flex;align-items:center;gap:8px;">
- <button class="icon-btn" id="leftSearchBtn" title="搜索"><i class="iconfont icon-SEARCH"></i></button>
- <button class="icon-btn" id="newReportBtn" title="新建报告"><i class="iconfont icon-CREATE"></i></button>
- </div>
- </div>
- <div class="docs-area">
- <div class="doc-list">
- <div class="doc-card file-item" onclick="showFilePreview(this)" data-content="智慧园区建设项目可行性研究报告 原文预览..." data-name="智慧园区建设项目可行性研究报告">
- <div class="doc-thumb">📄</div>
- <div class="file-actions">
- <button class="action-btn" onclick="downloadReport(this)" title="下载">⬇️</button>
- <button class="action-btn" onclick="archiveReport(this)" title="归档">🗂️</button>
- <button class="action-btn" onclick="deleteReport(this)" title="删除">🗑️</button>
- </div>
- <div class="doc-meta">
- <div class="doc-title">智慧园区建设项目可行性研究报告</div>
- <div class="doc-time">2025/12/30 00:00:00</div>
- </div>
- </div>
- <div class="doc-card file-item" onclick="showFilePreview(this)" data-content="华东市场分析 原文预览..." data-name="华东市场分析">
- <div class="doc-thumb">📄</div>
- <div class="file-actions">
- <button class="action-btn" onclick="downloadReport(this)" title="下载">⬇️</button>
- <button class="action-btn" onclick="archiveReport(this)" title="归档">🗂️</button>
- <button class="action-btn" onclick="deleteReport(this)" title="删除">🗑️</button>
- </div>
- <div class="doc-meta">
- <div class="doc-title">华东市场分析</div>
- <div class="doc-time">2026/1/26 01:51:44</div>
- </div>
- </div>
- <div class="doc-card file-item" onclick="showFilePreview(this)" data-content="季度销售简报 原文预览..." data-name="季度销售简报">
- <div class="doc-thumb">📄</div>
- <div class="file-actions">
- <button class="action-btn" onclick="downloadReport(this)" title="下载">⬇️</button>
- <button class="action-btn" onclick="archiveReport(this)" title="归档">🗂️</button>
- <button class="action-btn" onclick="deleteReport(this)" title="删除">🗑️</button>
- </div>
- <div class="doc-meta">
- <div class="doc-title">季度销售简报</div>
- <div class="doc-time">2026/1/24 13:51:44</div>
- </div>
- </div>
- </div>
- </div>
- <div class="recent-area">
- <div class="recent-section">
- <div class="section-title small">最近操作</div>
- <div class="recent-list" id="recentList">
- <div class="recent-item" title="AI 已生成报告初稿:季度销售简报..."><span class="recent-text">AI 已生成报告初稿:季度销售简报...</span></div>
- <div class="recent-item" title="文件 '市场调研数据.pdf' 解析完成"><span class="recent-text">文件 '市场调研数据.pdf' 解析完成</span></div>
- <div class="recent-item" title="附件 '财务预测表.xlsx' 解析完成"><span class="recent-text">附件 '财务预测表.xlsx' 解析完成</span></div>
- <div class="recent-item" title="用户 张三 上传文件 '公司报表.xlsx'"><span class="recent-text">用户 张三 上传文件 '公司报表.xlsx'</span></div>
- </div>
- </div>
- </div>
- </div>
- <div class="doc-section" id="leftFilesSection" style="display:none;">
- <div class="doc-section-header">
- <div>我的附件</div>
- <div class="badge-count" id="leftFilesCount">0</div>
- </div>
- <div class="doc-list">
- <!-- Attachments will be listed here -->
- </div>
- </div>
- </div>
- </div>
- <!-- 中间编辑区 -->
- <div class="center-panel">
- <div class="editor-title-bar" style="display:flex;justify-content:space-between;align-items:center;gap:12px;">
- <div style="display:flex;align-items:center;gap:12px;">
- <div class="editor-main-title" id="editorMainTitle" role="button" tabindex="0" title="报告标题">智慧园区建设项目可行性研究报告</div>
- <span id="reportStatusTag" class="report-status" title="点击切换状态">草稿</span>
- </div>
- <div class="editor-actions" style="display:flex;align-items:center;gap:8px;">
- <button class="icon-btn" id="toggleViewBtn" title="切换:原文/标记" onclick="toggleView()"><i class="iconfont icon-BIG_PROMOTION"></i></button>
- <button class="icon-btn" id="reportElementsTopBtn" title="报告要素" onclick="openReportElementsModal()"><i class="iconfont icon-COMPONENTS"></i></button>
- <button class="icon-btn" id="exportBtn" title="导出" onclick="showExportMenu(this)"><i class="iconfont icon-UPLOAD"></i></button>
- <button class="icon-btn" id="moreBtn" title="更多" onclick="showMoreMenu(this)">⋯</button>
- </div>
- </div>
- <div class="editor-scroll">
- <!-- 原文视图 -->
- <div class="editor-content" id="contentOriginal" contenteditable="true" oncontextmenu="showContextMenu(event)">
- <h1>智慧园区建设项目可行性研究报告</h1>
- <h2>一、项目背景</h2>
- <p>随着数字经济的快速发展,智慧园区已成为推动产业升级和城市现代化的重要载体。本项目旨在构建集智能化管理、低碳绿色、产业协同于一体的新型智慧园区。</p>
- <h3>1.1 行业现状</h3>
- <p>根据最新市场调研数据显示,2024年中国智慧园区市场规模已达到1,789亿元,同比增长18%,预计2025年将突破2,100亿元。</p>
- <!-- 数据表格 -->
- <div class="data-table-card">
- <div class="data-table-header">
- <div class="data-table-title">
- <span>📊</span>
- <span>市场规模数据</span>
- </div>
- <div class="data-table-source">来源: 市场调研数据.pdf</div>
- </div>
- <table class="data-table">
- <thead>
- <tr>
- <th>年份</th>
- <th>市场规模(亿元)</th>
- <th>同比增长</th>
- </tr>
- </thead>
- <tbody>
- <tr><td>2022</td><td>1,280</td><td>15.2%</td></tr>
- <tr><td>2023</td><td>1,516</td><td>18.4%</td></tr>
- <tr><td>2024</td><td>1,789</td><td>18.0%</td></tr>
- <tr><td>2025E</td><td>2,100</td><td>17.4%</td></tr>
- </tbody>
- </table>
- </div>
- <h2>二、项目概述</h2>
- <p>本项目位于华南地区核心区域,规划总面积约50万平方米,预计总投资12.5亿元。项目将分三期建设,首期重点打造智能制造产业集群和数字服务中心。</p>
- <h3>2.1 项目定位</h3>
- <p>本项目致力于打造粤港澳大湾区最具代表性的智慧园区标杆项目,通过引入先进的信息技术和管理理念,实现产业数字化转型和高质量发展。</p>
- <h3>2.2 建设内容</h3>
- <p>项目建设内容主要包括智慧基础设施建设、产业服务平台搭建、数字化管理平台开发等三大方面,总建筑面积约35万平方米。</p>
- <h2>三、投资估算</h2>
- <p>项目总投资为12.5亿元,其中建设投资10.2亿元,流动资金2.3亿元。资金来源包括政府投资4亿元、企业自筹6.5亿元、银行贷款2亿元。</p>
- <h2>四、市场分析</h2>
- <p>当前智慧园区市场需求旺盛,随着产业数字化转型的加速推进,预计未来5年内智慧园区市场规模将保持20%以上的年增长率。</p>
- <h2>五、经济效益分析</h2>
- <p>项目建设期预计3年,运营期20年。项目建成后预计年营业收入8.6亿元,年利润总额2.4亿元,投资回收期约为6.2年。</p>
- </div>
- <!-- 标记视图 -->
- <div class="editor-content" id="contentMarked" style="display:none;" contenteditable="true" oncontextmenu="showContextMenu(event)">
- <h1>智慧园区建设项目可行性研究报告</h1>
- <h2>一、项目背景</h2>
- <p>随着<span class="entity-highlight" onclick="showDataRelationModal('数字经济', '产业趋势', 'concept', '技术方案说明.pdf')" contenteditable="false">数字经济</span>的快速发展,<span class="entity-highlight" onclick="showDataRelationModal('智慧园区', '核心实体', 'entity', '项目可行性研究报告.docx')" contenteditable="false">智慧园区</span>已成为推动<span class="entity-highlight" onclick="showDataRelationModal('产业升级', '概念', 'concept', '项目可行性研究报告.docx')" contenteditable="false">产业升级</span>和<span class="entity-highlight" onclick="showDataRelationModal('城市现代化', '概念', 'concept', '项目可行性研究报告.docx')" contenteditable="false">城市现代化</span>的重要载体。本项目旨在构建集<span class="entity-highlight" onclick="showDataRelationModal('智能化管理', '技术', 'concept', '技术方案说明.pdf')" contenteditable="false">智能化管理</span>、<span class="entity-highlight" onclick="showDataRelationModal('低碳绿色', '概念', 'concept', '项目可行性研究报告.docx')" contenteditable="false">低碳绿色</span>、<span class="entity-highlight" onclick="showDataRelationModal('产业协同', '模式', 'concept', '项目可行性研究报告.docx')" contenteditable="false">产业协同</span>于一体的新型智慧园区。</p>
- <!-- AI优化建议卡片 -->
- <div class="ai-suggestion-card" id="aiSuggestionCard" contenteditable="false">
- <div class="ai-suggestion-header">
- <span class="ai-suggestion-icon">💡</span>
- <span class="ai-suggestion-title">AI 优化建议</span>
- </div>
- <div class="ai-suggestion-content">
- 此处可补充具体的政策文件引用,增强论述的权威性。已从《市场调研数据.pdf》中提取到《"十四五"数字经济发展规划》等相关政策信息。
- </div>
- <div class="ai-suggestion-actions">
- <button class="suggest-btn accept" onclick="acceptSuggestion()">✓ 采纳建议</button>
- <button class="suggest-btn ignore" onclick="ignoreSuggestion()">✕ 忽略</button>
- </div>
- </div>
- <h3>1.1 行业现状</h3>
- <p>根据最新市场调研数据显示,2024年中国<span class="entity-highlight" onclick="showDataRelationModal('智慧园区', '核心实体', 'entity', '市场调研数据.pdf')" contenteditable="false">智慧园区</span>市场规模已达到<span class="entity-highlight" onclick="showDataRelationModal('1,789亿元', '市场规模数据', 'data', '市场调研数据.pdf')" contenteditable="false">1,789亿元</span>,同比增长<span class="entity-highlight" onclick="showDataRelationModal('18%', '增长率数据', 'data', '市场调研数据.pdf')" contenteditable="false">18%</span>,预计2025年将突破<span class="entity-highlight" onclick="showDataRelationModal('2,100亿元', '预测数据', 'data', '市场调研数据.pdf')" contenteditable="false">2,100亿元</span>。</p>
- <!-- 数据表格 -->
- <div class="data-table-card" contenteditable="false">
- <div class="data-table-header">
- <div class="data-table-title">
- <span>📊</span>
- <span>市场规模数据</span>
- </div>
- <div class="data-table-source">来源: 市场调研数据.pdf</div>
- </div>
- <table class="data-table">
- <thead>
- <tr>
- <th>年份</th>
- <th>市场规模(亿元)</th>
- <th>同比增长</th>
- </tr>
- </thead>
- <tbody>
- <tr><td>2022</td><td><span class="entity-highlight" onclick="showDataRelationModal('1,280亿元', '历史数据', 'data', '市场调研数据.pdf')" contenteditable="false">1,280</span></td><td><span class="entity-highlight" onclick="showDataRelationModal('15.2%', '增长率', 'data', '市场调研数据.pdf')" contenteditable="false">15.2%</span></td></tr>
- <tr><td>2023</td><td><span class="entity-highlight" onclick="showDataRelationModal('1,516亿元', '历史数据', 'data', '市场调研数据.pdf')" contenteditable="false">1,516</span></td><td><span class="entity-highlight" onclick="showDataRelationModal('18.4%', '增长率', 'data', '市场调研数据.pdf')" contenteditable="false">18.4%</span></td></tr>
- <tr><td>2024</td><td><span class="entity-highlight" onclick="showDataRelationModal('1,789亿元', '当前数据', 'data', '市场调研数据.pdf')" contenteditable="false">1,789</span></td><td><span class="entity-highlight" onclick="showDataRelationModal('18.0%', '增长率', 'data', '市场调研数据.pdf')" contenteditable="false">18.0%</span></td></tr>
- <tr><td><span class="entity-highlight" onclick="showDataRelationModal('2025E', '预测年份', 'data', '市场调研数据.pdf')" contenteditable="false">2025E</span></td><td><span class="entity-highlight" onclick="showDataRelationModal('2,100亿元', '预测数据', 'data', '市场调研数据.pdf')" contenteditable="false">2,100</span></td><td><span class="entity-highlight" onclick="showDataRelationModal('17.4%', '预测增长率', 'data', '市场调研数据.pdf')" contenteditable="false">17.4%</span></td></tr>
- </tbody>
- </table>
- </div>
- <h2>二、项目概述</h2>
- <p>本项目位于<span class="entity-highlight" onclick="showDataRelationModal('华南地区', '地理位置', 'location', '项目可行性研究报告.docx')" contenteditable="false">华南地区</span>核心区域,规划总面积约<span class="entity-highlight" onclick="showDataRelationModal('50万平方米', '面积数据', 'data', '项目可行性研究报告.docx')" contenteditable="false">50万平方米</span>,预计总投资<span class="entity-highlight" onclick="showDataRelationModal('12.5亿元', '投资额', 'data', '项目可行性研究报告.docx')" contenteditable="false">12.5亿元</span>。项目将分<span class="entity-highlight" onclick="showDataRelationModal('三期', '建设周期', 'data', '项目可行性研究报告.docx')" contenteditable="false">三期</span>建设,首期重点打造<span class="entity-highlight" onclick="showDataRelationModal('智能制造产业集群', '产业类型', 'concept', '项目可行性研究报告.docx')" contenteditable="false">智能制造产业集群</span>和<span class="entity-highlight" onclick="showDataRelationModal('数字服务中心', '基础设施', 'concept', '项目可行性研究报告.docx')" contenteditable="false">数字服务中心</span>。</p>
- <h3>2.1 项目定位</h3>
- <p>本项目致力于打造<span class="entity-highlight" onclick="showDataRelationModal('粤港澳大湾区', '区域', 'location', '项目可行性研究报告.docx')" contenteditable="false">粤港澳大湾区</span>最具代表性的<span class="entity-highlight" onclick="showDataRelationModal('智慧园区标杆项目', '项目类型', 'concept', '项目可行性研究报告.docx')" contenteditable="false">智慧园区标杆项目</span>,通过引入先进的信息技术和管理理念,实现<span class="entity-highlight" onclick="showDataRelationModal('产业数字化转型', '转型目标', 'concept', '项目可行性研究报告.docx')" contenteditable="false">产业数字化转型</span>和<span class="entity-highlight" onclick="showDataRelationModal('高质量发展', '发展目标', 'concept', '项目可行性研究报告.docx')" contenteditable="false">高质量发展</span>。</p>
- <h3>2.2 建设内容</h3>
- <p>项目建设内容主要包括<span class="entity-highlight" onclick="showDataRelationModal('智慧基础设施', '建设内容', 'concept', '项目可行性研究报告.docx')" contenteditable="false">智慧基础设施</span>建设、<span class="entity-highlight" onclick="showDataRelationModal('产业服务平台', '建设内容', 'concept', '项目可行性研究报告.docx')" contenteditable="false">产业服务平台</span>搭建、<span class="entity-highlight" onclick="showDataRelationModal('数字化管理平台', '建设内容', 'concept', '项目可行性研究报告.docx')" contenteditable="false">数字化管理平台</span>开发等三大方面,总建筑面积约<span class="entity-highlight" onclick="showDataRelationModal('35万平方米', '建筑面积', 'data', '项目可行性研究报告.docx')" contenteditable="false">35万平方米</span>。</p>
- <!-- 数据引用卡片 -->
- <div class="data-reference-card" contenteditable="false">
- <div class="reference-header">
- <span class="reference-icon">🔗</span>
- <span class="reference-title">数据引用验证</span>
- </div>
- <div class="reference-content">
- <div class="reference-item">
- <span class="reference-label">来源文档:</span>
- <span class="reference-value">技术方案说明.pdf (第3页)</span>
- </div>
- <div class="reference-item">
- <span class="reference-label">验证状态:</span>
- <span class="reference-value success">✓ 已验证</span>
- </div>
- <div class="reference-item">
- <span class="reference-label">置信度:</span>
- <span class="reference-value">95.8%</span>
- </div>
- </div>
- </div>
- <h2>三、投资估算</h2>
- <p>项目总投资为<span class="entity-highlight" onclick="showDataRelationModal('12.5亿元', '总投资', 'data', '财务预测表.xlsx')" contenteditable="false">12.5亿元</span>,其中建设投资<span class="entity-highlight" onclick="showDataRelationModal('10.2亿元', '建设投资', 'data', '财务预测表.xlsx')" contenteditable="false">10.2亿元</span>,流动资金<span class="entity-highlight" onclick="showDataRelationModal('2.3亿元', '流动资金', 'data', '财务预测表.xlsx')" contenteditable="false">2.3亿元</span>。资金来源包括政府投资<span class="entity-highlight" onclick="showDataRelationModal('4亿元', '政府投资', 'data', '财务预测表.xlsx')" contenteditable="false">4亿元</span>、企业自筹<span class="entity-highlight" onclick="showDataRelationModal('6.5亿元', '自筹资金', 'data', '财务预测表.xlsx')" contenteditable="false">6.5亿元</span>、银行贷款<span class="entity-highlight" onclick="showDataRelationModal('2亿元', '贷款资金', 'data', '财务预测表.xlsx')" contenteditable="false">2亿元</span>。</p>
- <h2>四、市场分析</h2>
- <p>当前<span class="entity-highlight" onclick="showDataRelationModal('智慧园区', '产品', 'entity', '市场调研数据.pdf')" contenteditable="false">智慧园区</span>市场需求旺盛,随着<span class="entity-highlight" onclick="showDataRelationModal('产业数字化转型', '趋势', 'concept', '市场调研数据.pdf')" contenteditable="false">产业数字化转型</span>的加速推进,预计未来<span class="entity-highlight" onclick="showDataRelationModal('5年', '时间周期', 'data', '市场调研数据.pdf')" contenteditable="false">5年</span>内智慧园区市场规模将保持<span class="entity-highlight" onclick="showDataRelationModal('20%', '增长率预测', 'data', '市场调研数据.pdf')" contenteditable="false">20%</span>以上的年增长率。</p>
- <!-- 竞争分析卡片 -->
- <div class="competition-card" contenteditable="false">
- <div class="competition-header">
- <span class="competition-icon">🏆</span>
- <span class="competition-title">竞争格局分析</span>
- </div>
- <div class="competition-content">
- <div class="competition-item">
- <div class="competitor-name">领先企业A</div>
- <div class="competitor-share">市场份额: 28%</div>
- <div class="competitor-strength">优势: 技术领先</div>
- </div>
- <div class="competition-item">
- <div class="competitor-name">新兴企业B</div>
- <div class="competitor-share">市场份额: 18%</div>
- <div class="competitor-strength">优势: 服务创新</div>
- </div>
- <div class="competition-item">
- <div class="competitor-name">本项目</div>
- <div class="competitor-share">目标份额: 15%</div>
- <div class="competitor-strength">优势: 区域特色</div>
- </div>
- </div>
- </div>
- <h2>五、经济效益分析</h2>
- <p>项目建设期预计<span class="entity-highlight" onclick="showDataRelationModal('3年', '建设期', 'data', '财务预测表.xlsx')" contenteditable="false">3年</span>,运营期<span class="entity-highlight" onclick="showDataRelationModal('20年', '运营期', 'data', '财务预测表.xlsx')" contenteditable="false">20年</span>。项目建成后预计年营业收入<span class="entity-highlight" onclick="showDataRelationModal('8.6亿元', '年收入', 'data', '财务预测表.xlsx')" contenteditable="false">8.6亿元</span>,年利润总额<span class="entity-highlight" onclick="showDataRelationModal('2.4亿元', '年利润', 'data', '财务预测表.xlsx')" contenteditable="false">2.4亿元</span>,投资回收期约为<span class="entity-highlight" onclick="showDataRelationModal('6.2年', '回收期', 'data', '财务预测表.xlsx')" contenteditable="false">6.2年</span>。</p>
- <!-- 财务预测表格 -->
- <div class="data-table-card" contenteditable="false">
- <div class="data-table-header">
- <div class="data-table-title">
- <span>💰</span>
- <span>财务预测数据</span>
- </div>
- <div class="data-table-source">来源: 财务预测表.xlsx</div>
- </div>
- <table class="data-table">
- <thead>
- <tr>
- <th>年份</th>
- <th>营业收入(亿元)</th>
- <th>利润总额(亿元)</th>
- <th>投资回收</th>
- </tr>
- </thead>
- <tbody>
- <tr><td>第1年</td><td><span class="entity-highlight" onclick="showDataRelationModal('6.2亿元', '第1年收入', 'data', '财务预测表.xlsx')" contenteditable="false">6.2</span></td><td><span class="entity-highlight" onclick="showDataRelationModal('1.8亿元', '第1年利润', 'data', '财务预测表.xlsx')" contenteditable="false">1.8</span></td><td>运营初期</td></tr>
- <tr><td>第2年</td><td><span class="entity-highlight" onclick="showDataRelationModal('7.4亿元', '第2年收入', 'data', '财务预测表.xlsx')" contenteditable="false">7.4</span></td><td><span class="entity-highlight" onclick="showDataRelationModal('2.1亿元', '第2年利润', 'data', '财务预测表.xlsx')" contenteditable="false">2.1</span></td><td>快速增长</td></tr>
- <tr><td>第3年</td><td><span class="entity-highlight" onclick="showDataRelationModal('8.6亿元', '第3年收入', 'data', '财务预测表.xlsx')" contenteditable="false">8.6</span></td><td><span class="entity-highlight" onclick="showDataRelationModal('2.4亿元', '第3年利润', 'data', '财务预测表.xlsx')" contenteditable="false">2.4</span></td><td>达产年份</td></tr>
- </tbody>
- </table>
- </div>
- <!-- AI生成内容建议 -->
- <div class="ai-generated-card" contenteditable="false">
- <div class="ai-generated-header">
- <span class="ai-generated-icon">✨</span>
- <span class="ai-generated-title">AI 生成内容</span>
- </div>
- <div class="ai-generated-content">
- <p><strong>风险分析:</strong></p>
- <ul>
- <li>技术风险:新兴技术应用的不确定性</li>
- <li>市场风险:需求变化和竞争加剧</li>
- <li>运营风险:团队建设和管理挑战</li>
- <li>财务风险:资金链和投资回收压力</li>
- </ul>
- <div class="ai-generated-actions">
- <button class="btn btn-primary" onclick="insertGeneratedContent()">➕ 应用内容</button>
- <button class="btn" onclick="showToast('已忽略AI建议', 'info')">✕ 忽略</button>
- </div>
- </div>
- </div>
- </div>
- </div>
- </div>
- <!-- 右侧AI助手面板 -->
- <div class="right-panel">
- <!-- 报告要素区(上半) -->
- <div class="element-section">
- <div class="module-title" role="button" aria-label="打开报告要素列表">
- <div class="module-icon">📋</div>
- <div class="module-text">报告要素</div>
- <div class="elements-actions" style="display:flex;align-items:center;gap:8px;margin-left:8px;">
- <!-- report elements opener moved to editor actions -->
- </div>
- </div>
- <div class="element-header" style="display:flex;align-items:center;justify-content:space-between;padding-top:4px;border-top:1px solid transparent;">
- <div class="element-tabs" style="display:flex;align-items:center;gap:8px;">
- <div class="element-tab active" id="tabDynamic" onclick="switchElementTab('dynamic')">动态要素</div>
- <div class="element-tab" id="tabStatic" onclick="switchElementTab('static')">静态要素</div>
- </div>
- <div>
- <button class="icon-btn" id="elementSearchBtn" title="搜索" style="margin-left:8px;"><i class="iconfont icon-SEARCH"></i></button>
- </div>
- </div>
- <!-- 要素标签容器:动态 / 静态 两个面板,使用 tab 切换 -->
- <div id="dynamicTags" class="element-tags-wrap">
- <!-- 动态要素(AI 规则计算产生)示例 -->
- <span class="element-tag dynamic" draggable="true" ondragstart="handleTagDragStart(event, '市场上升信号')" ondragend="handleTagDragEnd(event)" onclick="showTagPopover(event, 'dynamic1')">
- <span class="tag-icon">📈</span>
- <span class="tag-name">市场上升信号</span>
- </span>
- <span class="element-tag dynamic" draggable="true" ondragstart="handleTagDragStart(event, '增长偏好')" ondragend="handleTagDragEnd(event)" onclick="showTagPopover(event, 'dynamic2')">
- <span class="tag-icon">⚡</span>
- <span class="tag-name">增长偏好</span>
- </span>
- </div>
- <div id="staticTags" class="element-tags-wrap" style="display:none;">
- <!-- 静态要素:直接来自原文采集(保留原有示例) -->
- <span class="element-tag static entity" draggable="true" ondragstart="handleTagDragStart(event, '智慧园区')" ondragend="handleTagDragEnd(event)" onclick="showTagPopover(event, 'smartpark')">
- <span class="tag-icon">🏢</span>
- <span class="tag-name">智慧园区</span>
- </span>
- <span class="element-tag static concept" draggable="true" ondragstart="handleTagDragStart(event, '产业升级')" ondragend="handleTagDragEnd(event)" onclick="showTagPopover(event, 'upgrade')">
- <span class="tag-icon">📈</span>
- <span class="tag-name">产业升级</span>
- </span>
- <span class="element-tag static concept" draggable="true" ondragstart="handleTagDragStart(event, '城市现代化')" ondragend="handleTagDragEnd(event)" onclick="showTagPopover(event, 'modern')">
- <span class="tag-icon">🌆</span>
- <span class="tag-name">城市现代化</span>
- </span>
- <span class="element-tag static concept" draggable="true" ondragstart="handleTagDragStart(event, '智能化管理')" ondragend="handleTagDragEnd(event)" onclick="showTagPopover(event, 'ai')">
- <span class="tag-icon">🤖</span>
- <span class="tag-name">智能化管理</span>
- </span>
- <span class="element-tag static data" draggable="true" ondragstart="handleTagDragStart(event, '1,789亿元')" ondragend="handleTagDragEnd(event)" onclick="showTagPopover(event, 'data1')">
- <span class="tag-icon">💰</span>
- <span class="tag-name">1,789亿元</span>
- </span>
- <span class="element-tag static data" draggable="true" ondragstart="handleTagDragStart(event, '18%')" ondragend="handleTagDragEnd(event)" onclick="showTagPopover(event, 'data2')">
- <span class="tag-icon">📊</span>
- <span class="tag-name">18%</span>
- </span>
- </div>
- </div>
- <!-- 要素详情弹出框 -->
- <div class="element-popover" id="elementPopover">
- <div class="popover-header">
- <div class="popover-icon entity" id="popoverIcon">🏢</div>
- <div class="popover-title" id="popoverTitle">智慧园区</div>
- <button class="popover-close" onclick="hideTagPopover()">×</button>
- </div>
- <div class="popover-body">
- <div class="popover-section">
- <div class="popover-label">类型</div>
- <div class="popover-value" id="popoverType">核心实体</div>
- </div>
- <div class="popover-section">
- <div class="popover-label">来源</div>
- <div class="popover-value" id="popoverSource">项目可行性研究报告.docx</div>
- </div>
- <div class="popover-section">
- <div class="popover-label">关联要素</div>
- <div class="popover-relations" id="popoverRelations">
- <span class="popover-relation">→ 产业升级</span>
- <span class="popover-relation">→ 城市现代化</span>
- </div>
- </div>
- <div class="popover-actions">
- <button class="btn" onclick="showToast('已定位到文档', 'info');hideTagPopover();">📍 定位</button>
- <button class="btn btn-primary" onclick="insertTagToEditor();hideTagPopover();">➕ 插入</button>
- </div>
- </div>
- </div>
- <!-- AI助手区 -->
- <div class="ai-assistant">
- <div class="ai-header">
- <div class="ai-avatar-sm">🤖</div>
- <div class="ai-info">
- <div class="ai-name">AI助手</div>
- <div class="ai-status">● 已加载项目上下文</div>
- </div>
- </div>
- <!-- AI Tab切换 -->
- <div class="ai-tabs">
- <div class="ai-tab active" onclick="switchAiTab(this, 'chat')">💬 对话</div>
- <div class="ai-tab" onclick="switchAiTab(this, 'suggest')">💡 建议</div>
- <div class="ai-tab" onclick="switchAiTab(this, 'memory')">🧠 记忆</div>
- </div>
- <!-- AI消息区 -->
- <div class="ai-messages" id="aiMessages">
- <div class="msg ai">
- <div class="msg-avatar">🤖</div>
- <div class="msg-bubble">您好!我已分析上传的5份文档,构建了项目知识图谱。有什么可以帮您的?</div>
- </div>
- <div class="msg user">
- <div class="msg-avatar">张</div>
- <div class="msg-bubble">帮我补充市场分析部分的竞争格局内容</div>
- </div>
- <div class="msg ai">
- <div class="msg-avatar">🤖</div>
- <div class="msg-bubble">好的,我已从《市场调研数据.pdf》中提取了竞争格局相关数据。建议如下:</div>
- </div>
- <!-- AI输入区 -->
- <div class="ai-input-area">
- <div class="ai-input-inner">
- <div class="ai-input-box">
- <div class="ai-input-row">
- <div class="ai-input-top">
- <textarea id="aiTextarea" placeholder="发消息给AI助手" rows="1" onkeydown="handleAiKey(event)" oninput="autoResizeTextarea(this)"></textarea>
- </div>
- <div class="ai-input-toolbar">
- <div class="left">
- <!-- 1: 自定义图标(icon-CREATE) -->
- <button class="ai-icon-btn" id="aiIconCreateBtn" title="上传"><i class="iconfont icon-CREATE"></i></button>
- <!-- 2: 将原来的 '@' 替换为 icon-a-SYMBOLS_ -->
- <button class="ai-icon-btn" title="@" onclick="showToast('@', 'info')"><i class="iconfont icon-a-SYMBOLS_"></i></button>
- <!-- 3: 将 '联网' 替换为 icon-hulianwangoff -->
- <button class="ai-icon-btn" title="联网" onclick="showToast('联网搜索(示意)', 'info')"><i class="iconfont icon-hulianwangoff"></i></button>
- </div>
- <div class="right">
- <!-- 4: 将语音输入图标替换为 icon-01 -->
- <button class="ai-icon-btn" title="语音输入" onclick="showToast('语音输入(示意)', 'info')"><i class="iconfont icon-01"></i></button>
- <button class="ai-send-btn" id="aiSendBtn" title="发送" onclick="sendAiMsg()">⬆</button>
- </div>
- </div>
- </div>
- </div>
- </div>
- <!-- hint removed as requested -->
- </div>
- </div>
- </div>
- </div>
- </div>
- <!-- 报告要素弹窗 -->
- <div class="report-elements-modal" id="reportElementsModal" aria-hidden="true">
- <div class="report-elements-card" role="dialog" aria-modal="true" aria-labelledby="reportElementsTitle">
- <div class="report-elements-header">
- <div class="report-elements-title" id="reportElementsTitle">报告要素</div>
- <div class="elements-search">
- <input type="text" id="elementsSearchInput" placeholder="搜索要素名称 / 类型..." />
- </div>
- </div>
- <div class="report-elements-body">
- <table class="elements-table" id="elementsTable">
- <thead>
- <tr>
- <th style="width:14%;">名称</th>
- <th style="width:18%;">描述</th>
- <th style="width:8%;">类型</th>
- <th style="width:10%;">要素类型</th>
- <th style="width:12%;">原值</th>
- <th style="width:12%;">新值</th>
- <th style="width:12%;">填充源</th>
- <th style="width:8%;">操作</th>
- </tr>
- </thead>
- <tbody id="elementsTbody">
- <!-- 动态生成 -->
- </tbody>
- </table>
- </div>
- <div class="report-elements-footer">
- <div style="margin-right:auto;display:flex;gap:12px;align-items:center;">
- <!-- 翻页控件已移除:改由分页数字按钮显示(默认显示最多 5 个页码) -->
- <div class="pagination" id="elementsPaginationInfo" style="font-size:12px;color:var(--text3);"></div>
- <div style="display:flex;align-items:center;gap:8px;margin-left:12px;">
- <label style="font-size:12px;color:var(--text3);">每页显示:</label>
- <select id="elementsPageSize" class="page-size-select">
- <option value="10">10</option>
- <option value="20">20</option>
- <option value="50">50</option>
- </select>
- </div>
- </div>
- <button class="btn" onclick="closeReportElementsModal()">取消</button>
- <button class="btn btn-primary" id="saveElementsBtn">保存全部</button>
- </div>
- </div>
- </div>
- <!-- 右键菜单 -->
- <div class="context-menu" id="contextMenu">
- <div class="context-menu-item" onclick="execContextAction('copy')">
- <span class="icon">📋</span>
- <span>复制</span>
- <span class="shortcut">Ctrl+C</span>
- </div>
- <div class="context-menu-item" onclick="execContextAction('cut')">
- <span class="icon">✂️</span>
- <span>剪切</span>
- <span class="shortcut">Ctrl+X</span>
- </div>
- <div class="context-menu-item" onclick="execContextAction('paste')">
- <span class="icon">📄</span>
- <span>粘贴</span>
- <span class="shortcut">Ctrl+V</span>
- </div>
- <div class="context-menu-divider"></div>
- <div class="context-menu-item" onclick="execContextAction('polish')">
- <span class="icon">✨</span>
- <span>AI 润色</span>
- </div>
- <div class="context-menu-item" onclick="execContextAction('spell')">
- <span class="icon">📝</span>
- <span>检查拼写</span>
- </div>
- <div class="context-menu-divider"></div>
- <div class="context-menu-item" onclick="execContextAction('mark')">
- <span class="icon">🏷️</span>
- <span>标记为要素</span>
- </div>
- <div class="context-menu-item" onclick="execContextAction('quote')">
- <span class="icon">💬</span>
- <span>引用到AI助手</span>
- </div>
- </div>
- <!-- 数据关系表弹窗 -->
- <div class="data-relation-modal" id="dataRelationModal">
- <div class="data-relation-card">
- <div class="data-relation-header">
- <div class="data-relation-icon" id="relationIcon">🏷️</div>
- <div class="data-relation-title">
- <h3 id="relationEntityName">标签数据关系</h3>
- <span id="relationEntityType">实体类型 · 数据来源</span>
- </div>
- <button class="data-relation-close" onclick="closeDataRelationModal()">×</button>
- </div>
- <div class="data-relation-body">
- <div class="relation-section">
- <div class="relation-label">📊 数据关系表</div>
- <table class="relation-table">
- <thead>
- <tr>
- <th style="width:25%">属性</th>
- <th style="width:30%">原始值</th>
- <th style="width:30%">当前标签值</th>
- <th style="width:15%">操作</th>
- </tr>
- </thead>
- <tbody id="relationTableBody">
- <!-- 动态生成 -->
- </tbody>
- </table>
- </div>
- <div class="relation-section">
- <div class="relation-label">🔗 关联要素</div>
- <div class="relation-tags" id="relationTags">
- <!-- 动态生成关联标签 -->
- </div>
- </div>
- </div>
- <div class="data-relation-footer">
- <button class="btn" onclick="showToast('已删除标记', 'info');closeDataRelationModal();">🗑️ 删除标记</button>
- <button class="btn" onclick="closeDataRelationModal()">取消</button>
- <button class="btn btn-primary" onclick="saveDataRelationChanges()">保存更改</button>
- </div>
- </div>
- </div>
- <!-- 导出菜单 -->
- <div class="export-menu" id="exportMenu" style="position:fixed;background:var(--white);border-radius:10px;box-shadow:0 8px 24px rgba(0,0,0,0.15);min-width:180px;display:none;z-index:2001;overflow:hidden;">
- <div style="padding:12px 16px;cursor:pointer;font-size:13px;display:flex;align-items:center;gap:10px;transition:background 0.15s;" onmouseover="this.style.background='var(--primary-light)'" onmouseout="this.style.background=''" onclick="showToast('导出PDF成功', 'success');hideExportMenu()">
- <span>📕</span><span>导出为 PDF</span>
- </div>
- <div style="padding:12px 16px;cursor:pointer;font-size:13px;display:flex;align-items:center;gap:10px;transition:background 0.15s;" onmouseover="this.style.background='var(--primary-light)'" onmouseout="this.style.background=''" onclick="showToast('导出Word成功', 'success');hideExportMenu()">
- <span>📘</span><span>导出为 Word</span>
- </div>
- </div>
- <!-- 要素关系图谱弹窗 -->
- <div class="knowledge-graph-modal" id="knowledgeGraphModal">
- <div class="kg-header">
- <div class="kg-title">
- <span class="kg-icon">🔗</span>
- <span>要素关系图谱</span>
- </div>
- <div class="kg-controls">
- <div class="kg-view-toggle">
- <button class="kg-view-btn active" onclick="switchGraphView('graph')" id="graphViewBtn">
- <span>🕸️</span>
- <span>图谱视图</span>
- </button>
- <button class="kg-view-btn" onclick="switchGraphView('list')" id="listViewBtn">
- <span>📋</span>
- <span>列表视图</span>
- </button>
- </div>
- <button class="kg-close" onclick="closeKnowledgeGraph()">×</button>
- </div>
- </div>
- <div class="kg-content">
- <!-- 图谱视图 -->
- <div class="kg-graph-view" id="graphView">
- <div class="graph-canvas" id="graphCanvas">
- <div class="graph-node core" style="left: 50%; top: 50%; transform: translate(-50%, -50%);" data-entity="智慧园区">
- <div class="node-icon">🏢</div>
- <div class="node-label">智慧园区</div>
- <div class="node-type">核心实体</div>
- </div>
- <div class="graph-node concept" style="left: 25%; top: 30%;" data-entity="产业升级">
- <div class="node-icon">📈</div>
- <div class="node-label">产业升级</div>
- <div class="node-type">概念</div>
- </div>
- <div class="graph-node concept" style="left: 75%; top: 30%;" data-entity="城市现代化">
- <div class="node-icon">🌆</div>
- <div class="node-label">城市现代化</div>
- <div class="node-type">概念</div>
- </div>
- <div class="graph-node concept" style="left: 15%; top: 70%;" data-entity="智能化管理">
- <div class="node-icon">🤖</div>
- <div class="node-label">智能化管理</div>
- <div class="node-type">技术</div>
- </div>
- <div class="graph-node data" style="left: 40%; top: 20%;" data-entity="1,789亿元">
- <div class="node-icon">💰</div>
- <div class="node-label">1,789亿元</div>
- <div class="node-type">市场规模</div>
- </div>
- <div class="graph-node data" style="left: 60%; top: 80%;" data-entity="12.5亿元">
- <div class="node-icon">💵</div>
- <div class="node-label">12.5亿元</div>
- <div class="node-type">投资额</div>
- </div>
- <div class="graph-node location" style="left: 85%; top: 70%;" data-entity="华南地区">
- <div class="node-icon">📍</div>
- <div class="node-label">华南地区</div>
- <div class="node-type">地理位置</div>
- </div>
- <!-- 关系连线 -->
- <svg class="graph-lines" width="100%" height="100%">
- <defs>
- <marker id="arrowhead" markerWidth="10" markerHeight="7" refX="9" refY="3.5" orient="auto">
- <polygon points="0 0, 10 3.5, 0 7" fill="#1890ff" opacity="0.6"/>
- </marker>
- </defs>
- <!-- 核心关系 -->
- <line x1="50%" y1="50%" x2="25%" y2="30%" stroke="#1890ff" stroke-width="2" opacity="0.6" marker-end="url(#arrowhead)"/>
- <line x1="50%" y1="50%" x2="75%" y2="30%" stroke="#1890ff" stroke-width="2" opacity="0.6" marker-end="url(#arrowhead)"/>
- <line x1="50%" y1="50%" x2="15%" y2="70%" stroke="#1890ff" stroke-width="2" opacity="0.6" marker-end="url(#arrowhead)"/>
- <line x1="50%" y1="50%" x2="40%" y2="20%" stroke="#1890ff" stroke-width="2" opacity="0.6" marker-end="url(#arrowhead)"/>
- <line x1="50%" y1="50%" x2="60%" y2="80%" stroke="#1890ff" stroke-width="2" opacity="0.6" marker-end="url(#arrowhead)"/>
- <line x1="50%" y1="50%" x2="85%" y2="70%" stroke="#1890ff" stroke-width="2" opacity="0.6" marker-end="url(#arrowhead)"/>
- </svg>
- </div>
- <div class="graph-legend">
- <div class="legend-item"><span class="legend-dot core"></span><span>核心实体</span></div>
- <div class="legend-item"><span class="legend-dot concept"></span><span>概念</span></div>
- <div class="legend-item"><span class="legend-dot data"></span><span>数据</span></div>
- <div class="legend-item"><span class="legend-dot location"></span><span>地点</span></div>
- </div>
- </div>
- <!-- 列表视图 -->
- <div class="kg-list-view" id="listView" style="display: none;">
- <div class="list-search">
- <input type="text" placeholder="🔍 搜索要素..." class="list-search-input" oninput="filterEntities(this.value)">
- </div>
- <div class="entity-categories">
- <div class="category-section">
- <div class="category-header">
- <span class="category-icon">🏢</span>
- <span class="category-title">核心实体 (1)</span>
- </div>
- <div class="entity-items">
- <div class="entity-item" onclick="highlightEntity('智慧园区')">
- <div class="entity-icon">🏢</div>
- <div class="entity-info">
- <div class="entity-name">智慧园区</div>
- <div class="entity-meta">6个关联 • 3个来源文档</div>
- </div>
- <div class="entity-actions">
- <button class="entity-action-btn" onclick="locateEntity('智慧园区')">📍</button>
- <button class="entity-action-btn" onclick="editEntity('智慧园区')">✏️</button>
- </div>
- </div>
- </div>
- </div>
- <div class="category-section">
- <div class="category-header">
- <span class="category-icon">💡</span>
- <span class="category-title">概念 (4)</span>
- </div>
- <div class="entity-items">
- <div class="entity-item" onclick="highlightEntity('产业升级')">
- <div class="entity-icon">📈</div>
- <div class="entity-info">
- <div class="entity-name">产业升级</div>
- <div class="entity-meta">2个关联 • 2个来源文档</div>
- </div>
- <div class="entity-actions">
- <button class="entity-action-btn" onclick="locateEntity('产业升级')">📍</button>
- <button class="entity-action-btn" onclick="editEntity('产业升级')">✏️</button>
- </div>
- </div>
- <div class="entity-item" onclick="highlightEntity('城市现代化')">
- <div class="entity-icon">🌆</div>
- <div class="entity-info">
- <div class="entity-name">城市现代化</div>
- <div class="entity-meta">2个关联 • 1个来源文档</div>
- </div>
- <div class="entity-actions">
- <button class="entity-action-btn" onclick="locateEntity('城市现代化')">📍</button>
- <button class="entity-action-btn" onclick="editEntity('城市现代化')">✏️</button>
- </div>
- </div>
- <div class="entity-item" onclick="highlightEntity('智能化管理')">
- <div class="entity-icon">🤖</div>
- <div class="entity-info">
- <div class="entity-name">智能化管理</div>
- <div class="entity-meta">1个关联 • 1个来源文档</div>
- </div>
- <div class="entity-actions">
- <button class="entity-action-btn" onclick="locateEntity('智能化管理')">📍</button>
- <button class="entity-action-btn" onclick="editEntity('智能化管理')">✏️</button>
- </div>
- </div>
- <div class="entity-item" onclick="highlightEntity('低碳绿色')">
- <div class="entity-icon">🌱</div>
- <div class="entity-info">
- <div class="entity-name">低碳绿色</div>
- <div class="entity-meta">1个关联 • 1个来源文档</div>
- </div>
- <div class="entity-actions">
- <button class="entity-action-btn" onclick="locateEntity('低碳绿色')">📍</button>
- <button class="entity-action-btn" onclick="editEntity('低碳绿色')">✏️</button>
- </div>
- </div>
- </div>
- </div>
- <div class="category-section">
- <div class="category-header">
- <span class="category-icon">📊</span>
- <span class="category-title">数据 (8)</span>
- </div>
- <div class="entity-items">
- <div class="entity-item" onclick="highlightEntity('1,789亿元')">
- <div class="entity-icon">💰</div>
- <div class="entity-info">
- <div class="entity-name">1,789亿元</div>
- <div class="entity-meta">市场规模数据 • 1个来源</div>
- </div>
- <div class="entity-actions">
- <button class="entity-action-btn" onclick="locateEntity('1,789亿元')">📍</button>
- <button class="entity-action-btn" onclick="editEntity('1,789亿元')">✏️</button>
- </div>
- </div>
- <div class="entity-item" onclick="highlightEntity('18%')">
- <div class="entity-icon">📊</div>
- <div class="entity-info">
- <div class="entity-name">18%</div>
- <div class="entity-meta">增长率数据 • 1个来源</div>
- </div>
- <div class="entity-actions">
- <button class="entity-action-btn" onclick="locateEntity('18%')">📍</button>
- <button class="entity-action-btn" onclick="editEntity('18%')">✏️</button>
- </div>
- </div>
- <div class="entity-item" onclick="highlightEntity('50万平方米')">
- <div class="entity-icon">📐</div>
- <div class="entity-info">
- <div class="entity-name">50万平方米</div>
- <div class="entity-meta">面积数据 • 1个来源</div>
- </div>
- <div class="entity-actions">
- <button class="entity-action-btn" onclick="locateEntity('50万平方米')">📍</button>
- <button class="entity-action-btn" onclick="editEntity('50万平方米')">✏️</button>
- </div>
- </div>
- <div class="entity-item" onclick="highlightEntity('12.5亿元')">
- <div class="entity-icon">💵</div>
- <div class="entity-info">
- <div class="entity-name">12.5亿元</div>
- <div class="entity-meta">投资额数据 • 1个来源</div>
- </div>
- <div class="entity-actions">
- <button class="entity-action-btn" onclick="locateEntity('12.5亿元')">📍</button>
- <button class="entity-action-btn" onclick="editEntity('12.5亿元')">✏️</button>
- </div>
- </div>
- </div>
- </div>
- <div class="category-section">
- <div class="category-header">
- <span class="category-icon">📍</span>
- <span class="category-title">地点 (1)</span>
- </div>
- <div class="entity-items">
- <div class="entity-item" onclick="highlightEntity('华南地区')">
- <div class="entity-icon">📍</div>
- <div class="entity-info">
- <div class="entity-name">华南地区</div>
- <div class="entity-meta">地理位置 • 1个来源</div>
- </div>
- <div class="entity-actions">
- <button class="entity-action-btn" onclick="locateEntity('华南地区')">📍</button>
- <button class="entity-action-btn" onclick="editEntity('华南地区')">✏️</button>
- </div>
- </div>
- </div>
- </div>
- </div>
- </div>
- </div>
- </div>
- <!-- FAB资源监控 -->
- <div style="position:fixed;bottom:24px;right:24px;z-index:1000;" id="fabContainer">
- <div id="fabPanel" style="position:absolute;bottom:60px;right:0;width:260px;background:var(--white);border-radius:12px;box-shadow:0 8px 32px rgba(0,0,0,0.15);display:none;overflow:hidden;">
- <div style="padding:14px 16px;background:linear-gradient(135deg, #52c41a 0%, #13c2c2 100%);color:white;font-weight:600;font-size:13px;">📊 资源监控</div>
- <div style="padding:14px 16px;">
- <div style="margin-bottom:12px;">
- <div style="display:flex;justify-content:space-between;margin-bottom:4px;font-size:12px;"><span>Token 消耗</span><span style="font-weight:500;">15.6K / 20K</span></div>
- <div style="height:6px;background:var(--bg);border-radius:3px;"><div style="height:100%;width:78%;background:var(--warning);border-radius:3px;"></div></div>
- </div>
- <div style="margin-bottom:12px;">
- <div style="display:flex;justify-content:space-between;margin-bottom:4px;font-size:12px;"><span>GPU 显存</span><span style="font-weight:500;">3.6G / 8G</span></div>
- <div style="height:6px;background:var(--bg);border-radius:3px;"><div style="height:100%;width:45%;background:var(--success);border-radius:3px;"></div></div>
- </div>
- <div style="display:flex;justify-content:space-between;padding:10px;background:var(--bg);border-radius:8px;">
- <div style="text-align:center;"><div style="font-size:16px;font-weight:600;color:var(--primary);">¥3.12</div><div style="font-size:10px;color:var(--text3);">本次会话</div></div>
- <div style="text-align:center;"><div style="font-size:16px;font-weight:600;color:var(--primary);">¥127.50</div><div style="font-size:10px;color:var(--text3);">本月累计</div></div>
- </div>
- </div>
- </div>
- <button id="fabBtn" style="width:50px;height:50px;border-radius:50%;background:linear-gradient(135deg, #52c41a 0%, #13c2c2 100%);border:none;cursor:grab;display:flex;align-items:center;justify-content:center;font-size:20px;color:white;box-shadow:0 6px 20px rgba(82,196,26,0.35);">📊</button>
- </div>
- <script>
- (function(){
- const container = document.getElementById('fabContainer');
- const btn = document.getElementById('fabBtn');
- if (!container || !btn) return;
- let dragging = false;
- let moved = false;
- let startX = 0, startY = 0, origLeft = 0, origTop = 0;
- // 默认位置:固定在右下角(24px 间距)
- container.style.right = '24px';
- container.style.bottom = '24px';
- container.style.left = '';
- container.style.top = '';
- function start(e) {
- const evt = e.touches ? e.touches[0] : e;
- dragging = true;
- moved = false;
- startX = evt.clientX;
- startY = evt.clientY;
- const rect = container.getBoundingClientRect();
- origLeft = rect.left;
- origTop = rect.top;
- document.addEventListener('mousemove', onMove);
- document.addEventListener('mouseup', end);
- document.addEventListener('touchmove', onMove, { passive: false });
- document.addEventListener('touchend', end);
- container.style.transition = 'none';
- btn.style.cursor = 'grabbing';
- e.preventDefault();
- }
- function onMove(e) {
- if (!dragging) return;
- const evt = e.touches ? e.touches[0] : e;
- const dx = evt.clientX - startX;
- const dy = evt.clientY - startY;
- if (Math.abs(dx) > 4 || Math.abs(dy) > 4) moved = true;
- const vw = window.innerWidth, vh = window.innerHeight;
- const rect = container.getBoundingClientRect();
- const w = rect.width, h = rect.height;
- let newLeft = origLeft + dx;
- let newTop = origTop + dy;
- newLeft = Math.max(8, Math.min(vw - w - 8, newLeft));
- newTop = Math.max(8, Math.min(vh - h - 8, newTop));
- container.style.left = newLeft + 'px';
- container.style.top = newTop + 'px';
- container.style.right = '';
- container.style.bottom = '';
- e.preventDefault();
- }
- function end() {
- if (!dragging) return;
- dragging = false;
- document.removeEventListener('mousemove', onMove);
- document.removeEventListener('mouseup', end);
- document.removeEventListener('touchmove', onMove);
- document.removeEventListener('touchend', end);
- container.style.transition = '';
- btn.style.cursor = 'grab';
- // 不持久化位置:拖动仅在当前会话有效,刷新/重新登录后恢复到默认右下角
- // if moved, prevent the immediate click from toggling the panel
- if (moved) {
- const preventer = function(ev) { ev.stopImmediatePropagation(); ev.preventDefault(); btn.removeEventListener('click', preventer, true); };
- btn.addEventListener('click', preventer, true);
- }
- }
- // replace inline onclick by controlled click handler
- btn.addEventListener('click', function(e){
- if (dragging || moved) { e.preventDefault(); e.stopPropagation(); return; }
- try { toggleFab(); } catch (err) { console.warn('toggleFab not available', err); }
- });
- // init and ensure position stays in viewport on resize
- window.addEventListener('resize', function(){
- try {
- const rect = container.getBoundingClientRect();
- const vw = window.innerWidth, vh = window.innerHeight;
- let left = rect.left, top = rect.top;
- const w = rect.width, h = rect.height;
- if (left > vw - 8) left = Math.max(8, vw - w - 8);
- if (top > vh - 8) top = Math.max(8, vh - h - 8);
- container.style.left = left + 'px';
- container.style.top = top + 'px';
- } catch (e) {}
- });
- btn.addEventListener('mousedown', start);
- btn.addEventListener('touchstart', start, { passive: false });
- loadPos();
- })();
- </script>
- <!-- 通知面板 -->
- <div class="notif-panel" id="notifPanel">
- <div style="padding:18px;border-bottom:1px solid var(--border);display:flex;justify-content:space-between;align-items:center;">
- <span style="font-size:15px;font-weight:600;">消息通知</span>
- <span style="font-size:12px;color:var(--primary);cursor:pointer;" onclick="showToast('已全部标为已读', 'success')">全部已读</span>
- </div>
- <div style="flex:1;overflow-y:auto;">
- <div style="padding:14px 18px;border-bottom:1px solid var(--border);background:var(--primary-light);cursor:pointer;" onclick="void(0)">
- <div style="display:flex;align-items:center;gap:8px;margin-bottom:6px;">
- <span style="width:22px;height:22px;background:#f6ffed;border-radius:50%;display:flex;align-items:center;justify-content:center;font-size:11px;">✅</span>
- <span style="flex:1;font-weight:500;font-size:13px;">文档解析完成</span>
- <span style="font-size:10px;color:var(--text3);">刚刚</span>
- </div>
- <div style="font-size:12px;color:var(--text2);">《市场调研数据.pdf》已解析完成,提取到35个实体</div>
- </div>
- <div style="padding:14px 18px;border-bottom:1px solid var(--border);cursor:pointer;">
- <div style="display:flex;align-items:center;gap:8px;margin-bottom:6px;">
- <span style="width:22px;height:22px;background:#fffbe6;border-radius:50%;display:flex;align-items:center;justify-content:center;font-size:11px;">💬</span>
- <span style="flex:1;font-weight:500;font-size:13px;">李四评论了您的报告</span>
- <span style="font-size:10px;color:var(--text3);">2小时前</span>
- </div>
- <div style="font-size:12px;color:var(--text2);">建议补充竞争格局分析...</div>
- </div>
- </div>
- </div>
- <!-- Toast容器 -->
- <div class="toast-container" id="toastContainer"></div>
- <!-- 遮罩 -->
- <div class="overlay" id="overlay" onclick="closeAll()"></div>
- <!-- 要素关系图谱遮罩 -->
- <div class="kg-overlay" id="kgOverlay" onclick="closeKnowledgeGraph()"></div>
- <script>
- // === 导航 ===
- document.addEventListener('DOMContentLoaded', function(){
- try {
- const ta = document.getElementById('aiTextarea');
- const send = document.getElementById('aiSendBtn');
- if (ta && send) {
- const update = () => {
- if (ta.value.trim().length > 0) send.classList.add('active');
- else send.classList.remove('active');
- };
- ta.addEventListener('input', update);
- update();
- }
- } catch (e) { console.warn('ai input init err', e); }
- });
- function navTo(page) {
- document.querySelectorAll('.page').forEach(p => p.classList.remove('active'));
- document.querySelectorAll('.menu-item').forEach(m => m.classList.remove('active'));
- document.getElementById('page-' + page)?.classList.add('active');
- document.querySelector('[data-page="' + page + '"]')?.classList.add('active');
- document.getElementById('page-editor').classList.remove('active');
- document.getElementById('sidebar').classList.remove('hidden');
- document.getElementById('mainContent').style.display = '';
- closeAll();
- }
- function goHome() { navTo('home'); }
- // === 编辑器 ===
- function openEditor() {
- document.querySelectorAll('.page').forEach(p => p.classList.remove('active'));
- document.getElementById('page-editor').classList.add('active');
- document.getElementById('sidebar').classList.add('hidden');
- document.getElementById('mainContent').style.display = 'none';
- closeAll();
- }
- function closeEditor() { navTo('home'); }
- // === 通知 ===
- function toggleNotif() {
- const panel = document.getElementById('notifPanel');
- const overlay = document.getElementById('overlay');
- const isOpen = panel.style.transform === 'translateX(0%)';
- panel.style.transform = isOpen ? 'translateX(100%)' : 'translateX(0%)';
- overlay.style.display = isOpen ? 'none' : 'block';
- }
- // === FAB ===
- function toggleFab() {
- const panel = document.getElementById('fabPanel');
- panel.style.display = panel.style.display === 'none' ? 'block' : 'none';
- }
- // === 思考模式 ===
- function setMode(el) {
- document.querySelectorAll('.mode-btn').forEach(b => b.classList.remove('active'));
- el.classList.add('active');
- showToast('已切换到 ' + el.textContent.trim(), 'success');
- }
- // === 首页AI输入 ===
- function toggleSendBtn() {
- const input = document.getElementById('homeAiInput');
- const btn = document.getElementById('homeSendBtn');
- btn.classList.toggle('show', input.value.trim().length > 0);
- }
- function handleHomeAi() {
- const input = document.getElementById('homeAiInput');
- if (input.value.trim()) {
- showToast('AI正在处理...', 'info');
- setTimeout(() => openEditor(), 1000);
- input.value = '';
- document.getElementById('homeSendBtn').classList.remove('show');
- }
- }
- // === 要素管理 ===
- const tagData = {
- smartpark: { icon: '🏢', type: 'entity', name: '智慧园区', typeText: '核心实体', source: '项目可行性研究报告.docx', relations: ['产业升级', '城市现代化', '智能化管理'] },
- upgrade: { icon: '📈', type: 'concept', name: '产业升级', typeText: '概念', source: '项目可行性研究报告.docx', relations: ['智慧园区', '城市现代化'] },
- modern: { icon: '🌆', type: 'concept', name: '城市现代化', typeText: '概念', source: '项目可行性研究报告.docx', relations: ['智慧园区', '产业升级'] },
- ai: { icon: '🤖', type: 'concept', name: '智能化管理', typeText: '技术', source: '技术方案说明.pdf', relations: ['智慧园区', '低碳绿色'] },
- green: { icon: '🌱', type: 'concept', name: '低碳绿色', typeText: '概念', source: '项目可行性研究报告.docx', relations: ['智慧园区'] },
- location: { icon: '📍', type: 'location', name: '华南地区', typeText: '地点', source: '项目可行性研究报告.docx', relations: ['智慧园区', '50万m²'] },
- data1: { icon: '💰', type: 'data', name: '1,789亿元', typeText: '市场规模数据', source: '市场调研数据.pdf', relations: ['智慧园区', '18%'] },
- data2: { icon: '📊', type: 'data', name: '18%', typeText: '增长率数据', source: '市场调研数据.pdf', relations: ['1,789亿元'] },
- data3: { icon: '📐', type: 'data', name: '50万平方米', typeText: '面积数据', source: '项目可行性研究报告.docx', relations: ['华南地区'] },
- data4: { icon: '💵', type: 'data', name: '12.5亿元', typeText: '投资额', source: '财务预测表.xlsx', relations: ['智慧园区'] },
- chart: { icon: '📊', type: 'asset', name: '趋势图', typeText: '图表资产', source: '资产库', relations: ['柱状图'] },
- template: { icon: '📝', type: 'asset', name: '结论模板', typeText: '文本模板', source: '资产库', relations: ['结论'] }
- };
- let currentTagName = '';
- function showTagPopover(event, tagId) {
- event.stopPropagation();
- const popover = document.getElementById('elementPopover');
- const data = tagData[tagId];
- if (!data) return;
- currentTagName = data.name;
- document.getElementById('popoverIcon').className = 'popover-icon ' + data.type;
- document.getElementById('popoverIcon').textContent = data.icon;
- document.getElementById('popoverTitle').textContent = data.name;
- document.getElementById('popoverType').textContent = data.typeText;
- document.getElementById('popoverSource').textContent = data.source;
- const relationsEl = document.getElementById('popoverRelations');
- relationsEl.innerHTML = data.relations.map(r =>
- `<span class="popover-relation" onclick="showToast('跳转到: ${r}', 'info')">${r}</span>`
- ).join('');
- const rect = event.currentTarget.getBoundingClientRect();
- popover.style.top = Math.min(rect.bottom + 8, window.innerHeight - 300) + 'px';
- popover.style.left = Math.min(rect.left, window.innerWidth - 300) + 'px';
- popover.classList.add('show');
- }
- function hideTagPopover() {
- document.getElementById('elementPopover').classList.remove('show');
- }
- // === Sync tags from editor content to right-panel lists ===
- function sanitizeId(name) {
- return 'tag_' + name.replace(/\s+/g, '_').replace(/[^\w\-]/g, '').toLowerCase();
- }
- function rgbToHex(rgb) {
- if (!rgb) return '';
- const m = rgb.match(/rgba?\((\d+),\s*(\d+),\s*(\d+)/i);
- if (!m) return '';
- const r = parseInt(m[1]), g = parseInt(m[2]), b = parseInt(m[3]);
- return "#" + ((1 << 24) + (r << 16) + (g << 8) + b).toString(16).slice(1);
- }
- function isColorRed(rgb) {
- if (!rgb) return false;
- const m = rgb.match(/rgba?\((\d+),\s*(\d+),\s*(\d+)/i);
- if (!m) return false;
- const r = parseInt(m[1]), g = parseInt(m[2]), b = parseInt(m[3]);
- return (r > 200 && g < 130 && b < 130);
- }
- function syncTagsFromContent() {
- try {
- console.debug('[debug] syncTagsFromContent start');
- const nodes = document.querySelectorAll('.editor-content .entity-highlight');
- const dynContainer = document.getElementById('dynamicTags');
- const statContainer = document.getElementById('staticTags');
- if (!dynContainer || !statContainer) return;
- // clear containers
- dynContainer.innerHTML = '';
- statContainer.innerHTML = '';
- nodes.forEach(node => {
- try {
- // extract visible text only (exclude badge/span text)
- let rawText = '';
- node.childNodes.forEach(n => {
- if (n.nodeType === Node.TEXT_NODE) rawText += n.textContent;
- else if (n.nodeType === Node.ELEMENT_NODE && n.classList && n.classList.contains('tag-name')) rawText += n.textContent;
- });
- rawText = (rawText || node.textContent || '').trim();
- if (!rawText) {
- console.debug('[debug] syncTagsFromContent: skipping empty node', node);
- return;
- }
- const computed = window.getComputedStyle(node);
- let bg = computed.backgroundColor || '';
- const border = computed.borderColor || '';
- const color = computed.color || '';
- console.debug('[debug] tag', rawText, { background: bg, border: border, color: color });
- // prefer background if not transparent
- if (!bg || bg === 'transparent' || bg.indexOf('0, 0, 0, 0') !== -1) {
- bg = border || color || '';
- }
- // Improved dynamic detection:
- // 1) explicit data-source indicating AI/generated
- // 2) presence of a .source-badge.ai inside the node
- // 3) existing color-based heuristic (fallback)
- let isDynamic = false;
- try {
- const ds = (node.dataset && node.dataset.source) ? String(node.dataset.source) : '';
- const category = (node.dataset && node.dataset.category) ? String(node.dataset.category) : '';
- if (ds && /ai|生成|AI/i.test(ds)) {
- isDynamic = true;
- } else if (category && /市场规模数据|data|AI/i.test(category)) {
- isDynamic = true;
- } else if (node.querySelector && node.querySelector('.source-badge.ai')) {
- isDynamic = true;
- } else {
- isDynamic = isColorRed(bg) || isColorRed(border) || /#ff4d4f/i.test((bg || '') + (border || ''));
- }
- } catch (sigErr) {
- console.warn('dynamic detection fallback err', sigErr);
- isDynamic = isColorRed(bg) || isColorRed(border) || /#ff4d4f/i.test((bg || '') + (border || ''));
- }
- console.debug('[debug] determined isDynamic=', isDynamic, 'for', rawText);
- const id = sanitizeId(rawText);
- if (!tagData[id]) {
- tagData[id] = { icon: '🏷️', type: 'entity', name: rawText, typeText: '来源: 文本', source: '文档', relations: [] };
- }
- const span = document.createElement('span');
- span.className = 'element-tag ' + (isDynamic ? 'dynamic' : 'static') + ' mapped';
- span.setAttribute('draggable', 'true');
- span.onclick = function (e) { e.stopPropagation(); showTagPopover(e, id); };
- span.ondragstart = (e) => handleTagDragStart(e, rawText);
- span.ondragend = (e) => handleTagDragEnd(e);
- // apply color styling to match source highlight (if available)
- const fill = rgbToHex(bg);
- if (fill) {
- span.style.background = fill;
- // choose light or dark text based on luminance
- const hex = fill.replace('#','');
- const r = parseInt(hex.substring(0,2),16), g = parseInt(hex.substring(2,4),16), b = parseInt(hex.substring(4,6),16);
- const luminance = (0.299*r + 0.587*g + 0.114*b);
- span.style.color = luminance > 160 ? '#000' : '#fff';
- span.style.border = '1px solid rgba(0,0,0,0.06)';
- }
- span.innerHTML = `<span class="tag-icon">${tagData[id].icon || '🏷️'}</span><span class="tag-name">${rawText}</span>`;
- if (isDynamic) {
- dynContainer.appendChild(span);
- }
- else statContainer.appendChild(span);
- } catch (e) { console.warn('syncTagsFromContent node err', e); }
- });
- // post-process: relocate any static tags that are actually red in the editor
- try { relocateRedStaticTags(dynContainer, statContainer); } catch (e) { console.warn('relocateRedStaticTags err', e); }
- console.debug('[debug] syncTagsFromContent done', { dynamicCount: dynContainer.children.length, staticCount: statContainer.children.length });
- } catch (e) { console.warn('syncTagsFromContent err', e); }
- }
- // Move any tags currently in static container that are red in the editor into dynamic container
- function relocateRedStaticTags(dynContainer, statContainer) {
- try {
- const staticChildren = Array.from(statContainer.children);
- if (staticChildren.length === 0) return;
- const editorNodes = Array.from(document.querySelectorAll('.editor-content .entity-highlight'));
- staticChildren.forEach(child => {
- try {
- const nameEl = child.querySelector('.tag-name');
- const tagName = nameEl ? nameEl.textContent.trim() : (child.textContent || '').trim();
- if (!tagName) return;
- // find corresponding editor node by exact text match (prefer full match)
- const sourceNode = editorNodes.find(n => {
- const text = (n.textContent || '').trim();
- return text === tagName || text.includes(tagName);
- });
- if (!sourceNode) return;
- const cs = window.getComputedStyle(sourceNode);
- const bg = cs.backgroundColor || cs.borderColor || cs.color || '';
- if (isColorRed(bg)) {
- // move element
- child.classList.remove('static');
- child.classList.add('dynamic');
- dynContainer.appendChild(child);
- console.debug('[debug] relocated tag to dynamic:', tagName);
- }
- } catch (e) { console.warn('relocate child err', e); }
- });
- } catch (e) { console.warn('relocateRedStaticTags overall err', e); }
- }
- // observe editor content changes and sync tags (debounced)
- (function() {
- let t = null;
- document.addEventListener('selectionchange', () => { /* no-op keep event loop active */ });
- document.addEventListener('DOMContentLoaded', () => {
- document.querySelectorAll('.editor-content').forEach(editor => {
- try {
- const mo = new MutationObserver(() => {
- clearTimeout(t);
- t = setTimeout(() => {
- try { syncTagsFromContent(); } catch(e) { console.warn('debounced sync err', e); }
- }, 150);
- });
- mo.observe(editor, { characterData: true, childList: true, subtree: true });
- } catch (e) { console.warn('attach mutation observer err', e); }
- });
- // initial sync after load
- try { syncTagsFromContent(); } catch(e) { console.warn('initial syncTagsFromContent err', e); }
- // ensure tabs trigger a sync when clicked
- try {
- const td = document.getElementById('tabDynamic');
- const ts = document.getElementById('tabStatic');
- if (td) { td.addEventListener('click', () => { try { syncTagsFromContent(); } catch(e){} }); }
- if (ts) { ts.addEventListener('click', () => { try { syncTagsFromContent(); } catch(e){} }); }
- } catch (e) { console.warn('attach tab click sync err', e); }
- });
- })();
- // 切换动态/静态要素 Tab
- function switchElementTab(tab) {
- try {
- const tabDyn = document.getElementById('tabDynamic');
- const tabStat = document.getElementById('tabStatic');
- const dyn = document.getElementById('dynamicTags');
- const stat = document.getElementById('staticTags');
- if (tab === 'dynamic') {
- if (tabDyn) tabDyn.classList.add('active');
- if (tabStat) tabStat.classList.remove('active');
- if (dyn) dyn.style.display = '';
- if (stat) stat.style.display = 'none';
- try { syncTagsFromContent(); } catch(e){ console.warn('sync on tab switch err', e); }
- } else {
- if (tabDyn) tabDyn.classList.remove('active');
- if (tabStat) tabStat.classList.add('active');
- if (dyn) dyn.style.display = 'none';
- if (stat) stat.style.display = '';
- try { syncTagsFromContent(); } catch(e){ console.warn('sync on tab switch err', e); }
- }
- } catch (e) { console.warn('switchElementTab err', e); }
- }
- function insertTagToEditor() {
- if (currentTagName) {
- showToast('已插入要素: ' + currentTagName, 'success');
- }
- }
- document.addEventListener('click', function(e) {
- if (!e.target.closest('.element-popover') && !e.target.closest('.element-tag')) {
- hideTagPopover();
- }
- });
- // === 标签拖拽功能 ===
- function handleTagDragStart(event, tagName) {
- currentTagName = tagName;
- event.currentTarget.classList.add('dragging');
- event.dataTransfer.setData('text/plain', tagName);
- event.dataTransfer.effectAllowed = 'copy';
- setTimeout(() => {
- document.querySelectorAll('.editor-content').forEach(el => {
- el.classList.add('drag-over');
- });
- }, 0);
- }
- function handleTagDragEnd(event) {
- event.currentTarget.classList.remove('dragging');
- document.querySelectorAll('.editor-content').forEach(el => {
- el.classList.remove('drag-over');
- });
- }
- // 编辑区放置事件
- document.addEventListener('DOMContentLoaded', function() {
- const editorContents = document.querySelectorAll('.editor-content');
- editorContents.forEach(editor => {
- editor.addEventListener('dragover', function(e) {
- e.preventDefault();
- e.dataTransfer.dropEffect = 'copy';
- });
- editor.addEventListener('drop', function(e) {
- e.preventDefault();
- const tagName = e.dataTransfer.getData('text/plain');
- if (tagName) {
- showToast('✓ 已插入要素: ' + tagName, 'success');
- }
- this.classList.remove('drag-over');
- });
- editor.addEventListener('dragleave', function(e) {
- if (!this.contains(e.relatedTarget)) {
- this.classList.remove('drag-over');
- }
- });
- });
- });
- // === 右键菜单 ===
- let selectedText = '';
- let selectedRange = null;
- function showContextMenu(event) {
- const selection = window.getSelection();
- selectedText = selection.toString().trim();
- selectedRange = selection.rangeCount > 0 ? selection.getRangeAt(0) : null;
- if (selectedText.length > 0) {
- event.preventDefault();
- const menu = document.getElementById('contextMenu');
- menu.style.top = event.clientY + 'px';
- menu.style.left = event.clientX + 'px';
- menu.classList.add('show');
- }
- }
- function hideContextMenu() {
- document.getElementById('contextMenu').classList.remove('show');
- }
- function execContextAction(action) {
- hideContextMenu();
- switch(action) {
- case 'copy':
- document.execCommand('copy');
- showToast('已复制到剪贴板', 'success');
- break;
- case 'cut':
- document.execCommand('cut');
- showToast('已剪切', 'success');
- break;
- case 'paste':
- document.execCommand('paste');
- break;
- case 'polish':
- showAiPolishConfirm();
- break;
- case 'spell':
- showSpellCheckConfirm();
- break;
- case 'mark':
- markAsEntity();
- break;
- case 'quote':
- const aiMessages = document.getElementById('aiMessages');
- const quotedText = selectedText.length > 50 ? selectedText.substring(0, 50) + '...' : selectedText;
- aiMessages.innerHTML += `
- <div class="msg user">
- <div class="msg-avatar">张</div>
- <div class="msg-bubble">
- <div style="padding:8px;background:rgba(255,255,255,0.5);border-radius:6px;margin-bottom:8px;font-size:12px;border-left:3px solid rgba(255,255,255,0.8);">
- 📝 引用: "${quotedText}"
- </div>
- 请基于这段内容帮我分析
- </div>
- </div>
- `;
- aiMessages.scrollTop = aiMessages.scrollHeight;
- showToast('已引用到AI助手', 'success');
- break;
- }
- }
- // === AI润色确认 ===
- function showAiPolishConfirm() {
- const polishedText = `经过AI润色优化:${selectedText.replace(/的/g, '的').replace(/了/g, '了').replace(/和/g, '与')}`;
- showConfirmDialog(
- 'AI润色确认',
- `原文: "${selectedText}"<br><br>润色后: "${polishedText}"`,
- () => applyTextReplacement(polishedText),
- () => showToast('已取消润色', 'info')
- );
- }
- // === 拼写检查确认 ===
- function showSpellCheckConfirm() {
- // 模拟拼写检查结果
- const correctedText = selectedText.replace(/园区/g, '园区').replace(/智慧/g, '智慧');
- const hasErrors = correctedText !== selectedText;
- if (hasErrors) {
- showConfirmDialog(
- '拼写检查结果',
- `发现拼写问题:<br>原文: "${selectedText}"<br>更正后: "${correctedText}"`,
- () => applyTextReplacement(correctedText),
- () => showToast('已忽略拼写建议', 'info')
- );
- } else {
- showToast('✓ 拼写检查完成,未发现错误', 'success');
- }
- }
- // === 标记为要素 ===
- function markAsEntity() {
- if (!selectedRange) return;
- const span = document.createElement('span');
- span.className = 'entity-highlight entity'; // 默认标记为entity类型
- span.contentEditable = 'false';
- // 标记来源为人工
- span.dataset.source = '人工标记';
- span.dataset.category = 'entity';
- span.onclick = function(e) { e.stopPropagation(); showDataRelationModal(selectedText, '自定义要素', 'entity', span.dataset.source); };
- span.textContent = selectedText;
- // 添加来源徽章
- const badge = document.createElement('span');
- badge.className = 'source-badge manual';
- badge.title = '来源:人工标记(点击查看详情)';
- badge.textContent = '人工';
- badge.onclick = function(e) { e.stopPropagation(); showDataRelationModal(selectedText, '自定义要素', 'entity', span.dataset.source); };
- span.appendChild(badge);
- selectedRange.deleteContents();
- selectedRange.insertNode(span);
- showToast(`✓ 已将"${selectedText}"标记为要素`, 'success');
- }
- // === 初始化:为已有实体标注添加来源徽章和类型样式(从 onclick 参数或 data-source 推断)===
- function initializeSourceBadges() {
- const nodes = document.querySelectorAll('.entity-highlight');
- nodes.forEach(node => {
- try {
- // 如果已经有 badge,就跳过
- if (node.querySelector && node.querySelector('.source-badge')) return;
- // 优先读取 data-source 和 data-category
- let source = node.dataset && node.dataset.source;
- let category = node.dataset && node.dataset.category;
- // 如果没有 data-source或data-category,从 onclick 属性解析参数
- if ((!source || !category) && node.getAttribute('onclick')) {
- const onclick = node.getAttribute('onclick');
- // 解析 showDataRelationModal('name','type','category','sourceFile')
- const m = onclick.match(/showDataRelationModal\(([^)]+)\)/);
- if (m && m[1]) {
- const parts = m[1].split(',').map(s => s.trim());
- if (parts.length >= 4) {
- if (!category) category = parts[2].replace(/^['"]|['"]$/g, '');
- if (!source) source = parts[3].replace(/^['"]|['"]$/g, '');
- }
- }
- }
- // 为元素添加类型类名(如果还没有的话)
- if (category && !node.classList.contains(category)) {
- node.classList.add(category);
- }
- // 标记类和徽章文字
- let cls = 'file', txt = '文件';
- if (!source) { cls = 'manual'; txt = '人工'; source = '未知'; }
- else if (/ai/i.test(source) || /生成/.test(source) || source === 'AI') { cls = 'ai'; txt = 'AI'; }
- else if (/人工|人工标记/.test(source)) { cls = 'manual'; txt = '人工'; }
- else { cls = 'file'; txt = source.split('/').pop(); if (txt.length > 10) txt = txt.slice(0,10) + '…'; }
- // 保存到dataset
- node.dataset.source = source;
- node.dataset.category = category || 'entity';
- const badge = document.createElement('span');
- badge.className = `source-badge ${cls}`;
- badge.title = `来源:${source}`;
- badge.textContent = txt;
- badge.onclick = function(e) { e.stopPropagation(); showDataRelationModal(node.textContent, '自定义要素', category || 'entity', source); };
- node.appendChild(badge);
- } catch (err) {
- console.error('initializeSourceBadges error', err);
- }
- });
- }
- // === 通用确认对话框 ===
- function showConfirmDialog(title, content, onConfirm, onCancel) {
- // 创建确认对话框
- const dialog = document.createElement('div');
- dialog.className = 'confirm-dialog';
- dialog.innerHTML = `
- <div class="confirm-content">
- <div class="confirm-header">
- <h3>${title}</h3>
- <button class="confirm-close" onclick="closeConfirmDialog(this)">×</button>
- </div>
- <div class="confirm-body">${content}</div>
- <div class="confirm-footer">
- <button class="btn" onclick="closeConfirmDialog(this); ${onCancel ? 'setTimeout(() => {' + onCancel.toString().replace(/^function\s*\(\)\s*\{/, '').replace(/}$/, '') + '}, 100)' : ''}">取消</button>
- <button class="btn btn-primary" onclick="closeConfirmDialog(this); ${onConfirm ? 'setTimeout(() => {' + onConfirm.toString().replace(/^function\s*\(\)\s*\{/, '').replace(/}$/, '') + '}, 100)' : ''}">确认应用</button>
- </div>
- </div>
- `;
- document.body.appendChild(dialog);
- setTimeout(() => dialog.classList.add('show'), 10);
- }
- function closeConfirmDialog(btn) {
- const dialog = btn.closest('.confirm-dialog');
- dialog.classList.remove('show');
- setTimeout(() => dialog.remove(), 300);
- }
- // === 文本替换功能 ===
- function applyTextReplacement(newText) {
- if (selectedRange) {
- selectedRange.deleteContents();
- selectedRange.insertNode(document.createTextNode(newText));
- showToast('✓ 内容已更新', 'success');
- }
- }
- // 点击其他地方关闭右键菜单
- document.addEventListener('click', function(e) {
- if (!e.target.closest('.context-menu')) {
- hideContextMenu();
- }
- });
- // === 实体标签编辑弹窗 ===
- const entityEditData = {
- smartpark: {
- icon: '🏢', name: '智慧园区', type: 'entity', typeText: '核心实体',
- source: '项目可行性研究报告.docx', originalValue: '智慧园区',
- relations: ['产业升级', '城市现代化', '智能化管理', '1,789亿元']
- }
- };
- let currentEditEntityId = null;
- function showEntityEditModal(event, entityId) {
- event.preventDefault();
- event.stopPropagation();
- const data = entityEditData[entityId];
- if (!data) return;
- currentEditEntityId = entityId;
- document.getElementById('editEntityIcon').textContent = data.icon;
- document.getElementById('editEntityName').textContent = data.name;
- document.getElementById('editEntityType').textContent = data.typeText + ' · 来自 ' + data.source;
- document.getElementById('editEntityValue').value = data.name;
- document.getElementById('editEntityCategory').value = data.type;
- const relationsEl = document.getElementById('editEntityRelations');
- relationsEl.innerHTML = data.relations.map(r =>
- `<span class="popover-relation" onclick="showToast('跳转到: ${r}', 'info')">${r}</span>`
- ).join('') + `<span style="padding:3px 8px;background:var(--primary-light);border-radius:4px;font-size:11px;color:var(--primary);cursor:pointer;" onclick="showToast('添加关联', 'info')">+ 添加</span>`;
- document.getElementById('entityEditModal').classList.add('show');
- }
- function closeEntityEditModal() {
- document.getElementById('entityEditModal').classList.remove('show');
- currentEditEntityId = null;
- }
- function saveEntityEdit() {
- const newValue = document.getElementById('editEntityValue').value;
- const newCategory = document.getElementById('editEntityCategory').value;
- if (currentEditEntityId && entityEditData[currentEditEntityId]) {
- entityEditData[currentEditEntityId].name = newValue;
- entityEditData[currentEditEntityId].type = newCategory;
- showToast('✓ 标签已更新: ' + newValue, 'success');
- }
- closeEntityEditModal();
- }
- // === AI对话 ===
- function handleAiKey(e) {
- if (e.key === 'Enter' && !e.shiftKey) {
- e.preventDefault();
- sendAiMsg();
- }
- }
- function autoResizeTextarea(el) {
- el.style.height = 'auto';
- el.style.height = Math.min(el.scrollHeight, 80) + 'px';
- }
- function sendAiMsg() {
- const textarea = document.getElementById('aiTextarea');
- const msg = textarea.value.trim();
- if (!msg) return;
- const container = document.getElementById('aiMessages');
- container.innerHTML += '<div class="msg user"><div class="msg-avatar">张</div><div class="msg-bubble">' + msg + '</div></div>';
- textarea.value = '';
- textarea.style.height = 'auto';
- container.scrollTop = container.scrollHeight;
- setTimeout(() => {
- container.innerHTML += '<div class="msg ai"><div class="msg-avatar">🤖</div><div class="msg-bubble">好的,我来帮您处理这个请求。基于已解析的文档,我找到了相关内容可以补充到报告中。</div></div>';
- container.scrollTop = container.scrollHeight;
- }, 1000);
- }
- // === 视图切换 ===
- function switchView(view) {
- const originalBtn = document.getElementById('viewOriginal');
- const markedBtn = document.getElementById('viewMarked');
- const originalContent = document.getElementById('contentOriginal');
- const markedContent = document.getElementById('contentMarked');
- if (view === 'original') {
- if (originalBtn) originalBtn.classList.add('active');
- if (markedBtn) markedBtn.classList.remove('active');
- if (originalContent) originalContent.style.display = 'block';
- if (markedContent) markedContent.style.display = 'none';
- showToast('已切换到原文视图 - 显示纯净文档内容', 'info');
- } else {
- if (originalBtn) originalBtn.classList.remove('active');
- if (markedBtn) markedBtn.classList.add('active');
- if (originalContent) originalContent.style.display = 'none';
- if (markedContent) markedContent.style.display = 'block';
- showToast('已切换到标记视图 - 显示AI提取的实体标记和分析内容', 'info');
- }
- }
- // === 文件高亮 ===
- function highlightFile(el) {
- document.querySelectorAll('.file-item').forEach(f => f.classList.remove('active'));
- el.classList.add('active');
- }
- // === 上传模拟 ===
- function simulateUpload() {
- showToast('请选择要上传的文件', 'info');
- }
- // === 导出菜单 ===
- function showExportMenu(btn) {
- const menu = document.getElementById('exportMenu');
- const rect = btn.getBoundingClientRect();
- menu.style.top = (rect.bottom + 8) + 'px';
- menu.style.right = (window.innerWidth - rect.right) + 'px';
- menu.style.display = 'block';
- }
- function hideExportMenu() {
- document.getElementById('exportMenu').style.display = 'none';
- }
- document.addEventListener('click', function(e) {
- if (!e.target.closest('[onclick*="showExportMenu"]') && !e.target.closest('#exportMenu')) {
- hideExportMenu();
- }
- });
- // === AI Tab切换 ===
- function switchAiTab(el, tab) {
- document.querySelectorAll('.ai-tab').forEach(t => t.classList.remove('active'));
- el.classList.add('active');
- showToast('切换到 ' + el.textContent.trim(), 'info');
- }
- // === AI建议 ===
- function acceptSuggestion() {
- try {
- const aiMessages = document.getElementById('aiMessages');
- if (aiMessages) {
- aiMessages.innerHTML += `<div class="msg user"><div class="msg-avatar">张</div><div class="msg-bubble">✓ 我已采纳建议</div></div>`;
- aiMessages.innerHTML += `<div class="msg ai"><div class="msg-avatar">🤖</div><div class="msg-bubble">已根据您的采纳更新内容并插入到文档中。</div></div>`;
- aiMessages.scrollTop = aiMessages.scrollHeight;
- }
- showToast && showToast('✓ 已采纳建议,已发送对话通知', 'success');
- const card = document.getElementById('aiSuggestionCard');
- if (card) try { card.remove(); } catch (e) {}
- } catch (e) { console.warn('acceptSuggestion err', e); }
- }
- function ignoreSuggestion() {
- try {
- const aiMessages = document.getElementById('aiMessages');
- if (aiMessages) {
- aiMessages.innerHTML += `<div class="msg user"><div class="msg-avatar">张</div><div class="msg-bubble">✕ 我已忽略该建议</div></div>`;
- aiMessages.innerHTML += `<div class="msg ai"><div class="msg-avatar">🤖</div><div class="msg-bubble">已忽略建议。如需恢复,请在“建议”中查找历史记录。</div></div>`;
- aiMessages.scrollTop = aiMessages.scrollHeight;
- }
- showToast && showToast('已忽略建议(已转为对话)', 'info');
- const card = document.getElementById('aiSuggestionCard');
- if (card) try { card.remove(); } catch (e) {}
- } catch (e) { console.warn('ignoreSuggestion err', e); }
- }
- function acceptContentSuggestion() {
- try {
- const aiMessages = document.getElementById('aiMessages');
- if (aiMessages) {
- aiMessages.innerHTML += `<div class="msg user"><div class="msg-avatar">张</div><div class="msg-bubble">✓ 请添加建议内容</div></div>`;
- aiMessages.innerHTML += `<div class="msg ai"><div class="msg-avatar">🤖</div><div class="msg-bubble">已插入竞争格局分析章节,您可以在文档中查看并编辑。</div></div>`;
- aiMessages.scrollTop = aiMessages.scrollHeight;
- }
- showToast && showToast('✓ 已添加建议内容(对话形式)', 'success');
- } catch (e) { console.warn('acceptContentSuggestion err', e); }
- }
- function insertGeneratedContent() {
- try {
- const aiMessages = document.getElementById('aiMessages');
- if (aiMessages) {
- aiMessages.innerHTML += `<div class="msg user"><div class="msg-avatar">张</div><div class="msg-bubble">➕ 插入 AI 生成内容</div></div>`;
- aiMessages.innerHTML += `<div class="msg ai"><div class="msg-avatar">🤖</div><div class="msg-bubble">已将 AI 生成的风险分析章节插入到报告中。</div></div>`;
- aiMessages.scrollTop = aiMessages.scrollHeight;
- }
- showToast && showToast('✓ 已插入 AI 生成内容(对话形式)', 'success');
- const card = document.querySelector('.ai-generated-card');
- if (card) try { card.remove(); } catch (e) {}
- } catch (e) { console.warn('insertGeneratedContent err', e); }
- }
- // 将 AI 建议卡片移动到 AI 聊天区(以对话形式呈现),避免页面布局错位
- (function(){
- function moveCardToChat(cardEl) {
- if (!cardEl) return false;
- const aiMessages = document.getElementById('aiMessages');
- if (!aiMessages) return false;
- const title = cardEl.querySelector('.ai-suggestion-title') ? cardEl.querySelector('.ai-suggestion-title').textContent : '';
- const contentEl = cardEl.querySelector('.ai-suggestion-content') || cardEl;
- const contentHtml = contentEl.innerHTML || cardEl.innerHTML;
- const wrapper = document.createElement('div');
- wrapper.className = 'msg ai';
- wrapper.innerHTML = `<div class="msg-avatar">🤖</div><div class="msg-bubble"><strong>${title}</strong><div style="margin-top:8px;">${contentHtml}</div></div>`;
- aiMessages.appendChild(wrapper);
- aiMessages.scrollTop = aiMessages.scrollHeight;
- return true;
- }
- document.addEventListener('DOMContentLoaded', function(){
- try {
- const suggest = document.getElementById('aiSuggestionCard');
- if (suggest) {
- moveCardToChat(suggest);
- try { suggest.remove(); } catch(e){}
- }
- const gen = document.querySelector('.ai-generated-card');
- if (gen) {
- moveCardToChat(gen);
- try { gen.remove(); } catch(e){}
- }
- } catch (e) { console.warn('move AI suggestion to chat err', e); }
- });
- })();
- // === Toast ===
- function showToast(msg, type) {
- const box = document.getElementById('toastContainer');
- const icons = { success: '✅', error: '❌', info: 'ℹ️', warning: '⚠️' };
- const colors = { success: '#f6ffed', error: '#fff1f0', info: 'var(--primary-light)', warning: '#fffbe6' };
- const textColors = { success: 'var(--success)', error: 'var(--danger)', info: 'var(--primary)', warning: 'var(--warning)' };
- const toast = document.createElement('div');
- toast.className = `toast show ${type}`;
- toast.innerHTML = '<span style="width:24px;height:24px;border-radius:50%;background:' + colors[type] + ';color:' + textColors[type] + ';display:flex;align-items:center;justify-content:center;font-size:12px;">' + icons[type] + '</span><span style="font-size:13px;margin-left:10px;">' + msg + '</span>';
- box.appendChild(toast);
- setTimeout(() => {
- toast.classList.remove('show');
- setTimeout(() => toast.remove(), 300);
- }, 3000);
- }
- // === 关闭所有 ===
- function closeAll() {
- document.getElementById('notifPanel').style.transform = 'translateX(100%)';
- document.getElementById('overlay').style.display = 'none';
- document.getElementById('fabPanel').style.display = 'none';
- }
- // === 数据关系表弹窗 ===
- let currentEntityData = {};
- function showDataRelationModal(entityName, entityType, entityCategory, sourceFile) {
- currentEntityData = {
- name: entityName,
- type: entityType,
- category: entityCategory,
- source: sourceFile,
- originalValue: entityName,
- currentValue: entityName
- };
- // 设置弹窗内容
- document.getElementById('relationEntityName').textContent = entityName;
- document.getElementById('relationEntityType').textContent = entityType + ' · 来自 ' + (sourceFile || '未知来源');
- // 动态创建或更新来源信息区域(如果 modal HTML 中不存在)
- let sourceInfoEl = document.getElementById('relationSourceInfo');
- if (!sourceInfoEl) {
- const titleEl = document.querySelector('.data-relation-title');
- if (titleEl) {
- sourceInfoEl = document.createElement('div');
- sourceInfoEl.id = 'relationSourceInfo';
- sourceInfoEl.className = 'relation-source';
- sourceInfoEl.style = 'margin-top:6px;font-size:12px;color:var(--text3);';
- titleEl.appendChild(sourceInfoEl);
- }
- }
- if (sourceInfoEl) {
- const safeSource = sourceFile || '未知来源';
- sourceInfoEl.innerHTML = `来源:<a href="#" id="relationSourceLink" onclick="openSourceFile(event, '${safeSource.replace(/'/g, "\\'")}')">查看来源文件</a>`;
- }
- // 设置图标
- const iconEl = document.getElementById('relationIcon');
- const icons = { entity: '🏢', concept: '💡', data: '📊', location: '📍' };
- iconEl.textContent = icons[entityCategory] || '🏷️';
- // 生成关系表
- generateRelationTable(entityName, entityType, entityCategory, sourceFile);
- // 显示弹窗
- document.getElementById('dataRelationModal').classList.add('show');
- }
- function generateRelationTable(entityName, entityType, entityCategory, sourceFile) {
- const tableBody = document.getElementById('relationTableBody');
- const relationTags = document.getElementById('relationTags');
- // 模拟关系数据
- const relationData = {
- '智慧园区': [
- { property: '实体名称', original: '智慧园区', current: '智慧园区', canEdit: true },
- { property: '实体类型', original: '核心实体', current: '核心实体', canEdit: false },
- { property: '数据来源', original: '项目可行性研究报告.docx', current: '项目可行性研究报告.docx', canEdit: false },
- { property: '出现次数', original: '8次', current: '8次', canEdit: false }
- ],
- '产业升级': [
- { property: '概念名称', original: '产业升级', current: '产业升级', canEdit: true },
- { property: '概念类型', original: '发展概念', current: '发展概念', canEdit: false },
- { property: '数据来源', original: '项目可行性研究报告.docx', current: '项目可行性研究报告.docx', canEdit: false },
- { property: '关联强度', original: '高', current: '高', canEdit: false }
- ],
- '1,789亿元': [
- { property: '数值', original: '1,789亿元', current: '1,789亿元', canEdit: true },
- { property: '数据类型', original: '市场规模', current: '市场规模', canEdit: false },
- { property: '数据来源', original: '市场调研数据.pdf', current: '市场调研数据.pdf', canEdit: false },
- { property: '置信度', original: '95%', current: '95%', canEdit: false }
- ]
- };
- const data = relationData[entityName] ? relationData[entityName].slice() : [
- { property: '标签名称', original: entityName, current: entityName, canEdit: true },
- { property: '标签类型', original: entityType, current: entityType, canEdit: false },
- { property: '数据来源', original: sourceFile || '未知来源', current: sourceFile || '未知来源', canEdit: false }
- ];
- // 确保展示来源文件(优先使用传入的 sourceFile)
- const hasSourceRow = data.some(r => /来源|数据来源/.test(r.property));
- if (!hasSourceRow) {
- data.unshift({ property: '来源文件', original: sourceFile || '未知来源', current: sourceFile || '未知来源', canEdit: false });
- } else if (sourceFile) {
- data.forEach(r => {
- if (/来源|数据来源/.test(r.property)) {
- r.original = sourceFile;
- r.current = sourceFile;
- }
- });
- }
- tableBody.innerHTML = data.map(row => `
- <tr>
- <td>${row.property}</td>
- <td class="original">${row.original}</td>
- <td class="current">
- ${row.canEdit ?
- `<input type="text" class="relation-input" value="${row.current}" onchange="updateEntityValue('${row.property}', this.value)">` :
- row.current
- }
- </td>
- <td>
- ${row.canEdit ? '<button class="btn" onclick="resetEntityValue(this)" style="font-size:11px;padding:4px 8px;">重置</button>' : '-'}
- </td>
- </tr>
- `).join('');
- // 生成关联标签
- const relatedEntities = ['产业升级', '城市现代化', '智能化管理', '1,789亿元', '华南地区'];
- relationTags.innerHTML = relatedEntities.map(entity => `
- <span class="relation-tag" onclick="showDataRelationModal('${entity}', '关联实体', 'entity', '关联文档')">${entity}</span>
- `).join('');
- }
- function updateEntityValue(property, newValue) {
- currentEntityData.currentValue = newValue;
- showToast(`已更新 ${property}: ${newValue}`, 'success');
- }
- function resetEntityValue(btn) {
- const input = btn.closest('tr').querySelector('.relation-input');
- input.value = currentEntityData.originalValue;
- currentEntityData.currentValue = currentEntityData.originalValue;
- showToast('已重置为原始值', 'info');
- }
- function saveDataRelationChanges() {
- if (currentEntityData.currentValue !== currentEntityData.originalValue) {
- showToast(`标签已更新: ${currentEntityData.originalValue} → ${currentEntityData.currentValue}`, 'success');
- // 这里可以添加实际的保存逻辑
- }
- closeDataRelationModal();
- }
- function closeDataRelationModal() {
- document.getElementById('dataRelationModal').classList.remove('show');
- }
- // === 要素关系图谱 ===
- function toggleKnowledgeGraph() {
- const modal = document.getElementById('knowledgeGraphModal');
- const overlay = document.getElementById('kgOverlay');
- const btn = document.getElementById('graphBtn');
- const isOpen = modal.classList.contains('show');
- if (isOpen) {
- modal.classList.remove('show');
- overlay.style.display = 'none';
- if (btn) btn.classList.remove('active');
- } else {
- modal.classList.add('show');
- overlay.style.display = 'block';
- if (btn) btn.classList.add('active');
- showToast('已打开要素关系图谱', 'info');
- }
- }
- function closeKnowledgeGraph() {
- const modal = document.getElementById('knowledgeGraphModal');
- const overlay = document.getElementById('kgOverlay');
- const btn = document.getElementById('graphBtn');
- modal.classList.remove('show');
- overlay.style.display = 'none';
- if (btn) btn.classList.remove('active');
- }
- function switchGraphView(view) {
- const graphView = document.getElementById('graphView');
- const listView = document.getElementById('listView');
- const graphBtn = document.getElementById('graphViewBtn');
- const listBtn = document.getElementById('listViewBtn');
- if (view === 'graph') {
- graphView.style.display = 'block';
- listView.style.display = 'none';
- graphBtn.classList.add('active');
- listBtn.classList.remove('active');
- showToast('切换到图谱视图', 'info');
- } else {
- graphView.style.display = 'none';
- listView.style.display = 'block';
- graphBtn.classList.remove('active');
- listBtn.classList.add('active');
- showToast('切换到列表视图', 'info');
- }
- }
- function filterEntities(query) {
- const items = document.querySelectorAll('.entity-item');
- const lowerQuery = query.toLowerCase();
- items.forEach(item => {
- const name = item.querySelector('.entity-name').textContent.toLowerCase();
- if (name.includes(lowerQuery) || query === '') {
- item.style.display = 'flex';
- } else {
- item.style.display = 'none';
- }
- });
- }
- function highlightEntity(entityName) {
- // 高亮图谱中的节点
- document.querySelectorAll('.graph-node').forEach(node => {
- node.classList.remove('highlighted');
- if (node.dataset.entity === entityName) {
- node.classList.add('highlighted');
- node.scrollIntoView({ behavior: 'smooth', block: 'center' });
- }
- });
- showToast('已高亮要素: ' + entityName, 'info');
- }
- function locateEntity(entityName) {
- closeKnowledgeGraph();
- // 切换到标记视图
- switchView('marked');
- // 滚动到对应位置(这里可以根据实际情况调整)
- showToast('已定位到要素: ' + entityName, 'success');
- }
- function editEntity(entityName) {
- showToast('编辑要素: ' + entityName, 'info');
- // 这里可以打开要素编辑弹窗
- }
- // 图谱节点点击事件
- document.addEventListener('DOMContentLoaded', function() {
- document.querySelectorAll('.graph-node').forEach(node => {
- node.addEventListener('click', function() {
- const entityName = this.dataset.entity;
- highlightEntity(entityName);
- });
- });
- });
-
- // 页面加载后初始化来源徽章
- document.addEventListener('DOMContentLoaded', function() {
- try {
- if (typeof initializeSourceBadges === 'function') initializeSourceBadges();
- } catch (e) {
- console.warn('initializeSourceBadges error', e);
- }
- });
- // === 段落选中与交互控制 ===
- // 将光标所在段落标记为 selected,仅高亮该段
- function updateSelectedParagraph() {
- try {
- const sel = window.getSelection();
- if (!sel || sel.rangeCount === 0) return;
- const anchor = sel.anchorNode;
- if (!anchor) return;
- // 找到包含光标的段落元素(p 或 div 作段落)
- let paragraph = null;
- if (anchor.nodeType === Node.TEXT_NODE) paragraph = anchor.parentElement.closest('p, div');
- else if (anchor.nodeType === Node.ELEMENT_NODE) paragraph = anchor.closest('p, div');
- // 仅在编辑区内生效
- document.querySelectorAll('.editor-content p, .editor-content div').forEach(el => el.classList.remove('selected'));
- if (paragraph && paragraph.closest('.editor-content')) {
- paragraph.classList.add('selected');
- }
- } catch (e) {
- console.warn('updateSelectedParagraph error', e);
- }
- }
- // 监听选择变化、点击、键盘输入以更新段落选中状态
- document.addEventListener('selectionchange', () => {
- const active = document.activeElement;
- if (active && active.classList && active.classList.contains('editor-content')) {
- updateSelectedParagraph();
- }
- });
- // 点击编辑区也触发更新(兼容鼠标点击)
- document.querySelectorAll('.editor-content').forEach(editor => {
- editor.addEventListener('click', updateSelectedParagraph);
- editor.addEventListener('keyup', updateSelectedParagraph);
- editor.addEventListener('input', updateSelectedParagraph);
- });
- // 点击页面其它区域时清除段落选中样式
- document.addEventListener('click', (e) => {
- if (!e.target.closest('.editor-content')) {
- document.querySelectorAll('.editor-content p.selected, .editor-content div.selected').forEach(p => p.classList.remove('selected'));
- }
- });
- // === AI润色/拼写确认与文本替换(右键) ===
- // showAiPolishConfirm / showSpellCheckConfirm / applyTextReplacement 已定义于右键逻辑区域
- // === 初始化 ===
- setTimeout(() => showToast('欢迎使用灵越智报平台 🎉', 'success'), 500);
- // 更新欢迎语
- (function() {
- const hour = new Date().getHours();
- let greeting = '早上好';
- if (hour >= 12 && hour < 18) greeting = '下午好';
- else if (hour >= 18) greeting = '晚上好';
- const el = document.querySelector('.welcome h1');
- if (el) el.innerHTML = greeting + ',张三!<span>智能报告,洞察未来。</span>';
- })();
- // 模拟解析进度
- let progress1 = 65;
- setInterval(() => {
- progress1 += Math.random() * 3;
- if (progress1 >= 100) {
- progress1 = 100;
- const el = document.querySelector('#parsingFile1 .file-status');
- if (el) {
- el.textContent = '✓ 已完成';
- el.className = 'file-status done';
- }
- } else {
- const el = document.querySelector('#parsingFile1 .file-status');
- if (el && el.classList.contains('parsing')) {
- el.textContent = '📊 解析中 ' + Math.floor(progress1) + '%';
- }
- }
- }, 2000);
- // === 文件项交互(悬浮图标、解析弹窗、查看/引用/删除) ===
- function initializeFileActions() {
- document.querySelectorAll('.file-item').forEach(item => {
- try {
- const statusEl = item.querySelector('.file-status');
- const statusParsing = statusEl && statusEl.classList.contains('parsing');
- const statusDone = statusEl && statusEl.classList.contains('done');
- // store demo content for viewing/引用
- if (!item.dataset.content) {
- item.dataset.content = `【${item.querySelector('.file-name') ? item.querySelector('.file-name').textContent : '文档'}】原文预览:这是用于演示的文档内容片段。`;
- }
- // create actions container
- let actions = item.querySelector('.file-actions');
- if (!actions) {
- actions = document.createElement('div');
- actions.className = 'file-actions';
- item.appendChild(actions);
- }
- actions.innerHTML = '';
- if (statusParsing) {
- // parsing: show delete icon on hover
- const delBtn = document.createElement('div');
- delBtn.className = 'action-btn';
- delBtn.title = '删除';
- delBtn.innerHTML = '🗑️';
- delBtn.onclick = (e) => { e.stopPropagation(); deleteFile(item); };
- actions.appendChild(delBtn);
- // clicking the file shows parsing popover
- item.onclick = (e) => { e.stopPropagation(); showParsingPopover(item); };
- } else {
- // done: show view, quote, delete
- const viewBtn = document.createElement('div');
- viewBtn.className = 'action-btn';
- viewBtn.title = '查看';
- viewBtn.innerHTML = '👁️';
- viewBtn.onclick = (e) => { e.stopPropagation(); showFilePreview(item); };
- actions.appendChild(viewBtn);
- const quoteBtn = document.createElement('div');
- quoteBtn.className = 'action-btn';
- quoteBtn.title = '引用到AI助手';
- quoteBtn.innerHTML = '🔗';
- quoteBtn.onclick = (e) => { e.stopPropagation(); quoteFileToAI(item); };
- actions.appendChild(quoteBtn);
- const delBtn = document.createElement('div');
- delBtn.className = 'action-btn';
- delBtn.title = '删除';
- delBtn.innerHTML = '🗑️';
- delBtn.onclick = (e) => { e.stopPropagation(); deleteFile(item); };
- actions.appendChild(delBtn);
- // clicking file selects it (preview)
- item.onclick = (e) => { e.stopPropagation(); showFilePreview(item); };
- }
- } catch (err) { console.error('init file action', err); }
- });
- }
- // 左侧 tabs 切换
- function switchLeftTab(tab) {
- const docs = document.getElementById('leftDocsSection');
- const files = document.getElementById('leftFilesSection');
- // safely toggle primary tab elements (some templates may use different ids)
- const tabDocsEl = document.getElementById('tabDocs');
- const tabFilesEl = document.getElementById('tabFiles');
- if (tabDocsEl) tabDocsEl.classList.toggle('active', tab === 'docs');
- if (tabFilesEl) tabFilesEl.classList.toggle('active', tab === 'files');
- // sync top header tabs if present (these IDs exist in the DOM)
- const topDocs = document.getElementById('tabDocsTop');
- const topFiles = document.getElementById('tabFilesTop');
- if (topDocs) topDocs.classList.toggle('active', tab === 'docs');
- if (topFiles) topFiles.classList.toggle('active', tab === 'files');
- // Keep the left panel split into two parts:
- // - top: dynamic area (.docs-area) that shows either "我的文档" or "我的附件"
- // - bottom: .recent-area (always visible)
- const docsArea = docs ? docs.querySelector('.docs-area') : null;
- const recentArea = docs ? docs.querySelector('.recent-area') : null;
- // Ensure the leftDocsSection is visible (we render top content into docsArea)
- if (docs) docs.style.display = '';
- if (files) files.style.display = 'none'; // keep files section unused visually
- if (recentArea) recentArea.style.display = '';
- if (tab === 'docs') {
- // show documents list in the top area
- if (docsArea) {
- docsArea.style.display = '';
- // re-render / sort documents if function exists
- try { if (typeof updateReportsCountAndSort === 'function') updateReportsCountAndSort(); } catch (e) { console.warn('updateReportsCountAndSort err', e); }
- // restore header title to "报告记录"
- try {
- const reportsTitleEl = document.getElementById('reportsTitle');
- const reportsCountEl = document.getElementById('reportsCount');
- if (reportsTitleEl && reportsTitleEl.firstChild) reportsTitleEl.firstChild.nodeValue = '报告记录 ';
- if (reportsCountEl) {
- // updateReportsCountAndSort already sets count, but ensure display format
- reportsCountEl.textContent = reportsCountEl.textContent || '· ' + (document.querySelectorAll('#leftDocsSection .doc-card').length || 0);
- }
- } catch (ee) { console.warn('restore reports title err', ee); }
- }
- } else {
- // show attachments list in the same top area
- if (docsArea) {
- docsArea.style.display = '';
- try { if (typeof renderAttachments === 'function') renderAttachments(); } catch (e) { console.warn('renderAttachments err', e); }
- }
- }
- }
- // 更新报告计数并按时间倒序排列列表
- function updateReportsCountAndSort() {
- try {
- const list = document.querySelector('#leftDocsSection .doc-list');
- if (!list) return;
- const items = Array.from(list.querySelectorAll('.doc-card'));
- // parse date from .doc-time like "2026/1/26 11:51:44"
- items.sort((a, b) => {
- const ta = a.querySelector('.doc-time') ? new Date(a.querySelector('.doc-time').textContent.replace(/-/g, '/')) : new Date(0);
- const tb = b.querySelector('.doc-time') ? new Date(b.querySelector('.doc-time').textContent.replace(/-/g, '/')) : new Date(0);
- return tb - ta;
- });
- // re-append in sorted order
- items.forEach(it => list.appendChild(it));
- // update count
- const count = items.length;
- const rc = document.getElementById('reportsCount');
- if (rc) rc.textContent = '· ' + count;
- const leftBadge = document.getElementById('leftDocsCount');
- if (leftBadge) leftBadge.textContent = count;
- } catch (e) { console.warn('updateReportsCountAndSort err', e); }
- }
- document.addEventListener('DOMContentLoaded', function() {
- try { updateReportsCountAndSort(); } catch (e) {}
- // wire up search/new buttons (placeholders)
- const searchBtn = document.getElementById('leftSearchBtn');
- if (searchBtn) searchBtn.onclick = () => { const q = prompt('搜索报告:'); if (q) alert('搜索: ' + q); };
- const newBtn = document.getElementById('newReportBtn');
- if (newBtn) newBtn.onclick = () => { alert('新建报告(示意)'); };
- });
- let currentParsingPopover = null;
- function showParsingPopover(item) {
- closeParsingPopover();
- const rect = item.getBoundingClientRect();
- const pop = document.createElement('div');
- pop.className = 'parsing-popover';
- pop.style.left = Math.min(rect.right + 12, window.innerWidth - 340) + 'px';
- pop.style.top = Math.max(rect.top, 80) + 'px';
- pop.innerHTML = `<div class="close-btn" onclick="closeParsingPopover()">×</div>
- <div class="title"><div>解析过程 — ${item.querySelector('.file-name') ? item.querySelector('.file-name').textContent : ''}</div></div>
- <div class="parsing-progress">
- <span class="seg active"></span>
- <span class="seg"></span>
- <span class="seg"></span>
- <span class="seg"></span>
- <span class="seg"></span>
- </div>
- <div class="steps-list">
- <div class="step active"><div class="dot"></div><div class="label">文档读取</div></div>
- <div class="step pending"><div class="dot"></div><div class="label">结构解析</div></div>
- <div class="step pending"><div class="dot"></div><div class="label">实体提取</div></div>
- <div class="step pending"><div class="dot"></div><div class="label">关系分析</div></div>
- <div class="step pending"><div class="dot"></div><div class="label">图谱构建</div></div>
- </div>
- <div style="margin-top:10px;font-size:12px;color:var(--text3);">注:此为演示流程,实际解析时间取决于文档大小与系统性能。</div>`;
- document.body.appendChild(pop);
- currentParsingPopover = pop;
- // simulate progress steps (vertical, completed=green, active=blue, pending=gray)
- let idx = 0;
- const steps = pop.querySelectorAll('.step');
- const segs = pop.querySelectorAll('.parsing-progress .seg');
- // ensure initial classes: first active, others pending
- steps.forEach((s, i) => {
- if (i === 0) { s.classList.remove('pending'); s.classList.add('active'); }
- else { s.classList.remove('active'); s.classList.add('pending'); }
- });
- const t = setInterval(() => {
- // mark previous as completed
- if (idx > 0 && steps[idx-1]) {
- steps[idx-1].classList.remove('active');
- steps[idx-1].classList.remove('pending');
- steps[idx-1].classList.add('completed');
- }
- // set current as active
- if (idx < steps.length && steps[idx]) {
- steps[idx].classList.remove('pending');
- steps[idx].classList.remove('completed');
- steps[idx].classList.add('active');
- }
- // update segs
- segs.forEach((s, i) => {
- if (i < idx) s.classList.add('active');
- else s.classList.remove('active');
- });
- idx++;
- if (idx > steps.length) {
- clearInterval(t);
- // mark last as completed
- if (steps[steps.length-1]) {
- steps[steps.length-1].classList.remove('active');
- steps[steps.length-1].classList.remove('pending');
- steps[steps.length-1].classList.add('completed');
- }
- showToast('解析完成:已生成要素与图谱', 'success');
- // update status to done for demo
- const statusEl = item.querySelector('.file-status');
- if (statusEl) { statusEl.textContent = '✓ 已完成'; statusEl.className = 'file-status done'; }
- initializeFileActions();
- closeParsingPopover();
- }
- }, 900);
- }
- function closeParsingPopover() {
- if (currentParsingPopover) { currentParsingPopover.remove(); currentParsingPopover = null; }
- }
- // === 本地文件预览映射与本地上下文存储(演示 / 可替换为后端接口) ===
- const fileContentsMap = {
- '市场调研数据.pdf': '【市场调研数据.pdf】\\n摘要:本报告基于2024年全国范围抽样调研,市场规模、增长率、细分行业表现等关键指标已整理。',
- '技术方案说明.pdf': '【技术方案说明.pdf】\\n摘要:本文档描述了平台架构、数据接入与解析流程、实体抽取模型与接口。',
- '项目可行性研究报告.docx': '【项目可行性研究报告.docx】\\n原文节选:本项目位于华南地区,规划总面积约50万平方米,预计总投资12.5亿元。',
- '财务预测表.xlsx': '【财务预测表.xlsx】\\n表格摘要:收入、利润、投资回收期与分年度预测数据(见表格)。'
- };
- const localKnowledgeContext = {
- files: {},
- addFile(fileName, content) { this.files[fileName] = content; },
- removeFile(fileName) { delete this.files[fileName]; },
- listFiles() { return Object.keys(this.files); },
- getCombinedContext() { return Object.values(this.files).join('\\n\\n'); }
- };
- function showFilePreview(item) {
- // 使用大弹窗模态展示文件预览(不再在右侧显示)
- closeFilePreview();
- const nameEl = item.querySelector('.file-name');
- const fileName = nameEl ? nameEl.textContent.trim() : null;
- const content = (fileName && fileContentsMap[fileName]) ? fileContentsMap[fileName] : (item.dataset.content || (fileName ? `原文:${fileName}` : '无预览内容'));
- const overlay = document.createElement('div');
- overlay.className = 'file-preview-modal-overlay';
- const modal = document.createElement('div');
- modal.className = 'file-preview-modal';
- modal.innerHTML = `
- <div class="modal-header">
- <div class="modal-title">${fileName || '文件预览'}</div>
- <div style="font-size:12px;color:var(--text3)">${new Date().toLocaleString()}</div>
- </div>
- <button class="file-preview-close" onclick="closeFilePreview()">×</button>
- <div class="modal-body" id="filePreviewContent">${content}</div>
- `;
- overlay.appendChild(modal);
- document.body.appendChild(overlay);
- // prevent scrolling behind modal
- document.body.style.overflow = 'hidden';
- showToast('已打开文件预览弹窗', 'info');
- }
- function closeFilePreview() {
- const overlay = document.querySelector('.file-preview-modal-overlay');
- if (overlay) overlay.remove();
- document.body.style.overflow = '';
- }
- // 将文件内容加入本地上下文(用于模拟后端检索/聊天上下文)
- function quoteFileToAI(item) {
- const nameEl = item.querySelector('.file-name');
- const fileName = nameEl ? nameEl.textContent.trim() : ('文件_' + Date.now());
- const content = (fileName && fileContentsMap[fileName]) ? fileContentsMap[fileName] : (item.dataset.content || fileName);
- localKnowledgeContext.addFile(fileName, content);
- const aiMessages = document.getElementById('aiMessages');
- const msg = `[知识背景引用] 已将 ${fileName} 的内容加入本地上下文。当前上下文文件:${localKnowledgeContext.listFiles().join(', ')}`;
- if (aiMessages) {
- aiMessages.innerHTML += `
- <div class="msg user">
- <div class="msg-avatar">张</div>
- <div class="msg-bubble">${msg}</div>
- </div>
- `;
- aiMessages.scrollTop = aiMessages.scrollHeight;
- }
- showToast('已把文件内容加入本地上下文,用于AI检索/问答(模拟)', 'success');
- }
- function deleteFile(item) {
- const name = item.querySelector('.file-name') ? item.querySelector('.file-name').textContent : '文件';
- if (!confirm(`确认删除 "${name}" 吗?`)) return;
- item.remove();
- showToast(`已删除 ${name}`, 'info');
- }
- // === 我的附件(左侧)渲染与操作 ===
- // 附件示例数据(显示为样例截图中的文件名与大小,parsedAt 用于排序)
- let attachmentsData = [
- { name: '市场调研数据.pdf', size: '5.8 MB', parsedAt: '2026-01-27T10:00:00' },
- { name: '技术方案说明.pdf', size: '3.6 MB', parsedAt: '2026-01-26T16:00:00' },
- { name: '项目可行性研究报告.docx', size: '2.4 MB', parsedAt: '2026-01-25T09:00:00' },
- { name: '财务预测表.xlsx', size: '1.2 MB', parsedAt: '2026-01-24T11:30:00' },
- { name: '会议纪要.md', size: '48 KB', parsedAt: '2026-01-23T08:00:00' }
- ];
- function formatParsedAt(iso) {
- const d = new Date(iso);
- if (isNaN(d)) return iso;
- const pad = n => n.toString().padStart(2, '0');
- return `${d.getFullYear()}/${d.getMonth()+1}/${d.getDate()} ${pad(d.getHours())}:${pad(d.getMinutes())}`;
- }
- function getIconForName(name) {
- const ext = (name.split('.').pop() || '').toLowerCase();
- if (ext === 'xlsx') return '<span class="file-badge xls">XLS</span>';
- if (ext === 'docx' || ext === 'doc') return '<span class="file-badge doc">DOC</span>';
- if (ext === 'pdf') return '<span class="file-badge pdf">PDF</span>';
- if (ext === 'md' || ext === 'txt') return '<span class="file-badge md">MD</span>';
- return '<span class="file-badge">FILE</span>';
- }
- function renderAttachments() {
- // Render into the left files top area by default (original behavior).
- // Fallback to leftDocsSection top area only if files area is missing.
- let container = document.querySelector('#leftFilesSection .doc-list');
- if (!container) container = document.querySelector('#leftDocsSection .docs-area .doc-list');
- if (!container) return;
- // sort by parsedAt desc
- attachmentsData.sort((a, b) => new Date(b.parsedAt) - new Date(a.parsedAt));
- container.innerHTML = attachmentsData.map(att => {
- return `
- <div class="doc-card file-item" data-name="${att.name}" data-parsedat="${att.parsedAt}">
- <div class="doc-thumb">${getIconForName(att.name)}</div>
- <div class="file-actions" style="display:flex;gap:6px;">
- <button class="action-btn" title="查看" onclick="showFilePreview(this.closest('.file-item'))">👁️</button>
- <button class="action-btn" title="引用到AI助手" onclick="quoteFileToAI(this.closest('.file-item'))">🔗</button>
- <button class="action-btn" title="删除" onclick="deleteAttachment(this.closest('.file-item'))">🗑️</button>
- </div>
- <div class="doc-meta">
- <div class="file-name">${att.name}</div>
- <div class="file-time">${att.size} · <span style="color:var(--success);font-weight:600;">✓ 已完成</span></div>
- </div>
- </div>
- `;
- }).join('');
- // update header title/count to "全部附件"
- try {
- const reportsTitleEl = document.getElementById('reportsTitle');
- const reportsCountEl = document.getElementById('reportsCount');
- if (reportsTitleEl && reportsTitleEl.firstChild) reportsTitleEl.firstChild.nodeValue = '全部附件 ';
- if (reportsCountEl) reportsCountEl.textContent = '· ' + attachmentsData.length;
- } catch (e) { console.warn('update reports title err', e); }
- // ensure hover/action wiring consistent
- initializeFileActions();
- }
- function deleteAttachment(item) {
- const name = item.querySelector('.file-name') ? item.querySelector('.file-name').textContent : '文件';
- if (!confirm(`确认删除 "${name}" 吗?`)) return;
- // remove from DOM
- item.remove();
- // remove from data model
- attachmentsData = attachmentsData.filter(a => a.name !== name);
- showToast(`已删除 ${name}`, 'info');
- }
- // 自动在 DOM 加载后渲染附件列表
- document.addEventListener('DOMContentLoaded', function() {
- try {
- console.debug('[debug] DOMContentLoaded: calling renderAttachments()');
- renderAttachments();
- } catch (e) { console.warn('renderAttachments err', e); }
- });
- // 安全绑定:确保顶部左右 tab 按钮可用并在页面刷新后渲染附件(防止浏览器缓存/执行顺序问题)
- document.addEventListener('DOMContentLoaded', function() {
- try {
- const tabDocsTop = document.getElementById('tabDocsTop');
- const tabFilesTop = document.getElementById('tabFilesTop');
- if (tabDocsTop) {
- try { tabDocsTop.removeAttribute('onclick'); } catch(e){}
- tabDocsTop.addEventListener('click', () => { console.debug('[debug] tabDocsTop clicked'); switchLeftTab('docs'); });
- }
- if (tabFilesTop) {
- try { tabFilesTop.removeAttribute('onclick'); } catch(e){}
- tabFilesTop.addEventListener('click', () => { console.debug('[debug] tabFilesTop clicked'); switchLeftTab('files'); });
- }
- // Force a re-render after a short delay to work around stale caches or race conditions
- setTimeout(() => {
- try { console.debug('[debug] delayed renderAttachments() executing'); if (typeof renderAttachments === 'function') renderAttachments(); } catch (e) { console.warn('delayed renderAttachments err', e); }
- }, 200);
- } catch (err) { console.warn('init tabs binding err', err); }
- });
- // call initializer after DOM ready
- document.addEventListener('DOMContentLoaded', function() {
- try { initializeFileActions(); } catch (e) { console.warn('init file actions err', e); }
- try { openEditor(); } catch (e) { console.warn('openEditor init err', e); }
- });
- // 确保左侧资源默认不选中任何文件
- document.addEventListener('DOMContentLoaded', function() {
- try {
- document.querySelectorAll('.left-panel .file-item.active').forEach(el => el.classList.remove('active'));
- } catch (e) { console.warn('clear active', e); }
- });
- // === 左侧资源面板 折叠/展开 控制 ===
- function toggleResourcePanel(btn) {
- const panel = document.querySelector('.left-panel');
- if (!panel) return;
- const isHidden = panel.dataset && panel.dataset.collapsed === 'true';
- if (!isHidden) {
- // collapse
- panel.dataset.collapsed = 'true';
- panel.style.display = 'none';
- if (btn) btn.textContent = '❯';
- // create small floating expand button
- let expand = document.getElementById('resourceExpandBtn');
- if (!expand) {
- expand = document.createElement('button');
- expand.id = 'resourceExpandBtn';
- expand.className = 'resource-expand-btn';
- expand.title = '展开资源面板';
- expand.innerHTML = '≡';
- expand.onclick = () => {
- expand.remove();
- panel.dataset.collapsed = 'false';
- panel.style.display = '';
- const pbtn = document.querySelector('.panel-toggle-btn');
- if (pbtn) pbtn.textContent = '❮';
- };
- document.body.appendChild(expand);
- }
- } else {
- // expand
- panel.dataset.collapsed = 'false';
- panel.style.display = '';
- if (btn) btn.textContent = '❮';
- const expand = document.getElementById('resourceExpandBtn');
- if (expand) expand.remove();
- }
- }
- </script>
- <script>
- // 页面级别强制:所有导航重定向到编辑器,并确保编辑器在初次打开时是可见的
- try {
- // 覆盖 navTo,以防止任何后续脚本或事件跳回首页/模板等
- window.navTo = function(page) {
- try { if (typeof openEditor === 'function') openEditor(); } catch (e) { console.warn('navTo override err', e); }
- return;
- };
- // 隐藏主内容并显示编辑器(双保险,防止样式闪烁)
- try {
- const mc = document.getElementById('mainContent');
- if (mc) mc.style.display = 'none';
- const pe = document.getElementById('page-editor');
- if (pe) pe.classList.add('active');
- // remove active state from other pages
- document.querySelectorAll('.page.active').forEach(p => { if (p.id !== 'page-editor') p.classList.remove('active'); });
- } catch (e) { console.warn('editor show err', e); }
- } catch (e) {
- console.warn('page-level override init err', e);
- }
- </script>
- <script>
- // 扫描并禁用所有内联 onclick 中的 openEditor/navTo(最终保障)
- try {
- document.querySelectorAll('[onclick]').forEach(el => {
- try {
- const v = el.getAttribute('onclick') || '';
- if (/\bopenEditor\s*\(|\bnavTo\s*\(/.test(v)) {
- el.setAttribute('onclick', 'void(0)');
- if (el.style) el.style.cursor = 'default';
- }
- } catch (e) {}
- });
- } catch (e) { console.warn('final disable onclicks err', e); }
- </script>
- <script>
- // 切换原文/标记(函数)
- function toggleView() {
- try {
- const ori = document.getElementById('contentOriginal');
- const mk = document.getElementById('contentMarked');
- const btn = document.getElementById('toggleViewBtn');
- if (!ori || !mk || !btn) return;
- // 切换视图显示
- if (ori.style.display === 'none') {
- ori.style.display = '';
- mk.style.display = 'none';
- } else {
- ori.style.display = 'none';
- mk.style.display = '';
- }
- // 切换按钮内的 icon(在两种 icon 之间切换)
- try {
- const hasBig = btn.querySelector && btn.querySelector('.icon-BIG_PROMOTION');
- if (hasBig) {
- btn.innerHTML = '<i class="iconfont icon-BASE_INFO"></i>';
- } else {
- btn.innerHTML = '<i class="iconfont icon-BIG_PROMOTION"></i>';
- }
- } catch (iconErr) {
- console.warn('toggleView icon swap err', iconErr);
- }
- } catch (e) { console.warn('toggleView err', e); }
- }
-
- // 绑定(以防内联 onclick 被覆写)
- document.addEventListener('DOMContentLoaded', function () {
- const tv = document.getElementById('toggleViewBtn');
- if (tv) {
- // remove any broken inline onclick and attach a robust listener
- try { tv.removeAttribute('onclick'); } catch(e) {}
- tv.addEventListener('click', toggleView);
- }
-
- // 搜索按钮 -> 聚焦顶部搜索框
- const searchBtn = document.getElementById('leftSearchBtn');
- if (searchBtn) {
- searchBtn.removeAttribute('onclick');
- searchBtn.addEventListener('click', () => {
- const topSearch = document.querySelector('.top-search') || document.querySelector('.search-input') || document.querySelector('.top-search-input');
- if (topSearch) { topSearch.focus(); if (typeof topSearch.select === 'function') topSearch.select(); }
- });
- }
-
- // 新建报告按钮 -> 在左侧插入示例新报告并进入编辑
- const newBtn = document.getElementById('newReportBtn');
- if (newBtn) {
- newBtn.removeAttribute('onclick');
- newBtn.addEventListener('click', () => {
- try {
- const list = document.querySelector('#leftDocsSection .doc-list');
- if (!list) return;
- const now = new Date();
- const pad = n => n.toString().padStart(2,'0');
- const fmt = `${now.getFullYear()}/${now.getMonth()+1}/${now.getDate()} ${pad(now.getHours())}:${pad(now.getMinutes())}:${pad(now.getSeconds())}`;
- const card = document.createElement('div');
- card.className = 'doc-card file-item';
- card.setAttribute('data-name', '新建报告 - 未命名');
- card.innerHTML = `<div class="doc-thumb">📄</div>
- <div class="doc-meta" style="width:100%;">
- <div class="doc-title">新建报告 - 未命名</div>
- <div style="display:flex;gap:10px;margin-top:8px;font-size:13px;color:var(--text2);">
- <span>📅 ${fmt}</span><span>👤 我</span>
- </div>
- </div>`;
- card.addEventListener('click', function(e){ e.stopPropagation(); showFilePreview(this); });
- list.insertBefore(card, list.firstChild);
- // update count/sort if function exists
- if (typeof updateReportsCountAndSort === 'function') updateReportsCountAndSort();
- // open editor if available
- try { if (typeof openEditor === 'function') openEditor(); } catch(e){}
- } catch (e) { console.warn('new report err', e); }
- });
- }
-
- // 安全绑定:给每个悬浮操作按钮(若存在)绑定实现(防止 inline 被清空)
- document.querySelectorAll('.action-btn').forEach(btn => {
- // if already has onclick via attribute, keep; else attach noop handlers for clarity
- if (!btn.onclick) {
- const t = btn.title || btn.getAttribute('title') || '';
- if (t.includes('下载') || t.includes('下载')) btn.addEventListener('click', () => { downloadReport(btn); });
- else if (t.includes('归档')) btn.addEventListener('click', () => { archiveReport(btn); });
- else if (t.includes('删除')) btn.addEventListener('click', () => { deleteReport(btn); });
- }
- });
- });
-
- // 占位:下载/归档/删除(若页面已定义则不覆盖)
- window.downloadReport = window.downloadReport || function(btn){
- try{ const card = btn.closest('.doc-card'); const name = card?.getAttribute('data-name')||'报告'; alert('下载:'+name); }catch(e){console.warn(e);}
- };
- window.archiveReport = window.archiveReport || function(btn){
- try{ const card = btn.closest('.doc-card'); if(card) card.classList.toggle('archived'); alert('已切换归档状态'); }catch(e){console.warn(e);}
- };
- window.deleteReport = window.deleteReport || function(btn){
- try{ const card = btn.closest('.doc-card'); const name = card?.getAttribute('data-name')||'报告'; if(!confirm('确认删除 "'+name+'"?')) return; card.remove(); if(typeof updateReportsCountAndSort==='function') updateReportsCountAndSort(); alert('已删除:'+name); }catch(e){console.warn(e);}
- };
- </script>
- <script>
- (function(){
- let pageSize = 10;
- let elements = [], filtered = [], currentPage = 1;
- function extractRawText(node) {
- let txt = '';
- node.childNodes.forEach(n => {
- if (n.nodeType === Node.TEXT_NODE) txt += n.textContent;
- else if (n.nodeType === Node.ELEMENT_NODE && !n.classList.contains('source-badge')) txt += n.textContent;
- });
- return (txt || node.textContent || '').trim();
- }
- function detectValueType(val) {
- if (!val) return '文本';
- if (/[¥¥元]|亿元|万/.test(val)) return '金额';
- const v = val.replace(/[,,\s]/g,'');
- if (/^\d+(\.\d+)?$/.test(v)) return '数字';
- return '文本';
- }
- function buildElementsFromContent() {
- elements = [];
- const nodes = document.querySelectorAll('.editor-content .entity-highlight');
- let idx = 0;
- nodes.forEach(node => {
- try {
- const name = extractRawText(node);
- if (!name) return;
- const desc = node.getAttribute('title') || '';
- const srcBadge = node.querySelector && node.querySelector('.source-badge');
- const src = (node.dataset && node.dataset.source) ? node.dataset.source : (srcBadge ? srcBadge.textContent : '未知');
- let elemType = '变量';
- try {
- const ds = (node.dataset && node.dataset.source) ? String(node.dataset.source) : '';
- if (ds && /ai|生成|AI/i.test(ds)) elemType = 'AI生成';
- else if (node.querySelector && node.querySelector('.source-badge.ai')) elemType = 'AI生成';
- } catch(e){}
- const valType = detectValueType(name);
- const id = 'el_' + (++idx) + '_' + sanitizeId(name);
- elements.push({ id, name, description: desc, type: valType, elementType: elemType, originalValue: name, newValue: name, source: src, node });
- } catch (e) { console.warn('buildElementsFromContent err', e); }
- });
- filtered = elements.slice();
- currentPage = 1;
- }
- function renderElementsList() {
- const tbody = document.getElementById('elementsTbody');
- const info = document.getElementById('elementsPaginationInfo');
- if (!tbody) return;
- const start = (currentPage-1)*pageSize;
- const pageItems = filtered.slice(start, start+pageSize);
- tbody.innerHTML = pageItems.map(it => `
- <tr data-id="${it.id}">
- <td>${it.name}</td>
- <td>${it.description || '-'}</td>
- <td>${it.type}</td>
- <td>${it.elementType}</td>
- <td class="orig">${it.originalValue}</td>
- <td class="new"><input type="text" value="${it.newValue}" style="width:100%;padding:6px;border:1px solid var(--border);border-radius:6px;" data-id="${it.id}" /></td>
- <td>${it.source}</td>
- <td><button class="icon-btn edit-btn" title="编辑" data-id="${it.id}"><i class="iconfont icon-EDIT"></i></button></td>
- </tr>
- `).join('');
- const total = filtered.length;
- const totalPages = Math.max(1, Math.ceil(total/pageSize));
- info.textContent = `第 ${currentPage} / ${totalPages} 页 • 共 ${total} 条`;
- renderPager();
- tbody.querySelectorAll('.edit-btn').forEach(btn=>{
- btn.addEventListener('click', ()=> {
- const id = btn.dataset.id;
- const input = tbody.querySelector('input[data-id="'+id+'"]');
- if (input) { input.focus(); input.select(); }
- });
- });
- }
- function renderPager() {
- const pagerContainer = document.getElementById('elementsPaginationInfo');
- if (!pagerContainer) return;
- const total = filtered.length;
- const totalPages = Math.max(1, Math.ceil(total/pageSize));
- // build numeric pager with up to 5 buttons (显示中间区域最多 5 个页码)
- const maxButtons = 5;
- let start = Math.max(1, currentPage - Math.floor(maxButtons/2));
- let end = start + maxButtons - 1;
- if (end > totalPages) { end = totalPages; start = Math.max(1, end - maxButtons + 1); }
- let html = `<div class="pager">`;
- // 恢复小型上一页 / 下一页 按钮(位于数字页码两侧)
- html += `<button class="pager-btn" data-action="prev" title="上一页">‹</button>`;
- for (let p = start; p <= end; p++) {
- html += `<button class="pager-btn ${p===currentPage?'active':''}" data-page="${p}">${p}</button>`;
- }
- html += `<button class="pager-btn" data-action="next" title="下一页">›</button>`;
- html += `</div>`;
- pagerContainer.innerHTML = html;
- // wire events
- pagerContainer.querySelectorAll('.pager-btn').forEach(btn=>{
- btn.addEventListener('click', (e)=>{
- const action = btn.dataset.action;
- if (action === 'first') { currentPage = 1; }
- else if (action === 'prev') { currentPage = Math.max(1, currentPage - 1); }
- else if (action === 'next') { currentPage = Math.min(totalPages, currentPage + 1); }
- else if (action === 'last') { currentPage = totalPages; }
- else if (btn.dataset.page) { currentPage = parseInt(btn.dataset.page, 10); }
- renderElementsList();
- });
- });
- // update page size select to current value
- const sel = document.getElementById('elementsPageSize');
- if (sel) sel.value = String(pageSize);
- }
- function openReportElementsModal() {
- try {
- buildElementsFromContent();
- renderElementsList();
- document.getElementById('reportElementsModal').classList.add('show');
- document.getElementById('reportElementsModal').setAttribute('aria-hidden','false');
- } catch (e) { console.warn('openReportElementsModal err', e); }
- }
- function closeReportElementsModal() {
- const el = document.getElementById('reportElementsModal');
- if (el) { el.classList.remove('show'); el.setAttribute('aria-hidden','true'); }
- }
- function applySearch() {
- const q = (document.getElementById('elementsSearchInput').value || '').toLowerCase().trim();
- if (!q) filtered = elements.slice();
- else filtered = elements.filter(it => (it.name||'').toLowerCase().includes(q) || (it.type||'').toLowerCase().includes(q) || (it.elementType||'').toLowerCase().includes(q));
- currentPage = 1;
- renderElementsList();
- }
- function changePage(delta) {
- const total = filtered.length;
- const totalPages = Math.max(1, Math.ceil(total/pageSize));
- currentPage = Math.min(Math.max(1, currentPage + delta), totalPages);
- renderElementsList();
- }
- function saveAllElements() {
- const tbody = document.getElementById('elementsTbody');
- if (!tbody) return;
- tbody.querySelectorAll('input[data-id]').forEach(inp => {
- const id = inp.dataset.id;
- const item = elements.find(e=>e.id===id);
- if (!item) return;
- item.newValue = inp.value;
- });
- elements.forEach(item => {
- try {
- if (item.newValue !== item.originalValue && item.node) {
- const badge = item.node.querySelector && item.node.querySelector('.source-badge');
- while (item.node.firstChild) item.node.removeChild(item.node.firstChild);
- item.node.appendChild(document.createTextNode(item.newValue));
- if (badge) item.node.appendChild(badge);
- item.node.dataset.currentValue = item.newValue;
- }
- } catch (e) { console.warn('apply element change err', e); }
- });
- showToast('✓ 要素已保存', 'success');
- closeReportElementsModal();
- }
- document.addEventListener('DOMContentLoaded', function(){
- const search = document.getElementById('elementsSearchInput');
- if (search) search.addEventListener('input', applySearch);
- const prev = document.getElementById('elementsPrevBtn');
- const next = document.getElementById('elementsNextBtn');
- if (prev) prev.addEventListener('click', ()=> changePage(-1));
- if (next) next.addEventListener('click', ()=> changePage(1));
- const saveBtn = document.getElementById('saveElementsBtn');
- if (saveBtn) saveBtn.addEventListener('click', saveAllElements);
- const openTopBtn = document.getElementById('reportElementsTopBtn');
- if (openTopBtn) openTopBtn.addEventListener('click', openReportElementsModal);
- const pageSizeSel = document.getElementById('elementsPageSize');
- if (pageSizeSel) {
- pageSizeSel.addEventListener('change', function(){
- const v = parseInt(this.value, 10) || 10;
- pageSize = v;
- currentPage = 1;
- renderElementsList();
- });
- }
- });
- window.openReportElementsModal = openReportElementsModal;
- window.closeReportElementsModal = closeReportElementsModal;
- })();
- </script>
- <script>
- // report status quick toggle
- (function(){
- const tag = document.getElementById('reportStatusTag');
- if (!tag) return;
- const statuses = ['草稿','审核中','已定稿'];
- let idx = 0;
- tag.addEventListener('click', function(){
- idx = (idx + 1) % statuses.length;
- tag.textContent = statuses[idx];
- showToast && showToast('状态已切换:' + statuses[idx], 'info');
- });
- })();
- </script>
- <script>
- (function(){
- let tooltip = null;
- function createTooltip() {
- tooltip = document.createElement('div');
- tooltip.className = 'icon-tooltip';
- document.body.appendChild(tooltip);
- return tooltip;
- }
- function showTooltip(el, text) {
- if (!tooltip) createTooltip();
- tooltip.textContent = text;
- tooltip.style.opacity = '1';
- const rect = el.getBoundingClientRect();
- const top = Math.max(8, rect.top - 36);
- const left = rect.left + (rect.width/2) - (tooltip.offsetWidth/2);
- tooltip.style.top = top + 'px';
- tooltip.style.left = Math.max(8, left) + 'px';
- }
- function hideTooltip() {
- if (!tooltip) return;
- tooltip.style.opacity = '0';
- }
- document.addEventListener('DOMContentLoaded', function(){
- document.querySelectorAll('.icon-btn, .ai-icon-btn').forEach(btn => {
- btn.addEventListener('mouseenter', function(){
- const txt = btn.dataset.tooltip || btn.getAttribute('title') || btn.getAttribute('aria-label') || '';
- if (txt) {
- showTooltip(btn, txt);
- }
- });
- btn.addEventListener('mouseleave', function(){ hideTooltip(); });
- });
- });
- })();
- </script>
- </body>
- <script>
- // 切换原文 / 标记(同时切换按钮内 icon)
- function toggleView() {
- try {
- const ori = document.getElementById('contentOriginal');
- const mk = document.getElementById('contentMarked');
- const btn = document.getElementById('toggleViewBtn');
- if (!ori || !mk || !btn) return;
- if (ori.style.display === 'none') {
- ori.style.display = '';
- mk.style.display = 'none';
- } else {
- ori.style.display = 'none';
- mk.style.display = '';
- }
- try {
- const hasBig = btn.querySelector && btn.querySelector('.icon-BIG_PROMOTION');
- if (hasBig) {
- btn.innerHTML = '<i class="iconfont icon-BASE_INFO"></i>';
- } else {
- btn.innerHTML = '<i class="iconfont icon-BIG_PROMOTION"></i>';
- }
- } catch (iconErr) {
- console.warn('toggleView icon swap err', iconErr);
- }
- } catch (e) { console.warn('toggleView err', e); }
- }
-
- // 更多菜单(示意)
- function showMoreMenu(btn) { try { showToast && showToast('更多菜单(示意)', 'info'); } catch(e){} }
-
- // 报告操作(占位实现)
- window.downloadReport = window.downloadReport || function(btn) {
- try {
- const card = btn.closest('.doc-card');
- const name = card?.getAttribute('data-name') || '报告';
- showToast && showToast('开始下载:' + name, 'info');
- } catch (e) { console.warn(e); }
- };
- window.archiveReport = window.archiveReport || function(btn) {
- try {
- const card = btn.closest('.doc-card');
- if (card) card.classList.toggle('archived');
- showToast && showToast('已切换归档状态', 'success');
- } catch (e) { console.warn(e); }
- };
- window.deleteReport = window.deleteReport || function(btn) {
- try {
- const card = btn.closest('.doc-card');
- const name = card?.getAttribute('data-name') || '报告';
- if (!confirm('确认删除 "' + name + '" 吗?')) return;
- card.remove();
- if (typeof updateReportsCountAndSort === 'function') updateReportsCountAndSort();
- showToast && showToast('已删除:' + name, 'success');
- } catch (e) { console.warn(e); }
- };
- </script>
- </html>
|