| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825 |
- <template>
- <div class="app-container" style="background-color: #F7F7F7;height: 100%;overflow: auto;">
- <div class="gw-statistic-row" style="height: 12vh;">
- <gw-statistic v-for="item in statisticData" :key="item.name" :name="item.name" :value="item.value"
- :color="item.color">
- </gw-statistic>
- </div>
- <div class="gw-statistic-row" style="height: 30vh;">
- <gw-card style="width: 50%;">
- <template v-slot:title>
- <span>模型服务接口统计</span>
- </template>
- <gw-echart ref="top9Ref"></gw-echart>
- </gw-card>
- <!-- <gw-card style="width: 33%;">-->
- <!-- <template v-slot:title>-->
- <!-- <span>模型计算结果统计</span>-->
- <!-- </template>-->
- <!-- <gw-echart ref="top10Ref"></gw-echart>-->
- <!-- </gw-card>-->
- <gw-card style="width: 50%;">
- <template v-slot:title>
- <div style="display: flex;align-items: center;justify-content: space-between;">
- <div>
- <span>模型计算成功统计</span>
- </div>
- <el-segmented v-model="dateType" :options="dateTypeOptions" @change="initChartTop11"/>
- </div>
- </template>
- <gw-echart ref="top11Ref"></gw-echart>
- </gw-card>
- </div>
- <div class="gw-statistic-row" style="height: 30vh;">
- <gw-card style="width: 50%;">
- <template v-slot:title>
- <div style="display: flex;align-items: center;justify-content: space-between;">
- <div>模型调用次数</div>
- <el-segmented v-model="dateType" :options="dateTypeOptions" @change="initChartTop3"/>
- </div>
- </template>
- <gw-echart ref="top3Ref"></gw-echart>
- </gw-card>
- <gw-card style="width: 50%;">
- <template v-slot:title>
- <div style="display: flex;align-items: center;justify-content: space-between;">
- <div>
- <span>应用调用统计</span>
- <span style="color: #79bbff;"> {{ todayModelCallCount }} </span>
- <span>次</span>
- </div>
- <el-segmented v-model="dateType" :options="dateTypeOptions" @change="initChartTop1"/>
- </div>
- </template>
- <gw-echart ref="top1Ref"></gw-echart>
- </gw-card>
- <!-- <gw-card style="width: 30%;">-->
- <!-- <template v-slot:title>-->
- <!-- <div style="display: flex;align-items: center;font-weight: bold;width: 100%;">-->
- <!-- <div style="">模型服务调用次数</div>-->
- <!-- <div style="width: 50%;margin-left: 20%;">-->
- <!-- <el-select v-model="userId" class="m-2" placeholder="选则用户" style="width: 100%;"-->
- <!-- @change="initChartTop2">-->
- <!-- <el-option v-for="item in userOptions" :key="item.value" :label="item.label" :value="item.value"/>-->
- <!-- </el-select>-->
- <!-- </div>-->
- <!-- </div>-->
- <!-- </template>-->
- <!-- <gw-echart ref="top2Ref"></gw-echart>-->
- <!-- </gw-card>-->
- </div>
- <div class="gw-statistic-row" style="height: 30vh;">
- <gw-card style="width: 50%;">
- <template v-slot:title>
- <div style="display: flex;align-items: center;justify-content: space-between;">
- <div>
- <span>任务统计</span>
- </div>
- <el-segmented v-model="task" :options="taskOptions" @change="initChartTop5"/>
- </div>
- </template>
- <gw-echart ref="top5Ref"></gw-echart>
- </gw-card>
- <gw-card style="width: 50%;">
- <template v-slot:title>
- <div style="display:flex;align-items: center;">
- <span style="margin-right: 5px;">报警信息</span>
- <el-tag type="danger" effect="dark" size="small">{{ alarmTableData.length }}</el-tag>
- </div>
- </template>
- <el-table stripe :data="alarmTableData" height="100%">
- <el-table-column align="center" width="120" prop="tm" label="报警时间">
- <template #default="scope">
- {{ scope.row.alertTime ? scope.row.alertTime.substring(5, 16) : '' }}
- </template>
- </el-table-column>
- <el-table-column align="center" width="100" prop="alertType" label="报警类型">
- <template #default="scope">
- <el-tag size="small" v-if="scope.row.alertType == '1'" type="danger">
- {{ alertTypeConversion(scope.row.alertType) }}
- </el-tag>
- <el-tag size="small" v-else-if="scope.row.alertType == '2'" type="warning">
- {{ alertTypeConversion(scope.row.alertType) }}
- </el-tag>
- <el-tag size="small" v-else type="info">{{ alertTypeConversion(scope.row.alertType) }}</el-tag>
- </template>
- </el-table-column>
- <el-table-column align="center" width="150" prop="modelName" label="模型名称"/>
- <el-table-column align="center" prop="alertContent" label="报警内容"/>
- </el-table>
- </gw-card>
- </div>
- <!-- <div class="gw-statistic-row" style="height: 30vh;">-->
- <!-- <gw-card>-->
- <!-- <template v-slot:title>-->
- <!-- <div style="width: 100%;display: flex;justify-content: flex-end;">-->
- <!-- <el-select-->
- <!-- v-model="dateType"-->
- <!-- class="m-2"-->
- <!-- style="width: 20%;"-->
- <!-- @change="initChartBottom1"-->
- <!-- <el-option-->
- <!-- v-for="item in dateTypeOptions"-->
- <!-- :key="item.value"-->
- <!-- :label="item.label"-->
- <!-- :value="item.value"-->
- <!-- />-->
- <!-- </el-select>-->
- <!-- </div>-->
- <!-- </template>-->
- <!-- <gw-echart ref="bt1Ref"></gw-echart>-->
- <!-- </gw-card>-->
- <!-- </div>-->
- <!-- <div class="gw-statistic-row" style="height: 40vh;">-->
- <!-- <div class="gw-statistic-body">-->
- <!-- <el-row :gutter="10" style="height: 100%;">-->
- <!-- <el-col :span="15" style="height: 100%;position: relative;">-->
- <!-- <div class="title">访问来源追溯</div>-->
- <!-- <div style="height: calc(100% - 20px)">-->
- <!-- <div id="visit_source_chart" class="chart_container"></div>-->
- <!-- </div>-->
- <!-- </el-col>-->
- <!-- <el-col :span="9" class="visit_number" style="height: 100%;">-->
- <!-- <div class="title">访问次数排行</div>-->
- <!-- <div style="height: calc(100% - 20px)">-->
- <!-- <div id="visit_number_chart" class="chart_container"></div>-->
- <!-- </div>-->
- <!-- </el-col>-->
- <!-- </el-row>-->
- <!-- </div>-->
- <!-- </div>-->
- </div>
- </template>
- <script setup>
- import {onMounted} from 'vue';
- import * as echarts from 'echarts';
- import GwStatistic from "@/views/monitor/service/GwStatistic.vue";
- import {
- getModelCallCount,
- getModelServiceCount,
- getModelServiceSuccessCount,
- getModelTypeCallCount,
- getStatisticData,
- getUserModelCallCount,
- getViewNumByCity
- } from "@/api/monitor/server.js";
- import {initEchartMap} from "@/utils/echarts/chinaMap.js";
- import GwCard from "@/views/monitor/service/GwCard.vue";
- import GwEchart from "@/components/chart/GwEchart.vue";
- import {listUser} from "@/api/system/user.js";
- import {parseTime} from "@/utils/ruoyi.js";
- import {getAlarmList} from "@/api/service/alarm.js";
- import {snailJobLine} from "@/api/service/timing.js";
- const statisticData = ref([
- {name: '模型总数', value: 0, color: '#477ACF'},
- {name: '当前服务总数', value: 0, color: '#40B0D7'},
- {name: '服务在线比率', value: '0%', color: '#2DBEA2'},
- {name: '当月热点服务', value: '无', color: '#487ACF'},
- {name: '累计调用次数', value: '0', color: '#4BBA9B'},
- ])
- const todayModelCallCount = ref(0)
- const userId = ref(null)
- const userOptions = ref([])
- listUser().then(res => {
- userOptions.value = res.rows.map(item => {
- return {
- label: item.nickName,
- value: item.userId
- }
- })
- })
- const top1Ref = ref(null)
- const top2Ref = ref(null)
- const top3Ref = ref(null)
- const top5Ref = ref(null)
- const top9Ref = ref(null)
- const top10Ref = ref(null)
- const top11Ref = ref(null)
- const modelTypeCallCount = ref(0)
- const dateType = ref('5')
- const dateTypeOptions = ref([
- {label: '今日', value: '1'},
- {label: '近三日', value: '2'},
- {label: '近一周', value: '3'},
- {label: '近一个月', value: '4'},
- {label: '全部', value: '5'},
- ])
- const task = ref('MONTH')
- const taskOptions = ref([
- {label: '今日', value: 'DAY'},
- {label: '最近一周', value: 'WEEK'},
- {label: '最近一月', value: 'MONTH'},
- {label: '全年', value: 'YEAR'},
- ])
- const bt1Ref = ref(null)
- const echartMapData = ref([])
- const alarmTableData = ref([
- {tm: '09-12 11:12', type: '格式异常', modelName: '上海沿海风暴潮预报模型', content: '调用返回异常'},
- {tm: '09-11 11:12', type: '请求异常', modelName: '上海沿海风暴潮预报模型', content: '模型连接失败'},
- {tm: '09-10 11:12', type: '服务器未响应', modelName: '上海沿海风暴潮预报模型', content: '服务器报错'},
- ])
- function getTimes(dateType) {
- let startTime = null, endTime = null
- const date = new Date()
- switch (dateType) {
- case '1':
- startTime = parseTime(new Date(), '{y}-{m}-{d}') + ' 00:00:00'
- date.setDate(date.getDate() + 1)
- endTime = parseTime(date, '{y}-{m}-{d}') + ' 00:00:00'
- break;
- case '2':
- endTime = parseTime(new Date())
- date.setDate(date.getDate() - 3)
- startTime = parseTime(date)
- break;
- case '3':
- endTime = parseTime(new Date())
- date.setDate(date.getDate() - 7)
- startTime = parseTime(date)
- break;
- case '4':
- endTime = parseTime(new Date())
- date.setDate(date.getDate() - 30)
- startTime = parseTime(date)
- break;
- default:
- }
- return {startTime, endTime}
- }
- function initChartTop1() {
- const params = {}
- const {startTime, endTime} = getTimes(dateType.value)
- params.startTime = startTime
- params.endTime = endTime
- getModelCallCount(params).then(res => {
- let chartData = res.data
- todayModelCallCount.value = chartData.map(item => item.TOTAL).reduce((acc, current) => acc + current, 0);
- const option = {
- tooltip: {
- trigger: 'item',
- formatter: '{a} <br/>{b} : {c} ({d}%)'
- },
- grid: {
- left: '5%',
- right: '5%',
- bottom: '0%',
- top: '10%',
- containLabel: true
- },
- xAxis: {
- // splitLine: {show: false},
- axisLabel: {
- rotate: 10 // 设置标签旋转45度
- },
- data: chartData.map(item => item.APPNAME),
- },
- yAxis: {
- type: 'value',
- name: '次',
- // splitLine: {show: false}
- },
- series: [
- {
- data: chartData.map(item => item.TOTAL),
- type: 'bar',
- itemStyle: {
- normal: {
- // 这里就可以实现,配置柱状图的颜色
- color: function (params) {
- const colorList = ['#477ACF', '#40B0D7', '#2DBEA2', '#487ACF', '#4BBA9B', '#529b2e', '#95d475', '#b3e19d', '#d1edc4'];
- return colorList[params.dataIndex]
- },
- }
- },
- label: {
- show: true, // 启用标签
- position: 'top' // 位置:顶部(可选 'inside'、'bottom' 等)
- }
- }
- ]
- };
- top1Ref.value.loadChart(option);
- })
- }
- function initChartTop2() {
- getUserModelCallCount(userId.value).then(res => {
- let chartData = res.data.map(item => {
- return {
- name: item.serviceName,
- value: item.TOTAL
- }
- })
- const option = {
- tooltip: {
- trigger: 'item'
- },
- legend: {
- top: '90%',
- left: 'center',
- textStyle: {
- fontSize: 12 // 提示框字体大小
- }
- },
- series: [
- {
- type: 'pie',
- radius: ['50%', '70%'],
- avoidLabelOverlap: false,
- label: {
- show: true,
- position: 'outside',
- formatter: '{c}',
- },
- labelLine: {
- show: true
- },
- color: ['#80D0F8', '#3384C2', '#14D3D4', '#69F3C0'],
- data: chartData
- }
- ]
- }
- top2Ref.value.loadChart(option)
- })
- }
- function initChartTop3() {
- const params = {}
- const {startTime, endTime} = getTimes(dateType.value)
- params.startTime = startTime
- params.endTime = endTime
- getModelTypeCallCount(params).then(res => {
- modelTypeCallCount.value = res.data.map(item => item.TOTAL).reduce((acc, current) => acc + current, 0);
- let chartData = res.data.map(item => {
- return {
- name: item.NAME,
- value: item.TOTAL
- }
- })
- const option = {
- tooltip: {
- trigger: 'item',
- formatter: '{a} <br/>{b} : {c} ({d}%)'
- },
- grid: {
- left: '5%',
- right: '5%',
- bottom: '0%',
- top: '10%',
- containLabel: true
- },
- legend: {
- type: "scroll",
- top: '90%',
- left: 'center',
- textStyle: {
- fontSize: 12 // 提示框字体大小
- }
- },
- series: [
- {
- name: '调用次数',
- type: 'pie',
- radius: ['50%', '70%'],
- avoidLabelOverlap: false,
- label: {
- show: true,
- fontSize: 14,
- position: 'outside',
- formatter: '{b} : {c} ({d}%)',
- },
- labelLine: {
- show: true
- },
- // color: ['#529b2e', '#95d475', '#b3e19d', '#d1edc4'],
- data: chartData
- }
- ]
- };
- top3Ref.value.loadChart(option)
- })
- }
- function initChartTop9() {
- getModelServiceCount().then(res => {
- modelTypeCallCount.value = res.data.map(item => item.TOTAL).reduce((acc, current) => acc + current, 0);
- let chartData = res.data.map(item => {
- return {
- name: item.NAME,
- value: item.TOTAL
- }
- })
- const option = {
- tooltip: {
- trigger: 'item',
- formatter: '{a} <br/>{b} : {c} ({d}%)'
- },
- grid: {
- left: '5%',
- right: '5%',
- bottom: '0%',
- top: '10%',
- containLabel: true
- },
- legend: {
- type: "scroll",
- top: '90%',
- left: 'center',
- textStyle: {
- fontSize: 12 // 提示框字体大小
- }
- },
- series: [
- {
- name: '接口个数',
- type: 'pie',
- radius: ['50%', '70%'],
- avoidLabelOverlap: false,
- label: {
- show: true,
- position: 'outside',
- fontSize: 14,
- formatter: '{b} : {c} ({d}%)',
- },
- labelLine: {
- show: true
- },
- // color: ['#529b2e', '#95d475', '#b3e19d', '#d1edc4'],
- data: chartData
- }
- ]
- };
- top9Ref.value.loadChart(option)
- })
- }
- function initChartTop11() {
- const params = {}
- const {startTime, endTime} = getTimes(dateType.value)
- params.startTime = startTime
- params.endTime = endTime
- getModelServiceSuccessCount(params).then(res => {
- let rawData = [
- res.data.map(item => item.SUCCESS),
- res.data.map(item => item.FAIL)
- ]
- const totalData = [];
- for (let i = 0; i < rawData[0].length; ++i) {
- let sum = 0;
- for (let j = 0; j < rawData.length; ++j) {
- sum += rawData[j][i];
- }
- totalData.push(sum);
- }
- const series = ['成功', '失败'].map((name, sid) => {
- return {
- name,
- type: 'bar',
- stack: 'total',
- barWidth: '60%',
- label: {
- show: true,
- formatter: (params) => Math.round(params.value * 1000) / 10 + '%'
- },
- data: rawData[sid].map((d, did) =>
- totalData[did] <= 0 ? 0 : d / totalData[did]
- )
- };
- });
- const option = {
- legend: {
- selectedMode: false,
- textStyle: {
- fontSize: 12 // 提示框字体大小
- }
- },
- tooltip: {
- trigger: 'item'
- },
- grid: {
- left: '5%',
- right: '5%',
- bottom: '0%',
- top: '20%',
- containLabel: true
- },
- yAxis: {
- type: 'value',
- axisLabel: {
- interval: 0,
- fontSize: 14,
- rotate: 10, // 设置标签旋转45度
- formatter: (params) => Math.round(params * 100) + '%'
- },
- },
- xAxis: {
- type: 'category',
- axisLabel: {
- interval: 0,
- fontSize: 14,
- rotate: 10 // 设置标签旋转45度
- },
- data: res.data.map(item => item.NAME)
- },
- series
- };
- top11Ref.value.loadChart(option)
- })
- }
- function initChartBottom1() {
- const params = {}
- const {startTime, endTime} = getTimes(dateType.value)
- params.startTime = startTime
- params.endTime = endTime
- getModelCallCount(params).then(res => {
- const chartData = res.data
- const option = {
- tooltip: {},
- grid: {
- left: '5%',
- right: '5%',
- bottom: '10%',
- top: '18%',
- containLabel: true
- },
- xAxis: {
- data: chartData.map(item => item.name),
- },
- yAxis: {
- type: 'value',
- name: '次',
- // splitLine: {show: false}
- },
- series: [
- {
- name: 'Access From',
- data: chartData.map(item => item.TOTAL),
- type: 'bar',
- label: {
- show: true, // 启用标签
- position: 'top' // 位置:顶部(可选 'inside'、'bottom' 等)
- }
- }
- ]
- };
- bt1Ref.value.loadChart(option)
- })
- }
- function initChartBottom2() {
- var chartDom = document.getElementById('bt2');
- var myChart = echarts.init(chartDom);
- var option;
- option = {
- radar: {
- // shape: 'circle',
- indicator: [
- {name: 'Sales', max: 6500},
- {name: 'Administration', max: 16000},
- {name: 'Information Technology', max: 30000},
- {name: 'Customer Support', max: 38000},
- {name: 'Development', max: 52000},
- {name: 'Marketing', max: 25000}
- ]
- },
- series: [
- {
- name: 'Budget vs spending',
- type: 'radar',
- data: [
- {
- value: [4200, 3000, 20000, 35000, 50000, 18000],
- name: 'Allocated Budget'
- },
- {
- value: [5000, 14000, 28000, 26000, 42000, 21000],
- name: 'Actual Spending'
- }
- ]
- }
- ]
- };
- option && myChart.setOption(option);
- }
- function initChartTop5() {
- snailJobLine({type: task.value}).then(res => {
- const chartData = res.data
- const option = {
- color: ['#2dbea2', '#f56c6c', '#b686d4', '#e6a23c'],
- legend: {
- textStyle: {
- fontSize: 12 // 提示框字体大小
- }
- },
- grid: {
- left: '5%',
- right: '5%',
- bottom: '10%',
- top: '15%',
- containLabel: true
- },
- // tooltip: {
- // trigger: 'axis'
- // },
- tooltip: {
- trigger: "item",
- // formatter: "{a} <br/>{b} : {c}"
- },
- xAxis: {
- type: 'category',
- data: chartData.map(item => item.createDt)
- },
- yAxis: {gridIndex: 0},
- series: [
- {
- type: 'line',
- name: '成功',
- smooth: true,
- seriesLayoutBy: 'row',
- emphasis: {focus: 'series'},
- data: chartData.map(item => item.success)
- },
- {
- type: 'line',
- name: '失败',
- smooth: true,
- seriesLayoutBy: 'row',
- emphasis: {focus: 'series'},
- data: chartData.map(item => item.failNum)
- },
- {
- type: 'line',
- name: '停止',
- smooth: true,
- seriesLayoutBy: 'row',
- emphasis: {focus: 'series'},
- data: chartData.map(item => item.stop)
- },
- {
- type: 'line',
- name: '取消',
- smooth: true,
- seriesLayoutBy: 'row',
- emphasis: {focus: 'series'},
- data: chartData.map(item => item.cancel)
- }
- ]
- };
- top5Ref.value.loadChart(option);
- })
- }
- function initVisitNumberChart(name, data) {
- data.sort((a, b) => b.value - a.value)
- let d = data.slice(0, 9)
- let yAxisData = []
- let seriesData = []
- for (let i in d) {
- yAxisData.push(d[i].name)
- seriesData.push(d[i].value)
- }
- // 初始化echarts实例
- let chart = echarts.init(document.getElementById(name))
- // 绘制图表
- let option = {
- tooltip: {
- trigger: 'axis',
- axisPointer: {
- type: 'shadow',
- },
- },
- grid: {
- left: '3%',
- right: '4%',
- bottom: '3%',
- top: '3%',
- containLabel: true,
- },
- xAxis: {
- type: 'value',
- boundaryGap: [0, 0.01],
- axisLabel: {
- fontSize: 14, // x轴标签字体大小
- },
- },
- yAxis: {
- type: 'category',
- data: yAxisData,
- axisLabel: {
- fontSize: 14, // x轴标签字体大小
- },
- },
- series: [
- {
- name: '访问次数',
- type: 'bar',
- data: seriesData,
- },
- ],
- }
- chart.setOption(option)
- }
- /** 获取统计数据 */
- function initStatisticData() {
- getStatisticData().then((r) => {
- statisticData.value = r.data
- })
- }
- /** 访问来源追溯 */
- function initViewNumByCity() {
- getViewNumByCity().then((r) => {
- echartMapData.value = r.data
- // 初始化地图
- initEchartMap('visit_source_chart', echartMapData.value)
- initVisitNumberChart('visit_number_chart', echartMapData.value)
- })
- }
- /** 获取模型服务调用次数 */
- function getAlarmData() {
- getAlarmList().then(res => {
- alarmTableData.value = res.data
- })
- }
- function alertTypeConversion(alertType) {
- switch (alertType) {
- case '1':
- return '服务器异常'
- case '2':
- return '接口异常'
- case '3':
- return '返回信息异常'
- default:
- return ''
- }
- }
- onMounted(() => {
- initStatisticData()
- initChartTop1()
- // initChartTop2()
- initChartTop3()
- initChartTop5()
- initChartTop9()
- initChartTop11()
- // initChartBottom1()
- // initChartBottom2()
- // initViewNumByCity()
- getAlarmData()
- });
- </script>
- <style scoped lang="scss">
- .boxShadow {
- box-shadow: -8px 0 15px -10px rgba(0, 0, 0, 0.1), /* 左侧阴影 */
- 8px 0 15px -10px rgba(0, 0, 0, 0.1); /* 右侧阴影 */
- transition: box-shadow 0.3s ease;
- }
- .gw-statistic-row {
- display: flex;
- align-content: center;
- gap: 10px;
- margin-bottom: 10px;
- .gw-statistic-body {
- box-shadow: 0px 0px 12px rgba(0, 0, 0, 0.12);
- width: 100%;
- height: 100%;
- border-radius: 8px;
- background: #fff;
- padding: 15px 20px;
- .title {
- font-weight: bold;
- margin: 0;
- }
- }
- }
- .chart_container {
- width: 100%;
- height: 100%;
- overflow: hidden;
- }
- </style>
|