edit-paths.vue 6.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265
  1. <template>
  2. <div class="video">
  3. <div class="overflow">
  4. <ui-icon
  5. ctrl
  6. :type="isScenePlayIng ? 'pausecircle-fill' : 'playon_fill'"
  7. :disabled="!paths.length"
  8. @click="play"
  9. />
  10. <ui-button
  11. type="primary"
  12. @click="addPath"
  13. width="200px"
  14. :class="{ disabled: isScenePlayIng }"
  15. >
  16. 添加视角
  17. </ui-button>
  18. </div>
  19. <div class="info" v-if="paths.length">
  20. <div class="meta">
  21. <div class="length">
  22. <span>视频时长</span>
  23. </div>
  24. <div
  25. class="fun-ctrl clear"
  26. @click="deleteAll"
  27. :class="{ disabled: isScenePlayIng }"
  28. >
  29. <ui-icon type="del" />
  30. <span>清空画面</span>
  31. </div>
  32. </div>
  33. <div class="photo-list" ref="listVm">
  34. <div
  35. v-for="(path, i) in paths"
  36. class="photo"
  37. :key="path.id"
  38. :class="{ active: current === path, disabled: isScenePlayIng }"
  39. @click="changeCurrent(path)"
  40. >
  41. <ui-icon
  42. type="del"
  43. ctrl
  44. @click.stop="deletePath(path)"
  45. :class="{ disabled: isScenePlayIng }"
  46. />
  47. <img :src="path.cover" />
  48. </div>
  49. </div>
  50. </div>
  51. <p class="un-video" v-else>暂无导览</p>
  52. </div>
  53. </template>
  54. <script setup lang="ts">
  55. import { loadPack, togetherCallback } from '@/utils'
  56. import { sdk, playSceneGuide, pauseSceneGuide, isScenePlayIng } from '@/sdk'
  57. import { createGuidePath, TemploraryID, useAutoSetMode, guides, enterOld } from '@/store'
  58. import { Dialog } from 'bill/index'
  59. import { useViewStack } from '@/hook'
  60. import { nextTick, ref, toRaw, watchEffect } from 'vue'
  61. import { showRightPanoStack, showLeftCtrlPanoStack, showLeftPanoStack, showRightCtrlPanoStack } from '@/env'
  62. import type { Guide, GuidePaths, GuidePath } from '@/store'
  63. type LocalPath = GuidePath & { blob?: Blob }
  64. type LocalPaths = LocalPath[]
  65. const props = defineProps< { data: Guide }>()
  66. const paths = ref<LocalPaths>([...props.data.paths])
  67. const current = ref<LocalPath>(paths.value[0])
  68. useViewStack(() =>
  69. togetherCallback([
  70. showRightPanoStack.push(ref(false)),
  71. showLeftCtrlPanoStack.push(ref(false)),
  72. showLeftPanoStack.push(ref(false)),
  73. showRightCtrlPanoStack.push(ref(false)),
  74. ])
  75. );
  76. useAutoSetMode(paths, {
  77. save() {
  78. props.data.paths = paths.value
  79. if (props.data.id === TemploraryID) {
  80. guides.value.push(props.data)
  81. }
  82. }
  83. })
  84. setTimeout(() => {
  85. paths.value = [{
  86. id: '123a',
  87. position: {x: 1, y: 1, z: 1},
  88. target: {x: 1, y: 1, z: 1},
  89. cover: 'https://gw.alicdn.com/tps/TB1W_X6OXXXXXcZXVXXXXXXXXXX-400-400.png',
  90. speed: 1,
  91. time: 1
  92. }]
  93. }, 1000)
  94. const addPath = () => {
  95. loadPack(async () => {
  96. const dataURL = await sdk.screenshot(260, 160)
  97. const res = await fetch(dataURL)
  98. const blob = await res.blob()
  99. const pose = sdk.getPose()
  100. const index = paths.value.indexOf(current.value) + 1
  101. const path: LocalPath = Object.assign(
  102. { blob },
  103. createGuidePath({ ...pose, cover: dataURL })
  104. )
  105. paths.value.splice(index, 0, path)
  106. current.value = path
  107. })
  108. }
  109. const deletePath = async (path: GuidePath, fore: boolean = false) => {
  110. if (fore || (await Dialog.confirm('确定要删除此画面吗?'))) {
  111. const index = paths.value.indexOf(path)
  112. if (~index) {
  113. paths.value.splice(index, 1)
  114. }
  115. if (path === current.value) {
  116. current.value = paths.value[index + (index === 0 ? 0 : -1)]
  117. }
  118. }
  119. }
  120. const deleteAll = async () => {
  121. if (await Dialog.confirm('确定要清空画面吗?')) {
  122. while (paths.value.length) {
  123. deletePath(paths.value[0], true)
  124. }
  125. current.value = paths.value[0]
  126. }
  127. }
  128. const changeCurrent = (path: GuidePath) => {
  129. sdk.comeTo({ dur: 300, ...path })
  130. current.value = path
  131. }
  132. const play = () => {
  133. if (isScenePlayIng.value) {
  134. pauseSceneGuide()
  135. } else {
  136. playSceneGuide(sdk, toRaw(paths.value), (index) => current.value = paths.value[index])
  137. }
  138. }
  139. const listVm = ref<HTMLDivElement>()
  140. watchEffect(async () => {
  141. const index = paths.value.indexOf(current.value)
  142. if (~index && listVm.value) {
  143. await nextTick()
  144. const scrollWidth = listVm.value.scrollWidth / paths.value.length
  145. const centerWidth = listVm.value.offsetWidth / 2
  146. const offsetLeft = scrollWidth * index - centerWidth
  147. listVm.value.scroll({
  148. left: offsetLeft,
  149. top: 0,
  150. })
  151. }
  152. })
  153. </script>
  154. <style lang="scss" scoped>
  155. .video {
  156. position: relative;
  157. .overflow {
  158. position: absolute;
  159. left: 50%;
  160. bottom: 100%;
  161. transform: translateX(-50%);
  162. margin-bottom: 20px;
  163. display: flex;
  164. align-items: center;
  165. .icon {
  166. margin-right: 20px;
  167. color: #fff;
  168. font-size: 50px;
  169. }
  170. }
  171. .meta {
  172. font-size: 12px;
  173. border-bottom: 1px solid rgba(255,255,255,.6);
  174. padding: 10px 20px;
  175. display: flex;
  176. justify-content: space-between;
  177. .length span {
  178. margin-right: 10px;
  179. }
  180. .clear {
  181. display: flex;
  182. align-items: center;
  183. .icon {
  184. font-size: 1.4em;
  185. margin-right: 5px;
  186. }
  187. }
  188. }
  189. .photo-list {
  190. padding: 10px 20px 20px;
  191. overflow-x: auto;
  192. display: flex;
  193. .photo {
  194. cursor: pointer;
  195. flex: none;
  196. position: relative;
  197. &.active {
  198. outline: 2px solid var(--colors-primary-base);
  199. }
  200. .icon {
  201. position: absolute;
  202. right: 10px;
  203. top: 10px;
  204. width: 24px;
  205. font-size: 12px;
  206. height: 24px;
  207. background-color: rgba(0,0,0,0.6);
  208. color: rgba(255,255,255,.6);
  209. display: flex;
  210. align-items: center;
  211. justify-content: center;
  212. cursor: pointer;
  213. border-radius: 50%;
  214. }
  215. &:not(:last-child) {
  216. margin-right: 10px;
  217. }
  218. img {
  219. width: 230px;
  220. height: 160px;
  221. display: block;
  222. }
  223. }
  224. }
  225. }
  226. .un-video {
  227. height: 100px;
  228. line-height: 100px;
  229. text-align: center;
  230. color: rgba(255,255,255,0.6);
  231. font-size: 1.2em;
  232. }
  233. </style>