feat: 添加主界面

master
luoer 2023-10-10 17:46:02 +08:00
parent 79e016fbd7
commit 2bc4ac1d34
17 changed files with 567 additions and 126 deletions

View File

@ -0,0 +1,16 @@
<template>
<a-modal title="选择图片" v-model:visible="visible" title-align="start" :closable="false">
<div class="flex flex-wrap gap-4">
<div v-for="item in images" :key="item" class="w-24 h-24 bg-gray-200 flex items-center justify-center">
<img :src="item" class="w-full h-full object-cover" />
</div>
</div>
</a-modal>
</template>
<script setup lang="ts">
const visible = ref(false);
const images = ref([]);
</script>
<style scoped></style>

View File

@ -0,0 +1,35 @@
export interface Block<T = any> {
id: string;
type: string;
x: number;
y: number;
w: number;
h: number;
xFixed: boolean;
yFixed: boolean;
bgImage?: string;
bgColor?: string;
data: T;
meta: Record<string, any>;
actived: false,
resizable: boolean,
draggable: boolean,
}
export const DefaultBlock: Block = {
id: "",
type: "",
x: 0,
y: 0,
w: 50,
h: 50,
xFixed: false,
yFixed: false,
bgImage: "",
bgColor: "",
data: {},
meta: {},
actived: false,
resizable: true,
draggable: true,
};

View File

@ -0,0 +1,26 @@
import { Component } from "vue";
import { Block } from "./block";
interface Blocker {
/**
*
*/
initial: Block;
/**
*
*/
render: Component;
/**
*
*/
option: Component;
}
/**
*
* @param blocker
* @returns
*/
export const defineBlocker = (blocker: Blocker) => {
return blocker;
};

View File

@ -0,0 +1,11 @@
export interface Container {
id: number | string;
title: string;
description?: string;
width: number;
height: number;
bgImage?: string;
bgColor?: string;
zoom: number;
}

View File

@ -0,0 +1,15 @@
import { InjectionKey, Ref } from "vue";
import { Block } from "./block";
import { Container } from "./container";
export interface Context {
current: {
block: Block;
};
blocks: Ref<Block[]>;
container: Ref<Container>;
setCurrentBlock: (block: Block) => void;
}
export const ContextKey = Symbol('ContextKey') as InjectionKey<Context>;

View File

@ -1 +1,4 @@
export const ContextKey = Symbol('ContextKey'); export * from './block';
export * from './blocker';
export * from './container';
export * from './context';

View File

@ -20,20 +20,47 @@
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { ContextKey } from './config'; import { ContextKey } from "./config";
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";
import PanelRight from "./panel-right/index.vue"; import PanelRight from "./panel-right/index.vue";
const context = reactive({ const blocks = ref<Block>([]);
current: {
const current = ref({
block: null as Block | null,
});
const container = ref<Container>({
id: 11,
title: "国庆节喜庆版式设计",
description: "适用于国庆节1日-7日间上午9:00-10:00播出的版式设计",
width: 1920,
height: 1080,
bgImage: "",
bgColor: "#ffffff",
zoom: 1,
});
const setCurrentBlock = (block: Block | null) => {
for (const block of blocks.value) {
block.active = false;
} }
}) if (!block) {
current.value.block = null;
provide(ContextKey, {}) return;
}
block.active = true;
current.value.block = block;
};
provide(ContextKey, {
current,
container,
blocks,
setCurrentBlock,
});
</script> </script>
<style lang="less"> <style lang="less">

View File

@ -0,0 +1,11 @@
import Text from "./text";
export const BlockerMap = {
[Text.initial.type]: Text,
};
export const getBlockerRender = (type: string) => {
return BlockerMap[type].render;
}
export default BlockerMap;

View File

@ -1,6 +1,27 @@
import { defineBlocker } from '../../config'
import Option from './option.vue' import Option from './option.vue'
import Render from './render.vue' import Render from './render.vue'
export * from './interface' export * from './interface'
export { Option, Render } export default defineBlocker({
initial: {
id: "",
type: "text",
x: 0,
y: 0,
w: 50,
h: 50,
xFixed: false,
yFixed: false,
bgImage: "",
bgColor: "",
data: {},
meta: {},
actived: false,
resizable: true,
draggable: true,
},
render: Render,
option: Option,
})

View File

@ -0,0 +1,74 @@
<template>
<div>
<div class="flex gap-4">
<a-form-item label="左侧">
<a-input-number v-model="data.x" :min="0" :max="100">
<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"
></i>
</a-tooltip>
</template>
</a-input-number>
</a-form-item>
<a-form-item label="顶部">
<a-input-number v-model="data.y" :min="0" :max="100">
<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"
></i>
</a-tooltip>
</template>
</a-input-number>
</a-form-item>
</div>
<div class="flex gap-4">
<a-form-item label="宽度">
<a-input-number v-model="data.w" :min="0" :max="100"> </a-input-number>
</a-form-item>
<a-form-item label="高度">
<a-input-number v-model="data.h" :min="0" :max="100"> </a-input-number>
</a-form-item>
</div>
<a-form-item label="背景图片">
<a-input v-model="data.bgImage" class="group w-full" allow-clear placeholder="暂无">
<template #prefix>
<a-link class="!text-xs">选择</a-link>
</template>
</a-input>
</a-form-item>
<a-form-item label="背景颜色">
<a-input v-model="data.bgColor" allow-clear placeholder="无">
<template #prefix>
<color-picker v-model="data.bgColor"></color-picker>
</template>
</a-input>
</a-form-item>
</div>
</template>
<script setup lang="ts">
import { PropType } from "vue";
defineProps({
data: {
type: Object as PropType<any>,
required: true,
},
});
</script>
<style scoped></style>

View File

@ -10,21 +10,9 @@
</a-menu-item> </a-menu-item>
<a-menu-item key="0_1"> <a-menu-item key="0_1">
<template #icon> <template #icon>
<i class="icon-park-outline-all-application"></i> <i class="icon-park-outline-rss"></i>
</template> </template>
Menu 2 当前组件
</a-menu-item>
<a-menu-item key="0_2">
<template #icon>
<i class="icon-park-outline-all-application"></i>
</template>
Menu 3
</a-menu-item>
<a-menu-item key="0_3">
<template #icon>
<i class="icon-park-outline-all-application"></i>
</template>
Menu 4
</a-menu-item> </a-menu-item>
</a-menu> </a-menu>
<div class="w-full justify-center gap-1 grid text-center pb-4"> <div class="w-full justify-center gap-1 grid text-center pb-4">
@ -47,12 +35,37 @@
</a-tooltip> </a-tooltip>
</div> </div>
</div> </div>
<div></div> <div v-show="!collapsed">
<ul class="list-none px-2 grid gap-2" @dragstart="onDragStart" @dragover="onDragOver">
<li v-for="i in 10" class="flex items-center justify-between gap-2 border border-slate-200 text-gray-500 px-2 py-1 rounded cursor-move"
:draggable="true"
:data-type="'text'"
>
<div class="">
<i class="icon-park-outline-pic text-base text-gray-500"></i>
</div>
<div class="flex-1 leading-0">
<div class="">图片</div>
<!-- <div class="text-xs text-gray-400 mt-1">image</div> -->
</div>
</li>
</ul>
</div>
</div> </div>
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
const collapsed = ref(false); const collapsed = ref(false);
const onDragStart = (e: Event) => {
console.log('start');
e.dataTransfer?.setData("type", (e.target as HTMLElement).dataset.type!);
}
const onDragOver = (e: Event) => {
console.log('over');
e.preventDefault();
}
</script> </script>
<style scoped></style> <style scoped></style>

View File

@ -0,0 +1,60 @@
<template>
<drag-resizer
:x="data.x"
:y="data.y"
:w="data.w"
:h="data.h"
:parentW="container.width"
:parentH="container.height"
:parentScaleX="container.zoom / 100"
:parentScaleY="container.zoom / 100"
:parentLimitation="true"
:preventActiveBehavior="!data.draggable"
:isActive="data.active"
:isResizable="data.resizable"
:style="blockStyle"
@dragging="onItemDragOrResize"
@resizing="onItemDragOrResize"
@activated="setCurrentBlock(data)"
>
<component :is="BlockerMap[data.type].render" :data="{}" />
</drag-resizer>
</template>
<script setup lang="ts">
import { PropType } from "vue";
import { Block, Container, ContextKey } from "../../config";
import { BlockerMap } from "../../items";
import DragResizer from "../../components/DragResizer.vue";
const props = defineProps({
data: {
type: Object as PropType<Block>,
required: true,
},
container: {
type: Object as PropType<Container>,
required: true,
},
});
const { setCurrentBlock } = inject(ContextKey);
const blockStyle = computed(() => {
const { bgColor, bgImage } = props.data;
return {
backgroundColor: bgColor,
backgroundImage: bgImage ? `url(${bgImage})` : undefined,
backgroundSize: "100% 100%",
};
});
const onItemDragOrResize = (rect: any) => {
props.data.x = rect.left;
props.data.y = rect.top;
props.data.w = rect.width;
props.data.h = rect.height;
};
</script>
<style scoped></style>

View File

@ -0,0 +1,93 @@
<template>
<div class="h-full flex items-center justify-between gap-4 px-4 bg-white">
<div class="flex-1">
<div class="group">
<span class="text-gray-400">描述: </span>
<span v-if="!descEditing">
{{ container.description }}
<i
class="!hidden !group-hover:inline-block icon-park-outline-edit text-gray-400 hover:text-gray-700 ml-1 cursor-pointer"
@click="onDescEdit"
></i>
</span>
<span v-else class="inline-flex items-center">
<a-input size="small" v-model="descContent" class="!w-96"></a-input>
<a-button type="text" size="small" @click="onDescEdited" class="ml-2">
<template #icon>
<i class="icon-park-outline-check"></i>
</template>
</a-button>
<a-button type="text" size="small" class="!text-gray-500" @click="descEditing = false">
<template #icon>
<i class="icon-park-outline-close"></i>
</template>
</a-button>
</span>
</div>
</div>
<div class="flex items-center">
<span class="text-gray-400 text-xs mr-2">
尺寸
<span class="text-gray-700"> {{ container.width }} * {{ container.height }} </span>
</span>
<span class="text-gray-400 text-xs mr-2">
比例
<span class="text-gray-700">
{{ parseInt(container.zoom * 100) }}%
</span>
</span>
<span class="text-gray-400 text-xs mr-2">
组件
<span class="text-gray-700">{{ 2 }} </span>
</span>
<a-tooltip content="预览" position="bottom">
<a-button type="text">
<template #icon>
<i class="icon-park-outline-play text-base !text-gray-600"></i>
</template>
</a-button>
</a-tooltip>
<a-popover position="br" trigger="click">
<a-button type="text">
<template #icon>
<i class="icon-park-outline-config text-base !text-gray-600"></i>
</template>
</a-button>
<template #content>
<span>
背景图片:
<a-link>选择</a-link>
</span>
<span class="inline-flex items-center">
背景颜色
<color-picker></color-picker>
</span>
</template>
</a-popover>
</div>
</div>
</template>
<script setup lang="ts">
const props = defineProps({
container: {
type: Object as PropType<any>,
required: true,
},
});
const descEditing = ref(false);
const descContent = ref("");
const onDescEdited = () => {
props.container.description = descContent.value;
descEditing.value = false;
};
const onDescEdit = () => {
descContent.value = props.container.description;
descEditing.value = true;
};
</script>
<style scoped></style>

View File

@ -1,68 +1,21 @@
<template> <template>
<div class="h-full grid grid-rows-[auto_1fr]"> <div class="h-full grid grid-rows-[auto_1fr]">
<div class="h-10 flex items-center justify-between gap-4 px-4 bg-white"> <div class="h-10">
<div> <ani-header :container="container"></ani-header>
<span class="group">
<span class="text-gray-400">描述: </span>
国庆节上午9:00-10:00版式设计
<i
class="!hidden !group-hover:inline-block icon-park-outline-edit text-gray-400 hover:text-gray-700 ml-1"
></i>
</span>
</div> </div>
<div class="flex items-center"> <div class="h-full w-full overflow-hidden p-4">
<span class="text-gray-400 text-xs mr-2"> <div class="dd1 w-full h-full flex items-center justify-center overflow-hidden relative bg-slate-50">
尺寸 <div
<span class="text-gray-700">1920 * 1080</span> class="relative"
</span> :style="containerStyle"
<span class="text-gray-400 text-xs mr-2"> @drop="onDragDrop"
比例 @dragover.prevent
<span class="text-gray-700">100%</span> @wheel="onMouseWheel"
</span> @mousedown="onMouseDown"
<span class="text-gray-400 text-xs mr-2"> @mousemove="onMouseMove"
组件
<span class="text-gray-700">3 </span>
</span>
<a-tooltip content="预览" position="bottom">
<a-button type="text">
<template #icon>
<i class="icon-park-outline-play text-base !text-gray-600"></i>
</template>
</a-button>
</a-tooltip>
<a-popover position="br" trigger="click">
<a-button type="text">
<template #icon>
<i class="icon-park-outline-config text-base !text-gray-600"></i>
</template>
</a-button>
<template #content>
<span>
背景图片:
<a-link>选择</a-link>
</span>
<span class="inline-flex items-center">
背景颜色
<color-picker></color-picker>
</span>
</template>
</a-popover>
</div>
</div>
<div class="h-full p-5 px-6">
<div class="bg-white h-full relative">
<drag-resizer
:style="{ backgroundColor: item.bgColor }"
:x="item.x"
:y="item.y"
:w="item.w"
:h="item.h"
:parentLimitation="true"
@dragging="onItemDragOrResize"
@resizing="onItemDragOrResize"
> >
<text-render :data="textCh"> </text-render> <ani-block v-for="block in blocks" :key="block.id" :data="block" :container="container"></ani-block>
</drag-resizer> </div>
</div> </div>
</div> </div>
</div> </div>
@ -70,10 +23,75 @@
<script setup lang="ts"> <script setup lang="ts">
import ColorPicker from "../components/ColorPicker.vue"; import ColorPicker from "../components/ColorPicker.vue";
import DragResizer from "../components/DragResizer.vue"; import AniBlock from "./components/block.vue";
import TextRender from "../items/text/render.vue"; import AniHeader from "./components/header.vue";
import { Block, ContextKey, Container } from "../config";
const item = ref({ const isStart = ref(false);
const position = ref({
x: 0,
y: 0,
startX: 0,
startY: 0,
mouseX: 0,
mouseY: 0,
});
const onMouseDown = (e: Event) => {
isStart.value = true;
position.value.startX = e.offsetX;
position.value.startY = e.offsetY;
};
const onMouseMove = (e: Event) => {
if (!isStart.value) {
return;
}
const scale = container.value.zoom;
position.value.x += (e.offsetX - position.value.startX) * scale;
position.value.y += (e.offsetY - position.value.startY) * scale;
};
const onMouseUp = () => {
isStart.value = false;
};
onMounted(() => {
window.addEventListener("mouseup", onMouseUp);
});
onUnmounted(() => {
window.removeEventListener("mouseup", onMouseUp);
});
const { blocks, container } = inject(ContextKey);
const containerStyle = computed(() => {
const { width, height, bgColor, bgImage, zoom } = container.value;
const { x, y } = position.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})`,
transform: `matrix(${zoom}, 0, 0, ${zoom}, ${x}, ${y})`,
// transformOrigin: "0 0",
};
});
const onDragDrop = (e: Event) => {
e.preventDefault();
e.stopPropagation();
const type = e.dataTransfer?.getData("type");
if (!type) {
return;
}
blocks.value.push({
x: 0, x: 0,
y: 0, y: 0,
w: 200, w: 200,
@ -83,25 +101,43 @@ const item = ref({
yFixed: false, yFixed: false,
resizable: true, resizable: true,
draggable: true, draggable: true,
}); type,
x: e.offsetX,
const onItemDragOrResize = (rect: any) => { y: e.offsetY,
item.value.x = rect.left; });
item.value.y = rect.top;
item.value.w = rect.width;
item.value.h = rect.height;
}; };
const textCh = { const onMouseWheel = (e: WheelEvent) => {
text: "进站前请准备好车票或乘车二维码。进站前请主动配合车站工作人员,接受安检,准备好自己的车票或乘车二维码。车站检票闸机会扫描并验证车票或乘车二维码,验证通过后方可进站。", e.preventDefault();
family: "", const prezoom = container.value.zoom;
bold: false, let zoom = prezoom;
italic: false, if (e.wheelDelta > 0) {
underline: false, console.log("放大");
size: 14, zoom += 0.1;
color: "#ffffff", if (zoom > 10) {
align: 3, return;
}
} else {
console.log("缩小");
zoom -= 0.1;
if (zoom < 0.1) {
return;
}
}
zoom = parseFloat(zoom.toFixed(1));
// const { x, y } = position.value
// const x1 = x + e.offsetX;
// const y1 = y + e.offsetY;
// position.value.x = x1 - (x1 - x) * (zoom / prezoom);
// position.value.y = y1 - (y1 - y) * (zoom / prezoom);
// position.value.x = e.clientX;
// position.value.y = e.clientY;
container.value.zoom = zoom;
}; };
</script> </script>
<style scoped></style> <style scoped>
.dd1 {
background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAwAAAAMCAIAAADZF8uwAAAAGUlEQVQYV2M4gwH+YwCGIasIUwhT25BVBADtzYNYrHvv4gAAAABJRU5ErkJggg==);
}
</style>

View File

@ -5,9 +5,8 @@
<a-radio value="2">字体</a-radio> <a-radio value="2">字体</a-radio>
</a-radio-group> </a-radio-group>
<a-form :model="block" layout="vertical"> <a-form :model="block" layout="vertical">
<div class="muti-form-item mt-2"> <div v-if="current.block" class="muti-form-item mt-2">
<a-divider orientation="left">基本设置</a-divider> <component :is="BlockerMap[current.block.type].option" :data="current.block" />
<block-attr :block="item"></block-attr>
</div> </div>
<div class="muti-form-item"> <div class="muti-form-item">
<a-divider orientation="left">中文设置</a-divider> <a-divider orientation="left">中文设置</a-divider>
@ -20,6 +19,10 @@
<script setup lang="ts"> <script setup lang="ts">
import BlockAttr from "./block-attr.vue"; import BlockAttr from "./block-attr.vue";
import TextAttr from "./text-attr.vue"; import TextAttr from "./text-attr.vue";
import { ContextKey } from "../config";
import BlockerMap from "../items";
const { current } = inject(ContextKey);
const item = ref<any>({ const item = ref<any>({
x: 0, x: 0,

View File

@ -54,8 +54,6 @@
<a-select v-model="block.align" :options="TextAlignOptions"></a-select> <a-select v-model="block.align" :options="TextAlignOptions"></a-select>
</a-form-item> </a-form-item>
</div> </div>
</div> </div>
</template> </template>
@ -63,6 +61,10 @@
import { PropType } from "vue"; import { PropType } from "vue";
import ColorPicker from "../components/ColorPicker.vue"; import ColorPicker from "../components/ColorPicker.vue";
import { TextAlignOptions, TextFamilyOptions } from "../interface"; import { TextAlignOptions, TextFamilyOptions } from "../interface";
import { ContextKey } from '../config';
const context = inject(ContextKey);
console.log('ctx', context);
defineProps({ defineProps({
block: { block: {

View File

@ -7,42 +7,37 @@ export {}
declare module '@vue/runtime-core' { declare module '@vue/runtime-core' {
export interface GlobalComponents { export interface GlobalComponents {
AAvatar: typeof import('@arco-design/web-vue')['Avatar']
AButton: typeof import('@arco-design/web-vue')['Button'] AButton: typeof import('@arco-design/web-vue')['Button']
ACheckbox: typeof import('@arco-design/web-vue')['Checkbox']
ADivider: typeof import('@arco-design/web-vue')['Divider'] ADivider: typeof import('@arco-design/web-vue')['Divider']
ADoption: typeof import('@arco-design/web-vue')['Doption'] ADoption: typeof import('@arco-design/web-vue')['Doption']
ADrawer: typeof import('@arco-design/web-vue')['Drawer']
ADropdown: typeof import('@arco-design/web-vue')['Dropdown']
ADropdownButton: typeof import('@arco-design/web-vue')['DropdownButton'] ADropdownButton: typeof import('@arco-design/web-vue')['DropdownButton']
AForm: typeof import('@arco-design/web-vue')['Form'] AForm: typeof import('@arco-design/web-vue')['Form']
AFormItem: typeof import('@arco-design/web-vue')['FormItem'] AFormItem: typeof import('@arco-design/web-vue')['FormItem']
AInput: typeof import('@arco-design/web-vue')['Input'] AInput: typeof import('@arco-design/web-vue')['Input']
AInputNumber: typeof import('@arco-design/web-vue')['InputNumber'] AInputNumber: typeof import('@arco-design/web-vue')['InputNumber']
AInputSearch: typeof import('@arco-design/web-vue')['InputSearch'] AInputPassword: typeof import('@arco-design/web-vue')['InputPassword']
ALayout: typeof import('@arco-design/web-vue')['Layout']
ALayoutContent: typeof import('@arco-design/web-vue')['LayoutContent']
ALayoutHeader: typeof import('@arco-design/web-vue')['LayoutHeader']
ALayoutSider: typeof import('@arco-design/web-vue')['LayoutSider']
ALink: typeof import('@arco-design/web-vue')['Link'] ALink: typeof import('@arco-design/web-vue')['Link']
AMenu: typeof import('@arco-design/web-vue')['Menu'] AMenu: typeof import('@arco-design/web-vue')['Menu']
AMenuItem: typeof import('@arco-design/web-vue')['MenuItem'] AMenuItem: typeof import('@arco-design/web-vue')['MenuItem']
AMenuItemGroup: typeof import('@arco-design/web-vue')['MenuItemGroup']
AModal: typeof import('@arco-design/web-vue')['Modal'] AModal: typeof import('@arco-design/web-vue')['Modal']
APopover: typeof import('@arco-design/web-vue')['Popover'] APopover: typeof import('@arco-design/web-vue')['Popover']
ARadio: typeof import('@arco-design/web-vue')['Radio'] ARadio: typeof import('@arco-design/web-vue')['Radio']
ARadioGroup: typeof import('@arco-design/web-vue')['RadioGroup'] ARadioGroup: typeof import('@arco-design/web-vue')['RadioGroup']
AScrollbar: typeof import('@arco-design/web-vue')['Scrollbar']
ASelect: typeof import('@arco-design/web-vue')['Select'] ASelect: typeof import('@arco-design/web-vue')['Select']
ASpin: typeof import('@arco-design/web-vue')['Spin'] ASpace: typeof import('@arco-design/web-vue')['Space']
ATag: typeof import('@arco-design/web-vue')['Tag'] ATag: typeof import('@arco-design/web-vue')['Tag']
ATextarea: typeof import('@arco-design/web-vue')['Textarea'] ATextarea: typeof import('@arco-design/web-vue')['Textarea']
ATooltip: typeof import('@arco-design/web-vue')['Tooltip'] ATooltip: typeof import('@arco-design/web-vue')['Tooltip']
Block: typeof import('./../components/editor/panel-main/components/block.vue')['default']
BlockAttr: typeof import('./../components/editor/panel-right/block-attr.vue')['default'] BlockAttr: typeof import('./../components/editor/panel-right/block-attr.vue')['default']
BreadCrumb: typeof import('./../components/breadcrumb/bread-crumb.vue')['default'] BreadCrumb: typeof import('./../components/breadcrumb/bread-crumb.vue')['default']
BreadPage: typeof import('./../components/breadcrumb/bread-page.vue')['default'] BreadPage: typeof import('./../components/breadcrumb/bread-page.vue')['default']
ColorPicker: typeof import('./../components/editor/components/ColorPicker.vue')['default'] ColorPicker: typeof import('./../components/editor/components/ColorPicker.vue')['default']
DragResizer: typeof import('./../components/editor/components/DragResizer.vue')['default'] DragResizer: typeof import('./../components/editor/components/DragResizer.vue')['default']
Editor: typeof import('./../components/editor/index.vue')['default'] Editor: typeof import('./../components/editor/index.vue')['default']
Header: typeof import('./../components/editor/panel-main/components/header.vue')['default']
ImagePicker: typeof import('./../components/editor/components/ImagePicker.vue')['default']
Option: typeof import('./../components/editor/items/text/option.vue')['default'] Option: typeof import('./../components/editor/items/text/option.vue')['default']
Page403: typeof import('./../components/error/page-403.vue')['default'] Page403: typeof import('./../components/error/page-403.vue')['default']
PanelHeader: typeof import('./../components/editor/panel-header/index.vue')['default'] PanelHeader: typeof import('./../components/editor/panel-header/index.vue')['default']