| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800180118021803180418051806180718081809181018111812181318141815181618171818181918201821182218231824182518261827182818291830183118321833183418351836183718381839184018411842184318441845184618471848184918501851185218531854185518561857185818591860186118621863186418651866186718681869187018711872187318741875187618771878187918801881188218831884188518861887188818891890189118921893189418951896189718981899190019011902190319041905190619071908190919101911191219131914191519161917191819191920192119221923192419251926192719281929193019311932193319341935193619371938193919401941194219431944194519461947194819491950195119521953195419551956195719581959196019611962196319641965196619671968196919701971197219731974197519761977197819791980198119821983198419851986198719881989199019911992199319941995199619971998199920002001200220032004200520062007200820092010201120122013201420152016201720182019202020212022202320242025202620272028202920302031203220332034203520362037203820392040204120422043204420452046204720482049205020512052205320542055205620572058205920602061206220632064206520662067206820692070207120722073207420752076207720782079208020812082208320842085208620872088208920902091209220932094209520962097209820992100210121022103210421052106210721082109211021112112211321142115211621172118211921202121212221232124212521262127212821292130213121322133213421352136213721382139214021412142214321442145214621472148214921502151215221532154215521562157215821592160216121622163216421652166216721682169217021712172217321742175217621772178217921802181218221832184218521862187218821892190219121922193219421952196219721982199220022012202220322042205220622072208220922102211221222132214221522162217221822192220222122222223222422252226222722282229223022312232223322342235223622372238223922402241224222432244224522462247224822492250225122522253225422552256225722582259226022612262226322642265226622672268226922702271227222732274227522762277227822792280228122822283228422852286228722882289229022912292229322942295229622972298229923002301230223032304230523062307230823092310231123122313231423152316231723182319232023212322232323242325232623272328232923302331233223332334233523362337233823392340234123422343234423452346234723482349235023512352235323542355235623572358235923602361236223632364236523662367236823692370237123722373237423752376237723782379238023812382238323842385238623872388238923902391239223932394239523962397239823992400240124022403240424052406240724082409241024112412241324142415241624172418241924202421242224232424242524262427242824292430243124322433243424352436243724382439244024412442244324442445244624472448244924502451245224532454245524562457245824592460246124622463246424652466246724682469247024712472247324742475247624772478247924802481248224832484248524862487248824892490249124922493249424952496249724982499250025012502250325042505250625072508250925102511251225132514251525162517251825192520252125222523252425252526252725282529253025312532253325342535253625372538253925402541254225432544254525462547254825492550255125522553255425552556255725582559256025612562256325642565256625672568256925702571257225732574257525762577257825792580258125822583258425852586258725882589259025912592259325942595259625972598259926002601260226032604260526062607260826092610261126122613261426152616261726182619262026212622262326242625262626272628262926302631263226332634263526362637263826392640264126422643264426452646264726482649265026512652265326542655265626572658265926602661266226632664266526662667266826692670267126722673267426752676267726782679268026812682268326842685268626872688268926902691269226932694269526962697269826992700270127022703270427052706270727082709271027112712271327142715271627172718271927202721272227232724272527262727272827292730273127322733273427352736273727382739274027412742274327442745274627472748274927502751275227532754275527562757275827592760276127622763276427652766276727682769277027712772277327742775277627772778277927802781278227832784278527862787278827892790279127922793279427952796279727982799280028012802280328042805280628072808280928102811281228132814281528162817281828192820282128222823282428252826282728282829283028312832283328342835283628372838283928402841284228432844284528462847284828492850285128522853285428552856285728582859286028612862286328642865286628672868286928702871287228732874287528762877287828792880288128822883288428852886288728882889289028912892289328942895289628972898289929002901290229032904290529062907290829092910291129122913291429152916291729182919292029212922292329242925292629272928292929302931293229332934293529362937293829392940294129422943294429452946294729482949295029512952295329542955295629572958295929602961296229632964296529662967296829692970297129722973297429752976297729782979298029812982298329842985298629872988298929902991299229932994299529962997299829993000300130023003300430053006300730083009301030113012301330143015301630173018301930203021302230233024302530263027302830293030303130323033303430353036303730383039304030413042304330443045304630473048304930503051305230533054305530563057305830593060306130623063306430653066306730683069307030713072307330743075307630773078307930803081308230833084308530863087308830893090309130923093309430953096309730983099310031013102310331043105310631073108310931103111311231133114311531163117311831193120312131223123312431253126312731283129313031313132313331343135313631373138313931403141314231433144314531463147314831493150315131523153315431553156315731583159316031613162316331643165316631673168316931703171317231733174317531763177317831793180318131823183318431853186318731883189319031913192319331943195319631973198319932003201320232033204320532063207320832093210321132123213321432153216321732183219322032213222322332243225322632273228322932303231323232333234323532363237323832393240324132423243324432453246324732483249325032513252325332543255325632573258325932603261326232633264326532663267326832693270327132723273327432753276327732783279328032813282328332843285328632873288328932903291329232933294329532963297329832993300330133023303330433053306330733083309331033113312331333143315331633173318331933203321332233233324332533263327332833293330333133323333333433353336333733383339334033413342334333443345334633473348334933503351335233533354335533563357335833593360336133623363336433653366336733683369337033713372337333743375337633773378337933803381338233833384338533863387338833893390339133923393339433953396339733983399340034013402340334043405340634073408340934103411341234133414341534163417341834193420342134223423342434253426342734283429343034313432343334343435343634373438343934403441344234433444344534463447344834493450345134523453345434553456345734583459346034613462346334643465346634673468346934703471347234733474347534763477347834793480348134823483348434853486348734883489349034913492349334943495349634973498349935003501350235033504350535063507350835093510351135123513351435153516351735183519352035213522352335243525352635273528352935303531353235333534353535363537353835393540354135423543354435453546354735483549355035513552355335543555355635573558355935603561356235633564356535663567356835693570357135723573357435753576357735783579358035813582358335843585358635873588358935903591359235933594359535963597359835993600360136023603360436053606360736083609361036113612361336143615361636173618361936203621362236233624362536263627362836293630363136323633363436353636363736383639364036413642364336443645364636473648364936503651365236533654365536563657365836593660366136623663366436653666366736683669367036713672367336743675367636773678367936803681368236833684368536863687368836893690369136923693369436953696369736983699370037013702370337043705370637073708370937103711371237133714371537163717371837193720372137223723372437253726372737283729373037313732373337343735373637373738373937403741374237433744374537463747374837493750375137523753375437553756375737583759376037613762376337643765376637673768376937703771377237733774377537763777377837793780378137823783378437853786378737883789379037913792379337943795379637973798379938003801380238033804380538063807380838093810381138123813381438153816381738183819382038213822382338243825382638273828382938303831383238333834383538363837383838393840384138423843384438453846384738483849385038513852385338543855385638573858385938603861386238633864386538663867386838693870387138723873387438753876387738783879388038813882388338843885388638873888388938903891389238933894389538963897389838993900390139023903390439053906390739083909391039113912391339143915391639173918391939203921392239233924392539263927392839293930393139323933393439353936393739383939394039413942394339443945394639473948394939503951395239533954395539563957395839593960396139623963396439653966396739683969397039713972397339743975397639773978397939803981398239833984398539863987398839893990399139923993399439953996399739983999400040014002400340044005400640074008400940104011401240134014401540164017401840194020402140224023402440254026402740284029403040314032403340344035403640374038403940404041404240434044404540464047404840494050405140524053405440554056405740584059406040614062406340644065406640674068406940704071407240734074407540764077407840794080408140824083408440854086408740884089409040914092409340944095409640974098409941004101410241034104410541064107410841094110411141124113411441154116411741184119412041214122412341244125412641274128412941304131413241334134413541364137413841394140414141424143414441454146414741484149415041514152415341544155415641574158415941604161416241634164416541664167416841694170417141724173417441754176417741784179418041814182418341844185418641874188418941904191419241934194419541964197419841994200420142024203420442054206420742084209421042114212421342144215421642174218421942204221422242234224422542264227422842294230423142324233423442354236423742384239424042414242424342444245424642474248424942504251425242534254425542564257425842594260426142624263426442654266426742684269427042714272427342744275427642774278427942804281428242834284428542864287428842894290429142924293429442954296429742984299430043014302430343044305430643074308430943104311431243134314431543164317431843194320432143224323432443254326432743284329433043314332433343344335433643374338433943404341434243434344434543464347434843494350 |
- <template>
- <el-container class="container">
- <!-- 侧边栏 --start -->
- <el-aside :class="asideWidth" id="el-aside">
- <el-menu
- ref="menu"
- :collapse="isCollapse"
- :uniqueOpened="true"
- default-active=""
- class="el-menu-vertical-demo"
- text-color="#303133"
- background-color="#f8f9fa"
- active-text-color="#409eff"
- :collapse-transition="false"
- >
- <!-- 展开收缩控制按钮 -->
- <el-menu-item index="0" @click="isCollapse=!isCollapse" id="fold">
- <i
- class="iconfont iconshouqi iconfont2"
- style="display:block;margin:0;text-align:center"
- :class="isCollapse?'rotate':'rotate2'"
- ></i>
- </el-menu-item>
- <!-- 加载数据 -->
- <el-sub-menu index="1">
- <template #title>
- <i class="iconfont iconshuju iconfont2"></i>
- <span>数据服务</span>
- </template>
- <el-sub-menu v-for="(data,index) in server_config" :key="data.id" :index="data.id" popper-append-to-body="false">
- <template #title>{{data.name}}</template>
-
- <!-- 基础地理实体使用文字列表方式 -->
- <template v-if="data.id === 'vectorData'">
- <el-menu-item
- v-for="(data2, idx) in data.children"
- :key="data2.name"
- :index="data.id + '-' + idx"
- @dblclick="addDatas(data.id, data2)"
- class="vector-data-item"
- >
- <span class="vector-data-name" :class="{ 'vector-data-loaded': data2.state !== 0 }">{{ data2.name }}</span>
- <el-popconfirm
- v-if="data2.state !== 0"
- title="确定卸载吗?"
- confirmButtonText="确认"
- cancelButtonText="取消"
- @confirm="DeleteDates(data.id, data2)"
- >
- <template #reference>
- <el-icon class="delete-vector-data-icon" title="卸载">
- <CircleClose />
- </el-icon>
- </template>
- </el-popconfirm>
- </el-menu-item>
- </template>
-
- <!-- 其他数据服务使用图片方式 -->
- <div v-else class="box" :index="index">
- <div
- class="imgbox"
- v-for="data2 in data.children"
- :key="data2.name"
- @click="addDatas(data.id,data2)"
- >
- <img :src="data2.thumbnail" alt class="img" />
- <img src="/img/common/cross.png" alt class="cross" v-show="data2.state != 0" />
- <span>{{data2.name}}</span>
- <el-popconfirm
- title="确定删除吗?"
- confirmButtonText="确认"
- cancelButtonText="取消"
- @confirm="DeleteDates(data.id,data2)"
- >
- <template #reference>
- <CircleClose
- v-show="data2.state != 0 && data.id !='baseLayer'"
- title="删除"
- class="circle-close-icon"
- />
- </template>
- </el-popconfirm>
- </div>
- </div>
- </el-sub-menu>
-
- <!-- 本地服务二级菜单 -->
- <el-sub-menu index="1-4">
- <template #title>自定义服务</template>
- <el-menu-item
- v-for="(service, index) in addCustomServices"
- :key="service.id || index"
- :index="'1-4-' + (service.id || index)"
- @dblclick="loadCustomService(service)"
- class="custom-service-item"
- >
- <span class="service-name" :class="{ 'service-loaded': isServiceLoaded(service) }">{{ service.name }}</span>
- <el-popconfirm
- v-if="isServiceLoaded(service)"
- title="确定卸载吗?"
- confirmButtonText="确认"
- cancelButtonText="取消"
- @confirm="unloadCustomService(service)"
- >
- <template #reference>
- <el-icon class="delete-service-icon" title="卸载">
- <CircleClose />
- </el-icon>
- </template>
- </el-popconfirm>
- </el-menu-item>
- </el-sub-menu>
-
- <el-menu-item index="1-5" @click="addCustomService" class="no-active-style">
- <el-icon @click.stop="addCustomService" class="square-plus-icon"><Plus /></el-icon>
- </el-menu-item>
- </el-sub-menu>
- <!-- 加载组件 -->
- <el-sub-menu v-for="(data,index) in config" :key="index" :index="data.id">
- <template #title>
- <i :class="data.icon" class="iconfont2"></i>
- <span>{{data.name}}</span>
- </template>
- <div class="box">
- <div
- class="imgbox"
- v-for="data2 in data.children"
- :key="data2.component"
- @click="data2.component && change(data2.component,data2.destroy)"
- >
- <img :src="data2.imgSrc" alt class="img" />
- <img
- src="/img/common/cross.png"
- alt
- class="cross"
- v-show="data2.component && (data2.component === view || data2.component === view2)"
- />
- <span>{{data2.name}}</span>
- </div>
- </div>
- </el-sub-menu>
- <!-- 模型菜单 -->
- <el-sub-menu index="model" v-if="modelConfig.id">
- <template #title>
- <i :class="modelConfig.icon" class="iconfont2"></i>
- <span>{{modelConfig.name}}</span>
- </template>
- <el-sub-menu
- v-for="(secondLevel, firstLevelKey) in groupedModels"
- :key="firstLevelKey"
- :index="'model-' + firstLevelKey"
- popper-append-to-body="false"
- >
- <template #title>
- <span>{{getFirstLevelName(firstLevelKey)}}</span>
- <el-tag size="small" type="info" style="margin-left: 8px;">{{getTotalCount(secondLevel)}}</el-tag>
- </template>
- <el-sub-menu
- v-for="(models, secondLevelKey) in secondLevel"
- :key="secondLevelKey"
- :index="'model-' + firstLevelKey + '-' + secondLevelKey"
- popper-append-to-body="false"
- >
- <template #title>
- <span>{{getSecondLevelName(secondLevelKey)}}</span>
- </template>
- <el-menu-item
- v-for="model in models"
- :key="model.id"
- :index="'model-' + firstLevelKey + '-' + secondLevelKey + '-' + model.id"
- @dblclick="loadModel(model)"
- class="model-menu-item"
- >
- <span class="model-name" :class="{ 'model-loaded': isModelLoaded(model.id) }">{{ model.name }}</span>
- <el-popconfirm
- v-if="isModelLoaded(model.id)"
- title="确定删除吗?"
- confirmButtonText="确认"
- cancelButtonText="取消"
- @confirm="removeModel(model)"
- >
- <template #reference>
- <el-icon
- class="delete-model-icon"
- title="删除模型"
- >
- <CircleClose />
- </el-icon>
- </template>
- </el-popconfirm>
- </el-menu-item>
- </el-sub-menu>
- </el-sub-menu>
- </el-sub-menu>
- <!-- 台风菜单 -->
- <el-sub-menu index="typhoon">
- <template #title>
- <img src="/img/typhoon.png" alt="" class="typhoon-icon" />
- <span>台风</span>
- </template>
- <!-- 实时台风菜单项 -->
- <el-menu-item index="typhoon-realtime" class="model-menu-item" @click="!showRealtimeTyphoon && loadRealtimeTyphoon()">
- <span class="typhoon-name" :class="{ 'typhoon-loaded': showRealtimeTyphoon }">实时台风</span>
- <el-popconfirm
- v-if="showRealtimeTyphoon"
- title="确定卸载吗?"
- confirmButtonText="确认"
- cancelButtonText="取消"
- @confirm="removeRealtimeTyphoon"
- >
- <template #reference>
- <el-icon
- class="delete-typhoon-icon"
- title="卸载台风"
- >
- <CircleClose />
- </el-icon>
- </template>
- </el-popconfirm>
- </el-menu-item>
- <!-- 风场菜单项 -->
- <el-menu-item index="typhoon-windfield" class="model-menu-item" @click="!showWindField && loadWindField()">
- <span class="typhoon-name" :class="{ 'typhoon-loaded': showWindField }">风场</span>
- <el-popconfirm
- v-if="showWindField"
- title="确定卸载吗?"
- confirmButtonText="确认"
- cancelButtonText="取消"
- @confirm="removeWindField"
- >
- <template #reference>
- <el-icon
- class="delete-typhoon-icon"
- title="卸载风场"
- >
- <CircleClose />
- </el-icon>
- </template>
- </el-popconfirm>
- </el-menu-item>
- <!-- 降雨菜单项 -->
- <el-menu-item index="typhoon-rainfall" class="model-menu-item" @click="!showRainfall && loadRainfall()">
- <span class="typhoon-name" :class="{ 'typhoon-loaded': showRainfall }">降雨</span>
- <el-popconfirm
- v-if="showRainfall"
- title="确定卸载吗?"
- confirmButtonText="确认"
- cancelButtonText="取消"
- @confirm="removeRainfall"
- >
- <template #reference>
- <el-icon
- class="delete-typhoon-icon"
- title="卸载降雨"
- >
- <CircleClose />
- </el-icon>
- </template>
- </el-popconfirm>
- </el-menu-item>
- <!-- 云图菜单项 -->
- <el-menu-item index="typhoon-cloud" class="model-menu-item" @click="!showCloudMap && loadCloudMap()">
- <span class="typhoon-name" :class="{ 'typhoon-loaded': showCloudMap }">云图</span>
- <el-popconfirm
- v-if="showCloudMap"
- title="确定卸载吗?"
- confirmButtonText="确认"
- cancelButtonText="取消"
- @confirm="removeCloudMap"
- >
- <template #reference>
- <el-icon
- class="delete-typhoon-icon"
- title="卸载云图"
- >
- <CircleClose />
- </el-icon>
- </template>
- </el-popconfirm>
- </el-menu-item>
- <!-- 雷达菜单项 -->
- <el-menu-item index="typhoon-radar" class="model-menu-item" @click="!showRadar && loadRadar()">
- <span class="typhoon-name" :class="{ 'typhoon-loaded': showRadar }">雷达</span>
- <el-popconfirm
- v-if="showRadar"
- title="确定卸载吗?"
- confirmButtonText="确认"
- cancelButtonText="取消"
- @confirm="removeRadar"
- >
- <template #reference>
- <el-icon
- class="delete-typhoon-icon"
- title="卸载雷达"
- >
- <CircleClose />
- </el-icon>
- </template>
- </el-popconfirm>
- </el-menu-item>
- </el-sub-menu>
- <!-- 业务场景菜单 -->
- <el-sub-menu index="business-scenario">
- <template #title>
- <i class="iconfont iconxingcheng iconfont2"></i>
- <span>业务场景</span>
- </template>
-
- <!-- 固定场景 -->
- <template v-for="scene in fixedScenes" :key="scene.sceneId">
- <el-menu-item
- :index="'business-scenario-' + scene.sceneId"
- class="model-menu-item"
- @click="loadFixedScene(scene)"
- >
- <span class="scene-name" :class="{ 'scene-loaded': isSceneLoaded(scene.sceneId) }">{{ scene.sceneName }}</span>
- <el-popconfirm
- v-if="isSceneLoaded(scene.sceneId)"
- title="确定卸载吗?"
- confirmButtonText="确认"
- cancelButtonText="取消"
- @confirm="unloadScene(scene)"
- >
- <template #reference>
- <el-icon
- class="delete-scene-icon"
- title="卸载场景"
- >
- <CircleClose />
- </el-icon>
- </template>
- </el-popconfirm>
- </el-menu-item>
- </template>
-
- <!-- 数据库场景 -->
- <template v-for="scene in businessScenes" :key="scene.sceneId">
- <el-menu-item
- :index="'business-scenario-' + scene.sceneId"
- class="model-menu-item"
- @click="loadBusinessScenarioFromDB(scene)"
- >
- <span class="scene-name" :class="{ 'scene-loaded': isSceneLoaded(scene.sceneId) }">{{ scene.sceneName }}</span>
- <el-popconfirm
- v-if="isSceneLoaded(scene.sceneId)"
- title="确定卸载吗?"
- confirmButtonText="确认"
- cancelButtonText="取消"
- @confirm="unloadScene(scene)"
- >
- <template #reference>
- <el-icon
- class="delete-scene-icon"
- title="卸载场景"
- >
- <CircleClose />
- </el-icon>
- </template>
- </el-popconfirm>
- <el-popconfirm
- v-else
- title="确定删除吗?"
- confirmButtonText="确认"
- cancelButtonText="取消"
- @confirm="handleDeleteBusinessScene(scene.sceneId)"
- >
- <template #reference>
- <el-icon
- class="delete-scene-icon"
- title="删除场景"
- >
- <CircleClose />
- </el-icon>
- </template>
- </el-popconfirm>
- </el-menu-item>
- </template>
-
- <el-menu-item index="business-scenario-create" @click="handleCreateScenario" class="no-active-style">
- <el-icon @click.stop="handleCreateScenario" class="square-plus-icon"><Plus /></el-icon>
- <span>创建场景</span>
- </el-menu-item>
- </el-sub-menu>
- </el-menu>
- </el-aside>
- <!-- 侧边栏部分--end -->
- <!-- 右侧内容展示部分 -->
- <el-main>
- <loading-bar></loading-bar>
- <!-- viewer组件 -->
- <component v-if="initViewer" :is="initViewer" :after-initviewer="removeLoad" :opening-animation="true"></component>
- <!-- 台风可视化组件 -->
- <TyphoonVisualization
- v-if="showTyphoon"
- :viewer="viewer"
- :visible="showTyphoon"
- :data-url="typhoonDataUrl"
- ></TyphoonVisualization>
- <!-- 实时台风可视化组件 -->
- <RealtimeTyphoon
- v-if="showRealtimeTyphoon"
- :viewer="viewer"
- :visible="showRealtimeTyphoon"
- ></RealtimeTyphoon>
- <!-- 风场可视化组件 -->
- <WindFieldVisualization
- v-if="showWindField"
- :viewer="viewer"
- :visible="showWindField"
- :data-url="`${PROXY_SLT}/Api/LastWind`"
- ></WindFieldVisualization>
- <!-- 降雨可视化组件 -->
- <RainfallVisualization
- v-if="showRainfall"
- :viewer="viewer"
- :visible="showRainfall"
- :default-hours="24"
- ></RainfallVisualization>
- <!-- 云图可视化组件 -->
- <CloudMapVisualization
- v-if="showCloudMap"
- :viewer="viewer"
- :visible="showCloudMap"
- :default-type="30"
- ></CloudMapVisualization>
- <!-- 雷达可视化组件 -->
- <RadarVisualization
- v-if="showRadar"
- :viewer="viewer"
- :visible="showRadar"
- ></RadarVisualization>
- <!-- 土地利用图例 -->
- <div class="land-use-legend" v-if="showLandUseLegend">
- <div class="land-use-legend-header">
- <span>土地利用图例</span>
- <span class="land-use-legend-toggle" @click="showLandUseLegend = false">−</span>
- </div>
- <div class="land-use-legend-body">
- <div class="land-use-legend-item" v-for="item in landUseLegendItems" :key="item.value">
- <span class="land-use-legend-color" :style="{ backgroundColor: item.color }"></span>
- <span class="land-use-legend-label">{{ item.label }}</span>
- </div>
- </div>
- </div>
- <!-- 添加自定义服务组件 -->
- <component
- v-if="addService"
- :is="addService"
- :clear-callback="addCustomService"
- :add-callback="addCallback"
- :get-layer-name="getName"
- ></component>
- <!-- 分析功能组件 -->
- <keep-alive>
- <component
- v-if="view"
- :is="view"
- :key="view"
- :delete-callback="deleteCallback"
- data-url="/data/netcdf/result_100_200_9_40.nc"
- skylineSpatialAnalysisUrl="http://www.supermapol.com/realspace/services/spatialAnalysis-data_all/restjsr/spatialanalyst/geometry/3d/skylinesectorbody.json"
- ></component>
- </keep-alive>
- <!-- 地质体组件,单独展示,需要销毁 -->
- <component v-if="view2" :is="view2" :key="view2"></component>
-
- <!-- 模型拆解弹框 -->
- <ModelDisassemblyDialog
- ref="modelDisassemblyDialog"
- :visible="showModelDialog"
- :model-path="'/models/启闭机.glb'"
- :model-name="'启闭机模型'"
- :icon-config="modelIconConfig"
- @close="showModelDialog = false"
- />
- <!-- 鹰眼组件 -->
- <Sm3dOverviewMap
- v-if="view === 'Sm3dOverviewMap'"
- :visible="view === 'Sm3dOverviewMap'"
- @close="view = ''"
- ></Sm3dOverviewMap>
-
- <!-- MVT属性弹窗组件 -->
- <MvtAttributePopup
- :visible="mvtPopupVisible"
- :position="mvtPopupPosition"
- :title="mvtPopupTitle"
- :attributes="mvtPopupAttributes"
- @close="mvtPopupVisible = false"
- />
- <!-- 闸门启闭控制组件(非莒口水闸场景使用) -->
- <GateControl
- v-if="!showJuKouScenePage"
- ref="gateControl"
- :visible="showGateControl"
- :initial-position="gatePosition"
- @close="showGateControl = false"
- @position-change="handleGatePositionChange"
- />
-
- <!-- 莒口水闸业务场景独立页面组件 -->
- <JuKouShuiZhaPage
- v-if="showJuKouScenePage"
- :viewer="viewer"
- :visible="showJuKouScenePage"
- @scene-loaded="onJuKouSceneLoaded"
- @scene-unloaded="onJuKouSceneUnloaded"
- />
- </el-main>
- </el-container>
- </template>
- <script>
- import config from "../../config/views_config.js"; //组件配置
- import server_config, { PROXY_TIANDITU_TILE, PROXY_SLT, TIANDITU_KEY } from "../../config/server_config.js"; //服务配置
- import server_flyTo_config from "../../config/server_position_config.js"; //服务坐标配置
- import layerManagement from "../../js/common/layerManagement.js"; //图层管理封装方法
- import waterLayer from "../../js/common/waterLayer.js"; //水面图层
- import camera from "../../js/common/camera.js"; //相机操作
- import loadingBar from "../../components/loading.vue"; //加载动画
- import TyphoonVisualization from "../../components/typhoon-visualization/typhoon-visualization.vue"; //台风可视化组件
- import RealtimeTyphoon from "../../components/realtime-typhoon/realtime-typhoon.vue"; //实时台风可视化组件
- import WindFieldVisualization from "../../components/wind-field-visualization/wind-field-visualization.vue"; //风场可视化组件
- import RainfallVisualization from "../../components/rainfall-visualization/rainfall-visualization.vue"; //降雨可视化组件
- import CloudMapVisualization from "../../components/cloud-map-visualization/cloud-map-visualization.vue"; //云图可视化组件
- import RadarVisualization from "../../components/radar-visualization/radar-visualization.vue"; //雷达可视化组件
- import CesiumThreeFusion from "@/components/ThreeCesiumIntegration/CesiumThreeFusion.vue"; //Three.js与Cesium融合组件
- import ModelDisassemblyDialog from "../../components/ModelDisassemblyDialog.vue"; //模型拆解弹框组件
- import { CircleClose, Plus, CirclePlus, Setting, Delete } from '@element-plus/icons-vue'; //删除图标
- import { listModel, getModel } from '@/api/watershed/model'; //模型API
- import { getDefaultMapConfig, saveMapConfig } from '@/api/cesium/mapConfig'; //地图配置API
- import serviceApi from '@/api/watershed/service'; //自定义服务API
- import { listBusinessScene, addBusinessScene, delBusinessScene } from '@/api/cesium/businessScene'; //业务场景API
- import { ElMessage, ElMessageBox } from 'element-plus';
- import { getToken } from '@/utils/auth'
- import axios from 'axios'
- import { onMounted, watch, computed } from 'vue'
- import ModelEditor from "../../components/draw/draw-solid-figure/ModelEditor"; //模型编辑器
- import MvtAttributePopup from "../../components/MvtAttributePopup.vue"; //MVT属性弹窗组件
- import MvtClickHandler from "../../js/common/mvtClickHandler.js"; //MVT点击处理器
- import GateControl from "../../components/gate-control/GateControl.vue"; //闸门启闭控制组件
- import JuKouShuiZhaPage from "../../components/ju-kou-scene/JuKouShuiZhaPage.vue"; //莒口水闸业务场景页面组件
- import { getFixedScenes, isFixedScene } from "../../config/fixedScenes.js"; //固定场景配置
- import { loadJuKouScene, unloadJuKouScene, getController, JuKouSceneConfig } from "../../business-scenes/JuKouShuiZhaScene.js"; //莒口水闸业务场景模块
- export default {
- name: "layout-aside",
- components: {
- loadingBar,
- CircleClose,
- TyphoonVisualization,
- RealtimeTyphoon,
- WindFieldVisualization,
- RainfallVisualization,
- CloudMapVisualization,
- RadarVisualization,
- CesiumThreeFusion,
- ModelDisassemblyDialog,
- MvtAttributePopup,
- GateControl,
- JuKouShuiZhaPage
- },
- setup() {
- // 使用 computed 来获取 window.viewer
- const viewer = computed(() => window.viewer);
-
- return {
- viewer
- }
- },
- data() {
- return {
- isCollapse: false,
- asideWidth: "asideWidth1",
- view: "",
- view2: "",
- initViewer: "",
- addService: "",
- sceneURL:
- "http://www.supermapol.com/realspace/services/3D-ZF_normal/rest/realspace",
- config: config,
- server_config: server_config,
- // 暴露给模板使用:Options API 模板只能访问 data/computed/methods,
- // 不能直接访问 import 的变量,否则模板里 PROXY_SLT 为 undefined,
- // 导致 :data-url="`${PROXY_SLT}/Api/LastWind`" 变成 "undefined/Api/LastWind"
- PROXY_SLT: PROXY_SLT,
- baseLayerObj: server_config[1].children[0],
- terrainLayerObj: null,
- addCustomServices: [],
- getNames: [], //记录自定义服务名称
- varName: null, //临时保存当前自定义名称
- loadedCustomServiceIds: [], //已加载的自定义服务ID
- modelConfig: {}, //模型菜单配置
- modelData: [], //模型数据
- loadedModelEntities: [], //已加载的模型实体
- currentModelEditor: null, //当前模型编辑器
- lastHighlightedModel: null, //最后高亮的模型
- saveConfigTimer: null, //保存配置的防抖定时器
- lastSaveTime: 0, //上次保存时间戳
- showTyphoon: false, //是否显示台风可视化
- typhoonDataUrl: '', //台风数据URL
- loadedTyphoonId: null, //已加载的台风ID
- showRealtimeTyphoon: false, //是否显示实时台风可视化
- showWindField: false, //是否显示风场可视化
- showRainfall: false, //是否显示降雨可视化
- showCloudMap: false, //是否显示云图可视化
- showRadar: false, //是否显示雷达可视化
- showLandUseLegend: false, //是否显示土地利用图例
- landUseLegendItems: [
- { value: 0, color: '#000000', label: '背景/海域无效像元' },
- { value: 1, color: '#FFF8DC', label: '耕地' },
- { value: 2, color: '#006400', label: '有林地' },
- { value: 3, color: '#2E8B57', label: '灌木林地' },
- { value: 4, color: '#98FB98', label: '草地' },
- { value: 5, color: '#00008B', label: '河流水面、湖泊、水库坑塘' },
- { value: 6, color: '#87CEEB', label: '冰川及永久积雪' },
- { value: 7, color: '#D2B48C', label: '裸地、戈壁、盐碱地' },
- { value: 8, color: '#FF00FF', label: '城乡建设用地' },
- { value: 9, color: '#00BFFF', label: '沼泽、滩涂湿地' },
- { value: 10, color: '#000000', label: '园地(果园、茶园等)' },
- { value: 11, color: '#000000', label: '荒漠、重度盐碱地' },
- { value: 12, color: '#000000', label: '独立工矿、公路铁路用地' },
- { value: 13, color: '#000000', label: '其他未利用土地' },
- ],
- showModelDialog: false, //是否显示模型弹框
- businessScenes: [], //业务场景列表(从数据库读取)
- fixedScenes: [], //固定场景列表
- currentSceneId: null, //当前加载的场景ID
- // 闸门控制相关
- showGateControl: false, //是否显示闸门控制面板
- gatePosition: 0, //闸门当前开度(0-100)
- // 莒口水闸场景页面显示
- showJuKouScenePage: false, //是否显示莒口水闸场景页面组件
- // MVT属性弹窗相关
- mvtPopupVisible: false, //弹窗是否可见
- mvtPopupPosition: { x: 0, y: 0 }, //弹窗位置
- mvtPopupTitle: '属性信息', //弹窗标题
- mvtPopupAttributes: {}, //属性数据
- // MVT点击处理器
- mvtClickHandler: null, //MVT点击处理器实例
- _hasMvtLayer: false, //是否有MVT图层加载
- // 模型拆解图标配置
- modelIconConfig: null, //场景模型拆解图标配置
- };
- },
- computed: {
- groupedModels() {
- const grouped = {}
- this.modelData.forEach(model => {
- const type = model.type || '5/5-1'
- const parts = type.split('/')
- const firstLevel = parts[0] || '5'
- const secondLevel = parts[1] || '5-1'
-
- if (!grouped[firstLevel]) {
- grouped[firstLevel] = {}
- }
- if (!grouped[firstLevel][secondLevel]) {
- grouped[firstLevel][secondLevel] = []
- }
- grouped[firstLevel][secondLevel].push(model)
- })
- return grouped
- }
- },
- methods: {
- async fetchCustomServices() {
- try {
- console.log('开始从数据库加载自定义服务...');
- const response = await serviceApi.getServiceList({});
- console.log('获取到的自定义服务:', response);
-
- if (response && response.rows) {
- this.addCustomServices = response.rows.map(service => ({
- id: service.id || service.serviceId,
- name: service.name || service.serviceName,
- type: service.type || service.serviceType,
- url: service.url || service.serviceUrl,
- tokenRequired: service.tokenRequired || service.token_required,
- token: service.token || service.serviceToken,
- status: service.status,
- layers: [{
- type: service.type || service.serviceType,
- layerName: service.name || service.serviceName
- }]
- }));
- console.log('处理后的自定义服务列表:', this.addCustomServices);
- }
- } catch (error) {
- console.error('加载自定义服务失败:', error);
- }
- },
- getModelTypeOptions() {
- return [
- {
- value: '1',
- label: '水利工程实体',
- children: [
- { value: '1-1', label: '水库工程' },
- { value: '1-2', label: '水闸工程' },
- { value: '1-3', label: '泵站工程' },
- { value: '1-4', label: '灌区工程' },
- { value: '1-5', label: '堤防与护岸工程' }
- ]
- },
- {
- value: '2',
- label: '水系水利设施',
- children: [
- { value: '2-1', label: '河流' },
- { value: '2-2', label: '湖泊与水库水面' },
- { value: '2-3', label: '渠道与输水管道' },
- { value: '2-4', label: '河口与海岸带' }
- ]
- },
- {
- value: '3',
- label: '地理环境要素',
- children: [
- { value: '3-1', label: '地形地貌' },
- { value: '3-2', label: '行政区划' },
- { value: '3-3', label: '重要地物' }
- ]
- },
- {
- value: '4',
- label: '自然生态景观',
- children: [
- { value: '4-1', label: '湖泊湿地' },
- { value: '4-2', label: '森林公园' },
- { value: '4-3', label: '地质公园' },
- { value: '4-4', label: '海岸带景观' }
- ]
- },
- {
- value: '5',
- label: '模型集与项目',
- children: [
- { value: '5-1', label: '待分类模型' },
- { value: '5-2', label: 'XX市防洪排涝工程' }
- ]
- }
- ]
- },
- getFirstLevelName(firstLevelKey) {
- const options = this.getModelTypeOptions()
- const option = options.find(opt => opt.value === firstLevelKey)
- return option ? option.label : firstLevelKey
- },
- getSecondLevelName(secondLevelKey) {
- const options = this.getModelTypeOptions()
- for (const opt of options) {
- if (opt.children) {
- const child = opt.children.find(c => c.value === secondLevelKey)
- if (child) return child.label
- }
- }
- return secondLevelKey
- },
- getTotalCount(secondLevel) {
- let count = 0
- for (const key in secondLevel) {
- count += secondLevel[key].length
- }
- return count
- },
- getTypeDisplayName(type) {
- const typeMapping = {
- 'RESERVOIR': '水库',
- 'HYDROPOWER': '水电站',
- 'IRRIGATION': '灌溉',
- 'FLOOD_CONTROL': '防洪',
- 'CANAL': '渠道',
- 'PUMPING_STATION': '泵站',
- 'OTHER': '其他'
- }
-
- if (typeMapping[type]) {
- return typeMapping[type]
- }
-
- return type
- },
- async fetchModels() {
- try {
- const data = await listModel({})
- const modelList = data.rows || []
- console.log('获取到的模型数据:', modelList)
-
- this.modelData = modelList.map(model => {
- console.log('模型详情:', model)
- return {
- id: model.id,
- name: model.name,
- type: model.type,
- format: model.format,
- uploadUnit: model.uploadUnit || '未知单位',
- filePath: model.filePath || model.file_path || model.url || '',
- coordinates: model.modelCoordinates || model.coordinates || '',
- rotationX: model.rotationX != null ? model.rotationX : (model.rotation_x != null ? model.rotation_x : 0),
- rotationY: model.rotationY != null ? model.rotationY : (model.rotation_y != null ? model.rotation_y : 0),
- rotationZ: model.rotationZ != null ? model.rotationZ : (model.rotation_z != null ? model.rotation_z : 0),
- scaleX: model.scaleX != null ? model.scaleX : (model.scale_x != null ? model.scale_x : 1),
- scaleY: model.scaleY != null ? model.scaleY : (model.scale_y != null ? model.scale_y : 1),
- scaleZ: model.scaleZ != null ? model.scaleZ : (model.scale_z != null ? model.scale_z : 1),
- originalData: model
- }
- })
-
- console.log('处理后的模型数据:', this.modelData)
-
- if (this.modelData.length > 0) {
- this.modelConfig = {
- id: 'model',
- icon: 'iconfont iconmoxing',
- name: '模型'
- }
- }
- } catch (error) {
- console.error('获取模型数据错误:', error)
- }
- },
- async loadModel(model, shouldFlyTo = true) {
- if (!viewer) {
- ElMessage.error('Cesium viewer 未初始化')
- return
- }
- if (!viewer.scene) {
- ElMessage.error('Cesium scene 未初始化')
- return
- }
- try {
- const latestData = await getModel(model.id)
- if (latestData && latestData.data) {
- const latest = latestData.data
- model.coordinates = latest.modelCoordinates || latest.coordinates || model.coordinates
- model.rotationX = latest.rotationX != null ? latest.rotationX : (model.rotationX || 0)
- model.rotationY = latest.rotationY != null ? latest.rotationY : (model.rotationY || 0)
- model.rotationZ = latest.rotationZ != null ? latest.rotationZ : (model.rotationZ || 0)
- model.scaleX = latest.scaleX != null ? latest.scaleX : (model.scaleX || 1)
- model.scaleY = latest.scaleY != null ? latest.scaleY : (model.scaleY || 1)
- model.scaleZ = latest.scaleZ != null ? latest.scaleZ : (model.scaleZ || 1)
- }
- } catch (fetchError) {
- console.warn('获取最新模型数据失败,使用缓存数据:', fetchError.message)
- }
- if (this.currentModelEditor) {
- if (this.currentModelEditor.destroy) {
- this.currentModelEditor.destroy();
- }
- this.currentModelEditor = null;
- }
- if (this.lastHighlightedModel) {
- this.restoreModelStyle(this.lastHighlightedModel);
- this.lastHighlightedModel = null;
- }
- if (this.isModelLoaded(model.id)) {
- this.removeModel(model)
- }
- try {
- if (!model.filePath) {
- ElMessage.warning('模型文件路径不存在,请检查数据库中的filePath字段')
- return
- }
- let modelUrl = model.filePath.replace(/\/+/g, '/')
-
- const isLocalPath = modelUrl.startsWith('/') || modelUrl.startsWith('./') || modelUrl.startsWith('../') || !modelUrl.startsWith('http')
-
- if (isLocalPath) {
- const baseURL = import.meta.env.VITE_APP_BASE_API || ''
- const apiUrl = baseURL + "/common/download/resource?resource=" + encodeURIComponent(modelUrl)
-
- try {
- const response = await axios({
- method: 'get',
- url: apiUrl,
- responseType: 'blob',
- headers: { 'Authorization': 'Bearer ' + getToken() }
- })
-
- if (response.data.size === 0) {
- ElMessage.error('模型文件为空,请检查文件是否有效')
- return
- }
-
- let mimeType = 'model/gltf-binary'
- const format = model.format ? model.format.toUpperCase() : 'GLB'
- const fileExtension = model.filePath ? model.filePath.split('.').pop().toLowerCase() : ''
-
- if (format === 'GLTF' || fileExtension === 'gltf') {
- mimeType = 'model/gltf+json'
- } else if (format === 'GLB' || fileExtension === 'glb') {
- mimeType = 'model/gltf-binary'
- } else if (format === 'OBJ' || fileExtension === 'obj') {
- mimeType = 'model/obj'
- }
-
- const blob = new Blob([response.data], { type: mimeType })
- modelUrl = URL.createObjectURL(blob)
- } catch (apiError) {
- console.error('通过API获取模型文件失败:', apiError)
- ElMessage.error('无法获取模型文件,请检查文件路径或权限')
- return
- }
- }
- let position
- if (model.coordinates && model.coordinates.trim() !== '' && model.coordinates !== '未设置') {
- try {
- const coords = model.coordinates.split(',').map(c => parseFloat(c.trim()))
-
- if (coords.length >= 2 && !isNaN(coords[0]) && !isNaN(coords[1])) {
- const lon = coords[0]
- const lat = coords[1]
- const modelHeight = coords[2] !== undefined && !isNaN(coords[2]) ? coords[2] : 0
-
- const cartographic = Cesium.Cartographic.fromDegrees(lon, lat)
-
- if (viewer.scene.globe && viewer.terrainProvider) {
- try {
- const updatedPositions = await Cesium.sampleTerrainMostDetailed(viewer.terrainProvider, [cartographic])
- const terrainHeight = updatedPositions[0].height || 0
- position = Cesium.Cartesian3.fromDegrees(lon, lat, modelHeight)
- } catch (terrainError) {
- position = Cesium.Cartesian3.fromDegrees(lon, lat, modelHeight)
- }
- } else {
- position = Cesium.Cartesian3.fromDegrees(lon, lat, modelHeight)
- }
- } else {
- position = Cesium.Cartesian3.fromDegrees(116.39, 39.9, 0)
- }
- } catch (e) {
- console.error('解析经纬度失败:', e)
- position = Cesium.Cartesian3.fromDegrees(116.39, 39.9, 0)
- }
- } else {
- position = Cesium.Cartesian3.fromDegrees(116.39, 39.9, 0)
- }
- if (!position) {
- ElMessage.error('位置计算失败')
- return
- }
- const entity = viewer.entities.add({
- name: model.id,
- position: position,
- model: {
- uri: modelUrl,
- minimumPixelSize: 0,
- maximumScale: 1,
- scale: 1.0,
- show: true,
- distanceDisplayCondition: new Cesium.DistanceDisplayCondition(0.0, 50000000.0),
- heightReference: Cesium.HeightReference.NONE
- },
- _rotation: { x: model.rotationX || 0, y: model.rotationY || 0, z: model.rotationZ || 0 },
- _scale: { x: model.scaleX || 1, y: model.scaleY || 1, z: model.scaleZ || 1 }
- })
- if (model.rotationX !== undefined || model.rotationY !== undefined || model.rotationZ !== undefined) {
- const heading = Cesium.Math.toRadians(model.rotationZ || 0);
- const pitch = Cesium.Math.toRadians(model.rotationX || 0);
- const roll = Cesium.Math.toRadians(model.rotationY || 0);
- const hpr = new Cesium.HeadingPitchRoll(heading, pitch, roll);
- entity.orientation = Cesium.Transforms.headingPitchRollQuaternion(position, hpr);
- }
- if (model.scaleX !== undefined || model.scaleY !== undefined || model.scaleZ !== undefined) {
- const scaleX = model.scaleX || 1;
- const scaleY = model.scaleY || 1;
- const scaleZ = model.scaleZ || 1;
- entity.model.scale = (scaleX + scaleY + scaleZ) / 3;
- entity._modelScale = { x: scaleX, y: scaleY, z: scaleZ };
- }
- this.loadedModelEntities.push(entity)
- // requestRenderMode 下,模型资源(glb)异步加载,注册短时渲染需求确保加载完成后立即显示
- if (window.__requestContinuousRender) {
- window.__requestContinuousRender('model-loading');
- clearTimeout(window.__modelLoadTimer);
- window.__modelLoadTimer = setTimeout(() => {
- window.__cancelContinuousRender && window.__cancelContinuousRender('model-loading');
- }, 5000);
- }
- if (shouldFlyTo) {
- setTimeout(() => {
- const calculateFlyDistance = () => {
- const tryGetModelSize = (attempts = 0) => {
- if (attempts > 15) {
- viewer.flyTo(entity, {
- duration: 2,
- offset: new Cesium.HeadingPitchRange(Cesium.Math.toRadians(0), Cesium.Math.toRadians(-30), 100)
- });
- return;
- }
-
- const entityPosition = entity.position.getValue(viewer.clock.currentTime);
- if (!entityPosition) {
- setTimeout(() => tryGetModelSize(attempts + 1), 300);
- return;
- }
-
- let bestModel = null;
- let bestDistance = Infinity;
-
- for (let i = 0; i < viewer.scene.primitives.length; i++) {
- const primitive = viewer.scene.primitives.get(i);
- if (primitive instanceof Cesium.Model) {
- const modelPosition = primitive.modelMatrix ? primitive.modelMatrix.clone() : null;
- if (modelPosition) {
- const modelCartesian = Cesium.Matrix4.getTranslation(modelPosition, new Cesium.Cartesian3());
- const distance = Cesium.Cartesian3.distance(entityPosition, modelCartesian);
- if (distance < bestDistance) {
- bestDistance = distance;
- bestModel = primitive;
- }
- }
- }
- }
-
- if (bestModel && bestModel.boundingSphere && bestModel.boundingSphere.radius > 0) {
- const modelRadius = bestModel.boundingSphere.radius;
- const flyDistance = modelRadius * 3;
-
- viewer.flyTo(entity, {
- duration: 2,
- offset: new Cesium.HeadingPitchRange(Cesium.Math.toRadians(0), Cesium.Math.toRadians(-30), flyDistance)
- });
- return;
- }
-
- setTimeout(() => tryGetModelSize(attempts + 1), 300);
- };
-
- tryGetModelSize();
- };
- calculateFlyDistance();
- }, 1500)
- }
- this.saveLoadedServices()
- this.addModelClickHandler(entity)
- } catch (error) {
- console.error('加载模型失败:', error)
- ElMessage.error('加载模型失败: ' + (error.message || '未知错误'))
- }
- },
- isModelLoaded(modelId) {
- return this.loadedModelEntities.some(entity => entity.name === modelId)
- },
- isSceneLoaded(sceneId) {
- return this.currentSceneId === sceneId
- },
- // 加载场景配置的模型
- async loadSceneModel(model) {
- const viewer = window.viewer
- if (!viewer || !viewer.scene) {
- console.error('Cesium viewer 未初始化')
- return
- }
- if (this.isModelLoaded(model.id)) {
- return
- }
- try {
- const Cesium = window.Cesium
-
- const coords = model.coordinates.split(',').map(c => parseFloat(c.trim()))
- if (coords.length < 2) {
- console.error('坐标格式不正确:', model.coordinates)
- return
- }
-
- const lon = coords[0]
- const lat = coords[1]
- const height = coords.length > 2 ? coords[2] : 0
-
- const position = Cesium.Cartesian3.fromDegrees(lon, lat, height)
-
- const rotation = model.rotation ? model.rotation.split(',').map(r => parseFloat(r.trim())) : [0, 0, 0]
- const heading = Cesium.Math.toRadians(rotation[2] || 0)
- const pitch = Cesium.Math.toRadians(rotation[0] || 0)
- const roll = Cesium.Math.toRadians(rotation[1] || 0)
- const hpr = new Cesium.HeadingPitchRoll(heading, pitch, roll)
-
- const entity = viewer.entities.add({
- id: model.id,
- name: model.name,
- position: position,
- orientation: Cesium.Transforms.headingPitchRollQuaternion(position, hpr),
- model: {
- uri: model.filePath,
- minimumPixelSize: 128,
- maximumScale: 20000,
- distanceDisplayCondition: new Cesium.DistanceDisplayCondition(0.0, 50000000.0),
- heightReference: Cesium.HeightReference.NONE,
- clampAnimations: true
- },
- disableTransform: model.disableTransform || false,
- gateNodePrefix: model.gateNodePrefix || '',
- gateNodeNames: model.gateNodeNames || []
- })
- this.loadedModelEntities.push(entity)
-
- const modelFileName = model.filePath.split('/').pop()
-
- const isModel = (obj) => {
- if (!obj) return false
- const hasModelProps = obj._gltf !== undefined ||
- obj.readyPromise !== undefined ||
- obj._url !== undefined ||
- obj.constructor?.name === 'Model' ||
- (obj._runtimeNodes && Array.isArray(obj._runtimeNodes)) ||
- obj._uri !== undefined
- return hasModelProps
- }
-
- const findModelRecursively = (collection) => {
- if (!collection || typeof collection.get !== 'function') {
- return null
- }
-
- let foundModels = []
-
- for (let i = 0; i < collection.length; i++) {
- const primitive = collection.get(i)
-
- if (isModel(primitive)) {
- const modelUrl = primitive._url || primitive._uri
- if (modelUrl && modelUrl.includes(modelFileName)) {
- return primitive
- }
- foundModels.push(primitive)
- }
-
- if (primitive && primitive.length !== undefined && typeof primitive.get === 'function') {
- const found = findModelRecursively(primitive)
- if (found) return found
- }
- }
-
- if (foundModels.length > 0) {
- return foundModels[0]
- }
-
- return null
- }
-
- const processModel = (modelPrimitive) => {
- // 查找 GLTF 数据的辅助函数
- const findGltfData = (obj) => {
- if (!obj || typeof obj !== 'object') return null;
-
- // 尝试从多个位置获取 GLTF 数据
- if (obj._gltf && obj._gltf.nodes) {
- return obj._gltf;
- }
- if (obj._model && obj._model._gltf && obj._model._gltf.nodes) {
- return obj._model._gltf;
- }
- if (obj._sourceChapters) {
- for (const chapter of obj._sourceChapters) {
- if (chapter._gltf && chapter._gltf.nodes) {
- return chapter._gltf;
- }
- }
- }
- // 深度搜索
- const searchDeep = (currentObj, depth = 0) => {
- if (depth > 3) return null;
- for (const key of Object.keys(currentObj)) {
- if (key.startsWith('_') && typeof currentObj[key] === 'object') {
- if (currentObj[key]._gltf && currentObj[key]._gltf.nodes) {
- return currentObj[key]._gltf;
- }
- const found = searchDeep(currentObj[key], depth + 1);
- if (found) return found;
- }
- }
- return null;
- };
- return searchDeep(obj);
- };
-
- const waitForGltf = (callback) => {
- let waitCount = 0
- const maxWaitCount = 30 // 减少等待次数,从50次降到30次
-
- const checkGltf = () => {
- waitCount++
-
- // 使用增强的查找函数
- const gltfData = findGltfData(modelPrimitive);
- const hasGltfData = gltfData !== null;
-
- console.log(`⏳ 等待 GLTF [${waitCount}/${maxWaitCount}]: ready=${modelPrimitive.ready}, 找到GLTF=${hasGltfData}`)
-
- if (hasGltfData) {
- console.log('✅ GLTF 数据完整,开始提取节点...')
- // 将找到的 GLTF 数据挂载到 modelPrimitive 上
- if (!modelPrimitive._gltf) {
- modelPrimitive._gltf = gltfData;
- }
- callback()
- } else if (waitCount < maxWaitCount) {
- setTimeout(checkGltf, 100) // 减少等待间隔,从200ms降到100ms
- } else {
- console.warn('⚠️ 等待超时,尝试提取现有数据...')
- callback()
- }
- }
-
- checkGltf()
- }
-
- const handleReadyModel = () => {
- console.log('🔧 handleReadyModel 被调用');
-
- this.applyTransparentMaterial(modelPrimitive, model.transparency)
- this.enableAntialiasing(modelPrimitive)
-
- const gateNames = entity.gateNodeNames
- const hasGateNames = gateNames && (Array.isArray(gateNames) || Array.isArray(gateNames.valueOf?.())) && gateNames.length > 0
-
- console.log('========== 莒口水闸场景处理 ==========');
- console.log('模型URL:', model.url);
- console.log('gateNodeNames:', gateNames);
- console.log('hasGateNames:', hasGateNames);
- console.log('entity:', entity);
- console.log('model:', model);
-
- // 对于莒口水闸场景,使用控制器初始化闸门
- const modelPath = model.url || model.filePath;
- const isJuKouScene = modelPath && modelPath.includes('莒口水闸')
- if (hasGateNames || isJuKouScene) {
- console.log('🔄 莒口水闸场景,使用控制器初始化闸门...');
- const that = this
-
- // 使用控制器初始化场景
- createController(entity, {
- initialPosition: that.gatePosition,
- onGateNodesReady: (gateNames) => {
- console.log(`✅ 闸门节点就绪,共 ${gateNames.length} 个闸门`);
-
- // 显示闸门控制面板
- that.showGateControl = true;
-
- // 更新 GateControl 组件的闸门列表
- that.$nextTick(() => {
- if (that.$refs.gateControl) {
- that.$refs.gateControl.updateGateList(gateNames);
- console.log('✅ 闸门列表已更新');
- } else if (window.gateControlInstance) {
- window.gateControlInstance.updateGateList(gateNames);
- console.log('✅ 通过全局实例更新闸门列表');
- }
- });
- }
- });
- }
- }
-
- if (modelPrimitive.ready === true) {
- handleReadyModel()
- } else if (modelPrimitive.readyPromise && typeof modelPrimitive.readyPromise.then === 'function') {
- try {
- const promise = modelPrimitive.readyPromise.then(handleReadyModel)
- if (promise && typeof promise.catch === 'function') {
- promise.catch((error) => {
- console.error('模型加载失败:', error)
- })
- }
- } catch (e) {
- console.error('处理 readyPromise 失败:', e)
- handleReadyModel()
- }
- } else {
- setTimeout(() => {
- handleReadyModel()
- }, 500)
- }
- }
-
- const findModelInPrimitives = () => {
- if (!window.viewer || !window.viewer.scene) {
- return null
- }
-
- const primitives = window.viewer.scene.primitives
- return findModelRecursively(primitives)
- }
-
- let targetModel = findModelInPrimitives()
-
- if (targetModel) {
- processModel(targetModel)
- } else {
- let pollCount = 0
- const maxPollCount = 50
- const pollForModel = () => {
- pollCount++
- targetModel = findModelInPrimitives()
-
- if (targetModel) {
- processModel(targetModel)
- } else if (pollCount < maxPollCount) {
- setTimeout(pollForModel, 200)
- }
- }
- pollForModel()
- return
- }
-
- } catch (error) {
- console.error('加载场景模型失败:', error)
- }
- },
- // 应用半透明材质
- applyTransparentMaterial(modelPrimitive, transparency) {
- try {
- const Cesium = window.Cesium
- const alpha = transparency !== undefined ? transparency : 0.7
-
- if (modelPrimitive._sourceChapter && modelPrimitive._sourceChapter._programs) {
- const chapters = modelPrimitive._sourceChapter
- if (chapters) {
- modelPrimitive.style = new Cesium.ModelStyle({
- color: Cesium.Color.fromAlpha(Cesium.Color.WHITE, alpha)
- })
- }
- }
-
- modelPrimitive.color = Cesium.Color.fromAlpha(Cesium.Color.WHITE, alpha)
-
- } catch (error) {
- console.error('应用半透明材质失败:', error)
- }
- },
- // 启用抗锯齿
- enableAntialiasing(modelPrimitive) {
- try {
- const Cesium = window.Cesium
-
- modelPrimitive.enableAntiAliasing = true
-
- if (modelPrimitive.silhouette) {
- modelPrimitive.silhouetteEnabled = true
- }
-
- } catch (error) {
- console.error('启用抗锯齿失败:', error)
- }
- },
- removeTyphoon() {
- console.log('卸载台风')
- this.showTyphoon = false
- this.typhoonDataUrl = ''
- this.loadedTyphoonId = null
- ElMessage.success('台风路径已卸载')
- },
- // 加载风场
- loadWindField() {
- console.log('加载风场')
- if (!window.viewer) {
- ElMessage.error('Cesium viewer 未初始化')
- return
- }
- this.showWindField = true
- ElMessage.success('风场加载成功')
- },
- // 卸载风场
- removeWindField() {
- console.log('卸载风场')
- this.showWindField = false
- ElMessage.success('风场已卸载')
- },
- // 加载降雨
- loadRainfall() {
- console.log('加载降雨')
- if (!window.viewer) {
- ElMessage.error('Cesium viewer 未初始化')
- return
- }
- this.showRainfall = true
- ElMessage.success('降雨加载成功')
- },
- // 卸载降雨
- removeRainfall() {
- console.log('卸载降雨')
- this.showRainfall = false
- ElMessage.success('降雨已卸载')
- },
- // 加载云图
- loadCloudMap() {
- console.log('加载云图')
- if (!window.viewer) {
- ElMessage.error('Cesium viewer 未初始化')
- return
- }
- this.showCloudMap = true
- ElMessage.success('云图加载成功')
- },
- // 卸载云图
- removeCloudMap() {
- console.log('卸载云图')
- this.showCloudMap = false
- ElMessage.success('云图已卸载')
- },
- // 加载雷达
- loadRadar() {
- console.log('加载雷达')
- if (!window.viewer) {
- ElMessage.error('Cesium viewer 未初始化')
- return
- }
- this.showRadar = true
- ElMessage.success('雷达加载成功')
- },
- // 卸载雷达
- removeRadar() {
- console.log('卸载雷达')
- this.showRadar = false
- ElMessage.success('雷达已卸载')
- },
- removeModel(model) {
- const index = this.loadedModelEntities.findIndex(entity => entity.name === model.id)
- if (index !== -1) {
- const entity = this.loadedModelEntities[index]
-
- // 恢复模型样式
- this.restoreModelStyle(entity);
-
- // 清理点击事件 handler
- if (entity._clickHandler) {
- entity._clickHandler.destroy();
- entity._clickHandler = null;
- }
-
- viewer.entities.remove(entity)
- this.loadedModelEntities.splice(index, 1)
- this.saveLoadedServices()
-
- // 如果当前编辑器是这个模型,清理编辑器
- if (this.currentModelEditor && this.currentModelEditor.entity === entity) {
- if (this.currentModelEditor.destroy) {
- this.currentModelEditor.destroy();
- }
- this.currentModelEditor = null;
- }
-
- // 如果最后高亮的模型是这个模型,清除引用
- if (this.lastHighlightedModel === entity) {
- this.lastHighlightedModel = null;
- }
-
- // ElMessage.success(`模型 "${model.name}" 已删除`)
- }
- },
- // 加载实时台风(API获取逻辑由 RealtimeTyphoon 组件内部处理)
- loadRealtimeTyphoon() {
- console.log('加载实时台风')
- if (!window.viewer) {
- ElMessage.error('Cesium viewer 未初始化')
- return
- }
- // 如果已加载历史台风,先卸载避免冲突
- if (this.showTyphoon) {
- this.removeTyphoon()
- }
- this.showRealtimeTyphoon = true
- },
- // 卸载实时台风
- removeRealtimeTyphoon() {
- console.log('卸载实时台风')
- this.showRealtimeTyphoon = false
- ElMessage.success('实时台风已卸载')
- },
- // 加载业务场景
- loadBusinessScenario(scenarioType) {
- console.log('加载业务场景:', scenarioType)
- if (!window.viewer) {
- ElMessage.error('Cesium viewer 未初始化')
- return
- }
- const scenarioConfig = {
- 'flood-control': {
- title: '防洪排涝',
- description: '加载防洪排涝业务场景相关数据'
- },
- 'water-resource': {
- title: '水资源管理',
- description: '加载水资源管理业务场景相关数据'
- },
- 'project-supervision': {
- title: '水利工程监管',
- description: '加载水利工程监管业务场景相关数据'
- },
- 'eco-environment': {
- title: '生态环境保护',
- description: '加载生态环境保护业务场景相关数据'
- }
- }
- const config = scenarioConfig[scenarioType]
- if (config) {
- ElMessage.info(`${config.title}:${config.description}`)
- console.log(config.description)
- } else {
- ElMessage.warning('未知的业务场景类型')
- }
- },
- // 创建业务场景 - 通知父组件打开弹窗
- handleCreateScenario() {
- console.log('触发创建业务场景事件')
- this.$emit('open-create-scenario')
- },
- // 从数据库刷新业务场景列表
- refreshBusinessScenes() {
- console.log('从数据库加载业务场景列表...')
- listBusinessScene({}).then(response => {
- if (response.code === 200) {
- this.businessScenes = response.rows || []
- console.log('业务场景列表加载成功:', this.businessScenes)
- } else {
- console.error('加载业务场景列表失败:', response.msg)
- ElMessage.error('加载业务场景列表失败')
- }
- }).catch(error => {
- console.error('加载业务场景列表出错:', error)
- ElMessage.error('加载业务场景列表出错')
- })
- },
- // 加载固定场景列表
- loadFixedScenes() {
- this.fixedScenes = getFixedScenes()
- console.log('固定场景列表加载成功:', this.fixedScenes)
- },
- // 加载固定场景
- async loadFixedScene(scene) {
- console.log('加载固定场景:', scene)
-
- if (!window.viewer) {
- ElMessage.error('Cesium viewer 未初始化')
- return
- }
-
- if (!window.Cesium) {
- ElMessage.error('Cesium 库未加载')
- return
- }
- try {
- // 设置当前场景ID
- this.currentSceneId = scene.sceneId
-
- // 加载第三人称相机位置(视口跳转)
- if (scene.thirdPersonCameraPos) {
- const cameraPos = JSON.parse(scene.thirdPersonCameraPos)
-
- if (!Array.isArray(cameraPos) || cameraPos.length !== 3) {
- console.error('相机位置格式不正确:', cameraPos)
- ElMessage.error('相机位置数据格式错误')
- return
- }
-
- const Cesium = window.Cesium
- const posCartesian = Cesium.Cartesian3.fromArray(cameraPos)
-
- if (!posCartesian) {
- console.error('Cartesian3.fromArray 返回 undefined')
- ElMessage.error('坐标转换失败')
- return
- }
-
- // 优先使用方向向量和上向量设置相机
- if (scene.cameraDirection && scene.cameraUp) {
- const direction = Cesium.Cartesian3.fromArray(JSON.parse(scene.cameraDirection))
- const up = Cesium.Cartesian3.fromArray(JSON.parse(scene.cameraUp))
-
- if (direction && up) {
- console.log('使用方向向量和上向量设置相机')
- window.viewer.camera.setView({
- destination: posCartesian,
- orientation: {
- direction: direction,
- up: up
- }
- })
- } else {
- // 回退到HPR角度设置
- const heading = scene.thirdPersonCameraHeading || 0
- const pitch = scene.thirdPersonCameraPitch || Cesium.Math.toRadians(-90)
- const roll = scene.thirdPersonCameraRoll || 0
-
- window.viewer.camera.setView({
- destination: posCartesian,
- orientation: {
- heading: heading,
- pitch: pitch,
- roll: roll
- }
- })
- }
- } else {
- // 使用目标位置计算方向向量
- if (scene.thirdPersonCameraTarget) {
- const cameraTarget = JSON.parse(scene.thirdPersonCameraTarget)
- if (Array.isArray(cameraTarget) && cameraTarget.length === 3) {
- const targetCartesian = Cesium.Cartesian3.fromArray(cameraTarget)
- if (targetCartesian) {
- const direction = Cesium.Cartesian3.subtract(posCartesian, targetCartesian, new Cesium.Cartesian3())
- Cesium.Cartesian3.normalize(direction, direction)
-
- window.viewer.camera.setView({
- destination: posCartesian,
- orientation: {
- direction: direction,
- up: new Cesium.Cartesian3(0, 0, 1)
- }
- })
- }
- }
- } else {
- // 使用HPR角度设置
- const heading = scene.thirdPersonCameraHeading || 0
- const pitch = scene.thirdPersonCameraPitch || Cesium.Math.toRadians(-90)
- const roll = scene.thirdPersonCameraRoll || 0
-
- window.viewer.camera.setView({
- destination: posCartesian,
- orientation: {
- heading: heading,
- pitch: pitch,
- roll: roll
- }
- })
- }
- }
- }
- // 莒口水闸场景使用独立页面组件
- if (scene.sceneId === JuKouSceneConfig.sceneId) {
- console.log('📌 使用莒口水闸独立页面组件')
-
- // 显示莒口水闸页面组件
- this.showJuKouScenePage = true
- this.gatePosition = scene.initialGatePosition || 0
-
- // 等待组件加载场景
- // 场景加载逻辑由 JuKouShuiZhaPage 组件内部处理
- } else {
- // 其他场景使用通用加载方法
- if (scene.loadedModels) {
- try {
- const models = JSON.parse(scene.loadedModels)
- console.log('需要加载的模型:', models)
- for (const model of models) {
- const modelObj = {
- id: model.name,
- name: model.name,
- filePath: model.filePath,
- coordinates: model.coordinates,
- rotation: model.rotation || '0,0,0',
- transparency: model.transparency,
- disableTransform: scene.disableModelTransform || false,
- gateNodePrefix: scene.gateNodePrefix || '',
- gateNodeNames: scene.gateNodeNames || []
- }
- this.loadSceneModel(modelObj)
- }
- } catch (e) {
- console.error('解析模型配置失败:', e)
- }
- }
-
- if (scene.enableGateControl) {
- this.gatePosition = scene.initialGatePosition || 0
- console.log('📌 场景启用闸门控制,等待模型加载...')
- }
- }
- // 加载模型拆解图标
- if (scene.modelDisassemblyIcon) {
- this.modelIconConfig = scene.modelDisassemblyIcon
- }
-
- ElMessage.success(`已加载固定场景:${scene.sceneName}`)
- } catch (error) {
- console.error('加载固定场景失败:', error)
- ElMessage.error('加载固定场景失败')
- }
- },
- // 卸载场景
- unloadScene(scene) {
- console.log('卸载场景:', scene)
-
- // 莒口水闸场景使用独立卸载方法
- if (scene.sceneId === JuKouSceneConfig.sceneId) {
- console.log('📌 使用莒口水闸独立场景卸载方法')
- unloadJuKouScene()
- } else {
- // 其他场景使用通用卸载方法
- if (scene.loadedModels) {
- try {
- const models = JSON.parse(scene.loadedModels)
- console.log('需要卸载的模型:', models)
- for (const model of models) {
- this.unloadSceneModel(model.name)
- }
- } catch (e) {
- console.error('解析模型配置失败:', e)
- }
- }
- }
-
- // 关闭闸门控制面板
- this.showGateControl = false
- this.gatePosition = 0
-
- // 关闭莒口水闸页面组件
- this.showJuKouScenePage = false
- // 清除模型拆解图标配置
- this.modelIconConfig = null
- // 清除当前场景ID
- this.currentSceneId = null
-
- ElMessage.success(`已卸载场景:${scene.sceneName}`)
- },
- // 卸载场景模型
- unloadSceneModel(modelId) {
- const viewer = window.viewer
- if (!viewer) {
- console.error('Cesium viewer 未初始化')
- return
- }
-
- // 查找并移除模型实体
- const entityIndex = this.loadedModelEntities.findIndex(entity => entity.name === modelId)
- if (entityIndex !== -1) {
- const entity = this.loadedModelEntities[entityIndex]
- viewer.entities.remove(entity)
- this.loadedModelEntities.splice(entityIndex, 1)
- console.log('场景模型卸载完成:', modelId)
- } else {
- console.log('未找到需要卸载的模型:', modelId)
- }
- },
- // 显示删除确认对话框
- showDeleteConfirm(scene) {
- console.log('显示删除确认:', scene)
-
- // 检查是否为固定场景
- if (isFixedScene(scene.sceneId)) {
- ElMessage.warning('固定场景不能删除,只能卸载')
- return
- }
-
- ElMessageBox.confirm(
- '确定要删除场景 "' + scene.sceneName + '" 吗?',
- '提示',
- {
- confirmButtonText: '确定',
- cancelButtonText: '取消',
- type: 'warning'
- }
- ).then(() => {
- this.handleDeleteBusinessScene(scene.sceneId)
- }).catch(() => {
- ElMessage.info('已取消删除')
- })
- },
- // 删除业务场景
- handleDeleteBusinessScene(sceneId) {
- console.log('删除业务场景:', sceneId)
- delBusinessScene(sceneId).then(response => {
- if (response.code === 200) {
- ElMessage.success('业务场景删除成功')
- // 刷新场景列表
- this.refreshBusinessScenes()
- } else {
- ElMessage.error('删除业务场景失败: ' + response.msg)
- }
- }).catch(error => {
- console.error('删除业务场景出错:', error)
- ElMessage.error('删除业务场景出错')
- })
- },
- // 从数据库加载业务场景
- loadBusinessScenarioFromDB(scene) {
- console.log('从数据库加载业务场景:', scene)
-
- if (!window.viewer) {
- ElMessage.error('Cesium viewer 未初始化')
- return
- }
-
- if (!window.Cesium) {
- ElMessage.error('Cesium 库未加载')
- return
- }
- try {
- // 设置当前场景ID
- this.currentSceneId = scene.sceneId
- // 加载第三人称相机位置(视口跳转)
- if (scene.thirdPersonCameraPos && scene.thirdPersonCameraTarget) {
- console.log('thirdPersonCameraPos:', scene.thirdPersonCameraPos)
- console.log('thirdPersonCameraTarget:', scene.thirdPersonCameraTarget)
-
- const cameraPos = JSON.parse(scene.thirdPersonCameraPos)
- const cameraTarget = JSON.parse(scene.thirdPersonCameraTarget)
- console.log('解析后的相机位置:', cameraPos, '类型:', Array.isArray(cameraPos))
- console.log('解析后的目标位置:', cameraTarget, '类型:', Array.isArray(cameraTarget))
-
- // 检查数组长度是否正确
- if (!Array.isArray(cameraPos) || cameraPos.length !== 3) {
- console.error('相机位置格式不正确:', cameraPos)
- ElMessage.error('相机位置数据格式错误')
- return
- }
-
- if (!Array.isArray(cameraTarget) || cameraTarget.length !== 3) {
- console.error('目标位置格式不正确:', cameraTarget)
- ElMessage.error('目标位置数据格式错误')
- return
- }
-
- // 使用 Cesium 的 flyTo 方法跳转到指定视角
- const Cesium = window.Cesium
- console.log('Cesium.Cartesian3:', Cesium.Cartesian3)
-
- const posCartesian = Cesium.Cartesian3.fromArray(cameraPos)
- console.log('posCartesian:', posCartesian)
-
- const targetCartesian = Cesium.Cartesian3.fromArray(cameraTarget)
- console.log('targetCartesian:', targetCartesian)
-
- if (!posCartesian || !targetCartesian) {
- console.error('Cartesian3.fromArray 返回 undefined')
- ElMessage.error('坐标转换失败')
- return
- }
-
- const direction = Cesium.Cartesian3.subtract(
- posCartesian,
- targetCartesian,
- new Cesium.Cartesian3()
- )
- console.log('direction before normalize:', direction)
-
- Cesium.Cartesian3.normalize(direction, direction)
- console.log('direction after normalize:', direction)
-
- // 优先使用方向向量和上向量精确恢复相机状态
- if (scene.thirdPersonCameraDirection && scene.thirdPersonCameraUp) {
- try {
- const directionArray = JSON.parse(scene.thirdPersonCameraDirection)
- const upArray = JSON.parse(scene.thirdPersonCameraUp)
-
- if (directionArray && directionArray.length === 3 && upArray && upArray.length === 3) {
- const direction = new Cesium.Cartesian3(
- directionArray[0],
- directionArray[1],
- directionArray[2]
- )
- const up = new Cesium.Cartesian3(
- upArray[0],
- upArray[1],
- upArray[2]
- )
-
- console.log('使用方向向量和上向量恢复相机状态')
- console.log('方向向量:', direction)
- console.log('上向量:', up)
-
- // 使用 setView 方法精确恢复相机状态
- window.viewer.camera.setView({
- destination: posCartesian,
- orientation: {
- direction: direction,
- up: up
- }
- })
- console.log('使用方向向量和上向量恢复相机完成')
- return // 使用方向向量恢复后继续后续逻辑
- }
- } catch (e) {
- console.error('解析方向向量或上向量失败:', e)
- }
- }
-
- // 如果没有方向向量,使用 HPR 角度(数据库中已存储为弧度值)
- const heading = scene.thirdPersonCameraHeading || 0
- const pitch = scene.thirdPersonCameraPitch || Cesium.Math.toRadians(-90) // 默认俯视90度
- const roll = scene.thirdPersonCameraRoll || 0
- console.log('保存的相机朝向(弧度):', heading, pitch, roll)
- console.log('保存的相机朝向(角度):', Cesium.Math.toDegrees(heading), Cesium.Math.toDegrees(pitch), Cesium.Math.toDegrees(roll))
-
- // 使用 setView 方法设置相机位置和朝向
- // 注意:数据库中保存的已经是弧度值,不需要再转换
- window.viewer.camera.setView({
- destination: posCartesian,
- orientation: {
- heading: heading,
- pitch: pitch,
- roll: roll
- }
- })
- console.log('使用HPR角度恢复相机完成')
- }
- // 加载第一人称相机设置
- if (scene.firstPersonCameraPos && scene.firstPersonCameraTarget) {
- const firstPersonPos = JSON.parse(scene.firstPersonCameraPos)
- const firstPersonTarget = JSON.parse(scene.firstPersonCameraTarget)
- // 可以根据需要应用第一人称相机设置
- console.log('第一人称相机位置:', firstPersonPos, firstPersonTarget)
- }
- // 加载模型
- if (scene.loadedModels) {
- const models = JSON.parse(scene.loadedModels)
- console.log('需要加载的模型:', models)
- // 可以根据需要加载模型
- }
- // 加载数据服务
- if (scene.dataServices) {
- const services = JSON.parse(scene.dataServices)
- console.log('需要加载的数据服务:', services)
- // 可以根据需要加载数据服务
- }
- // 加载POI点
- if (scene.poiPoints) {
- const poiPoints = JSON.parse(scene.poiPoints)
- console.log('需要加载的POI点:', poiPoints)
- // 可以根据需要加载POI点
- }
-
- // 数据库场景不显示闸门控制框
- this.showGateControl = false
-
- ElMessage.success(`已加载业务场景:${scene.sceneName}`)
- } catch (error) {
- console.error('加载业务场景失败:', error)
- ElMessage.error('加载业务场景失败')
- }
- },
- // 处理闸门位置变化(支持单独控制每个闸门)
- handleGatePositionChange(data) {
- console.log('🔧 handleGatePositionChange 被调用:', data)
-
- // 获取控制器实例
- const controller = getController()
- if (!controller || !controller.isInitialized) {
- console.warn('⚠️ 莒口水闸场景控制器未初始化')
- return
- }
-
- // 使用控制器设置闸门位置
- if (typeof data === 'number') {
- // 设置所有闸门位置
- this.gatePosition = data
- controller.setAllGatesPosition(data)
- } else if (data && data.gateName && typeof data.position === 'number') {
- // 设置单个闸门位置
- controller.setSingleGatePosition(data.gateName, data.position)
- }
- },
-
- // 莒口水闸场景加载完成回调
- onJuKouSceneLoaded() {
- console.log('✅ 莒口水闸场景页面组件加载完成')
- },
-
- // 莒口水闸场景卸载完成回调
- onJuKouSceneUnloaded() {
- console.log('✅ 莒口水闸场景页面组件已卸载')
- this.showJuKouScenePage = false
- },
- // 区分地质体组件(需销毁)和其他组件
- change(val, val2) {
- if (val2) {
- this.view = "";
- if (val === this.view2) {
- this.view2 = "";
- return;
- }
- this.view2 = val;
- } else {
- this.view2 = "";
- if (val === this.view) {
- this.view = "";
- return;
- }
- this.view = val;
- }
- },
- //添加公共服务
- addWebServe(obj, shouldFlyTo = true) {
- console.log('===== addWebServe 开始 =====');
- console.log('服务名称:', obj.name);
- console.log('服务类型:', obj.type);
- console.log('服务状态:', obj.state);
- console.log('是否有transparentColor:', obj.transparentColor);
-
- if (obj.state === 0) {
- // add mvt
- if (obj.type === "MVT") {
- console.log('MVT类型,直接返回');
- layerManagement.addMvtLayer(obj.proxiedUrl, obj.name, () => {
- obj.state = 1;
- window.store.actions.setChangeLayers();
- this.saveLoadedServices();
- });
- return;
- }
- // add scene
- console.log('REALSPACE类型,准备加载场景');
- // useServerView 为 true 时使用超图服务端默认视口,否则使用本地配置的视口
- const useServerView = obj.useServerView || false;
- // 构建场景选项,包含透明色配置
- const sceneOptions = {
- autoSetView: useServerView
- };
- if (obj.transparentColor) {
- sceneOptions.transparentColor = obj.transparentColor;
- sceneOptions.transparentColorTolerance = obj.transparentColorTolerance || 0.01;
- console.log('透明色配置:', sceneOptions);
- } else {
- console.log('没有配置透明色');
- }
-
- layerManagement.addScene(
- obj.proxiedUrl,
- sceneOptions,
- layers => {
- console.log('场景加载完成回调');
- obj.state = 1;
- window.store.actions.setChangeLayers();
- // 白膜添加线框
- if (obj.style && obj.style.fillStyle) {
- layers[0].style3D.lineColor = Cesium.Color.fromCssColorString(
- "rgb(67,67,67)"
- );
- layers[0].style3D.fillStyle =
- Cesium.FillStyle[obj.style.fillStyle];
- }
-
- this.saveLoadedServices();
- // 如果使用服务端默认视口,则不需要调用本地flyTo
- if (shouldFlyTo && !useServerView) {
- this.flyTo(obj.name, server_flyTo_config);
- }
- }
- );
- } else {
- console.log('服务已加载,仅执行飞行');
- if (shouldFlyTo) {
- this.flyTo(obj.name, server_flyTo_config);
- }
- }
- },
- // 添加底图
- addBaseLayer(obj) {
- if (this.baseLayerObj.type === obj.type) return;
- this.baseLayerObj.state = 0;
- obj.state = 1;
- this.baseLayerObj = obj;
- let type = obj.type;
- let url = obj.proxiedUrl;
- let imageryLayerCollection = viewer.scene.globe._imageryLayerCollection;
- let layer = imageryLayerCollection.get(0);
- let imageryProvider;
-
- // 保存注记图层(如果存在)
- let labelLayer = null;
- for (let i = 0; i < imageryLayerCollection.length; i++) {
- const currentLayer = imageryLayerCollection.get(i);
- if (currentLayer && currentLayer.imageryProvider && currentLayer.imageryProvider.url && currentLayer.imageryProvider.url.includes('cia_c')) {
- labelLayer = currentLayer;
- imageryLayerCollection.remove(currentLayer);
- break;
- }
- }
-
- if (imageryLayerCollection.get(2)) {
- imageryLayerCollection.remove(imageryLayerCollection.get(2));
- }
- if (imageryLayerCollection.get(1)) {
- imageryLayerCollection.remove(imageryLayerCollection.get(1));
- }
- switch (type) {
- case "BINGMAP":
- imageryProvider = new Cesium.BingMapsImageryProvider({
- url: url,
- key:
- "Aq0D7MCY5ErORA9vrwFtfE9aancUq5J6uNjw0GieF0ostaIrVuJZ8ScXxNHHvEwS"
- });
- break;
- case "TIANDITU":
- // 使用天地图影像底图(使用HTTPS)
- imageryProvider = new Cesium.WebMapTileServiceImageryProvider({
- url: `${PROXY_TIANDITU_TILE}/img_c/wmts?service=WMTS&request=GetTile&version=1.0.0&LAYER=img&tileMatrixSet=c&TileMatrix={TileMatrix}&TileRow={TileRow}&TileCol={TileCol}&style=default&format=tiles&tk=${TIANDITU_KEY}`,
- layer: 'img',
- style: 'default',
- format: 'image/jpeg',
- tileMatrixSetID: 'c',
- subdomains: ['t0', 't1', 't2', 't3', 't4', 't5', 't6', 't7'],
- tilingScheme: new Cesium.GeographicTilingScheme(),
- tileMatrixLabels: ['1', '2', '3', '4', '5', '6', '7', '8', '9', '10', '11', '12', '13', '14', '15', '16', '17', '18', '19'],
- maximumLevel: 15,
- show: true
- });
- break;
- case "IMAGE":
- imageryProvider = new Cesium.SingleTileImageryProvider({
- url: url
- });
- break;
- case "OSM":
- imageryProvider = new Cesium.createOpenStreetMapImageryProvider({
- url: url
- });
- break;
- case "MAPBOX":
- imageryProvider = new Cesium.MapboxImageryProvider({
- mapId: "mapbox.dark"
- });
- break;
- case "SUPERMAPDARK":
- imageryProvider = new Cesium.SuperMapImageryProvider({
- url: url
- });
- break;
- case "SUPERMAPLIGHT":
- imageryProvider = new Cesium.SuperMapImageryProvider({
- url: url
- });
- break;
- case "GRIDIMAGERY":
- imageryProvider = imageryProvider;
- break;
- default:
- imageryProvider = new Cesium.SingleTileImageryProvider({
- url: url
- });
- break;
- }
- if (type != "GRIDIMAGERY") {
- imageryLayerCollection.addImageryProvider(imageryProvider, 0);
- imageryLayerCollection.remove(layer);
-
- // 重新添加注记图层
- if (type === "TIANDITU") {
- // 当天地图作为底图时,总是添加影像注记图层
- // tk 来自外部配置 supermap.config.js
- labelLayer = imageryLayerCollection.addImageryProvider(
- new Cesium.WebMapTileServiceImageryProvider({
- url: `${PROXY_TIANDITU_TILE}/cia_c/wmts?service=WMTS&request=GetTile&version=1.0.0&LAYER=cia&tileMatrixSet=c&TileMatrix={TileMatrix}&TileRow={TileRow}&TileCol={TileCol}&style=default&format=tiles&tk=${TIANDITU_KEY}`,
- layer: 'cia',
- style: 'default',
- format: 'image/png',
- tileMatrixSetID: 'c',
- subdomains: ['t0', 't1', 't2', 't3', 't4', 't5', 't6', 't7'],
- tilingScheme: new Cesium.GeographicTilingScheme(),
- tileMatrixLabels: ['1', '2', '3', '4', '5', '6', '7', '8', '9', '10', '11', '12', '13', '14', '15', '16', '17', '18', '19'],
- maximumLevel: 15,
- show: true
- })
- );
- console.log('为天地图添加影像注记图层');
- } else if (!labelLayer) {
- // 其他底图时,如果没有注记图层,则添加
- // tk 来自外部配置 supermap.config.js
- labelLayer = imageryLayerCollection.addImageryProvider(
- new Cesium.WebMapTileServiceImageryProvider({
- url: `${PROXY_TIANDITU_TILE}/cia_c/wmts?service=WMTS&request=GetTile&version=1.0.0&LAYER=cia&tileMatrixSet=c&TileMatrix={TileMatrix}&TileRow={TileRow}&TileCol={TileCol}&style=default&format=tiles&tk=${TIANDITU_KEY}`,
- layer: 'cia',
- style: 'default',
- format: 'image/png',
- tileMatrixSetID: 'c',
- subdomains: ['t0', 't1', 't2', 't3', 't4', 't5', 't6', 't7'],
- tilingScheme: new Cesium.GeographicTilingScheme(),
- tileMatrixLabels: ['1', '2', '3', '4', '5', '6', '7', '8', '9', '10', '11', '12', '13', '14', '15', '16', '17', '18', '19'],
- maximumLevel: 15,
- show: true
- })
- );
- console.log('添加天地图影像注记图层');
- } else {
- imageryLayerCollection.addImageryProvider(labelLayer);
- console.log('恢复天地图影像注记图层');
- }
- }
- if (type == "GRIDIMAGERY") {
- imageryLayerCollection.addImageryProvider(
- new Cesium.TileCoordinatesImageryProvider(),
- 1
- );
- imageryLayerCollection.addImageryProvider(
- new Cesium.GridImageryProvider(),
- 2
- );
- }
- this.saveLoadedServices();
- },
- // 添加在线地形
- addOnlineTerrain(obj) {
- if (this.terrainLayerObj) this.terrainLayerObj.state = 0;
- obj.state = 1;
- this.terrainLayerObj = obj;
- let type = obj.type;
- let url = obj.proxiedUrl;
- switch (type) {
- case "STKTerrain":
- viewer.terrainProvider = new Cesium.CesiumTerrainProvider({
- url: url,
- isSct: false
- });
- break;
- case "tianDiTuTerrain":
- console.log('加载 Cesium 官方地形...');
- const cesiumIonToken = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJqdGkiOiJiZGYwNWZmMi0wYTJjLTQ3MDgtODk3Mi1mNTg4YjljZTMzYWUiLCJpZCI6MjA0NTU5LCJpYXQiOjE3MTE0NDc4NDh9.xU-eWEQxv-FeADvsH_uM35EKRN6brqmXl1AQm6phnEM";
- Cesium.Ion.defaultAccessToken = cesiumIonToken;
- // 低性能机器关闭水面掩码和顶点法线,减少地形数据量和光照计算负担
- const perfLow = window.__perfTier === 'low';
- try {
- viewer.terrainProvider = Cesium.createWorldTerrain({
- requestWaterMask: !perfLow,
- requestVertexNormals: !perfLow
- });
- console.log('Cesium 地形加载成功', perfLow ? '(低性能模式:已关闭水面掩码/顶点法线)' : '');
- } catch (e) {
- console.error('Cesium 地形加载失败:', e);
- viewer.terrainProvider = new Cesium.EllipsoidTerrainProvider();
- }
- break;
- case "supermapOnlineTerrain":
- viewer.terrainProvider = new Cesium.SCTTerrainProvider({
- urls: [url]
- });
- break;
- default:
- break;
- }
- this.saveLoadedServices();
- },
- // 移除地形
- removeTerrainLayer() {
- viewer.terrainProvider = new Cesium.EllipsoidTerrainProvider({
- ellipsoid: viewer.scene.globe.ellipsoid
- });
- this.terrainLayerObj = null;
- },
- addDatas(id, obj, shouldFlyTo = true) {
- switch (id) {
- case "webServe":
- this.addWebServe(obj, shouldFlyTo);
- break;
- case "baseLayer":
- this.addBaseLayer(obj);
- break;
- case "onlineTerrain":
- this.addOnlineTerrain(obj);
- break;
- case "vectorData":
- this.addVectorData(obj, shouldFlyTo);
- break;
- }
- },
-
- // 添加矢量数据服务 - 根据类型选择加载方式
- addVectorData(obj, shouldFlyTo = true) {
- console.log('===== addVectorData 开始 =====');
- console.log('服务名称:', obj.name);
- console.log('服务URL:', obj.proxiedUrl);
- console.log('服务类型:', obj.type);
- console.log('服务状态:', obj.state);
- if (!viewer) {
- ElMessage.error('Cesium viewer 未初始化');
- return;
- }
- // 检查URL是否为空
- if (!obj.proxiedUrl || obj.proxiedUrl.trim() === '') {
- ElMessage.warning(`${obj.name} 暂无数据`);
- return;
- }
- if (obj.state === 0) {
- // 根据数据类型选择加载方式
- if (obj.type === 'MVT') {
- // MVT 类型使用矢量瓦片加载
- console.log('使用MVT方式加载');
- layerManagement.addMvtLayer(obj.proxiedUrl, obj.name, obj.style, (mvtLayer) => {
- if (mvtLayer) {
- obj.state = 1;
- window.store.actions.setChangeLayers();
- this.saveLoadedServices();
- console.log(`${obj.name} MVT加载成功`);
- ElMessage.success(`${obj.name} 加载成功`);
-
- // 添加MVT图层点击事件处理,传入当前图层的瓦片URL
- this.addMvtClickHandler(obj.proxiedUrl);
- } else {
- ElMessage.error(`加载 ${obj.name} 失败`);
- }
- });
- } else if (obj.type === 'RESTFEATURE') {
- // RESTFEATURE 类型使用GeoJsonDataSource加载REST数据服务
- console.log('使用RESTFEATURE方式加载');
- this.addRestFeatureLayer(obj, shouldFlyTo);
- } else if (obj.type === 'RESTIMAGE') {
- // RESTIMAGE 类型使用 SuperMapImageryProvider 加载栅格地图服务
- console.log('使用RESTIMAGE方式加载');
- const imgLayer = layerManagement.addImageLayer(obj.proxiedUrl, obj.name, { skipOutputPrj: obj.skipOutputPrj });
- if (imgLayer) {
- obj.state = 1;
- window.store.actions.setChangeLayers();
- this.saveLoadedServices();
- console.log(`${obj.name} 栅格图加载成功`);
- ElMessage.success(`${obj.name} 加载成功`);
- // 土地利用加载时显示图例
- if (obj.name === '土地利用') {
- this.showLandUseLegend = true;
- }
- } else {
- ElMessage.error(`加载 ${obj.name} 失败`);
- }
- } else {
- ElMessage.warning(`不支持的数据类型: ${obj.type}`);
- }
- } else {
- console.log('服务已加载');
- if (shouldFlyTo) {
- this.flyToVectorData(obj.name);
- }
- }
- },
-
- // 使用GeoJsonDataSource加载REST要素服务(参考SHP转GeoJson的加载方式)
- addRestFeatureLayer(obj, shouldFlyTo = true) {
- console.log('===== 使用GeoJsonDataSource加载矢量数据 =====');
- console.log('服务名称:', obj.name);
- console.log('服务URL:', obj.proxiedUrl);
-
- // 获取样式配置
- const style = obj.style || {
- fillColor: '#00FF00',
- fillOpacity: 0.4,
- strokeColor: '#0055FF',
- strokeWidth: 2
- };
-
- // 参考SHP转GeoJson的加载方式,使用Cesium.GeoJsonDataSource.load
- layerManagement.addGeoJsonLayerWithStyle(obj.proxiedUrl, obj.name, style, (geoJsonLayer) => {
- if (geoJsonLayer) {
- obj.state = 1;
- window.store.actions.setChangeLayers();
- this.saveLoadedServices();
-
- console.log(`${obj.name} 矢量数据加载成功`);
-
- if (shouldFlyTo) {
- viewer.flyTo(geoJsonLayer, { duration: 2 });
- }
-
- // 为矢量数据添加点击弹窗事件
- this.addVectorDataClickHandler(obj.name);
-
- ElMessage.success(`${obj.name} 加载成功`);
- } else {
- ElMessage.error(`加载 ${obj.name} 失败`);
- }
- });
- },
-
-
-
- // 定位到矢量数据
- flyToVectorData(name) {
- if (!viewer || !viewer.dataSources) return;
-
- const dataSources = viewer.dataSources._dataSources;
- for (const ds of dataSources) {
- if (ds.name === name || (ds.name && ds.name.includes(name))) {
- viewer.flyTo(ds, { duration: 2 });
- break;
- }
- }
- },
-
- // 为矢量数据添加点击弹窗事件
- addVectorDataClickHandler(layerName) {
- if (!viewer) return;
-
- // 确保只添加一次点击事件
- if (viewer._vectorDataClickHandler) {
- viewer._vectorDataClickHandler.destroy();
- }
-
- viewer._vectorDataClickHandler = new Cesium.ScreenSpaceEventHandler(viewer.scene.canvas);
- viewer._vectorDataClickHandler.setInputAction((movement) => {
- const picked = viewer.scene.pick(movement.position);
-
- if (Cesium.defined(picked) && picked.id && picked.id._dataSource) {
- const dataSource = picked.id._dataSource;
- const entity = picked.id;
-
- // 检查是否是当前加载的矢量数据层
- if (dataSource.name === layerName || (dataSource.name && dataSource.name.includes(layerName))) {
- this.showVectorDataPopup(entity, movement.position);
- }
- } else if (Cesium.defined(picked) && picked.id && picked.id._properties) {
- // 处理通过GeoJsonDataSource加载的实体
- const entity = picked.id;
- this.showVectorDataPopup(entity, movement.position);
- }
- }, Cesium.ScreenSpaceEventType.LEFT_CLICK);
- },
-
- // 显示矢量数据弹窗
- showVectorDataPopup(entity, position) {
- console.log('点击矢量数据:', entity);
-
- // 获取实体属性
- let properties = {};
- if (entity._properties) {
- properties = entity._properties.getValue(Cesium.JulianDate.now());
- } else if (entity.properties) {
- properties = entity.properties.getValue(Cesium.JulianDate.now()) || entity.properties;
- }
-
- // 构建弹窗内容
- let content = '<div style="padding: 10px; max-width: 300px;">';
- content += `<h3 style="margin: 0 0 10px 0; font-size: 14px; color: #333;">${entity.name || '属性信息'}</h3>`;
-
- if (properties && typeof properties === 'object') {
- for (const key in properties) {
- if (properties.hasOwnProperty(key)) {
- let value = properties[key];
- if (value === null || value === undefined) value = '';
- if (typeof value === 'object') value = JSON.stringify(value);
- content += `<div style="margin: 5px 0; font-size: 12px;">
- <span style="color: #666;">${key}:</span>
- <span style="margin-left: 5px; color: #333;">${value}</span>
- </div>`;
- }
- }
- } else {
- content += '<div style="color: #999; font-size: 12px;">暂无属性信息</div>';
- }
- content += '</div>';
-
- // 使用tooltip显示弹窗(tooltip2.js的API是 showAt(message, top))
- if (window.tooltip) {
- const topPosition = position.y + 'px';
- window.tooltip.showAt(content, topPosition);
- }
- },
-
- // 添加MVT图层点击事件处理
- addMvtClickHandler(tileBaseUrl) {
- console.log('aside.vue addMvtClickHandler 被调用', tileBaseUrl);
-
- // 初始化 MVT 图层列表(支持多图层)
- if (!this._mvtTileUrls) {
- this._mvtTileUrls = [];
- }
-
- if (tileBaseUrl) {
- const tileUrl = tileBaseUrl.endsWith('/')
- ? tileBaseUrl + 'tiles/{z}/{x}/{y}.mvt'
- : tileBaseUrl + '/tiles/{z}/{x}/{y}.mvt';
-
- // 避免重复添加相同的瓦片URL
- if (!this._mvtTileUrls.includes(tileUrl)) {
- this._mvtTileUrls.push(tileUrl);
- }
- }
-
- // 初始化点击处理器(单例)
- if (!this.mvtClickHandler) {
- this.mvtClickHandler = new MvtClickHandler(this);
- }
-
- // 标记已加载 MVT 图层,全局点击处理器会进行检测
- this._hasMvtLayer = true;
- },
-
- // 初始化viewer后的传入回调:添加底图和清除加载动画
- async removeLoad() {
- this.isRestoring = true; // 标记正在恢复,禁止保存
-
- setTimeout(() => {
- let loading = document.getElementById("loadingbar");
- if (loading) loading.remove(); //移除加载动画
- document.getElementById("el-aside").classList.remove("disable"); //解除禁止点击
- }, 1000);
-
- // 等待自定义服务加载完成后再恢复
- setTimeout(async () => {
- await this.fetchCustomServices();
- await this.restoreLoadedServices();
- this.isRestoring = false; // 恢复完成,允许保存
-
- // 加载水面图层
- this.loadWaterLayers();
- }, 1500);
- },
-
- // 加载水面图层
- loadWaterLayers() {
- console.log('========== loadWaterLayers 开始执行 ==========');
- console.log('window.viewer:', window.viewer);
-
- if (!window.viewer) {
- console.error('window.viewer 不存在,无法加载水面图层');
- return;
- }
-
- console.log('开始加载莒口水闸河道范围水面...');
-
- // 加载莒口水闸河道范围水面
- waterLayer.loadWaterLayer(
- '/js/莒口水闸河道范围.geojson',
- '莒口水闸河道',
- (dataSource) => {
- console.log('水面图层加载完成', dataSource);
- }
- );
- },
- // 删除场景公共服务
- DeleteDates(datatype, obj) {
- switch (datatype) {
- case "webServe":
- obj.layers.forEach(layer => {
- layerManagement.layersDelete(layer.type, layer.layerName, () => {
- obj.state = 0;
- window.store.actions.setChangeLayers();
- this.saveLoadedServices();
- });
- });
- break;
- case "baseLayer":
- break;
- case "onlineTerrain":
- this.removeTerrainLayer();
- obj.state = 0;
- this.saveLoadedServices();
- break;
- case "vectorData":
- // 根据矢量数据类型选择卸载方式:MVT用removeVectorTilesMap,RESTIMAGE用imageryLayers,RESTFEATURE用dataSources
- let vectorDeleteType;
- if (obj.type === 'MVT') {
- vectorDeleteType = 'MVT';
- } else if (obj.type === 'RESTIMAGE') {
- vectorDeleteType = 'IMG';
- } else {
- vectorDeleteType = 'GEOJSON';
- }
- layerManagement.layersDelete(vectorDeleteType, obj.name, () => {
- obj.state = 0;
- window.store.actions.setChangeLayers();
- this.saveLoadedServices();
- // 土地利用卸载时隐藏图例
- if (obj.name === '土地利用') {
- this.showLandUseLegend = false;
- }
- });
- // MVT 卸载后从图层列表中移除,并更新标记
- if (obj.type === 'MVT') {
- // 从 _mvtTileUrls 中移除当前图层的 URL
- if (this._mvtTileUrls && obj.proxiedUrl) {
- const tileUrl = obj.proxiedUrl.endsWith('/')
- ? obj.proxiedUrl + 'tiles/{z}/{x}/{y}.mvt'
- : obj.proxiedUrl + '/tiles/{z}/{x}/{y}.mvt';
- const index = this._mvtTileUrls.indexOf(tileUrl);
- if (index > -1) {
- this._mvtTileUrls.splice(index, 1);
- }
- }
- // 只有当所有 MVT 图层都卸载时,才清除标记
- this._hasMvtLayer = this._mvtTileUrls && this._mvtTileUrls.length > 0;
- this.mvtPopupVisible = false;
- }
- break;
- }
- },
- isServiceLoaded(service) {
- if (!service) {
- return false;
- }
- const serviceId = service.id || service.serviceId;
- return this.loadedCustomServiceIds.includes(serviceId);
- },
- async loadCustomService(service, flyTo = true, shouldSave = true) {
- console.log('========== 开始加载自定义服务 ==========');
- console.log('服务名称:', service.name);
- console.log('服务类型:', service.type);
- console.log('服务URL:', service.url);
- console.log('是否跳转视角:', flyTo);
- console.log('viewer状态:', viewer);
- if (!viewer) {
- ElMessage.error('Cesium viewer 未初始化');
- return;
- }
- if (this.isServiceLoaded(service)) {
- console.log('服务已加载,执行定位');
- if (flyTo) {
- const type = service.type || (service.layers && service.layers[0] ? service.layers[0].type : null);
- if (type === 'SCENE' || type === 'S3M') {
- const layerNames = service.loadedLayerNames || [service.name];
- let foundLayer = null;
- for (const name of layerNames) {
- foundLayer = viewer.scene.layers.find(name);
- if (foundLayer) break;
- }
- if (foundLayer) {
- console.log('跳转到图层:', foundLayer.name);
- viewer.flyTo(foundLayer, { duration: 2 });
- } else if (viewer.scene.layers.layerQueue && viewer.scene.layers.layerQueue.length > 0) {
- viewer.flyTo(viewer.scene.layers.layerQueue[0], { duration: 2 });
- }
- } else {
- const layerName = service.name;
- camera.flyByLayerName(type, layerName);
- }
- }
- return;
- }
- try {
- const type = service.type || (service.layers && service.layers[0] ? service.layers[0].type : null);
- const url = service.url;
- const layerName = service.name;
- const token = service.token;
- const tokenRequired = service.tokenRequired;
- const serviceId = service.id || service.serviceId;
- const markAsLoaded = () => {
- if (!this.loadedCustomServiceIds.includes(serviceId)) {
- this.loadedCustomServiceIds.push(serviceId);
- }
- };
- switch (type) {
- case 'SCENE':
- let sceneOptions = {
- autoSetView: flyTo
- };
- if (tokenRequired && token) {
- sceneOptions.SceneToken = token;
- }
- const beforeLayersCount = viewer.scene.layers ? viewer.scene.layers.layerQueue.length : 0;
- console.log('加载前图层数量:', beforeLayersCount);
- console.log('是否自动跳转视角:', flyTo);
- layerManagement.addScene(url, sceneOptions, (layers) => {
- console.log('SCENE加载成功:', layers);
- markAsLoaded();
- const afterLayers = viewer.scene.layers ? viewer.scene.layers.layerQueue : [];
- const newLayers = afterLayers.slice(beforeLayersCount);
- console.log('新添加的图层:', newLayers);
- const newLayerNames = newLayers.map(l => l.name);
- console.log('新图层名称:', newLayerNames);
- service.loadedLayerNames = newLayerNames;
- const currentPosition = viewer.camera.positionCartographic;
- service.defaultCameraPosition = {
- longitude: currentPosition.longitude,
- latitude: currentPosition.latitude,
- height: currentPosition.height,
- heading: viewer.camera.heading,
- pitch: viewer.camera.pitch,
- roll: viewer.camera.roll
- };
- console.log('记录默认视口位置:', service.defaultCameraPosition);
- if (flyTo && newLayers.length > 0) {
- camera.flyByLayerName('SCENE', newLayers[0].name);
- }
- });
- break;
- case 'S3M':
- let scps = [{ url: url, options: { name: layerName } }];
- layerManagement.addS3mLayers(scps, (layers) => {
- console.log('S3M加载成功:', layers);
- markAsLoaded();
- if (flyTo) {
- camera.flyByLayerName('S3M', layerName);
- }
- });
- break;
- case 'IMG':
- case 'IMAGE':
- let imgLayer = layerManagement.addImageLayer(url, layerName);
- console.log('IMG/IMAGE加载成功:', imgLayer);
- markAsLoaded();
- if (flyTo) {
- camera.flyByLayerName('IMG', layerName);
- }
- break;
- case 'TERRAIN':
- let terrainProvider = layerManagement.addTerrainLayer(url, false);
- console.log('TERRAIN加载成功:', terrainProvider);
- markAsLoaded();
- break;
- case 'MVT':
- layerManagement.addMvtLayer(url, layerName, (mvtlayer) => {
- console.log('MVT加载成功:', mvtlayer);
- markAsLoaded();
- if (flyTo) {
- viewer.flyTo(mvtlayer, { duration: 2 });
- }
- // 添加MVT图层点击事件处理,传入当前图层的瓦片URL
- this.addMvtClickHandler(url);
- });
- break;
- case 'SHP':
- case 'GEOJSON':
- case 'VECTOR_TILE':
- layerManagement.addGeoJsonLayer(url, layerName, (geoJsonLayer) => {
- console.log('SHP/GEOJSON/VECTOR_TILE加载成功:', geoJsonLayer);
- markAsLoaded();
- if (flyTo) {
- viewer.flyTo(geoJsonLayer, { duration: 2 });
- }
- });
- break;
- default:
- ElMessage.warning('不支持的服务类型: ' + type);
- return;
- }
- if (shouldSave) {
- ElMessage.success(`服务 "${service.name}" 加载成功`);
- }
- console.log('========== 自定义服务加载完成 ==========');
-
- if (shouldSave) {
- this.saveLoadedServices();
- }
- } catch (error) {
- console.error('加载自定义服务失败:', error);
- ElMessage.error('加载服务失败: ' + (error.message || '未知错误'));
- }
- },
- unloadCustomService(service) {
- console.log('开始卸载服务:', service);
- if (!service) {
- console.warn('服务对象为空');
- return;
- }
-
- const serviceId = service.id || service.serviceId;
- const serviceType = service.type || 'SCENE';
-
- const finishUnload = () => {
- const idx = this.loadedCustomServiceIds.indexOf(serviceId);
- if (idx > -1) {
- this.loadedCustomServiceIds.splice(idx, 1);
- console.log('已从loadedCustomServiceIds中移除服务:', serviceId);
- }
- window.store.actions.setChangeLayers();
- this.saveLoadedServices();
- this.$forceUpdate();
- };
-
- if (serviceType === 'SCENE' && service.loadedLayerNames && service.loadedLayerNames.length > 0) {
- console.log('使用loadedLayerNames卸载SCENE图层:', service.loadedLayerNames);
- let unloadedCount = 0;
- service.loadedLayerNames.forEach((layerName, index) => {
- console.log('卸载图层:', layerName);
- layerManagement.layersDelete('S3M', layerName, () => {
- console.log('图层已卸载:', layerName);
- unloadedCount++;
- if (unloadedCount === service.loadedLayerNames.length) {
- finishUnload();
- }
- });
- });
- } else if (service.layers && service.layers.length > 0) {
- let unloadedCount = 0;
- service.layers.forEach((layer, index) => {
- console.log('卸载图层:', layer.type, layer.layerName);
- layerManagement.layersDelete(layer.type, layer.layerName, () => {
- console.log('图层已卸载:', layer.layerName);
- unloadedCount++;
- if (unloadedCount === service.layers.length) {
- finishUnload();
- }
- });
- });
- } else {
- const type = service.type || 'SCENE';
- const layerName = service.name;
- console.log('卸载参数 - serviceId:', serviceId, 'type:', type, 'layerName:', layerName);
- layerManagement.layersDelete(type, layerName, () => {
- console.log('服务已卸载:', service.name);
- finishUnload();
- });
- }
- },
- // 控制自定义服务组件显隐
- addCustomService() {
- if (this.addService === "") {
- this.addService = "Sm3dCustomService";
- // 重置面板位置到默认值,使用setTimeout确保组件完全渲染
- setTimeout(() => {
- const panel = document.getElementById('CustomService-panel');
- if (panel) {
- panel.style.left = '35%';
- panel.style.top = '25%';
- }
- }, 100);
- } else {
- this.addService = "";
- }
- // 移除自定义服务菜单项的选中状态
- this.$nextTick(() => {
- const menu = this.$refs.menu;
- if (menu) {
- menu.activeIndex = "";
- }
- });
- },
- // 自定义服务添加回调函数
- addCallback(layers, add_type, serviceInfo) {
- console.log('接收到addCallback调用:', layers, add_type, serviceInfo);
-
- if (!Array.isArray(layers)) {
- layers = [layers];
- }
- let serviceOption = [];
- let serviceUrl = serviceInfo ? serviceInfo.url : "";
- let serviceToken = serviceInfo ? serviceInfo.token : "";
- let isAddToken = serviceInfo ? serviceInfo.tokenRequired : false;
-
- try {
- for (let i = 0; i < layers.length; i++) {
- if (!layers[i]) {
- console.log('layers[' + i + '] 是 undefined 或 null,跳过');
- continue;
- }
- if (layers[i].hasOwnProperty("_isS3MB")) {
- let option = {
- type: "S3M",
- layerName: layers[i].name
- ? layers[i].name
- : "s3m_" + new Date().getTime().toFixed(6)
- };
- serviceOption.push(option);
- if (!layers[i].maxVisibleAltitude)
- layers[i].maxVisibleAltitude = 8000;
- } else if (viewer.imageryLayers.contains(layers[i])) {
- let option = {
- type: "IMG",
- layerName: layers[i].imageryProvider.tablename
- ? layers[i].imageryProvider.tablename
- : "image" + new Date().getTime().toFixed(6)
- };
- serviceOption.push(option);
- } else if (
- layers[i].imageryLayer &&
- layers[i].imageryLayer._imageryProvider instanceof
- Cesium.MvtProviderGL
- ) {
- let option = {
- type: "MVT",
- layerName: layers[i].name
- ? layers[i].name
- : "mvt" + new Date().getTime().toFixed(6)
- };
- serviceOption.push(option);
- let index = this.addCustomServices.length;
- let obj = {
- name: layers[i].name ? layers[i].name : "自定义服务" + index,
- layers: serviceOption
- };
- this.addCustomServices.push(obj);
- this.addService = "";
-
- // 存储服务到数据库
- console.log('准备调用saveServiceToDatabase (MVT):', serviceInfo, add_type, obj.name);
- if (serviceInfo && serviceUrl) {
- console.log('调用saveServiceToDatabase (MVT)');
- this.saveServiceToDatabase(serviceInfo, add_type, obj.name);
- } else {
- console.log('serviceInfo或serviceUrl为空 (MVT):', serviceInfo, serviceUrl);
- }
-
- return;
- } else {
- let option = {
- type: "TERRAIN",
- layerName: layers[i].tablename ? layers[i].tablename : "terrain"
- };
- serviceOption.push(option);
- }
- }
- } catch (error) {
- console.error('处理layers时发生错误:', error);
- }
-
- if (this.varName) this.getNames.push(this.varName);
- let index = this.addCustomServices.length;
- let len = this.getNames.length - 1;
- let obj = {
- name: this.varName ? this.varName : "自定义服务" + index,
- layers: serviceOption
- };
- this.addCustomServices.push(obj);
- this.addService = "";
- this.varName = undefined;
-
- // 存储服务到数据库
- // SHP上传后端已自动保存,跳过前端重复保存
- const isShpUpload = serviceUrl && serviceUrl.startsWith('/profile/geojson/');
- if (!isShpUpload) {
- console.log('准备调用saveServiceToDatabase (非MVT):', serviceInfo, add_type, obj.name);
- if (serviceInfo && serviceUrl) {
- console.log('调用saveServiceToDatabase (非MVT)');
- this.saveServiceToDatabase(serviceInfo, add_type, obj.name);
- } else {
- console.log('serviceInfo或serviceUrl为空 (非MVT):', serviceInfo, serviceUrl);
- }
- } else {
- console.log('SHP上传后端已自动保存,直接标记为已加载');
- this.addService = "";
- this.varName = undefined;
- // 使用上传响应中返回的id更新服务对象并标记为已加载
- const serviceId = serviceInfo && serviceInfo.id;
- if (serviceId) {
- // 更新已在列表中的obj,使isServiceLoaded能匹配
- obj.id = serviceId;
- obj.url = serviceUrl;
- obj.type = add_type || 'GEOJSON';
- obj.layers = [{ type: 'GEOJSON', layerName: serviceInfo.layerName || obj.name }];
- if (!this.loadedCustomServiceIds.includes(serviceId)) {
- this.loadedCustomServiceIds.push(serviceId);
- }
- }
- return;
- }
-
- if ((add_type && add_type === "SCENE") || add_type === "TERRAIN") return;
- camera.flyByLayerName(add_type, obj.layers[0].layerName); //定位
- },
-
- // 保存服务到数据库
- saveServiceToDatabase(serviceInfo, serviceType, serviceName) {
- console.log('开始保存服务到数据库:', serviceInfo, serviceType, serviceName);
-
- const serviceData = {
- name: serviceName,
- type: serviceType,
- url: serviceInfo.url,
- tokenRequired: serviceInfo.tokenRequired ? 1 : 0,
- token: serviceInfo.token,
- status: 'NORMAL'
- };
-
- console.log('准备保存服务:', serviceData);
-
- serviceApi.addService(serviceData)
- .then(response => {
- console.log('服务保存成功:', response);
- const newServiceId = response.data || response.serviceId;
- if (newServiceId) {
- const service = this.addCustomServices.find(s => s.name === serviceName);
- if (service) {
- service.id = newServiceId;
- service.url = serviceInfo.url;
- service.token = serviceInfo.token;
- service.tokenRequired = serviceInfo.tokenRequired;
- service.type = serviceType;
- }
- if (!this.loadedCustomServiceIds.includes(newServiceId)) {
- this.loadedCustomServiceIds.push(newServiceId);
- }
- }
- })
- .catch(error => {
- console.error('服务保存失败:', error);
- if (error.response) {
- console.error('错误响应状态:', error.response.status);
- console.error('错误响应数据:', error.response.data);
- } else if (error.request) {
- console.error('错误请求:', error.request);
- } else {
- console.error('错误信息:', error.message);
- }
- });
- },
- // 自定义服务获取名称回调
- getName(name) {
- this.varName = name;
- },
- // 添加服务飞行函数(公共服务配置好的定位)
- flyTo(webName, obj) {
- let cameraParam = obj[webName];
- if (cameraParam) {
- let destination;
- // 支持两种格式:经纬度格式和笛卡尔坐标格式
- if (cameraParam.longitude !== undefined && cameraParam.latitude !== undefined) {
- destination = Cesium.Cartesian3.fromDegrees(
- cameraParam.longitude,
- cameraParam.latitude,
- cameraParam.height || 50000
- );
- } else if (cameraParam.Cartesian3) {
- destination = new Cesium.Cartesian3(
- cameraParam.Cartesian3.x,
- cameraParam.Cartesian3.y,
- cameraParam.Cartesian3.z
- );
- }
-
- viewer.scene.camera.flyTo({
- destination: destination,
- orientation: {
- heading: cameraParam.heading,
- pitch: cameraParam.pitch,
- roll: cameraParam.roll
- },
- duration: 2
- });
- return;
- } else {
- }
- },
- //图层管理右键删除回调
- deleteCallback(node_rightClick) {
- this.server_config[0].children.forEach(sceneObj => {
- sceneObj.layers.forEach(layerObj => {
- if (layerObj.layerName === node_rightClick.label) {
- sceneObj.state = 0;
- return;
- }
- });
- });
- },
- // 保存已加载的服务到数据库 (带防抖)
- saveLoadedServices() {
- if (this.isRestoring) {
- console.log('正在恢复配置,跳过保存');
- return;
- }
-
- if (this.saveConfigTimer) {
- clearTimeout(this.saveConfigTimer);
- }
-
- this.saveConfigTimer = setTimeout(async () => {
- const now = Date.now();
- if (now - this.lastSaveTime < 2000) {
- console.log('距离上次保存不足2秒,跳过保存');
- return;
- }
-
- try {
- const loadedServices = {
- baseLayer: this.baseLayerObj ? {
- type: this.baseLayerObj.type,
- name: this.baseLayerObj.name,
- proxiedUrl: this.baseLayerObj.proxiedUrl
- } : null,
- terrainLayer: this.terrainLayerObj ? {
- type: this.terrainLayerObj.type,
- name: this.terrainLayerObj.name,
- proxiedUrl: this.terrainLayerObj.proxiedUrl
- } : null,
- webServices: this.server_config[0].children
- .filter(service => service.state === 1)
- .map(service => ({
- type: service.type,
- name: service.name,
- proxiedUrl: service.proxiedUrl,
- layers: service.layers,
- style: service.style
- }))
- };
-
- const loadedModels = this.loadedModelEntities.map(entity => {
- const model = this.modelData.find(m => m.id === entity.name);
- return model ? {
- id: model.id,
- name: model.name,
- type: model.type,
- filePath: model.filePath,
- coordinates: model.coordinates,
- format: model.format,
- rotationX: model.rotationX,
- rotationY: model.rotationY,
- rotationZ: model.rotationZ,
- scaleX: model.scaleX,
- scaleY: model.scaleY,
- scaleZ: model.scaleZ
- } : null;
- }).filter(m => m !== null);
- const loadedCustomServices = this.addCustomServices
- .filter(service => this.loadedCustomServiceIds.includes(service.id || service.serviceId))
- .map(service => ({
- id: service.id || service.serviceId,
- name: service.name,
- type: service.type,
- url: service.url,
- tokenRequired: service.tokenRequired,
- token: service.token,
- loadedLayerNames: service.loadedLayerNames || []
- }));
- const configData = {
- configName: '默认配置',
- baseLayerType: loadedServices.baseLayer ? loadedServices.baseLayer.type : '',
- baseLayerName: loadedServices.baseLayer ? loadedServices.baseLayer.name : '',
- baseLayerUrl: loadedServices.baseLayer ? loadedServices.baseLayer.proxiedUrl : '',
- terrainLayerType: loadedServices.terrainLayer ? loadedServices.terrainLayer.type : '',
- terrainLayerName: loadedServices.terrainLayer ? loadedServices.terrainLayer.name : '',
- terrainLayerUrl: loadedServices.terrainLayer ? loadedServices.terrainLayer.proxiedUrl : '',
- webServices: JSON.stringify(loadedServices.webServices),
- loadedModels: JSON.stringify(loadedModels),
- loadedCustomServices: JSON.stringify(loadedCustomServices)
- };
- console.log('========== 保存地图配置 ==========');
- await saveMapConfig(configData);
- this.lastSaveTime = Date.now();
- console.log('地图配置已保存到数据库');
- } catch (error) {
- console.error('保存地图配置失败:', error);
- console.error('错误详情:', error.message);
- if (error.response) {
- console.error('响应状态:', error.response.status);
- console.error('响应数据:', error.response.data);
- }
- }
- }, 500);
- },
- // 从数据库恢复已加载的服务和模型
- async restoreLoadedServices() {
- try {
- console.log('========== 开始恢复地图配置 ==========');
- const response = await getDefaultMapConfig();
- console.log('API响应:', response);
-
- if (!response || !response.data) {
- console.log('没有找到保存的地图配置,使用默认配置');
- return;
- }
- const config = response.data;
- console.log('保存的配置:', config);
-
- let loadedServices = null;
- let loadedModels = null;
- // 保存需要恢复的自定义服务信息(不立即设置loadedCustomServiceIds,避免跳过加载步骤)
- let customServicesToLoad = [];
- if (config.loadedCustomServices) {
- try {
- const savedCustomServices = JSON.parse(config.loadedCustomServices);
- console.log('解析的自定义服务配置:', savedCustomServices);
- if (savedCustomServices && savedCustomServices.length > 0) {
- customServicesToLoad = savedCustomServices.map(s => ({
- id: s.id,
- loadedLayerNames: s.loadedLayerNames || []
- }));
- console.log('需要恢复的自定义服务:', customServicesToLoad);
- }
- } catch (e) {
- console.error('解析自定义服务配置失败:', e);
- }
- }
- // 解析服务配置
- if (config.webServices) {
- try {
- loadedServices = JSON.parse(config.webServices);
- console.log('解析的服务配置:', loadedServices);
- } catch (e) {
- console.error('解析服务配置失败:', e);
- }
- }
- // 解析模型配置
- if (config.loadedModels) {
- try {
- loadedModels = JSON.parse(config.loadedModels);
- console.log('解析的模型配置:', loadedModels);
- } catch (e) {
- console.error('解析模型配置失败:', e);
- }
- }
- // 恢复底图 (server_config[2] 是在线底图)
- if (config.baseLayerType) {
- const baseLayer = this.server_config[2].children.find(
- layer => layer.type === config.baseLayerType
- );
- if (baseLayer) {
- console.log('恢复底图:', baseLayer.name);
- await new Promise(resolve => {
- this.addBaseLayer(baseLayer);
- setTimeout(resolve, 500);
- });
- }
- }
- // 恢复地形 (server_config[3] 是在线地形)
- if (config.terrainLayerType) {
- const terrainLayer = this.server_config[3].children.find(
- layer => layer.type === config.terrainLayerType
- );
- if (terrainLayer) {
- console.log('恢复地形:', terrainLayer.name);
- await new Promise(resolve => {
- this.addOnlineTerrain(terrainLayer);
- setTimeout(resolve, 500);
- });
- }
- }
- // 恢复公共服务 (server_config[1] 是公共服务)
- if (loadedServices && loadedServices.length > 0) {
- for (const webService of loadedServices) {
- const service = this.server_config[1].children.find(
- s => s.name === webService.name
- );
- if (service) {
- console.log('恢复公共服务:', service.name);
- await new Promise(resolve => {
- service.state = 0;
- this.addWebServe(service, false);
- setTimeout(resolve, 500);
- });
- }
- }
- }
- // 恢复模型 —— 默认初始化时不加载模型,由用户手动加载
- // if (loadedModels && loadedModels.length > 0) {
- // for (const modelData of loadedModels) {
- // const model = this.modelData.find(m => m.id === modelData.id);
- // if (model && !this.isModelLoaded(model.id)) {
- // await new Promise(resolve => {
- // this.loadModel(model, false);
- // setTimeout(resolve, 1000);
- // });
- // }
- // }
- // }
- // 恢复自定义服务 (初始化时不跳转视角,不保存)
- if (customServicesToLoad && customServicesToLoad.length > 0) {
- for (const serviceInfo of customServicesToLoad) {
- const customService = this.addCustomServices.find(
- s => (s.id || s.serviceId) === serviceInfo.id
- );
- if (customService && !this.isServiceLoaded(customService)) {
- // 恢复 loadedLayerNames
- if (serviceInfo.loadedLayerNames && serviceInfo.loadedLayerNames.length > 0) {
- customService.loadedLayerNames = serviceInfo.loadedLayerNames;
- console.log('恢复服务loadedLayerNames:', customService.name, serviceInfo.loadedLayerNames);
- }
- console.log('恢复自定义服务:', customService.name);
- await new Promise(resolve => {
- this.loadCustomService(customService, false, false);
- setTimeout(resolve, 1000);
- });
- }
- }
- }
- console.log('地图配置恢复完成');
- } catch (error) {
- console.error('恢复地图配置失败:', error);
- console.error('错误详情:', error.message);
- console.error('错误堆栈:', error.stack);
- if (error.response) {
- console.error('响应状态:', error.response.status);
- console.error('响应数据:', error.response.data);
- }
- }
- },
- // 清除所有持久化数据
- clearPersistence() {
- localStorage.removeItem('cesium_loaded_services');
- localStorage.removeItem('cesium_loaded_models');
- console.log('持久化数据已清除');
- },
-
- // 为模型添加点击事件,激活编辑器
- addModelClickHandler(entity) {
- if (!entity || !window.viewer) return;
-
- const handler = new Cesium.ScreenSpaceEventHandler(window.viewer.canvas);
-
- handler.setInputAction((movement) => {
- const picked = window.viewer.scene.pick(movement.position);
-
- console.log('pick结果:', picked);
- console.log('picked.id:', picked ? picked.id : null);
- console.log('picked.id === entity:', picked ? (picked.id === entity) : false);
-
- if (Cesium.defined(picked) && picked.id === entity) {
- console.log('点击了模型:', entity);
- console.log('entity.model:', entity.model);
- console.log('entity类型:', entity.constructor.name);
-
- // 先恢复上一个模型的样式
- if (this.lastHighlightedModel && this.lastHighlightedModel !== entity) {
- this.restoreModelStyle(this.lastHighlightedModel);
- }
-
- // 高亮显示模型
- this.highlightModel(entity);
- this.lastHighlightedModel = entity;
-
- // 激活编辑器 - 已禁用,不再显示模型控制器
- // this.activateModelEditor(entity);
-
- // 标记已选中模型
- this._isModelSelected = true;
- }
- }, Cesium.ScreenSpaceEventType.LEFT_CLICK);
-
- // 保存 handler,以便后续清理
- if (!entity._clickHandler) {
- entity._clickHandler = handler;
- }
- },
-
- // 全局点击处理器 - 检测空白区域点击取消选中
- initGlobalClickHandler() {
- if (!window.viewer || this._globalClickHandlerInitialized) return;
-
- const handler = new Cesium.ScreenSpaceEventHandler(window.viewer.canvas);
-
- handler.setInputAction(async (movement) => {
- const picked = window.viewer.scene.pick(movement.position);
-
- console.log('全局点击 - picked:', picked);
-
- // ========== MVT 矢量瓦片点击检测 ==========
- // MVT 无法被 scene.pick() 拾取,只在未拾取到任何实体时才尝试 MVT 查询,
- // 避免与 GeoJSON 要素、模型等其他实体的点击事件冲突
- if (this._hasMvtLayer && this.mvtClickHandler && !Cesium.defined(picked)) {
- // 获取点击位置经纬度
- const ray = window.viewer.camera.getPickRay(movement.position);
- const cartesian = window.viewer.scene.globe.pick(ray, window.viewer.scene);
-
- if (cartesian) {
- const cartographic = Cesium.Cartographic.fromCartesian(cartesian);
- const lon = Cesium.Math.toDegrees(cartographic.longitude);
- const lat = Cesium.Math.toDegrees(cartographic.latitude);
-
- // 计算当前相机 zoom:画面能渲染说明该级别有瓦片,优先查该级别命中最快
- // (兼容只在特定比例尺发布的服务,如水资源分区仅 z=6/7 有瓦片)
- const camHeight = window.viewer.camera.positionCartographic.height;
- const currentZoom = Math.round(Math.log2((2 * Math.PI * 6378137) / camHeight));
- // 遍历所有已加载的 MVT 图层进行查询
- await this.mvtClickHandler.handleClick(lon, lat, {
- x: movement.position.x,
- y: movement.position.y
- }, this._mvtTileUrls || [], currentZoom);
- }
- }
-
- // 如果点击的是GeoJSON点,不处理(让model-transform-panel.vue处理)
- if (picked && picked.id && picked.id.isGeoJsonPoint) {
- console.log('点击了GeoJSON点,不处理');
- return;
- }
-
- // 检查是否点击了空白区域(不是模型)
- // 如果没有 pick 到任何东西,或者 pick 到的东西不是当前选中的模型
- let isClickingOnSelectedModel = false;
-
- if (picked && picked.id && this.lastHighlightedModel) {
- // 检查是否点击的是当前选中的模型
- if (picked.id === this.lastHighlightedModel ||
- (typeof picked.id === 'string' && picked.id === this.lastHighlightedModel._id)) {
- isClickingOnSelectedModel = true;
- }
- }
-
- // 如果点击的不是当前选中的模型,且有选中的模型,则取消选中
- if (!isClickingOnSelectedModel && this.lastHighlightedModel) {
- console.log('取消选中模型');
- this.restoreModelStyle(this.lastHighlightedModel);
-
- // 销毁编辑器
- if (this.currentModelEditor) {
- this.currentModelEditor.destroy();
- this.currentModelEditor = null;
- }
-
- this.lastHighlightedModel = null;
- this._isModelSelected = false;
- }
- }, Cesium.ScreenSpaceEventType.LEFT_CLICK);
-
- this._globalClickHandlerInitialized = true;
- console.log('全局点击处理器已初始化');
- },
-
- // 恢复模型样式
- restoreModelStyle(entity) {
- if (!entity) return;
-
- if (entity._originalShow !== undefined) {
- entity.show = entity._originalShow;
- }
- if (entity.model) {
- entity.model.color = entity._originalColor;
- entity.model.colorBlendMode = entity._originalColorBlendMode;
- entity.model.colorBlendAmount = entity._originalColorBlendAmount;
- }
- },
-
- // 高亮显示模型
- highlightModel(entity) {
- if (!entity) return;
-
- // 保存原始状态
- if (!entity._originalShow) {
- entity._originalShow = entity.show;
- }
- if (!entity._originalColor) {
- entity._originalColor = entity.model ? entity.model.color : undefined;
- }
- if (!entity._originalColorBlendMode) {
- entity._originalColorBlendMode = entity.model ? entity.model.colorBlendMode : undefined;
- }
- if (!entity._originalColorBlendAmount) {
- entity._originalColorBlendAmount = entity.model ? entity.model.colorBlendAmount : undefined;
- }
-
- // 高亮显示模型 - 使用透明模式
- if (entity.model) {
- console.log('模型存在,设置透明');
- // 尝试直接设置颜色
- entity.model.color = Cesium.Color.YELLOW;
- } else {
- console.log('模型不存在,实体结构:', entity);
- }
- },
-
- // 激活模型编辑器
- activateModelEditor(entity) {
- if (!entity || !window.viewer) return;
-
- // 清除之前的编辑器
- if (this.currentModelEditor) {
- if (this.currentModelEditor.destroy) {
- this.currentModelEditor.destroy();
- }
- this.currentModelEditor = null;
- }
-
- // 创建新的编辑器
- try {
- console.log('创建 ModelEditor for entity:', entity);
- console.log('window.viewer:', window.viewer);
- console.log('window.viewer.primitives:', window.viewer ? window.viewer.primitives : undefined);
- this.currentModelEditor = new ModelEditor(window.viewer, entity);
-
- // 等待模型加载完成
- if (this.currentModelEditor.waitForModelLoad) {
- this.currentModelEditor.waitForModelLoad().then(() => {
- console.log('模型加载完成,编辑器初始化成功');
- }).catch((error) => {
- console.error('模型加载或编辑器初始化失败:', error);
- });
- }
-
- console.log('ModelEditor 创建成功');
- } catch (error) {
- console.error('创建 ModelEditor 失败:', error);
- this.currentModelEditor = null;
- }
- }
- },
-
- watch: {
- isCollapse(val) {
- if (val) {
- this.asideWidth = "asideWidth2";
- } else {
- this.asideWidth = "asideWidth1";
- }
- }
- },
- mounted() {
- document.getElementById("el-aside").classList.add("disable"); //禁止点击
- // 直接初始化 viewer,不使用 window.onload
- this.$nextTick(() => {
- this.initViewer = "Sm3dViewer";
- // 获取模型数据
- this.fetchModels();
- // 从数据库加载自定义服务
- this.fetchCustomServices();
- // 从数据库加载业务场景列表
- this.refreshBusinessScenes();
- // 加载固定场景列表
- this.loadFixedScenes();
- });
-
- // 延迟初始化全局点击处理器,等待 viewer 准备好
- setTimeout(() => {
- console.log('延迟初始化全局点击处理器');
- if (window.viewer) {
- this.viewer = window.viewer;
- this.initGlobalClickHandler();
- }
- }, 3000);
- // 监听创建场景成功事件,刷新业务场景列表
- this.refreshScenesHandler = () => {
- this.refreshBusinessScenes()
- }
- document.addEventListener('refreshBusinessScenes', this.refreshScenesHandler)
- },
- beforeUnmount() {
- // 移除事件监听
- document.removeEventListener('refreshBusinessScenes', this.refreshScenesHandler)
- }
- };
- </script>
- <style lang="scss" scoped>
- .container {
- position: relative;
- z-index: 1;
- width: 100%;
- height: 100%;
- pointer-events: auto;
- }
- /* 侧边栏宽度控制 */
- .asideWidth1 {
- width: 280px !important;
- transition-duration: 0.3s;
- height: 100% !important;
- }
- .asideWidth2 {
- transition-duration: 0.5s;
- width: 40px !important;
- height: 100% !important;
- display: flex !important;
- justify-content: center !important;
- }
- /* 确保el-container能够铺满整个容器 */
- .el-container {
- height: 100% !important;
- width: 100% !important;
- pointer-events: auto;
- }
- /* 确保el-aside能够正确接收点击事件 */
- .el-aside {
- position: relative;
- z-index: 1000;
- pointer-events: auto;
- }
- /* 确保el-main能够铺满整个容器 */
- .el-main {
- position: relative;
- z-index: 1;
- height: 100% !important;
- width: 100% !important;
- padding: 0 !important;
- margin: 0 !important;
- pointer-events: auto;
- }
- /* 确保cesiumContainer能够铺满整个el-main */
- #cesiumContainer {
- position: relative;
- z-index: 10;
- height: 100% !important;
- width: 100% !important;
- pointer-events: auto;
- }
- /* 确保cesium-viewer能够铺满整个cesiumContainer */
- .cesium-viewer {
- height: 100% !important;
- width: 100% !important;
- pointer-events: auto;
- }
- /* 确保cesium-widget能够铺满整个cesium-viewer */
- .cesium-widget {
- height: 100% !important;
- width: 100% !important;
- pointer-events: auto;
- }
- /* 确保canvas能够铺满整个cesium-widget */
- .cesium-widget canvas {
- position: relative;
- z-index: 5;
- height: 100% !important;
- width: 100% !important;
- pointer-events: auto;
- }
- /* 确保侧边栏菜单能够正确接收点击事件 */
- .el-menu-vertical-demo {
- position: relative;
- z-index: 1001;
- pointer-events: auto;
- }
- /* 确保加载动画不影响点击事件 */
- #loadingbar {
- position: absolute;
- top: 50%;
- left: 50%;
- transform: translate(-50%, -50%);
- z-index: 9999;
- pointer-events: none;
- }
- /* 侧边栏样式增强 */
- .el-aside {
- background-color: #f8f9fa;
- overflow-y: scroll;
- overflow-x: hidden;
- scrollbar-width: none;
- /* firefox */
- -ms-overflow-style: none;
- height: 100% !important;
- box-shadow: 2px 0 10px rgba(0, 0, 0, 0.05);
- .el-menu--collapse {
- width: 40px;
- height: 100% !important;
- }
-
- :deep(.el-menu--collapse.el-menu) {
- width: 40px !important;
- margin: 0 auto !important;
- }
-
- :deep(.el-menu--collapse .el-menu-item),
- :deep(.el-menu--collapse .el-submenu__title),
- :deep(.el-menu--collapse .el-sub-menu__title) {
- padding: 0 !important;
- text-align: center !important;
- display: flex !important;
- align-items: center !important;
- justify-content: center !important;
- position: relative !important;
- left: 0 !important;
- right: 0 !important;
- width: 40px !important;
- height: 45px !important;
- margin: 0 auto !important;
- /* 强制开启hover触发,解决折叠后无法识别子项问题 */
- pointer-events: auto !important;
- cursor: pointer !important;
- }
-
- :deep(.el-menu--collapse .el-sub-menu) {
- width: 40px !important;
- /* 二级弹框容器相对定位,让三级能基于它定位 */
- position: relative !important;
- }
-
- :deep(.el-menu--collapse .el-menu-item > *),
- :deep(.el-menu--collapse .el-submenu__title > *),
- :deep(.el-menu--collapse .el-sub-menu__title > *) {
- margin: 0 !important;
- padding: 0 !important;
- position: static !important;
- left: auto !important;
- right: auto !important;
- top: auto !important;
- bottom: auto !important;
- transform: none !important;
- float: none !important;
- display: inline-block !important;
- width: auto !important;
- height: auto !important;
- }
-
- :deep(.el-menu--collapse .el-menu-item i.rotate),
- :deep(.el-menu--collapse .el-submenu__title i.rotate),
- :deep(.el-menu--collapse .el-sub-menu__title i.rotate) {
- margin: 0 !important;
- padding: 0 !important;
- display: inline-block !important;
- text-align: center !important;
- width: auto !important;
- height: auto !important;
- position: static !important;
- left: auto !important;
- right: auto !important;
- transform: rotate(-90deg) !important;
- line-height: normal !important;
- float: none !important;
- }
-
- :deep(.el-menu--collapse .el-submenu__title .iconfont2),
- :deep(.el-menu--collapse .el-menu-item .iconfont2),
- :deep(.el-menu--collapse .el-sub-menu__title .iconfont2) {
- margin: 0 !important;
- padding: 0 !important;
- display: inline-block !important;
- text-align: center !important;
- width: auto !important;
- height: auto !important;
- position: static !important;
- left: auto !important;
- right: auto !important;
- transform: none !important;
- float: none !important;
- }
-
- :deep(.el-menu--collapse .el-sub-menu__title .typhoon-icon) {
- margin: 0 !important;
- padding: 0 !important;
- display: inline-block !important;
- text-align: center !important;
- width: 20px !important;
- height: 20px !important;
- position: static !important;
- left: auto !important;
- right: auto !important;
- transform: none !important;
- float: none !important;
- }
-
- :deep(.el-menu--collapse .el-submenu__title span),
- :deep(.el-menu--collapse .el-menu-item span),
- :deep(.el-menu--collapse .el-sub-menu__title span) {
- display: none !important;
- }
-
- :deep(.el-menu--collapse .el-submenu__icon-arrow),
- :deep(.el-menu--collapse .el-sub-menu__icon-arrow) {
- display: none !important;
- }
- .el-menu {
- border-right: none;
- height: 100% !important;
- .el-menu-item,
- .el-submenu__title {
- height: 45px;
- line-height: 45px;
- }
-
- :deep(.el-menu--collapse .el-menu-item),
- :deep(.el-menu--collapse .el-submenu__title),
- :deep(.el-menu--collapse .el-sub-menu__title) {
- padding: 0 !important;
- text-align: center !important;
- display: flex !important;
- align-items: center !important;
- justify-content: center !important;
- position: relative !important;
- left: 0 !important;
- right: 0 !important;
- width: 40px !important;
- height: 45px !important;
- line-height: normal !important;
- }
- #fold {
- height: 35px;
- transition-duration: 1s;
- color: #303133 !important;
- padding: 0 !important;
- text-align: center !important;
- line-height: 35px !important;
- display: flex !important;
- align-items: center !important;
- justify-content: center !important;
- background-color: transparent !important;
- }
-
- #fold.is-active {
- background-color: transparent !important;
- color: #303133 !important;
- }
-
- #fold:hover {
- background-color: rgba(64, 158, 255, 0.1) !important;
- }
- }
- }
- /* 修复Element Plus 2.x 子菜单标题样式 */
- .el-submenu__title {
- padding: 0 12px !important;
- }
- .el-menu--vertical > .el-menu-item {
- padding: 0 12px !important;
- }
- /* 确保标题框尺寸正确 */
- .el-submenu__title .el-submenu__icon-arrow {
- margin-top: -4px !important;
- }
- /* 修复图标和文字对齐 */
- .iconfont2 {
- font-size: 20px !important;
- margin-right: 10px;
- vertical-align: middle;
- color: #303133;
- }
- /* 台风图标样式 */
- .typhoon-icon {
- width: 20px;
- height: 20px;
- margin-right: 10px;
- vertical-align: middle;
- }
- /* 子菜单项文字颜色 */
- .el-menu--vertical .el-sub-menu .el-menu-item {
- color: #303133 !important;
- font-size: 0.875rem !important;
- line-height: 1.5;
- }
- /* 本地服务菜单项文字向左移动 */
- .custom-service-item {
- display: flex;
- justify-content: space-between;
- align-items: center;
- padding-left: 50px !important;
- }
- .custom-service-item.is-active {
- background-color: transparent !important;
- }
- .custom-service-item .service-name {
- flex: 1;
- overflow: hidden;
- text-overflow: ellipsis;
- white-space: nowrap;
- }
- .custom-service-item .service-name.service-loaded {
- color: #409eff;
- font-weight: 500;
- }
- .custom-service-item .delete-service-icon {
- margin-left: 8px;
- cursor: pointer;
- color: #909399;
- transition: color 0.3s;
- }
- .custom-service-item .delete-service-icon:hover {
- color: #f56c6c;
- }
- /* 自定义服务菜单项移除选中样式 */
- .no-active-style {
- background-color: transparent !important;
- }
- .no-active-style.is-active {
- background-color: transparent !important;
- }
- /* 基础地理实体菜单项样式 */
- .vector-data-item {
- display: flex;
- justify-content: space-between;
- align-items: center;
- padding-left: 50px !important;
- }
- .vector-data-item.is-active {
- background-color: transparent !important;
- }
- .vector-data-item .vector-data-name {
- flex: 1;
- overflow: hidden;
- text-overflow: ellipsis;
- white-space: nowrap;
- }
- .vector-data-item .vector-data-name.vector-data-loaded {
- color: #409eff;
- font-weight: 500;
- }
- .vector-data-item .delete-vector-data-icon {
- margin-left: 8px;
- cursor: pointer;
- color: #909399;
- transition: color 0.3s;
- }
- .vector-data-item .delete-vector-data-icon:hover {
- color: #f56c6c;
- }
- /* 模型菜单项样式 */
- .model-menu-item {
- display: flex;
- justify-content: space-between;
- align-items: center;
- padding-left: 50px !important;
- }
- .model-menu-item.is-active {
- background-color: transparent !important;
- }
- .model-menu-item .model-name {
- flex: 1;
- overflow: hidden;
- text-overflow: ellipsis;
- white-space: nowrap;
- padding-left: 10px;
- }
- .model-menu-item .model-name.model-loaded {
- color: #409eff;
- font-weight: 500;
- }
- .model-menu-item .delete-model-icon {
- margin-left: 8px;
- cursor: pointer;
- color: #909399;
- transition: color 0.3s;
- }
- .model-menu-item .delete-model-icon:hover {
- color: #f56c6c;
- }
- /* 台风加载状态 */
- .el-menu-item .typhoon-name.typhoon-loaded {
- color: #409eff;
- font-weight: 500;
- }
- .el-menu-item .delete-typhoon-icon {
- margin-left: 8px;
- cursor: pointer;
- color: #909399;
- transition: color 0.3s;
- }
- /* 场景加载状态 */
- .model-menu-item .scene-name {
- flex: 1;
- overflow: hidden;
- text-overflow: ellipsis;
- white-space: nowrap;
- cursor: pointer;
- }
- .model-menu-item .scene-name.scene-loaded {
- color: #409eff;
- font-weight: 500;
- }
- .model-menu-item .delete-scene-icon {
- margin-left: 8px;
- cursor: pointer;
- color: #909399;
- transition: color 0.3s;
- }
- .model-menu-item .delete-scene-icon:hover {
- color: #f56c6c;
- }
- .el-menu-item .delete-typhoon-icon:hover {
- color: #f56c6c;
- }
- /* 滚动条隐藏 */
- .el-aside::-webkit-scrollbar {
- display: none;
- }
- /* 功能模块网格布局 */
- .box {
- font-family: "Microsoft Yahei";
- display: flex;
- width: 100%;
- padding: 0 10px 0 28px;
- justify-content: space-between;
- box-sizing: border-box;
- flex-wrap: wrap;
- .imgbox {
- position: relative;
- width: 100px;
- height: 100px;
- display: flex;
- box-sizing: border-box;
- margin-bottom: 5px;
- flex-wrap: wrap;
- justify-content: center;
- span {
- font-size: 12px;
- color: #303133;
- }
- .img {
- width: 100%;
- height: 72px;
- border-radius: 4px;
- background-color: #585858;
- border: 1px solid #e4e7ed;
- box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
- transition: all 0.3s ease;
- }
- .img:hover {
- border: 2px solid #409eff;
- box-shadow: 0 4px 8px rgba(64, 158, 255, 0.2);
- transform: translateY(-2px);
- }
- .cross {
- width: 25px;
- height: 25px;
- position: absolute;
- top: 0;
- left: 0;
- }
- }
- }
- /* 删除图标样式 */
- .circle-close-icon {
- position: absolute;
- right: 0px;
- top: 3px;
- color: #ffffff;
- cursor: pointer;
- width: 16px;
- height: 16px;
- font-size: 16px;
- outline: none;
- }
- .circle-close-icon:focus,
- .circle-close-icon:active {
- outline: none;
- box-shadow: none;
- }
- /* el-menu-item 内的删除图标样式 */
- .el-menu-item .circle-close-icon {
- top: 50%;
- transform: translateY(-50%);
- color: #000000;
- right: 25px;
- }
- /* 展开/收缩按钮动画 */
- .rotate2 {
- transform: rotate(0deg);
- -ms-transform: rotate(0); /* IE 9 */
- -moz-transform: rotate(0); /* Firefox */
- -webkit-transform: rotate(0); /* Safari 和 Chrome */
- -o-transform: rotate(0); /* Opera */
- transition-duration: 1s;
- }
- .rotate {
- margin-left: 8px !important;
- line-height: 52px !important;
- transform: rotate(-90deg);
- -ms-transform: rotate(-90deg); /* IE 9 */
- -moz-transform: rotate(-90deg); /* Firefox */
- -webkit-transform: rotate(-90deg); /* Safari 和 Chrome */
- -o-transform: rotate(-90deg); /* Opera */
- transition-duration: 1s;
- }
- /* 禁用状态 */
- .disable {
- pointer-events: none;
- }
- /* 图标样式 */
- .iconfont2 {
- font-size: 20px !important;
- margin-right: 10px;
- }
- /* 方形加号图标 */
- .square-plus-icon {
- display: inline-flex;
- align-items: center;
- justify-content: center;
- width: 24px;
- height: 24px;
- border: 1.5px solid #909399;
- border-radius: 4px;
- font-size: 16px;
- margin-left: 10px;
- }
- /* 场景名称 */
- .scene-name {
- flex: 1;
- overflow: hidden;
- text-overflow: ellipsis;
- white-space: nowrap;
- }
- /* 删除图标 */
- .delete-icon {
- display: inline-flex;
- align-items: center;
- justify-content: center;
- width: 24px;
- height: 24px;
- font-size: 16px;
- margin-left: 8px;
- cursor: pointer;
- opacity: 0.6;
- transition: opacity 0.2s;
- flex-shrink: 0;
- }
- .delete-icon:hover {
- opacity: 1;
- color: #f56c6c;
- }
- // ########### 关键修复:折叠状态下 二级弹框保留原位置 + 三级强制弹出 ###########
- // 1. 折叠状态二级弹框:强制保留原有正确位置,不做任何改动
- :deep(.el-menu--collapse .el-sub-menu > .el-sub-menu__popper) {
- position: fixed !important;
- margin-left: 0 !important;
- transform: none !important;
- z-index: 9998 !important;
- }
- // 2. 折叠状态二级弹框容器:hover时强制显示三级内容,核心触发逻辑
- :deep(.el-menu--collapse .el-sub-menu__popper) {
- pointer-events: auto !important;
- &:hover .box {
- display: flex !important;
- }
- }
- // 3. 折叠状态三级内容(box):强制定位在二级弹框右侧,顶对齐
- :deep(.el-menu--collapse .el-sub-menu__popper .box) {
- position: absolute !important;
- left: 100% !important; // 紧贴二级弹框右侧
- top: 0 !important; // 和二级弹框顶对齐
- z-index: 9999 !important;
- width: 300px !important; // 适配你的imgbox布局
- background: #f8f9fa !important;
- border: 1px solid #e4e7ed !important;
- border-left: none !important;
- box-shadow: 2px 2px 10px rgba(0,0,0,0.05) !important;
- padding: 10px !important;
- margin: 0 !important;
- // 强制显示,解决折叠后被隐藏问题
- display: flex !important;
- pointer-events: auto !important;
- }
- // 4. 折叠状态三级imgbox布局适配:清除多余间距,正常显示
- :deep(.el-menu--collapse .el-sub-menu__popper .box .imgbox) {
- margin: 0 10px 10px 0 !important;
- }
- // 5. 展开状态:恢复原有布局,不影响任何效果
- :deep(.el-menu--not-collapse .el-sub-menu__popper .box) {
- position: static !important;
- width: 100% !important;
- box-shadow: none !important;
- border: none !important;
- }
- // 土地利用图例样式
- .land-use-legend {
- position: absolute;
- bottom: 20px;
- right: 20px;
- z-index: 1000;
- background: rgba(20, 25, 40, 0.85);
- border: 1px solid rgba(100, 180, 255, 0.4);
- border-radius: 6px;
- padding: 10px 12px;
- max-height: 60vh;
- overflow-y: auto;
- user-select: none;
- backdrop-filter: blur(4px);
- .land-use-legend-header {
- display: flex;
- justify-content: space-between;
- align-items: center;
- margin-bottom: 8px;
- font-size: 14px;
- font-weight: bold;
- color: #e0e0e0;
- border-bottom: 1px solid rgba(100, 180, 255, 0.2);
- padding-bottom: 6px;
- .land-use-legend-toggle {
- cursor: pointer;
- color: #aaa;
- font-size: 16px;
- line-height: 1;
- padding: 0 4px;
- &:hover { color: #fff; }
- }
- }
- .land-use-legend-body {
- display: flex;
- flex-direction: column;
- gap: 4px;
- }
- .land-use-legend-item {
- display: flex;
- align-items: center;
- gap: 8px;
- font-size: 12px;
- color: #ccc;
- }
- .land-use-legend-color {
- display: inline-block;
- width: 16px;
- height: 12px;
- border: 1px solid rgba(255, 255, 255, 0.3);
- border-radius: 2px;
- flex-shrink: 0;
- }
- .land-use-legend-label {
- white-space: nowrap;
- }
- }
- </style>
|