wenhongquan пре 1 година
родитељ
комит
5f9e05f0f2

+ 5 - 0
ruoyi-ui-vue3/src/router/index.js

@@ -57,6 +57,11 @@ export const constantRoutes = [
     component: () => import("@/views/error/401"),
     hidden: true,
   },
+  {
+    path: "/device/data",
+    component: () => import("@/views/device/sensordash/device.vue"),
+    hidden: true,
+  },
   // {
   //   path: "",
   //   component: Layout,

+ 811 - 0
ruoyi-ui-vue3/src/views/device/sensordash/device.vue

@@ -0,0 +1,811 @@
+<template>
+  <div style="padding: 10px 15px">
+    <el-row>
+      <el-col :span="6" style="padding-right: 10px">
+        <el-card class="box-card">
+          <template #header>
+            <div class="card-header">
+              <span>设备区域</span>
+              <div style="display: none">
+                <el-button type="primary">
+                  添加
+                </el-button>
+              </div>
+            </div>
+          </template>
+          <div class="tree">
+            <LayTree :data="data" :tail-node-icon="false" :onlyIconControl="true" v-model:selectedKey="selectedKey"
+                     @node-click="handleClick">
+              <template #title="{ data }">
+                <div style="display: flex;align-items: center;">
+                  <el-icon v-if="(data.id + '').indexOf('device') != -1">
+                    <Cpu />
+                  </el-icon> {{ data.name }}</div>
+              </template>
+            </LayTree>
+          </div>
+        </el-card>
+      </el-col>
+
+      <el-col :span="18">
+        <el-card class="box-card">
+          <div style="display: flex; flex-direction: row; justify-content: space-between">
+            <div style="display: flex; flex-direction: row;">
+              <div style="
+                  display: flex;
+                  flex-direction: row;
+                  flex-wrap: nowrap;
+                  align-items: center;
+                ">
+                <div style="font-size: 12px; width: 100px;">传感器编号:</div>
+                <el-input v-model="searchform.id" placeholder="请输入"></el-input>
+              </div>
+              <div style="
+                  display: flex;
+                  flex-direction: row;
+                  flex-wrap: nowrap;
+                  align-items: center;
+                ">
+                <div style="font-size: 12px; width: 80px;margin-right: 10px;text-align: right;">描述:</div>
+                <el-input v-model="searchform.desc" placeholder="请输入"></el-input>
+              </div>
+            </div>
+            <div>
+              <el-button type="primary" plain @click="initdata">重置</el-button>
+              <el-button type="primary" @click="getalldata">搜索</el-button>
+            </div>
+          </div>
+        </el-card>
+
+        <el-card class="box-card" style="margin-top: 10px;">
+          <template #header>
+            <div class="card-header">
+              <span>传感器台账</span>
+              <div style="display: none">
+                <el-button type="primary" @click="handleImport">导入</el-button>
+                <el-button type="primary" @click="goadd">添加</el-button>
+                <el-button type="danger" @click="deleteall">批量删除</el-button>
+                <el-button type="primary" plain @click="ziduanshow = true">显示字段</el-button>
+              </div>
+            </div>
+          </template>
+
+          <el-row>
+            <el-col :span="24" style="padding-left: 10px">
+              <el-table :data="devicetabledata" ref="tableref" :border="true">
+                <el-table-column type="selection" width="55" />
+                <el-table-column v-for="item in cloumdata.filter(i => i.visible)" :prop="item.prop"
+                                 :label="item.label"></el-table-column>
+                <el-table-column label="协议">
+                  <template #default="scope">
+                    <div>
+                      {{ getProtocalTypeName(scope.row) }}
+                    </div>
+                  </template>
+                </el-table-column>
+                <el-table-column label="操作">
+                  <template #default="scope">
+                    <div>
+                      <el-button link @click="lookdata(scope.row)">
+                        <el-tooltip effect="dark" content="查看数据">
+                          <el-icon>
+                            <PieChart />
+                          </el-icon>
+                        </el-tooltip>
+                      </el-button>
+
+                      <el-button link @click="goedit(scope.row)" style="display:none">
+                        <el-tooltip effect="dark" content="编辑">
+                          <el-icon>
+                            <Edit />
+                          </el-icon>
+                        </el-tooltip>
+                      </el-button>
+                      <el-button link @click="cdbd(scope.row)" style="display:none">
+                        <el-tooltip effect="dark" content="测点绑定">
+                          <el-icon>
+                            <VideoPlay />
+                          </el-icon>
+                        </el-tooltip>
+                      </el-button>
+                      <el-popconfirm title="确定删除该传感器?" @confirm="deleterow(scope.row)" >
+                        <template #reference>
+                          <el-button link style="display:none"><el-tooltip effect="dark" content="删除"><el-icon>
+                            <Delete  />
+                          </el-icon></el-tooltip></el-button>
+                        </template>
+                      </el-popconfirm>
+                    </div>
+                  </template>
+                </el-table-column>
+              </el-table>
+
+
+            </el-col>
+            <el-col :span="24" style="margin-top: 10px;">
+              <el-pagination style="float: right;" small background layout="prev, pager, next" :total="pagedata.total"
+                             :page-size="pagedata.pageSize" :current-page="pagedata.pageNum" @current-change="onchangepage"
+                             class="mt-4" />
+            </el-col>
+          </el-row>
+        </el-card>
+      </el-col>
+    </el-row>
+
+
+    <el-dialog title="测点配置" v-model="cdbdshow">
+
+      <div style="display: flex; flex-direction: row; justify-content: space-between">
+        <div style="display: flex; flex-direction: row;">
+
+
+
+        </div>
+        <div>
+          <el-button type="primary" plain @click="addrow">新增</el-button>
+        </div>
+      </div>
+
+      <el-table :data="pointdata" style="margin-top: 15px" height="400px">
+        <el-table-column label="变量名" prop="name">
+          <template #default="scope">
+            <el-input v-model="scope.row.name">
+            </el-input>
+          </template>
+        </el-table-column>
+        <el-table-column label="名称" prop="label">
+          <template #default="scope">
+            <el-input v-model="scope.row.label">
+            </el-input>
+          </template>
+        </el-table-column>
+        <el-table-column label="单位" prop="unitId">
+          <template #default="scope">
+            <el-popover placement="right" :width="400" trigger="click">
+              <template #reference>
+                <el-button plain>{{ `${scope.row.unit}(${scope.row.unitType})` }}</el-button>
+              </template>
+              <el-table :data="allUnit" height="500"
+                        @cellClick="(row1) => { scope.row.unit = row1.unitName; scope.row.unitType = row1.unitSymbol; }">
+                <el-table-column property="unitName" label="单位名称" />
+                <el-table-column property="unitSymbol" label="单位符号" />
+                <el-table-column property="unitType" label="分类" />
+              </el-table>
+            </el-popover>
+
+          </template>
+        </el-table-column>
+        <el-table-column label="描述" prop="desc">
+          <template #default="scope">
+            <el-input v-model="scope.row.desc">
+            </el-input>
+          </template>
+        </el-table-column>
+        <el-table-column label="点表配置" prop="dataPointId">
+          <template #default="scope">
+            <span v-if="currentsensor.protocalType == 3">无需配置</span>
+            <div v-if="currentsensor.protocalType != 3">
+              <el-popover placement="right" :width="400" trigger="click">
+                <template #reference>
+                  <el-button plain>{{ `${(currentdatapointlist.find(i => (i.id + "") == scope.row.dataPointId) ??
+                    { name: '失效' }).name}` }}</el-button>
+                </template>
+                <el-table :data="currentdatapointlist" @cellClick="(row1) => { scope.row.dataPointId = row1.id; }">
+                  <el-table-column property="name" label="点位名称" />
+                  <el-table-column property="valueType" label="变量类型" />
+                  <el-table-column property="addr" label="地址" />
+                </el-table>
+              </el-popover>
+            </div>
+
+
+          </template>
+        </el-table-column>
+        <el-table-column label="操作">
+          <template #default="scope">
+            <el-popconfirm title="确定删除该测点?" @confirm="deldatapoint(scope.$index)">
+              <template #reference>
+                <el-button link><el-tooltip effect="dark" content="删除"><el-icon>
+                  <Delete />
+                </el-icon></el-tooltip></el-button>
+              </template>
+            </el-popconfirm>
+          </template>
+        </el-table-column>
+      </el-table>
+
+
+      <template #footer>
+        <div class="dialog-footer">
+          <el-button @click="cdbdshow = false;"> 取消 </el-button>
+          <el-button type="primary" @click="dosave"> 保存 </el-button>
+        </div>
+      </template>
+    </el-dialog>
+
+    <el-dialog title="字段显示" v-model="ziduanshow" width="50%" @close="ziduanshow = false">
+      <div style="display: flex;flex-wrap: wrap;align-content: center;justify-content: center;align-items: center;">
+        <el-checkbox v-for="item in cloumdata" v-model="item.visible" :label="item.label" size="large" />
+      </div>
+
+    </el-dialog>
+
+    <el-dialog title="测点数据查看" v-model="datashow" width="50%" @close="datashow = false">
+      <div>
+        <!--        显示测点实时数据-->
+        <el-table @cellClick="doshowhistory" :data="JSON.parse(currentsensor.datapoints)" style="margin-top: 15px"
+                  height="250px">
+          <el-table-column label="变量名" prop="name">
+            <template #default="scope">
+              <span>{{ scope.row.name }}</span>
+            </template>
+          </el-table-column>
+          <el-table-column label="名称" prop="name">
+            <template #default="scope">
+              <span>{{ scope.row.label }}</span>
+            </template>
+          </el-table-column>
+          <el-table-column label="数值" prop="name">
+            <template #default="scope">
+                 <span>{{showdata(scope.row.name,scope.row.unitType)
+
+                   }}</span>
+            </template>
+          </el-table-column>
+          <el-table-column label="单位" prop="name">
+            <template #default="scope">
+              <span>{{ scope.row.unitType }}</span>
+            </template>
+          </el-table-column>
+          <el-table-column label="时间" prop="name">
+            <template #default="scope">
+                 <span>{{
+                     (useWSStore().getMessage()[currentsensor.id]) ?
+                       (useWSStore().getMessage()[currentsensor.id][scope.row.name]?.time) : '-'
+                   }}</span>
+            </template>
+          </el-table-column>
+        </el-table>
+
+      </div>
+      <div style="position: relative;">
+        <div style="margin-top: 10px" v-if="currentname != ''">变量 {{ currentname }} 数值曲线</div>
+
+        <div style="position: absolute;right:10px;top:10px">
+          <el-date-picker
+            v-model="value1"
+            style="z-index: 10000;"
+            type="datetimerange"
+            :shortcuts="shortcuts"
+            range-separator="到"
+            start-placeholder="开始时间"
+            end-placeholder="结束时间"
+            @change="doshowhistory"
+          />
+        </div>
+        <!--        显示历史数据 折线图-->
+        <div ref="chartlinediv" style="height: 250px" v-loading="isloading"></div>
+
+
+      </div>
+    </el-dialog>
+
+    <el-dialog :title="upload.title" v-model="upload.open" width="400px" append-to-body>
+      <el-upload ref="uploadRef" :limit="1" accept=".xlsx, .xls" :headers="upload.headers"
+                 :action="upload.url + '?updateSupport=' + upload.updateSupport" :disabled="upload.isUploading"
+                 :on-progress="handleFileUploadProgress" :on-success="handleFileSuccess" :auto-upload="false" drag
+                 :data="{deviceId:uploadParams}">
+        <el-icon class="el-icon--upload"><upload-filled /></el-icon>
+        <div class="el-upload__text">将文件拖到此处,或<em>点击上传</em></div>
+        <template #tip>
+          <div class="el-upload__tip text-center">
+            <span>仅允许导入xls、xlsx格式文件。</span>
+            <!-- <el-link type="primary" :underline="false" style="font-size:12px;vertical-align: baseline;"
+                      @click="importTemplate">下载模板</el-link> -->
+          </div>
+        </template>
+      </el-upload>
+      <template #footer>
+        <div class="dialog-footer">
+          <el-button type="primary" @click="submitFileForm">确 定</el-button>
+          <el-button @click="upload.open = false">取 消</el-button>
+        </div>
+      </template>
+    </el-dialog>
+  </div>
+</template>
+
+<script setup>
+import { getToken } from "@/utils/auth";
+// import { ElMessage } from 'element-plus'
+import { onMounted, ref, watch, watchEffect } from "vue";
+import { LayTree } from "@layui/layui-vue";
+import "@layui/layui-vue/lib/index.css";
+import { useRoute, useRouter } from "vue-router";
+import { listEquipmentOrganizational } from "@/api/data/equipmentOrganizational"
+import { listEquipmentSbook } from "@/api/data/equipmentSbook"
+import { listSensor, delSensor, addSensor, updateSensor, listSensorData, listSensorRecordData } from "@/api/data/sensor"
+import { listDatapoint } from "@/api/data/datapoint";
+import { listUnit } from "@/api/data/unit";
+import useWSStore from "@/store/modules/websocket"
+import * as echarts from 'echarts';
+
+
+import { ElMessage, ElMessageBox } from "element-plus";
+import moment from "moment";
+
+const { proxy } = getCurrentInstance();
+const { protocal_type, sensor_type, sensor_status } = proxy.useDict("protocal_type", "sensor_type", "sensor_status");
+const pointdata = ref([]);
+
+const route = useRoute();
+const router = useRouter();
+const ziduanshow = ref(false);
+
+
+const showdata = (name, un) => {
+
+  let value = "-";
+  try {
+    value = useWSStore().getMessage()[currentsensor.value.id][name].value
+    if (un == "~") {
+      return moment(value*1000).format("yyyy-MM-DD HH:mm:ss");
+    }
+    value = value ? parseFloat(value)+"" : '-';
+  }catch(e) {
+  }
+  return value;
+}
+
+const tableref = ref(null);
+const value1 = ref([
+  moment().subtract(1,'days'),
+  moment(),
+])
+const shortcuts = [
+  {
+    text: '1天',
+    value: () => {
+      const end = moment()
+      const start = moment().subtract(1, 'days');
+      return [start, end]
+    },
+  },
+  {
+    text: '3天',
+    value: () => {
+      const end = moment()
+      const start = moment().subtract(3, 'days');
+      return [start, end]
+    },
+  },
+  {
+    text: '1周',
+    value: () => {
+      const end = moment()
+      const start = moment().subtract(7, 'days');
+      return [start, end]
+    },
+  },
+]
+
+const uploadParams = ref();
+
+const cloumdata = ref([
+  { label: '传感器编号', prop: 'id', visible: true },
+  { label: '传感器名称', prop: 'name', visible: true },
+  { label: '描述', prop: 'sensorDesc', visible: true },
+  { label: '状态', prop: 'statusname', visible: true },
+  { label: '类型', prop: 'sensorType', visible: true },
+])
+
+/*** 用户导入参数 */
+const upload = reactive({
+  // 是否显示弹出层(用户导入)
+  open: false,
+  // 弹出层标题(用户导入)
+  title: "",
+  // 是否禁用上传
+  isUploading: false,
+  // 是否更新已经存在的用户数据
+  updateSupport: 0,
+  // 设置上传的请求头部
+  headers: { Authorization: "Bearer " + getToken() },
+  // 上传的地址
+  url: import.meta.env.VITE_APP_BASE_API + "/data/sensor/invoke"
+});
+
+
+const onchangepage = (page) => {
+  pagedata.value.pageNum = page;
+  getalldata();
+}
+const pagedata = ref({
+  total: 0,
+  pageSize: 10,
+  pageNum: 1
+});
+
+const allUnit = ref([])
+
+const deleterow = (item) => {
+  delSensor(item.id).then((res) => {
+    ElMessage.success("删除成功");
+    getalldata();
+  })
+}
+
+const deleteall = () => {
+  let selected = tableref.value.getSelectionRows();
+  if (selected.length == 0) {
+    ElMessage.warning("请选择要删除的行");
+    return;
+  }
+
+  if (selected.length > 0) {
+    ElMessageBox.confirm(
+      `是否删除${selected.length}条数据`,
+      "警告", {
+        confirmButtonText: "确认",
+        cancelButtonText: "取消",
+        type: 'warning',
+      }
+    ).then(() => {
+
+      delSensor(selected.map(i => i.id).join(",")).then((res) => {
+        ElMessage.success("删除成功");
+        getalldata();
+      })
+
+    });
+  }
+
+}
+
+/** 导入按钮操作 */
+const handleImport = () => {
+  const getdeviceid = (node) => {
+    if (node.children) {
+      return node.children.map(item => {
+        return getdeviceid(item);
+      }).join(",")
+    } else {
+      if ((node.id + "").indexOf("device") > -1) {
+        return node.id;
+      } else {
+        return "";
+      }
+    }
+  }
+  if (currentnode.value != null) {
+    let deviceids = getdeviceid(currentnode.value).split(",").filter(i => i != "").join(",");
+    console.log(deviceids.split(',').length == 1)
+    if (deviceids != null && deviceids.split(',').length == 1) {
+      uploadParams.value = deviceids.replaceAll("device_", '');
+      console.log(uploadParams.value)
+      upload.title = "传感器导入";
+      upload.open = true;
+    } else {
+      ElMessage({
+        message: '请选择传感器所属设备',
+        type: 'warning',
+      })
+    }
+  } else {
+    ElMessage({
+      message: '请选择传感器所属设备',
+      type: 'warning',
+    })
+  }
+
+
+};
+
+function submitFileForm() {
+  proxy.$refs["uploadRef"].submit();
+  upload.open = false;
+};
+
+const initdata = () => {
+  searchform.value.id = '';
+  searchform.value.desc = '';
+  pagedata.value = {
+    total: 0,
+    pageSize: 10,
+    pageNum: 1
+  }
+}
+
+const goadd = () => {
+  router.push("/device/sensordash/add?type=0")
+}
+
+const goedit = (item) => {
+  localStorage.setItem("currentsensor", JSON.stringify(item))
+  router.push("/device/sensordash/add?type=1")
+}
+
+const getProtocalTypeName = (item) => {
+  for (var index in protocal_type.value) {
+    if (protocal_type.value[index].value == item.protocalType) {
+      return protocal_type.value[index].label
+    }
+  }
+}
+
+
+
+const currentnode = ref(null);
+const getalldata = () => {
+  const getdeviceid = (node) => {
+    if (node.children) {
+      return node.children.map(item => {
+        return getdeviceid(item);
+      }).join(",")
+    } else {
+      if ((node.id + "").indexOf("device") > -1) {
+        return node.id;
+      } else {
+        return "";
+      }
+    }
+  }
+  let deviceids = getdeviceid(currentnode.value).split(",").filter(i => i != "").join(",");
+  console.log(deviceids)
+  if (deviceids != "") {
+    deviceids = deviceids.replaceAll("device_", "")
+    listSensor({ ...pagedata.value, params: { deviceids: deviceids, ...searchform.value }}).then(res => {
+      const { rows, total, page, size } = res;
+      pagedata.value = { total: total, pageNum: page, pageSize: 10 };
+      console.log(rows);
+      devicetabledata.value = rows.map(item => {
+        try {
+          var statusname = sensor_status.value.find(i => i.value == item.status).label;
+          item["statusname"] = statusname;
+        } catch (e) {
+
+        }
+        return item;
+      });
+    })
+  } else {
+    pagedata.value = { total: 0, pageNum: 1, pageSize: 10 };
+    devicetabledata.value = [];
+  }
+}
+
+const handleClick = (node) => {
+  //循环获取子的id
+  currentnode.value = node;
+  getalldata();
+};
+const selectedKey = ref(4);
+const data = ref([]);
+
+
+const getTreedata = () => {
+  listEquipmentOrganizational({ pageSize: 10000 }).then(res => {
+    const { rows, total, page, size } = res;
+    let rows1 = rows.map(item => {
+      if(item.name=="仿岩质潮间带生态修复实施安全保障工程"){
+        item.name="水上水下一体化监控"
+      }
+      if(item.name=="红树林-盐沼生态海岸线生态修复实施安全保障支撑工程"){
+        item.name="岸基环境生态全自动监控"
+      }
+      if(item.name=="海洋防文减文预警工程"){
+        item.name="防灾减灾预警监控"
+      }
+      if(item.name=="入海污染物自动监管工程"){
+        item.name="入海污染物监控"
+      }
+      return item;
+    })
+    //获取设备数据
+    listEquipmentSbook({ page: 1, pageSize: 100000 }).then(res1 => {
+      res1.rows.forEach(item => {
+        item["parentId"] = item.equipmentTreeId;
+        item["id"] = "device_" + item.id;
+        rows1.push(item);
+        data.value = proxy.handleTree(rows1, "id", "parentId");
+      })
+    })
+  })
+}
+getTreedata();
+
+
+const devicetabledata = ref([]);
+const searchform = ref({
+  id: "",
+  desc: ""
+});
+
+const currentdatapointlist = ref([]);
+const cdbdshow = ref(false);
+const currentsensor = ref(null);
+const cdbd = (item) => {
+  cdbdshow.value = true;
+  listDatapoint({ sensorId: item.id }).then(res => {
+    const { rows, total, page, size } = res;
+    currentdatapointlist.value = rows;
+    currentsensor.value = item;
+    try {
+      pointdata.value = JSON.parse(item.datapoints);
+    } catch (e) {}
+
+  })
+
+}
+const realtimedata = ref({})
+const datashow = ref(false);
+const lookdata = (item) => {
+  //弹窗
+  datashow.value = true;
+  currentsensor.value = item;
+  listSensorData({ page: 1, pageSize: 10000, id: item.id }).then(res => {
+    const { rows, total, page, size } = res;
+    if (rows.length > 0) {
+      if (rows[0].recordData) {
+        let data = JSON.parse(rows[0].recordData);
+        useWSStore().setMessagetype1(rows[0].tblSensor.id, data);
+      }
+    }
+
+  })
+
+  //直接接mqtt 数据
+}
+
+
+const deldatapoint = (index) => {
+  pointdata.value.splice(index, 1)
+}
+
+const addrow = () => {
+  if (!pointdata.value) { pointdata.value = []; }
+  pointdata.value.push({
+    name: "",
+    unit: "",
+    unitType: "0",
+    desc: "",
+    label: "",
+    dataPointId: null
+  })
+}
+onMounted(() => {
+  listUnit({}).then(res => {
+    allUnit.value = res.data;
+  })
+})
+
+const dosave = () => {
+  let data = JSON.stringify(pointdata.value.filter(i => i.name !== ""));
+  currentsensor.value.datapoints = data;
+  updateSensor(currentsensor.value).then(res => {
+    ElMessage.success("保存成功");
+    cdbdshow.value = false;
+    getalldata();
+  })
+}
+
+const historytabledata = ref([]);
+const chartlinediv = ref(null)
+const currentname = ref("")
+const isloading = ref(false);
+watchEffect(() => {
+  if (currentsensor.value && useWSStore().getMessage()[currentsensor.value.id] && useWSStore().getMessage()[currentsensor.value.id][currentname.value]) {
+    let data = useWSStore().getMessage()[currentsensor.value.id][currentname.value];
+    var isadd = false;
+    historytabledata.value.forEach(i => {
+      if (moment(data.time).utc() - moment(i[0]).utc() > 10) {
+        isadd = true;
+      }
+    })
+    if (isadd) {
+      historytabledata.value.splice(0, 0, [moment(data.time).valueOf(), data.value])
+    }
+
+  }
+  if (historytabledata.value.length > 0) {
+    const usedmemoryInstance = echarts.init(chartlinediv.value, "macarons");
+    usedmemoryInstance.setOption({
+      tooltip: {
+        trigger: 'axis',
+        position: function(pt) {
+          return [pt[0], '10%'];
+        }
+      },
+      xAxis: {
+        type: 'time',
+        splitLine: {
+          show: false
+        }
+      },
+      yAxis: {
+        type: 'value',
+      },
+      dataZoom: [{
+        type: 'inside',
+        start: 80,
+        end: 100
+      },
+        {
+          start: 0,
+          end: 20
+        }
+      ],
+      series: [{
+        name: currentname.value,
+        data: historytabledata.value,
+        showSymbol: false,
+        type: 'line',
+        connectNulls: false,
+        //     lineStyle: {
+        // curveness: 0 // 设置曲率为0,使线条不弯曲,即首尾不相连
+        //   },
+        areaStyle: {},
+        smooth: true
+      }]
+    });
+    console.error(historytabledata.value)
+    window.addEventListener("resize", () => {
+      usedmemoryInstance.resize()
+    });
+  } else {
+    if (chartlinediv.value) {
+      chartlinediv.value.innerHTML = "无数据";
+    }
+
+  }
+
+
+})
+const doshowhistory = (row) => {
+  if (row != undefined && row != null && row.name!=undefined) {
+    currentname.value = row.name;
+  } else {
+    if (currentname.value == undefined || currentname.value == "" || currentname.value == null) {
+      ElMessage.warning("请选择监测点")
+      return;
+    }
+  }
+  isloading.value = true;
+// debugger
+
+  //获取历史数据
+  listSensorRecordData({ sensorId: currentsensor.value.id, pointName: currentname.value, pageNum: 1, pageSize: 10000, orderByColumn: "createTime", isAsc: "desc", params: { name: currentname.value, starttime: moment(value1.value[0]).format("YYYY-MM-DD HH:mm:ss"), endtime:moment(value1.value[1]).format("YYYY-MM-DD HH:mm:ss")} }).then(res => {
+    const { rows, total, page, size } = res;
+    isloading.value = false;
+    historytabledata.value = rows.map(i => {
+      return [moment(i.createTime).valueOf(), i.pointValue]
+    });
+  })
+}
+
+const inportSensor = () => {
+
+}
+</script>
+
+<style lang="scss">
+.layui-tree .layui-this .layui-tree-txt {
+  color: #038de0 !important;
+}
+</style>
+
+<style lang="scss" scoped>
+.card-header {
+  display: flex;
+  flex-direction: row;
+  justify-content: space-between;
+}
+
+.tree {
+  *,
+  *:before,
+  *:after {
+    box-sizing: content-box !important;
+  }
+}
+</style>

+ 323 - 0
ruoyi-ui-vue3/src/views/device/sensordash/video.vue

@@ -0,0 +1,323 @@
+<template>
+  <div style="padding: 10px 15px">
+    <el-row>
+      <el-col :span="6" style="padding-right: 10px">
+        <el-card class="box-card" style="min-height: 80vh;">
+          <template #header>
+            <div class="card-header">
+              <span>视频设备</span>
+              <div>
+              <el-button v-if="isvideoshow" @click="isvideoshow=false">
+                  返回
+                </el-button></div>
+            </div>
+          </template>
+          <div class="tree">
+            <LayTree :data="data" :tail-node-icon="false" :onlyIconControl="true" v-model:selectedKey="selectedKey"
+              @node-click="handleClick">
+              <template #title="{ data }">
+                <div style="display: flex;flex-direction: row;justify-content: space-around;">
+                  <div>{{ data.name }}</div>
+                </div>
+              </template>
+            </LayTree>
+          </div>
+        </el-card>
+      </el-col>
+      <el-col :span="18"  v-if="isvideoshow">
+        <div style="height: 100%;">
+           <iframe id="FrameID" :key="currenturl" style="width: 100%;height:100%" :src="currenturl"/>
+        </div>
+      </el-col>
+      <el-col :span="18" v-if="!isvideoshow">
+        <el-card class="box-card">
+          <div style="display: flex; flex-direction: row; justify-content: space-between">
+            <div>
+              <div style="
+                  display: flex;
+                  flex-direction: row;
+                  flex-wrap: nowrap;
+                  align-items: center;
+                ">
+                <div style="font-size: 12px; width: 80px">设备名称:</div>
+                <el-input v-model="searchform.name" placeholder="设备名称"></el-input>
+              </div>
+            </div>
+            <div>
+              <el-button type="primary" plain>重置</el-button>
+              <el-button type="danger" @click="deleteall" style="display: none">批量删除</el-button>
+              <el-button type="primary" @click="getVideoList">搜索</el-button>
+            </div>
+          </div>
+        </el-card>
+
+        <el-card class="box-card" style="margin-top: 10px;">
+          <template #header>
+            <div class="card-header">
+              <span>主要设备</span>
+              <div>
+                <el-button type="primary" @click="goAdd()" style="display: none">添加</el-button>
+                <el-button type="warning" style="display: none">同步</el-button>
+              </div>
+            </div>
+          </template>
+
+          <el-row>
+            <el-col :span="24" style="padding-left: 10px">
+              <el-table :data="devicetabledata" :border="true" ref="tableref" >
+                <el-table-column type="selection" width="55"></el-table-column>
+                <el-table-column label="设备名称" prop="name"></el-table-column>
+                <el-table-column label="平台编码" prop="sn"></el-table-column>
+                <el-table-column label="设备类型" prop="type"></el-table-column>
+                <el-table-column label="主要设备协议类型" prop="protocolType"></el-table-column>
+                <el-table-column label="设备互联编码" prop="connectCode"></el-table-column>
+                <el-table-column label="设备型号" prop="model"></el-table-column>
+
+              </el-table>
+
+
+            </el-col>
+            <el-col :span="24" style="margin-top: 10px;">
+              <el-pagination style="float: right;" small background layout="prev, pager, next" :total="pagedata.total"
+                :page-size="pagedata.size" :current-page="pagedata.current" @current-change="onchangepage" class="mt-4" />
+            </el-col>
+          </el-row>
+        </el-card>
+      </el-col>
+    </el-row>
+  </div>
+</template>
+
+<script setup lang="ts" name="Units">
+import { ref } from "vue";
+import { useRoute, useRouter } from "vue-router";
+import { LayTree } from "@layui/layui-vue";
+import "@layui/layui-vue/lib/index.css";
+import { listVideo, getVideo, delVideo, addVideo, updateVideo } from "@/api/data/video";
+import { listVideoDetail, getVideoDetail, delVideoDetail, addVideoDetail, updateVideoDetail } from "@/api/data/videoDetail";
+import { ElMessage, ElMessageBox } from "element-plus";
+
+const isvideoshow = ref(false);
+const route = useRoute();
+const router = useRouter();
+
+const treeList = ref([])
+
+const currenturl = ref("");
+
+// const tableref = ref(null);
+
+const vidoeParams = ref({
+    id: null,
+    name: "",
+    sn: "",
+    type: "",
+    protocolType: '',
+    connectCode: "",
+    model: "",
+    videoFormat: "",
+    vendorType: '',
+    serialNumber: ''
+})
+
+
+
+
+const handleClick = (node) => {
+  if (node.videoId != null) {
+    searchform.value.id = node.videoId
+  } else {
+    searchform.value.id = node.id
+  }
+
+  currenturl.value = node.remark;
+  isvideoshow.value = true;
+
+
+
+
+
+
+};
+const selectedKey = ref(1);
+const data = ref([
+  {
+    title: "大华西门nvr1",
+    id: 1,
+    checked: true,
+    spread: true,
+    children: [
+      {
+        title: "东门立柱摄像机",
+        id: 2,
+        href: "https://www.layui.com/",
+      },
+      {
+        title: "东门立柱摄像机",
+        id: 3,
+        href: "https://www.layui.com/",
+      },
+      {
+        title: "东门立柱摄像机",
+        id: 4,
+        href: "https://www.layui.com/",
+      },
+      {
+        title: "东门立柱摄像机",
+        id: 5,
+        href: "https://www.layui.com/",
+      },
+      {
+        title: "东门立柱摄像机",
+        id: 6,
+        href: "https://www.layui.com/",
+      },
+    ],
+  },
+]);
+
+const handleClickTitle = (data) => {
+  console.log(data)
+}
+
+const pagedata = ref({
+  total: 0,
+  size: 10,
+  current: 1
+});
+
+
+const devicetabledata = ref([1]);
+
+const searchform = ref({
+  name: "",
+  id: null,
+});
+
+const onchangepage = (page) => {
+  pagedata.value.current = page;
+  getVideoList();
+}
+
+// const godetail = () => {
+//   router.push("/device/camera/detail")
+// }
+
+const godelVideo = (item) => {
+  console.log(item)
+  delVideo(item.id).then(res => {
+    if (res.code == 200) {
+      getVideoList();
+    }
+  })
+}
+
+const getVideoDetailList = () => {
+  listVideoDetail({ pageSize: 10000 }).then(res => {
+    console.log(treeList.value);
+    treeList.value.forEach(video => {
+      video.children = [];
+      video["spread"] = true;
+      video["disabled"] = true;
+      res.rows.forEach(detail => {
+        if (video.id == detail.videoId) {
+          detail.name = detail.cameraName
+          video.children.push(detail);
+        }
+      })
+    })
+    console.log(treeList.value);
+    data.value = treeList.value
+    return res
+  })
+}
+
+const tableref = ref(null);
+
+const deleteall = () => {
+  let selected = tableref.value.getSelectionRows();
+  if (selected.length == 0) {
+    ElMessage.warning("请选择要删除的行");
+    return;
+  }
+
+  if (selected.length > 0) {
+    ElMessageBox.confirm(
+      `是否删除${selected.length}条数据`,
+      "警告",
+      {
+        confirmButtonText: "确认",
+        cancelButtonText: "取消",
+        type: 'warning',
+      }
+    ).then(() => {
+
+      delVideo(selected.map(i => i.id).join(",")).then((res) => {
+        ElMessage.success("删除成功");
+        getVideoList();
+        getVideoDeviceList();
+      })
+
+    });
+  }
+
+}
+
+ function getVideoDeviceList() {
+  listVideo({ pageSize: 10000 }).then(res => {
+    treeList.value = res.rows;
+    getVideoDetailList()
+  })
+}
+
+const goDetail = (item ) => {
+  router.push({ path: '/device/camera/detail',query: { id: item.id } })
+}
+
+const getVideoList = () => {
+  console.log(searchform.value);
+  listVideo({ ...searchform.value, pageSize: pagedata.value.size, pageNum: pagedata.value.current }).then(res => {
+    devicetabledata.value = res.rows
+  })
+}
+
+const goAdd = () => {
+  router.push({ path: '/device/camera/add'})
+}
+
+const goUpdate = (item) => {
+  router.push({ path: '/device/camera/add', query: { id: item.id } })
+}
+
+getVideoDeviceList();
+getVideoList();
+// const res = videoTreeList();
+// console.log(res);
+</script>
+
+<style></style>
+
+<style lang="scss" scoped>
+.card-header {
+  display: flex;
+  flex-direction: row;
+  justify-content: space-between;
+}
+
+.tree {
+
+  *,
+  *:before,
+  *:after {
+    box-sizing: content-box !important;
+  }
+
+}
+
+</style>
+
+<style lang="scss">
+.layui-disabled{
+    color: #000 !important;
+  }
+</style>

+ 2 - 2
ruoyi-ui-vue3/vite.config.js

@@ -30,8 +30,8 @@ export default defineConfig(({ mode, command }) => {
       proxy: {
         // https://cn.vitejs.dev/config/#server-proxy
         "/dev-api": {
-          // target: "http://58.252.235.18:8084/iot/prod-api",
-          target: "http://localhost:8989",
+          target: "http://58.252.235.18:8084/iot/prod-api",
+          // target: "http://localhost:8989",
           changeOrigin: true,
           rewrite: (p) => p.replace(/^\/dev-api/, ""),
         },