index.vue 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802
  1. <template>
  2. <div class="application-monitor">
  3. <!-- 右侧主内容区 -->
  4. <div class="main-content">
  5. <!-- 应用统计卡片 -->
  6. <div class="stats-cards">
  7. <div class="stat-card" v-for="item in getLimitedList()" :key="item.name">
  8. <div class="stat-icon approved">
  9. <el-icon><CircleCheckFilled /></el-icon>
  10. </div>
  11. <div class="stat-content">
  12. <div class="stat-label">{{ item.name }}</div>
  13. <div class="stat-number">{{ item.value }} 个</div>
  14. </div>
  15. </div>
  16. <!-- <div class="stat-card">
  17. <div class="stat-icon approved">
  18. <el-icon><CircleCheckFilled /></el-icon>
  19. </div>
  20. <div class="stat-content">
  21. <div class="stat-label">已审批应用</div>
  22. <div class="stat-number">490个</div>
  23. </div>
  24. </div>
  25. <div class="stat-card">
  26. <div class="stat-icon pending">
  27. <el-icon><Clock /></el-icon>
  28. </div>
  29. <div class="stat-content">
  30. <div class="stat-label">待上架应用</div>
  31. <div class="stat-number">168个</div>
  32. </div>
  33. </div>
  34. <div class="stat-card">
  35. <div class="stat-icon online">
  36. <el-icon><UploadFilled /></el-icon>
  37. </div>
  38. <div class="stat-content">
  39. <div class="stat-label">已上架应用</div>
  40. <div class="stat-number">43个</div>
  41. </div>
  42. </div>
  43. <div class="stat-card">
  44. <div class="stat-icon offline">
  45. <el-icon><DeleteFilled /></el-icon>
  46. </div>
  47. <div class="stat-content">
  48. <div class="stat-label">已下架应用</div>
  49. <div class="stat-number">20个</div>
  50. </div>
  51. </div> -->
  52. </div>
  53. <!-- 内容模块 -->
  54. <div class="content-modules">
  55. <!-- 应用状态分布 -->
  56. <div class="module">
  57. <div class="module-header">
  58. <h3 class="module-title">应用状态分布</h3>
  59. </div>
  60. <div class="module-content">
  61. <div ref="statusChart" class="chart-container"></div>
  62. </div>
  63. </div>
  64. <!-- 快捷应用 -->
  65. <div class="module">
  66. <div class="module-header">
  67. <h3 class="module-title">应用趋势图</h3>
  68. <el-select v-model="selectedAppRange" size="small" style="width: 120px;" @change="handleAppChange">
  69. <el-option
  70. v-for="item in selectAapplications"
  71. :key="item.id"
  72. :label="item.title"
  73. :value="item.title"
  74. />
  75. </el-select>
  76. <div>
  77. <el-date-picker
  78. size="small"
  79. :clearable="false"
  80. v-model="datetimeValue"
  81. type="daterange"
  82. @change="handleDateChange"
  83. :shortcuts="shortcuts"
  84. format="YYYY-MM-DD"
  85. range-separator="到"
  86. start-placeholder="开始时间"
  87. end-placeholder="结束时间"
  88. />
  89. </div>
  90. <!-- <a href="#" class="module-more">更多</a> -->
  91. </div>
  92. <div class="module-content">
  93. <div ref="lineChartRes" class="chart-container"></div>
  94. <!-- <div class="quick-app" v-for="(app, index) in quickApps" :key="index">
  95. <div class="app-item">
  96. <span class="app-icon" :style="{ backgroundColor: getAppColor(app.type) }"
  97. ><el-icon><Promotion /></el-icon
  98. ></span>
  99. <span class="app-name">{{ app.name }}</span>
  100. </div>
  101. </div> -->
  102. </div>
  103. </div>
  104. <!-- 应用上新 -->
  105. <div class="module">
  106. <div class="module-header">
  107. <h3 class="module-title">应用上新</h3>
  108. <!-- <a href="#" class="module-more">更多</a> -->
  109. </div>
  110. <div class="module-content">
  111. <div class="new-app" v-for="(app, index) in newApplications" :key="index">
  112. <div class="app-info">
  113. <span class="app-name">{{ app.title }} </span>
  114. <span class="app-date">{{ app.createTime }}</span>
  115. </div>
  116. </div>
  117. </div>
  118. </div>
  119. <!-- 应用热度排行TOP5 -->
  120. <div class="module">
  121. <div class="module-header">
  122. <h3 class="module-title">应用热度排行TOP5</h3>
  123. <!-- <a href="#" class="module-more">更多</a> -->
  124. </div>
  125. <div class="module-content">
  126. <div class="hot-app" v-for="(app, index) in hotApps" :key="index">
  127. <div class="app-rank">
  128. <span class="rank-number">{{ index + 1 }}</span>
  129. <span class="app-name">{{ app.name }}</span>
  130. </div>
  131. <div class="app-stats">
  132. <!-- <div class="stat-bar" :style="{ width: app.percentage + '%' }"></div> -->
  133. <div style="width: 100%">
  134. <el-progress :percentage="app.percentage" />
  135. </div>
  136. <span class="stat-count">{{ app.count }}次</span>
  137. </div>
  138. </div>
  139. </div>
  140. </div>
  141. </div>
  142. </div>
  143. </div>
  144. </template>
  145. <script>
  146. import * as echarts from "echarts";
  147. import appCenter from "@/api/appCenter";
  148. import { topApp,countAppInfo } from "@/api/count";
  149. import moment from "moment";
  150. export default {
  151. name: "ApplicationMonitor",
  152. components: {},
  153. data() {
  154. return {
  155. statusChart: null,
  156. lineChart: null,
  157. itemApplications: [],
  158. applications: [],
  159. newApplications: [],
  160. statusOptions: [],
  161. buffOptions: [],
  162. typeArrs: [],
  163. selectAapplications: [],
  164. selectedAppRange: '',
  165. datetimeValue:[new Date(new Date(new Date().setMonth(new Date().getMonth() - 1))).setDate(new Date().getDate() + 1),new Date()],// 默认显示最近一个月
  166. shortcuts: [
  167. {
  168. text: '当天',
  169. value: () => {
  170. const end = new Date()
  171. const start = new Date()
  172. start.setDate(start.getDate())
  173. return [start, end]
  174. },
  175. },
  176. {
  177. text: '最近一周',
  178. value: () => {
  179. const end = new Date()
  180. const start = new Date()
  181. start.setDate(start.getDate() - 7)
  182. start.setDate(start.getDate() + 1)
  183. return [start, end]
  184. },
  185. },
  186. {
  187. text: '最近1月',
  188. value: () => {
  189. const end = new Date()
  190. const start = new Date()
  191. start.setMonth(start.getMonth() - 1)
  192. start.setDate(start.getDate() + 1)
  193. return [start, end]
  194. },
  195. },
  196. {
  197. text: '最近1年',
  198. value: () => {
  199. const end = new Date()
  200. const start = new Date()
  201. start.setFullYear(start.getFullYear() - 1)
  202. start.setDate(start.getDate() + 1)
  203. return [start, end]
  204. },
  205. },
  206. // {
  207. // text: 'Last 3 months',
  208. // value: () => {
  209. // const end = new Date()
  210. // const start = new Date()
  211. // start.setMonth(start.getMonth() - 3)
  212. // return [start, end]
  213. // },
  214. // },
  215. ],
  216. // 快捷应用数据
  217. quickApps: [
  218. { name: "农产品安全管控示范应用场", type: "green" },
  219. { name: "崇明区乡村振兴一张图", type: "yellow" },
  220. { name: "数字孪生黄浦", type: "blue" },
  221. ],
  222. // 应用热度排行数据
  223. hotApps: [],
  224. };
  225. },
  226. mounted() {
  227. this.initData();
  228. window.addEventListener("resize", this.handleResize);
  229. },
  230. beforeUnmount() {
  231. window.removeEventListener("resize", this.handleResize);
  232. this.destroyCharts();
  233. },
  234. methods: {
  235. initData() {
  236. this.getDmsCNameAType("applevel");
  237. this.getDmsCNameAType("appstatus");
  238. this.getDmsDataList();
  239. let param = {
  240. num: 6,
  241. start: '2000-01-01',
  242. end: moment().format("YYYY-MM-DD"),
  243. }
  244. this.getAppCount(param);
  245. },
  246. // 获取应用统计数据
  247. getAppCount(param) {
  248. let that = this;
  249. that.hotApps =[]
  250. topApp(param.num,param.start,param.end).then((res) => {
  251. let maxv = 100;
  252. res.forEach((item,index) => {
  253. if(index === 0){
  254. maxv = item.count;
  255. }
  256. item.percentage = (item.count/maxv*100).toFixed(1);
  257. let str = { name: item.name.trim() == '' ? '其他' : item.name, count: item.count, percentage: item.percentage }
  258. that.hotApps.push(str);
  259. });
  260. // console.log(that.hotApps);
  261. });
  262. },
  263. handleAppChange(){
  264. // console.log(this.selectedAppRange);
  265. this.getCountAppInfo();
  266. },
  267. handleDateChange(){
  268. // console.log(this.datetimeValue);
  269. this.getCountAppInfo();
  270. },
  271. //获取应用统计Charts图表
  272. getCountAppInfo(){
  273. let start = moment(this.datetimeValue[0]).format("YYYY-MM-DD 00:00:00")
  274. let end = moment(this.datetimeValue[1]).format("YYYY-MM-DD 23:59:59")
  275. countAppInfo(start,end,this.selectedAppRange).then((res) => {
  276. // console.log(res);
  277. this.drawLineChart(res);
  278. })
  279. },
  280. drawLineChart(data){
  281. let that = this;
  282. let times = []
  283. let values = []
  284. data.forEach((item,index) => {
  285. times.push(moment(item.time).format("YYYY-MM-DD"))
  286. values.push(item.count)
  287. })
  288. // 基于准备好的dom,初始化echarts实例
  289. let lineChart = echarts.init(this.$refs.lineChartRes);
  290. const option = {
  291. tooltip: {
  292. trigger: "axis",
  293. axisPointer: { type: "shadow" },
  294. backgroundColor: 'rgba(0, 25, 50, 0.8)',
  295. borderColor: '#1E90FF',
  296. textStyle: {
  297. color: '#fff'
  298. },
  299. formatter: function (params) {
  300. let str = '';
  301. params.forEach(item => {
  302. str += `${item.name} <br/> ${item.seriesName}: ${item.value}次<br/>`;
  303. });
  304. return str;
  305. }
  306. },
  307. grid: {
  308. left: '3%',
  309. right: '4%',
  310. bottom: '3%',
  311. containLabel: true
  312. },
  313. xAxis: {
  314. type: 'category',
  315. data: times,
  316. axisLabel: {
  317. color: '#a3b6c7'
  318. },
  319. axisLine: {
  320. lineStyle: {
  321. color: '#1E90FF'
  322. }
  323. }
  324. },
  325. yAxis: {
  326. type: 'value',
  327. axisLabel: {
  328. color: '#a3b6c7'
  329. },
  330. axisLine: {
  331. lineStyle: {
  332. color: '#1E90FF'
  333. }
  334. },
  335. splitLine: {
  336. lineStyle: {
  337. color: 'rgba(30, 144, 255, 0.2)'
  338. }
  339. }
  340. },
  341. series: [
  342. {
  343. name: "调用次数",
  344. type: 'line',
  345. data: values,
  346. itemStyle: {
  347. color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
  348. { offset: 0, color: '#1E90FF' },
  349. { offset: 1, color: '#00BFFF' }
  350. ])
  351. }
  352. }
  353. ]
  354. }
  355. // 绘制图表
  356. lineChart.setOption(option);
  357. that.lineChart = lineChart;
  358. },
  359. handleResize() {
  360. if (this.statusChart) this.statusChart.resize();
  361. if (this.lineChart) this.lineChart.resize();
  362. },
  363. destroyCharts() {
  364. if (this.statusChart) {
  365. this.statusChart.dispose();
  366. this.statusChart = null;
  367. }
  368. if (this.lineChart) {
  369. this.lineChart.dispose();
  370. this.lineChart = null;
  371. }
  372. },
  373. getLimitedList() {
  374. return this.typeArrs.slice(0, 4); // 获取前四个元素
  375. },
  376. getDmsCNameAType(param) {
  377. if (param === "applevel") {
  378. this.buffOptions = [];
  379. let obj = this.$getDmsTypes("applevel");
  380. for(let key in obj){
  381. this.buffOptions.push({
  382. label: key,
  383. value: obj[key]
  384. })
  385. }
  386. } else if (param === "appstatus") {
  387. this.statusOptions = [];
  388. let obj = this.$getDmsTypes("appstatus");
  389. for(let key in obj){
  390. this.statusOptions.push({
  391. label: key,
  392. value: obj[key]
  393. })
  394. }
  395. }
  396. // let requestParams = {
  397. // cName: param,
  398. // type: 0,
  399. // };
  400. // appCenter.getDmsCNameAType(requestParams).then((res) => {
  401. // if (res.code === 200 && res.content) {
  402. // let option = res.content.map((item) => ({
  403. // label: item.index,
  404. // value: item.name,
  405. // }));
  406. // if (param === "applevel") {
  407. // this.buffOptions = option;
  408. // } else if (param === "appstatus") {
  409. // this.statusOptions = option;
  410. // }
  411. // }
  412. // });
  413. },
  414. getDmsDataList() {
  415. let requestParams = {
  416. columnId: systemConfig.columnIds[1], // 应用中心栏目id
  417. states: 0,
  418. orderBy: JSON.stringify([{ field: "create_time", orderByType: 2 }]),
  419. pageSize: 9999,
  420. page: 0,
  421. };
  422. appCenter.getDmsDataList(requestParams).then((res) => {
  423. if (res.code === 200) {
  424. this.itemApplications = res.content.data.map((item) => ({
  425. ...item,
  426. status:
  427. item.status === 0
  428. ? "待审核"
  429. : item.status === 1
  430. ? "待发布"
  431. : item.status === 2
  432. ? "未完成"
  433. : "已完成",
  434. buffName:
  435. this.buffOptions.find((info) => info.label == item.appbuff.trim())?.value ||
  436. "",
  437. appstautName:
  438. this.statusOptions.find((info) => info.label == item.appstauts.trim())
  439. ?.value || "",
  440. createTime: moment(item.create_time).format("YYYY-MM-DD HH:mm:ss"),
  441. }));
  442. this.applications = this.itemApplications;
  443. this.selectAapplications = this.applications.filter((item) => item.yylx == "0"); //yylx 0:内部 1:外部
  444. this.newApplications = this.applications.slice(0, 10); // 获取前十个元素
  445. this.selectedAppRange = this.applications[0].title;
  446. this.getTypeArrs();
  447. this.getCountAppInfo();
  448. } else {
  449. this.applications = [];
  450. this.selectAapplications = [];
  451. this.selectedAppRange = '';
  452. }
  453. });
  454. },
  455. getTypeArrs() {
  456. let that = this;
  457. that.statusOptions.forEach((item) => {
  458. let arr = this.applications.filter((info) => info.appstauts == item.label);
  459. let name = item.value,length = arr.length;
  460. let str = { name: name, value: length };
  461. that.typeArrs.push(str);
  462. });
  463. this.initStatusChart();
  464. },
  465. initStatusChart() {
  466. let colors = ["#52C41A", "#FAAD14", "#F5222D", "#1E90FF"];
  467. let seriesData = this.typeArrs.map((item, index) => ({
  468. // value: item.value, name: item.name, itemStyle: { color: colors[index] } //自定义颜色
  469. value: item.value,
  470. name: item.name, // 随机颜色
  471. }));
  472. seriesData = seriesData.filter((item) => item.value > 0);// 过滤掉值为0的项
  473. this.statusChart = echarts.init(this.$refs.statusChart);
  474. const option = {
  475. tooltip: {
  476. trigger: "item",
  477. backgroundColor: "rgba(0, 25, 50, 0.8)",
  478. borderColor: "#1E90FF",
  479. textStyle: {
  480. color: "#fff",
  481. },
  482. },
  483. legend: {
  484. orient: "horizontal",
  485. bottom: 0,
  486. textStyle: {
  487. color: "#a3b6c7",
  488. },
  489. },
  490. series: [
  491. {
  492. name: "应用状态分布",
  493. type: "pie",
  494. radius: ["40%", "70%"],
  495. avoidLabelOverlap: false,
  496. itemStyle: {
  497. borderRadius: 10,
  498. borderColor: "#0A192F",
  499. borderWidth: 2,
  500. },
  501. label: {
  502. show: false,
  503. position: "center",
  504. },
  505. emphasis: {
  506. label: {
  507. show: true,
  508. fontSize: 20,
  509. fontWeight: "bold",
  510. color: "#fff",
  511. },
  512. },
  513. labelLine: {
  514. show: false,
  515. },
  516. data: seriesData,
  517. },
  518. ],
  519. };
  520. this.statusChart.setOption(option);
  521. },
  522. // 获取应用图标的背景颜色
  523. getAppColor(type) {
  524. const colorMap = {
  525. green: "rgba(103, 194, 58, 0.2)",
  526. yellow: "rgba(230, 162, 60, 0.2)",
  527. blue: "rgba(64, 158, 255, 0.2)",
  528. };
  529. return colorMap[type] || "rgba(64, 158, 255, 0.2)";
  530. },
  531. },
  532. };
  533. </script>
  534. <style lang="less" scoped>
  535. .application-monitor {
  536. width: 100%;
  537. background-color: #08224a;
  538. color: #ffffff;
  539. }
  540. /* 右侧主内容区 */
  541. .main-content {
  542. flex: 1;
  543. padding: 20px 30px;
  544. overflow-y: auto;
  545. }
  546. /* 统计卡片 */
  547. .stats-cards {
  548. display: flex;
  549. gap: 20px;
  550. margin-bottom: 20px;
  551. flex-wrap: wrap;
  552. }
  553. .stat-card {
  554. flex: 1;
  555. min-width: 200px;
  556. height: 100px;
  557. background-color: rgba(255, 255, 255, 0.05);
  558. border-radius: 8px;
  559. padding: 20px;
  560. display: flex;
  561. align-items: center;
  562. gap: 15px;
  563. transition: all 0.3s ease;
  564. &:hover {
  565. background-color: rgba(255, 255, 255, 0.08);
  566. }
  567. }
  568. .stat-icon {
  569. width: 60px;
  570. height: 60px;
  571. border-radius: 50%;
  572. display: flex;
  573. align-items: center;
  574. justify-content: center;
  575. font-size: 24px;
  576. &.approved {
  577. background-color: rgba(64, 158, 255, 0.2);
  578. color: #409eff;
  579. }
  580. &.pending {
  581. background-color: rgba(230, 162, 60, 0.2);
  582. color: #e6a23c;
  583. }
  584. &.online {
  585. background-color: rgba(103, 194, 58, 0.2);
  586. color: #67c23a;
  587. }
  588. &.offline {
  589. background-color: rgba(245, 108, 108, 0.2);
  590. color: #f56c6c;
  591. }
  592. }
  593. .stat-content {
  594. display: flex;
  595. flex-direction: column;
  596. gap: 5px;
  597. }
  598. .stat-label {
  599. font-size: 14px;
  600. color: rgba(255, 255, 255, 0.6);
  601. }
  602. .stat-number {
  603. font-size: 28px;
  604. font-weight: bold;
  605. color: #ffffff;
  606. }
  607. /* 内容模块 */
  608. .content-modules {
  609. display: grid;
  610. grid-template-columns: 1fr 1fr;
  611. grid-template-rows: 1fr 1fr;
  612. gap: 20px;
  613. }
  614. /* 模块通用样式 */
  615. .module {
  616. background-color: rgba(255, 255, 255, 0.05);
  617. border-radius: 8px;
  618. overflow: hidden;
  619. }
  620. .module-header {
  621. padding: 15px 20px;
  622. display: flex;
  623. justify-content: space-between;
  624. align-items: center;
  625. border-bottom: 1px solid rgba(255, 255, 255, 0.1);
  626. }
  627. .module-title {
  628. font-size: 16px;
  629. font-weight: bold;
  630. margin: 0;
  631. color: #ffffff;
  632. }
  633. .module-more {
  634. font-size: 12px;
  635. color: rgba(255, 255, 255, 0.6);
  636. text-decoration: none;
  637. &:hover {
  638. color: #409eff;
  639. }
  640. }
  641. .module-content {
  642. padding: 20px;
  643. height: 320px;
  644. overflow: auto;
  645. }
  646. /* 应用状态分布图 */
  647. .chart-container {
  648. width: 100%;
  649. height: 300px;
  650. }
  651. /* 快捷应用 */
  652. .quick-app {
  653. margin-bottom: 15px;
  654. }
  655. .app-item {
  656. display: flex;
  657. align-items: center;
  658. gap: 10px;
  659. padding: 10px;
  660. background-color: rgba(255, 255, 255, 0.05);
  661. border-radius: 6px;
  662. transition: all 0.3s ease;
  663. &:hover {
  664. background-color: rgba(255, 255, 255, 0.08);
  665. }
  666. }
  667. .app-icon {
  668. width: 32px;
  669. height: 32px;
  670. border-radius: 4px;
  671. display: flex;
  672. align-items: center;
  673. justify-content: center;
  674. color: #ffffff;
  675. font-size: 14px;
  676. }
  677. .app-name {
  678. font-size: 14px;
  679. color: rgba(255, 255, 255, 0.8);
  680. }
  681. /* 应用上新 */
  682. .new-app {
  683. // margin-bottom: 15px;
  684. // padding-bottom: 15px;
  685. padding: 6px 0px 6px 0px;
  686. border-bottom: 1px solid rgba(255, 255, 255, 0.05);
  687. &:last-child {
  688. margin-bottom: 0;
  689. padding-bottom: 0;
  690. border-bottom: none;
  691. }
  692. }
  693. .app-info {
  694. display: flex;
  695. justify-content: space-between;
  696. align-items: center;
  697. }
  698. .app-date {
  699. font-size: 12px;
  700. color: rgba(255, 255, 255, 0.6);
  701. }
  702. /* 使用热度排行 */
  703. .hot-app {
  704. margin-bottom: 20px;
  705. display: flex;
  706. flex-direction: column;
  707. gap: 8px;
  708. }
  709. .app-rank {
  710. display: flex;
  711. align-items: center;
  712. gap: 10px;
  713. }
  714. .rank-number {
  715. width: 20px;
  716. height: 20px;
  717. border-radius: 50%;
  718. background-color: rgba(255, 255, 255, 0.1);
  719. display: flex;
  720. align-items: center;
  721. justify-content: center;
  722. font-size: 12px;
  723. font-weight: bold;
  724. }
  725. .app-stats {
  726. display: flex;
  727. align-items: center;
  728. gap: 10px;
  729. }
  730. .stat-bar {
  731. flex: 1;
  732. height: 6px;
  733. background-color: #409eff;
  734. border-radius: 3px;
  735. }
  736. .stat-count {
  737. font-size: 12px;
  738. color: rgba(255, 255, 255, 0.6);
  739. min-width: 100px;
  740. text-align: right;
  741. }
  742. /* 响应式设计 */
  743. @media (max-width: 1200px) {
  744. .content-modules {
  745. grid-template-columns: 1fr;
  746. grid-template-rows: repeat(4, auto);
  747. }
  748. .stats-cards {
  749. flex-direction: column;
  750. }
  751. .stat-card {
  752. min-width: auto;
  753. }
  754. }
  755. </style>