123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317 |
- <script setup lang="ts">
- import { computed, onMounted, onUnmounted, ref } from 'vue';
- import VChart from 'vue-echarts';
- import { LineChart, PieChart } from 'echarts/charts';
- import {
- DatasetComponent,
- GridComponent,
- LegendComponent,
- MarkLineComponent,
- TitleComponent,
- TooltipComponent,
- TransformComponent,
- } from 'echarts/components';
- import { use } from 'echarts/core';
- import { CanvasRenderer } from 'echarts/renderers';
- import SvgIcon from './SvgIcon.vue';
- import type { MonitoringPointData } from '@/types';
- use([
- CanvasRenderer,
- TitleComponent,
- DatasetComponent,
- TransformComponent,
- TooltipComponent,
- LegendComponent,
- GridComponent,
- LineChart,
- PieChart,
- MarkLineComponent,
- ]);
- const list = ref<number[]>([]);
- interface Props {
- data: MonitoringPointData;
- monitoringId?: number;
- iconShow: boolean;
- }
- const emit = defineEmits(['editorClick', 'historicalDataClick']);
- const props = defineProps<Props>();
- const getCurvedData = () => {
- list.value = [];
- if (props.data.tempData) {
- props.data.tempData.forEach((item) => {
- list.value.push(item.value);
- });
- }
- };
- const getColor = (value: number) => {
- if (props.data.status === 1) {
- if (value === 1) {
- return '#32BAC0';
- } else if (value === 2) {
- return 'rgba(50,186,192,0.15)';
- }
- }
- if (props.data.status === 2 || props.data.status === 3) {
- if (value === 1) {
- return '#F56C6C';
- } else if (value === 2) {
- return 'rgba(245,108,108,0.15)';
- }
- }
- if (props.data.status === 0 || props.data.status === -1 || !props.data.status) {
- if (value === 1) {
- return '#999';
- } else if (value === 2) {
- return 'rgba(153,153,153,0.15)';
- }
- }
- };
- const option = computed(() => {
- getCurvedData();
- if (!props.data) return {};
- return {
- backgroundColor: getColor(2),
- xAxis: {
- show: false, // 隐藏 X 轴
- type: 'category',
- boundaryGap: false, // 关闭首尾留白
- },
- yAxis: {
- type: 'value', // 数值轴
- min: 0, // 最小值
- max: 100, // 最大值
- interval: 20, // 刻度间隔
- show: false, // 隐藏 X 轴
- },
- series: [
- {
- type: 'line',
- data: list.value,
- symbol: 'none', // 不显示数据点
- color: getColor(1),
- lineStyle: {
- width: 2,
- },
- markLine: {
- // 添加最大值虚线
- silent: true,
- symbol: 'none',
- lineStyle: {
- type: 'dashed',
- color: 'rgba(151,151,151,0.5)',
- width: 1,
- },
- label: {
- show: true, // 必须开启标签
- position: 'insideStartTop', // 标签位置(左侧上方)
- formatter: '{c}°C', // 显示值({c} 会自动替换为 yAxis 的值)
- color: 'rgba(0,0,0,0.5)', // 文字颜色
- verticalAlign: 'bottom', // 垂直对齐方式
- offset: [-6, 0], // 微调位置 [水平偏移, 垂直偏移]
- fontSize: '10px',
- },
- data: [
- {
- yAxis: props.data.tempUpper, // 在最大值位置画线
- },
- ],
- },
- },
- ],
- // 关键配置:调整 grid 边距
- grid: {
- top: 20, // 上边距为0
- right: 8, // 右边距为0
- bottom: 8, // 下边距为0
- left: 8, // 左边距为0
- containLabel: false, // 不包含坐标轴标签区域
- },
- };
- });
- const textContainer = ref<HTMLElement | null>(null);
- const shouldShowEllipsis = ref(false);
- // 判断是否溢出
- const checkOverflow = () => {
- if (!textContainer.value) return;
- const element = textContainer.value;
- shouldShowEllipsis.value = element.scrollWidth > element.clientWidth;
- };
- // 自动响应式处理
- const handleResize = () => {
- checkOverflow();
- };
- const editorMonitoring = (id: number) => {
- emit('editorClick', id);
- };
- const addHistoricalData = (data: MonitoringPointData) => {
- emit('historicalDataClick', data);
- };
- // 生命周期钩子
- onMounted(() => {
- checkOverflow();
- window.addEventListener('resize', handleResize);
- });
- onUnmounted(() => {
- window.removeEventListener('resize', handleResize);
- });
- </script>
- <template>
- <div :class="monitoringId === data.id ? 'line-chart-content choose-content-border' : 'line-chart-content'">
- <AFlex justify="space-between" align="center">
- <APopover v-if="shouldShowEllipsis">
- <template #content>
- {{ data.name }}
- </template>
- <div ref="textContainer" class="line-chart-header-text">{{ data.name }}</div>
- </APopover>
- <div v-else ref="textContainer" class="line-chart-header-text">{{ data.name }}</div>
- <SvgIcon v-if="iconShow" class="line-chart-header-icon" @click="editorMonitoring(data.id)" name="adjustment" />
- </AFlex>
- <div @click="addHistoricalData(data)" class="chart-container" style="cursor: pointer">
- <VChart class="chart chart-bgc" :option="option" />
- </div>
- <AFlex justify="space-between" align="center" class="line-chart-footer">
- <AFlex align="center">
- <AFlex
- justify="center"
- align="center"
- class="line-chart-footer-div"
- :style="`background:${props.data.status === 0 || props.data.status === -1 || !props.data.status ? '#F0F0F0' : '#f2fcf9'}`"
- >
- <SvgIcon
- class="line-chart-icon"
- :style="`color:${props.data.status === 0 || props.data.status === -1 || !props.data.status ? '#999' : '#32BAC0'}`"
- name="temperature"
- /></AFlex>
- <div>
- <span class="line-chart-footer-text">{{ data.temperature }}</span>
- <span class="line-chart-footer-unit">℃</span>
- </div>
- </AFlex>
- <AFlex align="center">
- <AFlex
- justify="center"
- align="center"
- class="line-chart-footer-div"
- :style="`background:${props.data.status === 0 || props.data.status === -1 || !props.data.status ? '#F0F0F0' : '#f2fcf9'}`"
- ><SvgIcon
- class="line-chart-icon"
- :style="`color:${props.data.status === 0 || props.data.status === -1 || !props.data.status ? '#999' : '#32BAC0'}`"
- name="humidity"
- /></AFlex>
- <div>
- <span class="line-chart-footer-text">{{ data.humidity }}</span>
- <span class="line-chart-footer-unit">%</span>
- </div>
- </AFlex>
- </AFlex>
- </div>
- </template>
- <style lang="scss" scoped>
- /* 确保图表区域继承光标样式 */
- .chart-container :deep(canvas) {
- cursor: pointer !important;
- }
- .chart-bgc {
- width: 172px;
- height: 94px;
- margin-top: 12px; /* 关键:隐藏图表溢出部分 */
- overflow: hidden;
- cursor: pointer;
- border-radius: 8px; /* 圆角 */
- }
- .line-chart-icon {
- font-size: 16px;
- color: #32bac0;
- }
- .line-chart-footer-unit {
- font-size: 12px;
- font-style: normal;
- font-weight: 400;
- line-height: 20px;
- color: rgb(0 0 0 / 85%);
- text-align: left;
- }
- .line-chart-footer-text {
- font-size: 16px;
- font-style: normal;
- font-weight: 500;
- line-height: 28px;
- color: rgb(0 0 0 / 85%);
- text-align: left;
- }
- .line-chart-footer-div {
- width: 30px;
- height: 30px;
- background: #f2fcf9;
- border-radius: 8px;
- }
- .line-chart-footer {
- margin-top: 12px;
- }
- .line-chart-header-icon {
- cursor: pointer;
- }
- .line-chart-header-text {
- max-width: 160px;
- overflow: hidden; /* 隐藏溢出内容 */
- font-size: 16px;
- font-style: normal;
- font-weight: 400;
- line-height: 24px;
- color: rgb(0 0 0 / 85%);
- text-align: left;
- text-overflow: ellipsis; /* 显示省略号 */
- white-space: nowrap; /* 强制不换行 */
- }
- .line-chart-content {
- width: 204px;
- height: 204px;
- padding: 16px;
- margin: 2px 4px 14px;
- background: #fff;
- border-radius: 8px;
- box-shadow: 0 2px 6px 0 rgb(0 0 0 / 10%);
- }
- .choose-content-border {
- border: 2px solid var(--antd-color-primary-hover);
- }
- .line-chart-content:hover {
- border: 2px solid var(--antd-color-primary-hover);
- }
- </style>
|