|
@@ -0,0 +1,712 @@
|
|
|
|
+<script setup lang="ts">
|
|
|
|
+import { computed, onMounted, ref, watch } from 'vue';
|
|
|
|
+import VChart from 'vue-echarts';
|
|
|
|
+import dayjs from 'dayjs';
|
|
|
|
+import { LineChart, PieChart } from 'echarts/charts';
|
|
|
|
+import {
|
|
|
|
+ AxisPointerComponent,
|
|
|
|
+ DatasetComponent,
|
|
|
|
+ GridComponent,
|
|
|
|
+ LegendComponent,
|
|
|
|
+ MarkLineComponent,
|
|
|
|
+ TitleComponent,
|
|
|
|
+ TooltipComponent,
|
|
|
|
+ TransformComponent,
|
|
|
|
+} from 'echarts/components';
|
|
|
|
+import { use } from 'echarts/core';
|
|
|
|
+import { CanvasRenderer } from 'echarts/renderers';
|
|
|
|
+
|
|
|
|
+import SvgIcon from '@/components/SvgIcon.vue';
|
|
|
|
+import { useRequest } from '@/hooks/request';
|
|
|
|
+import { t } from '@/i18n';
|
|
|
|
+import {
|
|
|
|
+ addBatchMonitorPointAlarm,
|
|
|
|
+ getMonitorPointAlarm,
|
|
|
|
+ getMonitorPointInfo,
|
|
|
|
+ getPointTimeSeries,
|
|
|
|
+ updateBatchMonitorPointAlarm,
|
|
|
|
+ updateLimits,
|
|
|
|
+} from '@/api';
|
|
|
|
+
|
|
|
|
+import type { FormInstance, Rule } from 'ant-design-vue/es/form';
|
|
|
|
+import type { SelectValue } from 'ant-design-vue/es/select';
|
|
|
|
+import type { Dayjs } from 'dayjs';
|
|
|
|
+import type {
|
|
|
|
+ FormatterData,
|
|
|
|
+ HistoricalDataSequence,
|
|
|
|
+ LimitForm,
|
|
|
|
+ MonitoringPointData,
|
|
|
|
+ TempHumidityControlSettings,
|
|
|
|
+ WarningItem,
|
|
|
|
+} from '@/types';
|
|
|
|
+
|
|
|
|
+use([
|
|
|
|
+ CanvasRenderer,
|
|
|
|
+ TitleComponent,
|
|
|
|
+ DatasetComponent,
|
|
|
|
+ TransformComponent,
|
|
|
|
+ TooltipComponent,
|
|
|
|
+ LegendComponent,
|
|
|
|
+ GridComponent,
|
|
|
|
+ LineChart,
|
|
|
|
+ PieChart,
|
|
|
|
+ MarkLineComponent,
|
|
|
|
+ AxisPointerComponent,
|
|
|
|
+]);
|
|
|
|
+
|
|
|
|
+const { handleRequest } = useRequest();
|
|
|
|
+
|
|
|
|
+interface Props {
|
|
|
|
+ monitoringId?: number;
|
|
|
|
+ monitoringData: MonitoringPointData[];
|
|
|
|
+}
|
|
|
|
+const props = defineProps<Props>();
|
|
|
|
+const formRef = ref<FormInstance>();
|
|
|
|
+const dateShortcut = ref<number>(0);
|
|
|
|
+const degreeValue = ref<string>('1');
|
|
|
|
+const limitOpen = ref<boolean>(false);
|
|
|
|
+const warningOpen = ref<boolean>(false);
|
|
|
|
+const warningShow = ref<boolean>(false);
|
|
|
|
+const curvedDataList = ref<TempHumidityControlSettings>();
|
|
|
|
+type RangeValue = [Dayjs, Dayjs];
|
|
|
|
+const historicalDataValue = ref<RangeValue>();
|
|
|
|
+const warningList = ref<WarningItem[]>([
|
|
|
|
+ {
|
|
|
|
+ pointId: props.monitoringId,
|
|
|
|
+ enabled: false,
|
|
|
|
+ type: 1,
|
|
|
|
+ val: 0,
|
|
|
|
+ duration: 0,
|
|
|
|
+ },
|
|
|
|
+ {
|
|
|
|
+ pointId: props.monitoringId,
|
|
|
|
+ enabled: false,
|
|
|
|
+ type: 2,
|
|
|
|
+ val: 0,
|
|
|
|
+ duration: 0,
|
|
|
|
+ },
|
|
|
|
+]);
|
|
|
|
+const limitForm = ref<LimitForm>({
|
|
|
|
+ id: undefined,
|
|
|
|
+ regionId: undefined,
|
|
|
|
+ tempUpper: 0,
|
|
|
|
+ tempLower: 0,
|
|
|
|
+ tempPreset: 0,
|
|
|
|
+ humidityUpper: 0,
|
|
|
|
+ humidityLower: 0,
|
|
|
|
+ humidityPreset: 0,
|
|
|
|
+ batch: false,
|
|
|
|
+});
|
|
|
|
+const degreeData = ref([
|
|
|
|
+ {
|
|
|
|
+ value: '1',
|
|
|
|
+ label: '温度',
|
|
|
|
+ icon: 'temperature',
|
|
|
|
+ },
|
|
|
|
+ {
|
|
|
|
+ value: '2',
|
|
|
|
+ label: '湿度',
|
|
|
|
+ icon: 'humidity',
|
|
|
|
+ },
|
|
|
|
+]);
|
|
|
|
+
|
|
|
|
+const rules: Record<string, Rule[]> = {
|
|
|
|
+ id: [{ required: true, message: t('common.cannotEmpty'), trigger: 'change' }],
|
|
|
|
+ tempUpper: [{ required: true, message: t('common.cannotEmpty'), trigger: 'change' }],
|
|
|
|
+ tempLower: [{ required: true, message: t('common.cannotEmpty'), trigger: 'change' }],
|
|
|
|
+ tempPreset: [{ required: true, message: t('common.cannotEmpty'), trigger: 'change' }],
|
|
|
|
+ humidityUpper: [{ required: true, message: t('common.cannotEmpty'), trigger: 'change' }],
|
|
|
|
+ humidityLower: [{ required: true, message: t('common.cannotEmpty'), trigger: 'change' }],
|
|
|
|
+ humidityPreset: [{ required: true, message: t('common.cannotEmpty'), trigger: 'change' }],
|
|
|
|
+};
|
|
|
|
+
|
|
|
|
+const addLimit = () => {
|
|
|
|
+ limitOpen.value = true;
|
|
|
|
+ limitForm.value.batch = false;
|
|
|
|
+};
|
|
|
|
+
|
|
|
|
+const addWarning = () => {
|
|
|
|
+ getMonitorPointList();
|
|
|
|
+ warningOpen.value = true;
|
|
|
|
+};
|
|
|
|
+
|
|
|
|
+const getWarning = () => {
|
|
|
|
+ handleRequest(async () => {
|
|
|
|
+ if (warningShow.value) {
|
|
|
|
+ await updateBatchMonitorPointAlarm(warningList.value);
|
|
|
|
+ } else {
|
|
|
|
+ await addBatchMonitorPointAlarm(warningList.value);
|
|
|
|
+ }
|
|
|
|
+ });
|
|
|
|
+
|
|
|
|
+ warningOpen.value = false;
|
|
|
|
+};
|
|
|
|
+
|
|
|
|
+const getMonitorPointList = () => {
|
|
|
|
+ handleRequest(async () => {
|
|
|
|
+ if (props.monitoringId) {
|
|
|
|
+ const data = await getMonitorPointAlarm(props.monitoringId);
|
|
|
|
+ if (data.length) {
|
|
|
|
+ warningShow.value = true;
|
|
|
|
+ data.forEach((item, i) => {
|
|
|
|
+ const { id, pointId, enabled, type, val, duration } = item;
|
|
|
|
+ Object.assign(warningList.value[i], {
|
|
|
|
+ id,
|
|
|
|
+ pointId,
|
|
|
|
+ enabled,
|
|
|
|
+ type,
|
|
|
|
+ val,
|
|
|
|
+ duration,
|
|
|
|
+ });
|
|
|
|
+ });
|
|
|
|
+ } else {
|
|
|
|
+ warningShow.value = false;
|
|
|
|
+ warningList.value = [
|
|
|
|
+ {
|
|
|
|
+ pointId: props.monitoringId,
|
|
|
|
+ enabled: false,
|
|
|
|
+ type: 1,
|
|
|
|
+ val: 0,
|
|
|
|
+ duration: 0,
|
|
|
|
+ },
|
|
|
|
+ {
|
|
|
|
+ pointId: props.monitoringId,
|
|
|
|
+ enabled: false,
|
|
|
|
+ type: 2,
|
|
|
|
+ val: 0,
|
|
|
|
+ duration: 0,
|
|
|
|
+ },
|
|
|
|
+ ];
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ });
|
|
|
|
+};
|
|
|
|
+
|
|
|
|
+const getLimit = () => {
|
|
|
|
+ formRef.value
|
|
|
|
+ ?.validate()
|
|
|
|
+ .then(() => {
|
|
|
|
+ handleRequest(async () => {
|
|
|
|
+ await updateLimits(limitForm.value);
|
|
|
|
+ getMonitorPoint();
|
|
|
|
+ limitOpen.value = false;
|
|
|
|
+ });
|
|
|
|
+ })
|
|
|
|
+ .catch(() => {});
|
|
|
|
+};
|
|
|
|
+
|
|
|
|
+const getMonitorPoint = () => {
|
|
|
|
+ handleRequest(async () => {
|
|
|
|
+ if (props.monitoringId) {
|
|
|
|
+ const { humidityLower, humidityPreset, humidityUpper, tempLower, tempPreset, tempUpper, id, regionId } =
|
|
|
|
+ await getMonitorPointInfo(props.monitoringId);
|
|
|
|
+
|
|
|
|
+ Object.assign(limitForm.value, {
|
|
|
|
+ humidityLower,
|
|
|
|
+ humidityPreset,
|
|
|
|
+ humidityUpper,
|
|
|
|
+ tempLower,
|
|
|
|
+ tempPreset,
|
|
|
|
+ tempUpper,
|
|
|
|
+ id,
|
|
|
|
+ regionId,
|
|
|
|
+ });
|
|
|
|
+ }
|
|
|
|
+ });
|
|
|
|
+};
|
|
|
|
+// 初始化默认值
|
|
|
|
+initDateRange();
|
|
|
|
+
|
|
|
|
+const disabledDate = (current: Dayjs): boolean => {
|
|
|
|
+ return current ? current.isAfter(dayjs().endOf('day')) : false;
|
|
|
|
+};
|
|
|
|
+
|
|
|
|
+// 添加格式转换
|
|
|
|
+const formatDate = (date: Dayjs) => date.format('YYYY-MM-DD HH:mm:ss');
|
|
|
|
+
|
|
|
|
+// 处理日期快捷选择
|
|
|
|
+function updateDateRange(type: number) {
|
|
|
|
+ const today = dayjs().endOf('day');
|
|
|
|
+ let startDate: Dayjs;
|
|
|
|
+
|
|
|
|
+ switch (type) {
|
|
|
|
+ case 0: // 最近1天
|
|
|
|
+ startDate = today.subtract(1, 'day').startOf('day');
|
|
|
|
+ break;
|
|
|
|
+ case 1: // 最近7天
|
|
|
|
+ startDate = today.subtract(6, 'days').startOf('day');
|
|
|
|
+ break;
|
|
|
|
+ case 2: // 最近30天
|
|
|
|
+ startDate = today.subtract(29, 'days').startOf('day');
|
|
|
|
+ break;
|
|
|
|
+ default:
|
|
|
|
+ return;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ historicalDataValue.value = [startDate, today];
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+// 监听快捷选项变化
|
|
|
|
+watch(dateShortcut, (newVal) => {
|
|
|
|
+ if (newVal !== 3) {
|
|
|
|
+ updateDateRange(newVal);
|
|
|
|
+ getPointTimeSeriesList();
|
|
|
|
+ }
|
|
|
|
+});
|
|
|
|
+
|
|
|
|
+// 初始化日期范围
|
|
|
|
+function initDateRange() {
|
|
|
|
+ updateDateRange(dateShortcut.value);
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+// 处理自定义选择
|
|
|
|
+const handleShortcutChange = (value: SelectValue) => {
|
|
|
|
+ if (value !== 3) {
|
|
|
|
+ updateDateRange(value as number);
|
|
|
|
+ }
|
|
|
|
+};
|
|
|
|
+
|
|
|
|
+const getPointTimeSeriesList = () => {
|
|
|
|
+ handleRequest(async () => {
|
|
|
|
+ if (props.monitoringId) {
|
|
|
|
+ const data = historicalDataValue.value?.map(formatDate) || [];
|
|
|
|
+ curvedDataList.value = await getPointTimeSeries(
|
|
|
|
+ {
|
|
|
|
+ startTime: data[0],
|
|
|
|
+ endTime: data[1],
|
|
|
|
+ },
|
|
|
|
+ props.monitoringId,
|
|
|
|
+ );
|
|
|
|
+ }
|
|
|
|
+ });
|
|
|
|
+};
|
|
|
|
+
|
|
|
|
+const getCurvedData = (data: HistoricalDataSequence[], value: string) => {
|
|
|
|
+ const listNumber: number[] = [];
|
|
|
|
+ const listString: string[] = [];
|
|
|
|
+ if (data) {
|
|
|
|
+ data.forEach((item) => {
|
|
|
|
+ if (value === 'time') {
|
|
|
|
+ listString.push(item.time.slice(5));
|
|
|
|
+ }
|
|
|
|
+ if (value === 'value') {
|
|
|
|
+ listNumber.push(item.value);
|
|
|
|
+ }
|
|
|
|
+ });
|
|
|
|
+ }
|
|
|
|
+ if (value === 'time') {
|
|
|
|
+ return listString;
|
|
|
|
+ } else if (value === 'value') {
|
|
|
|
+ return listNumber;
|
|
|
|
+ } else {
|
|
|
|
+ return [];
|
|
|
|
+ }
|
|
|
|
+};
|
|
|
|
+
|
|
|
|
+const option = computed(() => {
|
|
|
|
+ if (!curvedDataList.value) return {};
|
|
|
|
+ return {
|
|
|
|
+ color: ['#1F8FFB', '#32BAC0'],
|
|
|
|
+ title: {
|
|
|
|
+ subtext: '单位: ' + (degreeValue.value === '1' ? '°C' : '%'),
|
|
|
|
+ padding: [0, 0, 0, 30], // 标题内边距
|
|
|
|
+ subtextStyle: {
|
|
|
|
+ color: '#999', // 设置副标题颜色
|
|
|
|
+ fontSize: 10, // 设置副标题字体大小
|
|
|
|
+ },
|
|
|
|
+ },
|
|
|
|
+ tooltip: {
|
|
|
|
+ trigger: 'axis',
|
|
|
|
+ formatter(params: FormatterData[]) {
|
|
|
|
+ let result = params[0].name + '<br/>';
|
|
|
|
+ params.forEach(function (item) {
|
|
|
|
+ // 创建一个圆形的 HTML 元素
|
|
|
|
+ const circle = `<span style="display: inline-block; width: 10px; height: 10px; border-radius: 50%; background-color: ${item.color}; margin-right: 5px;"></span>`;
|
|
|
|
+ result += circle + item.seriesName + ': ' + item.value + '°C<br/>';
|
|
|
|
+ });
|
|
|
|
+ return result;
|
|
|
|
+ },
|
|
|
|
+ backgroundColor: 'rgba(255, 255, 255, 0.75)',
|
|
|
|
+ },
|
|
|
|
+ legend: {
|
|
|
|
+ bottom: 0,
|
|
|
|
+ data: [
|
|
|
|
+ {
|
|
|
|
+ name:
|
|
|
|
+ '室内' +
|
|
|
|
+ (degreeValue.value === '1' ? '温度' : '湿度') +
|
|
|
|
+ '设定值:' +
|
|
|
|
+ (degreeValue.value === '1' ? curvedDataList.value.tempPreset : curvedDataList.value.humidityPreset) +
|
|
|
|
+ '℃',
|
|
|
|
+ icon: 'path://M128 501.333v64H0v-64zm213.333 0v64h-128v-64zm213.334 0v64h-128v-64zm234.666 0v64h-128v-64zm234.667 0v64H896v-64z',
|
|
|
|
+ itemStyle: {
|
|
|
|
+ color: '#67C23A',
|
|
|
|
+ },
|
|
|
|
+ selectedMode: false,
|
|
|
|
+ },
|
|
|
|
+ {
|
|
|
|
+ name:
|
|
|
|
+ '室内' +
|
|
|
|
+ (degreeValue.value === '1' ? '温度' : '湿度') +
|
|
|
|
+ '上限值:' +
|
|
|
|
+ (degreeValue.value === '1' ? curvedDataList.value.tempUpper : curvedDataList.value.humidityUpper) +
|
|
|
|
+ '℃',
|
|
|
|
+ icon: 'path://M128 501.333v64H0v-64zm213.333 0v64h-128v-64zm213.334 0v64h-128v-64zm234.666 0v64h-128v-64zm234.667 0v64H896v-64z',
|
|
|
|
+ itemStyle: {
|
|
|
|
+ color: '#F56C6C',
|
|
|
|
+ },
|
|
|
|
+ },
|
|
|
|
+ {
|
|
|
|
+ name: '室内' + (degreeValue.value === '1' ? '温度' : '湿度'),
|
|
|
|
+ itemStyle: {
|
|
|
|
+ color: '#1F8FFB',
|
|
|
|
+ },
|
|
|
|
+ },
|
|
|
|
+ {
|
|
|
|
+ name: '送风' + (degreeValue.value === '1' ? '温度' : '湿度'),
|
|
|
|
+ itemStyle: {
|
|
|
|
+ color: '#32BAC0',
|
|
|
|
+ },
|
|
|
|
+ },
|
|
|
|
+ ],
|
|
|
|
+ itemGap: 20, // 图例间距
|
|
|
|
+ // 文本样式配置
|
|
|
|
+ textStyle: {
|
|
|
|
+ fontWeight: 400,
|
|
|
|
+ fontSize: 12,
|
|
|
|
+ color: '#000000',
|
|
|
|
+ lineHeight: 22, // 需要配合 itemHeight 使用
|
|
|
|
+ },
|
|
|
|
+ },
|
|
|
|
+ xAxis: {
|
|
|
|
+ type: 'category',
|
|
|
|
+ data:
|
|
|
|
+ degreeValue.value === '1'
|
|
|
|
+ ? getCurvedData(curvedDataList.value.tempData, 'time')
|
|
|
|
+ : getCurvedData(curvedDataList.value.humidityData, 'time'),
|
|
|
|
+ axisLine: {
|
|
|
|
+ lineStyle: {
|
|
|
|
+ type: 'dashed', // 虚线样式
|
|
|
|
+ color: '#EBEBEB', // 轴线颜色
|
|
|
|
+ },
|
|
|
|
+ },
|
|
|
|
+ // X轴文本颜色配置
|
|
|
|
+ axisLabel: {
|
|
|
|
+ color: '#999', // 文本颜色
|
|
|
|
+ },
|
|
|
|
+ axisTick: {
|
|
|
|
+ show: false, // 隐藏刻度线
|
|
|
|
+ },
|
|
|
|
+ },
|
|
|
|
+ yAxis: {
|
|
|
|
+ type: 'value',
|
|
|
|
+ min: 0,
|
|
|
|
+ max: 40,
|
|
|
|
+ interval: 10,
|
|
|
|
+ axisLine: {
|
|
|
|
+ lineStyle: {
|
|
|
|
+ color: '#999999', // Y轴线颜色
|
|
|
|
+ },
|
|
|
|
+ },
|
|
|
|
+ splitLine: {
|
|
|
|
+ lineStyle: {
|
|
|
|
+ type: 'dashed', // 设置为虚线
|
|
|
|
+ color: '#EBEBEB', // 指定颜色
|
|
|
|
+ },
|
|
|
|
+ },
|
|
|
|
+ },
|
|
|
|
+ series: [
|
|
|
|
+ // 室内温度设定值虚线
|
|
|
|
+ {
|
|
|
|
+ name:
|
|
|
|
+ '室内' +
|
|
|
|
+ (degreeValue.value === '1' ? '温度' : '湿度') +
|
|
|
|
+ '设定值:' +
|
|
|
|
+ (degreeValue.value === '1' ? curvedDataList.value.tempPreset : curvedDataList.value.humidityPreset) +
|
|
|
|
+ '℃',
|
|
|
|
+ type: 'line',
|
|
|
|
+ silent: true, // 设置该系列不响应事件,图例不可点击
|
|
|
|
+ lineStyle: {
|
|
|
|
+ color: '#67C23A',
|
|
|
|
+ width: 0, // 隐藏实际线条
|
|
|
|
+ },
|
|
|
|
+ markLine: {
|
|
|
|
+ symbol: 'none',
|
|
|
|
+ lineStyle: {
|
|
|
|
+ type: 'dashed',
|
|
|
|
+ color: '#67C23A',
|
|
|
|
+ },
|
|
|
|
+ data: [{ yAxis: 24 }],
|
|
|
|
+ label: { show: false },
|
|
|
|
+ },
|
|
|
|
+ },
|
|
|
|
+ // 室内温度上限值虚线
|
|
|
|
+ {
|
|
|
|
+ name:
|
|
|
|
+ '室内' +
|
|
|
|
+ (degreeValue.value === '1' ? '温度' : '湿度') +
|
|
|
|
+ '上限值:' +
|
|
|
|
+ (degreeValue.value === '1' ? curvedDataList.value.tempUpper : curvedDataList.value.humidityUpper) +
|
|
|
|
+ '℃',
|
|
|
|
+ type: 'line',
|
|
|
|
+ silent: true, // 设置该系列不响应事件,图例不可点击
|
|
|
|
+ lineStyle: {
|
|
|
|
+ color: '#F56C6C',
|
|
|
|
+ width: 0,
|
|
|
|
+ },
|
|
|
|
+ markLine: {
|
|
|
|
+ symbol: 'none',
|
|
|
|
+ lineStyle: {
|
|
|
|
+ type: 'dashed',
|
|
|
|
+ color: '#F56C6C',
|
|
|
|
+ },
|
|
|
|
+ data: [{ yAxis: 26 }],
|
|
|
|
+ label: { show: false },
|
|
|
|
+ },
|
|
|
|
+ },
|
|
|
|
+ // 室内温度折线
|
|
|
|
+ {
|
|
|
|
+ name: '室内' + (degreeValue.value === '1' ? '温度' : '湿度'),
|
|
|
|
+ type: 'line',
|
|
|
|
+ data:
|
|
|
|
+ degreeValue.value === '1'
|
|
|
|
+ ? getCurvedData(curvedDataList.value.tempData, 'value')
|
|
|
|
+ : getCurvedData(curvedDataList.value.humidityData, 'value'),
|
|
|
|
+ smooth: true,
|
|
|
|
+ lineStyle: {
|
|
|
|
+ color: '#1F8FFB',
|
|
|
|
+ },
|
|
|
|
+ showSymbol: true, // 启用数据点
|
|
|
|
+ symbolSize: 0, // 默认隐藏数据点(大小为0)
|
|
|
|
+ // 关键修复:emphasis 配置需独立声明
|
|
|
|
+ emphasis: {
|
|
|
|
+ symbolSize: 20, // 悬停时显示
|
|
|
|
+ itemStyle: {
|
|
|
|
+ color: '#fff',
|
|
|
|
+ borderColor: '#1F8FFB',
|
|
|
|
+ borderWidth: 8,
|
|
|
|
+ },
|
|
|
|
+ },
|
|
|
|
+ },
|
|
|
|
+ // 送风温度折线
|
|
|
|
+ {
|
|
|
|
+ name: '送风' + (degreeValue.value === '1' ? '温度' : '湿度'),
|
|
|
|
+ type: 'line',
|
|
|
|
+ data:
|
|
|
|
+ degreeValue.value === '1'
|
|
|
|
+ ? getCurvedData(curvedDataList.value.supplyTempData, 'value')
|
|
|
|
+ : getCurvedData(curvedDataList.value.supplyHumidityData, 'value'),
|
|
|
|
+ smooth: true,
|
|
|
|
+ lineStyle: {
|
|
|
|
+ color: '#32BAC0',
|
|
|
|
+ },
|
|
|
|
+ showSymbol: true, // 启用数据点
|
|
|
|
+ symbolSize: 0, // 默认隐藏数据点(大小为0)
|
|
|
|
+ // 关键修复:emphasis 配置需独立声明
|
|
|
|
+ emphasis: {
|
|
|
|
+ symbolSize: 20, // 悬停时显示
|
|
|
|
+ itemStyle: {
|
|
|
|
+ color: '#fff',
|
|
|
|
+ borderColor: '#32BAC0',
|
|
|
|
+ borderWidth: 8,
|
|
|
|
+ },
|
|
|
|
+ },
|
|
|
|
+ },
|
|
|
|
+ ],
|
|
|
|
+ grid: {
|
|
|
|
+ top: 30, // 上边距为0
|
|
|
|
+ right: 30, // 右边距为0
|
|
|
|
+ // bottom: 30, // 下边距为0
|
|
|
|
+ left: 30, // 左边距为0
|
|
|
|
+ },
|
|
|
|
+ };
|
|
|
|
+});
|
|
|
|
+
|
|
|
|
+watch(
|
|
|
|
+ () => props.monitoringId,
|
|
|
|
+ (count) => {
|
|
|
|
+ if (count) {
|
|
|
|
+ getMonitorPoint();
|
|
|
|
+ getMonitorPointList();
|
|
|
|
+ getPointTimeSeriesList();
|
|
|
|
+ }
|
|
|
|
+ },
|
|
|
|
+);
|
|
|
|
+
|
|
|
|
+onMounted(() => {
|
|
|
|
+ getMonitorPoint();
|
|
|
|
+ getPointTimeSeriesList();
|
|
|
|
+});
|
|
|
|
+</script>
|
|
|
|
+
|
|
|
|
+<template>
|
|
|
|
+ <div>
|
|
|
|
+ <AFlex justify="space-between" align="center">
|
|
|
|
+ <div style="width: 160px">
|
|
|
|
+ <ASegmented v-model:value="degreeValue" :options="degreeData" :block="true">
|
|
|
|
+ <template #label="{ payload }">
|
|
|
|
+ <SvgIcon :name="payload.icon" />
|
|
|
|
+ {{ payload.label }}
|
|
|
|
+ </template>
|
|
|
|
+ </ASegmented>
|
|
|
|
+ </div>
|
|
|
|
+ <div>
|
|
|
|
+ <AButton type="text" class="icon-button icon-style" @click="addLimit">
|
|
|
|
+ <SvgIcon name="setting" />
|
|
|
|
+ 设置温湿度限值
|
|
|
|
+ </AButton>
|
|
|
|
+ <AButton type="text" class="icon-button icon-style" @click="addWarning">
|
|
|
|
+ <SvgIcon name="setting" />
|
|
|
|
+ 设置温湿度预警
|
|
|
|
+ </AButton>
|
|
|
|
+ </div>
|
|
|
|
+ </AFlex>
|
|
|
|
+ <AFlex class="date-selection-div">
|
|
|
|
+ <ASelect v-model:value="dateShortcut" placeholder="请选择" class="date-shortcut" @change="handleShortcutChange">
|
|
|
|
+ <ASelectOption :value="0">最近1天</ASelectOption>
|
|
|
|
+ <ASelectOption :value="1">最近7天</ASelectOption>
|
|
|
|
+ <ASelectOption :value="2">最近30天</ASelectOption>
|
|
|
|
+ <ASelectOption :value="3">自定义</ASelectOption>
|
|
|
|
+ </ASelect>
|
|
|
|
+ <ARangePicker v-model:value="historicalDataValue" :disabled-date="disabledDate" :allow-clear="false" />
|
|
|
|
+ </AFlex>
|
|
|
|
+
|
|
|
|
+ <VChart class="chart" :option="option" />
|
|
|
|
+
|
|
|
|
+ <AModal
|
|
|
|
+ v-model:open="limitOpen"
|
|
|
|
+ title="设置温湿度限值"
|
|
|
|
+ :footer="null"
|
|
|
|
+ width="704px"
|
|
|
|
+ :mask-closable="false"
|
|
|
|
+ :keyboard="false"
|
|
|
|
+ >
|
|
|
|
+ <AForm ref="formRef" :model="limitForm" layout="vertical" :rules="rules">
|
|
|
|
+ <AFormItem label="请选择监控点名" name="id">
|
|
|
|
+ <ASelect
|
|
|
|
+ class="select-width"
|
|
|
|
+ v-model:value="limitForm.id"
|
|
|
|
+ :options="monitoringData"
|
|
|
|
+ :field-names="{ label: 'name', value: 'id' }"
|
|
|
|
+ placeholder="请选择"
|
|
|
|
+ />
|
|
|
|
+ </AFormItem>
|
|
|
|
+ <AFlex justify="space-between">
|
|
|
|
+ <AFormItem label="室内温度设定值" name="tempPreset">
|
|
|
|
+ <AInputNumber class="select-width" v-model:value="limitForm.tempPreset" :min="0" :max="9999" />
|
|
|
|
+ </AFormItem>
|
|
|
|
+ <AFormItem label="室内温度上限值" name="tempUpper">
|
|
|
|
+ <AInputNumber class="select-width" v-model:value="limitForm.tempUpper" :min="0" :max="9999" />
|
|
|
|
+ </AFormItem>
|
|
|
|
+ <AFormItem label="室内温度下限值" name="tempLower">
|
|
|
|
+ <AInputNumber class="select-width" v-model:value="limitForm.tempLower" :min="0" :max="9999" />
|
|
|
|
+ </AFormItem>
|
|
|
|
+ </AFlex>
|
|
|
|
+ <AFlex justify="space-between">
|
|
|
|
+ <AFormItem label="室内湿度设定值" name="humidityPreset">
|
|
|
|
+ <AInputNumber class="select-width" v-model:value="limitForm.humidityPreset" :min="0" :max="9999" />
|
|
|
|
+ </AFormItem>
|
|
|
|
+ <AFormItem label="室内湿度上限值" name="humidityUpper">
|
|
|
|
+ <AInputNumber class="select-width" v-model:value="limitForm.humidityUpper" :min="0" :max="9999" />
|
|
|
|
+ </AFormItem>
|
|
|
|
+ <AFormItem label="室内湿度下限值" name="humidityLower">
|
|
|
|
+ <AInputNumber class="select-width" v-model:value="limitForm.humidityLower" :min="0" :max="9999" />
|
|
|
|
+ </AFormItem>
|
|
|
|
+ </AFlex>
|
|
|
|
+ <ACheckbox v-model:checked="limitForm.batch">批量设置</ACheckbox>
|
|
|
|
+ </AForm>
|
|
|
|
+ <AFlex justify="flex-end" class="limit-top">
|
|
|
|
+ <AButton class="limit-button default-button" @click="limitOpen = false">{{ $t('common.cancel') }}</AButton>
|
|
|
|
+ <AButton type="primary" @click="getLimit">{{ $t('common.confirm') }}</AButton>
|
|
|
|
+ </AFlex>
|
|
|
|
+ </AModal>
|
|
|
|
+
|
|
|
|
+ <AModal v-model:open="warningOpen" :footer="null" width="704px" :mask-closable="false" :keyboard="false">
|
|
|
|
+ <template #title>
|
|
|
|
+ <div class="warning-title">设置温湿度预警</div>
|
|
|
|
+ </template>
|
|
|
|
+
|
|
|
|
+ <div v-for="item in warningList" :key="item.id" class="warning-div warning-color">
|
|
|
|
+ <ACheckbox v-model:checked="item.enabled" class="warning-color">{{
|
|
|
|
+ item.type === 1 ? '启用温度预警' : '启用湿度预警'
|
|
|
|
+ }}</ACheckbox>
|
|
|
|
+ <AFlex align="center" class="warning-flex">
|
|
|
|
+ <div>{{ item.type === 1 ? '室内温度大于' : '室内湿度大于' }}</div>
|
|
|
|
+
|
|
|
|
+ <AInputNumber class="input-width" v-model:value="item.val" :min="1" :max="9999" />
|
|
|
|
+ <div>{{ item.type === 1 ? '℃' : '%' }}</div>
|
|
|
|
+ <div>,且持续时间大于</div>
|
|
|
|
+ <AInputNumber class="input-width" v-model:value="item.duration" :min="1" :max="9999" />
|
|
|
|
+ <div>min时,触发</div>
|
|
|
|
+ </AFlex>
|
|
|
|
+ </div>
|
|
|
|
+
|
|
|
|
+ <AFlex justify="flex-end" class="limit-top">
|
|
|
|
+ <AButton class="default-button limit-button" @click="warningOpen = false">{{ $t('common.cancel') }}</AButton>
|
|
|
|
+ <AButton type="primary" @click="getWarning">{{ $t('common.confirm') }}</AButton>
|
|
|
|
+ </AFlex>
|
|
|
|
+ </AModal>
|
|
|
|
+ </div>
|
|
|
|
+</template>
|
|
|
|
+
|
|
|
|
+<style lang="scss" scoped>
|
|
|
|
+.chart {
|
|
|
|
+ width: 560px;
|
|
|
|
+ height: 700px;
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+.date-selection-div {
|
|
|
|
+ margin-top: 16px;
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+.date-shortcut {
|
|
|
|
+ width: 96px;
|
|
|
|
+ margin-right: 12px;
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+.warning-div {
|
|
|
|
+ padding-top: 8px;
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+.warning-color {
|
|
|
|
+ font-size: 14px;
|
|
|
|
+ font-style: normal;
|
|
|
|
+ font-weight: 400;
|
|
|
|
+ line-height: 22px;
|
|
|
|
+ color: rgb(0 0 0 / 65%);
|
|
|
|
+ text-align: left;
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+.check-box {
|
|
|
|
+ margin-top: 8px;
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+.warning-title {
|
|
|
|
+ margin-bottom: 16px;
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+.warning-flex {
|
|
|
|
+ margin: 16px 0 32px;
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+.input-width {
|
|
|
|
+ width: 120px;
|
|
|
|
+ margin: 0 12px;
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+.limit-top {
|
|
|
|
+ margin-top: 24px;
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+.limit-button {
|
|
|
|
+ margin: 0 16px;
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+.select-width {
|
|
|
|
+ width: 192px;
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+.icon-style {
|
|
|
|
+ color: var(--antd-color-primary);
|
|
|
|
+}
|
|
|
|
+</style>
|