123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352 |
- <template>
- <div class="video">
- <div class="overflow">
- <ui-icon
- ctrl
- :type="isScenePlayIng ? 'pause' : 'preview'"
- :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>{{paths.reduce((t, c) => t + c.time, 0).toFixed(1)}}s
- </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">
- <template v-for="(path, i) in paths" :key="path.id">
- <div
- class="photo"
- :class="{ active: current === path, disabled: isScenePlayIng }"
- @click="changeCurrent(path)"
- >
- <ui-icon
- type="del"
- ctrl
- @click.stop="deletePath(path)"
- :class="{ disabled: isScenePlayIng }"
- />
- <img :src="getResource(getFileUrl(path.cover))" />
- </div>
- <div class="set-phone-attr" v-if="i !== paths.length - 1">
- <ui-input
- type="number"
- width="54px"
- height="26px"
- :modelValue="path.speed"
- @update:modelValue="(val: number) => updatePathInfo(i, { speed: val })"
- :ctrl="false"
- :min="0.1"
- :max="10"
- >
- <template #icon><span>m/s</span></template>
- </ui-input>
- <ui-input
- type="number"
- width="54px"
- height="26px"
- :modelValue="path.time"
- @update:modelValue="(val: number) => updatePathInfo(i, { time: val })"
- :ctrl="false"
- :min="0.1"
- :max="20"
- class="time"
- >
- <template #icon><span class="time">s</span></template>
- </ui-input>
- </div>
- </template>
-
- </div>
- </div>
- <p class="un-video" v-else>暂无导览</p>
- </div>
- </template>
- <script setup lang="ts">
- import { loadPack, togetherCallback, getFileUrl, asyncTimeout } from '@/utils'
- import { sdk, playSceneGuide, pauseSceneGuide, isScenePlayIng } from '@/sdk'
- import { createGuidePath, isTemploraryID, useAutoSetMode, guides, getGuidePaths, guidePaths } from '@/store'
- import { Dialog, Message } from 'bill/index'
- import { useViewStack } from '@/hook'
- import { nextTick, ref, toRaw, watchEffect } from 'vue'
- import { showRightPanoStack, showLeftCtrlPanoStack, showLeftPanoStack, showRightCtrlPanoStack, getResource } from '@/env'
- import type { Guide, GuidePaths, GuidePath } from '@/store'
- import type { CalcPathProps } from '@/sdk'
- const props = defineProps< { data: Guide }>()
- const paths = ref<GuidePaths>(getGuidePaths(props.data))
- const current = ref<GuidePath>(paths.value[0])
- const updatePathInfo = (index: number, calcInfo: CalcPathProps[1]) => {
- const info = sdk.calcPathInfo(
- paths.value.slice(index, index + 2) as any,
- calcInfo
- )
- Object.assign(paths.value[index], info)
- }
- useViewStack(() =>
- togetherCallback([
- showRightPanoStack.push(ref(false)),
- showLeftCtrlPanoStack.push(ref(false)),
- showLeftPanoStack.push(ref(false)),
- showRightCtrlPanoStack.push(ref(false)),
- ])
- );
- useAutoSetMode(paths, {
- save() {
- if (!paths.value.length) {
- Dialog.alert('无法保存空路径导览!')
- throw '无法保存空路径导览!'
- }
- const oldPaths = getGuidePaths(props.data)
- props.data.cover = paths.value[0].cover
- guidePaths.value = guidePaths.value
- .filter(path => !oldPaths.includes(path))
- .concat(paths.value)
- if (isTemploraryID(props.data.id)) {
- console.error("现在才保存?")
- guides.value.push(props.data)
- }
- },
- }, false)
- 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: GuidePath = createGuidePath({
- ...pose,
- guideId: props.data.id,
- cover: { url: dataURL, blob }
- })
- paths.value.splice(index, 0, path)
- current.value = path
- if (paths.value.length > 1) {
- const index = paths.value.length - 2
- updatePathInfo(index, { time: 3 })
- }
- })
- }
- 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('确定要清空画面吗?')) {
- paths.value.length = 0
- current.value = paths.value[0]
- }
- }
- const changeCurrent = (path: GuidePath) => {
- sdk.comeTo({ dur: 300, ...path })
- current.value = path
- }
- const play = async () => {
- if (isScenePlayIng.value) {
- pauseSceneGuide()
- } else {
- changeCurrent(paths.value[0])
- await asyncTimeout(400)
- playSceneGuide(toRaw(paths.value), (index) => {
- current.value = paths.value[index - 1]
- })
- }
- }
- 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: 30px;
- }
- }
- .meta {
- font-size: 12px;
- border-bottom: 1px solid rgba(255,255,255,.16);
- 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;
- .set-phone-attr {
- padding: 0 10px;
- display: flex;
- flex-direction: column;
- justify-content: space-evenly;
- align-items: center;
- position: relative;
- &::before,
- &::after {
- content: '';
- color: rgba(255,255,255,.6);
- position: absolute;
- top: 50%;
- transform: translateY(-50%);
- }
- &::before {
- left: 0;
- right: 7px;
- height: 2px;
- background-color: currentColor;
- }
- &::after {
- right: -5px;
- width: 0;
- height: 0;
- border: 5px solid transparent;
- border-left: 7px solid currentColor;
- }
- }
-
- .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%;
- }
- 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>
- <style lang="scss">
- .set-phone-attr {
- .ui-input .text input {
- padding: 8px 4px;
- }
- .ui-input .text {
- font-size: 12px;
- }
- .ui-input .text.suffix .retouch {
- right: 4px;
- }
- .ui-input .text.suffix input {
- padding-right: 28px;
- text-align: right;
- }
- .ui-input.time .text.suffix input {
- padding-right: 18px;
- text-align: right;
- }
- }
- </style>
|