web架构师编辑器内容-创建业务组件和编辑器基本行为

news2025/1/26 15:37:42

编辑器主要分为三部分,左侧是组件模板库,中间是画布区域,右侧是面板设置区域。
左侧是预设各种组件模板进行添加
中间是使用交互手段来更新元素的值
右侧是使用表单的方式来更新元素的值。
大致效果:
在这里插入图片描述

  1. 左侧组件模板库
    最初的模板配置:
export const defaultTextTemplates = [
  {
    text: '大标题',
    fontSize: '30px',
    fontWeight: 'bold',
    tag: 'h2'
  },
  {
    text: '楷体副标题',
    fontSize: '20px',
    fontWeight: 'bold',
    fontFamily: '"KaiTi","STKaiti"',
    tag: 'h2'
  },
  {
    text: '正文内容',
    tag: 'p'
  },
  {
    text: '宋体正文内容',
    tag: 'p',
    fontFamily: '"SimSun","STSong"'
  },
  {
    text: 'Arial style',
    tag: 'p',
    fontFamily: '"Arial", sans-serif'
  },
  {
    text: 'Comic Sans',
    tag: 'p',
    fontFamily: '"Comic Sans MS"'
  },
  {
    text: 'Courier New',
    tag: 'p',
    fontFamily: '"Courier New", monospace'
  },
  {
    text: 'Times New Roman',
    tag: 'p',
    fontFamily: '"Times New Roman", serif'
  },
  {
    text: '链接内容',
    color: '#1890ff',
    textDecoration: 'underline',
    tag: 'p'
  },
  {
    text: '按钮内容',
    color: '#ffffff',
    backgroundColor: '#1890ff',
    borderWidth: '1px',
    borderColor: '#1890ff',
    borderStyle: 'solid',
    borderRadius: '2px',
    paddingLeft: '10px',
    paddingRight: '10px',
    paddingTop: '5px',
    paddingBottom: '5px',
    width: '100px',
    tag: 'button',
    textAlign: 'center'
  }
]

在component-list组件中循环渲染这个模板
compnent-list组件:

<div
  class="component-item"
  v-for="(item, index) in props.list"
  @click="onItemClick(item)"
  :key="index"
>
  <LText v-bind="item"></LText>
</div>

// LText组件
<component class="l-text-component" :is="props.tag" :style="styleProps" @click="handleClick">{{ props.text }}
</component>
  1. 中间画布区
    基本的数据结构
export interface ComponentData {
  props: { [key: string]: any }
  id: string
  name: string
}

在左侧模板区域点击的时候,会emit一个onItemCreated事件:

const onItemCreated = (props: ComponentData) => {
  store.commit('addComponent', props)
}

store里面的addComponent方法:

addComponent(state, props) {
  const newComponent: ComponentData =
    id: uuidv4(),
    name: 'l-text',
    props
  }
  state.components.push(newComponent)
},

渲染中间画布区域:

<div v-for="component in components" :key="component.id">
    <EditWrapper v-if="!component.isHidden"
      :id="component.id"
      @set-active="setActive"
      :active="component.id === (currentElement && currentElement.id)" :props="component.props"
    >
    <component :is="canvasComponentList[component.name as 'l-text' | 'l-image' | 'l-shape']" v-bind="component.props" :isEditing="true"/>
  </EditWrapper>
  </div>

editWrapper组件就是为了隔离两个组件,方便后续的一些拖拽,拉伸,吸附的一些效果。

<template>
<div class="edit-wrapper" @click="itemClick"
    @dblclick="itemEdit"
    ref="editWrapper"
    :class="{active: active}" :style="styleProps"
    :data-component-id="id"
> 
  <!-- 元素的扩大 -->
  <div class="move-wrapper" ref="moveWrapper" @mousedown="startMove">
    <slot></slot>
  </div>
  <div class='resizers'>
    <div class='resizer top-left' @mousedown="startResize($event, 'top-left')"></div>
    <div class='resizer top-right'  @mousedown="startResize($event, 'top-right')"></div>
    <div class='resizer bottom-left' @mousedown="startResize($event, 'bottom-left')"></div>
    <div class='resizer bottom-right' @mousedown="startResize($event, 'bottom-right')"></div>
  </div>
</div>
</template>
  1. 右侧设置面板区域的渲染:
    在中间画布区域进行点击的时候,通过setActive事件,我们可以拿到当前的元素,
// store中的setActive
setActive(state, currentId: string) {
  state.currentElement = currentId;
},

然后就可以通过props-table组件进行渲染了:

<PropsTable v-if="currentElement && currentElement.props" 
  :props="currentElement.props"
  @change="handleChange"
></PropsTable>

props-table比较麻烦我们来一一讲解,首先来看一下props-talbe的template部分:

<template>
  <div class="props-table">
    <div
      v-for="(value, key) in finalProps"
      :key="key"
      :class="{ 'no-text': !value.text }"
      class="prop-item"
      :id="`item-${key}`"
    >
      <span class="label" v-if="value.text">{{ value.text }}</span>
      <div :class="`prop-component component-${value.component}`">
        <component
          :is="value.component"
          :[value.valueProp]="value.value"
          v-bind="value.extraProps"
          v-on="value.events"
        >
          <template v-if="value.options">
            <component
              :is="value.subComponent"
              v-for="(option, k) in value.options"
              :key="k"
              :value="option.value"
            >
              <render-vnode :vNode="option.text"></render-vnode>
            </component>
          </template>
        </component>
      </div>
    </div>
  </div>
</template>

我们最终渲染的是finalProps这个数据,finalProps数据的生成:

// 属性转化成表单的映射表 key:属性  value:使用的组件
export const mapPropsToForms: PropsToForms = {
  // 比如: text 属性,使用 a-input 这个组件去编辑
  text: {
    text: '文本',
    component: 'a-input',
    afterTransform: (e: any) => e.target.value,
  },
  fontSize: {
    text: '字号',
    component: 'a-input-number',
    // 为了适配类型,进行一定的转换
    initalTransform: (v: string) => parseInt(v),
    afterTransform: (e: number) => e ? `${e}px` : '',
  },
  lineHeight: {
    text: '行高',
    component: 'a-slider',
    extraProps: {
      min: 0,
      max: 3,
      step: 0.1
    },
    initalTransform: (v: string) => parseFloat(v)
  },
  textAlign: {
    component: 'a-radio-group',
    subComponent: 'a-radio-button',
    text: '对齐',
    options: [
      {
        value: 'left',
        text: '左'
      },
      {
        value: 'center',
        text: '中'
      },
      {
        value: 'right',
        text: '右'
      }
    ],
    afterTransform: (e: any) => e.target.value
  },
  fontFamily: {
    component: 'a-select',
    subComponent: 'a-select-option',
    text: '字体',
    options: [
      {
        value: '',
        text: '无'
      },
      ...fontFamilyOptions
    ],
    afterTransform: (e: any) => e
  },
  color: {
    component: 'color-pick',
    text: '字体颜色',
    afterTransform: (e: any) => e
  }
}
const finalProps = computed(() => {
  // reduce是使用loadsh里面的
  return reduce(
    props.props,
    (result, value, key) => {
      const newKey = key as keyof AllComponentProps;
      const item = mapPropsToForms[newKey];
      if (item) {
       // v-model默认绑定的值,是value,可以自定义
       // v-model双向数据绑定的事件,默认是change事件,也可以自定义
       // initalTransform编辑前的value转换,为了适配类型,进行一定的转换
       // afterTransform 处理上双向数据绑定后的值。
        const {
          valueProp = 'value',
          eventName = 'change',
          initalTransform,
          afterTransform,
        } = item;
        const newItem: FormProps = {
          ...item,
          value: initalTransform ? initalTransform(value) : value,
          valueProp,
          eventName,
          events: {
            [eventName]: (e: any) => {
              context.emit('change', {
                key,
                value: afterTransform ? afterTransform(e) : e,
              });
            },
          },
        };
        result[newKey] = newItem;
      }
      return result;
    },
    {} as { [key: string]: FormProps }
  );
});

我们传递的props值是这样的:
在这里插入图片描述
最终转换成出来的值是这样的
在这里插入图片描述
在这里插入图片描述
当组件内的change事件改变后,组件内部会触发

context.emit('change', { key, value: afterTransform ? afterTransform(e) : e,});

在父组件中接收change事件来改变stroe中的compoents的值

const handleChange = (e) => {
  console.log('event', e);
  store.commit('updateComponent', e)
}

在store中改变components属性

updateComponent(state, { id, key, value, isProps}) {
  const updatedComponent = state.components.find((component) => component.id === (id || state.currentElement)) as any
  if(updatedComponent) {
    updatedComponent.props[key as keyof TextComponentProps] = value;
  }
}

难点:

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1329844.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

2023.12.21 关于 Redis 常用数据结构 和 单线程模型

目录 各数据结构具体编码方式 查看 key 对应 value 的编码方式 Reids 单线程模型 经典面试题 IO 多路复用 Redis 常用数据结构 Redis 中所有的 key 均为 String 类型&#xff0c;而不同的是 value 的数据类型却有很多种以下介绍 5 种 value 常见的数据类型 注意&#xff1…

下一站,上岸@24考研er

时间过的好快&#xff0c; 考研倒计时①天 去年这个时候&#xff0c; 我应该也是充满未知地进入即将来到的考研初试 去年&#xff0c;这个时候&#xff0c;疫情&#x1f637;刚刚放开 许多人都&#x1f411;&#xff0c;发烧&#xff0c;可幸的是我受影响不大 &#x1f3…

itk中的配准整理

文章目录 Perform 2D Translation Registration With Mean Squares效果:源码: 多模态互信息配准 Perform Multi Modality Registration With Viola Wells Mutual Information效果图源码: Register Image to Another Using Landmarks 通过标记点配准图像效果图源码 Perform 2D T…

关键字:import关键字

在 Java 中&#xff0c;import关键字用于导入类或接口&#xff0c;使你可以在代码中使用它们而无需完全限定其名称。以下是使用import关键字的示例代码&#xff1a; 在上述示例中&#xff0c;通过使用import关键字导入了java.util.ArrayList类&#xff0c;这样就可以在代码中直…

代码图形注释自动生成(通过文字图像)

0. 简介 大家在学&#xff08;CTRL&#xff09;习&#xff08;C&#xff09;别人代码的时候&#xff0c;看到别人的代码程序&#xff0c;在日志中有很多很酷的代码注释&#xff0c;或者是有一些图形化注释方便理解。之前本人以为都是一个个手敲出来的。然后在网上一番搜索&…

80x86汇编—寻址方式

文章目录 术语解释8086寻址方式直数寻址寄存器间接寻址寄存器相对寻址基址变址寻址比例变址寻址方式基址比例变址寻址方式 术语解释 EA&#xff1a;有效地址&#xff0c;通过段地址&#xff1a;偏移地址组合得到的Effect Address 位移量&#xff1a;一般是常量和标号&#xff…

HTTP前端请求

目录 HTTP 请求1.请求组成2.请求方式与数据格式get 请求示例post 请求示例json 请求示例multipart 请求示例数据格式小结 3.表单3.1.作用与语法3.2.常见的表单项 4.session 原理5.jwt 原理 HTTP 请求 1.请求组成 请求由三部分组成 请求行请求头请求体 可以用 telnet 程序测…

解决xcode 运行不老iPhone 15 iOS 17.1 设备的问题

问题 最近要查看一下ios 17.1的设备的性能&#xff0c;但是当前版本的Xcode运行不了 解决方法 1、更新Xcode版本到15.1以上 2、更新完成后&#xff0c;大概率出现这个情况 原因&#xff1a;在app Store中更新到Xcode15后,运行不了模拟器和真机.需要下载iOS 17对应的模拟器.&…

力扣每日一题day38[106. 从中序与后序遍历序列构造二叉树]

给定两个整数数组 inorder 和 postorder &#xff0c;其中 inorder 是二叉树的中序遍历&#xff0c; postorder 是同一棵树的后序遍历&#xff0c;请你构造并返回这颗 二叉树 。 示例 1: 输入&#xff1a;inorder [9,3,15,20,7], postorder [9,15,7,20,3] 输出&#xff1a;[…

Java 面试 多线程遇到的问题,如何处理

问题描述 某服务在运行过程中抛出了 RejectedExecutionException。 配置参数&#xff1a; corepoolsize 50, maxpoolsize 50&#xff0c; workqueue 为 SynchronousQueue 现象&#xff1a; 当新的task被拒绝时&#xff0c;pool size未达到配置值50. Caused by: java.util.conc…

dotnet命令创建C#项目,VSCode打开

在命令行中创建项目并运行 1.首先安装.net 下载地址:.NET | 构建。测试。部署。 2.在 cmd 控制台输入 dotnet --vesion 检查版本号是否正常 3.我用git bash环境输入命令创建项目 // 创建文件夹 mkdir MyVSCode // 进入该文件夹 cd MyVSCode/ // 创建控制台项目 dotnet …

java类和对象的思想概述

0.面向对象Object OOP——名人名言&#xff1a;类是写出来的&#xff0c;对象是new出来的 **> 学习面向对象的三条路线 java类以及类成员&#xff1a;&#xff08;重点&#xff09;类成员——属性、方法、构造器、&#xff08;熟悉&#xff09;代码块、内部类面向对象特征&…

【音视频】Mesh、Mcu、SFU三种框架的总结

目录 三种网络场景介绍 【Mesh】 【MCU】(MultiPoint Control Unit) 【SFU】(Selective Forwarding Unit) 三种网络架构的优缺点 Mesh架构 MCU架构(MultiPoint Control Unit) SFU架构(Selective Forwarding Unit) 总结 参考文章 三种网络场景介绍 【Mesh】 Mesh架构…

<软考高项备考>《论文专题 - 24 整合管理(2) 》

3 过程2-制订项目管理计划 3.1 问题 4W1H过程1-制定项目章程做什么定义、准备和协调项目计划的所有组成部分&#xff0c;并把它们整合为一份综合项目管理计划的过程&#xff1b;作用&#xff1a;生成一份综合文件&#xff0c;用于确定所有项目工作的基础及其执行方式为什么做…

大创项目推荐 深度学习+python+opencv实现动物识别 - 图像识别

文章目录 0 前言1 课题背景2 实现效果3 卷积神经网络3.1卷积层3.2 池化层3.3 激活函数&#xff1a;3.4 全连接层3.5 使用tensorflow中keras模块实现卷积神经网络 4 inception_v3网络5 最后 0 前言 &#x1f525; 优质竞赛项目系列&#xff0c;今天要分享的是 &#x1f6a9; *…

Uniapp 开发 BLE

BLE 低功耗蓝牙&#xff08;Bluetooth Low Energy&#xff0c;或称Bluetooth LE、BLE&#xff0c;旧商标Bluetooth Smart&#xff09;&#xff0c;用于医疗保健、运动健身、安防、工业控制、家庭娱乐等领域。在如今的物联网时代下大放异彩&#xff0c;扮演者重要一环&#xff…

互联网账户一证通查询名下账号

核验身份后一键在线查询名下所有关联号码以及注册。 名下电话卡查询&#xff1a;全国移动电话卡“一证通查” 手机号绑定查询&#xff1a;https://tb3.cn/A3lhMk

Wavesurfer.js绘制波形图

HTML使用Wavesurfer.js 要使用wavesurfer.js&#xff0c;首先需要在HTML文件中引入Wavesurfer.js库&#xff0c;然后创建一个音频元素并将其添加到页面中。接下来&#xff0c;初始化Wavesurfer实例并配置相关选项。以下是一个简单的示例&#xff1a; 在HTML文件中引入Wavesurf…

一款外置MOS开关降压型 LED 恒流控制器应用方案

一、基本概述 TX6121 是一款高效率、高精度的降压型大功率 LED 恒流驱动控制器芯片。芯片采用固定关断时间的峰值电流控制方式&#xff0c;关断时间可通过外部电容进行调节&#xff0c;工作频率可根据用户要求而改变。 通过调节外置的电流采样电阻&#xff0c;能控制高亮度 LE…

【Linux笔记】用户和权限管理基本命令介绍

&#x1f34e;个人博客&#xff1a;个人主页 &#x1f3c6;个人专栏&#xff1a;Linux学习 ⛳️ 功不唐捐&#xff0c;玉汝于成 目录 ​编辑 前言&#xff1a; 命令&#xff1a; whoami&#xff1a; passwd&#xff1a; useradd&#xff1a; userdel&#xff1a; chm…