dataModel.
value
!.
blocks
= [
...focusData.
value
.
unfocus
,
]
as
VisualEditorBlockData
[];
tip
:
"ctrl+d, backspance, delete,"
,
map一下
toolButtons
并编写对应样式
<div class="head">
{toolButtons.map((btn, index) => (
<div key={index} class="head-btn" onClick={btn.handler}>
<i class={`iconfont ${btn.icon}`}></i>
<span>{btn.label}</span>
</div>
八、给选中组件添加拖拽点
水平、垂直拖拽
按下shift键拖拽组件时,组件只能横向或纵向移动
修改 blockDragger
中的mousemove
函数
当鼠标横向移动的距离 大于 纵向移动的距离,将纵向的偏移置为0
当鼠标横向移动的距离 小于 纵向移动的距离,将横向的偏移置为0
const mousemove = (e: MouseEvent) => {
let durX = e.clientX - dragState.startX;
let durY = e.clientY - dragState.startY;
if (e.shiftKey) {
if (Math.abs(durX) > Math.abs(durY)) {
durY = 0;
} else {
durX = 0;
focusData.value.focus.forEach((block, i) => {
block.top = dragState.startPos[i].top + durY;
block.left = dragState.startPos[i].left + durX;
给组件添加拖拽点
有些组件可以调整宽度和高度,有些只能调整高度 或 只能调整宽度
可以调整宽高的显示六个拖拽点,只能调整宽度的显示左右两个点,只能调整高度的显示上下两个点
给声明的数据结构VisualEditorComponent
添加 resize
属性,控制组件是否可以调整宽度或者高度
export interface VisualEditorComponent {
key: string;
label: string;
preview: () => JSX.Element;
render: () => JSX.Element;
+ resize?: { width?: boolean; height?: boolean };
在visual.config
中给注册的button
input
组件添加resize属性
visualConfig.registry("button", {
label: "按钮",
preview: () => <ElButton>按钮</ElButton>,
render: () => <ElButton>渲染按钮</ElButton>,
resize: { width: true, height: true },
visualConfig.registry("input", {
label: "输入框",
preview: () => <ElInput />,
render: () => <ElInput />,
resize: { width: true },
新建 block-resizer
组件,负责拖拽点的显示和拖拽拉伸
接受 block
和 component
(在config中注册的组件对象)两个属性
通过 width
和 height
控制拖拽点的显示
import {
VisualEditorBlockData,
VisualEditorComponent,
VisualEditorConfig,
} from "@/packages/visual-editor.utils";
import { defineComponent, PropType } from "vue";
import "./style.scss";
export const BlockResizer = defineComponent({
props: {
block: { type: Object as PropType<VisualEditorBlockData>, required: true },
component: {
type: Object as PropType<VisualEditorComponent>,
required: true,
setup(props) {
const { width, height } = props.component.resize || {};
return () => (
{/* 显示上下中间的两个点 */}
{height && (
<div class="block-resize block-resize-top"></div>
<div class="block-resize block-resize-bottom"></div>
{}
{width && (
<div class="block-resize block-resize-left"></div>
<div class="block-resize block-resize-right"></div>
{}
{width && height && (
<div class="block-resize block-resize-top-left"></div>
<div class="block-resize block-resize-top-right"></div>
<div class="block-resize block-resize-bottom-left"></div>
<div class="block-resize block-resize-bottom-right"></div>
block-resizer
样式
$space: 6px;
$size: 6px;
$primary: #409eff;
.block-resize {
position: absolute;
top: -$space;
left: -$space;
right: -$space;
bottom: -$space;
width: $size;
height: $size;
background-color: $primary;
&.block-resize-top {
left: calc(50% - #{$size / 2});
right: initial;
bottom: initial;
&.block-resize-bottom {
left: calc(50% - #{$size / 2});
right: initial;
top: initial;
&.block-resize-left {
top: calc(50% - #{$size / 2});
bottom: initial;
right: initial;
&.block-resize-right {
top: calc(50% - #{$size / 2});
left: initial;
bottom: initial;
&.block-resize-top-left {
right: initial;
bottom: initial;
&.block-resize-top-right {
left: initial;
bottom: initial;
&.block-resize-bottom-left {
top: initial;
right: initial;
&.block-resize-bottom-right {
left: initial;
top: initial;
在 visual-editor-block
中引用
组件选中状态,且可以调整宽高状态下才显示 拖拽点
<div class={classes.value} style={styles.value} ref={el}>
{Render}
{props.block?.focus && (width || height) && (
<BlockResizer
block={props.block!}
component={component!}
></BlockResizer>
九、调整组件宽高大小
1、数据类型补充
给VisualEditorBlockData
添加 宽高 和 是否调整过宽高 的属性
给 VisualEditorComponent
的 render
方法添加回调参数 size
export interface VisualEditorBlockData {
top: number;
left: number;
componentKey: string;
adjustPosition: boolean;
focus: boolean;
+ width: number;
+ height: number;
+ hasResize: boolean;
export interface VisualEditorComponent {
key: string;
label: string;
preview: () => JSX.Element;
+ render: (data: { size: { width?: number; height?: number } }) => JSX.Element;
resize?: { width?: boolean; height?: boolean };
2、visual-editor-block
渲染组件时将 block的size属性传给render函数
const renderProps = {
size: props.block?.hasResize
width: props.block.width,
height: props.block.height,
: {},
const Render = component?.render(renderProps);
3、visual.config
在组件的渲染函数中使用传进来的 size
数据
visualConfig.registry("button", {
label: "按钮",
preview: () => <ElButton>按钮</ElButton>,
+ render: ({ size }) => (
<ElButton style={{ width: `${size.width}px`, height: `${size.height}px` }}>
</ElButton>
resize: { width: true, height: true },
visualConfig.registry("input", {
label: "输入框",
preview: () => <ElInput />,
+ render: ({ size }) => <ElInput style={{ width: `${size.width}px` }} />,
resize: { width: true },
监听拉伸节点事件,调整组件大小
声明Direction
枚举,在mousemove
时判断是哪个节点发生的事件,做不同操作
实现onMousedown
方法,并监听每个节点的 onMousedown
事件
当鼠标在节点按下的时候,触发 mousedown
事件,在该事件中记录初始值,并监听mousemove
和 mouseup
事件
鼠标点击节点移动时,执行 mousemove
事件,在这里来计算组件的宽高 (核心点,有兴趣可以深入研究,内含彩蛋),修改block的 width
和height
,重新进行渲染。
import {
VisualEditorBlockData,
VisualEditorComponent,
VisualEditorConfig,
} from "@/packages/visual-editor.utils";
import { defineComponent, PropType } from "vue";
import "./style.scss";
enum Direction {
start = "start",
center = "center",
end = "end",
export const BlockResizer = defineComponent({
props: {
block: { type: Object as PropType<VisualEditorBlockData>, required: true },
component: {
type: Object as PropType<VisualEditorComponent>,
required: true,
setup(props) {
const { width, height } = props.component.resize || {};
const onMousedown = (() => {
let data = {
startX: 0,
startY: 0,
startWidth: 0,
startHeight: 0,
startLeft: 0,
startTop: 0,
direction: {} as { horizontal: Direction; vertical: Direction },
const mousemove = (e: MouseEvent) => {
const {
startX,
startY,
startWidth,
startHeight,
direction,
startLeft,
startTop,
} = data;
let { clientX: moveX, clientY: moveY } = e;
if (direction.horizontal === Direction.center) {
moveX = startX;
if (direction.vertical === Direction.center) {
moveY = startY;
let durX = moveX - startX;
let durY = moveY - startY;
const block = props.block as VisualEditorBlockData;
if (direction.vertical === Direction.start) {
durY = -durY;
block.top = startTop - durY;
if (direction.horizontal === Direction.start) {
durX = -durX;
block.left = startLeft - durX;
const width = startWidth + durX;
const height = startHeight + durY;
block.width = width;
block.height = height;
block.hasResize = true;
const mouseup = (e: MouseEvent) => {
console.log(e);
document.body.removeEventListener("mousemove", mousemove);
document.body.removeEventListener("mouseup", mouseup);
const mousedown = (
e: MouseEvent,
direction: { horizontal: Direction; vertical: Direction }
) => {
e.stopPropagation();
document.body.addEventListener("mousemove", mousemove);
document.body.addEventListener("mouseup", mouseup);
data = {
startX: e.clientX,
startY: e.clientY,
direction,
startWidth: props.block.width,
startHeight: props.block.height,
startLeft: props.block.left,
startTop: props.block.top,
return mousedown;
})();
return () => (
{height && (
class="block-resize block-resize-top"
onMousedown={(e) =>
onMousedown(e, {
horizontal: Direction.center,
vertical: Direction.start,
></div>
class="block-resize block-resize-bottom"
onMousedown={(e) =>
onMousedown(e, {
horizontal: Direction.center,
vertical: Direction.end,
></div>
{width && (
class="block-resize block-resize-left"
onMousedown={(e) =>
onMousedown(e, {
horizontal: Direction.start,
vertical: Direction.center,
></div>
class="block-resize block-resize-right"
onMousedown={(e) =>
onMousedown(e, {
horizontal: Direction.end,
vertical: Direction.center,
></div>
{width && height && (
class="block-resize block-resize-top-left"
onMousedown={(e) =>
onMousedown(e, {
horizontal: Direction.start,
vertical: Direction.start,
></div>
class="block-resize block-resize-top-right"
onMousedown={(e) =>
onMousedown(e, {
horizontal: Direction.end,
vertical: Direction.start,
></div>
class="block-resize block-resize-bottom-left"
onMousedown={(e) =>
onMousedown(e, {
horizontal: Direction.start,
vertical: Direction.end,
></div>
class="block-resize block-resize-bottom-right"
onMousedown={(e) =>
onMousedown(e, {
horizontal: Direction.end,
vertical: Direction.end,
></div>
拖拽时的鼠标效果,需要给每个节点添加 cursor
属性
英文 东西南北 首字母来表示 上下左右
下一节 组件拖拽辅助线对齐与组件属性设置
Miller
Vue.js