lishuangjiang@potevio.com 2 жил өмнө
parent
commit
2c471caa29

+ 1 - 1
.env.production

@@ -5,7 +5,7 @@ VITE_APP_TITLE = 物联网平台
 VITE_APP_ENV = 'production'
 
 # 应用访问路径 例如使用前缀 /admin/
-VITE_APP_CONTEXT_PATH = '/'
+VITE_APP_CONTEXT_PATH = '/iot/'
 
 # 监控地址
 VITE_APP_MONITRO_ADMIN = '/admin/applications'

+ 3 - 1
src/api/monitor/online/index.ts

@@ -1,6 +1,7 @@
 import request from '@/utils/request'
 import { OnlineQuery, OnlineVO } from './types'
 import { AxiosPromise } from 'axios'
+import {parseStrEmpty} from "@/utils/ruoyi";
 
 // 查询在线用户列表
 export function list(query: OnlineQuery): AxiosPromise<OnlineVO[]> {
@@ -14,7 +15,8 @@ export function list(query: OnlineQuery): AxiosPromise<OnlineVO[]> {
 // 强退用户
 export function forceLogout(tokenId: string) {
   return request({
-    url: '/monitor/online' + tokenId,
+    url: '/monitor/online/kickoutByTokenValue',
     method: 'post',
+    data: parseStrEmpty(tokenId),
   })
 }

+ 3 - 0
src/views/index.vue

@@ -132,6 +132,9 @@ const state = reactive({
 })
 // 设备数量统计图表
 const setBar1 = (dataObj:object) => {
+  if (typeof dataObj === 'object' && Object.keys(dataObj).length === 0) {
+    dataObj = {'设备总数': 0}
+  }
   const keys = Object.keys(dataObj)
   const values = Object.values(dataObj)
   let totlaNum = 0

+ 6 - 4
src/views/iot/alarm/config.vue

@@ -3,6 +3,7 @@
     ref="crudRef"
     :data="data"
     :column="column"
+    :addBtn = "true"
     :table-props="{
         selection: false,
       }"
@@ -21,7 +22,8 @@ import { IColumn } from '@/components/common/types/tableCommon'
 import { getConfigList, saveConfig, deleteConfig } from './api/alarm.api'
 import { getRuleList } from '../ruleEngine/api/rule.api'
 import YtCrud from '@/components/common/yt-crud.vue'
-import { getConfigAll } from '../channel/api/configs.api'
+// import { getConfigAll } from '../channel/api/configs.api'
+import { getTemplatesList } from '../channel/api/templates.api'
 
 const state = reactive({
   page: {
@@ -66,7 +68,7 @@ const column = ref<IColumn[]>([{
   label: '消息模板',
   key: 'messageTemplateId',
   type: 'select',
-  tableWidth: 120,
+  tableWidth: 360,
   componentProps: {
     labelAlias: 'title',
     valueAlias: 'id',
@@ -116,10 +118,10 @@ const getDict = async () => {
     }
   })
 
-  getConfigAll().then(res => {
+  getTemplatesList().then(res => {
     column.value.forEach(item => {
     if (item.key === 'messageTemplateId') {
-      item.componentProps.options = res.data
+      item.componentProps.options = res.data.rows
     }
   })
   })

+ 2 - 1
src/views/iot/alarm/list.vue

@@ -58,6 +58,7 @@ const column: IColumn[] = [{
   label: '是否已读',
   key: 'read',
   tableWidth: 120,
+  hide: true,
   type: 'switch',
 }, {
   label: '告警时间',
@@ -67,7 +68,7 @@ const column: IColumn[] = [{
 }, {
   label: '告警内容',
   key: 'details',
-  tableWidth: 250,
+  tableWidth: 360,
   componentProps: {
     type: 'textarea',
     rows: 4,

+ 7 - 1
src/views/iot/channel/config.vue

@@ -3,6 +3,7 @@
     ref="crudRef"
     :data="data"
     :column="column"
+    :addBtn="true"
     v-model:page="state.page"
     v-model:query="state.query"
     :total="state.total"
@@ -165,7 +166,12 @@ getChannel()
 // 保存数据
 const onSave = ({type, data, cancel}: any) => {
   state.loading = true
-  addConfig(toRaw(data)).then(res => {
+  let data2: any = {}
+  for (let p in data) {
+    data2[p] = data[p]
+  }
+  data2['param'] = JSON.stringify(data['param'])
+  addConfig(data2).then(res => {
     ElMessage.success(type === 'add' ? '添加成功' : '编辑成功')
     cancel()
     getData()

+ 1 - 0
src/views/iot/channel/template.vue

@@ -3,6 +3,7 @@
     ref="crudRef"
     :data="data"
     :column="column"
+    :addBtn="true"
     v-model:page="state.page"
     v-model:query="state.query"
     :total="state.total"

+ 1 - 1
src/views/iot/plugins/detail.vue

@@ -31,7 +31,7 @@
           v-model:value="data.file"
           :fileType="['jar']"
           :limit="1"
-          :fileSize="100"
+          :fileSize="150"
           :params="uploadParam"
           @uploadSuccess="(res) => uploadJarSuccess(res, data)"
           uploadType="url"

+ 59 - 0
src/views/iot/ruleEngine/ruleSys/components/AlertAction.vue

@@ -0,0 +1,59 @@
+<template>
+  <div>
+    <el-form label-width="120px" v-for="service in config.services" :key="service">
+      <el-form-item label="转换脚本">
+        <code-editor style="width: 100%" v-model:code="service.script" />
+      </el-form-item>
+    </el-form>
+  </div>
+</template>
+
+<script lang="ts" setup>
+import CodeEditor from '@/components/CodeEditor/index.vue'
+
+const props = defineProps({
+  config: {
+    type: Object,
+    default: () => {
+      return {
+        type: 'alert',
+        services: [
+          {
+            script: '',
+          },
+        ],
+      }
+    },
+  },
+})
+
+const configRef = ref<any>(props.config)
+const init = (data) => {
+  if (!configRef.value.services) configRef.value.services = data.services
+  if (configRef.value.services.length > 1) {
+    configRef.value.services.splice(1, configRef.value.services.length - 1)
+  }
+}
+watch(
+  () => props.config,
+  (newV) => {
+    init(newV)
+  },
+  {
+    immediate: true,
+    deep: true,
+  }
+)
+</script>
+
+<style scoped>
+.CodeMirror {
+  height: 300px !important;
+}
+</style>
+<style>
+.CodeMirror pre.CodeMirror-line,
+.CodeMirror pre.CodeMirror-line-like {
+  line-height: 21px !important;
+}
+</style>

+ 22 - 12
src/views/iot/ruleEngine/ruleSys/index.vue

@@ -45,7 +45,7 @@
             <filtera v-if="activeName === 2" v-model:filters="row.filters" />
           </el-tab-pane>
           <el-tab-pane label="输出" :name="3">
-            <Output v-if="activeName === 3" v-model:list="row.actions" type="rule" actions="device,http,mqtt,kafka,tcp" />
+            <Output v-if="activeName === 3" v-model:list="row.actions" type="rule" actions="device,http,mqtt,kafka,tcp,alert" />
           </el-tab-pane>
         </el-tabs>
       </template>
@@ -79,23 +79,27 @@ const column: IColumn[] = [{
   key: 'state',
   slot: true,
   formHide: true,
-}, {
+},
+  {
   label: '规则类型',
   key: 'type',
   type: 'select',
-  search: true,
+  search: false,
   componentProps: {
     defaultValue: 'scene',
     clearable: false,
     options: [{
       label: '场景联动',
       value: 'scene',
-    }, {
-      label: '数据流转',
-      value: 'flow'
-    }]
+    }
+    // , {
+    //   label: '数据流转',
+    //   value: 'flow'
+    // }
+    ]
   }
-}, {
+},
+  {
   label: '执行日志',
   key: 'log',
   slot: true,
@@ -139,8 +143,9 @@ const onSave = ({type, data, cancel}: any) => {
     const mObj = {
       type: m.type,
       pk: m.pk,
-      deviceDn: m.deviceDn,
-      device: m.device,
+      dn: m.dn,
+      // deviceDn: m.deviceDn,
+      // device: m.device,
       conditions: m.conditions.map(m2 => ({
         ...m2,
         device: m.device,
@@ -155,8 +160,9 @@ const onSave = ({type, data, cancel}: any) => {
     const mObj = {
       type: 'device',
       pk: m.pk,
-      deviceDn: m.deviceDn,
-      device: m.device,
+      dn: m.dn,
+      // deviceDn: m.deviceDn,
+      // device: m.device,
       conditions: m.conditions.map(m2 => ({
         ...m2,
         device: m.device,
@@ -169,6 +175,10 @@ const onSave = ({type, data, cancel}: any) => {
     }
   })
   obj.actions = (obj.actions || [])?.map(m => {
+    m.saved = true
+    if (m.config) {
+      return m
+    }
     return {
       ...m,
       config: JSON.stringify(m),

+ 145 - 130
src/views/iot/ruleEngine/ruleSys/modules/output.vue

@@ -6,9 +6,9 @@
           <template #title>
             <div class="flex" style="justify-content: space-between;width: 100%;">
               <div class="cu-title" @click.stop>
-                <el-radio-group v-model="item.type" @change="actionTypeChange(item)">
+                <el-radio-group v-model="item.type" @change="actionTypeChange(item)" :disabled="!!item.saved">
                   <el-radio v-if="actions.indexOf('device') >= 0" :label="'device'">设备控制 </el-radio>
-                  <el-radio v-if="actions.indexOf('alarm') >= 0" :label="'alarm'">告警消息 </el-radio>
+                  <el-radio v-if="actions.indexOf('alert') >= 0" :label="'alert'">告警消息 </el-radio>
                   <el-radio v-if="actions.indexOf('scene') >= 0" :label="'scene'">场景控制 </el-radio>
                   <el-radio v-if="actions.indexOf('http') >= 0" :label="'http'">http推送 </el-radio>
                   <el-radio v-if="actions.indexOf('mqtt') >= 0" :label="'mqtt'">mqtt推送 </el-radio>
@@ -36,6 +36,10 @@
           <div class="condition-box" v-if="item.type === 'tcp'">
             <TcpAction :config="item" />
           </div>
+          <div class="condition-box" v-if="item.type === 'alert'">
+            <AlertAction :config="item" />
+            保存后,在警告配置中关联此规则
+          </div>
         </el-collapse-item>
       </el-collapse>
     </div>
@@ -43,153 +47,164 @@
   </div>
 </template>
 <script lang="ts" setup>
-import { propTypes } from '@/utils/propTypes'
-import DeviceAction from '../components/DeviceAction.vue'
-import HttpAction from '../components/HttpAction.vue'
-import MqttAction from '../components/MqttAction.vue'
-import KafkaAction from '../components/KafkaAction.vue'
-import TcpAction from '../components/TcpAction.vue'
+  import { propTypes } from '@/utils/propTypes'
+  import DeviceAction from '../components/DeviceAction.vue'
+  import HttpAction from '../components/HttpAction.vue'
+  import MqttAction from '../components/MqttAction.vue'
+  import KafkaAction from '../components/KafkaAction.vue'
+  import TcpAction from '../components/TcpAction.vue'
+  import AlertAction from '../components/AlertAction.vue'
 
-const props = defineProps({
-  list: propTypes.array.def([]),
-  actions: propTypes.string.def(''),
-})
-const emits = defineEmits(['update:list'])
-const arr: number[] = []
-for (let i = 0; i < 10; i++) {
-  arr.push(i)
-}
-const activeName = ref<number[]>(arr)
-const dataList = ref<any[]>(props.list || [])
-watch(() => dataList.value.length, (newV) => {
-  const arr = dataList.value.map(m => {
-    if (m.config) {
-      const obj = JSON.parse(m.config || '{}')
-      return obj
-    }
-    return m
-  })
-  dataList.value = arr
-  emits('update:list', arr)
-}, {
-  // deep: true,
-  immediate: true,
-})
-// 新增输出
-const handleAdd = () => {
-  dataList.value.push({
-    services: [],
+  const props = defineProps({
+    list: propTypes.array.def([]),
+    actions: propTypes.string.def(''),
   })
-}
+  const emits = defineEmits(['update:list'])
+  const arr: number[] = []
+  for (let i = 0; i < 10; i++) {
+    arr.push(i)
+  }
+  const activeName = ref<number[]>(arr)
+  const dataList = ref<any[]>(props.list || [])
+  watch(
+    () => dataList.value.length,
+    (newV) => {
+      const arr = dataList.value.map((m) => {
+        if (m.config) {
+          const obj = JSON.parse(m.config || '{}')
+          return obj
+        }
+        return m
+      })
+      dataList.value = arr
+      emits('update:list', arr)
+    },
+    {
+      // deep: true,
+      immediate: true,
+    }
+  )
+  // 新增输出
+  const handleAdd = () => {
+    dataList.value.push({
+      services: [],
+    })
+  }
 
-// 删除输出
-const removeListener = (index: number) => {
-  dataList.value.splice(index, 1)
-}
+  // 删除输出
+  const removeListener = (index: number) => {
+    dataList.value.splice(index, 1)
+  }
 
-const deviceActionRef = ref()
-const actionTypeChange = (item) => {
-  console.log(item)
-  if (item.services.length == 0) {
-    if (item.type == 'http') {
-      item.services = [
-        {
-          url: '',
-          script: `this.translate=function(msg){
+  const deviceActionRef = ref()
+  const actionTypeChange = (item) => {
+    console.log(item)
+    if (item.services.length == 0) {
+      if (item.type == 'http') {
+        item.services = [
+          {
+            url: '',
+            script: `this.translate=function(msg,device){
+        }`,
+          },
+        ]
+      } else if (item.type == 'alert') {
+        item.services = [
+          {
+            script: `this.translate=function(msg,device){
         }`,
-        },
-      ]
-    } else if (item.type == 'mqtt') {
-      item.services = [
-        {
-          host: '',
-          port: 1883,
-          username: '',
-          password: '',
-          script: `this.translate=function(msg){
+          },
+        ]
+      } else if (item.type == 'mqtt') {
+        item.services = [
+          {
+            host: '',
+            port: 1883,
+            username: '',
+            password: '',
+            script: `this.translate=function(msg,device){
         }`,
-        },
-      ]
-    } else if (item.type == 'kafka') {
-      item.services = [
-        {
-          services: '',
-          ack: '',
-          script: `this.translate=function(msg){
+          },
+        ]
+      } else if (item.type == 'kafka') {
+        item.services = [
+          {
+            services: '',
+            ack: '',
+            script: `this.translate=function(msg,device){
         }`,
-        },
-      ]
-    } else if (item.type === 'tcp') {
-      item.services = [
-        {
-          host: '',
-          port: 1883,
-          script: `this.translate=function(msg){
-        }`
-        },
-      ]
+          },
+        ]
+      } else if (item.type === 'tcp') {
+        item.services = [
+          {
+            host: '',
+            port: 1883,
+            script: `this.translate=function(msg,device){
+        }`,
+          },
+        ]
+      }
     }
   }
-}
-onUnmounted(() => {
-  console.log('onUnmounted')
-  dataList.value = []
-})
+  onUnmounted(() => {
+    console.log('onUnmounted')
+    dataList.value = []
+  })
 </script>
 
 <style lang="scss" scoped>
-.list-box {
-  margin-top: 10px;
-  .cu-title {
-    width: calc(100% - 30px);
-    cursor: auto;
-    display: flex;
-    align-items: center;
-    .item {
-      width: 250px;
-      margin-right: 10px;
+  .list-box {
+    margin-top: 10px;
+    .cu-title {
+      width: calc(100% - 30px);
+      cursor: auto;
+      display: flex;
+      align-items: center;
+      .item {
+        width: 250px;
+        margin-right: 10px;
+      }
     }
   }
-}
-::v-deep(.el-collapse-item__header) {
-  background-color: #f2f2f2;
-  padding: 0 12px;
-}
-.condition-box {
-  background-color: #f2f2f2;
-  padding: 15px;
-  border-top: 1px solid #d9d9d9;
-  .main {
-    border: 2px dashed rgb(217, 217, 217);
-    .title {
-      padding: 12px;
-      color: #333;
-      font-weight: 600;
-      border-bottom: 1px solid rgb(217, 217, 217);
-    }
-    .main-box {
-      padding: 10px;
-      .box {
-        display: flex;
-        align-items: center;
-        .item {
-          border: 2px dashed rgb(217, 217, 217);
-          padding: 6px;
+  ::v-deep(.el-collapse-item__header) {
+    background-color: #f2f2f2;
+    padding: 0 12px;
+  }
+  .condition-box {
+    background-color: #f2f2f2;
+    padding: 15px;
+    border-top: 1px solid #d9d9d9;
+    .main {
+      border: 2px dashed rgb(217, 217, 217);
+      .title {
+        padding: 12px;
+        color: #333;
+        font-weight: 600;
+        border-bottom: 1px solid rgb(217, 217, 217);
+      }
+      .main-box {
+        padding: 10px;
+        .box {
           display: flex;
-          justify-content: space-between;
           align-items: center;
-          margin-top: 10px;
-          flex: 1;
-          .param-item {
-            margin-bottom: 8px;
+          .item {
+            border: 2px dashed rgb(217, 217, 217);
+            padding: 6px;
+            display: flex;
+            justify-content: space-between;
+            align-items: center;
+            margin-top: 10px;
+            flex: 1;
+            .param-item {
+              margin-bottom: 8px;
+            }
           }
+          // .el-button {
+          //   width: 50px;
+          // }
         }
-        // .el-button {
-        //   width: 50px;
-        // }
       }
     }
   }
-
-}
 </style>

+ 3 - 2
src/views/system/dept/index.vue

@@ -3,7 +3,7 @@
     <transition :enter-active-class="proxy?.animate.searchAnimate.enter" :leave-active-class="proxy?.animate.searchAnimate.leave">
       <div class="search" v-show="showSearch">
         <el-form ref="queryFormRef" :model="queryParams" :inline="true" label-width="68px">
-          <el-form-item label="菜单名称" prop="menuName">
+          <el-form-item label="部门名称" prop="menuName">
             <el-input v-model="queryParams.deptName" placeholder="请输入部门名称" clearable @keyup.enter="handleQuery" />
           </el-form-item>
           <el-form-item label="状态" prop="status">
@@ -26,7 +26,7 @@
 <!--            <el-button type="primary" plain icon="Plus" @click="handleAdd()" v-hasPermi="['system:dept:add']">新增 </el-button>-->
 <!--          </el-col>-->
           <el-col :span="1.5">
-            <el-button type="primary" plain icon="Plus" @click="handleAdd()"  v-if="checkPermi['system:dept:add']">新增 </el-button>
+            <el-button type="primary" plain icon="Plus" @click="handleAdd()"  v-show = "hasPermission('system:dept:add')">新增 </el-button>
           </el-col>
           <el-col :span="1.5">
             <el-button type="info" plain icon="Sort" @click="handleToggleExpandAll">展开/折叠</el-button>
@@ -144,6 +144,7 @@ import { listDept, getDept, delDept, addDept, updateDept, listDeptExcludeChild }
 import { ComponentInternalInstance } from 'vue'
 import { DeptForm, DeptQuery, DeptVO } from '@/api/system/dept/types'
 import { FormInstance, TableInstance } from 'element-plus'
+import { hasPermission } from '@/utils/auth'
 
 interface DeptOptionsType {
   id: number | string

+ 1 - 0
src/views/system/user/index.vue

@@ -386,6 +386,7 @@ const data = reactive<PageData<UserForm, UserQuery>>({
       { required: true, message: '用户名称不能为空', trigger: 'blur' },
       { min: 2, max: 20, message: '用户名称长度必须介于 2 和 20 之间', trigger: 'blur' },
     ],
+    deptId: [{ required: true, message: '部门不能为空', trigger: 'blur' }],
     nickName: [{ required: true, message: '用户昵称不能为空', trigger: 'blur' }],
     password: [
       { required: true, message: '用户密码不能为空', trigger: 'blur' },