低代码可视化拖拽编辑器实现方案

news2024/11/25 16:24:19

一、前言

随着业务不断发展,低代码、无代码平台越来越常见,它降低开发门槛、快速响应业务需求、提升开发效率。零开发经验的业务人员通过可视化拖拽等方式,即可快速搭建各种应用。本文主要是讲解低代码可视化拖拽平台前端展示层面的实现逻辑和方案,对于后端逻辑、数据库设计、以及自动化部署等暂时没有涉及。

二、展示区划分

首先我们需要先清晰我们要实现的UI展示效果,分为三部分(组件选项区、可视化展示区、元素配置编辑区)低代码

1、组件选项区

1.1 数据格式定义

为了展示出各种元素,先定义元素的类型(文字、图片、按钮、banner、表单等等),具体数据格式如下,详情可以查看源码路径(src/config/template.ts、src/config/base.ts),这些组件的每一项也可以存储在库,通过接口查询回来,只是这里没有实现。

  • template.ts: 定义所有类型自定义组件的配置
export const config: any =  {
   text: [
     {
       config: {
         name: 'content-box',
         noDrag: 1,
         slot: [
           {
             name: 'content-input',
             style: {
               backgroundImage: require('@/assets/title1-left-icon.png'),
               backgroundRepeat: 'no-repeat',
               backgroundSize: 'contain',
               borderWidth: 0,
               fontSize: '14px',
               height: '13px',
               lineHeight: '32px',
               width: '18px'
             },
             value: ''
           },
           {
             name: 'content-input',
             style: {
               height: '32px',
               paddingLeft: '5px',
               paddingRight: '5px'
             },
             value: "<div style=\"line-height: 2;\"><span style=\"font-size: 16px; color: #fce7b6;\"><strong>活动规则</strong></span></div>"
           },
           {
             name: 'content-input',
             style: {
               backgroundImage: require('@/assets/title1-right-icon.png'),
               backgroundRepeat: 'no-repeat',
               backgroundSize: 'contain',
               borderWidth: 0,
               fontSize: '14px',
               height: '13px',
               lineHeight: '32px',
               marginRight: '5px',
               width: '18px'
             },
             value: ''
           }
         ],
         style: {
           alignItems: 'center',
           backgroundColor: 'rgba(26, 96, 175, 1)',
           display: 'flex',
           height: '40px',
           justifyContent: 'center',
           paddingLeft: '1px'
         },
         value: ''
       },
       name: '带点的标题',
       preview: require('@/assets/title1.jpg')
     }
   ],
   img: [
     {
       config: {
         value: require('@/assets/gift.png'),
         name: 'content-asset',
         style: {
           width: '100px',
           height: '100px',
           display: 'inline-block'
         }
       },
       preview: require('@/assets/gift.png'),
       name: '礼包'
     }
   ],
   btn: [
     ....
   ],
   form: [
     ...
   ]
 }
  • base.ts: 中定义基本组件的配置
export const config: any = {
  text: {
     value: '<div style="text-align: center; line-height: 1;"><span style="font-size: 14px; color: #333333;">这是一行文字</span></div>',
     style: {},
     name: 'content-input'
   },
   multipleText: {
     value: '<div style="text-align: center; line-height: 1.5;"><span style="font-size: 14px; color: #333333;">这是多行文字<br />这是多行文字<br />这是多行文字<br /></span></div>',
     name: 'content-input',
     style: {}
   },
   img: {
     value: require('@/assets/logo.png'),
     name: 'content-asset',
     style: {
       width: '100px',
       height: '100px',
       display: 'inline-block'
     }
   },
   box: {
     name: 'content-box',
     noDrag: 0,
     style: {
       width: '100%',
       minHeight: '100px',
       height: 'auto',
       display: 'inline-block',
       boxSizing: 'border-box'
     },
     slot: []
   }
 }

基本元素(文字content-input、图片content-asset)主要包含以下属性: name(组件名称)、style(行内样式)、value(内容值)

盒子元素(content-box)主要包含以下属性: name(组件名称)、style(行内样式)、noDrag(是否可拖拽)、slot(插槽内容)

1.2 实现可拖拽

为了实现可拖拽效果,这里使用了sortable.js拖拽库来实现。更多使用细节可查看官方文档

关键实现代码如下:

// 左侧选项区DOM结构
<el-tabs tab-position="left" class="tabs-list" v-model="activeType">
  <el-tab-pane v-for="item in props.tabConfig" :key="item.value" :label="item.label" :name="item.value">
    <template #label>
      <span class="tabs-list-item">
        <i :class="`iconfont ${item.icon}`"></i>
        <span>{{item.label}}</span>
      </span>
    </template>
    <div class="tab-content">
      <div class="tab-content-title">{{item.label}}</div>
      <div class="main-box" ref="mainBox">
        <div class="config-item base" v-if="activeType === 'base'" data-name="text" @click="addToSubPage(Base.config['text'])">
          <el-icon :size="20"><Document /></el-icon>
          <div>文本</div>
        </div>
        <div class="config-item base" v-if="activeType === 'base'"  data-name="box" @click="addToSubPage(Base.config['box'])">
          <el-icon :size="20"><Box /></el-icon>
          <div>盒子</div>
        </div>
        <div class="config-item" v-for="_item in item.children" :key="_item" :data-name="_item" @click="addToSubPage(Base.config[_item])">
          <div v-if="activeType === 'text'" class="config-item-text" v-html="Base.config[_item].value"></div>
          <img v-if="activeType === 'img'" class="config-item-img" :src="Base.config[_item].value"/>
        </div>
        <div class="config-item" v-for="(tItem, tIndex) in Template.config[activeType]" :key="tItem.id" :data-type="activeType" :data-index="tIndex" @click="addToSubPage(tItem.config)">
          <img :src="tItem.preview" class="preview">
        </div>
      </div>
    </div>
  </el-tab-pane>
</el-tabs>
const mainBox = ref()
const initSortableSide = (): void => {
  // 获取mainBox下每一个元素,遍历并注册拖拽组
  Array.from(mainBox.value).forEach(($box, index) => {
    instance[`_sortable_${index}`] && instance[`_sortable_${index}`].destroy()
    instance[`_sortable_${index}`] = Sortable.create($box, {
      filter: '.ignore', // 需要过滤或忽略指定元素
      sort: false, // 不允许组内排序
      group: {
        name: 'shared', // 自定义组名
        pull: 'clone', // 从当前组克隆拖出
        put: false, // 不允许拖入
      },
      // 开始拖拽回调函数
      onStart: () => {
        // 给subpage展示区添加选中框样式
       (document.querySelector('.subpage') as HTMLElement).classList.add('active')
      },
      // 结束拖拽回调函数
      onEnd: ({ item, originalEvent }: any) => {
        ...
      }
    })
  })
}

这里主要讲一下onEnd里面的逻辑,当拖拽组件并将其移动到中间的可视化展示区的时候,需要做以下2个关键操作。

  1. 判断是否拖拽到可视化展示区内
  2. 获取当前拖拽元素的配置,并更新pinia中store的值。(pinia是vue新一代状态管理插件,可以认为是vuex5.)
onEnd: ({ item, originalEvent }: any) => {
      // 获取鼠标放开后的X、Y坐标
    const { pageX, pageY } = originalEvent
    // 获取可视化展示区的上下左右坐标
    const { left, right, top, bottom } = (document.querySelector('.subpage') as HTMLElement).getBoundingClientRect()
    const { dataset } = item
    // 为了移除被clone到可视化区的dom结构,通过配置来渲染可视化区的内容
    if ((document.querySelector('.subpage') as HTMLElement).contains(item)) {
      item.remove()
    }
      // 编辑判断
    if (pageX > left && pageX  < right && pageY > top && pageY < bottom) {
      // 获取自定义属性中的name、type 、index
      const { name, type, index } = dataset
      let currConfigItem = {} as any
      // 若存在type 说明不是基础类型,在template.ts找到对应的配置。
      if (type) {
        currConfigItem = utils.cloneDeep(Template.config[type][index].config)
        // 使用nanoid 生成唯一id
        currConfigItem.id = utils.nanoid()
        // 递归遍历组件内部的slot,为每个元素添加唯一id
        currConfigItem.slot = configItemAddId(currConfigItem.slot)
      } else {
        // 基础类型操作
        currConfigItem = utils.cloneDeep(Base.config[name])
        currConfigItem.id = utils.nanoid()
      }
      // 修改pinia的store数据
      templateStore.config.push(currConfigItem)
      // 触发更新(通过watch实现)
      key.value = Date.now()
    } else {
      console.log('false')
    }
      // 移除中间可视化区选中样式
    (document.querySelector('.subpage') as HTMLElement).classList.remove('active')
  }

2、可视化展示区

中间的可视化展示区的功能主要是提供用户对具体元素选中以及拖拽操作。因此主要实现元素展示 、选中框以及可拖拽功能。

2.1 元素展示

元素展示比较简单,只需要通过遍历pinia的store中的页面配置config,并用动态组件component标签展示即可

<component v-for="item in template.config" :key="item.id" :is="item.name" :config="item" :id="item.id">
</component>

2.2 实现选中框

实现选中框的逻辑相对复杂一点,其中关键的两个事件是hover(鼠标悬浮在元素上)和select(鼠标点击元素)。

定义一个响应式对象来存储它们的变化情况:

const catcher: any = reactive(
  {
    hover: {
      id: '', // 元素id
      rect: {}, // 元素坐标
      eleName: '' // 元素类名
    },
    select: {
      id: '',
      rect: {},
      eleName: ''
    }
  }
)

定义事件监听器(mouseover、click)

import { onMounted, ref } from 'vue'
const subpage = ref()

const listeners = {
  mouseover: (e: any) => {
    // findUpwardElement方法为向上查找最近的目标元素
    const $el = utils.findUpwardElement(e.target, editorElements, 'classList')
    if ($el) {
      catcher.hover.id = $el.id
      // 重置catcher响应式对象
      resetRect($el, 'hover')
    } else {
      catcher.hover.rect.width = 0
      catcher.hover.id = ''
    }
  },
  click: (e: any) => {
    const $el = utils.findUpwardElement(e.target, editorElements, 'classList')
    if ($el) {
      template.activeElemId = $el.id
      catcher.select.id = $el.id
      resetRect($el, 'select')
    } else if (!utils.findUpwardElement(e.target, ['mouse-catcher'], 'classList')) {
      removeSelect()
    }
  }
} as any

onMounted(() => {
  Object.keys(listeners).forEach(event => {
    subpage.value.addEventListener(event, listeners[event], true)
  })
})

定义修改catcher响应式对象方法

interface rectInter {
  width: number;
  height: number;
  top: number;
  left: number;
}

// 修改catcher对象方法
const resetRect = ($el: HTMLElement, type: string): void => {
  if ($el) {
    const parentRect = utils.pick(subpage.value.getBoundingClientRect(), 'left', 'top')
    const rect: rectInter = utils.pick($el.getBoundingClientRect(), 'width', 'height', 'left', 'top')
    rect.left -= parentRect.left
    rect.top -= parentRect.top
    catcher[type].rect = rect
    catcher[type].eleName = $el.className
  }
}

const removeSelect = (): void => {
  catcher.select.rect.width = 0
  catcher.select.id = ''
  catcher.hover.rect.width = 0
  catcher.hover.id = ''
  template.activeElemId = ''
}

// 重置select配置
const resetSelectRect = (id: string): void => {
  if (id) {
    resetRect(document.getElementById(id) as HTMLElement, 'select')
  } else {
    removeSelect()
  }
}

选中框组件

选中框组件包括选中框主体(通过不同颜色区分盒子还是元素)、功能栏(上下移动、删除、复制)。

// 将catcher对象传入组件
<MouseCatcher class="ignore" v-model="catcher"></MouseCatcher>

比较关键的点是在操作功能栏的时候对全局配置的修改,详细逻辑可以查看源码(src/components/mouse-catcher/index.vue)

2.3 实现可视区拖拽

接下来是实现可视化展示区的可拖拽,这个区域与选项区不同,它允许内部元素的排序以及拖到别的拖拽组(盒子)。

关键逻辑如下:(主要分析onEnd回调中的逻辑)

const initSortableSubpage = (): void => {
  instance._sortableSubpage && instance._sortableSubpage.destroy()
  instance._sortableSubpage = Sortable.create(document.querySelector('.subpage'), {
    group: 'shared',
    filter: '.ignore',
    onStart: ({ item }: any) => {
      console.log(item.id)
    },
    onEnd: (obj: any) => {
      let { newIndex, oldIndex, originalEvent, item, to } = obj
      // 在可视区盒子内拖拽
      if (to.classList.contains('subpage')) {
        const { pageX } = originalEvent
        const { left, right } = (document.querySelector('.subpage') as HTMLElement).getBoundingClientRect()
        // 判断是否移出可视区
        if (pageX < left || pageX > right) {
          // 移出可视区,则移除元素
          templateStore.config.splice(oldIndex, 1)
        } else {
          // 判断移动位置发生更改
          if (newIndex !== oldIndex) {
            // 新的位置在最后一位,需要减1
            if (newIndex === templateStore.config.length) {
              newIndex = newIndex - 1
            }
             // 旧的位置在最后一位,需要减1
            if (oldIndex === templateStore.config.length) {
              oldIndex = oldIndex - 1
            }
            // 数据互换位置
            const oldVal = utils.cloneDeep(templateStore.config[oldIndex])
            const newVal = utils.cloneDeep(templateStore.config[newIndex])
            utils.fill(templateStore.config, oldVal, newIndex, newIndex + 1)
            utils.fill(templateStore.config, newVal, oldIndex, oldIndex + 1)
          }
        }
      } else { // 若将元素移动至其他拖拽组(盒子)
        const itemIndex = templateStore.config.findIndex((x: any) => x.id === item.id)
        const currContentBox = utils.findConfig(templateStore.config, to.id)
        const currItem = templateStore.config.splice(itemIndex, 1)[0]
        currContentBox.slot.push(currItem)
      }
    }
  })
}

2.4 实现盒子内拖拽

这里需要注意需要筛选可视区盒子subpage中类名为content-box,并且不包含类名为no-drag的。

其关键逻辑也是在onEnd回调函数里,需要区分元素在当前盒子内部移动、元素移动到其他盒子、元素移动到可视区(subpage)盒子三种情况。

const initSortableContentBox = () => {
  console.log(Array.from(document.querySelectorAll('.subpage .content-box')).filter((x: any) => !x.classList.contains('no-drag')))
  Array.from(document.querySelectorAll('.subpage .content-box')).filter((x: any) => !x.classList.contains('no-drag')).forEach(($content, contentIndex) => {
    instance[`_sortableContentBox_${contentIndex}`] && instance[`_sortableContentBox_${contentIndex}`].destroy()
    instance[`_sortableContentBox_${contentIndex}`] = Sortable.create($content, {
      group: 'shared',
      onStart: ({ from }: any) => {
        console.log(from.id)
      },
      onEnd: (obj: any) => {
        let { newIndex, oldIndex, item, to, from } = obj
        if (to.classList.contains('subpage')) { // 元素移动至可视区盒子
          const currContentBox = utils.findConfig(templateStore.config, from.id)
          const currItemIndex = currContentBox.slot.findIndex((x: any) => x.id === item.id)
          const currItem = currContentBox.slot.splice(currItemIndex, 1)[0]
          templateStore.config.push(currItem)
        } else {
          if (from.id === to.id) {
             // 同一盒子中移动
            const currContentBox = utils.findConfig(templateStore.config, from.id)
            if (newIndex !== oldIndex) {
              if (newIndex === currContentBox.length) {
                newIndex = newIndex - 1
              }
              if (oldIndex === currContentBox.length) {
                oldIndex = oldIndex - 1
              }
              const oldVal = utils.cloneDeep(currContentBox.slot[oldIndex])
              const newVal = utils.cloneDeep(currContentBox.slot[newIndex])
              utils.fill(currContentBox.slot, oldVal, newIndex, newIndex + 1)
              utils.fill(currContentBox.slot, newVal, oldIndex, oldIndex + 1)
            }
          } else {
            // 从一个盒子移动到另一个盒子
            const currContentBox = utils.findConfig(templateStore.config, from.id)
            const currItemIndex = currContentBox.slot.findIndex((x: any) => x.id === item.id)
            const currItem = currContentBox.slot.splice(currItemIndex, 1)[0]
            const toContentBox = utils.findConfig(templateStore.config, to.id)
            toContentBox.slot.push(currItem)
          }
        }
      }
    })
  })
}

、元素配置编辑区

该区域是用于编辑修改元素的行内样式,目前简单实现了字体、位置布局、背景、边框、阴影配置。

3.1 字体编辑

字体编辑功能使用富文本编辑器tinymce,这里使用vue3-tinymce,它是基于 vue@3.x + tinymce@5.8.x 封装的富文本编辑器。

更多配置可参考官方文档, 下面的对vue3-tinymce进行封装。

<template>
  <vue3-tinymce v-model="state.content" :setting="state.setting" />
</template>

<script lang="ts" setup>
import { reactive, watch } from 'vue';
// 引入组件
import Vue3Tinymce from '@jsdawn/vue3-tinymce'
import { useTemplateStore } from '@/stores/template'
import { findConfig } from '@/utils'

const template = useTemplateStore()
const props = defineProps({
  modelValue: {
    type: String,
    default: ''
  }
})

const state = reactive({
  content: '',
  setting: {
    height: 300,
    language: 'zh-Hans',
    language_url: '/tinymce/langs/zh-Hans.js'
  }
})

watch(() => props.modelValue, () => {
  props.modelValue && (state.content = findConfig(template.config, props.modelValue)?.value)
})

watch(() => state.content, () => {
  const config = findConfig(template.config, props.modelValue)
  config && (config.value = state.content)
})
</script>

3.2 位置布局

可修改元素的内外边距、宽高、布局类型(display)、定位类型(position)。

3.3 背景

可修改元素背景颜色、圆角、渐变方式。

3.4 边框

可修改边框类型,包括无边框、实线、虚线、点线

3.5 阴影

可修改阴影颜色、以及阴影的X、Y、距离、大小。

推荐一款前段时间用的不错的低代码,JNPF快速开发平台,采用SpringBoot微服务架构、支持SpringCloud模式,完善了平台的扩增基础,满足系统快速开发、灵活拓展、无缝集成和高性能应用等综合能力;采用前后端分离模式,前端和后端的开发人员可分工合作负责不同板块,省事便捷。你可以试试看!

基础组件

文字组件

<script lang="ts">
export default {
  name: "ContentInput"
};
</script>

<script setup lang='ts'>
import { PropType } from 'vue';
import { useStyleFix } from '@/utils/hooks'

const props = defineProps({
  config: {
    type: Object as PropType<any>
  }
})
</script>

<template>
  <div 
    class="content-input"
    v-html="props.config.value"
    :style="[props.config.style, useStyleFix(props.config.style)]"
  >
  </div>
</template>

<style lang='scss' scoped>
.content-input {
  word-break: break-all;
  user-select: none;
}
</style>

图片组件

<script lang="ts">
export default {
  name: "ContentAsset"
};
</script>

<script setup lang='ts'>
import { PropType } from 'vue'

const props = defineProps({
  config: {
    type: Object as PropType<any>
  }
})
</script>
<template>
  <div class="content-asset" :style="props.config.style">
    <img :src="props.config.value">
  </div>
</template>

<style lang='scss' scoped>
img {
  width: 100%;
  height: 100%;
}
</style>

盒子组件

<script lang="ts">
export default {
  name: "ContentBox"
}
</script>

<script setup lang='ts'>
import { PropType } from 'vue'
const props = defineProps({
    config: {
    type: Object as PropType<any>
  }
})
</script>
<template>
  <div :class="['content-box', { 'no-drag': props.config.noDrag }]" :style="props.config.style">
    <component v-for="item in props.config.slot" :key="item.id" :is="item.name" :config="item" :id="item.id"></component>
  </div>
</template>

<style lang='scss' scoped>
</style>

到这里基本的实现流程都完毕,目前的版本还比较简单,还有很多可以实现的功能,比如撤回、重做、自定义组件选项、接入数据库等。

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

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

相关文章

@Async使用什么线程池?

文章目录 前言一、前言1、ThreadPoolTaskExecutor2、SimpleAsyncTaskExecutor3、测试代码 二、各种情况模拟1、未配置线程池2、配置异步线程池3、配置1个或多个非异步线程池4、同时配置异步和非异步线程池 三、总结 前言 本文的目的&#xff0c;主要是看到网上各种说辞&#x…

JVM-类加载与运行区详细分析(一)

目录 一、为什么会有类加载机制 二、类加载机制原理是什么 1、什么是类加载器&#xff1a;宏观 2、类加载器工作原理 1、装载 2、链接 3、初始化 3、何为装载的机制&#xff1a;微观 4、上面既然我们已经知道了啥是双亲委派了&#xff0c;那么怎么去破坏呢&#xff1f;…

【设计模式】工厂模式(简单工厂模式、工厂方法模式、抽象工厂模式)详记

注&#xff1a;本文仅供学习参考&#xff0c;如有错漏还请指正&#xff01; 参考文献/文章地址&#xff1a; https://zh.wikipedia.org/wiki/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F%EF%BC%9A%E5%8F%AF%E5%A4%8D%E7%94%A8%E9%9D%A2%E5%90%91%E5%AF%B9%E8%B1%A1%E8%BD%AF%E4%BB%B…

第20章:MySQL索引失效案例

1.全值匹配我最爱 当SQL查询 EXPLAIN SELECT SQL_NO_CACHE * FROM student WHERE age30 AND classId4 AND NAMEabcd; 创建3个索引 idx_age,idx_age_classid,idx_age_classid_name 当前优化器会选择跟where条件匹配最高的idx_age_classid_name索引&#xff0c;直接查询出对…

[CVPR 2023] Imagic:使用扩散模型进行基于文本的真实图像编辑

[CVPR 2023] Imagic:使用扩散模型进行基于文本的真实图像编辑 Paper Title: Imagic: Text-Based Real Image Editing with Diffusion Models The first author performed this work as an intern at Google Research. Project page: https://imagic-editing.github.io/. 原文…

登出成功后token过期方案

目录 需求分析解决方案实现步骤登出成功相关逻辑改造携带token请求相关逻辑需求分析 登录成功后,系统会返回一个token给客户端使用,token可以用来获取登录后的一些资源或者进行一些操作。当用户在系统中注销或者退出登录时,需要对token进行过期处理,以保证系统的安全性和数…

校园网WiFi IPv6免流上网

ipv6的介绍 IPv6是国际协议的最新版本&#xff0c;用它来取代IPv4主要是为了解决IPv4网络地址枯竭的问题&#xff0c;也在其他很多方面对IPv4有所改进&#xff0c;比如网络的速度和安全性。 IPv4是一个32位的地址&#xff0c;随着用户的增加在2011年国家报道说IPv4的网络地址即…

基于前后端交互的论坛系统(课设高分必过)

目录 前言概述 一.前期准备 项目演示 用户注册 用户登录 主页面 发帖页面 个人信息及修改 用户主帖 站内信 需求分析 技术选型 二.建表分析 三.环境搭建 技术环境的检查与安装 检查JDK ​编辑 检查数据库 检查Maven 检查git ​编辑 项目启动的通用配置 新…

【系统架构】第五章-软件工程基础知识(需求工程和系统分析与设计)

软考-系统架构设计师知识点提炼-系统架构设计师教程&#xff08;第2版&#xff09; 需求工程 软件需求3个层次&#xff1a; 业务需求&#xff1a;反映了组织机构或客户对系统、产品高层次的目标要求用户需求&#xff1a;描述了用户使用产品必须要完成的任务&#xff0c;是用户…

rust abc(3): 布尔和字符类型的使用并与C/C++对比

文章目录 1. 目的2. 布尔类型2.1 只能赋值为小写的 true, false2.2 不能把数字赋值给bool类型变量2.3 正确写法汇总 3. 字符类型3.1 UTF-8 编码3.2 字符的意思是单个字符&#xff0c;多个字符不能用单引号 4. 总结 1. 目的 继续熟悉 rust 语言的基本数据类型, 感受 rust 编译期…

Redis【实战篇】---- 短信登录

Redis【实战篇】---- 短信登录 1. 导入黑马点评项目1. 导入SQL2. 有关当前模型3. 导入后端项目4. 导入前端项目5. 运行前端项目 2. 基于Session实现登录流程3. 实现发送短信验证码功能4. 实现登录拦截功能5. 隐藏用户敏感信息6. session共享问题7. Redis代替session业务1. 设计…

Spark10-11

10. 广播变量 10.1 广播变量的使用场景 在很多计算场景&#xff0c;经常会遇到两个RDD进行JOIN&#xff0c;如果一个RDD对应的数据比较大&#xff0c;一个RDD对应的数据比较小&#xff0c;如果使用JOIN&#xff0c;那么会shuffle&#xff0c;导致效率变低。广播变量就是将相对…

【C/C++】使用类和对象 练习EasyX图形库

创作不易&#xff0c;本篇文章如果帮助到了你&#xff0c;还请点赞 关注支持一下♡>&#x16966;<)!! 主页专栏有更多知识&#xff0c;如有疑问欢迎大家指正讨论&#xff0c;共同进步&#xff01; &#x1f525;c系列专栏&#xff1a;C/C零基础到精通 &#x1f525; 给大…

【关联式容器】之map和set

【关联式容器】之map和set 容器类型树形结构的关联式容器mapset&#xff0c;multiset&#xff0c;multimap的区别与联系 容器类型 在STL中&#xff0c;我们接触过许多容器&#xff0c;例如&#xff1a;vector&#xff0c;list&#xff0c;stack&#xff0c;queue&#xff0c;m…

conll2003数据集下载与预处理

CoNLL-2003 数据集包括 1,393 篇英文新闻文章和 909 篇德文新闻文章。我们将查看英文数据。 1. 下载CoNLL-2003数据集 https://data.deepai.org/conll2003.zip 下载后解压你会发现有如下文件。 打开train.txt文件&#xff0c; 你会发现如下内容。 CoNLL-2003 数据文件包含由单…

逍遥自在学C语言 | 指针陷阱-空指针与野指针

前言 在C语言中&#xff0c;指针是一种非常强大和灵活的工具&#xff0c;但同时也容易引发一些问题&#xff0c;其中包括空指针和野指针。 本文将带你了解这两个概念的含义、产生原因以及如何避免它们所导致的问题。 一、人物简介 第一位闪亮登场&#xff0c;有请今后会一直…

【玩转Docker小鲸鱼叭】理解DockerFile如此简单

DockerFile构建过程 DockerFile 是Docker的一个配置文件&#xff0c;本质上来说它只是一个文本文件&#xff0c;它是用来构建Docker镜像的。DockerFile配置文件中包含了一系列的指令和配置信息&#xff0c;用于描述如何构建镜像以及如何运行容器。通过编写 Dockerfile&#xf…

RISC-V处理器的设计与实现(二)—— CPU框架设计

前面我们选好了要实现的指令集&#xff0c;并且了解了每个指令的功能&#xff08;传送门&#xff1a;RISC-V处理器的设计与实现&#xff08;一&#xff09;—— 基本指令集_Patarw_Li的博客-CSDN博客&#xff09;&#xff0c;接下来我们就可以开始设计cpu了。当然我们不可能一上…

ChatGPT更新的使用指南,与其他类似的人工智能的软件和服务-更新版(2023-6-25)

文章目录 一、什么是ChatGPT二、如何使用三、如何使用ChatGPT帮助我们的工作和生活四、高阶用法1、角色扮演2、英语口语老师3、在搜索引擎中集成ChatGPT 五、常见问题五、其他类似的软件和服务 如果你还不知道如何注册和使用&#xff0c;可看末尾&#xff0c;手把手教你。 一、…

Linux线程同步

同步的几种方式&#xff1a;信号量&#xff0c;互斥锁&#xff0c;条件变量&#xff0c;读写锁 同步&#xff1a;对程序的执行过程进行控制&#xff0c;保证对临界资源的访问同一时刻只能有一个进程或线程访问。 2.1信号量 存在P操作&#xff1a;获取资源&#xff0c;信号量…