react中使用react-konva实现画板框选内容
文章目录
- 一、前言
- 1.1、`API`文档
- 1.2、`Github`仓库
- 二、图形
- 2.1、拖拽`draggable`
- 2.2、图片`Image`
- 2.3、变形`Transformer`
- 三、实现
- 3.1、依赖
- 3.2、源码
- 3.2.1、`KonvaContainer`组件
- 3.2.2、`use-key-press`文件
- 3.3、效果图
- 四、最后
一、前言
本文用到的react-konva
是基于react
封装的图形绘制。Konva
是一个HTML5 Canvas JavaScript
框架,它通过对 2d context
的扩展实现了在桌面端和移动端的可交互性。Konva
提供了高性能的动画,补间,节点嵌套,布局,滤镜,缓存,事件绑定(桌面/移动端)等等功能。你可以使用 Konva
在舞台上绘制图形,给图形添加事件,移动、缩放和旋转图形并且支持高性能的动画即使包含数千个图形。
1.1、API
文档
英文文档点击【前往】,中文文档点击【前往】
1.2、Github
仓库
点击【前往】访问Github
仓库,在线示例地址,点击【前往】
二、图形
在线制图最基础的应用是拖拽元素,比如,在画布上拖拽一张图片或某种形状,对该图片进行缩放或旋转操作。
画布就是<Stage>
,每个图层为<Layer>
。
2.1、拖拽draggable
konva
中内置了很多形状的元素,比如圆形、矩形等,以下示例为星型,这里先用<Star>
试一下:
import Konva from 'konva'
import { Circle, Rect, Stage, Layer, Text, Star } from 'react-konva'const Shape = () => {const [star, setStar] = useState({x: 300,y: 300,rotation: 20,isDragging: false,})const handleDragStart = () => {setStar({...star,isDragging: true,})}const handleDragEnd = (e: any) => {setStar({...star,x: e.target.x(),y: e.target.y(),isDragging: false,})}return (<Stage width={1000} height={600}><Layer><Starkey="starid"id="starid"x={star.x}y={star.y}numPoints={5}innerRadius={20}outerRadius={40}fill="#89b717"opacity={0.8}draggablerotation={star.rotation}shadowColor="black"shadowBlur={10}shadowOpacity={0.6}shadowOffsetX={star.isDragging ? 10 : 5}shadowOffsetY={star.isDragging ? 10 : 5}scaleX={star.isDragging ? 1.2 : 1}scaleY={star.isDragging ? 1.2 : 1}onDragStart={handleDragStart}onDragEnd={handleDragEnd}/></Layer></Stage>)
}
其中,可以给 Star
配置一些基础的属性,如:x
、y
指该元素在画布上的坐标位置,rotaition
指元素的旋转角度;fill
指元素的填充颜色,scaleX
、scaleY
指元素在 x
、y
轴上的放大比例等等。
在拖拽的时候,我们要给该元素添加一些拖拽事件,如上:添加 handleDragStart
更改isDragging
属性,使其在拖动时产生形变;添加 onDragEnd
事件,更改isDragging
和 x
、y
属性,来改变拖动位置,关闭拖动形变特效等。
观察上面的代码发现某些属性和react-dnd
类似,但在使用 drag
事件的时候,发现比 react-dnd
方便很多,可能因为底层是 canvas
的原因吧!
2.2、图片Image
有两种方式可以导入图片,一个是用 react-hooks
,一个是调用 react
生命周期函数,这里为了图省事,用 hooks
。
先安装 konva
的官方库use-image
,use-image
提供好了跨域属性anonymous
,封装一下图片组件:
import { Image } from 'react-konva'
import useImage from 'use-image'const KonvaImage = ({ url = '' }) => {const [image] = useImage(url, 'anonymous')return <Image image={image} />
}export default KonvaImage
如果仍显示跨域问题不能生成图片,需要在服务器端添加跨域头或者做一层转发了。
2.3、变形Transformer
元素变形,需要引用 konva
的Transformer
组件,该组件可以使元素的缩放、旋转。如下代码,在选中某元素后,会展示 Transformer
组件,在该组件上存在boundBoxFunc
属性,当用户触发元素的变形行为时,该函数会被调用,返回一个包含形变后元素的信息(下面代码中为 newBox
)。
import React, { useState, useEffect, useRef } from 'react'
import { Image, Transformer } from 'react-konva'
import Konva from 'konva'
import useImage from 'use-image'const KonvaImage = ({ url = '', isSelected = false }) => {const [image] = useImage(url)const imgRef = useRef()const trRef = useRef()useEffect(() => {if (isSelected) {trRef.current.nodes([imgRef.current])trRef.current.getLayer().batchDraw()}}, [isSelected])return (<><Image image={image} draggable ref={imgRef} />{isSelected && (<Transformerref={trRef}boundBoxFunc={(oldBox, newBox) => {// limit resizeif (newBox.width < 5 || newBox.height < 5) {return oldBox}const { width, height } = newBox// console.log('width', width);// console.log('height', height);return newBox}}/>)}</>)
}export default KonvaImage
三、实现
3.1、依赖
安装如下所需依赖:
npm install react-konva konva use-image --save
3.2、源码
3.2.1、KonvaContainer
组件
KonvaContainer
图片框选区域组件源码如下所示:
/*** @Description: KonvaContainer图片框选区域组件* @props url 需要框选的图片的URL地址* @props width 宽度* @props height 高度* @props defaultValue 默认框选起来区域的数据* @onChange 回调方法,通知父组件框选的内容信息* @author 小马甲丫* @date 2023-12-05 03:22:27
*/
import React from 'react';
import useImage from 'use-image';
import { Stage, Layer, Rect, Image, Transformer } from 'react-konva';
import useKeyPress from '@/hooks/use-key-press';/*** 框选的图片* @param url* @constructor*/
const BackgroundImage = ({ url }) => {const [image] = useImage(url);return <Image image={image} />;
};/*** 背景白板* @param width* @param height* @constructor*/
const BackgroundWhite = ({ width, height }) => {return (<Rectx={0}y={0}width={width}height={height}fill="#fff"id="rectangleBg"name="rectangleBg"/>);
};/*** 框选出来的框* @param canvas* @param shapeProps* @param onSelect* @param onChange* @constructor*/
const Rectangle = ({ canvas, shapeProps, onSelect, onChange }) => {const shapeRef = React.useRef();return (<RectonClick={() => onSelect(shapeRef)}onTap={() => onSelect(shapeRef)}ref={shapeRef}{...shapeProps}name="rectangle"draggableonMouseOver={() => {document.body.style.cursor = 'move';}}onMouseOut={() => {document.body.style.cursor = 'default';}}onDragEnd={(e) => {onChange({...shapeProps,x: e.target.x(),y: e.target.y(),});}}dragBoundFunc={(pos) => {const shapeWidth = shapeRef.current.attrs.width;const shapeHeight = shapeRef.current.attrs.height;let x = pos.x;if (x <= 0) {x = 0;} else if (x + shapeWidth >= canvas.width) {x = canvas.width - shapeWidth;}let y = pos.y;if (y < 0) {y = 0;} else if (y + shapeHeight > canvas.height) {y = canvas.height - shapeHeight;}return {x,y,};}}onTransformEnd={() => {// transformer is changing scale of the node// and NOT its width or height// but in the store we have only width and height// to match the data better we will reset scale on transform endconst node = shapeRef.current;const scaleX = node.scaleX();const scaleY = node.scaleY();// we will reset it backnode.scaleX(1);node.scaleY(1);onChange({...shapeProps,x: node.x(),y: node.y(),// set minimal valuewidth: Math.max(5, node.width() * scaleX),height: Math.max(node.height() * scaleY),});}}/>);
};/*** 主容器* @param props* @constructor*/
const KonvaContainer = (props) => {const [imageObject, setImageObject] = React.useState({width: props.width,height: props.height,url: props.url,});const [rectanglesField, setRectanglesField] = React.useState([]);const [selectedId, selectShape] = React.useState(null);const trRef = React.useRef();const layerRef = React.useRef();const Konva = window.Konva;const hideTransformer = () => {trRef.current.nodes([]);};/*** 初始化框选框* @param list*/const initRectangles = (list) => {const rects = list.map((item, index) => ({...item,id: `rect_${index}`,fill: 'rgb(160, 76,4, 0.3)',}));setRectanglesField(rects);};/*** 监听prop值变换*/React.useEffect(() => {const {url = '',width = 0,height = 0,defaultValue = [],} = props || {};setImageObject({width,height,url,});hideTransformer();// 图片地址不一致说明变更图片,需要重置选框if (url !== imageObject.url) {setRectanglesField([]);selectShape(null);}initRectangles(defaultValue);}, [props.url, props.width, props.height, props.defaultValue]);/*** 更新框选框数据* @param rects*/const updateRectangles = (rects) => {setRectanglesField(rects);props.onChange(rects);};/*** 添加框选框*/const addRec = () => {const data = rectanglesField;const rects = data.slice();const id = `rect_${rects.length}`;rects[rects.length] = {id,...getSelectionObj(),};updateRectangles(rects);selectShape(id);};/*** 删除框选框*/const delRec = () => {const data = rectanglesField;const rects = data.slice().filter((rect) => rect.id !== selectedId);updateRectangles(rects);hideTransformer();document.body.style.cursor = 'default';selectShape(null);};const selectionRectRef = React.useRef();const selection = React.useRef({visible: false,x1: 0,y1: 0,x2: 0,y2: 0,});/*** 高亮框选框* @param id*/const activeTransformer = (id) => {const activeRect =layerRef.current.find('.rectangle').find((elementNode) => elementNode.attrs.id === id) ||selectionRectRef.current;trRef.current.nodes([activeRect]);};/*** useKeyPress监听键盘按键删除键del和返回键backspace* 8 返回键* 46 删除键*/useKeyPress([8, 46], (e) => {// disable click eventKonva.listenClickTap = false;if (e.target.style[0] === 'cursor') delRec();});/*** 获取选中的框选框的信息*/const getSelectionObj = () => {return {x: Math.min(selection.current.x1, selection.current.x2),y: Math.min(selection.current.y1, selection.current.y2),width: Math.abs(selection.current.x1 - selection.current.x2),height: Math.abs(selection.current.y1 - selection.current.y2),fill: 'rgb(160, 76,4, 0.3)',};};/*** 更新框选框*/const updateSelectionRect = () => {const node = selectionRectRef.current;node.setAttrs({...getSelectionObj(),visible: selection.current.visible,});node.getLayer().batchDraw();};/*** 开始绘制框选框* @param e*/const onMouseDown = (e) => {const isTransformer = e.target.findAncestor('Transformer');if (isTransformer) {return;}hideTransformer();const pos = e.target.getStage().getPointerPosition();selection.current.visible = true;selection.current.x1 = pos.x;selection.current.y1 = pos.y;selection.current.x2 = pos.x;selection.current.y2 = pos.y;updateSelectionRect();};/*** 绘制框选框中* @param e*/const onMouseMove = (e) => {if (!selection.current.visible) {return;}const pos = e.target.getStage().getPointerPosition();selection.current.x2 = pos.x;selection.current.y2 = pos.y;updateSelectionRect();};/*** 结束绘制框选框* @param e*/const onMouseUp = (e) => {// 点击Rect框时,会返回该Rect的id// 画框时鼠标在Rect上松开,会返回该Rect的idconst dragId = e.target.getId();if (!selection.current.visible) {return;}// 是否鼠标拖动,并且偏移量大于10时才算拖动。拖动Rect没有偏移量,画框才有偏移量const { current: { x1 = 0, x2 = 0, y1 = 0, y2 = 0 } = {} } = selection || {};const isMove = (x1 !== x2 && Math.abs(x1 - x2) > 10) || (y1 !== y2 && Math.abs(y1 - y2) > 10);// 点击后有拖动就添加Rect框,并且偏移量大于10时才算拖动if (isMove) {addRec();}// 设置可调节大小节点if (!!dragId && !isMove) {// 点击已有的Rect框才设置,并且拖动小于10,也就是没有拖动activeTransformer(dragId);} else if (isMove) {// 拖动大于10,生成新的Rect框activeTransformer();}selection.current.visible = false;// disable click eventKonva.listenClickTap = false;updateSelectionRect();};return (<Stagewidth={imageObject.width}height={imageObject.height}onMouseDown={onMouseDown}onMouseUp={onMouseUp}onMouseMove={onMouseMove}><Layer ref={layerRef}><BackgroundWhite {...imageObject} /><BackgroundImage {...imageObject} />{rectanglesField.map((rect, i) => {return (<Rectanglekey={i}getKey={i}canvas={imageObject}shapeProps={rect}isSelected={rect.id === selectedId}getLength={rectanglesField.length}onSelect={() => {selectShape(rect.id);}}onChange={(newAttrs) => {const rects = rectanglesField.slice();rects[i] = newAttrs;updateRectangles(rects);}}/>);})}<Transformerref={trRef}rotationSnaps={[0, 90, 180, 270]}keepRatio={false}anchorSize={4}anchorStroke='#a04c04'anchorFill="#fff"borderStroke='#a04c04'borderDash={[1, 1]}enabledAnchors={['top-left', 'top-right', 'bottom-left', 'bottom-right']}boundBoxFunc={(oldBox, newBox) => {// limit resize// newBox.rotation !== 0进入return oldBox,就可实现不让旋转if (newBox.width < 20 || newBox.height < 20) {return oldBox;}return newBox;}}/><Rect ref={selectionRectRef} /></Layer></Stage>);
};export default KonvaContainer;
3.2.2、use-key-press
文件
用到了下面这个hook
文件use-key-press
:
import { useCallback, useEffect, MutableRefObject } from 'react';type keyType = KeyboardEvent['keyCode'] | KeyboardEvent['key'];
type keyFilter = keyType | keyType[];
type EventHandler = (event: KeyboardEvent) => void;
type keyEvent = 'keydown' | 'keyup';
type BasicElement = HTMLElement | Element | Document | Window;
type TargetElement = BasicElement | MutableRefObject<null | undefined>;
type EventOptions = {events?: keyEvent[];target?: TargetElement;
};const modifierKey: any = {ctrl: (event: KeyboardEvent) => event.ctrlKey,shift: (event: KeyboardEvent) => event.shiftKey,alt: (event: KeyboardEvent) => event.altKey,meta: (event: KeyboardEvent) => event.metaKey,
};const defaultEvents: keyEvent[] = ['keydown'];/*** 判断对象类型* @param obj 参数对象* @returns String*/
function isType<T>(obj: T): string {return Object.prototype.toString.call(obj).replace(/^\[object (.+)\]$/, '$1').toLowerCase();
}/*** 获取当前元素* @param target TargetElement* @param defaultElement 默认绑定的元素*/
function getTargetElement(target?: TargetElement, defaultElement?: BasicElement) {if (!target) {return defaultElement;}if ('current' in target) {return target.current;}return target;
}/*** 按键是否激活* @param event 键盘事件* @param keyFilter 当前键*/
const keyActivated = (event: KeyboardEvent, keyFilter: any) => {const type = isType(keyFilter);const { keyCode } = event;if (type === 'number') {return keyCode === keyFilter;}const keyCodeArr = keyFilter.split('.');// 符合条件的长度let genLen = 0;// 组合键keyCodeArr.forEach((key) => {const genModifier = modifierKey[key];if ((genModifier && genModifier) || keyCode === key) {genLen++;}});return genLen === keyCodeArr.length;
};/*** 键盘按下预处理方法* @param event 键盘事件* @param keyFilter 键码集*/
const genKeyFormate = (event: KeyboardEvent, keyFilter: any) => {const type = isType(keyFilter);if (type === 'string' || type === 'number') {return keyActivated(event, keyFilter);}// 多个键if (type === 'array') {return keyFilter.some((item: keyFilter) => keyActivated(event, item));}return false;
};/*** 监听键盘按下/松开* @param keyCode* @param eventHandler* @param options*/
const useKeyPress = (keyCode: keyFilter,eventHandler?: EventHandler,options: EventOptions = {},
) => {const { target, events = defaultEvents } = options;const callbackHandler = useCallback((event) => {if (genKeyFormate(event, keyCode)) {typeof eventHandler === 'function' && eventHandler(event);}},[keyCode],);useEffect(() => {const el = getTargetElement(target, window)!;events.forEach((eventName) => {el.addEventListener(eventName, callbackHandler);});return () => {events.forEach((eventName) => {el.removeEventListener(eventName, callbackHandler);});};}, [keyCode, events, callbackHandler]);
};export default useKeyPress;
3.3、效果图
页面效果如下所示:
四、最后
本人每篇文章都是一字一句码出来,希望大佬们多提提意见。顺手来个三连击,点赞👍收藏💖关注✨。创作不易,给我打打气,加加油☕
相关文章:

react中使用react-konva实现画板框选内容
文章目录 一、前言1.1、API文档1.2、Github仓库 二、图形2.1、拖拽draggable2.2、图片Image2.3、变形Transformer 三、实现3.1、依赖3.2、源码3.2.1、KonvaContainer组件3.2.2、use-key-press文件 3.3、效果图 四、最后 一、前言 本文用到的react-konva是基于react封装的图形绘…...

es6 相关面试总结
1、es6 是什么 新一代的js 语言标准,对其核心做了升级优化,更加适合大型应用开发。 2、箭头函数优缺点 优点: 1.代码优化 2.this 指向不会变动,永远指向其父元素 缺点: 1.没有arguments 参数 2.不能通过 appl…...

【Hive】——数据仓库
1.1 数仓概念 数据仓库(data warehouse):是一个用于存储,分析,报告的数据系统 目的:是构建面向分析的集成化数据环境,分析结果为企业提供决策支持 特点: 数据仓库本身不产生任何数据…...

算法基础九
螺旋矩阵2 给你一个正整数 n ,生成一个包含 1 到 n2 所有元素,且元素按顺时针顺序螺旋排列的 n x n 正方形矩阵 matrix。 示例 1: 输入:n 3 输出:[[1,2,3],[8,9,4],[7,6,5]] 示例 2: 输入:n …...

QT-在ui界面中给QWidget增加Layout布局的两种方法
2023-12-05 QT-在ui界面中给QWidget增加Layout布局的两种方法 方式一 在UI界面,用拖拽的方式加入Layout方式二 用notepad软件打开.ui文件,手动加入Layout代码 目标:去除右下角红标,给tab标签增加Layout属性。 方式一 在UI界面&am…...

免费的网页数据抓取工具有哪些?【2024附下载链接】
在网络上,有许多网页数据抓取工具可供选择。本文将探讨其如何全网采集数据并支持指定网站抓取。我们将比较不同的数据采集工具,帮助您找到最适合您需求的工具。 网页数据抓取工具种类 在选择网页数据抓取工具之前,让我们先了解一下这些工具…...

报错:Parsed mapper file: ‘file mapper.xml 导致无法启动
报错 : Logging initialized using class org.apache.ibatis.logging.stdout.StdOutImpl adapter. Registered plugin: com.github.yulichang.interceptor.MPJInterceptor3b2c8bda Parsed mapper file: file [/Mapper.xml] application无法启动 我这边产生原因是项…...

Linux驱动开发学习笔记2《LED驱动开发试验》
目录 一、Linux下LED灯驱动原理 1.地址映射 二、硬件原理图分析 三、实验程序编写 1.LED 灯驱动程序编写 2.编写测试APP 四、运行测试 1.编译驱动程序和测试APP (1)编译驱动程序 (2)编译测试APP 2.运行测试 一、Linux下…...

hive数据库查看参数/hive查看当前环境配置
文章目录 一、hive查看当前环境配置命令 在一次hive数据库执行命令 set ngmr.exec.modecluster时,想看一下 ngmr.exec.mode参数原先的值是什么,所以写一下本篇博文,讲一下怎么查看hive中的参数。 一、hive查看当前环境配置命令 set &#…...

ajax中get和post的区别,datatype返回的数据类型有哪些?web开发中数据提交的几种方式,有什么区别。百度使用哪种方式?
在Ajax中,GET和POST是两种常见的HTTP请求方法。它们有以下区别: GET请求:使用GET请求时,参数数据会附加在URL的末尾,以查询字符串的形式发送给服务器。GET请求是幂等的,也就是说多次发送相同的GET请求&…...

STM32用flash保存参数实现平衡擦写的一种方法
#FLASH平衡擦写# 一、概述 简易示意图如下: 写参数前要擦除对应的扇区 全为0XFFFFFFFF操作的最小单位为32位 uint32_t; 当一块扇区写完时,将所有有用参数复制到第二块扇区,开始写新的参数,如果所有参数写完,又重第…...

Aho Corasick Algorithm
文章目录 前言介绍实现参考 前言 Aho Corasick Algorithm又叫AC自动机,该算法是一个匹配算法,用来匹配文本Text中多个patterns分别出现的次数; 我们定义n为patterns的总长度;m为Text的长度; 问题:在ahis…...

用户管理 --汇总
一、第一节课 1.1 本人写的 前端: 鱼皮 --> 用户中心 第1节课-CSDN博客 中期: 一、用户管理 第1节课中间-CSDN博客 后端: 一、用户管理-CSDN博客 其他的链接 亿图脑图MindMaster 1.2 优秀球友,推荐 Docs 另…...

Flutter视频播放器在iOS端和Android端都能实现全屏播放
Flutter开发过程中,对于视频播放的三方组件有很多,在Android端适配都挺好,但是在适配iPhone手机的时候,如果设置了UIInterfaceOrientationLandscapeLeft和UIInterfaceOrientationLandscapeRight都为false的情况下,无法…...

面试遇到的一些问题(二)
1、v-if v-show 区别,他们的生命周期区别 v-show: (类似于display:none/black 的切换)不管初始值是true 或false 都会进行渲染,状态改变也不会销毁和重新生成。不会影响生命周期 v-if : 是根据条件,dom进行删除插入操作。 依附于普通元素时:会触发父组件的beforeUpdate和u…...

JDK8新特性:Lambda表达式规则及用法,方法引用
目录 Lambda表达式是JDK8新增的一种语法格式 1.作用 2.用法规则: 3.方法引用 Lambda表达式是JDK8新增的一种语法格式 1.作用 简化匿名内部类的代码写法 Lambad用法前提:只能简化函数式接口(一般加有Funcationallnterface)&a…...

【GIS】JDK版本升级到17后,GeoServer的图层无法通过openLayer预览
JDK版本升级到17后,图层无法通过openLayer预览 1. 错误图示 终端输出的错误 网页端无法显示图层,并且输出错误提示 2.原因猜测 估计可能是由于java17的模块化,Java被分成了多个独立部署和运行的模块,这使得Java应用能够更快…...

vue 批量下载文件,不走后端接口的方法
今天ld提了一个需求,说页面的列表里面有要下载的地址,然后点击批量下载。我思索片刻,给出了代码 1.这个是列表页面的代码 <!-- 这个是列表页面的代码 --> <el-table :data"userListShow" align"center"border highlight-…...

科技云报道:AI+PaaS,中国云计算市场迎来新“变量”?
科技云报道原创。 没有小的市场,只有还没有被发现的大生意。 随着企业数字化转型的逐级深入,市场需求进一步向PaaS和SaaS层进发,使之成为公有云服务市场增长的主要动力。 根据IDC最新发布的报告显示,2022-2027五年间中国公有云…...

Windows Service Name重复问题
Windows Service Name重复问题 1,问题 2,打开命令提示符,管理员身份运行 3,输入命令:sc delete MYSQL57 4,验证一下,可以看见已经没有感叹号啦 ,可以看见已经没有感叹号啦...

BBS项目
一.BBS项目介绍 1.项目开发流程 项目立项 ------> 公司高层决定需求调研和分析 ------> 市场人员,技术人员参与 -需求文档说明开发部门开会 ------> 确定项目架构,技术选型,数据库设计UI,UD团队(产品经…...

Java基础——对象类型转换(向上、向下转型)
非继承关系的类之间对象类型不可以互相类型转换,只有继承关系才可以互相转换。 简单说,对象类型转换的前提要是继承关系。 对象类型转换分为:向上转型和向下转型。多态就是一种自动向上转型。 向上转型:子类对象用父类类型接收…...

期末速成数据库极简版【查询】(2)
目录 select数据查询----表 【1】筛选列 【2】where简单查询 【3】top-n/distinct/排序的查询 【4】常用内置函数 常用日期函数 常用的字符串函数 【5】模糊查询 【6】表数据操作——增/删/改 插入 更新 删除 【7】数据汇总 聚合 分类 🙂&#…...

2023年终总结-轻舟已过万重山
自我介绍 高考大省的读书人 白,陇西布衣,流落楚、汉。-与韩荆州书 我来自孔孟故里山东济宁,也许是小学时的某一天,我第一次接触到了电脑,从此对它产生了强烈的兴趣,高中我有一个愿望:成为一名计…...

手机号,邮箱,密码,验证码正则表达式[Java]
Util类: public abstract class RegexPatterns {/*** 手机号正则*/public static final String PHONE_REGEX "^1([38][0-9]|4[579]|5[0-3,5-9]|6[6]|7[0135678]|9[89])\\d{8}$";/*** 邮箱正则*/public static final String EMAIL_REGEX "^[a-zA-Z…...

普冉(PUYA)单片机开发笔记(7): ADC-轮询式多路采样
概述 应用中经常会有使用单片机进行模数转换的需求。PY32F003 具有 1 个 12 位的模拟数字转换器(ADC),今天我们一起来使用一下这个 ADC。 数据手册中对 ADC 简介如下。 SAR ADC:逐次逼近式 ADC,原理参见“参考链接&a…...

uniapp切换页面时报错问题
我们来看如下错误: 该错误的意思是不能切换到 tabbar 页面。tabbar页面通常是公共页面或者底部导航栏,如果我们用 navigateTo 或者 redirectTo 都不能实现页面切换。 我们有两种方式: 第一种是用 switchTab 来进行切换,但注意切…...

Nginx 简单入门操作
前言:之前的文章有些过就不罗嗦了。 Nginx 基础内容 是什么? Nginx 是一个轻量级的 HTTP 服务器,采用事件驱动、异步非阻塞处理方式的服务器,它具有极好的 IO 性能,常用于 HTTP服务器(包含动静分离)、正向代理、反向代理、负载均衡 等等. Nginx 和 Node.js 在很多方…...

ChatGPT是科学还是艺术?
OpenAI最近谈到GPT4变懒的问题,说“它更像是多人共同参与的艺术创作”,那到底大模型是科学还是艺术?...

线程及实现方式
一、线程 线程是一个基本的CPU执行单元,也是程序执行流的最小单位。引入线程之后,不仅是进程之间可以并发,进程内的各线程之间也可以并发,从而进一步提升了系统的并发度,使得一个进程内也可以并发处理各种任务&#x…...