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.mzph.cn/news/132548.shtml

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

相关文章

搭建WAMP网站教程(Windows+Apache+MySQL+PHP)

之前为了学习网络安全&#xff0c;从搭建网站学起&#xff0c;对网站运行有个初步的了解。 今天翻到了之前的笔记&#xff0c;顺手发到csdn上了。 搭建网站步骤 一、Apache 安装Apache&#xff0c;下载Apache之后把Apache解压&#xff0c;此处解压到C:\目录下 2.然后要记得安…

leetcode:2926. 平衡子序列的最大和 【树状数组维护最大前缀和】

题目链接 lc2926 题目描述 题目思路 定义b[i] nums[i] - i 目标是从b中找到一个非降子序列使得元素和最大 # b[i] nums[i] - i # 找到b的一个非降子序列使得元素和最大 # f[i]: 子序列最后一个数下标是i&#xff0c;对应的最大子序列 # f[i] max (max f[j], 0) nums[i] …

计算机编程软件编程基础知识,中文编程工具下载分享

计算机编程软件编程基础知识&#xff0c;中文编程工具下载分享 给大家分享一款中文编程工具&#xff0c;零基础轻松学编程&#xff0c;不需英语基础&#xff0c;编程工具可下载。 这款工具不但可以连接部分硬件&#xff0c;而且可以开发大型的软件&#xff0c;象如图这个实例…

idea集成测试插件替代postman

idea集成测试插件替代postman 兄弟萌&#xff0c;你再测试接口是否无bug是否流畅的时候是否还在使用“postman”来回切换进行测试呢&#xff1f; 页面切换进行测试&#xff0c;有没有感觉很麻烦呢&#xff1f; 打开postman&#xff0c;输入接口地址&#xff0c;有没有感觉很麻烦…

关于网站安全的一些讨论

互联网的普及和发展为企业和个人提供了巨大的机会&#xff0c;但同时也伴随着网络安全威胁的增加。网站被攻击是一个常见的问题&#xff0c;可能导致数据泄露、服务中断和声誉受损。在本文中&#xff0c;我们将探讨与网络安全紧密相关的因素&#xff0c;分析为什么网站容易受到…

阿里云安全恶意程序检测(速通一)

阿里云安全恶意程序检测 赛题理解赛题介绍赛题说明数据说明评测指标 赛题分析数据特征解题思路 数据探索数据特征类型数据分布箱型图 变量取值分布缺失值异常值分析训练集的tid特征标签分布测试集数据探索同上 数据集联合分析file_id分析API分析 特征工程与基线模型构造特征与特…

vcenter跨版本升级

vcenter跨版本升级&#xff08;比如从6.7升级到7.0&#xff09;1.如果您有VCHA&#xff0c;需要关闭移除 vCenter HA 配置 2. 一定要先做好VC的备份 将VC做一个内存快照以便备份 3.下载好后&#xff0c;在电脑上解压镜像ISO&#xff0c;进入这个文件夹运行程序&#xff0c;并…

性能优化之懒加载 - 基于观察者模式和单例模式的实现

一、引入 在前端性能优化中&#xff0c;关于图片/视频等内容的懒加载一直都是优化利器。当用户看到对应的视图模块时&#xff0c;才去请求加载对应的图像。 原理也很简单&#xff0c;通过浏览器提供的 IntersectionObserver - Web API 接口参考 | MDN (mozilla.org)&#xff0c…

深入理解强化学习——多臂赌博机:10臂测试平台

分类目录&#xff1a;《深入理解强化学习》总目录 为了大致评估贪心方法和 ϵ − \epsilon- ϵ−贪心方法相对的有效性&#xff0c;我们将它们在一系列测试问题上进行了定量比较。这组问题是2000个随机生成的 k k k臂赌博机问题&#xff0c;且 k 10 k10 k10。在每一个赌博机问…

【Head First 设计模式】-- 观察者模式

背景 客户有一个WeatherData对象&#xff0c;负责追踪温度、湿度和气压等数据。现在客户给我们提了个需求&#xff0c;让我们利用WeatherData对象取得数据&#xff0c;并更新三个布告板&#xff1a;目前状况、气象统计和天气预报。 WeatherData对象提供了4个接口&#xff1a; …

从零入门Chrome插件开发

什么是 Chrome 插件 谷歌浏览器在推出时就以其快速、安全和简洁的特点受到了广大用户的欢迎。随着浏览器的不断发展&#xff0c;谷歌为用户提供了插件开发平台&#xff0c;使开发者能够为浏览器添加各种功能和定制化选项。从此&#xff0c;插件成为了提升用户体验和个性化的重…

AI:54-基于深度学习的树木种类识别

🚀 本文选自专栏:AI领域专栏 从基础到实践,深入了解算法、案例和最新趋势。无论你是初学者还是经验丰富的数据科学家,通过案例和项目实践,掌握核心概念和实用技能。每篇案例都包含代码实例,详细讲解供大家学习。 📌📌📌在这个漫长的过程,中途遇到了不少问题,但是…

318. 最大单词长度乘积

318. 最大单词长度乘积 难度: 中等 来源: 每日一题 2023.11.06 给你一个字符串数组 words &#xff0c;找出并返回 length(words[i]) * length(words[j]) 的最大值&#xff0c;并且这两个单词不含有公共字母。如果不存在这样的两个单词&#xff0c;返回 0 。 示例 1&…

Canvas 梦幻树生长动画

canvas可以制作出非常炫酷的动画&#xff0c;以下是一个梦幻树的示例。 效果图 源代码 <!DOCTYPE> <html> <head> <meta http-equiv"Content-Type" content"text/html; charsetutf-8" /> <title>梦幻数生长动画</title&…

【数据结构】树与二叉树(二):树的表示C语言:树形表示法、嵌套集合表示法、嵌套括号表示法 、凹入表示法

文章目录 5.1 树的基本概念5.1.1 树的定义5.1.2 森林的定义5.1.3 树的术语5.1.4 树的表示1&#xff0e;树形表示法2&#xff0e;嵌套集合表示法结构体创建树主函数 3&#xff0e;嵌套括号表示法结构体创建树嵌套括号表示法主函数 4&#xff0e;凹入表示法结构体创建树凹入表示法…

python调用飞书机器人发送文件

当前飞书webhook机器人还不支持发送文件类型的群消息&#xff0c;可以申请创建一个机器人应用来实现群发送文件消息。 创建机器人后&#xff0c;需要开通一系列权限&#xff0c;然后发布。由管理员审核通过后&#xff0c;才可使用。 包括如下的权限&#xff0c;可以获取群的c…

深度学习服务器(Linux)开发环境搭建教程

当你拿到一台服务器的使用权时&#xff0c;最头疼的莫过于登陆服务区并配置开发环境。本文将从0开始&#xff0c;讲述一台刚申请的服务器远程登陆并配置开发环境的全过程。希望对你有所帮助 1.登陆服务器 打开MobaXterm软件&#xff0c;创建一个新的Session&#xff0c;选择S…

图及谱聚类商圈聚类中的应用

背景 在O2O业务场景中&#xff0c;有商圈的概念&#xff0c;商圈是业务运营的单元&#xff0c;有对应的商户BD负责人以及配送运力负责任。这些商圈通常是一定地理围栏构成的区域&#xff0c;区域内包括商户和用户&#xff0c;商圈和商圈之间就通常以道路、河流等围栏进行分隔。…

MySQL EXPLAIN查看执行计划

MySQL 执⾏计划是 MySQL 查询优化器分析 SQL 查询时⽣成的⼀份详细计划&#xff0c;包括表如何连 接、是否⾛索引、表扫描⾏数等。通过这份执⾏计划&#xff0c;我们可以分析这条 SQL 查询中存在的 问题&#xff08;如是否出现全表扫描&#xff09;&#xff0c;从⽽进⾏针对优化…

双十一运动健身好物推荐,这几款健身好物一定不要错过!

双十一购物狂欢节又要到了&#xff0c;又要到买买买的时候了&#xff01;相信有很多想健身的小白还在发愁不知道买啥装备&#xff1f;别急&#xff0c;三年健身达人这就给你们分享我的年度健身好物&#xff01; 第一款&#xff1a;南卡Runner Pro4s骨传导耳机 推荐理由&#…