ソースを参照

Merge branch 'master' into professional2

yanglzh 1 年間 前
コミット
0cb9d4d0fc

+ 17 - 0
.env.test

@@ -0,0 +1,17 @@
+VITE_SERVER_PROTOCOL = 'http:'
+VITE_SERVER_HOSTNAME = '127.0.0.1:8200'
+
+# 基础服务路径
+VITE_SERVER_URL = ''
+# 基础接口路径
+VITE_API_URL = '/api/v1'
+# 指数管理页面用到的
+VITE_ASSESS_URL = '/assess/v1'
+# 大屏前端
+VITE_SCREEN_URL = '/plugin/screen/'
+# 组态图前端
+VITE_TOPO_URL = '/plugin/topo/'
+# modbus服务
+VITE_MODBUS_API = '/modbus'
+# ice104协议网关服务
+VITE_ICE104_API = '/ice104'

+ 1 - 0
package.json

@@ -9,6 +9,7 @@
     "build": "vite build",
     "build:golocal": "vite build --mode golocal",
     "build:open": "vite build --mode open",
+    "build:test": "vite build --mode test",
     "deploy:zip": "npm run build && npm run zipAndUpload && npm run unzip && npm run success",
     "zipAndUpload": "cd dist && rm -rf zhgy.sagoo.cn.zip && zip -r -q zhgy.sagoo.cn.zip ./ && ssh iot 'sudo rm /www/wwwroot/zhgy.sagoo.cn-old.zip' | ssh iot 'sudo mv /www/wwwroot/zhgy.sagoo.cn.zip /www/wwwroot/zhgy.sagoo.cn-old.zip' | scp -r -O zhgy.sagoo.cn.zip iot:/www/wwwroot",
     "unzip": "ssh iot 'cd /www/wwwroot/ && sudo unzip -q -o -d ./zhgy.sagoo.cn zhgy.sagoo.cn.zip'",

+ 1 - 2
src/api/device/index.ts

@@ -114,10 +114,9 @@ export default {
   },
   dev_asset_metadata: {
     getList: (params: object) => get('/product/dev_asset_metadata/list', params),
-    getProKey: (params: object) => get('/product/dev_asset_metadata/key', params),
     add: (params: object) => post('/product/dev_asset_metadata/add', params),
     edit: (params: object) => put('/product/dev_asset_metadata/edit', params),
-    detail: (params: object) => get('/product/dev_asset_metadata/get', params),
+    detail: (params: object) => get('/product/dev_asset_metadata/key', params),
     delete: (params: object) => del('/product/dev_asset_metadata/delete', params),
   }
 

+ 2 - 0
src/api/iotCard/index.ts

@@ -26,5 +26,7 @@ export default {
   },
   platform: {
     getList: (params: object) => get('/sim_factory/list', params),
+    addItem: (data: object) => post('/sim_factory/add', data),
+    editItem: (data: object) => put('/sim_factory/edit', data),
   }
 }

+ 2 - 0
src/api/system/index.ts

@@ -8,6 +8,8 @@ export default {
     editPassword: (data: object) => post('/user/editPassword', data),
     captcha: () => get('/captcha'),
     logout: () => post('/loginOut'),
+    ssoList: () => get('/system/sys_oauth/list_front'),
+    oauth: (data: object) => post('/oauth', data),
   },
   api: {
     getList: (params?: object) => get('/system/api/tree', params),

ファイルの差分が大きいため隠しています
+ 12 - 0
src/assets/gitee.svg


+ 4 - 2
src/router/index.ts

@@ -8,6 +8,8 @@ import { staticRoutes, dynamicRoutes } from '/@/router/route';
 import { initFrontEndControlRoutes } from '/@/router/frontEnd';
 import { initBackEndControlRoutes } from '/@/router/backEnd';
 
+const whiteList = ['/login', '/sso/gitee']
+
 /**
  * 创建一个可以被 Vue 应用程序使用的路由实例
  * @method createRouter(options: RouterOptions): Router
@@ -220,7 +222,7 @@ router.beforeEach(async (to, from, next) => {
 
 	// 正常流程
 	const token = localStorage.token;
-	if (to.path === '/login' && !token) {
+	if (whiteList.includes(to.path) && !token) {
 		next();
 		NProgress.done();
 	} else {
@@ -234,7 +236,7 @@ router.beforeEach(async (to, from, next) => {
 			Session.clear();
 			resetRoute();
 			NProgress.done();
-		} else if (token && to.path === '/login') {
+		} else if (token && whiteList.includes(to.path)) {
 			next('/');
 			NProgress.done();
 		} else {

+ 8 - 0
src/router/route.ts

@@ -103,6 +103,14 @@ export const staticRoutes: Array<RouteRecordRaw> = [
 		meta: {
 			title: '登录',
 		},
+	},
+	{
+		path: '/sso/:type',
+		name: 'sso',
+		component: () => import('/@/views/sso/index.vue'),
+		meta: {
+			title: 'sso登录',
+		},
 	}
 	/**
 	 * 提示:写在这里的为全屏界面,不建议写在这里

+ 21 - 13
src/views/iot/device/instance/component/edit.vue

@@ -66,11 +66,13 @@
           <el-input v-model="intro" type="textarea" placeholder="请输入设备说明"></el-input>
         </el-form-item>
         <el-form-item label="设备图片">
-          <upload-vue :imgs="phone" @set-imgs="setImgsPhone" :limit="deviceImgLimit"></upload-vue>
-        </el-form-item>
+<!--					<upload-vue :imgs="phone" @set-imgs="setImgsPhone" :limit="deviceImgLimit"></upload-vue>-->
+          <uploadVue :img="phone" @set-imgs="setImgsPhone"></uploadVue>
+				</el-form-item>
         <el-form-item label="证书图片">
-          <upload-vue :imgs="certificate" @set-imgs="setImgsCertificate" :limit="deviceImgLimit"></upload-vue>
-        </el-form-item>
+<!--					<upload-vue :imgs="certificate" @set-imgs="setImgsCertificate" :limit="deviceImgLimit"></upload-vue>-->
+          <uploadVue :img="certificate" @set-imgs="setImgsCertificate"></uploadVue>
+				</el-form-item>
       </el-form>
       <template #footer>
         <span class="dialog-footer">
@@ -88,7 +90,7 @@
 import { reactive, toRefs, defineComponent, ref, unref, nextTick, onMounted } from 'vue';
 import api from '/@/api/device';
 import apiSystem from '/@/api/system';
-import { ElMessage } from "element-plus";
+import {ElMessage, UploadProps} from "element-plus";
 import tagVue from './tag.vue';
 import Map from './map.vue';
 import UploadVue from '/@/components/upload/index.vue';
@@ -140,8 +142,8 @@ interface DicState {
   rules: {};
   deviceImgLimit: number;
   certificateLimit: number;
-  phone: any[];
-  certificate: any[];
+  phone: string;
+  certificate: string;
   intro: string;
 }
 interface Tag {
@@ -180,8 +182,8 @@ export default defineComponent({
       },
       deviceImgLimit: 0,
       certificateLimit: 0,
-      phone: [],
-      certificate: [],
+      phone: "",
+      certificate: "",
       intro: ""
     });
 
@@ -226,12 +228,18 @@ export default defineComponent({
       }
     };
     // 上传设备图
-    const setImgsPhone = (res: any) => {
-      state.phone = res;
+    // const setImgsPhone = (res:any) => {
+    //   state.phone = res;
+    // }
+    const setImgsPhone: UploadProps['onSuccess'] = (response) => {
+      state.phone = response
     }
     // 上传设备资格证书
-    const setImgsCertificate = (res: any) => {
-      state.certificate = res;
+    // const setImgsCertificate = (res:any) => {
+    //   state.certificate = res;
+    // }
+    const setImgsCertificate: UploadProps['onSuccess'] = (response) => {
+      state.certificate = response
     }
     // 关闭弹窗
     const closeDialog = () => {

+ 21 - 4
src/views/iot/device/product/component/editPro.vue

@@ -13,12 +13,15 @@
 				</el-form-item>
 
 				<el-form-item label="产品分类" prop="categoryId">
-					<el-cascader :options="cateData" :props="{ checkStrictly: true, emitPath: false, value: 'id', label: 'name' }" placeholder="请选择分类" clearable class="w100" v-model="ruleForm.categoryId">
+					<el-cascader :options="cateData" :props="{ checkStrictly: true, emitPath: false, value: 'id', label: 'name' }" placeholder="请选择分类" class="w" clearable v-model="ruleForm.categoryId">
 						<template #default="{ node, data }">
 							<span>{{ data.name }}</span>
 							<span v-if="!node.isLeaf"> ({{ data.children.length }}) </span>
 						</template>
 					</el-cascader>
+
+          <!-- 添加产品分类 -->
+          <el-button type="success" @click="onOpenAddCategory()" style="margin-left: 5px;">添加产品分类</el-button>
 				</el-form-item>
 
 				<el-form-item label="消息协议" prop="messageProtocol">
@@ -93,6 +96,7 @@
 				</span>
 			</template>
 		</el-dialog>
+    <EditCategory ref="editCategoryRef" @getCateList="getCategoryList" />
 	</div>
 </template>
 
@@ -105,6 +109,7 @@ import { validateNoSpace } from '/@/utils/validator';
 
 import { ElMessage, UploadProps } from 'element-plus'
 import getOrigin from '/@/utils/origin'
+import EditCategory from "/@/views/iot/device/category/component/edit.vue";
 
 interface RuleFormState {
 	id: number
@@ -151,7 +156,7 @@ const form = {
 
 export default defineComponent({
 	name: 'deviceEditPro',
-	components: { uploadVue },
+	components: {EditCategory, uploadVue },
 	setup(prop, { emit }) {
 		const formRef = ref<HTMLElement | null>(null)
 		const baseURL: string | undefined | boolean = getOrigin(import.meta.env.VITE_API_URL)
@@ -161,6 +166,7 @@ export default defineComponent({
 
 		const certList = ref([])
 		const submitLoading = ref(false)
+    const editCategoryRef = ref();
 
 		const state = reactive<DicState | any>({
 			isShowDialog: false,
@@ -279,20 +285,31 @@ export default defineComponent({
 				}
 			})
 		}
-
-
+    // 打开新增产品分类弹窗
+    const onOpenAddCategory = () => {
+      editCategoryRef.value.openDialog();
+    };
+    // 获取产品分类列表
+    const getCategoryList = () => {
+      api.category.getList({ status: 1 }).then((res: any) => {
+        state.cateData = res.category || []
+      })
+    }
 
 		return {
 			transportProtocolChange,
 			submitLoading,
 			certList,
 			openDialog,
+      onOpenAddCategory,
 			handleAvatarSuccess,
 			closeDialog,
 			onCancel,
 			onSubmit,
 			network_server_type,
+      getCategoryList,
 			formRef,
+      editCategoryRef,
 			...toRefs(state),
 		}
 	},

+ 199 - 0
src/views/iot/iotCard/platformManage/addOrEditItem.vue

@@ -0,0 +1,199 @@
+<!-- 平台接入-新增或者编辑 -->
+<template>
+		<el-dialog :title="ruleForm.id ? '新增' : '编辑'" v-model="isShowDialog" width="650px">
+			<el-form :model="ruleForm" ref="formRef" :rules="rules" label-width="100px">
+				<el-form-item label="平台类型" prop="types">
+          <el-select style="width: 100%;" v-model="ruleForm.types" placeholder="请选择">
+            <el-option label="电信" value="1"></el-option>
+            <el-option disabled label="联通" value="2"></el-option>
+            <el-option label="移动" value="3"></el-option>
+          </el-select>
+        </el-form-item>
+        <el-form-item label="名称" prop="name">
+          <el-input v-model="ruleForm.name" placeholder="请输入名称" />
+        </el-form-item>
+        <el-form-item label="App ID" prop="appKey">
+          <el-input v-model="ruleForm.appKey" placeholder="请输入App ID" />
+        </el-form-item>
+        <el-form-item v-if="ruleForm.types == 1" label="secretKey" prop="appSecret">
+          <el-input v-model="ruleForm.appSecret" placeholder="请输入secretKey" />
+        </el-form-item>
+        <el-form-item v-if="ruleForm.types == 1" label="用户id" prop="userId">
+          <el-input v-model="ruleForm.userId" placeholder="请输入用户id" />
+        </el-form-item>
+        <el-form-item label="密码" prop="password">
+          <el-input v-model="ruleForm.password" placeholder="请输入密码" />
+        </el-form-item>
+        <el-form-item v-if="ruleForm.types == 3" label="接口地址" prop="restUrl">
+          <el-input v-model="ruleForm.restUrl" placeholder="请输入接口地址" />
+        </el-form-item>
+        <el-form-item label="状态">
+          <!-- 1启用,0禁用 -->
+          <el-switch v-model="ruleForm.status" :active-value="1" :inactive-value="0" inline-prompt active-text="启" inactive-text="禁"></el-switch>
+        </el-form-item>
+        <el-form-item label="说明">
+          <el-input :rows="6" type="textarea" v-model="ruleForm.remark" placeholder="请输入说明" />
+        </el-form-item>
+			</el-form>
+			<template #footer>
+				<span class="dialog-footer">
+					<el-button @click="onCancel">取 消</el-button>
+					<el-button :loading="btnLoading" type="primary" @click="onSubmit">确定</el-button>
+				</span>
+			</template>
+		</el-dialog>
+</template>
+
+<script lang="ts" setup>
+import api from '/@/api/iotCard';
+import { reactive, toRefs, defineComponent, ref, unref } from 'vue';
+import { ElMessageBox, ElMessage } from 'element-plus';
+import { useSearch } from "/@/hooks/useCommon"
+import { useRouter } from 'vue-router';
+
+const router = useRouter();
+
+const isShowDialog = ref(false);
+const formRef = ref<HTMLElement | null>(null);
+const emit = defineEmits(['updateList']);
+const btnLoading = ref(false);
+
+const ruleForm = ref({
+  id: 0,
+  types: "1",
+  name: "",
+  userId: "",
+  password: "",
+  appSecret: "",
+  remark: "",
+  appKey: "",
+  restUrl: "",
+  status: 1
+})
+
+const rules = ref({
+  types: [{ required: true, message: '请选择平台类型', trigger: 'change' }],
+  name: [{ required: true, message: '请输入名称', trigger: 'change' }],
+  userId: [{ required: true, message: '请输入用户id', trigger: 'change' }],
+  password: [{ required: true, message: '请输入密码', trigger: 'change' }],
+  appSecret: [{ required: true, message: '请输入secretKey', trigger: 'change' }],
+  appKey: [{ required: true, message: '请输入App ID', trigger: 'change' }],
+  restUrl: [{ required: true, message: '请输入接口地址', trigger: 'change' }]
+})
+
+/**
+ * 新增
+ */
+const onSubmit = () => {
+  const formWrap = unref(formRef) as any;
+  if (!formWrap) return;
+  formWrap.validate(async (valid: boolean) => {
+    if (!valid) return
+    btnLoading.value = true
+    console.log(ruleForm.value)
+    if(ruleForm.value.id) {
+      // 修改
+      api.platform.editItem(ruleForm.value)
+      .then(() => {
+        ElMessage({ type: 'success', message: '修改成功' })
+        emit('updateList')
+        closeDialog();
+        resetForm();
+      })
+      .finally(() => (btnLoading.value = false))
+    }else{
+      // 新增
+      api.platform.addItem(ruleForm.value)
+      .then(() => {
+        ElMessage({ type: 'success', message: '添加成功' })
+        emit('updateList')
+        closeDialog();
+        resetForm();
+      })
+      .finally(() => (btnLoading.value = false))
+      }
+  });
+};
+
+const resetForm = () => {
+  ruleForm.value  = {
+    id: 0,
+    types: "1",
+    name: "",
+    userId: "",
+    password: "",
+    appSecret: "",
+    remark: "",
+    appKey: "",
+    restUrl: ""
+  }
+}
+
+/**
+ * 取消
+ */
+const onCancel = () => {
+  closeDialog();
+};
+
+/**
+ * 关闭弹窗
+ */
+const closeDialog = () => {
+  isShowDialog.value = false;
+};
+
+
+const formatOperator = (val:any) => {
+  // 1电信,2联通,3移动
+  if(val == 1) {
+    return "电信"
+  }else if(val == 2) {
+    return "联通"
+  }else if(val == 3) {
+    return "移动"
+  }
+}
+
+const formatType = (val:any) => {
+  // 1月卡,2季卡,3年卡,4其他
+  if(val == 1) {
+    return "月卡"
+  }else if(val == 2) {
+    return "季卡"
+  }else if(val == 3) {
+    return "年卡"
+  }else if(val == 4) {
+    return "其他"
+  }
+}
+
+const formatStatus = (val:any) => {
+  // 1:可激活 2:测试激活 3:测试去激活 4:在用5:停机6:运营商管理状态
+  if(val == 1) {
+    return "可激活"
+  }else if(val == 2) {
+    return "测试激活"
+  }else if(val == 3) {
+    return "测试去激活"
+  }else if(val == 4) {
+    return "在用"
+  }else if(val == 5) {
+    return "停机"
+  }else if(val == 6) {
+    return "运营商管理状态"
+  }
+}
+
+const openDialog = (item:any) => {
+  console.log(item)
+  // router.push('/iotmanager/iotCard/index/detail/'+item.id);
+  if(item) {
+    // 修改
+    ruleForm.value = { ...item };
+  }
+  isShowDialog.value = true;
+}
+
+defineExpose({ openDialog })
+</script>

+ 30 - 17
src/views/iot/iotCard/platformManage/index.vue

@@ -4,7 +4,7 @@
     <el-card shadow="nover" class="page-full-part">
       <el-form :model="params" inline ref="queryRef">
         <el-form-item prop="deptName" class="mr10">
-          <el-input @keyup.enter.native="getList" style="width: 240px;" v-model="params.keyWord" placeholder="请输入ICCID或卡号" clearable />
+          <el-input @keyup.enter.native="getList" style="width: 240px;" v-model="params.keyWord" placeholder="请输入关键字搜索" clearable />
         </el-form-item>
         <el-form-item>
           <el-button type="primary" @click="getList">
@@ -19,6 +19,12 @@
             </el-icon>
             重置
           </el-button>
+          <el-button type="primary" @click="toAddItemPage()">
+            <el-icon>
+              <ele-FolderAdd />
+            </el-icon>
+            新增
+          </el-button>
         </el-form-item>
       </el-form>
       <el-table
@@ -28,11 +34,14 @@
         style="width: 100%"
       >
         <el-table-column label="名称" prop="name" align="center" />
-        <el-table-column label="平台类型" prop="types" align="center">
+        <!-- <el-table-column label="平台类型" prop="types" align="center">
         	<template #default="scope">{{ formatOperator(scope.row.types) }}</template>
-        </el-table-column>
+        </el-table-column> -->
         <el-table-column label="状态" prop="simStatus" align="center">
-        	<template #default="scope">{{ formatStatus(scope.row.simStatus) }}</template>
+        	<template #default="scope">
+            <el-tag type="primary" v-if="scope.row.status">{{ formatStatus(scope.row.status) }}</el-tag>
+            <el-tag type="danger" v-else>{{ formatStatus(scope.row.status) }}</el-tag>
+            </template>
         </el-table-column> 
         <el-table-column label="说明" prop="remark" align="center" />       
 
@@ -52,14 +61,18 @@
       />
     </el-card>
     <!-- <EditDept ref="editDeptRef" @deptList="deptList" /> -->
+    <AddOrEditItem ref="AddOrEditItemRef" @updateList="getList()" />
   </div>
 </template>
 
 <script lang="ts" setup>
 import api from '/@/api/iotCard';
+import { defineAsyncComponent, ref, reactive, onMounted } from 'vue';
 import { ElMessageBox, ElMessage } from 'element-plus';
 import { useSearch } from "/@/hooks/useCommon"
 import { useRouter } from 'vue-router';
+const AddOrEditItem = defineAsyncComponent(() => import('./addOrEditItem.vue'));
+
 const { params, tableData, getList, loading } = useSearch<any[]>(
   api.platform.getList,
   "Data"
@@ -69,6 +82,8 @@ getList();
 
 const router = useRouter();
 
+const AddOrEditItemRef = ref();
+
 /** 重置按钮操作 */
 const resetQuery = () => {
 	params.keyWord = ""
@@ -115,23 +130,21 @@ const formatType = (val:any) => {
 }
 
 const formatStatus = (val:any) => {
-  // 1:可激活 2:测试激活 3:测试去激活 4:在用5:停机6:运营商管理状态
+  // 1:开启 0:禁用
   if(val == 1) {
-    return "可激活"
-  }else if(val == 2) {
-    return "测试激活"
-  }else if(val == 3) {
-    return "测试去激活"
-  }else if(val == 4) {
-    return "在用"
-  }else if(val == 5) {
-    return "停机"
-  }else if(val == 6) {
-    return "运营商管理状态"
+    return "开启"
+  }else if(val == 0) {
+    return "禁用"
   }
 }
 
 const onOpenDetail = (item:any) => {
-  router.push('/iotmanager/iotCard/index/detail/'+item.id);
+  // router.push('/iotmanager/iotCard/index/detail/'+item.id);
+  AddOrEditItemRef.value.openDialog(item);
+}
+
+const toAddItemPage = () => {
+  AddOrEditItemRef.value.openDialog();
+  // router.push('/iotmanager/iotCard/platformManage/add');
 }
 </script>

+ 12 - 0
src/views/iot/iotmanager/dashboard.vue

@@ -520,6 +520,18 @@ export default defineComponent({
 				initEchartsResizeFun();
 			}
 		);
+		watch(
+			() => state.lineChartAlarmTotalData,
+			() => {
+				initLineChart();
+			}
+		);
+		watch(
+			() => state.pieChartData,
+			() => {
+				initPieChart();
+			}
+		);
 		// 监听 vuex 中是否开启深色主题
 		watch(
 			() => store.state.themeConfig.themeConfig.isIsDark,

+ 12 - 14
src/views/iot/network/server/create.vue

@@ -92,19 +92,9 @@
             <el-form style="width: 600px;margin: 0 auto;" :model="form" label-width="98px">
               <el-form-item label="协议">
                 <el-select v-model="form.protocol.name" placeholder="请选择协议适配">
-                  <el-option v-for="dict in network_protocols" :key="dict.value" :label="dict.label" :value="dict.value">
-                  </el-option>
-                  <!-- <el-option label="Modbus RTU" value="Modbus RTU" />
-                                    <el-option label="Modbus TCP" value="Modbus TCP" />
-                                    <el-option label="Omron Hostlink" value="Omron Hostlink" />
-                                    <el-option label="Omron FINS UDP" value="Omron FINS UDP" />
-                                    <el-option label="Omron FINS TCP" value="Omron FINS TCP" />
-                                    <el-option label="Simatic S7-200 Smart" value="Simatic S7-200 Smart" />
-                                    <el-option label="Simatic S7-200" value="Simatic S7-200" />
-                                    <el-option label="Simatic S7-300" value="Simatic S7-300" />
-                                    <el-option label="Simatic S7-400" value="Simatic S7-400" />
-                                    <el-option label="Simatic S7-1200" value="Simatic S7-1200" />
-                                    <el-option label="Simatic S7-1500" value="Simatic S7-1500" /> -->
+                  <el-option v-for="dict in messageData" :key="dict.types" :label="dict.title" :value="dict.name"></el-option>
+                  <!-- 增加系统默认的mqtt选项 -->
+                  <el-option label="Sagoo Mqtt" value="SagooMqtt"> </el-option>
                 </el-select>
               </el-form-item>
               <el-form-item label="协议参数">
@@ -135,6 +125,7 @@ import { useRoute } from 'vue-router';
 
 import api from '/@/api/network';
 import api2 from '/@/api/system';
+import deviceApi from '/@/api/device'
 
 interface TableDataState {
   // ids: number[];
@@ -148,6 +139,7 @@ interface TableDataState {
   detail: object,
   form: object,
   certificateList: object[];
+  messageData: object[];
   stick: {
     // 分隔符
     "delimit,omitempty": string,
@@ -218,6 +210,7 @@ export default defineComponent({
       detail: {},
       activeViewName: ['1', '2', '3'],
       certificateList: [],
+      messageData: [],
       form: {
         id: "",
         // AccessToken
@@ -245,7 +238,7 @@ export default defineComponent({
         },
         // 协议适配
         protocol: {
-          name: "ModbusTCP",
+          name: "SagooMqtt",
           options: {}
         },
         // 心跳包
@@ -260,6 +253,11 @@ export default defineComponent({
         devices: []
       }
     });
+
+    deviceApi.product.getTypesAll({ types: 'protocol' }).then((res: any) => {
+      state.messageData = res || [];
+    });
+
     const mirrorRef = ref('mirrorRef')
     const activeName = ref('first')
     const getDetail = () => {

+ 12 - 3
src/views/iot/network/server/edit.vue

@@ -92,8 +92,9 @@
             <el-form style="width: 600px;margin: 0 auto;" :model="form" label-width="98px">
               <el-form-item label="协议">
                 <el-select v-model="form.protocol.name" placeholder="请选择协议适配">
-                  <el-option v-for="dict in network_protocols" :key="dict.value" :label="dict.label" :value="dict.value">
-                  </el-option>
+                  <el-option v-for="dict in messageData" :key="dict.types" :label="dict.title" :value="dict.name"></el-option>
+                  <!-- 增加系统默认的mqtt选项 -->
+                  <el-option label="Sagoo Mqtt" value="SagooMqtt"> </el-option>
                 </el-select>
               </el-form-item>
               <el-form-item label="协议参数">
@@ -123,6 +124,7 @@ import { useRoute, useRouter } from 'vue-router';
 
 import api from '/@/api/network';
 import api2 from '/@/api/system';
+import deviceApi from '/@/api/device'
 
 interface TableDataState {
   activeViewName: string[];
@@ -133,6 +135,7 @@ interface TableDataState {
   detail: object,
   form: object,
   certificateList: object[];
+  messageData: object[];
   stick: {
     // 分隔符
     "delimit,omitempty": string,
@@ -201,6 +204,7 @@ export default defineComponent({
         mode: '',
         content: ''
       },
+      messageData: [],
       certificateList: [],
       detail: {},
       activeViewName: ['1', '2', '3'],
@@ -230,7 +234,7 @@ export default defineComponent({
         },
         // 协议适配
         protocol: {
-          name: "ModbusTCP",
+          name: "SagooMqtt",
           options: {}
         },
         // 心跳包
@@ -245,6 +249,11 @@ export default defineComponent({
         devices: []
       }
     });
+
+    deviceApi.product.getTypesAll({ types: 'protocol' }).then((res: any) => {
+      state.messageData = res || [];
+    });
+
     const mirrorRef = ref('mirrorRef')
     const activeName = ref('first')
     const getDetail = () => {

+ 11 - 3
src/views/iot/network/tunnel/create.vue

@@ -81,8 +81,9 @@
             <el-form style="width: 600px;margin: 0 auto;" :model="form" label-width="68px">
               <el-form-item label="协议">
                 <el-select v-model="form.protoccol.name" placeholder="请选择协议适配">
-                  <el-option v-for="dict in network_protocols" :key="dict.value" :label="dict.label" :value="dict.value">
-                  </el-option>
+                  <el-option v-for="dict in messageData" :key="dict.types" :label="dict.title" :value="dict.name"></el-option>
+                  <!-- 增加系统默认的mqtt选项 -->
+                  <el-option label="Sagoo Mqtt" value="SagooMqtt"> </el-option>
                 </el-select>
               </el-form-item>
               <el-form-item label="协议参数">
@@ -108,9 +109,11 @@ import serverDetail from './component/serverDetail.vue'
 import { useRoute, useRouter } from 'vue-router';
 
 import api from '/@/api/network';
+import deviceApi from '/@/api/device'
 
 interface TableDataState {
   activeViewName: string[];
+  messageData: any[];
   resourceModalPro: {
     mode: string,
     content: string,
@@ -142,6 +145,7 @@ export default defineComponent({
         content: ''
       },
       detail: {},
+      messageData: [],
       activeViewName: ['1', '2', '3', '4', '5'],
       form: {
         // 名称
@@ -167,7 +171,7 @@ export default defineComponent({
         },
         // 协议适配
         protoccol: {
-          name: "ModbusTCP",
+          name: "SagooMqtt",
           options: {}
         },
         // 心跳包
@@ -185,6 +189,10 @@ export default defineComponent({
         ]
       }
     });
+    
+    deviceApi.product.getTypesAll({ types: 'protocol' }).then((res: any) => {
+      state.messageData = res || [];
+    });
 
     const mirrorRef = ref('mirrorRef')
     const activeName = ref('first')

+ 239 - 230
src/views/iot/network/tunnel/edit.vue

@@ -1,104 +1,105 @@
 <template>
-    <el-card class="system-dic-container" style="position: relative;">
-        <el-tabs v-model="activeName">
-            <el-tab-pane label="编辑通道" name="first">
-                <el-collapse v-model="activeViewName">
-                    <el-collapse-item title="基本信息" name="1">
-                        <div class="collapse-wrap">
-                            <el-form style="width: 600px;margin: 0 auto;" :model="form" label-width="68px">
-                                <el-form-item label="名称">
-                                    <el-input v-model="form.name" show-word-limit maxlength="20" placeholder="请填写名称" />
-                                </el-form-item>
-                                <el-form-item label="类型">
-                                    <el-select v-model="form.types" placeholder="请选择类型">
-                                        <el-option v-for="dict in network_tunnel_type" :key="dict.value" :label="dict.label" :value="dict.value">
-                                        </el-option>
-                                    </el-select>
-                                </el-form-item>
-                                <el-form-item v-show="form.types != 'serial'" label="地址">
-                                    <el-input v-model="form.addr" placeholder="端口号,IP:端口" />
-                                </el-form-item>
-                                <el-form-item label="启用">
-                                    <el-switch v-model="form.status" :active-value="1" :inactive-value="0" />
-                                </el-form-item>
-                            </el-form>
-                        </div>
-                    </el-collapse-item>
-                    <el-collapse-item v-show="form.types == 'serial'" title="串口参数" name="2">
-                        <div class="collapse-wrap">
-                            <el-form style="width: 600px;margin: 0 auto;" :model="form" label-width="68px">
-                                <el-form-item label="端口">
-                                    <el-button>/dev/ttyS0</el-button>
-                                </el-form-item>
-                                <el-form-item label="波特率">
-                                    <el-select v-model="form.serial.baud_rate" placeholder="请选择波特率">
-                                        <el-option v-for="dict in tunnel_serial_baudrate" :key="dict.value" :label="dict.label" :value="dict.value">
-                                        </el-option>
-                                    </el-select>
-                                </el-form-item>
-                                <el-form-item label="数据位">
-                                    <el-select v-model="form.serial.data_bits" placeholder="请选择数据位">
-                                        <el-option v-for="dict in tunnel_serial_databits" :key="dict.value" :label="dict.label" :value="dict.value">
-                                        </el-option>
-                                    </el-select>
-                                </el-form-item>
-                                <el-form-item label="停止位">
-                                    <el-select v-model="form.serial.stop_bits" placeholder="请选择停止位">
-                                        <el-option v-for="dict in tunnel_serial_stopbits" :key="dict.value" :label="dict.label" :value="dict.value">
-                                        </el-option>
-                                    </el-select>
-                                </el-form-item>
-                                <el-form-item label="检验位">
-                                    <el-select v-model="form.serial.parity" placeholder="请选择检验位">
-                                        <el-option v-for="dict in tunnel_serial_parity" :key="dict.value" :label="dict.label" :value="dict.value">
-                                        </el-option>
-                                    </el-select>
-                                </el-form-item>
-                            </el-form>
-                        </div>
-                    </el-collapse-item>
-                    <el-collapse-item v-show="form.type != 'serial'" title="心跳包" name="4">
-                        <el-form style="width: 600px;margin: 0 auto;" :model="form" label-width="68px">
-                            <el-form-item label="启用">
-                                <el-switch v-model="form.heartbeat.enable" />
-                            </el-form-item>
-                        </el-form>
-                    </el-collapse-item>
-                    <el-collapse-item v-if="form.types == 'serial' || form.types == 'tcp-client' || form.types == 'udp-client'" title="断线重连" name="4">
-                        <el-form style="width: 600px;margin: 0 auto;" :model="form" label-width="68px">
-                            <el-form-item label="启用">
-                                <el-switch v-model="form.retry.enable" />
-                            </el-form-item>
-                            <el-form-item label="间隔">
-                                <el-input-number v-model="form.retry.timeout" :min="0" @change="handleChange" />
-                            </el-form-item>
-                            <el-form-item label="最大次数">
-                                <el-input-number v-model="form.retry.maximum" :min="0" @change="handleChange" />
-                            </el-form-item>
-                        </el-form>
-                    </el-collapse-item>
-                    <el-collapse-item title="协议适配" name="5">
-                        <el-form style="width: 600px;margin: 0 auto;" :model="form" label-width="68px">
-                            <el-form-item label="协议">
-                                <el-select v-model="form.protoccol.name" placeholder="请选择协议适配">
-                                    <el-option v-for="dict in network_protocols" :key="dict.value" :label="dict.label" :value="dict.value">
-                                    </el-option>
-                                </el-select>
-                            </el-form-item>
-                            <el-form-item label="协议参数">
-                                <codeEditor class="params" ref="mirrorRef" :mode="resourceModalPro.mode" :content="resourceModalPro.content">
-                                </codeEditor>
-                            </el-form-item>
-                        </el-form>
-                    </el-collapse-item>
-                </el-collapse>
-            </el-tab-pane>
-        </el-tabs>
-        <div style="position: absolute;right:20px;top: 20px;">
-            <el-button size="medium" @click="$router.replace('/iotmanager/network/tunnel')">取消</el-button>
-            <el-button @click="submit" size="medium" type="primary">提交</el-button>
-        </div>
-    </el-card>
+  <el-card class="system-dic-container" style="position: relative;">
+    <el-tabs v-model="activeName">
+      <el-tab-pane label="编辑通道" name="first">
+        <el-collapse v-model="activeViewName">
+          <el-collapse-item title="基本信息" name="1">
+            <div class="collapse-wrap">
+              <el-form style="width: 600px;margin: 0 auto;" :model="form" label-width="68px">
+                <el-form-item label="名称">
+                  <el-input v-model="form.name" show-word-limit maxlength="20" placeholder="请填写名称" />
+                </el-form-item>
+                <el-form-item label="类型">
+                  <el-select v-model="form.types" placeholder="请选择类型">
+                    <el-option v-for="dict in network_tunnel_type" :key="dict.value" :label="dict.label" :value="dict.value">
+                    </el-option>
+                  </el-select>
+                </el-form-item>
+                <el-form-item v-show="form.types != 'serial'" label="地址">
+                  <el-input v-model="form.addr" placeholder="端口号,IP:端口" />
+                </el-form-item>
+                <el-form-item label="启用">
+                  <el-switch v-model="form.status" :active-value="1" :inactive-value="0" />
+                </el-form-item>
+              </el-form>
+            </div>
+          </el-collapse-item>
+          <el-collapse-item v-show="form.types == 'serial'" title="串口参数" name="2">
+            <div class="collapse-wrap">
+              <el-form style="width: 600px;margin: 0 auto;" :model="form" label-width="68px">
+                <el-form-item label="端口">
+                  <el-button>/dev/ttyS0</el-button>
+                </el-form-item>
+                <el-form-item label="波特率">
+                  <el-select v-model="form.serial.baud_rate" placeholder="请选择波特率">
+                    <el-option v-for="dict in tunnel_serial_baudrate" :key="dict.value" :label="dict.label" :value="dict.value">
+                    </el-option>
+                  </el-select>
+                </el-form-item>
+                <el-form-item label="数据位">
+                  <el-select v-model="form.serial.data_bits" placeholder="请选择数据位">
+                    <el-option v-for="dict in tunnel_serial_databits" :key="dict.value" :label="dict.label" :value="dict.value">
+                    </el-option>
+                  </el-select>
+                </el-form-item>
+                <el-form-item label="停止位">
+                  <el-select v-model="form.serial.stop_bits" placeholder="请选择停止位">
+                    <el-option v-for="dict in tunnel_serial_stopbits" :key="dict.value" :label="dict.label" :value="dict.value">
+                    </el-option>
+                  </el-select>
+                </el-form-item>
+                <el-form-item label="检验位">
+                  <el-select v-model="form.serial.parity" placeholder="请选择检验位">
+                    <el-option v-for="dict in tunnel_serial_parity" :key="dict.value" :label="dict.label" :value="dict.value">
+                    </el-option>
+                  </el-select>
+                </el-form-item>
+              </el-form>
+            </div>
+          </el-collapse-item>
+          <el-collapse-item v-show="form.type != 'serial'" title="心跳包" name="4">
+            <el-form style="width: 600px;margin: 0 auto;" :model="form" label-width="68px">
+              <el-form-item label="启用">
+                <el-switch v-model="form.heartbeat.enable" />
+              </el-form-item>
+            </el-form>
+          </el-collapse-item>
+          <el-collapse-item v-if="form.types == 'serial' || form.types == 'tcp-client' || form.types == 'udp-client'" title="断线重连" name="4">
+            <el-form style="width: 600px;margin: 0 auto;" :model="form" label-width="68px">
+              <el-form-item label="启用">
+                <el-switch v-model="form.retry.enable" />
+              </el-form-item>
+              <el-form-item label="间隔">
+                <el-input-number v-model="form.retry.timeout" :min="0" @change="handleChange" />
+              </el-form-item>
+              <el-form-item label="最大次数">
+                <el-input-number v-model="form.retry.maximum" :min="0" @change="handleChange" />
+              </el-form-item>
+            </el-form>
+          </el-collapse-item>
+          <el-collapse-item title="协议适配" name="5">
+            <el-form style="width: 600px;margin: 0 auto;" :model="form" label-width="68px">
+              <el-form-item label="协议">
+                <el-select v-model="form.protoccol.name" placeholder="请选择协议适配">
+                  <el-option v-for="dict in messageData" :key="dict.types" :label="dict.title" :value="dict.name"></el-option>
+                  <!-- 增加系统默认的mqtt选项 -->
+                  <el-option label="Sagoo Mqtt" value="SagooMqtt"> </el-option>
+                </el-select>
+              </el-form-item>
+              <el-form-item label="协议参数">
+                <codeEditor class="params" ref="mirrorRef" :mode="resourceModalPro.mode" :content="resourceModalPro.content">
+                </codeEditor>
+              </el-form-item>
+            </el-form>
+          </el-collapse-item>
+        </el-collapse>
+      </el-tab-pane>
+    </el-tabs>
+    <div style="position: absolute;right:20px;top: 20px;">
+      <el-button size="medium" @click="$router.replace('/iotmanager/network/tunnel')">取消</el-button>
+      <el-button @click="submit" size="medium" type="primary">提交</el-button>
+    </div>
+  </el-card>
 </template>
 <script lang="ts">
 import { toRefs, reactive, onMounted, ref, defineComponent, getCurrentInstance } from 'vue';
@@ -110,142 +111,150 @@ import serverDetail from './component/serverDetail.vue'
 import { useRoute, useRouter } from 'vue-router';
 
 import api from '/@/api/network';
+import deviceApi from '/@/api/device'
 
 interface TableDataState {
-    activeViewName: string[];
-    resourceModalPro: {
-        mode: string,
-        content: string,
-    },
-    detail: object,
-    form: object
+  activeViewName: string[];
+  messageData: any[];
+  resourceModalPro: {
+    mode: string,
+    content: string,
+  },
+  detail: object,
+  form: object
 }
 export default defineComponent({
-    name: 'tunnelCreate',
-    components: { codeEditor, serverDetail },
-    props: {
-        type: {
-            type: String,
-            default: ''
+  name: 'tunnelCreate',
+  components: { codeEditor, serverDetail },
+  props: {
+    type: {
+      type: String,
+      default: ''
+    }
+  },
+
+  setup(props, context) {
+    const { proxy } = getCurrentInstance() as any;
+    const route = useRoute();
+    const router = useRouter();
+    const { network_tunnel_type, tunnel_serial_baudrate, tunnel_serial_databits, tunnel_serial_stopbits, tunnel_serial_parity, network_protocols } = proxy.useDict('network_tunnel_type', 'tunnel_serial_baudrate', 'tunnel_serial_databits', 'tunnel_serial_stopbits', 'tunnel_serial_parity', 'network_protocols');
+
+    const state = reactive<TableDataState>({
+      resourceModalPro: {
+        mode: '',
+        content: ''
+      },
+      detail: {},
+      activeViewName: ['1', '2', '3', '4', '5'],
+      messageData: [],
+      form: {
+        id: '',
+        // 名称
+        name: '新建通道',
+        // 类型
+        types: 'serial',
+        // 禁用
+        status: false,
+        // 地址
+        addr: '',
+        // 串口参数
+        serial: {
+          baud_rate: "9600",
+          data_bits: "6",
+          stop_bits: "1",
+          parity: '0'
+        },
+        // 断线重连
+        retry: {
+          enable: true,
+          timeout: 30,
+          maximum: 0,
+        },
+        // 协议适配
+        protoccol: {
+          name: "SagooMqtt",
+          options: {}
+        },
+        // 心跳包
+        heartbeat: {
+          enable: false,
+          hex: "",
+          regex: "^\\w+$",
+          text: "",
+          timeout: 30
         }
-    },
+      }
+    });
 
-    setup(props, context) {
-        const { proxy } = getCurrentInstance() as any;
-        const route = useRoute();
-        const router = useRouter();
-        const { network_tunnel_type, tunnel_serial_baudrate, tunnel_serial_databits, tunnel_serial_stopbits, tunnel_serial_parity, network_protocols } = proxy.useDict('network_tunnel_type', 'tunnel_serial_baudrate', 'tunnel_serial_databits', 'tunnel_serial_stopbits', 'tunnel_serial_parity', 'network_protocols');
+    deviceApi.product.getTypesAll({ types: 'protocol' }).then((res: any) => {
+      state.messageData = res || [];
+    });
 
-        const state = reactive<TableDataState>({
-            resourceModalPro: {
-                mode: '',
-                content: ''
-            },
-            detail: {},
-            activeViewName: ['1', '2', '3', '4', '5'],
-            form: {
-                id: '',
-                // 名称
-                name: '新建通道',
-                // 类型
-                types: 'serial',
-                // 禁用
-                status: false,
-                // 地址
-                addr: '',
-                // 串口参数
-                serial: {
-                    baud_rate: "9600",
-                    data_bits: "6",
-                    stop_bits: "1",
-                    parity: '0'
-                },
-                // 断线重连
-                retry: {
-                    enable: true,
-                    timeout: 30,
-                    maximum: 0,
-                },
-                // 协议适配
-                protoccol: {
-                    name: "Modbus RTU",
-                    options: {}
-                },
-                // 心跳包
-                heartbeat: {
-                    enable: false,
-                    hex: "",
-                    regex: "^\\w+$",
-                    text: "",
-                    timeout: 30
-                }
-            }
-        });
-        const activeName = ref('first')
-        const mirrorRef = ref('mirrorRef')
-        const submit = () => {
-            // 串口参数-检验位-无
-            if (state.form.serial.parity == 0) {
-                state.form.serial.rs485 = false
-                delete state.form.serial.port
-            }
-            // 串口参数-检验位-偶/奇
-            if (state.form.serial.parity == 1 || state.form.serial.parity == 2) {
-                state.form.serial.port = null
-                delete state.form.serial.rs485
-            }
-            // return
-            api.tunnel.editItem({ ...state.form }).then((res: any) => {
-                ElMessage.success('修改成功')
-                router.go(-1);
-            });
-        };
-        const getDetail = () => {
-            const id = route.params && route.params.id;
-            api.tunnel.getDetail({ "id": id }).then((res: any) => {
-                const { id, name, types, status, addr, serial, retry, protoccol, heartbeat } = res
-                state.form['name'] = name
-                state.form['types'] = types
-                state.form['addr'] = addr
-                state.form['status'] = status
-                state.form['serial'] = JSON.parse(serial || '{}')
-                state.form['retry'] = JSON.parse(retry || '{}')
-                state.form['heartbeat'] = JSON.parse(heartbeat || '{}')
-                state.form['protoccol'] = protoccol ? JSON.parse(protoccol) : { name: "Modbus RTU", options: {} }
-                state.form['id'] = id
-                if (protoccol) {
-                    let jsonData = JSON.stringify(JSON.parse(protoccol).options)
-                    state.resourceModalPro.content = JSON.stringify(JSON.parse(jsonData), null, 4);
-                    mirrorRef.value.setValue(state.resourceModalPro.content);
-                }
-            })
-        };
-        onMounted(() => {
-            getDetail();
-        });
-        return {
-            mirrorRef,
-            activeName,
-            getDetail,
-            network_tunnel_type,
-            tunnel_serial_baudrate,
-            tunnel_serial_databits,
-            tunnel_serial_stopbits,
-            tunnel_serial_parity,
-            network_protocols,
-            submit,
-            ...toRefs(props),
-            ...toRefs(state),
-        };
-    },
+    const activeName = ref('first')
+    const mirrorRef = ref('mirrorRef')
+    const submit = () => {
+      // 串口参数-检验位-无
+      if (state.form.serial.parity == 0) {
+        state.form.serial.rs485 = false
+        delete state.form.serial.port
+      }
+      // 串口参数-检验位-偶/奇
+      if (state.form.serial.parity == 1 || state.form.serial.parity == 2) {
+        state.form.serial.port = null
+        delete state.form.serial.rs485
+      }
+      // return
+      api.tunnel.editItem({ ...state.form }).then((res: any) => {
+        ElMessage.success('修改成功')
+        router.go(-1);
+      });
+    };
+    const getDetail = () => {
+      const id = route.params && route.params.id;
+      api.tunnel.getDetail({ "id": id }).then((res: any) => {
+        const { id, name, types, status, addr, serial, retry, protoccol, heartbeat } = res
+        state.form['name'] = name
+        state.form['types'] = types
+        state.form['addr'] = addr
+        state.form['status'] = status
+        state.form['serial'] = JSON.parse(serial || '{}')
+        state.form['retry'] = JSON.parse(retry || '{}')
+        state.form['heartbeat'] = JSON.parse(heartbeat || '{}')
+        state.form['protoccol'] = protoccol ? JSON.parse(protoccol) : { name: "Modbus RTU", options: {} }
+        state.form['id'] = id
+        if (protoccol) {
+          let jsonData = JSON.stringify(JSON.parse(protoccol).options)
+          state.resourceModalPro.content = JSON.stringify(JSON.parse(jsonData), null, 4);
+          mirrorRef.value.setValue(state.resourceModalPro.content);
+        }
+      })
+    };
+    onMounted(() => {
+      getDetail();
+    });
+    return {
+      mirrorRef,
+      activeName,
+      getDetail,
+      network_tunnel_type,
+      tunnel_serial_baudrate,
+      tunnel_serial_databits,
+      tunnel_serial_stopbits,
+      tunnel_serial_parity,
+      network_protocols,
+      submit,
+      ...toRefs(props),
+      ...toRefs(state),
+    };
+  },
 });
 </script>
 
 <style>
 .CodeMirror {
-    width: 100%;
-    height: 600px;
-    font-size: 16px;
+  width: 100%;
+  height: 600px;
+  font-size: 16px;
 }
 </style>
 <style lang="scss" scoped>
@@ -253,13 +262,13 @@ export default defineComponent({
     position: relative;
     padding-left: 20px;
 
-    .el-collapse-item__arrow {
-        margin: 0 !important;
-        position: absolute;
-        left: 0;
-        right: 0;
+  .el-collapse-item__arrow {
+    margin: 0 !important;
+    position: absolute;
+    left: 0;
+    right: 0;
 
-    }
+  }
 }
 
 :deep(.el-input),

+ 11 - 9
src/views/iot/operate/remoteconf/index.vue

@@ -176,16 +176,18 @@ export default defineComponent({
     // 页面加载时
     onMounted(() => {
       api.remoteconf.getProductList({ status: '1', name: '' }).then((res: any) => {
-        productOptions.value = res.product.map((item: any) => {
-          return {
-            value: item.key,
-            label: item.name,
-            status: item.status,
+        if (res.product) {
+          productOptions.value = res.product.map((item: any) => {
+            return {
+              value: item.key,
+              label: item.name,
+              status: item.status,
+            }
+          })
+          if (productOptions.value.length > 0) {
+            product.value = productOptions.value[0].value
+            selectProduct.value = productOptions.value[0].label
           }
-        })
-        if (productOptions.value.length > 0) {
-          product.value = productOptions.value[0].value
-          selectProduct.value = productOptions.value[0].label
         }
       })
     })

+ 11 - 3
src/views/iot/ota-update/update/component/deviceList.vue

@@ -1,6 +1,6 @@
 <template>
   <div class="ota-edit-module-container">
-    <el-dialog :title="'设备详情'" v-model="isShowDialog" width="769px">
+    <el-dialog :title="'设备详情'" :before-close="closeDialog" v-model="isShowDialog" width="769px">
       <div class="search">
         <el-form inline ref="queryRef">
           <el-form-item label="设备名称:" prop="name">
@@ -84,6 +84,7 @@ interface TableDataState {
     };
   };
   isShowDialog: boolean;
+  timeoutTimer: any;
 }
 
 export default defineComponent({
@@ -102,6 +103,7 @@ export default defineComponent({
         },
       },
       isShowDialog: false,
+      timeoutTimer: null,
     });
     // 打开弹窗
     const openDialog = (row: any) => {
@@ -112,9 +114,11 @@ export default defineComponent({
         state.tableData.total = res.Total;
       }).finally(() => (state.tableData.loading = false));
       state.isShowDialog = true;
+      timer()
     };
     // 关闭弹窗
     const closeDialog = () => {
+      clearTimeout(state.timeoutTimer);
       state.isShowDialog = false;
     };
     // 取消
@@ -127,6 +131,7 @@ export default defineComponent({
         state.tableData.data = res.Data;
         state.tableData.total = res.Total;
       }).finally(() => (state.tableData.loading = false));
+      timer();
     };
     // 手动下发
     const distribute = (row: any) => {
@@ -135,10 +140,13 @@ export default defineComponent({
       api.batch.distribute({deviceKey: deviceKey, strategyId: strategyId}).then(() => {
         ElMessage.success('操作成功');
       })
+    }
+    // 定时请求列表
+    const timer = () => {
       // 因列表更新数据不是实时更新,需设置定时后在请求列表
-      setTimeout(() => {
+      state.timeoutTimer = setTimeout(() => {
         getDetail();
-      }, 500);
+      }, 3000);
     }
     return {
       getDetail,

+ 4 - 0
src/views/iot/ota-update/update/component/edit.vue

@@ -115,6 +115,7 @@ interface RuleFormState {
   url: string;
   ossurl: string,
   urlName: string;
+  size: string;
 }
 
 interface UpdateState {
@@ -169,6 +170,7 @@ export default defineComponent({
         url: '',
         ossurl: '',
         urlName: '',
+        size: '',
       },
       productData: [],
       moduleData: [],
@@ -246,6 +248,7 @@ export default defineComponent({
         url: '',
         ossurl: '',
         urlName: '',
+        size: '',
       };
     };
     const updateImg = (res: any) => {
@@ -253,6 +256,7 @@ export default defineComponent({
         state.ruleForm.url = res.data.full_path
         state.ruleForm.urlName = res.data.name
         state.ruleForm.ossurl = res.data.full_path
+        state.ruleForm.size = res.data.size;
         fileList.value = []
         ElMessage.success('上传成功');
       } else {

+ 14 - 0
src/views/login/component/account.vue

@@ -44,6 +44,9 @@
 				<span>{{ $t('message.account.accountBtnText') }}</span>
 			</el-button>
 		</el-form-item>
+		<!-- <el-form-item class="login-animation4">
+			<img src="/@/assets/gitee.svg" alt="" class="gitee" @click="authLogin('gitee')">
+		</el-form-item> -->
 		<changePwd ref="changePwdRef"></changePwd>
 	</el-form>
 </template>
@@ -97,6 +100,7 @@ export default defineComponent({
 		});
 		onMounted(() => {
 			getCaptcha();
+			// api.login.ssoList()
 		});
 		// 时间获取
 		const currentTime = computed(() => {
@@ -110,6 +114,15 @@ export default defineComponent({
 			});
 		};
 
+		function authLogin(type: string) {
+			if (type === 'gitee') {
+				const client_id = 'a0585ded445f240f2adc7957989bdd644fa2cdf0db7d98b0a940ec92df6a0934'
+				const redirect_uri = 'http://localhost:8888/#/sso/gitee'
+				window.open(`https://gitee.com/oauth/authorize?client_id=${client_id}&redirect_uri=${encodeURIComponent(redirect_uri)}&response_type=code`)
+				return
+			}
+		}
+
 		// 登录
 		const onSignIn = () => {
 			// 验证表单
@@ -222,6 +235,7 @@ export default defineComponent({
 			changePwdRef,
 			onSignIn,
 			getCaptcha,
+			authLogin,
 			...toRefs(state),
 		};
 	},

+ 282 - 0
src/views/sso/component/account.vue

@@ -0,0 +1,282 @@
+<template>
+	<el-form ref="loginForm" size="large" class="login-content-form" :model="ruleForm" :rules="formRules">
+		<el-form-item class="login-animation1" prop="userName">
+			<el-input type="text" :placeholder="$t('message.account.accountPlaceholder1')" v-model="ruleForm.userName" clearable autocomplete="off">
+				<template #prefix>
+					<el-icon class="el-input__icon">
+						<ele-User />
+					</el-icon>
+				</template>
+			</el-input>
+		</el-form-item>
+		<el-form-item class="login-animation2" prop="password">
+			<el-input :type="isShowPassword ? 'text' : 'password'" :placeholder="$t('message.account.accountPlaceholder2')" v-model="ruleForm.password" autocomplete="off" @keyup.enter="onSignIn">
+				<template #prefix>
+					<el-icon class="el-input__icon">
+						<ele-Unlock />
+					</el-icon>
+				</template>
+				<template #suffix>
+					<i class="iconfont el-input__icon login-content-password" :class="isShowPassword ? 'icon-yincangmima' : 'icon-xianshimima'" @click="isShowPassword = !isShowPassword">
+					</i>
+				</template>
+			</el-input>
+		</el-form-item>
+		<el-form-item class="login-animation3" prop="captcha">
+			<el-col :span="15">
+				<el-input type="text" maxlength="4" :placeholder="$t('message.account.accountPlaceholder3')" v-model="ruleForm.captcha" clearable autocomplete="off" @keyup.enter="onSignIn">
+					<template #prefix>
+						<el-icon class="el-input__icon">
+							<ele-Position />
+						</el-icon>
+					</template>
+				</el-input>
+			</el-col>
+			<el-col :span="1"></el-col>
+			<el-col :span="8">
+				<div class="login-content-code">
+					<el-image class="login-content-code-img" @click="getCaptcha" width="130" height="38" :src="captchaSrc" style="cursor: pointer" />
+				</div>
+			</el-col>
+		</el-form-item>
+		<el-form-item class="login-animation4">
+			<el-button type="primary" class="login-content-submit" @click="onSignIn" :loading="loading.signIn">
+				<span>{{ $t('message.account.accountBtnText') }}</span>
+			</el-button>
+		</el-form-item>
+		<changePwd ref="changePwdRef"></changePwd>
+	</el-form>
+</template>
+
+<script lang="ts">
+import { ref, toRefs, reactive, defineComponent, computed, onMounted, getCurrentInstance } from 'vue';
+import { useRoute, useRouter } from 'vue-router';
+import { ElMessage } from 'element-plus';
+import { useI18n } from 'vue-i18n';
+import { initFrontEndControlRoutes } from '/@/router/frontEnd';
+import { initBackEndControlRoutes } from '/@/router/backEnd';
+import { useStore } from '/@/store/index';
+import { Session, Local } from '/@/utils/storage';
+import { formatAxis } from '/@/utils/formatTime';
+import { encrypt } from '/@/utils/rsa'
+import api from '/@/api/system';
+
+// 是否是开源版本
+const ISOPEN = import.meta.env.VITE_ISOPEN
+
+export default defineComponent({
+	name: 'loginAccount',
+	setup() {
+		const changePwdRef = ref();
+		const { t } = useI18n();
+		const store = useStore();
+		const route = useRoute();
+		const router = useRouter();
+		const { proxy } = getCurrentInstance() as any;
+		const state = reactive({
+			isShowPassword: false,
+			ruleForm: {
+				userName: ISOPEN ? 'demo' : '',
+				password: ISOPEN ? 'demo123456' : '',
+				captcha: '',
+				VerifyKey: '',
+			},
+			formRules: {
+				userName: [{ required: true, trigger: 'blur', message: '用户名不能为空' }],
+				password: [{ required: true, trigger: 'blur', message: '密码不能为空' }],
+				captcha: [{ required: true, trigger: 'blur', message: '验证码不能为空' }],
+			},
+			loading: {
+				signIn: false,
+			},
+			captchaSrc: '',
+		});
+		onMounted(() => {
+			getCaptcha();
+		});
+		// 时间获取
+		const currentTime = computed(() => {
+			return formatAxis(new Date());
+		});
+
+		const getCaptcha = () => {
+			api.login.captcha().then((res: any) => {
+				state.captchaSrc = res.img;
+				state.ruleForm.VerifyKey = res.key;
+			});
+		};
+
+		// 登录
+		const onSignIn = () => {
+			// 验证表单
+			proxy.$refs.loginForm
+				.validate(async (valid: boolean) => {
+					if (valid) {
+						state.loading.signIn = true;
+						let password: string
+						if (sessionStorage.isRsaEnabled) {
+							password = await encrypt(state.ruleForm.password)
+						} else {
+							password = state.ruleForm.password
+						}
+						api.login
+							.login({
+								...state.ruleForm,
+								password
+							})
+							.then(async (res: any) => {
+								// 检查是否需要更换密码
+								if (res.isChangePwd) {
+									ElMessage.error(`密码已超过${sessionStorage.sysPasswordChangePeriod}天未修改,请先修改密码再登录`)
+									state.loading.signIn = false;
+									getCaptcha();
+									return changePwdRef.value.toShow({
+										userName: state.ruleForm.userName,
+										oldUserPassword: state.ruleForm.password,
+									})
+								}
+
+								localStorage.setItem('token', res.token);
+								const userInfos = res.userInfo;
+								userInfos.avatar = proxy.getUpFileUrl(userInfos.avatar);
+								// 存储 token 到浏览器缓存
+								Local.set('userInfo', userInfos);
+								// 存储用户信息到浏览器缓存
+								Session.set('userInfo', userInfos);
+
+
+								// 获取权限配置,上传文件类型等
+								const [columnRes, buttonRes, uploadFileRes] = await Promise.all([api.getInfoByKey('sys.column.switch'), api.getInfoByKey('sys.button.switch'), api.getInfoByKey('sys.uploadFile.way')])
+
+								const isSecurityControlEnabled = sessionStorage.isSecurityControlEnabled || null
+								localStorage.setItem('btnNoAuth', (isSecurityControlEnabled && Number(buttonRes?.data?.configValue)) ? '' : '1');
+								localStorage.setItem('colNoAuth', (isSecurityControlEnabled && Number(columnRes?.data?.configValue)) ? '' : '1');
+								localStorage.setItem('uploadFileWay', uploadFileRes?.data?.configValue || '0');
+
+								await store.dispatch('userInfos/setUserInfos', userInfos);
+
+								currentUser();
+							})
+							.catch(() => {
+								state.loading.signIn = false;
+								getCaptcha();
+							});
+					}
+				})
+				.catch(() => { });
+		};
+		// 获取登录用户信息
+		const currentUser = async () => {
+			api.login.currentUser().then(async (res: any) => {
+				localStorage.setItem('userId', res.Info.id);
+				// 设置用户菜单
+				Session.set('userMenu', res.Data || []);
+				store.dispatch('requestOldRoutes/setBackEndControlRoutes', res || []);
+				if (!store.state.themeConfig.themeConfig.isRequestRoutes) {
+					// 前端控制路由,2、请注意执行顺序
+					await initFrontEndControlRoutes();
+					signInSuccess();
+				} else {
+					// 模拟后端控制路由,isRequestRoutes 为 true,则开启后端控制路由
+					// 添加完动态路由,再进行 router 跳转,否则可能报错 No match found for location with path "/"
+					await initBackEndControlRoutes();
+					// 执行完 initBackEndControlRoutes,再执行 signInSuccess
+					signInSuccess();
+				}
+			});
+			// // 设置按钮权限
+			// Session.set('permissions', res.data.permissions);
+			// // 1、请注意执行顺序(存储用户信息到vuex)
+			// await store.dispatch('userInfos/setPermissions', res.data.permissions);
+		};
+		// 登录成功后的跳转
+		const signInSuccess = () => {
+			// 修改首页重定向的地址,从后台配置中获取首页的地址并在登录之后和刷新页面时进行修改
+			const sysinfo = JSON.parse(localStorage.sysinfo || '{}');
+			const homePage = router.getRoutes().find((item) => item.path === '/');
+			homePage && (homePage.redirect = sysinfo.systemHomePageRoute || '/home');
+			// 初始化登录成功时间问候语
+			let currentTimeInfo = currentTime.value;
+			// 登录成功,跳到转首页
+			// 添加完动态路由,再进行 router 跳转,否则可能报错 No match found for location with path "/"
+			// 如果是复制粘贴的路径,非首页/登录页,那么登录成功后重定向到对应的路径中
+			if (route.query?.redirect) {
+				router.push({
+					path: route.query?.redirect as string,
+					query: route.query.params ? (Object.keys(route.query?.params as string).length > 0 ? JSON.parse(route.query?.params as string) : '') : '',
+				});
+			} else {
+				router.push('/');
+			}
+			// 登录成功提示
+			// 关闭 loading
+			state.loading.signIn = false;
+			const signInText = t('message.signInText');
+			ElMessage.success(`${currentTimeInfo},${signInText}`);
+		};
+		return {
+			changePwdRef,
+			onSignIn,
+			getCaptcha,
+			...toRefs(state),
+		};
+	},
+});
+</script>
+
+<style scoped lang="scss">
+.login-content-form {
+	width: 400px;
+	margin-top: 20px;
+
+	@for $i from 1 through 4 {
+		.login-animation#{$i} {
+			opacity: 0;
+			animation-name: error-num;
+			animation-duration: 0.5s;
+			animation-fill-mode: forwards;
+			animation-delay: calc($i/10) + s;
+		}
+	}
+
+	.login-content-password {
+		display: inline-block;
+		width: 20px;
+		cursor: pointer;
+
+		&:hover {
+			color: #909399;
+		}
+	}
+
+	.login-content-code {
+		display: flex;
+		align-items: center;
+		justify-content: space-around;
+
+		.login-content-code-img {
+			width: 100%;
+			height: 40px;
+			line-height: 40px;
+			background-color: #ffffff;
+			border: 1px solid rgb(220, 223, 230);
+			cursor: pointer;
+			transition: all ease 0.2s;
+			border-radius: 4px;
+			user-select: none;
+
+			&:hover {
+				border-color: #c0c4cc;
+				transition: all ease 0.2s;
+			}
+		}
+	}
+
+	.login-content-submit {
+		width: 100%;
+		letter-spacing: 2px;
+		font-weight: 300;
+		margin-top: 15px;
+	}
+}
+</style>

+ 334 - 0
src/views/sso/index.vue

@@ -0,0 +1,334 @@
+<template>
+	<div class="login-container flex-row">
+		<div class="part">
+			<div class="title">SSO</div>
+		</div>
+	</div>
+</template>
+
+<script lang="ts">
+import { toRefs, reactive, computed, defineComponent, getCurrentInstance } from 'vue';
+import { useRoute, useRouter } from 'vue-router';
+import { useStore } from '/@/store/index';
+import dayjs from 'dayjs';
+import api from '/@/api/system';
+import { Session, Local } from '/@/utils/storage';
+import { initFrontEndControlRoutes } from '/@/router/frontEnd';
+import { initBackEndControlRoutes } from '/@/router/backEnd';
+import { formatAxis } from '/@/utils/formatTime';
+import { ElMessage } from 'element-plus';
+
+// 定义接口来定义对象的类型
+interface LoginState {
+	tabsActiveName: string;
+	isScan: boolean;
+}
+
+export default defineComponent({
+	components: {
+	},
+	data: function () {
+		return {
+			dayjs,
+			sysinfo: {
+				buildVersion: '',
+				systemName: '',
+				buildTime: '',
+				systemCopyright: '',
+				systemLogo: '',
+				systemLoginPIC: '',
+			},
+		};
+	},
+	mounted() {
+		this.sysinfo = JSON.parse(localStorage.sysinfo || '{}');
+	},
+	setup() {
+		const route = useRoute()
+		const router = useRouter();
+		const store = useStore();
+		const { proxy } = getCurrentInstance() as any;
+
+		// 时间获取
+		const currentTime = computed(() => {
+			return formatAxis(new Date());
+		});
+
+		api.login.oauth({
+			code: location.search.split('=')[1],
+			types: route.params.type,
+			state: ''
+		}).then(async (res: any) => {
+
+			localStorage.setItem('token', res.token);
+			const userInfos = res.userInfo;
+			userInfos.avatar = proxy.getUpFileUrl(userInfos.avatar);
+			// 存储 token 到浏览器缓存
+			Local.set('userInfo', userInfos);
+			// 存储用户信息到浏览器缓存
+			Session.set('userInfo', userInfos);
+
+
+			// 获取权限配置,上传文件类型等
+			// const [columnRes, buttonRes, uploadFileRes] = await Promise.all([api.getInfoByKey('sys.column.switch'), api.getInfoByKey('sys.button.switch'), api.getInfoByKey('sys.uploadFile.way')])
+
+			// const isSecurityControlEnabled = sessionStorage.isSecurityControlEnabled || null
+			// localStorage.setItem('btnNoAuth', (isSecurityControlEnabled && Number(buttonRes?.data?.configValue)) ? '' : '1');
+			// localStorage.setItem('colNoAuth', (isSecurityControlEnabled && Number(columnRes?.data?.configValue)) ? '' : '1');
+			// localStorage.setItem('uploadFileWay', uploadFileRes?.data?.configValue || '0');
+
+			await store.dispatch('userInfos/setUserInfos', userInfos);
+
+			currentUser();
+		})
+
+		// 获取登录用户信息
+		const currentUser = async () => {
+			api.login.currentUser().then(async (res: any) => {
+				localStorage.setItem('userId', res.Info.id);
+				// 设置用户菜单
+				Session.set('userMenu', res.Data || []);
+				store.dispatch('requestOldRoutes/setBackEndControlRoutes', res || []);
+				if (!store.state.themeConfig.themeConfig.isRequestRoutes) {
+					// 前端控制路由,2、请注意执行顺序
+					await initFrontEndControlRoutes();
+					signInSuccess();
+				} else {
+					// 模拟后端控制路由,isRequestRoutes 为 true,则开启后端控制路由
+					// 添加完动态路由,再进行 router 跳转,否则可能报错 No match found for location with path "/"
+					await initBackEndControlRoutes();
+					// 执行完 initBackEndControlRoutes,再执行 signInSuccess
+					signInSuccess();
+				}
+			});
+			// // 设置按钮权限
+			// Session.set('permissions', res.data.permissions);
+			// // 1、请注意执行顺序(存储用户信息到vuex)
+			// await store.dispatch('userInfos/setPermissions', res.data.permissions);
+		};
+		// 登录成功后的跳转
+		const signInSuccess = () => {
+			// 修改首页重定向的地址,从后台配置中获取首页的地址并在登录之后和刷新页面时进行修改
+			const sysinfo = JSON.parse(localStorage.sysinfo || '{}');
+			const homePage = router.getRoutes().find((item) => item.path === '/');
+			homePage && (homePage.redirect = sysinfo.systemHomePageRoute || '/home');
+			if (route.query?.redirect) {
+				router.push({
+					path: route.query?.redirect as string,
+					query: route.query.params ? (Object.keys(route.query?.params as string).length > 0 ? JSON.parse(route.query?.params as string) : '') : '',
+				});
+			} else {
+				router.push('/');
+			}
+			// 登录成功提示
+			ElMessage.success('登录成功');
+		};
+
+		return {};
+	},
+});
+</script>
+
+<style scoped lang="scss">
+html[data-theme='dark'] {
+	.login-container {
+		background: #293146;
+	}
+
+	.left {
+		background-image: url(/@/assets/login-bg-dark.svg);
+	}
+
+	.title {
+		color: #aaa;
+	}
+}
+
+.flex {
+	display: flex;
+	align-items: center;
+}
+
+.text {
+	color: #fff;
+}
+
+.switch {
+	position: fixed;
+	right: 20px;
+	top: 20px;
+}
+
+.login-container {
+	width: 100vw;
+	height: 100vh;
+	position: relative;
+	background: #fff;
+
+	.title {
+		font-size: 30px;
+		color: #333;
+		font-weight: bold;
+		letter-spacing: 20px;
+	}
+
+	.logo {
+		font-size: 30px;
+		color: #fff;
+
+		.logoimg {
+			height: 50px;
+			display: block;
+			margin-right: 12px;
+		}
+	}
+
+	.img {
+		width: 50%;
+		display: block;
+		margin: 15vh 0;
+	}
+
+	.part {
+		flex: 1;
+		display: flex;
+		flex-flow: column nowrap;
+		justify-content: center;
+		align-items: center;
+	}
+
+	.left {
+		height: 100vh;
+		background-image: url(/@/assets/login-bg.svg);
+		background-repeat: no-repeat;
+		background-size: auto 100%;
+		background-position: right center;
+		align-items: flex-start;
+		padding-left: 8%;
+	}
+
+	.login-icon-group {
+		width: 100%;
+		height: 100%;
+		position: relative;
+
+		.login-icon-group-title {
+			display: flex;
+			align-items: center;
+			justify-content: center;
+			margin: 12px 0;
+
+			img {
+				width: auto;
+				height: 40px;
+			}
+
+			&-text {
+				padding-left: 20px;
+				color: var(--el-color-primary);
+			}
+		}
+
+		&-icon {
+			width: 60%;
+			height: 70%;
+			position: absolute;
+			left: 0;
+			bottom: 0;
+		}
+	}
+
+	.login-content-out {
+		width: 100%;
+		height: 100%;
+		padding-top: calc(50vh - 227px);
+	}
+
+	.login-content {
+		width: 500px;
+		padding: 20px;
+		margin-left: calc(50% - 500px);
+		background-color: rgba(255, 255, 255, 0.8);
+		border: 5px solid var(--el-color-primary-light-8);
+		border-radius: 5px;
+		overflow: hidden;
+		z-index: 1;
+		position: relative;
+
+		.login-content-main {
+			margin: 0 auto;
+			width: 80%;
+
+			.login-content-title {
+				color: var(--el-text-color-primary);
+				font-weight: 500;
+				font-size: 22px;
+				text-align: center;
+				letter-spacing: 4px;
+				margin: 15px 0 30px;
+				white-space: nowrap;
+				z-index: 5;
+				position: relative;
+				transition: all 0.3s ease;
+			}
+		}
+
+		.login-content-main-sacn {
+			position: absolute;
+			top: 0;
+			right: 0;
+			width: 50px;
+			height: 50px;
+			overflow: hidden;
+			cursor: pointer;
+			transition: all ease 0.3s;
+			color: var(--el-text-color-primary);
+
+			&-delta {
+				position: absolute;
+				width: 35px;
+				height: 70px;
+				z-index: 2;
+				top: 2px;
+				right: 21px;
+				background: var(--el-color-white);
+				transform: rotate(-45deg);
+			}
+
+			&:hover {
+				opacity: 1;
+				transition: all ease 0.3s;
+				color: var(--el-color-primary) !important;
+			}
+
+			i {
+				width: 47px;
+				height: 50px;
+				display: inline-block;
+				font-size: 48px;
+				position: absolute;
+				right: 2px;
+				top: -1px;
+			}
+		}
+	}
+
+	.login-footer {
+		position: absolute;
+		bottom: 5px;
+		width: 100%;
+
+		&-content {
+			width: 100%;
+			display: flex;
+
+			&-warp {
+				margin: auto;
+				color: #e0e3e9;
+				text-align: center;
+				animation: error-num 1s ease-in-out;
+			}
+		}
+	}
+}
+</style>

+ 1 - 5
src/views/system/monitor/plugin/index.vue

@@ -34,11 +34,7 @@
 				<el-table-column label="插件类型" v-col="'types'" align="center" prop="types" />
 				<el-table-column label="功能类型" v-col="'handleType'" align="center" prop="handleType" />
 				<el-table-column label="说明" v-col="'description'" show-overflow-tooltip align="left" prop="description" />
-				<el-table-column label="作者" v-col="'author'" align="center" prop="author">
-					<template #default="scope">
-						{{ JSON.parse(scope.row.author).join(" ") }}
-					</template>
-				</el-table-column>
+				<el-table-column label="作者" v-col="'author'" align="center" prop="author"></el-table-column>
 				<el-table-column label="状态" v-col="'status'" align="center" prop="status" width="80">
 					<template #default="scope">
 						<el-tag type="success" size="small" v-if="scope.row.status === 1">正常</el-tag>

この差分においてかなりの量のファイルが変更されているため、一部のファイルを表示していません