PenAnimates.vue 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656
  1. <template>
  2. <div class="animations">
  3. <template v-if="props.pen.animations.length">
  4. <t-collapse
  5. v-model="openedCollapses"
  6. :borderless="true"
  7. :expand-on-row-click="true"
  8. >
  9. <t-collapse-panel v-for="(item, i) in props.pen.animations" :value="i">
  10. <template #header>
  11. <div class="flex middle" @click.stop>
  12. <t-input
  13. :title="item.name"
  14. :value="item.name"
  15. autoWidth
  16. class="mr-8"
  17. style="max-width: 60px"
  18. @change="changeAnimateName($event, item)"
  19. @blur="checkAnimateName(item)"
  20. @focus="bakAnimateName(item.name)"
  21. />
  22. <stop-circle-icon v-if="isPlaying === i"
  23. class="hover primary"
  24. style="font-size: 16px"
  25. @click="stop"/>
  26. <play-circle-icon v-else
  27. class="hover"
  28. style="font-size: 16px"
  29. @click="play(i)"/>
  30. <!-- <t-icon
  31. v-if="isPlaying === i"
  32. name="stop-circle"
  33. class="hover primary"
  34. style="font-size: 16px"
  35. @click="stop"
  36. />
  37. <t-icon
  38. v-else
  39. name="play-circle"
  40. class="hover"
  41. style="font-size: 16px"
  42. @click="play(i)"
  43. /> -->
  44. </div>
  45. </template>
  46. <template #headerRightContent>
  47. <t-space size="small" @click.stop>
  48. <edit-icon v-if="!props.pen.type && item.animate"
  49. class="hover mr-4"
  50. @click="animate = item"/>
  51. <!-- <t-icon
  52. v-if="!props.pen.type && item.animate"
  53. name="edit"
  54. class="hover mr-4"
  55. @click="animate = item"
  56. /> -->
  57. <t-popconfirm
  58. content="确认删除该动画吗"
  59. placement="left"
  60. @confirm="delAnimate(i)"
  61. >
  62. <delete-icon class="hover"/>
  63. <!-- <t-icon name="delete" class="hover" /> -->
  64. </t-popconfirm>
  65. </t-space>
  66. </template>
  67. <template v-if="props.pen.type">
  68. <div class="form-item">
  69. <label>动画类型</label>
  70. <t-select v-model="item.lineAnimateType" placeholder="水流" @change="changeValue(i)">
  71. <t-option :key="0" :value="0" label="水流" />
  72. <t-option :key="1" :value="1" label="水珠" />
  73. <t-option :key="2" :value="2" label="圆点" />
  74. <t-option v-if="['polyline','line'].includes(props.pen.lineName)" :key="3" :value="3" label="箭头" />
  75. <t-option v-if="['polyline','line'].includes(props.pen.lineName)" :key="4" :value="4" label="水滴" />
  76. </t-select>
  77. </div>
  78. <div class="form-item mt-8">
  79. <label>运动速度</label>
  80. <t-slider
  81. class="ml-12"
  82. v-model="item.animateSpan"
  83. :show-tooltip="true"
  84. :min="1"
  85. :max="10"
  86. @change="changeValue(i)"
  87. />
  88. </div>
  89. <div class="form-item mt-8">
  90. <label>动画颜色</label>
  91. <t-color-picker
  92. class="w-full"
  93. format="CSS"
  94. :enable-alpha="true"
  95. :recent-colors="null"
  96. :swatch-colors="defaultPureColor"
  97. :color-modes="['monochrome']"
  98. :show-primary-color-preview="false"
  99. :clearable="true"
  100. v-model="item.animateColor"
  101. @change="changeValue(i)"
  102. />
  103. </div>
  104. <div class="form-item mt-8">
  105. <label>发光效果</label>
  106. <t-switch
  107. class="ml-8 mt-8"
  108. size="small"
  109. v-model="item.animateShadow"
  110. @change="changeValue(i)"
  111. />
  112. </div>
  113. <div v-if="item.animateShadow" class="form-item mt-8">
  114. <label>发光颜色</label>
  115. <t-color-picker
  116. class="w-full"
  117. format="CSS"
  118. :enable-alpha="true"
  119. :recent-colors="null"
  120. :swatch-colors="defaultPureColor"
  121. :color-modes="['monochrome']"
  122. :show-primary-color-preview="false"
  123. :clearable="true"
  124. v-model="item.animateShadowColor"
  125. @change="changeValue(i)"
  126. />
  127. </div>
  128. <div v-if="item.animateShadow" class="form-item mt-8">
  129. <label>发光模糊</label>
  130. <t-input-number
  131. v-model="item.animateShadowBlur"
  132. theme="column"
  133. :min="1"
  134. placeholder="默认6"
  135. @change="changeValue(i)"
  136. />
  137. </div>
  138. <div class="form-item mt-8">
  139. <label>轨迹宽度</label>
  140. <t-input-number
  141. v-model="item.animateLineWidth"
  142. theme="column"
  143. :min="1"
  144. placeholder="默认6"
  145. @change="changeValue(i)"
  146. />
  147. </div>
  148. <div v-if="item.lineAnimateType===3||item.lineAnimateType===4" class="form-item mt-8">
  149. <label>轨迹间隔</label>
  150. <t-input-number
  151. v-model="item.animateInterval"
  152. theme="column"
  153. :min="1"
  154. placeholder="默认100"
  155. @change="changeValue(i)"
  156. />
  157. </div>
  158. <div class="form-item mt-8">
  159. <label>反向流动</label>
  160. <t-switch
  161. class="ml-8 mt-8"
  162. size="small"
  163. v-model="item.animateReverse"
  164. @change="changeValue(i)"
  165. />
  166. </div>
  167. <div class="form-item mt-8">
  168. <label>播放次数</label>
  169. <t-input-number
  170. v-model="item.animateCycle"
  171. theme="column"
  172. :min="1"
  173. placeholder="无限"
  174. title="缺省无限循环播放"
  175. @change="changeValue(i)"
  176. />
  177. </div>
  178. <div class="form-item mt-8">
  179. <label>自动播放</label>
  180. <t-switch
  181. class="ml-8 mt-8"
  182. size="small"
  183. v-model="item.autoPlay"
  184. @change="changeAnimateAutoPlay($event, item)"
  185. />
  186. </div>
  187. <div
  188. class="form-item mt-8"
  189. >
  190. <label>下个动画类型</label>
  191. <t-radio-group class="ml-8" v-model="item.temType" @change="item.nextAnimate = ''">
  192. <t-radio value="id">图元</t-radio>
  193. <t-radio value="tag">组</t-radio>
  194. </t-radio-group>
  195. </div>
  196. <div
  197. class="form-item mt-8"
  198. title="当前动画结束后自动播放下一个对象的动画"
  199. >
  200. <label>下个动画</label>
  201. <t-tree-select
  202. v-if="item.temType === 'id'"
  203. v-model="item.nextAnimate"
  204. :data="penTree"
  205. filterable
  206. placeholder="无"
  207. />
  208. <t-select
  209. v-else
  210. v-model="item.nextAnimate"
  211. :options="groups"
  212. placeholder="组"
  213. />
  214. </div>
  215. </template>
  216. <template v-else>
  217. <div class="form-item">
  218. <label>动画类型</label>
  219. <t-select
  220. v-model="item.animate"
  221. clearable
  222. placeholder="动画"
  223. :options="animateList"
  224. @change="changeAnimate(item)"
  225. />
  226. </div>
  227. <div class="form-item mt-8">
  228. <label>播放次数</label>
  229. <t-input-number
  230. v-model="item.animateCycle"
  231. theme="column"
  232. :min="1"
  233. placeholder="无限"
  234. title="缺省无限循环播放"
  235. />
  236. </div>
  237. <div class="form-item mt-8">
  238. <label>结束状态</label>
  239. <t-select v-model="item.keepAnimateState" placeholder="初始状态">
  240. <t-option :key="false" :value="false" label="初始状态" />
  241. <t-option :key="true" :value="true" label="当前状态" />
  242. </t-select>
  243. </div>
  244. <div class="form-item mt-8">
  245. <label>线性播放</label>
  246. <t-tooltip content="仅支持数字属性匀速线性播放" placement="top">
  247. <t-select v-model="item.linear" placeholder="是">
  248. <t-option :key="true" :value="true" label="是" />
  249. <t-option :key="false" :value="false" label="否" />
  250. </t-select>
  251. </t-tooltip>
  252. </div>
  253. <div class="form-item mt-8">
  254. <label>自动播放</label>
  255. <t-switch
  256. class="ml-8 mt-8"
  257. size="small"
  258. v-model="item.autoPlay"
  259. @change="changeAnimateAutoPlay($event, item)"
  260. />
  261. </div>
  262. <div
  263. class="form-item mt-8"
  264. >
  265. <label>下个动画类型</label>
  266. <t-radio-group class="ml-8" v-model="item.temType" @change="item.nextAnimate = ''">
  267. <t-radio value="id">图元</t-radio>
  268. <t-radio value="tag">组</t-radio>
  269. </t-radio-group>
  270. </div>
  271. <div
  272. class="form-item mt-8"
  273. title="当前动画结束后自动播放下一个对象的动画"
  274. >
  275. <label>下个动画</label>
  276. <t-tree-select
  277. v-if="item.temType === 'id'"
  278. v-model="item.nextAnimate"
  279. :data="penTree"
  280. filterable
  281. placeholder="无"
  282. />
  283. <t-select
  284. v-else
  285. v-model="item.nextAnimate"
  286. :options="groups"
  287. placeholder="组"
  288. />
  289. </div>
  290. </template>
  291. </t-collapse-panel>
  292. </t-collapse>
  293. <t-divider />
  294. <div class="p-16">
  295. <t-button class="w-full" @click="addAnimate" style="height: 30px">
  296. 添加动画
  297. </t-button>
  298. </div>
  299. </template>
  300. <div class="flex column center blank" v-else>
  301. <img src="/img/blank.png" />
  302. <div class="gray center">还没有动画</div>
  303. <div class="mt-8">
  304. <t-button @click="addAnimate" style="height: 30px">添加动画</t-button>
  305. </div>
  306. </div>
  307. </div>
  308. <AnimateFrames
  309. v-if="animate"
  310. :animate="animate"
  311. @close="animate = undefined"
  312. />
  313. </template>
  314. <script lang="ts" setup>
  315. import { onBeforeMount, ref, watch, onUnmounted,onBeforeUnmount } from 'vue';
  316. import { getPenTree } from '@/services/common';
  317. import { deepClone } from '@meta2d/core';
  318. import AnimateFrames from './AnimateFrames.vue';
  319. import { defaultPureColor } from '@/services/defaults';
  320. import { MessagePlugin } from 'tdesign-vue-next';
  321. import {StopCircleIcon, PlayCircleIcon, EditIcon, DeleteIcon} from 'tdesign-icons-vue-next';
  322. const props = defineProps<{
  323. pen: any;
  324. }>();
  325. let animateNameBak = '';
  326. function changeAnimateAutoPlay(value, item) {
  327. if (value) {
  328. props.pen.autoPlay = true;
  329. if (props.pen.animations.length === 1) {
  330. item.autoPlay = true;
  331. return;
  332. }
  333. props.pen.animations.forEach((i) => {
  334. if (i.name !== item.name) {
  335. i.autoPlay = false;
  336. }
  337. });
  338. item.autoPlay = true;
  339. } else {
  340. let allFalse = props.pen.animations.every((i) => !i.autoPlay);
  341. if (allFalse) {
  342. props.pen.autoPlay = false;
  343. props.pen.frames = null;
  344. }
  345. }
  346. }
  347. const checkAnimateName = (item:any) => {
  348. if(!item.name) {
  349. MessagePlugin.warning('动画名不能为空!');
  350. item.name = animateNameBak;
  351. }
  352. }
  353. const bakAnimateName = (name:string) => {
  354. animateNameBak = name;
  355. }
  356. const changeAnimateName = (event, item) => {
  357. let result = props.pen.animations.filter(
  358. (animation) => animation.name === event
  359. );
  360. if (result && result.length) {
  361. MessagePlugin.warning('已存在同名动画!');
  362. return;
  363. } else {
  364. item.name = event;
  365. }
  366. };
  367. const penTree: any = ref([]);
  368. const groups: any = ref([]);
  369. const openedCollapses = ref([0]);
  370. const animate: any = ref(undefined);
  371. const animateList = [
  372. {
  373. label: '闪烁',
  374. value: '闪烁',
  375. data: [
  376. {
  377. visible: true,
  378. duration: 100,
  379. },
  380. {
  381. visible: false,
  382. duration: 100,
  383. },
  384. ],
  385. },
  386. {
  387. label: '缩放',
  388. value: '缩放',
  389. data: [
  390. {
  391. scale: 1.1,
  392. duration: 100,
  393. },
  394. {
  395. scale: 1,
  396. duration: 400,
  397. },
  398. ],
  399. },
  400. {
  401. label: '旋转',
  402. value: '旋转',
  403. data: [
  404. {
  405. rotate: 360,
  406. duration: 1000,
  407. },
  408. ],
  409. },
  410. {
  411. label: '逆向旋转',
  412. value: '逆向旋转',
  413. data: [
  414. {
  415. rotate: -360,
  416. duration: 1000,
  417. },
  418. ],
  419. },
  420. {
  421. label: '上下跳动',
  422. value: '上下跳动',
  423. data: [
  424. {
  425. y: -10,
  426. duration: 100,
  427. },
  428. { y: 0, duration: 100 },
  429. { y: -10, duration: 200 },
  430. ],
  431. },
  432. {
  433. label: '左右跳动',
  434. value: '左右跳动',
  435. data: [
  436. {
  437. x: -10,
  438. duration: 100,
  439. },
  440. {
  441. x: 10,
  442. duration: 80,
  443. },
  444. {
  445. x: -10,
  446. duration: 50,
  447. },
  448. {
  449. x: 10,
  450. duration: 30,
  451. },
  452. {
  453. x: 0,
  454. duration: 300,
  455. },
  456. ],
  457. },
  458. {
  459. label: '颜色变化',
  460. value: '颜色变化',
  461. data: [
  462. { color: '#4583ff', duration: 200 },
  463. { color: '#ff4000', duration: 200 },
  464. ],
  465. },
  466. {
  467. label: '背景变化',
  468. value: '背景变化',
  469. data: [
  470. { background: '#4583ff', duration: 200 },
  471. { background: '#ff4000', duration: 200 },
  472. ],
  473. },
  474. {
  475. label: '文字变化',
  476. value: '文字变化',
  477. data: [
  478. { text: '乐吾乐', duration: 200 },
  479. { text: 'le5le', duration: 200 },
  480. ],
  481. },
  482. {
  483. label: '状态变化',
  484. value: '状态变化',
  485. data: [
  486. { showChild: 0, duration: 200 },
  487. { showChild: 1, duration: 200 },
  488. ],
  489. },
  490. {
  491. label: '翻转',
  492. value: '翻转',
  493. data: [
  494. { flipX: true, flipY: true, duration: 200 },
  495. { flipX: false, flipY: false, duration: 200 },
  496. ],
  497. },
  498. {
  499. label: '自定义',
  500. value: 'custom',
  501. data: [],
  502. },
  503. ];
  504. const isPlaying = ref(-1);
  505. onBeforeMount(() => {
  506. if (!props.pen.animations) {
  507. props.pen.animations = [];
  508. }
  509. const p = meta2d.findOne(props.pen.id);
  510. if (p?.calculative?.start) {
  511. // @ts-ignore
  512. isPlaying.value = p?.currentAnimation;
  513. }
  514. meta2d.on('animateEnd', cancleAnimatePlayState);
  515. penTree.value = getPenTree().map(i=>{
  516. i.label+='-'+ i.value;
  517. return i
  518. }
  519. );
  520. groups.value = [];
  521. const d = meta2d.store.data as any;
  522. if (d.groups) {
  523. for (const item of d.groups) {
  524. groups.value.push({ label: item, value: item });
  525. }
  526. }
  527. });
  528. onBeforeUnmount(() => {
  529. meta2d.off('animateEnd', cancleAnimatePlayState);
  530. })
  531. const cancleAnimatePlayState = (pen) => {
  532. if(pen.id === props.pen.id) {
  533. isPlaying.value = -1;
  534. }
  535. }
  536. const watcher = watch(
  537. () => props.pen.id,
  538. () => {
  539. const p = meta2d.findOne(props.pen.id);
  540. if (p?.calculative?.start) {
  541. // @ts-ignore
  542. isPlaying.value = p?.currentAnimation;
  543. } else {
  544. isPlaying.value = -1;
  545. }
  546. }
  547. );
  548. const addAnimate = () => {
  549. openedCollapses.value.push(props.pen.animations.length);
  550. props.pen.animations.push({
  551. name: '动画' + (props.pen.animations.length + 1),
  552. temType: 'id'
  553. });
  554. };
  555. const delAnimate = (i: number) => {
  556. if (isPlaying.value === i) {
  557. meta2d.stopAnimate(props.pen.id);
  558. isPlaying.value = undefined;
  559. }
  560. if(props.pen.animate) {
  561. meta2d.setValue({
  562. id:props.pen.id,
  563. animate:undefined,
  564. animateCycle:undefined,
  565. autoPlay:undefined,
  566. frames:[]
  567. })
  568. }
  569. if(props.pen.type){
  570. meta2d.setValue({
  571. id:props.pen.id,
  572. autoPlay:undefined,
  573. });
  574. }
  575. props.pen.animations.splice(i, 1);
  576. animate.value = undefined;
  577. };
  578. const changeAnimate = (item: any) => {
  579. const animate: any = animateList.find((elem: any) => {
  580. return elem.value === item.animate;
  581. });
  582. if (!animate) {
  583. return;
  584. }
  585. item.frames = deepClone(animate.data);
  586. };
  587. const play = (i: number) => {
  588. meta2d.startAnimate(props.pen.id, i);
  589. isPlaying.value = i;
  590. };
  591. const stop = () => {
  592. meta2d.stopAnimate(props.pen.id);
  593. isPlaying.value = -1;
  594. };
  595. const changeValue = (i) => {
  596. if(i===isPlaying.value){
  597. play(i);
  598. }
  599. };
  600. onUnmounted(() => {
  601. watcher();
  602. });
  603. </script>
  604. <style lang="postcss" scoped>
  605. .animations {
  606. height: 100%;
  607. .blank {
  608. height: 70%;
  609. img {
  610. padding: 16px;
  611. opacity: 0.9;
  612. }
  613. }
  614. :deep(.t-collapse) {
  615. .t-collapse-panel__header {
  616. .t-input {
  617. border-color: transparent;
  618. &:hover {
  619. border-color: var(--color-border-input);
  620. }
  621. }
  622. }
  623. .t-collapse-panel__icon:hover {
  624. background: none;
  625. svg {
  626. color: var(--color-primary);
  627. }
  628. }
  629. }
  630. }
  631. </style>