添加debounce函数实现
drawApp添加绑定/取消绑定表单对象(处理表单更新覆盖图形变换等操作) 调整图形选中事件发布逻辑和触发总的图形选中事件发布
This commit is contained in:
parent
cc737578ba
commit
3e1bbc92bf
@ -5,7 +5,7 @@
|
||||
outlined
|
||||
v-model.number="linkModel.lineWidth"
|
||||
type="number"
|
||||
@blur="onUpdate"
|
||||
@blur="onFormUpdate"
|
||||
label="线宽"
|
||||
lazy-rules
|
||||
:rules="[(val) => (val && val > 0) || '画布宽必须大于0']"
|
||||
@ -14,7 +14,7 @@
|
||||
<q-input
|
||||
outlined
|
||||
v-model="linkModel.lineColor"
|
||||
@blur="onUpdate"
|
||||
@blur="onFormUpdate"
|
||||
label="线色"
|
||||
lazy-rules
|
||||
:rules="[(val) => (val && val.length > 0) || '线色不能为空']"
|
||||
@ -27,7 +27,7 @@
|
||||
@change="
|
||||
(val) => {
|
||||
linkModel.lineColor = val;
|
||||
onUpdate();
|
||||
onFormUpdate();
|
||||
}
|
||||
"
|
||||
/>
|
||||
@ -50,7 +50,7 @@
|
||||
outlined
|
||||
v-model.number="linkModel.segmentsCount"
|
||||
type="number"
|
||||
@blur="onUpdate"
|
||||
@blur="onFormUpdate"
|
||||
label="曲线分段数量"
|
||||
lazy-rules
|
||||
:rules="[(val) => (val && val > 0) || '曲线分段数量必须大于0']"
|
||||
@ -62,34 +62,41 @@
|
||||
import { LinkData } from 'src/examples/app/graphics/LinkInteraction';
|
||||
import { Link } from 'src/graphics/link/Link';
|
||||
import { useDrawStore } from 'src/stores/draw-store';
|
||||
import { onMounted, reactive, watch } from 'vue';
|
||||
import { onMounted, onUnmounted, reactive, watch } from 'vue';
|
||||
|
||||
const drawStore = useDrawStore();
|
||||
const linkModel = reactive(new LinkData());
|
||||
|
||||
drawStore.$subscribe;
|
||||
watch(
|
||||
() => drawStore.selectedGraphic,
|
||||
(val) => {
|
||||
if (val && val.type == Link.Type) {
|
||||
// console.log('link变更');
|
||||
linkModel.copyFrom(val.saveData() as LinkData);
|
||||
}
|
||||
}
|
||||
);
|
||||
// watch(
|
||||
// () => drawStore.selectedGraphic,
|
||||
// (val) => {
|
||||
// console.log('监控');
|
||||
// if (val && val.type == Link.Type) {
|
||||
// console.log('link变更');
|
||||
// linkModel.copyFrom(val.saveData() as LinkData);
|
||||
// }
|
||||
// }
|
||||
// );
|
||||
|
||||
onMounted(() => {
|
||||
// console.log('link 属性表单 mounted');
|
||||
const link = drawStore.selectedGraphic as Link;
|
||||
if (link) {
|
||||
linkModel.copyFrom(link.saveData());
|
||||
}
|
||||
drawStore.bindFormData(linkModel);
|
||||
// const link = drawStore.selectedGraphic as Link;
|
||||
// if (link) {
|
||||
// linkModel.copyFrom(link.saveData());
|
||||
// }
|
||||
});
|
||||
|
||||
function onUpdate() {
|
||||
console.log('link 属性更新');
|
||||
onUnmounted(() => {
|
||||
console.log('link 属性表单 unmounted');
|
||||
drawStore.unbindFormData(linkModel);
|
||||
});
|
||||
|
||||
function onFormUpdate() {
|
||||
const link = drawStore.selectedGraphic as Link;
|
||||
if (link) {
|
||||
console.log('link 属性更新', link);
|
||||
drawStore.getDrawApp().updateGraphicAndRecord(link, linkModel);
|
||||
}
|
||||
}
|
||||
|
@ -15,7 +15,7 @@
|
||||
import { SignalData } from 'src/examples/app/graphics/SignalInteraction';
|
||||
import { Signal } from 'src/graphics/signal/Signal';
|
||||
import { useDrawStore } from 'src/stores/draw-store';
|
||||
import { onMounted, reactive, ref, watch } from 'vue';
|
||||
import { onMounted, onUnmounted, reactive, ref, watch } from 'vue';
|
||||
|
||||
const drawStore = useDrawStore();
|
||||
const signalModel = reactive(new SignalData());
|
||||
@ -40,6 +40,7 @@ watch(
|
||||
);
|
||||
|
||||
onMounted(() => {
|
||||
drawStore.bindFormData(signalModel);
|
||||
const signal = drawStore.selectedGraphic as Signal;
|
||||
if (signal) {
|
||||
signalModel.copyFrom(signal.saveData());
|
||||
@ -47,6 +48,10 @@ onMounted(() => {
|
||||
}
|
||||
});
|
||||
|
||||
onUnmounted(() => {
|
||||
drawStore.unbindFormData(signalModel);
|
||||
});
|
||||
|
||||
function onUpdate() {
|
||||
signalModel.direction = (directionSelect as never)[signalDirection.value];
|
||||
const signal = drawStore.selectedGraphic as Signal;
|
||||
|
@ -22,7 +22,15 @@ import {
|
||||
} from '../plugins';
|
||||
import { CommonMouseTool } from '../plugins/CommonMousePlugin';
|
||||
import { MenuItemOptions } from '../ui/Menu';
|
||||
import { DOWN, LEFT, RIGHT, UP, recursiveChildren } from '../utils';
|
||||
import {
|
||||
DOWN,
|
||||
DebouncedFunction,
|
||||
LEFT,
|
||||
RIGHT,
|
||||
UP,
|
||||
debounce,
|
||||
recursiveChildren,
|
||||
} from '../utils';
|
||||
import {
|
||||
GraphicDataUpdateOperation,
|
||||
UpdateCanvasOperation,
|
||||
@ -233,6 +241,16 @@ export interface IDrawApp extends IGraphicApp {
|
||||
* @param data
|
||||
*/
|
||||
updateGraphicAndRecord(g: JlGraphic, data: GraphicData): void;
|
||||
/**
|
||||
* 绑定form表单对象
|
||||
* @param form
|
||||
*/
|
||||
bindFormData(form: GraphicData): void;
|
||||
/**
|
||||
* 解绑form表单对象
|
||||
* @param form
|
||||
*/
|
||||
unbindFormData(form: GraphicData): void;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -258,6 +276,8 @@ export class JlDrawApp extends GraphicApp implements IDrawApp {
|
||||
drawAssistants: DrawAssistant[] = [];
|
||||
_drawing = false;
|
||||
|
||||
private debouncedFormDataUpdator: DebouncedFunction<(g: JlGraphic) => void>;
|
||||
|
||||
get drawing(): boolean {
|
||||
return this._drawing;
|
||||
}
|
||||
@ -274,6 +294,9 @@ export class JlDrawApp extends GraphicApp implements IDrawApp {
|
||||
this.appOperationRecord();
|
||||
// 绑定通用键盘操作
|
||||
this.bindKeyboardOperation();
|
||||
this.formDataSyncListen();
|
||||
|
||||
this.debouncedFormDataUpdator = debounce(this.doFormDataUpdate, 60);
|
||||
}
|
||||
|
||||
setOptions(options: DrawAppOptions): void {
|
||||
@ -484,6 +507,57 @@ export class JlDrawApp extends GraphicApp implements IDrawApp {
|
||||
graphic.eventMode = 'static';
|
||||
graphic.selectable = true;
|
||||
graphic.draggable = true;
|
||||
graphic.on('repaint', () => {
|
||||
this.handleFormDataUpdate(graphic);
|
||||
});
|
||||
graphic.on('transformend', () => {
|
||||
this.handleFormDataUpdate(graphic);
|
||||
});
|
||||
}
|
||||
|
||||
formData: GraphicData | undefined = undefined;
|
||||
|
||||
/**
|
||||
* 绑定form表单对象
|
||||
* @param form
|
||||
*/
|
||||
bindFormData(form: GraphicData): void {
|
||||
this.formData = form;
|
||||
if (this.selectedGraphics.length == 1) {
|
||||
this.formData.copyFrom(this.selectedGraphics[0].saveData());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 移除form绑定
|
||||
* @param form
|
||||
*/
|
||||
unbindFormData(form: GraphicData): void {
|
||||
if (this.formData == form) {
|
||||
this.formData = undefined;
|
||||
}
|
||||
}
|
||||
|
||||
private formDataSyncListen(): void {
|
||||
this.on('graphicselected', () => {
|
||||
if (this.selectedGraphics.length == 1) {
|
||||
this.handleFormDataUpdate(this.selectedGraphics[0]);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理表单数据更新(使用debounce限流)
|
||||
*/
|
||||
private handleFormDataUpdate(g: JlGraphic): void {
|
||||
this.debouncedFormDataUpdator(this, g);
|
||||
}
|
||||
|
||||
private doFormDataUpdate(g: JlGraphic): void {
|
||||
if (this.selectedGraphics.length > 1) return;
|
||||
if (this.formData && g.type === this.formData.graphicType) {
|
||||
this.formData.copyFrom(g.saveData());
|
||||
}
|
||||
}
|
||||
|
||||
updateCanvasAndRecord(data: ICanvasProperties) {
|
||||
|
@ -46,6 +46,7 @@ import {
|
||||
} from '../plugins/KeyboardPlugin';
|
||||
import { ContextMenu, ContextMenuPlugin } from '../ui/ContextMenu';
|
||||
import { MenuItemOptions } from '../ui/Menu';
|
||||
import { DebouncedFunction, debounce } from '../utils';
|
||||
import { getRectangleCenter, recursiveChildren } from '../utils/GraphicUtils';
|
||||
import {
|
||||
GraphicCreateOperation,
|
||||
@ -317,6 +318,7 @@ export interface GraphicAppEvents extends GlobalMixins.GraphicAppEvents {
|
||||
'options-update': [options: GraphicAppOptions]; // 配置更新
|
||||
graphicselectedchange: [graphic: JlGraphic, selected: boolean];
|
||||
graphicchildselectedchange: [child: DisplayObject, selected: boolean];
|
||||
graphicselected: [graphics: JlGraphic[]];
|
||||
'viewport-scaled': [vp: Viewport];
|
||||
drag_op_start: [event: AppDragEvent];
|
||||
drag_op_move: [event: AppDragEvent];
|
||||
@ -509,11 +511,6 @@ export interface IGraphicScene extends EventEmitter<GraphicAppEvents> {
|
||||
* @param graphics
|
||||
*/
|
||||
updateSelected(...graphics: JlGraphic[]): void;
|
||||
/**
|
||||
* 发布选中对象改变事件
|
||||
* @param graphic
|
||||
*/
|
||||
fireSelectedChange(graphic: JlGraphic): void;
|
||||
/**
|
||||
* 选中所有图形
|
||||
*/
|
||||
@ -563,6 +560,8 @@ abstract class GraphicSceneBase
|
||||
|
||||
menuPlugin: ContextMenuPlugin; // 菜单插件
|
||||
|
||||
private debounceEmitFunc: DebouncedFunction<() => void>;
|
||||
|
||||
wsMsgBroker: AppWsMsgBroker; // websocket消息代理
|
||||
constructor(options: GraphicAppOptions) {
|
||||
super();
|
||||
@ -628,6 +627,11 @@ abstract class GraphicSceneBase
|
||||
this.menuPlugin = new ContextMenuPlugin(this);
|
||||
|
||||
this.wsMsgBroker = new AppWsMsgBroker(this);
|
||||
|
||||
this.debounceEmitFunc = debounce(this.doEmitAppGraphicSelected, 50);
|
||||
this.on('graphicselectedchange', () => {
|
||||
this.debounceEmitFunc(this);
|
||||
});
|
||||
}
|
||||
abstract get app(): GraphicApp;
|
||||
|
||||
@ -883,12 +887,10 @@ abstract class GraphicSceneBase
|
||||
// graphic可能是vue的Proxy对象,会导致canvas删除时因不是同一个对象而无法从画布移除
|
||||
const g = this.graphicStore.deleteGraphics(graphic);
|
||||
if (g) {
|
||||
// 清除选中
|
||||
g.updateSelected(false);
|
||||
// 从画布移除
|
||||
this.canvas.removeGraphic(g);
|
||||
// 清除选中
|
||||
if (g.updateSelected(false)) {
|
||||
this.fireSelectedChange(g);
|
||||
}
|
||||
// 对象删除处理
|
||||
g.onDelete();
|
||||
this.emit('graphicdeleted', g);
|
||||
@ -936,15 +938,6 @@ abstract class GraphicSceneBase
|
||||
this.updateSelected(...this.queryStore.getAllGraphics());
|
||||
}
|
||||
|
||||
/**
|
||||
* 发送选中变化事件
|
||||
* @param graphic
|
||||
*/
|
||||
fireSelectedChange(graphic: JlGraphic) {
|
||||
// console.log('通知选中变化', this.selecteds)
|
||||
const select = graphic.selected;
|
||||
this.emit('graphicselectedchange', graphic, select);
|
||||
}
|
||||
/**
|
||||
* 更新选中
|
||||
*/
|
||||
@ -955,16 +948,19 @@ abstract class GraphicSceneBase
|
||||
}
|
||||
if (graphic.selected) {
|
||||
graphic.updateSelected(false);
|
||||
this.fireSelectedChange(graphic);
|
||||
}
|
||||
});
|
||||
graphics.forEach((graphic) => {
|
||||
if (graphic.updateSelected(true)) {
|
||||
this.fireSelectedChange(graphic);
|
||||
}
|
||||
graphic.updateSelected(true);
|
||||
});
|
||||
}
|
||||
|
||||
private doEmitAppGraphicSelected(): void {
|
||||
// 场景发布图形选中
|
||||
this.emit('graphicselected', this.selectedGraphics);
|
||||
// this.app.emit('graphicselected', this.selectedGraphics);
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新画布
|
||||
* @param param
|
||||
|
@ -642,6 +642,10 @@ export abstract class JlGraphic extends Container {
|
||||
this.removeAllChildSelected();
|
||||
this.emit('unselected', this);
|
||||
}
|
||||
const app = this.getGraphicApp();
|
||||
if (app) {
|
||||
app.emit('graphicselectedchange', this, this.selected);
|
||||
}
|
||||
}
|
||||
|
||||
hasSelectedChilds(): boolean {
|
||||
@ -678,11 +682,16 @@ export abstract class JlGraphic extends Container {
|
||||
});
|
||||
}
|
||||
fireChildSelected(child: DisplayObject) {
|
||||
if (child.selected) {
|
||||
const selected = child.selected;
|
||||
if (selected) {
|
||||
this.emit('childselected', child);
|
||||
} else {
|
||||
this.emit('childunselected', child);
|
||||
}
|
||||
const app = this.getGraphicApp();
|
||||
if (app) {
|
||||
app.emit('graphicchildselectedchange', child, selected);
|
||||
}
|
||||
}
|
||||
exitChildEdit() {
|
||||
this.childEdit = false;
|
||||
|
@ -271,7 +271,6 @@ export class CommonMouseTool extends AppInteractionPlugin {
|
||||
graphic.invertChildSelected(target);
|
||||
} else {
|
||||
graphic.invertSelected();
|
||||
app.fireSelectedChange(graphic);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
|
@ -1,3 +1,4 @@
|
||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||
import {
|
||||
Container,
|
||||
DisplayObject,
|
||||
@ -17,9 +18,11 @@ import { JlGraphic } from '../core';
|
||||
import { AbsorbablePosition, VectorText } from '../graphic';
|
||||
import { DraggablePoint } from '../graphic/DraggablePoint';
|
||||
import {
|
||||
DebouncedFunction,
|
||||
angleToAxisx,
|
||||
calculateLineMidpoint,
|
||||
convertRectangleToPolygonPoints,
|
||||
debounce,
|
||||
distance,
|
||||
recursiveChildren,
|
||||
} from '../utils';
|
||||
@ -298,6 +301,8 @@ export class GraphicTransformPlugin extends InteractionPluginBase {
|
||||
ap.tryAbsorb(...targets);
|
||||
}
|
||||
}
|
||||
// const start = new Date().getTime();
|
||||
|
||||
// 事件发布
|
||||
targets.forEach((target) => {
|
||||
if (target.shiftStartPoint && target.shiftLastPoint) {
|
||||
@ -314,6 +319,8 @@ export class GraphicTransformPlugin extends InteractionPluginBase {
|
||||
);
|
||||
}
|
||||
});
|
||||
// const dt = new Date().getTime() - start;
|
||||
// console.log('拖拽耗时', `${dt}ms`, targets);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -847,11 +854,14 @@ export class BoundsGraphic extends Graphics {
|
||||
alpha: 1,
|
||||
};
|
||||
obj: DisplayObject;
|
||||
debouncedRedraw: DebouncedFunction<() => void>;
|
||||
constructor(graphic: DisplayObject) {
|
||||
super();
|
||||
this.obj = graphic;
|
||||
this.name = BoundsGraphic.Name;
|
||||
this.visible = false;
|
||||
|
||||
this.debouncedRedraw = debounce(this.doRedraw, 50);
|
||||
this.obj.on('transformstart', this.onObjTransformStart, this);
|
||||
this.obj.on('transformend', this.onObjTransformEnd, this);
|
||||
if (this.obj.children && this.obj.children.length > 0) {
|
||||
@ -880,7 +890,6 @@ export class BoundsGraphic extends Graphics {
|
||||
onGraphicRepaint(): void {
|
||||
if (this.visible) {
|
||||
this.redraw();
|
||||
this.visible = true;
|
||||
}
|
||||
}
|
||||
|
||||
@ -892,8 +901,13 @@ export class BoundsGraphic extends Graphics {
|
||||
}
|
||||
|
||||
redraw() {
|
||||
this.debouncedRedraw(this);
|
||||
}
|
||||
doRedraw() {
|
||||
const visible = this.visible;
|
||||
this.visible = false; // 屏蔽包围框本身
|
||||
const bounds = new Polygon(this.obj.localBoundsToCanvasPoints());
|
||||
this.clear().lineStyle(BoundsGraphic.BoundsLineStyle).drawShape(bounds);
|
||||
this.visible = visible;
|
||||
}
|
||||
}
|
||||
|
37
src/jlgraphic/utils/debounce.ts
Normal file
37
src/jlgraphic/utils/debounce.ts
Normal file
@ -0,0 +1,37 @@
|
||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||
export interface DebouncedFunction<F extends (...args: any[]) => any> {
|
||||
(context: ThisParameterType<F>, ...args: Parameters<F>): void;
|
||||
cancel: () => void;
|
||||
}
|
||||
|
||||
export function debounce<F extends (...args: Parameters<F>) => any>(
|
||||
fn: F,
|
||||
waitMs = 250
|
||||
): DebouncedFunction<F> {
|
||||
let timeoutId: ReturnType<typeof setTimeout> | undefined;
|
||||
|
||||
const debouncedFunction = function (
|
||||
context: ThisParameterType<F>,
|
||||
...args: Parameters<F>
|
||||
) {
|
||||
const invokeFunction = function () {
|
||||
timeoutId = undefined;
|
||||
fn.apply(context, args);
|
||||
};
|
||||
|
||||
if (timeoutId !== undefined) {
|
||||
console.debug('debounce clear timeout', fn);
|
||||
clearTimeout(timeoutId);
|
||||
}
|
||||
|
||||
timeoutId = setTimeout(invokeFunction, waitMs);
|
||||
};
|
||||
|
||||
debouncedFunction.cancel = function () {
|
||||
if (timeoutId !== undefined) {
|
||||
clearTimeout(timeoutId);
|
||||
}
|
||||
};
|
||||
|
||||
return debouncedFunction;
|
||||
}
|
@ -3,6 +3,8 @@ import { Point, Rectangle } from 'pixi.js';
|
||||
export * from './GraphicUtils';
|
||||
export * from './IntersectUtils';
|
||||
|
||||
export * from './debounce';
|
||||
|
||||
export const UP: Point = new Point(0, -1);
|
||||
export const DOWN: Point = new Point(0, 1);
|
||||
export const LEFT: Point = new Point(-1, 0);
|
||||
|
@ -1,6 +1,6 @@
|
||||
import { defineStore } from 'pinia';
|
||||
import { destroyDrawApp, getDrawApp, initDrawApp } from 'src/examples/app';
|
||||
import { DrawAssistant, IDrawApp, JlGraphic } from 'src/jlgraphic';
|
||||
import { DrawAssistant, GraphicData, IDrawApp, JlGraphic } from 'src/jlgraphic';
|
||||
import { IJlCanvas } from 'src/jlgraphic/app/JlGraphicApp';
|
||||
|
||||
export const useDrawStore = defineStore('draw', {
|
||||
@ -52,6 +52,22 @@ export const useDrawStore = defineStore('draw', {
|
||||
getJlCanvas(): IJlCanvas {
|
||||
return this.getDrawApp().canvas;
|
||||
},
|
||||
bindFormData(form: GraphicData): void {
|
||||
console.log('绑定form数据', form);
|
||||
const app = this.getDrawApp();
|
||||
app.bindFormData(form);
|
||||
// app.app.on('graphicselectedchange', () => {
|
||||
// if (app.selectedGraphics.length == 1) {
|
||||
// const g = app.selectedGraphics[0];
|
||||
// if (g.type === form.graphicType) {
|
||||
// form.copyFrom(g.saveData());
|
||||
// }
|
||||
// }
|
||||
// });
|
||||
},
|
||||
unbindFormData(form: GraphicData): void {
|
||||
console.log('取消绑定form数据', form);
|
||||
},
|
||||
initDrawApp() {
|
||||
const app = initDrawApp();
|
||||
app.on('interaction-plugin-resume', (plugin) => {
|
||||
@ -63,7 +79,8 @@ export const useDrawStore = defineStore('draw', {
|
||||
}
|
||||
}
|
||||
});
|
||||
app.on('graphicselectedchange', () => {
|
||||
app.on('graphicselected', () => {
|
||||
console.log('批量选中事件', app.selectedGraphics.length);
|
||||
this.selectedGraphics = app.selectedGraphics;
|
||||
});
|
||||
this.selectedGraphics = [];
|
||||
|
Loading…
Reference in New Issue
Block a user