Merge remote-tracking branch 'origin/master' into bj-rtss

This commit is contained in:
fan 2023-07-14 14:41:02 +08:00
commit 83eab6c5b3
64 changed files with 5554 additions and 349 deletions

View File

@ -14,8 +14,11 @@
"build": "quasar build" "build": "quasar build"
}, },
"dependencies": { "dependencies": {
"@pixi/graphics-extras": "^7.2.4",
"@quasar/extras": "^1.0.0", "@quasar/extras": "^1.0.0",
"@stomp/stompjs": "^7.0.0", "@stomp/stompjs": "^7.0.0",
"alova": "^2.7.1",
"axios": "^1.4.0",
"google-protobuf": "^3.21.2", "google-protobuf": "^3.21.2",
"js-base64": "^3.7.5", "js-base64": "^3.7.5",
"pinia": "^2.0.11", "pinia": "^2.0.11",
@ -37,6 +40,7 @@
"eslint-plugin-vue": "^9.0.0", "eslint-plugin-vue": "^9.0.0",
"prettier": "^2.5.1", "prettier": "^2.5.1",
"protoc-gen-ts": "^0.8.6", "protoc-gen-ts": "^0.8.6",
"ts-md5": "^1.3.1",
"typescript": "^4.5.4" "typescript": "^4.5.4"
}, },
"engines": { "engines": {

View File

@ -28,7 +28,7 @@ module.exports = configure(function (/* ctx */) {
// app boot file (/src/boot) // app boot file (/src/boot)
// --> boot files are part of "main.js" // --> boot files are part of "main.js"
// https://v2.quasar.dev/quasar-cli-vite/boot-files // https://v2.quasar.dev/quasar-cli-vite/boot-files
boot: [], boot: ['axios', '@pixi/graphics-extras'],
// https://v2.quasar.dev/quasar-cli-vite/quasar-config-js#css // https://v2.quasar.dev/quasar-cli-vite/quasar-config-js#css
css: ['app.scss'], css: ['app.scss'],

View File

@ -0,0 +1,7 @@
import { boot } from 'quasar/wrappers';
import * as GraphicsExtras from '@pixi/graphics-extras';
// "async" is optional;
// more info on params: https://v2.quasar.dev/quasar-cli/boot-files
export default boot(async (/* { app, router, ... } */) => {
GraphicsExtras;
});

111
src/boot/axios.ts Normal file
View File

@ -0,0 +1,111 @@
import axios, { AxiosInstance } from 'axios';
import { AxiosError } from 'axios';
import { Dialog } from 'quasar';
import { boot } from 'quasar/wrappers';
import { getJwtToken } from 'src/examples/app/configs/TokenManage';
import { getHttpBase } from 'src/examples/app/configs/UrlManage';
declare module '@vue/runtime-core' {
interface ComponentCustomProperties {
$axios: AxiosInstance;
}
}
interface ErrorData {
status: number;
title: string;
detail: string;
code: number;
}
export class ApiError {
origin: AxiosError;
/**
*
*/
code: number;
/**
*
*/
title: string;
/**
*
*/
detail?: string;
constructor(origin: AxiosError<unknown, unknown>) {
this.origin = origin;
const response = origin.response;
if (response) {
const err = response.data as ErrorData;
this.code = err.code;
this.title = err.title;
this.detail = err.detail;
} else {
this.code = origin.status || -1;
this.title = origin.message;
}
}
static from(err: AxiosError<unknown, unknown>): ApiError {
return new ApiError(err);
}
/**
*
* @returns
*/
isAuthError(): boolean {
return this.origin.response?.status === 401;
}
}
// Be careful when using SSR for cross-request state pollution
// due to creating a Singleton instance here;
// If any client changes this (global) instance, it might be a
// good idea to move this instance creation inside of the
// "export default () => {}" function below (which runs individually
// for each client)
const api = axios.create({ baseURL: getHttpBase() });
export default boot(({ app, router }) => {
// for use inside Vue files (Options API) through this.$axios and this.$api
// 拦截请求,添加
api.interceptors.request.use(
(config) => {
config.headers.Authorization = getJwtToken();
return config;
},
(err: AxiosError) => {
return Promise.reject(ApiError.from(err));
}
);
api.interceptors.response.use(
(response) => {
return response;
},
(err) => {
if (err.response && err.response.status === 401) {
Dialog.create({
title: '认证失败',
message: '认证失败或登录超时,请重新登录',
persistent: true,
}).onOk(() => {
router.push({ name: 'login' });
});
}
return Promise.reject(ApiError.from(err));
}
);
app.config.globalProperties.$axios = axios;
// ^ ^ ^ this will allow you to use this.$axios (for Vue Options API form)
// so you won't necessarily have to import axios in each vue file
app.config.globalProperties.$api = api;
// ^ ^ ^ this will allow you to use this.$api (for Vue Options API form)
// so you can easily perform requests against your app's API
});
export { api };

View File

@ -10,9 +10,18 @@
<template v-if="drawStore.drawGraphicType === Link.Type"> <template v-if="drawStore.drawGraphicType === Link.Type">
<link-template></link-template> <link-template></link-template>
</template> </template>
<template v-if="drawStore.drawGraphicType === Rect.Type">
<rect-template></rect-template>
</template>
<template v-if="drawStore.drawGraphicType === Platform.Type"> <template v-if="drawStore.drawGraphicType === Platform.Type">
<platform-template></platform-template> <platform-template></platform-template>
</template> </template>
<template v-if="drawStore.drawGraphicType === Station.Type">
<station-template></station-template>
</template>
<template v-if="drawStore.drawGraphicType === Train.Type">
<TrainProperty></TrainProperty>
</template>
</q-card-section> </q-card-section>
</q-card> </q-card>
</div> </div>
@ -33,12 +42,24 @@
<link-property <link-property
v-if="drawStore.selectedGraphicType === Link.Type" v-if="drawStore.selectedGraphicType === Link.Type"
></link-property> ></link-property>
<rect-property
v-if="drawStore.selectedGraphicType === Rect.Type"
></rect-property>
<platform-property <platform-property
v-if="drawStore.selectedGraphicType === Platform.Type" v-if="drawStore.selectedGraphicType === Platform.Type"
></platform-property> ></platform-property>
<station-property
v-if="drawStore.selectedGraphicType === Station.Type"
></station-property>
<train-property
v-if="drawStore.selectedGraphicType === Train.Type"
></train-property>
<iscs-fan-property <iscs-fan-property
v-else-if="drawStore.selectedGraphicType === IscsFan.Type" v-else-if="drawStore.selectedGraphicType === IscsFan.Type"
></iscs-fan-property> ></iscs-fan-property>
<signal-property
v-else-if="drawStore.selectedGraphicType === Signal.Type"
></signal-property>
</q-card-section> </q-card-section>
</template> </template>
</q-card> </q-card>
@ -47,15 +68,25 @@
<script setup lang="ts"> <script setup lang="ts">
import LinkTemplate from './templates/LinkTemplate.vue'; import LinkTemplate from './templates/LinkTemplate.vue';
import RectTemplate from './templates/RectTemplate.vue';
import PlatformTemplate from './templates/PlatformTemplate.vue'; import PlatformTemplate from './templates/PlatformTemplate.vue';
import StationTemplate from './templates/StationTemplate.vue';
import CanvasProperty from './properties/CanvasProperty.vue'; import CanvasProperty from './properties/CanvasProperty.vue';
import LinkProperty from './properties/LinkProperty.vue'; import LinkProperty from './properties/LinkProperty.vue';
import RectProperty from './properties/RectProperty.vue';
import PlatformProperty from './properties/PlatformProperty.vue'; import PlatformProperty from './properties/PlatformProperty.vue';
import StationProperty from './properties/StationProperty.vue';
import TrainProperty from './properties/TrainProperty.vue';
import IscsFanProperty from './properties/IscsFanProperty.vue'; import IscsFanProperty from './properties/IscsFanProperty.vue';
import SignalProperty from './properties/SignalProperty.vue';
import { Link } from 'src/graphics/link/Link'; import { Link } from 'src/graphics/link/Link';
import { Rect } from 'src/graphics/rect/Rect';
import { Platform } from 'src/graphics/platform/Platform'; import { Platform } from 'src/graphics/platform/Platform';
import { Station } from 'src/graphics/station/Station';
import { Train } from 'src/graphics/train/Train';
import { useDrawStore } from 'src/stores/draw-store'; import { useDrawStore } from 'src/stores/draw-store';
import { IscsFan } from 'src/graphics/iscs-fan/IscsFan'; import { IscsFan } from 'src/graphics/iscs-fan/IscsFan';
import { Signal } from 'src/graphics/signal/Signal';
const drawStore = useDrawStore(); const drawStore = useDrawStore();
</script> </script>

View File

@ -58,19 +58,13 @@ const canvas = reactive({
}); });
onMounted(() => { onMounted(() => {
console.log('画布属性表单mounted');
const jc = drawStore.getJlCanvas(); const jc = drawStore.getJlCanvas();
canvas.width = jc.properties.width; canvas.width = jc.properties.width;
canvas.height = jc.properties.height; canvas.height = jc.properties.height;
canvas.backgroundColor = jc.properties.backgroundColor; canvas.backgroundColor = jc.properties.backgroundColor;
}); });
onUnmounted(() => {
console.log('画布属性表单unmounted');
});
function onUpdate() { function onUpdate() {
console.log('画布属性更新');
const app = drawStore.getDrawApp(); const app = drawStore.getDrawApp();
app.updateCanvasAndRecord({ app.updateCanvasAndRecord({
...canvas, ...canvas,

View File

@ -1,19 +1,18 @@
<template> <template>
<q-form> <q-form>
<q-input outlined readonly v-model="stationModel.id" label="id" hint="" /> <q-input outlined readonly v-model="platformModel.id" label="id" hint="" />
<q-input <q-input
outlined outlined
v-model.number="stationModel.lineWidth" v-model.number="platformModel.lineWidth"
type="number" type="number"
@blur="onUpdate" @blur="onUpdate"
label="线宽" label="线宽"
lazy-rules lazy-rules
:rules="[(val) => (val && val > 0) || '画布宽必须大于0']" :rules="[(val) => (val && val > 0) || '画布宽必须大于0']"
/> />
<q-input <q-input
outlined outlined
v-model="stationModel.lineColor" v-model="platformModel.lineColor"
@blur="onUpdate" @blur="onUpdate"
label="线色" label="线色"
lazy-rules lazy-rules
@ -23,10 +22,10 @@
<q-icon name="colorize" class="cursor-pointer"> <q-icon name="colorize" class="cursor-pointer">
<q-popup-proxy cover transition-show="scale" transition-hide="scale"> <q-popup-proxy cover transition-show="scale" transition-hide="scale">
<q-color <q-color
v-model="stationModel.lineColor" v-model="platformModel.lineColor"
@change=" @change="
(val) => { (val) => {
stationModel.lineColor = val; platformModel.lineColor = val;
onUpdate(); onUpdate();
} }
" "
@ -35,13 +34,20 @@
</q-icon> </q-icon>
</template> </template>
</q-input> </q-input>
<q-select <q-select
outlined outlined
v-model="stationModel.hasdoor" @blur="onUpdate"
:options="options" v-model="hasDoor"
:options="optionsDoor"
label="是否有屏蔽门" label="是否有屏蔽门"
/> />
<q-select
outlined
@blur="onUpdate"
v-model="trainDirection"
:options="optionsDirection"
label="行驶方向"
/>
</q-form> </q-form>
</template> </template>
@ -49,37 +55,58 @@
import { PlatformData } from 'src/examples/app/graphics/PlatformInteraction'; import { PlatformData } from 'src/examples/app/graphics/PlatformInteraction';
import { Platform } from 'src/graphics/platform/Platform'; import { Platform } from 'src/graphics/platform/Platform';
import { useDrawStore } from 'src/stores/draw-store'; import { useDrawStore } from 'src/stores/draw-store';
import { onMounted, reactive, watch } from 'vue'; import { onMounted, reactive, ref, watch } from 'vue';
const drawStore = useDrawStore(); const drawStore = useDrawStore();
const stationModel = reactive(new PlatformData()); const platformModel = reactive(new PlatformData());
const options = [true, false]; const hasDoor = ref('是');
const optionsDoor = ['是', '否'];
const trainDirection = ref('向左');
const optionsDirection = ['向左', '向右'];
enum showSelect {
= 'true',
= 'false',
向左 = 'left',
向右 = 'right',
}
enum showSelectData {
true = '是',
false = '否',
left = '向左',
right = '向右',
}
drawStore.$subscribe; drawStore.$subscribe;
watch( watch(
() => drawStore.selectedGraphic, () => drawStore.selectedGraphic,
(val) => { (val) => {
if (val && val.type == Platform.Type) { if (val && val.type == Platform.Type) {
// console.log('station'); platformModel.copyFrom(val.saveData() as PlatformData);
stationModel.copyFrom(val.saveData() as PlatformData); hasDoor.value = (showSelectData as never)[platformModel.hasdoor + ''];
trainDirection.value = (showSelectData as never)[
platformModel.trainDirection
];
} }
} }
); );
onMounted(() => { onMounted(() => {
//console.log('station mounted'); const platform = drawStore.selectedGraphic as Platform;
const station = drawStore.selectedGraphic as Platform; if (platform) {
platformModel.copyFrom(platform.saveData());
if (station) { hasDoor.value = (showSelectData as never)[platformModel.hasdoor + ''];
stationModel.copyFrom(station.saveData()); trainDirection.value = (showSelectData as never)[
platformModel.trainDirection
];
} }
}); });
function onUpdate() { function onUpdate() {
//console.log(stationModel, 'station '); platformModel.hasdoor = JSON.parse((showSelect as never)[hasDoor.value]);
const station = drawStore.selectedGraphic as Platform; platformModel.trainDirection = (showSelect as never)[trainDirection.value];
if (station) { const platform = drawStore.selectedGraphic as Platform;
drawStore.getDrawApp().updateGraphicAndRecord(station, stationModel); if (platform) {
drawStore.getDrawApp().updateGraphicAndRecord(platform, platformModel);
} }
} }
</script> </script>

View File

@ -0,0 +1,74 @@
<template>
<q-form>
<q-input outlined readonly v-model="stationModel.id" label="id" hint="" />
<q-input
outlined
v-model.number="stationModel.lineWidth"
type="number"
@blur="onUpdate"
label="线宽"
lazy-rules
:rules="[(val) => (val && val > 0) || '画布宽必须大于0']"
/>
<q-input
outlined
v-model="stationModel.lineColor"
@blur="onUpdate"
label="线色"
lazy-rules
:rules="[(val) => (val && val.length > 0) || '线色不能为空']"
>
<template v-slot:append>
<q-icon name="colorize" class="cursor-pointer">
<q-popup-proxy cover transition-show="scale" transition-hide="scale">
<q-color
v-model="stationModel.lineColor"
@change="
(val) => {
stationModel.lineColor = val;
onUpdate();
}
"
/>
</q-popup-proxy>
</q-icon>
</template>
</q-input>
</q-form>
</template>
<script setup lang="ts">
import { RectData } from 'src/examples/app/graphics/RectInteraction';
import { Rect } from 'src/graphics/rect/Rect';
import { useDrawStore } from 'src/stores/draw-store';
import { onMounted, reactive, watch } from 'vue';
const drawStore = useDrawStore();
const stationModel = reactive(new RectData());
drawStore.$subscribe;
watch(
() => drawStore.selectedGraphic,
(val) => {
if (val && val.type == Rect.Type) {
stationModel.copyFrom(val.saveData() as RectData);
}
}
);
onMounted(() => {
const station = drawStore.selectedGraphic as Rect;
if (station) {
stationModel.copyFrom(station.saveData());
}
});
function onUpdate() {
const station = drawStore.selectedGraphic as Rect;
if (station) {
drawStore.getDrawApp().updateGraphicAndRecord(station, stationModel);
}
}
</script>

View File

@ -0,0 +1,57 @@
<template>
<q-form>
<q-input outlined readonly v-model="signalModel.id" label="id" hint="" />
<q-select
outlined
@blur="onUpdate"
v-model="signalDirection"
:options="optionsDirection"
label="方向"
/>
</q-form>
</template>
<script setup lang="ts">
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';
const drawStore = useDrawStore();
const signalModel = reactive(new SignalData());
const signalDirection = ref('向右');
const optionsDirection = ['向左', '向右', '向上', '向下'];
enum directionSelect {
向左 = 'left',
向右 = 'right',
向上 = 'up',
向下 = 'down',
}
drawStore.$subscribe;
watch(
() => drawStore.selectedGraphic,
(val) => {
if (val && val.type == Signal.Type) {
signalModel.copyFrom(val.saveData() as SignalData);
signalDirection.value = (directionSelect as never)[signalModel.direction];
}
}
);
onMounted(() => {
const signal = drawStore.selectedGraphic as Signal;
if (signal) {
signalModel.copyFrom(signal.saveData());
signalDirection.value = (directionSelect as never)[signalModel.direction];
}
});
function onUpdate() {
signalModel.direction = (directionSelect as never)[signalDirection.value];
const signal = drawStore.selectedGraphic as Signal;
if (signal) {
drawStore.getDrawApp().updateGraphicAndRecord(signal, signalModel);
}
}
</script>

View File

@ -0,0 +1,200 @@
<template>
<q-form>
<q-input outlined readonly v-model="stationModel.id" label="id" hint="" />
<q-input
outlined
label="车站名称"
@blur="onUpdate"
v-model="stationModel.code"
lazy-rules
/>
<q-input
outlined
v-model.number="stationModel.codeFontSize"
type="number"
@blur="onUpdate"
label="字体大小"
lazy-rules
:rules="[(val) => (val && val > 0) || '字体大小必须大于0']"
/>
<q-input
outlined
v-model="stationModel.codeColor"
@blur="onUpdate"
label="字体颜色"
lazy-rules
:rules="[(val) => (val && val.length > 0) || '线色不能为空']"
>
<template v-slot:append>
<q-icon name="colorize" class="cursor-pointer">
<q-popup-proxy cover transition-show="scale" transition-hide="scale">
<q-color
v-model="stationModel.codeColor"
@change="
(val) => {
stationModel.codeColor = val;
onUpdate();
}
"
/>
</q-popup-proxy>
</q-icon>
</template>
</q-input>
<q-select
outlined
@blur="onUpdate"
v-model="hasCircle"
:options="optionsCircle"
label="是否有圆圈"
/>
<div v-if="stationModel.hasCircle">
<q-item-label header>位置</q-item-label>
<q-item>
<q-item-section no-wrap class="q-gutter-sm column">
<div class="row">
<q-input
outlined
@blur="onUpdate"
label="x"
v-model.number="stationModel.circlePoint.x"
type="number"
step="any"
class="col"
/>
<q-input
outlined
@blur="onUpdate"
label="y"
v-model.number="stationModel.circlePoint.y"
type="number"
step="any"
class="col"
/>
</div>
</q-item-section>
</q-item>
<q-input
outlined
v-model.number="stationModel.radius"
type="number"
@blur="onUpdate"
label="半径"
lazy-rules
:rules="[(val) => (val && val > 0) || '半径大小必须大于0']"
/>
<q-input
outlined
v-model="stationModel.fillColor"
@blur="onUpdate"
label="填充颜色"
lazy-rules
:rules="[(val) => (val && val.length > 0) || '线色不能为空']"
>
<template v-slot:append>
<q-icon name="colorize" class="cursor-pointer">
<q-popup-proxy
cover
transition-show="scale"
transition-hide="scale"
>
<q-color
v-model="stationModel.fillColor"
@change="
(val) => {
stationModel.fillColor = val;
onUpdate();
}
"
/>
</q-popup-proxy>
</q-icon>
</template>
</q-input>
<q-input
outlined
v-model.number="stationModel.borderWidth"
type="number"
@blur="onUpdate"
label="边框宽度"
lazy-rules
:rules="[(val) => (val && val > 0) || '画布宽必须大于0']"
/>
<q-input
outlined
v-model="stationModel.borderColor"
@blur="onUpdate"
label="边框颜色"
lazy-rules
:rules="[(val) => (val && val.length > 0) || '线色不能为空']"
>
<template v-slot:append>
<q-icon name="colorize" class="cursor-pointer">
<q-popup-proxy
cover
transition-show="scale"
transition-hide="scale"
>
<q-color
v-model="stationModel.borderColor"
@change="
(val) => {
stationModel.borderColor = val;
onUpdate();
}
"
/>
</q-popup-proxy>
</q-icon>
</template>
</q-input>
</div>
</q-form>
</template>
<script setup lang="ts">
import { StationData } from 'src/examples/app/graphics/StationInteraction';
import { Station } from 'src/graphics/station/Station';
import { useDrawStore } from 'src/stores/draw-store';
import { onMounted, reactive, ref, watch } from 'vue';
const drawStore = useDrawStore();
const stationModel = reactive(new StationData());
const hasCircle = ref('是');
const optionsCircle = ['是', '否'];
enum showSelect {
= 'true',
= 'false',
}
enum showSelectData {
true = '是',
false = '否',
}
drawStore.$subscribe;
watch(
() => drawStore.selectedGraphic,
(val) => {
if (val && val.type == Station.Type) {
stationModel.copyFrom(val.saveData() as StationData);
hasCircle.value = (showSelectData as never)[stationModel.hasCircle + ''];
}
}
);
onMounted(() => {
const station = drawStore.selectedGraphic as Station;
if (station) {
stationModel.copyFrom(station.saveData());
hasCircle.value = (showSelectData as never)[stationModel.hasCircle + ''];
}
});
function onUpdate() {
stationModel.hasCircle = JSON.parse((showSelect as never)[hasCircle.value]);
const station = drawStore.selectedGraphic as Station;
if (station) {
drawStore.getDrawApp().updateGraphicAndRecord(station, stationModel);
}
}
</script>

View File

@ -0,0 +1,198 @@
<template>
<q-form>
<q-input outlined readonly v-model="trainModel.id" label="id" hint="" />
<q-input
outlined
v-model="trainModel.code"
label="车号"
hint=""
@blur="onUpdate"
/>
<q-input
outlined
v-model="trainModel.codeColor"
@blur="onUpdate"
label="车号颜色"
lazy-rules
:rules="[(val) => (val && val.length > 0) || '车号颜色不能为空']"
>
<template v-slot:append>
<q-icon name="colorize" class="cursor-pointer">
<q-popup-proxy cover transition-show="scale" transition-hide="scale">
<q-color
v-model="trainModel.codeColor"
@change="
(val) => {
trainModel.codeColor = val;
onUpdate();
}
"
/>
</q-popup-proxy>
</q-icon>
</template>
</q-input>
<q-input
outlined
v-model="trainModel.headColor"
@blur="onUpdate"
label="箭头颜色"
lazy-rules
:rules="[(val) => (val && val.length > 0) || '车箭头颜色不能为空']"
>
<template v-slot:append>
<q-icon name="colorize" class="cursor-pointer">
<q-popup-proxy cover transition-show="scale" transition-hide="scale">
<q-color
v-model="trainModel.headColor"
@change="
(val) => {
trainModel.headColor = val;
onUpdate();
}
"
/>
</q-popup-proxy>
</q-icon>
</template>
</q-input>
<q-input
outlined
v-model.number="trainModel.codeFontSize"
type="number"
@blur="onUpdate"
label="字体大小"
lazy-rules
:rules="[(val) => (val && val > 0) || '字体大小必须大于0']"
/>
<q-select
outlined
@blur="onUpdate"
v-model="hasBorder"
:options="optionsDoor"
label="是否有边框"
/>
<q-input
outlined
v-model.number="trainModel.borderWidth"
type="number"
@blur="onUpdate"
label="边框线宽"
lazy-rules
:rules="[(val) => (val && val > 0) || '边框线宽必须大于0']"
/>
<q-input
outlined
v-model="trainModel.borderColor"
@blur="onUpdate"
label="边框颜色"
lazy-rules
:rules="[(val) => (val && val.length > 0) || '边框颜色不能为空']"
>
<template v-slot:append>
<q-icon name="colorize" class="cursor-pointer">
<q-popup-proxy cover transition-show="scale" transition-hide="scale">
<q-color
v-model="trainModel.borderColor"
@change="
(val) => {
trainModel.borderColor = val;
onUpdate();
}
"
/>
</q-popup-proxy>
</q-icon>
</template>
</q-input>
<q-input
outlined
v-model="trainModel.bodyColor"
@blur="onUpdate"
label="背景颜色"
lazy-rules
:rules="[(val) => (val && val.length > 0) || '背景颜色不能为空']"
>
<template v-slot:append>
<q-icon name="colorize" class="cursor-pointer">
<q-popup-proxy cover transition-show="scale" transition-hide="scale">
<q-color
v-model="trainModel.bodyColor"
@change="
(val) => {
trainModel.bodyColor = val;
onUpdate();
}
"
/>
</q-popup-proxy>
</q-icon>
</template>
</q-input>
<q-select
outlined
@blur="onUpdate"
v-model="trainDirection"
:options="optionsDirection"
label="行驶方向"
/>
</q-form>
</template>
<script setup lang="ts">
import { TrainData } from 'src/examples/app/graphics/TrainInteraction';
import { Train } from 'src/graphics/train/Train';
import { useDrawStore } from 'src/stores/draw-store';
import { onMounted, reactive, ref, watch } from 'vue';
const drawStore = useDrawStore();
const trainModel = reactive(new TrainData());
const hasBorder = ref('是');
const optionsDoor = ['是', '否'];
const trainDirection = ref('向左');
const optionsDirection = ['向左', '向右'];
enum showSelect {
= 'true',
= 'false',
向左 = 'left',
向右 = 'right',
}
enum showSelectData {
true = '是',
false = '否',
left = '向左',
right = '向右',
}
drawStore.$subscribe;
watch(
() => drawStore.selectedGraphic,
(val) => {
if (val && val.type == Train.Type) {
trainModel.copyFrom(val.saveData() as TrainData);
hasBorder.value = (showSelectData as never)[trainModel.hasBorder + ''];
trainDirection.value = (showSelectData as never)[
trainModel.trainDirection
];
}
}
);
onMounted(() => {
const train = drawStore.selectedGraphic as Train;
if (train) {
trainModel.copyFrom(train.saveData());
hasBorder.value = (showSelectData as never)[trainModel.hasBorder + ''];
trainDirection.value = (showSelectData as never)[trainModel.trainDirection];
}
});
function onUpdate() {
trainModel.hasBorder = JSON.parse((showSelect as never)[hasBorder.value]);
trainModel.trainDirection = (showSelect as never)[trainDirection.value];
const train = drawStore.selectedGraphic as Train;
if (train) {
drawStore.getDrawApp().updateGraphicAndRecord(train, trainModel);
}
}
</script>

View File

@ -0,0 +1,76 @@
<template>
<q-form>
<q-input
outlined
v-model.number="template.lineWidth"
type="number"
@blur="onUpdate"
label="线宽 *"
lazy-rules
:rules="[(val) => (val && val > 0) || '线宽必须大于0']"
/>
<q-input
outlined
v-model="template.lineColor"
@blur="onUpdate"
label="线色 *"
lazy-rules
:rules="[(val) => (val && val.length > 0) || '线色不能为空']"
>
<template v-slot:append>
<q-icon name="colorize" class="cursor-pointer">
<q-popup-proxy cover transition-show="scale" transition-hide="scale">
<q-color
v-model="template.lineColor"
@change="
(val) => {
template.lineColor = val;
onUpdate();
}
"
/>
</q-popup-proxy>
</q-icon>
</template>
</q-input>
</q-form>
</template>
<script setup lang="ts">
import { LinkTemplate } from 'src/graphics/link/Link';
import { useDrawStore } from 'src/stores/draw-store';
import { onMounted, reactive } from 'vue';
const drawStore = useDrawStore();
const template = reactive({
lineWidth: 1,
lineColor: '#0000ff',
curve: false,
segmentsCount: 10,
});
onMounted(() => {
const type = drawStore.drawGraphicType;
if (type) {
const gt = drawStore.drawGraphicTemplate;
if (gt) {
const lt = gt as LinkTemplate;
template.lineWidth = lt.lineWidth;
template.lineColor = lt.lineColor;
template.curve = lt.curve;
template.segmentsCount = lt.segmentsCount;
}
}
});
function onUpdate() {
const gt = drawStore.drawGraphicTemplate as LinkTemplate;
if (gt) {
gt.lineWidth = template.lineWidth;
gt.lineColor = template.lineColor;
gt.curve = template.curve;
gt.segmentsCount = template.segmentsCount;
}
}
</script>

View File

@ -0,0 +1,70 @@
<template>
<q-form>
<q-input
outlined
v-model.number="template.lineWidth"
type="number"
@blur="onUpdate"
label="字体大小 *"
lazy-rules
:rules="[(val) => (val && val > 0) || '线宽必须大于0']"
/>
<q-input
outlined
v-model="template.lineColor"
@blur="onUpdate"
label="字体颜色 *"
lazy-rules
:rules="[(val) => (val && val.length > 0) || '线色不能为空']"
>
<template v-slot:append>
<q-icon name="colorize" class="cursor-pointer">
<q-popup-proxy cover transition-show="scale" transition-hide="scale">
<q-color
v-model="template.lineColor"
@change="
(val) => {
template.lineColor = val;
onUpdate();
}
"
/>
</q-popup-proxy>
</q-icon>
</template>
</q-input>
</q-form>
</template>
<script setup lang="ts">
import { LinkTemplate } from 'src/graphics/link/Link';
import { useDrawStore } from 'src/stores/draw-store';
import { onMounted, reactive } from 'vue';
const drawStore = useDrawStore();
const template = reactive({
lineWidth: 1,
lineColor: '#0000ff',
});
onMounted(() => {
const type = drawStore.drawGraphicType;
if (type) {
const gt = drawStore.drawGraphicTemplate;
if (gt) {
const lt = gt as LinkTemplate;
template.lineWidth = lt.lineWidth;
template.lineColor = lt.lineColor;
}
}
});
function onUpdate() {
const gt = drawStore.drawGraphicTemplate as LinkTemplate;
if (gt) {
gt.lineWidth = template.lineWidth;
gt.lineColor = template.lineColor;
}
}
</script>

View File

@ -0,0 +1,69 @@
<template>
<q-form>
<q-input
outlined
v-model.number="template.codeFontSize"
type="number"
@blur="onUpdate"
label="字体大小 *"
lazy-rules
:rules="[(val) => (val && val > 0) || '字体大小必须大于0']"
/>
<q-input
outlined
v-model="template.headColor"
@blur="onUpdate"
label="箭头颜色 *"
lazy-rules
:rules="[(val) => (val && val.length > 0) || '箭头颜色不能为空']"
>
<template v-slot:append>
<q-icon name="colorize" class="cursor-pointer">
<q-popup-proxy cover transition-show="scale" transition-hide="scale">
<q-color
v-model="template.headColor"
@change="
(val) => {
template.headColor = val;
onUpdate();
}
"
/>
</q-popup-proxy>
</q-icon>
</template>
</q-input>
</q-form>
</template>
<script setup lang="ts">
import { TrainTemplate } from 'src/graphics/train/Train';
import { useDrawStore } from 'src/stores/draw-store';
import { onMounted, reactive } from 'vue';
const drawStore = useDrawStore();
const template = reactive({
codeFontSize: 22,
headColor: '#00FF00',
});
onMounted(() => {
const type = drawStore.drawGraphicType;
if (type) {
const gt = drawStore.drawGraphicTemplate;
if (gt) {
const lt = gt as TrainTemplate;
template.codeFontSize = lt.codeFontSize;
template.headColor = lt.headColor;
}
}
});
function onUpdate() {
const gt = drawStore.drawGraphicTemplate as TrainTemplate;
if (gt) {
gt.codeFontSize = template.codeFontSize;
gt.headColor = template.headColor;
}
}
</script>

View File

@ -0,0 +1,42 @@
export class PageQueryDto {
current: number;
size: number;
orders?: OrderItemDto[];
constructor(current: number, size: number, orders?: OrderItemDto[]) {
this.current = current;
this.size = size;
this.orders = orders;
}
}
export class OrderItemDto {
column: string;
asc: boolean;
constructor(column: string, asc: boolean) {
this.column = column;
this.asc = asc;
}
static asc(column: string): OrderItemDto {
return new OrderItemDto(column, true);
}
static desc(column: string): OrderItemDto {
return new OrderItemDto(column, false);
}
}
export interface PageDto<T = unknown> {
records: T[];
/**
*
*/
total: number;
/**
*
*/
current: number;
/**
*
*/
size: number;
}

View File

@ -0,0 +1,77 @@
import { api } from 'src/boot/axios';
import { PageDto, PageQueryDto } from './ApiCommon';
import { Md5 } from 'ts-md5';
const UserUriBase = '/api/user';
interface RegisterInfo {
name: string;
mobile: string;
password: string;
}
interface User {
id: string;
name: string;
mobile: string;
password: string;
registerTime: string;
}
const PasswordSult = '4a6d74126bfd06d69406fcccb7e7d5d9'; // 密码加盐
function encryptPassword(password: string): string {
const md5 = new Md5();
return md5.appendStr(`${password}${PasswordSult}`).end() as string;
}
/**
*
* @param info
* @returns
*/
export async function register(info: RegisterInfo): Promise<User> {
const response = await api.post(`${UserUriBase}/register`, {
...info,
password: encryptPassword(info.password),
});
return response.data as User;
}
interface LoginInfo {
account: string;
password: string;
}
/**
*
* @param loginInfo
* @returns
*/
export async function login(loginInfo: LoginInfo): Promise<string> {
const info = { ...loginInfo, password: encryptPassword(loginInfo.password) };
const response = await api.post(`${UserUriBase}/login`, info);
return response.data;
}
export class PagingQueryParams extends PageQueryDto {
name?: string;
roleId?: number;
}
/**
*
* @param params
* @returns
*/
export async function pageQuery(
params: PagingQueryParams
): Promise<PageDto<User>> {
const response = await api.get(`${UserUriBase}/paging`, {
params: params,
});
return response.data;
}
export function distributeRole(query: { userId: number; roleIds: number[] }) {
return api.post('/api/role/distribute', query);
}

View File

@ -7,6 +7,10 @@ message RtssGraphicStorage {
repeated Link links = 2; repeated Link links = 2;
repeated IscsFan iscsFans = 3; repeated IscsFan iscsFans = 3;
repeated Platform Platforms = 4; repeated Platform Platforms = 4;
repeated Station stations = 5;
repeated Rect Rects = 6;
repeated Train train = 7;
repeated Signal signals = 8;
} }
message Canvas { message Canvas {
@ -64,17 +68,57 @@ message Link {
repeated Point points = 7; // repeated Point points = 7; //
} }
message Rect {
CommonInfo common = 1;
string code = 2;
int32 lineWidth = 3; // 线
string lineColor = 4; // 线
Point point = 5; //
float width = 6;//
float height = 7; //
repeated Point points = 8; //
}
message Platform { message Platform {
CommonInfo common = 1; CommonInfo common = 1;
string code = 2; string code = 2;
bool hasdoor = 3; // bool hasdoor = 3; //
int32 lineWidth = 4; // 线 string trainDirection = 4; // --
string lineColor = 5; // 线 int32 lineWidth = 5; // 线
string lineColorDoor = 6; // 线 string lineColor = 6; // 线
Point point = 7; // string lineColorDoor = 7; // 线
float width = 8;// Point point = 8; //
float height = 9; // float width = 9;//
repeated string orbitCode = 10;// float height = 10; //
repeated string orbitCode = 11;//
}
message Station {
CommonInfo common = 1;
string code = 2;
bool hasCircle = 3; // --线
int32 radius = 4; //
int32 borderWidth = 5; // 线
string borderColor = 6; //
string fillColor = 7; //
string codeColor = 8; //
int32 codeFontSize = 9; //
Point point = 10; //
Point circlePoint = 11; //
}
message Train {
CommonInfo common = 1;
string code = 2;
string codeColor = 3; //
int32 codeFontSize = 4; //
Point point = 5; //
string trainDirection = 6; //
bool hasBorder = 7; //
int32 borderWidth = 8; // 线
string borderColor = 9; //
string headColor = 10; //
string bodyColor = 11; //
} }
message IscsFan { message IscsFan {
@ -83,3 +127,8 @@ message IscsFan {
} }
message Turnout {} message Turnout {}
message Signal {
CommonInfo common = 1;
string code = 2;
}

View File

@ -0,0 +1,14 @@
syntax = "proto3";
package graphicStates;
//
message CommonState {
string code = 1;
string graphicType = 2;
}
message IscsFan {
CommonState common = 1;
int32 state = 2;
}

View File

@ -0,0 +1,13 @@
const JwtTokenKey = 'jwttoken';
export function saveJwtToken(token: string) {
sessionStorage.setItem(JwtTokenKey, `Bearer ${token}`);
}
export function getJwtToken(): string | null {
return sessionStorage.getItem(JwtTokenKey);
}
export function clearJwtToken(): void {
sessionStorage.removeItem(JwtTokenKey);
}

View File

@ -0,0 +1,13 @@
function getHost(): string {
// return '192.168.3.7:9081';
return '192.168.3.233:9081';
// return '192.168.3.7:9081';
}
export function getHttpBase() {
return `http://${getHost()}`;
}
export function getWebsocketUrl() {
return `ws://${getHost()}/ws-default`;
}

View File

@ -2,12 +2,14 @@ import * as pb_1 from 'google-protobuf';
import { import {
ChildTransform, ChildTransform,
GraphicData, GraphicData,
GraphicState,
GraphicTransform, GraphicTransform,
IChildTransform, IChildTransform,
IGraphicTransform, IGraphicTransform,
} from 'src/jlgraphic'; } from 'src/jlgraphic';
import { toStorageTransform } from '..'; import { toStorageTransform } from '..';
import { graphicData } from '../protos/draw_data_storage'; import { graphicData } from '../protos/draw_data_storage';
import { graphicStates } from '../protos/graphic_states';
export interface ICommonInfo { export interface ICommonInfo {
id: string; id: string;
@ -27,10 +29,10 @@ export abstract class GraphicDataBase implements GraphicData {
this._data = data; this._data = data;
} }
static defaultCommonInfo(): graphicData.CommonInfo { static defaultCommonInfo(graphicType: string): graphicData.CommonInfo {
return new graphicData.CommonInfo({ return new graphicData.CommonInfo({
id: '', id: '',
graphicType: '', graphicType: graphicType,
transform: new graphicData.Transform({ transform: new graphicData.Transform({
position: new graphicData.Point({ x: 0, y: 0 }), position: new graphicData.Point({ x: 0, y: 0 }),
scale: new graphicData.Point({ x: 1, y: 1 }), scale: new graphicData.Point({ x: 1, y: 1 }),
@ -99,3 +101,41 @@ export abstract class GraphicDataBase implements GraphicData {
return pb_1.Message.equals(this._data, other._data); return pb_1.Message.equals(this._data, other._data);
} }
} }
export interface IProtoGraphicState extends pb_1.Message {
common: graphicStates.CommonState;
}
export abstract class GraphicStateBase implements GraphicState {
_state: IProtoGraphicState;
constructor(state: IProtoGraphicState) {
this._state = state;
}
static defaultCommonState(graphicType: string): graphicStates.CommonState {
return new graphicStates.CommonState({
code: '',
graphicType: graphicType,
});
}
getState<S extends IProtoGraphicState>(): S {
return this._state as S;
}
get code(): string {
return this._state.common.code;
}
get graphicType(): string {
return this._state.common.graphicType;
}
clone(): GraphicState {
throw new Error('Method not implemented.');
}
copyFrom(data: GraphicStateBase): void {
pb_1.Message.copyInto(data._state, this._state);
}
eq(data: GraphicStateBase): boolean {
return pb_1.Message.equals(this._state, data._state);
}
}

View File

@ -1,7 +1,12 @@
import * as pb_1 from 'google-protobuf'; import * as pb_1 from 'google-protobuf';
import { IIscsFanData } from 'src/graphics/iscs-fan/IscsFan'; import {
IIscsFanData,
IIscsFanState,
IscsFan,
} from 'src/graphics/iscs-fan/IscsFan';
import { graphicData } from '../protos/draw_data_storage'; import { graphicData } from '../protos/draw_data_storage';
import { GraphicDataBase } from './GraphicDataBase'; import { graphicStates } from '../protos/graphic_states';
import { GraphicDataBase, GraphicStateBase } from './GraphicDataBase';
export class IscsFanData extends GraphicDataBase implements IIscsFanData { export class IscsFanData extends GraphicDataBase implements IIscsFanData {
constructor(data?: graphicData.IscsFan) { constructor(data?: graphicData.IscsFan) {
@ -10,7 +15,7 @@ export class IscsFanData extends GraphicDataBase implements IIscsFanData {
fan = data; fan = data;
} else { } else {
fan = new graphicData.IscsFan({ fan = new graphicData.IscsFan({
common: GraphicDataBase.defaultCommonInfo(), common: GraphicDataBase.defaultCommonInfo(IscsFan.Type),
}); });
} }
super(fan); super(fan);
@ -36,3 +41,32 @@ export class IscsFanData extends GraphicDataBase implements IIscsFanData {
return pb_1.Message.equals(this.data, other.data); return pb_1.Message.equals(this.data, other.data);
} }
} }
export class IscsFanState extends GraphicStateBase implements IIscsFanState {
constructor(proto?: graphicStates.IscsFan) {
let states;
if (proto) {
states = proto;
states.common.graphicType = IscsFan.Type;
} else {
states = new graphicStates.IscsFan({
common: GraphicStateBase.defaultCommonState(IscsFan.Type),
});
}
super(states);
}
get states(): graphicStates.IscsFan {
return this.getState<graphicStates.IscsFan>();
}
get state(): number {
return this.states.state;
}
set state(v: number) {
this.states.state = v;
}
clone(): IscsFanState {
return new IscsFanState(this.states.cloneMessage());
}
}

View File

@ -33,6 +33,12 @@ export class PlatformData extends GraphicDataBase implements IPlatformData {
set hasdoor(v: boolean) { set hasdoor(v: boolean) {
this.data.hasdoor = v; this.data.hasdoor = v;
} }
get trainDirection(): string {
return this.data.trainDirection;
}
set trainDirection(v: string) {
this.data.trainDirection = v;
}
get lineWidth(): number { get lineWidth(): number {
return this.data.lineWidth; return this.data.lineWidth;
} }

View File

@ -0,0 +1,77 @@
import * as pb_1 from 'google-protobuf';
import { IPointData } from 'pixi.js';
import { IRectData } from 'src/graphics/rect/Rect';
import { graphicData } from '../protos/draw_data_storage';
import { GraphicDataBase } from './GraphicDataBase';
export class RectData extends GraphicDataBase implements IRectData {
constructor(data?: graphicData.Rect) {
let rect;
if (!data) {
rect = new graphicData.Rect({
common: GraphicDataBase.defaultCommonInfo(),
});
} else {
rect = data;
}
super(rect);
}
public get data(): graphicData.Rect {
return this.getData<graphicData.Rect>();
}
get code(): string {
return this.data.code;
}
set code(v: string) {
this.data.code = v;
}
get lineWidth(): number {
return this.data.lineWidth;
}
set lineWidth(v: number) {
this.data.lineWidth = v;
}
get lineColor(): string {
return this.data.lineColor;
}
set lineColor(v: string) {
this.data.lineColor = v;
}
get point(): IPointData {
return this.data.point;
}
set point(point: IPointData) {
this.data.point = new graphicData.Point({ x: point.x, y: point.y });
}
get width(): number {
return this.data.width;
}
set width(v: number) {
this.data.width = v;
}
get height(): number {
return this.data.height;
}
set height(v: number) {
this.data.height = v;
}
get points(): IPointData[] {
return this.data.points;
}
set points(points: IPointData[]) {
this.data.points = points.map(
(p) => new graphicData.Point({ x: p.x, y: p.y })
);
}
clone(): RectData {
return new RectData(this.data.cloneMessage());
}
copyFrom(data: RectData): void {
pb_1.Message.copyInto(data.data, this.data);
}
eq(other: RectData): boolean {
return pb_1.Message.equals(this.data, other.data);
}
}

View File

@ -0,0 +1,37 @@
import * as pb_1 from 'google-protobuf';
import { ISignalData, Signal } from 'src/graphics/signal/Signal';
import { graphicData } from '../protos/draw_data_storage';
import { GraphicDataBase } from './GraphicDataBase';
export class SignalData extends GraphicDataBase implements ISignalData {
constructor(data?: graphicData.Signal) {
let signal;
if (!data) {
signal = new graphicData.Signal({
common: GraphicDataBase.defaultCommonInfo(Signal.Type),
});
} else {
signal = data;
}
super(signal);
}
public get data(): graphicData.Signal {
return this.getData<graphicData.Signal>();
}
get code(): string {
return this.data.code;
}
set code(v: string) {
this.data.code = v;
}
clone(): SignalData {
return new SignalData(this.data.cloneMessage());
}
copyFrom(data: SignalData): void {
pb_1.Message.copyInto(data.data, this.data);
}
eq(other: SignalData): boolean {
return pb_1.Message.equals(this.data, other.data);
}
}

View File

@ -0,0 +1,92 @@
import * as pb_1 from 'google-protobuf';
import { IPointData } from 'pixi.js';
import { IStationData } from 'src/graphics/station/Station';
import { graphicData } from '../protos/draw_data_storage';
import { GraphicDataBase } from './GraphicDataBase';
export class StationData extends GraphicDataBase implements IStationData {
constructor(data?: graphicData.Station) {
let station;
if (!data) {
station = new graphicData.Station({
common: GraphicDataBase.defaultCommonInfo(),
});
} else {
station = data;
}
super(station);
}
public get data(): graphicData.Station {
return this.getData<graphicData.Station>();
}
get code(): string {
return this.data.code;
}
set code(v: string) {
this.data.code = v;
}
get hasCircle(): boolean {
return this.data.hasCircle;
}
set hasCircle(v: boolean) {
this.data.hasCircle = v;
}
get radius(): number {
return this.data.radius;
}
set radius(v: number) {
this.data.radius = v;
}
get borderWidth(): number {
return this.data.borderWidth;
}
set borderWidth(v: number) {
this.data.borderWidth = v;
}
get borderColor(): string {
return this.data.borderColor;
}
set borderColor(v: string) {
this.data.borderColor = v;
}
get fillColor(): string {
return this.data.fillColor;
}
set fillColor(v: string) {
this.data.fillColor = v;
}
get codeColor(): string {
return this.data.codeColor;
}
set codeColor(v: string) {
this.data.codeColor = v;
}
get codeFontSize(): number {
return this.data.codeFontSize;
}
set codeFontSize(v: number) {
this.data.codeFontSize = v;
}
get point(): IPointData {
return this.data.point;
}
set point(point: IPointData) {
this.data.point = new graphicData.Point({ x: point.x, y: point.y });
}
get circlePoint(): IPointData {
return this.data.circlePoint;
}
set circlePoint(point: IPointData) {
this.data.circlePoint = new graphicData.Point({ x: point.x, y: point.y });
}
clone(): StationData {
return new StationData(this.data.cloneMessage());
}
copyFrom(data: StationData): void {
pb_1.Message.copyInto(data.data, this.data);
}
eq(other: StationData): boolean {
return pb_1.Message.equals(this.data, other.data);
}
}

View File

@ -0,0 +1,92 @@
import * as pb_1 from 'google-protobuf';
import { IPointData } from 'pixi.js';
import { ITrainData } from 'src/graphics/train/Train';
import { graphicData } from '../protos/draw_data_storage';
import { GraphicDataBase } from './GraphicDataBase';
export class TrainData extends GraphicDataBase implements ITrainData {
constructor(data?: graphicData.Train) {
let train;
if (!data) {
train = new graphicData.Train({
common: GraphicDataBase.defaultCommonInfo(),
});
} else {
train = data;
}
super(train);
}
public get data(): graphicData.Train {
return this.getData<graphicData.Train>();
}
get code(): string {
return this.data.code;
}
set code(v: string) {
this.data.code = v;
}
get codeColor(): string {
return this.data.codeColor;
}
set codeColor(v: string) {
this.data.codeColor = v;
}
get codeFontSize(): number {
return this.data.codeFontSize;
}
set codeFontSize(v: number) {
this.data.codeFontSize = v;
}
get trainDirection(): string {
return this.data.trainDirection;
}
set trainDirection(v: string) {
this.data.trainDirection = v;
}
get hasBorder(): boolean {
return this.data.hasBorder;
}
set hasBorder(v: boolean) {
this.data.hasBorder = v;
}
get borderWidth(): number {
return this.data.borderWidth;
}
set borderWidth(v: number) {
this.data.borderWidth = v;
}
get borderColor(): string {
return this.data.borderColor;
}
set borderColor(v: string) {
this.data.borderColor = v;
}
get headColor(): string {
return this.data.headColor;
}
set headColor(v: string) {
this.data.headColor = v;
}
get bodyColor(): string {
return this.data.bodyColor;
}
set bodyColor(v: string) {
this.data.bodyColor = v;
}
get point(): IPointData {
return this.data.point;
}
set point(point: IPointData) {
this.data.point = new graphicData.Point({ x: point.x, y: point.y });
}
clone(): TrainData {
return new TrainData(this.data.cloneMessage());
}
copyFrom(data: TrainData): void {
pb_1.Message.copyInto(data.data, this.data);
}
eq(other: TrainData): boolean {
return pb_1.Message.equals(this.data, other.data);
}
}

View File

@ -1,11 +1,19 @@
import { fromUint8Array, toUint8Array } from 'js-base64'; import { fromUint8Array, toUint8Array } from 'js-base64';
import { IPointData, Point } from 'pixi.js'; import { IPointData, Point } from 'pixi.js';
import { IscsFan } from 'src/graphics/iscs-fan/IscsFan'; import { IscsFan, IscsFanTemplate } from 'src/graphics/iscs-fan/IscsFan';
import { IscsFanDraw } from 'src/graphics/iscs-fan/IscsFanDrawAssistant'; import { IscsFanDraw } from 'src/graphics/iscs-fan/IscsFanDrawAssistant';
import { Link } from 'src/graphics/link/Link'; import { Link, LinkTemplate } from 'src/graphics/link/Link';
import { LinkDraw } from 'src/graphics/link/LinkDrawAssistant'; import { LinkDraw } from 'src/graphics/link/LinkDrawAssistant';
import { Rect } from 'src/graphics/rect/Rect';
import { RectDraw } from 'src/graphics/rect/RectDrawAssistant';
import { Platform } from 'src/graphics/platform/Platform'; import { Platform } from 'src/graphics/platform/Platform';
import { PlatformDraw } from 'src/graphics/platform/PlatformDrawAssistant'; import { PlatformDraw } from 'src/graphics/platform/PlatformDrawAssistant';
import { Station } from 'src/graphics/station/Station';
import { Train } from 'src/graphics/train/Train';
import { StationDraw } from 'src/graphics/station/StationDrawAssistant';
import { Signal, SignalTemplate } from 'src/graphics/signal/Signal';
import { SignalDraw } from 'src/graphics/signal/SignalDrawAssistant';
import { TrainDraw } from 'src/graphics/train/TrainDrawAssistant';
import { import {
CombinationKey, CombinationKey,
GraphicApp, GraphicApp,
@ -16,10 +24,15 @@ import {
} from 'src/jlgraphic'; } from 'src/jlgraphic';
import { ContextMenu } from 'src/jlgraphic/ui/ContextMenu'; import { ContextMenu } from 'src/jlgraphic/ui/ContextMenu';
import { MenuItemOptions } from 'src/jlgraphic/ui/Menu'; import { MenuItemOptions } from 'src/jlgraphic/ui/Menu';
import { IscsFanData } from './graphics/IscsFanInteraction'; import { IscsFanData, IscsFanState } from './graphics/IscsFanInteraction';
import { LinkData } from './graphics/LinkInteraction'; import { LinkData } from './graphics/LinkInteraction';
import { RectData } from './graphics/RectInteraction';
import { PlatformData } from './graphics/PlatformInteraction'; import { PlatformData } from './graphics/PlatformInteraction';
import { StationData } from './graphics/StationInteraction';
import { SignalData } from './graphics/SignalInteraction';
import { TrainData } from './graphics/TrainInteraction';
import { graphicData } from './protos/draw_data_storage'; import { graphicData } from './protos/draw_data_storage';
import { Notify } from 'quasar';
export function fromStoragePoint(p: graphicData.Point): Point { export function fromStoragePoint(p: graphicData.Point): Point {
return new Point(p.x, p.y); return new Point(p.x, p.y);
@ -91,16 +104,27 @@ export function initDrawApp(dom: HTMLElement): JlDrawApp {
const app = drawApp; const app = drawApp;
app.setOptions({ app.setOptions({
drawAssistants: [ drawAssistants: [
new LinkDraw(app, () => { new LinkDraw(app, new LinkTemplate(new LinkData())),
return new LinkData(); new IscsFanDraw(
}), app,
new IscsFanDraw(app, () => { new IscsFanTemplate(new IscsFanData(), new IscsFanState())
return new IscsFanData(); ),
}), new SignalDraw(app, new SignalTemplate(new SignalData())),
new PlatformDraw(app, () => { new TrainDraw(app, () => {
return new PlatformData(); return new TrainData();
}), }),
], ],
// isSupportDeletion: (g): boolean => {
// if (g.type === Signal.Type) {
// Notify.create({
// type: 'warning',
// message: '信号机不支持删除',
// timeout: 1000,
// });
// return false;
// }
// return true;
// },
}); });
// 画布右键菜单 // 画布右键菜单
@ -129,6 +153,14 @@ export function initDrawApp(dom: HTMLElement): JlDrawApp {
}, },
}) })
); );
app.addKeyboardListener(
new KeyListener({
value: 'KeyR',
onPress: () => {
app.interactionPlugin(Rect.Type).resume();
},
})
);
app.addKeyboardListener( app.addKeyboardListener(
new KeyListener({ new KeyListener({
value: 'KeyF', value: 'KeyF',
@ -145,13 +177,37 @@ export function initDrawApp(dom: HTMLElement): JlDrawApp {
}, },
}) })
); );
app.addKeyboardListener(
new KeyListener({
value: 'KeyO',
onPress: () => {
app.interactionPlugin(Station.Type).resume();
},
})
);
app.addKeyboardListener(
new KeyListener({
value: 'KeyH',
onPress: () => {
app.interactionPlugin(Signal.Type).resume();
},
})
);
app.addKeyboardListener(
new KeyListener({
value: 'KeyT',
onPress: () => {
app.interactionPlugin(Train.Type).resume();
},
})
);
app.addKeyboardListener( app.addKeyboardListener(
new KeyListener({ new KeyListener({
value: '1', value: '1',
onPress: () => { onPress: () => {
app.queryStore.queryByType<IscsFan>(IscsFan.Type).forEach((fan) => { app.queryStore.queryByType<IscsFan>(IscsFan.Type).forEach((fan) => {
fan.__state = fan.__state + 1; fan.states.state = fan.states.state + 1;
fan.__state = fan.__state % 5; fan.states.state = fan.states.state % 5;
fan.repaint(); fan.repaint();
}); });
}, },
@ -183,12 +239,24 @@ export function saveDrawDatas(app: JlDrawApp) {
if (Link.Type === g.type) { if (Link.Type === g.type) {
const linkData = (g as Link).saveData(); const linkData = (g as Link).saveData();
storage.links.push((linkData as LinkData).data); storage.links.push((linkData as LinkData).data);
} else if (Rect.Type === g.type) {
const rectData = (g as Rect).saveData();
storage.Rects.push((rectData as RectData).data);
} else if (IscsFan.Type === g.type) { } else if (IscsFan.Type === g.type) {
const IscsFanData = (g as IscsFan).saveData(); const IscsFanData = (g as IscsFan).saveData();
storage.iscsFans.push((IscsFanData as IscsFanData).data); storage.iscsFans.push((IscsFanData as IscsFanData).data);
} else if (Platform.Type === g.type) { } else if (Platform.Type === g.type) {
const platformData = (g as Platform).saveData(); const platformData = (g as Platform).saveData();
storage.Platforms.push((platformData as PlatformData).data); storage.Platforms.push((platformData as PlatformData).data);
} else if (Station.Type === g.type) {
const stationData = (g as Station).saveData();
storage.stations.push((stationData as StationData).data);
} else if (Signal.Type === g.type) {
const signalData = (g as Signal).saveData();
storage.signals.push((signalData as SignalData).data);
} else if (Train.Type === g.type) {
const trainData = (g as Train).saveData();
storage.train.push((trainData as TrainData).data);
} }
}); });
const base64 = fromUint8Array(storage.serialize()); const base64 = fromUint8Array(storage.serialize());
@ -210,12 +278,27 @@ export function loadDrawDatas(app: GraphicApp) {
storage.links.forEach((link) => { storage.links.forEach((link) => {
datas.push(new LinkData(link)); datas.push(new LinkData(link));
}); });
storage.Rects.forEach((rect) => {
datas.push(new RectData(rect));
});
storage.iscsFans.forEach((fan) => { storage.iscsFans.forEach((fan) => {
datas.push(new IscsFanData(fan)); datas.push(new IscsFanData(fan));
}); });
storage.Platforms.forEach((platform) => { storage.Platforms.forEach((platform) => {
datas.push(new PlatformData(platform)); datas.push(new PlatformData(platform));
}); });
storage.stations.forEach((station) => {
datas.push(new StationData(station));
});
storage.signals.forEach((data) => {
datas.push(new SignalData(data));
});
storage.stations.forEach((signal) => {
datas.push(new StationData(signal));
});
storage.stations.forEach((train) => {
datas.push(new StationData(train));
});
app.loadGraphic(datas); app.loadGraphic(datas);
} else { } else {
app.loadGraphic([]); app.loadGraphic([]);

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,191 @@
/**
* Generated by the protoc-gen-ts. DO NOT EDIT!
* compiler version: 4.22.2
* source: graphic_states.proto
* git: https://github.com/thesayyn/protoc-gen-ts */
import * as pb_1 from "google-protobuf";
export namespace graphicStates {
export class CommonState extends pb_1.Message {
#one_of_decls: number[][] = [];
constructor(data?: any[] | {
code?: string;
graphicType?: string;
}) {
super();
pb_1.Message.initialize(this, Array.isArray(data) ? data : [], 0, -1, [], this.#one_of_decls);
if (!Array.isArray(data) && typeof data == "object") {
if ("code" in data && data.code != undefined) {
this.code = data.code;
}
if ("graphicType" in data && data.graphicType != undefined) {
this.graphicType = data.graphicType;
}
}
}
get code() {
return pb_1.Message.getFieldWithDefault(this, 1, "") as string;
}
set code(value: string) {
pb_1.Message.setField(this, 1, value);
}
get graphicType() {
return pb_1.Message.getFieldWithDefault(this, 2, "") as string;
}
set graphicType(value: string) {
pb_1.Message.setField(this, 2, value);
}
static fromObject(data: {
code?: string;
graphicType?: string;
}): CommonState {
const message = new CommonState({});
if (data.code != null) {
message.code = data.code;
}
if (data.graphicType != null) {
message.graphicType = data.graphicType;
}
return message;
}
toObject() {
const data: {
code?: string;
graphicType?: string;
} = {};
if (this.code != null) {
data.code = this.code;
}
if (this.graphicType != null) {
data.graphicType = this.graphicType;
}
return data;
}
serialize(): Uint8Array;
serialize(w: pb_1.BinaryWriter): void;
serialize(w?: pb_1.BinaryWriter): Uint8Array | void {
const writer = w || new pb_1.BinaryWriter();
if (this.code.length)
writer.writeString(1, this.code);
if (this.graphicType.length)
writer.writeString(2, this.graphicType);
if (!w)
return writer.getResultBuffer();
}
static deserialize(bytes: Uint8Array | pb_1.BinaryReader): CommonState {
const reader = bytes instanceof pb_1.BinaryReader ? bytes : new pb_1.BinaryReader(bytes), message = new CommonState();
while (reader.nextField()) {
if (reader.isEndGroup())
break;
switch (reader.getFieldNumber()) {
case 1:
message.code = reader.readString();
break;
case 2:
message.graphicType = reader.readString();
break;
default: reader.skipField();
}
}
return message;
}
serializeBinary(): Uint8Array {
return this.serialize();
}
static deserializeBinary(bytes: Uint8Array): CommonState {
return CommonState.deserialize(bytes);
}
}
export class IscsFan extends pb_1.Message {
#one_of_decls: number[][] = [];
constructor(data?: any[] | {
common?: CommonState;
state?: number;
}) {
super();
pb_1.Message.initialize(this, Array.isArray(data) ? data : [], 0, -1, [], this.#one_of_decls);
if (!Array.isArray(data) && typeof data == "object") {
if ("common" in data && data.common != undefined) {
this.common = data.common;
}
if ("state" in data && data.state != undefined) {
this.state = data.state;
}
}
}
get common() {
return pb_1.Message.getWrapperField(this, CommonState, 1) as CommonState;
}
set common(value: CommonState) {
pb_1.Message.setWrapperField(this, 1, value);
}
get has_common() {
return pb_1.Message.getField(this, 1) != null;
}
get state() {
return pb_1.Message.getFieldWithDefault(this, 2, 0) as number;
}
set state(value: number) {
pb_1.Message.setField(this, 2, value);
}
static fromObject(data: {
common?: ReturnType<typeof CommonState.prototype.toObject>;
state?: number;
}): IscsFan {
const message = new IscsFan({});
if (data.common != null) {
message.common = CommonState.fromObject(data.common);
}
if (data.state != null) {
message.state = data.state;
}
return message;
}
toObject() {
const data: {
common?: ReturnType<typeof CommonState.prototype.toObject>;
state?: number;
} = {};
if (this.common != null) {
data.common = this.common.toObject();
}
if (this.state != null) {
data.state = this.state;
}
return data;
}
serialize(): Uint8Array;
serialize(w: pb_1.BinaryWriter): void;
serialize(w?: pb_1.BinaryWriter): Uint8Array | void {
const writer = w || new pb_1.BinaryWriter();
if (this.has_common)
writer.writeMessage(1, this.common, () => this.common.serialize(writer));
if (this.state != 0)
writer.writeInt32(2, this.state);
if (!w)
return writer.getResultBuffer();
}
static deserialize(bytes: Uint8Array | pb_1.BinaryReader): IscsFan {
const reader = bytes instanceof pb_1.BinaryReader ? bytes : new pb_1.BinaryReader(bytes), message = new IscsFan();
while (reader.nextField()) {
if (reader.isEndGroup())
break;
switch (reader.getFieldNumber()) {
case 1:
reader.readMessage(message.common, () => message.common = CommonState.deserialize(reader));
break;
case 2:
message.state = reader.readInt32();
break;
default: reader.skipField();
}
}
return message;
}
serializeBinary(): Uint8Array {
return this.serialize();
}
static deserializeBinary(bytes: Uint8Array): IscsFan {
return IscsFan.deserialize(bytes);
}
}
}

View File

@ -0,0 +1,51 @@
import { Graphics } from 'pixi.js';
export function drawArrow(
polygon: Graphics,
x: number,
y: number,
length: number,
radius: number,
lineWidth: number
) {
const trianglAcme = { x, y };
const triangleP1 = {
x: x - radius - Math.sin(Math.PI / 6),
y: y + Math.cos(Math.PI / 6) * radius,
};
const triangleP2 = {
x: x - radius - Math.sin(Math.PI / 6),
y: y - Math.cos(Math.PI / 6) * radius,
};
const lineP1 = {
x: x - radius - Math.sin(Math.PI / 6),
y: y + lineWidth / 2,
};
const lineP2 = {
x: x - length,
y: y + lineWidth / 2,
};
const lineP3 = {
x: x - length,
y: y - lineWidth / 2,
};
const lineP4 = {
x: x - radius - Math.sin(Math.PI / 6),
y: y - lineWidth / 2,
};
polygon.drawPolygon(
trianglAcme.x,
trianglAcme.y,
triangleP1.x,
triangleP1.y,
lineP1.x,
lineP1.y,
lineP2.x,
lineP2.y,
lineP3.x,
lineP3.y,
lineP4.x,
lineP4.y,
triangleP2.x,
triangleP2.y
);
}

View File

@ -1,6 +1,7 @@
import { import {
GraphicAnimation, GraphicAnimation,
GraphicData, GraphicData,
GraphicState,
JlGraphic, JlGraphic,
JlGraphicTemplate, JlGraphicTemplate,
} from 'src/jlgraphic'; } from 'src/jlgraphic';
@ -23,12 +24,16 @@ export interface IIscsFanData extends GraphicData {
set code(v: string); set code(v: string);
} }
export interface IIscsFanState extends GraphicState {
get state(): number;
set state(v: number);
}
export class IscsFan extends JlGraphic { export class IscsFan extends JlGraphic {
static Type = 'IscsFan'; static Type = 'IscsFan';
_border: Sprite; _border: Sprite;
_fan: Sprite; _fan: Sprite;
fanTextures: FanTextures; fanTextures: FanTextures;
__state = 0;
constructor(fanTextures: FanTextures) { constructor(fanTextures: FanTextures) {
super(IscsFan.Type); super(IscsFan.Type);
@ -42,28 +47,31 @@ export class IscsFan extends JlGraphic {
this.addChild(this._border); this.addChild(this._border);
this.addChild(this._fan); this.addChild(this._fan);
} }
get states(): IIscsFanState {
return this.getStates<IIscsFanState>();
}
doRepaint(): void { doRepaint(): void {
if (this.__state === 0) { if (this.states.state === 0) {
// 停止 // 停止
this.stopFanRun(); this.stopFanRun();
this._fan.rotation = 0; this._fan.rotation = 0;
this._fan.texture = this.fanTextures.gray; this._fan.texture = this.fanTextures.gray;
} else if (this.__state === 1) { } else if (this.states.state === 1) {
// 正常运行 // 正常运行
this._fan.texture = this.fanTextures.green; this._fan.texture = this.fanTextures.green;
// 动画 // 动画
this.initFanRun(); this.initFanRun();
} else if (this.__state === 2) { } else if (this.states.state === 2) {
// 报警运行 // 报警运行
this._fan.texture = this.fanTextures.yellow; this._fan.texture = this.fanTextures.yellow;
// 动画 // 动画
this.initFanRun(); this.initFanRun();
} else if (this.__state === 3) { } else if (this.states.state === 3) {
// 故障 // 故障
this.stopFanRun(); this.stopFanRun();
this._fan.rotation = 0; this._fan.rotation = 0;
this._fan.texture = this.fanTextures.red; this._fan.texture = this.fanTextures.red;
} else if (this.__state === 4) { } else if (this.states.state === 4) {
// 通信故障 // 通信故障
// 停止 // 停止
this.stopFanRun(); this.stopFanRun();
@ -99,12 +107,18 @@ export class IscsFan extends JlGraphic {
export class IscsFanTemplate extends JlGraphicTemplate<IscsFan> { export class IscsFanTemplate extends JlGraphicTemplate<IscsFan> {
fanTextures?: FanTextures; fanTextures?: FanTextures;
constructor() { constructor(dataTemplate: IIscsFanData, stateTemplate: IIscsFanState) {
super(IscsFan.Type); super(IscsFan.Type, {
dataTemplate,
stateTemplate,
});
} }
new(): IscsFan { new(): IscsFan {
if (this.fanTextures) { if (this.fanTextures) {
return new IscsFan(this.fanTextures); const g = new IscsFan(this.fanTextures);
g.loadData(this.datas);
g.loadState(this.states);
return g;
} }
throw new Error('资源未加载/加载失败'); throw new Error('资源未加载/加载失败');
} }

View File

@ -1,7 +1,10 @@
import { FederatedMouseEvent, Point } from 'pixi.js'; import { FederatedMouseEvent, Point } from 'pixi.js';
import { import {
AbsorbableLine,
AbsorbablePosition,
GraphicDrawAssistant, GraphicDrawAssistant,
GraphicInteractionPlugin, GraphicInteractionPlugin,
GraphicTransformEvent,
JlDrawApp, JlDrawApp,
JlGraphic, JlGraphic,
} from 'src/jlgraphic'; } from 'src/jlgraphic';
@ -13,9 +16,8 @@ export class IscsFanDraw extends GraphicDrawAssistant<
> { > {
_iscsFan: IscsFan | null = null; _iscsFan: IscsFan | null = null;
constructor(app: JlDrawApp, createData: () => IIscsFanData) { constructor(app: JlDrawApp, template: IscsFanTemplate) {
const template = new IscsFanTemplate(); super(app, template, IscsFan.Type, '风机');
super(app, template, createData, IscsFan.Type, '风机');
IscsFanInteraction.init(app); IscsFanInteraction.init(app);
} }
@ -66,12 +68,42 @@ export class IscsFanInteraction extends GraphicInteractionPlugin<IscsFan> {
bind(g: IscsFan): void { bind(g: IscsFan): void {
g.eventMode = 'static'; g.eventMode = 'static';
g.cursor = 'pointer'; g.cursor = 'pointer';
g.scalable = true; // g.scalable = true;
g.rotatable = true; g.rotatable = true;
g.on('drag-start', () => {
console.log('风机拖拽');
});
g.on('transformstart', (e: GraphicTransformEvent) => {
if (e.isShift()) {
g.getGraphicApp().setOptions({
absorbablePositions: buildAbsorbablePositions(g),
});
}
});
} }
unbind(g: IscsFan): void { unbind(g: IscsFan): void {
g.eventMode = 'none'; g.eventMode = 'none';
g.scalable = false; // g.scalable = false;
g.rotatable = false; g.rotatable = false;
} }
} }
function buildAbsorbablePositions(
g: IscsFan
): AbsorbablePosition[] | undefined {
const app = g.getGraphicApp();
const canvas = app.canvas;
const store = app.queryStore;
const aps: AbsorbablePosition[] = [];
store.queryByType(IscsFan.Type).forEach((fan) => {
if (fan.id === g.id) return;
const p = fan.position;
aps.push(
new AbsorbableLine(new Point(0, p.y), new Point(canvas.width, p.y))
);
aps.push(
new AbsorbableLine(new Point(p.x, 0), new Point(p.x, canvas.height))
);
});
return aps;
}

View File

@ -89,8 +89,10 @@ export class LinkTemplate extends JlGraphicTemplate<Link> {
lineWidth: number; lineWidth: number;
lineColor: string; lineColor: string;
segmentsCount: number; segmentsCount: number;
constructor() { constructor(dataTemplate: ILinkData) {
super(Link.Type); super(Link.Type, {
dataTemplate,
});
this.lineWidth = 2; this.lineWidth = 2;
this.lineColor = '#000000'; this.lineColor = '#000000';
this.curve = false; this.curve = false;

View File

@ -30,12 +30,13 @@ import {
ILineGraphic, ILineGraphic,
PolylineEditPlugin, PolylineEditPlugin,
addWayPoint, addWayPoint,
addWaypointConfig,
clearWayPoint, clearWayPoint,
clearWaypointsConfig,
getWaypointRangeIndex, getWaypointRangeIndex,
removeBezierWayPoint,
removeLineWayPoint,
} from 'src/jlgraphic/plugins/GraphicEditPlugin'; } from 'src/jlgraphic/plugins/GraphicEditPlugin';
import { ContextMenu } from 'src/jlgraphic/ui/ContextMenu'; import { ContextMenu } from 'src/jlgraphic/ui/ContextMenu';
import { MenuItemOptions, MenuOptions } from 'src/jlgraphic/ui/Menu';
import { ILinkData, Link, LinkTemplate } from './Link'; import { ILinkData, Link, LinkTemplate } from './Link';
export interface ILinkDrawOptions { export interface ILinkDrawOptions {
@ -67,8 +68,8 @@ export class LinkDraw extends GraphicDrawAssistant<LinkTemplate, ILinkData> {
}, },
}); });
constructor(app: JlDrawApp, createData: () => ILinkData) { constructor(app: JlDrawApp, template: LinkTemplate) {
super(app, new LinkTemplate(), createData, Link.Type, '轨道Link'); super(app, template, Link.Type, '轨道Link');
this.container.addChild(this.graphic); this.container.addChild(this.graphic);
this.graphicTemplate.curve = true; this.graphicTemplate.curve = true;
@ -251,7 +252,7 @@ function buildAbsorbablePositions(link: Link): AbsorbablePosition[] {
* @param dp * @param dp
* @param index * @param index
*/ */
function onEditPointCreate( function onPolylineEditPointCreate(
g: ILineGraphic, g: ILineGraphic,
dp: DraggablePoint, dp: DraggablePoint,
index: number index: number
@ -267,8 +268,78 @@ function onEditPointCreate(
} }
}); });
} }
dp.on('rightclick', (e: FederatedMouseEvent) => {
// dp.getGraphicApp().openContextMenu(EditPointContextMenu.DefaultMenu);
// 路径中的点
const app = dp.getGraphicApp();
app.registerMenu(EditPointContextMenu);
removeWaypointConfig.handler = () => {
removeLineWayPoint(link, index);
};
clearWaypointsConfig.handler = () => {
clearWayPoint(link, false);
};
EditPointContextMenu.open(e.global);
});
} }
function onCurveEditPointCreate(
g: ILineGraphic,
dp: DraggablePoint,
index: number
): void {
const link = g as Link;
if (index === 0 || index == link.datas.points.length - 1) {
// 端点
dp.on('transformstart', (e: GraphicTransformEvent) => {
if (e.isShift()) {
link.getGraphicApp().setOptions({
absorbablePositions: buildAbsorbablePositions(link),
});
}
});
}
const c = index % 3;
dp.on('rightclick', (e: FederatedMouseEvent) => {
// dp.getGraphicApp().openContextMenu(EditPointContextMenu.DefaultMenu);
if (c === 0) {
// 路径中的点
const app = dp.getGraphicApp();
app.registerMenu(EditPointContextMenu);
removeWaypointConfig.handler = () => {
removeBezierWayPoint(link, index);
};
clearWaypointsConfig.handler = () => {
clearWayPoint(link, true);
};
EditPointContextMenu.open(e.global);
}
});
}
export const addWaypointConfig: MenuItemOptions = {
name: '添加路径点',
};
export const addWaySegmentingConfig: MenuItemOptions = {
name: '细分',
};
export const removeWaypointConfig: MenuItemOptions = {
name: '移除路径点',
};
export const clearWaypointsConfig: MenuItemOptions = {
name: '清除所有路径点',
};
const menuOptions: MenuOptions = {
name: '图形编辑点菜单',
groups: [
{
items: [removeWaypointConfig, clearWaypointsConfig],
},
],
};
const EditPointContextMenu = ContextMenu.init(menuOptions);
const LinkEditMenu: ContextMenu = ContextMenu.init({ const LinkEditMenu: ContextMenu = ContextMenu.init({
name: '轨道编辑菜单', name: '轨道编辑菜单',
groups: [ groups: [
@ -311,14 +382,17 @@ export class LinkPointsEditPlugin extends GraphicInteractionPlugin<Link> {
const target = e.target as DisplayObject; const target = e.target as DisplayObject;
const link = target.getGraphic() as Link; const link = target.getGraphic() as Link;
this.app.updateSelected(link); this.app.updateSelected(link);
const p = link.getGraphicApp().toCanvasCoordinates(e.global);
console.log('右键坐标', p);
addWaypointConfig.handler = () => { addWaypointConfig.handler = () => {
const linePoints = link.linePoints; const linePoints = link.linePoints;
const p = link.screenToLocalPoint(e.global); console.log('添加路径点', linePoints, p);
const { start, end } = getWaypointRangeIndex( const { start, end } = getWaypointRangeIndex(
linePoints, linePoints,
link.datas.curve, link.datas.curve,
p p,
link.datas.lineWidth
); );
addWayPoint(link, link.datas.curve, start, end, p); addWayPoint(link, link.datas.curve, start, end, p);
}; };
@ -338,7 +412,7 @@ export class LinkPointsEditPlugin extends GraphicInteractionPlugin<Link> {
); );
if (!lep) { if (!lep) {
lep = new BezierCurveEditPlugin(link, { lep = new BezierCurveEditPlugin(link, {
onEditPointCreate, onEditPointCreate: onCurveEditPointCreate,
}); });
link.addAssistantAppend(lep); link.addAssistantAppend(lep);
} }
@ -348,7 +422,9 @@ export class LinkPointsEditPlugin extends GraphicInteractionPlugin<Link> {
PolylineEditPlugin.Name PolylineEditPlugin.Name
); );
if (!lep) { if (!lep) {
lep = new PolylineEditPlugin(link, { onEditPointCreate }); lep = new PolylineEditPlugin(link, {
onEditPointCreate: onPolylineEditPointCreate,
});
link.addAssistantAppend(lep); link.addAssistantAppend(lep);
} }
} }

View File

@ -3,6 +3,7 @@ import {
GraphicData, GraphicData,
JlGraphic, JlGraphic,
JlGraphicTemplate, JlGraphicTemplate,
VectorText,
getRectangleCenter, getRectangleCenter,
} from 'src/jlgraphic'; } from 'src/jlgraphic';
@ -11,6 +12,8 @@ export interface IPlatformData extends GraphicData {
set code(v: string); set code(v: string);
get hasdoor(): boolean; // 是否有屏蔽门 get hasdoor(): boolean; // 是否有屏蔽门
set hasdoor(v: boolean); set hasdoor(v: boolean);
get trainDirection(): string; // 行驶方向--屏蔽门上下
set trainDirection(v: string);
get lineWidth(): number; // 线宽 get lineWidth(): number; // 线宽
set lineWidth(v: number); set lineWidth(v: number);
get lineColor(): string; // 站台线色 get lineColor(): string; // 站台线色
@ -28,43 +31,90 @@ export interface IPlatformData extends GraphicData {
eq(other: IPlatformData): boolean; eq(other: IPlatformData): boolean;
} }
//站台颜色
export enum PlatformColorEnum {
blue = '0x0fe81f', //站台的颜色
lightBlue = '0x55d15d',
yellow = '0xfbff00',
white = '0xffffff',
lozengeRed = '0xff0000', //站台旁的菱形图标
whiteNumbers = '0xffffff', //站台旁白色数字
HCharYellow = '0xfbff00', //站台旁的H字符
HCharWhite = '0xffffff',
HCharRed = '0xff0000',
doorBlue = '0x008000', //屏蔽门的颜色
doorRed = '0xff0000',
}
const platformConsts = {
width: 60,
height: 20,
lineWidth: 3,
besideFontSize: 12,
doorOpenSpacing: 5,
doorPlatformSpacing: 10,
besideSpacing: 10,
};
export class Platform extends JlGraphic { export class Platform extends JlGraphic {
static Type = 'Platform'; static Type = 'Platform';
platformGraphic: Graphics; platformGraphic: Graphics;
doorGraphic: Graphics; doorGraphic: Graphics;
doorCloseGraphic: Graphics;
besideGraphic: Graphics;
codeGraph: VectorText = new VectorText(''); //站台旁数字、字符
constructor() { constructor() {
super(Platform.Type); super(Platform.Type);
this.platformGraphic = new Graphics(); this.platformGraphic = new Graphics();
this.doorGraphic = new Graphics(); this.doorGraphic = new Graphics();
this.doorCloseGraphic = new Graphics();
this.besideGraphic = new Graphics();
this.addChild(this.platformGraphic); this.addChild(this.platformGraphic);
this.addChild(this.doorGraphic); this.addChild(this.doorGraphic);
this.addChild(this.doorCloseGraphic);
this.addChild(this.besideGraphic);
this.addChild(this.codeGraph);
this.codeGraph.setVectorFontSize(platformConsts.besideFontSize);
} }
get datas(): IPlatformData { get datas(): IPlatformData {
return this.getDatas<IPlatformData>(); return this.getDatas<IPlatformData>();
} }
doRepaint(): void { doRepaint(): void {
const width = this.datas.width;
const height = this.datas.height;
//屏蔽门 //屏蔽门
const doorGraphic = this.doorGraphic; const doorGraphic = this.doorGraphic;
const doorCloseGraphic = this.doorCloseGraphic;
doorGraphic.clear(); doorGraphic.clear();
doorCloseGraphic.clear();
if (this.datas.hasdoor) { if (this.datas.hasdoor) {
doorGraphic.clear();
doorGraphic.lineStyle( doorGraphic.lineStyle(
this.datas.lineWidth, this.datas.lineWidth,
new Color(this.datas.lineColorDoor) new Color(this.datas.lineColorDoor)
); );
const width = this.datas.width; doorGraphic.moveTo(-width / 2 - this.datas.lineWidth / 2, 0);
const height = this.datas.height; doorGraphic.lineTo(-platformConsts.doorOpenSpacing, 0);
doorGraphic.moveTo( doorGraphic.moveTo(platformConsts.doorOpenSpacing, 0);
-width / 2 - this.datas.lineWidth / 2, doorGraphic.lineTo(width / 2 + this.datas.lineWidth / 2, 0);
-height / 2 - 10 //屏蔽门闭合
doorCloseGraphic.lineStyle(
this.datas.lineWidth,
new Color(this.datas.lineColorDoor)
); );
doorGraphic.lineTo( doorCloseGraphic.moveTo(-platformConsts.doorOpenSpacing, 0);
width / 2 + this.datas.lineWidth / 2, doorCloseGraphic.lineTo(platformConsts.doorOpenSpacing, 0);
-height / 2 - 10 doorGraphic.position.set(
0,
-height / 2 - platformConsts.doorPlatformSpacing
);
doorCloseGraphic.position.set(
0,
-height / 2 - platformConsts.doorPlatformSpacing
); );
} }
//站台
const platformGraphic = this.platformGraphic; const platformGraphic = this.platformGraphic;
platformGraphic.clear(); platformGraphic.clear();
platformGraphic.lineStyle( platformGraphic.lineStyle(
@ -74,14 +124,64 @@ export class Platform extends JlGraphic {
platformGraphic.beginFill(this.datas.lineColor, 1); platformGraphic.beginFill(this.datas.lineColor, 1);
platformGraphic.drawRect(0, 0, this.datas.width, this.datas.height); platformGraphic.drawRect(0, 0, this.datas.width, this.datas.height);
platformGraphic.endFill; platformGraphic.endFill;
const rect = new Rectangle(0, 0, this.datas.width, this.datas.height); const rectP = new Rectangle(0, 0, this.datas.width, this.datas.height);
platformGraphic.pivot = getRectangleCenter(rect); platformGraphic.pivot = getRectangleCenter(rectP);
this.position.set(this.datas.point.x, this.datas.point.y); this.position.set(this.datas.point.x, this.datas.point.y);
//站台旁菱形图标
const besideGraphic = this.besideGraphic;
besideGraphic.clear();
besideGraphic.lineStyle(1, new Color(PlatformColorEnum.lozengeRed));
besideGraphic.drawRect(0, 0, this.datas.height / 4, this.datas.height / 4);
const rect = new Rectangle(
0,
0,
this.datas.height / 4,
this.datas.height / 4
);
besideGraphic.pivot = getRectangleCenter(rect);
besideGraphic.rotation = Math.PI / 4;
besideGraphic.position.set(
-width / 2 - this.datas.lineWidth / 2 - platformConsts.besideSpacing,
0
);
//站台旁的数字、字符
const codeGraph = this.codeGraph;
codeGraph.text = 'H';
codeGraph.anchor.set(0.5);
codeGraph.position.set(
-width / 2 - this.datas.lineWidth / 2 - platformConsts.besideSpacing,
0
);
codeGraph.style.fill = PlatformColorEnum.HCharYellow;
//站台方向
if (this.datas.trainDirection == 'right') {
doorGraphic.position.set(
0,
height / 2 + platformConsts.doorPlatformSpacing
);
doorCloseGraphic.position.set(
0,
height / 2 + platformConsts.doorPlatformSpacing
);
besideGraphic.position.set(
width / 2 + this.datas.lineWidth / 2 + platformConsts.besideSpacing,
0
);
codeGraph.position.set(
width / 2 + this.datas.lineWidth / 2 + platformConsts.besideSpacing,
0
);
}
//子元素显隐
doorCloseGraphic.visible = false;
/* besideGraphic.visible = false;
codeGraph.visible = false; */
} }
} }
export class PlatformTemplate extends JlGraphicTemplate<Platform> { export class PlatformTemplate extends JlGraphicTemplate<Platform> {
hasdoor: boolean; hasdoor: boolean;
trainDirection: string;
lineWidth: number; lineWidth: number;
lineColor: string; lineColor: string;
lineColorDoor: string; lineColorDoor: string;
@ -89,12 +189,13 @@ export class PlatformTemplate extends JlGraphicTemplate<Platform> {
height: number; height: number;
constructor() { constructor() {
super(Platform.Type); super(Platform.Type);
this.lineWidth = 2;
this.lineColor = '#000000';
this.lineColorDoor = '0x008000';
this.hasdoor = true; this.hasdoor = true;
this.width = 100; this.trainDirection = 'left';
this.height = 30; this.lineWidth = platformConsts.lineWidth;
this.lineColor = PlatformColorEnum.yellow;
this.lineColorDoor = PlatformColorEnum.doorBlue;
this.width = platformConsts.width;
this.height = platformConsts.height;
} }
new(): Platform { new(): Platform {
return new Platform(); return new Platform();

View File

@ -7,14 +7,15 @@ import {
} from 'pixi.js'; } from 'pixi.js';
import { import {
GraphicDrawAssistant, GraphicDrawAssistant,
GraphicInteractionPlugin,
JlDrawApp, JlDrawApp,
KeyListener, JlGraphic,
getRectangleCenter, getRectangleCenter,
} from 'src/jlgraphic'; } from 'src/jlgraphic';
import { IPlatformData, Platform, PlatformTemplate } from './Platform'; import { IPlatformData, Platform, PlatformTemplate } from './Platform';
export interface ILinkDrawOptions { export interface IPlatformDrawOptions {
newData: () => IPlatformData; newData: () => IPlatformData;
} }
@ -26,15 +27,6 @@ export class PlatformDraw extends GraphicDrawAssistant<
platformGraphic: Graphics = new Graphics(); platformGraphic: Graphics = new Graphics();
doorGraphic: Graphics = new Graphics(); doorGraphic: Graphics = new Graphics();
// 快捷绘制
keypListener: KeyListener = new KeyListener({
value: 'KeyP',
global: true,
onPress: () => {
this.graphicTemplate.hasdoor = true;
},
});
constructor(app: JlDrawApp, createData: () => IPlatformData) { constructor(app: JlDrawApp, createData: () => IPlatformData) {
super( super(
app, app,
@ -46,24 +38,20 @@ export class PlatformDraw extends GraphicDrawAssistant<
this.container.addChild(this.platformGraphic); this.container.addChild(this.platformGraphic);
this.container.addChild(this.doorGraphic); this.container.addChild(this.doorGraphic);
this.graphicTemplate.hasdoor = true; this.graphicTemplate.hasdoor = true;
platformInteraction.init(app);
} }
bind(): void { bind(): void {
super.bind(); super.bind();
this.app.addKeyboardListener(this.keypListener);
} }
unbind(): void { unbind(): void {
super.unbind(); super.unbind();
this.app.removeKeyboardListener(this.keypListener);
} }
clearCache(): void { clearCache(): void {
this.platformGraphic.clear(); this.platformGraphic.clear();
this.doorGraphic.clear(); this.doorGraphic.clear();
} }
onRightClick(): void {
this.createAndStore(true);
}
onLeftDown(e: FederatedPointerEvent): void { onLeftDown(e: FederatedPointerEvent): void {
const { x, y } = this.toCanvasCoordinates(e.global); const { x, y } = this.toCanvasCoordinates(e.global);
const p = new Point(x, y); const p = new Point(x, y);
@ -85,6 +73,7 @@ export class PlatformDraw extends GraphicDrawAssistant<
const height = template.height; const height = template.height;
doorGraphic.moveTo(-width / 2 - template.lineWidth / 2, -height / 2 - 10); doorGraphic.moveTo(-width / 2 - template.lineWidth / 2, -height / 2 - 10);
doorGraphic.lineTo(width / 2 + template.lineWidth / 2, -height / 2 - 10); doorGraphic.lineTo(width / 2 + template.lineWidth / 2, -height / 2 - 10);
doorGraphic.position.set(p.x, p.y);
} }
//站台 //站台
@ -102,6 +91,7 @@ export class PlatformDraw extends GraphicDrawAssistant<
prepareData(data: IPlatformData): boolean { prepareData(data: IPlatformData): boolean {
const template = this.graphicTemplate; const template = this.graphicTemplate;
data.hasdoor = template.hasdoor; data.hasdoor = template.hasdoor;
data.trainDirection = template.trainDirection;
data.point = this.point; data.point = this.point;
data.lineWidth = template.lineWidth; data.lineWidth = template.lineWidth;
data.lineColor = template.lineColor; data.lineColor = template.lineColor;
@ -111,3 +101,29 @@ export class PlatformDraw extends GraphicDrawAssistant<
return true; return true;
} }
} }
export class platformInteraction extends GraphicInteractionPlugin<Platform> {
static Name = 'platform_transform';
constructor(app: JlDrawApp) {
super(platformInteraction.Name, app);
}
static init(app: JlDrawApp) {
return new platformInteraction(app);
}
filter(...grahpics: JlGraphic[]): Platform[] | undefined {
return grahpics
.filter((g) => g.type === Platform.Type)
.map((g) => g as Platform);
}
bind(g: Platform): void {
g.eventMode = 'static';
g.cursor = 'pointer';
g.scalable = true;
g.rotatable = true;
}
unbind(g: Platform): void {
g.eventMode = 'none';
g.scalable = false;
g.rotatable = false;
}
}

86
src/graphics/rect/Rect.ts Normal file
View File

@ -0,0 +1,86 @@
import { Color, Graphics, IPointData, Point } from 'pixi.js';
import { GraphicData, JlGraphic, JlGraphicTemplate } from 'src/jlgraphic';
export interface IRectData extends GraphicData {
get code(): string; // 编号
set code(v: string);
get lineWidth(): number; // 线宽
set lineWidth(v: number);
get lineColor(): string; // 线色
set lineColor(v: string);
get point(): IPointData; // 位置坐标
set point(point: IPointData);
get width(): number; // 宽度
set width(v: number);
get height(): number; // 高度
set height(v: number);
get points(): IPointData[]; // 线坐标点
set points(points: IPointData[]);
clone(): IRectData;
copyFrom(data: IRectData): void;
eq(other: IRectData): boolean;
}
const rectConsts = {
lineWidth: 2,
lineColor: '0xff0000',
width: 60,
height: 20,
};
export class Rect extends JlGraphic {
static Type = 'Rect';
rectGraphic: Graphics;
constructor() {
super(Rect.Type);
this.rectGraphic = new Graphics();
this.addChild(this.rectGraphic);
}
get datas(): IRectData {
return this.getDatas<IRectData>();
}
doRepaint(): void {
const width = this.datas.width;
const height = this.datas.height;
if (this.linePoints.length == 0) {
const r1 = new Point(this.datas.point.x, this.datas.point.y);
const r2 = new Point(r1.x + width, r1.y);
const r3 = new Point(r1.x + width, r1.y + height);
const r4 = new Point(r1.x, r1.y + height);
this.datas.points = [r1, r2, r3, r4, r1];
}
const rectGraphic = this.rectGraphic;
rectGraphic.clear();
rectGraphic.lineStyle(
this.datas.lineWidth,
new Color(this.datas.lineColor)
);
rectGraphic.drawPolygon(this.datas.points);
}
get linePoints(): IPointData[] {
return this.datas.points;
}
set linePoints(points: IPointData[]) {
const old = this.datas.clone();
old.points = points;
this.updateData(old);
}
}
export class RectTemplate extends JlGraphicTemplate<Rect> {
lineWidth: number;
lineColor: string;
width: number;
height: number;
constructor() {
super(Rect.Type);
this.lineWidth = rectConsts.lineWidth;
this.lineColor = rectConsts.lineColor;
this.width = rectConsts.width;
this.height = rectConsts.height;
}
new(): Rect {
return new Rect();
}
}

View File

@ -0,0 +1,261 @@
import {
FederatedPointerEvent,
Graphics,
Point,
IHitArea,
DisplayObject,
FederatedMouseEvent,
} from 'pixi.js';
import {
DraggablePoint,
GraphicApp,
GraphicDrawAssistant,
GraphicInteractionPlugin,
GraphicTransformEvent,
JlDrawApp,
JlGraphic,
linePoint,
} from 'src/jlgraphic';
import AbsorbablePoint, {
AbsorbablePosition,
} from 'src/jlgraphic/graphic/AbsorbablePosition';
import {
ILineGraphic,
PolylineEditPlugin,
addWaySegmentingConfig,
addPolygonSegmentingPoint,
clearWayPoint,
clearWaypointsConfig,
getWayLineIndex,
} from 'src/jlgraphic/plugins/GraphicEditPlugin';
import { ContextMenu } from 'src/jlgraphic/ui/ContextMenu';
import { IRectData, Rect, RectTemplate } from './Rect';
import { Link } from '../link/Link';
export interface IRectDrawOptions {
newData: () => IRectData;
}
export class RectDraw extends GraphicDrawAssistant<RectTemplate, IRectData> {
point1: Point | null = null;
point2: Point | null = null;
rectGraphic: Graphics = new Graphics();
constructor(app: JlDrawApp, createData: () => IRectData) {
super(app, new RectTemplate(), createData, Rect.Type, '站台Rect');
this.container.addChild(this.rectGraphic);
RectPointsEditPlugin.init(app);
}
bind(): void {
super.bind();
}
unbind(): void {
super.unbind();
}
clearCache(): void {
this.rectGraphic.clear();
}
onLeftDown(e: FederatedPointerEvent): void {
const { x, y } = this.toCanvasCoordinates(e.global);
const p = new Point(x, y);
if (this.point1 === null) {
this.point1 = p;
} else {
this.point2 = p;
this.createAndStore(true);
this.point1 = null;
this.point2 = null;
}
}
redraw(p: Point): void {
const template = this.graphicTemplate;
if (this.point1 === null) return;
const rectGraphic = this.rectGraphic;
rectGraphic.clear();
rectGraphic.lineStyle(template.lineWidth, template.lineColor);
rectGraphic.drawRect(...this.normalize(this.point1, p));
}
//根据画的两个点确定左上角的点的坐标和矩形宽高
private normalize(p1: Point, p2: Point): [number, number, number, number] {
const { abs } = Math;
const x = p1.x < p2.x ? p1.x : p2.x;
const y = p1.y < p2.y ? p1.y : p2.y;
const w = abs(p1.x - p2.x);
const h = abs(p1.y - p2.y);
return [x, y, w, h];
}
prepareData(data: IRectData): boolean {
if (this.point1 == null) {
console.log('Rect绘制因点不够取消绘制');
return false;
}
const p1 = this.point1 as Point;
const p2 = this.point2 as Point;
const [x, y, width, height] = this.normalize(p1, p2);
const template = this.graphicTemplate;
data.point = new Point(x, y);
data.lineWidth = template.lineWidth;
data.lineColor = template.lineColor;
data.width = width;
data.height = height;
return true;
}
}
//碰撞检测
export class RectGraphicHitArea implements IHitArea {
rect: Rect;
constructor(rect: Rect) {
this.rect = rect;
}
contains(x: number, y: number): boolean {
let contains = false;
const p = new Point(x, y);
const rectData = this.rect.datas;
//contains = pointPolygon(p, rectData.points, rectData.lineWidth);是否包含多边形内部
const tolerance = rectData.lineWidth;
for (let i = 0; i < rectData.points.length - 1; i++) {
const p1 = rectData.points[i];
const p2 = rectData.points[i + 1];
contains = contains || linePoint(p1, p2, p, tolerance);
if (contains) {
break;
}
}
return contains;
}
}
/**
*
* @param rect
* @returns
*/
function buildAbsorbablePositions(rect: Rect): AbsorbablePosition[] {
const aps: AbsorbablePosition[] = [];
const rects = rect.queryStore.queryByType<Rect>(Rect.Type);
const links = rect.queryStore.queryByType<Link>(Link.Type);
links.forEach((other) => {
const apa = new AbsorbablePoint(
other.localToCanvasPoint(other.getStartPoint())
);
const apb = new AbsorbablePoint(
other.localToCanvasPoint(other.getEndPoint())
);
aps.push(apa, apb);
});
rects.forEach((other) => {
if (other.id == rect.id) {
return;
}
other.linePoints.forEach((point) => {
const absorbablePoint = new AbsorbablePoint(
other.localToCanvasPoint(point)
);
aps.push(absorbablePoint);
});
});
return aps;
}
/**
*
* @param g
* @param dp
* @param index
*/
function onEditPointCreate(g: ILineGraphic, dp: DraggablePoint): void {
const rect = g as Rect;
// 端点
dp.on('transformstart', (e: GraphicTransformEvent) => {
if (e.isShift()) {
rect.getGraphicApp().setOptions({
absorbablePositions: buildAbsorbablePositions(rect),
});
}
});
}
const RectEditMenu: ContextMenu = ContextMenu.init({
name: '矩形编辑菜单',
groups: [
{
items: [addWaySegmentingConfig, clearWaypointsConfig],
},
],
});
/**
* rect路径编辑
*/
export class RectPointsEditPlugin extends GraphicInteractionPlugin<Rect> {
static Name = 'RectPointsDrag';
constructor(app: GraphicApp) {
super(RectPointsEditPlugin.Name, app);
app.registerMenu(RectEditMenu);
}
static init(app: GraphicApp): RectPointsEditPlugin {
return new RectPointsEditPlugin(app);
}
filter(...grahpics: JlGraphic[]): Rect[] | undefined {
return grahpics.filter((g) => g.type == Rect.Type) as Rect[];
}
bind(g: Rect): void {
g.rectGraphic.eventMode = 'static';
g.rectGraphic.cursor = 'pointer';
g.rectGraphic.hitArea = new RectGraphicHitArea(g);
g.on('_rightclick', this.onContextMenu, this);
g.on('selected', this.onSelected, this);
g.on('unselected', this.onUnselected, this);
}
unbind(g: Rect): void {
g.off('_rightclick', this.onContextMenu, this);
g.off('selected', this.onSelected, this);
g.off('unselected', this.onUnselected, this);
}
onContextMenu(e: FederatedMouseEvent) {
const target = e.target as DisplayObject;
const rect = target.getGraphic() as Rect;
this.app.updateSelected(rect);
addWaySegmentingConfig.handler = () => {
const linePoints = rect.linePoints;
const p = rect.screenToLocalPoint(e.global);
const { start, end } = getWayLineIndex(linePoints, p);
addPolygonSegmentingPoint(rect, start, end);
};
clearWaypointsConfig.handler = () => {
clearWayPoint(rect, false);
};
RectEditMenu.open(e.global);
}
onSelected(g: DisplayObject): void {
const rect = g as Rect;
let lep = rect.getAssistantAppend<PolylineEditPlugin>(
PolylineEditPlugin.Name
);
if (!lep) {
lep = new PolylineEditPlugin(rect, { onEditPointCreate });
rect.addAssistantAppend(lep);
}
lep.showAll();
}
onUnselected(g: DisplayObject): void {
const rect = g as Rect;
const lep = rect.getAssistantAppend<PolylineEditPlugin>(
PolylineEditPlugin.Name
);
if (lep) {
lep.hideAll();
}
}
}

View File

@ -0,0 +1,52 @@
import { Container } from '@pixi/display';
import { Graphics } from 'pixi.js';
export enum LampEnum {
lampPostColor = '0xc0c0c0',
lampDefaultColor = '0xff0000',
logicModeColor = '0x000000',
}
const lampConsts = {
lampRadius: 10,
logicModeLineWidth: 2,
logicModeDistance: 7,
};
export class Lamp extends Container {
circleLamp: Graphics = new Graphics();
logicMode: Graphics = new Graphics();
constructor() {
super();
this.addChild(this.circleLamp);
this.addChild(this.logicMode);
}
paint(radiusX: number, radiusY: number) {
this.circleLamp.clear();
this.circleLamp
.beginFill(LampEnum.lampDefaultColor, 1)
.drawCircle(radiusX, radiusY, lampConsts.lampRadius)
.endFill();
this.logicMode
.clear()
.lineStyle(lampConsts.logicModeLineWidth, LampEnum.logicModeColor)
.moveTo(
radiusX - lampConsts.logicModeDistance,
radiusY + lampConsts.logicModeDistance
)
.lineTo(
radiusX + lampConsts.logicModeDistance,
radiusY - lampConsts.logicModeDistance
)
.moveTo(
radiusX - lampConsts.logicModeDistance,
radiusY - lampConsts.logicModeDistance
)
.lineTo(
radiusX + lampConsts.logicModeDistance,
radiusY + lampConsts.logicModeDistance
);
}
}

View File

@ -0,0 +1,51 @@
import { Container } from '@pixi/display';
import { Graphics } from 'pixi.js';
import { Lamp } from './Lamp';
export enum LampEnum {
lampPostColor = '0xc0c0c0',
}
const lampConsts = {
verticalLampPostLength: 20,
levelLampPostLength: 5,
postLineWidth: 3,
lampRadius: 10,
};
export class LampMainBody extends Container {
lampNum = 1;
lampPost: Graphics = new Graphics();
lamps: Lamp[] = [];
constructor() {
super();
}
paint(lampNum: number) {
if (lampNum < 1) {
throw new Error('信号机灯数量异常');
}
this.lampNum = lampNum;
this.removeChildren(0);
this.lampPost = new Graphics();
this.lampPost
.lineStyle(lampConsts.postLineWidth, LampEnum.lampPostColor)
.moveTo(0, -lampConsts.verticalLampPostLength / 2)
.lineTo(0, lampConsts.verticalLampPostLength / 2)
.moveTo(0, 0)
.lineTo(lampConsts.levelLampPostLength, 0);
this.addChild(this.lampPost);
this.lamps = [];
for (let i = 0; i < this.lampNum; i++) {
const lamp = new Lamp();
const radiusX =
(1 + i * 2) * lampConsts.lampRadius + lampConsts.levelLampPostLength;
lamp.paint(radiusX, 0);
this.addChild(lamp);
this.lamps.push(lamp);
}
}
// setState() {}
}

View File

@ -0,0 +1,102 @@
import { Graphics } from 'pixi.js';
import {
GraphicData,
JlGraphic,
JlGraphicTemplate,
VectorText,
} from 'src/jlgraphic';
import { LampMainBody } from './LampMainBody';
import { drawArrow } from '../CommonGraphics';
export interface ISignalData extends GraphicData {
get code(): string; // 编号
set code(v: string);
clone(): ISignalData;
copyFrom(data: ISignalData): void;
eq(other: ISignalData): boolean;
}
export enum SignalColorEnum {
humanControlColor = '0xffff00',
fleetModeColor = '0x00ff00',
codeColor = '0X000000',
}
const signalConsts = {
lampNum: 1,
codeFontSize: 11,
nameOffsetY: 20,
fleetModeLength: 33,
fleetModeRadius: 10,
fleetModeLineWidth: 6,
};
export class Signal extends JlGraphic {
static Type = 'signal';
codeGraph: VectorText = new VectorText('');
humanControl: Graphics = new Graphics();
fleetMode: Graphics = new Graphics();
lampMainBody: LampMainBody = new LampMainBody();
constructor() {
super(Signal.Type);
this.codeGraph.name = 'signalCode';
this.addChild(this.codeGraph);
this.addChild(this.humanControl);
this.addChild(this.fleetMode);
this.addChild(this.lampMainBody);
}
get datas(): ISignalData {
return this.getDatas<ISignalData>();
}
paint(): void {
this.lampMainBody.paint(signalConsts.lampNum);
this.humanControl.beginFill(SignalColorEnum.humanControlColor, 1);
if (this.humanControl.drawRegularPolygon) {
this.humanControl.drawRegularPolygon(-10, 0, 10, 3, Math.PI / 2);
}
this.humanControl.endFill();
this.fleetMode.beginFill(SignalColorEnum.fleetModeColor, 1);
drawArrow(
this.fleetMode,
this.lampMainBody.width + signalConsts.fleetModeLength,
0,
signalConsts.fleetModeLength,
signalConsts.fleetModeRadius,
signalConsts.fleetModeLineWidth
);
this.fleetMode.endFill();
this.codeGraph.text = this.datas?.code || '信号机编号';
this.codeGraph.style.fill = SignalColorEnum.codeColor;
this.codeGraph.setVectorFontSize(signalConsts.codeFontSize);
this.codeGraph.anchor.set(0.5);
this.codeGraph.style.fill = SignalColorEnum.codeColor;
const codeTransform = this.datas?.childTransforms?.find(
(item) => item.name === 'signalCode'
);
if (codeTransform) {
const position = codeTransform?.transform.position;
const rotation = codeTransform?.transform?.rotation;
this.codeGraph.position.set(position?.x, position?.y);
this.codeGraph.rotation = rotation || 0;
} else {
this.codeGraph.position.set(0, signalConsts.nameOffsetY);
}
}
doRepaint(): void {
this.paint();
}
}
export class SignalTemplate extends JlGraphicTemplate<Signal> {
constructor(dataTemplate: ISignalData) {
super(Signal.Type, {
dataTemplate,
});
}
new(): Signal {
return new Signal();
}
}

View File

@ -0,0 +1,133 @@
import { FederatedPointerEvent, Point } from 'pixi.js';
import {
AbsorbableLine,
AbsorbablePosition,
GraphicApp,
GraphicDrawAssistant,
GraphicInteractionPlugin,
GraphicTransformEvent,
JlDrawApp,
JlGraphic,
} from 'src/jlgraphic';
import { ISignalData, Signal, SignalTemplate } from './Signal';
import { IscsFan } from '../iscs-fan/IscsFan';
export interface ISignalDrawOptions {
newData: () => ISignalData;
}
export class SignalDraw extends GraphicDrawAssistant<
SignalTemplate,
ISignalData
> {
_signal: Signal | null = null;
constructor(app: JlDrawApp, template: SignalTemplate) {
super(
app,
template,
'svguse: ../../drawIcon.svg#icon-signal',
'信号机Signal'
);
signalInteraction.init(app);
}
public get signal(): Signal {
if (!this._signal) {
this._signal = this.graphicTemplate.new();
this.signal.loadData(this.graphicTemplate.datas);
this.container.addChild(this.signal);
}
return this._signal;
}
clearCache(): void {
//this.codeGraph.clear();
}
onRightClick(): void {
this.createAndStore(true);
}
onLeftUp(e: FederatedPointerEvent): void {
this.container.position.copyFrom(this.toCanvasCoordinates(e.global));
this.createAndStore(true);
}
redraw(p: Point): void {
this.signal.paint();
this.container.position.set(p.x, p.y);
}
prepareData(data: ISignalData): boolean {
data.transform = this.container.saveTransform();
return true;
}
}
export class signalInteraction extends GraphicInteractionPlugin<Signal> {
static Name = 'signal_transform';
constructor(app: JlDrawApp) {
super(signalInteraction.Name, app);
}
static init(app: JlDrawApp) {
return new signalInteraction(app);
}
filter(...grahpics: JlGraphic[]): Signal[] | undefined {
return grahpics
.filter((g) => g.type === Signal.Type)
.map((g) => g as Signal);
}
bind(g: Signal): void {
g.eventMode = 'static';
g.cursor = 'pointer';
g.scalable = true;
g.rotatable = true;
g.codeGraph.draggable = true;
g.codeGraph.selectable = true;
g.codeGraph.rotatable = true;
g.codeGraph.scalable = true;
g.codeGraph.transformSave = true;
g.codeGraph.eventMode = 'static';
g.codeGraph.on('transformstart', (e: GraphicTransformEvent) => {
if (e.isShift()) {
console.log(
'信号机画布坐标',
e.target.position,
e.target.getPositionOnCanvas()
);
const app = g.getGraphicApp();
app.setOptions({
absorbablePositions: buildAbsorbablePositions(app),
});
}
});
}
// onScaleDragEnd() {
// console.log('-----------------');
// }
unbind(g: Signal): void {
g.eventMode = 'none';
g.scalable = false;
g.rotatable = false;
g.codeGraph.draggable = false;
g.codeGraph.selectable = false;
g.codeGraph.rotatable = false;
g.codeGraph.scalable = false;
g.codeGraph.transformSave = false;
g.codeGraph.eventMode = 'none';
}
}
function buildAbsorbablePositions(app: GraphicApp): AbsorbablePosition[] {
const canvas = app.canvas;
const store = app.queryStore;
const aps: AbsorbablePosition[] = [];
store.queryByType(IscsFan.Type).forEach((fan) => {
const p = fan.position;
aps.push(
new AbsorbableLine(new Point(0, p.y), new Point(canvas.width, p.y))
);
aps.push(
new AbsorbableLine(new Point(p.x, 0), new Point(p.x, canvas.height))
);
});
return aps;
}

View File

@ -0,0 +1,101 @@
import { Color, Graphics, IPointData, Point } from 'pixi.js';
import {
GraphicData,
JlGraphic,
JlGraphicTemplate,
VectorText,
} from 'src/jlgraphic';
export interface IStationData extends GraphicData {
get code(): string; // 编号
set code(v: string);
get hasCircle(): boolean; // 是否有圆圈--线网图
set hasCircle(v: boolean);
get radius(): number; // 半径
set radius(v: number);
get borderWidth(): number; // 线宽
set borderWidth(v: number);
get borderColor(): string; // 圆边框线色
set borderColor(v: string);
get fillColor(): string; // 圆填充颜色
set fillColor(v: string);
get codeColor(): string; // 车站字体颜色
set codeColor(v: string);
get codeFontSize(): number; // 车站字体大小
set codeFontSize(v: number);
get point(): IPointData; // 位置坐标
set point(point: IPointData);
get circlePoint(): IPointData; // 位置坐标
set circlePoint(point: IPointData);
clone(): IStationData;
copyFrom(data: IStationData): void;
eq(other: IStationData): boolean;
}
export class Station extends JlGraphic {
static Type = 'station';
codeGraph: VectorText = new VectorText(''); //站台旁数字、字符
circleGraphic: Graphics;
constructor() {
super(Station.Type);
this.circleGraphic = new Graphics();
this.addChild(this.codeGraph);
this.addChild(this.circleGraphic);
}
get datas(): IStationData {
return this.getDatas<IStationData>();
}
doRepaint(): void {
this.circleGraphic.clear();
this.position.set(this.datas.point.x, this.datas.point.y);
const codeGraph = this.codeGraph;
if (this.datas.code == '') {
codeGraph.text = '车站Station';
} else {
codeGraph.text = this.datas.code;
}
codeGraph.style.fill = this.datas.codeColor;
codeGraph.setVectorFontSize(this.datas.codeFontSize);
codeGraph.anchor.set(0.5);
if (this.datas.hasCircle) {
const circleGraphic = this.circleGraphic;
circleGraphic.lineStyle(
this.datas.borderWidth,
new Color(this.datas.borderColor)
);
circleGraphic.beginFill(this.datas.fillColor, 1);
circleGraphic.drawCircle(0, 0, this.datas.radius);
circleGraphic.endFill;
circleGraphic.position.set(
this.datas.circlePoint.x,
this.datas.circlePoint.y
);
}
}
}
export class StationTemplate extends JlGraphicTemplate<Station> {
hasCircle: boolean;
radius: number;
borderWidth: number;
borderColor: string;
fillColor: string;
codeColor: string;
codeFontSize: number;
circlePoint: IPointData;
constructor() {
super(Station.Type);
this.hasCircle = false;
this.radius = 5;
this.borderWidth = 1;
this.borderColor = '0xff0000';
this.fillColor = '0xff0000';
this.codeColor = '0xF48815';
this.codeFontSize = 22;
this.circlePoint = new Point(0, -20);
}
new(): Station {
return new Station();
}
}

View File

@ -0,0 +1,93 @@
import { FederatedPointerEvent, Point } from 'pixi.js';
import {
GraphicDrawAssistant,
GraphicInteractionPlugin,
JlDrawApp,
JlGraphic,
VectorText,
} from 'src/jlgraphic';
import { IStationData, Station, StationTemplate } from './Station';
export interface IStationDrawOptions {
newData: () => IStationData;
}
export class StationDraw extends GraphicDrawAssistant<
StationTemplate,
IStationData
> {
point: Point = new Point(0, 0);
codeGraph: VectorText = new VectorText('');
constructor(app: JlDrawApp, createData: () => IStationData) {
super(app, new StationTemplate(), createData, Station.Type, '车站Station');
this.container.addChild(this.codeGraph);
stationInteraction.init(app);
}
bind(): void {
super.bind();
}
unbind(): void {
super.unbind();
}
clearCache(): void {
//this.codeGraph.clear();
}
onLeftDown(e: FederatedPointerEvent): void {
const { x, y } = this.toCanvasCoordinates(e.global);
const p = new Point(x, y);
this.point = p;
this.createAndStore(true);
}
redraw(p: Point): void {
const codeGraph = this.codeGraph;
codeGraph.text = '车站Station';
codeGraph.anchor.set(0.5);
codeGraph.style.fill = '0xf48815';
codeGraph.position.set(p.x, p.y);
codeGraph.setVectorFontSize(22);
}
prepareData(data: IStationData): boolean {
const template = this.graphicTemplate;
data.point = this.point;
data.hasCircle = template.hasCircle;
data.radius = template.radius;
data.borderWidth = template.borderWidth;
data.borderColor = template.borderColor;
data.fillColor = template.fillColor;
data.codeColor = template.codeColor;
data.codeFontSize = template.codeFontSize;
data.circlePoint = template.circlePoint;
return true;
}
}
export class stationInteraction extends GraphicInteractionPlugin<Station> {
static Name = 'station_transform';
constructor(app: JlDrawApp) {
super(stationInteraction.Name, app);
}
static init(app: JlDrawApp) {
return new stationInteraction(app);
}
filter(...grahpics: JlGraphic[]): Station[] | undefined {
return grahpics
.filter((g) => g.type === Station.Type)
.map((g) => g as Station);
}
bind(g: Station): void {
g.eventMode = 'static';
g.cursor = 'pointer';
g.scalable = true;
g.rotatable = true;
}
unbind(g: Station): void {
g.eventMode = 'none';
g.scalable = false;
g.rotatable = false;
}
}

185
src/graphics/train/Train.ts Normal file
View File

@ -0,0 +1,185 @@
import { Color, Graphics, IPointData } from 'pixi.js';
import {
GraphicData,
JlGraphic,
JlGraphicTemplate,
VectorText,
} from 'src/jlgraphic';
export interface ITrainData extends GraphicData {
get code(): string; // 车号
set code(v: string);
get codeColor(): string; // 车号颜色
set codeColor(v: string);
get codeFontSize(): number; // 车号字体大小
set codeFontSize(v: number);
get trainDirection(): string; // 行驶方向
set trainDirection(v: string);
get hasBorder(): boolean; // 是否有边框
set hasBorder(v: boolean);
get borderWidth(): number; // 边框线宽
set borderWidth(v: number);
get borderColor(): string; // 边框颜色
set borderColor(v: string);
get headColor(): string; // 箭头颜色
set headColor(v: string);
get bodyColor(): string; // 背景色
set bodyColor(v: string);
get point(): IPointData; // 位置坐标
set point(point: IPointData);
clone(): ITrainData;
copyFrom(data: ITrainData): void;
eq(other: ITrainData): boolean;
}
// 列车颜色
export enum TrainColorEnum {
headColor = '0x00FF00', // 箭头颜色
bodyColor = '0xA388B1', // 背景色
codeColor = '0xffffff', // 车号颜色
borderColor = '0xA3E198', // 边框的颜色
}
export const trainConsts = {
borderWidth: 1,
codeFontSize: 22,
marginX: 2, // 图形x轴边距
pauseW: 2, // 停止框宽度
};
export class Train extends JlGraphic {
static Type = 'Train';
arrowLeft: Graphics;
pauseLeft: Graphics;
arrowRight: Graphics;
pauseRight: Graphics;
codeRact: Graphics;
codeGraph: VectorText = new VectorText(''); //车号
constructor() {
super(Train.Type);
this.arrowLeft = new Graphics();
this.pauseLeft = new Graphics();
this.arrowRight = new Graphics();
this.pauseRight = new Graphics();
this.codeRact = new Graphics();
this.addChild(this.arrowLeft);
this.addChild(this.pauseLeft);
this.addChild(this.arrowRight);
this.addChild(this.pauseRight);
this.addChild(this.codeRact);
this.addChild(this.codeGraph);
this.codeGraph.setVectorFontSize(trainConsts.codeFontSize);
}
get datas(): ITrainData {
return this.getDatas<ITrainData>();
}
doRepaint(): void {
this.position.set(this.datas.point.x, this.datas.point.y);
const codeGraph = this.codeGraph;
const codeRact = this.codeRact;
if (this.datas.code == '') {
codeGraph.text = '01110111';
} else {
codeGraph.text = this.datas.code;
}
codeGraph.setVectorFontSize(this.datas.codeFontSize);
codeGraph.anchor.set(0.5);
const style = {
fill: this.datas.codeColor,
padding: 5,
};
codeGraph.style = style;
const {
x: codeX,
y: codeY,
width: codeWidth,
height: codeHeight,
} = codeGraph.getLocalBounds();
const marginX = trainConsts.marginX;
const pauseW = trainConsts.pauseW;
const arrowLeft = this.arrowLeft;
arrowLeft.beginFill(this.datas.headColor, 1);
arrowLeft.drawPolygon([
-codeHeight * 0.4 - marginX - pauseW - marginX - codeWidth / 2,
0,
-marginX - pauseW - marginX - codeWidth / 2,
codeHeight / 2,
-marginX - pauseW - marginX - codeWidth / 2,
-codeHeight / 2,
]);
arrowLeft.endFill();
this.pauseLeft.beginFill(this.datas.headColor, 1);
this.pauseLeft.drawRect(0, 0, pauseW, codeHeight);
this.pauseLeft.endFill();
this.pauseLeft.position.set(
-marginX - pauseW - codeWidth / 2,
-codeHeight / 2
);
this.pauseRight.beginFill(this.datas.headColor, 1);
this.pauseRight.drawRect(0, 0, pauseW, codeHeight);
this.pauseRight.endFill();
this.pauseRight.position.set(marginX + codeWidth / 2, -codeHeight / 2);
const arrowRight = this.arrowRight;
arrowRight.beginFill(this.datas.headColor, 1);
arrowRight.drawPolygon([
codeWidth / 2 + marginX + pauseW + marginX + codeHeight * 0.4,
0,
codeWidth / 2 + marginX + pauseW + marginX,
codeHeight / 2,
codeWidth / 2 + marginX + pauseW + marginX,
-codeHeight / 2,
]);
arrowRight.endFill();
if (this.datas.hasBorder) {
codeRact.visible = true;
codeRact.lineStyle(
this.datas.borderWidth,
new Color(this.datas.borderColor)
);
codeRact.beginFill(new Color(this.datas.bodyColor));
codeRact.drawRect(codeX, codeY, codeWidth, codeHeight);
codeRact.endFill();
} else {
codeRact.visible = false;
}
// 运行方向控制箭头停止显隐
if (this.datas.trainDirection == 'right') {
this.arrowLeft.visible = false;
this.arrowRight.visible = true;
this.pauseLeft.visible = false;
this.pauseRight.visible = true;
} else {
this.arrowLeft.visible = true;
this.arrowRight.visible = false;
this.pauseLeft.visible = true;
this.pauseRight.visible = false;
}
}
}
export class TrainTemplate extends JlGraphicTemplate<Train> {
trainDirection: string;
codeFontSize: number;
hasBorder: boolean;
borderWidth: number;
borderColor: string;
headColor: string;
codeColor: string;
bodyColor: string;
constructor() {
super(Train.Type);
this.trainDirection = 'left';
this.codeFontSize = trainConsts.codeFontSize;
this.hasBorder = true;
this.borderWidth = trainConsts.borderWidth;
this.borderColor = TrainColorEnum.borderColor;
this.headColor = TrainColorEnum.headColor;
this.codeColor = TrainColorEnum.codeColor;
this.bodyColor = TrainColorEnum.bodyColor;
}
new(): Train {
return new Train();
}
}

View File

@ -0,0 +1,138 @@
import { Color, FederatedPointerEvent, Graphics, Point } from 'pixi.js';
import {
GraphicDrawAssistant,
GraphicInteractionPlugin,
JlDrawApp,
JlGraphic,
VectorText,
} from 'src/jlgraphic';
import { ITrainData, Train, TrainTemplate, trainConsts } from './Train';
export interface ITrainDrawOptions {
newData: () => ITrainData;
}
export class TrainDraw extends GraphicDrawAssistant<TrainTemplate, ITrainData> {
point: Point = new Point(0, 0);
arrowLeft: Graphics = new Graphics();
pauseLeft: Graphics = new Graphics();
codeRact: Graphics = new Graphics();
constructor(app: JlDrawApp, createData: () => ITrainData) {
super(app, new TrainTemplate(), createData, Train.Type, '列车Train');
this.container.addChild(this.arrowLeft);
this.container.addChild(this.pauseLeft);
this.container.addChild(this.codeRact);
this.graphicTemplate.hasBorder = true;
trainInteraction.init(app);
}
bind(): void {
super.bind();
}
unbind(): void {
super.unbind();
}
clearCache(): void {
this.arrowLeft.clear();
this.pauseLeft.clear();
this.codeRact.clear();
}
onLeftDown(e: FederatedPointerEvent): void {
const { x, y } = this.toCanvasCoordinates(e.global);
const p = new Point(x, y);
this.point = p;
this.createAndStore(true);
}
redraw(p: Point): void {
const template = this.graphicTemplate;
const codeGraph = new VectorText(''); // 车号
codeGraph.setVectorFontSize(22);
codeGraph.anchor.set(0.5);
codeGraph.text = '01110111';
const style = { padding: 5 };
codeGraph.style = style;
const { width, height } = codeGraph.getLocalBounds();
codeGraph.destroy();
const marginX = trainConsts.marginX;
const pauseW = trainConsts.pauseW;
// 边框
if (template.hasBorder) {
const codeRact = this.codeRact;
codeRact.clear();
codeRact.lineStyle(template.borderWidth, new Color(template.borderColor));
codeRact.beginFill(new Color(template.bodyColor));
codeRact.drawRect(-width / 2, -height / 2, width, height);
codeRact.endFill();
codeRact.position.set(p.x, p.y);
}
// 箭头
const arrowLeft = this.arrowLeft;
arrowLeft.clear();
this.point.set(p.x, p.y);
arrowLeft.beginFill(template.headColor, 1);
arrowLeft.drawPolygon([
-height * 0.4 - marginX - pauseW - marginX - width / 2,
0,
-marginX - pauseW - marginX - width / 2,
height / 2,
-marginX - pauseW - marginX - width / 2,
-height / 2,
]);
arrowLeft.endFill();
arrowLeft.position.set(this.point.x, this.point.y);
// 停止框
const pauseLeft = this.pauseLeft;
pauseLeft.clear();
pauseLeft.beginFill(template.headColor, 1);
pauseLeft.drawRect(0, 0, pauseW, height);
pauseLeft.endFill();
pauseLeft.position.set(
this.point.x - marginX - pauseW - width / 2,
this.point.y - height / 2
);
}
prepareData(data: ITrainData): boolean {
const template = this.graphicTemplate;
data.code = '01110111';
data.codeColor = template.codeColor;
data.codeFontSize = template.codeFontSize;
data.hasBorder = template.hasBorder;
data.trainDirection = template.trainDirection;
data.point = this.point;
data.borderWidth = template.borderWidth;
data.borderColor = template.borderColor;
data.headColor = template.headColor;
data.bodyColor = template.bodyColor;
return true;
}
}
export class trainInteraction extends GraphicInteractionPlugin<Train> {
static Name = 'train_transform';
constructor(app: JlDrawApp) {
super(trainInteraction.Name, app);
}
static init(app: JlDrawApp) {
return new trainInteraction(app);
}
filter(...grahpics: JlGraphic[]): Train[] | undefined {
return grahpics.filter((g) => g.type === Train.Type).map((g) => g as Train);
}
bind(g: Train): void {
g.eventMode = 'static';
g.cursor = 'pointer';
g.scalable = true;
g.rotatable = true;
}
unbind(g: Train): void {
g.eventMode = 'none';
g.scalable = false;
g.rotatable = false;
}
}

View File

@ -45,7 +45,6 @@ export abstract class GraphicDrawAssistant<
icon: string; // 界面显示的图标 icon: string; // 界面显示的图标
container: Container = new Container(); container: Container = new Container();
graphicTemplate: GT; graphicTemplate: GT;
createGraphicData: () => GD;
escListener: KeyListener = new KeyListener({ escListener: KeyListener = new KeyListener({
value: 'Escape', value: 'Escape',
@ -61,7 +60,6 @@ export abstract class GraphicDrawAssistant<
constructor( constructor(
graphicApp: JlDrawApp, graphicApp: JlDrawApp,
graphicTemplate: GT, graphicTemplate: GT,
createGraphicData: () => GD,
icon: string, icon: string,
description: string description: string
) { ) {
@ -69,7 +67,6 @@ export abstract class GraphicDrawAssistant<
this.app = graphicApp; this.app = graphicApp;
this.type = graphicTemplate.type; this.type = graphicTemplate.type;
this.graphicTemplate = graphicTemplate; this.graphicTemplate = graphicTemplate;
this.createGraphicData = createGraphicData;
this.icon = icon; this.icon = icon;
this.description = description; this.description = description;
this.app.registerGraphicTemplates(this.graphicTemplate); this.app.registerGraphicTemplates(this.graphicTemplate);
@ -102,7 +99,9 @@ export abstract class GraphicDrawAssistant<
unbind(): void { unbind(): void {
this.clearCache(); this.clearCache();
const canvas = this.canvas; const canvas = this.canvas;
canvas.removeChild(this.container); if (this.container?.parent) {
canvas.removeChild(this.container);
}
canvas.off('mousedown', this.onLeftDown, this); canvas.off('mousedown', this.onLeftDown, this);
canvas.off('mousemove', this.onMouseMove, this); canvas.off('mousemove', this.onMouseMove, this);
canvas.off('mouseup', this.onLeftUp, this); canvas.off('mouseup', this.onLeftUp, this);
@ -161,7 +160,7 @@ export abstract class GraphicDrawAssistant<
* App * App
*/ */
createAndStore(finish: boolean): JlGraphic | null { createAndStore(finish: boolean): JlGraphic | null {
const data = this.createGraphicData(); const data = this.graphicTemplate.datas as GD;
data.id = this.nextId(); data.id = this.nextId();
data.graphicType = this.graphicTemplate.type; data.graphicType = this.graphicTemplate.type;
if (!this.prepareData(data)) { if (!this.prepareData(data)) {
@ -457,13 +456,13 @@ export class JlDrawApp extends GraphicApp {
* *
*/ */
deleteSelectedGraphics() { deleteSelectedGraphics() {
const deletes = this.selectedGraphics.slice( const deletes = this.deleteGraphics(...this.selectedGraphics);
0, if (deletes.length > 0) {
this.selectedGraphics.length // 删除图形对象操作记录
); this.opRecord.record(new GraphicDeleteOperation(this, deletes));
this.deleteGraphics(...this.selectedGraphics); } else {
// 删除图形对象操作记录 console.debug('没有删除元素,不记录');
this.opRecord.record(new GraphicDeleteOperation(this, deletes)); }
} }
updateCanvasAndRecord(data: ICanvasProperties) { updateCanvasAndRecord(data: ICanvasProperties) {

View File

@ -288,6 +288,7 @@ export interface GraphicAppEvents extends GlobalMixins.GraphicAppEvents {
drag_op_end: [event: AppDragEvent]; drag_op_end: [event: AppDragEvent];
'pre-menu-handle': [menu: MenuItemOptions]; 'pre-menu-handle': [menu: MenuItemOptions];
'post-menu-handle': [menu: MenuItemOptions]; 'post-menu-handle': [menu: MenuItemOptions];
'websocket-state-change': [app: GraphicApp, connected: boolean];
destroy: [app: GraphicApp]; destroy: [app: GraphicApp];
} }
@ -319,6 +320,11 @@ export interface IGraphicAppConfig {
* true * true
*/ */
cullable?: boolean; cullable?: boolean;
/**
*
*/
isSupportDeletion?: (g: JlGraphic) => boolean;
} }
/** /**
@ -527,7 +533,10 @@ export class GraphicApp extends EventEmitter<GraphicAppEvents> {
*/ */
handleGraphicStates(graphicStates: GraphicState[]) { handleGraphicStates(graphicStates: GraphicState[]) {
graphicStates.forEach((state) => { graphicStates.forEach((state) => {
const list = this.queryStore.queryByIdOrCode(state.code); const list = this.queryStore.queryByIdOrCodeAndType(
state.code,
state.graphicType
);
if (list.length == 0) { if (list.length == 0) {
const template = this.getGraphicTemplatesByType(state.graphicType); const template = this.getGraphicTemplatesByType(state.graphicType);
const g = template.new(); const g = template.new();
@ -630,7 +639,7 @@ export class GraphicApp extends EventEmitter<GraphicAppEvents> {
this.addGraphics(g); this.addGraphics(g);
}); });
// 加载数据关系 // 加载数据关系
this.graphicStore.getAllGraphics().forEach((g) => g.loadRealtions()); this.graphicStore.getAllGraphics().forEach((g) => g.loadRelations());
// 更新id生成器 // 更新id生成器
const max = const max =
this.graphicStore this.graphicStore
@ -725,8 +734,19 @@ export class GraphicApp extends EventEmitter<GraphicAppEvents> {
* *
* @param graphics * @param graphics
*/ */
deleteGraphics(...graphics: JlGraphic[]) { deleteGraphics(...graphics: JlGraphic[]): JlGraphic[] {
graphics.forEach((g) => this.doDeleteGraphics(g)); const dels = graphics.filter((g) => {
if (
this._options?.isSupportDeletion == undefined ||
this._options.isSupportDeletion(g)
) {
this.doDeleteGraphics(g);
return true;
}
console.debug(`type=${g.type},id=${g.id}的图形不支持删除`);
return false;
});
return dels;
} }
/** /**
@ -888,10 +908,10 @@ export class GraphicApp extends EventEmitter<GraphicAppEvents> {
this.emit('destroy', this); this.emit('destroy', this);
if (this.wsMsgBroker) { if (this.wsMsgBroker) {
this.wsMsgBroker.close(); this.wsMsgBroker.close();
if (!StompCli.hasAppMsgBroker()) { // if (!StompCli.hasAppMsgBroker()) {
// 如果没有其他消息代理关闭websocket Stomp客户端 // // 如果没有其他消息代理关闭websocket Stomp客户端
StompCli.close(); // StompCli.close();
} // }
} }
this.interactionPluginMap.forEach((plugin) => { this.interactionPluginMap.forEach((plugin) => {
plugin.pause(); plugin.pause();

View File

@ -37,6 +37,12 @@ export interface GraphicQueryStore {
* @param v * @param v
*/ */
queryByIdOrCode(v: string): JlGraphic[]; queryByIdOrCode(v: string): JlGraphic[];
/**
* id或code及类型查询图形
* @param v
* @param type
*/
queryByIdOrCodeAndType(v: string, type: string): JlGraphic[];
/** /**
* code和类型获取图形 * code和类型获取图形
* @param code * @param code
@ -69,6 +75,7 @@ export class GraphicStore implements GraphicQueryStore {
this.store = new Map<string, JlGraphic>(); this.store = new Map<string, JlGraphic>();
this.relationManage = new RelationManage(app); this.relationManage = new RelationManage(app);
} }
/** /**
* *
*/ */
@ -125,6 +132,15 @@ export class GraphicStore implements GraphicQueryStore {
}); });
return list; return list;
} }
queryByIdOrCodeAndType(s: string, type: string): JlGraphic[] {
const list: JlGraphic[] = [];
this.store.forEach((g) => {
if (g.isIdOrCode(s) && g.type === type) {
list.push(g);
}
});
return list;
}
queryByCodeAndType<T extends JlGraphic>( queryByCodeAndType<T extends JlGraphic>(
code: string, code: string,
type: string type: string

View File

@ -148,6 +148,23 @@ DisplayObject.prototype.getAllParentScaled =
}); });
return scaled; return scaled;
}; };
DisplayObject.prototype.getPositionOnCanvas =
function getPositionOnCanvas(): Point {
if (this.parent.isCanvas()) {
return this.position;
} else {
return this.parent.localToCanvasPoint(this.position);
}
};
DisplayObject.prototype.updatePositionByCanvasPosition =
function updatePositionByCanvasPosition(p: IPointData): void {
if (this.parent.isCanvas()) {
this.position.copyFrom(p);
} else {
const localPosition = this.parent.canvasToLocalPoint(p);
this.position.copyFrom(localPosition);
}
};
DisplayObject.prototype.saveTransform = function saveTransform() { DisplayObject.prototype.saveTransform = function saveTransform() {
return GraphicTransform.fromObject(this); return GraphicTransform.fromObject(this);
}; };
@ -755,7 +772,7 @@ export abstract class JlGraphic extends Container {
/** /**
* *
*/ */
loadRealtions() {} loadRelations() {}
/** /**
* *
@ -923,15 +940,47 @@ export abstract class JlGraphic extends Container {
} }
} }
export type CreateData = () => GraphicData;
export type CreateState = () => GraphicState;
export interface IGraphicTemplateOptions {
dataTemplate?: GraphicData;
stateTemplate?: GraphicState;
}
/** /**
* *
*/ */
export abstract class JlGraphicTemplate<G extends JlGraphic> { export abstract class JlGraphicTemplate<G extends JlGraphic> {
readonly type: string; readonly type: string;
options: IGraphicTemplateOptions;
constructor(type: string) { constructor(type: string, options: IGraphicTemplateOptions) {
this.type = type; this.type = type;
this.options = options;
} }
get datas(): GraphicData {
if (this.options.dataTemplate) {
return this.options.dataTemplate.clone();
}
throw new Error(`type=${this.type}的图形模板没有数据模板`);
}
get states(): GraphicState {
if (this.options.stateTemplate) {
return this.options.stateTemplate.clone();
}
throw new Error(`type=${this.type}的图形模板没有状态模板`);
}
// getDataCreator<T extends CreateData>(): T {
// return this.options.dataCreator as T;
// }
// getStateCreator<T extends CreateState>(): T {
// return this.options.stateCreator as T;
// }
/** /**
* *
*/ */

View File

@ -60,9 +60,28 @@ declare namespace GlobalMixins {
_rotatable: boolean; // 是否可旋转 _rotatable: boolean; // 是否可旋转
rotatable: boolean; rotatable: boolean;
worldAngle: number; // 世界角度,(-180, 180] worldAngle: number; // 世界角度,(-180, 180]
/**
*
*/
getAllParentScaled(): PointType; getAllParentScaled(): PointType;
saveTransform(): GraphicTransform; // 保存变换 /**
loadTransform(transform: GraphicTransform): void; // 加载变换 *
*/
getPositionOnCanvas(): PointType;
/**
*
* @param p
*/
updatePositionByCanvasPosition(p: IPointData): void;
/**
*
*/
saveTransform(): GraphicTransform;
/**
*
* @param transform
*/
loadTransform(transform: GraphicTransform): void;
isChild(obj: DisplayObject): boolean; // 是否子元素 isChild(obj: DisplayObject): boolean; // 是否子元素
isParent(obj: DisplayObject): boolean; // 是否父元素 isParent(obj: DisplayObject): boolean; // 是否父元素
isAssistantAppend(): boolean; // 是否辅助附加图形 isAssistantAppend(): boolean; // 是否辅助附加图形
@ -83,14 +102,45 @@ declare namespace GlobalMixins {
isCanvas(): boolean; // 是否画布对象 isCanvas(): boolean; // 是否画布对象
getViewport(): Viewport; // 获取视口 getViewport(): Viewport; // 获取视口
getGraphicApp(): GraphicApp; // 获取图形app getGraphicApp(): GraphicApp; // 获取图形app
localToCanvasPoint(p: IPointData): PointType; // 图形本地坐标转为画布坐标 /**
localToCanvasPoints(...points: IPointData[]): PointType[]; // 批量转换 *
canvasToLocalPoint(p: IPointData): PointType; // 画布坐标转为图形本地坐标 * @param p
canvasToLocalPoints(...points: IPointData[]): PointType[]; // 批量转换 */
localToCanvasPoint(p: IPointData): PointType;
localToScreenPoint(p: IPointData): PointType; // 本地坐标转为屏幕坐标 /**
localToScreenPoints(...points: IPointData[]): PointType[]; // 批量 *
screenToLocalPoint(p: IPointData): PointType; // 屏幕坐标转为本地坐标 * @param points
*/
localToCanvasPoints(...points: IPointData[]): PointType[];
/**
*
* @param p
*/
canvasToLocalPoint(p: IPointData): PointType;
/**
*
* @param points
*/
canvasToLocalPoints(...points: IPointData[]): PointType[];
/**
*
* @param p
*/
localToScreenPoint(p: IPointData): PointType;
/**
*
* @param points
*/
localToScreenPoints(...points: IPointData[]): PointType[];
/**
*
* @param p
*/
screenToLocalPoint(p: IPointData): PointType;
/**
*
* @param points
*/
screenToLocalPoints(...points: IPointData[]): PointType[]; // 批量 screenToLocalPoints(...points: IPointData[]): PointType[]; // 批量
localBoundsToCanvasPoints(): PointType[]; // 本地包围框转为多边形点坐标 localBoundsToCanvasPoints(): PointType[]; // 本地包围框转为多边形点坐标

View File

@ -11,6 +11,8 @@ import {
calculateFootPointFromPointToLine, calculateFootPointFromPointToLine,
calculateIntersectionPointOfCircleAndPoint, calculateIntersectionPointOfCircleAndPoint,
distance, distance,
distance2,
isLineContainOther,
linePoint, linePoint,
} from '../utils'; } from '../utils';
import { VectorGraphic, VectorGraphicUtil } from './VectorGraphic'; import { VectorGraphic, VectorGraphicUtil } from './VectorGraphic';
@ -19,12 +21,25 @@ import { VectorGraphic, VectorGraphicUtil } from './VectorGraphic';
* *
*/ */
export interface AbsorbablePosition extends Container { export interface AbsorbablePosition extends Container {
/**
* ()
* @param other
*/
isOverlapping(other: AbsorbablePosition): boolean;
/**
*
* @param other
* @returns >0<0另一个吸附范围大=0两个吸附范围一样大
*/
compareTo(other: AbsorbablePosition): number;
/** /**
* *
* @param objs * @param objs
* @returns truefalse * @returns truefalse
*/ */
tryAbsorb(...objs: DisplayObject[]): boolean; tryAbsorb(...objs: DisplayObject[]): void;
} }
/** /**
@ -57,6 +72,11 @@ export default class AbsorbablePoint
absorbRange: number; absorbRange: number;
scaledListenerOn = false; scaledListenerOn = false;
/**
*
* @param point
* @param absorbRange
*/
constructor(point: IPointData, absorbRange = 10) { constructor(point: IPointData, absorbRange = 10) {
super(AbsorbablePointGraphic.geometry); super(AbsorbablePointGraphic.geometry);
this._point = new Point(point.x, point.y); this._point = new Point(point.x, point.y);
@ -65,19 +85,37 @@ export default class AbsorbablePoint
this.interactive; this.interactive;
VectorGraphicUtil.handle(this); VectorGraphicUtil.handle(this);
} }
tryAbsorb(...objs: DisplayObject[]): boolean { compareTo(other: AbsorbablePosition): number {
for (let i = 0; i < objs.length; i++) { if (other instanceof AbsorbablePoint) {
const obj = objs[i]; return this.absorbRange - other.absorbRange;
if ( }
distance(this._point.x, this._point.y, obj.position.x, obj.position.y) < throw new Error('非可吸附点');
this.absorbRange }
) { isOverlapping(other: AbsorbablePosition): boolean {
obj.position.copyFrom(this._point); if (other instanceof AbsorbablePoint) {
return true; return (
} this._point.equals(other._point) &&
this.absorbRange === other.absorbRange
);
} }
return false; return false;
} }
tryAbsorb(...objs: DisplayObject[]): void {
for (let i = 0; i < objs.length; i++) {
const obj = objs[i];
const canvasPosition = obj.getPositionOnCanvas();
if (
distance(
this._point.x,
this._point.y,
canvasPosition.x,
canvasPosition.y
) < this.absorbRange
) {
obj.updatePositionByCanvasPosition(this._point);
}
}
}
updateOnScaled() { updateOnScaled() {
const scaled = this.getAllParentScaled(); const scaled = this.getAllParentScaled();
const scale = Math.max(scaled.x, scaled.y); const scale = Math.max(scaled.x, scaled.y);
@ -94,6 +132,12 @@ export class AbsorbableLine extends Graphics implements AbsorbablePosition {
absorbRange: number; absorbRange: number;
_color = '#E77E0E'; _color = '#E77E0E';
/**
*
* @param p1
* @param p2
* @param absorbRange
*/
constructor(p1: IPointData, p2: IPointData, absorbRange = 20) { constructor(p1: IPointData, p2: IPointData, absorbRange = 20) {
super(); super();
this.p1 = new Point(p1.x, p1.y); this.p1 = new Point(p1.x, p1.y);
@ -101,6 +145,22 @@ export class AbsorbableLine extends Graphics implements AbsorbablePosition {
this.absorbRange = absorbRange; this.absorbRange = absorbRange;
this.redraw(); this.redraw();
} }
isOverlapping(other: AbsorbablePosition): boolean {
if (other instanceof AbsorbableLine) {
const contain = isLineContainOther(
{ p1: this.p1, p2: this.p2 },
{ p1: other.p1, p2: other.p2 }
);
return contain;
}
return false;
}
compareTo(other: AbsorbablePosition): number {
if (other instanceof AbsorbableLine) {
return distance2(this.p1, this.p2) - distance2(other.p1, other.p2);
}
throw new Error('非可吸附线');
}
redraw() { redraw() {
this.clear(); this.clear();
this.lineStyle(1, new Color(this._color)); this.lineStyle(1, new Color(this._color));
@ -108,17 +168,19 @@ export class AbsorbableLine extends Graphics implements AbsorbablePosition {
this.lineTo(this.p2.x, this.p2.y); this.lineTo(this.p2.x, this.p2.y);
} }
tryAbsorb(...objs: DisplayObject[]): boolean { tryAbsorb(...objs: DisplayObject[]): void {
for (let i = 0; i < objs.length; i++) { for (let i = 0; i < objs.length; i++) {
const obj = objs[i]; const obj = objs[i];
const p = obj.position.clone(); const canvasPosition = obj.getPositionOnCanvas();
if (linePoint(this.p1, this.p2, p, this.absorbRange, true)) { if (linePoint(this.p1, this.p2, canvasPosition, this.absorbRange, true)) {
const fp = calculateFootPointFromPointToLine(this.p1, this.p2, p); const fp = calculateFootPointFromPointToLine(
obj.position.copyFrom(fp); this.p1,
return true; this.p2,
canvasPosition
);
obj.updatePositionByCanvasPosition(fp);
} }
} }
return false;
} }
} }
@ -131,6 +193,12 @@ export class AbsorbableCircle extends Graphics implements AbsorbablePosition {
radius: number; radius: number;
_color = '#E77E0E'; _color = '#E77E0E';
/**
*
* @param p
* @param radius
* @param absorbRange
*/
constructor(p: IPointData, radius: number, absorbRange = 10) { constructor(p: IPointData, radius: number, absorbRange = 10) {
super(); super();
this.p0 = new Point(p.x, p.y); this.p0 = new Point(p.x, p.y);
@ -138,6 +206,18 @@ export class AbsorbableCircle extends Graphics implements AbsorbablePosition {
this.absorbRange = absorbRange; this.absorbRange = absorbRange;
this.redraw(); this.redraw();
} }
isOverlapping(other: AbsorbablePosition): boolean {
if (other instanceof AbsorbableCircle) {
return this.p0.equals(other.p0) && this.radius === other.radius;
}
return false;
}
compareTo(other: AbsorbablePosition): number {
if (other instanceof AbsorbableCircle) {
return this.absorbRange - other.absorbRange;
}
throw new Error('非可吸附圆');
}
redraw() { redraw() {
this.clear(); this.clear();
@ -145,14 +225,15 @@ export class AbsorbableCircle extends Graphics implements AbsorbablePosition {
this.drawCircle(this.p0.x, this.p0.y, this.radius); this.drawCircle(this.p0.x, this.p0.y, this.radius);
} }
tryAbsorb(...objs: DisplayObject[]): boolean { tryAbsorb(...objs: DisplayObject[]): void {
for (let i = 0; i < objs.length; i++) { for (let i = 0; i < objs.length; i++) {
const obj = objs[i]; const obj = objs[i];
const canvasPosition = obj.getPositionOnCanvas();
const len = distance( const len = distance(
this.p0.x, this.p0.x,
this.p0.y, this.p0.y,
obj.position.x, canvasPosition.x,
obj.position.y canvasPosition.y
); );
if ( if (
len > this.radius - this.absorbRange && len > this.radius - this.absorbRange &&
@ -162,12 +243,10 @@ export class AbsorbableCircle extends Graphics implements AbsorbablePosition {
const p = calculateIntersectionPointOfCircleAndPoint( const p = calculateIntersectionPointOfCircleAndPoint(
this.p0, this.p0,
this.radius, this.radius,
obj.position canvasPosition
); );
obj.position.copyFrom(p); obj.updatePositionByCanvasPosition(p);
return true;
} }
} }
return false;
} }
} }

View File

@ -9,9 +9,20 @@ import type { GraphicApp } from '../app/JlGraphicApp';
import { GraphicState } from '../core/JlGraphic'; import { GraphicState } from '../core/JlGraphic';
export interface StompCliOption { export interface StompCliOption {
wsUrl: string; // websocket url /**
token: string; // 认证token * websocket url地址
reconnectDelay?: number; // 重连延时默认3秒 */
wsUrl: string;
/**
* token
*/
token?: string;
/**
*
* @returns
*/
onAuthenticationFailed?: () => void;
reconnectDelay?: number; // 重连延时默认3秒,设置为0不重连.
heartbeatIncoming?: number; // 服务端过来的心跳间隔默认30秒 heartbeatIncoming?: number; // 服务端过来的心跳间隔默认30秒
heartbeatOutgoing?: number; // 到服务端的心跳间隔默认30秒 heartbeatOutgoing?: number; // 到服务端的心跳间隔默认30秒
} }
@ -25,28 +36,28 @@ const DefaultStompOption: StompCliOption = {
}; };
export class StompCli { export class StompCli {
private static enabled = false;
private static options: StompCliOption;
private static client: StompClient; private static client: StompClient;
private static options: StompCliOption;
private static appMsgBroker: AppWsMsgBroker[] = []; private static appMsgBroker: AppWsMsgBroker[] = [];
/**
* key-
*/
subscriptions: Map<string, AppStateSubscription> = new Map<
string,
AppStateSubscription
>();
private static connected = false; private static connected = false;
static new(options: StompCliOption) { static new(options: StompCliOption) {
if (StompCli.enabled) { if (StompCli.client) {
// 以及启用 // 已经创建
return; return;
// throw new Error('websocket 已连接若确实需要重新连接请先断开StompCli.close再重新StompCli.new')
} }
StompCli.enabled = true;
StompCli.options = Object.assign({}, DefaultStompOption, options); StompCli.options = Object.assign({}, DefaultStompOption, options);
StompCli.client = new StompClient({ StompCli.client = new StompClient({
brokerURL: StompCli.options.wsUrl, brokerURL: StompCli.options.wsUrl,
connectHeaders: { connectHeaders: {
Authorization: StompCli.options.token, Authorization: StompCli.options.token ? StompCli.options.token : '',
// Authorization: ''
}, },
// debug: (str) => {
// console.log(str)
// }
reconnectDelay: StompCli.options.reconnectDelay, reconnectDelay: StompCli.options.reconnectDelay,
heartbeatIncoming: StompCli.options.heartbeatIncoming, heartbeatIncoming: StompCli.options.heartbeatIncoming,
heartbeatOutgoing: StompCli.options.heartbeatOutgoing, heartbeatOutgoing: StompCli.options.heartbeatOutgoing,
@ -55,43 +66,47 @@ export class StompCli {
StompCli.client.onConnect = () => { StompCli.client.onConnect = () => {
// console.log('websocket连接(重连),重新订阅', StompCli.appMsgBroker.length) // console.log('websocket连接(重连),重新订阅', StompCli.appMsgBroker.length)
StompCli.connected = true; StompCli.connected = true;
StompCli.emitConnectStateChangeEvent();
StompCli.appMsgBroker.forEach((broker) => { StompCli.appMsgBroker.forEach((broker) => {
broker.resubscribe(); broker.resubscribe();
}); });
}; };
StompCli.client.onStompError = (frame: Frame) => { StompCli.client.onStompError = (frame: Frame) => {
console.error( const errMsg = frame.headers['message'];
'Stomp收到error消息,可能是认证失败(暂时没有判断具体错误类型,后需添加判断),关闭Stomp客户端', if (errMsg === '401') {
frame console.warn('认证失败,断开WebSocket连接');
); StompCli.close();
StompCli.close(); if (StompCli.options.onAuthenticationFailed) {
StompCli.options.onAuthenticationFailed();
}
} else {
console.error('收到Stomp错误消息', frame);
}
}; };
StompCli.client.onDisconnect = (frame: Frame) => { StompCli.client.onDisconnect = (frame: Frame) => {
console.log('Stomp 断开连接', frame); console.log('Stomp 断开连接', frame);
StompCli.connected = false; StompCli.connected = false;
// StompCli.appMsgBroker.forEach(broker => { StompCli.emitConnectStateChangeEvent();
// broker.close();
// });
}; };
StompCli.client.onWebSocketClose = (evt: CloseEvent) => { StompCli.client.onWebSocketClose = (evt: CloseEvent) => {
console.log('websocket 关闭', evt); console.log('websocket 关闭', evt);
StompCli.connected = false; StompCli.connected = false;
StompCli.emitConnectStateChangeEvent();
}; };
// websocket错误处理 // websocket错误处理
StompCli.client.onWebSocketError = (err: Event) => { StompCli.client.onWebSocketError = (err: Event) => {
console.log('websocket错误', err); console.log('websocket错误', err);
// StompCli.appMsgBroker.forEach(broker => {
// broker.unsbuscribeAll();
// });
}; };
StompCli.client.activate(); StompCli.client.activate();
} }
static isEnabled(): boolean { static emitConnectStateChangeEvent() {
return StompCli.enabled; StompCli.appMsgBroker.forEach((broker) => {
broker.app.emit('websocket-state-change', broker.app, StompCli.connected);
});
} }
static isConnected(): boolean { static isConnected(): boolean {
@ -129,7 +144,6 @@ export class StompCli {
* websocket连接 * websocket连接
*/ */
static close() { static close() {
StompCli.enabled = false;
StompCli.connected = false; StompCli.connected = false;
if (StompCli.client) { if (StompCli.client) {
StompCli.client.deactivate(); StompCli.client.deactivate();
@ -138,21 +152,32 @@ export class StompCli {
} }
// 状态订阅消息转换器 // 状态订阅消息转换器
export type MessageConverter = (message: Uint8Array) => GraphicState[]; export type GraphicStateMessageConvert = (
message: Uint8Array
) => GraphicState[];
// 订阅消息处理器
export type SubscriptionMessageHandle = (message: Uint8Array) => void;
// 图形app状态订阅 // 图形app状态订阅
export class AppStateSubscription { export interface AppStateSubscription {
/**
*
*/
destination: string; destination: string;
messageConverter: MessageConverter; /**
subscription?: StompSubscription; // 订阅成功对象,用于取消订阅 *
constructor( */
destination: string, messageConverter?: GraphicStateMessageConvert;
messageConverter: MessageConverter, /**
subscription?: StompSubscription *
) { */
this.destination = destination; messageHandle?: SubscriptionMessageHandle;
this.messageConverter = messageConverter; /**
this.subscription = subscription; *
} * 使
*/
subscription?: StompSubscription;
} }
/** /**
@ -175,8 +200,16 @@ export class AppWsMsgBroker {
sub.subscription = StompCli.trySubscribe( sub.subscription = StompCli.trySubscribe(
sub.destination, sub.destination,
(message: Message) => { (message: Message) => {
const graphicStates = sub.messageConverter(message.binaryBody); if (sub.messageConverter) {
this.app.handleGraphicStates(graphicStates); const graphicStates = sub.messageConverter(message.binaryBody);
this.app.handleGraphicStates(graphicStates);
} else if (sub.messageHandle) {
sub.messageHandle(message.binaryBody);
} else {
console.error(
`订阅destination:${sub.destination}没有消息处理器或图形状态消息转换器`
);
}
} }
); );
// console.log('代理订阅结果', sub.subscription) // console.log('代理订阅结果', sub.subscription)

View File

@ -194,18 +194,17 @@ export class CommonMouseTool extends AppInteractionPlugin {
const graphic = this.leftDownTarget.getGraphic(); const graphic = this.leftDownTarget.getGraphic();
if (graphic) { if (graphic) {
const app = this.app; const app = this.app;
// console.log(this.leftDownTarget.isGraphic());
// 图形选中 // 图形选中
if (!e.ctrlKey && !graphic.selected && graphic.selectable) { if (!e.ctrlKey && !graphic.selected && graphic.selectable) {
app.updateSelected(graphic); app.updateSelected(graphic);
graphic.childEdit = false; graphic.childEdit = false;
this.graphicSelect = true; this.graphicSelect = true;
} else if ( } else if (!e.ctrlKey && graphic.selected && graphic.childEdit) {
!e.ctrlKey && if (
graphic.selected && this.leftDownTarget.isGraphicChild() &&
graphic.childEdit && this.leftDownTarget.selectable
this.leftDownTarget.isGraphicChild() ) {
) {
if (this.leftDownTarget.selectable) {
graphic.setChildSelected(this.leftDownTarget); graphic.setChildSelected(this.leftDownTarget);
} else { } else {
graphic.exitChildEdit(); graphic.exitChildEdit();

View File

@ -2,7 +2,6 @@ import {
Color, Color,
Container, Container,
DisplayObject, DisplayObject,
FederatedMouseEvent,
Graphics, Graphics,
IDestroyOptions, IDestroyOptions,
IPointData, IPointData,
@ -10,12 +9,12 @@ import {
} from 'pixi.js'; } from 'pixi.js';
import { JlGraphic } from '../core'; import { JlGraphic } from '../core';
import { DraggablePoint } from '../graphic'; import { DraggablePoint } from '../graphic';
import { ContextMenu } from '../ui/ContextMenu';
import { MenuItemOptions, MenuOptions } from '../ui/Menu';
import { import {
calculateDistanceFromPointToLine,
calculateFootPointFromPointToLine, calculateFootPointFromPointToLine,
calculateLineSegmentingPoint,
calculateMirrorPoint, calculateMirrorPoint,
assertBezierPoints, convertToBezierParams,
distance2, distance2,
linePoint, linePoint,
pointPolygon, pointPolygon,
@ -33,13 +32,13 @@ export abstract class GraphicEditPlugin<
this.sortableChildren = true; this.sortableChildren = true;
this.graphic.on('transformstart', this.hideAll, this); this.graphic.on('transformstart', this.hideAll, this);
this.graphic.on('transformend', this.showAll, this); this.graphic.on('transformend', this.showAll, this);
this.graphic.on('repaint', this.showAll, this); this.graphic.on('repaint', this.updateEditedPointsPosition, this);
} }
destroy(options?: boolean | IDestroyOptions | undefined): void { destroy(options?: boolean | IDestroyOptions | undefined): void {
this.graphic.off('transformstart', this.hideAll, this); this.graphic.off('transformstart', this.hideAll, this);
this.graphic.off('transformend', this.showAll, this); this.graphic.off('transformend', this.showAll, this);
this.graphic.off('repaint', this.showAll, this); this.graphic.off('repaint', this.updateEditedPointsPosition, this);
super.destroy(options); super.destroy(options);
} }
@ -54,26 +53,6 @@ export abstract class GraphicEditPlugin<
} }
} }
export const addWaypointConfig: MenuItemOptions = {
name: '添加路径点',
};
export const removeWaypointConfig: MenuItemOptions = {
name: '移除路径点',
};
export const clearWaypointsConfig: MenuItemOptions = {
name: '清除所有路径点',
};
const menuOptions: MenuOptions = {
name: '图形编辑点菜单',
groups: [
{
items: [removeWaypointConfig, clearWaypointsConfig],
},
],
};
const EditPointContextMenu = ContextMenu.init(menuOptions);
export interface ILineGraphic extends JlGraphic { export interface ILineGraphic extends JlGraphic {
get linePoints(): IPointData[]; get linePoints(): IPointData[];
set linePoints(points: IPointData[]); set linePoints(points: IPointData[]);
@ -103,10 +82,50 @@ export abstract class LineEditPlugin extends GraphicEditPlugin<ILineGraphic> {
abstract initEditPoints(): void; abstract initEditPoints(): void;
} }
export function getWayLineIndex(
points: IPointData[],
p: IPointData
): { start: number; end: number } {
let start = 0;
let end = 0;
let minDistance = 0;
for (let i = 1; i < points.length; i++) {
const sp = points[i - 1];
const ep = points[i];
let distance = calculateDistanceFromPointToLine(sp, ep, p);
distance = Math.round(distance * 100) / 100;
if (i == 1) {
minDistance = distance;
}
if (distance == minDistance) {
const minX = Math.min(sp.x, ep.x);
const maxX = Math.max(sp.x, ep.x);
const minY = Math.min(sp.y, ep.y);
const maxY = Math.max(sp.y, ep.y);
const point = calculateFootPointFromPointToLine(sp, ep, p);
if (
point.x >= minX &&
point.x <= maxX &&
point.y >= minY &&
point.y <= maxY
) {
start = i - 1;
}
}
if (distance < minDistance) {
minDistance = distance;
start = i - 1;
}
}
end = start + 1;
return { start, end };
}
export function getWaypointRangeIndex( export function getWaypointRangeIndex(
points: IPointData[], points: IPointData[],
curve: boolean, curve: boolean,
p: IPointData p: IPointData,
lineWidth: number
): { start: number; end: number } { ): { start: number; end: number } {
let start = 0; let start = 0;
let end = 0; let end = 0;
@ -124,17 +143,25 @@ export function getWaypointRangeIndex(
} }
} else { } else {
// 贝塞尔曲线 // 贝塞尔曲线
assertBezierPoints(points); const bps = convertToBezierParams(points);
for (let i = 0; i < points.length - 3; i += 3) { for (let i = 0; i < bps.length; i++) {
const p1 = points[i]; const bp = bps[i];
const cp1 = points[i + 1]; if (pointPolygon(p, [bp.p1, bp.cp1, bp.cp2, bp.p2], lineWidth)) {
const cp2 = points[i + 2]; start = i * 3;
const p2 = points[i + 3]; end = start + 3;
if (pointPolygon(p, [p1, cp1, cp2, p2], 1)) {
start = i;
end = i + 3;
} }
} }
// assertBezierPoints(points);
// for (let i = 0; i < points.length - 3; i += 3) {
// const p1 = points[i];
// const cp1 = points[i + 1];
// const cp2 = points[i + 2];
// const p2 = points[i + 3];
// if (pointPolygon(p, [p1, cp1, cp2, p2], lineWidth)) {
// start = i;
// end = i + 3;
// }
// }
} }
return { start, end }; return { start, end };
} }
@ -170,19 +197,7 @@ export class PolylineEditPlugin extends LineEditPlugin {
for (let i = 0; i < cps.length; i++) { for (let i = 0; i < cps.length; i++) {
const p = cps[i]; const p = cps[i];
const dp = new DraggablePoint(p); const dp = new DraggablePoint(p);
dp.on('rightclick', (e: FederatedMouseEvent) => {
// dp.getGraphicApp().openContextMenu(EditPointContextMenu.DefaultMenu);
// 路径中的点
const app = dp.getGraphicApp();
app.registerMenu(EditPointContextMenu);
removeWaypointConfig.handler = () => {
removeLineWayPoint(this.graphic, i);
};
clearWaypointsConfig.handler = () => {
clearWayPoint(this.graphic, false);
};
EditPointContextMenu.open(e.global);
});
dp.on('transforming', () => { dp.on('transforming', () => {
const tlp = this.graphic.canvasToLocalPoint(dp.position); const tlp = this.graphic.canvasToLocalPoint(dp.position);
const cp = this.linePoints[i]; const cp = this.linePoints[i];
@ -251,6 +266,21 @@ export function addLineWayPoint(
graphic.linePoints = points; graphic.linePoints = points;
} }
export function addPolygonSegmentingPoint(
graphic: ILineGraphic,
start: number,
end: number,
knife = 2
) {
const linePoints = graphic.linePoints;
const points = linePoints.slice(0, start + 1);
points.push(
...calculateLineSegmentingPoint(linePoints[start], linePoints[end], knife)
);
points.push(...linePoints.slice(end));
graphic.linePoints = points;
}
function assertBezierWayPoint(i: number) { function assertBezierWayPoint(i: number) {
const c = i % 3; const c = i % 3;
if (c !== 0) { if (c !== 0) {
@ -265,6 +295,7 @@ export function addBezierWayPoint(
p: IPointData p: IPointData
) { ) {
if (start === end) { if (start === end) {
console.error('添加贝塞尔曲线路径点开始结束点相等', start);
throw new Error('开始结束点不能一致'); throw new Error('开始结束点不能一致');
} }
assertBezierWayPoint(start); assertBezierWayPoint(start);
@ -394,21 +425,6 @@ export class BezierCurveEditPlugin extends LineEditPlugin {
this.drawAuxiliaryLine(line, p, np); this.drawAuxiliaryLine(line, p, np);
this.auxiliaryLines.push(line); this.auxiliaryLines.push(line);
} }
dp.on('rightclick', (e: FederatedMouseEvent) => {
// dp.getGraphicApp().openContextMenu(EditPointContextMenu.DefaultMenu);
if (c === 0) {
// 路径中的点
const app = dp.getGraphicApp();
app.registerMenu(EditPointContextMenu);
removeWaypointConfig.handler = () => {
removeBezierWayPoint(this.graphic, i);
};
clearWaypointsConfig.handler = () => {
clearWayPoint(this.graphic, true);
};
EditPointContextMenu.open(e.global);
}
});
dp.on('transforming', (e: GraphicTransformEvent) => { dp.on('transforming', (e: GraphicTransformEvent) => {
const tlp = this.graphic.canvasToLocalPoint(dp.position); const tlp = this.graphic.canvasToLocalPoint(dp.position);
const cp = this.linePoints[i]; const cp = this.linePoints[i];

View File

@ -14,7 +14,7 @@ import {
} from '.'; } from '.';
import { GraphicApp } from '../app'; import { GraphicApp } from '../app';
import { JlGraphic } from '../core'; import { JlGraphic } from '../core';
import { AbsorbablePosition } from '../graphic'; import { AbsorbablePosition, VectorText } from '../graphic';
import { DraggablePoint } from '../graphic/DraggablePoint'; import { DraggablePoint } from '../graphic/DraggablePoint';
import { import {
angleToAxisx, angleToAxisx,
@ -171,11 +171,40 @@ export class GraphicTransformPlugin extends InteractionPluginBase {
this.app.canvas.addAssistantAppend(this.apContainer); this.app.canvas.addAssistantAppend(this.apContainer);
app.on('options-update', (options) => { app.on('options-update', (options) => {
if (options.absorbablePositions) { if (options.absorbablePositions) {
this.absorbablePositions = options.absorbablePositions; this.absorbablePositions = this.filterAbsorbablePositions(
options.absorbablePositions
);
} }
}); });
} }
/**
*
* @param positions
* @returns
*/
filterAbsorbablePositions(
positions: AbsorbablePosition[]
): AbsorbablePosition[] {
const aps: AbsorbablePosition[] = [];
for (let i = 0; i < positions.length; i++) {
const ap1 = positions[i];
let ap: AbsorbablePosition | null = ap1;
for (let j = positions.length - 1; j > i; j--) {
const ap2 = positions[j];
if (ap.isOverlapping(ap2) && ap.compareTo(ap2) <= 0) {
ap = null;
break;
}
}
if (ap != null) {
aps.push(ap);
}
}
// console.log(positions, aps);
return aps;
}
static new(app: GraphicApp) { static new(app: GraphicApp) {
return new GraphicTransformPlugin(app); return new GraphicTransformPlugin(app);
} }
@ -187,7 +216,7 @@ export class GraphicTransformPlugin extends InteractionPluginBase {
this.app.on('graphicselectedchange', this.onGraphicSelectedChange, this); this.app.on('graphicselectedchange', this.onGraphicSelectedChange, this);
this.app.on( this.app.on(
'graphicchildselectedchange', 'graphicchildselectedchange',
this.onGraphicChildSelectedChange, this.onGraphicSelectedChange,
this this
); );
} }
@ -198,7 +227,7 @@ export class GraphicTransformPlugin extends InteractionPluginBase {
this.app.off('graphicselectedchange', this.onGraphicSelectedChange, this); this.app.off('graphicselectedchange', this.onGraphicSelectedChange, this);
this.app.off( this.app.off(
'graphicchildselectedchange', 'graphicchildselectedchange',
this.onGraphicChildSelectedChange, this.onGraphicSelectedChange,
this this
); );
} }
@ -255,9 +284,10 @@ export class GraphicTransformPlugin extends InteractionPluginBase {
targets.forEach((target) => { targets.forEach((target) => {
if (target.shiftStartPoint) { if (target.shiftStartPoint) {
target.shiftLastPoint = target.position.clone(); target.shiftLastPoint = target.position.clone();
const { dx, dy } = e.toTargetShiftLen(target.parent);
target.position.set( target.position.set(
target.shiftStartPoint.x + e.dsx, target.shiftStartPoint.x + dx,
target.shiftStartPoint.y + e.dsy target.shiftStartPoint.y + dy
); );
} }
}); });
@ -265,9 +295,7 @@ export class GraphicTransformPlugin extends InteractionPluginBase {
if (this.absorbablePositions) { if (this.absorbablePositions) {
for (let i = 0; i < this.absorbablePositions.length; i++) { for (let i = 0; i < this.absorbablePositions.length; i++) {
const ap = this.absorbablePositions[i]; const ap = this.absorbablePositions[i];
if (ap.tryAbsorb(...targets)) { ap.tryAbsorb(...targets);
break;
}
} }
} }
// 事件发布 // 事件发布
@ -335,7 +363,7 @@ export class GraphicTransformPlugin extends InteractionPluginBase {
br.visible = false; br.visible = false;
} }
} }
if (g.scalable) { if (g.scalable || g.rotatable) {
// 缩放点 // 缩放点
let sp = g.getAssistantAppend<TransformPoints>(TransformPoints.Name); let sp = g.getAssistantAppend<TransformPoints>(TransformPoints.Name);
if (!sp) { if (!sp) {
@ -350,19 +378,19 @@ export class GraphicTransformPlugin extends InteractionPluginBase {
} }
} }
onGraphicChildSelectedChange(child: DisplayObject, selected: boolean) { // onGraphicChildSelectedChange(child: DisplayObject, selected: boolean) {
let br = child.getAssistantAppend<BoundsGraphic>(BoundsGraphic.Name); // let br = child.getAssistantAppend<BoundsGraphic>(BoundsGraphic.Name);
if (!br) { // if (!br) {
// 绘制辅助包围框 // // 绘制辅助包围框
br = new BoundsGraphic(child); // br = new BoundsGraphic(child);
} // }
if (selected) { // if (selected) {
br.redraw(); // br.redraw();
br.visible = true; // br.visible = true;
} else { // } else {
br.visible = false; // br.visible = false;
} // }
} // }
} }
/** /**
@ -419,6 +447,10 @@ export class TransformPoints extends Container {
* *
*/ */
startAngle = 0; startAngle = 0;
/**
*
*/
angleAssistantText: VectorText;
/** /**
* *
*/ */
@ -433,6 +465,10 @@ export class TransformPoints extends Container {
this.obj = obj; this.obj = obj;
this.name = TransformPoints.Name; this.name = TransformPoints.Name;
this.angleAssistantText = new VectorText('');
this.angleAssistantText.setVectorFontSize(16);
this.angleAssistantText.anchor.set(0.5);
// 创建缩放拖拽点 // 创建缩放拖拽点
this.ltScalePoint = new DraggablePoint(new Point()); this.ltScalePoint = new DraggablePoint(new Point());
this.ltScalePoint.name = TransformPoints.LeftTopName; this.ltScalePoint.name = TransformPoints.LeftTopName;
@ -460,6 +496,19 @@ export class TransformPoints extends Container {
this.addChild(this.lScalePoint); this.addChild(this.lScalePoint);
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) {
recursiveChildren(this.obj as Container, (child) => {
child.on('transformstart', this.onObjTransformStart, this);
child.on('transformend', this.onObjTransformEnd, this);
});
}
const pg = this.obj.getGraphic();
if (pg != null) {
pg.on('transformstart', this.onObjTransformStart, this);
pg.on('transformend', this.onObjTransformEnd, this);
}
this.obj.on('repaint', this.onGraphicRepaint, this); this.obj.on('repaint', this.onGraphicRepaint, this);
this.children.forEach((dp) => { this.children.forEach((dp) => {
dp.on('transformstart', this.onScaleDragStart, this); dp.on('transformstart', this.onScaleDragStart, this);
@ -520,6 +569,19 @@ export class TransformPoints extends Container {
); );
this.obj.emit('transformstart', GraphicTransformEvent.rotate(this.obj)); this.obj.emit('transformstart', GraphicTransformEvent.rotate(this.obj));
// app.emit('transformstart', app.selectedGraphics); // app.emit('transformstart', app.selectedGraphics);
this.obj.getCanvas().addAssistantAppends(this.angleAssistantText);
this.updateAngleAssistantText(de);
}
updateAngleAssistantText(de: GraphicTransformEvent) {
this.angleAssistantText.text = this.obj.angle + '°';
let cursorPoint = de.data?.startPosition;
if (de.data?.currentPosition) {
cursorPoint = de.data?.currentPosition;
}
if (cursorPoint) {
this.angleAssistantText.position.x = cursorPoint.x;
this.angleAssistantText.position.y = cursorPoint.y - 10;
}
} }
/** /**
* *
@ -529,11 +591,13 @@ export class TransformPoints extends Container {
// 旋转角度计算逻辑取锚点y负方向一点作为旋转点求旋转点和锚点所形成的直线与x轴角度此角度+90°即为最终旋转角度再将旋转角度限制到(-180,180]之间 // 旋转角度计算逻辑取锚点y负方向一点作为旋转点求旋转点和锚点所形成的直线与x轴角度此角度+90°即为最终旋转角度再将旋转角度限制到(-180,180]之间
let angle = angleToAxisx(this.rotatePivot, de.target.position); let angle = angleToAxisx(this.rotatePivot, de.target.position);
angle = Math.floor(angle / this.angleStep) * this.angleStep; angle = Math.floor(angle / this.angleStep) * this.angleStep;
angle = (angle + 90) % 360; const parentAngle = this.obj.parent.worldAngle;
angle = (angle + 90 - parentAngle) % 360;
if (angle > 180) { if (angle > 180) {
angle = angle - 360; angle = angle - 360;
} }
this.obj.angle = angle; this.obj.angle = angle;
this.updateAngleAssistantText(de);
// this.obj.emit('rotatemove', this.obj); // this.obj.emit('rotatemove', this.obj);
} }
/** /**
@ -542,6 +606,7 @@ export class TransformPoints extends Container {
*/ */
onRotateEnd() { onRotateEnd() {
this.showAll(); this.showAll();
this.obj.getCanvas().removeAssistantAppends(this.angleAssistantText);
this.rotateAngleStepKeyListeners.forEach((listener) => this.rotateAngleStepKeyListeners.forEach((listener) =>
this.obj.getGraphicApp().removeKeyboardListener(listener) this.obj.getGraphicApp().removeKeyboardListener(listener)
); );
@ -789,6 +854,17 @@ export class BoundsGraphic extends Graphics {
this.visible = false; this.visible = false;
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) {
recursiveChildren(this.obj as Container, (child) => {
child.on('transformstart', this.onObjTransformStart, this);
child.on('transformend', this.onObjTransformEnd, this);
});
}
const pg = this.obj.getGraphic();
if (pg != null) {
pg.on('transformstart', this.onObjTransformStart, this);
pg.on('transformend', this.onObjTransformEnd, this);
}
this.obj.on('repaint', this.onGraphicRepaint, this); this.obj.on('repaint', this.onGraphicRepaint, this);
graphic.addAssistantAppend(this); graphic.addAssistantAppend(this);
} }

View File

@ -99,7 +99,7 @@ export class AppDragEvent {
type: 'start' | 'move' | 'end'; type: 'start' | 'move' | 'end';
target: DisplayObject; target: DisplayObject;
original: FederatedPointerEvent; original: FederatedPointerEvent;
start: Point; start: Point; // 画布坐标
constructor( constructor(
app: GraphicApp, app: GraphicApp,
type: 'start' | 'move' | 'end', type: 'start' | 'move' | 'end',
@ -146,6 +146,9 @@ export class AppDragEvent {
return this.original.pointerType === 'touch'; return this.original.pointerType === 'touch';
} }
/**
* ()
*/
public get end(): Point { public get end(): Point {
return this.app.toCanvasCoordinates(this.original.global); return this.app.toCanvasCoordinates(this.original.global);
} }
@ -167,6 +170,15 @@ export class AppDragEvent {
public get dsy(): number { public get dsy(): number {
return this.end.y - this.start.y; return this.end.y - this.start.y;
} }
/**
*
*/
toTargetShiftLen(target: DisplayObject): { dx: number; dy: number } {
const sl = target.canvasToLocalPoint(this.start);
const el = target.canvasToLocalPoint(this.end);
return { dx: el.x - sl.x, dy: el.y - sl.y };
}
} }
/** /**

View File

@ -67,15 +67,16 @@ export function recursiveFindChild(
container: Container, container: Container,
finder: (child: DisplayObject) => boolean finder: (child: DisplayObject) => boolean
): DisplayObject | null { ): DisplayObject | null {
let result = null;
for (let i = 0; i < container.children.length; i++) { for (let i = 0; i < container.children.length; i++) {
const child = container.children[i]; const child = container.children[i];
if (finder(child)) { if (finder(child)) {
return child; return child;
} else if (child.children) { } else if (child.children) {
return recursiveFindChild(child as Container, finder); result = recursiveFindChild(child as Container, finder);
} }
} }
return null; return result;
} }
export interface BezierParam { export interface BezierParam {
@ -287,6 +288,31 @@ export function calculateLineMidpoint(p1: IPointData, p2: IPointData): Point {
return new Point(x, y); return new Point(x, y);
} }
/**
* 线--线
* @param p1
* @param p2
* @param knife
* @returns
*/
export function calculateLineSegmentingPoint(
p1: IPointData,
p2: IPointData,
knife: number
): IPointData[] {
const segmentingPoints: IPointData[] = [];
const x = p1.x < p2.x ? p1.x : p2.x;
const y = p1.y < p2.y ? p1.y : p2.y;
const w = Math.abs(p1.x - p2.x);
const h = Math.abs(p1.y - p2.y);
for (let i = 0; i < knife - 1; i++) {
const pointX = x + (w * (i + 1)) / knife;
const pointy = y + (h * (i + 1)) / knife;
segmentingPoints.push(new Point(pointX, pointy));
}
return segmentingPoints;
}
/** /**
* 线 * 线
* @param p1 * @param p1
@ -517,3 +543,153 @@ export function angleOfIncludedAngle(
} }
return angle; return angle;
} }
/**
* 线
* @param point1
* @param point2
* @returns
*/
export function getNormalVector(
point1: IPointData,
point2: IPointData
): number[] {
const x1 = point1.x,
y1 = point1.y;
const x2 = point2.x,
y2 = point2.y;
const length = Math.sqrt((y2 - y1) ** 2 + (x2 - x1) ** 2);
return [(y2 - y1) / length, (x1 - x2) / length];
}
/**
*
* @param point
* @param normal
* @param length
* @returns
*/
export function movePointAlongNormal(
point: IPointData,
normal: number[],
length: number
): Point {
const newPoint = new Point(
point.x + length * normal[0],
point.y + length * normal[1]
);
return newPoint;
}
/**
* 线(线 )
* @param line1
* @param line2
* @returns
*/
export function getIntersectionPoint(line1: number[], line2: number[]) {
const a1 = line1[0],
b1 = line1[1];
const a2 = line1[2],
b2 = line1[3];
const a3 = line2[0],
b3 = line2[1];
const a4 = line2[2],
b4 = line2[3];
const denominator = (a3 - a4) * (b1 - b2) - (a1 - a2) * (b3 - b4);
if (denominator === 0) {
return new Point(a1, b1);
}
const x =
((a3 - a4) * (a2 * b1 - a1 * b2) - (a1 - a2) * (a4 * b3 - a3 * b4)) /
denominator;
const y =
((b3 - b4) * (b2 * a1 - b1 * a2) - (b1 - b2) * (b4 * a3 - b3 * a4)) /
-denominator;
return new Point(x, y);
}
/**
* 线
* @param p1
* @param p2
* @param pa
* @param pb
* @returns
*/
export function isParallelLines(
p1: IPointData,
p2: IPointData,
pa: IPointData,
pb: IPointData
): boolean {
const vle1 = Vector2.direction(Vector2.from(p1), Vector2.from(p2));
const vle2 = Vector2.direction(Vector2.from(pa), Vector2.from(pb));
if (vle2.equals(vle1)) {
return true;
}
return vle1.equals(Vector2.direction(Vector2.from(pb), Vector2.from(pa)));
}
/**
* 线
* @param p1
* @param p2
* @param p
* @returns
*/
export function isPointOnLine(
p1: IPointData,
p2: IPointData,
p: IPointData
): boolean {
const vp1 = Vector2.from(p1);
const vp2 = Vector2.from(p2);
const vp = Vector2.from(p);
if (vp1.equals(vp) || vp2.equals(vp)) {
return true;
}
const vle = Vector2.direction(vp1, Vector2.from(p2));
const vpe = Vector2.direction(vp1, vp);
if (vle.equals(vpe)) {
return (
Vector2.difference(vp1, vp2).squaredLength() >=
Vector2.difference(vp1, vp).squaredLength()
);
}
return false;
}
/**
* 线
* @param line1
* @param line2
* @returns
*/
export function isLineContainOther(
line1: { p1: IPointData; p2: IPointData },
line2: { p1: IPointData; p2: IPointData }
): boolean {
return (
(isPointOnLine(line1.p1, line1.p2, line2.p1) &&
isPointOnLine(line1.p1, line1.p2, line2.p2)) ||
(isPointOnLine(line2.p1, line2.p2, line1.p1) &&
isPointOnLine(line2.p1, line2.p2, line1.p2))
);
}
/** 均分线段, 返回各线段端点 */
export function splitLineEvenly(
p1: IPointData,
p2: IPointData,
count: number
): IPointData[][] {
const [stepX, stepY] = [(p2.x - p1.x) / count, (p2.y - p1.y) / count];
return Array(count)
.fill(1)
.map((_, i) => {
return [
{ x: p1.x + stepX * i, y: p1.y + stepY * i },
{ x: p1.x + stepX * (i + 1), y: p1.y + stepY * (i + 1) },
];
});
}

View File

@ -11,6 +11,19 @@
Title Title
</q-toolbar-title> </q-toolbar-title>
<div class="q-gutter-sm row items-center no-wrap">
<q-btn
round
dense
flat
color="white"
:icon="$q.fullscreen.isActive ? 'fullscreen_exit' : 'fullscreen'"
@click="toggleFullscreen"
v-if="$q.screen.gt.sm"
>
</q-btn>
</div>
<q-btn dense flat round icon="menu" @click="toggleRightDrawer" /> <q-btn dense flat round icon="menu" @click="toggleRightDrawer" />
</q-toolbar> </q-toolbar>
<q-resize-observer @resize="onHeaderResize" /> <q-resize-observer @resize="onHeaderResize" />
@ -100,19 +113,27 @@
<draw-properties></draw-properties> <draw-properties></draw-properties>
</q-drawer> </q-drawer>
<q-page-container> <q-page-container id="app-page">
<div id="draw-app-container"></div> <div id="draw-app-container"></div>
</q-page-container> </q-page-container>
</q-layout> </q-layout>
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { Base64 } from 'js-base64';
import { useQuasar } from 'quasar';
import DrawProperties from 'src/components/draw-app/DrawProperties.vue'; import DrawProperties from 'src/components/draw-app/DrawProperties.vue';
import { getDrawApp, loadDrawDatas } from 'src/examples/app'; import { getDrawApp, loadDrawDatas } from 'src/examples/app';
import { getJwtToken } from 'src/examples/app/configs/TokenManage';
import { getWebsocketUrl } from 'src/examples/app/configs/UrlManage';
import { AppStateSubscription } from 'src/jlgraphic';
import { useDrawStore } from 'src/stores/draw-store'; import { useDrawStore } from 'src/stores/draw-store';
import { onMounted, onUnmounted, ref } from 'vue'; import { Ref, onMounted, onUnmounted, ref } from 'vue';
import { useRouter } from 'vue-router';
const $q = useQuasar();
const drawStore = useDrawStore(); const drawStore = useDrawStore();
const router = useRouter();
const leftDrawerOpen = ref(false); const leftDrawerOpen = ref(false);
const rightDrawerOpen = ref(false); const rightDrawerOpen = ref(false);
@ -127,13 +148,46 @@ function toggleRightDrawer() {
const link = ref('outbox'); const link = ref('outbox');
function toggleFullscreen(e: unknown): void {
// const dom = document.getElementById('app-page');
// if (dom) {
// $q.fullscreen.toggle(dom);
// }
$q.fullscreen.toggle();
}
onMounted(() => { onMounted(() => {
console.log('绘制应用layout mounted'); console.log('绘制应用layout mounted');
const basic = Base64.decode('Zm9vOmJhcg==');
console.log(basic);
const dom = document.getElementById('draw-app-container'); const dom = document.getElementById('draw-app-container');
if (dom) { if (dom) {
const drawApp = drawStore.initDrawApp(dom); const drawApp = drawStore.initDrawApp(dom);
drawApp.on('websocket-state-change', (app, connected) => {
console.log('应用websocket状态变更', connected);
});
loadDrawDatas(drawApp); loadDrawDatas(drawApp);
onResize(); onResize();
drawApp.enableWsStomp({
wsUrl: getWebsocketUrl(),
token: getJwtToken() as string,
onAuthenticationFailed: () => {
$q.dialog({
title: '认证失败',
message: '认证失败或登录超时,请重新登录',
persistent: true,
}).onOk(() => {
router.push({ name: 'login' });
});
},
});
drawApp.subscribe({
destination: '/queue/line/3/device',
messageHandle: (msg) => {
console.log('设备消息处理', msg);
},
});
} }
}); });

93
src/pages/UserLogin.vue Normal file
View File

@ -0,0 +1,93 @@
<template>
<q-layout>
<q-page-container>
<q-page class="flex bg-image flex-center">
<q-card
v-bind:style="$q.screen.lt.sm ? { width: '80%' } : { width: '30%' }"
style="min-width: 350px"
>
<q-card-section>
<q-avatar size="100px" class="absolute-center shadow-10">
<img src="icons/favicon-96x96.png" />
</q-avatar>
</q-card-section>
<q-card-section>
<div class="text-center q-pt-lg">
<div class="col text-h6 ellipsis">登录</div>
</div>
</q-card-section>
<q-card-section>
<q-form @submit="doLogin" class="q-gutter-md">
<q-input
filled
v-model="loginInfo.account"
label="账号"
lazy-rules
type="tel"
mask="###########"
:rules="[(val) => val.length == 11 || '请输入正确手机格式!']"
/>
<q-input
type="password"
filled
v-model="loginInfo.password"
label="密码"
lazy-rules
/>
<div class="text-right">
<q-btn flat label="注册" to="/register" color="primary" />
</div>
<div class="text-center">
<q-btn label="登录" type="submit" color="primary" />
</div>
</q-form>
</q-card-section>
</q-card>
</q-page>
</q-page-container>
</q-layout>
</template>
<script setup lang="ts">
import { useQuasar } from 'quasar';
import { ApiError } from 'src/boot/axios';
import { login } from 'src/examples/app/api/UserApi';
import {
clearJwtToken,
saveJwtToken,
} from 'src/examples/app/configs/TokenManage';
import { reactive } from 'vue';
import { useRouter } from 'vue-router';
const $q = useQuasar();
const router = useRouter();
const loginInfo = reactive({
account: '',
password: '',
});
async function doLogin() {
try {
clearJwtToken();
const token = await login(loginInfo);
saveJwtToken(token);
router.push({ name: 'home' });
} catch (err) {
const apiErr = err as ApiError;
$q.notify({
type: 'negative',
message: apiErr.title,
});
}
}
</script>
<style>
.bg-image {
background-image: linear-gradient(135deg, #5481fd 0%, #0e02b1 80%);
}
</style>

View File

@ -0,0 +1,97 @@
<template>
<q-layout>
<q-page-container>
<q-page class="flex bg-image flex-center">
<q-card
v-bind:style="$q.screen.lt.sm ? { width: '80%' } : { width: '30%' }"
style="min-width: 350px"
>
<q-card-section>
<q-avatar size="100px" class="absolute-center shadow-10">
<img src="icons/favicon-96x96.png" />
</q-avatar>
</q-card-section>
<q-card-section>
<div class="text-center q-pt-lg">
<div class="col text-h6 ellipsis">注册</div>
</div>
</q-card-section>
<q-card-section>
<q-form @submit="doRegister" class="q-gutter-md">
<q-input
filled
v-model="loginInfo.name"
label="姓名"
lazy-rules
/>
<q-input
filled
v-model="loginInfo.mobile"
label="手机号"
lazy-rules
type="tel"
mask="###########"
:rules="[(val) => val.length == 11 || '请输入正确手机格式!']"
/>
<q-input
type="password"
filled
v-model="loginInfo.password"
label="密码"
lazy-rules
/>
<div class="text-center q-gutter-md">
<q-btn label="注册" type="submit" color="primary" />
<q-btn label="取消" to="/login" color="secondary" />
</div>
</q-form>
</q-card-section>
</q-card>
</q-page>
</q-page-container>
</q-layout>
</template>
<script setup lang="ts">
import { useQuasar } from 'quasar';
import { register } from 'src/examples/app/api/UserApi';
import { ApiError } from 'src/boot/axios';
import { reactive } from 'vue';
import { useRouter } from 'vue-router';
const $q = useQuasar();
const router = useRouter();
const loginInfo = reactive({
name: '',
mobile: '',
password: '',
});
async function doRegister() {
console.log(loginInfo);
try {
await register(loginInfo);
$q.notify({
type: 'positive',
message: '注册成功',
});
router.push({ name: 'login' });
} catch (err) {
const apiErr = err as ApiError;
$q.notify({
type: 'negative',
message: apiErr.title,
});
}
}
</script>
<style>
.bg-image {
background-image: linear-gradient(135deg, #5481fd 0%, #0e02b1 80%);
}
</style>

View File

@ -3,9 +3,22 @@ import { RouteRecordRaw } from 'vue-router';
const routes: RouteRecordRaw[] = [ const routes: RouteRecordRaw[] = [
{ {
path: '/', path: '/',
name: 'home',
component: () => import('layouts/DrawLayout.vue'), component: () => import('layouts/DrawLayout.vue'),
}, },
{
path: '/login',
name: 'login',
component: () => import('pages/UserLogin.vue'),
},
{
path: '/register',
name: 'register',
component: () => import('pages/UserRegister.vue'),
},
// Always leave this as last one, // Always leave this as last one,
// but you can also remove it // but you can also remove it
{ {

View File

@ -183,6 +183,11 @@
resolved "https://registry.npmmirror.com/@pixi/filter-noise/-/filter-noise-7.2.4.tgz#0586a00381ec0e63f6c00d49cd58b781eaf07f37" resolved "https://registry.npmmirror.com/@pixi/filter-noise/-/filter-noise-7.2.4.tgz#0586a00381ec0e63f6c00d49cd58b781eaf07f37"
integrity sha512-QAU9Ybj2ZQrWM9ZEjTTC0iLnQcuyNoZNRinxSbg1G0yacpmsSb9wvV5ltIZ66+hfY+90+u2Nudt/v9g6pvOdGg== integrity sha512-QAU9Ybj2ZQrWM9ZEjTTC0iLnQcuyNoZNRinxSbg1G0yacpmsSb9wvV5ltIZ66+hfY+90+u2Nudt/v9g6pvOdGg==
"@pixi/graphics-extras@^7.2.4":
version "7.2.4"
resolved "https://registry.npmmirror.com/@pixi/graphics-extras/-/graphics-extras-7.2.4.tgz#72ac967992f239d3671d6e680ac891471619fe07"
integrity sha512-0yT91yqF3KLiZI/iLRcfcYlTVpkVyWsfGtWEIorZs0eX+/zYx7um7EJ2h7tFORI/1FxA2maR4td5vpgCwOLJAQ==
"@pixi/graphics@7.2.4": "@pixi/graphics@7.2.4":
version "7.2.4" version "7.2.4"
resolved "https://registry.npmmirror.com/@pixi/graphics/-/graphics-7.2.4.tgz#8500b604c36184736926393cb0ca9b9de9afef86" resolved "https://registry.npmmirror.com/@pixi/graphics/-/graphics-7.2.4.tgz#8500b604c36184736926393cb0ca9b9de9afef86"
@ -729,6 +734,11 @@ ajv@^8.0.1:
require-from-string "^2.0.2" require-from-string "^2.0.2"
uri-js "^4.2.2" uri-js "^4.2.2"
alova@^2.7.1:
version "2.7.1"
resolved "https://registry.npmmirror.com/alova/-/alova-2.7.1.tgz#b91b7c80137a44b57792badda9e7ab09acf93f55"
integrity sha512-/+lbPt+u/c4rBx4fq83795Xuh8Ohj5ZB4tJ7pxL2BxLgRlLNCT9/I1yGrgJEz/jXTqIZHMI2EeBAIb/B0u76tw==
ansi-escapes@^4.2.1: ansi-escapes@^4.2.1:
version "4.3.2" version "4.3.2"
resolved "https://registry.npmmirror.com/ansi-escapes/-/ansi-escapes-4.3.2.tgz#6b2291d1db7d98b6521d5f1efa42d0f3a9feb65e" resolved "https://registry.npmmirror.com/ansi-escapes/-/ansi-escapes-4.3.2.tgz#6b2291d1db7d98b6521d5f1efa42d0f3a9feb65e"
@ -810,6 +820,11 @@ async@^3.2.3:
resolved "https://registry.npmmirror.com/async/-/async-3.2.4.tgz#2d22e00f8cddeb5fde5dd33522b56d1cf569a81c" resolved "https://registry.npmmirror.com/async/-/async-3.2.4.tgz#2d22e00f8cddeb5fde5dd33522b56d1cf569a81c"
integrity sha512-iAB+JbDEGXhyIUavoDl9WP/Jj106Kz9DEn1DPgYw5ruDn0e3Wgi3sKFm55sASdGBNOQB8F59d9qQ7deqrHA8wQ== integrity sha512-iAB+JbDEGXhyIUavoDl9WP/Jj106Kz9DEn1DPgYw5ruDn0e3Wgi3sKFm55sASdGBNOQB8F59d9qQ7deqrHA8wQ==
asynckit@^0.4.0:
version "0.4.0"
resolved "https://registry.npmmirror.com/asynckit/-/asynckit-0.4.0.tgz#c79ed97f7f34cb8f2ba1bc9790bcc366474b4b79"
integrity sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==
autoprefixer@^10.4.2: autoprefixer@^10.4.2:
version "10.4.14" version "10.4.14"
resolved "https://registry.npmmirror.com/autoprefixer/-/autoprefixer-10.4.14.tgz#e28d49902f8e759dd25b153264e862df2705f79d" resolved "https://registry.npmmirror.com/autoprefixer/-/autoprefixer-10.4.14.tgz#e28d49902f8e759dd25b153264e862df2705f79d"
@ -822,6 +837,15 @@ autoprefixer@^10.4.2:
picocolors "^1.0.0" picocolors "^1.0.0"
postcss-value-parser "^4.2.0" postcss-value-parser "^4.2.0"
axios@^1.4.0:
version "1.4.0"
resolved "https://registry.npmmirror.com/axios/-/axios-1.4.0.tgz#38a7bf1224cd308de271146038b551d725f0be1f"
integrity sha512-S4XCWMEmzvo64T9GfvQDOXgYRDJ/wsSZc7Jvdgx5u1sd0JwsuPLqb3SYmusag+edF6ziyMensPVqLTSc1PiSEA==
dependencies:
follow-redirects "^1.15.0"
form-data "^4.0.0"
proxy-from-env "^1.1.0"
balanced-match@^1.0.0: balanced-match@^1.0.0:
version "1.0.2" version "1.0.2"
resolved "https://registry.npmmirror.com/balanced-match/-/balanced-match-1.0.2.tgz#e83e3a7e3f300b34cb9d87f615fa0cbf357690ee" resolved "https://registry.npmmirror.com/balanced-match/-/balanced-match-1.0.2.tgz#e83e3a7e3f300b34cb9d87f615fa0cbf357690ee"
@ -1047,6 +1071,13 @@ colord@^2.9.3:
resolved "https://registry.npmmirror.com/colord/-/colord-2.9.3.tgz#4f8ce919de456f1d5c1c368c307fe20f3e59fb43" resolved "https://registry.npmmirror.com/colord/-/colord-2.9.3.tgz#4f8ce919de456f1d5c1c368c307fe20f3e59fb43"
integrity sha512-jeC1axXpnb0/2nn/Y1LPuLdgXBLH7aDcHu4KEKfqw3CUhX7ZpfBSlPKyqXE6btIgEzfWtrX3/tyBCaCvXvMkOw== integrity sha512-jeC1axXpnb0/2nn/Y1LPuLdgXBLH7aDcHu4KEKfqw3CUhX7ZpfBSlPKyqXE6btIgEzfWtrX3/tyBCaCvXvMkOw==
combined-stream@^1.0.8:
version "1.0.8"
resolved "https://registry.npmmirror.com/combined-stream/-/combined-stream-1.0.8.tgz#c3d45a8b34fd730631a110a8a2520682b31d5a7f"
integrity sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==
dependencies:
delayed-stream "~1.0.0"
commander@^2.19.0: commander@^2.19.0:
version "2.20.3" version "2.20.3"
resolved "https://registry.npmmirror.com/commander/-/commander-2.20.3.tgz#fd485e84c03eb4881c20722ba48035e8531aeb33" resolved "https://registry.npmmirror.com/commander/-/commander-2.20.3.tgz#fd485e84c03eb4881c20722ba48035e8531aeb33"
@ -1177,6 +1208,11 @@ define-lazy-prop@^2.0.0:
resolved "https://registry.npmmirror.com/define-lazy-prop/-/define-lazy-prop-2.0.0.tgz#3f7ae421129bcaaac9bc74905c98a0009ec9ee7f" resolved "https://registry.npmmirror.com/define-lazy-prop/-/define-lazy-prop-2.0.0.tgz#3f7ae421129bcaaac9bc74905c98a0009ec9ee7f"
integrity sha512-Ds09qNh8yw3khSjiJjiUInaGX9xlqZDY7JVryGxdxV7NPeuqQfplOpQ66yJFZut3jLa5zOwkXw1g9EI2uKh4Og== integrity sha512-Ds09qNh8yw3khSjiJjiUInaGX9xlqZDY7JVryGxdxV7NPeuqQfplOpQ66yJFZut3jLa5zOwkXw1g9EI2uKh4Og==
delayed-stream@~1.0.0:
version "1.0.0"
resolved "https://registry.npmmirror.com/delayed-stream/-/delayed-stream-1.0.0.tgz#df3ae199acadfb7d440aaae0b29e2272b24ec619"
integrity sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==
depd@2.0.0: depd@2.0.0:
version "2.0.0" version "2.0.0"
resolved "https://registry.npmmirror.com/depd/-/depd-2.0.0.tgz#b696163cc757560d09cf22cc8fad1571b79e76df" resolved "https://registry.npmmirror.com/depd/-/depd-2.0.0.tgz#b696163cc757560d09cf22cc8fad1571b79e76df"
@ -1806,6 +1842,20 @@ flatted@^3.1.0:
resolved "https://registry.npmmirror.com/flatted/-/flatted-3.2.7.tgz#609f39207cb614b89d0765b477cb2d437fbf9787" resolved "https://registry.npmmirror.com/flatted/-/flatted-3.2.7.tgz#609f39207cb614b89d0765b477cb2d437fbf9787"
integrity sha512-5nqDSxl8nn5BSNxyR3n4I6eDmbolI6WT+QqR547RwxQapgjQBmtktdP+HTBb/a/zLsbzERTONyUB5pefh5TtjQ== integrity sha512-5nqDSxl8nn5BSNxyR3n4I6eDmbolI6WT+QqR547RwxQapgjQBmtktdP+HTBb/a/zLsbzERTONyUB5pefh5TtjQ==
follow-redirects@^1.15.0:
version "1.15.2"
resolved "https://registry.npmmirror.com/follow-redirects/-/follow-redirects-1.15.2.tgz#b460864144ba63f2681096f274c4e57026da2c13"
integrity sha512-VQLG33o04KaQ8uYi2tVNbdrWp1QWxNNea+nmIB4EVM28v0hmP17z7aG1+wAkNzVq4KeXTq3221ye5qTJP91JwA==
form-data@^4.0.0:
version "4.0.0"
resolved "https://registry.npmmirror.com/form-data/-/form-data-4.0.0.tgz#93919daeaf361ee529584b9b31664dc12c9fa452"
integrity sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==
dependencies:
asynckit "^0.4.0"
combined-stream "^1.0.8"
mime-types "^2.1.12"
forwarded@0.2.0: forwarded@0.2.0:
version "0.2.0" version "0.2.0"
resolved "https://registry.npmmirror.com/forwarded/-/forwarded-0.2.0.tgz#2269936428aad4c15c7ebe9779a84bf0b2a81811" resolved "https://registry.npmmirror.com/forwarded/-/forwarded-0.2.0.tgz#2269936428aad4c15c7ebe9779a84bf0b2a81811"
@ -2322,7 +2372,7 @@ mime-db@1.52.0, "mime-db@>= 1.43.0 < 2":
resolved "https://registry.npmmirror.com/mime-db/-/mime-db-1.52.0.tgz#bbabcdc02859f4987301c856e3387ce5ec43bf70" resolved "https://registry.npmmirror.com/mime-db/-/mime-db-1.52.0.tgz#bbabcdc02859f4987301c856e3387ce5ec43bf70"
integrity sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg== integrity sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==
mime-types@~2.1.24, mime-types@~2.1.34: mime-types@^2.1.12, mime-types@~2.1.24, mime-types@~2.1.34:
version "2.1.35" version "2.1.35"
resolved "https://registry.npmmirror.com/mime-types/-/mime-types-2.1.35.tgz#381a871b62a734450660ae3deee44813f70d959a" resolved "https://registry.npmmirror.com/mime-types/-/mime-types-2.1.35.tgz#381a871b62a734450660ae3deee44813f70d959a"
integrity sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw== integrity sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==
@ -2681,6 +2731,11 @@ proxy-addr@~2.0.7:
forwarded "0.2.0" forwarded "0.2.0"
ipaddr.js "1.9.1" ipaddr.js "1.9.1"
proxy-from-env@^1.1.0:
version "1.1.0"
resolved "https://registry.npmmirror.com/proxy-from-env/-/proxy-from-env-1.1.0.tgz#e102f16ca355424865755d2c9e8ea4f24d58c3e2"
integrity sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==
punycode@1.3.2: punycode@1.3.2:
version "1.3.2" version "1.3.2"
resolved "https://registry.npmmirror.com/punycode/-/punycode-1.3.2.tgz#9653a036fb7c1ee42342f2325cceefea3926c48d" resolved "https://registry.npmmirror.com/punycode/-/punycode-1.3.2.tgz#9653a036fb7c1ee42342f2325cceefea3926c48d"
@ -3106,6 +3161,11 @@ toidentifier@1.0.1:
resolved "https://registry.npmmirror.com/toidentifier/-/toidentifier-1.0.1.tgz#3be34321a88a820ed1bd80dfaa33e479fbb8dd35" resolved "https://registry.npmmirror.com/toidentifier/-/toidentifier-1.0.1.tgz#3be34321a88a820ed1bd80dfaa33e479fbb8dd35"
integrity sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA== integrity sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==
ts-md5@^1.3.1:
version "1.3.1"
resolved "https://registry.npmmirror.com/ts-md5/-/ts-md5-1.3.1.tgz#f5b860c0d5241dd9bb4e909dd73991166403f511"
integrity sha512-DiwiXfwvcTeZ5wCE0z+2A9EseZsztaiZtGrtSaY5JOD7ekPnR/GoIVD5gXZAlK9Na9Kvpo9Waz5rW64WKAWApg==
tslib@^1.8.1: tslib@^1.8.1:
version "1.14.1" version "1.14.1"
resolved "https://registry.npmmirror.com/tslib/-/tslib-1.14.1.tgz#cf2d38bdc34a134bcaf1091c41f6619e2f672d00" resolved "https://registry.npmmirror.com/tslib/-/tslib-1.14.1.tgz#cf2d38bdc34a134bcaf1091c41f6619e2f672d00"