index.vue 7.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272
  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. v-if="activeAttrib?.key !== 'frames' && !play"
  25. :data="{ id: '0', name: 'global-frame', time: 0 }"
  26. :frame-action="frameAction"
  27. @change-frame-action="(action) => (frameAction = action.action)"
  28. />
  29. <BottomPano>
  30. <Bottom
  31. :am="focusAM"
  32. v-model:follow="follow"
  33. v-model:current-time="currentTime"
  34. v-model:active="activeAttrib"
  35. />
  36. </BottomPano>
  37. </div>
  38. </template>
  39. <script lang="ts" setup>
  40. import Left from "./left.vue";
  41. import Right from "./right/index.vue";
  42. import GlobalFrame from "./right/frame.vue";
  43. import Bottom from "./bottom.vue";
  44. import BottomPano from "@/layout/bottom-pano.vue";
  45. import router from "@/router";
  46. import { enterEdit } from "@/store";
  47. import { useViewStack } from "@/hook";
  48. import { ams, AnimationModel, autoSaveAnimationModel } from "@/store/animation";
  49. import { computed, nextTick, onUnmounted, reactive, ref, watch, watchEffect } from "vue";
  50. import { Active } from "./type";
  51. import { getAddTLItemAttr } from "@/components/drawing-time-line/check";
  52. import { Message } from "bill/expose-common";
  53. import { mergeFuns, uuid } from "@/components/drawing/hook";
  54. import { title } from "./type";
  55. import { amMap, getAMKey, currentTime, play } from "@/sdk/association/animation";
  56. import { AnimationModel3D } from "@/sdk";
  57. import {
  58. showBottomBarStack,
  59. showLeftCtrlPanoStack,
  60. showLeftPanoStack,
  61. showRightPanoStack,
  62. showSearchStack,
  63. } from "@/env";
  64. enterEdit(() => router.back());
  65. useViewStack(autoSaveAnimationModel);
  66. useViewStack(() =>
  67. mergeFuns(
  68. showBottomBarStack.push(ref(true)),
  69. showRightPanoStack.push(computed(() => !!focusAM.value && !play.value)),
  70. showLeftPanoStack.push(computed(() => !play.value)),
  71. showLeftCtrlPanoStack.push(computed(() => !play.value)),
  72. showSearchStack.push(computed(() => !play.value))
  73. )
  74. );
  75. const focusAM = ref<AnimationModel>();
  76. const activeAttrib = ref<Active>();
  77. const follow = ref(false);
  78. const frameAction = ref<string>();
  79. const amM = computed(() => focusAM.value && amMap[getAMKey(focusAM.value)]);
  80. onUnmounted(() => (currentTime.value = 0));
  81. const asyncOper = (item: AnimationModel, oper: (obj: AnimationModel3D) => void) => {
  82. let onCleanup = () => {};
  83. if (amMap[getAMKey(item)]?.am) {
  84. oper(amMap[getAMKey(item)]!.am!);
  85. } else {
  86. onCleanup = watchEffect(() => {
  87. if (amMap[getAMKey(item)]?.am!) {
  88. oper(amMap[getAMKey(item)]!.am!);
  89. onCleanup && onCleanup();
  90. }
  91. });
  92. }
  93. return onCleanup;
  94. };
  95. watchEffect((onCleanup) => {
  96. if (!amM.value) return;
  97. const am3d = amM.value.am;
  98. if (!am3d) return;
  99. const updateMat = (data: any) => {
  100. if (!data.byControl) return;
  101. if (activeAttrib.value?.key === "frames") {
  102. const frame = focusAM.value!.frames[activeAttrib.value.ndx];
  103. frame.mat = JSON.parse(JSON.stringify(am3d.getModelPose()));
  104. } else {
  105. focusAM.value!.mat = JSON.parse(JSON.stringify(am3d.getModelPose()));
  106. }
  107. };
  108. am3d.bus.on("transformChanged", updateMat);
  109. switch (frameAction.value) {
  110. case "translate":
  111. am3d.enterMoveMode();
  112. break;
  113. case "rotate":
  114. am3d.enterRotateMode();
  115. break;
  116. case "scale":
  117. am3d.enterScaleMode();
  118. break;
  119. }
  120. onCleanup(() => {
  121. am3d.leaveTransform();
  122. am3d.bus.off("transformChanged", updateMat);
  123. });
  124. });
  125. const updateFocus = (am?: AnimationModel) => {
  126. if (focusAM.value) {
  127. asyncOper(focusAM.value, (item) => {
  128. item.changeSelect(false);
  129. });
  130. }
  131. activeAttrib.value = undefined;
  132. focusAM.value = am;
  133. am && asyncOper(am, (item) => item.changeSelect(true));
  134. };
  135. watchEffect((onCleanup) => {
  136. onCleanup(
  137. mergeFuns(
  138. ams.value.map((item) => {
  139. return asyncOper(item, (s) =>
  140. s.bus.on("changeSelect", (f) => {
  141. if (f) {
  142. focusAM.value = item;
  143. } else if (focusAM.value === item) {
  144. focusAM.value = undefined;
  145. }
  146. })
  147. );
  148. })
  149. )
  150. );
  151. });
  152. watch(activeAttrib, (_a, _b, onCleanup) => {
  153. if (!activeAttrib.value) return;
  154. const cur = focusAM.value![activeAttrib.value.key][activeAttrib.value.ndx];
  155. const updateFocus = () => {
  156. const rang = [cur.time, cur.time + (cur.duration || 0)];
  157. if (currentTime.value < rang[0] || currentTime.value > rang[1]) {
  158. activeAttrib.value = undefined;
  159. }
  160. };
  161. follow.value = true;
  162. currentTime.value = cur.time!;
  163. nextTick(() => (follow.value = false));
  164. onCleanup(
  165. watch(
  166. () => [currentTime.value, cur.time, cur.time + (cur.duration || 0)],
  167. updateFocus,
  168. { flush: "sync" }
  169. )
  170. );
  171. });
  172. const add = <T extends Active["key"]>(
  173. key: T,
  174. preset: Partial<AnimationModel[T][0]> = {}
  175. ) => {
  176. const attr = getAddTLItemAttr(focusAM.value![key], currentTime.value, 10, 1);
  177. if (!attr) {
  178. Message.error("当前时间已存在其他" + title[key]);
  179. } else {
  180. const item = reactive({
  181. id: uuid(),
  182. name: title[key],
  183. ...attr,
  184. ...preset,
  185. } as any);
  186. if (key === "frames") {
  187. asyncOper(
  188. focusAM.value!,
  189. (am3d) => (item.mat = JSON.parse(JSON.stringify(am3d.getModelPose())))
  190. );
  191. }
  192. focusAM.value![key].push(item);
  193. activeAttrib.value = {
  194. ndx: focusAM.value![key].length - 1,
  195. key,
  196. };
  197. }
  198. };
  199. let cleanSelect: (() => void) | null = null;
  200. const changeSelect = ({ select, unSelect }: Record<string, AnimationModel[]>) => {
  201. cleanSelect && cleanSelect();
  202. cleanSelect = mergeFuns(
  203. ...select.map((item) => asyncOper(item, (am) => am.changeShow(true))),
  204. ...unSelect.map((item) =>
  205. asyncOper(item, (am) => {
  206. if (item === focusAM.value) {
  207. focusAM.value = undefined;
  208. }
  209. am.changeShow(false);
  210. })
  211. )
  212. );
  213. };
  214. const deleteAm = (am: AnimationModel) => {
  215. if (am === focusAM.value) {
  216. activeAttrib.value = undefined;
  217. focusAM.value = undefined;
  218. }
  219. ams.value.splice(ams.value.indexOf(am), 1);
  220. };
  221. </script>
  222. <style lang="scss" scoped>
  223. .animation-layout {
  224. --bottom-height: 70px;
  225. &.focusAM {
  226. --bottom-height: 225px;
  227. }
  228. }
  229. .animation-left {
  230. height: calc(100vh - var(--bottom-height));
  231. position: absolute;
  232. width: var(--left-pano-width);
  233. }
  234. .animation-right {
  235. height: calc(100vh - var(--bottom-height));
  236. padding-top: 0;
  237. position: absolute;
  238. right: 0;
  239. top: 0;
  240. }
  241. </style>
  242. <style>
  243. .animation-left .left-pano {
  244. bottom: 0 !important;
  245. }
  246. </style>