前端通过draggable结合fabricjs实现拖拽至画布生成元素自定义编排功能

前端通过draggable结合fabricjs实现拖拽自定义编排功能

太久没有更新了,主要最近行情不太好失业了一段时间,一度到怀疑人生,然后就是做的东西大多没有什么含金量,没什么好分享的就很尴尬。
刚好最近遇到一个奇葩的需求,一个基地管理的需求,由于项目中的基地很偏,地图上都定位不到,只能通过一个图片作为底图,然后在上面绘制一些图层,需要做一个自定义编排的需求,先上图:
在这里插入图片描述

上面是实现的demo,首先上html结构代码(技术栈:V3+TS+elementplus)

<div class="massif-box"><!-- 左侧栏 --><div class="asidebox"><div class="topbar"><divclass="tabitem" @click="changetype(item)"v-for="item in typelist" :key="item.code" :class="{cur:item.code == curtype}">{{ item.name }}</div></div><div class="barcontent"><div class="searchbar"><el-inputv-model="searchvalue"style="width:calc(100% - 20px)"placeholder="请输入关键字":suffix-icon="Search"/></div><div class="block-content"><div class="block-item" v-for="(item) in curlist" :key="item.id" :class="{cur:item.id == cur!.id}" @click.capture="selectitem(item)"><div class="imgbox"></div><div class="text">{{ item.name }}</div><el-tag type="success" class="tag">陆基</el-tag><template v-if="item.id == cur.id"><el-icon class="icon" @click.stop="editattr"><EditPen /></el-icon><el-icon class="icon" @click.stop="removevnode"><Delete /></el-icon></template></div></div><button @click="tojson">画布转json</button><button @click="tocanvas">json回显画布</button></div></div><!-- 右边内容区域 --><div class="basecontent" ref="basecontent" @drop="drop" @dragover="dragOver"><!-- 画布容器 --><canvas id="canvas"></canvas><!-- 可拖拽元素 --><div class="toolone" @dragstart.capture="onStart"><el-tooltipclass="box-item"effect="dark"content="地块"placement="right"><div class="item-one"><img :src="massifimg" alt="" :draggable="true"/></div></el-tooltip><el-tooltipclass="box-item"effect="dark"content="塘口"placement="right"><div class="item-two"><img :src="pondimg" alt="" :draggable="true"/></div></el-tooltip><el-tooltipplacement="right-start"class="custom-tooltip"effect="light"><template #content><div class="tip-box" @dragstart.stop="onStart"><div class="device-one tip-item"><img :src="video" alt="" :draggable="true"/><div>xxx</div></div><div class="device-two tip-item"><img :src="onedevice" alt="" :draggable="true"/><div>yyyy</div></div><div class="device-three tip-item"><img :src="video" alt="" :draggable="true"/><div>mmmm</div></div></div></template><div class="item-three"><img :src="deviceimg" alt=""/></div></el-tooltip></div><!-- 右下角工具元素 --><div class="tooltwo"><div class="top"><img :src="layerimg" alt="" @click="openlyer"/></div><div class="center"><img :src="daohangimg" alt="" /><img :src="screenimg" alt="" /><img :src="reductionimg" alt=""/></div><div class="bottom"><img :src="addimg" alt="" @click="zoomIn" /><img :src="minusimg" alt="" @click="zoomOut"/></div></div><ponddialog :pondparams="circleparams" ref="pondDialog" @get-value="updatecanvas"/><massifdialog :massifparams="rectparams" ref="massifDialog" @get-value="updatecanvas"/><devicedialog :deviceparams="deviceparams" ref="deviceDialog" @get-value="updatecanvas"/><layerdialog :layerlist="layerlist" ref="layerDialog" @get-visible="updatevisible"/> </div></div>

结构分为左侧菜单栏和右侧画布区域,通过左上角的图标拖拽到画布上生成图形,选中图形弹出属性设置框,可以调制样式或更新数据。整个画布也可以转成json存储,通过json也可以回显画布。

  import { EditPen, Plus, Delete, Search } from '@element-plus/icons-vue';EditPen Plus  Delete Search//弹框组件import ponddialog from './ponddialog.vue';import massifdialog from './massifdialog.vue';import devicedialog from './devicedialog.vue';import layerdialog from './layerdialog.vue';//图片import massifimg from'@/assets/imgs/massif/massif.png';import pondimg from'@/assets/imgs/massif/pond.png';import deviceimg from'@/assets/imgs/massif/device.png';import addimg from'@/assets/imgs/massif/add.png';import minusimg from'@/assets/imgs/massif/minus.png';import screenimg from'@/assets/imgs/massif/screen.png';import reductionimg from'@/assets/imgs/massif/reduction.png';import daohangimg from'@/assets/imgs/massif/daohang.png';import layerimg from'@/assets/imgs/massif/layer.png';import video from'@/assets/imgs/massif/video.png';import onedevice from'@/assets/imgs/massif/onedevice.png';import basemap from'@/assets/imgs/massif/basemap2.png';//画布插件import * as fabric from 'fabric';//生成唯一id方法import { generateUUID } from '@/utils'//参数类型声明import {Rectparams,Circleparams,DeviceParams,p, Curparams} from './types'// 画布区域的父级元素  const basecontent = ref<HTMLElement>();//canvas实例let canvas: fabric.Canvas;//搜索值let searchvalue = ref<string>('');//以下是左侧列表的相关数据//图层的类型const typelist = ref<{name:string,code:string}[]>([{name:'地块',code:'Rect'},{name:'塘口',code:'Circle'},{name:'设备',code:'Device'}]);//当前图层类型const curtype = ref<string>('Rect');//所有图层数组let vnodelist = ref<Curparams[]>([])//选择类型const changetype = (item:{name:string,code:string})=>{curtype.value = item.code;}//根据类型过滤出当前列表let curlist = computed(()=>{let list =  vnodelist.value.filter(item => (item.types == curtype.value && item.name.startsWith(searchvalue.value)));return list})//生命周期初始化画布onMounted(() => {  initFabricCanvas(drawbasemap);//drawbasemap是绘制地图的});//图层弹框数据let layerlist = ref<Array<fabric.Object & p >>([] as Array<fabric.Object & p >)watch(()=>vnodelist.value,()=>{layerlist.value = canvas!.getObjects() as Array<fabric.Object & p>},{deep:true})//画布初始化操作function initFabricCanvas(callback) {  if (!basecontent.value) return;  canvas = new fabric.Canvas('canvas', {  width: basecontent.value.offsetWidth,  height: basecontent.value.offsetHeight,preserveObjectStacking:true});callback && callback()}//绘制底图  地图就是最底层的假地图图片,所以需要默认先绘制const drawbasemap = ()=>{const img = new Image();  img.src = basemap; let id = generateUUID(); img.onload = () => {  const imgLayer = new fabric.Image(img, {  selectable:false,hasControls:false,  left: 0,  top: 0,  scaleX: canvas!.width / img.width,  scaleY: canvas!.height / img.height,  z: 1,id,types:'Base'  });  canvas!.add(imgLayer);}}//开始拖拽事件,根据classname判断拖拽的元素,不同的classname传递不同的typefunction onStart(e){let classname = ref<string>('')classname.value = e.target.parentElement.className.split(' ')[0];switch (classname.value) {case 'item-one':e.dataTransfer.setData('type', 'Rect');break;case 'item-two':e.dataTransfer.setData('type', 'Circle');break;case 'device-one':e.dataTransfer.setData('type', 'device-one');break;case 'device-two':e.dataTransfer.setData('type', 'device-two');break;case 'device-three':e.dataTransfer.setData('type', 'device-three');break;default:break;}}//拖拽过程中阻止默认事件function dragOver(e){e.preventDefault();}//拖拽完成绘制图形function drop(e) {let types = ref<string>('');types.value = e.dataTransfer.getData('type');//这里拿到拖拽开始事件传递过来的typelet vnode:fabric.Object;let id = generateUUID();switch (types.value) {//根据type绘制不同的图形case 'Rect':let objone = {selectable: true, // 是否可选hasControls:false,top:(e.pageY - (e.pageY - e.offsetY))/scale.value,left:(e.pageX - (e.pageX - e.offsetX))/scale.value,//创建对象的x坐标width: 150, //宽和高height: 300,fill:'rgba(73, 120, 236,0.6)', //填充颜色stroke:'rgba(38, 162, 234,1)', //线条颜色strokeWidth: 4, //线条宽度strokeOpacity:0.5,types:types.value,id,name:'地块',z:2,classify:'a',area:2,zoomX:scale.value,zoomY:scale.value,angle:0,visible:true}vnode = new fabric.Rect(objone) // 开始绘制canvas!.add(vnode); //添加到画布中去vnodelist.value.push(objone);break;case 'Circle':let objtwo = {selectable: true, // 是否可选hasControls:false,top:(e.pageY - (e.pageY - e.offsetY))/scale.value,left:(e.pageX - (e.pageX - e.offsetX))/scale.value,//创建对象的x坐标rx: 25,    // 圆的水平半径ry: 25,    // 圆的垂直半径fill: 'rgba(73, 120, 236,0.6)', // 填充颜色  stroke: 'rgba(255,255,255,1)', // 描边颜色  strokeWidth: 1, // 描边宽度  types:types.value,id,name:'塘口',z:3,zoomX:scale.value,zoomY:scale.value,visible:true}vnode = new fabric.Ellipse(objtwo);canvas!.add(vnode);vnodelist.value.push(objtwo);break;case 'device-one':const imgone = new Image();  imgone.src = video;let oneparams = drawdevice(e,id,'视频监控');imgone.onload = () => {  const imgerone = new fabric.Image(imgone, oneparams);  canvas!.add(imgerone); vnodelist.value.push(oneparams);} break;case 'device-two':const imgtwo = new Image();  imgtwo.src = onedevice;  let twoparams = drawdevice(e,id,'一体设备');imgtwo.onload = () => {  const imgertwo = new fabric.Image(imgtwo,twoparams);  canvas!.add(imgertwo);vnodelist.value.push(twoparams);  } break;case 'device-three':const imgthree = new Image();  imgthree.src = video;let threeparams = drawdevice(e,id,'安防视频');imgthree.onload = () => {  const imgerthree = new fabric.Image(imgthree, threeparams);  canvas!.add(imgerthree);vnodelist.value.push(threeparams);} break;default:break;}  reorderObjectsByZ()}//绘制设备类图层参数处理function drawdevice(e:DragEvent,id:string,name:string){return {selectable: true, // 是否可选hasControls:false,top:(e.pageY - (e.pageY - e.offsetY))/scale.value,left:(e.pageX - (e.pageX - e.offsetX))/scale.value,z: 4,id,name,refnumber:'0',versionid:'',types:'Device',zoomX:scale.value,zoomY:scale.value, visible:true}}//循环画布中的元素始终保持层级z有效function reorderObjectsByZ() {//因为后绘制的图形层级会高一些,为了跟据z属性保持层级逻辑if (canvas) {  const objects = canvas!.getObjects().sort((a:fabric.Object & p, b:fabric.Object & p) => a.z - b.z);  //根据z属性排序canvas.clear(); // 移除所有现有对象  objects.forEach(obj => {canvas.add(obj); // 重新添加对象  });  }  } //选中激活对应的图形let cur = ref<Curparams>({} as Curparams);//记录当前选中的数据let rectparams = ref<Rectparams>({} as Rectparams)let circleparams = ref<Circleparams>({} as Circleparams)let deviceparams = ref<DeviceParams>({} as DeviceParams)//选择图层获取参数function selectitem(item){cur.value = item;closeall();canvas!.getObjects().forEach((obj: fabric.Object & p) => {if(cur.value.id == obj.id){//根据唯一id判断选中的哪个元素,获取数据回填表单let defaultparam = {id:obj.id,name:obj.name,types:obj.types}switch(obj.types){case 'Rect':let rectfill = splitRgbaSimple(obj.fill as string);let rectstroke = splitRgbaSimple(obj.stroke as string);rectparams.value = {...defaultparam,fill:rectfill.color,fillopacity:rectfill.opacity,width: obj.width,  height: obj.height,strokeWidth: obj.strokeWidth,stroke: rectstroke.color, strokeOpacity:rectstroke.opacity,area:obj.area ? + obj.area : 0,classify:obj.classify + '',angle:obj.angle};          break;case 'Circle':let circlefill = splitRgbaSimple(obj.fill as string);let circlestroke = splitRgbaSimple(obj.stroke as string);circleparams.value = {...defaultparam,fill:circlefill.color,fillopacity:circlefill.opacity,left: obj.left,  top: obj.top,  rx: obj.rx*2,  ry: obj.ry*2,strokeWidth: obj.strokeWidth,stroke: circlestroke.color,strokeOpacity:circlestroke.opacity,};          break;case 'Device':deviceparams.value = {  ...defaultparam,refnumber:obj.refnumber + '',versionid:obj.versionid + '',};break;}canvas!.setActiveObject(obj); // 激活选中元素  canvas!.renderAll(); //重新渲染画布(虽然选中元素通常会自动触发重绘)}});  }//将rgba提取为rgb的格式和透明度function splitRgbaSimple(rgbaString:string) {  const alphaIndex = rgbaString.lastIndexOf(',');  const opacity = parseFloat(rgbaString.slice(alphaIndex + 1, -1)) * 100;const rgbString = rgbaString.slice(5, alphaIndex); const color = `rgb(${rgbString.replace(/\s+/g,'')})`;return { color, opacity };  } //弹框实例const pondDialog = ref();const massifDialog = ref();const deviceDialog = ref();const layerDialog = ref();//打开修改属性弹框function editattr(){let mapflag = {'Rect': massifDialog,'Circle': pondDialog,'Device': deviceDialog,}mapflag[cur.value.types].value.disbled = true;}//回填数据点击确定更新图层function updatecanvas(params:any){console.log(params);canvas!.getObjects().forEach((obj: fabric.Object & p) => {if(cur.value.id == obj.id){            switch(obj.types){case 'Rect':obj.set({...params,width:params.width ? +params.width : 50,height:params.height ? +params.height : 100,stroke:rgbToRgba(params.stroke,params.strokeOpacity),fill:rgbToRgba(params.fill,params.fillopacity),angle:+params.angle});break;case 'Circle':obj.set({...params,rx:params.rx ? (+params.rx)/2 : 25,ry:params.ry ? (+params.ry)/2 : 25,left:+params.left,top:+params.top,stroke:rgbToRgba(params.stroke,params.strokeOpacity),fill:rgbToRgba(params.fill,params.fillopacity)});break;case 'Device':obj.set(params);break;}updatelist({name:obj.name,id:obj.id,types:obj.types})//更新列表中的数据canvas!.requestRenderAll(); // 重新渲染画布(虽然选中元素通常会自动触发重绘)}}); }//处理颜色格式 最终显示是rgba的格式function rgbToRgba(rgbString, alpha) {  const rgbArray = rgbString.replace(/^rgb\(([^)]+)\)$/, '$1').split(',');  const r = parseInt(rgbArray[0].trim(), 10);  const g = parseInt(rgbArray[1].trim(), 10);  const b = parseInt(rgbArray[2].trim(), 10);alpha = (parseInt(alpha) / 100).toFixed(1);return `rgba(${r}, ${g}, ${b}, ${alpha})`;  }  //控制图层弹框中选的图层控制当前元素的显示与隐藏function updatevisible(item){canvas!.getObjects().forEach((obj: fabric.Object & p) => {if(item.id == obj.id){//查找当前选中的元素            obj.visible = item.visible;canvas!.requestRenderAll()}});}//删除图层function removevnode(){canvas!.getObjects().forEach((obj: fabric.Object & p) => {if(cur!.value!.id == obj?.id){//查找当前选中的元素            canvas!.remove(obj);//移除元素removelist();//对应的左侧栏数据也移除}});  }//更新左侧列表数据function updatelist(params){    let i = vnodelist.value.findIndex(item => item.id == cur.value.id);vnodelist.value[i] = params;}//画布转jsonconst tojson = ()=>{let jsonbefore = ref<{version:string,objects:Array<fabric.Object & p>}>({objects:[],version:'6.1.0'})canvas!.getObjects().forEach((obj: fabric.Object & p) => {let objadd = obj.toObject();['id','z','name','types','selectable','hasControls','classify','area','refnumber','versionid'].forEach(item=>{obj[item] && (objadd[item] = obj[item])})objadd.hasControls = false;      jsonbefore.value.objects.push(objadd)});//调试专用localStorage.setItem('canvas',JSON.stringify(jsonbefore));canvas!.clear()//调用接口//return JSON.stringify(jsonbefore)}//用json回显画布const tocanvas = ()=>{let json = JSON.parse(localStorage.getItem('canvas') as string); canvas!.loadFromJSON(json._value, () => {          canvas!.requestRenderAll();setTimeout(()=>{scale.value = canvas!.getObjects()[1].zoomX as number;vnodelist.value = canvas!.getObjects().map((item:fabric.Object & p) =>{return {id: item.id,types:item.types,name:item.name,visible:item.visible,}})},100)});  }//移除左侧列表数据function removelist(){closeall()let i = vnodelist.value.findIndex(item => item.id == cur.value.id);vnodelist.value.splice(i, 1);}//关闭所有弹框function closeall(){[massifDialog,pondDialog,deviceDialog,layerDialog].forEach(item =>{item.value.disbled = false;})}//放大缩小事件 最大放大两倍 最小还原1:1let scale = ref<number>(1);function zoomIn() {  if (scale.value < 2) {  scale.value += 0.1; // 可以调整步长来平滑缩放  canvas!.setZoom(scale.value)}  }//缩小function zoomOut() {  if (scale.value > 1) {  scale.value -= 0.1;  canvas!.setZoom(scale.value);}  }//打开图层弹框function openlyer(){closeall();layerDialog.value.disbled = true;}

以上是全部代码,上述代码中解决了以下问题
1、拖拽是基于html5的新特性draggable结合其自带的拖拽方法拿到xy坐标,计算位于目标元素xy坐标
2、fabric画布元素的层级问题,无论元素创建的先后始终保证自定义层级有效(reorderObjectsByZ方法)
3、fabric画布转json自定义参数丢失的问题(tojson 方法)
4、fabric画布放大或缩小后xy坐标偏移的问题 (记录scale缩放比,始终计算left与top值)
5、ts中fabric画布元素类型如何兼容自定义属性(自定义P类型,与fabric.object交叉声明)

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

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【李林880-2025版本】个人错题01 第十六章节——喻老讲解版

十六章 这里需要注意的是三个设的变量都要满足的不等式条件 根据题目的最长中间的一段需要满足大于其他两个变量的不等式条件 最后根据几何概型方法求出概率 两个情况 重要思想[逆事件] &#xff1a;7个正品找到了3个次品都找到了 这里首先从六个空中选出两个次品位置&…

《Web项目跨域请求后端Api设置Cookie失败问题?》

问题描述&#xff1a; 在web项目中跨域请求api时&#xff0c;api登录成功后需要向域名中设置cookie实现在两个域名下共享&#xff0c;但是登录接口返回成功&#xff0c;响应头中也有set-cookie&#xff0c;实际却无法设置到cookie中… web项目访问时的域名https://b.com/ api所…

【HarmonyOS 4.0】@BuilderParam 装饰器

1. BuilderParam 装饰器 BuilderParam 装饰器用于装饰自定义组件(struct)中的属性&#xff0c;其装饰的属性可作为一个UI结构的占位符&#xff0c;待创建该组件时&#xff0c;可通过参数为其传入具体的内容。参数必须满足俩个条件&#xff1a; 2.1 参数类型必须是个函数&#x…

C++ 设计模式——代理模式

C 设计模式——代理模式 C 设计模式——代理模式1. 主要组成成分2. 逐步构建代理模式2.1 抽象主题类定义2.2 真实主题类实现2.3 代理类实现2.4 主函数 3. 代理模式 UML 图代理模式 UML 图解析 4. 代理模式的优点5. 代理模式的缺点6. 代理模式的分类7. 代理模式和装饰者模式比较…

深度学习——分布式训练

目录 1. 前言2.分布式训练的分类3.不并行&#xff08;单机单卡&#xff09;4. 数据并行 DP和DDP4.1 异同点4.2 原理4.3 DP 实现&#xff08;单机多卡&#xff09;4.4 DDP 实现&#xff08;单机多卡&#xff0c;多机分布式&#xff09;4.4.1DDP 基本概念4.4.2 DDP之单机多卡4.4.…

jmeter中CSV 数据文件设置用例

1、CSV数据文件的基础使用 线程组->添加->配置远近->CSV数据文件设置 2、多条用例运行CSV数据文件 由于我的csv请求的json数据有“&#xff0c;”所以我这边 分隔符选择了*号 写了两行需要测试的用例&#xff0c;需要添加一个“循环控制器” 线程组->添加-&g…

Tower for Mac Git客户端管理软件

Mac分享吧 文章目录 效果一、下载软件二、开始安装1、双击运行软件&#xff0c;将其从左侧拖入右侧文件夹中&#xff0c;等待安装完毕2、应用程序显示软件图标&#xff0c;表示安装成功 三、运行测试1、打开软件&#xff0c;测试2、克隆项目&#xff0c;测试 安装完成&#xf…

一文读懂大语言模型:基础概念篇

在当今AI时代&#xff0c;大语言模型正以前所未有的速度重塑我们的世界。作为NLP领域的明星&#xff0c;它们不仅理解语言&#xff0c;更创造语言&#xff0c;开启了智能交互的新纪元。 本文将介绍着重介绍大模型的概念&#xff0c;帮助大家简单了解其技术原理、发展历程&#…

Autosar(Davinci) --- 创建一个Implementation Data Types

前言 这里我们讲一下如何创建一个Implementation Data Types&#xff08;IDT) 一、什么是IDT 二、如何创建一个IDT 鼠标右键【Implementation Data Types】,选择【new Type Reference...】 起一个名字【IdtDoorState】&#xff0c;Data Types选择【boolean】&#xff0c;这里…

海康VisionMaster使用学习笔记18-常见问题排查

常见问题排查思路 常见问题排查方法-安装阶段 常见问题排查方法-启动阶段 常见问题排查方法-使用阶段 常见问题排查方法-相关资料 关于VisionMaster使用的学习笔记到这里告一段落了,继续海康二次开发的学习. 海康二次开发 海康相机二次开发学习笔记1-环境配置

设计模式-结构型模式-适配器模式

1.适配器模式定义 将类的接口转化为客户期望的另一个接口&#xff0c;适配器可以让不兼容的两个类一起协同工作&#xff1b; 1.1 适配器模式的优缺点 优点 将目标类和适配者类解耦&#xff0c;通过引入一个适配器类来重用现有的适配者类&#xff0c;无需修改原有结构&#x…

linux安装elasticsearch

只考虑单机的情况&#xff0c;因为没有那么大的需求 首先创建个文件夹用于存放elasticsearch cd /opt/mkdir modulecd module根据官网提示下载 官网文档链接 wget https://artifacts.elastic.co/downloads/elasticsearch/elasticsearch-8.15.0-x86_64.rpmwget https://artifac…

USB详解,配置及难点

一、USB发展历史 二、USB简介 USB有USB1.0/1.1/2.0/3.0多个版本&#xff0c;标准USB由4根线组成,VCC&#xff0c;GND&#xff0c;D&#xff0c;D-&#xff0c;其中D和D-是数据线&#xff0c;采用差分传输。在USB主机上,D-和D都是接了15K的电阻到地,所以在没有设备接入的时候,D、…

2024年第六届控制与机器人国际会议(ICCR 2024)即将召开!

2024年第六届控制与机器人国际会议&#xff08;ICCR 2024&#xff09;将于2024年12月5日至7日在日本横滨举行。智能机器人结合了多种概念、学科和技术&#xff0c;共同创造出各种有用的设备、操作器和自主实体&#xff0c;为特定人类社区服务&#xff0c;如制造设备、医疗和远程…

极光推送(JPush)赋能登虹科技,打造智慧视觉云平台新体验

近日&#xff0c;中国领先的客户互动和营销科技服务商极光&#xff08;Aurora Mobile&#xff0c;纳斯达克股票代码&#xff1a;JG&#xff09;与杭州登虹科技有限公司&#xff08;以下简称“登虹科技&#xff08;Closeli&#xff09;”&#xff09;达成合作&#xff0c;借助极…

C++学习笔记——打印ASCII码

一、题目描述 二、代码 #include <iostream> using namespace std; int main() {char a_char;int a_int;cin >> a_char;a_int a_char;cout << a_int;return 0; }

企业建站技术路线探索

前言 企业站是指企业或公司创建的官方网站&#xff0c;用于展示企业信息、产品和服务。它通常包括公司简介、产品或服务介绍、联系方式、新闻更新等内容。企业站的目的是提升品牌形象、提供客户服务和促进业务发展。在跨境贸易中&#xff0c;企业建站尤为关键&#xff0c;因为…

python-docx复制的行格式多出一个单元格

目录 一、问题 二、原因 三、解决方法 一、问题 我需要在下述表格中第三行的编号、证件名称、来源、证明对象一列的第三行填入1、1、1、1&#xff0c;之后复制第三行的格式&#xff0c;再填入2、2、2、2&#xff0c;再复制第三行的格式&#xff0c;填入3、3、3、3&#xff…

VBA技术资料MF180:将某个文件夹中的某类图片导入Word

我给VBA的定义&#xff1a;VBA是个人小型自动化处理的有效工具。利用好了&#xff0c;可以大大提高自己的工作效率&#xff0c;而且可以提高数据的准确度。“VBA语言専攻”提供的教程一共九套&#xff0c;分为初级、中级、高级三大部分&#xff0c;教程是对VBA的系统讲解&#…

工作实战-项目压测记录

1-1-1每分钟的单量 1-1-2第二版测试 2022年5月16日 17:43:11 成功 失败 其它(nginx) 真实入库单量 总单量 52 1 447 500 2022年5月16日 19:42:18 成功 失败 其它(nginx) 真实入库单量 总单量 311 689 306 1000 2-0-1. 20线程-2000单执行结果 2-1-0. 40线…