| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590 |
- <template>
- <div ref="wrapperRef" class="bg absolute top-0 left-0 size-full z-10">
- <div class="mx-a overflow-hidden" :style="targetWrapperStyle">
- <div class="bg kb-screen" id="inspect-kb" :style="targetAreaStyle">
- <header class="header">{{ t('stat.inspectvue702065-0') }}</header>
- <main class="inspect-kb-content">
- <!-- 第一行:筛选条件 -->
- <el-row :gutter="16" class="filter-row">
- <el-col :span="24">
- <el-card class="filter-card" shadow="hover" style="border: none">
- <el-form
- class="-mb-15px"
- :model="queryParams"
- ref="queryFormRef"
- :inline="true"
- label-width="auto">
- <el-form-item :label="t('stat.inspectvue702065-1')" prop="project_name">
- <el-tree-select
- v-model="queryParams.deptId"
- :data="deptList"
- :props="defaultProps"
- check-strictly
- node-key="id"
- filterable
- :placeholder="t('stat.inspectvue702065-2')"
- clearable
- style="width: 200px" />
- </el-form-item>
- <el-form-item :label="t('stat.inspectvue702065-3')" prop="createTime">
- <el-date-picker
- v-model="queryParams.createTime"
- value-format="YYYY-MM-DD HH:mm:ss"
- type="daterange"
- start-:placeholder="t('stat.inspectvue702065-4')"
- end-:placeholder="t('stat.inspectvue702065-5')"
- :default-time="[new Date('1 00:00:00'), new Date('1 23:59:59')]"
- class="!w-240px"
- @change="handleDateChange" />
- </el-form-item>
- <el-form-item>
- <el-button type="primary" @click="handleQuery">
- <Icon icon="ep:search" class="mr-5px" />{{
- t('stat.inspectvue702065-6')
- }}</el-button
- >
- <el-button @click="resetQuery" class="btn-default">
- <Icon icon="ep:refresh" class="mr-5px" />{{
- t('stat.inspectvue702065-7')
- }}</el-button
- >
- </el-form-item>
- </el-form>
- </el-card>
- </el-col>
- </el-row>
- <!-- 第二行:统计卡片行 -->
- <el-row :gutter="16" class="summary-row">
- <el-col :span="6">
- <el-card class="stat-card stat-card-neutral-1" shadow="hover" style="border: none">
- <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">{{ t('stat.inspectvue702065-8') }}</div>
- </div>
- <el-divider class="stat-divider" />
- <div class="stat-body">
- <div class="stat-item">
- <div class="stat-sub-label">{{ t('stat.inspectvue702065-9') }}</div>
- <div class="stat-sub-value text-slate-700">{{ day.total }}</div>
- </div>
- <div class="stat-item">
- <div class="stat-sub-label">{{ t('stat.inspectvue702065-10') }}</div>
- <div class="stat-sub-value text-amber-600">{{ day.todo }}</div>
- </div>
- </div>
- </div>
- </el-card>
- </el-col>
- <el-col :span="6">
- <el-card class="stat-card stat-card-neutral-2" shadow="hover" style="border: none">
- <div class="stat-content">
- <div class="stat-header">
- <div class="stat-icon-wrapper">
- <Icon icon="ep:star" class="stat-icon" />
- </div>
- <div class="stat-title">{{ t('stat.inspectvue702065-11') }}</div>
- </div>
- <el-divider class="stat-divider" />
- <div class="stat-body">
- <div class="stat-item">
- <div class="stat-sub-label">{{ t('stat.inspectvue702065-9') }}</div>
- <div class="stat-sub-value text-slate-700">{{ week.total }}</div>
- </div>
- <div class="stat-item">
- <div class="stat-sub-label">{{ t('stat.inspectvue702065-10') }}</div>
- <div class="stat-sub-value text-amber-600">{{ week.todo }}</div>
- </div>
- </div>
- </div>
- </el-card>
- </el-col>
- <el-col :span="6">
- <el-card class="stat-card stat-card-neutral-3" shadow="hover" style="border: none">
- <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">{{ t('stat.inspectvue702065-12') }}</div>
- </div>
- <el-divider class="stat-divider" />
- <div class="stat-body">
- <div class="stat-item">
- <div class="stat-sub-label">{{ t('stat.inspectvue702065-9') }}</div>
- <div class="stat-sub-value text-slate-700">{{ month.total }}</div>
- </div>
- <div class="stat-item">
- <div class="stat-sub-label">{{ t('stat.inspectvue702065-10') }}</div>
- <div class="stat-sub-value text-amber-600">{{ month.todo }}</div>
- </div>
- </div>
- </div>
- </el-card>
- </el-col>
- <el-col :span="6">
- <el-card class="stat-card stat-card-neutral-4" shadow="hover" style="border: none">
- <div class="stat-content">
- <div class="stat-header">
- <div class="stat-icon-wrapper">
- <Icon icon="ep:data-analysis" class="stat-icon" />
- </div>
- <div class="stat-title">{{ t('stat.inspectvue702065-13') }}</div>
- </div>
- <el-divider class="stat-divider" />
- <div class="stat-body">
- <div class="stat-item">
- <div class="stat-sub-label">{{ t('stat.inspectvue702065-9') }}</div>
- <div class="stat-sub-value text-slate-700">{{ total.total }}</div>
- </div>
- <div class="stat-item">
- <div class="stat-sub-label">{{ t('stat.inspectvue702065-10') }}</div>
- <div class="stat-sub-value text-amber-600">{{ total.todo }}</div>
- </div>
- </div>
- </div>
- </el-card>
- </el-col>
- </el-row>
- <!-- 第三行:图表行 -->
- <el-row :gutter="16" class="status-row">
- <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 chart-title-dot-blue"></div>
- <span class="chart-title">{{ t('stat.inspectvue702065-14') }}</span>
- </div>
- </div>
- </template>
- <el-row class="chart-grid">
- <el-col :span="8" class="chart-item">
- <div ref="finishedChartRef" class="gauge-chart"></div>
- <div class="chart-label clickable" @click="clickStatus('工单已执行')">
- <span class="label-dot label-dot-blue"></span>
- <span class="label-text">{{ t('stat.inspectvue702065-15') }}</span>
- </div>
- </el-col>
- <el-col :span="8" class="chart-item">
- <div ref="writeChartRef" class="gauge-chart"></div>
- <div class="chart-label clickable" @click="clickStatus('工单待执行')">
- <span class="label-dot label-dot-amber"></span>
- <span class="label-text">{{ t('stat.inspectvue702065-16') }}</span>
- </div>
- </el-col>
- <el-col :span="8" class="chart-item">
- <div ref="ignoreChartRef" class="gauge-chart"></div>
- <div class="chart-label clickable" @click="clickStatus('工单忽略')">
- <span class="label-dot label-dot-gray"></span>
- <span class="label-text">{{ t('stat.inspectvue702065-17') }}</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 chart-title-dot-green"></div>
- <span class="chart-title">{{ t('stat.inspectvue702065-18') }}</span>
- </div>
- </div>
- </template>
- <el-row class="chart-grid">
- <el-col :span="8" class="chart-item">
- <div ref="finishedTodayChartRef" class="gauge-chart"></div>
- <div class="chart-label clickable" @click="clickStatus('设备已执行')">
- <span class="label-dot label-dot-blue"></span>
- <span class="label-text">{{ t('stat.inspectvue702065-19') }}</span>
- </div>
- </el-col>
- <el-col :span="8" class="chart-item">
- <div ref="writeTodayChartRef" class="gauge-chart"></div>
- <div class="chart-label clickable" @click="clickStatus('设备待执行')">
- <span class="label-dot label-dot-amber"></span>
- <span class="label-text">{{ t('stat.inspectvue702065-20') }}</span>
- </div>
- </el-col>
- <el-col :span="8" class="chart-item">
- <div ref="ignoreTodayChartRef" class="gauge-chart"></div>
- <div class="chart-label clickable" @click="clickStatus('设备忽略')">
- <span class="label-dot label-dot-gray"></span>
- <span class="label-text">{{ t('stat.inspectvue702065-17') }}</span>
- </div>
- </el-col>
- </el-row>
- </el-card>
- </el-col>
- </el-row>
- <!-- 第四行:消息统计行 -->
- <el-row :gutter="16" class="trend-row">
- <el-col :span="8">
- <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 chart-title-dot-purple"></div>
- <span class="chart-title">{{ t('stat.inspectvue702065-21') }}</span>
- </div>
- </div>
- </template>
- <div ref="statusChartRef" class="pie-chart-container"></div>
- </el-card>
- </el-col>
- <el-col :span="16">
- <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 chart-title-dot-indigo"></div>
- <span class="chart-title">{{ t('stat.inspectvue702065-22') }}</span>
- </div>
- </div>
- </template>
- <div ref="chartContainer" class="bar-chart-container"></div>
- </el-card>
- </el-col>
- </el-row>
- <!-- TODO 第五行:地图 -->
- </main>
- </div>
- </div>
- </div>
- </template>
- <script setup lang="ts" name="Index">
- import * as echarts from 'echarts/core'
- import { useI18n } from 'vue-i18n'
- import { BarChart } from 'echarts/charts' // 显式导入柱状图模块
- const { t } = useI18n()
- 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'
- const { currentRoute, push } = useRouter()
- import { formatDate } from '@/utils/formatTime'
- import { IotStatApi } from '@/api/pms/stat'
- import { defaultProps, handleTree } from '@/utils/tree'
- import { ref } from 'vue'
- import { DeptTreeItem } from '@/api/system/dept'
- import * as DeptApi from '@/api/system/dept'
- import { useUserStore } from '@/store/modules/user'
- import { DESIGN_HEIGHT, DESIGN_WIDTH } from '@/utils/kb'
- // TODO @super:参考下 /Users/yunai/Java/yudao-ui-admin-vue3/src/views/mall/home/index.vue,拆一拆组件
- /** IoT 首页 */
- defineOptions({ name: 'IotInspectStat' })
- const wrapperRef = ref<HTMLDivElement>()
- const scale = ref(1)
- const chartInstances = new Set<ReturnType<typeof echarts.init>>()
- let resizeObserver: ResizeObserver | null = null
- let resizeRaf = 0
- const targetWrapperStyle = computed(() => ({
- width: `${DESIGN_WIDTH * scale.value}px`,
- height: `${DESIGN_HEIGHT * scale.value}px`
- }))
- const targetAreaStyle = computed(() => ({
- '--kb-scale': scale.value,
- width: `${DESIGN_WIDTH * scale.value}px`,
- height: `${DESIGN_HEIGHT * scale.value}px`
- }))
- const getChartInstance = (el: HTMLElement | null) => {
- if (!el) return null
- const chart = echarts.getInstanceByDom(el) || echarts.init(el)
- chartInstances.add(chart)
- return chart
- }
- const resizeCharts = () => {
- chartInstances.forEach((chart) => chart.resize())
- }
- const updateScale = () => {
- cancelAnimationFrame(resizeRaf)
- resizeRaf = requestAnimationFrame(() => {
- const wrapper = wrapperRef.value
- if (!wrapper) return
- const { clientWidth, clientHeight } = wrapper
- if (!clientWidth || !clientHeight) return
- scale.value = Math.min(clientWidth / DESIGN_WIDTH, clientHeight / DESIGN_HEIGHT)
- nextTick(resizeCharts)
- })
- }
- // 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 deptList = ref<DeptTreeItem[]>([]) // 树形结构部门列表
- const queryParams = reactive({
- deptId: undefined,
- createTime: undefined,
- 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 ignoreChartRef = ref() // 上下行消息量统计的图表
- const finishedChartRef = ref() // 上下行消息量统计的图表
- const writeTodayChartRef = ref() // 上下行消息量统计的图表
- const ignoreTodayChartRef = ref() // 上下行消息量统计的图表
- const finishedTodayChartRef = ref() // 上下行消息量统计的图表
- const statusChartRef = 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 handleQuery = () => {
- getStats()
- // initChart()
- // initCharts()
- }
- const resetQuery = () => {
- // 重置查询参数
- queryParams.startTime = Date.now() - 7 * 24 * 60 * 60 * 1000
- queryParams.endTime = Date.now()
- queryParams.deptId = useUserStore().getUser.deptId
- queryParams.createTime = null
- // 重新获取数据
- getStats()
- }
- const handleDateChange = () => {
- handleQuery()
- }
- const clickStatus = (type) => {
- let status = ''
- const createTime = queryParams.createTime
- const deptId = queryParams.deptId
- if (type === '工单待执行') {
- status = 'todo'
- push({ name: 'IotInspectOrdere', params: { deptId, status, createTime } })
- } else if (type === '工单已执行') {
- status = 'finished'
- push({ name: 'IotInspectOrdere', params: { deptId, status, createTime } })
- } else if (type === '工单忽略') {
- status = 'ignore'
- push({ name: 'IotInspectOrdere', params: { deptId, status, createTime } })
- } else if (type === '设备忽略') {
- status = 'ignore'
- push({ name: 'IotInspectOrderDetailStat', params: { deptId, status, createTime } })
- } else if (type === '设备待执行') {
- status = 'todo'
- push({ name: 'IotInspectOrderDetailStat', params: { deptId, status, createTime } })
- } else if (type === '设备已执行') {
- status = 'finished'
- push({ name: 'IotInspectOrderDetailStat', params: { deptId, status, createTime } })
- }
- }
- const day = ref({
- total: undefined,
- todo: undefined
- })
- const week = ref({
- total: undefined,
- todo: undefined
- })
- const month = ref({
- total: undefined,
- todo: undefined
- })
- const total = ref({
- total: undefined,
- todo: undefined
- })
- const status = ref({
- finished: 0,
- todo: 0,
- ignore: 0
- })
- const todayStatus = ref({
- finished: 0,
- todo: 0,
- ignore: 0
- })
- const typeData = ref({})
- // 消息统计数据
- 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.getInspectDay().then((res) => {
- day.value = res
- })
- IotStatApi.getInspectWeek().then((res) => {
- week.value = res
- })
- IotStatApi.getInspectMonth().then((res) => {
- month.value = res
- })
- IotStatApi.getInspectTotal().then((res) => {
- total.value = res
- })
- IotStatApi.getInspectStatus(queryParams).then((res) => {
- status.value = res
- initChart()
- initCharts()
- })
- IotStatApi.getInspectDeviceStatus(queryParams).then((res) => {
- todayStatus.value = res
- initChart()
- initCharts()
- })
- IotStatApi.getInspectItemStatus(queryParams).then((res) => {
- typeData.value = res
- initChart()
- initCharts()
- })
- // statsData.value = await ProductCategoryApi.getIotStatisticsSummary()
- //
- // // 获取消息统计数据
- // messageStats.value = await ProductCategoryApi.getIotStatisticsDeviceMessageSummary(queryParams)
- // 初始化图表
- }
- /** 初始化图表 */
- const initCharts = () => {
- const chart = getChartInstance(statusChartRef.value)
- if (!chart) return
- chart.setOption({
- tooltip: {
- trigger: 'item'
- },
- legend: {
- // top: '5%',
- // right: '10%',
- // align: 'center',
- orient: 'horizontal', // 水平排列图例项
- bottom: '0%', // 放置在底部
- icon: 'circle'
- },
- series: [
- {
- name: '',
- type: 'pie',
- radius: ['0%', '70%'],
- avoidLabelOverlap: false,
- center: ['50%', '45%'],
- label: {
- show: false,
- position: 'outside'
- },
- emphasis: {
- label: {
- show: true,
- fontSize: 15,
- fontWeight: 'bold'
- }
- },
- labelLine: {
- show: true
- },
- data: typeData.value
- }
- ]
- })
- chart.off('click')
- chart.on('click', (params) => {
- console.log('点击的数据值为:', params.value)
- console.log('点击的数据类型为:', params.data.type)
- debugger
- const createTime = queryParams.createTime
- const deptId = queryParams.deptId
- const status = params.data.name
- push({ name: 'IotInspectItemStat', params: { deptId, status, createTime } })
- })
- // echarts.init(statusChartRef.value).setOption({
- // })
- //待执行
- initGaugeChart(
- writeTodayChartRef.value,
- todayStatus.value.todo === undefined ? 0 : todayStatus.value.todo,
- '#f50',
- '设备待执行'
- )
- initGaugeChart(
- ignoreTodayChartRef.value,
- todayStatus.value.ignore === undefined ? 0 : todayStatus.value.ignore,
- 'purple',
- '设备忽略'
- )
- //已执行
- initGaugeChart(
- finishedTodayChartRef.value,
- todayStatus.value.finished === undefined ? 0 : todayStatus.value.finished,
- '#05b',
- '设备已执行'
- )
- // 工单待执行
- initGaugeChart(
- writeChartRef.value,
- status.value.todo === undefined ? 0 : status.value.todo,
- '#f50',
- '工单待执行'
- )
- // 忽略
- initGaugeChart(
- ignoreChartRef.value,
- status.value.ignore === undefined ? 0 : status.value.ignore,
- 'purple',
- '工单忽略'
- )
- //工单已执行
- initGaugeChart(
- finishedChartRef.value,
- status.value.finished === undefined ? 0 : status.value.finished,
- '#05b',
- '工单已执行'
- )
- // 消息量统计
- //initMessageChart()
- }
- /** 初始化仪表盘图表 */
- const initGaugeChart = (el: any, value: number, color: string, type: string) => {
- const chart = getChartInstance(el)
- if (!chart) return
- chart.setOption({
- series: [
- {
- type: 'gauge',
- startAngle: 360,
- endAngle: 0,
- min: 0,
- max: value, // 使用设备总数作为最大值
- progress: {
- show: true,
- width: 15,
- itemStyle: {
- color: color
- }
- },
- axisLine: {
- lineStyle: {
- width: 15,
- 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: 20,
- fontWeight: 'bold',
- fontFamily: 'Inter, sans-serif',
- color: color,
- offsetCenter: [0, '0'],
- formatter: (value: number) => {
- return `${value} `
- }
- },
- data: [{ value: value, type: type }]
- }
- ]
- })
- // 添加点击事件监听器
- chart.off('click')
- chart.on('click', (params) => {
- console.log('点击的数据值为:', params.value)
- console.log('点击的数据类型为:', params.data.type)
- const createTime = queryParams.createTime
- const deptId = queryParams.deptId
- let status = ''
- if (params.data.type === '工单待执行') {
- status = 'todo'
- push({ name: 'IotInspectOrdere', params: { deptId, status, createTime } })
- } else if (params.data.type === '工单已执行') {
- status = 'finished'
- push({ name: 'IotInspectOrdere', params: { deptId, status, createTime } })
- } else if (params.data.type === '工单忽略') {
- status = 'ignore'
- push({ name: 'IotInspectOrdere', params: { deptId, status, createTime } })
- } else if (params.data.type === '设备待执行') {
- status = 'todo'
- push({ name: 'IotInspectOrderDetailStat', params: { deptId, status, createTime } })
- } else if (params.data.type === '设备已执行') {
- status = 'finished'
- push({ name: 'IotInspectOrderDetailStat', params: { deptId, status, createTime } })
- } else if (params.data.type === '设备忽略') {
- status = 'ignore'
- push({ name: 'IotInspectOrderDetailStat', params: { deptId, status, createTime } })
- }
- })
- }
- /** 初始化消息统计图表 */
- 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
- })
- // 配置图表
- getChartInstance(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: ReturnType<typeof echarts.init> | null = 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()
- debugger
- // 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}`
- }
- },
- 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',
- itemStyle: {
- color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
- { offset: 0, color: '#2196df' },
- { offset: 1, color: '#2196df' }
- ])
- },
- emphasis: {
- focus: 'series'
- },
- data: repairs
- }
- ]
- }
- // 初始化图表
- chartInstance = getChartInstance(chartContainer.value)
- if (!chartInstance) return
- chartInstance.setOption(option)
- handleResize()
- }
- // 自适应调整
- const handleResize = () => {
- chartInstance?.resize()
- }
- /** 初始化 */
- onMounted(async () => {
- nextTick(updateScale)
- resizeObserver = new ResizeObserver(updateScale)
- if (wrapperRef.value) {
- resizeObserver.observe(wrapperRef.value)
- }
- window.addEventListener('resize', updateScale)
- deptList.value = handleTree(await DeptApi.getSimpleDeptList())
- queryParams.deptId = useUserStore().getUser.deptId
- await getStats()
- nextTick(resizeCharts)
- // await initChart()
- // await initCharts()
- })
- onUnmounted(() => {
- resizeObserver?.disconnect()
- window.removeEventListener('resize', updateScale)
- cancelAnimationFrame(resizeRaf)
- chartInstances.forEach((chart) => chart.dispose())
- chartInstances.clear()
- })
- </script>
- <style lang="scss" scoped>
- @import url('@/styles/kb.scss');
- // 响应式优化
- @media (width <= 1200px) {
- .stat-sub-value {
- font-size: 24px;
- }
- }
- @media (width <= 768px) {
- .stat-card {
- margin-bottom: 16px;
- }
- .chart-item {
- padding: 8px;
- }
- .gauge-chart {
- height: 140px;
- }
- }
- // 筛选卡片样式
- .filter-card {
- background: linear-gradient(to bottom, #fafafa, #fff);
- border: 1px solid #e5e7eb;
- border-radius: 12px;
- transition: all 0.3s ease;
- &:hover {
- box-shadow: 0 4px 12px rgb(0 0 0 / 5%);
- }
- :deep(.el-card__body) {
- padding: 20px;
- }
- :deep(.el-form-item__label) {
- font-weight: 500;
- color: #6b7280;
- }
- }
- .btn-primary {
- background: linear-gradient(135deg, #64748b 0%, #475569 100%);
- border: none;
- transition: all 0.3s ease;
- &:hover {
- background: linear-gradient(135deg, #475569 0%, #334155 100%);
- transform: translateY(-1px);
- box-shadow: 0 4px 8px rgb(100 116 139 / 30%);
- }
- }
- .btn-default {
- color: #6b7280;
- border-color: #d1d5db;
- transition: all 0.3s ease;
- &:hover {
- color: #4b5563;
- background: #f9fafb;
- border-color: #9ca3af;
- }
- }
- // 统计卡片基础样式
- .stat-card {
- position: relative;
- overflow: hidden;
- background: #fff;
- border: 1px solid #e5e7eb;
- border-radius: 12px;
- transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
- &:hover {
- border-color: #d1d5db;
- transform: translateY(-4px);
- box-shadow: 0 12px 24px -8px rgb(0 0 0 / 10%);
- }
- :deep(.el-card__body) {
- padding: 0;
- }
- }
- // 中性色渐变背景(低饱和度)
- .stat-card-neutral-1 {
- background: linear-gradient(135deg, #f8fafc 0%, #f1f5f9 100%);
- }
- .stat-card-neutral-2 {
- background: linear-gradient(135deg, #f1f5f9 0%, #e2e8f0 100%);
- }
- .stat-card-neutral-3 {
- background: linear-gradient(135deg, #f8fafc 0%, #e2e8f0 100%);
- }
- .stat-card-neutral-4 {
- background: linear-gradient(135deg, #f1f5f9 0%, #cbd5e1 100%);
- }
- // 统计内容区域
- .stat-content {
- padding: 20px;
- }
- .stat-header {
- display: flex;
- align-items: center;
- gap: 12px;
- margin-bottom: 16px;
- }
- .stat-icon-wrapper {
- display: flex;
- width: 48px;
- height: 48px;
- background: #0090ff;
- border-radius: 12px;
- box-shadow: 0 4px 8px rgb(100 116 139 / 20%);
- align-items: center;
- justify-content: center;
- }
- .stat-icon {
- font-size: 24px;
- color: white;
- }
- .stat-title {
- font-size: 15px;
- font-weight: 600;
- color: #475569;
- }
- .stat-divider {
- margin: 12px 0;
- border-color: #e2e8f0;
- }
- .stat-body {
- display: flex;
- flex-direction: column;
- gap: 12px;
- }
- .stat-item {
- display: flex;
- justify-content: space-between;
- align-items: center;
- padding: 8px 12px;
- background: rgb(255 255 255 / 60%);
- border-radius: 8px;
- transition: all 0.2s ease;
- &:hover {
- background: rgb(255 255 255 / 90%);
- }
- }
- .stat-sub-label {
- font-size: 13px;
- font-weight: 500;
- color: #64748b;
- }
- .stat-sub-value {
- font-size: 28px;
- font-weight: 700;
- line-height: 1;
- }
- // 图表卡片增强样式
- .chart-card-enhanced {
- background: #fff;
- border: 1px solid #e5e7eb;
- border-radius: 12px;
- transition: all 0.3s ease;
- &:hover {
- border-color: #d1d5db;
- box-shadow: 0 8px 24px rgb(0 0 0 / 8%);
- }
- :deep(.el-card__header) {
- padding: 16px 20px;
- background: linear-gradient(to right, #fafafa, #fff);
- border-bottom: 1px solid #f1f5f9;
- }
- :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%;
- box-shadow: 0 2px 4px rgb(0 0 0 / 10%);
- }
- .chart-title-dot-blue {
- background: #05b;
- }
- .chart-title-dot-green {
- background: #05b;
- }
- .chart-title-dot-purple {
- background: #05b;
- }
- .chart-title-dot-indigo {
- background: #05b;
- }
- .chart-title {
- font-size: 16px;
- font-weight: 600;
- letter-spacing: 0.3px;
- color: #334155;
- }
- .chart-grid {
- min-height: 220px;
- }
- .chart-item {
- display: flex;
- flex-direction: column;
- align-items: center;
- padding: 12px;
- transition: all 0.2s ease;
- &:hover {
- background: #f8fafc;
- border-radius: 8px;
- }
- }
- .gauge-chart {
- width: 100%;
- height: 160px;
- }
- .chart-label {
- display: flex;
- padding: 6px 12px;
- margin-top: 8px;
- background: #f1f5f9;
- border-radius: 20px;
- transition: all 0.2s ease;
- align-items: center;
- gap: 6px;
- .chart-item:hover & {
- background: #e2e8f0;
- }
- &.clickable {
- cursor: pointer;
- &:hover {
- background: #cbd5e1;
- transform: scale(1.05);
- }
- }
- }
- .label-dot {
- display: inline-block;
- width: 8px;
- height: 8px;
- border-radius: 50%;
- }
- .label-dot-blue {
- background: #64748b;
- box-shadow: 0 0 0 2px rgb(100 116 139 / 20%);
- }
- .label-dot-amber {
- background: #d97706;
- box-shadow: 0 0 0 2px rgb(217 119 6 / 20%);
- }
- .label-dot-gray {
- background: #78716c;
- box-shadow: 0 0 0 2px rgb(120 113 108 / 20%);
- }
- .label-text {
- font-size: 13px;
- font-weight: 500;
- color: #64748b;
- }
- .pie-chart-container {
- width: 100%;
- height: 300px;
- }
- .bar-chart-container {
- width: 100%;
- height: 300px;
- }
- .kb-screen {
- overflow: hidden;
- }
- .inspect-kb-content {
- display: grid;
- height: calc(100% - 52px * var(--kb-scale));
- padding: calc(16px * var(--kb-scale)) calc(20px * var(--kb-scale)) calc(20px * var(--kb-scale));
- gap: calc(12px * var(--kb-scale));
- grid-template-rows:
- calc(74px * var(--kb-scale)) calc(174px * var(--kb-scale))
- calc(300px * var(--kb-scale)) minmax(0, 1fr);
- }
- .inspect-kb-content > .el-row {
- height: 100%;
- margin-bottom: 0 !important;
- }
- .inspect-kb-content > .el-row > .el-col {
- height: 100%;
- }
- .filter-card,
- .stat-card,
- .chart-card-enhanced {
- height: 100%;
- background: linear-gradient(180deg, rgb(235 243 255 / 76%) 0%, rgb(221 233 251 / 58%) 100%);
- border: 1px solid rgb(255 255 255 / 68%) !important;
- border-radius: calc(8px * var(--kb-scale));
- box-shadow:
- inset 0 1px 0 rgb(255 255 255 / 78%),
- 0 calc(10px * var(--kb-scale)) calc(24px * var(--kb-scale)) rgb(46 90 164 / 10%);
- backdrop-filter: blur(12px);
- }
- .filter-card:hover,
- .stat-card:hover,
- .chart-card-enhanced:hover {
- border-color: rgb(255 255 255 / 84%) !important;
- transform: none;
- box-shadow:
- inset 0 1px 0 rgb(255 255 255 / 86%),
- 0 calc(12px * var(--kb-scale)) calc(26px * var(--kb-scale)) rgb(46 90 164 / 14%);
- }
- .filter-card :deep(.el-card__body) {
- display: flex;
- height: 100%;
- padding: calc(12px * var(--kb-scale)) calc(18px * var(--kb-scale));
- align-items: center;
- }
- .filter-card :deep(.el-form) {
- display: flex;
- width: 100%;
- margin-bottom: 0 !important;
- align-items: center;
- gap: calc(16px * var(--kb-scale));
- }
- .filter-card :deep(.el-form-item) {
- margin-right: 0;
- margin-bottom: 0;
- }
- .filter-card :deep(.el-form-item__label) {
- height: calc(32px * var(--kb-scale));
- padding-right: calc(10px * var(--kb-scale));
- font-size: calc(14px * var(--kb-scale));
- line-height: calc(32px * var(--kb-scale));
- color: #31527e;
- }
- .filter-card :deep(.el-tree-select) {
- width: calc(220px * var(--kb-scale)) !important;
- }
- .filter-card :deep(.el-date-editor) {
- width: calc(320px * var(--kb-scale)) !important;
- }
- .filter-card :deep(.el-input__wrapper),
- .filter-card :deep(.el-select__wrapper) {
- min-height: calc(32px * var(--kb-scale));
- padding: 0 calc(10px * var(--kb-scale));
- background: rgb(255 255 255 / 72%);
- }
- .filter-card :deep(.el-range-input),
- .filter-card :deep(.el-range-separator),
- .filter-card :deep(.el-input__inner) {
- font-size: calc(13px * var(--kb-scale));
- }
- .filter-card :deep(.el-button) {
- height: calc(32px * var(--kb-scale));
- padding: 0 calc(14px * var(--kb-scale));
- margin-left: calc(8px * var(--kb-scale));
- font-size: calc(13px * var(--kb-scale));
- }
- .stat-card {
- overflow: hidden;
- }
- .stat-card :deep(.el-card__body) {
- height: 100%;
- padding: 0;
- }
- .stat-card-neutral-1,
- .stat-card-neutral-2,
- .stat-card-neutral-3,
- .stat-card-neutral-4 {
- background: linear-gradient(180deg, rgb(255 255 255 / 58%) 0%, rgb(213 227 249 / 42%) 100%);
- }
- .stat-content {
- display: flex;
- height: 100%;
- padding: calc(16px * var(--kb-scale));
- flex-direction: column;
- }
- .stat-header {
- margin-bottom: calc(8px * var(--kb-scale));
- gap: calc(10px * var(--kb-scale));
- }
- .stat-icon-wrapper {
- width: calc(38px * var(--kb-scale));
- height: calc(38px * var(--kb-scale));
- background: linear-gradient(180deg, rgb(255 255 255 / 96%) 0%, rgb(235 244 255 / 92%) 100%);
- border: 1px solid rgb(255 255 255 / 88%);
- border-radius: calc(8px * var(--kb-scale));
- box-shadow: 0 calc(5px * var(--kb-scale)) calc(10px * var(--kb-scale)) rgb(57 111 194 / 12%);
- }
- .stat-icon {
- font-size: calc(19px * var(--kb-scale));
- color: #2474dd;
- }
- .stat-title {
- font-family: YouSheBiaoTiHei, sans-serif;
- font-size: calc(20px * var(--kb-scale));
- font-weight: normal;
- letter-spacing: 0;
- color: #03409b;
- }
- .stat-divider {
- margin: 0 0 calc(8px * var(--kb-scale));
- border-color: rgb(3 64 155 / 12%);
- }
- .stat-body {
- display: grid;
- min-height: 0;
- flex: 1;
- gap: calc(10px * var(--kb-scale));
- grid-template-columns: repeat(2, minmax(0, 1fr));
- }
- .stat-item {
- padding: calc(8px * var(--kb-scale)) calc(12px * var(--kb-scale));
- background: rgb(255 255 255 / 38%);
- border: 1px solid rgb(255 255 255 / 46%);
- border-radius: calc(6px * var(--kb-scale));
- }
- .stat-sub-label {
- font-size: calc(13px * var(--kb-scale));
- color: #55708f;
- }
- .stat-sub-value {
- font-family: YouSheBiaoTiHei, sans-serif;
- font-size: calc(28px * var(--kb-scale));
- font-weight: normal;
- color: #1762c8;
- }
- .stat-sub-value.text-amber-600 {
- color: #e87b1e;
- }
- .chart-card-enhanced {
- display: flex;
- overflow: hidden;
- flex-direction: column;
- }
- .chart-card-enhanced :deep(.el-card__header) {
- height: calc(42px * var(--kb-scale));
- padding: 0 calc(16px * var(--kb-scale)) 0 calc(36px * var(--kb-scale));
- background: transparent;
- border-bottom: 1px solid rgb(3 64 155 / 12%);
- }
- .chart-card-enhanced :deep(.el-card__body) {
- min-height: 0;
- padding: calc(8px * var(--kb-scale)) calc(12px * var(--kb-scale));
- flex: 1;
- }
- .chart-header,
- .chart-title-wrapper {
- height: 100%;
- }
- .chart-title-wrapper {
- position: relative;
- }
- .chart-title-dot {
- position: absolute;
- left: calc(-22px * var(--kb-scale));
- width: calc(4px * var(--kb-scale));
- height: calc(18px * var(--kb-scale));
- background: #03409b;
- border-radius: calc(2px * var(--kb-scale));
- box-shadow: none;
- }
- .chart-title {
- font-family: YouSheBiaoTiHei, sans-serif;
- font-size: calc(22px * var(--kb-scale));
- font-weight: normal;
- letter-spacing: 0;
- color: #03409b;
- }
- .chart-grid {
- height: 100%;
- min-height: 0;
- }
- .chart-item {
- height: 100%;
- padding: calc(4px * var(--kb-scale));
- justify-content: center;
- }
- .chart-item:hover {
- background: transparent;
- }
- .gauge-chart {
- height: calc(190px * var(--kb-scale));
- }
- .chart-label {
- min-height: calc(28px * var(--kb-scale));
- padding: calc(4px * var(--kb-scale)) calc(10px * var(--kb-scale));
- margin-top: 0;
- background: rgb(255 255 255 / 52%);
- border-radius: calc(5px * var(--kb-scale));
- gap: calc(6px * var(--kb-scale));
- }
- .chart-label.clickable:hover {
- background: rgb(255 255 255 / 78%);
- transform: none;
- }
- .label-dot {
- width: calc(7px * var(--kb-scale));
- height: calc(7px * var(--kb-scale));
- }
- .label-text {
- font-size: calc(13px * var(--kb-scale));
- color: #466484;
- }
- .pie-chart-container,
- .bar-chart-container {
- width: 100%;
- height: 100%;
- }
- </style>
|