|
@@ -1,16 +1,16 @@
|
|
|
<template>
|
|
|
<div class="video">
|
|
|
<div class="overflow">
|
|
|
- <ui-icon
|
|
|
- ctrl
|
|
|
- :type="isScenePlayIng ? 'pause' : 'preview'"
|
|
|
- :disabled="!paths.length"
|
|
|
+ <ui-icon
|
|
|
+ ctrl
|
|
|
+ :type="isScenePlayIng ? 'pause' : 'preview'"
|
|
|
+ :disabled="!paths.length"
|
|
|
@click="play"
|
|
|
/>
|
|
|
- <ui-button
|
|
|
- type="primary"
|
|
|
- @click="addPath"
|
|
|
- width="200px"
|
|
|
+ <ui-button
|
|
|
+ type="primary"
|
|
|
+ @click="addPath"
|
|
|
+ width="200px"
|
|
|
:class="{ disabled: isScenePlayIng }"
|
|
|
>
|
|
|
添加视角
|
|
@@ -19,11 +19,11 @@
|
|
|
<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
|
|
|
+ <span>视频时长</span>{{ paths.reduce((t, c) => t + c.time, 0).toFixed(1) }}s
|
|
|
</div>
|
|
|
- <div
|
|
|
- class="fun-ctrl clear"
|
|
|
- @click="deleteAll"
|
|
|
+ <div
|
|
|
+ class="fun-ctrl clear"
|
|
|
+ @click="deleteAll"
|
|
|
:class="{ disabled: isScenePlayIng }"
|
|
|
>
|
|
|
<ui-icon type="del" />
|
|
@@ -33,48 +33,47 @@
|
|
|
|
|
|
<div class="photo-list" ref="listVm">
|
|
|
<template v-for="(path, i) in paths" :key="path.id">
|
|
|
- <div
|
|
|
- class="photo"
|
|
|
+ <div
|
|
|
+ class="photo"
|
|
|
:class="{ active: current === path, disabled: isScenePlayIng }"
|
|
|
@click="changeCurrent(path)"
|
|
|
>
|
|
|
- <ui-icon
|
|
|
- type="del"
|
|
|
- ctrl
|
|
|
- @click.stop="deletePath(path)"
|
|
|
- :class="{ disabled: isScenePlayIng }"
|
|
|
+ <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"
|
|
|
+ <ui-input
|
|
|
+ type="number"
|
|
|
+ width="54px"
|
|
|
height="26px"
|
|
|
- :modelValue="path.speed"
|
|
|
+ :modelValue="path.speed"
|
|
|
@update:modelValue="(val: number) => updatePathInfo(i, { speed: val })"
|
|
|
- :ctrl="false"
|
|
|
- :min="0.1"
|
|
|
+ :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"
|
|
|
+ <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"
|
|
|
+ :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>
|
|
@@ -82,30 +81,47 @@
|
|
|
</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 { loadPack, togetherCallback, getFileUrl, asyncTimeout } from "@/utils";
|
|
|
+import {
|
|
|
+ sdk,
|
|
|
+ playSceneGuide,
|
|
|
+ pauseSceneGuide,
|
|
|
+ isScenePlayIng,
|
|
|
+ analysisPose,
|
|
|
+ analysisPoseInfo,
|
|
|
+} 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'
|
|
|
+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 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)
|
|
|
-}
|
|
|
+ const info = sdk.calcPathInfo(paths.value.slice(index, index + 2) as any, calcInfo);
|
|
|
+ Object.assign(paths.value[index], info);
|
|
|
+};
|
|
|
|
|
|
-useViewStack(() =>
|
|
|
+useViewStack(() =>
|
|
|
togetherCallback([
|
|
|
showRightPanoStack.push(ref(false)),
|
|
|
showLeftCtrlPanoStack.push(ref(false)),
|
|
@@ -114,97 +130,105 @@ useViewStack(() =>
|
|
|
])
|
|
|
);
|
|
|
|
|
|
-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)
|
|
|
- }
|
|
|
+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)
|
|
|
+ false
|
|
|
+);
|
|
|
|
|
|
const addPath = () => {
|
|
|
loadPack(async () => {
|
|
|
- const dataURL = await sdk.screenshot(260, 160)
|
|
|
- const res = await fetch(dataURL)
|
|
|
- const blob = await res.blob()
|
|
|
+ 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,
|
|
|
+ const pose = sdk.getPose();
|
|
|
+ const index = paths.value.indexOf(current.value) + 1;
|
|
|
+ const path: GuidePath = createGuidePath({
|
|
|
+ ...analysisPose(pose),
|
|
|
guideId: props.data.id,
|
|
|
- cover: { url: dataURL, blob }
|
|
|
- })
|
|
|
- paths.value.splice(index, 0, path)
|
|
|
- current.value = path
|
|
|
+ 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 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 (fore || (await Dialog.confirm("确定要删除此画面吗?"))) {
|
|
|
+ const index = paths.value.indexOf(path);
|
|
|
if (~index) {
|
|
|
- paths.value.splice(index, 1)
|
|
|
+ paths.value.splice(index, 1);
|
|
|
}
|
|
|
if (path === current.value) {
|
|
|
- current.value = paths.value[index + (index === 0 ? 0 : -1)]
|
|
|
+ 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]
|
|
|
+ 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
|
|
|
-}
|
|
|
+ if (sdk.setPose) {
|
|
|
+ sdk.setPose({ dur: 300, ...path, ...analysisPoseInfo(path) });
|
|
|
+ } else {
|
|
|
+ sdk.comeTo({ dur: 300, ...path });
|
|
|
+ }
|
|
|
+ current.value = path;
|
|
|
+};
|
|
|
|
|
|
const play = async () => {
|
|
|
if (isScenePlayIng.value) {
|
|
|
- pauseSceneGuide()
|
|
|
+ pauseSceneGuide();
|
|
|
} else {
|
|
|
- changeCurrent(paths.value[0])
|
|
|
- await asyncTimeout(400)
|
|
|
- playSceneGuide(toRaw(paths.value), (index) => {
|
|
|
- current.value = paths.value[index - 1]
|
|
|
- })
|
|
|
+ changeCurrent(paths.value[0]);
|
|
|
+ await asyncTimeout(400);
|
|
|
+ playSceneGuide(toRaw(paths.value) as any, (index) => {
|
|
|
+ current.value = paths.value[index - 1];
|
|
|
+ });
|
|
|
}
|
|
|
-}
|
|
|
+};
|
|
|
|
|
|
-const listVm = ref<HTMLDivElement>()
|
|
|
+const listVm = ref<HTMLDivElement>();
|
|
|
watchEffect(async () => {
|
|
|
- const index = paths.value.indexOf(current.value)
|
|
|
+ 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
|
|
|
+ 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>
|
|
@@ -229,11 +253,11 @@ watchEffect(async () => {
|
|
|
|
|
|
.meta {
|
|
|
font-size: 12px;
|
|
|
- border-bottom: 1px solid rgba(255,255,255,.16);
|
|
|
+ border-bottom: 1px solid rgba(255, 255, 255, 0.16);
|
|
|
padding: 10px 20px;
|
|
|
display: flex;
|
|
|
justify-content: space-between;
|
|
|
-
|
|
|
+
|
|
|
.length span {
|
|
|
margin-right: 10px;
|
|
|
}
|
|
@@ -248,7 +272,6 @@ watchEffect(async () => {
|
|
|
}
|
|
|
}
|
|
|
|
|
|
-
|
|
|
.photo-list {
|
|
|
padding: 10px 20px 20px;
|
|
|
overflow-x: auto;
|
|
@@ -264,8 +287,8 @@ watchEffect(async () => {
|
|
|
|
|
|
&::before,
|
|
|
&::after {
|
|
|
- content: '';
|
|
|
- color: rgba(255,255,255,.6);
|
|
|
+ content: "";
|
|
|
+ color: rgba(255, 255, 255, 0.6);
|
|
|
position: absolute;
|
|
|
top: 50%;
|
|
|
transform: translateY(-50%);
|
|
@@ -284,12 +307,11 @@ watchEffect(async () => {
|
|
|
border-left: 7px solid currentColor;
|
|
|
}
|
|
|
}
|
|
|
-
|
|
|
+
|
|
|
.photo {
|
|
|
cursor: pointer;
|
|
|
flex: none;
|
|
|
position: relative;
|
|
|
-
|
|
|
|
|
|
&.active {
|
|
|
outline: 2px solid var(--colors-primary-base);
|
|
@@ -302,8 +324,8 @@ watchEffect(async () => {
|
|
|
width: 24px;
|
|
|
font-size: 12px;
|
|
|
height: 24px;
|
|
|
- background-color: rgba(0,0,0,0.6);
|
|
|
- color: rgba(255,255,255,.6);
|
|
|
+ background-color: rgba(0, 0, 0, 0.6);
|
|
|
+ color: rgba(255, 255, 255, 0.6);
|
|
|
display: flex;
|
|
|
align-items: center;
|
|
|
justify-content: center;
|
|
@@ -311,7 +333,6 @@ watchEffect(async () => {
|
|
|
border-radius: 50%;
|
|
|
}
|
|
|
|
|
|
-
|
|
|
img {
|
|
|
width: 230px;
|
|
|
height: 160px;
|
|
@@ -324,7 +345,7 @@ watchEffect(async () => {
|
|
|
height: 100px;
|
|
|
line-height: 100px;
|
|
|
text-align: center;
|
|
|
- color: rgba(255,255,255,0.6);
|
|
|
+ color: rgba(255, 255, 255, 0.6);
|
|
|
font-size: 1.2em;
|
|
|
}
|
|
|
</style>
|
|
@@ -349,4 +370,4 @@ watchEffect(async () => {
|
|
|
text-align: right;
|
|
|
}
|
|
|
}
|
|
|
-</style>
|
|
|
+</style>
|