chart.vue 49 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467
  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-8">
  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. <h4 class="m-0">
  56. {{ cpuValue + "%"
  57. }}<i
  58. class="feather icon-arrow-up text-c-green"
  59. ></i>
  60. </h4>
  61. <span>CPU使用率</span>
  62. </div>
  63. <div class="col-auto">
  64. <h4 class="m-0">
  65. {{ GpuValue + "%"
  66. }}<i
  67. class="feather icon-arrow-down text-c-red"
  68. ></i>
  69. </h4>
  70. <span>GPU使用率</span>
  71. </div>
  72. <!-- <div class="col">
  73. <h4 class="m-0">
  74. 40.05%<i
  75. class="feather icon-arrow-up text-c-green"
  76. ></i>
  77. </h4>
  78. <span>Bounce Rate</span>
  79. </div> -->
  80. </div>
  81. <div
  82. ref="siteVisitorChart"
  83. style="height: 285px; width: 100%"
  84. ></div>
  85. </div>
  86. </div>
  87. </div>
  88. <!-- -->
  89. <div class="col-xl-4">
  90. <div class="row">
  91. <!-- 支持分析卡片 -->
  92. <div class="col-xl-12 col-md-6">
  93. <div class="card">
  94. <div class="card-body">
  95. <div class="row">
  96. <div class="col-6">
  97. <h6>Memory</h6>
  98. </div>
  99. <!-- <div class="col">
  100. <div class="dropdown float-end">
  101. <a
  102. class="dropdown-toggle text-c-blue"
  103. href="#"
  104. data-bs-toggle="dropdown"
  105. >
  106. 最近5个月
  107. </a>
  108. <div class="dropdown-menu dropdown-menu-end">
  109. <a class="dropdown-item" href="#">1周</a>
  110. <a class="dropdown-item" href="#">2年</a>
  111. <a class="dropdown-item" href="#">3个月</a>
  112. </div>
  113. </div>
  114. </div> -->
  115. </div>
  116. <div class="row">
  117. <div class="col-6">
  118. <h2 class="mt-2 mb-0">
  119. {{ currentPercentage.total + "GB" }}
  120. </h2>
  121. <span>总内存</span>
  122. <h6 class="mb-0 mt-3">
  123. 空闲内存:
  124. <span class="text-primary">{{
  125. currentPercentage.free + "GB"
  126. }}</span>
  127. </h6>
  128. <h6>
  129. 已使用:
  130. <span class="text-danger">{{
  131. currentPercentage.used + "GB"
  132. }}</span>
  133. </h6>
  134. </div>
  135. <div class="col">
  136. <!-- 支持工单完成率图表 -->
  137. <div
  138. ref="hdComplitedTicket"
  139. style="height: 130px; width: 100%"
  140. ></div>
  141. </div>
  142. </div>
  143. </div>
  144. </div>
  145. </div>
  146. <!-- 知识库统计卡片 -->
  147. <div class="col-xl-12 col-md-6">
  148. <div class="card">
  149. <div class="card-body">
  150. <div
  151. class="row"
  152. style="display: flex; align-items: center"
  153. >
  154. <div class="col-auto">
  155. <h6>知识库统计</h6>
  156. </div>
  157. <div
  158. class="col"
  159. style="
  160. display: flex;
  161. justify-content: flex-end;
  162. "
  163. >
  164. <el-select
  165. v-model="kenValue"
  166. @change="kenChange"
  167. placeholder="请选择"
  168. >
  169. <el-option
  170. v-for="item in kenList"
  171. :key="item.id"
  172. :label="item.name"
  173. :value="item.id"
  174. >
  175. </el-option>
  176. </el-select>
  177. <!-- <div class="dropdown float-end">
  178. <a
  179. class="dropdown-toggle text-c-blue"
  180. href="#"
  181. data-bs-toggle="dropdown"
  182. >
  183. 最近30天
  184. </a>
  185. <div
  186. class="dropdown-menu dropdown-menu-end"
  187. >
  188. <a class="dropdown-item" href="#">1周</a>
  189. <a class="dropdown-item" href="#">2年</a>
  190. <a class="dropdown-item" href="#"
  191. >3个月</a
  192. >
  193. </div>
  194. </div> -->
  195. </div>
  196. </div>
  197. <div class="row">
  198. <div class="col-6 p-r-0">
  199. <template
  200. v-for="(count, type) in fileTypeCounts"
  201. >
  202. <h6
  203. :key="type"
  204. class="my-3"
  205. v-if="
  206. ['pdf', 'docx', 'xlsx'].includes(
  207. type.toLowerCase()
  208. )
  209. "
  210. >
  211. <i
  212. class="feather"
  213. :class="getFileTypeIcon(type)"
  214. style="
  215. font-size: 20px;
  216. margin-right: 8px;
  217. "
  218. :style="{
  219. color: getFileTypeColor(type),
  220. }"
  221. ></i>
  222. {{ type.toUpperCase() }}
  223. <span class="ms-2"> </span>
  224. <span
  225. :class="getChangeClass(type)"
  226. class="ms-2 f-14"
  227. >
  228. <i
  229. class="feather"
  230. :class="getChangeIcon(type)"
  231. ></i>
  232. {{ count }}
  233. <!-- {{ getChangePercentage(type) }} -->
  234. </span>
  235. </h6>
  236. </template>
  237. </div>
  238. <div class="col-6">
  239. <!-- 设备占比饼图 -->
  240. <div
  241. ref="chartPercent"
  242. style="height: 131px"
  243. ></div>
  244. </div>
  245. </div>
  246. </div>
  247. </div>
  248. </div>
  249. <!-- <div class="col-xl-12 col-md-6">
  250. <div class="card">
  251. <div class="card-body">
  252. <h6>Traffic tracker</h6>
  253. <div class="row mt-2">
  254. <div class="col-6">
  255. <span class="d-block text-uppercase"
  256. >Inbound</span
  257. >
  258. <h3 class="mt-1">180 GB</h3>
  259. <div
  260. ref="transactions1"
  261. style="
  262. height: 50px;
  263. width: 100%;
  264. margin: 0 auto;
  265. "
  266. ></div>
  267. </div>
  268. <div class="col-6">
  269. <span class="d-block text-uppercase"
  270. >Outbound</span
  271. >
  272. <h3 class="mt-1">597.1 GB</h3>
  273. <div
  274. ref="transactions2"
  275. style="
  276. height: 50px;
  277. width: 100%;
  278. margin: 0 auto;
  279. "
  280. ></div>
  281. </div>
  282. </div>
  283. </div>
  284. </div>
  285. </div>
  286. <div class="col-xl-12 col-md-6">
  287. <div class="card">
  288. <div class="card-body">
  289. <div class="row d-flex align-items-center">
  290. <div class="col-6 p-r-0">
  291. <span class="d-block mb-1"
  292. ><i
  293. class="fas fa-circle f-10 m-r-5"
  294. style="color: #a367dc"
  295. ></i
  296. >Desktop [66.6%]</span
  297. >
  298. <span class="d-block mb-1"
  299. ><i
  300. class="fas fa-circle f-10 m-r-5"
  301. style="color: #67b7dc"
  302. ></i
  303. >Mobile [29.7%]</span
  304. >
  305. <span class="d-block"
  306. ><i
  307. class="fas fa-circle f-10 m-r-5"
  308. style="color: #6771dc"
  309. ></i
  310. >Tablet [38.6%]</span
  311. >
  312. </div>
  313. <div class="col-6">
  314. <div
  315. ref="deviceChart"
  316. id="device-chart"
  317. style="height: 140px"
  318. ></div>
  319. </div>
  320. </div>
  321. </div>
  322. </div>
  323. </div> -->
  324. </div>
  325. </div>
  326. <!-- 其他统计卡片组 -->
  327. <div class="col-sm-12">
  328. <div class="row g-0">
  329. <!-- 交易量卡片 -->
  330. <div class="col-md-4 col-xl-2 col-sm-6">
  331. <div class="card">
  332. <div class="card-body">
  333. <span>知识库数量</span>
  334. <h3>{{ kenList.length }}</h3>
  335. <div
  336. ref="real4Chart"
  337. style="height: 20px; width: 100%"
  338. ></div>
  339. </div>
  340. </div>
  341. </div>
  342. <!-- 当前库存卡片 -->
  343. <div class="col-md-4 col-xl-2 col-sm-6">
  344. <div class="card">
  345. <div class="card-body">
  346. <span>文件已解析数量</span>
  347. <h3>{{ docList.run_3 }}</h3>
  348. <div
  349. ref="real6Chart"
  350. style="height: 20px; width: 100%"
  351. ></div>
  352. </div>
  353. </div>
  354. </div>
  355. <!-- 跳出率卡片 -->
  356. <div class="col-md-4 col-xl-2 col-sm-6">
  357. <div class="card">
  358. <div class="card-body">
  359. <span>文件未解析数量</span>
  360. <h3>{{ docList.run_0 }}</h3>
  361. <div
  362. ref="real1Chart"
  363. style="height: 20px; width: 100%"
  364. ></div>
  365. </div>
  366. </div>
  367. </div>
  368. <!-- 订单总额卡片 -->
  369. <div class="col-md-4 col-xl-2 col-sm-6">
  370. <div class="card">
  371. <div class="card-body">
  372. <span>文档数量</span>
  373. <h3>{{ docTotal }}</h3>
  374. <div
  375. ref="real5Chart"
  376. style="height: 20px; width: 100%"
  377. ></div>
  378. </div>
  379. </div>
  380. </div>
  381. <!-- 收入卡片 -->
  382. <div class="col-md-4 col-xl-2 col-sm-6">
  383. <div class="card">
  384. <div class="card-body">
  385. <span>模版数量</span>
  386. <h3>{{ temTotal }}</h3>
  387. <div
  388. ref="real2Chart"
  389. style="height: 20px; width: 100%"
  390. ></div>
  391. </div>
  392. </div>
  393. </div>
  394. <!-- 新购买卡片 -->
  395. <div class="col-md-4 col-xl-2 col-sm-6">
  396. <div class="card">
  397. <div class="card-body">
  398. <span>模块数量</span>
  399. <h3>{{ pageTotal }}</h3>
  400. <div
  401. ref="real3Chart"
  402. style="height: 20px; width: 100%"
  403. ></div>
  404. </div>
  405. </div>
  406. </div>
  407. </div>
  408. </div>
  409. </div>
  410. </div>
  411. </div>
  412. </div>
  413. </div>
  414. </div>
  415. </div>
  416. </div>
  417. </div>
  418. </template>
  419. <script>
  420. import * as am4core from "@amcharts/amcharts4/core";
  421. import * as am4charts from "@amcharts/amcharts4/charts";
  422. import am4themes_animated from "@amcharts/amcharts4/themes/animated";
  423. import BootstrapWrapper from "@/components/BootstrapWrapper/index.vue";
  424. import { searchlistDoc, searchList } from "@/api/document";
  425. import { pageTemplate } from "@/api/template";
  426. import {
  427. listBuckets,
  428. structure_count,
  429. GET_Rbq,
  430. gpu_info,
  431. doc_status,
  432. } from "@/api/knowledge";
  433. export default {
  434. name: "WidgetChart",
  435. components: {
  436. BootstrapWrapper,
  437. },
  438. data() {
  439. return {
  440. charts: {}, // 存储图表实例
  441. chartData: {},
  442. currentPercentage: {},
  443. cpuValue: 0,
  444. GpuValue: 0,
  445. kenValue: null, //知识库
  446. kenList: [],
  447. previousCounts: {}, // 用于存储上一次的数量,计算变化率
  448. fileTypeCounts: {}, // 用于存储上一次的数量,计算变化率
  449. docTotal: 0,
  450. temTotal: 0,
  451. pageTotal: 0,
  452. docList: {},
  453. chartUpdateInterval: null, // 添加新的数据属性用于存储定时器
  454. isPageVisible: !document.hidden, // 添加新的数据属性
  455. };
  456. },
  457. watch: {
  458. $route: {
  459. immediate: true,
  460. handler(newRoute) {
  461. this.isOnDashboard = newRoute.path === "/dashboard";
  462. this.handleRouteChange();
  463. },
  464. },
  465. // 监听页面可见性
  466. isPageVisible: {
  467. immediate: true,
  468. handler(newValue) {
  469. console.log(newValue);
  470. if (newValue) {
  471. // 页面变为可见时
  472. console.log("Page became visible");
  473. this.initSiteVisitorChart();
  474. } else {
  475. // 页面变为不可见时
  476. console.log("Page became hidden");
  477. this.cleanupCharts();
  478. }
  479. },
  480. },
  481. },
  482. mounted() {
  483. document.addEventListener("visibilitychange", this.handleVisibilityChange);
  484. // 使用动画主题
  485. am4core.useTheme(am4themes_animated);
  486. this.init();
  487. // 初始化各个图表
  488. this.initHdComplitedTicket();
  489. this.initChartPercent();
  490. this.initStorageChart();
  491. this.initRealTimeCharts();
  492. this.initSiteVisitorChart(); // 添加这行
  493. this.initTransactionCharts();
  494. this.initDeviceChart(); // 添加这行
  495. },
  496. // 修改生命周期钩子
  497. activated() {
  498. console.log(1123);
  499. // 当组件被激活时重新初始化图表
  500. this.initSiteVisitorChart();
  501. },
  502. deactivated() {
  503. // 当组件被停用时清理资源
  504. this.cleanupCharts();
  505. },
  506. beforeDestroy() {
  507. // 组件销毁前清理所有资源
  508. this.cleanupCharts();
  509. this.closeWebSocket();
  510. // 清理其他图表实例
  511. Object.values(this.charts).forEach((chart) => {
  512. if (chart) {
  513. if (typeof chart.dispose === "function") {
  514. chart.dispose();
  515. } else if (typeof chart === "number") {
  516. clearInterval(chart);
  517. }
  518. }
  519. });
  520. },
  521. methods: {
  522. // 处理页面可见性变化
  523. handleVisibilityChange() {
  524. this.isPageVisible = !document.hidden;
  525. },
  526. closeWebSocket() {
  527. if (this.ws) {
  528. this.ws.close();
  529. this.ws = null;
  530. }
  531. },
  532. handleRouteChange() {
  533. if (this.isOnDashboard) {
  534. this.initWebSocket(); // 初始化 WebSocket
  535. } else {
  536. this.closeWebSocket();
  537. }
  538. },
  539. /* websocket 请求 */
  540. initWebSocket() {
  541. // 获取基础 API 地址并替换 http/https 为 ws/wss
  542. const wsUrl = process.env.VUE_APP_BASE_API.replace(/^http/, "ws");
  543. // 创建 WebSocket 连接
  544. this.ws = new WebSocket(`${wsUrl}/ws/monitoring`);
  545. // 连接成功回调
  546. this.ws.onopen = () => {
  547. console.log("WebSocket连接成功");
  548. };
  549. // 接收消息回调
  550. this.ws.onmessage = (event) => {
  551. try {
  552. const data = JSON.parse(event.data);
  553. // 检查数据结构是否完整
  554. if (
  555. data &&
  556. data.data &&
  557. data.data.system &&
  558. data.data.system.cpu &&
  559. data.data.system.cpu.memory
  560. ) {
  561. // 更新图表数据
  562. this.chartData = data;
  563. this.cpuValue = data.data.system.cpu.total_usage;
  564. this.GpuValue = data.data.system.gpu.devices[0]?.power || 0;
  565. this.currentPercentage = data.data.system.cpu.memory;
  566. } else {
  567. console.warn("Received incomplete data structure:", data);
  568. }
  569. } catch (error) {
  570. console.error("Error processing WebSocket message:", error);
  571. }
  572. };
  573. // 连接关闭回调
  574. this.ws.onclose = () => {
  575. console.log("WebSocket连接关闭");
  576. };
  577. // 连接错误回调
  578. this.ws.onerror = (error) => {
  579. console.error("WebSocket连接错误:", error);
  580. };
  581. },
  582. // 初始化支持工单完成率图表
  583. initHdComplitedTicket() {
  584. const chart = am4core.create(
  585. this.$refs.hdComplitedTicket,
  586. am4charts.GaugeChart
  587. );
  588. chart.logo.disabled = true;
  589. chart.innerRadius = am4core.percent(82);
  590. // 修改主轴配置
  591. const axis = chart.xAxes.push(new am4charts.ValueAxis());
  592. axis.min = 0;
  593. axis.max = 100;
  594. axis.strictMinMax = true;
  595. // 设置固定的半径
  596. axis.renderer.radius = 80; // 使用固定像素值而不是百分比
  597. axis.renderer.inside = true;
  598. axis.renderer.line.strokeOpacity = 0;
  599. axis.renderer.ticks.template.strokeOpacity = 1;
  600. axis.renderer.ticks.template.length = 10;
  601. axis.renderer.grid.template.disabled = true;
  602. axis.renderer.labels.template.radius = 40;
  603. axis.renderer.labels.template.adapter.add("text", () => "");
  604. // 修改第二轴配置
  605. const axis2 = chart.xAxes.push(new am4charts.ValueAxis());
  606. axis2.min = 0;
  607. axis2.max = 100;
  608. // 设置固定的内半径
  609. axis2.renderer.innerRadius = 65; // 使用固定像素值
  610. axis2.renderer.radius = 80; // 设置固定的外半径
  611. axis2.strictMinMax = true;
  612. axis2.renderer.labels.template.disabled = true;
  613. axis2.renderer.ticks.template.disabled = true;
  614. axis2.renderer.grid.template.disabled = true;
  615. // 配置填充范围
  616. const range0 = axis2.axisRanges.create();
  617. range0.value = 0;
  618. range0.endValue = 50;
  619. range0.axisFill.fillOpacity = 1;
  620. range0.axisFill.fill = am4core.color("#19BCBF");
  621. const range1 = axis2.axisRanges.create();
  622. range1.value = 50;
  623. range1.endValue = 100;
  624. range1.axisFill.fillOpacity = 1;
  625. range1.axisFill.fill = am4core.color("#eff3f6");
  626. // 添加标签
  627. const label = chart.radarContainer.createChild(am4core.Label);
  628. label.isMeasured = false;
  629. label.fontSize = 18;
  630. label.x = am4core.percent(50);
  631. label.y = am4core.percent(100);
  632. label.horizontalCenter = "middle";
  633. label.verticalCenter = "bottom";
  634. label.text = "50%";
  635. const label2 = chart.radarContainer.createChild(am4core.Label);
  636. label2.isMeasured = false;
  637. label2.fontSize = 12;
  638. label2.x = am4core.percent(50);
  639. label2.y = am4core.percent(100);
  640. label2.horizontalCenter = "middle";
  641. label2.verticalCenter = "top";
  642. label2.text = "Complited Ticket";
  643. // 配置指针
  644. const hand = chart.hands.push(new am4charts.ClockHand());
  645. hand.axis = axis2;
  646. hand.innerRadius = am4core.percent(20);
  647. hand.startWidth = 0;
  648. hand.pin.disabled = true;
  649. hand.value = 50;
  650. hand.disabled = true;
  651. // 更新事件处理
  652. hand.events.on("propertychanged", (ev) => {
  653. range0.endValue = ev.target.value;
  654. range1.value = ev.target.value;
  655. axis2.invalidate();
  656. });
  657. // 设置图表容器的固定大小
  658. //chart.width = 160; // 设置固定宽度
  659. // 定时更新数据
  660. const interval = setInterval(() => {
  661. const value = this.currentPercentage.percent; // Math.round(Math.random() * 100);
  662. label.text = value + "%";
  663. new am4core.Animation(
  664. hand,
  665. {
  666. property: "value",
  667. to: value,
  668. },
  669. 1000,
  670. am4core.ease.cubicOut
  671. ).start();
  672. }, 2000);
  673. chart.padding(0, 0, 0, 0);
  674. // 保存图表实例和定时器
  675. this.charts.hdComplitedTicket = {
  676. chart: chart,
  677. interval: interval,
  678. };
  679. },
  680. // 初始化设备占比饼图
  681. initChartPercent() {
  682. const chart = am4core.create(this.$refs.chartPercent, am4charts.PieChart);
  683. chart.logo.disabled = true;
  684. chart.innerRadius = 30;
  685. // 配置系列
  686. const pieSeries = chart.series.push(new am4charts.PieSeries());
  687. pieSeries.dataFields.value = "size";
  688. pieSeries.dataFields.category = "sector";
  689. pieSeries.labels.template.disabled = true;
  690. pieSeries.ticks.template.disabled = true;
  691. pieSeries.colors.step = 3;
  692. this.charts.chartPercent = chart;
  693. },
  694. // 初始化存储使用趋势图
  695. initStorageChart() {
  696. const chart = am4core.create(this.$refs.storageChart, am4charts.XYChart);
  697. // 添加数据
  698. chart.data = [
  699. {
  700. date: "2012-03-01",
  701. price: 180,
  702. price1: 50,
  703. },
  704. {
  705. date: "2012-03-02",
  706. price: 252,
  707. price1: 40,
  708. },
  709. {
  710. date: "2012-03-03",
  711. price: 185,
  712. price1: 55,
  713. },
  714. {
  715. date: "2012-03-04",
  716. price: 110,
  717. price1: 38,
  718. },
  719. {
  720. date: "2012-03-05",
  721. price: 158,
  722. price1: 87,
  723. },
  724. {
  725. date: "2012-03-06",
  726. price: 200,
  727. price1: 67,
  728. },
  729. {
  730. date: "2012-03-07",
  731. price: 187,
  732. price1: 100,
  733. },
  734. ];
  735. // 配置X轴
  736. const dateAxis = chart.xAxes.push(new am4charts.DateAxis());
  737. dateAxis.renderer.grid.template.location = 0;
  738. dateAxis.renderer.grid.template.disabled = true;
  739. dateAxis.startLocation = 0.6;
  740. dateAxis.endLocation = 0.4;
  741. dateAxis.renderer.inside = true;
  742. dateAxis.renderer.labels.template.fill = am4core.color("#463699");
  743. // 配置Y轴
  744. const valueAxis = chart.yAxes.push(new am4charts.ValueAxis());
  745. valueAxis.logarithmic = true;
  746. valueAxis.renderer.minGridDistance = 0;
  747. valueAxis.renderer.inside = true;
  748. valueAxis.renderer.labels.template.disabled = true;
  749. // 配置第一个数据系列
  750. const series = chart.series.push(new am4charts.LineSeries());
  751. series.dataFields.valueY = "price";
  752. series.dataFields.dateX = "date";
  753. series.tensionX = 0.77;
  754. series.strokeWidth = 2;
  755. series.fillOpacity = 0.1;
  756. series.tooltipText = "{valueY.value}";
  757. series.fill = am4core.color("#19BCBF");
  758. series.stroke = am4core.color("#19BCBF");
  759. // 配置第二个数据系列
  760. const series1 = chart.series.push(new am4charts.LineSeries());
  761. series1.dataFields.valueY = "price1";
  762. series1.dataFields.dateX = "date";
  763. series1.tensionX = 0.77;
  764. series1.strokeWidth = 2;
  765. series1.fillOpacity = 0.1;
  766. series1.tooltipText = "{valueY.value}";
  767. series1.fill = am4core.color("#463699");
  768. series1.stroke = am4core.color("#463699");
  769. // 添加第一个系列的圆点
  770. const bullet = series.bullets.push(new am4charts.CircleBullet());
  771. bullet.circle.fill = am4core.color("#fff");
  772. bullet.circle.strokeWidth = 3;
  773. // 添加第二个系列的圆点
  774. const bullet1 = series1.bullets.push(new am4charts.CircleBullet());
  775. bullet1.circle.fill = am4core.color("#fff");
  776. bullet1.circle.strokeWidth = 3;
  777. // 添加光标
  778. chart.cursor = new am4charts.XYCursor();
  779. chart.cursor.fullWidthLineX = true;
  780. chart.cursor.xAxis = dateAxis;
  781. chart.cursor.lineX.strokeWidth = 0;
  782. chart.cursor.lineX.fill = am4core.color("#fff");
  783. chart.cursor.lineX.fillOpacity = 0;
  784. chart.padding(0, 0, 0, 0);
  785. this.charts.storageChart = chart;
  786. },
  787. // 修改initSiteVisitorChart方法
  788. initSiteVisitorChart() {
  789. // 如果已存在图表实例,先销毁它
  790. if (this.charts.siteVisitorChart) {
  791. this.charts.siteVisitorChart.dispose();
  792. }
  793. const chart = am4core.create(
  794. this.$refs.siteVisitorChart,
  795. am4charts.XYChart
  796. );
  797. chart.logo.disabled = true;
  798. chart.hiddenState.properties.opacity = 0;
  799. chart.padding(0, 0, 0, 0);
  800. chart.zoomOutButton.disabled = true;
  801. // 初始化数据
  802. const data = [];
  803. let visits = 10;
  804. for (let i = 0; i <= 30; i++) {
  805. visits -= Math.round(
  806. (Math.random() < 0.5 ? 1 : -1) * Math.random() * 10
  807. );
  808. data.push({
  809. date: new Date().setSeconds(i - 30),
  810. cpuValue: this.cpuValue || 0, //Math.abs(visits),
  811. gpuValue: this.GpuValue || 0,
  812. });
  813. }
  814. chart.data = data;
  815. // 配置X轴
  816. const dateAxis = chart.xAxes.push(new am4charts.DateAxis());
  817. dateAxis.renderer.grid.template.location = 0;
  818. dateAxis.renderer.minGridDistance = 30;
  819. dateAxis.dateFormats.setKey("second", "ss");
  820. dateAxis.periodChangeDateFormats.setKey("second", "[bold]h:mm a");
  821. dateAxis.periodChangeDateFormats.setKey("minute", "[bold]h:mm a");
  822. dateAxis.periodChangeDateFormats.setKey("hour", "[bold]h:mm a");
  823. dateAxis.renderer.inside = true;
  824. dateAxis.renderer.axisFills.template.disabled = true;
  825. dateAxis.renderer.ticks.template.disabled = true;
  826. dateAxis.interpolationDuration = 500;
  827. dateAxis.rangeChangeDuration = 500;
  828. // 配置Y轴
  829. const valueAxis = chart.yAxes.push(new am4charts.ValueAxis());
  830. valueAxis.tooltip.disabled = true;
  831. valueAxis.interpolationDuration = 500;
  832. valueAxis.rangeChangeDuration = 500;
  833. valueAxis.renderer.inside = true;
  834. valueAxis.renderer.minLabelPosition = 0.05;
  835. valueAxis.renderer.maxLabelPosition = 0.95;
  836. valueAxis.renderer.axisFills.template.disabled = true;
  837. valueAxis.renderer.ticks.template.disabled = true;
  838. // CPU 数据系列(红色)
  839. const cpuSeries = chart.series.push(new am4charts.LineSeries());
  840. cpuSeries.dataFields.dateX = "date";
  841. cpuSeries.dataFields.valueY = "cpuValue";
  842. cpuSeries.interpolationDuration = 500;
  843. cpuSeries.defaultState.transitionDuration = 0;
  844. cpuSeries.tensionX = 0.8;
  845. cpuSeries.stroke = am4core.color("#FF425C"); // 红色
  846. cpuSeries.strokeWidth = 2;
  847. // CPU 系列渐变
  848. cpuSeries.fillOpacity = 1;
  849. const cpuGradient = new am4core.LinearGradient();
  850. cpuGradient.addColor(am4core.color("#FF425C"), 0.2);
  851. cpuGradient.addColor(am4core.color("#FF425C"), 0);
  852. cpuSeries.fill = cpuGradient;
  853. // GPU 数据系列(蓝色)
  854. const gpuSeries = chart.series.push(new am4charts.LineSeries());
  855. gpuSeries.dataFields.dateX = "date";
  856. gpuSeries.dataFields.valueY = "gpuValue";
  857. gpuSeries.interpolationDuration = 500;
  858. gpuSeries.defaultState.transitionDuration = 0;
  859. gpuSeries.tensionX = 0.8;
  860. gpuSeries.stroke = am4core.color("#19BCBF"); // 蓝色
  861. gpuSeries.strokeWidth = 2;
  862. // GPU 系列渐变
  863. gpuSeries.fillOpacity = 1;
  864. const gpuGradient = new am4core.LinearGradient();
  865. gpuGradient.addColor(am4core.color("#19BCBF"), 0.2);
  866. gpuGradient.addColor(am4core.color("#19BCBF"), 0);
  867. gpuSeries.fill = gpuGradient;
  868. // CPU 圆点
  869. const cpuBullet = cpuSeries.createChild(am4charts.CircleBullet);
  870. cpuBullet.circle.radius = 5;
  871. cpuBullet.fillOpacity = 1;
  872. cpuBullet.fill = am4core.color("#FF425C");
  873. cpuBullet.isMeasured = false;
  874. // GPU 圆点
  875. const gpuBullet = gpuSeries.createChild(am4charts.CircleBullet);
  876. gpuBullet.circle.radius = 5;
  877. gpuBullet.fillOpacity = 1;
  878. gpuBullet.fill = am4core.color("#19BCBF");
  879. gpuBullet.isMeasured = false;
  880. // 更新两个系列的圆点位置
  881. cpuSeries.events.on("validated", () => {
  882. cpuBullet.moveTo(cpuSeries.dataItems.last.point);
  883. cpuBullet.validatePosition();
  884. });
  885. gpuSeries.events.on("validated", () => {
  886. gpuBullet.moveTo(gpuSeries.dataItems.last.point);
  887. gpuBullet.validatePosition();
  888. });
  889. // 设置初始缩放
  890. chart.events.on("datavalidated", () => {
  891. dateAxis.zoom({ start: 1 / 15, end: 1.2 }, false, true);
  892. });
  893. // 存储图表实例
  894. this.charts.siteVisitorChart = chart;
  895. /* // 启动数据更新
  896. setTimeout(() => {
  897. this.startVisitorDataInterval(cpuSeries, gpuSeries);
  898. }, 100); */
  899. // 修改数据更新逻辑
  900. this.startVisitorDataInterval(cpuSeries, gpuSeries);
  901. },
  902. // 修改 startVisitorDataInterval 方法
  903. startVisitorDataInterval(cpuSeries, gpuSeries) {
  904. // 清除已存在的定时器
  905. if (this.chartUpdateInterval) {
  906. clearInterval(this.chartUpdateInterval);
  907. }
  908. this.chartUpdateInterval = setInterval(() => {
  909. const lastDataItem = cpuSeries.dataItems.getIndex(
  910. cpuSeries.dataItems.length - 1
  911. );
  912. if (lastDataItem && lastDataItem.dateX) {
  913. cpuSeries.chart.addData(
  914. {
  915. date: new Date(lastDataItem.dateX.getTime() + 1000),
  916. cpuValue: this.cpuValue || 0,
  917. gpuValue: this.GpuValue || 0,
  918. },
  919. 1
  920. );
  921. }
  922. }, 1000);
  923. },
  924. // 添加清理方法
  925. cleanupCharts() {
  926. // 清除定时器
  927. if (this.chartUpdateInterval) {
  928. clearInterval(this.chartUpdateInterval);
  929. this.chartUpdateInterval = null;
  930. }
  931. // 清除图表实例
  932. if (this.charts.siteVisitorChart) {
  933. this.charts.siteVisitorChart.dispose();
  934. this.charts.siteVisitorChart = null;
  935. }
  936. },
  937. handleVisitorChartVisibility() {
  938. if (document.hidden) {
  939. if (this.charts.siteVisitorInterval) {
  940. clearInterval(this.charts.siteVisitorInterval);
  941. }
  942. } else {
  943. const series = this.charts.siteVisitorChart.series.getIndex(0);
  944. this.startVisitorDataInterval(series);
  945. }
  946. },
  947. // 初始化实时数据小图表
  948. initRealTimeCharts() {
  949. // 交易量图表
  950. const real4Chart = am4core.create(
  951. this.$refs.real4Chart,
  952. am4charts.XYChart
  953. );
  954. // ... 配置real4Chart
  955. this.charts.real4Chart = real4Chart;
  956. // 库存图表
  957. const real6Chart = am4core.create(
  958. this.$refs.real6Chart,
  959. am4charts.XYChart
  960. );
  961. // ... 配置real6Chart
  962. this.charts.real6Chart = real6Chart;
  963. // 跳出率图表
  964. const real1Chart = am4core.create(
  965. this.$refs.real1Chart,
  966. am4charts.XYChart
  967. );
  968. // ... 配置real1Chart
  969. this.charts.real1Chart = real1Chart;
  970. // 订单总额图表
  971. const real5Chart = am4core.create(
  972. this.$refs.real5Chart,
  973. am4charts.XYChart
  974. );
  975. // ... 配置real5Chart
  976. this.charts.real5Chart = real5Chart;
  977. // 收入图表
  978. const real2Chart = am4core.create(
  979. this.$refs.real2Chart,
  980. am4charts.XYChart
  981. );
  982. // ... 配置real2Chart
  983. this.charts.real2Chart = real2Chart;
  984. // 新购买图表
  985. const real3Chart = am4core.create(
  986. this.$refs.real3Chart,
  987. am4charts.XYChart
  988. );
  989. // ... 配置real3Chart
  990. this.charts.real3Chart = real3Chart;
  991. },
  992. initTransactionCharts() {
  993. // Transactions 1 Chart
  994. const transactions1 = am4core.create(
  995. this.$refs.transactions1,
  996. am4charts.XYChart
  997. );
  998. transactions1.data = this.generateTransactionData("#19BCBF");
  999. this.configureTransactionChart(transactions1);
  1000. this.charts.transactions1 = transactions1;
  1001. // Transactions 2 Chart
  1002. const transactions2 = am4core.create(
  1003. this.$refs.transactions2,
  1004. am4charts.XYChart
  1005. );
  1006. transactions2.data = this.generateTransactionData("#FF425C");
  1007. this.configureTransactionChart(transactions2);
  1008. this.charts.transactions2 = transactions2;
  1009. },
  1010. generateTransactionData(color) {
  1011. const data = [];
  1012. for (let i = 0; i <= 12; i++) {
  1013. data.push({
  1014. category: i,
  1015. value: Math.random() * 30 + 10,
  1016. color: color,
  1017. });
  1018. }
  1019. return data;
  1020. },
  1021. configureTransactionChart(chart) {
  1022. // Create axes
  1023. const categoryAxis = chart.xAxes.push(new am4charts.CategoryAxis());
  1024. categoryAxis.dataFields.category = "category";
  1025. categoryAxis.renderer.grid.template.disabled = true;
  1026. categoryAxis.renderer.labels.template.disabled = true;
  1027. categoryAxis.renderer.minGridDistance = 10;
  1028. const valueAxis = chart.yAxes.push(new am4charts.ValueAxis());
  1029. valueAxis.renderer.grid.template.disabled = true;
  1030. valueAxis.renderer.labels.template.disabled = true;
  1031. valueAxis.min = 0;
  1032. valueAxis.max = 50;
  1033. // Create series
  1034. const series = chart.series.push(new am4charts.ColumnSeries());
  1035. series.dataFields.valueY = "value";
  1036. series.dataFields.categoryX = "category";
  1037. series.columns.template.propertyFields.fill = "color";
  1038. series.columns.template.propertyFields.stroke = "color";
  1039. series.columns.template.width = am4core.percent(40);
  1040. // Add tooltip
  1041. series.tooltipText = "{valueY}";
  1042. chart.cursor = new am4charts.XYCursor();
  1043. chart.cursor.behavior = "none";
  1044. // Remove padding
  1045. chart.paddingBottom = 0;
  1046. chart.paddingTop = 0;
  1047. chart.paddingLeft = 0;
  1048. chart.paddingRight = 0;
  1049. },
  1050. initDeviceChart() {
  1051. const chart = am4core.create(this.$refs.deviceChart, am4charts.PieChart);
  1052. // 添加数据
  1053. chart.data = [
  1054. { sector: "Mobile", size: 14.6 },
  1055. { sector: "Tablet", size: 9.3 },
  1056. { sector: "Desktop", size: 22.5 },
  1057. ];
  1058. // 设置内圆半径
  1059. chart.innerRadius = 30;
  1060. // 添加并配置饼图系列
  1061. const pieSeries = chart.series.push(new am4charts.PieSeries());
  1062. pieSeries.dataFields.value = "size";
  1063. pieSeries.dataFields.category = "sector";
  1064. pieSeries.labels.template.disabled = true;
  1065. pieSeries.ticks.template.disabled = true;
  1066. pieSeries.colors.step = 2;
  1067. // 保存图表实例
  1068. this.charts.deviceChart = chart;
  1069. },
  1070. /* 初始化 */
  1071. init() {
  1072. structure_count().then((res) => {
  1073. if (res.status !== 200) return;
  1074. this.kenList = res.data;
  1075. if (this.kenList.length > 0) {
  1076. this.kenValue = this.kenList[0].id;
  1077. this.kenVal = this.kenList[0];
  1078. // 处理文件类型统计数据
  1079. this.updateChartPercentData(this.kenList[0]);
  1080. }
  1081. });
  1082. GET_Rbq().then((res) => {
  1083. console.log(res);
  1084. });
  1085. /* 获取文档数量 */
  1086. searchlistDoc({ page: 1, pageSize: 9999 }).then((res) => {
  1087. this.docTotal = res.data.dataList.length;
  1088. });
  1089. searchList({ page: 1, pageSize: 9999 }).then((res) => {
  1090. if (!res) return;
  1091. this.temTotal = res.data.dataList.length;
  1092. });
  1093. pageTemplate({ page: 1, pageSize: 9999 }).then((res) => {
  1094. if (!res) return;
  1095. this.pageTotal = res.data.dataList.length;
  1096. });
  1097. /* 获取文件解析数量 */
  1098. doc_status().then((res) => {
  1099. this.docList = res.data.data.run_stats;
  1100. console.log(this.docList.run_3);
  1101. });
  1102. },
  1103. /* 选择知识库 */
  1104. kenChange(val) {
  1105. this.kenVal = this.kenList.find((el) => el.id === val);
  1106. // 更新图表数据
  1107. this.updateChartPercentData(this.kenVal);
  1108. },
  1109. // 更新文件类型统计数据
  1110. updateChartPercentData(kenData) {
  1111. if (!kenData) return;
  1112. // 获取所有的 file_type_stats
  1113. const allFileStats = this.getAllFileTypeStats(kenData);
  1114. // 合并所有的文件统计数据
  1115. const mergedFileStats = this.mergeFileStats(allFileStats);
  1116. // 保存上一次的数据用于计算变化率
  1117. this.previousCounts = { ...this.fileTypeCounts };
  1118. // 更新当前数据
  1119. this.fileTypeCounts = mergedFileStats;
  1120. // 计算总文件数和百分比
  1121. const totalFiles = Object.values(mergedFileStats).reduce(
  1122. (a, b) => a + b,
  1123. 0
  1124. );
  1125. // 转换数据格式
  1126. const chartData = Object.entries(mergedFileStats).map(
  1127. ([type, count]) => ({
  1128. sector: type.toUpperCase(),
  1129. size: count /* Number(((count / totalFiles) * 100).toFixed(1)) */,
  1130. count: count,
  1131. })
  1132. );
  1133. // 更新图表数据
  1134. if (this.charts.chartPercent) {
  1135. this.charts.chartPercent.data = chartData;
  1136. }
  1137. },
  1138. // 获取文件类型对应的图标
  1139. getFileTypeIcon(type) {
  1140. const icons = {
  1141. pdf: "icon-file-text",
  1142. docx: "icon-file",
  1143. xlsx: "icon-file",
  1144. };
  1145. return icons[type.toLowerCase()] || "icon-file";
  1146. },
  1147. // 获取文件类型对应的颜色
  1148. getFileTypeColor(type) {
  1149. const colors = {
  1150. pdf: "#dc67ce",
  1151. docx: "#8067dc",
  1152. xlsx: "#67b7dc",
  1153. };
  1154. return colors[type.toLowerCase()] || "#666666";
  1155. },
  1156. // 计算变化率
  1157. getChangePercentage(type) {
  1158. const currentCount = this.fileTypeCounts[type] || 0;
  1159. const previousCount = this.previousCounts[type] || currentCount;
  1160. if (previousCount === 0) return "0%";
  1161. const change = ((currentCount - previousCount) / previousCount) * 100;
  1162. return `${Math.abs(change).toFixed(1)}%`;
  1163. },
  1164. // 获取变化率的图标
  1165. getChangeIcon(type) {
  1166. const currentCount = this.fileTypeCounts[type] || 0;
  1167. const previousCount = this.previousCounts[type] || currentCount;
  1168. return currentCount >= previousCount
  1169. ? "icon-arrow-up"
  1170. : "icon-arrow-down";
  1171. },
  1172. // 获取变化率的样式类
  1173. getChangeClass(type) {
  1174. const currentCount = this.fileTypeCounts[type] || 0;
  1175. const previousCount = this.previousCounts[type] || currentCount;
  1176. return currentCount >= previousCount ? "text-c-green" : "text-c-red";
  1177. },
  1178. // 递归获取所有的 file_type_stats
  1179. getAllFileTypeStats(data) {
  1180. let results = [];
  1181. // 处理当前层级的 file_type_stats
  1182. if (data.file_type_stats) {
  1183. results.push(data.file_type_stats);
  1184. }
  1185. // 处理 directories 中的 file_type_stats
  1186. if (data.directories && Array.isArray(data.directories)) {
  1187. data.directories.forEach((dir) => {
  1188. results = results.concat(this.getAllFileTypeStats(dir));
  1189. });
  1190. }
  1191. return results;
  1192. },
  1193. // 合并多个 file_type_stats 对象
  1194. mergeFileStats(fileStatsList) {
  1195. return fileStatsList.reduce((merged, current) => {
  1196. Object.entries(current).forEach(([type, count]) => {
  1197. merged[type] = (merged[type] || 0) + count;
  1198. });
  1199. return merged;
  1200. }, {});
  1201. },
  1202. },
  1203. // 修改beforeDestroy钩子
  1204. beforeDestroy() {
  1205. // 清理所有图表实例
  1206. Object.values(this.charts).forEach((chart) => {
  1207. if (chart) {
  1208. if (typeof chart.dispose === "function") {
  1209. chart.dispose();
  1210. } else if (typeof chart === "number") {
  1211. clearInterval(chart);
  1212. }
  1213. }
  1214. });
  1215. this.closeWebSocket(); // 组件销毁前关闭 WebSocket
  1216. // 移除事件监听器
  1217. document.removeEventListener(
  1218. "visibilitychange",
  1219. this.handleVisitorChartVisibility
  1220. );
  1221. },
  1222. };
  1223. </script>
  1224. <style lang="scss" scoped>
  1225. .widget-chart {
  1226. height: calc(100vh - 85px);
  1227. padding: 15px;
  1228. font-family: "Roboto", sans-serif;
  1229. font-size: 14px;
  1230. color: #686c71;
  1231. font-weight: 400;
  1232. background: #f8f9fa;
  1233. a {
  1234. text-decoration: none;
  1235. }
  1236. // 卡片基础样式
  1237. .card {
  1238. border-radius: 2px;
  1239. box-shadow: 0 0px 0 1px #e2e5e8;
  1240. border: none;
  1241. margin-bottom: 24px;
  1242. background: #fff;
  1243. .card-body {
  1244. padding: 1.5rem;
  1245. h6 {
  1246. color: #111;
  1247. font-size: 16px;
  1248. .text-primary {
  1249. color: #19bcbf !important;
  1250. }
  1251. .text-danger {
  1252. color: #ff425c !important;
  1253. }
  1254. }
  1255. }
  1256. }
  1257. // 文字样式
  1258. h2 {
  1259. font-size: 28px;
  1260. font-weight: 600;
  1261. color: #333;
  1262. margin: 0;
  1263. }
  1264. h6 {
  1265. font-size: 14px;
  1266. color: #6c757d;
  1267. margin-bottom: 0.5rem;
  1268. }
  1269. // 图标样式
  1270. .feather {
  1271. width: 20px;
  1272. height: 20px;
  1273. vertical-align: text-bottom;
  1274. }
  1275. // 颜色方案
  1276. .text-c-blue {
  1277. color: #19bcbf;
  1278. }
  1279. .text-c-green {
  1280. color: #2ed8b6;
  1281. }
  1282. .text-c-red {
  1283. color: #ff5370;
  1284. }
  1285. .text-c-purple {
  1286. color: #8067dc;
  1287. }
  1288. // 下拉菜单样式
  1289. .dropdown-toggle {
  1290. &:after {
  1291. margin-left: 0.5em;
  1292. }
  1293. }
  1294. .dropdown-menu {
  1295. border-radius: 4px;
  1296. box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15);
  1297. .dropdown-item {
  1298. padding: 0.5rem 1rem;
  1299. &:hover {
  1300. background-color: #f8f9fa;
  1301. }
  1302. }
  1303. }
  1304. // 图表容器样式
  1305. [ref="hdComplitedTicket"],
  1306. [ref="chartPercent"],
  1307. [ref="storageChart"] {
  1308. margin: 1rem 0;
  1309. }
  1310. // 小型图表样式
  1311. [ref^="real"] {
  1312. height: 20px;
  1313. width: 100%;
  1314. margin-top: 0.5rem;
  1315. }
  1316. // 统计数字样式
  1317. .mt-2 {
  1318. h3 {
  1319. font-size: 24px;
  1320. font-weight: 600;
  1321. color: #333;
  1322. margin: 0.5rem 0;
  1323. }
  1324. }
  1325. // 图标指标样式
  1326. .fas.fa-circle {
  1327. font-size: 10px;
  1328. margin-right: 8px;
  1329. &.text-c-blue {
  1330. color: #19bcbf;
  1331. }
  1332. &.text-c-purple {
  1333. color: #8067dc;
  1334. }
  1335. }
  1336. }
  1337. // 响应式布局调整
  1338. @media (max-width: 768px) {
  1339. .widget-chart {
  1340. .card {
  1341. margin-bottom: 16px;
  1342. }
  1343. }
  1344. }
  1345. </style>
  1346. <style src="./css/bootstrap.min.css" scoped></style>