|
@@ -0,0 +1,249 @@
|
|
|
+<template>
|
|
|
+ <div class="video">
|
|
|
+ <div class="overflow">
|
|
|
+ <ui-icon
|
|
|
+ ctrl
|
|
|
+ :type="isScenePlayIng ? 'pausecircle-fill' : 'playon_fill'"
|
|
|
+ :disabled="!paths.length"
|
|
|
+ @click="play"
|
|
|
+ />
|
|
|
+ <ui-button
|
|
|
+ type="primary"
|
|
|
+ @click="addPath"
|
|
|
+ width="200px"
|
|
|
+ :class="{ disabled: isScenePlayIng }"
|
|
|
+ >
|
|
|
+ 添加视角
|
|
|
+ </ui-button>
|
|
|
+ </div>
|
|
|
+ <div class="info" v-if="paths.length">
|
|
|
+ <div class="meta">
|
|
|
+ <div class="length">
|
|
|
+ <span>视频时长</span>
|
|
|
+ </div>
|
|
|
+ <div
|
|
|
+ class="fun-ctrl clear"
|
|
|
+ @click="deleteAll"
|
|
|
+ :class="{ disabled: isScenePlayIng }"
|
|
|
+ >
|
|
|
+ <ui-icon type="del" />
|
|
|
+ <span>清空画面</span>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <div class="photo-list" ref="listVm">
|
|
|
+ <div
|
|
|
+ v-for="(path, i) in paths"
|
|
|
+ class="photo"
|
|
|
+ :key="path.id"
|
|
|
+ :class="{ active: current === path, disabled: isScenePlayIng }"
|
|
|
+ @click="changeCurrent(path)"
|
|
|
+ >
|
|
|
+ <ui-icon
|
|
|
+ type="del"
|
|
|
+ ctrl
|
|
|
+ @click.stop="deletePath(path)"
|
|
|
+ :class="{ disabled: isScenePlayIng }"
|
|
|
+ />
|
|
|
+ <img :src="path.cover" />
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ <p class="un-video" v-else>暂无导览</p>
|
|
|
+ </div>
|
|
|
+</template>
|
|
|
+
|
|
|
+<script setup lang="ts">
|
|
|
+import { loadPack, togetherCallback } from '@/utils'
|
|
|
+import { sdk, playSceneGuide, pauseSceneGuide, isScenePlayIng } from '@/sdk'
|
|
|
+import { createGuidePath, leave, sysBus, useAutoSetMode } from '@/store'
|
|
|
+import { Dialog } from 'bill/index'
|
|
|
+import { useViewStack } from '@/hook'
|
|
|
+import { nextTick, ref, toRaw, watchEffect } from 'vue'
|
|
|
+import { showRightPanoStack, showLeftCtrlPanoStack, showLeftPanoStack, showRightCtrlPanoStack } from '@/env'
|
|
|
+
|
|
|
+import type { GuidePaths, GuidePath } from '@/store'
|
|
|
+
|
|
|
+type LocalPath = GuidePath & { blob?: Blob }
|
|
|
+type LocalPaths = LocalPath[]
|
|
|
+
|
|
|
+const props = defineProps< { data: GuidePaths }>()
|
|
|
+const paths = ref<LocalPaths>([...props.data])
|
|
|
+const current = ref<LocalPath>(paths.value[0])
|
|
|
+
|
|
|
+useViewStack(() =>
|
|
|
+ togetherCallback([
|
|
|
+ showRightPanoStack.push(ref(false)),
|
|
|
+ showLeftCtrlPanoStack.push(ref(false)),
|
|
|
+ showLeftPanoStack.push(ref(false)),
|
|
|
+ showRightCtrlPanoStack.push(ref(false)),
|
|
|
+ ])
|
|
|
+);
|
|
|
+
|
|
|
+useAutoSetMode(paths, {
|
|
|
+ save() {
|
|
|
+ sysBus.on('save', () => setTimeout(leave), { last: true })
|
|
|
+ }
|
|
|
+})
|
|
|
+
|
|
|
+const addPath = () => {
|
|
|
+ loadPack(async () => {
|
|
|
+ const dataURL = await sdk.screenshot(260, 160)
|
|
|
+ const res = await fetch(dataURL)
|
|
|
+ const blob = await res.blob()
|
|
|
+
|
|
|
+ const pose = sdk.getPose()
|
|
|
+ const index = paths.value.indexOf(current.value) + 1
|
|
|
+ const path: LocalPath = Object.assign(
|
|
|
+ { blob },
|
|
|
+ createGuidePath({ ...pose, cover: dataURL })
|
|
|
+ )
|
|
|
+ paths.value.splice(index, 0, path)
|
|
|
+ current.value = path
|
|
|
+ })
|
|
|
+}
|
|
|
+
|
|
|
+const deletePath = async (path: GuidePath, fore: boolean = false) => {
|
|
|
+ if (fore || (await Dialog.confirm('确定要删除此画面吗?'))) {
|
|
|
+ const index = paths.value.indexOf(path)
|
|
|
+ if (~index) {
|
|
|
+ paths.value.splice(index, 1)
|
|
|
+ }
|
|
|
+ if (path === current.value) {
|
|
|
+ current.value = paths.value[index + (index === 0 ? 0 : -1)]
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+const deleteAll = async () => {
|
|
|
+ if (await Dialog.confirm('确定要清空画面吗?')) {
|
|
|
+ while (paths.value.length) {
|
|
|
+ deletePath(paths.value[0], true)
|
|
|
+ }
|
|
|
+ current.value = paths.value[0]
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+const changeCurrent = (path: GuidePath) => {
|
|
|
+ sdk.comeTo({ dur: 300, ...path })
|
|
|
+ current.value = path
|
|
|
+}
|
|
|
+
|
|
|
+const play = () => {
|
|
|
+ if (isScenePlayIng.value) {
|
|
|
+ pauseSceneGuide()
|
|
|
+ } else {
|
|
|
+ playSceneGuide(sdk, toRaw(paths.value), (index) => current.value = paths.value[index])
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+const listVm = ref<HTMLDivElement>()
|
|
|
+watchEffect(async () => {
|
|
|
+ const index = paths.value.indexOf(current.value)
|
|
|
+ if (~index && listVm.value) {
|
|
|
+ await nextTick()
|
|
|
+ const scrollWidth = listVm.value.scrollWidth / paths.value.length
|
|
|
+ const centerWidth = listVm.value.offsetWidth / 2
|
|
|
+ const offsetLeft = scrollWidth * index - centerWidth
|
|
|
+
|
|
|
+ listVm.value.scroll({
|
|
|
+ left: offsetLeft,
|
|
|
+ top: 0,
|
|
|
+ })
|
|
|
+ }
|
|
|
+})
|
|
|
+</script>
|
|
|
+
|
|
|
+<style lang="scss" scoped>
|
|
|
+.video {
|
|
|
+ position: relative;
|
|
|
+
|
|
|
+ .overflow {
|
|
|
+ position: absolute;
|
|
|
+ left: 50%;
|
|
|
+ bottom: 100%;
|
|
|
+ transform: translateX(-50%);
|
|
|
+ margin-bottom: 20px;
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+
|
|
|
+ .icon {
|
|
|
+ margin-right: 20px;
|
|
|
+ color: #fff;
|
|
|
+ font-size: 50px;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ .meta {
|
|
|
+ font-size: 12px;
|
|
|
+ border-bottom: 1px solid rgba(255,255,255,.6);
|
|
|
+ padding: 10px 20px;
|
|
|
+ display: flex;
|
|
|
+ justify-content: space-between;
|
|
|
+
|
|
|
+ .length span {
|
|
|
+ margin-right: 10px;
|
|
|
+ }
|
|
|
+
|
|
|
+ .clear {
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ .icon {
|
|
|
+ font-size: 1.4em;
|
|
|
+ margin-right: 5px;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+ .photo-list {
|
|
|
+ padding: 10px 20px 20px;
|
|
|
+ overflow-x: auto;
|
|
|
+ display: flex;
|
|
|
+
|
|
|
+ .photo {
|
|
|
+ cursor: pointer;
|
|
|
+ flex: none;
|
|
|
+ position: relative;
|
|
|
+
|
|
|
+ &.active {
|
|
|
+ outline: 2px solid var(--colors-primary-base);
|
|
|
+ }
|
|
|
+
|
|
|
+ .icon {
|
|
|
+ position: absolute;
|
|
|
+ right: 10px;
|
|
|
+ top: 10px;
|
|
|
+ width: 24px;
|
|
|
+ font-size: 12px;
|
|
|
+ height: 24px;
|
|
|
+ background-color: rgba(0,0,0,0.6);
|
|
|
+ color: rgba(255,255,255,.6);
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ justify-content: center;
|
|
|
+ cursor: pointer;
|
|
|
+ border-radius: 50%;
|
|
|
+ }
|
|
|
+
|
|
|
+ &:not(:last-child) {
|
|
|
+ margin-right: 10px;
|
|
|
+ }
|
|
|
+
|
|
|
+ img {
|
|
|
+ width: 230px;
|
|
|
+ height: 160px;
|
|
|
+ display: block;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|
|
|
+.un-video {
|
|
|
+ height: 100px;
|
|
|
+ line-height: 100px;
|
|
|
+ text-align: center;
|
|
|
+ color: rgba(255,255,255,0.6);
|
|
|
+ font-size: 1.2em;
|
|
|
+}
|
|
|
+</style>
|
|
|
+
|