batchPoint.vue 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650
  1. <template>
  2. <div id="manageTenantZone">
  3. <el-row style="margin:5px 0 0 0;">
  4. <el-button style="float:left;" size="small" type="default" icon="el-icon-back" @click="goback"></el-button>
  5. <div id="buildFloor">
  6. <span class="buildFloor">建筑楼层</span>
  7. <el-cascader ref="floorCascader" placeholder="请选择" :props="props" :options="bfOptions" filterable size="small" @change="changeFloor"></el-cascader>
  8. </div>
  9. <my-cascader ref="cascader" @change="changeDevice"></my-cascader>
  10. <el-button type="primary" size="mini" @click="handleClickSave" :disabled="!isChange" style="float:right;">确认</el-button>
  11. <el-button size="mini" style="float:right;margin-right:10px;" @click="cancel">取消</el-button>
  12. </el-row>
  13. <el-row class="main-container">
  14. <el-row class="main-container-header">
  15. <el-select v-model="isPoint" @change="handleChangeIspoint" placeholder="请选择" style="width: 100px;margin-left: 10px;">
  16. <el-option v-for="item in isPointOptions" :key="item.value" :label="item.label" :value="item.value"></el-option>
  17. </el-select>
  18. <el-input
  19. style="width:250px;margin-left: 10px;"
  20. placeholder="请输入设备标识关键字"
  21. clearable
  22. suffix-icon="el-icon-search"
  23. @change="handleChangeKeyword"
  24. v-model="keyword"
  25. ></el-input>
  26. </el-row>
  27. <el-row class="main-container-box">
  28. <el-col :span="3" class="container-box-left">
  29. <el-select v-model="showName" placeholder="请选择" style="width: 100%;">
  30. <el-option v-for="item in showNameOptions" :key="item.value" :label="item.label" :value="item.value"></el-option>
  31. </el-select>
  32. <div class="equip-list">
  33. <el-scrollbar style="height:100%;" v-show="equipList.length">
  34. <ul v-infinite-scroll="getEquipData" infinite-scroll-disabled="disabled">
  35. <li class="equip-item"
  36. v-for="item in equipList"
  37. :class="{'equip-item-selected': item.selected}"
  38. :key="item.id"
  39. :title="item[showName]"
  40. @click="handleSelectIcon(item)"
  41. >
  42. <p class="equip-item-text">{{item[showName] || '--'}}</p>
  43. <i v-if="item.hasLocation" class="iconfont icon-hook equip-item-icon" style="color: #67C23A;font-size: 14px;"></i>
  44. <i v-else class="iconfont icon-cross equip-item-icon" style="color: #ccc;font-size: 14px;"></i>
  45. </li>
  46. <p style="text-align: center;" v-if="loading">加载中...</p>
  47. <p style="text-align: center;" v-if="noMore">没有更多了</p>
  48. </ul>
  49. </el-scrollbar>
  50. <div v-show="!equipList.length" class="no-data">
  51. <div class="position-icon">
  52. <i class="icon-wushuju iconfont"></i>
  53. 暂无数据
  54. </div>
  55. </div>
  56. </div>
  57. </el-col>
  58. <el-col id="canvasBox" class="container-box-right" :span="21" v-loading="canvasLoading">
  59. <div v-show="showCanvas" style="height: 100%;">
  60. <canvas :height="cadHeight" :width="cadWidth" id="floorCanvas" ref="graphy"></canvas>
  61. </div>
  62. <div class="no-data" v-show="!showCanvas">
  63. <div class="position-icon">
  64. <i class="icon-wushuju iconfont"></i>
  65. 暂无数据
  66. </div>
  67. </div>
  68. </el-col>
  69. </el-row>
  70. </el-row>
  71. <el-popover ref='popover' placement='right' trigger='manual' v-model='visible' width='400'>
  72. <div style="text-align: right;margin-bottom: 10px;">
  73. <span style="float: left;">设定Z轴高度</span>
  74. <el-link icon="el-icon-close" :underline="false" @click="visible = false"></el-link>
  75. </div>
  76. <el-row>
  77. <p>距地面高度:{{equipZ}}mm</p>
  78. </el-row>
  79. <div class="elSlider">
  80. <el-slider v-model="equipZ" :min="minHeight" :max="maxHeight" :step="1" :marks="marks"></el-slider>
  81. </div>
  82. <el-row style="text-align: right;">
  83. <el-button size="mini" @click="visible = false">取消</el-button>
  84. <el-button type="primary" size="mini" @click="handleUpdateZ">确认</el-button>
  85. </el-row>
  86. </el-popover>
  87. <span class='popStyle' :style='popoverPosition' v-popover:popover></span>
  88. </div>
  89. </template>
  90. <script>
  91. import floorCascader from "@/components/ledger/lib/floorCascader";
  92. import myCascader from "@/components/ledger/lib/cascader";
  93. import { queryEquip, updateEquip, buildingQuery } from '@/api/scan/request';
  94. import { LocationPointScene, FloorView, Opt, SpaceItem, ZoneItem } from "@saga-web/cad-engine/lib";
  95. import { SPoint, SColor } from "@saga-web/draw";
  96. import { mapGetters } from "vuex";
  97. import { SMathUtil } from "@saga-web/cad-engine/lib/utils/SMathUtil"
  98. export default {
  99. components: {
  100. floorCascader,
  101. myCascader
  102. },
  103. data() {
  104. return {
  105. visible: false,
  106. equipZ: 0,
  107. maxHeight: 99000,
  108. minHeight: -99000,
  109. popoverPosition: {
  110. top: 0,
  111. left: 0,
  112. },
  113. selectedIcon: null,
  114. buildId: "", //建筑Id
  115. floorId: "", //楼层Id
  116. floorMap: "", //楼层平面图地址
  117. floorOutline: null, //楼层平面图蒙版地址
  118. classCode: "", //设备类别
  119. isPoint: "all", //是否有坐标(全部-all、有坐标-yes、无坐标-no)
  120. keyword: "", //设备标识关键字
  121. showName: "localName", //显示名称
  122. isIndeterminate: false, //全选样式显示
  123. checkAll: false, //是否全选
  124. checkedEquip: [], //选中的设备
  125. cadWidth: 800,
  126. cadHeight: 800,
  127. view: "",
  128. scene: null,
  129. showCanvas: false, //是否有底图数据
  130. canvasLoading: false,
  131. isPointOptions: [{
  132. value: "all",
  133. label: "全部"
  134. }, {
  135. value: "yes",
  136. label: "有坐标"
  137. }, {
  138. value: "no",
  139. label: "无坐标"
  140. }],
  141. showNameOptions: [{
  142. value: "localName",
  143. label: "显示设备本地名称"
  144. }, {
  145. value: "localId",
  146. label: "显示设备本地编码"
  147. }, {
  148. value: "cADID",
  149. label: "显示图纸编码"
  150. }],
  151. equipList: [],
  152. marks: {
  153. 0: '本层地面',
  154. 99000: '本层屋顶'
  155. },
  156. loading: false,
  157. pageNumber: 1,
  158. pageSize: 50,
  159. total: 0,
  160. isChange: false, //平面图设备是否更改
  161. isMarking: false, // 标记是否选中的是无坐标的设备,true-打点,打完以后重置为false
  162. props: {
  163. value: 'id',
  164. label: 'localName',
  165. children: 'floor'
  166. }, // 建筑楼层字段绑定
  167. bfOptions: [], // 建筑楼层下拉
  168. fIdToFloor: {}, // 楼层id映射楼层对象
  169. };
  170. },
  171. computed: {
  172. ...mapGetters("layout", ["projectId", "projects"]),
  173. noMore () {
  174. return this.equipList.length >= this.total
  175. },
  176. disabled () {
  177. return this.loading || this.noMore
  178. }
  179. },
  180. created() {
  181. this.getCascader()
  182. },
  183. mounted() {
  184. this.cadWidth = document.getElementById('canvasBox').offsetWidth;
  185. this.cadHeight = document.getElementById('canvasBox').offsetHeight;
  186. // 添加监听点击事件,处理popover显隐
  187. window.addEventListener('click', this.handlePopover, false)
  188. this.$once('hook:beforeDestroy', () => {
  189. window.removeEventListener('click', this.handlePopover)
  190. })
  191. },
  192. methods: {
  193. // 返回
  194. goback() {
  195. this.confirm(() => {
  196. this.$router.push({ name: "facilityLedger" });
  197. })
  198. },
  199. getCascader() {
  200. let param = {
  201. cascade: [
  202. { name: "floor", orders: "floorSequenceID desc" }
  203. ],
  204. orders: "localName asc",
  205. pageNumber: 1,
  206. pageSize: 50
  207. }
  208. buildingQuery(param, res => {
  209. this.fIdToFloor = {}
  210. this.bfOptions = res.content.map(t => {
  211. if (t.floor && t.floor.length) {
  212. t.floor = t.floor.map(item => {
  213. this.fIdToFloor[item.id] = item;
  214. return item
  215. }).filter(it => it)
  216. }
  217. return t
  218. })
  219. })
  220. },
  221. // 二次确认
  222. confirm(callback) {
  223. if (this.isChange) {
  224. this.$confirm('平面图数据未保存,此操作将不会保存修改数据!', '提示', {
  225. confirmButtonText: '确定',
  226. cancelButtonText: '取消',
  227. type: 'warning'
  228. }).then(() => {
  229. callback();
  230. });
  231. } else {
  232. callback();
  233. }
  234. },
  235. // 点击列表设备
  236. handleSelectIcon(equip) {
  237. // 保存当前选中设备数据
  238. this.equipData = equip;
  239. // 选中样式处理
  240. this.equipList.forEach(item => {
  241. if (equip.id === item.id) {
  242. item.selected = true;
  243. } else {
  244. item.selected = false;
  245. }
  246. })
  247. // 有坐标 执行选中
  248. if (equip.hasLocation) {
  249. // 清除打点动作
  250. this.isMarking = false;
  251. // 关联平面图设备选中
  252. if (this.scene) {
  253. this.scene.iconList.forEach(item => {
  254. if (item.data.id === equip.id) {
  255. this.scene.selectContainer.setItem(item);
  256. }
  257. })
  258. }
  259. } else {
  260. // 无坐标 执行打点
  261. this.isMarking = true;
  262. }
  263. },
  264. handleClickIcon(item, events) {
  265. const e = events[0];
  266. this.selectedIcon = item;
  267. if (item.data.Z) {
  268. this.equipZ = (item.data.Z);
  269. } else {
  270. this.equipZ = 0;
  271. }
  272. this.visible = false
  273. this.$nextTick(() =>{
  274. this.popoverPosition.top = `${e.clientY}px`;
  275. this.popoverPosition.left = `${e.clientX}px`;
  276. this.visible = true;
  277. })
  278. },
  279. // 修改设备Z坐标
  280. handleUpdateZ() {
  281. if (this.selectedIcon.data.Z !== this.equipZ) {
  282. this.selectedIcon.data.Z = this.equipZ;
  283. this.isChange = true;
  284. this.visible = false;
  285. try {
  286. const str = this.selectedIcon.textItemList[0].text.split('\n');
  287. str[2] = this.equipZ
  288. this.selectedIcon.textItemList[0].text = str.join('\n');
  289. } catch (e) {
  290. console.log(e);
  291. }
  292. }
  293. },
  294. listChange(item, ev) {
  295. if (!ev[0].length) {
  296. this.visible = false;
  297. this.selectedIcon = null;
  298. }
  299. },
  300. /**
  301. * @description 处理popover显隐
  302. * 当点击位置不在canvas内部时 ( 如点击 页面空白,图例,楼层,设备设施左侧列表项时),将visible置为false,隐藏popover
  303. */
  304. handlePopover(e) {
  305. this.$nextTick(() => {
  306. if (!this.$refs.graphy.contains(e.target)) {
  307. this.visible = false
  308. }
  309. })
  310. },
  311. //获取所以主体数据---用于维护信息
  312. getEquipData() {
  313. if (this.classCode && this.floorId && (this.floorId != "noKnow" || this.floorId != "all")) {
  314. this.loading = true;
  315. let params = {
  316. filters: `classCode='${this.classCode}'`,
  317. pageSize: this.pageSize,
  318. orders: 'createTime desc, id desc',
  319. pageNumber: this.pageNumber
  320. }
  321. if (this.buildId == 'noKnow') {
  322. params.filters += `;buildingId isNull`
  323. } else if (this.buildId && this.buildId != 'all') {
  324. params.filters += `;buildingId='${this.buildId}'`
  325. }
  326. if (this.floorId == 'noKnow') {
  327. params.filters += `;floorId isNull`
  328. } else if (this.floorId && this.floorId != 'all') {
  329. params.filters += `;floorId='${this.floorId}'`
  330. }
  331. if (this.isPoint == "yes") {
  332. params.filters += `;not bimLocation isNull`
  333. } else if (this.isPoint == "no") {
  334. params.filters += `;bimLocation isNull`
  335. }
  336. if (this.keyword) {
  337. params.filters += `;localName contain'${this.keyword}' or localId contain '${this.keyword}' or cADID contain '${this.keyword}'`
  338. }
  339. this.canvasLoading = true;
  340. queryEquip(params, (res) => {
  341. this.equipList = this.equipList.concat(res.content.map(item => {item.selected = false; item.hasLocation = !!item.bimLocation; return item;}));
  342. if (this.view && this.view.scene) {
  343. res.content.forEach(item =>{
  344. if (item.locationJson && item.locationJson.x && item.locationJson.y) {
  345. this.scene.addIconText({
  346. id: item.id,
  347. X: item.locationJson.x,
  348. Y: -item.locationJson.y,
  349. Z: item.locationJson.z,
  350. text: [`${item.localName || '--'}\n${item.localId || '--'}\n${item.locationJson.z || '--'}`]
  351. });
  352. }
  353. });
  354. this.scene.iconMove(this, this.iconMove);
  355. this.scene.spaceClick(this, this.spaceClick);
  356. this.scene.iconChangeText(this, this.handleClickIcon);
  357. this.view.update();
  358. this.canvasLoading = false;
  359. this.isChange = false;
  360. }
  361. this.total = res.total;
  362. if (!this.noMore) { //判断是否有更多数据
  363. this.pageNumber++;
  364. }
  365. this.loading = false;
  366. })
  367. }
  368. },
  369. //设备移动回调
  370. iconMove(item) {
  371. this.isChange = true;
  372. },
  373. // 打点
  374. spaceClick(item, e) {
  375. if (this.isMarking) {
  376. const p = item.mapToScene(e[0].x, e[0].y)
  377. this.scene.addIconText({
  378. id: this.equipData.id,
  379. X: p.x,
  380. Y: p.y,
  381. Z: 0,
  382. text: [`${this.equipData.localName || '--'}\n${this.equipData.localId || '--'}\n0`]
  383. });
  384. // 保存按钮
  385. this.isChange = true;
  386. // 设置有坐标标识
  387. this.equipData.hasLocation = true;
  388. // 绑定事件
  389. this.scene.iconChangeText(this, this.handleClickIcon)
  390. // 重置打点动作
  391. this.isMarking = false;
  392. }
  393. // 取消选中状态
  394. this.scene && this.scene.selectContainer.clear()
  395. },
  396. //初始化条件并查询设备
  397. initParams() {
  398. this.pageNumber = 1;
  399. this.total = 0;
  400. this.equipList = [];
  401. this.clearIcon();
  402. if (this.view) {
  403. this.view.update();
  404. }
  405. this.getEquipData();
  406. },
  407. //修改楼层
  408. changeFloor(value) {
  409. this.showCanvas = false;
  410. this.clearGraphy();
  411. if (value[0]) {
  412. this.buildId = value[0];
  413. }
  414. if (value[1]) {
  415. this.floorId = value[1];
  416. this.floorData = this.fIdToFloor[value[1]];
  417. } else {
  418. this.floorId = "";
  419. this.floorData = {}
  420. }
  421. if (this.floorData.infos && this.floorData.infos.floorMap) {
  422. this.floorMap = this.floorData.infos.floorMap;
  423. this.showCanvas = true;
  424. this.getGraphy();
  425. } else {
  426. this.initParams();
  427. this.floorMap = "";
  428. }
  429. if (this.floorData.infos && this.floorData.infos.height) {
  430. this.maxHeight = this.floorData.infos.height;
  431. this.minHeight = -(this.floorData.infos.height);
  432. } else {
  433. this.maxHeight = 99000;
  434. this.minHeight = -99000;
  435. }
  436. if (this.floorData.outline && this.floorData.outline.length) {
  437. this.floorOutline = this.floorData.outline;
  438. } else {
  439. this.floorOutline = null;
  440. }
  441. },
  442. //修改设备族
  443. changeDevice(value) {
  444. if (value.code) {
  445. this.classCode = value.code;
  446. } else {
  447. this.classCode = "";
  448. }
  449. this.initParams();
  450. },
  451. //切换是否有坐标
  452. handleChangeIspoint() {
  453. this.initParams();
  454. },
  455. //检索关键字
  456. handleChangeKeyword() {
  457. this.initParams();
  458. },
  459. // 保存数据
  460. handleClickSave() {
  461. if (this.scene && this.scene.iconList.length && this.isChange) {
  462. this.isChange = false
  463. let param = {content: []}
  464. this.scene.iconList.map((item) => {
  465. if (item.data.id) {
  466. param.content.push({
  467. id: item.data.id,
  468. bimLocation: `${item.x},${-item.y},${item.data.Z}`
  469. })
  470. }
  471. })
  472. updateEquip(param, () => {
  473. this.$message({message: '保存成功!', type: 'success'});
  474. })
  475. }
  476. },
  477. // 获取底图
  478. getGraphy() {
  479. this.scene = new LocationPointScene();
  480. this.scene.selectContainer.connect('listChange', this, this.listChange)
  481. this.canvasLoading = true;
  482. const temp = this.floorMap.split('.')[1];
  483. if (temp && temp.toString().toLowerCase() == 'png' || temp.toString().toLowerCase() == 'jpg') {
  484. let x = 0;
  485. let y = 0;
  486. let scale = 1;
  487. if (this.floorData && this.floorData.infos && this.floorData.infos.floorMapRatio && this.floorData.infos.floorMapShift) {
  488. const point = JSON.parse(this.floorData.infos.floorMapShift);
  489. x = point.x;
  490. y = point.y;
  491. scale = this.floorData.infos.floorMapRatio;
  492. }
  493. this.scene.loadImg(`/image-service/common/image_get?systemId=dataPlatform&key=${this.floorMap}`, res => {
  494. this.getGraphtSuc(res)
  495. }, x, y, scale)
  496. } else {
  497. this.scene.loadUrl(`/image-service/common/file_get?systemId=revit&key=${this.floorMap}`).then(res => {
  498. this.getGraphtSuc(res)
  499. })
  500. }
  501. },
  502. getGraphtSuc(res) {
  503. this.initParams();
  504. this.canvasLoading = false;
  505. if (res == 'error') {
  506. this.FloorMap = '';
  507. console.log('数据解析异常');
  508. return;
  509. }
  510. this.view.scene = this.scene;
  511. // 蒙版
  512. if (this.floorOutline) {
  513. Opt.sceneMarkColor = new SColor('#ffffff');
  514. let newArr = this.floorOutline.map(t => {
  515. return new SPoint(t.X, t.Y);
  516. })
  517. this.scene.addSceneMark(newArr)
  518. }
  519. this.scene.isSpaceSelectable = false;
  520. this.view.fitSceneToView();
  521. // this.view.minScale = this.view.scale;
  522. // if (this.$refs.canvasFun) {
  523. // this.$refs.canvasFun.everyScale = this.view.scale;
  524. // }
  525. },
  526. //清空平面图
  527. clearGraphy() {
  528. if (this.view) {
  529. this.view.scene = null;
  530. return
  531. }
  532. this.view = new FloorView("floorCanvas");
  533. },
  534. //清空标记
  535. clearIcon() {
  536. if (this.scene)
  537. this.scene.clearIcon();
  538. },
  539. // 取消操作
  540. cancel() {
  541. this.initParams();
  542. }
  543. },
  544. watch: {
  545. projectId() {
  546. this.$router.push({ name: "facilityLedger" });
  547. }
  548. }
  549. };
  550. </script>
  551. <style lang="less" scoped>
  552. @normal-border-color: #e4e4e4;
  553. @normal-border-active-color: #e8e8e8;
  554. #manageTenantZone {
  555. position: relative;
  556. height: 100%;
  557. #buildFloor{
  558. margin-left: 10px;
  559. float: left;
  560. .buildFloor {
  561. color: #999999;
  562. font-size: 14px;
  563. margin-right: 12px;
  564. }
  565. }
  566. .main-container {
  567. margin-top: 10px;
  568. background: #fff;
  569. border: 1px solid @normal-border-color;
  570. height: calc(100% - 53px);
  571. overflow: hidden;
  572. .main-container-header {
  573. padding: 10px 0;
  574. border-bottom: 1px solid @normal-border-color;
  575. }
  576. .main-container-box {
  577. height: calc(100% - 53px);
  578. .container-box-left {
  579. height: 100%;
  580. border-right: 1px solid @normal-border-color;
  581. /deep/ .el-input__inner {
  582. border: none;
  583. border-bottom: 1px solid #DCDFE6;
  584. border-radius: 0;
  585. }
  586. .equip-list {
  587. height: calc(100% - 32px);
  588. /deep/ .el-scrollbar__wrap {
  589. overflow-x: hidden;
  590. }
  591. .equip-item {
  592. padding-left: 15px;
  593. padding-right: 10px;
  594. width: 100%;
  595. height: 32px;
  596. line-height: 32px;
  597. box-sizing: border-box;
  598. cursor: pointer;
  599. .equip-item-text {
  600. display: inline-block;
  601. width: calc(100% - 20px);
  602. overflow: hidden;
  603. white-space: nowrap;
  604. text-overflow: ellipsis;
  605. }
  606. .equip-item-icon {
  607. float: right;
  608. }
  609. }
  610. .equip-item-selected {
  611. background-color: #f5f7fa;
  612. }
  613. .equip-item:hover {
  614. background-color: #f5f7fa;
  615. }
  616. }
  617. }
  618. .container-box-right {
  619. height: 100%;
  620. }
  621. }
  622. }
  623. .popStyle {
  624. position: fixed;
  625. }
  626. }
  627. .elSlider{
  628. padding: 10px 24px;
  629. margin-bottom:15px;
  630. /deep/ .el-slider__marks-text:nth-child(2) {
  631. width: 80px;
  632. }
  633. }
  634. .no-data {
  635. height: 100%;
  636. text-align: center;
  637. box-sizing: border-box;
  638. position: relative;
  639. .position-icon {
  640. position: absolute;
  641. top: 50%;
  642. left: 50%;
  643. transform: translate(-50%, -50%);
  644. }
  645. }
  646. </style>