feat: 优化编辑器数据传递方式
parent
52432821b4
commit
497b1a3dd4
File diff suppressed because one or more lines are too long
|
|
@ -1,23 +1,17 @@
|
||||||
<template>
|
<template>
|
||||||
<font-render :data="data.params.fontCh">
|
<font-render :data="model.params.fontCh">
|
||||||
{{ time }}
|
{{ time }}
|
||||||
</font-render>
|
</font-render>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { dayjs } from "@/libs/dayjs";
|
import { dayjs } from "@/libs/dayjs";
|
||||||
import { PropType, onMounted, onUnmounted, ref } from "vue";
|
import { onMounted, onUnmounted, ref } from "vue";
|
||||||
import { FontRender } from "../components/font";
|
import { FontRender } from "../components/font";
|
||||||
import { Time } from "./interface";
|
import { Time } from "./interface";
|
||||||
|
|
||||||
const props = defineProps({
|
const model = defineModel<Time>({ required: true });
|
||||||
data: {
|
const format = computed(() => model.value.params.fontCh.content || "HH:mm:ss");
|
||||||
type: Object as PropType<Time>,
|
|
||||||
required: true,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
const format = computed(() => props.data.params.fontCh.content || "HH:mm:ss");
|
|
||||||
const time = ref(dayjs().format(format.value));
|
const time = ref(dayjs().format(format.value));
|
||||||
let timer: any = null;
|
let timer: any = null;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -40,3 +40,16 @@ export interface Container {
|
||||||
*/
|
*/
|
||||||
bgColor: string;
|
bgColor: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const defaultContainer: Container = {
|
||||||
|
id: 11,
|
||||||
|
title: "国庆节喜庆版式设计",
|
||||||
|
description: "适用于国庆节1日-7日间上午9:00-10:00播出的版式设计",
|
||||||
|
x: 0,
|
||||||
|
y: 0,
|
||||||
|
zoom: 0.7,
|
||||||
|
width: 1920,
|
||||||
|
height: 1080,
|
||||||
|
bgImage: "",
|
||||||
|
bgColor: "#ffffff",
|
||||||
|
};
|
||||||
|
|
|
||||||
|
|
@ -12,7 +12,7 @@ export interface Context {
|
||||||
/**
|
/**
|
||||||
* 运行时数据
|
* 运行时数据
|
||||||
*/
|
*/
|
||||||
current: Ref<Current>;
|
currentBlock: Ref<Block | null>;
|
||||||
/**
|
/**
|
||||||
* 组件列表
|
* 组件列表
|
||||||
*/
|
*/
|
||||||
|
|
|
||||||
|
|
@ -1,15 +1,144 @@
|
||||||
import { Ref } from "vue";
|
import { Container, defaultContainer } from "./container";
|
||||||
import { Container } from "./container";
|
|
||||||
import { Block } from "./block";
|
import { Block } from "./block";
|
||||||
|
import { ReferenceLine } from "./ref-line";
|
||||||
|
import { BlockerMap } from "../blocks";
|
||||||
|
import { cloneDeep } from "lodash-es";
|
||||||
|
import { CSSProperties, InjectionKey } from "vue";
|
||||||
|
|
||||||
/**
|
export const useEditor = () => {
|
||||||
* TODO
|
/**
|
||||||
*/
|
* 画布设置
|
||||||
export class Editor {
|
*/
|
||||||
public container: Ref<Container> = {} as Ref<Container>;
|
const container = ref<Container>({ ...defaultContainer });
|
||||||
public content: Ref<Block> = {} as Ref<Block>;
|
/**
|
||||||
|
* 组件列表
|
||||||
|
*/
|
||||||
|
const blocks = ref<Block[]>([]);
|
||||||
|
/**
|
||||||
|
* 选中组件
|
||||||
|
*/
|
||||||
|
const currentBlock = ref<Block | null>(null);
|
||||||
|
/**
|
||||||
|
* 参考线
|
||||||
|
*/
|
||||||
|
const refLine = new ReferenceLine(blocks, currentBlock as any);
|
||||||
|
|
||||||
constructor() {
|
/**
|
||||||
// TODO
|
* 添加组件
|
||||||
}
|
* @param type 组件类型
|
||||||
}
|
* @param x 横坐标
|
||||||
|
* @param y 纵坐标
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
const addBlock = (type: string, x = 0, y = 0) => {
|
||||||
|
if (!type) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const blocker = BlockerMap[type];
|
||||||
|
if (!blocker) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const ids = blocks.value.map((i) => Number(i.id));
|
||||||
|
const maxId = ids.length ? Math.max.apply(null, ids) : 0;
|
||||||
|
const id = (maxId + 1).toString();
|
||||||
|
const title = `${blocker.title}${id}`;
|
||||||
|
blocks.value.push({
|
||||||
|
...cloneDeep(blocker.initial),
|
||||||
|
id,
|
||||||
|
x,
|
||||||
|
y,
|
||||||
|
title,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 移除组件
|
||||||
|
* @param block 组件
|
||||||
|
*/
|
||||||
|
const rmBlock = (block: Block) => {
|
||||||
|
const index = blocks.value.indexOf(block);
|
||||||
|
if (index > -1) {
|
||||||
|
blocks.value.splice(index, 1);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 格式化组件样式
|
||||||
|
* @param block 组件
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
const formatBlockStyle = (block: Block) => {
|
||||||
|
const { bgColor, bgImage } = block;
|
||||||
|
return {
|
||||||
|
backgroundColor: bgColor,
|
||||||
|
backgroundImage: bgImage ? `url(${bgImage})` : undefined,
|
||||||
|
backgroundSize: "100% 100%",
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 格式化容器样式
|
||||||
|
* @param container 容器
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
const formatContainerStyle = (container: Container) => {
|
||||||
|
const { width, height, bgColor, bgImage, zoom, x, y } = container;
|
||||||
|
return {
|
||||||
|
position: "absolute",
|
||||||
|
width: `${width}px`,
|
||||||
|
height: `${height}px`,
|
||||||
|
backgroundColor: bgColor,
|
||||||
|
backgroundImage: bgImage ? `url(${bgImage})` : undefined,
|
||||||
|
backgroundSize: "100% 100%",
|
||||||
|
transform: `translate3d(${x}px, ${y}px, 0) scale(${zoom})`,
|
||||||
|
} as CSSProperties;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 设置当前选中组件
|
||||||
|
* @param block 组件
|
||||||
|
*/
|
||||||
|
const setCurrentBlock = (block: Block | null) => {
|
||||||
|
for (const item of blocks.value) {
|
||||||
|
item.actived = false;
|
||||||
|
}
|
||||||
|
if (!block) {
|
||||||
|
currentBlock.value = null;
|
||||||
|
} else {
|
||||||
|
block.actived = true;
|
||||||
|
currentBlock.value = block;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 重置画布坐标和比例
|
||||||
|
*/
|
||||||
|
const setContainerOrigin = () => {
|
||||||
|
container.value.x = 0;
|
||||||
|
container.value.y = 0;
|
||||||
|
const el = document.querySelector(".juetan-editor-container");
|
||||||
|
if (el) {
|
||||||
|
const { width, height } = el.getBoundingClientRect();
|
||||||
|
const wZoom = width / container.value.width;
|
||||||
|
const hZoom = height / container.value.width;
|
||||||
|
const zoom = Math.floor((wZoom > hZoom ? wZoom : hZoom) * 10000) / 10000;
|
||||||
|
container.value.zoom = zoom;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return {
|
||||||
|
container,
|
||||||
|
blocks,
|
||||||
|
currentBlock,
|
||||||
|
refLine,
|
||||||
|
BlockerMap,
|
||||||
|
setCurrentBlock,
|
||||||
|
setContainerOrigin,
|
||||||
|
addBlock,
|
||||||
|
rmBlock,
|
||||||
|
formatBlockStyle,
|
||||||
|
formatContainerStyle,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
export const EditorKey = Symbol("EditorKey") as InjectionKey<ReturnType<typeof useEditor>>;
|
||||||
|
|
|
||||||
|
|
@ -2,17 +2,21 @@
|
||||||
<a-modal :visible="visible" :fullscreen="true" :footer="false" class="ani-modal">
|
<a-modal :visible="visible" :fullscreen="true" :footer="false" class="ani-modal">
|
||||||
<div class="w-full h-full bg-slate-100 grid grid-rows-[auto_1fr] select-none">
|
<div class="w-full h-full bg-slate-100 grid grid-rows-[auto_1fr] select-none">
|
||||||
<div class="h-13 bg-white border-b border-slate-200 z-10">
|
<div class="h-13 bg-white border-b border-slate-200 z-10">
|
||||||
<panel-header></panel-header>
|
<panel-header v-model:container="container"></panel-header>
|
||||||
</div>
|
</div>
|
||||||
<div class="grid grid-cols-[auto_1fr_auto] overflow-hidden">
|
<div class="grid grid-cols-[auto_1fr_auto] overflow-hidden">
|
||||||
<div class="h-full overflow-hidden bg-white shadow-[2px_0_6px_rgba(0,0,0,.05)] z-10">
|
<div class="h-full overflow-hidden bg-white shadow-[2px_0_6px_rgba(0,0,0,.05)] z-10">
|
||||||
<panel-left></panel-left>
|
<panel-left @rm-block="rmBlock" @current-block="setCurrentBlock"></panel-left>
|
||||||
</div>
|
</div>
|
||||||
<div class="w-full h-full">
|
<div class="w-full h-full">
|
||||||
<panel-main></panel-main>
|
<panel-main
|
||||||
|
v-model:rightPanelCollapsed="rightPanelCollapsed"
|
||||||
|
@add-block="addBlock"
|
||||||
|
@current-block="setCurrentBlock"
|
||||||
|
></panel-main>
|
||||||
</div>
|
</div>
|
||||||
<div class="h-full overflow-hidden bg-white shadow-[-2px_0_6px_rgba(0,0,0,.05)]">
|
<div class="h-full overflow-hidden bg-white shadow-[-2px_0_6px_rgba(0,0,0,.05)]">
|
||||||
<panel-right></panel-right>
|
<panel-right v-model:collapsed="rightPanelCollapsed" v-model:block="currentBlock"></panel-right>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -21,7 +25,7 @@
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { Block, Container, ContextKey, ReferenceLine } from "./config";
|
import { EditorKey, useEditor } from "./config/editor";
|
||||||
import PanelHeader from "./panel-header/index.vue";
|
import PanelHeader from "./panel-header/index.vue";
|
||||||
import PanelLeft from "./panel-left/index.vue";
|
import PanelLeft from "./panel-left/index.vue";
|
||||||
import PanelMain from "./panel-main/index.vue";
|
import PanelMain from "./panel-main/index.vue";
|
||||||
|
|
@ -29,41 +33,14 @@ import PanelRight from "./panel-right/index.vue";
|
||||||
import AppnifyPreview from "./preview/index.vue";
|
import AppnifyPreview from "./preview/index.vue";
|
||||||
|
|
||||||
const visible = defineModel("visible", { default: false });
|
const visible = defineModel("visible", { default: false });
|
||||||
|
const rightPanelCollapsed = ref(false);
|
||||||
|
const leftPanelCollapsed = ref(false);
|
||||||
const preview = ref(false);
|
const preview = ref(false);
|
||||||
|
|
||||||
/**
|
const editor = useEditor();
|
||||||
* 运行时上下文
|
const { container, blocks, currentBlock, addBlock, rmBlock, setCurrentBlock } = editor;
|
||||||
*/
|
|
||||||
const current = ref({
|
|
||||||
block: null as Block | null,
|
|
||||||
rightPanelCollapsed: false,
|
|
||||||
});
|
|
||||||
|
|
||||||
/**
|
provide(EditorKey, editor);
|
||||||
* 组件列表
|
|
||||||
*/
|
|
||||||
const blocks = ref<Block[]>([]);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 参考线
|
|
||||||
*/
|
|
||||||
const refLine = new ReferenceLine(blocks, current);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 画布容器
|
|
||||||
*/
|
|
||||||
const container = ref<Container>({
|
|
||||||
id: 11,
|
|
||||||
title: "国庆节喜庆版式设计",
|
|
||||||
description: "适用于国庆节1日-7日间上午9:00-10:00播出的版式设计",
|
|
||||||
x: 0,
|
|
||||||
y: 0,
|
|
||||||
zoom: 0.7,
|
|
||||||
width: 1920,
|
|
||||||
height: 1080,
|
|
||||||
bgImage: "",
|
|
||||||
bgColor: "#ffffff",
|
|
||||||
});
|
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
loadData();
|
loadData();
|
||||||
|
|
@ -93,54 +70,6 @@ const loadData = async () => {
|
||||||
container.value = data.container;
|
container.value = data.container;
|
||||||
blocks.value = data.children;
|
blocks.value = data.children;
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
|
||||||
* 设置当前选中的组件
|
|
||||||
*/
|
|
||||||
const setCurrentBlock = (block: Block | null) => {
|
|
||||||
for (const block of blocks.value) {
|
|
||||||
block.actived = false;
|
|
||||||
}
|
|
||||||
if (!block) {
|
|
||||||
current.value.block = null;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
block.actived = true;
|
|
||||||
current.value.block = block;
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 恢复画布到原始比例和远点
|
|
||||||
*/
|
|
||||||
const setContainerOrigin = () => {
|
|
||||||
container.value.x = 0;
|
|
||||||
container.value.y = 0;
|
|
||||||
const el = document.querySelector(".juetan-editor-container");
|
|
||||||
if (el) {
|
|
||||||
const { width, height } = el.getBoundingClientRect();
|
|
||||||
const wZoom = width / container.value.width;
|
|
||||||
const hZoom = height / container.value.width;
|
|
||||||
const zoom = Math.floor((wZoom > hZoom ? wZoom : hZoom) * 10000) / 10000;
|
|
||||||
container.value.zoom = zoom;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 提供上下文注入
|
|
||||||
*/
|
|
||||||
provide(ContextKey, {
|
|
||||||
current,
|
|
||||||
container,
|
|
||||||
blocks,
|
|
||||||
refLine,
|
|
||||||
setCurrentBlock,
|
|
||||||
setContainerOrigin,
|
|
||||||
loadData,
|
|
||||||
saveData,
|
|
||||||
preview() {
|
|
||||||
preview.value = true;
|
|
||||||
},
|
|
||||||
});
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="less">
|
<style lang="less">
|
||||||
|
|
|
||||||
|
|
@ -23,15 +23,19 @@
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { Message } from "@arco-design/web-vue";
|
import { Message } from "@arco-design/web-vue";
|
||||||
import { ContextKey } from "../config";
|
import { Container, ContextKey } from "../config";
|
||||||
import AniTexter from "../panel-main/components/texter.vue";
|
import AniTexter from "../panel-main/components/texter.vue";
|
||||||
|
|
||||||
const { saveData, container } = inject(ContextKey)!;
|
const { saveData } = inject(ContextKey)!;
|
||||||
|
|
||||||
const onSaveData = () => {
|
const onSaveData = () => {
|
||||||
saveData();
|
saveData();
|
||||||
Message.success("保存成功");
|
Message.success("保存成功");
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const container = defineModel<Container>("container", {
|
||||||
|
required: true,
|
||||||
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped></style>
|
<style scoped></style>
|
||||||
|
|
|
||||||
|
|
@ -41,7 +41,7 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div v-show="!collapsed">
|
<div v-show="!collapsed">
|
||||||
<ul v-show="key === 'list'" class="list-none px-2 grid gap-2" @dragstart="onDragStart" @dragover="onDragOver">
|
<ul v-show="key === 'list'" class="list-none px-2 grid gap-2" @dragstart="onDragStart" @dragover.prevent>
|
||||||
<li
|
<li
|
||||||
v-for="item in blockList"
|
v-for="item in blockList"
|
||||||
:key="item.type"
|
:key="item.type"
|
||||||
|
|
@ -65,11 +65,11 @@
|
||||||
:key="item.id"
|
:key="item.id"
|
||||||
class="group h-8 w-full overflow-hidden grid grid-cols-[auto_1fr_auto] items-center gap-2 bg-gray-100 text-gray-500 px-2 py-1 rounded border border-transparent"
|
class="group h-8 w-full overflow-hidden grid grid-cols-[auto_1fr_auto] items-center gap-2 bg-gray-100 text-gray-500 px-2 py-1 rounded border border-transparent"
|
||||||
:class="{
|
:class="{
|
||||||
'!bg-brand-50': current.block === item,
|
'!bg-brand-50': currentBlock === item,
|
||||||
'!text-brand-500': current.block === item,
|
'!text-brand-500': currentBlock === item,
|
||||||
'!border-brand-300': current.block === item,
|
'!border-brand-300': currentBlock === item,
|
||||||
}"
|
}"
|
||||||
@click="setCurrentBlock(item)"
|
@click="emit('current-block', item)"
|
||||||
>
|
>
|
||||||
<div class="">
|
<div class="">
|
||||||
<i class="text-base" :class="getIcon(item.type)"></i>
|
<i class="text-base" :class="getIcon(item.type)"></i>
|
||||||
|
|
@ -80,7 +80,7 @@
|
||||||
<div class="w-4">
|
<div class="w-4">
|
||||||
<i
|
<i
|
||||||
class="!hidden !group-hover:inline-block text-gray-400 hover:text-gray-700 icon-park-outline-delete !text-xs"
|
class="!hidden !group-hover:inline-block text-gray-400 hover:text-gray-700 icon-park-outline-delete !text-xs"
|
||||||
@click="onDeleteBlock($event, item)"
|
@click.prevent="emit('rm-block', item)"
|
||||||
></i>
|
></i>
|
||||||
</div>
|
</div>
|
||||||
</li>
|
</li>
|
||||||
|
|
@ -90,13 +90,19 @@
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { BlockerMap, getIcon } from "../blocks";
|
import { getIcon } from "../blocks";
|
||||||
import { Block, ContextKey } from "../config";
|
import { Block } from "../config";
|
||||||
|
import { EditorKey } from "../config/editor";
|
||||||
|
|
||||||
const { blocks, current, setCurrentBlock } = inject(ContextKey)!;
|
const { blocks, currentBlock, BlockerMap } = inject(EditorKey)!;
|
||||||
const blockList = Object.values(BlockerMap);
|
const blockList = Object.values(BlockerMap);
|
||||||
const collapsed = ref(false);
|
const collapsed = ref(false);
|
||||||
const key = ref("list");
|
const key = ref<"list" | "data">("list");
|
||||||
|
|
||||||
|
const emit = defineEmits<{
|
||||||
|
(event: "rm-block", block: Block): void;
|
||||||
|
(event: "current-block", block: Block | null): void;
|
||||||
|
}>();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 拖拽开始时设置数据
|
* 拖拽开始时设置数据
|
||||||
|
|
@ -104,25 +110,6 @@ const key = ref("list");
|
||||||
const onDragStart = (e: DragEvent) => {
|
const onDragStart = (e: DragEvent) => {
|
||||||
e.dataTransfer?.setData("type", (e.target as HTMLElement).dataset.type!);
|
e.dataTransfer?.setData("type", (e.target as HTMLElement).dataset.type!);
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
|
||||||
* 拖拽时阻止默认行为
|
|
||||||
*/
|
|
||||||
const onDragOver = (e: Event) => {
|
|
||||||
console.log("over");
|
|
||||||
e.preventDefault();
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 删除组件
|
|
||||||
*/
|
|
||||||
const onDeleteBlock = async (e: Event, block: Block) => {
|
|
||||||
e.preventDefault();
|
|
||||||
const index = blocks.value.indexOf(block);
|
|
||||||
if (index > -1) {
|
|
||||||
blocks.value.splice(index, 1);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped></style>
|
<style scoped></style>
|
||||||
|
|
|
||||||
|
|
@ -64,10 +64,7 @@ const onItemDragging = (rect: any) => {
|
||||||
rect.left += x;
|
rect.left += x;
|
||||||
rect.top += y;
|
rect.top += y;
|
||||||
}
|
}
|
||||||
props.data.x = rect.left;
|
onItemResizing(rect);
|
||||||
props.data.y = rect.top;
|
|
||||||
props.data.w = rect.width;
|
|
||||||
props.data.h = rect.height;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
||||||
|
|
@ -58,12 +58,12 @@
|
||||||
</a-form>
|
</a-form>
|
||||||
</template>
|
</template>
|
||||||
</a-popover>
|
</a-popover>
|
||||||
<a-tooltip :content="current.rightPanelCollapsed ? '展开' : '折叠'" position="bottom">
|
<a-tooltip :content="rightPanelCollapsed ? '展开' : '折叠'" position="bottom">
|
||||||
<a-button type="text" @click="current.rightPanelCollapsed = !current.rightPanelCollapsed">
|
<a-button type="text" @click="rightPanelCollapsed = !rightPanelCollapsed">
|
||||||
<template #icon>
|
<template #icon>
|
||||||
<i
|
<i
|
||||||
class="text-base !text-gray-600"
|
class="text-base !text-gray-600"
|
||||||
:class="current.rightPanelCollapsed ? 'icon-park-outline-expand-right' : 'icon-park-outline-expand-left'"
|
:class="rightPanelCollapsed ? 'icon-park-outline-expand-right' : 'icon-park-outline-expand-left'"
|
||||||
></i>
|
></i>
|
||||||
</template>
|
</template>
|
||||||
</a-button>
|
</a-button>
|
||||||
|
|
@ -78,7 +78,9 @@ import InputImage from "../../components/InputImage.vue";
|
||||||
import { ContextKey } from "../../config";
|
import { ContextKey } from "../../config";
|
||||||
import AniTexter from "./texter.vue";
|
import AniTexter from "./texter.vue";
|
||||||
|
|
||||||
const { container, blocks, current, preview, setContainerOrigin } = inject(ContextKey)!;
|
const { container, blocks, preview, setContainerOrigin } = inject(ContextKey)!;
|
||||||
|
|
||||||
|
const rightPanelCollapsed = defineModel<boolean>();
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped></style>
|
<style scoped></style>
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
<template>
|
<template>
|
||||||
<div class="h-full grid grid-rows-[auto_1fr]">
|
<div class="h-full grid grid-rows-[auto_1fr]">
|
||||||
<div class="h-10">
|
<div class="h-10">
|
||||||
<ani-header :container="container"></ani-header>
|
<ani-header :container="container" v-model:rightPanelCollapsed="rightPanelCollapsed"></ani-header>
|
||||||
</div>
|
</div>
|
||||||
<div class="h-full w-full overflow-hidden p-4">
|
<div class="h-full w-full overflow-hidden p-4">
|
||||||
<div
|
<div
|
||||||
|
|
@ -50,40 +50,34 @@
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { cloneDeep } from "lodash-es";
|
import { Block, Scene } from "../config";
|
||||||
import { CSSProperties } from "vue";
|
|
||||||
import { BlockerMap } from "../blocks";
|
|
||||||
import { ContextKey, Scene } from "../config";
|
|
||||||
import AniBlock from "./components/block.vue";
|
import AniBlock from "./components/block.vue";
|
||||||
import AniHeader from "./components/header.vue";
|
import AniHeader from "./components/header.vue";
|
||||||
|
import { EditorKey } from "../config/editor";
|
||||||
|
|
||||||
const { blocks, container, refLine, setCurrentBlock } = inject(ContextKey)!;
|
const rightPanelCollapsed = defineModel<boolean>();
|
||||||
|
|
||||||
|
const { blocks, container, refLine, formatContainerStyle } = inject(EditorKey)!;
|
||||||
const scene = new Scene(container);
|
const scene = new Scene(container);
|
||||||
|
|
||||||
|
const emit = defineEmits<{
|
||||||
|
(event: "add-block", type: string, x?: number, y?: number): void;
|
||||||
|
(event: "current-block", block: Block | null): void;
|
||||||
|
}>();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 清空当前组件
|
* 清空当前组件
|
||||||
*/
|
*/
|
||||||
const onClick = (e: Event) => {
|
const onClick = (e: Event) => {
|
||||||
if (e.target === e.currentTarget) {
|
if (e.target === e.currentTarget) {
|
||||||
setCurrentBlock(null);
|
emit("current-block", null);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 容器样式
|
* 容器样式
|
||||||
*/
|
*/
|
||||||
const containerStyle = computed(() => {
|
const containerStyle = computed(() => formatContainerStyle(container.value));
|
||||||
const { width, height, bgColor, bgImage, zoom, x, y } = container.value;
|
|
||||||
return {
|
|
||||||
position: "absolute",
|
|
||||||
width: `${width}px`,
|
|
||||||
height: `${height}px`,
|
|
||||||
backgroundColor: bgColor,
|
|
||||||
backgroundImage: bgImage ? `url(${bgImage})` : undefined,
|
|
||||||
backgroundSize: "100% 100%",
|
|
||||||
transform: `translate3d(${x}px, ${y}px, 0) scale(${zoom})`,
|
|
||||||
} as CSSProperties;
|
|
||||||
});
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 接收拖拽并新增组件
|
* 接收拖拽并新增组件
|
||||||
|
|
@ -91,23 +85,11 @@ const containerStyle = computed(() => {
|
||||||
const onDragDrop = (e: DragEvent) => {
|
const onDragDrop = (e: DragEvent) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
|
|
||||||
const type = e.dataTransfer?.getData("type");
|
const type = e.dataTransfer?.getData("type");
|
||||||
if (!type) {
|
if (!type) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const blocker = BlockerMap[type];
|
emit("add-block", type, e.offsetX, e.offsetY);
|
||||||
const currentIds = blocks.value.map((item) => Number(item.id));
|
|
||||||
const maxId = currentIds.length ? Math.max.apply(null, currentIds) : 0;
|
|
||||||
const id = (maxId + 1).toString();
|
|
||||||
const title = `${blocker.title}${id}`;
|
|
||||||
blocks.value.push({
|
|
||||||
...cloneDeep(blocker.initial),
|
|
||||||
id,
|
|
||||||
title,
|
|
||||||
x: e.offsetX,
|
|
||||||
y: e.offsetY,
|
|
||||||
});
|
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,13 +1,13 @@
|
||||||
<template>
|
<template>
|
||||||
<div class="h-full w-[248px] overflow-hidden" :style="`display: ${current.rightPanelCollapsed ? 'none' : 'block'}`">
|
<div class="h-full w-[248px] overflow-hidden" :style="`display: ${collapsed ? 'none' : 'block'}`">
|
||||||
<div v-if="current.block" class="p-3">
|
<div v-if="block" class="p-3">
|
||||||
<a-radio-group type="button" default-value="1" class="w-full mb-2">
|
<a-radio-group type="button" default-value="1" class="w-full mb-2">
|
||||||
<a-radio value="1">属性</a-radio>
|
<a-radio value="1">属性</a-radio>
|
||||||
<a-radio value="2">文本</a-radio>
|
<a-radio value="2">文本</a-radio>
|
||||||
</a-radio-group>
|
</a-radio-group>
|
||||||
<a-form :model="{}" layout="vertical">
|
<a-form :model="{}" layout="vertical">
|
||||||
<div class="muti-form-item mt-2">
|
<div class="muti-form-item mt-2">
|
||||||
<component :is="BlockerMap[current.block.type].option" :data="current.block" />
|
<component :is="BlockerMap[block.type].option" v-model="block" />
|
||||||
</div>
|
</div>
|
||||||
</a-form>
|
</a-form>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -19,9 +19,10 @@
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { BlockerMap } from "../blocks";
|
import { BlockerMap } from "../blocks";
|
||||||
import { ContextKey } from "../config";
|
import { Block } from "../config";
|
||||||
|
|
||||||
const { current } = inject(ContextKey)!;
|
const collapsed = defineModel<boolean>();
|
||||||
|
const block = defineModel<Block | null>();
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped></style>
|
<style scoped></style>
|
||||||
|
|
|
||||||
|
|
@ -39,9 +39,9 @@
|
||||||
import { Message } from "@arco-design/web-vue";
|
import { Message } from "@arco-design/web-vue";
|
||||||
import { useFullscreen } from "@vueuse/core";
|
import { useFullscreen } from "@vueuse/core";
|
||||||
import { BlockerMap } from "../blocks";
|
import { BlockerMap } from "../blocks";
|
||||||
import { ContextKey } from "../config";
|
import { EditorKey } from "../config/editor";
|
||||||
|
|
||||||
const { container, blocks } = inject(ContextKey)!;
|
const { container, blocks } = inject(EditorKey)!;
|
||||||
|
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
visible: {
|
visible: {
|
||||||
|
|
|
||||||
|
|
@ -8,9 +8,6 @@ const signoutlist = ["/login"];
|
||||||
export const authGuard: NavigationGuardWithThis<undefined> = async function (to) {
|
export const authGuard: NavigationGuardWithThis<undefined> = async function (to) {
|
||||||
// 放在外面,pinia-plugin-peristedstate 插件会失效
|
// 放在外面,pinia-plugin-peristedstate 插件会失效
|
||||||
const userStore = useUserStore(store);
|
const userStore = useUserStore(store);
|
||||||
if (to.meta?.auth === false) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
if (whitelist.includes(to.path) || to.name === "_all") {
|
if (whitelist.includes(to.path) || to.name === "_all") {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue