低代码信创开发核心技术(二):手撕灵活好用的Vue拖拉拽布局系统

news2024/11/23 11:33:44

前言

随着信息化时代的到来,软件已经成为企业和个人不可或缺的工具。然而,许多人在开发软件时遇到了各种问题,比如开发周期长、技术门槛高、成本高昂等等。为了解决这些问题,低代码平台应运而生。低代码平台是一种快速开发工具,它可以帮助开发者快速构建应用程序,从而提高开发效率和降低成本。

近年来,国产软件市场蓬勃发展,越来越多的企业开始关注自主创新和信息化发展。低代码平台作为信息化创新的重要工具,也逐渐受到了广泛关注。同时,随着Vue.js等前端框架的普及,拖拉拽布局系统也成为了低代码平台的核心技术之一。灵活好用的拖拉拽布局系统,能够帮助开发者快速搭建界面,提高开发效率。

在这篇博客中,我们将深入探究基于Vue.js的拖拽布局的实现方法,以及如何使用它来快速构建应用程序的界面。

阅读本文结合请结合上一篇文章理解:
低代码信创开发核心技术(一):基于Vue.js的描述依赖渲染DDR实现模型驱动的组件

效果预览

在这里插入图片描述
通过动图我们可以了解,这个布局功能已经可以实现在元素前添加、元素后添加、元素内添加,并能支持复杂的父子关系嵌套的添加。
在这里插入图片描述
甚至结合Ant Design Vue的选项卡,也可以轻松使用。

知识储备

1、您需要先掌握Vue.js 3.0版本的相关知识。
2、您需要先了解浏览器内置的window对象、事件驱动机制和拖拽相关的API。
3、您需要先了解HTML5和CSS3的相关知识。

整体界面布局

在这里插入图片描述

这里我们设想左边是一个工具栏,通过拖拽到中间工作空间可以把控件拖拽上去,与此同时未来在选中控件之后,在右侧还可以设置控件的相关属性。因为写这篇博客的时候没有找美工,所以尽管丑了点,功能还是设想的比较全面的。

这里我们用FrontendBlocks设计一下,一键生成出来界面布局代码:

文件名:Designer.vue

<template>
	<div class="root">
		<div class="DesignerControls">
			<div class="BtnControl">Label</div>
		</div>
		<div class="DesignerWorkSpace">
		</div>
		<div class="DesignerAttrs">
			<div class="attrItem">
				<div class="attrName">属性名</div>
				<input class="attrValue" />
			</div>
		</div>
	</div>
</template>

<script>
	export default {
		props: ['id', 'text', 'context', 'initData'],
		data() {
			return {}
		},
		mounted() {
			this.context.initControl(this)
		}
	}
</script>

<style>
	html,
	body,
	.root {
		padding: 0;
		margin: 0;
		width: 100%;
		height: 100%;
	}

	.root {
		box-sizing: border-box;
		font-weight: normal;
		font-style: normal;
		text-align: left;
		display: flex;
		position: relative;
		width: 100%;
		flex-direction: row;
		justify-content: flex-start;
		align-items: flex-start;
	}

	.DesignerControls {
		box-sizing: border-box;
		background-color: rgba(243, 243, 243, 1);
		font-weight: normal;
		font-style: normal;
		text-align: left;
		flex-shrink: 0;
		display: flex;
		position: relative;
		width: 180px;
		height: 100%;
		padding: 8px;
		flex-direction: column;
		justify-content: flex-start;
		align-items: flex-start;
		overflow: hidden scroll;
	}

	.BtnControl {
		box-sizing: border-box;
		border: 1px solid rgba(0, 0, 0, 1);
		border-top: 1px solid rgba(0, 0, 0, 1);
		border-bottom: 1px solid rgba(0, 0, 0, 1);
		border-left: 1px solid rgba(0, 0, 0, 1);
		border-right: 1px solid rgba(0, 0, 0, 1);
		font-size: 12px;
		font-weight: normal;
		font-style: normal;
		text-align: left;
		display: flex;
		position: relative;
		width: 100%;
		padding: 8px;
		margin: 0px 0px 8px 0px;
		flex-direction: column;
		justify-content: flex-start;
		align-items: flex-start;
	}

	.DesignerWorkSpace {
		box-sizing: border-box;
		font-weight: normal;
		font-style: normal;
		text-align: left;
		position: relative;
		width: 100%;
		height: 100%;
		flex-direction: column;
		justify-content: flex-start;
		align-items: flex-start;
	}

	.DesignerAttrs {
		box-sizing: border-box;
		background-color: rgba(243, 243, 243, 1);
		font-weight: normal;
		font-style: normal;
		text-align: left;
		flex-shrink: 0;
		display: flex;
		position: relative;
		width: 240px;
		height: 100%;
		flex-direction: column;
		justify-content: flex-start;
		align-items: flex-start;
		overflow: hidden scroll;
	}

	.attrItem {
		box-sizing: border-box;
		border: 1px solid rgba(167, 167, 167, 1);
		border-top: none;
		border-bottom: 1px solid rgba(167, 167, 167, 1);
		border-left: none;
		border-right: none;
		font-weight: normal;
		font-style: normal;
		text-align: left;
		display: flex;
		position: relative;
		width: 100%;
		padding: 4px;
		flex-direction: row;
		justify-content: flex-start;
		align-items: center;
	}

	.attrName {
		box-sizing: border-box;
		font-size: 12px;
		font-weight: normal;
		font-style: normal;
		text-align: left;
		flex-shrink: 0;
		display: flex;
		position: relative;
		width: 30%;
		flex-direction: column;
		justify-content: flex-start;
		align-items: flex-start;
	}

	.attrValue {
		box-sizing: border-box;
		border: 1px solid rgba(0, 0, 0, 1);
		border-top: 1px solid rgba(0, 0, 0, 1);
		border-bottom: 1px solid rgba(0, 0, 0, 1);
		border-left: 1px solid rgba(0, 0, 0, 1);
		border-right: 1px solid rgba(0, 0, 0, 1);
		font-weight: normal;
		font-style: normal;
		text-align: left;
		display: flex;
		position: relative;
		width: 100%;
		padding: 5px;
		flex-direction: column;
		justify-content: flex-start;
		align-items: flex-start;
	}
</style>

JavaScript拖拽事件简述

整体拖拽流程如下:
在这里插入图片描述

给需要拖拽的元素绑定 dragstart 事件,该事件在开始拖拽时触发。在该事件中,我们可以设置拖拽数据和拖拽效果。
当鼠标移动到其他元素上时,会触发 dragenter 事件。在该事件中,我们可以设置拖拽目标元素的样式,以反馈给用户当前的拖拽位置。
接着,dragover 事件会持续触发,直到拖拽元素离开了拖拽目标元素。在该事件中,我们可以阻止默认行为,以便能够将元素放置到拖拽目标元素上。
当拖拽元素被放置到拖拽目标元素上时,会触发 drop 事件。在该事件中,我们可以获取拖拽数据,并进行相应的处理操作。
最后,当拖拽完成时,会触发 dragend 事件。在该事件中,我们可以进行一些清理工作,比如重置拖拽元素的样式。

需要注意的是:
拖拽过程中,需要设置拖拽元素和拖拽目标元素的 draggable 属性为 true。
在 dragover 事件中,需要阻止默认行为,以便能够将元素放置到拖拽目标元素上。
在 drop 事件中,需要阻止默认行为,并且需要确保拖拽元素和拖拽目标元素都支持相应的拖拽类型。
在 dragstart 事件中,可以设置拖拽数据和拖拽效果。在 drop 事件中,可以获取拖拽数据,并进行相应的处理操作。
在 dragend 事件中,需要重置拖拽元素的样式,并进行一些清理工作。

是不是很简单呢?

控件栏发起拖拽事件

接下来我们稍作改造,先把Label那个可以拖拽的按钮改成由JSON维护的:

<div v-for="(item) in controlTools" class="BtnControl"
    draggable="true"
    @dragstart="dragStart($event,item.value)"
	@dragend="dragLeaveWorkSpace($event)">
	{{item.name}}
</div>
		data() {
			return {
				controlTools:[{
					name:"Label",
					value:'文本说明'
				},{
					name:"TextBox",
					value:'<input type="text" />'
				},{
					name:"Button",
					value:'<button type="button">搜索</button>'
				}]
			}
		}

这里的我们定义了两个事件处理,一个是dragStart,另一个是dragLeaveWorkSpace。先在methods里把这两个函数实现了,不过我写的时候也不知道dragLeaveWorkSpace应该处理什么,反正先留着空:

dragStart(e,data) {
	e.dataTransfer.setData("content", data);
},
dragLeaveWorkSpace(e) {
	e.preventDefault();
}

然后为了方便测试,我们把工作区改造一下:

<DropTarget class="DesignerWorkSpace">
	<DropTarget class="DemoBox">
		<div class="DemoBox">
			<DropTarget class="DemoBox">
				<div class="DemoBox">
					<DropTarget class="DemoBox">
					</DropTarget>
				</div>
			</DropTarget>
		</div>
	</DropTarget>
	<div class="DemoBox" style="display: flex;flex-direction: row;margin-top: 10px;">
		<DropTarget class="DemoBox" style="width: 100%;padding: 20px;"></DropTarget>
		<DropTarget class="DemoBox" style="width: 100%;padding: 20px;"></DropTarget>
		<DropTarget class="DemoBox" style="width: 100%;padding: 20px;"></DropTarget>
		<DropTarget class="DemoBox" style="width: 100%;padding: 20px;"></DropTarget>
		<DropTarget class="DemoBox" style="width: 100%;padding: 20px;"></DropTarget>
	</div>
</DropTarget>

把DemoBox的样式写上

.DemoBox {
	border: 1px solid #a3a3a3;
	padding: 50px;
	box-sizing: border-box;
	position: relative;
}

实现DropTarget 可拖放区域

我们写个新组件DropTarget.vue,因为上面工作区我们要往里放子元素,所以自然就要用到slot。

<div class="DropTarget"
	@dragenter="dragEnter($event)"
	@dragover="dragOver($event)"
	@drop="drop($event)">
	<slot></slot>
</div>

然后给DropTarget上个背景色,这个不是必须的

.DropTarget {
	background-color: #96969632;
}

接下来写dragEnter方法,考虑到有可能有子元素父元素之间的关系处理,那么就先写个递归函数放methods里,向上不断找父级,只要碰到class里有DropTarget的,那一定是目标组件。

calcRealTarget(element) {
	let target = element;
	if (target.classList.contains("DropTarget")) {
		return target;
	}
	if (!element.parentElement) return null;
	return this.calcRealTarget(element.parentElement)
}

然后我们正式开始写dragEnter方法:

dragEnter(e) {
	let target = this.calcRealTarget(e.target)
	if (target == null) return false;
	e.preventDefault();
	e.stopPropagation();
	// 处理添加
	target.originBgColor = target.style.backgroundColor
	target.style.outline = "1px dashed #74c3ff"
	target.style.outlineOffset = "-1px"
	target.classList.add('draging')
	if (window.currentDropTarget && window.currentDropTarget != target) {
		let oldDropTarget = window.currentDropTarget
		oldDropTarget.style.backgroundColor = oldDropTarget.originBgColor
		oldDropTarget.style.outline = null
		oldDropTarget.classList.remove('draging')
	}
	window.currentDropTarget = target
}

这里有个技巧,使用outline样式,相比于border来说,它不会因为占文档流的像素而使界面位置发生位移,特别是复杂界面处理的过程中,向外差一两个像素都是灾难性的。这里outlineOffset设置-1px,可以有效防止和边框border重叠,观感更好。
代码中,我们每次DragEnter事件中都会往window对象写一个属性currentDropTarget,把当前对象传过去。这里判断一遍,主要是为了防止鼠标从父容器移动到子容器的时候会再次触发DragEnter事件,为了不出现卡顿,展现出丝般顺滑的感觉,这里需要判断一下,一旦发生了同级别之间的目标转移,则立即把前一个组件的样式清掉。
为什么我这里不用dragLeave事件呢?那是因为鼠标拖拽划过父子容器的时候势必会触发一次dragLeave,从而导致样式被莫名清空。

为了配合鼠标动作,有一个直观的展现,那么这里我们用伪类做一个不会吃掉鼠标事件的半透明遮罩,测试一下,瞬间这感觉就上来了。不过要注意的是,可拖放区域一定要显式声明position样式,否则这个遮罩会超出边界。

.draging::before {
	content: ' ';
	width: 100%;
	height: 100%;
	left: 0px;
	top: 0px;
	opacity: 0.3;
	position: absolute;
	background-color: #74c3ff;
	z-index: 99999;
	pointer-events: none;
}

接下来我们编写dragOver事件的函数:

dragOver(e) {
	let target = this.calcRealTarget(e.target)
	if (target == null) return false;

	// 判断什么时候可以显示排序:1、当前事件对象不是容器 2、当前事件对象虽然是容器,但父组件也是容器
	let showOrderLine = false;
	if (target != e.target) {
		showOrderLine = true;
	}
	if (target.parentElement.classList.contains("DropTarget")) {
		showOrderLine = true
	}

	// 清除原有提示状态
	if (window.currentDropBefore && window.currentDropBefore != e.target) {
		let oldDropBefore = window.currentDropBefore
		oldDropBefore.style.borderInlineStart = null
		window.currentDropBefore = null;
	}
	if (window.currentDropAfter && window.currentDropAfter != e.target) {
		let oldDropAfter = window.currentDropAfter
		oldDropAfter.style.borderInlineEnd = null
		window.currentDropAfter = null
	}
	// 需要显示则显示
	if (showOrderLine) {
		if (e.offsetX < (e.target.offsetWidth * 0.25)) {
			e.target.style.borderInlineStart = "2px solid #ff6600"
			window.currentDropBefore = e.target
			if (window.currentDropAfter) {
				window.currentDropAfter.style.borderInlineEnd = null
			}
		} else if (e.offsetX > (e.target.offsetWidth * 0.75)) {
			e.target.style.borderInlineEnd = "2px solid #ff6600"
			window.currentDropAfter = e.target
			if (window.currentDropBefore) {
				window.currentDropBefore.style.borderInlineStart = null
			}
		} else {
			e.target.style.borderInlineStart = null
			e.target.style.borderInlineEnd = null
			window.currentDropBefore = null
			window.currentDropAfter = null
		}
	}
	e.preventDefault();
},

因为我们在实现拖放的同时还要实现排序,所以这里我们就约定一个可拖放区域的左侧25%的区域是同级别向前插入一个元素,右侧25%区域是向同级别后面追加一个元素,只有中间的50%区域是向其中填充子元素,如果前后无需排序,则整片区域都是当做添加子元素,鼠标拖拽到哪个区域,便会有对应区域的样式展示,这里使用borderInlineStart和borderInlineEnd可以很方便的展现样式,为什么用到Inline呢?这是因为outline不支持分别设置外框线。
然后我们实现drop方法:

drop(e) {
	let target = this.calcRealTarget(e.target)
	if (target == null) return false;
	e.preventDefault();
	e.stopPropagation();
	let data = e.dataTransfer.getData('content');

	// 清除因拖拽产生的样式
	if (window.currentDropTarget) {
		window.currentDropTarget.style.backgroundColor = window.currentDropTarget.originBgColor
		window.currentDropTarget.style.outline = null
		window.currentDropTarget.classList.remove('draging')
	}
	if (window.currentDropBefore) window.currentDropBefore.style.borderInlineStart = null
	if (window.currentDropAfter) window.currentDropAfter.style.borderInlineEnd = null

	// 完成拖拽操作并添加DOM元素
	// TODO:这里需要修改一下,我们可以通过DOM元素的.__vnode.ctx.proxy属性获取到VUE的vnode对象
	// 或者是通过VUE当前组件的.$el和实际拿到的DOM元素进行匹配。两种方式都可以找到VUE的代理对象
	// 通过代理对象,可以将这里面的slot换成通过数组来维护,通过DDR方式,递归将JSON渲染成组件
	// 如果不了解数组和控件系统的思想,可以看上一篇文章
	let newNode=document.createElement("div")
	newNode.innerHTML=data
	if (window.currentDropBefore) {
		window.currentDropBefore.parentElement.insertBefore(newNode, window.currentDropBefore)
	} else if (window.currentDropAfter) {
		window.currentDropAfter.after(newNode)
	} else {
		window.currentDropTarget.appendChild(newNode)
	}
}

这里把传递过来的信息接收到变量data里,然后清除掉所有临时加的样式,接下来就是创建元素、把元素放置在合适的位置上即可。
如果结合上篇文章,这里其实最好是用系统预设的组件,比如uiTextBox.vue,全程JSON控制,控制起来非常方便。

dragEnd事件处理

最后就是我们回到Designer.vue里,照着刚才的drag最后清理的逻辑,当拖拽结束时还原样式。这里后续要做二次拖拽(工具栏拖拽到工作区,从工作区一个组件拖拽到另一个组件里),我们可以在这个事件处理中销毁原组件。

dragLeaveWorkSpace(e) {
	e.preventDefault();
	if (window.currentDropTarget) {
		let oldDropTarget = window.currentDropTarget
		oldDropTarget.style.backgroundColor = oldDropTarget.originBgColor
		oldDropTarget.style.outline = null
		oldDropTarget.classList.remove('draging')
		window.currentDropTarget = null
	}
}

总结

本文主要介绍了基于Vue.js的拖拽布局的实现方法和如何使用它来快速构建应用程序的界面。深入探究了基于Vue.js的拖拽布局的实现方法,并展示了其效果预览和整体界面布局。最后,通过包括Vue.js 3.0版本、浏览器内置的window对象、事件驱动机制、拖拽相关API非常简单的实现了拖拽布局机制。
当我们能够创造出快速完成布局的系统之后,我们就可以结合上文所说的控件系统完成控件属性的设置、生成JSON代码保存到后台,然后再从后台读出JSON来渲染界面。
当我们能够快速批量的制造页面之后,就可以开始考虑结合后台整体实现模型驱动架构(MDA:Model Driven Architecture,它是一种软件设计方法论,通过将系统的业务逻辑和技术实现分离,将系统的关注点从技术层面转移到业务层面,提高了软件的可维护性和可重用性。在MDA架构中,模型是软件开发的核心,程序员通过定义模型来描述系统的业务逻辑和功能需求,然后使用模型转换工具将模型转换成最终的代码。),从而向使用部门或客户提供能够支撑其完成信息化创新的基础工具。

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

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

相关文章

Golang每日一练(leetDay0067) 第十行、打家劫舍I

目录 195. 第十行 Tenth Line &#x1f31f; 198. 打家劫舍 I House Robber &#x1f31f;&#x1f31f; &#x1f31f; 每日一练刷题专栏 &#x1f31f; Golang每日一练 专栏 Python每日一练 专栏 C/C每日一练 专栏 Java每日一练 专栏 195. 第十行 Tenth Line 给定一…

PySide6/PyQT多线程之 异常情况和优先级指南

前言 在PySide6/PyQT 中使用多线程时&#xff0c;线程的优先级和异常情况处理同样是重要的概念。 本文纯理论知识&#xff0c;无实操。换句话说&#xff0c;就是水文~~ 尽管在一般情况下我们不需要过多关注线程的优先级&#xff0c;但了解它的概念对于特定场景下的多线程编程仍…

Java每日一练(20230516) 最小栈、组合总和II、相同的树

目录 1. 最小栈 &#x1f31f; 2. 组合总和 II &#x1f31f;&#x1f31f; 3. 相同的树 &#x1f31f; &#x1f31f; 每日一练刷题专栏 &#x1f31f; Golang每日一练 专栏 Python每日一练 专栏 C/C每日一练 专栏 Java每日一练 专栏 1. 最小栈 设计一个支持 push…

[入门必看]数据结构5.4:树、森林

[入门必看]数据结构5.4&#xff1a;树、森林 第五章 树与二叉树5.4 树、森林知识总览5.4.1 树的存储结构5.4.2 树、森林与二叉树的转化5.4.3 树和森林的遍历 5.4.1 树的存储结构树的逻辑结构回顾&#xff1a;二叉树的顺序存储如何实现树的顺序存储&#xff1f;树的存储1&#x…

【连续介质力学】张量的范数、各向同性和各向异性张量、同轴张量和极分解

张量的范数 张量的大小&#xff0c;使用Frobenius 范数&#xff1a; ∣ ∣ v ⃗ ∣ ∣ v ⃗ ⋅ v ⃗ v i v i &#xff08;向量&#xff09; ||\vec v|| \sqrt{\vec v \cdot \vec v} \sqrt{v_iv_i} &#xff08;向量&#xff09; ∣∣v ∣∣v ⋅v ​vi​vi​ ​&#xff…

okhttp篇2:Dispatcher

Dispatchers维护着一个线程池&#xff0c;3个双端队列&#xff0c;准备执行的AsynCall&#xff0c;正在执行的AsynCall&#xff0c;正在执行的同步Call&#xff08;RealCall&#xff09;。 同时规定每个Host最多同时请求5个Request&#xff0c;同时可最多执行64个Request。 p…

玩转Google开源C++单元测试框架Google Test系列(gtest)之一 - 初识gtest

一、前言 本篇将介绍一些gtest的基本使用&#xff0c;包括下载&#xff0c;安装&#xff0c;编译&#xff0c;建立我们第一个测试Demo工程&#xff0c;以及编写一个最简单的测试案例。 二、下载 如果不记得网址&#xff0c; 直接在google里搜gtest&#xff0c;第一个就是。目…

Docker下Gitlab配置Let’s Encrypt证书

Docker下Gitlab配置Let’s Encrypt证书 1 参考文档2 常见问题2.1 前置条件2.2 不支持ip2.3 重复签发2.4 外网无法访问 ※3 内网穿透配置&#xff08;可选&#xff09;4 Gitlab 创建并配置Let’s Encrypt证书4.1 开放Let’s Encrypt签发所需端口4.2 新增存储HTTPS证书文件夹4.3 …

多态与虚函数(补)

多态与虚函数&#xff08;补&#xff09; 静态联编与动态联编的深层次理解多态底层原理 示例示例一示例二示例三示例四 对象与内存虚析构函数构造函数为什么不能是虚函数&#xff1f; 静态联编与动态联编的深层次理解 我们首先看下面一段代码 class object { private: int va…

C learning_12 操作符前篇(算术操作符、移位操作符、位操作符、赋值操作符、单目操作符、关系操作符、逻辑操作符)

目录 算术操作符 移位操作符 移位规则 位操作符 交换两个整形变量的写法 赋值操作符 单目操作符 sizeof和数组的纠缠 和--运算符 多组输入的方案 关系操作符 逻辑操作符 算术操作符 -- 加法操作符&#xff08;&#xff09;&#xff1a;用于将两个值相加。 -- 减法操…

Python爬虫(二):Requests库

所谓爬虫就是模拟客户端发送网络请求&#xff0c;获取网络响应&#xff0c;并按照一定的规则解析获取的数据并保存的程序。要说 Python 的爬虫必然绕不过 Requests 库。 1 简介 对于 Requests 库&#xff0c;官方文档是这么说的&#xff1a; Requests 唯一的一个非转基因的 P…

存储知识点:RAID0、RAID1、RAID5、RAID10特点是什么?所需的硬盘数量分别为多少?

RAID&#xff08;Redundant Array of Independent Disks&#xff09;是一种将多个独立的硬盘组合成一个逻辑磁盘的技术&#xff0c;目的是提高性能或容错能力。RAID有不同的级别&#xff0c;常见的有RAID0、RAID1、RAID5、RAID10等。下面我们来介绍这些级别的特点和所需的硬盘数…

套接字编程简介

作者&#xff1a;V7 博客&#xff1a;https://www.jvmstack.cn 一碗鸡汤 少年辛苦终身事&#xff0c;莫向光阴惰寸功。 —— 杜荀鹤 Socket概述 在计算机中产生和接受IO流的数据源是多种多样的&#xff0c;在网络编程中&#xff0c;有一个特殊的数据源就是socket。通俗点soc…

linux的系统日志

目录 一、日志文件的产生 二、日志文件存放在哪儿 &#xff08;1&#xff09;文本日志 &#xff08;2&#xff09;二进制日志 三、日志存放规则的配置文件 四、日志轮转 五、分析和监控日志 一、日志文件的产生 日志内容&#xff1a;内核、开机引导、守护进程启动运行的…

华为和思科两种常见的网络设备如何进行ospf配置?

概述 ospf&#xff08;开放最短路径优先&#xff09;是一种基于链路状态的动态路由协议&#xff0c;它可以在网络中自动发现和维护最优的路由路径。ospf广泛应用于大型和复杂的网络环境&#xff0c;因为它具有以下优点&#xff1a; 支持分层路由&#xff0c;可以将网络划分为…

WebAssembly黑暗的一面

案例1&#xff1a;技术支持诈骗 什么是技术支持诈骗&#xff1f; 技术支持诈骗是一种电话欺诈&#xff0c;其中诈骗者声称可以提供合法的技术支持服务。该骗局可能以陌生电话开始&#xff0c;骗子通常会声称来自合法的第三方的员工&#xff0c;如“微软”或“Windows部门”。他…

YOLOv5实现目标分类计数并显示在图像上

有同学后台私信我&#xff0c;想用YOLOv5实现目标的分类计数&#xff0c;因此本文将在之前目标计数博客的基础上添加一些代码&#xff0c;实现分类计数。阅读本文前请先看那篇博客&#xff0c;链接如下&#xff1a; YOLOv5实现目标计数_Albert_yeager的博客 1. 分类实现 以co…

web 实验一 HTML基本标签实验

实验原理 通过创建HTML5网页&#xff0c;验证form内多种元素标签及其属性的作用及意义。 实验目的 理解并掌握Form表单提交必须声明的内容 理解并掌握Input元素中多种类型属性的使用方法及使用场景 理解并掌握Label元素的使用方法 理解并掌握Datalist元素的使用方法 理解并掌握…

软件测试学习——笔记一

一、软件和软件测试 1、软件和软件分类 &#xff08;1&#xff09;软件&#xff1a;程序、数据、文档——用户手册 &#xff08;2&#xff09;软件的分类 按层次划分&#xff1a;系统软件、应用软件按组织划分&#xff1a;开源软件&#xff08;代码公开&#xff09;、商业软…

RSA 加密算法在C++中的实现 面向初学者(附代码)

概述 博文的一&#xff0c;二部分为基础知识的铺垫。分别从密码学&#xff0c;数论两个方面为理解RSA算法做好了准备。第三部分是对RSA加密过程的具体介绍&#xff0c;主要涉及其密钥对&#xff08;key-pair&#xff09;的获取。前三个部分与编程实践无关&#xff0c;可以当作…