xu 1 rok temu
commit
c23504fe6f

+ 14 - 0
.env.development

@@ -0,0 +1,14 @@
+ENV = 'development'
+
+VITE_CLI_PORT = 8080
+VITE_SERVER_PORT = 8222
+VITE_BASE_API = /api
+VITE_FILE_API = /api
+VITE_BASE_PATH = http://192.168.110.218
+VITE_POSITION = close
+VITE_EDITOR = webstorm
+
+
+// VITE_EDITOR = webstorm 如果使用webstorm开发且要使用dom定位到代码行功能 请先自定添加 webstorm到环境变量 再将VITE_EDITOR值修改为webstorm
+// 如果使用docker-compose开发模式,设置为下面的地址或本机主机IP
+// VITE_BASE_PATH = http://106.52.134.22

+ 8 - 0
.env.production

@@ -0,0 +1,8 @@
+ENV = 'production'
+
+VITE_CLI_PORT = 8222
+VITE_SERVER_PORT = 443
+VITE_BASE_API = /api
+VITE_FILE_API = /api
+#下方修改为你的线上ip
+VITE_BASE_PATH = http://110.40.223.170

+ 24 - 0
.gitignore

@@ -0,0 +1,24 @@
+# Logs
+logs
+*.log
+npm-debug.log*
+yarn-debug.log*
+yarn-error.log*
+pnpm-debug.log*
+lerna-debug.log*
+
+node_modules
+dist
+dist-ssr
+*.local
+
+# Editor directories and files
+.vscode/*
+!.vscode/extensions.json
+.idea
+.DS_Store
+*.suo
+*.ntvs*
+*.njsproj
+*.sln
+*.sw?

+ 3 - 0
.vscode/extensions.json

@@ -0,0 +1,3 @@
+{
+  "recommendations": ["Vue.volar"]
+}

+ 5 - 0
README.md

@@ -0,0 +1,5 @@
+# Vue 3 + Vite
+
+This template should help get you started developing with Vue 3 in Vite. The template uses Vue 3 `<script setup>` SFCs, check out the [script setup docs](https://v3.vuejs.org/api/sfc-script-setup.html#sfc-script-setup) to learn more.
+
+Learn more about IDE Support for Vue in the [Vue Docs Scaling up Guide](https://vuejs.org/guide/scaling-up/tooling.html#ide-support).

+ 14 - 0
index.html

@@ -0,0 +1,14 @@
+<!doctype html>
+<html lang="en">
+  <head>
+    <meta charset="UTF-8" />
+    <link rel="icon" type="image/svg+xml" href="/jx.png" />
+    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
+    <title>军信路灯控制</title>
+  </head>
+  <body>
+    <div id="app"></div>
+    <script type="text/javascript" src="https://api.map.baidu.com/api?v=3.0&ak=YwKx9ej9X37pvSHGl0Y9S1G2tvtKzP0v"></script>
+    <script type="module" src="/src/main.js"></script>
+  </body>
+</html>

+ 26 - 0
package.json

@@ -0,0 +1,26 @@
+{
+  "name": "jx_ld_web",
+  "private": true,
+  "version": "0.0.0",
+  "type": "module",
+  "scripts": {
+    "dev": "vite",
+    "build": "vite build",
+    "preview": "vite preview"
+  },
+  "dependencies": {
+    "@dataview/datav-vue3": "0.0.0-test.1672506674342",
+    "@element-plus/icons-vue": "^2.3.1",
+    "axios": "^1.7.7",
+    "echarts": "^5.5.1",
+    "element-plus": "^2.8.4",
+    "jsencrypt": "^3.3.2",
+    "path": "^0.12.7",
+    "process": "^0.11.10",
+    "vue": "^3.5.10"
+  },
+  "devDependencies": {
+    "@vitejs/plugin-vue": "^5.1.4",
+    "vite": "^5.4.8"
+  }
+}

Plik diff jest za duży
+ 1049 - 0
pnpm-lock.yaml


BIN
public/jx.png


BIN
public/jx1.png


Plik diff jest za duży
+ 1 - 0
public/vite.svg


+ 555 - 0
src/App.vue

@@ -0,0 +1,555 @@
+<template>
+  <div class="app">
+    <el-container class="container" @mousedown="startDrag" @mousemove="drag" @mouseup="endDrag" @mouseleave="endDrag" @contextmenu.prevent>
+      <BorderBox1 class="cs">
+        <div style="margin: 20px 0 10px 0;">在线设备:{{onlineData.online}}</div>
+        <div style="margin: 0 0 20px 0;">总设备:{{onlineData.total}}</div>
+        <el-progress type="dashboard" :percentage="percentage" :color="colors" />
+      </BorderBox1>
+      <div/>
+      <img ref="map" :src="imageSrc" alt="Map" @dragstart.prevent />
+      <el-button v-for="(button, index) in buttons" :key="index" class="button" :style="button.style" @click="openRegionDrawer(button)">
+        {{ button.text }}
+      </el-button>
+    </el-container>
+    <el-drawer
+        v-model="table"
+        :title="regionName"
+        direction="rtl"
+        size="90%"
+    >
+      <el-header>
+        <el-button type="primary" @click="openAddDevice = true">添加设备</el-button>
+        <el-button type="primary" :disabled="!selectedDevices.length" @click="openDeviceTiming=true">设备定时</el-button>
+        <el-button type="primary" :disabled="selectedDevices.length !== 1" @click="openLoopTiming=true">单设备回路定时</el-button>
+        <el-button type="success" :disabled="!selectedDevices.length" @click="batchDeviceSwitch(1)">开</el-button>
+        <el-button type="danger" :disabled="!selectedDevices.length" @click="batchDeviceSwitch(0)">关</el-button>
+      </el-header>
+      <el-main>
+        <el-table :data="deviceData" @selection-change="handleSelectionChange">
+          <el-table-column type="selection" width="55" />
+          <el-table-column label="编号" prop="id" width="100" align="center"/>
+          <el-table-column label="设备sn" prop="sn" width="200" align="center"/>
+          <el-table-column label="名称" prop="name" width="150" align="center"/>
+          <el-table-column label="类型" prop="genre" width="150" align="center"/>
+          <el-table-column label="回路" prop="deviceLoops" width="250" align="center">
+            <template #default="scope">
+              <span v-for="(item2, index) in scope.row.deviceLoops" :key="index">&nbsp;
+                <el-tooltip
+                    class="item"
+                    effect="dark"
+                    :content="`时控1:${item2.timeCondition1OnTime}-${item2.timeCondition1OffTime},时控2:${item2.timeCondition2OnTime}-${item2.timeCondition2OffTime}`"
+                    placement="top-start"
+                >
+<!--                  ${item2}-${scope.row.relayTimeStr[index]}-->
+                  <el-icon
+                      v-if="item2.state"
+                      style="color: green;"
+                      @click="switchLoop(item2)"
+                  >
+                  <CircleCheckFilled />
+                </el-icon>
+                <el-icon
+                    v-if="!item2.state"
+                    @click="switchLoop(item2)"
+                >
+                  <SwitchButton />
+                </el-icon>
+                </el-tooltip>
+              </span>
+            </template>
+          </el-table-column >
+          <el-table-column label="状态" prop="state" width="150" align="center">
+            <template #default="scope">
+              <el-tag type="success" v-if="scope.row.state">
+                在线
+              </el-tag>
+              <el-tag type="info" v-if="!scope.row.state">离线</el-tag>
+            </template>
+          </el-table-column>
+          <el-table-column label="是否外接" prop="isSun" width="150" align="center">
+            <template #default="scope">
+              <el-switch
+                  v-model="scope.row.isSun"
+                  class="ml-2"
+                  style="--el-switch-on-color: #13ce66; --el-switch-off-color: #ff4949"
+                  @change="save"
+              />
+            </template>
+          </el-table-column>
+          <el-table-column label="操作" prop="action" width="400" align="center">
+            <template #default="scope">
+              <el-button type="success" @click="switchDevice(scope.row,1)">开</el-button>
+              <el-button type="danger" @click="switchDevice(scope.row,0)">关</el-button>
+              <el-button type="primary" v-if="scope.row.isSun" @click="">太阳能数据</el-button>
+              <el-button type="primary" @click="openUpdateDevice(scope.row)">编辑</el-button>
+              <el-button type="danger" @click="deleteDevice(scope.$index)">删除</el-button>
+            </template>
+          </el-table-column>
+        </el-table>
+      </el-main>
+      <el-dialog
+          v-model="openAddDevice"
+          title="新增"
+          width="400"
+          align-center
+      >
+        <el-form label-width="auto">
+          <el-form-item label="ID" label-position="left">
+            <el-input placeholder="ID" v-model.number="addDeviceData.id"></el-input>
+          </el-form-item>
+          <el-form-item label="Sn" label-position="left">
+            <el-input placeholder="Sn" v-model="addDeviceData.sn"></el-input>
+          </el-form-item>
+          <el-form-item label="名称" label-position="left">
+            <el-input placeholder="名称" v-model="addDeviceData.name"></el-input>
+          </el-form-item>
+          <el-form-item label="类型" label-position="left">
+            <el-input placeholder="四路控制" v-model="addDeviceData.genre"></el-input>
+          </el-form-item>
+          <el-form-item label="回路数" label-position="left">
+            <el-input placeholder="4" v-model.number="addDeviceData.loopNumber"></el-input>
+          </el-form-item>
+          <el-form-item>
+            <el-button type="primary" @click="addDevice">提交</el-button>
+          </el-form-item>
+        </el-form>
+      </el-dialog>
+      <el-dialog
+          v-model="isUpdateDevice"
+          title="编辑"
+          width="400"
+          align-center
+      >
+        <el-form label-width="auto">
+          <el-form-item label="ID" label-position="left">
+            <el-input placeholder="ID" v-model.number="updateDeviceData.id"></el-input>
+          </el-form-item>
+          <el-form-item label="Sn" label-position="left">
+            <el-input placeholder="Sn" v-model="updateDeviceData.sn"></el-input>
+          </el-form-item>
+          <el-form-item label="名称" label-position="left">
+            <el-input placeholder="名称" v-model="updateDeviceData.name"></el-input>
+          </el-form-item>
+          <el-form-item v-for="item in updateDeviceData.deviceLoops" label="回路名" label-position="left">
+            <el-input placeholder="回路名" v-model="item.name"></el-input>
+          </el-form-item>
+          <el-form-item>
+            <el-button type="primary" @click="updateDevice">提交</el-button>
+          </el-form-item>
+        </el-form>
+      </el-dialog>
+      <el-dialog
+          v-model="openDeviceTiming"
+          title="设备定时"
+          width="800"
+          align-center
+      >
+        <el-form ref="elFormRefTime" :model="formDataTime" label-position="right" :rules="rule" label-width="80px">
+          <deviceReplayTimeSet :selected-devices="selectedDevices" @save="save" />
+        </el-form>
+      </el-dialog>
+      <el-dialog
+          v-model="openLoopTiming"
+          title="设备定时"
+          width="800"
+          align-center
+      >
+        <el-form ref="elFormRefTime" :model="formDataTime" label-position="right" :rules="rule" label-width="80px">
+          <deviceLoopReplayTimeSet :selected-devices="selectedDevices" @save="save" />
+        </el-form>
+      </el-dialog>
+    </el-drawer>
+  </div>
+</template>
+
+<script setup>
+import {ref, onMounted, reactive, computed} from 'vue';
+import {BorderBox1,BorderBox3 } from '@dataview/datav-vue3';
+import {ElContainer, ElButton, ElMessage, ElMessageBox} from 'element-plus';
+import {deviceBatchSwitch, deviceLoopSwitch, deviceSwitch, getOnlineDevice, queryData, saveData} from "@/api/device.js";
+import DeviceReplayTimeSet from "@/comm/deviceReplayTimeSet.vue";
+import DeviceLoopReplayTimeSet from "@/comm/deviceLoopReplayTimeSet.vue";
+
+const imageSrc = '/src/static/jx4.png'; // 替换为你的图片路径
+const map = ref(null);
+let isDragging = false;
+let dragStartX = 0;
+let dragStartY = 0;
+let currentMapLeft = 0;
+let currentMapTop = 0;
+const dragSpeed = 0.1; // 控制拖动速度,值越小速度越慢
+
+const buttons = ref([
+  { text: '生活区', initialLeft: 500, initialTop: 1800 },
+  { text: '股份公司进场上坡', initialLeft: 500, initialTop: 1550 },
+  { text: '地磅房至污泥厂门口', initialLeft: 1150, initialTop: 1500 },
+  { text: '三角花园至油库', initialLeft: 1500, initialTop: 1250 },
+  { text: '地磅房至搅拌站', initialLeft: 950, initialTop: 1900 },
+  { text: '搅拌站至三岔路口', initialLeft: 2100, initialTop: 1200 },
+  { text: '垃圾堆体道路', initialLeft: 2300, initialTop: 600 },
+  { text: '污水进场道路', initialLeft: 2200, initialTop: 300 }
+]);
+
+function startDrag(event) {
+  isDragging = true;
+  dragStartX = event.clientX;
+  dragStartY = event.clientY;
+}
+
+function drag(event) {
+  if (!isDragging) return;
+
+  const containerRect = map.value.parentElement.getBoundingClientRect();
+  const mapRect = map.value.getBoundingClientRect();
+
+  const deltaX = (event.clientX - dragStartX) * dragSpeed;
+  const deltaY = (event.clientY - dragStartY) * dragSpeed;
+
+  const newX = currentMapLeft + deltaX;
+  const newY = currentMapTop + deltaY;
+
+  // 计算边界限制
+  const minX = -(mapRect.width - containerRect.width); // 左侧边缘不能完全移出容器
+  const maxX = 0; // 右侧边缘可以超过容器
+  const minY = -(mapRect.height - containerRect.height); // 顶部边缘不能完全移出容器
+  const maxY = 0; // 底部边缘可以超过容器
+
+  // 应用边界限制
+  const finalX = Math.max(minX, Math.min(maxX, newX));
+  const finalY = Math.max(minY, Math.min(maxY, newY));
+
+  currentMapLeft = finalX;
+  currentMapTop = finalY;
+
+  map.value.style.left = `${finalX}px`;
+  map.value.style.top = `${finalY}px`;
+
+  // 更新按钮位置
+  updateButtonPositions();
+}
+
+function endDrag() {
+  isDragging = false;
+}
+
+function updateButtonPositions() {
+  buttons.value.forEach((button) => {
+    button.style = {
+      left: `${button.initialLeft + currentMapLeft}px`,
+      top: `${button.initialTop + currentMapTop}px`
+    };
+  });
+}
+
+const regionData = ref()
+const onlineData = ref({
+  online: 0,
+  total:0
+})
+const getData = async() => {
+  await queryData().then(res => {
+    console.log(res)
+    regionData.value = res.data
+  })
+  await getOnlineDevice().then(res => {
+    console.log(res.data)
+    onlineData.value = res.data
+  })
+}
+
+const percentage = computed(() => {
+  if (onlineData.value.total === 0) {
+    return 0;
+  }
+  const result = (onlineData.value.online / onlineData.value.total) * 100;
+  return Math.round(result);
+});
+
+const colors = [
+  { color: '#778899', percentage: 20 },
+  { color: '#DC143C', percentage: 40 },
+  { color: '#FFD700', percentage: 60 },
+  { color: '#1989fa', percentage: 80 },
+  { color: '#00FF7F', percentage: 100 },
+]
+
+const table = ref(false)
+
+const regionName = ref()
+const deviceData = ref()
+const openRegionDrawer = (val) => {
+  table.value = true;
+  regionName.value = val.text;
+
+  for (let i = 0; i < regionData.value.length; i++) {
+    if (regionData.value[i].name === regionName.value) {
+      deviceData.value = regionData.value[i].devices
+      addDeviceData.value.regionId = regionData.value[i].id
+      break;
+    }
+  }
+}
+
+//新增
+const openAddDevice = ref(false)
+const addDeviceData = ref({
+  id: null,
+  sn: '',
+  name: '',
+  regionId: null,
+  genre: "八回路控制",
+  state: 1,
+  isSun: false,
+  loopNumber : 8,
+  deviceLoops: []
+})
+
+const addDevice = () => {
+  if (addDeviceData.value.id === null || addDeviceData.value.sn === null) {
+    ElMessage.error("数据不能为空")
+    return
+  }
+  for (let i = 0; i < addDeviceData.value.loopNumber; i++) {
+    const loop = {
+      id: i+1,
+      deviceId: addDeviceData.value.sn,
+      state: 0,
+      name: "回路"+(i+1),
+      timeCondition1OnTime: '关闭',
+      timeCondition1OffTime: '关闭',
+      timeCondition2OnTime: '关闭',
+      timeCondition2OffTime: '关闭',
+    }
+    addDeviceData.value.deviceLoops.push(loop)
+  }
+
+  deviceData.value.push(addDeviceData.value)
+  save()
+  ElMessage.success("添加成功")
+  openAddDevice.value = false
+  // 新增设备逻辑
+}
+//编辑
+const isUpdateDevice = ref(false)
+const updateDeviceData = ref()
+const openUpdateDevice = (val) => {
+  updateDeviceData.value = val
+  isUpdateDevice.value = true
+}
+
+const updateDevice = () => {
+  if (updateDeviceData.value.id === null || updateDeviceData.value.sn === null) {
+    ElMessage.error("数据不能为空")
+    return
+  }
+  save()
+  isUpdateDevice.value = false
+  ElMessage.success("修改成功")
+}
+
+//删除
+const deleteDevice = (index) => {
+  ElMessageBox.confirm(
+      '将永久删除该数据,确定吗?',
+      '删除',
+      {
+        confirmButtonText: '确定',
+        cancelButtonText: '取消',
+        type: 'warning',
+      }
+  )
+      .then(() => {
+        deviceData.value.splice(index,1)
+        ElMessage({
+          type: 'success',
+          message: '删除成功',
+        })
+        save()
+      })
+      .catch(() => {
+        ElMessage({
+          type: 'info',
+          message: '删除已取消',
+        })
+      })
+}
+
+// 开关回路
+const switchLoop = async(val) => {
+  val.state = val.state === 1 ? 0 : 1
+  console.log(val)
+  await deviceLoopSwitch(val).then(res => {
+    console.log(res)
+    // 开关回路
+  })
+}
+// 开关设备
+const switchDevice = async(val, state) => {
+  if (val.state === 0) {
+    ElMessage.error("设备离线")
+    return
+  }
+  await deviceSwitch({
+    device: val,
+    state: state
+  }).then(res => {
+    console.log(res)
+    // 开关设备
+    for (let i = 0; i < val.deviceLoops.length; i++) {
+      val.deviceLoops[i].state = state
+    }
+    save()
+  })
+}
+
+const batchDeviceSwitch = async (state) => {
+  await deviceBatchSwitch({
+    devices: selectedDevices.value,
+    state: state
+  }).then(res => {
+    if (res.code === 0) {
+      ElMessage.success("批量操作成功")
+      openDeviceTiming.value = false
+      // 批量开关设备
+      deviceData.value
+      for (let i = 0; i < deviceData.value.length; i++) {
+        for (let j = 0; j < selectedDevices.value.length; j++) {
+          if (deviceData.value[i].sn === selectedDevices.value[j].sn) {
+            for (let k = 0; k < deviceData.value[i].deviceLoops.length; k++) {
+              if (deviceData.value[i].deviceLoops[k].deviceId === selectedDevices.value[j].sn) {
+                deviceData.value[i].deviceLoops[k].state = state
+              }
+            }
+          }
+        }
+      }
+      save()
+    } else {
+      ElMessage.error(res.msg)
+    }
+    // 批量开关设备
+
+  })
+}
+
+const selectedDevices = ref([])
+const handleSelectionChange = (val) => {
+  selectedDevices.value = val
+  console.log(val)
+  // 选中设备
+}
+
+// 设备定时
+const openDeviceTiming = ref(false)
+
+const formDataTime = ref({
+  timeCondition1OnTime: '关闭',
+  timeCondition1OffTime: '关闭',
+  timeCondition2OnTime: '关闭',
+  timeCondition2OffTime: '关闭',
+})
+
+// 验证规则
+const rule = reactive({
+  deviceSn: [{
+    required: true,
+    message: '',
+    trigger: ['input', 'blur'],
+  }],
+  deviceId: [{
+    required: true,
+    message: '',
+    trigger: ['input', 'blur'],
+  }],
+  circuitNum: [{
+    required: true,
+    message: '',
+    trigger: ['input', 'blur'],
+  }],
+  status: [{
+    required: true,
+    message: '',
+    trigger: ['input', 'blur'],
+  }],
+  module: [{
+    required: true,
+    message: '',
+    trigger: ['input', 'blur'],
+  }],
+})
+
+// 回路定时
+const openLoopTiming = ref(false)
+
+//保存
+const save = () => {
+  saveData(regionData.value)
+}
+// 定时查询
+setInterval(() => {
+  getData()
+}, 300000);
+
+onMounted(() => {
+  // 初始化地图位置
+  map.value.style.left = `${currentMapLeft}px`;
+  map.value.style.top = `${currentMapTop}px`;
+
+  // 初始化按钮位置
+  updateButtonPositions();
+  getData()
+});
+</script>
+
+<style scoped>
+.app {
+  position: relative;
+  width: 100vw;
+  height: 100vh;
+  overflow: hidden;
+}
+
+.container {
+  position: relative;
+  width: 100%;
+  height: 100%;
+  background-color: #f4f4f4;
+  border: 1px solid #ccc;
+  cursor: move;
+}
+
+img {
+  position: absolute;
+  top: 0;
+  left: 0;
+  max-width: none; /* 确保图片不受最大宽度限制 */
+  max-height: none; /* 确保图片不受最大高度限制 */
+  user-select: none;
+  -webkit-user-drag: none; /* 禁止图片拖动 */
+}
+
+.button {
+  position: absolute;
+  padding: 5px 10px;
+  background-color: rgba(0, 0, 0, 0.5);
+  color: white;
+  border-radius: 5px;
+  cursor: pointer;
+  user-select: none;
+}
+.cs {
+  z-index: 10;
+  width: 200px;
+  height: 230px;
+  color: white;
+  text-align: center;
+}
+.cs1 {
+  z-index: 10;
+  width: 200px;
+  height: 100px;
+  color: white;
+  text-align: center;
+}
+</style>

+ 47 - 0
src/api/device.js

@@ -0,0 +1,47 @@
+import service from "../../utils/request.js";
+
+export const queryData = () => {
+    return service({
+        url: '/api/get',
+        method: 'get'
+    })
+}
+
+export const saveData = (data) => {
+    return service({
+        url: '/api/saveDevice',
+        method: 'post',
+        data
+    })
+}
+
+export const deviceLoopSwitch = (data) => {
+    return service({
+        url: '/api/deviceLoopSwitch',
+        method: 'post',
+        data
+    })
+}
+
+export const deviceSwitch = (data) => {
+    return service({
+        url: '/api/deviceSwitch',
+        method: 'post',
+        data
+    })
+}
+
+export const deviceBatchSwitch = (data) => {
+    return service({
+        url: '/api/deviceBatchSwitch',
+        method: 'post',
+        data
+    })
+}
+
+export const getOnlineDevice = () => {
+    return service({
+        url: '/api/getOnlineDevice',
+        method: 'get'
+    })
+}

+ 207 - 0
src/comm/deviceLoopReplayTimeSet.vue

@@ -0,0 +1,207 @@
+<template>
+  <el-button v-for="item in selectedDevices[0].deviceLoops" @click="selectItem(item)" :class="{ 'active': s === item.id }">{{item.name}}</el-button>
+  <div style="margin: 5px 0;"/>
+  <el-form ref="elFormRefTime" label-position="right" label-width="80px">
+    <el-row>
+      <el-col :span="8" class="grid-cell">
+        <el-form-item label="时1开:" prop="timeCondition1OnTime" :rules="timeRule">
+          <el-time-select
+            v-model="timeValue1"
+            start="00:10"
+            step="00:10"
+            end="24:00"
+            placeholder="请选择时间"
+            :disabled="openTimeOption1 !== '手动'"
+          />
+        </el-form-item>
+      </el-col>
+      <el-col :span="8" :offset="2">
+        <el-select v-model="openTimeOption1">
+          <el-option
+            v-for="item in timeList"
+            :key="item.value"
+            :label="item.label"
+            :value="item.value"
+          />
+        </el-select>
+      </el-col>
+    </el-row>
+    <el-row>
+      <el-col :span="8" class="grid-cell">
+        <el-form-item label="时1关:" prop="timeCondition1OnTime" :rules="timeRule">
+          <el-time-select
+            v-model="timeValue2"
+            start="00:10"
+            step="00:10"
+            end="24:00"
+            placeholder="请选择时间"
+            :disabled="shutTimeOption1 !== '手动'"
+          />
+        </el-form-item>
+      </el-col>
+      <el-col :span="8" :offset="2">
+        <el-select v-model="shutTimeOption1">
+          <el-option
+            v-for="item in timeList"
+            :key="item.value"
+            :label="item.label"
+            :value="item.value"
+          />
+        </el-select>
+      </el-col>
+    </el-row>
+    <el-row>
+      <el-col :span="8" class="grid-cell">
+        <el-form-item label="时2开:" prop="timeCondition1OnTime" :rules="timeRule">
+          <el-time-select
+            v-model="timeValue3"
+            start="00:10"
+            step="00:10"
+            end="24:00"
+            placeholder="请选择时间"
+            :disabled="openTimeOption2 !== '手动'"
+          />
+        </el-form-item>
+      </el-col>
+      <el-col :span="8" :offset="2">
+        <el-select v-model="openTimeOption2">
+          <el-option
+            v-for="item in timeList"
+            :key="item.value"
+            :label="item.label"
+            :value="item.value"
+          />
+        </el-select>
+      </el-col>
+    </el-row>
+    <el-row>
+      <el-col :span="8" class="grid-cell">
+        <el-form-item label="时2关:" prop="timeCondition1OnTime" :rules="timeRule">
+          <el-time-select
+            v-model="timeValue4"
+            start="00:10"
+            step="00:10"
+            end="24:00"
+            placeholder="请选择时间"
+            :disabled="shutTimeOption2 !== '手动'"
+          />
+        </el-form-item>
+      </el-col>
+      <el-col :span="8" :offset="2">
+        <el-select v-model="shutTimeOption2">
+          <el-option
+            v-for="item in timeList"
+            :key="item.value"
+            :label="item.label"
+            :value="item.value"
+          />
+        </el-select>
+      </el-col>
+    </el-row>
+    <el-row>
+      <el-col :span="3" :offset="20">
+        <el-button type="primary" size="large" @click="enterDialogTime">确 定</el-button>
+      </el-col>
+    </el-row>
+  </el-form>
+</template>
+<script>
+export default {
+  name: 'DeviceReplayTimeSet',
+}
+</script>
+
+<script setup>
+import { ref, reactive, onMounted, toRefs } from 'vue'
+import { ElMessage } from 'element-plus'
+
+const d = ref([1,2,3,4,5,6,7,8])
+
+const s = ref(0)
+
+const selectItem = (item) => {
+  s.value = item
+}
+
+const props = defineProps({
+  selectedDevices: Array,
+})
+const emit = defineEmits(['save'])
+
+const { selectedDevices } = toRefs(props)
+
+// 批量定时
+const enterDialogTime = async() => {
+  timeFormat()
+  if (s.value === 0) {
+    ElMessage({
+      message: '请先选择回路',
+      type: 'warning'
+    })
+    return
+  }
+  selectedDevices.value[0].deviceLoops[s.value-1].timeCondition1OnTime = formDataTime.timeCondition1OnTime
+  selectedDevices.value[0].deviceLoops[s.value-1].timeCondition1OffTime = formDataTime.timeCondition1OffTime
+  selectedDevices.value[0].deviceLoops[s.value-1].timeCondition2OnTime = formDataTime.timeCondition2OnTime
+  selectedDevices.value[0].deviceLoops[s.value-1].timeCondition2OffTime = formDataTime.timeCondition2OffTime
+
+  console.log(formDataTime)
+  emit('save')
+
+  ElMessage({
+    message: '操作成功',
+    type: 'success'
+  })
+
+}
+
+// 对前端输入时控进行检验
+const timeRule = [{
+  pattern: /^(日出|日落|关闭|(0?[0-9]|1[0-9]|2[0-3]):([0-5][0-9]))$/,
+  message: '请输入正确的时控时间',
+  trigger: 'blur',
+}]
+// 时间值
+const timeValue1 = ref('')
+const timeValue2 = ref('')
+const timeValue3 = ref('')
+const timeValue4 = ref('')
+// 时间模式列表
+const timeList = [
+  { label: '关闭', value: '关闭' },
+  { label: '日落', value: '日落' },
+  { label: '日出', value: '日出' },
+  { label: '手动', value: '手动' },
+]
+// 时间模式值
+const openTimeOption1 = ref('关闭')
+const shutTimeOption1 = ref('关闭')
+const openTimeOption2 = ref('关闭')
+const shutTimeOption2 = ref('关闭')
+
+// 需要传输的时控格式
+const formDataTime = reactive({
+  timeCondition1OnTime: '关闭',
+  timeCondition1OffTime: '关闭',
+  timeCondition2OnTime: '关闭',
+  timeCondition2OffTime: '关闭',
+})
+
+const timeFormat = () => {
+  const openTime1 = openTimeOption1.value === '手动' ? timeValue1 : openTimeOption1
+  const shutTime1 = shutTimeOption1.value === '手动' ? timeValue2 : shutTimeOption1
+  const openTime2 = openTimeOption2.value === '手动' ? timeValue3 : openTimeOption2
+  const shutTime2 = shutTimeOption2.value === '手动' ? timeValue4 : shutTimeOption2
+  formDataTime.timeCondition1OnTime = openTime1.value
+  formDataTime.timeCondition1OffTime = shutTime1.value
+  formDataTime.timeCondition2OnTime = openTime2.value
+  formDataTime.timeCondition2OffTime = shutTime2.value
+}
+
+</script>
+<style scoped>
+.active {
+  background-color: blue;
+  color: white;
+}
+</style>

+ 188 - 0
src/comm/deviceReplayTimeSet.vue

@@ -0,0 +1,188 @@
+<template>
+  <el-form ref="elFormRefTime" label-position="right" label-width="80px">
+    <el-row>
+      <el-col :span="8" class="grid-cell">
+        <el-form-item label="时1开:" prop="timeCondition1OnTime" :rules="timeRule">
+          <el-time-select
+            v-model="timeValue1"
+            start="00:10"
+            step="00:10"
+            end="24:00"
+            placeholder="请选择时间"
+            :disabled="openTimeOption1 !== '手动'"
+          />
+        </el-form-item>
+      </el-col>
+      <el-col :span="8" :offset="2">
+        <el-select v-model="openTimeOption1">
+          <el-option
+            v-for="item in timeList"
+            :key="item.value"
+            :label="item.label"
+            :value="item.value"
+          />
+        </el-select>
+      </el-col>
+    </el-row>
+    <el-row>
+      <el-col :span="8" class="grid-cell">
+        <el-form-item label="时1关:" prop="timeCondition1OnTime" :rules="timeRule">
+          <el-time-select
+            v-model="timeValue2"
+            start="00:10"
+            step="00:10"
+            end="24:00"
+            placeholder="请选择时间"
+            :disabled="shutTimeOption1 !== '手动'"
+          />
+        </el-form-item>
+      </el-col>
+      <el-col :span="8" :offset="2">
+        <el-select v-model="shutTimeOption1">
+          <el-option
+            v-for="item in timeList"
+            :key="item.value"
+            :label="item.label"
+            :value="item.value"
+          />
+        </el-select>
+      </el-col>
+    </el-row>
+    <el-row>
+      <el-col :span="8" class="grid-cell">
+        <el-form-item label="时2开:" prop="timeCondition1OnTime" :rules="timeRule">
+          <el-time-select
+            v-model="timeValue3"
+            start="00:10"
+            step="00:10"
+            end="24:00"
+            placeholder="请选择时间"
+            :disabled="openTimeOption2 !== '手动'"
+          />
+        </el-form-item>
+      </el-col>
+      <el-col :span="8" :offset="2">
+        <el-select v-model="openTimeOption2">
+          <el-option
+            v-for="item in timeList"
+            :key="item.value"
+            :label="item.label"
+            :value="item.value"
+          />
+        </el-select>
+      </el-col>
+    </el-row>
+    <el-row>
+      <el-col :span="8" class="grid-cell">
+        <el-form-item label="时2关:" prop="timeCondition1OnTime" :rules="timeRule">
+          <el-time-select
+            v-model="timeValue4"
+            start="00:10"
+            step="00:10"
+            end="24:00"
+            placeholder="请选择时间"
+            :disabled="shutTimeOption2 !== '手动'"
+          />
+        </el-form-item>
+      </el-col>
+      <el-col :span="8" :offset="2">
+        <el-select v-model="shutTimeOption2">
+          <el-option
+            v-for="item in timeList"
+            :key="item.value"
+            :label="item.label"
+            :value="item.value"
+          />
+        </el-select>
+      </el-col>
+    </el-row>
+    <el-row>
+      <el-col :span="3" :offset="20">
+        <el-button type="primary" size="large" @click="enterDialogTime">确 定</el-button>
+      </el-col>
+    </el-row>
+  </el-form>
+</template>
+<script>
+export default {
+  name: 'DeviceReplayTimeSet',
+}
+</script>
+
+<script setup>
+import { ref, reactive, onMounted, toRefs } from 'vue'
+import { ElMessage } from 'element-plus'
+
+const props = defineProps({
+  selectedDevices: Array,
+})
+const emit = defineEmits(['save'])
+
+const { selectedDevices } = toRefs(props)
+
+// 批量定时
+const enterDialogTime = async() => {
+  timeFormat()
+  for (let index = 0; index < selectedDevices.value.length; index++) {
+    for (let i = 0; i < selectedDevices.value[index].deviceLoops.length; i++) {
+      selectedDevices.value[index].deviceLoops[i].timeCondition1OnTime = formDataTime.timeCondition1OnTime
+      selectedDevices.value[index].deviceLoops[i].timeCondition1OffTime = formDataTime.timeCondition1OffTime
+      selectedDevices.value[index].deviceLoops[i].timeCondition2OnTime = formDataTime.timeCondition2OnTime
+      selectedDevices.value[index].deviceLoops[i].timeCondition2OffTime = formDataTime.timeCondition2OffTime
+    }
+  }
+
+  console.log(formDataTime)
+  emit('save')
+
+  ElMessage({
+    message: '操作成功',
+    type: 'success'
+  })
+
+}
+
+// 对前端输入时控进行检验
+const timeRule = [{
+  pattern: /^(日出|日落|关闭|(0?[0-9]|1[0-9]|2[0-3]):([0-5][0-9]))$/,
+  message: '请输入正确的时控时间',
+  trigger: 'blur',
+}]
+// 时间值
+const timeValue1 = ref('')
+const timeValue2 = ref('')
+const timeValue3 = ref('')
+const timeValue4 = ref('')
+// 时间模式列表
+const timeList = [
+  { label: '关闭', value: '关闭' },
+  { label: '日落', value: '日落' },
+  { label: '日出', value: '日出' },
+  { label: '手动', value: '手动' },
+]
+// 时间模式值
+const openTimeOption1 = ref('关闭')
+const shutTimeOption1 = ref('关闭')
+const openTimeOption2 = ref('关闭')
+const shutTimeOption2 = ref('关闭')
+
+// 需要传输的时控格式
+const formDataTime = reactive({
+  timeCondition1OnTime: '关闭',
+  timeCondition1OffTime: '关闭',
+  timeCondition2OnTime: '关闭',
+  timeCondition2OffTime: '关闭',
+})
+
+const timeFormat = () => {
+  const openTime1 = openTimeOption1.value === '手动' ? timeValue1 : openTimeOption1
+  const shutTime1 = shutTimeOption1.value === '手动' ? timeValue2 : shutTimeOption1
+  const openTime2 = openTimeOption2.value === '手动' ? timeValue3 : openTimeOption2
+  const shutTime2 = shutTimeOption2.value === '手动' ? timeValue4 : shutTimeOption2
+  formDataTime.timeCondition1OnTime = openTime1.value
+  formDataTime.timeCondition1OffTime = shutTime1.value
+  formDataTime.timeCondition2OnTime = openTime2.value
+  formDataTime.timeCondition2OffTime = shutTime2.value
+}
+
+</script>

+ 18 - 0
src/main.js

@@ -0,0 +1,18 @@
+// main.ts
+import { createApp } from 'vue'
+import ElementPlus from 'element-plus'
+import 'element-plus/dist/index.css'
+import App from './App.vue'
+import * as ElementPlusIconsVue from '@element-plus/icons-vue'
+const app = createApp(App)
+
+for (const [key, component] of Object.entries(ElementPlusIconsVue)) {
+    app.component(key, component)
+}
+
+import * as DataV from '@dataview/datav-vue3';
+
+app.use(DataV, { classNamePrefix: 'dv-' });
+
+app.use(ElementPlus)
+app.mount('#app')

BIN
src/static/jx4.png


+ 79 - 0
src/style.css

@@ -0,0 +1,79 @@
+:root {
+  font-family: Inter, system-ui, Avenir, Helvetica, Arial, sans-serif;
+  line-height: 1.5;
+  font-weight: 400;
+
+  color-scheme: light dark;
+  color: rgba(255, 255, 255, 0.87);
+  background-color: #242424;
+
+  font-synthesis: none;
+  text-rendering: optimizeLegibility;
+  -webkit-font-smoothing: antialiased;
+  -moz-osx-font-smoothing: grayscale;
+}
+
+a {
+  font-weight: 500;
+  color: #646cff;
+  text-decoration: inherit;
+}
+a:hover {
+  color: #535bf2;
+}
+
+body {
+  margin: 0;
+  display: flex;
+  place-items: center;
+  min-width: 320px;
+  min-height: 100vh;
+}
+
+h1 {
+  font-size: 3.2em;
+  line-height: 1.1;
+}
+
+button {
+  border-radius: 8px;
+  border: 1px solid transparent;
+  padding: 0.6em 1.2em;
+  font-size: 1em;
+  font-weight: 500;
+  font-family: inherit;
+  background-color: #1a1a1a;
+  cursor: pointer;
+  transition: border-color 0.25s;
+}
+button:hover {
+  border-color: #646cff;
+}
+button:focus,
+button:focus-visible {
+  outline: 4px auto -webkit-focus-ring-color;
+}
+
+.card {
+  padding: 2em;
+}
+
+#app {
+  max-width: 1280px;
+  margin: 0 auto;
+  padding: 2rem;
+  text-align: center;
+}
+
+@media (prefers-color-scheme: light) {
+  :root {
+    color: #213547;
+    background-color: #ffffff;
+  }
+  a:hover {
+    color: #747bff;
+  }
+  button {
+    background-color: #f9f9f9;
+  }
+}

+ 50 - 0
utils/request.js

@@ -0,0 +1,50 @@
+// /utils/request.ts
+
+import axios from 'axios'
+// import useUserStore from '@/store/modules/user'
+//创建axios实例
+const request = axios.create({
+    baseURL: import.meta.env.VITE_BASE_API,
+    timeout: 5000,
+})
+//请求拦截
+request.interceptors.request.use((config) => {
+    // const userStore = useUserStore()
+    // if (userStore.token) {
+    //     config.headers.token = userStore.token
+    // }
+    return config
+})
+
+//响应拦截
+request.interceptors.response.use(
+    (response) => {
+        return response.data
+    },
+    (error) => {
+        let message = ''
+        const status = error.response.status
+        switch (status) {
+            case 401:
+                message = 'TOKEN过期'
+                break
+            case 403:
+                message = '无权访问'
+                break
+            case 404:
+                message = '请求地址错误'
+                break
+            case 500:
+                message = '服务器出现问题'
+                break
+            default:
+                message = '网络出现问题'
+                break
+        }
+        //提示错误信息
+        //...
+        return Promise.reject(error)
+    },
+)
+//对外暴露
+export default request

+ 42 - 0
vite.config.js

@@ -0,0 +1,42 @@
+import { defineConfig } from 'vite'
+import vue from '@vitejs/plugin-vue'
+import * as path from 'path'
+
+// https://vitejs.dev/config/
+export default defineConfig({
+    plugins: [vue()],
+    resolve: {
+        alias: {
+            '@': path.resolve(__dirname, './src'),
+        },
+    },
+    // 解决跨域问题
+    server: {
+        host: '0.0.0.0',
+        // port: 4000,
+        proxy: {
+            '/api': {
+                target: 'http://192.168.110.218:8222',
+                // secure: false, // 请求是否为https
+                changeOrigin: true,
+                rewrite:(path)=>path.replace(/^\/api/,'') //api替换为'',
+            },
+        },
+    },
+
+
+
+    // resolve: {
+    //   alias: {
+    //     '@': path.resolve(__dirname, 'src'),
+    //   },
+    // },
+    css: {
+        preprocessorOptions: {
+            less: {
+                additionalData: `@import "@/assets/style/base.less";`,
+            }
+        },
+    },
+})
+