|
@@ -0,0 +1,411 @@
|
|
|
|
+<template>
|
|
|
|
+ <div class="animate-frames">
|
|
|
|
+ <div class="head">
|
|
|
|
+ <label>{{ animate.name }} </label>
|
|
|
|
+ <t-icon
|
|
|
|
+ name="close"
|
|
|
|
+ class="hover"
|
|
|
|
+ style="font-size: 16px"
|
|
|
|
+ @click="close"
|
|
|
|
+ />
|
|
|
|
+ </div>
|
|
|
|
+ <div style="height: calc(100% - 42px); overflow: auto">
|
|
|
|
+ <template v-if="animate.frames.length">
|
|
|
|
+ <t-collapse
|
|
|
|
+ v-model="openedCollapses"
|
|
|
|
+ :borderless="true"
|
|
|
|
+ :expand-on-row-click="true"
|
|
|
|
+ @change="onChangeCollapse"
|
|
|
|
+ >
|
|
|
|
+ <t-collapse-panel v-for="(item, i) in animate.frames" :value="i">
|
|
|
|
+ <template #header>
|
|
|
|
+ <label style="font-weight: normal">{{ `帧${i + 1}` }}</label>
|
|
|
|
+ </template>
|
|
|
|
+ <template #headerRightContent>
|
|
|
|
+ <t-space size="small" @click.stop>
|
|
|
|
+ <t-tooltip content="在当前帧后面添加动画帧">
|
|
|
|
+ <t-icon
|
|
|
|
+ name="folder-add"
|
|
|
|
+ class="hover ml-4"
|
|
|
|
+ @click="addFrame(i)"
|
|
|
|
+ />
|
|
|
|
+ </t-tooltip>
|
|
|
|
+ <t-tooltip content="添加属性">
|
|
|
|
+ <t-dropdown
|
|
|
|
+ :options="[
|
|
|
|
+ { content: '操作一', value: 1 },
|
|
|
|
+ { content: '操作二', value: 2 },
|
|
|
|
+ ]"
|
|
|
|
+ >
|
|
|
|
+ <t-icon name="file-add" class="hover ml-4" />
|
|
|
|
+ </t-dropdown>
|
|
|
|
+ </t-tooltip>
|
|
|
|
+ <t-popconfirm
|
|
|
|
+ content="确认删除该动画帧吗"
|
|
|
|
+ placement="left"
|
|
|
|
+ @confirm="animate.frames.splice(i, 1)"
|
|
|
|
+ >
|
|
|
|
+ <t-icon name="delete" class="hover ml-4" />
|
|
|
|
+ </t-popconfirm>
|
|
|
|
+ </t-space>
|
|
|
|
+ </template>
|
|
|
|
+ <section>
|
|
|
|
+ <div class="form-item">
|
|
|
|
+ <label>时长</label>
|
|
|
|
+ <t-input-number
|
|
|
|
+ v-model="item.duration"
|
|
|
|
+ theme="normal"
|
|
|
|
+ :min="1"
|
|
|
|
+ placeholder="毫秒"
|
|
|
|
+ suffix="ms"
|
|
|
|
+ />
|
|
|
|
+ </div>
|
|
|
|
+ <div class="form-item mt-8" v-for="prop in frameProps[i]">
|
|
|
|
+ <template v-if="propDescriptions[prop]">
|
|
|
|
+ <label>
|
|
|
|
+ {{ propDescriptions[prop].label }}
|
|
|
|
+ </label>
|
|
|
|
+
|
|
|
|
+ <t-input-number
|
|
|
|
+ v-if="propDescriptions[prop].type === 'number'"
|
|
|
|
+ v-model="item[prop]"
|
|
|
|
+ theme="normal"
|
|
|
|
+ :placeholder="propDescriptions[prop].placeholder"
|
|
|
|
+ :min="propDescriptions[prop].min"
|
|
|
|
+ :max="propDescriptions[prop].max"
|
|
|
|
+ :step="propDescriptions[prop].step"
|
|
|
|
+ />
|
|
|
|
+ <t-select
|
|
|
|
+ v-else-if="propDescriptions[prop].type === 'select'"
|
|
|
|
+ v-model="item[prop]"
|
|
|
|
+ :placeholder="propDescriptions[prop].placeholder"
|
|
|
|
+ :options="propDescriptions[prop].options"
|
|
|
|
+ />
|
|
|
|
+ <t-color-picker
|
|
|
|
+ v-else-if="propDescriptions[prop].type === 'color'"
|
|
|
|
+ v-model="item[prop]"
|
|
|
|
+ :placeholder="propDescriptions[prop].placeholder"
|
|
|
|
+ format="CSS"
|
|
|
|
+ :enable-alpha="true"
|
|
|
|
+ :color-modes="
|
|
|
|
+ propDescriptions[prop].colorModes ||
|
|
|
|
+ propDescriptions[prop].colorModesFn(
|
|
|
|
+ item[propDescriptions[prop].colorModesBind]
|
|
|
|
+ )
|
|
|
|
+ "
|
|
|
|
+ :show-primary-color-preview="false"
|
|
|
|
+ :clearable="true"
|
|
|
|
+ />
|
|
|
|
+ <t-switch
|
|
|
|
+ v-else-if="propDescriptions[prop].type === 'bool'"
|
|
|
|
+ v-model="item[prop]"
|
|
|
|
+ class="ml-8 mt-8"
|
|
|
|
+ size="small"
|
|
|
|
+ />
|
|
|
|
+ <t-select
|
|
|
|
+ v-else-if="propDescriptions[prop].type === 'child'"
|
|
|
|
+ v-model="item[prop]"
|
|
|
|
+ :placeholder="propDescriptions[prop].placeholder"
|
|
|
|
+ >
|
|
|
|
+ <t-option
|
|
|
|
+ v-for="(child, i) in selections.pen.children"
|
|
|
|
+ :key="i"
|
|
|
|
+ :value="i"
|
|
|
|
+ :label="`状态${i + 1}`"
|
|
|
|
+ />
|
|
|
|
+ </t-select>
|
|
|
|
+ <t-input
|
|
|
|
+ v-else
|
|
|
|
+ v-model="item[prop]"
|
|
|
|
+ :placeholder="propDescriptions[prop].placeholder"
|
|
|
|
+ />
|
|
|
|
+ </template>
|
|
|
|
+ <template v-else>
|
|
|
|
+ <label>
|
|
|
|
+ {{ prop }}
|
|
|
|
+ </label>
|
|
|
|
+ <t-input v-model="item[prop]" />
|
|
|
|
+ </template>
|
|
|
|
+ </div>
|
|
|
|
+ </section>
|
|
|
|
+ </t-collapse-panel>
|
|
|
|
+ </t-collapse>
|
|
|
|
+ <t-divider />
|
|
|
|
+ <div class="mt-16 px-16">
|
|
|
|
+ <t-button class="w-full" @click="addFrame" style="height: 30px"
|
|
|
|
+ >添加帧</t-button
|
|
|
|
+ >
|
|
|
|
+ </div>
|
|
|
|
+ </template>
|
|
|
|
+ <div class="flex column center blank" v-else>
|
|
|
|
+ <img src="/img/blank.png" />
|
|
|
|
+ <div class="gray center">还没有动画帧</div>
|
|
|
|
+ <div class="mt-8">
|
|
|
|
+ <t-button @click="addFrame" style="height: 30px">添加动画帧</t-button>
|
|
|
|
+ </div>
|
|
|
|
+ </div>
|
|
|
|
+ </div>
|
|
|
|
+ </div>
|
|
|
|
+</template>
|
|
|
|
+
|
|
|
|
+<script lang="ts" setup>
|
|
|
|
+import { onMounted, reactive, ref } from 'vue';
|
|
|
|
+
|
|
|
|
+import { useSelection } from '@/services/selections';
|
|
|
|
+
|
|
|
|
+const { animate } = defineProps<{
|
|
|
|
+ animate: { name: string; frames: any[] };
|
|
|
|
+}>();
|
|
|
|
+
|
|
|
|
+const emit = defineEmits(['close']);
|
|
|
|
+
|
|
|
|
+const { selections }: { selections: any } = useSelection();
|
|
|
|
+
|
|
|
|
+const openedCollapses = ref([0]);
|
|
|
|
+
|
|
|
|
+const frameProps: any = reactive({});
|
|
|
|
+
|
|
|
|
+const propDescriptions: any = {
|
|
|
|
+ visible: {
|
|
|
|
+ label: '显示',
|
|
|
|
+ type: 'bool',
|
|
|
|
+ sort: 0,
|
|
|
|
+ },
|
|
|
|
+ scale: {
|
|
|
|
+ label: '缩放',
|
|
|
|
+ type: 'number',
|
|
|
|
+ sort: 1,
|
|
|
|
+ min: 0.01,
|
|
|
|
+ max: 100,
|
|
|
|
+ },
|
|
|
|
+ rotate: {
|
|
|
|
+ label: '旋转',
|
|
|
|
+ type: 'number',
|
|
|
|
+ sort: 2,
|
|
|
|
+ min: 0,
|
|
|
|
+ max: 360,
|
|
|
|
+ placeholder: '°',
|
|
|
|
+ },
|
|
|
|
+ x: {
|
|
|
|
+ label: 'X位移',
|
|
|
|
+ type: 'number',
|
|
|
|
+ sort: 3,
|
|
|
|
+ placeholder: 'px',
|
|
|
|
+ },
|
|
|
|
+ y: {
|
|
|
|
+ label: 'Y位移',
|
|
|
|
+ type: 'number',
|
|
|
|
+ sort: 4,
|
|
|
|
+ placeholder: 'px',
|
|
|
|
+ },
|
|
|
|
+ color: {
|
|
|
|
+ label: '前景颜色',
|
|
|
|
+ type: 'color',
|
|
|
|
+ sort: 5,
|
|
|
|
+ },
|
|
|
|
+ bkType: {
|
|
|
|
+ label: '背景类型',
|
|
|
|
+ type: 'select',
|
|
|
|
+ options: [
|
|
|
|
+ {
|
|
|
|
+ label: '纯色',
|
|
|
|
+ value: 0,
|
|
|
|
+ },
|
|
|
|
+ {
|
|
|
|
+ label: '线性渐变',
|
|
|
|
+ value: 1,
|
|
|
|
+ },
|
|
|
|
+ {
|
|
|
|
+ label: '径向渐变',
|
|
|
|
+ value: 2,
|
|
|
|
+ },
|
|
|
|
+ ],
|
|
|
|
+ sort: 6,
|
|
|
|
+ },
|
|
|
|
+ background: {
|
|
|
|
+ label: '背景颜色',
|
|
|
|
+ type: 'color',
|
|
|
|
+ colorModesFn: (t: number) => {
|
|
|
|
+ if (t) {
|
|
|
|
+ return ['linear-gradient'];
|
|
|
|
+ } else {
|
|
|
|
+ return ['monochrome'];
|
|
|
|
+ }
|
|
|
|
+ },
|
|
|
|
+ colorModesBind: 'bkType',
|
|
|
|
+ sort: 7,
|
|
|
|
+ },
|
|
|
|
+ text: {
|
|
|
|
+ label: '文字',
|
|
|
|
+ sort: 8,
|
|
|
|
+ },
|
|
|
|
+ showChild: {
|
|
|
|
+ label: '状态',
|
|
|
|
+ type: 'child',
|
|
|
|
+ sort: 9,
|
|
|
|
+ },
|
|
|
|
+ flipX: {
|
|
|
|
+ label: '水平翻转',
|
|
|
|
+ type: 'bool',
|
|
|
|
+ sort: 10,
|
|
|
|
+ },
|
|
|
|
+ flipY: {
|
|
|
|
+ label: '垂直翻转',
|
|
|
|
+ type: 'bool',
|
|
|
|
+ sort: 11,
|
|
|
|
+ },
|
|
|
|
+ progress: {
|
|
|
|
+ label: '进度',
|
|
|
|
+ type: 'number',
|
|
|
|
+ step: 0.1,
|
|
|
|
+ min: 0,
|
|
|
|
+ max: 1,
|
|
|
|
+ placeholder: '0 - 1',
|
|
|
|
+ sort: 12,
|
|
|
|
+ },
|
|
|
|
+ progressColor: {
|
|
|
|
+ label: '进度颜色',
|
|
|
|
+ type: 'color',
|
|
|
|
+ colorModes: ['monochrome'],
|
|
|
|
+ sort: 13,
|
|
|
|
+ },
|
|
|
|
+ verticalProgress: {
|
|
|
|
+ label: '垂直进度',
|
|
|
|
+ type: 'bool',
|
|
|
|
+ sort: 14,
|
|
|
|
+ },
|
|
|
|
+ globalAlpha: {
|
|
|
|
+ label: '透明度',
|
|
|
|
+ type: 'number',
|
|
|
|
+ step: 0.1,
|
|
|
|
+ min: 0,
|
|
|
|
+ max: 1,
|
|
|
|
+ placeholder: '0 - 1',
|
|
|
|
+ sort: 15,
|
|
|
|
+ },
|
|
|
|
+ dash: {
|
|
|
|
+ label: '线条样式',
|
|
|
|
+ type: 'select',
|
|
|
|
+ options: [
|
|
|
|
+ {
|
|
|
|
+ label: `<svg xmlns="http://www.w3.org/2000/svg" version="1.1" style="height: 20px;">
|
|
|
|
+ <g fill="none" stroke="black" stroke-width="1">
|
|
|
|
+ <path d="M0 9 l85 0" />
|
|
|
|
+ </g>
|
|
|
|
+ </svg>`,
|
|
|
|
+ value: 0,
|
|
|
|
+ },
|
|
|
|
+ {
|
|
|
|
+ label: `<svg xmlns="http://www.w3.org/2000/svg" version="1.1" style="height: 20px;">
|
|
|
|
+ <g fill="none" stroke="black" stroke-width="1">
|
|
|
|
+ <path stroke-dasharray="5 5" d="M0 9 l85 0" />
|
|
|
|
+ </g>
|
|
|
|
+ </svg>`,
|
|
|
|
+ value: 1,
|
|
|
|
+ },
|
|
|
|
+ {
|
|
|
|
+ label: `<svg xmlns="http://www.w3.org/2000/svg" version="1.1" style="height: 20px;">
|
|
|
|
+ <g fill="none" stroke="black" stroke-width="1">
|
|
|
|
+ <path stroke-dasharray="10 10" d="M0 9 l85 0" />
|
|
|
|
+ </g>
|
|
|
|
+ </svg>`,
|
|
|
|
+ value: 2,
|
|
|
|
+ },
|
|
|
|
+ {
|
|
|
|
+ label: `<svg xmlns="http://www.w3.org/2000/svg" version="1.1" style="height: 20px;">
|
|
|
|
+ <g fill="none" stroke="black" stroke-width="1">
|
|
|
|
+ <path stroke-dasharray="10 10 2 10" d="M0 9 l85 0" />
|
|
|
|
+ </g>
|
|
|
|
+ </svg>`,
|
|
|
|
+ value: 3,
|
|
|
|
+ },
|
|
|
|
+ ],
|
|
|
|
+ sort: 16,
|
|
|
|
+ },
|
|
|
|
+};
|
|
|
|
+
|
|
|
|
+onMounted(() => {
|
|
|
|
+ onChangeCollapse([0]);
|
|
|
|
+});
|
|
|
|
+
|
|
|
|
+const addFrame = (i?: number) => {
|
|
|
|
+ if (i == undefined) {
|
|
|
|
+ i = animate.frames.length - 1;
|
|
|
|
+ }
|
|
|
|
+ animate.frames.splice(i, 0, {});
|
|
|
|
+
|
|
|
|
+ openedCollapses.value = [i + 1];
|
|
|
|
+ onChangeCollapse(openedCollapses.value);
|
|
|
|
+};
|
|
|
|
+
|
|
|
|
+const onChangeCollapse = (val: number[]) => {
|
|
|
|
+ if (!animate.frames.length) {
|
|
|
|
+ return;
|
|
|
|
+ }
|
|
|
|
+ for (const i of val) {
|
|
|
|
+ frameProps[i] = [];
|
|
|
|
+ for (const key in animate.frames[i]) {
|
|
|
|
+ if (key !== 'duration') {
|
|
|
|
+ frameProps[i].push(key);
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ frameProps[i].sort((a: any, b: any) => {
|
|
|
|
+ a.sort > b.sort ? 1 : -1;
|
|
|
|
+ });
|
|
|
|
+ }
|
|
|
|
+};
|
|
|
|
+
|
|
|
|
+const close = () => {
|
|
|
|
+ emit('close');
|
|
|
|
+};
|
|
|
|
+</script>
|
|
|
|
+<style lang="postcss" scoped>
|
|
|
|
+.animate-frames {
|
|
|
|
+ position: fixed;
|
|
|
|
+ top: 100px;
|
|
|
|
+ bottom: 12px;
|
|
|
|
+ right: 308px;
|
|
|
|
+ width: 300px;
|
|
|
|
+ border: 1px solid var(--color-border-input);
|
|
|
|
+ border-radius: 4px;
|
|
|
|
+ box-shadow: rgba(39, 54, 78, 0.08) 0px 2px 10px 0px,
|
|
|
|
+ rgba(39, 54, 78, 0.1) 4px 12px 40px 0px;
|
|
|
|
+
|
|
|
|
+ & > .head {
|
|
|
|
+ display: flex;
|
|
|
|
+ align-items: center;
|
|
|
|
+ justify-content: space-between;
|
|
|
|
+ background-color: var(--color-background-popup);
|
|
|
|
+ padding: 12px 16px;
|
|
|
|
+ font-size: 14px;
|
|
|
|
+ line-height: 1;
|
|
|
|
+ color: var(--color-title);
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ .blank {
|
|
|
|
+ height: 70%;
|
|
|
|
+ img {
|
|
|
|
+ padding: 16px;
|
|
|
|
+ opacity: 0.9;
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ :deep(.t-collapse) {
|
|
|
|
+ .t-collapse-panel__header {
|
|
|
|
+ .t-input {
|
|
|
|
+ border-color: transparent;
|
|
|
|
+ &:hover {
|
|
|
|
+ border-color: var(--color-border-input);
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ .t-collapse-panel__icon:hover {
|
|
|
|
+ background: none;
|
|
|
|
+ svg {
|
|
|
|
+ color: var(--color-primary);
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+}
|
|
|
|
+</style>
|