Ver Fonte

增加通道管理和模板管理的列表页面

yanglzh há 2 anos atrás
pai
commit
6023ea699e

+ 3 - 1
.env

@@ -16,4 +16,6 @@ VITE_ASSESS_URL = '/base-api/assess/v1'
 # 大屏前端
 VITE_SCREEN_URL = '/plugin/screen/'
 # 组态图前端
-VITE_TOPO_URL = '/plugin/topo/'
+VITE_TOPO_URL = '/plugin/topo/'
+# modbus服务
+VITE_MODBUS_API = 'http://101.200.198.249:8188'

+ 23 - 0
src/api/device/modbus.ts

@@ -0,0 +1,23 @@
+import { get, post } from '/@/utils/request_modbus';
+
+export default {
+  channel: {
+    getList: (params: object) => get('/device', params),
+    addDevice: (data: object) => post('/device/add', data),
+    deleteDevice: (data: object) => post('/device/delete', data),
+    editDevice: (data: object) => post('/device/edit', data),
+  },
+  template: {
+    getList: (params: object) => get('/template', params),
+    getDataId: (params: object) => get('/dict/getdataid', params),
+    addTemplate: (data: object) => post('/template/add', data),
+    deleteTemplate: (data: object) => post('/template/delete', data),
+    editTemplate: (data: object) => post('/template/edit', data),
+    importFile: (data: object, config: object) => post('/data_area/import', data, config),
+    exportFile: (params: object) => get('/data_area/import', params, {
+      headers: {
+        responseType: 'blob'
+      }
+    })
+  }
+}

+ 51 - 0
src/hooks/useCommonModbus.ts

@@ -0,0 +1,51 @@
+import { reactive, ref } from 'vue'
+
+export default function () {
+  const statusParams = reactive({
+    status: 1
+  })
+
+  return { statusParams }
+}
+
+export function useSearch<T>(api: any, resKey: string, expandParams?: any) {
+
+  //  <pagination v-if="params.total" :total="params.total" v-model:page="params.pageNum" v-model:limit="params.pageSize" @pagination="getList()" />
+
+  // import api from '/@/api/system';
+  // import { ApiRow } from '/@/api/model/system/menu';
+  // import { useSearch } from '/@/hooks/useCommon';
+
+  // const { params, tableData, getList } = useSearch<ApiRow[]>(api.api.getList, 'Info', { name: '', address: '' });
+  // getList() // 获取列表数据
+
+  interface SearchParams {
+    page: number;
+    size: number;
+    total: number;
+    [key: string]: any;
+  }
+
+  const params = reactive<SearchParams>({
+    page: 1,
+    size: 10,
+    total: 0,
+    ...expandParams
+  })
+
+  const loading = ref(false)
+
+  const tableData = ref<T[] | any[]>([])
+
+  const getList = async (page?: number) => {
+    page && (params.page = page);
+    tableData.value = [];
+    loading.value = true;
+    params.total = 0;
+    let res = await api(params).finally(() => loading.value = false)
+    tableData.value = (resKey ? (res[resKey]) : (res)) || [];
+    params.total = res.Total;
+  };
+
+  return { params, tableData, getList, loading }
+}

+ 137 - 0
src/utils/request_modbus.ts

@@ -0,0 +1,137 @@
+import axios from 'axios';
+import { ElMessage, ElMessageBox } from 'element-plus';
+import { Session } from '/@/utils/storage';
+
+// 配置新建一个 axios 实例
+const service = axios.create({
+	baseURL: import.meta.env.VITE_MODBUS_API,
+	timeout: 50000,
+	headers: { 'Content-Type': 'application/json' },
+});
+
+// 添加请求拦截器
+service.interceptors.request.use(
+	(config) => {
+		// 在发送请求之前做些什么 token
+		if (Session.get('token')) {
+			(<any>config.headers).common['Authorization'] = `Bearer ${Session.get('token')}`;
+		}
+		return config;
+	},
+	(error) => {
+		// 对请求错误做些什么
+		return Promise.reject(error);
+	}
+);
+
+// 添加响应拦截器
+service.interceptors.response.use(
+	(response) => {
+		// 对响应数据做点什么
+		const res = response.data;
+		const code = response.data.code
+		if (code === 401) {
+			ElMessageBox.alert('登录状态已过期,请重新登录', '提示',
+				{ confirmButtonText: '确定', showCancelButton: false, closeOnHashChange: false, closeOnPressEscape: false, closeOnClickModal: false, showClose: false })
+				.then(() => {
+					Session.clear(); // 清除浏览器全部临时缓存
+					window.location.href = '/'; // 去登录页
+				})
+				.catch(() => { });
+		} else if (code === undefined && res.message === undefined) { // 可能是下载文件
+			return response
+		} else if (code !== 0) {
+			ElMessage.error(res.message)
+			return Promise.reject(new Error(res.message))
+		} else {
+			// 分页的数据
+			if (res.data?.Total !== undefined) {
+				return {
+					list: res.data.Data,
+					total: res.data.Total,
+					page: res.data.currentPage,
+					...res.data,
+				}
+			}
+			// if (res.data?.Data) {
+			// 	return res.data.Data
+			// }
+			if (res.data?.Info && res.data?.Data) { // currentUser接口
+				return res.data
+			}
+			if (res.data?.Data === undefined) {
+				return res.data
+			}
+			return res.data.Data
+		}
+	},
+	(error) => {
+		// 对响应错误做点什么
+		if (error.message.indexOf('timeout') != -1) {
+			ElMessage.error('网络超时');
+		} else if (error.message == 'Network Error') {
+			ElMessage.error('网络连接错误');
+		} else {
+			if (error.response.data) ElMessage.error(error.response.statusText);
+			else ElMessage.error('接口路径找不到');
+		}
+		return Promise.reject(error);
+	}
+);
+
+// 导出 axios 实例
+export default service;
+
+export function get(url: string, params?: any, config?: any): any {
+	return service({
+		url,
+		method: "get",
+		...config,
+		params
+	})
+}
+
+export function post(url: string, data?: any, config?: any): any {
+	return service({
+		url,
+		method: "post",
+		...config,
+		data
+	})
+}
+
+export function put(url: string, data?: any): any {
+	return service({
+		url,
+		method: "put",
+		data
+	})
+}
+export function del(url: string, data?: any): any {
+	return service({
+		url,
+		method: "delete",
+		data
+	})
+}
+
+
+export function file(url: string, params?: any, method: 'get' | 'post' = 'get'): any {
+	if (method === 'get') {
+		return service({
+			url,
+			method,
+			params,
+			timeout: 30000,
+			responseType: 'arraybuffer',
+		});
+	} else {
+		return service({
+			url,
+			method,
+			timeout: 100000,
+			data: params,
+			responseType: 'blob',
+		});
+	}
+}

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

@@ -0,0 +1,190 @@
+<template>
+	<div class="system-edit-dic-container">
+		<el-dialog :title="(ruleForm.id!==0?'修改':'添加')+'设备'" v-model="isShowDialog" width="769px">
+			<el-form :model="ruleForm" ref="formRef" :rules="rules" size="default" label-width="110px">
+       <el-form-item label="设备标识" prop="key">
+          <el-input v-model="ruleForm.key" placeholder="请输入设备标识" :disabled="ruleForm.id" />
+        </el-form-item>
+        <el-form-item label="设备名称" prop="name">
+          <el-input v-model="ruleForm.name" placeholder="请输入设备名称" />
+        </el-form-item>
+
+        
+
+           <el-form-item label="所属产品" prop="productId">
+       
+                <el-select v-model="ruleForm.productId" placeholder="请选择所属产品" class="w100">
+              <el-option
+                v-for="item in productData"
+                :key="item.id"
+                :label="item.name"
+                :value="item.id"
+              />
+            </el-select>
+            </el-form-item> 
+
+         <el-form-item label="所属部门" prop="deptId">
+              <el-cascader :options="deptData" :props="{ checkStrictly: true,emitPath: false, value: 'deptId', label: 'deptName' }" placeholder="请选择所属部门" clearable class="w100" v-model="ruleForm.deptId">
+                <template #default="{ node, data }">
+                  <span>{{ data.deptName }}</span>
+                  <span v-if="!node.isLeaf"> ({{ data.children.length }}) </span>
+                </template>
+              </el-cascader>
+            </el-form-item> 
+
+           
+        
+        <el-form-item label="设备证书" prop="certificate">
+          <el-input v-model="ruleForm.certificate" placeholder="请输入设备证书" />
+        </el-form-item>
+
+        <el-form-item label="设备秘钥" prop="secureKey">
+          <el-input v-model="ruleForm.secureKey" placeholder="请输入设备秘钥" />
+        </el-form-item>
+
+         <el-form-item label="固件版本号" prop="version">
+          <el-input v-model="ruleForm.version" placeholder="请输入固件版本号" />
+        </el-form-item>
+      
+  
+        <el-form-item label="备注" prop="desc">
+          <el-input v-model="ruleForm.desc" type="textarea" placeholder="请输入内容"></el-input>
+        </el-form-item>
+			</el-form>
+			<template #footer>
+				<span class="dialog-footer">
+					<el-button @click="onCancel" size="default">取 消</el-button>
+					<el-button type="primary" @click="onSubmit" size="default">{{ruleForm.id!==0?'修 改':'添 加'}}</el-button>
+				</span>
+			</template>
+		</el-dialog>
+	</div>
+</template>
+
+<script lang="ts">
+import { reactive, toRefs, defineComponent,ref, unref } from 'vue';
+import api from '/@/api/device';
+import {ElMessage} from "element-plus";
+interface RuleFormState {
+  id:number;
+  name:string;
+  certificate:string;
+  secureKey:string;
+  version:string;
+  productId:number;
+  deptId:number;
+  desc:string;
+}
+interface DicState {
+	isShowDialog: boolean;
+	ruleForm: RuleFormState;
+  rules:{}
+}
+
+export default defineComponent({
+	name: 'deviceEditPro',
+	setup(prop,{emit}) {
+    const formRef = ref<HTMLElement | null>(null);
+		const state = reactive<DicState>({
+			isShowDialog: false,
+      productData: [], // 分类数据
+      deptData: [], // 
+			ruleForm: {
+        id:0,
+        name:'',
+        productId:'',
+        deptId:0,
+        certificate:'',
+        secureKey:'',
+        version:'',
+        desc:''
+			},
+      rules: {
+        name: [
+          { required: true, message: "设备名称不能为空", trigger: "blur" }
+        ],
+        key: [
+          { required: true, message: "设备标识不能为空", trigger: "blur" }
+        ],
+        productId: [{ required: true, message: '所属产品不能为空', trigger: 'blur' }],
+        deptId: [{ required: true, message: '所属部门不能为空', trigger: 'blur' }],
+       
+      }
+		});
+		// 打开弹窗
+		const openDialog = (row: RuleFormState|null) => {
+      resetForm();
+
+        api.product.getLists({ status: 1 }).then((res: any) => {
+          state.productData = res.product || [];
+        });
+        api.dept.getList({ status: -1 }).then((res: any) => {
+          state.deptData = res || [];
+        });
+
+
+      if (row){
+        // api.dict.getType(row.id).then((res:any)=>{
+        //   state.ruleForm = res.data.dictType
+        // })
+        state.ruleForm = row;
+      }
+			state.isShowDialog = true;
+		};
+    const resetForm = ()=>{
+      state.ruleForm = {
+        id:0,
+        name:'',
+        productId:'',
+        deptId:0,
+        certificate:'',
+        secureKey:'',
+        version:'',
+        desc:''
+      }
+    };
+		// 关闭弹窗
+		const closeDialog = () => {
+			state.isShowDialog = false;
+		};
+		// 取消
+		const onCancel = () => {
+			closeDialog();
+		};
+		// 新增
+		const onSubmit = () => {
+      const formWrap = unref(formRef) as any;
+      if (!formWrap) return;
+      formWrap.validate((valid: boolean) => {
+        if (valid) {
+          if(state.ruleForm.id!==0){
+            //修改
+            api.instance.edit(state.ruleForm).then(()=>{
+              ElMessage.success('设备类型修改成功');
+              closeDialog(); // 关闭弹窗
+              emit('typeList')
+            })
+          }else{
+            //添加
+            api.instance.add(state.ruleForm).then(()=>{
+              ElMessage.success('设备类型添加成功');
+              closeDialog(); // 关闭弹窗
+              emit('typeList')
+            })
+          }
+        }
+      });
+		};
+
+
+		return {
+			openDialog,
+			closeDialog,
+			onCancel,
+			onSubmit,
+      formRef,
+			...toRefs(state),
+		};
+	},
+});
+</script>

+ 147 - 0
src/views/iot/device/channel/component/list.vue

@@ -0,0 +1,147 @@
+<template>
+	<div class="system-edit-dic-container">
+		<el-dialog  v-model="isShowDialog" :show-close="false"  width="75%" :fullscreen="dialogFullScreen">
+		 <template #header="{ close, titleId, titleClass }">
+      <div class="my-header">
+        <h4 :id="titleId" :class="titleClass">数据记录</h4>
+		 
+		 <div>
+            <i class="iconfont "  :class="!dialogFullScreen ? 'icon-fullscreen' : 'icon-tuichuquanping'"   @click="quanping"  style="font-size: 22px;cursor: pointer;"></i>
+			<i class="el-icon"  @click="close" style="font-size: 22px;cursor: pointer;    margin-left: 10px; position: relative; top: 3px;"><svg viewBox="0 0 1024 1024" xmlns="http://www.w3.org/2000/svg" data-v-029747aa=""><path fill="currentColor" d="M764.288 214.592 512 466.88 259.712 214.592a31.936 31.936 0 0 0-45.12 45.12L466.752 512 214.528 764.224a31.936 31.936 0 1 0 45.12 45.184L512 557.184l252.288 252.288a31.936 31.936 0 0 0 45.12-45.12L557.12 512.064l252.288-252.352a31.936 31.936 0 1 0-45.12-45.184z"></path></svg></i>
+			
+		</div>
+      </div>
+    </template>
+
+			<el-table :data="tableData.data" style="width: 100%"  v-loading="tableData.loading">
+				<el-table-column label="时间" prop="ts" :show-overflow-tooltip="true" />
+				<el-table-column label="属性值" prop="value" :show-overflow-tooltip="true" />
+			</el-table>
+			<pagination
+				v-show="tableData.total > 0"
+				:total="tableData.total"
+				v-model:page="tableData.param.pageNum"
+				v-model:limit="tableData.param.pageSize"
+				@pagination="typeList"
+			/>
+		</el-dialog>
+	</div>
+</template>
+ 
+<script lang="ts">
+import { reactive, toRefs, defineComponent, ref, unref } from 'vue';
+import { Close } from '@element-plus/icons-vue';
+
+import api from '/@/api/device';
+import { ElMessage } from 'element-plus';
+
+interface DicState {
+	isShowDialog: boolean;
+}
+
+// 定义接口来定义对象的类型
+interface TableDataRow {
+	id: number;
+	name: string;
+	key: string;
+
+	createBy: string;
+}
+interface TableDataState {
+	ids: number[];
+	tableData: {
+		data: Array<TableDataRow>;
+		total: number;
+		loading: boolean;
+		param: {
+			pageNum: number;
+			pageSize: number;
+			id: number;
+		};
+	};
+}
+
+export default defineComponent({
+	name: 'deviceEditPro',
+	setup(prop, { emit }) {
+		const formRef = ref<HTMLElement | null>(null);
+		const state = reactive<DicState>({
+			isShowDialog: false,
+		    dialogFullScreen: false,
+			tableData: {
+				data: [],
+				total: 0,
+				loading: false,
+				param: {
+					pageNum: 1,
+					pageSize: 10,
+					id: 0,
+					propertyKey:'',
+				},
+			},
+		});
+		// 打开弹窗
+		const openDialog = (row: RuleFormState | null,devid) => {
+			resetForm();
+			if (row) {
+				console.log(row);
+				state.tableData.param.id = devid;
+				state.tableData.param.propertyKey=row.key
+				typeList();
+
+			}
+			state.isShowDialog = true;
+		};
+
+		const typeList = () => {
+			state.tableData.loading = true;
+			api.instance.getLogDetail(state.tableData.param).then((res: any) => {
+				state.tableData.data = res.List;
+				state.tableData.total = res.Total;
+				//state.ruleForm = res.data.dictType
+			}).finally(() => (state.tableData.loading = false));
+
+		};
+		const resetForm = () => {
+			state.tableData= {
+				data: [],
+				total: 0,
+				loading: false,
+				param: {
+					pageNum: 1,
+					pageSize: 10,
+				},
+			}
+		};
+		// 关闭弹窗
+		const closeDialog = () => {
+			state.isShowDialog = false;
+		};
+		const quanping=()=>{
+			state.dialogFullScreen = state.dialogFullScreen?false:true;
+		}
+		// 取消
+		const onCancel = () => {
+			closeDialog();
+		};
+
+		return {
+            Close,
+			quanping,
+			typeList,
+			openDialog,
+			closeDialog,
+			onCancel,
+			formRef,
+			...toRefs(state),
+		};
+	},
+});
+</script>
+<style scoped>
+.my-header {
+  display: flex;
+  flex-direction: row;
+  justify-content: space-between;
+}
+</style>

+ 873 - 0
src/views/iot/device/channel/detail.vue

@@ -0,0 +1,873 @@
+<template>
+  <div class="system-dic-container">
+    <div class="content">
+      <div class="cont_box">
+        <div class="title">设备:{{ detail.name }}</div>
+        <div class="pro-status"><span :class="developer_status == 2 ? 'on' : 'off'"></span>{{ developer_status == 2 ? '在线' : '离线' }}</div>
+
+        <!-- <div class="pro-option" @click="CkOption">{{ developer_status == 2 ? '下线' : '上线' }}</div> -->
+      </div>
+    </div>
+
+		<div class="content-box">
+			<el-tabs v-model="activeName" class="demo-tabs" @tab-click="handleClick">
+
+				<el-tab-pane label="运行状态" name="3">
+					<div style=" display: flex; padding: 10px;flex-wrap: wrap;" >
+						<div class="ant-card">
+							<div class="ant-card-body">
+								<div class="cardflex">
+									<div>设备状态</div>
+									<div @click="getrunData()" style="cursor: pointer;">
+										<el-icon style="font-size: 18px;">
+											<ele-Refresh />
+										</el-icon>
+									</div>
+								</div>
+
+								<div class="statusname" v-if="areaData.status==0">未启用</div>
+								<div class="statusname" v-if="areaData.status==1">离线</div>
+								<div class="statusname" v-if="areaData.status==2">在线</div>
+								<div class="cardflex comtest">
+									<div> 数据时间</div>
+									<div>{{areaData.lastOnlineTime || '未启用'}}</div>
+								</div>
+							</div>
+						</div>
+
+						<div class="ant-card" v-for="(item, index) in areaData.properties" :key="index">
+							<div class="ant-card-body">
+								<div class="cardflex">
+									<div>{{item.name}}</div>
+									<div style="cursor: pointer;">
+										<el-icon  style="font-size: 18px;"  @click="getrunData()">
+											<ele-Refresh />
+										</el-icon>
+										<el-icon  style="font-size: 18px;    margin-left: 10px;" @click="onOpenListDetail(item)">
+											<ele-Expand />
+										</el-icon>
+									</div>
+								</div>
+
+								<div class="statusname">{{item.value}}{{item.unit}}</div>
+								<div class="">
+									<devantd :json="item.list" :antdid="item.key" v-if="item.type=='int' || item.type=='float'"/>
+								</div>
+							</div>
+						</div>
+
+
+
+					</div>
+				</el-tab-pane>
+
+
+
+
+				<el-tab-pane label="设备信息" name="1">
+					<div class="pro-box">
+						<div class="protitle">设备信息</div>
+            <div>
+              <el-button type="primary" @click="onOpenEditDic(detail)">编辑</el-button>
+            </div>
+          </div>
+
+					<div class="ant-descriptions-view">
+						<table>
+							<tbody>
+								<tr class="ant-descriptions-row">
+                  <th class="ant-descriptions-item-label ant-descriptions-item-colon">设备标识</th>
+                  <td class="ant-descriptions-item-content" colspan="1">{{ detail.key }}</td>
+									<th class="ant-descriptions-item-label ant-descriptions-item-colon">设备名称</th>
+									<td class="ant-descriptions-item-content" colspan="1">{{ detail.name }}</td>
+									<th class="ant-descriptions-item-label ant-descriptions-item-colon">所属产品</th>
+									<td class="ant-descriptions-item-content" colspan="1">{{ detail.productName }}</td>
+
+								</tr>
+								<tr class="ant-descriptions-row">
+									<th class="ant-descriptions-item-label ant-descriptions-item-colon">消息协议</th>
+									<td class="ant-descriptions-item-content" colspan="1">{{ prodetail.messageProtocol }}</td>
+									<th class="ant-descriptions-item-label ant-descriptions-item-colon">链接协议</th>
+									<td class="ant-descriptions-item-content" colspan="1">{{ prodetail.transportProtocol }}</td>
+                  <th class="ant-descriptions-item-label ant-descriptions-item-colon">设备类型</th>
+                  <td class="ant-descriptions-item-content" colspan="1">{{ prodetail.deviceType }}</td>
+								</tr>
+								<tr class="ant-descriptions-row">
+                  <th class="ant-descriptions-item-label ant-descriptions-item-colon">固件版本</th>
+                  <td class="ant-descriptions-item-content" colspan="1">{{ prodetail.version }}</td>
+									<th class="ant-descriptions-item-label ant-descriptions-item-colon">注册时间</th>
+									<td class="ant-descriptions-item-content" colspan="1">{{ prodetail.updatedAt }}</td>
+									<th class="ant-descriptions-item-label ant-descriptions-item-colon">最后上线时间</th>
+									<td class="ant-descriptions-item-content" colspan="1">{{ prodetail.lastOnlineTime || '' }}</td>
+								</tr>
+                <tr class="ant-descriptions-row">
+                  <th class="ant-descriptions-item-label ant-descriptions-item-colon">说明</th>
+                  <td class="ant-descriptions-item-content" colspan="5">{{ prodetail.desc }}</td>
+                </tr>
+
+
+              </tbody>
+								</table>
+								</div>
+								</el-tab-pane>
+        <el-tab-pane label="物模型" name="2">
+          <div class="wu-box">
+            <el-tabs type="border-card" v-model="activetab" @tab-click="wuhandleClick">
+              <el-tab-pane label="属性定义" name="attr">
+                <div class="wu-title">
+                  <div class="title">属性定义</div>
+                  <div>
+                    <el-button type="primary" @click="onOpenEditAttr()">添加</el-button>
+                  </div>
+                </div>
+
+                <el-table style="width: 100%" :data="tableData.data" v-if="activetab == 'attr'">
+                  <el-table-column label="属性标识" align="center" prop="key" />
+                  <el-table-column label="属性名称" prop="name" :show-overflow-tooltip="true" />
+                  <el-table-column prop="valueType" label="数据类型" width="100" align="center">
+                    <template #default="scope">
+                      <span>{{ scope.row.valueType.type }}</span>
+                    </template>
+                  </el-table-column>
+                  <el-table-column prop="decimals" label="精度" width="60" align="center">
+                    <template #default="scope">
+                      <span>{{ scope.row.valueType.decimals }}</span>
+                    </template>
+                  </el-table-column>
+                  <el-table-column prop="unit" label="单位" width="60" align="center">
+                    <template #default="scope">
+                      <span>{{ scope.row.valueType.unit }}</span>
+                    </template>
+                  </el-table-column>
+                  <el-table-column prop="accessMode" label="是否只读" width="120" align="center">
+                    <template #default="scope">
+                      <el-tag type="info" size="small" v-if="scope.row.accessMode">只读</el-tag>
+                      <el-tag type="success" size="small" v-else>读写</el-tag>
+                    </template>
+                  </el-table-column>
+                  <el-table-column label="说明" prop="desc" :show-overflow-tooltip="true" />
+                  <el-table-column label="操作" width="300" align="center" fixed="right">
+                    <template #default="scope">
+                      <el-button size="small" text type="warning" @click="onEditAttr(scope.row)">修改</el-button>
+                      <el-button size="small" text type="danger" @click="onRowDel(scope.row.key, 'attr')">删除</el-button>
+                    </template>
+                  </el-table-column>
+                </el-table>
+              </el-tab-pane>
+              <el-tab-pane label="功能定义" name="fun">
+                <div class="wu-title">
+                  <div class="title">功能定义</div>
+                  <div>
+                    <el-button type="primary" @click="onOpenEditFun()">添加</el-button>
+                  </div>
+                </div>
+
+                <el-table style="width: 100%" :data="tableData.data" v-if="activetab == 'fun'">
+                  <el-table-column label="功能标识" align="center" prop="key" />
+                  <el-table-column label="名称" prop="name" :show-overflow-tooltip="true" />
+
+                  <el-table-column label="描述" prop="desc" :show-overflow-tooltip="true" />
+                  <el-table-column label="操作" width="300" align="center" fixed="right">
+                    <template #default="scope">
+                      <el-button size="small" text type="warning" @click="onEditFun(scope.row)">修改</el-button>
+                      <el-button size="small" text type="danger" @click="onRowDel(scope.row.key, 'fun')">删除</el-button>
+                    </template>
+                  </el-table-column>
+                </el-table>
+              </el-tab-pane>
+              <el-tab-pane label="事件定义" name="event">
+                <div class="wu-title">
+                  <div class="title">事件定义</div>
+                  <div>
+                    <el-button type="primary" @click="onOpenEditEvent()">添加</el-button>
+                  </div>
+                </div>
+
+                <el-table style="width: 100%" :data="tableData.data" v-if="activetab == 'event'">
+                  <el-table-column label="事件标识" align="center" prop="key" />
+                  <el-table-column label="名称" prop="name" :show-overflow-tooltip="true" />
+                  <el-table-column prop="level" label="事件级别" width="120" align="center">
+                    <template #default="scope">
+                      <el-tag type="primary" size="small" v-if="scope.row.level == 0">普通</el-tag>
+                      <el-tag type="warning" size="small" v-if="scope.row.level == 1">警告</el-tag>
+                      <el-tag type="danger" size="small" v-if="scope.row.level == 2">紧急</el-tag>
+                    </template>
+                  </el-table-column>
+                  <el-table-column label="描述" prop="desc" :show-overflow-tooltip="true" />
+
+                  <el-table-column label="操作" width="300" align="center" fixed="right">
+                    <template #default="scope">
+                      <el-button size="small" text type="warning" @click="onEditEvent(scope.row)">修改</el-button>
+                      <el-button size="small" text type="danger" @click="onRowDel(scope.row.key, 'event')">删除</el-button>
+                    </template>
+                  </el-table-column>
+                </el-table>
+              </el-tab-pane>
+              <el-tab-pane label="标签定义" name="tab">
+                <div class="wu-title">
+                  <div class="title">标签定义</div>
+                  <div>
+                    <el-button type="primary" @click="onOpenEditTab()">添加</el-button>
+                  </div>
+                </div>
+
+
+                <el-table style="width: 100%" :data="tableData.data" v-if="activetab == 'tab'">
+                  <el-table-column label="属性标识" align="center" prop="key" />
+                  <el-table-column label="属性名称" prop="name" :show-overflow-tooltip="true" />
+                  <el-table-column prop="valueType" label="数据类型" width="120" align="center">
+                    <template #default="scope">
+                      <span>{{ scope.row.valueType.type }}</span>
+                    </template>
+                  </el-table-column>
+                  <el-table-column prop="accessMode" label="是否只读" width="120" align="center">
+                    <template #default="scope">
+                      <el-tag type="info" size="small" v-if="scope.row.accessMode">只读</el-tag>
+                      <el-tag type="success" size="small" v-else>读写</el-tag>
+                    </template>
+                  </el-table-column>
+                  <el-table-column label="描述" prop="desc" :show-overflow-tooltip="true" />
+                  <el-table-column label="操作" width="300" align="center" fixed="right">
+                    <template #default="scope">
+                      <el-button size="small" text type="warning" @click="onEditTag(scope.row)">修改</el-button>
+                      <el-button size="small" text type="danger" @click="onRowDel(scope.row.key, 'tab')">删除</el-button>
+                    </template>
+                  </el-table-column>
+                </el-table>
+              </el-tab-pane>
+            </el-tabs>
+            <pagination v-show="tableData.total > 0" :total="tableData.total" v-model:page="tableData.param.pageNum" v-model:limit="tableData.param.pageSize" @pagination="getList" />
+          </div>
+        </el-tab-pane>
+
+
+        <el-tab-pane label="日志管理" name="4">
+          <div class="system-user-search mb15">
+            <el-form :model="logtableData.param" ref="queryRef" :inline="true" label-width="68px">
+              <el-form-item label="日志类型" prop="types">
+                <el-select v-model="logtableData.param.types" placeholder="日志类型" clearable size="default">
+                  <el-option v-for="item in logTypeData" :key="item" :label="item" :value="item" />
+                </el-select>
+              </el-form-item>
+
+              <el-form-item label="创建时间" prop="dateRange">
+                <el-date-picker v-model="logtableData.param.dateRange" size="default" value-format="YYYY-MM-DD" type="daterange" range-separator="-" start-placeholder="开始日期" end-placeholder="结束日期"></el-date-picker>
+              </el-form-item>
+              <el-form-item>
+                <el-button size="default" type="primary" class="ml10" @click="getlog">
+                  <el-icon>
+                    <ele-Search />
+                  </el-icon>
+                  查询
+                </el-button>
+                <el-button size="default" @click="resetQuery(queryRef)">
+                  <el-icon>
+                    <ele-Refresh />
+                  </el-icon>
+                  重置
+                </el-button>
+              </el-form-item>
+            </el-form>
+          </div>
+          <el-table style="width: 100%" :data="logtableData.data">
+            <el-table-column label="类型" align="center" prop="type" />
+            <el-table-column label="时间" prop="ts" :show-overflow-tooltip="true" />
+
+            <el-table-column label="内容" prop="content" :show-overflow-tooltip="true" />
+            <el-table-column label="操作" width="300" align="center" fixed="right">
+              <template #default="scope">
+                <el-button size="small" text type="warning" @click="onLogDetail(scope.row)">查看</el-button>
+              </template>
+            </el-table-column>
+          </el-table>
+
+          <pagination v-show="logtableData.total > 0" :total="logtableData.total" v-model:page="logtableData.param.pageNum" v-model:limit="logtableData.param.pageSize" @pagination="getlog" />
+        </el-tab-pane>
+      </el-tabs>
+    </div>
+    <EditDic ref="editDicRef" @typeList="typeList" />
+    <EditAttr ref="editAttrRef" @typeList="getproperty" />
+    <EditFun ref="editFunRef" @typeList="getfunction" />
+    <EditEvent ref="editEventRef" @typeList="getevent" />
+    <EditTab ref="editTabRef" @typeList="gettab" />
+    <ListDic ref="listDicRef" />
+
+    <el-dialog v-model="dialogVisible" title="返回Json数据" width="30%">
+      <JsonViewer :value="jsonData" boxed sort theme="jv-dark" @click="onKeyclick" />
+
+      <template #footer>
+        <span class="dialog-footer">
+          <el-button @click="dialogVisible = false">关闭</el-button>
+        </span>
+      </template>
+    </el-dialog>
+  </div>
+</template>
+<script lang="ts">
+import { toRefs, reactive, onMounted, ref, defineComponent } from 'vue';
+import { ElMessageBox, ElMessage, FormInstance } from 'element-plus';
+
+import 'vue3-json-viewer/dist/index.css';
+
+import EditDic from '../product/component/editPro.vue';
+import EditAttr from '../product/component/editAttr.vue';
+import EditFun from '../product/component/editFun.vue';
+import EditEvent from '../product/component/editEvent.vue';
+import EditTab from '../product/component/editTab.vue';
+import devantd from '/@/components/devantd/index.vue';
+import ListDic from './component/list.vue';
+
+
+import { useRoute } from 'vue-router';
+
+import api from '/@/api/device';
+
+interface TableDataState {
+  ids: number[];
+  tableData: {
+    data: [];
+    total: number;
+    loading: boolean;
+    param: {
+      pageNum: number;
+      pageSize: number;
+      name: string;
+      deviceType: string;
+      status: string;
+      dateRange: string[];
+    };
+  };
+  logtableData: {
+    data: [];
+    total: number;
+    loading: boolean;
+    param: {
+      pageNum: number;
+      pageSize: number;
+      name: string;
+      deviceType: string;
+      status: string;
+      dateRange: string[];
+    };
+  };
+}
+export default defineComponent({
+  name: 'deviceEditPro',
+  components: { EditDic, EditAttr, EditFun, EditEvent, EditTab, devantd, ListDic },
+
+	setup(prop, context) {
+		const route = useRoute();
+		const editDicRef = ref();
+		const editAttrRef = ref();
+		const editFunRef = ref();
+		const listDicRef=ref();
+		const editEventRef = ref();
+		const editTabRef = ref();
+		const state = reactive<TableDataState>({
+			areaData:[],
+			isShowDialog: false,
+			dialogVisible: false,
+			logTypeData: [],
+			jsonData: '',
+			activeName: '3', // 分类数据
+			activetab: 'attr', // 分类数据
+			detail: [],
+			prodetail: [],
+			product_id: 0,
+			developer_status: 0,
+			tableData: {
+				data: [],
+				total: 0,
+				loading: false,
+				param: {
+					pageNum: 1,
+					productId: 0,
+					pageSize: 10,
+					status: '',
+					dateRange: [],
+				},
+			},
+			logtableData: {
+				data: [],
+				total: 0,
+				loading: false,
+				param: {
+					pageNum: 1,
+					productId: 0,
+					pageSize: 10,
+					status: '',
+					dateRange: [],
+				},
+			},
+		});
+
+		onMounted(() => {
+			const ids = route.params && route.params.id;
+			api.instance.detail(ids).then((res: any) => {
+				state.detail = res.data;
+				state.developer_status = res.data.status;
+				state.tableData.param.productId = res.data.product.id;
+				state.product_id = res.data.product.id;
+				getrunData();
+				api.product.detail(res.data.product.id).then((res: any) => {
+					state.prodetail = res.data;
+					console.log(res.data);
+				});
+
+				//第一次加载
+				api.model.property(state.tableData.param).then((res: any) => {
+					state.tableData.data = res.Data;
+					state.tableData.total = res.Total;
+				});
+			});
+
+		});
+
+
+    const onLogDetail = (row: TableDataRow) => {
+      state.jsonData = JSON.parse(row.content);
+      console.log(JSON.parse(row.content));
+      state.dialogVisible = true;
+    };
+
+    //编辑属性
+    const onEditAttr = (row: TableDataRow) => {
+      editAttrRef.value.openDialog(row, state.product_id);
+    };
+
+    //编辑功能
+    const onEditFun = (row: TableDataRow) => {
+      editFunRef.value.openDialog(row, state.product_id);
+    };
+
+    //编辑事件
+    const onEditEvent = (row: TableDataRow) => {
+      editEventRef.value.openDialog(row, state.product_id);
+    };
+
+    //编辑标签
+    const onEditTag = (row: TableDataRow) => {
+      editTabRef.value.openDialog(row, state.product_id);
+    };
+
+    //打开添加属性弹窗
+    const onOpenEditAttr = () => {
+      editAttrRef.value.openDialog({ product_id: state.product_id, id: 0, accessMode: 0 });
+    };
+
+    //打开添加功能弹窗
+    const onOpenEditFun = () => {
+      editFunRef.value.openDialog({ product_id: state.product_id, id: 0 });
+    };
+    //打开添加事件弹窗
+    const onOpenEditEvent = () => {
+      editEventRef.value.openDialog({ product_id: state.product_id, id: 0, level: 0 });
+    };
+
+    //打开添加事件弹窗
+    const onOpenEditTab = () => {
+      editTabRef.value.openDialog({ product_id: state.product_id, id: 0, accessMode: 0 });
+    };
+
+    //查看日志列表
+    const onOpenListDetail = (row: TableDataRow) => {
+      listDicRef.value.openDialog(row, state.detail.id);
+    };
+
+    // 打开修改产品弹窗
+    const onOpenEditDic = (row: TableDataRow) => {
+      editDicRef.value.openDialog(row);
+    };
+
+    // 删除产品
+    const onRowDel = (key, type) => {
+      let msg = `此操作将永久删除该数据吗?,是否继续?`;
+
+      if (key.length === 0) {
+        ElMessage.error('请选择要删除的数据。');
+        return;
+      }
+      ElMessageBox.confirm(msg, '提示', {
+        confirmButtonText: '确认',
+        cancelButtonText: '取消',
+        type: 'warning',
+      })
+        .then(() => {
+          if (type == 'attr') {
+            api.model.propertydel(state.product_id, key).then(() => {
+              ElMessage.success('删除成功');
+              getproperty();
+            });
+          }
+          if (type == 'fun') {
+            api.model.functiondel(state.product_id, key).then(() => {
+              ElMessage.success('删除成功');
+              getfunction();
+            });
+          }
+          if (type == 'event') {
+            api.model.eventdel(state.product_id, key).then(() => {
+              ElMessage.success('删除成功');
+              getevent();
+            });
+          }
+          if (type == 'tab') {
+            api.model.tagdel(state.product_id, key).then(() => {
+              ElMessage.success('删除成功');
+              tagdel();
+            });
+          }
+        })
+        .catch(() => { });
+    };
+
+    //根据不同类型获取列表
+    const getList = () => {
+      switch (state.activetab) {
+        case 'attr':
+          getproperty();
+          break;
+        case 'fun':
+          getfunction();
+          break;
+        case 'event':
+          getevent();
+          break;
+        case 'tab':
+          gettab();
+          break;
+      }
+    };
+
+    const getproperty = () => {
+      api.model.property(state.tableData.param).then((res: any) => {
+        state.tableData.data = res.Data;
+        state.tableData.total = res.Total;
+      });
+    };
+
+    const getfunction = () => {
+      api.model.function(state.tableData.param).then((res: any) => {
+        state.tableData.data = res.Data;
+        state.tableData.total = res.Total;
+      });
+    };
+    const getevent = () => {
+      api.model.event(state.tableData.param).then((res: any) => {
+        state.tableData.data = res.Data;
+        state.tableData.total = res.Total;
+      });
+    };
+
+    const gettab = () => {
+      api.model.tag(state.tableData.param).then((res: any) => {
+        state.tableData.data = res.Data;
+        state.tableData.total = res.Total;
+      });
+    };
+
+    const wuhandleClick = (tab: TabsPaneContext) => {
+      state.activetab = tab.props.name;
+      switch (tab.props.name) {
+        case 'attr':
+          getproperty();
+          break;
+        case 'fun':
+          getfunction();
+          break;
+        case 'event':
+          getevent();
+          break;
+        case 'tab':
+          gettab();
+          break;
+      }
+    };
+
+    const handleClick = (tab: TabsPaneContext, event: Event) => {
+      console.log(tab, event);
+      if (tab.props.name == 4) {
+        //获取日志
+        getlog();
+        getlogtype();
+      } else if (tab.props.name == 2) {
+        getList();
+      } else if (tab.props.name == 3) {
+        getrunData();
+      }
+    };
+
+    const getrunData = () => {
+      api.instance.getrun_status({ id: state.detail.id }).then((res: any) => {
+          state.areaData = res
+          let properties=state.areaData.properties;
+
+          var temp = new Array();
+
+          properties.forEach(function (item, index) {
+              let datalist=item.list;
+              temp[index] = [];
+              var temps = new Array();
+              datalist.forEach(function (a, b) {
+                 if(b<15){
+                  temps.push(a);
+                 }
+              });
+              temp[index]['name']=item.name
+              temp[index]['key']=item.key
+              temp[index]['type']=item.type
+              temp[index]['unit']=item.unit
+              temp[index]['value']=item.value
+              temp[index]['list']=temps
+
+          });
+
+          state.areaData.properties=temp;
+      });
+
+    };
+
+    const getlogtype = () => {
+      api.instance.getlogcate({}).then((res: any) => {
+        state.logTypeData = res.list;
+      });
+    };
+
+    const getlog = () => {
+      state.logtableData.param.deviceKey = state.detail.key;
+      api.instance.getLogList(state.logtableData.param).then((res: any) => {
+        console.log(res, '22222222');
+        state.logtableData.data = res.list;
+        state.logtableData.total = res.Total;
+      });
+    };
+
+    const CkOption = () => {
+      if (state.developer_status == 2) {
+        api.instance.devoffline({ id: state.detail.id }).then((res: any) => {
+          ElMessage.success('操作成功');
+          state.developer_status = 1;
+        });
+      } else {
+        api.instance.devonline({ id: state.detail.id }).then((res: any) => {
+          ElMessage.success('操作成功');
+          state.developer_status = 2;
+        });
+      }
+    };
+    const tinyAreas = () => {
+      var data = state.data;
+
+      const tinyArea = new TinyArea('container', {
+        height: 60,
+        autoFit: false,
+        data,
+        smooth: true,
+        areaStyle: {
+          fill: '#d6e3fd',
+        },
+      });
+      tinyArea.render();
+    }
+    return {
+      tinyAreas,
+      editDicRef,
+      editAttrRef,
+      listDicRef,
+      editFunRef,
+      editEventRef,
+      editTabRef,
+      onOpenListDetail,
+      getrunData,
+      getlog,
+      getlogtype,
+      onLogDetail,
+      CkOption,
+      onRowDel,
+      onEditFun,
+      onEditEvent,
+      onEditTag,
+      onEditAttr,
+      getList,
+      getproperty,
+      getfunction,
+      getevent,
+      gettab,
+      wuhandleClick,
+      onOpenEditTab,
+      onOpenEditEvent,
+      onOpenEditAttr,
+      onOpenEditFun,
+      onOpenEditDic,
+      handleClick,
+      ...toRefs(state),
+    };
+  },
+});
+</script>
+  <style>
+.content {
+	background: #fff;
+	width: 100%;
+	padding: 20px;
+}
+.content-box {
+	background: #fff;
+	width: 100%;
+	padding: 20px;
+	margin-top: 20px;
+}
+.cont_box {
+	display: flex;
+}
+.cont_box .title {
+	font-size: 24px;
+}
+.cont_box .pro-status {
+	line-height: 40px;
+	margin-left: 30px;
+}
+.cont_box .pro-status .on {
+	background: #52c41a;
+}
+.cont_box .pro-status .off {
+	background: #c41a1a;
+}
+.cont_box .pro-status span {
+	position: relative;
+	top: -1px;
+	display: inline-block;
+	width: 6px;
+	height: 6px;
+	vertical-align: middle;
+	border-radius: 50%;
+	margin-right: 5px;
+}
+.cont_box .pro-option {
+	line-height: 40px;
+	margin-left: 10px;
+	color: #1890ff;
+	cursor: pointer;
+}
+.content-box .pro-box {
+	display: flex;
+	padding: 10px;
+  justify-content: space-between;
+
+}
+.content-box .pro-box .protitle {
+	font-size: 18px;
+	font-weight: bold;
+	line-height: 35px;
+}
+.content-box .pro-box .buttonedit {
+	border: 0px;
+	color: #1890ff;
+}
+table {
+	border-collapse: collapse;
+	text-indent: initial;
+	border-spacing: 2px;
+}
+tbody {
+	box-sizing: border-box;
+	display: table-row-group;
+	vertical-align: middle;
+	border-color: inherit;
+}
+
+tr {
+	display: table-row;
+	vertical-align: inherit;
+	border-color: inherit;
+}
+.ant-descriptions-view {
+	width: 100%;
+	overflow: hidden;
+	border-radius: 4px;
+}
+.ant-descriptions-view {
+	border: 1px solid #e8e8e8;
+}
+.ant-descriptions-view table {
+	width: 100%;
+	table-layout: fixed;
+}
+.ant-descriptions-view > table {
+	table-layout: auto;
+}
+.ant-descriptions-row {
+	border-bottom: 1px solid #e8e8e8;
+}
+.ant-descriptions-item-label {
+	color: rgba(0, 0, 0, 0.85);
+	font-weight: 400;
+	font-size: 14px;
+	line-height: 1.5;
+}
+.ant-descriptions-item-label {
+	padding: 16px 24px;
+	border-right: 1px solid #e8e8e8;
+}
+.ant-descriptions-item-label {
+	background-color: #fafafa;
+}
+.ant-descriptions-item-content {
+	padding: 16px 24px;
+	border-right: 1px solid #e8e8e8;
+	display: table-cell;
+	color: rgba(0, 0, 0, 0.65);
+	font-size: 14px;
+	line-height: 1.5;
+}
+.wu-box {
+	border: #e8e8e8 solid 1px;
+	padding: 20px;
+	width: 100%;
+}
+.wu-box .wu-title {
+	display: flex;
+	flex-direction: row;
+	justify-content: space-between;
+	padding: 20px;
+	border-bottom: #e8e8e8 1px solid;
+}
+.wu-box .wu-title .title {
+	font-size: 18px;
+}
+.ant-card {
+	box-sizing: border-box;
+	margin: 10px;
+	width: 23.2%;
+	font-size: 14px;
+	font-variant: tabular-nums;
+  border: 1px solid  var(--next-border-color-light);
+
+	line-height: 1.5;
+	list-style: none;
+	font-feature-settings: 'tnum';
+	position: relative;
+	border-radius: 2px;
+	transition: all 0.3s;
+}
+.ant-card-body {
+	padding: 24px;
+	zoom: 1;
+}
+.cardflex {
+	display: flex;
+	justify-content: space-between;
+}
+.statusname {
+	font-size: 30px;
+	margin-top: 10px;
+  margin-bottom: 15px;
+}
+.comtest {
+	margin-top: 20px;
+	height: 30px;
+	line-height: 30px;
+}
+</style>
+
+

+ 87 - 0
src/views/iot/device/channel/index.vue

@@ -0,0 +1,87 @@
+<template>
+	<div class="page">
+		<el-card shadow="hover">
+			<div class="search">
+				<el-form :model="params" :inline="true" ref="queryRef">
+					<el-form-item label="通道名称" prop="title">
+						<el-input v-model="params.title" placeholder="请输入通道名称" clearablestyle="width: 240px" @keyup.enter.native="getList(1)" />
+					</el-form-item>
+					<el-form-item label="注册码" prop="number">
+						<el-input v-model="params.number" placeholder="请输入注册码" clearablestyle="width: 240px" @keyup.enter.native="getList(1)" />
+					</el-form-item>
+					<el-form-item>
+						<el-button size="default" type="primary" class="ml10" @click="getList(1)">
+							<el-icon>
+								<ele-Search />
+							</el-icon>
+							查询
+						</el-button>
+						<el-button size="default" @click="resetQuery()">
+							<el-icon>
+								<ele-Refresh />
+							</el-icon>
+							重置
+						</el-button>
+						<el-button type="success" @click="addOrEdit()" v-auth="'add'">
+							<el-icon>
+								<ele-FolderAdd />
+							</el-icon>
+							新增通道
+						</el-button>
+					</el-form-item>
+				</el-form>
+			</div>
+			<el-table :data="tableData" style="width: 100%" v-loading="loading">
+				<el-table-column type="index" label="序号" width="80" align="center" />
+				<el-table-column prop="title" label="通道名称" align="center" show-overflow-tooltip></el-table-column>
+				<el-table-column prop="number" label="注册码" align="center" show-overflow-tooltip></el-table-column>
+				<el-table-column prop="slaveId" label="设备地址" align="center" show-overflow-tooltip></el-table-column>
+				<el-table-column label="操作" width="100" align="center">
+					<template #default="scope">
+						<el-button size="small" text type="primary" @click="addOrEdit(scope.row)" v-auth="'edit'">详情</el-button>
+						<el-button size="small" text type="danger" @click="onDel(scope.row)" v-auth="'del'">删除</el-button>
+					</template>
+				</el-table-column>
+			</el-table>
+			<pagination v-if="params.total" :total="params.total" v-model:page="params.page" v-model:limit="params.size" @pagination="getList()" />
+		</el-card>
+		<EditForm ref="editFormRef" @getList="getList()"></EditForm>
+	</div>
+</template>
+
+<script lang="ts" setup>
+import { ref } from 'vue';
+import EditForm from './component/edit.vue';
+import api from '/@/api/device/modbus';
+import { ElMessageBox, ElMessage } from 'element-plus';
+import { useSearch } from '/@/hooks/useCommonModbus';
+
+const editFormRef = ref();
+const queryRef = ref();
+
+const { params, tableData, getList, loading } = useSearch(api.channel.getList, 'list', { title: '', number: '' });
+
+getList();
+
+const addOrEdit = async (row?: any) => {
+	editFormRef.value.open(row);
+};
+
+// 重置表单
+const resetQuery = () => {
+	queryRef.value.resetFields();
+	getList(1);
+};
+
+const onDel = (row: any) => {
+	ElMessageBox.confirm(`此操作将删除接口:“${row.title}”,是否继续?`, '提示', {
+		confirmButtonText: '确认',
+		cancelButtonText: '取消',
+		type: 'warning',
+	}).then(async () => {
+		await api.channel.deleteDevice({ number: row.number });
+		ElMessage.success('删除成功');
+		getList();
+	});
+};
+</script>

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

@@ -0,0 +1,190 @@
+<template>
+	<div class="system-edit-dic-container">
+		<el-dialog :title="(ruleForm.id!==0?'修改':'添加')+'设备'" v-model="isShowDialog" width="769px">
+			<el-form :model="ruleForm" ref="formRef" :rules="rules" size="default" label-width="110px">
+       <el-form-item label="设备标识" prop="key">
+          <el-input v-model="ruleForm.key" placeholder="请输入设备标识" :disabled="ruleForm.id" />
+        </el-form-item>
+        <el-form-item label="设备名称" prop="name">
+          <el-input v-model="ruleForm.name" placeholder="请输入设备名称" />
+        </el-form-item>
+
+        
+
+           <el-form-item label="所属产品" prop="productId">
+       
+                <el-select v-model="ruleForm.productId" placeholder="请选择所属产品" class="w100">
+              <el-option
+                v-for="item in productData"
+                :key="item.id"
+                :label="item.name"
+                :value="item.id"
+              />
+            </el-select>
+            </el-form-item> 
+
+         <el-form-item label="所属部门" prop="deptId">
+              <el-cascader :options="deptData" :props="{ checkStrictly: true,emitPath: false, value: 'deptId', label: 'deptName' }" placeholder="请选择所属部门" clearable class="w100" v-model="ruleForm.deptId">
+                <template #default="{ node, data }">
+                  <span>{{ data.deptName }}</span>
+                  <span v-if="!node.isLeaf"> ({{ data.children.length }}) </span>
+                </template>
+              </el-cascader>
+            </el-form-item> 
+
+           
+        
+        <el-form-item label="设备证书" prop="certificate">
+          <el-input v-model="ruleForm.certificate" placeholder="请输入设备证书" />
+        </el-form-item>
+
+        <el-form-item label="设备秘钥" prop="secureKey">
+          <el-input v-model="ruleForm.secureKey" placeholder="请输入设备秘钥" />
+        </el-form-item>
+
+         <el-form-item label="固件版本号" prop="version">
+          <el-input v-model="ruleForm.version" placeholder="请输入固件版本号" />
+        </el-form-item>
+      
+  
+        <el-form-item label="备注" prop="desc">
+          <el-input v-model="ruleForm.desc" type="textarea" placeholder="请输入内容"></el-input>
+        </el-form-item>
+			</el-form>
+			<template #footer>
+				<span class="dialog-footer">
+					<el-button @click="onCancel" size="default">取 消</el-button>
+					<el-button type="primary" @click="onSubmit" size="default">{{ruleForm.id!==0?'修 改':'添 加'}}</el-button>
+				</span>
+			</template>
+		</el-dialog>
+	</div>
+</template>
+
+<script lang="ts">
+import { reactive, toRefs, defineComponent,ref, unref } from 'vue';
+import api from '/@/api/device';
+import {ElMessage} from "element-plus";
+interface RuleFormState {
+  id:number;
+  name:string;
+  certificate:string;
+  secureKey:string;
+  version:string;
+  productId:number;
+  deptId:number;
+  desc:string;
+}
+interface DicState {
+	isShowDialog: boolean;
+	ruleForm: RuleFormState;
+  rules:{}
+}
+
+export default defineComponent({
+	name: 'deviceEditPro',
+	setup(prop,{emit}) {
+    const formRef = ref<HTMLElement | null>(null);
+		const state = reactive<DicState>({
+			isShowDialog: false,
+      productData: [], // 分类数据
+      deptData: [], // 
+			ruleForm: {
+        id:0,
+        name:'',
+        productId:'',
+        deptId:0,
+        certificate:'',
+        secureKey:'',
+        version:'',
+        desc:''
+			},
+      rules: {
+        name: [
+          { required: true, message: "设备名称不能为空", trigger: "blur" }
+        ],
+        key: [
+          { required: true, message: "设备标识不能为空", trigger: "blur" }
+        ],
+        productId: [{ required: true, message: '所属产品不能为空', trigger: 'blur' }],
+        deptId: [{ required: true, message: '所属部门不能为空', trigger: 'blur' }],
+       
+      }
+		});
+		// 打开弹窗
+		const openDialog = (row: RuleFormState|null) => {
+      resetForm();
+
+        api.product.getLists({ status: 1 }).then((res: any) => {
+          state.productData = res.product || [];
+        });
+        api.dept.getList({ status: -1 }).then((res: any) => {
+          state.deptData = res || [];
+        });
+
+
+      if (row){
+        // api.dict.getType(row.id).then((res:any)=>{
+        //   state.ruleForm = res.data.dictType
+        // })
+        state.ruleForm = row;
+      }
+			state.isShowDialog = true;
+		};
+    const resetForm = ()=>{
+      state.ruleForm = {
+        id:0,
+        name:'',
+        productId:'',
+        deptId:0,
+        certificate:'',
+        secureKey:'',
+        version:'',
+        desc:''
+      }
+    };
+		// 关闭弹窗
+		const closeDialog = () => {
+			state.isShowDialog = false;
+		};
+		// 取消
+		const onCancel = () => {
+			closeDialog();
+		};
+		// 新增
+		const onSubmit = () => {
+      const formWrap = unref(formRef) as any;
+      if (!formWrap) return;
+      formWrap.validate((valid: boolean) => {
+        if (valid) {
+          if(state.ruleForm.id!==0){
+            //修改
+            api.instance.edit(state.ruleForm).then(()=>{
+              ElMessage.success('设备类型修改成功');
+              closeDialog(); // 关闭弹窗
+              emit('typeList')
+            })
+          }else{
+            //添加
+            api.instance.add(state.ruleForm).then(()=>{
+              ElMessage.success('设备类型添加成功');
+              closeDialog(); // 关闭弹窗
+              emit('typeList')
+            })
+          }
+        }
+      });
+		};
+
+
+		return {
+			openDialog,
+			closeDialog,
+			onCancel,
+			onSubmit,
+      formRef,
+			...toRefs(state),
+		};
+	},
+});
+</script>

+ 147 - 0
src/views/iot/device/template/component/list.vue

@@ -0,0 +1,147 @@
+<template>
+	<div class="system-edit-dic-container">
+		<el-dialog  v-model="isShowDialog" :show-close="false"  width="75%" :fullscreen="dialogFullScreen">
+		 <template #header="{ close, titleId, titleClass }">
+      <div class="my-header">
+        <h4 :id="titleId" :class="titleClass">数据记录</h4>
+		 
+		 <div>
+            <i class="iconfont "  :class="!dialogFullScreen ? 'icon-fullscreen' : 'icon-tuichuquanping'"   @click="quanping"  style="font-size: 22px;cursor: pointer;"></i>
+			<i class="el-icon"  @click="close" style="font-size: 22px;cursor: pointer;    margin-left: 10px; position: relative; top: 3px;"><svg viewBox="0 0 1024 1024" xmlns="http://www.w3.org/2000/svg" data-v-029747aa=""><path fill="currentColor" d="M764.288 214.592 512 466.88 259.712 214.592a31.936 31.936 0 0 0-45.12 45.12L466.752 512 214.528 764.224a31.936 31.936 0 1 0 45.12 45.184L512 557.184l252.288 252.288a31.936 31.936 0 0 0 45.12-45.12L557.12 512.064l252.288-252.352a31.936 31.936 0 1 0-45.12-45.184z"></path></svg></i>
+			
+		</div>
+      </div>
+    </template>
+
+			<el-table :data="tableData.data" style="width: 100%"  v-loading="tableData.loading">
+				<el-table-column label="时间" prop="ts" :show-overflow-tooltip="true" />
+				<el-table-column label="属性值" prop="value" :show-overflow-tooltip="true" />
+			</el-table>
+			<pagination
+				v-show="tableData.total > 0"
+				:total="tableData.total"
+				v-model:page="tableData.param.pageNum"
+				v-model:limit="tableData.param.pageSize"
+				@pagination="typeList"
+			/>
+		</el-dialog>
+	</div>
+</template>
+ 
+<script lang="ts">
+import { reactive, toRefs, defineComponent, ref, unref } from 'vue';
+import { Close } from '@element-plus/icons-vue';
+
+import api from '/@/api/device';
+import { ElMessage } from 'element-plus';
+
+interface DicState {
+	isShowDialog: boolean;
+}
+
+// 定义接口来定义对象的类型
+interface TableDataRow {
+	id: number;
+	name: string;
+	key: string;
+
+	createBy: string;
+}
+interface TableDataState {
+	ids: number[];
+	tableData: {
+		data: Array<TableDataRow>;
+		total: number;
+		loading: boolean;
+		param: {
+			pageNum: number;
+			pageSize: number;
+			id: number;
+		};
+	};
+}
+
+export default defineComponent({
+	name: 'deviceEditPro',
+	setup(prop, { emit }) {
+		const formRef = ref<HTMLElement | null>(null);
+		const state = reactive<DicState>({
+			isShowDialog: false,
+		    dialogFullScreen: false,
+			tableData: {
+				data: [],
+				total: 0,
+				loading: false,
+				param: {
+					pageNum: 1,
+					pageSize: 10,
+					id: 0,
+					propertyKey:'',
+				},
+			},
+		});
+		// 打开弹窗
+		const openDialog = (row: RuleFormState | null,devid) => {
+			resetForm();
+			if (row) {
+				console.log(row);
+				state.tableData.param.id = devid;
+				state.tableData.param.propertyKey=row.key
+				typeList();
+
+			}
+			state.isShowDialog = true;
+		};
+
+		const typeList = () => {
+			state.tableData.loading = true;
+			api.instance.getLogDetail(state.tableData.param).then((res: any) => {
+				state.tableData.data = res.List;
+				state.tableData.total = res.Total;
+				//state.ruleForm = res.data.dictType
+			}).finally(() => (state.tableData.loading = false));
+
+		};
+		const resetForm = () => {
+			state.tableData= {
+				data: [],
+				total: 0,
+				loading: false,
+				param: {
+					pageNum: 1,
+					pageSize: 10,
+				},
+			}
+		};
+		// 关闭弹窗
+		const closeDialog = () => {
+			state.isShowDialog = false;
+		};
+		const quanping=()=>{
+			state.dialogFullScreen = state.dialogFullScreen?false:true;
+		}
+		// 取消
+		const onCancel = () => {
+			closeDialog();
+		};
+
+		return {
+            Close,
+			quanping,
+			typeList,
+			openDialog,
+			closeDialog,
+			onCancel,
+			formRef,
+			...toRefs(state),
+		};
+	},
+});
+</script>
+<style scoped>
+.my-header {
+  display: flex;
+  flex-direction: row;
+  justify-content: space-between;
+}
+</style>

+ 873 - 0
src/views/iot/device/template/detail.vue

@@ -0,0 +1,873 @@
+<template>
+  <div class="system-dic-container">
+    <div class="content">
+      <div class="cont_box">
+        <div class="title">设备:{{ detail.name }}</div>
+        <div class="pro-status"><span :class="developer_status == 2 ? 'on' : 'off'"></span>{{ developer_status == 2 ? '在线' : '离线' }}</div>
+
+        <!-- <div class="pro-option" @click="CkOption">{{ developer_status == 2 ? '下线' : '上线' }}</div> -->
+      </div>
+    </div>
+
+		<div class="content-box">
+			<el-tabs v-model="activeName" class="demo-tabs" @tab-click="handleClick">
+
+				<el-tab-pane label="运行状态" name="3">
+					<div style=" display: flex; padding: 10px;flex-wrap: wrap;" >
+						<div class="ant-card">
+							<div class="ant-card-body">
+								<div class="cardflex">
+									<div>设备状态</div>
+									<div @click="getrunData()" style="cursor: pointer;">
+										<el-icon style="font-size: 18px;">
+											<ele-Refresh />
+										</el-icon>
+									</div>
+								</div>
+
+								<div class="statusname" v-if="areaData.status==0">未启用</div>
+								<div class="statusname" v-if="areaData.status==1">离线</div>
+								<div class="statusname" v-if="areaData.status==2">在线</div>
+								<div class="cardflex comtest">
+									<div> 数据时间</div>
+									<div>{{areaData.lastOnlineTime || '未启用'}}</div>
+								</div>
+							</div>
+						</div>
+
+						<div class="ant-card" v-for="(item, index) in areaData.properties" :key="index">
+							<div class="ant-card-body">
+								<div class="cardflex">
+									<div>{{item.name}}</div>
+									<div style="cursor: pointer;">
+										<el-icon  style="font-size: 18px;"  @click="getrunData()">
+											<ele-Refresh />
+										</el-icon>
+										<el-icon  style="font-size: 18px;    margin-left: 10px;" @click="onOpenListDetail(item)">
+											<ele-Expand />
+										</el-icon>
+									</div>
+								</div>
+
+								<div class="statusname">{{item.value}}{{item.unit}}</div>
+								<div class="">
+									<devantd :json="item.list" :antdid="item.key" v-if="item.type=='int' || item.type=='float'"/>
+								</div>
+							</div>
+						</div>
+
+
+
+					</div>
+				</el-tab-pane>
+
+
+
+
+				<el-tab-pane label="设备信息" name="1">
+					<div class="pro-box">
+						<div class="protitle">设备信息</div>
+            <div>
+              <el-button type="primary" @click="onOpenEditDic(detail)">编辑</el-button>
+            </div>
+          </div>
+
+					<div class="ant-descriptions-view">
+						<table>
+							<tbody>
+								<tr class="ant-descriptions-row">
+                  <th class="ant-descriptions-item-label ant-descriptions-item-colon">设备标识</th>
+                  <td class="ant-descriptions-item-content" colspan="1">{{ detail.key }}</td>
+									<th class="ant-descriptions-item-label ant-descriptions-item-colon">设备名称</th>
+									<td class="ant-descriptions-item-content" colspan="1">{{ detail.name }}</td>
+									<th class="ant-descriptions-item-label ant-descriptions-item-colon">所属产品</th>
+									<td class="ant-descriptions-item-content" colspan="1">{{ detail.productName }}</td>
+
+								</tr>
+								<tr class="ant-descriptions-row">
+									<th class="ant-descriptions-item-label ant-descriptions-item-colon">消息协议</th>
+									<td class="ant-descriptions-item-content" colspan="1">{{ prodetail.messageProtocol }}</td>
+									<th class="ant-descriptions-item-label ant-descriptions-item-colon">链接协议</th>
+									<td class="ant-descriptions-item-content" colspan="1">{{ prodetail.transportProtocol }}</td>
+                  <th class="ant-descriptions-item-label ant-descriptions-item-colon">设备类型</th>
+                  <td class="ant-descriptions-item-content" colspan="1">{{ prodetail.deviceType }}</td>
+								</tr>
+								<tr class="ant-descriptions-row">
+                  <th class="ant-descriptions-item-label ant-descriptions-item-colon">固件版本</th>
+                  <td class="ant-descriptions-item-content" colspan="1">{{ prodetail.version }}</td>
+									<th class="ant-descriptions-item-label ant-descriptions-item-colon">注册时间</th>
+									<td class="ant-descriptions-item-content" colspan="1">{{ prodetail.updatedAt }}</td>
+									<th class="ant-descriptions-item-label ant-descriptions-item-colon">最后上线时间</th>
+									<td class="ant-descriptions-item-content" colspan="1">{{ prodetail.lastOnlineTime || '' }}</td>
+								</tr>
+                <tr class="ant-descriptions-row">
+                  <th class="ant-descriptions-item-label ant-descriptions-item-colon">说明</th>
+                  <td class="ant-descriptions-item-content" colspan="5">{{ prodetail.desc }}</td>
+                </tr>
+
+
+              </tbody>
+								</table>
+								</div>
+								</el-tab-pane>
+        <el-tab-pane label="物模型" name="2">
+          <div class="wu-box">
+            <el-tabs type="border-card" v-model="activetab" @tab-click="wuhandleClick">
+              <el-tab-pane label="属性定义" name="attr">
+                <div class="wu-title">
+                  <div class="title">属性定义</div>
+                  <div>
+                    <el-button type="primary" @click="onOpenEditAttr()">添加</el-button>
+                  </div>
+                </div>
+
+                <el-table style="width: 100%" :data="tableData.data" v-if="activetab == 'attr'">
+                  <el-table-column label="属性标识" align="center" prop="key" />
+                  <el-table-column label="属性名称" prop="name" :show-overflow-tooltip="true" />
+                  <el-table-column prop="valueType" label="数据类型" width="100" align="center">
+                    <template #default="scope">
+                      <span>{{ scope.row.valueType.type }}</span>
+                    </template>
+                  </el-table-column>
+                  <el-table-column prop="decimals" label="精度" width="60" align="center">
+                    <template #default="scope">
+                      <span>{{ scope.row.valueType.decimals }}</span>
+                    </template>
+                  </el-table-column>
+                  <el-table-column prop="unit" label="单位" width="60" align="center">
+                    <template #default="scope">
+                      <span>{{ scope.row.valueType.unit }}</span>
+                    </template>
+                  </el-table-column>
+                  <el-table-column prop="accessMode" label="是否只读" width="120" align="center">
+                    <template #default="scope">
+                      <el-tag type="info" size="small" v-if="scope.row.accessMode">只读</el-tag>
+                      <el-tag type="success" size="small" v-else>读写</el-tag>
+                    </template>
+                  </el-table-column>
+                  <el-table-column label="说明" prop="desc" :show-overflow-tooltip="true" />
+                  <el-table-column label="操作" width="300" align="center" fixed="right">
+                    <template #default="scope">
+                      <el-button size="small" text type="warning" @click="onEditAttr(scope.row)">修改</el-button>
+                      <el-button size="small" text type="danger" @click="onRowDel(scope.row.key, 'attr')">删除</el-button>
+                    </template>
+                  </el-table-column>
+                </el-table>
+              </el-tab-pane>
+              <el-tab-pane label="功能定义" name="fun">
+                <div class="wu-title">
+                  <div class="title">功能定义</div>
+                  <div>
+                    <el-button type="primary" @click="onOpenEditFun()">添加</el-button>
+                  </div>
+                </div>
+
+                <el-table style="width: 100%" :data="tableData.data" v-if="activetab == 'fun'">
+                  <el-table-column label="功能标识" align="center" prop="key" />
+                  <el-table-column label="名称" prop="name" :show-overflow-tooltip="true" />
+
+                  <el-table-column label="描述" prop="desc" :show-overflow-tooltip="true" />
+                  <el-table-column label="操作" width="300" align="center" fixed="right">
+                    <template #default="scope">
+                      <el-button size="small" text type="warning" @click="onEditFun(scope.row)">修改</el-button>
+                      <el-button size="small" text type="danger" @click="onRowDel(scope.row.key, 'fun')">删除</el-button>
+                    </template>
+                  </el-table-column>
+                </el-table>
+              </el-tab-pane>
+              <el-tab-pane label="事件定义" name="event">
+                <div class="wu-title">
+                  <div class="title">事件定义</div>
+                  <div>
+                    <el-button type="primary" @click="onOpenEditEvent()">添加</el-button>
+                  </div>
+                </div>
+
+                <el-table style="width: 100%" :data="tableData.data" v-if="activetab == 'event'">
+                  <el-table-column label="事件标识" align="center" prop="key" />
+                  <el-table-column label="名称" prop="name" :show-overflow-tooltip="true" />
+                  <el-table-column prop="level" label="事件级别" width="120" align="center">
+                    <template #default="scope">
+                      <el-tag type="primary" size="small" v-if="scope.row.level == 0">普通</el-tag>
+                      <el-tag type="warning" size="small" v-if="scope.row.level == 1">警告</el-tag>
+                      <el-tag type="danger" size="small" v-if="scope.row.level == 2">紧急</el-tag>
+                    </template>
+                  </el-table-column>
+                  <el-table-column label="描述" prop="desc" :show-overflow-tooltip="true" />
+
+                  <el-table-column label="操作" width="300" align="center" fixed="right">
+                    <template #default="scope">
+                      <el-button size="small" text type="warning" @click="onEditEvent(scope.row)">修改</el-button>
+                      <el-button size="small" text type="danger" @click="onRowDel(scope.row.key, 'event')">删除</el-button>
+                    </template>
+                  </el-table-column>
+                </el-table>
+              </el-tab-pane>
+              <el-tab-pane label="标签定义" name="tab">
+                <div class="wu-title">
+                  <div class="title">标签定义</div>
+                  <div>
+                    <el-button type="primary" @click="onOpenEditTab()">添加</el-button>
+                  </div>
+                </div>
+
+
+                <el-table style="width: 100%" :data="tableData.data" v-if="activetab == 'tab'">
+                  <el-table-column label="属性标识" align="center" prop="key" />
+                  <el-table-column label="属性名称" prop="name" :show-overflow-tooltip="true" />
+                  <el-table-column prop="valueType" label="数据类型" width="120" align="center">
+                    <template #default="scope">
+                      <span>{{ scope.row.valueType.type }}</span>
+                    </template>
+                  </el-table-column>
+                  <el-table-column prop="accessMode" label="是否只读" width="120" align="center">
+                    <template #default="scope">
+                      <el-tag type="info" size="small" v-if="scope.row.accessMode">只读</el-tag>
+                      <el-tag type="success" size="small" v-else>读写</el-tag>
+                    </template>
+                  </el-table-column>
+                  <el-table-column label="描述" prop="desc" :show-overflow-tooltip="true" />
+                  <el-table-column label="操作" width="300" align="center" fixed="right">
+                    <template #default="scope">
+                      <el-button size="small" text type="warning" @click="onEditTag(scope.row)">修改</el-button>
+                      <el-button size="small" text type="danger" @click="onRowDel(scope.row.key, 'tab')">删除</el-button>
+                    </template>
+                  </el-table-column>
+                </el-table>
+              </el-tab-pane>
+            </el-tabs>
+            <pagination v-show="tableData.total > 0" :total="tableData.total" v-model:page="tableData.param.pageNum" v-model:limit="tableData.param.pageSize" @pagination="getList" />
+          </div>
+        </el-tab-pane>
+
+
+        <el-tab-pane label="日志管理" name="4">
+          <div class="system-user-search mb15">
+            <el-form :model="logtableData.param" ref="queryRef" :inline="true" label-width="68px">
+              <el-form-item label="日志类型" prop="types">
+                <el-select v-model="logtableData.param.types" placeholder="日志类型" clearable size="default">
+                  <el-option v-for="item in logTypeData" :key="item" :label="item" :value="item" />
+                </el-select>
+              </el-form-item>
+
+              <el-form-item label="创建时间" prop="dateRange">
+                <el-date-picker v-model="logtableData.param.dateRange" size="default" value-format="YYYY-MM-DD" type="daterange" range-separator="-" start-placeholder="开始日期" end-placeholder="结束日期"></el-date-picker>
+              </el-form-item>
+              <el-form-item>
+                <el-button size="default" type="primary" class="ml10" @click="getlog">
+                  <el-icon>
+                    <ele-Search />
+                  </el-icon>
+                  查询
+                </el-button>
+                <el-button size="default" @click="resetQuery(queryRef)">
+                  <el-icon>
+                    <ele-Refresh />
+                  </el-icon>
+                  重置
+                </el-button>
+              </el-form-item>
+            </el-form>
+          </div>
+          <el-table style="width: 100%" :data="logtableData.data">
+            <el-table-column label="类型" align="center" prop="type" />
+            <el-table-column label="时间" prop="ts" :show-overflow-tooltip="true" />
+
+            <el-table-column label="内容" prop="content" :show-overflow-tooltip="true" />
+            <el-table-column label="操作" width="300" align="center" fixed="right">
+              <template #default="scope">
+                <el-button size="small" text type="warning" @click="onLogDetail(scope.row)">查看</el-button>
+              </template>
+            </el-table-column>
+          </el-table>
+
+          <pagination v-show="logtableData.total > 0" :total="logtableData.total" v-model:page="logtableData.param.pageNum" v-model:limit="logtableData.param.pageSize" @pagination="getlog" />
+        </el-tab-pane>
+      </el-tabs>
+    </div>
+    <EditDic ref="editDicRef" @typeList="typeList" />
+    <EditAttr ref="editAttrRef" @typeList="getproperty" />
+    <EditFun ref="editFunRef" @typeList="getfunction" />
+    <EditEvent ref="editEventRef" @typeList="getevent" />
+    <EditTab ref="editTabRef" @typeList="gettab" />
+    <ListDic ref="listDicRef" />
+
+    <el-dialog v-model="dialogVisible" title="返回Json数据" width="30%">
+      <JsonViewer :value="jsonData" boxed sort theme="jv-dark" @click="onKeyclick" />
+
+      <template #footer>
+        <span class="dialog-footer">
+          <el-button @click="dialogVisible = false">关闭</el-button>
+        </span>
+      </template>
+    </el-dialog>
+  </div>
+</template>
+<script lang="ts">
+import { toRefs, reactive, onMounted, ref, defineComponent } from 'vue';
+import { ElMessageBox, ElMessage, FormInstance } from 'element-plus';
+
+import 'vue3-json-viewer/dist/index.css';
+
+import EditDic from '../product/component/editPro.vue';
+import EditAttr from '../product/component/editAttr.vue';
+import EditFun from '../product/component/editFun.vue';
+import EditEvent from '../product/component/editEvent.vue';
+import EditTab from '../product/component/editTab.vue';
+import devantd from '/@/components/devantd/index.vue';
+import ListDic from './component/list.vue';
+
+
+import { useRoute } from 'vue-router';
+
+import api from '/@/api/device';
+
+interface TableDataState {
+  ids: number[];
+  tableData: {
+    data: [];
+    total: number;
+    loading: boolean;
+    param: {
+      pageNum: number;
+      pageSize: number;
+      name: string;
+      deviceType: string;
+      status: string;
+      dateRange: string[];
+    };
+  };
+  logtableData: {
+    data: [];
+    total: number;
+    loading: boolean;
+    param: {
+      pageNum: number;
+      pageSize: number;
+      name: string;
+      deviceType: string;
+      status: string;
+      dateRange: string[];
+    };
+  };
+}
+export default defineComponent({
+  name: 'deviceEditPro',
+  components: { EditDic, EditAttr, EditFun, EditEvent, EditTab, devantd, ListDic },
+
+	setup(prop, context) {
+		const route = useRoute();
+		const editDicRef = ref();
+		const editAttrRef = ref();
+		const editFunRef = ref();
+		const listDicRef=ref();
+		const editEventRef = ref();
+		const editTabRef = ref();
+		const state = reactive<TableDataState>({
+			areaData:[],
+			isShowDialog: false,
+			dialogVisible: false,
+			logTypeData: [],
+			jsonData: '',
+			activeName: '3', // 分类数据
+			activetab: 'attr', // 分类数据
+			detail: [],
+			prodetail: [],
+			product_id: 0,
+			developer_status: 0,
+			tableData: {
+				data: [],
+				total: 0,
+				loading: false,
+				param: {
+					pageNum: 1,
+					productId: 0,
+					pageSize: 10,
+					status: '',
+					dateRange: [],
+				},
+			},
+			logtableData: {
+				data: [],
+				total: 0,
+				loading: false,
+				param: {
+					pageNum: 1,
+					productId: 0,
+					pageSize: 10,
+					status: '',
+					dateRange: [],
+				},
+			},
+		});
+
+		onMounted(() => {
+			const ids = route.params && route.params.id;
+			api.instance.detail(ids).then((res: any) => {
+				state.detail = res.data;
+				state.developer_status = res.data.status;
+				state.tableData.param.productId = res.data.product.id;
+				state.product_id = res.data.product.id;
+				getrunData();
+				api.product.detail(res.data.product.id).then((res: any) => {
+					state.prodetail = res.data;
+					console.log(res.data);
+				});
+
+				//第一次加载
+				api.model.property(state.tableData.param).then((res: any) => {
+					state.tableData.data = res.Data;
+					state.tableData.total = res.Total;
+				});
+			});
+
+		});
+
+
+    const onLogDetail = (row: TableDataRow) => {
+      state.jsonData = JSON.parse(row.content);
+      console.log(JSON.parse(row.content));
+      state.dialogVisible = true;
+    };
+
+    //编辑属性
+    const onEditAttr = (row: TableDataRow) => {
+      editAttrRef.value.openDialog(row, state.product_id);
+    };
+
+    //编辑功能
+    const onEditFun = (row: TableDataRow) => {
+      editFunRef.value.openDialog(row, state.product_id);
+    };
+
+    //编辑事件
+    const onEditEvent = (row: TableDataRow) => {
+      editEventRef.value.openDialog(row, state.product_id);
+    };
+
+    //编辑标签
+    const onEditTag = (row: TableDataRow) => {
+      editTabRef.value.openDialog(row, state.product_id);
+    };
+
+    //打开添加属性弹窗
+    const onOpenEditAttr = () => {
+      editAttrRef.value.openDialog({ product_id: state.product_id, id: 0, accessMode: 0 });
+    };
+
+    //打开添加功能弹窗
+    const onOpenEditFun = () => {
+      editFunRef.value.openDialog({ product_id: state.product_id, id: 0 });
+    };
+    //打开添加事件弹窗
+    const onOpenEditEvent = () => {
+      editEventRef.value.openDialog({ product_id: state.product_id, id: 0, level: 0 });
+    };
+
+    //打开添加事件弹窗
+    const onOpenEditTab = () => {
+      editTabRef.value.openDialog({ product_id: state.product_id, id: 0, accessMode: 0 });
+    };
+
+    //查看日志列表
+    const onOpenListDetail = (row: TableDataRow) => {
+      listDicRef.value.openDialog(row, state.detail.id);
+    };
+
+    // 打开修改产品弹窗
+    const onOpenEditDic = (row: TableDataRow) => {
+      editDicRef.value.openDialog(row);
+    };
+
+    // 删除产品
+    const onRowDel = (key, type) => {
+      let msg = `此操作将永久删除该数据吗?,是否继续?`;
+
+      if (key.length === 0) {
+        ElMessage.error('请选择要删除的数据。');
+        return;
+      }
+      ElMessageBox.confirm(msg, '提示', {
+        confirmButtonText: '确认',
+        cancelButtonText: '取消',
+        type: 'warning',
+      })
+        .then(() => {
+          if (type == 'attr') {
+            api.model.propertydel(state.product_id, key).then(() => {
+              ElMessage.success('删除成功');
+              getproperty();
+            });
+          }
+          if (type == 'fun') {
+            api.model.functiondel(state.product_id, key).then(() => {
+              ElMessage.success('删除成功');
+              getfunction();
+            });
+          }
+          if (type == 'event') {
+            api.model.eventdel(state.product_id, key).then(() => {
+              ElMessage.success('删除成功');
+              getevent();
+            });
+          }
+          if (type == 'tab') {
+            api.model.tagdel(state.product_id, key).then(() => {
+              ElMessage.success('删除成功');
+              tagdel();
+            });
+          }
+        })
+        .catch(() => { });
+    };
+
+    //根据不同类型获取列表
+    const getList = () => {
+      switch (state.activetab) {
+        case 'attr':
+          getproperty();
+          break;
+        case 'fun':
+          getfunction();
+          break;
+        case 'event':
+          getevent();
+          break;
+        case 'tab':
+          gettab();
+          break;
+      }
+    };
+
+    const getproperty = () => {
+      api.model.property(state.tableData.param).then((res: any) => {
+        state.tableData.data = res.Data;
+        state.tableData.total = res.Total;
+      });
+    };
+
+    const getfunction = () => {
+      api.model.function(state.tableData.param).then((res: any) => {
+        state.tableData.data = res.Data;
+        state.tableData.total = res.Total;
+      });
+    };
+    const getevent = () => {
+      api.model.event(state.tableData.param).then((res: any) => {
+        state.tableData.data = res.Data;
+        state.tableData.total = res.Total;
+      });
+    };
+
+    const gettab = () => {
+      api.model.tag(state.tableData.param).then((res: any) => {
+        state.tableData.data = res.Data;
+        state.tableData.total = res.Total;
+      });
+    };
+
+    const wuhandleClick = (tab: TabsPaneContext) => {
+      state.activetab = tab.props.name;
+      switch (tab.props.name) {
+        case 'attr':
+          getproperty();
+          break;
+        case 'fun':
+          getfunction();
+          break;
+        case 'event':
+          getevent();
+          break;
+        case 'tab':
+          gettab();
+          break;
+      }
+    };
+
+    const handleClick = (tab: TabsPaneContext, event: Event) => {
+      console.log(tab, event);
+      if (tab.props.name == 4) {
+        //获取日志
+        getlog();
+        getlogtype();
+      } else if (tab.props.name == 2) {
+        getList();
+      } else if (tab.props.name == 3) {
+        getrunData();
+      }
+    };
+
+    const getrunData = () => {
+      api.instance.getrun_status({ id: state.detail.id }).then((res: any) => {
+          state.areaData = res
+          let properties=state.areaData.properties;
+
+          var temp = new Array();
+
+          properties.forEach(function (item, index) {
+              let datalist=item.list;
+              temp[index] = [];
+              var temps = new Array();
+              datalist.forEach(function (a, b) {
+                 if(b<15){
+                  temps.push(a);
+                 }
+              });
+              temp[index]['name']=item.name
+              temp[index]['key']=item.key
+              temp[index]['type']=item.type
+              temp[index]['unit']=item.unit
+              temp[index]['value']=item.value
+              temp[index]['list']=temps
+
+          });
+
+          state.areaData.properties=temp;
+      });
+
+    };
+
+    const getlogtype = () => {
+      api.instance.getlogcate({}).then((res: any) => {
+        state.logTypeData = res.list;
+      });
+    };
+
+    const getlog = () => {
+      state.logtableData.param.deviceKey = state.detail.key;
+      api.instance.getLogList(state.logtableData.param).then((res: any) => {
+        console.log(res, '22222222');
+        state.logtableData.data = res.list;
+        state.logtableData.total = res.Total;
+      });
+    };
+
+    const CkOption = () => {
+      if (state.developer_status == 2) {
+        api.instance.devoffline({ id: state.detail.id }).then((res: any) => {
+          ElMessage.success('操作成功');
+          state.developer_status = 1;
+        });
+      } else {
+        api.instance.devonline({ id: state.detail.id }).then((res: any) => {
+          ElMessage.success('操作成功');
+          state.developer_status = 2;
+        });
+      }
+    };
+    const tinyAreas = () => {
+      var data = state.data;
+
+      const tinyArea = new TinyArea('container', {
+        height: 60,
+        autoFit: false,
+        data,
+        smooth: true,
+        areaStyle: {
+          fill: '#d6e3fd',
+        },
+      });
+      tinyArea.render();
+    }
+    return {
+      tinyAreas,
+      editDicRef,
+      editAttrRef,
+      listDicRef,
+      editFunRef,
+      editEventRef,
+      editTabRef,
+      onOpenListDetail,
+      getrunData,
+      getlog,
+      getlogtype,
+      onLogDetail,
+      CkOption,
+      onRowDel,
+      onEditFun,
+      onEditEvent,
+      onEditTag,
+      onEditAttr,
+      getList,
+      getproperty,
+      getfunction,
+      getevent,
+      gettab,
+      wuhandleClick,
+      onOpenEditTab,
+      onOpenEditEvent,
+      onOpenEditAttr,
+      onOpenEditFun,
+      onOpenEditDic,
+      handleClick,
+      ...toRefs(state),
+    };
+  },
+});
+</script>
+  <style>
+.content {
+	background: #fff;
+	width: 100%;
+	padding: 20px;
+}
+.content-box {
+	background: #fff;
+	width: 100%;
+	padding: 20px;
+	margin-top: 20px;
+}
+.cont_box {
+	display: flex;
+}
+.cont_box .title {
+	font-size: 24px;
+}
+.cont_box .pro-status {
+	line-height: 40px;
+	margin-left: 30px;
+}
+.cont_box .pro-status .on {
+	background: #52c41a;
+}
+.cont_box .pro-status .off {
+	background: #c41a1a;
+}
+.cont_box .pro-status span {
+	position: relative;
+	top: -1px;
+	display: inline-block;
+	width: 6px;
+	height: 6px;
+	vertical-align: middle;
+	border-radius: 50%;
+	margin-right: 5px;
+}
+.cont_box .pro-option {
+	line-height: 40px;
+	margin-left: 10px;
+	color: #1890ff;
+	cursor: pointer;
+}
+.content-box .pro-box {
+	display: flex;
+	padding: 10px;
+  justify-content: space-between;
+
+}
+.content-box .pro-box .protitle {
+	font-size: 18px;
+	font-weight: bold;
+	line-height: 35px;
+}
+.content-box .pro-box .buttonedit {
+	border: 0px;
+	color: #1890ff;
+}
+table {
+	border-collapse: collapse;
+	text-indent: initial;
+	border-spacing: 2px;
+}
+tbody {
+	box-sizing: border-box;
+	display: table-row-group;
+	vertical-align: middle;
+	border-color: inherit;
+}
+
+tr {
+	display: table-row;
+	vertical-align: inherit;
+	border-color: inherit;
+}
+.ant-descriptions-view {
+	width: 100%;
+	overflow: hidden;
+	border-radius: 4px;
+}
+.ant-descriptions-view {
+	border: 1px solid #e8e8e8;
+}
+.ant-descriptions-view table {
+	width: 100%;
+	table-layout: fixed;
+}
+.ant-descriptions-view > table {
+	table-layout: auto;
+}
+.ant-descriptions-row {
+	border-bottom: 1px solid #e8e8e8;
+}
+.ant-descriptions-item-label {
+	color: rgba(0, 0, 0, 0.85);
+	font-weight: 400;
+	font-size: 14px;
+	line-height: 1.5;
+}
+.ant-descriptions-item-label {
+	padding: 16px 24px;
+	border-right: 1px solid #e8e8e8;
+}
+.ant-descriptions-item-label {
+	background-color: #fafafa;
+}
+.ant-descriptions-item-content {
+	padding: 16px 24px;
+	border-right: 1px solid #e8e8e8;
+	display: table-cell;
+	color: rgba(0, 0, 0, 0.65);
+	font-size: 14px;
+	line-height: 1.5;
+}
+.wu-box {
+	border: #e8e8e8 solid 1px;
+	padding: 20px;
+	width: 100%;
+}
+.wu-box .wu-title {
+	display: flex;
+	flex-direction: row;
+	justify-content: space-between;
+	padding: 20px;
+	border-bottom: #e8e8e8 1px solid;
+}
+.wu-box .wu-title .title {
+	font-size: 18px;
+}
+.ant-card {
+	box-sizing: border-box;
+	margin: 10px;
+	width: 23.2%;
+	font-size: 14px;
+	font-variant: tabular-nums;
+  border: 1px solid  var(--next-border-color-light);
+
+	line-height: 1.5;
+	list-style: none;
+	font-feature-settings: 'tnum';
+	position: relative;
+	border-radius: 2px;
+	transition: all 0.3s;
+}
+.ant-card-body {
+	padding: 24px;
+	zoom: 1;
+}
+.cardflex {
+	display: flex;
+	justify-content: space-between;
+}
+.statusname {
+	font-size: 30px;
+	margin-top: 10px;
+  margin-bottom: 15px;
+}
+.comtest {
+	margin-top: 20px;
+	height: 30px;
+	line-height: 30px;
+}
+</style>
+
+

+ 90 - 0
src/views/iot/device/template/index.vue

@@ -0,0 +1,90 @@
+<template>
+	<div class="page">
+		<el-card shadow="hover">
+			<div class="search">
+				<el-form :model="params" :inline="true" ref="queryRef">
+					<el-form-item label="模板名称" prop="title">
+						<el-input v-model="params.title" placeholder="请输入模板名称" clearablestyle="width: 240px" @keyup.enter.native="getList(1)" />
+					</el-form-item>
+					<el-form-item>
+						<el-button size="default" type="primary" class="ml10" @click="getList(1)">
+							<el-icon>
+								<ele-Search />
+							</el-icon>
+							查询
+						</el-button>
+						<el-button size="default" @click="resetQuery()">
+							<el-icon>
+								<ele-Refresh />
+							</el-icon>
+							重置
+						</el-button>
+						<el-button type="success" @click="addOrEdit()" v-auth="'add'">
+							<el-icon>
+								<ele-FolderAdd />
+							</el-icon>
+							新增模板
+						</el-button>
+					</el-form-item>
+				</el-form>
+			</div>
+			<el-table :data="tableData" style="width: 100%" v-loading="loading">
+				<el-table-column type="index" label="序号" width="80" align="center" />
+				<el-table-column prop="title" label="模板名称" align="center" show-overflow-tooltip></el-table-column>
+				<el-table-column prop="mode" label="模式" align="center" width="120" show-overflow-tooltip>
+          <template #default="{ row }">
+          {{ row.mode === 0 ? '顺序读取' : '批量读取' }}
+        </template>
+        </el-table-column>
+				<el-table-column prop="remarks" label="备注" align="center" show-overflow-tooltip></el-table-column>
+				<el-table-column label="操作" width="160" align="center">
+					<template #default="scope">
+						<el-button size="small" text type="primary" >导入</el-button>
+						<el-button size="small" text type="primary" >导出</el-button>
+						<el-button size="small" text type="primary" @click="addOrEdit(scope.row)" v-auth="'edit'">详情</el-button>
+						<el-button size="small" text type="danger" @click="onDel(scope.row)" v-auth="'del'">删除</el-button>
+					</template>
+				</el-table-column>
+			</el-table>
+			<pagination v-if="params.total" :total="params.total" v-model:page="params.page" v-model:limit="params.size" @pagination="getList()" />
+		</el-card>
+		<EditForm ref="editFormRef" @getList="getList()"></EditForm>
+	</div>
+</template>
+
+<script lang="ts" setup>
+import { ref } from 'vue';
+import EditForm from './component/edit.vue';
+import api from '/@/api/device/modbus';
+import { ElMessageBox, ElMessage } from 'element-plus';
+import { useSearch } from '/@/hooks/useCommonModbus';
+
+const editFormRef = ref();
+const queryRef = ref();
+
+const { params, tableData, getList, loading } = useSearch(api.template.getList, 'list', { title: '', number: '' });
+
+getList();
+
+const addOrEdit = async (row?: any) => {
+	editFormRef.value.open(row);
+};
+
+// 重置表单
+const resetQuery = () => {
+	queryRef.value.resetFields();
+	getList(1);
+};
+
+const onDel = (row: any) => {
+	ElMessageBox.confirm(`此操作将删除接口:“${row.title}”,是否继续?`, '提示', {
+		confirmButtonText: '确认',
+		cancelButtonText: '取消',
+		type: 'warning',
+	}).then(async () => {
+		await api.channel.deleteDevice({ number: row.number });
+		ElMessage.success('删除成功');
+		getList();
+	});
+};
+</script>