소스 검색

增加资产关系页面,修复资产管理报错问题

yanglzh 11 달 전
부모
커밋
2bd9ce1d14

+ 7 - 0
src/api/device/index.ts

@@ -119,6 +119,13 @@ export default {
     detail: (params: object) => get('/asset/asset/get', params),
     delete: (params: object) => del('/asset/asset/delete', params),
   },
+  assetRelationship: {
+    getList: (params: object) => get('/asset/assetRelationship/list', params),
+    add: (params: object) => post('/asset/assetRelationship/add', params),
+    edit: (params: object) => put('/asset/assetRelationship/edit', params),
+    detail: (params: object) => get('/asset/assetRelationship/get', params),
+    delete: (params: object) => del('/asset/assetRelationship/delete', params),
+  },
   dev_asset_metadata: {
     getList: (params: object) => get('/asset/assetMetadata/list', params),
     add: (params: object) => post('/asset/assetMetadata/add', params),

+ 2 - 7
src/views/iot/property/attribute/index.vue

@@ -36,7 +36,7 @@
 								<el-icon v-if="data.is_type != '2'">
 									<Folder />
 								</el-icon>
-                <SvgIcon name="iconfont icon-siweidaotu" v-if="data.is_type == '2'"></SvgIcon>
+								<SvgIcon name="iconfont icon-siweidaotu" v-if="data.is_type == '2'"></SvgIcon>
 								{{ node.label }}
 							</span>
 						</div>
@@ -119,11 +119,6 @@ const getCateList = () => {
 			params.productKey = res.product[0].key
 			getList()
 			mergedData.value = matchProductsToCategories(productData.value, cateData.value);
-
-			// 默认加载第一个设备对应属性
-			if (productData.value.length > 0) {
-				handleNodeClick(mergedData.value[0].children[0])
-			}
 		});
 	})
 }
@@ -153,7 +148,7 @@ const buildTree = (category: any, productData: any) => {
 		label: category.name,
 		key: category.key,
 		is_type: '1', // 1是分类
-		children: [],
+		children: [] as any[],
 	}
 
 	if (category.children && category.children.length > 0) {

+ 2 - 7
src/views/iot/property/dossier/index.vue

@@ -37,7 +37,7 @@
 								<el-icon v-if="data.is_type != '2'">
 									<Folder />
 								</el-icon>
-                <SvgIcon name="iconfont icon-siweidaotu" v-if="data.is_type == '2'"></SvgIcon>
+								<SvgIcon name="iconfont icon-siweidaotu" v-if="data.is_type == '2'"></SvgIcon>
 								{{ node.label }}
 							</span>
 						</div>
@@ -117,11 +117,6 @@ const getCateList = () => {
 		device.product.getLists({}).then((res: any) => {
 			productData.value = res.product
 			mergedData.value = matchProductsToCategories(productData.value, cateData.value)
-
-			// 默认加载第一个设备对应属性
-			if (productData.value.length > 0) {
-				handleNodeClick(mergedData.value[0].children[0])
-			}
 		})
 	})
 }
@@ -151,7 +146,7 @@ const buildTree = (category: any, productData: any) => {
 		label: category.name,
 		key: category.key,
 		is_type: '1', // 1是分类
-		children: [],
+		children: [] as any[],
 	}
 
 	if (category.children && category.children.length > 0) {

+ 145 - 0
src/views/iot/property/relationship/component/from.vue

@@ -0,0 +1,145 @@
+
+<template>
+	<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-input v-model="formData[item.name]" :placeholder="'请输入' + item.title" @input="saveData()" :readonly="disable" />
+      </el-form-item>
+
+      <el-form-item :label="item.title + ':'" :prop="item.name" class="form-item" v-if="item.types === 'textarea'">
+        <el-input v-model="formData[item.name]" type="textarea" @input="saveData()" :readonly="disable" />
+      </el-form-item>
+
+      <el-form-item v-if="item.types === 'date'" :label="item.title + ':'">
+        <el-date-picker v-model="formData[item.name]" :default-value="item.value" type="date" value-format="YYYY-MM-DD" placeholder="请选择时间" class="w100" clearable @change="saveData()" :readonly="disable" />
+      </el-form-item>
+
+      <el-form-item :label="item.title + ':'" prop="path" v-if="item.types === 'file'">
+        <el-upload class="avatar-uploader" :action="uploadUrl" :headers="headers" :show-file-list="false" :on-success="customCallback(item.name)" :disabled="disable">
+          <img v-if="formData[item.name]" :src="formData[item.name]" class="avatar" />
+          <el-icon v-else class="avatar-uploader-icon" v-if="!disable">
+            <Plus />
+          </el-icon>
+        </el-upload>
+      </el-form-item>
+
+		</div>
+	</div>
+</template>
+
+<script lang="ts">
+import {onMounted, defineComponent, reactive, toRefs} from 'vue'
+import getOrigin from '/@/utils/origin'
+import { Plus } from '@element-plus/icons-vue'
+import { getToken } from "/@/utils/auth";
+
+interface FromState {
+  dataList: any,
+  formData: any,
+  disable: boolean,
+}
+
+export default defineComponent({
+  name: 'dossierFromData',
+  props: {
+    DataList: {
+      type: Array,
+      default: () => [],
+    },
+    disable: {
+      type: Boolean,
+      default: () => false,
+    }
+  },
+  setup(prop, { emit }) {
+    const uploadUrl = getOrigin(import.meta.env.VITE_API_URL + '/common/singleFile')
+    const headers = {
+      Authorization: 'Bearer ' + getToken(),
+    }
+    // const emit = defineEmits(['SetSaveData'])
+
+    const state = reactive<FromState>({
+      dataList: prop.DataList,
+      formData: {},
+      disable: prop.disable,
+    })
+
+    onMounted(() => {
+      initFormData();
+    });
+
+    const initFormData = () => {
+      for (const item of state.dataList) {
+        state.formData[item.name] = item.value ? item.value : ''
+      }
+    }
+
+    const customCallback = (customValue: string) => {
+      return function (file: any) {
+        state.formData[customValue] = file.data.full_path
+        saveData();
+      }
+    }
+
+    const saveData = () => {
+      const updatedData = []
+
+      for (const item of state.dataList) {
+        updatedData.push({
+          productKey: item.productKey,
+          name: item.name,
+          value: state.formData[item.name], // 更新为formData的实际值
+          fieldName: item.fieldName,
+        })
+      }
+
+      emit('SetSaveData', updatedData)
+    }
+
+    return {
+      headers,
+      uploadUrl,
+      customCallback,
+      saveData,
+      ...toRefs(state),
+    };
+  },
+
+  components: {Plus},
+})
+</script>
+
+<style scoped lang="scss">
+.form-item {
+	flex: 0 0 25%;
+}
+
+.avatar-uploader .avatar {
+	width: 178px;
+	height: 178px;
+	display: block;
+}
+</style>
+<style scoped>
+.avatar-uploader .el-upload {
+	border: 1px dashed var(--el-border-color);
+	border-radius: 6px;
+	cursor: pointer;
+	position: relative;
+	overflow: hidden;
+	transition: var(--el-transition-duration-fast);
+}
+
+.avatar-uploader .el-upload:hover {
+	border-color: var(--el-color-primary);
+}
+
+.el-icon.avatar-uploader-icon {
+	font-size: 28px;
+	color: #8c939d;
+	width: 178px;
+	height: 178px;
+	text-align: center;
+}
+</style>

+ 239 - 0
src/views/iot/property/relationship/edit.vue

@@ -0,0 +1,239 @@
+<template>
+  <el-dialog class="api-edit" v-model="showDialog" :title="`${formData.id ? '编辑设备档案' : '新增设备档案'}`" width="800px" :close-on-click-modal="false" :close-on-press-escape="false">
+    <el-form class="inline-form" ref="formRef" :model="formData" :rules="ruleForm" label-width="120px">
+
+      <el-row>
+        <el-col :span="12">
+          <el-form-item label="选择产品" prop="productKey">
+            <el-select v-model="formData.productKey" placeholder="请选择产品" class="w100" disabled>
+              <el-option v-for="item in productData" :key="item.key" :label="item.name" :value="item.key">
+                <span style="float: left">{{ item.name }}</span>
+                <span style="float: right; font-size: 13px">{{ item.key }}</span>
+              </el-option>
+            </el-select>
+          </el-form-item>
+        </el-col>
+
+        <el-col :span="12">
+          <el-form-item label="选择设备" prop="deviceKey">
+            <el-select v-model="formData.deviceKey" placeholder="请选择设备" class="w100" filterable clearable @change="handleSelectionChange">
+              <el-option v-for="item in deviceList" :key="item.key" :label="item.name" :value="item.key">
+                <span style="float: left">{{ item.name }}</span>
+                <span style="float: right; font-size: 13px">{{ item.key }}</span>
+              </el-option>
+            </el-select>
+          </el-form-item>
+        </el-col>
+      </el-row>
+
+      <el-row>
+        <el-col :span="12">
+          <el-form-item label="设备名称" prop="deviceName">
+            <el-input v-model.trim="formData.deviceName" placeholder="请输入设备名称" disabled />
+          </el-form-item>
+        </el-col>
+
+        <el-col :span="12">
+          <el-form-item label="设备编码" prop="deviceNumber">
+            <el-input v-model.trim="formData.deviceNumber" placeholder="请输入设备编码" />
+          </el-form-item>
+        </el-col>
+      </el-row>
+
+      <el-row>
+        <el-col :span="12">
+          <el-form-item label="所属区域" prop="area">
+            <el-cascader :options="orgData" :props="{ checkStrictly: true, emitPath: false, value: 'id', label: 'name' }" placeholder="请选择区域" clearable class="w100" v-model="formData.area">
+              <template #default="{ node, data }">
+                <span>{{ data.name }}</span>
+                <span v-if="!node.isLeaf"> ({{ data.children.length }}) </span>
+              </template>
+            </el-cascader>
+          </el-form-item>
+        </el-col>
+
+        <el-col :span="12">
+          <el-form-item label="安装时间">
+            <el-date-picker v-model="formData.installTime" type="date" value-format="YYYY-MM-DD" placeholder="请选择时间" class="w100" clearable />
+          </el-form-item>
+        </el-col>
+      </el-row>
+
+      <el-row>
+        <el-col :span="12">
+          <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="formData.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-col>
+
+        <el-col :span="12">
+          <el-form-item label="设备类型">
+            <el-input v-model.trim="formData.deviceCategory" placeholder="请输入设备类型" />
+          </el-form-item>
+        </el-col>
+      </el-row>
+
+      <el-divider content-position="left" v-if="Datalist">自定义属性</el-divider>
+      <FromData :DataList="Datalist" @SetSaveData="SetSaveData" v-if="Datalist && Datalist.length > 0"></FromData>
+
+    </el-form>
+    <template #footer>
+      <div class="dialog-footer">
+        <el-button @click="showDialog = false">取消</el-button>
+        <el-button type="primary" @click="onSubmit">确定</el-button>
+      </div>
+    </template>
+  </el-dialog>
+</template>
+
+<script lang="ts" setup>
+import { ref, reactive, nextTick } from 'vue';
+import api from '/@/api/device'
+import system from '/@/api/system';
+import FromData from './component/from.vue';
+
+import { ruleRequired } from '/@/utils/validator';
+import { ElMessage } from 'element-plus';
+
+const emit = defineEmits(['getList']);
+
+const showDialog = ref(false);
+const formRef = ref();
+const orgData = ref();
+const deviceList = ref();
+const productData = ref();
+const deptData = ref();
+const Datalist = ref();
+const newData = ref([]);
+const baseForm = {
+  id: undefined,
+  productKey: '',
+  deviceKey: '',
+  deviceName: '',
+  area: "",
+  deviceNumber: '',
+  deviceCategory: '',
+  installTime: '',
+  deptId: '',
+  data: [],
+
+};
+
+
+const SetSaveData = (data: any) => {
+  formData.data = data;
+}
+const formData = reactive({
+  ...baseForm,
+});
+
+const ruleForm = {
+  productKey: [ruleRequired('所属产品不能为空')],
+  deviceName: [ruleRequired('设备名称不能为空')],
+  deviceKey: [ruleRequired('设备不能为空')],
+};
+const handleSelectionChange = (value: any) => {
+  const selectedOption = deviceList.value.find(option => option.key === value);
+  if (selectedOption) {
+    formData.deviceName = selectedOption.name;
+  } else {
+    formData.deviceName = '';
+  }
+}
+
+const onSubmit = async () => {
+  await formRef.value.validate();
+  const theApi = formData.id ? api.dev_asset.edit : api.dev_asset.add;
+  await theApi(formData);
+  ElMessage.success('操作成功');
+  resetForm();
+  showDialog.value = false;
+  emit('getList');
+};
+
+const resetForm = async () => {
+  Object.assign(formData, { ...baseForm });
+  Datalist.value = ''
+  formRef.value && formRef.value.resetFields();
+};
+
+const getIdByKey = (key: string) => {
+  for (let i = 0; i < productData.value.length; i++) {
+    if (productData.value[i].key === key) {
+      return productData.value[i].id;
+    }
+  }
+  return null; // 如果没有找到匹配的key,则返回null(或者其他合适的值)
+}
+
+const open = async (row: any, productInfo: any) => {
+  resetForm();
+  showDialog.value = true;
+  nextTick(() => {
+    system.org.getList({ status: 1 }).then((res: any) => {
+      res.forEach((item: any) => {
+        item.id = item.id.toString();
+      });
+      orgData.value = res || [];
+    });
+
+    //获取 所有的产品
+    api.product.getLists({}).then((resp: any) => {
+      productData.value = resp.product;
+      if (row.id) {
+        productInfo = {
+          id: getIdByKey(row.productKey),
+          key: row.productKey,
+        }
+      }
+      //根据产品ID获取设备列表
+      api.device.allList({ productKey: productInfo.key }).then((resd: any) => {
+        deviceList.value = resd.device || [];
+      });
+    })
+
+    //获取部门
+    api.dept.getList({ status: -1 }).then((res: any) => {
+      res.forEach((item: any) => {
+        item.deptId = item.deptId.toString();
+      });
+      deptData.value = res || [];
+    });
+
+    if (row.id) {
+
+      api.dev_asset.detail({ deviceKey: row.deviceKey }).then((resde: any) => {
+        Object.assign(formData, { ...resde });
+        formData.productKey = row.productKey
+        const newArray = resde.data.map(obj => {
+          const { name, value, ...rest } = obj;
+          const newObj = { name, value, ...rest };
+          newObj[name] = value ? value : '';
+          return newObj;
+        });
+        Datalist.value = newArray
+      });
+    } else {
+      //获取档案属性
+      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 || [];
+      });
+      formData.productKey = productInfo.key
+
+    }
+  });
+};
+
+defineExpose({ open });
+</script>
+<style scoped lang="scss">
+.demo-form-inline .el-input {
+  --el-input-width: 320px;
+}
+</style>

+ 162 - 0
src/views/iot/property/relationship/index.vue

@@ -0,0 +1,162 @@
+<template>
+	<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'">
+					<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="treeData" :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">{{ data.name }}</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="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()" />
+				</div>
+			</el-card>
+		</div>
+		<EditForm ref="editFormRef" @getList="getList(1)"></EditForm>
+	</div>
+</template>
+
+<script lang="ts" setup>
+import api from '/@/api/system'
+import device from '/@/api/device'
+import { useSearch } from '/@/hooks/useCommon'
+
+import { ElMessageBox, ElMessage } from 'element-plus'
+import EditForm from './edit.vue'
+
+import { ref, onMounted } from 'vue'
+const defaultProps = {
+	children: 'children',
+	label: 'name',
+}
+
+const queryRef = ref()
+const treeData = ref()
+const editFormRef = ref()
+const productIno = ref()
+const ids = ref<number[]>([])
+
+
+api.role.getList({ status: -1 }).then((res: any) => {
+	treeData.value = res
+})
+
+const { params, tableData, getList, loading } = useSearch<any[]>(device.assetRelationship.getList, 'Data', { keyWord: '', roleId: null })
+getList()
+const handleSelectionChange = (selection: any[]) => {
+	ids.value = selection.map((item) => item.id);
+};
+
+
+const addOrEdit = async (row?: any) => {
+	if (row) {
+		editFormRef.value.open(row, productIno.value)
+		return
+	} else {
+		editFormRef.value.open({}, productIno.value)
+	}
+}
+
+const handleNodeClick = (data: any) => {
+	params.roleId = data.id
+	getList()
+}
+
+const batchdel = () => {
+	ElMessageBox.confirm('是否确认要批量删除这些数据吗?', '提示', {
+		confirmButtonText: '确认',
+		cancelButtonText: '取消',
+		type: 'warning',
+	}).then(async () => {
+		await device.dev_asset.delete({ ids: ids.value })
+		ElMessage.success('删除成功')
+		getList()
+	})
+}
+
+const del = (row: any) => {
+	ElMessageBox.confirm('是否确认删除名称为:"' + row.deviceName + '"的数据项?', '提示', {
+		confirmButtonText: '确认',
+		cancelButtonText: '取消',
+		type: 'warning',
+	}).then(async () => {
+		await device.dev_asset.delete({ ids: row.id })
+		ElMessage.success('删除成功')
+		getList()
+	})
+}
+
+
+// getCateList()
+</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;
+		}
+	}
+}
+</style>