feat: 添加主界面
parent
79e016fbd7
commit
2bc4ac1d34
|
|
@ -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>
|
||||||
|
|
@ -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,
|
||||||
|
};
|
||||||
|
|
@ -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;
|
||||||
|
};
|
||||||
|
|
@ -0,0 +1,11 @@
|
||||||
|
|
||||||
|
export interface Container {
|
||||||
|
id: number | string;
|
||||||
|
title: string;
|
||||||
|
description?: string;
|
||||||
|
width: number;
|
||||||
|
height: number;
|
||||||
|
bgImage?: string;
|
||||||
|
bgColor?: string;
|
||||||
|
zoom: number;
|
||||||
|
}
|
||||||
|
|
@ -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>;
|
||||||
|
|
||||||
|
|
@ -1 +1,4 @@
|
||||||
export const ContextKey = Symbol('ContextKey');
|
export * from './block';
|
||||||
|
export * from './blocker';
|
||||||
|
export * from './container';
|
||||||
|
export * from './context';
|
||||||
|
|
@ -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">
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
|
|
@ -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,
|
||||||
|
})
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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>
|
||||||
|
|
@ -10,24 +10,12 @@
|
||||||
</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">
|
||||||
<a-tooltip content="帮助" position="right">
|
<a-tooltip content="帮助" position="right">
|
||||||
<a-button type="text">
|
<a-button type="text">
|
||||||
<template #icon>
|
<template #icon>
|
||||||
|
|
@ -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>
|
||||||
|
|
|
||||||
|
|
@ -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>
|
||||||
|
|
@ -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>
|
||||||
|
|
@ -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 class="flex items-center">
|
|
||||||
<span class="text-gray-400 text-xs mr-2">
|
|
||||||
尺寸:
|
|
||||||
<span class="text-gray-700">1920 * 1080</span>
|
|
||||||
</span>
|
|
||||||
<span class="text-gray-400 text-xs mr-2">
|
|
||||||
比例:
|
|
||||||
<span class="text-gray-700">100%</span>
|
|
||||||
</span>
|
|
||||||
<span class="text-gray-400 text-xs mr-2">
|
|
||||||
组件:
|
|
||||||
<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>
|
||||||
<div class="h-full p-5 px-6">
|
<div class="h-full w-full overflow-hidden p-4">
|
||||||
<div class="bg-white h-full relative">
|
<div class="dd1 w-full h-full flex items-center justify-center overflow-hidden relative bg-slate-50">
|
||||||
<drag-resizer
|
<div
|
||||||
:style="{ backgroundColor: item.bgColor }"
|
class="relative"
|
||||||
:x="item.x"
|
:style="containerStyle"
|
||||||
:y="item.y"
|
@drop="onDragDrop"
|
||||||
:w="item.w"
|
@dragover.prevent
|
||||||
:h="item.h"
|
@wheel="onMouseWheel"
|
||||||
:parentLimitation="true"
|
@mousedown="onMouseDown"
|
||||||
@dragging="onItemDragOrResize"
|
@mousemove="onMouseMove"
|
||||||
@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,38 +23,121 @@
|
||||||
|
|
||||||
<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,
|
x: 0,
|
||||||
y: 0,
|
y: 0,
|
||||||
w: 200,
|
startX: 0,
|
||||||
h: 100,
|
startY: 0,
|
||||||
bgColor: "#0099ff",
|
mouseX: 0,
|
||||||
xFixed: false,
|
mouseY: 0,
|
||||||
yFixed: false,
|
|
||||||
resizable: true,
|
|
||||||
draggable: true,
|
|
||||||
});
|
});
|
||||||
|
|
||||||
const onItemDragOrResize = (rect: any) => {
|
const onMouseDown = (e: Event) => {
|
||||||
item.value.x = rect.left;
|
isStart.value = true;
|
||||||
item.value.y = rect.top;
|
position.value.startX = e.offsetX;
|
||||||
item.value.w = rect.width;
|
position.value.startY = e.offsetY;
|
||||||
item.value.h = rect.height;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const textCh = {
|
const onMouseMove = (e: Event) => {
|
||||||
text: "进站前请准备好车票或乘车二维码。进站前请主动配合车站工作人员,接受安检,准备好自己的车票或乘车二维码。车站检票闸机会扫描并验证车票或乘车二维码,验证通过后方可进站。",
|
if (!isStart.value) {
|
||||||
family: "",
|
return;
|
||||||
bold: false,
|
}
|
||||||
italic: false,
|
const scale = container.value.zoom;
|
||||||
underline: false,
|
position.value.x += (e.offsetX - position.value.startX) * scale;
|
||||||
size: 14,
|
position.value.y += (e.offsetY - position.value.startY) * scale;
|
||||||
color: "#ffffff",
|
};
|
||||||
align: 3,
|
|
||||||
|
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,
|
||||||
|
y: 0,
|
||||||
|
w: 200,
|
||||||
|
h: 100,
|
||||||
|
bgColor: "#0099ff",
|
||||||
|
xFixed: false,
|
||||||
|
yFixed: false,
|
||||||
|
resizable: true,
|
||||||
|
draggable: true,
|
||||||
|
type,
|
||||||
|
x: e.offsetX,
|
||||||
|
y: e.offsetY,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const onMouseWheel = (e: WheelEvent) => {
|
||||||
|
e.preventDefault();
|
||||||
|
const prezoom = container.value.zoom;
|
||||||
|
let zoom = prezoom;
|
||||||
|
if (e.wheelDelta > 0) {
|
||||||
|
console.log("放大");
|
||||||
|
zoom += 0.1;
|
||||||
|
if (zoom > 10) {
|
||||||
|
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>
|
||||||
|
|
|
||||||
|
|
@ -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,
|
||||||
|
|
|
||||||
|
|
@ -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: {
|
||||||
|
|
|
||||||
|
|
@ -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']
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue