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

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

  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;
  }
}

难点:

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

博客引擎 Hexo 入门介绍+安装笔记

Hexo Hexo is a fast, simple & powerful blog framework. 一直使用的是 jekyll&#xff0c;文章越写越多&#xff0c;不太好管理。是时候换个博客尝试一下。 Prepare blog zh_CN 本机为 MAC。不同系统会略有不同&#xff0c;但是大同小异。 Node.js 必须。 作用&…

LLM之RAG实战(八)| 使用Neo4j和LlamaIndex实现多模态RAG

人工智能和大型语言模型领域正在迅速发展。一年前&#xff0c;没有人使用LLM来提高生产力。时至今日&#xff0c;很难想象我们大多数人或多或少都在使用LLM提供服务&#xff0c;从个人助手到文生图场景。由于大量的研究和兴趣&#xff0c;LLM每天都在变得越来越好、越来越聪明。…

网站使用https认证

随着网络的普及和依赖程度的增加&#xff0c;网站安全性问题也日益凸显。为了确保用户和网站之间的数据传输安全&#xff0c;采用HTTPS认证已经变得至关重要。 1.数据安全是首要任务 在互联网上&#xff0c;信息传输是网站运作的基础。然而&#xff0c;未加密的传输容易受到中…

计算机网络——计算机网络的概述(一)

前言&#xff1a; 面对马上的期末考试&#xff0c;也为了以后找工作&#xff0c;需要掌握更多的知识&#xff0c;而且我们现实生活中也已经离不开计算机&#xff0c;更离不开计算机网络&#xff0c;今天开始我们就对计算机网络的知识进行一个简单的学习与记录。 目录 一、什么…

案例136:基于微信小程序的公交信息在线查询系统

文末获取源码 开发语言&#xff1a;Java 框架&#xff1a;SSM JDK版本&#xff1a;JDK1.8 数据库&#xff1a;mysql 5.7 开发软件&#xff1a;eclipse/myeclipse/idea Maven包&#xff1a;Maven3.5.4 小程序框架&#xff1a;uniapp 小程序开发软件&#xff1a;HBuilder X 小程序…

使用Velero备份、恢复k8s集群上的资源

一、Velero简介 Velero提供备份和恢复 Kubernetes 集群资源和持久卷的工具。 Velero功能&#xff1a; 对群集进行备份&#xff0c;并在丢失时进行还原。将集群资源迁移到其他集群。 Velero 包括&#xff1a; 在群集上运行的服务器在本地运行的命令行客户端 开源地址&…

【终极教程】Cocos2dx服务端重构(优化cocos2dx服务端)

文章目录 概述问题概述1. 代码混淆代码加密具体步骤测试和配置阶段IPA 重签名操作步骤2. 缺乏文档3. 缺乏推荐的最佳实践4. 性能问题 总结 概述 Cocos2dx是一个非常流行的跨平台游戏引擎&#xff0c;开发者可以使用这个引擎来开发iOS、Android和Web游戏。同时&#xff0c;Coco…

SSTI模板注入(Flask+Jinja2)

文章目录 一、前置知识1.1 模板引擎1.2 渲染 二、SSTI模板注入2.1 原理2.2 沙箱逃逸沙箱逃逸payload讲解其他重要payload 2.3 过滤绕过 三、PasecaCTF-2019-Web-Flask SSTI 一、前置知识 1.1 模板引擎 模板引擎&#xff08;这里特指用于Web开发的模板引擎&#xff09;是为了使…

nodejs+vue+ElementUi会员制停车场车位系统

总之&#xff0c;智能停车系统使停车场管理工作规范化&#xff0c;系统化&#xff0c;程序化&#xff0c;避免停车场管理的随意性&#xff0c;提高信息处理的速度和准确性&#xff0c;能够及时、准确、有效的查询和修改停车场情况。 三、任务&#xff1a;小组任务和个人任务 智…

Linux中vim中进行替换/批量替换

Linux中vim中进行替换/批量替换 一:在 Vim 中进行文本替换的操作是通过使用 :s&#xff08;substitute&#xff09;命令来实现的。这里是一些基本的替换命令 替换当前行的第一个匹配项: :s/old/new/这将替换当前行中第一个出现的 “old” 为 “new”。 替换当前行的所有匹配项…

工作实践篇 Flink(一:flink提交jar)

一&#xff1a;参数 flink 模式 – standalone 二&#xff1a;步骤 1. 将本地测试好的代码进行本地运行。确保没问题&#xff0c;进行打包。 2. 找到打好的jar包&#xff0c;将jar包上传到对应的服务器。 3. 执行flink命令&#xff0c;跑代码。 /opt/flink/flink-1.13.6/bi…

ASP.Net实现姓名添加查询(三层架构)

目录 演示功能&#xff1a; 点击启动生成页面 点击搜索模糊查询 点击添加跳转新界面 点击Button添加姓名 步骤&#xff1a; 1、建文件 2、添加引用关系 3、根据数据库中的列写Models下的XueshengModels类 4、DAL下的DBHelper&#xff08;对数据库进行操作&#xff09;…

轻量Http客户端工具VSCode和IDEA

文章目录 前言Visual Studio Code 的插件 REST Client编写第一个案例进阶&#xff0c;设置变量进阶&#xff0c;设置Token 前言 作为一个WEB工程师&#xff0c;在日常的使用过程中&#xff0c;HTTP请求是必不可少的。我们采用的HTTP工具有如下&#xff1a; Postman Insomnia Ap…

MyBatis见解3

8.MyBatis的关联查询 8.3.一对多查询 需求&#xff1a;查询所有用户信息及用户关联的账户信息。 分析&#xff1a;用户信息和他的账户信息为一对多关系&#xff0c;并且查询过程中如果用户没有账户信息&#xff0c;此时也要将用户信息查询出来&#xff0c;此时左外连接查询比…

Spring和Spring Boot框架中怎么理解Bean这个核心概念

在Spring和Spring Boot框架中&#xff0c;Bean是一个核心概念。要理解Spring Boot中的Bean&#xff0c;我们可以从以下几个方面进行&#xff1a; 定义&#xff1a; Bean是Spring框架中的一个对象&#xff0c;由Spring容器管理。当我们在应用程序中需要某个对象时&#xff0c;我…

Deepin更换仿Mac主题

上一篇博客说了要写一篇deepin系统的美化教程 先看效果图&#xff1a; 准备工作&#xff1a; 1.你自己 嘻嘻嘻 2.能上网的deepin15.11电脑 首先去下载主题 本次需要系统美化3部分&#xff1a;1.图标 2.光标 3.壁纸 开始之前&#xff0c;请先把你的窗口特效打开&#xff0c;…

华为设备命令行操作基础

熟悉VRP命令行并且熟练掌握VRP配置是高效管理华为网络设备的必备基础。 设备初始化启动 管理员和工程师如果要访问在通用路由平台VRP上运行的华为产品&#xff0c;首先要进入启动程序。开机界面信息提供了系统启动的运行程序和正在运行的VRP版本及其加载路径。启动完成以后&am…

spring aop实际开发中怎么用,Spring Boot整合AOP,spring boot加spring mvc一起使用aop,项目中使用aop

前言&#xff1a;本文不介绍 AOP 的基本概念、动态代理方式实现 AOP&#xff0c;以及 Spring 框架去实现 AOP。本文重点介绍 Spring Boot 项目中如何使用 AOP&#xff0c;也就是实际项目开发中如何使用 AOP 去实现相关功能。 如果有需要了解 AOP 的概念、动态代理实现 AOP 的&…

【PHP手麻系统源码】基于mysql+laravel+vue开发的医院手术麻醉系统源码,实现围术期患者数据的自动采集与共享

手麻系统作为医院信息化系统的一环&#xff0c;由监护设备数据采集系统和麻醉信息管理系统两个子部分组成。手麻信息系统覆盖了患者术前、术中、术后的手术过程&#xff0c;可以实现麻醉信息的电子化和手术麻醉全过程动态跟踪。 以服务围术期临床业务工作的开展为核心&#xf…

分布式搜索elasticsearch概念

什么是elasticsearch&#xff1f; elasticsearch是一款非常强大的开源搜索引擎&#xff0c;可以帮助我们从海量数据中快速找到需要的内容 目录 elasticsearch的场景 elasticsearch的发展 Lucene篇 Elasticsearch篇 elasticsearch的安装 elasticsearch的场景 elasticsear…