当前位置: 首页 > news >正文

字玩FontPlayer开发笔记12 Vue3撤销重做功能

字玩FontPlayer开发笔记12 Vue3撤销重做功能

字玩FontPlayer是笔者开源的一款字体设计工具,使用Vue3 + ElementUI开发,源代码:github | gitee

笔记

撤销重做功能是设计工具必不可少的模块,以前尝试使用成熟的库实现撤销重做功能,但是细节问题有很多,就一直搁置了。这几天着手自己实现撤销重做模块,目前基本成形,虽然还有很多地方待完善,但还是先记录一下成果。

Vue3实现撤销重做的基本原理是记录状态(store)改变,每次状态改变的时候记录一下状态,撤销时恢复上一次记录的状态。虽然原理并不复杂,但在实际项目中遇到的细节问题很多,具体问题如下:

  1. 在组件创建过程中,会用到一些局部变量如mousedown、mousemove,在撤销过程中,store内的变量被更新了,但是局部变量没有随之更新,导致一些情况下逻辑失常。解决办法是把这些局部变量统一放到store里,这样将局部变量变成全局变量,这样在撤销重做时可以调用读取这些变量,然后在保存更新状态时记录这些变量。

  2. 在撤销重做过程中,有时候尽管变量随之更新,但是监听器事件并没有更新,这样就导致了在撤销创建钢笔组件之后,钢笔组件被清空了,但是监听事件并没有被移除,再次创建时,又会重复创建监听器。解决办法是使用一个map记录监听器,添加监听器事件的时候,记录map中对应事件key值为true,每次添加时只有map中没有记录该事件时才会添加,这样保证不会重复添加事件监听器。

  3. 对于一些复杂的模块比如脚本编写,为优化体验在关闭脚本编写窗口以后,执行撤销操作时会将上次编辑的脚本全部撤销,这时直接撤销可能导致用户误操作,所以需要给出提示。实现办法是在undoStack中存放的记录增加undoTip和redoTip字段,如果保存状态时填写了这两项,执行撤销重做操作的时候会给出提示。

  4. 在一些使用滑动条的操作中,连续滑动应该属与一次操作,直至滑动停止后,再次滑动才算下一次操作,对于这些情况需要使用防抖节流保证在一次中只压栈一次。

  5. 在一些情况下,可能会使用watch监听状态变化,以便在状态变换时保存状态,但是这样使用有些危险,比如有时候状态在改变其他状态时被连带改变,在watch中记录数据可能导致一个操作需要执行两次或多次撤销才能完成撤销操作。这些情况需要再具体设计中避免。

具体实现
保存状态

在每次执行用户操作时,记录一下操作前的状态,保存在undoStack中,以便在undo操作中进行恢复原先状态。这个保存操作使用saveState方法完成。

const saveState = (opName: String, opStores: StoreType[], opType: OpType, options: OpOption = {newRecord: true,undoTip: '',redoTip: '',
}) => {let stack = []if (opType === OpType.Redo) {stack = redoStack} else if (opType === OpType.Undo) {redoStack.length = 0stack = undoStack}let states: any = {}for (let i = 0; i < opStores.length; i++) {const opStore = opStores[i]switch(opStore) {case StoreType.EditCharacter: {states.editCharacterFile = R.clone(options.editCharacterFile || editCharacterFile.value)break}case StoreType.GlyphCompnent: {states.draggable = options.draggable || draggable.valuestates.dragOption = R.clone(options.dragOption || dragOption.value)states.checkRefLines = options.checkRefLines || checkRefLines.valuestates.checkJoints = options.checkJoints || checkJoints.value//states.jointsCheckedMap = R.clone(options.jointsCheckedMap || jointsCheckedMap.value)break}case StoreType.EditGlyph: {states.editGlyph = R.clone(options.editGlyph || editGlyph.value)break}case StoreType.Status: {states.editStatus = options.editStatus || editStatus.valuebreak}case StoreType.Tools: {states.tool = R.clone(options.tool || tool.value)break}case StoreType.Pen: {states.editingPen = R.clone(editingPen.value)states.pointsPen = R.clone(pointsPen.value)states.selectAnchor = R.clone(selectAnchor.value)states.selectPenPoint = R.clone(selectPenPoint.value)states.mousedownPen = mousedownPen.valuestates.mousemovePen = mousemovePen.valuebreak}case StoreType.Polygon: {states.editingPolygon = R.clone(editingPolygon.value)states.pointsPolygon = R.clone(pointsPolygon.value)states.mousedownPolygon = mousedownPolygon.valuestates.mousemovePolygon = mousemovePolygon.valuebreak}case StoreType.Rectangle: {states.editingRectangle = R.clone(editingRectangle.value)states.rectX = R.clone(rectX.value)states.rectY = R.clone(rectY.value)states.rectWidth = R.clone(rectWidth.value)states.rectHeight = R.clone(rectHeight.value)break}case StoreType.Ellipse: {states.editingEllipse = R.clone(editingEllipse.value)states.ellipseX = R.clone(ellipseX.value)states.ellipseY = R.clone(ellipseY.value)states.radiusX = R.clone(radiusX.value)states.radiusY = R.clone(radiusY.value)break}}}if (options.newRecord) {stack.push({opName,opStores,states,options})} else {const record = stack.pop()record.opName = opNamerecord.opStores = opStoresrecord.states = statesrecord.options = optionsstack.push(record)}
}

这里opStores指当前操作具体要更改哪些状态,在保存状态时除非特殊情况,一般不需要传入状态变量具体的值,只需要传入opStores标识即可,saveState函数会根据标识自动记录状态副本。笔者项目中opStores枚举设置如下:

enum StoreType {EditCharacter,EditGlyph,Tools,Pen,Polygon,Rectangle,Ellipse,GlyphCompnent,Status,
}

除了撤销功能,在记录重做状态的时候,也使用saveState保存状态记录,opType参数就是标明是当前状态记录时压入撤销操作栈还是重做操作栈的。

另外,对于一些特殊情况,比如需要提示或需要手动传入状态时,需要设置options参数,进行标明。

interface OpOption {newRecord?: boolean;undoTip?: string;redoTip?: string;[key: string]: any;
}

newRecord作用为是否需要新建记录还是在上一条记录中更新。这对于一些需要把连续操作合并在一个操作记录中的情况非常有用。比如在创建矩形或椭圆形状时,用户需要连续拖动光标以拖拽出矩形或椭圆形状,这其中的连续数据变换不能全部记录成单独状态压栈,但又需要记录跟踪每一步变化,所以这里就可以标注newRecord为false,在上一条记录中更新数据,这样执行撤销操作时,也只用一步就可以撤销整个操作。

// 保存状态
saveState('创建长方形组件',[StoreType.Rectangle,glyph ? StoreType.EditGlyph : StoreType.EditCharacter],OpType.Undo,{newRecord: false,}
)

undoTip和redoTip是为了先给用户提示,用户同意后在执行撤销或重做操作。这对于一些重要操作很有必要。

// 保存状态
saveState('编辑脚本与变量', [editStatus.value === Status.Glyph ? StoreType.EditGlyph : StoreType.EditCharacter
],OpType.Undo,{undoTip: '撤销编辑脚本与变量操作会将您上次在脚本编辑窗口的全部操作撤销,确认撤销?',redoTip: '重做编辑脚本与变量操作会将您上次在脚本编辑窗口的全部操作重做,确认重做?',newRecord: true,}
)

另外,有时候在记录状态的时候,状态变量实际上已经更改,这时候需要手动在options中传入原先的状态。
对于使用滑动条的操作,需要使用防抖节流保证一次滑动中只保存一次状态:

const saveGlyphEditState = (options) => {// 保存状态saveState('编辑字形参数', [editStatus.value === Status.Glyph ? StoreType.EditGlyph : StoreType.EditCharacter],OpType.Undo,options,)
}let opTimer = null
let opstatus = false
watch(editGlyph, (newValue, oldValue) => {if (opTimer) {clearTimeout(opTimer)}opTimer = setTimeout(() => {opstatus = falseclearTimeout(opTimer)}, 500)if (!opstatus) {saveGlyphEditState({editGlyph: oldValue,})opstatus = true}
}, {deep: true,
})
更新状态

在执行撤销或重做操作时,需要更新状态,统一封装成函数:

const updateState = (record) => {for (let i = 0; i < record.opStores.length; i++) {const opStore = record.opStores[i]switch(opStore) {case StoreType.EditCharacter: {for (let i = 0; i < selectedFile.value.characterList.length; i++) {if (editCharacterFileUUID.value === selectedFile.value.characterList[i].uuid) {selectedFile.value.characterList[i] = record.states.editCharacterFile}}break}case StoreType.EditGlyph: {for (let i = 0; i < glyphs.value.length; i++) {if (glyphs.value[i].uuid === editGlyphUUID.value) {glyphs.value[i] = record.states.editGlyph}}for (let i = 0; i < radical_glyphs.value.length; i++) {if (radical_glyphs.value[i].uuid === editGlyphUUID.value) {radical_glyphs.value[i] = record.states.editGlyph}}for (let i = 0; i < stroke_glyphs.value.length; i++) {if (stroke_glyphs.value[i].uuid === editGlyphUUID.value) {stroke_glyphs.value[i] = record.states.editGlyph}}for (let i = 0; i < comp_glyphs.value.length; i++) {if (comp_glyphs.value[i].uuid === editGlyphUUID.value) {comp_glyphs.value[i] = record.states.editGlyph}}break}case StoreType.Tools: {tool.value = record.states.toolbreak}case StoreType.Status: {editStatus.value = record.states.editStatusbreak}case StoreType.Pen: {pointsPen.value = record.states.pointsPeneditingPen.value = record.states.editingPenselectAnchor.value = record.states.selectAnchorselectPenPoint.value = record.states.selectPenPointmousedownPen.value = record.states.mousedownPenmousemovePen.value = record.states.mousemovePenbreak}case StoreType.Polygon: {pointsPolygon.value = record.states.pointsPolygoneditingPolygon.value = record.states.editingPolygonmousedownPolygon.value = record.states.mousedownPolygonmousemovePolygon.value = record.states.mousemovePolygonbreak}case StoreType.Rectangle: {editingRectangle.value = record.states.editingRectanglerectX.value = record.states.rectXrectY.value = record.states.rectYrectWidth.value = record.states.rectWidthrectHeight.value = record.states.rectHeightbreak}case StoreType.Ellipse: {editingEllipse.value = record.states.editingEllipseellipseX.value = record.states.ellipseYellipseY.value = record.states.ellipseXradiusX.value = record.states.radiusXradiusY.value = record.states.radiusYbreak}}}
}
撤销操作

每次执行撤销操作时,需要将记录保存至重做操作列表(redoStack),然后更新状态。

const undo = () => {if (!undoStack.length) returnconst record = undoStack[undoStack.length - 1]if (record.options.undoTip) {ElMessageBox.confirm(record.options.undoTip,`撤销${record.opName}`,{confirmButtonText: '确定',cancelButtonText: '取消',type: 'warning',}).then(() => {undoStack.pop()saveState(record.opName, record.opStores, OpType.Redo, record.options)updateState(record)ElMessage({type: 'success',message: `撤销${record.opName}`,})}).catch(() => {})} else {undoStack.pop()saveState(record.opName, record.opStores, OpType.Redo, record.options)updateState(record)}
}
重做操作

每次执行重做操作时,需要先更新状态,然后将记录保存至撤销操作列表(undoStack)。

const redo = () => {if (!redoStack.length) returnconst record = redoStack[redoStack.length - 1]if (record.options.redoTip) {ElMessageBox.confirm(record.options.redoTip,`重做${record.opName}`,{confirmButtonText: '确定',cancelButtonText: '取消',type: 'warning',}).then(() => {redoStack.pop()updateState(record)saveState(record.opName, record.opStores, OpType.Undo, record.options)ElMessage({type: 'success',message: `重做${record.opName}`,})}).catch(() => {})} else {redoStack.pop()updateState(record)saveState(record.opName, record.opStores, OpType.Undo, record.options)}
}
清空操作栈
const clearState = () => {redoStack.length = 0undoStack.length = 0
}

相关文章:

字玩FontPlayer开发笔记12 Vue3撤销重做功能

字玩FontPlayer开发笔记12 Vue3撤销重做功能 字玩FontPlayer是笔者开源的一款字体设计工具&#xff0c;使用Vue3 ElementUI开发&#xff0c;源代码&#xff1a;github | gitee 笔记 撤销重做功能是设计工具必不可少的模块&#xff0c;以前尝试使用成熟的库实现撤销重做功能…...

无人机图传模块:深入理解其工作原理与实际效用

无人机图传模块作为无人机系统的关键组成部分&#xff0c;承担着将无人机拍摄的图像和视频实时传输至地面控制站或接收设备的重任。本文将深入探讨无人机图传模块的工作原理及其在实际应用中的效用&#xff0c;帮助读者更好地理解这一技术的奥秘。 一、无人机图传模块的工作原…...

PDF文件提取开源工具调研总结

概述 PDF是一种日常工作中广泛使用的跨平台文档格式&#xff0c;常常包含丰富的内容&#xff1a;包括文本、图表、表格、公式、图像。在现代信息处理工作流中发挥了重要的作用&#xff0c;尤其是RAG项目中&#xff0c;通过将非结构化数据转化为结构化和可访问的信息&#xff0…...

Linux(Centos 7.6)命令详解:dos2unix

1.命令作用 将Windows格式文件件转换为Unix、Linux格式的文件(也可以转换成其他格式的) 2.命令语法 Usage: dos2unix [options] [file ...] [-n infile outfile ...] 3.参数详解 options: -c, --convmode&#xff0c;转换方式&#xff0c;支持ascii, 7bit, iso, mac,默认…...

梯度提升决策树树(GBDT)公式推导

### 逻辑回归的损失函数 逻辑回归模型用于分类问题&#xff0c;其输出是一个概率值。对于二分类问题&#xff0c;逻辑回归模型的输出可以表示为&#xff1a; \[ P(y 1 | x) \frac{1}{1 e^{-F(x)}} \] 其中 \( F(x) \) 是一个线性组合函数&#xff0c;通常表示为&#xff…...

跨域问题分析及解决方案

1、跨域 指的是浏览器不能执行其他网站的脚本。它是由浏览器的同源策略造成的&#xff0c;是浏览器对javascript施加的安全限制。 2、同源策略&#xff1a;是指协议&#xff0c;域名&#xff0c;端口都要相同&#xff0c;其中有一个不同都会产生跨域&#xff1b; 3、跨域流程…...

【三国游戏——贪心、排序】

题目 代码 #include <bits/stdc.h> using namespace std; using ll long long; const int N 1e510; int a[N], b[N], c[N]; int w[4][N]; int main() {int n;cin >> n;for(int i 1; i < n; i)cin >> a[i];for(int i 1; i < n; i)cin >> b[i…...

深入理解 Java 的数据类型与运算符

Java学习资料 Java学习资料 Java学习资料 在 Java 编程中&#xff0c;数据类型与运算符是构建程序的基础元素。它们决定了数据在程序中的存储方式以及如何对数据进行各种操作。 一、数据类型 &#xff08;一&#xff09;基本数据类型 整型&#xff1a; 用于存储整数数值&…...

WOA-CNN-GRU-Attention、CNN-GRU-Attention、WOA-CNN-GRU、CNN-GRU四模型对比多变量时序预测

WOA-CNN-GRU-Attention、CNN-GRU-Attention、WOA-CNN-GRU、CNN-GRU四模型对比多变量时序预测 目录 WOA-CNN-GRU-Attention、CNN-GRU-Attention、WOA-CNN-GRU、CNN-GRU四模型对比多变量时序预测预测效果基本介绍程序设计参考资料 预测效果 基本介绍 基于WOA-CNN-GRU-Attention、…...

(二叉树)

我们今天就开始引进一个新的数据结构了&#xff1a;我们所熟知的&#xff1a;二叉树&#xff1b; 但是我们在引进二叉树之前我们先了解一下树&#xff1b; 树 树的概念和结构&#xff1a; 树是⼀种⾮线性的数据结构&#xff0c;它是由 n &#xff08; n>0 &#xff09; …...

Linux shell 批量验证端口连通性

脚本 #!/bin/bash # #database check #set -o nounset LOCALIPifconfig | grep inet | head -1 | awk {print $2} | sed s/addr\:// IPLIST192.168.1.99 192.168.1.98 192.168.1.97 PORTLIST81 82 83 84 85 86 check_nc(){ for CHECK_IP in $IPLIST dofor CHECK_PORT in $PORT…...

Java 中实体类与操作类分离

目录 一、为啥要把实体类和操作类分开 二、实体类长啥样&#xff0c;怎么用 三、操作类的使命与实现 四、实战演练&#xff1a;实体类与操作类协同工作 五、拓展思考&#xff1a;这种分离带来的好处与进一步优化 六、总结与展望 家人们&#xff0c;今天我想跟你们唠唠我在…...

创建 pdf 合同模板

创建 pdf 合同模板 一、前言二、模板展示三、制作过程 一、前言 前段时间要求创建“pdf”模板&#xff0c;学会了后感觉虽然简单&#xff0c;但开始也折腾了好久&#xff0c;这里做个记录。 二、模板展示 要创建这样的模板 三、制作过程 新建一个“Word”&#xff0c;这里命…...

【Prometheus】PromQL进阶用法

✨✨ 欢迎大家来到景天科技苑✨✨ &#x1f388;&#x1f388; 养成好习惯&#xff0c;先赞后看哦~&#x1f388;&#x1f388; &#x1f3c6; 作者简介&#xff1a;景天科技苑 &#x1f3c6;《头衔》&#xff1a;大厂架构师&#xff0c;华为云开发者社区专家博主&#xff0c;…...

BOBO小火炬全套源码XE修复版2025(火炬天花板二次开发版)

《小火炬全套源码 传奇游戏源码讲解》 小火炬全套源码是一种用于开发经典传奇类游戏的源码包。传奇游戏作为一款经典的多人在线角色扮演游戏&#xff08;MMORPG&#xff09;&#xff0c;有着庞大的用户基础和强大的游戏生态。小火炬全套源码主要提供了从基础架构到核心功能的完…...

【基于无线电的数据通信链】Link 11 仿真测试

〇、废话 Link 11 仿真测试 涉及多个方面&#xff0c;包括信号仿真、协议模拟、数据链路层的仿真以及网络性能评估等。Link 11 是一种基于 HF&#xff08;高频&#xff09; 或 UHF&#xff08;超高频&#xff09; 波段的无线通信协议&#xff0c;主要用于军事通信系统中。为了…...

WPF实战案例 | C# WPF实现计算器源码

WPF实战案例 | C# WPF实现计算器源码 一、设计来源计算器应用程序讲解1.1 主界面1.2 计算界面 二、效果和源码2.1 界面设计&#xff08;XAML&#xff09;2.2 代码逻辑&#xff08;C#&#xff09;2.3 实现步骤总结 源码下载更多优质源码分享 作者&#xff1a;xcLeigh 文章地址&a…...

WebSocket 和 Socket 的区别

一、协议层次和工作方式 1.1 &#xff09;Socket 1.1.1&#xff09;Socket位于传输层&#xff0c;通常使用TCP或UDP协议 1.1.2&#xff09;提供了一个通用的网络编程接口&#xff0c;允许应用程序通过它发送和接收数据 1.1.3&#xff09;一般需要手动管理连接&#xff0c;错…...

Matlab自学笔记四十五:日期时间型和字符、字符串以及double型的相互转换方法

1.说明 在Matlab中&#xff0c;大多数函数都有这样的功能&#xff1a;创建函数本身具有转换的功能&#xff0c;例如double函数&#xff0c;可以创建双精度浮点数&#xff0c;也可以把输入参数转换成双精度浮点数&#xff0c;再例如string&#xff0c;可以创建字符串&#xff0…...

Python基础学习(六)unittest 框架

1.介绍 是 Python自带的单元测试框架 - 自带的, 可以直接使用, 不需要单外安装 - 测试人员&#xff0c;用来做自动化测试, 作为自动化测试的执行框架,即管理和执行用例的 核心要素&#xff1a; TestCase 测试用例, 这个测试用例是 unittest 的组成部分,作用是用来书写真正的…...

Python数据可视化(够用版):懂基础 + 专业的图表抛给Tableau等专业绘图工具

我先说说文章标题中的“够用版”啥意思&#xff0c;为什么这么写。 按照我个人观点&#xff0c;在使用Python进行数据分析时&#xff0c;我们有时候肯定要结合到图表去进行分析&#xff0c;去直观展现数据的规律和特定&#xff0c;那么我们肯定要做一些简单的可视化&#xff0…...

麒麟操作系统服务架构保姆级教程(十三)tomcat环境安装以及LNMT架构

如果你想拥有你从未拥有过的东西&#xff0c;那么你必须去做你从未做过的事情 之前咱们学习了LNMP架构&#xff0c;但是PHP对于技术来说确实是老掉牙了&#xff0c;PHP的市场占有量越来越少了&#xff0c;我认识一个10年的PHP开发工程师&#xff0c;十年工资从15k到今天的6k&am…...

Docker集成onlyoffice实现预览功能

1.拉取镜像 docker pull onlyoffice/documentserver 2. 数据卷挂载 mkdir -p app/onlyoffice/DocumentServer/logs mkdir -p app/onlyoffice/DocumentServer/data mkdir -p app/onlyoffice/DocumentServer/lib mkdir -p app/onlyoffice/DocumentServer/db 3.运行容器 docker ru…...

Flowable 管理各业务流程:流程设计器 (获取流程模型 XML)、流程部署、启动流程、流程审批、流程挂起和激活、任务分配

文章目录 引言I 表结构主要表前缀及其用途核心表II 流程设计器(Flowable BPMN模型编辑器插件)Flowable-UIvue插件III 流程部署部署步骤例子:根据流程模型ID部署IV 启动流程启动步骤ACT_RE_PROCDEF:流程定义相关信息例子:根据流程 ID 启动流程V 流程审批审批步骤Flowable 审…...

BladeDISC++:Dynamic Shape AI 编译器下的显存优化技术

近年来&#xff0c;随着深度学习技术的迅猛发展&#xff0c;越来越多的模型展现出动态特性&#xff0c;这引发了对动态形状深度学习编译器(Dynamic Shape AI Compiler)的广泛关注。本文将介绍阿里云 PAI 团队近期发布的 BladeDISC项目&#xff0c;探讨在动态场景下如何优化深度…...

FFmpeg常用命令

文章目录 一、 FFmpeg 音视频的处理流程二、FFmpeg 常用命令2.1、查看本机支持的采集设备2.2、 录制视频2.2.1、原始视频2.2.2、编码的视频 2.3、录制音频&#xff1a;2.3.1、原始音频2.3.2、编码的音频 2.4、录制音视频&#xff1a;2.5、文件格式转换&#xff1a;2.6、提取音频…...

http请求开启长连接导致请求偶发失败

问题描述&#xff1a; http长连接的意思是服务器为了调用时减少TCP三次握手开销&#xff0c;会复用之前已经发起的请求&#xff0c;比较适合频繁交互&#xff08;比如数据推送、流水线操作等&#xff09;的场景&#xff0c;但是如果超过服务器配置的连接最大空闲时间&#xff0…...

JUnit单元测试

单元测试 就是针对最小的功能单元&#xff08;方法&#xff09;&#xff0c;编写测试代码对其正确性进行测试 JUnit 最流行的java测试框架之一&#xff0c;方柏霓进行单元测试 入门程序 使用Junit&#xff0c;对UserService的方法进行单元测试 1.在pom.xml中&#xff0c;…...

智慧公安(实景三维公安基层基础平台)建设方案——第4章

4 建设内容 4.1 标准规范体系 在国家和地方公安基层信息化标准规范的基础上,结合项目实际情况,制定标准规范及管理制度,构建统一的标准规范体系,以便更好地实现公安基层基础信息的高度共享、平台运行的统一协调、业务流程最优化。主要包括以下内容: 1. 业务标准规范 (…...

LLMs(大型语言模型)的多智能体:Auto-GPT

LLMs(大型语言模型)的多智能体:Auto-GPT 是指在一个系统中集成多个具有不同能力、角色和任务的智能体,这些智能体能够相互协作、沟通和交互,以共同完成复杂的任务或解决复杂的问题。每个智能体都可以被视为一个独立的实体,具有自己的策略、目标和知识库,通过相互之间的…...

《Effective Java》学习笔记——第2部分 对象通用方法最佳实践

文章目录 第2部分 所有对象通用方法一、前言二、最佳实践内容1. equals()方法2. hashCode()方法3. toString() 方法4. clone() 方法5. finalize() 方法6. compareTo()方法&#xff08;实现 Comparable 接口&#xff09; 三、小结 第2部分 所有对象通用方法 一、前言 《Effect…...

2024年智慧消防一体化安全管控年度回顾与2025年预测

随着科技的飞速发展&#xff0c;智慧营区一体化安全管控在2024年取得了显著进展&#xff0c;同时也为2025年的发展奠定了坚实基础。 2024年年度回顾 政策支持力度持续加大&#xff1a;国家对消防安全的重视程度不断提高&#xff0c;出台了一系列涵盖技术创新、市场应用、人才培…...

艺术家迟首飞在特殊历史时刻展现中国艺术力量

艺术家迟首飞在特殊历史时刻展现中国艺术力量 艺术创作的边界正被不断拓展。中国艺术家迟首飞以其纪实视野&#xff0c;将传统与现代元素巧妙融合&#xff0c;展现全球艺坛力量&#xff0c;创作出一系列精彩作品。尤其是《平安兔》《福》与TikTok标志的结合的作品&#xff0c;…...

探索微服务架构:从单体应用到微服务的转变

引言 随着互联网业务的日益复杂和用户需求的快速增长&#xff0c;软件开发的架构模式也在不断演进。从最早的单体应用架构到后来的分层架构&#xff0c;再到如今备受关注的微服务架构&#xff0c;每一种架构模式都试图解决软件开发中的不同挑战。尤其是在现代互联网企业中&…...

MongoDB vs Redis:相似与区别

前言 在当今的数据库领域&#xff0c;MongoDB 和 Redis 都是备受关注的非关系型数据库&#xff08;NoSQL&#xff09;&#xff0c;它们各自具有独特的优势和适用场景。本文将深入探讨 MongoDB 和 Redis 的特点&#xff0c;并详细对比它们之间的相似之处和区别&#xff0c;帮助…...

Jenkins-pipeline语法说明

一. 简述&#xff1a; Jenkins Pipeline 是一种持续集成和持续交付&#xff08;CI/CD&#xff09;工具&#xff0c;它允许用户通过代码定义构建、测试和部署流程。 二. 关于jenkinsfile&#xff1a; 1. Sections部分&#xff1a; Pipeline里的Sections通常包含一个或多个Direc…...

MySQL(3)运算符、排序与分页

运算符 一、算术运算符 加减乘除余 举例&#xff1a; SELECT 1001 FROM DUAL; 结果为101&#xff0c;与java中的连接字符串不同。 SELECT 100A FROM DUAL; 结果为100。 也可以理解为&#xff0c;遇到非数值类型时&#xff0c;先转换为数值类型&#xff08;如2可以转换…...

Kafka面试题----Kafka消息是采用Pull模式,还是Push模式

Pull 模式为主 消费者主动拉取&#xff1a;Kafka 中的消费者是基于 Pull 模式来获取消息的。消费者通过向 Kafka 集群发送拉取请求&#xff0c;主动地从 Broker 中获取消息。这种方式使得消费者可以根据自身的消费能力和处理速度来灵活地控制消息的拉取频率和数量&#xff0c;…...

BLE透传方案,IoT短距无线通信的“中坚力量”

在物联网&#xff08;IoT&#xff09;短距无线通信生态系统中&#xff0c;低功耗蓝牙&#xff08;BLE&#xff09;数据透传是一种无需任何网络或基础设施即可完成双向通信的技术。其主要通过简单操作串口的方式进行无线数据传输&#xff0c;最高能满足2Mbps的数据传输速率&…...

借助 .pth 文件完成多个 Python 解释器的合并

相关搜索 conda 虚拟环境如何使用 ROS 的 Python 模块conda 虚拟环境找不到 catkin_pkg 问题描述 如果你在 Ubuntu 20.04 中装了 conda&#xff0c;那么你的 Ubuntu 会有这些 Python 解释器&#xff1a; /usr/bin/python3&#xff1a;系统的解释器 (版本为 3.8.10&#xff0…...

今天也是记录小程序进展的一天(破晓时8)

嗨嗨嗨朋友们&#xff0c;今天又来记录一下小程序的进展啦&#xff01;真是太激动了&#xff0c;项目又迈出了重要的一步&#xff0c;231啦&#xff01;感觉每一步的努力都在积累&#xff0c;功能逐渐完善&#xff0c;离最终上线的目标越来越近了。大家一直支持着这个项目&…...

python高级加密算法AES对信息进行加密和解密

AES&#xff08;高级加密标准&#xff09;是一种广泛使用的对称加密算法&#xff0c;它以字节为单位处理数据&#xff0c;将明文分组加密成密文。AES算法的核心在于一个轮函数&#xff0c;该函数会对数据执行多次变换&#xff0c;包括字节代换、行移位、列混合和轮密钥加。这些…...

# [Unity]【游戏开发】 脚本生命周期与常见事件方法

在Unity中,脚本的生命周期是指脚本从创建到销毁的整个过程,以及在此过程中触发的各类事件。掌握脚本生命周期对优化游戏开发过程和避免性能问题至关重要。本文将详细探讨脚本生命周期的关键事件、常见的事件方法,并通过实例说明如何在合适的时机执行脚本逻辑,以确保游戏的流…...

《探秘鸿蒙Next:非结构化数据处理与模型轻量化的完美适配》

在鸿蒙Next的人工智能应用场景中&#xff0c;处理非结构化数据并使其适配模型轻量化需求是一项关键且具有挑战性的任务。以下是一些有效的方法和策略。 数据预处理 数据清洗&#xff1a;非结构化数据中往往存在噪声、重复和错误数据。对于文本数据&#xff0c;要去除乱码、特殊…...

Spring Boot框架下的上海特产销售商城网站开发之旅

摘要 本项目基于Spring Boot框架开发&#xff0c;旨在创建一个网络上海特产销售商城网站。在黄菊华老师的指导下&#xff0c;该项目不仅涵盖了核心代码讲解和答辩指导&#xff0c;还提供了详尽的开发文档、开题报告、任务书及PPT等毕业设计辅导材料。黄老师是《Vue.js入门与商城…...

HTML 基础入门:核心标签全解析

在网页开发的世界里&#xff0c;HTML&#xff08;超文本标记语言&#xff09;是基石般的存在。它负责构建网页的基本结构&#xff0c;为用户呈现出丰富多样的内容。今天&#xff0c;就让我们一起深入了解 HTML 中几个极为关键的基础标签&#xff0c;开启网页创作的第一步。 一…...

Docker基础安装与使用

Docker 简介 Docker 是一个开源的容器化平台&#xff0c;用于开发、部署和运行应用程序。它通过将应用程序及其依赖项打包到一个轻量级的、可移植的容器中&#xff0c;实现了应用程序的快速部署和跨环境一致性。 Docker 的核心概念 容器&#xff08;Container&#xff09;&a…...

基于Docker的Spark分布式集群

目录 1. 说明 2. 服务器规划 3. 步骤 3.1 要点 3.2 配置文件 3.2 访问Spark Master 4. 使用测试 5. 参考 1. 说明 以docker容器方式实现apache spark计算集群&#xff0c;能灵活的增减配置与worker数目。 2. 服务器规划 服务器 (1master, 3workers) ip开放端口备注ce…...

物业管理软件引领智能社区高效服务与管理创新

内容概要 物业管理软件是在智能社区建设中不可或缺的重要工具。随着城市化进程的加速&#xff0c;社区管理的复杂性也在不断上升&#xff0c;如何提高服务效率和管理水平&#xff0c;已经成为物业公司面临的主要挑战。在这样的背景下&#xff0c;物业管理软件以其强大的功能和…...

NoETL | 数据虚拟化如何在数据不移动的情况下实现媲美物理移动的实时交付?

在我们之前的文章中&#xff0c;我们回顾了Denodo在逻辑数据仓库和逻辑数据湖场景中所使用的主要优化技术&#xff08;具体内容请参阅之前的文章&#xff09;。 数据架构 | 逻辑数据仓库与物理数据仓库性能对比_物理数仓、逻辑数仓-CSDN博客文章浏览阅读1.5k次&#xff0c;点赞…...