chart.vue 68 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762176317641765176617671768176917701771177217731774177517761777177817791780178117821783178417851786178717881789179017911792179317941795179617971798179918001801180218031804180518061807180818091810181118121813181418151816181718181819182018211822182318241825182618271828182918301831183218331834183518361837183818391840184118421843184418451846184718481849185018511852185318541855185618571858185918601861186218631864186518661867186818691870187118721873187418751876187718781879188018811882188318841885188618871888188918901891189218931894189518961897189818991900190119021903190419051906190719081909191019111912191319141915191619171918191919201921192219231924192519261927192819291930193119321933193419351936193719381939194019411942194319441945194619471948194919501951195219531954195519561957195819591960196119621963196419651966196719681969197019711972197319741975197619771978197919801981198219831984198519861987198819891990199119921993199419951996199719981999200020012002200320042005
  1. <template>
  2. <div class="bootstrap-scope">
  3. <div class="widget-chart">
  4. <!-- 主容器 -->
  5. <div class="pcoded-main-container">
  6. <div class="pcoded-wrapper">
  7. <div class="pcoded-content">
  8. <div class="pcoded-inner-content">
  9. <div class="main-body">
  10. <div class="page-wrapper">
  11. <!-- 主要内容区域 -->
  12. <div class="row">
  13. <!-- 云计算存储卡片 -->
  14. <!-- <div class="col-xl-4 col-md-12">
  15. <div class="card">
  16. <div class="card-body pb-0">
  17. <div class="row">
  18. <div class="col-auto">
  19. <h6>云计算</h6>
  20. </div>
  21. </div>
  22. <div class="row mt-2">
  23. <div class="col-auto">
  24. <span class="d-block">
  25. 存储
  26. </span>
  27. <h6 class="ms-3 mt-1">10.5 GB</h6>
  28. </div>
  29. <div class="col">
  30. <span class="d-block">
  31. 带宽
  32. </span>
  33. <h6 class="ms-3 mt-1">50 GB</h6>
  34. </div>
  35. </div>
  36. </div>
  37. <div ref="storageChart" style="height: 100px"></div>
  38. </div>
  39. </div> -->
  40. <div class="col-xl-6">
  41. <div class="card">
  42. <div class="card-header">
  43. <div class="row">
  44. <div class="col">
  45. <h5>C/GPU使用率</h5>
  46. </div>
  47. <!-- <div class="col-auto text-end">
  48. <span>This Month</span>
  49. </div> -->
  50. </div>
  51. </div>
  52. <div class="card-body">
  53. <div class="row mb-2">
  54. <div class="col-auto">
  55. <h2 class="m-0">
  56. <strong>{{ cpuValue + "%"
  57. }}</strong>
  58. <i
  59. class="feather icon-arrow-up text-c-green"
  60. ></i>
  61. </h2>
  62. <span>CPU使用率</span>
  63. </div>
  64. <div class="col-auto">
  65. <h2 class="m-0">
  66. <strong>{{ GpuValue + "%"
  67. }}</strong><i
  68. class="feather icon-arrow-down text-c-red"
  69. ></i>
  70. </h2>
  71. <span>GPU使用率</span>
  72. </div>
  73. <!-- <div class="col">
  74. <h4 class="m-0">
  75. 40.05%<i
  76. class="feather icon-arrow-up text-c-green"
  77. ></i>
  78. </h4>
  79. <span>Bounce Rate</span>
  80. </div> -->
  81. </div>
  82. <div
  83. ref="siteVisitorChart"
  84. style="height: 245px; width: 100%"
  85. ></div>
  86. </div>
  87. </div>
  88. </div>
  89. <div class="col-xl-3">
  90. <div class="row">
  91. <!-- GPU温度 -->
  92. <div class="col-xl-12 col-md-6">
  93. <div class="card">
  94. <div class="card-body">
  95. <div
  96. class="row"
  97. style="display: flex; align-items: center"
  98. >
  99. <div class="col-auto">
  100. <h6>温度监控</h6>
  101. </div>
  102. </div>
  103. <div class="row">
  104. <!-- CPU温度 -->
  105. <div class="col-6">
  106. <h2 class="mb-0" style="margin-top: 20px !important">
  107. {{ cpuTemperature }}°C
  108. </h2>
  109. <span>CPU温度</span>
  110. <div class="temperature-select-wrapper" style="margin-top: 15px">
  111. <select
  112. class="temperature-select"
  113. :value="Math.round(cpuTemperature / temperatureData.cpu.critical)"
  114. disabled
  115. >
  116. <option v-for="n in 24" :key="`cpu-${n}`" :value="n"></option>
  117. </select>
  118. <div
  119. class="temperature-progress"
  120. :style="{
  121. width: `${(cpuTemperature / temperatureData.cpu.critical) * 100}%`,
  122. backgroundColor: getTemperatureColor(cpuTemperature, 'cpu'),
  123. }"
  124. ></div>
  125. </div>
  126. </div>
  127. <!-- GPU温度 -->
  128. <div class="col-6">
  129. <h2 class="mb-0" style="margin-top: 20px !important">
  130. {{ gpuTemperature }}°C
  131. </h2>
  132. <span>GPU温度</span>
  133. <div class="temperature-select-wrapper" style="margin-top: 15px">
  134. <select
  135. class="temperature-select"
  136. :value="Math.round(gpuTemperature / temperatureData.gpu.critical)"
  137. disabled
  138. >
  139. <option v-for="n in 24" :key="`gpu-${n}`" :value="n"></option>
  140. </select>
  141. <div
  142. class="temperature-progress"
  143. :style="{
  144. width: `${(gpuTemperature / temperatureData.gpu.critical) * 100}%`,
  145. backgroundColor: getTemperatureColor(gpuTemperature, 'gpu'),
  146. }"
  147. ></div>
  148. </div>
  149. </div>
  150. </div>
  151. </div>
  152. </div>
  153. </div>
  154. <!-- 磁盘信息 -->
  155. <div class="col-xl-12 col-md-6">
  156. <div class="card">
  157. <div class="card-body">
  158. <div class="row">
  159. <div class="col-6">
  160. <h6>磁盘信息</h6>
  161. </div>
  162. </div>
  163. <div class="row">
  164. <div class="col-12">
  165. <div
  166. ref="diskChart"
  167. style="height: 110px"
  168. ></div>
  169. </div>
  170. </div>
  171. </div>
  172. </div>
  173. </div>
  174. <!-- <div class="col-xl-12 col-md-6">
  175. <div class="card">
  176. <div class="card-body">
  177. <h6>Traffic tracker</h6>
  178. <div class="row mt-2">
  179. <div class="col-6">
  180. <span class="d-block text-uppercase"
  181. >Inbound</span
  182. >
  183. <h3 class="mt-1">180 GB</h3>
  184. <div
  185. ref="transactions1"
  186. style="
  187. height: 50px;
  188. width: 100%;
  189. margin: 0 auto;
  190. "
  191. ></div>
  192. </div>
  193. <div class="col-6">
  194. <span class="d-block text-uppercase"
  195. >Outbound</span
  196. >
  197. <h3 class="mt-1">597.1 GB</h3>
  198. <div
  199. ref="transactions2"
  200. style="
  201. height: 50px;
  202. width: 100%;
  203. margin: 0 auto;
  204. "
  205. ></div>
  206. </div>
  207. </div>
  208. </div>
  209. </div>
  210. </div>
  211. <div class="col-xl-12 col-md-6">
  212. <div class="card">
  213. <div class="card-body">
  214. <div class="row d-flex align-items-center">
  215. <div class="col-6 p-r-0">
  216. <span class="d-block mb-1"
  217. ><i
  218. class="fas fa-circle f-10 m-r-5"
  219. style="color: #a367dc"
  220. ></i
  221. >Desktop [66.6%]</span
  222. >
  223. <span class="d-block mb-1"
  224. ><i
  225. class="fas fa-circle f-10 m-r-5"
  226. style="color: #67b7dc"
  227. ></i
  228. >Mobile [29.7%]</span
  229. >
  230. <span class="d-block"
  231. ><i
  232. class="fas fa-circle f-10 m-r-5"
  233. style="color: #6771dc"
  234. ></i
  235. >Tablet [38.6%]</span
  236. >
  237. </div>
  238. <div class="col-6">
  239. <div
  240. ref="deviceChart"
  241. id="device-chart"
  242. style="height: 140px"
  243. ></div>
  244. </div>
  245. </div>
  246. </div>
  247. </div>
  248. </div> -->
  249. </div>
  250. </div>
  251. <div class="col-xl-3">
  252. <div class="row">
  253. <!-- 支持分析卡片 -->
  254. <div class="col-xl-12 col-md-6">
  255. <div class="card">
  256. <div class="card-body">
  257. <div class="row">
  258. <div class="col-6">
  259. <h6>Memory</h6>
  260. </div>
  261. <!-- <div class="col">
  262. <div class="dropdown float-end">
  263. <a
  264. class="dropdown-toggle text-c-blue"
  265. href="#"
  266. data-bs-toggle="dropdown"
  267. >
  268. 最近5个月
  269. </a>
  270. <div class="dropdown-menu dropdown-menu-end">
  271. <a class="dropdown-item" href="#">1周</a>
  272. <a class="dropdown-item" href="#">2年</a>
  273. <a class="dropdown-item" href="#">3个月</a>
  274. </div>
  275. </div>
  276. </div> -->
  277. </div>
  278. <div class="row">
  279. <div class="col-6">
  280. <h2 class="mt-2 mb-0">
  281. {{ currentPercentage.total + "GB" }}
  282. </h2>
  283. <span>总内存</span>
  284. <h6 class="mb-0 mt-3">
  285. 空闲内存:
  286. <span class="text-primary">{{
  287. currentPercentage.available + "GB"
  288. }}</span>
  289. </h6>
  290. <h6>
  291. 已使用:
  292. <span class="text-danger">{{
  293. currentPercentage.used + "GB"
  294. }}</span>
  295. </h6>
  296. </div>
  297. <div class="col">
  298. <!-- 支持工单完成图表 -->
  299. <div
  300. ref="hdComplitedTicket"
  301. style="height: 100px; width: 100%"
  302. ></div>
  303. </div>
  304. </div>
  305. </div>
  306. </div>
  307. </div>
  308. <!-- 知识库统计卡片 -->
  309. <div class="col-xl-12 col-md-6">
  310. <div class="card">
  311. <div class="card-body">
  312. <div
  313. class="row"
  314. style="display: flex; align-items: center"
  315. >
  316. <div class="col-auto">
  317. <h6>知识库统计</h6>
  318. </div>
  319. <div
  320. class="col"
  321. style="
  322. display: flex;
  323. justify-content: flex-end;
  324. "
  325. >
  326. <el-select
  327. v-model="kenValue"
  328. @change="kenChange"
  329. placeholder="请选择"
  330. >
  331. <el-option
  332. v-for="item in kenList"
  333. :key="item.id"
  334. :label="item.name"
  335. :value="item.id"
  336. >
  337. </el-option>
  338. </el-select>
  339. <!-- <div class="dropdown float-end">
  340. <a
  341. class="dropdown-toggle text-c-blue"
  342. href="#"
  343. data-bs-toggle="dropdown"
  344. >
  345. 最近30天
  346. </a>
  347. <div
  348. class="dropdown-menu dropdown-menu-end"
  349. >
  350. <a class="dropdown-item" href="#">1周</a>
  351. <a class="dropdown-item" href="#">2年</a>
  352. <a class="dropdown-item" href="#"
  353. >3个月</a
  354. >
  355. </div>
  356. </div> -->
  357. </div>
  358. </div>
  359. <div class="row">
  360. <div class="col-6 p-r-0">
  361. <template
  362. v-for="(count, type) in fileTypeCounts"
  363. >
  364. <h6
  365. :key="type"
  366. class="my-3"
  367. v-if="
  368. ['pdf', 'docx', 'xlsx'].includes(
  369. type.toLowerCase()
  370. )
  371. "
  372. >
  373. <i
  374. class="feather"
  375. :class="getFileTypeIcon(type)"
  376. style="
  377. font-size: 20px;
  378. margin-right: 8px;
  379. "
  380. :style="{
  381. color: getFileTypeColor(type),
  382. }"
  383. ></i>
  384. {{ type.toUpperCase() }}
  385. <span class="ms-2"> </span>
  386. <span
  387. :class="getChangeClass(type)"
  388. class="ms-2 f-14"
  389. >
  390. <i
  391. class="feather"
  392. :class="getChangeIcon(type)"
  393. ></i>
  394. {{ count }}
  395. <!-- {{ getChangePercentage(type) }} -->
  396. </span>
  397. </h6>
  398. </template>
  399. </div>
  400. <div class="col-6">
  401. <!-- 设备占比饼图 -->
  402. <div
  403. ref="chartPercent"
  404. style="height: 100px"
  405. ></div>
  406. </div>
  407. </div>
  408. </div>
  409. </div>
  410. </div>
  411. <!-- <div class="col-xl-12 col-md-6">
  412. <div class="card">
  413. <div class="card-body">
  414. <h6>Traffic tracker</h6>
  415. <div class="row mt-2">
  416. <div class="col-6">
  417. <span class="d-block text-uppercase"
  418. >Inbound</span
  419. >
  420. <h3 class="mt-1">180 GB</h3>
  421. <div
  422. ref="transactions1"
  423. style="
  424. height: 50px;
  425. width: 100%;
  426. margin: 0 auto;
  427. "
  428. ></div>
  429. </div>
  430. <div class="col-6">
  431. <span class="d-block text-uppercase"
  432. >Outbound</span
  433. >
  434. <h3 class="mt-1">597.1 GB</h3>
  435. <div
  436. ref="transactions2"
  437. style="
  438. height: 50px;
  439. width: 100%;
  440. margin: 0 auto;
  441. "
  442. ></div>
  443. </div>
  444. </div>
  445. </div>
  446. </div>
  447. </div>
  448. <div class="col-xl-12 col-md-6">
  449. <div class="card">
  450. <div class="card-body">
  451. <div class="row d-flex align-items-center">
  452. <div class="col-6 p-r-0">
  453. <span class="d-block mb-1"
  454. ><i
  455. class="fas fa-circle f-10 m-r-5"
  456. style="color: #a367dc"
  457. ></i
  458. >Desktop [66.6%]</span
  459. >
  460. <span class="d-block mb-1"
  461. ><i
  462. class="fas fa-circle f-10 m-r-5"
  463. style="color: #67b7dc"
  464. ></i
  465. >Mobile [29.7%]</span
  466. >
  467. <span class="d-block"
  468. ><i
  469. class="fas fa-circle f-10 m-r-5"
  470. style="color: #6771dc"
  471. ></i
  472. >Tablet [38.6%]</span
  473. >
  474. </div>
  475. <div class="col-6">
  476. <div
  477. ref="deviceChart"
  478. id="device-chart"
  479. style="height: 140px"
  480. ></div>
  481. </div>
  482. </div>
  483. </div>
  484. </div>
  485. </div> -->
  486. </div>
  487. </div>
  488. <!-- 其他统计卡片组 -->
  489. <div class="col-sm-12">
  490. <div class="row g-0">
  491. <!-- 交易量卡片 -->
  492. <div class="col-md-4 col-xl-2 col-sm-6">
  493. <div class="card">
  494. <div class="card-body">
  495. <span>知识库数量</span>
  496. <h2><strong>{{ kenList.length }}</strong></h2>
  497. <div
  498. ref="real4Chart"
  499. style="height: 20px; width: 100%"
  500. ></div>
  501. </div>
  502. </div>
  503. </div>
  504. <!-- 当前库存卡片 -->
  505. <div class="col-md-4 col-xl-2 col-sm-6">
  506. <div class="card">
  507. <div class="card-body">
  508. <span>文件已解析数量</span>
  509. <h2><strong>{{ docList.run_3 }}</strong></h2>
  510. <div
  511. ref="real6Chart"
  512. style="height: 20px; width: 100%"
  513. ></div>
  514. </div>
  515. </div>
  516. </div>
  517. <!-- 跳出率卡片 -->
  518. <div class="col-md-4 col-xl-2 col-sm-6">
  519. <div class="card">
  520. <div class="card-body">
  521. <span>文件未解析数量</span>
  522. <h2><strong>{{ docList.run_0 }}</strong></h2>
  523. <div
  524. ref="real1Chart"
  525. style="height: 20px; width: 100%"
  526. ></div>
  527. </div>
  528. </div>
  529. </div>
  530. <!-- 订单总额卡片 -->
  531. <div class="col-md-4 col-xl-2 col-sm-6">
  532. <div class="card">
  533. <div class="card-body">
  534. <span>文档数量</span>
  535. <h2><strong>{{ docTotal }}</strong></h2>
  536. <div
  537. ref="real5Chart"
  538. style="height: 20px; width: 100%"
  539. ></div>
  540. </div>
  541. </div>
  542. </div>
  543. <!-- 收入卡片 -->
  544. <div class="col-md-4 col-xl-2 col-sm-6">
  545. <div class="card">
  546. <div class="card-body">
  547. <span>模版数量</span>
  548. <h2><strong>{{ temTotal }}</strong></h2>
  549. <div
  550. ref="real2Chart"
  551. style="height: 20px; width: 100%"
  552. ></div>
  553. </div>
  554. </div>
  555. </div>
  556. <!-- 新购买卡片 -->
  557. <div class="col-md-4 col-xl-2 col-sm-6">
  558. <div class="card">
  559. <div class="card-body">
  560. <span>模块数量</span>
  561. <h2><strong>{{ pageTotal }}</strong></h2>
  562. <div
  563. ref="real3Chart"
  564. style="height: 20px; width: 100%"
  565. ></div>
  566. </div>
  567. </div>
  568. </div>
  569. </div>
  570. </div>
  571. </div>
  572. </div>
  573. </div>
  574. </div>
  575. </div>
  576. </div>
  577. </div>
  578. </div>
  579. </div>
  580. </template>
  581. <script>
  582. import * as am4core from "@amcharts/amcharts4/core";
  583. import * as am4charts from "@amcharts/amcharts4/charts";
  584. import am4themes_animated from "@amcharts/amcharts4/themes/animated";
  585. import BootstrapWrapper from "@/components/BootstrapWrapper/index.vue";
  586. import { searchlistDoc, searchList } from "@/api/document";
  587. import { pageTemplate } from "@/api/template";
  588. import {
  589. listBuckets,
  590. structure_count,
  591. GET_Rbq,
  592. gpu_info,
  593. doc_status,
  594. } from "@/api/knowledge";
  595. export default {
  596. name: "WidgetChart",
  597. components: {
  598. BootstrapWrapper,
  599. },
  600. data() {
  601. return {
  602. charts: {
  603. diskChart: null,
  604. // 其他图表...
  605. },
  606. chartData: {},
  607. currentPercentage: {},
  608. cpuValue: 0,
  609. GpuValue: 0,
  610. kenValue: null, //知识库
  611. kenList: [],
  612. previousCounts: {}, // 用于存储上一次的数量,计算变化率
  613. fileTypeCounts: {}, // 用于存储上一次的数量,计算变化率
  614. docTotal: 0,
  615. temTotal: 0,
  616. pageTotal: 0,
  617. docList: {},
  618. chartUpdateInterval: null, // 添加新的数据属性用于存储定时器
  619. isPageVisible: !document.hidden, // 添加新的数据属性
  620. cpuTemperature: 0,
  621. gpuTemperature: 0,
  622. temperatureData: {
  623. cpu: {
  624. current: 0,
  625. high: 95, // 默认CPU高温阈值
  626. critical: 100 // 默认CPU临界温度
  627. },
  628. gpu: {
  629. current: 0,
  630. high: 85, // 默认GPU高温阈值
  631. critical: 90 // 默认GPU临界温度
  632. }
  633. },
  634. isCurrentPage: false, // 添加标记当前页面状态的变量
  635. };
  636. },
  637. watch: {
  638. $route: {
  639. immediate: true,
  640. handler(newRoute) {
  641. const isDashboard = newRoute.path === "/dashboard";
  642. if (isDashboard && !this.isCurrentPage) {
  643. // 进入dashboard页面
  644. this.isCurrentPage = true;
  645. this.initWebSocket();
  646. } else if (!isDashboard && this.isCurrentPage) {
  647. // 离开dashboard页面
  648. this.isCurrentPage = false;
  649. this.closeWebSocket();
  650. }
  651. },
  652. },
  653. // 监听页面可见性
  654. isPageVisible: {
  655. immediate: true,
  656. handler(newValue) {
  657. console.log(newValue);
  658. if (newValue) {
  659. // 页面变为可见时
  660. console.log("Page became visible");
  661. this.initSiteVisitorChart();
  662. } else {
  663. // 页面变为不可见时
  664. console.log("Page became hidden");
  665. this.cleanupCharts();
  666. }
  667. },
  668. },
  669. },
  670. mounted() {
  671. document.addEventListener("visibilitychange", this.handleVisibilityChange);
  672. // 使用动画主题
  673. am4core.useTheme(am4themes_animated);
  674. this.init();
  675. // 初始化各个图表
  676. this.initHdComplitedTicket();
  677. this.initChartPercent();
  678. this.initStorageChart();
  679. this.initRealTimeCharts();
  680. this.initSiteVisitorChart(); // 添加这行
  681. this.initTransactionCharts();
  682. this.initDeviceChart(); // 添加这行
  683. this.initDiskChart();
  684. },
  685. // 修改生命周期钩子
  686. activated() {
  687. console.log(1123);
  688. // 当组件被激活时重新初始化图表
  689. this.initSiteVisitorChart();
  690. },
  691. deactivated() {
  692. // 当组件被停用时清理资源
  693. this.cleanupCharts();
  694. },
  695. beforeDestroy() {
  696. // 组件销毁前清理所有资源
  697. this.cleanupCharts();
  698. this.closeWebSocket();
  699. // 清理其他图表实例
  700. Object.values(this.charts).forEach((chart) => {
  701. if (chart) {
  702. if (typeof chart.dispose === "function") {
  703. chart.dispose();
  704. } else if (typeof chart === "number") {
  705. clearInterval(chart);
  706. }
  707. }
  708. });
  709. },
  710. methods: {
  711. // 处理页面可见性变化
  712. handleVisibilityChange() {
  713. this.isPageVisible = !document.hidden;
  714. },
  715. closeWebSocket() {
  716. if (this.ws) {
  717. this.ws.close();
  718. this.ws = null;
  719. }
  720. // 清除重连定时器
  721. if (this.wsReconnectTimer) {
  722. clearTimeout(this.wsReconnectTimer);
  723. this.wsReconnectTimer = null;
  724. }
  725. },
  726. handleRouteChange() {
  727. if (this.isOnDashboard) {
  728. this.initWebSocket(); // 初始化 WebSocket
  729. } else {
  730. this.closeWebSocket();
  731. }
  732. },
  733. /* websocket 请求 */
  734. initWebSocket() {
  735. // 如果不是当前页面或已经存在连接,则不建立新连接
  736. if (!this.isCurrentPage || this.ws) {
  737. return;
  738. }
  739. const wsUrl = process.env.VUE_APP_BASE_API.replace(/^http/, "ws");
  740. this.ws = new WebSocket(`${wsUrl}/ws/monitoring`);
  741. this.ws.onopen = () => {
  742. console.log("WebSocket连接成功");
  743. };
  744. this.ws.onmessage = (event) => {
  745. if (!this.isCurrentPage) return; // 如果不是当前页面,不处理消息
  746. try {
  747. const data = JSON.parse(event.data);
  748. // 检查数据结构是否完整
  749. /* console.log("gpuTemperature", this.gpuTemperature); */
  750. if (
  751. data &&
  752. data.data &&
  753. data.data.system &&
  754. data.data.system.cpu &&
  755. data.data.system.cpu.memory
  756. ) {
  757. // 更新图表数据
  758. this.chartData = data;
  759. this.cpuValue = data.data.system.cpu.total_usage;
  760. this.GpuValue = data.data.system.gpu.devices[0]?.power || 0;
  761. // 更新 GPU 温度
  762. /* this.gpuTemperature =
  763. data.data.system.gpu.devices[0]?.temperature || 0; */
  764. this.currentPercentage = data.data.system.cpu.memory;
  765. this.handleWebSocketMessage(data)
  766. } else {
  767. console.warn("Received incomplete data structure:", data);
  768. }
  769. } catch (error) {
  770. console.error("Error processing WebSocket message:", error);
  771. }
  772. };
  773. this.ws.onclose = () => {
  774. console.log("WebSocket连接关闭");
  775. // 只在当前页面时进行重连
  776. if (this.isCurrentPage && !this.wsReconnectTimer) {
  777. this.wsReconnectTimer = setTimeout(() => {
  778. this.initWebSocket();
  779. }, 3000);
  780. }
  781. };
  782. this.ws.onerror = (error) => {
  783. console.error("WebSocket连接错误:", error);
  784. // 连接错误时关闭连接,触发重连
  785. this.closeWebSocket();
  786. };
  787. },
  788. // 添加一个用于检查 WebSocket 连接状态的方法
  789. checkWebSocketConnection() {
  790. if (!this.ws || this.ws.readyState !== WebSocket.OPEN) {
  791. console.log("WebSocket disconnected, attempting to reconnect...");
  792. this.initWebSocket();
  793. }
  794. },
  795. // 初始化支持工单完成率图表
  796. initHdComplitedTicket() {
  797. const chart = am4core.create(
  798. this.$refs.hdComplitedTicket,
  799. am4charts.GaugeChart
  800. );
  801. chart.logo.disabled = true;
  802. chart.innerRadius = am4core.percent(82);
  803. // 修改主配置
  804. const axis = chart.xAxes.push(new am4charts.ValueAxis());
  805. axis.min = 0;
  806. axis.max = 100;
  807. axis.strictMinMax = true;
  808. // 设置固定的半径
  809. axis.renderer.radius = 80; // 使用固定像素值而不是百分比
  810. axis.renderer.inside = true;
  811. axis.renderer.line.strokeOpacity = 0;
  812. axis.renderer.ticks.template.strokeOpacity = 1;
  813. axis.renderer.ticks.template.length = 10;
  814. axis.renderer.grid.template.disabled = true;
  815. axis.renderer.labels.template.radius = 40;
  816. axis.renderer.labels.template.adapter.add("text", () => "");
  817. // 修改第二轴配置
  818. const axis2 = chart.xAxes.push(new am4charts.ValueAxis());
  819. axis2.min = 0;
  820. axis2.max = 100;
  821. // 设置固定的内半径
  822. axis2.renderer.innerRadius = 65; // 使用固定像素值
  823. axis2.renderer.radius = 80; // 设置固定的外半径
  824. axis2.strictMinMax = true;
  825. axis2.renderer.labels.template.disabled = true;
  826. axis2.renderer.ticks.template.disabled = true;
  827. axis2.renderer.grid.template.disabled = true;
  828. // 配置填充范围
  829. const range0 = axis2.axisRanges.create();
  830. range0.value = 0;
  831. range0.endValue = 50;
  832. range0.axisFill.fillOpacity = 1;
  833. range0.axisFill.fill = am4core.color("#19BCBF");
  834. const range1 = axis2.axisRanges.create();
  835. range1.value = 50;
  836. range1.endValue = 100;
  837. range1.axisFill.fillOpacity = 1;
  838. range1.axisFill.fill = am4core.color("#eff3f6");
  839. // 添加标签
  840. const label = chart.radarContainer.createChild(am4core.Label);
  841. label.isMeasured = false;
  842. label.fontSize = 18;
  843. label.x = am4core.percent(50);
  844. label.y = am4core.percent(100);
  845. label.horizontalCenter = "middle";
  846. label.verticalCenter = "bottom";
  847. label.text = "50%";
  848. const label2 = chart.radarContainer.createChild(am4core.Label);
  849. label2.isMeasured = false;
  850. label2.fontSize = 12;
  851. label2.x = am4core.percent(50);
  852. label2.y = am4core.percent(100);
  853. label2.horizontalCenter = "middle";
  854. label2.verticalCenter = "top";
  855. label2.text = "Complited Ticket";
  856. // 配置指针
  857. const hand = chart.hands.push(new am4charts.ClockHand());
  858. hand.axis = axis2;
  859. hand.innerRadius = am4core.percent(20);
  860. hand.startWidth = 0;
  861. hand.pin.disabled = true;
  862. hand.value = 50;
  863. hand.disabled = true;
  864. // 更新事件处理
  865. hand.events.on("propertychanged", (ev) => {
  866. range0.endValue = ev.target.value;
  867. range1.value = ev.target.value;
  868. axis2.invalidate();
  869. });
  870. // 设置图表容器的固定大小
  871. //chart.width = 160; // 设置固定宽度
  872. // 定时更新数据
  873. const interval = setInterval(() => {
  874. const value = this.currentPercentage.percent; // Math.round(Math.random() * 100);
  875. label.text = value + "%";
  876. new am4core.Animation(
  877. hand,
  878. {
  879. property: "value",
  880. to: value,
  881. },
  882. 1000,
  883. am4core.ease.cubicOut
  884. ).start();
  885. }, 2000);
  886. chart.padding(0, 0, 0, 0);
  887. // 保存表实例和定时器
  888. this.charts.hdComplitedTicket = {
  889. chart: chart,
  890. interval: interval,
  891. };
  892. },
  893. // 初始化设备占比饼图
  894. initChartPercent() {
  895. const chart = am4core.create(this.$refs.chartPercent, am4charts.PieChart);
  896. chart.logo.disabled = true;
  897. chart.innerRadius = 30;
  898. // 配置系列
  899. const pieSeries = chart.series.push(new am4charts.PieSeries());
  900. pieSeries.dataFields.value = "size";
  901. pieSeries.dataFields.category = "sector";
  902. pieSeries.labels.template.disabled = true;
  903. pieSeries.ticks.template.disabled = true;
  904. pieSeries.colors.step = 3;
  905. this.charts.chartPercent = chart;
  906. },
  907. // 初始化存储使用趋势图
  908. initStorageChart() {
  909. const chart = am4core.create(this.$refs.storageChart, am4charts.XYChart);
  910. // 添加数据
  911. chart.data = [
  912. {
  913. date: "2012-03-01",
  914. price: 180,
  915. price1: 50,
  916. },
  917. {
  918. date: "2012-03-02",
  919. price: 252,
  920. price1: 40,
  921. },
  922. {
  923. date: "2012-03-03",
  924. price: 185,
  925. price1: 55,
  926. },
  927. {
  928. date: "2012-03-04",
  929. price: 110,
  930. price1: 38,
  931. },
  932. {
  933. date: "2012-03-05",
  934. price: 158,
  935. price1: 87,
  936. },
  937. {
  938. date: "2012-03-06",
  939. price: 200,
  940. price1: 67,
  941. },
  942. {
  943. date: "2012-03-07",
  944. price: 187,
  945. price1: 100,
  946. },
  947. ];
  948. // 配置X轴
  949. const dateAxis = chart.xAxes.push(new am4charts.DateAxis());
  950. dateAxis.renderer.grid.template.location = 0;
  951. dateAxis.renderer.grid.template.disabled = true;
  952. dateAxis.startLocation = 0.6;
  953. dateAxis.endLocation = 0.4;
  954. dateAxis.renderer.inside = true;
  955. dateAxis.renderer.labels.template.fill = am4core.color("#463699");
  956. // 配置Y轴
  957. const valueAxis = chart.yAxes.push(new am4charts.ValueAxis());
  958. valueAxis.logarithmic = true;
  959. valueAxis.renderer.minGridDistance = 0;
  960. valueAxis.renderer.inside = true;
  961. valueAxis.renderer.labels.template.disabled = true;
  962. // 配置第一个数据系列
  963. const series = chart.series.push(new am4charts.LineSeries());
  964. series.dataFields.valueY = "price";
  965. series.dataFields.dateX = "date";
  966. series.tensionX = 0.77;
  967. series.strokeWidth = 2;
  968. series.fillOpacity = 0.1;
  969. series.tooltipText = "{valueY.value}";
  970. series.fill = am4core.color("#19BCBF");
  971. series.stroke = am4core.color("#19BCBF");
  972. // 配置第二个数据系列
  973. const series1 = chart.series.push(new am4charts.LineSeries());
  974. series1.dataFields.valueY = "price1";
  975. series1.dataFields.dateX = "date";
  976. series1.tensionX = 0.77;
  977. series1.strokeWidth = 2;
  978. series1.fillOpacity = 0.1;
  979. series1.tooltipText = "{valueY.value}";
  980. series1.fill = am4core.color("#463699");
  981. series1.stroke = am4core.color("#463699");
  982. // 添加第一个系列的圆点
  983. const bullet = series.bullets.push(new am4charts.CircleBullet());
  984. bullet.circle.fill = am4core.color("#fff");
  985. bullet.circle.strokeWidth = 3;
  986. // 添加第二个系列的圆点
  987. const bullet1 = series1.bullets.push(new am4charts.CircleBullet());
  988. bullet1.circle.fill = am4core.color("#fff");
  989. bullet1.circle.strokeWidth = 3;
  990. // 添加光标
  991. chart.cursor = new am4charts.XYCursor();
  992. chart.cursor.fullWidthLineX = true;
  993. chart.cursor.xAxis = dateAxis;
  994. chart.cursor.lineX.strokeWidth = 0;
  995. chart.cursor.lineX.fill = am4core.color("#fff");
  996. chart.cursor.lineX.fillOpacity = 0;
  997. chart.padding(0, 0, 0, 0);
  998. this.charts.storageChart = chart;
  999. },
  1000. // 修改initSiteVisitorChart方法
  1001. initSiteVisitorChart() {
  1002. // 如果已存在图表实例,先销毁它
  1003. if (this.charts.siteVisitorChart) {
  1004. this.charts.siteVisitorChart.dispose();
  1005. }
  1006. const chart = am4core.create(
  1007. this.$refs.siteVisitorChart,
  1008. am4charts.XYChart
  1009. );
  1010. chart.logo.disabled = true;
  1011. chart.hiddenState.properties.opacity = 0;
  1012. chart.padding(0, 0, 0, 0);
  1013. chart.zoomOutButton.disabled = true;
  1014. // 初始化数据
  1015. const data = [];
  1016. let visits = 10;
  1017. for (let i = 0; i <= 30; i++) {
  1018. visits -= Math.round(
  1019. (Math.random() < 0.5 ? 1 : -1) * Math.random() * 10
  1020. );
  1021. data.push({
  1022. date: new Date().setSeconds(i - 30),
  1023. cpuValue: this.cpuValue || 0, //Math.abs(visits),
  1024. gpuValue: this.GpuValue || 0,
  1025. });
  1026. }
  1027. chart.data = data;
  1028. // 配置X轴
  1029. const dateAxis = chart.xAxes.push(new am4charts.DateAxis());
  1030. dateAxis.renderer.grid.template.location = 0;
  1031. dateAxis.renderer.minGridDistance = 30;
  1032. dateAxis.dateFormats.setKey("second", "ss");
  1033. dateAxis.periodChangeDateFormats.setKey("second", "[bold]h:mm a");
  1034. dateAxis.periodChangeDateFormats.setKey("minute", "[bold]h:mm a");
  1035. dateAxis.periodChangeDateFormats.setKey("hour", "[bold]h:mm a");
  1036. dateAxis.renderer.inside = true;
  1037. dateAxis.renderer.axisFills.template.disabled = true;
  1038. dateAxis.renderer.ticks.template.disabled = true;
  1039. dateAxis.interpolationDuration = 500;
  1040. dateAxis.rangeChangeDuration = 500;
  1041. // 配置Y轴
  1042. const valueAxis = chart.yAxes.push(new am4charts.ValueAxis());
  1043. valueAxis.tooltip.disabled = true;
  1044. valueAxis.interpolationDuration = 500;
  1045. valueAxis.rangeChangeDuration = 500;
  1046. valueAxis.renderer.inside = true;
  1047. valueAxis.renderer.minLabelPosition = 0.05;
  1048. valueAxis.renderer.maxLabelPosition = 0.95;
  1049. valueAxis.renderer.axisFills.template.disabled = true;
  1050. valueAxis.renderer.ticks.template.disabled = true;
  1051. // CPU 数据系列(红色)
  1052. const cpuSeries = chart.series.push(new am4charts.LineSeries());
  1053. cpuSeries.dataFields.dateX = "date";
  1054. cpuSeries.dataFields.valueY = "cpuValue";
  1055. cpuSeries.interpolationDuration = 500;
  1056. cpuSeries.defaultState.transitionDuration = 0;
  1057. cpuSeries.tensionX = 0.8;
  1058. cpuSeries.stroke = am4core.color("#FF425C"); // 红色
  1059. cpuSeries.strokeWidth = 2;
  1060. // CPU 系列渐变
  1061. cpuSeries.fillOpacity = 1;
  1062. const cpuGradient = new am4core.LinearGradient();
  1063. cpuGradient.addColor(am4core.color("#FF425C"), 0.2);
  1064. cpuGradient.addColor(am4core.color("#FF425C"), 0);
  1065. cpuSeries.fill = cpuGradient;
  1066. // GPU 数据系列(蓝色)
  1067. const gpuSeries = chart.series.push(new am4charts.LineSeries());
  1068. gpuSeries.dataFields.dateX = "date";
  1069. gpuSeries.dataFields.valueY = "gpuValue";
  1070. gpuSeries.interpolationDuration = 500;
  1071. gpuSeries.defaultState.transitionDuration = 0;
  1072. gpuSeries.tensionX = 0.8;
  1073. gpuSeries.stroke = am4core.color("#19BCBF"); // 蓝色
  1074. gpuSeries.strokeWidth = 2;
  1075. // GPU 系列渐变
  1076. gpuSeries.fillOpacity = 1;
  1077. const gpuGradient = new am4core.LinearGradient();
  1078. gpuGradient.addColor(am4core.color("#19BCBF"), 0.2);
  1079. gpuGradient.addColor(am4core.color("#19BCBF"), 0);
  1080. gpuSeries.fill = gpuGradient;
  1081. // CPU 圆点
  1082. const cpuBullet = cpuSeries.createChild(am4charts.CircleBullet);
  1083. cpuBullet.circle.radius = 5;
  1084. cpuBullet.fillOpacity = 1;
  1085. cpuBullet.fill = am4core.color("#FF425C");
  1086. cpuBullet.isMeasured = false;
  1087. // GPU 圆点
  1088. const gpuBullet = gpuSeries.createChild(am4charts.CircleBullet);
  1089. gpuBullet.circle.radius = 5;
  1090. gpuBullet.fillOpacity = 1;
  1091. gpuBullet.fill = am4core.color("#19BCBF");
  1092. gpuBullet.isMeasured = false;
  1093. // 更新两个系列圆点位置
  1094. cpuSeries.events.on("validated", () => {
  1095. cpuBullet.moveTo(cpuSeries.dataItems.last.point);
  1096. cpuBullet.validatePosition();
  1097. });
  1098. gpuSeries.events.on("validated", () => {
  1099. gpuBullet.moveTo(gpuSeries.dataItems.last.point);
  1100. gpuBullet.validatePosition();
  1101. });
  1102. // 设置初始缩放
  1103. chart.events.on("datavalidated", () => {
  1104. dateAxis.zoom({ start: 1 / 15, end: 1.2 }, false, true);
  1105. });
  1106. // 存储图表实例
  1107. this.charts.siteVisitorChart = chart;
  1108. /* // 启动数据更新
  1109. setTimeout(() => {
  1110. this.startVisitorDataInterval(cpuSeries, gpuSeries);
  1111. }, 100); */
  1112. // 修改数据更新逻辑
  1113. this.startVisitorDataInterval(cpuSeries, gpuSeries);
  1114. },
  1115. // 修改 startVisitorDataInterval 方法
  1116. startVisitorDataInterval(cpuSeries, gpuSeries) {
  1117. // 清除已存在的定时器
  1118. if (this.chartUpdateInterval) {
  1119. clearInterval(this.chartUpdateInterval);
  1120. }
  1121. this.chartUpdateInterval = setInterval(() => {
  1122. const lastDataItem = cpuSeries.dataItems.getIndex(
  1123. cpuSeries.dataItems.length - 1
  1124. );
  1125. if (lastDataItem && lastDataItem.dateX) {
  1126. cpuSeries.chart.addData(
  1127. {
  1128. date: new Date(lastDataItem.dateX.getTime() + 1000),
  1129. cpuValue: this.cpuValue || 0,
  1130. gpuValue: this.GpuValue || 0,
  1131. },
  1132. 1
  1133. );
  1134. }
  1135. }, 1000);
  1136. },
  1137. // 添加清理方法
  1138. cleanupCharts() {
  1139. // 清除定时器
  1140. if (this.chartUpdateInterval) {
  1141. clearInterval(this.chartUpdateInterval);
  1142. this.chartUpdateInterval = null;
  1143. }
  1144. // 清除图表实例
  1145. if (this.charts.siteVisitorChart) {
  1146. this.charts.siteVisitorChart.dispose();
  1147. this.charts.siteVisitorChart = null;
  1148. }
  1149. },
  1150. handleVisitorChartVisibility() {
  1151. if (document.hidden) {
  1152. if (this.charts.siteVisitorInterval) {
  1153. clearInterval(this.charts.siteVisitorInterval);
  1154. }
  1155. } else {
  1156. const series = this.charts.siteVisitorChart.series.getIndex(0);
  1157. this.startVisitorDataInterval(series);
  1158. }
  1159. },
  1160. // 初始化实时数据小图表
  1161. initRealTimeCharts() {
  1162. // 交易量图表
  1163. const real4Chart = am4core.create(
  1164. this.$refs.real4Chart,
  1165. am4charts.XYChart
  1166. );
  1167. // ... 配置real4Chart
  1168. this.charts.real4Chart = real4Chart;
  1169. // 库存图表
  1170. const real6Chart = am4core.create(
  1171. this.$refs.real6Chart,
  1172. am4charts.XYChart
  1173. );
  1174. // ... 配置real6Chart
  1175. this.charts.real6Chart = real6Chart;
  1176. // 跳出率图表
  1177. const real1Chart = am4core.create(
  1178. this.$refs.real1Chart,
  1179. am4charts.XYChart
  1180. );
  1181. // ... 配置real1Chart
  1182. this.charts.real1Chart = real1Chart;
  1183. // 订单总额图表
  1184. const real5Chart = am4core.create(
  1185. this.$refs.real5Chart,
  1186. am4charts.XYChart
  1187. );
  1188. // ... 配置real5Chart
  1189. this.charts.real5Chart = real5Chart;
  1190. // 收入图表
  1191. const real2Chart = am4core.create(
  1192. this.$refs.real2Chart,
  1193. am4charts.XYChart
  1194. );
  1195. // ... 配置real2Chart
  1196. this.charts.real2Chart = real2Chart;
  1197. // 新购买图表
  1198. const real3Chart = am4core.create(
  1199. this.$refs.real3Chart,
  1200. am4charts.XYChart
  1201. );
  1202. // ... 配置real3Chart
  1203. this.charts.real3Chart = real3Chart;
  1204. },
  1205. initTransactionCharts() {
  1206. // Transactions 1 Chart
  1207. const transactions1 = am4core.create(
  1208. this.$refs.transactions1,
  1209. am4charts.XYChart
  1210. );
  1211. transactions1.data = this.generateTransactionData("#19BCBF");
  1212. this.configureTransactionChart(transactions1);
  1213. this.charts.transactions1 = transactions1;
  1214. // Transactions 2 Chart
  1215. const transactions2 = am4core.create(
  1216. this.$refs.transactions2,
  1217. am4charts.XYChart
  1218. );
  1219. transactions2.data = this.generateTransactionData("#FF425C");
  1220. this.configureTransactionChart(transactions2);
  1221. this.charts.transactions2 = transactions2;
  1222. },
  1223. generateTransactionData(color) {
  1224. const data = [];
  1225. for (let i = 0; i <= 12; i++) {
  1226. data.push({
  1227. category: i,
  1228. value: Math.random() * 30 + 10,
  1229. color: color,
  1230. });
  1231. }
  1232. return data;
  1233. },
  1234. configureTransactionChart(chart) {
  1235. // Create axes
  1236. const categoryAxis = chart.xAxes.push(new am4charts.CategoryAxis());
  1237. categoryAxis.dataFields.category = "category";
  1238. categoryAxis.renderer.grid.template.disabled = true;
  1239. categoryAxis.renderer.labels.template.disabled = true;
  1240. categoryAxis.renderer.minGridDistance = 10;
  1241. const valueAxis = chart.yAxes.push(new am4charts.ValueAxis());
  1242. valueAxis.renderer.grid.template.disabled = true;
  1243. valueAxis.renderer.labels.template.disabled = true;
  1244. valueAxis.min = 0;
  1245. valueAxis.max = 50;
  1246. // Create series
  1247. const series = chart.series.push(new am4charts.ColumnSeries());
  1248. series.dataFields.valueY = "value";
  1249. series.dataFields.categoryX = "category";
  1250. series.columns.template.propertyFields.fill = "color";
  1251. series.columns.template.propertyFields.stroke = "color";
  1252. series.columns.template.width = am4core.percent(40);
  1253. // Add tooltip
  1254. series.tooltipText = "{valueY}";
  1255. chart.cursor = new am4charts.XYCursor();
  1256. chart.cursor.behavior = "none";
  1257. // Remove padding
  1258. chart.paddingBottom = 0;
  1259. chart.paddingTop = 0;
  1260. chart.paddingLeft = 0;
  1261. chart.paddingRight = 0;
  1262. },
  1263. initDeviceChart() {
  1264. const chart = am4core.create(this.$refs.deviceChart, am4charts.PieChart);
  1265. // 添加数据
  1266. chart.data = [
  1267. { sector: "Mobile", size: 14.6 },
  1268. { sector: "Tablet", size: 9.3 },
  1269. { sector: "Desktop", size: 22.5 },
  1270. ];
  1271. // 设置内圆半径
  1272. chart.innerRadius = 30;
  1273. // 添加并配置饼图系列
  1274. const pieSeries = chart.series.push(new am4charts.PieSeries());
  1275. pieSeries.dataFields.value = "size";
  1276. pieSeries.dataFields.category = "sector";
  1277. pieSeries.labels.template.disabled = true;
  1278. pieSeries.ticks.template.disabled = true;
  1279. pieSeries.colors.step = 2;
  1280. // 保存图表实例
  1281. this.charts.deviceChart = chart;
  1282. },
  1283. /* 初始化 */
  1284. init() {
  1285. structure_count().then((res) => {
  1286. if (res.status !== 200) return;
  1287. this.kenList = res.data;
  1288. if (this.kenList.length > 0) {
  1289. this.kenValue = this.kenList[0].id;
  1290. this.kenVal = this.kenList[0];
  1291. // 处理文件类型统计数据
  1292. this.updateChartPercentData(this.kenList[0]);
  1293. }
  1294. });
  1295. /* 获取文档数量 */
  1296. searchlistDoc({ page: 1, pageSize: 9999 }).then((res) => {
  1297. this.docTotal = res.data.dataList.length;
  1298. });
  1299. searchList({ page: 1, pageSize: 9999 }).then((res) => {
  1300. if (!res) return;
  1301. this.temTotal = res.data.dataList.length;
  1302. });
  1303. pageTemplate({ page: 1, pageSize: 9999 }).then((res) => {
  1304. if (!res) return;
  1305. this.pageTotal = res.data.dataList.length;
  1306. });
  1307. /* 获取文件解析数量 */
  1308. doc_status().then((res) => {
  1309. this.docList = res.data.data.run_stats;
  1310. console.log(this.docList.run_3);
  1311. });
  1312. },
  1313. /* 选择知识库 */
  1314. kenChange(val) {
  1315. this.kenVal = this.kenList.find((el) => el.id === val);
  1316. // 更新图表数据
  1317. this.updateChartPercentData(this.kenVal);
  1318. },
  1319. // 更新文件类型统计数据
  1320. updateChartPercentData(kenData) {
  1321. if (!kenData) return;
  1322. // 获取所有的 file_type_stats
  1323. const allFileStats = this.getAllFileTypeStats(kenData);
  1324. // 合并所有的文件统计数据
  1325. const mergedFileStats = this.mergeFileStats(allFileStats);
  1326. // 保存上一次的数据用于计算变化率
  1327. this.previousCounts = { ...this.fileTypeCounts };
  1328. // 更新当前数据
  1329. this.fileTypeCounts = mergedFileStats;
  1330. // 计算总文件数和百分比
  1331. const totalFiles = Object.values(mergedFileStats).reduce(
  1332. (a, b) => a + b,
  1333. 0
  1334. );
  1335. // 转换数据格式
  1336. const chartData = Object.entries(mergedFileStats).map(
  1337. ([type, count]) => ({
  1338. sector: type.toUpperCase(),
  1339. size: count /* Number(((count / totalFiles) * 100).toFixed(1)) */,
  1340. count: count,
  1341. })
  1342. );
  1343. // 更新图表数据
  1344. if (this.charts.chartPercent) {
  1345. this.charts.chartPercent.data = chartData;
  1346. }
  1347. },
  1348. // 获取文件类型对应的图标
  1349. getFileTypeIcon(type) {
  1350. const icons = {
  1351. pdf: "icon-file-text",
  1352. docx: "icon-file",
  1353. xlsx: "icon-file",
  1354. };
  1355. return icons[type.toLowerCase()] || "icon-file";
  1356. },
  1357. // 获取文件类型对应的颜色
  1358. getFileTypeColor(type) {
  1359. const colors = {
  1360. pdf: "#dc67ce",
  1361. docx: "#8067dc",
  1362. xlsx: "#67b7dc",
  1363. };
  1364. return colors[type.toLowerCase()] || "#666666";
  1365. },
  1366. // 计算化率
  1367. getChangePercentage(type) {
  1368. const currentCount = this.fileTypeCounts[type] || 0;
  1369. const previousCount = this.previousCounts[type] || currentCount;
  1370. if (previousCount === 0) return "0%";
  1371. const change = ((currentCount - previousCount) / previousCount) * 100;
  1372. return `${Math.abs(change).toFixed(1)}%`;
  1373. },
  1374. // 获取变化率的图标
  1375. getChangeIcon(type) {
  1376. const currentCount = this.fileTypeCounts[type] || 0;
  1377. const previousCount = this.previousCounts[type] || currentCount;
  1378. return currentCount >= previousCount
  1379. ? "icon-arrow-up"
  1380. : "icon-arrow-down";
  1381. },
  1382. // 获取变化率的样式类
  1383. getChangeClass(type) {
  1384. const currentCount = this.fileTypeCounts[type] || 0;
  1385. const previousCount = this.previousCounts[type] || currentCount;
  1386. return currentCount >= previousCount ? "text-c-green" : "text-c-red";
  1387. },
  1388. // 递归获取所有的 file_type_stats
  1389. getAllFileTypeStats(data) {
  1390. let results = [];
  1391. // 处理当前层级的 file_type_stats
  1392. if (data.file_type_stats) {
  1393. results.push(data.file_type_stats);
  1394. }
  1395. // 处理 directories 中的 file_type_stats
  1396. if (data.directories && Array.isArray(data.directories)) {
  1397. data.directories.forEach((dir) => {
  1398. results = results.concat(this.getAllFileTypeStats(dir));
  1399. });
  1400. }
  1401. return results;
  1402. },
  1403. // 合并多个 file_type_stats 对象
  1404. mergeFileStats(fileStatsList) {
  1405. return fileStatsList.reduce((merged, current) => {
  1406. Object.entries(current).forEach(([type, count]) => {
  1407. merged[type] = (merged[type] || 0) + count;
  1408. });
  1409. return merged;
  1410. }, {});
  1411. },
  1412. // 处理WebSocket消息的方法修改
  1413. handleWebSocketMessage(data) {
  1414. try {
  1415. if (data?.data?.system?.temperature) {
  1416. this.updateTemperatures(data.data.system.temperature);
  1417. }
  1418. } catch (error) {
  1419. console.error("Error processing WebSocket message:", error);
  1420. }
  1421. },
  1422. updateTemperatures(temperatureData) {
  1423. // 更新CPU温度
  1424. if (temperatureData.cpu && Array.isArray(temperatureData.cpu)) {
  1425. // 获取Package温度作为主要CPU温度
  1426. const packageTemp = temperatureData.cpu.find(temp => temp.label === 'Package id 0');
  1427. if (packageTemp) {
  1428. this.temperatureData.cpu = {
  1429. current: packageTemp.current,
  1430. high: packageTemp.high || this.temperatureData.cpu.high,
  1431. critical: packageTemp.critical || this.temperatureData.cpu.critical
  1432. };
  1433. this.cpuTemperature = packageTemp.current;
  1434. }
  1435. // 记录最高核心温度用于告警
  1436. const maxCoreTemp = Math.max(
  1437. ...temperatureData.cpu
  1438. .filter(temp => temp.label.startsWith('Core'))
  1439. .map(temp => temp.current)
  1440. );
  1441. if (maxCoreTemp > this.temperatureData.cpu.high) {
  1442. console.warn(`CPU temperature warning: ${maxCoreTemp}°C`);
  1443. }
  1444. }
  1445. // 更新GPU温度
  1446. if (temperatureData.gpu && temperatureData.gpu.length > 0) {
  1447. const gpuTemp = temperatureData.gpu[0];
  1448. this.temperatureData.gpu.current = gpuTemp.temperature;
  1449. this.gpuTemperature = gpuTemp.temperature;
  1450. if (gpuTemp.temperature > this.temperatureData.gpu.high) {
  1451. console.warn(`GPU temperature warning: ${gpuTemp.temperature}°C`);
  1452. }
  1453. }
  1454. },
  1455. getTemperatureColor(temp, type = 'cpu') {
  1456. const thresholds = this.temperatureData[type];
  1457. const percentage = (temp / thresholds.critical) * 100;
  1458. if (percentage >= 90) return "#FF425C"; // 危险温度 - 红色
  1459. if (percentage >= 70) return "#FFA726"; // 警告温度 - 橙色
  1460. return "#19BCBF"; // 正常温度 - 蓝色
  1461. },
  1462. // 初始化磁盘图表
  1463. initDiskChart() {
  1464. try {
  1465. // 创建环形图实例
  1466. const chart = am4core.create(this.$refs.diskChart, am4charts.PieChart);
  1467. if (!chart) return;
  1468. // 禁用 logo
  1469. if (chart.logo) {
  1470. chart.logo.disabled = true;
  1471. }
  1472. // 设置内半径创建环形效果
  1473. chart.innerRadius = am4core.percent(65);
  1474. // 添加数据
  1475. chart.data = this.formatDiskData();
  1476. // 创建饼图系列
  1477. const series = chart.series.push(new am4charts.PieSeries());
  1478. if (series) {
  1479. // 配置数据字段
  1480. series.dataFields.value = "used";
  1481. series.dataFields.category = "diskName";
  1482. // 禁用标签和刻度
  1483. series.labels.template.disabled = true;
  1484. series.ticks.template.disabled = true;
  1485. // 配置切片样式
  1486. if (series.slices.template) {
  1487. series.slices.template.strokeWidth = 2;
  1488. series.slices.template.strokeOpacity = 1;
  1489. series.slices.template.stroke = am4core.color("#fff");
  1490. // 设置切片颜色
  1491. series.slices.template.adapter.add("fill", (fill, target) => {
  1492. const dataItem = target.dataItem;
  1493. if (dataItem) {
  1494. const percentUsed = (dataItem.values.value.value / dataItem.dataContext.total) * 100;
  1495. if (percentUsed > 80) return am4core.color("#FF425C"); // 危险
  1496. if (percentUsed > 60) return am4core.color("#FFA726"); // 警告
  1497. return am4core.color("#19BCBF"); // 正常
  1498. }
  1499. return fill;
  1500. });
  1501. // 配置悬浮提示
  1502. series.slices.template.tooltipHTML = `
  1503. <div style="padding: 10px;">
  1504. <div style="margin-bottom: 5px;color:#333;"><strong>{diskName}</strong></div>
  1505. <div style="color: #666;">总容量: {total.formatNumber('#.##')} GB</div>
  1506. <div style="color: #FF425C;">已使用: {used.formatNumber('#.##')} GB</div>
  1507. <div style="color: #19BCBF;">可用空间: {free.formatNumber('#.##')} GB</div>
  1508. <div style="color: #666;">使用率: {percent.formatNumber('#.##')}%</div>
  1509. </div>
  1510. `;
  1511. // 设置悬浮提示样式
  1512. if (series.tooltip) {
  1513. series.tooltip.getFillFromObject = false;
  1514. series.tooltip.pointerOrientation = "vertical";
  1515. series.tooltip.dy = -5;
  1516. series.tooltip.dx = 0;
  1517. if (series.tooltip.background) {
  1518. series.tooltip.background.fill = am4core.color("#ffffff");
  1519. series.tooltip.background.stroke = am4core.color("#19BCBF");
  1520. series.tooltip.background.strokeWidth = 1;
  1521. series.tooltip.background.cornerRadius = 4;
  1522. }
  1523. }
  1524. }
  1525. // 添加中心标签
  1526. const label = chart.seriesContainer.createChild(am4core.Label);
  1527. label.text = "{percent.formatNumber('#.#')}%";
  1528. label.horizontalCenter = "middle";
  1529. label.verticalCenter = "middle";
  1530. label.fontSize = 20;
  1531. label.fontWeight = "600";
  1532. // 更新中心标签的值
  1533. series.events.on("validated", () => {
  1534. const total = series.dataItems.getIndex(0).dataContext.total;
  1535. const used = series.dataItems.getIndex(0).dataContext.used;
  1536. const percent = (used / total) * 100;
  1537. label.text = `${percent.toFixed(1)}%`;
  1538. });
  1539. }
  1540. // 保存图表实例
  1541. this.charts.diskChart = chart;
  1542. // 设置自动更新
  1543. this.startDiskDataInterval();
  1544. } catch (error) {
  1545. console.error("初始化磁盘图表失败:", error);
  1546. }
  1547. },
  1548. // 格式化磁盘数据
  1549. formatDiskData() {
  1550. if (!this.chartData?.data?.system?.cpu?.disk?.partitions) {
  1551. return [];
  1552. }
  1553. const diskNameMap = {
  1554. C: "系统盘",
  1555. D: "数据盘",
  1556. E: "扩展盘",
  1557. };
  1558. return this.chartData.data.system.cpu.disk.partitions
  1559. .filter(partition => partition.mountpoint === "/")
  1560. .map(partition => {
  1561. const driveLetter = partition.device.replace(":\\", "");
  1562. const diskName = diskNameMap[driveLetter] || `${driveLetter}盘`;
  1563. return {
  1564. device: driveLetter,
  1565. diskName: diskName,
  1566. used: partition.used,
  1567. free: partition.free,
  1568. total: partition.total,
  1569. percent: partition.percent
  1570. };
  1571. });
  1572. },
  1573. // 启动磁盘数据更新定时器
  1574. startDiskDataInterval() {
  1575. if (this.diskUpdateInterval) {
  1576. clearInterval(this.diskUpdateInterval);
  1577. }
  1578. this.diskUpdateInterval = setInterval(() => {
  1579. if (this.charts.diskChart) {
  1580. this.charts.diskChart.data = this.formatDiskData();
  1581. }
  1582. }, 2000); // 每2秒更新一次
  1583. },
  1584. /* // 在 WebSocket 消息处理中添加磁盘数据更新
  1585. handleWebSocketMessage(event) {
  1586. try {
  1587. const data = JSON.parse(event.data);
  1588. if (data?.data?.system?.cpu?.disk) {
  1589. this.chartData = data;
  1590. if (this.charts.diskChart) {
  1591. this.charts.diskChart.data = this.formatDiskData();
  1592. }
  1593. }
  1594. } catch (error) {
  1595. console.error("Error processing WebSocket message:", error);
  1596. }
  1597. }, */
  1598. },
  1599. // 修改beforeDestroy钩子
  1600. beforeDestroy() {
  1601. this.isCurrentPage = false;
  1602. this.closeWebSocket();
  1603. // 清理所有图表实例
  1604. Object.values(this.charts).forEach((chart) => {
  1605. if (chart) {
  1606. if (typeof chart.dispose === "function") {
  1607. chart.dispose();
  1608. } else if (typeof chart === "number") {
  1609. clearInterval(chart);
  1610. }
  1611. }
  1612. });
  1613. // 移除事件监听器
  1614. document.removeEventListener(
  1615. "visibilitychange",
  1616. this.handleVisitorChartVisibility
  1617. );
  1618. },
  1619. };
  1620. </script>
  1621. <style lang="scss" scoped>
  1622. .widget-chart {
  1623. height: calc(100vh - 85px);
  1624. padding: 15px;
  1625. font-family: "Roboto", sans-serif;
  1626. font-size: 14px;
  1627. color: #686c71;
  1628. font-weight: 400;
  1629. background: #f8f9fa;
  1630. a {
  1631. text-decoration: none;
  1632. }
  1633. // 卡片基础样式
  1634. .card {
  1635. border-radius: 2px;
  1636. box-shadow: 0 0px 0 1px #e2e5e8;
  1637. border: none;
  1638. margin-bottom: 17px;
  1639. background: #fff;
  1640. .card-body {
  1641. padding: 1.5rem;
  1642. h6 {
  1643. color: #111;
  1644. font-size: 16px;
  1645. .text-primary {
  1646. color: #19bcbf !important;
  1647. }
  1648. .text-danger {
  1649. color: #ff425c !important;
  1650. }
  1651. }
  1652. }
  1653. }
  1654. // 文字样式
  1655. h2 {
  1656. font-size: 28px;
  1657. font-weight: 600;
  1658. color: #333;
  1659. margin: 0;
  1660. }
  1661. h6 {
  1662. font-size: 14px;
  1663. color: #6c757d;
  1664. margin-bottom: 0.5rem;
  1665. }
  1666. // 图标样式
  1667. .feather {
  1668. width: 20px;
  1669. height: 20px;
  1670. vertical-align: text-bottom;
  1671. }
  1672. // 颜色方案
  1673. .text-c-blue {
  1674. color: #19bcbf;
  1675. }
  1676. .text-c-green {
  1677. color: #2ed8b6;
  1678. }
  1679. .text-c-red {
  1680. color: #ff5370;
  1681. }
  1682. .text-c-purple {
  1683. color: #8067dc;
  1684. }
  1685. // 下拉菜单样式
  1686. .dropdown-toggle {
  1687. &:after {
  1688. margin-left: 0.5em;
  1689. }
  1690. }
  1691. .dropdown-menu {
  1692. border-radius: 4px;
  1693. box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15);
  1694. .dropdown-item {
  1695. padding: 0.5rem 1rem;
  1696. &:hover {
  1697. background-color: #f8f9fa;
  1698. }
  1699. }
  1700. }
  1701. // 图表容器样式
  1702. [ref="hdComplitedTicket"],
  1703. [ref="chartPercent"],
  1704. [ref="storageChart"] {
  1705. margin: 1rem 0;
  1706. }
  1707. // 小型图表样式
  1708. [ref^="real"] {
  1709. height: 20px;
  1710. width: 100%;
  1711. margin-top: 0.5rem;
  1712. }
  1713. // 统计数字样式
  1714. .mt-2 {
  1715. h3 {
  1716. font-size: 24px;
  1717. font-weight: 600;
  1718. color: #333;
  1719. margin: 0.5rem 0;
  1720. }
  1721. }
  1722. // 图标指标样式
  1723. .fas.fa-circle {
  1724. font-size: 10px;
  1725. margin-right: 8px;
  1726. &.text-c-blue {
  1727. color: #19bcbf;
  1728. }
  1729. &.text-c-purple {
  1730. color: #8067dc;
  1731. }
  1732. }
  1733. }
  1734. // 响应式布局调整
  1735. @media (max-width: 768px) {
  1736. .widget-chart {
  1737. .card {
  1738. margin-bottom: 16px;
  1739. }
  1740. }
  1741. }
  1742. .progress {
  1743. background-color: #f0f0f0;
  1744. border-radius: 3px;
  1745. overflow: hidden;
  1746. }
  1747. .progress-bar {
  1748. transition: width 0.3s ease;
  1749. }
  1750. .temperature-select-wrapper {
  1751. position: relative;
  1752. width: 100%;
  1753. height: 20px;
  1754. .temperature-select {
  1755. position: absolute;
  1756. top: 0;
  1757. left: 0;
  1758. width: 100%;
  1759. height: 100%;
  1760. appearance: none;
  1761. background: transparent;
  1762. border: none;
  1763. pointer-events: none;
  1764. z-index: 1;
  1765. &::-ms-expand {
  1766. display: none;
  1767. }
  1768. option {
  1769. background: transparent;
  1770. }
  1771. }
  1772. .temperature-progress {
  1773. position: absolute;
  1774. top: 0;
  1775. left: 0;
  1776. height: 100%;
  1777. background-color: #19bcbf;
  1778. transition: width 0.3s ease;
  1779. border-radius: 3px;
  1780. &::after {
  1781. content: "";
  1782. position: absolute;
  1783. top: 0;
  1784. right: 0;
  1785. width: 100%;
  1786. height: 100%;
  1787. background: repeating-linear-gradient(
  1788. 90deg,
  1789. currentColor,
  1790. currentColor 4px,
  1791. transparent 4px,
  1792. transparent 8px
  1793. );
  1794. opacity: 0.2;
  1795. }
  1796. }
  1797. }
  1798. // 添加条纹背景
  1799. .temperature-select-wrapper::before {
  1800. content: "";
  1801. position: absolute;
  1802. top: 0;
  1803. left: 0;
  1804. width: 100%;
  1805. height: 100%;
  1806. background: repeating-linear-gradient(
  1807. 90deg,
  1808. #e0e0e0,
  1809. #e0e0e0 4px,
  1810. #f0f0f0 4px,
  1811. #f0f0f0 8px
  1812. );
  1813. border-radius: 3px;
  1814. }
  1815. // 更新温度进度条样式
  1816. .temperature-select-wrapper {
  1817. margin-bottom: 10px;
  1818. .temperature-progress {
  1819. transition: all 0.3s ease;
  1820. &::after {
  1821. opacity: 0.15;
  1822. }
  1823. }
  1824. }
  1825. // 添加新的温度显示样式
  1826. .col-6 {
  1827. h2 {
  1828. font-size: 24px;
  1829. font-weight: 600;
  1830. }
  1831. span {
  1832. color: #666;
  1833. font-size: 14px;
  1834. }
  1835. }
  1836. </style>
  1837. <style src="./css/bootstrap.min.css" scoped></style>