Browse Source

删除package-lock 迁移设备台账代码

duxiangyu 3 years ago
parent
commit
521ef9b09d
74 changed files with 19323 additions and 12429 deletions
  1. 1 0
      .gitignore
  2. 0 12429
      package-lock.json
  3. 71 0
      src/components/ledger/components/addDialog/dialogZone.vue
  4. 202 0
      src/components/ledger/components/cenote/dialog/addCenoteDialog.vue
  5. 198 0
      src/components/ledger/components/cenote/dialog/addEquipDialog.vue
  6. 223 0
      src/components/ledger/components/cenote/dialog/addSpaceDialog.vue
  7. 147 0
      src/components/ledger/components/cenote/dialog/addSystemDialog.vue
  8. 252 0
      src/components/ledger/components/cenote/elevationMap.vue
  9. 202 0
      src/components/ledger/components/cenote/relatedSpaceList.vue
  10. 172 0
      src/components/ledger/components/cenote/table/cenoteTable.vue
  11. 148 0
      src/components/ledger/components/cenote/table/deviceTable.vue
  12. 145 0
      src/components/ledger/components/cenote/table/systemTable.vue
  13. 126 0
      src/components/ledger/components/details/detail/cruxDialog.vue
  14. 143 0
      src/components/ledger/components/details/detail/exhibitionBaseInformation.vue
  15. 147 0
      src/components/ledger/components/details/detail/exhibitionCrux.vue
  16. 98 0
      src/components/ledger/components/details/detail/exhibitionEnergy.vue
  17. 70 0
      src/components/ledger/components/details/detail/exhibitionEnergyO.vue
  18. 36 0
      src/components/ledger/components/details/detail/exhibitionFile.vue
  19. 52 0
      src/components/ledger/components/details/detail/exhibitionImage.vue
  20. 29 0
      src/components/ledger/components/details/detail/exhibitionMaintain.vue
  21. 51 0
      src/components/ledger/components/details/detail/exhibitionVideo.vue
  22. 215 0
      src/components/ledger/components/details/detail/lookFile.vue
  23. 306 0
      src/components/ledger/components/details/detail/lookImage.vue
  24. 277 0
      src/components/ledger/components/details/detail/lookVideo.vue
  25. 49 0
      src/components/ledger/components/details/detail/selectRadio.vue
  26. 1336 0
      src/components/ledger/components/handsontables/assets.vue
  27. 1466 0
      src/components/ledger/components/handsontables/device.vue
  28. 852 0
      src/components/ledger/components/handsontables/system.vue
  29. 173 0
      src/components/ledger/components/lib/assets.vue
  30. 312 0
      src/components/ledger/components/lib/bimDialog.vue
  31. 97 0
      src/components/ledger/components/lib/buildfloorCascader.vue
  32. 120 0
      src/components/ledger/components/lib/buildingDialog.vue
  33. 164 0
      src/components/ledger/components/lib/cascader.vue
  34. 415 0
      src/components/ledger/components/lib/cenoteGraphy.vue
  35. 77 0
      src/components/ledger/components/lib/cenoteType.vue
  36. 82 0
      src/components/ledger/components/lib/detailsDia.vue
  37. 271 0
      src/components/ledger/components/lib/dynamicDialog.vue
  38. 127 0
      src/components/ledger/components/lib/editSysFloor.vue
  39. 142 0
      src/components/ledger/components/lib/equipCascader.vue
  40. 159 0
      src/components/ledger/components/lib/floorCascader.vue
  41. 315 0
      src/components/ledger/components/lib/formInput.vue
  42. 326 0
      src/components/ledger/components/lib/linkassets.vue
  43. 190 0
      src/components/ledger/components/lib/lookImages.vue
  44. 60 0
      src/components/ledger/components/lib/myPagination.vue
  45. 154 0
      src/components/ledger/components/lib/noAllDataFloor.vue
  46. 147 0
      src/components/ledger/components/lib/partsDieList.vue
  47. 62 0
      src/components/ledger/components/lib/qrcode.vue
  48. 99 0
      src/components/ledger/components/lib/spaceSelect.vue
  49. 285 0
      src/components/ledger/components/lib/staticDialog.vue
  50. 128 0
      src/components/ledger/components/lib/system.vue
  51. 120 0
      src/components/ledger/components/lib/title.vue
  52. 262 0
      src/components/ledger/components/lib/uploadFiles.vue
  53. 360 0
      src/components/ledger/components/lib/uploadImgs.vue
  54. 377 0
      src/components/ledger/components/lib/uploadImgsName.vue
  55. 341 0
      src/components/ledger/components/rentList/roomCountDrawer.vue
  56. 464 0
      src/components/ledger/components/report/dataexport.vue
  57. 82 0
      src/components/ledger/components/report/datafan.vue
  58. 160 0
      src/components/ledger/components/report/dataorigin.vue
  59. 199 0
      src/components/ledger/components/system/dialog/addCenoteDialog.vue
  60. 287 0
      src/components/ledger/components/system/dialog/addEquipDialog.vue
  61. 252 0
      src/components/ledger/components/system/dialog/addSpaceDialog.vue
  62. 112 0
      src/components/ledger/components/system/lib/equipType.vue
  63. 174 0
      src/components/ledger/components/system/table/cenoteTable.vue
  64. 160 0
      src/components/ledger/components/system/table/deviceTable.vue
  65. 199 0
      src/components/ledger/components/system/table/spaceTable.vue
  66. 398 0
      src/components/ledger/components/tableTransfers.vue
  67. 679 0
      src/components/ledger/facility/addfacility.vue
  68. 661 0
      src/components/ledger/facility/batchPoint.vue
  69. 302 0
      src/components/ledger/facility/batchlink.vue
  70. 675 0
      src/components/ledger/facility/details/index.vue
  71. 296 0
      src/components/ledger/facility/index.vue
  72. 506 0
      src/components/ledger/facility/parts/index.vue
  73. 587 0
      src/components/ledger/facility/partsmanage/addparts/index.vue
  74. 751 0
      src/components/ledger/facility/partsmanage/index.vue

+ 1 - 0
.gitignore

@@ -1 +1,2 @@
 node_modules
+package-lock.json

File diff suppressed because it is too large
+ 0 - 12429
package-lock.json


+ 71 - 0
src/components/ledger/components/addDialog/dialogZone.vue

@@ -0,0 +1,71 @@
+<template>
+  <!-- Form -->
+  <el-dialog title="收货地址" :visible.sync="dialogFormVisible" width="30%">
+    <el-form :model="ruleForm" :rules="rules" ref="ruleForm" label-width="100px" class="demo-ruleForm">
+      <el-form-item label="分区名称" prop="Name">
+        <el-input v-model="ruleForm.Name"></el-input>
+      </el-form-item>
+      <el-form-item label="分区ID" prop="Id">
+        <el-input v-model="ruleForm.Id"></el-input>
+      </el-form-item>
+      <el-form-item style="float: right;margin-top: 20px">
+        <el-button @click="dialogFormVisible = false">取 消</el-button>
+        <el-button type="primary" @click="submitForm('ruleForm')">添 加</el-button>
+      </el-form-item>
+    </el-form>
+  </el-dialog>
+</template>
+
+<script>
+import {
+  zoneCreateType
+} from "@/api/scan/request"
+export default {
+  name: "dialogZone",
+  data() {
+    return {
+      dialogFormVisible: false,
+      ruleForm: {
+        Name: '',
+        Id: '',
+        ParentId: "OtherZone"
+      },
+      rules: {
+        Name: [
+          { required: true, message: '请输入分区名称', trigger: 'blur' },
+          // {min: 3, max: 5, message: '长度在 3 到 5 个字符', trigger: 'blur'}
+        ],
+        Id: [
+          { required: true, message: '请输入分区ID', trigger: 'blur' },
+        ],
+      }
+    };
+  },
+  methods: {
+    submitForm(formName) {
+      this.$refs[formName].validate((valid) => {
+        if (valid) {
+          let pa = {
+            Content: [this.ruleForm]
+          }
+          zoneCreateType(pa, res=>{
+            this.$message.success('添加成功')
+            this.dialogFormVisible = false
+            this.ruleForm.Name = ''
+            this.ruleForm.Id = '';
+            this.$emit('createSuccess')
+          })
+
+        } else {
+          console.log('error submit!!');
+          return false;
+        }
+      });
+    },
+  }
+}
+</script>
+
+<style scoped>
+
+</style>

+ 202 - 0
src/components/ledger/components/cenote/dialog/addCenoteDialog.vue

@@ -0,0 +1,202 @@
+<template>
+  <el-dialog :title="title" :visible.sync="dialogVisible" width="900px" id="addEqDialog">
+    <el-row class="filters">
+      <el-col :span="9">
+        <el-input placeholder="输入竖井名称或竖井本地编码进行查询" v-model="keycode" clearable @keyup.enter.native="getTableData">
+          <i slot="suffix" class="el-input__icon el-icon-search" @click="getTableData"></i>
+        </el-input>
+      </el-col>
+      <el-col :span="15">
+        <cenote-type @change="changeCenote"></cenote-type>
+      </el-col>
+    </el-row>
+    <div class="table-box">
+      <el-table :data="tableData" style="width: 100%" height="350" v-loading="loading" :header-cell-style="headerStyle"
+                ref="multipleTable"
+                @selection-change="handleSelectionChange">
+        <el-table-column type="selection" width="55"></el-table-column>
+        <el-table-column :label="`${inSpaceType}名称`" show-overflow-tooltip min-width="100">
+          <template slot-scope="scope">
+            <div>
+              {{ scope.row.localName || scope.row.name || '' }}
+            </div>
+          </template>
+        </el-table-column>
+        <el-table-column prop="localId" :label="`${inSpaceType}本地编码`" show-overflow-tooltip
+                         min-width="100"></el-table-column>
+        <el-table-column prop="infos.shaftFuncType" :label="`${inSpaceType}类`" show-overflow-tooltip
+                         min-width="100"></el-table-column>
+        <el-table-column prop="action" label="操作" min-width="100">
+          <template slot-scope="scope">
+            <el-button size="mini" @click="toDetail(scope.$index, scope.row)" plain>查看详情</el-button>
+          </template>
+        </el-table-column>
+      </el-table>
+      <!-- 分页 -->
+      <el-pagination class="fr" v-show="tableData && tableData.length" @size-change="handleSizeChange"
+                     @current-change="handleCurrentChange"
+                     :current-page="page.pageNumber" :page-sizes="page.pageSizes" :page-size="page.pageSize"
+                     layout="total, sizes, prev, pager, next, jumper"
+                     :total="page.total"></el-pagination>
+    </div>
+    <span slot="footer" class="dialog-footer">
+      <el-button size="small" @click="dialogVisible = false">取 消</el-button>
+      <el-button size="small" type="primary" @click="savaRelation">确 定</el-button>
+    </span>
+  </el-dialog>
+</template>
+
+<script>
+import { linkShaft, unshaftThroughShaft } from "@/api/scan/request";
+import { getDataDictionary } from "@/api/dict"
+
+import cenoteType from "@/components/ledger/lib/cenoteType";
+import { mapGetters } from "vuex";
+
+export default {
+  components: {
+    cenoteType
+  },
+  computed: {
+    ...mapGetters("layout", ["projectId"])
+  },
+  data() {
+    return {
+      title: "添加连通的其他竖井",
+      keycode: '', //输入查询条件
+      cenoteType: '', // 选中的竖井类型
+      shaftType: {}, //全部竖井类型
+      inSpaceType: '竖井',
+      dialogVisible: false,
+      tableData: [],
+      loading: false,
+      selections: [], // 选中项
+      page: {
+        pageSize: 50,
+        pageSizes: [10, 20, 50, 100],
+        pageNumber: 1,
+        total: 0
+      },
+      headerStyle: {
+        backgroundColor: '#e1e4e5',
+        color: '#2b2b2b',
+        lineHeight: '30px'
+      } // 列表样式
+    };
+  },
+  props: {
+    type: String, //选中的tab页
+    params: Object //查看的竖井关系信息
+  },
+  created() {
+    // 查询 竖井所有类型
+    // queryShaftType(res => {
+    let param = {
+      "pageNumber": 1,
+      "pageSize": 50,
+      "filters": "code = 'shaftFuncType'",
+      "type": "shaft",
+      "orders": "sort asc, name desc",
+    }
+    getDataDictionary(param, res => {
+      res.content.forEach(item => {
+        item.dataSource && item.dataSource.forEach(i => {
+          this.shaftType[i.code] = i.name
+        })
+      })
+      this.getTableData()
+    })
+  },
+  methods: {
+    //修改竖井类型
+    changeCenote(value) {
+      this.cenoteType = value.Id
+      this.getTableData()
+    },
+    // 显示弹窗
+    showDialog() {
+      this.dialogVisible = true
+      this.page.pageNumber = 1
+      this.tableData = []
+      this.getTableData()
+    },
+    getTableData() {
+      let params = {
+        data: {
+          filters: this.cenoteType ? `projectId='${this.projectId}';infos.shaftFuncType='${this.cenoteType}'` : `projectId='${this.projectId}'`,
+          orders: "createTime desc, id asc",
+          pageNumber: this.page.pageNumber,
+          pageSize: this.page.pageSize,
+        },
+        shaftId: this.params.ShaftID
+      }
+      if (this.keycode != '') {
+        params.data.filters += `;name contain '${this.keycode}' or localName contain '${this.keycode}' or localId contain '${this.keycode}'`
+      }
+      unshaftThroughShaft(params, res => {
+        this.tableData = res.content
+        this.tableData.forEach(item => {
+          if (item.infos && item.infos.shaftFuncType) {
+            item.infos.shaftFuncType = this.shaftType[item.infos.shaftFuncType]
+          }
+        })
+        this.page.total = res.total
+      })
+    },
+    //选中项修改
+    handleSelectionChange(val) {
+      this.selections = val;
+    },
+    savaRelation() {
+      if (this.selections.length) {
+        let params = {
+          shaftId: this.params.ShaftID,
+          shaftOtherIdList: this.selections.map(item => {
+            return item.id
+          })
+        }
+        linkShaft(params, res => {
+          this.dialogVisible = false
+          this.$message.success('关联成功!')
+          this.$emit('refresh')
+        })
+      } else {
+        this.$message('请选择要关联的设备')
+      }
+    },
+    //改变pagesize
+    handleSizeChange(pageSize) {
+      this.page.pageSize = pageSize;
+      this.getTableData();
+    },
+    //改变pageno
+    handleCurrentChange(pageNo) {
+      this.page.pageNumber = pageNo;
+      this.getTableData();
+    },
+    // 查看详情
+    toDetail() {
+      this.$message('开发中')
+    }
+  },
+};
+</script>
+<style lang="less" scoped>
+#addEqDialog {
+  .filters {
+    margin-bottom: 10px;
+
+    /deep/ .el-input__inner {
+      vertical-align: baseline;
+    }
+  }
+
+  .table-box {
+    height: 370px;
+
+    .fr {
+      margin-top: 10px;
+    }
+  }
+}
+</style>

+ 198 - 0
src/components/ledger/components/cenote/dialog/addEquipDialog.vue

@@ -0,0 +1,198 @@
+<template>
+  <el-dialog :title="title" :visible.sync="dialogVisible" width="900px" id="addEqDialog">
+    <el-row class="filters">
+      <el-col :span="9">
+        <el-input placeholder="输入设备名称或设备本地编码进行查询" v-model="keycode" clearable @keyup.enter.native="getTableData">
+          <i slot="suffix" class="el-input__icon el-icon-search" @click="getTableData"></i>
+        </el-input>
+      </el-col>
+      <el-col :span="15">
+        <floor-cascader @change="changeFloor"></floor-cascader>
+      </el-col>
+    </el-row>
+    <div class="table-box">
+      <el-table :data="tableData" style="width: 100%" height="350" v-loading="loading" :header-cell-style="headerStyle"
+                ref="multipleTable"
+                @selection-change="handleSelectionChange">
+        <el-table-column type="selection" width="55"></el-table-column>
+        <el-table-column :label="`${inSpaceType}名称`" show-overflow-tooltip min-width="100">
+          <template slot-scope="scope">
+            <div>
+              {{ scope.row.localName || scope.row.name|| '' }}
+            </div>
+          </template>
+        </el-table-column>
+        <el-table-column prop="localId" :label="`${inSpaceType}本地编码`" show-overflow-tooltip
+                         min-width="100"></el-table-column>
+        <el-table-column prop="equipCategory.name" :label="`${inSpaceType}类`" show-overflow-tooltip
+                         min-width="100"></el-table-column>
+        <el-table-column prop="ShaftListName" label="已关联其他竖井" show-overflow-tooltip min-width="100"
+                         max-width="200"></el-table-column>
+        <el-table-column prop="action" label="操作" min-width="100">
+          <template slot-scope="scope">
+            <el-button size="mini" @click="toDetail(scope.$index, scope.row)" plain>查看详情</el-button>
+          </template>
+        </el-table-column>
+      </el-table>
+      <!-- 分页 -->
+      <el-pagination class="fr" v-show="tableData && tableData.length" @size-change="handleSizeChange"
+                     @current-change="handleCurrentChange"
+                     :current-page="page.pageNumber" :page-sizes="page.pageSizes" :page-size="page.pageSize"
+                     layout="total, sizes, prev, pager, next, jumper"
+                     :total="page.total"></el-pagination>
+    </div>
+    <span slot="footer" class="dialog-footer">
+      <el-button size="small" @click="dialogVisible = false">取 消</el-button>
+      <el-button size="small" type="primary" @click="savaRelation">确 定</el-button>
+    </span>
+  </el-dialog>
+</template>
+
+<script>
+import { unshaftInEq, linkEqSh } from "@/api/scan/request";
+import floorCascader from "@/components/ledger/lib/floorCascader";
+import { mapGetters } from "vuex";
+
+export default {
+  components: {
+    floorCascader
+  },
+  computed: {
+    ...mapGetters("layout", ["projectId"])
+  },
+  data() {
+    return {
+      title: "添加竖井内的设备",
+      keycode: '', //输入查询条件
+      Buildfloor: ['all'], // 选中的建筑楼层
+      inSpaceType: '设备',
+      dialogVisible: false,
+      tableData: [],
+      loading: false,
+      selections: [], // 选中项
+      page: {
+        pageSize: 50,
+        pageSizes: [10, 20, 50, 100],
+        pageNumber: 1,
+        total: 0
+      },
+      headerStyle: {
+        backgroundColor: '#e1e4e5',
+        color: '#2b2b2b',
+        lineHeight: '30px'
+      } // 列表样式
+    };
+  },
+  props: {
+    type: String, //选中的tab页
+    params: Object //查看的竖井关系信息
+  },
+  created() {
+  },
+  methods: {
+    //修改建筑楼层
+    changeFloor(value) {
+      this.Buildfloor = value
+      this.getTableData()
+    },
+    // 显示弹窗
+    showDialog() {
+      this.dialogVisible = true
+      this.page.pageNumber = 1
+      this.tableData = []
+      this.getTableData()
+    },
+    getTableData() {
+      let params = {
+        data: {
+          cascade: [{ name: 'equipCategory' }, { name: 'shaftList' }],
+          filters: `not id isNull`,
+          orders: "createTime desc, id desc",
+          pageNumber: this.page.pageNumber,
+          pageSize: this.page.pageSize,
+        },
+        shaftId: this.params.ShaftID
+      }
+      if (this.Buildfloor[0] == "noKnow") {
+        params.data.filters += `;buildingId isNull`
+      } else if (this.Buildfloor[0] && this.Buildfloor[0] != "all") {
+        params.data.filters += `;buildingId='${this.Buildfloor[0]}'`
+      }
+      if (this.Buildfloor[1] == "noKnow") {
+        params.data.filters += `;floorId isNull`
+      } else if (this.Buildfloor[1] && this.Buildfloor[1] != "all") {
+        params.data.filters += `;floorId='${this.Buildfloor[1]}'`
+      }
+      if (this.keycode != '') {
+        params.data.filters += `;name contain '${this.keycode}' or localName contain '${this.keycode}' or localId contain '${this.keycode}'`
+      }
+      unshaftInEq(params, res => {
+        res.content.forEach(item => {
+          item.ShaftListName = ""
+          if (item.shaftList && item.shaftList.length) {
+            item.ShaftListName = item.shaftList.map(shaft => {
+              return shaft.localName ? shaft.localName : shaft.name
+            }).join("、")
+          }
+        })
+        this.tableData = res.content
+        this.page.total = res.total
+      })
+    },
+    //选中项修改
+    handleSelectionChange(val) {
+      this.selections = val;
+    },
+    savaRelation() {
+      if (this.selections.length) {
+        let params = {
+          shaftId: this.params.ShaftID,
+          equipIdList: this.selections.map(item => {
+            return item.id
+          })
+        }
+        linkEqSh(params, res => {
+          this.dialogVisible = false
+          this.$message.success('关联成功!')
+          this.$emit('refresh')
+        })
+      } else {
+        this.$message('请选择要关联的设备')
+      }
+    },
+    //改变pagesize
+    handleSizeChange(pageSize) {
+      this.page.pageSize = pageSize;
+      this.getTableData();
+    },
+    //改变pageno
+    handleCurrentChange(pageNo) {
+      this.page.pageNumber = pageNo;
+      this.getTableData();
+    },
+    // 查看详情
+    toDetail() {
+      this.$message('开发中')
+    }
+  },
+};
+</script>
+<style lang="less" scoped>
+#addEqDialog {
+  .filters {
+    margin-bottom: 10px;
+
+    /deep/ .el-input__inner {
+      vertical-align: baseline;
+    }
+  }
+
+  .table-box {
+    height: 370px;
+
+    .fr {
+      margin-top: 10px;
+    }
+  }
+}
+</style>

+ 223 - 0
src/components/ledger/components/cenote/dialog/addSpaceDialog.vue

@@ -0,0 +1,223 @@
+
+<template>
+  <el-dialog :title="title" :visible.sync="dialog" :before-close="handleClose" width="900px" id="addEqDialog">
+    <el-row class="filters">
+      <el-col :span="7" style="width:268px;padding:0px;">
+        <el-input style placeholder="输入业务空间名称、本地编码进行查询" v-model="keycode" clearable @keyup.enter.native="getTableData">
+          <i slot="suffix" class="el-input__icon el-icon-search" @click="getTableData"></i>
+        </el-input>
+      </el-col>
+      <el-col :span="8.5" style="padding:0 0;margin-left:-2px">
+        <floor-cascader ref="floorcas" @change="changeFloor"></floor-cascader>
+      </el-col>
+      <el-col :span="7.5" style="padding:0 0;">
+        <space-select ref="spacesel" @change="changeSpace"></space-select>
+      </el-col>
+    </el-row>
+    <el-row class="filters" :gutter="20"></el-row>
+    <div class="table-box">
+      <el-table :data="tableData" style="width: 100%" height="350" v-loading="loading" :header-cell-style="headerStyle" ref="multipleTable"
+        @selection-change="handleSelectionChange">
+        <el-table-column type="selection" width="55"></el-table-column>
+        <el-table-column :label="`${inSpaceType}本地名称`" show-overflow-tooltip min-width="100">
+          <template slot-scope="scope">
+            {{scope.row.localName||scope.row.name||''}}
+          </template>
+        </el-table-column>
+        <el-table-column prop="localId" :label="`${inSpaceType}本地编码`" show-overflow-tooltip min-width="100"></el-table-column>
+        <el-table-column prop :label="`所属建筑楼层`" show-overflow-tooltip min-width="100">
+          <template slot-scope="scope">
+            {{ scope.row.buildingId && floorType[scope.row.buildingId]? ((floorType[scope.row.buildingId] || '') + (scope.row.floorId && floorType[scope.row.floorId] ? (' - ' + floorType[scope.row.floorId] || '') : '')) : ''}}
+          </template>
+        </el-table-column>
+        <el-table-column prop="classCode" :label="`空间类型`" show-overflow-tooltip min-width="100">
+          <template slot-scope="scope">
+            {{spaceType[scope.row.classCode] || ''}}
+          </template>
+        </el-table-column>
+        <el-table-column prop="action" label="操作" min-width="100">
+          <template slot-scope="scope">
+            <el-button size="mini" @click="toDetail(scope.$index, scope.row)" plain>查看详情</el-button>
+          </template>
+        </el-table-column>
+      </el-table>
+      <!-- 分页 -->
+      <el-pagination class="fr" v-show="tableData && tableData.length" @size-change="handleSizeChange" @current-change="handleCurrentChange"
+        :current-page="page.pageNumber" :page-sizes="page.pageSizes" :page-size="page.pageSize" layout="total, sizes, prev, pager, next, jumper"
+        :total="page.total"></el-pagination>
+    </div>
+    <span slot="footer" class="dialog-footer">
+      <el-button size="small" @click="handleClose">取 消</el-button>
+      <el-button size="small" type="primary" @click="savaRelation">确 定</el-button>
+    </span>
+  </el-dialog>
+</template>
+
+<script>
+import {
+  shaftUnlinkSpaceQuery,
+  unSysZoneSpace,
+  linkShaftSpace
+} from "@/api/scan/request";
+import floorCascader from "@/components/ledger/lib/floorCascader";
+import dictionaryCas from "@/components/config_point/dictionaryCascader";
+import spaceSelect from "@/components/ledger/lib/spaceSelect";
+import { mapGetters } from "vuex";
+export default {
+  components: {
+    floorCascader,
+    dictionaryCas,
+    spaceSelect
+  },
+  computed: {
+    ...mapGetters("layout", ["projectId"]),
+    dialog() {
+      return this.dialogVisible;
+    }
+  },
+  data() {
+    return {
+      title: "添加竖井所在业务空间",
+      keycode: "", //输入查询条件
+      inSpaceType: "业务空间",
+      Buildfloor: ["all"], // 选中的建筑楼层
+      Spacecategory: null, // 选中的业务空间类型
+      tableData: [],
+      floorData: {}, //楼层
+      spaceData: {}, //业务空间分区
+      loading: false,
+      selections: [], // 选中项
+      page: {
+        pageSize: 50,
+        pageSizes: [10, 20, 50, 100],
+        pageNumber: 1,
+        total: 0
+      },
+      headerStyle: {
+        backgroundColor: "#e1e4e5",
+        color: "#2b2b2b",
+        lineHeight: "30px"
+      } // 列表样式
+    };
+  },
+  props: {
+    params: Object, //查看的竖井关系信息
+    spaceType: Object,
+    dialogVisible: Boolean,
+    floorType: Object
+  },
+  mounted() { },
+  methods: {
+    // 显示弹窗
+    showDialog() {
+      this.dialogVisible = true;
+    },
+    //修改建筑楼层
+    changeFloor(value) {
+      this.Buildfloor = value;
+      this.getTableData();
+    },
+    //修改空间类型
+    changeSpace(value) {
+      this.Spacecategory = value;
+      this.getTableData();
+    },
+    getTableData() {
+      let params = {
+        data: {
+          filters: "not id isNull",
+          orders: "localName desc",
+          pageNumber: this.page.pageNumber,
+          pageSize: this.page.pageSize
+        },
+        ShaftId: this.$route.query.ShaftId
+      };
+      if (this.Buildfloor[0] == "noKnow") {
+        params.data.filters += `;buildingId isNull`;
+      } else if (this.Buildfloor[0] && this.Buildfloor[0] != "all") {
+        params.data.filters += `;buildingId='${this.Buildfloor[0]}'`;
+      }
+      if (this.Buildfloor[1] == "noKnow") {
+        params.data.filters += `;floorId isNull`;
+      } else if (this.Buildfloor[1] && this.Buildfloor[1] != "all") {
+        params.data.filters += `;floorId='${this.Buildfloor[1]}'`;
+      }
+      if (this.Spacecategory) {
+        params.data.filters += `;classCode='${this.Spacecategory}'`;
+      }
+      if (this.keycode != "") {
+        params.data.filters += `;name contain '${this.keycode}' or localName contain '${this.keycode}'`;
+      }
+      shaftUnlinkSpaceQuery(params, res => {
+        this.tableData = res.content;
+        this.page.total = res.total;
+      });
+    },
+    handleClose() {
+      this.$emit("update:dialogVisible", false);
+    },
+    //选中项修改
+    handleSelectionChange(val) {
+      this.selections = val;
+    },
+    savaRelation() {
+      if (this.selections.length) {
+        let params = {
+          Content: this.selections.map(item => {
+            return {
+              spaceID: item.id,
+              shaftId: this.$route.query.ShaftId,
+              objectType: item.classCode
+            };
+          })
+        };
+        linkShaftSpace(params, res => {
+          this.$message.success("关联成功!");
+          this.$emit("refresh");
+        });
+      } else {
+        this.$message("请选择要关联的设备");
+      }
+    },
+    //改变pagesize
+    handleSizeChange(pageSize) {
+      this.page.pageSize = pageSize;
+      this.getTableData();
+    },
+    //改变pageno
+    handleCurrentChange(pageNo) {
+      this.page.pageNumber = pageNo;
+      this.getTableData();
+    },
+    // 查看详情
+    toDetail() {
+      this.$message("开发中");
+    }
+  },
+  watch: {
+    dialogVisible: {
+      handler() {
+        if (this.dialogVisible) {
+          this.page.pageNumber = 1;
+          this.tableData = [];
+          this.getTableData();
+        }
+      },
+      immediate: true
+    }
+  }
+};
+</script>
+<style lang="less" scoped>
+#addEqDialog {
+  .filters {
+    margin: 0 0 10px 0;
+  }
+  .table-box {
+    height: 370px;
+    .fr {
+      margin-top: 10px;
+    }
+  }
+}
+</style>

+ 147 - 0
src/components/ledger/components/cenote/dialog/addSystemDialog.vue

@@ -0,0 +1,147 @@
+<template>
+  <el-dialog :title="title" :visible.sync="dialogVisible" width="900px" id="addEqDialog">
+    <div class="table-box">
+      <el-table :data="tableData" style="width: 100%" height="350" v-loading="loading" :header-cell-style="headerStyle" ref="multipleTable"
+        @selection-change="handleSelectionChange">
+        <el-table-column  type="selection" width="55"></el-table-column>
+        <el-table-column :label="`${inSpaceType}名称`" show-overflow-tooltip min-width="100">
+          <template slot-scope="scope">
+            <div>
+              {{scope.row.localName||scope.row.name ||''}}
+            </div>
+          </template>
+        </el-table-column>
+        <el-table-column prop="localId" :label="`${inSpaceType}本地编码`" show-overflow-tooltip min-width="100"></el-table-column>
+        <el-table-column prop="categoryNames.name" :label="`${inSpaceType}类`" show-overflow-tooltip min-width="100"></el-table-column>
+        <el-table-column prop="action" label="操作" min-width="100">
+          <template slot-scope="scope">
+            <el-button size="mini" @click="toDetail(scope.$index, scope.row)" plain>查看详情</el-button>
+          </template>
+        </el-table-column>
+      </el-table>
+      <!-- 分页 -->
+      <el-pagination class="fr" v-show="tableData && tableData.length" @size-change="handleSizeChange" @current-change="handleCurrentChange"
+        :current-page="page.pageNumber" :page-sizes="page.pageSizes" :page-size="page.pageSize" layout="total, sizes, prev, pager, next, jumper"
+        :total="page.total"></el-pagination>
+    </div>
+    <span slot="footer" class="dialog-footer">
+      <el-button size="small" @click="dialogVisible = false">取 消</el-button>
+      <el-button size="small" type="primary" @click="savaRelation">确 定</el-button>
+    </span>
+  </el-dialog>
+</template>
+
+<script>
+import { unshaftInSys, linkSySh } from "@/api/scan/request";
+import floorCascader from "@/components/ledger/lib/floorCascader";
+import { mapGetters } from "vuex";
+export default {
+  components: {
+    floorCascader
+  },
+  computed: {
+    ...mapGetters("layout", ["projectId"])
+  },
+  data() {
+    return {
+      title: "添加竖井内的系统",
+      inSpaceType: '系统',
+      dialogVisible: false,
+      tableData: [],
+      loading: false,
+      selections: [], // 选中项
+      page: {
+        pageSize: 50,
+        pageSizes: [10, 20, 50, 100],
+        pageNumber: 1,
+        total: 0
+      },
+      headerStyle: {
+        backgroundColor: '#e1e4e5',
+        color: '#2b2b2b',
+        lineHeight: '30px'
+      } // 列表样式
+    };
+  },
+  props: {
+    type: String, //选中的tab页
+    params: Object //查看的竖井关系信息
+  },
+  created() { },
+  methods: {
+    // 显示弹窗
+    showDialog() {
+      this.dialogVisible = true
+      this.page.pageNumber = 1
+      this.tableData = []
+      this.getTableData()
+    },
+    getTableData() {
+      let params = {
+        data: {
+          cascade: [{ name: 'categoryNames' }],
+          orders: "createTime desc, id desc",
+          pageNumber: this.page.pageNumber,
+          pageSize: this.page.pageSize,
+        },
+        shaftId: this.params.ShaftID
+      }
+      unshaftInSys(params, res => {
+        this.tableData = res.content
+        this.page.total = res.total
+      })
+    },
+    //选中项修改
+    handleSelectionChange(val) {
+      this.selections = val;
+    },
+    savaRelation() {
+      if(this.selections.length){
+        let params = {
+          shaftId: this.params.ShaftID,
+          sysIdList: this.selections.map(item => {
+            return item.id
+          })
+        }
+        linkSySh(params, res => {
+          this.dialogVisible = false
+          this.$message.success('关联成功!')
+          this.$emit('refresh')
+        })
+      }else {
+        this.$message('请选择要关联的设备')
+      }
+    },
+    //改变pagesize
+    handleSizeChange(pageSize) {
+      this.page.pageSize = pageSize;
+      this.getTableData();
+    },
+    //改变pageno
+    handleCurrentChange(pageNo) {
+      this.page.pageNumber = pageNo;
+      this.getTableData();
+    },
+    // 查看详情
+    toDetail() {
+      this.$message('开发中')
+    }
+  },
+};
+</script>
+<style lang="less" scoped>
+#addEqDialog {
+  .filters {
+    margin-bottom: 10px;
+    /deep/ .el-input__inner {
+      vertical-align: baseline;
+    }
+  }
+  .table-box {
+    height: 370px;
+    .fr{
+      margin-top: 10px;
+    }
+  }
+}
+</style>

+ 252 - 0
src/components/ledger/components/cenote/elevationMap.vue

@@ -0,0 +1,252 @@
+<template>
+  <div v-loading="load" class="saga-elevation">
+    <div v-if="elevationData.length" class="saga-floor">
+      <div class="saga-rf">
+        <p class="saga-floorName">RF</p>
+        <div class="saga-spaceList">
+          <div class="saga-group" v-for="(winth, index) in groupWidth" :key="index" :style="`width: ${100*winth}px;`"></div>
+        </div>
+      </div>
+      <div class="saga-floor-item" v-for="floor in elevationData" :key="floor.id">
+        <p class="saga-floorName" :title="floor.localName?floor.localName:floor.name">
+          {{floor.localName?floor.localName:floor.name}}
+          <i class="el-icon-right"></i>
+        </p>
+        <div class="saga-spaceList">
+          <div class="saga-group" v-for="(group, groupIndex) in floor.group" :style="`width: ${100*groupWidth[groupIndex]}px;`">
+            <div class="saga-spaceItem" 
+            :class="[spaceItem.IsAI?spaceItem.checked?'saga-spaceItem-checked':'saga-spaceItem-ai':'']" 
+            v-for="spaceItem in group.spaceList" 
+            :key="spaceItem.RoomID"
+            :title="spaceItem.RoomLocalName?spaceItem.RoomLocalName:spaceItem.RoomName"
+            @click="handleClickCheck(spaceItem)"
+            >
+            {{spaceItem.localName || spaceItem.name}}
+            </div>
+          </div>
+        </div>
+      </div>
+    </div>
+    <div v-else class="saga-center">
+      <i class="icon-wushuju iconfont"></i>
+      暂无数据
+    </div>
+  </div>
+</template>
+
+<script>
+  import { shaftVerticalSpace, shaftZoneLink } from "@/api/scan/request"
+
+  import {mapGetters} from 'vuex'
+  export default {
+  data () {
+    return {
+      elevationData: [],
+      groupWidth: {},
+      checkedList: [],
+      load: false,
+    }
+  },
+  computed: {
+    ...mapGetters("layout", ["projectId"])
+  },
+  props: {
+    params: Object,
+    onlyRead: Boolean,
+    isEdit: Boolean,
+    isAI: Boolean,
+  },
+  methods: {
+    init () { //获取数据并格式化数据
+      this.elevationData = [];
+      //根据isAI判断请求参数是否包含AI推介数据(暂未完成)
+      this.params.aiSwitch = this.isAI
+      this.load = true;
+      // 查询竖井关联的空间垂直交通关系
+      shaftVerticalSpace(this.params, (res) => {
+        let data = res.content || [];
+        let max = 0, idMap = {}, groupMap = {}, copyData = JSON.parse(JSON.stringify(data));
+        this.groupWidth = {};
+        copyData.reverse().forEach(floor => {
+          if (floor.zoneSpaceList && floor.zoneSpaceList.length) {
+            floor.zoneSpaceList.forEach(space => {
+              if (idMap[space.id]) {  // 该业务空间在映射列表中
+                groupMap[idMap[space.id]].push(space)
+                space.spaceIdList.forEach(RoomID =>{
+                  if (!idMap[RoomID]) {
+                    idMap[RoomID] = idMap[space.id];
+                  }
+                })
+              } else { // 该业务空间不在映射列表中
+                max++;
+                idMap[space.id] = max;
+                groupMap[max] = [];
+                this.groupWidth[max] = 0;
+                groupMap[max].push(space);
+                space.spaceIdList.forEach(RoomID =>{
+                  if (!idMap[RoomID]) {
+                    idMap[RoomID] = max;
+                  }
+                })
+              }
+            })
+          }
+        })
+        data.forEach((floor, index) =>{
+          floor.group = {};
+          Object.keys(groupMap).forEach(groupIndex =>{
+            let max = 0;
+            floor.group[groupIndex] = {
+              spaceList: []
+            };
+            if (floor.zoneSpaceList && floor.zoneSpaceList.length) {
+              floor.zoneSpaceList.forEach(space => {
+                if (groupMap[groupIndex].find(item => { return space.id == item.id })) {
+                  space.checked = false;
+                  floor.group[groupIndex].spaceList.push(space);
+                  max++;
+                }
+              })
+            }
+            if (max > this.groupWidth[groupIndex]) {
+              this.groupWidth[groupIndex] = max;
+            }
+          })
+        });
+        this.elevationData = data
+        this.load = false;
+      })
+    },
+    handleClickCheck (sapceItem) { //点选推介的业务空间
+      if (sapceItem.IsAI && this.isEdit && !this.onlyRead) { //是AI推荐并且编辑模式下
+        sapceItem.checked = sapceItem.checked? false: true;
+        if (sapceItem.checked) {
+          this.checkedList.push(sapceItem);
+          // 选中平面图的对应空间
+          this.$emit('elevationChecked',sapceItem.RoomID);
+        } else {
+          let index = this.checkedList.findIndex(item =>{ return item.RoomID == sapceItem.RoomID })
+          if (index != -1) { // 找到对应的数据
+            this.checkedList.splice(index,1);
+            // 取消选中平面图的对应空间
+            this.$emit('elevationUncheck',sapceItem.RoomID);
+          }
+        }
+      }
+    },
+    savaEdit () {// 接口保存数据和业务空间的关系
+      let params = {
+        type: this.params.objectType,
+        data: {
+          shaftId: this.params.shaftId,
+          spaceIdList: this.checkedList.map(item => {
+            return item.RoomID
+          })
+        }
+      }
+      shaftZoneLink(params, res =>{
+        // this.$message.success("保存成功");
+        this.init();
+      })
+    }
+  },
+  watch: {
+    projectId() {
+      if (this.params.buildingId && this.params.objectType && this.params.shaftId) {
+        this.init();
+      }
+      console.log(this.projectId)
+    },
+    // isAI() {
+    //   if (this.params.BuildingId && this.params.ObjectType && this.params.ShaftId) {
+    //     this.init();
+    //   }
+    // },
+    params: {
+      handler() {
+        if (this.params.buildingId && this.params.objectType && this.params.shaftId) {
+          this.init();
+        }
+      },
+      immediate: true,
+      deep: true,
+    }
+  }
+}
+</script>
+
+<style lang="less">
+  .saga-elevation {
+    height: 100%;
+    position: relative;
+    .saga-center{
+      position: absolute;
+      top: 50%;
+      left: 50%;
+      transform: translate(-50%,-60%);
+      text-align: center;
+    }
+    .saga-floor {
+      display: flex;
+      box-sizing: border-box;
+      flex-direction: column;
+      justify-content: flex-end;
+      padding: 60px 10px 20px 0px;
+      .saga-rf {
+        display: flex;
+        flex-direction: row;
+        font-size: 12px;
+        color: blue;
+      }
+      .saga-floor-item {
+        display: flex;
+        flex-direction: row;
+        font-size: 12px;
+        height: 60px;
+        line-height: 60px;
+      }
+      .saga-floorName {
+        min-width: 80px;
+        text-align: right;
+        overflow: hidden;
+        text-overflow: ellipsis;
+        white-space: nowrap;
+      }
+      .saga-spaceList {
+        display: flex;
+        flex-direction: row;
+        flex: 1;
+        border-bottom: 2px solid #c1c6d2;
+        box-sizing: border-box;
+        .saga-group {
+          display: flex;
+          flex-direction: row;
+          margin-left: 25px;
+          box-sizing: border-box;
+          .saga-spaceItem {
+            width: 100px;
+            margin-left: 1px;
+            text-align: center;
+            overflow: hidden;
+            text-overflow:ellipsis;
+            white-space: nowrap;
+            background-color: #ffcc00;
+            border: 1px solid #333333;
+          }
+          .saga-spaceItem-ai{
+            cursor: pointer;
+            box-sizing: border-box;
+            background-color: #eaeae9;
+            border: 1px dashed #333333;
+          }
+          .saga-spaceItem-checked {
+            cursor: pointer;
+            box-sizing: border-box;
+            background-color: #1abc9c;
+            border: 1px dashed #333333;
+          }
+        }
+      }
+    }
+  }
+</style>

+ 202 - 0
src/components/ledger/components/cenote/relatedSpaceList.vue

@@ -0,0 +1,202 @@
+<template>
+  <div id="relatedSpaceList" style="height:100%;">
+    <div class="query">
+      <el-input v-model="keyword" placeholder="请输入关键词" @keyup.enter.native="getSpaceList" style="width:240px;margin:0 0;">
+        <i slot="suffix" class="el-input__icon el-icon-search" @click="getSpaceList"></i>
+      </el-input>
+      <label style="line-height:32px;padding: 0 10px;">所属建筑楼层:</label>
+      <el-cascader v-model="Buildfloor" :options="buildingData" :props="{value: 'id', label: 'localName', children: 'floor'}" @change="handleChange"></el-cascader>
+    </div>
+    <div class="table-area" style="height:calc(100% - 52px)">
+      <el-table :data="tableData" style="width: 100%;height:calc(100% - 48px);" v-loading="loading" :header-cell-style="headerStyle">
+        <el-table-column label='业务空间建筑楼层' show-overflow-tooltip min-width="200" header-align='center' align="center">
+          <template slot-scope="scope">
+            {{ scope.row.buildingId && floorType[scope.row.buildingId]? ((floorType[scope.row.buildingId] || '') + (scope.row.floorId && floorType[scope.row.floorId] ? (' - ' + floorType[scope.row.floorId] || '') : '')) : ''}}
+          </template>
+        </el-table-column>
+        <el-table-column label='业务空间类型' show-overflow-tooltip min-width="200" header-align='center' align="center">
+          <template slot-scope="scope">
+            {{spaceType[scope.row.classCode] || ''}}
+          </template>
+        </el-table-column>
+        <el-table-column prop='localName' label='业务空间本地名称' show-overflow-tooltip min-width="200" header-align='center' align="center">
+        </el-table-column>
+        <el-table-column prop='localId' label='业务空间本地编码' show-overflow-tooltip min-width="200" header-align='center' align="center">
+        </el-table-column>
+        <el-table-column v-if="$route.query.onlyRead !== 'true'" label='操作' show-overflow-tooltip min-width="90" header-align='center' align='center'>
+          <template slot-scope="scope">
+            <el-button size="mini" @click="handleDelete(scope.$index, scope.row)" type="danger" plain icon="el-icon-delete"></el-button>
+          </template>
+        </el-table-column>
+      </el-table>
+      <!-- 分页 -->
+      <el-pagination @size-change="getSpaceList" @current-change="getSpaceList" :current-page="currentPage" :page-sizes="pageSizes"
+        :page-size="pageSize" layout="total, sizes, prev, pager, next, jumper" :total="tableData.length" style="float:right;padding:20px 20px 0 0;">
+      </el-pagination>
+    </div>
+  </div>
+</template>
+
+<script>
+import { shaftSpaceQuery, spaceInShaftUnlink } from "@/api/scan/request";
+export default {
+  data() {
+    return {
+      //表格头样式
+      headerStyle: {
+        backgroundColor: '#e1e4e5',
+        color: '#2b2b2b',
+        lineHeight: '30px'
+      },
+      pageSizes: [10, 20, 50, 100],
+      pageSize: 50,
+      currentPage: 1,
+      keyword: '',//关键词
+      floor: '',//当前楼层
+      tableData: [],//表格数据
+      loading: false,//加载
+      Buildfloor: ["all"],//当前选中的楼层
+      page: {
+        pageSize: 50,
+        pageSizes: [10, 20, 50, 100],
+        pageNumber: 1,
+        total: 0
+      },
+      buildingData: [],//楼层数据
+    }
+  },
+  components: {
+  },
+  methods: {
+    //空间列表
+    getSpaceList() {
+      this.loading = true;
+      let params = {
+        data: {
+          filters: "not id isNull",
+          orders: "localName desc",
+          pageNumber: this.page.pageNumber,
+          pageSize: this.page.pageSize
+        },
+        ShaftId: this.$route.query.ShaftId
+      };
+      if (this.Buildfloor[0] == "noKnow") {
+        params.data.filters += `;buildingId isNull`;
+      } else if (this.Buildfloor[0] && this.Buildfloor[0] != "all") {
+        params.data.filters += `;buildingId='${this.Buildfloor[0]}'`;
+      }
+      if (this.Buildfloor[1] == "noKnow") {
+        params.data.filters += `;floorId isNull`;
+      } else if (this.Buildfloor[1] && this.Buildfloor[1] != "all") {
+        params.data.filters += `;floorId='${this.Buildfloor[1]}'`;
+      }
+      if (this.space) {
+        params.data.filters += `;classCode='${this.space}'`;
+      }
+      if (this.keyword) {
+        params.data.filters += `;name contain '${this.keyword}' or localName contain '${this.keyword}'`;
+      }
+      shaftSpaceQuery(params, res => {
+        this.loading = false;
+        this.tableData = res.content
+      })
+    },
+    //切换楼层
+    handleChange() {
+      this.getSpaceList();
+    },
+    // 删除关系
+    handleDelete(index, row) {
+      this.$confirm("确认删除该关系?", "提示", {
+        confirmButtonText: "确定",
+        cancelButtonText: "取消",
+        type: "warning"
+      })
+        .then(() => {
+          let params = {
+            data: [
+              {
+                shaftId: this.$route.query.ShaftId,
+                spaceId: row.id
+              }
+            ],
+            type: row.classCode
+          };
+          this.deleteSpInSh(params);
+        })
+        .catch(() => {
+          this.$message("取消删除");
+        });
+    },
+    // 删除系统所在竖井关系
+    deleteSpInSh(params) {
+      spaceInShaftUnlink(params, res => {
+        this.$message.success("删除成功");
+        this.getSpaceList();
+      });
+    },
+    //楼层
+    setFloorData(val) {
+      if (val && val.length) {
+        let bL = val.concat();
+        bL.map(item => {
+          if (!(item.floor && item.floor.length))
+					item.floor = []
+					item.floor.unshift({
+						id: "all",
+						localName: "全部"
+					}, {
+						id: "noKnow",
+						localName: "未明确楼层"
+					})
+        });
+				bL.unshift({
+					id: "all",
+					localName: "全部"
+				}, {
+					id: "noKnow",
+					localName: "未明确建筑"
+				})
+        this.buildingData = bL;
+      }
+      else {
+        this.buildingData = [];
+      }
+    }
+  },
+  props: {
+    space: '',//空间id
+    buildingList: {//建筑楼层
+      Type: Array,
+      default: []
+    },
+    spaceType: Object,
+    floorType: Object
+  },
+  watch: {
+    space: {
+      handler() {
+        this.getSpaceList();
+      },
+      immediate: true
+    }
+  }
+}
+</script>
+
+<style lang="less" scoped>
+.query {
+  padding: 10px 0px;
+}
+#relatedSpaceList {
+  /deep/ .el-scrollbar__wrap {
+    overflow-x: hidden;
+  }
+  /deep/ .el-table__body-wrapper{
+    overflow-x:hidden;
+    overflow-y:auto;
+    height:calc(100% - 47px);
+    width:100%;
+  }
+}
+</style>

+ 172 - 0
src/components/ledger/components/cenote/table/cenoteTable.vue

@@ -0,0 +1,172 @@
+<template>
+  <div id="eqInSp">
+    <el-row>
+      <el-button type="primary" @click="add">添加{{ inSpaceType }}</el-button>
+      <el-tooltip class="item" effect="dark" content="可前往“全部关系总览”中计算连通的其它竖井" placement="right">
+        <el-button>按空间计算</el-button>
+      </el-tooltip>
+    </el-row>
+    <div class="table-box">
+      <el-table :data="tableData" style="width: 100%" height="100%" v-loading="loading"
+                :header-cell-style="headerStyle">
+        <el-table-column :label="`${inSpaceType}名称`" show-overflow-tooltip min-width="100">
+          <template slot-scope="scope">
+            <div>
+              {{ scope.row.localName || scope.row.name || '' }}
+            </div>
+          </template>
+        </el-table-column>
+        <el-table-column prop="localId" :label="`${inSpaceType}本地编码`" show-overflow-tooltip
+                         min-width="100"></el-table-column>
+        <el-table-column :label="`${inSpaceType}类型`" show-overflow-tooltip min-width="100">
+          <template slot-scope="scope">
+            <div>
+              {{ scope.row.infos.shaftFuncType }}
+            </div>
+          </template>
+        </el-table-column>
+        <el-table-column prop="action" label="操作" min-width="100">
+          <template slot-scope="scope">
+            <el-tooltip class="item" effect="dark" content="删除关系" placement="left">
+              <el-button size="mini" @click="handleDelete(scope.$index, scope.row)" type="danger" plain
+                         icon="el-icon-delete"></el-button>
+            </el-tooltip>
+          </template>
+        </el-table-column>
+        <template slot="empty">
+          <div style="height: 60%;transform: translateY(50%);">
+            <i class="icon-wushuju iconfont"></i>
+            可前往“全部关系总览”中计算连通的其它竖井
+          </div>
+        </template>
+      </el-table>
+    </div>
+    <!-- 添加设备弹窗 -->
+    <addCenoteDialog ref="addCenoteDialog" @refresh="getTableData" :type="type" :params="params"></addCenoteDialog>
+  </div>
+</template>
+
+<script>
+import { queryCenoteTableData, queryShaftType, shthroughshUnlink } from "@/api/scan/request";
+import { mapGetters } from "vuex";
+import { getDataDictionary } from "@/api/dict"
+import addCenoteDialog from "@/components/ledger/cenote/dialog/addCenoteDialog"
+
+export default {
+  components: {
+    addCenoteDialog
+  },
+  computed: {
+    ...mapGetters("layout", ["projectId"])
+  },
+  data() {
+    return {
+      inSpaceType: '竖井',
+      headerStyle: {
+        backgroundColor: '#e1e4e5',
+        color: '#2b2b2b',
+        lineHeight: '30px'
+      }, // 列表样式
+      loading: false, // loading
+      tableData: [], //列表数据
+      shaftType: {} //竖井类型
+    };
+  },
+  props: {
+    params: Object,
+    type: String
+  },
+  created() {
+    // 查询 竖井所有类型
+    // queryShaftType(res => {
+    let param = {
+      "pageNumber": 1,
+      "pageSize": 50,
+      "filters": "code = 'shaftFuncType'",
+      "type": "shaft",
+      "orders": "sort asc, name desc",
+    }
+    getDataDictionary(param, res => {
+      res.content.forEach(item => {
+        item.dataSource && item.dataSource.forEach(i => {
+          this.shaftType[i.code] = i.name
+        })
+      })
+      this.getTableData()
+    })
+  },
+  methods: {
+    // 获取列表数据
+    getTableData() {
+      let params = {
+        filters: `id='${this.params.ShaftID}'`,
+        cascade: [
+          {
+            name: 'shaftThroughList'
+          }
+        ]
+      }
+      queryCenoteTableData(params, res => {
+        this.tableData = res.content[0].shaftThroughList || []
+        this.tableData.forEach(item => {
+          if (item.infos && item.infos.shaftFuncType) {
+            item.infos.shaftFuncType = this.shaftType[item.infos.shaftFuncType]
+          }
+        })
+      })
+    },
+    // 删除关系
+    handleDelete(index, row) {
+      this.$confirm("确认删除该关系?", "提示", {
+        confirmButtonText: '确定',
+        cancelButtonText: '取消',
+        type: 'warning'
+      }).then(() => {
+        let params = {
+          shaftOtherId: row.id,
+          shaftId: this.params.ShaftID
+        }
+        this.deleteShThroughSh(params);
+      }).catch(() => {
+        this.$message("取消删除")
+      })
+    },
+    // 删除设备所在空间关系
+    deleteShThroughSh(params) {
+      shthroughshUnlink(params, res => {
+        this.$message.success('删除成功')
+        this.getTableData()
+      })
+    },
+    // 改变pagesize
+    handleSizeChange(pageSize) {
+      this.page.pageSize = pageSize;
+      this.getTableData();
+    },
+    // 改变pageno
+    handleCurrentChange(pageNo) {
+      this.page.pageNumber = pageNo;
+      this.getTableData();
+    },
+    // 添加设备
+    add() {
+      this.$refs.addCenoteDialog.showDialog()
+    }
+  },
+  watch: {
+    type() {
+      this.getTableData()
+    }
+  }
+};
+</script>
+<style lang="less" scoped>
+#eqInSp {
+  height: 100%;
+
+  .table-box {
+    margin-top: 10px;
+    height: calc(100% - 50px);
+  }
+}
+</style>

+ 148 - 0
src/components/ledger/components/cenote/table/deviceTable.vue

@@ -0,0 +1,148 @@
+<template>
+  <div id="eqInSp">
+    <el-row>
+      <el-button type="primary" @click="add">添加{{ inSpaceType }}</el-button>
+      <el-tooltip class="item" effect="dark" content="可前往“全部关系总览”中计算竖井内设备" placement="right">
+        <el-button>按空间计算</el-button>
+      </el-tooltip>
+    </el-row>
+    <div class="table-box">
+      <el-table :data="tableData" style="width: 100%" height="100%" v-loading="loading"
+                :header-cell-style="headerStyle">
+        <el-table-column :label="`${inSpaceType}名称`" show-overflow-tooltip min-width="100">
+          <template slot-scope="scope">
+            <div>
+              {{ scope.row.localName || scope.row.name || '' }}
+            </div>
+          </template>
+        </el-table-column>
+        <el-table-column prop="localId" :label="`${inSpaceType}本地编码`" show-overflow-tooltip
+                         min-width="100"></el-table-column>
+        <el-table-column :label="`${inSpaceType}类型`" show-overflow-tooltip min-width="100">
+          <template slot-scope="scope">
+            <div>
+              {{ scope.row.equipCategory.name }}
+            </div>
+          </template>
+        </el-table-column>
+        <el-table-column prop="action" label="操作" min-width="100">
+          <template slot-scope="scope">
+            <el-button title="删除关系" size="mini" @click="handleDelete(scope.$index, scope.row)" type="danger" plain
+                       icon="el-icon-delete"></el-button>
+          </template>
+        </el-table-column>
+        <template slot="empty">
+          <div style="height: 60%;transform: translateY(50%);">
+            <i class="icon-wushuju iconfont"></i>
+            可前往“全部关系总览”中计算竖井内设备
+          </div>
+        </template>
+      </el-table>
+    </div>
+    <!-- 添加设备弹窗 -->
+    <addEquipDialog ref="addEqDialog" @refresh="getTableData" :type="type" :params="params"></addEquipDialog>
+  </div>
+</template>
+
+<script>
+import { queryCenoteTableData, eqinshUnlink } from "@/api/scan/request";
+import { mapGetters } from "vuex";
+import addEquipDialog from "@/components/ledger/cenote/dialog/addEquipDialog"
+
+export default {
+  components: {
+    addEquipDialog
+  },
+  computed: {
+    ...mapGetters("layout", ["projectId"])
+  },
+  data() {
+    return {
+      inSpaceType: '设备',
+      headerStyle: {
+        backgroundColor: '#e1e4e5',
+        color: '#2b2b2b',
+        lineHeight: '30px'
+      }, // 列表样式
+      loading: false, // loading
+      tableData: [] //列表数据
+    };
+  },
+  props: {
+    params: Object,
+    type: String
+  },
+  created() {
+    this.getTableData()
+  },
+  methods: {
+    // 获取列表数据
+    getTableData() {
+      let params = {
+        filters: `id='${this.params.ShaftID}'`,
+        cascade: [
+          {
+            name: 'equipmentList',
+            cascade: [{ name: 'equipCategory' }]
+          }
+        ]
+      }
+      queryCenoteTableData(params, res => {
+        this.tableData = res.content[0].equipmentList || []
+      })
+    },
+    // 删除关系
+    handleDelete(index, row) {
+      this.$confirm("确认删除该关系?", "提示", {
+        confirmButtonText: '确定',
+        cancelButtonText: '取消',
+        type: 'warning'
+      }).then(() => {
+        let params = {
+          equipId: row.id,
+          shaftId: this.params.ShaftID
+        }
+        this.deleteEqInSh(params);
+      }).catch(() => {
+        this.$message("取消删除")
+      })
+    },
+    // 删除设备所在竖井关系
+    deleteEqInSh(params) {
+      eqinshUnlink(params, res => {
+        this.$message.success('删除成功')
+        this.getTableData()
+      })
+    },
+    // 改变pagesize
+    handleSizeChange(pageSize) {
+      this.page.pageSize = pageSize;
+      this.getTableData();
+    },
+    // 改变pageno
+    handleCurrentChange(pageNo) {
+      this.page.pageNumber = pageNo;
+      this.getTableData();
+    },
+    // 添加设备
+    add() {
+      this.$refs.addEqDialog.showDialog()
+    }
+  },
+  watch: {
+    type() {
+      this.getTableData()
+    }
+  }
+};
+</script>
+<style lang="less" scoped>
+#eqInSp {
+  height: 100%;
+
+  .table-box {
+    margin-top: 10px;
+    height: calc(100% - 50px);
+  }
+}
+</style>

+ 145 - 0
src/components/ledger/components/cenote/table/systemTable.vue

@@ -0,0 +1,145 @@
+<template>
+  <div id="eqInSp">
+    <el-row>
+      <el-button type="primary" @click="add">添加{{inSpaceType}}</el-button>
+      <el-tooltip class="item" effect="dark" content="可前往“全部关系总览”中计算竖井内系统" placement="right">
+        <el-button>按空间计算</el-button>
+      </el-tooltip>
+    </el-row>
+    <div class="table-box">
+      <el-table :data="tableData" style="width: 100%" height="100%" v-loading="loading" :header-cell-style="headerStyle">
+        <el-table-column :label="`${inSpaceType}名称`" show-overflow-tooltip min-width="100">
+          <template slot-scope="scope">
+            <div>
+              {{scope.row.localName||scope.row.name||''}}
+            </div>
+          </template>
+        </el-table-column>
+        <el-table-column prop="localId" :label="`${inSpaceType}本地编码`" show-overflow-tooltip min-width="100"></el-table-column>
+        <el-table-column :label="`${inSpaceType}类型`" show-overflow-tooltip min-width="100">
+          <template slot-scope="scope">
+            <div>
+              {{scope.row.categoryNames.categoryName}}
+            </div>
+          </template>
+        </el-table-column>
+        <el-table-column prop="action" label="操作" min-width="100">
+          <template slot-scope="scope">
+            <el-tooltip class="item" effect="dark" content="删除关系" placement="left">
+              <el-button size="mini" @click="handleDelete(scope.$index, scope.row)" type="danger" plain icon="el-icon-delete"></el-button>
+            </el-tooltip>
+          </template>
+        </el-table-column>
+        <template slot="empty">
+          <div style="height: 60%;transform: translateY(50%);">
+            <i class="icon-wushuju iconfont"></i>
+            可前往“全部关系总览”中计算竖井内系统
+          </div>
+        </template>
+      </el-table>
+    </div>
+    <!-- 添加设备弹窗 -->
+    <addSystemDialog ref="addEqDialog" @refresh="getTableData" :type="type" :params="params"></addSystemDialog>
+  </div>
+</template>
+
+<script>
+import { queryCenoteTableData, syinshUnlink } from "@/api/scan/request";
+import { mapGetters } from "vuex";
+import addSystemDialog from "@/components/ledger/cenote/dialog/addSystemDialog"
+export default {
+  components: {
+    addSystemDialog
+  },
+  computed: {
+    ...mapGetters("layout", ["projectId"])
+  },
+  data() {
+    return {
+      inSpaceType: '系统',
+      headerStyle: {
+        backgroundColor: '#e1e4e5',
+        color: '#2b2b2b',
+        lineHeight: '30px'
+      }, // 列表样式
+      loading: false, // loading
+      tableData: [] //列表数据
+    };
+  },
+  props: {
+    params: Object,
+    type: String
+  },
+  created() {
+    this.getTableData()
+  },
+  methods: {
+    // 获取列表数据
+    getTableData() {
+      let params = {
+        filters: `id='${this.params.ShaftID}'`,
+        cascade: [
+          {
+            name: 'systemList',
+            cascade: [{ name: 'categoryNames' }]
+          }
+        ]
+      }
+      queryCenoteTableData(params, res => {
+        this.tableData = res.content[0].systemList || []
+      })
+    },
+    // 删除关系
+    handleDelete(index, row) {
+      this.$confirm("确认删除该关系?", "提示", {
+        confirmButtonText: '确定',
+        cancelButtonText: '取消',
+        type: 'warning'
+      }).then(() => {
+        let params = {
+          sysId: row.id,
+          shaftId: this.params.ShaftID
+        }
+        this.deleteSyInSh(params);
+      }).catch(() => {
+        this.$message("取消删除")
+      })
+    },
+    // 删除系统所在竖井关系
+    deleteSyInSh(params) {
+      syinshUnlink(params, res => {
+        this.$message.success('删除成功')
+        this.getTableData()
+      })
+    },
+    // 改变pagesize
+    handleSizeChange(pageSize) {
+      this.page.pageSize = pageSize;
+      this.getTableData();
+    },
+    // 改变pageno
+    handleCurrentChange(pageNo) {
+      this.page.pageNumber = pageNo;
+      this.getTableData();
+    },
+    // 添加设备
+    add() {
+      this.$refs.addEqDialog.showDialog()
+    }
+  },
+  watch: {
+    type() {
+      this.getTableData()
+    }
+  }
+};
+</script>
+<style lang="less" scoped>
+#eqInSp {
+  height: 100%;
+  .table-box {
+    margin-top: 10px;
+    height: calc(100% - 50px);
+  }
+}
+</style>

+ 126 - 0
src/components/ledger/components/details/detail/cruxDialog.vue

@@ -0,0 +1,126 @@
+<template>
+  <el-dialog title="关键信息点" :visible.sync="dialogFormVisible" class="crux-dialog">
+    <section v-for="(val,key,i) in checkList" :key="i">
+      <h4 class="base">{{ key == 'undefined' ? '基本信息' : key }}</h4>
+      <el-checkbox
+        v-for="(option,index) in val"
+        :key="option+index"
+        :label="option.InfoPointName"
+        v-model="option.KeyWord"
+      />
+    </section>
+    <div slot="footer" class="dialog-footer">
+      <el-button @click="dialogFormVisible = false">取 消</el-button>
+      <el-button type="primary" @click="save">保 存</el-button>
+    </div>
+  </el-dialog>
+</template>
+
+<script>
+import { setDataDictionary } from "@/api/scan/request";
+
+export default {
+  name: "cruxDialog",
+  props: ['crux', 'type'],
+  data() {
+    return {
+      dialogFormVisible: false,
+      checkList: {},
+      checkboxGroup: []
+    }
+  },
+  computed: {
+    allChecks() {
+      const tmp = [];
+      for (const key in this.checkList) {
+        if (this.checkList.hasOwnProperty(key)) {
+          const element = this.checkList[key];
+          tmp.push(...element);
+        }
+      }
+      return tmp;
+    }
+  },
+
+  watch: {
+    crux() {
+      this.changeList()
+    }
+  },
+  methods: {
+    changeList() {
+      const tmp = {};
+      for (let key in this.crux) {
+        if (this.crux.hasOwnProperty(key)) {
+          const element = this.crux[key];
+          tmp[key] = [
+            ...element.paths.map(i => ({
+              InfoPointCode: i.InfoPointCode,
+              KeyWord: i.KeyWord || false,
+              type: i.type,
+              code: i.InfoPointCode,
+              classCode: i.classCode,
+              id: i.id,
+              groupCode: i.groupCode,
+              InfoPointName: i.InfoPointName
+            }))
+          ]
+        }
+      }
+      this.checkList = tmp;
+    },
+    dialogOpen() {
+      this.dialogFormVisible = true
+    },
+    save() {
+      let arrLenght = this.allChecks.filter(i => i.KeyWord)
+      if (arrLenght.length > 6) {
+        this.$message.info('不可超过6个')
+        return false
+      } else {
+        let param = {
+          content: []
+        }
+        param.content = this.allChecks.map(i => {
+          if (i.InfoPointName) {
+            delete i.InfoPointName
+          }
+          // return { ...i }
+          return {
+            keyWord: i.KeyWord,
+            // type: this.type,
+            type: i.type,
+            code: i.InfoPointCode,
+            classCode: i.classCode,
+            id: i.id,
+            groupCode: i.groupCode
+          }
+        })
+        setDataDictionary(param, res => {
+          this.dialogFormVisible = false
+          this.$emit('cruxSuccess')
+          this.$message.success('保存成功')
+
+        });
+
+      }
+
+
+    }
+  }
+}
+</script>
+
+<style scoped lang="less">
+.crux-dialog {
+  .el-checkbox {
+    display: inline-block;
+    width: 20%;
+  }
+
+  /deep/ .el-dialog__body {
+    height: 420px;
+    overflow: auto;
+  }
+}
+</style>

+ 143 - 0
src/components/ledger/components/details/detail/exhibitionBaseInformation.vue

@@ -0,0 +1,143 @@
+<template>
+    <div class="exhibition-baseInformation">
+        <section v-for="(val, key, i) in exhibitionBaseInformation.information">
+            <h4 class="base">{{ key == "undefined" ? "基本信息" : key }}</h4>
+            <div class="infoPoint-item" v-for="(value, keys, index) in val.paths">
+                <span> {{ value.InfoPointName }}:</span>
+                <el-popover
+                    v-if="value.Path == 'EquipQRCode' || value.Path == 'RoomQRCode' || value.Path == 'ShaftQRCode'"
+                    placement="right"
+                    width="360"
+                    trigger="hover"
+                >
+                    <p style="border-bottom: 1px solid #eee;padding:2px 0 4px 0;'">{{ value.InfoPointName }}</p>
+                    <div style="overflow: hidden">
+                        <div style="float: left; width: 60%" v-if="value.Value">
+                            <img
+                                style="width: 200px; height: 200px"
+                                :src="'/image-service/common/file_get/' + value.Value + '?systemId=dataPlatform'"
+                                alt="二维码"
+                            />
+                        </div>
+                        <div v-else style="width: 100%; text-align: center; padding-top: 10px">暂无数据</div>
+                        <div style="float: right; width: 40%; margin-top: 30px">
+                            <span>{{ value.Value }}</span>
+                        </div>
+                    </div>
+
+                    <el-button slot="reference" type="text">有</el-button>
+                </el-popover>
+                <span v-else-if="Array.isArray(value.Value) && value.dataType == 'ATTACHMENT'">
+                    <el-popover v-if="value.Value.length" placement="right" width="250" trigger="hover">
+                        <p style="border-bottom: 1px solid #eee;padding:2px 0 4px 0;'">{{ value.InfoPointName }}</p>
+                        <div v-for="(pic, index) in value.Value">
+                            <div>
+                                图纸名称:{{ pic.name }}
+                                <a
+                                    style="cursor: pointer; color: #409eff"
+                                    :href="`/image-service/common/image_get?systemId=dataPlatform&key=${pic.key}`"
+                                    :download="pic.name"
+                                    >下载</a
+                                >
+                            </div>
+                        </div>
+                        <el-button slot="reference" type="text">有</el-button>
+                    </el-popover>
+                    <span v-else>-</span>
+                </span>
+
+                <el-popover
+                    v-else-if="value.Value && value.Value.length && `${value.InfoPointName}:${value.Value}`.length > 21"
+                    placement="right"
+                    width="160"
+                    trigger="hover"
+                >
+                    <p style="border-bottom: 1px solid #eee;padding:2px 0 4px 0;'">{{ value.InfoPointName }}</p>
+                    <p>{{ value.Value }}</p>
+                    <div style="text-align: center; margin: 0">
+                        <el-button type="text" size="mini" @click="handleCopy(value.Value)">复制</el-button>
+                    </div>
+                    <el-button slot="reference" type="text">{{ handleName(value.InfoPointName, value.Value) }}</el-button>
+                </el-popover>
+                <span v-else-if="Array.isArray(value.Value) && value.dataType == 'MENUM'">{{ value | filterMenumValue }}</span>
+                <span v-else>{{ value.Value }}</span>
+            </div>
+        </section>
+    </div>
+</template>
+
+<script>
+export default {
+    name: "exhibition-baseInformation",
+    props: ["exhibitionBaseInformation"],
+    data() {
+        return {};
+    },
+    filters: {
+        filterMenumValue(value) {
+            return value.Value.map((val) => {
+                return value.dataSource.find((item) => item.code === val).name;
+            }).join(",");
+        },
+    },
+    created() {},
+    methods: {
+        downloadFile(key) {
+            window.open("/image-service/common/file_get/" + key + "?systemId=dataPlatform");
+        },
+        handleCopy(data) {
+            let oInput = document.createElement("input");
+            if (typeof data != "string") {
+                data = JSON.stringify(data);
+            }
+            oInput.value = data;
+            document.body.appendChild(oInput);
+            oInput.select();
+            document.execCommand("Copy");
+            this.$message.success("复制成功");
+            oInput.remove();
+        },
+        handleName(infoPoint, val) {
+            let value = val;
+            if (Array.isArray(value)) {
+                value = JSON.stringify(val);
+            }
+            let text = `${infoPoint}:${value}`;
+            return text.length > 21 ? value.substring(0, 21 - `${infoPoint}:`.length) + "..." : value;
+        },
+    },
+};
+</script>
+\ 
+<style scoped lang="less">
+.exhibition-baseInformation {
+    section {
+        /*border: 1px solid #eeeeee;*/
+        /*margin: 10px;*/
+
+        .base {
+            margin: 10px;
+            font-weight: 600;
+            /*text-indent: 10px;*/
+            border-bottom: 1px dashed #eee;
+        }
+
+        /deep/ .el-button--text {
+            color: #555 !important;
+            font-size: 14px;
+        }
+    }
+
+    .ml-10 {
+        margin-left: 10px;
+        text-indent: 10px;
+    }
+
+    .infoPoint-item {
+        width: 315px;
+        display: inline-block;
+        margin-left: 20px;
+        vertical-align: text-top;
+    }
+}
+</style>

+ 147 - 0
src/components/ledger/components/details/detail/exhibitionCrux.vue

@@ -0,0 +1,147 @@
+<template>
+  <section class="exhibition-crux">
+    <p class="title">
+      <span class="iconfont icon-juxing"></span>
+      {{ exhibitionCrux.title }}
+      <span class="icon-shezhi iconfont setting" @click="setting"></span></p>
+    <div>
+      <div v-if="exhibitionCrux.cruxArray.length">
+        <div class="crux-list" v-for="(item,index) in exhibitionCrux.cruxArray" :key="index">{{ item.name }}:
+
+          <el-popover
+            v-if="item.Path == 'equipQRCode' || item.Path == 'roomQRCode' || item.Path == 'shaftQRCode'"
+            placement="top"
+            width="360"
+            trigger="hover">
+            <p style="border-bottom: 1px solid #eee;padding:2px 0 4px 0;'">{{ item.InfoPointName }}</p>
+            <div style="overflow:hidden;">
+              <div style="float:left;width: 60%">
+                <img
+                  style="width:200px;height:200px;"
+                  :src="'/image-service/common/file_get/'+ item.value +'?systemId=dataPlatform'"
+                  alt="二维码"
+                >
+              </div>
+              <div style="float:right;width: 40%;margin-top: 30px">
+                <span>{{ item.value }}</span>
+              </div>
+            </div>
+            <div style="text-align: center; margin: 0">
+              <el-button type="text" size="mini" @click="handleCopy(item.value)">复制</el-button>
+            </div>
+            <el-button slot="reference" type="text">{{ item.value ? '有' : '-' }}</el-button>
+          </el-popover>
+          <el-popover
+            v-else-if="Array.isArray(item.value) && item.dataType== 'ATTACHMENT'"
+            placement="right"
+            width="250"
+            trigger="hover">
+            <p style="border-bottom: 1px solid #eee;padding:2px 0 4px 0;'">{{ item.InfoPointName }}</p>
+            <div v-for="(pic,index) in item.value">
+              <div>图纸名称:{{ pic.name }}
+                <el-button size="mini" style="" type="text" @click="downloadFile(pic.key)">下载</el-button>
+              </div>
+            </div>
+            <el-button slot="reference" type="text">{{ item.value ? '有' : '-' }}</el-button>
+          </el-popover>
+
+          <el-popover
+            v-else-if="item.value && item.value.length && `${item.InfoPointName}:${item.value}`.length>21"
+            placement="top"
+            width="160"
+            trigger="hover">
+            <p style="border-bottom: 1px solid #eee;padding:2px 0 4px 0;'">{{ item.InfoPointName }}</p>
+            <p>{{ item.value }}</p>
+            <div style="text-align: center; margin: 0">
+              <el-button type="text" size="mini" @click="handleCopy(item.value)">复制</el-button>
+            </div>
+            <el-button slot="reference" type="text">{{ handleName(item.InfoPointName, item.value) }}</el-button>
+          </el-popover>
+          <span v-else>{{ item.value }}</span>
+        </div>
+      </div>
+    </div>
+    <cruxDialog
+      ref="setting"
+      :crux="exhibitionCrux.allMessage"
+      :type="type"
+      @cruxSuccess="cruxSuccess"
+
+    />
+  </section>
+</template>
+
+<script>
+import cruxDialog from "./cruxDialog";
+
+export default {
+  name: "exhibition-crux",
+  props: ['exhibitionCrux', 'type'],
+  components: { cruxDialog },
+  data() {
+    return {
+      list: []
+    }
+  },
+  methods: {
+    downloadFile(key) {
+      window.open("/image-service/common/file_get/" + key + "?systemId=dataPlatform")
+    },
+    setting() {
+      this.$refs.setting.dialogOpen()
+    },
+    cruxSuccess() {
+      this.$emit('cruxSuccess')
+    },
+    handleCopy(data) {
+      let oInput = document.createElement('input')
+      oInput.value = data
+      document.body.appendChild(oInput)
+      oInput.select()
+      document.execCommand('Copy')
+      this.$message.success('复制成功')
+      oInput.remove()
+    },
+    handleName(infoPoint, val) {
+      let value = val;
+      if (Array.isArray(value)) {
+        value = JSON.stringify(val)
+      }
+      let text = `${infoPoint}:${value}`;
+      return text.length > 21 ? value.substring(0, 21 - `${infoPoint}:`.length) + '...' : value;
+    }
+  }
+}
+</script>
+
+<style lang="less" scoped>
+.exhibition-crux {
+  padding: 10px 0;
+
+  /deep/ .el-button--text {
+    color: #555 !important;
+    font-size: 14px;
+  }
+
+  .title {
+    /*border-left: 8px solid black;*/
+    margin: 0 20px 0 10px;
+    font-weight: 600;
+    /*text-indent: 10px;*/
+    display: block;
+    overflow: hidden;
+
+    .setting {
+      float: right;
+    }
+
+  }
+
+  .crux-list {
+    display: inline-block;
+    width: 315px;
+    margin-left: 20px;
+    vertical-align: text-top;
+  }
+}
+</style>

+ 98 - 0
src/components/ledger/components/details/detail/exhibitionEnergy.vue

@@ -0,0 +1,98 @@
+<template>
+  <div class="exhibition-energy">
+    <div class="title">
+      <section v-for="(val,key,i) in exhibitionEnergy.list">
+        <h4 class="base">{{key}}</h4>
+        <div class="table-dynamic" v-for="(value,keys,index) in val.paths">
+          <div class="table-title"> {{ value.InfoPointName }}:</div>
+          <p class="iot_data" :style="{'color':value.value? '' :'#F56C6C'}" :title="value.value ? `${value.value}` : '未维护'">{{ value.value ? `${value.value}` : '未维护' }}</p>
+          <p v-if="value.data == '0'">{{ value.data }}{{ value.Unit }}</p>
+          <p v-else-if="value.data">{{ value.data }}{{ value.Unit }}</p>
+          <!--          <p v-else-if="value.error">{{value.error}}</p>-->
+          <el-popover
+            v-else-if="value.error"
+            placement="right"
+            width="160"
+            trigger="hover">
+            <p style="border-bottom: 1px solid #eee;padding:2px 0 4px 0;'">错误原因</p>
+            <p>{{ '表号功能号不存在' }}</p>
+            <div style="text-align: center; margin: 0">
+              <el-button type="text" size="mini" @click="handleCopy(value.error)">复制错误信息</el-button>
+            </div>
+            <el-button slot="reference" type="text" style="color:#F56C6C">error</el-button>
+          </el-popover>
+          <p v-else>--</p>
+
+          <p>{{value.receivetime ? formatDate(value.receivetime):'--'}}</p>
+        </div>
+      </section>
+    </div>
+  </div>
+</template>
+
+<script>
+  export default {
+    name: "exhibitionEnergy",
+    props: ['exhibitionEnergy'],
+    methods:{
+      formatDate(str) {
+        if (str) {
+          if (str.includes('-')) {
+            return str
+          } else {
+            if (str.length > 8) {
+              return str.substr(0, 4) + "-" + str.substr(4, 2) + "-" + str.substr(6, 2) + " " + str.substr(8, 2) + ":" + str.substr(10, 2) + ":" + str.substr(12, 2)
+            } else if (str === 'null') {
+                return '--'
+            }else {
+                return str.substr(0, 4) + "-" + str.substr(4, 2) + "-" + str.substr(6, 2) + " " + str.substr(8, 2)
+            }
+          }
+        } else {
+          return '--'
+        }
+      },
+      handleCopy(data) {
+        let oInput = document.createElement('input')
+        oInput.value = data
+        document.body.appendChild(oInput)
+        oInput.select()
+        document.execCommand('Copy')
+        this.$message.success('复制成功')
+        oInput.remove()
+      },
+    }
+  }
+</script>
+
+<style scoped lang="less">
+  .exhibition-energy {
+    .base {
+      margin: 10px;
+      font-weight: 600;
+      text-indent: 10px;
+      border-bottom: 1px dashed #eee;
+    }
+
+    .table-dynamic {
+      /*height: 85px;*/
+      width: 100%;
+      margin: 5px 0;
+      padding-left: 20px;
+      box-sizing: border-box;
+    }
+
+    .iot_data {
+        white-space: nowrap;
+        overflow: hidden;
+        text-overflow: ellipsis;
+    }
+
+    .table-title {
+      float: left;
+      max-width: 200px;
+      width: 100px;
+      height: 85px;
+    }
+  }
+</style>

+ 70 - 0
src/components/ledger/components/details/detail/exhibitionEnergyO.vue

@@ -0,0 +1,70 @@
+<template>
+  <div class="exhibition-energy">
+    <div class="title">
+      <section>
+        <h4 class="base" v-show="exhibitionEnergy.firstName">{{exhibitionEnergy.firstName}}</h4>
+        <div v-if="exhibitionEnergy.dynamic.length">
+          <div v-for="(item,index) in exhibitionEnergy.dynamic" :key="index" class="table-dynamic">
+            <div class="table-title">{{item.InfoPointName}}:</div>
+            <div>
+              <p>表号功能号 {{item.value}}</p>
+              <p>{{item.data ? item.data :item.error}} {{item.Unit}}</p>
+              <p>{{formatDate(item.receivetime)}}</p>
+            </div>
+          </div>
+        </div>
+         <div v-else style="text-align: center;margin-top: 200px">暂无数据</div>
+      </section>
+    </div>
+  </div>
+</template>
+
+<script>
+  export default {
+    name: "exhibitionEnergy",
+    props: ['exhibitionEnergy'],
+    methods:{
+      formatDate(str) {
+        if (str) {
+          if (str.includes('-')) {
+            return str
+          } else {
+            if (str.length > 8) {
+              return str.substr(0, 4) + "-" + str.substr(4, 2) + "-" + str.substr(6, 2) + " " + str.substr(8, 2) + ":" + str.substr(10, 2) + ":" + str.substr(12, 2)
+            } else {
+              return str.substr(0, 4) + "-" + str.substr(4, 2) + "-" + str.substr(6, 2) + " " + str.substr(8, 2)
+            }
+          }
+        } else {
+          return '--'
+        }
+      },
+    }
+  }
+</script>
+
+<style scoped lang="less">
+  .exhibition-energy {
+    .base {
+      margin: 10px;
+      font-weight: 600;
+      text-indent: 10px;
+      border-bottom: 1px dashed #eee;
+    }
+
+    .table-dynamic {
+      height: 85px;
+      width: 100%;
+      margin: 5px 0;
+      padding-left: 20px;
+      box-sizing: border-box;
+    }
+
+    .table-title {
+      float: left;
+      max-width: 200px;
+      width: 100px;
+      height: 85px;
+    }
+  }
+</style>

+ 36 - 0
src/components/ledger/components/details/detail/exhibitionFile.vue

@@ -0,0 +1,36 @@
+<template>
+  <div class="exhibition-file">
+    <p class="title">
+      <span class=" iconfont icon-fujian"></span>
+      {{exhibitionFile.title}}</p>
+    <div>
+      <lookFile :exhibitionFile="exhibitionFile"/>
+    </div>
+
+  </div>
+</template>
+
+<script>
+  import lookFile from "./lookFile";
+    export default {
+      name: "exhibition-file",
+      props: ['exhibitionFile'],
+      data() {return{}},
+      components:{lookFile}
+    }
+</script>
+
+<style scoped lang="less">
+  .exhibition-file {
+    border: 1px solid #eeeeee;
+    background: #FFFFFF;
+    margin-bottom: 10px;
+
+    .title {
+      border-bottom: 1px solid #eee;
+      padding: 10px 0;
+      font-weight: 600;
+      text-indent: 10px;
+    }
+  }
+</style>

+ 52 - 0
src/components/ledger/components/details/detail/exhibitionImage.vue

@@ -0,0 +1,52 @@
+<template>
+  <div class="exhibition-image">
+    <p class="title">
+      <span class=" icon-floorplan iconfont"></span>
+      <span>{{exhibitionImage.title}}</span>
+    </p>
+    <div class="image">
+      <lookImage :exhibitionImage="exhibitionImage"/>
+    </div>
+
+  </div>
+</template>
+
+<script>
+  import lookImage from "./lookImage";
+  export default {
+    name: "exhibition-image",
+    props: ['exhibitionImage'],
+    components:{lookImage},
+    data() {
+      return {
+
+      }
+    },
+    methods:{
+      deepcopy(obj) {
+        return JSON.parse(JSON.stringify(obj));
+      },
+    }
+  }
+</script>
+
+<style lang="less" scoped>
+  .exhibition-image {
+    border: 1px solid #eeeeee;
+    background: #fff;
+    margin-bottom: 10px;
+    .title {
+      border-bottom: 1px solid #eee;
+      padding: 10px 0;
+      font-weight: 600;
+      text-indent: 10px;
+    }
+
+    .image {
+      margin: 10px;
+      /*text-align: center;*/
+    }
+
+  }
+
+</style>

+ 29 - 0
src/components/ledger/components/details/detail/exhibitionMaintain.vue

@@ -0,0 +1,29 @@
+<template>
+  <div class="exhibition-maintain">
+    <p class="title disable">
+      <span class=" iconfont icon-weixiubeijing "></span>
+      {{exhibitionMaintain.title}}
+      <span class="iconfont icon-right " style="float: right;margin-right: 20px"></span></p>
+  </div>
+</template>
+
+<script>
+  export default {
+    name: "exhibitionMaintain",
+    props: ['exhibitionMaintain']
+  }
+</script>
+
+<style scoped lang="less">
+  .exhibition-maintain {
+
+    .title {
+      margin-left: 10px;
+      font-weight: 600;
+      /*text-indent: 10px;*/
+    }
+    .disable{
+      color:#ccc;
+    }
+  }
+</style>

+ 51 - 0
src/components/ledger/components/details/detail/exhibitionVideo.vue

@@ -0,0 +1,51 @@
+<template>
+  <div class="exhibition-video">
+    <p class="title">
+      <span class=" icon-shipin-tianchong iconfont"></span>
+      {{exhibitionVideo.title}}</p>
+    <div class="image">
+      <lookVideo  :exhibitionVideo="exhibitionVideo"/>
+    </div>
+
+  </div>
+</template>
+
+<script>
+  import lookVideo from "./lookVideo";
+  export default {
+    name: "exhibition-video",
+    props: ['exhibitionVideo'],
+    components:{lookVideo},
+    data() {
+      return {
+
+      }
+    },
+    methods:{
+      deepcopy(obj) {
+        return JSON.parse(JSON.stringify(obj));
+      },
+    }
+  }
+</script>
+
+<style lang="less" scoped>
+  .exhibition-video {
+    border: 1px solid #eeeeee;
+    background: #fff;
+    margin-bottom: 10px;
+    .title {
+      border-bottom: 1px solid #eee;
+      padding: 10px 0;
+      font-weight: 600;
+      text-indent: 10px;
+    }
+
+    .image {
+      margin: 10px;
+      /*text-align: center;*/
+    }
+
+  }
+
+</style>

+ 215 - 0
src/components/ledger/components/details/detail/lookFile.vue

@@ -0,0 +1,215 @@
+<template>
+  <div class="exhibition-file">
+    <div v-if="Object.keys(exhibitionFile.list).length">
+      <div v-for="(val,key,i) in exhibitionFile.list" :key="i">
+        <div class="" v-for="(j,index) in val">
+          <p class="title">
+            <span class="iconfont icon-juxing"></span>
+            {{key}}
+          </p>
+          <div v-if="j.fileList !=undefined && j.fileList.length">
+            <div v-for="(item,index) in j.fileList" class="content" :key="index">
+              <!--          图标-->
+              <div class="file-box" >
+                <div class="file-box-left ">
+                  <div class="file-box-left-content ">
+                    <span class="iconfont icon-Excel excel fs20"></span>
+                  </div>
+                </div>
+                <div class="file-box-center">
+                  <p style="margin-top: 20px" :title="item.name"> {{delFile(item.name)}}</p>
+                  <p> {{item.createTime}}</p>
+                </div>
+                <div class="file-box-right ">
+                  <a
+                    style="height: 88px;cursor: pointer;color: #555"
+                    class="iconfont icon-download1 df fs20"
+                    :href="`/image-service/common/file_get/${item.key}?systemId=dataPlatform`"
+                    :download="item.name"
+                  />
+                </div>
+              </div>
+
+            </div>
+
+          </div>
+          <div v-else style="text-align: center;margin: 10px auto">暂无数据</div>
+        </div>
+      </div>
+    </div>
+    <div v-else style="text-align: center;margin: 10px auto">暂无数据</div>
+  </div>
+</template>
+
+<script>
+
+  export default {
+    props: ['exhibitionFile'],
+    data() {
+      return {};
+    },
+    created() {
+    },
+    methods: {
+      //处理地址
+      delFile(name) {
+        return name && name.length > 14 ? name.substring(0, 14) + "..." : name
+      },
+      //删除图片
+      deleteFile(i, key) {
+        this.filesArr.splice(i, 1);
+        this.$emit("change", this.filesArr, this.defined);
+      },
+
+      //上传
+      uploadAndSubmit(item) {
+        // var form = document.forms["demoForm"];
+
+        // if (form["file"].files.length > 0) {
+        // 寻找表单域中的 <input type="file" ... /> 标签
+        // var file = form["file"].files[0];
+
+        let file = item.file;
+        // try sending
+        let reader = new FileReader();
+
+        let vm = this;
+
+        let fileType = file.name.split(".");
+        let type = fileType[fileType.length - 1];
+        let key = "&key=" + fileType[0] + file.uid + "." + type
+        let CreateTime = tools.formatDate(new Date(file.lastModified))
+        reader.onloadstart = function () {
+          // 这个事件在读取开始时触发
+        };
+        reader.onprogress = function (p) {
+          // 这个事件在读取进行中定时触发
+        };
+
+        reader.onload = function () {
+          // 这个事件在读取成功结束后触发
+        };
+        reader.onloadend = function () {
+          // 这个事件在读取结束后,无论成功或者失败都会触发
+          if (reader.error) {
+          } else {
+            // document.getElementById("bytesRead").textContent = file.size;
+            // 构造 XMLHttpRequest 对象,发送文件 Binary 数据
+            var xhr = new XMLHttpRequest();
+            xhr.open(
+              /* method */
+              "POST",
+              /* target url */
+              "/image-service/common/file_upload?systemId=dataPlatform&secret=9e0891a7a8c8e885&overwrite=true" + key
+              /*, async, default to true */
+            );
+            //xhr.overrideMimeType("application/octet-stream");
+            xhr.send(reader.result);
+            xhr.onreadystatechange = function () {
+              if (xhr.readyState == 4) {
+                console.log(xhr)
+                if (xhr.status == 200) {
+                  let fileObject = {
+                    key: key.split("=")[1],
+                    type: type,
+                    name: file.name,
+                    createTime: CreateTime,
+                    systemId: 'dataPlatform'
+                  }
+                  vm.filesArr.push(fileObject);
+                  vm.$emit("change", vm.filesArr, vm.defined);
+                } else {
+                  this.$message.error(res.data.ResultMsg)
+                }
+              }
+            };
+          }
+        };
+        reader.readAsArrayBuffer(file);
+      }
+    },
+
+  };
+</script>
+
+<style lang="less" scoped>
+  .exhibition-file {
+    .icon-download1:hover {
+      color: #409eff;
+      cursor: pointer;
+    }
+
+    .title {
+      /*border-left: 8px solid black;*/
+      margin: 10px;
+      font-weight: 600;
+      /*text-indent: 10px;*/
+      display: block;
+      overflow: hidden;
+    }
+
+    .df {
+      display: flex;
+      justify-content: center;
+      align-items: center;
+    }
+
+    .file-box {
+      border: 1px solid #eee;
+      margin: 10px;
+      height: 88px;
+
+      .file-box-left {
+        display: inline-block;
+        width: 20%;
+        height: 100%;
+        overflow: hidden;
+        text-align: center;
+
+        .file-box-left-content {
+          background: #eeeeee;
+          width: 50px;
+          height: 50px;
+          margin: 20px 14px;
+          .excel{
+            display: block;
+            padding-top: 5px;
+          }
+        }
+      }
+
+      .file-box-center {
+        display: inline-block;
+        width: 58%;
+        height: 100%;
+        overflow: hidden;
+        text-align: center;
+      }
+
+      .file-box-right {
+        display: inline-block;
+        width: 19%;
+        height: 100%;
+        overflow: hidden;
+        a:hover {
+          color: #409eff !important;
+        }
+      }
+    }
+
+
+    .df {
+      display: flex;
+      justify-content: center;
+      align-items: center;
+    }
+
+    .fs20 {
+      font-size: 20px;
+    }
+
+    .list-name {
+      text-align: center;
+    }
+  }
+</style>

+ 306 - 0
src/components/ledger/components/details/detail/lookImage.vue

@@ -0,0 +1,306 @@
+<template>
+  <div id="sagaUploads-look-image">
+    <div class="saga-upload-images">
+      <div v-if="Object.keys(exhibitionImage.list).length">
+        <div v-for="(val,key,i) in exhibitionImage.list" :key="i">
+          <div class="" v-for="(j,index) in val">
+            <p class="title">
+              <span class="iconfont icon-juxing"></span>
+              {{ key }}
+            </p>
+            <div v-if="j.PicList !=undefined && j.PicList.length >0">
+              <template v-for="item in j.PicList">
+                <div class="point-look-image">
+                  <img @click="lookImg"
+                       :src=handleLookImage(item.key) alt
+                       v-load style="margin-bottom: 5px">
+                  <p class="pic-name">{{ handleName(item.name) }}</p>
+                </div>
+              </template>
+            </div>
+            <div v-else style="text-align: center;margin: 10px auto">暂无数据</div>
+          </div>
+        </div>
+      </div>
+      <div style="text-align: center" v-else>暂无数据</div>
+    </div>
+    <details-dialog :title="'图片'" :iframeSrc="iframeSrc" :dialog="dialog"
+                    :setData="exhibitionImage.arr"></details-dialog>
+  </div>
+</template>
+<script>
+import detailsDialog from "@/components/business_space/lib/detailsDia"
+
+export default {
+  components: {
+    detailsDialog
+  },
+  props: ['exhibitionImage'],
+  data() {
+    return {
+      baseUrl: "",
+      imageGetUrl: "/image-service/common/image_get?systemId=dataPlatform",
+      imageUploadUrl: "/image-service/common/image_upload?systemId=dataPlatform&secret=9e0891a7a8c8e885&overwrite=true",
+      imagesArr: [],
+      iframeSrc: "",
+      dialog: {
+        details: false
+      }
+    };
+  },
+  methods: {
+    handleLookImage(key) {
+      return `/image-service/common/image_get?systemId=dataPlatform&key=${key}`
+    },
+    handleName(val) {
+      return val && val.length > 10 ? val.substring(1, 10) + '...' : val
+    },
+    //查看图片
+    lookImg() {
+      this.dialog.details = true
+      this.iframeSrc =
+      //  process.env.BASE_URL +
+      window.__systemConf.BASE_URL +
+       ":8890/photo-View.html"
+      // this.iframeSrc = process.env.BASE_URL + "/photo-View.html"
+    },
+
+    //上传
+    uploadAndSubmit(item, key) {
+      let file = item.file;
+      // try sending
+      let reader = new FileReader();
+
+      let vm = this;
+
+      let fileType = file.name.split(".");
+      let type = fileType[fileType.length - 1];
+      let CreateTime = tools.formatDate(new Date(file.lastModified))
+
+      let uploadKey = file.uid
+      if (!!key) {
+        uploadKey = key
+      }
+
+      reader.onloadstart = function () {
+        // 这个事件在读取开始时触发
+      };
+      reader.onprogress = function (p) {
+        // 这个事件在读取进行中定时触发
+      };
+
+      reader.onload = function () {
+        // 这个事件在读取成功结束后触发
+      };
+      reader.onloadend = function () {
+        // 这个事件在读取结束后,无论成功或者失败都会触发
+        if (reader.error) {
+        } else {
+          // document.getElementById("bytesRead").textContent = file.size;
+          // 构造 XMLHttpRequest 对象,发送文件 Binary 数据
+          var xhr = new XMLHttpRequest();
+          xhr.open(
+            /* method */
+            "POST",
+            /* target url */
+            vm.imageUploadUrl + "&key=" + uploadKey + "." + type
+            /*, async, default to true */
+          );
+          //xhr.overrideMimeType("application/octet-stream");
+          xhr.send(reader.result);
+          xhr.onreadystatechange = function () {
+            if (xhr.readyState == 4) {
+              if (xhr.status == 200) {
+                if (vm.type == 'image') {
+                  vm.imagesArr.push({
+                    name: uploadKey + '',
+                    key: uploadKey + "." + type,
+                    systemId: "dataPlatform",
+                    type: "image",
+                    createTime: CreateTime
+                  });
+                }
+                if (type == 'mp4') {
+                  vm.videoPicArr.push({
+                    name: uploadKey + '',
+                    key: uploadKey + "." + type,
+                    systemId: "dataPlatform",
+                    type: "video",
+                    createTime: CreateTime
+                  });
+                  vm.creatImg(vm.imageGetUrl + "&key=" + uploadKey + "." + type, uploadKey)
+                }
+                // if (vm.type == "video" && type == "png") {
+                //   vm.videoPicArr.push({
+                //     Name: uploadKey + '',
+                //     Key: uploadKey + "." + type,
+                //     SystemId: "dataPlatform",
+                //     Type: "image_video",
+                //     CreateTime
+                //   })
+                // }
+                vm.$emit("change", vm.imagesArr, vm.defined, vm.videoPicArr);
+              }
+            }
+          };
+        }
+      };
+      reader.readAsArrayBuffer(file);
+    },
+    dataURLtoBlob(dataURI, type) {
+      var binary = atob(dataURI.split(',')[1]);
+      var array = [];
+      for (var i = 0; i < binary.length; i++) {
+        array.push(binary.charCodeAt(i));
+      }
+      return new Blob([new Uint8Array(array)], { type: type });
+    },
+
+    creatImg(reader, key) {
+      var videoDom = document.getElementById('video');
+      videoDom.src = reader;
+      let vm = this
+      videoDom.onloadeddata = function () {
+        // 这里可以打印视频时长
+        // 这里取得视频封面
+        var canvas = document.createElement('canvas');
+        canvas.width = 300;
+        canvas.height = 300 * this.videoHeight / this.videoWidth;
+        canvas.getContext('2d').drawImage(this, 0, 0, canvas.width, canvas.height);
+        //将canvas的base64位图片转换成图片png的file
+        var blob = vm.dataURLtoBlob(canvas.toDataURL('image/png'), "image/png")
+        //将其转换成file对象
+        var file = new File([blob], "video_image.png", { type: "image/png", lastModified: Date.now() })//blob转file
+        vm.uploadAndSubmit({ file: file }, key)
+      }
+    },
+  },
+  watch: {
+    keysArr: function (val) {
+      this.imageFalg();
+    }
+  },
+  //自定义指令
+  directives: {
+    load: function (el) {
+      let imgDom = document.createElement("img");
+      imgDom.style.position = "absolute";
+      imgDom.style.top = "-999px";
+      imgDom.style.opacity = 0;
+      imgDom.src = el.src;
+      el.src = "";
+      imgDom.onload = () => {
+        let width = imgDom.width;
+        let height = imgDom.height;
+        if (width > height) {
+          el.style.height = "100%";
+          el.style.width = "auto";
+          el.style.position = "absolute";
+          el.style.left = "50%";
+          el.style.top = "0";
+          el.style.transform = "translateX(-50%)";
+          el.style.webkitTransform = "translateX(-50%) translateY(0)";
+          el.style.MozTransform = "translateX(-50%) translateY(0)";
+          el.style.msTransform = "translateX(-50%) translateY(0)";
+          el.style.OTransform = "translateX(-50%) translateY(0)";
+        } else if (width < height) {
+          el.src = imgDom.src;
+          el.style.width = "100%";
+          el.style.height = "auto";
+          el.style.position = "absolute";
+          el.style.top = "50%";
+          el.style.left = "0";
+          el.style.transform = "translateY(-50%) translateX(0)";
+          el.style.webkitTransform = "translateY(-50%) translateX(0)";
+          el.style.MozTransform = "translateY(-50%) translateX(0)";
+          el.style.msTransform = "translateY(-50%) translateX(0)";
+          el.style.OTransform = "translateY(-50%) translateX(0)";
+        } else {
+          el.style.width = "100%";
+          el.style.height = "100%";
+          el.style.position = "absolute";
+          el.style.top = "0";
+          el.style.left = "0";
+          el.style.transform = "translateY(0) translateX(0)";
+          el.style.webkitTransform = "translateY(0) translateX(0)";
+          el.style.MozTransform = "translateY(0) translateX(0)";
+          el.style.msTransform = "translateY(0) translateX(0)";
+          el.style.OTransform = "translateY(0) translateX(0)";
+        }
+        el.src = imgDom.src;
+      };
+    }
+  }
+};
+</script>
+<style lang="less">
+#sagaUploads-look-image {
+  overflow: hidden;
+
+  .title {
+    /*border-left: 8px solid black;*/
+    margin: 10px 10px 10px 0;
+    font-weight: 600;
+    /*text-indent: 10px;*/
+    display: block;
+    overflow: hidden;
+  }
+
+  .avatar-uploader {
+    height: 180px;
+    width: 180px;
+    overflow: hidden;
+
+    .el-upload {
+      width: 180px;
+      height: 180px;
+
+      .el-upload-dragger {
+        width: 180px;
+        height: 180px;
+
+        .el-icon-plus {
+          display: block;
+          width: 20px;
+          height: 20px;
+          font-size: 20px;
+          margin: 80px;
+        }
+      }
+    }
+  }
+
+  .pic-name {
+    position: absolute;
+    right: 72px;
+    display: inline-block;
+    background: #fff;
+    color: #000;
+    padding: 3px;
+    z-index: 99;
+    border-radius: 8px;
+    bottom: 5px;
+  }
+
+  .point-look-image {
+    height: 180px;
+    margin-bottom: 10px;
+    position: relative;
+    overflow: hidden;
+
+    img {
+      z-index: 11;
+    }
+
+    i {
+      position: absolute;
+      bottom: 10px;
+      right: 10px;
+      background-color: #fff;
+      padding: 5px;
+      cursor: pointer;
+      z-index: 66;
+    }
+  }
+}
+</style>

+ 277 - 0
src/components/ledger/components/details/detail/lookVideo.vue

@@ -0,0 +1,277 @@
+<template>
+  <div id="sagaUploads-look-video">
+    <div class="saga-upload-images">
+      <div v-if="exhibitionVideo.list.length">
+        <template v-for="(item,index) in exhibitionVideo.list">
+          <div class="point-look-video">
+            <video
+              width="100%"
+              height="100%;"
+              :src="imageGetUrl + '&key=' +item.key"
+              controls="controls"
+            >您的浏览器不支持 video 标签。</video>
+          </div>
+        </template>
+      </div>
+      <div v-else style="text-align: center;margin: 10px auto">暂无数据</div>
+    </div>
+  </div>
+</template>
+<script>
+
+  export default {
+    props: ['exhibitionVideo'],
+    data() {
+      return {
+        baseUrl: "",
+        imageGetUrl: "/image-service/common/image_get?systemId=dataPlatform",
+        imageUploadUrl: "/image-service/common/image_upload?systemId=dataPlatform&secret=9e0891a7a8c8e885&overwrite=true",
+        imagesArr: [],
+        iframeSrc: "",
+        dialog: {
+          details: false
+        }
+      };
+    },
+    methods: {
+      //上传
+      uploadAndSubmit(item, key) {
+        // var form = document.forms["demoForm"];
+
+        // if (form["file"].files.length > 0) {
+        // 寻找表单域中的 <input type="file" ... /> 标签
+        // var file = form["file"].files[0];
+
+        console.log("lalala", item)
+        let file = item.file;
+        // try sending
+        let reader = new FileReader();
+
+        let vm = this;
+
+        let fileType = file.name.split(".");
+        let type = fileType[fileType.length - 1];
+        let CreateTime = tools.formatDate(new Date(file.lastModified))
+
+        let uploadKey = file.uid
+        if (!!key) {
+          uploadKey = key
+        }
+
+        reader.onloadstart = function () {
+          // 这个事件在读取开始时触发
+        };
+        reader.onprogress = function (p) {
+          // 这个事件在读取进行中定时触发
+        };
+
+        reader.onload = function () {
+          // 这个事件在读取成功结束后触发
+        };
+        reader.onloadend = function () {
+          // 这个事件在读取结束后,无论成功或者失败都会触发
+          if (reader.error) {
+          } else {
+            // document.getElementById("bytesRead").textContent = file.size;
+            // 构造 XMLHttpRequest 对象,发送文件 Binary 数据
+            var xhr = new XMLHttpRequest();
+            xhr.open(
+              /* method */
+              "POST",
+              /* target url */
+              vm.imageUploadUrl + "&key=" + uploadKey + "." + type
+              /*, async, default to true */
+            );
+            //xhr.overrideMimeType("application/octet-stream");
+            xhr.send(reader.result);
+            xhr.onreadystatechange = function () {
+              if (xhr.readyState == 4) {
+                if (xhr.status == 200) {
+                  if (vm.type == 'image') {
+                    vm.imagesArr.push({
+                      name: uploadKey + '',
+                      key: uploadKey + "." + type,
+                      systemId: "dataPlatform",
+                      type: "image",
+                      createTime: CreateTime
+                    });
+                  }
+                  if (type == 'mp4') {
+                    vm.imagesArr.push({
+                      name: uploadKey + '',
+                      key: uploadKey + "." + type,
+                      systemId: "dataPlatform",
+                      type: "video",
+                      createTime: CreateTime
+                    });
+                    vm.creatImg(vm.imageGetUrl + "&key=" + uploadKey + "." + type, uploadKey)
+                  }
+                  if (vm.type == "video" && type == "png") {
+                    vm.videoPicArr.push({
+                      name: uploadKey + '',
+                      key: uploadKey + "." + type,
+                      systemId: "dataPlatform",
+                      type: "image_video",
+                      createTime: CreateTime
+                    })
+                  }
+                  vm.$emit("change", vm.imagesArr, vm.defined, vm.videoPicArr);
+                }
+              }
+            };
+          }
+        };
+        reader.readAsArrayBuffer(file);
+      },
+      dataURLtoBlob(dataURI, type) {
+        var binary = atob(dataURI.split(',')[1]);
+        var array = [];
+        for (var i = 0; i < binary.length; i++) {
+          array.push(binary.charCodeAt(i));
+        }
+        return new Blob([new Uint8Array(array)], { type: type });
+      },
+
+      creatImg(reader, key) {
+        var videoDom = document.getElementById('video');
+        videoDom.src = reader;
+        let vm = this
+        videoDom.onloadeddata = function () {
+          // 这里可以打印视频时长
+          // 这里取得视频封面
+          var canvas = document.createElement('canvas');
+          canvas.width = 300;
+          canvas.height = 300 * this.videoHeight / this.videoWidth;
+          canvas.getContext('2d').drawImage(this, 0, 0, canvas.width, canvas.height);
+          //将canvas的base64位图片转换成图片png的file
+          var blob = vm.dataURLtoBlob(canvas.toDataURL('image/png'), "image/png")
+          //将其转换成file对象
+          var file = new File([blob], "video_image.png", { type: "image/png", lastModified: Date.now() })//blob转file
+          vm.uploadAndSubmit({ file: file }, key)
+        }
+      },
+    },
+    watch: {
+      keysArr: function (val) {
+        this.imageFalg();
+      }
+    },
+    //自定义指令
+    directives: {
+      load: function (el) {
+        let imgDom = document.createElement("img");
+        imgDom.style.position = "absolute";
+        imgDom.style.top = "-999px";
+        imgDom.style.opacity = 0;
+        imgDom.src = el.src;
+        el.src = "";
+        imgDom.onload = () => {
+          let width = imgDom.width;
+          let height = imgDom.height;
+          if (width > height) {
+            el.style.height = "100%";
+            el.style.width = "auto";
+            el.style.position = "absolute";
+            el.style.left = "50%";
+            el.style.top = "0";
+            el.style.transform = "translateX(-50%)";
+            el.style.webkitTransform = "translateX(-50%) translateY(0)";
+            el.style.MozTransform = "translateX(-50%) translateY(0)";
+            el.style.msTransform = "translateX(-50%) translateY(0)";
+            el.style.OTransform = "translateX(-50%) translateY(0)";
+          } else if (width < height) {
+            el.src = imgDom.src;
+            el.style.width = "100%";
+            el.style.height = "auto";
+            el.style.position = "absolute";
+            el.style.top = "50%";
+            el.style.left = "0";
+            el.style.transform = "translateY(-50%) translateX(0)";
+            el.style.webkitTransform = "translateY(-50%) translateX(0)";
+            el.style.MozTransform = "translateY(-50%) translateX(0)";
+            el.style.msTransform = "translateY(-50%) translateX(0)";
+            el.style.OTransform = "translateY(-50%) translateX(0)";
+          } else {
+            el.style.width = "100%";
+            el.style.height = "100%";
+            el.style.position = "absolute";
+            el.style.top = "0";
+            el.style.left = "0";
+            el.style.transform = "translateY(0) translateX(0)";
+            el.style.webkitTransform = "translateY(0) translateX(0)";
+            el.style.MozTransform = "translateY(0) translateX(0)";
+            el.style.msTransform = "translateY(0) translateX(0)";
+            el.style.OTransform = "translateY(0) translateX(0)";
+          }
+          el.src = imgDom.src;
+        };
+      }
+    }
+  };
+</script>
+<style lang="less">
+  #sagaUploads-look-video {
+    overflow: hidden;
+
+    .title {
+      /*border-left: 8px solid black;*/
+      margin: 10px;
+      font-weight: 600;
+      /*text-indent: 10px;*/
+      display: block;
+      overflow: hidden;
+    }
+
+    .avatar-uploader {
+      height: 180px;
+      width: 180px;
+      overflow: hidden;
+
+      .el-upload {
+        width: 180px;
+        height: 180px;
+
+        .el-upload-dragger {
+          width: 180px;
+          height: 180px;
+          .el-icon-plus {
+            display: block;
+            width: 20px;
+            height: 20px;
+            font-size: 20px;
+            margin: 80px;
+          }
+        }
+      }
+    }
+      .pic-name {
+        position: absolute;
+        right: 72px;
+        display: inline-block;
+        background: #fff;
+        color: #000;
+        padding: 3px;
+        z-index: 99;
+        border-radius: 8px;
+        bottom: 5px;
+      }
+
+    .point-look-video {
+      height: 180px;
+      position: relative;
+      overflow: hidden;
+      img {
+        z-index: 11;
+      }
+      i {
+        position: absolute;
+        bottom: 10px;
+        right: 10px;
+        background-color: #fff;
+        padding: 5px;
+        cursor: pointer;
+        z-index: 66;
+      }
+    }
+  }
+</style>

+ 49 - 0
src/components/ledger/components/details/detail/selectRadio.vue

@@ -0,0 +1,49 @@
+<template>
+  <div class="select-radio">
+    <p class="title">
+      <span class="iconfont icon-juxing"></span>
+      {{selectRadio.information}}
+    </p>
+    <template>
+      <el-radio-group
+        style=" margin: 0 10px;"
+        v-model="selectRadio.initRadio"
+        @change="changeRadio"
+        v-for="(item,index) in selectRadio.radioList"
+        :key="index"
+      >
+        <el-radio :label="item.label">{{item.value}}</el-radio>
+      </el-radio-group>
+    </template>
+  </div>
+</template>
+
+<script>
+  export default {
+    name: "selectRadio",
+    props: ['selectRadio'],
+    data() {
+      return {
+        radio: '1'
+      };
+    },
+    methods: {
+      changeRadio(val) {
+        this.$emit('changeRadio', val)
+      }
+    }
+  }
+</script>
+
+<style scoped lang="less">
+  .select-radio {
+    .title {
+      /*border-left: 8px solid black;*/
+      margin: 10px 40px 10px 10px;
+      font-weight: 600;
+      /*text-indent: 10px;*/
+      display: inline-block;
+
+    }
+  }
+</style>

File diff suppressed because it is too large
+ 1336 - 0
src/components/ledger/components/handsontables/assets.vue


File diff suppressed because it is too large
+ 1466 - 0
src/components/ledger/components/handsontables/device.vue


+ 852 - 0
src/components/ledger/components/handsontables/system.vue

@@ -0,0 +1,852 @@
+<template>
+    <div style="height: calc(100% - 44.5px); padding: 0px 10px 10px; box-sizing: border-box">
+        <div class="hanson-bar">
+            <div style="float: right; overflow: hidden">
+                <el-checkbox v-model="isShowDelivered" @change="getHeaderData(mess)">显示有值信息点</el-checkbox>
+                <span style="margin-left: 10px">当前筛选条件下共{{ page.total || "--" }}系统</span>
+            </div>
+            <el-select v-model="onlyRead" @change="getHeaderData(mess)" style="width: 100px; margin-right: 20px; vertical-align: bottom">
+                <el-option v-for="item in options" :key="item.value" :label="item.label" :value="item.value"></el-option>
+            </el-select>
+            <el-select v-model="showType" @change="handleChangeShowType" style="width: 100px; margin-right: 10px; vertical-align: bottom">
+                <el-option v-for="item in showTypes" :key="item.value" :label="item.label" :value="item.value"></el-option>
+            </el-select>
+            <el-select
+                v-show="mess.deviceId"
+                v-model="showInfoList"
+                filterable
+                clearable
+                multiple
+                collapse-tags
+                @change="initTable"
+                style="width: 200px; margin-right: 8px; vertical-align: bottom"
+                placeholder="请选择信息点"
+            >
+                <el-option v-for="item in headerOptions" :key="item.code" :label="item.name" :value="item.code"> </el-option>
+            </el-select>
+            <el-button size="small" style="width: 80px" @click="addDevice" icon="iconfont icon-tianjia">添加系统</el-button>
+            <el-button size="small" style="width: 80px" @click="reset" icon="iconfont icon-shuaxin">刷新</el-button>
+            <el-button v-show="!onlyRead" size="small" style="width: 80px" @click="undo" icon="iconfont icon-undo">撤销</el-button>
+        </div>
+        <qrcode :dialog="myDialog" :addBody="true" ref="qrcode"></qrcode>
+        <upload-files-dialog ref="upload" @changeFile="fileChange" :keysArr="filesArr" firmDataType="row" :dialog="myDialog"></upload-files-dialog>
+        <upload-img-dialog
+            :read="onlyRead ? true : false"
+            @changeFile="imgChange"
+            :keysArr="imgsArr"
+            firmDataType="row"
+            :dialog="myDialog"
+        ></upload-img-dialog>
+        <pic-dialog :dialog="myDialog" :keysArr="picsArr" @change="changePics"></pic-dialog>
+        <div class="center middle_sty" style="height: 91%" v-show="!mess.deviceId && (!tableData || !tableData.length)">
+            <p><i class="icon-wushuju iconfont"></i>请选择系统类型</p>
+        </div>
+        <div class="center middle_sty" style="height: 91%" v-show="mess.deviceId && (!tableData || !tableData.length)">
+            <p><i class="icon-wushuju iconfont"></i>暂无数据</p>
+        </div>
+        <div v-show="mess.deviceId && tableData && tableData.length" v-loading="loading" id="handsontable" ref="handsontable"></div>
+        <div v-show="mess.deviceId && tableData && tableData.length" class="right">
+            <my-pagination @change="getTableData" :page="page"></my-pagination>
+        </div>
+        <!-- <dialog-assets :assetType="[this.mess.deviceId]" @close="getClose" v-if="myDialog.addDevice" ref="assets" :dialog="myDialog"></dialog-assets> -->
+        <details-dialog :iframeSrc="iframeSrc" v-if="myDialog.details" :dialog="myDialog"></details-dialog>
+        <system-relevance @close="reset" :id="systemId" :dialog="myDialog"></system-relevance>
+        <!-- 多选枚举类型维护 -->
+        <menum :dialog="myDialog" :updateInfoData="updateInfoData" :updateInput="updateInput" @change="handleChangeMenum"></menum>
+        <!-- 不支持的输入方式 -->
+        <el-dialog title="临时维护信息点" :visible.sync="myDialog.update" @close="handleCloseUpdate" width="670px">
+            <el-row>
+                该信息点未定义对应组件,现在维护数据不确定后续是否可用。如确实需要维护,请点击
+                <el-link @click="updateInputShow = true" type="primary" :underline="false">继续维护</el-link>
+                。
+            </el-row>
+            <el-row style="margin-top: 20px" v-show="updateInputShow">
+                <el-input type="textarea" :autosize="{ minRows: 4, maxRows: 8 }" placeholder="请输入内容" v-model="updateInput"></el-input>
+            </el-row>
+            <span slot="footer" class="dialog-footer">
+                <el-button @click="myDialog.update = false">取 消</el-button>
+                <el-button type="primary" @click="handleClickUpdate">确 认</el-button>
+            </span>
+        </el-dialog>
+        <!-- 关联楼层 -->
+        <editSysfloor ref="editFloor" @relateSuccess="relateSuccess"></editSysfloor>
+        <!-- 新增系统 -->
+        <el-dialog title="确定新增系统的类型" :visible.sync="myDialog.addDevice" width="30%">
+            <el-row>
+                <my-cascader ref="cascader" @change="changeAddType" :all="true"></my-cascader>
+            </el-row>
+            <span slot="footer" class="dialog-footer">
+                <el-button type="primary" @click="toAddDevice">下 一 步</el-button>
+            </span>
+        </el-dialog>
+    </div>
+</template>
+<script>
+import { BeatchQueryParam, deleteGeneralSys, querySysLinkBuild, updateGeneralSys } from "@/api/scan/request";
+import { getDataDictionary, deliveredInfos } from "@/api/dict";
+
+import myCascader from "@/components/ledger/lib/system";
+import tools from "@/utils/scan/tools";
+import handsonUtils from "@/utils/scan/hasontableUtils";
+import showTools from "@/utils/handsontable/notShow";
+import qrcode from "@/components/business_space/lib/qrcode";
+import uploadFilesDialog from "@/components/dialogs/list/filesDialog";
+import uploadImgDialog from "@/components/dialogs/list/uploadImgDialog";
+import picDialog from "@/components/business_space/dialogs/list/picDialog";
+import myPagination from "@/components/common/myPagination";
+import detailsDialog from "@/components/business_space/lib/detailsDia";
+import systemRelevance from "@/components/ledger/tableTransfers";
+import Handsontable from "handsontable-pro";
+import text from "@/utils/handsontable/mainText";
+import "handsontable-pro/dist/handsontable.full.css";
+import editSysfloor from "@/components/ledger/lib/editSysFloor";
+import menum from "@/components/dialogs/list/menum";
+
+import { mapGetters } from "vuex";
+
+export default {
+    components: {
+        qrcode, //二维码页面
+        uploadFilesDialog,
+        uploadImgDialog,
+        picDialog,
+        myPagination,
+        detailsDialog,
+        systemRelevance,
+        myCascader,
+        editSysfloor,
+        menum,
+    },
+    computed: {
+        ...mapGetters("layout", ["projectId", "secret", "userId", "group_code"]),
+        showTypes() {
+            return this.onlyRead
+                ? [
+                      { value: "Visible", label: "只看采集信息" },
+                      { value: "all", label: "全部" },
+                  ]
+                : [
+                      { value: "Visible", label: "只看采集信息" },
+                      { value: "all", label: "全部" },
+                  ];
+        },
+    },
+    data() {
+        return {
+            options: [
+                {
+                    value: true,
+                    label: "只读模式",
+                },
+                {
+                    value: false,
+                    label: "编辑模式",
+                },
+            ],
+            tableData: [],
+            mess: {},
+            tableHeader: null,
+            page: {
+                size: 50,
+                sizes: [10, 30, 50, 100, 150, 200],
+                total: 0,
+                currentPage: 1,
+            },
+            myDialog: {
+                qrcode: false, //二维码弹窗
+                uploadFiles: false, //上传文件
+                uploadImgs: false, //上传单个图片
+                pic: false, //多个图片
+                addDevice: false,
+                details: false, //详情页
+                relevance: false, //关联设备
+                update: false, //临时维护信息点
+                menum: false, //临时多选枚举类型信息点
+            },
+            row: null, //被修改的row
+            filesArr: [], //保存临时的文件key
+            messKey: null,
+            imgsArr: [], //临时保存的图片key数组
+            picsArr: [], //临时设备图片keys数组
+            noShowList: ["SysID", "SysName"],
+            copyMain: null,
+            iframeSrc: "",
+            systemId: "",
+            onlyRead: true,
+            showType: "Visible",
+            linkNameFalg: false,
+            qrcodeUrl: "", //二维码图片地址
+            loading: false,
+            addData: {}, //添加设备选择的设备类型
+            inputMap: {
+                flowBuild: {
+                    editable: true,
+                    code: "flowBuild",
+                    name: "建筑楼层",
+                    path: "flowBuild",
+                    category: "STATIC",
+                    dataType: "ENUM",
+                },
+            }, //信息点和输入方式映射表
+            updateInputShow: false, //是否显示临时维护输入框
+            updateInfoData: {}, //当前信息点信息
+            updateInfoPoint: "", //临时维护信息点
+            updateInput: "", //临时维护信息点值
+            headerOptions: [], // 信息点选项列表
+            showInfoList: [], // 筛选的信息点列表
+            isShowDelivered: false, // 是否显示已交付的信息点
+        };
+    },
+    created() {},
+    mounted() {},
+    methods: {
+        //触发父组件change
+        getClose(val) {
+            this.$emit("close", val);
+        },
+        //获取表头
+        getHeaderData(code) {
+            this.mess = code;
+
+            if (this.mess.deviceId) {
+                // 清空筛选的信息点列表
+                this.showInfoList = [];
+                // 查询已交付的信息点
+                if (this.isShowDelivered) {
+                    this.getDeliveredInfos(this.mess.deviceId);
+                } else {
+                    // 查询全部信息点
+                    this.getDataDictionary(this.mess.deviceId);
+                }
+            }
+        },
+        /**
+         * 查询已交付的信息点
+         * @param classCode 设备类型
+         */
+        getDeliveredInfos(classCode) {
+            let params = {
+                classCode: classCode,
+                objType: "system",
+            };
+            if (this.mess.buildId == "noKnow") {
+                params.buildingId = `isNull`;
+            } else if (this.mess.buildId && this.mess.buildId != "all") {
+                params.buildingId = `${this.mess.buildId}`;
+            }
+
+            if (this.mess.floorId == "noKnow") {
+                params.floorId = `isNull`;
+            } else if (this.mess.floorId && this.mess.floorId != "all") {
+                params.floorId = `${this.mess.floorId}`;
+            }
+            deliveredInfos(params, (res) => {
+                this.tableHeader = res.content;
+                this.codeToDataSource = {};
+                this.tableHeader.forEach((item) => {
+                    if (item.path) {
+                        this.inputMap[item.path] = item;
+                    }
+                    if (item.dataSource) {
+                        try {
+                            this.codeToDataSource[item.code] = {};
+                            item.dataSource.forEach((dic) => {
+                                this.codeToDataSource[item.code][dic.code] = dic.name;
+                            });
+                        } catch (e) {
+                            console.log(e);
+                        }
+                    }
+                });
+                this.getTableData();
+            });
+        },
+        /**
+         * 查询全部信息点
+         * @param classCode 设备类型
+         */
+        getDataDictionary(classCode) {
+            let params = {
+                type: classCode,
+                orders: "sort asc, name desc",
+            };
+            getDataDictionary(params, (res) => {
+                this.tableHeader = res.content;
+                this.codeToDataSource = {};
+                this.tableHeader.forEach((item) => {
+                    if (item.path) {
+                        this.inputMap[item.path] = item;
+                    }
+                    if (item.dataSource) {
+                        try {
+                            this.codeToDataSource[item.code] = {};
+                            item.dataSource.forEach((dic) => {
+                                this.codeToDataSource[item.code][dic.code] = dic.name;
+                            });
+                        } catch (e) {
+                            console.log(e);
+                        }
+                    }
+                });
+                this.getTableData();
+            });
+        },
+        changeWatch() {
+            this.reset();
+        },
+        //获取主体数据
+        getTableData() {
+            let _this = this;
+            this.loading = true;
+            if (!!this.mess.deviceId) {
+                let param = {
+                    data: {
+                        pageSize: this.page.size,
+                        orders: "createTime desc, localName desc, localId desc, id desc",
+                        pageNumber: this.page.currentPage,
+                        filters: `classCode='${this.mess.deviceId}'`,
+                    },
+                };
+                if (this.mess.buildId == "noKnow") {
+                    param.buildingId = 1;
+                } else if (this.mess.buildId && this.mess.buildId != "all") {
+                    param.buildingId = this.mess.buildId;
+                }
+                if (this.mess.floorId == "noKnow") {
+                    param.floorId = 1;
+                } else if (this.mess.floorId && this.mess.floorId != "all") {
+                    param.floorId = this.mess.floorId;
+                }
+                querySysLinkBuild(param, (res) => {
+                    this.loading = false;
+                    _this.tableData = res.content;
+                    _this.copyMain = tools.deepCopy(res.content);
+                    _this.page.total = res.total;
+                    if (res.content && res.content.length) {
+                        if (this.onlyRead) {
+                            this.getBatch(res.content);
+                        }
+                        _this.initTable();
+                    }
+                });
+            }
+        },
+        //撤回
+        undo() {
+            if (this.tableData.length) {
+                this.hot.undo();
+            }
+        },
+        //刷新
+        reset() {
+            if (this.mess.deviceId) {
+                this.getTableData();
+            } else {
+                this.$message({
+                    message: "请选择系统类型",
+                    type: "warning",
+                });
+            }
+        },
+        //添加设备
+        addDevice() {
+            this.myDialog.addDevice = true;
+            if (this.mess.deviceId) {
+                this.$nextTick(() => {
+                    this.$refs.cascader.setValue(this.mess.deviceId);
+                });
+            }
+        },
+        //下一步
+        toAddDevice() {
+            if (this.addData.category) {
+                this.$router.push({
+                    path: "/ledger/systemadd",
+                    query: this.addData,
+                });
+            }
+        },
+        //选择设备类型-添加设备
+        changeAddType(val) {
+            this.addData.category = val.classCode;
+            this.addData.categoryName = val.categoryName;
+            this.addData.showType = this.showType;
+            console.log("val", val, this.addData);
+        },
+        /**
+         * 切换信息点查看类型
+         */
+        handleChangeShowType() {
+            this.showInfoList = [];
+            this.initTable();
+        },
+        //格式化表头
+        formatHeaderData(list) {
+            let arr = tools.copyArr(list);
+            let data = showTools.headerTextFilter(arr, "system", this.onlyRead, this.showType);
+            data.unshift("操作");
+            if (this.showType == "all") {
+                data.splice(3, 0, "所属建筑楼层");
+            }
+            return data;
+        },
+        //格式化表内容
+        formatHeaderType(list) {
+            let arr = tools.copyArr(list);
+            let data = showTools.headerTypeFilter(arr, "system", this.onlyRead, this.showType);
+            this.headerOptions = this.showType === "all" ? tools.copyArr(this.tableHeader) : this.tableHeader.filter((item) => item.visible);
+            data.unshift(
+                {
+                    data: "caozuo",
+                    renderer: tools.lookDetails,
+                    readOnly: true,
+                }
+                // {
+                //   data: "Count",
+                //   renderer: tools.num,
+                //   readOnly: true
+                // }
+            );
+            if (this.onlyRead) {
+                data.map((item) => {
+                    item.readOnly = true;
+                });
+            }
+            if (this.showType == "all") {
+                data.splice(3, 0, {
+                    data: "buildingFloorInfoList",
+                    renderer: text.sysInBuildFloor,
+                    readOnly: this.onlyRead,
+                });
+            }
+            return data;
+        },
+        //获取动态参数
+        getBatch(data) {
+            let param = {
+                groupCode: this.group_code,
+                appId: "datacenter",
+                projectId: this.projectId,
+                data: [],
+            };
+            //一级遍历list
+            this.tableHeader.map((head) => {
+                //如果一级标签为动态参数或者设定参数放入数据等待请求
+                if (head.category != "STATIC") {
+                    data.map((item) => {
+                        let cur = tools.dataForKey(item, head.path);
+                        if (cur) {
+                            param.data.push({
+                                objectId: item.id,
+                                infoCode: head.code,
+                            });
+                        }
+                    });
+                }
+            });
+            if (param.data.length) {
+                BeatchQueryParam(param, (res) => {
+                    if (!this.onlyRead) {
+                        return false;
+                    }
+                    this.tableData = data.map((item) => {
+                        res.data.map((child) => {
+                            if (item.id == child.objectId) {
+                                if (!!child.data || child.data == 0) {
+                                    this.tableHeader.map((head) => {
+                                        if (head.code == child.infoCode) {
+                                            let contentVal = child.data;
+                                            if (this.codeToDataSource[child.infoCode]) {
+                                                contentVal = this.codeToDataSource[child.infoCode][child.data];
+                                            }
+                                            tools.setDataForKey(item, head.path, contentVal);
+                                        }
+                                    });
+                                } else {
+                                    this.tableHeader.map((head) => {
+                                        if (head.code == child.infoCode) {
+                                            tools.setDataForKey(
+                                                item,
+                                                head.path,
+                                                child.error ? (child.value ? "表号功能号格式错误" : "表号功能号不存在") : "暂未采集到实时数据"
+                                            );
+                                        }
+                                    });
+                                }
+                            }
+                        });
+                        return item;
+                    });
+                    this.hot.loadData(this.tableData);
+                });
+            }
+        },
+        /**
+         * 筛选要展示的表头函数
+         */
+        filterShowInfo(list) {
+            let arr = this.tableHeader.filter((item) => {
+                return item.code === "localId" || item.code === "localName" || list.includes(item.code);
+            });
+            return arr;
+        },
+        //初始化插件
+        initTable() {
+            var container = document.getElementById("handsontable");
+            let winHeight = document.documentElement.clientHeight;
+            if (this.hot) {
+                this.hot.destroy();
+                this.hot = null;
+            }
+            this.hot = new Handsontable(container, {
+                data: this.tableData,
+                fixedColumnsLeft: 3,
+                colHeaders: this.formatHeaderData(this.showInfoList.length ? this.filterShowInfo(this.showInfoList) : this.tableHeader), //表头文案
+                columns: this.formatHeaderType(this.showInfoList.length ? this.filterShowInfo(this.showInfoList) : this.tableHeader), //数据显示格式
+                filters: true,
+                maxRows: this.tableData.length,
+                height: winHeight - 335,
+                columnSorting: true, //添加排序
+                sortIndicator: true, //添加排序
+                renderAllRows: true,
+                // observeChanges: false, //启用observeChanges插件
+                // colWidths: 200,
+                autoColumnSize: true,
+                language: "zh-CN",
+                fillHandle: "vertical", //允许纵向填充
+                manualColumnResize: true,
+                manualColumnMove: true,
+                dropdownMenu: ["filter_by_condition", "filter_by_value", "filter_action_bar"],
+                contextMenu: this.onlyRead
+                    ? false
+                    : {
+                          items: {
+                              remove_row: {
+                                  name: "删除该系统",
+                              },
+                          },
+                      },
+                // 事件
+                afterChange: this.handleUpdataTable, //修改后
+                afterFilter: this.trimmedRows, //排序前
+                afterRemoveRow: this.handleDeleteTableRow, //右键删除
+                afterOnCellMouseDown: this.eventClick, //鼠标点击
+            });
+            let pro = document.getElementById("hot-display-license-info");
+            if (!!pro) {
+                pro.parentNode.removeChild(pro);
+            }
+            this.isLoading = false;
+        },
+        //表格中的点击
+        eventClick(el, rowArr) {
+            //点击的是表头
+            if (rowArr.row < 0) {
+                return;
+            }
+            //被筛选过后的数组
+            let trimmedArr = this.trimmedRows();
+            //是否启用了排序
+            let isSort = this.hot.getPlugin("columnSorting").isSorted();
+            if (trimmedArr.length && isSort) {
+                let sortArr = this.myHotArr.getPlugin("columnSorting").rowsMapper.__arrayMap;
+                let infos = this.tableData[trimmedArr[sortArr[rowArr.row]]];
+                this.getInfors(infos, { row: sortArr[rowArr.row], col: rowArr.col });
+            } else if (isSort) {
+                //排序后的数组
+                let sortArr = this.hot.getPlugin("columnSorting").rowsMapper.__arrayMap;
+                let infos = this.tableData[sortArr[rowArr.row]];
+                this.getInfors(infos, { row: sortArr[rowArr.row], col: rowArr.col });
+            } else if (trimmedArr.length) {
+                let infos = this.tableData[trimmedArr[rowArr.row]];
+                this.getInfors(infos, { row: trimmedArr[rowArr.row], col: rowArr.col });
+            } else {
+                let infos = this.tableData[rowArr.row];
+                this.getInfors(infos, rowArr);
+            }
+        },
+        handleUpdataTable(changeData, source) {
+            if (!this.onlyRead && source != "ObserveChanges.change") {
+                if (changeData) {
+                    let trimmedArr = this.trimmedRows();
+                    let param = handsonUtils.getParam(changeData, source, this.hot, trimmedArr);
+                    let data = [];
+                    for (let i = 0; i < param.length; i++) {
+                        data.push(param[i]);
+                    }
+                    //如果data中包含/且data长度为1,将其转换成.
+                    if (changeData.length == 1 && changeData[0][1].indexOf("/") > 0) {
+                        changeData[0][1] = changeData[0][1].split("/").join(".");
+                    }
+
+                    //存在data进行修改请求
+                    if (data && data.length) {
+                        this.updateBusiness(data, changeData);
+                    }
+                }
+            }
+        },
+        //删除资产
+        async handleDeleteTableRow() {
+            this.$confirm("此操作将删除该系统, 是否继续?", "提示", {
+                confirmButtonText: "确定",
+                cancelButtonText: "取消",
+                type: "warning",
+            })
+                .then(() => {
+                    let params = tools.differenceArr(this.tableData, this.copyMain);
+                    let param = [];
+                    params.map((item) => {
+                        param.push({ id: item.id });
+                    });
+                    this.removeSys(param);
+                })
+                .catch(() => {
+                    this.getTableData();
+                    this.$message("取消删除");
+                });
+        },
+        async removeSys(param) {
+            await deleteGeneralSys(param, (res) => {
+                this.$message.success("删除成功");
+                this.$emit("close", "");
+                this.getTableData();
+            });
+        },
+        // 修改
+        updateBusiness(data, change) {
+            let param = {
+                    content: [],
+                },
+                keyList = [];
+            //生成要修改字段列表
+            change.map((item) => {
+                // let key = item[1].split(".")[0]
+                // if (item[1] && keyList.indexOf(key) == -1) {
+                //   keyList.push(key);
+                // }
+                if (item[1] && keyList.indexOf(item[1]) == -1) {
+                    keyList.push(item[1]);
+                }
+            });
+            //生成对应修改数据
+            data.map((item, index) => {
+                let nullList = [];
+                keyList.map((value) => {
+                    let itemData = tools.dataForKey(item, value);
+                    tools.setDataForKey(item, value, itemData == "" ? null : itemData);
+                    // 数据为空加入置空列表(空数据类型包含: undefind / "" / null )
+                    itemData || nullList.push(value);
+                });
+                // 置空列表不为空
+                if (nullList.length) {
+                    // 深拷贝需更新的对象,并添加置空列表
+                    const updateItem = JSON.parse(JSON.stringify(item));
+                    updateItem.nullList = nullList;
+                    param.content.push(updateItem);
+                } else {
+                    // 无置空内容
+                    param.content.push(item);
+                }
+            });
+            updateGeneralSys(param, (res) => {});
+        },
+        //获取到了正确的信息
+        getInfors(infos, row) {
+            //其他的开始判断
+            let val = this.hot.colToProp(row.col);
+            let inputData = this.inputMap[val];
+            this.systemId = infos.SysID;
+            this.row = row.row;
+            this.messKey = val;
+            switch (val) {
+                //操作
+                case "caozuo":
+                    // window.open(`http://adm.sagacloud.cn:8058/system?id=${infos.id}&pid=${this.projectId}&secret=${this.secret}`,"_blank")
+                    // this.$message("开发中...")
+                    console.log(infos);
+                    this.$router.push({
+                        path: "/ledger/systemDetail",
+                        query: { SysID: infos.id, Name: infos.localName || infos.name, SysType: infos.classCode },
+                    });
+                    return false;
+                //设备二维码图片
+                // case 'EquipQRCode':
+                case "defaultQRCode":
+                    this.qrcodeUrl = this.tableData[row.row].EquipQRCode;
+                    if (!!this.qrcodeUrl) {
+                        this.myDialog.qrcode = true;
+                    } else {
+                        this.$message("此设备没有设备二维码");
+                    }
+                    return false;
+                //关联设备数量
+                case "count":
+                    if (!this.onlyRead) {
+                        this.myDialog.relevance = true;
+                    }
+                    return false;
+                case "buildingFloorInfoList":
+                    if (!this.onlyRead) {
+                        this.$refs.editFloor.showDialog(this.tableData[row.row]);
+                    }
+                    return false;
+                //系统图(F2)
+                case "infos.systemChart":
+                    let SSPPdata = tools.dataForKey(this.tableData[row.row], val);
+                    this.imgsArr = SSPPdata ? SSPPdata : [];
+                    if (!this.onlyRead || this.imgsArr.length) {
+                        this.myDialog.uploadImgs = true;
+                    }
+                    return false;
+                default:
+                    break;
+            }
+            if (!this.onlyRead && !inputData.editable) {
+                this.$confirm("该信息点的值为自动生成,不可人工维护!", "提示", {
+                    confirmButtonText: "我知道了",
+                    showCancelButton: false,
+                    type: "warning",
+                    center: true,
+                }).then(() => {
+                    return false;
+                });
+            }
+            // 维护多选枚举值
+            if (!this.onlyRead && inputData.dataType == "MENUM") {
+                this.updateInfoData = inputData;
+                this.updateInfoPoint = val;
+                this.updateInput = tools.dataForKey(this.tableData[row.row], val);
+                this.myDialog.menum = true;
+                return false;
+            }
+            if (!this.onlyRead && showTools.inputModeArr.indexOf(inputData.dataType) == "-1") {
+                this.updateInfoPoint = val;
+                this.updateInput = tools.dataForKey(this.tableData[row.row], val);
+                this.myDialog.update = true;
+                return false;
+            }
+        },
+        //关闭临时维护弹窗回调
+        handleCloseUpdate() {
+            this.updateInputShow = false;
+            this.updateInfoPoint = "";
+            this.updateInput = "";
+        },
+        //更新临时维护信息点
+        handleClickUpdate() {
+            // this.$message("更新成功");
+            tools.setDataForKey(this.tableData[this.row], this.updateInfoPoint, this.updateInput);
+            this.handleUpdataTable([[this.row, this.updateInfoPoint, null, this.updateInput]], "edit");
+            this.updateInputShow = false;
+            this.myDialog.update = false;
+            this.updateInput = "";
+        },
+        //多选枚举类型值修改
+        handleChangeMenum(newValue) {
+            tools.setDataForKey(this.tableData[this.row], this.updateInfoPoint, newValue);
+            this.handleUpdataTable([[this.row, this.updateInfoPoint, null, newValue]], "edit");
+            this.myDialog.menum = false;
+            this.updateInput = "";
+        },
+        //关联建筑楼层成功
+        relateSuccess(data) {
+            this.getTableData();
+            // tools.setDataForKey(this.tableData[this.row], "BuildingFloorInfoList", data)
+        },
+        //获取被筛选掉的行号
+        trimmedRows() {
+            // var plugin = hot.getPlugin('trimRows').trimmedRows;//获取被筛选掉的行号
+            var plugin = this.hot.getPlugin("trimRows").trimmedRows;
+            let dataLength = this.tableData.length;
+            let dataArr = new Array();
+            for (let i = 0; i < dataLength; i++) {
+                dataArr.push(i);
+            }
+            if (plugin.length <= 0) {
+                dataArr = undefined;
+            } else {
+                dataArr = this.array_diff(dataArr, plugin);
+            }
+            return dataArr || [];
+            // var DataArray = new Array();
+
+            // for (var i = 0; i < plugin.length; i++) {
+            //     // 通过行号获取数据
+            //     DataArray.push(this.hot.getSourceDataAtRow(plugin[i]));
+            // }
+        },
+        //上传文件弹窗触发事件
+        fileChange(keys) {
+            this.setDataToMain(keys, this.messKey, this.row);
+        },
+        //上传图片弹窗触发事件
+        imgChange(keys) {
+            this.setDataToMain(keys, this.messKey, this.row);
+        },
+        //设备图片弹窗改变事件
+        changePics(keys) {
+            this.setDataToMain(keys, this.messKey, this.row);
+        },
+        //判断是否有值,有值赋值
+        setDataToMain(data, key, row) {
+            if (!!data && data != "--") {
+                if (!!this.tableData[row]) {
+                    tools.setDataForKey(this.tableData[row], key, data);
+                    this.handleUpdataTable([[row, key, null, data]], "edit");
+                } else {
+                    this.tableData[row] = {};
+                    tools.setDataForKey(this.tableData[row], key, data);
+                }
+            } else {
+                tools.setDataForKey(this.tableData[row], key, "");
+            }
+        },
+    },
+    watch: {
+        projectId() {
+            this.tableData = [];
+            this.mess.deviceId = null;
+            this.page.total = 0;
+        },
+        showTypes: {
+            handler(newName, oldName) {
+                if (newName && newName[0] && newName[0].value) {
+                    this.showType = newName[0].value;
+                } else {
+                    this.showType = "Visible";
+                }
+            },
+            immediate: true,
+            deep: true,
+        },
+    },
+};
+</script>
+<style lang="less" scoped>
+/deep/ .el-select__tags-text {
+    display: inline-block;
+    max-width: 80px;
+    white-space: nowrap;
+    text-overflow: ellipsis;
+    overflow: hidden;
+}
+/deep/ .el-select .el-tag__close.el-icon-close {
+    right: -4px;
+    top: -6px;
+}
+</style>
+<style lang="less">
+.hanson-bar {
+    height: 40px;
+    padding: 5px 0;
+    font-size: 14px;
+    overflow: hidden;
+    margin-top: 0;
+    line-height: 40px;
+
+    .iconfont {
+        font-size: 12px;
+    }
+}
+</style>

+ 173 - 0
src/components/ledger/components/lib/assets.vue

@@ -0,0 +1,173 @@
+<template>
+  <div id="cascaderMap">
+    <span class="buildFloor" style="padding-right: 12px;">设备族</span>
+    <el-select v-model="value" placeholder="请选择" clearable :props="props" filterable
+               :style="isWidth ? '' : 'width:160px;'" size="small" @change="changeVal">
+      <el-option v-for="item in options" :key="item.code" :label="item.facility" :value="item.code"></el-option>
+    </el-select>
+    <!-- <el-cascader
+      placeholder="请选择"
+      :options="options"
+      v-model="value"
+      :props="props"
+      filterable
+      :style="isWidth ? '' : 'width:160px;'"
+      size="small"
+      @change="changeVal"
+      change-on-select
+    ></el-cascader> -->
+  </div>
+</template>
+<script>
+import { mapGetters } from 'vuex';
+import { getEquipBelongs, queryProperty } from "@/api/scan/request";
+
+export default {
+  name: "getCode",
+  props: {
+    isWidth: {
+      type: Boolean,
+      default: true
+    },
+    all: {
+      type: Boolean,
+      default: false,
+    }
+  },
+  computed: {
+    ...mapGetters("layout", [
+      "projectId",
+    ])
+  },
+  data() {
+    return {
+      value: [],
+      options: [],
+      props: {
+        value: "code",
+        label: "facility"
+      },
+      falg: true,
+      content: []
+    };
+  },
+  created() {
+    this.getData();
+  },
+  watch: {
+    projectId() {
+      this.value = ['']
+    }
+  },
+  methods: {
+    setValue(val) {
+      this.value = val
+    },
+    //修改val
+    changeVal(val) {
+      let value = {}
+      this.options.map(item => {
+        if (item.code == val) {
+          value = item
+        }
+      })
+      this.value = val
+      this.$emit("change", value)
+    },
+    getData() {
+      let _this = this
+      let param1 = {
+        data: {
+          distinct: true,
+          pageNumber: 1,
+          pageSize: 500,
+          projection: ["family", "familyName"]
+        }
+      }, param2 = {
+        distinct: true,
+        pageNumber: 1,
+        pageSize: 500,
+        projection: [
+          "family"
+        ]
+      }
+      let promise1 = new Promise((resolve, reject) => {
+        getEquipBelongs(param1, res => {
+          resolve(res)
+        })
+      })
+      let promise2 = new Promise((resolve, reject) => {
+        queryProperty(param2, res => {
+          resolve(res)
+        })
+      })
+      Promise.all([promise1, promise2]).then((res) => {
+        let allData = res[0], data = res[1]
+        _this.options = _this.changeArr(allData.content)
+        if (_this.value) {
+          _this.changeVal(this.value)
+        }
+        if (!_this.all) {
+          _this.content = data.content.map(item => {
+            return item.family
+          });
+          _this.changeList();
+          if (_this.value) {
+            _this.changeVal(this.value)
+          }
+        }
+      })
+      // getEquipBelongs(params, res => {
+      //   this.options = this.changeArr(res.Content)
+      //   if (!!this.value && this.value.length) {
+      //     let value = {}
+      //     this.options.map(item => {
+      //       if (item.code == this.value[0]) {
+      //         value = item
+      //       }
+      //     })
+      //     this.$emit("change", value)
+      //   }
+      //   if (!this.falg) {
+      //     this.changeList()
+      //   }
+      // })
+      console.log(this.options,'=========================')
+    },
+    changeArr(arr) {
+      let data = [];
+      arr.map(item => {
+        data.push({ code: item.family, facility: item.familyName, disabled: false })
+      });
+      return data;
+    },
+    pushData(content) {
+      this.content = content
+      if (this.options.length) {
+        this.falg = true
+        this.changeList()
+      } else {
+        this.falg = false
+      }
+    },
+    changeList() {
+      this.options = this.options.filter(item => {
+        if (this.content.indexOf(item.code) > -1) {
+          return item
+        }
+      })
+    }
+  }
+};
+</script>
+<style lang="less" scoped>
+#cascaderMap {
+  float: left;
+  margin-left: 10px;
+
+  .buildFloor {
+    color: #999999;
+    font-size: 14px;
+  }
+}
+</style>

+ 312 - 0
src/components/ledger/components/lib/bimDialog.vue

@@ -0,0 +1,312 @@
+<template>
+  <el-dialog
+    title='修改BIM模型中的坐标'
+    v-if='dialog.bimcode'
+    custom-class='custom-dialog'
+    :visible.sync='dialog.bimcode'
+    width='900px'
+    @close='handleClose'
+  >
+    <div id='bim-code' v-if='Object.keys(bimcodeobj).length>0'>
+      <div class='eq'>
+        <span class='eq-name'>设备名称 &nbsp;&nbsp;</span>
+        {{ bimcodeobj.localName || bimcodeobj.name || '--' }}
+      </div>
+      <div>
+        <noAllDataFloor @change='changeFloor' ref='floors' @getFloorMap='getFloorMap'></noAllDataFloor>
+        <span v-if='isShow' class='errMsg'>请选择设备所属建筑楼层</span>
+      </div>
+      <div class='canvas-box' v-loading='canvasLoading'>
+        <canvas v-show='isdata' :id='`floorCanvas${id}`' :width='cadWidth' :height='cadHeight' ref='canvas'
+                tabindex='0'></canvas>
+        <p v-show='!isdata'>当前楼层暂无平面图,请前往“建筑楼层管理”中给楼层添加平面图</p>
+      </div>
+    </div>
+    <div slot='footer' class='footer' v-if="!read">
+      <el-button @click='handleClose'>取 消</el-button>
+      <el-button type='primary' @click='save'>确 定</el-button>
+    </div>
+  </el-dialog>
+</template>
+<script>
+import noAllDataFloor from './noAllDataFloor'
+import { FloorView, LocationPointScene } from '@saga-web/cad-engine/lib'
+import { SPoint } from '@saga-web/draw/lib'
+import { updateEquip } from '@/api/scan/request'
+import { SGraphItem } from '@saga-web/graph/lib'
+
+export default {
+  components: {
+    noAllDataFloor,
+  },
+  props: {
+    dialog: {
+      type: Object,
+      default: function () {
+        return {
+          bimcode: false,
+        }
+      },
+    },
+    bimcodeobj: {
+      default: {},
+    },
+    read: {
+      default: true,
+      type: Boolean
+    }
+  },
+  data() {
+    return {
+      cadWidth: 800,
+      cadHeight: 400,
+      Buildfloor: '',
+      value: [],
+      isdata: true,
+      floorData: {},
+      canvasLoading: false,
+      isShow: false,
+      id: 'ty',
+      view: '',
+      drawMainScene: '',
+      floorToMap: {},
+    }
+  },
+  created() {
+  },
+  mounted() {
+    console.log(this.bimcodeobj)
+    this.init()
+  },
+  methods: {
+    init() {
+      this.isShow = true
+      this.$nextTick(function () {
+        if (this.bimcodeobj.floorId) {
+          this.isShow = false
+        }
+        if (this.$refs.floors) {
+          this.$refs.floors.setValue([this.bimcodeobj.buildingId, this.bimcodeobj.floorId])
+        }
+      })
+    },
+    //切换楼层
+    changeFloor(value) {
+      this.isShow = true
+      if (value.length > 1) {
+        this.isShow = false
+        this.floorData = this.floorToMap[value[1]]
+      } else {
+        this.isShow = false
+        this.floorData = {}
+      }
+      this.getFloorData()
+    },
+    // 初始化canvas
+    initGraphy(Id, type) {
+      // type=1 => id:模型id 这里不需要
+      // type=2 => id:floormap jsonz格式
+      // type = 3 => id:floormap png或者jpg格式
+      let that = this
+      that.clearGraphy()
+      that.drawMainScene = new LocationPointScene()
+      that.canvasLoading = true
+      if (type == 2) {
+        that.drawMainScene.loadUrl(`/image-service/common/file_get?systemId=revit&key=${Id}`).then((res) => {
+          that.getGraphtSuc(res)
+        })
+      } else if (type == 3) {
+        let x = 0;
+        let y = 0;
+        let scale = 1;
+        if (this.floorData && this.floorData.infos && this.floorData.infos.floorMapRatio && this.floorData.infos.floorMapShift) {
+          try {
+            const point = JSON.parse(this.floorData.infos.floorMapShift);
+            x = point.x;
+            y = point.y;
+            scale = this.floorData.infos.floorMapRatio;
+          } catch (err) {
+            console.log(err);
+          }
+        }
+        that.drawMainScene.loadImg(`/image-service/common/image_get?systemId=dataPlatform&key=${Id}`, (res) => {
+            that.getGraphtSuc(res)
+        }, x, y, scale)
+      }
+    },
+    // 获取底图成功
+    getGraphtSuc(res) {
+      this.canvasLoading = false
+      if (res == 'error') {
+        this.FloorMap = ''
+        console.log('数据解析异常');
+        return
+      }
+      if (res.Result == 'failure') {
+        this.showTools = false
+        this.$message.warning(res.Message)
+        return
+      }
+      this.view.scene = this.drawMainScene
+      if (this.bimcodeobj.locationJson) {
+        let obj = {
+          X: this.bimcodeobj.locationJson.x,
+          Y: -this.bimcodeobj.locationJson.y,
+        }
+        //回写编辑
+        this.drawMainScene.addMarker(obj)
+      }
+      this.view.fitSceneToView()
+      this.drawMainScene.isSpaceSelectable = false
+      if (!this.read) {
+        this.drawMainScene.spaceClick(this, this.canvasClick) //锚点触发
+      }
+      if (this.floorData.outline && this.floorData.outline.length) {
+        let newArr = this.floorData.outline.map((t) => {
+          return new SPoint(t.X, t.Y)
+        })
+        this.drawMainScene.addSceneMark(newArr)
+      }
+    },
+    //获取楼层数据
+    getFloorData() {
+      if (this.floorData && this.floorData.infos && this.floorData.infos.floorMap) {
+        this.isdata = true
+        let floorMap = this.floorData.infos.floorMap
+        const temp = floorMap.split('.')[1];
+        if (temp && (temp.toLowerCase() == 'png' || temp.toLowerCase() == 'jpg')) {
+          this.initGraphy(floorMap, 3)
+        } else {
+          this.initGraphy(floorMap, 2)
+        }
+      } else {
+        //暂无数据
+        this.isdata = false
+        this.clearGraphy()
+      }
+    },
+    //关闭弹窗
+    handleClose(val) {
+      this.view = ''
+      this.drawMainScene = ''
+      this.$emit('closeBIM', val)
+    },
+    //保存事件
+    save() {
+      if (this.drawMainScene) {
+        if (this.drawMainScene.markerList.length) {
+          let obj = this.drawMainScene.markerList[0], BIMLocation;
+
+          if (this.bimcodeobj.bimLocation && this.bimcodeobj.bimLocation.split(',')[2]) {
+            BIMLocation = `${obj.x.toFixed(2)},${(obj.y * -1).toFixed(2)},${this.bimcodeobj.bimLocation.split(',')[2]}`
+          } else {
+            BIMLocation = `${obj.x.toFixed(2)},${(obj.y * -1).toFixed(2)},0`
+          }
+          const bfloor = this.$refs.floors.value
+          let param = {
+            content: [
+              {
+                id: this.bimcodeobj.id,
+                buildingId: bfloor[0] == 'noKnow'?undefined:bfloor[0],
+                floorId: bfloor[1] == 'noKnow'?undefined:bfloor[1],
+                bimLocation: BIMLocation
+              },
+            ],
+            projection: ['bimLocation', 'buildingId', 'floorId'],
+          }
+          updateEquip(param, (res) => {
+            if (res.result == 'success') {
+              this.$message.success('保存成功')
+              this.handleClose('update')
+            }
+          })
+        }
+      }
+    },
+    // 清空平面图
+    clearGraphy() {
+      if (this.view) {
+        this.view.scene = null
+        return
+      }
+      let id = `floorCanvas${this.id}`
+      this.view = new FloorView(id)
+    },
+    //得到floormap
+    getFloorMap(val) {
+      this.floorToMap = val
+      this.floorData = this.floorToMap[this.bimcodeobj.floorId]
+      this.getFloorData()
+    },
+    // canvas点击事件
+    canvasClick(item, event) {
+      if (item instanceof SGraphItem) {
+        this.clearMark()
+        const p = item.mapToScene(event[0].x, event[0].y)
+        let obj = {
+          X: p.x,
+          Y: p.y,
+        }
+        this.drawMainScene.addMarker(obj)
+        this.view.fitSceneToView()
+      }
+    },
+    //清空标记
+    clearMark() {
+      if (this.drawMainScene) {
+        this.drawMainScene.markerList.forEach((t) => {
+          this.drawMainScene.removeItem(t)
+        })
+        this.drawMainScene.markerList = []
+      }
+    },
+  },
+  watch: {
+    bimcodeobj: {
+      immediate: true,
+      handler(val) {
+        this.init()
+      },
+    },
+  },
+}
+</script>
+<style lang="less" scoped>
+#bim-code {
+  height: 500px;
+
+  .eq {
+    padding-bottom: 10px;
+
+    .eq-name {
+      color: #999999;
+      padding-left: 10px;
+    }
+  }
+
+  .errMsg {
+    color: red;
+    padding-left: 10px;
+  }
+
+  #buildFloor {
+    clear: both;
+    margin-bottom: 10px;
+  }
+
+  .canvas-box {
+    border: 1px solid #ccc;
+    clear: both;
+    width: 800px;
+    height: 400px;
+    margin-left: 10px;
+    display: flex;
+    justify-content: center;
+    align-items: center;
+  }
+}
+</style>
+<style lang="less">
+#bim-code {
+}
+</style>

+ 97 - 0
src/components/ledger/components/lib/buildfloorCascader.vue

@@ -0,0 +1,97 @@
+<template>
+  <el-cascader v-model="value" :options="options" :props="props" clearable ref="elCascader" @remove-tag="removeTag"></el-cascader>
+</template>
+<script>
+// import { buildingQuery } from "@/api/scan/request";
+import { buildingQuery } from '@/api/object/build';
+
+export default {
+  data() {
+    return {
+      value: [],
+      props: {
+        multiple: true,
+        checkStrictly: true,
+        value: 'id',
+        label: 'localName',
+        children: 'floor'
+      },
+      options: []
+    }
+  },
+  props: {
+    SysID: {
+      default: ''
+    }
+  },
+
+  methods: {
+    getCascader() {
+      let param = {
+        cascade: [
+          { name: "floor", orders: "floorSequenceID desc" }
+        ],
+        orders: "localName asc",
+        pageNumber: 1,
+        pageSize: 50
+      }
+      buildingQuery(param, res => {
+        res.content.map(t => {
+          if (t.floor && t.floor.length) {
+            t.floor = t.floor.map(item => {
+              if (item.id == this.FloorID) return
+              item.BuildID = item.id
+              item.BuildLocalName = item.localName || item.name
+              return item
+            }).filter(it => it)
+          }
+        })
+        this.options = res.content
+      })
+    },
+    getSelectedNodes() {
+      let nodes = this.$refs.elCascader.getCheckedNodes();
+      let arr = [], flag = false, brr = [];
+      if (nodes.length) {
+        nodes.map(t => {
+          let obj = {
+            BuildName: t.pathLabels[0],
+            BuildID: t.path[0]
+          }
+          if (t.path.length > 1) {
+            obj.FloorName = t.pathLabels[1]
+            obj.FloorID = t.path[1]
+          } else {
+            flag = true;
+          }
+          arr.push(obj);
+        })
+        if (flag) {
+          for (let i = 0; i < arr.length; i++) {
+            for (let j = 0; j < arr.length; j++) {
+              if (!arr[i].FloorID && arr[j].FloorID && arr[i].BuildID == arr[j].BuildID) {
+                brr.push(i)
+              }
+            }
+          }
+          arr = arr.map((t, i) => {
+            if (brr.indexOf(i) > -1) {
+              return undefined;
+            }
+            return t;
+          }).filter(item => item);
+        }
+      }
+      return arr;
+    },
+    removeTag(){
+      console.log(arguments)
+    }
+  }
+}
+</script>
+<style lang="less" scoped>
+.el-cascader {
+  width: 100%;
+}
+</style>

+ 120 - 0
src/components/ledger/components/lib/buildingDialog.vue

@@ -0,0 +1,120 @@
+<!--
+    systemType 设备所属类型
+-->
+<template>
+    <el-dialog title="所属建筑" :visible.sync="dialog.building" width="750px">
+        <div style="height: 50px;">
+            <el-select v-model="valueList" :disabled="onlyRead" multiple clearable filterable placeholder="请选择" style="width: 100%;">
+                <el-option v-for="item in buildingData" :key="item.id" :label="item.localName" :value="item.id"> </el-option>
+            </el-select>
+        </div>
+        <span slot="footer" class="dialog-footer" v-if="!onlyRead">
+            <el-button type="primary" @click="getChange">确 定</el-button>
+        </span>
+    </el-dialog>
+</template>
+<script>
+import { buildingQuery, shaftLinkBuild } from "@/api/scan/request"
+import { mapGetters } from 'vuex'
+
+export default {
+    props: {
+        dialog: {
+            type: Object,
+            default: function () {
+                return {
+                    building: true
+                };
+            }
+        },
+        shaftId: {
+            type: String,
+            default: ""
+        },//设备id
+        onlyRead: {
+            type: Boolean,
+            default: false
+        },
+        buildingList: {
+            type: [Array]
+        }
+    },
+    data() {
+        return {
+            buildingData: [],
+            valueList: [] //选中的建筑
+        };
+    },
+    computed: {
+        ...mapGetters("layout", ["projectId"])
+    },
+    created() {
+        this.getBuildingData();
+    },
+    methods: {
+        getBuildingData() {
+            let param = {
+                orders: "localName asc",
+                pageNumber: 1,
+                pageSize: 1000
+            }
+            buildingQuery(param, res => {
+                if (res.result == 'success') {
+                    this.buildingData = res.content;
+                } else {
+                    this.$message.error(res.message)
+                }
+            })
+        },
+        getLinkBuildingData(){
+            this.valueList = this.buildingList.map(item => { return item.id })
+        },
+        getChange() {
+            if (this.shaftId) { //有竖井id
+                const params = {
+                    shaftId: this.shaftId,
+                    buildingIdList: this.valueList
+                }
+               shaftLinkBuild(params, res => {
+                   if (res.result === 'success') {
+                       this.$message.success("关联成功!");
+                       const linkBuild = this.buildingData.filter(item => {
+                            return this.valueList.find(id => {return item.id === id});
+                        })
+                        this.$emit("change", linkBuild);
+                        this.dialog.building = false;
+                        this.multipleSelection = [];
+                   } else {
+                       this.$message.error(this.message);
+                   }
+               })
+            } else {
+                const linkBuild = this.buildingData.filter(item => {
+                    return this.valueList.find(id => {return item.id === id});
+                })
+                this.$emit("change", linkBuild);
+                this.dialog.building = false;
+                this.multipleSelection = [];
+            }
+        }
+    },
+    watch: {
+        dialog: {
+            deep: true,
+            handler() {
+                if (this.dialog.building) {
+                    this.getLinkBuildingData()
+                }
+            }
+        },
+        projectId() {
+            this.buildingData = [];
+            this.valueList = [];
+            this.getBuildingData();
+        }
+    }
+};
+</script>
+
+<style lang="less">
+</style>

+ 164 - 0
src/components/ledger/components/lib/cascader.vue

@@ -0,0 +1,164 @@
+<template>
+  <div id="cascaderMap">
+    <span class="buildFloor" style="margin-right: 12px;">设备类别</span>
+    <slot></slot>
+    <el-select v-model="value" placeholder="请选择" clearable :props="props" filterable
+               :style="isWidth ? '' : 'width:160px;'" size="small"
+               @change="changeVal">
+      <el-option v-for="item in options" :key="item.code" :label="item.facility" :value="item.code"></el-option>
+    </el-select>
+    <!-- <el-cascader placeholder="请选择" :options="options" v-model="value" :props="props" filterable :style="isWidth ? '' : 'width:160px;'" size="small"
+      @change="changeVal" change-on-select></el-cascader> -->
+  </div>
+</template>
+<script>
+import { mapGetters } from 'vuex';
+import { queryEquip } from "@/api/object/equip";
+import { queryPhysicsAllType } from "@/api/dict";
+
+export default {
+  name: "getCode",
+  props: {
+    isWidth: {
+      type: Boolean,
+      default: true
+    },
+    all: {
+      type: Boolean,
+      default: false,
+    }
+  },
+  computed: {
+    ...mapGetters("layout", ["projectId"])
+  },
+  data() {
+    return {
+      value: "",
+      options: [],
+      props: {
+        value: "code",
+        label: "facility"
+      },
+      falg: true,
+      content: []
+    };
+  },
+  created() {
+    this.getData();
+
+  },
+  watch: {
+    projectId() {
+      this.value = '';
+      this.getData();
+    }
+  },
+  methods: {
+    setValue(val) {
+      this.value = val
+    },
+    //修改val
+    changeVal(val) {
+      let value = {}
+      let flag = true;
+      this.options.map(item => {
+        if (item.code == val) {
+          flag = false;
+          value = item
+        }
+      })
+      this.value = val
+      if (flag) {
+        this.value = '';
+      }
+      console.log(value)
+      this.$emit("change", value)
+    },
+    //获取当前项目下的设备类型(只拿到编码-需要过滤)
+    getData(val) {
+      let param2 = {
+        distinct: true,
+        pageNumber: 1,
+        pageSize: 1000,
+        projection: [
+          "classCode"
+        ]
+      }
+      if (val && val[0] != 'all') {
+        param2.filters = ''
+        if (val[0] == "noKnow") {
+          param2.filters += `;buildingId isNull`;
+        } else if (val[0] && val[0] != "all") {
+          param2.filters += `;buildingId='${val[0]}'`;
+        }
+        if (val[1] == "noKnow") {
+          param2.filters += `;floorId isNull`;
+        } else if (val[1] && val[1] != "all") {
+          param2.filters += `;floorId='${val[1]}'`;
+        }
+        param2.filters = param2.filters.slice(1)
+      }
+      console.log(param2,'param2')
+
+      let param1 = {
+        type: "equipment"
+      }
+
+      let promise2 = new Promise((resolve, reject) => {
+        queryEquip(param2, res => {
+          resolve(res)
+        })
+      })
+      let promise1 = new Promise((resolve, reject) => {
+        queryPhysicsAllType(param1, res => {
+          resolve(res)
+        })
+      })
+
+      Promise.all([promise1, promise2]).then((res) => {
+        let allData = res[0], data = res[1]
+        this.options = this.formatOptions(allData.content)
+        if (!this.all) {
+          this.content = data.content.map(t => {
+            return t.classCode
+          });
+          this.filterForOptions();
+        }
+        if (this.value) {
+          this.changeVal(this.value)
+        }
+      })
+    },
+    //格式化options数据
+    formatOptions(arr) {
+      let data = [];
+      arr && arr.map(t => {
+        let temp = {};
+        temp.code = t.code;
+        temp.facility = t.name === t.aliasName ? t.name : `${t.name}(${t.aliasName})`;
+        data.push(temp)
+      })
+      return data;
+    },
+    //过滤
+    filterForOptions() {
+      this.options = this.options.filter(item => {
+        if (this.content.indexOf(item.code) > -1) {
+          return item
+        }
+      })
+    }
+  }
+};
+</script>
+<style lang="less" scoped>
+#cascaderMap {
+  float: left;
+  margin-left: 10px;
+
+  .buildFloor {
+    color: #999999;
+    font-size: 14px;
+  }
+}
+</style>

+ 415 - 0
src/components/ledger/components/lib/cenoteGraphy.vue

@@ -0,0 +1,415 @@
+<template>
+  <div id="cenoteGraphy" style="height: 100%; width: 100%" ref="graphy">
+    <div v-show="!floorMap">
+      <div
+        class="center"
+        style="height: 400px; padding-top: 182px; box-sizing: border-box"
+      >
+        <i class="icon-wushuju iconfont"></i>
+        暂无数据
+      </div>
+    </div>
+    <div class="canvas-box" v-show="floorMap" v-loading="canvasLoading">
+      <canvas
+        id="floorCanvas"
+        :width="canvasWidth"
+        :height="canvasHeight"
+        ref="canvas"
+        tabindex="0"
+      ></canvas>
+      <el-row class="canvas-actions-box">
+        <canvasFun
+          @scale="scale"
+          @groupSelect="groupSelect"
+          @fit="fit"
+          @savePng="savePng"
+          @saveSvg="saveSvg"
+          @saveJson="saveJson"
+          :config="config"
+          ref="canvasFun"
+        ></canvasFun>
+      </el-row>
+    </div>
+  </div>
+</template>
+
+<script>
+import canvasFun from "@/components/business_space/newGraphy/canvasFun";
+import { SColor, SPoint } from "@saga-web/draw/lib";
+import {
+  DivideFloorScene,
+  SpaceItem,
+  ZoneItem,
+  Opt,
+} from "@saga-web/cad-engine/lib";
+import { FloorView } from "@saga-web/cad-engine/lib/FloorView";
+import { colorArr } from "@/utils/spaceColor";
+import {
+  getFloorMsgByFloorID,
+  shaftSpaceQuery,
+  shaftZoneLinkReplace,
+  zoneQueryOutline,
+} from "@/api/scan/request";
+import { mapGetters } from "vuex";
+export default {
+  props: {
+    buildFloorName: String, //建筑楼层名称
+  },
+  data() {
+    return {
+      canvasLoading: false, //加载canvas
+      floorMap: "", //地图
+      canvasWidth: 800,
+      canvasHeight: 600,
+      view: null,
+      scene: null,
+      buildingData: [], //建筑,楼层
+      space: "", //当前空间类型
+      businessSpaceList: [], //所有业务空间
+      BSPRelaISPList: [], //已关联元空间的业务空间
+      zoneList: [], // 业务空间-canvas图中
+      selectAble: false,
+      relatedSpaceIdList: [], //已关联的业务空间id
+      config: {
+        isEdit: false,
+        groupSelect: true,
+      },
+      sceneMarkList: [],
+      isFirstFlag: true,
+    };
+  },
+  mounted() {
+    this.canvasWidth = this.$refs.graphy.offsetWidth;
+    this.canvasHeight = this.$refs.graphy.offsetHeight;
+    this.isFirstFlag = true;
+  },
+  components: {
+    canvasFun,
+  },
+  computed: {
+    ...mapGetters("layout", ["projectId", "projects"]),
+    projectName() {
+      let projectObj = this.projects.find((item) => {
+        return item.id == this.projectId;
+      });
+      return projectObj
+        ? projectObj.name
+          ? projectObj.name
+          : projectObj.id
+        : this.projectId;
+    },
+  },
+  created() {
+    Opt.sceneMarkColor = new SColor("#ffffff");
+  },
+  methods: {
+    //获取楼层map
+    getFloorMap(buildfloor, space) {
+      if (
+        buildfloor.length == 2 &&
+        space &&
+        buildfloor[0] &&
+        buildfloor[1] &&
+        buildfloor[1] !== "all" &&
+        buildfloor[1] !== "noKnow"
+      ) {
+        this.buildingData = buildfloor;
+        let pa = {
+          filters: `id='${this.buildingData[1]}'`,
+        };
+        getFloorMsgByFloorID(pa, (res) => {
+          this.floorMap = res.content[0].infos
+            ? res.content[0].infos.floorMap
+            : "";
+          this.space = space;
+          // 蒙版
+          if (res.content[0].outline) {
+            this.sceneMarkList = res.content[0].outline.map((t) => {
+              return new SPoint(t.X, t.Y);
+            });
+          }
+          this.getGraphy();
+        });
+      } else {
+        this.floorMap = "";
+      }
+    },
+    //加载
+    load() {
+      if (this.scene) {
+        this.scene.clearSpaceSelection();
+        this.scene.clearZoneSelection();
+      }
+      //加载底板
+      this.getGraphy();
+    },
+    //获取底图
+    getGraphy() {
+      let that = this;
+      that.clearGraphy();
+      if (!this.floorMap) return;
+      that.scene = new DivideFloorScene();
+      that.canvasLoading = true;
+      that.scene
+        .loadUrl(
+          `/image-service/common/file_get?systemId=revit&key=${this.floorMap}`
+        )
+        .then((res) => {
+          that.canvasLoading = false;
+          if (res == "error") {
+            this.floorMap = "";
+            console.log("数据解析异常");
+            return;
+          }
+          that.view.scene = that.scene;
+          if (that.sceneMarkList.length) {
+            that.scene.addSceneMark(that.sceneMarkList);
+          }
+          that.scene.isSpaceSelectable = false;
+          // 绘制业务空间
+          that.getBusinessSpace();
+          // if (this.isFirstFlag) {
+          //   that.fit();
+          //   that.view.minScale = that.view.scale;
+          //   if (that.$refs.canvasFun) {
+          //     that.$refs.canvasFun.everyScale = that.view.scale;
+          //   }
+          //   this.isFirstFlag = false;
+          // }
+          that.fit();
+          that.view.minScale = that.view.scale;
+          if (that.$refs.canvasFun) {
+            that.$refs.canvasFun.everyScale = that.view.scale;
+          }
+          this.isFirstFlag = false;
+        });
+    },
+    // 获取当前分区下的业务空间
+    getBusinessSpace() {
+      this.canvasLoading = true;
+      let promise1 = new Promise((resolve, reject) => {
+        let params = {
+          data: {
+            filters: `not id isNull;classCode='${this.space}';buildingId='${this.buildingData[0]}';floorId='${this.buildingData[1]}'`,
+            Orders: "localName desc",
+          },
+          ShaftId: this.$route.query.ShaftId,
+        };
+        shaftSpaceQuery(params, (res) => {
+          resolve(res);
+        });
+      });
+      let promise2 = new Promise((resolve, reject) => {
+        let pa = {
+          filters: `not id isNull`,
+          orders: "createTime desc, id asc",
+          pageSize: 1000,
+          zoneType: this.space,
+        };
+        if (this.buildingData.length && this.buildingData.length > 1) {
+          pa.BuildingId = this.buildingData[0];
+          pa.FloorId = this.buildingData[1];
+        }
+        zoneQueryOutline(pa, (res) => {
+          resolve(res);
+        });
+      });
+      Promise.all([promise1, promise2]).then((values) => {
+        let relatedZone = values[0].content;
+        let zoneData = values[1].content;
+        this.relatedSpaceIdList = [];
+        relatedZone.map((item) => {
+          this.relatedSpaceIdList.push(item.id);
+        });
+        // 所有业务空间
+        this.businessSpaceList = zoneData;
+        // 已关联元空间的业务空间
+        this.BSPRelaISPList = [];
+        zoneData.map((t) => {
+          if (t.outline && t.outline.length) {
+            this.BSPRelaISPList.push(t);
+          }
+        });
+        // 绘制业务空间
+        let tempArr = this.BSPRelaISPList.map((t, i) => {
+          if (t.floorId == this.buildingData[1] && t.classCode == this.space) {
+            return {
+              RoomLocalName: t.localName,
+              OutLine: t.outline,
+              RoomID: t.id,
+              Color: colorArr[i % colorArr.length],
+              HighLightFlag:
+                relatedZone.findIndex((item) => item.id == t.id) > -1,
+              Transparency:
+                relatedZone.findIndex((item) => item.id == t.id) > -1 ? 20 : 1,
+            };
+          } else {
+            return undefined;
+          }
+        }).filter((item) => item);
+        this.scene.removeAllZone();
+        this.scene.addZoneList(tempArr);
+        this.scene.click(this, this.canvasClick);
+        if (this.config.isEdit) {
+          this.scene.zoneList.map((t) => {
+            t.selected = t.highLightFlag;
+            t.transparency = 20;
+            t.highLightFlag = false;
+          });
+        }
+        this.canvasLoading = false;
+      });
+    },
+    // canvas点击事件
+    canvasClick(item, event) {
+      if (!this.config.isEdit) {
+        item.selected = false;
+      }
+    },
+    // 立面图选中空间对应平面图选中
+    canvasChecked(RoomID) {
+      this.scene.zoneList.map((zoneItem) => {
+        if (zoneItem.data.RoomID == RoomID) {
+          zoneItem.selected = true;
+        }
+      });
+    },
+    // 立面图取消选中对应平面图取消选中
+    canvasUncheck(RoomID) {
+      this.scene.zoneList.map((zoneItem) => {
+        if (zoneItem.data.RoomID == RoomID) {
+          zoneItem.selected = false;
+        }
+      });
+    },
+    //取消编辑
+    cancelEdit() {
+      this.config.isEdit = false;
+      if (this.scene) {
+        this.scene.clearZoneSelection();
+      }
+      this.getBusinessSpace();
+    },
+    //编辑
+    edit() {
+      this.config.isEdit = true;
+      this.scene.zoneList.map((t) => {
+        t.selected = t.highLightFlag;
+        t.transparency = 20;
+        t.highLightFlag = false;
+      });
+    },
+    //保存编辑
+    saveEdit() {
+      let arr = this.scene.getSelectedZone();
+      let param = {
+        buildingId: this.buildingData[0],
+        floorId: this.buildingData[1],
+        type: this.space,
+        data: {
+          shaftId: this.$route.query.ShaftId,
+          spaceIdList: [],
+        },
+      };
+      param.data.spaceIdList = arr.map((t) => {
+        return t.data.RoomID;
+      });
+      shaftZoneLinkReplace(param, (res) => {
+        this.config.isEdit = false;
+        this.$message.success("保存成功");
+        this.getBusinessSpace();
+      });
+    },
+    // 清除canvas
+    clearGraphy() {
+      // debugger
+      this.scene = null;
+      if (this.view) {
+        this.view.scene = null;
+        // return
+      }
+      this.view = null;
+      this.view = new FloorView("floorCanvas");
+      this.view.connect("leftMove", this, function () {});
+    },
+    // 适配底图到窗口
+    fit() {
+      this.view.DragMove = true;
+      if (this.scene.sceneMark) {
+        this.view.fitItemToView([this.scene.sceneMark]);
+        return;
+      }
+      this.view.fitSceneToView();
+    },
+    // 缩放
+    scale(val) {
+      if (!this.view) {
+        return;
+      }
+      let scale = this.view.scale;
+      this.view.scaleByPoint(
+        val / scale,
+        this.canvasWidth / 2,
+        this.canvasHeight / 2
+      );
+    },
+    // 保存为png
+    savePng() {
+      this.view.saveImage(
+        `${this.projectName}-${this.buildFloorName}.png`,
+        "png"
+      );
+    },
+    // 保存为svg
+    saveSvg() {
+      this.view.saveSceneSvg(
+        `${this.projectName}-${this.buildFloorName}.svg`,
+        6400,
+        4800
+      );
+    },
+    // 框选
+    groupSelect() {
+      this.scene.isRectSelection = 2;
+    },
+    // 保存json
+    saveJson() {
+      this.view.saveFloorJson(
+        `${this.projectName}-${this.buildFloorName}.json`
+      );
+    },
+  },
+  watch: {
+    "view.scale": {
+      handler(n) {
+        if (this.$refs.canvasFun) {
+          let s = (n * 10) / this.view.minScale;
+          this.$refs.canvasFun.sliderVal = s > 1000 ? 1000 : s;
+        }
+      },
+    },
+    "scene.isRectSelection": {
+      handler(n) {
+        if (!n) {
+          this.$refs.canvasFun.active = "";
+        }
+      },
+    },
+  },
+};
+</script>
+
+<style lang="less" scoped>
+.canvas-actions-box {
+  position: absolute;
+  bottom: 20px;
+  left: 50%;
+  width: 352px;
+  transform: translateX(-50%);
+  z-index: 999;
+}
+.canvas-box {
+  width: 100%;
+  height: 100%;
+}
+</style>

+ 77 - 0
src/components/ledger/components/lib/cenoteType.vue

@@ -0,0 +1,77 @@
+<template>
+  <div id="cascaderMap">
+    <span class="buildFloor" style="margin-right: 12px;">竖井类型</span>
+    <el-select v-model="id" @change="changeVal">
+    <el-option
+      v-for="item in options"
+      :key="item.id"
+      :label="item.name"
+      :value="item.id">
+    </el-option>
+  </el-select>
+  </div>
+</template>
+<script>
+import { mapGetters } from "vuex";
+import { getCenoteType } from "@/api/scan/request";
+export default {
+  name: "getCode",
+  computed: {
+    ...mapGetters("layout", ["projectId", "secret", "userId"])
+  },
+  data() {
+    return {
+      id: "",
+      options: []
+    };
+  },
+  created() {
+    this.getData()
+  },
+  watch: {
+    projectId() {
+      this.getData()
+      if (this.options.length) {
+        this.changeVal(this.options[0].id)
+      }
+    }
+  },
+  methods: {
+    //修改val
+    changeVal(val) {
+      let value = {};
+      this.options.map(item => {
+        if (item.id == val) {
+          value = item;
+        }
+      });
+      this.id = val;
+      this.$emit("change", value);
+    },
+    async getData() {
+      let param = {
+        projectId: this.projectId
+      }
+      await getCenoteType (param, res => {
+        let data = res.content;
+        data.unshift({
+          id: "",
+          name: "全部"
+        });
+        this.options = data;
+        this.id = "";
+      })
+    }
+  }
+};
+</script>
+<style lang="less" scoped>
+#cascaderMap {
+  float: left;
+  margin-left: 10px;
+  .buildFloor {
+    color: #999999;
+    font-size: 14px;
+  }
+}
+</style>

+ 82 - 0
src/components/ledger/components/lib/detailsDia.vue

@@ -0,0 +1,82 @@
+<template>
+  <el-dialog
+    :title="title"
+    append-to-body
+    class="iframe"
+    :visible.sync="dialog.details"
+    width="500px"
+  >
+    <iframe
+      id="google_ads_frame2"
+      name="google_ads_frame2"
+      frameborder="0"
+      width="100%"
+      height="600px"
+      :src="iframeSrc"
+      marginwidth="0"
+      marginheight="0"
+      vspace="0"
+      hspace="0"
+      allowtransparency="true"
+      scrolling="no"
+      allowfullscreen="true"
+    ></iframe>
+  </el-dialog>
+</template>
+
+<script>
+export default {
+  props: {
+    iframeSrc: {
+      type: String,
+      default: ""
+    },
+    title: {
+      type: String,
+      default: "详情"
+    },
+    dialog: {
+      type: Object,
+      default: function () {
+        return {
+          details: false
+        }
+      }
+    },
+    setData: {
+      type: [Array, String],
+      default: function () {
+        return []
+      }
+    }
+  },
+  methods: {
+    setMess() {
+      this.$nextTick(_ => {
+        let iframe = document.getElementById("google_ads_frame2")
+        iframe.onload = () => {
+          iframe.contentWindow.postMessage(this.setData, "*")
+        }
+      })
+    }
+  },
+  watch: {
+    dialog: {
+      deep: true,
+      handler: function () {
+        if (this.dialog.details) {
+          this.setMess()
+        }
+      }
+    }
+  }
+}
+</script>
+<style scoped>
+#google_ads_frame2{
+    height: 350px;
+    overflow: auto;
+}
+</style>
+
+

+ 271 - 0
src/components/ledger/components/lib/dynamicDialog.vue

@@ -0,0 +1,271 @@
+<template>
+    <el-dialog title="深化点表导入" :visible.sync="dialogFormVisible" class="dialogFormVisible" @close="handleCloseDialog">
+        <div v-show="active == 1">
+            <h5 class="title">1、需要在深化点表中插入1列,表头为“反馈信息”,此列请不要填充内容。</h5>
+            <h5 class="title">2、深化点表中需含“本地编码”、“信息点编码”、“表号”、“功能号”、“反馈信息”五列,对五列的具体位置和顺序不做要求。</h5>
+            <h5 class="title">
+                3、本地编码不能为空:系统从上到下逐行识别信息,若遇到本地编码的单元格为空时,系统将判定已完成信息识别,不再向下继续识别。
+            </h5>
+            <h5 class="title">
+                4、上传深化点表结束后,请在弹窗左下角点击“下载导入结果”,下载后查看表格“反馈信息”列,若上传失败,则根据失败原因在反馈列显示“本地编码重复”、“信息点编码不能为空”、“表号不能为空”、“功能号不能为空”等。
+            </h5>
+            <el-upload
+                class="upload-demo"
+                drag
+                :action="`${baseUrl}${dataCenter}/graphic/import/point`"
+                accept=".xlsx"
+                :show-file-list="false"
+                :headers="headers"
+                name="file"
+                multiple
+                :on-progress="progress"
+                :on-success="success"
+                :on-error="error"
+            >
+                <i class="el-icon-upload"></i>
+                <div class="el-upload__text">将Excel文件拖到此处,或<em>点击上传Excel文件</em></div>
+                <div class="el-upload__tip tips" slot="tip">
+                    上传的Excel数据将完全覆盖当前设备信息点(原有数据不会保留)
+                    <el-button style="float: right" v-if="historyKey" type="primary" @click="download(historyKey)">下载上一次导入结果</el-button>
+                </div>
+            </el-upload>
+        </div>
+        <div v-show="active == 2">
+            <div class="block">
+                <el-timeline>
+                    <el-timeline-item
+                        v-for="(activity, index) in activities"
+                        :key="index"
+                        :icon="activity.icon"
+                        :type="activity.type"
+                        :color="activity.color"
+                        :size="activity.size"
+                    >
+                        {{ activity.content }}
+                    </el-timeline-item>
+                </el-timeline>
+            </div>
+            <el-button :disabled="!messageKey" @click="download(messageKey)">下载导入结果</el-button>
+            <footer slot="footer" style="text-align: right" class="dialog-footer">
+                <el-button @click="active = 1">返回</el-button>
+                <el-button type="primary" @click="dialogFormVisible = false">确认</el-button>
+            </footer>
+        </div>
+    </el-dialog>
+</template>
+
+<script>
+import storage from "@/framework/utils/storage";
+import { baseUrl, dataCenter } from "@/api/scan/config.js";
+import { queryDownloadsKey } from "@/api/scan/request";
+import { mapGetters } from "vuex";
+
+export default {
+    data() {
+        return {
+            active: 1,
+            dialogFormVisible: false,
+            messageKey: "",
+            historyKey: "",
+            activities: [],
+            baseUrl: baseUrl,
+            dataCenter: dataCenter,
+        };
+    },
+    computed: {
+        ...mapGetters("layout", ["projectId"]),
+        headers() {
+            return {
+                //头信息中的 ProjectId 改为 projectId    nh-2021.11.25
+                projectId: this.projectId,
+                Comming: "adm",
+                Account: storage.get("user_name"),
+            };
+        },
+    },
+    methods: {
+        openDialog() {
+            this.dialogFormVisible = true;
+        },
+        /**
+         * 获取导入历史key
+         */
+        getHistoryKey() {
+            const params = {
+                filters: "type = 'iot'",
+                orders: "createTime desc",
+                pageSize: 1,
+            };
+            queryDownloadsKey(params, (res) => {
+                if (res.result === "success" && res.content && res.content.length && res.content[0].key) {
+                    this.historyKey = res.content[0].key;
+                } else {
+                    this.historyKey = "";
+                }
+            });
+        },
+        progress(event, file, fileList) {
+            this.active = 2;
+            this.activities = [
+                {
+                    content: "数据文件处理中..",
+                    size: "large",
+                    type: "primary",
+                    icon: "el-icon-check",
+                    color: "#0bbd87",
+                },
+            ];
+        },
+        success(event, file, fileList) {
+            if (event.result == "success") {
+                this.messageKey = event.message;
+                if (event.content && event.content[0] && event.content[0].state === "0") {
+                    // 全部导入成功
+                    this.activities = [
+                        {
+                            content: "数据文件处理中..",
+                            size: "large",
+                            type: "primary",
+                            icon: "el-icon-check",
+                            color: "#0bbd87",
+                        },
+                        {
+                            content: `全部导入成功(导入信息点:${event.content[0].successCount} 条)`,
+                            color: "#0bbd87",
+                            icon: "el-icon-check",
+                            size: "large",
+                            type: "success",
+                        },
+                    ];
+                    // 导入成功刷新台账数据
+                    this.$emit("upDataDevice");
+                } else if (event.content && event.content[0] && event.content[0].state === "1") {
+                    // 部分导入成功
+                    this.activities = [
+                        {
+                            content: "数据文件处理中..",
+                            size: "large",
+                            type: "primary",
+                            icon: "el-icon-check",
+                            color: "#0bbd87",
+                        },
+                        {
+                            content: `部分导入成功(导入成功:${event.content[0].successCount} 条;导入失败:${event.content[0].failCount} 条)`,
+                            color: "#0bbd87",
+                            icon: "el-icon-check",
+                            size: "large",
+                            type: "success",
+                        },
+                    ];
+                    // 导入成功刷新台账数据
+                    this.$emit("upDataDevice");
+                } else {
+                    // 导入失败
+                    this.activities = [
+                        {
+                            content: "数据文件处理中..",
+                            size: "large",
+                            type: "primary",
+                            icon: "el-icon-check",
+                            color: "#0bbd87",
+                        },
+                        {
+                            content: "导入失败!",
+                            color: "#f56c6c",
+                            icon: "el-icon-close",
+                            size: "large",
+                            type: "primary",
+                        },
+                    ];
+                }
+            } else {
+                this.messageKey = "";
+                if (event.result === "failure" && event.message) {
+                    this.activities = [
+                        {
+                            content: "数据文件处理中..",
+                            size: "large",
+                            type: "primary",
+                            icon: "el-icon-check",
+                            color: "#0bbd87",
+                        },
+                        {
+                            content: `导入失败!(${event.message})`,
+                            color: "#f56c6c",
+                            icon: "el-icon-close",
+                            size: "large",
+                            type: "primary",
+                        },
+                    ];
+                } else {
+                    this.activities = [
+                        {
+                            content: "数据文件处理中..",
+                            size: "large",
+                            type: "primary",
+                            icon: "el-icon-check",
+                            color: "#0bbd87",
+                        },
+                        {
+                            content: "导入失败!",
+                            color: "#f56c6c",
+                            icon: "el-icon-close",
+                            size: "large",
+                            type: "primary",
+                        },
+                    ];
+                }
+            }
+        },
+        error(event, file, fileList) {},
+        download(key) {
+            const url = `${baseUrl}${dataCenter}/graphic/downloads/point?key=` + key;
+            let a = document.createElement("a");
+            a.href = url;
+            a.download = `${key}.xlsx`;
+            a.click();
+        },
+        handleCloseDialog() {
+            this.messageKey = "";
+            this.historyKey = "";
+            this.active = 1;
+            this.activities = [];
+        },
+    },
+    watch: {
+        dialogFormVisible(n, o) {
+            // 打开弹窗获取历史导入key值
+            if (n) this.getHistoryKey();
+        },
+    },
+};
+</script>
+
+<style scoped lang="less">
+.dialogFormVisible {
+    .time {
+        margin: 10px 0;
+        font-size: 15px;
+
+        .text {
+            margin-right: 10px;
+            font-size: 14px;
+        }
+    }
+
+    .tips {
+        color: red;
+        font-size: 14px;
+        font-weight: bold;
+    }
+
+    /deep/ .el-upload {
+        width: 100%;
+    }
+
+    /deep/ .el-upload-dragger {
+        margin: 0 auto;
+        width: 100%;
+    }
+}
+</style>

+ 127 - 0
src/components/ledger/components/lib/editSysFloor.vue

@@ -0,0 +1,127 @@
+<template>
+  <el-dialog title="所属建筑楼层" :visible.sync="connectDialogVis" width="50%" id="messageDialog">
+    <div class="cascader-row">
+      <div style="line-height:32px;">添加{{ system.SysLocalName }}所属建筑楼层 :</div>
+      <div style="width:70%">
+        <bfCascader ref="bfCascader" :SysID="system.SysID"></bfCascader>
+      </div>
+    </div>
+
+
+    <span slot="footer" class="dialog-footer">
+      <el-button size="small" @click="connectDialogVis=false">取消</el-button>
+      <el-button size="small" type="primary" @click="save">确认</el-button>
+    </span>
+  </el-dialog>
+</template>
+<script>
+import bfCascader from './buildfloorCascader'
+import { sysRelateBuild, sysRelateFloor } from "@/api/scan/request";
+
+export default {
+  data() {
+    return {
+      buildName: '',
+      connectDialogVis: false,
+      system: {}
+    }
+  },
+  components: {
+    bfCascader
+  },
+  props: {
+    isCreate: {
+      default: false
+    }
+  },
+  methods: {
+    showDialog(system) {
+      this.system = system;
+      this.connectDialogVis = true;
+      this.$nextTick(() => {
+        this.$refs.bfCascader.getCascader()
+        let arr = this.system.buildingFloorInfoList || [];
+        let value = []
+        if (arr.length) {
+          arr.map(t => {
+            if (t.FloorID) {
+              value.push([t.BuildID, t.FloorID]);
+            } else {
+              value.push([t.BuildID])
+            }
+          })
+        }
+        this.$refs.bfCascader.value = value
+        console.log(value)
+      })
+    },
+    save() {
+      // 如果是创建
+      if (this.isCreate) {
+        let data = this.$refs.bfCascader.getSelectedNodes();
+        this.connectDialogVis = false;
+        this.$emit('relateSuccess', data);
+        return
+      }
+      let arr = this.$refs.bfCascader.value;
+      let buildIds = [], floorIds = [];
+      arr.map(t => {
+        if (buildIds.indexOf(t[0]) < 0) {
+          buildIds.push(t[0]);
+        }
+        if (t.length > 1 && floorIds.indexOf(t[1]) < 0) {
+          floorIds.push(t[1]);
+        }
+      })
+      let buildPa = {
+        sysId: this.system.id,
+        buildingIdList: buildIds
+      }
+      let floorPa = {
+        sysId: this.system.id,
+        floorIdList: floorIds
+      }
+      let promise1 = new Promise((resolve, reject) => {
+        sysRelateBuild(buildPa, res => {
+          resolve(res);
+        })
+      })
+      let primise2 = new Promise((resolve, reject) => {
+        sysRelateFloor(floorPa, res => {
+          resolve(res);
+        })
+      })
+      let buildFloor = this.$refs.bfCascader.getSelectedNodes();
+      Promise.all([promise1, primise2]).then(res => {
+        this.connectDialogVis = false;
+        this.$message.success("关联成功");
+        this.$emit('relateSuccess', buildFloor);
+      })
+    }
+  }
+}
+</script>
+<style lang="less" scoped>
+#messageDialog {
+  .cascader-row {
+    max-height: 200px;
+    overflow: auto;
+  }
+
+  /*.el-row {*/
+  /*  !*height: 50px;*!*/
+  /*  max-height: 200px;*/
+  /*  overflow-y: auto;*/
+  /*  overflow-x: hidden;*/
+  /*}*/
+  /*.el-row > div {*/
+  /*  float: left;*/
+  /*}*/
+  /*.el-row > div + div {*/
+  /*  margin-left: 10px;*/
+  /*}*/
+  /*/deep/ .el-input__inner {*/
+  /*  vertical-align: baseline;*/
+  /*}*/
+}
+</style>

+ 142 - 0
src/components/ledger/components/lib/equipCascader.vue

@@ -0,0 +1,142 @@
+<template>
+    <div id="cascaderMap">
+        <span class="buildFloor" style="margin-right: 12px">设备类别</span>
+        <el-cascader
+            ref="cascader"
+            v-model="value"
+            placeholder="请选择"
+            filterable
+            clearable
+            :props="props"
+            :options="options"
+            :style="isWidth ? '' : 'width:160px;'"
+            size="small"
+            @change="changeVal"
+            popper-class="equip-cascader"
+        >
+        </el-cascader>
+    </div>
+</template>
+<script>
+import { mapGetters } from "vuex";
+import { getEquipLkSys } from "@/api/dict";
+
+export default {
+    name: "getCode",
+    props: {
+        isWidth: {
+            type: Boolean,
+            default: true,
+        },
+        all: {
+            type: Boolean,
+            default: false,
+        },
+    },
+    computed: {
+        ...mapGetters("layout", ["projectId"]),
+    },
+    data() {
+        return {
+            value: [],
+            options: [],
+            props: {
+                value: "code",
+                label: "name",
+                children: "children",
+            },
+        };
+    },
+    created() {
+        this.getData();
+    },
+    watch: {
+        projectId() {
+            this.value = "";
+            this.getData();
+        },
+    },
+    methods: {
+        setValue(val) {
+            let flag = true;
+            this.options.forEach((sys) => {
+                sys.children.forEach((eq) => {
+                    if (eq.code === val) {
+                        flag = false;
+                        this.value = [sys.code, eq.code];
+                    }
+                });
+            });
+            // 如果未找到对应的设备类,则置空
+            if (flag) this.value = [];
+        },
+        //修改val
+        changeVal(val) {
+            let value = {};
+            if (val && val[1]) {
+                this.options.forEach((sys) => {
+                    sys.children.forEach((eq) => {
+                        if (eq.code === val[1]) {
+                            value.facility = eq.name;
+                            value.code = eq.code;
+                        }
+                    });
+                });
+            }
+            this.$emit("change", value);
+        },
+        //获取当前项目下的设备类型(只拿到编码-需要过滤)
+        getData(val) {
+            let params = {
+                distinct: true,
+                pageNumber: 1,
+                pageSize: 1000,
+                projection: ["classCode"],
+            };
+            if (val && val[0] != "all") {
+                params.filters = "";
+                if (val[0] == "noKnow") {
+                    params.filters += `;buildingId isNull`;
+                } else if (val[0] && val[0] != "all") {
+                    params.filters += `;buildingId='${val[0]}'`;
+                }
+                if (val[1] == "noKnow") {
+                    params.filters += `;floorId isNull`;
+                } else if (val[1] && val[1] != "all") {
+                    params.filters += `;floorId='${val[1]}'`;
+                }
+                params.filters = params.filters.slice(1);
+            }
+            getEquipLkSys(params, (res) => {
+                if (res.content) {
+                    this.options = res.content;
+                } else {
+                    this.options = [];
+                }
+            });
+        },
+    },
+};
+</script>
+<style lang="less">
+.equip-cascader {
+    .el-cascader-menu__wrap {
+        height: 550px;
+    }
+}
+</style>
+<style lang="less" scoped>
+#cascaderMap {
+    float: left;
+    margin-left: 10px;
+
+    .buildFloor {
+        color: #999999;
+        font-size: 14px;
+    }
+
+    /deep/ .el-scrollbar__wrap {
+        height: 563px;
+    }
+}
+</style>

+ 159 - 0
src/components/ledger/components/lib/floorCascader.vue

@@ -0,0 +1,159 @@
+<template>
+    <div id="buildFloor">
+        <span class="buildFloor">建筑楼层</span>
+        <el-cascader ref="floorCascader" placeholder="请选择建筑楼层" :options="options" v-model="value" filterable size="small" :style="isWidth ? '' : 'width:160px;'" @change="changeCascader"></el-cascader>
+    </div>
+</template>
+<script>
+    import tools from "@/utils/scan/tools";
+    import { buildingQuery } from '@/api/object/build';
+    import { floorQuery } from '@/api/object/floor';
+    import { mapGetters, mapActions } from "vuex";
+
+    export default {
+        props: {
+            isWidth: {
+                type: Boolean,
+                default: true
+            },
+            type: {
+                type: String,
+                default: "yes"
+            }
+        },
+        computed: {
+            ...mapGetters("layout", [
+                "projectId",
+                "secret",
+                "userId"
+            ])
+        },
+        data() {
+            return {
+                options: [],
+                param: {
+                    ProjId: '',
+                    secret: ''
+                },
+                value: ['all']
+            };
+        },
+        created() {
+            this.param.ProjId = this.projectId
+            this.param.secret = this.secret
+            this.getData()
+        },
+        watch: {
+            projectId() {
+                this.value = ['all'];
+                this.options = [];
+                this.param.ProjId = this.projectId
+                this.param.secret = this.secret
+                this.getData()
+                this.changeCascader(['all'])
+            }
+        },
+        methods: {
+            //设置默认选项
+            setValue(val) {
+                if (val && val.length) {
+                    this.value = val
+                }
+            },
+            //获取数据
+            getData() {
+                let data, buildParams = {
+                    pageNumber: 1,
+                    pageSize: 1000,
+                    orders: "localName asc",
+                    projection: [
+                        "id",
+                        "localName"
+                    ]
+                }, floorParams = {
+                    orders: "floorSequenceID desc",
+                    pageNumber: 1,
+                    pageSize: 1000
+                }
+                let promise1 = new Promise((resolve, reject) => {
+                    buildingQuery(buildParams, res => {
+                        resolve(res)
+                    })
+                })
+                let promise2 = new Promise((resolve, reject) => {
+                    floorQuery(floorParams, res => {
+                        resolve(res)
+                    })
+                })
+                Promise.all([promise1, promise2]).then(values => {
+                    let builData = values[0].content, floorData = values[1].content
+                    data = builData.map(build => {
+                        return {
+                            value: build.id,
+                            label: build.localName
+                        }
+                    })
+                    data.unshift({
+                        value: "all",
+                        label: "全部"
+                    }, {
+                        value: "noKnow",
+                        label: "未明确建筑"
+                    })
+                    data.forEach(build => {
+                        floorData.forEach(floor => {
+                            if (build.value == floor.buildingId && floor.id && floor.localName && this.type == "yes") {
+                                if (build.children) {
+                                    build.children.push({
+                                        value: floor.id,
+                                        label: floor.localName,
+                                        FloorSequenceID: floor.floorSequenceID,
+                                        infos: floor.infos || {},
+                                        outline: floor.outline || null,
+                                    })
+                                } else {
+                                    build.children = []
+                                    build.children.push({
+                                        value: "all",
+                                        label: "全部"
+                                    },{
+                                        value: 'noKnow',
+                                        label: "未明确楼层"
+                                    },{
+                                        value: floor.id,
+                                        label: floor.localName,
+                                        FloorSequenceID: floor.floorSequenceID,
+                                        infos: floor.infos || {},
+                                        outline: floor.outline || null,
+                                    })
+                                }
+                            }
+                        })
+                    })
+                    this.options = data
+                })
+            },
+            //改变item
+            changeCascader(value) {
+                let node = this.$refs.floorCascader.getCheckedNodes();
+                node && this.$emit("change", value, node[0].data);
+            }
+        }
+    };
+</script>
+<style lang="less">
+    .el-cascader .el-input .el-input__inner {
+        vertical-align: bottom;
+    }
+</style>
+<style lang="less" scoped>
+    #buildFloor {
+        margin-left: 10px;
+        float: left;
+    }
+    .buildFloor {
+        color: #999999;
+        font-size: 14px;
+        margin-right: 12px;
+    }
+</style>

+ 315 - 0
src/components/ledger/components/lib/formInput.vue

@@ -0,0 +1,315 @@
+<!--
+A1	手工填写-单个-数字-无单位
+A2	手工填写-单个-数字-有单位
+A3	手工填写-多个-数字-无单位
+A4	手工填写-多个-数字-有单位
+A5	手工填写-单个-数字范围-无单位
+A6	手工填写-单个-数字范围-有单位
+A7	手工填写-多个-数字范围-无单位
+A8	手工填写-多个-数字范围-有单位
+B1	手工填写-单个-文本
+B2	手工填写-多个-文本
+C1	手工填写-单个-日期时间值
+C2	手工填写-单个-日期时间段
+C3	手工填写-多个-日期时间值
+C4	手工填写-多个-日期时间段
+C5	手工填写-单个-日期值
+C6	手工填写-单个-日期段
+C7	手工填写-多个-日期值
+C8	手工填写-多个-日期段
+C9	手工填写-单个-时间值
+C10	手工填写-单个-时间段
+C11	手工填写-多个-时间值
+C12	手工填写-多个-时间段
+D1	字典选择-单个-单选
+D2	字典选择-单个-多选
+D3	字典选择-多个-单选
+D4	字典选择-多个-多选
+E1	字典布尔选择-单个
+E2	字典布尔选择-多个
+F1	上传-单个文件 -->
+
+<template>
+  <el-form
+    :label-position="'right'"
+    :labelWidth="width + 'px'"
+    :model="formLabelAlign"
+    ref="form"
+    @submit.native.prevent
+  >
+    <el-form-item :label="label" :rules="isRule ? { required: true, message: '不能为空'} : {}">
+      <!-- 普通输入类型 -->
+      <el-input
+        size="small"
+        v-if="!isShow && (type == 'default' || type == 'B1')"
+        v-model="formLabelAlign.name"
+        style="width: 9rem;"
+        @change="onSubmit"
+        @keyup.enter.native="onSubmit"
+      >
+        <template slot="append" v-if="unit">{{unit}}</template>
+      </el-input>
+      <el-input
+        type="number"
+        v-if="!isShow && (type == 'A1' || type == 'A2')"
+        v-model="formLabelAlign.name"
+        style="width: 9rem;"
+        @change="onSubmit"
+        @keyup.enter.native="onSubmit"
+      >
+        <template slot="append" v-if="unit">{{unit}}</template>
+      </el-input>
+      <!-- date类型 -->
+      <el-date-picker
+        v-if="!isShow && (type == 'year' || type == 'C5')"
+        v-model="formLabelAlign.name"
+        type="date"
+        value-format="yyyy-MM-dd"
+        @change="onSubmit"
+        :clearable="false"
+        placeholder="选择日期"
+      ></el-date-picker>
+      <!-- 级联选择 -->
+      <el-cascader
+        v-if="!isShow && (type == 'cascader' || type == 'D1')"
+        :options="typeArr"
+        v-model="formLabelAlign.name"
+        @change="onSubmit"
+        :props="props"
+      ></el-cascader>
+      <!-- 日期到分 -->
+      <el-date-picker
+        v-if="!isShow && type == 'C1'"
+        v-model="formLabelAlign.name"
+        type="datetime"
+        value-format="yyyy-MM-dd HH:MM"
+        @change="onSubmit"
+        :clearable="false"
+        placeholder="选择日期"
+      ></el-date-picker>
+      <!-- 输入文本框 -->
+      <el-input
+        v-if="!isShow && type == 'B2'"
+        type="textarea"
+        :rows="2"
+        @change="onSubmit"
+        @keyup.enter.native="onSubmit"
+        placeholder="请输入内容"
+        v-model="formLabelAlign.name"
+      ></el-input>
+      <!-- 日期 -->
+      <el-date-picker
+        v-if="!isShow && type == 'C6'"
+        v-model="formLabelAlign.name"
+        type="datetimerange"
+        range-separator="至"
+        start-placeholder="开始日期"
+        end-placeholder="结束日期"
+        value-format="yyyy-MM-dd HH:MM"
+        @change="onSubmit"
+        :clearable="false"
+        placeholder="选择日期"
+      ></el-date-picker>
+      <!-- 点击确定 -->
+      <i
+        v-if="!isShow  && (type == 'default' || type == 'B1')"
+        class="el-input__icon el-icon-check hover"
+        @click="onSubmit"
+      ></i>
+      <!-- 显示基本内容 -->
+      <span v-if="isShow" @click="changeItem" class="hover">
+        {{ filterArr(formLabelAlign.name) }} {{unit}}
+        <i class="el-icon-edit" v-if="type != 'X'"></i>
+      </span>
+      <slot name="mess"></slot>
+    </el-form-item>
+  </el-form>
+</template>
+
+<script>
+export default {
+  name: "ownerInput",
+  props: {
+    type: {
+      type: String,
+      default: "default"
+    }, //类型
+    value: [String, Number, Array], //value值
+    label: String, //label值,从父级传入
+    isRule: Boolean, //是否需要规则
+    keys: [String, Number], //
+    typeArr: [Array, String], //当其为级联或者下拉时传入
+    unit: {
+      //单位
+      type: String,
+      default: ""
+    },
+    width: {
+      type: Number,
+      default: 100
+    }
+  },
+
+  data() {
+    return {
+      formLabelAlign: {
+        name: ""
+      },
+      key: "",
+      isShow: true,
+      props: {
+        label: "name",
+        value: "code",
+        children: "content"
+      } //修改默认数据格式
+    };
+  },
+
+  methods: {
+    //点击确定或者url
+    onSubmit() {
+      if (this.formLabelAlign.name == "" || this.formLabelAlign.name == []) {
+        this.$message.error("请确定值不为空");
+      } else {
+        this.isShow = true;
+        if (this.type == 'cascader' || this.type == 'D1') {
+          let data = this.formLabelAlign.name
+          this.$emit("change", data[data.length - 1], this.keys);
+        } else {
+          this.$emit("change", this.formLabelAlign.name, this.keys);
+        }
+      }
+    },
+
+    //点击文案出现输入
+    changeItem() {
+      if (this.type == "X") {
+        this.$message('该信息点不支持编辑')
+        return;
+      } else {
+        this.isShow = false;
+      }
+    },
+
+    //对数组中的空数组去除
+    toMyNeed(arr) {
+      return arr.map(res => {
+        let param = {};
+        if (res.content && res.content.length != 0) {
+          param.content = this.toMyNeed(res.content);
+        }
+        param.name = res.name;
+        param.code = res.code;
+        return param;
+      });
+    },
+
+    //获取级联选中的值
+    getCascaderObj(val, opt) {
+      let data = this.getMyVal(val, opt, 'name')
+      data.length > 1 ? data = data.join('/') : data = data.join('')
+      return data
+    },
+
+    getMyVal(val, content, code) {
+      let data = []
+      if (content && content.length) {
+        for (let i = 0; i < content.length; i++) {
+          if (content[i].code == val) {
+            data.push(content[i][code])
+            break
+          } else {
+            if (content[i].content && content.length) {
+              for (let j = 0; j < content[i].content.length; j++) {
+                if (content[i].content[j].code == val) {
+                  data.push(content[i][code])
+                  data.push(content[i].content[j][code])
+                  break
+                } else {
+                  if (content[i].content[j].content && content[i].content[j].content.length) {
+                    for (let k = 0; k < content[i].content[j].content.length; k++) {
+                      if (content[i].content[j].content[k].code == val) {
+                        data.push(content[i][code])
+                        data.push(content[i].content[j][code])
+                        data.push(content[i].content[j].content[k][code])
+                        break
+                      } else {
+                      }
+                    }
+                  }
+                }
+              }
+            }
+          }
+        }
+      }
+      if (!data.length) {
+        data = null
+      }
+      return data
+    },
+
+    getMap(val, opt) {
+      return opt.map(function (value, index, array) {
+        for (var itm of opt) {
+          if (itm.code == value.code) {
+            opt = itm.content;
+            return itm;
+          }
+        }
+        return null;
+      });
+    },
+
+    //数组过滤
+    filterArr(val) {
+      let value = ""; //最后输出的文案
+      let isNeedType = this.type == 'cascader' || this.type == 'D1'
+      if (this.type == 'C6') {
+        value = value != "" ? val[0] + '至' + val[1] : "--"
+      } else if (this.type == 'cascader' || this.type == 'D1') {
+        if (val && val.length) {
+          value = this.getCascaderObj(val[val.length - 1], this.typeArr, 'name') || "--";
+        }
+      } else {
+        value = val || "--";
+      }
+      return value;
+    }
+  },
+
+  created() {
+    if (typeof (this.typeArr) == Object) {
+      this.typeArr = this.toMyNeed(this.typeArr)
+    }
+    if (this.type == 'cascader' || this.type == 'D1') {
+      if (this.value == '' || this.value == undefined) {
+        this.formLabelAlign.name = []
+      } else {
+        this.formLabelAlign.name = this.getMyVal(this.value, this.typeArr, 'code');
+      }
+    } else {
+      this.formLabelAlign.name = this.value;
+    }
+    this.key = this.label;
+  },
+
+  watch: {
+    label() { }
+  }
+};
+</script>
+
+<style lang="less">
+.hover:hover {
+  cursor: pointer;
+  color: #409eff;
+}
+input::-webkit-outer-spin-button,
+input::-webkit-inner-spin-button {
+  -webkit-appearance: none;
+}
+input[type="number"] {
+  -moz-appearance: textfield;
+}
+</style>

+ 326 - 0
src/components/ledger/components/lib/linkassets.vue

@@ -0,0 +1,326 @@
+<template>
+  <el-dialog title="批量关联资产" :visible.sync="dialog.linkAssets" @open="handleOpenReset" width="700px">
+    <el-row>
+      <span class="condition-title">设备类型:</span>
+      <el-select v-model="deviceType" filterable size="small" @change="handleChangeDevice" style="width:350px;">
+        <el-option v-for="item in deviceTypeList" :key="item.classCode" :label="item.name"
+                   :value="item.classCode"></el-option>
+      </el-select>
+    </el-row>
+    <el-row style="margin-top:30px;">
+      <span class="condition-title">建筑楼层:</span>
+      <el-cascader :options="bfoptions" v-model="buildFloor" filterable size="small" @change="handleChangeBf"
+                   style="width:350px;"></el-cascader>
+    </el-row>
+    <el-row style="margin-top:30px;" v-show="spaceShow">
+      <span class="condition-title">分区类型:</span>
+      <el-select v-model="zone" filterable size="small" @change="handleChangeZone" style="width:350px;">
+        <el-option v-for="item in zoneList" :key="item.code" :label="item.name" :value="item.code"></el-option>
+      </el-select>
+    </el-row>
+    <el-row style="margin-top:30px;" v-show="spaceShow && zone != 'all' && zone != 'noKnow'">
+      <span class="condition-title">空间实例:</span>
+      <el-select v-model="space" multiple filterable collapse-tags style="width:350px;" placeholder="请选择">
+        <el-option v-for="item in spaceList" :key="item.value" :label="item.label" :value="item.value">
+        </el-option>
+      </el-select>
+    </el-row>
+    <span slot="footer" class="dialog-footer">
+      <el-button @click="handleClickClose">取 消</el-button>
+      <el-button type="primary" @click="toLinkAssets">确 认</el-button>
+    </span>
+  </el-dialog>
+</template>
+<script>
+
+import { zoneQuery, TypePartsDie } from '@/api/scan/request'
+import { queryPhysicsAllType } from '@/api/dict'
+import { buildingQuery } from '@/api/object/build'
+import { floorQuery } from '@/api/object/floor'
+
+import { mapGetters } from "vuex"
+
+export default {
+  props: {
+    dialog: {
+      type: Object,
+      default: function () {
+        return {
+          linkAssets: false
+        };
+      }
+    }
+  },
+  computed: {
+    ...mapGetters("layout", ["projectId", "secret", "userId"])
+  },
+  data() {
+    return {
+      bfoptions: [], //建筑楼层列表
+      buildFloor: ['all'], //选择的建筑楼层
+      deviceTypeList: [], //未关联资产的设备或部件列表
+      deviceType: '', //选择的设备类型
+      zone: 'all', //空间分区
+      zoneList: [], //空间分区列表
+      spaceList: [], //空间实例列表
+      space: [], //选择的空间实例
+      spaceShow: false
+    };
+  },
+  created() {
+    this.getPartsDieType() //获取未关联资产的设备或部件类型
+    this.getBuildFloorData() //获取建筑楼层数据
+    this.getSpaceData() //获取分区类型数据
+  },
+  mounted() {
+  },
+  methods: {
+    //打开弹窗回调,初始化选择
+    handleOpenReset() {
+      this.deviceType = ''
+      this.buildFloor = ['all']
+      this.zone = 'all'
+      this.space = []
+      this.spaceShow = false
+    },
+    //获取未关联资产的设备或部件类型
+    getPartsDieType() {
+      let _this = this
+      TypePartsDie(res => {       
+        _this.deviceTypeList = res.content
+      })
+    },
+    //获取建筑楼层数据
+    getBuildFloorData() {
+      let data, buildParams = {
+        pageNumber: 1,
+        pageSize: 1000,
+        orders: "localName asc",
+        projection: [
+          "id",
+          "localName"
+        ]
+      }, floorParams = {
+        orders: "floorSequenceID desc",
+        pageNumber: 1,
+        pageSize: 1000,
+        projection: [
+          "buildingId",
+          "id",
+          "localName",
+          "floorSequenceID"
+        ]
+      }
+      let promise1 = new Promise((resolve, reject) => {
+        buildingQuery(buildParams, res => {
+          resolve(res)
+        })
+      })
+      let promise2 = new Promise((resolve, reject) => {
+        floorQuery(floorParams, res => {
+          resolve(res)
+        })
+      })
+      Promise.all([promise1, promise2]).then(values => {
+        let builData = values[0].content, floorData = values[1].content
+        data = builData.map(build => {
+          return {
+            value: build.id,
+            label: build.localName
+          }
+        })
+        data.unshift({
+          value: "all",
+          label: "全部"
+        }, {
+          value: "noKnow",
+          label: "不在建筑内"
+        })
+        data.forEach(build => {
+          floorData.forEach(floor => {
+            if (build.value == floor.buildingId && floor.id && floor.localName) {
+              if (build.children) {
+                build.children.push({
+                  value: floor.id,
+                  label: floor.localName,
+                  FloorSequenceID: floor.floorSequenceID
+                })
+              } else {
+                build.children = []
+                build.children.push({
+                  value: "all",
+                  label: "全部"
+                }, {
+                  value: 'noKnow',
+                  label: "不在楼层内"
+                }, {
+                  value: floor.id,
+                  label: floor.localName,
+                  FloorSequenceID: floor.floorSequenceID
+                })
+              }
+            }
+          })
+        })
+        this.bfoptions = data
+      })
+    },
+    //获取空间分区数据
+    getSpaceData() {
+      let params = {
+        type: "space"
+      }
+      queryPhysicsAllType(params, res => {
+        if (res.content) {
+					this.zoneList = res.content.filter(item => {
+						if (item.name !== item.aliasName) {
+							item.name = `${item.name}(${item.aliasName})`
+						}
+						if (item.code != 'Ispace') {
+							return item
+						}
+					})
+					this.zoneList.unshift({
+						name: '全部',
+						code: 'all'
+					})
+					this.zoneList.push({
+						name: '不在任何业务空间内',
+						code: 'noKnow'
+					})
+				}
+      })
+    },
+    // 修改设备类型
+    handleChangeDevice(val) {
+      this.zoneListIsShow()
+    },
+    // 修改建筑楼层
+    handleChangeBf(val) {
+      this.zoneListIsShow()
+      // 重置空间实例选择
+      this.space = []
+      this.spaceList = []
+      this.handleChangeSpaceItem()
+    },
+    //判断是否显示空间分区选择
+    zoneListIsShow() {
+      let lastVal = this.buildFloor.slice(-1)[0]
+      if (!lastVal || lastVal == 'noKnow' || this.buildFloor[0] == 'all' || this.deviceType.length != 6) {
+        this.spaceShow = false
+      } else {
+        this.spaceShow = true
+      }
+    },
+    //修改分区类型
+    handleChangeZone() {
+      // 重置空间实例选择
+      this.space = []
+      this.spaceList = []
+      this.handleChangeSpaceItem()
+    },
+    // 获取空间实例
+    handleChangeSpaceItem() {
+      if (this.zone != 'all' && this.zone != 'noKnow') {
+        let nodes = []
+        let recursionGetData = (pageNum, zone) => {
+          pageNum = pageNum ? pageNum : 1
+          let params = {
+            orders: "createTime desc, id asc",
+            pageNumber: pageNum,
+            pageSize: 1000,
+            projection: ["id", "name", "localName", "createTime"],
+            zoneType: zone
+          }
+          if (this.spaceShow) {
+            if (this.buildFloor.length == 2 && this.buildFloor[1] != 'all') {
+              params.buildingId = this.buildFloor[0];
+              params.floorId = this.buildFloor[1];
+            } else {
+              params.buildingId = this.buildFloor[0];
+            }
+          }
+          zoneQuery(params, res => {
+            nodes = nodes.concat(res.content.map(item => ({
+              value: item.id,
+              label: item.localName ? item.localName : item.name
+            })))
+            if (res.total / (res.pageSize * res.pageNumber) > 1) {
+              recursionGetData(res.pageNumber + 1, zone)
+            } else {
+              this.spaceList = nodes
+            }
+          })
+        }
+        recursionGetData(1, this.zone)
+      }
+    },
+    // 点击取消关闭弹窗
+    handleClickClose() {
+      this.dialog.linkAssets = false
+    },
+    // 批量关联资产详情页
+    toLinkAssets() {
+      let query = {}
+      // 校验必填项
+      if (!this.deviceType) {
+        this.$message.info('请选择设备类型!')
+        return false
+      } else if (!this.buildFloor.length) {
+        this.$message.info('请选择建筑楼层!')
+        return false
+      }
+      if (this.spaceShow) { //选择了空间
+        if (this.zone == 'all') {//全部空间
+          query = {
+            deviceType: this.deviceType,
+            buildFloor: this.buildFloor
+          }
+        } else {
+          if (this.space.length) {//选择了具体的空间实例
+            query = {
+              deviceType: this.deviceType,
+              buildFloor: this.buildFloor,
+              spaceList: this.space
+            }
+          } else {
+            query = {
+              deviceType: this.deviceType,
+              buildFloor: this.buildFloor,
+              spaceList: this.zone //(包含具体的业务空间分区和不在任何分区两种情况)
+            }
+          }
+        }
+      } else {
+        query = {
+          deviceType: this.deviceType,
+          buildFloor: this.buildFloor
+        }
+      }
+      this.$router.push({
+        name: "batchlinkAssets",
+        params: query
+      })
+    }
+  },
+  watch: {}
+};
+</script>
+
+<style lang="less" scoped>
+.condition-title {
+  width: 100px;
+  display: inline-block;
+  text-align: right;
+  margin-left: 10px;
+  margin-right: 12px;
+  color: #999999;
+  font-size: 14px;
+  vertical-align: top;
+}
+
+/deep/ .el-dialog__body {
+  max-height: 420px;
+  overflow-y: auto;
+}
+</style>

+ 190 - 0
src/components/ledger/components/lib/lookImages.vue

@@ -0,0 +1,190 @@
+<!--
+    上传图片的弹窗
+-->
+
+<template>
+  <el-dialog title="上传图片" :visible.sync="dialog.lookPic" v-if="dialog.lookPic" width="500px">
+    <div style="max-height:500px;overflow-y:auto;">
+      <el-tabs type="border-card">
+        <el-tab-pane>
+          <span slot="label">图片</span>
+          <div style="width:500px;max-height:500px;">
+            <div v-if="picArrs.length">
+              <iframe
+                id="google_ads_frame3"
+                name="google_ads_frame3"
+                frameborder="0"
+                width="100%"
+                height="500px"
+                :src="iframeSrc"
+                marginwidth="0"
+                marginheight="0"
+                vspace="0"
+                hspace="0"
+                allowtransparency="true"
+                scrolling="no"
+                allowfullscreen="true"
+              ></iframe>
+            </div>
+            <div v-if="!picArrs.length">暂无图片</div>
+          </div>
+        </el-tab-pane>
+        <el-tab-pane v-if="videoArr.length">
+          <span slot="label">视频</span>
+          <div style="width:500px;">
+            <div
+              v-for=" (item,index) in videoArr "
+              style="width:200px;height:200px;float:left;margin: 5px;"
+              :key="index"
+            >
+              <video
+                width="100%"
+                height="100%"
+                :src="
+                '/image-service/common/image_get?systemId=dataPlatform&key='+item.key"
+                controls
+                :poster="'/image-service/common/image_get?systemId=dataPlatform&key='+ item.key +'&width=200'"
+              ></video>
+            </div>
+          </div>
+        </el-tab-pane>
+      </el-tabs>
+    </div>
+  </el-dialog>
+</template>
+<script>
+  import uploadImgs from "@/components/ledger/lib/uploadImgsName";
+
+  export default {
+  components: {
+    uploadImgs
+  },
+  props: {
+    dialog: {
+      type: Object,
+      default: function () {
+        return {
+          lookPic: true
+        };
+      }
+    },
+    keysArr: {
+      type: Array,
+      default: function () {
+        return []
+      }
+    },
+    read: {
+      type: Boolean,
+      default: false
+    }
+  },
+  data() {
+    return {
+      picArrs: [],
+      panoramaArr: [],
+      videoArr: [],
+      videoPicArr: [],
+      changeKeys: [],
+      iframeSrc: ""
+    };
+  },
+  created() { },
+  mounted() { },
+  methods: {
+    imageItem(images) {
+      this.picArrs = images
+      this.change()
+    },
+
+    panoramaItem(images) {
+      this.panoramaArr = images
+      this.change()
+    },
+
+    videoItem(videos, pe, pics) {
+      this.videoArr = videos
+      this.videoPicArr = pics
+      this.change()
+    },
+
+    change() {
+      //   let picsArr = this.getArr(this.picArrs, "设备图片", "image")
+      //   let videos = this.getArr(this.videoArr, "视频", "video")
+      //   let videoPics = this.getArr(this.videoPicArr, "视频资料", "image_video")
+      //   let panoramas = this.getArr(this.panoramaArr, "全景照片", "panorama")
+      let picsArr = this.picArrs
+      let videos = this.videoArr
+      let videoPics = this.videoPicArr
+      let panoramas = this.panoramaArr
+      this.changeKeys = picsArr.concat(videos).concat(videoPics).concat(panoramas)
+      console.log(this.changeKeys)
+      this.$emit("change", this.changeKeys)
+    },
+
+    getArr(arr, name, type) {
+      return arr.map(item => {
+        return { "systemId": "dataPlatform", "name": name, "type": type, "key": item }
+      })
+    },
+
+    //将父组件传来的数据进行分组
+    fatherTochild() {
+      this.panoramaArr = []
+      this.videoArr = []
+      this.videoPicArr = []
+      this.picArrs = []
+      if (this.keysArr instanceof Array) {
+        this.keysArr.map(item => {
+          if (item.type == 'panorama') {
+            this.panoramaArr.push(item)
+          } else if (item.type == "video") {
+            this.videoArr.push(item)
+          } else if (item.type == 'image_video') {
+            this.videoPicArr.push(item)
+          } else {
+            this.picArrs.push(item)
+          }
+        })
+      } else {
+        this.panoramaArr = []
+        this.videoArr = []
+        this.videoPicArr = []
+        this.picArrs = []
+      }
+      if (this.picArrs.length) {
+        this.$nextTick(_ => {
+          this.iframeSrc = 
+          window.__systemConf.BASE_URL +
+          // process.env.BASE_URL + 
+          ":8890/photo-View.html"
+          // this.iframeSrc = process.env.BASE_URL + "/photo-View.html"
+          let iframe = document.getElementById("google_ads_frame3")
+          console.log(iframe)
+          iframe.onload = () => {
+            console.log("onload")
+            let arr = this.picArrs.map(i => ({
+              key: i.Key,
+              name: i.Name,
+              type: i.Type
+            }))
+            iframe.contentWindow.postMessage(arr, "*")
+          }
+        })
+      }
+    }
+  },
+  watch: {
+    dialog: {
+      deep: true,
+      handler: function () {
+        if (this.dialog.lookPic) {
+          this.fatherTochild()
+        }
+      }
+    }
+  }
+};
+</script>
+<style>
+</style>

+ 60 - 0
src/components/ledger/components/lib/myPagination.vue

@@ -0,0 +1,60 @@
+<!--
+    props:{
+        size: 当前页的页数,
+        sizes: 下拉的条数,
+        total: 总条数,
+        currentPage: 当前页
+    }
+    @change 发生改变
+-->
+<template>
+    <div class="block">
+        <el-pagination
+        @size-change="handleSizeChange"
+        @current-change="handleCurrentChange"
+        :current-page.sync="page.currentPage"
+        :page-sizes="page.sizes"
+        :page-size="page.size"
+        :layout=" isSmall ? 'total, sizes, prev, pager, next, jumper' : 'prev, pager, next'"
+        :total="page.total">
+        </el-pagination>
+    </div>
+</template>
+<script>
+export default {
+  name: "pagination",
+  props: {
+    page: {
+      type: Object
+    },
+    isSmall:{
+      type:Boolean,
+      default: true
+    }
+  },
+  methods: {
+    //当前条数发生改变
+    handleSizeChange(val) {
+      this.page.size = val;
+      this.change()
+    },
+    //当前页发生改变
+    handleCurrentChange(val) {
+      this.page.currentPage = val;
+      this.change()
+    },
+    //发生改变
+    change() {
+      this.$emit("change");
+    }
+  }
+};
+</script>
+<style>
+.el-pagination button, .el-pagination span:not([class*=suffix]) {
+    vertical-align: middle;
+}
+.block{
+  /* margin-top: 5px; */
+}
+</style>

+ 154 - 0
src/components/ledger/components/lib/noAllDataFloor.vue

@@ -0,0 +1,154 @@
+<template>
+  <div id="buildFloor">
+    <span class="buildFloor">建筑楼层</span>
+    <el-cascader placeholder="请选择建筑楼层" :options="options" v-model="value" filterable size="small"
+                 :style="isWidth ? '' : 'width:160px;'" @change="changeCascader"></el-cascader>
+  </div>
+</template>
+<script>
+import tools from "@/utils/scan/tools"
+// import { floorQuery, buildingQuery } from '@/api/scan/request'
+import { buildingQuery } from '@/api/object/build'
+import { floorQuery } from '@/api/object/floor'
+
+import { mapGetters, mapActions } from "vuex"
+
+export default {
+  props: {
+    isWidth: {
+      type: Boolean,
+      default: true
+    },
+    type: {
+      type: String,
+      default: "yes"
+    }
+  },
+  computed: {
+    ...mapGetters("layout", [
+      "projectId",
+      "secret",
+      "userId"
+    ])
+  },
+  data() {
+    return {
+      options: [],
+      param: {
+        ProjId: '',
+        secret: ''
+      },
+      value: [],
+      floorToMap: {}
+    };
+  },
+  created() {
+    this.param.ProjId = this.projectId
+    this.param.secret = this.secret
+    this.getData()
+  },
+  watch: {
+    projectId() {
+      this.options = [];
+      this.param.ProjId = this.projectId
+      this.param.secret = this.secret
+      this.getData()
+      this.changeCascader([])
+    }
+  },
+  methods: {
+    //设置默认选项
+    setValue(val) {
+      if (val && val.length) {
+        this.value = val
+      }
+    },
+    //获取数据
+    getData() {
+      let data, buildParams = {
+        pageNumber: 1,
+        pageSize: 1000,
+        orders: "localName asc",
+        projection: [
+          "id",
+          "localName"
+        ]
+      }, floorParams = {
+        orders: "floorSequenceID desc",
+        pageNumber: 1,
+        pageSize: 1000
+      }
+      let promise1 = new Promise((resolve, reject) => {
+        buildingQuery(buildParams, res => {
+          resolve(res)
+        })
+      })
+      let promise2 = new Promise((resolve, reject) => {
+        floorQuery(floorParams, res => {
+          resolve(res)
+        })
+      })
+      Promise.all([promise1, promise2]).then(values => {
+        let builData = values[0].content, floorData = values[1].content
+        data = builData.map(build => {
+          return {
+            value: build.id,
+            label: build.localName
+          }
+        })
+        data.unshift({
+          value: "noKnow",
+          label: "未明确建筑"
+        })
+        data.forEach(build => {
+          floorData.forEach(floor => {
+            this.floorToMap[floor.id] = floor;
+            if (build.value == floor.buildingId && floor.id && floor.localName && this.type == "yes") {
+              if (build.children) {
+                build.children.push({
+                  value: floor.id,
+                  label: floor.localName,
+                  FloorSequenceID: floor.floorSequenceID
+                })
+              } else {
+                build.children = []
+                build.children.push({
+                  value: 'noKnow',
+                  label: "未明确楼层"
+                }, {
+                  value: floor.id,
+                  label: floor.localName,
+                  FloorSequenceID: floor.floorSequenceID
+                })
+              }
+            }
+          })
+        })
+        this.$emit('getFloorMap', this.floorToMap)
+        this.options = data
+      })
+    },
+    //改变item
+    changeCascader(value) {
+      this.$emit("change", value)
+    }
+  }
+};
+</script>
+<style lang="less">
+.el-cascader .el-input .el-input__inner {
+  vertical-align: bottom;
+}
+</style>
+<style lang="less" scoped>
+#buildFloor {
+  margin-left: 10px;
+  float: left;
+}
+
+.buildFloor {
+  color: #999999;
+  font-size: 14px;
+  margin-right: 12px;
+}
+</style>

+ 147 - 0
src/components/ledger/components/lib/partsDieList.vue

@@ -0,0 +1,147 @@
+<template>
+  <div id="cascaderMap">
+    <span class="buildFloor" style="margin-right: 12px;">设备类</span>
+    <el-select v-model="value" placeholder="请选择" :props="props" filterable :style="isWidth ? '' : 'width:160px;'"
+               size="small" @change="changeVal">
+      <el-option v-for="item in options" :key="item.code" :label="item.facility" :value="item.code"></el-option>
+    </el-select>
+    <!-- <el-cascader placeholder="请选择" :options="options" v-model="value" :props="props" filterable :style="isWidth ? '' : 'width:160px;'" size="small"
+      @change="changeVal" change-on-select></el-cascader> -->
+  </div>
+</template>
+<script>
+import { mapGetters } from 'vuex';
+import { getEqCode, getEquipBelongs, queryPartsDie } from "@/api/scan/request";
+
+export default {
+  name: "getCode",
+  props: {
+    isWidth: {
+      type: Boolean,
+      default: true
+    },
+    Family: {
+      type: String,
+      default: '',
+    }
+  },
+  computed: {
+    ...mapGetters("layout", ["projectId"])
+  },
+  data() {
+    return {
+      value: "",
+      options: [],
+      props: {
+        value: "code",
+        label: "facility"
+      },
+      falg: true,
+      content: []
+    };
+  },
+  mounted() {
+    if (this.Family != "") {
+      this.getAllData()
+    }
+  },
+  watch: {
+    projectId() {
+      this.value = '';
+      this.getAllData()
+    }
+    // Family: {
+    //   handler: function (val) {
+    //     if(val != "") {
+    //       this.getAllData()
+    //     }
+    //   },
+    //   immediate: true
+    // }
+  },
+  methods: {
+    setValue(val) {
+      if (val) {
+        this.value = val
+      }
+    },
+    //修改val
+    changeVal(val) {
+      let value = {}
+      this.options.map(item => {
+        if (item.code == val) {
+          value = item
+        }
+      })
+      this.value = val
+      this.$emit("change", value)
+    },
+    //获取当前项目下的设备类型(只拿到编码-需要过滤)
+    getData() {
+      let param = {
+        distinct: true,
+        pageNumber: 1,
+        pageSize: 500,
+        projection: [
+          "classCode"
+        ]
+      }
+      queryPartsDie(param, res => {
+        this.content = res.content
+        this.filterForOptions()
+        let val = this.options.length ? this.options[0].code : ""
+        this.changeVal(val)
+      })
+    },
+    //获取物理世界所有设备类型
+    getAllData() {
+      let param = {
+        data: {
+          filters: `family='${this.Family}'`,
+          distinct: true,
+          orders: "equipName asc",
+          pageNumber: 1,
+          pageSize: 500,
+          projection: [
+            "equipCode", "equipName"
+          ]
+        }
+      }
+      getEquipBelongs(param, res => {
+        this.options = this.formatOptions(res.content)
+        this.getData()
+      })
+    },
+    //格式化options数据
+    formatOptions(arr) {
+      let data = [];
+      arr.map(t => {
+        let temp = {};
+        temp.code = t.equipCode;
+        temp.facility = t.equipName;
+        data.push(temp)
+      })
+      return data;
+    },
+    //过滤
+    filterForOptions() {
+      this.options = this.options.filter(item => {
+        if (this.content.find(value => value.classCode == item.code)) {
+          return item
+        }
+      })
+    }
+  }
+};
+</script>
+<style lang="less" scoped>
+#cascaderMap {
+  float: left;
+  margin-left: 10px;
+
+  .buildFloor {
+    color: #999999;
+    font-size: 14px;
+  }
+}
+</style>

+ 62 - 0
src/components/ledger/components/lib/qrcode.vue

@@ -0,0 +1,62 @@
+<template>
+  <el-dialog
+    title="二维码"
+    v-if="dialog.qrcode"
+    custom-class="custom-dialog"
+    :visible.sync="dialog.qrcode"
+    width="500px"
+  >
+    <div>
+      <div id="qrcode">
+        <img
+          style="width:200px;height:200px;margin:auto;display:block;"
+          :src="'/image-service/common/file_get/'+ qrcodeUrl +'?systemId=dataPlatform'"
+          alt="二维码"
+        >
+      </div>
+    </div>
+  </el-dialog>
+</template>
+<script>
+// import { qrcanvas } from "qrcanvas";
+export default {
+  props: {
+    dialog: {
+      type: Object,
+      default: function () {
+        return {
+          qrcode: false
+        };
+      }
+    },
+    qrcodeUrl: {
+      default: ""
+    }
+  },
+  data() {
+    return {};
+  },
+  created() {},
+  mounted() {
+  },
+  methods: {
+    // getCanvas(id) {
+    //     let canvas = qrcanvas({
+    //         cellSize: 8,
+    //         data: 'http://service.sagacloud.cn/qrcode/' + 'Pea3a31168a30d4056acda3a56e557940b'
+    //     });
+    //     canvas.style.margin = "auto"
+    //     canvas.style.display = "block"
+    //     this.$nextTick(() => {
+    //         document.getElementById("qrcode").innerHTML = null;
+    //         document.getElementById("qrcode").appendChild(canvas);
+    //     });
+    // }
+  }
+};
+</script>
+<style lang="less" scoped>
+#qrcode {
+    height: 300px;
+}
+</style>

+ 99 - 0
src/components/ledger/components/lib/spaceSelect.vue

@@ -0,0 +1,99 @@
+<template>
+  <div id="spaceSelect">
+    <span class="buildFloor" style="padding-right:12px">空间类型</span>
+    <el-cascader
+      v-model="value"
+      placeholder="请选择空间类型"
+      clearable
+      :style="isWidth ? '' : 'width:160px;'" size="small" 
+      :props="{value:'code',label:'name',children:'ZoneType'}"
+      :options="options" 
+      @change="changeVal">
+    </el-cascader>
+  </div>
+</template>
+
+<script>
+import { queryAllZoneType } from '@/api/scan/request';
+import { mapGetters } from 'vuex'
+export default {
+  computed: {
+    ...mapGetters("layout", ["projectId"])
+  },
+  props: {
+    isWidth: {
+      type: Boolean,
+      default: true
+    },
+    isAll: {
+			type: Boolean,
+			default: false
+    }
+  },
+  data() {
+    return {
+      value:[null],
+      options: [],
+      props: {
+        isWidth:false
+      }
+    }
+  },
+  methods: {
+    setValue(val) {
+      this.value = val;
+      this.changeVal(val);
+    },
+    //获取下拉框数据
+    getOptionData() {
+      let params1 = {}
+      //Cascade: [{ Name: 'zoneType', Filters: `ProjectId='${this.projectId}'` }]
+      queryAllZoneType(params1, res => {
+        this.options = res.content;
+        if (this.isAll) {
+					this.options.unshift({
+						code: "",
+						name: "全部"
+					});
+					this.value = [''];
+        }
+        this.changeVal(this.value);
+      })
+    },
+    //改变空间
+    changeVal(value) {
+      let val = value[value.length - 1];
+      let space = {};
+      this.options.forEach(item => {
+        // if (item.ZoneType) {
+          // zoneType级联其他分区, 新版已删除
+          // item.ZoneType.forEach(other => {
+          //   if (other.code == val) {
+          //     space = other
+          //   }
+          // })
+        // } else {
+          if (item.code == val) {
+            space = item
+          }
+        // }
+      })
+      this.$emit('change', val, space, value)
+    }
+  },
+  created() {
+    this.getOptionData();
+  }
+}
+</script>
+
+<style lang="less" scoped>
+#spaceSelect {
+  float: left;
+  margin-left: 10px;
+  .buildFloor {
+    color: #999999;
+    font-size: 14px;
+  }
+}
+</style>

+ 285 - 0
src/components/ledger/components/lib/staticDialog.vue

@@ -0,0 +1,285 @@
+<template>
+    <el-dialog title="台账信息导入" :visible.sync="dialogFormVisible" class="dialogFormVisible" @close="handleCloseDialog">
+        <div v-show="active == 1">
+            <h5 class="title">
+                1、只可导入维护数据类型为“单个-数值量(Num)、单个-字符量(Str)、单个-布尔量(Bool)、单个-单选枚举量(Eunm)、单个-日期时间量(DTime)”的静态信息点的值。
+            </h5>
+            <h5 class="title">2、“单个-布尔量(Bool)、单个-单选枚举量(Eunm)”的值需填写选项的编码、“单个-日期时间量(DTime)”的值需填写YYYYMMDD。</h5>
+            <h5 class="title">3、第一列为识别设备实例的唯一标识;第二列为实例的设备类型。其他列为信息点,可任意增加信息点列。</h5>
+            <h5 class="title">4、设备唯一标识三选一:设备本地编码EquipLocalID、数字化交付编码DigitaIDeliveryID、设备类型ClassCode。</h5>
+            <h5 class="title">5、行为对象实例对应该信息点的值。值为空时不做处理,不会清空台账中已维护的值。</h5>
+            <el-upload
+                class="upload-demo"
+                drag
+                :action="`${baseUrl}${dataCenter}/object/equip/import/`"
+                accept=".xlsx"
+                :show-file-list="false"
+                :headers="headers"
+                name="file"
+                multiple
+                :on-progress="progress"
+                :on-success="success"
+                :on-error="error"
+            >
+                <i class="el-icon-upload"></i>
+                <div class="el-upload__text">将Excel文件拖到此处,或<em>点击上传Excel文件</em></div>
+                <div class="el-upload__tip tips" slot="tip">
+                    上传的Excel数据将完全覆盖当前设备信息点(原有数据不会保留)
+                    <el-button style="float: right;" type="primary" @click="downloadEquipTemplate()">下载模板</el-button>
+                    <el-button style="float: right; margin-right: 10px;" v-if="historyKey" type="primary" @click="download(historyKey)">下载上一次导入结果</el-button>
+                </div>
+            </el-upload>
+        </div>
+        <div v-show="active == 2">
+            <div class="block">
+                <el-timeline>
+                    <el-timeline-item
+                        v-for="(activity, index) in activities"
+                        :key="index"
+                        :icon="activity.icon"
+                        :type="activity.type"
+                        :color="activity.color"
+                        :size="activity.size"
+                    >
+                        {{ activity.content }}
+                    </el-timeline-item>
+                </el-timeline>
+            </div>
+            <el-button :disabled="!messageKey" @click="download(messageKey)">下载导入结果</el-button>
+            <footer slot="footer" style="text-align: right" class="dialog-footer">
+                <el-button @click="active = 1">返回</el-button>
+                <el-button type="primary" @click="dialogFormVisible = false">确认</el-button>
+            </footer>
+        </div>
+    </el-dialog>
+</template>
+
+<script>
+import storage from "@/framework/utils/storage";
+import { baseUrl, dataCenter } from "@/api/scan/config.js";
+import { queryDownloadsKey, equipTemplate } from "@/api/scan/request";
+import { mapGetters } from "vuex";
+
+export default {
+    data() {
+        return {
+            active: 1,
+            dialogFormVisible: false,
+            messageKey: "",
+            historyKey: "",
+            activities: [],
+            baseUrl: baseUrl,
+            dataCenter: dataCenter,
+        };
+    },
+    computed: {
+        ...mapGetters("layout", ["projectId"]),
+        headers() {
+            return {
+                //头信息中的 ProjectId 改为 projectId    nh-2021.11.25
+                projectId: this.projectId,
+                Comming: "adm",
+                Account: storage.get("user_name"),
+            };
+        },
+    },
+    methods: {
+        openDialog() {
+            this.dialogFormVisible = true;
+        },
+        /**
+         * 获取导入历史key
+         */
+        getHistoryKey() {
+            const params = {
+                filters: "type = 'static'",
+                orders: "createTime desc",
+                pageSize: 1,
+            };
+            queryDownloadsKey(params, (res) => {
+                queryDownloadsKey(params, (res) => {
+                    if (res.result === "success" && res.content && res.content.length && res.content[0].key) {
+                        this.historyKey = res.content[0].key;
+                    } else {
+                        this.historyKey = "";
+                    }
+                });
+            });
+        },
+        progress(event, file, fileList) {
+            this.active = 2;
+            this.activities = [
+                {
+                    content: "数据文件处理中..",
+                    size: "large",
+                    type: "primary",
+                    icon: "el-icon-check",
+                    color: "#0bbd87",
+                },
+            ];
+        },
+        success(event, file, fileList) {
+            if (event.result == "success") {
+                this.messageKey = event.message;
+                if (event.content && event.content[0] && event.content[0].state === "0") {
+                    // 全部导入成功
+                    this.activities = [
+                        {
+                            content: "数据文件处理中..",
+                            size: "large",
+                            type: "primary",
+                            icon: "el-icon-check",
+                            color: "#0bbd87",
+                        },
+                        {
+                            content: `全部导入成功(导入信息点:${event.content[0].successCount} 条)`,
+                            color: "#0bbd87",
+                            icon: "el-icon-check",
+                            size: "large",
+                            type: "success",
+                        },
+                    ];
+                    // 导入成功刷新台账数据
+                    this.$emit("upDataDevice");
+                } else if (event.content && event.content[0] && event.content[0].state === "1") {
+                    // 部分导入成功
+                    this.activities = [
+                        {
+                            content: "数据文件处理中..",
+                            size: "large",
+                            type: "primary",
+                            icon: "el-icon-check",
+                            color: "#0bbd87",
+                        },
+                        {
+                            content: `部分导入成功(导入成功:${event.content[0].successCount} 条;导入失败:${event.content[0].failCount} 条)`,
+                            color: "#0bbd87",
+                            icon: "el-icon-check",
+                            size: "large",
+                            type: "success",
+                        },
+                    ];
+                    // 导入成功刷新台账数据
+                    this.$emit("upDataDevice");
+                } else {
+                    // 导入失败
+                    this.activities = [
+                        {
+                            content: "数据文件处理中..",
+                            size: "large",
+                            type: "primary",
+                            icon: "el-icon-check",
+                            color: "#0bbd87",
+                        },
+                        {
+                            content: "导入失败!",
+                            color: "#f56c6c",
+                            icon: "el-icon-close",
+                            size: "large",
+                            type: "primary",
+                        },
+                    ];
+                }
+            } else {
+                this.messageKey = "";
+                if (event.result === "failure" && event.message) {
+                    this.activities = [
+                        {
+                            content: "数据文件处理中..",
+                            size: "large",
+                            type: "primary",
+                            icon: "el-icon-check",
+                            color: "#0bbd87",
+                        },
+                        {
+                            content: `导入失败!(${event.message})`,
+                            color: "#f56c6c",
+                            icon: "el-icon-close",
+                            size: "large",
+                            type: "primary",
+                        },
+                    ];
+                } else {
+                    this.activities = [
+                        {
+                            content: "数据文件处理中..",
+                            size: "large",
+                            type: "primary",
+                            icon: "el-icon-check",
+                            color: "#0bbd87",
+                        },
+                        {
+                            content: "导入失败!",
+                            color: "#f56c6c",
+                            icon: "el-icon-close",
+                            size: "large",
+                            type: "primary",
+                        },
+                    ];
+                }
+            }
+        },
+        error(event, file, fileList) {},
+        download(key) {
+            const url = `${baseUrl}${dataCenter}/graphic/downloads/point?key=` + key;
+            let a = document.createElement("a");
+            a.href = url;
+            a.download = `${key}.xlsx`;
+            a.click();
+        },
+        /**
+         * 台账信息导入模板
+         */
+        downloadEquipTemplate() {
+            equipTemplate(
+                {},
+                (res) => {
+                    console.log("下载台账信息导入模板");
+                },
+                "台账信息导入模板.xlsx"
+            );
+        },
+        handleCloseDialog() {
+            this.messageKey = "";
+            this.historyKey = "";
+            this.active = 1;
+            this.activities = [];
+        },
+    },
+    watch: {
+        dialogFormVisible(n, o) {
+            // 打开弹窗获取历史导入key值
+            if (n) this.getHistoryKey();
+        },
+    },
+};
+</script>
+
+<style scoped lang="less">
+.dialogFormVisible {
+    .time {
+        margin: 10px 0;
+        font-size: 15px;
+
+        .text {
+            margin-right: 10px;
+            font-size: 14px;
+        }
+    }
+
+    .tips {
+        color: red;
+        font-size: 14px;
+        font-weight: bold;
+    }
+
+    /deep/ .el-upload {
+        width: 100%;
+    }
+
+    /deep/ .el-upload-dragger {
+        margin: 0 auto;
+        width: 100%;
+    }
+}
+</style>

+ 128 - 0
src/components/ledger/components/lib/system.vue

@@ -0,0 +1,128 @@
+<template>
+  <div id="cascaderMap">
+    <span class="buildFloor" style="padding-right: 12px;">所属专业系统类型</span>
+    <el-select v-model="value" placeholder="请选择" :props="props" filterable :style="isWidth ? '' : 'width:160px;'"
+               size="small" @change="changeVal">
+      <el-option v-for="item in options" :key="item.classCode" :label="item.categoryName"
+                 :value="item.classCode"></el-option>
+    </el-select>
+    <!-- <el-cascader placeholder="请选择" :options="options" v-model="value" :props="props" filterable :style="isWidth ? '' : 'width:160px;'" size="small"
+      @change="changeVal" change-on-select></el-cascader> -->
+  </div>
+</template>
+<script>
+import { mapGetters } from 'vuex';
+import { queryLinkSys } from "@/api/scan/request";
+import { queryPhysicsAllType } from "@/api/dict";
+
+
+export default {
+  name: "getCode",
+  props: {
+    isWidth: {
+      type: Boolean,
+      default: true
+    },
+    all: {
+      default: false
+    }
+  },
+  computed: {
+    ...mapGetters("layout", ["projectId"])
+  },
+  data() {
+    return {
+      value: [],
+      options: [],
+      props: {
+        value: "classCode",
+        label: "categoryName"
+      }
+    };
+  },
+  created() {
+    this.init()
+  },
+  watch: {
+    projectId() {
+      this.value = ''
+      this.init()
+    }
+  },
+  methods: {
+    init() {
+      if (this.all) {
+        //物理世界所有系统
+        this.getAllData();
+      } else {
+        //当前项目下已有的系统
+        this.getData();
+      }
+    },
+    setValue(val) {
+      this.value = val
+    },
+    //修改val
+    changeVal(val) {
+      let value = {}
+      this.options.map(item => {
+        if (item.classCode == val) {
+          value = item
+        }
+      })
+      this.value = val
+      this.$emit("change", value)
+    },
+    getData() {
+      let param = {
+        distinct: true,
+        pageNumber: 1,
+        pageSize: 1000,
+        projection: [
+          "classCode", "CategoryName"
+        ]
+      }
+      queryLinkSys(param, res => {
+        this.options = res.content;
+        if (this.value && this.options.find(item => item.classCode === this.value)) {
+          this.changeVal(this.value)
+        } else {
+            this.changeVal("")
+        }
+      })
+    },
+    getAllData() {
+      let param = {
+        type: 'system'
+      }
+      queryPhysicsAllType(param, res => {
+        this.options = this.changeArr(res.content)
+        if (this.value && this.options.find(item => item.classCode === this.value)) {
+          this.changeVal(this.value)
+        } else {
+            this.changeVal("")
+        }
+      })
+    },
+    changeArr(arr) {
+      let data = [];
+      arr.forEach(item => {
+        data.push({ classCode: item.classCode || item.code, categoryName: item.categoryName || item.name });
+      });
+      console.log(data, 'data')
+      return data;
+    }
+  }
+};
+</script>
+<style lang="less" scoped>
+#cascaderMap {
+  float: left;
+  margin-left: 10px;
+
+  .buildFloor {
+    color: #999999;
+    font-size: 14px;
+  }
+}
+</style>

+ 120 - 0
src/components/ledger/components/lib/title.vue

@@ -0,0 +1,120 @@
+<!--
+    清单页头部个数组件
+-->
+<template>
+    <div id="listHeader">
+        <div class="border-main">
+            <!-- <p class="list-name"><span style="padding-right: 12px;">项目名称</span>{{mess.perjectName}}</p> -->
+            <div class="card-view">
+                <span class="card" v-for="(i,index) in mess.name" :key="index">
+                    <span>{{index == 1 ? "已对应" : "全部"}}{{i.key}}</span>
+                    <span class="num">{{i.num |numFormat}}</span>
+                </span>
+            </div>
+        </div>
+    </div>
+</template>
+<script>
+export default {
+  props: {
+    mess: {
+      type: Object,
+      default: function() {
+        return {
+          perjectName: "风雅苑",
+          name: [
+            {
+              key: "测试",
+              num: 1548514
+            },
+            {
+              key: "测试",
+              num: 154852145
+            }
+          ]
+        };
+      }
+    }
+  },
+  data() {
+    return {};
+  },
+  methods: {
+  },
+  filters: {
+    numFormat(num) {
+      if (num && num != null) {
+        num = String(num);
+        var left = num.split(".")[0],
+          right = num.split(".")[1];
+        right = right
+          ? right.length >= 2
+            ? "." + right.substr(0, 2)
+            : "." + right + "0"
+          : "";
+        var temp = left
+          .split("")
+          .reverse()
+          .join("")
+          .match(/(\d{1,3})/g);
+        return (
+          (Number(num) < 0 ? "-" : "") +
+          temp
+            .join(",")
+            .split("")
+            .reverse()
+            .join("") +
+          right
+        );
+      } else if (num === 0) {
+        // 注意===在这里的使用,如果传入的num为0,if中会将其判定为boolean类型,故而要另外做===判断
+        return "0";
+      } else {
+        return "--";
+      }
+    }
+  }
+};
+</script>
+<style lang="less" scoped>
+#listHeader {
+  background-color: #fff;
+//   width: calc(100% - 20px);
+//   height: 120px;
+//   margin: 10px;
+  .border-main {
+    // width: 100%;
+    height: 100%;
+    border: 1px solid #dfe6ec;
+    // border-radius: 5px;
+    padding-left: 10px;
+    .list-name {
+      line-height: 28px;
+      font-size: 14px;
+      margin-top: 10px;
+    }
+    .card-view {
+      // height: 80px;
+      // overflow: hidden;
+      display: flex;           
+      margin-top: 10px;
+      background-color: #e4e4e4;
+      margin-right: 10px;
+      .card {
+          padding: 10px 0 10px 20px;
+          width: 140px;
+          text-align: left;
+        span {
+          color: #797979;
+          font-size: 13px;
+        }
+        span.num {
+          color: #3d3d3d;
+          font-size: 17px;
+          padding-left: 8px;
+        }
+      }
+    }
+  }
+}
+</style>

+ 262 - 0
src/components/ledger/components/lib/uploadFiles.vue

@@ -0,0 +1,262 @@
+<!--
+  上传组件
+  type: 类型,默认image
+  identify: key值,通过val获取
+  disabled: 是否可用
+  index: 父组件的下标
+  imageIndex: 图片下标
+  isShow: 图片的显示
+ -->
+<template>
+  <div id="saga-upload" v-loading="loading">
+    <div id="uploadFile">
+      <el-upload
+        class="upload-file"
+        action drag
+        :show-file-list="false"
+        :http-request="uploadAndSubmit">
+        <el-button size="small" type="primary" v-if="!readOnly">点击上传</el-button>
+        <div slot="tip" class="el-upload__tip" v-if="!readOnly">请上传文件,且不大于50M</div>
+      </el-upload>
+      <div v-show="item && filesArr.length"  v-for="(item,index) in filesArr" :key="item.key">
+        <p class="file-list">
+          <span class="file-name" :title="item.name">{{item.name}}</span>
+          <i title="下载" class="el-icon-download" style="margin-left:10px; cursor:pointer" @click="download(item)"></i>
+          <i v-if="!readOnly" title="删除" class="el-icon-close delete-icon" style="margin-left:10px; cursor:pointer" @click="deleteFile(index,item)"></i>
+        </p>
+      </div>
+    </div>
+  </div>
+</template>
+
+<script>
+import tools from "@/utils/scan/tools";
+
+export default {
+  props: {
+    keysArr: {
+      type: [Array, String],
+      default: function () {
+        return []
+      }
+    },
+    readOnly: {
+      type: Boolean,
+      default: false
+      },
+      max: {
+        type: [Number, String],
+        default: 6
+      },
+      defined: null,
+      firmDataType: {
+        type: String
+      },
+      information: {
+        type: Object
+      },
+      infoType: {
+        type: String
+      }
+    },
+    data() {
+      return {
+        filesArr: [],
+        isShow: Boolean,
+        loading:false
+      };
+    },
+    created() {
+      this.fileFalg()
+      // this.resetFile()
+    },
+    computed: {
+      showFileList() {
+        if (this.firmDataType === 'dialog') {
+          this.isShow = false
+        } else {
+          this.isShow = true
+        }
+        return this.isShow
+      }
+    },
+    methods: {
+      //判断是否为空
+      fileFalg() {
+        let type = typeof (this.keysArr)
+        if (type == 'string') {
+          this.filesArr = [this.keysArr]
+        } else {
+          this.filesArr = tools.deepCopy(this.keysArr)
+        }
+        this.filesArr = tools.deepCopy(this.keysArr)
+        if (!this.keysArr) {
+          this.filesArr = []
+        }
+      },
+      resetFile() {
+        this.filesArr = []
+      },
+
+
+      //点击下载
+      download(item) {
+        if (item.key) {
+          let a = document.createElement("a");
+          a.href = `/image-service/common/file_get/${item.key}?systemId=dataPlatform`;
+          a.download = `${item.name}`;
+          a.click();
+          document.body.removeChild(a);
+        } else {
+          this.$message({
+            message: "无该获取该文件资源链接!",
+            type: "error"
+          });
+        }
+      },
+
+      //删除图片
+      deleteFile(i, key) {
+        this.filesArr.splice(i, 1);
+        this.$emit("change", this.filesArr, this.defined);
+      },
+
+      //上传
+      uploadAndSubmit(item) {
+        // var form = document.forms["demoForm"];
+
+        // if (form["file"].files.length > 0) {
+        // 寻找表单域中的 <input type="file" ... /> 标签
+        // var file = form["file"].files[0];
+
+        let file = item.file;
+        // try sending
+        let size = item.file.size;
+        if( size > 50*1024*1024 ) { //文件大于50M
+          this.$message.error('文件不可大于50M,请重新上传')
+          return false
+        }
+        else {
+          let reader = new FileReader();
+          let vm = this;
+          let fileType = file.name.split(".");
+          let type = fileType[fileType.length - 1];
+          let key = "&key=" + fileType[0] + file.uid + "." + type
+          let CreateTime = tools.formatDate(new Date(file.lastModified))
+          reader.onloadstart = function () {
+            // 这个事件在读取开始时触发
+          };
+          reader.onprogress = function (p) {
+            // 这个事件在读取进行中定时触发
+            let value = (p.loaded / size) * 100;
+            // console.log(p.loaded,size,value,'loaded')
+          };
+
+          reader.onload = function () {
+
+            // 这个事件在读取成功结束后触发
+          };
+          reader.onloadend = function () {
+
+            this.loading = false
+            // 这个事件在读取结束后,无论成功或者失败都会触发
+            if (reader.error) {
+            } else {
+              // document.getElementById("bytesRead").textContent = file.size;
+              // 构造 XMLHttpRequest 对象,发送文件 Binary 数据
+              var xhr = new XMLHttpRequest();
+              xhr.open(
+                /* method */
+                "POST",
+                /* target url */
+                "/image-service/common/file_upload?systemId=dataPlatform&secret=9e0891a7a8c8e885&overwrite=true" + key
+                /*, async, default to true */
+              );
+              //xhr.overrideMimeType("application/octet-stream");
+              xhr.send(reader.result);
+              xhr.onreadystatechange = function () {
+                if (xhr.readyState == 4) {
+                  console.log(xhr)
+                  if (xhr.status == 200) {
+                    let fileObject = {
+                      key: key.split("=")[1],
+                      type,
+                      name: file.name,
+                      createTime: CreateTime,
+                      systemId: 'dataPlatform'
+                    }
+                    vm.filesArr.push(fileObject);
+                    this.loading =false
+                    vm.$emit("change", vm.filesArr, vm.defined);
+                  } else {
+                    this.$message.error(res.data.ResultMsg)
+                  }
+                }
+              };
+            }
+          };
+          reader.readAsArrayBuffer(file);
+        }
+
+      }
+    },
+    watch: {
+      keysArr: function (val) {
+        this.fileFalg()
+      }
+    }
+  };
+</script>
+
+<style lang="less">
+  #saga-upload {
+    .dill-image {
+      position: absolute;
+      right: 0;
+      top: 0;
+      font-size: 20px;
+    }
+
+    .el-upload-dragger {
+      width: 180px;
+      height: 180px;
+    }
+
+    img {
+      position: absolute;
+      top: 0;
+      bottom: 0;
+      left: 0;
+      right: 0;
+      width: 100%;
+      height: 100%;
+    }
+
+    #uploadFile {
+      .upload-file {
+        overflow: hidden;
+        .el-upload-dragger {
+          width: inherit;
+          height: inherit;
+          border: none;
+        }
+      }
+      .file-list {
+        .file-name {
+          display: inline-block;
+          width: 400px;
+          font-size: 12px;
+          color: #409eff;
+          overflow: hidden;
+          white-space: nowrap;
+          text-overflow: ellipsis;
+          vertical-align: top;
+          cursor: pointer;
+        }
+        .file-name:hover {
+          color: #66b1ff;
+        }
+      }
+    }
+  }
+</style>

+ 360 - 0
src/components/ledger/components/lib/uploadImgs.vue

@@ -0,0 +1,360 @@
+<!--
+    @param keysArr 承接数组
+    @param readOnly  删除按钮是否显示,上传是否显示
+    @param max     上传最大数限制
+    @click change  承接数组发生变化时触发函数
+-->
+<template>
+  <div id="sagaUploads">
+    <div class="saga-upload-images">
+      <div v-if="type != 'video'" class="point-image" v-for="(item,index) in imagesArr">
+        <i v-if="!readOnly" class="el-icon-delete" @click="delImage(index,item)"></i>
+        <img :src="imageGetUrl + '&key=' +item.key" @click="lookImg" alt v-load>
+      </div>
+      <div v-if="type == 'video'" class="point-image" v-for="(item,index) in imagesArr">
+        <i v-if="!readOnly" class="el-icon-delete" @click="delImage(index,item)"></i>
+        <video
+            width="100%"
+            height="100%;"
+            :src="imageGetUrl + '&key=' +item.key"
+            controls="controls"
+        >您的浏览器不支持 video 标签。
+        </video>
+      </div>
+      <div v-if="!readOnly" style="float:left;">
+        <el-upload
+            class="avatar-uploader"
+            :http-request="uploadAndSubmit"
+            :show-file-list="false"
+            :accept="accept"
+            action
+            drag
+            style="position: relative"
+        >
+          <i class="el-icon-plus avatar-uploader-icon"></i>
+        </el-upload>
+        <video style="display:none;" id="video" controls/>
+      </div>
+    </div>
+    <details-dialog :title="'图片'" :iframeSrc="iframeSrc" :dialog="dialog" :setData="imagesArr"></details-dialog>
+  </div>
+</template>
+<script>
+import tools from "@/utils/scan/tools";
+import detailsDialog from "@/components/business_space/lib/detailsDia"
+
+export default {
+  components: {
+    detailsDialog
+  },
+  props: {
+    keysArr: {
+      type: [Array, String]
+    },
+    readOnly: {
+      type: Boolean,
+      default: false
+      },
+      max: {
+        type: [Number, String],
+        default: 6
+      },
+      accept: {
+        type: String,
+        default: "image/*"
+      },
+      type: {
+        type: String,
+        default: "image"
+      },
+      defined: null,
+      videoPicArr: {
+        type: Array,
+        default: function () {
+          return []
+        }
+      }
+    },
+    data() {
+      return {
+        baseUrl: "",
+        imageGetUrl: "/image-service/common/image_get?systemId=dataPlatform",
+        imageUploadUrl: "/image-service/common/image_upload?systemId=dataPlatform&secret=9e0891a7a8c8e885&overwrite=true",
+        imagesArr: [],
+        iframeSrc: "",
+        dialog: {
+          details: false
+        }
+      };
+    },
+    created() {
+      this.imageFalg();
+    },
+    methods: {
+      //判断是否为空
+      imageFalg() {
+        let type = typeof this.keysArr;
+        if (type == "string") {
+          this.imagesArr = [this.keysArr];
+        } else {
+          this.imagesArr = tools.deepCopy(this.keysArr);
+        }
+
+        if (!this.keysArr) {
+          this.imagesArr = [];
+        }
+      },
+      //查看图片
+      lookImg() {
+        this.dialog.details = true
+        this.iframeSrc = 
+        // process.env.BASE_URL + 
+        window.__systemConf.BASE_URL +
+        ":8890/photo-View.html"
+        // this.iframeSrc = process.env.BASE_URL + "/photo-View.html"
+      },
+      //删除图片
+      delImage(i, key) {
+        if (this.type == "video") {
+          this.videoPicArr = this.videoPicArr.map(item => {
+            if (item.substring(0, item.length - 3) == this.imagesArr[i].substring(0, this.imagesArr[i].length - 3)) {
+              return undefined
+            } else {
+              return item
+            }
+          }).filter(p => p)
+        }
+        this.imagesArr.splice(i, 1);
+        this.$emit("change", this.imagesArr, this.defined, this.videoPicArr);
+      },
+
+      //上传
+      uploadAndSubmit(item, key) {
+        // var form = document.forms["demoForm"];
+
+        // if (form["file"].files.length > 0) {
+        // 寻找表单域中的 <input type="file" ... /> 标签
+        // var file = form["file"].files[0];
+
+        let file = item.file;
+        // try sending
+        let reader = new FileReader();
+
+        let vm = this;
+
+        let fileType = file.name.split(".");
+        let type = fileType[fileType.length - 1];
+        let CreateTime = tools.formatDate(new Date(file.lastModified))
+
+        let uploadKey = file.uid
+        if (!!key) {
+          uploadKey = key
+        }
+
+        reader.onloadstart = function () {
+          // 这个事件在读取开始时触发
+        };
+        reader.onprogress = function (p) {
+          // 这个事件在读取进行中定时触发
+        };
+
+        reader.onload = function () {
+          // 这个事件在读取成功结束后触发
+        };
+        reader.onloadend = function () {
+          // 这个事件在读取结束后,无论成功或者失败都会触发
+          if (reader.error) {
+          } else {
+            // document.getElementById("bytesRead").textContent = file.size;
+            // 构造 XMLHttpRequest 对象,发送文件 Binary 数据
+            var xhr = new XMLHttpRequest();
+            xhr.open(
+              /* method */
+              "POST",
+              /* target url */
+              vm.imageUploadUrl + "&key=" + uploadKey + "." + type
+              /*, async, default to true */
+            );
+            //xhr.overrideMimeType("application/octet-stream");
+            xhr.send(reader.result);
+            xhr.onreadystatechange = function () {
+              if (xhr.readyState == 4) {
+                if (xhr.status == 200) {
+                  if (vm.type == 'image') {
+                    vm.imagesArr.push({
+                      name: uploadKey + '',
+                      key: uploadKey + "." + type,
+                      systemId: "dataPlatform",
+                      type: "image",
+                      createTime: CreateTime
+                    });
+                  }
+                  if (type == 'mp4') {
+                    vm.imagesArr.push({
+                      name: uploadKey + '',
+                      key: uploadKey + "." + type,
+                      systemId: "dataPlatform",
+                      type: "video",
+                      createTime: CreateTime
+                    });
+                    vm.creatImg(vm.imageGetUrl + "&key=" + uploadKey + "." + type, uploadKey)
+                  }
+                  console.log(vm.type, type)
+                  if (vm.type == "video" && type == "png") {
+                    console.log("触发时评上传图片回调")
+                    vm.videoPicArr.push({
+                      name: uploadKey + '',
+                      key: uploadKey + "." + type,
+                      systemId: "dataPlatform",
+                      type: "image_video",
+                      createTime: CreateTime
+                    })
+                  }
+                  vm.$emit("change", vm.imagesArr, vm.defined, vm.videoPicArr);
+                }
+              }
+            };
+          }
+        };
+        reader.readAsArrayBuffer(file);
+      },
+      dataURLtoBlob: function (dataURI, type) {
+        var binary = atob(dataURI.split(',')[1]);
+        var array = [];
+        for (var i = 0; i < binary.length; i++) {
+          array.push(binary.charCodeAt(i));
+        }
+        return new Blob([new Uint8Array(array)], {type: type});
+      },
+
+      creatImg(reader, key) {
+        var videoDom = document.getElementById('video');
+        videoDom.src = reader;
+        let vm = this
+        videoDom.onloadeddata = function () {
+          // 这里可以打印视频时长
+          // 这里取得视频封面
+          var canvas = document.createElement('canvas');
+          canvas.width = 300;
+          canvas.height = 300 * this.videoHeight / this.videoWidth;
+          canvas.getContext('2d').drawImage(this, 0, 0, canvas.width, canvas.height);
+          //将canvas的base64位图片转换成图片png的file
+          var blob = vm.dataURLtoBlob(canvas.toDataURL('image/png'), "image/png")
+          //将其转换成file对象
+          var file = new File([blob], "video_image.png", {type: "image/png", lastModified: Date.now()})//blob转file
+          vm.uploadAndSubmit({file: file}, key)
+        }
+      },
+    },
+    watch: {
+      keysArr: function (val) {
+        this.imageFalg();
+      }
+    },
+    //自定义指令
+    directives: {
+      load: function (el) {
+        let imgDom = document.createElement("img");
+        imgDom.style.position = "absolute";
+        imgDom.style.top = "-999px";
+        imgDom.style.opacity = 0;
+        imgDom.src = el.src;
+        el.src = "";
+        imgDom.onload = () => {
+          let width = imgDom.width;
+          let height = imgDom.height;
+          if (width > height) {
+            el.style.height = "100%";
+            el.style.width = "auto";
+            el.style.position = "absolute";
+            el.style.left = "50%";
+            el.style.top = "0";
+            el.style.transform = "translateX(-50%)";
+            el.style.webkitTransform = "translateX(-50%) translateY(0)";
+            el.style.MozTransform = "translateX(-50%) translateY(0)";
+            el.style.msTransform = "translateX(-50%) translateY(0)";
+            el.style.OTransform = "translateX(-50%) translateY(0)";
+          } else if (width < height) {
+            el.src = imgDom.src;
+            el.style.width = "100%";
+            el.style.height = "auto";
+            el.style.position = "absolute";
+            el.style.top = "50%";
+            el.style.left = "0";
+            el.style.transform = "translateY(-50%) translateX(0)";
+            el.style.webkitTransform = "translateY(-50%) translateX(0)";
+            el.style.MozTransform = "translateY(-50%) translateX(0)";
+            el.style.msTransform = "translateY(-50%) translateX(0)";
+            el.style.OTransform = "translateY(-50%) translateX(0)";
+          } else {
+            el.style.width = "100%";
+            el.style.height = "100%";
+            el.style.position = "absolute";
+            el.style.top = "0";
+            el.style.left = "0";
+            el.style.transform = "translateY(0) translateX(0)";
+            el.style.webkitTransform = "translateY(0) translateX(0)";
+            el.style.MozTransform = "translateY(0) translateX(0)";
+            el.style.msTransform = "translateY(0) translateX(0)";
+            el.style.OTransform = "translateY(0) translateX(0)";
+          }
+          el.src = imgDom.src;
+        };
+      }
+    }
+  };
+</script>
+<style lang="less">
+  #sagaUploads {
+    overflow: hidden;
+
+    .avatar-uploader {
+      height: 180px;
+      width: 180px;
+      overflow: hidden;
+
+      .el-upload {
+        width: 180px;
+        height: 180px;
+
+        .el-upload-dragger {
+          width: 180px;
+          height: 180px;
+
+          .el-icon-plus {
+            display: block;
+            width: 20px;
+            height: 20px;
+            font-size: 20px;
+            margin: 80px;
+          }
+        }
+      }
+    }
+
+    .point-image {
+      width: 180px;
+      height: 180px;
+      float: left;
+      position: relative;
+      margin-right: 10px;
+      margin-bottom: 10px;
+      border: 1px solid #ccc;
+      overflow: hidden;
+
+      image {
+        z-index: 11;
+      }
+
+      i {
+        position: absolute;
+        bottom: 10px;
+        right: 10px;
+        background-color: #fff;
+        padding: 5px;
+        cursor: pointer;
+        z-index: 66;
+      }
+    }
+  }
+</style>

+ 377 - 0
src/components/ledger/components/lib/uploadImgsName.vue

@@ -0,0 +1,377 @@
+<!--
+    @param keysArr 承接数组
+    @param readOnly  删除按钮是否显示,上传是否显示
+    @param max     上传最大数限制
+    @click change  承接数组发生变化时触发函数
+-->
+<template>
+  <div id="sagaUploads">
+    <div class="saga-upload-images">
+      <div v-if="type != 'video'" class="point-view" v-for="(item,index) in imagesArr">
+        <div class="point-image">
+          <i v-if="!readOnly" class="el-icon-delete" @click="delImage(index,item)"></i>
+          <img @click="lookImg" :src="imageGetUrl + '&key=' +item.key" alt v-load>
+        </div>
+        <form-input :label="''" @change="getName" :keys="index" :value="item.name" :width="10"></form-input>
+      </div>
+      <div v-if="type == 'video'" class="point-view" v-for="(item,index) in imagesArr">
+        <div class="point-image">
+          <i v-if="!readOnly" class="el-icon-delete" @click="delImage(index,item)"></i>
+          <video
+            width="100%"
+            height="100%;"
+            :src="imageGetUrl + '&key=' +item.key"
+            controls="controls"
+          >您的浏览器不支持 video 标签。</video>
+        </div>
+        <form-input :label="''" @change="getName" :keys="index" :value="item.name" :width="10"></form-input>
+      </div>
+      <!--      v-if="!readOnly && imagesArr.length < max"-->
+      <div style="float:left;" v-loading="uploading">
+        <el-upload
+        	v-show="!readOnly"
+          class="avatar-uploader"
+          :http-request="uploadAndSubmit"
+          :show-file-list="false"
+          :accept="accept"
+          action
+          drag
+          style="position: relative"
+        >
+          <i class="el-icon-plus avatar-uploader-icon"></i>
+        </el-upload>
+        <video style="display:none;" id="video" controls/>
+      </div>
+    </div>
+    <details-dialog :title="'图片'" :iframeSrc="iframeSrc" :dialog="dialog" :setData="imagesArr"></details-dialog>
+  </div>
+</template>
+<script>
+import tools from "@/utils/scan/tools";
+import formInput from "@/components/business_space/lib/formInput"
+import detailsDialog from "@/components/business_space/lib/detailsDia"
+
+export default {
+  components: {
+    formInput,
+    detailsDialog
+  },
+  props: {
+    keysArr: {
+      type: [Array, String]
+    },
+    readOnly: {
+      type: Boolean,
+      default: false
+    },
+    max: {
+      type: [Number, String],
+      default: 6
+    },
+    accept: {
+      type: String,
+      default: "image/*"
+    },
+    type: {
+      type: String,
+      default: "image"
+    },
+    defined: null,
+    videoPicArr: {
+      type: Array,
+      default: function () {
+        return []
+      }
+    }
+  },
+  data() {
+    return {
+      baseUrl: "",
+      imageGetUrl: "/image-service/common/image_get?systemId=dataPlatform",
+      imageUploadUrl: "/image-service/common/image_upload?systemId=dataPlatform&secret=9e0891a7a8c8e885&overwrite=true",
+      imagesArr: [],
+      iframeSrc: "",
+      dialog: {
+        details: false
+      },
+      uploading: false
+    };
+  },
+  created() {
+    this.imageFalg();
+  },
+  methods: {
+    getName(name, index) {
+      this.imagesArr[index].name = name
+      this.$emit("change", this.imagesArr, this.defined, this.videoPicArr);
+    },
+    //判断是否为空
+    imageFalg() {
+      let type = typeof this.keysArr;
+      if (type == "string") {
+        this.imagesArr = [this.keysArr];
+      } else {
+        this.imagesArr = tools.copyArr(this.keysArr);
+      }
+
+      if (!this.keysArr) {
+        this.imagesArr = [];
+      }
+    },
+
+    //查看图片
+    lookImg() {
+      this.dialog.details = true
+      this.iframeSrc = 
+      // process.env.BASE_URL + 
+      window.__systemConf.BASE_URL +
+      ":8890/photo-View.html"
+      // this.iframeSrc = process.env.BASE_URL + "/photo-View.html"
+    },
+
+    //删除图片
+    delImage(i, key) {
+      if (this.type == "video") {
+        this.videoPicArr = this.videoPicArr.map(item => {
+          if (item.key.substring(0, item.length - 3) == this.imagesArr[i].key.substring(0, this.imagesArr[i].length - 3)) {
+            return undefined
+          } else {
+            return item
+          }
+        }).filter(p => p)
+      }
+      this.imagesArr.splice(i, 1);
+      this.$emit("change", this.imagesArr, this.defined, this.videoPicArr);
+    },
+
+    //上传
+    uploadAndSubmit(item, key) {
+      // var form = document.forms["demoForm"];
+
+      // if (form["file"].files.length > 0) {
+      // 寻找表单域中的 <input type="file" ... /> 标签
+      // var file = form["file"].files[0];
+
+      let file = item.file;
+      // try sending
+      let reader = new FileReader();
+
+      let vm = this;
+
+      let fileType = file.name.split(".");
+      let type = fileType[fileType.length - 1];
+      let CreateTime = tools.formatDate(new Date(file.lastModified))
+
+      let uploadKey = file.uid
+      if (!!key) {
+        uploadKey = key
+      }
+
+      reader.onloadstart = function () {
+        // 这个事件在读取开始时触发
+      };
+      reader.onprogress = function (p) {
+        // 这个事件在读取进行中定时触发
+      };
+
+      reader.onload = function () {
+        // 这个事件在读取成功结束后触发
+      };
+      reader.onloadend = function () {
+        // 这个事件在读取结束后,无论成功或者失败都会触发
+        if (reader.error) {
+        } else {
+          // document.getElementById("bytesRead").textContent = file.size;
+          // 构造 XMLHttpRequest 对象,发送文件 Binary 数据
+          vm.uploading = true;
+          var xhr = new XMLHttpRequest();
+          xhr.open(
+            /* method */
+            "POST",
+            /* target url */
+            vm.imageUploadUrl + "&key=" + uploadKey + "." + type
+            /*, async, default to true */
+          );
+          //xhr.overrideMimeType("application/octet-stream");
+          xhr.send(reader.result);
+          xhr.onreadystatechange = function () {
+            if (xhr.readyState == 4) {
+              if (xhr.status == 200) {
+                vm.uploading = false;
+                if (vm.type == 'image') {
+                  vm.imagesArr.push(
+                    {
+                      name: uploadKey + '',
+                      key: uploadKey + "." + type,
+                      systemId: "dataPlatform",
+                      type: "image",
+                      createTime: CreateTime
+                    }
+                  );
+                }
+                if (type == 'mp4') {
+                  vm.imagesArr.push({
+                      name: uploadKey + '',
+                      key: uploadKey + "." + type,
+                      systemId: "dataPlatform",
+                      type: "video",
+                      createTime: CreateTime
+                  }
+                  );
+                  vm.creatImg(vm.imageGetUrl + "&key=" + uploadKey + "." + type, uploadKey)
+                }
+                if (vm.type == "video" && type == "png") {
+                    console.log(vm.videoPicArr)
+                //   vm.videoPicArr.push({
+                //     name: uploadKey + '',
+                //     key: uploadKey + "." + type,
+                //     systemId: "dataPlatform",
+                //     type: "image_video",
+                //     createTime: CreateTime
+                //   })
+                }
+                vm.$emit("change", vm.imagesArr, vm.defined, vm.videoPicArr);
+              }
+            }
+          };
+        }
+      };
+      reader.readAsArrayBuffer(file);
+    },
+    dataURLtoBlob(dataURI, type) {
+      var binary = atob(dataURI.split(',')[1]);
+      var array = [];
+      for (var i = 0; i < binary.length; i++) {
+        array.push(binary.charCodeAt(i));
+      }
+      return new Blob([new Uint8Array(array)], { type: type });
+    },
+
+    creatImg(reader, key) {
+      var videoDom = document.getElementById('video');
+      videoDom.src = reader;
+      let vm = this
+      videoDom.onloadeddata = function () {
+        // 这里可以打印视频时长
+        // 这里取得视频封面
+        var canvas = document.createElement('canvas');
+        canvas.width = 300;
+        canvas.height = 300 * this.videoHeight / this.videoWidth;
+        canvas.getContext('2d').drawImage(this, 0, 0, canvas.width, canvas.height);
+        //将canvas的base64位图片转换成图片png的file
+        var blob = vm.dataURLtoBlob(canvas.toDataURL('image/png'), "image/png")
+        //将其转换成file对象
+        var file = new File([blob], "video_image.png", { type: "image/png", lastModified: Date.now() })//blob转file
+        vm.uploadAndSubmit({ file: file }, key)
+      }
+    },
+  },
+  watch: {
+    keysArr: function (val) {
+      this.imageFalg();
+    }
+  },
+  //自定义指令
+  directives: {
+    load: function (el) {
+      let imgDom = document.createElement("img");
+      imgDom.style.position = "absolute";
+      imgDom.style.top = "-999px";
+      imgDom.style.opacity = 0;
+      imgDom.src = el.src;
+      el.src = "";
+      imgDom.onload = () => {
+        let width = imgDom.width;
+        let height = imgDom.height;
+        if (width > height) {
+          el.style.height = "100%";
+          el.style.width = "auto";
+          el.style.position = "absolute";
+          el.style.left = "50%";
+          el.style.top = "0";
+          el.style.transform = "translateX(-50%)";
+          el.style.webkitTransform = "translateX(-50%) translateY(0)";
+          el.style.MozTransform = "translateX(-50%) translateY(0)";
+          el.style.msTransform = "translateX(-50%) translateY(0)";
+          el.style.OTransform = "translateX(-50%) translateY(0)";
+        } else if (width < height) {
+          el.src = imgDom.src;
+          el.style.width = "100%";
+          el.style.height = "auto";
+          el.style.position = "absolute";
+          el.style.top = "50%";
+          el.style.left = "0";
+          el.style.transform = "translateY(-50%) translateX(0)";
+          el.style.webkitTransform = "translateY(-50%) translateX(0)";
+          el.style.MozTransform = "translateY(-50%) translateX(0)";
+          el.style.msTransform = "translateY(-50%) translateX(0)";
+          el.style.OTransform = "translateY(-50%) translateX(0)";
+        } else {
+          el.style.width = "100%";
+          el.style.height = "100%";
+          el.style.position = "absolute";
+          el.style.top = "0";
+          el.style.left = "0";
+          el.style.transform = "translateY(0) translateX(0)";
+          el.style.webkitTransform = "translateY(0) translateX(0)";
+          el.style.MozTransform = "translateY(0) translateX(0)";
+          el.style.msTransform = "translateY(0) translateX(0)";
+          el.style.OTransform = "translateY(0) translateX(0)";
+        }
+        el.src = imgDom.src;
+      };
+    }
+  }
+};
+</script>
+<style lang="less">
+#sagaUploads {
+  overflow: hidden;
+  .avatar-uploader {
+    height: 180px;
+    width: 180px;
+    overflow: hidden;
+    .el-upload {
+      width: 180px;
+      height: 180px;
+      .el-upload-dragger {
+        width: 180px;
+        height: 180px;
+        .el-icon-plus {
+          display: block;
+          width: 20px;
+          height: 20px;
+          font-size: 20px;
+          margin: 80px;
+        }
+      }
+    }
+  }
+  .point-view {
+    float: left;
+    margin-right: 10px;
+    margin-bottom: 10px;
+    overflow: hidden;
+    height: 240px;
+  }
+  .point-image {
+    width: 180px;
+    height: 180px;
+    position: relative;
+    border: 1px solid #ccc;
+    overflow: hidden;
+    img {
+      z-index: 11;
+    }
+    i {
+      position: absolute;
+      bottom: 10px;
+      right: 10px;
+      background-color: #fff;
+      padding: 5px;
+      cursor: pointer;
+      z-index: 66;
+    }
+  }
+}
+</style>

+ 341 - 0
src/components/ledger/components/rentList/roomCountDrawer.vue

@@ -0,0 +1,341 @@
+<template>
+  <el-drawer title="关联的业务空间" :visible.sync="drawer" :direction="direction" :before-close="handleClose" size="20%" custom-class="room-count">
+    <div class="drawer-body" :style="{height:drawerBodyHeight}">
+      <div class="default-zone zone">
+        <p>默认业务空间</p>
+        <div v-if="!(readonly&&!zoneList.GeneralZone.length)">
+          <div v-for="(item,index) in zoneList.GeneralZone" :key="item.RoomID" class="zoneItem"
+            :title="`${item.BuildLocalName}-${item.FloorLocalName}-${item.RoomLocalName || item.RoomName}`">
+            {{item.BuildLocalName}}-{{item.FloorLocalName}}-{{item.RoomLocalName || item.RoomName}}
+            <i size="mini" class="el-icon-delete" title="删除" @click="deleteZoneItem('GeneralZone',index)" v-if="!readonly"></i>
+          </div>
+          <div class="zoneItem" style="border:none;padding:0;" v-if="!readonly">
+            <el-cascader placeholder="请选择" v-model="GeneralVal" :options="option.GeneralZone" :props="props" filterable @expand-change="expandChange"
+              @change="change" style="width:100%;">
+            </el-cascader>
+          </div>
+        </div>
+        <div v-else class="center">
+          <i class="icon-wushuju iconfont"></i>
+          暂无数据
+        </div>
+      </div>
+      <div class="tenant-zone zone">
+        <p>租户业务空间</p>
+        <div v-if="!(readonly&&!zoneList.TenantZone.length)">
+          <div v-for="(item,index) in zoneList.TenantZone" :key="item.RoomID" class="zoneItem"
+            :title="`${item.BuildLocalName}-${item.FloorLocalName}-${item.RoomLocalName || item.RoomName}`">
+            {{item.BuildLocalName}}-{{item.FloorLocalName}}-{{item.RoomLocalName || item.RoomName}}
+            <i size="mini" class="el-icon-delete" title="删除" @click="deleteZoneItem('TenantZone',index)" v-if="!readonly"></i>
+          </div>
+          <div class="zoneItem" style="border:none;padding:0;" v-if="!readonly">
+            <el-cascader placeholder="请选择" v-model="TenantVal" :options="option.TenantZone" :props="props" filterable
+              @expand-change="expandChangeTenantZone" @change="changeTenantZone" style="width:100%;">
+            </el-cascader>
+          </div>
+        </div>
+        <div v-else class="center">
+          <i class="icon-wushuju iconfont"></i>
+          暂无数据
+        </div>
+      </div>
+    </div>
+    <div class="btn" v-if="!readonly">
+      <el-button type="primary" @click="save">保存</el-button>
+    </div>
+  </el-drawer>
+</template>
+<script>
+import {
+  tnRelateTenantZone, //关联租户空间
+  tnRelateGeneralZone, //关联默认空间
+  tnRelateOneZone,// 关联业务空间
+  delTnRelateZone, // 删除关系业务空间与租户
+  // buildingQuery, //建筑查询
+  queryParentZone, //业务空间查询GeneralZone-TenantZone
+} from '@/api/scan/request'
+import { buildingQuery } from '@/api/object/build'
+
+import { mapGetters } from "vuex";
+export default {
+  data() {
+    return {
+      drawer: false,
+      direction: 'rtl',
+      zoneList: {
+        GeneralZone: [],
+        TenantZone: []
+      },
+      option: {
+        GeneralZone: [],
+        TenantZone: []
+      },
+      props: {
+        children: 'Floor'
+      },
+      GeneralVal: [],
+      TenantVal: [],
+      curTenantId: '',
+      drawerBodyHeight: ''
+    }
+  },
+  props: {
+    readonly: {
+      default: true
+    }
+  },
+  computed: {
+    ...mapGetters("layout", ["projectId"])
+  },
+  watch: {
+    projectId() {
+      this.init();
+    }
+  },
+  mounted() {
+    this.drawerBodyHeight = document.body.clientHeight - 152 + 'px'
+  },
+  created() {
+    this.init();
+  },
+  methods: {
+    init() {
+      this.getBuilding()
+    },
+    getBuilding() {
+      let pa = {
+        cascade: [{ name: 'floor', orders: 'floorSequenceID desc' }],
+        order: 'localName asc'
+      }
+      //todo 检测所有大小写
+      buildingQuery(pa, res => {
+        this.option.GeneralZone = res.content.map(build => {
+          build.value = build.id;
+          build.label = build.localName;
+          if (build.floor) {
+            build.floor = build.floor.map(floor => {
+              floor.value = floor.id;
+              floor.label = floor.localName;
+              floor.floor = [];
+              return floor;
+            });
+            build.floor.unshift({ value: 'noknowFloor', label: '未明确楼层', Floor: [] })
+          }
+          return build;
+        })
+        this.option.GeneralZone.unshift({ value: 'noknowBuild', label: '未明确建筑', Floor: [] });
+        this.option.TenantZone = JSON.parse(JSON.stringify(this.option.GeneralZone));
+      })
+    },
+    expandChange(val) {
+      this.middleware(val, "GeneralZone")
+    },
+    expandChangeTenantZone(val) {
+      this.middleware(val, "TenantZone")
+    },
+    middleware(val, type) {
+      let filters = '';
+      if (val[0] == 'noknowBuild') { //未明确建筑
+        filters = `buildingId isNull`
+        this.getZone(filters, type, val)
+      } else if (val[1]) {
+        if (val[1] == "noknowFloor") {
+          filters = `buildingId='${val[0]}';floorId isNull`
+        } else {
+          filters = `buildingId='${val[0]}';floorId='${val[1]}'`
+        }
+        this.getZone(filters, type, val)
+      }
+    },
+    getZone(filters, type, selected) {
+      if (!filters) return;
+      let params = {
+        Cascade: [{ Name: 'building' }, { Name: 'floor' }],
+        Filters: `${filters};ObjectType='${type}'`,
+        Orders: "RoomID asc",
+        PageSize: 1000,
+        Projection: ["RoomID", "RoomLocalName", "RoomName", "ObjectType", "BuildingId", "FloorId"]
+      }
+      queryParentZone(params, res => {
+        if (res.Content.length) {
+          let tempArr = res.Content.map(t => {
+            t.BuildLocalName = t.Building ? t.Building.BuildLocalName : '未明确建筑';
+            t.FloorLocalName = t.Floor ? t.Floor.FloorLocalName : '未明确楼层';
+            delete t.Floor;
+            t.value = JSON.parse(JSON.stringify(t));
+            t.label = t.RoomLocalName || t.RoomName;
+            return t;
+          })
+          if (selected.length == 1) {
+            this.option[type][0].Floor = tempArr;
+          } else {
+            for (let i = 0; i < this.option[type].length; i++) {
+              if (this.option[type][i].value == selected[0]) {
+                for (let j = 0; j < this.option[type][i].Floor.length; j++) {
+                  if (this.option[type][i].Floor[j].value == selected[1]) {
+                    this.option[type][i].Floor[j].Floor = tempArr;
+                  }
+                }
+              }
+            }
+          }
+        }
+      })
+    },
+    change(val) {
+      let zoneObj = val[val.length - 1];
+      if (zoneObj.BuildLocalName == '未明确建筑') {
+        zoneObj.FloorLocalName = ''
+      }
+      if (this.zoneList.GeneralZone.findIndex((item) => (item.RoomID == zoneObj.RoomID)) == -1) {
+        this.zoneList.GeneralZone.push(zoneObj);
+        this.relateGeneral('GeneralZone', zoneObj.RoomID)
+      } else {
+        this.$message.warning('已有业务空间,请重新选择')
+      }
+      this.GeneralVal = [];
+    },
+    changeTenantZone(val) {
+      let zoneObj = val[val.length - 1];
+      if (zoneObj.BuildLocalName == '未明确建筑') {
+        zoneObj.FloorLocalName = ''
+      }
+      if (this.zoneList.TenantZone.findIndex((item) => (item.RoomID == zoneObj.RoomID)) == -1) {
+        this.zoneList.TenantZone.push(zoneObj);
+        this.relateGeneral('TenantZone', zoneObj.RoomID)
+      } else {
+        this.$message.warning('已有业务空间,请重新选择')
+      }
+      this.TenantVal = [];
+    },
+    showDrawer(tenant) {
+      this.curTenantId = tenant.TenantID;
+      if (tenant.ZoneGeneraltList) {
+        this.zoneList.GeneralZone = tenant.ZoneGeneraltList.map(t => {
+          if (t.Building) {
+            t.BuildLocalName = t.Building.BuildLocalName || t.Building.BuildName;
+          } else {
+            t.BuildLocalName = '未明确建筑'
+          }
+          if (t.Floor) {
+            t.FloorLocalName = t.Floor.FloorLocalName || t.Floor.FloorName;
+          } else {
+            t.FloorLocalName = '未明确楼层'
+          }
+          if (t.BuildLocalName == '未明确建筑') {
+            t.FloorLocalName = ''
+          }
+          return t;
+        });
+      } else {
+        this.zoneList.GeneralZone = [];
+      }
+      if (tenant.ZoneTenantList) {
+        this.zoneList.TenantZone = tenant.ZoneTenantList.map(t => {
+          if (t.Building) {
+            t.BuildLocalName = t.Building.BuildLocalName || t.Building.BuildName;
+          } else {
+            t.BuildLocalName = '未明确建筑'
+          }
+          if (t.Floor) {
+            t.FloorLocalName = t.Floor.FloorLocalName || t.Floor.FloorName;
+          } else {
+            t.FloorLocalName = '未明确楼层'
+          }
+          if (t.BuildLocalName == '未明确建筑') {
+            t.FloorLocalName = ''
+          }
+          return t;
+        });
+      } else {
+        this.zoneList.TenantZone = [];
+      }
+      this.drawer = true;
+    },
+    handleClose(done) {
+      this.zoneList.GeneralZone = [];
+      this.zoneList.GeneralZone = [];
+      this.$emit('refresh');
+      done();
+    },
+    // 删除
+    deleteZoneItem(type, index) {
+      let pa = {
+        type: type,
+        data: [
+          {
+            SpaceID: this.zoneList[type][index].RoomID,
+            TenantID: this.curTenantId
+          }
+        ]
+      }
+      this.zoneList[type].splice(index, 1);
+      delTnRelateZone(pa, res => { })
+    },
+    // 关联默认空间
+    relateGeneral(type, SpaceId) {
+      let pa = {
+        type: type,
+        data: {
+          Content: [
+            {
+              SpaceId: SpaceId,
+              TenantID: this.curTenantId
+            }
+          ]
+        }
+      }
+      tnRelateOneZone(pa, res => { })
+    },
+    save() {
+      this.$emit('refresh');
+      this.drawer = false;
+    }
+  }
+}
+</script>
+<style lang="less" scoped>
+.room-count {
+  .drawer-body {
+    position: relative;
+    padding: 10px 10px 30px;
+    overflow-y: auto;
+    .zone {
+      padding: 6px 14px 22px;
+      box-shadow: 0 0 3px #cacaca;
+      p {
+        margin-bottom: 4px;
+      }
+      .zoneItem {
+        position: relative;
+        padding: 2px 34px 2px 10px;
+        border: 1px solid #ccc;
+        border-radius: 4px;
+        white-space: nowrap;
+        overflow: hidden;
+        text-overflow: ellipsis;
+        i {
+          position: absolute;
+          right: 10px;
+          top: 50%;
+          transform: translateY(-50%);
+          cursor: pointer;
+        }
+      }
+      .zoneItem + .zoneItem {
+        margin-top: 6px;
+      }
+    }
+    .zone + .zone {
+      margin-top: 10px;
+    }
+  }
+  .btn {
+    position: absolute;
+    width: calc(100% - 20px);
+    bottom: 20px;
+    right: 20px;
+    text-align: right;
+  }
+}
+</style>

+ 464 - 0
src/components/ledger/components/report/dataexport.vue

@@ -0,0 +1,464 @@
+<template>
+    <div>
+        <el-select v-model="value" placeholder="请选择" clearable>
+            <el-option v-for="item in options" :key="item.value" :label="item.label" :value="item.value"></el-option>
+        </el-select>
+        <el-button @click="handleClickDownload" :disabled="!value" type="primary" :loading="loading" style="margin-left: 15px; margin-right: 10px"
+            >导出Excel</el-button
+        >
+    </div>
+</template>
+
+<script>
+import jsontoExcel from "@/utils/JOSNToExcel";
+import { State } from "@/data/mapTable";
+import { queryProperty, dBindingPoint, dEquipCount, dEquipExport, dSystemExport } from "@/api/scan/request";
+import { queryEquip } from "@/api/object/equip";
+import { mapGetters } from "vuex";
+
+export default {
+    data() {
+        return {
+            loading: false,
+            value: "",
+            dataList: [],
+            options: [
+                {
+                    value: "header1",
+                    label: "已建立关联的设备资产",
+                },
+                {
+                    value: "header2",
+                    label: "未关联资产的设备",
+                },
+                {
+                    value: "header3",
+                    label: "未关联设备的资产",
+                },
+                {
+                    value: "header4",
+                    label: "设备类-交付统计",
+                },
+                {
+                    value: "header5",
+                    label: "设备实例交付",
+                },
+                {
+                    value: "header6",
+                    label: "设备已交付静态信息点",
+                },
+                {
+                    value: "header7",
+                    label: "设备已交付iot信息点",
+                },
+                {
+                    value: "header8",
+                    label: "系统已交付静态信息点",
+                },
+                {
+                    value: "header9",
+                    label: "系统已交付iot信息点",
+                },
+            ],
+            header1: [
+                {
+                    fieldName: "building.localName",
+                    headerName: "建筑",
+                },
+                {
+                    fieldName: "floor.localName",
+                    headerName: "楼层",
+                },
+                {
+                    fieldName: "equipCategory.name",
+                    headerName: "设备类",
+                },
+                {
+                    fieldName: "localName",
+                    headerName: "设备本地名称",
+                },
+                {
+                    fieldName: "localId",
+                    headerName: "设备本地编码",
+                },
+                {
+                    fieldName: "id",
+                    headerName: "设备id",
+                },
+                {
+                    fieldName: "bimId",
+                    headerName: "设备BIMID",
+                },
+                {
+                    fieldName: "property.equipFamilyList.0.familyName",
+                    headerName: "资产设备族",
+                },
+                {
+                    fieldName: "property.localName",
+                    headerName: "资产本地名称",
+                },
+                {
+                    fieldName: "property.localId",
+                    headerName: "资产本地编码",
+                },
+                {
+                    fieldName: "property.id",
+                    headerName: "资产id",
+                },
+                {
+                    fieldName: "property.createTime",
+                    headerName: "资产创建时间",
+                },
+                {
+                    fieldName: "property.codeType",
+                    headerName: "资产现场验证状态",
+                },
+                {
+                    fieldName: "infos.installLocation",
+                    headerName: "安装位置",
+                },
+            ],
+            header2: [
+                {
+                    fieldName: "building.localName",
+                    headerName: "建筑",
+                },
+                {
+                    fieldName: "floor.localName",
+                    headerName: "楼层",
+                },
+                {
+                    fieldName: "equipCategory.name",
+                    headerName: "设备类",
+                },
+                {
+                    fieldName: "localName",
+                    headerName: "设备本地名称",
+                },
+                {
+                    fieldName: "localId",
+                    headerName: "设备本地编码",
+                },
+                {
+                    fieldName: "id",
+                    headerName: "设备id",
+                },
+                {
+                    fieldName: "bimId",
+                    headerName: "设备BIMID",
+                },
+                {
+                    fieldName: "property.equipFamilyList.0.familyName",
+                    headerName: "资产设备族",
+                },
+                {
+                    fieldName: "property.localName",
+                    headerName: "资产本地名称",
+                },
+                {
+                    fieldName: "property.localId",
+                    headerName: "资产本地编码",
+                },
+                {
+                    fieldName: "property.id",
+                    headerName: "资产id",
+                },
+                {
+                    fieldName: "property.createTime",
+                    headerName: "资产创建时间",
+                },
+                {
+                    fieldName: "property.codeType",
+                    headerName: "资产现场验证状态",
+                },
+                {
+                    fieldName: "infos.installLocation",
+                    headerName: "安装位置",
+                },
+            ],
+            header3: [
+                {
+                    fieldName: "building.localName",
+                    headerName: "建筑",
+                },
+                {
+                    fieldName: "floor.localName",
+                    headerName: "楼层",
+                },
+                {
+                    fieldName: "equipment.equipCategory.name",
+                    headerName: "设备类",
+                },
+                {
+                    fieldName: "equipment.localName",
+                    headerName: "设备本地名称",
+                },
+                {
+                    fieldName: "equipment.localId",
+                    headerName: "设备本地编码",
+                },
+                {
+                    fieldName: "equipment.id",
+                    headerName: "设备id",
+                },
+                {
+                    fieldName: "equipment.bimId",
+                    headerName: "设备BIMID",
+                },
+                {
+                    fieldName: "equipFamilyList.0.familyName",
+                    headerName: "资产设备族",
+                },
+                {
+                    fieldName: "localName",
+                    headerName: "资产本地名称",
+                },
+                {
+                    fieldName: "localId",
+                    headerName: "资产本地编码",
+                },
+                {
+                    fieldName: "id",
+                    headerName: "资产id",
+                },
+                {
+                    fieldName: "createTime",
+                    headerName: "资产创建时间",
+                },
+                {
+                    fieldName: "codeType",
+                    headerName: "资产现场验证状态",
+                },
+                {
+                    fieldName: "infos.installLocation",
+                    headerName: "安装位置",
+                },
+            ],
+        };
+    },
+    computed: {
+        ...mapGetters("layout", ["projectId", "projectName"]),
+    },
+    methods: {
+        formatDate(now) {
+            let year = now.getFullYear();
+            let month = now.getMonth() + 1;
+            let date = now.getDate();
+            let hour = now.getHours();
+            let minute = now.getMinutes();
+            let second = now.getSeconds();
+            return (
+                year +
+                (month > 10 ? month : "0" + month) +
+                (date > 10 ? date : "0" + date) +
+                hour +
+                (minute > 10 ? minute : "0" + minute) +
+                (second > 10 ? second : "0" + second)
+            );
+        },
+        handleClickDownload() {
+            this.loading = true;
+            this.dataList = [];
+            if (this.value == "header1") {
+                this.getheader1Data();
+            } else if (this.value == "header2") {
+                this.getheader2Data();
+            } else if (this.value == "header3") {
+                this.getheader3Data();
+            } else if (this.value == "header4") {
+                this.getheader4Data();
+            } else if (this.value == "header5") {
+                this.getheader5Data();
+            } else if (this.value == "header6") {
+                this.getheader6Data();
+            } else if (this.value == "header7") {
+                this.getheader7Data();
+            } else if (this.value == "header8") {
+                this.getheader8Data();
+            } else if (this.value == "header9") {
+                this.getheader9Data();
+            }
+        },
+        getheader1Data(pageNum) {
+            pageNum = pageNum ? pageNum : 1;
+            let params = {
+                cascade: [
+                    {
+                        name: "equipCategory",
+                        // projection: ["equipCode", "name"]
+                    },
+                    {
+                        name: "property",
+                        cascade: [
+                            {
+                                name: "equipFamilyList",
+                                // projection: [ "family", "familyName" ]
+                            },
+                        ],
+                        // projection: ["family", "localName", "localId", "id", "codeType", "createTime"]
+                    },
+                    {
+                        name: "building",
+                        // projection: ["localName", "name", "id"]
+                    },
+                    {
+                        name: "floor",
+                        // projection: ["localName", "name", "id"]
+                    },
+                ],
+                filters: "not propertyId isnull",
+                orders: "createTime desc, id asc",
+                pageNumber: pageNum,
+                pageSize: 1000,
+                // projection: ["createTime", "buildingId", "floorId", "classCode", "localName", "localId", "id", "bimId", "infos"]
+            };
+            queryEquip(params, (res) => {
+                this.dataList = this.dataList.concat(
+                    res.content.map((item) => {
+                        item.property.codeType = State[item.property.codeType];
+                        // item.property.Family = Family[item.property.Family]
+                        return item;
+                    })
+                );
+                if (res.total / (res.pageSize * res.pageNumber) > 1) {
+                    this.getheader1Data(res.pageNumber + 1);
+                } else {
+                    let time = +new Date();
+                    this.loading = false;
+                    jsontoExcel.downloadExl(this.header1, this.dataList, `已建立关联的资产设备-${time}`);
+                }
+            });
+        },
+        getheader2Data(pageNum) {
+            pageNum = pageNum ? pageNum : 1;
+            let params = {
+                cascade: [
+                    {
+                        name: "equipCategory",
+                        // projection: ["equipCode", "name"]
+                    },
+                    {
+                        name: "building",
+                        // projection: ["localName", "name", "id"]
+                    },
+                    {
+                        name: "floor",
+                        // projection: ["localName", "name", "id"]
+                    },
+                ],
+                filters: " propertyId isnull ",
+                orders: "createTime desc, id asc",
+                pageNumber: pageNum,
+                pageSize: 1000,
+                // projection: ["createTime", "buildingId", "floorId", "classCode", "localName", "localId", "id", "bimId", "infos"]
+            };
+            queryEquip(params, (res) => {
+                this.dataList = this.dataList.concat(res.content);
+                if (res.total / (res.pageSize * res.pageNumber) > 1) {
+                    this.getheader2Data(res.pageNumber + 1);
+                } else {
+                    let time = +new Date();
+                    this.loading = false;
+                    jsontoExcel.downloadExl(this.header2, this.dataList, `未关联资产的设备-${time}`);
+                }
+            });
+        },
+        getheader3Data(pageNum) {
+            pageNum = pageNum ? pageNum : 1;
+            let params = {
+                cascade: [
+                    {
+                        name: "equipFamilyList",
+                        // projection: [ "family", "familyName" ]
+                    },
+                    {
+                        name: "building",
+                        // projection: [ "localName", "name", "id" ]
+                    },
+                    {
+                        name: "floor",
+                        // projection: [ "localName", "name", "id" ]
+                    },
+                ],
+                filters: "id isnull",
+                orders: "createTime desc, id asc",
+                pageNumber: pageNum,
+                pageSize: 1000,
+                // projection: [ "buildingId", "floorId", "family", "localName", "localId", "id", "createTime", "codeType", "infos"]
+            };
+            queryProperty(params, (res) => {
+                this.dataList = this.dataList.concat(
+                    res.content.map((item) => {
+                        item.codeType = State[item.codeType];
+                        return item;
+                    })
+                );
+                if (res.total / (res.pageSize * res.pageNumber) > 1) {
+                    this.getheader3Data(res.pageNumber + 1);
+                } else {
+                    let time = +new Date();
+                    this.loading = false;
+                    jsontoExcel.downloadExl(this.header3, this.dataList, `未关联设备的资产-${time}`);
+                }
+            });
+        },
+        getheader4Data() {
+            dEquipCount(
+                { projectId: this.projectId },
+                () => {
+                    this.loading = false;
+                },
+                `${this.projectName}-设备类-交付统计-${this.formatDate(new Date())}.xlsx`
+            );
+        },
+        getheader5Data() {
+            dBindingPoint(
+                { projectId: this.projectId },
+                () => {
+                    this.loading = false;
+                },
+                `${this.projectName}-设备实例交付-${this.formatDate(new Date())}.xlsx`
+            );
+        },
+        getheader6Data() {
+            dEquipExport(
+                { type: "static", projectId: this.projectId },
+                () => {
+                    this.loading = false;
+                },
+                `${this.projectName}-设备已交付静态信息点-${this.formatDate(new Date())}.xlsx`
+            );
+        },
+        getheader7Data() {
+            dEquipExport(
+                { type: "iot", projectId: this.projectId },
+                () => {
+                    this.loading = false;
+                },
+                `${this.projectName}-设备已交付iot信息点-${this.formatDate(new Date())}.xlsx`
+            );
+        },
+        getheader8Data() {
+            dSystemExport(
+                { type: "static", projectId: this.projectId },
+                () => {
+                    this.loading = false;
+                },
+                `${this.projectName}-系统已交付静态信息点-${this.formatDate(new Date())}.xlsx`
+            );
+        },
+        getheader9Data() {
+            dSystemExport(
+                { type: "iot", projectId: this.projectId },
+                () => {
+                    this.loading = false;
+                },
+                `${this.projectName}-系统已交付iot信息点-${this.formatDate(new Date())}.xlsx`
+            );
+        },
+    },
+};
+</script>
+
+<style scoped>
+</style>

+ 82 - 0
src/components/ledger/components/report/datafan.vue

@@ -0,0 +1,82 @@
+<template>
+  <div class="saga-fill">
+    <div class="mess-view">
+      <el-tooltip class="item" effect="dark" :content="renderData.tips" placement="bottom-start">
+        <i class="el-icon-info"><span>{{renderData.title}}</span></i>
+      </el-tooltip>
+    </div>
+    <div class="doughnut-view">
+      <fan-chart v-if="renderData.needCountO" type="type" width="200" height="200" :name="renderData.title" :renderData="renderData.needCountO" :id="id">
+      </fan-chart>
+    </div>
+    <slot></slot>
+  </div>
+</template>
+
+<script>
+import fanChart from "@/components/echarts/fanChart"
+export default {
+  data() {
+    return {
+      echartsData: [],
+      color: ["#AB7948", "#CAA679", "#DEC9A8", "#B3CFC4", "#7DAAA7", "#7484A6", "#9DABCB"]
+    }
+  },
+  created() {
+    this.echartsData = this.renderData.needCountO;
+    // this.echartsData.forEach((item, index) => {
+    //   item.itemStyle = {
+    //     color: this.color[index]
+    //   }
+    // })
+  },
+  components: {
+    fanChart
+  },
+  methods: {
+  },
+  props: {
+    id: {
+      type: String,
+      default: function createRandomId() {
+        return (Math.random() * 10000000).toString(16).substr(0, 4) + '-' + (new Date()).getTime() + '-' + Math.random().toString().substr(2, 5);
+      }
+    },
+    renderData: {
+      type: Object,
+      default: function () {
+        return {}
+      }
+    }
+  },
+}
+</script>
+
+<style lang="less" scoped>
+.mess-view {
+  box-sizing: border-box;
+  border-bottom: 1px solid #ddd;
+  padding: 10px 20px 10px 15px;
+  .saga-title {
+    font-size: 14px;
+    color: #333;
+    font-weight: 600;
+    line-height: 14px;
+  }
+  i {
+    span {
+      padding-left: 6px;
+    }
+  }
+}
+.saga-fill {
+  width: 345px;
+  // height: 220px;
+  .doughnut-view {
+    height: 165px;
+    width: 100%;
+    padding: 0 5px;
+    box-sizing: border-box;
+  }
+}
+</style>

+ 160 - 0
src/components/ledger/components/report/dataorigin.vue

@@ -0,0 +1,160 @@
+<template>
+    <div :class="filterClass ? 'saga-fill reset-fill' : 'saga-fill'">
+        <div class="mess-view">
+            <el-tooltip class="item" effect="dark" :content="renderData.tips" placement="bottom-start">
+                <i :class="renderData.title ? 'el-icon-info' : ''"
+                    ><span>{{ renderData.title }}</span></i
+                >
+            </el-tooltip>
+            <el-button style="float: right" v-if="renderData.downloadUrl" :loading="downLoading" @click="handleClickDownload(renderData.downloadUrl)"
+                >下载Excel报告</el-button
+            >
+        </div>
+        <div class="doughnut-view">
+            <report-doughnut
+                v-if="renderData.needCountO || renderData.needCountT"
+                type="type"
+                width="200"
+                height="200"
+                :sum="renderData.sum"
+                :name="renderData.title"
+                :renderData="echartsData"
+                :id="id"
+                :text="renderData.text"
+            >
+            </report-doughnut>
+        </div>
+        <p style="text-align: center">{{ renderData.val }}</p>
+        <slot></slot>
+    </div>
+</template>
+
+<script>
+import reportDoughnut from "@/components/echarts/reportDoughnut";
+import { mapGetters } from "vuex";
+import { eqNotSp } from "@/api/scan/request";
+
+export default {
+    name: "dataOrgin",
+    components: {
+        reportDoughnut,
+    },
+    props: {
+        id: {
+            type: String,
+            default: function createRandomId() {
+                return (
+                    (Math.random() * 10000000).toString(16).substr(0, 4) + "-" + new Date().getTime() + "-" + Math.random().toString().substr(2, 5)
+                );
+            },
+        },
+        renderData: {
+            type: Object,
+            default: function () {
+                return {};
+            },
+        },
+        filterClass: String,
+    },
+    data() {
+        return {
+            echartsData: [],
+            downLoading: false,
+        };
+    },
+    created() {
+        this.changeData();
+    },
+    computed: {
+        ...mapGetters("layout", ["projectId", "projectName"]),
+    },
+    methods: {
+        formatDate(now) {
+            let year = now.getFullYear();
+            let month = now.getMonth() + 1;
+            let date = now.getDate();
+            let hour = now.getHours();
+            let minute = now.getMinutes();
+            let second = now.getSeconds();
+            return (
+                year +
+                (month > 10 ? month : "0" + month) +
+                (date > 10 ? date : "0" + date) +
+                hour +
+                (minute > 10 ? minute : "0" + minute) +
+                (second > 10 ? second : "0" + second)
+            );
+        },
+        changeData() {
+            this.echartsData = [
+                {
+                    name: this.renderData.contentValueO + this.renderData.needCountO,
+                    value: this.renderData.needCountO,
+                },
+                {
+                    name: this.renderData.contentValueT + this.renderData.needCountT,
+                    value: this.renderData.needCountT,
+                },
+            ];
+        },
+        /**
+         * 根据url下载报告
+         *
+         */
+        handleClickDownload(url) {
+            this.downLoading = true;
+            eqNotSp(
+                { projectId: this.projectId },
+                () => {
+                    this.downLoading = false;
+                },
+                `${this.projectName || this.projectId}-设备所在物业空间-${this.formatDate(new Date())}.xlsx`
+            );
+        },
+    },
+    watch: {
+        renderData: {
+            deep: true,
+            handler: function () {
+                this.changeData();
+            },
+        },
+    },
+};
+</script>
+
+<style lang="less" scoped>
+.mess-view {
+    box-sizing: border-box;
+    border-bottom: 1px solid #ddd;
+    padding: 10px 20px 10px 15px;
+
+    .saga-title {
+        font-size: 14px;
+        color: #333;
+        font-weight: 600;
+        line-height: 14px;
+    }
+
+    i {
+        span {
+            padding-left: 6px;
+        }
+    }
+}
+
+.saga-fill {
+    width: 400px;
+    // height: 220px;
+    .doughnut-view {
+        height: 165px;
+        width: 100%;
+        padding: 0 5px;
+        box-sizing: border-box;
+    }
+}
+.reset-fill {
+    background: #fff;
+    width: 353px;
+}
+</style>

+ 199 - 0
src/components/ledger/components/system/dialog/addCenoteDialog.vue

@@ -0,0 +1,199 @@
+<template>
+  <el-dialog :title="title" :visible.sync="dialogVisible" width="900px" id="addEqDialog">
+    <el-row class="filters">
+      <el-col :span="7" style="width:268px">
+        <el-input placeholder="输入竖井名称或竖井本地编码进行查询" v-model="keycode" clearable @keyup.enter.native="getTableData">
+          <i slot="suffix" class="el-input__icon el-icon-search" @click="getTableData"></i>
+        </el-input>
+      </el-col>
+      <el-col :span="7.5">
+        <cenote-type @change="changeCenote"></cenote-type>
+      </el-col>
+    </el-row>
+    <div class="table-box">
+      <el-table :data="tableData" style="width: 100%" height="350" v-loading="loading" :header-cell-style="headerStyle"
+                ref="multipleTable"
+                @selection-change="handleSelectionChange">
+        <el-table-column type="selection" width="55"></el-table-column>
+        <el-table-column :label="`${inSpaceType}名称`" show-overflow-tooltip min-width="100">
+          <template slot-scope="scope">
+            <div>
+              {{ scope.row.localName || scope.row.name || '' }}
+            </div>
+          </template>
+        </el-table-column>
+        <el-table-column prop="localId" :label="`${inSpaceType}本地编码`" show-overflow-tooltip
+                         min-width="100"></el-table-column>
+        <el-table-column prop="infos.shaftFuncType" :label="`${inSpaceType}类`" show-overflow-tooltip
+                         min-width="100"></el-table-column>
+        <el-table-column prop="action" label="操作" min-width="100">
+          <template slot-scope="scope">
+            <el-button size="mini" @click="toDetail(scope.$index, scope.row)" plain>查看详情</el-button>
+          </template>
+        </el-table-column>
+      </el-table>
+      <!-- 分页 -->
+      <el-pagination class="fr" v-show="tableData && tableData.length" @size-change="handleSizeChange"
+                     @current-change="handleCurrentChange"
+                     :current-page="page.pageNumber" :page-sizes="page.pageSizes" :page-size="page.pageSize"
+                     layout="total, sizes, prev, pager, next, jumper"
+                     :total="page.total"></el-pagination>
+    </div>
+    <span slot="footer" class="dialog-footer">
+      <el-button size="small" @click="dialogVisible = false">取 消</el-button>
+      <el-button size="small" type="primary" @click="savaRelation">确 定</el-button>
+    </span>
+  </el-dialog>
+</template>
+
+<script>
+import { linkSysSh, unSysShaft } from "@/api/scan/request";
+import cenoteType from "@/components/ledger/lib/cenoteType";
+import { mapGetters } from "vuex";
+import { getDataDictionary } from "@/api/dict"
+
+export default {
+  components: {
+    cenoteType
+  },
+  computed: {
+    ...mapGetters("layout", ["projectId"])
+  },
+  data() {
+    return {
+      title: "添加系统所在竖井",
+      keycode: '', //输入查询条件
+      cenoteType: '', // 选中的竖井类型
+      shaftType: {}, //全部竖井类型
+      inSpaceType: '竖井',
+      dialogVisible: false,
+      tableData: [],
+      loading: false,
+      selections: [], // 选中项
+      page: {
+        pageSize: 50,
+        pageSizes: [10, 20, 50, 100],
+        pageNumber: 1,
+        total: 0
+      },
+      headerStyle: {
+        backgroundColor: '#e1e4e5',
+        color: '#2b2b2b',
+        lineHeight: '30px'
+      } // 列表样式
+    };
+  },
+  props: {
+    type: String, //选中的tab页
+    params: Object //查看的竖井关系信息
+  },
+  created() {
+    let param = {
+      "pageNumber": 1,
+      "pageSize": 50,
+      "filters": "code = 'shaftFuncType'",
+      "type": "shaft",
+      "orders": "sort asc, name desc",
+    }
+    getDataDictionary(param, res => {
+      res.content.forEach(item => {
+        item.dataSource && item.dataSource.forEach(i => {
+          this.shaftType[i.code] = i.name
+        })
+      })
+    })
+  },
+  methods: {
+    //修改竖井类型
+    changeCenote(value) {
+      this.cenoteType = value.id
+      this.getTableData()
+    },
+    // 显示弹窗
+    showDialog() {
+      this.dialogVisible = true
+      this.page.pageNumber = 1
+      this.tableData = []
+      this.getTableData()
+    },
+    getTableData() {
+      let params = {
+        data: {
+          filters: this.cenoteType ? `projectId='${this.projectId}';infos.shaftFuncType='${this.cenoteType}'` : `projectId='${this.projectId}'`,
+          orders: "createTime desc, id asc",
+          pageNumber: this.page.pageNumber,
+          pageSize: this.page.pageSize,
+        },
+        shaftId: this.params.ShaftID,
+        sysId: this.$route.query.SysID
+      }
+      if (this.keycode != '') {
+        params.data.filters += `;name contain '${this.keycode}' or localName contain '${this.keycode}' or localId contain '${this.keycode}'`
+      }
+      unSysShaft(params, res => {
+        this.tableData = res.content
+        this.tableData.forEach(item => {
+          if (item.infos && item.infos.shaftFuncType) {
+            item.infos.shaftFuncType = this.shaftType[item.infos.shaftFuncType]
+          }
+        })
+        this.page.total = res.total
+      })
+    },
+    //选中项修改
+    handleSelectionChange(val) {
+      this.selections = val;
+    },
+    savaRelation() {
+      if (this.selections.length) {
+        let params = {
+          sysId: this.$route.query.SysID,
+          shaftIdList: this.selections.map(item => {
+            return item.id
+          })
+        }
+        linkSysSh(params, res => {
+          this.dialogVisible = false
+          this.$message.success('关联成功!')
+          this.$emit('refresh')
+        })
+      } else {
+        this.$message('请选择要关联的竖井')
+      }
+    },
+    //改变pagesize
+    handleSizeChange(pageSize) {
+      this.page.pageSize = pageSize;
+      this.getTableData();
+    },
+    //改变pageno
+    handleCurrentChange(pageNo) {
+      this.page.pageNumber = pageNo;
+      this.getTableData();
+    },
+    // 查看详情
+    toDetail() {
+      this.$message('开发中')
+    }
+  },
+};
+</script>
+<style lang="less" scoped>
+#addEqDialog {
+  .filters {
+    margin-bottom: 10px;
+
+    /deep/ .el-input__inner {
+      vertical-align: baseline;
+    }
+  }
+
+  .table-box {
+    height: 370px;
+
+    .fr {
+      margin-top: 10px;
+    }
+  }
+}
+</style>

+ 287 - 0
src/components/ledger/components/system/dialog/addEquipDialog.vue

@@ -0,0 +1,287 @@
+<template>
+    <el-dialog :title="title" :visible.sync="dialogVisible" width="900px" id="addEqDialog">
+        <el-row class="filters">
+            <el-col :span="7" style="width: 268px">
+                <el-input placeholder="输入设备名称、设备本地编码或数字化交付编码进行查询" v-model="keycode" clearable @clear="inputMsg" @keyup.enter.native="inputMsg">
+                    <i slot="suffix" class="el-input__icon el-icon-search" @click="inputMsg"></i>
+                </el-input>
+            </el-col>
+            <el-col :span="8.5" style="padding: 0 0">
+                <floor-cascader @change="changeFloor" style="margin-left: 5px"></floor-cascader>
+            </el-col>
+            <el-col :span="8.5" style="padding-right: 0">
+                <myCascader @change="changeDevice" :params="childParams"></myCascader>
+            </el-col>
+        </el-row>
+
+        <div class="table-box">
+            <el-table
+                :data="tableData"
+                style="width: 100%"
+                height="350"
+                v-loading="loading"
+                :header-cell-style="headerStyle"
+                ref="multipleTable"
+                @selection-change="handleSelectionChange"
+            >
+                <el-table-column type="selection" width="55"></el-table-column>
+                <el-table-column :label="`${inSpaceType}名称`" show-overflow-tooltip min-width="100">
+                    <template slot-scope="scope">
+                        <div>{{ scope.row.localName || scope.row.name || "" }}</div>
+                    </template>
+                </el-table-column>
+                <el-table-column prop="localId" :label="`${inSpaceType}本地编码`" show-overflow-tooltip min-width="100"></el-table-column>
+                <el-table-column :label="`数字化交付编码`" show-overflow-tooltip min-width="100">
+                    <template slot-scope="scope">
+                        <div>{{ scope.row.infos ? (scope.row.infos.digitalDeliveryID ? scope.row.infos.digitalDeliveryID : "") : "" }}</div>
+                    </template>
+                </el-table-column>
+                <el-table-column prop="equipCategory.name" :label="`${inSpaceType}类`" show-overflow-tooltip min-width="100"></el-table-column>
+                <el-table-column prop="action" label="操作" min-width="80">
+                    <template slot-scope="scope">
+                        <el-button size="mini" @click="toDetail(scope.$index, scope.row)" plain>查看详情</el-button>
+                    </template>
+                </el-table-column>
+            </el-table>
+            <!-- 分页 -->
+            <el-pagination
+                class="fr"
+                v-show="tableData && tableData.length"
+                @size-change="handleSizeChange"
+                @current-change="handleCurrentChange"
+                :current-page="page.pageNumber"
+                :page-sizes="page.pageSizes"
+                :page-size="page.pageSize"
+                layout="total, sizes, prev, pager, next, jumper"
+                :total="page.total"
+            ></el-pagination>
+        </div>
+        <span slot="footer" class="dialog-footer">
+            <el-button
+                size="small"
+                type="primary"
+                title="选择设备类型后生效"
+                style="float: left"
+                :disabled="linkBtnDisabled"
+                :loading="linkBtnLoading"
+                @click="handleClickLink"
+                >添加全部</el-button
+            >
+            <el-button size="small" @click="dialogVisible = false">取 消</el-button>
+            <el-button size="small" type="primary" @click="savaRelation">确 定</el-button>
+        </span>
+    </el-dialog>
+</template>
+
+<script>
+import { sysLinkEquip, unSysEq, sysBatchLinkEquip } from "@/api/scan/request";
+import floorCascader from "@/components/ledger/lib/floorCascader";
+import myCascader from "@/components/ledger/system/lib/equipType";
+import { mapGetters } from "vuex";
+
+export default {
+    components: {
+        floorCascader,
+        myCascader,
+    },
+
+    data() {
+        return {
+            title: "添加系统内设备",
+            keycode: "", //输入查询条件
+            Buildfloor: ["all"], // 选中的建筑楼层
+            Equipcategory: null, // 选中的设备类型
+            inSpaceType: "设备",
+            dialogVisible: false,
+            tableData: [],
+            loading: false,
+            linkBtnDisabled: true,
+            linkBtnLoading: false,
+            selections: [], // 选中项
+            page: {
+                pageSize: 50,
+                pageSizes: [10, 20, 50, 100],
+                pageNumber: 1,
+                total: 0,
+            },
+            headerStyle: {
+                backgroundColor: "#e1e4e5",
+                color: "#2b2b2b",
+                lineHeight: "30px",
+            }, // 列表样式,
+        };
+    },
+    computed: {
+        ...mapGetters("layout", ["projectId"]),
+        childParams() {
+            let temp = JSON.parse(JSON.stringify(this.params));
+            return temp;
+        },
+    },
+    props: {
+        type: String, //选中的tab页
+        params: Object, //查看的竖井关系信息
+    },
+    created() {},
+    methods: {
+        //修改建筑楼层
+        changeFloor(value) {
+            this.Buildfloor = value;
+            this.page.pageNumber = 1;
+            this.getTableData();
+        },
+        // 显示弹窗
+        showDialog() {
+            this.dialogVisible = true;
+            this.page.pageNumber = 1;
+            this.tableData = [];
+            this.getTableData();
+        },
+        // 搜索框
+        inputMsg() {
+            this.page.pageNumber = 1;
+            this.getTableData();
+        },
+        getTableData() {
+            let params = {
+                data: {
+                    filters: `not id isNull`,
+                    cascade: [{ name: "equipCategory" }],
+                    orders: "createTime desc, id desc",
+                    pageNumber: this.page.pageNumber,
+                    pageSize: this.page.pageSize,
+                },
+                id: this.$route.query.SysID,
+                shaftId: this.params.ShaftID,
+            };
+            if (this.Buildfloor[0] == "noKnow") {
+                params.data.filters += `;buildingId isNull`;
+            } else if (this.Buildfloor[0] && this.Buildfloor[0] != "all") {
+                params.data.filters += `;buildingId='${this.Buildfloor[0]}'`;
+            }
+            if (this.Buildfloor[1] == "noKnow") {
+                params.data.filters += `;floorId isNull`;
+            } else if (this.Buildfloor[1] && this.Buildfloor[1] != "all") {
+                params.data.filters += `;floorId='${this.Buildfloor[1]}'`;
+            }
+            if (this.keycode != "") {
+                params.data.filters += `;name contain '${this.keycode}' or localName contain '${this.keycode}' or localId contain '${this.keycode}' or infos.digitalDeliveryID contain '${this.keycode}'`;
+            }
+            if (this.Equipcategory) {
+                this.linkBtnDisabled = false;
+                params.data.filters += `;classCode='${this.Equipcategory}'`;
+            } else {
+                this.linkBtnDisabled = true;
+            }
+            unSysEq(params, (res) => {
+                res.content.forEach((item) => {
+                    item.ShaftListName = "";
+                    if (item.shaftList && item.shaftList.length) {
+                        item.ShaftListName = item.shaftList
+                            .map((shaft) => {
+                                return shaft.localName ? shaft.localName : shaft.name;
+                            })
+                            .join("、");
+                    }
+                });
+                this.tableData = res.content;
+                this.page.total = res.total;
+            });
+        },
+        /**
+         * 按条件批量关联设备
+         */
+        handleClickLink() {
+            this.linkBtnLoading = true;
+            let params = {
+                classCode: this.Equipcategory,
+                sysId: this.$route.query.SysID,
+            };
+            if (this.Buildfloor[0] == "noKnow") {
+                params.buildingId = `isnull`;
+            } else if (this.Buildfloor[0] && this.Buildfloor[0] != "all") {
+                params.buildingId = `${this.Buildfloor[0]}`;
+            }
+            if (this.Buildfloor[1] == "noKnow") {
+                params.floorId = `isnull`;
+            } else if (this.Buildfloor[1] && this.Buildfloor[1] != "all") {
+                params.floorId = `${this.Buildfloor[1]}`;
+            }
+            if (this.keycode != "") {
+                params.vague = `${this.keycode}`;
+            }
+            sysBatchLinkEquip(params, (res) => {
+                if (res.result === "success") {
+                    this.linkBtnLoading = false;
+                    this.dialogVisible = false;
+                    this.$message.success("关联成功!");
+                    this.$emit("refresh");
+                } else {
+                    this.linkBtnLoading = false;
+                    this.$message.console.error(`关联失败!${res.message ? "失败原因:" + res.message : ""}`);
+                }
+            });
+        },
+        //选中项修改
+        handleSelectionChange(val) {
+            this.selections = val;
+        },
+        savaRelation() {
+            if (this.selections.length) {
+                let params = {
+                    sysId: this.$route.query.SysID,
+                    equipIdList: this.selections.map((item) => {
+                        return item.id;
+                    }),
+                };
+                sysLinkEquip(params, (res) => {
+                    this.dialogVisible = false;
+                    this.$message.success("关联成功!");
+                    this.$emit("refresh");
+                });
+            } else {
+                this.$message("请选择要关联的设备");
+            }
+        },
+        //修改设备类别
+        changeDevice(value) {
+            this.page.pageNumber = 1; //pageSize初始化
+            this.Equipcategory = value;
+            this.getTableData();
+        },
+        //改变pagesize
+        handleSizeChange(pageSize) {
+            this.page.pageSize = pageSize;
+            this.getTableData();
+        },
+        //改变pageno
+        handleCurrentChange(pageNo) {
+            this.page.pageNumber = pageNo;
+            this.getTableData();
+        },
+        // 查看详情
+        toDetail() {
+            this.$message("开发中");
+        },
+    },
+};
+</script>
+<style lang="less" scoped>
+#addEqDialog {
+    .filters {
+        margin-bottom: 10px;
+    }
+
+    .table-box {
+        height: 370px;
+
+        .fr {
+            margin-top: 10px;
+        }
+    }
+}
+
+/deep/ #buildFloor .buildFloor {
+    margin: 0 5px 0 5px;
+}
+</style>

+ 252 - 0
src/components/ledger/components/system/dialog/addSpaceDialog.vue

@@ -0,0 +1,252 @@
+<template>
+  <el-dialog :title="title" :visible.sync="dialog" :before-close="handleClose" width="900px" id="addEqDialog">
+    <el-row class="filters">
+      <el-col :span="7" style="width:268px;padding:0px;">
+        <el-input style placeholder="输入业务空间名称、本地编码进行查询" v-model="keycode" clearable @keyup.enter.native="getTableData">
+          <i slot="suffix" class="el-input__icon el-icon-search" @click="getTableData"></i>
+        </el-input>
+      </el-col>
+      <el-col :span="8.5" style="padding:0 0;margin-left:-2px">
+        <floor-cascader ref="floorcas" @change="changeFloor"></floor-cascader>
+      </el-col>
+      <el-col :span="7.5" style="padding:0 0;">
+        <space-select ref="spacesel" :isAll="true" @change="changeSpace"></space-select>
+      </el-col>
+    </el-row>
+    <el-row class="filters" :gutter="20"></el-row>
+    <div class="table-box">
+      <el-table :data="tableData" style="width: 100%" height="350" v-loading="loading" :header-cell-style="headerStyle"
+                ref="multipleTable"
+                @selection-change="handleSelectionChange">
+        <el-table-column type="selection" width="55"></el-table-column>
+        <el-table-column :label="`${inSpaceType}本地名称`" show-overflow-tooltip min-width="100">
+          <template slot-scope="scope">
+            {{ scope.row.localName || scope.row.name || '' }}
+          </template>
+        </el-table-column>
+        <el-table-column prop="localId" :label="`${inSpaceType}本地编码`" show-overflow-tooltip
+                         min-width="100"></el-table-column>
+        <el-table-column prop :label="`所属建筑楼层`" show-overflow-tooltip min-width="100">
+          <template slot-scope="scope">
+            {{
+              scope.row.buildingId && floorType[scope.row.buildingId] ? ((floorType[scope.row.buildingId] || '') + (scope.row.floorId && floorType[scope.row.floorId] ? (' - ' + floorType[scope.row.floorId] || '') : '')) : ''
+            }}
+          </template>
+        </el-table-column>
+        <el-table-column prop="objectType" :label="`空间类型`" show-overflow-tooltip min-width="100">
+          <template slot-scope="scope">
+            {{ spaceType[scope.row.classCode] || '' }}
+          </template>
+        </el-table-column>
+        <el-table-column prop="action" label="操作" min-width="100">
+          <template slot-scope="scope">
+            <el-button size="mini" @click="toDetail(scope.$index, scope.row)" plain>查看详情</el-button>
+          </template>
+        </el-table-column>
+      </el-table>
+      <!-- 分页 -->
+      <el-pagination class="fr" v-show="tableData && tableData.length" @size-change="handleSizeChange"
+                     @current-change="handleCurrentChange"
+                     :current-page="page.pageNumber" :page-sizes="page.pageSizes" :page-size="page.pageSize"
+                     layout="total, sizes, prev, pager, next, jumper"
+                     :total="page.total"></el-pagination>
+    </div>
+    <span slot="footer" class="dialog-footer">
+      <el-button size="small" @click="handleClose">取 消</el-button>
+      <el-button size="small" type="primary" @click="savaRelation">确 定</el-button>
+    </span>
+  </el-dialog>
+</template>
+
+<script>
+import {
+  unSysZoneSpace,
+  linkSysSp,
+  queryDictionaryHead
+} from "@/api/scan/request";
+import floorCascader from "@/components/ledger/lib/floorCascader";
+import dictionaryCas from "@/components/config_point/dictionaryCascader";
+import spaceSelect from "@/components/ledger/lib/spaceSelect";
+import { mapGetters } from "vuex";
+
+export default {
+  components: {
+    floorCascader,
+    dictionaryCas,
+    spaceSelect
+  },
+  computed: {
+    ...mapGetters("layout", ["projectId"]),
+    dialog() {
+      return this.dialogVisible;
+    }
+  },
+  data() {
+    return {
+      title: "添加系统所在业务空间",
+      keycode: "", //输入查询条件
+      inSpaceType: "业务空间",
+      Buildfloor: ["all"], // 选中的建筑楼层
+      Spacecategory: null, // 选中的业务空间类型
+      spaceValue: [""], //记录切换成功的业务空间值
+      tableData: [],
+      floorData: {}, //楼层
+      spaceData: {}, //业务空间分区
+      loading: false,
+      selections: [], // 选中项
+      page: {
+        pageSize: 50,
+        pageSizes: [10, 20, 50, 100],
+        pageNumber: 1,
+        total: 0
+      },
+      headerStyle: {
+        backgroundColor: "#e1e4e5",
+        color: "#2b2b2b",
+        lineHeight: "30px"
+      } // 列表样式
+    };
+  },
+  props: {
+    type: String, //选中的tab页
+    params: Object, //查看的竖井关系信息
+    spaceType: Object,
+    dialogVisible: Boolean,
+    floorType: Object
+  },
+  mounted() {
+  },
+  methods: {
+    // 显示弹窗
+    showDialog() {
+      this.dialogVisible = true;
+    },
+    //修改建筑楼层
+    changeFloor(value) {
+      this.page.pageNumber = 1;
+      this.Buildfloor = value;
+      this.getTableData();
+    },
+    //修改空间类型
+    changeSpace(val, space, value) {
+      if (this.selections.length) {
+        this.$confirm('关联的业务空间未保存, 是否继续?', '提示', {
+          confirmButtonText: '确定',
+          cancelButtonText: '取消',
+          type: 'warning'
+        }).then(() => {
+          this.Spacecategory = val;
+          this.spaceValue = value;
+          this.getTableData();
+        }).catch(() => {
+          this.$refs.spacesel.value = this.spaceValue;
+        });
+      } else {
+        this.Spacecategory = val;
+        this.spaceValue = value;
+        this.getTableData();
+      }
+    },
+    getTableData() {
+      let params = {
+        data: {
+          filters: "not id isNull",
+          orders: "localName desc",
+          pageNumber: this.page.pageNumber,
+          pageSize: this.page.pageSize
+        },
+        shaftId: this.params.ShaftID,
+        sysId: this.$route.query.SysID
+      };
+      if (this.Buildfloor[0] == "noKnow") {
+        params.data.filters += `;buildingId isNull`;
+      } else if (this.Buildfloor[0] && this.Buildfloor[0] != "all") {
+        params.data.filters += `;buildingId='${this.Buildfloor[0]}'`;
+      }
+      if (this.Buildfloor[1] == "noKnow") {
+        params.data.filters += `;floorId isNull`;
+      } else if (this.Buildfloor[1] && this.Buildfloor[1] != "all") {
+        params.data.filters += `;floorId='${this.Buildfloor[1]}'`;
+      }
+      if (this.Spacecategory) {
+        params.data.filters += `;classCode='${this.Spacecategory}'`;
+      }
+      if (this.keycode != "") {
+        params.data.filters += `;localId contain '${this.keycode}' or localName contain '${this.keycode}'`;
+      }
+
+      unSysZoneSpace(params, res => {
+        this.tableData = res.content;
+        this.page.total = res.total;
+      });
+    },
+    handleClose() {
+      this.$emit("update:dialogVisible", false);
+    },
+    //选中项修改
+    handleSelectionChange(val) {
+      this.selections = val;
+    },
+    savaRelation() {
+      if (this.selections.length) {
+        let params = {
+          content: this.selections.map(item => {
+            return {
+              spaceId: item.id,
+              sysId: this.$route.query.SysID,
+              objectType: item.classCode
+            };
+          })
+        };
+        linkSysSp(params, res => {
+          this.$message.success("关联成功!");
+          this.$emit("refresh");
+        });
+      } else {
+        this.$message("请选择要关联的业务空间");
+      }
+    },
+    //改变pagesize
+    handleSizeChange(pageSize) {
+      this.page.pageSize = pageSize;
+      this.getTableData();
+    },
+    //改变pageno
+    handleCurrentChange(pageNo) {
+      this.page.pageNumber = pageNo;
+      this.getTableData();
+    },
+    // 查看详情
+    toDetail() {
+      this.$message("开发中");
+    }
+  },
+  watch: {
+    dialogVisible: {
+      handler() {
+        if (this.dialogVisible) {
+          this.page.pageNumber = 1;
+          this.tableData = [];
+          this.getTableData();
+        }
+      },
+      immediate: true
+    }
+  }
+};
+</script>
+<style lang="less" scoped>
+#addEqDialog {
+  .filters {
+    margin: 0 0 10px 0;
+  }
+
+  .table-box {
+    height: 370px;
+
+    .fr {
+      margin-top: 10px;
+    }
+  }
+}
+</style>

+ 112 - 0
src/components/ledger/components/system/lib/equipType.vue

@@ -0,0 +1,112 @@
+<template>
+    <div id="cascaderMap">
+        <span class="buildFloor" style="margin-right: 12px">设备类型</span>
+        <el-cascader
+            v-model="value"
+            placeholder="请选择"
+            filterable
+            clearable
+            :props="props"
+            :options="options"
+            :style="isWidth ? '' : 'width:160px;'"
+            size="small"
+            @change="changeVal"
+        >
+            <template slot-scope="{ data }">
+                <span>{{ data.name }}</span>
+                <i v-if="data.flag" class="iconfont icon-dangqian" style="color: #E6A23C;vertical-align: bottom;"></i>
+            </template>
+        </el-cascader>
+    </div>
+</template>
+<script>
+import { mapGetters } from "vuex";
+import { getEquipTypeTree } from "@/api/object/equip";
+
+export default {
+    name: "getCode",
+    props: {
+        isWidth: {
+            type: Boolean,
+            default: true,
+        },
+        all: {
+            type: Boolean,
+            default: false,
+        },
+        //系统
+        params: Object,
+    },
+    computed: {
+        ...mapGetters("layout", ["projectId"]),
+    },
+    data() {
+        return {
+            value: [],
+            options: [],
+            props: {
+                value: "code",
+                label: "name",
+                children: "children",
+            },
+            falg: true,
+            content: [],
+        };
+    },
+    created() {
+        this.getData();
+    },
+    watch: {
+        projectId() {
+            this.value = [];
+            this.getData();
+        },
+    },
+    methods: {
+        setValue(val) {
+            this.value = val;
+        },
+        //修改val
+        changeVal(val) {
+            this.$emit("change", val[1]);
+        },
+        /**
+         * 获取当前项目下的系统-设备类型树
+         */
+        getData() {
+            getEquipTypeTree({}, (res) => {
+                if (res.content) {
+                    this.options = res.content;
+                    this.optionSort();
+                } else {
+                    this.options = [];
+                }
+            });
+        },
+        // 系统排序
+        optionSort() {
+            console.log(this.params);
+            const index = this.options.findIndex((item) => {
+                return item.code === this.params.SysType;
+            });
+            //如果存在当前系统,将当前系统置顶
+            if (index !== -1) {
+                const currentSys = this.options.splice(index, 1)[0];
+                currentSys.flag = true; //添加当前系统标志
+                this.options.unshift(currentSys);
+            }
+        },
+    },
+};
+</script>
+<style lang="less" scoped>
+#cascaderMap {
+    float: left;
+    margin-left: 10px;
+
+    .buildFloor {
+        color: #999999;
+        font-size: 14px;
+    }
+}
+</style>

+ 174 - 0
src/components/ledger/components/system/table/cenoteTable.vue

@@ -0,0 +1,174 @@
+<template>
+  <div id="eqInSp">
+    <el-row>
+      <el-button type="primary" @click="add">添加{{ inSpaceType }}</el-button>
+      <el-tooltip class="item" effect="dark" content="可前往“全部关系总览”中,按系统内包含的设备与竖井关系计算" placement="right">
+        <el-button>按系统内设备与竖井关系计算</el-button>
+      </el-tooltip>
+    </el-row>
+    <div class="table-box">
+      <el-table :data="tableData" style="width: 100%" height="100%" v-loading="loading"
+                :header-cell-style="headerStyle">
+        <el-table-column :label="`${inSpaceType}名称`" show-overflow-tooltip min-width="100">
+          <template slot-scope="scope">
+            <div>
+              {{ scope.row.localName || scope.row.name || '' }}
+            </div>
+          </template>
+        </el-table-column>
+        <el-table-column prop="localId" :label="`${inSpaceType}本地编码`" show-overflow-tooltip
+                         min-width="100"></el-table-column>
+        <el-table-column :label="`${inSpaceType}类型`" show-overflow-tooltip min-width="100">
+          <template slot-scope="scope">
+            <div>
+              {{ scope.row.infos.shaftFuncType }}
+            </div>
+          </template>
+        </el-table-column>
+        <el-table-column prop="action" label="操作" min-width="100">
+          <template slot-scope="scope">
+            <el-tooltip class="item" effect="dark" content="删除关系" placement="left">
+              <el-button size="mini" @click="handleDelete(scope.$index, scope.row)" type="danger" plain
+                         icon="el-icon-delete"></el-button>
+            </el-tooltip>
+          </template>
+        </el-table-column>
+        <template slot="empty">
+          <div style="height: 60%;transform: translateY(50%);">
+            <i class="icon-wushuju iconfont"></i>
+            可前往“全部关系总览”中,按系统内包含的设备与竖井关系计算
+          </div>
+        </template>
+      </el-table>
+    </div>
+    <!-- 添加设备弹窗 -->
+    <addCenoteDialog ref="addCenoteDialog" @refresh="getTableData" :type="type" :params="params"></addCenoteDialog>
+  </div>
+</template>
+
+<script>
+import { queryLinkSys, syinshUnlink } from "@/api/scan/request";
+import { mapGetters } from "vuex";
+import { getDataDictionary } from "@/api/dict"
+import addCenoteDialog from "@/components/ledger/system/dialog/addCenoteDialog"
+
+export default {
+  components: {
+    addCenoteDialog
+  },
+  computed: {
+    ...mapGetters("layout", ["projectId"])
+  },
+  data() {
+    return {
+      inSpaceType: '竖井',
+      headerStyle: {
+        backgroundColor: '#e1e4e5',
+        color: '#2b2b2b',
+        lineHeight: '30px'
+      }, // 列表样式
+      loading: false, // loading
+      tableData: [], //列表数据
+      shaftType: {} //竖井类型
+    };
+  },
+  props: {
+    params: Object,
+    type: String
+  },
+  created() {
+    // 查询 竖井所有类型
+    // queryShaftType(res => {
+    let param = {
+      "pageNumber": 1,
+      "pageSize": 50,
+      "filters": "code = 'shaftFuncType'",
+      "type": "shaft",
+      "orders": "sort asc, name desc",
+    }
+    getDataDictionary(param, res => {
+      res.content.forEach(item => {
+        item.dataSource && item.dataSource.forEach(i => {
+          this.shaftType[i.code] = i.name
+        })
+      })
+      this.getTableData()
+    })
+  },
+  methods: {
+    // 获取列表数据
+    getTableData() {
+      let params = {
+        filters: `id='${this.params.SysID}'`,
+        cascade: [
+          {
+            name: 'shaftList',
+          }
+        ]
+      }
+      queryLinkSys(params, res => {
+        // this.tableData = res.content[0].shaft || []
+        this.tableData = res.content[0].shaftList || []
+        this.tableData.forEach(item => {
+          if (item.infos && item.infos.shaftFuncType) {
+            item.infos.shaftFuncType = this.shaftType[item.infos.shaftFuncType]
+          }
+        })
+      })
+    },
+    // 删除关系
+    handleDelete(index, row) {
+      let sysId = this.$route.query.SysID
+      this.$confirm("确认删除该关系?", "提示", {
+        confirmButtonText: '确定',
+        cancelButtonText: '取消',
+        type: 'warning'
+      }).then(() => {
+        let params = {
+          shaftId: row.id,
+          sysId: sysId
+        }
+        this.deleteShThroughSh(params);
+      }).catch(() => {
+        this.$message("取消删除")
+      })
+    },
+    // 删除设备所在空间关系
+    deleteShThroughSh(params) {
+      syinshUnlink(params, res => {
+        this.$message.success('删除成功')
+        this.getTableData()
+      })
+    },
+    // 改变pagesize
+    handleSizeChange(pageSize) {
+      this.page.pageSize = pageSize;
+      this.getTableData();
+    },
+    // 改变pageno
+    handleCurrentChange(pageNo) {
+      this.page.pageNumber = pageNo;
+      this.getTableData();
+    },
+    // 添加设备
+    add() {
+      this.$refs.addCenoteDialog.showDialog()
+    }
+  },
+  watch: {
+    type() {
+      // this.getTableData()
+    }
+  }
+};
+</script>
+<style lang="less" scoped>
+#eqInSp {
+  height: 100%;
+
+  .table-box {
+    margin-top: 10px;
+    height: calc(100% - 50px);
+  }
+}
+</style>

+ 160 - 0
src/components/ledger/components/system/table/deviceTable.vue

@@ -0,0 +1,160 @@
+<template>
+    <div id="eqInSp">
+        <el-row>
+            <el-button type="primary" @click="add">添加{{ inSpaceType }}</el-button>
+            <!-- <el-tooltip class="item" effect="dark" content="可前往“全部关系总览”中计算竖井内设备" placement="right">
+        <el-button>按空间计算</el-button>
+      </el-tooltip> -->
+        </el-row>
+        <div class="table-box">
+            <el-table :data="tableData" style="width: 100%" height="100%" v-loading="loading" :header-cell-style="headerStyle">
+                <el-table-column :label="`${inSpaceType}名称`" show-overflow-tooltip min-width="100">
+                    <template slot-scope="scope">
+                        <div>
+                            {{ scope.row.localName || scope.row.name || "" }}
+                        </div>
+                    </template>
+                </el-table-column>
+                <el-table-column prop="localId" :label="`${inSpaceType}本地编码`" show-overflow-tooltip min-width="100"></el-table-column>
+                <el-table-column :label="`数字化交付编码`" show-overflow-tooltip min-width="100">
+                    <template slot-scope="scope">
+                        <div>{{ scope.row.infos ? (scope.row.infos.digitalDeliveryID ? scope.row.infos.digitalDeliveryID : "") : "" }}</div>
+                    </template>
+                </el-table-column>
+                <el-table-column :label="`${inSpaceType}类型`" show-overflow-tooltip min-width="100">
+                    <template slot-scope="scope">
+                        <div>
+                            {{ scope.row.equipCategory.name }}
+                        </div>
+                    </template>
+                </el-table-column>
+                <el-table-column prop="action" label="操作" min-width="100">
+                    <template slot-scope="scope">
+                        <el-button
+                            title="删除关系"
+                            size="mini"
+                            @click="handleDelete(scope.$index, scope.row)"
+                            type="danger"
+                            plain
+                            icon="el-icon-delete"
+                        ></el-button>
+                    </template>
+                </el-table-column>
+                <template slot="empty">
+                    <div style="height: 60%; transform: translateY(50%)">
+                        <i class="icon-wushuju iconfont"></i>
+                        可前往“全部关系总览”中计算系统内设备
+                    </div>
+                </template>
+            </el-table>
+        </div>
+        <!-- 添加设备弹窗 -->
+        <addEquipDialog ref="addEqDialog" @refresh="getTableData" :type="type" :params="params"></addEquipDialog>
+    </div>
+</template>
+
+<script>
+import { queryLinkSys, eqInSysUnlink } from "@/api/scan/request";
+import { mapGetters } from "vuex";
+import addEquipDialog from "@/components/ledger/system/dialog/addEquipDialog";
+
+export default {
+    components: {
+        addEquipDialog,
+    },
+    computed: {
+        ...mapGetters("layout", ["projectId"]),
+    },
+    data() {
+        return {
+            inSpaceType: "设备",
+            headerStyle: {
+                backgroundColor: "#e1e4e5",
+                color: "#2b2b2b",
+                lineHeight: "30px",
+            }, // 列表样式
+            loading: false, // loading
+            tableData: [], //列表数据
+        };
+    },
+    props: {
+        params: Object,
+        type: String,
+    },
+    created() {
+        this.getTableData();
+    },
+    methods: {
+        // 获取列表数据
+        getTableData() {
+            let params = {
+                filters: `id='${this.params.SysID}'`,
+                cascade: [
+                    {
+                        name: "equipmentList",
+                        cascade: [{ name: "equipCategory" }],
+                    },
+                ],
+            };
+            queryLinkSys(params, (res) => {
+                this.tableData = res.content[0].equipmentList || [];
+            });
+        },
+        // 删除关系
+        handleDelete(index, row) {
+            let sysId = this.$route.query.SysID;
+            this.$confirm("确认删除该关系?", "提示", {
+                confirmButtonText: "确定",
+                cancelButtonText: "取消",
+                type: "warning",
+            })
+                .then(() => {
+                    let params = {
+                        equipId: row.id,
+                        sysId: sysId,
+                    };
+                    this.deleteEqInSh(params);
+                })
+                .catch(() => {
+                    this.$message("取消删除");
+                });
+        },
+        // 删除设备所在竖井关系
+        deleteEqInSh(params) {
+            eqInSysUnlink(params, (res) => {
+                this.$message.success("删除成功");
+                this.getTableData();
+            });
+        },
+        // 改变pagesize
+        handleSizeChange(pageSize) {
+            this.page.pageSize = pageSize;
+            this.getTableData();
+        },
+        // 改变pageno
+        handleCurrentChange(pageNo) {
+            this.page.pageNumber = pageNo;
+            this.getTableData();
+        },
+        // 添加设备
+        add() {
+            this.$refs.addEqDialog.showDialog();
+        },
+    },
+    watch: {
+        type() {
+            // this.getTableData()
+        },
+    },
+};
+</script>
+<style lang="less" scoped>
+#eqInSp {
+    height: 100%;
+
+    .table-box {
+        margin-top: 10px;
+        height: calc(100% - 50px);
+    }
+}
+</style>

+ 199 - 0
src/components/ledger/components/system/table/spaceTable.vue

@@ -0,0 +1,199 @@
+<template>
+  <div id="eqInSp">
+    <el-row>
+      <el-button type="primary" @click="add">添加{{ inSpaceType }}</el-button>
+      <el-tooltip
+        class="item"
+        effect="dark"
+        content="可前往“全部关系总览”中,按系统内包含的设备与业务空间关系计算"
+        placement="right"
+      >
+        <el-button>按系统内设备与业务空间关系计算</el-button>
+      </el-tooltip>
+    </el-row>
+    <div class="table-box">
+      <el-table
+        :data="tableData"
+        style="width: 100%"
+        height="100%"
+        v-loading="loading"
+        :header-cell-style="headerStyle"
+      >
+        <el-table-column
+          :label="`${inSpaceType}名称`"
+          show-overflow-tooltip
+          min-width="100"
+        >
+          <template slot-scope="scope">
+            <div>{{ scope.row.localName || scope.row.name || "" }}</div>
+          </template>
+        </el-table-column>
+        <el-table-column
+          prop="localId"
+          :label="`${inSpaceType}本地编码`"
+          show-overflow-tooltip
+          min-width="100"
+        ></el-table-column>
+        <el-table-column
+          :label="`${inSpaceType}类型`"
+          show-overflow-tooltip
+          min-width="100"
+        >
+          <template slot-scope="scope">
+            <div>{{ spaceType[scope.row.classCode] }}</div>
+          </template>
+        </el-table-column>
+        <el-table-column prop="action" label="操作" min-width="100">
+          <template slot-scope="scope">
+            <el-tooltip
+              class="item"
+              effect="dark"
+              content="删除关系"
+              placement="left"
+            >
+              <el-button
+                size="mini"
+                @click="handleDelete(scope.$index, scope.row)"
+                type="danger"
+                plain
+                icon="el-icon-delete"
+              ></el-button>
+            </el-tooltip>
+          </template>
+        </el-table-column>
+        <template slot="empty">
+          <div style="height: 60%; transform: translateY(50%)">
+            <i class="icon-wushuju iconfont"></i>
+            可前往“全部关系总览”中按系统内包含的设备与业务空间关系计算
+          </div>
+        </template>
+      </el-table>
+    </div>
+    <!-- 添加空间弹窗 -->
+    <addSpaceDialog
+      :dialogVisible.sync="dialogVisible"
+      ref="addSpaceDialog"
+      @refresh="refresh"
+      :params="params"
+      :spaceType="spaceType"
+      :floorType="floorType"
+    ></addSpaceDialog>
+  </div>
+</template>
+
+<script>
+import { queryLinkSys, spaceInSysUnlink } from "@/api/scan/request";
+import { mapGetters } from "vuex";
+import addSpaceDialog from "@/components/ledger/system/dialog/addSpaceDialog";
+
+export default {
+  components: {
+    addSpaceDialog,
+  },
+  computed: {
+    ...mapGetters("layout", ["projectId"]),
+  },
+  data() {
+    return {
+      inSpaceType: "业务空间",
+      dialogVisible: false,
+      headerStyle: {
+        backgroundColor: "#e1e4e5",
+        color: "#2b2b2b",
+        lineHeight: "30px",
+      }, // 列表样式
+      loading: false, // loading
+      tableData: [], //列表数据
+    };
+  },
+  props: {
+    params: Object,
+    type: String,
+    spaceType: Object,
+    floorType: {},
+  },
+  created() {
+    this.getTableData();
+  },
+  methods: {
+    // 获取列表数据
+    getTableData() {
+      let params = {
+        filters: `id='${this.params.SysID}'`,
+        cascade: [
+          {
+            name: "zoneSpaceBaseTableList",
+          },
+        ],
+      };
+      queryLinkSys(params, (res) => {
+        this.tableData = res.content[0].zoneSpaceBaseTableList || [];
+      });
+    },
+    // 删除关系
+    handleDelete(index, row) {
+      this.$confirm("确认删除该关系?", "提示", {
+        confirmButtonText: "确定",
+        cancelButtonText: "取消",
+        type: "warning",
+      })
+        .then(() => {
+          let params = {
+            data: [
+              {
+                sysId: this.params.SysID,
+                spaceId: row.id,
+              },
+            ],
+            type: row.classCode,
+          };
+          this.deleteSyInSh(params);
+        })
+        .catch(() => {
+          this.$message("取消删除");
+        });
+    },
+    // 删除系统所在竖井关系
+    deleteSyInSh(params) {
+      spaceInSysUnlink(params, (res) => {
+        this.$message.success("删除成功");
+        this.getTableData();
+      });
+    },
+    // 改变pagesize
+    handleSizeChange(pageSize) {
+      this.page.pageSize = pageSize;
+      this.getTableData();
+    },
+    // 改变pageno
+    handleCurrentChange(pageNo) {
+      this.page.pageNumber = pageNo;
+      this.getTableData();
+    },
+    // 添加设备
+    add() {
+      this.dialogVisible = true;
+    },
+    //刷新
+    refresh() {
+      this.dialogVisible = false;
+      this.getTableData();
+    },
+  },
+  watch: {
+    type() {
+      // this.getTableData()
+    },
+  },
+};
+</script>
+<style lang="less" scoped>
+#eqInSp {
+  height: 100%;
+
+  .table-box {
+    margin-top: 10px;
+    height: calc(100% - 50px);
+  }
+}
+</style>

+ 398 - 0
src/components/ledger/components/tableTransfers.vue

@@ -0,0 +1,398 @@
+<template>
+  <el-dialog title="关联设备" :before-close="close" :visible.sync="dialog.relevance" width="900px">
+    <div id="tableTransfers">
+      <div class="main-left">
+        <h4>未关联该系统的设备</h4>
+        <div class="border-view">
+          <div class="search">
+            <div class="search-small" style="padding: 10px 10px 0 10px">
+              <el-input placeholder="输入设备名称进行查询" v-model="search1" style="width:300px;margin-right: 10px;" size="small"
+                        clearable></el-input>
+              <el-button @click="getLeftData" size="small">查找</el-button>
+            </div>
+            <div class="search-small" style="padding: 10px">
+              <floor-cascader :isWidth="false" @change="changeBuild"></floor-cascader>
+              <cascader :isWidth="false" @change="changeCascader" :all="true"></cascader>
+            </div>
+          </div>
+          <el-table ref="multipleTable" :data="tableData" height="300px" tooltip-effect="dark" style="width: 100%"
+                    class="data-table"
+                    @selection-change="handleSelectionChange" v-loading="isLoading1" border>
+            <el-table-column type="selection" width="55"></el-table-column>
+            <el-table-column label="设备名称" width="120">
+              <template slot-scope="scope">{{ scope.row.EquipLocalName || scope.row.EquipName }}</template>
+            </el-table-column>
+            <el-table-column label="设备编号" width="120">
+              <template slot-scope="scope">{{ scope.row.EquipLocalID || "--" }}</template>
+            </el-table-column>
+            <el-table-column label="设备类" show-overflow-tooltip>
+              <template slot-scope="scope">{{ getName(scope.row.Category) }}</template>
+            </el-table-column>
+            <el-table-column prop="address" label="安装位置" show-overflow-tooltip>
+              <template slot-scope="scope">{{ scope.row.InstallLocation || "--" }}</template>
+            </el-table-column>
+          </el-table>
+          <div class="right" style="overflow:hidden;">
+            <my-pagination @change="changePages1" :page="pages1"></my-pagination>
+          </div>
+        </div>
+      </div>
+      <div class="main-button">
+        <el-button class="top" @click="rightToLeft" :disabled="SelectionList && SelectionList.length ? false : true"
+                   :type="SelectionList && SelectionList.length ? 'primary' : ''" icon="el-icon-arrow-left"
+                   circle></el-button>
+        <el-button class="bottom" @click="leftToRight"
+                   :disabled="multipleSelection && multipleSelection.length ? false : true"
+                   :type="multipleSelection && multipleSelection.length ? 'primary' : ''" icon="el-icon-arrow-right"
+                   circle></el-button>
+      </div>
+      <div class="main-right">
+        <h4>已关联该系统的设备</h4>
+        <div class="border-view">
+          <div class="search">
+            <div class="search-big">
+              <el-input placeholder="输入设备名称进行查询" v-model="search2" style="width:200px; margin-bottom: 10px;"
+                        size="small" clearable>
+              </el-input>
+              <el-button @click="getRightData" size="small">查找</el-button>
+            </div>
+          </div>
+          <el-table ref="multipleTable" :data="table2Data" height="300px" tooltip-effect="dark" style="width: 100%"
+                    class="data-table"
+                    @selection-change="tableChange" v-loading="isLoading2" border>
+            <el-table-column type="selection" width="55"></el-table-column>
+            <el-table-column label="设备名称">
+              <template slot-scope="scope">{{ scope.row.EquipLocalName || scope.row.EquipName }}</template>
+            </el-table-column>
+          </el-table>
+          <div class="right">
+            <my-pagination :page="pages2" @change="changePages2" :isSmall="false"></my-pagination>
+          </div>
+        </div>
+      </div>
+    </div>
+  </el-dialog>
+</template>
+<script>
+import myPagination from "@/components/common/myPagination";
+import floorCascader from "@/components/ledger/lib/floorCascader";
+import cascader from "@/components/ledger/lib/cascader";
+
+import {
+  getEquipNotInSys,
+  getEquipInSys,
+  getEquipBelongs,
+  sysLinkEquip,
+  sysUnlinkEquip
+} from "@/api/scan/request"
+import {
+  mapGetters,
+  mapActions
+} from "vuex";
+
+export default {
+  props: {
+    dialog: {
+      type: Object,
+      default: function () {
+        return {
+          relevance: false
+        }
+      }
+    },
+    id: {
+      type: String
+    }
+  },
+  components: {
+    myPagination,
+    floorCascader,
+    cascader
+  },
+  computed: {
+    ...mapGetters("layout", ["projectId", "secret", "userId"])
+  },
+  data() {
+    return {
+      dialogVisible: true,
+      pages1: {
+        size: 50,
+        sizes: [10, 20, 30, 50, 100, 200],
+        total: 0,
+        currentPage: 1
+      },
+      pages2: {
+        size: 50,
+        sizes: [10, 20, 30, 50, 100, 200],
+        total: 0,
+        currentPage: 1
+      },
+      search1: '', //左侧输条件
+      search2: '',//右侧输入条件
+      buildingId: '',//建筑id
+      floorId: '',//楼层id
+      category: '',//设备类型
+      tableData: [], //第一个表格的数据
+      table2Data: [], //第二个表格的数据
+      multipleSelection: [], //第一个表格的被选择数组
+      SelectionList: [], //第二个表格的被选择数组
+      List: [],
+      isLoading1: false,
+      isLoading2: false,
+    };
+  },
+  created() {
+    this.getAllData();
+  },
+  methods: {
+    //关闭
+    close(done) {
+      this.$emit("close")
+      done()
+    },
+    //根据设备类型编码获取名称
+    getName(code) {
+      let str = ""
+      this.List.map(item => {
+        if (item.code == code) {
+          str = item.facility
+        }
+      })
+      return str
+    },
+    //获取物理世界所有设备类型
+    getAllData() {
+      let param = {
+        data: {
+          distinct: true,
+          orders: "equipName asc",
+          pageNumber: 1,
+          pageSize: 500,
+          projection: [
+            "equipCode", "equipName"
+          ]
+        }
+      }
+      getEquipBelongs(param, res => {
+        this.List = this.formatOptions(res.content)
+      })
+    },
+    //格式化List数据
+    formatOptions(arr) {
+      let data = [];
+      arr.map(t => {
+        let temp = {};
+        temp.code = t.equipCode;
+        temp.facility = t.equipName;
+        data.push(temp)
+      })
+      return data;
+    },
+    //获取两侧数据
+    getAllTable() {
+      this.pages1.currentPage = 1
+      this.pages2.currentPage = 1
+      this.getLeftData()
+      this.getRightData()
+    },
+    //pages1改变
+    changePages1(page) {
+      this.pages1.PageNumber = page
+      this.getLeftData()
+    },
+    changePages2(page) {
+      this.pages2.PageNumber = page
+      this.getRightData()
+    },
+    //修改设备类型
+    changeCascader(val) {
+      this.category = val.code
+      this.getLeftData()
+    },
+    //获取未关联该系统的设备
+    getLeftData() {
+      this.isLoading1 = true
+      let param = {
+        data: {
+          Filters: '',
+          Orders: "EquipID desc",
+          PageNumber: this.pages1.currentPage,
+          PageSize: this.pages1.size
+        },
+        sysId: this.id
+      }
+      if (this.search1 != '') {
+        param.data.Filters = `EquipLocalName contain '${this.search1}' || EquipName contain '${this.search1}';`
+      }
+      //建筑id
+      if (this.buildingId == "noKnow") {
+        param.data.Filters += `buildingId isNull;`
+      } else if (this.buildingId && this.buildingId != "all") {
+        param.data.Filters += `buildingId='${this.buildingId}';`
+      }
+      //楼层id
+      if (this.floorId == "noKnow") {
+        param.data.Filters += `floorId isNull;`
+      } else if (this.floorId && this.floorId != "all") {
+        param.data.Filters += `floorId='${this.floorId}';`
+      }
+      //设备类型
+      if (this.category) {
+        param.data.Filters += `category='${this.category}';`
+      }
+      if (param.data.Filters) {
+        param.data.Filters = param.data.Filters.substring(0, param.data.Filters.length - 1)
+      } else {
+        delete param.data.Filters;
+      }
+      getEquipNotInSys(param, res => {
+        this.tableData = res.Content
+        this.pages1.total = res.Total
+        this.isLoading1 = false
+      })
+    },
+    //获取已关联该系统的设备
+    getRightData() {
+      this.isLoading2 = true
+      let param = {
+        Filters: `sysId='${this.id}'`,
+        Orders: "EquipID desc",
+        PageNumber: this.pages2.currentPage,
+        PageSize: this.pages2.size
+      }
+      if (this.search2 != '') {
+        param.Filters += `;EquipLocalName contain '${this.search2}' || EquipName contain '${this.search2}'`
+      }
+      getEquipInSys(param, res => {
+        this.table2Data = res.Content
+        this.pages2.total = res.Total
+        this.isLoading2 = false
+      })
+    },
+    //左侧多选框改变
+    handleSelectionChange(val) {
+      this.multipleSelection = val
+    },
+    //右侧多选框发生改变
+    tableChange(val) {
+      this.SelectionList = val;
+    },
+    //关联
+    leftToRight() {
+      let relationList = this.multipleSelection
+      relationList = relationList.map(t => {
+        return t.EquipID || t.EquipId
+      })
+      this.relationChange(relationList)
+    },
+    //取消关联
+    rightToLeft() {
+      let tempIDs = this.SelectionList.map(t => {
+        return {
+          EquipID: t.EquipId,
+          SysID: t.SysID
+        }
+      })
+      sysUnlinkEquip(tempIDs, res => {
+        this.$message.success('修改关联关系成功')
+        this.getAllTable()
+      })
+    },
+    //更新设备 系统关系
+    relationChange(arr) {
+      let param = {
+        sysId: this.id,
+        equipIdList: arr
+      }
+      sysLinkEquip(param, res => {
+        this.$message.success('修改关联关系成功')
+        this.getAllTable()
+      })
+    },
+    //修改楼层
+    changeBuild(val) {
+      this.buildingId = val[0]
+      if (val[1]) {
+        this.floorId = val[1]
+      }
+      this.getLeftData()
+    },
+  },
+  watch: {
+    dialog: {
+      deep: true,
+      handler: function () {
+        if (this.dialog.relevance) {
+          this.search1 = ''
+          this.search2 = ''
+          this.getAllTable()
+        }
+      }
+    }
+  }
+};
+</script>
+<style lang="less">
+#tableTransfers {
+  height: 500px;
+  width: 100%;
+  overflow-x: hidden;
+  overflow-y: auto;
+
+  h4 {
+    font-size: 20px;
+    font-weight: 500;
+  }
+
+  .search {
+    .search-small {
+      overflow: hidden;
+    }
+
+    border-bottom: #dcdfe6 1px solid;
+
+    .search-big {
+      padding: 10px;
+    }
+  }
+
+  .main-left {
+    float: left;
+    width: 550px;
+    height: 500px;
+  }
+
+  .border-view {
+    border: 1px solid #ccc;
+  }
+
+  .main-button {
+    float: left;
+    width: 60px;
+    height: 500px;
+    position: relative;
+
+    .top {
+      position: absolute;
+      bottom: 300px;
+      left: 10px;
+    }
+
+    .bottom {
+      position: absolute;
+      bottom: 200px;
+      left: 10px;
+    }
+  }
+
+  .el-button + .el-button {
+    margin: 0;
+  }
+
+  .main-right {
+    height: 300px;
+    float: left;
+    width: 230px;
+  }
+}
+</style>

+ 679 - 0
src/components/ledger/facility/addfacility.vue

@@ -0,0 +1,679 @@
+<template>
+    <div id="deviceList">
+        <el-row class="right">
+            <span style="float: left">当前选择的设备类型:{{ category.name }}</span>
+            <el-select v-model="showType" @change="initTable" style="width: 125px; margin-right: 10px; vertical-align: bottom">
+                <el-option v-for="item in showTypes" :key="item.value" :label="item.label" :value="item.value"></el-option>
+            </el-select>
+            <span>增加</span>
+            <el-input-number v-model="addNum" :controls="false" style="width: 50px" :min="1" :max="50"></el-input-number>
+            <span>个{{ category.name }}</span>
+            <el-button @click="handleAddTableRow">增加</el-button>
+        </el-row>
+        <div class="tableBox">
+            <div class="center middle_sty" style="flex: 2" v-show="tableData && !tableData.length">
+                <p>
+                    <i class="icon-wushuju iconfont"></i>
+                    暂无数据
+                </p>
+            </div>
+            <div class="tableLeft" v-show="tableData && tableData.length">
+                <handson-table ref="table"></handson-table>
+            </div>
+        </div>
+        <el-row class="center">
+            <el-button type="primary" size="medium" @click="handleCreateTableData" class="create_button" :disabled="createDisable"
+                >创建设备</el-button
+            >
+        </el-row>
+        <!-- 多选枚举类型维护 -->
+        <menum :dialog="myDialog" :updateInfoData="updateInfoData" :updateInput="updateInput" @change="handleChangeMenum"></menum>
+        <!-- 二维码弹窗 -->
+        <qrcode :dialog="myDialog" :qrcodeUrl="qrcodeUrl" :addBody="true" ref="qrcode"></qrcode>
+        <upload-files-dialog :read="onlyRead ? true : false" ref="upload" @changeFile="fileChange" :keysArr="filesArr" :dialog="myDialog">
+        </upload-files-dialog>
+        <upload-img-dialog :read="onlyRead ? true : false" @changeFile="imgChange" :keysArr="imgsArr" :dialog="myDialog"></upload-img-dialog>
+        <pic-dialog :read="onlyRead ? true : false" :dialog="myDialog" :keysArr="picsArr" @change="changePics"></pic-dialog>
+        <system-type
+            :device="category"
+            :dialog="myDialog"
+            :type="onlyRead ? 'read' : 'edit'"
+            @change="changeSystemType"
+            :list="systemList"
+        ></system-type>
+        <details-dialog :iframeSrc="iframeSrc" v-if="myDialog.details" :dialog="myDialog"></details-dialog>
+    </div>
+</template>
+<script>
+import tools from "@/utils/scan/tools";
+import showTools from "@/utils/handsontable/notShow";
+import text from "@/utils/handsontable/mainText";
+import session from "@/framework/utils/storage";
+import buildFloor from "@/utils/handsontable/buildFloorData";
+
+import handsonTable from "@/components/common/handsontable";
+import { getEquipBelongs, createEquipAndProperty } from "@/api/scan/request";
+import { getDataDictionary } from "@/api/dict";
+
+import { mapGetters } from "vuex";
+
+import qrcode from "@/components/ledger/lib/qrcode";
+import uploadFilesDialog from "@/components/dialogs/list/filesDialog";
+import uploadImgDialog from "@/components/dialogs/list/uploadImgDialog";
+import picDialog from "@/components/dialogs/list/picDialog";
+import menum from "@/components/dialogs/list/menum";
+import systemType from "@/components/dialogs/list/systemType";
+
+export default {
+    components: {
+        qrcode, //二维码页面
+        uploadFilesDialog,
+        uploadImgDialog,
+        picDialog,
+        systemType,
+        handsonTable,
+        menum,
+    },
+    data() {
+        return {
+            addNum: 1,
+            onlyRead: false,
+            showTypes: [
+                { value: "Visible", label: "只看采集信息" },
+                { value: "all", label: "全部" },
+            ],
+            tableHeader: [],
+            // tableData: session.get("deviceAddData")
+            //     ? session.get("deviceAddData").length
+            //         ? session.get("deviceAddData")
+            //         : [{ checked: 1 }]
+            //     : [{ checked: 1 }],
+            tableData: session.get("deviceAddData")
+                ? session.get("deviceAddData").length
+                    ? session.get("deviceAddData")
+                    : []
+                : [],
+            copyTableData: [],
+            category: "", //当前设备类
+            myDialog: {
+                qrcode: false, //二维码弹窗
+                uploadFiles: false, //上传文件
+                uploadImgs: false, //上传单个图片
+                pic: false, //多个图片
+                addDevice: false,
+                systemType: false,
+                details: false, //详情页
+                changeRea: false,
+                lookPic: false, //图片查看
+                menum: false, //临时多选枚举类型信息点
+            },
+            inputMap: {
+                flowBuild: {
+                    editable: true,
+                    code: "flowBuild",
+                    name: "建筑楼层",
+                    path: "flowBuild",
+                    category: "STATIC",
+                    dataType: "ENUM",
+                },
+            }, //信息点和输入方式映射表
+            qrcodeUrl: "", //二维码图片地址
+            filesArr: [], //保存临时的文件key
+            imgsArr: [], //临时保存的图片key数组
+            picsArr: [], //临时设备图片keys数组
+            systemList: [], //关联的系统
+            systemId: null,
+            graphyId: null,
+            id: 0,
+            showType: this.$route.query.showType,
+            EquipmentList: [],
+            firmId: "", //品牌型号所需字段--族id三位编码(传设备类id或部件类id)
+            buildFloorData: [],
+            floorFlag: true,
+            createDisable: false,
+            updateInfoData: {}, //当前信息点信息
+            updateInfoPoint: "", //临时维护信息点键
+            updateInput: "", //临时维护信息点值
+        };
+    },
+    computed: {
+        ...mapGetters("layout", ["projectId", "secret", "userId"]),
+    },
+    created() {
+        this.category = this.$route.query;
+        buildFloor.getData(this.buildFloorData);
+        this.getBelongs();
+        this.getTableHeader();
+    },
+    methods: {
+        //查询设备类对应的 族
+        getBelongs() {
+            let params = {
+                data: {
+                    filters: `equipCode='${this.category.deviceId}'`,
+                    pageNumber: 1,
+                    pageSize: 50,
+                },
+            };
+            getEquipBelongs(params, (res) => {
+                if (res.content.length) {
+                    this.firmId = res.content[0].family;
+                }
+            });
+        },
+        //将数组转换成optiosn格式
+        changeArr(arr) {
+            return arr.map((item) => {
+                if (item.floors && item.floors.length) {
+                    return {
+                        value: item.id,
+                        label: item.infos.BuildLocalName,
+                        children: item.floors.map((i) => {
+                            return {
+                                value: i.id,
+                                label: i.infos.FloorLocalName || "未知",
+                            };
+                        }),
+                    };
+                } else {
+                    return {
+                        value: item.id,
+                        label: item.infos.BuildLocalName,
+                        children: null,
+                        isChilren: 1,
+                    };
+                }
+            });
+        },
+        // 获取表头数据(初始化表格)
+        async getTableHeader() {
+            let params = {
+                orders: "sort asc, name desc",
+                pageNumber: 1,
+                pageSize: 1000,
+                type: this.category.deviceId,
+            };
+            await getDataDictionary(params, (res) => {
+                this.tableHeader = res.content;
+                this.tableHeader.forEach((item) => {
+                    if (item.path) {
+                        this.inputMap[item.path] = item;
+                    }
+                });
+                this.initTable();
+            });
+        },
+        // 创建设备数据
+        async handleCreateTableData() {
+            let newData = this.tableData.filter((item) => {
+                let keys = Object.keys(item);
+                keys.map((key) => {
+                    //将值为空字符串的属性删除
+                    if (item[key] == "") {
+                        delete item[key];
+                    }
+                });
+                let newK = Object.keys(item);
+                if ((item.checked && newK.length > 1) || (!item.checked && newK.length)) {
+                    return item;
+                }
+            });
+            if (!newData.length) {
+                this.$message("创建信息为空,请录入信息后再创建!");
+                return;
+            }
+            let flag = false;
+            newData.map((item) => {
+                if (!item.localName) {
+                    flag = true;
+                }
+                // 处理建筑-楼层
+                if (item.flowBuild) {
+                    let bid = item.flowBuild.split("-");
+                    item.buildingId = item.flowBuild.split("-")[0];
+                    if (bid[1]) {
+                        item.floorId = item.flowBuild.split("-")[1];
+                    }
+                }
+                //处理关联的系统
+                if (item.linkSystem && item.linkSystem.length) {
+                    item.SystemList = [];
+                    item.linkSystem.map((t) => {
+                        item.SystemList.push(t.id);
+                    });
+                }
+                // 关联创建资产
+                // item.propertyId = !!item.checked;
+                item.classCode = this.category.deviceId;
+            });
+            if (flag) {
+                this.$message.info("存在设备的本地名称为空,请检查");
+                return;
+            }
+
+            // for (let i = 0; i < newData.length; i++) {
+            //   let params = newData[i]
+            //   params.Family = this.firmId;
+            //   params.Category = this.category.deviceId;
+            //   if (newData[i].PropertyId) {//同时创建资产
+            //     await createPropertyData([params], async res => {
+            //       params.PropertyId = res.EntityList[0].EquipID
+            //       await createEquip([params], res => {
+            //         if (i == newData.length - 1) {
+            //           this.$router.push({
+            //             path: "/ledger/facility",
+            //             query: { deviceId: this.category.deviceId }
+            //           });
+            //           session.remove("deviceAddData")
+            //         }
+            //       })
+            //     })
+            //   } else {
+            //     await createEquip([params], res => {
+            //       if (i == newData.length - 1) {
+            //         this.$router.push({
+            //           path: "/ledger/facility",
+            //           query: { deviceId: this.category.deviceId }
+            //         });
+            //         session.remove("deviceAddData")
+            //       }
+            //     });
+            //   }
+            // }
+            this.createDisable = true;
+            //待接口修改为关联创建即可修改
+            let param = {
+                cascade: [],
+                content: newData,
+            };
+            createEquipAndProperty(param, (res) => {
+                this.createDisable = false;
+                this.$message.success("创建成功");
+                session.remove("deviceAddData");
+                this.$router.push({
+                    name: "facilityLedger",
+                    params: { deviceId: this.category.deviceId,facility:this.category.name },
+                });
+            });
+        },
+        // 删除表格行
+        handleDeleteTableRow(a, b, c, d) {
+            if (d && d === "ContextMenu.removeRow") {
+                this.$message.success("删除成功");
+                this.formaTableData();
+            }
+        },
+        // 添加行
+        handleAddTableRow() {
+            let addRowLength = this.addNum;
+            for (let i = 0; i < addRowLength; i++) {
+                // this.tableData.push({ checked: 1 });
+                this.tableData.push({});
+            }
+            this.initTable();
+            this.formaTableData();
+        },
+        //修改
+        handleUpdataTable(changeData, source) {
+            if (!this.onlyRead && source != "ObserveChanges.change") {
+                this.formaTableData();
+            }
+        },
+        //保存去掉空字段的新增数据
+        formaTableData() {
+            let newData = this.tableData.filter((item) => {
+                let keys = Object.keys(item);
+                keys.map((key) => {
+                    //将值为空字符串的属性删除
+                    if (item[key] == "") {
+                        delete item[key];
+                    }
+                });
+                if (keys.length && Object.keys(item).length) {
+                    return item;
+                }
+            });
+            session.set("deviceAddData", newData);
+        },
+        //格式化表头显示的数据
+        formatHeaderData(list) {
+            let arr = tools.copyArr(list);
+            let data = showTools.headerTextFilter(arr, "equipment", this.onlyRead, this.showType, true);
+            // data.unshift("同时创建资产", "所属系统实例");
+            data.unshift("所属系统实例");
+            if (this.floorFlag) {
+                data.splice(1, 0, "所属建筑楼层");
+            }
+            return data;
+        },
+        formatHeaderType(list) {
+            //格式化表头头映射的数据
+            let arr = tools.copyArr(list);
+            let data = showTools.headerTypeFilter(arr, "equipment", this.onlyRead, this.showType, true);
+            data.unshift(
+                // {
+                //     type: "checkbox",
+                //     checkedTemplate: 1,
+                //     uncheckedTemplate: 0,
+                //     data: "checked",
+                //     label: {
+                //         position: "after",
+                //     },
+                // },
+                {
+                    data: "linkSystem",
+                    renderer: text.systemList,
+                    readOnly: true,
+                }
+            );
+            if (this.floorFlag) {
+                data.splice(1, 0, {
+                    data: "flowBuild",
+                    renderer: tools.customDropdownRenderer,
+                    editor: "chosen",
+                    chosenOptions: {
+                        // multiple: true,//多选
+                        data: this.buildFloorData,
+                    },
+                });
+            }
+            return data;
+        },
+        initTable() {
+            //实例化表格
+            let settings = {
+                data: this.tableData,
+                colHeaders: this.formatHeaderData(this.tableHeader),
+                columns: this.formatHeaderType(this.tableHeader),
+                rowHeights: 30,
+                fillHandle: "vertical", //允许纵向填充
+                maxRows: this.tableData.length,
+                contextMenu: this.onlyRead
+                    ? false
+                    : {
+                          items: {
+                              remove_row: {
+                                  name: "删除设备",
+                              },
+                          },
+                      },
+                // 事件
+                afterChange: this.handleUpdataTable, //修改后
+                afterFilter: this.trimmedRows, //排序前
+                afterRemoveRow: this.handleDeleteTableRow, //右键删除
+                afterOnCellMouseDown: this.handleTdClick, //鼠标点击
+            };
+            this.$nextTick(() => {
+                this.tableExample = this.$refs.table.init(settings);
+            });
+        },
+        //去除数组中相同的元素
+        array_diff(a, b) {
+            for (var i = 0; i < b.length; i++) {
+                for (var j = 0; j < a.length; j++) {
+                    if (a[j] == b[i]) {
+                        a.splice(j, 1);
+                        j = j - 1;
+                    }
+                }
+            }
+            return a;
+        },
+        //上传文件弹窗触发事件
+        fileChange(keys) {
+            this.setDataToMain(keys, this.messKey, this.row);
+        },
+        //上传图片弹窗触发事件
+        imgChange(keys) {
+            this.setDataToMain(keys, this.messKey, this.row);
+        },
+        //设备图片弹窗改变事件
+        changePics(keys) {
+            this.setDataToMain(keys, this.messKey, this.row);
+        },
+        //关联系统变更
+        changeSystemType(data) {
+            tools.setDataForKey(this.tableData[this.row], "linkSystem", data);
+        },
+        //表格点击事件
+        handleTdClick(el, rowArr) {
+            //点击的是表头
+            if (rowArr.row < 0) {
+                return;
+            }
+            //被筛选过后的数组
+            let trimmedArr = this.trimmedRows();
+            //是否启用了排序
+            let isSort = this.tableExample.getPlugin("columnSorting").isSorted();
+            if (trimmedArr.length && isSort) {
+                let sortArr = this.myHotArr.getPlugin("columnSorting").rowsMapper.__arrayMap;
+                let infos = this.tableData[trimmedArr[sortArr[rowArr.row]]];
+                this.getInfors(infos, { row: sortArr[rowArr.row], col: rowArr.col });
+            } else if (isSort) {
+                //排序后的数组
+                let sortArr = this.tableExample.getPlugin("columnSorting").rowsMapper.__arrayMap;
+                let infos = this.tableData[sortArr[rowArr.row]];
+                this.getInfors(infos, { row: sortArr[rowArr.row], col: rowArr.col });
+            } else if (trimmedArr.length) {
+                let infos = this.tableData[trimmedArr[rowArr.row]];
+                this.getInfors(infos, { row: trimmedArr[rowArr.row], col: rowArr.col });
+            } else {
+                let infos = this.tableData[rowArr.row];
+                this.getInfors(infos, rowArr);
+            }
+        },
+        //获取被筛选掉的行号
+        trimmedRows() {
+            var plugin = this.tableExample.getPlugin("trimRows").trimmedRows;
+            let dataLength = this.tableData.length;
+            let dataArr = new Array();
+            for (let i = 0; i < dataLength; i++) {
+                dataArr.push(i);
+            }
+            if (plugin.length <= 0) {
+                dataArr = undefined;
+            } else {
+                dataArr = this.array_diff(dataArr, plugin);
+            }
+            return dataArr || [];
+        },
+        getInfors(infos, row) {
+            let val = this.tableExample.colToProp(row.col);
+            let inputData = this.inputMap[val];
+            this.row = row.row;
+            this.messKey = val;
+            switch (val) {
+                //操作
+                case "caozuo":
+                    // window.open(`http://adm.sagacloud.cn:8058/spread?id=${infos.EquipID}&pid=${this.projectId}&secret=${this.secret}`,"_blank")
+                    this.$message("开发中...");
+                    return false;
+                case "defaultQRCode":
+                    this.qrcodeUrl = this.tableData[row.row][val];
+                    if (this.qrcodeUrl) {
+                        this.myDialog.qrcode = true;
+                    } else {
+                        this.$message("此设备没有设备二维码");
+                    }
+                    return false;
+                // 同时创建资产
+                case "checked":
+                    return false;
+                //关联系统
+                case "linkSystem":
+                    if (!this.onlyRead) {
+                        this.systemList = this.tableData[row.row].linkSystem || [];
+                        this.myDialog.systemType = true;
+                    }
+                    return false;
+                //关联资产
+                case "linkEquipLocalName":
+                    if (this.linkNameFalg) {
+                        this.myDialog.changeRea = true;
+                    }
+                    return false;
+                /**
+                 * 关联资产后才可维护的信息点
+                 */
+                // case 'flowBuild': //所属建筑楼层
+                // case 'infos.productDate': //生产日期
+                // case 'infos.serialNum': //出厂编号
+                // case 'infos.assetID': //资产编号
+                // case 'infos.purchasePrice': //采购价格
+                // case 'infos.insuranceNum': //保险单号
+                // case 'infos.insuranceFile': //保险文件
+                //     if (!this.onlyRead) {
+                //         const linkId = this.tableData[row.row].linkId;
+                //         if (!linkId) { //未关联资产
+                //             this.$confirm('您暂未关联设备对应的资产,该信息点不可编辑', '提示', {
+                //                 confirmButtonText: '我知道了',
+                //                 showCancelButton: false,
+                //                 type: 'warning',
+                //                 center: true
+                //             }).then(() => {
+                //                 return false;
+                //             });
+                //         }
+                //     }
+                //     break
+                //保险文件--设备文档--安装质检报告
+                case "infos.insuranceFile": //保险文件
+                case "infos.archive": //设备文档
+                case "infos.checkReport": //安装质检报告
+                case "infos.maintainManual": //维修保养手册
+                case "infos.approachingAcceptance": //进场验收单
+                case "infos.acceptanceReport": //验收报告
+                case "infos.operationManual": //操作说明书
+                case "infos.originalCertificate": //原厂证明
+                case "infos.testReport": //检测报告
+                case "infos.productCertification": //产品合格证
+                case "infos.installInstruction": //安装说明书
+                case "infos.supplierContract": //供应合同
+                case "infos.drawing": //设备图纸
+                case "infos.installDrawing": //安装图纸
+                case "infos.designIDGraph": //设计工况空调机组处理过程焓湿图(F2)
+                case "infos.fanCharacterCurve": //风机性能曲线(F1)
+                    let IPSdata = tools.dataForKey(this.tableData[row.row], val);
+                    this.filesArr = IPSdata ? IPSdata : [];
+                    this.myDialog.uploadFiles = true;
+                    return false;
+                //安装照片--设备铭牌照片
+                case "infos.installPic":
+                case "infos.nameplate":
+                    let SSPPdata = tools.dataForKey(this.tableData[row.row], val);
+                    this.imgsArr = SSPPdata ? SSPPdata : [];
+                    this.myDialog.uploadImgs = true;
+                    return false;
+                //设备照片
+                case "infos.pic":
+                    let Pdata = tools.dataForKey(this.tableData[row.row], val);
+                    this.picsArr = Pdata ? Pdata : [];
+                    this.myDialog.pic = true;
+                    return false;
+                //包含的部件字段
+                case "count":
+                    if (this.onlyRead) {
+                        this.$router.push({ path: "/ledger/parts", query: { deviceId: infos.EquipID } });
+                    } else {
+                        this.$router.push({
+                            path: "/ledger/partsmanage",
+                            query: { deviceId: infos.EquipID, typeId: this.mess.deviceId },
+                        });
+                    }
+                    return false;
+                default:
+                    break;
+            }
+            // 维护多选枚举值
+            if (!this.onlyRead && inputData.dataType == "MENUM") {
+                this.updateInfoData = inputData;
+                this.updateInfoPoint = val;
+                this.updateInput = tools.dataForKey(this.tableData[row.row], val);
+                this.myDialog.menum = true;
+                return false;
+            }
+            // 区间值和复数值暂无组件支持输入
+            if (!this.onlyRead && (inputData.region || inputData.multiple)) {
+                this.$confirm('该信息点为区间值/复数值,暂无组件支持输入!', '提示', {
+                    confirmButtonText: '我知道了',
+                    showCancelButton: false,
+                    type: 'warning',
+                    center: true
+                }).then(() => {
+                    return false;
+                });
+            }
+            if (!this.onlyRead && !inputData.editable) {
+                this.$confirm("该信息点的值为自动生成,不可人工维护!", "提示", {
+                    confirmButtonText: "我知道了",
+                    showCancelButton: false,
+                    type: "warning",
+                    center: true,
+                }).then(() => {
+                    return false;
+                });
+            }
+        },
+        utilToKey(key, name, data, messName) {
+            if (key == name) {
+                this.setDataToMain(data[key], messName, this.row);
+            }
+        },
+        //多选枚举类型值修改
+        handleChangeMenum(newValue) {
+            tools.setDataForKey(this.tableData[this.row], this.updateInfoPoint, newValue);
+            this.handleUpdataTable([[this.row, this.updateInfoPoint, null, newValue]], "edit");
+            this.myDialog.menum = false;
+            this.updateInput = "";
+        },
+        //判断是否有值,有值赋值
+        setDataToMain(data, key, row) {
+            if (!!data && data != "--") {
+                if (!!this.tableData[row]) {
+                    //铭牌照片特殊处理
+                    tools.setDataForKey(this.tableData[row], key, data);
+                    // this.tableData[row][key] = data;
+                } else {
+                    this.tableData[row] = {};
+                    tools.setDataForKey(this.tableData[row], key, data);
+                }
+            } else {
+                tools.setDataForKey(this.tableData[row], key, "");
+            }
+        },
+    },
+};
+</script>
+<style lang="less" scoped>
+#deviceList {
+    overflow: hidden;
+    height: 100%;
+    background-color: #fff;
+    padding: 10px;
+    position: relative;
+
+    .right {
+        background: #fff;
+    }
+
+    .search-header {
+        overflow: hidden;
+        padding: 0 10px 10px 10px;
+        border-bottom: 1px solid #bcbcbc;
+    }
+
+    .tableBox {
+        display: flex;
+        height: calc(100% - 100px);
+        margin-top: 10px;
+
+        .tableLeft {
+            flex: 1;
+        }
+    }
+
+    .create_button {
+        margin-top: 10px;
+    }
+}
+</style>

+ 661 - 0
src/components/ledger/facility/batchPoint.vue

@@ -0,0 +1,661 @@
+<template>
+  	<div id="manageTenantZone">
+		<el-row style="margin:5px 0 0 0;">
+			<el-button style="float:left;" size="small" type="default" icon="el-icon-back" @click="goback"></el-button>
+			<div id="buildFloor">
+                <span class="buildFloor">建筑楼层</span>
+                <el-cascader ref="floorCascader" placeholder="请选择" :props="props" v-model="floorValue" :options="bfOptions" filterable size="small" @change="changeFloor"></el-cascader>
+            </div>
+			<my-cascader ref="cascader" @change="changeDevice"></my-cascader>
+			<el-button type="primary" size="mini" @click="handleClickSave" :disabled="!isChange" style="float:right;">确认</el-button>
+			<el-button size="mini" style="float:right;margin-right:10px;" @click="cancel">取消</el-button>
+		</el-row>
+		<el-row class="main-container">
+			<el-row class="main-container-header">
+				<el-select v-model="isPoint" @change="handleChangeIspoint" placeholder="请选择" style="width: 100px;margin-left: 10px;">
+					<el-option v-for="item in isPointOptions" :key="item.value" :label="item.label" :value="item.value"></el-option>
+				</el-select>
+				<el-input
+					style="width:250px;margin-left: 10px;"
+					placeholder="请输入设备标识关键字"
+					clearable
+					suffix-icon="el-icon-search"
+					@change="handleChangeKeyword"
+					v-model="keyword"
+				></el-input>
+			</el-row>
+			<el-row class="main-container-box">
+				<el-col :span="3" class="container-box-left">
+					<el-select v-model="showName" placeholder="请选择" style="width: 100%;">
+						<el-option v-for="item in showNameOptions" :key="item.value" :label="item.label" :value="item.value"></el-option>
+					</el-select>
+					<div class="equip-list">
+						<el-scrollbar style="height:100%;" v-show="equipList.length">
+							<ul v-infinite-scroll="getEquipData" infinite-scroll-disabled="disabled">
+								<li class="equip-item"
+                                    v-for="item in equipList"
+                                    :class="{'equip-item-selected': item.selected}"
+                                    :key="item.id"
+                                    :title="item[showName]"
+                                    @click="handleSelectIcon(item)"
+                                >
+									<p class="equip-item-text">{{item[showName] || '--'}}</p>
+									<i v-if="item.hasLocation" class="iconfont icon-hook equip-item-icon" style="color: #67C23A;font-size: 14px;"></i>
+									<i v-else class="iconfont icon-cross equip-item-icon" style="color: #ccc;font-size: 14px;"></i>
+								</li>
+								<p style="text-align: center;" v-if="loading">加载中...</p>
+								<p style="text-align: center;" v-if="noMore">没有更多了</p>
+							</ul>
+						</el-scrollbar>
+						<div v-show="!equipList.length" class="no-data">
+							<div class="position-icon">
+								<i class="icon-wushuju iconfont"></i>
+								暂无数据
+							</div>
+						</div>
+					</div>
+				</el-col>
+				<el-col id="canvasBox" class="container-box-right" :span="21" v-loading="canvasLoading">
+					<div v-show="showCanvas" style="height: 100%;">
+						<canvas :height="cadHeight" :width="cadWidth" id="floorCanvas" ref="graphy"></canvas>
+					</div>
+					<div class="no-data" v-show="!showCanvas">
+						<div class="position-icon">
+							<i class="icon-wushuju iconfont"></i>
+							暂无数据
+						</div>
+					</div>
+				</el-col>
+			</el-row>
+		</el-row>
+        <el-popover ref='popover' placement='right' trigger='manual' v-model='visible' width='400'>
+            <div style="text-align: right;margin-bottom: 10px;">
+                <span style="float: left;">设定Z轴高度</span>
+                <el-link icon="el-icon-close" :underline="false" @click="visible = false"></el-link>
+            </div>
+            <el-row>
+                <p>距地面高度:{{equipZ}}mm</p>
+            </el-row>
+            <div class="elSlider">
+                <el-slider v-model="equipZ" :min="minHeight" :max="maxHeight" :step="1" :marks="marks"></el-slider>
+            </div>
+            <el-row style="text-align: right;">
+                <el-button size="mini" @click="visible = false">取消</el-button>
+                <el-button type="primary" size="mini" @click="handleUpdateZ">确认</el-button>
+            </el-row>
+        </el-popover>
+        <span class='popStyle' :style='popoverPosition' v-popover:popover></span>
+  	</div>
+</template>
+<script>
+import floorCascader from "@/components/ledger/lib/floorCascader";
+import myCascader from "@/components/ledger/lib/cascader";
+import { queryEquip, updateEquip, buildingQuery } from '@/api/scan/request';
+import { LocationPointScene, FloorView, Opt, SpaceItem, ZoneItem } from "@saga-web/cad-engine/lib";
+import { SPoint, SColor } from "@saga-web/draw";
+import { mapGetters } from "vuex";
+import { SMathUtil } from "@saga-web/cad-engine/lib/utils/SMathUtil"
+export default {
+  components: {
+    floorCascader,
+    myCascader
+  },
+  data() {
+    return {
+        visible: false,
+        equipZ: 0,
+        maxHeight: 99000,
+        minHeight: -99000,
+        popoverPosition: {
+            top: 0,
+            left: 0,
+        },
+        selectedIcon: null,
+        floorValue: "",
+        oldFloorValue: "",
+		buildId: "", //建筑Id
+		floorId: "", //楼层Id
+		floorMap: "", //楼层平面图地址
+		floorOutline: null, //楼层平面图蒙版地址
+		classCode: "", //设备类别
+		isPoint: "all", //是否有坐标(全部-all、有坐标-yes、无坐标-no)
+		keyword: "", //设备标识关键字
+		showName: "localName", //显示名称
+		isIndeterminate: false, //全选样式显示
+		checkAll: false, //是否全选
+		checkedEquip: [], //选中的设备
+		cadWidth: 800,
+		cadHeight: 800,
+		view: "",
+      	scene: null,
+		showCanvas: false, //是否有底图数据
+		canvasLoading: false,
+		isPointOptions: [{
+          value: "all",
+          label: "全部"
+        }, {
+          value: "yes",
+          label: "有坐标"
+        }, {
+          value: "no",
+          label: "无坐标"
+		}],
+		showNameOptions: [{
+          value: "localName",
+          label: "显示设备本地名称"
+        }, {
+          value: "localId",
+          label: "显示设备本地编码"
+        }, {
+          value: "cADID",
+          label: "显示图纸编码"
+		}],
+        equipList: [],
+        marks: {
+            0: '本层地面',
+            99000: '本层屋顶'
+        },
+		loading: false,
+		pageNumber: 1,
+		pageSize: 50,
+        total: 0,
+        isChange: false, //平面图设备是否更改
+        isMarking: false, // 标记是否选中的是无坐标的设备,true-打点,打完以后重置为false
+        props: {
+            value: 'id',
+            label: 'localName',
+            children: 'floor'
+        }, // 建筑楼层字段绑定
+        bfOptions: [], // 建筑楼层下拉
+        fIdToFloor: {}, // 楼层id映射楼层对象
+	};
+  },
+  computed: {
+	...mapGetters("layout", ["projectId", "projects"]),
+	noMore () {
+		return this.equipList.length >= this.total
+	},
+	disabled () {
+		return this.loading || this.noMore
+	}
+  },
+  created() {
+      this.getCascader()
+  },
+  mounted() {
+    this.cadWidth = document.getElementById('canvasBox').offsetWidth;
+    this.cadHeight = document.getElementById('canvasBox').offsetHeight;
+    // 添加监听点击事件,处理popover显隐
+    window.addEventListener('click', this.handlePopover, false)
+    this.$once('hook:beforeDestroy', () => {
+        window.removeEventListener('click', this.handlePopover)
+    })
+  },
+  methods: {
+    // 返回
+    goback() {
+        this.confirm(() => {
+            this.$router.push({ name: "facilityLedger" });
+        })
+    },
+    getCascader() {
+      let param = {
+        cascade: [
+          { name: "floor", orders: "floorSequenceID desc" }
+        ],
+        orders: "localName asc",
+        pageNumber: 1,
+        pageSize: 50
+      }
+      buildingQuery(param, res => {
+        this.fIdToFloor = {}
+        this.bfOptions = res.content.map(t => {
+            if (t.floor && t.floor.length) {
+                 t.floor = t.floor.map(item => {
+                    this.fIdToFloor[item.id] = item;
+                    return item
+                }).filter(it => it)
+            }
+            return t
+        })
+      })
+    },
+    // 二次确认
+    confirm(callback, cancel) {
+        if (this.isChange) {
+            this.$confirm('平面图数据未保存,此操作将不会保存修改数据!', '提示', {
+                confirmButtonText: '确定',
+                cancelButtonText: '取消',
+                type: 'warning'
+            }).then(() => {
+                callback();
+            }). catch(() => {
+                cancel();
+            });
+        } else {
+            callback();
+        }
+    },
+    // 点击列表设备
+    handleSelectIcon(equip) {
+        // 保存当前选中设备数据
+        this.equipData = equip;
+        // 选中样式处理
+        this.equipList.forEach(item => {
+            if (equip.id === item.id) {
+                item.selected = true;
+            } else {
+                item.selected = false;
+            }
+        })
+        // 有坐标 执行选中
+        if (equip.hasLocation) {
+            // 清除打点动作
+            this.isMarking = false;
+            // 关联平面图设备选中
+            if (this.scene) {
+                this.scene.iconList.forEach(item => {
+                    if (item.data.id === equip.id) {
+                        this.scene.selectContainer.setItem(item);
+                    }
+                })
+            }
+        } else {
+            // 无坐标 执行打点
+            this.isMarking = true;
+        }
+    },
+    handleClickIcon(item, events) {
+        const e = events[0];
+        this.selectedIcon = item;
+        if (item.data.Z) {
+            this.equipZ = (item.data.Z);
+        } else {
+            this.equipZ = 0;
+        }
+        this.visible = false
+        this.$nextTick(() =>{
+            this.popoverPosition.top = `${e.clientY}px`;
+            this.popoverPosition.left = `${e.clientX}px`;
+            this.visible = true;
+        })
+    },
+    // 修改设备Z坐标
+    handleUpdateZ() {
+        if (this.selectedIcon.data.Z !== this.equipZ) {
+            this.selectedIcon.data.Z = this.equipZ;
+            this.isChange = true;
+            this.visible = false;
+            try {
+                const str = this.selectedIcon.textItemList[0].text.split('\n');
+                str[2] = this.equipZ
+                this.selectedIcon.textItemList[0].text = str.join('\n');
+            } catch (e) {
+                console.log(e);
+            }
+        }
+    },
+    listChange(item, ev) {
+        if (!ev[0].length) {
+            this.visible = false;
+            this.selectedIcon = null;
+        }
+    },
+    /**
+     * @description 处理popover显隐
+     * 当点击位置不在canvas内部时 ( 如点击 页面空白,图例,楼层,设备设施左侧列表项时),将visible置为false,隐藏popover
+     */
+    handlePopover(e) {
+        this.$nextTick(() => {
+            if (!this.$refs.graphy.contains(e.target)) {
+                this.visible = false
+            }
+        })
+    },
+	//获取所以主体数据---用于维护信息
+	getEquipData() {
+		if (this.classCode && this.floorId && (this.floorId != "noKnow" || this.floorId != "all")) {
+			this.loading = true;
+			let params = {
+				filters: `classCode='${this.classCode}'`,
+				pageSize: this.pageSize,
+				orders: 'createTime desc, id desc',
+				pageNumber: this.pageNumber
+			}
+			if (this.buildId == 'noKnow') {
+				params.filters += `;buildingId isNull`
+			} else if (this.buildId && this.buildId != 'all') {
+				params.filters += `;buildingId='${this.buildId}'`
+			}
+
+			if (this.floorId == 'noKnow') {
+				params.filters += `;floorId isNull`
+			} else if (this.floorId && this.floorId != 'all') {
+				params.filters += `;floorId='${this.floorId}'`
+			}
+
+			if (this.isPoint == "yes") {
+				params.filters += `;not bimLocation isNull`
+			} else if (this.isPoint == "no") {
+				params.filters += `;bimLocation isNull`
+			}
+
+			if (this.keyword) {
+				params.filters += `;localName contain'${this.keyword}' or localId contain '${this.keyword}' or cADID contain '${this.keyword}'`
+			}
+
+	        this.canvasLoading = true;
+			queryEquip(params, (res) => {
+                this.equipList = [];
+				this.equipList = this.equipList.concat(res.content.map(item => {item.selected = false; item.hasLocation = !!item.bimLocation; return item;}));
+				if (this.view && this.view.scene) {
+					res.content.forEach(item =>{
+						if (item.locationJson && item.locationJson.x && item.locationJson.y) {
+                            this.scene.addIconText({
+                                id: item.id,
+                                X: item.locationJson.x, 
+                                Y: -item.locationJson.y, 
+                                Z: item.locationJson.z,
+                                text: [`${item.localName || '--'}\n${item.localId || '--'}\n${item.locationJson.z || '--'}`]
+                            });
+						}
+                    });
+                    this.scene.iconMove(this, this.iconMove);
+                    this.scene.spaceClick(this, this.spaceClick);
+                    this.scene.iconChangeText(this, this.handleClickIcon);
+					this.view.update();
+                    this.canvasLoading = false;
+                    this.isChange = false;
+				}
+				this.total = res.total;
+				if (!this.noMore) { //判断是否有更多数据
+					this.pageNumber++;
+				}
+				this.loading = false;
+			})
+		}
+    },
+    //设备移动回调
+    iconMove(item) {
+        this.isChange = true;
+    },
+    // 打点
+    spaceClick(item, e) {
+        if (this.isMarking) {
+            const p = item.mapToScene(e[0].x, e[0].y)
+            this.scene.addIconText({
+                id: this.equipData.id,
+                X: p.x, 
+                Y: p.y, 
+                Z: 0,
+                text: [`${this.equipData.localName || '--'}\n${this.equipData.localId || '--'}\n0`]
+            });
+            // 保存按钮
+            this.isChange = true;
+            // 设置有坐标标识
+            this.equipData.hasLocation = true;
+            // 绑定事件
+            this.scene.iconChangeText(this, this.handleClickIcon)
+            // 重置打点动作
+            this.isMarking = false;
+        }
+        // 取消选中状态
+        this.scene && this.scene.selectContainer.clear()
+    },
+	//初始化条件并查询设备
+	initParams() {
+		this.pageNumber = 1;
+		this.total = 0;
+		this.equipList = [];
+        this.clearIcon();
+        if (this.view) {
+            this.view.update();
+        }
+		this.getEquipData();
+	},
+    //修改楼层
+    changeFloor(value) {
+        this.confirm(() => {         
+            this.oldFloorValue = value;
+            this.isChange = false;
+            this.showCanvas = false;
+            this.clearGraphy();
+            if (value[0]) {
+                this.buildId = value[0];
+            }
+            if (value[1]) {
+                this.floorId = value[1];
+                this.floorData = this.fIdToFloor[value[1]];
+            } else {
+                this.floorId = "";
+                this.floorData = {}
+            }
+            if (this.floorData.infos && this.floorData.infos.floorMap) {
+                this.floorMap = this.floorData.infos.floorMap;
+                this.showCanvas = true;
+                this.getGraphy();
+            } else {
+                this.initParams();
+                this.floorMap = "";
+            }
+            if (this.floorData.infos && this.floorData.infos.height) {
+                this.maxHeight = this.floorData.infos.height;
+                this.minHeight = -(this.floorData.infos.height);
+            } else {
+                this.maxHeight = 99000;
+                this.minHeight = -99000;
+            }
+            if (this.floorData.outline && this.floorData.outline.length) {
+                this.floorOutline = this.floorData.outline;
+            } else {
+                this.floorOutline = null;
+            }
+        }, () => {
+            this.floorValue = this.oldFloorValue;
+        })
+	},
+    //修改设备族
+    changeDevice(value) {
+		if (value.code) {
+			this.classCode = value.code;
+		} else {
+			this.classCode = "";
+		}
+		this.initParams();
+	},
+	//切换是否有坐标
+	handleChangeIspoint() {
+		this.initParams();
+	},
+	//检索关键字
+	handleChangeKeyword() {
+		this.initParams();
+    },
+    // 保存数据
+    handleClickSave() {
+        if (this.scene && this.scene.iconList.length && this.isChange) {
+            this.isChange = false
+            let param = {content: []}
+            this.scene.iconList.map((item) => {
+                if (item.data.id) {
+                    param.content.push({
+                        id: item.data.id,
+                        bimLocation: `${item.x},${-item.y},${item.data.Z}`
+                    })
+                }
+            })
+            updateEquip(param, () => {
+                this.$message({message: '保存成功!', type: 'success'});
+            })
+        }
+    },
+	// 获取底图
+    getGraphy() {
+      this.scene = new LocationPointScene();
+      this.scene.selectContainer.connect('listChange', this, this.listChange)
+      this.canvasLoading = true;
+      const temp = this.floorMap.split('.')[1];
+      if (temp && temp.toString().toLowerCase() == 'png' || temp.toString().toLowerCase() == 'jpg') {
+        let x = 0;
+        let y = 0;
+        let scale = 1;
+        if (this.floorData && this.floorData.infos && this.floorData.infos.floorMapRatio && this.floorData.infos.floorMapShift) {
+            const point = JSON.parse(this.floorData.infos.floorMapShift);
+            x = point.x;
+            y = point.y;
+            scale = this.floorData.infos.floorMapRatio;
+        }
+        this.scene.loadImg(`/image-service/common/image_get?systemId=dataPlatform&key=${this.floorMap}`, res => {
+          this.getGraphtSuc(res)
+        }, x, y, scale)
+      } else {
+        this.scene.loadUrl(`/image-service/common/file_get?systemId=revit&key=${this.floorMap}`).then(res => {
+          this.getGraphtSuc(res)
+        })
+      }
+	},
+	getGraphtSuc(res) {
+	  this.initParams();
+      this.canvasLoading = false;
+      if (res == 'error') {
+        this.FloorMap = '';
+        console.log('数据解析异常');
+        return;
+      }
+      this.view.scene = this.scene;
+      // 蒙版
+      if (this.floorOutline) {
+        Opt.sceneMarkColor = new SColor('#ffffff');
+        let newArr = this.floorOutline.map(t => {
+          return new SPoint(t.X, t.Y);
+        })
+        this.scene.addSceneMark(newArr)
+      }
+	  this.scene.isSpaceSelectable = false;
+	  this.view.fitSceneToView();
+    //   this.view.minScale = this.view.scale;
+    //   if (this.$refs.canvasFun) {
+    //     this.$refs.canvasFun.everyScale = this.view.scale;
+	//   }
+    },
+	//清空平面图
+    clearGraphy() {
+      if (this.view) {
+        this.view.scene = null;
+        return
+	  }
+      this.view = new FloorView("floorCanvas");
+	},
+	//清空标记
+	clearIcon() {
+        if (this.scene)
+        this.scene.clearIcon();
+    },
+    // 取消操作
+    cancel() {
+        this.initParams();
+    }
+  },
+  watch: {
+    projectId() {
+      this.$router.push({ name: "facilityLedger" });
+    }
+  }
+};
+</script>
+<style lang="less" scoped>
+@normal-border-color: #e4e4e4;
+@normal-border-active-color: #e8e8e8;
+#manageTenantZone {
+  position: relative;
+  height: 100%;
+  #buildFloor{
+    margin-left: 10px;
+    float: left;
+    .buildFloor {
+        color: #999999;
+        font-size: 14px;
+        margin-right: 12px;
+    }
+  }
+  .main-container {
+    margin-top: 10px;
+    background: #fff;
+    border: 1px solid @normal-border-color;
+    height: calc(100% - 53px);
+	overflow: hidden;
+	.main-container-header {
+		padding: 10px 0;
+		border-bottom: 1px solid @normal-border-color;
+	}
+	.main-container-box {
+		height: calc(100% - 53px);
+		.container-box-left {
+			height: 100%;
+			border-right: 1px solid @normal-border-color;
+			/deep/ .el-input__inner {
+				border: none;
+				border-bottom: 1px solid #DCDFE6;
+				border-radius: 0;
+			}
+			.equip-list {
+				height: calc(100% - 32px);
+				/deep/ .el-scrollbar__wrap {
+					overflow-x: hidden;
+				}
+				.equip-item {
+					padding-left: 15px;
+					padding-right: 10px;
+					width: 100%;
+					height: 32px;
+					line-height: 32px;
+					box-sizing: border-box;
+					cursor: pointer;
+					.equip-item-text {
+						display: inline-block;
+						width: calc(100% - 20px);
+						overflow: hidden;
+						white-space: nowrap;
+						text-overflow: ellipsis;
+					}
+					.equip-item-icon {
+						float: right;
+					}
+                }
+                .equip-item-selected {
+                    background-color: #f5f7fa;
+                }
+				.equip-item:hover {
+					background-color: #f5f7fa;
+				}
+			}
+		}
+		.container-box-right {
+			height: 100%;
+		}
+	}
+  }
+  .popStyle {
+    position: fixed;
+  }
+}
+.elSlider{
+    padding: 10px 24px;
+    margin-bottom:15px;
+    /deep/ .el-slider__marks-text:nth-child(2) {
+        width: 80px;
+    }
+}
+.no-data {
+	height: 100%;
+	text-align: center;
+	box-sizing: border-box;
+	position: relative;
+	.position-icon {
+		position: absolute;
+		top: 50%;
+		left: 50%;
+		transform: translate(-50%, -50%);
+	}
+}
+</style>

+ 302 - 0
src/components/ledger/facility/batchlink.vue

@@ -0,0 +1,302 @@
+<template>
+  <div class="box">
+    <div class="condition">
+      <div class="header">
+        <el-button style="float:left;" size="small" type="default" icon="el-icon-back" @click="goBack"></el-button>
+        <span class="statistics-title">发现未关联资产的设备:<b>{{ unlinkNumber }}</b></span>
+        <span class="statistics-title">可在此功能中批量关联的:<b>{{ linkNumber }}</b></span>
+        <el-button style="float:right;margin-top:1px;" @click="handleClickCreate">保存选定的设备资产关联关系</el-button>
+        <el-button v-show="Object.keys(radio).length" style="float:right;margin-top:1px;margin-right:15px;"
+                   @click="handleClickReset">重置
+        </el-button>
+        <span style="float:right;margin-top:5px;font-size:12px;margin-right:10px;color:#999;">不能批量处理的请在设备台账列表中操作</span>
+      </div>
+      <div class="main">
+        <el-table ref="multipleTable" :data="tableData" v-loading='loading' stripe height="100%"
+                  :span-method="objectSpanMethod" :header-cell-style="headerStyle">
+          <el-table-column prop="localName" label="设备本地名称" show-overflow-tooltip width="150">
+            <!-- <template slot-scope="scope">{{ scope.row.EquipLocalName?scope.row.EquipLocalName:scope.row.EquipName }}</template> -->
+          </el-table-column>
+          <el-table-column prop="localId" label="设备本地编码" show-overflow-tooltip max-width="150">
+            <!-- <template slot-scope="scope">{{ scope.row.EquipLocalID?scope.row.EquipLocalID:scope.row.EquipID }}</template> -->
+          </el-table-column>
+          <el-table-column prop="equipCategory.name" label="设备类" show-overflow-tooltip
+                           width="100"></el-table-column>
+          <el-table-column prop="cADID" label="设计图纸中编码" show-overflow-tooltip max-width="150"></el-table-column>
+          <el-table-column label="所属建筑楼层">
+            <template slot-scope="scope">
+              {{
+                scope.row.building ? scope.row.building.localName : ''
+              }}-{{ scope.row.floor ? scope.row.floor.localName : '' }}
+            </template>
+          </el-table-column>
+          <el-table-column prop="PropertyItem.localName" label="资产本地名称" show-overflow-tooltip width="150">
+            <!-- <template slot-scope="scope">{{ scope.row.AssetsLocalName }}</template> -->
+          </el-table-column>
+          <el-table-column prop="PropertyItem.localId" label="资产本地编码" show-overflow-tooltip max-width="150">
+            <!-- <template slot-scope="scope">{{ scope.row.AssetsLocalId }}</template> -->
+          </el-table-column>
+          <el-table-column prop="PropertyItem.familyName.name" label="设备族" show-overflow-tooltip
+                           width="100"></el-table-column>
+          <el-table-column prop="PropertyItem.cADID" label="设计图纸中编码" show-overflow-tooltip
+                           max-width="150"></el-table-column>
+          <el-table-column label="所属建筑楼层" show-overflow-tooltip width="200">
+            <template slot-scope="scope">
+              {{
+                scope.row.PropertyItem.building ? scope.row.PropertyItem.building.localName : ''
+              }}-{{ scope.row.PropertyItem.floor ? scope.row.PropertyItem.floor.localName : '' }}
+            </template>
+          </el-table-column>
+          <el-table-column prop="address" label="操作" width="100">
+            <template slot-scope="scope">
+              <el-radio v-model="radio[scope.row.id]" :label="scope.row.PropertyItem.id"
+                        :disabled="scope.row.disabled" @change="handleChangeLink(scope.row)">&nbsp;
+              </el-radio>
+            </template>
+          </el-table-column>
+        </el-table>
+      </div>
+      <!-- <div class=footer>
+        <el-pagination
+          class="right"
+          v-show="tableData && tableData.length"
+          @size-change="handleSizeChange"
+          @current-change="handleCurrentChange"
+          :current-page="page.pageNumber"
+          :page-sizes="page.pageSizes"
+          :page-size="page.pageSize"
+          layout="total, sizes, prev, pager, next, jumper"
+          :total="page.total">
+        </el-pagination>
+      </div> -->
+    </div>
+  </div>
+</template>
+
+<script>
+import {mapGetters} from "vuex"
+//api
+import {equipCategoryProperty, equipRproperty} from "@/api/scan/request";
+
+export default {
+  components: {},
+  computed: {
+    ...mapGetters("layout", ["projectId", "userId", "secret"])
+  },
+  created() {
+    this.params = this.$route.params
+    this.getTableData()
+  },
+  data() {
+    return {
+      params: {},
+      loading: false,
+      unlinkNumber: 0,
+      linkNumber: 0,
+      // page: {
+      //   pageSize: 50,
+      //   pageSizes: [10, 20, 50, 100],
+      //   pageNumber: 1,
+      //   total: 0
+      // },
+      radio: {},
+      contentData: [],
+      tableData: [],
+      mergeCol: [],
+      headerStyle: {
+        backgroundColor: '#e1e4e5',
+        color: '#2b2b2b',
+        lineHeight: '30px'
+      }, // 列表样式
+    }
+  },
+  methods: {
+    //获取表格数据
+    getTableData(pageNum) {
+      this.loading = true
+      let fl = this.formatFilter(pageNum)
+      let param = {
+        category: fl.category || undefined,
+        spaceIdList: fl.spaceIdList || undefined,
+        
+        orders: "createTime desc",
+        pageNumber: 1,
+        pageSize: 1000
+      }
+      equipCategoryProperty(param, res => {
+        this.unlinkNumber = res.total
+        this.linkNumber += res.content.length
+        this.contentData = this.contentData.concat(res.content)
+        if (res.total / (res.pageSize * res.pageNumber) > 1) {
+          this.getTableData(res.pageNumber + 1)
+        } else {
+          this.contentData.forEach(item => {
+            if (item.propertyList && item.propertyList.length) {
+              item.propertyList.forEach((Assets, index) => {
+                let obj = JSON.parse(JSON.stringify(Object.assign(item, { PropertyItem: Assets, disabled: false })))
+                delete obj.propertyList
+                this.tableData.push(obj)
+                if (index == 0) {
+                  this.mergeCol.push(item.propertyList.length)
+                } else {
+                  this.mergeCol.push(false)
+                }
+              })
+            }
+          })
+          this.loading = false
+        }
+      })
+    },
+    //格式化条件
+    formatFilter(pageNum) {
+      pageNum = pageNum ? pageNum : 1
+      let param = {
+        data: {
+          orders: "createTime desc, id asc",
+          pageNumber: pageNum,
+          pageSize: 1000
+        },
+        category: this.params.deviceType
+      }
+
+      if (this.params.buildFloor[0] == "noKnow") {
+        param.data.filters = `buildingId isNull`
+      } else if (this.params.buildFloor[0] && this.params.buildFloor[0] != "all") {
+        param.data.filters = `buildingId='${this.params.buildFloor[0]}'`
+        if (this.params.buildFloor[1] == "noKnow") {
+          param.data.filters += `;floorId isNull`
+        } else if (this.params.buildFloor[1] && this.params.buildFloor[1] != "all") {
+          param.data.filters += `;floorId='${this.params.buildFloor[1]}'`
+        }
+      }
+      if (this.params.hasOwnProperty('spaceList')) {
+        if (typeof this.params.spaceList == 'string') {
+          param.spaceIdList = `&spacrType=${this.params.spaceList}`
+        } else {
+          let spaceIdList = ''
+          this.params.spaceList.forEach(item => {
+            spaceIdList += `&spaceIdList=${item}`
+          })
+          param.spaceIdList = spaceIdList
+        }
+      }
+      return param
+    },
+    objectSpanMethod({ row, column, rowIndex, columnIndex }) {
+      if (columnIndex < 5) {
+        if (this.mergeCol[rowIndex]) {
+          return {
+            rowspan: this.mergeCol[rowIndex],
+            colspan: 1
+          };
+        } else {
+          return {
+            rowspan: 0,
+            colspan: 1
+          };
+        }
+      }
+    },
+    //切换关联
+    handleChangeLink(row) {
+      this.tableData.forEach(item => {
+        item.disabled = false
+        if (Object.keys(this.radio).length) {
+          for (let key in this.radio) {
+            if (item.id != key && item.PropertyItem.id == this.radio[key]) {
+              item.disabled = true
+            }
+          }
+        }
+      })
+    },
+    //重置关联选择
+    handleClickReset() {
+      this.tableData.forEach(item => {
+        item.disabled = false
+      })
+      this.radio = {}
+    },
+    //创建已选
+    handleClickCreate() {
+      let params = {
+        content: []
+      }
+      if (Object.keys(this.radio).length) {
+        for (let key in this.radio) {
+          params.content.push({
+            id: this.radio[key],
+            equipId: key
+          })
+          console.log(params.content)
+        }
+        equipRproperty(params, res => {
+          this.radio = {}
+          this.linkNumber = 0
+          this.contentData = []
+          this.tableData = []
+          this.getTableData()
+          this.$message.success('关联成功!')
+        })
+      } else {
+        this.$message.info('请选择要关联的关系!')
+      }
+    },
+    //切换每页显示多少条数据
+    // handleSizeChange(val) {
+    //   this.page.pageSize = val
+    //   this.getTableData()
+    // },
+    //切换页数
+    // handleCurrentChange(val) {
+    //   this.page.pageNumber = val
+    //   this.getTableData()
+    // },
+    //返回
+    goBack() {
+      this.$router.push({ name: "facilityLedger" })
+    }
+  },
+  watch: {
+    projectId() {
+      this.$router.push({ name: "facilityLedger" })
+    }
+  }
+};
+</script>
+
+<style scoped lang='less'>
+.box {
+  .condition {
+    padding: 10px;
+    display: flex;
+    height: 100%;
+    flex-direction: column;
+    border: 1px solid #dfe6ec;
+    background: #fff;
+
+    .header {
+      padding-bottom: 10px;
+      border-bottom: 1px solid #bcbcbc;
+
+      .statistics-title {
+        margin-left: 20px;
+        line-height: 32px;
+      }
+    }
+
+    .main {
+      margin-top: 10px;
+      height: calc(100% - 46px);
+    }
+  }
+}
+</style>
+
+<style lang="less">
+.el-table th {
+  background-color: #d9d9d9;
+  color: #000;
+}
+</style>

+ 675 - 0
src/components/ledger/facility/details/index.vue

@@ -0,0 +1,675 @@
+<template>
+  <div class="tableDisplay">
+    <div class="header">
+      <el-button style="float:left;" size="small" type="default" icon="el-icon-back" @click="goBack"></el-button>
+      <span :title="`${name} - ${code}`" class="details-title"><b>{{ name }} - {{ code }}</b></span>
+    </div>
+    <div class="content">
+      <el-scrollbar style="height:100%;">
+        <div class="content-scrollbar">
+          <div class="content-left">
+            <div class="content-keyPoint">
+              <exhibitionCrux :exhibitionCrux="exhibitionCrux" :type="type" @cruxSuccess="cruxSuccess"/>
+            </div>
+            <div class="content-showType">
+              <selectRadio
+                :selectRadio="selectRadio"
+                @changeRadio="changeRadio"
+              />
+            </div>
+            <div class="content-point">
+              <div class="content-point-left">
+                <exhibitionBaseInformation :exhibitionBaseInformation="exhibitionBaseInformation"/>
+              </div>
+              <div class="content-point-right">
+                <exhibitionEnergy :exhibitionEnergy="exhibitionEnergy"/>
+              </div>
+            </div>
+          </div>
+          <div class="content-right">
+            <div class="content-repair">
+              <exhibitionMaintain :exhibitionMaintain="exhibitionMaintain"/>
+            </div>
+            <div class="content-media">
+              <exhibitionImage :exhibitionImage="exhibitionImage" v-show="isShowImage"/>
+              <exhibitionVideo :exhibitionVideo="exhibitionVideo" v-show="isShowVideo"/>
+              <exhibitionFile :exhibitionFile="exhibitionFile" v-show="isShowFile"/>
+            </div>
+          </div>
+        </div>
+      </el-scrollbar>
+    </div>
+    <div class="footer">
+      <el-button-group style="float:right;margin:8px 10px 0 0">
+        <el-button
+          :disabled="list.findIndex(item => item.value == id) == 0"
+          @click="handleClickPrev"
+          size="mini"
+          icon="el-icon-arrow-left">
+          上一页
+        </el-button>
+        <el-select size="mini" style="float:left;margin:0 10px;" v-model="id" @change="handleChangeExample"
+                   placeholder="请选择">
+          <el-option
+            v-for="item in list"
+            :key="item.value"
+            :label="item.label"
+            :value="item.value">
+          </el-option>
+        </el-select>
+        <el-button
+          :disabled="list.findIndex(item => item.value == id) == list.length-1"
+          @click="handleClickNext"
+          size="mini">
+          下一页
+          <i class="el-icon-arrow-right el-icon--right"></i>
+        </el-button>
+      </el-button-group>
+    </div>
+  </div>
+</template>
+
+<script>
+import exhibitionBaseInformation from "@/components/ledger/details/detail/exhibitionBaseInformation";
+import exhibitionFile from "@/components/ledger/details/detail/exhibitionFile";
+import exhibitionImage from "@/components/ledger/details/detail/exhibitionImage";
+import exhibitionVideo from "@/components/ledger/details/detail/exhibitionVideo";
+import exhibitionCrux from "@/components/ledger/details/detail/exhibitionCrux";
+import exhibitionEnergy from "@/components/ledger/details/detail/exhibitionEnergy";
+import exhibitionMaintain from "@/components/ledger/details/detail/exhibitionMaintain";
+import selectRadio from "@/components/ledger/details/detail/selectRadio";
+import tools from "@/utils/scan/tools"
+import {BeatchQueryParam} from "@/api/scan/request";
+import {getDataDictionary} from "@/api/dict";
+import {queryEquip} from "@/api/object/equip"
+import {mapGetters} from "vuex";
+
+export default {
+  name: "deviceDetails",
+  components: {
+    exhibitionBaseInformation,
+    exhibitionFile,
+    exhibitionVideo,
+    exhibitionImage,
+    exhibitionMaintain,
+    selectRadio,
+    exhibitionCrux,
+    exhibitionEnergy
+  },
+  created() {
+    this.id = this.$route.query.equipId;
+    this.name = this.$route.query.equipName;
+    this.code = this.$route.query.equipCode;
+    this.type = this.$route.query.type;
+    this.list = this.$route.query.data;
+    this.getData();
+  },
+  computed: {
+    ...mapGetters("layout", ["projectId", "secret", "userId", "group_code"])
+  },
+  data() {
+    return {
+      id: '', //当前设备id
+      name: '', //当前设备本地名称
+      code: '', //当前设备的本地编码
+      type: '', //设备类型
+      list: [], //设备列表
+      pointData: [], //信息点数据
+      exampleData: {}, //实例数据
+      currentRadio: '1',//当前选中的select
+      criterias: [], //动态参数
+      criteriasArray: [],
+      selectRadio: {
+        information: '设备信息点',
+        initRadio: '1',
+        radioList: [
+          {
+            value: '显示需采集信息点',
+            label: '1'
+          },
+          {
+            value: '显示有值信息点',
+            label: '2'
+          },
+          {
+            value: '显示全部信息点',
+            label: '3'
+          }
+        ]
+      },
+      exhibitionBaseInformation: {
+        information: {}
+      },
+      exhibitionEnergy: {
+        title: '能耗信息',
+        firstName: '',
+        dynamic: [],
+        list: {}
+      },
+      exhibitionMaintain: {
+        title: '维修/维保记录'
+      },
+      exhibitionFile: {
+        title: '文件',
+        list: {}
+      },
+      exhibitionCrux: {
+        title: '关键信息点:',
+        cruxArray: [],
+        allMessage: [],//全部信息点
+      },
+      exhibitionImage: {
+        title: '图片',
+        list: {},
+        arr: []
+      },
+      exhibitionVideo: {
+        title: '视频',
+        list: []
+      },
+      instance: {},//实例信息处理
+      isShowImage: false,
+      isShowVideo: false,
+      isShowFile: false,
+    }
+  },
+  methods: {
+    deepCopy(obj) {
+      return JSON.parse(JSON.stringify(obj))
+    },
+    cruxSuccess() {
+      this.getData()
+    },
+    // 获取表头和实例数据(动态数据在组件中获取,在此格式化请求参数)
+    getData() {
+      let params1 = {
+        orders: "sort asc",
+        pageNumber: 1,
+        pageSize: 1000,
+        type: this.type
+      }, params2 = {
+        filters: `id='${this.id}'`,
+      };
+      let promise1 = new Promise((resolve, reject) => {
+        getDataDictionary(params1, res => {
+          resolve(res)
+        })
+      })
+      let promise2 = new Promise((resolve, reject) => {
+        queryEquip(params2, res => {
+          resolve(res)
+        })
+      })
+      // 数据初始化
+      this.exhibitionEnergy.dynamic = [];
+      this.criterias = [];
+
+      Promise.all([promise1, promise2]).then(values => {
+        this.pointData = values[0].content;
+        this.exampleData = values[1].content[0];
+        this.name = this.exampleData.localName ? this.exampleData.localName : this.exampleData.name ? this.exampleData.name : '';
+        this.code = this.exampleData.localId ? this.exampleData.localId : '';
+        let obj = this.deepCopy(values[1].content[0]);
+        obj = tools.flattenKeys(obj);
+        for (let key in obj) {           //删除对象中数组为空的项
+          if (Array.isArray(obj[key]) && !obj[key].length) {
+            delete obj[key]
+          }
+        }
+        this.instance = obj;
+        this.pointData.forEach(item => {
+          if (item.category != "STATIC") {
+            let cur = tools.dataForKey(this.exampleData, item.path);
+            if (cur) {
+              this.criterias.push({
+                objectId: this.exampleData.id,
+                infoCode: item.code
+              })
+            }
+          }
+            if (this.instance.hasOwnProperty(item.path)) {
+                item.value = this.instance[item.path]
+            }
+        })
+        let params = { //获取动态参数
+          groupCode: this.group_code,
+          appId: "datacenter",
+          projectId: this.projectId,
+          data: []
+        }
+        params.data = this.criterias
+        if (this.criterias.length) {
+          BeatchQueryParam(params, res => {
+            this.criteriasArray = res.data;
+            res.data.length && res.data.map(child => {
+              this.pointData.map(head => {
+                if (head.code == child.infoCode) {
+                  if (head.dataSource) {
+                    head.dataSource.forEach(i => {
+                      if (i.code == child.data) {
+                        this.$set(head, 'data', i.name)
+                      }
+                    })
+                  } else {
+                    this.$set(head, 'data', child.data)
+                  }
+                  // this.$set(head, 'svalue', child.value)
+                  this.$set(head, 'svalue', child.infoValue)
+                  // this.$set(head, 'receivetime', child.receivetime)
+                  this.$set(head, 'receivetime', child.time)
+                  this.$set(head, 'error', child.error)
+                }
+                return head
+              })
+            })
+            this.handleRadios(this.currentRadio);
+            this.handleEnclosure() //处理图片视频文件信息
+          })
+        } else {
+          this.handleRadios(this.currentRadio);
+          this.handleEnclosure() //处理图片视频文件信息
+        }
+
+      })
+    },
+    handleEnclosure() {
+      //图片文件处理,脱离radio控制
+      let isArr = []
+      isArr = this.pointData.filter(k => k.value)
+      this.handlePicList(isArr)
+      this.handleFileList(isArr)
+    },
+    handleRadios(val) { //判断radio的值显示关键信息,基本信息,图片,文件
+      let arr = []
+      //关键信息点
+      this.exhibitionCrux.cruxArray = this.pointData.filter(i => i.keyWord)
+      this.exhibitionCrux.cruxArray.map(i => {
+        if (this.instance.hasOwnProperty(i.path)) {
+          return {
+            ...i,
+            value: this.instance[i.path]
+          }
+        }
+      })
+
+      if (val == 1) { //需采集信息
+        arr = this.pointData.filter(i => i.visible)
+      } else if (val == 2) { //有值信息点
+        arr = this.pointData.filter(k => k.value)
+      } else { //全部信息点
+        arr = this.pointData
+      }
+      this.displayData(arr)
+    },
+    handlePicList(arr) {
+      let picObject = {}
+      let video = []
+      //处理数据格式
+      arr.map(i => {
+        if (i.dataType == 'ATTACHMENT') {
+          switch (i.path) {
+            case 'infos.installPic':
+            case 'infos.nameplate':
+            case 'infos.pic':
+              if (this.instance.hasOwnProperty(i.path)) {
+                i.PicList = this.instance[i.path].filter(i => i.type === 'image' || i.type === 'panorama')
+                video = video.concat(this.instance[i.path].filter(i => i.type === 'video'))
+              }
+              if (picObject[i.name]) {
+              } else {
+                picObject[i.name] = []
+              }
+              picObject[i.name].push({
+                ...i,
+              })
+              break;
+          }
+        }
+
+      })
+      //展示图片
+      let isArray = [] //获取图片数组
+      for (let i in picObject) {
+        if (Array.isArray(picObject[i])) {
+          let list = picObject[i]
+          list.forEach(i => {
+            if (i.PicList) {
+              isArray.push(...i.PicList)
+            }
+          })
+        }
+      }
+      this.exhibitionImage.list = picObject
+      this.exhibitionImage.arr = isArray
+      this.exhibitionVideo.list = video
+      //判断是否显示图片组件
+      this.isShowImage = isArray.length ? true : false
+      //判断是否显示video组件
+      this.isShowVideo = video.length ? true : false
+
+    },
+    handleFileList(arr) {
+      let picObject = {}
+      //处理数据格式
+      arr.map(i => {
+        if (i.dataType == 'ATTACHMENT') {
+          switch (i.path) {
+            case 'infos.originalCertificate': //原厂证明
+            case 'infos.acceptanceReport': //验收报告
+            case 'infos.maintainManual': //维修保养手册
+            case 'infos.archive': //设备文档
+            case 'infos.drawing': //设备图纸
+            case 'infos.approachingAcceptance': //进场验收单
+            case 'infos.testReport': //检测报告
+            case 'infos.supplierContract': //供应合同
+            case 'infos.productCertification': //产品合格证
+            case 'infos.operationManual': //操作说明书
+            case 'infos.insuranceFile': //保险文件
+            case 'infos.checkReport': //安装质检报告
+            case 'infos.installDrawing': //安装图纸
+            case 'infos.installInstruction': //安装说明书
+            case 'infos.designIDGraph': //设计工况空调机组处理过程焓湿图
+            case 'infos.fanCharacterCurve': //风机性能曲线
+              if (this.instance.hasOwnProperty(i.path)) {
+                i.fileList = this.instance[i.path]
+              }
+              if (picObject[i.name]) {
+
+              } else {
+                picObject[i.name] = []
+              }
+              picObject[i.name].push({
+                ...i,
+              })
+              break
+          }
+        }
+      })
+      //展示文件
+      this.exhibitionFile.list = picObject
+      let isArray = [] //获取文件数组
+      for (let i in picObject) {
+        if (Array.isArray(picObject[i])) {
+          let list = picObject[i]
+          list.forEach(i => {
+            if (i.fileList) {
+              isArray.push(...i.fileList)
+            }
+          })
+        }
+      }
+      //判断是否显示文件组件
+      this.isShowFile = isArray.length ? true : false
+
+    },
+    changeRadio(val) { //1,需采集,2,有值,3,全部
+      this.handleRadios(val)
+      this.currentRadio = val
+    },
+    formatDate(str) {
+      if (str) {
+        if (str.includes('-')) {
+          return str
+        } else {
+          if (str.length > 8) {
+            return str.substr(0, 4) + "-" + str.substr(4, 2) + "-" + str.substr(6, 2) + " " + str.substr(8, 2) + ":" + str.substr(10, 2) + ":" + str.substr(12, 2)
+          } else {
+            return str.substr(0, 4) + "-" + str.substr(4, 2) + "-" + str.substr(6, 2) + " " + str.substr(8, 2)
+          }
+        }
+      } else {
+        return '--'
+      }
+    },
+    displayData(arr) {//对数据进行处理传给组件展示
+      const result = {}
+      const resultEnergy = {};
+      arr.map(i => {
+        if (this.instance.hasOwnProperty(i.path) && this.currentRadio != 2) {
+          i.value = this.instance[i.path]
+        }
+        if (i.dataType == 'DATETIME') {
+          if (this.instance.hasOwnProperty(i.path)) {
+            let time = this.instance[i.path]
+            i.value = this.formatDate(time)
+          }
+        }
+        if (i.dataSource && i.dataSource.length) {
+          // let source = JSON.parse(i.dataSource)
+          let source = i.dataSource
+          //判断输入类型
+          if (i.dataType == 'ENUM') {
+            let d1l = tools.formatDataSource(i.dataSource)
+            d1l.forEach(k => {
+              if (k.code == this.instance[i.path]) {
+                i.value = k.name
+              }
+            })
+          } else if (i.dataType == 'MENUM' || i.dataType == 'DATETIME'){
+            if (this.instance.hasOwnProperty(i.path)) {
+              if (!Array.isArray(this.instance[i.path])) {
+                let transArray = this.instance[i.path].split(',')
+                i.value = source.filter(item => transArray.includes(item.code)).map(item => item.name).join(',')
+              }
+            }
+          }
+          source.forEach(j => {
+            if (j.code == this.instance[i.path]) {
+              i.value = j.name
+            }
+          })
+        }
+        // 动态信息点处理
+        switch (i.category) {
+          case "STATIC":
+            if (result[`${i.firstName}${i.secondName ? '/' + i.secondName : ''}`]) {
+              result[`${i.firstName}${i.secondName ? '/' + i.secondName : ''}`].paths.push({
+                Path: i.path,
+                InfoPointName: i.name,
+                InfoPointCode: i.code,
+                Value: i.value,
+                Visible: i.visible,
+                KeyWord: i.keyWord,
+                classCode: i.classCode,
+                id: i.id,
+                groupCode: i.groupCode,
+                type: i.type,
+                dataType: i.dataType,
+                dataSource: i.dataSource,
+              })
+            } else {
+              result[`${i.firstName}${i.secondName ? '/' + i.secondName : ''}`] = {
+                paths: [{
+                  Path: i.path,
+                  InfoPointName: i.name,
+                  InfoPointCode: i.code,
+                  Value: i.value,
+                  Visible: i.visible,
+                  KeyWord: i.keyWord,
+                  classCode: i.classCode,
+                  id: i.id,
+                  groupCode: i.groupCode,
+                  type: i.type,
+                  dataType: i.dataType,
+                  dataSource: i.dataSource,
+                }]
+              }
+            }
+            break
+          default:
+            if (resultEnergy[`${i.firstName}${i.secondName ? '/' + i.secondName : ''}`]) {
+              resultEnergy[`${i.firstName}${i.secondName ? '/' + i.secondName : ''}`].paths.push({
+                Path: i.path,
+                InfoPointName: i.name,
+                InfoPointCode: i.code,
+                value: i.svalue,
+                Visible: i.visible,
+                KeyWord: i.keyWord,
+                data: i.data,
+                receivetime: i.receivetime,
+                error: i.error,
+                Unit: i.unit,
+                FirstName: i.firstName,
+                dataType: i.dataType,
+                dataSource: i.dataSource,
+              })
+            } else {
+              resultEnergy[`${i.firstName}${i.secondName ? '/' + i.secondName : ''}`] = {
+                paths: [{
+                  Path: i.path,
+                  InfoPointName: i.name,
+                  InfoPointCode: i.code,
+                  value: i.svalue,
+                  Visible: i.visible,
+                  KeyWord: i.keyWord,
+                  data: i.data,
+                  receivetime: i.receivetime,
+                  error: i.error,
+                  Unit: i.unit,
+                  FirstName: i.firstName,
+                  dataType: i.dataType,
+                  dataSource: i.dataSource,
+                }]
+              }
+            }
+        }
+      })
+      this.exhibitionBaseInformation.information = result
+      this.exhibitionCrux.allMessage = result
+      this.exhibitionEnergy.list = resultEnergy;
+    },
+    goBack() {
+      this.$router.push({ name: "facilityLedger" })
+    },
+    // 切换实例
+    handleChangeExample() {
+      this.selectRadio.initRadio = '1';
+      this.currentRadio = '1';
+      this.getData();
+    },
+    // 切换上一个实例
+    handleClickPrev() {
+      let index = this.list.findIndex(item => item.value == this.id);
+      this.id = this.list[index - 1].value;
+      this.handleChangeExample();
+    },
+    // 切换下一个实例
+    handleClickNext() {
+      let index = this.list.findIndex(item => item.value == this.id);
+      this.id = this.list[index + 1].value;
+      this.handleChangeExample();
+    },
+  },
+  watch: {
+    projectId() {
+      this.$router.push({ path: "/ledger/facility" })
+    }
+  }
+}
+</script>
+
+<style scoped lang="less">
+@bgc: #fff;
+.tableDisplay {
+  .header {
+    padding-bottom: 10px;
+    overflow: hidden;
+    text-overflow: ellipsis;
+    white-space: nowrap;
+
+    .details-title {
+      color: #333;
+      font-size: 16px;
+      margin-left: 20px;
+      line-height: 32px;
+      cursor: pointer;
+    }
+  }
+
+  .content {
+    height: calc(100% - 77px);
+
+    .content-scrollbar {
+      display: flex;
+      height: 100%;
+      flex-direction: row;
+      box-sizing: border-box;
+      background-color: #f2f2f2;
+
+      .content-left {
+        display: flex;
+        flex-direction: column;
+        flex: 1;
+        box-sizing: border-box;
+
+        .content-keyPoint {
+          border: 1px solid #ccc;
+          box-sizing: border-box;
+          margin-bottom: 10px;
+          // overflow: hidden;
+          background: @bgc;
+        }
+
+        .content-showType {
+          height: 50px;
+          border: 1px solid #ccc;
+          border-bottom: none;
+          box-sizing: border-box;
+          background: @bgc;
+        }
+
+        .content-point {
+          display: flex;
+          flex-direction: row;
+          border: 1px solid #ccc;
+          box-sizing: border-box;
+          background: @bgc;
+
+          .content-point-left {
+            flex: 1;
+          }
+
+          .content-point-right {
+            width: 300px;
+            border-left: 1px solid #ccc;
+          }
+        }
+      }
+
+      .content-right {
+        width: 400px;
+        margin-left: 10px;
+        box-sizing: border-box;
+
+        .content-repair {
+          height: 50px;
+          line-height: 50px;
+          margin-bottom: 10px;
+          border: 1px solid #ccc;
+          background: @bgc;
+          box-sizing: border-box;
+        }
+
+        .content-media {
+          box-sizing: border-box;
+        }
+      }
+    }
+  }
+
+  .footer {
+    position: fixed;
+    bottom: 0;
+    left: 235px;
+    width: calc(100% - 235px);
+    height: 45px;
+    background-color: #fff;
+    box-sizing: border-box;
+  }
+}
+
+/deep/ .el-scrollbar__wrap {
+  overflow-x: hidden;
+}
+
+</style>

+ 296 - 0
src/components/ledger/facility/index.vue

@@ -0,0 +1,296 @@
+<template>
+    <div id="deviceList">
+        <saga-title :mess="mess"></saga-title>
+        <div class="table-main">
+            <div class="search-header">
+                <floor-cascader @change="changeFloor"></floor-cascader>
+                <my-cascader ref="cascader" @change="changeDevice"></my-cascader>
+                <el-tooltip class="item" effect="dark" content="按照本地名称 | 本地编码 | 图纸编码 | 交付编码关键字搜索" placement="top-start">
+                    <el-input
+                        v-show="param.deviceId"
+                        v-model="param.keyWord"
+                        @keyup.enter.native="handleChangeKeyWord"
+                        @clear="handleChangeKeyWord"
+                        clearable
+                        placeholder="请输入关键字"
+                        prefix-icon="el-icon-search"
+                        style="width: 250px; margin-left: 10px"
+                    ></el-input>
+                </el-tooltip>
+                <!-- <el-button size="small" @click="handleLinkAssets" class="iconfont icon-batchassociation">批量关联资产</el-button> -->
+                <!-- <el-tooltip class="item" effect="dark" content="请选择建筑楼层和设备类" placement="top-start"> -->
+                <el-dropdown @command="handleCommand" class="deviceList-excel">
+                    <el-button type="primary"> 数据导入<i class="el-icon-arrow-down el-icon--right"></i> </el-button>
+                    <el-dropdown-menu slot="dropdown">
+                        <el-dropdown-item command="dynamic">深化点表导入</el-dropdown-item>
+                        <el-dropdown-item command="static">台账信息导入</el-dropdown-item>
+                    </el-dropdown-menu>
+                </el-dropdown>
+                <!-- </el-tooltip> -->
+            </div>
+            <hanson-table @close="close" :graphyId="graphyId" ref="tableMain"></hanson-table>
+        </div>
+        <!-- <table-transfers></table-transfers> -->
+        <linkassets-dialog :dialog="dialog"></linkassets-dialog>
+        <!-- 深化点表导入 -->
+        <dynamic-dialog ref="dynamicDialog" @upDataDevice="upDataDevice"></dynamic-dialog>
+        <!-- 台账信息导入 -->
+        <static-dialog ref="staticDialog" @upDataDevice="upDataDevice"></static-dialog>
+    </div>
+</template>
+<script>
+import sagaTitle from "../components/ledger/lib/title";
+import floorCascader from "../components/ledger/lib/floorCascader";
+import myCascader from "../components/ledger/lib/equipCascader";
+import hansonTable from "../components/ledger/handsontables/device";
+import tableTransfers from "../components/ledger/tableTransfers";
+import linkassetsDialog from "../components/ledger/lib/linkassets";
+import { countEquip } from "../api/object/equip";
+import dynamicDialog from "../components/ledger/lib/dynamicDialog";
+import staticDialog from "../components/ledger/lib/staticDialog";
+import { mapGetters } from "vuex";
+
+export default {
+    components: {
+        sagaTitle,
+        floorCascader,
+        myCascader,
+        hansonTable,
+        tableTransfers,
+        linkassetsDialog,
+        staticDialog,
+        dynamicDialog,
+    },
+    data() {
+        return {
+            currentBuilding: "", // 当前建筑楼层信息
+            mess: {
+                perjectName: "",
+                name: [
+                    {
+                        key: "设备",
+                        num: 0,
+                    },
+                    {
+                        key: "资产",
+                        num: 0,
+                    },
+                ],
+            },
+            param: {
+                buildId: "",
+                floorId: "",
+                deviceId: "",
+                keyWord: "", //检索关键字
+                ProjId: "",
+                secret: "",
+            },
+            assetGroupList: [],
+            graphyId: null,
+            dialog: {
+                linkAssets: false,
+            },
+        };
+    },
+    computed: {
+        ...mapGetters("layout", ["projectId", "projects", "secret", "userId"]),
+    },
+    created() {
+        this.param.ProjId = this.projectId;
+        this.param.secret = this.secret;
+        this.getProjName();
+        this.getCount();
+        // let deviceId = this.$route.params.deviceId;
+        // if (deviceId) {
+        //     this.$nextTick(() => {
+        //         this.$refs.cascader.setValue(deviceId);
+        //     });
+        // }
+    },
+    activated() {
+        if (!this.$route.meta.isBack) {
+            this.param.ProjId = this.projectId;
+            this.param.secret = this.secret;
+            this.getProjName();
+            this.getCount();
+            let deviceId = this.$route.params.deviceId;
+            if (deviceId) {
+                this.$nextTick(() => {
+                    this.$refs.cascader.setValue(deviceId);
+                    this.changeDevice({ code: deviceId, facility: this.$route.params.facility });
+                });
+            } else {
+                this.$nextTick(() => {
+                    this.$refs.cascader.setValue("");
+                    this.changeDevice({ code: "", facility: "" });
+                });
+            }
+        }
+        this.$route.meta.isBack = false;
+    },
+    /**
+     * 路由守卫
+     * 从部件和详情页返回,设置页面缓存
+     */
+    beforeRouteEnter(to, from, next) {
+        const routerList = ["/ledger/parts", "/ledger/partsmanage", "/ledger/deviceDetails"]
+        if (routerList.includes(from.path)) {
+            to.meta.isBack = true;
+        } else {
+            to.meta.isBack = false;
+        }
+        next();
+    },
+    watch: {
+        projectId() {
+            this.param.ProjId = this.projectId;
+            this.param.secret = this.secret;
+            this.getProjName();
+            this.getCount();
+        },
+    },
+    methods: {
+        getProjName() {
+            this.projects.forEach((item) => {
+                if (item.id == this.projectId) this.mess.perjectName = item.name;
+            });
+            // this.getEqCode()
+        },
+        close(val) {
+            this.getCount();
+            // if (val && this.$refs.cascader) {
+            //     this.$refs.cascader.changeVal(val.code);
+            // }
+            // this.$refs.cascader.getData(this.currentBuilding);
+            this.$refs.tableMain.getTableData();
+        },
+        // getEqCode() {
+        //   getEquipmentFamily(res => {
+        //     debugger
+        //     this.assetGroupList = res.Content
+        //     this.getGraphyId()
+        //   })
+        // },
+        //获取统计数量
+        getCount() {
+            let param = {
+                filters: "not propertyId isnull",
+            };
+            countEquip({}, (res) => {
+                this.mess.name[0].num = res.count;
+            });
+            countEquip(param, (res) => {
+                this.mess.name[1].num = res.count;
+            });
+        },
+        // getGraphyId() {
+        //   let param = {
+        //     ProjId: this.param.ProjId,
+        //     secret: this.param.secret,
+        //     type: "SystemEquip"
+        //   }
+        //   getGraphyId(param).then(res => {
+        //     if (res.data.Result == "success") {
+        //       debugger
+        //       this.graphyId = res.data.graph_id
+        //       if (this.$refs.tableMain)
+        //         this.$refs.tableMain.setGraphyId(this.graphyId, this.assetGroupList);
+        //     } else {
+        //       this.$message.error("请求错误:" + res.data.ResultMsg)
+        //     }
+        //   }).catch(_ => {
+        //     this.$message.error("请求错误")
+        //   })
+        // },
+        //修改楼层
+        changeFloor(value) {
+            if (value[0]) {
+                this.param.buildId = value[0];
+            }
+            if (value[1]) {
+                this.param.floorId = value[1];
+            } else {
+                this.param.floorId = null;
+            }
+            if (!!this.param.deviceId) {
+                if (this.$refs.tableMain) this.$refs.tableMain.getHeaderData(this.param);
+            }
+            this.currentBuilding = value;
+            this.$refs.cascader.getData(value);
+        },
+
+        //修改设备族
+        changeDevice(value) {
+            this.param.deviceId = value.code || "";
+            this.param.name = value.facility || "";
+            this.param.keyWord = "";
+            if (this.$refs.tableMain) this.$refs.tableMain.getHeaderData(this.param);
+        },
+        // 修改检索关键字
+        handleChangeKeyWord() {
+            if (this.$refs.tableMain) this.$refs.tableMain.getTableData(this.param.keyWord);
+        },
+        // 刷新数据
+        upDataDevice() {
+            this.$refs.tableMain.getTableData();
+        },
+        // 批量关联资产
+        handleLinkAssets() {
+            this.dialog.linkAssets = true;
+        },
+        /**
+         * 选择下拉菜单
+         * @param command   下拉选项标识
+         */
+        handleCommand(command) {
+            // 选择深化点表导入
+            if (command === "dynamic") {
+                this.$refs.dynamicDialog.openDialog();
+            } else if (command === "static") {
+                // 选择台账信息导入
+                this.$refs.staticDialog.openDialog();
+            }
+        },
+    },
+};
+</script>
+<style lang="less" scoped>
+#deviceList {
+    overflow: hidden;
+    height: 100%;
+    background-color: #fff;
+    position: relative;
+
+    .table-main {
+        position: absolute;
+        top: 87px;
+        bottom: 0;
+        left: 0;
+        right: 0;
+        background-color: #fff;
+        // border-radius: 5px;
+        // padding: 10px 0;
+    }
+
+    .search-header {
+        overflow: hidden;
+        padding-bottom: 10px;
+        margin: 0 10px;
+        border-bottom: 1px solid #bcbcbc;
+
+        .deviceList-excel {
+            float: right;
+            margin-right: 15px;
+        }
+
+        .icon-batchassociation {
+            float: right;
+            font-size: 12px;
+
+            /deep/ span {
+                margin-left: 5px;
+            }
+        }
+    }
+}
+</style>

+ 506 - 0
src/components/ledger/facility/parts/index.vue

@@ -0,0 +1,506 @@
+<template>
+    <div id="partsList">
+        <el-row>
+            <el-tabs @tab-click="getTableHeader" v-model="tabVal">
+                <el-tab-pane
+                    v-for="t in partsTypeList"
+                    :key="t.category"
+                    :name="t.category"
+                    :label="`${t.categoryName}(${t.count ? t.count : 0})`"
+                ></el-tab-pane>
+            </el-tabs>
+        </el-row>
+        <el-row class="left">
+            <el-button size="small" type="default" icon="el-icon-back" @click="goback" style="margin-right: 10px"></el-button>
+            <el-select v-model="showType" @change="initTable" style="width: 100px; margin-right: 10px; vertical-align: bottom">
+                <el-option v-for="item in showTypes" :key="item.value" :label="item.label" :value="item.value"> </el-option>
+            </el-select>
+            <el-button @click="getTableHeader">刷新</el-button>
+        </el-row>
+        <div class="tableBox">
+            <div class="center middle_sty" v-show="tableData && !tableData.length" style="flex: 2">
+                <p>
+                    <i class="icon-wushuju iconfont"></i>
+                    暂无数据
+                </p>
+            </div>
+            <div class="tableLeft" v-show="tableData && tableData.length">
+                <handson-table ref="table"></handson-table>
+            </div>
+        </div>
+        <el-pagination
+            class="right"
+            v-show="tableData && tableData.length"
+            @size-change="handleSizeChange"
+            @current-change="handleCurrentChange"
+            :current-page="page.pageNumber"
+            :page-sizes="page.pageSizes"
+            :page-size="page.pageSize"
+            layout="total, sizes, prev, pager, next, jumper"
+            :total="page.total"
+        ></el-pagination>
+        <!-- 二维码弹窗 -->
+        <qrcode :dialog="myDialog" :qrcodeUrl="qrcodeUrl" :addBody="true" ref="qrcode"></qrcode>
+        <upload-files-dialog :read="onlyRead ? true : false" ref="upload" @changeFile="fileChange" :keysArr="filesArr" :dialog="myDialog">
+        </upload-files-dialog>
+        <upload-img-dialog :read="onlyRead ? true : false" @changeFile="imgChange" :keysArr="imgsArr" :dialog="myDialog"></upload-img-dialog>
+        <pic-dialog :read="onlyRead ? true : false" :dialog="myDialog" :keysArr="picsArr" @change="changePics"></pic-dialog>
+    </div>
+</template>
+<script>
+import tools from "@/utils/scan/tools";
+import showTools from "@/utils/handsontable/notShow";
+
+import qrcode from "@/components/ledger/lib/qrcode";
+import uploadFilesDialog from "@/components/dialogs/list/filesDialog";
+import uploadImgDialog from "@/components/dialogs/list/uploadImgDialog";
+import picDialog from "@/components/dialogs/list/picDialog";
+import systemType from "@/components/dialogs/list/systemType";
+import handsonTable from "@/components/common/handsontable";
+import { BeatchQueryParam, deleteParts, getEquipPartsCon, getEquipPartsType } from "@/api/scan/request";
+import { getDataDictionary } from "@/api/dict";
+import { mapGetters } from "vuex";
+
+export default {
+    components: {
+        qrcode, //二维码页面
+        uploadFilesDialog,
+        uploadImgDialog,
+        picDialog,
+        systemType,
+        handsonTable,
+    },
+    data() {
+        return {
+            deviceId: "", //设备id
+            isTableRightShow: false,
+            spaceList: "",
+            onlyRead: true,
+            tableHeader: [],
+            page: {
+                pageSize: 50,
+                pageSizes: [10, 20, 50, 100],
+                pageNumber: 1,
+                total: 0,
+            },
+            showTypes: [
+                { value: "Visible", label: "只看采集信息" },
+                { value: "all", label: "全部" },
+            ],
+            showType: "Visible",
+            tableData: [],
+            copyTableData: [],
+            tableExample: null,
+            tabVal: "",
+            partsTypeList: [], //tabs
+            myDialog: {
+                qrcode: false, //二维码弹窗
+                uploadFiles: false, //上传文件
+                uploadImgs: false, //上传单个图片
+                pic: false, //多个图片
+                addDevice: false,
+                systemType: false,
+                details: false, //详情页
+                changeRea: false,
+                lookPic: false, //图片查看
+            },
+            inputMap: {},
+            qrcodeUrl: "", //二维码图片地址
+            filesArr: [], //保存临时的文件key
+            imgsArr: [], //临时保存的图片key数组
+            picsArr: [], //临时设备图片keys数组
+            systemId: null,
+            graphyId: null,
+            mess: {},
+            id: 0,
+            EquipmentList: [],
+            firmId: "", //品牌型号所需字段
+            queryParam: {}, //返回到指定位置
+        };
+    },
+    computed: {
+        ...mapGetters("layout", ["projectId", "secret", "userId", "group_code"]),
+    },
+    created() {
+        this.queryParam = this.$route.query;
+        this.deviceId = this.$route.query.deviceId;
+        this.getTypes();
+    },
+    methods: {
+        //获取当前设备下的部件类
+        async getTypes() {
+            let params = {
+                filters: `id='${this.deviceId}'`,
+                pageNumber: 1,
+                pageSize: 50,
+            };
+            await getEquipPartsType(params, (res) => {
+                this.partsTypeList = res.content;
+                if (this.partsTypeList.length) {
+                    this.tabVal = this.partsTypeList[0].category;
+                    this.getTableHeader();
+                }
+            });
+        },
+        // 获取表头数据(初始化表格)
+        async getTableHeader() {
+            let params = {
+                orders: "sort asc, name desc",
+                pageNumber: 1,
+                pageSize: 1000,
+                type: this.tabVal,
+            };
+            await getDataDictionary(params, (res) => {
+                this.tableHeader = res.content;
+                this.codeToDataSource = {};
+                this.tableHeader.forEach((item) => {
+                    if (item.path) {
+                        this.inputMap[item.path] = item;
+                    }
+                    if (item.dataSource) {
+                        try {
+                            this.codeToDataSource[item.code] = {};
+                            item.dataSource.forEach((dic) => {
+                                this.codeToDataSource[item.code][dic.code] = dic.name;
+                            });
+                        } catch (e) {
+                            console.log(e);
+                        }
+                    }
+                });
+                this.getTableData();
+            });
+        },
+        //获取表格数据
+        async getTableData() {
+            let params = {
+                pageNumber: this.page.pageNumber,
+                pageSize: this.page.PageSize,
+                orders: "createTime desc, localName desc, localId desc, id desc",
+                filters: `classCode='${this.tabVal}';parentId='${this.deviceId}'`,
+            };
+            await getEquipPartsCon(params, (res) => {
+                this.tableData = res.content;
+                this.copyTableData = tools.deepCopy(res.content);
+                this.page.pageNumber = res.pageNumber;
+                this.page.total = res.total;
+                if (this.tableData && this.tableData.length) {
+                    this.getBatch(res.content);
+                    this.initTable();
+                }
+            });
+        },
+        //切换每页显示多少条数据
+        handleSizeChange(val) {
+            this.page.pageSize = val;
+            this.getTableHeader();
+        },
+        //切换页数
+        handleCurrentChange(val) {
+            this.page.pageNumber = val;
+            this.getTableHeader();
+        },
+        formatHeaderData(list) {
+            //格式化表头显示的数据
+            let arr = tools.copyArr(list);
+            let data = showTools.headerTextFilter(arr, "parts", this.onlyRead, this.showType, true);
+            // data.unshift("查看详情", "当前关联的资产");
+            data.unshift("查看详情");
+            return data;
+        },
+        formatHeaderType(list) {
+            //格式化表头头映射的数据
+            let arr = tools.copyArr(list);
+            let data = showTools.headerTypeFilter(arr, "parts", this.onlyRead, this.showType, true);
+            data.unshift(
+                {
+                  data: "caozuo",
+                  renderer: tools.lookDetails,
+                  readOnly: true
+                },
+                // {
+                //     data: "PropertyLocalName",
+                //     readOnly: true,
+                // }
+            );
+            return data;
+        },
+        initTable() {
+            //实例化表格
+            let settings = {
+                data: this.tableData,
+                colHeaders: this.formatHeaderData(this.tableHeader),
+                columns: this.formatHeaderType(this.tableHeader),
+                rowHeights: 30,
+                fillHandle: "vertical", //允许纵向填充
+                maxRows: this.tableData.length,
+                contextMenu: this.onlyRead
+                    ? false
+                    : {
+                          items: {
+                              remove_row: {
+                                  name: "删除部件",
+                              },
+                          },
+                      },
+                // 事件
+                afterRemoveRow: this.handleDeleteTableRow, //右键删除
+                afterOnCellMouseDown: this.handleTdClick, //鼠标点击
+            };
+            this.$nextTick(() => {
+                this.tableExample = this.$refs.table.init(settings);
+            });
+        },
+        //表格点击事件
+        handleTdClick(el, rowArr) {
+            //点击的是表头
+            if (rowArr.row < 0) {
+                return;
+            }
+            //被筛选过后的数组
+            let trimmedArr = this.trimmedRows();
+            //是否启用了排序
+            let isSort = this.tableExample.getPlugin("columnSorting").isSorted();
+            if (trimmedArr.length && isSort) {
+                let sortArr = this.myHotArr.getPlugin("columnSorting").rowsMapper.__arrayMap;
+                let infos = this.tableData[trimmedArr[sortArr[rowArr.row]]];
+                this.getInfors(infos, { row: sortArr[rowArr.row], col: rowArr.col });
+            } else if (isSort) {
+                //排序后的数组
+                let sortArr = this.tableExample.getPlugin("columnSorting").rowsMapper.__arrayMap;
+                let infos = this.tableData[sortArr[rowArr.row]];
+                this.getInfors(infos, { row: sortArr[rowArr.row], col: rowArr.col });
+            } else if (trimmedArr.length) {
+                let infos = this.tableData[trimmedArr[rowArr.row]];
+                this.getInfors(infos, { row: trimmedArr[rowArr.row], col: rowArr.col });
+            } else {
+                let infos = this.tableData[rowArr.row];
+                this.getInfors(infos, rowArr);
+            }
+        },
+        //获取被筛选掉的行号
+        trimmedRows() {
+            var plugin = this.tableExample.getPlugin("trimRows").trimmedRows;
+            let dataLength = this.tableData.length;
+            let dataArr = new Array();
+            for (let i = 0; i < dataLength; i++) {
+                dataArr.push(i);
+            }
+            if (plugin.length <= 0) {
+                dataArr = undefined;
+            } else {
+                dataArr = this.array_diff(dataArr, plugin);
+            }
+            return dataArr || [];
+        },
+        getInfors(infos, row) {
+            //其他的开始判断
+            let val = this.tableExample.colToProp(row.col);
+            if (val == "caozuo") {
+                // window.open(`http://adm.sagacloud.cn:8058/spread?id=${infos.EquipID}&pid=${this.projectId}&secret=${this.secret}`,"_blank")
+                this.$message("开发中...");
+            }
+            //设备二维码图片
+            if (val == "defaultQRCode") {
+                this.qrcodeUrl = this.tableData[row.row].defaultQRCode;
+                if (!!this.qrcodeUrl) {
+                    this.myDialog.qrcode = true;
+                } else {
+                    this.$message("此设备没有设备二维码");
+                }
+            }
+            //关联系统
+            if (val == "linkSystem") {
+                // this.systemList = this.tableData[row.row].linkSystem || [];
+                this.myDialog.systemType = true;
+            }
+            //保险文件--设备文档--安装质检报告
+            if (
+                val == "infos.insuranceFile" || //保险文件
+                val == "infos.archive" || //设备文档
+                val == "infos.checkReport" || //安装质检报告
+                val == "infos.maintainManual" || //维修保养手册
+                val == "infos.approachingAcceptance" || //进场验收单
+                val == "infos.acceptanceReport" || //验收报告
+                val == "infos.operationManual" || //操作说明书
+                val == "infos.originalCertificate" || //原厂证明
+                val == "infos.testReport" || //检测报告
+                val == "infos.productCertification" || //产品合格证
+                val == "infos.installInstruction" || //
+                val == "infos.supplierContract" || //供应合同
+                val == "infos.drawing" || //设备图纸
+                val == "infos.installDrawing" //安装图纸
+            ) {
+                let data = tools.dataForKey(this.tableData[row.row], val);
+                this.filesArr = data ? data : [];
+                this.myDialog.uploadFiles = true;
+            }
+            //安装照片--设备铭牌照片
+            if (val == "infos.installPic" || val == "infos.nameplate") {
+                let data = tools.dataForKey(this.tableData[row.row], val);
+                this.imgsArr = data ? data : [];
+                this.myDialog.uploadImgs = true;
+            }
+            //设备照片
+            if (val == "infos.pic") {
+                let data = tools.dataForKey(this.tableData[row.row], val);
+                this.picsArr = data ? data : [];
+                this.myDialog.pic = true;
+            }
+            this.row = row.row;
+            this.messKey = val;
+        },
+        //去除数组中相同的元素
+        array_diff(a, b) {
+            for (var i = 0; i < b.length; i++) {
+                for (var j = 0; j < a.length; j++) {
+                    if (a[j] == b[i]) {
+                        a.splice(j, 1);
+                        j = j - 1;
+                    }
+                }
+            }
+            return a;
+        },
+        // 删除表格行
+        handleDeleteTableRow() {
+            let params = tools.differenceArr(this.tableData, this.copyTableData);
+            if (params.length < 1 || this.tableData > this.copyTableData) {
+                return;
+            }
+            let param = {
+                projectId: this.projectId,
+                data: [],
+            };
+            params.map((item) => {
+                param.data.push({ EquipID: item.EquipID });
+            });
+            this.$confirm("此操作将删除部件,是否继续?", "提示", {
+                confirmButtonText: "确定",
+                cancelButtonText: "取消",
+                type: "warning",
+            })
+                .then(() => {
+                    this.deleteTableData(param);
+                })
+                .catch(() => {
+                    this.getTableHeader();
+                    this.$message("取消删除");
+                });
+        },
+        // 删除表格数据
+        async deleteTableData(params) {
+            await deleteParts(params, (res) => {
+                this.$message.success("删除成功!");
+                this.getTypes();
+            });
+        },
+        //上传文件弹窗触发事件
+        fileChange(keys) {
+            this.setDataToMain(keys, this.messKey, this.row);
+        },
+        //上传图片弹窗触发事件
+        imgChange(keys) {
+            this.setDataToMain(keys, this.messKey, this.row);
+        },
+        //设备图片弹窗改变事件
+        changePics(keys) {
+            this.setDataToMain(keys, this.messKey, this.row);
+        },
+        //获取动态参数
+        getBatch(data) {
+            let param = {
+                groupCode: this.group_code,
+                appId: "datacenter",
+                projectId: this.projectId,
+                data: [],
+            };
+            this.tableHeader.map((head) => {
+                if (head.category != "STATIC") {
+                    data.map((item) => {
+                        let cur = tools.dataForKey(item, head.path);
+                        if (cur) {
+                            param.data.push({
+                                objectId: item.id,
+                                infoCode: head.code,
+                            });
+                        }
+                    });
+                }
+            });
+            if (param.data.length) {
+                BeatchQueryParam(param, (res) => {
+                    this.tableData = data.map((item) => {
+                        res.data.map((child) => {
+                            if (item.id == child.objectId) {
+                                if (child.data || child.data == 0) {
+                                    this.tableHeader.map((head) => {
+                                        if (head.code == child.infoCode) {
+                                            let contentVal = child.data;
+                                            if (this.codeToDataSource[child.infoCode]) {
+                                                contentVal = this.codeToDataSource[child.infoCode][child.data];
+                                            }
+                                            tools.setDataForKey(item, head.path, contentVal);
+                                        }
+                                    });
+                                } else {
+                                    this.tableHeader.map((head) => {
+                                        if (head.code == child.infoCode) {
+                                            tools.setDataForKey(
+                                                item,
+                                                head.path,
+                                                child.error
+                                                // child.error ? child.value ? "表号功能号格式错误" : "表号功能号不存在" : "暂未采集到实时数据"
+                                            );
+                                        }
+                                    });
+                                }
+                            }
+                        });
+                        return item;
+                    });
+                    this.tableExample.loadData(this.tableData);
+                });
+            }
+        },
+        // 返回
+        goback() {
+            this.$router.push({ path: "/ledger/facility"});
+        },
+    },
+};
+</script>
+<style lang="less" scoped>
+#partsList {
+    overflow: hidden;
+    height: 100%;
+    background-color: #fff;
+    padding: 10px;
+    position: relative;
+
+    .right {
+        background: #fff;
+    }
+
+    .search-header {
+        overflow: hidden;
+        padding: 0 10px 10px 10px;
+        border-bottom: 1px solid #bcbcbc;
+    }
+
+    .tableBox {
+        display: flex;
+        height: calc(100% - 150px);
+        margin-top: 10px;
+
+        .tableLeft {
+            flex: 1;
+        }
+    }
+}
+
+.el-pagination button,
+.el-pagination span:not([class*="suffix"]) {
+    vertical-align: middle;
+}
+</style>

+ 587 - 0
src/components/ledger/facility/partsmanage/addparts/index.vue

@@ -0,0 +1,587 @@
+<template>
+    <div id="deviceList">
+        <el-row class="left">
+            <span style="float: left">当前选择的部件类型:{{ category.name }}</span>
+            <!-- <div style="width:200px;display:inline-block;text-align:left;color:gray;">
+        <span>隐藏自动填充的信息</span>
+        <el-checkbox v-model="isWatch" @change="getTableHeader"></el-checkbox>
+      </div> -->
+            <span style="float: right">
+                <el-select v-model="showType" @change="initTable" style="width: 100px; margin-right: 10px; vertical-align: bottom">
+                    <el-option v-for="item in showTypes" :key="item.value" :label="item.label" :value="item.value"> </el-option>
+                </el-select>
+                <span>增加</span>
+                <el-input-number v-model="addNum" :controls="false" style="width: 50px" :min="1" :max="50"></el-input-number>
+                <span>个{{ category.name }}</span>
+                <el-button @click="handleAddTableRow">增加</el-button>
+            </span>
+        </el-row>
+        <div class="tableBox">
+            <div class="center middle_sty" style="flex: 2" v-show="tableData && !tableData.length">
+                <p>
+                    <i class="icon-wushuju iconfont"></i>
+                    暂无数据
+                </p>
+            </div>
+            <div class="tableLeft" v-show="tableData && tableData.length">
+                <handson-table ref="table"></handson-table>
+            </div>
+        </div>
+        <el-row class="center">
+            <el-button type="primary" size="medium" @click="handleCreateTableData" class="create_button" :disabled="createDisable"
+                >创建部件</el-button
+            >
+        </el-row>
+        <!-- 二维码弹窗 -->
+        <qrcode :dialog="myDialog" :qrcodeUrl="qrcodeUrl" :addBody="true" ref="qrcode"></qrcode>
+        <upload-files-dialog :read="onlyRead ? true : false" ref="upload" @changeFile="fileChange" :keysArr="filesArr" :dialog="myDialog">
+        </upload-files-dialog>
+        <upload-img-dialog :read="onlyRead ? true : false" @changeFile="imgChange" :keysArr="imgsArr" :dialog="myDialog"></upload-img-dialog>
+        <pic-dialog :read="onlyRead ? true : false" :dialog="myDialog" :keysArr="picsArr" @change="changePics"></pic-dialog>
+        <details-dialog :iframeSrc="iframeSrc" v-if="myDialog.details" :dialog="myDialog"></details-dialog>
+        <!-- 多选枚举类型维护 -->
+        <menum :dialog="myDialog" :updateInfoData="updateInfoData" :updateInput="updateInput" @change="handleChangeMenum"></menum>
+        <!-- 不支持的输入方式 -->
+        <el-dialog title="临时维护信息点" :visible.sync="myDialog.update" @close="handleCloseUpdate" width="670px">
+            <el-row>
+                该信息点未定义对应组件,现在维护数据不确定后续是否可用。如确实需要维护,请点击
+                <el-link @click="updateInputShow = true" type="primary" :underline="false">继续维护</el-link>
+                。
+            </el-row>
+            <el-row style="margin-top: 20px" v-show="updateInputShow">
+                <el-input type="textarea" :autosize="{ minRows: 4, maxRows: 8 }" placeholder="请输入内容" v-model="updateInput"></el-input>
+            </el-row>
+            <span slot="footer" class="dialog-footer">
+                <el-button @click="myDialog.update = false">取 消</el-button>
+                <el-button type="primary" @click="handleClickUpdate">确 认</el-button>
+            </span>
+        </el-dialog>
+    </div>
+</template>
+<script>
+import tools from "@/utils/scan/tools";
+import handsonUtils from "@/utils/hasontableUtils";
+import showTools from "@/utils/handsontable/notShow";
+import text from "@/utils/handsontable/mainText";
+import session from "@/framework/utils/storage";
+
+import handsonTable from "@/components/common/handsontable";
+import {
+    // getDataDictionary,
+    createPart,
+    createPropertyData,
+    getEquipBelongs,
+} from "@/api/scan/request";
+import { getDataDictionary } from "@/api/dict";
+
+import { mapGetters, mapActions } from "vuex";
+
+import qrcode from "@/components/ledger/lib/qrcode";
+import uploadFilesDialog from "@/components/dialogs/list/filesDialog";
+import uploadImgDialog from "@/components/dialogs/list/uploadImgDialog";
+import picDialog from "@/components/dialogs/list/picDialog";
+import menum from "@/components/dialogs/list/menum";
+
+export default {
+    components: {
+        qrcode, //二维码页面
+        uploadFilesDialog,
+        uploadImgDialog,
+        picDialog,
+        handsonTable,
+        menum,
+    },
+    data() {
+        return {
+            addNum: 1,
+            onlyRead: false,
+            tableHeader: [],
+            // tableData: session.get("partsAddData")
+            //     ? session.get("partsAddData").length
+            //         ? session.get("partsAddData")
+            //         : [{ Checked: 1 }]
+            //     : [{ Checked: 1 }],
+            tableData: session.get("partsAddData")
+                ? session.get("partsAddData").length
+                    ? session.get("partsAddData")
+                    : [{}]
+                : [{}],
+            showTypes: [
+                // { value: "partInfo", label: '隐藏信息点' },
+                { value: "Visible", label: "只看采集信息" },
+                { value: "all", label: "全部" },
+            ],
+            showType: this.$route.query.showType,
+            copyTableData: [],
+            category: "", //当前设备类
+            myDialog: {
+                qrcode: false, //二维码弹窗
+                uploadFiles: false, //上传文件
+                uploadImgs: false, //上传单个图片
+                pic: false, //多个图片
+                addDevice: false,
+                systemType: false,
+                details: false, //详情页
+                changeRea: false,
+                lookPic: false, //图片查看
+                update: false, //临时维护信息点
+                menum: false, //临时多选枚举类型信息点
+            },
+            inputMap: {
+                flowBuild: {
+                    editable: true,
+                    code: "flowBuild",
+                    name: "建筑楼层",
+                    path: "flowBuild",
+                    category: "STATIC",
+                    dataType: "ENUM",
+                },
+            }, //信息点和输入方式映射表
+            updateInputShow: false, //是否显示临时维护输入框
+            updateInfoData: {}, //当前信息点信息
+            updateInfoPoint: "", //临时维护信息点
+            updateInput: "", //临时维护信息点值
+            qrcodeUrl: "", //二维码图片地址
+            filesArr: [], //保存临时的文件key
+            imgsArr: [], //临时保存的图片key数组
+            picsArr: [], //临时设备图片keys数组
+            systemId: null,
+            graphyId: null,
+            id: 0,
+            EquipmentList: [],
+            firmId: "", //品牌型号所需字段--族id三位编码(传设备类id或部件类id)
+            createDisable: false,
+        };
+    },
+    computed: {
+        ...mapGetters("layout", ["projectId", "secret", "userId"]),
+    },
+    created() {
+        this.category = this.$route.query;
+        this.category.parentId = this.category.code.substr(0, this.category.code.length - 2);
+        this.getBelongs();
+        this.getTableHeader();
+    },
+    methods: {
+        //查询部件类对应的 族
+        getBelongs() {
+            let params = {
+                data: {
+                    filters: `equipCode='${this.category.code}'`,
+                    pageNumber: 1,
+                    pageSize: 50,
+                },
+            };
+            getEquipBelongs(params, (res) => {
+                if (res.content.length) {
+                    this.firmId = res.content[0].family;
+                }
+            });
+        },
+        // 获取表头数据(初始化表格)
+        async getTableHeader() {
+            let params = {
+                orders: "sort asc, name desc",
+                pageNumber: 1,
+                pageSize: 1000,
+                type: this.category.code,
+                // Filters: `type='Equipment' or type='${this.category.Code}'`
+            };
+            await getDataDictionary(params, (res) => {
+                this.tableHeader = res.content;
+                this.tableHeader.forEach((item) => {
+                    if (item.path) {
+                        this.inputMap[item.path] = item;
+                    }
+                });
+                this.initTable();
+            });
+        },
+        // 创建部件数据
+        async handleCreateTableData() {
+            let newData = this.tableData.filter((item) => {
+                let keys = Object.keys(item);
+                keys.map((key) => {
+                    //将值为空字符串的属性删除
+                    if (item[key] == "") {
+                        delete item[key];
+                    }
+                });
+                let newK = Object.keys(item);
+                if ((item.Checked && newK.length > 1) || (!item.Checked && newK.length)) {
+                    return item;
+                }
+            });
+            if (!newData.length) {
+                this.$message("创建信息为空,请录入信息后再创建!");
+                return;
+            }
+            let flag = false;
+            newData.map((item) => {
+                if (!item.localName || !item.localId) {
+                    flag = true;
+                }
+            });
+            if (flag) {
+                this.$message.info("存在设备的本地名称或本地编码为空,请检查");
+                return;
+            }
+
+            for (let i = 0; i < newData.length; i++) {
+                newData[i].parentId = this.category.deviceId; //当前设备id
+                newData[i].classCode = this.category.code; //当前部件分类
+                newData[i].family = this.firmId;
+                //待接口修改为关联创建
+                let param = {
+                    cascade: [],
+                    content: [newData[i]],
+                };
+                this.createDisable = true;
+                await createPart(param, async (res) => {
+                    // if (newData[i].Checked) {
+                    //     //同时创建资产
+                    //     param.content[0].equipmentId = res.entityList[0].id;
+                    //     await createPropertyData(param, (res) => {
+                    //         // todo 创建资产
+                    //     });
+                    // }
+                    if (i == newData.length - 1) {
+                        this.createDisable = false;
+                        this.$message.success("创建成功");
+                        session.remove("partsAddData");
+                        this.$router.push({
+                            path: "/ledger/partsmanage",
+                            query: {
+                                deviceId: this.category.deviceId,
+                                typeId: this.category.parentId,
+                                pageNo: this.category.pageNo,
+                                pageSize: this.category.pageSize,
+                            },
+                        });
+                    }
+                });
+            }
+        },
+        // 删除表格行
+        handleDeleteTableRow(a, b, c, d) {
+            if (d && d === "ContextMenu.removeRow") {
+                this.$message.success("删除成功");
+                this.formaTableData();
+            }
+        },
+        // 添加行
+        handleAddTableRow() {
+            let addRowLength = this.addNum;
+            for (let i = 0; i < addRowLength; i++) {
+                // this.tableData.push({ Checked: 1 });
+                this.tableData.push({});
+            }
+            this.initTable();
+            this.formaTableData();
+        },
+        //修改
+        handleUpdataTable(changeData, source) {
+            if (!this.onlyRead && source != "ObserveChanges.change") {
+                this.formaTableData();
+            }
+        },
+        //保存去掉空字段的新增数据
+        formaTableData() {
+            let newData = this.tableData.filter((item) => {
+                let keys = Object.keys(item);
+                keys.map((key) => {
+                    //将值为空字符串的属性删除
+                    if (item[key] == "") {
+                        delete item[key];
+                    }
+                });
+                if (keys.length && Object.keys(item).length) {
+                    return item;
+                }
+            });
+            session.set("partsAddData", newData);
+        },
+        //格式化表头显示的数据
+        formatHeaderData(list) {
+            let arr = tools.copyArr(list);
+            let data = showTools.headerTextFilter(arr, "parts", this.onlyRead, this.showType, true);
+            // data.unshift("同时创建资产");
+            return data;
+        },
+        formatHeaderType(list) {
+            //格式化表头头映射的数据
+            let arr = tools.copyArr(list);
+            let data = showTools.headerTypeFilter(arr, "parts", this.onlyRead, this.showType, true);
+            // data.unshift({
+            //     type: "checkbox",
+            //     checkedTemplate: 1,
+            //     uncheckedTemplate: 0,
+            //     data: "Checked",
+            //     label: {
+            //         position: "after",
+            //     },
+            // });
+            return data;
+        },
+        initTable() {
+            //实例化表格
+            let settings = {
+                data: this.tableData,
+                colHeaders: this.formatHeaderData(this.tableHeader),
+                columns: this.formatHeaderType(this.tableHeader),
+                rowHeights: 30,
+                fillHandle: "vertical", //允许纵向填充
+                maxRows: this.tableData.length,
+                contextMenu: this.onlyRead
+                    ? false
+                    : {
+                          items: {
+                              remove_row: {
+                                  name: "删除部件",
+                              },
+                          },
+                      },
+                // 事件
+                afterChange: this.handleUpdataTable, //修改后
+                afterFilter: this.trimmedRows, //排序前
+                afterRemoveRow: this.handleDeleteTableRow, //右键删除
+                afterOnCellMouseDown: this.handleTdClick, //鼠标点击
+            };
+            this.$nextTick(() => {
+                this.tableExample = this.$refs.table.init(settings);
+            });
+        },
+        //去除数组中相同的元素
+        array_diff(a, b) {
+            for (var i = 0; i < b.length; i++) {
+                for (var j = 0; j < a.length; j++) {
+                    if (a[j] == b[i]) {
+                        a.splice(j, 1);
+                        j = j - 1;
+                    }
+                }
+            }
+            return a;
+        },
+        //上传文件弹窗触发事件
+        fileChange(keys) {
+            this.setDataToMain(keys, this.messKey, this.row);
+        },
+        //上传图片弹窗触发事件
+        imgChange(keys) {
+            this.setDataToMain(keys, this.messKey, this.row);
+        },
+        //设备图片弹窗改变事件
+        changePics(keys) {
+            this.setDataToMain(keys, this.messKey, this.row);
+        },
+        //表格点击事件
+        handleTdClick(el, rowArr) {
+            //点击的是表头
+            if (rowArr.row < 0) {
+                return;
+            }
+            //被筛选过后的数组
+            let trimmedArr = this.trimmedRows();
+            //是否启用了排序
+            let isSort = this.tableExample.getPlugin("columnSorting").isSorted();
+            if (trimmedArr.length && isSort) {
+                let sortArr = this.myHotArr.getPlugin("columnSorting").rowsMapper.__arrayMap;
+                let infos = this.tableData[trimmedArr[sortArr[rowArr.row]]];
+                this.getInfors(infos, { row: sortArr[rowArr.row], col: rowArr.col });
+            } else if (isSort) {
+                //排序后的数组
+                let sortArr = this.tableExample.getPlugin("columnSorting").rowsMapper.__arrayMap;
+                let infos = this.tableData[sortArr[rowArr.row]];
+                this.getInfors(infos, { row: sortArr[rowArr.row], col: rowArr.col });
+            } else if (trimmedArr.length) {
+                let infos = this.tableData[trimmedArr[rowArr.row]];
+                this.getInfors(infos, { row: trimmedArr[rowArr.row], col: rowArr.col });
+            } else {
+                let infos = this.tableData[rowArr.row];
+                this.getInfors(infos, rowArr);
+            }
+        },
+        //获取被筛选掉的行号
+        trimmedRows() {
+            var plugin = this.tableExample.getPlugin("trimRows").trimmedRows;
+            let dataLength = this.tableData.length;
+            let dataArr = new Array();
+            for (let i = 0; i < dataLength; i++) {
+                dataArr.push(i);
+            }
+            if (plugin.length <= 0) {
+                dataArr = undefined;
+            } else {
+                dataArr = this.array_diff(dataArr, plugin);
+            }
+            return dataArr || [];
+        },
+        getInfors(infos, row) {
+            //其他的开始判断
+            let val = this.tableExample.colToProp(row.col);
+            let inputData = this.inputMap[val];
+            this.row = row.row;
+            this.messKey = val;
+            //设备二维码图片
+            if (val == "defaultQRCode") {
+                this.myDialog.qrcode = true;
+                this.$refs.qrcode.getCanvas(1111);
+            }
+            //   if (
+            //     val == "flowBuild" || //所属建筑楼层
+            //     val == "infos.productDate" || //生产日期
+            //     val == "infos.serialNum" || //出厂编号
+            //     val == "infos.assetID" || //资产编号
+            //     val == "infos.purchasePrice" || //采购价格
+            //     val == "infos.insuranceNum" || //保险单号
+            //     val == "infos.insuranceFile" //保险文件
+            //   ) {
+            //     const propertyId = this.tableData[row.row].propertyId;
+            //     if (!propertyId) { //未关联资产
+            //         this.$confirm('您暂未关联设备对应的资产,该信息点不可编辑', '提示', {
+            //             confirmButtonText: '我知道了',
+            //             showCancelButton: false,
+            //             type: 'warning',
+            //             center: true
+            //         }).then(() => {
+            //             return false;
+            //         });
+            //     }
+            //     return false
+            //   }
+            //保险文件--设备文档--安装质检报告
+            if (
+                val == "infos.insuranceFile" || //保险文件
+                val == "infos.archive" || //设备文档
+                val == "infos.checkReport" || //安装质检报告
+                val == "infos.maintainManual" || //维修保养手册
+                val == "infos.approachingAcceptance" || //进场验收单
+                val == "infos.acceptanceReport" || //验收报告
+                val == "infos.operationManual" || //操作说明书
+                val == "infos.originalCertificate" || //原厂证明
+                val == "infos.testReport" || //检测报告
+                val == "infos.productCertification" || //产品合格证
+                val == "infos.installInstruction" || //
+                val == "infos.supplierContract" || //供应合同
+                val == "infos.drawing" || //设备图纸
+                val == "infos.installDrawing" //安装图纸
+            ) {
+                let data = tools.dataForKey(this.tableData[row.row], val);
+                this.filesArr = data ? data : [];
+                this.myDialog.uploadFiles = true;
+            }
+            //安装照片--设备铭牌照片
+            if (val == "infos.installPic" || val == "infos.nameplate") {
+                let data = tools.dataForKey(this.tableData[row.row], val);
+                this.imgsArr = data ? data : [];
+                this.myDialog.uploadImgs = true;
+            }
+            //设备照片
+            if (val == "infos.pic") {
+                let data = tools.dataForKey(this.tableData[row.row], val);
+                this.picsArr = data ? data : [];
+                this.myDialog.pic = true;
+            }
+            if (!this.onlyRead && !inputData.editable) {
+                this.$confirm("该信息点的值为自动生成,不可人工维护!", "提示", {
+                    confirmButtonText: "我知道了",
+                    showCancelButton: false,
+                    type: "warning",
+                    center: true,
+                }).then(() => {
+                    return false;
+                });
+            }
+            // 维护多选枚举值
+            if (!this.onlyRead && inputData.dataType == "MENUM") {
+                this.updateInfoData = inputData;
+                this.updateInfoPoint = val;
+                this.updateInput = tools.dataForKey(this.tableData[row.row], val);
+                this.myDialog.menum = true;
+                return false;
+            }
+            if (!this.onlyRead && showTools.inputModeArr.indexOf(inputData.dataType) == "-1") {
+                this.updateInfoPoint = val;
+                this.updateInput = tools.dataForKey(this.tableData[row.row], val);
+                this.myDialog.update = true;
+                return false;
+            }
+        },
+        //关闭临时维护弹窗回调
+        handleCloseUpdate() {
+            this.updateInputShow = false;
+            this.updateInfoPoint = "";
+            this.updateInput = "";
+        },
+        //更新临时维护信息点
+        handleClickUpdate() {
+            tools.setDataForKey(this.tableData[this.row], this.updateInfoPoint, this.updateInput);
+            this.handleUpdataTable([[this.row, this.updateInfoPoint, null, this.updateInput]], "edit");
+            this.updateInputShow = false;
+            this.myDialog.update = false;
+            this.updateInput = "";
+        },
+        //多选枚举类型值修改
+        handleChangeMenum(newValue) {
+            tools.setDataForKey(this.tableData[this.row], this.updateInfoPoint, newValue);
+            this.handleUpdataTable([[this.row, this.updateInfoPoint, null, newValue]], "edit");
+            this.myDialog.menum = false;
+            this.updateInput = "";
+        },
+        utilToKey(key, name, data, messName) {
+            if (key == name) {
+                this.setDataToMain(data[key], messName, this.row);
+            }
+        },
+        //判断是否有值,有值赋值
+        setDataToMain(data, key, row) {
+            if (!!data && data != "--") {
+                if (!!this.tableData[row]) {
+                    //铭牌照片特殊处理
+                    tools.setDataForKey(this.tableData[row], key, data);
+                    // this.tableData[row][key] = data;
+                } else {
+                    this.tableData[row] = {};
+                    tools.setDataForKey(this.tableData[row], key, data);
+                }
+            } else {
+                tools.setDataForKey(this.tableData[row], key, "");
+            }
+        },
+    },
+};
+</script>
+<style lang="less" scoped>
+#deviceList {
+    overflow: hidden;
+    height: 100%;
+    background-color: #fff;
+    padding: 10px;
+    position: relative;
+
+    .right {
+        background: #fff;
+    }
+
+    .search-header {
+        overflow: hidden;
+        padding: 0 10px 10px 10px;
+        border-bottom: 1px solid #bcbcbc;
+    }
+
+    .tableBox {
+        display: flex;
+        height: calc(100% - 100px);
+        margin-top: 10px;
+
+        .tableLeft {
+            flex: 1;
+        }
+    }
+
+    .create_button {
+        margin-top: 10px;
+    }
+}
+</style>

+ 751 - 0
src/components/ledger/facility/partsmanage/index.vue

@@ -0,0 +1,751 @@
+<template>
+    <div id="deviceList">
+        <el-row>
+            <el-tabs @tab-click="tabsClick" v-model="tabVal">
+                <el-tab-pane v-for="t in partsTypeList" :key="t.code" :name="t.code" :label="`${t.name}(${t.count ? t.count : 0})`"></el-tab-pane>
+            </el-tabs>
+        </el-row>
+        <el-row class="left">
+            <!-- <div style="width:200px;display:inline-block;text-align:left;color:gray;">
+        <span>隐藏自动填充的信息</span>
+        <el-checkbox v-model="allMess" @change="getTableHeader"></el-checkbox>
+      </div> -->
+            <el-button size="small" type="default" icon="el-icon-back" @click="goback" style="margin-right: 10px"></el-button>
+            <el-select v-model="showType" @change="initTable" style="width: 100px; margin-right: 10px; vertical-align: bottom">
+                <el-option v-for="item in showTypes" :key="item.value" :label="item.label" :value="item.value"> </el-option>
+            </el-select>
+            <el-button @click="handleCreateTableData">添加部件</el-button>
+            <el-button @click="getTableHeader">刷新</el-button>
+            <el-button v-show="!onlyRead" @click="undo">撤销</el-button>
+            <div style="float: right; overflow: hidden">
+                <span style="width: 20px; float: left; display: block; height: 20px; cursor: pointer" @click="changeAssetsFalg">
+                    <i v-show="!onlyRead" class="el-icon-fa el-icon-fa-compass"></i>
+                </span>
+                <span>当前筛选条件下共{{ page.total || "--" }}部件</span>
+            </div>
+        </el-row>
+        <div class="tableBox">
+            <div class="center middle_sty" v-show="tableData && !tableData.length">
+                <p>
+                    <i class="icon-wushuju iconfont"></i>
+                    暂无数据
+                </p>
+            </div>
+            <div class="tableLeft" v-show="tableData && tableData.length">
+                <handson-table ref="table"></handson-table>
+            </div>
+        </div>
+        <el-pagination
+            class="right"
+            v-show="tableData && tableData.length"
+            @size-change="handleSizeChange"
+            @current-change="handleCurrentChange"
+            :current-page="page.pageNumber"
+            :page-sizes="page.pageSizes"
+            :page-size="page.pageSize"
+            layout="total, sizes, prev, pager, next, jumper"
+            :total="page.total"
+        ></el-pagination>
+        <!-- 二维码弹窗 -->
+        <qrcode :dialog="myDialog" :qrcodeUrl="qrcodeUrl" :addBody="true" ref="qrcode"></qrcode>
+        <upload-files-dialog :read="onlyRead ? true : false" ref="upload" @changeFile="fileChange" :keysArr="filesArr" :dialog="myDialog">
+        </upload-files-dialog>
+        <upload-img-dialog :read="onlyRead ? true : false" @changeFile="imgChange" :keysArr="imgsArr" :dialog="myDialog"></upload-img-dialog>
+        <pic-dialog :read="onlyRead ? true : false" :dialog="myDialog" :keysArr="picsArr" @change="changePics"></pic-dialog>
+        <details-dialog :iframeSrc="iframeSrc" v-if="myDialog.details" :dialog="myDialog"></details-dialog>
+        <!-- 关联资产 -->
+        <change-rea @changeProperty="changeProperty" :dialog="myDialog" :category="deviceType"></change-rea>
+        <!-- 多选枚举类型维护 -->
+        <menum :dialog="myDialog" :updateInfoData="updateInfoData" :updateInput="updateInput" @change="handleChangeMenum"></menum>
+        <!-- 不支持的输入方式 -->
+        <el-dialog title="临时维护信息点" :visible.sync="myDialog.update" @close="handleCloseUpdate" width="670px">
+            <el-row>
+                该信息点未定义对应组件,现在维护数据不确定后续是否可用。如确实需要维护,请点击
+                <el-link @click="updateInputShow = true" type="primary" :underline="false">继续维护</el-link>
+                。
+            </el-row>
+            <el-row style="margin-top: 20px" v-show="updateInputShow">
+                <el-input type="textarea" :autosize="{ minRows: 4, maxRows: 8 }" placeholder="请输入内容" v-model="updateInput"></el-input>
+            </el-row>
+            <span slot="footer" class="dialog-footer">
+                <el-button @click="myDialog.update = false">取 消</el-button>
+                <el-button type="primary" @click="handleClickUpdate">确 认</el-button>
+            </span>
+        </el-dialog>
+    </div>
+</template>
+<script>
+import tools from "@/utils/scan/tools";
+import handsonUtils from "@/utils/hasontableUtils";
+import showTools from "@/utils/handsontable/notShow";
+import text from "@/utils/handsontable/mainText";
+
+import handsonTable from "@/components/common/handsontable";
+import {
+    getPartsAllType,
+    // getDataDictionary,
+    getEquipPartsCon,
+    updateParts,
+    deleteParts,
+    getEquipBelongs,
+} from "@/api/scan/request";
+import { getDataDictionary } from "@/api/dict";
+
+import { mapGetters, mapActions } from "vuex";
+
+import qrcode from "@/components/ledger/lib/qrcode";
+import uploadFilesDialog from "@/components/dialogs/list/filesDialog";
+import uploadImgDialog from "@/components/dialogs/list/uploadImgDialog";
+import picDialog from "@/components/dialogs/list/picDialog";
+import changeRea from "@/components/dialogs/changeRea";
+import menum from "@/components/dialogs/list/menum";
+
+export default {
+    components: {
+        qrcode, //二维码页面
+        uploadFilesDialog,
+        uploadImgDialog,
+        picDialog,
+        handsonTable,
+        changeRea,
+        menum,
+    },
+    data() {
+        return {
+            shaftId: "", //要操作的部件id
+            isTableRightShow: false,
+            spaceList: "",
+            onlyRead: false,
+            showTypes: [
+                // { value: "partInfo", label: '隐藏信息点' },
+                { value: "Visible", label: "只看采集信息" },
+                { value: "all", label: "全部" },
+            ],
+            showType: "Visible",
+            tableHeader: [],
+            page: {
+                pageSize: 50,
+                pageSizes: [10, 20, 50, 100],
+                pageNumber: 1,
+                total: 0,
+            },
+            tableData: [],
+            copyTableData: [],
+            inputMap: {
+                flowBuild: {
+                    editable: true,
+                    code: "flowBuild",
+                    name: "建筑楼层",
+                    path: "flowBuild",
+                    category: "STATIC",
+                    dataType: "ENUM",
+                },
+            }, //信息点和输入方式映射表
+            updateInputShow: false, //是否显示临时维护输入框
+            updateInfoData: {}, //当前信息点信息
+            updateInfoPoint: "", //临时维护信息点
+            updateInput: "", //临时维护信息点值
+            tableExample: null,
+            tabVal: "",
+            activeTab: {}, //当前tab页index
+            partsTypeList: [], //tabs
+            myDialog: {
+                qrcode: false, //二维码弹窗
+                uploadFiles: false, //上传文件
+                uploadImgs: false, //上传单个图片
+                pic: false, //多个图片
+                addDevice: false,
+                systemType: false,
+                details: false, //详情页
+                lookPic: false, //图片查看
+                changeRea: false, //关联的资产
+                update: false, //临时维护信息点
+                menum: false, //临时多选枚举类型信息点
+            },
+            qrcodeUrl: "", //二维码图片地址
+            filesArr: [], //保存临时的文件key
+            imgsArr: [], //临时保存的图片key数组
+            picsArr: [], //临时设备图片keys数组
+            systemId: null,
+            graphyId: null,
+            mess: {},
+            id: 0,
+            EquipmentList: [],
+            firmId: "", //品牌型号所需字段--族id三位编码(传设备类id或部件类id)
+            deviceType: {},
+            queryParam: {}, //返回到指定位置
+        };
+    },
+    computed: {
+        ...mapGetters("layout", ["projectId", "secret", "userId"]),
+    },
+    created() {
+        this.queryParam = this.$route.query;
+        this.deviceId = this.$route.query.deviceId; //设备id
+        this.typeId = this.$route.query.typeId; //设备类id
+        this.mess.deviceId = this.typeId;
+        this.getTypes();
+    },
+    methods: {
+        getBelongs() {
+            let params = {
+                data: {
+                    filters: `equipCode='${this.tabVal}'`,
+                    pageNumber: 1,
+                    pageSize: 50,
+                },
+            };
+            getEquipBelongs(params, (res) => {
+                if (res.content.length) {
+                    this.firmId = res.content[0].family;
+                    this.deviceType.assetType = res.content[0].family;
+                    this.deviceType.assetName = res.content[0].familyName;
+                }
+            });
+        },
+        //tab页点击事件
+        tabsClick(tab) {
+            this.activeTab = this.partsTypeList[tab.index];
+            this.getTableHeader();
+        },
+        //获取当前设备类下的所有部件类
+        async getTypes() {
+            let params = {
+                category: this.typeId, //设备类型
+                id: this.deviceId, //设备id
+            };
+            await getPartsAllType(params, (res) => {
+                this.partsTypeList = res.content;
+                this.tabVal = this.partsTypeList[0].code;
+                this.activeTab = this.partsTypeList[0];
+                this.getTableHeader();
+                this.getBelongs();
+            });
+        },
+        // 获取表头数据(初始化表格)
+        async getTableHeader() {
+            let params = {
+                orders: "sort asc, name desc",
+                pageNumber: 1,
+                pageSize: 1000,
+                type: this.tabVal,
+            };
+            await getDataDictionary(params, (res) => {
+                this.tableHeader = res.content;
+                this.tableHeader.forEach((item) => {
+                    if (item.path) {
+                        this.inputMap[item.path] = item;
+                    }
+                });
+                this.getTableData();
+            });
+        },
+        //获取表格数据
+        async getTableData() {
+            let params = {
+                pageNumber: this.page.pageNumber,
+                pageSize: this.page.pageSize,
+                orders: "createTime desc, localName desc, localId desc, id desc",
+                filters: `classCode='${this.tabVal}';parentId='${this.deviceId}'`,
+            };
+            await getEquipPartsCon(params, (res) => {
+                this.tableData = res.content;
+                this.copyTableData = tools.deepCopy(res.content);
+                this.page.pageNumber = res.pageNumber;
+                this.page.total = res.total;
+                if (this.tableData && this.tableData.length) {
+                    this.initTable();
+                }
+            });
+        },
+        // 修改表格内容
+        async updateTableData(data, change) {
+            let param = {
+                    projectId: this.projectId,
+                    data: {
+                        content: [],
+                    },
+                },
+                keyList = [];
+            //生成要修改字段列表
+            change.map((item) => {
+                if (item[1] && keyList.indexOf(item[1]) == -1) {
+                    keyList.push(item[1]);
+                }
+            });
+            //生成对应修改数据
+            data.map((item, index) => {
+                let nullList = [];
+                keyList.map((value) => {
+                    let itemData = tools.dataForKey(item, value);
+                    tools.setDataForKey(item, value, itemData == "" ? null : itemData);
+                    // 数据为空加入置空列表(空数据类型包含: undefind / "" / null )
+                    itemData || nullList.push(value);
+                });
+                // 置空列表不为空
+                if (nullList.length) {
+                    // 深拷贝需更新的对象,并添加置空列表
+                    const updateItem = JSON.parse(JSON.stringify(item));
+                    updateItem.nullList = nullList;
+                    param.data.content.push(updateItem);
+                } else {
+                    // 无置空内容
+                    param.data.content.push(item);
+                }
+            });
+            await updateParts(param, (res) => {});
+        },
+        // 删除表格数据
+        async deleteTableData(params) {
+            await deleteParts(params, (res) => {
+                this.$message.success("删除成功!");
+                this.getTypes();
+            });
+        },
+        //保存元空间关联
+        async saveSpaceList() {
+            let data = {
+                shaftId: this.shaftId,
+                spaceId: this.spaceList,
+            };
+            const params = {
+                projId: this.projectId,
+                secret: this.secret,
+                data: data,
+            };
+            await saveCenoteRelateSpace(params, (res) => {
+                this.$message.success("关联成功!");
+                this.getTableHeader();
+            });
+        },
+        // 添加部件
+        handleCreateTableData() {
+            this.activeTab.deviceId = this.deviceId;
+            this.$router.push({
+                name: "partsadd",
+                query: {
+                    ...this.activeTab,
+                    pageNo: this.queryParam.pageNo,
+                    pageSize: this.queryParam.pageSize,
+                    showType: this.showType,
+                },
+            });
+        },
+        // 删除表格行
+        handleDeleteTableRow() {
+            let params = tools.differenceArr(this.tableData, this.copyTableData);
+            if (params.length < 1 || this.tableData > this.copyTableData) {
+                return;
+            }
+            let param = {
+                projectId: this.projectId,
+                data: [],
+            };
+            params.map((item) => {
+                param.data.push({ id: item.id });
+            });
+            this.$confirm("此操作将删除部件,是否继续?", "提示", {
+                confirmButtonText: "确定",
+                cancelButtonText: "取消",
+                type: "warning",
+            })
+                .then(() => {
+                    this.deleteTableData(param);
+                })
+                .catch(() => {
+                    this.getTableHeader();
+                    this.$message("取消删除");
+                });
+        },
+        //修改
+        handleUpdataTable(changeData, source) {
+            if (!this.onlyRead) {
+                if (changeData) {
+                    let trimmedArr = this.trimmedRows();
+                    let param = handsonUtils.getParam(changeData, source, this.tableExample, trimmedArr);
+                    let data = [];
+                    for (let i = 0; i < param.length; i++) {
+                        data.push(param[i]);
+                    }
+                    //如果data中包含/且data长度为1,将其转换成.
+                    if (changeData.length == 1 && changeData[0][1] && changeData[0][1].indexOf("/") > 0) {
+                        changeData[0][1] = changeData[0][1].split("/").join(".");
+                    }
+
+                    // 存在data进行修改请求
+                    if (data && data.length) {
+                        this.updateTableData(data, changeData);
+                    }
+                }
+            }
+        },
+        //撤回
+        undo() {
+            this.tableExample.undo();
+        },
+        //切换每页显示多少条数据
+        handleSizeChange(val) {
+            this.page.pageSize = val;
+            this.getTableHeader();
+        },
+        //切换页数
+        handleCurrentChange(val) {
+            this.page.pageNumber = val;
+            this.getTableHeader();
+        },
+        formatHeaderData(list) {
+            //格式化表头显示的数据
+            let arr = tools.copyArr(list);
+            let data = showTools.headerTextFilter(arr, "parts", this.onlyRead, this.showType, true);
+            // data.unshift("查看详情", "当前关联的资产");
+            data.unshift("查看详情");
+            return data;
+        },
+        formatHeaderType(list) {
+            //格式化表头头映射的数据
+            let arr = tools.copyArr(list);
+            let data = showTools.headerTypeFilter(arr, "parts", this.onlyRead, this.showType, true);
+            data.unshift(
+                {
+                    data: "caozuo",
+                    renderer: tools.lookDetails,
+                    readOnly: true,
+                }
+                // {
+                //     data: "propertyLocalName",
+                //     readOnly: true,
+                // }
+            );
+            return data;
+        },
+        initTable() {
+            //实例化表格
+            let settings = {
+                data: this.tableData,
+                colHeaders: this.formatHeaderData(this.tableHeader),
+                columns: this.formatHeaderType(this.tableHeader),
+                rowHeights: 30,
+                fillHandle: "vertical", //允许纵向填充
+                maxRows: this.tableData.length,
+                contextMenu: this.onlyRead
+                    ? false
+                    : {
+                          items: {
+                              remove_row: {
+                                  name: "删除部件",
+                              },
+                          },
+                      },
+                // 事件
+                afterChange: this.handleUpdataTable, //修改后
+                afterRemoveRow: this.handleDeleteTableRow, //右键删除
+                afterOnCellMouseDown: this.handleTdClick, //鼠标点击
+            };
+            this.$nextTick(() => {
+                this.tableExample = this.$refs.table.init(settings);
+            });
+        },
+        //去除数组中相同的元素
+        array_diff(a, b) {
+            for (var i = 0; i < b.length; i++) {
+                for (var j = 0; j < a.length; j++) {
+                    if (a[j] == b[i]) {
+                        a.splice(j, 1);
+                        j = j - 1;
+                    }
+                }
+            }
+            return a;
+        },
+        //修改关联的资产
+        changeProperty(val) {
+            this.setDataToMain(val.propertyId, "propertyId", this.row);
+            this.setDataToMain(val.linkEquipLocalName, "linkEquipLocalName", this.row);
+        },
+        //上传文件弹窗触发事件
+        fileChange(keys) {
+            this.setDataToMain(keys, this.messKey, this.row);
+        },
+        //上传图片弹窗触发事件
+        imgChange(keys) {
+            this.setDataToMain(keys, this.messKey, this.row);
+        },
+        //设备图片弹窗改变事件
+        changePics(keys) {
+            this.setDataToMain(keys, this.messKey, this.row);
+        },
+        //表格点击事件
+        handleTdClick(el, rowArr) {
+            //点击的是表头
+            if (rowArr.row < 0) {
+                return;
+            }
+            //被筛选过后的数组
+            let trimmedArr = this.trimmedRows();
+            //是否启用了排序
+            let isSort = this.tableExample.getPlugin("columnSorting").isSorted();
+            if (trimmedArr.length && isSort) {
+                let sortArr = this.myHotArr.getPlugin("columnSorting").rowsMapper.__arrayMap;
+                let infos = this.tableData[trimmedArr[sortArr[rowArr.row]]];
+                this.getInfors(infos, { row: sortArr[rowArr.row], col: rowArr.col });
+            } else if (isSort) {
+                //排序后的数组
+                let sortArr = this.tableExample.getPlugin("columnSorting").rowsMapper.__arrayMap;
+                let infos = this.tableData[sortArr[rowArr.row]];
+                this.getInfors(infos, { row: sortArr[rowArr.row], col: rowArr.col });
+            } else if (trimmedArr.length) {
+                let infos = this.tableData[trimmedArr[rowArr.row]];
+                this.getInfors(infos, { row: trimmedArr[rowArr.row], col: rowArr.col });
+            } else {
+                let infos = this.tableData[rowArr.row];
+                this.getInfors(infos, rowArr);
+            }
+        },
+        //获取被筛选掉的行号
+        trimmedRows() {
+            var plugin = this.tableExample.getPlugin("trimRows").trimmedRows;
+            let dataLength = this.tableData.length;
+            let dataArr = new Array();
+            for (let i = 0; i < dataLength; i++) {
+                dataArr.push(i);
+            }
+            if (plugin.length <= 0) {
+                dataArr = undefined;
+            } else {
+                dataArr = this.array_diff(dataArr, plugin);
+            }
+            return dataArr || [];
+        },
+        getInfors(infos, row) {
+            this.row = row.row;
+            let val = this.tableExample.colToProp(row.col);
+            let inputData = this.inputMap[val];
+            this.messKey = val;
+            switch (val) {
+                //操作
+                case "caozuo":
+                    // window.open(`http://adm.sagacloud.cn:8058/spread?id=${infos.EquipID}&pid=${this.projectId}&secret=${this.secret}`,"_blank")
+                    this.$message("开发中...");
+                    break;
+                //设备二维码图片
+                case "qRCodePic":
+                    this.qrcodeUrl = this.tableData[row.row].EquipQRCode;
+                    if (!!this.qrcodeUrl) {
+                        this.myDialog.qrcode = true;
+                    } else {
+                        this.$message("此设备没有设备二维码");
+                    }
+                    break;
+                //关联系统
+                case "linkSystem":
+                    if (!this.onlyRead) {
+                        this.curDevice = infos.EquipID;
+                        this.systemList = this.tableData[row.row].linkSystem || [];
+                        this.myDialog.systemType = true;
+                    }
+                    break;
+                //关联资产
+                case "propertyLocalName":
+                    if (this.linkNameFalg) {
+                        this.myDialog.changeRea = true;
+                    }
+                    break;
+                /**
+                 * 关联资产后才可维护的信息点
+                 */
+                // case 'flowBuild': //所属建筑楼层
+                // case 'infos.productDate': //生产日期
+                // case 'infos.serialNum': //出厂编号
+                // case 'infos.assetID': //资产编号
+                // case 'infos.purchasePrice': //采购价格
+                // case 'infos.insuranceNum': //保险单号
+                // case 'infos.insuranceFile': //保险文件
+                //     const propertyId = this.tableData[row.row].propertyId;
+                //     if (!propertyId) { //未关联资产
+                //         this.$confirm('您暂未关联设备对应的资产,该信息点不可编辑', '提示', {
+                //             confirmButtonText: '我知道了',
+                //             showCancelButton: false,
+                //             type: 'warning',
+                //             center: true
+                //         }).then(() => {
+                //             return false;
+                //         });
+                //     }
+                //     break
+                //保险文件--设备文档--安装质检报告
+                case "infos.insuranceFile": //保险文件
+                case "infos.archive": //设备文档
+                case "infos.checkReport": //安装质检报告
+                case "infos.maintainManual": //维修保养手册
+                case "infos.approachingAcceptance": //进场验收单
+                case "infos.acceptanceReport": //验收报告
+                case "infos.operationManual": //操作说明书
+                case "infos.originalCertificate": //原厂证明
+                case "infos.testReport": //检测报告
+                case "infos.productCertification": //产品合格证
+                case "infos.installInstruction": //安装说明书
+                case "infos.supplierContract": //供应合同
+                case "infos.drawing": //设备图纸
+                case "infos.installDrawing": //安装图纸
+                    let IPSdata = tools.dataForKey(this.tableData[row.row], val);
+                    this.filesArr = IPSdata ? IPSdata : [];
+                    this.myDialog.uploadFiles = true;
+                    break;
+                //安装照片--设备铭牌照片
+                case "infos.installPic":
+                case "infos.nameplate":
+                    let SSPPdata = tools.dataForKey(this.tableData[row.row], val);
+                    this.imgsArr = SSPPdata ? SSPPdata : [];
+                    this.myDialog.uploadImgs = true;
+                    break;
+                //设备照片
+                case "infos.pic":
+                    let Pdata = tools.dataForKey(this.tableData[row.row], val);
+                    this.picsArr = Pdata ? Pdata : [];
+                    this.myDialog.pic = true;
+                    break;
+                //包含的部件字段
+                case "count":
+                    if (this.onlyRead) {
+                        this.$router.push({ path: "/ledger/parts", query: { deviceId: infos.EquipID } });
+                    } else {
+                        this.$router.push({
+                            path: "/ledger/partsmanage",
+                            query: { deviceId: infos.EquipID, typeId: this.mess.deviceId },
+                        });
+                    }
+                    break;
+                default:
+                    break;
+            }
+            if (!this.onlyRead && !inputData.editable) {
+                this.$confirm("该信息点的值为自动生成,不可人工维护!", "提示", {
+                    confirmButtonText: "我知道了",
+                    showCancelButton: false,
+                    type: "warning",
+                    center: true,
+                }).then(() => {
+                    return false;
+                });
+            }
+            // 维护多选枚举值
+            if (!this.onlyRead && inputData.dataType == "MENUM") {
+                this.updateInfoData = inputData;
+                this.updateInfoPoint = val;
+                this.updateInput = tools.dataForKey(this.tableData[row.row], val);
+                this.myDialog.menum = true;
+                return false;
+            }
+            if (!this.onlyRead && showTools.inputModeArr.indexOf(inputData.dataType) == "-1") {
+                this.updateInfoPoint = val;
+                this.updateInput = tools.dataForKey(this.tableData[row.row], val);
+                this.myDialog.update = true;
+                return false;
+            }
+        },
+        //关闭临时维护弹窗回调
+        handleCloseUpdate() {
+            this.updateInputShow = false;
+            this.updateInfoPoint = "";
+            this.updateInput = "";
+        },
+        //更新临时维护信息点
+        handleClickUpdate() {
+            tools.setDataForKey(this.tableData[this.row], this.updateInfoPoint, this.updateInput);
+            this.handleUpdataTable([[this.row, this.updateInfoPoint, null, this.updateInput]], "edit");
+            this.updateInputShow = false;
+            this.myDialog.update = false;
+            this.updateInput = "";
+        },
+        //多选枚举类型值修改
+        handleChangeMenum(newValue) {
+            tools.setDataForKey(this.tableData[this.row], this.updateInfoPoint, newValue);
+            this.handleUpdataTable([[this.row, this.updateInfoPoint, null, newValue]], "edit");
+            this.myDialog.menum = false;
+            this.updateInput = "";
+        },
+        utilToKey(key, name, data, messName) {
+            if (key == name) {
+                this.setDataToMain(data[key], messName, this.row);
+            }
+        },
+        //判断是否有值,有值赋值
+        setDataToMain(data, key, row) {
+            if (!!data && data != "--") {
+                if (!!this.tableData[row]) {
+                    tools.setDataForKey(this.tableData[row], key, data);
+                    this.handleUpdataTable([[row, key, null, data]], "edit");
+                } else {
+                    this.tableData[row] = {};
+                    tools.setDataForKey(this.tableData[row], key, data);
+                }
+            } else {
+                tools.setDataForKey(this.tableData[row], key, "");
+            }
+        },
+        //修改资产
+        changeAssetsFalg() {
+            if (this.linkNameFalg) {
+                this.linkNameFalg = false;
+            } else {
+                this.$confirm(
+                    "<p>真的要在这里修改部件关联的资产么?</p><p>真的能通过列表信息找到想关联的资产么?</p><p>在这里修改关联的资产一定是因为之前关联错了要修改么?</p>",
+                    "提示",
+                    {
+                        dangerouslyUseHTMLString: true,
+                        confirmButtonText: "没错!我要改关联的资产",
+                        cancelButtonText: "我就点开看看",
+                        confirmButtonClass: "cancelButtonClass",
+                        cancelButtonClass: "cancelButtonClass",
+                    }
+                )
+                    .then((_) => {
+                        this.linkNameFalg = true;
+                    })
+                    .catch((_) => {
+                        this.$message("取消");
+                    });
+            }
+        },
+        // 返回
+        goback() {
+            this.$router.push({ path: "/ledger/facility" });
+        },
+    },
+};
+</script>
+<style lang="less" scoped>
+#deviceList {
+    overflow: hidden;
+    height: 100%;
+    background-color: #fff;
+    padding: 10px;
+    position: relative;
+
+    .right {
+        background: #fff;
+    }
+
+    .search-header {
+        overflow: hidden;
+        padding: 0 10px 10px 10px;
+        border-bottom: 1px solid #bcbcbc;
+    }
+
+    .tableBox {
+        display: flex;
+        height: calc(100% - 150px);
+        margin-top: 10px;
+
+        .tableLeft,
+        .middle_sty {
+            flex: 1;
+        }
+    }
+}
+
+.el-pagination button,
+.el-pagination span:not([class*="suffix"]) {
+    vertical-align: middle;
+}
+</style>