添加debounce函数实现

drawApp添加绑定/取消绑定表单对象(处理表单更新覆盖图形变换等操作)
调整图形选中事件发布逻辑和触发总的图形选中事件发布
This commit is contained in:
walker 2023-09-18 11:15:40 +08:00
parent cc737578ba
commit 3e1bbc92bf
10 changed files with 209 additions and 49 deletions

View File

@ -5,7 +5,7 @@
outlined outlined
v-model.number="linkModel.lineWidth" v-model.number="linkModel.lineWidth"
type="number" type="number"
@blur="onUpdate" @blur="onFormUpdate"
label="线宽" label="线宽"
lazy-rules lazy-rules
:rules="[(val) => (val && val > 0) || '画布宽必须大于0']" :rules="[(val) => (val && val > 0) || '画布宽必须大于0']"
@ -14,7 +14,7 @@
<q-input <q-input
outlined outlined
v-model="linkModel.lineColor" v-model="linkModel.lineColor"
@blur="onUpdate" @blur="onFormUpdate"
label="线色" label="线色"
lazy-rules lazy-rules
:rules="[(val) => (val && val.length > 0) || '线色不能为空']" :rules="[(val) => (val && val.length > 0) || '线色不能为空']"
@ -27,7 +27,7 @@
@change=" @change="
(val) => { (val) => {
linkModel.lineColor = val; linkModel.lineColor = val;
onUpdate(); onFormUpdate();
} }
" "
/> />
@ -50,7 +50,7 @@
outlined outlined
v-model.number="linkModel.segmentsCount" v-model.number="linkModel.segmentsCount"
type="number" type="number"
@blur="onUpdate" @blur="onFormUpdate"
label="曲线分段数量" label="曲线分段数量"
lazy-rules lazy-rules
:rules="[(val) => (val && val > 0) || '曲线分段数量必须大于0']" :rules="[(val) => (val && val > 0) || '曲线分段数量必须大于0']"
@ -62,34 +62,41 @@
import { LinkData } from 'src/examples/app/graphics/LinkInteraction'; import { LinkData } from 'src/examples/app/graphics/LinkInteraction';
import { Link } from 'src/graphics/link/Link'; import { Link } from 'src/graphics/link/Link';
import { useDrawStore } from 'src/stores/draw-store'; import { useDrawStore } from 'src/stores/draw-store';
import { onMounted, reactive, watch } from 'vue'; import { onMounted, onUnmounted, reactive, watch } from 'vue';
const drawStore = useDrawStore(); const drawStore = useDrawStore();
const linkModel = reactive(new LinkData()); const linkModel = reactive(new LinkData());
drawStore.$subscribe; drawStore.$subscribe;
watch( // watch(
() => drawStore.selectedGraphic, // () => drawStore.selectedGraphic,
(val) => { // (val) => {
if (val && val.type == Link.Type) { // console.log('');
// console.log('link'); // if (val && val.type == Link.Type) {
linkModel.copyFrom(val.saveData() as LinkData); // console.log('link');
} // linkModel.copyFrom(val.saveData() as LinkData);
} // }
); // }
// );
onMounted(() => { onMounted(() => {
// console.log('link mounted'); // console.log('link mounted');
const link = drawStore.selectedGraphic as Link; drawStore.bindFormData(linkModel);
if (link) { // const link = drawStore.selectedGraphic as Link;
linkModel.copyFrom(link.saveData()); // if (link) {
} // linkModel.copyFrom(link.saveData());
// }
}); });
function onUpdate() { onUnmounted(() => {
console.log('link 属性更新'); console.log('link 属性表单 unmounted');
drawStore.unbindFormData(linkModel);
});
function onFormUpdate() {
const link = drawStore.selectedGraphic as Link; const link = drawStore.selectedGraphic as Link;
if (link) { if (link) {
console.log('link 属性更新', link);
drawStore.getDrawApp().updateGraphicAndRecord(link, linkModel); drawStore.getDrawApp().updateGraphicAndRecord(link, linkModel);
} }
} }

View File

@ -15,7 +15,7 @@
import { SignalData } from 'src/examples/app/graphics/SignalInteraction'; import { SignalData } from 'src/examples/app/graphics/SignalInteraction';
import { Signal } from 'src/graphics/signal/Signal'; import { Signal } from 'src/graphics/signal/Signal';
import { useDrawStore } from 'src/stores/draw-store'; 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 drawStore = useDrawStore();
const signalModel = reactive(new SignalData()); const signalModel = reactive(new SignalData());
@ -40,6 +40,7 @@ watch(
); );
onMounted(() => { onMounted(() => {
drawStore.bindFormData(signalModel);
const signal = drawStore.selectedGraphic as Signal; const signal = drawStore.selectedGraphic as Signal;
if (signal) { if (signal) {
signalModel.copyFrom(signal.saveData()); signalModel.copyFrom(signal.saveData());
@ -47,6 +48,10 @@ onMounted(() => {
} }
}); });
onUnmounted(() => {
drawStore.unbindFormData(signalModel);
});
function onUpdate() { function onUpdate() {
signalModel.direction = (directionSelect as never)[signalDirection.value]; signalModel.direction = (directionSelect as never)[signalDirection.value];
const signal = drawStore.selectedGraphic as Signal; const signal = drawStore.selectedGraphic as Signal;

View File

@ -22,7 +22,15 @@ import {
} from '../plugins'; } from '../plugins';
import { CommonMouseTool } from '../plugins/CommonMousePlugin'; import { CommonMouseTool } from '../plugins/CommonMousePlugin';
import { MenuItemOptions } from '../ui/Menu'; import { MenuItemOptions } from '../ui/Menu';
import { DOWN, LEFT, RIGHT, UP, recursiveChildren } from '../utils'; import {
DOWN,
DebouncedFunction,
LEFT,
RIGHT,
UP,
debounce,
recursiveChildren,
} from '../utils';
import { import {
GraphicDataUpdateOperation, GraphicDataUpdateOperation,
UpdateCanvasOperation, UpdateCanvasOperation,
@ -233,6 +241,16 @@ export interface IDrawApp extends IGraphicApp {
* @param data * @param data
*/ */
updateGraphicAndRecord(g: JlGraphic, data: GraphicData): void; 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[] = []; drawAssistants: DrawAssistant[] = [];
_drawing = false; _drawing = false;
private debouncedFormDataUpdator: DebouncedFunction<(g: JlGraphic) => void>;
get drawing(): boolean { get drawing(): boolean {
return this._drawing; return this._drawing;
} }
@ -274,6 +294,9 @@ export class JlDrawApp extends GraphicApp implements IDrawApp {
this.appOperationRecord(); this.appOperationRecord();
// 绑定通用键盘操作 // 绑定通用键盘操作
this.bindKeyboardOperation(); this.bindKeyboardOperation();
this.formDataSyncListen();
this.debouncedFormDataUpdator = debounce(this.doFormDataUpdate, 60);
} }
setOptions(options: DrawAppOptions): void { setOptions(options: DrawAppOptions): void {
@ -484,6 +507,57 @@ export class JlDrawApp extends GraphicApp implements IDrawApp {
graphic.eventMode = 'static'; graphic.eventMode = 'static';
graphic.selectable = true; graphic.selectable = true;
graphic.draggable = 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) { updateCanvasAndRecord(data: ICanvasProperties) {

View File

@ -46,6 +46,7 @@ import {
} from '../plugins/KeyboardPlugin'; } from '../plugins/KeyboardPlugin';
import { ContextMenu, ContextMenuPlugin } from '../ui/ContextMenu'; import { ContextMenu, ContextMenuPlugin } from '../ui/ContextMenu';
import { MenuItemOptions } from '../ui/Menu'; import { MenuItemOptions } from '../ui/Menu';
import { DebouncedFunction, debounce } from '../utils';
import { getRectangleCenter, recursiveChildren } from '../utils/GraphicUtils'; import { getRectangleCenter, recursiveChildren } from '../utils/GraphicUtils';
import { import {
GraphicCreateOperation, GraphicCreateOperation,
@ -317,6 +318,7 @@ export interface GraphicAppEvents extends GlobalMixins.GraphicAppEvents {
'options-update': [options: GraphicAppOptions]; // 配置更新 'options-update': [options: GraphicAppOptions]; // 配置更新
graphicselectedchange: [graphic: JlGraphic, selected: boolean]; graphicselectedchange: [graphic: JlGraphic, selected: boolean];
graphicchildselectedchange: [child: DisplayObject, selected: boolean]; graphicchildselectedchange: [child: DisplayObject, selected: boolean];
graphicselected: [graphics: JlGraphic[]];
'viewport-scaled': [vp: Viewport]; 'viewport-scaled': [vp: Viewport];
drag_op_start: [event: AppDragEvent]; drag_op_start: [event: AppDragEvent];
drag_op_move: [event: AppDragEvent]; drag_op_move: [event: AppDragEvent];
@ -509,11 +511,6 @@ export interface IGraphicScene extends EventEmitter<GraphicAppEvents> {
* @param graphics * @param graphics
*/ */
updateSelected(...graphics: JlGraphic[]): void; updateSelected(...graphics: JlGraphic[]): void;
/**
*
* @param graphic
*/
fireSelectedChange(graphic: JlGraphic): void;
/** /**
* *
*/ */
@ -563,6 +560,8 @@ abstract class GraphicSceneBase
menuPlugin: ContextMenuPlugin; // 菜单插件 menuPlugin: ContextMenuPlugin; // 菜单插件
private debounceEmitFunc: DebouncedFunction<() => void>;
wsMsgBroker: AppWsMsgBroker; // websocket消息代理 wsMsgBroker: AppWsMsgBroker; // websocket消息代理
constructor(options: GraphicAppOptions) { constructor(options: GraphicAppOptions) {
super(); super();
@ -628,6 +627,11 @@ abstract class GraphicSceneBase
this.menuPlugin = new ContextMenuPlugin(this); this.menuPlugin = new ContextMenuPlugin(this);
this.wsMsgBroker = new AppWsMsgBroker(this); this.wsMsgBroker = new AppWsMsgBroker(this);
this.debounceEmitFunc = debounce(this.doEmitAppGraphicSelected, 50);
this.on('graphicselectedchange', () => {
this.debounceEmitFunc(this);
});
} }
abstract get app(): GraphicApp; abstract get app(): GraphicApp;
@ -883,12 +887,10 @@ abstract class GraphicSceneBase
// graphic可能是vue的Proxy对象会导致canvas删除时因不是同一个对象而无法从画布移除 // graphic可能是vue的Proxy对象会导致canvas删除时因不是同一个对象而无法从画布移除
const g = this.graphicStore.deleteGraphics(graphic); const g = this.graphicStore.deleteGraphics(graphic);
if (g) { if (g) {
// 清除选中
g.updateSelected(false);
// 从画布移除 // 从画布移除
this.canvas.removeGraphic(g); this.canvas.removeGraphic(g);
// 清除选中
if (g.updateSelected(false)) {
this.fireSelectedChange(g);
}
// 对象删除处理 // 对象删除处理
g.onDelete(); g.onDelete();
this.emit('graphicdeleted', g); this.emit('graphicdeleted', g);
@ -936,15 +938,6 @@ abstract class GraphicSceneBase
this.updateSelected(...this.queryStore.getAllGraphics()); 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) { if (graphic.selected) {
graphic.updateSelected(false); graphic.updateSelected(false);
this.fireSelectedChange(graphic);
} }
}); });
graphics.forEach((graphic) => { graphics.forEach((graphic) => {
if (graphic.updateSelected(true)) { graphic.updateSelected(true);
this.fireSelectedChange(graphic);
}
}); });
} }
private doEmitAppGraphicSelected(): void {
// 场景发布图形选中
this.emit('graphicselected', this.selectedGraphics);
// this.app.emit('graphicselected', this.selectedGraphics);
}
/** /**
* *
* @param param * @param param

View File

@ -642,6 +642,10 @@ export abstract class JlGraphic extends Container {
this.removeAllChildSelected(); this.removeAllChildSelected();
this.emit('unselected', this); this.emit('unselected', this);
} }
const app = this.getGraphicApp();
if (app) {
app.emit('graphicselectedchange', this, this.selected);
}
} }
hasSelectedChilds(): boolean { hasSelectedChilds(): boolean {
@ -678,11 +682,16 @@ export abstract class JlGraphic extends Container {
}); });
} }
fireChildSelected(child: DisplayObject) { fireChildSelected(child: DisplayObject) {
if (child.selected) { const selected = child.selected;
if (selected) {
this.emit('childselected', child); this.emit('childselected', child);
} else { } else {
this.emit('childunselected', child); this.emit('childunselected', child);
} }
const app = this.getGraphicApp();
if (app) {
app.emit('graphicchildselectedchange', child, selected);
}
} }
exitChildEdit() { exitChildEdit() {
this.childEdit = false; this.childEdit = false;

View File

@ -271,7 +271,6 @@ export class CommonMouseTool extends AppInteractionPlugin {
graphic.invertChildSelected(target); graphic.invertChildSelected(target);
} else { } else {
graphic.invertSelected(); graphic.invertSelected();
app.fireSelectedChange(graphic);
} }
} }
} else { } else {

View File

@ -1,3 +1,4 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
import { import {
Container, Container,
DisplayObject, DisplayObject,
@ -17,9 +18,11 @@ import { JlGraphic } from '../core';
import { AbsorbablePosition, VectorText } from '../graphic'; import { AbsorbablePosition, VectorText } from '../graphic';
import { DraggablePoint } from '../graphic/DraggablePoint'; import { DraggablePoint } from '../graphic/DraggablePoint';
import { import {
DebouncedFunction,
angleToAxisx, angleToAxisx,
calculateLineMidpoint, calculateLineMidpoint,
convertRectangleToPolygonPoints, convertRectangleToPolygonPoints,
debounce,
distance, distance,
recursiveChildren, recursiveChildren,
} from '../utils'; } from '../utils';
@ -298,6 +301,8 @@ export class GraphicTransformPlugin extends InteractionPluginBase {
ap.tryAbsorb(...targets); ap.tryAbsorb(...targets);
} }
} }
// const start = new Date().getTime();
// 事件发布 // 事件发布
targets.forEach((target) => { targets.forEach((target) => {
if (target.shiftStartPoint && target.shiftLastPoint) { 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, alpha: 1,
}; };
obj: DisplayObject; obj: DisplayObject;
debouncedRedraw: DebouncedFunction<() => void>;
constructor(graphic: DisplayObject) { constructor(graphic: DisplayObject) {
super(); super();
this.obj = graphic; this.obj = graphic;
this.name = BoundsGraphic.Name; this.name = BoundsGraphic.Name;
this.visible = false; this.visible = false;
this.debouncedRedraw = debounce(this.doRedraw, 50);
this.obj.on('transformstart', this.onObjTransformStart, this); this.obj.on('transformstart', this.onObjTransformStart, this);
this.obj.on('transformend', this.onObjTransformEnd, this); this.obj.on('transformend', this.onObjTransformEnd, this);
if (this.obj.children && this.obj.children.length > 0) { if (this.obj.children && this.obj.children.length > 0) {
@ -880,7 +890,6 @@ export class BoundsGraphic extends Graphics {
onGraphicRepaint(): void { onGraphicRepaint(): void {
if (this.visible) { if (this.visible) {
this.redraw(); this.redraw();
this.visible = true;
} }
} }
@ -892,8 +901,13 @@ export class BoundsGraphic extends Graphics {
} }
redraw() { redraw() {
this.debouncedRedraw(this);
}
doRedraw() {
const visible = this.visible;
this.visible = false; // 屏蔽包围框本身 this.visible = false; // 屏蔽包围框本身
const bounds = new Polygon(this.obj.localBoundsToCanvasPoints()); const bounds = new Polygon(this.obj.localBoundsToCanvasPoints());
this.clear().lineStyle(BoundsGraphic.BoundsLineStyle).drawShape(bounds); this.clear().lineStyle(BoundsGraphic.BoundsLineStyle).drawShape(bounds);
this.visible = visible;
} }
} }

View 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;
}

View File

@ -3,6 +3,8 @@ import { Point, Rectangle } from 'pixi.js';
export * from './GraphicUtils'; export * from './GraphicUtils';
export * from './IntersectUtils'; export * from './IntersectUtils';
export * from './debounce';
export const UP: Point = new Point(0, -1); export const UP: Point = new Point(0, -1);
export const DOWN: Point = new Point(0, 1); export const DOWN: Point = new Point(0, 1);
export const LEFT: Point = new Point(-1, 0); export const LEFT: Point = new Point(-1, 0);

View File

@ -1,6 +1,6 @@
import { defineStore } from 'pinia'; import { defineStore } from 'pinia';
import { destroyDrawApp, getDrawApp, initDrawApp } from 'src/examples/app'; 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'; import { IJlCanvas } from 'src/jlgraphic/app/JlGraphicApp';
export const useDrawStore = defineStore('draw', { export const useDrawStore = defineStore('draw', {
@ -52,6 +52,22 @@ export const useDrawStore = defineStore('draw', {
getJlCanvas(): IJlCanvas { getJlCanvas(): IJlCanvas {
return this.getDrawApp().canvas; 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() { initDrawApp() {
const app = initDrawApp(); const app = initDrawApp();
app.on('interaction-plugin-resume', (plugin) => { 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 = app.selectedGraphics;
}); });
this.selectedGraphics = []; this.selectedGraphics = [];