React实现一个拖拽排序组件 - 支持多行多列、支持TypeScript、支持Flip动画、可自定义拖拽区域

一、效果展示

排序:

丝滑的Flip动画 

 自定义列数 (并且宽度会随着屏幕宽度自适应)

自定义拖拽区域:(扩展性高,可以全部可拖拽、自定义拖拽图标)

二、主要思路

Tip: 本代码的CSS使用Tailwindcss, 如果没安装的可以自行安装这个库,也可以去问GPT,让它帮忙改成普通的CSS版本的代码

1. 一些ts类型:

import { CSSProperties, MutableRefObject, ReactNode } from "react"
/**有孩子的,基础的组件props,包含className style children */
interface baseChildrenProps {/**组件最外层的className */className?: string/**组件最外层的style */style?: CSSProperties/**孩子 */children?: ReactNode
}
/**ItemRender渲染函数的参数 */
type itemProps<T> = {/**当前元素 */item: T,/**当前索引 */index: number,/**父元素宽度 */width: number/**可拖拽的盒子,只有在这上面才能拖拽。自由放置位置。提供了一个默认的拖拽图标。可以作为包围盒,将某块内容作为拖拽区域 */DragBox: (props: baseChildrenProps) => ReactNode
}
/**拖拽排序组件的props */
export interface DragSortProps<T> {/**组件最外层的className */className?: string/**组件最外层的style */style?: CSSProperties/**列表,拖拽后会改变里面的顺序 */list: T[]/**用作唯一key,在list的元素中的属性名,比如id。必须传递 */keyName: keyof T/**一行个数,默认1 */cols?: number/**元素间距,单位px,默认0 (因为一行默认1) */marginX?: number/**当列表长度变化时,是否需要Flip动画,默认开启 (可能有点略微的动画bug) */flipWithListChange?: boolean/**每个元素的渲染函数 */ItemRender: (props: itemProps<T>) => ReactNode/**拖拽结束事件,返回排序好的新数组,在里面自己调用setList */afterDrag: (list: T[]) => any
}

2. 使用事件委托

监听所有子元素的拖拽开始、拖拽中、拖拽结束事件,减少绑定事件数量的同时,还能优化代码。

/**拖拽排序组件 */
const DragSort = function <T>({list,ItemRender,afterDrag,keyName,cols = 1,marginX = 0,flipWithListChange = true,className,style,
}: DragSortProps<T>) {const listRef = useRef<HTMLDivElement>(null);/**记录当前正在拖拽哪个元素 */const nowDragItem = useRef<HTMLDivElement>();const itemWidth = useCalculativeWidth(listRef, marginX, cols);//使用计算宽度钩子,计算每个元素的宽度 (代码后面会有)const [dragOpen, setDragOpen] = useState(false); //是否开启拖拽 (鼠标进入指定区域开启)/**事件委托- 监听 拖拽开始 事件,添加样式 */const onDragStart: DragEventHandler<HTMLDivElement> = (e) => {if (!listRef.current) return;e.stopPropagation(); //阻止冒泡/**这是当前正在被拖拽的元素 */const target = e.target as HTMLDivElement;//设置被拖拽元素“留在原地”的样式。为了防止设置正在拖拽的元素样式,所以用定时器,宏任务更晚执行setTimeout(() => {target.classList.add(...movingClass); //设置正被拖动的元素样式target.childNodes.forEach((k) => (k as HTMLDivElement).classList?.add(...opacityClass)); //把子元素都设置为透明,避免影响}, 0);//记录当前拖拽的元素nowDragItem.current = target;//设置鼠标样式e.dataTransfer.effectAllowed = "move";};/**事件委托- 监听 拖拽进入某个元素 事件,在这里只是DOM变化,数据顺序没有变化 */const onDragEnter: DragEventHandler<HTMLDivElement> = (e) => {e.preventDefault(); //阻止默认行为,默认是不允许元素拖动到人家身上的if (!listRef.current || !nowDragItem.current) return;/**孩子数组,每次都会获取最新的 */const children = [...listRef.current.children];/**真正会被挪动的元素(当前正悬浮在哪个元素上面) */ //找到符合条件的父节点const realTarget = findParent(e.target as Element, (now) => children.indexOf(now) !== -1);//边界判断if (realTarget === listRef.current || realTarget === nowDragItem.current || !realTarget) {// console.log("拖到自身或者拖到外面");return;}//拿到两个元素的索引,用来判断这俩元素应该怎么移动/**被拖拽元素在孩子数组中的索引 */const nowDragtItemIndex = children.indexOf(nowDragItem.current);/**被进入元素在孩子数组中的索引 */const enterItemIndex = children.indexOf(realTarget);//当用户选中文字,然后去拖动这个文字时,就会触发 (可以通过禁止选中文字来避免)if (enterItemIndex === -1 || nowDragtItemIndex === -1) {console.log("若第二个数为-1,说明拖动的不是元素,而是“文字”", enterItemIndex, nowDragtItemIndex);return;}if (nowDragtItemIndex < enterItemIndex) {// console.log("向下移动");listRef.current.insertBefore(nowDragItem.current, realTarget.nextElementSibling);} else {// console.log("向上移动");listRef.current.insertBefore(nowDragItem.current, realTarget);}};/**事件委托- 监听 拖拽结束 事件,删除样式,设置当前列表 */const onDragEnd: DragEventHandler<HTMLDivElement> = (e) => {if (!listRef.current) return;/**当前正在被拖拽的元素 */const target = e.target as Element;target.classList.remove(...movingClass);//删除前面添加的 被拖拽元素的样式,回归原样式target.childNodes.forEach((k) => (k as Element).classList?.remove(...opacityClass));//删除所有子元素的透明样式/**拿到当前DOM的id顺序信息 */const ids = [...listRef.current.children].map((k) => String(k.id)); //根据id,判断到时候应该怎么排序//把列表按照id排序const newList = [...list].sort(function (a, b) {const aIndex = ids.indexOf(String(a[keyName]));const bIndex = ids.indexOf(String(b[keyName]));if (aIndex === -1 && bIndex === -1) return 0;else if (aIndex === -1) return 1;else if (bIndex === -1) return -1;else return aIndex - bIndex;});afterDrag(newList);//触发外界传入的回调函数setDragOpen(false);//拖拽完成后,再次禁止拖拽 };/**拖拽按钮组件 */  //只有鼠标悬浮在这上面的时候,才开启拖拽,做到“指定区域拖拽”const DragBox = ({ className, style, children }: baseChildrenProps) => {return (<divstyle={{ ...style }}className={cn("hover:cursor-grabbing", className)}onMouseEnter={() => setDragOpen(true)}onMouseLeave={() => setDragOpen(false)}>{children || <DragIcon size={20} color="#666666" />}</div>);};return (<divclassName={cn(cols === 1 ? "" : "flex flex-wrap", className)}style={style}ref={listRef}onDragStart={onDragStart}onDragEnter={onDragEnter}onDragOver={(e) => e.preventDefault()} //被拖动的对象被拖到其它容器时(因为默认不能拖到其它元素上)onDragEnd={onDragEnd}>{list.map((item, index) => {const key = item[keyName] as string;return (<div id={key} key={key} style={{ width: itemWidth, margin: `4px ${marginX / 2}px` }} draggable={dragOpen} className="my-1">{ItemRender({ item, index, width: itemWidth, DragBox })}</div>);})}</div>);
};

3. 使用Flip做动画

对于这种移动位置的动画,普通的CSS和JS动画已经无法满足了:

        可以使用Flip动画来做:FLIP是 First、Last、Invert和 Play四个单词首字母的缩写, 意思就是,记录一开始的位置、记录结束的位置、记录位置的变化、让元素开始动画 

        主要的思路为: 记录原位置、记录现位置、记录位移大小,最重要的点来了, 使用CSS的 transform ,让元素在被改动位置的一瞬间, translate 定位到原本的位置上(通过我们前面计算的位移大小), 然后给元素加上 过渡 效果,再让它慢慢回到原位即可。

        代码如下 (没有第三方库,基本都是自己手写实现)

        这里还使用了JS提供的 Web Animations API,具有极高的性能,不阻塞主线程。

        但是由于API没有提供动画完成的回调,故这里使用定时器做回调触发

/**位置的类型 */
interface position {x: number,y: number
}/**Flip动画 */
export class Flip {/**dom元素 */private dom: Element/**原位置 */private firstPosition: position | null = null/**动画时间 */private duration: number/**正在移动的动画会有一个专属的class类名,可以用于标识 */static movingClass = "__flipMoving__"constructor(dom: Element, duration = 500) {this.dom = domthis.duration = duration}/**获得元素的当前位置信息 */private getDomPosition(): position {const rect = this.dom.getBoundingClientRect()return {x: rect.left,y: rect.top}}/**给原始位置赋值 */recordFirst(firstPosition?: position) {if (!firstPosition) firstPosition = this.getDomPosition()this.firstPosition = { ...firstPosition }}/**播放动画 */play(callback?: () => any) {if (!this.firstPosition) {console.warn('请先记录原始位置');return}const lastPositon = this.getDomPosition()const dif: position = {x: lastPositon.x - this.firstPosition.x,y: lastPositon.y - this.firstPosition.y,}// console.log(this, dif);if (!dif.x && !dif.y) returnthis.dom.classList.add(Flip.movingClass)this.dom.animate([{ transform: `translate(${-dif.x}px, ${-dif.y}px)` },{ transform: `translate(0px, 0px)` }], { duration: this.duration })setTimeout(() => {this.dom.classList.remove(Flip.movingClass)callback?.()}, this.duration);}
}
/**Flip多元素同时触发 */
export class FlipList {/**Flip列表 */private flips: Flip[]/**正在移动的动画会有一个专属的class类名,可以用于标识 */static movingClass = Flip.movingClass/**Flip多元素同时触发 - 构造函数* @param domList 要监听的DOM列表* @param duration 动画时长,默认500ms*/constructor(domList: Element[], duration?: number) {this.flips = domList.map((k) => new Flip(k, duration))}/**记录全部初始位置 */recordFirst() {this.flips.forEach((flip) => flip.recordFirst())}/**播放全部动画 */play(callback?: () => any) {this.flips.forEach((flip) => flip.play(callback))}
}

然后在特定的地方插入代码,记录元素位置,做动画,插入了动画之后的代码,见下面的“完整代码”模块

三、完整代码

1.类型定义

// type.tsimport { CSSProperties, ReactNode } from "react"
/**有孩子的,基础的组件props,包含className style children */
interface baseChildrenProps {/**组件最外层的className */className?: string/**组件最外层的style */style?: CSSProperties/**孩子 */children?: ReactNode
}
/**ItemRender渲染函数的参数 */
type itemProps<T> = {/**当前元素 */item: T,/**当前索引 */index: number,/**父元素宽度 */width: number/**可拖拽的盒子,只有在这上面才能拖拽。自由放置位置。提供了一个默认的拖拽图标。可以作为包围盒,将某块内容作为拖拽区域 */DragBox: (props: baseChildrenProps) => ReactNode
}
/**拖拽排序组件的props */
export interface DragSortProps<T> {/**组件最外层的className */className?: string/**组件最外层的style */style?: CSSProperties/**列表,拖拽后会改变里面的顺序 */list: T[]/**用作唯一key,在list的元素中的属性名,比如id。必须传递 */keyName: keyof T/**一行个数,默认1 */cols?: number/**元素间距,单位px,默认0 (因为一行默认1) */marginX?: number/**当列表长度变化时,是否需要Flip动画,默认开启 (可能有点略微的动画bug) */flipWithListChange?: boolean/**每个元素的渲染函数 */ItemRender: (props: itemProps<T>) => ReactNode/**拖拽结束事件,返回排序好的新数组,在里面自己调用setList */afterDrag: (list: T[]) => any
} 

2. 部分不方便使用Tailwindcss的CSS

由于这段背景设置为tailwindcss过于麻烦,所以单独提取出来

/* index.module.css *//*拖拽时,留在原地的元素*/
.background {background: linear-gradient(45deg,rgba(0, 0, 0, 0.3) 0,rgba(0, 0, 0, 0.3) 25%,transparent 25%,transparent 50%,rgba(0, 0, 0, 0.3) 50%,rgba(0, 0, 0, 0.3) 75%,transparent 75%,transparent);background-size: 20px 20px;border-radius: 5px;
}

3. 计算每个子元素宽度的Hook

一个响应式计算宽度的hook,可以用于列表的多列布局

// hooks/alculativeWidth.tsimport { RefObject, useEffect, useState } from "react";/**根据父节点的ref和子元素的列数等数据,计算出子元素的宽度。用于响应式布局* @param fatherRef 父节点的ref* @param marginX 子元素的水平间距* @param cols 一行个数 (一行有几列)* @param callback 根据浏览器宽度自动计算大小后的回调函数,参数是计算好的子元素宽度* @returns 返回子元素宽度的响应式数据*/
const useCalculativeWidth = (fatherRef: RefObject<HTMLDivElement>, marginX: number, cols: number, callback?: (nowWidth: number) => void) => {const [itemWidth, setItemWidth] = useState(200);useEffect(() => {/**计算单个子元素宽度,根据list的宽度计算 */const countWidth = () => {const width = fatherRef.current?.offsetWidth;if (width) {const _width = (width - marginX * (cols + 1)) / cols;setItemWidth(_width);callback && callback(_width)}};countWidth(); //先执行一次,后续再监听绑定window.addEventListener("resize", countWidth);return () => window.removeEventListener("resize", countWidth);}, [fatherRef, marginX, cols]);return itemWidth
}
export default useCalculativeWidth

4. Flip动画实现

// lib/common/util/animation.ts/**位置的类型 */
interface position {x: number,y: number
}/**Flip动画 */
export class Flip {/**dom元素 */private dom: Element/**原位置 */private firstPosition: position | null = null/**动画时间 */private duration: number/**正在移动的动画会有一个专属的class类名,可以用于标识 */static movingClass = "__flipMoving__"constructor(dom: Element, duration = 500) {this.dom = domthis.duration = duration}/**获得元素的当前位置信息 */private getDomPosition(): position {const rect = this.dom.getBoundingClientRect()return {x: rect.left,y: rect.top}}/**给原始位置赋值 */recordFirst(firstPosition?: position) {if (!firstPosition) firstPosition = this.getDomPosition()this.firstPosition = { ...firstPosition }}/**播放动画 */play(callback?: () => any) {if (!this.firstPosition) {console.warn('请先记录原始位置');return}const lastPositon = this.getDomPosition()const dif: position = {x: lastPositon.x - this.firstPosition.x,y: lastPositon.y - this.firstPosition.y,}// console.log(this, dif);if (!dif.x && !dif.y) returnthis.dom.classList.add(Flip.movingClass)this.dom.animate([{ transform: `translate(${-dif.x}px, ${-dif.y}px)` },{ transform: `translate(0px, 0px)` }], { duration: this.duration })setTimeout(() => {this.dom.classList.remove(Flip.movingClass)callback?.()}, this.duration);}
}
/**Flip多元素同时触发 */
export class FlipList {/**Flip列表 */private flips: Flip[]/**正在移动的动画会有一个专属的class类名,可以用于标识 */static movingClass = Flip.movingClass/**Flip多元素同时触发 - 构造函数* @param domList 要监听的DOM列表* @param duration 动画时长,默认500ms*/constructor(domList: Element[], duration?: number) {this.flips = domList.map((k) => new Flip(k, duration))}/**记录全部初始位置 */recordFirst() {this.flips.forEach((flip) => flip.recordFirst())}/**播放全部动画 */play(callback?: () => any) {this.flips.forEach((flip) => flip.play(callback))}
}

4. 一些工具函数

import { type ClassValue, clsx } from "clsx"
import { twMerge } from "tailwind-merge"/**Tailwindcss的 合并css类名 函数* @param inputs 要合并的类名* @returns */
export function cn(...inputs: ClassValue[]) {return twMerge(clsx(inputs))
}/**查找符合条件的父节点* @param node 当前节点。如果当前节点就符合条件,就会返回当前节点* @param target 参数是当前找到的节点,返回一个布尔值,为true代表找到想要的父节点* @returns 没找到则返回null,找到了返回Element*/
export function findParent(node: Element, target: (nowNode: Element) => boolean) {while (node && !target(node)) {if (node.parentElement) {node = node.parentElement;} else {return null;}}return node;
}

5. 完整组件代码

import { DragEventHandler, useEffect,  useRef, useState } from "react";
import { DragSortProps } from "./type";
import useCalculativeWidth from "@/hooks/calculativeWidth"; 
import { cn, findParent } from "@/lib/util"; 
import style from "./index.module.css";
import { DragIcon } from "../../UI/MyIcon"; //这个图标可以自己找喜欢的
import { FlipList } from "@/lib/common/util/animation";/**拖拽时,留在原位置的元素的样式 */
const movingClass = [style.background]; //使用数组是为了方便以后添加其他类名
/**拖拽时,留在原位置的子元素的样式 */
const opacityClass = ["opacity-0"]; //使用数组是为了方便以后添加其他类名/**拖拽排序组件 */
const DragSort = function <T>({list,ItemRender,afterDrag,keyName,cols = 1,marginX = 0,flipWithListChange = true,className,style,
}: DragSortProps<T>) {const listRef = useRef<HTMLDivElement>(null);/**记录当前正在拖拽哪个元素 */const nowDragItem = useRef<HTMLDivElement>();const itemWidth = useCalculativeWidth(listRef, marginX, cols);/**存储flipList动画实例 */const flipListRef = useRef<FlipList>();const [dragOpen, setDragOpen] = useState(false); //是否开启拖拽 (鼠标进入指定区域开启)/**创建记录新的动画记录,并立即记录当前位置 */const createNewFlipList = (exceptTarget?: Element) => {if (!listRef.current) return;//记录动画const listenChildren = [...listRef.current.children].filter((k) => k !== exceptTarget); //除了指定元素,其它的都动画flipListRef.current = new FlipList(listenChildren, 300);flipListRef.current.recordFirst();};//下面这两个是用于,当列表变化时,进行动画useEffect(() => {if (!flipWithListChange) return;createNewFlipList();}, [list]);useEffect(() => {if (!flipWithListChange) return;createNewFlipList();return () => {flipListRef.current?.play(() => flipListRef.current?.recordFirst());};}, [list.length]);/**事件委托- 监听 拖拽开始 事件,添加样式 */const onDragStart: DragEventHandler<HTMLDivElement> = (e) => {if (!listRef.current) return;e.stopPropagation(); //阻止冒泡/**这是当前正在被拖拽的元素 */const target = e.target as HTMLDivElement;//设置被拖拽元素“留在原地”的样式。为了防止设置正在拖拽的元素样式,所以用定时器,宏任务更晚执行setTimeout(() => {target.classList.add(...movingClass); //设置正被拖动的元素样式target.childNodes.forEach((k) => (k as HTMLDivElement).classList?.add(...opacityClass)); //把子元素都设置为透明,避免影响}, 0);//记录元素的位置,用于Flip动画createNewFlipList(target);//记录当前拖拽的元素nowDragItem.current = target;//设置鼠标样式e.dataTransfer.effectAllowed = "move";};/**事件委托- 监听 拖拽进入某个元素 事件,在这里只是DOM变化,数据顺序没有变化 */const onDragEnter: DragEventHandler<HTMLDivElement> = (e) => {e.preventDefault(); //阻止默认行为,默认是不允许元素拖动到人家身上的if (!listRef.current || !nowDragItem.current) return;/**孩子数组,每次都会获取最新的 */const children = [...listRef.current.children];/**真正会被挪动的元素(当前正悬浮在哪个元素上面) */ //找到符合条件的父节点const realTarget = findParent(e.target as Element, (now) => children.indexOf(now) !== -1);//边界判断if (realTarget === listRef.current || realTarget === nowDragItem.current || !realTarget) {// console.log("拖到自身或者拖到外面");return;}if (realTarget.className.includes(FlipList.movingClass)) {// console.log("这是正在动画的元素,跳过");return;}//拿到两个元素的索引,用来判断这俩元素应该怎么移动/**被拖拽元素在孩子数组中的索引 */const nowDragtItemIndex = children.indexOf(nowDragItem.current);/**被进入元素在孩子数组中的索引 */const enterItemIndex = children.indexOf(realTarget);//当用户选中文字,然后去拖动这个文字时,就会触发 (可以通过禁止选中文字来避免)if (enterItemIndex === -1 || nowDragtItemIndex === -1) {console.log("若第二个数为-1,说明拖动的不是元素,而是“文字”", enterItemIndex, nowDragtItemIndex);return;}//Flip动画 - 记录原始位置flipListRef.current?.recordFirst();if (nowDragtItemIndex < enterItemIndex) {// console.log("向下移动");listRef.current.insertBefore(nowDragItem.current, realTarget.nextElementSibling);} else {// console.log("向上移动");listRef.current.insertBefore(nowDragItem.current, realTarget);}//Flip动画 - 播放flipListRef.current?.play();};/**事件委托- 监听 拖拽结束 事件,删除样式,设置当前列表 */const onDragEnd: DragEventHandler<HTMLDivElement> = (e) => {if (!listRef.current) return;/**当前正在被拖拽的元素 */const target = e.target as Element;target.classList.remove(...movingClass); //删除前面添加的 被拖拽元素的样式,回归原样式target.childNodes.forEach((k) => (k as Element).classList?.remove(...opacityClass)); //删除所有子元素的透明样式/**拿到当前DOM的id顺序信息 */const ids = [...listRef.current.children].map((k) => String(k.id)); //根据id,判断到时候应该怎么排序//把列表按照id排序const newList = [...list].sort(function (a, b) {const aIndex = ids.indexOf(String(a[keyName]));const bIndex = ids.indexOf(String(b[keyName]));if (aIndex === -1 && bIndex === -1) return 0;else if (aIndex === -1) return 1;else if (bIndex === -1) return -1;else return aIndex - bIndex;});afterDrag(newList); //触发外界传入的回调函数setDragOpen(false); //拖拽完成后,再次禁止拖拽};/**拖拽按钮组件 */ //只有鼠标悬浮在这上面的时候,才开启拖拽,做到“指定区域拖拽”const DragBox = ({ className, style, children }: baseChildrenProps) => {return (<divstyle={{ ...style }}className={cn("hover:cursor-grabbing", className)}onMouseEnter={() => setDragOpen(true)}onMouseLeave={() => setDragOpen(false)}>{children || <DragIcon size={20} color="#666666" />}</div>);};return (<divclassName={cn(cols === 1 ? "" : "flex flex-wrap", className)}style={style}ref={listRef}onDragStart={onDragStart}onDragEnter={onDragEnter}onDragOver={(e) => e.preventDefault()} //被拖动的对象被拖到其它容器时(因为默认不能拖到其它元素上)onDragEnd={onDragEnd}>{list.map((item, index) => {const key = item[keyName] as string;return (<div id={key} key={key} style={{ width: itemWidth, margin: `4px ${marginX / 2}px` }} draggable={dragOpen} className="my-1">{ItemRender({ item, index, width: itemWidth, DragBox })}</div>);})}</div>);
};
export default DragSort;

6. 效果图的测试用例

一开始展示的效果图的实现代码

"use client";
import { useState } from "react";
import DragSort from "@/components/base/tool/DragSort";
import { Button, InputNumber } from "antd";
export default function page() {interface item {id: number;}const [list, setList] = useState<item[]>([]); //当前列表const [cols, setCols] = useState(1); //一行个数/**创建一个新的元素 */const createNewItem = () => {setList((old) =>old.concat([{id: Date.now(),},]));};return (<div className="p-2 bg-[#a18c83] w-screen h-screen overflow-auto"><Button type="primary" onClick={createNewItem}>点我添加</Button>一行个数: <InputNumber value={cols} min={1} onChange={(v) => setCols(v!)} /><DragSortlist={list}keyName={"id"}cols={cols}marginX={10}afterDrag={(list) => setList(list)}ItemRender={({ item, index, DragBox }) => {return (<div className="flex items-center border rounded-sm p-2 gap-1 bg-white"><DragBox /><div>序号:{index},</div><div>ID:{item.id}</div>{/* <DragBox className="bg-stone-400 text-white p-1">自定义拖拽位置</DragBox> */}</div>);}}/></div>);
}

四、结语

        哪里做的不好、有bug等,欢迎指出

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

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

相关文章

FreeRTOS_任务通知

目录 1. 任务通知简介 2. 发送任务通知 2.1 函数 xTaskNotify() 2.2 函数 xTaskNotifyFromISR() 2.3 函数 xTaskNotifyGive() 2.4 函数 vTaskNotifyGiveFromISR() 2.5 函数 xTaskNotifyAndQuery() 2.6 函数 xTaskNotifyAndQueryFromISR() 3. 任务通知通用发送函数 3.…

MySQL数据脱敏(Data masking plugin functions)

对于企业而言&#xff0c;数据脱敏可以在数据共享或测试时用于保护敏感数据&#xff08;如信用卡&#xff0c;社保卡&#xff0c;地址等&#xff09;。通过对敏感数据进行脱敏处理&#xff0c;组织可以最大限度地降低数据泄露和未经授权访问的风险&#xff0c;同时仍能够使用真…

[Machine Learning][Part 8]神经网络的学习训练过程

目录 训练过程 一、建立模型&#xff1a; 二、建立损失函数 J(w,b): 三、寻找最小损失函数的(w,b)组合 为什么需要激活函数 激活函数种类 二分法逻辑回归模型 线性回归模型 回归模型 训练过程 一、建立模型&#xff1a; 根据需求建立模型&#xff0c;从前面神经网络的…

springboot常见网络相关错误及原因解析

在基于spring-boot开发过程尤其是上线后&#xff0c;经常出现网络相关的错误&#xff0c;令人难以琢磨和下手&#xff0c;所以就spring-boot使用过程中可能碰到的网络相关问题进行分析&#xff0c;结合网络转包、日志报错和前端输出&#xff0c;针对网络连接超时、连接被拒绝、…

JVM虚拟机:如何查看自己的JVM默认的垃圾回收器

只需要在程序运行的时候指定下面的参数就可以看到当前自己的JVM默认的垃圾回收器是什么&#xff1f;如下所示&#xff1a; 如上所示&#xff0c;默认使用的是G1回收器&#xff0c;这是我的电脑&#xff0c;因为我的电脑安装jdk的版本是1.9 如果你的jdk的版本是1.8&#xff0c;那…

K8s:部署 CNI 网络组件+k8s 多master集群部署+负载均衡及Dashboard k8s仪表盘图像化展示管理

目录 1 部署 CNI 网络组件 1.1 部署 flannel 1.2 部署 Calico 1.3 部署 CoreDNS 2 负载均衡部署 3 部署 Dashboard 1 部署 CNI 网络组件 1.1 部署 flannel K8S 中 Pod 网络通信&#xff1a; ●Pod 内容器与容器之间的通信 在同一个 Pod 内的容器&#xff08;Pod 内的容…

Pyhotn: Mac安装selenium没有chromedriver-114以上及chromedriver无法挪到/usr/bin目录下的问题

1.0 安装selenium 终端输入&#xff1a; pip install selenium 查看版本&#xff1a; pip show selenium2.0 安装chromedriver 查看chrome版本 网上大多数是&#xff0c;基本到114就停了。 https://registry.npmmirror.com/binary.html?pathchromedriver/ 各种搜索&#…

8-2、T型加减速计算简化【51单片机控制步进电机-TB6600系列】

摘要&#xff1a;本节介绍简化T型加减速计算过程&#xff0c;使其适用于单片机数据处理。简化内容包括浮点数转整型数计算、加减速对称处理、预处理计算 一、浮点数转整型数计算 1.1简化∆t_1计算 根据上一节内容已知 K0.676 step1.8/X&#xff08;x为细分值&#xff0c;1.8对…

Windows 系统服务器部署jar包时,推荐使用winsw,将jar包注册成服务,并设置开机启动。

一、其他方式不推荐的原因 1、Spring Boot生成的jar包&#xff0c;可以直接用java -jar运行&#xff0c;但是前提是需要登录用户&#xff0c;而且注销用户后会退出程序&#xff0c;所以不可用。 2、使用计划任务&#xff0c;写一个bat处理文件&#xff0c;里面写java -jar运行…

视频编辑软件Corel VideoStudio 会声会影2024中文剪辑使用教程

会声会影&#xff08;Corel VideoStudio&#xff09;2024为加拿大Corel公司发布的一款功能丰富的视频编辑软件。会声会影2023简单易用&#xff0c;具有史无前例的强大功能&#xff0c;拖放式标题、转场、覆叠和滤镜&#xff0c;色彩分级、动态分屏视频和新增强的遮罩创建器&…

技术分享 | 一文带你了解测试流程的体系

软件测试是软件质量保证的关键步骤。越早发现软件中存在的问题&#xff0c;修复问题的成本就越低&#xff0c;软件质量也就越高&#xff0c;软件发布后的维护费用越低。 为了能更好的保障软件质量&#xff0c;在软件测试的实践中&#xff0c;慢慢形成了一些流程用来达到这一目…

蓝桥云课--1014 第 1 场算法双周赛

2-数树数【算法赛】&#xff08;找规律&#xff09; 一、题目要求 二、思路 由此可以推导出来&#xff0c;当s[i]L时&#xff0c;下一个编号当前编号*2-1&#xff1b;当s[i]R时&#xff0c;下一个编号当前编号*2&#xff1b; 三、代码 #include<bits/stdc.h> #define…

Selenium学习(Java + Edge)

Selenium /səˈliːniəm/ 1. 简介 ​ Selenium是一个用于Web应用程序自动化测试工具。Selenium测试直接运行在浏览器中&#xff0c;就像真正的用户在操作一样。支持的浏览器包括IE、Mozilla Firefox、Safari、Google Chrome、Opera、Edge等。 ​ 适用于自动化测试&#x…

缺陷之灵魂操作bug

一、前言 正常来说&#xff0c;我们在测试缺陷的时候都是按照case来测试的&#xff0c;但是有些场景&#xff0c;例如说发散思维这种场景&#xff0c;就会找到一些比较不太正常、不好复现的缺陷&#xff0c;然后如果要辅助研发修复&#xff0c;就会极为痛苦。 二、场景描述 大…

装修服务预约小程序的内容如何

大小装修不断&#xff0c;市场中大小品牌也比较多&#xff0c;对需求客户来说&#xff0c;可以线下咨询也可以线上寻找品牌&#xff0c;总是可以找到满意的服务公司&#xff0c;而对装修公司来说如今线下流量匮乏&#xff0c;很多东西也难以通过线下方式承载&#xff0c;更需要…

腾讯云CVM服务器操作系统镜像大全

腾讯云CVM服务器的公共镜像是由腾讯云官方提供的镜像&#xff0c;公共镜像包含基础操作系统和腾讯云提供的初始化组件&#xff0c;公共镜像分为Windows和Linux两大类操作系统&#xff0c;如TencentOS Server、Windows Server、OpenCloudOS、CentOS Stream、CentOS、Ubuntu、Deb…

多目标优化中的“latent action”是什么?

2020 NeurIPS 中的“latent action”&#xff1a; Our model defines latent action as a boundary that splits the region represented by a node into a high-performing and a low performing region. 这里的latent action代表一个边界&#xff08;分类器&#xff09;&…

【案例】3D地球

效果图&#xff1a; 直接放源码 <!DOCTYPE html> <html> <head><meta http-equiv"Content-Type" content"text/html; charsetutf-8" /><meta name"viewport" content"initial-scale1.0, user-scalableno" …

Mysql高级——Mysql8一主一从,多主多从搭建

修改 /etc/hosts文件 ip地址 master1 ip地址 master2 ip地址 slave1 ip地址 slave2一主一从 create database master1db;create table master1db.master1tab(name char(50));insert into master1db.master1tab VALUES(1111);insert into master1db.master1tab VALUES(2222);m…

ARM 版 OpenEuler 22.03 部署 KubeSphere v3.4.0 不完全指南续篇

作者&#xff1a;运维有术 前言 知识点 定级&#xff1a;入门级KubeKey 安装部署 ARM 版 KubeSphere 和 KubernetesARM 版 KubeSphere 和 Kubernetes 常见问题 实战服务器配置 (个人云上测试服务器) 主机名IPCPU内存系统盘数据盘用途ks-master-1172.16.33.1661650200KubeSp…