Viewer.vue 21 KB

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