在vue3+typescript中使用d3 version 7注意的地方

前几天在做一个前端项目,需要在一个vue3+typescript的项目中使用d3

上次做vue2+d3v5的项目已经很多年了,这次不仅是vue3,用的typescript,而且d3也升级到v7了,有很多东西不一样了。

这里记录一下,避免日后忘记。

在使用d3的各个类时,给定type定义

typescript是一个类型严格的语言,在vue3+typescript项目中调用d3时,最好也遵循这一要求,除非万不得已,尽量不要使用any类型。

Selection类

所有的d3的链式操作,都是基于d3.select返回的Selection类进行的,可以说d3最重要的类就是Selection

在d3中,Selection类是这样定义的

/*** A D3 Selection of elements.** The first generic "GElement" refers to the type of the selected element(s).* The second generic "Datum" refers to the type of the datum of a selected element(s).* The third generic "PElement" refers to the type of the parent element(s) in the D3 selection.* The fourth generic "PDatum" refers to the type of the datum of the parent element(s).*/
export interface Selection<GElement extends BaseType, Datum, PElement extends BaseType, PDatum>

这是一个typescript模板,模板必须要给定变量类型才能在编译期生成真实的类。这个模板需要给定4各变量类型:

第一个是select要选取的目标的类型,一般来说是DOM上的节点,对于svg而言,主要有:SVGSVGElement、SVGGElement、SVGPathElement这3类。实际上svg还有SVGAElement、SVGCircleElement等很多,但是一般来说d3不用那些类型。不过,如果在应用的时候确实遇到了,也要确实的写上。

按照d3的要求,其实还有一种BaseType是不需要和DOM节点相关的,这个一般来说用于进行数据处理,而不是用于DOM操作。

第二个是数据的类型。实际上在d3内部,如果仅仅是select,此时是没有指定数据类型的,那么这个时候,这个数据类型就是unknown。如果在select后通过链式调用了data或者datum,此时就有了数据类型。

这里给定的数据的类型必须和data或者datum调用的数据的类型一致,否则typescript的严格模式会报错。如果是关闭typescript的strict模式,确实可以关闭掉这个报错,但是我们用typescript不就是为了享受它为我们检查错误吗?所以我从不关闭,发现了错误就去改掉。

第三个是选取的DOM节点的父节点。现实应用中,有时数据是来自父节点的,所以要用到。如果不需要父节点的相关信息,那么可以设定为null。

第四个是选取的DOM节点的父节点的数据类型。如果用到了来自父节点的数据,这里也要匹配。如果没有用到父节点的数据,那么可以设定为undefined。

所以说如果要选取svg根节点,暂时没有指定数据,父节点也不需要,更别提父节点的数据了。那么返回的就是

let svgElement: Ref<SVGSVGElement | null> = ref<SVGSVGElement | null>(null);
let d3svgElement: Selection<SVGSVGElement, unknown, null, undefined>;

这段代码中的第一行是在vue3的单文件组件中,获取DOM节点的方法。第二行则是把DOM节点传递给d3.select后返回的Selection

注意,这里我并没有使用select函数,而是简单的定义了一个变量而已。这是因为在setup阶段,tempelate并没有被编译,DOM并没有生成,所以如果此时用select函数,那么只能返回一个空值

在DOM已经生成的情况下,就可以使用select函数来进行操作了。

type Vector2d = Array<Array<number> >let nodeSelection: Selection<SVGGElement, unknown, null, undefined> = select<SVGGElement, unknown>(theDOM)
let selectionWithData:Selection<SVGPathElement, Vector2d, null, undefined>=nodeSelection.select<SVGPathElement>("path").datum<Vector2d>(pointsXY)

select函数也是一个typescript模板,而且还是一个多态的模板。

/*** For each selected element, select the first descendant element that matches the specified selector string.* If no element matches the specified selector for the current element, the element at the current index will* be null in the returned selection. If multiple elements match the selector, only the first matching element* in document order is selected. Selection.select does not affect grouping: it preserves the existing group* structure and indexes, and propagates data (if any) to selected children.** If the current element has associated data, this data is propagated to the* corresponding selected element.** The generic represents the type of the descendant element to be selected.** @param selector CSS selector string*/// eslint-disable-next-line @definitelytyped/no-unnecessary-genericsselect<DescElement extends BaseType>(selector: string): Selection<DescElement, Datum, PElement, PDatum>;/*** Create an empty sub-selection. Selection.select does not affect grouping: it preserves the existing group* structure and indexes.*/// eslint-disable-next-line @definitelytyped/no-unnecessary-genericsselect<DescElement extends BaseType>(selector: null): Selection<null, undefined, PElement, PDatum>;/*** For each selected element, select the descendant element returned by the selector function.* If no element is returned by the selector function for the current element, the element at the* current index will be null in the returned selection. Selection.select does not affect grouping:* it preserves the existing group structure and indexes, and propagates data (if any) to selected children.** If the current element has associated data, this data is propagated to the* corresponding selected element.** The generic represents the type of the descendant element to be selected.** @param selector A selector function, which is evaluated for each selected element, in order, being passed the current datum (d),* the current index (i), and the current group (nodes), with this as the current DOM element (nodes[i]).* It must return an element, or null if there is no matching element.*/select<DescElement extends BaseType>(selector: ValueFn<GElement, Datum, DescElement>,): Selection<DescElement, Datum, PElement, PDatum>;

当一个Selection进一步的select时,会调用多态的第一个形态,此时,该模板要给定DOM类型,而且该类型必须和实际select的DOM节点类型一致,否则typescript会报错。

而全新创建一个Selection时,会调用多态的第三个形态。里面有2个变量类型,第一个是所选DOM节点的类型,第二个是数据的类型。DOM类型和实际选择的必须一致。而数据类型可以先保留为unknown等待数据绑定操作,unknown类型是兼容的。

这个多态的第二个形态是创建一个空的Selection,没有太大用处。

而在select之后直接链式操作绑定数据的话,数据类型也要给定,如果给定的数据类型和真实绑定的数据类型不一致的话,typescript会报错。

Selection类的call函数

JavaScript中的call是把函数中的this调整为调用call时参数中的第一个实参,并把调用call时后续的参数作为实参传递给函数。

d3的Selectiong类也有一个call函数

    /*** Invoke the specified function exactly once, passing in this selection along with any optional arguments.* Returns this selection.** @param func A function which is passed this selection as the first argument along with any optional arguments.* @param args List of optional arguments to be passed to the callback function.*/call<Args extends any[]>(func: (selection: Selection<GElement, Datum, PElement, PDatum>, ...args: Args) => void,...args: Args): this;

 call函数的第一个实参是这个被调用的函数,这个函数必须是无返回值的,或者说返回void型

如果这个函数中涉及到this的话,把调用者这个Selection对象作为this传递给这个被call的函数。而且函数中定义的this的类型和Selection的类型必须一致,否则typescript会报错。

call函数的后续实参作为被调用函数的其他实参传入。

Selection对象通过call调用的函数,只执行一次,下次再想执行必须要再次使用call调用。

我一般用这个来处理事件响应,比如点击、拖拽、缩放、平移

ValueFn类

在d3中,很多地方都会允许使用一个函数进行处理。比如说从一组数据里抽出“生日”,然后把“生日”图形呈现出来;或者在select的时候,设定一个条件,满足条件的才选取,不满足条件的忽略;再比如通过attr设定transform属性的内容时,通过一个函数返回这个内容,可以根据数据生成不同的内容以实现更加多变的效果……

这个函数实际上是一个typescript模板

/*** Callback type for selections and transitions*/
export type ValueFn<T extends BaseType, Datum, Result> = (this: T,datum: Datum,index: number,groups: T[] | ArrayLike<T>,
) => Result;

这个模板输入3种类型,并返回一个函数。

输入的三种类型中:

第一种输入类型Basetype和Selection中的Basetype差不多,代表着这个函数作用节点的类型,如果节点类型不匹配,typescript会报错。

第二种输入类型Datum是数据类型。如果数据类型和真实的类型不匹配,typescript也会报错。

第三种输入类型其实是“生成出来的这个函数”的返回值类型,必须要和调用这个函数的位置的需求相匹配。

而返回的这个函数,从模板上看有4个形参和1个返回值。但是4个形参中的第一个是this,实际上也就是调用这个函数的对象,一般来说是Selectiong中的DOM节点。

一般来说ValueFn是只读的,就是说不会修改什么数据,但是理论上确实可以做很多事情,比如说对调用者本身做一些修改。即使对调用者本身不做任何修改,有些时候确实需要调用者的一些信息。

而这三个形参中:

第一个形参是输入的数据

第二个形参是调用者调用时的索引

第三个形参是调用者的数组。

在d3中,在select后通过链式调用data绑定数据后,d3会分析输入数据的数量,并根据这个数量在enter/exit/remove/join函数中调整Selection子节点的数量。比如说输入数据有5条,但是Selection内只有3个path子节点,而链式操作有需要有和输入数据一一对应的path子节点,那么就会通过enter/exit/remove/join自动调整,把path节点增加到5个。这5个path子节点所组成的Array就是这第三个形参。

因为箭头函数的形参中不能包含this,所以有时候我们需要用下面这种方法来定义ValueFn,然后在用到的地方调用

let SymbolTransform: ValueFn<SVGPathElement | BaseType, Vector1d, string> = function (this: SVGPathElement | BaseType,d: Vector1d,i: number,a: Array<BaseType | SVGPathElement> | ArrayLike<BaseType | SVGPathElement>
): string {const x = xScale(d[2]);const y = yScale(d[0]);return `translate(${x},${y})`;
};

注意,在d3v5之前,这个函数的第4个形参不是调用者的Array,而是数据d的Array。所以如果遇到了代码迁移之类的工作,在这里要调整。

事件响应类

D3 6.0 migration guide / D3 | Observable

d3在从v5升级到v6时有很多改变,其中一个重要变化就是事件管理器大幅更新了。

事件响应函数

在d3的Selection类中,有一个on函数,可以通过这个函数对事件绑定一个函数,以此实现事件响应。这个on函数的定义是这样的:

    /*** Return the currently-assigned listener for the specified event typename on the first (non-null) selected element,* if any, If multiple typenames are specified, the first matching listener is returned.** @param typenames The typenames is a string event type, such as click, mouseover, or submit; any DOM event type supported by your browser may be used.* The type may be optionally followed by a period (.) and a name; the optional name allows multiple callbacks to be registered* to receive events of the same type, such as click.foo and click.bar. To specify multiple typenames, separate typenames with spaces,* such as "input change"" or "click.foo click.bar".*/on(typenames: string): ((this: GElement, event: any, d: Datum) => void) | undefined;/*** Remove a listener for the specified event type names. To remove all listeners for a given name,* pass null as the listener and ".foo" as the typename, where foo is the name; to remove all listeners with no name, specify "." as the typename.** @param typenames The typenames is a string event type, such as click, mouseover, or submit; any DOM event type supported by your browser may be used.* The type may be optionally followed by a period (.) and a name; the optional name allows multiple callbacks to be registered* to receive events of the same type, such as click.foo and click.bar. To specify multiple typenames, separate typenames with spaces,* such as "input change"" or "click.foo click.bar".* @param listener null to indicate removal of listener*/on(typenames: string, listener: null): this;/*** Add an event listener for the specified event type names. If an event listener was previously registered for the same typename* on a selected element, the old listener is removed before the new listener is added.** When a specified event is dispatched on a selected node, the specified listener will be evaluated for each selected element.** @param typenames The typenames is a string event type, such as click, mouseover, or submit; any DOM event type supported by your browser may be used.* The type may be optionally followed by a period (.) and a name; the optional name allows multiple callbacks to be registered* to receive events of the same type, such as click.foo and click.bar. To specify multiple typenames, separate typenames with spaces,* such as "input change"" or "click.foo click.bar".* @param listener A listener function which will be evaluated for each selected element,* being passed the current event (event) and the current datum (d), with this as the current DOM element (event.currentTarget).* Listeners always see the latest datum for their element.* Note: while you can use event.pageX and event.pageY directly,* it is often convenient to transform the event position to the local coordinate system of that element that received the event using d3.pointer.* @param options An optional options object may specify characteristics about the event listener, such as wehether it is captures or passive; see element.addEventListener.*/on(typenames: string, listener: (this: GElement, event: any, d: Datum) => void, options?: any): this;

这是一个typescript的多态函数,很明显,第二种多态——不绑定任何相应函数——是最简单的,也是默认的。而第一种是最常用的,也就是绑定一个“有三个形参的函数”,而这个函数:

第一个形参是调用者this,也就是被绑定的Selection对象——也就是DOM节点。

第二个形参是事件,注意这个事件的类型是any,但是实际上必须是d3的event类型的标签字符串。而d3的event一共分为4个大类brush/dispatch/drag/zoom,每个大类还有多个event类型。

第三个形参是数据,就是Selection中绑定的数据,其类型也必须一致,否则typescript会报错。

通过事件响应函数,可以对第三个形参所对应的实参进行操作,比如在drag事件响应函数中,把数据修改为drag事件的x、y坐标。

事件响应函数没有像ValueFn一样有一个单独定义的模板,所以我们只好手动定义。因为这个函数包含了this,所以定义起来稍微有点麻烦,只能使用变量式函数定义法。

let ClickSymbol: (this: SVGPathElement, event: any, d: Vector1d) => void = function (event: MouseEvent,d: Vector1d
): void {console.log(this)
};node.select<SVGGElement>(".symbolElement").selectAll<SVGPathElement, Vector1d>("path").data<Vector1d>(pointsXY).join("path").attr("d", symbol<Vector1d>(symbolCircle, 10)).attr("pointnum", (d: Vector1d, i: number, a: Array<SVGPathElement> | ArrayLike<SVGPathElement>) => i).style("stroke", "black").style("fill", "white").attr("transform", SymbolTransform).on("click", ClickSymbol);

调用的时候用Selection的链式操作,通过on函数调用。

注意pointnum这里调用了一个ValueFn,只不过它是匿名函数定义的,而匿名函数中不可以在形参里用this,所以这里只有3个形参。

Zoom函数

zoom这个词的愿意虽然是缩放,但是d3的zoom却还可以平移。

Zoomable Scatterplot / D3 | Observable

这里有一个典型的zoom应用的例子,虽然是用JavaScript写的,但是其基本思路和typescript并无不同,如果要迁移到typescript,直接加上类型标注基本就可以。

ZoomBehavior是一个模板函数,在Javascript中,函数也是一个对象,可以有自己的属性,函数在运行时也可以调用自己的属性以实现更加复杂多变的功能。模板的两个变量类型,其中第一个是该函数要作用的DOM节点的类型,在typescript内置对象中定一个了一个Element类型,几乎涵盖了所有HTML的DOM类型;第二个是数据类型。也就是对应着Selection对象中DOM节点的类型和绑定的数据的类型。

这个函数是由zoom函数生成的,然后通过链式调用可以修改其内部参数。其中最主要的就是通过on函数增加对zoom事件的响应

let xScale:ScaleLinear<number, number, never> = scaleLinear();
let yScale:ScaleLinear<number, number, never> = scaleLinear();const zoomBeh: ZoomBehavior<Element, unknown> = zoom().scaleExtent([0.5, 32]).on("zoom", (event: D3ZoomEvent<Element, any>, d: unknown): void => {gElement.attr("transform", event.transform.toString());gElement.selectAll("rect").attr("stroke-width", 2 / event.transform.k);event.transform.rescaleX(xScale);event.transform.rescaleY(yScale);});select(plotElement.value as Element).call(zoomBeh).call(zoomBeh.transform, zoomIdentity);

比如说gElement是一个Selection对象,里面包含有一些图形。而我们需要它对zoom事件有所响应,直接通过

gElement.attr("transform", event.transform.toString());

 就可以让它随zoom事件平移和缩放。

而如果说这里面包含的图案中有一些是线条图案,而我们不希望线条的宽度随着缩放而有所变化,也就是说只缩放尺寸,不缩放粗细。就可以这样

gElement.selectAll("rect").attr("stroke-width", 2 / event.transform.k);

这个例子当中的线条图案是rect,如果是其他的图案,比如circle/path之类的,方法类似。

而比例尺也要有针对性的进行调整

event.transform.rescaleX(xScale);
event.transform.rescaleY(yScale);

另外,注意on函数的调用。

就如“事件响应函数”章节所述,on函数的第一个参数是事件类型,是一个字符串,而第二个参数是事件的响应函数。

这个响应函数包含3个形参,但是第一个形参是this,而这里是采用匿名函数的方法直接定义的,所以第一个this就不要写在形参表中,形参表中只有event和d。

而由于on的第一个参数是字符串zoom,所以事件类型也是确定的,这里就是D3ZoomEvent<Element, any>,这里指定事件类型有助于响应函数的编码,因为D3ZoomEvent类有transform成员以及配套的其他函数,编码时编辑器可以高亮提示、自动跳转,typescript也会做代码检查。

最后通过

select(plotElement.value as Element).call(zoomBeh).call(zoomBeh.transform, zoomIdentity);

把zoom函数绑定到对应的DOM元素上,可以看到这个DOM元素是vue3直接操作DOM元素,一旦这个DOM元素上发生了zoom缩放事件,就会响应。

zoomIdentify是d3自带的一个类。 第二个call是对平移进行响应。

Drag函数

Drag函数是一个模板

/*** A D3 Drag Behavior** The first generic refers to the type of element to be dragged.* The second generic refers to the type of the datum of the dragged element.* The third generic refers to the type of the drag behavior subject.** The subject of a drag gesture represents the thing being dragged.* It is computed when an initiating input event is received,* such as a mousedown or touchstart, immediately before the drag gesture starts.* The subject is then exposed as event.subject on subsequent drag events for this gesture.** The default subject is the datum of the element in the originating selection (see drag)* that received the initiating input event; if this datum is undefined,* an object representing the coordinates of the pointer is created.* When dragging circle elements in SVG, the default subject is thus the datum of the circle being dragged.* With Canvas, the default subject is the canvas element’s datum (regardless of where on the canvas you click).* In this case, a custom subject accessor would be more appropriate,* such as one that picks the closest circle to the mouse within a given search radius.*/
export interface DragBehavior<GElement extends DraggedElementBaseType, Datum, Subject> extends Function

和zoom函数不同,drag函数的模板有3个变量类型,其中第一个和第二个和zoom的模板一样代表着调用者的类型和绑定的数据的类型。第三个是drag事件的具体类型。这是因为drag事件包含3个具体类型:start/drag/end,而zoom是单纯的事件类型。

DragBehavior函数通过drag函数生成,并通过链式调用可以修改其内部参数。并最终通过call函数绑定到Selection实例上。

let symbolDragBeh: DragBehavior<SVGPathElement, Vector1d, SubjectPosition | Vector1d>;
symbolDragBeh = drag<SVGPathElement, Vector1d>().on("start", SymbolDragStart).on("drag", SymbolDragMove).on("end", SymbolDragEnd);node.select<SVGGElement>(".symbolElement").selectAll<SVGPathElement, Vector1d>("path").data<Vector1d>(pointsXY).join("path").attr("d", symbol<Vector1d>(symbolCircle, 10)).attr("pointnum", (d: Vector1d, i: number, a: Array<SVGPathElement> | ArrayLike<SVGPathElement>) => i).style("stroke", "black").style("fill", "white").attr("transform", SymbolTransform).on("click", ClickSymbol);.call(symbolDragBeh)

注意,这里click和drag是两个完全不同的事件,要分别绑定,而且绑定的方法不一样。因为drag事件非常复杂,要分为start/drag/end这三个步骤。

而这3个步骤所对应的响应函数则是这样的:

let SymbolDragStart: (this: SVGPathElement, event: MouseEvent, d: Vector1d) => void = function (event, d) {emit('DragChangeCurrentPoint', Number.parseInt(d.getAttribute('pointnum')))
};let SymbolDragMove: (this: SVGPathElement, event: MouseEvent, d: Vector1d) => void = function (event, d) {emit('DragEditPoint', [event.x, event.y])
};
let SymbolDragEnd: (this: SVGPathElement, event: MouseEvent, d: Vector1d) => void = function (event, d) {emit('DragEnd')
};

可以看到,这就是3个常规的事件响应函数,只要按照正常的事件响应函数编写即可。

vue3和d3的集成

直接在template中定义svg的DOM树

首先,最好是在vue3的单文件组件的template中直接定义好DOM树,尤其是svg的树。

市面上很多代码非常乐于利用ts/js在代码中生成DOM树节点,这也是前端进入框架时代以后的绝大多数风格。

这样不是说不行,但是代码运行效率会低一些,尤其是在使用d3的时候会比较敏感。这倒不是说d3会使得这样的代码执行效率更慢,而是因为d3是一个高效的库,一般来讲,用到d3的项目对于运行速度往往比其他项目要求更苛刻。

在vue3大型项目中,可以在单文件组件的template段直接把svg的基本DOM树定义好,然后使用ts来直接操作这些DOM节点,这样会快一些。

在vue3中d3怎样select

模板引用 | Vue.js

根据官方文档,通过ref函数可以获取template中定义好的DOM节点。然后把这个节点直接传给d3的select函数即可。要注意的点是:

1) 因为ref是响应式的,要通过.value属性来获取其本来值。

2) 从语法上讲,ref有可能是null,所以要通过后缀!明确告诉typescript这个值非空

生命周期

根据vue3的官方文档

https://cn.vuejs.org/guide/essentials/lifecycle.html

在beforeMount和mounted这两个钩子中间,DOM被编译并渲染。如果是在template中定义svg的DOM树,那么到了mounted这个钩子的时候,DOM才真实存在;在此之前,不存在。所以如果在不存在的时候select,那就只能返回空的选择集。

所以所有的Selection对象实例都可以在setup阶段定义,但是具体的赋值却必须要等到onMounted()中进行才可以。否则一旦select为空,则后续所有操作都是虚无,最后在渲染完成后,虽然代码执行看起来毫无错误,但是页面上却什么也没有。

当然,如果不在template中定义svg的DOM树,而是通过d3的create/append函数在setup阶段创建svg相关节点,那可以忽略生命周期的影响,不过速度会略微慢一点。

<template><svg xmlns="http://www.w3.org/2000/svg" ref="svgElement"></svg>
</template>
import { ref, Ref, onMounted } from "vue";
import { select, Selection } from "d3";let svgElement: Ref<SVGSVGElement | null> = ref<SVGSVGElement | null>(null);
let d3svgElement: Selection<SVGSVGElement, unknown, null, undefined>;onMounted(():void=>{d3svgElement = select(svgElement.value!)
})

在上一篇帖子中

https://blog.csdn.net/silent_missile/article/details/138819959

我用vite创建了一个新的vue3项目,然后把d3代码嵌入其中,并使用了composite api的setup,所以setup中所有的select都没有选中,测试显示代码运行没有任何错误,但是图形就是显示不出来。这是因为如果select目标为空,d3是不会报错的。

然后我用了vite创建了一个新的纯typescript项目,并把DOM用硬编码写在HTML里,就能正确显示。

然后我不再使用template硬编码svg的DOM树,而是采用d3的create/append创建DOM节点作为d3的Selection,这样也没问题。

最终我确认这是vue3的生命周期的问题,并把所有的select移动到onMounted里,这才算解决了这个问题。

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

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

相关文章

go-Expect-实验

实验脚本程序 test.sh read -p "Would you like to rejoin it to the cluster? [y/N]:" v echo if [ "$v" "y" ];thenecho "$(date %s)shark A" >> ./test.log fi read -p "Would you like to rejoin it to the cluste…

JDK14和JDK1.14相同吗?

关于Java开发工具包&#xff08;JDK&#xff09;的命名&#xff0c;我们可以从Java版本的发展历史来详细解析其命名规则。 在Java的早期版本中&#xff0c;JDK的版本号使用了“1.x”的格式&#xff0c;其中“x”是版本号&#xff0c;如JDK 1.0、JDK 1.1、JDK 1.2等。然而&…

Zynq UltraScale+ RFSoC 配置存储器器件

Zynq UltraScale RFSoC 配置存储器器件 下表所示闪存器件支持通过 Vivado 软件对 Zynq UltraScale RFSoC 器件执行擦除、空白检查、编程和验证等配置操 作。 本附录中的表格所列赛灵思系列非易失性存储器将不断保持更新 &#xff0c; 并支持通过 Vivado 软件对其中所列…

【C语言】6.C语言VS实用调试技巧(2)

文章目录 6.调试举例17.调试举例28.调试举例3&#xff1a;数组9.编程常⻅错误归类9.1 编译型错误9.2 链接型错误9.3 运⾏时错误 – 6.调试举例1 求 1!2!3!4!…10! 的和。 int main() {int n 0;int i 0;int ret 1;int sum 0;for (n 1; n < 3; n) {for (i 1; i < …

knife4j案例

1.导入 <dependency><groupId>com.github.xiaoymin</groupId><artifactId>knife4j-spring-boot-starter</artifactId> </dependency>2.在配置类中加入 knife4j 相关配置并设置静态资源映射&#xff08;否则接口文档页面无法访问&#xff…

就这?轻轻松松在RK356X Android11适配ML307R Cat.1模组

开源鸿蒙硬件方案领跑者 触觉智能 Industio 本文基于IDO-SXB3568主板&#xff0c;介绍Android11平台上适配中移物联ML307R Cat.1 4G模组的方法。该方法适用于触觉所有RK356X的主板。 IDO-SXB3568是触觉智能推出的RK3568行业主板&#xff0c;预计6月上旬正式上架售卖。该行业主…

CCF PTA 2022年11月C++大富翁游戏

【问题描述】 小明很喜欢玩大富翁游戏&#xff0c;这个游戏的规则如下&#xff1a; 1、游戏地图是有 N 个格子&#xff0c;分别编号从 1 到 N。玩家一开始位于 1 号格子。 2、地图的每个格子上都有事件&#xff0c;事件有以下两种类型&#xff1a; A&#xff09;罚款 x 枚金币…

使用Locust进行性能测试:快速指南与Python代码示例

性能测试是确保应用能够在预期负载下正常运行的关键步骤。在各种性能测试工具中&#xff0c;Locust以其轻量级、易用性以及可扩展性脱颖而出。它是一个用Python编写的开源性能测试工具&#xff0c;不仅能够模拟数以万计的用户对网站或应用程序进行压力测试&#xff0c;还允许测…

鸿蒙OS开发:【一次开发,多端部署】(应用UX设计原则)

应用UX设计原则 设计原则 当为多种不同的设备开发应用时&#xff0c;有如下设计原则&#xff1a; 差异性 充分了解所要支持的设备&#xff0c;包括屏幕尺寸、交互方式、使用场景、用户人群等&#xff0c;对设备的特性进行针对性的设计。 一致性 除了要考虑每个设备的特性…

shell脚本基础(简单if结构)

简单if结构 简单的if结构是&#xff1a; if expression then command command … fi 在使用这种简单if结构时&#xff0c;要特别注意测试条件后如果没有“&#xff1b;”&#xff0c;则then语句要换行&#xff0c;否则会产生不必要的错误。如果if和then可以处于同一行&#xff…

54.指针

目录 一.什么是指针&#xff1f; 二&#xff0e;定义一个指针变量 三&#xff0e;指针变量类型 四&#xff0e;取地址运算符& 五.取值运算符* 六.视频教程 一.什么是指针&#xff1f; 口语中的指针一般指指针变量&#xff0c;指针变量存放的是一个地址。普通变量存放…

电脑缺失api-ms-win-crt-runtime-l1-1-0.dll文件的几种修复方法

当您在使用电脑过程中遇到程序启动失败&#xff0c;提示缺少“api-ms-win-crt-runtime-l1-1-0.dll”文件时&#xff0c;不必过于焦虑&#xff0c;此问题通常与Windows系统的Visual C Redistributable组件未正确安装或损坏有关。小编将介绍5种修复电脑缺失api-ms-win-crt-runtim…

计算机毕业设计 | vue+springboot电影票售卖 影院售票商城 电影管理系统(附源码+论文)

1&#xff0c;项目背景 目的&#xff1a;本课题主要目标是设计并能够实现一个基于web网页的电影院购票选座系统&#xff0c;整个网站项目使用了B/S架构&#xff0c;基于vue和SpringBoot框架下开发&#xff1b;管理员通过后台管理系统实现管理影院信息&#xff0c;电影信息&…

[OpenGL] 点光源阴影(万向阴影贴图)

本章节源码 点击此处 文档持续更新 一 为什么采用点透视投影 透视投影: 由于点光源是一个点向四周发散的光线,所以这将导致点光源会以不同的角度到达场景中的不同表面&#xff0c;造成近大远小的效果,所以要采用透视投影矩阵来处理点光源的阴影,透视投影能够正确反映这种随着…

华为正式放弃高通芯片 | 百能云芯

5月15日&#xff0c;据外媒最新报道&#xff0c;高通公司正式确认&#xff0c;华为已无需依赖其处理器供应。 在出口许可被正式吊销前&#xff0c;高通的首席财务官已公开表示&#xff0c;预计明年与华为之间的芯片销售将为零&#xff0c;因为华为决定不再从高通购买4G芯片。 报…

[AI智能摄像头]RV1126部署yolov5并加速

导出onnx模型 yolov5官方地址 git clone https://github.com/ultralytics/yolov5 利用官方命令导出python export.py --weights yolov5n.pt --include onnx 利用代码导出 import os import sys os.chdir(sys.path[0]) import onnx import torch sys.path.append(..) from m…

在微信小程序项目中安装和使用 Vant 组件库

vant Wwapp 小程序开发组件库官网 Vant Weapp - 轻量、可靠的小程序 UI 组件库 安装 Vant 组件库 1.在微信小程序项目文件目录的空白位置右键&#xff0c;选择在外部终端窗口中打开 2在命令行输入如下命令&#xff08;在项目中创建包管理配置文件 package.json&#xff09; …

代码随想录Day28

17.电话号码的字母组合 题目&#xff1a;17. 电话号码的字母组合 - 力扣&#xff08;LeetCode&#xff09; 思路&#xff1a;有点难&#xff0c;每个数字对应几个字母&#xff0c;首先要建立这种映射&#xff0c;之后才能根据传进去的digits找到要用哪些字母集进行组合&#xf…

django drf 写view 视图有多少中方法

在Django REST Framework (DRF) 中&#xff0c;编写视图有几种常见的方法&#xff1a; Function-Based Views (基于函数的视图): 使用函数编写视图是最简单和最直接的方式。您可以在函数中编写逻辑来处理请求&#xff0c;并返回适当的响应。这些视图函数可以直接映射到特定的 U…

Service Worker的生命周期和全局对象和API

Service Worker的生命周期和全局对象和API 当我们注册了Service Worker后&#xff0c;它会经历生命周期的各个阶段&#xff0c;同时会触发相应的事件。整个生命周期包括了&#xff1a;installing --> installed --> activating --> activated --> redundant。当Se…