Viewer.vue 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681
  1. <template>
  2. <AppHeader v-show="!fscChecked" :project="project" :show-adjust="showAdjust" @update="onPointsUpdate" />
  3. <article>
  4. <main>
  5. <div class="split" v-if="source">
  6. <iframe ref="sourceFrame" :src="sourceURL" frameborder="0" @load="onLoadSource"></iframe>
  7. <div class="tools" v-show="!showAdjust && !fscChecked && (dbsChecked || (!target && !bimChecked))">
  8. <div class="item-date">
  9. <calendar name="source" :count="scenes.length" :value="sourceDate" :highlighted="sourceDays" @selected="onSelected" @pick="onPickDate" @prev="onPrevDate" @next="onNextDate"></calendar>
  10. </div>
  11. <div class="item-mode" v-if="source.type == 2">
  12. <div class="iconfont icon-show_roaming" :class="{ active: mode == 0 }" @click="onModeChange(0)"></div>
  13. <div class="iconfont icon-show_plane" :class="{ active: mode == 1 }" @click="onModeChange(1)"></div>
  14. </div>
  15. </div>
  16. <div class="points" v-if="showAdjust">
  17. <div :class="{ active: points.p1 }" @click="onP1Click('left')">
  18. <i class="iconfont" :class="[points.p1 ? 'icon-positioning01' : 'icon-positioning02']"></i>
  19. <span>P1</span>
  20. </div>
  21. <div :class="{ active: points.p2 }" @click="onP2Click('left')">
  22. <i class="iconfont" :class="[points.p2 ? 'icon-positioning01' : 'icon-positioning02']"></i>
  23. <span>P2</span>
  24. </div>
  25. </div>
  26. </div>
  27. <div class="split" v-if="target">
  28. <iframe ref="targetFrame" :src="targetURL" frameborder="0" @load="onLoadTarget"></iframe>
  29. <div class="tools" v-show="!fscChecked && !bimChecked">
  30. <div class="item-date target">
  31. <calendar name="target" :value="targetDate" :highlighted="targetDays" @selected="onSelected" @pick="onPickDate" @prev="onPrevDate" @next="onNextDate"></calendar>
  32. </div>
  33. </div>
  34. <div class="points" v-if="showAdjust">
  35. <div :class="{ active: points.p1 }" @click="onP1Click('right')">
  36. <i class="iconfont" :class="[points.p1 ? 'icon-positioning01' : 'icon-positioning02']"></i>
  37. <span>P1</span>
  38. </div>
  39. <div :class="{ active: points.p2 }" @click="onP1Click('right')">
  40. <i class="iconfont" :class="[points.p2 ? 'icon-positioning01' : 'icon-positioning02']"></i>
  41. <span>P2</span>
  42. </div>
  43. </div>
  44. </div>
  45. <div class="model" v-show="!showAdjust">
  46. <div class="bim" :class="{ active: bimChecked, disable: project && !project.bimData }" v-show="!fscChecked">
  47. <div @click="onBimChecked">
  48. <i class="iconfont icon-BIM"></i>
  49. <span>BIM</span>
  50. </div>
  51. </div>
  52. <div class="dbs" :class="{ active: dbsChecked, disable: scenes.length < 2 && !bimChecked }" @click="onDbsChecked" v-show="!fscChecked">
  53. <i class="iconfont icon-split_screen"></i>
  54. <span>分屏</span>
  55. </div>
  56. <div class="fsc" :class="{ active: fscChecked }" @click="onFscChecked">
  57. <i class="iconfont" :class="[fscChecked ? 'icon-full_screen_selected' : 'icon-full_screen_normal']"></i>
  58. <span>全屏</span>
  59. </div>
  60. </div>
  61. </main>
  62. <Toast v-if="showTips" type="warn" :content="showTips" :close="() => (showTips = null)" />
  63. </article>
  64. </template>
  65. <script setup>
  66. import { ref, onMounted, computed, nextTick } from 'vue'
  67. import { http } from '@/utils/request'
  68. import browser from '@/utils/browser'
  69. import Toast from '@/components/dialog/Toast'
  70. import AppHeader from '@/components/header'
  71. import Calendar from '@/components/calendar'
  72. import sync, { loadSourceScene, loadTargetScene, setPanoWithBim} from '@/utils/sync'
  73. // 是否BIM模式
  74. const showBim = ref(browser.urlHasValue('bim'))
  75. // 是否校准模式
  76. const showSplit = ref(browser.urlHasValue('split'))
  77. const showAdjust = ref(browser.urlHasValue('adjust'))
  78. const bimChecked = ref()
  79. const dbsChecked = ref(null)
  80. const fscChecked = ref(null)
  81. const datepickName = ref(null)
  82. const sourceFrame = ref(null)
  83. const targetFrame = ref(null)
  84. const mode = ref(0)
  85. const source = ref(null)
  86. const target = ref(null)
  87. const project = ref(null)
  88. const points = ref({ p1: false, p2: false })
  89. const showTips = ref(null)
  90. const scenes = computed(() => {
  91. if (!project.value) {
  92. return []
  93. }
  94. return project.value.sceneList.map(item => {
  95. return {
  96. num: item.num,
  97. type: item.type,
  98. createTime: item.createTime,
  99. }
  100. })
  101. })
  102. const sourceURL = computed(() => {
  103. if (bimChecked.value && !dbsChecked.value) {
  104. return `smart-bim.html?m=${project.value.bimData.bimOssFilePath}`
  105. }
  106. if (source.value.type < 2) {
  107. // 看看、看见场景
  108. return `smart-kankan.html?m=${source.value.num}&dev`
  109. } else {
  110. // 深时场景
  111. return `smart-laser.html?m=${source.value.num}&dev`
  112. }
  113. })
  114. const targetURL = computed(() => {
  115. if (bimChecked.value) {
  116. return `smart-bim.html?m=${project.value.bimData.bimOssFilePath}`
  117. }
  118. if (source.value.type < 2) {
  119. // 看看、看见场景
  120. return `smart-kankan.html?m=${target.value.num}&dev`
  121. } else {
  122. // 深时场景
  123. return `smart-laser.html?m=${target.value.num}&dev`
  124. }
  125. })
  126. const sourceDate = computed(() => {
  127. if (source.value) {
  128. console.log(source.value.createTime.toDate())
  129. return source.value.createTime.toDate()
  130. }
  131. })
  132. const targetDate = computed(() => {
  133. if (target.value) {
  134. return target.value.createTime.toDate()
  135. }
  136. })
  137. const sourceDays = computed(() => {
  138. let dates = []
  139. if (datepickName.value == 'source') {
  140. if (dbsChecked.value) {
  141. // 分屏模式
  142. if (bimChecked.value) {
  143. // BIM模式
  144. dates = scenes.value.map(item => item.createTime.toDate())
  145. } else {
  146. // 非BIM模式
  147. dates = scenes.value.filter(item => item.createTime != target.value.createTime).map(item => item.createTime.toDate())
  148. }
  149. } else {
  150. // 非分屏模式
  151. dates = scenes.value.map(item => item.createTime.toDate())
  152. }
  153. }
  154. return {
  155. dates: dates,
  156. }
  157. })
  158. const targetDays = computed(() => {
  159. let dates = []
  160. if (datepickName.value == 'target') {
  161. dates = scenes.value.filter(item => item.createTime != source.value.createTime).map(item => item.createTime.toDate())
  162. }
  163. return {
  164. dates: dates,
  165. }
  166. })
  167. const onLoadSource = () => {
  168. if (bimChecked.value && !dbsChecked.value) {
  169. // BIM单屏模式
  170. return
  171. }
  172. loadSourceScene(sourceFrame, source.value.type < 2 ? 'kankan' : 'laser')
  173. }
  174. const onLoadTarget = () => {
  175. if (bimChecked.value) {
  176. loadTargetScene(targetFrame, 'bim')
  177. } else {
  178. loadTargetScene(targetFrame, target.value.type < 2 ? 'kankan' : 'laser', mode.value)
  179. }
  180. }
  181. const onModeChange = targetMode => {
  182. window.Log('changeMode:' + targetMode, '#3cffff')
  183. if (sync.sourceInst) {
  184. sync.sourceInst.loaded.then(sdk => sdk.scene.changeMode(targetMode))
  185. mode.value = targetMode
  186. }
  187. }
  188. const onPickDate = name => {
  189. datepickName.value = name
  190. }
  191. const onSelected = data => {
  192. if (!data.payload) {
  193. return
  194. }
  195. let { name, payload } = data
  196. let date = payload.format('YYYY-mm-dd')
  197. let dates = (name == 'source' ? sourceDays : targetDays).value.dates.map(item => item.format('YYYY-mm-dd'))
  198. if (dates.indexOf(date) != -1) {
  199. let time = payload.format('YYYY-mm-dd HH:MM')
  200. let find = scenes.value.find(c => c.createTime.indexOf(time) != -1)
  201. if (find) {
  202. if (name == 'source') {
  203. if (find.num != source.value.num) {
  204. source.value = find
  205. }
  206. } else {
  207. if (find.num != target.value.num) {
  208. target.value = find
  209. }
  210. }
  211. }
  212. }
  213. datepickName.value = null
  214. }
  215. const onPrevDate = name => {
  216. let scene = null
  217. if (name == 'source') {
  218. scene = source
  219. } else {
  220. scene = target
  221. }
  222. let index = scenes.value.findIndex(item => item.num == scene.value.num)
  223. if (index == -1) {
  224. return
  225. }
  226. if (--index == -1) {
  227. index = scenes.value.length - 1
  228. }
  229. if (target.value) {
  230. // 分屏模式判断
  231. if (name == 'source') {
  232. if (scenes.value[index].createTime == target.value.createTime) {
  233. index--
  234. }
  235. } else {
  236. if (scenes.value[index].createTime == source.value.createTime) {
  237. index--
  238. }
  239. }
  240. if (index == -1) {
  241. index = scenes.value.length - 1
  242. }
  243. }
  244. scene.value = scenes.value[index]
  245. }
  246. const onNextDate = name => {
  247. let scene = null
  248. if (name == 'source') {
  249. scene = source
  250. } else {
  251. scene = target
  252. }
  253. let index = scenes.value.findIndex(item => item.num == scene.value.num)
  254. if (index == -1) {
  255. return
  256. }
  257. if (++index > scenes.value.length - 1) {
  258. index = 0
  259. }
  260. if (target.value) {
  261. // 分屏模式判断
  262. if (name == 'source') {
  263. if (scenes.value[index].createTime == target.value.createTime) {
  264. index++
  265. }
  266. } else {
  267. if (scenes.value[index].createTime == source.value.createTime) {
  268. index++
  269. }
  270. }
  271. if (index > scenes.value.length - 1) {
  272. index = 0
  273. }
  274. }
  275. scene.value = scenes.value[index]
  276. }
  277. // bim点击
  278. const onBimChecked = () => {
  279. if (!project.value || !project.value.bimData) {
  280. showTips.value = '未发现BIM文件'
  281. return
  282. }
  283. if (bimChecked.value) {
  284. bimChecked.value = false
  285. if (dbsChecked.value) {
  286. // 如果没有多场景数据,取消分屏
  287. if (scenes.value.length < 2) {
  288. onDbsChecked()
  289. return
  290. }
  291. // 判断是否分屏状态
  292. let index = scenes.value.findIndex(item => item.num == source.value.num)
  293. if (index == -1) {
  294. return
  295. }
  296. if (++index > scenes.value.length - 1) {
  297. index = 0
  298. }
  299. target.value = scenes.value[index]
  300. }
  301. } else {
  302. bimChecked.value = true
  303. }
  304. }
  305. // 分屏点击
  306. const onDbsChecked = () => {
  307. if (!dbsChecked.value && scenes.value.length < 2 && !bimChecked.value) {
  308. showTips.value = '未发现对比场景'
  309. return
  310. }
  311. dbsChecked.value = !dbsChecked.value
  312. if (dbsChecked.value) {
  313. if (bimChecked.value) {
  314. // BIM分屏
  315. source.value = scenes.value[0]
  316. target.value = project.value.bimData
  317. } else {
  318. // 四维看看、激光场景分屏
  319. let index = scenes.value.findIndex(item => item.num == source.value.num)
  320. if (index == -1) {
  321. return
  322. }
  323. if (++index > scenes.value.length - 1) {
  324. index = 0
  325. }
  326. target.value = scenes.value[index]
  327. }
  328. } else {
  329. target.value = null
  330. //targetApp = null
  331. views.clear()
  332. }
  333. }
  334. // 全屏点击
  335. const onFscChecked = () => {
  336. let element = document.documentElement
  337. fscChecked.value = !fscChecked.value
  338. if (fscChecked.value) {
  339. if (element.requestFullscreen) {
  340. element.requestFullscreen()
  341. } else if (element.webkitRequestFullScreen) {
  342. element.webkitRequestFullScreen()
  343. } else if (element.mozRequestFullScreen) {
  344. element.mozRequestFullScreen()
  345. } else if (element.msRequestFullscreen) {
  346. element.msRequestFullscreen()
  347. }
  348. } else {
  349. if (document.exitFullscreen) {
  350. document.exitFullscreen()
  351. } else if (document.webkitCancelFullScreen) {
  352. document.webkitCancelFullScreen()
  353. } else if (document.mozCancelFullScreen) {
  354. document.mozCancelFullScreen()
  355. } else if (document.msExitFullscreen) {
  356. document.msExitFullscreen()
  357. }
  358. }
  359. }
  360. const onPointsUpdate = type => {
  361. points.value[type] = true
  362. }
  363. const onP1Click = (type) =>{
  364. if(!points.value.p1){
  365. showTips.value = '您还未选择关联位置'
  366. return
  367. }
  368. // todo 定位
  369. alert(type)
  370. }
  371. const onP2Click = (type) =>{
  372. if(!points.value.p2){
  373. showTips.value = '您还未选择关联位置'
  374. return
  375. }
  376. // todo 定位
  377. alert(type)
  378. }
  379. onMounted(() => {
  380. const num = browser.valueFromUrl('m') || ''
  381. const projectId = browser.valueFromUrl('projectId') || 1
  382. http.get(`smart-site/project/info?projectId=${projectId}`)
  383. .then(response => {
  384. if (response.success) {
  385. if (response.data) {
  386. if (response.data.panos) {
  387. try {
  388. response.data.panos = JSON.parse(response.data.panos)
  389. points.value.p1 = true
  390. points.value.p2 = true
  391. setPanoWithBim(response.data.panos)
  392. } catch (error) {
  393. console.error(error)
  394. }
  395. }
  396. project.value = response.data
  397. if (project.value.sceneList.length) {
  398. if (num) {
  399. source.value = project.value.sceneList.find(c => c.num == num)
  400. }
  401. if (!source.value) {
  402. source.value = project.value.sceneList[0]
  403. }
  404. if (showAdjust.value || showSplit.value) {
  405. onBimChecked()
  406. nextTick(() => onDbsChecked())
  407. } else if (showBim.value) {
  408. onBimChecked()
  409. }
  410. }
  411. }
  412. } else {
  413. showTips.value = response.message
  414. }
  415. })
  416. .catch(() => {
  417. showTips.value = '服务器连接失败'
  418. })
  419. })
  420. </script>
  421. <style lang="scss" scoped>
  422. article {
  423. display: flex;
  424. width: 100%;
  425. height: 100%;
  426. overflow: hidden;
  427. }
  428. aside {
  429. width: 160px;
  430. height: 100%;
  431. background-color: rgba(0, 0, 0, 0.8);
  432. h4 {
  433. font-size: 16px;
  434. text-align: center;
  435. }
  436. ul {
  437. margin-top: 20px;
  438. }
  439. li {
  440. margin: 0;
  441. padding: 0;
  442. font-size: 16px;
  443. margin-left: 20px;
  444. cursor: pointer;
  445. &:hover,
  446. &.active {
  447. color: #00c8af;
  448. }
  449. }
  450. }
  451. main {
  452. flex: 1;
  453. width: 100%;
  454. height: 100%;
  455. position: relative;
  456. display: flex;
  457. &.full {
  458. .split {
  459. width: 50%;
  460. }
  461. }
  462. iframe {
  463. position: absolute;
  464. left: 0;
  465. top: 0;
  466. z-index: 1000;
  467. width: 100%;
  468. height: 100%;
  469. }
  470. .split {
  471. margin-left: 2px;
  472. width: 100%;
  473. height: 100%;
  474. overflow: hidden;
  475. position: relative;
  476. &:first-child,
  477. &:last-child {
  478. margin-left: 0;
  479. }
  480. .points {
  481. position: absolute;
  482. left: 50%;
  483. top: 64px;
  484. z-index: 9999;
  485. display: flex;
  486. transform: translateX(-50%);
  487. div {
  488. cursor:pointer;
  489. margin-left: 20px;
  490. width: 70px;
  491. height: 88px;
  492. background: rgba(27, 27, 28, 0.8);
  493. box-shadow: 0px 4px 4px 0px rgba(0, 0, 0, 0.25), inset 0px 0px 0px 2px rgba(255, 255, 255, 0.1);
  494. border-radius: 8px 8px 8px 8px;
  495. opacity: 1;
  496. border: 1px solid #000000;
  497. display: flex;
  498. flex-direction: column;
  499. align-items: center;
  500. justify-content: center;
  501. &.active {
  502. color: #0076f6;
  503. }
  504. i {
  505. font-size: 24px;
  506. }
  507. span {
  508. font-size: 16px;
  509. margin-top: 10px;
  510. }
  511. }
  512. }
  513. }
  514. .model {
  515. position: absolute;
  516. left: 50px;
  517. bottom: 40px;
  518. z-index: 1000;
  519. display: flex;
  520. flex-direction: column;
  521. > div {
  522. cursor: pointer;
  523. width: 50px;
  524. height: 50px;
  525. margin-top: 16px;
  526. background: rgba(27, 27, 28, 0.8);
  527. box-shadow: 0px 4px 4px 0px rgba(0, 0, 0, 0.25);
  528. border-radius: 47px 47px 47px 47px;
  529. border: 1px solid #000000;
  530. display: flex;
  531. flex-direction: column;
  532. align-items: center;
  533. justify-content: center;
  534. > div {
  535. width: 100%;
  536. height: 100%;
  537. display: flex;
  538. flex-direction: column;
  539. align-items: center;
  540. justify-content: center;
  541. }
  542. &.active {
  543. color: #0076f6;
  544. }
  545. &.disable {
  546. opacity: 0.5;
  547. }
  548. span {
  549. font-size: 12px;
  550. padding-top: 1px;
  551. transform: scale(0.8);
  552. }
  553. }
  554. }
  555. .tools {
  556. position: absolute;
  557. width: 100%;
  558. bottom: 40px;
  559. z-index: 2000;
  560. display: flex;
  561. justify-content: center;
  562. align-items: center;
  563. color: #fff;
  564. pointer-events: none;
  565. > div {
  566. pointer-events: all;
  567. }
  568. .item-mode {
  569. height: 50px;
  570. background: rgba(27, 27, 28, 0.8);
  571. box-shadow: 0px 4px 4px 0px rgba(0, 0, 0, 0.25);
  572. border-radius: 47px 47px 47px 47px;
  573. border: 1px solid #000000;
  574. display: flex;
  575. justify-content: center;
  576. align-items: center;
  577. margin-left: 10px;
  578. margin-right: 10px;
  579. font-size: 16px;
  580. padding: 0 16px;
  581. div {
  582. cursor: pointer;
  583. font-size: 18px;
  584. }
  585. div:last-child {
  586. margin-left: 20px;
  587. }
  588. div.active {
  589. color: #0076f6;
  590. }
  591. }
  592. }
  593. }
  594. </style>
  595. <style lang="scss">
  596. #app {
  597. background-color: rgba(0, 0, 0, 0.8);
  598. display: flex;
  599. flex-direction: column;
  600. }
  601. .vuejs3-datepicker__calendar {
  602. background: rgba(27, 27, 28, 0.8);
  603. box-shadow: 0px 4px 4px 0px rgba(0, 0, 0, 0.25);
  604. border-radius: 4px 4px 4px 4px;
  605. opacity: 1;
  606. border: 1px solid #000000;
  607. filter: blur(undefinedpx);
  608. color: #fff;
  609. }
  610. .vuejs3-datepicker__calendar-topbar {
  611. display: none !important;
  612. }
  613. .vuejs3-datepicker__calendar header .up:not(.disabled):hover {
  614. background: rgba(0, 0, 0, 0.3);
  615. color: #fff;
  616. }
  617. .vuejs3-datepicker__calendar header .prev:after {
  618. border-left: 1px solid #fff;
  619. border-bottom: 1px solid #fff;
  620. }
  621. .vuejs3-datepicker__calendar header .prev:not(.disabled):hover {
  622. background: rgba(0, 0, 0, 0.3);
  623. }
  624. .vuejs3-datepicker__calendar header .next:after {
  625. border-top: 1px solid #fff;
  626. border-right: 1px solid #fff;
  627. }
  628. .vuejs3-datepicker__calendar header .next:not(.disabled):hover {
  629. background: rgba(0, 0, 0, 0.3);
  630. }
  631. .highlighted {
  632. background: #4ebde1 !important;
  633. }
  634. .selected {
  635. background: #0076f6 !important;
  636. }
  637. </style>