Преглед на файлове

feat: 增加产品、规则、任务的统计

lvkun преди 2 години
родител
ревизия
ebde0d205f

+ 44 - 0
src/components/StatisticsTemplate/index.vue

@@ -0,0 +1,44 @@
+<template>
+<a-card :title="title" >
+  <a-row justify="center">
+      <a-col
+        v-for="item in list"
+        :key="item.key"
+        :xs="12"  :sm="12"  :md="6"  :lg="8"  :xl="4"
+      >
+        <div class="statistic" >
+          <span class="count" :style="{color: item.color}" >{{item.value}}</span>
+          <span>{{item.unit}}</span>
+        </div>
+        <div class="label" >
+          {{item.label}}
+        </div>
+      </a-col>
+    </a-row>
+</a-card>
+</template>
+<script lang='ts' setup >
+
+interface IProps {
+  title: string,
+  list: {value: string | number, key?: string, label: string, color: string, unit: string }[]
+}
+
+const props = defineProps<IProps>()
+
+</script>
+<style lang='less' scoped >
+@import '~@/styles/theme.less';
+.statistic {
+  color: @sublabel-color;
+  .count {
+    font-size: 48px;
+    margin-right: 10px;
+
+  }
+}
+.label {
+  font-size: 18px;
+  color: @sublabel-color;
+}
+</style>

+ 7 - 0
src/controller/iot/model.ts

@@ -2,6 +2,13 @@ import { addModel, addModelPlugin, debugModelPlugin, delModel, getModel, getMode
 import { message } from 'ant-design-vue'
 
 export class ModelController {
+  static forwardStatisticMap = new Map([
+    ['MQTT', { label: 'MQTT', color: 'green' }],
+    ['TOTAL', { label: '总数', color: 'grey' }],
+    ['HTTP', { label: 'HTTP', color: 'orange' }],
+    ['COAP', { label: 'COAP', color: 'skyblue' }]
+  ])
+
   static async list (params: {modelLabel?: string, limit?: number, lastId?: string} = {
     modelLabel: '',
     limit: 10000,

+ 12 - 0
src/controller/iot/rule.ts

@@ -19,6 +19,18 @@ export class RuleController {
     [SubjectEventEnum.MODEL_DELETE, { name: '模型删除', key: SubjectEventEnum.MODEL_DELETE }]
   ])
 
+  static forwardStatisticMap = new Map([
+    ['TOTAL', { label: '总数', color: 'green' }],
+    ['DISABLE', { label: '禁用数', color: 'grey' }],
+    ['ENABLE', { label: '开始数', color: 'orange' }]
+  ])
+
+  static linkStatisticMap = new Map([
+    ['TOTAL', { label: '总数', color: 'green' }],
+    ['DISABLE', { label: '禁用数', color: 'grey' }],
+    ['ENABLE', { label: '开始数', color: 'orange' }]
+  ])
+
   /** 转发规则 分页获取转发规则 */
   static async pageForward (params: IOT.API.RULE.ForwardRuleQueryParams) {
     return await getForwardRulePage(params)

+ 6 - 0
src/controller/iot/task.ts

@@ -10,6 +10,12 @@ export class TaskController {
 
   static taskTypeList = ['DEVICE_CMD', 'DEVICE_MSG', 'DEVICE_DISCONNECT']
 
+  static taskStatisticsMap = new Map([
+    ['TOTAL', { label: '总数', color: 'green' }],
+    ['DISABLED', { label: '禁用数', color: 'grey' }],
+    ['ENABLE', { label: '启用数量', color: 'orange' }]
+  ])
+
   static async page (params: {page: number, pageSize: number, taskLabel?: string, status?: boolean | string}) {
     return getTaskPage(params)
   }

+ 0 - 1
src/pages/Iot/dashboard/deviceAccess/index.vue

@@ -124,7 +124,6 @@ const getStatisList = async () => {
 
   RuleController.forwardCount().then(r => {
     sourceList[1].value = r.data
-    console.log('RuleController:', r.data)
   })
 
   RuleController.linkCount().then(r => {

+ 21 - 2
src/pages/Iot/model/index.vue

@@ -1,5 +1,9 @@
 <template>
-  <a-card>
+  <StatisticsTemplate
+    title="产品统计"
+    :list="state.modelCount"
+  />
+  <a-card style="margin-top: 20px;" >
     <a-row justify="space-between" >
       <a-col>
         <a-input-search
@@ -97,6 +101,7 @@ import { onMounted, reactive } from 'vue'
 import { computed } from '@vue/reactivity'
 import { Form } from 'ant-design-vue'
 import { useRouter } from 'vue-router'
+import StatisticsTemplate from '@/components/StatisticsTemplate/index.vue'
 
 const columns = [
   {
@@ -149,7 +154,8 @@ const state = reactive<{
     total: 0,
     modelLabel: ''
   },
-  payloadType: ['JSON', '二进制流']
+  payloadType: ['JSON', '二进制流'],
+  modelCount: []
 })
 
 const modalTitle = computed(() => state.opraState === 'add' ? '新增产品模型' : '编辑产品模型')
@@ -174,6 +180,18 @@ const rulesRef = reactive({
 
 const { resetFields, validate, validateInfos } = useForm(modelRef, rulesRef)
 
+// 获取统计
+const getModelCount = async () => {
+  const { data } = await ModelController.count()
+  state.modelCount = Object.keys(data).map(key => {
+    const item = ModelController.forwardStatisticMap.get(key)
+    return {
+      ...item,
+      value: data[key]
+    }
+  })
+}
+
 const change = ({ current }) => {
   state.queryParams.page = current
   getModel()
@@ -225,6 +243,7 @@ const getModel = async () => {
 onMounted(async () => {
   getModel()
   state.transport = await CommonController.getTransport()
+  getModelCount()
 })
 </script>
 

+ 23 - 2
src/pages/Iot/rule/forwardRule.vue

@@ -1,5 +1,9 @@
 <template>
-<a-card>
+  <StatisticsTemplate
+    title="规则统计"
+    :list="state.forwardCount"
+  />
+<a-card style="margin-top: 20px;" >
   <a-row justify="space-between" >
     <a-col :span="18" >
     <a-form style="width: 100%;"  :label-col="{span: 5}" >
@@ -401,6 +405,9 @@ import type { FormItemProps } from '@/components/FormPro/index.vue'
 import { Form, Empty } from 'ant-design-vue'
 import { computed } from '@vue/reactivity'
 import TestDialog from './components/testDialog.vue'
+import { object } from 'vue-types'
+import { RtsController } from '@/controller/rts'
+import StatisticsTemplate from '@/components/StatisticsTemplate/index.vue'
 
 const columns = [
   {
@@ -619,7 +626,8 @@ const state = reactive({
   stepCount: 0,
   opraState: 'add',
   forwardId: '',
-  testVisble: false
+  testVisble: false,
+  forwardCount: []
 })
 
 const requestHeader = { key: '', value: '' }
@@ -790,6 +798,18 @@ const addForwardState = () => {
 
 const changeStatus = async (record) => RuleController.updateForwardStatus({ id: record.id, status: record.status })
 
+// 获取统计规则
+const getCount = async () => {
+  const { data } = await RuleController.forwardCount()
+  state.forwardCount = Object.keys(data).map(key => {
+    const item = RuleController.forwardStatisticMap.get(key)
+    return {
+      ...item,
+      value: data[key]
+    }
+  })
+}
+
 /** 提交转发规则 */
 const ok = async (visibleKey: string) => {
   if (state.stepCount === 1) {
@@ -866,6 +886,7 @@ const getForwardList = async () => {
 
 onMounted(() => {
   getForwardList()
+  getCount()
 })
 </script>
 <style lang='less' scoped >

+ 21 - 2
src/pages/Iot/rule/linkRules.vue

@@ -1,5 +1,9 @@
 <template>
-  <a-card>
+   <StatisticsTemplate
+    title="规则统计"
+    :list="state.linkCount"
+  />
+  <a-card style="margin-top: 20px;" >
     <a-row justify="space-between" >
       <a-col :span="12" >
         <a-space>
@@ -660,6 +664,7 @@ import SelectDevice from './components/selectDevice.vue'
 import { useId } from '@/hooks'
 import { Form } from 'ant-design-vue'
 import TestDialog from './components/testDialog.vue'
+import StatisticsTemplate from '@/components/StatisticsTemplate/index.vue'
 
 const {
   proxy: { $forceUpdate }
@@ -875,7 +880,8 @@ const state = reactive({
   opraModel: '',
   opraIndex: 0, // 点击 条件 与 动作 时的下标
   opraKey: '',
-  testVisble: false
+  testVisble: false,
+  linkCount: []
 })
 
 watch(
@@ -945,6 +951,18 @@ const { resetFields, validate, validateInfos } = useForm(bodyParamsState, {
   ruleLabel: [{ required: true, message: '请填写联动规则名称' }]
 })
 
+// 获取统计规则
+const getCount = async () => {
+  const { data } = await RuleController.linkCount()
+  state.linkCount = Object.keys(data).map(key => {
+    const item = RuleController.linkStatisticMap.get(key)
+    return {
+      ...item,
+      value: data[key]
+    }
+  })
+}
+
 const changeStatus = async (record) => RuleController.updateLinkStatus({ id: record.id, status: record.status })
 
 const openModalDebug = (id: string) => {
@@ -1114,6 +1132,7 @@ const getLinkPage = async () => {
 onMounted(() => {
   getLinkPage()
   getModelList()
+  getCount()
 })
 
 </script>

+ 174 - 0
src/pages/Iot/sys/email.vue

@@ -0,0 +1,174 @@
+<template>
+<a-spin :spinning="state.loading" >
+  <a-row :gutter="[16, 16]">
+      <a-col span="6" >
+        <a-card>
+          <a-row>
+            <!-- <a-col span="24" class="logo-box" >
+              <img class="logo" :src="logoPng" />
+              <div class="label center" >{{ state.sys.sysLabel}}</div>
+            </a-col>
+            <a-col span="24" style="margin-top: 20px;" >
+              <a-row justify="center" >
+                <a-space>
+                  <a-tag color="pink" >蛟龙</a-tag>
+                  <a-tag color="red" >云联</a-tag>
+                  <a-tag color="orange" >快速</a-tag>
+                  <a-tag color="green" >简洁</a-tag>
+                  <a-tag color="cyan" >智慧</a-tag>
+                </a-space>
+              </a-row>
+            </a-col> -->
+            <a-col  span="24" class="email" >
+            <a-form>
+              <a-form-item label="邮件地址📧" >
+                {{ state.sys.mailConf?.host }}
+              </a-form-item>
+              <a-form-item label="邮件协议🔗" >
+                {{ state.sys.mailConf?.protocol }}
+              </a-form-item>
+              <a-form-item label="邮件账号🔢" >
+                {{ state.sys.mailConf?.username }}
+              </a-form-item>
+              <a-form-item label="邮件密码🫥" >
+                {{ state.sys.mailConf?.password }}
+              </a-form-item>
+            </a-form>
+
+            </a-col>
+            <a-col span="24" class="center" >
+              <a-button type="primary" @click="openModal" >修改</a-button>
+            </a-col>
+          </a-row>
+        </a-card>
+      </a-col>
+      <a-col span="18" >
+      </a-col>
+  </a-row>
+</a-spin>
+  <modal-pro
+    label="修改邮箱"
+    :visible="state.visible"
+    ok-text="确定"
+    cancel-text="取消"
+    @cancel="state.visible = false"
+    @ok="ok"
+  >
+  <a-form :label-col="{span: 4}" :wrapper-col="{span: 14}">
+    <a-form-item label="邮件地址" v-bind="validateInfos.host">
+      <a-input v-model:value="modelRef.host"  />
+    </a-form-item>
+    <a-form-item label="邮件端口" v-bind="validateInfos.port">
+      <a-input v-model:value="modelRef.port"  />
+    </a-form-item>
+    <a-form-item label="邮件协议" v-bind="validateInfos.protocol">
+      <a-input v-model:value="modelRef.protocol"  />
+    </a-form-item>
+    <a-form-item label="邮件账号" v-bind="validateInfos.username">
+      <a-input v-model:value="modelRef.username"  />
+    </a-form-item>
+    <a-form-item label="邮件密码" v-bind="validateInfos.password">
+      <a-input v-model:value="modelRef.password"  />
+    </a-form-item>
+   </a-form>
+  </modal-pro>
+</template>
+
+<script lang="ts" setup >
+import { SysController } from '@/controller'
+import { onMounted, reactive } from 'vue'
+import { Form } from 'ant-design-vue'
+
+const logoPng = require('@/assets/logo.png')
+
+const state = reactive<{
+  sys: Partial<IOT.API.SYS.Sys>,
+  visible: boolean
+  loading: boolean
+}>({
+  sys: {},
+  visible: false,
+  loading: false
+})
+
+const modelRef = reactive({
+  host: '',
+  port: '',
+  protocol: '',
+  username: '',
+  password: ''
+})
+
+const useForm = Form.useForm
+
+const { resetFields, validate, validateInfos } = useForm(modelRef, reactive({
+  host: [{ required: true, message: '请填写邮件地址' }],
+  port: [{ required: true, message: '请填写邮件端口' }],
+  protocol: [{ required: true, message: '请填写邮件协议' }],
+  username: [{ required: true, message: '请填写邮件账号' }],
+  password: [{ required: true, message: '请填写邮件密码' }]
+}))
+
+const ok = () => {
+  validate().then(async () => {
+    await SysController.updateSysConf({
+      ...state.sys,
+      mailConf: modelRef
+    })
+    state.visible = false
+    getLogo()
+  })
+}
+
+const openModal = () => {
+  state.visible = true
+}
+
+const getLogo = async () => {
+  state.loading = true
+  const { data } = await SysController.sysConf()
+  state.loading = false
+  state.sys = data
+  resetFields(data.mailConf)
+}
+
+onMounted(() => {
+  getLogo()
+})
+
+</script>
+
+<style lang="less" scoped >
+.logo-box {
+  display: flex;
+  flex-direction: column;
+  justify-content: center;
+  align-items: center;
+  .logo {
+    width: 200px;
+    height: 200px;
+    border-radius: 50%;
+    border: 1px solid #ccc;
+  }
+
+}
+
+.email {
+  display: flex;
+  // justify-content: center;
+  margin-left: 70px;
+  margin-top: 20px;
+  font-size: 22px;
+}
+
+.label {
+  font-size: 32px;
+  margin-top: 20px;
+}
+
+.center {
+  display: flex;
+  justify-content: center;
+  align-items: center;
+}
+</style>

+ 0 - 102
src/pages/Iot/sys/logo.vue

@@ -1,102 +0,0 @@
-<template>
-  <a-row :gutter="[16, 16]">
-    <a-col span="6" >
-      <a-card>
-        <a-row>
-          <a-col span="24" class="logo-box" >
-            <img class="logo" :src="logoPng" />
-            <div class="label center" >{{ state.sys.sysLabel}}</div>
-          </a-col>
-          <a-col span="24" style="margin-top: 20px;" >
-            <a-row justify="center" >
-              <a-space>
-                <a-tag color="pink" >蛟龙</a-tag>
-                <a-tag color="red" >云联</a-tag>
-                <a-tag color="orange" >快速</a-tag>
-                <a-tag color="green" >简洁</a-tag>
-                <a-tag color="cyan" >智慧</a-tag>
-              </a-space>
-            </a-row>
-          </a-col>
-          <a-col  span="24" class="email" >
-           <a-form>
-            <a-form-item label="邮件地址📧" >
-              {{ state.sys.mailConf?.host }}
-            </a-form-item>
-            <a-form-item label="邮件协议🔗" >
-              {{ state.sys.mailConf?.protocol }}
-            </a-form-item>
-            <a-form-item label="邮件账号🔢" >
-              {{ state.sys.mailConf?.username }}
-            </a-form-item>
-            <a-form-item label="邮件密码🫥" >
-              {{ state.sys.mailConf?.password }}
-            </a-form-item>
-           </a-form>
-          </a-col>
-        </a-row>
-      </a-card>
-    </a-col>
-    <a-col span="18" >
-    </a-col>
-  </a-row>
-
-</template>
-
-<script lang="ts" setup >
-import { SysController } from '@/controller'
-import { onMounted, reactive } from 'vue'
-
-const logoPng = require('@/assets/logo.png')
-
-const state = reactive<{
-  sys: Partial<IOT.API.SYS.Sys>
-}>({
-  sys: {}
-})
-
-const getLogo = async () => {
-  const { data } = await SysController.sysConf()
-  state.sys = data
-}
-
-onMounted(() => {
-  getLogo()
-})
-
-</script>
-
-<style lang="less" scoped >
-.logo-box {
-  display: flex;
-  flex-direction: column;
-  justify-content: center;
-  align-items: center;
-  .logo {
-    width: 200px;
-    height: 200px;
-    border-radius: 50%;
-    border: 1px solid #ccc;
-  }
-
-}
-
-.email {
-  display: flex;
-  // justify-content: center;
-  margin-left: 70px;
-  margin-top: 20px;
-  font-size: 22px;
-}
-
-.label {
-  font-size: 32px;
-  margin-top: 20px;
-}
-
-.center {
-  display: flex;
-  justify-content: center;
-  align-items: center;
-}
-</style>

+ 36 - 2
src/pages/Iot/task/manage.vue

@@ -1,5 +1,22 @@
 <template>
-<a-card>
+    <a-card title="任务统计"  v-if="state.statistics.length !== 0" >
+      <a-row justify="center">
+        <a-col
+          v-for="item in state.statistics"
+          :key="item.key"
+          :xs="12"  :sm="12"  :md="6"  :lg="8"  :xl="4"
+        >
+          <div class="statistic" >
+            <span class="count" :style="{color: item.color}" >{{item.value}}</span>
+            <span>台</span>
+          </div>
+          <div class="label" >
+            {{item.label}}
+          </div>
+        </a-col>
+      </a-row>
+  </a-card>
+<a-card style="margin-top: 20px;" >
   <a-row justify="space-between" >
     <a-col span="12" >
       <a-form layout="inline" >
@@ -255,7 +272,8 @@ const state = reactive({
   attrList: [],
   deviceList: [],
   deviceModalVisible: false,
-  taskId: ''
+  taskId: '',
+  statistics: []
 })
 
 const modalRef = reactive({
@@ -377,6 +395,21 @@ const getDeviceList = async () => {
   state.deviceList = data
 }
 
+const getStatistics = async () => {
+  const { data } = await TaskController.statistics()
+  if (data == null) {
+    state.statistics = []
+  } else {
+    state.statistics = Object.keys(data).map(key => {
+      const item = TaskController.taskStatisticsMap.get(key)
+      return {
+        ...item,
+        value: data[key]
+      }
+    })
+  }
+}
+
 const getTaskById = async (id: string) => {
   const { data } = await TaskController.byId(id)
   console.log('getTaskById:', data)
@@ -408,6 +441,7 @@ onMounted(() => {
   getTaskPage()
   getModelList()
   getDeviceList()
+  getStatistics()
 })
 
 </script>

+ 4 - 4
src/router/index.ts

@@ -159,13 +159,13 @@ export const routes: Array<ROUTER.RoutesProps> = [
       {
         path: '/sys',
         name: '系统设置',
-        redirect: '/sys/logo',
+        redirect: '/sys/email',
         icon: 'DatabaseOutlined',
         children: [
           {
-            path: '/sys/logo',
-            name: '系统设置',
-            component: () => import('@/pages/iot/sys/logo.vue')
+            path: '/sys/email',
+            name: '邮箱配置',
+            component: () => import('@/pages/iot/sys/email.vue')
           },
           {
             path: '/sys/notice',