| 1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045 |
- <template>
- <!-- 第一行:统计卡片行 -->
- <el-row :gutter="16" class="mb-6">
- <el-col :span="6">
- <el-card class="stat-card stat-card-gradient-1" shadow="hover">
- <div class="stat-content">
- <div class="stat-header">
- <div class="stat-icon-wrapper">
- <Icon icon="ep:data-line" class="stat-icon" />
- </div>
- <div class="stat-title">MTTR</div>
- </div>
- <el-divider class="stat-divider" />
- <div class="stat-body">
- <div class="stat-label">平均解决时间</div>
- <div class="stat-value">{{ statsData.productCategoryCount }}</div>
- </div>
- </div>
- </el-card>
- </el-col>
- <el-col :span="6">
- <el-card class="stat-card stat-card-gradient-2" shadow="hover">
- <div class="stat-content">
- <div class="stat-header">
- <div class="stat-icon-wrapper">
- <Icon icon="ep:trend-charts" class="stat-icon" />
- </div>
- <div class="stat-title">近一周工单数量</div>
- </div>
- <el-divider class="stat-divider" />
- <div class="stat-body">
- <div class="stat-item">
- <div class="stat-sub-label">故障上报</div>
- <div class="stat-sub-value text-blue-500">{{ week.failureWeek }}</div>
- </div>
- <div class="stat-item">
- <div class="stat-sub-label">维修工单</div>
- <div class="stat-sub-value text-white">{{ week.maintainWeek }}</div>
- </div>
- </div>
- </div>
- </el-card>
- </el-col>
- <el-col :span="6">
- <el-card class="stat-card stat-card-gradient-3" shadow="hover">
- <div class="stat-content">
- <div class="stat-header">
- <div class="stat-icon-wrapper">
- <Icon icon="ep:calendar" class="stat-icon" />
- </div>
- <div class="stat-title">近一月工单数量</div>
- </div>
- <el-divider class="stat-divider" />
- <div class="stat-body">
- <div class="stat-item">
- <div class="stat-sub-label">故障上报</div>
- <div class="stat-sub-value text-blue-500">{{ month.failureMonth }}</div>
- </div>
- <div class="stat-item">
- <div class="stat-sub-label">维修工单</div>
- <div class="stat-sub-value text-white">{{ month.maintainMonth }}</div>
- </div>
- </div>
- </div>
- </el-card>
- </el-col>
- <el-col :span="6">
- <el-card class="stat-card stat-card-gradient-4" shadow="hover">
- <div class="stat-content">
- <div class="stat-header">
- <div class="stat-icon-wrapper">
- <Icon icon="ep:pie-chart" class="stat-icon" />
- </div>
- <div class="stat-title">工单总数量</div>
- </div>
- <el-divider class="stat-divider" />
- <div class="stat-body">
- <div class="stat-item">
- <div class="stat-sub-label">故障上报</div>
- <div class="stat-sub-value text-blue-500">{{ total.failureTotal }}</div>
- </div>
- <div class="stat-item">
- <div class="stat-sub-label">维修工单</div>
- <div class="stat-sub-value text-white">{{ total.maintainTotal }}</div>
- </div>
- </div>
- </div>
- </el-card>
- </el-col>
- </el-row>
- <!-- 第二行:图表行 -->
- <el-row :gutter="16" class="mb-6">
- <el-col :span="12">
- <el-card class="chart-card-enhanced" shadow="hover" style="border: none">
- <template #header>
- <div class="chart-header">
- <div class="chart-title-wrapper">
- <div class="chart-title-dot"></div>
- <span class="chart-title">故障上报状态统计</span>
- </div>
- </div>
- </template>
- <el-row class="chart-grid">
- <el-col :span="6" class="chart-item">
- <div ref="reportingChartRef" class="gauge-chart"></div>
- <div class="chart-label">
- <span class="label-dot label-dot-blue"></span>
- <span class="label-text">上报中</span>
- </div>
- </el-col>
- <el-col :span="6" class="chart-item">
- <div ref="dealFinishedChartRef" class="gauge-chart"></div>
- <div class="chart-label">
- <span class="label-dot label-dot-orange"></span>
- <span class="label-text">处理完成</span>
- </div>
- </el-col>
- <el-col :span="6" class="chart-item">
- <div ref="transOrderChartRef" class="gauge-chart"></div>
- <div class="chart-label">
- <span class="label-dot label-dot-purple"></span>
- <span class="label-text">转工单</span>
- </div>
- </el-col>
- <el-col :span="6" class="chart-item">
- <div ref="orderFinishChartRef" class="gauge-chart"></div>
- <div class="chart-label">
- <span class="label-dot label-dot-cyan"></span>
- <span class="label-text">工单处理完成</span>
- </div>
- </el-col>
- </el-row>
- </el-card>
- </el-col>
- <el-col :span="12">
- <el-card class="chart-card-enhanced" shadow="hover" style="border: none">
- <template #header>
- <div class="chart-header">
- <div class="chart-title-wrapper">
- <div class="chart-title-dot"></div>
- <span class="chart-title">维修工单状态统计</span>
- </div>
- </div>
- </template>
- <el-row class="chart-grid">
- <el-col :span="12" class="chart-item">
- <div ref="writeChartRef" class="gauge-chart"></div>
- <div class="chart-label">
- <span class="label-dot label-dot-indigo"></span>
- <span class="label-text">待填写</span>
- </div>
- </el-col>
- <el-col :span="12" class="chart-item">
- <div ref="finishedChartRef" class="gauge-chart"></div>
- <div class="chart-label">
- <span class="label-dot label-dot-emerald"></span>
- <span class="label-text">已完成</span>
- </div>
- </el-col>
- </el-row>
- </el-card>
- </el-col>
- </el-row>
- <!-- 第三行:消息统计行 -->
- <el-row>
- <el-col :span="24">
- <el-card class="chart-card-enhanced" shadow="hover" style="border: none">
- <template #header>
- <div class="chart-header">
- <div class="chart-title-wrapper">
- <div class="chart-title-dot"></div>
- <span class="chart-title">近一年数量统计</span>
- </div>
- </div>
- </template>
- <div ref="chartContainer" class="bar-chart-container"></div>
- </el-card>
- </el-col>
- </el-row>
- <!-- TODO 第四行:地图 -->
- </template>
- <script setup lang="ts" name="Index">
- import * as echarts from 'echarts/core'
- import { BarChart } from 'echarts/charts' // 显式导入柱状图模块
- import {
- GridComponent,
- LegendComponent,
- TitleComponent,
- ToolboxComponent,
- TooltipComponent
- } from 'echarts/components'
- import { GaugeChart, LineChart, PieChart } from 'echarts/charts'
- import { LabelLayout, UniversalTransition } from 'echarts/features'
- import { CanvasRenderer } from 'echarts/renderers'
- import {
- IotStatisticsDeviceMessageSummaryRespVO,
- IotStatisticsSummaryRespVO
- } from '@/api/iot/statistics'
- import { formatDate } from '@/utils/formatTime'
- import { IotStatApi } from '@/api/pms/stat'
- // TODO @super:参考下 /Users/yunai/Java/yudao-ui-admin-vue3/src/views/mall/home/index.vue,拆一拆组件
- /** IoT 首页 */
- defineOptions({ name: 'IoTHome' })
- // TODO @super:使用下 Echart 组件,参考 yudao-ui-admin-vue3/src/views/mall/home/components/TradeTrendCard.vue 等
- echarts.use([
- TooltipComponent,
- LegendComponent,
- PieChart,
- CanvasRenderer,
- LabelLayout,
- TitleComponent,
- ToolboxComponent,
- GridComponent,
- LineChart,
- UniversalTransition,
- GaugeChart,
- BarChart
- ])
- const timeRange = ref('7d') // 修改默认选择为近一周
- const dateRange = ref<[Date, Date] | null>(null)
- const queryParams = reactive({
- startTime: Date.now() - 7 * 24 * 60 * 60 * 1000, // 设置默认开始时间为 7 天前
- endTime: Date.now() // 设置默认结束时间为当前时间
- })
- // const deviceCountChartRef = ref() // 设备数量统计的图表
- const reportingChartRef = ref() // 在线设备统计的图表
- const dealFinishedChartRef = ref() // 离线设备统计的图表
- const transOrderChartRef = ref() // 待激活设备统计的图表
- const orderFinishChartRef = ref()
- const deviceMessageCountChartRef = ref() // 上下行消息量统计的图表
- const writeChartRef = ref() // 上下行消息量统计的图表
- const finishedChartRef = ref() // 上下行消息量统计的图表
- // 基础统计数据
- // TODO @super:初始为 -1,然后界面展示先是加载中?试试用 cursor 改哈
- const statsData = ref<IotStatisticsSummaryRespVO>({
- productCategoryCount: 0,
- productCount: 0,
- deviceCount: 0,
- deviceMessageCount: 0,
- productCategoryTodayCount: 0,
- productTodayCount: 0,
- deviceTodayCount: 0,
- deviceMessageTodayCount: 0,
- deviceOnlineCount: 0,
- deviceOfflineCount: 0,
- deviceInactiveCount: 0,
- productCategoryDeviceCounts: {}
- })
- const day = ref({
- failureDay: undefined,
- maintainDay: undefined
- })
- const week = ref({
- failureWeek: undefined,
- maintainWeek: undefined
- })
- const month = ref({
- failureMonth: undefined,
- maintainMonth: undefined
- })
- const total = ref({
- failureTotal: undefined,
- maintainTotal: undefined
- })
- const status = ref({
- failureStatus: {
- reporting: 0,
- trans: 0,
- finished: 0,
- orderFinished: 0
- },
- maintainStatus: {
- finished: 0,
- todo: 0
- }
- })
- // 消息统计数据
- const messageStats = ref<IotStatisticsDeviceMessageSummaryRespVO>({
- upstreamCounts: {},
- downstreamCounts: {}
- })
- /** 处理快捷时间范围选择 */
- const handleTimeRangeChange = (timeRange: string) => {
- const now = Date.now()
- let startTime: number
- // TODO @super:这个的计算,看看能不能结合 dayjs 简化。因为 1h、24h、7d 感觉是比较标准的。如果没有,抽到 utils/formatTime.ts 作为一个工具方法
- switch (timeRange) {
- case '1h':
- startTime = now - 60 * 60 * 1000
- break
- case '24h':
- startTime = now - 24 * 60 * 60 * 1000
- break
- case '7d':
- startTime = now - 7 * 24 * 60 * 60 * 1000
- break
- default:
- return
- }
- // 清空日期选择器
- dateRange.value = null
- // 更新查询参数
- queryParams.startTime = startTime
- queryParams.endTime = now
- // 重新获取数据
- getStats()
- }
- /** 处理自定义日期范围选择 */
- const handleDateRangeChange = (value: [Date, Date] | null) => {
- if (value) {
- // 清空快捷选项
- timeRange.value = ''
- // 更新查询参数
- queryParams.startTime = value[0].getTime()
- queryParams.endTime = value[1].getTime()
- // 重新获取数据
- getStats()
- }
- }
- /** 获取统计数据 */
- const getStats = async () => {
- // 获取基础统计数据
- IotStatApi.getMainDay().then((res) => {
- day.value = res
- })
- IotStatApi.getMainWeek().then((res) => {
- week.value = res
- })
- IotStatApi.getMainMonth().then((res) => {
- month.value = res
- })
- IotStatApi.getMainTotal().then((res) => {
- total.value = res
- })
- IotStatApi.getMainStatus().then((res) => {
- status.value = res
- console.log(JSON.stringify(status.value))
- initCharts()
- })
- // statsData.value = await ProductCategoryApi.getIotStatisticsSummary()
- //
- // // 获取消息统计数据
- // messageStats.value = await ProductCategoryApi.getIotStatisticsDeviceMessageSummary(queryParams)
- // 初始化图表
- }
- /** 初始化图表 */
- const initCharts = () => {
- // 设备数量统计
- // echarts.init(deviceCountChartRef.value).setOption({
- // tooltip: {
- // trigger: 'item'
- // },
- // legend: {
- // top: '5%',
- // right: '10%',
- // align: 'left',
- // orient: 'vertical',
- // icon: 'circle'
- // },
- // series: [
- // {
- // name: 'Access From',
- // type: 'pie',
- // radius: ['50%', '80%'],
- // avoidLabelOverlap: false,
- // center: ['30%', '50%'],
- // label: {
- // show: false,
- // position: 'outside'
- // },
- // emphasis: {
- // label: {
- // show: true,
- // fontSize: 20,
- // fontWeight: 'bold'
- // }
- // },
- // labelLine: {
- // show: false
- // },
- // data: Object.entries(statsData.value.productCategoryDeviceCounts).map(([name, value]) => ({
- // name,
- // value
- // }))
- // }
- // ]
- // })
- // 上报中
- initGaugeChart(
- reportingChartRef.value,
- status.value.failureStatus.reporting === undefined ? 0 : status.value.failureStatus.reporting,
- '#0d9'
- )
- // 处理完成
- initGaugeChart(
- dealFinishedChartRef.value,
- status.value.failureStatus.finished === undefined ? 0 : status.value.failureStatus.finished,
- '#f50'
- )
- // 转工单
- initGaugeChart(
- transOrderChartRef.value,
- status.value.failureStatus.trans === undefined ? 0 : status.value.failureStatus.trans,
- '#05b'
- )
- // 工单完成
- initGaugeChart(
- orderFinishChartRef.value,
- status.value.failureStatus.orderFinished === undefined
- ? 0
- : status.value.failureStatus.orderFinished,
- '#05b'
- )
- // 待填写
- initGaugeChart(
- writeChartRef.value,
- status.value.maintainStatus.todo === undefined ? 0 : status.value.maintainStatus.todo,
- '#05b'
- )
- //已完成
- initGaugeChart(
- finishedChartRef.value,
- status.value.maintainStatus.finished === undefined ? 0 : status.value.maintainStatus.finished,
- '#05b'
- )
- // 消息量统计
- //initMessageChart()
- }
- /** 初始化仪表盘图表 */
- const initGaugeChart = (el: any, value: number, color: string) => {
- echarts.init(el).setOption({
- series: [
- {
- type: 'gauge',
- startAngle: 360,
- endAngle: 0,
- min: 0,
- max: statsData.value.deviceCount || 100, // 使用设备总数作为最大值
- progress: {
- show: true,
- width: 12,
- itemStyle: {
- color: color
- }
- },
- axisLine: {
- lineStyle: {
- width: 12,
- color: [[1, '#E5E7EB']]
- }
- },
- axisTick: { show: false },
- splitLine: { show: false },
- axisLabel: { show: false },
- pointer: { show: false },
- anchor: { show: false },
- title: { show: false },
- detail: {
- valueAnimation: true,
- fontSize: 24,
- fontWeight: 'bold',
- fontFamily: 'Inter, sans-serif',
- color: color,
- offsetCenter: [0, '0'],
- formatter: (value: number) => {
- return `${value} `
- }
- },
- data: [{ value: value }]
- }
- ]
- })
- }
- /** 初始化消息统计图表 */
- const initMessageChart = () => {
- // 获取所有时间戳并排序
- // TODO @super:一些 idea 里的红色报错,要去处理掉噢。
- const timestamps = Array.from(
- new Set([
- ...messageStats.value.upstreamCounts.map((item) => Number(Object.keys(item)[0])),
- ...messageStats.value.downstreamCounts.map((item) => Number(Object.keys(item)[0]))
- ])
- ).sort((a, b) => a - b) // 确保时间戳从小到大排序
- // 准备数据
- const xdata = timestamps.map((ts) => formatDate(ts, 'YYYY-MM-DD HH:mm'))
- const upData = timestamps.map((ts) => {
- const item = messageStats.value.upstreamCounts.find(
- (count) => Number(Object.keys(count)[0]) === ts
- )
- return item ? Object.values(item)[0] : 0
- })
- const downData = timestamps.map((ts) => {
- const item = messageStats.value.downstreamCounts.find(
- (count) => Number(Object.keys(count)[0]) === ts
- )
- return item ? Object.values(item)[0] : 0
- })
- // 配置图表
- echarts.init(deviceMessageCountChartRef.value).setOption({
- tooltip: {
- trigger: 'axis',
- backgroundColor: 'rgba(255, 255, 255, 0.9)',
- borderColor: '#E5E7EB',
- textStyle: {
- color: '#374151'
- }
- },
- legend: {
- data: ['上行消息量', '下行消息量'],
- textStyle: {
- color: '#374151',
- fontWeight: 500
- }
- },
- grid: {
- left: '3%',
- right: '4%',
- bottom: '3%',
- containLabel: true
- },
- xAxis: {
- type: 'category',
- boundaryGap: false,
- data: xdata,
- axisLine: {
- lineStyle: {
- color: '#E5E7EB'
- }
- },
- axisLabel: {
- color: '#6B7280'
- }
- },
- yAxis: {
- type: 'value',
- axisLine: {
- lineStyle: {
- color: '#E5E7EB'
- }
- },
- axisLabel: {
- color: '#6B7280'
- },
- splitLine: {
- lineStyle: {
- color: '#F3F4F6'
- }
- }
- },
- series: [
- {
- name: '上行消息量',
- type: 'line',
- smooth: true, // 添加平滑曲线
- data: upData,
- itemStyle: {
- color: '#3B82F6'
- },
- lineStyle: {
- width: 2
- },
- areaStyle: {
- color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
- { offset: 0, color: 'rgba(59, 130, 246, 0.2)' },
- { offset: 1, color: 'rgba(59, 130, 246, 0)' }
- ])
- }
- },
- {
- name: '下行消息量',
- type: 'line',
- smooth: true, // 添加平滑曲线
- data: downData,
- itemStyle: {
- color: '#10B981'
- },
- lineStyle: {
- width: 2
- },
- areaStyle: {
- color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
- { offset: 0, color: 'rgba(16, 185, 129, 0.2)' },
- { offset: 1, color: 'rgba(16, 185, 129, 0)' }
- ])
- }
- }
- ]
- })
- }
- const chartContainer = ref(null)
- let chartInstance = null
- // 生成过去12个月份的标签 (格式: YYYY-MM)
- const generateMonthLabels = () => {
- const months = []
- const date = new Date()
- for (let i = 11; i >= 0; i--) {
- const tempDate = new Date(date.getFullYear(), date.getMonth() - i, 1)
- const year = tempDate.getFullYear()
- const month = String(tempDate.getMonth() + 1).padStart(2, '0')
- months.push(`${year}-${month}`)
- }
- return months
- }
- // 模拟数据获取
- const fetchChartData = async () => {
- // 模拟异步请求
- return new Promise((resolve) => {
- setTimeout(() => {
- resolve({
- months: generateMonthLabels(),
- faults: [20, 30, 100, 40, 20, 50, 70, 80, 60, 90, 100, 100],
- repairs: [10, 30, 90, 30, 10, 20, 60, 50, 22, 34, 70, 85]
- })
- }, 300)
- })
- }
- // 初始化图表配置
- const initChart = async () => {
- if (!chartContainer.value) return
- // 获取数据
- const { months, faults, repairs } = await fetchChartData()
- // ECharts配置
- const option = {
- tooltip: {
- trigger: 'axis',
- axisPointer: {
- type: 'shadow'
- },
- formatter: (params) => {
- return `${params[0].axisValue}<br/>
- ${params[0].marker} ${params[0].seriesName}: ${params[0].value}<br/>
- ${params[1].marker} ${params[1].seriesName}: ${params[1].value}`
- }
- },
- legend: {
- data: ['故障上报数量', '维修工单数量'],
- top: 25
- },
- grid: {
- left: '3%',
- right: '4%',
- bottom: '3%',
- containLabel: true
- },
- xAxis: {
- type: 'category',
- data: months,
- axisLabel: {
- rotate: 45,
- margin: 15
- }
- },
- yAxis: {
- type: 'value',
- axisLabel: {
- formatter: (value) => Math.floor(value).toString()
- }
- },
- series: [
- {
- name: '故障上报数量',
- type: 'bar',
- barGap: 0,
- itemStyle: {
- color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
- { offset: 0, color: '#188df0' },
- { offset: 1, color: '#188df0' }
- ])
- },
- emphasis: {
- focus: 'series'
- },
- data: faults
- },
- {
- name: '维修工单数量',
- type: 'bar',
- itemStyle: {
- color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
- { offset: 0, color: '#d3a137' },
- { offset: 1, color: '#d3a137' }
- ])
- },
- emphasis: {
- focus: 'series'
- },
- data: repairs
- }
- ]
- }
- // 初始化图表
- chartInstance = echarts.init(chartContainer.value)
- chartInstance.setOption(option)
- // 窗口缩放监听
- window.addEventListener('resize', handleResize)
- handleResize()
- }
- // 自适应调整
- const handleResize = () => {
- chartInstance?.resize()
- }
- /** 初始化 */
- onMounted(() => {
- getStats()
- initChart()
- })
- onUnmounted(() => {
- chartInstance?.dispose()
- window.removeEventListener('resize', handleResize)
- })
- </script>
- <style lang="scss" scoped>
- // 统计卡片基础样式
- .stat-card {
- border-radius: 12px;
- border: 1px solid rgba(207, 220, 237, 0.9);
- transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
- overflow: hidden;
- position: relative;
- background: linear-gradient(180deg, #ffffff 0%, #f4f8ff 100%);
- box-shadow:
- inset 0 1px 0 rgba(255, 255, 255, 0.95),
- inset 0 -10px 24px rgba(210, 225, 244, 0.26),
- 0 10px 24px rgba(32, 66, 120, 0.08);
- &:hover {
- transform: translateY(-4px);
- box-shadow:
- inset 0 1px 0 rgba(255, 255, 255, 0.98),
- inset 0 -12px 26px rgba(204, 220, 243, 0.32),
- 0 16px 32px rgba(32, 66, 120, 0.12);
- }
- :deep(.el-card__body) {
- padding: 0;
- }
- }
- // 渐变色背景
- .stat-card-gradient-1 {
- background:
- radial-gradient(circle at top left, rgba(121, 164, 255, 0.16), transparent 42%),
- linear-gradient(180deg, #ffffff 0%, #f4f8ff 100%);
- }
- .stat-card-gradient-2 {
- background:
- radial-gradient(circle at top left, rgba(118, 186, 255, 0.14), transparent 42%),
- linear-gradient(180deg, #ffffff 0%, #f3f8ff 100%);
- }
- .stat-card-gradient-3 {
- background:
- radial-gradient(circle at top left, rgba(96, 154, 241, 0.14), transparent 42%),
- linear-gradient(180deg, #ffffff 0%, #f5f9ff 100%);
- }
- .stat-card-gradient-4 {
- background:
- radial-gradient(circle at top left, rgba(137, 176, 242, 0.14), transparent 42%),
- linear-gradient(180deg, #ffffff 0%, #f4f8fe 100%);
- }
- // 统计内容区域
- .stat-content {
- padding: 20px;
- color: #1f2a44;
- }
- .stat-header {
- display: flex;
- align-items: center;
- gap: 12px;
- margin-bottom: 16px;
- }
- .stat-icon-wrapper {
- width: 48px;
- height: 48px;
- background: linear-gradient(180deg, rgba(255, 255, 255, 0.96) 0%, rgba(232, 240, 252, 0.92) 100%);
- border: 1px solid rgba(205, 219, 239, 0.9);
- border-radius: 12px;
- display: flex;
- align-items: center;
- justify-content: center;
- box-shadow:
- inset 0 1px 0 rgba(255, 255, 255, 0.95),
- inset 0 -6px 12px rgba(205, 220, 242, 0.34),
- 0 6px 14px rgba(66, 104, 168, 0.08);
- }
- .stat-icon {
- font-size: 24px;
- color: #5d79b7;
- }
- .stat-title {
- font-size: 16px;
- font-weight: 600;
- color: #000;
- }
- .stat-divider {
- border-color: rgba(201, 214, 234, 0.85);
- margin: 12px 0;
- }
- .stat-body {
- display: flex;
- flex-direction: column;
- gap: 12px;
- }
- .stat-label {
- font-size: 13px;
- color: #7182a1;
- margin-bottom: 4px;
- }
- .stat-value {
- font-size: 36px;
- font-weight: 700;
- color: #1f2f54;
- line-height: 1;
- }
- .stat-item {
- display: flex;
- justify-content: space-between;
- align-items: center;
- }
- .stat-sub-label {
- font-size: 13px;
- color: #7182a1;
- }
- .stat-sub-value {
- font-size: 28px;
- font-weight: 700;
- line-height: 1;
- color: #1f2f54;
- }
- // 图表卡片增强样式
- .chart-card-enhanced {
- border-radius: 12px;
- border: 1px solid #e5e7eb;
- transition: all 0.3s ease;
- &:hover {
- box-shadow: 0 8px 24px rgba(0, 0, 0, 0.08);
- border-color: #d1d5db;
- }
- :deep(.el-card__header) {
- padding: 16px 20px;
- border-bottom: 1px solid #f3f4f6;
- background: linear-gradient(to right, #fafafa, #ffffff);
- }
- :deep(.el-card__body) {
- padding: 20px;
- }
- }
- .chart-header {
- display: flex;
- align-items: center;
- justify-content: space-between;
- }
- .chart-title-wrapper {
- display: flex;
- align-items: center;
- gap: 8px;
- }
- .chart-title-dot {
- width: 8px;
- height: 8px;
- border-radius: 50%;
- background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
- box-shadow: 0 2px 4px rgba(102, 126, 234, 0.3);
- }
- .chart-title {
- font-size: 16px;
- font-weight: 600;
- color: #1f2937;
- letter-spacing: 0.3px;
- }
- .chart-grid {
- min-height: 220px;
- }
- .chart-item {
- display: flex;
- flex-direction: column;
- align-items: center;
- padding: 12px;
- transition: all 0.2s ease;
- &:hover {
- background: rgba(102, 126, 234, 0.03);
- border-radius: 8px;
- }
- }
- .gauge-chart {
- width: 100%;
- height: 160px;
- }
- .chart-label {
- display: flex;
- align-items: center;
- gap: 6px;
- margin-top: 8px;
- padding: 6px 12px;
- background: #f9fafb;
- border-radius: 20px;
- transition: all 0.2s ease;
- .chart-item:hover & {
- background: #f3f4f6;
- }
- }
- .label-dot {
- width: 8px;
- height: 8px;
- border-radius: 50%;
- display: inline-block;
- }
- .label-dot-blue {
- background: #3b82f6;
- box-shadow: 0 0 0 2px rgba(59, 130, 246, 0.2);
- }
- .label-dot-orange {
- background: #f97316;
- box-shadow: 0 0 0 2px rgba(249, 115, 22, 0.2);
- }
- .label-dot-purple {
- background: #8b5cf6;
- box-shadow: 0 0 0 2px rgba(139, 92, 246, 0.2);
- }
- .label-dot-cyan {
- background: #06b6d4;
- box-shadow: 0 0 0 2px rgba(6, 182, 212, 0.2);
- }
- .label-dot-indigo {
- background: #6366f1;
- box-shadow: 0 0 0 2px rgba(99, 102, 241, 0.2);
- }
- .label-dot-emerald {
- background: #10b981;
- box-shadow: 0 0 0 2px rgba(16, 185, 129, 0.2);
- }
- .label-text {
- font-size: 13px;
- color: #6b7280;
- font-weight: 500;
- }
- .bar-chart-container {
- height: 350px;
- width: 100%;
- }
- // 响应式优化
- @media (max-width: 1200px) {
- .stat-value {
- font-size: 28px;
- }
- .stat-sub-value {
- font-size: 24px;
- }
- }
- @media (max-width: 768px) {
- .stat-card {
- margin-bottom: 16px;
- }
- .chart-item {
- padding: 8px;
- }
- .gauge-chart {
- height: 140px;
- }
- }
- </style>
|