Merge remote-tracking branch 'origin/master' into bj-rtss
This commit is contained in:
commit
83eab6c5b3
@ -14,8 +14,11 @@
|
||||
"build": "quasar build"
|
||||
},
|
||||
"dependencies": {
|
||||
"@pixi/graphics-extras": "^7.2.4",
|
||||
"@quasar/extras": "^1.0.0",
|
||||
"@stomp/stompjs": "^7.0.0",
|
||||
"alova": "^2.7.1",
|
||||
"axios": "^1.4.0",
|
||||
"google-protobuf": "^3.21.2",
|
||||
"js-base64": "^3.7.5",
|
||||
"pinia": "^2.0.11",
|
||||
@ -37,6 +40,7 @@
|
||||
"eslint-plugin-vue": "^9.0.0",
|
||||
"prettier": "^2.5.1",
|
||||
"protoc-gen-ts": "^0.8.6",
|
||||
"ts-md5": "^1.3.1",
|
||||
"typescript": "^4.5.4"
|
||||
},
|
||||
"engines": {
|
||||
|
@ -28,7 +28,7 @@ module.exports = configure(function (/* ctx */) {
|
||||
// app boot file (/src/boot)
|
||||
// --> boot files are part of "main.js"
|
||||
// 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
|
||||
css: ['app.scss'],
|
||||
|
7
src/boot/@pixi/graphics-extras.ts
Normal file
7
src/boot/@pixi/graphics-extras.ts
Normal 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
111
src/boot/axios.ts
Normal 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 };
|
@ -10,9 +10,18 @@
|
||||
<template v-if="drawStore.drawGraphicType === Link.Type">
|
||||
<link-template></link-template>
|
||||
</template>
|
||||
<template v-if="drawStore.drawGraphicType === Rect.Type">
|
||||
<rect-template></rect-template>
|
||||
</template>
|
||||
<template v-if="drawStore.drawGraphicType === Platform.Type">
|
||||
<platform-template></platform-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>
|
||||
</div>
|
||||
@ -33,12 +42,24 @@
|
||||
<link-property
|
||||
v-if="drawStore.selectedGraphicType === Link.Type"
|
||||
></link-property>
|
||||
<rect-property
|
||||
v-if="drawStore.selectedGraphicType === Rect.Type"
|
||||
></rect-property>
|
||||
<platform-property
|
||||
v-if="drawStore.selectedGraphicType === Platform.Type"
|
||||
></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
|
||||
v-else-if="drawStore.selectedGraphicType === IscsFan.Type"
|
||||
></iscs-fan-property>
|
||||
<signal-property
|
||||
v-else-if="drawStore.selectedGraphicType === Signal.Type"
|
||||
></signal-property>
|
||||
</q-card-section>
|
||||
</template>
|
||||
</q-card>
|
||||
@ -47,15 +68,25 @@
|
||||
|
||||
<script setup lang="ts">
|
||||
import LinkTemplate from './templates/LinkTemplate.vue';
|
||||
import RectTemplate from './templates/RectTemplate.vue';
|
||||
import PlatformTemplate from './templates/PlatformTemplate.vue';
|
||||
import StationTemplate from './templates/StationTemplate.vue';
|
||||
import CanvasProperty from './properties/CanvasProperty.vue';
|
||||
import LinkProperty from './properties/LinkProperty.vue';
|
||||
import RectProperty from './properties/RectProperty.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 SignalProperty from './properties/SignalProperty.vue';
|
||||
import { Link } from 'src/graphics/link/Link';
|
||||
import { Rect } from 'src/graphics/rect/Rect';
|
||||
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 { IscsFan } from 'src/graphics/iscs-fan/IscsFan';
|
||||
import { Signal } from 'src/graphics/signal/Signal';
|
||||
|
||||
const drawStore = useDrawStore();
|
||||
</script>
|
||||
|
@ -58,19 +58,13 @@ const canvas = reactive({
|
||||
});
|
||||
|
||||
onMounted(() => {
|
||||
console.log('画布属性表单mounted');
|
||||
const jc = drawStore.getJlCanvas();
|
||||
canvas.width = jc.properties.width;
|
||||
canvas.height = jc.properties.height;
|
||||
canvas.backgroundColor = jc.properties.backgroundColor;
|
||||
});
|
||||
|
||||
onUnmounted(() => {
|
||||
console.log('画布属性表单unmounted');
|
||||
});
|
||||
|
||||
function onUpdate() {
|
||||
console.log('画布属性更新');
|
||||
const app = drawStore.getDrawApp();
|
||||
app.updateCanvasAndRecord({
|
||||
...canvas,
|
||||
|
@ -1,19 +1,18 @@
|
||||
<template>
|
||||
<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
|
||||
outlined
|
||||
v-model.number="stationModel.lineWidth"
|
||||
v-model.number="platformModel.lineWidth"
|
||||
type="number"
|
||||
@blur="onUpdate"
|
||||
label="线宽"
|
||||
lazy-rules
|
||||
:rules="[(val) => (val && val > 0) || '画布宽必须大于0']"
|
||||
/>
|
||||
|
||||
<q-input
|
||||
outlined
|
||||
v-model="stationModel.lineColor"
|
||||
v-model="platformModel.lineColor"
|
||||
@blur="onUpdate"
|
||||
label="线色"
|
||||
lazy-rules
|
||||
@ -23,10 +22,10 @@
|
||||
<q-icon name="colorize" class="cursor-pointer">
|
||||
<q-popup-proxy cover transition-show="scale" transition-hide="scale">
|
||||
<q-color
|
||||
v-model="stationModel.lineColor"
|
||||
v-model="platformModel.lineColor"
|
||||
@change="
|
||||
(val) => {
|
||||
stationModel.lineColor = val;
|
||||
platformModel.lineColor = val;
|
||||
onUpdate();
|
||||
}
|
||||
"
|
||||
@ -35,13 +34,20 @@
|
||||
</q-icon>
|
||||
</template>
|
||||
</q-input>
|
||||
|
||||
<q-select
|
||||
outlined
|
||||
v-model="stationModel.hasdoor"
|
||||
:options="options"
|
||||
@blur="onUpdate"
|
||||
v-model="hasDoor"
|
||||
:options="optionsDoor"
|
||||
label="是否有屏蔽门"
|
||||
/>
|
||||
<q-select
|
||||
outlined
|
||||
@blur="onUpdate"
|
||||
v-model="trainDirection"
|
||||
:options="optionsDirection"
|
||||
label="行驶方向"
|
||||
/>
|
||||
</q-form>
|
||||
</template>
|
||||
|
||||
@ -49,37 +55,58 @@
|
||||
import { PlatformData } from 'src/examples/app/graphics/PlatformInteraction';
|
||||
import { Platform } from 'src/graphics/platform/Platform';
|
||||
import { useDrawStore } from 'src/stores/draw-store';
|
||||
import { onMounted, reactive, watch } from 'vue';
|
||||
import { onMounted, reactive, ref, watch } from 'vue';
|
||||
|
||||
const drawStore = useDrawStore();
|
||||
const stationModel = reactive(new PlatformData());
|
||||
const options = [true, false];
|
||||
const platformModel = reactive(new PlatformData());
|
||||
const hasDoor = 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 == Platform.Type) {
|
||||
// console.log('station变更');
|
||||
stationModel.copyFrom(val.saveData() as PlatformData);
|
||||
platformModel.copyFrom(val.saveData() as PlatformData);
|
||||
hasDoor.value = (showSelectData as never)[platformModel.hasdoor + ''];
|
||||
trainDirection.value = (showSelectData as never)[
|
||||
platformModel.trainDirection
|
||||
];
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
onMounted(() => {
|
||||
//console.log('station 属性表单 mounted');
|
||||
const station = drawStore.selectedGraphic as Platform;
|
||||
|
||||
if (station) {
|
||||
stationModel.copyFrom(station.saveData());
|
||||
const platform = drawStore.selectedGraphic as Platform;
|
||||
if (platform) {
|
||||
platformModel.copyFrom(platform.saveData());
|
||||
hasDoor.value = (showSelectData as never)[platformModel.hasdoor + ''];
|
||||
trainDirection.value = (showSelectData as never)[
|
||||
platformModel.trainDirection
|
||||
];
|
||||
}
|
||||
});
|
||||
|
||||
function onUpdate() {
|
||||
//console.log(stationModel, 'station 属性更新');
|
||||
const station = drawStore.selectedGraphic as Platform;
|
||||
if (station) {
|
||||
drawStore.getDrawApp().updateGraphicAndRecord(station, stationModel);
|
||||
platformModel.hasdoor = JSON.parse((showSelect as never)[hasDoor.value]);
|
||||
platformModel.trainDirection = (showSelect as never)[trainDirection.value];
|
||||
const platform = drawStore.selectedGraphic as Platform;
|
||||
if (platform) {
|
||||
drawStore.getDrawApp().updateGraphicAndRecord(platform, platformModel);
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
74
src/components/draw-app/properties/RectProperty.vue
Normal file
74
src/components/draw-app/properties/RectProperty.vue
Normal 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>
|
57
src/components/draw-app/properties/SignalProperty.vue
Normal file
57
src/components/draw-app/properties/SignalProperty.vue
Normal 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>
|
200
src/components/draw-app/properties/StationProperty.vue
Normal file
200
src/components/draw-app/properties/StationProperty.vue
Normal 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>
|
198
src/components/draw-app/properties/TrainProperty.vue
Normal file
198
src/components/draw-app/properties/TrainProperty.vue
Normal 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>
|
76
src/components/draw-app/templates/RectTemplate.vue
Normal file
76
src/components/draw-app/templates/RectTemplate.vue
Normal 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>
|
70
src/components/draw-app/templates/StationTemplate.vue
Normal file
70
src/components/draw-app/templates/StationTemplate.vue
Normal 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>
|
69
src/components/draw-app/templates/TrainTemplate.vue
Normal file
69
src/components/draw-app/templates/TrainTemplate.vue
Normal 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>
|
42
src/examples/app/api/ApiCommon.ts
Normal file
42
src/examples/app/api/ApiCommon.ts
Normal 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;
|
||||
}
|
77
src/examples/app/api/UserApi.ts
Normal file
77
src/examples/app/api/UserApi.ts
Normal 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);
|
||||
}
|
@ -7,6 +7,10 @@ message RtssGraphicStorage {
|
||||
repeated Link links = 2;
|
||||
repeated IscsFan iscsFans = 3;
|
||||
repeated Platform Platforms = 4;
|
||||
repeated Station stations = 5;
|
||||
repeated Rect Rects = 6;
|
||||
repeated Train train = 7;
|
||||
repeated Signal signals = 8;
|
||||
}
|
||||
|
||||
message Canvas {
|
||||
@ -64,17 +68,57 @@ message Link {
|
||||
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 {
|
||||
CommonInfo common = 1;
|
||||
string code = 2;
|
||||
bool hasdoor = 3; // 是否有屏蔽门
|
||||
int32 lineWidth = 4; // 线宽
|
||||
string lineColor = 5; // 站台线色
|
||||
string lineColorDoor = 6; // 屏蔽门线色
|
||||
Point point = 7; // 位置坐标
|
||||
float width = 8;//宽度
|
||||
float height = 9; //高度
|
||||
repeated string orbitCode = 10;//站台轨
|
||||
string trainDirection = 4; // 行驶方向--屏蔽门上下
|
||||
int32 lineWidth = 5; // 线宽
|
||||
string lineColor = 6; // 站台线色
|
||||
string lineColorDoor = 7; // 屏蔽门线色
|
||||
Point point = 8; // 位置坐标
|
||||
float width = 9;//宽度
|
||||
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 {
|
||||
@ -83,3 +127,8 @@ message IscsFan {
|
||||
}
|
||||
|
||||
message Turnout {}
|
||||
|
||||
message Signal {
|
||||
CommonInfo common = 1;
|
||||
string code = 2;
|
||||
}
|
||||
|
14
src/examples/app/app_message/protos/graphic_states.proto
Normal file
14
src/examples/app/app_message/protos/graphic_states.proto
Normal 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;
|
||||
}
|
13
src/examples/app/configs/TokenManage.ts
Normal file
13
src/examples/app/configs/TokenManage.ts
Normal 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);
|
||||
}
|
13
src/examples/app/configs/UrlManage.ts
Normal file
13
src/examples/app/configs/UrlManage.ts
Normal 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`;
|
||||
}
|
@ -2,12 +2,14 @@ import * as pb_1 from 'google-protobuf';
|
||||
import {
|
||||
ChildTransform,
|
||||
GraphicData,
|
||||
GraphicState,
|
||||
GraphicTransform,
|
||||
IChildTransform,
|
||||
IGraphicTransform,
|
||||
} from 'src/jlgraphic';
|
||||
import { toStorageTransform } from '..';
|
||||
import { graphicData } from '../protos/draw_data_storage';
|
||||
import { graphicStates } from '../protos/graphic_states';
|
||||
|
||||
export interface ICommonInfo {
|
||||
id: string;
|
||||
@ -27,10 +29,10 @@ export abstract class GraphicDataBase implements GraphicData {
|
||||
this._data = data;
|
||||
}
|
||||
|
||||
static defaultCommonInfo(): graphicData.CommonInfo {
|
||||
static defaultCommonInfo(graphicType: string): graphicData.CommonInfo {
|
||||
return new graphicData.CommonInfo({
|
||||
id: '',
|
||||
graphicType: '',
|
||||
graphicType: graphicType,
|
||||
transform: new graphicData.Transform({
|
||||
position: new graphicData.Point({ x: 0, y: 0 }),
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
@ -1,7 +1,12 @@
|
||||
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 { GraphicDataBase } from './GraphicDataBase';
|
||||
import { graphicStates } from '../protos/graphic_states';
|
||||
import { GraphicDataBase, GraphicStateBase } from './GraphicDataBase';
|
||||
|
||||
export class IscsFanData extends GraphicDataBase implements IIscsFanData {
|
||||
constructor(data?: graphicData.IscsFan) {
|
||||
@ -10,7 +15,7 @@ export class IscsFanData extends GraphicDataBase implements IIscsFanData {
|
||||
fan = data;
|
||||
} else {
|
||||
fan = new graphicData.IscsFan({
|
||||
common: GraphicDataBase.defaultCommonInfo(),
|
||||
common: GraphicDataBase.defaultCommonInfo(IscsFan.Type),
|
||||
});
|
||||
}
|
||||
super(fan);
|
||||
@ -36,3 +41,32 @@ export class IscsFanData extends GraphicDataBase implements IIscsFanData {
|
||||
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());
|
||||
}
|
||||
}
|
||||
|
@ -33,6 +33,12 @@ export class PlatformData extends GraphicDataBase implements IPlatformData {
|
||||
set hasdoor(v: boolean) {
|
||||
this.data.hasdoor = v;
|
||||
}
|
||||
get trainDirection(): string {
|
||||
return this.data.trainDirection;
|
||||
}
|
||||
set trainDirection(v: string) {
|
||||
this.data.trainDirection = v;
|
||||
}
|
||||
get lineWidth(): number {
|
||||
return this.data.lineWidth;
|
||||
}
|
||||
|
77
src/examples/app/graphics/RectInteraction.ts
Normal file
77
src/examples/app/graphics/RectInteraction.ts
Normal 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);
|
||||
}
|
||||
}
|
37
src/examples/app/graphics/SignalInteraction.ts
Normal file
37
src/examples/app/graphics/SignalInteraction.ts
Normal 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);
|
||||
}
|
||||
}
|
92
src/examples/app/graphics/StationInteraction.ts
Normal file
92
src/examples/app/graphics/StationInteraction.ts
Normal 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);
|
||||
}
|
||||
}
|
92
src/examples/app/graphics/TrainInteraction.ts
Normal file
92
src/examples/app/graphics/TrainInteraction.ts
Normal 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);
|
||||
}
|
||||
}
|
@ -1,11 +1,19 @@
|
||||
import { fromUint8Array, toUint8Array } from 'js-base64';
|
||||
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 { Link } from 'src/graphics/link/Link';
|
||||
import { Link, LinkTemplate } from 'src/graphics/link/Link';
|
||||
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 { 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 {
|
||||
CombinationKey,
|
||||
GraphicApp,
|
||||
@ -16,10 +24,15 @@ import {
|
||||
} from 'src/jlgraphic';
|
||||
import { ContextMenu } from 'src/jlgraphic/ui/ContextMenu';
|
||||
import { MenuItemOptions } from 'src/jlgraphic/ui/Menu';
|
||||
import { IscsFanData } from './graphics/IscsFanInteraction';
|
||||
import { IscsFanData, IscsFanState } from './graphics/IscsFanInteraction';
|
||||
import { LinkData } from './graphics/LinkInteraction';
|
||||
import { RectData } from './graphics/RectInteraction';
|
||||
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 { Notify } from 'quasar';
|
||||
|
||||
export function fromStoragePoint(p: graphicData.Point): Point {
|
||||
return new Point(p.x, p.y);
|
||||
@ -91,16 +104,27 @@ export function initDrawApp(dom: HTMLElement): JlDrawApp {
|
||||
const app = drawApp;
|
||||
app.setOptions({
|
||||
drawAssistants: [
|
||||
new LinkDraw(app, () => {
|
||||
return new LinkData();
|
||||
}),
|
||||
new IscsFanDraw(app, () => {
|
||||
return new IscsFanData();
|
||||
}),
|
||||
new PlatformDraw(app, () => {
|
||||
return new PlatformData();
|
||||
new LinkDraw(app, new LinkTemplate(new LinkData())),
|
||||
new IscsFanDraw(
|
||||
app,
|
||||
new IscsFanTemplate(new IscsFanData(), new IscsFanState())
|
||||
),
|
||||
new SignalDraw(app, new SignalTemplate(new SignalData())),
|
||||
new TrainDraw(app, () => {
|
||||
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(
|
||||
new KeyListener({
|
||||
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(
|
||||
new KeyListener({
|
||||
value: '1',
|
||||
onPress: () => {
|
||||
app.queryStore.queryByType<IscsFan>(IscsFan.Type).forEach((fan) => {
|
||||
fan.__state = fan.__state + 1;
|
||||
fan.__state = fan.__state % 5;
|
||||
fan.states.state = fan.states.state + 1;
|
||||
fan.states.state = fan.states.state % 5;
|
||||
fan.repaint();
|
||||
});
|
||||
},
|
||||
@ -183,12 +239,24 @@ export function saveDrawDatas(app: JlDrawApp) {
|
||||
if (Link.Type === g.type) {
|
||||
const linkData = (g as Link).saveData();
|
||||
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) {
|
||||
const IscsFanData = (g as IscsFan).saveData();
|
||||
storage.iscsFans.push((IscsFanData as IscsFanData).data);
|
||||
} else if (Platform.Type === g.type) {
|
||||
const platformData = (g as Platform).saveData();
|
||||
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());
|
||||
@ -210,12 +278,27 @@ export function loadDrawDatas(app: GraphicApp) {
|
||||
storage.links.forEach((link) => {
|
||||
datas.push(new LinkData(link));
|
||||
});
|
||||
storage.Rects.forEach((rect) => {
|
||||
datas.push(new RectData(rect));
|
||||
});
|
||||
storage.iscsFans.forEach((fan) => {
|
||||
datas.push(new IscsFanData(fan));
|
||||
});
|
||||
storage.Platforms.forEach((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);
|
||||
} else {
|
||||
app.loadGraphic([]);
|
||||
|
File diff suppressed because it is too large
Load Diff
191
src/examples/app/protos/graphic_states.ts
Normal file
191
src/examples/app/protos/graphic_states.ts
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
51
src/graphics/CommonGraphics.ts
Normal file
51
src/graphics/CommonGraphics.ts
Normal 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
|
||||
);
|
||||
}
|
@ -1,6 +1,7 @@
|
||||
import {
|
||||
GraphicAnimation,
|
||||
GraphicData,
|
||||
GraphicState,
|
||||
JlGraphic,
|
||||
JlGraphicTemplate,
|
||||
} from 'src/jlgraphic';
|
||||
@ -23,12 +24,16 @@ export interface IIscsFanData extends GraphicData {
|
||||
set code(v: string);
|
||||
}
|
||||
|
||||
export interface IIscsFanState extends GraphicState {
|
||||
get state(): number;
|
||||
set state(v: number);
|
||||
}
|
||||
|
||||
export class IscsFan extends JlGraphic {
|
||||
static Type = 'IscsFan';
|
||||
_border: Sprite;
|
||||
_fan: Sprite;
|
||||
fanTextures: FanTextures;
|
||||
__state = 0;
|
||||
|
||||
constructor(fanTextures: FanTextures) {
|
||||
super(IscsFan.Type);
|
||||
@ -42,28 +47,31 @@ export class IscsFan extends JlGraphic {
|
||||
this.addChild(this._border);
|
||||
this.addChild(this._fan);
|
||||
}
|
||||
get states(): IIscsFanState {
|
||||
return this.getStates<IIscsFanState>();
|
||||
}
|
||||
doRepaint(): void {
|
||||
if (this.__state === 0) {
|
||||
if (this.states.state === 0) {
|
||||
// 停止
|
||||
this.stopFanRun();
|
||||
this._fan.rotation = 0;
|
||||
this._fan.texture = this.fanTextures.gray;
|
||||
} else if (this.__state === 1) {
|
||||
} else if (this.states.state === 1) {
|
||||
// 正常运行
|
||||
this._fan.texture = this.fanTextures.green;
|
||||
// 动画
|
||||
this.initFanRun();
|
||||
} else if (this.__state === 2) {
|
||||
} else if (this.states.state === 2) {
|
||||
// 报警运行
|
||||
this._fan.texture = this.fanTextures.yellow;
|
||||
// 动画
|
||||
this.initFanRun();
|
||||
} else if (this.__state === 3) {
|
||||
} else if (this.states.state === 3) {
|
||||
// 故障
|
||||
this.stopFanRun();
|
||||
this._fan.rotation = 0;
|
||||
this._fan.texture = this.fanTextures.red;
|
||||
} else if (this.__state === 4) {
|
||||
} else if (this.states.state === 4) {
|
||||
// 通信故障
|
||||
// 停止
|
||||
this.stopFanRun();
|
||||
@ -99,12 +107,18 @@ export class IscsFan extends JlGraphic {
|
||||
|
||||
export class IscsFanTemplate extends JlGraphicTemplate<IscsFan> {
|
||||
fanTextures?: FanTextures;
|
||||
constructor() {
|
||||
super(IscsFan.Type);
|
||||
constructor(dataTemplate: IIscsFanData, stateTemplate: IIscsFanState) {
|
||||
super(IscsFan.Type, {
|
||||
dataTemplate,
|
||||
stateTemplate,
|
||||
});
|
||||
}
|
||||
new(): IscsFan {
|
||||
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('资源未加载/加载失败');
|
||||
}
|
||||
|
@ -1,7 +1,10 @@
|
||||
import { FederatedMouseEvent, Point } from 'pixi.js';
|
||||
import {
|
||||
AbsorbableLine,
|
||||
AbsorbablePosition,
|
||||
GraphicDrawAssistant,
|
||||
GraphicInteractionPlugin,
|
||||
GraphicTransformEvent,
|
||||
JlDrawApp,
|
||||
JlGraphic,
|
||||
} from 'src/jlgraphic';
|
||||
@ -13,9 +16,8 @@ export class IscsFanDraw extends GraphicDrawAssistant<
|
||||
> {
|
||||
_iscsFan: IscsFan | null = null;
|
||||
|
||||
constructor(app: JlDrawApp, createData: () => IIscsFanData) {
|
||||
const template = new IscsFanTemplate();
|
||||
super(app, template, createData, IscsFan.Type, '风机');
|
||||
constructor(app: JlDrawApp, template: IscsFanTemplate) {
|
||||
super(app, template, IscsFan.Type, '风机');
|
||||
IscsFanInteraction.init(app);
|
||||
}
|
||||
|
||||
@ -66,12 +68,42 @@ export class IscsFanInteraction extends GraphicInteractionPlugin<IscsFan> {
|
||||
bind(g: IscsFan): void {
|
||||
g.eventMode = 'static';
|
||||
g.cursor = 'pointer';
|
||||
g.scalable = true;
|
||||
// g.scalable = 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 {
|
||||
g.eventMode = 'none';
|
||||
g.scalable = false;
|
||||
// g.scalable = 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;
|
||||
}
|
||||
|
@ -89,8 +89,10 @@ export class LinkTemplate extends JlGraphicTemplate<Link> {
|
||||
lineWidth: number;
|
||||
lineColor: string;
|
||||
segmentsCount: number;
|
||||
constructor() {
|
||||
super(Link.Type);
|
||||
constructor(dataTemplate: ILinkData) {
|
||||
super(Link.Type, {
|
||||
dataTemplate,
|
||||
});
|
||||
this.lineWidth = 2;
|
||||
this.lineColor = '#000000';
|
||||
this.curve = false;
|
||||
|
@ -30,12 +30,13 @@ import {
|
||||
ILineGraphic,
|
||||
PolylineEditPlugin,
|
||||
addWayPoint,
|
||||
addWaypointConfig,
|
||||
clearWayPoint,
|
||||
clearWaypointsConfig,
|
||||
getWaypointRangeIndex,
|
||||
removeBezierWayPoint,
|
||||
removeLineWayPoint,
|
||||
} from 'src/jlgraphic/plugins/GraphicEditPlugin';
|
||||
import { ContextMenu } from 'src/jlgraphic/ui/ContextMenu';
|
||||
import { MenuItemOptions, MenuOptions } from 'src/jlgraphic/ui/Menu';
|
||||
import { ILinkData, Link, LinkTemplate } from './Link';
|
||||
|
||||
export interface ILinkDrawOptions {
|
||||
@ -67,8 +68,8 @@ export class LinkDraw extends GraphicDrawAssistant<LinkTemplate, ILinkData> {
|
||||
},
|
||||
});
|
||||
|
||||
constructor(app: JlDrawApp, createData: () => ILinkData) {
|
||||
super(app, new LinkTemplate(), createData, Link.Type, '轨道Link');
|
||||
constructor(app: JlDrawApp, template: LinkTemplate) {
|
||||
super(app, template, Link.Type, '轨道Link');
|
||||
this.container.addChild(this.graphic);
|
||||
this.graphicTemplate.curve = true;
|
||||
|
||||
@ -251,7 +252,7 @@ function buildAbsorbablePositions(link: Link): AbsorbablePosition[] {
|
||||
* @param dp
|
||||
* @param index
|
||||
*/
|
||||
function onEditPointCreate(
|
||||
function onPolylineEditPointCreate(
|
||||
g: ILineGraphic,
|
||||
dp: DraggablePoint,
|
||||
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({
|
||||
name: '轨道编辑菜单',
|
||||
groups: [
|
||||
@ -311,14 +382,17 @@ export class LinkPointsEditPlugin extends GraphicInteractionPlugin<Link> {
|
||||
const target = e.target as DisplayObject;
|
||||
const link = target.getGraphic() as Link;
|
||||
this.app.updateSelected(link);
|
||||
const p = link.getGraphicApp().toCanvasCoordinates(e.global);
|
||||
console.log('右键坐标', p);
|
||||
|
||||
addWaypointConfig.handler = () => {
|
||||
const linePoints = link.linePoints;
|
||||
const p = link.screenToLocalPoint(e.global);
|
||||
console.log('添加路径点', linePoints, p);
|
||||
const { start, end } = getWaypointRangeIndex(
|
||||
linePoints,
|
||||
link.datas.curve,
|
||||
p
|
||||
p,
|
||||
link.datas.lineWidth
|
||||
);
|
||||
addWayPoint(link, link.datas.curve, start, end, p);
|
||||
};
|
||||
@ -338,7 +412,7 @@ export class LinkPointsEditPlugin extends GraphicInteractionPlugin<Link> {
|
||||
);
|
||||
if (!lep) {
|
||||
lep = new BezierCurveEditPlugin(link, {
|
||||
onEditPointCreate,
|
||||
onEditPointCreate: onCurveEditPointCreate,
|
||||
});
|
||||
link.addAssistantAppend(lep);
|
||||
}
|
||||
@ -348,7 +422,9 @@ export class LinkPointsEditPlugin extends GraphicInteractionPlugin<Link> {
|
||||
PolylineEditPlugin.Name
|
||||
);
|
||||
if (!lep) {
|
||||
lep = new PolylineEditPlugin(link, { onEditPointCreate });
|
||||
lep = new PolylineEditPlugin(link, {
|
||||
onEditPointCreate: onPolylineEditPointCreate,
|
||||
});
|
||||
link.addAssistantAppend(lep);
|
||||
}
|
||||
}
|
||||
|
@ -3,6 +3,7 @@ import {
|
||||
GraphicData,
|
||||
JlGraphic,
|
||||
JlGraphicTemplate,
|
||||
VectorText,
|
||||
getRectangleCenter,
|
||||
} from 'src/jlgraphic';
|
||||
|
||||
@ -11,6 +12,8 @@ export interface IPlatformData extends GraphicData {
|
||||
set code(v: string);
|
||||
get hasdoor(): boolean; // 是否有屏蔽门
|
||||
set hasdoor(v: boolean);
|
||||
get trainDirection(): string; // 行驶方向--屏蔽门上下
|
||||
set trainDirection(v: string);
|
||||
get lineWidth(): number; // 线宽
|
||||
set lineWidth(v: number);
|
||||
get lineColor(): string; // 站台线色
|
||||
@ -28,43 +31,90 @@ export interface IPlatformData extends GraphicData {
|
||||
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 {
|
||||
static Type = 'Platform';
|
||||
|
||||
platformGraphic: Graphics;
|
||||
doorGraphic: Graphics;
|
||||
doorCloseGraphic: Graphics;
|
||||
besideGraphic: Graphics;
|
||||
codeGraph: VectorText = new VectorText(''); //站台旁数字、字符
|
||||
constructor() {
|
||||
super(Platform.Type);
|
||||
this.platformGraphic = new Graphics();
|
||||
this.doorGraphic = new Graphics();
|
||||
this.doorCloseGraphic = new Graphics();
|
||||
this.besideGraphic = new Graphics();
|
||||
this.addChild(this.platformGraphic);
|
||||
this.addChild(this.doorGraphic);
|
||||
this.addChild(this.doorCloseGraphic);
|
||||
this.addChild(this.besideGraphic);
|
||||
this.addChild(this.codeGraph);
|
||||
this.codeGraph.setVectorFontSize(platformConsts.besideFontSize);
|
||||
}
|
||||
|
||||
get datas(): IPlatformData {
|
||||
return this.getDatas<IPlatformData>();
|
||||
}
|
||||
doRepaint(): void {
|
||||
const width = this.datas.width;
|
||||
const height = this.datas.height;
|
||||
//屏蔽门
|
||||
const doorGraphic = this.doorGraphic;
|
||||
const doorCloseGraphic = this.doorCloseGraphic;
|
||||
doorGraphic.clear();
|
||||
doorCloseGraphic.clear();
|
||||
if (this.datas.hasdoor) {
|
||||
doorGraphic.clear();
|
||||
doorGraphic.lineStyle(
|
||||
this.datas.lineWidth,
|
||||
new Color(this.datas.lineColorDoor)
|
||||
);
|
||||
const width = this.datas.width;
|
||||
const height = this.datas.height;
|
||||
doorGraphic.moveTo(
|
||||
-width / 2 - this.datas.lineWidth / 2,
|
||||
-height / 2 - 10
|
||||
doorGraphic.moveTo(-width / 2 - this.datas.lineWidth / 2, 0);
|
||||
doorGraphic.lineTo(-platformConsts.doorOpenSpacing, 0);
|
||||
doorGraphic.moveTo(platformConsts.doorOpenSpacing, 0);
|
||||
doorGraphic.lineTo(width / 2 + this.datas.lineWidth / 2, 0);
|
||||
//屏蔽门闭合
|
||||
doorCloseGraphic.lineStyle(
|
||||
this.datas.lineWidth,
|
||||
new Color(this.datas.lineColorDoor)
|
||||
);
|
||||
doorGraphic.lineTo(
|
||||
width / 2 + this.datas.lineWidth / 2,
|
||||
-height / 2 - 10
|
||||
doorCloseGraphic.moveTo(-platformConsts.doorOpenSpacing, 0);
|
||||
doorCloseGraphic.lineTo(platformConsts.doorOpenSpacing, 0);
|
||||
doorGraphic.position.set(
|
||||
0,
|
||||
-height / 2 - platformConsts.doorPlatformSpacing
|
||||
);
|
||||
doorCloseGraphic.position.set(
|
||||
0,
|
||||
-height / 2 - platformConsts.doorPlatformSpacing
|
||||
);
|
||||
}
|
||||
//站台
|
||||
const platformGraphic = this.platformGraphic;
|
||||
platformGraphic.clear();
|
||||
platformGraphic.lineStyle(
|
||||
@ -74,14 +124,64 @@ export class Platform extends JlGraphic {
|
||||
platformGraphic.beginFill(this.datas.lineColor, 1);
|
||||
platformGraphic.drawRect(0, 0, this.datas.width, this.datas.height);
|
||||
platformGraphic.endFill;
|
||||
const rect = new Rectangle(0, 0, this.datas.width, this.datas.height);
|
||||
platformGraphic.pivot = getRectangleCenter(rect);
|
||||
const rectP = new Rectangle(0, 0, this.datas.width, this.datas.height);
|
||||
platformGraphic.pivot = getRectangleCenter(rectP);
|
||||
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> {
|
||||
hasdoor: boolean;
|
||||
trainDirection: string;
|
||||
lineWidth: number;
|
||||
lineColor: string;
|
||||
lineColorDoor: string;
|
||||
@ -89,12 +189,13 @@ export class PlatformTemplate extends JlGraphicTemplate<Platform> {
|
||||
height: number;
|
||||
constructor() {
|
||||
super(Platform.Type);
|
||||
this.lineWidth = 2;
|
||||
this.lineColor = '#000000';
|
||||
this.lineColorDoor = '0x008000';
|
||||
this.hasdoor = true;
|
||||
this.width = 100;
|
||||
this.height = 30;
|
||||
this.trainDirection = 'left';
|
||||
this.lineWidth = platformConsts.lineWidth;
|
||||
this.lineColor = PlatformColorEnum.yellow;
|
||||
this.lineColorDoor = PlatformColorEnum.doorBlue;
|
||||
this.width = platformConsts.width;
|
||||
this.height = platformConsts.height;
|
||||
}
|
||||
new(): Platform {
|
||||
return new Platform();
|
||||
|
@ -7,14 +7,15 @@ import {
|
||||
} from 'pixi.js';
|
||||
import {
|
||||
GraphicDrawAssistant,
|
||||
GraphicInteractionPlugin,
|
||||
JlDrawApp,
|
||||
KeyListener,
|
||||
JlGraphic,
|
||||
getRectangleCenter,
|
||||
} from 'src/jlgraphic';
|
||||
|
||||
import { IPlatformData, Platform, PlatformTemplate } from './Platform';
|
||||
|
||||
export interface ILinkDrawOptions {
|
||||
export interface IPlatformDrawOptions {
|
||||
newData: () => IPlatformData;
|
||||
}
|
||||
|
||||
@ -26,15 +27,6 @@ export class PlatformDraw extends GraphicDrawAssistant<
|
||||
platformGraphic: 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) {
|
||||
super(
|
||||
app,
|
||||
@ -46,24 +38,20 @@ export class PlatformDraw extends GraphicDrawAssistant<
|
||||
this.container.addChild(this.platformGraphic);
|
||||
this.container.addChild(this.doorGraphic);
|
||||
this.graphicTemplate.hasdoor = true;
|
||||
platformInteraction.init(app);
|
||||
}
|
||||
|
||||
bind(): void {
|
||||
super.bind();
|
||||
this.app.addKeyboardListener(this.keypListener);
|
||||
}
|
||||
unbind(): void {
|
||||
super.unbind();
|
||||
this.app.removeKeyboardListener(this.keypListener);
|
||||
}
|
||||
|
||||
clearCache(): void {
|
||||
this.platformGraphic.clear();
|
||||
this.doorGraphic.clear();
|
||||
}
|
||||
onRightClick(): void {
|
||||
this.createAndStore(true);
|
||||
}
|
||||
onLeftDown(e: FederatedPointerEvent): void {
|
||||
const { x, y } = this.toCanvasCoordinates(e.global);
|
||||
const p = new Point(x, y);
|
||||
@ -85,6 +73,7 @@ export class PlatformDraw extends GraphicDrawAssistant<
|
||||
const height = template.height;
|
||||
doorGraphic.moveTo(-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 {
|
||||
const template = this.graphicTemplate;
|
||||
data.hasdoor = template.hasdoor;
|
||||
data.trainDirection = template.trainDirection;
|
||||
data.point = this.point;
|
||||
data.lineWidth = template.lineWidth;
|
||||
data.lineColor = template.lineColor;
|
||||
@ -111,3 +101,29 @@ export class PlatformDraw extends GraphicDrawAssistant<
|
||||
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
86
src/graphics/rect/Rect.ts
Normal 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();
|
||||
}
|
||||
}
|
261
src/graphics/rect/RectDrawAssistant.ts
Normal file
261
src/graphics/rect/RectDrawAssistant.ts
Normal 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();
|
||||
}
|
||||
}
|
||||
}
|
52
src/graphics/signal/Lamp.ts
Normal file
52
src/graphics/signal/Lamp.ts
Normal 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
|
||||
);
|
||||
}
|
||||
}
|
51
src/graphics/signal/LampMainBody.ts
Normal file
51
src/graphics/signal/LampMainBody.ts
Normal 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() {}
|
||||
}
|
@ -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();
|
||||
}
|
||||
}
|
133
src/graphics/signal/SignalDrawAssistant.ts
Normal file
133
src/graphics/signal/SignalDrawAssistant.ts
Normal 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;
|
||||
}
|
101
src/graphics/station/Station.ts
Normal file
101
src/graphics/station/Station.ts
Normal 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();
|
||||
}
|
||||
}
|
93
src/graphics/station/StationDrawAssistant.ts
Normal file
93
src/graphics/station/StationDrawAssistant.ts
Normal 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
185
src/graphics/train/Train.ts
Normal 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();
|
||||
}
|
||||
}
|
138
src/graphics/train/TrainDrawAssistant.ts
Normal file
138
src/graphics/train/TrainDrawAssistant.ts
Normal 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;
|
||||
}
|
||||
}
|
@ -45,7 +45,6 @@ export abstract class GraphicDrawAssistant<
|
||||
icon: string; // 界面显示的图标
|
||||
container: Container = new Container();
|
||||
graphicTemplate: GT;
|
||||
createGraphicData: () => GD;
|
||||
|
||||
escListener: KeyListener = new KeyListener({
|
||||
value: 'Escape',
|
||||
@ -61,7 +60,6 @@ export abstract class GraphicDrawAssistant<
|
||||
constructor(
|
||||
graphicApp: JlDrawApp,
|
||||
graphicTemplate: GT,
|
||||
createGraphicData: () => GD,
|
||||
icon: string,
|
||||
description: string
|
||||
) {
|
||||
@ -69,7 +67,6 @@ export abstract class GraphicDrawAssistant<
|
||||
this.app = graphicApp;
|
||||
this.type = graphicTemplate.type;
|
||||
this.graphicTemplate = graphicTemplate;
|
||||
this.createGraphicData = createGraphicData;
|
||||
this.icon = icon;
|
||||
this.description = description;
|
||||
this.app.registerGraphicTemplates(this.graphicTemplate);
|
||||
@ -102,7 +99,9 @@ export abstract class GraphicDrawAssistant<
|
||||
unbind(): void {
|
||||
this.clearCache();
|
||||
const canvas = this.canvas;
|
||||
if (this.container?.parent) {
|
||||
canvas.removeChild(this.container);
|
||||
}
|
||||
canvas.off('mousedown', this.onLeftDown, this);
|
||||
canvas.off('mousemove', this.onMouseMove, this);
|
||||
canvas.off('mouseup', this.onLeftUp, this);
|
||||
@ -161,7 +160,7 @@ export abstract class GraphicDrawAssistant<
|
||||
* 创建并添加到图形App
|
||||
*/
|
||||
createAndStore(finish: boolean): JlGraphic | null {
|
||||
const data = this.createGraphicData();
|
||||
const data = this.graphicTemplate.datas as GD;
|
||||
data.id = this.nextId();
|
||||
data.graphicType = this.graphicTemplate.type;
|
||||
if (!this.prepareData(data)) {
|
||||
@ -457,13 +456,13 @@ export class JlDrawApp extends GraphicApp {
|
||||
* 删除选中图形对象
|
||||
*/
|
||||
deleteSelectedGraphics() {
|
||||
const deletes = this.selectedGraphics.slice(
|
||||
0,
|
||||
this.selectedGraphics.length
|
||||
);
|
||||
this.deleteGraphics(...this.selectedGraphics);
|
||||
const deletes = this.deleteGraphics(...this.selectedGraphics);
|
||||
if (deletes.length > 0) {
|
||||
// 删除图形对象操作记录
|
||||
this.opRecord.record(new GraphicDeleteOperation(this, deletes));
|
||||
} else {
|
||||
console.debug('没有删除元素,不记录');
|
||||
}
|
||||
}
|
||||
|
||||
updateCanvasAndRecord(data: ICanvasProperties) {
|
||||
|
@ -288,6 +288,7 @@ export interface GraphicAppEvents extends GlobalMixins.GraphicAppEvents {
|
||||
drag_op_end: [event: AppDragEvent];
|
||||
'pre-menu-handle': [menu: MenuItemOptions];
|
||||
'post-menu-handle': [menu: MenuItemOptions];
|
||||
'websocket-state-change': [app: GraphicApp, connected: boolean];
|
||||
destroy: [app: GraphicApp];
|
||||
}
|
||||
|
||||
@ -319,6 +320,11 @@ export interface IGraphicAppConfig {
|
||||
* 超出屏幕显示范围是否剪裁,默认true
|
||||
*/
|
||||
cullable?: boolean;
|
||||
|
||||
/**
|
||||
* 是否支持删除操作
|
||||
*/
|
||||
isSupportDeletion?: (g: JlGraphic) => boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -527,7 +533,10 @@ export class GraphicApp extends EventEmitter<GraphicAppEvents> {
|
||||
*/
|
||||
handleGraphicStates(graphicStates: GraphicState[]) {
|
||||
graphicStates.forEach((state) => {
|
||||
const list = this.queryStore.queryByIdOrCode(state.code);
|
||||
const list = this.queryStore.queryByIdOrCodeAndType(
|
||||
state.code,
|
||||
state.graphicType
|
||||
);
|
||||
if (list.length == 0) {
|
||||
const template = this.getGraphicTemplatesByType(state.graphicType);
|
||||
const g = template.new();
|
||||
@ -630,7 +639,7 @@ export class GraphicApp extends EventEmitter<GraphicAppEvents> {
|
||||
this.addGraphics(g);
|
||||
});
|
||||
// 加载数据关系
|
||||
this.graphicStore.getAllGraphics().forEach((g) => g.loadRealtions());
|
||||
this.graphicStore.getAllGraphics().forEach((g) => g.loadRelations());
|
||||
// 更新id生成器
|
||||
const max =
|
||||
this.graphicStore
|
||||
@ -725,8 +734,19 @@ export class GraphicApp extends EventEmitter<GraphicAppEvents> {
|
||||
* 删除图形
|
||||
* @param graphics 图形对象
|
||||
*/
|
||||
deleteGraphics(...graphics: JlGraphic[]) {
|
||||
graphics.forEach((g) => this.doDeleteGraphics(g));
|
||||
deleteGraphics(...graphics: JlGraphic[]): JlGraphic[] {
|
||||
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);
|
||||
if (this.wsMsgBroker) {
|
||||
this.wsMsgBroker.close();
|
||||
if (!StompCli.hasAppMsgBroker()) {
|
||||
// 如果没有其他消息代理,关闭websocket Stomp客户端
|
||||
StompCli.close();
|
||||
}
|
||||
// if (!StompCli.hasAppMsgBroker()) {
|
||||
// // 如果没有其他消息代理,关闭websocket Stomp客户端
|
||||
// StompCli.close();
|
||||
// }
|
||||
}
|
||||
this.interactionPluginMap.forEach((plugin) => {
|
||||
plugin.pause();
|
||||
|
@ -37,6 +37,12 @@ export interface GraphicQueryStore {
|
||||
* @param v
|
||||
*/
|
||||
queryByIdOrCode(v: string): JlGraphic[];
|
||||
/**
|
||||
* 根据id或code及类型查询图形
|
||||
* @param v
|
||||
* @param type
|
||||
*/
|
||||
queryByIdOrCodeAndType(v: string, type: string): JlGraphic[];
|
||||
/**
|
||||
* 根据code和类型获取图形
|
||||
* @param code
|
||||
@ -69,6 +75,7 @@ export class GraphicStore implements GraphicQueryStore {
|
||||
this.store = new Map<string, JlGraphic>();
|
||||
this.relationManage = new RelationManage(app);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取所有图形对象
|
||||
*/
|
||||
@ -125,6 +132,15 @@ export class GraphicStore implements GraphicQueryStore {
|
||||
});
|
||||
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>(
|
||||
code: string,
|
||||
type: string
|
||||
|
@ -148,6 +148,23 @@ DisplayObject.prototype.getAllParentScaled =
|
||||
});
|
||||
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() {
|
||||
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> {
|
||||
readonly type: string;
|
||||
options: IGraphicTemplateOptions;
|
||||
|
||||
constructor(type: string) {
|
||||
constructor(type: string, options: IGraphicTemplateOptions) {
|
||||
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;
|
||||
// }
|
||||
|
||||
/**
|
||||
* 初始化一个新的图形对象
|
||||
*/
|
||||
|
70
src/jlgraphic/global.d.ts
vendored
70
src/jlgraphic/global.d.ts
vendored
@ -60,9 +60,28 @@ declare namespace GlobalMixins {
|
||||
_rotatable: boolean; // 是否可旋转
|
||||
rotatable: boolean;
|
||||
worldAngle: number; // 世界角度,(-180, 180]
|
||||
/**
|
||||
* 获取所有父级元素叠加缩放
|
||||
*/
|
||||
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; // 是否子元素
|
||||
isParent(obj: DisplayObject): boolean; // 是否父元素
|
||||
isAssistantAppend(): boolean; // 是否辅助附加图形
|
||||
@ -83,14 +102,45 @@ declare namespace GlobalMixins {
|
||||
isCanvas(): boolean; // 是否画布对象
|
||||
getViewport(): Viewport; // 获取视口
|
||||
getGraphicApp(): GraphicApp; // 获取图形app
|
||||
localToCanvasPoint(p: IPointData): PointType; // 图形本地坐标转为画布坐标
|
||||
localToCanvasPoints(...points: IPointData[]): PointType[]; // 批量转换
|
||||
canvasToLocalPoint(p: IPointData): PointType; // 画布坐标转为图形本地坐标
|
||||
canvasToLocalPoints(...points: IPointData[]): PointType[]; // 批量转换
|
||||
|
||||
localToScreenPoint(p: IPointData): PointType; // 本地坐标转为屏幕坐标
|
||||
localToScreenPoints(...points: IPointData[]): PointType[]; // 批量
|
||||
screenToLocalPoint(p: IPointData): PointType; // 屏幕坐标转为本地坐标
|
||||
/**
|
||||
* 图形本地坐标转为画布坐标
|
||||
* @param p 图形本地坐标
|
||||
*/
|
||||
localToCanvasPoint(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[]; // 批量
|
||||
|
||||
localBoundsToCanvasPoints(): PointType[]; // 本地包围框转为多边形点坐标
|
||||
|
@ -11,6 +11,8 @@ import {
|
||||
calculateFootPointFromPointToLine,
|
||||
calculateIntersectionPointOfCircleAndPoint,
|
||||
distance,
|
||||
distance2,
|
||||
isLineContainOther,
|
||||
linePoint,
|
||||
} from '../utils';
|
||||
import { VectorGraphic, VectorGraphicUtil } from './VectorGraphic';
|
||||
@ -19,12 +21,25 @@ import { VectorGraphic, VectorGraphicUtil } from './VectorGraphic';
|
||||
* 抽象可吸附位置
|
||||
*/
|
||||
export interface AbsorbablePosition extends Container {
|
||||
/**
|
||||
* 是否与另一个可吸附位置重叠(相似,但可能范围不同)
|
||||
* @param other
|
||||
*/
|
||||
isOverlapping(other: AbsorbablePosition): boolean;
|
||||
|
||||
/**
|
||||
* 与另一个相似的吸附位置比较范围大小
|
||||
* @param other
|
||||
* @returns >0此吸附范围大,<0另一个吸附范围大,=0两个吸附范围一样大
|
||||
*/
|
||||
compareTo(other: AbsorbablePosition): number;
|
||||
|
||||
/**
|
||||
* 尝试吸附图形对象
|
||||
* @param objs 图形对象列表
|
||||
* @returns 如果吸附成功,返回true,否则false
|
||||
*/
|
||||
tryAbsorb(...objs: DisplayObject[]): boolean;
|
||||
tryAbsorb(...objs: DisplayObject[]): void;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -57,6 +72,11 @@ export default class AbsorbablePoint
|
||||
absorbRange: number;
|
||||
scaledListenerOn = false;
|
||||
|
||||
/**
|
||||
*
|
||||
* @param point 画布坐标
|
||||
* @param absorbRange
|
||||
*/
|
||||
constructor(point: IPointData, absorbRange = 10) {
|
||||
super(AbsorbablePointGraphic.geometry);
|
||||
this._point = new Point(point.x, point.y);
|
||||
@ -65,19 +85,37 @@ export default class AbsorbablePoint
|
||||
this.interactive;
|
||||
VectorGraphicUtil.handle(this);
|
||||
}
|
||||
tryAbsorb(...objs: DisplayObject[]): boolean {
|
||||
for (let i = 0; i < objs.length; i++) {
|
||||
const obj = objs[i];
|
||||
if (
|
||||
distance(this._point.x, this._point.y, obj.position.x, obj.position.y) <
|
||||
this.absorbRange
|
||||
) {
|
||||
obj.position.copyFrom(this._point);
|
||||
return true;
|
||||
compareTo(other: AbsorbablePosition): number {
|
||||
if (other instanceof AbsorbablePoint) {
|
||||
return this.absorbRange - other.absorbRange;
|
||||
}
|
||||
throw new Error('非可吸附点');
|
||||
}
|
||||
isOverlapping(other: AbsorbablePosition): boolean {
|
||||
if (other instanceof AbsorbablePoint) {
|
||||
return (
|
||||
this._point.equals(other._point) &&
|
||||
this.absorbRange === other.absorbRange
|
||||
);
|
||||
}
|
||||
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() {
|
||||
const scaled = this.getAllParentScaled();
|
||||
const scale = Math.max(scaled.x, scaled.y);
|
||||
@ -94,6 +132,12 @@ export class AbsorbableLine extends Graphics implements AbsorbablePosition {
|
||||
absorbRange: number;
|
||||
_color = '#E77E0E';
|
||||
|
||||
/**
|
||||
*
|
||||
* @param p1 画布坐标
|
||||
* @param p2 画布坐标
|
||||
* @param absorbRange
|
||||
*/
|
||||
constructor(p1: IPointData, p2: IPointData, absorbRange = 20) {
|
||||
super();
|
||||
this.p1 = new Point(p1.x, p1.y);
|
||||
@ -101,6 +145,22 @@ export class AbsorbableLine extends Graphics implements AbsorbablePosition {
|
||||
this.absorbRange = absorbRange;
|
||||
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() {
|
||||
this.clear();
|
||||
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);
|
||||
}
|
||||
|
||||
tryAbsorb(...objs: DisplayObject[]): boolean {
|
||||
tryAbsorb(...objs: DisplayObject[]): void {
|
||||
for (let i = 0; i < objs.length; i++) {
|
||||
const obj = objs[i];
|
||||
const p = obj.position.clone();
|
||||
if (linePoint(this.p1, this.p2, p, this.absorbRange, true)) {
|
||||
const fp = calculateFootPointFromPointToLine(this.p1, this.p2, p);
|
||||
obj.position.copyFrom(fp);
|
||||
return true;
|
||||
const canvasPosition = obj.getPositionOnCanvas();
|
||||
if (linePoint(this.p1, this.p2, canvasPosition, this.absorbRange, true)) {
|
||||
const fp = calculateFootPointFromPointToLine(
|
||||
this.p1,
|
||||
this.p2,
|
||||
canvasPosition
|
||||
);
|
||||
obj.updatePositionByCanvasPosition(fp);
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@ -131,6 +193,12 @@ export class AbsorbableCircle extends Graphics implements AbsorbablePosition {
|
||||
radius: number;
|
||||
_color = '#E77E0E';
|
||||
|
||||
/**
|
||||
*
|
||||
* @param p 画布坐标
|
||||
* @param radius
|
||||
* @param absorbRange
|
||||
*/
|
||||
constructor(p: IPointData, radius: number, absorbRange = 10) {
|
||||
super();
|
||||
this.p0 = new Point(p.x, p.y);
|
||||
@ -138,6 +206,18 @@ export class AbsorbableCircle extends Graphics implements AbsorbablePosition {
|
||||
this.absorbRange = absorbRange;
|
||||
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() {
|
||||
this.clear();
|
||||
@ -145,14 +225,15 @@ export class AbsorbableCircle extends Graphics implements AbsorbablePosition {
|
||||
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++) {
|
||||
const obj = objs[i];
|
||||
const canvasPosition = obj.getPositionOnCanvas();
|
||||
const len = distance(
|
||||
this.p0.x,
|
||||
this.p0.y,
|
||||
obj.position.x,
|
||||
obj.position.y
|
||||
canvasPosition.x,
|
||||
canvasPosition.y
|
||||
);
|
||||
if (
|
||||
len > this.radius - this.absorbRange &&
|
||||
@ -162,12 +243,10 @@ export class AbsorbableCircle extends Graphics implements AbsorbablePosition {
|
||||
const p = calculateIntersectionPointOfCircleAndPoint(
|
||||
this.p0,
|
||||
this.radius,
|
||||
obj.position
|
||||
canvasPosition
|
||||
);
|
||||
obj.position.copyFrom(p);
|
||||
return true;
|
||||
obj.updatePositionByCanvasPosition(p);
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
@ -9,9 +9,20 @@ import type { GraphicApp } from '../app/JlGraphicApp';
|
||||
import { GraphicState } from '../core/JlGraphic';
|
||||
|
||||
export interface StompCliOption {
|
||||
wsUrl: string; // websocket url
|
||||
token: string; // 认证token
|
||||
reconnectDelay?: number; // 重连延时,默认3秒
|
||||
/**
|
||||
* websocket url地址
|
||||
*/
|
||||
wsUrl: string;
|
||||
/**
|
||||
* 认证token
|
||||
*/
|
||||
token?: string;
|
||||
/**
|
||||
* 认证失败处理
|
||||
* @returns
|
||||
*/
|
||||
onAuthenticationFailed?: () => void;
|
||||
reconnectDelay?: number; // 重连延时,默认3秒,设置为0不重连.
|
||||
heartbeatIncoming?: number; // 服务端过来的心跳间隔,默认30秒
|
||||
heartbeatOutgoing?: number; // 到服务端的心跳间隔,默认30秒
|
||||
}
|
||||
@ -25,28 +36,28 @@ const DefaultStompOption: StompCliOption = {
|
||||
};
|
||||
|
||||
export class StompCli {
|
||||
private static enabled = false;
|
||||
private static options: StompCliOption;
|
||||
private static client: StompClient;
|
||||
private static options: StompCliOption;
|
||||
private static appMsgBroker: AppWsMsgBroker[] = [];
|
||||
/**
|
||||
* key-订阅路径
|
||||
*/
|
||||
subscriptions: Map<string, AppStateSubscription> = new Map<
|
||||
string,
|
||||
AppStateSubscription
|
||||
>();
|
||||
private static connected = false;
|
||||
static new(options: StompCliOption) {
|
||||
if (StompCli.enabled) {
|
||||
// 以及启用
|
||||
if (StompCli.client) {
|
||||
// 已经创建
|
||||
return;
|
||||
// throw new Error('websocket 已连接,若确实需要重新连接,请先断开StompCli.close再重新StompCli.new')
|
||||
}
|
||||
StompCli.enabled = true;
|
||||
StompCli.options = Object.assign({}, DefaultStompOption, options);
|
||||
StompCli.client = new StompClient({
|
||||
brokerURL: StompCli.options.wsUrl,
|
||||
connectHeaders: {
|
||||
Authorization: StompCli.options.token,
|
||||
// Authorization: ''
|
||||
Authorization: StompCli.options.token ? StompCli.options.token : '',
|
||||
},
|
||||
// debug: (str) => {
|
||||
// console.log(str)
|
||||
// }
|
||||
reconnectDelay: StompCli.options.reconnectDelay,
|
||||
heartbeatIncoming: StompCli.options.heartbeatIncoming,
|
||||
heartbeatOutgoing: StompCli.options.heartbeatOutgoing,
|
||||
@ -55,43 +66,47 @@ export class StompCli {
|
||||
StompCli.client.onConnect = () => {
|
||||
// console.log('websocket连接(重连),重新订阅', StompCli.appMsgBroker.length)
|
||||
StompCli.connected = true;
|
||||
StompCli.emitConnectStateChangeEvent();
|
||||
StompCli.appMsgBroker.forEach((broker) => {
|
||||
broker.resubscribe();
|
||||
});
|
||||
};
|
||||
|
||||
StompCli.client.onStompError = (frame: Frame) => {
|
||||
console.error(
|
||||
'Stomp收到error消息,可能是认证失败(暂时没有判断具体错误类型,后需添加判断),关闭Stomp客户端',
|
||||
frame
|
||||
);
|
||||
const errMsg = frame.headers['message'];
|
||||
if (errMsg === '401') {
|
||||
console.warn('认证失败,断开WebSocket连接');
|
||||
StompCli.close();
|
||||
if (StompCli.options.onAuthenticationFailed) {
|
||||
StompCli.options.onAuthenticationFailed();
|
||||
}
|
||||
} else {
|
||||
console.error('收到Stomp错误消息', frame);
|
||||
}
|
||||
};
|
||||
|
||||
StompCli.client.onDisconnect = (frame: Frame) => {
|
||||
console.log('Stomp 断开连接', frame);
|
||||
StompCli.connected = false;
|
||||
// StompCli.appMsgBroker.forEach(broker => {
|
||||
// broker.close();
|
||||
// });
|
||||
StompCli.emitConnectStateChangeEvent();
|
||||
};
|
||||
StompCli.client.onWebSocketClose = (evt: CloseEvent) => {
|
||||
console.log('websocket 关闭', evt);
|
||||
StompCli.connected = false;
|
||||
StompCli.emitConnectStateChangeEvent();
|
||||
};
|
||||
// websocket错误处理
|
||||
StompCli.client.onWebSocketError = (err: Event) => {
|
||||
console.log('websocket错误', err);
|
||||
// StompCli.appMsgBroker.forEach(broker => {
|
||||
// broker.unsbuscribeAll();
|
||||
// });
|
||||
};
|
||||
|
||||
StompCli.client.activate();
|
||||
}
|
||||
|
||||
static isEnabled(): boolean {
|
||||
return StompCli.enabled;
|
||||
static emitConnectStateChangeEvent() {
|
||||
StompCli.appMsgBroker.forEach((broker) => {
|
||||
broker.app.emit('websocket-state-change', broker.app, StompCli.connected);
|
||||
});
|
||||
}
|
||||
|
||||
static isConnected(): boolean {
|
||||
@ -129,7 +144,6 @@ export class StompCli {
|
||||
* 关闭websocket连接
|
||||
*/
|
||||
static close() {
|
||||
StompCli.enabled = false;
|
||||
StompCli.connected = false;
|
||||
if (StompCli.client) {
|
||||
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状态订阅
|
||||
export class AppStateSubscription {
|
||||
export interface AppStateSubscription {
|
||||
/**
|
||||
* 订阅路径
|
||||
*/
|
||||
destination: string;
|
||||
messageConverter: MessageConverter;
|
||||
subscription?: StompSubscription; // 订阅成功对象,用于取消订阅
|
||||
constructor(
|
||||
destination: string,
|
||||
messageConverter: MessageConverter,
|
||||
subscription?: StompSubscription
|
||||
) {
|
||||
this.destination = destination;
|
||||
this.messageConverter = messageConverter;
|
||||
this.subscription = subscription;
|
||||
}
|
||||
/**
|
||||
* 图形状态消息转换
|
||||
*/
|
||||
messageConverter?: GraphicStateMessageConvert;
|
||||
/**
|
||||
* 订阅消息处理
|
||||
*/
|
||||
messageHandle?: SubscriptionMessageHandle;
|
||||
/**
|
||||
* 订阅成功对象,用于取消订阅
|
||||
* 非客户端使用
|
||||
*/
|
||||
subscription?: StompSubscription;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -175,8 +200,16 @@ export class AppWsMsgBroker {
|
||||
sub.subscription = StompCli.trySubscribe(
|
||||
sub.destination,
|
||||
(message: Message) => {
|
||||
if (sub.messageConverter) {
|
||||
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)
|
||||
|
@ -194,18 +194,17 @@ export class CommonMouseTool extends AppInteractionPlugin {
|
||||
const graphic = this.leftDownTarget.getGraphic();
|
||||
if (graphic) {
|
||||
const app = this.app;
|
||||
// console.log(this.leftDownTarget.isGraphic());
|
||||
// 图形选中
|
||||
if (!e.ctrlKey && !graphic.selected && graphic.selectable) {
|
||||
app.updateSelected(graphic);
|
||||
graphic.childEdit = false;
|
||||
this.graphicSelect = true;
|
||||
} else if (
|
||||
!e.ctrlKey &&
|
||||
graphic.selected &&
|
||||
graphic.childEdit &&
|
||||
this.leftDownTarget.isGraphicChild()
|
||||
} else if (!e.ctrlKey && graphic.selected && graphic.childEdit) {
|
||||
if (
|
||||
this.leftDownTarget.isGraphicChild() &&
|
||||
this.leftDownTarget.selectable
|
||||
) {
|
||||
if (this.leftDownTarget.selectable) {
|
||||
graphic.setChildSelected(this.leftDownTarget);
|
||||
} else {
|
||||
graphic.exitChildEdit();
|
||||
|
@ -2,7 +2,6 @@ import {
|
||||
Color,
|
||||
Container,
|
||||
DisplayObject,
|
||||
FederatedMouseEvent,
|
||||
Graphics,
|
||||
IDestroyOptions,
|
||||
IPointData,
|
||||
@ -10,12 +9,12 @@ import {
|
||||
} from 'pixi.js';
|
||||
import { JlGraphic } from '../core';
|
||||
import { DraggablePoint } from '../graphic';
|
||||
import { ContextMenu } from '../ui/ContextMenu';
|
||||
import { MenuItemOptions, MenuOptions } from '../ui/Menu';
|
||||
import {
|
||||
calculateDistanceFromPointToLine,
|
||||
calculateFootPointFromPointToLine,
|
||||
calculateLineSegmentingPoint,
|
||||
calculateMirrorPoint,
|
||||
assertBezierPoints,
|
||||
convertToBezierParams,
|
||||
distance2,
|
||||
linePoint,
|
||||
pointPolygon,
|
||||
@ -33,13 +32,13 @@ export abstract class GraphicEditPlugin<
|
||||
this.sortableChildren = true;
|
||||
this.graphic.on('transformstart', this.hideAll, 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 {
|
||||
this.graphic.off('transformstart', this.hideAll, 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);
|
||||
}
|
||||
|
||||
@ -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 {
|
||||
get linePoints(): IPointData[];
|
||||
set linePoints(points: IPointData[]);
|
||||
@ -103,10 +82,50 @@ export abstract class LineEditPlugin extends GraphicEditPlugin<ILineGraphic> {
|
||||
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(
|
||||
points: IPointData[],
|
||||
curve: boolean,
|
||||
p: IPointData
|
||||
p: IPointData,
|
||||
lineWidth: number
|
||||
): { start: number; end: number } {
|
||||
let start = 0;
|
||||
let end = 0;
|
||||
@ -124,17 +143,25 @@ export function getWaypointRangeIndex(
|
||||
}
|
||||
} else {
|
||||
// 贝塞尔曲线
|
||||
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], 1)) {
|
||||
start = i;
|
||||
end = i + 3;
|
||||
const bps = convertToBezierParams(points);
|
||||
for (let i = 0; i < bps.length; i++) {
|
||||
const bp = bps[i];
|
||||
if (pointPolygon(p, [bp.p1, bp.cp1, bp.cp2, bp.p2], lineWidth)) {
|
||||
start = i * 3;
|
||||
end = start + 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 };
|
||||
}
|
||||
@ -170,19 +197,7 @@ export class PolylineEditPlugin extends LineEditPlugin {
|
||||
for (let i = 0; i < cps.length; i++) {
|
||||
const p = cps[i];
|
||||
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', () => {
|
||||
const tlp = this.graphic.canvasToLocalPoint(dp.position);
|
||||
const cp = this.linePoints[i];
|
||||
@ -251,6 +266,21 @@ export function addLineWayPoint(
|
||||
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) {
|
||||
const c = i % 3;
|
||||
if (c !== 0) {
|
||||
@ -265,6 +295,7 @@ export function addBezierWayPoint(
|
||||
p: IPointData
|
||||
) {
|
||||
if (start === end) {
|
||||
console.error('添加贝塞尔曲线路径点开始结束点相等', start);
|
||||
throw new Error('开始结束点不能一致');
|
||||
}
|
||||
assertBezierWayPoint(start);
|
||||
@ -394,21 +425,6 @@ export class BezierCurveEditPlugin extends LineEditPlugin {
|
||||
this.drawAuxiliaryLine(line, p, np);
|
||||
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) => {
|
||||
const tlp = this.graphic.canvasToLocalPoint(dp.position);
|
||||
const cp = this.linePoints[i];
|
||||
|
@ -14,7 +14,7 @@ import {
|
||||
} from '.';
|
||||
import { GraphicApp } from '../app';
|
||||
import { JlGraphic } from '../core';
|
||||
import { AbsorbablePosition } from '../graphic';
|
||||
import { AbsorbablePosition, VectorText } from '../graphic';
|
||||
import { DraggablePoint } from '../graphic/DraggablePoint';
|
||||
import {
|
||||
angleToAxisx,
|
||||
@ -171,11 +171,40 @@ export class GraphicTransformPlugin extends InteractionPluginBase {
|
||||
this.app.canvas.addAssistantAppend(this.apContainer);
|
||||
app.on('options-update', (options) => {
|
||||
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) {
|
||||
return new GraphicTransformPlugin(app);
|
||||
}
|
||||
@ -187,7 +216,7 @@ export class GraphicTransformPlugin extends InteractionPluginBase {
|
||||
this.app.on('graphicselectedchange', this.onGraphicSelectedChange, this);
|
||||
this.app.on(
|
||||
'graphicchildselectedchange',
|
||||
this.onGraphicChildSelectedChange,
|
||||
this.onGraphicSelectedChange,
|
||||
this
|
||||
);
|
||||
}
|
||||
@ -198,7 +227,7 @@ export class GraphicTransformPlugin extends InteractionPluginBase {
|
||||
this.app.off('graphicselectedchange', this.onGraphicSelectedChange, this);
|
||||
this.app.off(
|
||||
'graphicchildselectedchange',
|
||||
this.onGraphicChildSelectedChange,
|
||||
this.onGraphicSelectedChange,
|
||||
this
|
||||
);
|
||||
}
|
||||
@ -255,9 +284,10 @@ export class GraphicTransformPlugin extends InteractionPluginBase {
|
||||
targets.forEach((target) => {
|
||||
if (target.shiftStartPoint) {
|
||||
target.shiftLastPoint = target.position.clone();
|
||||
const { dx, dy } = e.toTargetShiftLen(target.parent);
|
||||
target.position.set(
|
||||
target.shiftStartPoint.x + e.dsx,
|
||||
target.shiftStartPoint.y + e.dsy
|
||||
target.shiftStartPoint.x + dx,
|
||||
target.shiftStartPoint.y + dy
|
||||
);
|
||||
}
|
||||
});
|
||||
@ -265,9 +295,7 @@ export class GraphicTransformPlugin extends InteractionPluginBase {
|
||||
if (this.absorbablePositions) {
|
||||
for (let i = 0; i < this.absorbablePositions.length; i++) {
|
||||
const ap = this.absorbablePositions[i];
|
||||
if (ap.tryAbsorb(...targets)) {
|
||||
break;
|
||||
}
|
||||
ap.tryAbsorb(...targets);
|
||||
}
|
||||
}
|
||||
// 事件发布
|
||||
@ -335,7 +363,7 @@ export class GraphicTransformPlugin extends InteractionPluginBase {
|
||||
br.visible = false;
|
||||
}
|
||||
}
|
||||
if (g.scalable) {
|
||||
if (g.scalable || g.rotatable) {
|
||||
// 缩放点
|
||||
let sp = g.getAssistantAppend<TransformPoints>(TransformPoints.Name);
|
||||
if (!sp) {
|
||||
@ -350,19 +378,19 @@ export class GraphicTransformPlugin extends InteractionPluginBase {
|
||||
}
|
||||
}
|
||||
|
||||
onGraphicChildSelectedChange(child: DisplayObject, selected: boolean) {
|
||||
let br = child.getAssistantAppend<BoundsGraphic>(BoundsGraphic.Name);
|
||||
if (!br) {
|
||||
// 绘制辅助包围框
|
||||
br = new BoundsGraphic(child);
|
||||
}
|
||||
if (selected) {
|
||||
br.redraw();
|
||||
br.visible = true;
|
||||
} else {
|
||||
br.visible = false;
|
||||
}
|
||||
}
|
||||
// onGraphicChildSelectedChange(child: DisplayObject, selected: boolean) {
|
||||
// let br = child.getAssistantAppend<BoundsGraphic>(BoundsGraphic.Name);
|
||||
// if (!br) {
|
||||
// // 绘制辅助包围框
|
||||
// br = new BoundsGraphic(child);
|
||||
// }
|
||||
// if (selected) {
|
||||
// br.redraw();
|
||||
// br.visible = true;
|
||||
// } else {
|
||||
// br.visible = false;
|
||||
// }
|
||||
// }
|
||||
}
|
||||
|
||||
/**
|
||||
@ -419,6 +447,10 @@ export class TransformPoints extends Container {
|
||||
* 起始图形角度
|
||||
*/
|
||||
startAngle = 0;
|
||||
/**
|
||||
* 当前角度信息文本辅助
|
||||
*/
|
||||
angleAssistantText: VectorText;
|
||||
/**
|
||||
* 旋转角度步长
|
||||
*/
|
||||
@ -433,6 +465,10 @@ export class TransformPoints extends Container {
|
||||
this.obj = obj;
|
||||
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.name = TransformPoints.LeftTopName;
|
||||
@ -460,6 +496,19 @@ export class TransformPoints extends Container {
|
||||
this.addChild(this.lScalePoint);
|
||||
this.obj.on('transformstart', this.onObjTransformStart, 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.children.forEach((dp) => {
|
||||
dp.on('transformstart', this.onScaleDragStart, this);
|
||||
@ -520,6 +569,19 @@ export class TransformPoints extends Container {
|
||||
);
|
||||
this.obj.emit('transformstart', GraphicTransformEvent.rotate(this.obj));
|
||||
// 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]之间
|
||||
let angle = angleToAxisx(this.rotatePivot, de.target.position);
|
||||
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) {
|
||||
angle = angle - 360;
|
||||
}
|
||||
this.obj.angle = angle;
|
||||
this.updateAngleAssistantText(de);
|
||||
// this.obj.emit('rotatemove', this.obj);
|
||||
}
|
||||
/**
|
||||
@ -542,6 +606,7 @@ export class TransformPoints extends Container {
|
||||
*/
|
||||
onRotateEnd() {
|
||||
this.showAll();
|
||||
this.obj.getCanvas().removeAssistantAppends(this.angleAssistantText);
|
||||
this.rotateAngleStepKeyListeners.forEach((listener) =>
|
||||
this.obj.getGraphicApp().removeKeyboardListener(listener)
|
||||
);
|
||||
@ -789,6 +854,17 @@ export class BoundsGraphic extends Graphics {
|
||||
this.visible = false;
|
||||
this.obj.on('transformstart', this.onObjTransformStart, 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);
|
||||
graphic.addAssistantAppend(this);
|
||||
}
|
||||
|
@ -99,7 +99,7 @@ export class AppDragEvent {
|
||||
type: 'start' | 'move' | 'end';
|
||||
target: DisplayObject;
|
||||
original: FederatedPointerEvent;
|
||||
start: Point;
|
||||
start: Point; // 画布坐标
|
||||
constructor(
|
||||
app: GraphicApp,
|
||||
type: 'start' | 'move' | 'end',
|
||||
@ -146,6 +146,9 @@ export class AppDragEvent {
|
||||
return this.original.pointerType === 'touch';
|
||||
}
|
||||
|
||||
/**
|
||||
* 终点坐标(画布坐标)
|
||||
*/
|
||||
public get end(): Point {
|
||||
return this.app.toCanvasCoordinates(this.original.global);
|
||||
}
|
||||
@ -167,6 +170,15 @@ export class AppDragEvent {
|
||||
public get dsy(): number {
|
||||
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 };
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -67,15 +67,16 @@ export function recursiveFindChild(
|
||||
container: Container,
|
||||
finder: (child: DisplayObject) => boolean
|
||||
): DisplayObject | null {
|
||||
let result = null;
|
||||
for (let i = 0; i < container.children.length; i++) {
|
||||
const child = container.children[i];
|
||||
if (finder(child)) {
|
||||
return child;
|
||||
} else if (child.children) {
|
||||
return recursiveFindChild(child as Container, finder);
|
||||
result = recursiveFindChild(child as Container, finder);
|
||||
}
|
||||
}
|
||||
return null;
|
||||
return result;
|
||||
}
|
||||
|
||||
export interface BezierParam {
|
||||
@ -287,6 +288,31 @@ export function calculateLineMidpoint(p1: IPointData, p2: IPointData): Point {
|
||||
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
|
||||
@ -517,3 +543,153 @@ export function angleOfIncludedAngle(
|
||||
}
|
||||
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) },
|
||||
];
|
||||
});
|
||||
}
|
||||
|
@ -11,6 +11,19 @@
|
||||
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-toolbar>
|
||||
<q-resize-observer @resize="onHeaderResize" />
|
||||
@ -100,19 +113,27 @@
|
||||
<draw-properties></draw-properties>
|
||||
</q-drawer>
|
||||
|
||||
<q-page-container>
|
||||
<q-page-container id="app-page">
|
||||
<div id="draw-app-container"></div>
|
||||
</q-page-container>
|
||||
</q-layout>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { Base64 } from 'js-base64';
|
||||
import { useQuasar } from 'quasar';
|
||||
import DrawProperties from 'src/components/draw-app/DrawProperties.vue';
|
||||
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 { onMounted, onUnmounted, ref } from 'vue';
|
||||
import { Ref, onMounted, onUnmounted, ref } from 'vue';
|
||||
import { useRouter } from 'vue-router';
|
||||
|
||||
const $q = useQuasar();
|
||||
const drawStore = useDrawStore();
|
||||
const router = useRouter();
|
||||
|
||||
const leftDrawerOpen = ref(false);
|
||||
const rightDrawerOpen = ref(false);
|
||||
@ -127,13 +148,46 @@ function toggleRightDrawer() {
|
||||
|
||||
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(() => {
|
||||
console.log('绘制应用layout mounted');
|
||||
|
||||
const basic = Base64.decode('Zm9vOmJhcg==');
|
||||
console.log(basic);
|
||||
const dom = document.getElementById('draw-app-container');
|
||||
if (dom) {
|
||||
const drawApp = drawStore.initDrawApp(dom);
|
||||
drawApp.on('websocket-state-change', (app, connected) => {
|
||||
console.log('应用websocket状态变更', connected);
|
||||
});
|
||||
loadDrawDatas(drawApp);
|
||||
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
93
src/pages/UserLogin.vue
Normal 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>
|
97
src/pages/UserRegister.vue
Normal file
97
src/pages/UserRegister.vue
Normal 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>
|
@ -3,9 +3,22 @@ import { RouteRecordRaw } from 'vue-router';
|
||||
const routes: RouteRecordRaw[] = [
|
||||
{
|
||||
path: '/',
|
||||
name: 'home',
|
||||
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,
|
||||
// but you can also remove it
|
||||
{
|
||||
|
62
yarn.lock
62
yarn.lock
@ -183,6 +183,11 @@
|
||||
resolved "https://registry.npmmirror.com/@pixi/filter-noise/-/filter-noise-7.2.4.tgz#0586a00381ec0e63f6c00d49cd58b781eaf07f37"
|
||||
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":
|
||||
version "7.2.4"
|
||||
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"
|
||||
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:
|
||||
version "4.3.2"
|
||||
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"
|
||||
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:
|
||||
version "10.4.14"
|
||||
resolved "https://registry.npmmirror.com/autoprefixer/-/autoprefixer-10.4.14.tgz#e28d49902f8e759dd25b153264e862df2705f79d"
|
||||
@ -822,6 +837,15 @@ autoprefixer@^10.4.2:
|
||||
picocolors "^1.0.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:
|
||||
version "1.0.2"
|
||||
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"
|
||||
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:
|
||||
version "2.20.3"
|
||||
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"
|
||||
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:
|
||||
version "2.0.0"
|
||||
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"
|
||||
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:
|
||||
version "0.2.0"
|
||||
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"
|
||||
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"
|
||||
resolved "https://registry.npmmirror.com/mime-types/-/mime-types-2.1.35.tgz#381a871b62a734450660ae3deee44813f70d959a"
|
||||
integrity sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==
|
||||
@ -2681,6 +2731,11 @@ proxy-addr@~2.0.7:
|
||||
forwarded "0.2.0"
|
||||
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:
|
||||
version "1.3.2"
|
||||
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"
|
||||
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:
|
||||
version "1.14.1"
|
||||
resolved "https://registry.npmmirror.com/tslib/-/tslib-1.14.1.tgz#cf2d38bdc34a134bcaf1091c41f6619e2f672d00"
|
||||
|
Loading…
Reference in New Issue
Block a user