桌面端快检记录新增列表及同步mes

This commit is contained in:
2026-06-22 17:38:49 +08:00
parent 3bce685f3a
commit efcd73a565
37 changed files with 2481 additions and 416 deletions

View File

@@ -5,15 +5,6 @@
<a-button type="primary" v-auth="'mes:mes_material:add'" @click="handleAdd" preIcon="ant-design:plus-outlined">新增</a-button>
<a-button type="primary" v-auth="'mes:mes_material:exportXls'" preIcon="ant-design:export-outlined" @click="onExportXls">导出</a-button>
<j-upload-button type="primary" v-auth="'mes:mes_material:importExcel'" preIcon="ant-design:import-outlined" @click="onImportXls">导入</j-upload-button>
<a-button
type="primary"
v-auth="'mes:mes_material:rubberQuickTestInspect'"
preIcon="ant-design:experiment-outlined"
:disabled="selectedRowKeys.length === 0"
@click="handleRubberQuickTest"
>
检验
</a-button>
<a-dropdown v-if="selectedRowKeys.length > 0">
<template #overlay>
<a-menu>
@@ -38,10 +29,7 @@ import { useListPage } from '/@/hooks/system/useListPage';
import MesMaterialModal from './modules/MesMaterialModal.vue';
import { columns, searchFormSchema } from './MesMaterial.data';
import { batchDelete, deleteOne, getExportUrl, getImportUrl, list } from './MesMaterial.api';
import { batchFromMaterial } from '/@/views/xslmes/mesXslRubberQuickTestRecord/MesXslRubberQuickTestRecord.api';
import { useMessage } from '/@/hooks/web/useMessage';
const { createMessage } = useMessage();
const [registerModal, { openModal }] = useModal();
const { tableContext, onExportXls, onImportXls } = useListPage({
tableProps: {
@@ -75,18 +63,6 @@ async function batchHandleDelete() {
function handleSuccess() {
reload();
}
async function handleRubberQuickTest() {
if (!selectedRowKeys.value?.length) {
createMessage.warning('请至少选择一条胶料');
return;
}
try {
await batchFromMaterial({ materialIds: [...selectedRowKeys.value] });
createMessage.success('快检记录已生成,请到「胶料快检记录」中编辑');
} catch (e: any) {
createMessage.error(e?.message || '生成失败');
}
}
function getTableAction(record) {
return [{ label: '编辑', onClick: handleEdit.bind(null, record), auth: 'mes:mes_material:edit' }];
}

View File

@@ -13,7 +13,9 @@ enum Api {
exportXls = '/xslmes/mesXslRubberQuickTestRecord/exportXls',
queryById = '/xslmes/mesXslRubberQuickTestRecord/queryById',
queryLineList = '/xslmes/mesXslRubberQuickTestRecord/queryLineListByRecordId',
batchFromMaterial = '/xslmes/mesXslRubberQuickTestRecord/batchFromMaterial',
queryStdLineList = '/xslmes/mesXslRubberQuickTestRecord/queryStdLineListByRecordId',
queryRawLineList = '/xslmes/mesXslRubberQuickTestRecord/queryRawLineListByRecordId',
queryChartPointList = '/xslmes/mesXslRubberQuickTestRecord/queryChartPointListByRecordId',
}
export const getExportUrl = Api.exportXls;
@@ -25,7 +27,11 @@ export const queryById = (params: { id: string }) => defHttp.get({ url: Api.quer
export const queryLineListByRecordId = (params: { id: string }) => defHttp.get({ url: Api.queryLineList, params });
export const batchFromMaterial = (params) => defHttp.post({ url: Api.batchFromMaterial, params });
export const queryStdLineListByRecordId = (params: { id: string }) => defHttp.get({ url: Api.queryStdLineList, params });
export const queryRawLineListByRecordId = (params: { id: string }) => defHttp.get({ url: Api.queryRawLineList, params });
export const queryChartPointListByRecordId = (params: { id: string }) => defHttp.get({ url: Api.queryChartPointList, params });
export const deleteOne = (params, handleSuccess) => {
return defHttp.delete({ url: Api.deleteOne, params }, { joinParamsToUrl: true }).then(() => {

View File

@@ -4,14 +4,13 @@ import { JVxeColumn, JVxeTypes } from '/@/components/jeecg/JVxeTable/types';
const numProps = { style: { width: '100%' }, precision: 6 };
export const columns: BasicColumn[] = [
{ title: '号', align: 'center', dataIndex: 'recordNo', width: 150 },
{ title: '快检记录号', align: 'center', dataIndex: 'recordNo', width: 150 },
{ title: '胶料名称', align: 'center', dataIndex: 'rubberMaterialName', width: 140 },
{ title: '生产机台', align: 'center', dataIndex: 'prodEquipmentName', width: 120 },
{ title: '生产日期', align: 'center', dataIndex: 'productionDate', width: 110 },
{ title: '机台', align: 'center', dataIndex: 'prodEquipmentName', width: 120 },
{ title: '密炼日期', align: 'center', dataIndex: 'productionDate', width: 110 },
{ title: '车次编号', align: 'center', dataIndex: 'trainNo', width: 100 },
{ title: '班次', align: 'center', dataIndex: 'workShift_dictText', width: 80 },
{ title: '班组', align: 'center', dataIndex: 'workTeam_dictText', width: 80 },
{ title: '检验次数', align: 'center', dataIndex: 'inspectTimes', width: 90 },
{ title: '试验次数', align: 'center', dataIndex: 'inspectTimes', width: 90 },
{ title: '检验时间', align: 'center', dataIndex: 'inspectTime', width: 165 },
{
title: '检验人',
@@ -20,22 +19,24 @@ export const columns: BasicColumn[] = [
width: 100,
customRender: ({ record }) => record?.inspectorRealname || record?.inspectorUserId_dictText || '',
},
{ title: '检验类型', align: 'center', dataIndex: 'quickTestTypeName', width: 120 },
{ title: '实验标准', align: 'center', dataIndex: 'stdName', width: 120 },
{ title: '实验方法', align: 'center', dataIndex: 'testMethodName', width: 120 },
{ title: '实验类型', align: 'center', dataIndex: 'quickTestTypeName', width: 120 },
{ title: '检验结果', align: 'center', dataIndex: 'inspectResult_dictText', width: 90 },
{ title: '生产计划', align: 'center', dataIndex: 'productionPlanNo', width: 120 },
{ title: '密炼计划', align: 'center', dataIndex: 'productionPlanNo', width: 120 },
{ title: '检验机台', align: 'center', dataIndex: 'inspectEquipmentName', width: 120 },
{ title: '胶料卡片号', align: 'center', dataIndex: 'rubberCardNo', width: 120 },
{ title: '胶料批次', align: 'center', dataIndex: 'rubberBatchNo', width: 120 },
];
export const searchFormSchema: FormSchema[] = [
{ label: '号', field: 'recordNo', component: 'Input', colProps: { span: 6 } },
{ label: '快检记录号', field: 'recordNo', component: 'Input', colProps: { span: 6 } },
{ label: '胶料名称', field: 'rubberMaterialName', component: 'Input', colProps: { span: 6 } },
{
label: '生产机台',
label: '机台',
field: 'prodEquipmentLedgerId',
component: 'JDictSelectTag',
componentProps: { dictCode: 'mes_xsl_equipment_ledger,equipment_name,id', placeholder: '请选择生产机台' },
componentProps: { dictCode: 'mes_xsl_equipment_ledger,equipment_name,id', placeholder: '请选择机台' },
colProps: { span: 6 },
},
{
@@ -53,20 +54,13 @@ export const searchFormSchema: FormSchema[] = [
colProps: { span: 6 },
},
{
label: '班组',
field: 'workTeam',
component: 'JDictSelectTag',
componentProps: { dictCode: 'xslmes_rubber_quick_test_work_team', placeholder: '请选择班组' },
colProps: { span: 6 },
},
{
label: '检验类型',
label: '实验类型',
field: 'quickTestTypeId',
component: 'JSearchSelect',
componentProps: {
dict: 'mes_xsl_rubber_quick_test_type,type_name,id',
async: true,
placeholder: '请选择验类型',
placeholder: '请选择验类型',
},
colProps: { span: 6 },
},
@@ -82,7 +76,7 @@ export const searchFormSchema: FormSchema[] = [
},
colProps: { span: 6 },
},
{ label: '生产计划', field: 'productionPlanNo', component: 'Input', colProps: { span: 6 } },
{ label: '密炼计划', field: 'productionPlanNo', component: 'Input', colProps: { span: 6 } },
{ label: '胶料批次', field: 'rubberBatchNo', component: 'Input', colProps: { span: 6 } },
{
label: '检验结果',
@@ -103,7 +97,7 @@ export const formSchema: FormSchema[] = [
{ label: '', field: 'inspectorUsername', component: 'Input', show: false },
{ label: '', field: 'quickTestTypeId', component: 'Input', show: false },
{
label: '号',
label: '快检记录号',
field: 'recordNo',
component: 'Input',
componentProps: { readonly: true, placeholder: '保存时自动生成' },
@@ -115,13 +109,31 @@ export const formSchema: FormSchema[] = [
componentProps: { readonly: true },
},
{
label: '生产机台',
label: '实验标准',
field: 'stdName',
component: 'Input',
componentProps: { readonly: true },
},
{
label: '实验方法',
field: 'testMethodName',
component: 'Input',
componentProps: { readonly: true },
},
{
label: '实验类型',
field: 'quickTestTypeName',
component: 'Input',
componentProps: { readonly: true },
},
{
label: '炼机台',
field: 'prodEquipmentName',
component: 'Input',
slot: 'prodEquipmentPicker',
},
{
label: '生产日期',
label: '密炼日期',
field: 'productionDate',
component: 'DatePicker',
componentProps: { valueFormat: 'YYYY-MM-DD', style: { width: '100%' } },
@@ -134,13 +146,7 @@ export const formSchema: FormSchema[] = [
componentProps: { dictCode: 'xslmes_rubber_quick_test_work_shift', placeholder: '请选择班次' },
},
{
label: '班组',
field: 'workTeam',
component: 'JDictSelectTag',
componentProps: { dictCode: 'xslmes_rubber_quick_test_work_team', placeholder: '请选择班组' },
},
{
label: '检验次数',
label: '试验次数',
field: 'inspectTimes',
component: 'InputNumber',
componentProps: { style: { width: '100%' }, min: 0, precision: 0 },
@@ -176,23 +182,13 @@ export const formSchema: FormSchema[] = [
}),
},
{ label: '', field: 'inspectorRealname', component: 'Input', show: false },
{
label: '检验类型',
field: 'quickTestTypeId',
component: 'JSearchSelect',
componentProps: {
dict: 'mes_xsl_rubber_quick_test_type,type_name,id',
async: true,
placeholder: '请选择检验类型',
},
},
{
label: '检验结果',
field: 'inspectResult',
component: 'JDictSelectTag',
componentProps: { dictCode: 'xslmes_rubber_quick_test_record_result', placeholder: '合格/不合格' },
},
{ label: '生产计划', field: 'productionPlanNo', component: 'Input' },
{ label: '密炼计划', field: 'productionPlanNo', component: 'Input' },
{
label: '检验机台',
field: 'inspectEquipmentName',
@@ -203,6 +199,40 @@ export const formSchema: FormSchema[] = [
{ label: '胶料批次', field: 'rubberBatchNo', component: 'Input' },
];
export const stdLineJVxeColumns: JVxeColumn[] = [
{ title: '', key: 'dataPointId', type: JVxeTypes.hidden },
{ title: '数据点', key: 'pointName', type: JVxeTypes.normal, width: 160, disabled: true },
{ title: '下限值', key: 'lowerLimit', type: JVxeTypes.normal, width: 100, disabled: true },
{ title: '下警告值', key: 'lowerWarn', type: JVxeTypes.normal, width: 100, disabled: true },
{ title: '目标值', key: 'targetValue', type: JVxeTypes.normal, width: 100, disabled: true },
{ title: '上警告值', key: 'upperWarn', type: JVxeTypes.normal, width: 100, disabled: true },
{ title: '上限值', key: 'upperLimit', type: JVxeTypes.normal, width: 100, disabled: true },
];
export const rawLineJVxeColumns: JVxeColumn[] = [
{ title: '', key: 'dataPointId', type: JVxeTypes.hidden },
{ title: '编号', key: 'rowNo', type: JVxeTypes.normal, width: 90, disabled: true },
{ title: '数据点', key: 'inspectItem', type: JVxeTypes.normal, width: 140, disabled: true },
{ title: '下限值', key: 'lowerLimit', type: JVxeTypes.normal, width: 90, disabled: true },
{ title: '上限值', key: 'upperLimit', type: JVxeTypes.normal, width: 90, disabled: true },
{
title: '检测值',
key: 'inspectValue',
type: JVxeTypes.inputNumber,
width: 100,
componentProps: numProps,
},
{
title: '行检验结果',
key: 'rowInspectResult',
type: JVxeTypes.select,
width: 110,
disabled: true,
dictCode: 'xslmes_rubber_quick_test_record_result',
},
];
/** @deprecated 历史汇总明细,仅兼容旧数据 */
export const lineJVxeColumns: JVxeColumn[] = [
{ title: '', key: 'dataPointId', type: JVxeTypes.hidden },
{ title: '检验项目', key: 'inspectItem', type: JVxeTypes.normal, width: 180, disabled: true },

View File

@@ -3,14 +3,14 @@
v-bind="$attrs"
destroyOnClose
:title="title"
width="1100px"
width="1200px"
@register="registerModal"
@ok="handleSubmit"
>
<BasicForm @register="registerForm">
<template #prodEquipmentPicker="{ model, field }">
<a-input-group compact style="display: flex; width: 100%">
<a-input v-model:value="model[field]" read-only placeholder="请选择生产机台" style="flex: 1" />
<a-input v-model:value="model[field]" read-only placeholder="请选择机台" style="flex: 1" />
<a-button type="primary" :disabled="isDetail" @click="openProdEquipmentSelect">选择</a-button>
<a-button v-if="model.prodEquipmentLedgerId && !isDetail" @click="clearProdEquipment(model)">清除</a-button>
</a-input-group>
@@ -23,34 +23,86 @@
</a-input-group>
</template>
</BasicForm>
<a-divider orientation="left">检验明细由实验标准带出不可增删</a-divider>
<JVxeTable
v-if="tableReady"
ref="lineTableRef"
row-number
keep-source
:toolbar="false"
:insert-row="false"
:remove-btn="false"
:max-height="380"
:loading="lineLoading"
:columns="lineJVxeColumns"
:dataSource="lineDataSource"
:disabled="isDetail"
/>
<template v-if="useNewDetail">
<a-divider orientation="left">数据标准明细</a-divider>
<JVxeTable
v-if="tableReady"
row-number
keep-source
:toolbar="false"
:insert-row="false"
:remove-btn="false"
:max-height="260"
:loading="detailLoading"
:columns="stdLineJVxeColumns"
:dataSource="stdLineDataSource"
disabled
/>
<a-divider orientation="left">试验结果明细</a-divider>
<JVxeTable
v-if="tableReady"
row-number
keep-source
:toolbar="false"
:insert-row="false"
:remove-btn="false"
:max-height="320"
:loading="detailLoading"
:columns="rawLineJVxeColumns"
:dataSource="rawLineDataSource"
:disabled="isDetail"
/>
<a-divider orientation="left">曲线图</a-divider>
<a-row :gutter="16">
<a-col :span="12">
<div class="chart-title">温度曲线上模/下模</div>
<div ref="tempChartRef" class="chart-box"></div>
</a-col>
<a-col :span="12">
<div class="chart-title">S'(dNm) 曲线</div>
<div ref="torqueChartRef" class="chart-box"></div>
</a-col>
</a-row>
</template>
<template v-else>
<a-divider orientation="left">检验明细(历史数据)</a-divider>
<JVxeTable
v-if="tableReady"
row-number
keep-source
:toolbar="false"
:insert-row="false"
:remove-btn="false"
:max-height="380"
:loading="detailLoading"
:columns="lineJVxeColumns"
:dataSource="lineDataSource"
:disabled="isDetail"
/>
</template>
<MesXslEquipmentLedgerSelectModal @register="registerLedgerModal" @select="onLedgerSelect" />
</BasicModal>
</template>
<script lang="ts" setup>
import { computed, ref, unref } from 'vue';
import { computed, nextTick, ref, unref, watch, type Ref } from 'vue';
import { BasicModal, useModalInner, useModal } from '/@/components/Modal';
import { BasicForm, useForm } from '/@/components/Form/index';
import type { JVxeTableInstance } from '/@/components/jeecg/JVxeTable/types';
import { useMessage } from '/@/hooks/web/useMessage';
import { useUserStore } from '/@/store/modules/user';
import { formSchema, lineJVxeColumns } from '../MesXslRubberQuickTestRecord.data';
import { saveOrUpdate, queryById, queryLineListByRecordId } from '../MesXslRubberQuickTestRecord.api';
import { useECharts } from '/@/hooks/web/useECharts';
import {
formSchema,
lineJVxeColumns,
stdLineJVxeColumns,
rawLineJVxeColumns,
} from '../MesXslRubberQuickTestRecord.data';
import { saveOrUpdate, queryById, queryChartPointListByRecordId } from '../MesXslRubberQuickTestRecord.api';
import MesXslEquipmentLedgerSelectModal from '/@/views/xslmes/mesXslEquipInspectConfig/components/MesXslEquipmentLedgerSelectModal.vue';
const emit = defineEmits(['register', 'success']);
@@ -59,9 +111,19 @@
const isDetail = ref(false);
const tableReady = ref(false);
const lineLoading = ref(false);
const detailLoading = ref(false);
const useNewDetail = ref(false);
const stdLineDataSource = ref<Recordable[]>([]);
const rawLineDataSource = ref<Recordable[]>([]);
const chartPointDataSource = ref<Recordable[]>([]);
const lineDataSource = ref<Recordable[]>([]);
const lineTableRef = ref<JVxeTableInstance>();
const tempChartRef = ref<HTMLDivElement | null>(null);
const torqueChartRef = ref<HTMLDivElement | null>(null);
const { setOptions: setTempChartOptions, resize: resizeTempChart } = useECharts(tempChartRef as Ref<HTMLDivElement>);
const { setOptions: setTorqueChartOptions, resize: resizeTorqueChart } = useECharts(torqueChartRef as Ref<HTMLDivElement>);
const ledgerPickTarget = ref<'prod' | 'inspect'>('prod');
const [registerForm, { resetFields, setFieldsValue, validate, setProps, getFieldsValue }] = useForm({
@@ -75,19 +137,21 @@
const [registerModal, { setModalProps, closeModal }] = useModalInner(async (data) => {
tableReady.value = false;
stdLineDataSource.value = [];
rawLineDataSource.value = [];
chartPointDataSource.value = [];
lineDataSource.value = [];
useNewDetail.value = false;
await resetFields();
setModalProps({ confirmLoading: false, showCancelBtn: data?.showFooter, showOkBtn: data?.showFooter });
isDetail.value = !data?.showFooter;
setProps({ disabled: !data?.showFooter });
if (data?.record?.id) {
lineLoading.value = true;
detailLoading.value = true;
try {
const mainRaw = await queryById({ id: data.record.id });
const m = (mainRaw as any)?.id != null ? mainRaw : (mainRaw as any)?.result ?? mainRaw;
const linesRaw = await queryLineListByRecordId({ id: data.record.id });
const list = Array.isArray(linesRaw) ? linesRaw : (linesRaw as any)?.result ?? [];
const patch: Recordable = { ...m };
if (data?.showFooter && !patch.inspectorRealname && !patch.inspectorUserId) {
const user = userStore.getUserInfo || {};
@@ -96,16 +160,95 @@
patch.inspectorRealname = user.realname;
}
await setFieldsValue(patch);
lineDataSource.value = list || [];
const stdLines = m?.stdLineList ?? [];
const rawLines = m?.rawLineList ?? [];
let chartPoints = m?.chartPointList ?? [];
const legacyLines = m?.lineList ?? [];
if (stdLines.length > 0 || rawLines.length > 0 || chartPoints.length > 0) {
useNewDetail.value = true;
stdLineDataSource.value = stdLines;
rawLineDataSource.value = rawLines;
if (!chartPoints.length && data?.record?.id) {
try {
const chartRes = await queryChartPointListByRecordId({ id: data.record.id });
chartPoints = Array.isArray(chartRes) ? chartRes : (chartRes as any)?.result ?? [];
} catch (_) {
chartPoints = [];
}
}
chartPointDataSource.value = chartPoints;
} else {
lineDataSource.value = legacyLines;
}
} finally {
lineLoading.value = false;
detailLoading.value = false;
}
}
tableReady.value = true;
if (useNewDetail.value) {
await scheduleRenderCharts(chartPointDataSource.value);
}
});
async function scheduleRenderCharts(points: Recordable[]) {
await nextTick();
await nextTick();
window.setTimeout(() => {
renderCharts(points);
resizeTempChart();
resizeTorqueChart();
}, 120);
}
watch(useNewDetail, async (visible) => {
if (visible && chartPointDataSource.value.length) {
await scheduleRenderCharts(chartPointDataSource.value);
}
});
const title = computed(() => (unref(isDetail) ? '快检记录详情' : '编辑胶料快检记录'));
function toChartNumber(value: unknown) {
if (value === null || value === undefined || value === '') return null;
const num = Number(value);
return Number.isFinite(num) ? num : null;
}
function renderCharts(points: Recordable[]) {
if (!points?.length) {
setTempChartOptions({ title: { text: '暂无曲线数据', left: 'center', top: 'center', textStyle: { fontSize: 14 } } });
setTorqueChartOptions({ title: { text: '暂无曲线数据', left: 'center', top: 'center', textStyle: { fontSize: 14 } } });
return;
}
const sorted = [...points].sort((a, b) => (a.sortNo ?? 0) - (b.sortNo ?? 0));
const times = sorted.map((p) => toChartNumber(p.timeMin) ?? 0);
setTempChartOptions({
title: undefined,
tooltip: { trigger: 'axis' },
legend: { data: ['上模温度', '下模温度'] },
grid: { left: 48, right: 24, top: 40, bottom: 32 },
xAxis: { type: 'category', name: '时间(min)', data: times },
yAxis: { type: 'value', name: '温度()', min: 189, max: 201, interval: 3 },
series: [
{ name: '上模温度', type: 'line', smooth: true, data: sorted.map((p) => toChartNumber(p.upperTemp)) },
{ name: '下模温度', type: 'line', smooth: true, data: sorted.map((p) => toChartNumber(p.lowerTemp)) },
],
});
setTorqueChartOptions({
title: undefined,
tooltip: { trigger: 'axis' },
legend: { data: ["S'(dNm)"] },
grid: { left: 48, right: 24, top: 40, bottom: 32 },
xAxis: { type: 'category', name: '时间(min)', data: times },
yAxis: { type: 'value', name: "S'(dNm)", min: 0, max: 14.8 },
series: [{ name: "S'(dNm)", type: 'line', smooth: true, data: sorted.map((p) => toChartNumber(p.torqueS)) }],
});
}
function openProdEquipmentSelect() {
ledgerPickTarget.value = 'prod';
const vals = getFieldsValue();
@@ -149,23 +292,36 @@
}
try {
const values = await validate();
const lineRef = lineTableRef.value as any;
const tableData = (lineRef?.getTableData?.() || lineDataSource.value || []) as Recordable[];
const lineList = tableData
.filter((r) => r && r.inspectItem)
.map((r) => ({
dataPointId: r.dataPointId,
inspectItem: r.inspectItem,
lowerLimit: r.lowerLimit,
inspectValue: r.inspectValue,
upperLimit: r.upperLimit,
}));
if (!lineList.length) {
createMessage.warning('检验明细不能为空');
return;
const payload: Recordable = { ...values };
if (unref(useNewDetail)) {
payload.stdLineList = stdLineDataSource.value || [];
payload.rawLineList = rawLineDataSource.value || [];
payload.chartPointList = chartPointDataSource.value || [];
if (!payload.stdLineList.length || !payload.rawLineList.length) {
createMessage.warning('数据标准明细与试验结果明细不能为空');
return;
}
} else {
const tableData = lineDataSource.value || [];
const lineList = tableData
.filter((r) => r && r.inspectItem)
.map((r) => ({
dataPointId: r.dataPointId,
inspectItem: r.inspectItem,
lowerLimit: r.lowerLimit,
inspectValue: r.inspectValue,
upperLimit: r.upperLimit,
}));
if (!lineList.length) {
createMessage.warning('检验明细不能为空');
return;
}
payload.lineList = lineList;
}
setModalProps({ confirmLoading: true });
await saveOrUpdate({ ...values, lineList }, true);
await saveOrUpdate(payload, true);
closeModal();
emit('success');
} finally {
@@ -173,3 +329,15 @@
}
}
</script>
<style scoped>
.chart-title {
font-size: 13px;
color: rgba(0, 0, 0, 0.65);
margin-bottom: 8px;
}
.chart-box {
width: 100%;
height: 220px;
}
</style>