Selaa lähdekoodia

完成遥控遥调操作交互

liling 1 vuosi sitten
vanhempi
commit
90d6ed6531

+ 2 - 2
src/layout/components/Sidebar/Logo.vue

@@ -31,8 +31,8 @@ export default {
   data() {
     return {
       // title: '边缘检测',
-      title: defaultSettings.title || '边检测',
-      subTitle: '传感器检测版本 V1.0',
+      title: defaultSettings.title || '边检测',
+      subTitle: '边代检测版本 V2.0.1',
       logo: require('@/assets/index/logo.png')
     }
   }

+ 2 - 0
src/router/index.js

@@ -99,6 +99,7 @@ export const constantRoutes = [
       }
     ]
   },
+  /*
   {
     path: '/testreport',
     component: Layout,
@@ -114,6 +115,7 @@ export const constantRoutes = [
       }
     ]
   },
+  */
   {
     path: '/system',
     component: Layout,

+ 3 - 1
src/views/device/check.vue

@@ -121,7 +121,7 @@
               <!-- 采样模块 -->
               <SampleMaster104
                 v-if="isMaster104==true"
-                :key="sampleUpdate1"
+                :key="sampleUpdate104"
                 :component-parames="{
                   'deviceName':deviceGet.name,
                   'curPlanId':deviceDetail.curPlanId,
@@ -265,6 +265,7 @@ export default {
       exampleUpdate: 'example-0',
       // 采样刷新值,用于控制用例刷新
       sampleUpdate: 'sample-0',
+      sampleUpdate104: 'sample104-0',
       // 步骤刷新值,用于控制用例刷新
       checkStepsUpdate: 'checkSteps-0',
       // 点击样式
@@ -541,6 +542,7 @@ export default {
     changePlan(curPlanId) {
       this.exampleUpdate = 'example-' + new Date().getTime()
       this.sampleUpdate = 'sample-' + new Date().getTime()
+      this.sampleUpdate104 = 'sample104-' + new Date().getTime()
       this.checkStepsUpdate = 'checkSteps-' + new Date().getTime()
       // console.log('changePlan curPlanId=', curPlanId)
       // this.$set(this.deviceDetail, 'curPlanId', curPlanId)

+ 454 - 0
src/views/device/components/ModelFormCommandOpt104.vue

@@ -0,0 +1,454 @@
+<!-- 添加采样 -->
+<template>
+  <el-dialog
+    :title="comonentVar.modelTitle"
+    :visible.sync="comonentVar.dialogVisible"
+    :destroy-on-close="true"
+    :close-on-click-modal="true"
+    @close="modelOnClose"
+  >
+    <div v-if="isYaoKong" class="model-form">
+      <!-- label-width 设置了:label 和 input 就不会换行 -->
+      <el-form ref="modelYaoKongOpt" :model="modelYaoKongOpt" label-width="30px">
+        <el-form-item prop="p1">
+          <el-radio-group v-model="modelYaoKongOpt.p1">
+            <el-radio label="1">分</el-radio>
+            <el-radio label="2">合</el-radio>
+          </el-radio-group>
+        </el-form-item>
+        <el-tabs v-model="activeYaoKongTabName" type="border-card">
+          <el-tab-pane label="直控" name="tab1" style="height: 10rem; text-align: center;">
+            <div class="submit" style="padding-top: 5%;">
+              <el-button
+                style="padding: 1rem 3rem;"
+                type="primary"
+                class="dark-button"
+                size="big"
+                :disabled="yaoKongCommand.result3===0"
+                @click="commitYaokong1"
+              >确定执行</el-button>
+            </div>
+            <div style="font-weight: bold;margin: 1rem 0;">操作结果:<span :class="`yaokong_pre_result${yaoKongCommand.result3}`">{{ yaoKongCommand.result3Text }}</span></div>
+          </el-tab-pane>
+          <el-tab-pane label="选控" name="tab2" style="text-align: center;">
+            <div class="submit" style="padding-top: 1%;">
+              <el-button
+                style="padding: 1rem 3.5rem;"
+                type="primary"
+                class="dark-button"
+                size="big"
+                :disabled="yaoKongCommand.result!==-1"
+                @click="commitYaokong_PreCtrl"
+              >预控</el-button>
+            </div>
+            <div style="font-weight: bold;margin: 1rem 0;">预控结果:<span :class="`yaokong_pre_result${yaoKongCommand.result}`">{{ yaoKongCommand.resultText }}</span></div>
+            <div style="border: 1px dashed #ccc" />
+            <div class="submit" style="padding-top: 4%;">
+              <el-button
+                style="padding: 1rem 3.5rem;"
+                type="primary"
+                :class="yaoKongCommand.result===1?dark-button:''"
+                size="big"
+                :disabled="yaoKongCommand.result!==1"
+                @click="commitYaokong_RunCtrl"
+              >遥控</el-button>
+            </div>
+            <div class="submit" style="padding-top: 4%;">
+              <el-button
+                style="padding: 1rem 3.5rem;"
+                size="big"
+                :disabled="yaoKongCommand.result!==1"
+                @click="commitYaokong_Cancel"
+              >撤消</el-button>
+            </div>
+            <div style="font-weight: bold;margin: 1rem 0;">操作结果:<span :class="`yaokong_pre_result${yaoKongCommand.result2}`">{{ yaoKongCommand.result2Text }}</span></div>
+          </el-tab-pane>
+        </el-tabs>
+      </el-form>
+      <div class="buttons" style="margin-top:20px">
+        <div class="cancel">
+          <el-button class="lightButton" icon="el-icon-circle-close" size="big" @click="cancelForm">取消</el-button>
+        </div>
+      </div>
+    </div>
+    <div v-if="isYaoTiao" class="model-form">
+      <div style="text-align: center;">
+        <div class="submit" style="padding-top: 2%;">
+          <el-button
+            style="padding: 1rem 3rem;"
+            type="primary"
+            class="dark-button"
+            size="big"
+            :disabled="yaoTiaoCommnad.result!==-1"
+            @click="commitYaoTiaoSelect"
+          >发送选择命令</el-button>
+        </div>
+        <div style=" font-weight: bold;margin: 1rem 0;">操作结果:<span :class="`yaokong_pre_result${yaoTiaoCommnad.result}`">{{ yaoTiaoCommnad.resultText }}</span></div>
+        <div style="border: 1px solid #ccc;padding: 3rem 5rem;">
+          <div style="font-size: 1.2rem;">
+            <span>*设定遥调值:</span>
+            <el-input
+              v-model="modelYaoTiaoOpt.value"
+              class=""
+              clearable
+              placeholder="请输入"
+              type="number"
+              maxlength="5"
+              :disabled="yaoTiaoCommnad.result!==1"
+              style="width:180px;margin-right: 2rem;"
+            />
+          </div>
+          <div style="margin: 2rem 0;">
+            <el-button
+              style="padding: 1rem 5rem;"
+              type="primary"
+              :class="yaoTiaoCommnad.result===1 ? dark-button:''"
+              size="big"
+              :disabled="yaoTiaoCommnad.result!==1"
+              @click="commitYaoTiaoExecute"
+            >执行</el-button>
+          </div>
+          <div style=" font-weight: bold;margin: 1rem 0;">操作结果:<span :class="`yaokong_pre_result${yaoTiaoCommnad.result2}`">{{ yaoTiaoCommnad.result2Text }}</span></div>
+        </div>
+      </div>
+      <div class="buttons" style="margin-top:20px">
+        <div class="cancel">
+          <el-button class="lightButton" icon="el-icon-circle-close" size="big" @click="cancelForm">取消</el-button>
+        </div>
+      </div>
+    </div>
+  </el-dialog>
+</template>
+<script>
+import { mapGetters } from 'vuex'
+import { Message } from 'element-ui'
+import {
+  postData,
+  putData
+} from '@/api/common-action'
+export default {
+  name: 'ModelFormSample',
+  // model-parames 模态框参数
+  props: {
+    modelParames: {
+      type: Object,
+      default: function() {
+        return {}
+      }
+    },
+    sampleData: {
+      type: Array,
+      default: function() {
+        return []
+      }
+    }
+  },
+  data() {
+    return {
+      comonentVar: {
+        modelTitle: '加载中...',
+        dialogVisible: true
+      },
+      activeYaoKongTabName: 'tab1',
+      // 表单默认数据,选项数组 一定要在这里先定义,否则无法正常获取
+      modelYaoKongOpt: {
+        p1: '1'
+      },
+      modelYaoTiaoOpt: {
+        value: ''
+      },
+      isYaoKong: false,
+      isYaoTiao: false,
+      yaoKongCommand: {
+        result: -1, // 预控结果
+        resultText: '未执行', // 预控结果描述
+        result2: -1, // 选控结果
+        result2Text: '需先进行预控', // 选控结果描述
+        result3: -1, // 直控状态
+        result3Text: '未执行' // 直控状态描述
+      },
+      yaoTiaoCommnad: {
+        result: -1,
+        resultText: '未执行',
+        result2: -1,
+        result2Text: '未执行'
+      },
+      // 下拉框单独定义 避免错位
+      valueTypeOptions: [],
+      checkPointFuctionOptions: [],
+      isupdata: false
+    }
+  },
+  computed: {
+    ...mapGetters([
+      'roles'
+    ])
+  },
+  created() {
+  },
+  mounted() {
+    // 初始化
+    this.$nextTick(() => {
+      this.initFunctions()
+      console.log('modelParames:', this.modelParames)
+    })
+  },
+  methods: {
+    // 初始化
+    async initFunctions() {
+      //  初始化 值类型 下拉框选项
+      this.valueTypeOptions = JSON.parse(localStorage.getItem('valueTypeOptions'))
+      // 因为 this.modelForm 在前面已经赋值过了,所以这里必须使用 set 的方式赋值,否则无法更新选项
+
+      //  初始化 功能码 下拉框选项
+      this.checkPointFuctionOptions = JSON.parse(localStorage.getItem('checkPointFuctionOptions'))
+      if (this.modelParames.modelFormData.range === 'COIL_STATUS') {
+        this.comonentVar.modelTitle = `遥控操作 ${this.modelParames.deviceName}-${this.modelParames.modelFormData.name}`
+        this.isYaoKong = true
+        this.isYaoTiao = false
+      } else {
+        this.comonentVar.modelTitle = `遥调操作 ${this.modelParames.deviceName}-${this.modelParames.modelFormData.name}`
+        this.isYaoKong = false
+        this.isYaoTiao = true
+      }
+      if (this.modelParames.modelFormData) {
+        if (this.modelParames.modelFormData.id > 0) {
+          //
+        }
+      }
+    },
+    commitYaokong1() {
+      // 直控执行
+      this.yaoKongCommand.result3 = 0
+      this.yaoKongCommand.result3Text = '进行中...'
+      var urlencoded = new URLSearchParams()
+      urlencoded.append('stage', 'execute')
+      urlencoded.append('tag', this.modelParames.modelFormData.iec104)
+      urlencoded.append('v1', this.modelYaoKongOpt.p1 === '1')
+      postData(`/iec104/master/${this.modelParames.curPlanId}/ctrl`, urlencoded).then((res) => {
+        this.yaoKongCommand.result3 = 1
+        this.yaoKongCommand.result3Text = '执行成功'
+      }).catch((_err) => {
+        this.yaoKongCommand.result3 = 2
+        this.yaoKongCommand.result3Text = '执行失败!'
+        const that = this
+        setTimeout(function() {
+          that.yaoKongCommand.result3 = -1
+          that.yaoKongCommand.result3Text = '未执行'
+        }, 3000)
+      })
+    },
+    commitYaokong_PreCtrl() {
+      this.yaoKongCommand.result2 = -1
+      this.yaoKongCommand.result2Text = '需先进行预控'
+      // 预控
+      this.yaoKongCommand.result = 0 // 禁用本身,禁用遥控
+      this.yaoKongCommand.resultText = '进行中...'
+      var urlencoded = new URLSearchParams()
+      urlencoded.append('stage', 'select')
+      urlencoded.append('tag', this.modelParames.modelFormData.iec104)
+      urlencoded.append('v1', this.modelYaoKongOpt.p1 === '1')
+      postData(`/iec104/master/${this.modelParames.curPlanId}/ctrl`, urlencoded).then((res) => {
+        this.yaoKongCommand.result = 1 // 可进行遥控
+        this.yaoKongCommand.resultText = '执行成功'
+      }).catch((_err) => {
+        this.yaoKongCommand.result = 2
+        this.yaoKongCommand.resultText = '执行失败!'
+        const that = this
+        setTimeout(function() {
+          that.yaoKongCommand.result = -1
+          that.yaoKongCommand.resultText = '未执行'
+        }, 3000)
+      })
+    },
+    commitYaokong_RunCtrl() {
+      // 遥控执行
+      this.yaoKongCommand.result2 = 0 // 禁用本身
+      this.yaoKongCommand.result2Text = '进行中...'
+      this.yaoKongCommand.result = -1 // 禁用预控及遥控
+      var urlencoded = new URLSearchParams()
+      urlencoded.append('stage', 'select')
+      urlencoded.append('tag', this.modelParames.modelFormData.iec104)
+      urlencoded.append('v1', this.modelYaoKongOpt.p1 === '1')
+      postData(`/iec104/master/${this.modelParames.curPlanId}/ctrl`, urlencoded).then((res) => {
+        // 操作成功
+        this.yaoKongCommand.result = -1
+        this.yaoKongCommand.resultText = '未执行'
+        this.yaoKongCommand.result2 = 1
+        this.yaoKongCommand.result2Text = '执行成功!'
+      }).catch((_err) => {
+        this.yaoKongCommand.result2 = 2 // 恢复本身
+        this.yaoKongCommand.result2Text = '执行失败!'
+        this.yaoKongCommand.result = -1 // 恢复预控
+        this.yaoKongCommand.resultText = '未执行'
+      })
+    },
+    commitYaokong_Cancel() {
+      // 遥控撤消
+      this.yaoKongCommand.result2 = 0
+      this.yaoKongCommand.result2Text = '进行中...'
+      // 操作成功
+      this.yaoKongCommand.result = -1
+      this.yaoKongCommand.resultText = '未执行'
+      this.yaoKongCommand.result2 = -1
+      this.yaoKongCommand.result2Text = '需先进行预控'
+    },
+    commitYaoTiaoSelect() {
+      // 遥调选择
+      this.yaoTiaoCommnad.result = -1 // 禁用本身
+      this.yaoTiaoCommnad.resultText = '进行中...'
+      this.yaoTiaoCommnad.result2 = -1 // 禁用遥控
+      this.modelYaoTiaoOpt.value = ''
+      var urlencoded = new URLSearchParams()
+      urlencoded.append('tag', this.modelParames.modelFormData.iec104)
+      postData(`/iec104/master/${this.modelParames.curPlanId}/normalized`, urlencoded).then((res) => {
+        // 操作成功
+        this.yaoTiaoCommnad.result2 = -1
+        this.yaoTiaoCommnad.result2Text = '未执行'
+        this.yaoTiaoCommnad.result = 1
+        this.yaoTiaoCommnad.resultText = '执行成功!'
+      }).catch((_err) => {
+        // 禁用 选择 按钮
+        this.yaoTiaoCommnad.result = 2
+        this.yaoTiaoCommnad.resultText = '执行失败!'
+        // 禁用 执行 按钮
+        this.yaoTiaoCommnad.result2 = -1
+        this.yaoTiaoCommnad.result2Text = '未执行'
+        const that = this
+        setTimeout(function() {
+          // 打开 选择 按钮
+          that.yaoTiaoCommnad.result = -1
+          that.yaoTiaoCommnad.resultText = '未执行'
+        }, 3000)
+      })
+    },
+    commitYaoTiaoExecute() {
+      // 遥调执行
+      const v = this.modelYaoTiaoOpt.value.replace(/ /gi, '')
+      if (v === '') {
+        Message({
+          message: '遥调值不能为空!',
+          type: 'error',
+          duration: 3 * 1000,
+          offset: window.screen.height / 3
+        })
+        return
+      }
+      if (isNaN(v)) {
+        Message({
+          message: '遥调值只能为数字!',
+          type: 'error',
+          duration: 3 * 1000,
+          offset: window.screen.height / 3
+        })
+        return
+      }
+      this.yaoTiaoCommnad.result2 = 0
+      this.yaoTiaoCommnad.result2Text = '执行中'
+      this.yaoTiaoCommnad.result = -1
+      var urlencoded = new URLSearchParams()
+      urlencoded.append('tag', this.modelParames.modelFormData.iec104)
+      urlencoded.append('value', v)
+      postData(`/iec104/master/${this.modelParames.curPlanId}/normalized`, urlencoded).then((res) => {
+        // 操作成功
+        this.yaoTiaoCommnad.result2 = 1
+        this.yaoTiaoCommnad.result2Text = '执行成功!'
+      }).catch((_err) => {
+        // 禁用 执行 按钮
+        this.yaoTiaoCommnad.result2 = 2
+        this.yaoTiaoCommnad.result2Text = '执行失败!'
+        const that = this
+        setTimeout(function() {
+          // 打开 选择 按钮
+          that.yaoTiaoCommnad.result = -1
+          that.yaoTiaoCommnad.resultText = '未执行'
+        }, 3000)
+      })
+    },
+    // 关闭model时执行
+    modelOnClose() {
+      this.$emit('toggleModel', 'ModelFormCommandOpt104', false)
+    },
+    // 表单提交按钮
+    submitForm() {
+      if (this.isupdata === false) {
+        // console.log('this.modelForm', this.modelForm)
+        // console.log('sampleData', this.sampleData)
+        const hasThis = this.sampleData.some(item => item.range === this.modelForm.range && item.offset === parseInt(this.modelForm.offset))
+        if (hasThis) {
+          this.$message({
+            type: 'error',
+            message: '该采样值已存在',
+            offset: window.screen.height / 3
+          })
+          return
+        }
+      }
+      let checkForm = false
+      this.$refs.modelForm.validate(f => { checkForm = f })
+      if (!checkForm) {
+        return
+      }
+
+      delete this.modelForm.code
+      const putUrl = `/test/plan/${this.modelParames.curPlanId}/point`
+      // const putUrl = `/test/plan/${this.modelForm.id}/point`
+      putData(putUrl, this.modelForm).then(res => {
+        // 保存表单
+        this.comonentVar.dialogVisible = false
+      })
+    },
+    // 表单取消按钮
+    cancelForm() {
+      this.modelForm = {}
+      this.comonentVar.dialogVisible = false
+    }
+  }
+}
+</script>
+<style lang="scss">
+.steps-setting {
+  padding: 10px 0;
+  label {
+    padding: 0;
+  }
+}
+</style>
+<style lang="scss" scoped>
+// 表单按钮样式
+
+.yaokong_pre_result2{
+  color: red;
+}
+.yaokong_pre_result0{
+  color: rgb(115, 115, 207);
+}
+.yaokong_pre_result1{
+  color: rgb(9, 240, 86);
+}
+.buttons {
+  display: flex;
+  justify-content: right;
+  padding-bottom: 20px;
+
+  .submit {
+    padding-left: 20px;
+  }
+}
+.model-form {
+  .setting-input {
+    width: 100px;
+  }
+}
+// 按钮公用样式
+.dark-button {
+  background-color: #00706B;
+  border: 1px #00706B solid;
+  color: #fff;
+}
+
+.light-button {
+  color: #00706B;
+  border: 1px #00706B solid;
+}
+</style>

+ 274 - 0
src/views/device/components/ModelFormSample_master104.vue

@@ -0,0 +1,274 @@
+<!-- 添加采样 -->
+<template>
+  <el-dialog
+    :title="comonentVar.modelTitle"
+    :visible.sync="comonentVar.dialogVisible"
+    :destroy-on-close="true"
+    :close-on-click-modal="true"
+    @close="modelOnClose"
+  >
+    <div class="model-form">
+      <!-- label-width 设置了:label 和 input 就不会换行 -->
+      <el-form ref="modelForm" :model="modelForm" label-width="100px" :rules="rules">
+        <el-form-item label="测点名称" prop="name" label-width="110px">
+          <el-input v-model="modelForm.name" autocomplete="off" maxlength="32" placeholder="请输入测点名称" />
+        </el-form-item>
+        <el-form-item label="设备名称" prop="groupingName" label-width="110px">
+          <el-input v-model="modelForm.groupingName" maxlength="20" placeholder="请输入设备名称" />
+        </el-form-item>
+        <el-form-item class="modelInput" label="点号类型" prop="range" label-width="110px">
+          <template slot-scope="scope">
+            <el-select
+              :key="toString(scope.row)"
+              v-model="modelForm.range"
+              clearable
+              placeholder=""
+            >
+              <el-option
+                v-for="item in checkPointFuctionOptions"
+                :key="item.id"
+                :label="item.name"
+                :value="item.id"
+              />
+            </el-select>
+          </template>
+        </el-form-item>
+        <el-form-item label="点号" prop="offset" label-width="110px">
+          <el-input v-model="modelForm.offset" type="number" maxlength="32" placeholder="请输入点号" />
+        </el-form-item>
+        <el-form-item label="关联遥信点号 (仅遥控可关联)" label-width="110px">
+          <el-input v-if="modelForm.range=='COIL_STATUS'" v-model="modelForm.relationTag" maxlength="6" placeholder="请输入遥信点号" />
+          <el-input v-else v-model="modelForm.relationTag" disabled />
+        </el-form-item>
+        <el-form-item label="关联遥测点号 (仅遥调可关联)" label-width="110px">
+          <el-input v-if="modelForm.range=='HOLDING_REGISTER'" v-model="modelForm.relationTag" maxlength="6" placeholder="请输入遥测点号" />
+          <el-input v-else v-model="modelForm.relationTag" disabled />
+        </el-form-item>
+        <el-form-item label="倍率" label-width="110px">
+          <el-input v-model="modelForm.scaling" maxlength="32" />
+        </el-form-item>
+        <el-form-item label="偏移量" label-width="110px">
+          <el-input v-model="modelForm.adjust" maxlength="32" />
+        </el-form-item>
+      </el-form>
+      <div class="buttons">
+        <div class="cancel">
+          <el-button class="lightButton" icon="el-icon-circle-close" size="small" @click="cancelForm">取消</el-button>
+        </div>
+        <div class="submit">
+          <el-button
+            type="primary"
+            icon="el-icon-circle-check"
+            class="dark-button"
+            size="small"
+            @click="submitForm"
+          >保存</el-button>
+        </div>
+      </div>
+    </div>
+  </el-dialog>
+</template>
+<script>
+import { mapGetters } from 'vuex'
+import {
+  httpGet,
+  putData
+} from '@/api/common-action'
+export default {
+  name: 'ModelFormSample',
+  // model-parames 模态框参数
+  props: {
+    modelParames: {
+      type: Object,
+      default: function() {
+        return {}
+      }
+    },
+    sampleData: {
+      type: Array,
+      default: function() {
+        return []
+      }
+    }
+  },
+  data() {
+    return {
+      rules: {
+        name: [{ required: true, message: '测点名称不能为空' }],
+        range: [{ required: true, message: '点号类型不能为空' }],
+        offset: [{ required: true, message: '点号不能为空' }],
+        groupingName: [{ required: true, message: '设备名称不能为空' }]
+      },
+      comonentVar: {
+        modelTitle: '加载中...',
+        dialogVisible: true
+      },
+      // 表单默认数据,选项数组 一定要在这里先定义,否则无法正常获取
+      modelForm: {
+        name: ''
+      },
+      // 下拉框单独定义 避免错位
+      valueTypeOptions: [],
+      checkPointFuctionOptions: [],
+      isupdata: false
+    }
+  },
+  computed: {
+    ...mapGetters([
+      'roles'
+    ])
+  },
+  created() {
+  },
+  mounted() {
+    // 初始化
+    this.$nextTick(() => {
+      this.initFunctions()
+      // console.log('sampleData', this.sampleData)
+    })
+  },
+  methods: {
+    // 初始化
+    async initFunctions() {
+      //  初始化 值类型 下拉框选项
+      this.valueTypeOptions = JSON.parse(localStorage.getItem('valueTypeOptions'))
+      // 因为 this.modelForm 在前面已经赋值过了,所以这里必须使用 set 的方式赋值,否则无法更新选项
+
+      //  初始化 功能码 下拉框选项
+      this.checkPointFuctionOptions = JSON.parse(localStorage.getItem('checkPointFuctionOptions'))
+
+      this.comonentVar.modelTitle = `[${this.modelParames.deviceName}]-添加采样`
+      if (this.modelParames.modelFormData) {
+        if (this.modelParames.modelFormData.id > 0) {
+          this.comonentVar.modelTitle = `[${this.modelParames.deviceName}]-编辑采样`
+          // #TODO: 编辑状态 锁住 寄存器范围和寄存器地址
+          this.isupdata = true
+          // 如果 寄存器范围 是 开关量 输出/输入 则 值类型 固定为 开关
+          // 如果 寄存器范围 是 其它值类型 默认为 16位有符号数
+          // this.$message({
+          //   message: `编辑功能还未准备好`,
+          //   type: 'error',
+          //   offset: window.screen.height / 3
+          // })
+          // 如果是编辑,从 接口 单测点查询 再获取一次数据
+          // /product/model/:product/:tag
+          // 例 :product 1100000645
+          // 例 :tag 6386 tag id 为采样列表序号id
+          const getUrl = `/product/model/${this.modelParames.productId}/${this.modelParames.modelFormData.iec104}`
+
+          httpGet(getUrl).then(res => {
+            this.modelForm = res
+            if (res.swapByte === 1 || res.swapByte === true) {
+              this.modelForm.swapByte = true
+            } else {
+              this.modelForm.swapByte = false
+            }
+            if (res.swapWord === 1 || res.swapWord === true) {
+              this.modelForm.swapWord = true
+            } else {
+              this.modelForm.swapWord = false
+            }
+          })
+          // this.modelForm = this.modelParames.modelFormData
+          // console.log('mounted this.modelForm= ', this.modelForm)
+        }
+      }
+
+      // 添加 采样 数据示例:
+      // {
+      //   "id": 0,
+      //   "name": "my_tag",
+      //   "dataType": "BOOL",
+      //   "range": "COIL_STATUS",
+      //   "offset": 0,
+      //   "scaling": 1,
+      //   "adjust": 0,
+      //   "swapByte": false,
+      //   "swapWord": false,
+      //   "pollingCycle": 0
+      // }
+
+      // this.$forceUpdate()
+      // httpGet(getUrl).then(res => {
+      //   delete res.code
+      //   console.log('获取测试规则 res= ', res)
+      //   this.modelForm.ruleOptions = res
+      // })
+    },
+    // 关闭model时执行
+    modelOnClose() {
+      this.$emit('toggleModel', 'ModelFormSample', false)
+    },
+    // 表单提交按钮
+    submitForm() {
+      if (this.isupdata === false) {
+        // console.log('this.modelForm', this.modelForm)
+        // console.log('sampleData', this.sampleData)
+        const hasThis = this.sampleData.some(item => item.range === this.modelForm.range && item.offset === parseInt(this.modelForm.offset))
+        if (hasThis) {
+          this.$message({
+            type: 'error',
+            message: '该采样值已存在',
+            offset: window.screen.height / 3
+          })
+          return
+        }
+      }
+      let checkForm = false
+      this.$refs.modelForm.validate(f => { checkForm = f })
+      if (!checkForm) {
+        return
+      }
+
+      delete this.modelForm.code
+      const putUrl = `/test/plan/${this.modelParames.curPlanId}/point`
+      // const putUrl = `/test/plan/${this.modelForm.id}/point`
+      putData(putUrl, this.modelForm).then(res => {
+        // 保存表单
+        this.comonentVar.dialogVisible = false
+      })
+    },
+    // 表单取消按钮
+    cancelForm() {
+      this.modelForm = {}
+      this.comonentVar.dialogVisible = false
+    }
+  }
+}
+</script>
+<style lang="scss">
+.steps-setting {
+  padding: 10px 0;
+  label {
+    padding: 0;
+  }
+}
+</style>
+<style lang="scss" scoped>
+// 表单按钮样式
+.buttons {
+  display: flex;
+  justify-content: right;
+  padding-bottom: 20px;
+
+  .submit {
+    padding-left: 20px;
+  }
+}
+.model-form {
+  .setting-input {
+    width: 100px;
+  }
+}
+// 按钮公用样式
+.dark-button {
+  background-color: #00706B;
+  border: 1px #00706B solid;
+  color: #fff;
+}
+
+.light-button {
+  color: #00706B;
+  border: 1px #00706B solid;
+}
+</style>

+ 3 - 1
src/views/device/components/ModelReport_master104.vue

@@ -1,6 +1,6 @@
 <!-- 设备报文 -->
 <template>
-  <div class="table-container" style="padding-top:20px;">
+  <div class="table-container" style="padding-top:20px;float: left;width: 100%;">
     <div class="buttons">
       <div class="title-main" style="line-height: 2rem;">报文</div>
       <div class="left">
@@ -328,10 +328,12 @@ export default {
     padding-bottom: 20px;
     .export{
       float: left;
+      width: 70px;
     }
     .clear,.refresh {
       padding-left: 20px;
       float: left;
+      width: 90px;
     }
   }
 }

+ 211 - 22
src/views/device/tables/Sample_master104.vue

@@ -14,7 +14,7 @@
 微信聊天记录 边缘代理2开发沟通 - 2023-06-27 采样表格无法分页,数据在内存中
 -->
 <template>
-  <div class="table-container">
+  <div class="table-container" style="padding-top:20px;float: left;width: 100%;">
     <!-- 采样模块 -->
     <div class="device-values">
       <!-- 采样 -->
@@ -81,15 +81,59 @@
           </div>
         </div>
       </div>
+      <!-- 查询栏 -->
+      <div class="table-container" height="auto" style="float: left;width: 100%;margin-bottom: 1rem;">
+        <span class="title">测点类型:</span>
+        <el-select v-model="querydata.pointtype" size="mini" clearable placeholder="请选择">
+          <el-option
+            v-for="item in pointTypeList"
+            :key="item.id"
+            :label="item.name"
+            :value="item.id"
+          />
+        </el-select>
+        <span class="title" style="margin-left: 2rem;">测点:</span>
+        <el-input
+          v-model="querydata.point"
+          class=""
+          clearable
+          placeholder="请输入测点号或名称"
+          type="text"
+          style="width:180px;margin-right: 2rem;"
+        />
+        <el-button
+          icon="el-icon-s-grid"
+          size="mini"
+          type="primary"
+          @click="query(false)"
+        >查询</el-button>
+      </div>
       <!-- 采样卡片 -->
-      <div v-if="showCard == true" class="table-container">
+      <div v-if="showCard == true" class="table-container" height="auto" style="float: left;width: 100%;">
         <div v-for="(item, index) in carddata" :key="index">
-          <div class="card">{{ item }}</div>
+          <div style="float: left;width: 100%;font-weight: bold;">{{ index }}</div>
+          <div style="float: left;width: 100%;">
+            <div v-for="(c, index2) in item" v-show="c.isshow" :key="index2" :class=" typeof(c.relationTag)!=='undefined' && c.relationTag!=='' ? 'card card_opt':'card'">
+              <div class="card-title">{{ c.occur || '-' }}<span v-if="typeof(c.relationTag)!=='undefined' && c.relationTag!==''" class="card_opt_btn" @click="commandOpt(c)">操作</span></div>
+              <div style="position: relative;">
+                <template v-if="typeof(c.relationTag)!=='undefined' && c.relationTag!==''">
+                  <span class="card-label" style="margin-top: 5%;" :title="`类型:${c.rangeName} 点号:${c.offset}`"><li>{{ c.name }}</li></span>
+                  <div class="card-label-linkline" />
+                  <span class="card-label" style="margin-top: 20%;" :title="`类型:${c.rangeName} 点号:${c.offset}`"><li>{{ c.name }}</li></span>
+                </template>
+                <template v-else>
+                  <span class="card-label" :title="`类型:${c.rangeName} 点号:${c.offset}`">{{ c.name }}</span>
+                </template>
+                <span v-if="c.range=='COIL_STATUS' || c.range=='INPUT_STATUS'" :class="`value_${c.value}`">{{ c.value }}</span>
+                <span v-else class="value_nubmer">{{ c.value }}</span>
+              </div>
+            </div>
+          </div>
         </div>
       </div>
       <!-- 采样表格 -->
-      <div v-if="showCard == false" class="table-container">
-        <el-table :key="tableIsUpdate" v-loading="vLoading" :data="tableData" stripe max-height="300">
+      <div v-if="showCard == false" class="table-container" style="float: left;width: 100%;">
+        <el-table :key="tableIsUpdate" v-loading="vLoading" :data="tableData.filter((r) => r.isshow)" stripe max-height="300">
           <el-table-column prop="iec104" label="104点号" width="100px" />
           <el-table-column prop="name" label="测点名称" />
           <el-table-column prop="groupingName" label="设备名称" />
@@ -104,20 +148,20 @@
           <el-table-column fixed="right" label="操作" width="250px">
             <template slot-scope="scope">
               <el-button
-                v-if="scope.row.range=='COIL_STATUS' && scope.row.relationTag!=''"
+                v-if="scope.row.range=='COIL_STATUS' && typeof(scope.row.relationTag)!=='undefined' && scope.row.relationTag!=''"
                 class="table-act"
                 type="text"
                 icon="el-icon-setting"
                 size="small"
-                @click="editRow(scope.$index)"
+                @click="commandOpt(scope.row)"
               >遥控操作</el-button>
               <el-button
-                v-if="scope.row.range=='COIL_SHOLDING_REGISTERTATUS' && scope.row.relationTag!=''"
+                v-if="scope.row.range=='HOLDING_REGISTER' && typeof(scope.row.relationTag)!=='undefined' && scope.row.relationTag!=''"
                 class="table-act"
                 type="text"
-                icon="el-icon-edit"
+                icon="el-icon-setting"
                 size="small"
-                @click="editRow(scope.$index)"
+                @click="commandOpt(scope.row)"
               >遥调操作</el-button>
               <el-button
                 class="table-act"
@@ -165,7 +209,7 @@
       -->
     </div>
     <!-- 添加/编辑 采样 model -->
-    <ModelFormSample
+    <ModelFormSample_master104
       v-if="showModelForm"
       :model-parames="{
         'curPlanId':componentParames.curPlanId,
@@ -177,6 +221,18 @@
       :sample-data="tableData"
       @toggleModel="closeModel"
     />
+    <!-- 遥控遥调操作 model -->
+    <ModelFormCommandOpt104
+      v-if="showCommandOpt104ModelForm"
+      :model-parames="{
+        'curPlanId':componentParames.curPlanId,
+        'productId':componentParames.productId,
+        'deviceName':componentParames.deviceName,
+        'curSuiteId':componentParames.curSuiteId,
+        'modelFormData':commandOpt104FormData
+      }"
+      @toggleModel="closeModel"
+    />
     <!-- #TODO: 以后做成公用 dialog ,不再使用 toggleModel 关闭 dialog -->
     <el-dialog
       :visible="showEchart"
@@ -202,12 +258,12 @@ import {
   delRecord,
   download
 } from '@/api/common-action'
-// 分页
-// import Pagination from '@/components/Pagination'
+import { dictOptions } from '@/api/dict'
 // 显示采样曲线
 import ModelSampleEchart from '../components/ModelSampleEchart'
 // 添加/编辑 采样
-import ModelFormSample from '../components/ModelFormSample'
+import ModelFormSample_master104 from '../components/ModelFormSample_master104'
+import ModelFormCommandOpt104 from '../components/ModelFormCommandOpt104'
 // 导入总线
 import { EventBus } from '@/main.js'
 export default {
@@ -215,7 +271,8 @@ export default {
   components: {
     // Pagination,
     ModelSampleEchart,
-    ModelFormSample
+    ModelFormSample_master104,
+    ModelFormCommandOpt104
   },
   props: {
     componentParames: {
@@ -233,12 +290,19 @@ export default {
       tableModelTag: 'default',
       carddata: [],
       tableData: [],
+      pointTypeList: [], // 测点类型列表
+      querydata: {
+        pointtype: '',
+        point: ''
+      },
+      commandOpt104FormData: {},
       // 采样
       tablePrames: {
         pageLimit: 5,
         paginationNumber: 1,
         paginationTotalElements: 1
       },
+      showCommandOpt104ModelForm: false,
       // 是否显示 添加/编辑 采样
       showModelForm: false,
       // 是否显示曲线
@@ -290,6 +354,8 @@ export default {
     },
     //
     async initFunctions() {
+      const res = await dictOptions('131')
+      this.pointTypeList = res
       // 所有需要加载时初始化的函数都放这里
       // 获取用例列表数据 ,不传分页信息,即采集默认分页获取第一页数据
       this.getTableData()
@@ -311,13 +377,39 @@ export default {
         document.body.removeChild(link)
       })
     },
+    query() {
+      // 采样值查询
+      for (const item in this.carddata) {
+        this.carddata[item].forEach(element => {
+          if (this.querydata.point === '' && this.querydata.pointtype === '') {
+            element.isshow = true
+          } else {
+            let isshowflag = false
+            if (this.querydata.pointtype !== '' && element.range === this.querydata.pointtype) {
+              isshowflag = true
+            }
+            if (this.querydata.point !== '' && (element.name.toLocaleUpperCase().indexOf(this.querydata.point.toLocaleUpperCase()) > -1) || (element.offset + '') === this.querydata.point) {
+              isshowflag = isshowflag || true
+            }
+            element.isshow = isshowflag
+          }
+        })
+      }
+      this.$forceUpdate()
+    },
+    commandOpt(row) {
+      // 遥控、遥调操作
+      if (row == null) return
+      this.commandOpt104FormData = row
+      this.showCommandOpt104ModelForm = true
+    },
     // 格式化bool值
     formatBool(val) {
       const rs = val.not.toString()
       return rs
     },
     // 格式化 测试状态
-    formatState(val) {
+    formatState() {
       return '未获取'
     },
 
@@ -328,13 +420,25 @@ export default {
         page: 0,
         limit: this.tablePrames.pageLimit
       }
-      // 获取表格数据
+      // 获取表格数据-测点列表
       // 接口 采样数据查询 /test/execute/:runner/values
       // :runner 例:3200000208
       httpGet(`/test/execute/${this.componentParames.curPlanId}/values?page=${pageObj.page}&size=${pageObj.limit}`).then((response) => {
         this.vLoading = false
         delete response.code
         this.tableData = response
+        const groupingNamelist = {}
+        for (let index = 0; index < response.length; index++) {
+          const element = response[index]
+          element['isshow'] = true // 默认为都显示。可通过查询条件更改其值
+          if (element.groupingName == null || element.groupingName === '') element.groupingName = '未分组'
+          if (groupingNamelist[element.groupingName]) {
+            groupingNamelist[element.groupingName].push(element)
+          } else {
+            groupingNamelist[element.groupingName] = [element]
+          }
+        }
+        this.carddata = groupingNamelist
         this.tablePrames.paginationTotalElements = response.totalElements * 1
         // this.requestData.deviceProtocolOptions = response
         // 刷新一次状态
@@ -367,7 +471,7 @@ export default {
 
           const delUrl = `/product/model/${this.componentParames.productId}/${this.tableData[index].id}`
           // console.log('delRow delUrl=', delUrl)
-          delRecord(delUrl).then(res => {
+          delRecord(delUrl).then(() => {
             this.$message({
               message: '删除成功',
               type: 'success',
@@ -393,8 +497,8 @@ export default {
       var getUrl = `/test/execute/${this.componentParames.curPlanId}/models`
       httpGet(getUrl).then(res => {
         if (this.tableData.length > 0) {
-          this.tableData.forEach(item => {
-            res.forEach(ritem => {
+          res.forEach(ritem => {
+            this.tableData.forEach(item => {
               if (item.id === ritem.id) {
                 item.value = ritem.value
                 item.occur = ritem.occur
@@ -402,7 +506,16 @@ export default {
                 this.tableIsUpdate = 'sample-' + new Date().getTime()
               }
             })
+            this.carddata.forEach((k, item) => {
+              item.forEach(item2 => {
+                if (item2.id === ritem.id) {
+                  item2.value = ritem.value
+                  item2.occur = ritem.occur
+                }
+              })
+            })
           })
+          this.$forceUpdate()
         }
         if (isHand != null && isHand) {
           this.$message({
@@ -438,6 +551,9 @@ export default {
         case 'ModelSampleEchart':
           this.showEchart = modelShow
           break
+        case 'ModelFormCommandOpt104':
+          this.showCommandOpt104ModelForm = false
+          break
         default:
           console.log('未获取到 modelName')
           break
@@ -492,14 +608,87 @@ export default {
 <style lang="scss" scoped>
 .card{
   float: left;
-  width: 10rem;
-  height: 5rem;
+  width: 13rem;
+  height: 7rem;
   border-radius: 5px;
   border: 1px solid #ccc;
+  margin: 1rem;
+  font-size: 0.8rem;
 }
 .card_opt{
   background-color: #ededed;
 }
+.card_opt_btn{
+  float: right;
+  cursor: pointer;
+}
+.card-title{
+  border-bottom: 1px dotted #ccc;
+  padding: 0.8rem 0.5rem;
+}
+.card-label{
+    margin-top: 11.5%;
+    top: 0;
+    left: 0;
+    width: 65%;
+    font-size: 1rem;
+    padding-left: 5%;
+    position: absolute;
+}
+.card-label>li{
+  white-space: nowrap;
+  text-overflow: ellipsis;
+  overflow: hidden;
+}
+.card-label-linkline{
+    margin-left: 12.5px;
+    height: 31px;
+    width: 1px;
+    left: 0;
+    margin-top: 20px;
+    top: 0;
+    position: absolute;
+    background: #a97272;
+    z-index: 1;
+}
+.value_0{
+    right:0;
+    top: 0;
+    width: 20%;
+    margin: 10px;
+    text-align: center;
+    border-radius: 2rem;
+    border: 1px solid #fff;
+    padding: 6px 0;
+    background: rgb(224, 156, 156);
+    color: #fff;
+    font-size: 1.5rem;
+    position: absolute;
+}
+.value_1{
+  right:0;
+    top: 0;
+    width: 20%;
+    margin: 10px;
+    text-align: center;
+    border-radius: 2rem;
+    border: 1px solid #fff;
+    padding: 6px 0;
+    background: red;
+    color: #fff;
+    font-size: 1.5rem;
+    position: absolute;
+}
+.value_nubmer{
+  right:0;
+    top: 0;
+    width: 35%;
+    margin: 10px;
+    text-align: right;
+    padding: 13px 0;
+    font-size: 1.2rem;
+    position: absolute;
+}
 .title-container {
   display: flex;
   justify-content: space-between;

+ 1 - 1
src/views/plan/components/PlanCheckPoint.vue

@@ -73,7 +73,7 @@
             />
           </template>
         </el-table-column>
-        <el-table-column prop="modbus" label="modbus点号" width="100">
+        <el-table-column prop="modbus" label="modbus点号" width="110">
           <!-- rs.offset -->
           <template slot-scope="scope">
             <el-input

+ 1 - 1
src/views/plan/components/PlanCheckPoint_master104.vue

@@ -209,7 +209,7 @@
         <el-form-item label="*设备名称" :label-width="formLabelWidth">
           <el-input v-model="form.groupingName" maxlength="20" placeholder="请输入设备名称" />
         </el-form-item>
-        <el-form-item class="modelInput" label="*点类型" :label-width="formLabelWidth">
+        <el-form-item class="modelInput" label="*点类型" :label-width="formLabelWidth">
           <template slot-scope="scope">
             <el-select
               :key="toString(scope.row)"