index.vue 40 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150
  1. <template>
  2. <div class="app-container">
  3. <el-row :gutter="20">
  4. <el-col :span="4" :xs="24">
  5. <div v-if="activeTab === 'area'" >
  6. <div class="head-container">
  7. <el-input v-model="areaName" placeholder="请输入区块名称" clearable size="small" prefix-icon="el-icon-search"
  8. style="margin-bottom: 20px" />
  9. </div>
  10. <div class="head-container">
  11. <el-tree :data="areaList" :props="defaultProps" :expand-on-click-node="false" :filter-node-method="filterNode" ref="tree"
  12. node-key="id" default-expand-all highlight-current @node-click="handleNodeClick" style="height: calc(100vh - 50px); overflow-y: auto;">
  13. <template slot-scope="scope">
  14. <div class="custom-tree-node">
  15. <span>{{ scope.node.label }}</span>
  16. <el-button
  17. v-if="scope.node.data.id !== 'all' && scope.node.data.children && scope.node.data.children.length > 0"
  18. size="mini"
  19. type="text"
  20. icon="el-icon-map-location"
  21. @click="showTopology(scope.node.data)"
  22. >拓扑图</el-button>
  23. </div>
  24. </template>
  25. </el-tree>
  26. </div>
  27. </div>
  28. <div v-if="activeTab === 'organ'">
  29. <div class="head-container">
  30. <el-input v-model="deptName" placeholder="请输入区块名称" clearable size="small" prefix-icon="el-icon-search"
  31. style="margin-bottom: 20px" />
  32. </div>
  33. <div class="head-container">
  34. <el-tree :key="treeKey" :data="deptList" :props="defaultDeptProps" :expand-on-click-node="false" :filter-node-method="filterNode" ref="tree"
  35. node-key="deptId" default-expand-all highlight-current @node-click="handleDeptNodeClick" style="height: calc(100vh - 50px); overflow-y: auto;">
  36. </el-tree>
  37. </div>
  38. </div>
  39. <div v-if="activeTab === 'facs'">
  40. <div class="head-container">
  41. <el-input v-model="areaName" placeholder="请输入区块名称" clearable size="small" prefix-icon="el-icon-search"
  42. style="margin-bottom: 20px"
  43. />
  44. </div>
  45. <div class="head-container">
  46. <el-tree :key="tableKey" :data="facsOptions" :props="defaultFacsProps" :expand-on-click-node="false"
  47. :filter-node-method="filterNode" ref="tree"
  48. node-key="id" default-expand-all highlight-current @node-click="handleFacsNodeClick"
  49. style="height: calc(100vh - 50px); overflow-y: auto;">
  50. </el-tree>
  51. </div>
  52. </div>
  53. <div v-if="activeTab === 'device'" class="device-tree-container">
  54. <div class="head-container">
  55. <el-input v-model="areaName" placeholder="请输入区块名称" clearable size="small" prefix-icon="el-icon-search"
  56. style="margin-bottom: 20px"
  57. />
  58. </div>
  59. <div class="head-container">
  60. <el-tree :key="tableKey" :data="deviceOptions" :props="defaultFacsProps" :expand-on-click-node="false"
  61. :filter-node-method="filterNode" ref="tree"
  62. node-key="id" default-expand-all highlight-current @node-click="handleDeviceNodeClick"
  63. class="device-tree">
  64. </el-tree>
  65. </div>
  66. </div>
  67. </el-col>
  68. <el-tabs v-model="activeTab" @tab-click="handleTabClick">
  69. <el-tab-pane label="地理位置" name="area"></el-tab-pane>
  70. <el-tab-pane label="组织机构" name="organ"></el-tab-pane>
  71. <el-tab-pane label="设施" name="facs"></el-tab-pane>
  72. <el-tab-pane label="设备" name="device"></el-tab-pane>
  73. </el-tabs>
  74. <el-col :span="20" :xs="24">
  75. <!-- 地理位置 -->
  76. <div v-if="activeTab === 'area'">
  77. <el-tabs v-model="activeDeviceTab" @tab-click="handleADTabClick">
  78. <el-tab-pane label="电表" name="electricMeter"></el-tab-pane>
  79. <el-tab-pane label="水表" name="waterMeter"></el-tab-pane>
  80. </el-tabs>
  81. <SubTitle title="已绑定列表" />
  82. <el-table v-loading="loading" :data="areaBound" style="width: 100%">
  83. <el-table-column label="表计编号" align="left" prop="meterDevice" />
  84. <el-table-column label="表计名称" align="left" prop="meterDeviceName" />
  85. <el-table-column label="边界类型" align="left" prop="objType">
  86. <template slot-scope="scope">
  87. {{ getObjTypeLabel(scope.row.objType) }}
  88. </template>
  89. </el-table-column>
  90. <el-table-column label="操作" align="center" class-name="small-padding fixed-width">
  91. <template slot-scope="scope">
  92. <el-button size="mini" type="text" icon="el-icon-arrow-down" @click="downToDevice(scope.row, 'area')">取消绑定</el-button>
  93. </template>
  94. </el-table-column>
  95. </el-table>
  96. <SubTitle title="未绑定列表" />
  97. <el-table v-loading="loading" :data="areaUnbound" style="width: 100%">
  98. <el-table-column label="表计编号" align="left" prop="deviceCode" />
  99. <el-table-column label="表计名称" align="left" prop="deviceName" width="200px"/>
  100. <el-table-column label="安装位置" align="left" prop="deviceLocation" width="200px"/>
  101. <el-table-column label="计量标签" align="center" prop="objTag">
  102. <template slot-scope="scope">
  103. {{formatDict(scope.row.objTag,'objTagOptions')}}
  104. </template>
  105. </el-table-column>
  106. <el-table-column label="采集方式" align="center" prop="colMode">
  107. <template slot-scope="scope">
  108. <span>{{ getColModeName(scope.row.colMode) }}</span>
  109. </template>
  110. </el-table-column>
  111. <el-table-column label="采集周期" align="center" prop="colCycle">
  112. <template slot-scope="scope">
  113. <span>{{ getColCycleName(scope.row.colCycle) }}</span>
  114. </template>
  115. </el-table-column>
  116. <el-table-column label="操作" align="center" class-name="small-padding fixed-width">
  117. <template slot-scope="scope">
  118. <div v-if="isAlreadyBound(scope.row.deviceCode, 'area')">
  119. <span>已绑定</span>
  120. </div>
  121. <div v-else>
  122. <el-button size="mini" type="text" icon="el-icon-arrow-up" @click="moveToDevice(scope.row, 'area')">绑定</el-button>
  123. </div>
  124. </template>
  125. </el-table-column>
  126. </el-table>
  127. <pagination v-show="total>0" :total="total" :page.sync="MeterQueryParams.pageNum"
  128. :limit.sync="MeterQueryParams.pageSize" @pagination="getMeterData('area')"
  129. />
  130. </div>
  131. <!-- 组织机构 -->
  132. <div v-if="activeTab === 'organ'">
  133. <el-tabs v-model="activeDeviceTab" @tab-click="handleADTabClick">
  134. <el-tab-pane label="电表" name="electricMeter"></el-tab-pane>
  135. <el-tab-pane label="水表" name="waterMeter"></el-tab-pane>
  136. </el-tabs>
  137. <SubTitle title="已绑定列表" />
  138. <el-table v-loading="loading" :data="organBound" style="width: 100%">
  139. <el-table-column label="表计编号" align="left" prop="meterDevice" />
  140. <el-table-column label="表计名称" align="left" prop="meterDeviceName" />
  141. <el-table-column label="边界类型" align="left" prop="objType">
  142. <template slot-scope="scope">
  143. {{ getObjTypeLabel(scope.row.objType) }}
  144. </template>
  145. </el-table-column>
  146. <el-table-column label="操作" align="center" class-name="small-padding fixed-width">
  147. <template slot-scope="scope">
  148. <el-button size="mini" type="text" icon="el-icon-arrow-down" @click="downToDevice(scope.row, 'organ')">取消绑定</el-button>
  149. </template>
  150. </el-table-column>
  151. </el-table>
  152. <SubTitle title="未绑定列表" />
  153. <el-table v-loading="loading" :data="organUnbound" style="width: 100%">
  154. <el-table-column label="表计编号" align="left" prop="deviceCode" />
  155. <el-table-column label="表计名称" align="left" prop="deviceName" width="200px"/>
  156. <el-table-column label="安装位置" align="left" prop="deviceLocation" width="200px"/>
  157. <el-table-column label="计量标签" align="center" prop="objTag">
  158. <template slot-scope="scope">
  159. {{formatDict(scope.row.objTag,'objTagOptions')}}
  160. </template>
  161. </el-table-column>
  162. <el-table-column label="采集方式" align="center" prop="colMode">
  163. <template slot-scope="scope">
  164. <span>{{ getColModeName(scope.row.colMode) }}</span>
  165. </template>
  166. </el-table-column>
  167. <el-table-column label="采集周期" align="center" prop="colCycle">
  168. <template slot-scope="scope">
  169. <span>{{ getColCycleName(scope.row.colCycle) }}</span>
  170. </template>
  171. </el-table-column>
  172. <el-table-column label="操作" align="center" class-name="small-padding fixed-width">
  173. <template slot-scope="scope">
  174. <div v-if="isAlreadyBound(scope.row.deviceCode, 'organ')">
  175. <span>已绑定</span>
  176. </div>
  177. <div v-else>
  178. <el-button size="mini" type="text" icon="el-icon-arrow-up" @click="moveToDevice(scope.row, 'organ')">绑定</el-button>
  179. </div>
  180. </template>
  181. </el-table-column>
  182. </el-table>
  183. <pagination v-show="total>0" :total="total" :page.sync="MeterQueryParams.pageNum"
  184. :limit.sync="MeterQueryParams.pageSize" @pagination="getMeterData('organ')" />
  185. </div>
  186. <!-- 设施 -->
  187. <div v-if="activeTab === 'facs'">
  188. <el-tabs v-model="activeDeviceTab" @tab-click="handleADTabClick">
  189. <el-tab-pane label="电表" name="electricMeter"></el-tab-pane>
  190. <el-tab-pane label="水表" name="waterMeter"></el-tab-pane>
  191. </el-tabs>
  192. <SubTitle title="已绑定列表" />
  193. <el-table v-loading="loading" :data="facsBound" style="width: 100%">
  194. <el-table-column label="表计编号" align="left" prop="meterDevice" />
  195. <el-table-column label="表计名称" align="left" prop="meterDeviceName" />
  196. <el-table-column label="边界类型" align="left" prop="objType">
  197. <template slot-scope="scope">
  198. {{ getObjTypeLabel(scope.row.objType) }}
  199. </template>
  200. </el-table-column>
  201. <el-table-column label="操作" align="center" class-name="small-padding fixed-width">
  202. <template slot-scope="scope">
  203. <el-button size="mini" type="text" icon="el-icon-arrow-down" @click="downToDevice(scope.row, 'facs')">取消绑定</el-button>
  204. </template>
  205. </el-table-column>
  206. </el-table>
  207. <SubTitle title="未绑定列表" />
  208. <el-table v-loading="loading" :data="facsUnbound" style="width: 100%">
  209. <el-table-column label="表计编号" align="left" prop="deviceCode" />
  210. <el-table-column label="表计名称" align="left" prop="deviceName" width="200px"/>
  211. <el-table-column label="安装位置" align="left" prop="deviceLocation" width="200px"/>
  212. <el-table-column label="计量标签" align="center" prop="objTag">
  213. <template slot-scope="scope">
  214. {{formatDict(scope.row.objTag,'objTagOptions')}}
  215. </template>
  216. </el-table-column>
  217. <el-table-column label="采集方式" align="center" prop="colMode">
  218. <template slot-scope="scope">
  219. <span>{{ getColModeName(scope.row.colMode) }}</span>
  220. </template>
  221. </el-table-column>
  222. <el-table-column label="采集周期" align="center" prop="colCycle">
  223. <template slot-scope="scope">
  224. <span>{{ getColCycleName(scope.row.colCycle) }}</span>
  225. </template>
  226. </el-table-column>
  227. <el-table-column label="操作" align="center" class-name="small-padding fixed-width">
  228. <template slot-scope="scope">
  229. <div v-if="isAlreadyBound(scope.row.deviceCode, 'facs')">
  230. <span>已绑定</span>
  231. </div>
  232. <div v-else>
  233. <el-button size="mini" type="text" icon="el-icon-arrow-up" @click="moveToDevice(scope.row, 'facs')">绑定</el-button>
  234. </div>
  235. </template>
  236. </el-table-column>
  237. </el-table>
  238. <pagination v-show="total>0" :total="total" :page.sync="MeterQueryParams.pageNum"
  239. :limit.sync="MeterQueryParams.pageSize" @pagination="getMeterData('facs')" />
  240. </div>
  241. <!--设备 -->
  242. <div v-if="activeTab === 'device'">
  243. <el-tabs v-model="activeDeviceTab" @tab-click="handleADTabClick">
  244. <el-tab-pane label="电表" name="electricMeter"></el-tab-pane>
  245. <el-tab-pane label="水表" name="waterMeter"></el-tab-pane>
  246. </el-tabs>
  247. <SubTitle title="已绑定列表" />
  248. <el-table v-loading="loading" :data="deviceBound" style="width: 100%">
  249. <el-table-column label="表计编号" align="left" prop="meterDevice" />
  250. <el-table-column label="表计名称" align="left" prop="meterDeviceName" />
  251. <el-table-column label="边界类型" align="left" prop="objType">
  252. <template slot-scope="scope">
  253. {{ getObjTypeLabel(scope.row.objType) }}
  254. </template>
  255. </el-table-column>
  256. <el-table-column label="操作" align="center" class-name="small-padding fixed-width">
  257. <template slot-scope="scope">
  258. <el-button size="mini" type="text" icon="el-icon-arrow-down" @click="downToDevice(scope.row, 'device')">取消绑定</el-button>
  259. </template>
  260. </el-table-column>
  261. </el-table>
  262. <SubTitle title="未绑定列表" />
  263. <el-table v-loading="loading" :data="deviceUnbound" style="width: 100%">
  264. <el-table-column label="表计编号" align="left" prop="deviceCode" />
  265. <el-table-column label="表计名称" align="left" prop="deviceName" width="200px"/>
  266. <el-table-column label="安装位置" align="left" prop="deviceLocation" width="200px"/>
  267. <el-table-column label="计量标签" align="center" prop="objTag">
  268. <template slot-scope="scope">
  269. {{formatDict(scope.row.objTag,'objTagOptions')}}
  270. </template>
  271. </el-table-column>
  272. <el-table-column label="采集方式" align="center" prop="colMode">
  273. <template slot-scope="scope">
  274. <span>{{ getColModeName(scope.row.colMode) }}</span>
  275. </template>
  276. </el-table-column>
  277. <el-table-column label="采集周期" align="center" prop="colCycle">
  278. <template slot-scope="scope">
  279. <span>{{ getColCycleName(scope.row.colCycle) }}</span>
  280. </template>
  281. </el-table-column>
  282. <el-table-column label="操作" align="center" class-name="small-padding fixed-width">
  283. <template slot-scope="scope">
  284. <div v-if="isAlreadyBound(scope.row.deviceCode, 'device')">
  285. <span>已绑定</span>
  286. </div>
  287. <div v-else>
  288. <el-button size="mini" type="text" icon="el-icon-arrow-up" @click="moveToDevice(scope.row, 'device')">绑定</el-button>
  289. </div>
  290. </template>
  291. </el-table-column>
  292. </el-table>
  293. <pagination v-show="total>0" :total="total" :page.sync="MeterQueryParams.pageNum"
  294. :limit.sync="MeterQueryParams.pageSize" @pagination="getMeterData('device')" />
  295. </div>
  296. </el-col>
  297. </el-row>
  298. <!-- 拓扑图弹框 -->
  299. <el-dialog :visible.sync="topologyDialogVisible" title="拓扑图" width="1500px" @open="scrollToCenter">
  300. <div class="topology-chart-container" ref="topologyChartContainer">
  301. <div ref="topologyChart" class="topology-chart" style="width: 8000px; height: 600px;"></div>
  302. </div>
  303. </el-dialog>
  304. </div>
  305. </template>
  306. <script>
  307. import * as echarts from 'echarts';
  308. import Treeselect from '@riophae/vue-treeselect'
  309. import '@riophae/vue-treeselect/dist/vue-treeselect.css'
  310. import { areaTreeSelect, listDetailArea } from '@/api/basecfg/area'
  311. import SubTitle from '@/components/SubTitle/index.vue'
  312. import { getDevProcess, getEmsTag } from '@/api/commonApi'
  313. import { listDevice } from '@/api/device/meterDevice'
  314. import { addAllByObj, listByObj ,getBoundaryTreeByArea } from '@/api/basecfg/meterBoundary'
  315. import { listDept } from '@/api/system/dept'
  316. import { getFacsCategoryTree, listAllFacs } from '@/api/basecfg/emsfacs'
  317. import { listSubsystemAll } from '@/api/adapter/subsystem'
  318. import {getTreeByFacs} from '@/api/device/device.js'
  319. export default {
  320. name: 'Device',
  321. dicts: ['sys_normal_disable'],
  322. components: { SubTitle, Treeselect },
  323. data() {
  324. return {
  325. topologyDialogVisible: false,
  326. activeTab: 'area',
  327. areaOptions: [],
  328. organOptions: [],
  329. facsOptions: undefined,
  330. facsAllOptions:undefined,
  331. deviceOptions:undefined,
  332. // 区域名称
  333. areaName: undefined,
  334. deptName:undefined,
  335. defaultProps: {
  336. children: 'children',
  337. label: 'areaName'
  338. },
  339. defaultDeptProps: {
  340. children: 'children',
  341. label: 'deptName'
  342. },
  343. defaultFacsProps: {
  344. children: 'children',
  345. label: 'label'
  346. },
  347. treeKey: 0,
  348. tableKey: 0,
  349. // 遮罩层
  350. loading: true,
  351. // 选中数组
  352. ids: [],
  353. // 非单个禁用
  354. single: true,
  355. // 非多个禁用
  356. multiple: true,
  357. // 显示搜索条件
  358. showSearch: true,
  359. // 区域对象表格数据
  360. areaList: [],
  361. deptList:[],
  362. facsList:[],
  363. deviceList:[],
  364. // 总条数
  365. total: 0,
  366. // 弹出层标题
  367. title: "",
  368. // 是否显示弹出层
  369. open: false,
  370. // 是否展开,默认全部展开
  371. isExpandAll: true,
  372. // 重新渲染表格状态
  373. refreshTable: true,
  374. // 标签选项
  375. emsTagOptions: [],
  376. tagCodeToColorMap:{},
  377. // 地理位置查询参数
  378. queryParams: {
  379. pageNum: 1,
  380. pageSize: 10,
  381. areaCode: null,
  382. parentCode: null,
  383. ancestors: null,
  384. areaName: null,
  385. shortName: null,
  386. desc: null,
  387. orderNum: null,
  388. status: null,
  389. tagNames:null,
  390. areaAttr:{}
  391. },
  392. // 组织机构查询参数
  393. DeptQueryParams: {
  394. deptName: undefined,
  395. status: undefined
  396. },
  397. // 设施 查询参数
  398. FacsQueryParams: {
  399. pageNum: 1,
  400. pageSize: 10,
  401. facsCode: null,
  402. facsName: null,
  403. facsCategory: 'E',
  404. facsSubCategory: null,
  405. enable: null,
  406. refArea: null,
  407. customAttrs: null
  408. },
  409. // 设备查询参数
  410. DeviceQueryParams: {
  411. psCode: null,
  412. pageNum: 1,
  413. pageSize: 10,
  414. deviceCode: null,
  415. deviceSubCategory: '',
  416. deviceCategory: 'E',
  417. locationRef: null,
  418. refFacs: null,
  419. customAttrs: null
  420. },
  421. // 水表、电表查询参数
  422. MeterQueryParams: {
  423. pageNum: 1,
  424. pageSize: 10,
  425. areaCode: null,
  426. bldgCode: null,
  427. deviceCode: null,
  428. meterCls: null,
  429. objTag: null,
  430. colCycle: null,
  431. colMode: null,
  432. magnification: null,
  433. specDesc: null
  434. },
  435. objStatusMapping: {
  436. 0: '正常',
  437. 1: '停用',
  438. },
  439. // 表单参数
  440. form: {
  441. areaAttr: {
  442. areaCode:null,
  443. attrOrg: null,
  444. mgrOrg: null,
  445. leader: null,
  446. phone: null,
  447. openDate: null,
  448. floorArea: null,
  449. usableArea: null,
  450. floor: null,
  451. tagCodes:null,
  452. tagNames:null,
  453. tagCodeList:[]
  454. }
  455. },
  456. // 表单校验
  457. rules: {
  458. areaCode: [
  459. { required: true, message: "区域代码不能为空", trigger: "blur" }
  460. ],
  461. areaName: [
  462. { required: true, message: "区域名称不能为空", trigger: "blur" }
  463. ],
  464. },
  465. objTagOptions: [
  466. { code: 0, name: '公摊表' },
  467. { code: 1, name: '个户表' }
  468. ],
  469. allAreaNode: {
  470. id: 'all',
  471. areaName: '全部',
  472. children: []
  473. },
  474. allDeptNode: {
  475. deptId: 'all',
  476. deptName: '全部',
  477. children: []
  478. },
  479. // 设施分类列表
  480. facsCategoryOptions: undefined,
  481. facsSubCategoryOptions: undefined,
  482. // 设备分类
  483. subCategoryOptions: undefined,
  484. subsystemOptions: undefined,
  485. subcategoryCode: '',
  486. devOptions: undefined,
  487. parentAreaCode:'',
  488. dialogTitle: '',
  489. deviceDialogVisible: false,
  490. activeDeviceTab: 'electricMeter',
  491. areaBound: [],
  492. areaUnbound: [],
  493. organBound: [],
  494. organUnbound: [],
  495. facsBound: [],
  496. facsUnbound: [],
  497. deviceBound: [],
  498. deviceUnbound: [],
  499. }
  500. },
  501. mounted() {
  502. this.getList();
  503. this.getFacsList();
  504. this.getDeviceList();
  505. },
  506. created() {
  507. this.getAreaFacsTree('0', 1)
  508. this.getAreaDeviceTree('0', 1)
  509. this.getAllDevProcess(this.subcategoryCode)
  510. this.getEmsTag('Area');
  511. this.getFacsOptions()
  512. this.getSubsystem()
  513. this.form.areaAttr = {};
  514. this.getList();
  515. },
  516. methods: {
  517. getAreaFacsTree(areaCode, layer) {
  518. areaTreeSelect(areaCode, layer).then(response => {
  519. this.facsOptions = [{
  520. id: '-1',
  521. label: '全部',
  522. children: response.data
  523. }]
  524. })
  525. },
  526. getAreaDeviceTree(areaCode, layer) {
  527. areaTreeSelect(areaCode, layer).then(response => {
  528. this.deviceOptions = [{
  529. id: null,
  530. label: '全部',
  531. children: response.data
  532. }]
  533. })
  534. },
  535. filterNode(value, data) {
  536. if (!value) return true
  537. return data.label.indexOf(value) !== -1
  538. },
  539. getObjTypeLabel(objType) {
  540. const typeLabels = {
  541. 1: '区域位置',
  542. 2: '设施',
  543. 3: '设备',
  544. 4: '组织'
  545. };
  546. return typeLabels[objType] || '未知类型';
  547. },
  548. formatDict(val, options, key = 'code', text = 'name') {
  549. if (!this[options] || !Array.isArray(this[options])) {
  550. console.error(`Expected an array for this[${options}], but got ${typeof this[options]}`);
  551. return '';
  552. }
  553. let name = '';
  554. this[options].forEach(item => {
  555. if (val === item[key]) {
  556. name = item[text];
  557. }
  558. });
  559. return name;
  560. },
  561. getColCycleName(colCycle) {
  562. const cycleMap = {
  563. 0: '实时',
  564. 1: '分钟',
  565. 2: '小时',
  566. 3: '天',
  567. 4: '月'
  568. }
  569. return cycleMap[colCycle] || ''
  570. },
  571. getColModeName(colMode) {
  572. const modeMap = {
  573. 0: '自动抄表',
  574. 1: '手动抄表'
  575. }
  576. return modeMap[colMode] || '未知'
  577. },
  578. /**分页*/
  579. handleTabClick(tab) {
  580. this.reset();
  581. this.MeterQueryParams.pageNum = 1;
  582. this.activeDeviceTab= 'electricMeter';
  583. if (tab.name === 'area') {
  584. this.showSearch = true;
  585. this.getList();
  586. } else if (tab.name === 'organ') {
  587. this.showSearch = true;
  588. this.getDeptList();
  589. } else if (tab.name === 'facs') {
  590. this.showSearch = true;
  591. this.getFacsList()
  592. }else if (tab.name === 'device') {
  593. this.showSearch = true;
  594. this.getDeviceList();
  595. }
  596. },
  597. /**电表、水表分页*/
  598. handleADTabClick(tab){
  599. this.activeDeviceTab = tab.name;
  600. this.getMeterData();
  601. },
  602. /**地理位置*/
  603. handleNodeClick(data) {
  604. // 不执行任何操作
  605. if (data.id === 'all') {
  606. return;
  607. }
  608. this.queryParams.areaCode = data.areaCode;
  609. if (data && data.ancestors) {
  610. const ancestorsArray = data.ancestors.split(',');
  611. if (ancestorsArray.length > 1) {
  612. // 当 ancestors 长度大于 1 时,取第二个 areaCode 作为父级 areaCode
  613. this.MeterQueryParams.areaCode= ancestorsArray[1];
  614. } else if (ancestorsArray.length === 1) {
  615. // 当 ancestors 长度等于 1 时,取该对象本身的 areaCode 作为父级 areaCode
  616. this.MeterQueryParams.areaCode = data.areaCode;
  617. }
  618. }
  619. this.getMeterData(data);
  620. },
  621. /**组织机构*/
  622. handleDeptNodeClick(data) {
  623. if (data.deptId === 'all') {
  624. return;
  625. }
  626. // 递归查找第一个叶子节点
  627. const firstLeafNode = this.findFirstLeafDeptNode(data);
  628. if (firstLeafNode) {
  629. this.queryParams.areaCode = firstLeafNode.deptId;
  630. } else {
  631. this.queryParams.areaCode = data.deptId;
  632. }
  633. this.getMeterData();
  634. },
  635. findFirstLeafDeptNode(node) {
  636. if (!node.children || node.children.length === 0) {
  637. return node;
  638. }
  639. for (let child of node.children) {
  640. const leafNode = this.findFirstLeafDeptNode(child);
  641. if (leafNode) {
  642. return leafNode;
  643. }
  644. }
  645. return null;
  646. },
  647. /** 设施节点点击事件 */
  648. handleFacsNodeClick(data) {
  649. if (data.id === '-1') {
  650. this.queryParams.areaCode = null;
  651. this.MeterQueryParams.areaCode = null;
  652. } else {
  653. this.queryParams.areaCode = data.id;
  654. }
  655. this.getMeterData();
  656. },
  657. /**设备*/
  658. handleDeviceNodeClick(data) {
  659. if (data.id === null) {
  660. this.queryParams.areaCode = null;
  661. this.MeterQueryParams.areaCode = null;
  662. } else {
  663. this.queryParams.areaCode = data.id;
  664. }
  665. this.getMeterData();
  666. },
  667. /** 查询区域对象树列表 */
  668. getList() {
  669. this.loading = true;
  670. listDetailArea(this.queryParams).then(response => {
  671. this.areaList = this.handleTree(response.data, "areaCode", "parentCode");
  672. this.areaList = this.extractTagNames(this.areaList);
  673. this.tableKey += 1; // 改变 key 值以强制重新渲染
  674. this.handleNodeClick(this.areaList[0]);//常泰北区
  675. this.loading = false;
  676. });
  677. },
  678. // 递归提取 tagNames
  679. extractTagNames(list) {
  680. return list.map(item => {
  681. if (item.areaAttr && item.areaAttr.tagNames) {
  682. item.tagNames = item.areaAttr.tagNames;
  683. } else {
  684. item.tagNames = '';
  685. }
  686. if (item.children) {
  687. item.children = this.extractTagNames(item.children);
  688. }
  689. return item;
  690. });
  691. },
  692. /** 查询 组织机构(部门)树列表 */
  693. getDeptList() {
  694. this.loading = true;
  695. listDept(this.DeptQueryParams).then(response => {
  696. this.deptList = this.handleTree(response.data, "deptId");
  697. this.deptList = this.extractTagNames(this.deptList);
  698. this.treeKey += 1;
  699. this.loading = false;
  700. this.handleDeptNodeClick(this.deptList[0]);
  701. });
  702. },
  703. /** 查询能源设施树列表 */
  704. getFacsList() {
  705. this.loading = true;
  706. getFacsCategoryTree().then(response => {
  707. this.facsOptions = response.data;
  708. this.tableKey += 1;
  709. // 第一个叶子节点
  710. if (this.facsOptions && this.facsOptions.length > 0 && this.facsOptions[0].children) {
  711. const firstLeafNode = this.findFirstLeafNode(this.facsOptions[0].children);
  712. if (firstLeafNode) {
  713. this.queryParams.areaCode = firstLeafNode.id;
  714. this.getMeterData();
  715. }
  716. }
  717. this.loading = false;
  718. })
  719. },
  720. /** 查询能源设备树列表 */
  721. getDeviceList() {
  722. this.loading = true;
  723. getTreeByFacs().then(response => {
  724. this.deviceOptions = response.data;
  725. this.loading = false;
  726. if (this.deviceOptions && this.deviceOptions.length > 0 && this.deviceOptions[0].children) {
  727. const firstLeafNode = this.findFirstLeafNode(this.deviceOptions[0].children);
  728. if (firstLeafNode) {
  729. this.queryParams.areaCode = firstLeafNode.id;
  730. this.getMeterData();
  731. }
  732. }
  733. })
  734. this.loading = false;
  735. },
  736. findFirstLeafNode(nodes) {
  737. if (!nodes || nodes.length === 0) return null;
  738. for (let node of nodes) {
  739. if (!node.children || node.children.length === 0) {
  740. return node;
  741. } else {
  742. const leafNode = this.findFirstLeafNode(node.children);
  743. if (leafNode) return leafNode;
  744. }
  745. }
  746. return null;
  747. },
  748. // 表单重置
  749. reset() {
  750. this.form = {
  751. id: null,
  752. areaCode: null,
  753. parentCode: null,
  754. ancestors: null,
  755. areaName: null,
  756. shortName: null,
  757. desc: null,
  758. orderNum: null,
  759. status: null,
  760. areaAttr: {
  761. areaCode: null,
  762. attrOrg: null,
  763. mgrOrg: null,
  764. leader: null,
  765. phone: null,
  766. openDate: null,
  767. floorArea: null,
  768. usableArea: null,
  769. floor: null,
  770. tagCodes: null,
  771. tagNames: null,
  772. tagCodeList: []
  773. }
  774. };
  775. this.resetForm("form");
  776. this.queryParams = {
  777. pageNum: 1,
  778. pageSize: 10,
  779. areaCode: null,
  780. parentCode: null,
  781. ancestors: null,
  782. areaName: null,
  783. shortName: null,
  784. desc: null,
  785. orderNum: null,
  786. status: null,
  787. tagNames:null,
  788. areaAttr:{}
  789. };
  790. },
  791. filterDataByAreaCode(list, areaCode) {
  792. let result = [];
  793. for (let i = 0; i < list.length; i++) {
  794. if (list[i].areaCode === areaCode) {
  795. if (list[i].children && list[i].children.length > 0) {
  796. list[i].children.forEach(child => {
  797. result.push(child);
  798. if (child.children && child.children.length > 0) {
  799. result = result.concat(this.filterDataByAreaCode(child.children, areaCode));
  800. }
  801. });
  802. }
  803. return result;
  804. } else if (list[i].children && list[i].children.length > 0) {
  805. const foundChildren = this.filterDataByAreaCode(list[i].children, areaCode);
  806. if (foundChildren.length > 0) {
  807. return foundChildren;
  808. }
  809. }
  810. }
  811. return result;
  812. },
  813. // 多选框选中数据
  814. handleSelectionChange(selection) {
  815. this.ids = selection.map(item => item.id)
  816. this.single = selection.length !== 1
  817. this.multiple = !selection.length
  818. },
  819. /**计量设备*/
  820. getMeterData() {
  821. this.loading = true;
  822. let meterCls = '';
  823. if (this.activeDeviceTab === 'electricMeter') {
  824. meterCls = 45;
  825. } else if (this.activeDeviceTab === 'waterMeter') {
  826. meterCls = 70;
  827. }
  828. let objType;
  829. if (this.activeTab === 'area') {
  830. objType = 1;
  831. listDevice({ ...this.MeterQueryParams, meterCls }).then(response => {
  832. this.areaUnbound = response.rows;
  833. this.total = response.total;
  834. this.loading = false; // 关闭加载状态
  835. });
  836. listByObj(objType, meterCls, this.queryParams.areaCode).then(response => {
  837. this.areaBound = response.data;
  838. this.loading = false;
  839. });
  840. } else if (this.activeTab === 'organ') {
  841. objType = 4;
  842. listDevice({ ...this.MeterQueryParams, meterCls }).then(response => {
  843. this.organUnbound = response.rows;
  844. this.total = response.total;
  845. this.loading = false;
  846. })
  847. listByObj(objType, meterCls, this.queryParams.areaCode).then(response => {
  848. this.organBound = response.data;
  849. this.loading = false;
  850. });
  851. this.loading = false;
  852. } else if (this.activeTab === 'facs') {
  853. objType = 2;
  854. listDevice({ ...this.MeterQueryParams, meterCls }).then(response => {
  855. this.facsUnbound = response.rows;
  856. this.total = response.total;
  857. this.loading = false;
  858. });
  859. listByObj(objType, meterCls, this.queryParams.areaCode).then(response => {
  860. this. facsBound = response.data;
  861. this.loading = false;
  862. });
  863. } else if (this.activeTab === 'device') {
  864. objType = 3;
  865. listDevice({ ...this.MeterQueryParams, meterCls }).then(response => {
  866. this.deviceUnbound = response.rows;
  867. this.total = response.total;
  868. this.loading = false;
  869. })
  870. listByObj(objType, meterCls, this.queryParams.areaCode).then(response => {
  871. this.deviceBound = response.data;
  872. this.loading = false;
  873. });
  874. }
  875. },
  876. /**绑定设备*/
  877. moveToDevice(row, tabType) {
  878. this.$confirm('是否确定绑定?', '提示', {
  879. confirmButtonText: '确定',
  880. cancelButtonText: '取消',
  881. type: 'warning'
  882. }).then(() => {
  883. let objType;
  884. if (tabType === 'area') {
  885. objType = 1;
  886. } else if (tabType === 'organ') {
  887. objType = 4;
  888. } else if (tabType === 'facs') {
  889. objType = 2;
  890. } else if (tabType === 'device') {
  891. objType = 3;
  892. }
  893. const index = this[`${tabType}Unbound`].indexOf(row);
  894. if (index !== -1) {
  895. this[`${tabType}Unbound`].splice(index, 1);
  896. }
  897. const boundRow = {
  898. meterDeviceName: row.deviceName,
  899. meterDevice: row.deviceCode,
  900. boundaryObj: this.queryParams.areaCode,
  901. objType: objType
  902. };
  903. this[`${tabType}Bound`].push(boundRow);
  904. this.saveByObj(tabType);
  905. }).catch(() => {});
  906. },
  907. isAlreadyBound(deviceCode, tabType) {
  908. return this[`${tabType}Bound`].some(boundDevice => boundDevice.meterDevice === deviceCode);
  909. },
  910. /**取消绑定设备*/
  911. downToDevice(row, tabType) {
  912. this.$confirm('是否取消绑定?', '提示', {
  913. confirmButtonText: '确定',
  914. cancelButtonText: '取消',
  915. type: 'warning'
  916. }).then(() => {
  917. const index = this[`${tabType}Bound`].indexOf(row);
  918. if (index !== -1) {
  919. this[`${tabType}Bound`].splice(index, 1);
  920. this[`${tabType}Unbound`].push(row);
  921. const objType = row.objType;
  922. const boundaryObj = row.boundaryObj;
  923. const meterCls = row.meterCls || (this.activeDeviceTab === 'electricMeter' ? 45 : 70);
  924. delete row.boundaryObj;
  925. delete row.meterDevice;
  926. delete row.objType;
  927. this.saveByObj(tabType, objType, boundaryObj, meterCls);
  928. }
  929. })
  930. },
  931. /**绑定保存*/
  932. saveByObj(tabType, objType = null, boundaryObj = null, meterCls = null) {
  933. const dataToSave = this[`${tabType}Bound`].map(item => {
  934. return {
  935. boundaryObj: item.boundaryObj,
  936. id: item.id,
  937. meterCls: item.meterCls || (this.activeDeviceTab === 'electricMeter' ? 45 : 70),
  938. meterDevice: item.meterDevice,
  939. objType: item.objType
  940. };
  941. });
  942. if (dataToSave.length === 0) {
  943. objType = objType || this.queryParams.objType;
  944. boundaryObj = boundaryObj || this.queryParams.areaCode;
  945. meterCls = meterCls || (this.activeDeviceTab === 'electricMeter' ? 45 : 70);
  946. } else {
  947. objType = dataToSave[0].objType;
  948. boundaryObj = dataToSave[0].boundaryObj;
  949. meterCls = dataToSave[0].meterCls;
  950. }
  951. addAllByObj(objType, meterCls, boundaryObj, dataToSave)
  952. .then(response => {
  953. if (response.code === 200) {
  954. this.$message.success('保存成功');
  955. this.getMeterData(tabType);
  956. }
  957. })
  958. },
  959. // 设施下拉框选项
  960. getFacsOptions() {
  961. const getFacsParams = {
  962. facsCategory: this.DeviceQueryParams.deviceCategory,
  963. subCategory: this.DeviceQueryParams.deviceSubCategory
  964. }
  965. listAllFacs(getFacsParams).then(response => {
  966. this.facsAllOptions = response.data
  967. })
  968. },
  969. getSubsystem() {
  970. listSubsystemAll().then(response => {
  971. this.subsystemOptions = response.data
  972. })
  973. },
  974. getAllDevProcess(subcategoryCode) {
  975. getDevProcess(subcategoryCode).then(response => {
  976. this.devOptions = response.data
  977. })
  978. },
  979. getEmsTag(tagModel) {
  980. getEmsTag(tagModel).then(response => {
  981. if (response && response.data) {
  982. this.emsTagOptions = response.data.map(item => ({
  983. label: item.tagName,
  984. value: item.tagCode,
  985. color: item.tagColor
  986. }));
  987. //颜色映射
  988. this.tagCodeToColorMap = {};
  989. this.emsTagOptions.forEach(item => {
  990. this.tagCodeToColorMap[item.value] = item.color;
  991. });
  992. }
  993. })
  994. },
  995. /**拓扑图*/
  996. showTopology(data) {
  997. this.topologyDialogVisible = true;
  998. this.$nextTick(() => {
  999. getBoundaryTreeByArea(data.areaCode).then(response => {
  1000. const topologyData = response.data;
  1001. console.log("拓扑图接口获取数据",topologyData);
  1002. this.initTreeECharts(topologyData);
  1003. });
  1004. });
  1005. },
  1006. initTreeECharts(topologyData) {
  1007. const chartDom = this.$refs.topologyChart;
  1008. const myChart = echarts.init(chartDom);
  1009. // 递归处理拓扑图数据,生成echarts需要的数据格式
  1010. const processTopologyData = (data) => {
  1011. const hasBoundDevices = data.bindElecMeterDevs && data.bindElecMeterDevs.length > 0;
  1012. const nodeColor = hasBoundDevices ? '#99e170' : '#D3D3D3';
  1013. const bindElecMeterDevsStr = hasBoundDevices
  1014. ? `绑定电表:${data.bindElecMeterDevs.join('\n ')}`
  1015. : '绑定电表:无';
  1016. return {
  1017. name: `${data.objName}\n${bindElecMeterDevsStr}`,
  1018. children: data.children ? data.children.map(child => processTopologyData(child)) : [],
  1019. value: [data.bindElecMeterDevs].flat().join(', '),
  1020. itemStyle: { // 设置节点样式
  1021. borderColor: '#D8D3D1',
  1022. borderWidth: 1,
  1023. color: nodeColor
  1024. }
  1025. };
  1026. };
  1027. // 处理根节点数据
  1028. const processedData = processTopologyData(topologyData);
  1029. // 指定图表的配置项和数据
  1030. const option = {
  1031. tooltip: {
  1032. trigger: 'item',
  1033. triggerOn: 'mousemove',
  1034. },
  1035. series: [
  1036. {
  1037. name: '拓扑图',
  1038. type: 'tree',
  1039. layout: 'orthogonal',
  1040. orient: 'TB', // 从上到下的布局
  1041. roam: true,
  1042. data: [processedData], // 直接使用传入的数据
  1043. symbol: 'rect', // 设置节点形状为长方形
  1044. symbolSize: [120, 70],
  1045. label: {
  1046. position: 'inside',
  1047. verticalAlign: 'middle',
  1048. align: 'center',
  1049. fontSize: 12,
  1050. },
  1051. leaves: {
  1052. label: {
  1053. position: 'inside', // 叶子节点标签位置在内部
  1054. verticalAlign: 'middle',
  1055. align: 'center'
  1056. }
  1057. },
  1058. expandAndCollapse: true,
  1059. animationDuration: 550,
  1060. animationDurationUpdate: 750,
  1061. itemStyle: {
  1062. borderColor: '#555',
  1063. borderWidth: 1,
  1064. },
  1065. edgeLabel: {
  1066. position: 'middle',
  1067. fontSize: 10
  1068. },
  1069. emphasis: {
  1070. itemStyle: {
  1071. borderColor: '#333',
  1072. borderWidth: 2
  1073. }
  1074. },
  1075. }
  1076. ]
  1077. };
  1078. myChart.setOption(option);
  1079. },
  1080. scrollToCenter() {
  1081. this.$nextTick(() => {
  1082. const container = this.$refs.topologyChartContainer;
  1083. container.scrollTo(container.scrollWidth / 2 - container.offsetWidth / 2, 0);
  1084. });
  1085. },
  1086. }
  1087. }
  1088. </script>
  1089. <style lang="scss" scoped>
  1090. .custom-tree-node {
  1091. flex: 1;
  1092. display: flex;
  1093. align-items: center;
  1094. justify-content: flex-start;
  1095. }
  1096. .custom-tree-node .el-button {
  1097. margin-left: 8px;
  1098. }
  1099. .topology-chart-container {
  1100. overflow-x: auto;
  1101. width: 100%;
  1102. }
  1103. .topology-chart {
  1104. width: 8000px;
  1105. height: 600px;
  1106. }
  1107. .device-tree-container {
  1108. height: calc(100vh - 50px);
  1109. overflow: auto;
  1110. }
  1111. .device-tree {
  1112. font-size: 12px;
  1113. width: 100%;
  1114. }
  1115. </style>