Browse Source

feat: 增加运行状态中属性的图形展示

yanglzh 7 months ago
parent
commit
7d4db1b093

+ 179 - 0
src/views/iot/device/instance/component/chart.vue

@@ -0,0 +1,179 @@
+<template>
+	<div class="system-edit-dic-container">
+		<el-dialog v-model="isShowDialog" title="数据记录" width="850px">
+			<!-- 这里是 echarts 线图 -->
+			<div id="lineChart" ref="chartRef" class="chart-container"></div>
+		</el-dialog>
+	</div>
+</template>
+
+<script lang="ts" setup>
+import { ref, onMounted, watch, nextTick } from 'vue'
+import api from '/@/api/device'
+import * as echarts from 'echarts'
+
+const loading = ref(false)
+const isShowDialog = ref(false)
+const chartRef = ref<HTMLElement | null>(null)
+let chartInstance: echarts.ECharts | null = null
+const lineData = ref([])
+
+// 初始化图表
+const initChart = () => {
+	if (chartRef.value) {
+		// 如果已有实例先销毁
+		if (chartInstance) {
+			chartInstance.dispose()
+		}
+		// 创建图表实例
+		chartInstance = echarts.init(chartRef.value)
+		// 设置加载状态
+		if (loading.value) {
+			chartInstance.showLoading()
+		} else {
+			chartInstance.hideLoading()
+		}
+		// 更新图表
+		updateChart()
+	}
+}
+
+// 更新图表数据
+const updateChart = () => {
+	if (!chartInstance) return
+
+	// 从 lineData 中提取数据
+	const xData = lineData.value.map((item: any) => item.ts?.slice(5)).reverse()
+	const yData = lineData.value.map((item: any) => item.value).reverse()
+
+	// 配置图表选项
+	const option = {
+		tooltip: {
+			trigger: 'axis',
+			formatter: '{b}<br />{a}: {c}',
+		},
+		grid: {
+			top: 15,
+			left: 40,
+			right: 30,
+			bottom: 50, // 增加底部空间,为 dataZoom 留出位置
+			containLabel: true,
+		},
+		dataZoom: [
+			{
+				type: 'slider', // 滑动条型数据区域缩放组件
+				show: true,
+				start: 0,
+				end: 100,
+				height: 20,
+				bottom: 10,
+				borderColor: 'transparent',
+				backgroundColor: '#f5f5f5',
+				fillerColor: 'rgba(167, 183, 204, 0.4)',
+				handleIcon:
+					'M10.7,11.9v-1.3H9.3v1.3c-4.9,0.3-8.8,4.4-8.8,9.4c0,5,3.9,9.1,8.8,9.4v1.3h1.3v-1.3c4.9-0.3,8.8-4.4,8.8-9.4C19.5,16.3,15.6,12.2,10.7,11.9z M13.3,24.4H6.7V23h6.6V24.4z M13.3,19.6H6.7v-1.4h6.6V19.6z',
+				handleSize: '80%',
+				handleStyle: {
+					color: '#fff',
+					shadowBlur: 3,
+					shadowColor: 'rgba(0, 0, 0, 0.6)',
+					shadowOffsetX: 2,
+					shadowOffsetY: 2,
+				},
+			},
+			{
+				type: 'inside', // 内置型数据区域缩放组件,允许鼠标滚轮或触摸板缩放
+				start: 0,
+				end: 100,
+			},
+		],
+		xAxis: {
+			type: 'category',
+			data: xData,
+			axisLabel: {
+				rotate: 0,
+			},
+		},
+		yAxis: {
+			type: 'value',
+		},
+		series: [
+			{
+				name: '数值',
+				type: 'line',
+				data: yData,
+				smooth: true,
+				lineStyle: {
+					width: 2,
+				},
+				symbolSize: 6,
+			},
+		],
+	}
+
+	// 设置图表配置
+	chartInstance.setOption(option)
+}
+
+// 监听数据变化,更新图表
+watch(
+	() => lineData.value,
+	() => {
+		nextTick(() => {
+			updateChart()
+		})
+	},
+	{ deep: true }
+)
+
+// 监听 loading 状态变化
+watch(
+	() => loading.value,
+	(newVal) => {
+		if (chartInstance) {
+			if (newVal) {
+				chartInstance.showLoading()
+			} else {
+				chartInstance.hideLoading()
+			}
+		}
+	}
+)
+
+// 监听弹窗显示状态
+watch(
+	() => isShowDialog.value,
+	(val) => {
+		if (val) {
+			nextTick(() => {
+				initChart()
+			})
+		}
+	}
+)
+
+// 打开弹窗
+const openDialog = (row: any, deviceKey: string) => {
+	isShowDialog.value = true
+	loading.value = true
+	api.instance
+		.getLogDetail({
+			deviceKey: deviceKey,
+			propertyKey: row.key,
+			pageSize: 100,
+		})
+		.then((res: any) => {
+			lineData.value = res.List
+			console.log(res.List)
+		})
+		.finally(() => (loading.value = false))
+}
+
+defineExpose({ openDialog })
+</script>
+<style scoped>
+.chart-container {
+	width: 100%;
+	height: 400px;
+}
+</style>

+ 49 - 36
src/views/iot/device/instance/component/list.vue

@@ -6,35 +6,47 @@
 					<h4 :id="titleId" :class="titleClass">数据记录</h4>
 
 					<div>
-						<i class="iconfont " :class="!dialogFullScreen ? 'icon-fullscreen' : 'icon-tuichuquanping'" @click="quanping" style="font-size: 22px;cursor: pointer;"></i>
-						<i class="el-icon" @click="close" style="font-size: 22px;cursor: pointer;    margin-left: 10px; position: relative; top: 3px;"><svg viewBox="0 0 1024 1024" xmlns="http://www.w3.org/2000/svg" data-v-029747aa="">
-								<path fill="currentColor"
-									d="M764.288 214.592 512 466.88 259.712 214.592a31.936 31.936 0 0 0-45.12 45.12L466.752 512 214.528 764.224a31.936 31.936 0 1 0 45.12 45.184L512 557.184l252.288 252.288a31.936 31.936 0 0 0 45.12-45.12L557.12 512.064l252.288-252.352a31.936 31.936 0 1 0-45.12-45.184z">
-								</path>
-							</svg></i>
-
+						<!-- <i
+							class="iconfont"
+							:class="!dialogFullScreen ? 'icon-fullscreen' : 'icon-tuichuquanping'"
+							@click="quanping"
+							style="font-size: 22px; cursor: pointer"
+						></i> -->
+						<i class="el-icon" @click="close" style="font-size: 22px; cursor: pointer; margin-left: 10px; position: relative; top: 3px"
+							><svg viewBox="0 0 1024 1024" xmlns="http://www.w3.org/2000/svg" data-v-029747aa="">
+								<path
+									fill="currentColor"
+									d="M764.288 214.592 512 466.88 259.712 214.592a31.936 31.936 0 0 0-45.12 45.12L466.752 512 214.528 764.224a31.936 31.936 0 1 0 45.12 45.184L512 557.184l252.288 252.288a31.936 31.936 0 0 0 45.12-45.12L557.12 512.064l252.288-252.352a31.936 31.936 0 1 0-45.12-45.184z"
+								></path></svg
+						></i>
 					</div>
 				</div>
 			</template>
 
-			<el-table :data="tableData.data" style="width: 100%" v-loading="tableData.loading">
+			<el-table :data="tableData.data" size="small" style="width: 100%" v-loading="tableData.loading" max-height="50vh">
 				<el-table-column label="时间" prop="ts" align="center" width="180" />
 				<el-table-column label="属性值" prop="value" align="center" show-overflow-tooltip />
 			</el-table>
-			<pagination v-show="tableData.total > 0" :total="tableData.total" v-model:page="tableData.param.pageNum" v-model:limit="tableData.param.pageSize" @pagination="typeList" />
+			<pagination
+				v-show="tableData.total > 0"
+				:total="tableData.total"
+				v-model:page="tableData.param.pageNum"
+				v-model:limit="tableData.param.pageSize"
+				@pagination="typeList"
+			/>
 		</el-dialog>
 	</div>
 </template>
 
 <script lang="ts">
-import { reactive, toRefs, defineComponent, ref } from 'vue';
-import { Close } from '@element-plus/icons-vue';
-import api from '/@/api/device';
+import { reactive, toRefs, defineComponent, ref } from 'vue'
+import { Close } from '@element-plus/icons-vue'
+import api from '/@/api/device'
 
 export default defineComponent({
 	name: 'deviceEditPro',
 	setup(prop, { emit }) {
-		const formRef = ref<HTMLElement | null>(null);
+		const formRef = ref<HTMLElement | null>(null)
 		const state = reactive({
 			isShowDialog: false,
 			dialogFullScreen: false,
@@ -49,28 +61,29 @@ export default defineComponent({
 					propertyKey: '',
 				},
 			},
-		});
+		})
 		// 打开弹窗
 		const openDialog = (row: any, deviceKey: string) => {
-			resetForm();
+			resetForm()
 			if (row) {
-				state.tableData.param.deviceKey = deviceKey;
+				state.tableData.param.deviceKey = deviceKey
 				state.tableData.param.propertyKey = row.key
-				typeList();
-
+				typeList()
 			}
-			state.isShowDialog = true;
-		};
+			state.isShowDialog = true
+		}
 
 		const typeList = () => {
-			state.tableData.loading = true;
-			api.instance.getLogDetail(state.tableData.param).then((res: any) => {
-				state.tableData.data = res.List;
-				state.tableData.total = res.Total;
-				//state.ruleForm = res.data.dictType
-			}).finally(() => (state.tableData.loading = false));
-
-		};
+			state.tableData.loading = true
+			api.instance
+				.getLogDetail(state.tableData.param)
+				.then((res: any) => {
+					state.tableData.data = res.List
+					state.tableData.total = res.Total
+					//state.ruleForm = res.data.dictType
+				})
+				.finally(() => (state.tableData.loading = false))
+		}
 		const resetForm = () => {
 			state.tableData = {
 				data: [],
@@ -83,18 +96,18 @@ export default defineComponent({
 					propertyKey: '',
 				},
 			}
-		};
+		}
 		// 关闭弹窗
 		const closeDialog = () => {
-			state.isShowDialog = false;
-		};
+			state.isShowDialog = false
+		}
 		const quanping = () => {
-			state.dialogFullScreen = state.dialogFullScreen ? false : true;
+			state.dialogFullScreen = state.dialogFullScreen ? false : true
 		}
 		// 取消
 		const onCancel = () => {
-			closeDialog();
-		};
+			closeDialog()
+		}
 
 		return {
 			Close,
@@ -105,9 +118,9 @@ export default defineComponent({
 			onCancel,
 			formRef,
 			...toRefs(state),
-		};
+		}
 	},
-});
+})
 </script>
 <style scoped>
 .my-header {

+ 14 - 1
src/views/iot/device/instance/detail.vue

@@ -45,6 +45,9 @@
                     <el-icon style="font-size: 18px;margin-left: 10px;" @click="onOpenListDetail(item)">
                       <ele-Expand />
                     </el-icon>
+                    <el-icon style="font-size: 18px;margin-left: 10px;" @click="onOpenChartDetail(item)">
+                      <ele-DataLine />
+                    </el-icon>
                   </div>
                 </div>
 
@@ -376,6 +379,7 @@
     <EditEvent ref="editEventRef" @typeList="getevent" />
     <EditTab ref="editTabRef" @typeList="gettab" />
     <ListDic ref="listDicRef" />
+    <ChartDic ref="chartDicRef" />
     <SubDevice ref="subDeviceRef" />
     <setAttr :device-key="detail.key" ref="setAttrRef" />
     <!-- 子设备-批量绑定弹窗 -->
@@ -406,6 +410,7 @@ import EditEvent from '../product/component/editEvent.vue';
 import EditTab from '../product/component/editTab.vue';
 import devantd from '/@/components/devantd/index.vue';
 import ListDic from './component/list.vue';
+import ChartDic from './component/chart.vue';
 import SubDevice from './component/subDevice.vue';
 import setAttr from './component/setAttr.vue';
 import SubDeviceMutipleBind from './component/subDeviceMutipleBind.vue';
@@ -455,7 +460,7 @@ interface TableDataState {
 }
 export default defineComponent({
   name: 'deviceEditPro',
-  components: { EditAssetRef, FromData, SubDeviceMutipleBind, SubDevice, EditDic, EditAttr, EditFun, EditEvent, EditTab, devantd, ListDic, functionCom, setAttr },
+  components: { EditAssetRef, FromData, SubDeviceMutipleBind, SubDevice, EditDic, EditAttr, EditFun, EditEvent, EditTab, devantd, ListDic, functionCom, setAttr, ChartDic },
 
   props: {
     deviceKey: String
@@ -481,6 +486,7 @@ export default defineComponent({
     const editAttrRef = ref();
     const editFunRef = ref();
     const listDicRef = ref();
+    const chartDicRef = ref();
     const editEventRef = ref();
     const editTabRef = ref();
     const subDeviceRef = ref();
@@ -760,6 +766,11 @@ export default defineComponent({
       listDicRef.value.openDialog(row, state.detail.key);
     };
 
+    //查看日志图形
+    const onOpenChartDetail = (row: any) => {
+      chartDicRef.value.openDialog(row, state.detail.key);
+    };
+
     // 打开修改产品弹窗
     const onOpenEditDic = (row: any) => {
       editDicRef.value.openDialog(row);
@@ -1041,6 +1052,7 @@ export default defineComponent({
       editDicRef,
       editAttrRef,
       listDicRef,
+      chartDicRef,
       editFunRef,
       editEventRef,
       editTabRef,
@@ -1051,6 +1063,7 @@ export default defineComponent({
       dataList,
       deviceAssetMetadata,
       deviceAssetData,
+      onOpenChartDetail,
       onOpenListDetail,
       getrunData,
       getlog,

+ 1 - 1
src/views/system/city/index.vue

@@ -34,7 +34,7 @@
         <el-table-column label="城市编号" v-col="'code'" prop="code" show-overflow-tooltip />
         <el-table-column label="状态" v-col="'status'" prop="status" width="80">
           <template #default="scope">
-            {{ scope.row.status === 1 ? '在线' : '不在线' }}
+            <el-tag :type="scope.row.status === 1 ? 'success' : 'danger'">{{ scope.row.status === 1 ? '启用' : '禁用' }}</el-tag>
           </template>
         </el-table-column>
         <el-table-column label="排序" v-col="'sort'" prop="sort" align="center" />