index.vue 104 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800180118021803180418051806180718081809181018111812181318141815181618171818181918201821182218231824182518261827182818291830183118321833183418351836183718381839184018411842184318441845184618471848184918501851185218531854185518561857185818591860186118621863186418651866186718681869187018711872187318741875187618771878187918801881188218831884188518861887188818891890189118921893189418951896189718981899190019011902190319041905190619071908190919101911191219131914191519161917191819191920192119221923192419251926192719281929193019311932193319341935193619371938193919401941194219431944194519461947194819491950195119521953195419551956195719581959196019611962196319641965196619671968196919701971197219731974197519761977197819791980198119821983198419851986198719881989199019911992199319941995199619971998199920002001200220032004200520062007200820092010201120122013201420152016201720182019202020212022202320242025202620272028202920302031203220332034203520362037203820392040204120422043204420452046204720482049205020512052205320542055205620572058205920602061206220632064206520662067206820692070207120722073207420752076207720782079208020812082208320842085208620872088208920902091209220932094209520962097209820992100210121022103210421052106210721082109211021112112211321142115211621172118211921202121212221232124212521262127212821292130213121322133213421352136213721382139214021412142214321442145214621472148214921502151215221532154215521562157215821592160216121622163216421652166216721682169217021712172217321742175217621772178217921802181218221832184218521862187218821892190219121922193219421952196219721982199220022012202220322042205220622072208220922102211221222132214221522162217221822192220222122222223222422252226222722282229223022312232223322342235223622372238223922402241224222432244224522462247224822492250225122522253225422552256225722582259226022612262226322642265226622672268226922702271227222732274227522762277227822792280228122822283228422852286228722882289229022912292229322942295229622972298229923002301230223032304230523062307230823092310231123122313231423152316231723182319232023212322232323242325232623272328232923302331233223332334233523362337233823392340234123422343234423452346234723482349235023512352235323542355235623572358235923602361236223632364236523662367236823692370237123722373237423752376237723782379238023812382238323842385238623872388238923902391239223932394239523962397239823992400240124022403240424052406240724082409241024112412241324142415241624172418241924202421242224232424242524262427242824292430243124322433243424352436243724382439244024412442244324442445244624472448244924502451245224532454245524562457245824592460246124622463246424652466246724682469247024712472247324742475247624772478247924802481248224832484248524862487248824892490249124922493249424952496249724982499250025012502250325042505250625072508250925102511251225132514251525162517251825192520252125222523252425252526252725282529253025312532253325342535253625372538253925402541254225432544254525462547254825492550255125522553255425552556255725582559256025612562256325642565256625672568256925702571257225732574257525762577257825792580258125822583258425852586258725882589259025912592259325942595259625972598259926002601260226032604260526062607260826092610261126122613261426152616261726182619262026212622262326242625262626272628262926302631263226332634263526362637263826392640264126422643264426452646264726482649265026512652265326542655265626572658265926602661266226632664266526662667266826692670267126722673267426752676267726782679268026812682268326842685268626872688268926902691269226932694269526962697269826992700270127022703270427052706270727082709271027112712271327142715271627172718271927202721272227232724272527262727272827292730273127322733273427352736273727382739274027412742274327442745274627472748274927502751275227532754275527562757275827592760276127622763276427652766276727682769277027712772277327742775277627772778277927802781278227832784278527862787278827892790279127922793279427952796279727982799280028012802280328042805280628072808280928102811281228132814281528162817281828192820282128222823282428252826282728282829283028312832283328342835283628372838283928402841284228432844284528462847284828492850285128522853285428552856285728582859286028612862286328642865286628672868286928702871287228732874287528762877287828792880288128822883288428852886288728882889289028912892289328942895289628972898289929002901290229032904290529062907290829092910291129122913291429152916291729182919292029212922292329242925292629272928292929302931293229332934293529362937293829392940294129422943294429452946294729482949295029512952295329542955295629572958295929602961296229632964296529662967296829692970297129722973297429752976297729782979298029812982298329842985298629872988298929902991299229932994299529962997299829993000300130023003300430053006300730083009301030113012301330143015301630173018301930203021302230233024302530263027302830293030303130323033303430353036303730383039304030413042304330443045304630473048304930503051305230533054305530563057305830593060306130623063306430653066306730683069307030713072307330743075307630773078307930803081308230833084308530863087308830893090309130923093309430953096309730983099310031013102310331043105310631073108310931103111311231133114311531163117311831193120312131223123312431253126312731283129313031313132313331343135313631373138313931403141314231433144314531463147314831493150315131523153315431553156315731583159316031613162316331643165316631673168316931703171317231733174317531763177317831793180318131823183318431853186318731883189319031913192319331943195319631973198319932003201320232033204320532063207320832093210321132123213321432153216321732183219322032213222322332243225322632273228322932303231323232333234323532363237323832393240324132423243324432453246324732483249325032513252325332543255325632573258325932603261326232633264326532663267326832693270327132723273327432753276327732783279328032813282328332843285328632873288328932903291329232933294
  1. <template>
  2. <div class="cockpit-dashboard" :class="{ 'fullscreen-mode': isFullscreenMode }">
  3. <!-- 顶部区域 -->
  4. <header class="dashboard-header">
  5. <div class="header-left">
  6. <div class="title-section">
  7. <h1 class="dashboard-title">智慧农业驾驶舱</h1>
  8. <p class="dashboard-subtitle">{{ getCurrentFarmDescription() }}</p>
  9. </div>
  10. </div>
  11. <div class="header-right">
  12. <div class="action-buttons">
  13. <div class="location-selector-group">
  14. <el-cascader
  15. v-model="selectedLocation"
  16. :options="locationOptions"
  17. :props="cascaderProps"
  18. size="small"
  19. style="width: 200px"
  20. placeholder="选择农场/地块"
  21. clearable>
  22. </el-cascader>
  23. <el-button
  24. icon="el-icon-check"
  25. type="primary"
  26. size="small"
  27. class="btn-confirm"
  28. @click="confirmLocationChange"
  29. :disabled="!hasLocationChanged">
  30. 确认
  31. </el-button>
  32. </div>
  33. <el-button
  34. icon="el-icon-refresh"
  35. type="success"
  36. size="small"
  37. class="btn-refresh"
  38. @click="refreshData">
  39. 刷新
  40. </el-button>
  41. <el-button
  42. icon="el-icon-full-screen"
  43. type="warning"
  44. size="small"
  45. class="btn-fullscreen"
  46. @click="openFullscreenCockpit">
  47. 全屏驾驶舱
  48. </el-button>
  49. </div>
  50. </div>
  51. </header>
  52. <!-- 主体区域 -->
  53. <div class="dashboard-body">
  54. <!-- 左侧面板 -->
  55. <div class="dashboard-left">
  56. <!-- 农机数量统计 -->
  57. <div class="panel-card">
  58. <div class="panel-header">
  59. <h3 class="panel-title">葡萄生长状况</h3>
  60. <el-radio-group v-model="cropTimeRange" size="mini">
  61. <el-radio-button label="week">本周</el-radio-button>
  62. <el-radio-button label="month">本月</el-radio-button>
  63. <el-radio-button label="season">本季</el-radio-button>
  64. </el-radio-group>
  65. </div>
  66. <div class="panel-content">
  67. <div class="chart-wrapper">
  68. <div id="cropStatusChart"></div>
  69. </div>
  70. <div class="chart-legend-mini">
  71. <div class="legend-item-mini" v-for="item in getCurrentCropLegend()" :key="item.name">
  72. <span class="legend-dot" :style="{ background: item.color }"></span>
  73. <span class="legend-text">{{ item.name }} ({{ item.value }}亩)</span>
  74. </div>
  75. </div>
  76. </div>
  77. </div>
  78. <!-- 设备运行状态 -->
  79. <div class="panel-card">
  80. <div class="panel-header">
  81. <h3 class="panel-title">设备运行状态</h3>
  82. <div class="device-summary-mini">
  83. <span>总: {{ deviceStats.total }}</span>
  84. <span class="online-text">在线: {{ deviceStats.online }}</span>
  85. </div>
  86. </div>
  87. <div class="panel-content">
  88. <div class="chart-wrapper">
  89. <div id="deviceStatusChart"></div>
  90. </div>
  91. <div class="device-stats-mini">
  92. <div class="stat-item">
  93. <span class="stat-label">在线率</span>
  94. <span class="stat-value success">{{ Math.round(deviceStats.online / deviceStats.total * 100) }}%</span>
  95. </div>
  96. <div class="stat-item">
  97. <span class="stat-label">离线</span>
  98. <span class="stat-value danger">{{ deviceStats.offline }}台</span>
  99. </div>
  100. </div>
  101. </div>
  102. </div>
  103. <!-- 环境监测 -->
  104. <div class="panel-card">
  105. <div class="panel-header">
  106. <h3 class="panel-title">环境监测</h3>
  107. </div>
  108. <div class="panel-content">
  109. <div class="env-grid-mini">
  110. <div class="env-item">
  111. <div class="env-name">温度</div>
  112. <div class="env-value-text">26.5°C</div>
  113. <div class="env-chart-mini" id="temperatureChart"></div>
  114. </div>
  115. <div class="env-item">
  116. <div class="env-name">湿度</div>
  117. <div class="env-value-text">68%</div>
  118. <div class="env-chart-mini" id="humidityChart"></div>
  119. </div>
  120. <div class="env-item">
  121. <div class="env-name">土壤</div>
  122. <div class="env-value-text">良好</div>
  123. <div class="env-chart-mini" id="soilChart"></div>
  124. </div>
  125. <div class="env-item">
  126. <div class="env-name">光照</div>
  127. <div class="env-value-text">85%</div>
  128. <div class="env-chart-mini" id="lightChart"></div>
  129. </div>
  130. </div>
  131. </div>
  132. </div>
  133. </div>
  134. <!-- 中央地图区域 -->
  135. <div class="dashboard-center">
  136. <div class="map-wrapper">
  137. <div class="map-header">
  138. <h3 class="map-title">农场地图</h3>
  139. </div>
  140. <div class="map-container-full">
  141. <div id="farmMap"></div>
  142. </div>
  143. </div>
  144. </div>
  145. <!-- 右侧面板 -->
  146. <div class="dashboard-right">
  147. <!-- 生产数据统计 -->
  148. <div class="panel-card">
  149. <div class="panel-header">
  150. <h3 class="panel-title">生产数据统计</h3>
  151. <el-radio-group v-model="productionTimeRange" size="mini">
  152. <el-radio-button label="month">月</el-radio-button>
  153. <el-radio-button label="quarter">季</el-radio-button>
  154. <el-radio-button label="year">年</el-radio-button>
  155. </el-radio-group>
  156. </div>
  157. <div class="panel-content">
  158. <div class="chart-wrapper">
  159. <div id="productionChart"></div>
  160. </div>
  161. </div>
  162. </div>
  163. <!-- 系统告警 -->
  164. <div class="panel-card">
  165. <div class="panel-header">
  166. <h3 class="panel-title">系统告警</h3>
  167. <el-badge :value="alertCount" class="badge-mini" type="danger">
  168. <span class="badge-text">待处理</span>
  169. </el-badge>
  170. </div>
  171. <div class="panel-content">
  172. <div class="alert-list-mini">
  173. <div class="alert-item-mini" v-for="alert in alertList" :key="alert.id" :class="alert.level">
  174. <div class="alert-icon-mini">
  175. <i :class="getAlertIcon(alert.level)"></i>
  176. </div>
  177. <div class="alert-info">
  178. <div class="alert-title-mini">{{ alert.title }}</div>
  179. <div class="alert-time-mini">{{ alert.time }}</div>
  180. </div>
  181. <div class="alert-status-mini" :class="alert.status">
  182. {{ getAlertStatusText(alert.status) }}
  183. </div>
  184. </div>
  185. </div>
  186. </div>
  187. </div>
  188. <!-- 农事任务执行进度 -->
  189. <div class="panel-card">
  190. <div class="panel-header">
  191. <h3 class="panel-title">农事任务执行进度</h3>
  192. </div>
  193. <div class="panel-content">
  194. <div class="task-progress-wrapper">
  195. <div class="task-chart-container">
  196. <div id="taskProgressChart"></div>
  197. </div>
  198. <div class="task-stats-grid">
  199. <div class="task-stat-item">
  200. <div class="stat-value total">{{ taskStats.total }}</div>
  201. <div class="stat-label">总任务数</div>
  202. </div>
  203. <div class="task-stat-item">
  204. <div class="stat-value completed">{{ taskStats.completed }}</div>
  205. <div class="stat-label">已完成</div>
  206. </div>
  207. <div class="task-stat-item">
  208. <div class="stat-value pending">{{ taskStats.pending }}</div>
  209. <div class="stat-label">待执行</div>
  210. </div>
  211. </div>
  212. </div>
  213. </div>
  214. </div>
  215. </div>
  216. </div>
  217. <!-- 底部汇总数据卡片 - 浮在地图上方 -->
  218. <div class="dashboard-summary-bottom">
  219. <div class="overview-card-mini" v-for="(item, index) in overviewData" :key="index">
  220. <div class="card-icon-mini" :style="{ backgroundColor: item.color }">
  221. <i :class="item.icon"></i>
  222. </div>
  223. <div class="card-info">
  224. <div class="card-value-mini">{{ item.value }}</div>
  225. <div class="card-label-mini">{{ item.label }}</div>
  226. </div>
  227. </div>
  228. </div>
  229. </div>
  230. </template>
  231. <script>
  232. export default {
  233. name: "Index",
  234. data() {
  235. return {
  236. // 版本号
  237. version: "3.6.5",
  238. selectedLocation: [], // 级联选择器的值
  239. confirmedLocation: [], // 已确认的筛选条件
  240. cropTimeRange: 'month',
  241. productionTimeRange: 'month',
  242. alertCount: 5,
  243. // 农事任务统计数据
  244. taskStats: {
  245. total: 12, // 总任务数
  246. completed: 9, // 已完成
  247. pending: 3, // 待执行
  248. completionRate: 75 // 完成率
  249. },
  250. // 全屏模式标志
  251. isFullscreenMode: false,
  252. // 级联选择器配置
  253. cascaderProps: {
  254. checkStrictly: true, // 允许选择任意一级
  255. label: 'label',
  256. value: 'value',
  257. children: 'children'
  258. },
  259. // 级联选择器选项
  260. locationOptions: [
  261. {
  262. label: '全部农场',
  263. value: 'all',
  264. children: []
  265. },
  266. {
  267. label: '东区智慧农场',
  268. value: 'east',
  269. children: [
  270. { label: 'A1地块', value: 'east_a1' },
  271. { label: 'A2地块', value: 'east_a2' },
  272. { label: 'A3地块', value: 'east_a3' }
  273. ]
  274. },
  275. {
  276. label: '西区智慧农场',
  277. value: 'west',
  278. children: [
  279. { label: 'B1地块', value: 'west_b1' },
  280. { label: 'B2地块', value: 'west_b2' }
  281. ]
  282. },
  283. {
  284. label: '南区智慧农场',
  285. value: 'south',
  286. children: [
  287. { label: 'C1地块', value: 'south_c1' },
  288. { label: 'C2地块', value: 'south_c2' },
  289. { label: 'C3地块', value: 'south_c3' }
  290. ]
  291. },
  292. {
  293. label: '北区智慧农场',
  294. value: 'north',
  295. children: [
  296. { label: 'D1地块', value: 'north_d1' },
  297. { label: 'D2地块', value: 'north_d2' }
  298. ]
  299. },
  300. {
  301. label: '中心监测农场',
  302. value: 'center',
  303. children: [
  304. { label: '实验地块1', value: 'center_test1' },
  305. { label: '实验地块2', value: 'center_test2' },
  306. { label: '示范地块', value: 'center_demo' }
  307. ]
  308. }
  309. ],
  310. // 概览数据
  311. overviewData: [
  312. {
  313. label: '农场总面积',
  314. value: '2,560',
  315. unit: '亩',
  316. change: '+5.2%',
  317. trend: 'up',
  318. color: '#10b981',
  319. icon: 'el-icon-location'
  320. },
  321. {
  322. label: '设备在线率',
  323. value: '95.8%',
  324. change: '+2.1%',
  325. trend: 'up',
  326. color: '#3b82f6',
  327. icon: 'el-icon-cpu'
  328. },
  329. {
  330. label: '葡萄健康率',
  331. value: '87.3%',
  332. change: '+1.8%',
  333. trend: 'up',
  334. color: '#8b5cf6',
  335. icon: 'el-icon-sunny'
  336. },
  337. {
  338. label: '月产量预期',
  339. value: '1,850',
  340. unit: '吨',
  341. change: '+8.5%',
  342. trend: 'up',
  343. color: '#f59e0b',
  344. icon: 'el-icon-box'
  345. }
  346. ],
  347. // 设备统计
  348. deviceStats: {
  349. total: 42,
  350. online: 38,
  351. offline: 4
  352. },
  353. // 告警列表
  354. alertList: [
  355. {
  356. id: 1,
  357. title: '东区土壤湿度过低',
  358. time: '2分钟前',
  359. level: 'warning',
  360. status: 'pending'
  361. },
  362. {
  363. id: 2,
  364. title: '西区温度传感器离线',
  365. time: '15分钟前',
  366. level: 'error',
  367. status: 'pending'
  368. },
  369. {
  370. id: 3,
  371. title: '南区灌溉系统异常',
  372. time: '1小时前',
  373. level: 'warning',
  374. status: 'processing'
  375. },
  376. {
  377. id: 4,
  378. title: '北区葡萄病虫害预警',
  379. time: '2小时前',
  380. level: 'info',
  381. status: 'resolved'
  382. }
  383. ],
  384. // 高德地图相关
  385. farmMap: null,
  386. farmMarkers: [],
  387. plotPolygons: []
  388. }
  389. },
  390. mounted() {
  391. // 初始化确认的位置为默认状态(全部农场)
  392. this.confirmedLocation = [];
  393. // 检测是否为全屏模式
  394. this.isFullscreenMode = this.$route.query.fullscreen === 'true';
  395. this.$nextTick(() => {
  396. // 等待页面完全渲染后再初始化图表
  397. this.waitForElementsAndInit();
  398. // 添加窗口调整监听器
  399. window.addEventListener('resize', this.handleResize);
  400. });
  401. },
  402. computed: {
  403. hasLocationChanged() {
  404. return JSON.stringify(this.selectedLocation) !== JSON.stringify(this.confirmedLocation);
  405. }
  406. },
  407. watch: {
  408. confirmedLocation: {
  409. handler(newLocation) {
  410. this.handleLocationDataUpdate();
  411. },
  412. deep: true
  413. },
  414. cropTimeRange: {
  415. handler(newRange, oldRange) {
  416. this.updateCropChart();
  417. }
  418. },
  419. productionTimeRange: {
  420. handler(newRange) {
  421. this.updateProductionChart();
  422. }
  423. }
  424. },
  425. beforeDestroy() {
  426. window.removeEventListener('resize', this.handleResize);
  427. // 清理高德地图资源
  428. if (this.farmMap) {
  429. if (this.farmMarkers) {
  430. this.farmMap.remove(this.farmMarkers);
  431. }
  432. if (this.plotPolygons) {
  433. this.farmMap.remove(this.plotPolygons);
  434. }
  435. this.farmMap.destroy();
  436. }
  437. },
  438. methods: {
  439. refreshData() {
  440. this.$message.success('数据刷新成功');
  441. // 这里可以添加实际的数据刷新逻辑
  442. console.log('手动刷新数据和图表...');
  443. this.directInitCharts();
  444. },
  445. goToDigitalDashboard() {
  446. // 使用新标签页打开农业数字化决策大屏(无菜单版本)
  447. const baseUrl = window.location.origin;
  448. window.open(`${baseUrl}/agri-digital/dashboard`, '_blank');
  449. },
  450. openFullscreenCockpit() {
  451. // 打开全屏驾驶舱
  452. const baseUrl = window.location.origin;
  453. const currentPath = this.$route.path;
  454. // 添加 fullscreen=true 参数
  455. window.open(`${baseUrl}${currentPath}?fullscreen=true`, '_blank');
  456. },
  457. getCurrentFarmDescription() {
  458. const currentSelection = this.getCurrentSelection();
  459. if (currentSelection.type === 'farm') {
  460. const farmDescriptions = {
  461. all: '全面展示各农场运营状况,实时监控智慧农业数据',
  462. east: '东区智慧农场 - 专注高效种植,数据驱动精准农业',
  463. west: '西区智慧农场 - 生态循环种植,科技助力绿色发展',
  464. south: '南区智慧农场 - 现代化设施农业,智能管控优质产出',
  465. north: '北区智慧农场 - 规模化种植基地,机械化智能化并重',
  466. center: '中心监测农场 - 技术研发示范,引领农业创新发展'
  467. };
  468. return farmDescriptions[currentSelection.id] || farmDescriptions.all;
  469. } else if (currentSelection.type === 'plot') {
  470. return `${currentSelection.farmName} - ${currentSelection.plotName} 地块详细监测数据`;
  471. }
  472. return '全面展示各农场运营状况,实时监控智慧农业数据';
  473. },
  474. getCurrentSelection() {
  475. if (!this.confirmedLocation || this.confirmedLocation.length === 0) {
  476. return { type: 'farm', id: 'all' };
  477. }
  478. if (this.confirmedLocation.length === 1) {
  479. // 选择的是农场
  480. return { type: 'farm', id: this.confirmedLocation[0] };
  481. } else if (this.confirmedLocation.length === 2) {
  482. // 选择的是地块
  483. const farmId = this.confirmedLocation[0];
  484. const plotId = this.confirmedLocation[1];
  485. const farmNames = {
  486. east: '东区智慧农场',
  487. west: '西区智慧农场',
  488. south: '南区智慧农场',
  489. north: '北区智慧农场',
  490. center: '中心监测农场'
  491. };
  492. // 从级联选择器中找到地块名称
  493. const farm = this.locationOptions.find(f => f.value === farmId);
  494. const plot = farm ? farm.children.find(p => p.value === plotId) : null;
  495. return {
  496. type: 'plot',
  497. farmId: farmId,
  498. plotId: plotId,
  499. farmName: farmNames[farmId] || '未知农场',
  500. plotName: plot ? plot.label : '未知地块'
  501. };
  502. }
  503. return { type: 'farm', id: 'all' };
  504. },
  505. confirmLocationChange() {
  506. // 确认选择的位置
  507. if (!this.selectedLocation || this.selectedLocation.length === 0) {
  508. this.confirmedLocation = [];
  509. this.$message.success('已切换为全部农场视图');
  510. } else {
  511. // 先设置确认的位置
  512. this.confirmedLocation = [...this.selectedLocation];
  513. // 基于选择的位置获取信息
  514. if (this.selectedLocation.length === 1) {
  515. const farmNames = {
  516. all: '全部农场',
  517. east: '东区智慧农场',
  518. west: '西区智慧农场',
  519. south: '南区智慧农场',
  520. north: '北区智慧农场',
  521. center: '中心监测农场'
  522. };
  523. this.$message.success(`已切换为 ${farmNames[this.selectedLocation[0]] || '未知农场'} 视图`);
  524. } else if (this.selectedLocation.length === 2) {
  525. const farmId = this.selectedLocation[0];
  526. const plotId = this.selectedLocation[1];
  527. const farmNames = {
  528. east: '东区智慧农场',
  529. west: '西区智慧农场',
  530. south: '南区智慧农场',
  531. north: '北区智慧农场',
  532. center: '中心监测农场'
  533. };
  534. // 从级联选择器中找到地块名称
  535. const farm = this.locationOptions.find(f => f.value === farmId);
  536. const plot = farm ? farm.children.find(p => p.value === plotId) : null;
  537. const farmName = farmNames[farmId] || '未知农场';
  538. const plotName = plot ? plot.label : '未知地块';
  539. this.$message.success(`已切换为 ${farmName} - ${plotName} 视图`);
  540. }
  541. }
  542. },
  543. clearLocation() {
  544. // 清空选择
  545. this.selectedLocation = [];
  546. this.confirmedLocation = [];
  547. this.$message.info('筛选条件已清空');
  548. },
  549. handleLocationDataUpdate() {
  550. const currentSelection = this.getCurrentSelection();
  551. if (currentSelection.type === 'farm') {
  552. // 选择农场时更新农场数据
  553. this.updateFarmData(currentSelection.id);
  554. } else if (currentSelection.type === 'plot') {
  555. // 选择地块时更新农场数据和地块特定数据
  556. this.updateFarmData(currentSelection.farmId);
  557. this.updatePlotData(currentSelection.plotId);
  558. }
  559. // 重新初始化图表和地图
  560. this.$nextTick(() => {
  561. this.initCharts();
  562. // 只更新地图内容,不重新创建地图
  563. this.updateMapContent();
  564. });
  565. },
  566. updatePlotData(plotId) {
  567. // 根据选择的地块更新特定数据
  568. console.log('更新地块数据:', plotId);
  569. // 这里可以添加地块特定的数据更新逻辑
  570. // 比如更新环境监测数据为该地块的具体数据
  571. },
  572. updateFarmData(farmId) {
  573. // 根据选择的农场更新数据
  574. this.updateOverviewData(farmId);
  575. this.updateDeviceStats(farmId);
  576. this.updateAlertList(farmId);
  577. // 重新初始化图表
  578. this.$nextTick(() => {
  579. // 延迟重新初始化图表
  580. setTimeout(() => {
  581. this.initCharts();
  582. }, 100);
  583. });
  584. },
  585. updateOverviewData(farmId) {
  586. // 模拟不同农场的数据
  587. const farmData = {
  588. all: {
  589. area: '2,560',
  590. deviceRate: '95.8%',
  591. healthRate: '87.3%',
  592. production: '1,850'
  593. },
  594. east: {
  595. area: '680',
  596. deviceRate: '97.2%',
  597. healthRate: '89.1%',
  598. production: '520'
  599. },
  600. west: {
  601. area: '590',
  602. deviceRate: '94.5%',
  603. healthRate: '86.8%',
  604. production: '450'
  605. },
  606. south: {
  607. area: '720',
  608. deviceRate: '96.8%',
  609. healthRate: '88.9%',
  610. production: '580'
  611. },
  612. north: {
  613. area: '570',
  614. deviceRate: '93.2%',
  615. healthRate: '85.4%',
  616. production: '300'
  617. }
  618. };
  619. const data = farmData[farmId] || farmData.all;
  620. this.overviewData[0].value = data.area;
  621. this.overviewData[1].value = data.deviceRate;
  622. this.overviewData[2].value = data.healthRate;
  623. this.overviewData[3].value = data.production;
  624. },
  625. updateDeviceStats(farmId) {
  626. // 模拟不同农场的设备统计
  627. const deviceData = {
  628. all: { total: 42, online: 38, offline: 4 },
  629. east: { total: 12, online: 11, offline: 1 },
  630. west: { total: 10, online: 9, offline: 1 },
  631. south: { total: 14, online: 13, offline: 1 },
  632. north: { total: 6, online: 5, offline: 1 }
  633. };
  634. const data = deviceData[farmId] || deviceData.all;
  635. this.deviceStats = data;
  636. },
  637. updateAlertList(farmId) {
  638. // 模拟不同农场的告警信息
  639. const alertData = {
  640. all: [
  641. { id: 1, title: '多个地块环境异常', time: '2分钟前', level: 'warning', status: 'pending' },
  642. { id: 2, title: '设备离线告警汇总', time: '15分钟前', level: 'error', status: 'pending' },
  643. { id: 3, title: '生产数据异常统计', time: '1小时前', level: 'warning', status: 'processing' },
  644. { id: 4, title: '综合监测预警信息', time: '2小时前', level: 'info', status: 'resolved' }
  645. ],
  646. east: [
  647. { id: 1, title: '东区A地块土壤湿度过低', time: '2分钟前', level: 'warning', status: 'pending' },
  648. { id: 2, title: '东区温度传感器离线', time: '15分钟前', level: 'error', status: 'pending' }
  649. ],
  650. west: [
  651. { id: 1, title: '西区B地块光照不足', time: '5分钟前', level: 'warning', status: 'pending' },
  652. { id: 2, title: '西区灌溉系统压力异常', time: '30分钟前', level: 'warning', status: 'processing' }
  653. ],
  654. south: [
  655. { id: 1, title: '南区C地块病虫害预警', time: '10分钟前', level: 'info', status: 'pending' },
  656. { id: 2, title: '南区设备维护提醒', time: '1小时前', level: 'info', status: 'resolved' }
  657. ],
  658. north: [
  659. { id: 1, title: '北区D地块温度过高', time: '8分钟前', level: 'warning', status: 'pending' }
  660. ]
  661. };
  662. const alerts = alertData[farmId] || alertData.all;
  663. this.alertList = alerts;
  664. this.alertCount = alerts.filter(alert => alert.status === 'pending').length;
  665. },
  666. handleResize() {
  667. // 响应式调整图表大小
  668. setTimeout(() => {
  669. this.resizeAllCharts();
  670. }, 100);
  671. },
  672. resizeAllCharts() {
  673. const chartIds = ['temperatureChart', 'humidityChart', 'soilChart', 'lightChart',
  674. 'cropStatusChart', 'deviceStatusChart', 'productionChart', 'farmMap'];
  675. chartIds.forEach(id => {
  676. const element = document.getElementById(id);
  677. if (element && this.$echarts) {
  678. const chart = this.$echarts.getInstanceByDom(element);
  679. if (chart) {
  680. chart.resize();
  681. }
  682. }
  683. });
  684. // 调整高德地图大小
  685. if (this.farmMap && typeof this.farmMap.resize === 'function') {
  686. try {
  687. this.farmMap.resize();
  688. } catch (error) {
  689. console.warn('地图resize失败:', error);
  690. }
  691. }
  692. },
  693. getAlertIcon(level) {
  694. const icons = {
  695. error: 'el-icon-warning',
  696. warning: 'el-icon-warning-outline',
  697. info: 'el-icon-info'
  698. };
  699. return icons[level] || 'el-icon-info';
  700. },
  701. getAlertStatusText(status) {
  702. const statusMap = {
  703. pending: '待处理',
  704. processing: '处理中',
  705. resolved: '已解决'
  706. };
  707. return statusMap[status] || '未知';
  708. },
  709. waitForElementsAndInit(attempt = 0) {
  710. const maxAttempts = 10;
  711. const delay = 200;
  712. console.log(`等待图表容器准备就绪... (尝试 ${attempt + 1}/${maxAttempts})`);
  713. // 检查关键元素是否有正确的尺寸
  714. const cropElement = document.getElementById('cropStatusChart');
  715. const deviceElement = document.getElementById('deviceStatusChart');
  716. if (cropElement && deviceElement) {
  717. const cropRect = cropElement.getBoundingClientRect();
  718. const deviceRect = deviceElement.getBoundingClientRect();
  719. console.log('Crop element size:', cropRect.width, 'x', cropRect.height);
  720. console.log('Device element size:', deviceRect.width, 'x', deviceRect.height);
  721. if (cropRect.width > 0 && cropRect.height > 0 &&
  722. deviceRect.width > 0 && deviceRect.height > 0) {
  723. // 元素有正确的尺寸,可以初始化图表
  724. console.log('图表容器准备就绪,开始初始化图表');
  725. this.directInitCharts();
  726. return;
  727. }
  728. }
  729. // 如果元素还没准备好,继续等待
  730. if (attempt < maxAttempts - 1) {
  731. setTimeout(() => {
  732. this.waitForElementsAndInit(attempt + 1);
  733. }, delay);
  734. } else {
  735. console.warn('图表容器等待超时,强制初始化');
  736. this.directInitCharts();
  737. }
  738. },
  739. directInitCharts() {
  740. console.log('Direct chart initialization...');
  741. console.log('ECharts available:', !!this.$echarts);
  742. console.log('ECharts object:', this.$echarts);
  743. // 直接初始化作物状况图表
  744. try {
  745. const cropElement = document.getElementById('cropStatusChart');
  746. console.log('cropStatusChart element:', cropElement);
  747. if (cropElement) {
  748. // 检查元素尺寸
  749. const rect = cropElement.getBoundingClientRect();
  750. console.log('cropElement dimensions:', rect.width, 'x', rect.height);
  751. // 如果尺寸为0,强制设置尺寸
  752. if (rect.width === 0 || rect.height === 0) {
  753. cropElement.style.width = '100%';
  754. cropElement.style.height = '300px';
  755. cropElement.style.display = 'block';
  756. }
  757. const cropChart = this.$echarts.init(cropElement);
  758. console.log('cropChart instance:', cropChart);
  759. cropChart.setOption({
  760. tooltip: {
  761. trigger: 'item',
  762. formatter: '{b}: {c}亩 ({d}%)'
  763. },
  764. series: [{
  765. name: '作物分布',
  766. type: 'pie',
  767. radius: ['45%', '75%'],
  768. center: ['50%', '50%'],
  769. data: [
  770. { value: 1200, name: '水稻', itemStyle: { color: '#10b981' } },
  771. { value: 580, name: '小麦', itemStyle: { color: '#3b82f6' } },
  772. { value: 520, name: '玉米', itemStyle: { color: '#f59e0b' } },
  773. { value: 260, name: '蔬菜', itemStyle: { color: '#8b5cf6' } }
  774. ],
  775. label: {
  776. show: true,
  777. position: 'inside',
  778. formatter: '{d}%',
  779. fontSize: 12,
  780. fontWeight: 'bold',
  781. color: 'white'
  782. },
  783. emphasis: {
  784. itemStyle: {
  785. shadowBlur: 10,
  786. shadowOffsetX: 0,
  787. shadowColor: 'rgba(0, 0, 0, 0.5)'
  788. }
  789. }
  790. }]
  791. });
  792. console.log('作物图表初始化成功');
  793. } else {
  794. console.error('cropStatusChart element not found!');
  795. }
  796. // 直接初始化设备状况图表
  797. const deviceElement = document.getElementById('deviceStatusChart');
  798. console.log('deviceStatusChart element:', deviceElement);
  799. if (deviceElement) {
  800. // 检查元素尺寸
  801. const rect = deviceElement.getBoundingClientRect();
  802. console.log('deviceElement dimensions:', rect.width, 'x', rect.height);
  803. // 如果尺寸为0,强制设置尺寸
  804. if (rect.width === 0 || rect.height === 0) {
  805. deviceElement.style.width = '100%';
  806. deviceElement.style.height = '300px';
  807. deviceElement.style.display = 'block';
  808. }
  809. const deviceChart = this.$echarts.init(deviceElement);
  810. console.log('deviceChart instance:', deviceChart);
  811. deviceChart.setOption({
  812. tooltip: {
  813. trigger: 'item',
  814. formatter: '{b}: {c}台 ({d}%)'
  815. },
  816. series: [{
  817. name: '设备状态',
  818. type: 'pie',
  819. radius: '70%',
  820. center: ['50%', '50%'],
  821. data: [
  822. { value: this.deviceStats.online, name: '在线', itemStyle: { color: '#10b981' } },
  823. { value: this.deviceStats.offline, name: '离线', itemStyle: { color: '#ef4444' } }
  824. ],
  825. label: {
  826. show: true,
  827. position: 'inside',
  828. formatter: '{d}%',
  829. fontSize: 12,
  830. fontWeight: 'bold',
  831. color: 'white'
  832. },
  833. emphasis: {
  834. itemStyle: {
  835. shadowBlur: 10,
  836. shadowOffsetX: 0,
  837. shadowColor: 'rgba(0, 0, 0, 0.5)'
  838. }
  839. }
  840. }]
  841. });
  842. console.log('设备图表初始化成功');
  843. } else {
  844. console.error('deviceStatusChart element not found!');
  845. }
  846. } catch (error) {
  847. console.error('图表初始化错误:', error);
  848. }
  849. // 初始化其他图表
  850. this.initEnvironmentCharts();
  851. this.initProductionChart();
  852. this.initTaskProgressChart();
  853. this.initFarmMap();
  854. },
  855. initChartsWithRetry(retryCount = 0) {
  856. const maxRetries = 3;
  857. const delay = 200 * (retryCount + 1); // 200ms, 400ms, 600ms
  858. setTimeout(() => {
  859. console.log(`Initializing charts... (attempt ${retryCount + 1})`);
  860. // 检查关键图表容器是否存在
  861. const cropChart = document.getElementById('cropStatusChart');
  862. const deviceChart = document.getElementById('deviceStatusChart');
  863. if (!cropChart || !deviceChart) {
  864. console.warn(`Key chart elements not ready, retrying... (${retryCount + 1}/${maxRetries})`);
  865. if (retryCount < maxRetries - 1) {
  866. this.initChartsWithRetry(retryCount + 1);
  867. return;
  868. }
  869. }
  870. this.initCharts();
  871. }, delay);
  872. },
  873. initCharts() {
  874. console.log('Initializing charts...');
  875. // 检查所有图表容器是否存在
  876. const chartElements = [
  877. 'temperatureChart', 'humidityChart', 'soilChart', 'lightChart',
  878. 'cropStatusChart', 'deviceStatusChart', 'productionChart', 'farmMap'
  879. ];
  880. chartElements.forEach(id => {
  881. const element = document.getElementById(id);
  882. if (!element) {
  883. console.warn(`Chart element ${id} not found`);
  884. } else {
  885. console.log(`Chart element ${id} found`);
  886. }
  887. });
  888. this.initEnvironmentCharts();
  889. this.initCropStatusChart();
  890. this.initDeviceStatusChart();
  891. this.initProductionChart();
  892. this.initTaskProgressChart();
  893. this.initFarmMap();
  894. console.log('Charts initialization completed');
  895. },
  896. initEnvironmentCharts() {
  897. // 温度图表
  898. const tempElement = document.getElementById('temperatureChart');
  899. if (!tempElement) {
  900. console.error('temperatureChart element not found');
  901. return;
  902. }
  903. const tempChart = this.$echarts.init(tempElement);
  904. tempChart.setOption({
  905. grid: { top: 10, right: 10, bottom: 10, left: 10 },
  906. xAxis: { show: false, type: 'category', data: ['周一', '周二', '周三', '周四', '周五', '周六', '周日'] },
  907. yAxis: { show: false, type: 'value' },
  908. series: [{
  909. type: 'line',
  910. data: [22, 24, 26, 25, 27, 26, 25],
  911. smooth: true,
  912. lineStyle: { color: '#10b981', width: 2 },
  913. areaStyle: {
  914. color: {
  915. type: 'linear',
  916. x: 0, y: 0, x2: 0, y2: 1,
  917. colorStops: [
  918. { offset: 0, color: 'rgba(16, 185, 129, 0.3)' },
  919. { offset: 1, color: 'rgba(16, 185, 129, 0.1)' }
  920. ]
  921. }
  922. },
  923. showSymbol: false
  924. }]
  925. });
  926. // 湿度图表
  927. const humidityElement = document.getElementById('humidityChart');
  928. if (!humidityElement) return;
  929. const humidityChart = this.$echarts.init(humidityElement);
  930. humidityChart.setOption({
  931. grid: { top: 10, right: 10, bottom: 10, left: 10 },
  932. xAxis: { show: false, type: 'category', data: ['周一', '周二', '周三', '周四', '周五', '周六', '周日'] },
  933. yAxis: { show: false, type: 'value' },
  934. series: [{
  935. type: 'line',
  936. data: [65, 68, 70, 66, 69, 68, 67],
  937. smooth: true,
  938. lineStyle: { color: '#3b82f6', width: 2 },
  939. areaStyle: {
  940. color: {
  941. type: 'linear',
  942. x: 0, y: 0, x2: 0, y2: 1,
  943. colorStops: [
  944. { offset: 0, color: 'rgba(59, 130, 246, 0.3)' },
  945. { offset: 1, color: 'rgba(59, 130, 246, 0.1)' }
  946. ]
  947. }
  948. },
  949. showSymbol: false
  950. }]
  951. });
  952. // 土壤图表
  953. const soilElement = document.getElementById('soilChart');
  954. if (!soilElement) return;
  955. const soilChart = this.$echarts.init(soilElement);
  956. soilChart.setOption({
  957. grid: { top: 10, right: 10, bottom: 10, left: 10 },
  958. xAxis: { show: false, type: 'category', data: ['周一', '周二', '周三', '周四', '周五', '周六', '周日'] },
  959. yAxis: { show: false, type: 'value' },
  960. series: [{
  961. type: 'bar',
  962. data: [7.2, 7.1, 7.3, 7.0, 7.2, 7.1, 7.2],
  963. itemStyle: {
  964. color: {
  965. type: 'linear',
  966. x: 0, y: 0, x2: 0, y2: 1,
  967. colorStops: [
  968. { offset: 0, color: '#8b5cf6' },
  969. { offset: 1, color: 'rgba(139, 92, 246, 0.6)' }
  970. ]
  971. }
  972. },
  973. barWidth: '60%'
  974. }]
  975. });
  976. // 光照图表
  977. const lightElement = document.getElementById('lightChart');
  978. if (!lightElement) return;
  979. const lightChart = this.$echarts.init(lightElement);
  980. lightChart.setOption({
  981. grid: { top: 10, right: 10, bottom: 10, left: 10 },
  982. xAxis: { show: false, type: 'category', data: ['周一', '周二', '周三', '周四', '周五', '周六', '周日'] },
  983. yAxis: { show: false, type: 'value' },
  984. series: [{
  985. type: 'line',
  986. data: [80, 85, 88, 82, 87, 85, 86],
  987. smooth: true,
  988. lineStyle: { color: '#f59e0b', width: 2 },
  989. areaStyle: {
  990. color: {
  991. type: 'linear',
  992. x: 0, y: 0, x2: 0, y2: 1,
  993. colorStops: [
  994. { offset: 0, color: 'rgba(245, 158, 11, 0.3)' },
  995. { offset: 1, color: 'rgba(245, 158, 11, 0.1)' }
  996. ]
  997. }
  998. },
  999. showSymbol: false
  1000. }]
  1001. });
  1002. },
  1003. initCropStatusChart() {
  1004. const chartElement = document.getElementById('cropStatusChart');
  1005. if (!chartElement) {
  1006. console.error('cropStatusChart element not found');
  1007. return;
  1008. }
  1009. const chart = this.$echarts.init(chartElement);
  1010. const cropData = this.getCropDataByTimeRange(this.cropTimeRange);
  1011. chart.setOption({
  1012. tooltip: {
  1013. trigger: 'item',
  1014. formatter: '{b}: {c}亩 ({d}%)'
  1015. },
  1016. legend: {
  1017. show: false // 不显示图例,因为我们有自定义的图例
  1018. },
  1019. series: [{
  1020. type: 'pie',
  1021. radius: ['45%', '75%'],
  1022. center: ['50%', '50%'],
  1023. data: cropData,
  1024. label: {
  1025. show: true,
  1026. position: 'inside',
  1027. formatter: '{d}%',
  1028. fontSize: 12,
  1029. fontWeight: 'bold',
  1030. color: 'white'
  1031. },
  1032. emphasis: {
  1033. itemStyle: {
  1034. shadowBlur: 10,
  1035. shadowOffsetX: 0,
  1036. shadowColor: 'rgba(0, 0, 0, 0.5)'
  1037. }
  1038. }
  1039. }]
  1040. });
  1041. },
  1042. initDeviceStatusChart() {
  1043. const chartElement = document.getElementById('deviceStatusChart');
  1044. if (!chartElement) {
  1045. console.error('deviceStatusChart element not found');
  1046. return;
  1047. }
  1048. const chart = this.$echarts.init(chartElement);
  1049. chart.setOption({
  1050. tooltip: {
  1051. trigger: 'item',
  1052. formatter: '{b}: {c}台 ({d}%)'
  1053. },
  1054. legend: {
  1055. show: false // 不显示图例,因为我们有自定义的统计信息
  1056. },
  1057. series: [{
  1058. type: 'pie',
  1059. radius: '70%',
  1060. center: ['50%', '50%'],
  1061. data: [
  1062. { value: this.deviceStats.online, name: '在线', itemStyle: { color: '#10b981' } },
  1063. { value: this.deviceStats.offline, name: '离线', itemStyle: { color: '#ef4444' } },
  1064. { value: this.deviceStats.total - this.deviceStats.online - this.deviceStats.offline, name: '故障', itemStyle: { color: '#f59e0b' } }
  1065. ],
  1066. label: {
  1067. show: true,
  1068. position: 'inside',
  1069. formatter: '{d}%',
  1070. fontSize: 12,
  1071. fontWeight: 'bold',
  1072. color: 'white'
  1073. },
  1074. emphasis: {
  1075. itemStyle: {
  1076. shadowBlur: 10,
  1077. shadowOffsetX: 0,
  1078. shadowColor: 'rgba(0, 0, 0, 0.5)'
  1079. }
  1080. }
  1081. }]
  1082. });
  1083. },
  1084. initProductionChart() {
  1085. const chartElement = document.getElementById('productionChart');
  1086. if (!chartElement) {
  1087. console.error('productionChart element not found');
  1088. return;
  1089. }
  1090. const chart = this.$echarts.init(chartElement);
  1091. const productionData = this.getProductionDataByTimeRange(this.productionTimeRange);
  1092. chart.setOption({
  1093. grid: { left: '15%', right: '10%', top: '15%', bottom: '15%' },
  1094. xAxis: {
  1095. type: 'category',
  1096. data: productionData.xAxis,
  1097. axisLine: { lineStyle: { color: '#e5e7eb' } },
  1098. axisTick: { show: false },
  1099. axisLabel: { color: '#6b7280', fontSize: 12 }
  1100. },
  1101. yAxis: {
  1102. type: 'value',
  1103. axisLine: { show: false },
  1104. axisTick: { show: false },
  1105. axisLabel: { color: '#6b7280', fontSize: 12 },
  1106. splitLine: { lineStyle: { color: '#f3f4f6' } }
  1107. },
  1108. tooltip: {
  1109. trigger: 'axis',
  1110. formatter: '{b}: {c}吨'
  1111. },
  1112. series: [{
  1113. type: 'bar',
  1114. data: productionData.data,
  1115. itemStyle: {
  1116. color: {
  1117. type: 'linear',
  1118. x: 0, y: 0, x2: 0, y2: 1,
  1119. colorStops: [
  1120. { offset: 0, color: '#10b981' },
  1121. { offset: 1, color: 'rgba(16, 185, 129, 0.6)' }
  1122. ]
  1123. }
  1124. },
  1125. barWidth: '50%'
  1126. }]
  1127. });
  1128. },
  1129. initTaskProgressChart() {
  1130. const chartElement = document.getElementById('taskProgressChart');
  1131. if (!chartElement) {
  1132. console.error('taskProgressChart element not found');
  1133. return;
  1134. }
  1135. const chart = this.$echarts.init(chartElement);
  1136. chart.setOption({
  1137. tooltip: {
  1138. trigger: 'item',
  1139. formatter: '{b}: {c}个 ({d}%)'
  1140. },
  1141. legend: {
  1142. show: false
  1143. },
  1144. series: [{
  1145. name: '任务进度',
  1146. type: 'pie',
  1147. radius: ['50%', '75%'],
  1148. center: ['50%', '50%'],
  1149. avoidLabelOverlap: false,
  1150. label: {
  1151. show: true,
  1152. position: 'center',
  1153. formatter: '{d}%',
  1154. fontSize: 24,
  1155. fontWeight: 'bold',
  1156. color: '#52c41a'
  1157. },
  1158. emphasis: {
  1159. label: {
  1160. show: true,
  1161. fontSize: 28,
  1162. fontWeight: 'bold'
  1163. }
  1164. },
  1165. labelLine: {
  1166. show: false
  1167. },
  1168. data: [
  1169. {
  1170. value: this.taskStats.completed,
  1171. name: '已完成',
  1172. itemStyle: {
  1173. color: {
  1174. type: 'linear',
  1175. x: 0, y: 0, x2: 0, y2: 1,
  1176. colorStops: [
  1177. { offset: 0, color: '#52c41a' },
  1178. { offset: 1, color: '#73d13d' }
  1179. ]
  1180. }
  1181. }
  1182. },
  1183. {
  1184. value: this.taskStats.pending,
  1185. name: '待执行',
  1186. itemStyle: {
  1187. color: 'rgba(255, 255, 255, 0.15)'
  1188. }
  1189. }
  1190. ]
  1191. }]
  1192. });
  1193. },
  1194. initFarmMap() {
  1195. const mapElement = document.getElementById('farmMap');
  1196. if (!mapElement) {
  1197. console.error('farmMap element not found');
  1198. return;
  1199. }
  1200. // 检查高德地图API是否加载
  1201. if (typeof AMap === 'undefined') {
  1202. console.error('高德地图API未加载,使用备用方案');
  1203. this.renderFallbackMap();
  1204. return;
  1205. }
  1206. try {
  1207. // 初始化高德地图 - 使用纯卫星影像图层(无路网)
  1208. // 图层说明:
  1209. // - TileLayer.Satellite: 卫星影像底图(纯影像,无道路标注)
  1210. // 如需添加路网图层,可添加: new AMap.TileLayer.RoadNet()
  1211. // 如需切换回矢量地图,可使用 mapStyle: 'amap://styles/whitesmoke' 并移除 layers 配置
  1212. this.farmMap = new AMap.Map('farmMap', {
  1213. zoom: 10,
  1214. center: [117.015029, 34.197274], // 默认中心点
  1215. viewMode: '2D',
  1216. resizeEnable: true,
  1217. layers: [
  1218. new AMap.TileLayer.Satellite() // 卫星影像底图(纯影像)
  1219. // new AMap.TileLayer.RoadNet() // 路网图层(已注释,如需显示道路名称可取消注释)
  1220. ]
  1221. });
  1222. // 等待地图加载完成
  1223. this.farmMap.on('complete', () => {
  1224. console.log('地图加载完成');
  1225. // 添加地图控件
  1226. this.farmMap.addControl(new AMap.Scale());
  1227. this.farmMap.addControl(new AMap.ToolBar({
  1228. position: 'RB'
  1229. }));
  1230. const currentSelection = this.getCurrentSelection();
  1231. if (currentSelection.type === 'farm' && currentSelection.id === 'all') {
  1232. // 显示所有农场位置概览
  1233. this.renderFarmOverviewOnMap();
  1234. } else if (currentSelection.type === 'farm') {
  1235. // 显示特定农场的地块详情
  1236. this.renderFarmDetailsOnMap(currentSelection.id);
  1237. } else if (currentSelection.type === 'plot') {
  1238. // 显示特定农场的地块详情,并高亮选中的地块
  1239. this.renderFarmDetailsOnMap(currentSelection.farmId, currentSelection.plotId);
  1240. }
  1241. });
  1242. } catch (error) {
  1243. console.error('地图初始化失败:', error);
  1244. this.renderFallbackMap();
  1245. }
  1246. },
  1247. renderFallbackMap() {
  1248. // 备用方案:使用ECharts显示农场分布
  1249. const mapElement = document.getElementById('farmMap');
  1250. if (!mapElement) return;
  1251. // 清空容器
  1252. mapElement.innerHTML = '';
  1253. // 使用ECharts作为备用方案
  1254. const chart = this.$echarts.init(mapElement);
  1255. const currentSelection = this.getCurrentSelection();
  1256. if (currentSelection.type === 'farm' && currentSelection.id === 'all') {
  1257. // 显示所有农场位置概览
  1258. chart.setOption({
  1259. title: {
  1260. text: '农场分布图(模拟地图)',
  1261. subtext: '地图API加载失败,显示备用方案',
  1262. left: 'center',
  1263. top: 20,
  1264. textStyle: {
  1265. fontSize: 16,
  1266. color: '#333'
  1267. },
  1268. subtextStyle: {
  1269. fontSize: 12,
  1270. color: '#666'
  1271. }
  1272. },
  1273. backgroundColor: '#f8fafc',
  1274. grid: { left: 0, right: 0, top: 60, bottom: 0 },
  1275. xAxis: {
  1276. show: false,
  1277. type: 'value',
  1278. min: 0,
  1279. max: 100
  1280. },
  1281. yAxis: {
  1282. show: false,
  1283. type: 'value',
  1284. min: 0,
  1285. max: 100
  1286. },
  1287. tooltip: {
  1288. trigger: 'item',
  1289. backgroundColor: 'transparent',
  1290. borderWidth: 0,
  1291. padding: 0,
  1292. formatter: function(params) {
  1293. const data = params.data;
  1294. return `
  1295. <div style="
  1296. padding: 0;
  1297. margin: 0;
  1298. min-width: 240px;
  1299. background: linear-gradient(135deg, #ffffff 0%, #f8fafc 100%);
  1300. border-radius: 12px;
  1301. box-shadow: 0 8px 25px rgba(0,0,0,0.15);
  1302. border: 1px solid #e2e8f0;
  1303. overflow: hidden;
  1304. font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
  1305. ">
  1306. <div style="
  1307. background: linear-gradient(135deg, #10b981 0%, #059669 100%);
  1308. padding: 12px 16px;
  1309. color: white;
  1310. ">
  1311. <div style="font-size: 15px; font-weight: 600; margin: 0;">${data[2]}</div>
  1312. <div style="font-size: 11px; opacity: 0.9; margin-top: 2px;">智慧农业基地</div>
  1313. </div>
  1314. <div style="padding: 14px;">
  1315. <div style="display: grid; grid-template-columns: 1fr 1fr; gap: 10px; margin-bottom: 12px;">
  1316. <div style="text-align: center; padding: 8px; background: #f1f5f9; border-radius: 6px;">
  1317. <div style="font-size: 16px; font-weight: 700; color: #1e293b;">${data[3]}</div>
  1318. <div style="font-size: 10px; color: #64748b;">面积(亩)</div>
  1319. </div>
  1320. <div style="text-align: center; padding: 8px; background: #ecfdf5; border-radius: 6px;">
  1321. <div style="font-size: 16px; font-weight: 700; color: #059669;">${data[5]}</div>
  1322. <div style="font-size: 10px; color: #64748b;">在线率</div>
  1323. </div>
  1324. </div>
  1325. <div style="display: flex; justify-content: space-between; align-items: center; padding: 6px 0; border-bottom: 1px solid #f1f5f9;">
  1326. <span style="color: #64748b; font-size: 12px;">设备数量</span>
  1327. <span style="color: #1e293b; font-weight: 600; font-size: 12px;">${data[4]}台</span>
  1328. </div>
  1329. <div style="display: flex; justify-content: space-between; align-items: center; padding: 6px 0;">
  1330. <span style="color: #64748b; font-size: 12px;">主要品种</span>
  1331. <span style="color: #1e293b; font-weight: 500; font-size: 12px;">${data[6]}</span>
  1332. </div>
  1333. </div>
  1334. </div>
  1335. `;
  1336. }
  1337. },
  1338. series: [{
  1339. type: 'scatter',
  1340. data: [
  1341. [20, 80, '东区智慧农场', '680', '12', '97.2%', '阳光玫瑰、玫瑰香'],
  1342. [70, 25, '西区智慧农场', '590', '10', '94.5%', '巨峰、红提'],
  1343. [80, 75, '南区智慧农场', '720', '14', '96.8%', '阳光玫瑰、夏黑'],
  1344. [25, 30, '北区智慧农场', '570', '6', '93.2%', '玫瑰香、巨峰'],
  1345. [50, 50, '中心监测农场', '200', '8', '98.5%', '克瑞森无核']
  1346. ],
  1347. symbolSize: function(value) {
  1348. return Math.max(35, parseInt(value[3]) / 20);
  1349. },
  1350. itemStyle: {
  1351. color: {
  1352. type: 'radial',
  1353. x: 0.5,
  1354. y: 0.5,
  1355. r: 0.8,
  1356. colorStops: [
  1357. { offset: 0, color: '#34d399' },
  1358. { offset: 0.7, color: '#10b981' },
  1359. { offset: 1, color: '#059669' }
  1360. ]
  1361. },
  1362. borderColor: '#ffffff',
  1363. borderWidth: 2,
  1364. shadowBlur: 15,
  1365. shadowColor: 'rgba(16, 185, 129, 0.4)',
  1366. shadowOffsetY: 3
  1367. },
  1368. emphasis: {
  1369. itemStyle: {
  1370. color: {
  1371. type: 'radial',
  1372. x: 0.5,
  1373. y: 0.5,
  1374. r: 0.8,
  1375. colorStops: [
  1376. { offset: 0, color: '#6ee7b7' },
  1377. { offset: 0.7, color: '#059669' },
  1378. { offset: 1, color: '#047857' }
  1379. ]
  1380. },
  1381. borderColor: '#ffffff',
  1382. borderWidth: 3,
  1383. shadowBlur: 25,
  1384. shadowColor: 'rgba(16, 185, 129, 0.6)',
  1385. shadowOffsetY: 5
  1386. },
  1387. scale: 1.2
  1388. },
  1389. label: {
  1390. show: true,
  1391. position: 'bottom',
  1392. formatter: function(params) {
  1393. return params.data[2];
  1394. },
  1395. color: '#1f2937',
  1396. fontSize: 13,
  1397. fontWeight: '600',
  1398. backgroundColor: 'rgba(255, 255, 255, 0.9)',
  1399. borderColor: '#e5e7eb',
  1400. borderWidth: 1,
  1401. borderRadius: 8,
  1402. padding: [4, 8],
  1403. shadowBlur: 3,
  1404. shadowColor: 'rgba(0, 0, 0, 0.1)',
  1405. shadowOffsetY: 1
  1406. },
  1407. emphasis: {
  1408. label: {
  1409. backgroundColor: '#ffffff',
  1410. borderColor: '#10b981',
  1411. color: '#059669'
  1412. }
  1413. }
  1414. }]
  1415. });
  1416. } else {
  1417. // 显示地块详情的备用方案
  1418. this.renderFarmDetailsECharts(chart, currentSelection.farmId || currentSelection.id, currentSelection.plotId);
  1419. }
  1420. },
  1421. renderFarmDetailsECharts(chart, farmId, highlightPlotId = null) {
  1422. // ECharts版本的农场详情
  1423. const farmDetailsMap = {
  1424. east: {
  1425. boundary: [[10, 70], [30, 90], [40, 85], [35, 65], [10, 70]],
  1426. plots: [
  1427. [15, 85, 'A1地块', '220', '4', '健康', '阳光玫瑰'],
  1428. [25, 80, 'A2地块', '230', '4', '健康', '玫瑰香'],
  1429. [35, 75, 'A3地块', '230', '4', '良好', '阳光玫瑰']
  1430. ]
  1431. },
  1432. west: {
  1433. boundary: [[60, 15], [80, 35], [75, 40], [55, 25], [60, 15]],
  1434. plots: [
  1435. [65, 25, 'B1地块', '295', '5', '健康', '巨峰'],
  1436. [75, 30, 'B2地块', '295', '5', '良好', '红提']
  1437. ]
  1438. },
  1439. south: {
  1440. boundary: [[70, 65], [90, 85], [85, 90], [65, 75], [70, 65]],
  1441. plots: [
  1442. [75, 75, 'C1地块', '240', '5', '健康', '阳光玫瑰'],
  1443. [80, 80, 'C2地块', '240', '5', '健康', '夏黑'],
  1444. [85, 75, 'C3地块', '240', '4', '良好', '阳光玫瑰']
  1445. ]
  1446. },
  1447. north: {
  1448. boundary: [[15, 20], [35, 40], [30, 45], [10, 30], [15, 20]],
  1449. plots: [
  1450. [20, 30, 'D1地块', '285', '3', '良好', '玫瑰香'],
  1451. [30, 35, 'D2地块', '285', '3', '健康', '巨峰']
  1452. ]
  1453. },
  1454. center: {
  1455. boundary: [[40, 40], [60, 60], [55, 65], [35, 50], [40, 40]],
  1456. plots: [
  1457. [45, 50, '实验地块1', '67', '3', '优秀', '克瑞森无核'],
  1458. [50, 55, '实验地块2', '67', '3', '优秀', '阳光玫瑰'],
  1459. [55, 50, '示范地块', '66', '2', '优秀', '醉金香']
  1460. ]
  1461. }
  1462. };
  1463. const farmData = farmDetailsMap[farmId];
  1464. if (!farmData) return;
  1465. chart.setOption({
  1466. title: {
  1467. text: `${farmId === 'east' ? '东区' : farmId === 'west' ? '西区' : farmId === 'south' ? '南区' : farmId === 'north' ? '北区' : '中心'}智慧农场地块分布`,
  1468. left: 'center',
  1469. top: 20,
  1470. textStyle: {
  1471. fontSize: 16,
  1472. color: '#333'
  1473. }
  1474. },
  1475. backgroundColor: '#f8fafc',
  1476. grid: { left: 0, right: 0, top: 60, bottom: 0 },
  1477. xAxis: {
  1478. show: false,
  1479. type: 'value',
  1480. min: 0,
  1481. max: 100
  1482. },
  1483. yAxis: {
  1484. show: false,
  1485. type: 'value',
  1486. min: 0,
  1487. max: 100
  1488. },
  1489. tooltip: {
  1490. trigger: 'item',
  1491. backgroundColor: 'transparent',
  1492. borderWidth: 0,
  1493. padding: 0,
  1494. formatter: function(params) {
  1495. if (params.seriesIndex === 0) return '';
  1496. const data = params.data;
  1497. const isHighlighted = params.color && (params.color.colorStops || params.color).toString().includes('#f59e0b');
  1498. const healthColor = data[5] === '优秀' ? '#10b981' : data[5] === '健康' ? '#059669' : data[5] === '良好' ? '#f59e0b' : '#ef4444';
  1499. const healthBgColor = data[5] === '优秀' ? '#ecfdf5' : data[5] === '健康' ? '#f0fdf4' : data[5] === '良好' ? '#fefbf3' : '#fef2f2';
  1500. return `
  1501. <div style="
  1502. padding: 0;
  1503. margin: 0;
  1504. min-width: 220px;
  1505. background: linear-gradient(135deg, #ffffff 0%, #f8fafc 100%);
  1506. border-radius: 12px;
  1507. box-shadow: 0 8px 25px rgba(0,0,0,0.15);
  1508. border: 1px solid #e2e8f0;
  1509. overflow: hidden;
  1510. font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
  1511. ">
  1512. <div style="
  1513. background: linear-gradient(135deg, ${healthColor} 0%, ${healthColor}dd 100%);
  1514. padding: 12px 14px;
  1515. color: white;
  1516. ">
  1517. <div style="font-size: 14px; font-weight: 600; margin: 0;">${data[2]}</div>
  1518. <div style="font-size: 10px; opacity: 0.9; margin-top: 2px;">农业种植地块</div>
  1519. </div>
  1520. <div style="padding: 12px;">
  1521. <div style="
  1522. display: inline-flex;
  1523. align-items: center;
  1524. padding: 4px 10px;
  1525. background: ${healthBgColor};
  1526. border-radius: 16px;
  1527. margin-bottom: 10px;
  1528. ">
  1529. <div style="
  1530. width: 5px;
  1531. height: 5px;
  1532. background: ${healthColor};
  1533. border-radius: 50%;
  1534. margin-right: 6px;
  1535. "></div>
  1536. <span style="color: ${healthColor}; font-size: 10px; font-weight: 600;">${data[5]}状态</span>
  1537. </div>
  1538. <div>
  1539. <div style="display: flex; justify-content: space-between; align-items: center; padding: 5px 0; border-bottom: 1px solid #f1f5f9;">
  1540. <span style="color: #64748b; font-size: 11px;">地块面积</span>
  1541. <span style="color: #1e293b; font-weight: 600; font-size: 12px;">${data[3]} 亩</span>
  1542. </div>
  1543. <div style="display: flex; justify-content: space-between; align-items: center; padding: 5px 0; border-bottom: 1px solid #f1f5f9;">
  1544. <span style="color: #64748b; font-size: 11px;">监测设备</span>
  1545. <span style="color: #1e293b; font-weight: 600; font-size: 12px;">${data[4]} 台</span>
  1546. </div>
  1547. <div style="display: flex; justify-content: space-between; align-items: center; padding: 5px 0;">
  1548. <span style="color: #64748b; font-size: 11px;">种植品种</span>
  1549. <span style="color: #1e293b; font-weight: 500; font-size: 12px;">${data[6]}</span>
  1550. </div>
  1551. </div>
  1552. </div>
  1553. </div>
  1554. `;
  1555. }
  1556. },
  1557. series: [
  1558. {
  1559. type: 'line',
  1560. data: farmData.boundary,
  1561. lineStyle: {
  1562. color: '#059669',
  1563. width: 3,
  1564. type: 'dashed'
  1565. },
  1566. symbol: 'none',
  1567. silent: true
  1568. },
  1569. {
  1570. type: 'scatter',
  1571. data: farmData.plots.map(plot => {
  1572. const isHighlighted = highlightPlotId && plot[2].includes(highlightPlotId.split('_')[1]);
  1573. return {
  1574. value: plot,
  1575. itemStyle: isHighlighted ? {
  1576. color: '#f59e0b',
  1577. shadowBlur: 20,
  1578. shadowColor: 'rgba(245, 158, 11, 0.8)',
  1579. borderColor: '#dc2626',
  1580. borderWidth: 3
  1581. } : {
  1582. color: '#10b981',
  1583. shadowBlur: 8,
  1584. shadowColor: 'rgba(16, 185, 129, 0.4)'
  1585. }
  1586. };
  1587. }),
  1588. symbolSize: function(value) {
  1589. const isHighlighted = highlightPlotId && value[2].includes(highlightPlotId.split('_')[1]);
  1590. return isHighlighted ? 35 : 25;
  1591. }
  1592. }
  1593. ]
  1594. });
  1595. },
  1596. renderFarmOverviewOnMap() {
  1597. // 清除之前的标记
  1598. if (this.farmMarkers) {
  1599. this.farmMap.remove(this.farmMarkers);
  1600. }
  1601. // 农场数据(基于新的中心位置分布)
  1602. const farmData = [
  1603. {
  1604. name: '东区智慧农场',
  1605. position: [117.065029, 34.227274],
  1606. area: '680',
  1607. devices: '12',
  1608. onlineRate: '97.2%',
  1609. crops: '阳光玫瑰、玫瑰香'
  1610. },
  1611. {
  1612. name: '西区智慧农场',
  1613. position: [116.965029, 34.167274],
  1614. area: '590',
  1615. devices: '10',
  1616. onlineRate: '94.5%',
  1617. crops: '巨峰、红提'
  1618. },
  1619. {
  1620. name: '南区智慧农场',
  1621. position: [117.025029, 34.147274],
  1622. area: '720',
  1623. devices: '14',
  1624. onlineRate: '96.8%',
  1625. crops: '阳光玫瑰、夏黑'
  1626. },
  1627. {
  1628. name: '北区智慧农场',
  1629. position: [116.985029, 34.247274],
  1630. area: '570',
  1631. devices: '6',
  1632. onlineRate: '93.2%',
  1633. crops: '玫瑰香、巨峰'
  1634. },
  1635. {
  1636. name: '中心监测农场',
  1637. position: [117.015029, 34.197274],
  1638. area: '200',
  1639. devices: '8',
  1640. onlineRate: '98.5%',
  1641. crops: '克瑞森无核'
  1642. }
  1643. ];
  1644. this.farmMarkers = [];
  1645. farmData.forEach((farm, index) => {
  1646. console.log('创建农场标记:', farm.name, farm.position);
  1647. // 创建简单的标记先测试
  1648. const marker = new AMap.Marker({
  1649. position: farm.position,
  1650. title: farm.name
  1651. });
  1652. // 完全避免SVG,使用高德地图内置样式
  1653. // 创建自定义HTML标记元素
  1654. const markerContent = document.createElement('div');
  1655. markerContent.style.cssText = `
  1656. width: 20px;
  1657. height: 20px;
  1658. background: #10b981;
  1659. border: 2px solid white;
  1660. border-radius: 50%;
  1661. box-shadow: 0 2px 6px rgba(0,0,0,0.3);
  1662. display: flex;
  1663. align-items: center;
  1664. justify-content: center;
  1665. padding: 3px;
  1666. `;
  1667. // 创建田地的4个小方格
  1668. const fieldGrid = document.createElement('div');
  1669. fieldGrid.style.cssText = `
  1670. display: grid;
  1671. grid-template-columns: 1fr 1fr;
  1672. grid-template-rows: 1fr 1fr;
  1673. gap: 1px;
  1674. width: 8px;
  1675. height: 8px;
  1676. `;
  1677. // 创建4个小方格
  1678. for (let i = 0; i < 4; i++) {
  1679. const gridItem = document.createElement('div');
  1680. gridItem.style.cssText = `
  1681. background: white;
  1682. border-radius: 1px;
  1683. opacity: 0.9;
  1684. `;
  1685. fieldGrid.appendChild(gridItem);
  1686. }
  1687. markerContent.appendChild(fieldGrid);
  1688. marker.setContent(markerContent);
  1689. marker.setOffset(new AMap.Pixel(-10, -10));
  1690. // 创建信息窗体 - 自定义样式 + 手动添加三角箭头
  1691. const infoWindow = new AMap.InfoWindow({
  1692. isCustom: true,
  1693. content: `
  1694. <div style="
  1695. padding: 0;
  1696. margin: 0;
  1697. min-width: 280px;
  1698. position: relative;
  1699. font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
  1700. ">
  1701. <!-- 主悬浮框 -->
  1702. <div style="
  1703. background: linear-gradient(135deg, #ffffff 0%, #f8fafc 100%);
  1704. border-radius: 12px;
  1705. box-shadow: 0 8px 25px rgba(0,0,0,0.15);
  1706. border: 1px solid #e2e8f0;
  1707. overflow: hidden;
  1708. position: relative;
  1709. margin-bottom: 8px;
  1710. ">
  1711. <div style="
  1712. background: linear-gradient(135deg, #10b981 0%, #059669 100%);
  1713. padding: 16px 20px;
  1714. color: white;
  1715. position: relative;
  1716. ">
  1717. <div style="
  1718. position: absolute;
  1719. top: -10px;
  1720. right: -10px;
  1721. width: 40px;
  1722. height: 40px;
  1723. background: rgba(255,255,255,0.1);
  1724. border-radius: 50%;
  1725. "></div>
  1726. <h4 style="
  1727. margin: 0;
  1728. font-size: 18px;
  1729. font-weight: 600;
  1730. text-shadow: 0 1px 2px rgba(0,0,0,0.1);
  1731. ">${farm.name}</h4>
  1732. <div style="
  1733. font-size: 12px;
  1734. opacity: 0.9;
  1735. margin-top: 4px;
  1736. ">智慧农业基地</div>
  1737. <!-- 关闭按钮 -->
  1738. <div onclick="this.parentElement.parentElement.parentElement.style.display='none'" style="
  1739. position: absolute;
  1740. top: 8px;
  1741. right: 8px;
  1742. width: 20px;
  1743. height: 20px;
  1744. border-radius: 50%;
  1745. background: rgba(255,255,255,0.2);
  1746. display: flex;
  1747. align-items: center;
  1748. justify-content: center;
  1749. cursor: pointer;
  1750. font-size: 12px;
  1751. color: white;
  1752. font-weight: bold;
  1753. ">×</div>
  1754. </div>
  1755. <div style="padding: 20px;">
  1756. <div style="display: grid; grid-template-columns: 1fr 1fr; gap: 16px; margin-bottom: 16px;">
  1757. <div style="text-align: center; padding: 12px; background: #f1f5f9; border-radius: 8px;">
  1758. <div style="font-size: 20px; font-weight: 700; color: #1e293b; margin-bottom: 4px;">${farm.area}</div>
  1759. <div style="font-size: 12px; color: #64748b;">总面积(亩)</div>
  1760. </div>
  1761. <div style="text-align: center; padding: 12px; background: #ecfdf5; border-radius: 8px;">
  1762. <div style="font-size: 20px; font-weight: 700; color: #059669; margin-bottom: 4px;">${farm.onlineRate}</div>
  1763. <div style="font-size: 12px; color: #64748b;">设备在线率</div>
  1764. </div>
  1765. </div>
  1766. <div style="space-y: 12px;">
  1767. <div style="
  1768. display: flex;
  1769. align-items: center;
  1770. padding: 8px 0;
  1771. border-bottom: 1px solid #f1f5f9;
  1772. ">
  1773. <div style="
  1774. width: 8px;
  1775. height: 8px;
  1776. background: #3b82f6;
  1777. border-radius: 50%;
  1778. margin-right: 12px;
  1779. "></div>
  1780. <span style="color: #475569; font-size: 14px; flex: 1;">设备数量</span>
  1781. <span style="color: #1e293b; font-weight: 600; font-size: 14px;">${farm.devices}台</span>
  1782. </div>
  1783. <div style="
  1784. display: flex;
  1785. align-items: center;
  1786. padding: 8px 0;
  1787. ">
  1788. <div style="
  1789. width: 8px;
  1790. height: 8px;
  1791. background: #f59e0b;
  1792. border-radius: 50%;
  1793. margin-right: 12px;
  1794. "></div>
  1795. <span style="color: #475569; font-size: 14px; flex: 1;">主要品种</span>
  1796. <span style="color: #1e293b; font-weight: 500; font-size: 14px;">${farm.crops}</span>
  1797. </div>
  1798. </div>
  1799. </div>
  1800. </div>
  1801. <!-- 自定义三角箭头 -->
  1802. <div style="
  1803. position: absolute;
  1804. bottom: -12px;
  1805. left: 50%;
  1806. transform: translateX(-50%);
  1807. width: 0;
  1808. height: 0;
  1809. border-left: 12px solid transparent;
  1810. border-right: 12px solid transparent;
  1811. border-top: 12px solid #cbd5e1;
  1812. z-index: 1;
  1813. filter: drop-shadow(0 2px 4px rgba(0,0,0,0.1));
  1814. "></div>
  1815. <div style="
  1816. position: absolute;
  1817. bottom: -10px;
  1818. left: 50%;
  1819. transform: translateX(-50%);
  1820. width: 0;
  1821. height: 0;
  1822. border-left: 10px solid transparent;
  1823. border-right: 10px solid transparent;
  1824. border-top: 10px solid #ffffff;
  1825. z-index: 2;
  1826. "></div>
  1827. </div>
  1828. `,
  1829. offset: new AMap.Pixel(0, -5)
  1830. });
  1831. // 点击标记显示信息窗体
  1832. marker.on('click', () => {
  1833. // 关闭其他可能打开的信息窗体
  1834. this.farmMap.clearInfoWindow();
  1835. // 在标记位置打开信息窗体
  1836. infoWindow.open(this.farmMap, marker.getPosition());
  1837. });
  1838. this.farmMarkers.push(marker);
  1839. });
  1840. // 添加标记到地图
  1841. console.log('添加标记到地图,标记数量:', this.farmMarkers.length);
  1842. this.farmMap.add(this.farmMarkers);
  1843. // 检查标记是否成功添加
  1844. setTimeout(() => {
  1845. console.log('地图上的标记数量:', this.farmMap.getAllOverlays('marker').length);
  1846. }, 500);
  1847. // 自适应显示所有农场
  1848. const bounds = new AMap.Bounds();
  1849. farmData.forEach(farm => {
  1850. bounds.extend(farm.position);
  1851. });
  1852. this.farmMap.setBounds(bounds, false, [50, 50, 50, 50]);
  1853. console.log('地图边界设置完成');
  1854. },
  1855. renderFarmDetailsOnMap(farmId, highlightPlotId = null) {
  1856. // 清除之前的标记
  1857. if (this.farmMarkers) {
  1858. this.farmMap.remove(this.farmMarkers);
  1859. }
  1860. if (this.plotPolygons) {
  1861. this.farmMap.remove(this.plotPolygons);
  1862. }
  1863. // 农场详情数据
  1864. const farmDetailsMap = {
  1865. east: {
  1866. center: [117.065029, 34.227274],
  1867. plots: [
  1868. {
  1869. name: 'A1地块',
  1870. area: '220',
  1871. devices: '4',
  1872. health: '健康',
  1873. crop: '阳光玫瑰',
  1874. polygon: [
  1875. [117.063029, 34.237274],
  1876. [117.067029, 34.237274],
  1877. [117.067029, 34.217274],
  1878. [117.063029, 34.217274]
  1879. ]
  1880. },
  1881. {
  1882. name: 'A2地块',
  1883. area: '230',
  1884. devices: '4',
  1885. health: '健康',
  1886. crop: '玫瑰香',
  1887. polygon: [
  1888. [117.067029, 34.237274],
  1889. [117.071029, 34.237274],
  1890. [117.071029, 34.217274],
  1891. [117.067029, 34.217274]
  1892. ]
  1893. },
  1894. {
  1895. name: 'A3地块',
  1896. area: '230',
  1897. devices: '4',
  1898. health: '良好',
  1899. crop: '阳光玫瑰',
  1900. polygon: [
  1901. [117.063029, 34.217274],
  1902. [117.071029, 34.217274],
  1903. [117.071029, 34.197274],
  1904. [117.063029, 34.197274]
  1905. ]
  1906. }
  1907. ]
  1908. },
  1909. west: {
  1910. center: [116.965029, 34.167274],
  1911. plots: [
  1912. {
  1913. name: 'B1地块',
  1914. area: '295',
  1915. devices: '5',
  1916. health: '健康',
  1917. crop: '巨峰',
  1918. polygon: [
  1919. [116.963029, 34.177274],
  1920. [116.967029, 34.177274],
  1921. [116.967029, 34.157274],
  1922. [116.963029, 34.157274]
  1923. ]
  1924. },
  1925. {
  1926. name: 'B2地块',
  1927. area: '295',
  1928. devices: '5',
  1929. health: '良好',
  1930. crop: '红提',
  1931. polygon: [
  1932. [116.967029, 34.177274],
  1933. [116.971029, 34.177274],
  1934. [116.971029, 34.157274],
  1935. [116.967029, 34.157274]
  1936. ]
  1937. }
  1938. ]
  1939. },
  1940. south: {
  1941. center: [117.025029, 34.147274],
  1942. plots: [
  1943. {
  1944. name: 'C1地块',
  1945. area: '240',
  1946. devices: '5',
  1947. health: '健康',
  1948. crop: '阳光玫瑰',
  1949. polygon: [
  1950. [117.023029, 34.157274],
  1951. [117.027029, 34.157274],
  1952. [117.027029, 34.137274],
  1953. [117.023029, 34.137274]
  1954. ]
  1955. },
  1956. {
  1957. name: 'C2地块',
  1958. area: '240',
  1959. devices: '5',
  1960. health: '健康',
  1961. crop: '夏黑',
  1962. polygon: [
  1963. [117.027029, 34.157274],
  1964. [117.031029, 34.157274],
  1965. [117.031029, 34.137274],
  1966. [117.027029, 34.137274]
  1967. ]
  1968. },
  1969. {
  1970. name: 'C3地块',
  1971. area: '240',
  1972. devices: '4',
  1973. health: '良好',
  1974. crop: '阳光玫瑰',
  1975. polygon: [
  1976. [117.023029, 34.137274],
  1977. [117.031029, 34.137274],
  1978. [117.031029, 34.117274],
  1979. [117.023029, 34.117274]
  1980. ]
  1981. }
  1982. ]
  1983. },
  1984. north: {
  1985. center: [116.985029, 34.247274],
  1986. plots: [
  1987. {
  1988. name: 'D1地块',
  1989. area: '285',
  1990. devices: '3',
  1991. health: '良好',
  1992. crop: '玫瑰香',
  1993. polygon: [
  1994. [116.983029, 34.257274],
  1995. [116.987029, 34.257274],
  1996. [116.987029, 34.237274],
  1997. [116.983029, 34.237274]
  1998. ]
  1999. },
  2000. {
  2001. name: 'D2地块',
  2002. area: '285',
  2003. devices: '3',
  2004. health: '健康',
  2005. crop: '巨峰',
  2006. polygon: [
  2007. [116.987029, 34.257274],
  2008. [116.991029, 34.257274],
  2009. [116.991029, 34.237274],
  2010. [116.987029, 34.237274]
  2011. ]
  2012. }
  2013. ]
  2014. },
  2015. center: {
  2016. center: [117.015029, 34.197274],
  2017. plots: [
  2018. {
  2019. name: '实验地块1',
  2020. area: '67',
  2021. devices: '3',
  2022. health: '优秀',
  2023. crop: '克瑞森无核',
  2024. polygon: [
  2025. [117.013029, 34.207274],
  2026. [117.017029, 34.207274],
  2027. [117.017029, 34.187274],
  2028. [117.013029, 34.187274]
  2029. ]
  2030. },
  2031. {
  2032. name: '实验地块2',
  2033. area: '67',
  2034. devices: '3',
  2035. health: '优秀',
  2036. crop: '阳光玫瑰',
  2037. polygon: [
  2038. [117.017029, 34.207274],
  2039. [117.021029, 34.207274],
  2040. [117.021029, 34.187274],
  2041. [117.017029, 34.187274]
  2042. ]
  2043. },
  2044. {
  2045. name: '示范地块',
  2046. area: '66',
  2047. devices: '2',
  2048. health: '优秀',
  2049. crop: '醉金香',
  2050. polygon: [
  2051. [117.013029, 34.187274],
  2052. [117.021029, 34.187274],
  2053. [117.021029, 34.167274],
  2054. [117.013029, 34.167274]
  2055. ]
  2056. }
  2057. ]
  2058. }
  2059. };
  2060. const farmData = farmDetailsMap[farmId];
  2061. if (!farmData) return;
  2062. // 设置地图中心
  2063. this.farmMap.setCenter(farmData.center);
  2064. this.farmMap.setZoom(14);
  2065. this.plotPolygons = [];
  2066. this.farmMarkers = [];
  2067. farmData.plots.forEach((plot, index) => {
  2068. const isHighlighted = highlightPlotId && plot.name.includes(highlightPlotId.split('_')[1]);
  2069. // 创建地块多边形
  2070. const polygon = new AMap.Polygon({
  2071. path: plot.polygon,
  2072. strokeColor: isHighlighted ? '#f59e0b' : '#10b981',
  2073. strokeWeight: isHighlighted ? 3 : 2,
  2074. strokeOpacity: 0.8,
  2075. fillColor: isHighlighted ? '#f59e0b' : '#10b981',
  2076. fillOpacity: isHighlighted ? 0.4 : 0.2,
  2077. cursor: 'pointer'
  2078. });
  2079. // 地块中心点坐标
  2080. const centerLng = plot.polygon.reduce((sum, point) => sum + point[0], 0) / plot.polygon.length;
  2081. const centerLat = plot.polygon.reduce((sum, point) => sum + point[1], 0) / plot.polygon.length;
  2082. // 创建地块标记
  2083. const marker = new AMap.Marker({
  2084. position: [centerLng, centerLat],
  2085. title: plot.name
  2086. });
  2087. // 添加自定义图标
  2088. try {
  2089. marker.setIcon(new AMap.Icon({
  2090. size: new AMap.Size(32, 32),
  2091. image: 'data:image/svg+xml;charset=utf-8,' + encodeURIComponent(`
  2092. <svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 32 32">
  2093. <defs>
  2094. <filter id="plotShadow" x="-50%" y="-50%" width="200%" height="200%">
  2095. <feDropShadow dx="0" dy="2" stdDeviation="2" flood-color="rgba(0,0,0,0.25)"/>
  2096. </filter>
  2097. <linearGradient id="plotGradient${index}" x1="0%" y1="0%" x2="100%" y2="100%">
  2098. <stop offset="0%" style="stop-color:${isHighlighted ? '#f59e0b' : '#10b981'};stop-opacity:1" />
  2099. <stop offset="100%" style="stop-color:${isHighlighted ? '#d97706' : '#059669'};stop-opacity:1" />
  2100. </linearGradient>
  2101. </defs>
  2102. <circle cx="16" cy="16" r="13" fill="url(#plotGradient${index})" filter="url(#plotShadow)"/>
  2103. <circle cx="16" cy="16" r="13" fill="none" stroke="${isHighlighted ? '#ffffff' : '#ffffff'}" stroke-width="2" opacity="0.9"/>
  2104. ${isHighlighted ?
  2105. '<circle cx="16" cy="16" r="13" fill="none" stroke="#dc2626" stroke-width="3" opacity="0.6" stroke-dasharray="4,2"/>' :
  2106. ''
  2107. }
  2108. <g transform="translate(16,16)">
  2109. <rect x="-6" y="-6" width="12" height="12" fill="none" stroke="white" stroke-width="1.5" opacity="0.9" rx="1"/>
  2110. <path d="M-3,-6 L-3,6 M0,-6 L0,6 M3,-6 L3,6" stroke="white" stroke-width="0.8" opacity="0.7"/>
  2111. <path d="M-6,-3 L6,-3 M-6,0 L6,0 M-6,3 L6,3" stroke="white" stroke-width="0.8" opacity="0.7"/>
  2112. <circle cx="0" cy="0" r="1.5" fill="white" opacity="0.95"/>
  2113. </g>
  2114. </svg>
  2115. `),
  2116. imageOffset: new AMap.Pixel(-16, -16)
  2117. }));
  2118. } catch (iconError) {
  2119. console.warn('设置地块图标失败:', iconError);
  2120. }
  2121. // 创建信息窗体 - 自定义样式 + 手动添加三角箭头
  2122. const healthColor = plot.health === '优秀' ? '#10b981' : plot.health === '健康' ? '#059669' : plot.health === '良好' ? '#f59e0b' : '#ef4444';
  2123. const healthBgColor = plot.health === '优秀' ? '#ecfdf5' : plot.health === '健康' ? '#f0fdf4' : plot.health === '良好' ? '#fefbf3' : '#fef2f2';
  2124. const infoWindow = new AMap.InfoWindow({
  2125. isCustom: true,
  2126. content: `
  2127. <div style="
  2128. padding: 0;
  2129. margin: 0;
  2130. min-width: 260px;
  2131. position: relative;
  2132. font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
  2133. ">
  2134. <!-- 主悬浮框 -->
  2135. <div style="
  2136. background: linear-gradient(135deg, #ffffff 0%, #f8fafc 100%);
  2137. border-radius: 12px;
  2138. box-shadow: 0 8px 25px rgba(0,0,0,0.15);
  2139. border: 1px solid #e2e8f0;
  2140. overflow: hidden;
  2141. position: relative;
  2142. margin-bottom: 8px;
  2143. ">
  2144. <div style="
  2145. background: linear-gradient(135deg, ${healthColor} 0%, ${healthColor}dd 100%);
  2146. padding: 14px 18px;
  2147. color: white;
  2148. position: relative;
  2149. ">
  2150. <div style="
  2151. position: absolute;
  2152. top: -8px;
  2153. right: -8px;
  2154. width: 32px;
  2155. height: 32px;
  2156. background: rgba(255,255,255,0.15);
  2157. border-radius: 50%;
  2158. "></div>
  2159. <h4 style="
  2160. margin: 0;
  2161. font-size: 16px;
  2162. font-weight: 600;
  2163. text-shadow: 0 1px 2px rgba(0,0,0,0.1);
  2164. ">${plot.name}</h4>
  2165. <div style="
  2166. font-size: 11px;
  2167. opacity: 0.9;
  2168. margin-top: 2px;
  2169. ">农业种植地块</div>
  2170. <!-- 关闭按钮 -->
  2171. <div onclick="this.parentElement.parentElement.parentElement.style.display='none'" style="
  2172. position: absolute;
  2173. top: 6px;
  2174. right: 6px;
  2175. width: 18px;
  2176. height: 18px;
  2177. border-radius: 50%;
  2178. background: rgba(255,255,255,0.2);
  2179. display: flex;
  2180. align-items: center;
  2181. justify-content: center;
  2182. cursor: pointer;
  2183. font-size: 10px;
  2184. color: white;
  2185. font-weight: bold;
  2186. ">×</div>
  2187. </div>
  2188. <div style="padding: 16px;">
  2189. <div style="
  2190. display: inline-flex;
  2191. align-items: center;
  2192. padding: 6px 12px;
  2193. background: ${healthBgColor};
  2194. border-radius: 20px;
  2195. margin-bottom: 14px;
  2196. ">
  2197. <div style="
  2198. width: 6px;
  2199. height: 6px;
  2200. background: ${healthColor};
  2201. border-radius: 50%;
  2202. margin-right: 8px;
  2203. "></div>
  2204. <span style="color: ${healthColor}; font-size: 12px; font-weight: 600;">${plot.health}状态</span>
  2205. </div>
  2206. <div style="space-y: 10px;">
  2207. <div style="
  2208. display: flex;
  2209. align-items: center;
  2210. justify-content: space-between;
  2211. padding: 8px 0;
  2212. border-bottom: 1px solid #f1f5f9;
  2213. ">
  2214. <span style="color: #64748b; font-size: 13px;">地块面积</span>
  2215. <span style="color: #1e293b; font-weight: 600; font-size: 14px;">${plot.area} 亩</span>
  2216. </div>
  2217. <div style="
  2218. display: flex;
  2219. align-items: center;
  2220. justify-content: space-between;
  2221. padding: 8px 0;
  2222. border-bottom: 1px solid #f1f5f9;
  2223. ">
  2224. <span style="color: #64748b; font-size: 13px;">监测设备</span>
  2225. <span style="color: #1e293b; font-weight: 600; font-size: 14px;">${plot.devices} 台</span>
  2226. </div>
  2227. <div style="
  2228. display: flex;
  2229. align-items: center;
  2230. justify-content: space-between;
  2231. padding: 8px 0;
  2232. ">
  2233. <span style="color: #64748b; font-size: 13px;">种植品种</span>
  2234. <span style="color: #1e293b; font-weight: 500; font-size: 14px;">${plot.crop}</span>
  2235. </div>
  2236. </div>
  2237. </div>
  2238. </div>
  2239. <!-- 自定义三角箭头 -->
  2240. <div style="
  2241. position: absolute;
  2242. bottom: -12px;
  2243. left: 50%;
  2244. transform: translateX(-50%);
  2245. width: 0;
  2246. height: 0;
  2247. border-left: 12px solid transparent;
  2248. border-right: 12px solid transparent;
  2249. border-top: 12px solid #cbd5e1;
  2250. z-index: 1;
  2251. filter: drop-shadow(0 2px 4px rgba(0,0,0,0.1));
  2252. "></div>
  2253. <div style="
  2254. position: absolute;
  2255. bottom: -10px;
  2256. left: 50%;
  2257. transform: translateX(-50%);
  2258. width: 0;
  2259. height: 0;
  2260. border-left: 10px solid transparent;
  2261. border-right: 10px solid transparent;
  2262. border-top: 10px solid #ffffff;
  2263. z-index: 2;
  2264. "></div>
  2265. </div>
  2266. `,
  2267. offset: new AMap.Pixel(-130, -15)
  2268. });
  2269. // 点击事件
  2270. const showInfo = () => {
  2271. // 关闭其他可能打开的信息窗体
  2272. this.farmMap.clearInfoWindow();
  2273. // 在地块中心位置打开信息窗体
  2274. infoWindow.open(this.farmMap, [centerLng, centerLat]);
  2275. };
  2276. marker.on('click', showInfo);
  2277. polygon.on('click', showInfo);
  2278. this.plotPolygons.push(polygon);
  2279. this.farmMarkers.push(marker);
  2280. });
  2281. // 添加到地图
  2282. this.farmMap.add(this.plotPolygons);
  2283. this.farmMap.add(this.farmMarkers);
  2284. },
  2285. updateMapContent() {
  2286. // 更新地图内容,不重新创建地图实例
  2287. if (this.farmMap) {
  2288. const currentSelection = this.getCurrentSelection();
  2289. if (currentSelection.type === 'farm' && currentSelection.id === 'all') {
  2290. this.renderFarmOverviewOnMap();
  2291. } else if (currentSelection.type === 'farm') {
  2292. this.renderFarmDetailsOnMap(currentSelection.id);
  2293. } else if (currentSelection.type === 'plot') {
  2294. this.renderFarmDetailsOnMap(currentSelection.farmId, currentSelection.plotId);
  2295. }
  2296. } else {
  2297. // 如果地图实例不存在,使用备用方案更新
  2298. const mapElement = document.getElementById('farmMap');
  2299. if (mapElement && this.$echarts) {
  2300. const chart = this.$echarts.getInstanceByDom(mapElement);
  2301. if (chart) {
  2302. const currentSelection = this.getCurrentSelection();
  2303. if (currentSelection.type === 'farm' && currentSelection.id === 'all') {
  2304. this.renderFallbackMap();
  2305. } else {
  2306. this.renderFarmDetailsECharts(chart, currentSelection.farmId || currentSelection.id, currentSelection.plotId);
  2307. }
  2308. }
  2309. }
  2310. }
  2311. },
  2312. getCropDataByTimeRange(timeRange) {
  2313. // 根据时间范围返回不同的葡萄品种数据
  2314. const cropDataMap = {
  2315. week: [
  2316. { value: 280, name: '阳光玫瑰', itemStyle: { color: '#10b981' } }, // 43.8%
  2317. { value: 180, name: '玫瑰香', itemStyle: { color: '#3b82f6' } }, // 28.1%
  2318. { value: 120, name: '巨峰', itemStyle: { color: '#f59e0b' } }, // 18.8%
  2319. { value: 60, name: '夏黑', itemStyle: { color: '#8b5cf6' } } // 9.4%
  2320. ],
  2321. month: [
  2322. { value: 1200, name: '阳光玫瑰', itemStyle: { color: '#10b981' } }, // 46.9%
  2323. { value: 580, name: '玫瑰香', itemStyle: { color: '#3b82f6' } }, // 22.7%
  2324. { value: 520, name: '巨峰', itemStyle: { color: '#f59e0b' } }, // 20.3%
  2325. { value: 260, name: '夏黑', itemStyle: { color: '#8b5cf6' } } // 10.2%
  2326. ],
  2327. season: [
  2328. { value: 3800, name: '阳光玫瑰', itemStyle: { color: '#10b981' } }, // 50.0%
  2329. { value: 1520, name: '玫瑰香', itemStyle: { color: '#3b82f6' } }, // 20.0%
  2330. { value: 1520, name: '巨峰', itemStyle: { color: '#f59e0b' } }, // 20.0%
  2331. { value: 760, name: '夏黑', itemStyle: { color: '#8b5cf6' } } // 10.0%
  2332. ]
  2333. };
  2334. return cropDataMap[timeRange] || cropDataMap.month;
  2335. },
  2336. getProductionDataByTimeRange(timeRange) {
  2337. // 根据时间范围返回不同的生产数据
  2338. const productionDataMap = {
  2339. month: {
  2340. xAxis: ['1月', '2月', '3月', '4月', '5月', '6月'],
  2341. data: [120, 200, 150, 80, 70, 110]
  2342. },
  2343. quarter: {
  2344. xAxis: ['第一季度', '第二季度', '第三季度', '第四季度'],
  2345. data: [470, 340, 380, 420]
  2346. },
  2347. year: {
  2348. xAxis: ['2019年', '2020年', '2021年', '2022年', '2023年', '2024年'],
  2349. data: [1350, 1480, 1620, 1750, 1680, 1800]
  2350. }
  2351. };
  2352. return productionDataMap[timeRange] || productionDataMap.month;
  2353. },
  2354. updateCropChart() {
  2355. // 更新葡萄生长状况图表
  2356. this.$nextTick(() => {
  2357. const chartElement = document.getElementById('cropStatusChart');
  2358. if (!chartElement || !this.$echarts) return;
  2359. const chart = this.$echarts.getInstanceByDom(chartElement);
  2360. if (!chart) {
  2361. this.initCropStatusChart();
  2362. return;
  2363. }
  2364. const cropData = this.getCropDataByTimeRange(this.cropTimeRange);
  2365. chart.setOption({
  2366. tooltip: {
  2367. trigger: 'item',
  2368. formatter: '{b}: {c}亩 ({d}%)'
  2369. },
  2370. legend: {
  2371. show: false
  2372. },
  2373. series: [{
  2374. type: 'pie',
  2375. radius: ['45%', '75%'],
  2376. center: ['50%', '50%'],
  2377. data: cropData,
  2378. label: {
  2379. show: true,
  2380. position: 'inside',
  2381. formatter: '{d}%',
  2382. fontSize: 12,
  2383. fontWeight: 'bold',
  2384. color: 'white'
  2385. },
  2386. emphasis: {
  2387. itemStyle: {
  2388. shadowBlur: 10,
  2389. shadowOffsetX: 0,
  2390. shadowColor: 'rgba(0, 0, 0, 0.5)'
  2391. }
  2392. }
  2393. }]
  2394. });
  2395. // 强制触发Vue响应式更新
  2396. this.$forceUpdate();
  2397. });
  2398. },
  2399. updateProductionChart() {
  2400. // 更新生产数据统计图表
  2401. this.$nextTick(() => {
  2402. const chartElement = document.getElementById('productionChart');
  2403. if (!chartElement || !this.$echarts) return;
  2404. const chart = this.$echarts.getInstanceByDom(chartElement);
  2405. if (!chart) return;
  2406. const productionData = this.getProductionDataByTimeRange(this.productionTimeRange);
  2407. chart.setOption({
  2408. xAxis: {
  2409. data: productionData.xAxis
  2410. },
  2411. series: [{
  2412. data: productionData.data
  2413. }]
  2414. });
  2415. });
  2416. },
  2417. updateCropLegend() {
  2418. // 根据当前时间范围更新葡萄品种图例显示
  2419. const cropData = this.getCropDataByTimeRange(this.cropTimeRange);
  2420. // 这里可以动态更新页面上的图例显示,如果需要的话
  2421. },
  2422. getCurrentCropLegend() {
  2423. // 获取当前时间范围的葡萄品种图例数据
  2424. const cropData = this.getCropDataByTimeRange(this.cropTimeRange);
  2425. return cropData.map(item => ({
  2426. name: item.name,
  2427. value: item.value,
  2428. color: item.itemStyle.color
  2429. }));
  2430. }
  2431. }
  2432. }
  2433. </script>
  2434. <style scoped lang="scss">
  2435. // ============ 驾驶舱整体布局 ============
  2436. .cockpit-dashboard {
  2437. width: 100%;
  2438. height: 100vh;
  2439. overflow: hidden;
  2440. background: #001528;
  2441. display: flex;
  2442. flex-direction: column;
  2443. color: #fff;
  2444. &.fullscreen-mode {
  2445. // 全屏模式下可以进一步定制样式
  2446. .dashboard-header {
  2447. height: 80px;
  2448. }
  2449. }
  2450. }
  2451. // ============ 头部区域 ============
  2452. .dashboard-header {
  2453. height: 100px;
  2454. background: linear-gradient(180deg, #002140 0%, #001528 100%);
  2455. border-bottom: 2px solid rgba(24, 144, 255, 0.3);
  2456. display: flex;
  2457. align-items: center;
  2458. padding: 0 20px;
  2459. gap: 20px;
  2460. flex-shrink: 0;
  2461. box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3);
  2462. .header-left {
  2463. flex: 1;
  2464. .title-section {
  2465. .dashboard-title {
  2466. font-size: 24px;
  2467. font-weight: 700;
  2468. color: #fff;
  2469. margin: 0 0 4px 0;
  2470. background: linear-gradient(135deg, #1890ff, #36cfc9);
  2471. -webkit-background-clip: text;
  2472. -webkit-text-fill-color: transparent;
  2473. background-clip: text;
  2474. text-shadow: 0 0 20px rgba(24, 144, 255, 0.5);
  2475. }
  2476. .dashboard-subtitle {
  2477. font-size: 12px;
  2478. color: rgba(255, 255, 255, 0.65);
  2479. margin: 0;
  2480. white-space: nowrap;
  2481. overflow: hidden;
  2482. text-overflow: ellipsis;
  2483. }
  2484. }
  2485. }
  2486. .header-right {
  2487. flex: 0 0 auto;
  2488. .action-buttons {
  2489. display: flex;
  2490. gap: 8px;
  2491. align-items: center;
  2492. .location-selector-group {
  2493. display: flex;
  2494. gap: 6px;
  2495. align-items: center;
  2496. padding: 6px 10px;
  2497. background: rgba(24, 144, 255, 0.08);
  2498. border-radius: 6px;
  2499. border: 1px solid rgba(24, 144, 255, 0.3);
  2500. }
  2501. // ============ 自定义按钮样式 ============
  2502. // 确认按钮 - 青绿色
  2503. .btn-confirm {
  2504. background: #00C48F !important;
  2505. border-color: #00C48F !important;
  2506. color: #ffffff !important;
  2507. border-radius: 6px;
  2508. font-weight: 500;
  2509. transition: all 0.3s ease;
  2510. &:hover:not(:disabled) {
  2511. background: #00E5A0 !important;
  2512. border-color: #00E5A0 !important;
  2513. box-shadow: 0 0 12px rgba(0, 196, 143, 0.5);
  2514. transform: translateY(-1px);
  2515. }
  2516. &:active:not(:disabled) {
  2517. transform: translateY(0);
  2518. }
  2519. &:disabled {
  2520. background: rgba(0, 196, 143, 0.3) !important;
  2521. border-color: rgba(0, 196, 143, 0.3) !important;
  2522. color: rgba(255, 255, 255, 0.5) !important;
  2523. cursor: not-allowed;
  2524. }
  2525. }
  2526. // 刷新按钮 - 科技蓝
  2527. .btn-refresh {
  2528. background: #409EFF !important;
  2529. border-color: #409EFF !important;
  2530. color: #ffffff !important;
  2531. border-radius: 6px;
  2532. font-weight: 500;
  2533. transition: all 0.3s ease;
  2534. &:hover {
  2535. background: #66b1ff !important;
  2536. border-color: #66b1ff !important;
  2537. box-shadow: 0 0 12px rgba(64, 158, 255, 0.5);
  2538. transform: translateY(-1px);
  2539. }
  2540. &:active {
  2541. transform: translateY(0);
  2542. }
  2543. }
  2544. // 全屏驾驶舱按钮 - 高亮蓝色
  2545. .btn-fullscreen {
  2546. background: linear-gradient(135deg, #2E65F3 0%, #00A6FF 100%) !important;
  2547. border-color: transparent !important;
  2548. color: #ffffff !important;
  2549. border-radius: 6px;
  2550. font-weight: 500;
  2551. transition: all 0.3s ease;
  2552. box-shadow: 0 2px 8px rgba(46, 101, 243, 0.3);
  2553. &:hover {
  2554. background: linear-gradient(135deg, #4578ff 0%, #1eb8ff 100%) !important;
  2555. box-shadow: 0 4px 16px rgba(46, 101, 243, 0.6);
  2556. transform: translateY(-1px);
  2557. }
  2558. &:active {
  2559. transform: translateY(0);
  2560. }
  2561. }
  2562. }
  2563. }
  2564. }
  2565. // ============ 主体区域 ============
  2566. .dashboard-body {
  2567. flex: 1;
  2568. display: flex;
  2569. gap: 12px;
  2570. padding: 12px;
  2571. overflow: hidden;
  2572. // 左侧面板
  2573. .dashboard-left {
  2574. width: 320px;
  2575. flex-shrink: 0;
  2576. display: flex;
  2577. flex-direction: column;
  2578. gap: 12px;
  2579. overflow-y: auto;
  2580. &::-webkit-scrollbar {
  2581. width: 4px;
  2582. }
  2583. &::-webkit-scrollbar-thumb {
  2584. background: rgba(24, 144, 255, 0.3);
  2585. border-radius: 2px;
  2586. }
  2587. // 左侧最后一个卡片扩展填充剩余空间
  2588. .panel-card:last-child {
  2589. flex: 1;
  2590. display: flex;
  2591. flex-direction: column;
  2592. min-height: 0;
  2593. .panel-content {
  2594. flex: 1;
  2595. display: flex;
  2596. flex-direction: column;
  2597. min-height: 0;
  2598. }
  2599. }
  2600. }
  2601. // 中央地图区域
  2602. .dashboard-center {
  2603. flex: 1;
  2604. display: flex;
  2605. flex-direction: column;
  2606. overflow: hidden;
  2607. }
  2608. // 右侧面板
  2609. .dashboard-right {
  2610. width: 320px;
  2611. flex-shrink: 0;
  2612. display: flex;
  2613. flex-direction: column;
  2614. gap: 12px;
  2615. overflow-y: auto;
  2616. &::-webkit-scrollbar {
  2617. width: 4px;
  2618. }
  2619. &::-webkit-scrollbar-thumb {
  2620. background: rgba(24, 144, 255, 0.3);
  2621. border-radius: 2px;
  2622. }
  2623. // 右侧最后一个卡片扩展填充剩余空间
  2624. .panel-card:last-child {
  2625. flex: 1;
  2626. display: flex;
  2627. flex-direction: column;
  2628. min-height: 0;
  2629. .panel-content {
  2630. flex: 1;
  2631. display: flex;
  2632. flex-direction: column;
  2633. min-height: 0;
  2634. }
  2635. }
  2636. }
  2637. }
  2638. // ============ 面板卡片通用样式 ============
  2639. .panel-card {
  2640. background: rgba(0, 33, 64, 0.6);
  2641. border: 1px solid rgba(24, 144, 255, 0.3);
  2642. border-radius: 8px;
  2643. overflow: hidden;
  2644. backdrop-filter: blur(10px);
  2645. flex-shrink: 0;
  2646. .panel-header {
  2647. background: rgba(24, 144, 255, 0.1);
  2648. border-bottom: 1px solid rgba(24, 144, 255, 0.3);
  2649. padding: 12px 16px;
  2650. display: flex;
  2651. justify-content: space-between;
  2652. align-items: center;
  2653. .panel-title {
  2654. font-size: 14px;
  2655. font-weight: 600;
  2656. color: #1890ff;
  2657. margin: 0;
  2658. text-shadow: 0 0 10px rgba(24, 144, 255, 0.5);
  2659. }
  2660. .device-summary-mini {
  2661. font-size: 11px;
  2662. color: rgba(255, 255, 255, 0.65);
  2663. display: flex;
  2664. gap: 8px;
  2665. .online-text {
  2666. color: #52c41a;
  2667. }
  2668. }
  2669. }
  2670. .panel-content {
  2671. padding: 12px;
  2672. }
  2673. }
  2674. // ============ 图表容器 ============
  2675. .chart-wrapper {
  2676. height: 180px;
  2677. width: 100%;
  2678. #cropStatusChart,
  2679. #deviceStatusChart,
  2680. #productionChart {
  2681. width: 100% !important;
  2682. height: 100% !important;
  2683. }
  2684. }
  2685. .chart-legend-mini {
  2686. display: grid;
  2687. grid-template-columns: 1fr 1fr;
  2688. gap: 6px 8px;
  2689. margin-top: 12px;
  2690. .legend-item-mini {
  2691. display: flex;
  2692. align-items: center;
  2693. gap: 6px;
  2694. font-size: 11px;
  2695. color: rgba(255, 255, 255, 0.85);
  2696. line-height: 1.4;
  2697. .legend-dot {
  2698. width: 8px;
  2699. height: 8px;
  2700. border-radius: 50%;
  2701. flex-shrink: 0;
  2702. }
  2703. .legend-text {
  2704. flex: 1;
  2705. word-break: break-all;
  2706. }
  2707. }
  2708. }
  2709. // 设备统计
  2710. .device-stats-mini {
  2711. display: flex;
  2712. justify-content: space-around;
  2713. margin-top: 12px;
  2714. padding-top: 12px;
  2715. border-top: 1px solid rgba(24, 144, 255, 0.2);
  2716. .stat-item {
  2717. text-align: center;
  2718. .stat-label {
  2719. display: block;
  2720. font-size: 11px;
  2721. color: rgba(255, 255, 255, 0.65);
  2722. margin-bottom: 4px;
  2723. }
  2724. .stat-value {
  2725. font-size: 16px;
  2726. font-weight: 600;
  2727. &.success {
  2728. color: #52c41a;
  2729. }
  2730. &.danger {
  2731. color: #ff4d4f;
  2732. }
  2733. }
  2734. }
  2735. }
  2736. // 环境监测网格
  2737. .env-grid-mini {
  2738. display: grid;
  2739. grid-template-columns: 1fr 1fr;
  2740. grid-template-rows: 1fr 1fr;
  2741. gap: 12px;
  2742. flex: 1;
  2743. min-height: 0;
  2744. .env-item {
  2745. background: rgba(24, 144, 255, 0.05);
  2746. border: 1px solid rgba(24, 144, 255, 0.2);
  2747. border-radius: 6px;
  2748. padding: 12px;
  2749. text-align: center;
  2750. display: flex;
  2751. flex-direction: column;
  2752. min-height: 0;
  2753. .env-name {
  2754. font-size: 11px;
  2755. color: rgba(255, 255, 255, 0.65);
  2756. margin-bottom: 6px;
  2757. flex-shrink: 0;
  2758. }
  2759. .env-value-text {
  2760. font-size: 14px;
  2761. font-weight: 600;
  2762. color: #1890ff;
  2763. margin-bottom: 8px;
  2764. flex-shrink: 0;
  2765. }
  2766. .env-chart-mini {
  2767. flex: 1;
  2768. min-height: 40px;
  2769. width: 100%;
  2770. }
  2771. }
  2772. }
  2773. // ============ 告警列表 ============
  2774. .alert-list-mini {
  2775. max-height: 300px;
  2776. overflow-y: auto;
  2777. &::-webkit-scrollbar {
  2778. width: 4px;
  2779. }
  2780. &::-webkit-scrollbar-thumb {
  2781. background: rgba(24, 144, 255, 0.3);
  2782. border-radius: 2px;
  2783. }
  2784. .alert-item-mini {
  2785. display: flex;
  2786. align-items: center;
  2787. gap: 10px;
  2788. padding: 12px 8px;
  2789. border-bottom: 1px solid rgba(24, 144, 255, 0.1);
  2790. transition: background-color 0.2s;
  2791. &:hover {
  2792. background: rgba(24, 144, 255, 0.08);
  2793. }
  2794. &:last-child {
  2795. border-bottom: none;
  2796. }
  2797. .alert-icon-mini {
  2798. width: 28px;
  2799. height: 28px;
  2800. border-radius: 6px;
  2801. display: flex;
  2802. align-items: center;
  2803. justify-content: center;
  2804. font-size: 14px;
  2805. color: white;
  2806. flex-shrink: 0;
  2807. }
  2808. &.warning .alert-icon-mini {
  2809. background: #faad14;
  2810. }
  2811. &.error .alert-icon-mini {
  2812. background: #ff4d4f;
  2813. }
  2814. &.info .alert-icon-mini {
  2815. background: #1890ff;
  2816. }
  2817. .alert-info {
  2818. flex: 1;
  2819. min-width: 0;
  2820. .alert-title-mini {
  2821. font-size: 12px;
  2822. color: rgba(255, 255, 255, 0.85);
  2823. font-weight: 500;
  2824. margin-bottom: 2px;
  2825. white-space: nowrap;
  2826. overflow: hidden;
  2827. text-overflow: ellipsis;
  2828. }
  2829. .alert-time-mini {
  2830. font-size: 10px;
  2831. color: rgba(255, 255, 255, 0.45);
  2832. }
  2833. }
  2834. .alert-status-mini {
  2835. font-size: 10px;
  2836. padding: 2px 6px;
  2837. border-radius: 10px;
  2838. font-weight: 500;
  2839. flex-shrink: 0;
  2840. &.pending {
  2841. background: rgba(250, 173, 20, 0.2);
  2842. color: #faad14;
  2843. }
  2844. &.processing {
  2845. background: rgba(24, 144, 255, 0.2);
  2846. color: #1890ff;
  2847. }
  2848. &.resolved {
  2849. background: rgba(82, 196, 26, 0.2);
  2850. color: #52c41a;
  2851. }
  2852. }
  2853. }
  2854. }
  2855. .badge-mini {
  2856. .badge-text {
  2857. color: rgba(255, 255, 255, 0.65);
  2858. font-size: 11px;
  2859. }
  2860. }
  2861. // ============ 地图区域 ============
  2862. .map-wrapper {
  2863. height: 100%;
  2864. display: flex;
  2865. flex-direction: column;
  2866. background: rgba(0, 33, 64, 0.6);
  2867. border: 1px solid rgba(24, 144, 255, 0.3);
  2868. border-radius: 8px;
  2869. overflow: hidden;
  2870. backdrop-filter: blur(10px);
  2871. .map-header {
  2872. background: rgba(24, 144, 255, 0.1);
  2873. border-bottom: 1px solid rgba(24, 144, 255, 0.3);
  2874. padding: 12px 16px;
  2875. .map-title {
  2876. font-size: 14px;
  2877. font-weight: 600;
  2878. color: #1890ff;
  2879. margin: 0;
  2880. text-shadow: 0 0 10px rgba(24, 144, 255, 0.5);
  2881. }
  2882. }
  2883. .map-container-full {
  2884. flex: 1;
  2885. position: relative;
  2886. overflow: hidden;
  2887. #farmMap {
  2888. width: 100%;
  2889. height: 100%;
  2890. background: #0a1929;
  2891. }
  2892. }
  2893. }
  2894. // ============ 底部汇总数据卡片 ============
  2895. .dashboard-summary-bottom {
  2896. position: absolute;
  2897. bottom: 24px;
  2898. left: 50%;
  2899. transform: translateX(-50%);
  2900. z-index: 2;
  2901. display: flex;
  2902. gap: 16px;
  2903. max-width: calc(100% - 680px); // 左右各留出340px空间(面板320px + 间距20px)
  2904. pointer-events: auto;
  2905. .overview-card-mini {
  2906. background: rgba(0, 33, 64, 0.85);
  2907. backdrop-filter: blur(10px);
  2908. border: 1px solid rgba(24, 144, 255, 0.4);
  2909. border-radius: 10px;
  2910. padding: 14px 18px;
  2911. display: flex;
  2912. align-items: center;
  2913. gap: 12px;
  2914. transition: all 0.3s ease;
  2915. min-width: 160px;
  2916. box-shadow: 0 4px 16px rgba(0, 0, 0, 0.4);
  2917. &:hover {
  2918. background: rgba(0, 33, 64, 0.95);
  2919. border-color: rgba(24, 144, 255, 0.6);
  2920. box-shadow: 0 6px 20px rgba(24, 144, 255, 0.4);
  2921. transform: translateY(-2px);
  2922. }
  2923. .card-icon-mini {
  2924. width: 44px;
  2925. height: 44px;
  2926. border-radius: 10px;
  2927. display: flex;
  2928. align-items: center;
  2929. justify-content: center;
  2930. font-size: 20px;
  2931. color: #fff;
  2932. flex-shrink: 0;
  2933. }
  2934. .card-info {
  2935. flex: 1;
  2936. .card-value-mini {
  2937. font-size: 22px;
  2938. font-weight: 700;
  2939. color: #fff;
  2940. line-height: 1;
  2941. margin-bottom: 4px;
  2942. }
  2943. .card-label-mini {
  2944. font-size: 12px;
  2945. color: rgba(255, 255, 255, 0.75);
  2946. white-space: nowrap;
  2947. }
  2948. }
  2949. }
  2950. }
  2951. // 响应式:小屏幕下调整卡片布局
  2952. @media (max-width: 1400px) {
  2953. .dashboard-summary-bottom {
  2954. max-width: calc(100% - 100px);
  2955. flex-wrap: wrap;
  2956. justify-content: center;
  2957. .overview-card-mini {
  2958. min-width: 140px;
  2959. padding: 12px 14px;
  2960. .card-icon-mini {
  2961. width: 38px;
  2962. height: 38px;
  2963. font-size: 18px;
  2964. }
  2965. .card-value-mini {
  2966. font-size: 18px !important;
  2967. }
  2968. .card-label-mini {
  2969. font-size: 11px !important;
  2970. }
  2971. }
  2972. }
  2973. }
  2974. // ============ 农事任务执行进度 ============
  2975. .task-progress-wrapper {
  2976. display: flex;
  2977. flex-direction: column;
  2978. gap: 12px;
  2979. flex: 1;
  2980. min-height: 0;
  2981. .task-chart-container {
  2982. flex: 1;
  2983. min-height: 140px;
  2984. width: 100%;
  2985. #taskProgressChart {
  2986. width: 100% !important;
  2987. height: 100% !important;
  2988. }
  2989. }
  2990. .task-stats-grid {
  2991. display: grid;
  2992. grid-template-columns: repeat(3, 1fr);
  2993. gap: 8px;
  2994. flex-shrink: 0;
  2995. .task-stat-item {
  2996. text-align: center;
  2997. padding: 8px;
  2998. background: rgba(24, 144, 255, 0.05);
  2999. border: 1px solid rgba(24, 144, 255, 0.15);
  3000. border-radius: 6px;
  3001. transition: all 0.3s ease;
  3002. &:hover {
  3003. background: rgba(24, 144, 255, 0.1);
  3004. border-color: rgba(24, 144, 255, 0.3);
  3005. }
  3006. .stat-value {
  3007. font-size: 18px;
  3008. font-weight: 700;
  3009. line-height: 1;
  3010. margin-bottom: 4px;
  3011. &.total {
  3012. color: #1890ff;
  3013. }
  3014. &.completed {
  3015. color: #52c41a;
  3016. }
  3017. &.pending {
  3018. color: #faad14;
  3019. }
  3020. }
  3021. .stat-label {
  3022. font-size: 11px;
  3023. color: rgba(255, 255, 255, 0.65);
  3024. }
  3025. }
  3026. }
  3027. }
  3028. </style>