feat: 重构编辑器目录
parent
497b1a3dd4
commit
c648519d42
File diff suppressed because one or more lines are too long
|
|
@ -1,5 +1,5 @@
|
|||
import { defineBlocker } from "../../config";
|
||||
import { font } from "../components/font";
|
||||
import { defineBlocker } from "../../core";
|
||||
import { font } from "../font";
|
||||
import { Date } from "./interface";
|
||||
import Option from "./option.vue";
|
||||
import Render from "./render.vue";
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import { Block } from "../../config";
|
||||
import { Font } from "../components/font";
|
||||
import { Block } from "../../core";
|
||||
import { Font } from "../font";
|
||||
|
||||
export interface DatePrams {
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -1,10 +1,10 @@
|
|||
<template>
|
||||
<base-option :data="data"></base-option>
|
||||
<base-option v-model="model"></base-option>
|
||||
<a-divider></a-divider>
|
||||
<font-option :data="data.params.fontCh">
|
||||
<font-option v-model="model.params.fontCh">
|
||||
<a-form-item label="日期格式">
|
||||
<a-auto-complete
|
||||
v-model="data.params.fontCh.content"
|
||||
v-model="model.params.fontCh.content"
|
||||
:data="FomatSuguestions"
|
||||
:allow-clear="true"
|
||||
placeholder="例如 HH:mm:ss"
|
||||
|
|
@ -25,17 +25,11 @@
|
|||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { PropType } from "vue";
|
||||
import BaseOption from "../../components/BaseOption.vue";
|
||||
import { FontOption } from "../components/font";
|
||||
import { FontOption } from "../font";
|
||||
import { Date, FomatSuguestions } from "./interface";
|
||||
|
||||
defineProps({
|
||||
data: {
|
||||
type: Object as PropType<Date>,
|
||||
required: true,
|
||||
},
|
||||
});
|
||||
const model = defineModel<Date>({ required: true });
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
|
|
@ -45,4 +39,4 @@ defineProps({
|
|||
}
|
||||
}
|
||||
</style>
|
||||
../components/font
|
||||
../components/font ../font
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@
|
|||
<script setup lang="ts">
|
||||
import { dayjs } from "@/libs/dayjs";
|
||||
import { PropType } from "vue";
|
||||
import { FontRender } from "../components/font";
|
||||
import { FontRender } from "../font";
|
||||
import { Date } from "./interface";
|
||||
|
||||
defineProps({
|
||||
|
|
@ -19,4 +19,4 @@ defineProps({
|
|||
</script>
|
||||
|
||||
<style scoped></style>
|
||||
../components/font
|
||||
../font
|
||||
|
|
|
|||
|
|
@ -2,20 +2,20 @@
|
|||
<div>
|
||||
<slot>
|
||||
<a-form-item label="内容">
|
||||
<a-textarea v-model="data.content" placeholder="输入内容..."></a-textarea>
|
||||
<a-textarea v-model="model.content" placeholder="输入内容..."></a-textarea>
|
||||
</a-form-item>
|
||||
</slot>
|
||||
|
||||
<a-form-item label="颜色">
|
||||
<input-color v-model="data.color"></input-color>
|
||||
<input-color v-model="model.color"></input-color>
|
||||
</a-form-item>
|
||||
|
||||
<div class="flex gap-4">
|
||||
<a-form-item label="字体">
|
||||
<a-select v-model="data.family" :options="FontFamilyOptions" class="w-full overflow-hidden"> </a-select>
|
||||
<a-select v-model="model.family" :options="FontFamilyOptions" class="w-full overflow-hidden"> </a-select>
|
||||
</a-form-item>
|
||||
<a-form-item label="大小">
|
||||
<a-input-number v-model="data.size" :min="12" :step="2"> </a-input-number>
|
||||
<a-input-number v-model="model.size" :min="12" :step="2"> </a-input-number>
|
||||
</a-form-item>
|
||||
</div>
|
||||
|
||||
|
|
@ -24,48 +24,42 @@
|
|||
<div class="h-8 flex items-center justify-between">
|
||||
<a-tag
|
||||
class="cursor-pointer !h-7"
|
||||
:color="data.bold ? 'blue' : ''"
|
||||
:bordered="data.bold ? true : false"
|
||||
@click="data.bold = !data.bold"
|
||||
:color="model.bold ? 'blue' : ''"
|
||||
:bordered="model.bold ? true : false"
|
||||
@click="model.bold = !model.bold"
|
||||
>
|
||||
<i class="icon-park-outline-text-bold"></i>
|
||||
</a-tag>
|
||||
<a-tag
|
||||
class="!h-7 cursor-pointer"
|
||||
:color="data.italic ? 'blue' : ''"
|
||||
:bordered="data.italic ? true : false"
|
||||
@click="data.italic = !data.italic"
|
||||
:color="model.italic ? 'blue' : ''"
|
||||
:bordered="model.italic ? true : false"
|
||||
@click="model.italic = !model.italic"
|
||||
>
|
||||
<i class="icon-park-outline-text-italic"></i>
|
||||
</a-tag>
|
||||
<a-tag
|
||||
class="!h-7 cursor-pointer"
|
||||
:color="data.underline ? 'blue' : ''"
|
||||
:bordered="data.underline ? true : false"
|
||||
@click="data.underline = !data.underline"
|
||||
:color="model.underline ? 'blue' : ''"
|
||||
:bordered="model.underline ? true : false"
|
||||
@click="model.underline = !model.underline"
|
||||
>
|
||||
<i class="icon-park-outline-text-underline"></i>
|
||||
</a-tag>
|
||||
</div>
|
||||
</a-form-item>
|
||||
<a-form-item label="方向">
|
||||
<a-select v-model="data.align" :options="AlignOptions"></a-select>
|
||||
<a-select v-model="model.align" :options="AlignOptions"></a-select>
|
||||
</a-form-item>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { PropType } from "vue";
|
||||
import InputColor from "../../../components/InputColor.vue";
|
||||
import InputColor from "../../components/InputColor.vue";
|
||||
import { AlignOptions, Font, FontFamilyOptions } from "./interface";
|
||||
|
||||
defineProps({
|
||||
data: {
|
||||
type: Object as PropType<Font>,
|
||||
required: true,
|
||||
},
|
||||
});
|
||||
const model = defineModel<Font>({ required: true });
|
||||
</script>
|
||||
|
||||
<style scoped></style>
|
||||
|
|
@ -1,6 +1,9 @@
|
|||
import { Blocker } from "../config";
|
||||
import { Blocker } from "../core";
|
||||
|
||||
const blockers: Record<string, Blocker> = import.meta.glob("./*/index.ts", { eager: true, import: "default" });
|
||||
const blockers: Record<string, Blocker> = import.meta.glob(["./*/index.ts", "!./font/*"], {
|
||||
eager: true,
|
||||
import: "default",
|
||||
});
|
||||
const BlockerMap: Record<string, Blocker> = {};
|
||||
|
||||
for (const blocker of Object.values(blockers)) {
|
||||
|
|
@ -20,4 +23,3 @@ const getIcon = (type: string) => {
|
|||
};
|
||||
|
||||
export { BlockerMap, getBlockerRender, getIcon, getTypeName };
|
||||
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import { defineBlocker } from "../../config";
|
||||
import { font } from "../components/font";
|
||||
import { defineBlocker } from "../../core";
|
||||
import { font } from "../font";
|
||||
import { Text } from "./interface";
|
||||
import Option from "./option.vue";
|
||||
import Render from "./render.vue";
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import { Block } from "../../config";
|
||||
import { Font } from "../components/font";
|
||||
import { Block } from "../../core";
|
||||
import { Font } from "../font";
|
||||
|
||||
export interface TextPrams {
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -1,22 +1,22 @@
|
|||
<template>
|
||||
<div>
|
||||
<base-option :data="data"></base-option>
|
||||
<base-option v-model="model"></base-option>
|
||||
</div>
|
||||
<a-divider></a-divider>
|
||||
<div>
|
||||
<div class="muti-form-item grid grid-cols-2 gap-x-4">
|
||||
<a-form-item label="是否滚动">
|
||||
<a-radio-group type="button" v-model="data.params.marquee" class="!w-full">
|
||||
<a-radio-group type="button" v-model="model.params.marquee" class="!w-full">
|
||||
<a-radio :value="false">否</a-radio>
|
||||
<a-radio :value="true">是</a-radio>
|
||||
</a-radio-group>
|
||||
</a-form-item>
|
||||
<a-form-item :disabled="!data.params.marquee" label="滚动速度">
|
||||
<a-input-number v-model="data.params.speed" :min="10" :step="10"></a-input-number>
|
||||
<a-form-item :disabled="!model.params.marquee" label="滚动速度">
|
||||
<a-input-number v-model="model.params.speed" :min="10" :step="10"></a-input-number>
|
||||
</a-form-item>
|
||||
</div>
|
||||
<a-form-item :disabled="!data.params.marquee" label="滚动方向">
|
||||
<a-radio-group type="button" v-model="data.params.direction" class="!w-full">
|
||||
<a-form-item :disabled="!model.params.marquee" label="滚动方向">
|
||||
<a-radio-group type="button" v-model="model.params.direction" class="!w-full">
|
||||
<a-radio v-for="item in DirectionOptions" :key="item.value" :value="item.value" class="dir-radio">
|
||||
<i :class="item.icon"></i>
|
||||
</a-radio>
|
||||
|
|
@ -24,21 +24,15 @@
|
|||
</a-form-item>
|
||||
</div>
|
||||
<a-divider></a-divider>
|
||||
<font-option :data="data.params.fontCh"></font-option>
|
||||
<font-option v-model="model.params.fontCh"></font-option>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { PropType } from "vue";
|
||||
import BaseOption from "../../components/BaseOption.vue";
|
||||
import { FontOption } from "../components/font";
|
||||
import { FontOption } from "../font";
|
||||
import { DirectionOptions, Text } from "./interface";
|
||||
|
||||
defineProps({
|
||||
data: {
|
||||
type: Object as PropType<Text>,
|
||||
required: true,
|
||||
},
|
||||
});
|
||||
const model = defineModel<Text>({ required: true });
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
|
|
@ -49,3 +43,4 @@ defineProps({
|
|||
}
|
||||
</style>
|
||||
../components/font
|
||||
../font
|
||||
|
|
@ -7,7 +7,7 @@
|
|||
|
||||
<script setup lang="ts">
|
||||
import { PropType } from "vue";
|
||||
import { FontRender, getFontStyle } from "../components/font";
|
||||
import { FontRender, getFontStyle } from "../font";
|
||||
import { Text } from "./interface";
|
||||
import AniMarquee from "./marquee.vue";
|
||||
|
||||
|
|
@ -24,4 +24,4 @@ const style = computed(() => {
|
|||
</script>
|
||||
|
||||
<style scoped></style>
|
||||
../components/font
|
||||
../components/font../font
|
||||
|
|
@ -1,5 +1,5 @@
|
|||
import { defineBlocker } from "../../config";
|
||||
import { font } from "../components/font";
|
||||
import { defineBlocker } from "../../core";
|
||||
import { font } from "../font";
|
||||
import { Time } from "./interface";
|
||||
import Option from "./option.vue";
|
||||
import Render from "./render.vue";
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import { Block } from "../../config";
|
||||
import { Font } from "../components/font";
|
||||
import { Block } from "../../core";
|
||||
import { Font } from "../font";
|
||||
|
||||
export interface TimeParams {
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -1,10 +1,10 @@
|
|||
<template>
|
||||
<base-option :data="data"></base-option>
|
||||
<base-option v-model="model"></base-option>
|
||||
<a-divider></a-divider>
|
||||
<font-option :data="data.params.fontCh">
|
||||
<font-option v-model="model.params.fontCh">
|
||||
<a-form-item label="时间格式">
|
||||
<a-auto-complete
|
||||
v-model="data.params.fontCh.content"
|
||||
v-model="model.params.fontCh.content"
|
||||
:data="FomatSuguestions"
|
||||
:allow-clear="true"
|
||||
placeholder="例如 HH:mm:ss"
|
||||
|
|
@ -25,17 +25,11 @@
|
|||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { PropType } from "vue";
|
||||
import BaseOption from "../../components/BaseOption.vue";
|
||||
import { FontOption } from "../components/font";
|
||||
import { FontOption } from "../font";
|
||||
import { Time, FomatSuguestions } from "./interface";
|
||||
|
||||
defineProps({
|
||||
data: {
|
||||
type: Object as PropType<Time>,
|
||||
required: true,
|
||||
},
|
||||
});
|
||||
const model = defineModel<Time>({ required: true });
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
|
|
@ -45,4 +39,4 @@ defineProps({
|
|||
}
|
||||
}
|
||||
</style>
|
||||
../components/font
|
||||
../font
|
||||
|
|
@ -1,5 +1,5 @@
|
|||
<template>
|
||||
<font-render :data="model.params.fontCh">
|
||||
<font-render :data="props.data.params.fontCh">
|
||||
{{ time }}
|
||||
</font-render>
|
||||
</template>
|
||||
|
|
@ -7,11 +7,18 @@
|
|||
<script setup lang="ts">
|
||||
import { dayjs } from "@/libs/dayjs";
|
||||
import { onMounted, onUnmounted, ref } from "vue";
|
||||
import { FontRender } from "../components/font";
|
||||
import { FontRender } from "../font";
|
||||
import { Time } from "./interface";
|
||||
import { PropType } from "vue";
|
||||
|
||||
const model = defineModel<Time>({ required: true });
|
||||
const format = computed(() => model.value.params.fontCh.content || "HH:mm:ss");
|
||||
const props = defineProps({
|
||||
data: {
|
||||
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));
|
||||
let timer: any = null;
|
||||
|
||||
|
|
@ -28,3 +35,4 @@ onUnmounted(() => {
|
|||
|
||||
<style scoped></style>
|
||||
../components/font
|
||||
../font
|
||||
|
|
@ -1,31 +1,35 @@
|
|||
<template>
|
||||
<div>
|
||||
<a-form-item label="组件名称">
|
||||
<a-input v-model="data.title"></a-input>
|
||||
<a-input v-model="model.title"></a-input>
|
||||
</a-form-item>
|
||||
|
||||
<div class="flex gap-4">
|
||||
<a-form-item label="左侧">
|
||||
<a-input-number v-model="data.x" :min="0" :max="container.width">
|
||||
<a-input-number v-model="model.x" :min="0" :max="container.width">
|
||||
<template #prefix>
|
||||
<a-tooltip content="固定水平方向">
|
||||
<i
|
||||
class="cursor-pointer text-gray-400 hover:text-gray-700"
|
||||
:class="data.xFixed ? 'icon-park-outline-lock text-gray-900' : 'icon-park-outline-unlock text-gray-400'"
|
||||
@click="data.xFixed = !data.xFixed"
|
||||
:class="
|
||||
model.xFixed ? 'icon-park-outline-lock text-gray-900' : 'icon-park-outline-unlock text-gray-400'
|
||||
"
|
||||
@click="model.xFixed = !model.xFixed"
|
||||
></i>
|
||||
</a-tooltip>
|
||||
</template>
|
||||
</a-input-number>
|
||||
</a-form-item>
|
||||
<a-form-item label="顶部">
|
||||
<a-input-number v-model="data.y" :min="0" :max="container.height">
|
||||
<a-input-number v-model="model.y" :min="0" :max="container.height">
|
||||
<template #prefix>
|
||||
<a-tooltip content="固定垂直方向">
|
||||
<i
|
||||
class="cursor-pointer text-gray-400 hover:text-gray-700"
|
||||
:class="data.yFixed ? 'icon-park-outline-lock text-gray-900' : 'icon-park-outline-unlock text-gray-400'"
|
||||
@click="data.yFixed = !data.yFixed"
|
||||
:class="
|
||||
model.yFixed ? 'icon-park-outline-lock text-gray-900' : 'icon-park-outline-unlock text-gray-400'
|
||||
"
|
||||
@click="model.yFixed = !model.yFixed"
|
||||
></i>
|
||||
</a-tooltip>
|
||||
</template>
|
||||
|
|
@ -35,37 +39,31 @@
|
|||
|
||||
<div class="flex gap-4">
|
||||
<a-form-item label="宽度">
|
||||
<a-input-number v-model="data.w" :min="0" :max="container.width"> </a-input-number>
|
||||
<a-input-number v-model="model.w" :min="0" :max="container.width"> </a-input-number>
|
||||
</a-form-item>
|
||||
<a-form-item label="高度">
|
||||
<a-input-number v-model="data.h" :min="0" :max="container.height"> </a-input-number>
|
||||
<a-input-number v-model="model.h" :min="0" :max="container.height"> </a-input-number>
|
||||
</a-form-item>
|
||||
</div>
|
||||
|
||||
<a-form-item label="背景图片">
|
||||
<input-image v-model="data.bgImage"></input-image>
|
||||
<input-image v-model="model.bgImage"></input-image>
|
||||
</a-form-item>
|
||||
|
||||
<a-form-item label="背景颜色">
|
||||
<input-color v-model="data.bgColor"></input-color>
|
||||
<input-color v-model="model.bgColor"></input-color>
|
||||
</a-form-item>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { PropType } from "vue";
|
||||
import { Block, ContextKey } from "../config";
|
||||
import { Block, EditorKey } from "../core";
|
||||
import InputColor from "./InputColor.vue";
|
||||
import InputImage from "./InputImage.vue";
|
||||
|
||||
defineProps({
|
||||
data: {
|
||||
type: Object as PropType<Block>,
|
||||
required: true,
|
||||
},
|
||||
});
|
||||
|
||||
const { container } = inject(ContextKey)!
|
||||
const model = defineModel<Block>({ required: true });
|
||||
const { container } = inject(EditorKey)!;
|
||||
</script>
|
||||
|
||||
<style scoped></style>
|
||||
../core../core/editor
|
||||
|
|
|
|||
|
|
@ -25,30 +25,20 @@
|
|||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { EditorKey, useEditor } from "./config/editor";
|
||||
import PanelHeader from "./panel-header/index.vue";
|
||||
import PanelLeft from "./panel-left/index.vue";
|
||||
import PanelMain from "./panel-main/index.vue";
|
||||
import PanelRight from "./panel-right/index.vue";
|
||||
import AppnifyPreview from "./preview/index.vue";
|
||||
import { EditorKey, useEditor } from "../core";
|
||||
import PanelHeader from "./PanelHeader.vue";
|
||||
import PanelLeft from "./PanelLeft.vue";
|
||||
import PanelMain from "./PanelMain.vue";
|
||||
import PanelRight from "./PanelRight.vue";
|
||||
import AppnifyPreview from "./EditorPreview.vue";
|
||||
|
||||
const visible = defineModel("visible", { default: false });
|
||||
const rightPanelCollapsed = ref(false);
|
||||
const leftPanelCollapsed = ref(false);
|
||||
const preview = ref(false);
|
||||
|
||||
const editor = useEditor();
|
||||
const { container, blocks, currentBlock, addBlock, rmBlock, setCurrentBlock } = editor;
|
||||
|
||||
provide(EditorKey, editor);
|
||||
|
||||
onMounted(() => {
|
||||
loadData();
|
||||
});
|
||||
|
||||
/**
|
||||
* 保存数据
|
||||
*/
|
||||
const saveData = () => {
|
||||
const data = {
|
||||
container: container.value,
|
||||
|
|
@ -58,9 +48,6 @@ const saveData = () => {
|
|||
localStorage.setItem("ANI_EDITOR_DATA", str);
|
||||
};
|
||||
|
||||
/**
|
||||
* 加载数据
|
||||
*/
|
||||
const loadData = async () => {
|
||||
const str = localStorage.getItem("ANI_EDITOR_DATA");
|
||||
if (!str) {
|
||||
|
|
@ -70,13 +57,14 @@ const loadData = async () => {
|
|||
container.value = data.container;
|
||||
blocks.value = data.children;
|
||||
};
|
||||
|
||||
provide(EditorKey, editor);
|
||||
onMounted(loadData);
|
||||
</script>
|
||||
|
||||
<style lang="less">
|
||||
.ani-modal {
|
||||
.muti-form-item .arco-form-item .arco-form-item-label {
|
||||
// color: #899;
|
||||
// font-size: 12px;
|
||||
line-height: 1;
|
||||
}
|
||||
.arco-modal-fullscreen {
|
||||
|
|
@ -107,3 +95,4 @@ const loadData = async () => {
|
|||
}
|
||||
}
|
||||
</style>
|
||||
../core/editor
|
||||
|
|
@ -39,18 +39,10 @@
|
|||
import { Message } from "@arco-design/web-vue";
|
||||
import { useFullscreen } from "@vueuse/core";
|
||||
import { BlockerMap } from "../blocks";
|
||||
import { EditorKey } from "../config/editor";
|
||||
import { EditorKey } from "../core";
|
||||
|
||||
const { container, blocks } = inject(EditorKey)!;
|
||||
|
||||
const props = defineProps({
|
||||
visible: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
});
|
||||
|
||||
const emit = defineEmits(["update:visible"]);
|
||||
const visible = defineModel<boolean>("visible");
|
||||
const el = ref<HTMLElement | null>(null);
|
||||
const { enter, isFullscreen, isSupported } = useFullscreen(el);
|
||||
|
||||
|
|
@ -58,13 +50,13 @@ watch(
|
|||
() => isFullscreen.value,
|
||||
() => {
|
||||
if (!isFullscreen.value) {
|
||||
emit("update:visible", false);
|
||||
visible.value = false;
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
watch(
|
||||
() => props.visible,
|
||||
() => visible.value,
|
||||
(value) => {
|
||||
if (!value) {
|
||||
return;
|
||||
|
|
@ -79,3 +71,4 @@ watch(
|
|||
</script>
|
||||
|
||||
<style scoped></style>
|
||||
../core/editor
|
||||
|
|
@ -43,7 +43,6 @@
|
|||
<div class="flex-1 truncate text-gray-600" :class="{ 'text-brand-500': selectedKeys.includes(item.id) }">
|
||||
{{ item.title }}(<span class="text-xs text-gray-400">1280 * 800</span>)
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -71,7 +70,7 @@
|
|||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { mockLoad } from "./mock";
|
||||
import { mockLoad } from "../utils/mock";
|
||||
|
||||
const props = defineProps({
|
||||
modelValue: {
|
||||
|
|
@ -186,3 +185,4 @@ watch(
|
|||
z-index: 2;
|
||||
}
|
||||
</style>
|
||||
../utils/mock
|
||||
|
|
|
|||
|
|
@ -7,20 +7,7 @@
|
|||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
|
||||
const props = defineProps({
|
||||
modelValue: {
|
||||
type: String,
|
||||
default: "",
|
||||
},
|
||||
});
|
||||
|
||||
const emit = defineEmits(["update:modelValue"]);
|
||||
|
||||
const model = computed({
|
||||
get: () => props.modelValue,
|
||||
set: (value) => emit("update:modelValue", value),
|
||||
});
|
||||
const model = defineModel<string>();
|
||||
</script>
|
||||
|
||||
<style scoped></style>
|
||||
|
|
|
|||
|
|
@ -8,22 +8,10 @@
|
|||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import ImagePicker from './ImagePicker.vue';
|
||||
import ImagePicker from "./ImagePicker.vue";
|
||||
|
||||
const props = defineProps({
|
||||
modelValue: {
|
||||
type: String,
|
||||
default: "",
|
||||
},
|
||||
});
|
||||
|
||||
const emit = defineEmits(["update:modelValue"]);
|
||||
const model = defineModel<string>();
|
||||
const visible = ref(false);
|
||||
|
||||
const model = computed({
|
||||
get: () => props.modelValue,
|
||||
set: (value) => emit("update:modelValue", value),
|
||||
});
|
||||
</script>
|
||||
|
||||
<style scoped></style>
|
||||
|
|
|
|||
|
|
@ -23,19 +23,15 @@
|
|||
|
||||
<script setup lang="ts">
|
||||
import { Message } from "@arco-design/web-vue";
|
||||
import { Container, ContextKey } from "../config";
|
||||
import AniTexter from "../panel-main/components/texter.vue";
|
||||
|
||||
const { saveData } = inject(ContextKey)!;
|
||||
import { Container } from "../core";
|
||||
import AniTexter from "./InputTexter.vue";
|
||||
|
||||
const onSaveData = () => {
|
||||
saveData();
|
||||
Message.success("保存成功");
|
||||
};
|
||||
|
||||
const container = defineModel<Container>("container", {
|
||||
required: true,
|
||||
});
|
||||
const container = defineModel<Container>("container", { required: true });
|
||||
</script>
|
||||
|
||||
<style scoped></style>
|
||||
../core
|
||||
|
|
@ -91,8 +91,7 @@
|
|||
|
||||
<script setup lang="ts">
|
||||
import { getIcon } from "../blocks";
|
||||
import { Block } from "../config";
|
||||
import { EditorKey } from "../config/editor";
|
||||
import { Block, EditorKey } from "../core";
|
||||
|
||||
const { blocks, currentBlock, BlockerMap } = inject(EditorKey)!;
|
||||
const blockList = Object.values(BlockerMap);
|
||||
|
|
@ -113,3 +112,4 @@ const onDragStart = (e: DragEvent) => {
|
|||
</script>
|
||||
|
||||
<style scoped></style>
|
||||
../core../core/editor
|
||||
|
|
@ -10,16 +10,16 @@
|
|||
<div
|
||||
class="relative"
|
||||
:style="containerStyle"
|
||||
@dragover.prevent
|
||||
@click="onClick"
|
||||
@drop="onDragDrop"
|
||||
@dragover.prevent
|
||||
@wheel="scene.onMouseWheel"
|
||||
@mousedown="scene.onMouseDown"
|
||||
@wheel="onMouseWheel"
|
||||
@mousedown="onMouseDown"
|
||||
>
|
||||
<ani-block v-for="block in blocks" :key="block.id" :data="block" :container="container"></ani-block>
|
||||
<template v-if="refLine.active.value">
|
||||
<template v-if="active">
|
||||
<div
|
||||
v-for="line in refLine.xl.value"
|
||||
v-for="line in xLines"
|
||||
:key="line.y"
|
||||
:style="{
|
||||
position: 'absolute',
|
||||
|
|
@ -31,7 +31,7 @@
|
|||
}"
|
||||
></div>
|
||||
<div
|
||||
v-for="line in refLine.yl.value"
|
||||
v-for="line in yLines"
|
||||
:key="line.x"
|
||||
:style="{
|
||||
position: 'absolute',
|
||||
|
|
@ -50,15 +50,14 @@
|
|||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { Block, Scene } from "../config";
|
||||
import AniBlock from "./components/block.vue";
|
||||
import AniHeader from "./components/header.vue";
|
||||
import { EditorKey } from "../config/editor";
|
||||
import { Block, EditorKey } from "../core";
|
||||
import AniBlock from "./PanelMainBlock.vue";
|
||||
import AniHeader from "./PanelMainHeader.vue";
|
||||
|
||||
const rightPanelCollapsed = defineModel<boolean>();
|
||||
|
||||
const { blocks, container, refLine, formatContainerStyle } = inject(EditorKey)!;
|
||||
const scene = new Scene(container);
|
||||
const rightPanelCollapsed = defineModel<boolean>("rightPanelCollapsed");
|
||||
const { blocks, container, refLine, formatContainerStyle, scene } = inject(EditorKey)!;
|
||||
const { onMouseDown, onMouseWheel } = scene;
|
||||
const { active, xLines, yLines } = refLine;
|
||||
|
||||
const emit = defineEmits<{
|
||||
(event: "add-block", type: string, x?: number, y?: number): void;
|
||||
|
|
@ -109,3 +108,4 @@ const onDragDrop = (e: DragEvent) => {
|
|||
background-position: 0 0, 10px 10px;
|
||||
}
|
||||
</style>
|
||||
../core../core/editor
|
||||
|
|
@ -26,9 +26,9 @@
|
|||
|
||||
<script setup lang="ts">
|
||||
import { PropType } from "vue";
|
||||
import { BlockerMap } from "../../blocks";
|
||||
import DragResizer from "../../components/DragResizer.vue";
|
||||
import { Block, Container, ContextKey } from "../../config";
|
||||
import { BlockerMap } from "../blocks";
|
||||
import DragResizer from "./DragResizer.vue";
|
||||
import { Block, Container, EditorKey } from "../core";
|
||||
|
||||
const props = defineProps({
|
||||
data: {
|
||||
|
|
@ -41,7 +41,8 @@ const props = defineProps({
|
|||
},
|
||||
});
|
||||
|
||||
const { setCurrentBlock, refLine } = inject(ContextKey)!;
|
||||
const { setCurrentBlock, refLine } = inject(EditorKey)!;
|
||||
const { active, recordBlocksXY, updateRefLine } = refLine;
|
||||
|
||||
/**
|
||||
* 组件样式
|
||||
|
|
@ -59,10 +60,10 @@ const blockStyle = computed(() => {
|
|||
* 拖拽组件
|
||||
*/
|
||||
const onItemDragging = (rect: any) => {
|
||||
if (refLine.active.value) {
|
||||
const { x = 0, y = 0 } = refLine.updateRefLine(rect);
|
||||
rect.left += x;
|
||||
rect.top += y;
|
||||
if (active.value) {
|
||||
const { offsetX = 0, offsetY = 0 } = updateRefLine(rect);
|
||||
rect.left += offsetX;
|
||||
rect.top += offsetY;
|
||||
}
|
||||
onItemResizing(rect);
|
||||
};
|
||||
|
|
@ -81,7 +82,7 @@ const onItemResizing = (rect: any) => {
|
|||
* 按下鼠标
|
||||
*/
|
||||
const onItemMouseDown = () => {
|
||||
refLine.active.value = true;
|
||||
active.value = true;
|
||||
};
|
||||
|
||||
/**
|
||||
|
|
@ -89,14 +90,14 @@ const onItemMouseDown = () => {
|
|||
*/
|
||||
const onItemActivated = (block: Block) => {
|
||||
setCurrentBlock(block);
|
||||
refLine.recordBlocksXY();
|
||||
recordBlocksXY();
|
||||
};
|
||||
|
||||
/**
|
||||
* 松开鼠标
|
||||
*/
|
||||
const onItemMouseup = () => {
|
||||
refLine.active.value = false;
|
||||
active.value = false;
|
||||
};
|
||||
</script>
|
||||
|
||||
|
|
@ -126,3 +127,4 @@ const onItemMouseup = () => {
|
|||
}
|
||||
}
|
||||
</style>
|
||||
../core../core/editor
|
||||
|
|
@ -31,7 +31,7 @@
|
|||
</a-button>
|
||||
</a-tooltip>
|
||||
<a-tooltip content="预览" position="bottom">
|
||||
<a-button type="text" @click="preview">
|
||||
<a-button type="text">
|
||||
<template #icon>
|
||||
<i class="icon-park-outline-play text-base !text-gray-600"></i>
|
||||
</template>
|
||||
|
|
@ -73,14 +73,15 @@
|
|||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import InputColor from "../../components/InputColor.vue";
|
||||
import InputImage from "../../components/InputImage.vue";
|
||||
import { ContextKey } from "../../config";
|
||||
import AniTexter from "./texter.vue";
|
||||
import InputColor from "./InputColor.vue";
|
||||
import InputImage from "./InputImage.vue";
|
||||
import AniTexter from "./InputTexter.vue";
|
||||
import { EditorKey } from "../core";
|
||||
|
||||
const { container, blocks, preview, setContainerOrigin } = inject(ContextKey)!;
|
||||
const { container, blocks, setContainerOrigin } = inject(EditorKey)!;
|
||||
|
||||
const rightPanelCollapsed = defineModel<boolean>();
|
||||
const rightPanelCollapsed = defineModel<boolean>("rightPanelCollapsed");
|
||||
</script>
|
||||
|
||||
<style scoped></style>
|
||||
../core/editor
|
||||
|
|
@ -1,13 +1,9 @@
|
|||
<template>
|
||||
<div class="h-full w-[248px] overflow-hidden" :style="`display: ${collapsed ? 'none' : 'block'}`">
|
||||
<div v-if="block" class="p-3">
|
||||
<a-radio-group type="button" default-value="1" class="w-full mb-2">
|
||||
<a-radio value="1">属性</a-radio>
|
||||
<a-radio value="2">文本</a-radio>
|
||||
</a-radio-group>
|
||||
<div v-if="model" class="p-3">
|
||||
<a-form :model="{}" layout="vertical">
|
||||
<div class="muti-form-item mt-2">
|
||||
<component :is="BlockerMap[block.type].option" v-model="block" />
|
||||
<component :is="BlockerMap[model.type].option" v-model="model" />
|
||||
</div>
|
||||
</a-form>
|
||||
</div>
|
||||
|
|
@ -19,10 +15,11 @@
|
|||
|
||||
<script setup lang="ts">
|
||||
import { BlockerMap } from "../blocks";
|
||||
import { Block } from "../config";
|
||||
import { Block } from "../core";
|
||||
|
||||
const collapsed = defineModel<boolean>();
|
||||
const block = defineModel<Block | null>();
|
||||
const collapsed = defineModel<boolean>("collapsed");
|
||||
const model = defineModel<Block | null>("block");
|
||||
</script>
|
||||
|
||||
<style scoped></style>
|
||||
../core
|
||||
|
|
@ -1,52 +0,0 @@
|
|||
import { InjectionKey, Ref } from "vue";
|
||||
import { Block } from "./block";
|
||||
import { Container } from "./container";
|
||||
import { ReferenceLine } from "./ref-line";
|
||||
|
||||
export interface Current {
|
||||
block: Block | null;
|
||||
rightPanelCollapsed: boolean;
|
||||
}
|
||||
|
||||
export interface Context {
|
||||
/**
|
||||
* 运行时数据
|
||||
*/
|
||||
currentBlock: Ref<Block | null>;
|
||||
/**
|
||||
* 组件列表
|
||||
*/
|
||||
blocks: Ref<Block[]>;
|
||||
/**
|
||||
* 画布配置
|
||||
*/
|
||||
container: Ref<Container>;
|
||||
/**
|
||||
* 参考线
|
||||
*/
|
||||
refLine: ReferenceLine;
|
||||
/**
|
||||
* 设置当前组件
|
||||
* @param block 组件
|
||||
* @returns
|
||||
*/
|
||||
setCurrentBlock: (block: Block | null) => void;
|
||||
/**
|
||||
* 容器自适应
|
||||
*/
|
||||
setContainerOrigin: () => void;
|
||||
/**
|
||||
* 保存数据
|
||||
*/
|
||||
saveData: () => void;
|
||||
/**
|
||||
* 加载数据
|
||||
*/
|
||||
loadData: () => void;
|
||||
/**
|
||||
* 预览
|
||||
*/
|
||||
preview: () => void;
|
||||
}
|
||||
|
||||
export const ContextKey = Symbol("ContextKey") as InjectionKey<Context>;
|
||||
|
|
@ -1,65 +0,0 @@
|
|||
import { Ref } from "vue";
|
||||
import { Container } from ".";
|
||||
|
||||
/**
|
||||
* 场景
|
||||
* @description 处理平移和缩放事件
|
||||
*/
|
||||
export class Scene {
|
||||
private startX = 0;
|
||||
private startY = 0;
|
||||
private cacheX = 0;
|
||||
private cacheY = 0;
|
||||
public minZoom = 0.5;
|
||||
public maxZoom = 10;
|
||||
public zoomStep = 0.1;
|
||||
|
||||
constructor(private container: Ref<Container>) {
|
||||
this.onMouseDown = this.onMouseDown.bind(this);
|
||||
this.onMouseMove = this.onMouseMove.bind(this);
|
||||
this.onMouseUp = this.onMouseUp.bind(this);
|
||||
this.onMouseWheel = this.onMouseWheel.bind(this);
|
||||
}
|
||||
|
||||
onMouseDown(e: MouseEvent) {
|
||||
this.startX = e.x;
|
||||
this.startY = e.y;
|
||||
this.cacheX = this.container.value.x;
|
||||
this.cacheY = this.container.value.y;
|
||||
window.addEventListener("mousemove", this.onMouseMove);
|
||||
window.addEventListener("mouseup", this.onMouseUp);
|
||||
}
|
||||
|
||||
onMouseMove(e: MouseEvent) {
|
||||
this.container.value.x = this.cacheX + (e.x - this.startX);
|
||||
this.container.value.y = this.cacheY + (e.y - this.startY);
|
||||
}
|
||||
|
||||
onMouseUp() {
|
||||
window.removeEventListener("mousemove", this.onMouseMove);
|
||||
window.removeEventListener("mouseup", this.onMouseUp);
|
||||
}
|
||||
|
||||
onMouseWheel(e: WheelEvent) {
|
||||
e.preventDefault();
|
||||
|
||||
const el = e.currentTarget as HTMLElement;
|
||||
const rect = el.getBoundingClientRect();
|
||||
const x = (e.clientX - rect.x) / this.container.value.zoom;
|
||||
const y = (e.clientY - rect.y) / this.container.value.zoom;
|
||||
const delta = -e.deltaY > 0 ? this.zoomStep : -this.zoomStep;
|
||||
|
||||
this.container.value.zoom += delta;
|
||||
if (this.container.value.zoom < this.minZoom) {
|
||||
this.container.value.zoom = this.minZoom;
|
||||
return;
|
||||
}
|
||||
if (this.container.value.zoom > this.maxZoom) {
|
||||
this.container.value.zoom = this.maxZoom;
|
||||
return;
|
||||
}
|
||||
|
||||
this.container.value.x += -x * delta + el.offsetWidth * (delta / 2);
|
||||
this.container.value.y += -y * delta + el.offsetHeight * (delta / 2);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,3 +1,6 @@
|
|||
/**
|
||||
* 组件参数
|
||||
*/
|
||||
export interface Block<T = any> {
|
||||
/**
|
||||
* 组件ID
|
||||
|
|
@ -1,6 +1,9 @@
|
|||
import { Component } from "vue";
|
||||
import { Block } from "./block";
|
||||
|
||||
/**
|
||||
* 组件配置
|
||||
*/
|
||||
export interface Blocker<T = any> {
|
||||
/**
|
||||
* 组件名称
|
||||
|
|
@ -1,3 +1,6 @@
|
|||
/**
|
||||
* 画布配置
|
||||
*/
|
||||
export interface Container {
|
||||
/**
|
||||
* 容器id
|
||||
|
|
@ -41,6 +44,9 @@ export interface Container {
|
|||
bgColor: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* 画布默认配置
|
||||
*/
|
||||
export const defaultContainer: Container = {
|
||||
id: 11,
|
||||
title: "国庆节喜庆版式设计",
|
||||
|
|
@ -1,9 +1,10 @@
|
|||
import { Container, defaultContainer } from "./container";
|
||||
import { Block } from "./block";
|
||||
import { ReferenceLine } from "./ref-line";
|
||||
import { useReferenceLine } from "./ref-line";
|
||||
import { BlockerMap } from "../blocks";
|
||||
import { cloneDeep } from "lodash-es";
|
||||
import { CSSProperties, InjectionKey } from "vue";
|
||||
import { useScene } from "./scene";
|
||||
|
||||
export const useEditor = () => {
|
||||
/**
|
||||
|
|
@ -21,7 +22,11 @@ export const useEditor = () => {
|
|||
/**
|
||||
* 参考线
|
||||
*/
|
||||
const refLine = new ReferenceLine(blocks, currentBlock as any);
|
||||
const refLine = useReferenceLine(blocks, currentBlock);
|
||||
/**
|
||||
* 画布移动和缩放
|
||||
*/
|
||||
const scene = useScene(container);
|
||||
|
||||
/**
|
||||
* 添加组件
|
||||
|
|
@ -131,6 +136,7 @@ export const useEditor = () => {
|
|||
blocks,
|
||||
currentBlock,
|
||||
refLine,
|
||||
scene,
|
||||
BlockerMap,
|
||||
setCurrentBlock,
|
||||
setContainerOrigin,
|
||||
|
|
@ -1,6 +1,6 @@
|
|||
export * from "./block";
|
||||
export * from "./blocker";
|
||||
export * from "./container";
|
||||
export * from "./context";
|
||||
export * from "./ref-line";
|
||||
export * from "./scene";
|
||||
export * from "./editor";
|
||||
|
|
@ -1,71 +1,58 @@
|
|||
import { Ref } from "vue";
|
||||
import { Block } from "./block";
|
||||
import { Current } from "./context";
|
||||
import { getClosestValInSortedArr } from "./util";
|
||||
|
||||
export interface DragRect {
|
||||
left: number;
|
||||
top: number;
|
||||
width: number;
|
||||
height: number;
|
||||
}
|
||||
import { getClosestValInSortedArr } from "../utils/closest";
|
||||
|
||||
/**
|
||||
* 参考线管理
|
||||
* 组件参考线
|
||||
* @param blocks 组件列表
|
||||
* @param current 当前组件
|
||||
* @returns
|
||||
*/
|
||||
export class ReferenceLine {
|
||||
private xYsMap = new Map<number, number[]>();
|
||||
private yXsMap = new Map<number, number[]>();
|
||||
private sortedXs: number[] = [];
|
||||
private sortedYs: number[] = [];
|
||||
private xLines: { y: number; xs: number[] }[] = [];
|
||||
private yLines: { x: number; ys: number[] }[] = [];
|
||||
public active = ref(false);
|
||||
public xl = ref<{ x: number; y: number; w: number }[]>([]);
|
||||
public yl = ref<{ x: number; y: number; h: number }[]>([]);
|
||||
|
||||
constructor(private blocks: Ref<Block[]>, private current: Ref<Current>) {
|
||||
this.updateRefLine = this.updateRefLine.bind(this);
|
||||
}
|
||||
export const useReferenceLine = (blocks: Ref<Block[]>, current: Ref<Block | null>) => {
|
||||
let xYsMap = new Map<number, number[]>();
|
||||
let yXsMap = new Map<number, number[]>();
|
||||
let sortedXs: number[] = [];
|
||||
let sortedYs: number[] = [];
|
||||
const active = ref(false);
|
||||
const xLines = ref<{ x: number; y: number; w: number }[]>([]);
|
||||
const yLines = ref<{ x: number; y: number; h: number }[]>([]);
|
||||
|
||||
/**
|
||||
* 记录所有组件坐标
|
||||
*/
|
||||
recordBlocksXY() {
|
||||
this.clear();
|
||||
const { xYsMap, yXsMap, blocks, current } = this;
|
||||
const recordBlocksXY = () => {
|
||||
clear();
|
||||
for (const block of blocks.value) {
|
||||
if (block === current.value.block) {
|
||||
if (block === current.value) {
|
||||
continue;
|
||||
}
|
||||
const { minX, minY, midX, midY, maxX, maxY } = this.getBlockBox(block);
|
||||
const { minX, minY, midX, midY, maxX, maxY } = getBlockBox(block);
|
||||
addBoxToMap(xYsMap, minX, [minY, maxY]);
|
||||
addBoxToMap(xYsMap, midX, [minY, maxY]);
|
||||
addBoxToMap(xYsMap, maxX, [minY, maxY]);
|
||||
|
||||
this.addBoxToMap(xYsMap, minX, [minY, maxY]);
|
||||
this.addBoxToMap(xYsMap, midX, [minY, maxY]);
|
||||
this.addBoxToMap(xYsMap, maxX, [minY, maxY]);
|
||||
|
||||
this.addBoxToMap(yXsMap, minY, [minX, maxX]);
|
||||
this.addBoxToMap(yXsMap, midY, [minX, maxX]);
|
||||
this.addBoxToMap(yXsMap, maxY, [minX, maxX]);
|
||||
addBoxToMap(yXsMap, minY, [minX, maxX]);
|
||||
addBoxToMap(yXsMap, midY, [minX, maxX]);
|
||||
addBoxToMap(yXsMap, maxY, [minX, maxX]);
|
||||
}
|
||||
this.sortedXs = Array.from(xYsMap.keys()).sort((a, b) => a - b);
|
||||
this.sortedYs = Array.from(yXsMap.keys()).sort((a, b) => a - b);
|
||||
}
|
||||
sortedXs = Array.from(xYsMap.keys()).sort((a, b) => a - b);
|
||||
sortedYs = Array.from(yXsMap.keys()).sort((a, b) => a - b);
|
||||
};
|
||||
|
||||
/**
|
||||
* 添加组件坐标
|
||||
*/
|
||||
addBoxToMap(map: Map<number, number[]>, xOrY: number, xsOrYs: number[]) {
|
||||
const addBoxToMap = (map: Map<number, number[]>, xOrY: number, xsOrYs: number[]) => {
|
||||
if (!map.get(xOrY)) {
|
||||
map.set(xOrY, []);
|
||||
}
|
||||
map.get(xOrY)?.push(...xsOrYs);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* 获取组件左中右坐标
|
||||
*/
|
||||
getBlockBox(block: Block) {
|
||||
const getBlockBox = (block: Block) => {
|
||||
const { x, y, w, h } = block ?? {};
|
||||
return {
|
||||
minX: x,
|
||||
|
|
@ -75,12 +62,12 @@ export class ReferenceLine {
|
|||
maxX: x + w,
|
||||
maxY: y + h,
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* 获取组件左中右坐标
|
||||
*/
|
||||
getRectBox(rect: DragRect) {
|
||||
const getRectBox = (rect: DragRect) => {
|
||||
const { left: x, top: y, width: w, height: h } = rect;
|
||||
return {
|
||||
minX: x,
|
||||
|
|
@ -90,18 +77,18 @@ export class ReferenceLine {
|
|||
maxX: x + w,
|
||||
maxY: y + h,
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* 清理数据
|
||||
*/
|
||||
clear() {
|
||||
this.xYsMap.clear();
|
||||
this.yXsMap.clear();
|
||||
this.sortedXs = [];
|
||||
this.sortedYs = [];
|
||||
this.xl.value = [];
|
||||
this.yl.value = [];
|
||||
function clear() {
|
||||
xYsMap.clear();
|
||||
yXsMap.clear();
|
||||
sortedXs = [];
|
||||
sortedYs = [];
|
||||
xLines.value = [];
|
||||
yLines.value = [];
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -112,18 +99,16 @@ export class ReferenceLine {
|
|||
* 5. 更新组件坐标
|
||||
* 6. 绘制参考线段
|
||||
*/
|
||||
updateRefLine(rect: DragRect) {
|
||||
this.xLines = [];
|
||||
this.yLines = [];
|
||||
const box = this.getRectBox(rect);
|
||||
const { xYsMap, yXsMap, sortedXs, sortedYs } = this;
|
||||
function updateRefLine(rect: DragRect) {
|
||||
const allXLines = [];
|
||||
const allYLines = [];
|
||||
const box = getRectBox(rect);
|
||||
let offsetX: number | undefined;
|
||||
let offsetY: number | undefined;
|
||||
if (!sortedXs.length && !sortedYs.length) {
|
||||
return { x: 0, y: 0 };
|
||||
}
|
||||
|
||||
let offsetX: number | undefined = undefined;
|
||||
let offsetY: number | undefined = undefined;
|
||||
|
||||
// 离最近X的距离
|
||||
const closetMinX = getClosestValInSortedArr(sortedXs, box.minX);
|
||||
const closetMidX = getClosestValInSortedArr(sortedXs, box.midX);
|
||||
|
|
@ -185,19 +170,19 @@ export class ReferenceLine {
|
|||
|
||||
if (offsetX !== undefined) {
|
||||
if (isEqualNum(0, closetMinX - targetBox.minX)) {
|
||||
this.yLines.push({
|
||||
allYLines.push({
|
||||
x: closetMinX,
|
||||
ys: [targetBox.minY, targetBox.maxY, ...(xYsMap.get(closetMinX) ?? [])],
|
||||
});
|
||||
}
|
||||
if (isEqualNum(0, closetMidX - targetBox.midX)) {
|
||||
this.yLines.push({
|
||||
allYLines.push({
|
||||
x: closetMidX,
|
||||
ys: [targetBox.midX, ...(xYsMap.get(closetMidX) ?? [])],
|
||||
});
|
||||
}
|
||||
if (isEqualNum(0, closetMaxX - targetBox.maxX)) {
|
||||
this.yLines.push({
|
||||
allYLines.push({
|
||||
x: closetMaxX,
|
||||
ys: [targetBox.minY, targetBox.maxY, ...(xYsMap.get(closetMaxX) ?? [])],
|
||||
});
|
||||
|
|
@ -206,45 +191,62 @@ export class ReferenceLine {
|
|||
|
||||
if (offsetY !== undefined) {
|
||||
if (isEqualNum(0, closetMinY - targetBox.minY)) {
|
||||
this.xLines.push({
|
||||
allXLines.push({
|
||||
y: closetMinY,
|
||||
xs: [targetBox.minX, targetBox.maxX, ...(yXsMap.get(closetMinY) ?? [])],
|
||||
});
|
||||
}
|
||||
if (isEqualNum(0, closetMidY - targetBox.midY)) {
|
||||
this.xLines.push({
|
||||
allXLines.push({
|
||||
y: closetMidY,
|
||||
xs: [targetBox.midX, ...(yXsMap.get(closetMidY) ?? [])],
|
||||
});
|
||||
}
|
||||
if (isEqualNum(0, closetMaxY - targetBox.maxY)) {
|
||||
this.xLines.push({
|
||||
allXLines.push({
|
||||
y: closetMaxY,
|
||||
xs: [targetBox.minX, targetBox.maxX, ...(yXsMap.get(closetMaxY) ?? [])],
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
const yl: any[] = [];
|
||||
for (const line of this.yLines) {
|
||||
const yls: any[] = [];
|
||||
for (const line of allYLines) {
|
||||
const y = Math.min(...line.ys);
|
||||
const h = Math.max(...line.ys) - y;
|
||||
yl.push({ x: line.x, y, h });
|
||||
yls.push({ x: line.x, y, h });
|
||||
}
|
||||
|
||||
const xl: any[] = [];
|
||||
for (const line of this.xLines) {
|
||||
const xls: any[] = [];
|
||||
for (const line of allXLines) {
|
||||
const x = Math.min(...line.xs);
|
||||
const w = Math.max(...line.xs) - x;
|
||||
xl.push({ y: line.y, x, w });
|
||||
xls.push({ y: line.y, x, w });
|
||||
}
|
||||
|
||||
this.yl.value = yl;
|
||||
this.xl.value = xl;
|
||||
yLines.value = yls;
|
||||
xLines.value = xls;
|
||||
|
||||
return {
|
||||
x: offsetX,
|
||||
y: offsetY,
|
||||
offsetX,
|
||||
offsetY,
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
active,
|
||||
xLines,
|
||||
yLines,
|
||||
recordBlocksXY,
|
||||
updateRefLine,
|
||||
};
|
||||
};
|
||||
|
||||
export interface DragRect {
|
||||
left: number;
|
||||
top: number;
|
||||
width: number;
|
||||
height: number;
|
||||
}
|
||||
|
||||
export type ReferenceLine = ReturnType<typeof useReferenceLine>;
|
||||
|
|
@ -0,0 +1,63 @@
|
|||
import { Ref } from "vue";
|
||||
import { Container } from ".";
|
||||
|
||||
/**
|
||||
* 画布场景
|
||||
* @description 处理平移和缩放事件
|
||||
*/
|
||||
export const useScene = (container: Ref<Container>) => {
|
||||
const minZoom = 0.5;
|
||||
const maxZoom = 10;
|
||||
const zoomStep = 0.1;
|
||||
let startX = 0;
|
||||
let startY = 0;
|
||||
let cacheX = 0;
|
||||
let cacheY = 0;
|
||||
|
||||
const onMouseDown = (e: MouseEvent) => {
|
||||
startX = e.x;
|
||||
startY = e.y;
|
||||
cacheX = container.value.x;
|
||||
cacheY = container.value.y;
|
||||
window.addEventListener("mousemove", onMouseMove);
|
||||
window.addEventListener("mouseup", onMouseUp);
|
||||
};
|
||||
|
||||
const onMouseMove = (e: MouseEvent) => {
|
||||
container.value.x = cacheX + (e.x - startX);
|
||||
container.value.y = cacheY + (e.y - startY);
|
||||
};
|
||||
|
||||
const onMouseUp = () => {
|
||||
window.removeEventListener("mousemove", onMouseMove);
|
||||
window.removeEventListener("mouseup", onMouseUp);
|
||||
};
|
||||
|
||||
const onMouseWheel = (e: WheelEvent) => {
|
||||
e.preventDefault();
|
||||
|
||||
const el = e.currentTarget as HTMLElement;
|
||||
const rect = el.getBoundingClientRect();
|
||||
const x = (e.clientX - rect.x) / container.value.zoom;
|
||||
const y = (e.clientY - rect.y) / container.value.zoom;
|
||||
const delta = -e.deltaY > 0 ? zoomStep : -zoomStep;
|
||||
|
||||
container.value.zoom += delta;
|
||||
if (container.value.zoom < minZoom) {
|
||||
container.value.zoom = minZoom;
|
||||
return;
|
||||
}
|
||||
if (container.value.zoom > maxZoom) {
|
||||
container.value.zoom = maxZoom;
|
||||
return;
|
||||
}
|
||||
|
||||
container.value.x += -x * delta + el.offsetWidth * (delta / 2);
|
||||
container.value.y += -y * delta + el.offsetHeight * (delta / 2);
|
||||
};
|
||||
|
||||
return {
|
||||
onMouseDown,
|
||||
onMouseWheel,
|
||||
};
|
||||
};
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
import Editor from "./components/Editor.vue";
|
||||
|
||||
export { Editor }
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
const sleep = (time: number) => {
|
||||
export const sleep = (time: number) => {
|
||||
return new Promise<void>((resolve) => {
|
||||
setTimeout(() => {
|
||||
resolve();
|
||||
|
|
@ -12,7 +12,7 @@
|
|||
<script setup lang="tsx">
|
||||
import { api } from "@/api";
|
||||
import { Table, useTable } from "@/components";
|
||||
import aniEditor from "@/components/editor/index.vue";
|
||||
import { Editor as aniEditor } from "@/components/editor";
|
||||
import dayjs from "dayjs";
|
||||
|
||||
const visible = ref(false);
|
||||
|
|
|
|||
|
|
@ -61,30 +61,30 @@ declare module '@vue/runtime-core' {
|
|||
ATree: typeof import('@arco-design/web-vue')['Tree']
|
||||
AUpload: typeof import('@arco-design/web-vue')['Upload']
|
||||
BaseOption: typeof import('./../components/editor/components/BaseOption.vue')['default']
|
||||
Block: typeof import('./../components/editor/panel-main/components/block.vue')['default']
|
||||
BreadCrumb: typeof import('./../components/breadcrumb/bread-crumb.vue')['default']
|
||||
BreadPage: typeof import('./../components/breadcrumb/bread-page.vue')['default']
|
||||
ColorPicker: typeof import('./../components/editor/components/ColorPicker.vue')['default']
|
||||
DragResizer: typeof import('./../components/editor/components/DragResizer.vue')['default']
|
||||
Editor: typeof import('./../components/editor/index.vue')['default']
|
||||
Editor: typeof import('./../components/editor/components/Editor.vue')['default']
|
||||
EditorPreview: typeof import('./../components/editor/components/EditorPreview.vue')['default']
|
||||
Empty: typeof import('./../components/empty/index.vue')['default']
|
||||
Header: typeof import('./../components/editor/panel-main/components/header.vue')['default']
|
||||
ImagePicker: typeof import('./../components/editor/components/ImagePicker.vue')['default']
|
||||
InputColor: typeof import('./../components/editor/components/InputColor.vue')['default']
|
||||
InputImage: typeof import('./../components/editor/components/InputImage.vue')['default']
|
||||
InputTexter: typeof import('./../components/editor/components/InputTexter.vue')['default']
|
||||
Marquee: typeof import('./../components/editor/blocks/text/marquee.vue')['default']
|
||||
Option: typeof import('./../components/editor/blocks/date/option.vue')['default']
|
||||
Page403: typeof import('./../components/error/page-403.vue')['default']
|
||||
PanelHeader: typeof import('./../components/editor/panel-header/index.vue')['default']
|
||||
PanelLeft: typeof import('./../components/editor/panel-left/index.vue')['default']
|
||||
PanelMain: typeof import('./../components/editor/panel-main/index.vue')['default']
|
||||
PanelRight: typeof import('./../components/editor/panel-right/index.vue')['default']
|
||||
Preview: typeof import('./../components/editor/preview/index.vue')['default']
|
||||
PanelHeader: typeof import('./../components/editor/components/PanelHeader.vue')['default']
|
||||
PanelLeft: typeof import('./../components/editor/components/PanelLeft.vue')['default']
|
||||
PanelMain: typeof import('./../components/editor/components/PanelMain.vue')['default']
|
||||
PanelMainBlock: typeof import('./../components/editor/components/PanelMainBlock.vue')['default']
|
||||
PanelMainHeader: typeof import('./../components/editor/components/PanelMainHeader.vue')['default']
|
||||
PanelRight: typeof import('./../components/editor/components/PanelRight.vue')['default']
|
||||
Render: typeof import('./../components/editor/blocks/date/render.vue')['default']
|
||||
RouterLink: typeof import('vue-router')['RouterLink']
|
||||
RouterView: typeof import('vue-router')['RouterView']
|
||||
'Temp.dev1': typeof import('./../components/breadcrumb/temp.dev1.vue')['default']
|
||||
Texter: typeof import('./../components/editor/panel-main/components/texter.vue')['default']
|
||||
Toast: typeof import('./../components/toast/toast.vue')['default']
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue