Parcourir la source

Merge branch 'master' of http://git.mydig.net/Sagoo-Cloud/sagoo-admin-ui into professional2

Xiahai il y a 1 an
Parent
commit
2e55524021
84 fichiers modifiés avec 4577 ajouts et 2232 suppressions
  1. 17 0
      .env.test
  2. 4 1
      README.md
  3. 1 0
      package.json
  4. 1 2
      src/api/device/index.ts
  5. 32 0
      src/api/iotCard/index.ts
  6. 1 0
      src/api/ota/index.ts
  7. 2 0
      src/api/system/index.ts
  8. 12 0
      src/assets/gitee.svg
  9. 3 4
      src/components/vue3cron/vue3cron.vue
  10. 0 2
      src/hooks/useCommonIce104.ts
  11. 2 1
      src/i18n/lang/zh-cn.ts
  12. 3 0
      src/layout/navBars/breadcrumb/user.vue
  13. 4 2
      src/router/index.ts
  14. 8 0
      src/router/route.ts
  15. 8 0
      src/theme/dark.scss
  16. 0 5
      src/theme/fast.scss
  17. 10 0
      src/utils/common.ts
  18. 7 7
      src/views/iot/alarm/setting/index.vue
  19. 4 4
      src/views/iot/certificate/index.vue
  20. 4 4
      src/views/iot/device/channel/component/detail.vue
  21. 4 4
      src/views/iot/device/channel/component/edit.vue
  22. 5 5
      src/views/iot/device/channel/component/taskDialog.vue
  23. 19 11
      src/views/iot/device/instance/component/edit.vue
  24. 1 1
      src/views/iot/device/instance/component/setAttr.vue
  25. 2 2
      src/views/iot/device/instance/component/subDevice.vue
  26. 59 67
      src/views/iot/device/instance/detail.vue
  27. 21 4
      src/views/iot/device/product/component/editPro.vue
  28. 5 5
      src/views/iot/device/template/component/deviceTemplateDialog.vue
  29. 5 5
      src/views/iot/device/template/component/edit.vue
  30. 2 2
      src/views/iot/device/template/detail.vue
  31. 29 35
      src/views/iot/ice104/device/component/edit.vue
  32. 45 46
      src/views/iot/ice104/device/component/editDeviceForm.vue
  33. 9 11
      src/views/iot/ice104/device/detail.vue
  34. 29 35
      src/views/iot/ice104/template/component/addOrEditTemplateTask.vue
  35. 28 43
      src/views/iot/ice104/template/component/edit.vue
  36. 41 49
      src/views/iot/ice104/template/component/editTemplateForm.vue
  37. 10 12
      src/views/iot/ice104/template/detail.vue
  38. 681 0
      src/views/iot/iotCard/dashboard.vue
  39. 712 0
      src/views/iot/iotCard/index/detail.vue
  40. 119 0
      src/views/iot/iotCard/index/index.vue
  41. 151 0
      src/views/iot/iotCard/platformManage/addOrEditItem.vue
  42. 112 0
      src/views/iot/iotCard/platformManage/index.vue
  43. 14 2
      src/views/iot/iotmanager/dashboard.vue
  44. 5 8
      src/views/iot/network/server/component/list.vue
  45. 44 54
      src/views/iot/network/server/component/serverDetail.vue
  46. 17 29
      src/views/iot/network/server/create.vue
  47. 7 10
      src/views/iot/network/server/detail.vue
  48. 366 360
      src/views/iot/network/server/edit.vue
  49. 1 1
      src/views/iot/network/tunnel/component/list.vue
  50. 71 78
      src/views/iot/network/tunnel/component/serverDetail.vue
  51. 14 7
      src/views/iot/network/tunnel/create.vue
  52. 9 13
      src/views/iot/network/tunnel/detail.vue
  53. 243 236
      src/views/iot/network/tunnel/edit.vue
  54. 78 120
      src/views/iot/noticeservices/config/setting.vue
  55. 11 9
      src/views/iot/operate/remoteconf/index.vue
  56. 84 90
      src/views/iot/ota-update/update/component/batch.vue
  57. 6 10
      src/views/iot/ota-update/update/component/check.vue
  58. 30 5
      src/views/iot/ota-update/update/component/deviceList.vue
  59. 4 0
      src/views/iot/ota-update/update/component/edit.vue
  60. 48 43
      src/views/iot/ota-update/update/component/info.vue
  61. 41 74
      src/views/iot/ota-update/update/detail.vue
  62. 78 82
      src/views/iot/property/attribute/index.vue
  63. 1 1
      src/views/iot/property/dossier/component/from.vue
  64. 3 3
      src/views/iot/property/dossier/edit.vue
  65. 80 92
      src/views/iot/property/dossier/index.vue
  66. 4 4
      src/views/iot/scene/list/index.vue
  67. 61 64
      src/views/iot/scene/manage/component/actionItem.vue
  68. 54 55
      src/views/iot/scene/manage/detail.vue
  69. 7 15
      src/views/iot/scene/manage/index.vue
  70. 14 0
      src/views/login/component/account.vue
  71. 282 0
      src/views/sso/component/account.vue
  72. 334 0
      src/views/sso/index.vue
  73. 1 1
      src/views/system/config/index.vue
  74. 6 8
      src/views/system/datahub/modeling/detail.vue
  75. 23 32
      src/views/system/datahub/source/detail.vue
  76. 1 1
      src/views/system/dict/index.vue
  77. 59 61
      src/views/system/manage/post/index.vue
  78. 58 59
      src/views/system/manage/role/index.vue
  79. 11 15
      src/views/system/manage/user/index.vue
  80. 43 51
      src/views/system/monitor/lastLinesLog/index.vue
  81. 65 77
      src/views/system/monitor/loginLog/index.vue
  82. 85 87
      src/views/system/monitor/operLog/index.vue
  83. 1 5
      src/views/system/monitor/plugin/index.vue
  84. 1 1
      src/views/system/monitor/server/index.vue

+ 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'

+ 4 - 1
README.md

@@ -21,4 +21,7 @@
 
 **.env.development.local**
 
-| 可在本地添加此文件进行配置,会覆盖默认配置及【.env.development】的配置,并且git会忽略这个文件,不会对其他的开发者的环境造成影响
+| 可在本地添加此文件进行配置,会覆盖默认配置及【.env.development】的配置,并且git会忽略这个文件,不会对其他的开发者的环境造成影响
+
+
+<el-form :model="params" inline ref="queryRef" @submit.prevent @keyup.enter="queryList">

+ 1 - 0
package.json

@@ -8,6 +8,7 @@
     "dev": "vite --force",
     "build": "vite build",
     "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),
   }
 

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

@@ -0,0 +1,32 @@
+
+/*
+ * @Author: vera_min vera_min@163.com
+ * @Date: 2023-10-23 22:45:52
+ * @LastEditors: vera_min vera_min@163.com
+ * @LastEditTime: 2023-10-25 10:20:08
+ * @FilePath: /sagoo-admin-ui/src/api/ice104/index.ts
+ * @Description: 这是默认设置,请设置`customMade`, 打开koroFileHeader查看配置 进行设置: https://github.com/OBKoro1/koro1FileHeader/wiki/%E9%85%8D%E7%BD%AE
+ */
+import { get, post, del, put } from '/@/utils/request';
+
+export default {
+  // 物联网卡列表
+  simCard: {
+    getList: (params: object) => get('/sim_collect/list', params),
+    addItem: (data: object) => post('/device/add', data),
+    editItem: (data: object) => put('/device/edit', data),
+    deleteItem: (data: object) => del('/sim_collect/delete', data),
+    detailItem: (params: object) => get('/sim_collect/one', params),
+    getFlowDataByDateRange: (data: object) => post('/sim_collect/flow_date', data),
+  },
+  dashboard: {
+    getFlowDataByDateRange: (data: object) => post('/sim_history_traffic/date', data),
+    getTop10Data: (data: object) => post('/sim_traffic_statis/top_flow', data),
+    getFlowData: (data: object) => get('/sim_traffic_statis/get', data)
+  },
+  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),
+  }
+}

+ 1 - 0
src/api/ota/index.ts

@@ -23,6 +23,7 @@ export default {
     del: (ids: number) => del('/operate/ota_strategy/delete', {ids}),
     add: (data: any) => post('/operate/ota_strategy/add', data),
     edit: (data: any) => put('/operate/ota_strategy/edit', data),
+    distribute: (data: any) => post('/operate/ota_strategy/distribute', data)
   },
   device: {
     getList: (data: any) => get('/operate/ota_detail/list', 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),

Fichier diff supprimé car celui-ci est trop grand
+ 12 - 0
src/assets/gitee.svg


+ 3 - 4
src/components/vue3cron/vue3cron.vue

@@ -1,9 +1,8 @@
 
 <template>
   <div class="vue3-cron-div">
-    <!-- <el-button class="language" type="text" @click="state.language = state.language === 'en' ? 'cn' : 'en'">{{
-            state.language === 'en' ? 'cn' : 'en'
-        }}</el-button> -->
+    <el-button class="language" type="text" >
+  </el-button>
     <el-tabs type="border-card">
       <el-tab-pane>
         <template #label>
@@ -255,7 +254,7 @@
         <span> cron预览: </span>
         <el-tag type="primary">
           {{ state.cron }}
-        </el-tag>
+        </el-tag> <span>{秒数} {分钟} {小时} {日期} {月份} {?} {年份}</span>
       </div>
       <div class="buttonDiv" style="text-align: right;">
         <el-button type="primary" size="mini" @click.stop="handleChange">{{ state.text.Save }}</el-button>

+ 0 - 2
src/hooks/useCommonIce104.ts

@@ -53,9 +53,7 @@ export function useSearch<T>(api: any, resKey: string, expandParams?: any) {
     loading.value = true;
     params.total = 0;
     let res = await api(params).finally(() => loading.value = false)
-    console.log(res)
     tableData.value = (resKey ? (res[resKey]) : (res)) || [];
-    console.log(tableData.value)
     params.total = res.total;
   };
 

+ 2 - 1
src/i18n/lang/zh-cn.ts

@@ -94,7 +94,8 @@ export default {
 		dropdown3: '404',
 		dropdown4: '401',
 		dropdown5: '退出登录',
-		dropdown6: '代码仓库',
+		dropdown7: '代码仓库',
+		dropdown8: '在线文档',
 		searchPlaceholder: '菜单搜索:支持中文、路由路径',
 		newTitle: '通知',
 		newBtn: '全部已读',

+ 3 - 0
src/layout/navBars/breadcrumb/user.vue

@@ -65,6 +65,7 @@
         <el-dropdown-menu>
           <!-- <el-dropdown-item command="/home">{{ $t('message.user.dropdown1') }}</el-dropdown-item> -->
           <el-dropdown-item command="/personal">{{ $t('message.user.dropdown2') }}</el-dropdown-item>
+          <el-dropdown-item command="document">{{ $t('message.user.dropdown8') }}</el-dropdown-item>
           <el-dropdown-item divided command="logOut">{{ $t('message.user.dropdown5') }}</el-dropdown-item>
         </el-dropdown-menu>
       </template>
@@ -177,6 +178,8 @@ export default defineComponent({
           .catch(() => { });
       } else if (path === 'wareHouse') {
         window.open('https://sagoo.cn');
+      } else if (path === 'document') {
+        window.open('https://iotdoc.sagoo.cn/')
       } else {
         router.push(path);
       }

+ 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登录',
+		},
 	}
 	/**
 	 * 提示:写在这里的为全屏界面,不建议写在这里

+ 8 - 0
src/theme/dark.scss

@@ -154,6 +154,14 @@
 	.home-card-item {
 		border: 1px solid var(--next-border-color-light) !important;
 	}
+	.flow-line-wrap {
+		background-color: transparent!important;
+		border: 1px solid var(--next-border-color-light) !important;
+		
+		.text {
+		 color: var(--next-bg-menuBar-black) !important;
+		}
+	}
 
 	.el-card {
 		background-color: var(--el-color-white) !important;

+ 0 - 5
src/theme/fast.scss

@@ -22,11 +22,6 @@
 		border: 1px solid var(--next-border-color-light);
 	}
 
-	&:hover {
-		box-shadow: 0 2px 12px var(--next-color-dark-hover);
-		transition: all ease 0.3s;
-	}
-
 	> .el-card,
 	&.el-card {
 		height: 100%;

+ 10 - 0
src/utils/common.ts

@@ -59,3 +59,13 @@ export function selectDictLabel(data: any[], value: string): string {
     })
     return actions.join('');
 }
+
+export function formatSize(kb:number) {
+    if (kb <= 0) {
+        return "0 B";
+    }
+
+    const units = ["B", "KB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB"];
+    const i = Math.floor(Math.log(kb) / Math.log(1024));
+    return (kb / Math.pow(1024, i)).toFixed(2) + " " + units[i];
+}

+ 7 - 7
src/views/iot/alarm/setting/index.vue

@@ -1,14 +1,14 @@
 <template>
-	<div class="page page-full">
+	<div class="page padding bg page-full">
 		<el-form :model="tableData.param" ref="queryRef" inline>
 			<el-form-item class="mb-0">
-				<el-button type="primary" class="ml10" @click="onOpenAdd" v-auth="'add'">
+				<el-button type="primary" @click="onOpenAdd" v-auth="'add'">
 					<el-icon>
 						<ele-FolderAdd />
 					</el-icon>
 					新增告警
 				</el-button>
-				<el-button type="primary" class="ml10" @click="onOpenLevel" v-auth="'level'">
+				<el-button type="primary" @click="onOpenLevel" v-auth="'level'">
 					<el-icon>
 						<ele-Setting />
 					</el-icon>
@@ -16,7 +16,7 @@
 				</el-button>
 			</el-form-item>
 		</el-form>
-		<el-divider />
+		<el-divider class="my-5" />
 		<el-row class="page-full-part">
 			<el-col :span="6" v-for="(item, index) in tableData.data" :key="index">
 				<div class="card">
@@ -28,20 +28,20 @@
 								</div>
 								<div class="card-item-body">
 									<div class="card-item-header">
-										<div class="" v-col="'name'">
+										<div v-col="'name'">
 											<div class="ellipsis card-item-header-name" style="width: 100%; height: 45px">{{ item.name }}</div>
 										</div>
 									</div>
 									<div class="card-item-content" v-col="'alarm'">
 										<div>
 											<label>触发:</label>
-											<div class="">
+											<div>
 												<div>级别:</div>
 											</div>
 										</div>
 										<div>
 											<label>{{ item.triggerTypeName }}</label>
-											<div class="">
+											<div>
 												<div>{{ item.alarmLevel.name }}</div>
 											</div>
 										</div>

+ 4 - 4
src/views/iot/certificate/index.vue

@@ -1,9 +1,9 @@
 <template>
 	<div class="page">
 		<el-card shadow="nover">
-			<el-form :model="state.tableData.param" ref="queryRef" inline label-width="60px">
-				<el-form-item label="关键字" prop="keyWord">
-					<el-input v-model="state.tableData.param.name" placeholder="请输入关键字" clearable @keyup.enter="queryList" />
+			<el-form :model="state.tableData.param" ref="queryRef" inline @submit.prevent @keyup.enter="queryList">
+				<el-form-item label="证书名称" prop="keyWord">
+					<el-input v-model="state.tableData.param.name" placeholder="请输入证书名称" clearable />
 				</el-form-item>
 				<el-form-item>
 					<el-button v-auth="'query'" type="primary" class="ml10" @click="queryList">
@@ -44,7 +44,7 @@
 						<span v-noauth="'startOrStop'">{{ scope.row.status ? '正常' : '暂停' }}</span>
 					</template>
 				</el-table-column>
-				<el-table-column v-col="'handle'" label="操作" width="180" align="center" fixed="right">
+				<el-table-column v-col="'handle'" label="操作" width="100" align="center" fixed="right">
 					<template #default="scope">
 						<el-button size="small" v-auth="'edit'" text type="primary" @click="operate('editParams', scope.row)">编辑</el-button>
 						<el-button size="small" v-auth="'del'" text type="info" @click="operate('delete', scope.row)">删除</el-button>

+ 4 - 4
src/views/iot/device/channel/component/detail.vue

@@ -1,5 +1,5 @@
 <template>
-	<el-dialog title="设备通道详情" v-model="dialogVisible" width="900px" :before-close="clsoeDialog" :close-on-click-modal="false">
+	<el-dialog title="设备通道详情" v-model="dialogVisible" width="900px" :before-close="closeDialog" :close-on-click-modal="false">
 		<div class="page-full" style="height: 60vh;">
 			<el-tabs v-model="activeName">
 				<el-tab-pane label="通道信息" name="1">
@@ -18,7 +18,7 @@
           </el-form-item> -->
 						<el-form-item label="" prop="">
 							<div align="right">
-								<el-button @click="clsoeDialog"> 取 消 </el-button>
+								<el-button @click="closeDialog"> 取 消 </el-button>
 								<!-- <el-button type="primary" @click="updateData()"> 保 存 </el-button> -->
 							</div>
 						</el-form-item>
@@ -116,7 +116,7 @@ export default {
 		downloadLog() {
 			window.open(getOrigin(import.meta.env.VITE_MODBUS_API) + '/debug/export_message?number=' + this.temp.number);
 		},
-		clsoeDialog() {
+		closeDialog() {
 			this.dialogVisible = false;
 			this.activeName = '1';
 			(this.$refs['dataForm'] as any).resetFields();
@@ -128,7 +128,7 @@ export default {
 					const tempData = Object.assign({}, this.temp);
 					api.channel.editDevice(tempData).then(() => {
 						this.$emit('getList');
-						this.clsoeDialog();
+						this.closeDialog();
 						ElMessage.success('操作成功!');
 					});
 				}

+ 4 - 4
src/views/iot/device/channel/component/edit.vue

@@ -1,5 +1,5 @@
 <template>
-	<el-dialog title="添加设备通道" v-model="dialogVisible" width="600px" :before-close="clsoeDialog" :close-on-click-modal="false">
+	<el-dialog title="添加设备通道" v-model="dialogVisible" width="600px" :before-close="closeDialog" :close-on-click-modal="false">
 		<el-form ref="dataForm" :rules="rules" :model="temp" label-position="left" label-width="120px" style="width: 90%; margin: 0 auto">
 			<el-form-item label="通道名称" prop="title">
 				<el-input v-model.trim="temp.title" placeholder="请输入通道名称" />
@@ -12,7 +12,7 @@
 			</el-form-item>
 		</el-form>
 		<template #footer class="dialog-footer">
-			<el-button @click="clsoeDialog()"> 取 消 </el-button>
+			<el-button @click="closeDialog()"> 取 消 </el-button>
 			<el-button type="primary" @click="createData()"> 保 存 </el-button>
 		</template>
 	</el-dialog>
@@ -46,7 +46,7 @@ export default {
 			// this.getList();
 			this.dialogVisible = true;
 		},
-		clsoeDialog() {
+		closeDialog() {
 			(this.$refs.dataForm as any).resetFields();
 			this.dialogVisible = false;
 		},
@@ -67,7 +67,7 @@ export default {
 				if (valid) {
 					api.channel.addDevice(this.temp).then(() => {
 						this.$emit('getList');
-						this.clsoeDialog();
+						this.closeDialog();
 						ElMessage.success('操作成功!');
 					});
 				}

+ 5 - 5
src/views/iot/device/channel/component/taskDialog.vue

@@ -1,5 +1,5 @@
 <template>
-	<el-dialog :title="textMap[dialogStatus]" v-model="dialogVisible" width="500px" :before-close="clsoeDialog" append-to-body :close-on-click-modal="false">
+	<el-dialog :title="textMap[dialogStatus]" v-model="dialogVisible" width="500px" :before-close="closeDialog" append-to-body :close-on-click-modal="false">
 		<el-form ref="dataForm" :rules="rules" :model="temp" label-width="80px">
 			<el-form-item label="标题" prop="title">
 				<el-input v-model="temp.title" placeholder="请输入标题" />
@@ -22,7 +22,7 @@
 			</el-form-item>
 		</el-form>
 		<template #footer class="dialog-footer">
-			<el-button @click="clsoeDialog"> 取 消 </el-button>
+			<el-button @click="closeDialog"> 取 消 </el-button>
 			<el-button type="primary" @click="dialogStatus === 'create' ? createData() : updateData()"> 保 存 </el-button>
 		</template>
 	</el-dialog>
@@ -83,7 +83,7 @@ export default {
 			this.getTemplateList();
 			this.dialogVisible = true;
 		},
-		clsoeDialog() {
+		closeDialog() {
 			this.dialogVisible = false;
 			this.temp = {
 				title: '',
@@ -113,7 +113,7 @@ export default {
 				if (valid) {
 					api.task.addDeviceJob(this.temp).then(() => {
 						this.$emit('finish');
-						this.clsoeDialog();
+						this.closeDialog();
 						ElMessage.success('操作成功!');
 					});
 				}
@@ -124,7 +124,7 @@ export default {
 				if (valid) {
 					api.task.editDeviceJob(this.temp).then(() => {
 						this.$emit('finish');
-						this.clsoeDialog();
+						this.closeDialog();
 						ElMessage.success('操作成功!');
 					});
 				}

+ 19 - 11
src/views/iot/device/instance/component/edit.vue

@@ -66,10 +66,12 @@
           <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>
+<!--					<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>
+<!--					<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>
@@ -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 = () => {

+ 1 - 1
src/views/iot/device/instance/component/setAttr.vue

@@ -89,7 +89,7 @@ function show(row: any) {
 }
 
 function onSubmit() {
-  if (!data.value) return ElMessage('请先输入属性值!')
+  if (data.value === '' || data.value === null || data.value === undefined) return ElMessage('请先输入属性值!')
   loading.value = true
   api.product.propertySet({
     deviceKey: props.deviceKey,

+ 2 - 2
src/views/iot/device/instance/component/subDevice.vue

@@ -11,7 +11,7 @@
 			</div>
 
 			<div class="content-box">
-				<el-tabs v-model="activeName" class="demo-tabs" @tab-click="handleClick">
+				<el-tabs v-model="activeName" @tab-click="handleClick">
 					<el-tab-pane label="运行状态" name="3">
 						<div style="display: flex; padding: 10px; flex-wrap: wrap">
 							<div class="ant-card">
@@ -50,7 +50,7 @@
 									</div>
 
 									<div class="statusname">{{ item.value }}{{ item.unit }}</div>
-									<div class="">
+									<div>
 										<devantd :json="item.list" :antdid="item.key" v-if="item.type == 'int' || item.type == 'float'" />
 									</div>
 								</div>

+ 59 - 67
src/views/iot/device/instance/detail.vue

@@ -1,5 +1,5 @@
 <template>
-  <div class="page bg">
+  <div class="page bg page-full">
     <div class="content">
       <div class="cont_box">
         <div class="title">设备:{{ detail.name }}</div>
@@ -9,11 +9,11 @@
       </div>
     </div>
 
-    <div class="content-box">
-      <el-tabs v-model="activeName" class="demo-tabs" @tab-click="handleClick">
+    <div class="content-box page-full-part page-full">
+      <el-tabs v-model="activeName" @tab-click="handleClick">
 
         <el-tab-pane label="运行状态" name="3">
-          <div style=" display: flex; padding: 10px;flex-wrap: wrap;">
+          <div style=" display: flex;flex-wrap: wrap;">
             <div class="ant-card">
               <div class="ant-card-body">
                 <div class="cardflex">
@@ -62,7 +62,7 @@
                     <div class="name">{{ vare }}</div> -->
                   </div>
                 </div>
-                <div class="">
+                <div>
                   <devantd :json="item.list" :antdid="item.key" v-if="item.type == 'int' || item.type == 'float' || item.type == 'string'" />
                 </div>
               </div>
@@ -88,7 +88,7 @@
             <el-descriptions-item label="链接协议">{{ prodetail.transportProtocol }}</el-descriptions-item>
             <el-descriptions-item label="设备类型">{{ prodetail.deviceType }}</el-descriptions-item>
             <el-descriptions-item label="固件版本">{{ detail.version }}</el-descriptions-item>
-            <el-descriptions-item label="注册时间">{{ detail.updatedAt }}</el-descriptions-item>
+            <el-descriptions-item label="注册时间">{{ detail.registryTime }}</el-descriptions-item>
             <el-descriptions-item label="最后上线时间">{{ detail.lastOnlineTime || '' }}</el-descriptions-item>
             <el-descriptions-item label="详细地址">{{ detail.address }}</el-descriptions-item>
             <el-descriptions-item label="说明">{{ detail.desc }}</el-descriptions-item>
@@ -312,39 +312,39 @@
           </div>
 
         </el-tab-pane>
-        <el-tab-pane label="设备档案" name="7">
+        <el-tab-pane label="设备档案" name="7" v-if="deviceAssetData">
           <el-form label-width="110px">
 
             <!--            <FromData :DataList="Datalist" v-if="Datalist && Datalist.length > 0" disable="true"></FromData>-->
-<!--            <div class="pro-box">-->
-<!--              <div class="protitle">设备档案</div>-->
-<!--              <div>-->
-<!--                <el-button type="primary" v-auth="'edit'" @click="onOpenEditAsset">编辑</el-button>-->
-<!--              </div>-->
-<!--            </div>-->
-
-<!--            <div class="ant-descriptions-view">-->
-<!--              <table>-->
-<!--                <tbody>-->
-<!--                <tr class="ant-descriptions-row" v-for="(item, index) in dataList" :key="index">-->
-<!--                  <th class="ant-descriptions-item-label ant-descriptions-item-colon">{{ item.title }}</th>-->
-<!--                  <td class="ant-descriptions-item-content" colspan="1">-->
-<!--                    <view v-if="item.types === 'file'">-->
-<!--                      <img :src="deviceAssetMetadata[item.name]" class="avatar" />-->
-<!--                    </view>-->
-<!--                    <view v-else>-->
-<!--                      <view v-if="item.pattern">-->
-<!--                        <el-link :href="deviceAssetMetadata[item.name]" type="primary" target="_blank">{{ deviceAssetMetadata[item.name] }}</el-link>-->
-<!--                      </view>-->
-<!--                      <view v-else>-->
-<!--                        {{ deviceAssetMetadata[item.name] }}-->
-<!--                      </view>-->
-<!--                    </view>-->
-<!--                  </td>-->
-<!--                </tr>-->
-<!--                </tbody>-->
-<!--              </table>-->
-<!--            </div>-->
+            <!--            <div class="pro-box">-->
+            <!--              <div class="protitle">设备档案</div>-->
+            <!--              <div>-->
+            <!--                <el-button type="primary" v-auth="'edit'" @click="onOpenEditAsset">编辑</el-button>-->
+            <!--              </div>-->
+            <!--            </div>-->
+
+            <!--            <div class="ant-descriptions-view">-->
+            <!--              <table>-->
+            <!--                <tbody>-->
+            <!--                <tr class="ant-descriptions-row" v-for="(item, index) in dataList" :key="index">-->
+            <!--                  <th class="ant-descriptions-item-label ant-descriptions-item-colon">{{ item.title }}</th>-->
+            <!--                  <td class="ant-descriptions-item-content" colspan="1">-->
+            <!--                    <view v-if="item.types === 'file'">-->
+            <!--                      <img :src="deviceAssetMetadata[item.name]" class="avatar" />-->
+            <!--                    </view>-->
+            <!--                    <view v-else>-->
+            <!--                      <view v-if="item.pattern">-->
+            <!--                        <el-link :href="deviceAssetMetadata[item.name]" type="primary" target="_blank">{{ deviceAssetMetadata[item.name] }}</el-link>-->
+            <!--                      </view>-->
+            <!--                      <view v-else>-->
+            <!--                        {{ deviceAssetMetadata[item.name] }}-->
+            <!--                      </view>-->
+            <!--                    </view>-->
+            <!--                  </td>-->
+            <!--                </tr>-->
+            <!--                </tbody>-->
+            <!--              </table>-->
+            <!--            </div>-->
 
             <div class="pro-box">
               <div class="protitle">设备档案</div>
@@ -472,11 +472,14 @@ interface TableDataState {
 }
 export default defineComponent({
   name: 'deviceEditPro',
-  components: {EditAssetRef, FromData, SubDeviceMutipleBind, SubDevice, EditDic, EditAttr, EditFun, EditEvent, EditTab, devantd, ListDic, functionCom, setAttr },
+  components: { EditAssetRef, FromData, SubDeviceMutipleBind, SubDevice, EditDic, EditAttr, EditFun, EditEvent, EditTab, devantd, ListDic, functionCom, setAttr },
 
   setup(prop, context) {
     const logqueryRef = ref();
 
+    // 属性列表,查询保留小数位使用
+    const propertyMap = new Map()
+
     const array_list = ref([]);
     const route = useRoute();
     const editDicRef = ref();
@@ -569,12 +572,7 @@ export default defineComponent({
         //加载全部属性
         datahub.node.getpropertyList({ key: state.detail.product.key }).then((re: any) => {
           array_list.value = re;
-        });
-
-        //第一次加载
-        api.model.property(state.tableData.param).then((res: any) => {
-          state.tableData.data = res.Data;
-          state.tableData.total = res.Total;
+          re.forEach((item: any) => propertyMap.set(item.key, item?.valueType));
         });
 
         // 加载对应设备档案
@@ -859,23 +857,22 @@ export default defineComponent({
       }
     };
 
-    const getValueText = (key, value) => {
-      let data = array_list.value;
-      for (let i = 0; i < data.length; i++) {
-        const item = data[i];
-        if (item.key === key) {
-          if (item.valueType.type === "enum") {
-            const option = item.valueType.elements.find((element) => element.value === value);
-            if (option) {
-              return option.text;
-            }
-          } else {
-            return value;
-          }
+    const getValueText = (key: String, value: String) => {
+      const item = propertyMap.get(key)
+
+      if (!item) return value
+
+      if (item.type === "enum") {
+        const option = item.elements.find((element: any) => element.value === value);
+        if (option) {
+          return option.text;
         }
+      } else if (item?.type === 'float' && item?.decimals) {
+        //  根据属性确定保留小数位数
+        return Number(value).toFixed(item.decimals)
+      } else {
+        return value;
       }
-
-      return value;
     }
     const getStatusText = (name, value) => {
       let data = array_list.value;
@@ -1030,6 +1027,7 @@ export default defineComponent({
       editAssetRef,
       dataList,
       deviceAssetMetadata,
+      deviceAssetData,
       onOpenListDetail,
       getrunData,
       getlog,
@@ -1163,13 +1161,7 @@ tr {
   border-color: inherit;
 }
 
-.wu-box {
-  border: #e8e8e8 solid 1px;
-  padding: 20px;
-  width: 100%;
-}
-
-.wu-box .wu-title {
+.wu-title {
   display: flex;
   flex-direction: row;
   justify-content: space-between;
@@ -1177,7 +1169,7 @@ tr {
   border-bottom: #e8e8e8 1px solid;
 }
 
-.wu-box .wu-title .title {
+.wu-title .title {
   font-size: 18px;
 }
 
@@ -1198,7 +1190,7 @@ tr {
 }
 
 .ant-card-body {
-  padding: 24px;
+  padding: 12px;
   zoom: 1;
 }
 

+ 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),
 		}
 	},

+ 5 - 5
src/views/iot/device/template/component/deviceTemplateDialog.vue

@@ -19,7 +19,7 @@
 		</el-table>
 		<pagination v-show="total > 0" :total="total" v-model:page="listQuery.page" v-model:limit="listQuery.size" @pagination="getList()" style="padding: 20px 0 0 !important" />
 
-		<el-dialog :title="textMap[dialogStatus]" v-model="dialogVisible" width="850px" :before-close="clsoeDialog" close="var-dialog" append-to-body :close-on-click-modal="false">
+		<el-dialog :title="textMap[dialogStatus]" v-model="dialogVisible" width="850px" :before-close="closeDialog" close="var-dialog" append-to-body :close-on-click-modal="false">
 			<el-form ref="dataForm" :rules="rules" :model="temp" label-position="right" label-width="95px">
 				<el-row>
 					<el-col :span="8">
@@ -94,7 +94,7 @@
 				</el-row>
 			</el-form>
 			<template #footer class="dialog-footer">
-				<el-button @click="clsoeDialog"> 取 消 </el-button>
+				<el-button @click="closeDialog"> 取 消 </el-button>
 				<el-button type="primary" @click="dialogStatus === 'create' ? createData() : updateData()"> 保 存 </el-button>
 			</template>
 		</el-dialog>
@@ -224,7 +224,7 @@ export default {
 				this.$refs['dataForm'].clearValidate();
 			});
 		},
-		clsoeDialog() {
+		closeDialog() {
 			this.dialogVisible = false;
 			// this.$refs.dataForm.resetFields()
 			this.temp = {
@@ -248,7 +248,7 @@ export default {
 					const tempData = Object.assign({}, this.temp);
 					api.data.addDeviceTemplate(tempData).then(() => {
 						this.handleFilter();
-						this.clsoeDialog();
+						this.closeDialog();
 						ElMessage.success('添加成功!');
 					});
 				}
@@ -264,7 +264,7 @@ export default {
 					const tempData = Object.assign({}, this.temp);
 					api.data.editDeviceTemplate(tempData).then(() => {
 						this.handleFilter();
-						this.clsoeDialog();
+						this.closeDialog();
 						ElMessage.success('操作成功!');
 					});
 				}

+ 5 - 5
src/views/iot/device/template/component/edit.vue

@@ -1,5 +1,5 @@
 <template>
-	<el-dialog :title="textMap[dialogStatus]" v-model="dialogVisible" :width="dialogWidth" :before-close="clsoeDialog" :close-on-click-modal="false">
+	<el-dialog :title="textMap[dialogStatus]" v-model="dialogVisible" :width="dialogWidth" :before-close="closeDialog" :close-on-click-modal="false">
 		<div class="page-full" style="height: 65vh;">
 			<el-form class="form" ref="dataForm" :rules="rules" :model="temp" label-position="right" label-width="120px" style="width: 400px">
 				<el-form-item label="模板名称" prop="title">
@@ -25,7 +25,7 @@
 			</el-tabs>
 		</div>
 		<template #footer class="dialog-footer">
-			<el-button @click="clsoeDialog"> 取 消 </el-button>
+			<el-button @click="closeDialog"> 取 消 </el-button>
 			<el-button type="primary" @click="dialogStatus === 'create' ? createData() : updateData()"> 保 存 </el-button>
 		</template>
 	</el-dialog>
@@ -85,7 +85,7 @@ export default {
 				this.getDataId();
 			}
 		},
-		clsoeDialog() {
+		closeDialog() {
 			this.activeName = '1';
 			this.temp = {
 				title: '',
@@ -107,7 +107,7 @@ export default {
 				if (valid) {
 					api.template.addTemplate(this.temp).then(() => {
 						this.$emit('getList');
-						this.clsoeDialog();
+						this.closeDialog();
 						ElMessage.success('操作成功!');
 					});
 				}
@@ -118,7 +118,7 @@ export default {
 				if (valid) {
 					api.template.editTemplate(this.temp).then(() => {
 						this.$emit('getList');
-						this.clsoeDialog();
+						this.closeDialog();
 						ElMessage.success('操作成功!');
 					});
 				}

+ 2 - 2
src/views/iot/device/template/detail.vue

@@ -10,7 +10,7 @@
 		</div>
 
 		<div class="content-box">
-			<el-tabs v-model="activeName" class="demo-tabs" @tab-click="handleClick">
+			<el-tabs v-model="activeName" @tab-click="handleClick">
 				<el-tab-pane label="运行状态" name="3">
 					<div style="display: flex; padding: 10px; flex-wrap: wrap">
 						<div class="ant-card">
@@ -49,7 +49,7 @@
 								</div>
 
 								<div class="statusname">{{ item.value }}{{ item.unit }}</div>
-								<div class="">
+								<div>
 									<devantd :json="item.list" :antdid="item.key" v-if="item.type == 'int' || item.type == 'float'" />
 								</div>
 							</div>

+ 29 - 35
src/views/iot/ice104/device/component/edit.vue

@@ -1,12 +1,6 @@
 <template>
-	<el-dialog
-		:title="isEdit ? '修改设备' : '添加设备'"
-		v-model="dialogVisible"
-		width="600px"
-		:before-close="closeDialog"
-		:close-on-click-modal="false"
-	>
-		<el-form ref="formRef" :rules="formRules" :model="ruleForm" label-position="left" label-width="120px" style="width: 90%; margin: 0 auto">
+	<el-dialog :title="isEdit ? '修改设备' : '添加设备'" v-model="dialogVisible" width="600px" :before-close="closeDialog" :close-on-click-modal="false">
+		<el-form ref="formRef" :rules="formRules" :model="ruleForm" label-position="right" label-width="120px">
 			<el-form-item label="设备编码" prop="number">
 				<el-input :disabled="isEdit" v-model="ruleForm.number" placeholder="请输入设备编码" />
 			</el-form-item>
@@ -82,33 +76,33 @@ const submitData = async () => {
 			api.device.editItem({
 				...ruleForm.value,
 			})
-			.then(() => {
-				ElMessage({ type: 'success', message: '修改成功' })
-				emit('updateList')
-				closeDialog()
-			})
-			.finally(() => (btnLoading.value = false))
+				.then(() => {
+					ElMessage({ type: 'success', message: '修改成功' })
+					emit('updateList')
+					closeDialog()
+				})
+				.finally(() => (btnLoading.value = false))
 		} else {
 			// 新增
 			api.device.addItem(ruleForm.value)
-			.then(() => {
-				ElMessage({ type: 'success', message: '添加成功' })
-				emit('updateList')
-				closeDialog()
-			})
-			.finally(() => (btnLoading.value = false))
+				.then(() => {
+					ElMessage({ type: 'success', message: '添加成功' })
+					emit('updateList')
+					closeDialog()
+				})
+				.finally(() => (btnLoading.value = false))
 		}
 	})
 }
 
-const handleProductChange = (data:any) => {
+const handleProductChange = (data: any) => {
 	ruleForm.value.deviceKey = "";
-	let findItem:any = productList.value.find((v: any) => v.key === data);
+	let findItem: any = productList.value.find((v: any) => v.key === data);
 	getDeviceList(findItem.id)
 }
 
-const getDeviceList = (id:number) => {
-	apiDevice.device.allList({productId: id}).then((res: any) => {
+const getDeviceList = (id: number) => {
+	apiDevice.device.allList({ productId: id }).then((res: any) => {
 		deviceList.value = res.device
 	})
 }
@@ -117,16 +111,16 @@ const getDeviceList = (id:number) => {
  * 关闭弹窗
  */
 const closeDialog = () => {
-  dialogVisible.value = false;
-  ruleForm.value = {
-	number: '',
-	title: '',
-	commonAddr: '',
-	subCode: '',
-	templateNumber: '',
-	productKey: '',
-	deviceKey: ''
-  }
+	dialogVisible.value = false;
+	ruleForm.value = {
+		number: '',
+		title: '',
+		commonAddr: '',
+		subCode: '',
+		templateNumber: '',
+		productKey: '',
+		deviceKey: ''
+	}
 }
 
 
@@ -135,7 +129,7 @@ const open = async (row: any) => {
 	if (row && row.number.toString()) {
 		ruleForm.value = row;
 		isEdit.value = true;
-	}else {
+	} else {
 		isEdit.value = false;
 	}
 }

+ 45 - 46
src/views/iot/ice104/device/component/editDeviceForm.vue

@@ -1,41 +1,40 @@
 <template>
-    <el-form ref="formRef" :rules="formRules" :model="ruleForm" label-position="left" label-width="120px" style="width: 70%;">
-        <el-form-item label="设备编码" prop="number">
-            <el-input :disabled="isEdit" v-model="ruleForm.number" placeholder="请输入设备编码" />
-        </el-form-item>
-        <el-form-item label="名称">
-            <el-input v-model="ruleForm.title" placeholder="请输入名称" />
-        </el-form-item>
-        <el-form-item label="设备通用地址">
-            <el-input type="number" v-model.number="ruleForm.commonAddr" placeholder="请输入设备通用地址" />
-        </el-form-item>
-        <el-form-item label="mac地址">
-            <el-input v-model="ruleForm.subCode" placeholder="请输入mac地址" />
-        </el-form-item>
-        <el-form-item label="模版编号">
-            <el-select v-model="ruleForm.templateNumber" placeholder="请选择模版编号" class="width100">
-                <el-option :label="item.title" :value="item.number" v-for="(item, index) in tableData" :key="index" />
-            </el-select>
-        </el-form-item>
-        <el-form-item label="产品key">
-            <el-select @change="(val) => handleProductChange(val, true)" v-model="ruleForm.productKey" placeholder="请选择产品key" class="width100">
-                <el-option :label="item.name" :value="item.key" v-for="(item, index) in productList" :key="index" />
-            </el-select>
-        </el-form-item>
-        <el-form-item label="设备key" v-if="ruleForm.productKey">
-            <el-select v-model="ruleForm.deviceKey" placeholder="请选择设备key" class="width100">
-                <el-option :label="item.name" :value="item.key" v-for="(item, index) in deviceList" :key="index" />
-            </el-select>
-        </el-form-item>
-        <el-form-item>
-            <el-button type="primary" v-auth="'save'" :loading="btnLoading" @click="submitData"> 保 存 </el-button>
-        </el-form-item>
-    </el-form>
+	<el-form ref="formRef" :rules="formRules" :model="ruleForm" label-position="right" label-width="110px" style="width: 70%;">
+		<el-form-item label="设备编码" prop="number">
+			<el-input :disabled="isEdit" v-model="ruleForm.number" placeholder="请输入设备编码" />
+		</el-form-item>
+		<el-form-item label="名称">
+			<el-input v-model="ruleForm.title" placeholder="请输入名称" />
+		</el-form-item>
+		<el-form-item label="设备通用地址">
+			<el-input type="number" v-model.number="ruleForm.commonAddr" placeholder="请输入设备通用地址" />
+		</el-form-item>
+		<el-form-item label="mac地址">
+			<el-input v-model="ruleForm.subCode" placeholder="请输入mac地址" />
+		</el-form-item>
+		<el-form-item label="模版编号">
+			<el-select v-model="ruleForm.templateNumber" placeholder="请选择模版编号" class="width100">
+				<el-option :label="item.title" :value="item.number" v-for="(item, index) in tableData" :key="index" />
+			</el-select>
+		</el-form-item>
+		<el-form-item label="产品key">
+			<el-select @change="(val) => handleProductChange(val, true)" v-model="ruleForm.productKey" placeholder="请选择产品key" class="width100">
+				<el-option :label="item.name" :value="item.key" v-for="(item, index) in productList" :key="index" />
+			</el-select>
+		</el-form-item>
+		<el-form-item label="设备key" v-if="ruleForm.productKey">
+			<el-select v-model="ruleForm.deviceKey" placeholder="请选择设备key" class="width100">
+				<el-option :label="item.name" :value="item.key" v-for="(item, index) in deviceList" :key="index" />
+			</el-select>
+		</el-form-item>
+		<el-form-item>
+			<el-button type="primary" v-auth="'save'" :loading="btnLoading" @click="submitData"> 保 存 </el-button>
+		</el-form-item>
+	</el-form>
 </template>
 <script lang="ts" setup>
-import { computed, reactive, ref, onMounted } from 'vue';
+import { computed, ref, onMounted } from 'vue';
 import api from '/@/api/ice104/index';
-import { useI18n } from 'vue-i18n';
 import { ElMessage } from 'element-plus';
 import { useSearch } from '/@/hooks/useCommonIce104';
 import apiDevice from '/@/api/device';
@@ -72,26 +71,26 @@ const submitData = async () => {
 			api.device.editItem({
 				...ruleForm.value,
 			})
-			.then(() => {
-				ElMessage({ type: 'success', message: '修改成功' })
-				emit('updateList')
-			})
-			.finally(() => (btnLoading.value = false))
+				.then(() => {
+					ElMessage({ type: 'success', message: '修改成功' })
+					emit('updateList')
+				})
+				.finally(() => (btnLoading.value = false))
 		}
 	})
 }
 
-const handleProductChange = (data:any, isClear: boolean) => {
-	if(isClear) {
+const handleProductChange = (data: any, isClear: boolean) => {
+	if (isClear) {
 		ruleForm.value.deviceKey = "";
 	}
-	let findItem:any = productList.value.find((v: any) => v.key === data);
-	if(!findItem) return;
+	let findItem: any = productList.value.find((v: any) => v.key === data);
+	if (!findItem) return;
 	getDeviceList(findItem.id)
 }
 
-const getDeviceList = (id:number) => {
-	apiDevice.device.allList({productId: id}).then((res: any) => {
+const getDeviceList = (id: number) => {
+	apiDevice.device.allList({ productId: id }).then((res: any) => {
 		deviceList.value = res.device
 	})
 }
@@ -102,7 +101,7 @@ const open = async (row: any) => {
 		ruleForm.value = row;
 		isEdit.value = true;
 		handleProductChange(ruleForm.value.productKey, false)
-	}else {
+	} else {
 		isEdit.value = false;
 	}
 }

+ 9 - 11
src/views/iot/ice104/device/detail.vue

@@ -7,17 +7,15 @@
  * @Description: 这是默认设置,请设置`customMade`, 打开koroFileHeader查看配置 进行设置: https://github.com/OBKoro1/koro1FileHeader/wiki/%E9%85%8D%E7%BD%AE
 -->
 <template>
-	<div class="page page-full">
-		<el-card shadow="nover" class="page-full-part">
-			<el-tabs v-model="activeName" class="demo-tabs">
-				<el-tab-pane label="设备详情" name="detail">
-					<EditDeviceForm ref="editFormRef" />
-				</el-tab-pane>
-				<el-tab-pane label="设备任务" name="task">
-					<DeviceTaskTable />
-				</el-tab-pane>
-			</el-tabs>
-		</el-card>
+	<div class="page border bg padding page-full Ipt-2">
+		<el-tabs v-model="activeName">
+			<el-tab-pane label="设备详情" name="detail">
+				<EditDeviceForm ref="editFormRef" />
+			</el-tab-pane>
+			<el-tab-pane label="设备任务" name="task">
+				<DeviceTaskTable />
+			</el-tab-pane>
+		</el-tabs>
 	</div>
 </template>
 

+ 29 - 35
src/views/iot/ice104/template/component/addOrEditTemplateTask.vue

@@ -1,13 +1,7 @@
 <!-- 添加或者修改设备 -->
 <template>
-	<el-dialog
-		:title="isEdit ? '修改模版点位' : '添加模版点位'"
-		v-model="dialogVisible"
-		width="600px"
-		:before-close="closeDialog"
-		:close-on-click-modal="false"
-	>
-		<el-form ref="formRef" :rules="formRules" :model="ruleForm" label-position="left" label-width="120px" style="width: 90%; margin: 0 auto">
+	<el-dialog :title="isEdit ? '修改模版点位' : '添加模版点位'" v-model="dialogVisible" width="600px" :before-close="closeDialog" :close-on-click-modal="false">
+		<el-form ref="formRef" :rules="formRules" :model="ruleForm" label-position="right" label-width="120px" style="width: 90%; margin: 0 auto">
 			<el-form-item label="点位名称" prop="title">
 				<el-input v-model="ruleForm.title" placeholder="请输入点位名称" />
 			</el-form-item>
@@ -43,7 +37,7 @@ const emit = defineEmits(['updateList']);
 
 const formRef = ref();
 const ruleForm = ref({
-    dtId: 0,
+	dtId: 0,
 	title: '',
 	templateNumber: route.params && route.params.id,
 	dataAttribName: '',
@@ -58,8 +52,8 @@ const formRules = computed(() => ({
 	dataAddress: [{ required: true, trigger: 'blur', message: '请输入数据项点位' }],
 	dataCoef: [{ required: true, trigger: 'blur', message: '请输入倍率' }],
 }));
-const getRandom = (num:number) =>{
-    return Math.floor((Math.random()+Math.floor(Math.random()*9+1))*Math.pow(10,num-1));
+const getRandom = (num: number) => {
+	return Math.floor((Math.random() + Math.floor(Math.random() * 9 + 1)) * Math.pow(10, num - 1));
 }
 const submitData = async () => {
 	formRef.value.validate((valid: boolean) => {
@@ -70,22 +64,22 @@ const submitData = async () => {
 			api.deviceTemplate.editItem({
 				...ruleForm.value,
 			})
-			.then(() => {
-				ElMessage({ type: 'success', message: '修改成功' })
-				emit('updateList')
-				closeDialog()
-			})
-			.finally(() => (btnLoading.value = false))
+				.then(() => {
+					ElMessage({ type: 'success', message: '修改成功' })
+					emit('updateList')
+					closeDialog()
+				})
+				.finally(() => (btnLoading.value = false))
 		} else {
 			// 新增
-            ruleForm.value.dtId = getRandom(10);
+			ruleForm.value.dtId = getRandom(10);
 			api.deviceTemplate.addItem(ruleForm.value)
-			.then(() => {
-				ElMessage({ type: 'success', message: '添加成功' })
-				emit('updateList')
-				closeDialog()
-			})
-			.finally(() => (btnLoading.value = false))
+				.then(() => {
+					ElMessage({ type: 'success', message: '添加成功' })
+					emit('updateList')
+					closeDialog()
+				})
+				.finally(() => (btnLoading.value = false))
 		}
 	})
 }
@@ -94,16 +88,16 @@ const submitData = async () => {
  * 关闭弹窗
  */
 const closeDialog = () => {
-  dialogVisible.value = false;
-  isEdit.value = false;
-  ruleForm.value = {
-    dtId: 0,
-	title: '',
-	templateNumber: route.params && route.params.id,
-	dataAttribName: '',
-	dataAddress: 0,
-	DataCoef: ''
-  }
+	dialogVisible.value = false;
+	isEdit.value = false;
+	ruleForm.value = {
+		dtId: 0,
+		title: '',
+		templateNumber: route.params && route.params.id,
+		dataAttribName: '',
+		dataAddress: 0,
+		DataCoef: ''
+	}
 }
 
 
@@ -113,7 +107,7 @@ const open = async (row: any) => {
 		row.DataCoef = row.dataCoef
 		ruleForm.value = row;
 		isEdit.value = true;
-	}else {
+	} else {
 		isEdit.value = false;
 	}
 }

+ 28 - 43
src/views/iot/ice104/template/component/edit.vue

@@ -1,12 +1,6 @@
 <template>
-	<el-dialog
-		:title="isEdit ? '修改模版' : '添加模版'"
-		v-model="dialogVisible"
-		width="600px"
-		:before-close="clsoeDialog"
-		:close-on-click-modal="false"
-	>
-		<el-form ref="formRef" :rules="formRules" :model="ruleForm" label-position="left" label-width="80px" style="width: 90%; margin: 0 auto">
+	<el-dialog :title="isEdit ? '修改模版' : '添加模版'" v-model="dialogVisible" width="600px" :before-close="closeDialog" :close-on-click-modal="false">
+		<el-form ref="formRef" :rules="formRules" :model="ruleForm" label-width="80px" style="width: 90%; margin: 0 auto">
 			<el-form-item label="模版编码" prop="number">
 				<el-input :disabled="isEdit" v-model="ruleForm.number" placeholder="请输入模版编码" />
 			</el-form-item>
@@ -14,31 +8,22 @@
 				<el-input v-model="ruleForm.title" placeholder="请输入模版名称" />
 			</el-form-item>
 			<el-form-item label="状态">
-				<el-switch
-					v-model="ruleForm.status"
-					inline-prompt
-					active-text="开"
-					inactive-text="关"
-					:active-value="1"
-					:inactive-value="0"
-					width="80"
-				/>
+				<el-switch v-model="ruleForm.status" inline-prompt active-text="开" inactive-text="关" :active-value="1" :inactive-value="0" width="80" />
 			</el-form-item>
-			
+
 			<el-form-item label="备注">
 				<el-input type="textarea" v-model="ruleForm.remarks" placeholder="请输入备注信息" />
 			</el-form-item>
 		</el-form>
 		<template #footer>
-			<el-button v-auth="'canceSaveTemplate'" @click="clsoeDialog()"> 取 消 </el-button>
+			<el-button v-auth="'canceSaveTemplate'" @click="closeDialog()"> 取 消 </el-button>
 			<el-button v-auth="'saveTemplate'" :loading="btnLoading" type="primary" @click="submitData"> 保 存 </el-button>
 		</template>
 	</el-dialog>
 </template>
 <script lang="ts" setup>
-import { computed, reactive, ref } from 'vue';
+import { computed, ref } from 'vue';
 import api from '/@/api/ice104/index';
-import { useI18n } from 'vue-i18n';
 import { ElMessage } from 'element-plus';
 
 const dialogVisible = ref(false);
@@ -67,46 +52,46 @@ const submitData = async () => {
 			api.template.editItem({
 				...ruleForm.value,
 			})
-			.then(() => {
-				ElMessage({ type: 'success', message: '修改成功' })
-				emit('updateList')
-				closeDialog()
-			})
-			.finally(() => (btnLoading.value = false))
+				.then(() => {
+					ElMessage({ type: 'success', message: '修改成功' })
+					emit('updateList')
+					closeDialog()
+				})
+				.finally(() => (btnLoading.value = false))
 		} else {
 			// 新增
 			api.template.addItem(ruleForm.value)
-			.then(() => {
-				ElMessage({ type: 'success', message: '添加成功' })
-				emit('updateList')
-				closeDialog()
-			})
-			.finally(() => (btnLoading.value = false))
+				.then(() => {
+					ElMessage({ type: 'success', message: '添加成功' })
+					emit('updateList')
+					closeDialog()
+				})
+				.finally(() => (btnLoading.value = false))
 		}
 	})
 }
 
+
 /**
  * 关闭弹窗
  */
 const closeDialog = () => {
-  dialogVisible.value = false;
-  ruleForm.value = {
-	number: '',
-	title: '',
-	status: 1,
-	remarks: "",
-	mode: 0
-  }
+	dialogVisible.value = false;
+	ruleForm.value = {
+		number: '',
+		title: '',
+		status: 1,
+		remarks: "",
+		mode: 0
+	}
 }
 
-
 const open = async (row: any) => {
 	dialogVisible.value = true
 	if (row && row.number.toString()) {
 		ruleForm.value = row;
 		isEdit.value = true;
-	}else {
+	} else {
 		isEdit.value = false;
 	}
 }

+ 41 - 49
src/views/iot/ice104/template/component/editTemplateForm.vue

@@ -1,31 +1,23 @@
 <template>
-    <el-form ref="formRef" :rules="formRules" :model="ruleForm" label-position="left" label-width="120px" style="width: 70%;">
-        <el-form-item label="模版编码" prop="number">
-            <el-input disabled v-model="ruleForm.number" placeholder="请输入模版编码" />
-        </el-form-item>
-        <el-form-item label="名称">
-            <el-input v-model="ruleForm.title" placeholder="请输入名称" />
-        </el-form-item>
-        <el-form-item label="状态">
-            <el-switch
-                v-model="ruleForm.status"
-                inline-prompt
-                active-text="开"
-                inactive-text="关"
-                :active-value="1"
-                :inactive-value="0"
-                width="80"
-            />
-        </el-form-item>
-        
-        <el-form-item label="备注">
-            <el-input type="textarea" v-model="ruleForm.remarks" placeholder="请输入备注信息" />
-        </el-form-item>
+  <el-form ref="formRef" :rules="formRules" :model="ruleForm" label-position="right" label-width="80px" style="width: 70%;">
+    <el-form-item label="模版编码" prop="number">
+      <el-input disabled v-model="ruleForm.number" placeholder="请输入模版编码" />
+    </el-form-item>
+    <el-form-item label="名称">
+      <el-input v-model="ruleForm.title" placeholder="请输入名称" />
+    </el-form-item>
+    <el-form-item label="状态">
+      <el-switch v-model="ruleForm.status" inline-prompt active-text="开" inactive-text="关" :active-value="1" :inactive-value="0" />
+    </el-form-item>
 
-        <el-form-item>
-            <el-button type="primary" :loading="btnLoading" @click="submitData"> 保 存 </el-button>
-        </el-form-item>
-    </el-form>
+    <el-form-item label="备注">
+      <el-input type="textarea" v-model="ruleForm.remarks" placeholder="请输入备注信息" />
+    </el-form-item>
+
+    <el-form-item>
+      <el-button type="primary" :loading="btnLoading" @click="submitData"> 保 存 </el-button>
+    </el-form-item>
+  </el-form>
 </template>
 <script lang="ts" setup>
 import { computed, reactive, ref, onMounted } from 'vue';
@@ -39,37 +31,37 @@ const btnLoading = ref(false);
 const emit = defineEmits(['updateList']);
 const formRef = ref();
 const ruleForm = ref({
-    number: '',
-	title: '',
-	status: 1,
-	remarks: "",
-	mode: 0
+  number: '',
+  title: '',
+  status: 1,
+  remarks: "",
+  mode: 0
 })
 
 const formRules = computed(() => ({
-	number: [{ required: true, trigger: 'change', message: '请输入设备编码' }],
+  number: [{ required: true, trigger: 'change', message: '请输入设备编码' }],
 }));
 
 const submitData = async () => {
-	formRef.value.validate((valid: boolean) => {
-		if (!valid) return
-		btnLoading.value = true
-		// 修改
-        api.template.editItem({
-            ...ruleForm.value,
-        })
-        .then(() => {
-            ElMessage({ type: 'success', message: '修改成功' })
-            emit('updateList')
-        })
-        .finally(() => (btnLoading.value = false))
-	})
+  formRef.value.validate((valid: boolean) => {
+    if (!valid) return
+    btnLoading.value = true
+    // 修改
+    api.template.editItem({
+      ...ruleForm.value,
+    })
+      .then(() => {
+        ElMessage({ type: 'success', message: '修改成功' })
+        emit('updateList')
+      })
+      .finally(() => (btnLoading.value = false))
+  })
 }
 
 const open = async (row: any) => {
-	if (row && row.number.toString()) {
-		ruleForm.value = row;
-	}
+  if (row && row.number.toString()) {
+    ruleForm.value = row;
+  }
 }
 
 defineExpose({ open })
@@ -77,6 +69,6 @@ defineExpose({ open })
 
 <style lang="scss" scoped>
 .width100 {
-	width: 100%;
+  width: 100%;
 }
 </style>

+ 10 - 12
src/views/iot/ice104/template/detail.vue

@@ -7,17 +7,15 @@
  * @Description: 这是默认设置,请设置`customMade`, 打开koroFileHeader查看配置 进行设置: https://github.com/OBKoro1/koro1FileHeader/wiki/%E9%85%8D%E7%BD%AE
 -->
 <template>
-	<div class="page page-full">
-		<el-card shadow="nover" class="page-full-part">
-			<el-tabs v-model="activeName">
-				<el-tab-pane label="模版详情" name="detail">
-					<EditTemplateForm ref="editFormRef" />
-				</el-tab-pane>
-				<el-tab-pane label="模版点位" name="point">
-					<TemplateTaskTable />
-				</el-tab-pane>
-			</el-tabs>
-		</el-card>
+	<div class="page bg padding Ipt-2 page-full">
+		<el-tabs v-model="activeName">
+			<el-tab-pane label="模版详情" name="detail">
+				<EditTemplateForm ref="editFormRef" />
+			</el-tab-pane>
+			<el-tab-pane label="模版点位" name="point">
+				<TemplateTaskTable />
+			</el-tab-pane>
+		</el-tabs>
 	</div>
 </template>
 
@@ -50,7 +48,7 @@ const onDel = (row: any) => {
 		cancelButtonText: '取消',
 		type: 'warning',
 	}).then(async () => {
-		await api.device.deleteItem({number: row.number});
+		await api.device.deleteItem({ number: row.number });
 		ElMessage.success('删除成功');
 		getList();
 	});

+ 681 - 0
src/views/iot/iotCard/dashboard.vue

@@ -0,0 +1,681 @@
+<!-- 物联网卡-详情 -->
+<template>
+  <div class="page">
+		<el-card shadow="nover" class="page-full-part">
+      <div shadow="nover" class="top-wrap">
+        <div class="title flex">
+          数据统计
+          <div class="select-wrap">
+            <el-select v-model="types" placeholder="请选择" style="width: 120px" @change="typeChange()">
+              <!-- 1电信,2联通,3移动 -->
+              <el-option label="电信" :value="1" />
+              <el-option label="联通" :value="2" />
+              <el-option label="移动" :value="3" />
+            </el-select>
+          </div>
+        </div>
+        <div class="top-inner-wrap">
+          <div class="line-wrap flow-line-wrap">
+            <div class="text-wrap">
+              <div class="text">昨日流量消耗</div>
+              <div>{{formatSize(statisticsData.yesterdayTotal * 1024 * 1024)}}</div>
+            </div>
+            <div class="line-inner-wrap" ref="yesterdayLine"></div>
+          </div>
+          <div class="line-wrap flow-line-wrap">
+            <div class="text-wrap">
+              <div class="text">当月流量消耗</div>
+              <div>{{formatSize(statisticsData.realMonthTotal * 1024 * 1024)}}</div>
+            </div>
+            <div class="line-inner-wrap" ref="monthLine"></div>
+          </div>
+
+          <div class="line-wrap flow-line-wrap">
+            <div class="text-wrap">
+              <div class="text">本年流量消耗</div>
+              <div>{{formatSize(statisticsData.yearTotal * 1024 * 1024)}}</div>
+            </div>
+            <div class="line-inner-wrap" ref="yearLine"></div>
+          </div>
+        </div>
+
+        
+      </div>
+			<div class="statistics-wrap">
+				<el-card shadow="nover" class="left-wrap">
+					<div class="top-title-wrap">
+							<div class="title">流量统计</div>
+							<div class="operate-wrap">
+								<el-button-group>
+									<el-button @click="changeDate(1)" :type="activeIndex == 1 ?  'primary' : ''">昨日</el-button>
+									<el-button @click="changeDate(2)" :type="activeIndex == 2 ?  'primary' : ''">近一周</el-button>
+									<el-button @click="changeDate(3)" :type="activeIndex == 3 ?  'primary' : ''">近一月</el-button>
+									<el-button @click="changeDate(4)" :type="activeIndex == 4 ?  'primary' : ''">近一年</el-button>
+								</el-button-group>
+									<el-date-picker
+										class="date-picker-wrap"
+										v-model="dateTimeRange"
+										:disabled-date="disabledDate"
+										type="datetimerange"
+										range-separator="至"
+										start-placeholder="开始时间"
+										end-placeholder="结束时间"
+										format="YYYY-MM-DD HH:mm:ss"
+										date-format="YYYY/MM/DD"
+										time-format="hh:mm:ss"
+									/>
+							</div>
+					</div>
+					<div style="height: 508px;" ref="flowLine"></div>
+				</el-card>
+
+				<el-card shadow="nover" class="right-wrap">
+					<div class="top-title-wrap">
+						<div class="title">流量使用TOP10</div>
+						<el-date-picker
+							class="date-picker-wrap"
+							v-model="dateRange"
+							:disabled-date="disabledDate"
+							type="daterange"
+							range-separator="至"
+							start-placeholder="开始日期"
+							end-placeholder="结束日期"
+							format="YYYY-MM-DD"
+							date-format="YYYY/MM/DD"
+						/>
+					</div>
+					<div class="ranking-list">
+						<div class="rank-item" v-for="(item, index) in rankList" :key="index">
+							<div :class="`number-item-${++index}`" class="number">{{index++}}</div>
+							<div class="card-num">{{item.accessNumber}}</div>
+							<el-progress class="progress-wrap" :text-inside="true" :stroke-width="16" :percentage="totalNum ? (item.value / totalNum * 100).toFixed(2) : 0" />
+							<div class="flow-num">{{formatSize(item.value * 1024 * 1024)}}</div>
+						</div>
+					</div>
+					
+				</el-card>
+			</div>
+		</el-card>
+  </div>
+</template>
+
+<script lang="ts" setup>
+import { ref, reactive, nextTick, watch, markRaw } from "vue"
+import { formatSize } from "/@/utils/common";
+import api from '/@/api/iotCard';
+import { useStore } from '/@/store/index';
+import * as echarts from 'echarts';
+import dayjs from 'dayjs';
+
+const store = useStore();
+
+const types = ref(1);
+const statisticsData = ref({
+	realMonthTotal: 0,
+	yearTotal: 0,
+	yesterdayTotal: 0
+})
+
+const flowLine = ref();
+const yesterdayLine = ref();
+const monthLine = ref();
+const yearLine = ref();
+
+const dateRange = ref<any>([
+  dayjs(new Date()).subtract(1, 'month'),
+  dayjs(new Date()),
+])
+
+
+const dateTimeRange = ref<any>([
+  dayjs(new Date()).subtract(6, 'day'),
+  dayjs(new Date()),
+])
+
+const totalNum = ref(0);
+
+const activeIndex= ref(2);
+
+const rankList = ref([]);
+
+const flowLineXAxisData = ref<any>([]);
+const flowLineData = ref<any>([]);
+
+const yearLineXAxisData = ref<any>([]);
+const yearLineData = ref<any>([]);
+
+const monthLineXAxisData = ref<any>([]);
+const monthLineData = ref<any>([]);
+
+const yesterdayLineXAxisData = ref<any>([dayjs(new Date()).subtract(1, 'day').format('YYYY-MM-DD')]);
+const yesterdayLineData = ref<any>([]);
+
+
+
+const state = reactive({
+	global: {
+		yesterdayLine: null,
+    monthLine: null,
+    yearLine: null,
+		dispose: [null, '', undefined],
+	} as any,
+	myCharts: [],
+	charts: {
+		theme: '',
+		bgColor: '',
+		color: '#303133',
+	},
+});
+
+const disabledDate = (time: Date) => {
+  return time.getTime() > Date.now()
+}
+
+const getFlowDataByDateRange = async (dateRangeData:any) => {
+  const simFlowRes = await api.dashboard.getFlowDataByDateRange({
+    sdate: dayjs(dateRangeData[0]).format('YYYY-MM-DD HH:mm:ss'),
+    edate: dayjs(dateRangeData[1]).format('YYYY-MM-DD HH:mm:ss'),
+    types: types.value
+  })
+	return simFlowRes.data;
+}
+
+const getYesterdayFlowData = async () => {
+  const yesterday = dayjs(new Date()).subtract(1, 'day').format('YYYY-MM-DD')
+  const res = await getFlowDataByDateRange([
+    yesterday + " 00:00:00",
+    yesterday + " 23:59:59"
+  ])
+  yesterdayLineData.value = [res[0].value];
+  initYesterdayLineChart();
+}
+const getMonthFlowData = async () => {
+  monthLineXAxisData.value = [];
+  monthLineData.value = [];
+  const monthDay1 = dayjs(new Date()).startOf('month').format('YYYY-MM-DD');
+  const monthDay2 = dayjs(new Date()).endOf('month').format('YYYY-MM-DD');
+  const res = await getFlowDataByDateRange([monthDay1, monthDay2])
+  res.reverse().forEach((item:any) => {
+    monthLineXAxisData.value.push(item.date);
+    monthLineData.value.push(item.value);
+  })
+
+  initMonthLineChart();
+}
+const getYearFlowData = async () => {
+	yearLineXAxisData.value = [];
+	yearLineData.value = [];
+	const year = dayjs(new Date()).startOf('year').format('YYYY');
+	const res = await getFlowDataByDateRange([
+		year + '-01-01',
+		year + '-12-31'
+	])
+	res.reverse().forEach((item:any) => {
+		yearLineXAxisData.value.push(item.date);
+		yearLineData.value.push(item.value);
+	})
+	initYearLineChart();
+}
+
+const getFlowData = async () => {
+	flowLineXAxisData.value = [];
+  flowLineData.value = [];
+	const res = await getFlowDataByDateRange(dateTimeRange.value)
+	res.reverse().forEach((item:any) => {
+		flowLineXAxisData.value.push(item.date);
+		flowLineData.value.push(item.value);
+	})
+	iniFlowLineChart();
+}
+
+const changeDate = (key:number) => {
+  // 1 昨天 2近一周 3近一月 4近一年
+  activeIndex.value = key;
+  if(key === 1) {
+    // 昨天
+    const yesterday = dayjs(new Date()).subtract(1, 'day').format('YYYY-MM-DD')
+    dateTimeRange.value = [
+      yesterday + " 00:00:00",
+      yesterday + " 23:59:59"
+    ]
+  }else if(key === 2) {
+    // 近一周
+    dateTimeRange.value = [
+      dayjs(new Date()).subtract(6, 'day'),
+      dayjs(new Date()),
+    ]
+  }else if(key === 3) {
+    // 近一月
+    dateTimeRange.value = [
+      dayjs(new Date()).subtract(1, 'month'),
+      dayjs(new Date()),
+    ]
+  }else if(key === 4) {
+    // 近一年
+    dateTimeRange.value = [
+      dayjs(new Date()).subtract(1, 'year'),
+      dayjs(new Date()),
+    ]
+  }
+  getFlowData();
+}
+
+const typeChange = () => {
+	getYesterdayFlowData();
+	getMonthFlowData();
+	getYearFlowData();
+	getFlowData();
+	getTop10Data();
+}
+
+const getTop10Data = async () => {
+  const top10Res = await api.dashboard.getTop10Data({
+    sdate: dayjs(dateRange.value[0]).format('YYYY-MM-DD'),
+    edate: dayjs(dateRange.value[1]).format('YYYY-MM-DD'),
+    types: types.value
+  })
+	rankList.value = top10Res.data || [];
+	totalNum.value = top10Res.data ? top10Res.data[0].value : 0;
+}
+const getFlowAllData = async () => {
+  const res = await api.dashboard.getFlowData({
+    types: types.value
+  })
+	statisticsData.value = res
+}
+
+// 折线图 - 昨日流量消耗
+const initYesterdayLineChart = () => {
+  if (!state.global.dispose.some((b: any) => b === state.global.yesterdayLine)) state.global.yesterdayLine.dispose();
+	state.global.yesterdayLine = markRaw(echarts.init(yesterdayLine.value, state.charts.theme));
+  const option = {
+    backgroundColor: state.charts.bgColor,
+    xAxis: {
+      data: yesterdayLineXAxisData.value,
+      show: false,
+    },
+    yAxis: [
+      {
+        type: 'value',
+        name: '',
+        show: false,
+        splitLine: { show: false, lineStyle: { type: 'dashed', color: '#f5f5f5' } },
+      },
+    ],
+    tooltip: { 
+			trigger: 'axis',
+			formatter: function (params:any) {
+					var relVal = params[0].name
+					let circle = `<i style="margin-right:4px;display: inline-block;width: 10px;height: 10px;border-radius: 50%;background-color:${params[0].color}"></i>`
+					relVal += '<br/>' + circle + ' 流量: ' + formatSize(params[0].value*1024*1024)
+					return relVal;
+				}
+		},
+    grid: { top: 10, right: 10, bottom: 10, left: 10 },
+    series: [
+      {
+        name: '流量',
+        type: 'line',
+        smooth: true,
+        data: yesterdayLineData.value,
+        lineStyle: { color: '#fe9a8b' },
+        itemStyle: { color: '#fe9a8b', borderColor: '#fe9a8b' },
+        areaStyle: {
+          color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
+            { offset: 0, color: '#fe9a8bb3' },
+            { offset: 1, color: '#fe9a8b03' },
+          ]),
+        },
+      }
+    ],
+  };
+  (<any>state.global.yesterdayLine).setOption(option);
+  (<any>state.myCharts).push(state.global.yesterdayLine);
+};
+
+
+// 折线图 - 当月流量消耗
+const initMonthLineChart = () => {
+  if (!state.global.dispose.some((b: any) => b === state.global.monthLine)) state.global.monthLine.dispose();
+	state.global.monthLine = markRaw(echarts.init(monthLine.value, state.charts.theme));
+  const option = {
+    backgroundColor: state.charts.bgColor,
+    xAxis: {
+      data: monthLineXAxisData.value,
+      show: false,
+    },
+    yAxis: [
+      {
+        type: 'value',
+        name: '',
+        show: false,
+      },
+    ],
+    tooltip: { 
+			trigger: 'axis',
+			formatter: function (params:any) {
+					var relVal = params[0].name
+					let circle = `<i style="margin-right:4px;display: inline-block;width: 10px;height: 10px;border-radius: 50%;background-color:${params[0].color}"></i>`
+					relVal += '<br/>' + circle + ' 流量: ' + formatSize(params[0].value*1024*1024)
+					return relVal;
+				}
+		},
+    grid: { top: 10, right: 10, bottom: 10, left: 10 },
+    series: [
+      {
+        name: '流量',
+        type: 'line',
+        smooth: true,
+        data: monthLineData.value,
+        lineStyle: { color: '#9E87FF' },
+        itemStyle: { color: '#9E87FF', borderColor: '#9E87FF' },
+				areaStyle: {
+					color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
+						{ offset: 0, color: '#9E87FFb3' },
+						{ offset: 1, color: '#9E87FF03' },
+					]),
+				},
+      }
+    ],
+  };
+  (<any>state.global.monthLine).setOption(option);
+  (<any>state.myCharts).push(state.global.monthLine);
+};
+
+// 折线图 - 本年流量消耗
+const initYearLineChart = () => {
+  if (!state.global.dispose.some((b: any) => b === state.global.yearLine)) state.global.yearLine.dispose();
+	state.global.yearLine = markRaw(echarts.init(yearLine.value, state.charts.theme));
+  const option = {
+    backgroundColor: state.charts.bgColor,
+    xAxis: {
+      data: yearLineXAxisData.value,
+      show: false,
+    },
+    yAxis: [
+      {
+        type: 'value',
+        name: '',
+        show: false,
+        splitLine: { show: false, lineStyle: { type: 'dashed', color: '#f5f5f5' } }
+			}
+    ],
+    tooltip: { 
+			trigger: 'axis',
+			formatter: function (params:any) {
+					var relVal = params[0].name
+					let circle = `<i style="margin-right:4px;display: inline-block;width: 10px;height: 10px;border-radius: 50%;background-color:${params[0].color}"></i>`
+					relVal += '<br/>' + circle + ' 流量: ' + formatSize(params[0].value*1024*1024)
+					return relVal;
+				}
+		},
+    grid: { top: 10, right: 10, bottom: 10, left: 10 },
+    series: [
+      {
+        name: '流量',
+        type: 'line',
+        smooth: true,
+        data: yearLineData.value,
+        lineStyle: { color: '#fe9a8b' },
+        itemStyle: { color: '#fe9a8b', borderColor: '#fe9a8b' },
+				areaStyle: {
+					color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
+						{ offset: 0, color: '#fe9a8bb3' },
+						{ offset: 1, color: '#fe9a8b03' },
+					]),
+				},
+      }
+    ],
+  };
+  (<any>state.global.yearLine).setOption(option);
+  (<any>state.myCharts).push(state.global.yearLine);
+};
+
+// 折线图 - 流量统计
+const iniFlowLineChart = async () => {
+  if (!state.global.dispose.some((b: any) => b === state.global.flowLine)) state.global.flowLine.dispose();
+	state.global.flowLine = markRaw(echarts.init(flowLine.value, state.charts.theme));
+  const option = {
+    backgroundColor: state.charts.bgColor,
+    grid: { top: 70, right: 20, bottom: 30, left: 30 },
+    tooltip: { 
+			trigger: 'axis',
+			formatter: function (params:any) {
+					var relVal = params[0].name
+					let circle = `<i style="margin-right:4px;display: inline-block;width: 10px;height: 10px;border-radius: 50%;background-color:${params[0].color}"></i>`
+					relVal += '<br/>' + circle + ' 流量: ' + params[0].value + 'MB'
+					return relVal;
+				}
+		},
+    xAxis: {
+      data: flowLineXAxisData.value,
+    },
+    yAxis: [
+      {
+        type: 'value',
+        name: '',
+        splitLine: { show: true, lineStyle: { type: 'dashed', color: '#f5f5f5' } },
+        axisLabel: {
+          margin: 2,
+          formatter: function (value:any) {
+            if (value >= 10000 && value < 10000000) {
+              value = value / 10000 + "W";
+            } else if (value >= 10000000) {
+              value = value / 10000000 + "KW";
+            }
+            return value;
+          }
+        },
+      },
+    ],
+    series: [
+      {
+        name: '流量',
+        type: 'line',
+        symbolSize: 6,
+        symbol: 'circle',
+        smooth: true,
+        data: flowLineData.value,
+        lineStyle: { color: '#9E87FF' },
+        itemStyle: { color: '#9E87FF', borderColor: '#9E87FF' },
+        areaStyle: {
+          color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
+            { offset: 0, color: '#9E87FFb3' },
+            { offset: 1, color: '#9E87FF03' },
+          ]),
+        },
+        emphasis: {
+          itemStyle: {
+            color: {
+              type: 'radial',
+              x: 0.5,
+              y: 0.5,
+              r: 0.5,
+              colorStops: [
+                { offset: 0, color: '#9E87FF' },
+                { offset: 0.4, color: '#9E87FF' },
+                { offset: 0.5, color: '#fff' },
+                { offset: 0.7, color: '#fff' },
+                { offset: 0.8, color: '#fff' },
+                { offset: 1, color: '#fff' },
+              ],
+            },
+            borderColor: '#9E87FF',
+            borderWidth: 2,
+          },
+        },
+      },
+    ],
+  };
+  (<any>state.global.flowLine).setOption(option);
+  (<any>state.myCharts).push(state.global.flowLine);
+};
+
+getFlowAllData();
+getYesterdayFlowData();
+getMonthFlowData();
+getYearFlowData();
+getFlowData();
+getTop10Data();
+
+// 监听 vuex 中是否开启深色主题
+watch(
+  () => store.state.themeConfig.themeConfig.isIsDark,
+  (isIsDark) => {
+    nextTick(() => {
+      state.charts.theme = isIsDark ? 'dark' : '';
+      state.charts.bgColor = isIsDark ? 'transparent' : '';
+      state.charts.color = isIsDark ? '#dadada' : '#303133';
+      setTimeout(() => {
+        iniFlowLineChart();
+        initYesterdayLineChart();
+        initMonthLineChart();
+        initYearLineChart();
+      }, 500);
+    });
+  },
+  {
+    deep: true,
+    immediate: true,
+  }
+);
+</script>
+
+<style lang="scss" scoped>
+.select-wrap {
+	text-align: right;
+}
+.top-wrap {
+	width: 100%;
+  .flex {
+    justify-content: space-between;
+  }
+	.top-inner-wrap {
+		display: flex;
+		margin-top: 10px;
+
+	}
+	.line-wrap {
+		flex: 1;
+		background-color: #fff;
+		background: #fcfcfc;
+		border: 1px solid #e0e4e8;
+		display: flex;
+		justify-content: space-between;
+		align-items: center;
+		padding: 20px;
+		position: relative;
+		.text-wrap {
+			position: absolute;
+			left: 20px;
+			top: 40px;
+			.text {
+				font-size: 14px;
+				color: #000000a3;
+			}
+			div:nth-child(2) {
+				font-size: 32px;
+				font-weight: 700;
+				overflow: hidden;
+				white-space: nowrap;
+				text-overflow: ellipsis;
+			}
+		}
+		.line-inner-wrap {
+			height: 100px;
+			width: calc(100% - 150px);
+			margin-left: 150px;
+		}
+	}
+	.line-wrap:not(:nth-child(1)) {
+		margin-left: 20px;
+	}
+}
+.title {
+	color: var(--el-text-color-primary);
+	font-size: 16px;
+	font-weight: 700;
+}
+.statistics-wrap {
+  display: flex;
+  justify-content: space-between;
+  align-items: normal;
+  margin-top: 20px;
+  width: 100%;
+
+  .left-wrap {
+    width: 66.3%;
+    .top-title-wrap {
+      display: flex;
+      justify-content: space-between;
+      align-items: center;
+      margin-bottom: 16px;
+      ::v-deep .el-date-editor.el-input__wrapper {
+        width: 360px!important;
+        margin-left: 12px;
+      }
+      .operate-wrap {
+        display: flex;
+        justify-content: center;
+        align-items: center;
+      }
+    }
+  }
+  .right-wrap {
+    width: calc(33.7% - 20px);
+    margin-left: 20px;
+		.top-title-wrap {
+      display: flex;
+      justify-content: space-between;
+      align-items: center;
+      margin-bottom: 16px;
+      ::v-deep .el-date-editor.el-input__wrapper {
+        max-width: 220px!important;
+      }
+    }
+		.ranking-list {
+			width: 100%;
+			.rank-item {
+				width: 100%;
+				display: flex;
+				justify-content: space-between;
+				align-items: center;
+				margin-bottom: 24px;
+
+        .number {
+          flex: 0 0 24px;
+          height: 24px;
+          color: #fff;
+          font-weight: 700;
+          line-height: 24px;
+          text-align: center;
+          background-color: #d1d1d1;
+        }
+        .number-item-1 {
+          color: #e50012;
+          background-color: #e500121a;
+        }
+        .number-item-2 {
+          color: #fba500;
+          background-color: #fba5001a;
+        }
+        .number-item-3 {
+          color: #597ef7;
+          background-color: #597ef71a;
+        }
+				.card-num {
+					width: 110px;
+				}
+				.progress-wrap {
+					width: 32%;
+				}
+				.flow-num {
+					width: 90px;
+					text-align: right;
+				}
+			}
+		}
+  }
+}
+  
+</style>

+ 712 - 0
src/views/iot/iotCard/index/detail.vue

@@ -0,0 +1,712 @@
+<!-- 物联网卡-详情 -->
+<template>
+  <div>
+    <el-card shadow="nover">
+       <el-descriptions class="margin-top" title="基本信息" :column="3" :size="size" border>
+        <!-- 卡号 -->
+        <el-descriptions-item>
+          <template #label>
+            <div class="cell-item">
+              <el-icon :style="iconStyle">
+                <user />
+              </el-icon>
+              卡号
+            </div>
+          </template>
+          {{sim.accNumber}}
+        </el-descriptions-item>
+
+        <!-- ICCID -->
+        <el-descriptions-item>
+          <template #label>
+            <div class="cell-item">
+              <el-icon :style="iconStyle">
+                <iphone />
+              </el-icon>
+              ICCID
+            </div>
+          </template>
+          {{sim.iccid}}
+        </el-descriptions-item>
+
+        <!-- 绑定设备 -->
+        <el-descriptions-item>
+          <template #label>
+            <div class="cell-item">
+              <el-icon :style="iconStyle">
+                <iphone />
+              </el-icon>
+              绑定设备
+            </div>
+          </template>
+          {{sim.bindDeviceName}}
+        </el-descriptions-item>
+
+        <!-- 平台类型 -->
+        <el-descriptions-item>
+          <template #label>
+            <div class="cell-item">
+              <el-icon :style="iconStyle">
+                <iphone />
+              </el-icon>
+              平台类型
+            </div>
+          </template>
+          {{sim.platTypes}}
+        </el-descriptions-item>
+
+        <!-- 	平台名称 -->
+        <el-descriptions-item>
+          <template #label>
+            <div class="cell-item">
+              <el-icon :style="iconStyle">
+                <location />
+              </el-icon>
+              平台名称
+            </div>
+          </template>
+          {{sim.platName}}
+        </el-descriptions-item>
+
+        <!-- 运营商 -->
+        <el-descriptions-item>
+          <template #label>
+            <div class="cell-item">
+              <el-icon :style="iconStyle">
+                <location />
+              </el-icon>
+              运营商
+            </div>
+          </template>
+          {{formatOperator(sim.types)}}
+        </el-descriptions-item>
+
+        <!-- 	类型 -->
+        <el-descriptions-item>
+          <template #label>
+            <div class="cell-item">
+              <el-icon :style="iconStyle">
+                <location />
+              </el-icon>
+              类型
+            </div>
+          </template>
+          {{formatType(sim.simTypes)}}
+        </el-descriptions-item>
+        
+        <!-- 	激活日期 -->
+        <el-descriptions-item>
+          <template #label>
+            <div class="cell-item">
+              <el-icon :style="iconStyle">
+                <location />
+              </el-icon>
+              激活日期
+            </div>
+          </template>
+          {{sim.activationTime}}
+        </el-descriptions-item>
+
+        <!-- 更新时间 -->
+        <el-descriptions-item>
+          <template #label>
+            <div class="cell-item">
+              <el-icon :style="iconStyle">
+                <location />
+              </el-icon>
+              更新时间
+            </div>
+          </template>
+          {{sim.updatedAt}}
+        </el-descriptions-item>
+
+
+        <!-- 总流量 -->
+        <el-descriptions-item>
+          <template #label>
+            <div class="cell-item">
+              <el-icon :style="iconStyle">
+                <location />
+              </el-icon>
+              总流量
+            </div>
+          </template>
+          {{sim.totalFlow}}
+        </el-descriptions-item>
+
+        <!-- 使用流量 -->
+        <el-descriptions-item>
+          <template #label>
+            <div class="cell-item">
+              <el-icon :style="iconStyle">
+                <location />
+              </el-icon>
+              使用流量
+            </div>
+          </template>
+          {{sim.usedFlow}}
+        </el-descriptions-item>
+
+        <!-- 剩余流量 -->
+        <el-descriptions-item>
+          <template #label>
+            <div class="cell-item">
+              <el-icon :style="iconStyle">
+                <location />
+              </el-icon>
+              剩余流量
+            </div>
+          </template>
+          {{sim.leaveFlow}}
+        </el-descriptions-item>
+
+        <!-- 状态 -->
+        <el-descriptions-item>
+          <template #label>
+            <div class="cell-item">
+              <el-icon :style="iconStyle">
+                <tickets />
+              </el-icon>
+              状态
+            </div>
+          </template>
+          <el-tag size="small">{{formatStatus(sim.simStatus)}}</el-tag>
+        </el-descriptions-item>
+
+        <!-- 说明 -->
+        <el-descriptions-item>
+          <template #label>
+            <div class="cell-item">
+              <el-icon :style="iconStyle">
+                <office-building />
+              </el-icon>
+              说明
+            </div>
+          </template>
+          {{sim.iccid}}
+        </el-descriptions-item>
+      </el-descriptions>
+    </el-card>
+
+    <div class="statistics-wrap gap-3">
+      <el-card shadow="nover" class="left-wrap">
+        <div class="top-title-wrap">
+            <div class="title">流量统计</div>
+            <div class="operate-wrap">
+              <el-button-group>
+                <el-button @click="changeDate(1)" :type="activeIndex == 1 ?  'primary' : ''">昨日</el-button>
+                <el-button @click="changeDate(2)" :type="activeIndex == 2 ?  'primary' : ''">近一周</el-button>
+                <el-button @click="changeDate(3)" :type="activeIndex == 3 ?  'primary' : ''">近一月</el-button>
+                <el-button @click="changeDate(4)" :type="activeIndex == 4 ?  'primary' : ''">近一年</el-button>
+              </el-button-group>
+                <el-date-picker
+                  class="date-picker-wrap"
+                  v-model="dateRange"
+                  type="datetimerange"
+                  range-separator="至"
+                  start-placeholder="开始时间"
+                  end-placeholder="结束时间"
+                  format="YYYY-MM-DD HH:mm:ss"
+                  date-format="YYYY/MM/DD"
+                  time-format="hh:mm:ss"
+                />
+            </div>
+        </div>
+        <div style="height: 460px;" ref="flowLine"></div>
+      </el-card>
+
+      <el-card shadow="nover" class="right-wrap">
+        <div class="title">数据统计</div>
+        <div class="line-wrap flow-line-wrap">
+          <div class="text-wrap">
+            <div class="text">昨日流量消耗</div>
+            <div>{{formatSize(statisticsData.yesterdayTotal * 1024 * 1024)}}</div>
+          </div>
+          <div class="line-inner-wrap" ref="yesterdayLine"></div>
+        </div>
+        <div class="line-wrap flow-line-wrap">
+          <div class="text-wrap">
+            <div class="text">当月流量消耗</div>
+            <div>{{formatSize(statisticsData.monthTotal * 1024 * 1024)}}</div>
+          </div>
+          <div class="line-inner-wrap" ref="monthLine"></div>
+        </div>
+
+        <div class="line-wrap flow-line-wrap">
+          <div class="text-wrap">
+            <div class="text">本年流量消耗</div>
+            <div>{{formatSize(statisticsData.yearTotal * 1024 * 1024)}}</div>
+          </div>
+          <div class="line-inner-wrap" ref="yearLine"></div>
+        </div>
+        
+      </el-card>
+    </div>
+  </div>
+</template>
+
+<script lang="ts" setup>
+import { ref, reactive, nextTick, watch, markRaw } from "vue";
+import { formatSize } from "/@/utils/common";
+import api from '/@/api/iotCard';
+import { useStore } from '/@/store/index';
+import { useRoute } from 'vue-router';
+import * as echarts from 'echarts';
+import dayjs from 'dayjs';
+
+const store = useStore();
+const route = useRoute();
+const sim = ref({
+  accNumber: "",// 卡号
+  iccid: "",// ICCID
+  bindDeviceName: "",// 绑定设备
+  platName: "",// 平台对接
+  types: "",// 运营商
+  simTypes: "",// 类型
+  totalFlow: "",// 总流量
+  usedFlow: "",// 使用流量
+  leaveFlow: "",// 剩余流量
+  activationTime: "",// 激活日期
+  updatedAt: "",// 更新时间
+  simStatus: "",// 状态
+  remark: ""// 说明
+})
+
+const flowLine = ref();
+const yesterdayLine = ref();
+const monthLine = ref();
+const yearLine = ref();
+
+const dateRange = ref<any>([
+  dayjs(new Date()).subtract(6, 'day'),
+  dayjs(new Date()),
+])
+const activeIndex= ref(2);
+
+const flowLineXAxisData = ref<any>([]);
+const flowLineData = ref<any>([]);
+
+const yearLineXAxisData = ref<any>([]);
+const yearLineData = ref<any>([]);
+
+const monthLineXAxisData = ref<any>([]);
+const monthLineData = ref<any>([]);
+
+const yesterdayLineXAxisData = ref<any>([dayjs(new Date()).subtract(1, 'day').format('YYYY-MM-DD')]);
+const yesterdayLineData = ref<any>([]);
+
+const statisticsData = ref({
+	monthTotal: 0,
+	yearTotal: 0,
+	yesterdayTotal: 0
+})
+
+const state = reactive({
+	global: {
+		yesterdayLine: null,
+    monthLine: null,
+    yearLine: null,
+		dispose: [null, '', undefined],
+	} as any,
+	myCharts: [],
+	charts: {
+		theme: '',
+		bgColor: '',
+		color: '#303133',
+	},
+});
+
+const getDetailInfo = async () => {
+  const res = await api.simCard.detailItem({ id: route.params.id });
+  sim.value = res.sim;
+  statisticsData.value = {
+    monthTotal: res.monthFlow,
+    yearTotal: res.yearFlow,
+    yesterdayTotal: res.yesterdayFlow
+  }
+  await getFlowDataByDateRange();
+  res.yearDataList.reverse().forEach((item:any) => {
+    yearLineXAxisData.value.push(item.date);
+    yearLineData.value.push(item.value);
+  })
+  res.monthDataList.reverse().forEach((item:any) => {
+    monthLineXAxisData.value.push(item.date);
+    monthLineData.value.push(item.value);
+  })
+  yesterdayLineData.value = [res.yearFlow];
+  iniFlowLineChart();
+  initYesterdayLineChart();
+  initMonthLineChart();
+  initYearLineChart();
+}
+
+
+getDetailInfo();
+
+
+const getFlowDataByDateRange = async () => {
+  const simFlowRes = await api.simCard.getFlowDataByDateRange({
+    sdate: activeIndex.value !== 1 ? dateRange.value[0].format('YYYY-MM-DD HH:mm:ss') : dateRange.value[0],
+    edate: activeIndex.value !== 1 ? dateRange.value[1].format('YYYY-MM-DD HH:mm:ss') : dateRange.value[1],
+    accNumber: sim.value.accNumber,
+    types: sim.value.types
+  })
+  simFlowRes.data.reverse().forEach((item:any) => {
+    flowLineXAxisData.value.push(item.date);
+    flowLineData.value.push(item.value);
+  })
+  iniFlowLineChart();
+}
+
+const changeDate = (key:number) => {
+  // 1 昨天 2近一周 3近一月 4近一年
+  activeIndex.value = key;
+  if(key === 1) {
+    // 昨天
+    const yesterday = dayjs(new Date()).subtract(1, 'day').format('YYYY-MM-DD')
+    dateRange.value = [
+      yesterday + " 00:00:00",
+      yesterday + " 23:59:59"
+    ]
+  }else if(key === 2) {
+    // 近一周
+    dateRange.value = [
+      dayjs(new Date()).subtract(6, 'day'),
+      dayjs(new Date()),
+    ]
+  }else if(key === 3) {
+    // 近一月
+    dateRange.value = [
+      dayjs(new Date()).subtract(1, 'month'),
+      dayjs(new Date()),
+    ]
+  }else if(key === 4) {
+    // 近一年
+    dateRange.value = [
+      dayjs(new Date()).subtract(1, 'year'),
+      dayjs(new Date()),
+    ]
+  }
+  flowLineXAxisData.value = [];
+  flowLineData.value = [];
+  getFlowDataByDateRange();
+
+}
+
+const formatOperator = (val:number) => {
+  // 1电信,2联通,3移动
+  return ['', '电信', '联通', '移动'][val];
+}
+
+const formatType = (val:number) => {
+  // 1月卡,2季卡,3年卡,4其他
+  return ['', '月卡', '季卡', '年卡', '其他'][val];
+}
+
+const formatStatus = (val:any) => {
+  // 1:可激活 2:测试激活 3:测试去激活 4:在用 5:停机 6:运营商管理状态
+  return ['', '可激活', '测试激活', '测试去激活', '在用', '停机', '运营商管理状态'][val];
+}
+
+// 折线图 - 昨日流量消耗
+const initYesterdayLineChart = () => {
+  if (!state.global.dispose.some((b: any) => b === state.global.yesterdayLine)) state.global.yesterdayLine.dispose();
+	state.global.yesterdayLine = markRaw(echarts.init(yesterdayLine.value, state.charts.theme));
+  const option = {
+    backgroundColor: state.charts.bgColor,
+    xAxis: {
+      data: yesterdayLineXAxisData.value,
+      show: false,
+    },
+    yAxis: [
+      {
+        type: 'value',
+        name: '',
+        show: false,
+        splitLine: { show: false, lineStyle: { type: 'dashed', color: '#f5f5f5' } }
+      },
+    ],
+    tooltip: { 
+			trigger: 'axis',
+			formatter: function (params:any) {
+					var relVal = params[0].name
+					let circle = `<i style="margin-right:4px;display: inline-block;width: 10px;height: 10px;border-radius: 50%;background-color:${params[0].color}"></i>`
+					relVal += '<br/>' + circle + ' 流量: ' + formatSize(params[0].value*1024*1024)
+					return relVal;
+				}
+		},
+    grid: { top: 10, right: 10, bottom: 10, left: 10 },
+    series: [
+      {
+        name: '流量',
+        type: 'line',
+        smooth: true,
+        data: yesterdayLineData.value,
+        lineStyle: { color: '#fe9a8b' },
+        itemStyle: { color: '#fe9a8b', borderColor: '#fe9a8b' }
+      }
+    ],
+  };
+  (<any>state.global.yesterdayLine).setOption(option);
+  (<any>state.myCharts).push(state.global.yesterdayLine);
+};
+
+
+// 折线图 - 当月流量消耗
+const initMonthLineChart = () => {
+  if (!state.global.dispose.some((b: any) => b === state.global.monthLine)) state.global.monthLine.dispose();
+	state.global.monthLine = markRaw(echarts.init(monthLine.value, state.charts.theme));
+  const option = {
+    backgroundColor: state.charts.bgColor,
+    xAxis: {
+      data: monthLineXAxisData.value,
+      show: false,
+    },
+    yAxis: [
+      {
+        type: 'value',
+        name: '',
+        show: false,
+        splitLine: { show: false, lineStyle: { type: 'dashed', color: '#f5f5f5' } }
+      },
+    ],
+    tooltip: { 
+			trigger: 'axis',
+			formatter: function (params:any) {
+					var relVal = params[0].name
+					let circle = `<i style="margin-right:4px;display: inline-block;width: 10px;height: 10px;border-radius: 50%;background-color:${params[0].color}"></i>`
+					relVal += '<br/>' + circle + ' 流量: ' + formatSize(params[0].value*1024*1024)
+					return relVal;
+				}
+		},
+    grid: { top: 10, right: 10, bottom: 10, left: 10 },
+    series: [
+      {
+        name: '流量',
+        type: 'line',
+        smooth: true,
+        data: monthLineData.value,
+        lineStyle: { color: '#9E87FF' },
+        itemStyle: { color: '#9E87FF', borderColor: '#9E87FF' },
+      }
+    ],
+  };
+  (<any>state.global.monthLine).setOption(option);
+  (<any>state.myCharts).push(state.global.monthLine);
+};
+
+// 折线图 - 本年流量消耗
+const initYearLineChart = () => {
+  if (!state.global.dispose.some((b: any) => b === state.global.yearLine)) state.global.yearLine.dispose();
+	state.global.yearLine = markRaw(echarts.init(yearLine.value, state.charts.theme));
+  const option = {
+    backgroundColor: state.charts.bgColor,
+    xAxis: {
+      data: yearLineXAxisData.value,
+      show: false,
+    },
+    yAxis: [
+      {
+        type: 'value',
+        name: '',
+        show: false,
+        splitLine: { show: false, lineStyle: { type: 'dashed', color: '#f5f5f5' } }
+      },
+    ],
+    tooltip: { 
+			trigger: 'axis',
+			formatter: function (params:any) {
+					var relVal = params[0].name
+					let circle = `<i style="margin-right:4px;display: inline-block;width: 10px;height: 10px;border-radius: 50%;background-color:${params[0].color}"></i>`
+					relVal += '<br/>' + circle + ' 流量: ' + formatSize(params[0].value*1024*1024)
+					return relVal;
+				}
+		},
+    grid: { top: 10, right: 10, bottom: 10, left: 10 },
+    series: [
+      {
+        name: '流量',
+        type: 'line',
+        smooth: true,
+        data: yearLineData.value,
+        lineStyle: { color: '#fe9a8b' },
+        itemStyle: { color: '#fe9a8b', borderColor: '#fe9a8b' }
+      }
+    ],
+  };
+  (<any>state.global.yearLine).setOption(option);
+  (<any>state.myCharts).push(state.global.yearLine);
+};
+
+// 折线图 - 流量统计
+const iniFlowLineChart = async () => {
+  if (!state.global.dispose.some((b: any) => b === state.global.flowLine)) state.global.flowLine.dispose();
+	state.global.flowLine = markRaw(echarts.init(flowLine.value, state.charts.theme));
+  const option = {
+    backgroundColor: state.charts.bgColor,
+    grid: { top: 70, right: 20, bottom: 30, left: 30 },
+    tooltip: { 
+			trigger: 'axis',
+			formatter: function (params:any) {
+					var relVal = params[0].name
+					let circle = `<i style="margin-right:4px;display: inline-block;width: 10px;height: 10px;border-radius: 50%;background-color:${params[0].color}"></i>`
+					relVal += '<br/>' + circle + ' 流量: ' + params[0].value + 'MB'
+					return relVal;
+				}
+		},
+    xAxis: {
+      data: flowLineXAxisData.value,
+    },
+    yAxis: [
+      {
+        type: 'value',
+        name: '',
+        splitLine: { show: true, lineStyle: { type: 'dashed', color: '#f5f5f5' } },
+        axisLabel: {
+          margin: 2,
+          formatter: function (value:any) {
+            if (value >= 10000 && value < 10000000) {
+              value = value / 10000 + "W";
+            } else if (value >= 10000000) {
+              value = value / 10000000 + "KW";
+            }
+            return value;
+          }
+        },
+      },
+    ],
+    series: [
+      {
+        name: '流量',
+        type: 'line',
+        symbolSize: 6,
+        symbol: 'circle',
+        smooth: true,
+        data: flowLineData.value,
+        lineStyle: { color: '#9E87FF' },
+        itemStyle: { color: '#9E87FF', borderColor: '#9E87FF' },
+        areaStyle: {
+          color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
+            { offset: 0, color: '#9E87FFb3' },
+            { offset: 1, color: '#9E87FF03' },
+          ]),
+        },
+        emphasis: {
+          itemStyle: {
+            color: {
+              type: 'radial',
+              x: 0.5,
+              y: 0.5,
+              r: 0.5,
+              colorStops: [
+                { offset: 0, color: '#9E87FF' },
+                { offset: 0.4, color: '#9E87FF' },
+                { offset: 0.5, color: '#fff' },
+                { offset: 0.7, color: '#fff' },
+                { offset: 0.8, color: '#fff' },
+                { offset: 1, color: '#fff' },
+              ],
+            },
+            borderColor: '#9E87FF',
+            borderWidth: 2,
+          },
+        },
+      },
+    ],
+  };
+  (<any>state.global.flowLine).setOption(option);
+  (<any>state.myCharts).push(state.global.flowLine);
+};
+
+// 监听 vuex 中是否开启深色主题
+watch(
+  () => store.state.themeConfig.themeConfig.isIsDark,
+  (isIsDark) => {
+    nextTick(() => {
+      state.charts.theme = isIsDark ? 'dark' : '';
+      state.charts.bgColor = isIsDark ? 'transparent' : '';
+      state.charts.color = isIsDark ? '#dadada' : '#303133';
+      setTimeout(() => {
+        iniFlowLineChart();
+        initYesterdayLineChart();
+        initMonthLineChart();
+        initYearLineChart();
+      }, 500);
+    });
+  },
+  {
+    deep: true,
+    immediate: true,
+  }
+);
+</script>
+
+<style lang="scss" scoped>
+.statistics-wrap {
+  display: flex;
+  justify-content: space-between;
+  align-items: normal;
+  margin-top: 20px;
+  .title {
+    color: var(--el-text-color-primary);
+    font-size: 16px;
+    font-weight: 700;
+  }
+  .left-wrap {
+    width: 66%;
+    .top-title-wrap {
+      display: flex;
+      justify-content: space-between;
+      align-items: center;
+      margin-bottom: 16px;
+      ::v-deep .el-date-editor.el-input__wrapper {
+        width: 360px!important;
+        margin-left: 12px;
+      }
+      .operate-wrap {
+        display: flex;
+        justify-content: center;
+        align-items: center;
+      }
+    }
+  }
+  .right-wrap {
+    width: 36%;
+    .line-wrap {
+      width: 100%;
+      background-color: #fff;
+      background: #fcfcfc;
+      border: 1px solid #e0e4e8;
+      display: flex;
+      justify-content: space-between;
+      align-items: center;
+      padding: 20px;
+      position: relative;
+      .text-wrap {
+        position: absolute;
+        left: 20px;
+        top: 40px;
+        .text {
+          font-size: 14px;
+          color: #000000a3;
+        }
+        div:nth-child(2) {
+          font-size: 32px;
+          font-weight: 700;
+          overflow: hidden;
+          white-space: nowrap;
+          text-overflow: ellipsis;
+        }
+      }
+      .line-inner-wrap {
+        height: 100px;
+        width: calc(100% - 120px);
+        margin-left: 120px;
+      }
+    }
+    .line-wrap:not(:nth-child(1)) {
+      margin-top: 20px;
+    }
+  }
+}
+</style>

+ 119 - 0
src/views/iot/iotCard/index/index.vue

@@ -0,0 +1,119 @@
+<!-- 物联网卡列表 -->
+<template>
+  <div class="page-full">
+    <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-form-item>
+        <el-form-item>
+          <el-button type="primary" @click="getList">
+            <el-icon>
+              <ele-Search />
+            </el-icon>
+            查询
+          </el-button>
+          <el-button @click="resetQuery()">
+            <el-icon>
+              <ele-Refresh />
+            </el-icon>
+            重置
+          </el-button>
+        </el-form-item>
+      </el-form>
+      <el-table
+        :data="tableData"
+        max-height="calc(100vh  - 210px);"
+        v-loading="loading"
+        style="width: 100%"
+      >
+        <el-table-column fixed="left" min-width="130" label="卡号" prop="accNumber" align="center" />
+        <el-table-column min-width="180" label="ICCID" prop="iccid" align="center" />
+        <el-table-column label="绑定设备" prop="bindDeviceName" align="center" />
+        <el-table-column label="平台对接" prop="platName" align="center" />
+        <el-table-column label="运营商" prop="types" align="center">
+          <template #default="scope">{{ formatOperator(scope.row.types) }}</template>
+        </el-table-column> 
+        <el-table-column label="类型" prop="simTypes" align="center">
+          <template #default="scope">{{ formatType(scope.row.simTypes) }}</template>
+        </el-table-column>
+        <el-table-column label="总流量" prop="totalFlow" align="center" />   
+        <el-table-column label="使用流量" prop="usedFlow" align="center" />    
+        <el-table-column label="剩余流量" prop="leaveFlow" align="center" />    
+        <el-table-column width="160" label="激活日期" prop="activationTime" align="center" />     
+        <el-table-column width="160" label="更新时间" prop="updatedAt" align="center" />    
+        <el-table-column label="状态" prop="simStatus" align="center">
+          <template #default="scope">{{ formatStatus(scope.row.simStatus) }}</template>
+        </el-table-column> 
+        <el-table-column width="110" label="操作" fixed="right" prop="handle" align="center">
+					<template #default="scope">
+						<el-button size="small" text type="primary" @click="onOpenDetail(scope.row)">详情</el-button>
+						<el-button size="small" text type="warning" @click="onDel(scope.row)">删除</el-button>
+					</template>
+        </el-table-column>
+      </el-table>
+      <pagination
+        v-if="params.total"
+        :total="params.total"
+        v-model:page="params.pageNum"
+        v-model:limit="params.pageSize"
+        @pagination="getList()"
+      />
+    </el-card>
+  </div>
+</template>
+
+<script lang="ts" setup>
+import api from '/@/api/iotCard';
+import { ElMessageBox, ElMessage } from 'element-plus';
+import { useSearch } from "/@/hooks/useCommon"
+import { useRouter } from 'vue-router';
+const { params, tableData, getList, loading } = useSearch<any[]>(
+  api.simCard.getList,
+  "Data"
+)
+
+getList();
+
+const router = useRouter();
+
+/** 重置按钮操作 */
+const resetQuery = () => {
+	params.keyWord = ""
+  getList();
+};
+
+/**
+ * 单一删除
+ */
+const onDel = (row: any) => {
+	ElMessageBox.confirm(`此操作将卡号为:“${row.accNumber}”,是否继续?`, '提示', {
+		confirmButtonText: '确认',
+		cancelButtonText: '取消',
+		type: 'warning',
+	}).then(async () => {
+		await api.simCard.deleteItem({ ids: [row.id] });
+		ElMessage.success('删除成功');
+		getList();
+	});
+};
+
+const formatOperator = (val:number) => {
+  // 1电信,2联通,3移动
+  return ['', '电信', '联通', '移动'][val];
+}
+
+const formatType = (val:number) => {
+  // 1月卡,2季卡,3年卡,4其他
+  return ['', '月卡', '季卡', '年卡', '其他'][val];
+}
+
+const formatStatus = (val:number) => {
+  // 1:可激活 2:测试激活 3:测试去激活 4:在用5:停机6:运营商管理状态
+  return ['', '可激活', '测试激活', '测试去激活', '在用', '停机', '运营商管理状态'][val];
+}
+
+const onOpenDetail = (item:any) => {
+  router.push('/iotmanager/iotCard/index/detail/'+item.id);
+}
+</script>

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

@@ -0,0 +1,151 @@
+<!-- 平台接入-新增或者编辑 -->
+<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 { ref, unref } from 'vue';
+import { ElMessage } from 'element-plus';
+
+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
+    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: "",
+    status: 1
+  }
+}
+
+/**
+ * 点击取消按钮
+ */
+const onCancel = () => {
+  closeDialog();
+};
+
+/**
+ * 关闭弹窗
+ */
+const closeDialog = () => {
+  isShowDialog.value = false;
+};
+
+const openDialog = (item:any) => {
+  if(item) {
+    // 修改
+    ruleForm.value = { ...item };
+  }
+  isShowDialog.value = true;
+}
+
+defineExpose({ openDialog })
+</script>

+ 112 - 0
src/views/iot/iotCard/platformManage/index.vue

@@ -0,0 +1,112 @@
+<!-- 平台接入列表 -->
+<template>
+  <div class="page-full">
+    <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="请输入关键字搜索" clearable />
+        </el-form-item>
+        <el-form-item>
+          <el-button type="primary" @click="getList">
+            <el-icon>
+              <ele-Search />
+            </el-icon>
+            查询
+          </el-button>
+          <el-button @click="resetQuery()">
+            <el-icon>
+              <ele-Refresh />
+            </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
+        :data="tableData"
+        max-height="calc(100vh  - 210px);"
+        v-loading="loading"
+        style="width: 100%"
+      >
+        <el-table-column label="名称" prop="name" align="center" />
+        <el-table-column label="状态" prop="simStatus" align="center">
+          <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" />       
+
+        <el-table-column width="110" label="操作" fixed="right" prop="handle" align="center">
+					<template #default="scope">
+						<el-button size="small" text type="primary" @click="onOpenDetail(scope.row)">详情</el-button>
+						<el-button size="small" text type="warning" @click="onDel(scope.row)">删除</el-button>
+					</template>
+        </el-table-column>
+      </el-table>
+      <pagination
+        v-if="params.total"
+        :total="params.total"
+        v-model:page="params.pageNum"
+        v-model:limit="params.pageSize"
+        @pagination="getList()"
+      />
+    </el-card>
+    <AddOrEditItem ref="AddOrEditItemRef" @updateList="getList()" />
+  </div>
+</template>
+
+<script lang="ts" setup>
+import api from '/@/api/iotCard';
+import { defineAsyncComponent, ref } from 'vue';
+import { ElMessageBox, ElMessage } from 'element-plus';
+import { useSearch } from "/@/hooks/useCommon"
+const AddOrEditItem = defineAsyncComponent(() => import('./addOrEditItem.vue'));
+
+const { params, tableData, getList, loading } = useSearch<any[]>(
+  api.platform.getList,
+  "Data"
+)
+getList();
+
+const AddOrEditItemRef = ref();
+
+/** 重置按钮操作 */
+const resetQuery = () => {
+	params.keyWord = ""
+  getList();
+};
+
+/**
+ * 单一删除
+ */
+const onDel = (row: any) => {
+	ElMessageBox.confirm(`此操作将卡号为:“${row.accNumber}”,是否继续?`, '提示', {
+		confirmButtonText: '确认',
+		cancelButtonText: '取消',
+		type: 'warning',
+	}).then(async () => {
+		await api.simCard.deleteItem({ ids: [row.id] });
+		ElMessage.success('删除成功');
+		getList();
+	});
+};
+
+const formatStatus = (val:any) => {
+  // 1:开启 0:禁用
+  return ['', '开启', '禁用'][val];
+}
+
+const onOpenDetail = (item:any) => {
+  AddOrEditItemRef.value.openDialog(item);
+}
+
+const toAddItemPage = () => {
+  AddOrEditItemRef.value.openDialog();
+}
+</script>

+ 14 - 2
src/views/iot/iotmanager/dashboard.vue

@@ -3,7 +3,7 @@
 		<el-row :gutter="15" class="home-card-one mb15">
 			<el-col :xs="24" :sm="12" :md="12" :lg="6" :xl="6" v-for="(v, k) in homeOne" :key="k" :class="{ 'home-media home-media-lg': k > 1, 'home-media-sm': k === 1 }">
 				<div class="home-card-item ">
-					<div class="">{{ v.num3 }}</div>
+					<div>{{ v.num3 }}</div>
 					<div class="flex-margin flex w100" :class="` home-one-animation${k}`">
 						<div class="flex-auto">
 
@@ -222,7 +222,7 @@ export default defineComponent({
 						splitLine: { show: true, lineStyle: { type: 'dashed', color: '#f5f5f5' } },
 						axisLabel: {
 							margin: 2,
-							formatter: function (value, index) {
+							formatter: function (value:any) {
 								if (value >= 10000 && value < 10000000) {
 									value = value / 10000 + "W";
 								} else if (value >= 10000000) {
@@ -474,6 +474,18 @@ export default defineComponent({
 				initEchartsResizeFun();
 			}
 		);
+		watch(
+			() => state.lineChartAlarmTotalData,
+			() => {
+				initLineChart();
+			}
+		);
+		watch(
+			() => state.pieChartData,
+			() => {
+				initPieChart();
+			}
+		);
 		// 监听 vuex 中是否开启深色主题
 		watch(
 			() => store.state.themeConfig.themeConfig.isIsDark,

+ 5 - 8
src/views/iot/network/server/component/list.vue

@@ -3,7 +3,7 @@
     <el-table-column align="center" prop="id" label="ID" width="100" v-col="'id'" />
     <!-- <el-table-column align="center" prop="server" label="服务器"/> -->
     <el-table-column align="center" prop="name" label="名称" v-col="'name'" />
-    <el-table-column align="center" prop="types" v-col="'types'" label="类型" :formatter="(a: any) => typesFormat(a.types)" />
+    <el-table-column align="center" prop="types" v-col="'types'" label="类型" :formatter="(a) => typesFormat(a.types)" />
     <el-table-column align="center" prop="addr" label="地址" v-col="'addr'" />
     <el-table-column show-overflow-tooltip align="center" prop="createdAt" label="创建时间" width="170" v-col="'createdAt'" />
     <!-- <el-table-column align="center" prop="last" label="最近上线"/> -->
@@ -42,7 +42,7 @@
 </template>
 
 <script lang="ts">
-import { ref, toRefs, reactive, onMounted, nextTick, getCurrentInstance, unref, defineComponent } from 'vue';
+import { toRefs, reactive, onMounted, getCurrentInstance, unref, defineComponent } from 'vue';
 import { ElMessageBox, ElMessage } from 'element-plus';
 import { useRouter } from 'vue-router';
 
@@ -75,7 +75,7 @@ export default defineComponent({
       default: () => { },
     },
   },
-  setup(props, { emit }) {
+  setup(props) {
     const router = useRouter();
 
     const { proxy } = getCurrentInstance() as any;
@@ -100,7 +100,7 @@ export default defineComponent({
     });
     // 改变状态
     const onChangeStatus = (id: number, status: number) => {
-      api.server.changeServerStatus({ id: id, status: status }).then((res: any) => {
+      api.server.changeServerStatus({ id: id, status: status }).then(() => {
         ElMessage.success(status ? '已开启' : '已关闭');
         fetchList();
       })
@@ -145,7 +145,7 @@ export default defineComponent({
         type: 'warning',
       })
         .then(() => {
-          api.server.deleteItem({ ids: [row.id] }).then((res: any) => {
+          api.server.deleteItem({ ids: [row.id] }).then(() => {
             fetchList()
             ElMessage.success('删除成功');
           });
@@ -181,8 +181,5 @@ export default defineComponent({
 ::v-deep div.more-opearte-wrap {
   flex-direction: row;
   background-color: pink;
-
-  // padding: 4px!important;
-  div {}
 }
 </style>

+ 44 - 54
src/views/iot/network/server/component/serverDetail.vue

@@ -8,44 +8,51 @@
 -->
 <!-- 服务器详情页 -->
 <template>
-    <div class="server-detail-wrap">
-        <div class="server-detail-item-wrap">
-            <div class="label">名称</div>
-            <div class="value">{{ detail.name }}</div>
-        </div>
-        <div class="server-detail-item-wrap">
-            <div class="label">类型</div>
-            <div class="value">{{ detail.types }}</div>
-        </div>
-        <div class="server-detail-item-wrap">
-            <div class="label">地址</div>
-            <div class="value">{{ detail.addr }}</div>
-        </div>
-        <div class="server-detail-item-wrap">
-            <div class="label">状态</div>
-            <div class="value">{{ detail.status ? '启动' : '未启动' }}</div>
-        </div>
-        <div class="server-detail-item-wrap">
-            <div class="label">禁用</div>
-            <div class="value">
-                <el-switch :loading="loading" :before-change="onChangeStatus" :disabled="!detail.status" :active-value="0" :inactive-value="1" size="small" v-model="detail.status" />
-            </div>
-        </div>
-        <div class="server-detail-item-wrap">
-            <div class="label">创建时间</div>
-            <div class="value">{{ detail.createdAt }}</div>
-        </div>
-    </div>
+    <el-descriptions :column="2" border>
+        <el-descriptions-item>
+            <template #label>
+                <div class="cell-item">名称</div>
+            </template>
+            {{detail.name}}
+        </el-descriptions-item>
+        <el-descriptions-item>
+            <template #label>
+                <div class="cell-item">类型</div>
+            </template>
+            {{detail.types}}
+        </el-descriptions-item>
+        <el-descriptions-item>
+            <template #label>
+                <div class="cell-item">地址</div>
+            </template>
+            {{detail.addr}}
+        </el-descriptions-item>
+        <el-descriptions-item>
+            <template #label>
+                <div class="cell-item">状态</div>
+            </template>
+            {{ detail.status ? '启动' : '未启动' }}
+        </el-descriptions-item>
+        <el-descriptions-item>
+            <template #label>
+                <div class="cell-item">禁用</div>
+            </template>
+            <el-switch :loading="loading" :before-change="onChangeStatus" :disabled="!detail.status" :active-value="0" :inactive-value="1" size="small" v-model="detail.status" />
+        </el-descriptions-item>
+        <el-descriptions-item>
+            <template #label>
+                <div class="cell-item">创建时间</div>
+            </template>
+            {{ detail.createdAt }}
+        </el-descriptions-item>
+    </el-descriptions>
 </template>
 <script lang="ts">
-import { toRefs, reactive, onMounted, ref, defineComponent } from 'vue';
-import { Delete, Edit, Search, Share, Upload } from '@element-plus/icons-vue';
-import { ElMessageBox, ElMessage } from 'element-plus';
-
+import { toRefs, reactive, onMounted, defineComponent } from 'vue';
+import { ElMessage } from 'element-plus';
 import api from '/@/api/network';
 
 interface TableDataState {
-    // detail: object,
     loading: boolean
 }
 export default defineComponent({
@@ -53,10 +60,10 @@ export default defineComponent({
     props: {
         detail: {
             type: Object,
-            default: ''
+            default: () => {}
         }
     },
-    setup(props, context) {
+    setup(props) {
         const state = reactive<TableDataState>({
             loading: false
         });
@@ -65,8 +72,8 @@ export default defineComponent({
         // 禁用状态
         const onChangeStatus = () => {
             state.loading = true
-            return new Promise((resolve) => {
-                api.server.changeServerStatus({ id: props.detail.id, status: 0 }).then((res: any) => {
+            return new Promise(() => {
+                api.server.changeServerStatus({ id: props.detail.id, status: 0 }).then(() => {
                     state.loading = false
                     ElMessage.success('已关闭');
                     props.detail.status = 0
@@ -83,20 +90,3 @@ export default defineComponent({
 });
 </script>
 
-<style lang="scss" scoped>
-.server-detail-wrap {
-    .server-detail-item-wrap {
-        display: flex;
-        justify-content: space-between;
-        padding: 10px;
-        border-left: 1px solid var(--el-border-color-light);
-        border-top: 1px solid var(--el-border-color-light);
-        border-right: 1px solid var(--el-border-color-light);
-    }
-
-    .server-detail-item-wrap:last-child {
-        border-bottom: 1px solid var(--el-border-color-light);
-    }
-}
-</style>
-

+ 17 - 29
src/views/iot/network/server/create.vue

@@ -1,6 +1,6 @@
 <template>
   <div class="page bg padding page-full Ipt-2" style="position: relative;">
-    <el-tabs v-model="activeName" class="demo-tabs" @tab-click="handleClick">
+    <el-tabs v-model="activeName" @tab-click="handleClick">
       <el-tab-pane label="新建服务器" name="first">
         <el-collapse v-model="activeViewName">
           <el-collapse-item title="基本信息" name="1">
@@ -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="协议参数">
@@ -126,28 +116,25 @@
 import { toRefs, reactive, onMounted, ref, defineComponent, getCurrentInstance, watch } from 'vue';
 import { Edit } from '@element-plus/icons-vue';
 import { ElMessage } from 'element-plus';
-import type { TabsPaneContext } from 'element-plus'
-import codeEditor from '/@/components/codeEditor/index.vue'
-
-import serverDetail from './component/serverDetail.vue'
+import type { TabsPaneContext } from 'element-plus';
+import codeEditor from '/@/components/codeEditor/index.vue';
 
 import { useRoute } from 'vue-router';
 
 import api from '/@/api/network';
 import api2 from '/@/api/system';
+import deviceApi from '/@/api/device'
 
 interface TableDataState {
-  // ids: number[];
-  // id: string;
   activeViewName: string[];
   resourceModalPro: {
     mode: string,
     content: string,
-    // content: object,
   },
   detail: object,
   form: object,
   certificateList: object[];
+  messageData: object[];
   stick: {
     // 分隔符
     "delimit,omitempty": string,
@@ -168,7 +155,7 @@ interface TableDataState {
 }
 export default defineComponent({
   name: 'serverCreate',
-  components: { codeEditor, serverDetail },
+  components: { codeEditor },
   props: {
     type: {
       type: String,
@@ -176,7 +163,7 @@ export default defineComponent({
     }
   },
 
-  setup(props, context) {
+  setup(props) {
     const { proxy } = getCurrentInstance() as any;
     const route = useRoute();
     const { network_server_type, network_protocols } = proxy.useDict('network_server_type', 'network_protocols');
@@ -218,6 +205,7 @@ export default defineComponent({
       detail: {},
       activeViewName: ['1', '2', '3'],
       certificateList: [],
+      messageData: [],
       form: {
         id: "",
         // AccessToken
@@ -245,7 +233,7 @@ export default defineComponent({
         },
         // 协议适配
         protocol: {
-          name: "ModbusTCP",
+          name: "SagooMqtt",
           options: {}
         },
         // 心跳包
@@ -260,6 +248,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 = () => {
@@ -285,10 +278,6 @@ export default defineComponent({
       api.server.addItem(params).then((res: any) => {
         ElMessage.success('添加成功')
         goBack()
-        // const { list, total, page } = res
-        // state.data = list
-        // state.total = total
-        // state.param.page = page
       });
     };
     onMounted(() => {
@@ -330,7 +319,6 @@ export default defineComponent({
       () => state.form.types,
       (value) => {
         getCertificateList()
-        // api.certificate.getList();
       }
     );
 

+ 7 - 10
src/views/iot/network/server/detail.vue

@@ -1,27 +1,24 @@
 <template>
-	<el-card class="system-dic-container" style="position: relative;">
-		<el-tabs v-model="activeName" class="demo-tabs" @tab-click="handleClick">
+	<div class="page bg border padding Ipt-2" style="position: relative;">
+		<el-tabs v-model="activeName" @tab-click="handleClick">
 			<el-tab-pane label="服务器详情" name="first">
 				<serverDetail :detail="detail" />
 			</el-tab-pane>
 			<!-- <el-tab-pane label="相关详情" name="second">相关详情</el-tab-pane>
 			<el-tab-pane label="通道" name="third">通道</el-tab-pane> -->
 		</el-tabs>
-		<div style="position: absolute;right:20px;top: 34px;">
+		<div style="position: absolute;right:20px;top: 18px;">
 			<el-icon @click="freshData" style="font-size: 16px;margin-right:6px;"><ele-RefreshRight /></el-icon>
 			<!-- <el-icon style="font-size: 16px;margin: 0 6px;"><ele-Operation /></el-icon> -->
 			<el-icon @click="toEdit"  style="cursor: pointer;font-size: 16px;"><ele-Edit /></el-icon>
 		</div>
-	</el-card>
+	</div>
 </template>
 <script lang="ts">
 import { toRefs, reactive, onMounted, ref, defineComponent } from 'vue';
-// import { Delete, Edit, Search, Share, Upload } from '@element-plus/icons-vue';
 import { ElMessage } from 'element-plus';
-import type { TabsPaneContext } from 'element-plus'
-
-import serverDetail from './component/serverDetail.vue'
-
+import type { TabsPaneContext } from 'element-plus';
+import serverDetail from './component/serverDetail.vue';
 import { useRoute, useRouter } from 'vue-router';
 
 import api from '/@/api/network';
@@ -43,7 +40,7 @@ export default defineComponent({
 		}
 	},
 
-	setup(props, context) {
+	setup(props) {
 		const route = useRoute();
 		const router = useRouter();
 		const state = reactive<TableDataState>({

+ 366 - 360
src/views/iot/network/server/edit.vue

@@ -1,403 +1,409 @@
 <template>
-    <el-card class="system-dic-container" style="position: relative;">
-        <el-tabs v-model="activeName" class="demo-tabs">
-            <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="98px">
-                                <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 @change="handleChangeType" v-model="form.types" placeholder="请选择类型">
-                                        <el-option v-for="dict in network_server_type" :key="dict.value" :label="dict.label" :value="dict.value">
-                                        </el-option>
-                                    </el-select>
-                                </el-form-item>
-                                <el-form-item v-if="form.types == 'tcp'" label="粘拆包规则">
-                                    <el-select @change="initData" v-model="stickValue" placeholder="请选择类型">
-                                        <el-option v-for="dict in stick_type" :key="dict.value" :label="dict.label" :value="dict.value">
-                                        </el-option>
-                                    </el-select>
-                                </el-form-item>
-                                <el-form-item v-if="form.types == 'tcp' && stickValue">
-                                    <el-form-item v-if="stickValue == '分隔符'" class="flex-column" label="分隔符">
-                                        <el-input v-model="stick['delimit,omitempty']" placeholder="请填写分隔符" />
-                                    </el-form-item>
-                                    <el-form-item v-if="stickValue == '自定义脚本'" class="flex-column" label="自定义脚本">
-                                        <el-input v-model="stick['custom,omitempty']" placeholder="请填写自定义脚本" />
-                                    </el-form-item>
-                                    <el-form-item v-if="stickValue == '固定长度'" class="flex-column" label="固定长度">
-                                        <el-input type="number" v-model="stick['fixedLen,omitempty']" placeholder="请填写固定长度" />
-                                    </el-form-item>
-                                    <el-form-item v-if="stickValue == '长度字段'" class="flex-column" label="长度">
-                                        <el-input type="number" v-model="stick['len,omitempty']['len']" placeholder="请填写长度" />
-                                    </el-form-item>
-                                    <el-form-item v-if="stickValue == '长度字段'" class="flex-column" label="偏移量">
-                                        <el-input type="number" v-model="stick['len,omitempty']['offset']" placeholder="请填写偏移量" />
-                                    </el-form-item>
-                                    <el-form-item v-if="stickValue == '长度字段'" class="flex-column" label="大小端">
-                                        <el-select v-model="stick['len,omitempty']['endian']" placeholder="请选择大小端">
-                                            <el-option label="大端" value="大端" />
-                                            <el-option label="小端" value="小端" />
-                                        </el-select>
-                                    </el-form-item>
-                                </el-form-item>
-                                <el-form-item label="地址">
-                                    <el-input v-model="form.addr" placeholder="端口号" />
-                                </el-form-item>
-                                <el-form-item label="开启TLS">
-                                    <el-radio-group v-model="form.isTls" class="ml-4">
-                                        <el-radio :label="1">是</el-radio>
-                                        <el-radio :label="0">否</el-radio>
-                                    </el-radio-group>
-                                </el-form-item>
-                                <el-form-item v-if="form.isTls == 1 && form.types != 'mqtt_server'" label="选择证书">
-                                    <el-select v-model="form.certificateId" placeholder="请选择证书">
-                                        <el-option v-for="item in certificateList" :key="item.id" :label="item.name" :value="item.id">
-                                        </el-option>
-                                    </el-select>
-                                </el-form-item>
-                                <el-form-item v-if="form.isTls == 1 && form.types == 'mqtt_server'" label="接入方式">
-                                    <el-select v-model="form.authType" placeholder="选择接入方式">
-                                        <el-option label="Basic" :value="1" />
-                                        <el-option label="AccessToken" :value="2" />
-                                        <!-- <el-option label="证书" :value="3" /> -->
-                                    </el-select>
-                                    <el-form-item v-if="form.authType == 1" class="flex-column" label="用户名">
-                                        <el-input v-model="form.authUser" placeholder="请填写用户名" />
-                                    </el-form-item>
-                                    <el-form-item v-if="form.authType == 1" class="flex-column" label="密码">
-                                        <el-input v-model="form.authPasswd" placeholder="请填写密码" />
-                                    </el-form-item>
-                                    <el-form-item v-if="form.authType == 2" class="flex-column" label="Aceess Token">
-                                        <el-input v-model="form.accessToken" placeholder="请填写Aceess Token" />
-                                    </el-form-item>
-                                </el-form-item>
-                                <el-form-item label="启用">
-                                    <el-switch :active-value="1" :inactive-value="0" v-model="form.status" />
-                                </el-form-item>
-                            </el-form>
-                        </div>
-                    </el-collapse-item>
-                    <el-collapse-item title="注册包" name="2">
-                        <el-form style="width: 600px;margin: 0 auto;" :model="form" label-width="98px">
-                            <el-form-item label="正则表达式">
-                                <el-input v-model="form.register.regex" placeholder="请填写名称" />
-                            </el-form-item>
-                        </el-form>
-                    </el-collapse-item>
-                    <el-collapse-item title="协议适配" name="3">
-                        <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-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/server')">取消</el-button>
-            <el-button @click="submit" size="medium" type="primary">提交</el-button>
-        </div>
-    </el-card>
+  <div class="page bg padding border page-full Ipt-2" 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="98px">
+                <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 @change="handleChangeType" v-model="form.types" placeholder="请选择类型">
+                    <el-option v-for="dict in network_server_type" :key="dict.value" :label="dict.label" :value="dict.value">
+                    </el-option>
+                  </el-select>
+                </el-form-item>
+                <el-form-item v-if="form.types == 'tcp'" label="粘拆包规则">
+                  <el-select @change="initData" v-model="stickValue" placeholder="请选择类型">
+                    <el-option v-for="dict in stick_type" :key="dict.value" :label="dict.label" :value="dict.value">
+                    </el-option>
+                  </el-select>
+                </el-form-item>
+                <el-form-item v-if="form.types == 'tcp' && stickValue">
+                  <el-form-item v-if="stickValue == '分隔符'" class="flex-column" label="分隔符">
+                    <el-input v-model="stick['delimit,omitempty']" placeholder="请填写分隔符" />
+                  </el-form-item>
+                  <el-form-item v-if="stickValue == '自定义脚本'" class="flex-column" label="自定义脚本">
+                    <el-input v-model="stick['custom,omitempty']" placeholder="请填写自定义脚本" />
+                  </el-form-item>
+                  <el-form-item v-if="stickValue == '固定长度'" class="flex-column" label="固定长度">
+                    <el-input type="number" v-model="stick['fixedLen,omitempty']" placeholder="请填写固定长度" />
+                  </el-form-item>
+                  <el-form-item v-if="stickValue == '长度字段'" class="flex-column" label="长度">
+                    <el-input type="number" v-model="stick['len,omitempty']['len']" placeholder="请填写长度" />
+                  </el-form-item>
+                  <el-form-item v-if="stickValue == '长度字段'" class="flex-column" label="偏移量">
+                    <el-input type="number" v-model="stick['len,omitempty']['offset']" placeholder="请填写偏移量" />
+                  </el-form-item>
+                  <el-form-item v-if="stickValue == '长度字段'" class="flex-column" label="大小端">
+                    <el-select v-model="stick['len,omitempty']['endian']" placeholder="请选择大小端">
+                      <el-option label="大端" value="大端" />
+                      <el-option label="小端" value="小端" />
+                    </el-select>
+                  </el-form-item>
+                </el-form-item>
+                <el-form-item label="地址">
+                  <el-input v-model="form.addr" placeholder="端口号" />
+                </el-form-item>
+                <el-form-item label="开启TLS">
+                  <el-radio-group v-model="form.isTls" class="ml-4">
+                    <el-radio :label="1">是</el-radio>
+                    <el-radio :label="0">否</el-radio>
+                  </el-radio-group>
+                </el-form-item>
+                <el-form-item v-if="form.isTls == 1 && form.types != 'mqtt_server'" label="选择证书">
+                  <el-select v-model="form.certificateId" placeholder="请选择证书">
+                    <el-option v-for="item in certificateList" :key="item.id" :label="item.name" :value="item.id">
+                    </el-option>
+                  </el-select>
+                </el-form-item>
+                <el-form-item v-if="form.isTls == 1 && form.types == 'mqtt_server'" label="接入方式">
+                  <el-select v-model="form.authType" placeholder="选择接入方式">
+                    <el-option label="Basic" :value="1" />
+                    <el-option label="AccessToken" :value="2" />
+                    <!-- <el-option label="证书" :value="3" /> -->
+                  </el-select>
+                  <el-form-item v-if="form.authType == 1" class="flex-column" label="用户名">
+                    <el-input v-model="form.authUser" placeholder="请填写用户名" />
+                  </el-form-item>
+                  <el-form-item v-if="form.authType == 1" class="flex-column" label="密码">
+                    <el-input v-model="form.authPasswd" placeholder="请填写密码" />
+                  </el-form-item>
+                  <el-form-item v-if="form.authType == 2" class="flex-column" label="Aceess Token">
+                    <el-input v-model="form.accessToken" placeholder="请填写Aceess Token" />
+                  </el-form-item>
+                </el-form-item>
+                <el-form-item label="启用">
+                  <el-switch :active-value="1" :inactive-value="0" v-model="form.status" />
+                </el-form-item>
+              </el-form>
+            </div>
+          </el-collapse-item>
+          <el-collapse-item title="注册包" name="2">
+            <el-form style="width: 600px;margin: 0 auto;" :model="form" label-width="98px">
+              <el-form-item label="正则表达式">
+                <el-input v-model="form.register.regex" placeholder="请填写名称" />
+              </el-form-item>
+            </el-form>
+          </el-collapse-item>
+          <el-collapse-item title="协议适配" name="3">
+            <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 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: 14px;">
+      <el-button size="small" @click="$router.replace('/iotmanager/network/server')">取消</el-button>
+      <el-button @click="submit" size="small" type="primary">提交</el-button>
+    </div>
+  </div>
 </template>
 <script lang="ts">
 import { watch, toRefs, reactive, onMounted, ref, defineComponent, getCurrentInstance } from 'vue';
 import { ElMessage } from 'element-plus';
-import type { TabsPaneContext } from 'element-plus'
-import codeEditor from '/@/components/codeEditor/index.vue'
-
-import serverDetail from './component/serverDetail.vue'
+import type { TabsPaneContext } from 'element-plus';
+import codeEditor from '/@/components/codeEditor/index.vue';
 
 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[];
-    resourceModalPro: {
-        mode: string,
-        content: string,
-    },
-    detail: object,
-    form: object,
-    certificateList: object[];
-    stick: {
-        // 分隔符
-        "delimit,omitempty": string,
-        // 自定义脚本
-        "custom,omitempty": string,
-        // 固定长度
-        "fixedLen,omitempty": number,
-        // 长度字段
-        "len,omitempty": {
-            "len": number,
-            "offset": number,
-            "endian": string
-        }
-    };
-    stick_type: object[];
-    stickValue: string;
+  activeViewName: string[];
+  resourceModalPro: {
+    mode: string,
+    content: string,
+  },
+  detail: object,
+  form: object,
+  certificateList: object[];
+  messageData: object[];
+  stick: {
+    // 分隔符
+    "delimit,omitempty": string,
+    // 自定义脚本
+    "custom,omitempty": string,
+    // 固定长度
+    "fixedLen,omitempty": number,
+    // 长度字段
+    "len,omitempty": {
+      "len": number,
+      "offset": number,
+      "endian": string
+    }
+  };
+  stick_type: object[];
+  stickValue: string;
 }
 export default defineComponent({
-    name: 'serverCreate',
-    components: { codeEditor, serverDetail },
-    props: {
-        type: {
-            type: String,
-            default: ''
-        }
-    },
+  name: 'serverCreate',
+  components: { codeEditor },
+  props: {
+    type: {
+      type: String,
+      default: ''
+    }
+  },
 
-    setup(props, context) {
-        const { proxy } = getCurrentInstance() as any;
-        const route = useRoute();
-        const router = useRouter();
-        const { network_server_type, network_protocols } = proxy.useDict('network_server_type', 'network_protocols');
+  setup(props) {
+    const { proxy } = getCurrentInstance() as any;
+    const route = useRoute();
+    const router = useRouter();
+    const { network_server_type, network_protocols } = proxy.useDict('network_server_type', 'network_protocols');
 
-        const state = reactive<TableDataState>({
-            // id: "",
-            stickValue: "",
-            stick: {
-                "delimit,omitempty": "",
-                "custom,omitempty": "",
-                "fixedLen,omitempty": 0,
-                "len,omitempty": {
-                    "len": 0,
-                    "offset": 0,
-                    "endian": ""
-                }
-            },
-            stick_type: [
-                {
-                    label: "分隔符",
-                    value: "分隔符"
-                },
-                {
-                    label: "自定义脚本",
-                    value: "自定义脚本"
-                },
-                {
-                    label: "固定长度",
-                    value: "固定长度"
-                },
-                {
-                    label: "长度字段",
-                    value: "长度字段"
-                }
-            ],
-            resourceModalPro: {
-                mode: '',
-                content: ''
-            },
-            certificateList: [],
-            detail: {},
-            activeViewName: ['1', '2', '3'],
-            form: {
-                // 名称
-                name: '',
-                // AccessToken
-                accessToken: "",
-                // 认证密码
-                authPasswd: "",
-                // 认证用户
-                authUser: "",
-                // 认证方式(1=Basic,2=AccessToken,3=证书)
-                authType: 3,
-                // 是否开启TLS
-                isTls: 0,
-                // 证书id
-                certificateId: "",
-                // 类型
-                types: 'tcp',
-                // 禁用
-                status: false,
-                // 地址
-                addr: '',
-                register: {
-                    regex: "^\w+$"
-                },
-                // 协议适配
-                protocol: {
-                    name: "ModbusTCP",
-                    options: {}
-                },
-                // 心跳包
-                heartbeat: {
-                    enable: false,
-                    hex: "",
-                    regex: "^\\w+$",
-                    text: "",
-                    timeout: 30
-                },
-                // 设备
-                devices: []
-            }
-        });
-        const mirrorRef = ref('mirrorRef')
-        const activeName = ref('first')
-        const getDetail = () => {
-            const id = route.params && route.params.id;
-            api.server.getDetail({ "id": id }).then((res: any) => {
+    const state = reactive<TableDataState>({
+      // id: "",
+      stickValue: "",
+      stick: {
+        "delimit,omitempty": "",
+        "custom,omitempty": "",
+        "fixedLen,omitempty": 0,
+        "len,omitempty": {
+          "len": 0,
+          "offset": 0,
+          "endian": ""
+        }
+      },
+      stick_type: [
+        {
+          label: "分隔符",
+          value: "分隔符"
+        },
+        {
+          label: "自定义脚本",
+          value: "自定义脚本"
+        },
+        {
+          label: "固定长度",
+          value: "固定长度"
+        },
+        {
+          label: "长度字段",
+          value: "长度字段"
+        }
+      ],
+      resourceModalPro: {
+        mode: '',
+        content: ''
+      },
+      messageData: [],
+      certificateList: [],
+      detail: {},
+      activeViewName: ['1', '2', '3'],
+      form: {
+        // 名称
+        name: '',
+        // AccessToken
+        accessToken: "",
+        // 认证密码
+        authPasswd: "",
+        // 认证用户
+        authUser: "",
+        // 认证方式(1=Basic,2=AccessToken,3=证书)
+        authType: 3,
+        // 是否开启TLS
+        isTls: 0,
+        // 证书id
+        certificateId: "",
+        // 类型
+        types: 'tcp',
+        // 禁用
+        status: false,
+        // 地址
+        addr: '',
+        register: {
+          regex: "^\w+$"
+        },
+        // 协议适配
+        protocol: {
+          name: "SagooMqtt",
+          options: {}
+        },
+        // 心跳包
+        heartbeat: {
+          enable: false,
+          hex: "",
+          regex: "^\\w+$",
+          text: "",
+          timeout: 30
+        },
+        // 设备
+        devices: []
+      }
+    });
 
-                const { id, isTls, authType, certificateId, authUser, authPasswd, accessToken, name, types, status, addr, register, protocol, heartbeat, devices, stick } = res
-                state.form["id"] = id
-                state.form["name"] = name
-                state.form["types"] = types
-                state.form["status"] = status
-                state.form["isTls"] = isTls
-                state.form["addr"] = addr
-                state.form["authType"] = authType
-                state.form["authUser"] = authUser
-                state.form["authPasswd"] = authPasswd
-                state.form["accessToken"] = accessToken
-                state.form["certificateId"] = certificateId
-                state.form["register"] = JSON.parse(register)
-                state.form["protocol"] = JSON.parse(protocol)
-                state.form["heartbeat"] = JSON.parse(heartbeat)
-                state.form["devices"] = JSON.parse(devices)
-                let stickInfo = JSON.parse(stick)
-                if (stickInfo.len && stickInfo.len.endian) {
-                    state.stick["len,omitempty"] = stickInfo.len
-                } else if (stickInfo.fixedLen) {
-                    state.stick["fixedLen,omitempty"] = stickInfo.fixedLen
-                } else if (stickInfo.custom) {
-                    state.stick["custom,omitempty"] = stickInfo.custom
-                } else if (stickInfo.delimit) {
-                    state.stick["delimit,omitempty"] = stickInfo.delimit
-                }
-                if (types == 'tcp') {
-                    if (state.stick['delimit,omitempty']) {
-                        state.stickValue = '分隔符'
-                    } else if (state.stick['custom,omitempty']) {
-                        state.stickValue = '自定义脚本'
-                    } else if (state.stick['fixedLen,omitempty']) {
-                        state.stickValue = '固定长度'
-                    } else if (state.stick['len,omitempty']) {
-                        state.stickValue = '长度字段'
-                    }
-                }
+    deviceApi.product.getTypesAll({ types: 'protocol' }).then((res: any) => {
+      state.messageData = res || [];
+    });
 
-                let jsonData = JSON.stringify(JSON.parse(protocol).options);
-                state.resourceModalPro.content = JSON.stringify(JSON.parse(jsonData), null, 4);
-                mirrorRef.value.setValue(state.resourceModalPro.content);
-            })
-        };
-        const submit = () => {
-            let params = {
-                ...state.form,
-                "stick": {
-                    ...state.stick
-                }
-            }
-            if (mirrorRef.value.getValue()) {
-                state.form.protocol.options = eval("(" + mirrorRef.value.getValue() + ")")
-            }
-            // return
-            api.server.editItem(params).then((res: any) => {
-                ElMessage.success('修改成功')
-                goBack()
-            });
-        };
-        const goBack = () => {
-            router.go(-1);
+    const mirrorRef = ref('mirrorRef')
+    const activeName = ref('first')
+    const getDetail = () => {
+      const id = route.params && route.params.id;
+      api.server.getDetail({ "id": id }).then((res: any) => {
+        const { id, isTls, authType, certificateId, authUser, authPasswd, accessToken, name, types, status, addr, register, protocol, heartbeat, devices, stick } = res
+        state.form["id"] = id
+        state.form["name"] = name
+        state.form["types"] = types
+        state.form["status"] = status
+        state.form["isTls"] = isTls
+        state.form["addr"] = addr
+        state.form["authType"] = authType
+        state.form["authUser"] = authUser
+        state.form["authPasswd"] = authPasswd
+        state.form["accessToken"] = accessToken
+        state.form["certificateId"] = certificateId
+        state.form["register"] = JSON.parse(register)
+        state.form["protocol"] = JSON.parse(protocol)
+        state.form["heartbeat"] = JSON.parse(heartbeat)
+        state.form["devices"] = JSON.parse(devices)
+        let stickInfo = JSON.parse(stick)
+        if (stickInfo.len && stickInfo.len.endian) {
+          state.stick["len,omitempty"] = stickInfo.len
+        } else if (stickInfo.fixedLen) {
+          state.stick["fixedLen,omitempty"] = stickInfo.fixedLen
+        } else if (stickInfo.custom) {
+          state.stick["custom,omitempty"] = stickInfo.custom
+        } else if (stickInfo.delimit) {
+          state.stick["delimit,omitempty"] = stickInfo.delimit
+        }
+        if (types == 'tcp') {
+          if (state.stick['delimit,omitempty']) {
+            state.stickValue = '分隔符'
+          } else if (state.stick['custom,omitempty']) {
+            state.stickValue = '自定义脚本'
+          } else if (state.stick['fixedLen,omitempty']) {
+            state.stickValue = '固定长度'
+          } else if (state.stick['len,omitempty']) {
+            state.stickValue = '长度字段'
+          }
         }
-        const initData = () => {
-            state.stick = {
-                "delimit,omitempty": "",
-                "custom,omitempty": "",
-                "fixedLen,omitempty": 0,
-                "len,omitempty": {
-                    "len": 0,
-                    "offset": 0,
-                    "endian": ""
-                }
-            }
+
+        let jsonData = JSON.stringify(JSON.parse(protocol).options);
+        state.resourceModalPro.content = JSON.stringify(JSON.parse(jsonData), null, 4);
+        mirrorRef.value.setValue(state.resourceModalPro.content);
+      })
+    };
+    const submit = () => {
+      let params = {
+        ...state.form,
+        "stick": {
+          ...state.stick
         }
-        const handleChangeType = () => {
-            if (state.form.types != 'mqtt_server' && state.form.isTls == 1) {
-                state.form.authType = 3
-                return;
-            }
-            if (state.form.isTls == 0) {
-                state.form.authType = ""
-            }
+      }
+      if (mirrorRef.value.getValue()) {
+        state.form.protocol.options = eval("(" + mirrorRef.value.getValue() + ")")
+      }
+      // return
+      api.server.editItem(params).then((res: any) => {
+        ElMessage.success('修改成功')
+        goBack()
+      });
+    };
+    const goBack = () => {
+      router.go(-1);
+    }
+    const initData = () => {
+      state.stick = {
+        "delimit,omitempty": "",
+        "custom,omitempty": "",
+        "fixedLen,omitempty": 0,
+        "len,omitempty": {
+          "len": 0,
+          "offset": 0,
+          "endian": ""
         }
-        const getCertificateList = () => {
-            api2.certificate.getList().then((res: any) => {
-                state.certificateList = res.Info;
-            })
+      }
+    }
+    const handleChangeType = () => {
+      if (state.form.types != 'mqtt_server' && state.form.isTls == 1) {
+        state.form.authType = 3
+        return;
+      }
+      if (state.form.isTls == 0) {
+        state.form.authType = ""
+      }
+    }
+    const getCertificateList = () => {
+      api2.certificate.getList().then((res: any) => {
+        state.certificateList = res.Info;
+      })
+    }
+    watch(
+      () => state.form.isTls,
+      (value) => {
+        if (value == 0) {
+          state.form.authType = ""
         }
-        watch(
-            () => state.form.isTls,
-            (value) => {
-                if (value == 0) {
-                    state.form.authType = ""
-                }
-            }
-        );
-        onMounted(() => {
-            getDetail();
-            getCertificateList();
-        });
+      }
+    );
+    onMounted(() => {
+      getDetail();
+      getCertificateList();
+    });
 
-        return {
-            mirrorRef,
-            activeName,
-            getDetail,
-            network_server_type,
-            network_protocols,
-            submit,
-            initData,
-            handleChangeType,
-            getCertificateList,
-            ...toRefs(props),
-            ...toRefs(state),
-        };
-    },
+    return {
+      mirrorRef,
+      activeName,
+      getDetail,
+      network_server_type,
+      network_protocols,
+      submit,
+      initData,
+      handleChangeType,
+      getCertificateList,
+      ...toRefs(props),
+      ...toRefs(state),
+    };
+  },
 });
 </script>
 <style lang="scss" scoped>
 ::v-deep .el-collapse-item__header {
-    position: relative;
-    padding-left: 20px;
+  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;
 
-    }
+  }
 }
 
 ::v-deep .el-input,
 ::v-deep .el-input-number {
-    width: 500px;
+  width: 500px;
 }
 
 ::v-deep .params {
-    width: 600px;
+  width: 600px;
 }
 
 ::v-deep .flex-column {
-    display: flex;
-    flex-direction: column;
+  display: flex;
+  flex-direction: column;
 
-    .el-form-item__label {
-        justify-content: flex-start;
-    }
+  .el-form-item__label {
+    justify-content: flex-start;
+  }
 }
 </style>
 

+ 1 - 1
src/views/iot/network/tunnel/component/list.vue

@@ -38,7 +38,7 @@
 </template>
 
 <script lang="ts">
-import { ref, toRefs, reactive, onMounted, getCurrentInstance, unref, watch, defineComponent } from 'vue';
+import { toRefs, reactive, onMounted, getCurrentInstance, unref, defineComponent } from 'vue';
 import { ElMessageBox, ElMessage } from 'element-plus';
 import { useRouter } from 'vue-router';
 

+ 71 - 78
src/views/iot/network/tunnel/component/serverDetail.vue

@@ -8,92 +8,85 @@
 -->
 <!-- 服务器详情页 -->
 <template>
-	<div class="server-detail-wrap">
-        <div class="server-detail-item-wrap">
-            <div class="label">名称</div>
-            <div class="value">{{detail.name}}</div>
-        </div>
-        <div class="server-detail-item-wrap">
-            <div class="label">类型</div>
-            <div class="value">{{detail.types}}</div>
-        </div>
-        <div class="server-detail-item-wrap">
-            <div class="label">地址</div>
-            <div class="value">{{detail.addr}}</div>
-        </div>
-        <div class="server-detail-item-wrap">
-            <div class="label">状态</div>
-            <div class="value">{{detail.status?'启动':'未启动'}}</div>
-        </div>
-         <div class="server-detail-item-wrap">
-            <div class="label">禁用</div>
-            <div class="value">
-                <el-switch :loading="loading" :before-change="onChangeStatus" :disabled="!detail.status" :active-value="0"  :inactive-value="1" size="small" v-model="detail.status" />
-            </div>
-        </div>
-        <div class="server-detail-item-wrap">
-            <div class="label">创建时间</div>
-            <div class="value">{{detail.createdAt}}</div>
-        </div>
-    </div>
+  <el-descriptions :column="2" border>
+    <el-descriptions-item>
+        <template #label>
+            <div class="cell-item">名称</div>
+        </template>
+        {{detail.name}}
+    </el-descriptions-item>
+    <el-descriptions-item>
+        <template #label>
+            <div class="cell-item">类型</div>
+        </template>
+        {{detail.types}}
+    </el-descriptions-item>
+    <el-descriptions-item>
+        <template #label>
+            <div class="cell-item">地址</div>
+        </template>
+        {{detail.addr}}
+    </el-descriptions-item>
+    <el-descriptions-item>
+        <template #label>
+            <div class="cell-item">状态</div>
+        </template>
+        {{ detail.status ? '启动' : '未启动' }}
+    </el-descriptions-item>
+    <el-descriptions-item>
+        <template #label>
+            <div class="cell-item">禁用</div>
+        </template>
+        <el-switch :loading="loading" :before-change="onChangeStatus" :disabled="!detail.status" :active-value="0" :inactive-value="1" size="small" v-model="detail.status" />
+    </el-descriptions-item>
+    <el-descriptions-item>
+        <template #label>
+            <div class="cell-item">创建时间</div>
+        </template>
+        {{ detail.createdAt }}
+    </el-descriptions-item>
+  </el-descriptions>
 </template>
 <script lang="ts">
-import { toRefs, reactive, onMounted, ref, defineComponent } from 'vue';
+import { toRefs, reactive, onMounted, defineComponent } from 'vue';
 import { ElMessage } from 'element-plus';
 
 import api from '/@/api/network';
 
 interface TableDataState {
-    loading: boolean
+  loading: boolean
 }
 export default defineComponent({
-	name: 'serverDetail',
-	props: {
-		detail: {
-			type: Object,
-			default: ''
-		}
-	},
-	setup(props, context) {
-		const state = reactive<TableDataState>({
-            loading: false
-		});
-		onMounted(() => {
-		});
-        // 禁用状态
-        const onChangeStatus = () => {
-            state.loading = true
-            return new Promise((resolve) => {
-                api.tunnel.changeTunnelStatus({id: props.detail.id, status: 0}).then((res:any) => {
-                state.loading = false
-		        ElMessage.success('已关闭');
-                props.detail.status = 0
-            })
-            })
-            
-        };
-		return {
-            onChangeStatus,
-			...toRefs(props),
-			...toRefs(state),
-		};
-	}
-});
-</script>
-
-<style lang="scss" scoped>
-.server-detail-wrap {
-    .server-detail-item-wrap {
-        display: flex;
-        justify-content: space-between;
-        padding: 10px;
-        border-left: 1px solid var(--el-border-color-light);
-        border-top: 1px solid var(--el-border-color-light);
-        border-right: 1px solid var(--el-border-color-light);
-    }
-    .server-detail-item-wrap:last-child {
-        border-bottom: 1px solid var(--el-border-color-light);
+  name: 'serverDetail',
+  props: {
+    detail: {
+      type: Object,
+      default: ''
     }
-}
-</style>
+  },
+  setup(props) {
+    const state = reactive<TableDataState>({
+      loading: false
+    });
+    onMounted(() => {
+    });
+    // 禁用状态
+    const onChangeStatus = () => {
+      state.loading = true
+      return new Promise(() => {
+        api.tunnel.changeTunnelStatus({ id: props.detail.id, status: 0 }).then(() => {
+          state.loading = false
+          ElMessage.success('已关闭');
+          props.detail.status = 0
+        })
+      })
 
+    };
+    return {
+      onChangeStatus,
+      ...toRefs(props),
+      ...toRefs(state),
+    };
+  }
+});
+</script>

+ 14 - 7
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="协议参数">
@@ -101,16 +102,17 @@
   </div>
 </template>
 <script lang="ts">
-import { toRefs, reactive, onMounted, ref, defineComponent, getCurrentInstance, nextTick } from 'vue';
+import { toRefs, reactive, onMounted, ref, defineComponent, getCurrentInstance } from 'vue';
 import { ElMessage } from 'element-plus';
 import codeEditor from '/@/components/codeEditor/index.vue'
-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,
@@ -121,7 +123,7 @@ interface TableDataState {
 }
 export default defineComponent({
   name: 'tunnelCreate',
-  components: { codeEditor, serverDetail },
+  components: { codeEditor },
   props: {
     type: {
       type: String,
@@ -129,7 +131,7 @@ export default defineComponent({
     }
   },
 
-  setup(props, context) {
+  setup(props) {
     const { proxy } = getCurrentInstance() as any;
     const route = useRoute();
     const router = useRouter();
@@ -142,6 +144,7 @@ export default defineComponent({
         content: ''
       },
       detail: {},
+      messageData: [],
       activeViewName: ['1', '2', '3', '4', '5'],
       form: {
         // 名称
@@ -167,7 +170,7 @@ export default defineComponent({
         },
         // 协议适配
         protoccol: {
-          name: "ModbusTCP",
+          name: "SagooMqtt",
           options: {}
         },
         // 心跳包
@@ -185,6 +188,10 @@ export default defineComponent({
         ]
       }
     });
+    
+    deviceApi.product.getTypesAll({ types: 'protocol' }).then((res: any) => {
+      state.messageData = res || [];
+    });
 
     const mirrorRef = ref('mirrorRef')
     const activeName = ref('first')

+ 9 - 13
src/views/iot/network/tunnel/detail.vue

@@ -1,23 +1,19 @@
 <template>
-	<el-card class="system-dic-container" style="position: relative;">
-		<el-tabs v-model="activeName" class="demo-tabs">
+	<div class="page bg border padding Ipt-2" style="position: relative;">
+		<el-tabs v-model="activeName">
 			<el-tab-pane label="通道详情" name="first">
 				<serverDetail :detail="detail" />
 			</el-tab-pane>
-			<!-- <el-tab-pane label="相关详情" name="second">相关详情</el-tab-pane>
-			<el-tab-pane label="通道" name="third">通道</el-tab-pane> -->
 		</el-tabs>
-		<div style="position: absolute;right:20px;top: 34px;">
+		<div style="position: absolute;right:20px;top: 18px;">
 			<el-icon @click="freshData" style="cursor: pointer;font-size: 16px;margin-right:6px;"><ele-RefreshRight /></el-icon>
-			<!-- <el-icon style="font-size: 16px;margin: 0 6px;"><ele-Operation /></el-icon> -->
 			<el-icon @click="toEdit" style="cursor: pointer;font-size: 16px;"><ele-Edit /></el-icon>
 		</div>
-	</el-card>
+	</div>
 </template>
 <script lang="ts">
 import { toRefs, reactive, onMounted, ref, defineComponent } from 'vue';
-import { ElMessageBox, ElMessage, FormInstance } from 'element-plus';
-import type { TabsPaneContext } from 'element-plus'
+import { ElMessage } from 'element-plus';
 
 import serverDetail from './component/serverDetail.vue'
 
@@ -50,12 +46,12 @@ export default defineComponent({
 				mode: '',
 				content: ''
 			},
-			detail:{}
+			detail: {}
 		});
 		const activeName = ref('first')
 		const getDetail = () => {
 			const id = route.params && route.params.id;
-			api.tunnel.getDetail({"id": id}).then((res: any) => {
+			api.tunnel.getDetail({ "id": id }).then((res: any) => {
 				state.detail = res
 			})
 		};
@@ -64,8 +60,8 @@ export default defineComponent({
 			ElMessage.success('刷新成功');
 		};
 		const toEdit = () => {
-            router.push(`/iotmanager/network/tunnel/edit/${route.params && route.params.id}`)
-        };
+			router.push(`/iotmanager/network/tunnel/edit/${route.params && route.params.id}`)
+		};
 		onMounted(() => {
 			getDetail()
 		});

+ 243 - 236
src/views/iot/network/tunnel/edit.vue

@@ -1,274 +1,281 @@
 <template>
-    <el-card class="system-dic-container" style="position: relative;">
-        <el-tabs v-model="activeName" class="demo-tabs">
-            <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';
 import { ElMessageBox, ElMessage, FormInstance } from 'element-plus';
 import codeEditor from '/@/components/codeEditor/index.vue'
 
-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 },
+  props: {
+    type: {
+      type: String,
+      default: ''
+    }
+  },
+
+  setup(props) {
+    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>
 ::v-deep .el-collapse-item__header {
-    position: relative;
-    padding-left: 20px;
+  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;
 
-    }
+  }
 }
 
 ::v-deep .el-input,
 ::v-deep .el-input-number {
-    width: 500px;
+  width: 500px;
 }
 
 ::v-deep .params {
-    width: 600px;
+  width: 600px;
 }
 </style>
 

+ 78 - 120
src/views/iot/noticeservices/config/setting.vue

@@ -1,131 +1,90 @@
 <template>
-	<div class="system-dic-container">
-		<el-card shadow="nover">
-			<div class="system-user-search mb15">
-				<el-form :model="tableData.param" ref="queryRef" inline @keyup.enter.native="dataList">
-					<el-form-item label="配置名称" prop="keyWord">
-						<el-input v-model="tableData.param.keyWord" placeholder="请输入配置名称" clearable style="width: 240px" />
-					</el-form-item>
-					<!-- <el-form-item label="通知方式" prop="name">
-						<el-input
-							v-model="tableData.param.sendGateway"
-							placeholder="请输入通知方式"
-							clearable
-							size="default"
-							style="width: 240px"
-							@keyup.enter.native="dataList"
-						/>
-					</el-form-item> -->
-
-					<el-form-item>
-						<el-button type="primary" class="ml10" @click="dataList">
-							<el-icon>
-								<ele-Search />
-							</el-icon>
-							查询
-						</el-button>
-						<el-button @click="resetQuery(queryRef)">
-							<el-icon>
-								<ele-Refresh />
-							</el-icon>
-							重置
-						</el-button>
-						<el-button type="primary" class="ml10" @click="onOpenAdd">
-							<el-icon>
-								<ele-FolderAdd />
-							</el-icon>
-							新增通知
-						</el-button>
-					</el-form-item>
-				</el-form>
-			</div>
-			<div>
-				<div style="border: 1px solid var(--next-border-color-light)"></div>
-				<el-row>
-					<el-col :span="8" v-for="(item, index) in tableData.data" :key="index">
-						<div class="grid-content card">
-							<div class="ant-card">
-								<div class="ant-card-body">
-									<div class="pro-table-card-item">
-										<div class="card-item-avatar">
-											<img width="88" height="88" :src="'/imgs/notice/' + tableData.param.sendGateway + '.svg'" alt="" />
-										</div>
-										<div class="card-item-body">
-											<div class="card-item-header">
-												<div class="ellipsis">
-													<div class="ellipsis card-item-header-name" style="width: 100%; height: 45px">{{ item.title }}</div>
-													<div class="card-item-header-name" style="display: none"></div>
-												</div>
-											</div>
-											<div class="card-item-content">
-												通知方式:<el-tag>{{ item.types == 1 ? '即时发送' : '预约发送' }}</el-tag>
-												<!-- <div>
-													<label>说明</label>
-													<div class="ellipsis">
-														<div style="width: 100%"></div>
-													</div>
-												</div> -->
-											</div>
+	<el-card shadow="nover" class="page">
+		<div class="system-user-search">
+			<el-form :model="tableData.param" ref="queryRef" inline @keyup.enter.native="dataList">
+				<el-form-item label="配置名称" prop="keyWord">
+					<el-input v-model="tableData.param.keyWord" placeholder="请输入配置名称" clearable style="width: 240px" />
+				</el-form-item>
+				<el-form-item>
+					<el-button type="primary" class="ml10" @click="dataList">
+						<el-icon>
+							<ele-Search />
+						</el-icon>
+						查询
+					</el-button>
+					<el-button @click="resetQuery(queryRef)">
+						<el-icon>
+							<ele-Refresh />
+						</el-icon>
+						重置
+					</el-button>
+					<el-button type="primary" class="ml10" @click="onOpenAdd">
+						<el-icon>
+							<ele-FolderAdd />
+						</el-icon>
+						新增通知
+					</el-button>
+				</el-form-item>
+			</el-form>
+		</div>
+		<el-row class="flex1">
+			<el-col :span="8" v-for="(item, index) in tableData.data" :key="index">
+				<div class="grid-content card">
+					<div class="ant-card">
+						<div class="ant-card-body">
+							<div class="pro-table-card-item">
+								<div class="card-item-avatar">
+									<img width="88" height="88" :src="'/imgs/notice/' + tableData.param.sendGateway + '.svg'" alt="" />
+								</div>
+								<div class="card-item-body">
+									<div class="card-item-header">
+										<div class="ellipsis">
+											<div class="ellipsis card-item-header-name" style="width: 100%; height: 45px">{{ item.title }}</div>
+											<div class="card-item-header-name" style="display: none"></div>
 										</div>
 									</div>
-								</div>
-							</div>
-							<div class="card-tools">
-								<div class="card-button" @click="onOpenEdit(item)">
-									<el-button type="primary" class="ml10" text bg>
-										<el-icon>
-											<ele-Edit />
-										</el-icon>
-										修改
-									</el-button>
-								</div>
-								<div class="card-button" @click="onOpenEditTem(item)">
-									<el-button type="primary" text bg>
-										<el-icon>
-											<ele-Wallet />
-										</el-icon>
-										模板配置
-									</el-button>
-								</div>
-								<!--<div class="card-button" @click="onOpenEdit(item)">
-									<el-button type="primary" text bg>
-										<el-icon>
-											<ele-View />
-										</el-icon>
-										调试
-									</el-button>
-								</div>
-
-							 	<div class="card-button" @click="onOpenEdit(item)">
-									<el-button type="info" text bg>
-										<el-icon>
-											<ele-Document />
-										</el-icon>
-										通知记录
-									</el-button>
-								</div> -->
-
-								<div class="card-button" @click="onRowDel(item)">
-									<el-button type="danger" text bg>
-										<el-icon>
-											<ele-Delete />
-										</el-icon>
-										删除
-									</el-button>
+									<div class="card-item-content">
+										通知方式:<el-tag>{{ item.types == 1 ? '即时发送' : '预约发送' }}</el-tag>
+									</div>
 								</div>
 							</div>
 						</div>
-					</el-col>
-				</el-row>
-			</div>
-			<div style="text-align: center;padding: 28px;" v-if="(tableData.total == 0)">暂无数据</div>
-			<pagination v-show="tableData.total > 0" :total="tableData.total" v-model:page="tableData.param.pageNum" v-model:limit="tableData.param.pageSize" @pagination="dataList" />
-		</el-card>
+					</div>
+					<div class="card-tools">
+						<div class="card-button" @click="onOpenEdit(item)">
+							<el-button type="primary" class="ml10" text bg>
+								<el-icon>
+									<ele-Edit />
+								</el-icon>
+								修改
+							</el-button>
+						</div>
+						<div class="card-button" @click="onOpenEditTem(item)">
+							<el-button type="primary" text bg>
+								<el-icon>
+									<ele-Wallet />
+								</el-icon>
+								模板配置
+							</el-button>
+						</div>
 
+						<div class="card-button" @click="onRowDel(item)">
+							<el-button type="danger" text bg>
+								<el-icon>
+									<ele-Delete />
+								</el-icon>
+								删除
+							</el-button>
+						</div>
+					</div>
+				</div>
+			</el-col>
+		</el-row>
+		<div style="text-align: center;padding: 28px;" v-if="(tableData.total == 0)">暂无数据</div>
+		<pagination v-show="tableData.total > 0" :total="tableData.total" v-model:page="tableData.param.pageNum" v-model:limit="tableData.param.pageSize" @pagination="dataList" />
 		<EditDic ref="editDicRef" @dataList="dataList" />
 		<EditTemDic ref="temeditDicRef" @dataList="dataList" />
-		<!-- 	<LevelDic ref="levelDicRef" @dataList="dataList" /> -->
-	</div>
+	</el-card>
 </template>
 
 <script lang="ts">
@@ -133,7 +92,6 @@ import { toRefs, reactive, onMounted, ref, getCurrentInstance, defineComponent }
 import { ElMessageBox, ElMessage, FormInstance } from 'element-plus';
 import EditDic from './component/setEdit.vue';
 import EditTemDic from './component/temEdit.vue';
-//import LevelDic from './component/level.vue';
 
 import api from '/@/api/notice';
 import { useRoute } from 'vue-router';

+ 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
         }
       })
     })

+ 84 - 90
src/views/iot/ota-update/update/component/batch.vue

@@ -1,87 +1,81 @@
 <template>
-  <div class="ota-module-container">
-    <el-card shadow="nover">
-      <div class="ota-module-search mb15">
-        <el-form :model="tableData.param" ref="queryRef" inline label-width="90px" @keyup.enter.native="getList(1)">
-          <el-form-item label="批次名称:" prop="name">
-            <el-input v-model="tableData.param.keyWord" placeholder="请输入批次名称" clearable style="width: 240px" />
-          </el-form-item>
+  <div class="page page-full">
+    <el-form :model="tableData.param" ref="queryRef" inline label-width="90px" @keyup.enter.native="getList(1)">
+      <el-form-item label="批次名称:" prop="name">
+        <el-input v-model="tableData.param.keyWord" placeholder="请输入批次名称" clearable style="width: 240px" />
+      </el-form-item>
+      <el-form-item>
+        <el-button type="primary" class="ml10" @click="getList(1)">
+          <el-icon>
+            <ele-Search />
+          </el-icon>
+          查询
+        </el-button>
+        <el-button @click="resetQuery()">
+          <el-icon>
+            <ele-Refresh />
+          </el-icon>
+          重置
+        </el-button>
+        <el-button type="primary" v-auth="'add'" @click="onOpenAdd()">
+          <el-icon>
+            <ele-FolderAdd />
+          </el-icon>
+          添加批次
+        </el-button>
+      </el-form-item>
+    </el-form>
+    <el-table :data="tableData.data" style="width: 100%" v-loading="tableData.loading">
+      <el-table-column prop="id" label="ID" width="100" />
+      <el-table-column prop="name" label="名称" />
+      <!--        <el-table-column prop="waitVersion" label="待升级版本号" width="120" />-->
+      <el-table-column label="类型" prop="types" width="120" align="center">
+        <template #default="scope">
+          <el-tag type="success" size="small" v-if="scope.row.types == 0">验证</el-tag>
+          <el-tag type="primary" size="small" v-else>升级</el-tag>
+        </template>
+      </el-table-column>
+      <el-table-column label="状态" prop="active" width="120" align="center">
+        <template #default="scope">
+          <el-tag type="success" size="small" v-if="scope.row.active == 1">是</el-tag>
+          <el-tag type="info" size="small" v-else>否</el-tag>
+        </template>
+      </el-table-column>
+      <el-table-column prop="method" label="协议方式" show-overflow-tooltip>
+        <template #default="scope">
+          <el-tag size="small" v-if="scope.row.method == 1">http</el-tag>
+          <el-tag size="small" v-if="scope.row.method == 2">https</el-tag>
+          <el-tag size="small" v-if="scope.row.method == 3">mqtt</el-tag>
+        </template>
+      </el-table-column>
+      <el-table-column prop="stratege" label="升级方式" show-overflow-tooltip>
+        <template #default="scope">
+          <el-tag size="small" v-if="scope.row.stratege == 1">静态升级 </el-tag>
+          <el-tag size="small" v-if="scope.row.stratege == 2">动态升级</el-tag>
+        </template>
+      </el-table-column>
 
-          <el-form-item>
-            <el-button type="primary" class="ml10" @click="getList(1)">
-              <el-icon>
-                <ele-Search />
-              </el-icon>
-              查询
-            </el-button>
-            <el-button @click="resetQuery()">
-              <el-icon>
-                <ele-Refresh />
-              </el-icon>
-              重置
-            </el-button>
-            <el-button type="primary" v-auth="'add'" @click="onOpenAdd()">
-              <el-icon>
-                <ele-FolderAdd />
-              </el-icon>
-              添加批次
-            </el-button>
-          </el-form-item>
-
-        </el-form>
-      </div>
-      <el-table :data="tableData.data" style="width: 100%" v-loading="tableData.loading">
-        <el-table-column prop="id" label="ID" width="100" />
-        <el-table-column prop="name" label="名称" />
-<!--        <el-table-column prop="waitVersion" label="待升级版本号" width="120" />-->
-        <el-table-column label="类型" prop="types" width="120" align="center">
-          <template #default="scope">
-            <el-tag type="success" size="small" v-if="scope.row.types == 0">验证</el-tag>
-            <el-tag type="info" size="small" v-else>升级</el-tag>
-          </template>
-        </el-table-column>
-        <el-table-column label="状态" prop="active" width="120" align="center">
-          <template #default="scope">
-            <el-tag type="success" size="small" v-if="scope.row.active == 1">是</el-tag>
-            <el-tag type="info" size="small" v-else>否</el-tag>
-          </template>
-        </el-table-column>
-        <el-table-column prop="method" label="协议方式" show-overflow-tooltip>
-          <template #default="scope">
-            <el-tag size="small" v-if="scope.row.method == 1">http</el-tag>
-            <el-tag size="small" v-if="scope.row.method == 2">https</el-tag>
-            <el-tag size="small" v-if="scope.row.method == 3">mqtt</el-tag>
-          </template>
-        </el-table-column>
-        <el-table-column prop="stratege" label="升级方式" show-overflow-tooltip>
-          <template #default="scope">
-            <el-tag size="small" v-if="scope.row.stratege == 1">静态升级 </el-tag>
-            <el-tag size="small" v-if="scope.row.stratege == 2">动态升级</el-tag>
-          </template>
-        </el-table-column>
-
-        <el-table-column prop="push" label="主动推送" show-overflow-tooltip>
-          <template #default="scope">
-            <el-tag size="small" v-if="scope.row.push == 1">是 </el-tag>
-            <el-tag size="small" v-if="scope.row.push == 2">否</el-tag>
-          </template>
-        </el-table-column>
-        <el-table-column prop="createdAt" label="创建时间" min-width="100" align="center" />
-        <el-table-column label="操作" width="200" align="center">
-          <template #default="scope">
-<!--            <router-link :to="'/iotmanager/operation/ota/update/device/' + scope.row.id" class="link-type" style="padding-right: 12px;font-size: 12px;color: #409eff;">-->
-<!--              <span>查看</span>-->
-<!--            </router-link>-->
-            <el-button size="small" text type="primary" @click="getDeviceList(scope.row)">查看</el-button>
-<!--            <el-button size="small" text type="warning" v-auth="'edit'" @click="CheckUpdate(scope.row)">编辑</el-button>-->
-<!--            <el-button size="small" text type="danger" v-auth="'del'" @click="del(scope.row)">删除</el-button>-->
-          </template>
-        </el-table-column>
-      </el-table>
-      <pagination v-show="tableData.total > 0" :total="tableData.total" v-model:page="tableData.param.pageNum" v-model:limit="tableData.param.pageSize" @pagination="getList" />
-      <CheckConfig ref="checkRef" @getList="getList(1)" />
-      <DeviceList ref="deviceRef" />
-    </el-card>
+      <el-table-column prop="push" label="主动推送" show-overflow-tooltip>
+        <template #default="scope">
+          <el-tag size="small" v-if="scope.row.push == 1">是 </el-tag>
+          <el-tag size="small" v-if="scope.row.push == 2">否</el-tag>
+        </template>
+      </el-table-column>
+      <el-table-column prop="createdAt" label="创建时间" min-width="100" align="center" />
+      <el-table-column label="操作" width="200" align="center">
+        <template #default="scope">
+          <!--            <router-link :to="'/iotmanager/operation/ota/update/device/' + scope.row.id" class="link-type" style="padding-right: 12px;font-size: 12px;color: #409eff;">-->
+          <!--              <span>查看</span>-->
+          <!--            </router-link>-->
+          <el-button size="small" text type="primary" @click="getDeviceList(scope.row)">查看</el-button>
+          <!--            <el-button size="small" text type="warning" v-auth="'edit'" @click="CheckUpdate(scope.row)">编辑</el-button>-->
+          <!--            <el-button size="small" text type="danger" v-auth="'del'" @click="del(scope.row)">删除</el-button>-->
+        </template>
+      </el-table-column>
+    </el-table>
+    <pagination v-show="tableData.total > 0" :total="tableData.total" v-model:page="tableData.param.pageNum" v-model:limit="tableData.param.pageSize" @pagination="getList" />
+    <CheckConfig ref="checkRef" @getList="getList(1)" />
+    <DeviceList ref="deviceRef" />
   </div>
 </template>
 
@@ -156,7 +150,7 @@ export default defineComponent({
       let active = 0;
       if (row.active === 1) active = 0;
       if (row.active === 0) active = 1;
-      api.batch.stop({id: row.id, active: active}).then((res: any) => {
+      api.batch.stop({ id: row.id, active: active }).then((res: any) => {
         ElMessage.success('操作成功');
         batchList();
       });
@@ -170,12 +164,12 @@ export default defineComponent({
       state.tableData.loading = true;
       state.tableData.param.devOtaFirmwareId = props.detail.id;
       api.batch
-          .getList(state.tableData.param)
-          .then((res: any) => {
-            state.tableData.data = res.Data;
-            state.tableData.total = res.Total;
-          })
-          .finally(() => (state.tableData.loading = false));
+        .getList(state.tableData.param)
+        .then((res: any) => {
+          state.tableData.data = res.Data;
+          state.tableData.total = res.Total;
+        })
+        .finally(() => (state.tableData.loading = false));
     };
     // 打开新增弹窗
     const onOpenAdd = () => {
@@ -206,7 +200,7 @@ export default defineComponent({
           getList();
         });
       })
-          .catch(() => { });
+        .catch(() => { });
     };
     /** 重置按钮操作 */
     const resetQuery = () => {

+ 6 - 10
src/views/iot/ota-update/update/component/check.vue

@@ -86,7 +86,7 @@ interface RuleFormState {
   name: string;
   waitVersion: string;
   method: string;
-  devices: Array;
+  devices: [];
   stratege: string;
   devOtaFirmwareId: number;
   push: string;
@@ -127,7 +127,7 @@ export default defineComponent({
         devOtaFirmwareId: 0,
         push: '2',
         pushDisabled: true,
-        types: '1',
+        types: "1",
         productId: '',
       },
       productData: [],
@@ -137,6 +137,7 @@ export default defineComponent({
         stratege: [{ required: true, message: '升级方式不能为空', trigger: 'blur' }],
         push: [{ required: true, message: '推送方式不能为空', trigger: 'blur' }],
         types: [{ required: true, message: '类型不能为空', trigger: 'blur' }],
+        devices: [{ required: true, message: '所属设备不能为空', trigger: 'blue'}],
       },
       deviceShow: false, // 所属设备是否显示
       deviceNameShow: false, // 回显设备名称状态
@@ -150,13 +151,8 @@ export default defineComponent({
     // 获取操作升级包类型
     const getFormType = () => {
       // 如果是验证类型,设备信息必填项
-      if (state.ruleForm.types === '1') {
+      if (state.ruleForm.types === "1") {
         state.deviceShow = true;
-        state.rules.devices = [{
-          required: true,
-          message: '所属设备不能为空',
-          trigger: 'blur'
-        }];
       } else { // 如果是升级类型,设备可选可不选
         delete (state.rules.devices);
         state.deviceShow = false;
@@ -194,7 +190,7 @@ export default defineComponent({
       }
       state.isShowDialog = true;
 
-      if (state.ruleForm.types == '1') {
+      if (state.ruleForm.types == "1") {
         state.deviceShow = true;
       }
     };
@@ -209,7 +205,7 @@ export default defineComponent({
         devOtaFirmwareId: 0,
         push: '2',
         pushDisabled: true,
-        types: '1',
+        types: "1",
         productId: '',
       };
     };

+ 30 - 5
src/views/iot/ota-update/update/component/deviceList.vue

@@ -1,14 +1,12 @@
 <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">
             <el-input v-model="tableData.param.deviceName" placeholder="请输入设备名称" clearable style="width: 240px" @submit.prevent />
           </el-form-item>
-
           <el-form-item>
-
             <el-button type="primary" class="ml10" @click="getDetail">
               <el-icon>
                 <ele-Search />
@@ -16,7 +14,6 @@
               查询
             </el-button>
           </el-form-item>
-
         </el-form>
       </div>
       <el-table :data="tableData.data" style="width: 100%" row-key="id" v-loading="tableData.loading">
@@ -47,6 +44,11 @@
           </template>
         </el-table-column>
         <el-table-column prop="createdAt" label="时间" min-width="100" align="center"></el-table-column>
+        <el-table-column label="操作" width="200" align="center">
+          <template #default="scope">
+            <el-button size="small" text type="primary" @click="distribute(scope.row)">手动下发</el-button>
+          </template>
+        </el-table-column>
       </el-table>
       <pagination v-show="tableData.total > 0" :total="tableData.total" v-model:page="tableData.param.pageNum" v-model:limit="tableData.param.pageSize" @pagination="getDetail" />
     </el-dialog>
@@ -56,6 +58,7 @@
 <script lang="ts">
 import api from '/@/api/ota';
 import {defineComponent, reactive, toRefs} from 'vue';
+import {ElMessage} from "element-plus";
 
 interface TableDataRow {
   id: number;
@@ -81,10 +84,11 @@ interface TableDataState {
     };
   };
   isShowDialog: boolean;
+  timeoutTimer: any;
 }
 
 export default defineComponent({
-  setup(prop) {
+  setup() {
     const state = reactive<TableDataState>({
       ids: [],
       tableData: {
@@ -99,6 +103,7 @@ export default defineComponent({
         },
       },
       isShowDialog: false,
+      timeoutTimer: null,
     });
     // 打开弹窗
     const openDialog = (row: any) => {
@@ -112,6 +117,7 @@ export default defineComponent({
     };
     // 关闭弹窗
     const closeDialog = () => {
+      clearTimeout(state.timeoutTimer);
       state.isShowDialog = false;
     };
     // 取消
@@ -125,11 +131,30 @@ export default defineComponent({
         state.tableData.total = res.Total;
       }).finally(() => (state.tableData.loading = false));
     };
+    // 手动下发
+    const distribute = (row: any) => {
+      const deviceKey = row.deviceKey;
+      const strategyId = row.strategyId;
+      api.batch.distribute({deviceKey: deviceKey, strategyId: strategyId}).then(() => {
+        ElMessage.success('操作成功');
+      });
+      // 定时请求列表数据
+      timer();
+    }
+    // 定时请求列表
+    const timer = () => {
+      console.log(11);
+      // 因列表更新数据不是实时更新,需设置定时后在请求列表
+      state.timeoutTimer = setTimeout(() => {
+        getDetail();
+      }, 3000);
+    }
     return {
       getDetail,
       openDialog,
       closeDialog,
       onCancel,
+      distribute,
       ...toRefs(state),
     };
   },

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

@@ -119,6 +119,7 @@ interface RuleFormState {
   url: string;
   ossurl: string,
   urlName: string;
+  size: string;
 }
 
 interface UpdateState {
@@ -173,6 +174,7 @@ export default defineComponent({
         url: '',
         ossurl: '',
         urlName: '',
+        size: '',
       },
       productData: [],
       moduleData: [],
@@ -250,6 +252,7 @@ export default defineComponent({
         url: '',
         ossurl: '',
         urlName: '',
+        size: '',
       };
     };
     const updateImg = (res: any) => {
@@ -257,6 +260,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 {

+ 48 - 43
src/views/iot/ota-update/update/component/info.vue

@@ -1,75 +1,77 @@
 <template>
-	<el-card class="system-dic-container" style="position: relative">
-		<div class="content">
-		
-			<div class="container">
-				<div class="item">升级包 ID:{{detail.id}}</div>
-				<div class="item">升级包名称:{{detail.name}}</div>
-				<div class="item">所属产品:{{detail.productName}}</div>
-			</div>
-			<div class="container">
-				<div class="item">升级包签名:d52b637c5eaf2bc9c24008bc4b723600</div>
-				<div class="item">升级包版本号:{{detail.version}}</div>
-				<div class="item">创建时间:{{detail.createdAt}}</div>
-			</div>
-			<div class="container">
-				<div class="item">签名算法:{{detail.are}}</div>
-				<div class="item">升级包状态:未验证</div>
-				<div class="item">验证进度:0%</div>
-			</div>
-			<div class="container">
-				<div class="item">升级包描述:{{detail.describe}}</div>
-				<div class="item">推送给设备的自定义信息:{{detail.info}}</div>
-				<div class="item"></div>
-			</div>
-			
+	<div class="content">
+		<div class="container">
+			<div class="item">升级包 ID:{{ detail.id }}</div>
+			<div class="item">升级包名称:{{ detail.name }}</div>
+			<div class="item">所属产品:{{ detail.productName }}</div>
 		</div>
-	</el-card>
+		<div class="container">
+			<div class="item">升级包签名:d52b637c5eaf2bc9c24008bc4b723600</div>
+			<div class="item">升级包版本号:{{ detail.version }}</div>
+			<div class="item">创建时间:{{ detail.createdAt }}</div>
+		</div>
+		<div class="container">
+			<div class="item">签名算法:{{ detail.are }}</div>
+			<div class="item">升级包状态:未验证</div>
+			<div class="item">验证进度:0%</div>
+		</div>
+		<div class="container">
+			<div class="item">升级包描述:{{ detail.describe }}</div>
+			<div class="item">推送给设备的自定义信息:{{ detail.info }}</div>
+			<div class="item"></div>
+		</div>
+	</div>
 </template>
 <script lang="ts" setup>
 
 const props = defineProps({
-
-detail: {
-  type: Object,
-  default: () => {}
-},
-
+	detail: {
+		type: Object,
+		default: () => { }
+	},
 })
 
 </script>
 
 <style scoped lang="scss">
-.status_list{
+.status_list {
 	width: 100%;
-    display: flex;
-    justify-content: space-between;
-    align-items: center;
+	display: flex;
+	justify-content: space-between;
+	align-items: center;
 	padding: 10px;
-	.otaflex{
+
+	.otaflex {
 		font-size: 14px;
 		display: flex;
 		align-items: center;
 		margin-left: -6px;
-		.otaflex_div1{
+
+		.otaflex_div1 {
 			padding: 0 24px;
 			min-width: 200px;
 			width: fit-content;
+
 			.otaflex_div2 {
 				align-items: center;
-				.title{
+
+				.title {
 					color: #666;
 					font-size: 14px;
 				}
+
 				span {
 					display: block;
 					border-radius: 50%;
+
 					.on {
 						background: #52c41a;
 					}
+
 					.off {
 						background: #c41a1a;
 					}
+
 					.ofn {
 						background: rgb(255, 138, 0);
 					}
@@ -78,7 +80,7 @@ detail: {
 						position: relative;
 						top: -1px;
 						display: inline-block;
-						width:10px;
+						width: 10px;
 						height: 10px;
 						vertical-align: middle;
 						border-radius: 50%;
@@ -86,18 +88,20 @@ detail: {
 					}
 
 				}
-				.otaflex_div3{
+
+				.otaflex_div3 {
 					font-size: 24px;
 					margin-top: 10px;
 					color: #373d41;
 				}
 			}
-	
-	
+
+
 		}
-		
+
 	}
 }
+
 .container {
 	display: flex;
 	padding: 10px;
@@ -106,6 +110,7 @@ detail: {
 .item {
 	flex: 1;
 }
+
 .desc {
 	margin-top: 15px;
 }

+ 41 - 74
src/views/iot/ota-update/update/detail.vue

@@ -1,73 +1,31 @@
 <template>
-  <div>
-    <el-card class="system-dic-container" style="position: relative">
-      <div class="content">
-        <div class="flex cont_box">
-          <div class="font26">升级包名称:{{detail.name}}</div>
-          <div class="pro-status"><span :class="detail.checkres == 1 ? 'on' : 'off'"></span>{{ detail.checkres == 1 ? '已验证' : '未验证' }}</div>
-        </div>
-        <div class="mt20"></div>
-        <div class="container">
-          <div class="item">升级包类型:{{detail.types==1?'整包':'差分'}}</div>
-          <div class="item">升级包签名:d52b637c5eaf2bc9c24008bc4b723600</div>
-        </div>
-        <div class="container">
-          <div class="item">签名算法:{{detail.are}}</div>
-          <div class="item">模块名称:{{detail.moduleName}}</div>
-        </div>
-
-        <!-- <div class="mt20"></div>
-        <div class="status_list">
-          <div class="otaflex">
-            <div class="otaflex_div1">
-                <div class="otaflex_div2">
-                  <span class="title">目标设备总数</span>
-                  <div class="count otaflex_div3">0</div>
-                </div>
-              </div>
-              <div class="otaflex_div1">
-                <div class="otaflex_div2">
-                  <span class="title"><span class="on"></span>目标成功数</span>
-                  <div class="count otaflex_div3">0</div>
-                </div>
-
-              </div>
-              <div class="otaflex_div1">
-                <div class="otaflex_div2">
-                  <span class="title"><span class="off"></span>目标失败数</span>
-                  <div class="count otaflex_div3">0</div>
-                </div>
-              </div>
-              <div class="otaflex_div1">
-                <div class="otaflex_div2">
-                  <span class="title"><span class="ofn"></span>目标取消数</span>
-                  <div class="count otaflex_div3">0</div>
-                </div>
-
-              </div>
-          </div>
-        </div> -->
+  <div class="page page-full padding bg border" style="position: relative">
+    <div class="content">
+      <div class="flex cont_box">
+        <div class="font26">升级包名称:{{ detail.name }}</div>
+        <div class="pro-status"><span :class="detail.checkres == 1 ? 'on' : 'off'"></span>{{ detail.checkres == 1 ? '已验证' : '未验证' }}</div>
       </div>
-    </el-card>
-    <div class="mt10"></div>
-    <el-card class="system-dic-container" style="position: relative">
-      <el-tabs v-model="activeTab">
-        <el-tab-pane label="批次管理" name="tab1">
-          <BatchList v-if="detail.id" :detail="detail" />
-        </el-tab-pane>
-<!--        <el-tab-pane label="设备列表" name="tab2">-->
-<!--          <DeviceList v-if="detail.id" :detail="detail" />-->
-<!--        </el-tab-pane>-->
-        <el-tab-pane label="升级包信息" name="tab3">
-          <InfoList v-if="detail.id" :detail="detail" />
-        </el-tab-pane>
-      </el-tabs>
-    </el-card>
+      <div class="container mt-2">
+        <div class="item">升级包类型:{{ detail.types == 1 ? '整包' : '差分' }}</div>
+        <div class="item">升级包签名:d52b637c5eaf2bc9c24008bc4b723600</div>
+      </div>
+      <div class="container mb-2">
+        <div class="item">签名算法:{{ detail.are }}</div>
+        <div class="item">模块名称:{{ detail.moduleName }}</div>
+      </div>
+    </div>
+    <el-tabs v-model="activeTab">
+      <el-tab-pane label="批次管理" name="tab1">
+        <BatchList v-if="detail.id" :detail="detail" />
+      </el-tab-pane>
+      <el-tab-pane label="升级包信息" name="tab3">
+        <InfoList v-if="detail.id" :detail="detail" />
+      </el-tab-pane>
+    </el-tabs>
   </div>
 </template>
 <script lang="ts">
 import { toRefs, reactive, onMounted, ref, defineComponent } from 'vue'
-import type { TabsPaneContext } from 'element-plus'
 import { useRoute } from 'vue-router'
 import { EditPen, DocumentAdd } from '@element-plus/icons-vue'
 import EditForm from '/@/views/iot/ota-update/update/component/edit.vue';
@@ -79,11 +37,11 @@ import api from '/@/api/ota'
 const editFormRef = ref()
 
 export default defineComponent({
-  components: { EditPen, EditForm, DocumentAdd, InfoList, DeviceList, BatchList},
+  components: { EditPen, EditForm, DocumentAdd, InfoList, DeviceList, BatchList },
   setup(props) {
     const route = useRoute()
     const state = reactive({
-      activeTab:'tab1',
+      activeTab: 'tab1',
       developer_status: 2,
       detail: {
         id: '',
@@ -119,30 +77,36 @@ export default defineComponent({
 </script>
 
 <style scoped lang="scss">
-.status_list{
+.status_list {
   width: 100%;
   display: flex;
   justify-content: space-between;
   align-items: center;
   padding: 10px;
-  .otaflex{
+
+  .otaflex {
     font-size: 12px;
     display: flex;
     align-items: center;
     margin-left: -6px;
-    .otaflex_div1{
+
+    .otaflex_div1 {
       padding: 0 24px;
       min-width: 200px;
       width: fit-content;
+
       .otaflex_div2 {
         align-items: center;
-        .title{
+
+        .title {
           color: #666;
           font-size: 14px;
         }
+
         span {
           display: block;
           border-radius: 50%;
+
           .on {
             background: #52c41a;
           }
@@ -150,6 +114,7 @@ export default defineComponent({
           .off {
             background: #c41a1a;
           }
+
           .ofn {
             background: rgb(255, 138, 0);
           }
@@ -158,7 +123,7 @@ export default defineComponent({
             position: relative;
             top: -1px;
             display: inline-block;
-            width:10px;
+            width: 10px;
             height: 10px;
             vertical-align: middle;
             border-radius: 50%;
@@ -166,7 +131,8 @@ export default defineComponent({
           }
 
         }
-        .otaflex_div3{
+
+        .otaflex_div3 {
           font-size: 24px;
           margin-top: 10px;
           color: #373d41;
@@ -178,14 +144,16 @@ export default defineComponent({
 
   }
 }
+
 .container {
   display: flex;
-  padding: 10px;
+  padding: 2px 0;
 }
 
 .item {
   flex: 1;
 }
+
 .desc {
   margin-top: 15px;
 }
@@ -199,7 +167,6 @@ export default defineComponent({
 .cont_box .pro-status {
   line-height: 40px;
   margin-left: 30px;
-  margin-top: 5px;
 
   .on {
     background: #52c41a;

+ 78 - 82
src/views/iot/property/attribute/index.vue

@@ -1,80 +1,76 @@
 <template>
-	<div class="page">
-		<el-card shadow="nover">
-			<el-form inline ref="queryRef" @keyup.enter="getList(1)">
-				<el-form-item label="关键字:" prop="keyWord">
-					<el-input v-model="params.keyWord" placeholder="请输入关键字" clearable style="width: 240px" />
-				</el-form-item>
-				<el-form-item>
-					<el-button type="primary" class="ml10" @click="getList(1)">
-						<el-icon>
-							<ele-Search />
-						</el-icon>
-						查询
-					</el-button>
-				</el-form-item>
-				<el-form-item>
-					<el-button type="success" @click="addOrEdit()" v-auth="'add'" v-if="productIno">
-						<el-icon>
-							<ele-FolderAdd />
-						</el-icon>
-						新增属性
-					</el-button>
-					<el-button type="danger" @click="batchdel()" v-auth="'batchdel'">
-						<el-icon>
-							<ele-FolderAdd />
-						</el-icon>
-						批量删除
-					</el-button>
-				</el-form-item>
-			</el-form>
-			<el-row :gutter="16">
-				<el-col :span="6">
-					<el-tree :data="mergedData" :props="defaultProps" accordion default-expand-all @node-click="handleNodeClick" style="border: 1px solid #eee;padding: 10px;margin-right: 10px;" class="mt-4" :default-expand-all="true" :node-key="'id'" highlight-current>
-						<template #default="{ node, data }">
-              <div class="custom-tree-node">
-                  <span class="tree-label">
-                    <el-icon v-if="data.is_type == '2'">
-                      <Expand />
-                    </el-icon>
-                    {{ node.label }}
-                  </span>
-              </div>
-						</template>
-					</el-tree>
-				</el-col>
-				<el-col :span="18"><el-table :data="tableData" @selection-change="handleSelectionChange" style="width: 100%" row-key="id" v-loading="loading">
+	<div class="page page-full border bg padding">
+		<el-form inline ref="queryRef" @keyup.enter="getList(1)">
+			<el-form-item label="关键字:" prop="keyWord">
+				<el-input v-model="params.keyWord" placeholder="请输入关键字" clearable style="width: 240px" />
+			</el-form-item>
+			<el-form-item>
+				<el-button type="primary" class="ml10" @click="getList(1)">
+					<el-icon>
+						<ele-Search />
+					</el-icon>
+					查询
+				</el-button>
+			</el-form-item>
+			<el-form-item>
+				<el-button type="primary" @click="addOrEdit()" v-auth="'add'" v-if="productIno">
+					<el-icon>
+						<ele-FolderAdd />
+					</el-icon>
+					新增属性
+				</el-button>
+				<el-button type="info" @click="batchdel()" v-auth="'batchdel'">
+					<el-icon>
+						<ele-FolderAdd />
+					</el-icon>
+					删除
+				</el-button>
+			</el-form-item>
+		</el-form>
+		<div class="page page-full-part flex-row gap-4">
+			<el-card style="width: 250px;" shadow="nover">
+				<el-tree :data="mergedData" :props="defaultProps" accordion default-expand-all @node-click="handleNodeClick" :node-key="'id'" highlight-current>
+					<template #default="{ node, data }">
+						<div class="custom-tree-node">
+							<span class="tree-label">
+								<el-icon v-if="data.is_type != '2'">
+									<Folder />
+								</el-icon>
+                <SvgIcon name="iconfont icon-siweidaotu" v-if="data.is_type == '2'"></SvgIcon>
+								{{ node.label }}
+							</span>
+						</div>
+					</template>
+				</el-tree>
+			</el-card>
+			<el-card class="flex1" shadow="nover">
+				<div class="page page-full">
+					<el-table :data="tableData" @selection-change="handleSelectionChange" style="width: 100%" row-key="id" v-loading="loading">
 						<el-table-column type="selection" width="55" align="center" />
-
 						<el-table-column prop="id" v-col="'id'" label="ID" min-width="100" show-overflow-tooltip></el-table-column>
 						<el-table-column prop="name" v-col="'name'" label="字段名称" show-overflow-tooltip></el-table-column>
 						<el-table-column prop="title" v-col="'title'" label="字段标题" show-overflow-tooltip></el-table-column>
 						<el-table-column prop="types" v-col="'types'" label="字段类型" show-overflow-tooltip></el-table-column>
-
-
 						<el-table-column prop="createdAt" v-col="'createdAt'" label="创建时间" width="160" align="center"></el-table-column>
 						<el-table-column label="操作" width="200" align="center">
 							<template #default="scope">
-
 								<el-button size="small" text v-auth="'edit'" type="warning" @click="addOrEdit(scope.row)">编辑</el-button>
-
 								<el-button size="small" text v-auth="'del'" type="info" @click="del(scope.row)">删除</el-button>
 							</template>
 						</el-table-column>
 					</el-table>
 					<pagination v-if="params.total" :total="params.total" v-model:page="params.pageNum" v-model:limit="params.pageSize" @pagination="getList()" />
-				</el-col>
-			</el-row>
-
-			<EditForm ref="editFormRef" @getList="getList(1)"></EditForm>
-		</el-card>
+				</div>
+			</el-card>
+		</div>
+		<EditForm ref="editFormRef" @getList="getList(1)"></EditForm>
 	</div>
 </template>
 
 <script lang="ts" setup>
 import device from '/@/api/device'
 import { useSearch } from '/@/hooks/useCommon'
-import { Expand } from '@element-plus/icons-vue';
+import { Folder } from '@element-plus/icons-vue';
 
 import { ElMessageBox, ElMessage } from 'element-plus'
 import EditForm from './edit.vue'
@@ -124,16 +120,16 @@ const getCateList = () => {
 			getList()
 			mergedData.value = matchProductsToCategories(productData.value, cateData.value);
 
-      // 默认加载第一个设备对应属性
-      if (productData.value.length > 0) {
-        handleNodeClick(mergedData.value[0].children[0])
-      }
-    });
+			// 默认加载第一个设备对应属性
+			if (productData.value.length > 0) {
+				handleNodeClick(mergedData.value[0].children[0])
+			}
+		});
 	})
 }
 
 const handleNodeClick = (data: any) => {
-  if (data.is_type === '2') {
+	if (data.is_type === '2') {
 		productIno.value = data;
 		params.productKey = data.key
 		getList()
@@ -206,26 +202,26 @@ const del = (row: any) => {
 </script>
 <style scoped lang="scss">
 .custom-tree-node {
-  width: 100%;
-  display: flex;
-  align-items: center;
-  justify-content: space-between;
-  font-size: 14px;
-  padding-right: 8px;
-  overflow: hidden;
-
-  .tree-label {
-    width: 100%;
-    overflow: hidden;
-    text-overflow: ellipsis;
-    white-space: nowrap;
-    margin-right: 10px;
-  }
+	width: 100%;
+	display: flex;
+	align-items: center;
+	justify-content: space-between;
+	font-size: 14px;
+	padding-right: 8px;
+	overflow: hidden;
+
+	.tree-label {
+		width: 100%;
+		overflow: hidden;
+		text-overflow: ellipsis;
+		white-space: nowrap;
+		margin-right: 10px;
+	}
 
-  &:hover {
-    .tree-options {
-      display: block;
-    }
-  }
+	&:hover {
+		.tree-options {
+			display: block;
+		}
+	}
 }
 </style>

+ 1 - 1
src/views/iot/property/dossier/component/from.vue

@@ -3,7 +3,7 @@
 	<div>
 		<div v-for="(item, index) in dataList" :key="index">
 
-      <el-form-item :label="item.title + ':'" :prop="item.name" class="form-item" v-if="item.types === 'input'">
+      <el-form-item :label="item.title + ''" :prop="item.name" class="form-item" v-if="item.types === 'input'">
         <el-input v-model="formData[item.name]" :placeholder="'请输入' + item.title" @input="saveData()" :readonly="disable" />
       </el-form-item>
 

+ 3 - 3
src/views/iot/property/dossier/edit.vue

@@ -176,7 +176,7 @@ const open = async (row: any, productInfo: any) => {
   showDialog.value = true;
   nextTick(() => {
     system.org.getList({ status: 1 }).then((res: any) => {
-      res.forEach((item:any) => {
+      res.forEach((item: any) => {
         item.id = item.id.toString();
       });
       orgData.value = res || [];
@@ -199,7 +199,7 @@ const open = async (row: any, productInfo: any) => {
 
     //获取部门
     api.dept.getList({ status: -1 }).then((res: any) => {
-      res.forEach((item:any) => {
+      res.forEach((item: any) => {
         item.deptId = item.deptId.toString();
       });
       deptData.value = res || [];
@@ -222,7 +222,7 @@ const open = async (row: any, productInfo: any) => {
       //获取档案属性
       api.dev_asset_metadata.getList({ productKey: productInfo.key, pageSize: 50, pageNum: 1, status: -1, total: 0 }).then((res: any) => {
         const sortedArray = res.Data.sort((a, b) => a.id - b.id);
-				Datalist.value = sortedArray || [];
+        Datalist.value = sortedArray || [];
       });
       formData.productKey = productInfo.key
 

+ 80 - 92
src/views/iot/property/dossier/index.vue

@@ -1,89 +1,77 @@
 <template>
-	<div class="page">
-		<el-card shadow="nover">
-			<el-form inline ref="queryRef" @keyup.enter="getList(1)">
-				<el-form-item label="名称:" prop="keyWord">
-					<el-input v-model="params.keyWord" placeholder="请输入名称" clearable style="width: 240px" />
-				</el-form-item>
-
-				<el-form-item>
-					<el-button type="primary" class="ml10" @click="getList(1)">
-						<el-icon>
-							<ele-Search />
-						</el-icon>
-						查询
-					</el-button>
-				</el-form-item>
-				<el-form-item>
-					<el-button type="success" @click="addOrEdit()" v-auth="'add'" v-if="productIno">
-						<el-icon>
-							<ele-FolderAdd />
-						</el-icon>
-						新增档案
-					</el-button>
-
-					<!-- <el-button type="primary" @click="addOrEdit()" v-if="productIno">
-						<el-icon>
-							<ele-FolderAdd />
-						</el-icon>
-						批量添加
-					</el-button> -->
-
-					<el-button type="danger" @click="batchdel()" v-auth="'batchdel'">
-						<el-icon>
-							<ele-FolderAdd />
-						</el-icon>
-						批量删除
-					</el-button>
-				</el-form-item>
-			</el-form>
-			<el-row :gutter="16">
-				<el-col :span="6">
-          <el-tree :data="mergedData" :props="defaultProps" accordion default-expand-all @node-click="handleNodeClick" style="border: 1px solid #eee;padding: 10px;margin-right: 10px;" class="mt-4" :default-expand-all="true" :node-key="'id'" highlight-current>
-            <template #default="{ node, data }">
-              <div class="custom-tree-node">
-                  <span class="tree-label">
-                    <el-icon v-if="data.is_type == '2'">
-                      <Expand />
-                    </el-icon>
-                    {{ node.label }}
-                  </span>
-              </div>
-            </template>
-          </el-tree>
-				</el-col>
-				<el-col :span="18"><el-table :data="tableData" style="width: 100%" @selection-change="handleSelectionChange" row-key="id" v-loading="loading">
+	<div class="page page-full border bg padding">
+		<el-form inline ref="queryRef" @keyup.enter="getList(1)">
+			<el-form-item label="名称" prop="keyWord">
+				<el-input v-model="params.keyWord" placeholder="请输入名称" clearable style="width: 240px" />
+			</el-form-item>
+
+			<el-form-item>
+				<el-button type="primary" class="ml10" @click="getList(1)">
+					<el-icon>
+						<ele-Search />
+					</el-icon>
+					查询
+				</el-button>
+			</el-form-item>
+			<el-form-item>
+				<el-button type="primary" @click="addOrEdit()" v-auth="'add'" v-if="productIno">
+					<el-icon>
+						<ele-FolderAdd />
+					</el-icon>
+					新增档案
+				</el-button>
+				<el-button type="info" @click="batchdel()" v-auth="'batchdel'">
+					<el-icon>
+						<ele-FolderAdd />
+					</el-icon>
+					删除
+				</el-button>
+			</el-form-item>
+		</el-form>
+		<div class="page page-full-part flex-row gap-4">
+			<el-card style="width: 250px;" shadow="nover">
+				<el-tree :data="mergedData" :props="defaultProps" accordion default-expand-all @node-click="handleNodeClick" :node-key="'id'" highlight-current>
+					<template #default="{ node, data }">
+						<div class="custom-tree-node">
+							<span class="tree-label">
+								<el-icon v-if="data.is_type != '2'">
+									<Folder />
+								</el-icon>
+                <SvgIcon name="iconfont icon-siweidaotu" v-if="data.is_type == '2'"></SvgIcon>
+								{{ node.label }}
+							</span>
+						</div>
+					</template>
+				</el-tree>
+			</el-card>
+			<el-card class="flex1" shadow="nover">
+				<div class="page page-full">
+					<el-table :data="tableData" style="width: 100%" @selection-change="handleSelectionChange" row-key="id" v-loading="loading">
 						<el-table-column type="selection" width="55" align="center" />
-
 						<el-table-column prop="deviceName" v-col="'deviceName'" label="设备名称" min-width="100" show-overflow-tooltip></el-table-column>
 						<el-table-column prop="deviceKey" v-col="'deviceKey'" label="设备KEY" show-overflow-tooltip></el-table-column>
-
 						<el-table-column prop="deviceNumber" v-col="'deviceNumber'" label="设备编码" show-overflow-tooltip></el-table-column>
-
 						<el-table-column prop="deviceCategory" v-col="'deviceCategory'" label="设备类型" show-overflow-tooltip></el-table-column>
-
 						<el-table-column prop="installTime" v-col="'installTime'" label="安装时间" width="160" align="center"></el-table-column>
-						<el-table-column label="操作" width="200" align="center">
+						<el-table-column label="操作" width="120" align="center">
 							<template #default="scope">
 								<el-button size="small" text type="warning" v-auth="'edit'" @click="addOrEdit(scope.row)">编辑</el-button>
-
 								<el-button size="small" text type="info" v-auth="'del'" @click="del(scope.row)">删除</el-button>
 							</template>
 						</el-table-column>
 					</el-table>
 					<pagination v-if="params.total" :total="params.total" v-model:page="params.pageNum" v-model:limit="params.pageSize" @pagination="getList()" />
-				</el-col>
-			</el-row>
-
-			<EditForm ref="editFormRef" @getList="getList(1)"></EditForm>
-		</el-card>
+				</div>
+			</el-card>
+		</div>
+		<EditForm ref="editFormRef" @getList="getList(1)"></EditForm>
 	</div>
 </template>
 
 <script lang="ts" setup>
 import device from '/@/api/device'
 import { useSearch } from '/@/hooks/useCommon'
-import { Expand } from '@element-plus/icons-vue'
+import { Folder } from '@element-plus/icons-vue'
 
 import { ElMessageBox, ElMessage } from 'element-plus'
 import EditForm from './edit.vue'
@@ -115,7 +103,7 @@ onMounted(() => {
 })
 const addOrEdit = async (row?: any) => {
 	if (row) {
-    editFormRef.value.open(row, productIno.value)
+		editFormRef.value.open(row, productIno.value)
 		return
 	} else {
 		editFormRef.value.open({}, productIno.value)
@@ -130,10 +118,10 @@ const getCateList = () => {
 			productData.value = res.product
 			mergedData.value = matchProductsToCategories(productData.value, cateData.value)
 
-      // 默认加载第一个设备对应属性
-      if (productData.value.length > 0) {
-        handleNodeClick(mergedData.value[0].children[0])
-      }
+			// 默认加载第一个设备对应属性
+			if (productData.value.length > 0) {
+				handleNodeClick(mergedData.value[0].children[0])
+			}
 		})
 	})
 }
@@ -216,26 +204,26 @@ const del = (row: any) => {
 </script>
 <style scoped lang="scss">
 .custom-tree-node {
-  width: 100%;
-  display: flex;
-  align-items: center;
-  justify-content: space-between;
-  font-size: 14px;
-  padding-right: 8px;
-  overflow: hidden;
-
-  .tree-label {
-    width: 100%;
-    overflow: hidden;
-    text-overflow: ellipsis;
-    white-space: nowrap;
-    margin-right: 10px;
-  }
-
-  &:hover {
-    .tree-options {
-      display: block;
-    }
-  }
+	width: 100%;
+	display: flex;
+	align-items: center;
+	justify-content: space-between;
+	font-size: 14px;
+	padding-right: 8px;
+	overflow: hidden;
+
+	.tree-label {
+		width: 100%;
+		overflow: hidden;
+		text-overflow: ellipsis;
+		white-space: nowrap;
+		margin-right: 10px;
+	}
+
+	&:hover {
+		.tree-options {
+			display: block;
+		}
+	}
 }
 </style>

+ 4 - 4
src/views/iot/scene/list/index.vue

@@ -1,12 +1,12 @@
 <template>
 	<div class="page">
 		<el-card shadow="nover">
-			<el-form inline ref="queryRef">
-				<el-form-item label="场景名称" prop="name">
-					<el-input v-model="params.keyWord" placeholder="场景名称" clearable style="width: 220px" @keyup.enter.native="getList(1)" />
+			<el-form inline ref="queryRef" @submit.prevent @keyup.enter.native="getList(1)">
+				<el-form-item label="场景名称" prop="name">
+					<el-input v-model="params.keyWord" placeholder="请输入" clearable style="width: 220px" />
 				</el-form-item>
 				<el-form-item label="状态" prop="status">
-					<el-select v-model="params.status" placeholder="发布状态" clearable style="width: 160px">
+					<el-select v-model="params.status" placeholder="" clearable style="width: 120px">
 						<el-option label="全部" :value="-1" />
 						<el-option label="成功" :value="1" />
 						<el-option label="失败" :value="0" />

+ 61 - 64
src/views/iot/scene/manage/component/actionItem.vue

@@ -1,8 +1,7 @@
 <template>
   <div class="type-item">
     <div v-for="(item, index) in actionList" :key="index" class="item " :class="index > 0 ? 'biankang' : ''">
-      <div class="conicon" style="width: 100%; text-align: right; position: relative; right: -8px; top: -8px; color: red"
-        v-if="index > 0">
+      <div class="conicon" style="width: 100%; text-align: right; position: relative; right: -8px; top: -8px; color: red" v-if="index > 0">
         <el-icon @click="delAction(index)">
           <CircleClose />
         </el-icon>
@@ -12,16 +11,14 @@
       </div>
       <div class="product flex flex-warp">
         <ActionSerialItem :index="index" :serial="item.serial" :sourceData="sourceData" @saveData="saveData" @delData="delData"></ActionSerialItem>
-
-      
       </div>
 
       <div class="title flex">
         <div class="icon"></div>并行动作
       </div>
       <div class="product flex flex-warp">
-            <ActionParallelItem :index="index"  :parallel="item.parallel" :sourceData="sourceData" @saveData="saveData" @delData="delData"></ActionParallelItem>
-      
+        <ActionParallelItem :index="index" :parallel="item.parallel" :sourceData="sourceData" @saveData="saveData" @delData="delData"></ActionParallelItem>
+
       </div>
     </div>
     <div>
@@ -29,12 +26,12 @@
         <el-button type="primary" :icon="DocumentAdd" @click="addAction()">新增场景动作</el-button>
       </div>
     </div>
- 
+
   </div>
 </template>
 
 <script lang="ts" setup>
-import { PropType, ref  } from 'vue'
+import { PropType, ref } from 'vue'
 import { DocumentAdd, CircleClose } from '@element-plus/icons-vue';
 import ActionSerialItem from './actionSerialItem.vue';
 import ActionParallelItem from './actionParallelItem.vue';
@@ -42,16 +39,16 @@ import api from '/@/api/scene';
 
 
 //初始化数据
-const actionList_temp=ref([{
-  serial:[{}],
-  parallel:[{}],
+const actionList_temp = ref([{
+  serial: [{}],
+  parallel: [{}],
 }]);
-const actionList=ref([{
-  serial:[{}],
-  parallel:[{}],
+const actionList = ref([{
+  serial: [{}],
+  parallel: [{}],
 }]);
 const originalSceneList = ref([{
-	id: 0
+  id: 0
 }]);
 interface testIValueType {
   id: string;
@@ -64,74 +61,74 @@ const props = defineProps({
     type: Array as PropType<testIValueType[]>,
     default: () => []
   },
-  scene_id:{
-    type:String,
+  scene_id: {
+    type: String,
     default: () => ''
   }
 })
 
 const getOneDetail = () => {
-			api.manage.getOneDetail({ "sceneId": props.scene_id, 'group': 'action' }).then((res: any) => {
-				if (!res) {
-					addActionDetail();
-					// getOneDetail();
-				}
-
-       	originalSceneList.value = res;
-        const combinedArray = res.map((scene: any)  => {
-          const parsedBodyJson = JSON.parse(scene.bodyjson);
-          if (Array.isArray(parsedBodyJson)) {
-            const serial = parsedBodyJson.map(item => item.serial).flat();
-            const parallel = parsedBodyJson.map(item => item.parallel).flat();
-            return {
-              serial,
-              parallel
-            };
-          } else {
-            return {
-              serial: [parsedBodyJson.serial].flat(),
-              parallel: [parsedBodyJson.parallel].flat()
-            };
-          }
-        });
-        actionList.value=combinedArray;
-			})
-		};
-getOneDetail();    
+  api.manage.getOneDetail({ "sceneId": props.scene_id, 'group': 'action' }).then((res: any) => {
+    if (!res) {
+      addActionDetail();
+      // getOneDetail();
+    }
+
+    originalSceneList.value = res;
+    const combinedArray = res.map((scene: any) => {
+      const parsedBodyJson = JSON.parse(scene.bodyjson);
+      if (Array.isArray(parsedBodyJson)) {
+        const serial = parsedBodyJson.map(item => item.serial).flat();
+        const parallel = parsedBodyJson.map(item => item.parallel).flat();
+        return {
+          serial,
+          parallel
+        };
+      } else {
+        return {
+          serial: [parsedBodyJson.serial].flat(),
+          parallel: [parsedBodyJson.parallel].flat()
+        };
+      }
+    });
+    actionList.value = combinedArray;
+  })
+};
+getOneDetail();
 
 //新增一条场景动作
 const addActionDetail = () => {
-		let data = {
-			sceneId: props.scene_id,
-			group: 'action',
-			bodyjson: actionList_temp.value,
-		}
-		api.manage.addDetail(data).then((res: any) => {
-			getOneDetail();
-		});
+  let data = {
+    sceneId: props.scene_id,
+    group: 'action',
+    bodyjson: actionList_temp.value,
+  }
+  api.manage.addDetail(data).then((res: any) => {
+    getOneDetail();
+  });
 }
 //删除一条场景
 const delData = (index: number) => {
-			let ids =originalSceneList.value[index].id;
-			api.manage.delDetail(ids).then((res: any) => {
-				// getOneDetail();
-			});
+  let ids = originalSceneList.value[index].id;
+  api.manage.delDetail(ids).then((res: any) => {
+    // getOneDetail();
+  });
 }
 
 //修改一条场景
 const saveData = (data: any) => {
-      let ids = originalSceneList.value[data.index].id;
-			api.manage.editDetail({ id: ids, bodyjson:actionList.value[data.index] }).then((res: any) => {
-				  getOneDetail();
-			});
+  let ids = originalSceneList.value[data.index].id;
+  api.manage.editDetail({ id: ids, bodyjson: actionList.value[data.index] }).then((res: any) => {
+    getOneDetail();
+  });
 
 }
 
 
 const addAction = () => {
   actionList.value.push({
-    'serial':[],
-    'parallel':[],
+    'serial': [],
+    'parallel': [],
   });
   addActionDetail();
 
@@ -143,7 +140,6 @@ const delAction = (index: number) => {
 
 </script>
 <style scoped lang="scss">
-
 .type-item {
   margin-top: 15px;
 
@@ -170,7 +166,8 @@ const delAction = (index: number) => {
 
   .biankang {
     margin-top: 10px;
-    border: 1px solid #cfcfcf;;
+    border: 1px solid #cfcfcf;
+    ;
     border-radius: 2px;
   }
 

+ 54 - 55
src/views/iot/scene/manage/detail.vue

@@ -1,48 +1,47 @@
 <template>
 	<div>
-	<el-card class="system-dic-container" style="position: relative;">
-		<div class="content">
-			<div class="flex cont_box">
-				<div class="font26">场景名称:{{ detail.name }}</div>
-				<div class="pro-status"><span :class="detail.status == 1 ? 'on' : 'off'"></span>{{ detail.status == 1
-					? '启用' : '未启用' }}</div>
+		<el-card class="system-dic-container" shadow="nover" style="position: relative;">
+			<div class="content">
+				<div class="flex cont_box">
+					<div class="font26">场景名称:{{ detail.name }}</div>
+					<div class="pro-status"><span :class="detail.status == 1 ? 'on' : 'off'"></span>{{ detail.status == 1
+						? '启用' : '未启用' }}</div>
+				</div>
+				<div class="flex">
+					<div class="desc" style="margin-right: 20px;">场景类型:{{ typeList[detail.sceneType] }}</div>
+					<div class="desc">场景描述:{{ detail.description }}</div>
+					<div class="edit" @click="addOrEdit(detail)"><el-link type="primary"> <el-icon>
+								<EditPen color="#409eff" />
+							</el-icon>修改</el-link></div>
+				</div>
 			</div>
-			<div class="flex">
-				<div class="desc" style="margin-right: 20px;">场景类型:{{typeList[detail.sceneType]}}</div>
-				<div class="desc">场景描述:{{ detail.description }}</div>
-				<div class="edit" @click="addOrEdit(detail)"><el-link type="primary"> <el-icon>
-							<EditPen color="#409eff" />
-						</el-icon>修改</el-link></div>
-			</div>
-		</div>
-	</el-card>
-	<el-card style="  margin-top: 15px;" v-if="detail.sceneType==='device'">
-		<div class="font20">场景定义</div>
-		<SceneItem v-if="showstatus && sourceData.length>0" :sceneList="sceneList" :sourceData="sourceData" :sceneType="detail.sceneType" @addScenesDetail="addScenesDetail"
-			@delScenesDetail="delScenesDetail" @editScenesDetail="editScenesDetail"></SceneItem>
-	</el-card>
-	<el-card style="  margin-top: 15px;" v-if="detail.sceneType==='timer'">
-		<el-form-item label="定时触发">
-          <div style="display:flex">
-            <el-input v-model="timerData.timer" placeholder="请输入cron表达式" />
-            <el-dialog v-model="dialogVisible" title="选择Cron规则" width="60%">
-              <vue3cron @handlelisten="handlelisten"  @close="cronclose"></vue3cron>
-            </el-dialog>
-            <el-button type="success" @click="showCron()" style="margin-left: 5px;">设置</el-button>
+		</el-card>
+		<el-card shadow="nover" style="margin-top: 15px;" v-if="detail.sceneType === 'device'">
+			<div class="font20">场景定义</div>
+			<SceneItem v-if="showstatus && sourceData.length > 0" :sceneList="sceneList" :sourceData="sourceData" :sceneType="detail.sceneType" @addScenesDetail="addScenesDetail" @delScenesDetail="delScenesDetail" @editScenesDetail="editScenesDetail"></SceneItem>
+		</el-card>
+		<el-card style="  margin-top: 15px;" v-if="detail.sceneType === 'timer'">
+			<el-form-item label="定时触发">
+				<div style="display:flex">
+					<el-input v-model="timerData.timer" placeholder="请输入cron表达式" />
+					<el-dialog v-model="dialogVisible" title="选择Cron规则" width="60%">
+						<vue3cron @handlelisten="handlelisten" @close="cronclose"></vue3cron>
+					</el-dialog>
+					<el-button type="success" @click="showCron()" style="margin-left: 5px;">设置</el-button>
 
-          </div>
-        </el-form-item>
-	</el-card>
-	<el-card style="  margin-top: 15px;">
-		<div class="font20">场景动作</div>
-		<ActionItem v-if="detail.id && sourceData.length>0" :scene_id="detail.id"  :sourceData="sourceData"></ActionItem>
-	</el-card>
+				</div>
+			</el-form-item>
+		</el-card>
+		<el-card shadow="nover" style="margin-top: 15px;">
+			<div class="font20">场景动作</div>
+			<ActionItem v-if="detail.id && sourceData.length > 0" :scene_id="detail.id" :sourceData="sourceData"></ActionItem>
+		</el-card>
 
-	<EditForm ref="editFormRef" @getList="getDetail()"></EditForm>
-</div>
+		<EditForm ref="editFormRef" @getList="getDetail()"></EditForm>
+	</div>
 </template>
 <script lang="ts">
-import { toRefs, reactive, ref, defineComponent,onMounted } from 'vue';
+import { toRefs, reactive, ref, defineComponent, onMounted } from 'vue';
 import { useRoute, useRouter } from 'vue-router';
 import { EditPen, DocumentAdd } from '@element-plus/icons-vue';
 import ActionItem from './component/actionItem.vue';
@@ -71,26 +70,26 @@ const originalSceneList = ref([{
 	id: 0
 }]);
 export default defineComponent({
-	components: { EditPen, EditForm, DocumentAdd, SceneItem, ActionItem,vue3cron},
+	components: { EditPen, EditForm, DocumentAdd, SceneItem, ActionItem, vue3cron },
 	setup(props, context) {
 		const route = useRoute();
 		const router = useRouter();
 		const state = reactive({
-			timer:'',
-			timer_id:0,
-			dialogVisible:false,
+			timer: '',
+			timer_id: 0,
+			dialogVisible: false,
 			developer_status: 2,
 			showstatus: false,
-			typeList:{
-				'device':'设备触发',
-				'manual':'手动触发',
-				'timer':'定时触发',
+			typeList: {
+				'device': '设备触发',
+				'manual': '手动触发',
+				'timer': '定时触发',
 			} as any,
 			sourceData: [],
 			timerData: {
-					triggerType:'timer',
-					timer:'',
-				},
+				triggerType: 'timer',
+				timer: '',
+			},
 			detail: {
 				id: '',
 				name: '',
@@ -123,7 +122,7 @@ export default defineComponent({
 		});
 
 		const handlelisten = (e: any) => {
-			state.timerData.timer=e.cron
+			state.timerData.timer = e.cron
 			api.manage.editDetail({ id: state.timer_id, bodyjson: state.timerData })
 		};
 		const showCron = () => {
@@ -150,7 +149,7 @@ export default defineComponent({
 		const getOneDetail = () => {
 
 			const id = route.params && route.params.id;
-			if(state.detail.sceneType=='device'){
+			if (state.detail.sceneType == 'device') {
 				api.manage.getOneDetail({ "sceneId": id, 'group': 'definition' }).then((res: any) => {
 					if (!res) {
 						addScenesDetail('definition');
@@ -168,21 +167,21 @@ export default defineComponent({
 				})
 			}
 			//定时触发
-			if(state.detail.sceneType=='timer'){
+			if (state.detail.sceneType == 'timer') {
 				api.manage.getOneDetail({ "sceneId": id, 'group': 'timer' }).then((res: any) => {
 					if (!res) {
 						let data = {
 							sceneId: id,
 							group: 'timer',
-							bodyjson:state.timerData,
+							bodyjson: state.timerData,
 						}
 						api.manage.addDetail(data).then((res: any) => {
 							getOneDetail();
 						});
 					}
 
-					state.timer_id=res[0].id
-					state.timerData=JSON.parse(res[0].bodyjson);
+					state.timer_id = res[0].id
+					state.timerData = JSON.parse(res[0].bodyjson);
 
 				})
 			}

+ 7 - 15
src/views/iot/scene/manage/index.vue

@@ -3,43 +3,36 @@
 		<el-card shadow="nover">
 			<div class="search">
 				<el-form inline ref="queryRef" @keyup.enter="getList(1)">
-					<el-form-item label="场景名称" prop="keyWord">
-						<el-input v-model="params.keyWord" placeholder="请输入场景名称" clearable style="width: 240px" />
+					<el-form-item label="场景名称" prop="keyWord">
+						<el-input v-model="params.keyWord" placeholder="请输入场景名称" clearable style="width: 200px" />
 					</el-form-item>
-
-					<el-form-item label="触发方式" prop="sceneType" style="width: 200px;">
-						<el-select v-model="params.sceneType" placeholder="触发方式" clearable style="width: 240px">
+					<el-form-item label="触发方式" prop="sceneType">
+						<el-select v-model="params.sceneType" placeholder="触发方式" clearable style="width: 140px">
 							<el-option label="设备触发" value="device" />
 							<el-option label="手动触发" value="manual" />
 							<el-option label="定时触发" value="timer" />
 						</el-select>
 					</el-form-item>
-
-					<el-form-item label="运行状态" prop="status" style="width: 200px;">
-						<el-select v-model="params.status" placeholder="运行状态" clearable style="width: 240px">
+					<el-form-item label="运行状态" prop="status">
+						<el-select v-model="params.status" placeholder="运行状态" clearable style="width: 140px">
 							<el-option label="全部" :value="-1" />
 							<el-option label="启用" :value="1" />
 							<el-option label="禁用" :value="0" />
 						</el-select>
 					</el-form-item>
-
 					<el-form-item>
-
 						<el-button type="primary" class="ml10" @click="getList(1)">
 							<el-icon>
 								<ele-Search />
 							</el-icon>
 							查询
 						</el-button>
-					</el-form-item>
-					<el-form-item>
 						<el-button type="success" v-auth="'add'" @click="addOrEdit()">
 							<el-icon>
 								<ele-FolderAdd />
 							</el-icon>
 							新增场景
 						</el-button>
-
 					</el-form-item>
 				</el-form>
 			</div>
@@ -82,8 +75,7 @@
 <script lang="ts" setup>
 import api from '/@/api/scene';
 import { useSearch } from '/@/hooks/useCommon';
-import { ElMessageBox, ElMessage, FormInstance } from 'element-plus';
-import getOrigin from '/@/utils/origin'
+import { ElMessageBox, ElMessage } from 'element-plus';
 import EditForm from './edit.vue';
 import { ref } from 'vue';
 import { useRouter } from 'vue-router';

+ 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 - 1
src/views/system/config/index.vue

@@ -38,7 +38,7 @@
         </el-form-item>
       </el-form>
       <!-- 字典切换 -->
-      <el-tabs v-model="tableData.param.moduleClassify" class="demo-tabs" @tab-change="dataList">
+      <el-tabs v-model="tableData.param.moduleClassify" @tab-change="dataList">
         <el-tab-pane v-for="dict in tabDataList" :label="dict.dictLabel" :name="dict.dictValue"></el-tab-pane>
       </el-tabs>
       <el-table :data="tableData.data" style="width: 100%" @selection-change="handleSelectionChange" v-loading="tableData.loading">

+ 6 - 8
src/views/system/datahub/modeling/detail.vue

@@ -1,25 +1,23 @@
 <template>
-  <div class="system-dic-container">
+  <div class="page page-full">
     <div class="content">
       <div class="cont_box">
         <div class="title">模型标识:{{ detail.key }}</div>
         <div class="title" style="margin-left: 20px">模型表名:{{ detail.name }}</div>
-
         <div class="pro-status"><span :class="developer_status == 1 ? 'on' : 'off'"></span>{{ developer_status == 1 ? '已发布' : '未发布' }}</div>
-
         <div class="pro-option" v-auth="'startOrStop'" @click="CkOption">{{ developer_status == 1 ? '停用' : '发布' }}</div>
       </div>
     </div>
 
-    <div class="content-box">
+    <div class="content-box page page-full-part">
       <div class="wu-box">
         <div class="system-user-search mb15">
-          <el-form :model="tableData.param" ref="queryRef" inline label-width="130px">
+          <el-form :model="tableData.param" ref="queryRef" inline @submit.prevent @keyup.enter="typeList">
             <el-form-item label="字段标题" prop="name">
-              <el-input v-model="tableData.param.name" placeholder="请输入字段标题" clearable style="width: 240px" @keyup.enter.native="typeList" />
+              <el-input v-model="tableData.param.name" placeholder="请输入字段标题" clearable style="width: 220px" />
             </el-form-item>
             <el-form-item label="字段名称" prop="key">
-              <el-input v-model="tableData.param.key" placeholder="请输入字段名称" clearable style="width: 240px" @keyup.enter.native="typeList" />
+              <el-input v-model="tableData.param.key" placeholder="请输入字段名称" clearable style="width: 220px" />
             </el-form-item>
 
             <el-form-item>
@@ -234,7 +232,7 @@ export default defineComponent({
   background: #fff;
   width: 100%;
   padding: 20px;
-  margin-top: 20px;
+  margin-top: 15px;
 }
 
 .cont_box {

+ 23 - 32
src/views/system/datahub/source/detail.vue

@@ -1,16 +1,15 @@
 <template>
-	<div class="system-dic-container">
+	<div class="page page-full">
 		<div class="content">
 			<div class="cont_box">
 				<div class="title">数据源名称:{{ detail.name }}</div>
 				<div class="pro-status"><span :class="developer_status == 1 ? 'on' : 'off'"></span>{{ developer_status == 1 ? '已发布' : '未发布' }}</div>
-
 				<div class="pro-option" v-auth="'startOrStop'" @click="CkOption">{{ developer_status == 1 ? '停用' : '发布' }}</div>
 			</div>
 		</div>
 
-		<div class="content-box">
-			<el-tabs v-model="activeName" class="demo-tabs" @tab-click="handleClick">
+		<div class="content-box page-full page-full-part">
+			<el-tabs v-model="activeName" @tab-click="handleClick">
 				<el-tab-pane label="数据源信息" name="1">
 					<el-form label-width="110px" inline>
 						<el-divider content-position="left">基本信息</el-divider>
@@ -133,35 +132,27 @@
 				</el-tab-pane>
 
 				<el-tab-pane label="数据节点" name="2">
-					<div class="wu-box">
-						<div class="wu-title">
-							<div class="title">数据节点</div>
-							<div v-if="developer_status == 0" v-auth="'add'"><el-button type="primary" @click="onOpenEdit()">添加</el-button></div>
-						</div>
-
-						<el-table :data="tableData.data" style="width: 100%" v-loading="tableData.loading">
-							<el-table-column label="ID" align="center" prop="nodeId" width="100" v-col="'id'" />
-							<el-table-column label="数据标识" prop="key" show-overflow-tooltip v-col="'key'" />
-							<el-table-column label="数据名称" prop="name" show-overflow-tooltip v-col="'name'" />
-							<el-table-column label="数据类型" prop="dataType" show-overflow-tooltip v-col="'dataType'" />
-							<el-table-column label="数据取值项" prop="value" show-overflow-tooltip v-col="'value'" />
-
-							<el-table-column prop="createdAt" label="创建时间" align="center" v-col="'createdAt'" width="180"></el-table-column>
-
-							<el-table-column label="操作" width="200" align="center" fixed="right">
-								<template #default="scope">
-									<el-button size="small" text type="warning" @click="onOpenEdit1(scope.row)" v-if="developer_status == 0" v-auth="'edit'">修改</el-button>
-									<el-button size="small" text type="danger" @click="onRowDel(scope.row)" v-if="developer_status == 0" v-auth="'del'">删除</el-button>
-								</template>
-							</el-table-column>
-						</el-table>
-						<pagination v-show="tableData.total > 0" :total="tableData.total" v-model:page="tableData.param.pageNum" @pagination="typeList" />
+					<div class="flex-row flex-end">
+						<el-button type="primary" size="small" @click="onOpenEdit()" v-if="developer_status == 0" v-auth="'add'">添加</el-button>
 					</div>
+					<el-table :data="tableData.data" style="width: 100%" v-loading="tableData.loading">
+						<el-table-column label="ID" align="center" prop="nodeId" width="100" v-col="'id'" />
+						<el-table-column label="数据标识" prop="key" show-overflow-tooltip v-col="'key'" />
+						<el-table-column label="数据名称" prop="name" show-overflow-tooltip v-col="'name'" />
+						<el-table-column label="数据类型" prop="dataType" show-overflow-tooltip v-col="'dataType'" />
+						<el-table-column label="数据取值项" prop="value" show-overflow-tooltip v-col="'value'" />
+						<el-table-column prop="createdAt" label="创建时间" align="center" v-col="'createdAt'" width="180"></el-table-column>
+						<el-table-column label="操作" width="200" align="center" fixed="right">
+							<template #default="scope">
+								<el-button size="small" text type="warning" @click="onOpenEdit1(scope.row)" v-if="developer_status == 0" v-auth="'edit'">修改</el-button>
+								<el-button size="small" text type="danger" @click="onRowDel(scope.row)" v-if="developer_status == 0" v-auth="'del'">删除</el-button>
+							</template>
+						</el-table-column>
+					</el-table>
+					<pagination v-show="tableData.total > 0" :total="tableData.total" v-model:page="tableData.param.pageNum" @pagination="typeList" />
 				</el-tab-pane>
 				<el-tab-pane label="查看数据" name="3">
-					<div class="wu-box">
-						<JsonViewer :value="jsonData" boxed sort theme="jv-dark" @click="onKeyclick" />
-					</div>
+					<JsonViewer :value="jsonData" boxed sort theme="jv-dark" @click="onKeyclick" />
 				</el-tab-pane>
 			</el-tabs>
 		</div>
@@ -356,8 +347,8 @@ export default defineComponent({
 .content-box {
 	background: #fff;
 	width: 100%;
-	padding: 20px;
-	margin-top: 20px;
+	padding: 10px 20px 20px;
+	margin-top: 15px;
 }
 
 .cont_box {

+ 1 - 1
src/views/system/dict/index.vue

@@ -39,7 +39,7 @@
         </el-form-item>
       </el-form>
       <!-- 字典切换 -->
-      <el-tabs v-model="tableData.param.moduleClassify" class="demo-tabs" @tab-change="typeList">
+      <el-tabs v-model="tableData.param.moduleClassify" @tab-change="typeList">
         <el-tab-pane v-for="dict in tabDataList" :label="dict.dictLabel" :name="dict.dictValue">
         </el-tab-pane>
       </el-tabs>

+ 59 - 61
src/views/system/manage/post/index.vue

@@ -1,71 +1,69 @@
 <template>
-  <div class="page">
-    <el-card shadow="nover">
-      <el-form :model="tableData.param" inline ref="queryRef">
-        <el-form-item label="岗位名称" prop="postName">
-          <el-input v-model="tableData.param.postName" placeholder="请输入岗位名称" class="w-50" clearable />
-        </el-form-item>
-        <el-form-item label="岗位编码" prop="postCode">
-          <el-input v-model="tableData.param.postCode" placeholder="请输入岗位编码" class="w-50" clearable />
-        </el-form-item>
-        <el-form-item label="状态" prop="status">
-          <el-select placeholder="请选择状态" class="w-50" v-model="tableData.param.status">
-            <el-option label="全部" :value="-1" />
-            <el-option label="启用" :value="1" />
-            <el-option label="禁用" :value="0" />
-          </el-select>
-        </el-form-item>
-        <el-form-item>
-          <el-button type="primary" class="ml10" @click="postList">
-            <el-icon>
-              <ele-Search />
-            </el-icon>
-            查询
-          </el-button>
-          <el-button @click="resetQuery()">
-            <el-icon>
-              <ele-Refresh />
-            </el-icon>
-            重置
-          </el-button>
-          <el-button type="primary" class="ml10" @click="onOpenAddPost" v-auth="'add'">
-            <el-icon>
-              <ele-FolderAdd />
-            </el-icon>
-            新增岗位
-          </el-button>
-          <!-- <el-button type="info" class="ml10" @click="onRowDel(null)">
+  <el-card shadow="nover" class="page">
+    <el-form :model="tableData.param" inline ref="queryRef">
+      <el-form-item label="岗位名称" prop="postName">
+        <el-input v-model="tableData.param.postName" placeholder="请输入岗位名称" class="w-50" clearable />
+      </el-form-item>
+      <el-form-item label="岗位编码" prop="postCode">
+        <el-input v-model="tableData.param.postCode" placeholder="请输入岗位编码" class="w-50" clearable />
+      </el-form-item>
+      <el-form-item label="状态" prop="status">
+        <el-select placeholder="请选择状态" style="width: 80px;" v-model="tableData.param.status">
+          <el-option label="全部" :value="-1" />
+          <el-option label="启用" :value="1" />
+          <el-option label="禁用" :value="0" />
+        </el-select>
+      </el-form-item>
+      <el-form-item>
+        <el-button type="primary" class="ml10" @click="postList">
+          <el-icon>
+            <ele-Search />
+          </el-icon>
+          查询
+        </el-button>
+        <el-button @click="resetQuery()">
+          <el-icon>
+            <ele-Refresh />
+          </el-icon>
+          重置
+        </el-button>
+        <el-button type="primary" class="ml10" @click="onOpenAddPost" v-auth="'add'">
+          <el-icon>
+            <ele-FolderAdd />
+          </el-icon>
+          新增岗位
+        </el-button>
+        <!-- <el-button type="info" class="ml10" @click="onRowDel(null)">
               <el-icon>
                 <ele-Delete />
               </el-icon>
               删除岗位
             </el-button> -->
-        </el-form-item>
-      </el-form>
-      <el-table :data="tableData.data" style="width: 100%" @selection-change="handleSelectionChange" row-key="postId" :tree-props="{ children: 'children', hasChildren: 'hasChildren' }" v-loading="tableData.loading">
-        <el-table-column type="selection" width="55" align="center" />
-        <el-table-column type="index" label="序号" width="60" align="center" />
-        <el-table-column prop="postCode" v-col="'postCode'" label="岗位编码" width="220" show-overflow-tooltip></el-table-column>
-        <el-table-column prop="postName" v-col="'postName'" label="岗位名称" show-overflow-tooltip></el-table-column>
-        <el-table-column prop="postSort" v-col="'postSort'" label="排序" width="60" align="center"></el-table-column>
-        <el-table-column prop="status" v-col="'status'" label="岗位状态" width="120" align="center">
-          <template #default="scope">
-            <el-tag type="success" size="small" v-if="scope.row.status === 1">启用</el-tag>
-            <el-tag type="info" size="small" v-else>禁用</el-tag>
-          </template>
-        </el-table-column>
-        <el-table-column prop="remark" v-col="'remark'" label="岗位描述" show-overflow-tooltip></el-table-column>
-        <el-table-column prop="createdAt" v-col="'createdAt'" label="创建时间" width="180" align="center"></el-table-column>
-        <el-table-column label="操作" width="100" v-col="'handle'">
-          <template #default="scope">
-            <el-button size="small" text type="warning" @click="onOpenEditPost(scope.row)" v-auth="'edit'">修改</el-button>
-            <el-button size="small" text type="info" @click="onRowDel(scope.row)" v-auth="'del'">删除</el-button>
-          </template>
-        </el-table-column>
-      </el-table>
-    </el-card>
+      </el-form-item>
+    </el-form>
+    <el-table :data="tableData.data" style="width: 100%" @selection-change="handleSelectionChange" row-key="postId" :tree-props="{ children: 'children', hasChildren: 'hasChildren' }" v-loading="tableData.loading">
+      <el-table-column type="selection" width="55" align="center" />
+      <el-table-column type="index" label="序号" width="60" align="center" />
+      <el-table-column prop="postCode" v-col="'postCode'" label="岗位编码" width="220" show-overflow-tooltip></el-table-column>
+      <el-table-column prop="postName" v-col="'postName'" label="岗位名称" show-overflow-tooltip></el-table-column>
+      <el-table-column prop="postSort" v-col="'postSort'" label="排序" width="60" align="center"></el-table-column>
+      <el-table-column prop="status" v-col="'status'" label="岗位状态" width="120" align="center">
+        <template #default="scope">
+          <el-tag type="success" size="small" v-if="scope.row.status === 1">启用</el-tag>
+          <el-tag type="info" size="small" v-else>禁用</el-tag>
+        </template>
+      </el-table-column>
+      <el-table-column prop="remark" v-col="'remark'" label="岗位描述" show-overflow-tooltip></el-table-column>
+      <el-table-column prop="createdAt" v-col="'createdAt'" label="创建时间" width="180" align="center"></el-table-column>
+      <el-table-column label="操作" width="100" v-col="'handle'">
+        <template #default="scope">
+          <el-button size="small" text type="warning" @click="onOpenEditPost(scope.row)" v-auth="'edit'">修改</el-button>
+          <el-button size="small" text type="info" @click="onRowDel(scope.row)" v-auth="'del'">删除</el-button>
+        </template>
+      </el-table-column>
+    </el-table>
     <EditPost ref="editPostRef" @getPostList="postList" />
-  </div>
+  </el-card>
 </template>
 
 <script lang="ts">

+ 58 - 59
src/views/system/manage/role/index.vue

@@ -1,57 +1,56 @@
 <template>
-  <div class="page">
-    <el-card shadow="nover">
-      <el-form :model="tableData.param" inline ref="queryRef">
-        <el-form-item label="角色名称" prop="name">
-          <el-input v-model="tableData.param.name" placeholder="请输入角色名称" class="w-50" clearable />
-        </el-form-item>
-        <el-form-item label="状态" prop="status">
-          <el-select placeholder="请选择状态" class="w-50" v-model="tableData.param.status">
-            <el-option label="全部" :value="-1" />
-            <el-option label="启用" :value="1" />
-            <el-option label="禁用" :value="0" />
-          </el-select>
-        </el-form-item>
-        <el-form-item>
-          <el-button type="primary" class="ml10" @click="roleList">
-            <el-icon>
-              <ele-Search />
-            </el-icon>
-            查询
-          </el-button>
-          <el-button @click="resetQuery()">
-            <el-icon>
-              <ele-Refresh />
-            </el-icon>
-            重置
-          </el-button>
-          <el-button type="primary" class="ml10" @click="onOpenAddRole" v-auth="'add'">
-            <el-icon>
-              <ele-FolderAdd />
-            </el-icon>
-            新增角色
-          </el-button>
-        </el-form-item>
-      </el-form>
-      <el-table :data="tableData.data" style="width: 100%" row-key="id" :tree-props="{ children: 'children', hasChildren: 'hasChildren' }" v-loading="tableData.loading">
-        <el-table-column type="index" label="序号" width="60" align="center" />
-        <el-table-column prop="name" v-col="'name'" label="角色名称" show-overflow-tooltip></el-table-column>
-        <el-table-column prop="remark" v-col="'remark'" label="角色描述" show-overflow-tooltip></el-table-column>
-        <el-table-column prop="listOrder" v-col="'listOrder'" label="排序" width="60" align="center"></el-table-column>
-        <el-table-column prop="status" v-col="'status'" label="角色状态" width="100" align="center">
-          <template #default="scope">
-            <el-tag type="success" size="small" v-if="scope.row.status === 1">启用</el-tag>
-            <el-tag type="info" size="small" v-else>禁用</el-tag>
-          </template>
-        </el-table-column>
-        <el-table-column prop="createdAt" v-col="'createdAt'" label="创建时间" width="170" align="center"></el-table-column>
-        <el-table-column label="操作" width="220" v-col="'handle'" align="center" fixed="right">
-          <template #default="scope">
-            <el-button size="small" type="text" @click="onOpenEditRole(scope.row)" v-auth="'edit'">修改</el-button>
-            <el-button size="small" text type="info" @click="onRowDel(scope.row)" v-auth="'del'">删除</el-button>
-            <el-button size="small" text type="success" @click="permission(scope.row)" v-auth="'role-premission'">角色权限</el-button>
-            <el-button size="small" text type="info" @click="dataPermission(scope.row)" v-auth="'data-premission'">数据权限</el-button>
-            <!-- <el-dropdown size="small">
+  <el-card shadow="nover" class="page">
+    <el-form :model="tableData.param" inline ref="queryRef">
+      <el-form-item label="角色名称" prop="name">
+        <el-input v-model="tableData.param.name" placeholder="请输入角色名称" class="w-50" clearable />
+      </el-form-item>
+      <el-form-item label="状态" prop="status">
+        <el-select placeholder="请选择状态" style="width: 80px;" v-model="tableData.param.status">
+          <el-option label="全部" :value="-1" />
+          <el-option label="启用" :value="1" />
+          <el-option label="禁用" :value="0" />
+        </el-select>
+      </el-form-item>
+      <el-form-item>
+        <el-button type="primary" @click="roleList">
+          <el-icon>
+            <ele-Search />
+          </el-icon>
+          查询
+        </el-button>
+        <el-button @click="resetQuery()">
+          <el-icon>
+            <ele-Refresh />
+          </el-icon>
+          重置
+        </el-button>
+        <el-button type="primary" @click="onOpenAddRole" v-auth="'add'">
+          <el-icon>
+            <ele-FolderAdd />
+          </el-icon>
+          新增角色
+        </el-button>
+      </el-form-item>
+    </el-form>
+    <el-table :data="tableData.data" style="width: 100%" row-key="id" :tree-props="{ children: 'children', hasChildren: 'hasChildren' }" v-loading="tableData.loading">
+      <el-table-column type="index" label="序号" width="60" align="center" />
+      <el-table-column prop="name" v-col="'name'" label="角色名称" show-overflow-tooltip></el-table-column>
+      <el-table-column prop="remark" v-col="'remark'" label="角色描述" show-overflow-tooltip></el-table-column>
+      <el-table-column prop="listOrder" v-col="'listOrder'" label="排序" width="60" align="center"></el-table-column>
+      <el-table-column prop="status" v-col="'status'" label="角色状态" width="100" align="center">
+        <template #default="scope">
+          <el-tag type="success" size="small" v-if="scope.row.status === 1">启用</el-tag>
+          <el-tag type="info" size="small" v-else>禁用</el-tag>
+        </template>
+      </el-table-column>
+      <el-table-column prop="createdAt" v-col="'createdAt'" label="创建时间" width="170" align="center"></el-table-column>
+      <el-table-column label="操作" width="220" v-col="'handle'" align="center" fixed="right">
+        <template #default="scope">
+          <el-button size="small" type="text" @click="onOpenEditRole(scope.row)" v-auth="'edit'">修改</el-button>
+          <el-button size="small" text type="info" @click="onRowDel(scope.row)" v-auth="'del'">删除</el-button>
+          <el-button size="small" text type="success" @click="permission(scope.row)" v-auth="'role-premission'">角色权限</el-button>
+          <el-button size="small" text type="info" @click="dataPermission(scope.row)" v-auth="'data-premission'">数据权限</el-button>
+          <!-- <el-dropdown size="small">
               <el-button type="text" size="small" style="margin-top:1px;margin-left:10px">更多
                 <el-icon>
                   <ele-ArrowDown />
@@ -64,15 +63,15 @@
                 </el-dropdown-menu>
               </template>
             </el-dropdown> -->
-          </template>
-        </el-table-column>
-      </el-table>
-      <!-- <pagination v-show="tableData.total>0" :total="tableData.total" v-model:page="tableData.param.pageNum" v-model:limit="tableData.param.pageSize" @pagination="roleList" /> -->
-    </el-card>
+        </template>
+      </el-table-column>
+    </el-table>
+    <!-- <pagination v-show="tableData.total>0" :total="tableData.total" v-model:page="tableData.param.pageNum" v-model:limit="tableData.param.pageSize" @pagination="roleList" /> -->
+
     <EditRole ref="editRoleRef" @getList="roleList" :list="tableData.data" />
     <permissionVue ref="permissionRef" />
     <EditPer ref="dataPermissionRef" :dept-data="deptData" />
-  </div>
+  </el-card>
 </template>
 
 <script lang="ts">

+ 11 - 15
src/views/system/manage/user/index.vue

@@ -1,6 +1,6 @@
 <template>
 	<div class="page flex-row gap-4">
-		<el-card shadow="nover" style="width:270px">
+		<el-card shadow="nover" style="width:260px">
 			<el-scrollbar>
 				<el-input :prefix-icon="search" v-model="filterText" placeholder="请输入组织名称" clearable style="width: 100%;" />
 				<el-tree ref="treeRef" class="filter-tree mt-4" :data="deptData" :props="deptProps" default-expand-all :filter-node-method="deptFilterNode" @node-click="handleNodeClick">
@@ -13,23 +13,19 @@
 			</el-scrollbar>
 		</el-card>
 		<el-card shadow="nover" class="flex1">
-			<el-form :model="tableData.param" ref="queryRef" inline label-width="68px">
-				<el-form-item label="关键字" prop="keyWords">
-					<el-input v-model="tableData.param.keyWords" placeholder="请输入用户名或姓名" clearable style="width: 240px" @keyup.enter.native="userList" />
+			<el-form :model="tableData.param" ref="queryRef" inline>
+				<el-form-item label="" prop="keyWords">
+					<el-input v-model="tableData.param.keyWords" placeholder="用户名或姓名搜索" clearable style="width: 165px" @keyup.enter.native="userList" />
 				</el-form-item>
-				<!--							<el-form-item label="手机号码" prop="mobile">-->
-				<!--								<el-input v-model="tableData.param.mobile" placeholder="请输入手机号码" clearable style="width: 240px" @keyup.enter.native="userList" />-->
-				<!--							</el-form-item>-->
-				<el-form-item label="状态" prop="status" style="width: 200px;">
-					<el-select v-model="tableData.param.status" placeholder="用户状态" style="width: 240px">
+				<el-form-item label="" prop="status">
+					<el-select v-model="tableData.param.status" placeholder="用户状态" style="width: 80px">
 						<el-option label="全部" :value="-1" />
 						<el-option label="启用" :value="1" />
 						<el-option label="禁用" :value="0" />
-						<!-- <el-option label="未验证" :value="2" /> -->
 					</el-select>
 				</el-form-item>
-				<el-form-item label="创建时间" prop="dateRange">
-					<el-date-picker v-model="tableData.param.dateRange" style="width: 240px" value-format="YYYY-MM-DD" type="daterange" range-separator="-" start-placeholder="开始日期" end-placeholder="结束日期"></el-date-picker>
+				<el-form-item label="" prop="dateRange">
+					<el-date-picker v-model="tableData.param.dateRange" style="width: 220px" value-format="YYYY-MM-DD" type="daterange" range-separator="-" start-placeholder="创建时间" end-placeholder="时间范围"></el-date-picker>
 				</el-form-item>
 				<el-form-item>
 					<el-button type="primary" class="ml10" @click="userList">
@@ -38,12 +34,12 @@
 						</el-icon>
 						查询
 					</el-button>
-					<el-button @click="resetQuery(queryRef)">
+					<!-- <el-button @click="resetQuery(queryRef)">
 						<el-icon>
 							<ele-Refresh />
 						</el-icon>
 						重置
-					</el-button>
+					</el-button> -->
 					<el-button type="primary" class="ml10" @click="onOpenAddUser" v-auth="'add'">
 						<el-icon>
 							<ele-FolderAdd />
@@ -73,7 +69,7 @@
 					</template>
 				</el-table-column>
 				<el-table-column prop="createdAt" label="创建时间" width="180" v-col="'createdAt'" align="center"></el-table-column>
-				<el-table-column label="操作" width="180" align="center" v-col="'handle'" fixed="right">
+				<el-table-column label="操作" width="130" align="center" v-col="'handle'" fixed="right">
 					<template #default="scope">
 						<!-- <el-button size="small" text type="warning" @click="onOpenEditUser(scope.row)" v-auths="['edit','del']">修改</el-button>
                 <el-button size="small" text type="warning" @click="onOpenEditUser(scope.row)" v-auth-all="['edit','del']">修改</el-button> -->

+ 43 - 51
src/views/system/monitor/lastLinesLog/index.vue

@@ -1,43 +1,37 @@
 <template>
-  <div class="page">
-    <el-card shadow="nover">
-      <el-tabs v-model="activeName" class="demo-tabs" @tab-click="handleClick">
-        <el-tab-pane label="服务日志" name="1"> </el-tab-pane>
-        <el-tab-pane label="数据库日志" name="2"> </el-tab-pane>
-        <el-tab-pane label="运行日志" name="3"> </el-tab-pane>
-      </el-tabs>
-      <!-- 日志列表 -->
-      <el-table ref="table" v-if="activeName === '1'" :data="tableData" style="width: 100%" row-key="id" v-loading="loading">
-        <el-table-column prop="name" label="文件名" show-overflow-tooltip></el-table-column>
-        <el-table-column prop="size" label="大小" show-overflow-tooltip></el-table-column>
-        <el-table-column prop="changeAt" label="修改时间" min-width="100" align="center"></el-table-column>
-        <el-table-column label="操作" width="200" align="center">
-          <template #default="scope">
-            <el-button size="small" text type="primary" v-if="!scope.row.folderName" @click="view(scope.row)">详情
-            </el-button>
-            <el-button size="small" text type="warning" v-auth="'download'" @click="down(scope.row)">下载</el-button>
-            <el-button size="small" text type="info" v-auth="'del'" @click="onRowDel(scope.row)">删除</el-button>
-          </template>
-        </el-table-column>
-      </el-table>
-
-      <!-- 数据库日志 -->
-      <el-table v-else-if="activeName === '2'" :data="tableData" style="width: 100%" row-key="id" v-loading="loading">
-        <el-table-column prop="name" label="文件名" show-overflow-tooltip></el-table-column>
-        <el-table-column prop="size" label="大小" show-overflow-tooltip></el-table-column>
-        <el-table-column prop="changeAt" label="修改时间" min-width="100" align="center"></el-table-column>
-        <el-table-column label="操作" width="200" align="center">
-          <template #default="scope">
-            <el-button size="small" text type="primary" v-if="!scope.row.folderName" @click="view(scope.row)">详情
-            </el-button>
-            <el-button size="small" text type="warning" v-auth="'download'" @click="down(scope.row)">下载</el-button>
-            <el-button size="small" text type="info" v-auth="'del'" @click="onRowDel(scope.row)">删除</el-button>
-          </template>
-        </el-table-column>
-      </el-table>
-      <template v-else>
-
-        <!-- 运行日志 -->
+  <div class="page bg page-full padding border Ipt-2">
+    <el-tabs v-model="activeName" @tab-click="handleClick">
+      <el-tab-pane label="服务日志" name="1">
+        <el-table ref="table" :data="tableData" style="width: 100%" row-key="id" v-loading="loading">
+          <el-table-column prop="name" label="文件名" show-overflow-tooltip></el-table-column>
+          <el-table-column prop="size" label="大小" show-overflow-tooltip></el-table-column>
+          <el-table-column prop="changeAt" label="修改时间" min-width="100" align="center"></el-table-column>
+          <el-table-column label="操作" width="200" align="center">
+            <template #default="scope">
+              <el-button size="small" text type="primary" v-if="!scope.row.folderName" @click="view(scope.row)">详情
+              </el-button>
+              <el-button size="small" text type="warning" v-auth="'download'" @click="down(scope.row)">下载</el-button>
+              <el-button size="small" text type="info" v-auth="'del'" @click="onRowDel(scope.row)">删除</el-button>
+            </template>
+          </el-table-column>
+        </el-table>
+      </el-tab-pane>
+      <el-tab-pane label="数据库日志" name="2">
+        <el-table :data="tableData" style="width: 100%" row-key="id" v-loading="loading">
+          <el-table-column prop="name" label="文件名" show-overflow-tooltip></el-table-column>
+          <el-table-column prop="size" label="大小" show-overflow-tooltip></el-table-column>
+          <el-table-column prop="changeAt" label="修改时间" min-width="100" align="center"></el-table-column>
+          <el-table-column label="操作" width="200" align="center">
+            <template #default="scope">
+              <el-button size="small" text type="primary" v-if="!scope.row.folderName" @click="view(scope.row)">详情
+              </el-button>
+              <el-button size="small" text type="warning" v-auth="'download'" @click="down(scope.row)">下载</el-button>
+              <el-button size="small" text type="info" v-auth="'del'" @click="onRowDel(scope.row)">删除</el-button>
+            </template>
+          </el-table-column>
+        </el-table>
+      </el-tab-pane>
+      <el-tab-pane label="运行日志" name="3">
         <div v-for="line in topMsg" :key="line" class="error-line">{{ line }}</div>
         <div v-if="runButtonShow" v-loading="runLoading" v-for="line in runMessage" :key="line" class="error-line">{{ line }}</div>
         <div v-else class="error-line">暂无数据</div>
@@ -45,17 +39,16 @@
           <el-button size="small" text type="warning" v-auth="'download'" @click="down">下载</el-button>
           <el-button size="small" text type="danger" v-auth="'del'" @click="onRowDel">删除</el-button>
         </div>
-      </template>
-
-      <el-dialog v-model="dialogVisible" title="查看详情">
-        <div v-for="line in topMsg" :key="line" class="error-line">{{ line }}</div>
-        <div v-for="line in errorMessage" :key="line" class="error-line">{{ line }}</div>
-      </el-dialog>
-      <el-dialog v-model="dialogVisible" title="查看详情">
-        <div v-for="line in topMsg" :key="line" class="error-line">{{ line }}</div>
-        <div v-for="line in errorMessage" :key="line" class="error-line">{{ line }}</div>
-      </el-dialog>
-    </el-card>
+      </el-tab-pane>
+    </el-tabs>
+    <el-dialog v-model="dialogVisible" title="查看详情">
+      <div v-for="line in topMsg" :key="line" class="error-line">{{ line }}</div>
+      <div v-for="line in errorMessage" :key="line" class="error-line">{{ line }}</div>
+    </el-dialog>
+    <el-dialog v-model="dialogVisible" title="查看详情">
+      <div v-for="line in topMsg" :key="line" class="error-line">{{ line }}</div>
+      <div v-for="line in errorMessage" :key="line" class="error-line">{{ line }}</div>
+    </el-dialog>
   </div>
 </template>
 
@@ -194,5 +187,4 @@ const handleClick = (tab: any, event: Event) => {
   height: calc(100vh - 110px);
   overflow-y: auto;
 }
-
 </style>

+ 65 - 77
src/views/system/monitor/loginLog/index.vue

@@ -1,85 +1,73 @@
 <template>
-  <div class="page">
-    <el-card shadow="nover">
-      <el-form :model="tableData.param" ref="queryRef" inline label-width="68px">
-        <el-form-item label="登录IP" prop="ipaddr">
-          <el-input v-model="tableData.param.ipaddr" placeholder="请输入登录地址" clearable style="width: 180px" @keyup.enter.native="dataList" />
-        </el-form-item>
+  <el-card shadow="nover" class="page">
+    <el-form :model="tableData.param" ref="queryRef" inline>
+      <el-form-item label="" prop="ipaddr">
+        <el-input v-model="tableData.param.ipaddr" placeholder="登录IP" clearable style="width: 150px" @keyup.enter="dataList" />
+      </el-form-item>
 
-        <el-form-item label="登录地点" prop="loginLocation">
-          <el-input v-model="tableData.param.loginLocation" placeholder="请输入登录地点" clearable style="width: 180px" @keyup.enter.native="dataList" />
-        </el-form-item>
+      <el-form-item label="" prop="loginLocation">
+        <el-input v-model="tableData.param.loginLocation" placeholder="登录地点" clearable style="width: 150px" @keyup.enter="dataList" />
+      </el-form-item>
 
-        <!-- <el-form-item label="用户名称" prop="userName">
-            <el-input v-model="tableData.param.userName" placeholder="请输入用户名称" clearable style="width: 180px;" @keyup.enter.native="dataList" />
-          </el-form-item> -->
+      <el-form-item label="" prop="status">
+        <el-select v-model="tableData.param.status" placeholder="状态" style="width: 75px">
+          <el-option label="全部" :value="-1" />
+          <el-option label="成功" :value="1" />
+          <el-option label="失败" :value="0" />
+        </el-select>
+      </el-form-item>
 
-        <el-form-item label="状态" prop="status">
-          <el-select v-model="tableData.param.status" placeholder="登录状态" style="width: 180px">
-            <el-option label="全部" :value="-1" />
-            <el-option label="成功" :value="1" />
-            <el-option label="失败" :value="0" />
-          </el-select>
-        </el-form-item>
+      <el-form-item label="" prop="dateRange">
+        <el-date-picker v-model="tableData.param.dateRange" style="width: 220px" value-format="YYYY-MM-DD" type="daterange" range-separator="-" start-placeholder="登录时间" end-placeholder="结束时间"></el-date-picker>
+      </el-form-item>
+      <el-form-item>
+        <el-button type="primary" @click="dataList">
+          <el-icon>
+            <ele-Search />
+          </el-icon>
+          查询
+        </el-button>
+        <!-- <el-button @click="resetQuery(queryRef)">
+          <el-icon>
+            <ele-Refresh />
+          </el-icon>
+          重置
+        </el-button> -->
+        <el-button type="info" @click="onRowDel(null)" v-auth="'del'">
+          <el-icon>
+            <ele-Delete />
+          </el-icon>
+          删除日志
+        </el-button>
 
-        <el-form-item label="登录时间" prop="dateRange">
-          <el-date-picker v-model="tableData.param.dateRange" style="width: 240px" value-format="YYYY-MM-DD" type="daterange" range-separator="-" start-placeholder="开始日期" end-placeholder="结束日期"></el-date-picker>
-        </el-form-item>
-        <el-form-item>
-          <el-button type="primary" class="ml10" @click="dataList">
-            <el-icon>
-              <ele-Search />
-            </el-icon>
-            查询
-          </el-button>
-          <el-button @click="resetQuery(queryRef)">
-            <el-icon>
-              <ele-Refresh />
-            </el-icon>
-            重置
-          </el-button>
-          <el-button type="info" class="ml10" @click="onRowDel(null)" v-auth="'del'">
-            <el-icon>
-              <ele-Delete />
-            </el-icon>
-            删除日志
-          </el-button>
-
-          <el-button type="primary" class="ml10" @click="onRowExport()">
-            <el-icon>
-              <ele-Download />
-            </el-icon>
-            导出日志
-          </el-button>
-          <!--<el-button type="info" class="ml10" @click="onRowClear()">
-              <el-icon>
-                <ele-Delete />
-              </el-icon>
-              清空日志
-            </el-button> -->
-        </el-form-item>
-      </el-form>
-      <el-table :data="tableData.data" style="width: 100%" @selection-change="handleSelectionChange" v-loading="tableData.loading">
-        <el-table-column type="selection" width="55" align="center" />
-        <el-table-column label="编号" align="center" width="100" prop="infoId" />
-        <el-table-column label="登录名称" align="center" prop="loginName" />
-        <el-table-column label="登录地址" align="center" prop="ipaddr" width="150" show-overflow-tooltip />
-        <el-table-column label="登录地点" v-col="'loginLocation'" align="center" prop="loginLocation" show-overflow-tooltip />
-        <el-table-column label="浏览器" align="center" prop="browser" />
-        <el-table-column label="操作系统" show-overflow-tooltip align="center" prop="os" />
-        <el-table-column label="登录状态" v-col="'status'" align="center" prop="status" width="90">
-          <template #default="scope">
-            <el-tag type="success" size="small" v-if="scope.row.status === 1">成功</el-tag>
-            <el-tag type="info" size="small" v-else>失败</el-tag>
-          </template>
-        </el-table-column>
-        <el-table-column label="操作信息" v-col="'msg'" show-overflow-tooltip prop="msg" align="center" />
-        <el-table-column label="登录日期" v-col="'loginTime'" align="center" prop="loginTime" width="180" />
-        <el-table-column label="登录模块" v-col="'module'" align="center" show-overflow-tooltip prop="module" width="120"></el-table-column>
-      </el-table>
-      <pagination v-show="tableData.total > 0" :total="tableData.total" v-model:page="tableData.param.pageNum" v-model:limit="tableData.param.pageSize" @pagination="dataList" />
-    </el-card>
-  </div>
+        <el-button type="primary" @click="onRowExport()">
+          <el-icon>
+            <ele-Download />
+          </el-icon>
+          导出日志
+        </el-button>
+      </el-form-item>
+    </el-form>
+    <el-table :data="tableData.data" style="width: 100%" @selection-change="handleSelectionChange" v-loading="tableData.loading">
+      <el-table-column type="selection" width="50" align="center" />
+      <el-table-column label="编号" align="center" width="90" prop="infoId" />
+      <el-table-column label="登录名称" align="center" prop="loginName" />
+      <el-table-column label="登录地址" align="center" prop="ipaddr" width="145" show-overflow-tooltip />
+      <el-table-column label="登录地点" v-col="'loginLocation'" align="center" prop="loginLocation" show-overflow-tooltip />
+      <el-table-column label="浏览器" align="center" prop="browser" />
+      <el-table-column label="操作系统" show-overflow-tooltip align="center" prop="os" />
+      <el-table-column label="登录状态" v-col="'status'" align="center" prop="status" width="90">
+        <template #default="scope">
+          <el-tag type="success" size="small" v-if="scope.row.status === 1">成功</el-tag>
+          <el-tag type="info" size="small" v-else>失败</el-tag>
+        </template>
+      </el-table-column>
+      <el-table-column label="操作信息" v-col="'msg'" show-overflow-tooltip prop="msg" align="center" />
+      <el-table-column label="登录日期" v-col="'loginTime'" align="center" prop="loginTime" width="160" />
+      <el-table-column label="登录模块" v-col="'module'" align="center" show-overflow-tooltip prop="module" width="120"></el-table-column>
+    </el-table>
+    <pagination v-show="tableData.total > 0" :total="tableData.total" v-model:page="tableData.param.pageNum" v-model:limit="tableData.param.pageSize" @pagination="dataList" />
+  </el-card>
 </template>
 
 <script lang="ts">

+ 85 - 87
src/views/system/monitor/operLog/index.vue

@@ -1,98 +1,96 @@
 <template>
-  <div class="page">
-    <el-card shadow="nover">
-      <el-form :model="tableData.param" ref="queryRef" inline label-width="68px">
-        <el-form-item label="系统模块" prop="title">
-          <el-input v-model="tableData.param.title" placeholder="请输入系统模块" clearable style="width: 180px" @keyup.enter="dataList" />
-        </el-form-item>
+  <el-card shadow="nover" class="page">
+    <el-form :model="tableData.param" ref="queryRef" inline>
+      <el-form-item label="" prop="title">
+        <el-input v-model="tableData.param.title" placeholder="系统模块" clearable style="width: 220px" @keyup.enter="dataList" />
+      </el-form-item>
 
-        <el-form-item label="操作人员" prop="operName">
-          <el-input v-model="tableData.param.operName" placeholder="请输入操作人员" clearable style="width: 180px" @keyup.enter="dataList" />
-        </el-form-item>
+      <el-form-item label="" prop="operName">
+        <el-input v-model="tableData.param.operName" placeholder="操作人员" clearable style="width: 220px" @keyup.enter="dataList" />
+      </el-form-item>
 
-        <el-form-item label="业务类型" prop="businessType">
-          <el-select v-model="tableData.param.businessType" placeholder="请选择类型" clearable style="width: 180px">
-            <el-option label="新增" :value="1" />
-            <el-option label="修改" :value="2" />
-            <el-option label="删除" :value="3" />
-            <el-option label="其它" :value="0" />
-          </el-select>
-        </el-form-item>
+      <el-form-item label="" prop="businessType">
+        <el-select v-model="tableData.param.businessType" placeholder="业务类型" clearable style="width: 120px">
+          <el-option label="新增" :value="1" />
+          <el-option label="修改" :value="2" />
+          <el-option label="删除" :value="3" />
+          <el-option label="其它" :value="0" />
+        </el-select>
+      </el-form-item>
 
-        <el-form-item label="状态" prop="status">
-          <el-select v-model="tableData.param.status" placeholder="请选择状态" style="width: 180px">
-            <el-option label="全部" :value="-1" />
-            <el-option label="正常" :value="1" />
-            <el-option label="异常" :value="0" />
-          </el-select>
-        </el-form-item>
-        <el-form-item>
-          <el-button type="primary" class="ml10" @click="dataList">
-            <el-icon>
-              <ele-Search />
-            </el-icon>
-            查询
-          </el-button>
-          <el-button @click="resetQuery(queryRef)">
-            <el-icon>
-              <ele-Refresh />
-            </el-icon>
-            重置
-          </el-button>
-          <el-button type="info" class="ml10" @click="onRowDel(null)" v-auth="'del'">
-            <el-icon>
-              <ele-Delete />
-            </el-icon>
-            删除日志
-          </el-button>
-          <!-- <el-button type="info" class="ml10" @click="onRowClear()">
+      <el-form-item label="" prop="status">
+        <el-select v-model="tableData.param.status" placeholder="请选择状态" style="width: 100px">
+          <el-option label="全部状态" :value="-1" />
+          <el-option label="正常" :value="1" />
+          <el-option label="异常" :value="0" />
+        </el-select>
+      </el-form-item>
+      <el-form-item>
+        <el-button type="primary" class="ml10" @click="dataList">
+          <el-icon>
+            <ele-Search />
+          </el-icon>
+          查询
+        </el-button>
+        <!-- <el-button @click="resetQuery(queryRef)">
+          <el-icon>
+            <ele-Refresh />
+          </el-icon>
+          重置
+        </el-button> -->
+        <el-button type="info" class="ml10" @click="onRowDel(null)" v-auth="'del'">
+          <el-icon>
+            <ele-Delete />
+          </el-icon>
+          删除日志
+        </el-button>
+        <!-- <el-button type="info" class="ml10" @click="onRowClear()">
               <el-icon>
                 <ele-Delete />
               </el-icon>
               清空日志
             </el-button> -->
-        </el-form-item>
-      </el-form>
-      <el-table :data="tableData.data" style="width: 100%" @selection-change="handleSelectionChange" v-loading="tableData.loading">
-        <el-table-column type="selection" width="55" align="center" />
-        <el-table-column label="编号" align="center" width="100" prop="operId" />
-        <el-table-column label="系统模块" align="center" prop="title" min-width="120" show-overflow-tooltip />
-        <el-table-column label="业务类型" align="center" prop="businessType" width="130">
-          <template #default="scope">
-            <span size="small" v-if="scope.row.businessType === 0">其他</span>
-            <span size="small" v-else-if="scope.row.businessType === 1">新增</span>
-            <span size="small" v-else-if="scope.row.businessType === 2">修改</span>
-            <span size="small" v-else-if="scope.row.businessType === 3">删除</span>
-          </template>
-        </el-table-column>
-        <el-table-column label="操作类型" v-col="'operatorType'" align="center" prop="operatorType" width="130">
-          <template #default="scope">
-            <span size="small" v-if="scope.row.operatorType === 0">其他</span>
-            <span size="small" v-else-if="scope.row.operatorType === 1">后台用户</span>
-            <span size="small" v-else-if="scope.row.operatorType === 2">手机端用户</span>
-          </template>
-        </el-table-column>
-        <el-table-column label="操作人员" v-col="'operName'" align="center" prop="operName" show-overflow-tooltip />
-        <el-table-column label="组织名称" align="center" prop="deptName" />
-        <el-table-column label="主机" show-overflow-tooltip align="center" prop="operIp" width="160" />
-        <el-table-column label="操作地点" v-col="'operLocation'" show-overflow-tooltip align="center" prop="operLocation" width="130" />
-        <el-table-column label="操作时间" v-col="'operTime'" align="center" prop="operTime" width="160" />
-        <el-table-column label="操作状态" v-col="'status'" align="center" prop="status" width="100">
-          <template #default="scope">
-            <el-tag type="success" size="small" v-if="scope.row.status === 1">正常</el-tag>
-            <el-tag type="warning" size="small" v-else-if="scope.row.status === 0">异常</el-tag>
-            <el-tag type="info" size="small" v-else>-</el-tag>
-          </template>
-        </el-table-column>
-        <!-- <el-table-column label="操作信息" show-overflow-tooltip prop="msg" /> -->
-        <el-table-column label="操作" v-col="'handle'" width="80" align="center" fixed="right">
-          <template #default="scope">
-            <el-button size="small" type="text" @click="onOpenDetail(scope.row)" v-auth="'detail'">详细</el-button>
-          </template>
-        </el-table-column>
-      </el-table>
-      <pagination v-show="tableData.total > 0" :total="tableData.total" v-model:page="tableData.param.pageNum" v-model:limit="tableData.param.pageSize" @pagination="dataList" />
-    </el-card>
+      </el-form-item>
+    </el-form>
+    <el-table :data="tableData.data" style="width: 100%" @selection-change="handleSelectionChange" v-loading="tableData.loading">
+      <el-table-column type="selection" width="55" align="center" fixed="left" />
+      <el-table-column label="编号" align="center" width="100" prop="operId" />
+      <el-table-column label="系统模块" align="center" prop="title" min-width="120" show-overflow-tooltip />
+      <el-table-column label="业务类型" align="center" prop="businessType" width="130">
+        <template #default="scope">
+          <span size="small" v-if="scope.row.businessType === 0">其他</span>
+          <span size="small" v-else-if="scope.row.businessType === 1">新增</span>
+          <span size="small" v-else-if="scope.row.businessType === 2">修改</span>
+          <span size="small" v-else-if="scope.row.businessType === 3">删除</span>
+        </template>
+      </el-table-column>
+      <el-table-column label="操作类型" v-col="'operatorType'" align="center" prop="operatorType" width="130">
+        <template #default="scope">
+          <span size="small" v-if="scope.row.operatorType === 0">其他</span>
+          <span size="small" v-else-if="scope.row.operatorType === 1">后台用户</span>
+          <span size="small" v-else-if="scope.row.operatorType === 2">手机端用户</span>
+        </template>
+      </el-table-column>
+      <el-table-column label="操作人员" v-col="'operName'" align="center" prop="operName" show-overflow-tooltip />
+      <el-table-column label="组织名称" align="center" prop="deptName" />
+      <el-table-column label="主机" show-overflow-tooltip align="center" prop="operIp" width="160" />
+      <el-table-column label="操作地点" v-col="'operLocation'" show-overflow-tooltip align="center" prop="operLocation" width="130" />
+      <el-table-column label="操作时间" v-col="'operTime'" align="center" prop="operTime" width="160" />
+      <el-table-column label="操作状态" v-col="'status'" align="center" prop="status" width="100">
+        <template #default="scope">
+          <el-tag type="success" size="small" v-if="scope.row.status === 1">正常</el-tag>
+          <el-tag type="warning" size="small" v-else-if="scope.row.status === 0">异常</el-tag>
+          <el-tag type="info" size="small" v-else>-</el-tag>
+        </template>
+      </el-table-column>
+      <!-- <el-table-column label="操作信息" show-overflow-tooltip prop="msg" /> -->
+      <el-table-column label="操作" v-col="'handle'" width="80" align="center" fixed="right">
+        <template #default="scope">
+          <el-button size="small" type="text" @click="onOpenDetail(scope.row)" v-auth="'detail'">详细</el-button>
+        </template>
+      </el-table-column>
+    </el-table>
+    <pagination v-show="tableData.total > 0" :total="tableData.total" v-model:page="tableData.param.pageNum" v-model:limit="tableData.param.pageSize" @pagination="dataList" />
 
     <el-dialog :title="currentRow.title + '详情'" v-model="dialogVisible" width="550px">
       <el-form :model="currentRow" ref="formRef" label-width="90px">
@@ -128,7 +126,7 @@
         </span>
       </template>
     </el-dialog>
-  </div>
+  </el-card>
 </template>
 
 <script lang="ts">

+ 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>

+ 1 - 1
src/views/system/monitor/server/index.vue

@@ -1,5 +1,5 @@
 <template>
-  <div class="" v-loading="loading">
+  <div v-loading="loading">
     <el-row :gutter="15">
       <el-col :xs="24" :sm="12" :md="8" class="mb-4">
         <el-card shadow="nover" class="box-card-meter">

Certains fichiers n'ont pas été affichés car il y a eu trop de fichiers modifiés dans ce diff