yusheng hai 2 meses
pai
achega
4fbe07105d

+ 168 - 0
src/api/ledgerAssets/index.js

@@ -0,0 +1,168 @@
+import request from '@/utils/request';
+
+// 获取类别实体分页
+export async function getBoatList(data) {
+  let par = new URLSearchParams(data);
+  const res = await request.get(`/main/category/pageSubstance?` + par, {});
+  if (res.data.code == 0) {
+    return res.data.data;
+  }
+  return Promise.reject(new Error(res.data.message));
+}
+
+// // 查询实例详情
+// export async function getCategoryInfo (id) {
+//   const res = await request.get(`/main/category/info/${id}`);
+//   if (res.data.code == 0) {
+//     return res.data;
+//   }
+//   return Promise.reject(new Error(res.data.message));
+// }
+
+// 获取单个类别实体明细分页
+export async function getPageSingle(data) {
+  let par = new URLSearchParams(data);
+  const res = await request.get(`/main/asset/pageSingleSubstance?` + par, {});
+  if (res.data.code == 0) {
+    return res.data.data;
+  }
+  return Promise.reject(new Error(res.data.message));
+}
+
+// 获取实体列表分页
+export async function getAssetList(data) {
+  let par = new URLSearchParams(data);
+  const res = await request.get(`/main/asset/page?` + par, {});
+  if (res.data.code == 0) {
+    return res.data.data;
+  }
+  return Promise.reject(new Error(res.data.message));
+}
+// 获取仪表列表分页
+export async function querySubstanceRunningMonitor(data) {
+  let par = new URLSearchParams(data);
+  const res = await request.get(`/main/asset/querySubstanceRunningMonitor?` + par, {});
+  if (res.data.code == 0) {
+    return res.data.data;
+  }
+  return Promise.reject(new Error(res.data.message));
+}
+
+// 查询实例详情
+export async function getAssetInfo(id) {
+  const res = await request.get(`main/asset/getById/${id}`);
+  if (res.data.code == 0) {
+    return res.data.data;
+  }
+  return Promise.reject(new Error(res.data.message));
+}
+// 保存or更新
+export async function saveOrEdit(data) {
+  const res = await request.post(`/main/asset/saveOrEdit`, data);
+  if (res.data.code == 0) {
+    return res.data;
+  }
+  return Promise.reject(new Error(res.data.message));
+}
+
+// 根据类别统计位置数量
+export async function getCount(data) {
+  const res = await request.get(`/main/asset/countPosition`, { params: data });
+  if (res.data.code == 0) {
+    return res.data.data;
+  }
+  return Promise.reject(new Error(res.data.message));
+}
+
+
+
+// 设备网络状态统计
+export async function getNetworkCount(data) {
+  const res = await request.get(`/main/asset/networkCount`, { params: data });
+  if (res.data.code == 0) {
+    return res.data.data;
+  }
+  return Promise.reject(new Error(res.data.message));
+}
+
+//移动分类
+export async function changeSubstanceCateId(data) {
+  const res = await request.post(`/main/asset/changeSubstanceCateId`, data);
+  if (res.data.code == 0) {
+    return res.data;
+  }
+  return Promise.reject(new Error(res.data.message));
+}
+
+//设置片区负责人
+export async function updateRepairUserAndGroup(data) {
+  const res = await request.post(`/main/asset/updateRepairUserAndGroup`, data);
+  if (res.data.code == 0) {
+    return res.data;
+  }
+  return Promise.reject(new Error(res.data.message));
+}
+
+//批量删除
+export async function batchDel(data) {
+  const res = await request.post(`/main/asset/batchDel`, data);
+  if (res.data.code == 0) {
+    return res.data;
+  }
+  return Promise.reject(new Error(res.data.message));
+}
+
+// 查询实例详情
+export async function factoryworkstation(id) {
+  const res = await request.get(`/main/factoryworkstation/getById/${id}`);
+  if (res.data.code == 0) {
+    return res.data.data;
+  }
+  return Promise.reject(new Error(res.data.message));
+}
+
+// 生成编码
+export async function getAssetNum(data) {
+  const res = await request.post(`/wms/outin/getAssetNum`, data);
+  if (res.data.code == 0) {
+    return res.data;
+  }
+  return Promise.reject(new Error(res.data.message));
+}
+
+// 生成批次
+export async function getCode(code) {
+  const res = await request.get(`/main/codemanage/getCode/${code}`);
+  if (res.data.code == 0) {
+    return res.data.data;
+  }
+  return Promise.reject(new Error(res.data.message));
+}
+
+// 导入备品备件
+export async function importCategorySparePart(data) {
+  const res = await request.post(
+    `/main/excelUpload/importCategorySparePart`,
+    data
+  );
+  if (res.data.code == 0) {
+    return res.data;
+  }
+  return Promise.reject(new Error(res.data.message));
+}
+//绑定设备BOM与实体关系
+export async function bomSubstanceBind(data) {
+  const res = await request.post(`/main/bomSubstance/bind`, data);
+  if (res.data.code == 0) {
+    return res.data;
+  }
+  return Promise.reject(new Error(res.data.message));
+}
+//解除绑定设备BOM与实体关系
+export async function batchUnbind(data) {
+  const res = await request.post(`/main/bomSubstance/batchUnbind`, data);
+  if (res.data.code == 0) {
+    return res.data;
+  }
+  return Promise.reject(new Error(res.data.message));
+}

+ 431 - 0
src/components/CommomSelect/dept-selectNew.vue

@@ -0,0 +1,431 @@
+<template>
+  <div class="tree-select-wrapper">
+    <el-select
+      ref="selectRef"
+      v-model="selectedLabel"
+      :popper-append-to-body="popperAppendToBody"
+      :teleported="teleported"
+      :placeholder="placeholder"
+      :disabled="disabled"
+      :clearable="clearable"
+      :filterable="filterable"
+      :filter-method="filterMethod"
+      popper-class="tree-select-popper"
+      @clear="handleClear"
+      @visible-change="handleVisibleChange"
+    >
+      <!-- 关键修复:el-option 的 value 不能绑定 selectedLabel,需要绑定一个唯一值 -->
+      <el-option
+        :value="selectedValue"
+        :label="selectedLabel"
+        style="height: auto; padding: 0"
+        class="tree-select-option"
+      >
+        <!-- 搜索框 -->
+        <div v-if="filterable" class="tree-search">
+          <el-input
+            v-model="searchText"
+            size="small"
+            placeholder="输入关键词搜索"
+            clearable
+            @input="handleSearch"
+          />
+        </div>
+
+        <!-- 树形结构 -->
+        <el-tree
+          ref="treeRef"
+          :data="treeData"
+          :props="treeProps"
+          :show-checkbox="multiple"
+          :node-key="nodeKey"
+          :default-expand-all="defaultExpandAll"
+          :default-expanded-keys="defaultExpandedKeys"
+          :filter-node-method="filterNode"
+          :check-strictly="checkStrictly"
+          :expand-on-click-node="false"
+          :highlight-current="!multiple"
+          :current-node-key="currentNodeKey"
+          @node-click="handleNodeClick"
+          @check="handleCheck"
+        >
+          <span slot-scope="{ node, data }" class="custom-tree-node">
+            <span>{{ node.label }}</span>
+            <span v-if="data.count" class="node-count">({{ data.count }})</span>
+          </span>
+        </el-tree>
+      </el-option>
+    </el-select>
+  </div>
+</template>
+
+<script>
+  import { listOrganizations } from '@/api/system/organization';
+
+  export default {
+    name: 'TreeSelect',
+    props: {
+      value: {
+        type: [String, Number, Array],
+        default: null
+      },
+      data: {
+        type: Array,
+        required: true,
+        default: () => []
+      },
+      multiple: {
+        type: Boolean,
+        default: false
+      },
+      placeholder: {
+        type: String,
+        default: '请选择'
+      },
+      disabled: {
+        type: Boolean,
+        default: false
+      },
+      clearable: {
+        type: Boolean,
+        default: true
+      },
+      filterable: {
+        type: Boolean,
+        default: false
+      },
+      nodeKey: {
+        type: String,
+        default: 'id'
+      },
+      treeProps: {
+        type: Object,
+        default: () => ({
+          children: 'children',
+          label: 'name'
+        })
+      },
+      defaultExpandAll: {
+        type: Boolean,
+        default: true
+      },
+      defaultExpandedKeys: {
+        type: Array,
+        default: () => []
+      },
+      checkStrictly: {
+        type: Boolean,
+        default: false
+      },
+      popperAppendToBody: {
+        type: Boolean,
+        default: false
+      },
+      teleported: {
+        type: Boolean,
+        default: false
+      }
+    },
+    data() {
+      return {
+        selectedLabel: '', // 显示的文本
+        selectedValue: null, // 选中的值(用于单选)
+        selectedValues: [], // 多选时选中的值数组
+        currentNodeKey: null, // 当前高亮的节点key
+        searchText: '', // 搜索关键词
+        treeData: [], // 过滤后的树数据
+        dataLoaded: false // 新增:标记数据是否加载完成
+      };
+    },
+    watch: {
+      value: {
+        handler() {
+          // 等待数据加载完成后再初始化
+          if (this.dataLoaded) {
+            this.initSelectedValue();
+          }
+        },
+        immediate: true
+      },
+      // 新增:监听 treeData 变化,数据加载完成后初始化选中值
+      treeData: {
+        handler(val) {
+          if (val && val.length && !this.dataLoaded) {
+            this.dataLoaded = true;
+            this.initSelectedValue();
+          }
+        },
+        immediate: true
+      }
+    },
+    async created() {
+      await this.getData(); // 改为 await 等待数据加载完成
+    },
+    mounted() {
+      this.$nextTick(() => {
+        if (this.$refs.treeRef) {
+          this.$refs.treeRef.$on('node-expand', this.updatePopperPosition);
+          this.$refs.treeRef.$on('node-collapse', this.updatePopperPosition);
+        }
+      });
+    },
+    beforeDestroy() {
+      if (this.$refs.treeRef) {
+        this.$refs.treeRef.$off('node-expand', this.updatePopperPosition);
+        this.$refs.treeRef.$off('node-collapse', this.updatePopperPosition);
+      }
+    },
+    methods: {
+      async getData(params = {}) {
+        const data = await listOrganizations(params);
+        this.treeData = this.$util.toTreeData({
+          data: data || [],
+          idField: 'id',
+          parentIdField: 'parentId'
+        });
+        // 数据加载完成后标记
+        this.dataLoaded = true;
+      },
+
+      // 初始化选中的值(优化:从 this.treeData 查找,而不是 this.data)
+      initSelectedValue() {
+        // 如果数据还没加载完成,先不处理
+        if (!this.dataLoaded && !this.treeData.length) {
+          return;
+        }
+
+        if (this.multiple) {
+          const val = this.value || [];
+          this.selectedValues = [...val];
+          this.selectedLabel = this.getSelectedLabels(val).join('、');
+          this.selectedValue = val.length ? val.join(',') : '';
+
+          this.$nextTick(() => {
+            if (this.$refs.treeRef) {
+              this.$refs.treeRef.setCheckedKeys(this.selectedValues);
+            }
+          });
+        } else {
+          this.selectedValue = this.value;
+          if (this.value !== null && this.value !== undefined) {
+            // 关键修复:从 this.treeData 查找,而不是 this.data
+            const node = this.findNodeByValue(this.value, this.treeData);
+            this.selectedLabel = node ? node[this.treeProps.label] : '';
+            this.currentNodeKey = this.value;
+          } else {
+            this.selectedLabel = '';
+            this.currentNodeKey = null;
+          }
+
+          this.$nextTick(() => {
+            if (this.$refs.treeRef && this.value) {
+              this.$refs.treeRef.setCurrentKey(this.value);
+            }
+          });
+        }
+      },
+
+      // 根据值查找节点(从指定数据源查找)
+      findNodeByValue(value, dataSource = this.treeData) {
+        if (!dataSource || !dataSource.length) return null;
+
+        for (const item of dataSource) {
+          if (item[this.nodeKey] === value) {
+            return item;
+          }
+          const children = item[this.treeProps.children];
+          if (children && children.length) {
+            const found = this.findNodeByValue(value, children);
+            if (found) return found;
+          }
+        }
+        return null;
+      },
+
+      // 获取节点标签
+      getNodeLabel(value, dataSource = this.treeData) {
+        const node = this.findNodeByValue(value, dataSource);
+        return node ? node[this.treeProps.label] : '';
+      },
+
+      // 批量获取标签文本(多选)
+      getSelectedLabels(values, dataSource = this.treeData, result = []) {
+        if (!dataSource || !dataSource.length) return result;
+
+        for (const item of dataSource) {
+          if (values.includes(item[this.nodeKey])) {
+            result.push(item[this.treeProps.label]);
+          }
+          const children = item[this.treeProps.children];
+          if (children && children.length) {
+            this.getSelectedLabels(values, children, result);
+          }
+        }
+        return result;
+      },
+
+      // 树节点点击(单选模式)
+      handleNodeClick(data) {
+        if (!this.multiple) {
+          const value = data[this.nodeKey];
+          const label = data[this.treeProps.label];
+
+          this.selectedValue = value;
+          this.selectedLabel = label;
+          this.currentNodeKey = value;
+
+          this.$emit('input', value);
+          this.$emit('change', value, data);
+
+          this.$nextTick(() => {
+            if (this.$refs.selectRef) {
+              this.$refs.selectRef.visible = false;
+            }
+          });
+        }
+      },
+
+      // 树节点勾选(多选模式)
+      handleCheck(data, checkedState) {
+        const checkedKeys = checkedState.checkedKeys;
+        this.selectedValues = [...checkedKeys];
+        this.selectedLabel = this.getSelectedLabels(checkedKeys).join('、');
+        this.selectedValue = checkedKeys.length ? checkedKeys.join(',') : '';
+
+        this.$emit('input', checkedKeys);
+        this.$emit('change', checkedKeys, data);
+      },
+
+      // 清空
+      handleClear() {
+        if (this.multiple) {
+          this.selectedValues = [];
+          this.selectedLabel = '';
+          this.selectedValue = '';
+          this.$emit('input', []);
+          if (this.$refs.treeRef) {
+            this.$refs.treeRef.setCheckedKeys([]);
+          }
+        } else {
+          this.selectedValue = null;
+          this.selectedLabel = '';
+          this.currentNodeKey = null;
+          this.$emit('input', null);
+          if (this.$refs.treeRef) {
+            this.$refs.treeRef.setCurrentKey(null);
+          }
+        }
+      },
+
+      // 下拉框显示/隐藏
+      handleVisibleChange(visible) {
+        if (visible) {
+          this.$nextTick(() => {
+            if (this.$refs.treeRef) {
+              if (this.multiple) {
+                this.$refs.treeRef.setCheckedKeys(this.selectedValues);
+              } else {
+                this.$refs.treeRef.setCurrentKey(this.selectedValue);
+              }
+            }
+            this.updatePopperPosition();
+          });
+        }
+      },
+
+      // 更新下拉框位置
+      updatePopperPosition() {
+        this.$nextTick(() => {
+          if (this.$refs.selectRef && this.$refs.selectRef.visible) {
+            const select = this.$refs.selectRef;
+            if (select.popper) {
+              select.popper.update();
+            }
+          }
+        });
+      },
+
+      // 树节点过滤
+      filterNode(value, data) {
+        if (!value) return true;
+        const label = data[this.treeProps.label];
+        return label && label.toLowerCase().includes(value.toLowerCase());
+      },
+
+      // 搜索方法
+      filterMethod(query) {
+        this.searchText = query;
+      },
+
+      // 处理搜索
+      handleSearch(value) {
+        if (this.$refs.treeRef) {
+          this.$refs.treeRef.filter(value);
+        }
+      }
+    }
+  };
+</script>
+
+<style scoped>
+  .tree-select-wrapper {
+    width: 100%;
+  }
+
+  .tree-search {
+    padding: 8px 12px;
+    border-bottom: 1px solid #e4e7ed;
+  }
+
+  .tree-search >>> .el-input__inner {
+    height: 28px;
+    line-height: 28px;
+  }
+
+  .custom-tree-node {
+    display: inline-block;
+    width: 100%;
+    font-size: 14px;
+  }
+
+  .node-count {
+    margin-left: 8px;
+    color: #909399;
+    font-size: 12px;
+  }
+</style>
+
+<style>
+  /* 下拉框样式 - 全局样式 */
+  .tree-select-popper {
+    max-height: 400px;
+    overflow: auto;
+    padding: 0 !important;
+  }
+
+  .tree-select-popper .el-tree {
+    min-width: 200px;
+    max-height: 350px;
+    overflow: auto;
+  }
+
+  .tree-select-popper .el-tree-node__content {
+    height: 32px;
+  }
+
+  .tree-select-popper .el-checkbox {
+    margin-right: 8px;
+  }
+
+  /* 关键修复:让 el-option 内容完全展开 */
+  .tree-select-option {
+    height: auto !important;
+    line-height: normal !important;
+    padding: 0 !important;
+  }
+
+  .tree-select-option .el-select-dropdown__item {
+    height: auto !important;
+    padding: 0 !important;
+  }
+</style>

+ 150 - 0
src/utils/dict/warehouse.js

@@ -0,0 +1,150 @@
+// =====================仓储管理
+
+export const warehousingType = [
+  { code: 3, label: '物料', key: 'WULIAO' },
+  { code: 4, label: '产品', key: 'CHANPIN' },
+  {
+    code: 5,
+    label: '周转车',
+    key: 'ZHOUZHUANCHE'
+  },
+  { code: 2, label: '舟皿', key: 'ZHOUMIN' },
+  {
+    code: 1,
+    label: '设备',
+    key: 'SHENGCHANSHEBEI'
+  },
+  { code: 6, label: '模具', key: 'MOJU' },
+  {
+    code: 7,
+    label: '备品备件',
+    key: 'BEIPINBEIJIAN'
+  }
+];
+
+// export const wh_assetDict = [
+//   { label: '物料', type: 'w', code: 'WULIAO' },
+//   // {  label: "产品", type: "c", code:'CHANPIN' },
+//   { label: '周转车', type: '2', code: 'ZHOUZHUANCHE' },
+//   { label: '舟皿', type: '1', code: 'ZHOUMIN' },
+//   { label: '设备', type: '0', code: 'SHENGCHANSHEBEI' },
+//   { label: '模具', type: '3', code: 'MOJU' },
+//   { label: '备品备件', type: '4', code: 'BEIPINBEIJIAN' }
+// ]
+export const sceneState = [
+  { code: 1, label: '生产入库' },
+  { code: 2, label: '采购入库' },
+  { code: 3, label: '赠送入库' },
+  { code: 4, label: '借用入库' },
+  // { code: 5, label: '退还入库' },
+  { code: 6, label: '其他入库' }
+];
+export const outputSceneState = [
+  { code: 1, label: '退供出库' },
+  { code: 2, label: '调拨出库' },
+  { code: 3, label: '销售出库' },
+  { code: 4, label: '领用出库' },
+  { code: 5, label: '报废出库' }
+];
+// 紧急状态
+export const emergencyState = [
+  { code: 1, label: '普通', color: 'red' },
+  { code: 2, label: '重要', color: 'blue' },
+  { code: 3, label: '紧急', color: 'red' }
+];
+export const materialType = [
+  { code: 1, label: '混炼料' },
+  { code: 2, label: '混合料' }
+];
+
+export const inputStatus = [
+  { code: 1, label: '入库' },
+  { code: 2, label: '出库' }
+];
+// 盘点类型
+export const wh_checkStatus = [
+  { code: 1, label: '库内' },
+  { code: 2, label: '库外' }
+];
+export const allocationType = [
+  { code: 1, label: '库内调拨' },
+  { code: 2, label: '库外调拨' }
+];
+export const allocationStatusDict = [
+  { code: 1, label: '空置', color: 'blue' },
+  { code: 2, label: '在用', color: 'green' },
+  { code: 3, label: '已满', color: 'red' },
+  { code: 4, label: '失效', color: 'grey' }
+];
+
+// 计划周期类型
+export const wh_cycleTypeStauts = [
+  { code: 1, label: '周期计划' },
+  { code: 2, label: '临时计划' }
+];
+
+// 是否锁库盘点(0:否;1:是;)盘点独有字段
+export const wh_isTreasuryLock = [
+  { code: false, label: '否' },
+  { code: true, label: '是' }
+];
+// 计划状态
+export const wh_planStatus = [
+  { code: 0, label: '未派单' },
+  { code: 4, label: '待审核' },
+  { code: 5, label: '已审核' },
+  { code: 6, label: '派单中' },
+  { code: 1, label: '已派单' },
+  { code: 2, label: '已完成' },
+  { code: 3, label: '已撤回' }
+];
+export const wh_planStatusEnum = [
+  { code: 'WEIPAIDAN', label: '未派单' },
+  { code: 'YIPAIDAN', label: '已派单' },
+  { code: 'YIWANCHENG', label: '已完成' },
+  { code: 'YICHEHUI', label: '已撤回' },
+  { code: 'DAISHENHE', label: '待审核' },
+  { code: 'YISHENHE', label: '已审核' },
+  { code: 'PAIDANZHONG', label: '派单中' }
+];
+// 盘点资产状态
+export const wh_equStatus = [
+  { code: 0, label: '待盘点', color: '' },
+  { code: 1, label: '盘亏', color: '#D9001B' },
+  { code: 2, label: '盘损', color: '#F59A23' },
+  { code: 3, label: '盘盈', color: '#70B603' },
+  { code: 4, label: '正常', color: '' }
+];
+
+// 网络状态
+export const networkStatus = [
+  { code: 0, label: '离线' },
+  { code: 1, label: '在线' },
+  { code: 2, label: '关机' },
+  { code: 3, label: '开机' }
+];
+
+// 状态(业务状态)
+export const businessStatus = [
+  { code: 0, label: '空闲' },
+  { code: 1, label: '占用' },
+  { code: 2, label: '故障' },
+  { code: 3, label: '维修' },
+  { code: 4, label: '保养' },
+  { code: 5, label: '巡点检' }
+];
+
+// 生命周期
+export const sourceStatus = {
+  0: '在库',
+  1: '在用',
+  2: '捐赠',
+  3: '转让',
+  4: '调拨',
+  5: '报废',
+  6: '启封',
+  7: '封存',
+  8: '限用',
+  9: '变更',
+  10: '停用',
+};

+ 990 - 0
src/views/equipmentOperationMonitoring/index.vue

@@ -0,0 +1,990 @@
+<template>
+  <vue-fullscreen
+    v-model="isFullscreen"
+    :exit-on-click-wrapper="false"
+    style="z-index: 999"
+  >
+    <div
+      class="energy-monitoring card-mode"
+      :style="{
+        height: isFullscreen ? 'calc(100vh)' : 'calc(100vh - 96px)'
+      }"
+    >
+      <el-card
+        shadow="never"
+        class="monitoring-card card-mode"
+        :style="{
+          height: isFullscreen ? 'calc(100vh)' : 'calc(100vh - 96px)'
+        }"
+      >
+        <!-- 主内容区 -->
+        <ele-split-layout
+          width="220px"
+          allow-collapse
+          :right-style="{ overflow: 'hidden' }"
+        >
+          <!-- 左侧树形菜单 -->
+          <div class="left-sidebar card-mode">
+            <el-tabs
+              v-model="activeTab"
+              type="border-card"
+              @tab-click="activeTabChange"
+            >
+              <el-tab-pane label="设备类型" name="org">
+                <el-tree
+                  :data="orgTreeData"
+                  :props="treeProps"
+                  node-key="id"
+                  highlight-current
+                  @node-click="handleNodeClick"
+                  style="height: calc(100vh - 180px)"
+                  ref="treeRef"
+                ></el-tree>
+              </el-tab-pane>
+              <el-tab-pane label="用能分区" name="energy">
+                <el-tree
+                  :data="treeData"
+                  :props="treeProps"
+                  node-key="id"
+                  default-expand-all
+                  ref="treeRef1"
+                  highlight-current
+                  @node-click="handleNodeClick"
+                ></el-tree>
+              </el-tab-pane>
+            </el-tabs>
+          </div>
+
+          <!-- 右侧内容区 -->
+          <template v-slot:content>
+            <div class="right-content card-mode">
+              <!-- 搜索区域 -->
+              <div class="search-area card-mode">
+                <!-- 设备状态标签 -->
+                <!-- <div class="status-tabs">
+                <el-radio-group
+                  v-model="statusFilter"
+                  size="small"
+                  @change="handleStatusChange"
+                >
+                  <el-radio-button label="all">全部</el-radio-button>
+                  <el-radio-button label="online">在线</el-radio-button>
+                  <el-radio-button label="offline">离线</el-radio-button>
+                  <el-radio-button label="fault">故障</el-radio-button>
+                </el-radio-group>
+              </div> -->
+                <!-- 搜索框 -->
+                <div class="search-input-wrapper">
+                  <el-input
+                    v-model="keyWord"
+                    placeholder="编码、名称、固资编码"
+                    prefix-icon="el-icon-search"
+                    clearable
+                    @keyup.enter.native="handleSearch"
+                    style="width: 280px"
+                  >
+                  </el-input>
+
+                  <DeptSelect
+                    v-model="postId"
+                    placeholder="使用单位"
+                    style="width: 280px"
+                  />
+
+                  <el-button type="primary" @click="handleSearch"
+                    >搜索</el-button
+                  >
+                  <el-button @click="handleReset">重置</el-button>
+                </div>
+
+                <div
+                  class="fullscreen-btn"
+                  @click="isFullscreen = !isFullscreen"
+                >
+                  <i
+                    :class="
+                      isFullscreen ? 'el-icon-close' : 'el-icon-full-screen'
+                    "
+                  ></i>
+                </div>
+              </div>
+
+              <!-- 更新时间 -->
+              <div class="update-time">更新时间:{{ updateTime }}</div>
+
+              <!-- 卡片视图 -->
+              <div
+                class="card-view"
+                :style="{
+                  height: isFullscreen
+                    ? 'calc(100vh - 155px)'
+                    : 'calc(100vh - 240px)'
+                }"
+              >
+                <el-row :gutter="10">
+                  <el-col
+                    v-for="item in deviceData"
+                    :key="item.id"
+                    :xs="24"
+                    :sm="12"
+                    :md="8"
+                    :lg="6"
+                  >
+                    <div
+                      class="device-card"
+                      :class="{
+                        'is-fault': item.status == '3' || item.status == '2'
+                      }"
+                      @click="details(item)"
+                    >
+                      <div class="card-header">
+                        <div>{{item.postName|}}</div>
+                        <div class="card-header-value">
+                          <div class="device-name">
+                            {{
+                              item.name +
+                              (item.codeNumber ? '-' + item.codeNumber : '')
+                            }}
+                          </div>
+                          <div class="card-actions">
+                            <el-tag
+                              :type="getStatusType(item.status, 1)"
+                              size="small"
+                              effect="dark"
+                              class="status-tag"
+                            >
+                              {{
+                                businessStatus.filter(
+                                  (row) => row.code == item.status
+                                )[0]?.label
+                              }}
+                            </el-tag>
+                          </div></div
+                        >
+                      </div>
+                      <div class="card-body">
+                        <el-tag
+                          :type="getStatusType(item.iotList.length ? 1 : 0)"
+                          size="small"
+                          effect="dark"
+                          class="status-tag"
+                        >
+                          {{ getStatusText(item.iotList.length ? 1 : 0) }}
+                        </el-tag>
+                        <div class="info-grid">
+                          <div class="info-row" v-for="iotItem in item.iotList">
+                            <span class="info-label">{{ iotItem.name }}</span>
+                            <span class="info-value">{{
+                              iotItem.dataType.type == 'enum' ||
+                              iotItem.dataType.type == 'bool'
+                                ? iotItem.dataType.specs[iotItem.value]
+                                : iotItem.value +
+                                  ' ' +
+                                  ((iotItem.unit && iotItem.unit) || '')
+                            }}</span>
+                          </div>
+                        </div>
+                      </div>
+                    </div>
+                  </el-col>
+                </el-row>
+              </div>
+
+              <!-- 分页 -->
+              <!-- <div class="pagination-area">
+              <el-pagination
+                @size-change="handleSizeChange"
+                @current-change="handleCurrentChange"
+                :current-page="pageNum"
+                :page-sizes="[10, 20, 50, 100]"
+                :page-size="pageSize"
+                layout="total, sizes, prev, pager, next, jumper"
+                :total="total"
+              ></el-pagination>
+            </div> -->
+            </div>
+          </template>
+        </ele-split-layout>
+      </el-card>
+    </div>
+  </vue-fullscreen>
+</template>
+
+<script>
+  import dictMixins from '@/mixins/dictMixins';
+  import tableColumnsMixin from '@/mixins/tableColumnsMixin';
+  import dayjs from 'dayjs';
+  import { querySubstanceRunningMonitor } from '@/api/ledgerAssets';
+  import { getTreeByPid } from '@/api/classifyManage';
+  import { basicAreaPageAPI } from '@/api/factoryModel';
+  import { businessStatus } from '@/utils/dict/warehouse';
+  import DeptSelect from '@/components/CommomSelect/dept-selectNew.vue';
+  import { component } from 'vue-fullscreen';
+
+  export default {
+    mixins: [dictMixins, tableColumnsMixin],
+    components: { DeptSelect, VueFullscreen: component },
+    data() {
+      return {
+        // 搜索表单
+        searchForm: {
+          energyType: '',
+          keyword: '',
+          deviceStatus: ''
+        },
+        businessStatus,
+        postId: '',
+        // 当前激活的标签页
+        activeTab: 'org',
+        // 更新时间
+        updateTime: dayjs().format('YYYY-M-D HH:mm:ss'),
+        // 搜索关键词
+        keyWord: '',
+        // 树形数据
+        treeData: [],
+        orgTreeData: [],
+        treeProps: {
+          children: 'children',
+          label: 'name'
+        },
+        // 分页参数
+        pageNum: 1,
+        pageSize: 10,
+        total: 0,
+        areaId: '',
+        isFullscreen: false,
+        cacheKeyUrl: 'ems-energy-monitoring-list',
+        deviceData: [],
+        categoryLevelId: '',
+        rootCategoryLevelId: ''
+      };
+    },
+    computed: {},
+    created() {
+      getTreeByPid(0).then((res) => {
+        this.orgTreeData = res.data.filter((item) =>
+          [
+            '4',
+            '14',
+            '1812765052571865089',
+            '5',
+            '8',
+            '7',
+            '26',
+            '18',
+            '15',
+            '6',
+            '11',
+            '16'
+          ].includes(item.id)
+        );
+        if (this.orgTreeData.length) {
+          this.handleNodeClick(this.orgTreeData[0]);
+          this.$nextTick(() => {
+            console.log(this.orgTreeData[0].id, 'this.orgTreeData[0].id');
+            this.$refs.treeRef.setCurrentKey(this.orgTreeData[0].id);
+          });
+        }
+      });
+      /* 获取区域集合 */
+
+      basicAreaPageAPI({
+        pageNum: 1,
+        size: 9999
+      }).then((res) => {
+        this.treeData = this.$util.toTreeData({
+          data: res,
+          idField: 'id',
+          parentIdField: 'parentId'
+        });
+      });
+    },
+    methods: {
+      activeTabChange() {
+        let data = null;
+        this.pageNum = 1;
+        if (this.activeTab == 'org') {
+          data = this.orgTreeData[0];
+          this.$nextTick(() => {
+            this.$refs.treeRef.setCurrentKey(this.orgTreeData[0].id);
+          });
+        } else {
+          data = this.treeData[0] || {};
+          this.$nextTick(() => {
+            this.$refs.treeRef1.setCurrentKey(this.treeData[0].id);
+          });
+        }
+        if (data.id) {
+          this.handleNodeClick(data);
+        } else {
+          this.total = 0;
+          this.deviceData = [];
+        }
+      },
+      // 加载设备数据(模拟)
+      loadDeviceData() {
+        querySubstanceRunningMonitor({
+          pageNum: 1,
+          size: 9999,
+          categoryLevelId: this.categoryLevelId,
+          rootCategoryLevelId: this.rootCategoryLevelId,
+          areaId: this.areaId,
+          keyWord: this.keyWord,
+          postId: this.postId
+        }).then((res) => {
+          this.updateTime = dayjs().format('YYYY-M-D HH:mm:ss');
+          this.deviceData = res.list.map((item) => {
+            let iotList = [];
+            if (item.iotPointDataList) {
+              item.iotPointDataList.forEach((element) => {
+                let data = item.iotModel.properties.find(
+                  (iotModel) => iotModel.identifier == element.identifier
+                );
+                if (data) {
+                  iotList.push({
+                    ...element,
+                    dataType: data.dataType
+                  });
+                }
+              });
+            }
+
+            item['iotList'] = item.iotDashboardPoint.length
+              ? iotList.filter((iotListItem) =>
+                  item.iotDashboardPoint.find(
+                    (Point) =>
+                      Point.identifier == iotListItem.identifier &&
+                      Point.checked1
+                  )
+                )
+              : iotList.filter((iotListItem, index) => index < 4);
+            return item;
+          });
+          this.total = res.count;
+          console.log(res);
+        });
+      },
+      // // 获取设备图标
+      // getDeviceIcon(type) {
+      //   const iconMap = {
+      //     water: 'el-icon-water-cup',
+      //     electric: 'el-icon-lightning',
+      //     gas: 'el-icon-stopwatch'
+      //   };
+      //   return iconMap[type] || 'el-icon-odometer';
+      // },
+      // 获取状态类型
+      getStatusType(status, type) {
+        const typeMap = !type
+          ? {
+              1: 'success',
+              0: 'info',
+              fault: 'danger'
+            }
+          : {
+              // { code: 0, label: '空闲' },
+              // { code: 1, label: '占用' },
+              // { code: 2, label: '故障' },
+              // { code: 3, label: '维修' },
+              // { code: 4, label: '保养' },
+              // { code: 5, label: '巡点检' }
+
+              0: 'success',
+              1: 'success',
+              4: 'success',
+              5: 'warning',
+
+              2: 'danger',
+              3: 'danger'
+            };
+        return typeMap[status];
+      },
+      // 获取状态文本
+      getStatusText(status) {
+        const textMap = {
+          1: '在线',
+          0: '离线',
+          fault: '故障'
+        };
+        return textMap[status] || status;
+      },
+
+      // 搜索
+      handleSearch() {
+        this.pageNum = 1;
+        this.loadDeviceData();
+      },
+      // 重置
+      handleReset() {
+        this.name = '';
+        this.postId = '';
+        this.pageNum = 1;
+        this.loadDeviceData();
+      },
+      // 树节点点击
+      handleNodeClick(data) {
+        this.categoryLevelId = '';
+        this.rootCategoryLevelId = '';
+        this.pageNum = 1;
+
+        this.areaId = '';
+        if (this.activeTab == 'org') {
+          this.categoryLevelId = data.id;
+          this.rootCategoryLevelId = data.rootCategoryLevelId;
+        } else {
+          this.areaId = data.id;
+        }
+        this.loadDeviceData();
+      },
+      // 卡片点击
+      // 跳转到详情页
+      details({ id, code, name }) {
+        window.history.pushState(
+          null,
+          '',
+          `/page-eam/ledgerAssets/equipment/detail?id=${id}&code=${code}&name=${name}&activeComp=internet`
+        );
+      },
+      // 分页大小变化
+      handleSizeChange(val) {
+        this.pageSize = val;
+        this.loadDeviceData();
+      },
+      // 页码变化
+      handleCurrentChange(val) {
+        this.pageNum = val;
+        this.loadDeviceData();
+      }
+    }
+  };
+</script>
+
+<style lang="scss" scoped>
+  .energy-monitoring {
+    // padding: 15px;
+
+    .monitoring-card {
+      ::v-deep .el-card__body {
+        height: 100%;
+        padding: 15px;
+      }
+
+      &.card-mode {
+        background: linear-gradient(
+          135deg,
+          #1a1c2e 0%,
+          #161825 100%
+        ) !important;
+        border-color: rgba(255, 255, 255, 0.1) !important;
+
+        ::v-deep .el-card__body {
+          background: transparent;
+        }
+      }
+    }
+
+    ::v-deep .ele-split-layout {
+      .ele-split-layout-left {
+        padding: 0;
+        background: transparent;
+        border: none;
+      }
+
+      .ele-split-layout-right {
+        padding: 0;
+      }
+
+      .ele-split-layout-trigger {
+        width: 8px;
+        background: #f0f0f0;
+        border-left: 1px solid #e0e0e0;
+        border-right: 1px solid #e0e0e0;
+
+        &:hover {
+          background: #d0d0d0;
+        }
+
+        .ele-split-layout-trigger-icon {
+          color: #909399;
+        }
+      }
+    }
+
+    .left-sidebar {
+      height: 100%;
+
+      ::v-deep .el-tabs--border-card {
+        box-shadow: none;
+        border: 1px solid #ebeef5;
+        height: 100%;
+
+        .el-tabs__header {
+          background-color: #f5f7fa;
+          border-bottom: 1px solid #ebeef5;
+          margin: 0;
+
+          .el-tabs__nav {
+            width: 100%;
+            display: flex;
+          }
+
+          .el-tabs__item {
+            flex: 1;
+            height: 39px;
+            line-height: 39px;
+            font-size: 13px;
+            padding: 0;
+            text-align: center;
+
+            &.is-active {
+              background-color: #fff;
+              border-bottom-color: #fff;
+            }
+          }
+        }
+
+        .el-tabs__content {
+          padding: 10px;
+          height: calc(100% - 38px);
+          overflow-y: auto;
+        }
+      }
+
+      ::v-deep .el-tree {
+        padding: 0;
+      }
+
+      &.card-mode {
+        ::v-deep .el-tabs--border-card {
+          background: linear-gradient(135deg, #151825 0%, #1a1c2e 100%);
+          border: 1px solid rgba(255, 255, 255, 0.08);
+          box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3);
+
+          .el-tabs__header {
+            background: rgba(255, 255, 255, 0.05);
+            border-bottom-color: rgba(255, 255, 255, 0.1);
+
+            .el-tabs__item {
+              color: rgba(255, 255, 255, 0.6);
+              border-right-color: rgba(255, 255, 255, 0.1);
+
+              &.is-active {
+                background: rgba(255, 255, 255, 0.1);
+                color: #409eff;
+                border-bottom-color: transparent;
+              }
+
+              &:hover {
+                color: rgba(255, 255, 255, 0.8);
+              }
+            }
+          }
+
+          .el-tabs__content {
+            background: transparent;
+          }
+        }
+
+        ::v-deep .el-tree {
+          color: rgba(255, 255, 255, 0.8);
+
+          .el-tree-node__content {
+            background: transparent;
+            color: rgba(255, 255, 255, 0.8);
+
+            &:hover {
+              background: rgba(64, 158, 255, 0.1);
+            }
+          }
+
+          .el-tree-node__label {
+            color: rgba(255, 255, 255, 0.8);
+          }
+
+          .is-current > .el-tree-node__content {
+            background: rgba(64, 158, 255, 0.2);
+            color: #409eff;
+          }
+        }
+      }
+    }
+
+    .right-content {
+      flex: 1;
+      min-width: 0;
+      padding-left: 15px;
+
+      &.card-mode {
+        background: transparent;
+      }
+
+      .search-area {
+        display: flex;
+        justify-content: space-between;
+        align-items: center;
+        margin-bottom: 10px;
+        padding-bottom: 10px;
+        border-bottom: 1px solid #ebeef5;
+
+        .status-tabs {
+          flex-shrink: 0;
+        }
+
+        .search-input-wrapper {
+          display: flex;
+          align-items: center;
+          gap: 10px;
+        }
+
+        .view-switch {
+          margin-left: 20px;
+          flex-shrink: 0;
+        }
+
+        &.card-mode {
+          background: linear-gradient(135deg, #151825 0%, #1a1c2e 100%);
+          border-radius: 12px;
+          padding: 15px;
+          margin-bottom: 10px;
+          border-bottom: 1px solid rgba(255, 255, 255, 0.1);
+          box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3);
+          border: 1px solid rgba(255, 255, 255, 0.08);
+
+          ::v-deep .el-radio-button__inner {
+            background: rgba(255, 255, 255, 0.08) !important;
+            border-color: rgba(255, 255, 255, 0.2) !important;
+            color: rgba(255, 255, 255, 0.8) !important;
+
+            &:hover {
+              color: #409eff;
+            }
+          }
+
+          ::v-deep
+            .el-radio-button__orig-radio:checked
+            + .el-radio-button__inner {
+            background-color: #409eff !important;
+            border-color: #409eff !important;
+            color: #fff !important;
+            box-shadow: 0 2px 8px rgba(64, 158, 255, 0.3);
+          }
+
+          ::v-deep .el-input__inner {
+            background: rgba(255, 255, 255, 0.08) !important;
+            border-color: rgba(255, 255, 255, 0.2) !important;
+            color: rgba(255, 255, 255, 0.8) !important;
+
+            &::-webkit-input-placeholder {
+              color: rgba(255, 255, 255, 0.4);
+            }
+          }
+
+          ::v-deep .el-input__icon {
+            color: rgba(255, 255, 255, 0.5);
+          }
+
+          ::v-deep .el-button--default {
+            background: rgba(255, 255, 255, 0.08);
+            border-color: rgba(255, 255, 255, 0.2);
+            color: rgba(255, 255, 255, 0.8);
+
+            &:hover {
+              background: rgba(255, 255, 255, 0.15);
+              color: #409eff;
+              border-color: rgba(64, 158, 255, 0.4);
+            }
+          }
+
+          ::v-deep .el-button--primary {
+            background: #409eff;
+            border-color: #409eff;
+
+            &:hover {
+              background: #66b1ff;
+              border-color: #66b1ff;
+            }
+          }
+        }
+      }
+
+      .update-time {
+        background: linear-gradient(135deg, #151825 0%, #1a1c2e 100%);
+        border-radius: 12px;
+        padding: 12px 15px;
+        color: rgba(255, 255, 255, 0.6);
+        border: 1px solid rgba(255, 255, 255, 0.08);
+        box-shadow: 0 2px 8px rgba(0, 0, 0, 0.2);
+        margin-bottom: 10px;
+      }
+
+      .card-view {
+        background: transparent;
+        border-radius: 12px;
+        padding: 15px;
+
+        overflow-y: auto;
+        padding-right: 10px;
+
+        .device-card {
+          height: 170px;
+          background: linear-gradient(
+            145deg,
+            rgba(30, 34, 50, 0.9) 0%,
+            rgba(20, 23, 35, 0.95) 100%
+          );
+          border: 1px solid rgba(255, 255, 255, 0.12);
+          border-radius: 12px;
+          padding: 20px;
+          margin-bottom: 20px;
+          cursor: pointer;
+          transition: all 0.3s;
+          box-shadow: 0 4px 16px rgba(0, 0, 0, 0.4),
+            inset 0 1px 0 rgba(255, 255, 255, 0.05);
+
+          &:hover {
+            box-shadow: 0 12px 28px rgba(0, 0, 0, 0.5),
+              0 0 0 1px rgba(64, 158, 255, 0.3);
+            transform: translateY(-4px);
+            border-color: rgba(64, 158, 255, 0.5);
+            background: linear-gradient(
+              145deg,
+              rgba(35, 40, 55, 0.95) 0%,
+              rgba(25, 28, 40, 0.98) 100%
+            );
+          }
+
+          &.is-fault {
+            background: linear-gradient(
+              145deg,
+              rgba(255, 107, 107, 0.15) 0%,
+              rgba(255, 107, 107, 0.08) 100%
+            );
+            border-color: rgba(255, 107, 107, 0.6);
+          }
+
+          .card-header {
+            height: 60px;
+            color: rgba(255, 255, 255, 0.9);
+            .card-header-value {
+              display: flex;
+              justify-content: space-between;
+              align-items: center;
+              // margin-bottom: 12px;
+              padding-bottom: 12px;
+              border-bottom: 1px solid rgba(255, 255, 255, 0.1);
+            }
+
+            .device-name {
+              font-size: 14px;
+              font-weight: 500;
+              color: rgba(255, 255, 255, 0.9);
+              flex: 1;
+              display: flex;
+              overflow: hidden;
+              text-overflow: ellipsis;
+              white-space: nowrap;
+            }
+
+            .card-actions {
+              .alarm-icon {
+                font-size: 18px;
+                color: rgba(255, 255, 255, 0.4);
+
+                &.has-alarm {
+                  color: #ff6b6b;
+                  animation: bellshake 2s infinite;
+                }
+              }
+            }
+          }
+
+          .card-body {
+            .status-tag {
+              margin-bottom: 12px;
+            }
+
+            .info-grid {
+              display: grid;
+              grid-template-columns: 1fr 1fr;
+              gap: 8px;
+
+              .info-row {
+                display: flex;
+                align-items: center;
+                margin-bottom: 0;
+
+                .info-label {
+                  font-size: 12px;
+                  color: rgba(255, 255, 255, 0.5);
+                  min-width: 60px;
+                }
+
+                .info-value {
+                  font-size: 12px;
+                  color: rgba(255, 255, 255, 0.7);
+                  flex: 1;
+                  overflow: hidden;
+                  text-overflow: ellipsis;
+                  white-space: nowrap;
+                  margin-left: 5px;
+                }
+              }
+            }
+          }
+        }
+      }
+
+      .pagination-area {
+        margin-top: 15px;
+        padding: 15px 0;
+
+        ::v-deep .el-pagination {
+          justify-content: flex-end;
+
+          .el-pagination__total {
+            margin-right: 16px;
+            color: rgba(255, 255, 255, 0.6);
+          }
+
+          .el-pagination__sizes {
+            margin-right: 16px;
+
+            .el-select .el-input .el-input__inner {
+              background: rgba(255, 255, 255, 0.1);
+              border-color: rgba(255, 255, 255, 0.2);
+              color: rgba(255, 255, 255, 0.8);
+
+              &::-webkit-input-placeholder {
+                color: rgba(255, 255, 255, 0.4);
+              }
+            }
+
+            .el-select-dropdown {
+              background: #1a1c2e;
+              border-color: rgba(255, 255, 255, 0.2);
+
+              .el-select-dropdown__item {
+                color: rgba(255, 255, 255, 0.8);
+
+                &:hover,
+                &.selected {
+                  background: rgba(64, 158, 255, 0.2);
+                  color: #fff;
+                }
+              }
+            }
+          }
+
+          .el-pager {
+            margin: 0 4px;
+
+            li {
+              margin: 0 4px;
+              border-radius: 50%;
+              min-width: 28px;
+              height: 28px;
+              line-height: 28px;
+              background: rgba(255, 255, 255, 0.05);
+              color: rgba(255, 255, 255, 0.6);
+
+              &.active {
+                background-color: #409eff;
+                color: #fff !important;
+                border-radius: 50%;
+              }
+
+              &:hover {
+                color: #409eff;
+                background: rgba(255, 255, 255, 0.1);
+              }
+            }
+          }
+
+          .btn-prev,
+          .btn-next {
+            border-radius: 50%;
+            min-width: 28px;
+            height: 28px;
+            line-height: 28px;
+            padding: 0;
+            background: rgba(255, 255, 255, 0.05);
+            color: rgba(255, 255, 255, 0.6);
+
+            &:hover {
+              color: #409eff;
+              background: rgba(255, 255, 255, 0.1);
+            }
+
+            &.disabled {
+              background: rgba(255, 255, 255, 0.02);
+              color: rgba(255, 255, 255, 0.2);
+            }
+          }
+
+          .el-pagination__jump {
+            margin-left: 16px;
+            color: rgba(255, 255, 255, 0.6);
+
+            input {
+              background: rgba(255, 255, 255, 0.1);
+              border-color: rgba(255, 255, 255, 0.2);
+              color: rgba(255, 255, 255, 0.8);
+
+              &::-webkit-input-placeholder {
+                color: rgba(255, 255, 255, 0.4);
+              }
+            }
+          }
+        }
+      }
+
+      ::v-deep .el-pagination {
+        justify-content: flex-end;
+      }
+    }
+  }
+
+  @keyframes bellshake {
+    0%,
+    100% {
+      transform: rotate(0deg);
+    }
+    10%,
+    30%,
+    50%,
+    70%,
+    90% {
+      transform: rotate(-10deg);
+    }
+    20%,
+    40%,
+    60%,
+    80% {
+      transform: rotate(10deg);
+    }
+  }
+  .fullscreen-btn {
+    position: absolute;
+    right: 15px;
+    width: 36px;
+    height: 36px;
+    display: flex;
+    align-items: center;
+    justify-content: center;
+    background: rgba(56, 255, 249, 0.15);
+    border: 1px solid #38fff9;
+    border-radius: 4px;
+    cursor: pointer;
+    transition: all 0.3s ease;
+
+    i {
+      font-size: 18px;
+      color: #38fff9;
+    }
+
+    &:hover {
+      background: rgba(56, 255, 249, 0.3);
+      box-shadow: 0 0 10px rgba(56, 255, 249, 0.5);
+    }
+  }
+</style>

+ 1 - 1
vue.config.js

@@ -36,7 +36,7 @@ module.exports = {
     proxy: {
       // 当我们的本地的请求 有/api的时候,就会代理我们的请求地址向另外一个服务器发出请求
       '/api': {
-        target: 'http://192.168.1.251:18086', //开发
+        target: 'http://192.168.1.3:18086', //开发
         // target: 'http://192.168.1.251:18186', //测试
         // target: 'http://192.168.1.23:18086',//罗
         // target: 'http://192.168.1.144:18086',//付