index.vue 8.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323
  1. <template>
  2. <div :class="{ focusAM: focusAM }" class="animation-layout">
  3. <Left
  4. :focus="focusAM"
  5. @update:focus="updateFocus"
  6. class="animation-left"
  7. @change-select="changeSelect"
  8. @delete="deleteAm"
  9. />
  10. <Right
  11. v-if="focusAM && !play"
  12. :am="focusAM"
  13. :frameAction="frameAction"
  14. @change-frame-action="(f) => (frameAction = f.action)"
  15. class="animation-right"
  16. v-model:activeAttrib="activeAttrib"
  17. @add-frame="add('frames', { duration: 0 })"
  18. @add-path="(preset) => add('paths', { reverse: false, ...preset })"
  19. @add-subtitle="add('subtitles', { content: '', background: '#000000' })"
  20. @add-action="(preset) => add('actions', preset)"
  21. @apply-global="k => ams.forEach((am: any) => (am[k] = focusAM![k]))"
  22. />
  23. <GlobalFrame
  24. class="global-frame"
  25. v-if="activeAttrib?.key !== 'frames' && !play"
  26. :data="{ id: '0', name: 'global-frame', time: 0 }"
  27. :frame-action="frameAction"
  28. @change-frame-action="(action) => (frameAction = action.action)"
  29. />
  30. <BottomPano class="strengthen-top">
  31. <Bottom
  32. :am="focusAM"
  33. :follow="play || follow"
  34. v-model:current-time="currentTime"
  35. v-model:active="activeAttrib"
  36. />
  37. </BottomPano>
  38. </div>
  39. </template>
  40. <script lang="ts" setup>
  41. import Left from "./left.vue";
  42. import Right from "./right/index.vue";
  43. import GlobalFrame from "./right/frame.vue";
  44. import Bottom from "./bottom.vue";
  45. import BottomPano from "@/layout/bottom-pano.vue";
  46. import router from "@/router";
  47. import { enterEdit, sysBus } from "@/store";
  48. import { useViewStack } from "@/hook";
  49. import { ams, AnimationModel, autoSaveAnimationModel } from "@/store/animation";
  50. import { computed, nextTick, onUnmounted, reactive, ref, watch, watchEffect } from "vue";
  51. import { Active } from "./type";
  52. import { getAddTLItemAttr } from "@/components/drawing-time-line/check";
  53. import { Message } from "bill/expose-common";
  54. import { mergeFuns, uuid } from "@/components/drawing/hook";
  55. import { title } from "./type";
  56. import { amMap, getAMKey, currentTime, play } from "@/sdk/association/animation";
  57. import { sdk, AnimationModel3D } from "@/sdk";
  58. import {
  59. showBottomBarStack,
  60. showLeftCtrlPanoStack,
  61. showLeftPanoStack,
  62. showRightPanoStack,
  63. showSearchStack,
  64. } from "@/env";
  65. import { clickListener } from "@/utils/event";
  66. import { useRMenus } from "@/components/right-menu";
  67. enterEdit(() => router.back());
  68. useViewStack(autoSaveAnimationModel);
  69. sysBus.on("leave", () => {
  70. updateFocus();
  71. });
  72. useViewStack(() => {
  73. const showRight = ref(false);
  74. return mergeFuns(
  75. showBottomBarStack.push(ref(true)),
  76. showRightPanoStack.push(showRight),
  77. showLeftPanoStack.push(ref(true)),
  78. showLeftCtrlPanoStack.push(computed(() => !play.value)),
  79. showSearchStack.push(computed(() => !play.value)),
  80. watchEffect((onCleanup) => {
  81. const cleanups: any[] = [];
  82. if (play.value || activeAttrib.value?.key === "frames") {
  83. cleanups.push(showRightPanoStack.push(ref(false)));
  84. }
  85. if (play.value) {
  86. cleanups.push(showLeftPanoStack.push(ref(false)));
  87. }
  88. onCleanup(mergeFuns(cleanups));
  89. }),
  90. watchEffect(() => {
  91. showRight.value = !!focusAM.value;
  92. })
  93. );
  94. });
  95. const focusAM = ref<AnimationModel>();
  96. const activeAttrib = ref<Active>();
  97. const follow = ref(false);
  98. const frameAction = ref<string>();
  99. const amM = computed(() => focusAM.value && amMap[getAMKey(focusAM.value)]);
  100. onUnmounted(() => {
  101. currentTime.value = 0;
  102. play.value = false;
  103. });
  104. const asyncOper = (item: AnimationModel, oper: (obj: AnimationModel3D) => void) => {
  105. let onCleanup = () => {};
  106. if (amMap[getAMKey(item)]?.am) {
  107. oper(amMap[getAMKey(item)]!.am!);
  108. } else {
  109. onCleanup = watchEffect(() => {
  110. if (amMap[getAMKey(item)]?.am!) {
  111. oper(amMap[getAMKey(item)]!.am!);
  112. onCleanup && onCleanup();
  113. }
  114. });
  115. }
  116. return onCleanup;
  117. };
  118. watchEffect((onCleanup) => {
  119. if (!amM.value) return;
  120. const am3d = amM.value.am;
  121. if (!am3d) return;
  122. const updateMat = (data: any) => {
  123. if (!data.byControl) return;
  124. if (activeAttrib.value?.key === "frames") {
  125. const frame = focusAM.value!.frames[activeAttrib.value.ndx];
  126. frame.mat = JSON.parse(JSON.stringify(am3d.getModelPose()));
  127. } else {
  128. focusAM.value!.mat = JSON.parse(JSON.stringify(am3d.getModelPose()));
  129. }
  130. };
  131. am3d.bus.on("transformChanged", updateMat);
  132. switch (frameAction.value) {
  133. case "translate":
  134. am3d.enterMoveMode();
  135. break;
  136. case "rotate":
  137. am3d.enterRotateMode();
  138. break;
  139. case "scale":
  140. am3d.enterScaleMode();
  141. break;
  142. }
  143. onCleanup(() => {
  144. am3d.leaveTransform();
  145. am3d.bus.off("transformChanged", updateMat);
  146. });
  147. });
  148. const updateFocus = (am?: AnimationModel) => {
  149. if (focusAM.value) {
  150. asyncOper(focusAM.value, (item) => {
  151. item.changeSelect(false);
  152. });
  153. }
  154. activeAttrib.value = undefined;
  155. focusAM.value = am;
  156. am && asyncOper(am, (item) => item.changeSelect(true));
  157. };
  158. watchEffect((onCleanup) => {
  159. onCleanup(
  160. mergeFuns(
  161. ams.value.map((item) => {
  162. return asyncOper(item, (s) =>
  163. s.bus.on("changeSelect", (f) => {
  164. if (f) {
  165. focusAM.value = item;
  166. } else if (focusAM.value === item) {
  167. focusAM.value = undefined;
  168. }
  169. })
  170. );
  171. })
  172. )
  173. );
  174. });
  175. watch(activeAttrib, (_a, _b, onCleanup) => {
  176. if (!activeAttrib.value) return;
  177. const cur = focusAM.value![activeAttrib.value.key][activeAttrib.value.ndx];
  178. const updateFocus = () => {
  179. const rang = [cur.time, cur.time + (cur.duration || 0)];
  180. if (currentTime.value < rang[0] || currentTime.value > rang[1]) {
  181. activeAttrib.value = undefined;
  182. }
  183. };
  184. follow.value = true;
  185. currentTime.value = cur.time!;
  186. nextTick(() => (follow.value = false));
  187. onCleanup(
  188. watch(
  189. () => [currentTime.value, cur.time, cur.time + (cur.duration || 0)],
  190. updateFocus,
  191. { flush: "sync" }
  192. )
  193. );
  194. });
  195. const add = <T extends Active["key"]>(
  196. key: T,
  197. preset: Partial<AnimationModel[T][0]> = {}
  198. ) => {
  199. const attr = getAddTLItemAttr(
  200. focusAM.value![key],
  201. currentTime.value,
  202. preset.duration || 10,
  203. 1
  204. );
  205. if (!attr) {
  206. Message.error("当前时间已存在其他" + title[key]);
  207. } else {
  208. const item = reactive({
  209. id: uuid(),
  210. name: title[key],
  211. ...attr,
  212. ...preset,
  213. } as any);
  214. if (key === "frames") {
  215. asyncOper(
  216. focusAM.value!,
  217. (am3d) => (item.mat = JSON.parse(JSON.stringify(am3d.getModelPose())))
  218. );
  219. }
  220. focusAM.value![key].push(item);
  221. activeAttrib.value = {
  222. ndx: focusAM.value![key].length - 1,
  223. key,
  224. };
  225. }
  226. };
  227. let cleanSelect: (() => void) | null = null;
  228. const changeSelect = ({ select, unSelect }: Record<string, AnimationModel[]>) => {
  229. cleanSelect && cleanSelect();
  230. cleanSelect = mergeFuns(
  231. ...select.map((item) => asyncOper(item, (am) => am.changeShow(true))),
  232. ...unSelect.map((item) =>
  233. asyncOper(item, (am) => {
  234. if (item === focusAM.value) {
  235. focusAM.value = undefined;
  236. }
  237. am.changeShow(false);
  238. })
  239. )
  240. );
  241. };
  242. const deleteAm = (am: AnimationModel) => {
  243. if (am === focusAM.value) {
  244. activeAttrib.value = undefined;
  245. focusAM.value = undefined;
  246. }
  247. ams.value.splice(ams.value.indexOf(am), 1);
  248. };
  249. let unMount: () => void;
  250. onUnmounted(
  251. clickListener(
  252. document.querySelector("#layout-app") as HTMLDivElement,
  253. (pixel) => {
  254. const pos = sdk.getPositionByScreen(pixel);
  255. if (!focusAM.value) return;
  256. unMount && unMount();
  257. setTimeout(() => {
  258. unMount = useRMenus(pixel, [
  259. {
  260. label: "移动到这里",
  261. icon: "move",
  262. handler() {
  263. amMap[getAMKey(focusAM.value!)]?.am?.moveModelTo(pixel, pos?.worldPos);
  264. },
  265. },
  266. ]);
  267. });
  268. },
  269. 2
  270. )
  271. );
  272. </script>
  273. <style lang="scss" scoped>
  274. .animation-layout {
  275. --bottom-height: 70px;
  276. &.focusAM {
  277. --bottom-height: 225px;
  278. }
  279. }
  280. .animation-left {
  281. height: calc(100vh - var(--bottom-height));
  282. position: absolute;
  283. width: var(--left-pano-width);
  284. }
  285. .animation-right {
  286. height: calc(100vh - var(--bottom-height));
  287. padding-top: 0;
  288. position: absolute;
  289. right: 0;
  290. top: 0;
  291. }
  292. </style>
  293. <style>
  294. .animation-left .left-pano {
  295. bottom: 0 !important;
  296. }
  297. </style>